From 82a67bfb702e9df499b41ad76f852f47d5cd7d31 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 3 Nov 2017 15:55:17 +0800
Subject: [PATCH 001/949] [DLPack] Upgrade dlpack to 0.2 (#609)

---
 apps/howto_deploy/cpp_deploy.cc               |  4 +--
 dlpack                                        |  2 +-
 include/tvm/packed_func_ext.h                 |  4 +--
 include/tvm/runtime/packed_func.h             | 36 +++++++++----------
 jvm/native/src/main/native/jni_helper_func.h  |  6 ++--
 .../main/native/ml_dmlc_tvm_native_c_api.cc   |  4 +--
 src/api/api_lang.cc                           |  4 +--
 src/codegen/llvm/codegen_amdgpu.cc            |  2 +-
 src/codegen/llvm/codegen_llvm.cc              |  2 +-
 src/codegen/llvm/codegen_nvptx.cc             |  2 +-
 src/codegen/stack_vm/stack_vm.h               | 12 +++----
 src/codegen/verilog/vpi_device_api.cc         |  4 +--
 src/contrib/cblas/cblas.cc                    |  6 ++--
 src/contrib/cudnn/cudnn_utils.cc              |  6 ++--
 src/contrib/nnpack/convolution.cc             | 16 ++++-----
 src/contrib/nnpack/fully_connected.cc         | 12 +++----
 src/pass/lower_tvm_builtin.cc                 |  2 +-
 src/pass/make_api.cc                          |  7 ++--
 src/runtime/c_runtime_api.cc                  | 22 ++++++------
 src/runtime/cpu_device_api.cc                 |  2 +-
 src/runtime/cuda/cuda_device_api.cc           |  8 ++---
 src/runtime/graph/graph_runtime.cc            |  2 +-
 src/runtime/metal/metal_common.h              |  8 ++---
 src/runtime/metal/metal_device_api.mm         |  8 ++---
 src/runtime/opencl/opencl_common.h            |  6 ++--
 src/runtime/opencl/opencl_device_api.cc       |  6 ++--
 src/runtime/pack_args.h                       |  6 ++--
 src/runtime/rocm/rocm_device_api.cc           |  8 ++---
 src/runtime/rpc/rpc_device_api.cc             |  4 +--
 src/runtime/rpc/rpc_session.cc                | 30 ++++++++--------
 tests/cpp/packed_func_test.cc                 |  2 +-
 31 files changed, 122 insertions(+), 121 deletions(-)

diff --git a/apps/howto_deploy/cpp_deploy.cc b/apps/howto_deploy/cpp_deploy.cc
index e3a88550dc2b..1fd22e5f2b5f 100644
--- a/apps/howto_deploy/cpp_deploy.cc
+++ b/apps/howto_deploy/cpp_deploy.cc
@@ -28,10 +28,10 @@ void Verify(tvm::runtime::Module mod, std::string fname) {
   DLTensor* x;
   DLTensor* y;
   int ndim = 1;
-  int dtype_code = kFloat;
+  int dtype_code = kDLFloat;
   int dtype_bits = 32;
   int dtype_lanes = 1;
-  int device_type = kCPU;
+  int device_type = kDLCPU;
   int device_id = 0;
   int64_t shape[1] = {10};
   TVMArrayAlloc(shape, ndim, dtype_code, dtype_bits, dtype_lanes,
diff --git a/dlpack b/dlpack
index 9422e98f3f4d..10892ac964f1 160000
--- a/dlpack
+++ b/dlpack
@@ -1 +1 @@
-Subproject commit 9422e98f3f4dafc6bc3473cf8484543ad376aab6
+Subproject commit 10892ac964f1af7c81aae145cd3fab78bbccd297
diff --git a/include/tvm/packed_func_ext.h b/include/tvm/packed_func_ext.h
index 1f66232baacc..5242a057659b 100644
--- a/include/tvm/packed_func_ext.h
+++ b/include/tvm/packed_func_ext.h
@@ -105,10 +105,10 @@ inline TNodeRef TVMArgValue::AsNodeRef() const {
 
 inline TVMArgValue::operator Halide::Expr() const {
   if (type_code_ == kNull) return Expr();
-  if (type_code_ == kInt) {
+  if (type_code_ == kDLInt) {
     return Expr(static_cast<int>(value_.v_int64));
   }
-  if (type_code_ == kFloat) {
+  if (type_code_ == kDLFloat) {
     return Expr(static_cast<float>(value_.v_float64));
   }
   TVM_CHECK_TYPE_CODE(type_code_, kNodeHandle);
diff --git a/include/tvm/runtime/packed_func.h b/include/tvm/runtime/packed_func.h
index 41537760e46d..4c08ffa6077c 100644
--- a/include/tvm/runtime/packed_func.h
+++ b/include/tvm/runtime/packed_func.h
@@ -217,25 +217,25 @@ class ExtTypeVTable {
 class TVMPODValue_ {
  public:
   operator double() const {
-    TVM_CHECK_TYPE_CODE(type_code_, kFloat);
+    TVM_CHECK_TYPE_CODE(type_code_, kDLFloat);
     return value_.v_float64;
   }
   operator int64_t() const {
-    TVM_CHECK_TYPE_CODE(type_code_, kInt);
+    TVM_CHECK_TYPE_CODE(type_code_, kDLInt);
     return value_.v_int64;
   }
   operator uint64_t() const {
-    TVM_CHECK_TYPE_CODE(type_code_, kInt);
+    TVM_CHECK_TYPE_CODE(type_code_, kDLInt);
     return value_.v_int64;
   }
   operator int() const {
-    TVM_CHECK_TYPE_CODE(type_code_, kInt);
+    TVM_CHECK_TYPE_CODE(type_code_, kDLInt);
     CHECK_LE(value_.v_int64,
              std::numeric_limits<int>::max());
     return static_cast<int>(value_.v_int64);
   }
   operator bool() const {
-    TVM_CHECK_TYPE_CODE(type_code_, kInt);
+    TVM_CHECK_TYPE_CODE(type_code_, kDLInt);
     return value_.v_int64 != 0;
   }
   operator void*() const {
@@ -430,7 +430,7 @@ class TVMRetValue : public TVMPODValue_ {
     return *this;
   }
   TVMRetValue& operator=(double value) {
-    this->SwitchToPOD(kFloat);
+    this->SwitchToPOD(kDLFloat);
     value_.v_float64 = value;
     return *this;
   }
@@ -445,12 +445,12 @@ class TVMRetValue : public TVMPODValue_ {
     return *this;
   }
   TVMRetValue& operator=(int64_t value) {
-    this->SwitchToPOD(kInt);
+    this->SwitchToPOD(kDLInt);
     value_.v_int64 = value;
     return *this;
   }
   TVMRetValue& operator=(int value) {
-    this->SwitchToPOD(kInt);
+    this->SwitchToPOD(kDLInt);
     value_.v_int64 = value;
     return *this;
   }
@@ -460,7 +460,7 @@ class TVMRetValue : public TVMPODValue_ {
     return *this;
   }
   TVMRetValue& operator=(bool value) {
-    this->SwitchToPOD(kInt);
+    this->SwitchToPOD(kDLInt);
     value_.v_int64 = value;
     return *this;
   }
@@ -609,9 +609,9 @@ class TVMRetValue : public TVMPODValue_ {
 // implementation details
 inline const char* TypeCode2Str(int type_code) {
   switch (type_code) {
-    case kInt: return "int";
-    case kUInt: return "uint";
-    case kFloat: return "float";
+    case kDLInt: return "int";
+    case kDLUInt: return "uint";
+    case kDLFloat: return "float";
     case kStr: return "str";
     case kBytes: return "bytes";
     case kHandle: return "handle";
@@ -648,11 +648,11 @@ inline TVMType String2TVMType(std::string s) {
   t.bits = 32; t.lanes = 1;
   const char* scan;
   if (s.substr(0, 3) == "int") {
-    t.code = kInt;  scan = s.c_str() + 3;
+    t.code = kDLInt;  scan = s.c_str() + 3;
   } else if (s.substr(0, 4) == "uint") {
-    t.code = kUInt; scan = s.c_str() + 4;
+    t.code = kDLUInt; scan = s.c_str() + 4;
   } else if (s.substr(0, 5) == "float") {
-    t.code = kFloat; scan = s.c_str() + 5;
+    t.code = kDLFloat; scan = s.c_str() + 5;
   } else if (s.substr(0, 6) == "handle") {
     t.code = kHandle;
     t.bits = 64;  // handle uses 64 bit by default.
@@ -724,17 +724,17 @@ class TVMArgsSetter {
              std::is_integral<T>::value>::type>
   void operator()(size_t i, T value) const {
     values_[i].v_int64 = static_cast<int64_t>(value);
-    type_codes_[i] = kInt;
+    type_codes_[i] = kDLInt;
   }
   void operator()(size_t i, uint64_t value) const {
     values_[i].v_int64 = static_cast<int64_t>(value);
     CHECK_LE(value,
              static_cast<uint64_t>(std::numeric_limits<int64_t>::max()));
-    type_codes_[i] = kInt;
+    type_codes_[i] = kDLInt;
   }
   void operator()(size_t i, double value) const {
     values_[i].v_float64 = value;
-    type_codes_[i] = kFloat;
+    type_codes_[i] = kDLFloat;
   }
   void operator()(size_t i, std::nullptr_t value) const {
     values_[i].v_handle = value;
diff --git a/jvm/native/src/main/native/jni_helper_func.h b/jvm/native/src/main/native/jni_helper_func.h
index db4224012354..dc04f4191d1a 100644
--- a/jvm/native/src/main/native/jni_helper_func.h
+++ b/jvm/native/src/main/native/jni_helper_func.h
@@ -161,10 +161,10 @@ void fromJavaContext(JNIEnv *env, jobject jctx, TVMContext *ctx) {
 
 jobject tvmRetValueToJava(JNIEnv *env, TVMValue value, int tcode) {
   switch (tcode) {
-    case kUInt:
-    case kInt:
+    case kDLUInt:
+    case kDLInt:
       return newTVMValueLong(env, static_cast<jlong>(value.v_int64));
-    case kFloat:
+    case kDLFloat:
       return newTVMValueDouble(env, static_cast<jdouble>(value.v_float64));
     case kModuleHandle:
       return newModule(env, reinterpret_cast<jlong>(value.v_handle));
diff --git a/jvm/native/src/main/native/ml_dmlc_tvm_native_c_api.cc b/jvm/native/src/main/native/ml_dmlc_tvm_native_c_api.cc
index ea567f265f5b..615e4716d669 100644
--- a/jvm/native/src/main/native/ml_dmlc_tvm_native_c_api.cc
+++ b/jvm/native/src/main/native/ml_dmlc_tvm_native_c_api.cc
@@ -62,7 +62,7 @@ JNIEXPORT void JNICALL Java_ml_dmlc_tvm_LibInfo_tvmFuncPushArgLong(
   value.v_int64 = static_cast<int64_t>(arg);
   TVMFuncArgsThreadLocalEntry *e = TVMFuncArgsThreadLocalStore::Get();
   e->tvmFuncArgValues.push_back(value);
-  e->tvmFuncArgTypes.push_back(kInt);
+  e->tvmFuncArgTypes.push_back(kDLInt);
 }
 
 JNIEXPORT void JNICALL Java_ml_dmlc_tvm_LibInfo_tvmFuncPushArgDouble(
@@ -71,7 +71,7 @@ JNIEXPORT void JNICALL Java_ml_dmlc_tvm_LibInfo_tvmFuncPushArgDouble(
   value.v_float64 = static_cast<double>(arg);
   TVMFuncArgsThreadLocalEntry *e = TVMFuncArgsThreadLocalStore::Get();
   e->tvmFuncArgValues.push_back(value);
-  e->tvmFuncArgTypes.push_back(kFloat);
+  e->tvmFuncArgTypes.push_back(kDLFloat);
 }
 
 JNIEXPORT void JNICALL Java_ml_dmlc_tvm_LibInfo_tvmFuncPushArgString(
diff --git a/src/api/api_lang.cc b/src/api/api_lang.cc
index 50531d73010f..85b7d92c6a25 100644
--- a/src/api/api_lang.cc
+++ b/src/api/api_lang.cc
@@ -27,9 +27,9 @@ TVM_REGISTER_API("_max_value")
 
 TVM_REGISTER_API("_const")
 .set_body([](TVMArgs args,  TVMRetValue* ret) {
-    if (args[0].type_code() == kInt) {
+    if (args[0].type_code() == kDLInt) {
       *ret = make_const(args[1], args[0].operator int64_t());
-    } else if (args[0].type_code() == kFloat) {
+    } else if (args[0].type_code() == kDLFloat) {
       *ret = make_const(args[1], args[0].operator double());
     } else {
       LOG(FATAL) << "only accept int or float";
diff --git a/src/codegen/llvm/codegen_amdgpu.cc b/src/codegen/llvm/codegen_amdgpu.cc
index 9b02f2bc299f..fdd2ec2e38d2 100644
--- a/src/codegen/llvm/codegen_amdgpu.cc
+++ b/src/codegen/llvm/codegen_amdgpu.cc
@@ -133,7 +133,7 @@ class CodeGenAMDGPU : public CodeGenLLVM {
 
 inline int DetectROCMComputeVersion() {
   TVMContext tvm_ctx;
-  tvm_ctx.device_type = kROCM;
+  tvm_ctx.device_type = kDLROCM;
   tvm_ctx.device_id = 0;
   TVMRetValue val;
   tvm::runtime::DeviceAPI::Get(tvm_ctx)->GetAttr(
diff --git a/src/codegen/llvm/codegen_llvm.cc b/src/codegen/llvm/codegen_llvm.cc
index 2654dee0f7e5..a4bb815b1b93 100644
--- a/src/codegen/llvm/codegen_llvm.cc
+++ b/src/codegen/llvm/codegen_llvm.cc
@@ -242,7 +242,7 @@ llvm::Type* CodeGenLLVM::LLVMType(const Type& t) const {
     CHECK_EQ(t.lanes(), 1);
     return t_void_p_;
   }
-  llvm::Type* etype;
+  llvm::Type* etype = nullptr;
   if (t.is_int() || t.is_uint()) {
     etype = llvm::Type::getIntNTy(*ctx_, t.bits());
   } else if (t.is_float()) {
diff --git a/src/codegen/llvm/codegen_nvptx.cc b/src/codegen/llvm/codegen_nvptx.cc
index d147709ff1a2..c0002873d5fc 100644
--- a/src/codegen/llvm/codegen_nvptx.cc
+++ b/src/codegen/llvm/codegen_nvptx.cc
@@ -132,7 +132,7 @@ class CodeGenNVPTX : public CodeGenLLVM {
 
 inline int DetectCUDAComputeVersion() {
   TVMContext tvm_ctx;
-  tvm_ctx.device_type = kGPU;
+  tvm_ctx.device_type = kDLGPU;
   tvm_ctx.device_id = 0;
   TVMRetValue val;
   tvm::runtime::DeviceAPI::Get(tvm_ctx)->GetAttr(
diff --git a/src/codegen/stack_vm/stack_vm.h b/src/codegen/stack_vm/stack_vm.h
index 7663a4586274..54972d39a5df 100644
--- a/src/codegen/stack_vm/stack_vm.h
+++ b/src/codegen/stack_vm/stack_vm.h
@@ -340,16 +340,16 @@ class StackVM {
   static OpCode GetLoad(TVMType t) {
     CHECK_EQ(t.lanes, 1U);
     if (t.code == kHandle) return ARRAY_LOAD_HANDLE;
-    if (t.code == kInt) {
+    if (t.code == kDLInt) {
       switch (t.bits) {
         case 32 : return ARRAY_LOAD_INT32;
         case 64 : return ARRAY_LOAD_INT64;
       }
-    } else if (t.code == kUInt) {
+    } else if (t.code == kDLUInt) {
       switch (t.bits) {
         case 32 : return ARRAY_LOAD_UINT32;
       }
-    } else if (t.code == kFloat) {
+    } else if (t.code == kDLFloat) {
       switch (t.bits) {
         case 64 : return ARRAY_LOAD_FP64;
       }
@@ -365,16 +365,16 @@ class StackVM {
   static OpCode GetStore(TVMType t) {
     CHECK_EQ(t.lanes, 1U);
     if (t.code == kHandle) return ARRAY_STORE_HANDLE;
-    if (t.code == kInt) {
+    if (t.code == kDLInt) {
       switch (t.bits) {
         case 32 : return ARRAY_STORE_INT32;
         case 64 : return ARRAY_STORE_INT64;
       }
-    } else if (t.code == kUInt) {
+    } else if (t.code == kDLUInt) {
       switch (t.bits) {
         case 32 : return ARRAY_STORE_UINT32;
       }
-    } else if (t.code == kFloat) {
+    } else if (t.code == kDLFloat) {
       switch (t.bits) {
         case 64 : return ARRAY_STORE_FP64;
       }
diff --git a/src/codegen/verilog/vpi_device_api.cc b/src/codegen/verilog/vpi_device_api.cc
index 2977a4d45ce5..4e0e73eb427b 100644
--- a/src/codegen/verilog/vpi_device_api.cc
+++ b/src/codegen/verilog/vpi_device_api.cc
@@ -91,10 +91,10 @@ class VPIDeviceAPI final : public runtime::DeviceAPI {
                       TVMContext ctx_from,
                       TVMContext ctx_to,
                       TVMStreamHandle stream) final {
-    if (static_cast<int>(ctx_from.device_type) == kVPI) {
+    if (static_cast<int>(ctx_from.device_type) == kDLVPI) {
       from = RealAddr(static_cast<const char*>(from) + from_offset, size);
     }
-    if (static_cast<int>(ctx_to.device_type) == kVPI) {
+    if (static_cast<int>(ctx_to.device_type) == kDLVPI) {
       to = RealAddr(static_cast<char*>(to) + to_offset, size);
     }
     memcpy(to, from, size);
diff --git a/src/contrib/cblas/cblas.cc b/src/contrib/cblas/cblas.cc
index 01ef12f0d04d..9ce85ae4fd95 100644
--- a/src/contrib/cblas/cblas.cc
+++ b/src/contrib/cblas/cblas.cc
@@ -30,9 +30,9 @@ TVM_REGISTER_GLOBAL("tvm.contrib.cblas.matmul")
     CHECK(C->strides == nullptr);
     CHECK(B->strides == nullptr);
     CHECK(A->strides == nullptr);
-    CHECK(TypeMatch(A->dtype, kFloat, 32));
-    CHECK(TypeMatch(B->dtype, kFloat, 32));
-    CHECK(TypeMatch(C->dtype, kFloat, 32));
+    CHECK(TypeMatch(A->dtype, kDLFloat, 32));
+    CHECK(TypeMatch(B->dtype, kDLFloat, 32));
+    CHECK(TypeMatch(C->dtype, kDLFloat, 32));
     cblas_sgemm(CblasColMajor,
                 transb ? CblasTrans : CblasNoTrans,
                 transa ? CblasTrans : CblasNoTrans,
diff --git a/src/contrib/cudnn/cudnn_utils.cc b/src/contrib/cudnn/cudnn_utils.cc
index 5929e2ffbc0f..5ca558f7d8ba 100644
--- a/src/contrib/cudnn/cudnn_utils.cc
+++ b/src/contrib/cudnn/cudnn_utils.cc
@@ -13,17 +13,17 @@ namespace contrib {
 // CuDNN Data Type
 cudnnDataType_t CuDNNDataType::DLTypeToCuDNNType(const DLDataType &dtype) {
   switch (dtype.code) {
-      case kInt:
+      case kDLInt:
         if (dtype.bits == 8 && dtype.lanes == 1) return CUDNN_DATA_INT8;
         else if (dtype.bits == 32 && dtype.lanes == 1) return CUDNN_DATA_INT32;
         else if (dtype.bits == 8 && dtype.lanes == 4) return CUDNN_DATA_INT8x4;
         else
           LOG(FATAL) << "Unsupported type";
         break;
-      case kUInt:
+      case kDLUInt:
         LOG(FATAL) << "Unsupported type";
         break;
-      case kFloat:
+      case kDLFloat:
         if (dtype.bits == 32 && dtype.lanes == 1) return CUDNN_DATA_FLOAT;
         else if (dtype.bits == 64 && dtype.lanes == 1) return CUDNN_DATA_DOUBLE;
         else if (dtype.bits == 16 && dtype.lanes == 1) return CUDNN_DATA_HALF;
diff --git a/src/contrib/nnpack/convolution.cc b/src/contrib/nnpack/convolution.cc
index f7a8597f2de9..8480a100dfd7 100644
--- a/src/contrib/nnpack/convolution.cc
+++ b/src/contrib/nnpack/convolution.cc
@@ -44,10 +44,10 @@ TVM_REGISTER_GLOBAL("tvm.contrib.nnpack.convolution_inference")
     CHECK(kernel->strides == nullptr);
     CHECK(bias->strides == nullptr);
 
-    CHECK(TypeMatch(input->dtype, kFloat, 32));
-    CHECK(TypeMatch(kernel->dtype, kFloat, 32));
-    CHECK(TypeMatch(bias->dtype, kFloat, 32));
-    CHECK(TypeMatch(output->dtype, kFloat, 32));
+    CHECK(TypeMatch(input->dtype, kDLFloat, 32));
+    CHECK(TypeMatch(kernel->dtype, kDLFloat, 32));
+    CHECK(TypeMatch(bias->dtype, kDLFloat, 32));
+    CHECK(TypeMatch(output->dtype, kDLFloat, 32));
 
     nnp_convolution_inference(nnp_convolution_algorithm_auto,
                               nnp_convolution_transform_strategy_block_based,
@@ -102,10 +102,10 @@ TVM_REGISTER_GLOBAL("tvm.contrib.nnpack.convolution_output")
     CHECK(kernel->strides == nullptr);
     CHECK(bias->strides == nullptr);
 
-    CHECK(TypeMatch(input->dtype, kFloat, 32));
-    CHECK(TypeMatch(kernel->dtype, kFloat, 32));
-    CHECK(TypeMatch(bias->dtype, kFloat, 32));
-    CHECK(TypeMatch(output->dtype, kFloat, 32));
+    CHECK(TypeMatch(input->dtype, kDLFloat, 32));
+    CHECK(TypeMatch(kernel->dtype, kDLFloat, 32));
+    CHECK(TypeMatch(bias->dtype, kDLFloat, 32));
+    CHECK(TypeMatch(output->dtype, kDLFloat, 32));
 
     nnp_convolution_output(nnp_convolution_algorithm_auto,
                            batch_size,
diff --git a/src/contrib/nnpack/fully_connected.cc b/src/contrib/nnpack/fully_connected.cc
index 2ae60d61bd51..6793ecaa36a7 100644
--- a/src/contrib/nnpack/fully_connected.cc
+++ b/src/contrib/nnpack/fully_connected.cc
@@ -29,9 +29,9 @@ TVM_REGISTER_GLOBAL("tvm.contrib.nnpack.fully_connected_inference")
     CHECK(C->strides == nullptr);
     CHECK(B->strides == nullptr);
     CHECK(A->strides == nullptr);
-    CHECK(TypeMatch(A->dtype, kFloat, 32));
-    CHECK(TypeMatch(B->dtype, kFloat, 32));
-    CHECK(TypeMatch(C->dtype, kFloat, 32));
+    CHECK(TypeMatch(A->dtype, kDLFloat, 32));
+    CHECK(TypeMatch(B->dtype, kDLFloat, 32));
+    CHECK(TypeMatch(C->dtype, kDLFloat, 32));
 
     nnp_fully_connected_inference(B->shape[1],
                                   B->shape[0],
@@ -58,9 +58,9 @@ TVM_REGISTER_GLOBAL("tvm.contrib.nnpack.fully_connected_output")
     CHECK(C->strides == nullptr);
     CHECK(B->strides == nullptr);
     CHECK(A->strides == nullptr);
-    CHECK(TypeMatch(A->dtype, kFloat, 32));
-    CHECK(TypeMatch(B->dtype, kFloat, 32));
-    CHECK(TypeMatch(C->dtype, kFloat, 32));
+    CHECK(TypeMatch(A->dtype, kDLFloat, 32));
+    CHECK(TypeMatch(B->dtype, kDLFloat, 32));
+    CHECK(TypeMatch(C->dtype, kDLFloat, 32));
 
     nnp_fully_connected_output(A->shape[0],
                                B->shape[1],
diff --git a/src/pass/lower_tvm_builtin.cc b/src/pass/lower_tvm_builtin.cc
index a12f96c2282e..105d58b95829 100644
--- a/src/pass/lower_tvm_builtin.cc
+++ b/src/pass/lower_tvm_builtin.cc
@@ -72,7 +72,7 @@ class BuiltinLower : public IRMutator {
     int64_t nbytes = GetVectorBytes(op->type);
     if (device_type_.defined()) {
       if (arith::GetConst(device_type_, &dev_type)) {
-        if (dev_type == kCPU) {
+        if (dev_type == kDLCPU) {
           int32_t constant_size = op->constant_allocation_size();
           if (constant_size > 0 && constant_size * nbytes < runtime::kMaxStackAlloca) {
             return stmt;
diff --git a/src/pass/make_api.cc b/src/pass/make_api.cc
index 5f124e5690c6..6290f63e611d 100644
--- a/src/pass/make_api.cc
+++ b/src/pass/make_api.cc
@@ -107,12 +107,13 @@ LoweredFunc MakeAPI(Stmt body,
       } else if (t.is_int() || t.is_uint()) {
         std::ostringstream msg;
         msg << name << ": Expect arg[" << i << "] to be int";
-        seq_check.emplace_back(AssertStmt::make(tcode == kInt, msg.str(), nop));
+        seq_check.emplace_back(AssertStmt::make(tcode == kDLInt, msg.str(), nop));
       } else {
         CHECK(t.is_float());
         std::ostringstream msg;
         msg << name << ": Expect arg[" << i << "] to be float";
-        seq_check.emplace_back(AssertStmt::make(tcode == kFloat, msg.str(), nop));
+        seq_check.emplace_back(
+            AssertStmt::make(tcode == kDLFloat, msg.str(), nop));
       }
     } else {
       args.push_back(v_arg);
@@ -148,7 +149,7 @@ LoweredFunc MakeAPI(Stmt body,
     seq_check.push_back(AttrStmt::make(
         node, attr::device_context_type, device_type, nop));
     Stmt set_device = IfThenElse::make(
-        device_type != kCPU, Evaluate::make(Call::make(
+        device_type != kDLCPU, Evaluate::make(Call::make(
             Int(32), intrinsic::tvm_call_packed,
             {StringImm::make(runtime::symbol::tvm_set_device),
              device_type, device_id}, Call::Intrinsic)));
diff --git a/src/runtime/c_runtime_api.cc b/src/runtime/c_runtime_api.cc
index ce4a65dc79e2..8fc9a16aa851 100644
--- a/src/runtime/c_runtime_api.cc
+++ b/src/runtime/c_runtime_api.cc
@@ -25,12 +25,12 @@ namespace runtime {
  */
 inline std::string DeviceName(int type) {
   switch (type) {
-    case kCPU: return "cpu";
-    case kGPU: return "gpu";
-    case kOpenCL: return "opencl";
-    case kMetal: return "metal";
-    case kVPI: return "vpi";
-    case kROCM: return "rocm";
+    case kDLCPU: return "cpu";
+    case kDLGPU: return "gpu";
+    case kDLOpenCL: return "opencl";
+    case kDLMetal: return "metal";
+    case kDLVPI: return "vpi";
+    case kDLROCM: return "rocm";
     case kExtDev: return "ext_dev";
     default: LOG(FATAL) << "unknown type =" << type; return "Unknown";
   }
@@ -126,7 +126,7 @@ inline void TVMArrayFree_(TVMArray* arr) {
 
 inline void VerifyType(int dtype_code, int dtype_bits, int dtype_lanes) {
   CHECK_GE(dtype_lanes, 1);
-  if (dtype_code == kFloat) {
+  if (dtype_code == kDLFloat) {
     CHECK_EQ(dtype_bits % 32, 0);
   } else {
     CHECK_EQ(dtype_bits % 8, 0);
@@ -382,10 +382,10 @@ int TVMArrayCopyFromTo(TVMArrayHandle from,
   CHECK_EQ(from_size, to_size)
       << "TVMArrayCopyFromTo: The size must exactly match";
   TVMContext ctx = from->ctx;
-  if (ctx.device_type == kCPU) {
+  if (ctx.device_type == kDLCPU) {
     ctx = to->ctx;
   } else {
-    CHECK(to->ctx.device_type == kCPU ||
+    CHECK(to->ctx.device_type == kDLCPU ||
           to->ctx.device_type == from->ctx.device_type)
         << "Can not copy across different ctx types directly";
   }
@@ -401,7 +401,7 @@ int TVMArrayCopyFromBytes(TVMArrayHandle handle,
                           size_t nbytes) {
   API_BEGIN();
   TVMContext cpu_ctx;
-  cpu_ctx.device_type = kCPU;
+  cpu_ctx.device_type = kDLCPU;
   cpu_ctx.device_id = 0;
   size_t arr_size = GetDataSize(handle);
   CHECK_EQ(arr_size, nbytes)
@@ -418,7 +418,7 @@ int TVMArrayCopyToBytes(TVMArrayHandle handle,
                         size_t nbytes) {
   API_BEGIN();
   TVMContext cpu_ctx;
-  cpu_ctx.device_type = kCPU;
+  cpu_ctx.device_type = kDLCPU;
   cpu_ctx.device_id = 0;
   size_t arr_size = GetDataSize(handle);
   CHECK_EQ(arr_size, nbytes)
diff --git a/src/runtime/cpu_device_api.cc b/src/runtime/cpu_device_api.cc
index 78947cd62cd5..1b2009e98e7f 100644
--- a/src/runtime/cpu_device_api.cc
+++ b/src/runtime/cpu_device_api.cc
@@ -68,7 +68,7 @@ class CPUDeviceAPI final : public DeviceAPI {
 
 struct CPUWorkspacePool : public WorkspacePool {
   CPUWorkspacePool() :
-      WorkspacePool(kCPU, CPUDeviceAPI::Global()) {}
+      WorkspacePool(kDLCPU, CPUDeviceAPI::Global()) {}
 };
 
 void* CPUDeviceAPI::AllocWorkspace(TVMContext ctx, size_t size) {
diff --git a/src/runtime/cuda/cuda_device_api.cc b/src/runtime/cuda/cuda_device_api.cc
index 340b286d87ca..fd2c54ffd58d 100644
--- a/src/runtime/cuda/cuda_device_api.cc
+++ b/src/runtime/cuda/cuda_device_api.cc
@@ -79,7 +79,7 @@ class CUDADeviceAPI final : public DeviceAPI {
     cudaStream_t cu_stream = static_cast<cudaStream_t>(stream);
     from = static_cast<const char*>(from) + from_offset;
     to = static_cast<char*>(to) + to_offset;
-    if (ctx_from.device_type == kGPU && ctx_to.device_type == kGPU) {
+    if (ctx_from.device_type == kDLGPU && ctx_to.device_type == kDLGPU) {
       CUDA_CALL(cudaSetDevice(ctx_from.device_id));
       if (ctx_from.device_id == ctx_to.device_id) {
         GPUCopy(from, to, size, cudaMemcpyDeviceToDevice, cu_stream);
@@ -88,10 +88,10 @@ class CUDADeviceAPI final : public DeviceAPI {
                             from, ctx_from.device_id,
                             size, cu_stream);
       }
-    } else if (ctx_from.device_type == kGPU && ctx_to.device_type == kCPU) {
+    } else if (ctx_from.device_type == kDLGPU && ctx_to.device_type == kDLCPU) {
       CUDA_CALL(cudaSetDevice(ctx_from.device_id));
       GPUCopy(from, to, size, cudaMemcpyDeviceToHost, cu_stream);
-    } else if (ctx_from.device_type == kCPU && ctx_to.device_type == kGPU) {
+    } else if (ctx_from.device_type == kDLCPU && ctx_to.device_type == kDLGPU) {
       CUDA_CALL(cudaSetDevice(ctx_to.device_id));
       GPUCopy(from, to, size, cudaMemcpyHostToDevice, cu_stream);
     } else {
@@ -140,7 +140,7 @@ class CUDADeviceAPI final : public DeviceAPI {
 typedef dmlc::ThreadLocalStore<CUDAThreadEntry> CUDAThreadStore;
 
 CUDAThreadEntry::CUDAThreadEntry()
-    : pool(kGPU, CUDADeviceAPI::Global()) {
+    : pool(kDLGPU, CUDADeviceAPI::Global()) {
 }
 
 CUDAThreadEntry* CUDAThreadEntry::ThreadLocal() {
diff --git a/src/runtime/graph/graph_runtime.cc b/src/runtime/graph/graph_runtime.cc
index 2cf6a1fb1330..d3f849d743dc 100644
--- a/src/runtime/graph/graph_runtime.cc
+++ b/src/runtime/graph/graph_runtime.cc
@@ -462,7 +462,7 @@ void GraphRuntime::SetupStorage() {
     int64_t shape[] = {static_cast<int64_t>(pool_entry_bytes[i] + 3) / 4};
     DLTensor* tensor;
     TVM_CCALL(TVMArrayAlloc(
-        shape, 1, kFloat, 32, 1, ctx_.device_type, ctx_.device_id, &tensor));
+        shape, 1, kDLFloat, 32, 1, ctx_.device_type, ctx_.device_id, &tensor));
     storage_pool_.push_back(tensor);
   }
   // Assign the pooled entries.
diff --git a/src/runtime/metal/metal_common.h b/src/runtime/metal/metal_common.h
index 35f78db4e814..d7980e64c9a9 100644
--- a/src/runtime/metal/metal_common.h
+++ b/src/runtime/metal/metal_common.h
@@ -45,14 +45,14 @@ class MetalWorkspace final : public DeviceAPI {
   ~MetalWorkspace();
   // Get command queue for given context.
   id<MTLCommandQueue> GetCommandQueue(TVMContext ctx) {
-    CHECK_EQ(ctx.device_type, kMetal);
+    CHECK_EQ(ctx.device_type, kDLMetal);
     CHECK(ctx.device_id >= 0  && static_cast<size_t>(ctx.device_id) < queues.size())
         << "Invalid Metal device_id=" << ctx.device_id;
     return queues[ctx.device_id];
   }
   // Get device for given context
   id<MTLDevice> GetDevice(TVMContext ctx) {
-    CHECK_EQ(ctx.device_type, kMetal);
+    CHECK_EQ(ctx.device_type, kDLMetal);
     CHECK(ctx.device_id >= 0  && static_cast<size_t>(ctx.device_id) < devices.size())
         << "Invalid Metal device_id=" << ctx.device_id;
     return devices[ctx.device_id];
@@ -91,9 +91,9 @@ class MetalThreadEntry {
   WorkspacePool pool;
   // constructor
   MetalThreadEntry()
-      : pool(static_cast<DLDeviceType>(kMetal), MetalWorkspace::Global()) {
+      : pool(static_cast<DLDeviceType>(kDLMetal), MetalWorkspace::Global()) {
     context.device_id = 0;
-    context.device_type = static_cast<DLDeviceType>(kMetal);
+    context.device_type = static_cast<DLDeviceType>(kDLMetal);
   }
   ~MetalThreadEntry();
   // Get temp buffer with at least size under ctx.
diff --git a/src/runtime/metal/metal_device_api.mm b/src/runtime/metal/metal_device_api.mm
index 4af274da98a3..f66d5b51e64a 100644
--- a/src/runtime/metal/metal_device_api.mm
+++ b/src/runtime/metal/metal_device_api.mm
@@ -150,13 +150,13 @@ int GetWarpSize(id<MTLDevice> dev) {
   this->Init();
   CHECK(stream == nullptr);
   TVMContext ctx = ctx_from;
-  if (ctx_from.device_type == kCPU) ctx = ctx_to;
+  if (ctx_from.device_type == kDLCPU) ctx = ctx_to;
   id<MTLCommandQueue> queue = GetCommandQueue(ctx);
   id<MTLCommandBuffer> cb = [queue commandBuffer];
   int from_dev_type = static_cast<int>(ctx_from.device_type);
   int to_dev_type = static_cast<int>(ctx_to.device_type);
 
-  if (from_dev_type == kMetal && to_dev_type == kMetal) {
+  if (from_dev_type == kDLMetal && to_dev_type == kDLMetal) {
     CHECK_EQ(ctx_from.device_id, ctx_to.device_id)
         << "Metal disallow cross device copy.";
     id<MTLBlitCommandEncoder> encoder = [cb blitCommandEncoder];
@@ -167,7 +167,7 @@ int GetWarpSize(id<MTLDevice> dev) {
              size:size];
     [encoder endEncoding];
     [cb commit];
-  } else if (from_dev_type == kMetal && to_dev_type == kCPU) {
+  } else if (from_dev_type == kDLMetal && to_dev_type == kDLCPU) {
     // copy to a local buffer before get into global buffer.
     id<MTLBuffer> from_buf = (__bridge id<MTLBuffer>)(from);
     if (from_buf.storageMode != MTLStorageModeShared) {
@@ -190,7 +190,7 @@ int GetWarpSize(id<MTLDevice> dev) {
              static_cast<char*>([from_buf contents]) + from_offset,
              size);
     }
-  } else if (from_dev_type == kCPU && to_dev_type == kMetal) {
+  } else if (from_dev_type == kDLCPU && to_dev_type == kDLMetal) {
     id<MTLBuffer> to_buf = (__bridge id<MTLBuffer>)(to);
     if (to_buf.storageMode != MTLStorageModeShared) {
       id<MTLBuffer> temp = MetalThreadEntry::ThreadLocal()
diff --git a/src/runtime/opencl/opencl_common.h b/src/runtime/opencl/opencl_common.h
index ccbe155e6204..e990aeba6a3e 100644
--- a/src/runtime/opencl/opencl_common.h
+++ b/src/runtime/opencl/opencl_common.h
@@ -133,7 +133,7 @@ class OpenCLWorkspace final : public DeviceAPI {
   void Init();
   // get the queue of the context
   cl_command_queue GetQueue(TVMContext ctx) {
-    CHECK_EQ(ctx.device_type, kOpenCL);
+    CHECK_EQ(ctx.device_type, kDLOpenCL);
     this->Init();
     CHECK(ctx.device_id >= 0  && static_cast<size_t>(ctx.device_id) < queues.size())
         << "Invalid OpenCL device_id=" << ctx.device_id;
@@ -178,9 +178,9 @@ class OpenCLThreadEntry {
   WorkspacePool pool;
   // constructor
   OpenCLThreadEntry()
-      : pool(kOpenCL, OpenCLWorkspace::Global()) {
+      : pool(kDLOpenCL, OpenCLWorkspace::Global()) {
     context.device_id = 0;
-    context.device_type = kOpenCL;
+    context.device_type = kDLOpenCL;
   }
   // get the global workspace
   static OpenCLThreadEntry* ThreadLocal();
diff --git a/src/runtime/opencl/opencl_device_api.cc b/src/runtime/opencl/opencl_device_api.cc
index f70207ebe881..e95fddaa867c 100644
--- a/src/runtime/opencl/opencl_device_api.cc
+++ b/src/runtime/opencl/opencl_device_api.cc
@@ -76,13 +76,13 @@ void OpenCLWorkspace::CopyDataFromTo(const void* from,
                                      TVMStreamHandle stream) {
   this->Init();
   CHECK(stream == nullptr);
-  if (ctx_from.device_type == kOpenCL && ctx_to.device_type == kOpenCL) {
+  if (ctx_from.device_type == kDLOpenCL && ctx_to.device_type == kDLOpenCL) {
     OPENCL_CALL(clEnqueueCopyBuffer(
         this->GetQueue(ctx_to),
         static_cast<cl_mem>((void*)from),  // NOLINT(*)
         static_cast<cl_mem>(to),
         from_offset, to_offset, size, 0, nullptr, nullptr));
-  } else if (ctx_from.device_type == kOpenCL && ctx_to.device_type == kCPU) {
+  } else if (ctx_from.device_type == kDLOpenCL && ctx_to.device_type == kDLCPU) {
     OPENCL_CALL(clEnqueueReadBuffer(
         this->GetQueue(ctx_from),
         static_cast<cl_mem>((void*)from),  // NOLINT(*)
@@ -90,7 +90,7 @@ void OpenCLWorkspace::CopyDataFromTo(const void* from,
         static_cast<char*>(to) + to_offset,
         0, nullptr, nullptr));
     OPENCL_CALL(clFinish(this->GetQueue(ctx_from)));
-  } else if (ctx_from.device_type == kCPU && ctx_to.device_type == kOpenCL) {
+  } else if (ctx_from.device_type == kDLCPU && ctx_to.device_type == kDLOpenCL) {
     OPENCL_CALL(clEnqueueWriteBuffer(
         this->GetQueue(ctx_to),
         static_cast<cl_mem>(to),
diff --git a/src/runtime/pack_args.h b/src/runtime/pack_args.h
index 3cb214161f22..0a00e79f07df 100644
--- a/src/runtime/pack_args.h
+++ b/src/runtime/pack_args.h
@@ -104,12 +104,12 @@ enum ArgConvertCode {
 inline ArgConvertCode GetArgConvertCode(TVMType t) {
   CHECK_EQ(t.lanes, 1U)
       << "Cannot pass vector type argument to devic function for now";
-  if (t.code == kInt) {
+  if (t.code == kDLInt) {
     if (t.bits == 64U) return INT64_TO_INT64;
     if (t.bits == 32U) return INT64_TO_INT32;
-  } else if (t.code == kUInt) {
+  } else if (t.code == kDLUInt) {
     if (t.bits == 32U) return INT64_TO_UINT32;
-  } else if (t.code == kFloat) {
+  } else if (t.code == kDLFloat) {
     if (t.bits == 64U) return FLOAT64_TO_FLOAT64;
     if (t.bits == 32U) return FLOAT64_TO_FLOAT32;
   } else if (t.code == kHandle) {
diff --git a/src/runtime/rocm/rocm_device_api.cc b/src/runtime/rocm/rocm_device_api.cc
index d7b4eabf01d4..1e6154163b35 100644
--- a/src/runtime/rocm/rocm_device_api.cc
+++ b/src/runtime/rocm/rocm_device_api.cc
@@ -77,7 +77,7 @@ class ROCMDeviceAPI final : public DeviceAPI {
     hipStream_t hip_stream = static_cast<hipStream_t>(stream);
     from = static_cast<const char*>(from) + from_offset;
     to = static_cast<char*>(to) + to_offset;
-    if (ctx_from.device_type == kROCM && ctx_to.device_type == kROCM) {
+    if (ctx_from.device_type == kDLROCM && ctx_to.device_type == kDLROCM) {
       ROCM_CALL(hipSetDevice(ctx_from.device_id));
       if (ctx_from.device_id == ctx_to.device_id) {
         GPUCopy(from, to, size, hipMemcpyDeviceToDevice, hip_stream);
@@ -86,10 +86,10 @@ class ROCMDeviceAPI final : public DeviceAPI {
                             from, ctx_from.device_id,
                             size, hip_stream);
       }
-    } else if (ctx_from.device_type == kROCM && ctx_to.device_type == kCPU) {
+    } else if (ctx_from.device_type == kDLROCM && ctx_to.device_type == kDLCPU) {
       ROCM_CALL(hipSetDevice(ctx_from.device_id));
       GPUCopy(from, to, size, hipMemcpyDeviceToHost, hip_stream);
-    } else if (ctx_from.device_type == kCPU && ctx_to.device_type == kROCM) {
+    } else if (ctx_from.device_type == kDLCPU && ctx_to.device_type == kDLROCM) {
       ROCM_CALL(hipSetDevice(ctx_to.device_id));
       GPUCopy(from, to, size, hipMemcpyHostToDevice, hip_stream);
     } else {
@@ -138,7 +138,7 @@ class ROCMDeviceAPI final : public DeviceAPI {
 typedef dmlc::ThreadLocalStore<ROCMThreadEntry> ROCMThreadStore;
 
 ROCMThreadEntry::ROCMThreadEntry()
-    : pool(kROCM, ROCMDeviceAPI::Global()) {
+    : pool(kDLROCM, ROCMDeviceAPI::Global()) {
 }
 
 ROCMThreadEntry* ROCMThreadEntry::ThreadLocal() {
diff --git a/src/runtime/rpc/rpc_device_api.cc b/src/runtime/rpc/rpc_device_api.cc
index e8cc5b94ad52..7674fa3e2334 100644
--- a/src/runtime/rpc/rpc_device_api.cc
+++ b/src/runtime/rpc/rpc_device_api.cc
@@ -55,12 +55,12 @@ class RPCDeviceAPI final : public DeviceAPI {
           static_cast<const RemoteSpace*>(to)->data, to_offset,
           size,  ctx_from, ctx_to, stream);
     } else if (from_dev_type > kRPCSessMask &&
-               to_dev_type == kCPU) {
+               to_dev_type == kDLCPU) {
       GetSess(ctx_from)->CopyFromRemote(
           static_cast<const RemoteSpace*>(from)->data, from_offset,
           to, to_offset, size,
           ctx_from);
-    } else if (from_dev_type == kCPU &&
+    } else if (from_dev_type == kDLCPU &&
                to_dev_type > kRPCSessMask) {
       GetSess(ctx_to)->CopyToRemote(
           (void*)from, from_offset,  // NOLINT(*)
diff --git a/src/runtime/rpc/rpc_session.cc b/src/runtime/rpc/rpc_session.cc
index 323faf4a9b1c..0fa021918ed2 100644
--- a/src/runtime/rpc/rpc_session.cc
+++ b/src/runtime/rpc/rpc_session.cc
@@ -162,9 +162,9 @@ class RPCSession::EventHandler {
       int tcode = type_codes[i];
       TVMValue value = arg_values[i];
       switch (tcode) {
-        case kInt:
-        case kUInt:
-        case kFloat:
+        case kDLInt:
+        case kDLUInt:
+        case kDLFloat:
         case kTVMType: {
           writer_->Write(&value, sizeof(TVMValue));
           break;
@@ -315,9 +315,9 @@ class RPCSession::EventHandler {
     int tcode = arg_buf_->tcode[arg_index_];
     static_assert(sizeof(TVMValue) == sizeof(uint64_t), "invariant");
     switch (tcode) {
-      case kInt:
-      case kUInt:
-      case kFloat:
+      case kDLInt:
+      case kDLUInt:
+      case kDLFloat:
       case kTVMType:
       case kHandle:
       case kStr:
@@ -352,9 +352,9 @@ class RPCSession::EventHandler {
     TVMValue& value = arg_buf_->value[arg_index_];
     if (arg_recv_stage_ == 0) {
       switch (tcode) {
-        case kInt:
-        case kUInt:
-        case kFloat:
+        case kDLInt:
+        case kDLUInt:
+        case kDLFloat:
         case kTVMType:
         case kTVMContext: {
           this->Read(&value, sizeof(TVMValue));
@@ -484,7 +484,7 @@ class RPCSession::EventHandler {
     this->Read(&offset, sizeof(offset));
     this->Read(&size, sizeof(size));
     this->Read(&ctx, sizeof(ctx));
-    if (ctx.device_type == kCPU) {
+    if (ctx.device_type == kDLCPU) {
       RPCCode code = RPCCode::kCopyAck;
       writer_->Write(&code, sizeof(code));
       writer_->Write(reinterpret_cast<char*>(handle) + offset, size);
@@ -492,7 +492,7 @@ class RPCSession::EventHandler {
       temp_data_.resize(size + 1);
       try {
         TVMContext cpu_ctx;
-        cpu_ctx.device_type = kCPU;
+        cpu_ctx.device_type = kDLCPU;
         cpu_ctx.device_id = 0;
         DeviceAPI::Get(ctx)->CopyDataFromTo(
             reinterpret_cast<void*>(handle), offset,
@@ -531,7 +531,7 @@ class RPCSession::EventHandler {
       int ret_tcode = kNull;
       RPCCode code = RPCCode::kReturn;
       std::string errmsg;
-      if (copy_ctx_.device_type == kCPU) {
+      if (copy_ctx_.device_type == kDLCPU) {
         this->Read(
             reinterpret_cast<char*>(copy_handle_) + copy_offset_, copy_size_);
       } else {
@@ -539,7 +539,7 @@ class RPCSession::EventHandler {
         this->Read(&temp_data_[0], copy_size_);
         try {
           TVMContext cpu_ctx;
-          cpu_ctx.device_type = kCPU;
+          cpu_ctx.device_type = kDLCPU;
           cpu_ctx.device_id = 0;
           DeviceAPI::Get(copy_ctx_)->CopyDataFromTo(
               temp_data_.data(), 0,
@@ -915,10 +915,10 @@ void RPCCopyAmongRemote(TVMArgs args, TVMRetValue *rv) {
   TVMContext ctx_to = args[6];
   TVMStreamHandle stream = args[7];
   TVMContext ctx = ctx_from;
-  if (ctx.device_type == kCPU) {
+  if (ctx.device_type == kDLCPU) {
     ctx = ctx_to;
   } else {
-    CHECK(ctx_to.device_type == kCPU ||
+    CHECK(ctx_to.device_type == kDLCPU ||
           ctx_to.device_type == ctx_from.device_type)
         << "Can not copy across different ctx types directly";
   }
diff --git a/tests/cpp/packed_func_test.cc b/tests/cpp/packed_func_test.cc
index 015d95c90adb..00e428f258a9 100644
--- a/tests/cpp/packed_func_test.cc
+++ b/tests/cpp/packed_func_test.cc
@@ -14,7 +14,7 @@ TEST(PackedFunc, Basic) {
   Var v = PackedFunc([&](TVMArgs args, TVMRetValue* rv) {
       CHECK(args.num_args == 3);
       CHECK(args.values[0].v_float64 == 1.0);
-      CHECK(args.type_codes[0] == kFloat);
+      CHECK(args.type_codes[0] == kDLFloat);
       CHECK(args.values[1].v_handle == &a);
       CHECK(args.type_codes[1] == kArrayHandle);
       CHECK(args.values[2].v_handle == &x);

From 0fa4d97588dcd1d23b732cbcd08eec98943f2e4a Mon Sep 17 00:00:00 2001
From: Yuwei Hu <huyuwei1995@gmail.com>
Date: Mon, 6 Nov 2017 12:08:22 +0800
Subject: [PATCH 002/949] [TOPI] fix weight layout in conv2d_transpose (#616)

---
 topi/python/topi/nn/conv2d_transpose.py          |  6 +++---
 .../topi/testing/conv2d_transpose_nchw_python.py | 16 ++++++++++++----
 .../python/test_topi_conv2d_transpose_nchw.py    |  2 +-
 3 files changed, 16 insertions(+), 8 deletions(-)

diff --git a/topi/python/topi/nn/conv2d_transpose.py b/topi/python/topi/nn/conv2d_transpose.py
index 33f66d95c798..c783360b8808 100644
--- a/topi/python/topi/nn/conv2d_transpose.py
+++ b/topi/python/topi/nn/conv2d_transpose.py
@@ -18,7 +18,7 @@ def conv2d_transpose_nchw(Input, Filter, strides, padding):
         4-D with shape [batch, in_channel, in_height, in_width]
 
     Filter : tvm.Tensor
-        4-D with shape [num_filter, in_channel, filter_height, filter_width]
+        4-D with shape [in_channel, num_filter, filter_height, filter_width]
 
     strides : tuple of two ints
         The spatial stride along height and width
@@ -32,7 +32,7 @@ def conv2d_transpose_nchw(Input, Filter, strides, padding):
         4-D with shape [batch, out_channel, out_height, out_width]
     """
     batch, in_c, in_h, in_w = Input.shape
-    out_c, _, filter_h, filter_w = Filter.shape
+    _, out_c, filter_h, filter_w = Filter.shape
     stride_h, stride_w = strides
     # dilate stage
     DilatedInput = dilate(Input, [1, 1, stride_h, stride_w], name='DilatedInput')
@@ -57,7 +57,7 @@ def conv2d_transpose_nchw(Input, Filter, strides, padding):
     Output = tvm.compute(
         (batch, out_c, out_h, out_w),
         lambda b, c, h, w: tvm.sum(
-            PaddedInput[b, dc, h+dh, w+dw] * Filter[c, dc, filter_h-1-dh, filter_w-1-dw],
+            PaddedInput[b, dc, h+dh, w+dw] * Filter[dc, c, filter_h-1-dh, filter_w-1-dw],
             axis=[dc, dh, dw]), tag="conv2d_transpose_nchw")
 
     return Output
diff --git a/topi/python/topi/testing/conv2d_transpose_nchw_python.py b/topi/python/topi/testing/conv2d_transpose_nchw_python.py
index 43af160e8038..2b78452b95cd 100644
--- a/topi/python/topi/testing/conv2d_transpose_nchw_python.py
+++ b/topi/python/topi/testing/conv2d_transpose_nchw_python.py
@@ -1,6 +1,7 @@
 # pylint: disable=unused-variable
 """Transposed convolution in python"""
 import numpy as np
+import scipy
 import topi
 from topi.nn.util import get_pad_tuple
 
@@ -14,7 +15,7 @@ def conv2d_transpose_nchw_python(a_np, w_np, stride, padding):
         4-D with shape [batch, in_channel, in_height, in_width]
 
     w_np : numpy.ndarray
-        4-D with shape [num_filter, in_channel, filter_height, filter_width]
+        4-D with shape [in_channel, num_filter, filter_height, filter_width]
 
     stride : int or a list/tuple of two ints
         Stride size, or [stride_height, stride_width]
@@ -28,7 +29,7 @@ def conv2d_transpose_nchw_python(a_np, w_np, stride, padding):
         4-D with shape [batch, out_channel, out_height, out_width]
     """
     batch, in_c, in_h, in_w = a_np.shape
-    out_c, _, filter_h, filter_w = w_np.shape
+    _, out_c, filter_h, filter_w = w_np.shape
     if isinstance(stride, int):
         stride_h = stride_w = stride
     else:
@@ -46,6 +47,13 @@ def conv2d_transpose_nchw_python(a_np, w_np, stride, padding):
     padded_a_np[:, :, bpad_top:dilated_a_np.shape[2]+bpad_top, \
         bpad_left:dilated_a_np.shape[3]+bpad_left] = dilated_a_np
     # convolution stage
-    rotated_w_np = np.rot90(w_np, k=2, axes=(2, 3))
-    b_np = topi.testing.conv2d_nchw_python(padded_a_np, rotated_w_np, stride=1, padding='VALID')
+    out_h = (in_h - 1) * stride_h - fpad_top - fpad_bottom + filter_h
+    out_w = (in_w - 1) * stride_w - fpad_left - fpad_right + filter_w
+    b_np = np.zeros((batch, out_c, out_h, out_w))
+    for n in range(batch):
+        for f in range(out_c):
+            for c in range(in_c):
+                out = scipy.signal.convolve2d(
+                    padded_a_np[n, c], w_np[c, f], mode='valid')
+                b_np[n, f] += out
     return b_np
diff --git a/topi/tests/python/test_topi_conv2d_transpose_nchw.py b/topi/tests/python/test_topi_conv2d_transpose_nchw.py
index 02e085387253..8aa86e6f38be 100644
--- a/topi/tests/python/test_topi_conv2d_transpose_nchw.py
+++ b/topi/tests/python/test_topi_conv2d_transpose_nchw.py
@@ -10,7 +10,7 @@ def verify_conv2d_transpose_nchw(batch, in_channel, in_size, num_filter, kernel,
     in_height = in_width = in_size
 
     A = tvm.placeholder((batch, in_channel, in_height, in_width), name='A')
-    W = tvm.placeholder((num_filter, in_channel, kernel, kernel), name='W')
+    W = tvm.placeholder((in_channel, num_filter, kernel, kernel), name='W')
     B = topi.nn.conv2d_transpose_nchw(A, W, [stride, stride], padding)
     C = topi.nn.relu(B)
 

From 033fd2666c14e035ade90cfd5543823d74007ae6 Mon Sep 17 00:00:00 2001
From: masahi <masahi129@gmail.com>
Date: Tue, 7 Nov 2017 06:40:25 +0900
Subject: [PATCH 003/949] add tanh dispatch (#619)

---
 src/codegen/llvm/intrin_rule_rocm.cc | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/codegen/llvm/intrin_rule_rocm.cc b/src/codegen/llvm/intrin_rule_rocm.cc
index fa27701870f9..38211db0b9b1 100644
--- a/src/codegen/llvm/intrin_rule_rocm.cc
+++ b/src/codegen/llvm/intrin_rule_rocm.cc
@@ -43,6 +43,8 @@ TVM_REGISTER_GLOBAL("tvm.intrin.rule.rocm.sqrt")
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.rocm.pow")
 .set_body(DispatchExternOCML);
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.rocm.tanh")
+.set_body(DispatchExternOCML);
 }  // namespace llvm
 }  // namespace codegen
 }  // namespace tvm

From 7c508eb4a79ee696c5dc99cbdd4dccea58e2138d Mon Sep 17 00:00:00 2001
From: eqy <Loser777@gmail.com>
Date: Mon, 6 Nov 2017 16:02:09 -0800
Subject: [PATCH 004/949] remove minimum 32-bit restriction (#621)

Change minimum 32-bit restriction for floating point types to 8-bit.
This change is to enable reduced precision types that may use vector operations underneath the hood (cases #lanes > 1 such as half4).
---
 src/runtime/c_runtime_api.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/runtime/c_runtime_api.cc b/src/runtime/c_runtime_api.cc
index 8fc9a16aa851..f036dccc381a 100644
--- a/src/runtime/c_runtime_api.cc
+++ b/src/runtime/c_runtime_api.cc
@@ -127,7 +127,7 @@ inline void TVMArrayFree_(TVMArray* arr) {
 inline void VerifyType(int dtype_code, int dtype_bits, int dtype_lanes) {
   CHECK_GE(dtype_lanes, 1);
   if (dtype_code == kDLFloat) {
-    CHECK_EQ(dtype_bits % 32, 0);
+    CHECK_EQ(dtype_bits % 8, 0);
   } else {
     CHECK_EQ(dtype_bits % 8, 0);
   }

From a1363b9e3c28cb2ebb19f84e7328b5744e28cee3 Mon Sep 17 00:00:00 2001
From: Leyuan Wang <laurawly@gmail.com>
Date: Wed, 8 Nov 2017 12:45:58 -0800
Subject: [PATCH 005/949] conv2d_56_64_128 mark==1 bug fixed (#624)

---
 topi/python/topi/cuda/conv2d_nchw.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/topi/python/topi/cuda/conv2d_nchw.py b/topi/python/topi/cuda/conv2d_nchw.py
index 81271938edea..fea0f542f448 100644
--- a/topi/python/topi/cuda/conv2d_nchw.py
+++ b/topi/python/topi/cuda/conv2d_nchw.py
@@ -80,7 +80,7 @@ def conv2d_56_64_128(s, temp, temp_R, temp_S, Filter_S, Out, Out_L, flag):
                 if mark % i == 0 and vthread_x > 0:
                     num_thread_x = i
                     break
-        if num_thread_x * vthread_x > 128:
+        if mark < 5 or num_thread_x * vthread_x > 128:
             num_thread_x = 8
             vthread_x = 8
         num_thread_y = 8

From e1854fea30bd61222a4eaf2c2217b72d96dc879a Mon Sep 17 00:00:00 2001
From: eqy <Loser777@gmail.com>
Date: Wed, 8 Nov 2017 15:47:07 -0800
Subject: [PATCH 006/949] Support vector operations for AMD (llvm IR) (#623)

* Support vector operations for AMD (llvm IR)

* fix whitespace

* update comments, docstring
---
 include/tvm/buffer.h             |  4 +++-
 python/tvm/schedule.py           |  9 +++++++--
 src/api/api_lang.cc              |  2 +-
 src/codegen/llvm/codegen_llvm.cc | 31 +++++++++++++++++++++++++++----
 src/codegen/llvm/codegen_llvm.h  |  1 +
 src/lang/buffer.cc               | 15 ++++++++++++---
 src/pass/ir_util.h               |  1 +
 7 files changed, 52 insertions(+), 11 deletions(-)

diff --git a/include/tvm/buffer.h b/include/tvm/buffer.h
index 610532e261a3..ad4872b8e4e0 100644
--- a/include/tvm/buffer.h
+++ b/include/tvm/buffer.h
@@ -51,8 +51,10 @@ class Buffer : public NodeRef {
    * \brief Get access ptr to the entire buffer.
    * \param access_mask The access mask
    * \param ptr_type The type of the pointer.
+   * \param content_lanes The number of lanes for the (data) type.
    */
-  TVM_DLL Expr access_ptr(int access_mask, Type ptr_type = Handle()) const;
+  TVM_DLL Expr access_ptr(int access_mask, Type ptr_type = Handle(),
+                          int content_lanes = 1) const;
   /*!
    * \brief Create an Expr that does a vector load at begin index.
    * \param begin The beginning index
diff --git a/python/tvm/schedule.py b/python/tvm/schedule.py
index 26be2de1a69a..6abe4aae2f6f 100644
--- a/python/tvm/schedule.py
+++ b/python/tvm/schedule.py
@@ -25,7 +25,7 @@ class Buffer(NodeBase):
     READ = 1
     WRITE = 2
 
-    def access_ptr(self, access_mask, ptr_type="handle"):
+    def access_ptr(self, access_mask, ptr_type="handle", content_lanes=1):
         """Get an access pointer to the head of buffer.
 
         This is the recommended method to get buffer data
@@ -41,6 +41,10 @@ def access_ptr(self, access_mask, ptr_type="handle"):
             The data type of the result pointer. Do not specify
             unless we want to cast pointer to specific type.
 
+        content_lanes: int, optional
+            The number of lanes for the data type. This value
+            is greater than one for vector types.
+
         Examples
         --------
         .. code-block:: python
@@ -63,7 +67,8 @@ def access_ptr(self, access_mask, ptr_type="handle"):
                 else:
                     raise ValueError("Unknown access_mask %s" % access_mask)
             access_mask = mask
-        return _api_internal._BufferAccessPtr(self, access_mask, ptr_type)
+        return _api_internal._BufferAccessPtr(self, access_mask, ptr_type,
+                                              content_lanes)
 
     def vload(self, begin, dtype=None):
         """Generate an Expr that loads dtype from begin index.
diff --git a/src/api/api_lang.cc b/src/api/api_lang.cc
index 85b7d92c6a25..94075b6ec059 100644
--- a/src/api/api_lang.cc
+++ b/src/api/api_lang.cc
@@ -159,7 +159,7 @@ TVM_REGISTER_API("_Buffer")
 TVM_REGISTER_API("_BufferAccessPtr")
 .set_body([](TVMArgs args,  TVMRetValue* ret) {
     *ret = args[0].operator Buffer()
-        .access_ptr(args[1], args[2]);
+        .access_ptr(args[1], args[2], args[3]);
   });
 
 TVM_REGISTER_API("_BufferVLoad")
diff --git a/src/codegen/llvm/codegen_llvm.cc b/src/codegen/llvm/codegen_llvm.cc
index a4bb815b1b93..d274af73ed82 100644
--- a/src/codegen/llvm/codegen_llvm.cc
+++ b/src/codegen/llvm/codegen_llvm.cc
@@ -509,6 +509,18 @@ llvm::Value* CodeGenLLVM::CreateBufferPtr(
   return builder_->CreateInBoundsGEP(buffer, index);
 }
 
+llvm::Value* CodeGenLLVM::CreateBufferVecPtr(
+    Type t, llvm::Value* buffer, llvm::Value* index) {
+  CHECK_GT(t.lanes(), 1);
+  llvm::PointerType* btype = llvm::dyn_cast<llvm::PointerType>(buffer->getType());
+  CHECK(btype != nullptr);
+  llvm::PointerType* ptype = LLVMType(t)->getPointerTo(btype->getAddressSpace());
+  if (btype != ptype) {
+    buffer = builder_->CreatePointerCast(buffer, ptype);
+  }
+  return builder_->CreateInBoundsGEP(buffer, index);
+}
+
 llvm::Value* CodeGenLLVM::GetVarValue(const Variable* v) const {
   auto it = var_map_.find(v);
   CHECK(it != var_map_.end()) << "cannot find variable " << v->name_hint;
@@ -572,10 +584,21 @@ llvm::Value* CodeGenLLVM::CreateIntrinsic(const Call* op) {
   } else if (op->is_intrinsic(intrinsic::tvm_address_of)) {
     const Load *l = op->args[0].as<Load>();
     CHECK(op->args.size() == 1 && l);
-    llvm::Value* ptr = CreateBufferPtr(
-        l->type, MakeValue(l->buffer_var), MakeValue(l->index));
-    unsigned addrspace = llvm::dyn_cast<llvm::PointerType>(
-        ptr->getType())->getAddressSpace();
+    const Ramp *r = l->index.as<Ramp>();
+    llvm::Value* ptr;
+    unsigned addrspace;
+    if (!r) {
+        ptr = CreateBufferPtr(
+          l->type, MakeValue(l->buffer_var), MakeValue(l->index));
+        addrspace = llvm::dyn_cast<llvm::PointerType>(
+          ptr->getType())->getAddressSpace();
+    } else {
+        Expr index = r->base / make_const(Int(32), r->lanes);
+        ptr = CreateBufferVecPtr(
+          l->type, MakeValue(l->buffer_var), MakeValue(index));
+        addrspace = llvm::dyn_cast<llvm::PointerType>(
+          ptr->getType())->getAddressSpace();
+    }
     return builder_->CreatePointerCast(ptr, t_void_->getPointerTo(addrspace));
   } else if (op->is_intrinsic(Call::reinterpret) && is_zero(op->args[0])) {
     return llvm::Constant::getNullValue(t_void_p_);
diff --git a/src/codegen/llvm/codegen_llvm.h b/src/codegen/llvm/codegen_llvm.h
index e4a0b24d381a..fbc74f092825 100644
--- a/src/codegen/llvm/codegen_llvm.h
+++ b/src/codegen/llvm/codegen_llvm.h
@@ -191,6 +191,7 @@ class CodeGenLLVM :
   llvm::Value* CreateMul(Type t, llvm::Value* a, llvm::Value* b);
   llvm::Value* CreateBroadcast(llvm::Value* value, int lanes);
   llvm::Value* CreateBufferPtr(Type t, llvm::Value* buffer, llvm::Value* index);
+  llvm::Value* CreateBufferVecPtr(Type t, llvm::Value* buffer, llvm::Value* index);
   // Vector concatenation.
   llvm::Value* CreateVecSlice(llvm::Value* vec, int begin, int extent);
   llvm::Value* CreateVecFlip(llvm::Value* vec);
diff --git a/src/lang/buffer.cc b/src/lang/buffer.cc
index 34abada14118..5cf7ddef3018 100644
--- a/src/lang/buffer.cc
+++ b/src/lang/buffer.cc
@@ -341,14 +341,23 @@ Buffer Buffer::MakeSlice(Array<Expr> begins, Array<Expr> extents) const {
                           0);
 }
 
-Expr Buffer::access_ptr(int access_mask, Type ptr_type) const {
+Expr Buffer::access_ptr(int access_mask, Type ptr_type, int content_lanes) const {
   const BufferNode* self = operator->();
-  Expr e_dtype = make_zero(self->dtype);
+  Expr e_dtype;
   Expr extent = (self->strides.size() == self->shape.size() ?
                  arith::ComputeExpr<ir::Mul>(self->strides[0], self->shape[0]):
                  arith::ComputeReduce<ir::Mul>(self->shape));
+  Expr elem_offset = self->elem_offset;
+  if (content_lanes > 1) {
+    e_dtype = make_zero(self->dtype.with_lanes(content_lanes));
+    extent = extent / make_const(self->elem_offset.type(), content_lanes);
+    elem_offset = self->elem_offset / make_const(self->elem_offset.type(),
+                                                 content_lanes);
+  } else {
+    e_dtype = make_zero(self->dtype);
+  }
   Array<Expr> acc_args{
-    e_dtype, self->data, self->elem_offset,
+    e_dtype, self->data, elem_offset,
         extent, make_const(Int(32), access_mask)};
   return ir::Call::make(
       ptr_type, ir::intrinsic::tvm_access_ptr, acc_args, ir::Call::Intrinsic);
diff --git a/src/pass/ir_util.h b/src/pass/ir_util.h
index ae7a026c1ecb..082d580a0e45 100644
--- a/src/pass/ir_util.h
+++ b/src/pass/ir_util.h
@@ -102,6 +102,7 @@ inline Expr AddressOffset(Var handle, Type dtype, int offset) {
 inline Expr AddressOffset(Var handle, Type dtype, Expr offset) {
   if (dtype.lanes() != 1) {
     offset = offset * make_const(offset.type(), dtype.lanes());
+    offset = Ramp::make(offset, make_const(offset.type(), 1), dtype.lanes());
   }
   return Call::make(
       Handle(), intrinsic::tvm_address_of,

From 918e2593004fa9076665dbf8ea090625926670d9 Mon Sep 17 00:00:00 2001
From: Erwan BERNARD <edmBernard@users.noreply.github.com>
Date: Thu, 9 Nov 2017 00:47:19 +0100
Subject: [PATCH 007/949] WIP: Add how_to readme to install tvm with nnpack
 support (#610)

* feat(docs) add how_to for tvm install with nnpack support

* feat(docs) change python package paragraph

* feat(doc) remove unsure sentence

* add comments on nnpack usage vs TVM

* remove mxnet nnpack tips for nthread change
---
 docs/how_to/nnpack.md | 144 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 144 insertions(+)
 create mode 100644 docs/how_to/nnpack.md

diff --git a/docs/how_to/nnpack.md b/docs/how_to/nnpack.md
new file mode 100644
index 000000000000..060bf9b5399f
--- /dev/null
+++ b/docs/how_to/nnpack.md
@@ -0,0 +1,144 @@
+### NNPACK for Multi-Core CPU Support in TVM
+[NNPACK](https://github.com/Maratyszcza/NNPACK) is an acceleration package
+for neural network computations, which can run on x86-64, ARMv7, or ARM64 architecture CPUs.
+Using NNPACK, higher-level libraries like _MXNet_ can speed up
+the execution on multi-core CPU computers, including laptops and mobile devices.
+
+***Note***: AS TVM already has natively tuned schedules, NNPACK is here mainly for reference and comparison purpose. 
+For regular use prefer native tuned TVM implementation.
+
+_TVM_ supports NNPACK for forward propagation (inference only) in convolution, max-pooling, and fully-connected layers.
+In this document, we give a high level overview of how to use NNPACK with _TVM_.
+
+### Conditions
+The underlying implementation of NNPACK utilizes several acceleration methods,
+including [fft](https://arxiv.org/abs/1312.5851) and [winograd](https://arxiv.org/abs/1509.09308).
+These algorithms work better on some special `batch size`, `kernel size`, and `stride` settings than on other,
+so depending on the context, not all convolution, max-pooling, or fully-connected layers can be powered by NNPACK.
+When favorable conditions for running NNPACKS are not met,
+
+NNPACK only supports Linux and OS X systems. Windows is not supported at present.
+The following table explains under which conditions NNPACK will work.
+
+| operation      | conditions |
+|:---------      |:---------- |
+|convolution     |2d convolution `and` no-bias=False `and` dilate=(1,1) `and` num_group=1 `and` batch-size = 1 or batch-size > 1 && stride = (1,1);|
+|pooling         | max-pooling `and` kernel=(2,2) `and` stride=(2,2) `and` pooling_convention=full    |
+|fully-connected| without any restrictions |
+
+### Build/Install LLVM
+LLVM is required for CPU codegen that needs LLVM.
+Since LLVM takes long time to build from source, you can download pre-built version of LLVM from [LLVM Download Page](http://releases.llvm.org/download.html).
+For llvm 4.0 you can do the following step : 
+
+```bash
+# Add llvm repository in apt source list
+echo "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-4.0 main" >> /etc/apt/sources.list
+
+# Update apt source list
+apt-get update
+# Install clang and full llvm
+apt-get install -y \
+    clang-4.0 \
+    clang-4.0-doc \
+    libclang-common-4.0-dev \
+    libclang-4.0-dev \
+    libclang1-4.0 \
+    libclang1-4.0-dbg \
+    libllvm-4.0-ocaml-dev \
+    libllvm4.0 \
+    libllvm4.0-dbg \
+    lldb-4.0 \
+    llvm-4.0 \
+    llvm-4.0-dev \
+    llvm-4.0-doc \
+    llvm-4.0-examples \
+    llvm-4.0-runtime \
+    clang-format-4.0 \
+    python-clang-4.0 \
+    libfuzzer-4.0-dev
+```
+
+### Build/Install NNPACK
+
+If the trained model meets some conditions of using NNPACK,
+you can build TVM with NNPACK support.
+Follow these simple steps:  
+* Build NNPACK shared library with the following commands. _TVM_ will link NNPACK dynamically.
+
+Note: The following NNPACK installation instructions have been tested on Ubuntu 16.04.
+
+#### Build [Ninja](https://ninja-build.org/)
+
+NNPACK need a recent version of Ninja. So we need to install ninja from source.
+```bash
+git clone git://github.com/ninja-build/ninja.git
+cd ninja
+./configure.py --bootstrap
+```
+
+Set the environment variable PATH to tell bash where to find the ninja executable. For example, assume we cloned ninja on the home directory ~. then we can added the following line in ~/.bashrc. 
+```bash
+export PATH="${PATH}:~/ninja"
+```
+
+#### Build [NNPACK](https://github.com/Maratyszcza/NNPACK)
+
+The new CMAKE version of NNPACK download [Peach](https://github.com/Maratyszcza/PeachPy) and other dependencies alone
+
+```bash
+git clone --recursive https://github.com/Maratyszcza/NNPACK.git
+cd NNPACK
+# Add PIC option in CFLAG and CXXFLAG to build NNPACK shared library
+sed -i "s|gnu99|gnu99 -fPIC|g" CMakeLists.txt
+sed -i "s|gnu++11|gnu++11 -fPIC|g" CMakeLists.txt
+mkdir build
+cd build
+# Generate ninja build rule and add shared library in configuration
+cmake -G Ninja -D BUILD_SHARED_LIBS=ON ..
+ninja
+sudo ninja install
+
+# Add NNPACK lib folder in your ldconfig
+echo "/usr/local/lib" > /etc/ld.so.conf.d/nnpack.conf
+sudo ldconfig
+```
+
+### Build TVM with NNPACK support
+
+```bash
+git clone --recursive https://github.com/dmlc/tvm
+```
+
+* Set `USE_NNPACK = 1` in config.mk.
+* Set `NNPACK_PATH` to the $(YOUR_NNPACK_INSTALL_PATH)
+* Set `LLVM_CONFIG = llvm-config-4.0` depending of llvm version installed
+
+after configuration use `make` to build TVM
+
+```bash
+make
+make install
+```
+
+#### Python Package Installation
+
+The python package for [tvm](https://github.com/dmlc/tvm) depends of [topi](https://github.com/dmlc/tvm/tree/master/topi).
+The tvm python package is located at `tvm/python` and topi python package is located in `tvm/topi/python` folder.
+There are several ways to install the package, in all these cases the TVM library and TOPI must be present in the python env:
+
+1. Set the environment variable PYTHONPATH to tell python where to find the libraries. For example, assume we cloned tvm on the home directory ~. then we can added the following line in ~/.bashrc. It is recommended for developers who may change the codes. The changes will be immediately reflected once you pulled the code and rebuild the project (no need to call setup again)
+
+```bash
+export PYTHONPATH=/path/to/tvm/python:/path/to/tvm/topi/python:${PYTHONPATH}
+```
+
+2. Install tvm and topi python bindings by setup.py:
+
+```bash
+# install tvm package for the current user
+cd topi/python
+python setup.py install --user; 
+cd ../../python
+python setup.py install --user; 
+```

From 093da271ed28302504d99bb8af91f833c40a7771 Mon Sep 17 00:00:00 2001
From: eqy <Loser777@gmail.com>
Date: Wed, 8 Nov 2017 17:13:02 -0800
Subject: [PATCH 008/949] inline AMD GPU functions (#625)

* Support vector operations for AMD (llvm IR)

* fix whitespace

* update comments, docstring

* inline AMD GPU functions
---
 src/codegen/llvm/codegen_amdgpu.cc | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/codegen/llvm/codegen_amdgpu.cc b/src/codegen/llvm/codegen_amdgpu.cc
index fdd2ec2e38d2..9b8995bf5516 100644
--- a/src/codegen/llvm/codegen_amdgpu.cc
+++ b/src/codegen/llvm/codegen_amdgpu.cc
@@ -176,6 +176,9 @@ runtime::Module BuildAMDGPU(Array<LoweredFunc> funcs, std::string target) {
     }
     mlib->setTargetTriple(tm->getTargetTriple().str());
     mlib->setDataLayout(tm->createDataLayout());
+    for (llvm::Function &f : mlib->functions()) {
+      f.addFnAttr(llvm::Attribute::AlwaysInline);
+    }
     cg->AddLinkModule(std::move(mlib));
   }
 

From b0b44d9422b7dbfb08def6b3302dd97f7a5a0ebd Mon Sep 17 00:00:00 2001
From: Yizhi Liu <javelinjs@gmail.com>
Date: Fri, 10 Nov 2017 01:56:29 +0800
Subject: [PATCH 009/949] android gemm for topi/recipe (#628)

---
 topi/recipe/gemm/android_gemm_square.py | 116 ++++++++++++++++++++++++
 1 file changed, 116 insertions(+)
 create mode 100644 topi/recipe/gemm/android_gemm_square.py

diff --git a/topi/recipe/gemm/android_gemm_square.py b/topi/recipe/gemm/android_gemm_square.py
new file mode 100644
index 000000000000..f6f3b5ab4589
--- /dev/null
+++ b/topi/recipe/gemm/android_gemm_square.py
@@ -0,0 +1,116 @@
+"""Example code to do square matrix multiplication on Android Phone."""
+import tvm
+import os
+from tvm.contrib import rpc, util, ndk
+import numpy as np
+
+# Set to be address of tvm proxy.
+proxy_host = os.environ["TVM_ANDROID_RPC_PROXY_HOST"]
+proxy_port = 9090
+key = "android"
+ 
+# Change target configuration.
+# Run `adb shell cat /proc/cpuinfo` to find the arch.
+arch = "arm64"
+target = "llvm -target=%s-linux-android" % arch
+
+def ngflops(N):
+    return 2.0 * float(N * N * N) / (10**9)
+
+dtype = 'float32'
+def evaluate(func, ctx, N, times):
+    a_np = np.random.uniform(size=(N, N)).astype(dtype)
+    b_np = np.random.uniform(size=(N, N)).astype(dtype)
+    a = tvm.nd.array(a_np, ctx)
+    b = tvm.nd.array(b_np, ctx)
+    c = tvm.nd.array(np.zeros((N, N), dtype=dtype), ctx)
+
+    time_f = func.time_evaluator(func.entry_name, ctx, number=times)
+    cost = time_f(a, b, c).mean
+    gf = ngflops(N) / cost
+    print('%g secs/op, %g GFLOPS' % (cost, gf))
+    np.testing.assert_almost_equal(c.asnumpy(), a_np.dot(b_np), decimal=2)
+
+def test_gemm_gpu(N, times, bn, num_block, num_thread):
+    assert(bn <= N)
+    assert(num_thread * num_thread * 16 <= N)
+    assert(num_block * num_block * 2 <= N)
+    A = tvm.placeholder((N, N), name='A')
+    B = tvm.placeholder((N, N), name='Btmp')
+    k = tvm.reduce_axis((0, N), name='k')
+
+    packedB = tvm.compute((N, N / bn, bn),
+              lambda x, y, z: B[x, y * bn + z], name = 'B')
+
+    C = tvm.compute(
+        (N, N),
+        lambda ii, jj: tvm.sum(A[ii, k] * packedB[k, jj / bn, jj % bn], axis=k),
+        name='C')
+
+    s = tvm.create_schedule(C.op)
+    CC = s.cache_write(C, "local")
+
+    block_x = tvm.thread_axis("blockIdx.x")
+    block_y = tvm.thread_axis("blockIdx.y")
+    thread_x = tvm.thread_axis("threadIdx.x")
+    thread_y = tvm.thread_axis("threadIdx.y")
+
+    thread_xz = tvm.thread_axis((0, 2), "vthread", name="vx")
+    thread_yz = tvm.thread_axis((0, 2), "vthread", name="vy")
+
+    pby, pbi = s[packedB].split(packedB.op.axis[0], nparts=num_thread)
+    pbx, pbj = s[packedB].split(packedB.op.axis[1], nparts=num_thread)
+    s[packedB].bind(pby, thread_y)
+    s[packedB].bind(pbx, thread_x)
+    pbz, pbk = s[packedB].split(packedB.op.axis[2], factor=8)
+    s[packedB].vectorize(pbk)
+
+    by, yi = s[C].split(C.op.axis[0], nparts=num_block)
+    bx, xi = s[C].split(C.op.axis[1], nparts=num_thread)
+
+    s[C].bind(by, block_y)
+    s[C].bind(bx, thread_y)
+    s[C].reorder(by, bx, yi, xi)
+
+    tyz, yi = s[C].split(yi, nparts=2)
+    ty, yi = s[C].split(yi, nparts=num_block)
+    txz, xi = s[C].split(xi, nparts=2)
+    tx, xi = s[C].split(xi, nparts=num_thread)
+
+    s[C].reorder(tyz, txz, ty, tx, yi, xi)
+    s[C].bind(tyz, thread_yz)
+    s[C].bind(txz, thread_xz)
+
+    s[C].bind(ty, block_x)
+    s[C].bind(tx, thread_x)
+
+    xyi, xxi = s[C].split(xi, factor=8)
+    s[C].reorder(tyz, txz, ty, tx, yi, xyi, xxi)
+    s[C].vectorize(xxi)
+
+    s[CC].compute_at(s[C], yi)
+    yo, xo = CC.op.axis
+    s[CC].reorder(k, yo, xo)
+    xo, xi = s[CC].split(xo, factor=8)
+    s[CC].vectorize(xi)
+
+    ko, ki = s[CC].split(k, factor=2)
+    s[CC].unroll(ki)
+
+    print(tvm.lower(s, [A, B, C], simple_mode=True))
+
+    f = tvm.build(s, [A, B, C], "opencl", target_host=target, name="gemm_gpu")
+    temp = util.tempdir()   
+    path_dso = temp.relpath("gemm_gpu.so")
+    f.export_library(path_dso, ndk.create_shared)
+
+    # connect to the proxy
+    remote = rpc.connect(proxy_host, proxy_port, key=key)
+    ctx = remote.cl(0)
+    remote.upload(path_dso)
+    f = remote.load_module("gemm_gpu.so")
+
+    evaluate(f, ctx, N, times)
+
+if __name__ == "__main__":
+    test_gemm_gpu(1024, times=5, bn=8, num_block=2, num_thread=8)

From d3cbe0dcd4f8e21ebe86baf0277bb25dc9ddf4d7 Mon Sep 17 00:00:00 2001
From: ziheng <ziheng@apache.org>
Date: Fri, 10 Nov 2017 19:02:46 -0800
Subject: [PATCH 010/949] [NNPACK] Add argument nthreads (#631)

---
 python/tvm/contrib/nnpack.py          | 16 ++++++++--------
 src/contrib/nnpack/convolution.cc     |  2 ++
 src/contrib/nnpack/fully_connected.cc |  4 ++++
 src/contrib/nnpack/nnpack_utils.cc    | 25 +++++++++++++++----------
 src/contrib/nnpack/nnpack_utils.h     |  2 ++
 5 files changed, 31 insertions(+), 18 deletions(-)

diff --git a/python/tvm/contrib/nnpack.py b/python/tvm/contrib/nnpack.py
index 66e7a9494d89..d6587df26229 100644
--- a/python/tvm/contrib/nnpack.py
+++ b/python/tvm/contrib/nnpack.py
@@ -16,7 +16,7 @@ def config(nthreads):
     """
     _Config(nthreads)
 
-def fully_connected_inference(lhs, rhs):
+def fully_connected_inference(lhs, rhs, nthreads=1):
     """Create an extern op that compute fully connected of 1D tensor lhs and
     2D tensor rhs with nnpack.
 
@@ -37,9 +37,9 @@ def fully_connected_inference(lhs, rhs):
         (m, ), [lhs, rhs],
         lambda ins, outs: _intrin.call_packed(
             "tvm.contrib.nnpack.fully_connected_inference",
-            ins[0], ins[1], outs[0]), name="C")
+            ins[0], ins[1], outs[0], nthreads), name="C")
 
-def fully_connected_output(lhs, rhs):
+def fully_connected_output(lhs, rhs, nthreads=1):
     """Create an extern op that compute fully connected of 2D tensor lhs and
     2D tensor rhs with nnpack.
 
@@ -61,9 +61,9 @@ def fully_connected_output(lhs, rhs):
         (n, m), [lhs, rhs],
         lambda ins, outs: _intrin.call_packed(
             "tvm.contrib.nnpack.fully_connected_output",
-            ins[0], ins[1], outs[0]), name="C")
+            ins[0], ins[1], outs[0], nthreads), name="C")
 
-def convolution_inference(data, kernel, bias, padding, stride):
+def convolution_inference(data, kernel, bias, padding, stride, nthreads=1):
     """Create an extern op to do inference convolution of 3D tensor data and
     4D tensor kernel and 1D tensor bias with nnpack.
 
@@ -104,9 +104,9 @@ def convolution_inference(data, kernel, bias, padding, stride):
         lambda ins, outs: _intrin.call_packed(
             "tvm.contrib.nnpack.convolution_inference", ins[0], ins[1], ins[2],
             outs[0], padding[0], padding[1], padding[2], padding[3],
-            stride[0], stride[1]), name="C")
+            stride[0], stride[1], nthreads), name="C")
 
-def convolution_output(data, kernel, bias, padding):
+def convolution_output(data, kernel, bias, padding, nthreads=1):
     """Create an extern op to compute convolution of 4D tensor data and
     4D tensor kernel and 1D tensor bias with nnpack.
 
@@ -142,6 +142,6 @@ def convolution_output(data, kernel, bias, padding):
         (batch, output_channels, output_height, output_width), [data, kernel, bias],
         lambda ins, outs: _intrin.call_packed(
             "tvm.contrib.nnpack.convolution_output", ins[0], ins[1], ins[2],
-            outs[0], padding[0], padding[1], padding[2], padding[3]), name="C")
+            outs[0], padding[0], padding[1], padding[2], padding[3], nthreads), name="C")
 
 _init_api("tvm.contrib.nnpack")
diff --git a/src/contrib/nnpack/convolution.cc b/src/contrib/nnpack/convolution.cc
index 8480a100dfd7..9ca02118aeb3 100644
--- a/src/contrib/nnpack/convolution.cc
+++ b/src/contrib/nnpack/convolution.cc
@@ -24,6 +24,7 @@ TVM_REGISTER_GLOBAL("tvm.contrib.nnpack.convolution_inference")
     nnp_padding input_padding{pad_top, pad_right, pad_bottom, pad_left};
     uint64_t stride_width = args[8], stride_height = args[9];
     nnp_size stride_size{stride_width, stride_height};
+    NNPackConfig(args[10]);
 
     CHECK_EQ(input->ndim, 3);
     CHECK_EQ(kernel->ndim, 4);
@@ -80,6 +81,7 @@ TVM_REGISTER_GLOBAL("tvm.contrib.nnpack.convolution_output")
     DLTensor* output = args[3];
     uint64_t pad_top = args[4], pad_right = args[5], pad_bottom = args[6], pad_left = args[7];
     nnp_padding input_padding{pad_top, pad_right, pad_bottom, pad_left};
+    NNPackConfig(args[8]);
 
     CHECK_EQ(input->ndim, 4);
     CHECK_EQ(kernel->ndim, 4);
diff --git a/src/contrib/nnpack/fully_connected.cc b/src/contrib/nnpack/fully_connected.cc
index 6793ecaa36a7..df6356d933aa 100644
--- a/src/contrib/nnpack/fully_connected.cc
+++ b/src/contrib/nnpack/fully_connected.cc
@@ -21,6 +21,8 @@ TVM_REGISTER_GLOBAL("tvm.contrib.nnpack.fully_connected_inference")
     DLTensor* A = args[0];
     DLTensor* B = args[1];
     DLTensor* C = args[2];
+    NNPackConfig(args[3]);
+
     CHECK_EQ(A->ndim, 1);
     CHECK_EQ(B->ndim, 2);
     CHECK_EQ(C->ndim, 1);
@@ -49,6 +51,8 @@ TVM_REGISTER_GLOBAL("tvm.contrib.nnpack.fully_connected_output")
     DLTensor* A = args[0];
     DLTensor* B = args[1];
     DLTensor* C = args[2];
+    NNPackConfig(args[3]);
+
     CHECK_EQ(A->ndim, 2);
     CHECK_EQ(B->ndim, 2);
     CHECK_EQ(C->ndim, 2);
diff --git a/src/contrib/nnpack/nnpack_utils.cc b/src/contrib/nnpack/nnpack_utils.cc
index e1e2773c1c8d..631f25b36647 100644
--- a/src/contrib/nnpack/nnpack_utils.cc
+++ b/src/contrib/nnpack/nnpack_utils.cc
@@ -14,18 +14,23 @@ NNPackThreadLocalEntry* NNPackThreadLocalEntry::ThreadLocal() {
   return NNPackThreadLocalStore::Get();
 }
 
+bool NNPackConfig(uint64_t nthreads) {
+  NNPackThreadLocalEntry *entry = NNPackThreadLocalEntry::ThreadLocal();
+  if (entry->threadpool != NULL &&
+      pthreadpool_get_threads_count(entry->threadpool) != nthreads) {
+    pthreadpool_destroy(entry->threadpool);
+    entry->threadpool = NULL;
+  }
+  if (entry->threadpool == NULL) {
+    entry->threadpool = pthreadpool_create(nthreads);
+  }
+  return true;
+}
+
+
 TVM_REGISTER_GLOBAL("contrib.nnpack._Config")
 .set_body([](TVMArgs args, TVMRetValue *ret) {
-    NNPackThreadLocalEntry *entry = NNPackThreadLocalEntry::ThreadLocal();
-    size_t nthreads = args[0].operator uint64_t();
-    if (entry->threadpool != NULL &&
-        pthreadpool_get_threads_count(entry->threadpool) != nthreads) {
-      pthreadpool_destroy(entry->threadpool);
-      entry->threadpool = NULL;
-    }
-    if (entry->threadpool == NULL) {
-      entry->threadpool = pthreadpool_create(nthreads);
-    }
+    CHECK(NNPackConfig(args[0]));
   });
 }  // namespace contrib
 }  // namespace tvm
diff --git a/src/contrib/nnpack/nnpack_utils.h b/src/contrib/nnpack/nnpack_utils.h
index 7a2232add145..fe7420786bde 100644
--- a/src/contrib/nnpack/nnpack_utils.h
+++ b/src/contrib/nnpack/nnpack_utils.h
@@ -18,6 +18,8 @@ struct NNPackThreadLocalEntry {
   pthreadpool_t threadpool{NULL};
   static NNPackThreadLocalEntry* ThreadLocal();
 };
+
+bool NNPackConfig(uint64_t nthreads);
 }  // namespace contrib
 }  // namespace tvm
 #endif  // TVM_CONTRIB_NNPACK_NNPACK_UTILS_H_

From 6e547634b090b59a352bcd608ce7e6fd63399752 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 11 Nov 2017 12:23:01 -0800
Subject: [PATCH 011/949] [PASS] Enhance LiftAttrScope (#632)

* [PASS] Enhance LiftAttrScope

* update vt
---
 src/pass/inject_virtual_thread.cc |   3 +-
 src/pass/lift_attr_scope.cc       | 140 ++++++++++++++++++++----------
 2 files changed, 98 insertions(+), 45 deletions(-)

diff --git a/src/pass/inject_virtual_thread.cc b/src/pass/inject_virtual_thread.cc
index 72b5753adbcf..07f59cb1b308 100644
--- a/src/pass/inject_virtual_thread.cc
+++ b/src/pass/inject_virtual_thread.cc
@@ -229,7 +229,8 @@ class VTInjector : public IRMutator {
     if (visit_touched_var_ && !vt_loop_injected_) {
       return InjectVTLoop(s, true);
     } else if (!allow_share_ && !vt_loop_injected_ &&
-               op->attr_key == attr::coproc_uop_scope) {
+               (op->attr_key == attr::coproc_uop_scope ||
+                op->attr_key == attr::coproc_scope)) {
       return InjectVTLoop(s, true);
     } else {
       Stmt body = Mutate(op->body);
diff --git a/src/pass/lift_attr_scope.cc b/src/pass/lift_attr_scope.cc
index fdf692782523..a3a60aaac4d1 100644
--- a/src/pass/lift_attr_scope.cc
+++ b/src/pass/lift_attr_scope.cc
@@ -7,6 +7,7 @@
  */
 #include <tvm/ir_pass.h>
 #include <tvm/ir_mutator.h>
+#include "./ir_util.h"
 
 namespace tvm {
 namespace ir {
@@ -57,41 +58,16 @@ class AttrScopeLifter : public IRMutator {
   }
 
   Stmt Mutate_(const Block* op, const Stmt& s) final {
-    Stmt first = this->Mutate(op->first);
-    NodeRef first_node_;
-    Expr first_value_;
-    std::swap(first_node_, attr_node_);
-    std::swap(first_value_, attr_value_);
-    Stmt rest = this->Mutate(op->rest);
-    if (attr_node_.defined() &&
-        attr_value_.defined() &&
-        first_node_.defined() &&
-        first_value_.defined() &&
-        attr_node_.same_as(first_node_) &&
-        attr_value_.same_as(first_value_)) {
-      if (first.same_as(op->first) && rest.same_as(op->rest)) {
-        return s;
-      } else {
-        return Block::make(first, rest);
-      }
-    } else {
-      if (first_node_.defined()) {
-        first = AttrStmt::make(
-            first_node_, attr_key_, first_value_, first);
-      }
-      if (attr_node_.defined()) {
-        rest = AttrStmt::make(
-            attr_node_, attr_key_, attr_value_, rest);
-        // undefine them
-        attr_node_ = NodeRef();
-        attr_value_ = Expr();
-      }
-      if (first.same_as(op->first) && rest.same_as(op->rest)) {
-        return s;
-      } else {
-        return Block::make(first, rest);
-      }
+    std::vector<Stmt> seq;
+    FlattenSeq(op->first, &seq);
+    FlattenSeq(op->rest, &seq);
+    seq = MutateSeq(seq);
+    if (seq.size() == 2 &&
+        seq[0].same_as(op->first) &&
+        seq[1].same_as(op->rest)) {
+      return s;
     }
+    return MergeSeq(seq);
   }
 
   Stmt Mutate_(const IfThenElse* op, const Stmt& s) final {
@@ -99,17 +75,17 @@ class AttrScopeLifter : public IRMutator {
       return IRMutator::Mutate_(op, s);
     }
     Stmt then_case = this->Mutate(op->then_case);
-    NodeRef first_node_;
-    Expr first_value_;
-    std::swap(first_node_, attr_node_);
-    std::swap(first_value_, attr_value_);
+    NodeRef first_node;
+    Expr first_value;
+    std::swap(first_node, attr_node_);
+    std::swap(first_value, attr_value_);
     Stmt else_case = this->Mutate(op->else_case);
     if (attr_node_.defined() &&
         attr_value_.defined() &&
-        first_node_.defined() &&
-        first_value_.defined() &&
-        attr_node_.same_as(first_node_) &&
-        attr_value_.same_as(first_value_)) {
+        first_node.defined() &&
+        first_value.defined() &&
+        attr_node_.same_as(first_node) &&
+        ValueSame(attr_value_, first_value)) {
       if (then_case.same_as(op->then_case) &&
           else_case.same_as(op->else_case)) {
         return s;
@@ -117,9 +93,9 @@ class AttrScopeLifter : public IRMutator {
         return IfThenElse::make(op->condition, then_case, else_case);
       }
     } else {
-      if (first_node_.defined()) {
+      if (first_node.defined()) {
         then_case = AttrStmt::make(
-            first_node_, attr_key_, first_value_, then_case);
+            first_node, attr_key_, first_value, then_case);
       }
       if (attr_node_.defined()) {
         else_case = AttrStmt::make(
@@ -138,6 +114,82 @@ class AttrScopeLifter : public IRMutator {
   }
 
  private:
+  void FlattenSeq(Stmt s, std::vector<Stmt>* res) {
+    if (const Block* op = s.as<Block>()) {
+      FlattenSeq(op->first, res);
+      FlattenSeq(op->rest, res);
+    } else if (const ProducerConsumer* op = s.as<ProducerConsumer>()) {
+      if (!op->is_producer) {
+        FlattenSeq(op->body, res);
+      } else {
+        res->emplace_back(s);
+      }
+    } else {
+      res->emplace_back(s);
+    }
+  }
+
+  std::vector<Stmt> MutateSeq(const std::vector<Stmt>& seq) {
+    std::vector<Stmt> res_seq;
+    NodeRef curr_node;
+    Expr curr_value;
+    Stmt curr_stmt;
+    for (const Stmt & stmt : seq) {
+      attr_node_ = NodeRef();
+      attr_value_ = Expr();
+      Stmt rest = this->Mutate(stmt);
+      if (attr_node_.defined() &&
+          attr_value_.defined() &&
+          curr_node.defined() &&
+          curr_value.defined() &&
+          attr_node_.same_as(curr_node) &&
+          ValueSame(attr_value_, curr_value)) {
+        curr_stmt = Block::make(curr_stmt, rest);
+      } else {
+        if (curr_stmt.defined()) {
+          if (curr_node.defined()) {
+            curr_stmt = AttrStmt::make(
+                curr_node, attr_key_, curr_value, curr_stmt);
+          }
+          res_seq.push_back(curr_stmt);
+        }
+        curr_stmt = rest;
+        curr_node = attr_node_;
+        curr_value = attr_value_;
+      }
+    }
+
+    if (curr_stmt.defined()) {
+      // keep attr_node_, attr_node_
+      if (res_seq.size() == 0) {
+        return {curr_stmt};
+      }
+      if (curr_node.defined()) {
+        curr_stmt = AttrStmt::make(
+            curr_node, attr_key_, curr_value, curr_stmt);
+      }
+      res_seq.push_back(curr_stmt);
+      // reset
+      attr_node_ = NodeRef();
+      attr_value_ = Expr();
+    }
+    return res_seq;
+  }
+
+  // value comparison that also compares content of int constant
+  static bool ValueSame(const Expr& a, const Expr& b) {
+    if (a.same_as(b)) return true;
+    if (a->type_key() != b->type_key()) return false;
+    if (a.type() != b.type()) return false;
+    if (const IntImm* op = a.as<IntImm>()) {
+      return op->value == b.as<IntImm>()->value;
+    }
+    if (const UIntImm* op = a.as<UIntImm>()) {
+      return op->value == b.as<UIntImm>()->value;
+    }
+    return false;
+  }
+
   std::string attr_key_;
   NodeRef attr_node_;
   Expr attr_value_;

From bb9d5f8cfea9dfbee4e65b74e2b0b86e19796e97 Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Sun, 12 Nov 2017 08:53:28 +0800
Subject: [PATCH 012/949] [TUTORIAL] use OpenCL on ARM board (#633)

---
 .../deployment/cross_compilation_and_rpc.py   | 58 ++++++++++++++++++-
 1 file changed, 56 insertions(+), 2 deletions(-)

diff --git a/tutorials/deployment/cross_compilation_and_rpc.py b/tutorials/deployment/cross_compilation_and_rpc.py
index 7848b2a23273..859b24472483 100644
--- a/tutorials/deployment/cross_compilation_and_rpc.py
+++ b/tutorials/deployment/cross_compilation_and_rpc.py
@@ -168,8 +168,8 @@
 #   `LLVM guide of cross compilation <https://clang.llvm.org/docs/CrossCompilation.html>`_.
 
 ######################################################################
-# Run Kernel Remotely by RPC
-# --------------------------
+# Run CPU Kernel Remotely by RPC
+# ------------------------------
 # Here we will show you how to run the kernel on the remote device:
 
 # replace host with the ip address of your device
@@ -204,6 +204,60 @@
 cost = time_f(a, b).mean
 print('%g secs/op' % cost)
 
+#########################################################################
+# Run OpenCL Kernel Remotely by RPC
+# ---------------------------------
+# As for remote OpenCL devices, the workflow is almost the same as above.
+# You can define the kernel, upload files, and run by RPC. The files
+# include host object, kernel source code and module meta file. We rely
+# on remote compiler to re-link them.
+#
+# .. note::
+#    Raspberry Pi does not support OpenCL, the following code is tested on
+#    Firefly-RK3399. The target_host should be 'llvm -target=aarch64-linux-gnu'.
+#    But here we set 'llvm' to enable this tutorial to run locally.
+
+# build kernel (different from cpu, we need bind axis for OpenCL)
+s = tvm.create_schedule(B.op)
+xo, xi = s[B].split(B.op.axis[0], factor=32)
+s[B].bind(xo, tvm.thread_axis("blockIdx.x"))
+s[B].bind(xi, tvm.thread_axis("threadIdx.x"))
+f = tvm.build(s, [A, B], "opencl", target_host="llvm", name="myadd")
+
+# save files
+path_o = temp.relpath("myadd.o")
+path_cl = temp.relpath("myadd.cl")
+path_json = temp.relpath("myadd.tvm_meta.json")
+f.save(path_o)
+f.imported_modules[0].save(path_cl)
+
+# upload files
+remote.upload(path_o)
+remote.upload(path_cl)
+remote.upload(path_json)
+
+# load files on remote device
+fhost = remote.load_module("myadd.o")
+fdev = remote.load_module("myadd.cl")
+fhost.import_module(fdev)
+
+# run
+ctx = remote.cl(0)
+a = tvm.nd.array(np.random.uniform(size=1024).astype(A.dtype), ctx)
+b = tvm.nd.array(np.zeros(1024, dtype=A.dtype), ctx)
+fhost(a, b)
+np.testing.assert_equal(b.asnumpy(), a.asnumpy() + 1)
+
+#####################################################################
+# Instead of uploading files separately, there is a more convinient way.
+# You can export libraray as a tar ball.
+path_tar = temp.relpath("myadd.tar")
+f.export_library(path_tar)
+remote.upload(path_tar)
+fhost = remote.load_module("myadd.tar")
+fhost(a, b)
+np.testing.assert_equal(b.asnumpy(), a.asnumpy() + 1)
+
 # terminate the server after experiment
 server.terminate()
 

From 18706afdc6491d1d40d7cbf3306b82ba262eb2cc Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 11 Nov 2017 18:15:31 -0800
Subject: [PATCH 013/949] [PASS] Update coproc sync (#634)

---
 python/tvm/build_module.py                    |  10 +-
 src/pass/coproc_sync.cc                       | 268 +++++++++++++++++-
 src/pass/storage_rewrite.cc                   |   8 +-
 src/runtime/thread_storage_scope.h            |   2 +-
 .../python/unittest/test_pass_storage_sync.py |  21 ++
 5 files changed, 303 insertions(+), 6 deletions(-)

diff --git a/python/tvm/build_module.py b/python/tvm/build_module.py
index ff6086778e7f..4fe502e987d5 100644
--- a/python/tvm/build_module.py
+++ b/python/tvm/build_module.py
@@ -201,7 +201,8 @@ def lower(sch,
     add_lower_pass = cfg.add_lower_pass if cfg.add_lower_pass else []
     lower_phase0 = [x[1] for x in add_lower_pass if x[0] == 0]
     lower_phase1 = [x[1] for x in add_lower_pass if x[0] == 1]
-    lower_phase2 = [x[1] for x in add_lower_pass if x[0] > 1]
+    lower_phase2 = [x[1] for x in add_lower_pass if x[0] == 2]
+    lower_phase3 = [x[1] for x in add_lower_pass if x[0] > 2]
     # normalize schedule first
     sch = sch.normalize()
     # Phase 0
@@ -213,6 +214,9 @@ def lower(sch,
     # Phase 1
     stmt = ir_pass.StorageFlatten(stmt, binds, 64)
     stmt = ir_pass.CanonicalSimplify(stmt)
+    for f in lower_phase1:
+        stmt = f(stmt)
+    # Phase 2
     if not simple_mode:
         stmt = ir_pass.LoopPartition(stmt)
     stmt = ir_pass.VectorizeLoop(stmt)
@@ -224,14 +228,14 @@ def lower(sch,
         cfg.auto_unroll_max_step,
         cfg.auto_unroll_max_depth,
         cfg.unroll_explicit)
-    for f in lower_phase1:
+    for f in lower_phase2:
         stmt = f(stmt)
     # Phase 2
     stmt = ir_pass.Simplify(stmt)
     stmt = ir_pass.LowerStorageAccessInfo(stmt)
     stmt = ir_pass.RemoveNoOp(stmt)
     stmt = ir_pass.RewriteUnsafeSelect(stmt)
-    for f in lower_phase2:
+    for f in lower_phase3:
         stmt = f(stmt)
     if simple_mode:
         return stmt
diff --git a/src/pass/coproc_sync.cc b/src/pass/coproc_sync.cc
index fa77942b6058..28be8aba2057 100644
--- a/src/pass/coproc_sync.cc
+++ b/src/pass/coproc_sync.cc
@@ -338,6 +338,256 @@ class CoProcBarrierDetector : public StorageAccessVisitor {
 };
 
 
+class CoProcInstDepDetector : public IRVisitor {
+ public:
+  explicit CoProcInstDepDetector(
+      const IterVar& coproc_axis,
+      const std::string& coproc_name)
+      : coproc_axis_(coproc_axis) {
+    sync_push_name_ = coproc_name + ".coproc_dep_push";
+    sync_pop_name_ = coproc_name + ".coproc_dep_pop";
+  }
+
+  void Plan(Stmt stmt) {
+    this->Visit(stmt);
+    if (last_state_.node != nullptr) {
+      MatchFixEnterPop(first_state_);
+      MatchFixExitPush(last_state_);
+    }
+  }
+
+  void Visit_(const AttrStmt* op) final {
+    if (op->attr_key == attr::coproc_scope &&
+        op->node.same_as(coproc_axis_)) {
+      const IntImm* ctx_id = op->value.as<IntImm>();
+      CHECK(ctx_id != nullptr);
+      curr_state_.clear();
+      curr_state_.node = op->body.get();
+      curr_state_.enter_ctx.insert(ctx_id->value);
+      curr_state_.exit_ctx.insert(ctx_id->value);
+      UpdateState();
+    } else {
+      IRVisitor::Visit_(op);
+    }
+  }
+
+  void Visit_(const For* op) final {
+    SyncState temp_first, temp_last;
+    std::swap(first_state_, temp_first);
+    std::swap(last_state_, temp_last);
+    this->Visit(op->body);
+    curr_state_.clear();
+    if (last_state_.node != nullptr) {
+      curr_state_.node = op;
+      CHECK(first_state_.node != nullptr);
+      // loop carry dependency
+      InjectSync(last_state_, first_state_,
+                 &(curr_state_.exit_push),
+                 &(curr_state_.enter_pop));
+      curr_state_.enter_ctx = first_state_.enter_ctx;
+      curr_state_.exit_ctx = last_state_.enter_ctx;
+    }
+    std::swap(first_state_, temp_first);
+    std::swap(last_state_, temp_last);
+    if (curr_state_.node != nullptr) {
+      UpdateState();
+    }
+  }
+
+  void Visit_(const IfThenElse* op) final {
+    SyncState temp_first, temp_last, curr_state;
+    std::swap(first_state_, temp_first);
+    std::swap(last_state_, temp_last);
+    {
+      // then stmt
+      this->Visit(op->then_case);
+      if (last_state_.node != nullptr) {
+        curr_state.node = op;
+        MatchFixEnterPop(first_state_);
+        MatchFixExitPush(last_state_);
+        curr_state.enter_ctx.insert(
+            first_state_.enter_ctx.begin(),
+            first_state_.enter_ctx.end());
+        curr_state.exit_ctx.insert(
+            last_state_.exit_ctx.begin(),
+            last_state_.exit_ctx.end());
+      }
+      first_state_.clear();
+      last_state_.clear();
+    }
+    if (op->else_case.defined()) {
+      this->Visit(op->else_case);
+      if (last_state_.node != nullptr) {
+        curr_state.node = op;
+        MatchFixEnterPop(first_state_);
+        MatchFixExitPush(last_state_);
+        curr_state.enter_ctx.insert(
+            first_state_.enter_ctx.begin(),
+            first_state_.enter_ctx.end());
+        curr_state.exit_ctx.insert(
+            last_state_.exit_ctx.begin(),
+            last_state_.exit_ctx.end());
+      }
+    }
+    // update in the trace.
+    std::swap(first_state_, temp_first);
+    std::swap(last_state_, temp_last);
+    std::swap(curr_state_, curr_state);
+    if (curr_state_.node != nullptr) {
+      UpdateState();
+    }
+  }
+
+  // insert before is stored in reverse order
+  // the first element is closest to the node.
+  std::unordered_map<const Node*, std::vector<Stmt> > insert_before_;
+  std::unordered_map<const Node*, std::vector<Stmt> > insert_after_;
+
+ private:
+  // state in the sync entry
+  struct SyncState {
+    // The statement of the state.
+    const Node* node{nullptr};
+    // Set of all possible contexts in the entering moment.
+    std::unordered_set<int> enter_ctx;
+    // Set of all possible contexts in the exit moment.
+    std::unordered_set<int> exit_ctx;
+    // existing pop performed at enter
+    std::vector<std::pair<int, int> > enter_pop;
+    // existing push peformed at exit
+    std::vector<std::pair<int, int> > exit_push;
+    // clear the state
+    void clear() {
+      node = nullptr;
+      enter_ctx.clear();
+      exit_ctx.clear();
+      enter_pop.clear();
+      exit_push.clear();
+    }
+  };
+  // inject proper sync into the pair
+  // record the push/pop sequence that could be possibly un-matched.
+  // return the push/pop message at enter/exit of the Block
+  // after considering the existing unmatcheded events and added events
+  void InjectSync(const SyncState& prev,
+                  const SyncState& next,
+                  std::vector<std::pair<int, int> >* prev_exit_push,
+                  std::vector<std::pair<int, int> >* next_enter_pop) {
+    prev_exit_push->clear();
+    next_enter_pop->clear();
+    // quick path
+    if (prev.exit_push.size() == 0 && next.enter_pop.size() == 0 &&
+        prev.exit_ctx.size() == 1 && next.enter_ctx.size() == 1) {
+      int from = *prev.exit_ctx.begin();
+      int to = *next.enter_ctx.begin();
+      if (from != to) {
+        insert_after_[prev.node].emplace_back(MakePush(from, to));
+        insert_before_[next.node].emplace_back(MakePop(from, to));
+        prev_exit_push->emplace_back(std::make_pair(from, to));
+        next_enter_pop->emplace_back(std::make_pair(from, to));
+      }
+      return;
+    }
+    // complicate path.
+    std::vector<std::pair<int, int> > vpush = prev.exit_push;
+    std::vector<std::pair<int, int> > vpop = next.enter_pop;
+    std::vector<std::pair<int, int> > pending;
+    for (int from : prev.exit_ctx) {
+      for (int to : next.enter_ctx) {
+        if (from != to) {
+          pending.emplace_back(std::make_pair(from, to));
+        }
+      }
+    }
+    // policy 1
+    std::vector<Stmt> prev_after, next_before;
+    for (const std::pair<int, int>& p : pending) {
+      if (std::find(prev.exit_push.begin(),
+                    prev.exit_push.end(), p) ==
+          prev.exit_push.end()) {
+        vpush.push_back(p);
+        prev_after.emplace_back(MakePush(p.first, p.second));
+      }
+      if (std::find(next.enter_pop.begin(),
+                    next.enter_pop.end(), p) ==
+          next.enter_pop.end()) {
+        vpop.push_back(p);
+        next_before.emplace_back(MakePop(p.first, p.second));
+      }
+    }
+    // fix pending
+    for (const std::pair<int, int>& p : vpush) {
+      if (std::find(vpop.begin(), vpop.end(), p) == vpop.end()) {
+        prev_after.emplace_back(MakePop(p.first, p.second));
+      } else {
+        prev_exit_push->push_back(p);
+      }
+    }
+    for (const std::pair<int, int>& p : vpop) {
+      if (std::find(vpush.begin(), vpush.end(), p) == vpush.end()) {
+        next_before.emplace_back(MakePush(p.first, p.second));
+      } else {
+        next_enter_pop->push_back(p);
+      }
+    }
+    if (prev_after.size() != 0) {
+      auto &v1 = insert_after_[prev.node];
+      v1.insert(v1.end(), prev_after.begin(), prev_after.end());
+    }
+    if (next_before.size() != 0) {
+      auto &v2 = insert_before_[next.node];
+      v2.insert(v2.end(), next_before.begin(), next_before.end());
+    }
+  }
+
+  void MatchFixEnterPop(const SyncState& state) {
+    if (state.enter_pop.size() == 0) return;
+    auto &vec = insert_before_[state.node];
+    for (const std::pair<int, int>& p : state.enter_pop) {
+      vec.push_back(MakePush(p.first, p.second));
+    }
+  }
+
+  void MatchFixExitPush(const SyncState& state) {
+    if (state.exit_push.size() == 0) return;
+    auto &vec = insert_after_[state.node];
+    for (const std::pair<int, int>& p : state.exit_push) {
+      vec.push_back(MakePop(p.first, p.second));
+    }
+  }
+
+  void UpdateState() {
+    if (last_state_.node != nullptr) {
+      std::vector<std::pair<int, int> > t1, t2;
+      InjectSync(last_state_, curr_state_, &t1, &t2);
+      std::swap(last_state_, curr_state_);
+    } else {
+      CHECK(first_state_.node == nullptr);
+      first_state_ = curr_state_;
+      last_state_ = curr_state_;
+    }
+  }
+
+  Stmt MakePush(int from, int to) {
+    return Evaluate::make(Call::make(
+        Int(32), sync_push_name_,
+        {make_const(Int(32), from), make_const(Int(32), to)},
+        Call::Intrinsic));
+  }
+  Stmt MakePop(int from, int to) {
+    return Evaluate::make(Call::make(
+        Int(32), sync_pop_name_,
+        {make_const(Int(32), from), make_const(Int(32), to)},
+        Call::Intrinsic));
+  }
+  // sync states.
+  SyncState first_state_, last_state_, curr_state_;
+  // Variables
+  IterVar coproc_axis_;
+  std::string sync_push_name_, sync_pop_name_;
+};
+
+
 class CoProcSyncInserter : public IRMutator {
  public:
   Stmt Insert(Stmt stmt) {
@@ -372,6 +622,18 @@ class CoProcSyncInserter : public IRMutator {
       auto& vec = insert_after_[kv.first];
       vec.insert(vec.end(), kv.second.begin(), kv.second.end());
     }
+    // Detect barrier
+    CoProcInstDepDetector sync_detector(
+        *visitor.coproc_.begin(), coproc_name);
+    sync_detector.Plan(stmt);
+    for (const auto& kv : sync_detector.insert_before_) {
+      auto& vec = insert_before_[kv.first];
+      vec.insert(vec.end(), kv.second.begin(), kv.second.end());
+    }
+    for (const auto& kv : sync_detector.insert_after_) {
+      auto& vec = insert_after_[kv.first];
+      vec.insert(vec.end(), kv.second.begin(), kv.second.end());
+    }
     return Mutate(stmt);
   }
 
@@ -379,7 +641,8 @@ class CoProcSyncInserter : public IRMutator {
     Stmt before, after;
     auto it = insert_before_.find(stmt.get());
     if (it != insert_before_.end()) {
-      before = MergeSeq(it->second);
+      before = MergeSeq(std::vector<Stmt>(
+          it->second.rbegin(), it->second.rend()));
     }
     it = insert_after_.find(stmt.get());
     if (it != insert_after_.end()) {
@@ -396,10 +659,13 @@ class CoProcSyncInserter : public IRMutator {
   }
 
  private:
+  // insert before is stored in reverse order
+  // the first element is closest to the node.
   std::unordered_map<const Node*, std::vector<Stmt> > insert_before_;
   std::unordered_map<const Node*, std::vector<Stmt> > insert_after_;
 };
 
+
 Stmt CoProcSync(Stmt stmt) {
   return CoProcSyncInserter().Insert(stmt);
 }
diff --git a/src/pass/storage_rewrite.cc b/src/pass/storage_rewrite.cc
index 933a84598b88..2f3616017215 100644
--- a/src/pass/storage_rewrite.cc
+++ b/src/pass/storage_rewrite.cc
@@ -189,7 +189,7 @@ class StoragePlanRewriter : public IRMutator {
     if (attach_map_.count(nullptr)) {
       std::vector<Stmt> nest;
       for (StorageEntry* e : attach_map_.at(nullptr)) {
-        CHECK_EQ(e->scope.rank, 0);
+        // CHECK_EQ(e->scope.rank, 0);
         if (e->new_alloc.defined()) {
           nest.emplace_back(AttrStmt::make(
               e->alloc_var, attr::storage_scope,
@@ -395,6 +395,12 @@ class StoragePlanRewriter : public IRMutator {
           e->new_alloc = Allocate::make(
               e->alloc_var, alloc_type, e->allocs[0]->extents,
               e->allocs[0]->condition, Evaluate::make(0));
+          if (e->scope.tag.length() != 0) {
+            MemoryInfo info = GetMemoryInfo(e->scope.to_string());
+            uint64_t total_elem = e->const_nbits / e->elem_type.bits();
+            CHECK_LE(total_elem * e->elem_type.bits(), info->max_num_bits)
+                << "Allocation exceed bound of memory tag " << e->scope.to_string();
+          }
         } else {
           // Build a merged allocation
           Expr combo_size;
diff --git a/src/runtime/thread_storage_scope.h b/src/runtime/thread_storage_scope.h
index e2767647dd77..48b5e8f1ef16 100644
--- a/src/runtime/thread_storage_scope.h
+++ b/src/runtime/thread_storage_scope.h
@@ -71,7 +71,7 @@ struct ThreadScope {
    */
   static ThreadScope make(const std::string& s) {
     ThreadScope r;
-    if (s == "vthread") {
+    if (s == "vthread" || s == "cthread") {
       // virtual thread at the same level as local
       r.rank = 1;
       r.dim_index = -1;
diff --git a/tests/python/unittest/test_pass_storage_sync.py b/tests/python/unittest/test_pass_storage_sync.py
index 8360ed2f0c3f..ce9e2f9a4af9 100644
--- a/tests/python/unittest/test_pass_storage_sync.py
+++ b/tests/python/unittest/test_pass_storage_sync.py
@@ -58,6 +58,27 @@ def meminfo_cache():
     assert(blist[-1].value.args[3].value == 10)
 
 
+def test_coproc_sync2():
+    ib = tvm.ir_builder.create()
+    n = tvm.var("n")
+    cp = tvm.thread_axis((0, 1), "cop")
+    ty = tvm.thread_axis("cthread")
+    A = ib.allocate("float32", 128, name="A")
+    ib.scope_attr(ty, "virtual_thread", 2)
+    with ib.new_scope():
+        ib.scope_attr(cp, "coproc_scope", 2)
+        A[ty] = 0.0
+    with ib.for_range(0, n, name="i") as i:
+        with ib.new_scope():
+            ib.scope_attr(cp, "coproc_scope", 1)
+            A[ty] = 1.0
+        with ib.new_scope():
+            ib.scope_attr(cp, "coproc_scope", 2)
+            A[ty] = 1.0
+    stmt = ib.get()
+    stmt = tvm.ir_pass.CoProcSync(stmt)
+
 if __name__ == "__main__":
     test_coproc_sync()
     test_storage_sync()
+    test_coproc_sync2()

From f5990efce668b56515c52e7e69ce5795a00a3b87 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 12 Nov 2017 14:50:00 -0800
Subject: [PATCH 014/949] [CODEGEN] Enable closure with no argument (#635)

---
 src/codegen/llvm/codegen_cpu.cc | 18 ++++++++++++------
 src/codegen/llvm/codegen_cpu.h  |  2 +-
 2 files changed, 13 insertions(+), 7 deletions(-)

diff --git a/src/codegen/llvm/codegen_cpu.cc b/src/codegen/llvm/codegen_cpu.cc
index bc04a6337f28..ea5d90bccc1f 100644
--- a/src/codegen/llvm/codegen_cpu.cc
+++ b/src/codegen/llvm/codegen_cpu.cc
@@ -337,7 +337,11 @@ void CodeGenCPU::CreateComputeScope(const AttrStmt* op) {
   builder_->SetInsertPoint(compute_call_end);
 }
 
-llvm::Value* CodeGenCPU::PackClosureData(const Array<Var>& vfields) {
+llvm::Value* CodeGenCPU::PackClosureData(const Array<Var>& vfields, uint64_t* num_bytes) {
+  if (vfields.size() == 0) {
+    *num_bytes = 0U;
+    return llvm::Constant::getNullValue(t_void_p_);
+  }
   std::vector<llvm::Type*> fields;
   for (Var v : vfields) {
     auto it = var_map_.find(v.get());
@@ -352,6 +356,8 @@ llvm::Value* CodeGenCPU::PackClosureData(const Array<Var>& vfields) {
         var_map_.at(vfields[i].get()),
         builder_->CreateInBoundsGEP(cdata, {zero, ConstInt32(i)}));
   }
+  *num_bytes = data_layout_->getTypeAllocSize(
+      llvm::cast<llvm::PointerType>(cdata->getType())->getElementType());
   return cdata;
 }
 
@@ -374,7 +380,8 @@ void CodeGenCPU::CreateParallelLaunch(const Stmt& body, int num_task) {
       "__tvm_parallel_lambda", module_.get());
   // allocate and setup the closure, call the closure.
   Array<Var> vfields = ir::UndefinedVars(body, {});
-  llvm::Value* cdata = PackClosureData(vfields);
+  uint64_t nbytes;
+  llvm::Value* cdata = PackClosureData(vfields, &nbytes);
   BasicBlock* par_launch_end = CheckCallSuccess(
       builder_->CreateCall(
           RuntimeTVMParallelLaunch(),
@@ -431,14 +438,13 @@ void CodeGenCPU::CreateStaticInit(const std::string& init_fname, const Stmt& bod
         ftype_tvm_static_init_, llvm::Function::ExternalLinkage, init_fname, module_.get());
   }
   // allocate and setup the closure, call the closure.
+  uint64_t nbytes;
   Array<Var> vfields = ir::UndefinedVars(body, {});
-  llvm::Value* cdata = PackClosureData(vfields);
-  llvm::Value* nbytes = ConstInt32(data_layout_->getTypeAllocSize(
-      llvm::cast<llvm::PointerType>(cdata->getType())->getElementType()));
+  llvm::Value* cdata = PackClosureData(vfields, &nbytes);
   BasicBlock* init_end = CheckCallSuccess(
       builder_->CreateCall(
           finit,
-          {gv, f, builder_->CreatePointerCast(cdata, t_void_p_), nbytes}));
+          {gv, f, builder_->CreatePointerCast(cdata, t_void_p_), ConstInt32(nbytes)}));
   // Setup the closure function.
   BasicBlock *lambda_entry = BasicBlock::Create(*ctx_, "entry", f);
   builder_->SetInsertPoint(lambda_entry);
diff --git a/src/codegen/llvm/codegen_cpu.h b/src/codegen/llvm/codegen_cpu.h
index ac7f2e34dc64..702d8777d50b 100644
--- a/src/codegen/llvm/codegen_cpu.h
+++ b/src/codegen/llvm/codegen_cpu.h
@@ -73,7 +73,7 @@ class CodeGenCPU : public CodeGenLLVM {
   llvm::Value* RuntimeTVMParallelLaunch();
   llvm::Value* RuntimeTVMParallelBarrier();
   llvm::Value* GetPackedFuncHandle(const std::string& str);
-  llvm::Value* PackClosureData(const Array<Var>& fields);
+  llvm::Value* PackClosureData(const Array<Var>& fields, uint64_t *num_bytes);
   llvm::Value* CreateStructRefPtr(Type t, llvm::Value* buffer, llvm::Value* index, int kind);
   void UnpackClosureData(llvm::Value*cdata,
                          const Array<Var>& fields,

From 22652d626b1e9c5af588634bbf1400b5f9194686 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 12 Nov 2017 16:30:02 -0800
Subject: [PATCH 015/949] [PASS] Fix vthread when extern access touching (#636)

---
 src/pass/inject_virtual_thread.cc             | 50 ++++++++++++++++---
 .../unittest/test_pass_inject_vthread.py      | 34 +++++++++++++
 2 files changed, 76 insertions(+), 8 deletions(-)

diff --git a/src/pass/inject_virtual_thread.cc b/src/pass/inject_virtual_thread.cc
index 07f59cb1b308..28e90ec4805b 100644
--- a/src/pass/inject_virtual_thread.cc
+++ b/src/pass/inject_virtual_thread.cc
@@ -15,11 +15,12 @@ namespace ir {
 // If expression is touched by var.
 class ExprTouched final : public IRVisitor {
  public:
-  explicit ExprTouched(const std::unordered_set<const Variable*> &touched)
-      : touched_var_(touched) {}
+  explicit ExprTouched(const std::unordered_set<const Variable*> &touched,
+                       bool check_write)
+      : touched_var_(touched), check_write_(check_write) {}
   void Visit(const NodeRef& n) final {
     // early stopping
-    if (expr_touched_) return;
+    if (expr_touched_ && !check_write_) return;
     IRVisitor::Visit(n);
   }
   void Visit_(const Load *op) final {
@@ -29,6 +30,24 @@ class ExprTouched final : public IRVisitor {
   void Visit_(const Variable *op) final {
     HandleUseVar(op);
   }
+  void Visit_(const Call *op) final {
+    if (op->is_intrinsic(intrinsic::tvm_access_ptr)) {
+      int rw_mask;
+      CHECK(arith::GetConstInt(op->args[4], &rw_mask));
+      const Variable* buffer_var = op->args[1].as<Variable>();
+      CHECK(buffer_var);
+      // read
+      if (rw_mask & 1) {
+        HandleUseVar(buffer_var);
+      }
+      if (rw_mask & 2) {
+        HandleWriteVar(buffer_var);
+      }
+      this->Visit(op->args[2]);
+    } else {
+      IRVisitor::Visit_(op);
+    }
+  }
   void HandleUseVar(const Variable* var) {
     auto it = touched_var_.find(var);
     if (it != touched_var_.end()) {
@@ -40,36 +59,49 @@ class ExprTouched final : public IRVisitor {
       used_vars_.push_back(var);
     }
   }
+  void HandleWriteVar(const Variable* var) {
+    write_vars_.push_back(var);
+  }
   // the fields.
   bool expr_touched_{false};
   std::vector<const Variable*> used_vars_;
+  std::vector<const Variable*> write_vars_;
   const std::unordered_set<const Variable*>& touched_var_;
+  bool check_write_;
 };
 
 // Analyze if the buffers are invariant to value of var
 class VarTouchedAnalysis : public IRVisitor {
  public:
   void Visit_(const LetStmt *op) {
-    ExprTouched tc(touched_var_);
+    ExprTouched tc(touched_var_, false);
     tc.Visit(op->value);
     Record(op->var.get(), tc);
     this->Visit(op->body);
   }
   void Visit_(const Store *op) {
-    ExprTouched tc(touched_var_);
+    ExprTouched tc(touched_var_, false);
     tc.Visit(op->value);
     tc.Visit(op->index);
     Record(op->buffer_var.get(), tc);
   }
   void Visit_(const For *op) {
-    ExprTouched tc(touched_var_);
+    ExprTouched tc(touched_var_, false);
     tc.Visit(op->min);
     tc.Visit(op->extent);
     Record(op->loop_var.get(), tc);
     this->Visit(op->body);
   }
+  // external function call
+  void Visit_(const Evaluate *op) {
+    ExprTouched tc(touched_var_, true);
+    tc.Visit(op->value);
+    for (const Variable* var : tc.write_vars_) {
+      Record(var, tc);
+    }
+  }
   void Visit_(const Allocate *op) {
-    ExprTouched tc(touched_var_);
+    ExprTouched tc(touched_var_, false);
     for (size_t i = 0; i < op->extents.size(); ++i) {
       tc.Visit(op->extents[i]);
     }
@@ -87,7 +119,9 @@ class VarTouchedAnalysis : public IRVisitor {
       touched_var_.insert(var);
     } else {
       for (const Variable* r : tc.used_vars_) {
-        affect_[r].push_back(var);
+        if (r != var) {
+          affect_[r].push_back(var);
+        }
       }
     }
   }
diff --git a/tests/python/unittest/test_pass_inject_vthread.py b/tests/python/unittest/test_pass_inject_vthread.py
index e4b3b51fbd2d..502a55574df0 100644
--- a/tests/python/unittest/test_pass_inject_vthread.py
+++ b/tests/python/unittest/test_pass_inject_vthread.py
@@ -28,5 +28,39 @@ def get_vthread(name):
     stmt = tvm.ir_pass.InjectVirtualThread(get_vthread("cthread"))
     assert len(stmt.body.body.extents) == 3
 
+
+def test_vthread_extern():
+    dtype = 'int64'
+    n = 100
+    m = 4
+    nthread = 2
+    def get_vthread(name):
+        tx = tvm.thread_axis(name)
+        ty = tvm.thread_axis(name)
+        ib = tvm.ir_builder.create()
+        with ib.for_range(0, n) as i:
+            ib.scope_attr(tx, "virtual_thread", nthread)
+            ib.scope_attr(ty, "virtual_thread", nthread)
+            A = ib.allocate("float32", m, name="A", scope="shared")
+            B = ib.allocate("float32", m, name="B", scope="shared")
+            C = ib.allocate("float32", m, name="C", scope="shared")
+            cbuffer = tvm.decl_buffer((m,), dtype=C.dtype, data=C.asnode())
+            abuffer = tvm.decl_buffer((m,), dtype=A.dtype, data=A.asnode())
+            bbuffer = tvm.decl_buffer((m,), dtype=B.dtype, data=B.asnode())
+            A[tx] = tx + 1.0
+            B[ty] = ty + 1.0
+            ib.emit(tvm.call_extern("int32", "Run",
+                                    abuffer.access_ptr("r"),
+                                    bbuffer.access_ptr("r"),
+                                    cbuffer.access_ptr("rw")))
+        return ib.get()
+
+    stmt = tvm.ir_pass.InjectVirtualThread(get_vthread("vthread"))
+    assert stmt.body.body.extents[0].value == 2
+    assert stmt.body.body.body.body.body.body.extents[0].value == 2
+    assert len(stmt.body.body.body.body.body.body.extents) == 3
+
+
 if __name__ == "__main__":
+    test_vthread_extern()
     test_vthread()

From 80c434dd076fd12c6aaf5e7cba422da53672b214 Mon Sep 17 00:00:00 2001
From: abergeron <bergearn@iro.umontreal.ca>
Date: Mon, 13 Nov 2017 15:56:47 -0500
Subject: [PATCH 016/949] Fix conda packages (#642)

* Make the tvm conda package build with in-place source and use cmake from conda.

* Add a package for topi.
---
 python/conda/build.sh       |  2 +-
 python/conda/meta.yaml      |  1 +
 topi/python/conda/meta.yaml | 30 ++++++++++++++++++++++++++++++
 3 files changed, 32 insertions(+), 1 deletion(-)
 create mode 100644 topi/python/conda/meta.yaml

diff --git a/python/conda/build.sh b/python/conda/build.sh
index 4965abc56b54..60582de9523b 100644
--- a/python/conda/build.sh
+++ b/python/conda/build.sh
@@ -26,7 +26,7 @@ mkdir -p build
 cd build
 # Enable static-libstdc++ to make it easier to link this library with
 # other C++ compilers
-CXXFLAGS=-static-libstdc++ cmake3 -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DUSE_CUDA=1 -DUSE_LLVM=1 -DINSTALL_DEV=1 $CUDA_ARGS ..
+CXXFLAGS=-static-libstdc++ cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DUSE_CUDA=1 -DUSE_LLVM=1 -DINSTALL_DEV=1 $CUDA_ARGS ..
 make -j20 VERBOSE=1
 make install/fast
 cd ..
diff --git a/python/conda/meta.yaml b/python/conda/meta.yaml
index 9ebb5afac543..f4e64dfd6bef 100644
--- a/python/conda/meta.yaml
+++ b/python/conda/meta.yaml
@@ -16,6 +16,7 @@ build:
 requirements:
   build:
     - llvmdev ==4.0.0
+    - cmake
     - python >=3
     - numpy
     - setuptools
diff --git a/topi/python/conda/meta.yaml b/topi/python/conda/meta.yaml
new file mode 100644
index 000000000000..37d3a35da433
--- /dev/null
+++ b/topi/python/conda/meta.yaml
@@ -0,0 +1,30 @@
+{% set version = "0.1.dev" %}
+
+package:
+  name: topi
+  version: {{ version }}
+
+source:
+  path: ..
+
+build:
+  number: 0
+  script: $PYTHON setup.py install
+
+requirements:
+  build:
+    - cmake
+    - python >=3
+    - numpy
+    - setuptools
+    - nose
+    - decorator
+  run:
+    - python >=3
+    - numpy
+    - decorator
+
+about:
+  home: https://github.com/dmlc/tvm
+  license: Apache2
+  summary: "TOPI: TVM Operator Inventory"

From a3d455e5ad1147b8b5b3d5ade3c1338f9fa7753c Mon Sep 17 00:00:00 2001
From: Leyuan Wang <laurawly@gmail.com>
Date: Mon, 13 Nov 2017 21:07:01 -0800
Subject: [PATCH 017/949] conv2d perf improved for conv2d_56_64_128, super
 resolution workloads added (#643)

* conv2d perf improved for conv2d_56_64_128, test name added to differentiate workloads

* fix lint error
---
 topi/python/topi/cuda/conv2d_nchw.py       | 32 ++++++++++++++--------
 topi/tests/python/test_topi_conv2d_nchw.py | 14 +++++++---
 2 files changed, 31 insertions(+), 15 deletions(-)

diff --git a/topi/python/topi/cuda/conv2d_nchw.py b/topi/python/topi/cuda/conv2d_nchw.py
index fea0f542f448..b6bd35768112 100644
--- a/topi/python/topi/cuda/conv2d_nchw.py
+++ b/topi/python/topi/cuda/conv2d_nchw.py
@@ -95,20 +95,30 @@ def conv2d_56_64_128(s, temp, temp_R, temp_S, Filter_S, Out, Out_L, flag):
         thread_yz = tvm.thread_axis((0, vthread_y), "vthread", name="vy")
 
         i, oc, h, w = s[Out].op.axis
-        ow, iw = s[Out].split(w, factor=num_thread_x)
-        oh, ih = s[Out].split(h, factor=vthread_x)
+        factor = util.get_const_int(Out.shape[3])
         ooc, ioc = s[Out].split(oc, factor=num_thread_y*vthread_y)
         oioc, iioc = s[Out].split(ioc, nparts=vthread_y)
-        s[Out].reorder(i, ooc, oh, ow, oioc, ih, iioc, iw)
-        oh = s[Out].fuse(oh, ow)
-        s[Out].bind(iw, thread_x)
         s[Out].bind(iioc, thread_y)
-        s[Out].bind(ih, thread_xz)
         s[Out].bind(oioc, thread_yz)
-        s[Out].bind(oh, block_x)
         s[Out].bind(ooc, block_y)
-
-        s[Out_L].compute_at(s[Out], iw)
+        if factor < num_thread_x*vthread_x:
+            oh, ih = s[Out].split(h, factor=num_thread_x*vthread_x//factor)
+            w = s[Out].fuse(ih, w)
+            ow, iw = s[Out].split(w, nparts=vthread_x)
+            s[Out].reorder(i, ooc, oh, oioc, ow, iioc, iw)
+            s[Out].bind(iw, thread_x)
+            s[Out].bind(ow, thread_xz)
+            s[Out].bind(oh, block_x)
+            s[Out_L].compute_at(s[Out], iw)
+        else:
+            ow, iw = s[Out].split(w, factor=num_thread_x)
+            oh, ih = s[Out].split(h, factor=vthread_x)
+            s[Out].reorder(i, ooc, oh, ow, oioc, ih, iioc, iw)
+            oh = s[Out].fuse(oh, ow)
+            s[Out].bind(iw, thread_x)
+            s[Out].bind(ih, thread_xz)
+            s[Out].bind(oh, block_x)
+            s[Out_L].compute_at(s[Out], iw)
 
         # schedule Out_L local write
         i, oc, h, w = s[Out_L].op.axis
@@ -350,14 +360,14 @@ def conv2d_56_64_64(s, Filter, temp_S, Filter_S, Out, Out_L):
     if util.get_const_int(Filter.shape[0]) == 64:
         opart2 = 8
         ifactor = 16
-    sfactor = max(1, ofactor//(opart2*2))
+    sfactor = max(1, ofactor // (opart2*2))
     spart = max(1, (wfactor + vthread-1) // vthread)
 
     block_x = tvm.thread_axis("blockIdx.x")
     block_y = tvm.thread_axis("blockIdx.y")
     block_z = tvm.thread_axis("blockIdx.z")
     thread_x = tvm.thread_axis((0, num_thread), "threadIdx.x")
-    thread_y = tvm.thread_axis((0, num_thread), "threadIdx.y")
+    thread_y = tvm.thread_axis((0, wfactor // vthread), "threadIdx.y")
     thread_xz = tvm.thread_axis((0, vthread), "vthread", name="vx")
     thread_yz = tvm.thread_axis((0, vthread), "vthread", name="vy")
 
diff --git a/topi/tests/python/test_topi_conv2d_nchw.py b/topi/tests/python/test_topi_conv2d_nchw.py
index 6e18d9110520..86cfc9493cdc 100644
--- a/topi/tests/python/test_topi_conv2d_nchw.py
+++ b/topi/tests/python/test_topi_conv2d_nchw.py
@@ -6,7 +6,6 @@
 from tvm.contrib.pickle_memoize import memoize
 from topi.util import get_const_tuple
 
-
 def verify_conv2d_nchw(batch, in_channel, in_size, num_filter, kernel, stride, padding):
     in_height = in_width = in_size
 
@@ -42,10 +41,10 @@ def check_device(device):
         w = tvm.nd.array(w_np, ctx)
         b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
         c = tvm.nd.array(np.zeros(get_const_tuple(C.shape), dtype=C.dtype), ctx)
-        with tvm.build_config(auto_unroll_max_step=128,
+        with tvm.build_config(auto_unroll_max_step=1400,
                               unroll_explicit=(device != "cuda")):
-            func1 = tvm.build(s1, [A, W, B], device)
-            func2 = tvm.build(s2, [A, W, C], device)
+            func1 = tvm.build(s1, [A, W, B], device, name="conv2d_%d_%d_%d_%d_%d_%d_%d" % (batch, in_channel, in_size, num_filter, kernel, stride, padding))
+            func2 = tvm.build(s2, [A, W, C], device, name="relu_%d_%d_%d_%d_%d_%d_%d" % (batch, in_channel, in_size, num_filter, kernel, stride, padding))
             func1(a, w, b)
             func2(a, w, c)
             np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
@@ -56,6 +55,7 @@ def check_device(device):
 
 
 def test_conv2d_nchw():
+    # ResNet18 worklaods
     verify_conv2d_nchw(1, 3, 224, 64, 7, 3, 2)
     verify_conv2d_nchw(1, 64, 56, 64, 3, 1, 1)
     verify_conv2d_nchw(1, 64, 56, 64, 1, 1, 0)
@@ -68,7 +68,13 @@ def test_conv2d_nchw():
     verify_conv2d_nchw(1, 256, 14, 512, 3, 2, 1)
     verify_conv2d_nchw(1, 256, 14, 512, 1, 2, 0)
     verify_conv2d_nchw(1, 512, 7, 512, 3, 1, 1)
+    # Vgg16 workloads
     verify_conv2d_nchw(1, 128, 122, 128, 3, 1, 1)
+    # Super resolution workloads
+    verify_conv2d_nchw(1, 1, 224, 64, 5, 1, 2)
+    verify_conv2d_nchw(1, 64, 224, 64, 3, 1, 1)
+    verify_conv2d_nchw(1, 64, 224, 32, 3, 1, 1)
+    verify_conv2d_nchw(1, 32, 224, 9, 3, 1, 1)
 
 if __name__ == "__main__":
     test_conv2d_nchw()

From afa6a3c16434ff6beb7d31a54bfd607540943045 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 13 Nov 2017 21:25:10 -0800
Subject: [PATCH 018/949] [APP] improve parameter pack (#645)

---
 apps/howto_deploy/tvm_runtime_pack.cc | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/apps/howto_deploy/tvm_runtime_pack.cc b/apps/howto_deploy/tvm_runtime_pack.cc
index e5c65b66b71a..9a090d863729 100644
--- a/apps/howto_deploy/tvm_runtime_pack.cc
+++ b/apps/howto_deploy/tvm_runtime_pack.cc
@@ -44,10 +44,19 @@
 // #include "../../src/runtime/rpc/rpc_event_impl.cc"
 // #include "../../src/runtime/rpc/rpc_server_env.cc"
 
+// These macros enables the device API when uncommented.
+#define TVM_CUDA_RUNTIME 1
+#define TVM_METAL_RUNTIME 1
+#define TVM_OPENCL_RUNTIME 1
+
 // Uncomment the following lines to enable Metal
 // #include "../../src/runtime/metal/metal_device_api.mm"
 // #include "../../src/runtime/metal/metal_module.mm"
 
+// Uncomment the following lines to enable CUDA
+// #include "../../src/runtime/cuda/cuda_device_api.cc"
+// #include "../../src/runtime/cuda/cuda_runtime.cc"
+
 // Uncomment the following lines to enable OpenCL
 // #include "../../src/runtime/opencl/opencl_device_api.cc"
 // #include "../../src/runtime/opencl/opencl_module.cc"

From f344a04ab1e2e6a3df56dcce1710ae61e2b31192 Mon Sep 17 00:00:00 2001
From: ziheng <ziheng@apache.org>
Date: Tue, 14 Nov 2017 10:11:29 -0800
Subject: [PATCH 019/949] [TOPI] Add out_dtype argument for conv2d; Add x86
 schedules (#646)

* [TOPI] Add out_dtype argument for conv2d; Add x86 schedules

* Fix

* Fix lint

* Fix
---
 topi/python/topi/__init__.py              |  1 +
 topi/python/topi/nn/conv2d.py             | 80 ++++++++++++-----------
 topi/python/topi/nn/depthwise_conv2d.py   |  6 +-
 topi/python/topi/rasp/conv2d.py           | 19 +++---
 topi/python/topi/rasp/depthwise_conv2d.py | 28 ++++----
 topi/python/topi/x86/__init__.py          |  5 ++
 topi/python/topi/x86/conv2d.py            | 37 +++++++++++
 7 files changed, 111 insertions(+), 65 deletions(-)
 create mode 100644 topi/python/topi/x86/__init__.py
 create mode 100644 topi/python/topi/x86/conv2d.py

diff --git a/topi/python/topi/__init__.py b/topi/python/topi/__init__.py
index 1306f9d9cac8..62a9ae153052 100644
--- a/topi/python/topi/__init__.py
+++ b/topi/python/topi/__init__.py
@@ -14,6 +14,7 @@
 from .transform import *
 from .broadcast import *
 from . import nn
+from . import x86
 from . import cuda
 from . import rasp
 from . import testing
diff --git a/topi/python/topi/nn/conv2d.py b/topi/python/topi/nn/conv2d.py
index db3a6079f96a..cc1ee0198c3d 100644
--- a/topi/python/topi/nn/conv2d.py
+++ b/topi/python/topi/nn/conv2d.py
@@ -9,7 +9,7 @@
 
 # workload description of conv2d
 Workload = namedtuple('Workload',
-                      ['height', 'width', 'in_filter', 'out_filter',
+                      ['in_dtype', 'out_dtype', 'height', 'width', 'in_filter', 'out_filter',
                        'hkernel', 'wkernel', 'hpad', 'wpad', 'hstride', 'wstride'])
 
 # schedule description of spatial
@@ -22,36 +22,36 @@
 
 _WORKLOADS = [
     # workloads of resnet18 on imagenet
-    Workload(224, 224, 3, 64, 7, 7, 3, 3, 2, 2),
-    Workload(56, 56, 64, 64, 3, 3, 1, 1, 1, 1),
-    Workload(56, 56, 64, 64, 1, 1, 0, 0, 1, 1),
-    Workload(56, 56, 64, 128, 3, 3, 1, 1, 2, 2),
-    Workload(56, 56, 64, 128, 1, 1, 0, 0, 2, 2),
-    Workload(28, 28, 128, 128, 3, 3, 1, 1, 1, 1),
-    Workload(28, 28, 128, 256, 3, 3, 1, 1, 2, 2),
-    Workload(28, 28, 128, 256, 1, 1, 0, 0, 2, 2),
-    Workload(14, 14, 256, 256, 3, 3, 1, 1, 1, 1),
-    Workload(14, 14, 256, 512, 3, 3, 1, 1, 2, 2),
-    Workload(14, 14, 256, 512, 1, 1, 0, 0, 2, 2),
-    Workload(7, 7, 512, 512, 3, 3, 1, 1, 1, 1),
+    Workload('float32', 'float32', 224, 224, 3, 64, 7, 7, 3, 3, 2, 2),
+    Workload('float32', 'float32', 56, 56, 64, 64, 3, 3, 1, 1, 1, 1),
+    Workload('float32', 'float32', 56, 56, 64, 64, 1, 1, 0, 0, 1, 1),
+    Workload('float32', 'float32', 56, 56, 64, 128, 3, 3, 1, 1, 2, 2),
+    Workload('float32', 'float32', 56, 56, 64, 128, 1, 1, 0, 0, 2, 2),
+    Workload('float32', 'float32', 28, 28, 128, 128, 3, 3, 1, 1, 1, 1),
+    Workload('float32', 'float32', 28, 28, 128, 256, 3, 3, 1, 1, 2, 2),
+    Workload('float32', 'float32', 28, 28, 128, 256, 1, 1, 0, 0, 2, 2),
+    Workload('float32', 'float32', 14, 14, 256, 256, 3, 3, 1, 1, 1, 1),
+    Workload('float32', 'float32', 14, 14, 256, 512, 3, 3, 1, 1, 2, 2),
+    Workload('float32', 'float32', 14, 14, 256, 512, 1, 1, 0, 0, 2, 2),
+    Workload('float32', 'float32', 7, 7, 512, 512, 3, 3, 1, 1, 1, 1),
     # workloads of mobile net on imagenet
-    Workload(224, 224, 3, 32, 3, 3, 1, 1, 2, 2),
-    Workload(112, 112, 32, 64, 1, 1, 0, 0, 1, 1),
-    Workload(56, 56, 64, 128, 1, 1, 0, 0, 1, 1),
-    Workload(56, 56, 128, 128, 1, 1, 0, 0, 1, 1),
-    Workload(28, 28, 128, 256, 1, 1, 0, 0, 1, 1),
-    Workload(28, 28, 256, 256, 1, 1, 0, 0, 1, 1),
-    Workload(14, 14, 256, 512, 1, 1, 0, 0, 1, 1),
-    Workload(14, 14, 512, 512, 1, 1, 0, 0, 1, 1),
-    Workload(7, 7, 512, 1024, 1, 1, 0, 0, 1, 1),
-    Workload(7, 7, 1024, 1024, 1, 1, 0, 0, 1, 1),
+    Workload('float32', 'float32', 224, 224, 3, 32, 3, 3, 1, 1, 2, 2),
+    Workload('float32', 'float32', 112, 112, 32, 64, 1, 1, 0, 0, 1, 1),
+    Workload('float32', 'float32', 56, 56, 64, 128, 1, 1, 0, 0, 1, 1),
+    Workload('float32', 'float32', 56, 56, 128, 128, 1, 1, 0, 0, 1, 1),
+    Workload('float32', 'float32', 28, 28, 128, 256, 1, 1, 0, 0, 1, 1),
+    Workload('float32', 'float32', 28, 28, 256, 256, 1, 1, 0, 0, 1, 1),
+    Workload('float32', 'float32', 14, 14, 256, 512, 1, 1, 0, 0, 1, 1),
+    Workload('float32', 'float32', 14, 14, 512, 512, 1, 1, 0, 0, 1, 1),
+    Workload('float32', 'float32', 7, 7, 512, 1024, 1, 1, 0, 0, 1, 1),
+    Workload('float32', 'float32', 7, 7, 1024, 1024, 1, 1, 0, 0, 1, 1),
 ]
 
 # platform specific schedule
 _CONV_SCHEDULE = {}
 
 @tvm.target.generic_func
-def conv2d(data, kernel, stride, padding, layout='NCHW'):
+def conv2d(data, kernel, stride, padding, layout='NCHW', out_dtype='float32'):
     """Conv2D operator.
 
     Parameters
@@ -79,14 +79,14 @@ def conv2d(data, kernel, stride, padding, layout='NCHW'):
     # search platform specific declaration first
     # default declaration
     if layout == 'NCHW':
-        return conv2d_nchw(data, kernel, stride, padding)
+        return conv2d_nchw(data, kernel, stride, padding, out_dtype)
     elif layout == 'HWCN':
-        return conv2d_hwcn(data, kernel, stride, padding)
+        return conv2d_hwcn(data, kernel, stride, padding, out_dtype)
     else:
         raise ValueError("not support this layout {} yet".format(layout))
 
 
-def _get_workload(data, kernel, stride, padding):
+def _get_workload(data, kernel, stride, padding, out_dtype):
     """ Get the workload structure. """
     _, CI, IH, IW = [x.value for x in data.shape]
     CO, _, KH, KW = [x.value for x in kernel.shape]
@@ -95,7 +95,8 @@ def _get_workload(data, kernel, stride, padding):
         HSTR, WSTR = stride
     else:
         HSTR, WSTR = stride, stride
-    return Workload(IH, IW, CI, CO, KH, KW, HPAD, WPAD, HSTR, WSTR)
+    assert data.dtype == kernel.dtype, "Do not support inputs with different data types now."
+    return Workload(data.dtype, out_dtype, IH, IW, CI, CO, KH, KW, HPAD, WPAD, HSTR, WSTR)
 
 
 @tvm.target.generic_func
@@ -108,10 +109,10 @@ def _get_schedule(wkl):
     # This return has no use, merely to supress pylint warning
     return wkl
 
-def _spatial_pack(data, kernel, stride, padding):
+def _spatial_pack(data, kernel, stride, padding, out_dtype):
     """ Compute convolution with pack on spatial axes. """
     assert data.shape[0].value == 1, "spatial pack convolution only support batch size=1"
-    wkl = _get_workload(data, kernel, stride, padding)
+    wkl = _get_workload(data, kernel, stride, padding, out_dtype)
     sch = _get_schedule(wkl)
 
     H, W = wkl.height, wkl.width
@@ -158,8 +159,8 @@ def _spatial_pack(data, kernel, stride, padding):
     dw = tvm.reduce_axis((0, KW), name='dw')
 
     conv = tvm.compute(ovshape, lambda n, co, h, w, vh, vw, vc: \
-        tvm.sum(data_vec[n, h, w, ci, vh*HSTR+dh, vw*WSTR+dw] *
-                kernel_vec[co, ci, dh, dw, vc],
+        tvm.sum(data_vec[n, h, w, ci, vh*HSTR+dh, vw*WSTR+dw].astype(out_dtype) *
+                kernel_vec[co, ci, dh, dw, vc].astype(out_dtype),
                 axis=[ci, dh, dw]), name='conv')
 
     output = tvm.compute(oshape, lambda n, co, h, w:
@@ -169,10 +170,10 @@ def _spatial_pack(data, kernel, stride, padding):
     return output
 
 
-def _im2col_pack(data, kernel, stride, padding):
+def _im2col_pack(data, kernel, stride, padding, out_dtype):
     """ Compute convolution with im2col pack layout. """
     assert data.shape[0].value == 1, "im2col pack convolution only support batch size=1"
-    wkl = _get_workload(data, kernel, stride, padding)
+    wkl = _get_workload(data, kernel, stride, padding, out_dtype)
     sch = _get_schedule(wkl)
 
     N = 1
@@ -234,7 +235,7 @@ def _im2col_pack(data, kernel, stride, padding):
     return output
 
 
-def conv2d_nchw(Input, Filter, stride, padding):
+def conv2d_nchw(Input, Filter, stride, padding, out_dtype='float32'):
     """Convolution operator in NCHW layout.
 
     Parameters
@@ -280,11 +281,12 @@ def conv2d_nchw(Input, Filter, stride, padding):
     return tvm.compute(
         (batch, out_channel, out_height, out_width),
         lambda nn, ff, yy, xx: tvm.sum(
-            temp[nn, rc, yy * stride_h + ry, xx * stride_w + rx] * Filter[ff, rc, ry, rx],
+            temp[nn, rc, yy * stride_h + ry, xx * stride_w + rx].astype(out_dtype) *
+            Filter[ff, rc, ry, rx].astype(out_dtype),
             axis=[rc, ry, rx]), tag="conv2d_nchw")
 
 
-def conv2d_hwcn(Input, Filter, stride, padding):
+def conv2d_hwcn(Input, Filter, stride, padding, out_dtype='float32'):
     """Convolution operator in HWCN layout.
 
     Parameters
@@ -329,8 +331,8 @@ def conv2d_hwcn(Input, Filter, stride, padding):
     Output = tvm.compute(
         (out_height, out_width, out_channel, batch),
         lambda yy, xx, ff, nn: tvm.sum(
-            PaddedInput[yy * stride_h + ry, xx * stride_w + rx, rc, nn] * Filter[ry, rx, rc, ff],
-            axis=[ry, rx, rc]),
+            PaddedInput[yy * stride_h + ry, xx * stride_w + rx, rc, nn].astype(out_dtype) *
+            Filter[ry, rx, rc, ff].astype(out_dtype), axis=[ry, rx, rc]),
         name="Conv2dOutput", tag="conv2d_hwcn")
     return Output
 
diff --git a/topi/python/topi/nn/depthwise_conv2d.py b/topi/python/topi/nn/depthwise_conv2d.py
index 40aed1572db1..785bdab27738 100644
--- a/topi/python/topi/nn/depthwise_conv2d.py
+++ b/topi/python/topi/nn/depthwise_conv2d.py
@@ -9,7 +9,7 @@
 from ..util import simplify
 
 
-def depthwise_conv2d_nchw(Input, Filter, stride, padding):
+def depthwise_conv2d_nchw(Input, Filter, stride, padding, out_dtype='float32'):
     """Depthwise convolution nchw forward operator.
 
     Parameters
@@ -51,8 +51,8 @@ def depthwise_conv2d_nchw(Input, Filter, stride, padding):
     Output = tvm.compute(
         (batch, out_channel, out_height, out_width),
         lambda b, c, i, j: tvm.sum(
-            (PaddedInput[b, c/channel_multiplier, i*stride_h + di, j*stride_w + dj] *
-             Filter[c/channel_multiplier, c%channel_multiplier, di, dj]),
+            (PaddedInput[b, c/channel_multiplier, i*stride_h+di, j*stride_w+dj].astype(out_dtype) *
+             Filter[c/channel_multiplier, c%channel_multiplier, di, dj].astype(out_dtype)),
             axis=[di, dj]),
         name='DepthwiseConv2d', tag="depthwise_conv2d_nchw")
     return Output
diff --git a/topi/python/topi/rasp/conv2d.py b/topi/python/topi/rasp/conv2d.py
index 86cb8a9d01a0..6e5a1b335ccf 100644
--- a/topi/python/topi/rasp/conv2d.py
+++ b/topi/python/topi/rasp/conv2d.py
@@ -12,6 +12,7 @@
 from .. import generic
 
 _SCHEDULES = [
+    # float32 imagenet
     SpatialPack(1, 8, 4, 1, 4, True),
     SpatialPack(1, 7, 4, 2, 4, True),
     SpatialPack(1, 4, 8, 4, 1, True),
@@ -25,6 +26,7 @@
     Im2ColPack(7, 4, 1, 8, False),
     Im2ColPack(7, 4, 1, 16, False),
 
+    # float32 mobilenet
     SpatialPack(2, 2, 4, 28, 1, True),
     SpatialPack(1, 4, 8, 14, 1, False),
     SpatialPack(1, 2, 16, 8, 1, True),
@@ -47,12 +49,12 @@ def _schedule_conv2d(wkl):
 
 
 @conv2d.register("rasp")
-def _declaration_conv2d(data, kernel, stride, padding, layout):
+def _declaration_conv2d(data, kernel, stride, padding, layout, out_dtype):
     assert layout == 'NCHW', "only support NCHW convolution on rasp"
     assert data.shape[0].value == 1, "only support batch size=1 convolution on rasp"
-    wkl = _get_workload(data, kernel, stride, padding)
+    wkl = _get_workload(data, kernel, stride, padding, out_dtype)
     sch = _get_schedule(wkl)
-    return _SCH_TO_DECL_FUNC[type(sch)](data, kernel, stride, padding)
+    return _SCH_TO_DECL_FUNC[type(sch)](data, kernel, stride, padding, out_dtype)
 
 
 def _schedule_spatial_conv2d(s, data, data_pad, data_vec,
@@ -64,10 +66,8 @@ def _schedule_spatial_conv2d(s, data, data_pad, data_vec,
         stride = infer_stride(data, kernel, output)
     else:
         stride = infer_stride(data_pad, kernel, output)
-    wkl = _get_workload(data, kernel, stride, padding)
-
-    with tvm.target.rasp():
-        sch = _get_schedule(wkl)
+    wkl = _get_workload(data, kernel, stride, padding, output.dtype)
+    sch = _get_schedule(wkl)
 
     H, W = wkl.height, wkl.width
     CI, CO = wkl.in_filter, wkl.out_filter
@@ -172,7 +172,7 @@ def _schedule_im2col_conv2d(s, data, data_pad, data_col, data_vec,
         stride = infer_stride(data, kernel, output)
     else:
         stride = infer_stride(data_pad, kernel, output)
-    wkl = _get_workload(data, kernel, stride, padding)
+    wkl = _get_workload(data, kernel, stride, padding, output.dtype)
 
     with _target.rasp():
         sch = _get_schedule(wkl)
@@ -280,7 +280,7 @@ def _schedule_im2col_conv2d(s, data, data_pad, data_col, data_vec,
 
     return s
 
-@generic.schedule_conv2d_nchw.register(["cpu", "rasp"])
+@generic.schedule_conv2d_nchw.register(["rasp"])
 def schedule_conv2d(outs):
     """Create schedule for tensors"""
     s = tvm.create_schedule([x.op for x in outs])
@@ -294,6 +294,7 @@ def traverse(op):
             for tensor in op.input_tensors:
                 if tensor.op.input_tensors:
                     traverse(tensor.op)
+
         if 'spatial_conv_output' in op.tag:
             output = op.output(0)
             conv_out = op.input_tensors[0]
diff --git a/topi/python/topi/rasp/depthwise_conv2d.py b/topi/python/topi/rasp/depthwise_conv2d.py
index e695f0463852..a6fd691f843f 100644
--- a/topi/python/topi/rasp/depthwise_conv2d.py
+++ b/topi/python/topi/rasp/depthwise_conv2d.py
@@ -8,22 +8,22 @@
 from .. import generic
 
 _Workload = namedtuple('Workload',
-                       ['height', 'width', 'channel', 'multiplier',
+                       ['in_dtype', 'out_dtype', 'height', 'width', 'channel', 'multiplier',
                         'hkernel', 'wkernel', 'hpad', 'wpad', 'hstride', 'wstride'])
 
 _Schedule = namedtuple('Schedule', ['vh', 'vw', 'vc', 'bc', 'unroll'])
 
 # workloads of depthwise conv mobile net on imagenet
 _WORKLOADS = [
-    _Workload(112, 112, 32, 1, 3, 3, 1, 1, 1, 1),
-    _Workload(112, 112, 64, 1, 3, 3, 1, 1, 2, 2),
-    _Workload(56, 56, 128, 1, 3, 3, 1, 1, 1, 1),
-    _Workload(56, 56, 128, 1, 3, 3, 1, 1, 2, 2),
-    _Workload(28, 28, 256, 1, 3, 3, 1, 1, 1, 1),
-    _Workload(28, 28, 256, 1, 3, 3, 1, 1, 2, 2),
-    _Workload(14, 14, 512, 1, 3, 3, 1, 1, 1, 1),
-    _Workload(14, 14, 512, 1, 3, 3, 1, 1, 2, 2),
-    _Workload(14, 14, 1024, 1, 3, 3, 1, 1, 1, 1),
+    _Workload('float32', 'float32', 112, 112, 32, 1, 3, 3, 1, 1, 1, 1),
+    _Workload('float32', 'float32', 112, 112, 64, 1, 3, 3, 1, 1, 2, 2),
+    _Workload('float32', 'float32', 56, 56, 128, 1, 3, 3, 1, 1, 1, 1),
+    _Workload('float32', 'float32', 56, 56, 128, 1, 3, 3, 1, 1, 2, 2),
+    _Workload('float32', 'float32', 28, 28, 256, 1, 3, 3, 1, 1, 1, 1),
+    _Workload('float32', 'float32', 28, 28, 256, 1, 3, 3, 1, 1, 2, 2),
+    _Workload('float32', 'float32', 14, 14, 512, 1, 3, 3, 1, 1, 1, 1),
+    _Workload('float32', 'float32', 14, 14, 512, 1, 3, 3, 1, 1, 2, 2),
+    _Workload('float32', 'float32', 7, 7, 1024, 1, 3, 3, 1, 1, 1, 1),
 ]
 
 _SCHEDULES = [
@@ -35,10 +35,10 @@
     _Schedule(1, 1, 4, 2, True),
     _Schedule(1, 1, 8, 8, True),
     _Schedule(1, 1, 4, 1, False),
-    _Schedule(2, 1, 4, 16, False),
+    _Schedule(1, 1, 4, 4, False),
 ]
 
-def _get_workload(data, kernel, stride, padding):
+def _get_workload(data, kernel, stride, padding, out_dtype):
     _, C, IH, IW = [x.value for x in data.shape]
     _, MT, KH, KW = [x.value for x in kernel.shape]
     HPAD, WPAD, _, _ = get_pad_tuple(padding, kernel)
@@ -46,7 +46,7 @@ def _get_workload(data, kernel, stride, padding):
         HSTR, WSTR = stride
     else:
         HSTR, WSTR = stride, stride
-    return _Workload(IH, IW, C, MT, KH, KW, HPAD, WPAD, HSTR, WSTR)
+    return _Workload(data.dtype, out_dtype, IH, IW, C, MT, KH, KW, HPAD, WPAD, HSTR, WSTR)
 
 
 def _schedule(s, data, data_pad, kernel, output, last):
@@ -55,7 +55,7 @@ def _schedule(s, data, data_pad, kernel, output, last):
         stride = infer_stride(data, kernel, output)
     else:
         stride = infer_stride(data_pad, kernel, output)
-    wkl = _get_workload(data, kernel, stride, padding)
+    wkl = _get_workload(data, kernel, stride, padding, output.dtype)
 
     if wkl not in _WORKLOADS:
         return s
diff --git a/topi/python/topi/x86/__init__.py b/topi/python/topi/x86/__init__.py
new file mode 100644
index 000000000000..d9912de2870d
--- /dev/null
+++ b/topi/python/topi/x86/__init__.py
@@ -0,0 +1,5 @@
+# pylint: disable=redefined-builtin, wildcard-import
+"""x86 specific declaration and schedules."""
+from __future__ import absolute_import as _abs
+
+from .conv2d import schedule_conv2d
diff --git a/topi/python/topi/x86/conv2d.py b/topi/python/topi/x86/conv2d.py
new file mode 100644
index 000000000000..0c91f8c25c88
--- /dev/null
+++ b/topi/python/topi/x86/conv2d.py
@@ -0,0 +1,37 @@
+# pylint: disable=invalid-name,unused-variable,invalid-name
+"""Conv2D schedule on x86"""
+import tvm
+from .. import generic
+from .. import tag
+
+@generic.schedule_conv2d_nchw.register(["cpu"])
+def schedule_conv2d(outs):
+    """Create schedule for tensors"""
+    s = tvm.create_schedule([x.op for x in outs])
+
+    def traverse(op):
+        """Traverse operators from computation graph"""
+        # inline all one-to-one-mapping operators except the last stage (output)
+        if tag.is_broadcast(op.tag):
+            if op not in s.outputs:
+                s[op].compute_inline()
+            for tensor in op.input_tensors:
+                if tensor.op.input_tensors:
+                    traverse(tensor.op)
+
+        if 'conv2d_nchw' in op.tag:
+            conv = op.output(0)
+            kernel = op.input_tensors[1]
+            data = op.input_tensors[0]
+            data_pad = None
+            if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
+                data_pad = data
+                data = data_pad.op.input_tensors[0]
+
+            C = conv
+            n, c, h, w = C.op.axis
+            s[C].parallel(c)
+            s[C].pragma(n, "parallel_launch_point")
+
+    traverse(outs[0].op)
+    return s

From 6aae21333f761b9d3c0fe6083dfa0ff444b7fd21 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 14 Nov 2017 15:01:15 -0800
Subject: [PATCH 020/949] [UNROLL] New unroll option (#647)

---
 include/tvm/ir_pass.h                     |  8 +++++++-
 python/tvm/build_module.py                |  4 +++-
 src/api/api_pass.cc                       |  2 +-
 src/pass/unroll_loop.cc                   | 16 +++++++++++++---
 tests/python/unittest/test_pass_unroll.py |  6 +++---
 5 files changed, 27 insertions(+), 9 deletions(-)

diff --git a/include/tvm/ir_pass.h b/include/tvm/ir_pass.h
index 6b95bd268652..897f96c763e7 100644
--- a/include/tvm/ir_pass.h
+++ b/include/tvm/ir_pass.h
@@ -205,10 +205,16 @@ Stmt NarrowChannelAccess(Stmt stmt);
  * \param stmt The statment to be unrolled.
  * \param auto_max_step The maximum step before stop attach automatic unroll
  * \param auto_min_depth The minimum depth before we can start automatic unroll
+ * \param auto_max_extent The maximum extent of the loop we can unroll,
+ *                        this is an legacy option that donot take the loop total steps into account.
  * \param explicit_unroll Whether explicitly unroll the loop, or leave unroll annotation to codegen.
  * \return Transformed stmt.
  */
-Stmt UnrollLoop(Stmt stmt, int auto_max_step, int auto_min_depth, bool explicit_unroll);
+Stmt UnrollLoop(Stmt stmt,
+                int auto_max_step,
+                int auto_min_depth,
+                int auto_max_extent,
+                bool explicit_unroll);
 
 /*!
  * \brief vectorize the constant loops
diff --git a/python/tvm/build_module.py b/python/tvm/build_module.py
index 4fe502e987d5..5756fe6d14c6 100644
--- a/python/tvm/build_module.py
+++ b/python/tvm/build_module.py
@@ -28,7 +28,8 @@ class BuildConfig(object):
     current = None
     defaults = {
         "auto_unroll_max_step": 0,
-        "auto_unroll_max_depth": 4,
+        "auto_unroll_max_depth": 8,
+        "auto_unroll_max_extent": 0,
         "unroll_explicit": True,
         "detect_global_barrier": False,
         "offset_factor": 0,
@@ -227,6 +228,7 @@ def lower(sch,
         stmt,
         cfg.auto_unroll_max_step,
         cfg.auto_unroll_max_depth,
+        cfg.auto_unroll_max_extent,
         cfg.unroll_explicit)
     for f in lower_phase2:
         stmt = f(stmt)
diff --git a/src/api/api_pass.cc b/src/api/api_pass.cc
index 2dacb32e54f7..a3134f5114e1 100644
--- a/src/api/api_pass.cc
+++ b/src/api/api_pass.cc
@@ -91,7 +91,7 @@ REGISTER_PASS4(Inline);
 REGISTER_PASS3(StorageFlatten);
 REGISTER_PASS4(IRTransform);
 REGISTER_PASS1(VectorizeLoop);
-REGISTER_PASS4(UnrollLoop);
+REGISTER_PASS5(UnrollLoop);
 REGISTER_PASS3(InjectCopyIntrin);
 REGISTER_PASS2(ThreadSync);
 REGISTER_PASS5(MakeAPI);
diff --git a/src/pass/unroll_loop.cc b/src/pass/unroll_loop.cc
index 7a0ce24963e6..01c5e6ebff00 100644
--- a/src/pass/unroll_loop.cc
+++ b/src/pass/unroll_loop.cc
@@ -19,9 +19,11 @@ class LoopUnroller : public IRMutator {
  public:
   explicit LoopUnroller(int auto_max_step,
                         int auto_max_depth,
+                        int auto_max_extent,
                         bool explicit_unroll)
       : auto_max_step_(auto_max_step),
         auto_max_depth_(auto_max_depth),
+        auto_max_extent_(auto_max_extent),
         explicit_unroll_(explicit_unroll) {
   }
 
@@ -42,10 +44,13 @@ class LoopUnroller : public IRMutator {
     // condition for auto unroll
     bool auto_unroll = (
         op->for_type == ForType::Serial &&
-        normal_loop_depth_ == 0 &&
         value >= 0 &&
-        unroll_depth_ <= auto_max_depth_ &&
-        value * step_count_ <= auto_max_step_);
+        normal_loop_depth_ == 0 &&
+        unroll_depth_ <= auto_max_depth_);
+
+    auto_unroll = auto_unroll && (
+        value * step_count_ <= auto_max_step_||
+        value <= auto_max_extent_);
 
     if (op->for_type == ForType::Unrolled) {
       CHECK_GE(value, 0)
@@ -127,6 +132,9 @@ class LoopUnroller : public IRMutator {
   // maximum number of step to perform auto unroll.
   int auto_max_step_;
   int auto_max_depth_;
+  // max extent of loop to auto unroll
+  // this not not count the total steps, only count the number of loops
+  int auto_max_extent_;
   bool explicit_unroll_;
   // Number of normal loops in scope
   int normal_loop_depth_{0};
@@ -140,10 +148,12 @@ class LoopUnroller : public IRMutator {
 Stmt UnrollLoop(Stmt stmt,
                 int auto_max_step,
                 int auto_max_depth,
+                int auto_max_extent,
                 bool explicit_unroll) {
   Stmt ret = LoopUnroller(
       auto_max_step,
       auto_max_depth,
+      auto_max_extent,
       explicit_unroll).Mutate(stmt);
   if (!ret.same_as(stmt)) {
     return ConvertSSA(ret);
diff --git a/tests/python/unittest/test_pass_unroll.py b/tests/python/unittest/test_pass_unroll.py
index 3c155e44aa0a..9e52a455e7da 100644
--- a/tests/python/unittest/test_pass_unroll.py
+++ b/tests/python/unittest/test_pass_unroll.py
@@ -14,11 +14,11 @@ def test_unroll_loop():
                                     tvm.make.Load(dtype, Ab.data, i) + 1,
                                     j + 1)))
     assert isinstance(stmt, tvm.stmt.For)
-    ret = tvm.ir_pass.UnrollLoop(stmt, 16, 8, True)
+    ret = tvm.ir_pass.UnrollLoop(stmt, 16, 8, 0, True)
     assert not isinstance(ret, tvm.stmt.For)
-    ret = tvm.ir_pass.UnrollLoop(stmt, 15, 8, True)
+    ret = tvm.ir_pass.UnrollLoop(stmt, 15, 8, 0, True)
     assert isinstance(ret, tvm.stmt.For)
-    ret = tvm.ir_pass.UnrollLoop(stmt, 16, 8, False)
+    ret = tvm.ir_pass.UnrollLoop(stmt, 16, 8, 0, False)
     assert isinstance(ret, tvm.stmt.For)
     assert ret.for_type == tvm.stmt.For.Unrolled
 

From dcbfb9ffb477e7ceb55be3586562eb58cedcc261 Mon Sep 17 00:00:00 2001
From: Leyuan Wang <laurawly@gmail.com>
Date: Wed, 15 Nov 2017 20:49:13 -0800
Subject: [PATCH 021/949] Conv2d scheduler tweaked for super resolution perf
 (#652)

* scheduler tweaked for super resolution perf

* lint error fixed

* lint error fixed

* conv2d_transpose schedule error fixed
---
 topi/python/topi/cuda/conv2d_nchw.py          | 35 +++++++++----------
 .../python/topi/cuda/conv2d_transpose_nchw.py |  8 ++---
 2 files changed, 20 insertions(+), 23 deletions(-)

diff --git a/topi/python/topi/cuda/conv2d_nchw.py b/topi/python/topi/cuda/conv2d_nchw.py
index b6bd35768112..a59a99624233 100644
--- a/topi/python/topi/cuda/conv2d_nchw.py
+++ b/topi/python/topi/cuda/conv2d_nchw.py
@@ -1,15 +1,17 @@
-#pylint: disable=invalid-name, no-member, too-many-locals, too-many-statements, too-many-arguments, too-many-branches
+#pylint: disable=invalid-name, no-member, too-many-locals, too-many-statements, too-many-arguments, too-many-branches, line-too-long
 """Schedule for conv2d_nchw with auto fusion"""
 import tvm
 from .. import util
 from .. import tag
 from .. import generic
 
-def conv2d_224_3_64(s, temp, temp_R, temp_S, Filter_S, Out, Out_L):
+def conv2d_224_3_64(s, temp, temp_R, temp_S, Filter_S, Out, Out_L, flag):
     """Schedule conv2d for specific feature_in_out_filter pattern"""
     # scheduler params
     ofactor = 16
     hfactor = 2
+    if flag >= 96:
+        hfactor = 4
     ow_size = util.get_const_int(Out.shape[3])
     num_thread = ow_size * hfactor
     vthread = ofactor
@@ -22,7 +24,8 @@ def conv2d_224_3_64(s, temp, temp_R, temp_S, Filter_S, Out, Out_L):
     oh, ih = s[Out].split(h, factor=hfactor)
     s[Out].reorder(ooc, oh, ioc, ih, w)
     oc = s[Out].fuse(ooc, oh)
-    w = s[Out].fuse(w, ih)
+    ow, _ = s[Out].split(w, nparts=ow_size)
+    w = s[Out].fuse(ow, ih)
     s[Out].bind(w, thread_x)
     s[Out].bind(ioc, thread_xz)
     s[Out].bind(oc, block_x)
@@ -360,7 +363,11 @@ def conv2d_56_64_64(s, Filter, temp_S, Filter_S, Out, Out_L):
     if util.get_const_int(Filter.shape[0]) == 64:
         opart2 = 8
         ifactor = 16
-    sfactor = max(1, ofactor // (opart2*2))
+    if util.get_const_int(Out.shape[2]) == 224:
+        num_thread = 4
+        wfactor = 112
+        ifactor = 4
+    sfactor = max(1, ofactor // (opart2*vthread))
     spart = max(1, (wfactor + vthread-1) // vthread)
 
     block_x = tvm.thread_axis("blockIdx.x")
@@ -368,7 +375,7 @@ def conv2d_56_64_64(s, Filter, temp_S, Filter_S, Out, Out_L):
     block_z = tvm.thread_axis("blockIdx.z")
     thread_x = tvm.thread_axis((0, num_thread), "threadIdx.x")
     thread_y = tvm.thread_axis((0, wfactor // vthread), "threadIdx.y")
-    thread_xz = tvm.thread_axis((0, vthread), "vthread", name="vx")
+    thread_xz = tvm.thread_axis((0, opart2), "vthread", name="vx")
     thread_yz = tvm.thread_axis((0, vthread), "vthread", name="vy")
 
     i, oc, h, w = s[Out].op.axis
@@ -394,10 +401,10 @@ def conv2d_56_64_64(s, Filter, temp_S, Filter_S, Out, Out_L):
     ic, dh, dw = s[Out_L].op.reduce_axis
     oic, iic = s[Out_L].split(ic, factor=ifactor)
     s[Out_L].reorder(oic, dh, dw, iic, h, w)
-
     fuse_index = s[Out_L].fuse(dw, dh)
     fuse_index = s[Out_L].fuse(fuse_index, oic)
     dw = fuse_index
+
     s[temp_S].compute_at(s[Out_L], dw)
     s[Filter_S].compute_at(s[Out_L], dw)
 
@@ -421,16 +428,6 @@ def schedule_conv2d_small_batch(outs):
 
     def schedule(temp, Filter, Output):
         """Schedule conv2d_nchw"""
-        block_h = util.get_const_int(Output.shape[3])
-        block_w = util.get_const_int(temp.shape[1])
-        if block_h % 48 == 0:
-            block_h = 48
-        elif block_h % 32 == 0:
-            block_h = 32
-        if block_w % 48 == 0:
-            block_w = 48
-        elif block_w % 32 == 0:
-            block_w = 32
 
         flag = util.get_const_int(Filter.shape[0])+util.get_const_int(Filter.shape[1])
 
@@ -450,7 +447,7 @@ def schedule(temp, Filter, Output):
             s[temp_G].reorder(i, oic, h, w, iic)
             temp_R = s.cache_write(temp_G, "global")
             temp_S = s.cache_read(temp_R, "shared", [temp_G])
-        elif util.get_const_int(Filter.shape[3]) == 7:
+        elif util.get_const_int(Filter.shape[3]) == 7 or (util.get_const_int(Output.shape[2] == 224) and flag < 128):
             temp_G = s.cache_read(temp, "global", [Output])
             s[temp_G].compute_inline()
             i, ic, h, w = s[temp_G].op.axis
@@ -472,8 +469,8 @@ def schedule(temp, Filter, Output):
             s[Output].set_scope("local")
             Out_L = Output
 
-        if util.get_const_int(Filter.shape[3]) == 7:
-            conv2d_224_3_64(s, temp, temp_R, temp_S, Filter_S, Out, Out_L)
+        if util.get_const_int(Filter.shape[3]) == 7 or (util.get_const_int(Output.shape[2] == 224) and flag < 128):
+            conv2d_224_3_64(s, temp, temp_R, temp_S, Filter_S, Out, Out_L, flag)
         elif 128 < flag < 512:
             conv2d_56_64_128(s, temp, temp_R, temp_S, Filter_S, Out, Out_L, flag)
         elif flag >= 512:
diff --git a/topi/python/topi/cuda/conv2d_transpose_nchw.py b/topi/python/topi/cuda/conv2d_transpose_nchw.py
index 82fd451f4ecb..edd255a8fac0 100644
--- a/topi/python/topi/cuda/conv2d_transpose_nchw.py
+++ b/topi/python/topi/cuda/conv2d_transpose_nchw.py
@@ -1,4 +1,4 @@
-#pylint: disable=invalid-name
+#pylint: disable=invalid-name, line-too-long
 """Schedule for conv2d_transpose_nchw with auto fusion"""
 import tvm
 from .. import util
@@ -42,7 +42,7 @@ def schedule(temp, Filter, Output):
             s[temp_G].reorder(i, oic, h, w, iic)
             temp_R = s.cache_write(temp_G, "global")
             temp_S = s.cache_read(temp_R, "shared", [temp_G])
-        elif util.get_const_int(Filter.shape[3]) == 7:
+        elif util.get_const_int(Filter.shape[3]) == 7 or (util.get_const_int(Output.shape[2] == 224) and flag < 128):
             temp_G = s.cache_read(temp, "global", [Output])
             s[temp_G].compute_inline()
             i, ic, h, w = s[temp_G].op.axis
@@ -64,8 +64,8 @@ def schedule(temp, Filter, Output):
             s[Output].set_scope("local")
             Out_L = Output
 
-        if util.get_const_int(Filter.shape[3]) == 7:
-            conv2d_224_3_64(s, temp, temp_R, temp_S, Filter_S, Out, Out_L)
+        if util.get_const_int(Filter.shape[3]) == 7 or (util.get_const_int(Output.shape[2] == 224) and flag < 128):
+            conv2d_224_3_64(s, temp, temp_R, temp_S, Filter_S, Out, Out_L, flag)
         elif 128 < flag < 512:
             conv2d_56_64_128(s, temp, temp_R, temp_S, Filter_S, Out, Out_L, flag)
         elif flag >= 512:

From b9f9b08133c6ecd96bd924feffe6a969aac504f9 Mon Sep 17 00:00:00 2001
From: haolongzhangm <1041563782@qq.com>
Date: Fri, 17 Nov 2017 01:12:46 +0800
Subject: [PATCH 022/949] Compat for opencl mode between cpu mode and gpu mode
 (#655)

some host opencl runtime may at cpu mode, but remote

client opencl runtime at gpu mode, compat it
---
 CONTRIBUTORS.md                         | 1 +
 src/runtime/opencl/opencl_device_api.cc | 9 +++++++--
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index ab9950a9f31d..944e76fd3b83 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -35,3 +35,4 @@ List of Contributors
 - [Qiao Zhang](https://github.com/zhangqiaorjc)
 - [Jian Weng](https://github.com/were)
 - [Masahiro Masuda](https://github.com/masahi)
+- [Haolong Zhang](https://github.com/haolongzhangm)
diff --git a/src/runtime/opencl/opencl_device_api.cc b/src/runtime/opencl/opencl_device_api.cc
index e95fddaa867c..23c897e04825 100644
--- a/src/runtime/opencl/opencl_device_api.cc
+++ b/src/runtime/opencl/opencl_device_api.cc
@@ -197,8 +197,13 @@ void OpenCLWorkspace::Init() {
   std::vector<cl_device_id> devices_matched =
       cl::GetDeviceIDs(this->platform_id, "gpu");
   if (devices_matched.size() == 0) {
-    LOG(WARNING) << "No OpenCL device any device matched given the options";
-    return;
+    LOG(WARNING) << "No OpenCL device any device matched given the options: gpu mode";
+    LOG(WARNING) << "Now try OpenCL cpu mode";
+    devices_matched = cl::GetDeviceIDs(this->platform_id, "cpu");
+    if (devices_matched.size() == 0) {
+      LOG(WARNING) << "No OpenCL device any device matched given the options: cpu mode";
+      return;
+    }
   }
   this->devices = devices_matched;
   cl_int err_code;

From bd3561d0de7754b414a27db3417f96a44ac36fb4 Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Sun, 19 Nov 2017 07:49:09 +0800
Subject: [PATCH 023/949] [RUNTIME] support limited save without cross compile
 (#659)

---
 src/codegen/build_metal.cc         |  2 +-
 src/codegen/build_opencl.cc        |  2 +-
 src/codegen/codegen.cc             |  2 +-
 src/codegen/codegen_source_base.h  | 14 ++++++++
 src/codegen/source_module.cc       | 57 ++++++++++++++++++++++++++++--
 src/runtime/opencl/opencl_module.h |  2 +-
 6 files changed, 73 insertions(+), 6 deletions(-)

diff --git a/src/codegen/build_metal.cc b/src/codegen/build_metal.cc
index f2a7e14f9a9f..42aa0965ec9d 100644
--- a/src/codegen/build_metal.cc
+++ b/src/codegen/build_metal.cc
@@ -35,7 +35,7 @@ runtime::Module BuildMetal(Array<LoweredFunc> funcs) {
   return MetalModuleCreate(code, fmt, ExtractFuncInfo(funcs), source);
 #else
   LOG(WARNING) << "Metal runtime not enabled, return a source module...";
-  return SourceModuleCreate(code, "metal");
+  return DeviceSourceModuleCreate(code, "metal", ExtractFuncInfo(funcs), "metal");
 #endif   // TVM_METAL_RUNTIME
 }
 
diff --git a/src/codegen/build_opencl.cc b/src/codegen/build_opencl.cc
index 499c88a009cd..51779d3f7a3e 100644
--- a/src/codegen/build_opencl.cc
+++ b/src/codegen/build_opencl.cc
@@ -27,7 +27,7 @@ runtime::Module BuildOpenCL(Array<LoweredFunc> funcs) {
   return OpenCLModuleCreate(code, "cl", ExtractFuncInfo(funcs));
 #else
   LOG(WARNING) << "OpenCL runtime not enabled, return a source module...";
-  return SourceModuleCreate(code, "cl");
+  return DeviceSourceModuleCreate(code, "cl", ExtractFuncInfo(funcs), "opencl");
 #endif   // TVM_OPENCL_RUNTIME
 }
 
diff --git a/src/codegen/codegen.cc b/src/codegen/codegen.cc
index e1f003d32b10..d289d627b310 100644
--- a/src/codegen/codegen.cc
+++ b/src/codegen/codegen.cc
@@ -38,7 +38,7 @@ std::string PackImportsToC(const runtime::Module& mod, bool system_lib) {
   stream->Write(sz);
   for (runtime::Module im : mod->imports()) {
     CHECK_EQ(im->imports().size(), 0U)
-        << "Only support simply one-level hierachy";
+        << "Only support simply one-level hierarchy";
     std::string tkey = im->type_key();
     std::string bin;
     stream->Write(tkey);
diff --git a/src/codegen/codegen_source_base.h b/src/codegen/codegen_source_base.h
index 0ee5b71d017c..bc99eeeb1d33 100644
--- a/src/codegen/codegen_source_base.h
+++ b/src/codegen/codegen_source_base.h
@@ -11,6 +11,7 @@
 #include <string>
 #include <vector>
 #include <unordered_map>
+#include "../runtime/meta_data.h"
 
 namespace tvm {
 namespace codegen {
@@ -108,6 +109,19 @@ class CodeGenSourceBase {
  * \param fmt The code. format.
  */
 runtime::Module SourceModuleCreate(std::string code, std::string fmt);
+
+/*!
+ * \brief Create a source module for viewing and limited saving
+ * \param code The code to be viewed.
+ * \param fmt The code. format.
+ * \param fmap The map function information map of each function.
+ * \param type_key The type_key of the runtime module of this source code
+ */
+runtime::Module DeviceSourceModuleCreate(
+  std::string code,
+  std::string fmt,
+  std::unordered_map<std::string, runtime::FunctionInfo> fmap,
+  std::string type_key);
 }  // namespace codegen
 }  // namespace tvm
 #endif  // TVM_CODEGEN_CODEGEN_SOURCE_BASE_H_
diff --git a/src/codegen/source_module.cc b/src/codegen/source_module.cc
index 1ad2168ae06e..23c0cbd8466e 100644
--- a/src/codegen/source_module.cc
+++ b/src/codegen/source_module.cc
@@ -5,6 +5,8 @@
  */
 #include <tvm/runtime/packed_func.h>
 #include "./codegen_source_base.h"
+#include "../runtime/file_util.h"
+#include "../runtime/meta_data.h"
 
 namespace tvm {
 namespace codegen {
@@ -12,8 +14,14 @@ namespace codegen {
 using runtime::TVMArgs;
 using runtime::TVMRetValue;
 using runtime::PackedFunc;
+
+using runtime::GetFileFormat;
+using runtime::GetMetaFilePath;
+using runtime::FunctionInfo;
+using runtime::SaveBinaryToFile;
+
 // Simulator function
-class SourceModuleNode final : public runtime::ModuleNode {
+class SourceModuleNode : public runtime::ModuleNode {
  public:
   SourceModuleNode(std::string code,
                    std::string fmt)
@@ -21,6 +29,7 @@ class SourceModuleNode final : public runtime::ModuleNode {
   const char* type_key() const {
     return "source";
   }
+
   PackedFunc GetFunction(
       const std::string& name,
       const std::shared_ptr<ModuleNode>& sptr_to_self) final {
@@ -33,7 +42,7 @@ class SourceModuleNode final : public runtime::ModuleNode {
     return code_;
   }
 
- private:
+ protected:
   std::string code_;
   std::string fmt_;
 };
@@ -44,6 +53,50 @@ runtime::Module SourceModuleCreate(std::string code, std::string fmt) {
   return runtime::Module(n);
 }
 
+// supports limited save without cross compile
+class DeviceSourceModuleNode final : public SourceModuleNode {
+ public:
+  DeviceSourceModuleNode(std::string code,
+                         std::string fmt,
+                         std::unordered_map<std::string, FunctionInfo> fmap,
+                         std::string type_key)
+      : SourceModuleNode(code, fmt), fmap_(fmap), type_key_(type_key) {}
+
+  const char* type_key() const {
+    return type_key_.c_str();
+  }
+
+  void SaveToFile(const std::string& file_name,
+                const std::string& format) final {
+    std::string fmt = GetFileFormat(file_name, format);
+    CHECK_EQ(fmt, fmt_)
+        << "Can only save to format=" << fmt_;
+    std::string meta_file = GetMetaFilePath(file_name);
+    SaveMetaDataToFile(meta_file, fmap_);
+    SaveBinaryToFile(file_name, code_);
+  }
+
+  void SaveToBinary(dmlc::Stream* stream) final {
+    stream->Write(fmt_);
+    stream->Write(fmap_);
+    stream->Write(code_);
+  }
+
+ private:
+  std::unordered_map<std::string, FunctionInfo> fmap_;
+  std::string type_key_;
+};
+
+runtime::Module DeviceSourceModuleCreate(
+    std::string code,
+    std::string fmt,
+    std::unordered_map<std::string, FunctionInfo> fmap,
+    std::string type_key) {
+  std::shared_ptr<DeviceSourceModuleNode> n =
+      std::make_shared<DeviceSourceModuleNode>(code, fmt, fmap, type_key);
+  return runtime::Module(n);
+}
+
 TVM_REGISTER_GLOBAL("module.source_module_create")
 .set_body([](TVMArgs args, TVMRetValue* rv) {
     *rv = SourceModuleCreate(args[0], args[1]);
diff --git a/src/runtime/opencl/opencl_module.h b/src/runtime/opencl/opencl_module.h
index 85c50e3e9755..54da2d9b0443 100644
--- a/src/runtime/opencl/opencl_module.h
+++ b/src/runtime/opencl/opencl_module.h
@@ -16,7 +16,7 @@
 namespace tvm {
 namespace runtime {
 /*!
- * \brief create a cuda module from data.
+ * \brief create a opencl module from data.
  *
  * \param data The module data.
  * \param fmt The format of the data, can be "clbin", "cl"

From 6c7f8cdd83cf57a4d8bc73169a420b83f0a35222 Mon Sep 17 00:00:00 2001
From: Leyuan Wang <laurawly@gmail.com>
Date: Sat, 18 Nov 2017 21:23:17 -0800
Subject: [PATCH 024/949] Fixed nnvm issue #239 (#660)

* scheduler tweaked for super resolution perf

* conv2d_transpose schedule error fixed

* nnvm issue #239 fixed
---
 topi/python/topi/cuda/conv2d_nchw.py | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/topi/python/topi/cuda/conv2d_nchw.py b/topi/python/topi/cuda/conv2d_nchw.py
index a59a99624233..0d3f5eedb0a2 100644
--- a/topi/python/topi/cuda/conv2d_nchw.py
+++ b/topi/python/topi/cuda/conv2d_nchw.py
@@ -75,6 +75,9 @@ def conv2d_56_64_128(s, temp, temp_R, temp_S, Filter_S, Out, Out_L, flag):
         if mark % 8 == 0 and mark % 7 == 0:
             num_thread_x = 8
             vthread_x = 7
+        elif mark % 4 == 0 and mark % 7 == 0:
+            num_thread_x = 4
+            vthread_x = 7
         else:
             for i in range(5, mark):
                 if mark % i == 0 and num_thread_x == 0:

From abe224ce42dc361061df255dc2a687b55177fff7 Mon Sep 17 00:00:00 2001
From: Sheng Zha <szha@users.noreply.github.com>
Date: Tue, 21 Nov 2017 09:25:35 -0800
Subject: [PATCH 025/949] [CONTRIB] MPS DNN Dense (#615)

* mps

* update
---
 Makefile                         |  1 +
 make/config.mk                   |  3 ++
 make/contrib/mps.mk              | 14 +++++
 python/tvm/contrib/mps.py        | 35 ++++++++++++
 src/contrib/mps/gemm.mm          | 93 ++++++++++++++++++++++++++++++++
 src/contrib/mps/mps_utils.cc     | 58 ++++++++++++++++++++
 src/contrib/mps/mps_utils.h      | 33 ++++++++++++
 tests/python/contrib/test_mps.py | 40 ++++++++++++++
 8 files changed, 277 insertions(+)
 create mode 100644 make/contrib/mps.mk
 create mode 100644 python/tvm/contrib/mps.py
 create mode 100644 src/contrib/mps/gemm.mm
 create mode 100644 src/contrib/mps/mps_utils.cc
 create mode 100644 src/contrib/mps/mps_utils.h
 create mode 100644 tests/python/contrib/test_mps.py

diff --git a/Makefile b/Makefile
index 4a16d5162102..75c5a563408e 100644
--- a/Makefile
+++ b/Makefile
@@ -133,6 +133,7 @@ endif
 include make/contrib/cblas.mk
 include make/contrib/nnpack.mk
 include make/contrib/cudnn.mk
+include make/contrib/mps.mk
 
 ifdef ADD_CFLAGS
 	CFLAGS += $(ADD_CFLAGS)
diff --git a/make/config.mk b/make/config.mk
index 53775df1ab36..8d8082c68142 100644
--- a/make/config.mk
+++ b/make/config.mk
@@ -68,3 +68,6 @@ USE_NNPACK = 0
 
 # Whether use CuDNN
 USE_CUDNN = 0
+
+# Whether use MPS
+USE_MPS = 0
diff --git a/make/contrib/mps.mk b/make/contrib/mps.mk
new file mode 100644
index 000000000000..501e62b2a671
--- /dev/null
+++ b/make/contrib/mps.mk
@@ -0,0 +1,14 @@
+MPS_CONTRIB_SRC = $(wildcard src/contrib/mps/*.mm, src/contrib/mps/*.cc)
+MPS_CONTRIB_OBJ = $(patsubst src/%.mm, build/%.o, $(MPS_CONTRIB_SRC))
+
+ifeq ($(USE_MPS), 1)
+FRAMEWORKS += -framework MetalPerformanceShaders
+CFLAGS += 
+ADD_LDFLAGS += 
+RUNTIME_DEP += $(MPS_CONTRIB_OBJ)
+endif
+
+build/contrib/mps/%.o: src/contrib/mps/%.mm src/contrib/mps/%.cc
+	@mkdir -p $(@D)
+	$(CXX) $(OBJCFLAGS) $(CFLAGS) -MM -MT build/contrib/mps/$*.o $< >build/contrib/mps/$*.d
+	$(CXX) $(OBJCFLAGS) -c $(CFLAGS) -c $< -o $@
diff --git a/python/tvm/contrib/mps.py b/python/tvm/contrib/mps.py
new file mode 100644
index 000000000000..d214d4b93631
--- /dev/null
+++ b/python/tvm/contrib/mps.py
@@ -0,0 +1,35 @@
+"""External function interface to MPS libraroes."""
+from __future__ import absolute_import as _abs
+
+from .. import api as _api
+from .. import intrin as _intrin
+
+
+def matmul(lhs, rhs, transa=False, transb=False):
+    """Create an extern op that compute matrix mult of A and rhs with CrhsLAS
+
+    This function serves as an example on how to calle external libraries.
+
+    Parameters
+    ----------
+    lhs : Tensor
+        The left matrix operand
+    rhs : Tensor
+        The right matrix operand
+    transa : bool
+        Whether transpose lhs
+    transb : bool
+        Whether transpose rhs
+
+    Returns
+    -------
+    C : Tensor
+        The result tensor.
+    """
+    m = lhs.shape[0]
+    n = rhs.shape[1]
+    return _api.extern(
+        (n, m), [lhs, rhs],
+        lambda ins, outs: _intrin.call_packed(
+            "tvm.contrib.mps.matmul", ins[0], ins[1], outs[0], transa, transb),
+        name="C")
diff --git a/src/contrib/mps/gemm.mm b/src/contrib/mps/gemm.mm
new file mode 100644
index 000000000000..f877cb8b0ea1
--- /dev/null
+++ b/src/contrib/mps/gemm.mm
@@ -0,0 +1,93 @@
+#include "../../runtime/metal/metal_common.h"
+#include <MetalPerformanceShaders/MetalPerformanceShaders.h>
+#include <dmlc/logging.h>
+#include <tvm/runtime/device_api.h>
+#include <tvm/runtime/registry.h>
+#include <tvm/runtime/util.h>
+
+namespace tvm {
+namespace contrib {
+
+using namespace runtime;
+
+TVM_REGISTER_GLOBAL("tvm.contrib.mps.matmul")
+    .set_body([](TVMArgs args, TVMRetValue *ret) {
+      DLTensor *A = args[0];
+      DLTensor *B = args[1];
+      DLTensor *C = args[2];
+      bool transa = args[3];
+      bool transb = args[4];
+      // call gemm for simple compact code.
+      CHECK_EQ(A->ndim, 2);
+      CHECK_EQ(B->ndim, 2);
+      CHECK_EQ(C->ndim, 2);
+      CHECK(C->strides == nullptr);
+      CHECK(B->strides == nullptr);
+      CHECK(A->strides == nullptr);
+      CHECK(TypeMatch(A->dtype, kDLFloat, 32));
+      CHECK(TypeMatch(B->dtype, kDLFloat, 32));
+      CHECK(TypeMatch(C->dtype, kDLFloat, 32));
+      // Get Metal device API
+      MetalThreadEntry* entry_ptr = MetalThreadEntry::ThreadLocal();
+      CHECK_EQ(A->ctx, B->ctx);
+      CHECK_EQ(A->ctx, C->ctx);
+      id<MTLDevice> dev = entry_ptr->metal_api->GetDevice(A->ctx);
+      id<MTLCommandQueue> queue = entry_ptr->metal_api->GetCommandQueue(A->ctx);
+      id<MTLCommandBuffer> cb = [queue commandBuffer];
+      NSUInteger M = A->shape[0 + transa?1:0];
+      NSUInteger N = B->shape[1 - transb?1:0];
+      NSUInteger K = B->shape[0 + transb?1:0];
+      CHECK_EQ(A->shape[1-transa?1:0], K);
+      // mps a
+      MPSDataType dtype = MPSType::DLTypeToMPSType(A->dtype);
+      MPSMatrixDescriptor *descA = [MPSMatrixDescriptor
+          matrixDescriptorWithDimensions:M
+                                 columns:K
+                                rowBytes:M * sizeof(dtype)
+                                dataType:dtype];
+      id<MTLBuffer> bufA = (__bridge id<MTLBuffer>)(A->data);
+      MPSMatrix *matrixA =
+          [[MPSMatrix alloc] initWithBuffer:bufA descriptor:descA];
+      // mps b
+      MPSMatrixDescriptor *descB = [MPSMatrixDescriptor
+          matrixDescriptorWithDimensions:K
+                                 columns:N
+                                rowBytes:K * sizeof(dtype)
+                                dataType:dtype];
+      id<MTLBuffer> bufB = (__bridge id<MTLBuffer>)(B->data);
+      MPSMatrix *matrixB =
+          [[MPSMatrix alloc] initWithBuffer:bufB descriptor:descB];
+      // mps c
+      MPSMatrixDescriptor *descC = [MPSMatrixDescriptor
+          matrixDescriptorWithDimensions:M
+                                 columns:N
+                                rowBytes:M * sizeof(dtype)
+                                dataType:dtype];
+      id<MTLBuffer> bufC = (__bridge id<MTLBuffer>)(C->data);
+      MPSMatrix *matrixC =
+          [[MPSMatrix alloc] initWithBuffer:bufC descriptor:descC];
+      // kernel
+
+      MPSMatrixMultiplication *mul_obj = [[MPSMatrixMultiplication alloc] init];
+      MPSMatrixMultiplication *sgemm = [mul_obj initWithDevice:dev
+                                                 transposeLeft:transa
+                                                transposeRight:transb
+                                                    resultRows:M
+                                                 resultColumns:N
+                                               interiorColumns:K
+                                                         alpha:1.0f
+                                                          beta:0.0f];
+      CHECK(sgemm != nil);
+      [sgemm encodeToCommandBuffer:cb
+                        leftMatrix:matrixA
+                       rightMatrix:matrixB
+                      resultMatrix:matrixC];
+      [cb commit];
+      [mul_obj dealloc];
+      [matrixA dealloc];
+      [matrixB dealloc];
+      [matrixC dealloc];
+    });
+
+} // namespace contrib
+} // namespace tvm
diff --git a/src/contrib/mps/mps_utils.cc b/src/contrib/mps/mps_utils.cc
new file mode 100644
index 000000000000..2e3ca6218bb4
--- /dev/null
+++ b/src/contrib/mps/mps_utils.cc
@@ -0,0 +1,58 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file Use external mps utils function
+ */
+#include "mps_utils.h"
+#include <MetalPerformanceShaders/MetalPerformanceShaders.h>
+#include <dmlc/thread_local.h>
+#include <tvm/runtime/registry.h>
+
+
+namespace tvm {
+namespace contrib {
+
+// MPS Data Type
+MPSDataType MPSType::DLTypeToMPSType(const DLDataType &dtype) {
+  switch (dtype.code) {
+      case kDLInt:
+        if (dtype.bits == 8 && dtype.lanes == 1) return MPSDataTypeInt8;
+        else if (dtype.bits == 16 && dtype.lanes == 1) return MPSDataTypeInt16;
+        else
+          LOG(FATAL) << "Unsupported type";
+        break;
+      case kDLUInt:
+        if (dtype.bits == 8 && dtype.lanes == 1) return MPSDataTypeUInt8;
+        else if (dtype.bits == 16 && dtype.lanes == 1) return MPSDataTypeUInt16;
+        else if (dtype.bits == 32 && dtype.lanes == 1) return MPSDataTypeUInt32;
+        LOG(FATAL) << "Unsupported type";
+        break;
+      case kDLFloat:
+        if (dtype.bits == 16 && dtype.lanes == 1) return MPSDataTypeFloat16;
+        else if (dtype.bits == 32 && dtype.lanes == 1) return MPSDataTypeFloat32;
+        else
+          LOG(FATAL) << "Unsupported type";
+        break;
+      default:
+        LOG(FATAL) << "Unsupported type";
+    }
+}
+
+// MetalThreadEntry
+
+MetalThreadEntry::MetalThreadEntry() {
+  auto func = runtime::Registry::Get("device_api.metal");
+  void *ret = (*func)();
+  metal_api = static_cast<runtime::metal::MetalWorkspace *>(ret);
+}
+
+MetalThreadEntry::~MetalThreadEntry() {
+}
+
+typedef dmlc::ThreadLocalStore<MetalThreadEntry> MetalThreadStore;
+
+MetalThreadEntry* MetalThreadEntry::ThreadLocal() {
+  return MetalThreadStore::Get();
+}
+
+}  // namespace contrib
+}  // namespace tvm
diff --git a/src/contrib/mps/mps_utils.h b/src/contrib/mps/mps_utils.h
new file mode 100644
index 000000000000..91336ce44edd
--- /dev/null
+++ b/src/contrib/mps/mps_utils.h
@@ -0,0 +1,33 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file Use external mps utils function
+ */
+
+#ifndef TVM_CONTRIB_MPS_MPS_UTILS_H_
+#define TVM_CONTRIB_MPS_MPS_UTILS_H_
+
+#include <dmlc/logging.h>
+#include <tvm/runtime/device_api.h>
+#include "../../runtime/metal/metal_common.h"
+
+
+namespace tvm {
+namespace contrib {
+
+/*! breif Convert DLTensor type to MPS type */
+struct MPSType {
+  static MPSDataType DLTypeToMPSType(const DLDataType &dtype);
+};  // struct MPSType
+
+
+struct MetalThreadEntry {
+  MetalThreadEntry();
+  ~MetalThreadEntry();
+  runtime::MetalWorkspace *metal_api{nullptr};
+  static MetalThreadEntry* ThreadLocal();
+};  // MetalThreadEntry
+
+}  // namespace contrib
+}  // namespace tvm
+
+#endif  // TVM_CONTRIB_MPS_MPS_UTILS_H_
diff --git a/tests/python/contrib/test_mps.py b/tests/python/contrib/test_mps.py
new file mode 100644
index 000000000000..68dcb135e908
--- /dev/null
+++ b/tests/python/contrib/test_mps.py
@@ -0,0 +1,40 @@
+import tvm
+import numpy as np
+from tvm.contrib import mps
+
+def test_matmul_add():
+    n = 1024
+    l = 128
+    m = 235
+    bias = tvm.var('bias', dtype=tvm.float32)
+    A = tvm.placeholder((n, l), name='A')
+    B = tvm.placeholder((l, m), name='B')
+    C1 = mps.matmul(A, B)
+    C2 = mps.matmul(B, A, True, True)
+    D1 = tvm.compute(C1.shape, lambda i, j: C1[i,j] + bias, name="D1")
+    D2 = tvm.compute(C2.shape, lambda i, j: C2[i,j] + bias, name="D2")
+    s1 = tvm.create_schedule(D1.op)
+    s2 = tvm.create_schedule(D2.op)
+
+    def verify(A, B, D, s, bias, target="llvm"):
+        if not tvm.module.enabled(target):
+            print("skip because %s is not enabled..." % target)
+            return
+        if not tvm.get_global_func("tvm.contrib.mps.matmul", True):
+            print("skip because extern function is not avalable")
+            return
+        ctx = tvm.cpu(0)
+        f = tvm.build(s, [A, B, D, bias], target)
+        a = tvm.nd.array(np.random.uniform(size=(n, l)).astype(A.dtype), ctx)
+        b = tvm.nd.array(np.random.uniform(size=(l, m)).astype(B.dtype), ctx)
+        d = tvm.nd.array(np.zeros((n, m), dtype=D.dtype), ctx)
+        bb = 10.0
+        f(a, b, d, bb)
+        np.testing.assert_allclose(
+            d.asnumpy(), np.dot(a.asnumpy(), b.asnumpy()) + bb, rtol=1e-5)
+    verify(A, B, D1, s1, bias)
+    verify(A, B, D2, s2, bias)
+
+
+if __name__ == "__main__":
+    test_matmul_add()

From 171ea2aea99f638ee587857f1d511eb7d6f183f2 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 21 Nov 2017 11:11:41 -0800
Subject: [PATCH 026/949] [PASS/SETUP] Fix minior issues (#663)

* [PASS/SETUP] Fix minior issues

* fix lint
---
 include/tvm/ir_pass.h                       | 20 +++++++---
 python/setup.py                             | 42 ++++++++++++++-------
 python/tvm/_ffi/libinfo.py                  |  3 +-
 src/api/api_pass.cc                         | 12 +++++-
 src/arithmetic/canonical.cc                 | 24 ++++++++++++
 tests/python/unittest/test_pass_simplify.py |  8 ++++
 6 files changed, 86 insertions(+), 23 deletions(-)

diff --git a/include/tvm/ir_pass.h b/include/tvm/ir_pass.h
index 897f96c763e7..b6b24822805a 100644
--- a/include/tvm/ir_pass.h
+++ b/include/tvm/ir_pass.h
@@ -22,13 +22,21 @@
 namespace tvm {
 namespace ir {
 
-inline Expr Simplify(Expr a) {
-  return Halide::Internal::simplify(a);
-}
+/*!
+ * \brief Simplify the expression.
+ * \param expr The expression to be simplifed.
+ * \param vrange The range information about the variable.
+ * \return Canonicalized statement.
+ */
+Expr Simplify(Expr expr, Map<Var, Range> vrange = Map<Var, Range>());
 
-inline Stmt Simplify(Stmt a) {
-  return Halide::Internal::simplify(a);
-}
+/*!
+ * \brief Simplify the statement.
+ * \param stmt The statement to be simplifed.
+ * \param vrange The range information about the variable.
+ * \return Canonicalized statement.
+ */
+Stmt Simplify(Stmt stmt, Map<Var, Range> vrange = Map<Var, Range>());
 
 /*!
  * \brief Simplify by applying canonical form.
diff --git a/python/setup.py b/python/setup.py
index 168729391412..5a87325e9a1a 100644
--- a/python/setup.py
+++ b/python/setup.py
@@ -18,16 +18,25 @@
     from setuptools import setup
     from setuptools.extension import Extension
 
-# We can not import `libinfo.py` in setup.py directly since __init__.py
-# Will be invoked which introduces dependences
-CURRENT_DIR = os.path.dirname(__file__)
-libinfo_py = os.path.join(CURRENT_DIR, './tvm/_ffi/libinfo.py')
-libinfo = {'__file__': libinfo_py}
-exec(compile(open(libinfo_py, "rb").read(), libinfo_py, 'exec'), libinfo, libinfo)
+def get_lib_path():
+    """Get library path, name and version"""
+    # We can not import `libinfo.py` in setup.py directly since __init__.py
+    # Will be invoked which introduces dependences
+    CURRENT_DIR = os.path.dirname(__file__)
+    libinfo_py = os.path.join(CURRENT_DIR, './tvm/_ffi/libinfo.py')
+    libinfo = {'__file__': libinfo_py}
+    exec(compile(open(libinfo_py, "rb").read(), libinfo_py, 'exec'), libinfo, libinfo)
+    lib_path = libinfo['find_lib_path']()
+    version = libinfo['__version__']
+    libs = [lib_path[0]]
+    if libs[0].find("runtime") == -1:
+        for name in lib_path[1:]:
+            if name.find("runtime") != -1:
+                libs.append(name)
+                break
+    return libs, version
 
-LIB_PATH = libinfo['find_lib_path']()
-_, LIB_NAME = os.path.split(LIB_PATH[0])
-__version__ = libinfo['__version__']
+LIB_LIST, __version__ = get_lib_path()
 
 def config_cython():
     """Try to configure cython and return cython configuration"""
@@ -81,18 +90,21 @@ def is_pure(self):
 
 # For bdist_wheel only
 if "bdist_wheel" in sys.argv:
-    shutil.copy(LIB_PATH[0], os.path.join(CURRENT_DIR, 'tvm'))
     with open("MANIFEST.in", "w") as fo:
-        fo.write("include tvm/%s\n" % LIB_NAME)
+        for path in LIB_LIST:
+            shutil.copy(path, os.path.join(CURRENT_DIR, 'tvm'))
+            _, libname = os.path.split(path)
+            fo.write("include tvm/%s\n" % libname)
     setup_kwargs = {
         "include_package_data": True
     }
 else:
     curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
-    rpath = os.path.relpath(LIB_PATH[0], curr_path)
+    for i, path in enumerate(LIB_LIST):
+        LIB_LIST[i] = os.path.relpath(path, curr_path)
     setup_kwargs = {
         "include_package_data": True,
-        "data_files": [('tvm', [rpath])]
+        "data_files": [('tvm', LIB_LIST)]
     }
 
 setup(name='tvm',
@@ -112,4 +124,6 @@ def is_pure(self):
 # Wheel cleanup
 if "bdist_wheel" in sys.argv:
     os.remove("MANIFEST.in")
-    os.remove("tvm/%s" % LIB_NAME)
+    for path in LIB_LIST:
+        _, libname = os.path.split(path)
+        os.remove("tvm/%s" % LIB_NAME)
diff --git a/python/tvm/_ffi/libinfo.py b/python/tvm/_ffi/libinfo.py
index 273e8f8fb003..f3ed174c0d44 100644
--- a/python/tvm/_ffi/libinfo.py
+++ b/python/tvm/_ffi/libinfo.py
@@ -74,7 +74,8 @@ def find_lib_path(name=None, search_path=None):
     if not use_runtime:
         # try to find lib_dll_path
         lib_found = [p for p in lib_dll_path if os.path.exists(p) and os.path.isfile(p)]
-    if use_runtime or not lib_found:
+        lib_found += [p for p in runtime_dll_path if os.path.exists(p) and os.path.isfile(p)]
+    else:
         # try to find runtime_dll_path
         use_runtime = True
         lib_found = [p for p in runtime_dll_path if os.path.exists(p) and os.path.isfile(p)]
diff --git a/src/api/api_pass.cc b/src/api/api_pass.cc
index a3134f5114e1..024af23a37e9 100644
--- a/src/api/api_pass.cc
+++ b/src/api/api_pass.cc
@@ -16,9 +16,17 @@ namespace ir {
 TVM_REGISTER_API("ir_pass.Simplify")
 .set_body([](TVMArgs args, TVMRetValue *ret) {
     if (args[0].IsNodeType<Stmt>()) {
-      *ret = Simplify(args[0].operator Stmt());
+      if (args.size() > 1) {
+        *ret = Simplify(args[0].operator Stmt(), args[1]);
+      } else {
+        *ret = Simplify(args[0].operator Stmt());
+      }
     } else {
-      *ret = Simplify(args[0].operator Expr());
+      if (args.size() > 1) {
+        *ret = Simplify(args[0].operator Expr(), args[1]);
+      } else {
+        *ret = Simplify(args[0].operator Expr());
+      }
     }
   });
 
diff --git a/src/arithmetic/canonical.cc b/src/arithmetic/canonical.cc
index 933a8f78ea16..808e070ef162 100644
--- a/src/arithmetic/canonical.cc
+++ b/src/arithmetic/canonical.cc
@@ -7,6 +7,7 @@
 #include <tvm/arithmetic.h>
 #include "./canonical.h"
 #include "./compute_expr.h"
+#include "arithmetic/Simplify.h"
 
 namespace tvm {
 namespace arith {
@@ -559,5 +560,28 @@ Stmt CanonicalSimplify(Stmt stmt) {
 Expr CanonicalSimplify(Expr expr) {
   return arith::Canonical().Simplify(expr);
 }
+
+template<typename T>
+T Simplify_(T a, Map<Var, Range> vrange) {
+  using namespace Halide::Internal;
+  Scope<Interval> rscope;
+  for (auto kv : vrange) {
+    Range r = kv.second;
+    rscope.push(
+        kv.first.get(),
+        Interval(r->min,
+                 simplify(r->min + r->extent - make_const(r->min.type(), 1))));
+  }
+  return Halide::Internal::simplify(a, true, rscope);
+}
+
+
+Expr Simplify(Expr a, Map<Var, Range> vrange) {
+  return Simplify_(a, vrange);
+}
+
+Stmt Simplify(Stmt a, Map<Var, Range> vrange) {
+  return Simplify_(a, vrange);
+}
 }  // namespace ir
 }  // namespace tvm
diff --git a/tests/python/unittest/test_pass_simplify.py b/tests/python/unittest/test_pass_simplify.py
index 2cc8825e37f3..9105693b3835 100644
--- a/tests/python/unittest/test_pass_simplify.py
+++ b/tests/python/unittest/test_pass_simplify.py
@@ -27,6 +27,13 @@ def test_basic():
     assert str(ret.value) == "(m - 1)"
 
 
+def test_bound():
+    m = tvm.var('m')
+    vrange = tvm.convert({m: tvm.Range(tvm.const(0), tvm.const(10))})
+    ret = tvm.ir_pass.Simplify(m % 10, vrange)
+    assert ret == m
+
+
 def test_canonical():
     x = tvm.var("x")
     z = tvm.const(3)
@@ -37,6 +44,7 @@ def test_canonical():
     assert(tvm.ir_pass.Equal(ret, 0))
 
 if __name__ == "__main__":
+    test_bound()
     test_basic()
     test_simplify()
     test_canonical()

From 29512f1d234de3395af9d4b473961c31b0968fa9 Mon Sep 17 00:00:00 2001
From: Siva <srk.it38@gmail.com>
Date: Fri, 24 Nov 2017 00:04:57 +0530
Subject: [PATCH 027/949] Documentation correction (#665)

Readability.
---
 src/README.md | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/src/README.md b/src/README.md
index e4fc992b2d8f..dfa7a1d33d22 100644
--- a/src/README.md
+++ b/src/README.md
@@ -4,13 +4,13 @@ Header files in include are public APIs that share across modules.
 There can be internal header files within each module that sit in src.
 
 ## Modules
-- common Internal common utilities.
-- api API function registration
-- lang The definition of DSL related data structure
-- arithmetic Arithmetic expression and set simplification
-- op The detail implementations about each operation(compute, scan, placeholder)
-- schedule The operations on the schedule graph before converting to IR.
-- pass The optimization pass on the IR structure
-- codegen The code generator.
-- runtime Minimum runtime related codes
-- contrib Contrib extension libraries
+- common: Internal common utilities.
+- api: API function registration
+- lang: The definition of DSL related data structure
+- arithmetic: Arithmetic expression and set simplification
+- op: The detail implementations about each operation(compute, scan, placeholder)
+- schedule: The operations on the schedule graph before converting to IR.
+- pass: The optimization pass on the IR structure
+- codegen: The code generator.
+- runtime: Minimum runtime related codes
+- contrib: Contrib extension libraries

From 3de26d2e3f093a8a0bdc8b868da540a5a3c4af33 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 25 Nov 2017 10:00:14 -0800
Subject: [PATCH 028/949] [PASS] Allow compact checking when strides is
 available (#669)

* [PASS] Allow compact checking when strides is available

* remove assert compact
---
 src/codegen/stack_vm/codegen_stack_vm.cc |  1 +
 src/pass/arg_binder.cc                   | 30 +++++++++++++++++-------
 src/pass/ir_util.cc                      |  5 ++++
 topi/python/topi/nn/conv2d.py            |  3 ++-
 4 files changed, 30 insertions(+), 9 deletions(-)

diff --git a/src/codegen/stack_vm/codegen_stack_vm.cc b/src/codegen/stack_vm/codegen_stack_vm.cc
index 97a2388f16e4..5b01dae7100a 100644
--- a/src/codegen/stack_vm/codegen_stack_vm.cc
+++ b/src/codegen/stack_vm/codegen_stack_vm.cc
@@ -362,6 +362,7 @@ void CodeGenStackVM::VisitExpr_(const Or *op) {
 }
 
 void CodeGenStackVM::VisitExpr_(const Not* op) {
+  this->Push(op->a);
   this->PushOp(StackVM::NOT);
 }
 
diff --git a/src/pass/arg_binder.cc b/src/pass/arg_binder.cc
index f9969cc5dba2..20c8593a1494 100644
--- a/src/pass/arg_binder.cc
+++ b/src/pass/arg_binder.cc
@@ -136,12 +136,6 @@ inline Expr TVMArrayGet(Type t, Var arr, intrinsic::TVMStructFieldKind kind) {
   return TVMStructGet(t, arr, 0, kind);
 }
 
-inline Stmt AssertNull(Var handle, std::string msg) {
-  return AssertStmt::make(Call::make(
-      Bool(1), intrinsic::tvm_handle_is_null,
-      {handle}, Call::PureIntrinsic), msg, Evaluate::make(0));
-}
-
 void ArgBinder::BindDLTensor(const Buffer& buffer,
                              const Expr& device_type,
                              const Expr& device_id,
@@ -201,10 +195,30 @@ void ArgBinder::BindDLTensor(const Buffer& buffer,
       v_strides, TVMArrayGet(Handle(), handle, intrinsic::kArrStrides),
       nop));
   if (buffer->strides.size() == 0) {
+    // Assert the buffer is compact
+    Type stype = buffer->shape[0].type();
+    Expr expect_stride = make_const(stype, 1);
+    Array<Expr> conds;
+    for (size_t i = buffer->shape.size(); i != 0; --i) {
+      size_t k = i - 1;
+      Expr svalue = cast(
+          stype,
+          Load::make(tvm_shape_type, v_strides,
+                     IntImm::make(Int(32), k), const_true(1)));
+      conds.push_back(expect_stride == svalue);
+      expect_stride = expect_stride * buffer->shape[k];
+    }
     std::ostringstream stride_err_msg;
     stride_err_msg << arg_name << ".strides:"
-                   << " expected to be nullptr for contiguous array";
-    init_nest_.emplace_back(AssertNull(v_strides, stride_err_msg.str()));
+                   << " expected to be compact array";
+    Stmt check =
+        AssertStmt::make(arith::ComputeReduce<ir::And>(conds),
+                         stride_err_msg.str(), Evaluate::make(0));
+    Expr is_null = Call::make(
+        Bool(1), intrinsic::tvm_handle_is_null,
+        {v_strides}, Call::PureIntrinsic);
+    check = IfThenElse::make(Not::make(is_null), check, Stmt());
+    init_nest_.emplace_back(Block::make(check, Evaluate::make(0)));
   } else {
     for (size_t k = 0; k < buffer->strides.size(); ++k) {
       std::ostringstream field_name;
diff --git a/src/pass/ir_util.cc b/src/pass/ir_util.cc
index 12551947ad7c..579706ca9964 100644
--- a/src/pass/ir_util.cc
+++ b/src/pass/ir_util.cc
@@ -33,6 +33,11 @@ Stmt MergeNest(const std::vector<Stmt>& nest, Stmt body) {
       CHECK(!n->else_case.defined());
       n->then_case = body;
       body = Stmt(n);
+    } else if (s.as<Block>()) {
+      auto n = std::make_shared<Block>(*s.as<Block>());
+      CHECK(is_no_op(n->rest));
+      n->rest = body;
+      body = Stmt(n);
     } else if (s.as<AssertStmt>()) {
       auto n = std::make_shared<AssertStmt>(*s.as<AssertStmt>());
       CHECK(is_no_op(n->body));
diff --git a/topi/python/topi/nn/conv2d.py b/topi/python/topi/nn/conv2d.py
index cc1ee0198c3d..11866aedc101 100644
--- a/topi/python/topi/nn/conv2d.py
+++ b/topi/python/topi/nn/conv2d.py
@@ -225,7 +225,8 @@ def _im2col_pack(data, kernel, stride, padding, out_dtype):
     wk = tvm.reduce_axis((0, KW), name='wk')
 
     conv = tvm.compute(ovshape, lambda n, co, im, vim, vco: \
-        tvm.sum(data_vec[n][im][ci][hk][wk][vim] * kernel_vec[co][ci][hk][wk][vco],
+        tvm.sum(data_vec[n][im][ci][hk][wk][vim].astype(out_dtype) *
+                kernel_vec[co][ci][hk][wk][vco].astype(out_dtype),
                 axis=[ci, hk, wk]), name='conv')
 
     output = tvm.compute(oshape, lambda n, co, h, w: \

From 6aa8e57f66d0abac5d12c4d88b92e3da12564877 Mon Sep 17 00:00:00 2001
From: ziheng <ziheng@apache.org>
Date: Mon, 27 Nov 2017 14:00:42 -0800
Subject: [PATCH 029/949] [TOPI] Fix for pooling (#673)

---
 topi/python/topi/nn/pooling.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/topi/python/topi/nn/pooling.py b/topi/python/topi/nn/pooling.py
index 955f724220a5..99b15e18e4e1 100644
--- a/topi/python/topi/nn/pooling.py
+++ b/topi/python/topi/nn/pooling.py
@@ -81,14 +81,14 @@ def pool(data, kernel, stride, padding, pool_type, ceil_mode=False):
     pad_top, pad_left, pad_down, pad_right = get_pad_tuple(
         padding, (kernel_height, kernel_width))
 
-    pad_before = [0, 0, pad_top, pad_left]
-    pad_after = [0, 0, pad_down, pad_right]
-
     if ceil_mode:
         # Additional padding to ensure we do ceil instead of floor when divide stride.
         pad_down += stride_height -1
         pad_right += stride_width - 1
 
+    pad_before = [0, 0, pad_top, pad_left]
+    pad_after = [0, 0, pad_down, pad_right]
+
     out_height = util.simplify((height - kernel_height + pad_top + pad_down) // stride_height + 1)
     out_width = util.simplify((width - kernel_width + pad_left + pad_right) // stride_width + 1)
 

From 93460475f73981da5a9d4960e2ae51c98ffdafe7 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 28 Nov 2017 13:26:17 -0800
Subject: [PATCH 030/949] [ARITH] Upgrade CanonicalSimplify to Simplify Mod
 (#676)

---
 include/tvm/ir_pass.h                        |  8 +-
 src/api/api_pass.cc                          | 12 ++-
 src/arithmetic/canonical.cc                  | 83 +++++++++++++++++---
 src/arithmetic/canonical.h                   |  2 +-
 tests/python/unittest/test_arith_simplify.py | 18 +++++
 5 files changed, 106 insertions(+), 17 deletions(-)

diff --git a/include/tvm/ir_pass.h b/include/tvm/ir_pass.h
index b6b24822805a..d0f32478eb4c 100644
--- a/include/tvm/ir_pass.h
+++ b/include/tvm/ir_pass.h
@@ -41,16 +41,20 @@ Stmt Simplify(Stmt stmt, Map<Var, Range> vrange = Map<Var, Range>());
 /*!
  * \brief Simplify by applying canonical form.
  * \param stmt The statement to be canonically simplifed.
+ * \param vrange The range information about the variable.
  * \return Canonicalized statement.
  */
-Stmt CanonicalSimplify(Stmt stmt);
+Stmt CanonicalSimplify(Stmt stmt,
+                       Map<Var, Range> vrange = Map<Var, Range>());
 
 /*!
  * \brief Simplify by applying canonical form.
  * \param expr The statement to be canonically simplifed.
+ * \param vrange The range information about the variable.
  * \return Canonicalized expression.
  */
-Expr CanonicalSimplify(Expr expr);
+Expr CanonicalSimplify(Expr expr,
+                       Map<Var, Range> vrange = Map<Var, Range>());
 
 /*!
  * \brief Deep compare lhs and rhs
diff --git a/src/api/api_pass.cc b/src/api/api_pass.cc
index 024af23a37e9..23deb03af482 100644
--- a/src/api/api_pass.cc
+++ b/src/api/api_pass.cc
@@ -33,9 +33,17 @@ TVM_REGISTER_API("ir_pass.Simplify")
 TVM_REGISTER_API("ir_pass.CanonicalSimplify")
 .set_body([](TVMArgs args, TVMRetValue *ret) {
     if (args[0].IsNodeType<Stmt>()) {
-      *ret = CanonicalSimplify(args[0].operator Stmt());
+      if (args.size() > 1) {
+        *ret = CanonicalSimplify(args[0].operator Stmt(), args[1]);
+      } else {
+        *ret = CanonicalSimplify(args[0].operator Stmt());
+      }
     } else {
-      *ret = CanonicalSimplify(args[0].operator Expr());
+      if (args.size() > 1) {
+        *ret = CanonicalSimplify(args[0].operator Expr(), args[1]);
+      } else {
+        *ret = CanonicalSimplify(args[0].operator Expr());
+      }
     }
   });
 
diff --git a/src/arithmetic/canonical.cc b/src/arithmetic/canonical.cc
index 808e070ef162..e7f9da1b448a 100644
--- a/src/arithmetic/canonical.cc
+++ b/src/arithmetic/canonical.cc
@@ -129,6 +129,11 @@ inline Expr Binary_(const T* op,
 // internal of canonical engine.
 class Canonical::Internal : public IRMutator {
  public:
+  explicit Internal(Map<Var, Range> vrange) {
+    for (auto kv : vrange) {
+      SetRange(kv.first, kv.second, 0);
+    }
+  }
   // stack entry.
   struct StackEntry {
     int max_level{0};
@@ -300,9 +305,25 @@ class Canonical::Internal : public IRMutator {
   Expr Mutate_(const Div* op, const Expr& e) final {
     return Binary(op, e);
   }
+  // Mod operator
   Expr Mutate_(const Mod* op, const Expr& e) final {
-    return Binary(op, e);
+    if (!EnableOpt(op->type)) {
+      return Binary(op, e);
+    }
+    CacheEntry a = Produce(op->a);
+    CacheEntry b = Produce(op->b);
+    if (a.has_side_effect || b.has_side_effect) {
+      return Binary_(op, e, a.value, b.value);
+    }
+    if (is_const(a.value) && is_const(b.value)) {
+      return ComputeExpr<Mul>(a.value, b.value);
+    } else if (is_const(b.value)) {
+      return SumModConst(a.AsSum(), b.value);
+    } else {
+      return Binary(op, e);
+    }
   }
+
   Expr Mutate_(const And* op, const Expr& e) final {
     Expr expr = IRMutator::Mutate_(op, e);
     op = expr.as<And>();
@@ -367,7 +388,7 @@ class Canonical::Internal : public IRMutator {
 
  private:
   template<typename T>
-  Expr Binary(const T* op, const Expr& e) {
+  Expr Binary(const T* op, Expr e) {
     Expr a = this->Mutate(op->a);
     Expr b = this->Mutate(op->b);
     BinaryExpr key{static_cast<int>(T::_type_info), a, b};
@@ -398,8 +419,8 @@ class Canonical::Internal : public IRMutator {
   std::vector<Var> var_rec_;
   // level counter
   int level_counter_{0};
-  // subroutine to do produce
-  Expr SumMulConst(ComExpr a, Expr v) {
+  // get constant int value
+  int64_t GetConstIntValue(const Expr& v) {
     int64_t value = 0;
     const int64_t *v1 = as_const_int(v);
     const uint64_t *v2 = as_const_uint(v);
@@ -411,7 +432,45 @@ class Canonical::Internal : public IRMutator {
                static_cast<uint64_t>(std::numeric_limits<int64_t>::max()));
       value = static_cast<int64_t>(*v2);
     }
-
+    return value;
+  }
+  // subroutine to do produce a % v
+  Expr SumModConst(ComExpr a, Expr v) {
+    int64_t value = GetConstIntValue(v);
+    std::shared_ptr<ComExprNode> n = std::make_shared<ComExprNode>();
+    int mod_level = 0;
+    n->base = a->base % value;
+    if (n->base != 0) mod_level = 1;
+    for (auto e : a->elem) {
+      if (e.scale % value == 0) continue;
+      e.scale = e.scale % value;
+      if (!EvalSet(v - e.value, var_range_).can_prove_positive()) {
+        mod_level = 2;
+      } else {
+        ++mod_level;
+      }
+      n->elem.push_back(e);
+    }
+    // cannot remove mode because there are more than two parts
+    if (mod_level >= 2) {
+      Expr ret = Sum2Expr(ComExpr(n), v.type()) % v;
+      return Binary(ret.as<Mod>(), ret);
+    }
+    ret_entry_.sum = ComExpr(n);
+    ret_entry_.max_level = stack_.back().max_level;
+    ret_entry_.has_side_effect = stack_.back().has_side_effect;
+    auto it = cache_sum_.find(ret_entry_.sum);
+    if (it != cache_sum_.end()) {
+      ret_entry_ = it->second;
+    } else {
+      ret_entry_.value = Sum2Expr(ret_entry_.sum, v.type());
+      cache_sum_[ret_entry_.sum] = ret_entry_;
+    }
+    return ret_entry_.value;
+  }
+  // subroutine to do produce
+  Expr SumMulConst(ComExpr a, Expr v) {
+    int64_t value = GetConstIntValue(v);
     if (value == 0) {
       return make_zero(v.type());
     }
@@ -421,9 +480,9 @@ class Canonical::Internal : public IRMutator {
     for (auto& e : vsum->elem) {
       e.scale *= value;
     }
+    ret_entry_.sum = ComExpr(vsum);
     ret_entry_.max_level = stack_.back().max_level;
     ret_entry_.has_side_effect = stack_.back().has_side_effect;
-    ret_entry_.sum = ComExpr(vsum);
     auto it = cache_sum_.find(ret_entry_.sum);
     if (it != cache_sum_.end()) {
       ret_entry_ = it->second;
@@ -536,8 +595,8 @@ class Canonical::Internal : public IRMutator {
 
 using CInternal = Canonical::Internal;
 
-Canonical::Canonical()
-    : ptr_(std::make_shared<Internal>()) {}
+Canonical::Canonical(Map<Var, Range> vrange)
+    : ptr_(std::make_shared<Internal>(vrange)) {}
 
 Expr Canonical::Simplify(Expr expr) {
   return ptr_->Mutate(expr);
@@ -553,12 +612,12 @@ void Canonical::SetRange(Var v, Range r, int level) {
 }  // namespace arith
 
 namespace ir {
-Stmt CanonicalSimplify(Stmt stmt) {
-  return arith::Canonical().Simplify(stmt);
+Stmt CanonicalSimplify(Stmt stmt, Map<Var, Range> vrange) {
+  return arith::Canonical(vrange).Simplify(stmt);
 }
 
-Expr CanonicalSimplify(Expr expr) {
-  return arith::Canonical().Simplify(expr);
+Expr CanonicalSimplify(Expr expr, Map<Var, Range> vrange) {
+  return arith::Canonical(vrange).Simplify(expr);
 }
 
 template<typename T>
diff --git a/src/arithmetic/canonical.h b/src/arithmetic/canonical.h
index 174acc20aebe..37f9a178f696 100644
--- a/src/arithmetic/canonical.h
+++ b/src/arithmetic/canonical.h
@@ -22,7 +22,7 @@ namespace arith {
 class Canonical {
  public:
   /*! \brief constructor */
-  Canonical();
+  explicit Canonical(Map<Var, Range> var_range);
   /*!
    * \brief simplify expression e.
    * \param expr The expression to be simplified.
diff --git a/tests/python/unittest/test_arith_simplify.py b/tests/python/unittest/test_arith_simplify.py
index 9ff8571eac42..8ce1773ee3c4 100644
--- a/tests/python/unittest/test_arith_simplify.py
+++ b/tests/python/unittest/test_arith_simplify.py
@@ -20,5 +20,23 @@ def test_simplify():
     zz = zz.a
     assert zz.a == x and zz.b.value == 4
 
+def test_simplify_mod():
+    """Not yet working, mock design"""
+    ib = tvm.ir_builder.create()
+    n = tvm.var('n')
+    j = tvm.var('j')
+    A = ib.pointer("float32", name="A")
+    with ib.for_range(0, 16, name="i") as i:
+        A[i] = A[((n * 4 + j * 2) * 8 + i+1) % 16]
+    body = ib.get()
+    stmt = tvm.ir_pass.CanonicalSimplify(body)
+    diff = tvm.ir_pass.CanonicalSimplify(stmt.body.value.index - (1 + i) % 16)
+    assert diff.value == 0
+    index = tvm.ir_pass.CanonicalSimplify(
+        (j + n * 32) % 16, {j: tvm.Range(0, 6)})
+    assert index == j
+
+
 if __name__ == "__main__":
+    test_simplify_mod()
     test_simplify()

From 0824de5489a6f7a9c94a804a7127d7536107edbf Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 28 Nov 2017 17:08:24 -0800
Subject: [PATCH 031/949] [ANDROID][RPC] Remove binary distro jar (#677)

* [RPC][JVM] Remove binary dist gradle from repo

* fix header
---
 .../gradle/wrapper/gradle-wrapper.jar           | Bin 53636 -> 0 bytes
 src/op/op_util.cc                               |   2 +-
 2 files changed, 1 insertion(+), 1 deletion(-)
 delete mode 100644 apps/android_rpc/gradle/wrapper/gradle-wrapper.jar

diff --git a/apps/android_rpc/gradle/wrapper/gradle-wrapper.jar b/apps/android_rpc/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index 13372aef5e24af05341d49695ee84e5f9b594659..0000000000000000000000000000000000000000
GIT binary patch
diff --git a/src/op/op_util.cc b/src/op/op_util.cc
index 7cf6711d2270..78e092ca844e 100644
--- a/src/op/op_util.cc
+++ b/src/op/op_util.cc
@@ -1,5 +1,5 @@
 /*!
- *  Copyright (c) 2017 by5A Contributors
+ *  Copyright (c) 2017 by Contributors
  * \brief Utility to make loop nest.
  * \file op_util.cc
  */

From e9ef432f9ca3a926ef1e3461517d78412bb7d085 Mon Sep 17 00:00:00 2001
From: solin319 <lipengfei19890603@126.com>
Date: Fri, 1 Dec 2017 01:37:11 +0800
Subject: [PATCH 032/949] fix parameter name in UnrollLoop (#679)

In unroll_loop.cc the parameter name is "auto_max_depth", but in ir_pass.h the parameter name is "auto_min_depth"
---
 include/tvm/ir_pass.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/include/tvm/ir_pass.h b/include/tvm/ir_pass.h
index d0f32478eb4c..e763a75e7ee0 100644
--- a/include/tvm/ir_pass.h
+++ b/include/tvm/ir_pass.h
@@ -216,7 +216,7 @@ Stmt NarrowChannelAccess(Stmt stmt);
  *
  * \param stmt The statment to be unrolled.
  * \param auto_max_step The maximum step before stop attach automatic unroll
- * \param auto_min_depth The minimum depth before we can start automatic unroll
+ * \param auto_max_depth The maximum depth before stop attach automatic unroll
  * \param auto_max_extent The maximum extent of the loop we can unroll,
  *                        this is an legacy option that donot take the loop total steps into account.
  * \param explicit_unroll Whether explicitly unroll the loop, or leave unroll annotation to codegen.
@@ -224,7 +224,7 @@ Stmt NarrowChannelAccess(Stmt stmt);
  */
 Stmt UnrollLoop(Stmt stmt,
                 int auto_max_step,
-                int auto_min_depth,
+                int auto_max_depth,
                 int auto_max_extent,
                 bool explicit_unroll);
 

From 94123a362e5673a29dd5567d437096bd229839f5 Mon Sep 17 00:00:00 2001
From: solin319 <lipengfei19890603@126.com>
Date: Fri, 1 Dec 2017 01:37:26 +0800
Subject: [PATCH 033/949] fix name bug in test_pass_inject_double_buffer (#678)

Change the parameter 'C' name
---
 tests/python/unittest/test_pass_inject_double_buffer.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/python/unittest/test_pass_inject_double_buffer.py b/tests/python/unittest/test_pass_inject_double_buffer.py
index 3136e33197ec..0e3500edf2e3 100644
--- a/tests/python/unittest/test_pass_inject_double_buffer.py
+++ b/tests/python/unittest/test_pass_inject_double_buffer.py
@@ -7,7 +7,7 @@ def test_double_buffer():
     tx = tvm.thread_axis("threadIdx.x")
     ib = tvm.ir_builder.create()
     A = ib.pointer("float32", name="A")
-    C = ib.pointer("float32", name="A")
+    C = ib.pointer("float32", name="C")
     ib.scope_attr(tx, "thread_extent", 1)
     with ib.for_range(0, n) as i:
         B = ib.allocate("float32", m, name="B", scope="shared")

From dfabb3616cec17ab9b284765c772f86a3c32a346 Mon Sep 17 00:00:00 2001
From: Yizhi Liu <javelinjs@gmail.com>
Date: Thu, 30 Nov 2017 14:19:49 -0800
Subject: [PATCH 034/949] [APP] fix gradle build for Android build (#685)

---
 apps/android_rpc/README.md                    |   8 +-
 .../gradle/wrapper/gradle-wrapper.properties  |   6 -
 apps/android_rpc/gradlew                      | 160 ------------------
 apps/android_rpc/gradlew.bat                  |  90 ----------
 4 files changed, 5 insertions(+), 259 deletions(-)
 delete mode 100644 apps/android_rpc/gradle/wrapper/gradle-wrapper.properties
 delete mode 100755 apps/android_rpc/gradlew
 delete mode 100644 apps/android_rpc/gradlew.bat

diff --git a/apps/android_rpc/README.md b/apps/android_rpc/README.md
index 062227b3e424..00cb597cec28 100644
--- a/apps/android_rpc/README.md
+++ b/apps/android_rpc/README.md
@@ -8,6 +8,8 @@ You will need JDK, [Android NDK](https://developer.android.com/ndk) and an Andro
 
 ### <a name="buildapk">Build APK</a>
 
+We use [Gradle](https://gradle.org) to build. Please follow [the installation instruction](https://gradle.org/install) for your operating system.
+
 Before you build the Android application, please refer to [TVM4J Installation Guide](https://github.com/dmlc/tvm/blob/master/jvm/README.md) and install tvm4j-core to your local maven repository. You can find tvm4j dependency declare in `app/build.gradle`. Modify it if it is necessary.
 
 ```
@@ -24,17 +26,17 @@ dependencies {
 }
 ```
 
-The Gradle build script is provided in the app root folder. It downloads the proper version of Gradle, compiles JNI, resolves Java dependencies and builds the Android application together with tvm4j. Run following script to build apk file.
+Now use Gradle to compile JNI, resolve Java dependencies and build the Android application together with tvm4j. Run following script to generate the apk file.
 
 ```bash
 export ANDROID_HOME=[Path to your Android SDK, e.g., ~/Android/sdk]
 cd apps/android_rpc
-./gradlew clean build
+gradle clean build
 ```
 
 In `app/build/outputs/apk` you'll find `app-release-unsigned.apk`, use `dev_tools/gen_keystore.sh` to generate a signature and use `dev_tools/sign_apk.sh` to get the signed apk file `app/build/outputs/apk/tvmrpc-release.apk`.
 
-Now upload `tvmrpc-release.apk` to your Android device and install it.
+Upload `tvmrpc-release.apk` to your Android device and install it.
 
 ### Build with OpenCL
 
diff --git a/apps/android_rpc/gradle/wrapper/gradle-wrapper.properties b/apps/android_rpc/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index 80a1f0954c16..000000000000
--- a/apps/android_rpc/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-#Mon Aug 14 21:31:55 CST 2017
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
diff --git a/apps/android_rpc/gradlew b/apps/android_rpc/gradlew
deleted file mode 100755
index 9d82f7891513..000000000000
--- a/apps/android_rpc/gradlew
+++ /dev/null
@@ -1,160 +0,0 @@
-#!/usr/bin/env bash
-
-##############################################################################
-##
-##  Gradle start up script for UN*X
-##
-##############################################################################
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn ( ) {
-    echo "$*"
-}
-
-die ( ) {
-    echo
-    echo "$*"
-    echo
-    exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-case "`uname`" in
-  CYGWIN* )
-    cygwin=true
-    ;;
-  Darwin* )
-    darwin=true
-    ;;
-  MINGW* )
-    msys=true
-    ;;
-esac
-
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
-    ls=`ls -ld "$PRG"`
-    link=`expr "$ls" : '.*-> \(.*\)$'`
-    if expr "$link" : '/.*' > /dev/null; then
-        PRG="$link"
-    else
-        PRG=`dirname "$PRG"`"/$link"
-    fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
-
-# Determine the Java command to use to start the JVM.
-if [ -n "$JAVA_HOME" ] ; then
-    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
-        # IBM's JDK on AIX uses strange locations for the executables
-        JAVACMD="$JAVA_HOME/jre/sh/java"
-    else
-        JAVACMD="$JAVA_HOME/bin/java"
-    fi
-    if [ ! -x "$JAVACMD" ] ; then
-        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-    fi
-else
-    JAVACMD="java"
-    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-fi
-
-# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
-    MAX_FD_LIMIT=`ulimit -H -n`
-    if [ $? -eq 0 ] ; then
-        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
-            MAX_FD="$MAX_FD_LIMIT"
-        fi
-        ulimit -n $MAX_FD
-        if [ $? -ne 0 ] ; then
-            warn "Could not set maximum file descriptor limit: $MAX_FD"
-        fi
-    else
-        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
-    fi
-fi
-
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
-    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
-
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
-    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
-    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-    JAVACMD=`cygpath --unix "$JAVACMD"`
-
-    # We build the pattern for arguments to be converted via cygpath
-    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
-    SEP=""
-    for dir in $ROOTDIRSRAW ; do
-        ROOTDIRS="$ROOTDIRS$SEP$dir"
-        SEP="|"
-    done
-    OURCYGPATTERN="(^($ROOTDIRS))"
-    # Add a user-defined pattern to the cygpath arguments
-    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
-        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
-    fi
-    # Now convert the arguments - kludge to limit ourselves to /bin/sh
-    i=0
-    for arg in "$@" ; do
-        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
-        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
-
-        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
-            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
-        else
-            eval `echo args$i`="\"$arg\""
-        fi
-        i=$((i+1))
-    done
-    case $i in
-        (0) set -- ;;
-        (1) set -- "$args0" ;;
-        (2) set -- "$args0" "$args1" ;;
-        (3) set -- "$args0" "$args1" "$args2" ;;
-        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
-        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
-        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
-        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
-        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
-        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
-    esac
-fi
-
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
-    JVM_OPTS=("$@")
-}
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
-
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/apps/android_rpc/gradlew.bat b/apps/android_rpc/gradlew.bat
deleted file mode 100644
index aec99730b4e8..000000000000
--- a/apps/android_rpc/gradlew.bat
+++ /dev/null
@@ -1,90 +0,0 @@
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem  Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto init
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:init
-@rem Get command-line arguments, handling Windowz variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%@eval[2+2]" == "4" goto 4NT_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
-@rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%$
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega

From 16e1b54d6f24426882fe6a9a22abb255fc904b49 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 30 Nov 2017 14:24:44 -0800
Subject: [PATCH 035/949] [CUDA] Enable int64 (#683)

* [CUDA] Enable int64

* [PYTHON] Fix rpc tutorial with opencl

* OK

* update
---
 python/tvm/contrib/rpc.py                     | 46 +++++++++-
 src/codegen/codegen_cuda.cc                   | 18 ++--
 tests/python/integration/test_ewise.py        | 87 ++++++++++---------
 tests/scripts/task_python_docs.sh             |  2 +-
 .../deployment/cross_compilation_and_rpc.py   |  2 +-
 5 files changed, 101 insertions(+), 54 deletions(-)

diff --git a/python/tvm/contrib/rpc.py b/python/tvm/contrib/rpc.py
index 7b29b1ddac01..3d14f1eb2b6e 100644
--- a/python/tvm/contrib/rpc.py
+++ b/python/tvm/contrib/rpc.py
@@ -15,6 +15,8 @@
 import struct
 import logging
 import multiprocessing
+import subprocess
+import time
 from . import util, cc, tar
 from ..module import load as _load_module
 from .._ffi.function import _init_api, register_func
@@ -117,6 +119,17 @@ def _connect_proxy_loop(addr, key):
         process.join()
 
 
+def _popen(cmd):
+    proc = subprocess.Popen(cmd,
+                            stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
+                            env=os.environ)
+    (out, _) = proc.communicate()
+    if proc.returncode != 0:
+        msg = "Server invoke error:\n"
+        msg += out
+        raise RuntimeError(msg)
+
+
 class Server(object):
     """Start RPC server on a seperate process.
 
@@ -140,15 +153,36 @@ class Server(object):
         If this is true, the host and port actually corresponds to the
         address of the proxy server.
 
+    use_popen : bool, optional
+        Whether to use Popen to start a fresh new process instead of fork.
+        This is recommended to switch on if we want to do local RPC demonstration
+        for GPU devices to avoid fork safety issues.
+
     key : str, optional
         The key used to identify the server in Proxy connection.
     """
-    def __init__(self, host, port=9091, port_end=9199, is_proxy=False, key=""):
+    def __init__(self,
+                 host,
+                 port=9091,
+                 port_end=9199,
+                 is_proxy=False,
+                 use_popen=False,
+                 key=""):
         self.host = host
         self.port = port
         self.libs = []
 
-        if not is_proxy:
+        if use_popen:
+            cmd = ["python",
+                   "-m", "tvm.exec.rpc_server",
+                   "--host=%s" % host,
+                   "--port=%s" % port]
+            self.proc = multiprocessing.Process(
+                target=subprocess.check_call, args=(cmd,))
+            self.proc.deamon = True
+            self.proc.start()
+            time.sleep(1)
+        elif not is_proxy:
             sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
             self.port = None
             for my_port in range(port, port_end):
@@ -168,11 +202,15 @@ def __init__(self, host, port=9091, port_end=9199, is_proxy=False, key=""):
             self.sock = sock
             self.proc = multiprocessing.Process(
                 target=_listen_loop, args=(self.sock,))
+            self.proc.deamon = True
+            self.proc.start()
         else:
             self.proc = multiprocessing.Process(
                 target=_connect_proxy_loop, args=((host, port), key))
-        self.proc.deamon = True
-        self.proc.start()
+            self.proc.deamon = True
+            self.proc.start()
+
+
 
     def terminate(self):
         """Terminate the server process"""
diff --git a/src/codegen/codegen_cuda.cc b/src/codegen/codegen_cuda.cc
index 136f6eb4c3d5..249128d12f62 100644
--- a/src/codegen/codegen_cuda.cc
+++ b/src/codegen/codegen_cuda.cc
@@ -66,7 +66,11 @@ void CodeGenCUDA::PrintType(Type t, std::ostream& os) const {  // NOLINT(*)
     }
   } else if (t.is_uint() || t.is_int()) {
     if (t.is_uint()) {
-      os << 'u';
+      if (t.lanes() != 1) {
+        os << "u";
+      } else {
+        os << "unsigned ";
+      }
     }
     if (t.bits() == 8 && t.lanes() == 4) {
       // directly 4 8 bit int in integer.
@@ -77,16 +81,16 @@ void CodeGenCUDA::PrintType(Type t, std::ostream& os) const {  // NOLINT(*)
       case 16: os << "short"; break;
       case 32: os << "int"; break;
       case 64: {
-        if (lanes != 1 && sizeof(long) == 64) {  // NOLINT(*)
-          os << "long"; break;
-        } else {
-          os << "int64_t"; break;
-        }
+        CHECK(sizeof(long) == 8)  // NOLINT(*)
+            << "CUDA not support int64 int in 32 bit system";
+        os << "long"; break;
       }
       case 1: os << "int"; break;
       default: fail = true; break;
     }
-    if (!fail && lanes == 1) return;
+    if (!fail && lanes == 1) {
+      return;
+    }
     if (!fail && (lanes >= 2 && lanes <= 4)) {
       os << lanes; return;
     }
diff --git a/tests/python/integration/test_ewise.py b/tests/python/integration/test_ewise.py
index 8cbfef3ebbf8..24adf6ff28af 100644
--- a/tests/python/integration/test_ewise.py
+++ b/tests/python/integration/test_ewise.py
@@ -80,53 +80,58 @@ def test_popcount_llvm():
         b.asnumpy(), list(map(lambda x: bin(x).count('1'), a.asnumpy())), rtol=1e-5)
 
 
+
 def test_add():
-    # graph
-    n = tvm.var('n')
-    A = tvm.placeholder((n,), name='A')
-    B = tvm.placeholder((n,), name='B')
-    bias = tvm.var("bias", dtype="float32")
-    scale = tvm.var("scale", dtype="float32")
-    C = tvm.compute(A.shape, lambda *i: A(*i) + B(*i) * scale + bias, name='C')
-    # schedule
-    s = tvm.create_schedule(C.op)
-    # create iter var and assign them tags.
-    num_thread = 32
-    bx, x = s[C].split(C.op.axis[0], factor=num_thread*4)
-    tx, x = s[C].split(x, nparts=num_thread)
-    _, x = s[C].split(x, factor=4)
-    s[C].bind(bx, tvm.thread_axis("blockIdx.x"))
-    s[C].bind(tx, tvm.thread_axis("threadIdx.x"))
-    s[C].vectorize(x)
+    def run(dtype):
+        # graph
+        n = tvm.var('n')
+        A = tvm.placeholder((n,), name='A', dtype=dtype)
+        B = tvm.placeholder((n,), name='B', dtype=dtype)
+        bias = tvm.var("bias", dtype=dtype)
+        scale = tvm.var("scale", dtype=dtype)
+        C = tvm.compute(A.shape, lambda *i: A(*i) + B(*i), name='C')
+        # schedule
+        s = tvm.create_schedule(C.op)
+        # create iter var and assign them tags.
+        num_thread = 16
+        bx, x = s[C].split(C.op.axis[0], factor=num_thread*4)
+        tx, x = s[C].split(x, nparts=num_thread)
+        _, x = s[C].split(x, factor=4)
+        s[C].bind(bx, tvm.thread_axis("blockIdx.x"))
+        s[C].bind(tx, tvm.thread_axis("threadIdx.x"))
+        s[C].vectorize(x)
 
-    # one line to build the function.
-    def check_device(device):
-        if not tvm.module.enabled(device):
-            print("skip because %s is not enabled.." % device)
-            return
-        fadd = tvm.build(s, [A, B, C, bias, scale],
-                         device,
-                         name="myadd")
-        ctx = tvm.context(device, 0)
-        # launch the kernel.
-        n = 1024
-        a = tvm.nd.array(np.random.uniform(size=n).astype(A.dtype), ctx)
-        b = tvm.nd.array(np.random.uniform(size=n).astype(B.dtype), ctx)
-        c = tvm.nd.array(np.zeros(n, dtype=C.dtype), ctx)
-        vbias = np.random.uniform()
-        vscale = np.random.uniform()
-        ftimer = fadd.time_evaluator(fadd.entry_name, ctx, number=10)
-        tcost = ftimer(a, b, c, vbias, vscale).mean
-        np.testing.assert_allclose(
-            c.asnumpy(), a.asnumpy() + b.asnumpy() * vscale + vbias, rtol=1e-6)
+        # one line to build the function.
+        def check_device(device):
+            if not tvm.module.enabled(device):
+                print("skip because %s is not enabled.." % device)
+                return
+            fadd = tvm.build(s, [A, B, C],
+                             device,
+                             name="myadd")
+            print(fadd.imported_modules[0].get_source())
+            ctx = tvm.context(device, 0)
+            # launch the kernel.
+            n = 1024
+            a = tvm.nd.array((np.random.uniform(size=n) * 256).astype(A.dtype), ctx)
+            b = tvm.nd.array((np.random.uniform(size=n) * 256).astype(B.dtype), ctx)
+            c = tvm.nd.array(np.zeros(n, dtype=C.dtype), ctx)
+            ftimer = fadd.time_evaluator(fadd.entry_name, ctx, number=1)
+            tcost = ftimer(a, b, c).mean
+            np.testing.assert_allclose(
+                c.asnumpy(), a.asnumpy() + b.asnumpy(), rtol=1e-6)
 
-    check_device("opencl")
-    check_device("metal")
-    check_device("cuda")
+        check_device("opencl")
+        check_device("metal")
+        check_device("cuda")
+    run("float32")
+    run("int32")
+    run("int64")
+    run("uint64")
 
 
 if __name__ == "__main__":
+    test_add()
     test_log_pow_llvm()
     test_popcount_llvm()
     test_exp()
-    test_add()
diff --git a/tests/scripts/task_python_docs.sh b/tests/scripts/task_python_docs.sh
index b55dae933c6b..77c6f22acca5 100755
--- a/tests/scripts/task_python_docs.sh
+++ b/tests/scripts/task_python_docs.sh
@@ -11,7 +11,7 @@ mv out docs/_build/html/jsdoc || exit -1
 rm -rf python/tvm/*.pyc python/tvm/*/*.pyc
 
 cd docs
-PYTHONPATH=../python make html || exit -1
+PYTHONPATH=`pwd`/../python make html || exit -1
 cd _build/html
 tar czf docs.tgz *
 mv docs.tgz ../../../
diff --git a/tutorials/deployment/cross_compilation_and_rpc.py b/tutorials/deployment/cross_compilation_and_rpc.py
index 859b24472483..bfd77f287336 100644
--- a/tutorials/deployment/cross_compilation_and_rpc.py
+++ b/tutorials/deployment/cross_compilation_and_rpc.py
@@ -101,7 +101,7 @@
 # same machine, for demonstration. This line can be omitted if we
 # started an remote server.
 #
-server = rpc.Server(host='0.0.0.0', port=9090)
+server = rpc.Server(host='0.0.0.0', port=9090, use_popen=True)
 
 ######################################################################
 # Declare and Cross Compile Kernel on Local Machine

From b94d26f416e06104b5d0253736d5e1b4e3f8da24 Mon Sep 17 00:00:00 2001
From: Salem Derisavi <33945117+derisavi-huawei@users.noreply.github.com>
Date: Thu, 30 Nov 2017 18:44:08 -0500
Subject: [PATCH 036/949] Consider variable range information during
 simplification of tensorize expressions (#674)

---
 src/arithmetic/canonical.cc |  2 ++
 src/op/tensorize.cc         | 22 +++++++++++++++-------
 2 files changed, 17 insertions(+), 7 deletions(-)

diff --git a/src/arithmetic/canonical.cc b/src/arithmetic/canonical.cc
index e7f9da1b448a..24369db02390 100644
--- a/src/arithmetic/canonical.cc
+++ b/src/arithmetic/canonical.cc
@@ -5,6 +5,7 @@
  */
 #include <tvm/ir_mutator.h>
 #include <tvm/arithmetic.h>
+#include <tvm/ir_pass.h>
 #include "./canonical.h"
 #include "./compute_expr.h"
 #include "arithmetic/Simplify.h"
@@ -612,6 +613,7 @@ void Canonical::SetRange(Var v, Range r, int level) {
 }  // namespace arith
 
 namespace ir {
+
 Stmt CanonicalSimplify(Stmt stmt, Map<Var, Range> vrange) {
   return arith::Canonical(vrange).Simplify(stmt);
 }
diff --git a/src/op/tensorize.cc b/src/op/tensorize.cc
index 9715fcbab1d6..243b7931da67 100644
--- a/src/op/tensorize.cc
+++ b/src/op/tensorize.cc
@@ -187,7 +187,8 @@ class TensorIntrinMatcher final : public IRMutator {
             const Stage& stage,
             const std::unordered_map<IterVar, Range>& out_dom,
             const std::unordered_map<Tensor, Array<Range> >& in_region,
-            const TensorIntrin& intrin) {
+            const TensorIntrin& intrin,
+            Map<Var, Range>* compute_intrin_iter_space) {
     CHECK(self == stage->op.get());
     // input remap.
     Array<Tensor> inputs = self->InputTensors();
@@ -232,6 +233,7 @@ class TensorIntrinMatcher final : public IRMutator {
       Range r = out_dom.at(iv);
       var_remap_[iv->var.get()] = target_iv->var + r->min;
       axis_remap_[iv] = target_iv;
+      compute_intrin_iter_space->Set(target_iv->var, target_iv->dom);
     }
     // Remap reduction axis
     CHECK_GE(self->reduce_axis.size(), intrin_compute->reduce_axis.size())
@@ -251,6 +253,7 @@ class TensorIntrinMatcher final : public IRMutator {
       Range r = out_dom.at(iv);
       var_remap_[iv->var.get()] = target_iv->var + r->min;
       axis_remap_[iv] = target_iv;
+      compute_intrin_iter_space->Set(target_iv->var, target_iv->dom);
     }
   }
 
@@ -275,9 +278,10 @@ Array<Expr> MatchTensorizeBody(
     const Stage& stage,
     const std::unordered_map<IterVar, Range>& out_dom,
     const std::unordered_map<Tensor, Array<Range> >& in_region,
-    const TensorIntrin& intrin) {
+    const TensorIntrin& intrin,
+    Map<Var, Range>* compute_intrin_iter_space) {
   TensorIntrinMatcher matcher;
-  matcher.Init(self, stage, out_dom, in_region, intrin);
+  matcher.Init(self, stage, out_dom, in_region, intrin, compute_intrin_iter_space);
   Array<Expr> ret;
   for (Expr expr : self->body) {
     ret.push_back(matcher.Mutate(expr));
@@ -291,14 +295,16 @@ void VerifyTensorizeBody(
     const std::unordered_map<IterVar, Range>& out_dom,
     const std::unordered_map<Tensor, Array<Range> >& in_region,
     const TensorIntrin& intrin) {
-  Array<Expr> body = MatchTensorizeBody(self, stage, out_dom, in_region, intrin);
+  Map<Var, Range> compute_intrin_iter_space;
+  Array<Expr> body = MatchTensorizeBody(self, stage, out_dom, in_region, intrin,
+                                        &compute_intrin_iter_space);
   const ComputeOpNode* intrin_compute = intrin->op.as<ComputeOpNode>();
   CHECK(intrin_compute) << "Only support compute intrinsic for now";
   CHECK_EQ(body.size(), intrin_compute->body.size())
       << "Tensorize failed: body size mismatch";
   for (size_t i = 0; i < body.size(); ++i) {
-    Expr lhs = CanonicalSimplify(body[i]);
-    Expr rhs = CanonicalSimplify(intrin_compute->body[i]);
+    Expr lhs = CanonicalSimplify(body[i], compute_intrin_iter_space);
+    Expr rhs = CanonicalSimplify(intrin_compute->body[i], compute_intrin_iter_space);
     if (lhs.type() != rhs.type()) {
       LOG(FATAL)
           << "Failed to match the data type with TensorIntrin "
@@ -459,11 +465,13 @@ TVM_REGISTER_API("test.op.MatchTensorizeBody")
     Map<IterVar, Range> out_dom = args[1];
     Map<Tensor, Array<Range> > in_region = args[2];
     TensorIntrin intrin = args[3];
+    Map<Var, Range> vrange;
     CHECK(stage->op.as<ComputeOpNode>());
     *ret = MatchTensorizeBody(stage->op.as<ComputeOpNode>(),
                               stage,
                               as_unordered_map(out_dom),
                               as_unordered_map(in_region),
-                              intrin);
+                              intrin,
+                              &vrange);
   });
 }  // namespace tvm

From 71a27331c9df8ae0373af485cdd7fbeca5254974 Mon Sep 17 00:00:00 2001
From: ziheng <ziheng@apache.org>
Date: Fri, 1 Dec 2017 12:38:33 -0800
Subject: [PATCH 037/949] [RANDOM] Init contrib.random Library (#684)

* [RANDOM] Init contrib.random library

* [RANDOM] Add uniform

* [RANDOM] Fix lint

* [RANDOM] Add comments and tests

* [RANDOM] Fix lint
---
 Makefile                            |   1 +
 make/config.mk                      |   3 +
 make/contrib/random.mk              |   6 ++
 python/tvm/api.py                   |   2 +
 python/tvm/contrib/random.py        |  58 +++++++++++
 src/contrib/random/random.cc        | 149 ++++++++++++++++++++++++++++
 tests/python/contrib/test_random.py |  55 ++++++++++
 7 files changed, 274 insertions(+)
 create mode 100644 make/contrib/random.mk
 create mode 100644 python/tvm/contrib/random.py
 create mode 100644 src/contrib/random/random.cc
 create mode 100644 tests/python/contrib/test_random.py

diff --git a/Makefile b/Makefile
index 75c5a563408e..31c8dac658fc 100644
--- a/Makefile
+++ b/Makefile
@@ -131,6 +131,7 @@ ifeq ($(USE_GRAPH_RUNTIME), 1)
 endif
 
 include make/contrib/cblas.mk
+include make/contrib/random.mk
 include make/contrib/nnpack.mk
 include make/contrib/cudnn.mk
 include make/contrib/mps.mk
diff --git a/make/config.mk b/make/config.mk
index 8d8082c68142..94153edc38f6 100644
--- a/make/config.mk
+++ b/make/config.mk
@@ -62,6 +62,9 @@ USE_GRAPH_RUNTIME = 1
 # Whether use BLAS, choices: openblas, atlas, blas, apple
 USE_BLAS = none
 
+# Whether use contrib.random in runtime
+USE_RANDOM = 0
+
 # Whether use NNPack
 USE_NNPACK = 0
 # NNPACK_PATH = none
diff --git a/make/contrib/random.mk b/make/contrib/random.mk
new file mode 100644
index 000000000000..aea6770101d4
--- /dev/null
+++ b/make/contrib/random.mk
@@ -0,0 +1,6 @@
+RANDOM_CONTRIB_SRC = $(wildcard src/contrib/random/*.cc)
+RANDOM_CONTRIB_OBJ = $(patsubst src/%.cc, build/%.o, $(RANDOM_CONTRIB_SRC))
+
+ifeq ($(USE_RANDOM), 1)
+	RUNTIME_DEP += $(RANDOM_CONTRIB_OBJ)
+endif
diff --git a/python/tvm/api.py b/python/tvm/api.py
index 903e40308ecb..dfe6e4cf7d28 100644
--- a/python/tvm/api.py
+++ b/python/tvm/api.py
@@ -371,6 +371,8 @@ def extern(shape, inputs, fcompute, name="extern", dtype=None, tag=""):
             raise ValueError("Cannot infer output type, please provide dtype argument")
         infered_type = types.pop()
         dtype = [infered_type for _ in shape]
+    if isinstance(dtype, str):
+        dtype = [dtype]
 
     for shp, dt in zip(shape, dtype):
         output_placeholders.append(decl_buffer(shp, dt, name))
diff --git a/python/tvm/contrib/random.py b/python/tvm/contrib/random.py
new file mode 100644
index 000000000000..04b018b0646f
--- /dev/null
+++ b/python/tvm/contrib/random.py
@@ -0,0 +1,58 @@
+"""External function interface to random library."""
+from __future__ import absolute_import as _abs
+
+from .. import api as _api
+from .. import intrin as _intrin
+from .._ffi.function import _init_api
+
+
+def randint(low, high, size, dtype='int32'):
+    """Return random integers from low (inclusive) to high (exclusive).
+    Return random integers from the "discrete uniform" distribution of the
+    specified dtype in the "half-open" interval [low, high).
+
+    Parameters
+    ----------
+    low : int
+        Lowest (signed) integer to be drawn from the distribution
+    high : int
+        One above the largest (signed) integer to be drawn from the distribution
+
+    Returns
+    -------
+    out : Tensor
+        A tensor with specified size and dtype
+    """
+    assert 'int' in dtype, "the type of randint output must be int or uint"
+    return _api.extern(size, [], lambda ins, outs: _intrin.call_packed(
+        "tvm.contrib.random.randint", int(low), int(high), outs[0]), dtype=dtype)
+
+
+def uniform(low, high, size):
+    """Draw samples from a uniform distribution.
+
+    Samples are uniformly distributed over the half-open interval [low, high)
+    (includes low, but excludes high). In other words, any value within the
+    given interval is equally likely to be drawn by uniform.
+
+    Parameters
+    ----------
+    low : float
+        Lower boundary of the output interval. All values generated will be
+        greater than or equal to low.
+    high : float
+        Upper boundary of the output interval. All values generated will be
+        less than high.
+    size : tuple of ints
+        Output shape. If the given shape is, e.g., (m, n, k), then m * n * k
+        samples are drawn.
+
+    Returns
+    -------
+    out : Tensor
+        A tensor with specified size and dtype.
+    """
+    return _api.extern(size, [], lambda ins, outs: _intrin.call_packed(
+        "tvm.contrib.random.uniform", float(low), float(high), outs[0]), dtype='float32')
+
+_init_api("tvm.contrib.random")
diff --git a/src/contrib/random/random.cc b/src/contrib/random/random.cc
new file mode 100644
index 000000000000..d0bcb18cb76b
--- /dev/null
+++ b/src/contrib/random/random.cc
@@ -0,0 +1,149 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file External random functions for tensor.
+ */
+#include <tvm/runtime/registry.h>
+#include <tvm/runtime/util.h>
+#include <dmlc/logging.h>
+#include <dmlc/thread_local.h>
+#include <algorithm>
+#include <random>
+#include <ctime>
+
+#define DLPACK_INTEGER_TYPE_SWITCH(type, DType, ...)    \
+  if (type.code == kDLInt && type.bits == 32) {         \
+    typedef int32_t DType;                              \
+    {__VA_ARGS__}                                       \
+  } else if (type.code == kDLInt && type.bits == 16) {  \
+    typedef int16_t DType;                              \
+    {__VA_ARGS__}                                       \
+  } else if (type.code == kDLInt && type.bits == 8) {   \
+    typedef int8_t DType;                               \
+    {__VA_ARGS__}                                       \
+  } else if (type.code == kDLUInt && type.bits == 32) { \
+    typedef uint32_t DType;                             \
+    {__VA_ARGS__}                                       \
+  } else if (type.code == kDLUInt && type.bits == 16) { \
+    typedef uint16_t DType;                             \
+    {__VA_ARGS__}                                       \
+  } else if (type.code == kDLUInt && type.bits == 8) {  \
+    typedef uint8_t DType;                              \
+    {__VA_ARGS__}                                       \
+  } else {                                              \
+    LOG(FATAL) << "unknown data type";                  \
+  }
+
+namespace tvm {
+namespace contrib {
+
+using namespace runtime;
+
+class RandomEngine {
+ public:
+  RandomEngine() {
+    this->Seed(time(0));
+  }
+  explicit RandomEngine(int seed) {
+    this->Seed(seed);
+  }
+
+  ~RandomEngine() {}
+
+  inline void Seed(int seed) {
+    rnd_engine_.seed(seed);
+    this->rseed_ = static_cast<unsigned>(seed);
+  }
+
+  inline unsigned GetSeed() const {
+    return rseed_;
+  }
+
+  inline unsigned GetRandInt() {
+    return rnd_engine_();
+  }
+
+  void SampleUniform(DLTensor* data, float low, float high) {
+    CHECK_GT(high, low) << "high must be bigger than low";
+    CHECK(data->strides == nullptr);
+
+    DLDataType dtype = data->dtype;
+    int64_t size = 1;
+    for (int i = 0; i < data->ndim; ++i) {
+      size *= data->shape[i];
+    }
+
+    CHECK(dtype.code == kDLFloat && dtype.bits == 32 && dtype.lanes == 1);
+
+    if (data->ctx.device_type == kDLCPU) {
+      std::uniform_real_distribution<float> uniform_dist(low, high);
+      std::generate_n(static_cast<float*>(data->data), size, [&] () {
+        return uniform_dist(rnd_engine_);
+      });
+    } else {
+      LOG(FATAL) << "Do not support random.randint on this device yet";
+    }
+  }
+
+ private:
+  std::mt19937 rnd_engine_;
+  unsigned rseed_;
+};
+
+struct RandomThreadLocalEntry {
+  RandomEngine random_engine;
+  static RandomThreadLocalEntry* ThreadLocal();
+};
+
+typedef dmlc::ThreadLocalStore<RandomThreadLocalEntry> RandomThreadLocalStore;
+
+RandomThreadLocalEntry* RandomThreadLocalEntry::ThreadLocal() {
+  return RandomThreadLocalStore::Get();
+}
+
+
+TVM_REGISTER_GLOBAL("tvm.contrib.random.randint")
+.set_body([](TVMArgs args, TVMRetValue *ret) {
+    RandomThreadLocalEntry *entry = RandomThreadLocalEntry::ThreadLocal();
+    int64_t low = args[0];
+    int64_t high = args[1];
+    DLTensor* out = args[2];
+    CHECK_GT(high, low) << "high must be bigger than low";
+    CHECK(out->strides == nullptr);
+
+    DLDataType dtype = out->dtype;
+    int64_t size = 1;
+    for (int i = 0; i < out->ndim; ++i) {
+      size *= out->shape[i];
+    }
+
+    DLPACK_INTEGER_TYPE_SWITCH(dtype, DType, {
+      int64_t numeric_low = std::numeric_limits<DType>::min();
+      int64_t numeric_high = std::numeric_limits<DType>::max();
+      numeric_high += 1;  // exclusive upper bound
+      low = std::max(low, numeric_low);
+      high = std::min(high, numeric_high);
+
+      if (out->ctx.device_type == kDLCPU) {
+          // file the data with random byte
+          std::generate_n(static_cast<DType*>(out->data), size, [&] () {
+            unsigned rint = entry->random_engine.GetRandInt();
+            return low + rint % (high - low);
+          });
+      } else {
+        LOG(FATAL) << "Do not support random.randint on this device yet";
+      }
+    })
+  });
+
+TVM_REGISTER_GLOBAL("tvm.contrib.random.uniform")
+.set_body([](TVMArgs args, TVMRetValue *ret) {
+    RandomThreadLocalEntry *entry = RandomThreadLocalEntry::ThreadLocal();
+    double low = args[0];
+    double high = args[1];
+    DLTensor* out = args[2];
+    entry->random_engine.SampleUniform(out, low, high);
+  });
+
+
+}  // namespace contrib
+}  // namespace tvm
diff --git a/tests/python/contrib/test_random.py b/tests/python/contrib/test_random.py
new file mode 100644
index 000000000000..46f2bbbfb805
--- /dev/null
+++ b/tests/python/contrib/test_random.py
@@ -0,0 +1,55 @@
+import tvm
+import numpy as np
+from tvm.contrib import random
+
+def test_randint():
+    m = 1024
+    n = 1024
+    A = random.randint(-127, 128, size=(m, n), dtype='int32')
+    s = tvm.create_schedule(A.op)
+
+    def verify(target="llvm"):
+        if not tvm.module.enabled(target):
+            print("skip because %s is not enabled..." % target)
+            return
+        if not tvm.get_global_func("tvm.contrib.random.randint", True):
+            print("skip because extern function is not avalable")
+            return
+        ctx = tvm.cpu(0)
+        f = tvm.build(s, [A], target)
+        a = tvm.nd.array(np.zeros((m, n), dtype=A.dtype), ctx)
+        f(a)
+        na = a.asnumpy()
+        assert abs(np.mean(na)) < 0.2
+        assert np.min(na) == -127
+        assert np.max(na) == 127
+    verify()
+
+
+def test_uniform():
+    m = 1024
+    n = 1024
+    A = random.uniform(0, 1, size=(m, n))
+    s = tvm.create_schedule(A.op)
+
+    def verify(target="llvm"):
+        if not tvm.module.enabled(target):
+            print("skip because %s is not enabled..." % target)
+            return
+        if not tvm.get_global_func("tvm.contrib.random.uniform", True):
+            print("skip because extern function is not avalable")
+            return
+        ctx = tvm.cpu(0)
+        f = tvm.build(s, [A], target)
+        a = tvm.nd.array(np.zeros((m, n), dtype=A.dtype), ctx)
+        f(a)
+        na = a.asnumpy()
+        assert abs(np.mean(na) - 0.5) < 1e-2
+        assert abs(np.min(na) - 0.0) < 1e-3
+        assert abs(np.max(na) - 1.0) < 1e-3
+    verify()
+
+
+if __name__ == "__main__":
+    test_randint()
+    test_uniform()

From 8a3dbd7971a3d7df7c3a728d18c7c6124bf68989 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 3 Dec 2017 22:38:28 -0800
Subject: [PATCH 038/949] Support rank-0 tensor (#687)

* Support rank-0 tensor

* fix lint
---
 include/tvm/buffer.h                          |  5 +++
 include/tvm/packed_func_ext.h                 |  4 ++
 include/tvm/tensor.h                          |  2 +-
 python/tvm/_ffi/ndarray.py                    |  7 ++--
 python/tvm/api.py                             |  1 -
 python/tvm/tensor.py                          |  8 +++-
 src/arithmetic/compute_expr.h                 | 12 ++++--
 src/lang/buffer.cc                            | 37 ++++++++++---------
 src/pass/arg_binder.cc                        | 20 +++++-----
 src/pass/inject_double_buffer.cc              |  3 +-
 src/pass/inject_virtual_thread.cc             |  3 +-
 src/pass/storage_flatten.cc                   | 15 ++++++--
 src/pass/storage_rewrite.cc                   |  2 +-
 src/runtime/c_runtime_api.cc                  | 10 +++--
 src/runtime/graph/graph_runtime.cc            |  6 ++-
 src/schedule/schedule_dataflow_rewrite.cc     |  6 +--
 tests/python/unittest/test_codegen_device.py  |  9 +++--
 tests/python/unittest/test_codegen_llvm.py    | 25 +++++++++++++
 tests/python/unittest/test_lang_tensor.py     | 13 +++++++
 .../unittest/test_runtime_packed_func.py      |  8 ++++
 topi/python/topi/nn/dense.py                  |  4 +-
 21 files changed, 143 insertions(+), 57 deletions(-)

diff --git a/include/tvm/buffer.h b/include/tvm/buffer.h
index ad4872b8e4e0..f2790f6df7d1 100644
--- a/include/tvm/buffer.h
+++ b/include/tvm/buffer.h
@@ -124,6 +124,11 @@ class BufferNode : public Node {
     v->Visit("offset_factor", &offset_factor);
   }
 
+  /*! \return preferred index type for this buffer node */
+  Type DefaultIndexType() const {
+    return shape.size() != 0 ? shape[0].type() : Int(32);
+  }
+
   // User can specify data_alignment and offset_factor to be 0
   // A default value will be picked.
   TVM_DLL static Buffer make(Var ptr,
diff --git a/include/tvm/packed_func_ext.h b/include/tvm/packed_func_ext.h
index 5242a057659b..542de6a368b5 100644
--- a/include/tvm/packed_func_ext.h
+++ b/include/tvm/packed_func_ext.h
@@ -14,6 +14,7 @@
 
 #include "./base.h"
 #include "./expr.h"
+#include "./tensor.h"
 #include "./runtime/packed_func.h"
 
 namespace tvm {
@@ -116,6 +117,9 @@ inline TVMArgValue::operator Halide::Expr() const {
   if (sptr->is_type<IterVarNode>()) {
     return IterVar(sptr)->var;
   }
+  if (sptr->is_type<TensorNode>()) {
+    return Tensor(sptr)();
+  }
   CHECK(NodeTypeChecker<Expr>::Check(sptr.get()))
       << "Expected type " << NodeTypeName<Expr>()
       << " but get " << sptr->type_key();
diff --git a/include/tvm/tensor.h b/include/tvm/tensor.h
index a6613a4dc424..4f46d86e93a5 100644
--- a/include/tvm/tensor.h
+++ b/include/tvm/tensor.h
@@ -188,7 +188,7 @@ inline bool Tensor::operator==(const Tensor& other) const {
 #define DEFINE_OVERLOAD_SLICE_UNARY_OP(Op)                              \
   inline Expr operator Op (const Tensor::Slice& a) {                    \
     return Op a.operator Expr() ;                                       \
-  }
+  }                                                                     \
 
 #define DEFINE_OVERLOAD_SLICE_BINARY_OP(Op)                             \
   template<typename T>                                                  \
diff --git a/python/tvm/_ffi/ndarray.py b/python/tvm/_ffi/ndarray.py
index b0dfd0f73fd9..135701a803c0 100644
--- a/python/tvm/_ffi/ndarray.py
+++ b/python/tvm/_ffi/ndarray.py
@@ -177,13 +177,14 @@ def copyfrom(self, source_array):
             shape = shape + (t.lanes,)
             t.lanes = 1
             dtype = str(t)
-        source_array = np.ascontiguousarray(source_array, dtype=dtype)
+
         if source_array.shape != shape:
             raise ValueError("array shape do not match the shape of NDArray {0} vs {1}".format(
                 source_array.shape, shape))
+        source_array = np.ascontiguousarray(source_array, dtype=dtype)
         assert source_array.flags['C_CONTIGUOUS']
         data = source_array.ctypes.data_as(ctypes.c_void_p)
-        nbytes = ctypes.c_size_t(np.prod(source_array.shape) * source_array.dtype.itemsize)
+        nbytes = ctypes.c_size_t(source_array.size * source_array.dtype.itemsize)
         check_call(_LIB.TVMArrayCopyFromBytes(self.handle, data, nbytes))
         return self
 
@@ -212,7 +213,7 @@ def asnumpy(self):
         np_arr = np.empty(shape, dtype=dtype)
         assert np_arr.flags['C_CONTIGUOUS']
         data = np_arr.ctypes.data_as(ctypes.c_void_p)
-        nbytes = ctypes.c_size_t(np.prod(np_arr.shape) * np_arr.dtype.itemsize)
+        nbytes = ctypes.c_size_t(np_arr.size * np_arr.dtype.itemsize)
         check_call(_LIB.TVMArrayCopyToBytes(self.handle, data, nbytes))
         return np_arr
 
diff --git a/python/tvm/api.py b/python/tvm/api.py
index dfe6e4cf7d28..08b3d95dca27 100644
--- a/python/tvm/api.py
+++ b/python/tvm/api.py
@@ -462,7 +462,6 @@ def decl_buffer(shape,
         elem_offset = var('%s_elem_offset' % name, shape[0].dtype)
     if data is None:
         data = var(name, "handle")
-
     return _api_internal._Buffer(
         data, dtype, shape, strides, elem_offset, name, scope,
         data_alignment, offset_factor)
diff --git a/python/tvm/tensor.py b/python/tvm/tensor.py
index 98a142e8c128..f169ff1b64ac 100644
--- a/python/tvm/tensor.py
+++ b/python/tvm/tensor.py
@@ -32,7 +32,7 @@ def dtype(self):
 itervar_cls = None
 
 @register_node
-class Tensor(NodeBase):
+class Tensor(NodeBase, _expr.ExprOp):
     """Tensor object, to construct, see function.Tensor"""
     def __call__(self, *indices):
         ndim = self.ndim
@@ -60,7 +60,13 @@ def __hash__(self):
 
     def __eq__(self, other):
         if not isinstance(other, Tensor):
+            if isinstance(other, _expr.ExprOp):
+                return _expr.EqualOp(self, other)
             return False
+        if self.ndim == 0 and other.ndim == 0:
+            raise ValueError("Equal == comparison among rank-0 tensor is ambiguous, "
+                             "use Tensor.equal for content expression equvalence, "
+                             "use Tensor.same_as for exact reference comparison")
         return _api_internal._TensorEqual(self, other)
 
     @property
diff --git a/src/arithmetic/compute_expr.h b/src/arithmetic/compute_expr.h
index 18ae8530fbe8..994bcb13eadc 100644
--- a/src/arithmetic/compute_expr.h
+++ b/src/arithmetic/compute_expr.h
@@ -33,11 +33,14 @@ inline Expr ComputeExpr(Expr lhs, Expr rhs) {
 /*!
  * \brief Compute an reduction with Op
  * \param values The input values.
+ * \param empty_value The value when return if it is empty, can be Expr()
+ *        which will cause an error to be rasied.
  * \tparam Op The computation operator
  * \return The result.
  */
 template<typename Op>
-inline Expr ComputeReduce(const Array<Expr>& values);
+inline Expr ComputeReduce(
+    const Array<Expr>& values, Expr empty_value);
 
 template<typename T>
 inline bool GetConst(Expr e, T* out);
@@ -139,8 +142,11 @@ inline Expr ComputeExpr<ir::Min>(Expr a, Expr b) {
 }
 
 template<typename Op>
-inline Expr ComputeReduce(const Array<Expr>& values) {
-  CHECK_NE(values.size(), 0U);
+inline Expr ComputeReduce(const Array<Expr>& values, Expr empty_value) {
+  if (values.size() == 0U) {
+    CHECK(empty_value.defined());
+    return empty_value;
+  }
   Expr res = values[0];
   for (size_t i = 1; i < values.size(); ++i) {
     res = ComputeExpr<Op>(res, values[i]);
diff --git a/src/lang/buffer.cc b/src/lang/buffer.cc
index 5cf7ddef3018..af76dcc94f71 100644
--- a/src/lang/buffer.cc
+++ b/src/lang/buffer.cc
@@ -11,15 +11,6 @@
 
 namespace tvm {
 
-Array<Expr> GetStrides(Array<Expr> shape) {
-  CHECK_NE(shape.size(), 0U);
-  std::vector<Expr> vec{make_const(shape[0].type(), 1)};
-  for (size_t i = shape.size() - 1; i != 0; --i) {
-    vec.push_back(shape[i - 1] * vec.back());
-  }
-  return Array<Expr>(vec.rbegin(), vec.rend());
-}
-
 Array<Expr> SimplifyArray(Array<Expr> array) {
   for (size_t i = 0; i < array.size(); ++i) {
     array.Set(i, ir::Simplify(array[i]));
@@ -235,10 +226,12 @@ inline Expr ElemOffset(const BufferNode* n, Array<Expr> index) {
   Expr base = n->elem_offset;
   if (n->strides.size() == 0) {
     CHECK_EQ(n->shape.size(), index.size());
-    if (is_zero(base)) {
-      base = index[0];
-    } else {
-      base = base + index[0];
+    if (n->shape.size() != 0) {
+      if (is_zero(base)) {
+        base = index[0];
+      } else {
+        base = base + index[0];
+      }
     }
     base = MergeMulMod(base);
     for (size_t i = 1; i < index.size(); ++i) {
@@ -294,9 +287,10 @@ Stmt Buffer::vstore(Array<Expr> begin, Expr value) const {
 
 Buffer Buffer::MakeStrideView() const {
   if ((*this)->strides.size() != 0) return *this;
+  if ((*this)->shape.size() == 0) return *this;
   std::vector<Expr> temp;
   auto n = std::make_shared<BufferNode>(*operator->());
-  Expr acc = make_const(n->shape[0].type(), 1);
+  Expr acc = make_const(n->DefaultIndexType(), 1);
   for (size_t i = n->shape.size(); i != 0 ; --i) {
     temp.push_back(acc);
     acc = acc * n->shape[i - 1];
@@ -344,9 +338,16 @@ Buffer Buffer::MakeSlice(Array<Expr> begins, Array<Expr> extents) const {
 Expr Buffer::access_ptr(int access_mask, Type ptr_type, int content_lanes) const {
   const BufferNode* self = operator->();
   Expr e_dtype;
-  Expr extent = (self->strides.size() == self->shape.size() ?
-                 arith::ComputeExpr<ir::Mul>(self->strides[0], self->shape[0]):
-                 arith::ComputeReduce<ir::Mul>(self->shape));
+  Expr extent;
+  if (self->shape.size() == 0) {
+    extent = make_const(self->DefaultIndexType(), 1);
+  } else if (self->strides.size() == self->shape.size()) {
+    int highest_dim = 0;
+    extent = arith::ComputeExpr<ir::Mul>(
+        self->strides[highest_dim], self->shape[highest_dim]);
+  } else {
+    extent = arith::ComputeReduce<ir::Mul>(self->shape, Expr());
+  }
   Expr elem_offset = self->elem_offset;
   if (content_lanes > 1) {
     e_dtype = make_zero(self->dtype.with_lanes(content_lanes));
@@ -383,7 +384,7 @@ Buffer BufferNode::make(Var data,
   }
   n->scope = std::move(scope);
   if (!elem_offset.defined()) {
-    elem_offset = make_const(n->shape[0].type(), 0);
+    elem_offset = make_const(n->DefaultIndexType(), 0);
   }
   if (data_alignment <= 0) {
     data_alignment = runtime::kAllocAlignment;
diff --git a/src/pass/arg_binder.cc b/src/pass/arg_binder.cc
index 20c8593a1494..cdd344670725 100644
--- a/src/pass/arg_binder.cc
+++ b/src/pass/arg_binder.cc
@@ -196,7 +196,7 @@ void ArgBinder::BindDLTensor(const Buffer& buffer,
       nop));
   if (buffer->strides.size() == 0) {
     // Assert the buffer is compact
-    Type stype = buffer->shape[0].type();
+    Type stype = buffer->DefaultIndexType();
     Expr expect_stride = make_const(stype, 1);
     Array<Expr> conds;
     for (size_t i = buffer->shape.size(); i != 0; --i) {
@@ -211,14 +211,16 @@ void ArgBinder::BindDLTensor(const Buffer& buffer,
     std::ostringstream stride_err_msg;
     stride_err_msg << arg_name << ".strides:"
                    << " expected to be compact array";
-    Stmt check =
-        AssertStmt::make(arith::ComputeReduce<ir::And>(conds),
-                         stride_err_msg.str(), Evaluate::make(0));
-    Expr is_null = Call::make(
-        Bool(1), intrinsic::tvm_handle_is_null,
-        {v_strides}, Call::PureIntrinsic);
-    check = IfThenElse::make(Not::make(is_null), check, Stmt());
-    init_nest_.emplace_back(Block::make(check, Evaluate::make(0)));
+    if (conds.size() != 0) {
+      Stmt check =
+          AssertStmt::make(arith::ComputeReduce<ir::And>(conds, Expr()),
+                           stride_err_msg.str(), Evaluate::make(0));
+      Expr is_null = Call::make(
+          Bool(1), intrinsic::tvm_handle_is_null,
+          {v_strides}, Call::PureIntrinsic);
+      check = IfThenElse::make(Not::make(is_null), check, Stmt());
+      init_nest_.emplace_back(Block::make(check, Evaluate::make(0)));
+    }
   } else {
     for (size_t k = 0; k < buffer->strides.size(); ++k) {
       std::ostringstream field_name;
diff --git a/src/pass/inject_double_buffer.cc b/src/pass/inject_double_buffer.cc
index e9bd8594ab4d..03ffdb01e107 100644
--- a/src/pass/inject_double_buffer.cc
+++ b/src/pass/inject_double_buffer.cc
@@ -81,7 +81,8 @@ class DoubleBufferInjector : public IRMutator {
   Stmt Mutate_(const Allocate* op, const Stmt& s) final {
     auto it = dbuffer_info_.find(op->buffer_var.get());
     if (it != dbuffer_info_.end()) {
-      it->second.stride = arith::ComputeReduce<Mul>(op->extents) * op->type.lanes();
+      it->second.stride = arith::ComputeReduce<Mul>
+          (op->extents, Expr()) * op->type.lanes();
       Stmt stmt = IRMutator::Mutate_(op, s);
       op = stmt.as<Allocate>();
       Array<Expr> new_extents{make_const(op->extents[0].type(), 2)};
diff --git a/src/pass/inject_virtual_thread.cc b/src/pass/inject_virtual_thread.cc
index 28e90ec4805b..bcf0e3d9fbaf 100644
--- a/src/pass/inject_virtual_thread.cc
+++ b/src/pass/inject_virtual_thread.cc
@@ -376,7 +376,8 @@ class VTInjector : public IRMutator {
     // always rewrite if not allow sharing.
     if (touched_var_.count(op->buffer_var.get()) || !allow_share_) {
       // place v on highest dimension.
-      Expr stride = arith::ComputeReduce<Mul>(op->extents) * op->type.lanes();
+      Expr stride = arith::ComputeReduce<Mul>(
+          op->extents, Expr()) * op->type.lanes();
       Array<Expr> other;
       other.push_back(make_const(op->extents[0].type(), num_threads_));
       for (Expr e : extents) {
diff --git a/src/pass/storage_flatten.cc b/src/pass/storage_flatten.cc
index f1aee504fb3e..46bed1fc9382 100644
--- a/src/pass/storage_flatten.cc
+++ b/src/pass/storage_flatten.cc
@@ -147,10 +147,11 @@ class StorageFlattener : public IRMutator {
         }
       }
       Array<Expr> strides;
-      if (dim_align_.count(key) != 0) {
+      if (dim_align_.count(key) != 0 && shape.size() != 0) {
         std::vector<Expr> rstrides;
         const std::vector<DimAlignInfo>& avec = dim_align_[key];
-        Expr stride = make_const(shape[0].type(), 1);
+        int first_dim = 0;
+        Expr stride = make_const(shape[first_dim].type(), 1);
         for (size_t i = shape.size(); i != 0; --i) {
           size_t dim = i - 1;
           if (dim < avec.size() && avec[dim].align_factor != 0) {
@@ -164,6 +165,7 @@ class StorageFlattener : public IRMutator {
         }
         strides = Array<Expr>(rstrides.rbegin(), rstrides.rend());
       }
+
       e.buffer = BufferNode::make(
           Var(key.GetName(), Handle()),
           op->type, shape, strides, Expr(),
@@ -176,13 +178,18 @@ class StorageFlattener : public IRMutator {
       Stmt ret;
 
       if (strides.size() != 0) {
+        int first_dim = 0;
         ret = Allocate::make(
             e.buffer->data, e.buffer->dtype,
-            {arith::ComputeExpr<Mul>(e.buffer->strides[0], e.buffer->shape[0])},
+            {arith::ComputeExpr<Mul>(e.buffer->strides[first_dim], e.buffer->shape[first_dim])},
             make_const(Bool(e.buffer->dtype.lanes()), true), body);
       } else {
+        shape = e.buffer->shape;
+        if (shape.size() == 0) {
+          shape.push_back(make_const(Int(32), 1));
+        }
         ret = Allocate::make(
-            e.buffer->data, e.buffer->dtype, e.buffer->shape,
+            e.buffer->data, e.buffer->dtype, shape,
             make_const(Bool(e.buffer->dtype.lanes()), true), body);
       }
       ret = AttrStmt::make(
diff --git a/src/pass/storage_rewrite.cc b/src/pass/storage_rewrite.cc
index 2f3616017215..9d47a64f8837 100644
--- a/src/pass/storage_rewrite.cc
+++ b/src/pass/storage_rewrite.cc
@@ -405,7 +405,7 @@ class StoragePlanRewriter : public IRMutator {
           // Build a merged allocation
           Expr combo_size;
           for (const Allocate* op : e->allocs) {
-            Expr sz = arith::ComputeReduce<Mul>(op->extents);
+            Expr sz = arith::ComputeReduce<Mul>(op->extents, make_const(Int(32), 1));
             if (alloc_type.lanes() != op->type.lanes()) {
               sz = (sz * make_const(sz.type(), op->type.lanes()) +
                     make_const(sz.type(), alloc_type.lanes() - 1)) /
diff --git a/src/runtime/c_runtime_api.cc b/src/runtime/c_runtime_api.cc
index f036dccc381a..dd8f80bcd72f 100644
--- a/src/runtime/c_runtime_api.cc
+++ b/src/runtime/c_runtime_api.cc
@@ -352,9 +352,13 @@ int TVMArrayAlloc(const tvm_index_t* shape,
   arr->dtype.code = static_cast<uint8_t>(dtype_code);
   arr->dtype.bits = static_cast<uint8_t>(dtype_bits);
   arr->dtype.lanes = static_cast<uint16_t>(dtype_lanes);
-  tvm_index_t* shape_copy = new tvm_index_t[ndim];
-  std::copy(shape, shape + ndim, shape_copy);
-  arr->shape = shape_copy;
+  if (ndim != 0) {
+    tvm_index_t* shape_copy = new tvm_index_t[ndim];
+    std::copy(shape, shape + ndim, shape_copy);
+    arr->shape = shape_copy;
+  } else {
+    arr->shape = nullptr;
+  }
   // ctx
   arr->ctx.device_type = static_cast<DLDeviceType>(device_type);
   arr->ctx.device_id = device_id;
diff --git a/src/runtime/graph/graph_runtime.cc b/src/runtime/graph/graph_runtime.cc
index d3f849d743dc..ed833d40848c 100644
--- a/src/runtime/graph/graph_runtime.cc
+++ b/src/runtime/graph/graph_runtime.cc
@@ -370,8 +370,10 @@ void GraphRuntime::LoadDLTensor(dmlc::Stream* strm, DLTensor* dst) {
   CHECK(strm->Read(&tensor.dtype, sizeof(tensor.dtype)))
       << "Invalid DLTensor file format";
   std::vector<int64_t> shape(tensor.ndim);
-  CHECK(strm->Read(&shape[0], sizeof(int64_t) * tensor.ndim))
-      << "Invalid DLTensor file format";
+  if (tensor.ndim != 0) {
+    CHECK(strm->Read(&shape[0], sizeof(int64_t) * tensor.ndim))
+        << "Invalid DLTensor file format";
+  }
   CHECK_EQ(tensor.ndim, dst->ndim) << "param dimension mismatch";
   CHECK(tensor.dtype.bits == dst->dtype.bits &&
         tensor.dtype.code == dst->dtype.code &&
diff --git a/src/schedule/schedule_dataflow_rewrite.cc b/src/schedule/schedule_dataflow_rewrite.cc
index a8dc4edf57f1..d1a69ecf0203 100644
--- a/src/schedule/schedule_dataflow_rewrite.cc
+++ b/src/schedule/schedule_dataflow_rewrite.cc
@@ -47,10 +47,10 @@ Expr InjectPredicate(const Array<Expr>& predicates,
   const Reduce* reduce = body.as<Reduce>();
   if (reduce) {
     std::shared_ptr<Reduce> n = std::make_shared<Reduce>(*reduce);
-    n->condition = n->condition && arith::ComputeReduce<ir::And>(predicates);
+    n->condition = n->condition && arith::ComputeReduce<ir::And>(predicates, Expr());
     return Expr(n);
   }
-  return Select::make(arith::ComputeReduce<ir::And>(predicates),
+  return Select::make(arith::ComputeReduce<ir::And>(predicates, Expr()),
                       body,
                       make_zero(body.type()));
 }
@@ -467,7 +467,7 @@ Array<Tensor> Schedule::rfactor(const Tensor& tensor,
   const Reduce* reduce = compute_op->body[idx].as<Reduce>();
   CHECK(reduce) << "Can only rfactor non-inline reductions";
   predicates.push_back(reduce->condition);
-  Expr predicate = arith::ComputeReduce<ir::And>(predicates);
+  Expr predicate = arith::ComputeReduce<ir::And>(predicates, Expr());
 
   std::unordered_map<const Variable*, Expr> vsub;
 
diff --git a/tests/python/unittest/test_codegen_device.py b/tests/python/unittest/test_codegen_device.py
index bbdd65e4be1c..56e3fc81910f 100644
--- a/tests/python/unittest/test_codegen_device.py
+++ b/tests/python/unittest/test_codegen_device.py
@@ -5,8 +5,8 @@
 def test_add_pipeline():
     n = tvm.var('n')
     A = tvm.placeholder((n,), name='A')
-    B = tvm.placeholder((n,), name='B')
-    C = tvm.compute(A.shape, lambda *i: A(*i) + B(*i), name='C')
+    B = tvm.placeholder((), name='B')
+    C = tvm.compute(A.shape, lambda *i: A(*i) + B(), name='C')
     D = tvm.compute(A.shape, lambda *i: C(*i) + 1, name='D')
     s = tvm.create_schedule(D.op)
 
@@ -48,7 +48,7 @@ def check_target(device, host="stackvm"):
         # launch the kernel.
         n = 1027
         a = tvm.nd.array(np.random.uniform(size=n).astype(Ab.dtype), ctx)
-        b = tvm.nd.array(np.random.uniform(size=n).astype(Bb.dtype), ctx)
+        b = tvm.nd.array(np.random.uniform(size=()).astype(Bb.dtype), ctx)
         d = tvm.nd.array(np.zeros(n, dtype=Db.dtype), ctx)
         f(a, b, d)
         np.testing.assert_allclose(
@@ -72,7 +72,7 @@ def check_module_save(device, host="stackvm"):
         # launch the kernel.
         n = 1027
         a = tvm.nd.array(np.random.uniform(size=n).astype(Ab.dtype), ctx)
-        b = tvm.nd.array(np.random.uniform(size=n).astype(Bb.dtype), ctx)
+        b = tvm.nd.array(np.random.uniform(size=()).astype(Bb.dtype), ctx)
         d = tvm.nd.array(np.zeros(n, dtype=Db.dtype), ctx)
         f(a, b, d)
         np.testing.assert_allclose(
@@ -84,5 +84,6 @@ def check_module_save(device, host="stackvm"):
     check_target("nvptx", host="llvm")
     check_target("rocm", host="llvm")
 
+
 if __name__ == "__main__":
     test_add_pipeline()
diff --git a/tests/python/unittest/test_codegen_llvm.py b/tests/python/unittest/test_codegen_llvm.py
index 0db06b934f1b..24996c842249 100644
--- a/tests/python/unittest/test_codegen_llvm.py
+++ b/tests/python/unittest/test_codegen_llvm.py
@@ -273,7 +273,32 @@ def check_llvm(n):
     check_llvm(64)
 
 
+def test_rank_zero():
+    def check_llvm(n):
+        if not tvm.module.enabled("llvm"):
+            return
+        A = tvm.placeholder((n, ), name='A')
+        scale = tvm.placeholder((), name='scale')
+        k = tvm.reduce_axis((0, n), name="k")
+        C = tvm.compute((), lambda : tvm.sum(A[k] * scale, axis=k), name="C")
+        D = tvm.compute((), lambda : C + 1)
+        s = tvm.create_schedule(D.op)
+        # build and invoke the kernel.
+        f = tvm.build(s, [A, scale, D], "llvm")
+        ctx = tvm.cpu(0)
+        # launch the kernel.
+        a = tvm.nd.array(np.random.randint(0, 2, size=(n,)).astype(A.dtype), ctx)
+        sc = tvm.nd.array(
+            np.random.randint(0, 2, size=()).astype(scale.dtype), ctx)
+        d = tvm.nd.empty((), D.dtype, ctx)
+        f(a, sc, d)
+        d_np = np.sum(a.asnumpy()) * sc.asnumpy() + 1
+        np.testing.assert_allclose(d.asnumpy(), d_np)
+    check_llvm(64)
+
+
 if __name__ == "__main__":
+    test_rank_zero()
     test_llvm_bool()
     test_llvm_persist_parallel()
     test_llvm_select()
diff --git a/tests/python/unittest/test_lang_tensor.py b/tests/python/unittest/test_lang_tensor.py
index 6f151749c849..1d8603dfc98b 100644
--- a/tests/python/unittest/test_lang_tensor.py
+++ b/tests/python/unittest/test_lang_tensor.py
@@ -19,6 +19,17 @@ def test_tensor():
     assert(T[0][0][0].astype('float16').dtype == 'float16')
 
 
+def test_rank_zero():
+    m = tvm.var('m')
+    A = tvm.placeholder((m,), name='A')
+    scale = tvm.placeholder((), name='s')
+    k = tvm.reduce_axis((0, m), name="k")
+    T = tvm.compute((), lambda : tvm.sum(A[k] * scale(), axis=k))
+    print(T)
+    print(T.op.body)
+    assert(tuple(T.shape) == ())
+
+
 def test_conv1d():
     n = tvm.var('n')
     A = tvm.placeholder((n+2), name='A')
@@ -173,7 +184,9 @@ def test_tensor_inputs():
     y = tvm.compute(x.shape, lambda i: x[i] + x[i])
     assert tuple(y.op.input_tensors) == (x,)
 
+
 if __name__ == "__main__":
+    test_rank_zero()
     test_tensor_inputs()
     test_tensor_reduce_multi_axis()
     test_conv1d()
diff --git a/tests/python/unittest/test_runtime_packed_func.py b/tests/python/unittest/test_runtime_packed_func.py
index 44b450b23fe2..279172555d2a 100644
--- a/tests/python/unittest/test_runtime_packed_func.py
+++ b/tests/python/unittest/test_runtime_packed_func.py
@@ -63,7 +63,15 @@ def myfunc(ss):
     f(a)
 
 
+def test_empty_array():
+    def myfunc(ss):
+        assert tuple(ss) == ()
+    x = tvm.convert(())
+    tvm.convert(myfunc)(x)
+
+
 if __name__ == "__main__":
+    test_empty_array()
     test_get_global()
     test_get_callback_with_node()
     test_convert()
diff --git a/topi/python/topi/nn/dense.py b/topi/python/topi/nn/dense.py
index caa736a41416..333692614bd1 100644
--- a/topi/python/topi/nn/dense.py
+++ b/topi/python/topi/nn/dense.py
@@ -25,7 +25,7 @@ def dense(data, weight, bias=None):
     """
     assert len(data.shape) == 2 and len(weight.shape) == 2, \
         "only support 2-dim dense"
-    if bias:
+    if bias is not None:
         assert len(bias.shape) == 1
     batch, in_dim = data.shape
     out_dim, _ = weight.shape
@@ -33,7 +33,7 @@ def dense(data, weight, bias=None):
     matmul = tvm.compute((batch, out_dim), \
                          lambda i, j: tvm.sum(data[i, k] * weight[j, k], axis=k), \
                          tag='dense')
-    if bias:
+    if bias is not None:
         matmul = tvm.compute((batch, out_dim), \
                              lambda i, j: matmul[i, j] + bias[j], \
                              tag=tag.BROADCAST)

From 49ac8d8b1f5bf969d411d176c4f7033666bb446f Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 4 Dec 2017 13:33:18 -0800
Subject: [PATCH 039/949] [CI] Enable llvm in CPU test (#688)

* [CI] Enable llvm in CPU test

* fix llvm
---
 Jenkinsfile                   | 1 +
 tests/ci_build/Dockerfile.cpu | 3 +++
 2 files changed, 4 insertions(+)

diff --git a/Jenkinsfile b/Jenkinsfile
index ef9666351ba5..793bf11f0d4a 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -120,6 +120,7 @@ stage('Build') {
            echo USE_CUDA=0 >> config.mk
            echo USE_OPENCL=0 >> config.mk
            echo USE_RPC=0 >> config.mk
+           echo LLVM_CONFIG=llvm-config-4.0 >> config.mk
            """
         make('cpu', '-j2')
         pack_lib('cpu', tvm_lib)
diff --git a/tests/ci_build/Dockerfile.cpu b/tests/ci_build/Dockerfile.cpu
index b113fc548fcb..2c7510365f9d 100644
--- a/tests/ci_build/Dockerfile.cpu
+++ b/tests/ci_build/Dockerfile.cpu
@@ -17,3 +17,6 @@ RUN bash /install/ubuntu_install_python_package.sh
 
 COPY install/ubuntu_install_java.sh /install/ubuntu_install_java.sh
 RUN bash /install/ubuntu_install_java.sh
+
+COPY install/ubuntu_install_llvm.sh /install/ubuntu_install_llvm.sh
+RUN bash /install/ubuntu_install_llvm.sh

From 0ef29283e3f7f940dc2b1a5d9fe9411b4696c3cf Mon Sep 17 00:00:00 2001
From: alex-weaver <awsweaver@gmail.com>
Date: Tue, 5 Dec 2017 20:06:46 +0000
Subject: [PATCH 040/949] Port build_module.py to C++ (#667)

* Port build_module.py to C++

* Fix lint errors

* Fix more lint errors

* Fix more lint errors

* Fix more lint errors

* Fix build error

* Implemented style fixes

* Fix lint errors

* Added function to construct target from string
lower now returns array

* Fix lint error

* Implemented review changes - style & Target options -> std::vector

* Fixed lint, argument alignment and added unit test

* Changed test to target LLVM, fixed sign compare warnings

* Reverted unit test to CUDA, changed Jenkinsfile to enable GPU for C++ tests

* Slight change to Jenkinsfile

* Changed build_module test from CUDA to LLVM

* Added function var() to construct a Var instance.
Changed implementation of LLVMEnabled()

* Reverted Jenkinsfile
---
 include/tvm/build_module.h     | 153 ++++++++++++++++
 include/tvm/expr.h             |   7 +
 include/tvm/schedule.h         |  10 +-
 src/codegen/build_module.cc    | 314 +++++++++++++++++++++++++++++++++
 src/lang/expr.cc               |   4 +
 tests/cpp/build_module_test.cc |  42 +++++
 6 files changed, 525 insertions(+), 5 deletions(-)
 create mode 100644 include/tvm/build_module.h
 create mode 100644 src/codegen/build_module.cc
 create mode 100644 tests/cpp/build_module_test.cc

diff --git a/include/tvm/build_module.h b/include/tvm/build_module.h
new file mode 100644
index 000000000000..a1563e8e7447
--- /dev/null
+++ b/include/tvm/build_module.h
@@ -0,0 +1,153 @@
+/*!
+*  Copyright (c) 2017 by Contributors
+* \file build_module.h
+* \brief Functions for compiling ops.
+*/
+#ifndef TVM_BUILD_MODULE_H_
+#define TVM_BUILD_MODULE_H_
+
+#include <string>
+#include <vector>
+#include "./tvm/runtime/packed_func.h"
+#include "./tvm/schedule_pass.h"
+#include "./tvm/lowered_func.h"
+
+namespace tvm {
+
+/*!
+* \brief Container for target device information.
+* Use target::llvm, target::cuda etc functions instead of constructing directly.
+*/
+struct Target {
+  /*! \brief The name of the target device */
+  std::string target_name;
+  /*! \brief The type of the target device */
+  DLDeviceType device_type;
+  /*! \brief The maximum threads that a schedule should use for this device */
+  int max_num_threads = 1;
+  /*! \brief The warp size that should be used by the LowerThreadAllreduce pass */
+  int thread_warp_size = 1;
+  /*! \brief Keys for this target */
+  std::unordered_set<std::string> keys;
+  /*! \brief Options for this target */
+  std::vector<std::string> options;
+
+  Target(const std::string& target_name,
+         DLDeviceType device_type,
+         int max_num_threads,
+         int thread_warp_size,
+         const std::unordered_set<std::string>& keys,
+         const std::vector<std::string>& options) :
+    target_name(target_name),
+    device_type(device_type),
+    max_num_threads(max_num_threads),
+    thread_warp_size(thread_warp_size),
+    keys(keys),
+    options(options) {
+  }
+
+  /*! \return the full device string to pass to codegen::Build */
+  EXPORT std::string str() const;
+
+  /*!
+   * \brief Create a Target given a string
+   * \param target_str the string to parse
+   */
+  EXPORT static Target create(const std::string& target_str);
+};
+
+/*! \brief This namespace provides functions to construct Target instances */
+namespace target {
+/*! \return A target for LLVM */
+EXPORT Target llvm();
+
+/*! \return A target for CUDA */
+EXPORT Target cuda();
+
+/*! \return A target for ROCm */
+EXPORT Target rocm();
+
+/*! \return A target for Metal */
+EXPORT Target metal();
+
+/*! \return A target for rasp */
+EXPORT Target rasp();
+
+/*! \return A target for stackvm */
+EXPORT Target stackvm();
+
+}  // namespace target
+
+/*!
+* \brief Container for build configuration options
+*/
+struct BuildConfig {
+  /*!
+   * \brief The data alignment to use when constructing buffers. If this is set to
+   * -1, then TVM's internal default will be used
+   */
+  int data_alignment = -1;
+  /*!
+   * \brief The offset factor to use when constructing buffers. If this is set to
+   * 0, then the offset field is not used.
+   */
+  int offset_factor = 0;
+
+  /*!
+   * \brief Splitting factor for loop splitting. If this is set to zero, no splitting will be
+   * done. Otherwise, a split will be done with this factor and the inner loop will be unrolled.
+   */
+  int double_buffer_split_loop = 1;
+  /*! \brief Threshold of number of steps in the loop to be automatically unrolled */
+  int auto_unroll_max_step = 0;
+  /*! \brief The maximum nested level of loops that can be automatically unrolled */
+  int auto_unroll_max_depth = 8;
+  /*! \brief The maximum extent of loop that will be unrolled */
+  int auto_unroll_max_extent = 0;
+  /*!
+   * \brief Whether to explicitly unroll the loop. If set to false, the unroll hint will
+   * be passed to the CodeGen phase. Set to true if CodeGen supports unroll pragma.
+   */
+  bool unroll_explicit = true;
+
+  /*! \brief Set to true if buffer arguments do not overlap. This enables more optimization. */
+  bool restricted_func = true;
+
+  /*! \brief Whether to detect global barrier */
+  bool detect_global_barrier = false;
+
+  BuildConfig() {
+  }
+};
+
+/*!
+* \brief Build a LoweredFunc given a schedule, args and binds
+* \param sch The schedule to lower.
+* \param args The arguments to the function.
+* \param name The name of the lowered function.
+* \param binds Buffer assignments.
+* \param config The build configuration.
+* \return The lowered function.
+*/
+EXPORT Array<LoweredFunc> lower(Schedule sch,
+                                const Array<Tensor>& args,
+                                const std::string& name,
+                                const std::unordered_map<Tensor, Buffer>& binds,
+                                const BuildConfig& config);
+
+/*!
+* \brief Build a device and host module for a specific target from an array of lowered functions.
+* \param funcs The functions to be built.
+* \param target The target device to build for.
+* \param target_host The target for building host code. If null, a suitable default will be used.
+* \param config The build configuration.
+* \return The built module.
+*/
+EXPORT runtime::Module build(const Array<LoweredFunc>& funcs,
+                             const Target& target,
+                             Target* target_host,
+                             const BuildConfig& config);
+
+}  // namespace tvm
+
+#endif  // TVM_BUILD_MODULE_H_
diff --git a/include/tvm/expr.h b/include/tvm/expr.h
index 4e4e25c0ce7d..c0f4fea24bf8 100644
--- a/include/tvm/expr.h
+++ b/include/tvm/expr.h
@@ -291,6 +291,13 @@ inline const char* IterVarType2String(IterVarType t) {
   return "Unknown";
 }
 
+/*!
+ * \brief Construct a new Var expression
+ * \param name_hint The name hint for the expression
+ * \param t The type of the expression
+ */
+TVM_DLL Var var(const std::string& name_hint, Type t = Int(32));
+
 /*
  * \brief Template function to convert Map to unordered_map
  *  Sometimes useful for API gluing when internal uses unordered_map
diff --git a/include/tvm/schedule.h b/include/tvm/schedule.h
index a0e4a2c9e829..3efc31774d40 100644
--- a/include/tvm/schedule.h
+++ b/include/tvm/schedule.h
@@ -81,7 +81,7 @@ class Stage : public NodeRef {
    * \param thread_ivar The thread axis to be binded.
    * \return reference to self.
    */
-  Stage& bind(IterVar ivar, IterVar thread_ivar);
+  EXPORT Stage& bind(IterVar ivar, IterVar thread_ivar);
   /*!
    * \brief Set predicate under which store to the array can be performed.
    *  Use this when there are duplicated threads doing the same store and we only
@@ -110,7 +110,7 @@ class Stage : public NodeRef {
    * \param p_inner The result inner domain.
    * \return reference to self.
    */
-  Stage& split(IterVar parent, Expr factor, IterVar* p_outer, IterVar* p_inner);  // NOLINT(*)
+  EXPORT Stage& split(IterVar parent, Expr factor, IterVar* p_outer, IterVar* p_inner);  // NOLINT(*)
   /*!
    * \brief Split the iteration with given number of parts.
    *
@@ -248,13 +248,13 @@ class Schedule : public NodeRef {
    * \brief Get the stage corresponds to the op
    * \param op The operation.
    */
-  Stage operator[](const Operation& op);
+  EXPORT Stage operator[](const Operation& op);
   /*!
    * \brief Short hand for getting the stage of tensor's operation.
    * \param tensor The tensor
    * \return The stage corresponding to the tensor's op
    */
-  Stage operator[](const Tensor& tensor) {
+  EXPORT Stage operator[](const Tensor& tensor) {
     return this->operator[](tensor->op);
   }
   /*!
@@ -493,7 +493,7 @@ class ScheduleNode : public Node {
    * \param ops The ops to be scheduled.
    * \return sch The created Schedule.
    */
-  static Schedule make(Array<Operation> ops);
+  EXPORT static Schedule make(Array<Operation> ops);
 
   static constexpr const char* _type_key = "Schedule";
   TVM_DECLARE_NODE_TYPE_INFO(ScheduleNode, Node);
diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc
new file mode 100644
index 000000000000..d936b873bcd2
--- /dev/null
+++ b/src/codegen/build_module.cc
@@ -0,0 +1,314 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ *  Compile executable modules.
+ * \file build_module.cc
+ */
+#include <tvm/build_module.h>
+#include <tvm/operation.h>
+#include <tvm/ir_pass.h>
+#include <tvm/codegen.h>
+
+
+namespace tvm {
+
+std::string Target::str() const {
+  std::ostringstream result;
+  result << target_name;
+  for (const auto &x : options) {
+    result << " " << x;
+  }
+  return result.str();
+}
+
+Target TargetFromName(const std::string& name) {
+  if (name == "llvm") {
+    return target::llvm();
+  } else if (name == "cuda" || name == "nvptx") {
+    return target::cuda();
+  } else if (name == "rocm" || name == "opencl") {
+    /* For now, assume rocm schedule for opencl */
+    return target::rocm();
+  } else if (name == "metal") {
+    return target::metal();
+  } else if (name == "stackvm" || name == "ext_dev") {
+    return target::stackvm();
+  } else {
+    LOG(ERROR) << "Unknown target name " << name;
+    return target::stackvm();
+  }
+}
+
+bool StartsWith(const std::string& str, const std::string& pattern) {
+  return str.compare(0, pattern.length(), pattern) == 0;
+}
+
+std::string GetDeviceName(const std::string& target_str) {
+  std::istringstream ss(target_str);
+  std::string target_name;
+  ss >> target_name;
+
+  std::string item;
+  while (ss >> item) {
+    if (StartsWith(item, "-device=")) {
+      return item.substr(std::string("-device=").length());
+    }
+  }
+
+  return "";
+}
+
+Target Target::create(const std::string& target_str) {
+  if (target_str.length() == 0) {
+    LOG(ERROR) << "target_str must not be empty";
+  }
+
+  std::istringstream ss(target_str);
+  std::string target_name;
+
+  ss >> target_name;
+  auto device_name = GetDeviceName(target_str);
+
+  auto result = device_name == "rasp" ?
+    target::rasp() :
+    TargetFromName(target_name);
+
+  std::string item;
+  while (ss >> item) {
+    result.options.push_back(item);
+  }
+
+  return result;
+}
+
+namespace target {
+Target llvm() {
+  std::unordered_set<std::string> keys({ "llvm", "cpu" });
+  std::vector<std::string> options;
+  return Target("llvm", kDLCPU, 512, 1, keys, options);
+}
+
+Target cuda() {
+  std::unordered_set<std::string> keys({ "cuda", "gpu" });
+  std::vector<std::string> options;
+  return Target("cuda", kDLGPU, 512, 32, keys, options);
+}
+
+Target rocm() {
+  std::unordered_set<std::string> keys({ "rocm", "gpu" });
+  std::vector<std::string> options;
+  return Target("rocm", kDLROCM, 256, 1, keys, options);
+}
+
+Target metal() {
+  std::unordered_set<std::string> keys({ "gpu" });
+  std::vector<std::string> options;
+  return Target("metal", kDLMetal, 256, 1, keys, options);
+}
+
+Target rasp() {
+  std::unordered_set<std::string> keys({ "llvm", "cpu" });
+  std::vector<std::string> options({
+    "-device=rasp",
+    "-mtriple=armv7l-none-linux-gnueabihf",
+    "-mcpu=cortex-a53",
+    "-mattr=+neon"
+  });
+  return Target("llvm", kDLCPU, 512, 1, keys, options);
+}
+
+Target stackvm() {
+  std::unordered_set<std::string> keys({ "stackvm", "cpu" });
+  std::vector<std::string> options;
+  return Target("stackvm", kDLCPU, 512, 1, keys, options);
+}
+}  // namespace target
+
+bool LLVMEnabled() {
+  const runtime::PackedFunc* pf = runtime::Registry::Get("codegen.build_llvm");
+  return pf != nullptr;
+}
+
+/*! \return The default host target for a given device target */
+Target DefaultTargetHost(Target target) {
+  if (target.device_type == kDLCPU) {
+    return target;
+  } else {
+    if (LLVMEnabled()) {
+      return target::llvm();
+    } else {
+      return target::stackvm();
+    }
+  }
+}
+
+Buffer BufferWithOffsetAlignment(Array<Expr> shape,
+                                 Type dtype,
+                                 std::string name,
+                                 int data_alignment,
+                                 int offset_factor) {
+  auto data = Var(name, Handle());
+
+  Expr elem_offset;
+  if (offset_factor != 0) {
+    elem_offset = Var(name + "_elem_offset", shape[0].type());
+  } else {
+    elem_offset = Expr();
+  }
+
+  return BufferNode::make(data, dtype, shape, Array<Expr>(), elem_offset, name, "",
+    data_alignment, offset_factor);
+}
+
+void GetBinds(const Array<Tensor>& args,
+              const std::unordered_map<Tensor, Buffer>& binds,
+              Map<Tensor, Buffer>* out_binds,
+              Array<NodeRef>* out_arg_list,
+              const BuildConfig& config) {
+  *out_binds = binds;
+
+  for (const auto &x : args) {
+    if (out_binds->find(x) == out_binds->end()) {
+      auto buf = BufferWithOffsetAlignment(x->shape, x->dtype, x->op->name,
+        config.data_alignment, config.offset_factor);
+      out_binds->Set(x, buf);
+      out_arg_list->push_back(buf);
+    } else {
+      out_arg_list->push_back((*out_binds)[x]);
+    }
+  }
+}
+
+/*!
+* \brief Build a Stmt given a schedule, args and binds. This function runs the IR passes.
+* \param sch The schedule to build.
+* \param args The arguments for the schedule.
+* \param binds Buffer assignments.
+* \param loop_partition True if the LoopPartition pass should be included.
+* \param out_arg_list Returns the arguments for the Stmt.
+* \param config The build configuration.
+* \return The built Stmt.
+*/
+Stmt BuildStmt(Schedule sch,
+               const Array<Tensor>& args,
+               const std::unordered_map<Tensor, Buffer>& binds,
+               bool loop_partition,
+               Array<NodeRef> *out_arg_list,
+               const BuildConfig& config) {
+  Map<Tensor, Buffer> out_binds;
+  GetBinds(args, binds, &out_binds, out_arg_list, config);
+
+  sch = sch.normalize();
+
+  // Phase 0
+  auto bounds = schedule::InferBound(sch);
+  auto stmt = schedule::ScheduleOps(sch, bounds);
+  stmt = ir::InjectPrefetch(stmt);
+
+  // Phase 1
+  stmt = ir::StorageFlatten(stmt, out_binds, 64);
+  stmt = ir::CanonicalSimplify(stmt);
+  if (loop_partition) {
+    stmt = ir::LoopPartition(stmt);
+  }
+  stmt = ir::VectorizeLoop(stmt);
+  stmt = ir::InjectVirtualThread(stmt);
+  stmt = ir::InjectDoubleBuffer(stmt, config.double_buffer_split_loop);
+  stmt = ir::StorageRewrite(stmt);
+  stmt = ir::UnrollLoop(stmt, config.auto_unroll_max_step, config.auto_unroll_max_depth,
+    config.auto_unroll_max_extent, config.unroll_explicit);
+
+  // Phase 2
+  stmt = ir::Simplify(stmt);
+  stmt = ir::LowerStorageAccessInfo(stmt);
+  stmt = ir::RemoveNoOp(stmt);
+  stmt = ir::RewriteUnsafeSelect(stmt);
+
+  return stmt;
+}
+
+Array<LoweredFunc> lower(Schedule sch,
+                         const Array<Tensor>& args,
+                         const std::string& name,
+                         const std::unordered_map<Tensor, Buffer>& binds,
+                         const BuildConfig& config) {
+  Array<NodeRef> out_arg_list;
+  auto stmt = BuildStmt(sch, args, binds, true, &out_arg_list, config);
+  return Array<LoweredFunc>({ ir::MakeAPI(stmt, name, out_arg_list, 0, config.restricted_func) });
+}
+
+runtime::Module build(const Array<LoweredFunc>& funcs,
+                      const Target& target,
+                      Target* target_host,
+                      const BuildConfig& config) {
+  std::unordered_set<std::string> all_names;
+  for (const auto &x : funcs) {
+    CHECK(all_names.count(x->name) == 0) << "Duplicate function name " << x->name;
+    all_names.insert(x->name);
+  }
+
+  Target target_host_val = target_host == nullptr ?
+    DefaultTargetHost(target) :
+    *target_host;
+
+  Array<LoweredFunc> fhost;
+  Array<LoweredFunc> fdevice;
+
+  for (const auto &x : funcs) {
+    if (x->func_type == kMixedFunc) {
+      auto func = x;
+      if (config.detect_global_barrier) {
+        func = ir::ThreadSync(func, "global");
+      }
+
+      func = ir::ThreadSync(func, "shared");
+      func = ir::LowerThreadAllreduce(func, target.thread_warp_size);
+      auto fsplits = ir::SplitHostDevice(func);
+      fhost.push_back(fsplits[0]);
+      for (auto f = fsplits.begin() + 1; f != fsplits.end(); ++f) {
+        fdevice.push_back(*f);
+      }
+    } else if (x->func_type == kHostFunc) {
+      fhost.push_back(x);
+    } else if (x->func_type == kDeviceFunc) {
+      fdevice.push_back(x);
+    } else {
+      LOG(FATAL) << "unknown function type " << x->func_type;
+    }
+  }
+
+  if (target.keys.count("gpu") > 0 && fdevice.size() == 0) {
+    LOG(WARNING) << "Specified target " + target.str() +
+      " but cannot find device code. Did you forget to bind?";
+  }
+
+  for (size_t i = 0; i < fhost.size(); ++i) {
+    auto func = fhost[i];
+    func = ir::BindDeviceType(func, target.device_type);
+    func = ir::LowerTVMBuiltin(func);
+    fhost.Set(i, func);
+  }
+
+
+  for (size_t i = 0; i < fdevice.size(); ++i) {
+    auto func = fdevice[i];
+    func = ir::LowerIntrin(func, target.target_name);
+    fdevice.Set(i, func);
+  }
+
+  for (size_t i = 0; i < fhost.size(); ++i) {
+    auto func = fhost[i];
+    func = ir::LowerIntrin(func, target_host_val.target_name);
+    func = ir::CombineContextCall(func);
+    fhost.Set(i, func);
+  }
+
+  auto mhost = codegen::Build(fhost, target_host_val.str());
+
+  if (fdevice.size() > 0) {
+    auto mdev = codegen::Build(fdevice, target.str());
+    mhost.Import(mdev);
+  }
+
+  return mhost;
+}
+}  // namespace tvm
diff --git a/src/lang/expr.cc b/src/lang/expr.cc
index 348733bad626..be83b521ed8e 100644
--- a/src/lang/expr.cc
+++ b/src/lang/expr.cc
@@ -47,6 +47,10 @@ std::ostream& operator<<(std::ostream& os, const NodeRef& n) {  // NOLINT(*)
   return os;
 }
 
+Var var(const std::string& name_hint, Type t) {
+  return Var(name_hint, t);
+}
+
 TVM_STATIC_IR_FUNCTOR(IRPrinter, vtable)
 .set_dispatch<IterVarNode>([](const IterVarNode *op, IRPrinter *p) {
     p->stream << "iter_var(";
diff --git a/tests/cpp/build_module_test.cc b/tests/cpp/build_module_test.cc
new file mode 100644
index 000000000000..fc3f6ac9324d
--- /dev/null
+++ b/tests/cpp/build_module_test.cc
@@ -0,0 +1,42 @@
+#include <dmlc/logging.h>
+#include <gtest/gtest.h>
+#include <tvm/tvm.h>
+#include <tvm/operation.h>
+#include <tvm/build_module.h>
+
+TEST(BuildModule, Basic) {
+  using namespace tvm;
+  auto n = var("n");
+  Array<Expr> shape;
+  shape.push_back(n);
+
+  auto A = placeholder(shape, Float(32), "A");
+  auto B = placeholder(shape, Float(32), "B");
+
+  auto C = compute(A->shape, [&A, &B](Expr i) {
+    return A[i] + B[i];
+  }, "C");
+
+  auto s = create_schedule({ C->op });
+
+  auto cAxis = C->op.as<ComputeOpNode>()->axis;
+
+  IterVar bx, tx;
+  s[C].split(cAxis[0], 64, &bx, &tx);
+
+  auto args = Array<Tensor>({ A, B, C });
+  std::unordered_map<Tensor, Buffer> binds;
+
+  BuildConfig config;
+  auto target = target::llvm();
+
+  auto lowered = lower(s, args, "func", binds, config);
+  auto module = build(lowered, target, nullptr, config);
+}
+
+
+int main(int argc, char ** argv) {
+  testing::InitGoogleTest(&argc, argv);
+  testing::FLAGS_gtest_death_test_style = "threadsafe";
+  return RUN_ALL_TESTS();
+}

From 555e084d23bc0bc032f1f8d902c637b1dcf77cde Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Thu, 7 Dec 2017 08:20:45 +0800
Subject: [PATCH 041/949] [CODEGEN] add callback post proc for opencl (#692)

---
 src/codegen/build_opencl.cc | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/codegen/build_opencl.cc b/src/codegen/build_opencl.cc
index 51779d3f7a3e..c4a53744d477 100644
--- a/src/codegen/build_opencl.cc
+++ b/src/codegen/build_opencl.cc
@@ -16,6 +16,7 @@ namespace tvm {
 namespace codegen {
 
 runtime::Module BuildOpenCL(Array<LoweredFunc> funcs) {
+  using tvm::runtime::Registry;
   bool output_ssa = false;
   CodeGenOpenCL cg;
   cg.Init(output_ssa);
@@ -23,6 +24,10 @@ runtime::Module BuildOpenCL(Array<LoweredFunc> funcs) {
     cg.AddFunction(f);
   }
   std::string code = cg.Finish();
+
+  if (const auto* f = Registry::Get("tvm_callback_opencl_postproc")) {
+    code = (*f)(code).operator std::string();
+  }
 #if TVM_OPENCL_RUNTIME
   return OpenCLModuleCreate(code, "cl", ExtractFuncInfo(funcs));
 #else

From 99831770a7ccbd808a67635fea5d1b1fa1e91fb1 Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Mon, 11 Dec 2017 19:50:04 +0800
Subject: [PATCH 042/949] [CODEGEN] add fp16 and fp64 enable pragma for opencl
 (#697)

* [CODEGEN] add fp16 and fp64 enable pragma for opencl

* fix style
---
 src/codegen/codegen_c.cc      |  4 +++-
 src/codegen/codegen_c.h       |  2 +-
 src/codegen/codegen_cuda.cc   |  2 +-
 src/codegen/codegen_cuda.h    |  2 +-
 src/codegen/codegen_metal.cc  |  2 +-
 src/codegen/codegen_metal.h   |  2 +-
 src/codegen/codegen_opencl.cc | 41 ++++++++++++++++++++++++++++++++---
 src/codegen/codegen_opencl.h  |  9 +++++++-
 8 files changed, 54 insertions(+), 10 deletions(-)

diff --git a/src/codegen/codegen_c.cc b/src/codegen/codegen_c.cc
index 2bbf8324eb51..b315707b52c5 100644
--- a/src/codegen/codegen_c.cc
+++ b/src/codegen/codegen_c.cc
@@ -272,7 +272,7 @@ void CodeGenC::PrintStorageScope(const std::string& scope, std::ostream& os) { /
   CHECK_EQ(scope, "global");
 }
 
-void CodeGenC::PrintType(Type t, std::ostream& os) const {  // NOLINT(*)
+void CodeGenC::PrintType(Type t, std::ostream& os) {  // NOLINT(*)
   CHECK_EQ(t.lanes(), 1)
       << "do not yet support vector types";
   if (t.is_handle()) {
@@ -402,7 +402,9 @@ inline void PrintBinaryIntrinsitc(const Call* op,
   }
 }
 void CodeGenC::VisitExpr_(const Cast *op, std::ostream& os) {  // NOLINT(*)
+  os << "(";
   this->PrintType(op->type, os);
+  os << ")";
   os << '(';
   this->PrintExpr(op->value, os);
   os << ')';
diff --git a/src/codegen/codegen_c.h b/src/codegen/codegen_c.h
index 8141c57c84a5..1c68dd18bd68 100644
--- a/src/codegen/codegen_c.h
+++ b/src/codegen/codegen_c.h
@@ -118,7 +118,7 @@ class CodeGenC :
    * \param t The type representation.
    * \param os The stream to print the ctype into
    */
-  virtual void PrintType(Type t, std::ostream& os) const; // NOLINT(*)
+  virtual void PrintType(Type t, std::ostream& os); // NOLINT(*)
   /*!
    * \brief Print expr representing the thread tag
    * \param IterVar iv The thread index to be binded;
diff --git a/src/codegen/codegen_cuda.cc b/src/codegen/codegen_cuda.cc
index 249128d12f62..90b01e91bd51 100644
--- a/src/codegen/codegen_cuda.cc
+++ b/src/codegen/codegen_cuda.cc
@@ -45,7 +45,7 @@ void CodeGenCUDA::BindThreadIndex(const IterVar& iv) {
       CastFromTo(iv->thread_tag, UInt(32), iv->var.type());
 }
 
-void CodeGenCUDA::PrintType(Type t, std::ostream& os) const {  // NOLINT(*)
+void CodeGenCUDA::PrintType(Type t, std::ostream& os) {  // NOLINT(*)
   int lanes = t.lanes();
   if (t.is_handle()) {
     CHECK_EQ(lanes, 1)
diff --git a/src/codegen/codegen_cuda.h b/src/codegen/codegen_cuda.h
index c0703523dfeb..9aa72d14cb06 100644
--- a/src/codegen/codegen_cuda.h
+++ b/src/codegen/codegen_cuda.h
@@ -26,7 +26,7 @@ class CodeGenCUDA final : public CodeGenC {
   void PrintVecBinaryOp(
       const std::string&op, Type t,
       Expr lhs, Expr rhs, std::ostream& os) final;  // NOLINT(*)
-  void PrintType(Type t, std::ostream& os) const final; // NOLINT(*)
+  void PrintType(Type t, std::ostream& os) final; // NOLINT(*)
   void PrintVecElemLoad(
       const std::string& vec, Type t, int i, std::ostream& os) final;  // NOLINT(*)
   void PrintVecElemStore(
diff --git a/src/codegen/codegen_metal.cc b/src/codegen/codegen_metal.cc
index 7eb1a03c2019..0df63aad49a2 100644
--- a/src/codegen/codegen_metal.cc
+++ b/src/codegen/codegen_metal.cc
@@ -132,7 +132,7 @@ void CodeGenMetal::BindThreadIndex(const IterVar& iv) {
       CastFromTo(iv->thread_tag, UInt(thread_index_bits_), iv->var.type());
 }
 
-void CodeGenMetal::PrintType(Type t, std::ostream& os) const {  // NOLINT(*)
+void CodeGenMetal::PrintType(Type t, std::ostream& os) {  // NOLINT(*)
   int lanes = t.lanes();
   if (t.is_handle()) {
     CHECK_EQ(lanes, 1)
diff --git a/src/codegen/codegen_metal.h b/src/codegen/codegen_metal.h
index e1bc09f0a939..80efef67332e 100644
--- a/src/codegen/codegen_metal.h
+++ b/src/codegen/codegen_metal.h
@@ -23,7 +23,7 @@ class CodeGenMetal final : public CodeGenC {
   void InitFuncState(LoweredFunc f) final;
   void PrintStorageScope(const std::string& scope, std::ostream& os) final; // NOLINT(*)
   void PrintStorageSync(const Call* op) final;  // NOLINT(*)
-  void PrintType(Type t, std::ostream& os) const final; // NOLINT(*)
+  void PrintType(Type t, std::ostream& os) final; // NOLINT(*)
   void BindThreadIndex(const IterVar& iv) final;  // NOLINT(*)
   // overload visitor
   void VisitExpr_(const Broadcast* op, std::ostream& os) final; // NOLINT(*)
diff --git a/src/codegen/codegen_opencl.cc b/src/codegen/codegen_opencl.cc
index 9d0e16f2862e..ccd164bcd9e0 100644
--- a/src/codegen/codegen_opencl.cc
+++ b/src/codegen/codegen_opencl.cc
@@ -30,6 +30,35 @@ void CodeGenOpenCL::AddFunction(LoweredFunc f) {
   CodeGenC::AddFunction(f);
 }
 
+std::string CodeGenOpenCL::Finish() {
+  // inject extension enable pragma for fp16 and fp64
+  if (enable_fp16_) {
+    decl_stream
+        << "#ifdef cl_khr_fp16\n"
+           "#pragma OPENCL EXTENSION cl_khr_fp16 : enable\n"
+           "#elif defined(cl_amd_fp16)\n"
+           "#pragma OPENCL EXTENSION cl_amd_fp16 : enable\n"
+           "#else\n"
+           "#error \"Half precision floating point not supported"
+                    "by OpenCL implementation on your device.\" \n"
+           "#endif\n\n";
+  }
+
+  if (enable_fp64_) {
+    decl_stream
+        << "#ifdef cl_khr_fp64\n"
+           "#pragma OPENCL EXTENSION cl_khr_fp64 : enable\n"
+           "#elif defined(cl_amd_fp64)\n"
+           "#pragma OPENCL EXTENSION cl_amd_fp64 : enable\n"
+           "#else\n"
+           "#error \"Double precision floating point not supported"
+                    "by OpenCL implementation on your device.\" \n"
+           "#endif\n\n";
+  }
+
+  return CodeGenC::Finish();
+}
+
 void CodeGenOpenCL::BindThreadIndex(const IterVar& iv) {
   CHECK(!var_idmap_.count(iv->var.get()));
   runtime::ThreadScope ts = runtime::ThreadScope::make(iv->thread_tag);
@@ -43,7 +72,7 @@ void CodeGenOpenCL::BindThreadIndex(const IterVar& iv) {
       CastFromTo(os.str(), UInt(64), iv->var.type());
 }
 
-void CodeGenOpenCL::PrintType(Type t, std::ostream& os) const {  // NOLINT(*)
+void CodeGenOpenCL::PrintType(Type t, std::ostream& os) {  // NOLINT(*)
   int lanes = t.lanes();
   if (t.is_handle()) {
     CHECK_EQ(lanes, 1)
@@ -53,9 +82,15 @@ void CodeGenOpenCL::PrintType(Type t, std::ostream& os) const {  // NOLINT(*)
   bool fail = false;
   if (t.is_float()) {
     switch (t.bits()) {
-      case 16: os << "half"; break;
+      case 16:
+        os << "half";
+        enable_fp16_ = true;
+        break;
       case 32: os << "float"; break;
-      case 64: os << "double"; break;
+      case 64:
+        os << "double";
+        enable_fp64_ = true;
+        break;
       default: fail = true; break;
     }
     if (!fail && lanes == 1) return;
diff --git a/src/codegen/codegen_opencl.h b/src/codegen/codegen_opencl.h
index a39d4e104c47..a10c165ee3a1 100644
--- a/src/codegen/codegen_opencl.h
+++ b/src/codegen/codegen_opencl.h
@@ -18,12 +18,14 @@ class CodeGenOpenCL final : public CodeGenC {
  public:
   CodeGenOpenCL();
   void AddFunction(LoweredFunc f);
+  std::string Finish();
+
   // override print thread tag.
   void InitFuncState(LoweredFunc f) final;
   void BindThreadIndex(const IterVar& iv) final;  // NOLINT(*)
   void PrintStorageScope(const std::string& scope, std::ostream& os) final; // NOLINT(*)
   void PrintStorageSync(const Call* op) final;  // NOLINT(*)
-  void PrintType(Type t, std::ostream& os) const final; // NOLINT(*)
+  void PrintType(Type t, std::ostream& os) final; // NOLINT(*)
   std::string GetVecLoad(Type t, const Variable* buffer,
                          Expr base) final;
   void PrintVecStore(const Variable* buffer,
@@ -34,6 +36,11 @@ class CodeGenOpenCL final : public CodeGenC {
                     Expr base, std::ostream& os);  // NOLINT(*)
   // overload visitor
   void VisitExpr_(const Broadcast* op, std::ostream& os) final; // NOLINT(*)
+
+ private:
+  // whether enable fp16 and fp64 extension
+  bool enable_fp16_{false};
+  bool enable_fp64_{false};
 };
 
 }  // namespace codegen

From 367ca3bfcee76637a06aa9c17101ffb852540bff Mon Sep 17 00:00:00 2001
From: abergeron <bergearn@iro.umontreal.ca>
Date: Mon, 11 Dec 2017 18:43:11 -0500
Subject: [PATCH 043/949] Fix long for windows in cuda (#700)

* Use long long for platforms where long is 32 bits (like windows).

* Make sure scalar chars are signed.

* Re-add NOLINT marker.
---
 src/codegen/codegen_cuda.cc | 23 +++++++++++++++++++----
 1 file changed, 19 insertions(+), 4 deletions(-)

diff --git a/src/codegen/codegen_cuda.cc b/src/codegen/codegen_cuda.cc
index 90b01e91bd51..cace8d8573bb 100644
--- a/src/codegen/codegen_cuda.cc
+++ b/src/codegen/codegen_cuda.cc
@@ -77,13 +77,28 @@ void CodeGenCUDA::PrintType(Type t, std::ostream& os) {  // NOLINT(*)
       os << "int"; return;
     }
     switch (t.bits()) {
-      case 8: os << "char"; break;
+      case 8: {
+        if (!t.is_uint() && t.lanes() == 1) {
+          os << "signed char"; break;
+        } else {
+          os << "char"; break;
+        }
+      }
       case 16: os << "short"; break;
       case 32: os << "int"; break;
       case 64: {
-        CHECK(sizeof(long) == 8)  // NOLINT(*)
-            << "CUDA not support int64 int in 32 bit system";
-        os << "long"; break;
+        if (sizeof(long) != 8) { // NOLINT(*)
+          if (t.lanes() == 1) {
+            os << "long long"; break;
+          } else if (t.lanes() == 2) {
+            os << "longlong"; break;
+          } else {
+            // No longlong3, longlong4
+            LOG(FATAL) << "Cannot convert type " << t << " to CUDA type on a L32 platform";
+          }
+        } else {
+          os << "long"; break;
+        }
       }
       case 1: os << "int"; break;
       default: fail = true; break;

From ce1c9d62075bdeb3c3f605fb0d4ba926f6127f0e Mon Sep 17 00:00:00 2001
From: Salem Derisavi <33945117+derisavi-huawei@users.noreply.github.com>
Date: Tue, 12 Dec 2017 20:10:35 -0500
Subject: [PATCH 044/949] 1) Make unroll code reusable 2) reduce
 non-determinisim in CanonicalSimplify (#701)

* 1) Refactored some parts of the unrolling code into their own methods so we can reuse unrolling functionality in other parts of the code. E.g., to explicitly unroll loops with count of 1 when they are programmatically created.
2) Reorder based on top operator before resorting to pointers, which causes non-determinism.

* Fixed lint errors
---
 src/arithmetic/canonical.cc |  2 +
 src/pass/unroll_loop.cc     | 79 +++++++++++++++++++++++--------------
 2 files changed, 52 insertions(+), 29 deletions(-)

diff --git a/src/arithmetic/canonical.cc b/src/arithmetic/canonical.cc
index 24369db02390..473e330de735 100644
--- a/src/arithmetic/canonical.cc
+++ b/src/arithmetic/canonical.cc
@@ -29,6 +29,8 @@ struct ComExprEntry {
   inline bool operator<(const ComExprEntry& other) const {
     if (level < other.level) return true;
     if (level > other.level) return false;
+    if (value.type_index() < other.value.type_index()) return true;
+    if (value.type_index() > other.value.type_index()) return false;
     return value.get() < other.value.get();
   }
 };
diff --git a/src/pass/unroll_loop.cc b/src/pass/unroll_loop.cc
index 01c5e6ebff00..22fd389454af 100644
--- a/src/pass/unroll_loop.cc
+++ b/src/pass/unroll_loop.cc
@@ -30,17 +30,7 @@ class LoopUnroller : public IRMutator {
   Stmt Mutate_(const For* op, const Stmt& s) {
     Stmt stmt = IRMutator::Mutate_(op, s);
     op = stmt.as<For>();
-    // constant folding.
-    Expr extent = ir::Simplify(op->extent);
-    const IntImm* v1 = extent.as<IntImm>();
-    const UIntImm* v2 = extent.as<UIntImm>();
-    int value = -1;
-    if (v1 != nullptr) {
-      value = static_cast<int>(v1->value);
-    }
-    if (v2 != nullptr) {
-      value = static_cast<int>(v2->value);
-    }
+    int value = GetExtent(op);
     // condition for auto unroll
     bool auto_unroll = (
         op->for_type == ForType::Serial &&
@@ -66,24 +56,7 @@ class LoopUnroller : public IRMutator {
     }
 
     if (auto_unroll && explicit_unroll_) {
-      using arith::ComputeExpr;
-      if (value == 0) return Evaluate::make(0);
-      Stmt body = op->body;
-      Map<Var, Expr> vmap;
-      Stmt unrolled;
-      for (int i = 0; i < value; ++i) {
-        Var lv(op->loop_var.node_);
-        vmap.Set(lv,
-                 ComputeExpr<Add>(
-                     op->min, make_const(op->loop_var.type(), i)));
-        Stmt step = Substitute(body, vmap);
-        if (unrolled.defined()) {
-          unrolled = Block::make(unrolled, step);
-        } else {
-          unrolled = step;
-        }
-      }
-      return unrolled;
+      return Unroll(op);
     } else {
       if (auto_unroll) {
         if (op->for_type != ForType::Unrolled) {
@@ -128,7 +101,47 @@ class LoopUnroller : public IRMutator {
     }
   }
 
+  Stmt Unroll(const For* op) {
+    using arith::ComputeExpr;
+    int value = GetExtent(op);
+    // For loop must have a constant integer extent
+    CHECK_NE(value, -1) << "loop doesn't have a constant integer extent";
+    if (value == 0) return Evaluate::make(0);
+    Stmt body = op->body;
+    Map<Var, Expr> vmap;
+    Stmt unrolled;
+    for (int i = 0; i < value; ++i) {
+      Var lv(op->loop_var.node_);
+      vmap.Set(lv,
+               ComputeExpr<Add>(
+                       op->min, make_const(op->loop_var.type(), i)));
+      Stmt step = Substitute(body, vmap);
+      if (unrolled.defined()) {
+        unrolled = Block::make(unrolled, step);
+      } else {
+        unrolled = step;
+      }
+    }
+    return unrolled;
+  }
+
  private:
+  // returns the extent of the loop if it's a constant integer, otherwise return -1
+  int GetExtent(const For* op) {
+    // constant folding.
+    Expr extent = ir::Simplify(op->extent);
+    const IntImm  *v1 = extent.as<IntImm>();
+    const UIntImm *v2 = extent.as<UIntImm>();
+    int value = -1;
+    if (v1 != nullptr) {
+      value = static_cast<int>(v1->value);
+    }
+    if (v2 != nullptr) {
+      value = static_cast<int>(v2->value);
+    }
+    return value;
+  }
+
   // maximum number of step to perform auto unroll.
   int auto_max_step_;
   int auto_max_depth_;
@@ -162,5 +175,13 @@ Stmt UnrollLoop(Stmt stmt,
   }
 }
 
+Stmt UnrollLoopExplicitly(Stmt stmt) {
+  const For* op = stmt.as<For>();
+  if (!op) {
+    LOG(FATAL) << "attempted to unroll a non-loop statement";
+  }
+  return LoopUnroller(0, 0, 0, false).Unroll(op);
+}
+
 }  // namespace ir
 }  // namespace tvm

From 793f4912ddab5cb228c815ac9e69f384cf3d2fc9 Mon Sep 17 00:00:00 2001
From: Salem Derisavi <33945117+derisavi-huawei@users.noreply.github.com>
Date: Wed, 13 Dec 2017 18:28:52 -0500
Subject: [PATCH 045/949] Simplify expressions early on (#702)

* Simplify expressions early on

* fixed lint errors
---
 src/arithmetic/int_set.cc | 2 +-
 src/schedule/bound.cc     | 5 +++++
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/arithmetic/int_set.cc b/src/arithmetic/int_set.cc
index 8a88ed23e262..b19aa4691e8b 100644
--- a/src/arithmetic/int_set.cc
+++ b/src/arithmetic/int_set.cc
@@ -27,7 +27,7 @@ inline IntSet IntSet::cover_interval() const {
     for (size_t i = 0; i < s->extents.size(); ++i) {
       max = max + s->extents[i] * s->strides[i] - s->strides[i];
     }
-    return IntervalSet::make(s->base.min, max);
+    return IntervalSet::make(s->base.min, Simplify(max));
   }
   LOG(FATAL) << "cannot convert set " << (*this)->type_key() << " to interval";
   return IntSet::everything();
diff --git a/src/schedule/bound.cc b/src/schedule/bound.cc
index 203ce28708a3..1a06970e52e4 100644
--- a/src/schedule/bound.cc
+++ b/src/schedule/bound.cc
@@ -6,6 +6,7 @@
 #include <tvm/ir_visitor.h>
 #include <tvm/schedule_pass.h>
 #include <tvm/operation.h>
+#include <tvm/ir_pass.h>
 #include <unordered_map>
 #include <unordered_set>
 #include "./graph.h"
@@ -209,6 +210,10 @@ Map<IterVar, Range> InferBound(const Schedule& sch) {
       ret[iv] = iv->dom;
     }
   }
+  for (auto& p : ret) {
+    ret[p.first] = Range::make_by_min_extent(ir::Simplify(p.second->min),
+                                             ir::Simplify(p.second->extent));
+  }
   return Map<IterVar, Range>(ret.begin(), ret.end());
 }
 

From fb14d9d747e3af3075117d1e1103d04713248d18 Mon Sep 17 00:00:00 2001
From: Cody Hao Yu <comaniac0422@gmail.com>
Date: Thu, 14 Dec 2017 16:35:50 -0800
Subject: [PATCH 046/949] Make duplicated function name checker working (#705)

---
 CONTRIBUTORS.md            | 1 +
 python/tvm/build_module.py | 1 +
 2 files changed, 2 insertions(+)

diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 944e76fd3b83..a62c11e5fa4d 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -36,3 +36,4 @@ List of Contributors
 - [Jian Weng](https://github.com/were)
 - [Masahiro Masuda](https://github.com/masahi)
 - [Haolong Zhang](https://github.com/haolongzhangm)
+- [Cody Hao Yu](https://github.com/comaniac)
diff --git a/python/tvm/build_module.py b/python/tvm/build_module.py
index 5756fe6d14c6..083074a6676e 100644
--- a/python/tvm/build_module.py
+++ b/python/tvm/build_module.py
@@ -310,6 +310,7 @@ def build(sch,
             raise ValueError("sch have to be Schedule, LoweredFunc or list of LoweredFunc")
         if x.name in fname_set:
             raise ValueError("Duplicate function name %s" % x.name)
+        fname_set.add(x.name)
 
     target = _target.current_target() if target is None else target
     target = _target.create(target) if target else _target.create("llvm")

From 9bb6d77ed295899d6c25f491fc3aef762410566c Mon Sep 17 00:00:00 2001
From: masahi <masahi129@gmail.com>
Date: Sat, 16 Dec 2017 18:06:49 -0500
Subject: [PATCH 047/949] fix cudnn output shape (#708)

---
 src/contrib/cudnn/conv_forward.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/contrib/cudnn/conv_forward.cc b/src/contrib/cudnn/conv_forward.cc
index fadcfa03b021..480a789303c6 100644
--- a/src/contrib/cudnn/conv_forward.cc
+++ b/src/contrib/cudnn/conv_forward.cc
@@ -114,7 +114,7 @@ TVM_REGISTER_GLOBAL("tvm.contrib.cudnn.conv2d.output_shape")
   int x_dim3 = args[10];
   int w_dim0 = args[11];
   int w_dim1 = args[12];
-  int w_dim2 = args[12];
+  int w_dim2 = args[13];
   int w_dim3 = args[14];
   void *out_shape = args[15];
   // Set Format

From 52871592db15b0354e706724ee110338fc1bb7bc Mon Sep 17 00:00:00 2001
From: Andrew Adams <andrew.b.adams@gmail.com>
Date: Sun, 17 Dec 2017 15:52:48 -0800
Subject: [PATCH 048/949] Halide -> HalideIR (#698)

---
 HalideIR                          |  2 +-
 include/tvm/expr.h                | 56 +++++++++---------
 include/tvm/ir.h                  | 96 +++++++++++++++----------------
 include/tvm/ir_operator.h         | 14 ++---
 include/tvm/packed_func_ext.h     | 10 ++--
 include/tvm/runtime/packed_func.h | 12 ++--
 include/tvm/tensor.h              |  2 +-
 src/api/api_ir.cc                 |  2 +-
 src/arithmetic/bound_deducer.cc   |  2 +-
 src/arithmetic/canonical.cc       |  4 +-
 src/arithmetic/compute_expr.h     | 10 ++--
 src/arithmetic/int_set.cc         |  2 +-
 src/arithmetic/int_set_internal.h |  2 +-
 src/lang/expr.cc                  |  6 +-
 src/lang/ir.cc                    |  4 +-
 src/lang/reflection.cc            |  2 +-
 src/lang/tensor.cc                |  2 +-
 src/op/compute_op.cc              |  2 +-
 src/op/extern_op.cc               |  2 +-
 src/op/scan_op.cc                 |  2 +-
 src/pass/inject_prefetch.cc       |  2 +-
 src/pass/ir_deep_compare.cc       |  4 +-
 src/pass/ir_mutator.cc            |  4 +-
 src/pass/storage_flatten.cc       |  2 +-
 src/schedule/message_passing.cc   |  2 +-
 tests/cpp/ir_mutator_test.cc      |  6 +-
 tests/cpp/ir_simplify_test.cc     |  2 +-
 tests/cpp/ir_ssa_test.cc          |  4 +-
 tests/cpp/ir_visitor_test.cc      |  2 +-
 29 files changed, 131 insertions(+), 131 deletions(-)

diff --git a/HalideIR b/HalideIR
index d91cf97d5d6c..aadbf02d6bd7 160000
--- a/HalideIR
+++ b/HalideIR
@@ -1 +1 @@
-Subproject commit d91cf97d5d6cd2b47ec408bb08e978b88cbf6ab7
+Subproject commit aadbf02d6bd7a545edbf6652494a7b07a97a06c1
diff --git a/include/tvm/expr.h b/include/tvm/expr.h
index c0f4fea24bf8..5d35728c24b4 100644
--- a/include/tvm/expr.h
+++ b/include/tvm/expr.h
@@ -16,31 +16,31 @@
 
 namespace tvm {
 
-using Halide::Type;
-using Halide::Float;
-using Halide::Bool;
-using Halide::Int;
-using Halide::UInt;
-using Halide::Handle;
-using Halide::ExprHash;
-using Halide::ExprEqual;
+using HalideIR::Type;
+using HalideIR::Float;
+using HalideIR::Bool;
+using HalideIR::Int;
+using HalideIR::UInt;
+using HalideIR::Handle;
+using HalideIR::ExprHash;
+using HalideIR::ExprEqual;
 
-using Halide::Expr;
-using Halide::VarExpr;
-using Halide::IR::RangeNode;
-using Halide::IR::FunctionRef;
-using Halide::IR::FunctionBaseNode;
-using Halide::Internal::Stmt;
-using Halide::Internal::IRPrinter;
-using Halide::Internal::Variable;
+using HalideIR::Expr;
+using HalideIR::VarExpr;
+using HalideIR::IR::RangeNode;
+using HalideIR::IR::FunctionRef;
+using HalideIR::IR::FunctionBaseNode;
+using HalideIR::Internal::Stmt;
+using HalideIR::Internal::IRPrinter;
+using HalideIR::Internal::Variable;
 
-using Halide::Internal::make_const;
-using Halide::Internal::make_zero;
-using Halide::Internal::as_const_int;
-using Halide::Internal::as_const_uint;
-using Halide::Internal::const_true;
-using Halide::Internal::const_false;
-using Halide::Internal::is_no_op;
+using HalideIR::Internal::make_const;
+using HalideIR::Internal::make_zero;
+using HalideIR::Internal::as_const_int;
+using HalideIR::Internal::as_const_uint;
+using HalideIR::Internal::const_true;
+using HalideIR::Internal::const_false;
+using HalideIR::Internal::is_no_op;
 
 inline Type TVMShapeIndexType() {
   if (std::is_signed<tvm_index_t>::value) {
@@ -51,7 +51,7 @@ inline Type TVMShapeIndexType() {
 }
 
 inline Type TVMType2Type(TVMType t) {
-  return Type(static_cast<halide_type_code_t>(t.code), t.bits, t.lanes);
+  return Type(static_cast<halideir_type_code_t>(t.code), t.bits, t.lanes);
 }
 
 inline TVMType Type2TVMType(Type t) {
@@ -71,7 +71,7 @@ inline int GetVectorBytes(Type dtype) {
 }
 
 /*! \brief a named variable in TVM */
-class Var : public Halide::VarExpr {
+class Var : public HalideIR::VarExpr {
  public:
   explicit Var(const std::string& name_hint = "v",
                Type t = Int(32)) : VarExpr(name_hint, t) {}
@@ -94,7 +94,7 @@ class Var : public Halide::VarExpr {
 class IterVarNode;
 
 /*!
- * \brief same as Halide::IR::Range
+ * \brief same as HalideIR::IR::Range
  *  except it provide an constructor with (begin, end)
  *
  *  \note Traditional Halide's Range have a constructor with
@@ -102,11 +102,11 @@ class IterVarNode;
  *   We decided to correct it by removing the constructor in HalideIR,
  *   and add it back in TVM's range.
  */
-class Range : public Halide::IR::Range {
+class Range : public HalideIR::IR::Range {
  public:
   /*! \brief constructor */
   Range() {}
-  explicit Range(std::shared_ptr<Node> n) : Halide::IR::Range(n) {}
+  explicit Range(std::shared_ptr<Node> n) : HalideIR::IR::Range(n) {}
   /*!
    * \brief constructor by begin and end
    * \param begin The begin of the range.
diff --git a/include/tvm/ir.h b/include/tvm/ir.h
index ae53d38b82b2..5b8b56be7db6 100644
--- a/include/tvm/ir.h
+++ b/include/tvm/ir.h
@@ -16,11 +16,11 @@
 namespace tvm {
 namespace ir {
 
-using Halide::Internal::ExprNode;
-using Halide::Internal::StmtNode;
-using Halide::Internal::IRNodeType;
-using Halide::Internal::ForType;
-using Halide::DeviceAPI;
+using HalideIR::Internal::ExprNode;
+using HalideIR::Internal::StmtNode;
+using HalideIR::Internal::IRNodeType;
+using HalideIR::Internal::ForType;
+using HalideIR::DeviceAPI;
 
 // Node container for CommReducer
 struct CommReducerNode;
@@ -433,50 +433,50 @@ enum TVMStructFieldKind : int {
 }   // namespace intrinsic
 
 // Reuse IR node defintiion from HalideIR
-using Halide::Internal::IntImm;
-using Halide::Internal::UIntImm;
-using Halide::Internal::FloatImm;
-using Halide::Internal::StringImm;
-using Halide::Internal::Cast;
-using Halide::Internal::Add;
-using Halide::Internal::Sub;
-using Halide::Internal::Mul;
-using Halide::Internal::Div;
-using Halide::Internal::Mod;
-using Halide::Internal::Min;
-using Halide::Internal::Max;
-using Halide::Internal::EQ;
-using Halide::Internal::NE;
-using Halide::Internal::LT;
-using Halide::Internal::LE;
-using Halide::Internal::GT;
-using Halide::Internal::GE;
-using Halide::Internal::And;
-using Halide::Internal::Or;
-using Halide::Internal::Not;
-using Halide::Internal::Select;
-using Halide::Internal::Load;
-using Halide::Internal::Ramp;
-using Halide::Internal::Broadcast;
-using Halide::Internal::Call;
-using Halide::Internal::Let;
-using Halide::Internal::LetStmt;
-using Halide::Internal::AttrStmt;
-using Halide::Internal::AssertStmt;
-using Halide::Internal::ProducerConsumer;
-using Halide::Internal::For;
-using Halide::Internal::Store;
-using Halide::Internal::Provide;
-using Halide::Internal::Allocate;
-using Halide::Internal::Free;
-using Halide::Internal::Realize;
-using Halide::Internal::Prefetch;
-using Halide::Internal::Block;
-using Halide::Internal::IfThenElse;
-using Halide::Internal::Evaluate;
-using Halide::Internal::Shuffle;
+using HalideIR::Internal::IntImm;
+using HalideIR::Internal::UIntImm;
+using HalideIR::Internal::FloatImm;
+using HalideIR::Internal::StringImm;
+using HalideIR::Internal::Cast;
+using HalideIR::Internal::Add;
+using HalideIR::Internal::Sub;
+using HalideIR::Internal::Mul;
+using HalideIR::Internal::Div;
+using HalideIR::Internal::Mod;
+using HalideIR::Internal::Min;
+using HalideIR::Internal::Max;
+using HalideIR::Internal::EQ;
+using HalideIR::Internal::NE;
+using HalideIR::Internal::LT;
+using HalideIR::Internal::LE;
+using HalideIR::Internal::GT;
+using HalideIR::Internal::GE;
+using HalideIR::Internal::And;
+using HalideIR::Internal::Or;
+using HalideIR::Internal::Not;
+using HalideIR::Internal::Select;
+using HalideIR::Internal::Load;
+using HalideIR::Internal::Ramp;
+using HalideIR::Internal::Broadcast;
+using HalideIR::Internal::Call;
+using HalideIR::Internal::Let;
+using HalideIR::Internal::LetStmt;
+using HalideIR::Internal::AttrStmt;
+using HalideIR::Internal::AssertStmt;
+using HalideIR::Internal::ProducerConsumer;
+using HalideIR::Internal::For;
+using HalideIR::Internal::Store;
+using HalideIR::Internal::Provide;
+using HalideIR::Internal::Allocate;
+using HalideIR::Internal::Free;
+using HalideIR::Internal::Realize;
+using HalideIR::Internal::Prefetch;
+using HalideIR::Internal::Block;
+using HalideIR::Internal::IfThenElse;
+using HalideIR::Internal::Evaluate;
+using HalideIR::Internal::Shuffle;
 // ir functions
-using Halide::Internal::is_const_power_of_two_integer;
+using HalideIR::Internal::is_const_power_of_two_integer;
 
 }  // namespace ir
 }  // namespace tvm
diff --git a/include/tvm/ir_operator.h b/include/tvm/ir_operator.h
index a0726f0030ab..8b27389db0d8 100644
--- a/include/tvm/ir_operator.h
+++ b/include/tvm/ir_operator.h
@@ -12,14 +12,14 @@
 
 namespace tvm {
 
-using Halide::likely;
-using Halide::likely_if_innermost;
+using HalideIR::likely;
+using HalideIR::likely_if_innermost;
 // functions
-using Halide::cast;
-using Halide::min;
-using Halide::max;
-using Halide::abs;
-using Halide::select;
+using HalideIR::cast;
+using HalideIR::min;
+using HalideIR::max;
+using HalideIR::abs;
+using HalideIR::select;
 
 /*!
  * \brief sum of of source expression over axis
diff --git a/include/tvm/packed_func_ext.h b/include/tvm/packed_func_ext.h
index 542de6a368b5..a598286001a3 100644
--- a/include/tvm/packed_func_ext.h
+++ b/include/tvm/packed_func_ext.h
@@ -104,7 +104,7 @@ inline TNodeRef TVMArgValue::AsNodeRef() const {
   return TNodeRef(sptr);
 }
 
-inline TVMArgValue::operator Halide::Expr() const {
+inline TVMArgValue::operator HalideIR::Expr() const {
   if (type_code_ == kNull) return Expr();
   if (type_code_ == kDLInt) {
     return Expr(static_cast<int>(value_.v_int64));
@@ -184,20 +184,20 @@ inline void TVMArgsSetter::operator()(size_t i, const NodeRef& other) const {  /
 }
 
 // type related stuffs
-inline TVMRetValue& TVMRetValue::operator=(const Halide::Type& t) {
+inline TVMRetValue& TVMRetValue::operator=(const HalideIR::Type& t) {
   return this->operator=(Type2TVMType(t));
 }
 
-inline TVMRetValue::operator Halide::Type() const {
+inline TVMRetValue::operator HalideIR::Type() const {
   return TVMType2Type(operator TVMType());
 }
 
-inline TVMArgValue::operator Halide::Type() const {
+inline TVMArgValue::operator HalideIR::Type() const {
   return TVMType2Type(operator TVMType());
 }
 
 inline void TVMArgsSetter::operator()(
-    size_t i, const Halide::Type& t) const {
+    size_t i, const HalideIR::Type& t) const {
   this->operator()(i, Type2TVMType(t));
 }
 }  // namespace runtime
diff --git a/include/tvm/runtime/packed_func.h b/include/tvm/runtime/packed_func.h
index 4c08ffa6077c..b01e662b97ab 100644
--- a/include/tvm/runtime/packed_func.h
+++ b/include/tvm/runtime/packed_func.h
@@ -17,7 +17,7 @@
 #include "./c_runtime_api.h"
 #include "./module.h"
 
-namespace Halide {
+namespace HalideIR {
 // Forward declare type for extensions
 // The header works fine without depending on this.
 struct Type;
@@ -351,8 +351,8 @@ class TVMArgValue : public TVMPODValue_ {
            typename = typename std::enable_if<
              std::is_class<TNodeRef>::value>::type>
   inline bool IsNodeType() const;
-  inline operator Halide::Type() const;
-  inline operator Halide::Expr() const;
+  inline operator HalideIR::Type() const;
+  inline operator HalideIR::Expr() const;
   // get internal node ptr, if it is node
   inline std::shared_ptr<Node>& node_sptr();
 };
@@ -531,8 +531,8 @@ class TVMRetValue : public TVMPODValue_ {
   inline TVMRetValue& operator=(const NodeRef& other);
   inline TVMRetValue& operator=(const std::shared_ptr<Node>& other);
   // type related
-  inline operator Halide::Type() const;
-  inline TVMRetValue& operator=(const Halide::Type& other);
+  inline operator HalideIR::Type() const;
+  inline TVMRetValue& operator=(const HalideIR::Type& other);
 
  private:
   template<typename T>
@@ -800,7 +800,7 @@ class TVMArgsSetter {
   inline void operator()(size_t i, const T& value) const;
   // NodeRef related extenstions: in tvm/packed_func_ext.h
   inline void operator()(size_t i, const NodeRef& other) const;  // NOLINT(*)
-  inline void operator()(size_t i, const Halide::Type& t) const;
+  inline void operator()(size_t i, const HalideIR::Type& t) const;
 
  private:
   /*! \brief The values fields */
diff --git a/include/tvm/tensor.h b/include/tvm/tensor.h
index 4f46d86e93a5..0a74f97c0640 100644
--- a/include/tvm/tensor.h
+++ b/include/tvm/tensor.h
@@ -23,7 +23,7 @@ class TensorNode;
 // internal node container for Operation
 class OperationNode;
 
-using Halide::IR::FunctionRef;
+using HalideIR::IR::FunctionRef;
 
 /*!
  * \brief Tensor structure representing a possible input,
diff --git a/src/api/api_ir.cc b/src/api/api_ir.cc
index 0a26af0ca43c..d0847aceceb3 100644
--- a/src/api/api_ir.cc
+++ b/src/api/api_ir.cc
@@ -27,7 +27,7 @@ TVM_REGISTER_API("make.For")
                      args[1],
                      args[2],
                      static_cast<ForType>(args[3].operator int()),
-                     static_cast<Halide::DeviceAPI>(args[4].operator int()),
+                     static_cast<HalideIR::DeviceAPI>(args[4].operator int()),
                      args[5]);
   });
 
diff --git a/src/arithmetic/bound_deducer.cc b/src/arithmetic/bound_deducer.cc
index e09834923c37..7a8c841025fa 100644
--- a/src/arithmetic/bound_deducer.cc
+++ b/src/arithmetic/bound_deducer.cc
@@ -16,7 +16,7 @@ namespace tvm {
 namespace arith {
 
 using namespace ir;
-using Halide::Internal::Interval;
+using HalideIR::Internal::Interval;
 
 // a visitor to find the path to the target variable
 // from a expression.
diff --git a/src/arithmetic/canonical.cc b/src/arithmetic/canonical.cc
index 473e330de735..27ccfb09cdeb 100644
--- a/src/arithmetic/canonical.cc
+++ b/src/arithmetic/canonical.cc
@@ -626,7 +626,7 @@ Expr CanonicalSimplify(Expr expr, Map<Var, Range> vrange) {
 
 template<typename T>
 T Simplify_(T a, Map<Var, Range> vrange) {
-  using namespace Halide::Internal;
+  using namespace HalideIR::Internal;
   Scope<Interval> rscope;
   for (auto kv : vrange) {
     Range r = kv.second;
@@ -635,7 +635,7 @@ T Simplify_(T a, Map<Var, Range> vrange) {
         Interval(r->min,
                  simplify(r->min + r->extent - make_const(r->min.type(), 1))));
   }
-  return Halide::Internal::simplify(a, true, rscope);
+  return HalideIR::Internal::simplify(a, true, rscope);
 }
 
 
diff --git a/src/arithmetic/compute_expr.h b/src/arithmetic/compute_expr.h
index 994bcb13eadc..5f44347f3539 100644
--- a/src/arithmetic/compute_expr.h
+++ b/src/arithmetic/compute_expr.h
@@ -14,9 +14,9 @@
 namespace tvm {
 namespace arith {
 
-using Halide::Internal::add_would_overflow;
-using Halide::Internal::sub_would_overflow;
-using Halide::Internal::mul_would_overflow;
+using HalideIR::Internal::add_would_overflow;
+using HalideIR::Internal::sub_would_overflow;
+using HalideIR::Internal::mul_would_overflow;
 
 /*!
  * \brief Compute the expression with the given binary op.
@@ -133,12 +133,12 @@ inline Expr ComputeExpr<ir::Mod>(Expr a, Expr b) {
 
 template<>
 inline Expr ComputeExpr<ir::Max>(Expr a, Expr b) {
-  return Halide::Internal::Interval::make_max(a, b);
+  return HalideIR::Internal::Interval::make_max(a, b);
 }
 
 template<>
 inline Expr ComputeExpr<ir::Min>(Expr a, Expr b) {
-  return Halide::Internal::Interval::make_min(a, b);
+  return HalideIR::Internal::Interval::make_min(a, b);
 }
 
 template<typename Op>
diff --git a/src/arithmetic/int_set.cc b/src/arithmetic/int_set.cc
index b19aa4691e8b..c004b9666a58 100644
--- a/src/arithmetic/int_set.cc
+++ b/src/arithmetic/int_set.cc
@@ -15,7 +15,7 @@
 namespace tvm {
 namespace arith {
 
-using Halide::Internal::Interval;
+using HalideIR::Internal::Interval;
 using namespace ir;
 
 inline IntSet IntSet::cover_interval() const {
diff --git a/src/arithmetic/int_set_internal.h b/src/arithmetic/int_set_internal.h
index fca4b819905f..9284e6e016e0 100644
--- a/src/arithmetic/int_set_internal.h
+++ b/src/arithmetic/int_set_internal.h
@@ -13,7 +13,7 @@
 namespace tvm {
 namespace arith {
 
-using Halide::Internal::Interval;
+using HalideIR::Internal::Interval;
 
 /*! \brief Set of continuous interval */
 struct IntervalSet : public IntSetNode {
diff --git a/src/lang/expr.cc b/src/lang/expr.cc
index be83b521ed8e..0fb783d70cb8 100644
--- a/src/lang/expr.cc
+++ b/src/lang/expr.cc
@@ -10,7 +10,7 @@
 
 namespace tvm {
 
-using Halide::IR::RangeNode;
+using HalideIR::IR::RangeNode;
 
 Range::Range(Expr begin, Expr end)
     : Range(std::make_shared<RangeNode>(
@@ -19,7 +19,7 @@ Range::Range(Expr begin, Expr end)
 }
 
 Range Range::make_by_min_extent(Expr min, Expr extent) {
-  return Range(std::make_shared<Halide::IR::RangeNode>(min, extent));
+  return Range(std::make_shared<HalideIR::IR::RangeNode>(min, extent));
 }
 
 IterVar IterVarNode::make(Range dom, Var var,
@@ -67,7 +67,7 @@ TVM_STATIC_IR_FUNCTOR(IRPrinter, vtable)
   });
 
 TVM_STATIC_IR_FUNCTOR(IRPrinter, vtable)
-.set_dispatch<RangeNode>([](const Halide::IR::RangeNode *op, IRPrinter *p) {
+.set_dispatch<RangeNode>([](const HalideIR::IR::RangeNode *op, IRPrinter *p) {
     p->stream << "range(min=" << op->min << ", ext=" << op->extent << ')';
   });
 
diff --git a/src/lang/ir.cc b/src/lang/ir.cc
index 776f1f2368f5..1e0a6e5065f4 100644
--- a/src/lang/ir.cc
+++ b/src/lang/ir.cc
@@ -11,7 +11,7 @@
 #include <memory>
 #include "../pass/ir_util.h"
 
-namespace Halide {
+namespace HalideIR {
 namespace Internal {
 
 using tvm::ir::CommReducerNode;
@@ -43,7 +43,7 @@ TVM_STATIC_IR_FUNCTOR(IRPrinter, vtable)
             << ")";
 });
 }  // namespace Internal
-}  // namespace Halide
+}  // namespace HalideIR
 
 namespace tvm {
 namespace ir {
diff --git a/src/lang/reflection.cc b/src/lang/reflection.cc
index f40e399ef911..a8152d214d1f 100644
--- a/src/lang/reflection.cc
+++ b/src/lang/reflection.cc
@@ -25,7 +25,7 @@ inline std::string Type2String(const Type& t) {
 
 inline Type String2Type(std::string s) {
   std::istringstream is(s);
-  halide_type_code_t code = Type::Int;
+  halideir_type_code_t code = Type::Int;
   if (s.substr(0, 3) == "int") {
     code = Type::Int; s = s.substr(3);
   } else if (s.substr(0, 4) == "uint") {
diff --git a/src/lang/tensor.cc b/src/lang/tensor.cc
index 5ed36287c54c..5db4f45e799f 100644
--- a/src/lang/tensor.cc
+++ b/src/lang/tensor.cc
@@ -16,7 +16,7 @@ Expr Tensor::operator()(Array<Var> indices) const {
 }
 
 Expr Tensor::operator()(Array<Expr> indices) const {
-  using Halide::Internal::Call;
+  using HalideIR::Internal::Call;
   CHECK_EQ(ndim(), indices.size())
       << "Tensor dimension mismatch in read"
       << "ndim = " << ndim() << ", indices.size=" << indices.size();
diff --git a/src/op/compute_op.cc b/src/op/compute_op.cc
index 11731361148d..9e7db1deea45 100644
--- a/src/op/compute_op.cc
+++ b/src/op/compute_op.cc
@@ -228,7 +228,7 @@ Stmt ComputeOpNode::BuildRealize(
     const std::unordered_map<IterVar, Range>& realize_map,
     const Stmt& realize_body) const {
   CHECK_EQ(stage->op.get(), this);
-  Halide::Internal::Region bounds;
+  HalideIR::Internal::Region bounds;
   for (IterVar iv : this->axis) {
     bounds.push_back(realize_map.at(iv));
   }
diff --git a/src/op/extern_op.cc b/src/op/extern_op.cc
index 68a51df32616..9b302f6e2504 100644
--- a/src/op/extern_op.cc
+++ b/src/op/extern_op.cc
@@ -113,7 +113,7 @@ Stmt ExternOpNode::BuildRealize(
   Stmt realize_body = body;
   for (int k = 0; k < num_outputs(); ++k) {
     Tensor t = stage->op.output(k);
-    Halide::Internal::Region bounds;
+    HalideIR::Internal::Region bounds;
     for (size_t i = 0; i < t->shape.size(); ++i) {
       bounds.push_back(
           Range::make_by_min_extent(
diff --git a/src/op/scan_op.cc b/src/op/scan_op.cc
index 48565b6eb6df..94e3a4aa6586 100644
--- a/src/op/scan_op.cc
+++ b/src/op/scan_op.cc
@@ -238,7 +238,7 @@ Stmt ScanOpNode::BuildRealize(
   for (size_t i = 0; i < update.size(); ++i) {
     Tensor t = stage->op.output(i);
     CHECK_EQ(static_cast<size_t>(t->value_index), i);
-    Halide::Internal::Region bounds;
+    HalideIR::Internal::Region bounds;
     bounds.push_back(tdom);
     for (size_t k = 1; k < this->update[i]->shape.size(); ++k, ++sp_idx) {
       IterVar sp_ax = this->spatial_axis_[sp_idx];
diff --git a/src/pass/inject_prefetch.cc b/src/pass/inject_prefetch.cc
index 6d903292a004..2749cb6a2db3 100644
--- a/src/pass/inject_prefetch.cc
+++ b/src/pass/inject_prefetch.cc
@@ -15,7 +15,7 @@ namespace ir {
 
 using arith::IntSet;
 using arith::DomainTouched;
-using Halide::Internal::Region;
+using HalideIR::Internal::Region;
 
 class PrefetchInjector : public IRMutator {
  public:
diff --git a/src/pass/ir_deep_compare.cc b/src/pass/ir_deep_compare.cc
index 9bb764129c36..8a1b09e49339 100644
--- a/src/pass/ir_deep_compare.cc
+++ b/src/pass/ir_deep_compare.cc
@@ -328,8 +328,8 @@ class IRDeepCompare :
     return order_;
   }
 
-  int CompareRegion(const Halide::Internal::Region& lhs,
-                    const Halide::Internal::Region& rhs) {
+  int CompareRegion(const HalideIR::Internal::Region& lhs,
+                    const HalideIR::Internal::Region& rhs) {
     if (order_ != 0) return order_;
     if (CompareValue(lhs.size(), rhs.size()) != 0) return order_;
     for (size_t i = 0; i < lhs.size(); ++i) {
diff --git a/src/pass/ir_mutator.cc b/src/pass/ir_mutator.cc
index 993b68f835d7..9ca9ccd190ff 100644
--- a/src/pass/ir_mutator.cc
+++ b/src/pass/ir_mutator.cc
@@ -206,7 +206,7 @@ Stmt IRMutator::Mutate_(const Provide* op, const Stmt& s) {
 
 Stmt IRMutator::Mutate_(const Realize* op, const Stmt& s) {
   IRMutator* m = this;
-  Halide::Internal::Region new_bounds;
+  HalideIR::Internal::Region new_bounds;
   bool bounds_changed = false;
 
   // Mutate the bounds
@@ -236,7 +236,7 @@ Stmt IRMutator::Mutate_(const Realize* op, const Stmt& s) {
 
 Stmt IRMutator::Mutate_(const Prefetch* op, const Stmt& s) {
   IRMutator* m = this;
-  Halide::Internal::Region new_bounds;
+  HalideIR::Internal::Region new_bounds;
   bool bounds_changed = false;
 
   // Mutate the bounds
diff --git a/src/pass/storage_flatten.cc b/src/pass/storage_flatten.cc
index 46bed1fc9382..45ad081f86cb 100644
--- a/src/pass/storage_flatten.cc
+++ b/src/pass/storage_flatten.cc
@@ -22,7 +22,7 @@
 namespace tvm {
 namespace ir {
 
-using Halide::Internal::Region;
+using HalideIR::Internal::Region;
 using runtime::StorageScope;
 using runtime::ThreadScope;
 using intrinsic::tvm_address_of;
diff --git a/src/schedule/message_passing.cc b/src/schedule/message_passing.cc
index 969a18ee9469..a144e7fc40d1 100644
--- a/src/schedule/message_passing.cc
+++ b/src/schedule/message_passing.cc
@@ -407,7 +407,7 @@ void PassUpBoundCheck(const Stage& s,
                       const Map<IterVar, Range>& dom_map,
                       std::unordered_map<IterVar, bool>* p_state) {
   auto& state = *p_state;
-  using Halide::Internal::can_prove;
+  using HalideIR::Internal::can_prove;
   for (size_t i = s->relations.size(); i != 0; --i) {
     IterVarRelation rel = s->relations[i - 1];
     if (rel.as<SplitNode>()) {
diff --git a/tests/cpp/ir_mutator_test.cc b/tests/cpp/ir_mutator_test.cc
index 7a0739950bb5..fd5a60756f1c 100644
--- a/tests/cpp/ir_mutator_test.cc
+++ b/tests/cpp/ir_mutator_test.cc
@@ -4,8 +4,8 @@
 
 namespace {
 using namespace tvm::ir;
-using namespace Halide::Internal;
-using namespace Halide;
+using namespace HalideIR::Internal;
+using namespace HalideIR;
 
 // replace variable to constant
 class IRVar2Const : public IRMutator {
@@ -38,7 +38,7 @@ TVM_STATIC_IR_FUNCTOR(IRVar2Const, vtable_expr)
 }  // namespace
 
 TEST(IRMutator, Basic) {
-  using namespace Halide::Internal;
+  using namespace HalideIR::Internal;
   using namespace tvm;
   Var x("x"), y;
   auto z = x + y;
diff --git a/tests/cpp/ir_simplify_test.cc b/tests/cpp/ir_simplify_test.cc
index e963bc9e991b..0667dc27367c 100644
--- a/tests/cpp/ir_simplify_test.cc
+++ b/tests/cpp/ir_simplify_test.cc
@@ -4,7 +4,7 @@
 #include <arithmetic/Simplify.h>
 
 TEST(IRSIMPLIFY, Basic) {
-  using namespace Halide::Internal;
+  using namespace HalideIR::Internal;
   simplify_test();
 }
 
diff --git a/tests/cpp/ir_ssa_test.cc b/tests/cpp/ir_ssa_test.cc
index 2de7dba080a3..97251eb5eeeb 100644
--- a/tests/cpp/ir_ssa_test.cc
+++ b/tests/cpp/ir_ssa_test.cc
@@ -5,7 +5,7 @@
 
 
 TEST(IRSSA, Convert) {
-  using namespace Halide::Internal;
+  using namespace HalideIR::Internal;
   using namespace tvm;
   Var x("x"), y;
   Expr let = Let::make(x, 1, x + 1);
@@ -17,7 +17,7 @@ TEST(IRSSA, Convert) {
 }
 
 TEST(IRSSA, Basic) {
-  using namespace Halide::Internal;
+  using namespace HalideIR::Internal;
   using namespace tvm;
   Var x("x"), y;
   auto z = Evaluate::make(x + y);
diff --git a/tests/cpp/ir_visitor_test.cc b/tests/cpp/ir_visitor_test.cc
index 0a649a09304c..930b0a273143 100644
--- a/tests/cpp/ir_visitor_test.cc
+++ b/tests/cpp/ir_visitor_test.cc
@@ -5,7 +5,7 @@
 #include <tvm/ir_pass.h>
 
 TEST(IRVisitor, CountVar) {
-  using namespace Halide::Internal;
+  using namespace HalideIR::Internal;
   using namespace tvm;
   int n_var = 0;
   Var x("x"), y;

From 60bf936ef04652762af69e65b4eb8b1de15e99a9 Mon Sep 17 00:00:00 2001
From: Salem Derisavi <33945117+derisavi-huawei@users.noreply.github.com>
Date: Mon, 18 Dec 2017 19:21:38 -0500
Subject: [PATCH 049/949] removed non-determinism from CanonicalSimplify (#704)

* 1) removed non-determinism from CanonicalSimplify
2) added couple of testcases for CanonicalSimplify

* Use IRDeepCompare instead of comparison of string representation

* Give a warning (instead of fatal error) when two "ComExprEntry"s are equal
---
 src/arithmetic/canonical.cc                 | 10 +++++++++-
 tests/python/unittest/test_pass_simplify.py | 10 ++++++++++
 2 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/src/arithmetic/canonical.cc b/src/arithmetic/canonical.cc
index 27ccfb09cdeb..e219b5541bdc 100644
--- a/src/arithmetic/canonical.cc
+++ b/src/arithmetic/canonical.cc
@@ -29,9 +29,17 @@ struct ComExprEntry {
   inline bool operator<(const ComExprEntry& other) const {
     if (level < other.level) return true;
     if (level > other.level) return false;
+    // compare top operator of entries and sort on that if possible (fast check)
     if (value.type_index() < other.value.type_index()) return true;
     if (value.type_index() > other.value.type_index()) return false;
-    return value.get() < other.value.get();
+    // if none of the above distinguishes the terms, compare the expression tree of the entries.
+    // This is a slower check.
+    int compare_result = Compare(value, other.value);
+    if (compare_result < 0) return true;
+    if (compare_result > 0) return false;
+    // it's a problem if we see identical entries at this point. They should've been merged earlier.
+    LOG(WARNING) << "we should not have identical entries at this point";
+    return false;
   }
 };
 
diff --git a/tests/python/unittest/test_pass_simplify.py b/tests/python/unittest/test_pass_simplify.py
index 9105693b3835..29b5b3a8450d 100644
--- a/tests/python/unittest/test_pass_simplify.py
+++ b/tests/python/unittest/test_pass_simplify.py
@@ -43,6 +43,16 @@ def test_canonical():
     ret = tvm.ir_pass.CanonicalSimplify(x / (z+z) - x / (z+z))
     assert(tvm.ir_pass.Equal(ret, 0))
 
+    #make sure terms are ordered based on their top operators (e.g., / always precedes %)
+    ret1 = tvm.ir_pass.CanonicalSimplify(x % 3 + x / 3)
+    ret2 = tvm.ir_pass.CanonicalSimplify(x / 3 + x % 3)
+    assert(tvm.ir_pass.Equal(ret1, ret2))
+
+    #when top operators match, compare string representation of terms
+    ret1 = tvm.ir_pass.CanonicalSimplify(x % 4 + x % 3)
+    ret2 = tvm.ir_pass.CanonicalSimplify(x % 3 + x % 4)
+    assert (tvm.ir_pass.Equal(ret1, ret2))
+
 if __name__ == "__main__":
     test_bound()
     test_basic()

From befb252980fe41c0909e7339a45eeec4f8b376ed Mon Sep 17 00:00:00 2001
From: Salem Derisavi <33945117+derisavi-huawei@users.noreply.github.com>
Date: Fri, 22 Dec 2017 04:31:53 -0500
Subject: [PATCH 050/949] During tensorize, call Simplify on algorithm and
 intrinsic definitions before CanonicalSimplify. This will prevent a number of
 false tensorize mismatches. (#718)

thanks, this we can use this solution for now
---
 src/arithmetic/canonical.cc | 18 ++++++++++++++++++
 src/op/tensorize.cc         |  6 ++++--
 2 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/src/arithmetic/canonical.cc b/src/arithmetic/canonical.cc
index e219b5541bdc..736b8dad78c7 100644
--- a/src/arithmetic/canonical.cc
+++ b/src/arithmetic/canonical.cc
@@ -648,6 +648,24 @@ T Simplify_(T a, Map<Var, Range> vrange) {
 
 
 Expr Simplify(Expr a, Map<Var, Range> vrange) {
+  // We should not pass an expression having a non-HalideIR op to
+  // Halide::Internal::simplify. Reduce op is the only such op at this time
+  // and it only appears as the top op in an expression. So we strip it
+  // first and send the sub-expressions to the simplifier.
+  if (const Reduce* r = a.as<Reduce>()) {
+    Array<Expr> new_source;
+    for (auto& e : r->source) {
+      new_source.push_back(Simplify_(e, vrange));
+    }
+    Expr new_condition = Simplify_(r->condition, vrange);
+    if (r->source.same_as(new_source) &&
+        r->condition.same_as(new_condition)) {
+      return a;
+    } else {
+      return Reduce::make(
+              r->combiner, new_source, r->axis, new_condition, r->value_index);
+    }
+  }
   return Simplify_(a, vrange);
 }
 
diff --git a/src/op/tensorize.cc b/src/op/tensorize.cc
index 243b7931da67..b4527f76e808 100644
--- a/src/op/tensorize.cc
+++ b/src/op/tensorize.cc
@@ -303,8 +303,10 @@ void VerifyTensorizeBody(
   CHECK_EQ(body.size(), intrin_compute->body.size())
       << "Tensorize failed: body size mismatch";
   for (size_t i = 0; i < body.size(); ++i) {
-    Expr lhs = CanonicalSimplify(body[i], compute_intrin_iter_space);
-    Expr rhs = CanonicalSimplify(intrin_compute->body[i], compute_intrin_iter_space);
+    Expr lhs = Simplify(body[i], compute_intrin_iter_space);
+    lhs = CanonicalSimplify(lhs, compute_intrin_iter_space);
+    Expr rhs = Simplify(intrin_compute->body[i], compute_intrin_iter_space);
+    rhs = CanonicalSimplify(rhs, compute_intrin_iter_space);
     if (lhs.type() != rhs.type()) {
       LOG(FATAL)
           << "Failed to match the data type with TensorIntrin "

From b3de356f1bf0f268f870703cfc5ef02150d58180 Mon Sep 17 00:00:00 2001
From: Salem Derisavi <33945117+derisavi-huawei@users.noreply.github.com>
Date: Fri, 22 Dec 2017 21:40:51 -0500
Subject: [PATCH 051/949] Added a regression test for #696 (#720)

---
 .../unittest/test_schedule_tensorize.py       | 27 +++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/tests/python/unittest/test_schedule_tensorize.py b/tests/python/unittest/test_schedule_tensorize.py
index d8553c025e2d..71ae493e51ae 100644
--- a/tests/python/unittest/test_schedule_tensorize.py
+++ b/tests/python/unittest/test_schedule_tensorize.py
@@ -126,7 +126,34 @@ def check_rfactor(factor, rfactor):
     check(16)
     check_rfactor(16, 16)
 
+# This tests whether algorithm and intrinsics expressions are simplified
+# as much as possible first and then checked for equality. See Issue #696
+def test_tensorize_op():
+    def op_intrin():
+        bh = 9
+        bw = 9
+        x = tvm.placeholder((5, 5), name='A')
+        y = tvm.compute((bh, bw), lambda i,j: x[j/3 + i%3, j%3+ i/3])
+
+        def intrin_func(ins, outs):
+            xx, = ins
+            zz = outs[0]
+            return tvm.call_packed("op", xx, zz)
+
+        with tvm.build_config(offset_factor=2):
+            return tvm.decl_tensor_intrin(y.op, intrin_func)
+
+    A = tvm.placeholder((5, 5), name='A')
+    B = tvm.compute((9,9), lambda i, j: A[j/3 + i%3, j%3 + i/3])
+    bt = op_intrin()
+    s = tvm.create_schedule(B.op)
+
+    x,y = B.op.axis
+    s[B].tensorize(x, bt)
+    s = s.normalize()
+    tvm.lower(s, [A, B])
 
 if __name__ == "__main__":
     test_tensorize_vadd()
     test_tensorize_matmul()
+    test_tensorize_op()

From 632d7b1ffced065cb5f15f6eb0582670cad624eb Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 23 Dec 2017 11:27:02 +0800
Subject: [PATCH 052/949] Update metal_module.mm

---
 src/runtime/metal/metal_module.mm | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/runtime/metal/metal_module.mm b/src/runtime/metal/metal_module.mm
index 76926f75bd63..bf7f606c952d 100644
--- a/src/runtime/metal/metal_module.mm
+++ b/src/runtime/metal/metal_module.mm
@@ -22,7 +22,6 @@
 namespace runtime {
 
 // Module to support thread-safe multi-GPU execution.
-// cuModule is a per-GPU module
 // The runtime will contain a per-device module table
 // The modules will be lazily loaded
 class MetalModuleNode final :public runtime::ModuleNode {

From e11b0e8a7268c76534305f5083b7b12902736124 Mon Sep 17 00:00:00 2001
From: Cody Hao Yu <comaniac0422@gmail.com>
Date: Fri, 22 Dec 2017 23:20:16 -0800
Subject: [PATCH 053/949] Fix dependency problem of reducer condition (#712)
 (#721)

* Make duplicated function name checker working

* Fix dependency checking problem for reducer condition (#712); add test

* Fix dependency checking problem for reducer condition (#712); add test

* Specify R to be computed inlined
---
 .gitignore                              | 1 +
 src/pass/ir_visitor.cc                  | 1 +
 tests/python/integration/test_reduce.py | 4 +++-
 3 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/.gitignore b/.gitignore
index f59a58552f8d..b645f6693b09 100644
--- a/.gitignore
+++ b/.gitignore
@@ -138,6 +138,7 @@ xcuserdata/
 *.xcscmblueprint
 .DS_Store
 tags
+cscope*
 
 # vim temporary files
 *.swp
diff --git a/src/pass/ir_visitor.cc b/src/pass/ir_visitor.cc
index 27add40f2bd9..12bad2bd5e86 100644
--- a/src/pass/ir_visitor.cc
+++ b/src/pass/ir_visitor.cc
@@ -134,6 +134,7 @@ DEFINE_BINOP_VISIT_(Or)
 void IRVisitor::Visit_(const Reduce* op) {
   VisitRDom(op->axis, this);
   VisitArray(op->source, this);
+  this->Visit(op->condition);
 }
 
 void IRVisitor::Visit_(const Cast* op) {
diff --git a/tests/python/integration/test_reduce.py b/tests/python/integration/test_reduce.py
index 8f2f053c9138..95c8bc6f24e8 100644
--- a/tests/python/integration/test_reduce.py
+++ b/tests/python/integration/test_reduce.py
@@ -7,8 +7,9 @@ def test_prim(reducer, np_reducer):
         n = tvm.var('n')
         m = tvm.var('m')
         A = tvm.placeholder((n, m), name='A')
+        R = tvm.compute((n, ), lambda i: tvm.select((i > 1), 1, 0), name='R')
         k = tvm.reduce_axis((0, m))
-        B = tvm.compute((n,), lambda i: reducer(A[i, k], axis=k, where=(i>1)), name='B')
+        B = tvm.compute((n,), lambda i: reducer(A[i, k], axis=k, where=(R[i]==1)), name='B')
         # schedule
         s = tvm.create_schedule(B.op)
         # create iter var and assign them tags.
@@ -16,6 +17,7 @@ def test_prim(reducer, np_reducer):
         xo, xi = s[B].split(B.op.axis[0], factor=num_thread)
         s[B].bind(xo, tvm.thread_axis("blockIdx.x"))
         s[B].bind(xi, tvm.thread_axis("threadIdx.x"))
+        s[R].compute_inline()
 
         # one line to build the function.
         def check_device(device, host="stackvm"):

From 3a000c6c3a3b67e54660bd986e22df27858dd047 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 24 Dec 2017 18:06:01 +0800
Subject: [PATCH 054/949] [CODEGEN] enable static handle cache (#723)

---
 include/tvm/ir.h                              |  5 ++++
 python/tvm/intrin.py                          | 27 +++++++++++++++++++
 src/codegen/llvm/codegen_cpu.cc               | 19 ++++++++-----
 src/codegen/llvm/codegen_cpu.h                |  1 +
 .../unittest/test_codegen_static_init.py      | 26 +++++++++++++++++-
 5 files changed, 71 insertions(+), 7 deletions(-)

diff --git a/include/tvm/ir.h b/include/tvm/ir.h
index 5b8b56be7db6..95e01382dd98 100644
--- a/include/tvm/ir.h
+++ b/include/tvm/ir.h
@@ -256,6 +256,11 @@ constexpr const char* tvm_if_then_else = "tvm_if_then_else";
  *  }
  */
 constexpr const char* tvm_access_ptr = "tvm_access_ptr";
+/*!
+ * \brief Create a function local static handle that iniitalizes to nullptr.
+ *  can be used to cache function local static resources.
+ */
+constexpr const char* tvm_static_handle = "tvm_static_handle";
 /*!
  * \brief Return a unique context id, used for hint of workspace separation.
  *  Different context id ganrantees not having overlapping workspace.
diff --git a/python/tvm/intrin.py b/python/tvm/intrin.py
index e13a1162bb0b..f8f65e25aa68 100644
--- a/python/tvm/intrin.py
+++ b/python/tvm/intrin.py
@@ -80,6 +80,33 @@ def call_pure_intrin(dtype, func_name, *args):
         dtype, func_name, convert(args), _Call.PureIntrinsic, None, 0)
 
 
+def call_intrin(dtype, func_name, *args):
+    """Build expression by calling an intrinsic function.
+
+    Intrinsics can be overloaded with multiple data types via
+    the intrinsic translation rule.
+
+    Parameters
+    ----------
+    dtype : str
+        The data type of the result.
+
+    func_name: str
+        The intrinsic function name.
+
+    args : list
+        Positional arguments.
+
+    Returns
+    -------
+    call : Expr
+        The call expression.
+    """
+    args = convert(args)
+    return _make.Call(
+        dtype, func_name, convert(args), _Call.Intrinsic, None, 0)
+
+
 def call_pure_extern(dtype, func_name, *args):
     """Build expression by calling a pure extern function.
 
diff --git a/src/codegen/llvm/codegen_cpu.cc b/src/codegen/llvm/codegen_cpu.cc
index ea5d90bccc1f..876d1e12d8a2 100644
--- a/src/codegen/llvm/codegen_cpu.cc
+++ b/src/codegen/llvm/codegen_cpu.cc
@@ -419,6 +419,16 @@ void CodeGenCPU::CreateParallelLaunch(const Stmt& body, int num_task) {
   builder_->SetInsertPoint(par_launch_end);
 }
 
+llvm::Value* CodeGenCPU::CreateStaticHandle() {
+  llvm::GlobalVariable* gv = new llvm::GlobalVariable(
+      *module_, t_void_p_, false,
+      llvm::GlobalValue::PrivateLinkage, 0,
+      "__tvm_static_handle");
+  gv->setAlignment(data_layout_->getTypeAllocSize(t_void_p_));
+  gv->setInitializer(llvm::Constant::getNullValue(t_void_p_));
+  return gv;
+}
+
 void CodeGenCPU::CreateStaticInit(const std::string& init_fname, const Stmt& body) {
   using llvm::BasicBlock;
   // closure data
@@ -426,12 +436,7 @@ void CodeGenCPU::CreateStaticInit(const std::string& init_fname, const Stmt& bod
       ftype_tvm_static_init_callback_,
       llvm::Function::PrivateLinkage,
       "__tvm_static_init_lambda", module_.get());
-  llvm::GlobalVariable* gv = new llvm::GlobalVariable(
-      *module_, t_void_p_, false,
-      llvm::GlobalValue::PrivateLinkage, 0,
-      "__tvm_static_handle");
-  gv->setAlignment(data_layout_->getTypeAllocSize(t_void_p_));
-  gv->setInitializer(llvm::Constant::getNullValue(t_void_p_));
+  llvm::Value* gv = CreateStaticHandle();
   llvm::Function* finit = module_->getFunction(init_fname);
   if (finit == nullptr) {
     finit = llvm::Function::Create(
@@ -599,6 +604,8 @@ void CodeGenCPU::AddStartupFunction() {
 llvm::Value* CodeGenCPU::CreateIntrinsic(const Call* op) {
   if (op->is_intrinsic(intrinsic::tvm_call_packed_lowered)) {
     return CreateCallPacked(op);
+  } else if (op->is_intrinsic(intrinsic::tvm_static_handle)) {
+    return CreateStaticHandle();
   } else if (op->is_intrinsic(intrinsic::tvm_throw_last_error)) {
     builder_->CreateRet(ConstInt32(-1));
     return ConstInt32(-1);
diff --git a/src/codegen/llvm/codegen_cpu.h b/src/codegen/llvm/codegen_cpu.h
index 702d8777d50b..5027dab911bd 100644
--- a/src/codegen/llvm/codegen_cpu.h
+++ b/src/codegen/llvm/codegen_cpu.h
@@ -72,6 +72,7 @@ class CodeGenCPU : public CodeGenLLVM {
   llvm::Value* RuntimeTVMAPISetLastError();
   llvm::Value* RuntimeTVMParallelLaunch();
   llvm::Value* RuntimeTVMParallelBarrier();
+  llvm::Value* CreateStaticHandle();
   llvm::Value* GetPackedFuncHandle(const std::string& str);
   llvm::Value* PackClosureData(const Array<Var>& fields, uint64_t *num_bytes);
   llvm::Value* CreateStructRefPtr(Type t, llvm::Value* buffer, llvm::Value* index, int kind);
diff --git a/tests/python/unittest/test_codegen_static_init.py b/tests/python/unittest/test_codegen_static_init.py
index ecb7d82df6cd..1a03de9ee000 100644
--- a/tests/python/unittest/test_codegen_static_init.py
+++ b/tests/python/unittest/test_codegen_static_init.py
@@ -1,7 +1,8 @@
 import tvm
+import ctypes
 import numpy as np
 
-def test_static_init():
+def test_static_callback():
     dtype = 'int64'
     n = tvm.var('n')
     Ab = tvm.decl_buffer((n, ), dtype)
@@ -22,6 +23,29 @@ def test_static_init():
     f(a)
     np.testing.assert_equal(a.asnumpy(), np.ones(a.shape[0]))
 
+def test_static_init():
+    dtype = 'int64'
+    n = tvm.var('n')
+    Ab = tvm.decl_buffer((n, ), dtype)
+    i = tvm.var('i')
+    ib = tvm.ir_builder.create()
+    handle = tvm.call_intrin("handle", "tvm_static_handle")
+    ib.emit(
+        tvm.call_packed("test_static_callback", handle, Ab))
+
+    @tvm.register_func("test_static_callback")
+    def test_cb(sh, A):
+        assert isinstance(sh, ctypes.c_void_p)
+        return sh
+
+    stmt = ib.get()
+    fapi = tvm.ir_pass.MakeAPI(stmt, "ramp", [Ab], 0, True)
+    fapi = tvm.ir_pass.LowerTVMBuiltin(fapi)
+    f = tvm.codegen.build_module(fapi, "llvm")
+    a = tvm.nd.array(np.zeros(10, dtype=dtype))
+    f(a)
+
 
 if __name__ == "__main__":
+    test_static_callback()
     test_static_init()

From d218c5fc0072495b41017af5a83c91e79f97ce22 Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Sun, 24 Dec 2017 19:09:57 +0800
Subject: [PATCH 055/949] [CODEGEN] update codegen for vector operation (#711)

* [CODEGEN] update codegen for vector operation

* update comment, fix for metal
---
 src/codegen/codegen_c.cc    | 18 +++++++++++++++++-
 src/codegen/codegen_cuda.cc | 15 +++++++++++++++
 src/codegen/codegen_cuda.h  |  1 +
 3 files changed, 33 insertions(+), 1 deletion(-)

diff --git a/src/codegen/codegen_c.cc b/src/codegen/codegen_c.cc
index b315707b52c5..1f9890d9a271 100644
--- a/src/codegen/codegen_c.cc
+++ b/src/codegen/codegen_c.cc
@@ -567,6 +567,10 @@ void CodeGenC::VisitExpr_(const Load* op, std::ostream& os) {  // NOLINT(*)
       std::string ref = GetVecLoad(op->type, op->buffer_var.get(), base);
       os << ref;
     } else {
+      // The assignment below introduces side-effect, and the resulting value cannot
+      // be reused across multiple expression, thus a new scope is needed
+      int vec_scope = BeginScope();
+
       // load seperately.
       std::string svalue = GetUniqueName("_");
       this->PrintIndent();
@@ -590,6 +594,7 @@ void CodeGenC::VisitExpr_(const Load* op, std::ostream& os) {  // NOLINT(*)
         PrintVecElemStore(svalue, op->type, i, value_temp.str());
       }
       os << svalue;
+      EndScope(vec_scope);
     }
   }
 }
@@ -609,6 +614,10 @@ void CodeGenC::VisitStmt_(const Store* op) {
       std::string value = this->PrintExpr(op->value);
       this->PrintVecStore(op->buffer_var.get(), t, base, value);
     } else {
+      // The assignment below introduces side-effect, and the resulting value cannot
+      // be reused across multiple expression, thus a new scope is needed
+      int vec_scope = BeginScope();
+
       // store elements seperately
       std::string index = SSAGetID(PrintExpr(op->index), op->index.type());
       std::string value = SSAGetID(PrintExpr(op->value), op->value.type());
@@ -629,6 +638,7 @@ void CodeGenC::VisitStmt_(const Store* op) {
         PrintVecElemLoad(value, op->value.type(), i, stream);
         stream << ";\n";
       }
+      EndScope(vec_scope);
     }
   }
 }
@@ -642,7 +652,13 @@ void CodeGenC::VisitExpr_(const Let* op, std::ostream& os) {  // NOLINT(*)
 }
 
 void CodeGenC::VisitExpr_(const Ramp* op, std::ostream& os) {  // NOLINT(*)
-  LOG(FATAL) << "Ramp: not supported ";
+  os << "((int" << op->lanes << ")(";
+  for (int i = 0; i < op->lanes; i++) {
+    os << "(" << PrintExpr(op->base) << ")" << "+(" << PrintExpr(op->stride) << "*" << i <<")";
+    if (i != op->lanes - 1)
+      os << ", ";
+  }
+  os << "))";
 }
 
 void CodeGenC::VisitExpr_(const Broadcast* op, std::ostream& os) {   // NOLINT(*)
diff --git a/src/codegen/codegen_cuda.cc b/src/codegen/codegen_cuda.cc
index cace8d8573bb..854bdd993127 100644
--- a/src/codegen/codegen_cuda.cc
+++ b/src/codegen/codegen_cuda.cc
@@ -120,6 +120,10 @@ void CodeGenCUDA::PrintVecBinaryOp(
   int lanes = t.lanes();
 
   {
+    // The assignment below introduces side-effect, and the resulting value cannot
+    // be reused across multiple expression, thus a new scope is needed
+    int vec_scope = BeginScope();
+
     // default: unpack into individual ops.
     std::string vlhs = SSAGetID(PrintExpr(lhs), lhs.type());
     std::string vrhs = SSAGetID(PrintExpr(rhs), rhs.type());
@@ -148,6 +152,7 @@ void CodeGenCUDA::PrintVecBinaryOp(
       PrintVecElemStore(sret, t, i, value_temp.str());
     }
     os << sret;
+    EndScope(vec_scope);
   }
 }
 
@@ -232,6 +237,16 @@ void CodeGenCUDA::VisitStmt_(const Evaluate *op) {
   }
 }
 
+void CodeGenCUDA::VisitExpr_(const Ramp* op, std::ostream& os) {
+  os << "((make_int" << op->lanes << ")(";
+  for (int i = 0; i < op->lanes; i++) {
+    os << "(" << PrintExpr(op->base) << ")" << "+(" << PrintExpr(op->stride) << "*" << i <<")";
+    if (i != op->lanes - 1)
+      os << ", ";
+  }
+  os << "))";
+}
+
 void CodeGenCUDA::VisitExpr_(const Broadcast* op, std::ostream& os) {   // NOLINT(*)
   std::string v = PrintExpr(op->value);
   os << "make_";
diff --git a/src/codegen/codegen_cuda.h b/src/codegen/codegen_cuda.h
index 9aa72d14cb06..b84b245860e0 100644
--- a/src/codegen/codegen_cuda.h
+++ b/src/codegen/codegen_cuda.h
@@ -33,6 +33,7 @@ class CodeGenCUDA final : public CodeGenC {
       const std::string& vec, Type t, int i, const std::string& value) final;
   void BindThreadIndex(const IterVar& iv) final;  // NOLINT(*)
   // overload visitor
+  void VisitExpr_(const Ramp* op, std::ostream& os) final; // NOLINT(*)
   void VisitExpr_(const Broadcast* op, std::ostream& os) final; // NOLINT(*)
   void VisitStmt_(const Evaluate *op) final;
 

From da39a1c1255008348471c678b2399f2bb9b5e26c Mon Sep 17 00:00:00 2001
From: masahi <masahi129@gmail.com>
Date: Mon, 25 Dec 2017 00:23:55 +0900
Subject: [PATCH 056/949] [ROCM] MIOpen contrib for convolution kernels (#722)

* fist working miopen support

* do FindFwdAlgo during build time

* fix lint

* update doc string

* import topi after checking if rocm is enabled

* add miopen namespace

* fixed descriptor overwrite bug

* add use_miopen option

* fix lint

* better miopen option handling

* fix typo

* fix options handling
---
 Makefile                              |   1 +
 make/config.mk                        |   3 +
 make/contrib/miopen.mk                |   8 +
 python/tvm/contrib/miopen.py          | 102 ++++++++++++
 python/tvm/target.py                  |  11 +-
 src/contrib/miopen/conv_forward.cc    | 221 ++++++++++++++++++++++++++
 src/contrib/miopen/miopen_utils.cc    |  78 +++++++++
 src/contrib/miopen/miopen_utils.h     |  59 +++++++
 tests/python/contrib/test_miopen.py   |  64 ++++++++
 tests/scripts/task_python_unittest.sh |   2 +-
 10 files changed, 545 insertions(+), 4 deletions(-)
 create mode 100644 make/contrib/miopen.mk
 create mode 100644 python/tvm/contrib/miopen.py
 create mode 100644 src/contrib/miopen/conv_forward.cc
 create mode 100644 src/contrib/miopen/miopen_utils.cc
 create mode 100644 src/contrib/miopen/miopen_utils.h
 create mode 100644 tests/python/contrib/test_miopen.py

diff --git a/Makefile b/Makefile
index 31c8dac658fc..4a6ac7f3c8f3 100644
--- a/Makefile
+++ b/Makefile
@@ -134,6 +134,7 @@ include make/contrib/cblas.mk
 include make/contrib/random.mk
 include make/contrib/nnpack.mk
 include make/contrib/cudnn.mk
+include make/contrib/miopen.mk
 include make/contrib/mps.mk
 
 ifdef ADD_CFLAGS
diff --git a/make/config.mk b/make/config.mk
index 94153edc38f6..837db40ebff3 100644
--- a/make/config.mk
+++ b/make/config.mk
@@ -72,5 +72,8 @@ USE_NNPACK = 0
 # Whether use CuDNN
 USE_CUDNN = 0
 
+# Whether use MIOpen
+USE_MIOPEN = 0
+
 # Whether use MPS
 USE_MPS = 0
diff --git a/make/contrib/miopen.mk b/make/contrib/miopen.mk
new file mode 100644
index 000000000000..10ed6cba0801
--- /dev/null
+++ b/make/contrib/miopen.mk
@@ -0,0 +1,8 @@
+MIOPEN_CONTRIB_SRC = $(wildcard src/contrib/miopen/*.cc)
+MIOPEN_CONTRIB_OBJ = $(patsubst src/%.cc, build/%.o, $(MIOPEN_CONTRIB_SRC))
+
+ifeq ($(USE_MIOPEN), 1)
+CFLAGS += -DTVM_USE_MIOPEN=1
+ADD_LDFLAGS += -lMIOpen
+RUNTIME_DEP += $(MIOPEN_CONTRIB_OBJ)
+endif
diff --git a/python/tvm/contrib/miopen.py b/python/tvm/contrib/miopen.py
new file mode 100644
index 000000000000..b76e70688c4e
--- /dev/null
+++ b/python/tvm/contrib/miopen.py
@@ -0,0 +1,102 @@
+"""External function interface to MIOpen library."""
+# pylint: disable-msg=C0103
+import ctypes
+import numpy as np
+from .. import api as _api
+from .. import intrin as _intrin
+from .. import get_global_func as _get_global_func
+
+
+def _get_np_int32_array_handle(arr):
+    """Return a void_p handle for a numpy array
+
+    Parameters
+    ----------
+    arr: numpy.NDArray
+        source numpy array
+
+    Returns
+    -------
+    ptr:  ctypes.c_void_p
+        pointer to the data
+    """
+    assert arr.dtype == np.int32
+    ptr = arr.ctypes.data_as(ctypes.POINTER(ctypes.c_int32))
+    return ctypes.cast(ptr, ctypes.c_void_p)
+
+
+def conv2d_forward(x,
+                   w,
+                   stride_h=1,
+                   stride_w=1,
+                   pad_h=0,
+                   pad_w=0,
+                   dilation_h=1,
+                   dilation_w=1,
+                   conv_mode=0):
+    """Create an extern op that compute 2D convolution with MIOpen
+
+    Parameters
+    ----------
+    x: Tensor
+        input feature map
+    w: Tensor
+        convolution weight
+    stride_h: int
+        height stride
+    stride_w: int
+        width stride
+    pad_h: int
+        height pad
+    pad_w: int
+        weight pad
+    dilation_h: int
+        height dilation
+    dilation_w: int
+        width dilation
+    conv_mode: int
+        0: miopenConvolution
+        1: miopenTranspose
+
+    Returns
+    -------
+    y: Tensor
+        The result tensor
+    """
+    assert conv_mode == 0, "Transpose convolutions not supported yet."
+    oshape = np.zeros((len(x.shape)), dtype=np.int32)
+    xshape = x.shape
+    wshape = w.shape
+    setup_func = _get_global_func("tvm.contrib.miopen.conv2d.setup")
+    algo = setup_func(conv_mode,
+                      pad_h,
+                      pad_w,
+                      stride_h,
+                      stride_w,
+                      dilation_h,
+                      dilation_w,
+                      xshape[0].value,
+                      xshape[1].value,
+                      xshape[2].value,
+                      xshape[3].value,
+                      wshape[0].value,
+                      wshape[1].value,
+                      wshape[2].value,
+                      wshape[3].value,
+                      _get_np_int32_array_handle(oshape))
+
+    return _api.extern(
+        list(oshape), [x, w],
+        lambda ins, outs: _intrin.call_packed(
+            "tvm.contrib.miopen.conv2d.forward",
+            conv_mode,
+            pad_h,
+            pad_w,
+            stride_h,
+            stride_w,
+            dilation_h,
+            dilation_w,
+            algo,
+            ins[0],
+            ins[1],
+            outs[0]), name="y")
diff --git a/python/tvm/target.py b/python/tvm/target.py
index 1bcd1de7d3d9..096459c1ac83 100644
--- a/python/tvm/target.py
+++ b/python/tvm/target.py
@@ -88,12 +88,17 @@ def __init__(self,
                  target_name,
                  options=None):
         self.target_name = target_name
-        self.options = _merge_opts([], options)
+        self.options = []
         self.device_name = ""
+        self.libs = []
         # Parse device option
-        for item in self.options:
-            if item.startswith("-device="):
+        for item in _merge_opts([], options):
+            if item.startswith("-libs="):
+                self.libs.append(item.split("=")[1])
+                continue
+            elif item.startswith("-device="):
                 self.device_name = item.split("=")[1]
+            self.options.append(item)
         # Target query searchs device name first
         if self.device_name:
             self.keys = (self.device_name,)
diff --git a/src/contrib/miopen/conv_forward.cc b/src/contrib/miopen/conv_forward.cc
new file mode 100644
index 000000000000..d85c08dee36c
--- /dev/null
+++ b/src/contrib/miopen/conv_forward.cc
@@ -0,0 +1,221 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file Use external miopen utils function
+ */
+#include <tvm/runtime/registry.h>
+#include <tvm/runtime/util.h>
+#include <tvm/runtime/device_api.h>
+#include "miopen_utils.h"
+
+namespace tvm {
+namespace contrib {
+namespace miopen {
+
+using namespace runtime;
+
+TVM_REGISTER_GLOBAL("tvm.contrib.miopen.conv2d.setup")
+.set_body([](TVMArgs args, TVMRetValue *ret) {
+  const int mode = args[0];
+  const int pad_h = args[1];
+  const int pad_w = args[2];
+  const int stride_h = args[3];
+  const int stride_w = args[4];
+  const int dilation_h = args[5];
+  const int dilation_w = args[6];
+  const int x_dim0 = args[7];
+  const int x_dim1 = args[8];
+  const int x_dim2 = args[9];
+  const int x_dim3 = args[10];
+  const int w_dim0 = args[11];
+  const int w_dim1 = args[12];
+  const int w_dim2 = args[13];
+  const int w_dim3 = args[14];
+  void *out_shape = args[15];
+
+  MIOpenThreadEntry* entry_ptr = MIOpenThreadEntry::ThreadLocal();
+  // Set Mode
+  entry_ptr->conv_entry.mode = static_cast<miopenConvolutionMode_t>(mode);
+  // Set Ctx
+  entry_ptr->conv_entry.ctx = TVMContext{kDLROCM, 0};
+  // Set Data Type
+  entry_ptr->conv_entry.data_type = miopenFloat;  // MIOpen only suppports fp32
+  // Set Desc
+  MIOPEN_CALL(miopenInitConvolutionDescriptor(entry_ptr->conv_entry.conv_desc,
+                                              entry_ptr->conv_entry.mode,
+                                              pad_h,
+                                              pad_w,
+                                              stride_h,
+                                              stride_w,
+                                              dilation_h,
+                                              dilation_w));
+  // Set Filter
+  MIOPEN_CALL(miopenSet4dTensorDescriptor(entry_ptr->conv_entry.filter_desc,
+                                          entry_ptr->conv_entry.data_type,
+                                          w_dim0,
+                                          w_dim1,
+                                          w_dim2,
+                                          w_dim3));
+  // Set Input
+  MIOPEN_CALL(miopenSet4dTensorDescriptor(entry_ptr->conv_entry.input_desc,
+                                          entry_ptr->conv_entry.data_type,
+                                          x_dim0,
+                                          x_dim1,
+                                          x_dim2,
+                                          x_dim3));
+
+  // Set Output shape
+  MIOPEN_CALL(miopenGetConvolutionForwardOutputDim(entry_ptr->conv_entry.conv_desc,
+                                                   entry_ptr->conv_entry.input_desc,
+                                                   entry_ptr->conv_entry.filter_desc,
+                                                   static_cast<int*>(out_shape),
+                                                   static_cast<int*>(out_shape) + 1,
+                                                   static_cast<int*>(out_shape) + 2,
+                                                   static_cast<int*>(out_shape) + 3));
+
+  const int *oshape = static_cast<int*>(out_shape);
+  // Set Output
+  MIOPEN_CALL(miopenSet4dTensorDescriptor(entry_ptr->conv_entry.output_desc,
+                                          entry_ptr->conv_entry.data_type,
+                                          oshape[0],
+                                          oshape[1],
+                                          oshape[2],
+                                          oshape[3]));
+
+  // Set workspace
+  size_t workspace_size = 0;
+  MIOPEN_CALL(miopenConvolutionForwardGetWorkSpaceSize(entry_ptr->handle,
+                                                       entry_ptr->conv_entry.filter_desc,
+                                                       entry_ptr->conv_entry.input_desc,
+                                                       entry_ptr->conv_entry.conv_desc,
+                                                       entry_ptr->conv_entry.output_desc,
+                                                       &workspace_size));
+  entry_ptr->conv_entry.UpdateWorkspace(workspace_size);
+
+  const size_t input_size = x_dim0 * x_dim1 * x_dim2 * x_dim3;
+  const size_t filter_size = w_dim0 * w_dim1 * w_dim2 * w_dim3;
+  const size_t output_size = oshape[0] * oshape[1] * oshape[2] * oshape[3];
+
+  runtime::DeviceAPI* rocm_api = entry_ptr->conv_entry.rocm_api;
+  float* input_buf = static_cast<float*>(rocm_api->AllocWorkspace(entry_ptr->conv_entry.ctx,
+                                                                  input_size * sizeof(float)));
+  float* filter_buf = static_cast<float*>(rocm_api->AllocWorkspace(entry_ptr->conv_entry.ctx,
+                                                                   filter_size * sizeof(float)));
+  float* output_buf = static_cast<float*>(rocm_api->AllocWorkspace(entry_ptr->conv_entry.ctx,
+                                                                   output_size * sizeof(float)));
+
+  const int request_algo_count = 4;
+  const bool exhaustive_search = false;
+  int returned_algo_count = 0;
+  miopenConvAlgoPerf_t perfs[4];
+
+  MIOPEN_CALL(miopenFindConvolutionForwardAlgorithm(entry_ptr->handle,
+                                                    entry_ptr->conv_entry.input_desc,
+                                                    input_buf,
+                                                    entry_ptr->conv_entry.filter_desc,
+                                                    filter_buf,
+                                                    entry_ptr->conv_entry.conv_desc,
+                                                    entry_ptr->conv_entry.output_desc,
+                                                    output_buf,
+                                                    request_algo_count,
+                                                    &returned_algo_count,
+                                                    perfs,
+                                                    entry_ptr->conv_entry.workspace,
+                                                    workspace_size,
+                                                    exhaustive_search));
+
+  rocm_api->FreeWorkspace(entry_ptr->conv_entry.ctx, input_buf);
+  rocm_api->FreeWorkspace(entry_ptr->conv_entry.ctx, filter_buf);
+  rocm_api->FreeWorkspace(entry_ptr->conv_entry.ctx, output_buf);
+
+  const std::vector<std::string> fwd_algo_names{
+      "miopenConvolutionFwdAlgoGEMM",
+      "miopenConvolutionFwdAlgoDirect",
+      "miopenConvolutionFwdAlgoFFT",
+      "miopenConvolutionFwdAlgoWinograd",
+  };
+  const auto best_algo = perfs[0].fwd_algo;
+  LOG(INFO) << "\tMIOpen Found " << returned_algo_count
+            << " fwd algorithms, choosing " << fwd_algo_names[best_algo];
+  for (int i = 0; i < returned_algo_count; ++i) {
+    LOG(INFO) << "\t\t" << i << ") " << fwd_algo_names[perfs[i].fwd_algo]
+              << " - time: " << perfs[i].time << " ms"
+              << ", Memory: " << perfs[i].memory;
+  }
+  // Set Algo
+  ret[0] = static_cast<int>(best_algo);
+});
+
+
+TVM_REGISTER_GLOBAL("tvm.contrib.miopen.conv2d.forward")
+.set_body([](TVMArgs args, TVMRetValue *ret) {
+  const int mode = args[0];
+  const int pad_h = args[1];
+  const int pad_w = args[2];
+  const int stride_h = args[3];
+  const int stride_w = args[4];
+  const int dilation_h = args[5];
+  const int dilation_w = args[6];
+  const int algo = args[7];
+  const DLTensor *x = args[8];
+  const DLTensor *w = args[9];
+  const DLTensor *y = args[10];
+
+  MIOpenThreadEntry* entry_ptr = MIOpenThreadEntry::ThreadLocal();
+  entry_ptr->conv_entry.fwd_algo = static_cast<miopenConvFwdAlgorithm_t>(algo);
+  // Set Mode
+  entry_ptr->conv_entry.mode = static_cast<miopenConvolutionMode_t>(mode);
+  // Set Ctx
+  entry_ptr->conv_entry.ctx = x->ctx;
+  // Set Data Type
+  entry_ptr->conv_entry.data_type = miopenFloat;  // MIOpen only suppports fp32
+  // Set Desc
+  MIOPEN_CALL(miopenInitConvolutionDescriptor(entry_ptr->conv_entry.conv_desc,
+                                              entry_ptr->conv_entry.mode,
+                                              pad_h,
+                                              pad_w,
+                                              stride_h,
+                                              stride_w,
+                                              dilation_h,
+                                              dilation_w));
+  // Set Filter
+  MIOPEN_CALL(miopenSet4dTensorDescriptor(entry_ptr->conv_entry.filter_desc,
+                                          entry_ptr->conv_entry.data_type,
+                                          w->shape[0],
+                                          w->shape[1],
+                                          w->shape[2],
+                                          w->shape[3]));
+  // Set Input
+  MIOPEN_CALL(miopenSet4dTensorDescriptor(entry_ptr->conv_entry.input_desc,
+                                          entry_ptr->conv_entry.data_type,
+                                          x->shape[0],
+                                          x->shape[1],
+                                          x->shape[2],
+                                          x->shape[3]));
+  // Set Output
+  MIOPEN_CALL(miopenSet4dTensorDescriptor(entry_ptr->conv_entry.output_desc,
+                                          entry_ptr->conv_entry.data_type,
+                                          y->shape[0],
+                                          y->shape[1],
+                                          y->shape[2],
+                                          y->shape[3]));
+
+  const float alpha = 1.f;
+  const float beta = 0.f;
+  MIOPEN_CALL(miopenConvolutionForward(entry_ptr->handle,
+                                       &alpha,
+                                       entry_ptr->conv_entry.input_desc,
+                                       x->data,
+                                       entry_ptr->conv_entry.filter_desc,
+                                       w->data,
+                                       entry_ptr->conv_entry.conv_desc,
+                                       entry_ptr->conv_entry.fwd_algo,
+                                       &beta,
+                                       entry_ptr->conv_entry.output_desc,
+                                       y->data,
+                                       entry_ptr->conv_entry.workspace,
+                                       entry_ptr->conv_entry.workspace_size));
+});
+
+}  // namespace miopen
+}  // namespace contrib
+}  // namespace tvm
diff --git a/src/contrib/miopen/miopen_utils.cc b/src/contrib/miopen/miopen_utils.cc
new file mode 100644
index 000000000000..3019b1b83e81
--- /dev/null
+++ b/src/contrib/miopen/miopen_utils.cc
@@ -0,0 +1,78 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file Use external miopen utils function
+ */
+#include "miopen_utils.h"
+#include <dmlc/thread_local.h>
+#include <tvm/runtime/registry.h>
+#include <vector>
+#include <string>
+
+namespace tvm {
+namespace contrib {
+namespace miopen {
+
+std::string miopenGetErrorString(int error_code) {
+  const std::vector<std::string> mio_err{
+      "StatusSuccess        ", "StatusNotInitialized ", "StatusInvalidValue   ",
+      "StatusBadParm        ", "StatusAllocFailed    ", "StatusInternalError  ",
+      "StatusNotImplemented ", "StatusUnknownError   "};
+  return mio_err[error_code];
+}
+
+// MiopenThreadEntry
+MIOpenThreadEntry::MIOpenThreadEntry() {
+  auto stream = runtime::ROCMThreadEntry::ThreadLocal()->stream;
+  auto func = runtime::Registry::Get("device_api.rocm");
+  void *ret = (*func)();
+  rocm_api = static_cast<runtime::DeviceAPI*>(ret);
+  MIOPEN_CALL(miopenCreate(&handle));
+  MIOPEN_CALL(miopenSetStream(handle, stream));
+  conv_entry.rocm_api = rocm_api;
+}
+
+MIOpenThreadEntry::~MIOpenThreadEntry() {
+  MIOPEN_CALL(miopenDestroy(handle));
+}
+
+typedef dmlc::ThreadLocalStore<MIOpenThreadEntry> MIOpenThreadStore;
+
+MIOpenThreadEntry* MIOpenThreadEntry::ThreadLocal() {
+  return MIOpenThreadStore::Get();
+}
+
+// ConvEntry
+
+ConvEntry::ConvEntry() {
+  MIOPEN_CALL(miopenCreateConvolutionDescriptor(&conv_desc));
+  MIOPEN_CALL(miopenCreateTensorDescriptor(&filter_desc));
+  MIOPEN_CALL(miopenCreateTensorDescriptor(&input_desc));
+  MIOPEN_CALL(miopenCreateTensorDescriptor(&output_desc));
+}
+
+ConvEntry::~ConvEntry() {
+  MIOPEN_CALL(miopenDestroyConvolutionDescriptor(conv_desc));
+  MIOPEN_CALL(miopenDestroyTensorDescriptor(filter_desc));
+  MIOPEN_CALL(miopenDestroyTensorDescriptor(input_desc));
+  MIOPEN_CALL(miopenDestroyTensorDescriptor(output_desc));
+  CleanWorkspace();
+}
+
+void ConvEntry::UpdateWorkspace(const size_t wsize) {
+  if (workspace_size < wsize) {
+    if (workspace != nullptr) {
+      CleanWorkspace();
+    }
+    workspace_size = wsize;
+    workspace = rocm_api->AllocWorkspace(ctx, workspace_size);
+  }
+}
+
+void ConvEntry::CleanWorkspace() {
+  if (workspace) rocm_api->FreeWorkspace(ctx, workspace);
+  workspace_size = 0;
+}
+
+}  // namespace miopen
+}  // namespace contrib
+}  // namespace tvm
diff --git a/src/contrib/miopen/miopen_utils.h b/src/contrib/miopen/miopen_utils.h
new file mode 100644
index 000000000000..b01fc017ed95
--- /dev/null
+++ b/src/contrib/miopen/miopen_utils.h
@@ -0,0 +1,59 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file Use external miopen utils function
+ */
+
+#ifndef TVM_CONTRIB_MIOPEN_MIOPEN_UTILS_H_
+#define TVM_CONTRIB_MIOPEN_MIOPEN_UTILS_H_
+
+#include <dmlc/logging.h>
+#include <miopen/miopen.h>
+#include <tvm/runtime/device_api.h>
+#include <string>
+#include "../../runtime/rocm/rocm_common.h"
+
+namespace tvm {
+namespace contrib {
+namespace miopen {
+
+std::string miopenGetErrorString(int error_code);
+
+#define MIOPEN_CALL(func)                                                      \
+  {                                                                            \
+    miopenStatus_t e = (func);                                                 \
+    CHECK_EQ(e, miopenStatusSuccess)                                           \
+        << "miopen error: " << miopenGetErrorString(e);                        \
+  }
+
+struct ConvEntry {
+  miopenConvolutionDescriptor_t conv_desc;
+  miopenConvolutionMode_t mode{miopenConvolution};
+  miopenTensorDescriptor_t filter_desc;
+  miopenDataType_t data_type{miopenFloat};
+  miopenTensorDescriptor_t input_desc;
+  miopenTensorDescriptor_t output_desc;
+  miopenConvFwdAlgorithm_t fwd_algo;
+  TVMContext ctx;
+  runtime::DeviceAPI *rocm_api;
+  void *workspace{nullptr};
+  size_t workspace_size{0};
+  ConvEntry();
+  ~ConvEntry();
+  void UpdateWorkspace(const size_t wsize);
+  void CleanWorkspace();
+};  // ConvThreadEntry
+
+struct MIOpenThreadEntry {
+  MIOpenThreadEntry();
+  ~MIOpenThreadEntry();
+  miopenHandle_t handle{nullptr};
+  ConvEntry conv_entry;
+  runtime::DeviceAPI *rocm_api{nullptr};
+  static MIOpenThreadEntry *ThreadLocal();
+};  // MIOpenThreadEntry
+
+}  // namespace miopen
+}  // namespace contrib
+}  // namespace tvm
+
+#endif  // TVM_CONTRIB_MIOPEN_MIOPEN_UTILS_H_
diff --git a/tests/python/contrib/test_miopen.py b/tests/python/contrib/test_miopen.py
new file mode 100644
index 000000000000..51e50f62db94
--- /dev/null
+++ b/tests/python/contrib/test_miopen.py
@@ -0,0 +1,64 @@
+import tvm
+from tvm.contrib import miopen
+import numpy as np
+
+
+def test_conv2d():
+    in_channel = 64
+    out_channel = 128
+    filter_h = 3
+    filter_w = 3
+    pad_h = 1
+    pad_w = 1
+    stride_h = 1
+    stride_w = 1
+    dilation_h = 1
+    dilation_w = 1
+
+    xshape = [1, in_channel, 64, 64]
+    if not tvm.module.enabled("rocm"):
+        print("skip because rocm is not enabled...")
+        return
+    if not tvm.get_global_func("tvm.contrib.miopen.conv2d.setup", True):
+        print("skip because miopen is not enabled...")
+        return
+    wshape = (out_channel, in_channel, filter_h, filter_w)
+
+    X = tvm.placeholder(xshape, name='X')
+    W = tvm.placeholder(wshape, name='W')
+    Y = miopen.conv2d_forward(X,
+                              W,
+                              stride_h,
+                              stride_w,
+                              pad_h,
+                              pad_w,
+                              dilation_h,
+                              dilation_w,
+                              conv_mode=0)
+
+    yshape = [x.value for x in Y.shape]
+    s = tvm.create_schedule(Y.op)
+
+    def verify():
+        ctx = tvm.rocm(0)
+        f = tvm.build(s, [X, W, Y], "rocm", target_host="llvm", name="conv2d")
+        x = tvm.nd.array(np.random.uniform(-1, 1, xshape).astype(np.float32), ctx)
+        w = tvm.nd.array(np.random.uniform(-1, 1, wshape).astype(np.float32), ctx)
+        y = tvm.nd.array(np.random.uniform(-1, 1, yshape).astype(np.float32), ctx)
+        f(x, w, y)
+
+        import topi
+        Y_ref = topi.nn.conv2d_nchw(X, W, (stride_h, stride_w), (pad_h, pad_w))
+        with tvm.target.rocm():
+            s_ref = topi.generic.schedule_conv2d_nchw([Y_ref])
+        f_ref = tvm.build(s_ref, [X, W, Y_ref], "rocm")
+        y_ref = tvm.nd.array(np.random.uniform(-1, 1, yshape).astype(np.float32), ctx)
+        f_ref(x, w, y_ref)
+        print("Max abs diff:", np.max(np.abs(y.asnumpy() - y_ref.asnumpy())))
+        np.testing.assert_allclose(y.asnumpy(), y_ref.asnumpy(), atol=1e-3)
+
+    verify()
+
+
+if __name__ == "__main__":
+    test_conv2d()
diff --git a/tests/scripts/task_python_unittest.sh b/tests/scripts/task_python_unittest.sh
index a77dc989ebe5..f0331609479c 100755
--- a/tests/scripts/task_python_unittest.sh
+++ b/tests/scripts/task_python_unittest.sh
@@ -1,6 +1,6 @@
 #!/bin/bash
 
-export PYTHONPATH=python
+export PYTHONPATH=python:topi/python
 
 rm -rf python/tvm/*.pyc python/tvm/*/*.pyc
 

From 377242a97fcf624d2b66112005018f61e36d6fcf Mon Sep 17 00:00:00 2001
From: Yuwei Hu <huyuwei1995@gmail.com>
Date: Mon, 25 Dec 2017 09:18:14 +0800
Subject: [PATCH 057/949] [TOPI] 1bit dense operator on x86_64 (#629)

* add x86_64 target

* add binary dense operator

* rebase

* improve schedule

* remove x86 target

* improve schedule
---
 topi/python/topi/generic/nn.py        | 36 ++++++++++++
 topi/python/topi/nn/__init__.py       |  1 +
 topi/python/topi/nn/bnn.py            | 79 +++++++++++++++++++++++++++
 topi/python/topi/x86/__init__.py      |  2 +
 topi/python/topi/x86/binarize_pack.py | 38 +++++++++++++
 topi/python/topi/x86/binary_dense.py  | 55 +++++++++++++++++++
 topi/tests/python/test_topi_bnn.py    | 55 +++++++++++++++++++
 7 files changed, 266 insertions(+)
 create mode 100644 topi/python/topi/nn/bnn.py
 create mode 100644 topi/python/topi/x86/binarize_pack.py
 create mode 100644 topi/python/topi/x86/binary_dense.py
 create mode 100644 topi/tests/python/test_topi_bnn.py

diff --git a/topi/python/topi/generic/nn.py b/topi/python/topi/generic/nn.py
index 2cb64407c88e..d606213a5270 100644
--- a/topi/python/topi/generic/nn.py
+++ b/topi/python/topi/generic/nn.py
@@ -176,3 +176,39 @@ def schedule_global_pool(outs):
         The computation schedule for the op.
     """
     return _default_schedule(outs, False)
+
+
+@tvm.target.generic_func
+def schedule_binarize_pack(outs):
+    """Schedule for binarize_pack
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+          The computation graph description of binarize_pack
+          in the format of an array of tensors.
+
+    Returns
+    -------
+    sch: Schedule
+        The computation schedule for the op.
+    """
+    return _default_schedule(outs, False)
+
+
+@tvm.target.generic_func
+def schedule_binary_dense(outs):
+    """Schedule for binary_dense
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+          The computation graph description of binary_dense
+          in the format of an array of tensors.
+
+    Returns
+    -------
+    sch: Schedule
+        The computation schedule for the op.
+    """
+    return _default_schedule(outs, False)
diff --git a/topi/python/topi/nn/__init__.py b/topi/python/topi/nn/__init__.py
index b6606108268c..3cdf3122e78e 100644
--- a/topi/python/topi/nn/__init__.py
+++ b/topi/python/topi/nn/__init__.py
@@ -13,3 +13,4 @@
 from .pooling import *
 from .softmax import *
 from .conv2d_transpose import *
+from .bnn import *
diff --git a/topi/python/topi/nn/bnn.py b/topi/python/topi/nn/bnn.py
new file mode 100644
index 000000000000..39b9d2a15a1b
--- /dev/null
+++ b/topi/python/topi/nn/bnn.py
@@ -0,0 +1,79 @@
+"""Binary Neural Network (BNN) Operators"""
+from __future__ import absolute_import as _abs
+import tvm
+from .. import tag
+from ..util import simplify, get_const_int
+
+
+def binarize_pack(data, axis=None, name="PackedInput"):
+    """Binarization and bit-packing along a certain axis.
+
+    Parameters
+    ----------
+    data : tvm.Tensor
+        n-D input, can be any layout.
+
+    axis : None or int
+        The axis along which to do binarization and bit-packing,
+        default is the last axis.
+
+    name : str, optional
+        The name prefix operators generate.
+
+    Returns
+    -------
+    output : tvm.Tensor
+        n-D, the same layout as input, dtype is uint32.
+    """
+    ishape = data.shape
+    if axis is None:
+        axis = len(ishape) - 1
+    assert get_const_int(ishape[axis]) % 32 == 0
+    n = len(ishape)
+    oshape = tuple(simplify(ishape[i] // 32) if i == axis \
+        else ishape[i] for i in range(n))
+
+    def _binarize_pack(*indices):
+        start_idx = [indices[i] * 32 if i == axis else indices[i] for i in range(n)]
+        packed = tvm.const(0, 'uint32')
+        for j in range(32):
+            idx = [start_idx[i] + j if i == axis else start_idx[i] for i in range(n)]
+            sign = (data(*idx) >= 0).astype("uint32")
+            packed = (packed | sign)
+            if j == 31:
+                return packed
+            packed = packed << 1
+
+    return tvm.compute(oshape, _binarize_pack, name=name, tag='binarize_pack')
+
+
+def binary_dense(data, weight):
+    """Binary matrix multiplication using xor and bit-count.
+
+    Parameters
+    ----------
+    data : tvm.Tensor
+        2-D with shape [batch, in_dim], dtype is uint32.
+
+    weight : tvm.Tensor
+        2-D with shape [out_dim, in_dim], dtype is uint32.
+
+    Returns
+    -------
+    output : tvm.Tensor
+        2-D with shape [batch, out_dim], dtype is float32.
+    """
+    assert data.dtype == 'uint32' and weight.dtype == 'uint32', \
+        "dtype of data and weight should be uint32"
+    assert len(data.shape) == 2 and len(weight.shape) == 2, \
+        "only support 2-dim binary dense"
+    batch, in_dim = data.shape
+    out_dim, _ = weight.shape
+    k = tvm.reduce_axis((0, in_dim), name='k')
+    matmul = tvm.compute((batch, out_dim), lambda i, j: \
+                          tvm.sum(tvm.popcount(data[i, k] ^ weight[j, k]), axis=k), \
+                          tag='binary_dense')
+
+    return tvm.compute((batch, out_dim), lambda i, j: \
+                        32 * in_dim - 2. * matmul(i, j), \
+                        tag=tag.ELEMWISE)
diff --git a/topi/python/topi/x86/__init__.py b/topi/python/topi/x86/__init__.py
index d9912de2870d..6ab37b8c03ac 100644
--- a/topi/python/topi/x86/__init__.py
+++ b/topi/python/topi/x86/__init__.py
@@ -3,3 +3,5 @@
 from __future__ import absolute_import as _abs
 
 from .conv2d import schedule_conv2d
+from .binarize_pack import schedule_binarize_pack
+from .binary_dense import schedule_binary_dense
diff --git a/topi/python/topi/x86/binarize_pack.py b/topi/python/topi/x86/binarize_pack.py
new file mode 100644
index 000000000000..adf0a714b3fb
--- /dev/null
+++ b/topi/python/topi/x86/binarize_pack.py
@@ -0,0 +1,38 @@
+# pylint: disable=invalid-name
+"""Schedule for binarization and bit-packing."""
+from __future__ import absolute_import as _abs
+import tvm
+from .. import generic
+
+
+@generic.schedule_binarize_pack.register(["cpu"])
+def schedule_binarize_pack(outs):
+    """Schedule for binarize_pack.
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+        The computation graph description of binarize_pack
+        in the format of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+        The computation schedule for binarize_pack.
+    """
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    s = tvm.create_schedule([x.op for x in outs])
+
+    def _schedule(Out):
+        s[Out].parallel(Out.op.axis[0])
+
+    def traverse(OP):
+        # schedule binarize_pack
+        if OP.tag == 'binarize_pack':
+            Out = OP.output(0)
+            _schedule(Out)
+        else:
+            raise RuntimeError("Unsupported operator: %s" % OP.tag)
+
+    traverse(outs[0].op)
+    return s
diff --git a/topi/python/topi/x86/binary_dense.py b/topi/python/topi/x86/binary_dense.py
new file mode 100644
index 000000000000..11fccba7b644
--- /dev/null
+++ b/topi/python/topi/x86/binary_dense.py
@@ -0,0 +1,55 @@
+# pylint: disable=invalid-name, unused-variable, unused-argument
+"""Schedule for binary dense operator."""
+from __future__ import absolute_import as _abs
+import tvm
+from .. import tag
+from .. import generic
+
+
+@generic.schedule_binary_dense.register(["cpu"])
+def schedule_binary_dense(outs):
+    """Schedule for binary_dense.
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+        The computation graph description of binary_dense
+        in the format of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+        The computation schedule for binary_dense.
+    """
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    s = tvm.create_schedule([x.op for x in outs])
+
+    def _schedule(A, B, C):
+        s[C].split(s[C].op.reduce_axis[0], factor=8)
+        s[C].parallel(s[C].op.axis[0])
+        if C.op in s.outputs:
+            Out = C
+        else:
+            Out = outs[0].op.output(0)
+        xo, xi = s[Out].split(Out.op.axis[1], factor=8)
+        s[Out].vectorize(xi)
+
+    def traverse(OP):
+        # inline all one-to-one-mapping operators except the last stage (output)
+        if tag.is_broadcast(OP.tag):
+            if OP not in s.outputs:
+                s[OP].compute_inline()
+            for tensor in OP.input_tensors:
+                if tensor.op.input_tensors:
+                    traverse(tensor.op)
+        # schedule binary_dense
+        elif OP.tag == 'binary_dense':
+            output = OP.output(0)
+            data = OP.input_tensors[0]
+            weight = OP.input_tensors[1]
+            _schedule(data, weight, output)
+        else:
+            raise RuntimeError("Unsupported operator: %s" % OP.tag)
+
+    traverse(outs[0].op)
+    return s
diff --git a/topi/tests/python/test_topi_bnn.py b/topi/tests/python/test_topi_bnn.py
new file mode 100644
index 000000000000..5e6a11afc602
--- /dev/null
+++ b/topi/tests/python/test_topi_bnn.py
@@ -0,0 +1,55 @@
+"""Test code for binary neural network operators."""
+import numpy as np
+import tvm
+import topi
+from topi.util import get_const_tuple
+from tvm.contrib.pickle_memoize import memoize
+
+
+def verify_binary_dense(batch, in_dim, out_dim):
+    A = tvm.placeholder((batch, in_dim), name='A')
+    B = tvm.placeholder((out_dim, in_dim), name='B')
+    bnn_A = topi.nn.binarize_pack(A)
+    bnn_B = topi.nn.binarize_pack(B)
+    # binary dense
+    bnn_A1 = tvm.placeholder(bnn_A.shape, dtype=bnn_A.dtype)
+    bnn_B1 = tvm.placeholder(bnn_B.shape, dtype=bnn_B.dtype)
+    bnn_C = topi.nn.binary_dense(bnn_A1, bnn_B1)
+    # schedule
+    with tvm.target.create('llvm'):
+        s1 = topi.generic.schedule_binarize_pack(bnn_A)
+        s2 = topi.generic.schedule_binarize_pack(bnn_B)
+        s3 = topi.generic.schedule_binary_dense(bnn_C)
+
+    dtype = A.dtype
+    @memoize("topi.tests.test_topi_binary_dense")
+    def get_ref_data():
+        # generate random matrix of +1 or -1 value
+        a_np = (np.random.randint(2, size=(batch, in_dim)) * 2 - 1).astype(dtype)
+        b_np = (np.random.randint(2, size=(out_dim, in_dim)) * 2 - 1).astype(dtype)
+        c_np = np.dot(a_np, b_np.T)
+        return (a_np, b_np, c_np)
+
+    a_np, b_np, c_np = get_ref_data()
+
+    ctx = tvm.cpu(0)
+    a = tvm.nd.array(a_np, ctx)
+    b = tvm.nd.array(b_np, ctx)
+    bnn_a = tvm.nd.array(np.zeros(get_const_tuple(bnn_A.shape), dtype=bnn_A.dtype), ctx)
+    bnn_b = tvm.nd.array(np.zeros(get_const_tuple(bnn_B.shape), dtype=bnn_B.dtype), ctx)
+    bnn_c = tvm.nd.array(np.zeros(get_const_tuple(bnn_C.shape), dtype=bnn_C.dtype), ctx)
+    f1 = tvm.build(s1, [A, bnn_A], 'llvm -mcpu=core-avx2')
+    f2 = tvm.build(s2, [B, bnn_B], 'llvm -mcpu=core-avx2')
+    f3 = tvm.build(s3, [bnn_A1, bnn_B1, bnn_C], 'llvm -mcpu=core-avx2')
+    f1(a, bnn_a)
+    f2(b, bnn_b)
+    f3(bnn_a, bnn_b, bnn_c)
+    np.testing.assert_allclose(bnn_c.asnumpy(), c_np, rtol=1e-5)
+
+def test_binary_dense():
+    verify_binary_dense(1, 4096, 1024)
+    verify_binary_dense(1, 1024, 1000)
+
+
+if __name__ == "__main__":
+    test_binary_dense()

From 02668ad892d7ed4e3ed6e4466607275211f723b9 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 26 Dec 2017 11:47:44 +0800
Subject: [PATCH 058/949] update dmlc-core (#728)

---
 dmlc-core | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/dmlc-core b/dmlc-core
index 04f91953ace7..674a662c22b9 160000
--- a/dmlc-core
+++ b/dmlc-core
@@ -1 +1 @@
-Subproject commit 04f91953ace74aced3bb317990515304c5425849
+Subproject commit 674a662c22b900b76e8a3c9b77987a2c5563ba71

From 5f60df34925f1ac402d0f0c9d4df8e054a6426f4 Mon Sep 17 00:00:00 2001
From: masahi <masahi129@gmail.com>
Date: Tue, 26 Dec 2017 12:50:32 +0900
Subject: [PATCH 059/949] [TOPI] add extern schedule for cudnn and miopen
 (#724)

* add extern schedule for miopen

* fix comment

* optionally dispatch to miopen from topi

* fix lint

* check if current target is None

* use generic dispatch for rocm conv2d

* fix lint

* fix workspace bug

* remove blank line

* remove blank line

* remove blank line
---
 src/contrib/miopen/conv_forward.cc   |  4 +-
 tests/python/contrib/test_miopen.py  | 11 ++--
 topi/python/topi/__init__.py         |  1 +
 topi/python/topi/cuda/__init__.py    |  1 +
 topi/python/topi/cuda/extern.py      | 40 +++++++++++++++
 topi/python/topi/generic/__init__.py |  1 +
 topi/python/topi/generic/extern.py   | 26 ++++++++++
 topi/python/topi/rocm/__init__.py    |  5 ++
 topi/python/topi/rocm/conv2d.py      | 77 ++++++++++++++++++++++++++++
 9 files changed, 160 insertions(+), 6 deletions(-)
 create mode 100644 topi/python/topi/cuda/extern.py
 create mode 100644 topi/python/topi/generic/extern.py
 create mode 100644 topi/python/topi/rocm/__init__.py
 create mode 100644 topi/python/topi/rocm/conv2d.py

diff --git a/src/contrib/miopen/conv_forward.cc b/src/contrib/miopen/conv_forward.cc
index d85c08dee36c..7090560e3889 100644
--- a/src/contrib/miopen/conv_forward.cc
+++ b/src/contrib/miopen/conv_forward.cc
@@ -105,6 +105,8 @@ TVM_REGISTER_GLOBAL("tvm.contrib.miopen.conv2d.setup")
 
   const int request_algo_count = 4;
   const bool exhaustive_search = false;
+  void* workspace = entry_ptr->conv_entry.workspace;
+  if (workspace_size == 0) workspace = nullptr;
   int returned_algo_count = 0;
   miopenConvAlgoPerf_t perfs[4];
 
@@ -119,7 +121,7 @@ TVM_REGISTER_GLOBAL("tvm.contrib.miopen.conv2d.setup")
                                                     request_algo_count,
                                                     &returned_algo_count,
                                                     perfs,
-                                                    entry_ptr->conv_entry.workspace,
+                                                    workspace,
                                                     workspace_size,
                                                     exhaustive_search));
 
diff --git a/tests/python/contrib/test_miopen.py b/tests/python/contrib/test_miopen.py
index 51e50f62db94..4e13b052e616 100644
--- a/tests/python/contrib/test_miopen.py
+++ b/tests/python/contrib/test_miopen.py
@@ -4,8 +4,8 @@
 
 
 def test_conv2d():
-    in_channel = 64
-    out_channel = 128
+    in_channel = 3
+    out_channel = 64
     filter_h = 3
     filter_w = 3
     pad_h = 1
@@ -15,7 +15,7 @@ def test_conv2d():
     dilation_h = 1
     dilation_w = 1
 
-    xshape = [1, in_channel, 64, 64]
+    xshape = [1, in_channel, 128, 128]
     if not tvm.module.enabled("rocm"):
         print("skip because rocm is not enabled...")
         return
@@ -37,7 +37,9 @@ def test_conv2d():
                               conv_mode=0)
 
     yshape = [x.value for x in Y.shape]
-    s = tvm.create_schedule(Y.op)
+    import topi
+    with tvm.target.create("rocm -libs=miopen"):
+        s = topi.generic.schedule_extern(Y)
 
     def verify():
         ctx = tvm.rocm(0)
@@ -47,7 +49,6 @@ def verify():
         y = tvm.nd.array(np.random.uniform(-1, 1, yshape).astype(np.float32), ctx)
         f(x, w, y)
 
-        import topi
         Y_ref = topi.nn.conv2d_nchw(X, W, (stride_h, stride_w), (pad_h, pad_w))
         with tvm.target.rocm():
             s_ref = topi.generic.schedule_conv2d_nchw([Y_ref])
diff --git a/topi/python/topi/__init__.py b/topi/python/topi/__init__.py
index 62a9ae153052..c28dfb34e8b6 100644
--- a/topi/python/topi/__init__.py
+++ b/topi/python/topi/__init__.py
@@ -19,3 +19,4 @@
 from . import rasp
 from . import testing
 from . import util
+from . import rocm
diff --git a/topi/python/topi/cuda/__init__.py b/topi/python/topi/cuda/__init__.py
index b898dde6a8cf..03cab1441b0c 100644
--- a/topi/python/topi/cuda/__init__.py
+++ b/topi/python/topi/cuda/__init__.py
@@ -13,3 +13,4 @@
 from .dense import schedule_dense
 from .pooling import schedule_pool, schedule_global_pool
 from .conv2d_transpose_nchw import schedule_conv2d_transpose_nchw
+from .extern import schedule_extern
diff --git a/topi/python/topi/cuda/extern.py b/topi/python/topi/cuda/extern.py
new file mode 100644
index 000000000000..34dae092cf69
--- /dev/null
+++ b/topi/python/topi/cuda/extern.py
@@ -0,0 +1,40 @@
+# pylint: disable=invalid-name, unused-variable,
+"""Schedule for cudnn and miopen extern op"""
+import tvm
+from .. import generic
+
+def _schedule_output(op, sch):
+    x = op.output(0)
+    fused = sch[x].fuse(*sch[x].op.axis)
+    num_thread = tvm.target.current_target(allow_none=False).max_num_threads
+    bx, tx = sch[x].split(fused, factor=num_thread)
+    sch[x].bind(bx, tvm.thread_axis("blockIdx.x"))
+    sch[x].bind(tx, tvm.thread_axis("threadIdx.x"))
+    return sch
+
+
+@generic.schedule_extern.register(["cuda", "gpu"])
+def schedule_extern(outs):
+    """Schedule for an extern op followed by injective operations.
+       For example, cudnn kernel + bias add + relu.
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+          The computation graph description of extern plus injective ops in the format
+          of an array of tensors.
+
+    Returns
+    -------
+    sch: Schedule
+        The computation schedule for the op.
+    """
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    s = tvm.create_schedule([x.op for x in outs])
+
+    tvm.schedule.AutoInlineInjective(s)
+    for out in outs:
+        if isinstance(out.op, tvm.tensor.ExternOp):
+            continue
+        _schedule_output(out.op, s)
+    return s
diff --git a/topi/python/topi/generic/__init__.py b/topi/python/topi/generic/__init__.py
index d10d5c5ebecc..8fc9143c3f86 100644
--- a/topi/python/topi/generic/__init__.py
+++ b/topi/python/topi/generic/__init__.py
@@ -17,3 +17,4 @@
 
 from .nn import *
 from .injective import *
+from .extern import *
diff --git a/topi/python/topi/generic/extern.py b/topi/python/topi/generic/extern.py
new file mode 100644
index 000000000000..082c1bca83ea
--- /dev/null
+++ b/topi/python/topi/generic/extern.py
@@ -0,0 +1,26 @@
+# pylint: disable=invalid-name
+"""generic declaration and schedules."""
+from __future__ import absolute_import as _abs
+
+import tvm
+
+@tvm.target.generic_func
+def schedule_extern(outs):
+    """Schedule for an extern op followed by injective operations.
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+          The computation graph description of extern plus injective ops in the format
+          of an array of tensors.
+
+    Returns
+    -------
+    sch: Schedule
+        The computation schedule for the op.
+    """
+    target = tvm.target.current_target(allow_none=False)
+    if target.target_name != "llvm":
+        raise RuntimeError("schedule_injective not registered for '%s'" % target)
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    return tvm.create_schedule([x.op for x in outs])
diff --git a/topi/python/topi/rocm/__init__.py b/topi/python/topi/rocm/__init__.py
new file mode 100644
index 000000000000..d2d7aaf0fd3d
--- /dev/null
+++ b/topi/python/topi/rocm/__init__.py
@@ -0,0 +1,5 @@
+# pylint: disable=redefined-builtin, wildcard-import
+"""rocm specific declaration and schedules."""
+from __future__ import absolute_import as _abs
+
+from .conv2d import *
diff --git a/topi/python/topi/rocm/conv2d.py b/topi/python/topi/rocm/conv2d.py
new file mode 100644
index 000000000000..c6e4eb817b11
--- /dev/null
+++ b/topi/python/topi/rocm/conv2d.py
@@ -0,0 +1,77 @@
+# pylint: disable=invalid-name, no-member, too-many-locals, too-many-statements, too-many-arguments, too-many-branches, line-too-long
+"""Schedule for rocm conv2d_nchw with auto fusion"""
+import tvm
+from tvm.contrib import miopen
+import topi
+from .. import generic
+from ..nn.conv2d import conv2d
+
+@conv2d.register("rocm")
+def conv2d_rocm(data, kernel, stride, padding, layout='NCHW', out_dtype='float32'):
+    """Conv2D operator for rocm backend.
+
+    Parameters
+    ----------
+    input : tvm.Tensor
+        4-D with shape [batch, in_channel, in_height, in_width]
+
+    filter : tvm.Tensor
+        4-D with shape [num_filter, in_channel, filter_height, filter_width]
+
+    stride : int or a list/tuple of two ints
+        stride size, or [stride_height, stride_width]
+
+    padding : int or a list/tuple of two ints
+        padding size, or [pad_height, pad_width]
+
+    layout : str
+        layout of data
+
+    Returns
+    -------
+    output : tvm.Tensor
+        4-D with shape [batch, out_channel, out_height, out_width]
+    """
+    assert layout == 'NCHW', "Only NCHW layout is supported."
+    assert isinstance(stride, int) or len(stride) == 2
+    if isinstance(stride, int):
+        stride_h = stride_w = stride
+    else:
+        stride_h, stride_w = stride
+    if isinstance(padding, int):
+        pad_h = pad_w = padding
+    else:
+        pad_h, pad_w = padding
+    target = tvm.target.current_target()
+    if "miopen" in target.libs:
+        return miopen.conv2d_forward(data,
+                                     kernel,
+                                     stride_h,
+                                     stride_w,
+                                     pad_h,
+                                     pad_w,
+                                     1,  # dilation_h
+                                     1,  # dilation_w
+                                     conv_mode=0)
+    return topi.nn.conv2d_nchw(data, kernel, stride, padding, out_dtype)
+
+
+@generic.schedule_conv2d_nchw.register(["rocm"])
+def schedule_conv2d_nchw(outs):
+    """Schedule for conv2d_nchw with rocm backend.
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+        The computation graph description of conv2d_nchw
+        in the format of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+        The computation schedule for conv2d_nchw.
+    """
+    target = tvm.target.current_target()
+    if target and "miopen" in target.libs:
+        return topi.generic.schedule_extern(outs)
+    return topi.cuda.schedule_conv2d_nchw(outs)

From 0163f6b1a1c9c714f6cf4ca46dd6efaf5b8a877c Mon Sep 17 00:00:00 2001
From: masahi <masahi129@gmail.com>
Date: Wed, 27 Dec 2017 13:12:21 +0900
Subject: [PATCH 060/949] [TOPI] CUDNN integration (#730)

* add target.libs to target str representation

* integrate cudnn into topi cuda

* append target.libs to target.options
---
 python/tvm/target.py                 |  6 +--
 src/codegen/llvm/llvm_common.cc      |  2 +-
 tests/python/contrib/test_cudnn.py   |  3 +-
 topi/python/topi/cuda/__init__.py    |  1 +
 topi/python/topi/cuda/conv2d.py      | 65 ++++++++++++++++++++++++++++
 topi/python/topi/cuda/conv2d_nchw.py |  5 +++
 topi/python/topi/rocm/conv2d.py      |  2 +-
 7 files changed, 77 insertions(+), 7 deletions(-)
 create mode 100644 topi/python/topi/cuda/conv2d.py

diff --git a/python/tvm/target.py b/python/tvm/target.py
index 096459c1ac83..092dbc0d1165 100644
--- a/python/tvm/target.py
+++ b/python/tvm/target.py
@@ -88,17 +88,15 @@ def __init__(self,
                  target_name,
                  options=None):
         self.target_name = target_name
-        self.options = []
+        self.options = _merge_opts([], options)
         self.device_name = ""
         self.libs = []
         # Parse device option
-        for item in _merge_opts([], options):
+        for item in self.options:
             if item.startswith("-libs="):
                 self.libs.append(item.split("=")[1])
-                continue
             elif item.startswith("-device="):
                 self.device_name = item.split("=")[1]
-            self.options.append(item)
         # Target query searchs device name first
         if self.device_name:
             self.keys = (self.device_name,)
diff --git a/src/codegen/llvm/llvm_common.cc b/src/codegen/llvm/llvm_common.cc
index dfe51fb373f7..b34bf0d5ec91 100644
--- a/src/codegen/llvm/llvm_common.cc
+++ b/src/codegen/llvm/llvm_common.cc
@@ -82,7 +82,7 @@ GetLLVMTargetMachine(const std::string& target_str,
       } else {
         LOG(FATAL) << "invalid -mfloat-abi option " << value;
       }
-    } else if (key == "-device") {
+    } else if (key == "-device" || key == "-libs") {
       // pass
     } else {
       LOG(FATAL) << "unknown option " << key;
diff --git a/tests/python/contrib/test_cudnn.py b/tests/python/contrib/test_cudnn.py
index 93e17ea54ad8..47561c57bcbd 100644
--- a/tests/python/contrib/test_cudnn.py
+++ b/tests/python/contrib/test_cudnn.py
@@ -41,7 +41,8 @@ def test_conv2d():
                              tensor_format=0,
                              algo=1)
     yshape = [x.value for x in Y.shape]
-    s = tvm.create_schedule(Y.op)
+    with tvm.target.create("cuda -libs=cudnn"):
+        s = tvm.create_schedule(Y.op)
     
     def verify():
         ctx = tvm.gpu(0)
diff --git a/topi/python/topi/cuda/__init__.py b/topi/python/topi/cuda/__init__.py
index 03cab1441b0c..314931b4cb42 100644
--- a/topi/python/topi/cuda/__init__.py
+++ b/topi/python/topi/cuda/__init__.py
@@ -2,6 +2,7 @@
 """CUDA specific declaration and schedules."""
 from __future__ import absolute_import as _abs
 
+from .conv2d import conv2d_cuda
 from .conv2d_nchw import schedule_conv2d_nchw
 from .conv2d_hwcn import schedule_conv2d_hwcn
 from .depthwise_conv2d import schedule_depthwise_conv2d_nchw, schedule_depthwise_conv2d_nhwc
diff --git a/topi/python/topi/cuda/conv2d.py b/topi/python/topi/cuda/conv2d.py
new file mode 100644
index 000000000000..62b5642abbab
--- /dev/null
+++ b/topi/python/topi/cuda/conv2d.py
@@ -0,0 +1,65 @@
+# pylint: disable=invalid-name, no-member, too-many-locals, too-many-statements, too-many-arguments, too-many-branches, line-too-long
+"""Compute definition for conv2d with cuda backend"""
+import tvm
+from tvm.contrib import cudnn
+import topi
+from ..nn.conv2d import conv2d
+
+@conv2d.register("cuda")
+def conv2d_cuda(data, kernel, stride, padding, layout='NCHW', out_dtype='float32'):
+    """Conv2D operator for cuda backend.
+
+    Parameters
+    ----------
+    input : tvm.Tensor
+        4-D with shape [batch, in_channel, in_height, in_width]
+
+    filter : tvm.Tensor
+        4-D with shape [num_filter, in_channel, filter_height, filter_width]
+
+    stride : int or a list/tuple of two ints
+        stride size, or [stride_height, stride_width]
+
+    padding : int or a list/tuple of two ints
+        padding size, or [pad_height, pad_width]
+
+    layout : str
+        layout of data
+
+    Returns
+    -------
+    output : tvm.Tensor
+        4-D with shape [batch, out_channel, out_height, out_width]
+    """
+    assert isinstance(stride, int) or len(stride) == 2
+    if isinstance(stride, int):
+        stride_h = stride_w = stride
+    else:
+        stride_h, stride_w = stride
+    if isinstance(padding, int):
+        pad_h = pad_w = padding
+    else:
+        pad_h, pad_w = padding
+    target = tvm.target.current_target()
+    if "cudnn" in target.libs:
+        assert layout != 'HWCN', "HWCN layout not supported with CUDNN."
+        tensor_format = 0 # CUDNN_TENSOR_NCHW
+        if layout == 'NHWC':
+            tensor_format = 1 # CUDNN_TENSOR_NHWC
+        return cudnn.conv2d_forward(data,
+                                    kernel,
+                                    stride_h,
+                                    stride_w,
+                                    pad_h,
+                                    pad_w,
+                                    1,  # dilation_h
+                                    1,  # dilation_w
+                                    conv_mode=1,
+                                    tensor_format=tensor_format,
+                                    algo=0)
+    elif layout == 'NCHW':
+        return topi.nn.conv2d_nchw(data, kernel, stride, padding, out_dtype)
+    elif layout == 'HWCN':
+        return topi.nn.conv2d_hwcn(data, kernel, stride, padding, out_dtype)
+    else:
+        raise ValueError("not support this layout {} yet".format(layout))
diff --git a/topi/python/topi/cuda/conv2d_nchw.py b/topi/python/topi/cuda/conv2d_nchw.py
index 0d3f5eedb0a2..e313029e74b0 100644
--- a/topi/python/topi/cuda/conv2d_nchw.py
+++ b/topi/python/topi/cuda/conv2d_nchw.py
@@ -1,6 +1,7 @@
 #pylint: disable=invalid-name, no-member, too-many-locals, too-many-statements, too-many-arguments, too-many-branches, line-too-long
 """Schedule for conv2d_nchw with auto fusion"""
 import tvm
+import topi
 from .. import util
 from .. import tag
 from .. import generic
@@ -516,6 +517,10 @@ def schedule_conv2d_nchw(outs):
     s: Schedule
         The computation schedule for conv2d_nchw.
     """
+    target = tvm.target.current_target()
+    if target.target_name == "cuda" and "cudnn" in target.libs:
+        return topi.generic.schedule_extern(outs)
+
     outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
     batch_size = util.get_const_int(outs[0].op.output(0).shape[0])
     if batch_size > 1:
diff --git a/topi/python/topi/rocm/conv2d.py b/topi/python/topi/rocm/conv2d.py
index c6e4eb817b11..4dd5e5fd0735 100644
--- a/topi/python/topi/rocm/conv2d.py
+++ b/topi/python/topi/rocm/conv2d.py
@@ -1,5 +1,5 @@
 # pylint: disable=invalid-name, no-member, too-many-locals, too-many-statements, too-many-arguments, too-many-branches, line-too-long
-"""Schedule for rocm conv2d_nchw with auto fusion"""
+"""Compute and schedule for rocm conv2d_nchw with auto fusion"""
 import tvm
 from tvm.contrib import miopen
 import topi

From b4131574f2fbebb03c00294984664258368e7b3c Mon Sep 17 00:00:00 2001
From: Xingjian Shi <xshiab@ust.hk>
Date: Wed, 27 Dec 2017 01:52:37 -0800
Subject: [PATCH 061/949] [TOPI]Support dim-0 tensor in topi broadcast/reduce
 (#731)

* support dim-0 tensor in topi ops

revert transform

* revert
---
 topi/python/topi/reduction.py            | 4 +---
 topi/tests/python/test_topi_broadcast.py | 2 ++
 topi/tests/python/test_topi_reduce.py    | 5 ++++-
 3 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/topi/python/topi/reduction.py b/topi/python/topi/reduction.py
index 3c6bf1ca0e2c..997ec8e9ba95 100644
--- a/topi/python/topi/reduction.py
+++ b/topi/python/topi/reduction.py
@@ -107,10 +107,8 @@ def comm_reduce(data, axis=None, keepdims=False, func=tvm.sum, is_idx_reduce=Fal
     ret : tvm.Tensor
     """
     ndim = len(data.shape)
+    assert ndim != 0, "Reduce a dim-0 input is not supported!"
     real_axis = _get_real_axis(ndim, axis)
-    if real_axis == list(range(ndim)) and keepdims is False:
-        raise ValueError("Currently we do not support all reduce + keepdims = False!"
-                         " axis={}, keepdims={}".format(axis, keepdims))
     reduce_axes = [tvm.reduce_axis((0, data.shape[i]), "k%d" %i) for i in real_axis]
     if keepdims:
         target_shape = [1 if i in real_axis else data.shape[i] for i in range(ndim)]
diff --git a/topi/tests/python/test_topi_broadcast.py b/topi/tests/python/test_topi_broadcast.py
index e5f88e9d4df6..28a9e721a4ea 100644
--- a/topi/tests/python/test_topi_broadcast.py
+++ b/topi/tests/python/test_topi_broadcast.py
@@ -89,12 +89,14 @@ def check_device(device):
 
 def test_broadcast_to():
     verify_broadcast_to_ele((1,), (10,))
+    verify_broadcast_to_ele((), (10,))
     verify_broadcast_to_ele((1, 1, 5, 4), (3, 4, 4, 4, 5, 4))
     verify_broadcast_to_ele((1, 128, 1, 32), (64, 128, 64, 32))
 
 
 def test_broadcast_binary():
     verify_broadcast_binary_ele((5, 2, 3), (2, 1), typ="add")
+    verify_broadcast_binary_ele((5, 2, 3), (), typ="add")
     verify_broadcast_binary_ele((5, 64, 128), (2, 5, 64, 1), typ="mul")
     verify_broadcast_binary_ele((2, 3, 1, 32), (64, 32), typ="div")
     verify_broadcast_binary_ele((1, 32), (64, 32), typ="sub")
diff --git a/topi/tests/python/test_topi_reduce.py b/topi/tests/python/test_topi_reduce.py
index 13cd8fcdcd21..08e66e1404b6 100644
--- a/topi/tests/python/test_topi_reduce.py
+++ b/topi/tests/python/test_topi_reduce.py
@@ -108,7 +108,10 @@ def test_reduce_map():
                           axis=None,
                           keepdims=True,
                           type="argmax")
-
+    verify_reduce_map_ele(in_shape=(31, 21, 15),
+                          axis=None,
+                          keepdims=False,
+                          type="sum")
 
 if __name__ == "__main__":
     test_reduce_map()

From bb6ddd2508bb6889cfe2f89d17d9238db853f2c2 Mon Sep 17 00:00:00 2001
From: kun-zh <32951065+kun-zh@users.noreply.github.com>
Date: Wed, 27 Dec 2017 17:52:48 +0800
Subject: [PATCH 062/949] [SCHEDULE] New Reduction Mode for Tensorize (#727)

* when there is no intrin func, using body for initialization. For issue 714.

* Refine code per review comments, and add a test case.

* Fix lint issues.
---
 src/op/tensorize.cc                           | 94 +++++++++++++++----
 .../test_schedule_tensorize_init_none.py      | 90 ++++++++++++++++++
 2 files changed, 167 insertions(+), 17 deletions(-)
 create mode 100644 tests/python/unittest/test_schedule_tensorize_init_none.py

diff --git a/src/op/tensorize.cc b/src/op/tensorize.cc
index b4527f76e808..6fa5459829fc 100644
--- a/src/op/tensorize.cc
+++ b/src/op/tensorize.cc
@@ -10,6 +10,7 @@
 #include "./op_util.h"
 #include "./compute_op.h"
 #include "../schedule/message_passing.h"
+#include "../arithmetic/compute_expr.h"
 
 namespace tvm {
 
@@ -322,6 +323,50 @@ void VerifyTensorizeBody(
   }
 }
 
+/*!
+ * \brief Transform the update part when there is no init func in tensorizing
+ * \param stage The stage for tensorizing.
+ * \param dom_map The range of each iter var.
+ * \param n The loop nest structured used in compute. 
+ * \param body The body func in tensorize intrin
+ * \param update The update func in tensorize intrin
+ * \return Transformed result.
+ */
+Stmt TransformUpdate(const Stage& stage,
+                     const std::unordered_map<IterVar, Range>& dom_map,
+                     const ComputeLoopNest& n,
+                     Stmt body,
+                     Stmt update) {
+  Array<Expr> conds;
+  std::unordered_set<const Variable*> banned;
+  for (size_t i = 0; i < stage->leaf_iter_vars.size(); ++i) {
+    IterVar iv = stage->leaf_iter_vars[i];
+    auto iit = stage->iter_var_attrs.find(iv);
+    if (iit != stage->iter_var_attrs.end()) {
+      const IterVarAttr& attr = (*iit).second;
+      if (attr->iter_type == kTensorized) {
+        break;
+      }
+    }
+    if (iv->iter_type == kCommReduce) {
+      auto vit = dom_map.find(iv);
+      CHECK(vit != dom_map.end());
+      const Range& vrange = vit->second;
+      conds.push_back(likely(iv->var > vrange->min));
+      banned.insert(iv->var.get());
+    }
+  }
+  for (const Expr& pred : n.main_predicates) {
+    if (ir::ExprUseVar(pred, banned)) {
+      LOG(FATAL) << "Tensorize update transform failed, the condition "
+                 << pred << " has a conflict with the reset condition";
+    }
+  }
+
+  return IfThenElse::make(arith::ComputeReduce<ir::Or>(conds, const_true(1)),
+                          update, body);
+}
+
 Stmt MakeTensorize(const ComputeOpNode* self,
                    const Stage& stage,
                    const std::unordered_map<IterVar, Range>& dom_map) {
@@ -416,32 +461,47 @@ Stmt MakeTensorize(const ComputeOpNode* self,
     return MergeNest(nest, body);
   } else {
     // Need to split reduction
-    CHECK(intrin->reduce_init.defined())
-        << "Reduction init op for intrin " << intrin << " is not defined";
     CHECK(intrin->reduce_update.defined())
         << "Reduction update op for intrin " << intrin << " is not defined";
     // Need init and update steps
     CHECK_NE(self->reduce_axis.size(), 0U);
     std::vector<std::vector<Stmt> > common(
         n.main_nest.begin(), n.main_nest.begin() + n.num_common_loop + 1);
-    // init nest
-    std::vector<std::vector<Stmt> > init_nest(
-        n.init_nest.begin(), n.init_nest.begin() + tloc + 1);
-    init_nest.emplace_back(op::MakeIfNest(n.init_predicates));
-    Stmt init = MergeNest(output_bind_nest, intrin->reduce_init);
-    init = Substitute(init, n.init_vmap);
-    init = MergeNest(init_nest, init);
-    // The update
     std::vector<std::vector<Stmt> > update_nest(
         n.main_nest.begin() + n.num_common_loop + 1, n.main_nest.begin() + tloc + 1);
     update_nest.emplace_back(op::MakeIfNest(n.main_predicates));
-    Stmt update = MergeNest(output_bind_nest, intrin->reduce_update);
-    update = MergeNest(input_bind_nest, update);
-    update = Substitute(update, vmap);
-    update = MergeNest(binder.asserts(), update);
-    update = Substitute(update, n.main_vmap);
-    update = MergeNest(update_nest, update);
-    return MergeNest(common, Block::make(init, update));
+
+    if (intrin->reduce_init.defined()) {
+      // init nest
+      std::vector<std::vector<Stmt> > init_nest(
+          n.init_nest.begin(), n.init_nest.begin() + tloc + 1);
+      init_nest.emplace_back(op::MakeIfNest(n.init_predicates));
+      Stmt init = MergeNest(output_bind_nest, intrin->reduce_init);
+      init = Substitute(init, n.init_vmap);
+      init = MergeNest(init_nest, init);
+      // The update
+      Stmt update = MergeNest(output_bind_nest, intrin->reduce_update);
+      update = MergeNest(input_bind_nest, update);
+      update = Substitute(update, vmap);
+      update = MergeNest(binder.asserts(), update);
+      update = Substitute(update, n.main_vmap);
+      update = MergeNest(update_nest, update);
+      return MergeNest(common, Block::make(init, update));
+    } else {
+      // When init op is not available, use body op for reset in the first iter.
+      CHECK(intrin->body.defined())
+          << "Normal body op for intrin " << intrin << " is not defined";
+      Stmt update = TransformUpdate(stage, dom_map, n,
+                                    intrin->body,
+                                    intrin->reduce_update);
+      update = MergeNest(output_bind_nest, update);
+      update = MergeNest(input_bind_nest, update);
+      update = Substitute(update, vmap);
+      update = MergeNest(binder.asserts(), update);
+      update = Substitute(update, n.main_vmap);
+      update = MergeNest(update_nest, update);
+      return MergeNest(common, update);
+    }
   }
 }
 
diff --git a/tests/python/unittest/test_schedule_tensorize_init_none.py b/tests/python/unittest/test_schedule_tensorize_init_none.py
new file mode 100644
index 000000000000..ce1d5633173a
--- /dev/null
+++ b/tests/python/unittest/test_schedule_tensorize_init_none.py
@@ -0,0 +1,90 @@
+import tvm
+
+def intrin_gemv(m, n):
+    w = tvm.placeholder((m, n), name='w')
+    x = tvm.placeholder((n,), name='x')
+    k = tvm.reduce_axis((0, n), name='k')
+    z = tvm.compute((m,), lambda i:
+                    tvm.sum(w[i, k] * x[k], axis=k), name='z')
+    Wb = tvm.decl_buffer(w.shape, w.dtype,
+                         name="W",
+                         offset_factor=16,
+                         strides=[tvm.var('ldw'), 1])
+    def intrin_func(ins, outs):
+        ww, xx = ins
+        zz = outs[0]
+        ww_ptr = ww.access_ptr("r")
+        xx_ptr = xx.access_ptr("r")
+        zz_ptr = zz.access_ptr("w")
+        body = tvm.call_packed(
+            "gemv", ww_ptr, xx_ptr, zz_ptr, n, ww.strides[0])
+        update = tvm.call_packed(
+            "gemv_add", ww_ptr, xx_ptr, zz_ptr, n, ww.strides[0])
+        return body, None, update
+
+    with tvm.build_config(data_alignment=16,
+                          offset_factor=16):
+        return tvm.decl_tensor_intrin(z.op, intrin_func,
+                                      binds={w: Wb})
+
+
+def test_tensorize_matmul():
+    n = 1024
+    m = n
+    l = n
+    A = tvm.placeholder((n, l), name='A')
+    B = tvm.placeholder((m, l), name='B')
+    k = tvm.reduce_axis((0, l), name='k')
+    C = tvm.compute((n, m), lambda i, j:
+                    tvm.sum(B[j, k] * A[i, k], axis=k), name='C')
+
+    def check(factor):
+        s = tvm.create_schedule(C.op)
+        x, y = C.op.axis
+        yo, yi = s[C].split(y, factor=factor)
+        gemv = intrin_gemv(factor, l)
+        s[C].tensorize(yi, gemv)
+        s = s.normalize()
+        dom_map = tvm.schedule.InferBound(s)
+        finfer = tvm.get_global_func("test.op.InferTensorizeRegion")
+        out_dom, in_dom = finfer(s[C], dom_map)
+        assert tvm.ir_pass.Equal(out_dom[x].extent, 1)
+        assert tvm.ir_pass.Equal(out_dom[y].extent, factor)
+        assert tvm.ir_pass.Equal(out_dom[y].min, yo * factor)
+        fmatch = tvm.get_global_func("test.op.MatchTensorizeBody")
+        body = fmatch(s[C], out_dom, in_dom, gemv)
+        assert tvm.ir_pass.Equal(tvm.ir_pass.CanonicalSimplify(body[0]),
+                                 tvm.ir_pass.CanonicalSimplify(gemv.op.body[0]))
+        stmt = tvm.schedule.ScheduleOps(s, dom_map)
+        tvm.lower(s, [A, B, C])
+
+
+    def check_rfactor(factor, rfactor):
+        s = tvm.create_schedule(C.op)
+        x, y = C.op.axis
+        rk = C.op.reduce_axis[0]
+        yo, yi = s[C].split(y, factor=factor)
+        ro, ri = s[C].split(rk, factor=rfactor)
+        s[C].reorder(yo, ro, yi, ri)
+        gemv = intrin_gemv(factor, rfactor)
+        s[C].tensorize(yi, gemv)
+        s = s.normalize()
+        dom_map = tvm.schedule.InferBound(s)
+        finfer = tvm.get_global_func("test.op.InferTensorizeRegion")
+        out_dom, in_dom = finfer(s[C], dom_map)
+        assert tvm.ir_pass.Equal(out_dom[x].extent, 1)
+        assert tvm.ir_pass.Equal(out_dom[y].extent, factor)
+        assert tvm.ir_pass.Equal(out_dom[y].min, yo * factor)
+        fmatch = tvm.get_global_func("test.op.MatchTensorizeBody")
+        body = fmatch(s[C], out_dom, in_dom, gemv)
+        assert tvm.ir_pass.Equal(tvm.ir_pass.CanonicalSimplify(body[0]),
+                                 tvm.ir_pass.CanonicalSimplify(gemv.op.body[0]))
+        stmt = tvm.schedule.ScheduleOps(s, dom_map)
+        tvm.lower(s, [A, B, C])
+
+    check(16)
+    check_rfactor(16, 16)
+
+
+if __name__ == "__main__":
+    test_tensorize_matmul()

From 3fd55cc8b95da6d832a694da337a6c18eb1b47f0 Mon Sep 17 00:00:00 2001
From: kun-zh <32951065+kun-zh@users.noreply.github.com>
Date: Fri, 29 Dec 2017 08:31:51 +0800
Subject: [PATCH 063/949] Re-organize the test cases for tensorize. (#736)

* when there is no intrin func, using body for initialization. For issue 714.

* Refine code per review comments, and add a test case.

* Fix lint issues.

* Re-organize the tensorize test cases, and add a new case for none-reset
mode.

* Fix a typo.

* Delete the unit case because merged it into test_schedule_tensorize.py already.
---
 .../unittest/test_schedule_tensorize.py       | 76 ++++++++++++++++
 .../test_schedule_tensorize_init_none.py      | 90 -------------------
 2 files changed, 76 insertions(+), 90 deletions(-)
 delete mode 100644 tests/python/unittest/test_schedule_tensorize_init_none.py

diff --git a/tests/python/unittest/test_schedule_tensorize.py b/tests/python/unittest/test_schedule_tensorize.py
index 71ae493e51ae..ca5836143ef3 100644
--- a/tests/python/unittest/test_schedule_tensorize.py
+++ b/tests/python/unittest/test_schedule_tensorize.py
@@ -40,6 +40,33 @@ def intrin_func(ins, outs):
         return tvm.decl_tensor_intrin(z.op, intrin_func,
                                       binds={w: Wb})
 
+def intrin_gemv_no_reset(m, n):
+    w = tvm.placeholder((m, n), name='w')
+    x = tvm.placeholder((n,), name='x')
+    k = tvm.reduce_axis((0, n), name='k')
+    z = tvm.compute((m,), lambda i:
+                    tvm.sum(w[i, k] * x[k], axis=k), name='z')
+    Wb = tvm.decl_buffer(w.shape, w.dtype,
+                         name="W",
+                         offset_factor=16,
+                         strides=[tvm.var('ldw'), 1])
+    def intrin_func(ins, outs):
+        ww, xx = ins
+        zz = outs[0]
+        ww_ptr = ww.access_ptr("r")
+        xx_ptr = xx.access_ptr("r")
+        zz_ptr = zz.access_ptr("w")
+        body = tvm.call_packed(
+            "gemv", ww_ptr, xx_ptr, zz_ptr, n, ww.strides[0])
+        update = tvm.call_packed(
+            "gemv_add", ww_ptr, xx_ptr, zz_ptr, n, ww.strides[0])
+        return body, None, update
+
+    with tvm.build_config(data_alignment=16,
+                          offset_factor=16):
+        return tvm.decl_tensor_intrin(z.op, intrin_func,
+                                      binds={w: Wb})
+
 
 def test_tensorize_vadd():
     m = 128
@@ -123,8 +150,57 @@ def check_rfactor(factor, rfactor):
         stmt = tvm.schedule.ScheduleOps(s, dom_map)
         tvm.lower(s, [A, B, C])
 
+    def check_rfactor_no_reset(factor, rfactor):
+        s = tvm.create_schedule(C.op)
+        x, y = C.op.axis
+        rk = C.op.reduce_axis[0]
+        yo, yi = s[C].split(y, factor=factor)
+        ro, ri = s[C].split(rk, factor=rfactor)
+        s[C].reorder(yo, ro, yi, ri)
+        gemv = intrin_gemv_no_reset(factor, rfactor)
+        s[C].tensorize(yi, gemv)
+        s = s.normalize()
+        dom_map = tvm.schedule.InferBound(s)
+        finfer = tvm.get_global_func("test.op.InferTensorizeRegion")
+        out_dom, in_dom = finfer(s[C], dom_map)
+        assert tvm.ir_pass.Equal(out_dom[x].extent, 1)
+        assert tvm.ir_pass.Equal(out_dom[y].extent, factor)
+        assert tvm.ir_pass.Equal(out_dom[y].min, yo * factor)
+        fmatch = tvm.get_global_func("test.op.MatchTensorizeBody")
+        body = fmatch(s[C], out_dom, in_dom, gemv)
+        assert tvm.ir_pass.Equal(tvm.ir_pass.CanonicalSimplify(body[0]),
+                                 tvm.ir_pass.CanonicalSimplify(gemv.op.body[0]))
+        stmt = tvm.schedule.ScheduleOps(s, dom_map)
+        tvm.lower(s, [A, B, C])
+
+    def check_rfactor_no_reset_multi_reduction(factor, rfactor):
+        s = tvm.create_schedule(C.op)
+        x, y = C.op.axis
+        rk = C.op.reduce_axis[0]
+        yo, yi = s[C].split(y, factor=factor)
+        ro, ri = s[C].split(rk, factor=rfactor)
+        roo, roi = s[C].split(ro, factor=2)
+        s[C].reorder(yo, roo, roi, yi, ri)
+        gemv = intrin_gemv_no_reset(factor, rfactor)
+        s[C].tensorize(yi, gemv)
+        s = s.normalize()
+        dom_map = tvm.schedule.InferBound(s)
+        finfer = tvm.get_global_func("test.op.InferTensorizeRegion")
+        out_dom, in_dom = finfer(s[C], dom_map)
+        assert tvm.ir_pass.Equal(out_dom[x].extent, 1)
+        assert tvm.ir_pass.Equal(out_dom[y].extent, factor)
+        assert tvm.ir_pass.Equal(out_dom[y].min, yo * factor)
+        fmatch = tvm.get_global_func("test.op.MatchTensorizeBody")
+        body = fmatch(s[C], out_dom, in_dom, gemv)
+        assert tvm.ir_pass.Equal(tvm.ir_pass.CanonicalSimplify(body[0]),
+                                 tvm.ir_pass.CanonicalSimplify(gemv.op.body[0]))
+        stmt = tvm.schedule.ScheduleOps(s, dom_map)
+        tvm.lower(s, [A, B, C])
+
     check(16)
     check_rfactor(16, 16)
+    check_rfactor_no_reset(16, 16)
+    check_rfactor_no_reset_multi_reduction(16, 16)
 
 # This tests whether algorithm and intrinsics expressions are simplified
 # as much as possible first and then checked for equality. See Issue #696
diff --git a/tests/python/unittest/test_schedule_tensorize_init_none.py b/tests/python/unittest/test_schedule_tensorize_init_none.py
deleted file mode 100644
index ce1d5633173a..000000000000
--- a/tests/python/unittest/test_schedule_tensorize_init_none.py
+++ /dev/null
@@ -1,90 +0,0 @@
-import tvm
-
-def intrin_gemv(m, n):
-    w = tvm.placeholder((m, n), name='w')
-    x = tvm.placeholder((n,), name='x')
-    k = tvm.reduce_axis((0, n), name='k')
-    z = tvm.compute((m,), lambda i:
-                    tvm.sum(w[i, k] * x[k], axis=k), name='z')
-    Wb = tvm.decl_buffer(w.shape, w.dtype,
-                         name="W",
-                         offset_factor=16,
-                         strides=[tvm.var('ldw'), 1])
-    def intrin_func(ins, outs):
-        ww, xx = ins
-        zz = outs[0]
-        ww_ptr = ww.access_ptr("r")
-        xx_ptr = xx.access_ptr("r")
-        zz_ptr = zz.access_ptr("w")
-        body = tvm.call_packed(
-            "gemv", ww_ptr, xx_ptr, zz_ptr, n, ww.strides[0])
-        update = tvm.call_packed(
-            "gemv_add", ww_ptr, xx_ptr, zz_ptr, n, ww.strides[0])
-        return body, None, update
-
-    with tvm.build_config(data_alignment=16,
-                          offset_factor=16):
-        return tvm.decl_tensor_intrin(z.op, intrin_func,
-                                      binds={w: Wb})
-
-
-def test_tensorize_matmul():
-    n = 1024
-    m = n
-    l = n
-    A = tvm.placeholder((n, l), name='A')
-    B = tvm.placeholder((m, l), name='B')
-    k = tvm.reduce_axis((0, l), name='k')
-    C = tvm.compute((n, m), lambda i, j:
-                    tvm.sum(B[j, k] * A[i, k], axis=k), name='C')
-
-    def check(factor):
-        s = tvm.create_schedule(C.op)
-        x, y = C.op.axis
-        yo, yi = s[C].split(y, factor=factor)
-        gemv = intrin_gemv(factor, l)
-        s[C].tensorize(yi, gemv)
-        s = s.normalize()
-        dom_map = tvm.schedule.InferBound(s)
-        finfer = tvm.get_global_func("test.op.InferTensorizeRegion")
-        out_dom, in_dom = finfer(s[C], dom_map)
-        assert tvm.ir_pass.Equal(out_dom[x].extent, 1)
-        assert tvm.ir_pass.Equal(out_dom[y].extent, factor)
-        assert tvm.ir_pass.Equal(out_dom[y].min, yo * factor)
-        fmatch = tvm.get_global_func("test.op.MatchTensorizeBody")
-        body = fmatch(s[C], out_dom, in_dom, gemv)
-        assert tvm.ir_pass.Equal(tvm.ir_pass.CanonicalSimplify(body[0]),
-                                 tvm.ir_pass.CanonicalSimplify(gemv.op.body[0]))
-        stmt = tvm.schedule.ScheduleOps(s, dom_map)
-        tvm.lower(s, [A, B, C])
-
-
-    def check_rfactor(factor, rfactor):
-        s = tvm.create_schedule(C.op)
-        x, y = C.op.axis
-        rk = C.op.reduce_axis[0]
-        yo, yi = s[C].split(y, factor=factor)
-        ro, ri = s[C].split(rk, factor=rfactor)
-        s[C].reorder(yo, ro, yi, ri)
-        gemv = intrin_gemv(factor, rfactor)
-        s[C].tensorize(yi, gemv)
-        s = s.normalize()
-        dom_map = tvm.schedule.InferBound(s)
-        finfer = tvm.get_global_func("test.op.InferTensorizeRegion")
-        out_dom, in_dom = finfer(s[C], dom_map)
-        assert tvm.ir_pass.Equal(out_dom[x].extent, 1)
-        assert tvm.ir_pass.Equal(out_dom[y].extent, factor)
-        assert tvm.ir_pass.Equal(out_dom[y].min, yo * factor)
-        fmatch = tvm.get_global_func("test.op.MatchTensorizeBody")
-        body = fmatch(s[C], out_dom, in_dom, gemv)
-        assert tvm.ir_pass.Equal(tvm.ir_pass.CanonicalSimplify(body[0]),
-                                 tvm.ir_pass.CanonicalSimplify(gemv.op.body[0]))
-        stmt = tvm.schedule.ScheduleOps(s, dom_map)
-        tvm.lower(s, [A, B, C])
-
-    check(16)
-    check_rfactor(16, 16)
-
-
-if __name__ == "__main__":
-    test_tensorize_matmul()

From dccc1e3fc4409349b5394960dee17ca2f376dc4d Mon Sep 17 00:00:00 2001
From: masahi <masahi129@gmail.com>
Date: Fri, 29 Dec 2017 10:53:05 +0900
Subject: [PATCH 064/949] Let CUDNN choose the best algo (#734)

* use cudnn findalgo to choose the best algo

* fix lint
---
 python/tvm/contrib/cudnn.py       | 79 ++++++++++++++++++++++++-
 src/contrib/cudnn/conv_forward.cc | 98 ++++++++++++++++++++++++++++++-
 topi/python/topi/cuda/conv2d.py   |  2 +-
 3 files changed, 176 insertions(+), 3 deletions(-)

diff --git a/python/tvm/contrib/cudnn.py b/python/tvm/contrib/cudnn.py
index e728e42f614e..5200f3193079 100644
--- a/python/tvm/contrib/cudnn.py
+++ b/python/tvm/contrib/cudnn.py
@@ -220,6 +220,70 @@ def conv2d_output_shape(tensor_format,
     return list(oshape)
 
 
+def conv2d_find_algo(tensor_format,
+                     pad_h,
+                     pad_w,
+                     stride_h,
+                     stride_w,
+                     dilation_h,
+                     dilation_w,
+                     x_shape,
+                     w_shape,
+                     y_shape):
+    """Choose the best algo for the given input.
+
+    Paramters
+    ---------
+    tensor_format: int
+        0: CUDNN_TENSOR_NCHW
+        1: CUDNN_TENSOR_NHWC
+        2: CUDNN_TENSOR_NCHW_VECT_C
+    pad_h: int
+        height pad
+    pad_w: int
+        weight pad
+    stride_h: int
+        height stride
+    stride_w: int
+        width stride
+    dilation_h: int
+        height dilation
+    dilation_w: int
+        width dilation
+    x_shape: list
+        input shape
+    w_shape: list
+        weight shape
+    y_shape: list
+        output shape
+
+    Returns
+    -------
+    algo: int
+        algo chosen by CUDNN
+    """
+    func = _get_global_func("tvm.contrib.cudnn.conv2d.find_algo")
+    return func(tensor_format,
+                pad_h,
+                pad_w,
+                stride_h,
+                stride_w,
+                dilation_h,
+                dilation_w,
+                x_shape[0].value,
+                x_shape[1].value,
+                x_shape[2].value,
+                x_shape[3].value,
+                w_shape[0].value,
+                w_shape[1].value,
+                w_shape[2].value,
+                w_shape[3].value,
+                y_shape[0],
+                y_shape[1],
+                y_shape[2],
+                y_shape[3])
+
+
 def conv2d_forward(x,
                    w,
                    stride_h=1,
@@ -230,7 +294,7 @@ def conv2d_forward(x,
                    dilation_w=1,
                    conv_mode=1,
                    tensor_format=0,
-                   algo=0):
+                   algo=-1):
     """Create an extern op that compute 2D convolution with CuDNN
 
     Parameters
@@ -260,6 +324,7 @@ def conv2d_forward(x,
         2: CUDNN_TENSOR_NCHW_VECT_C
     algo: int
         Forward algorithm, get index from ```algo_to_index``` function
+        if algo == -1, the best algo will be chosen by CUDNN
 
     Returns
     -------
@@ -275,6 +340,18 @@ def conv2d_forward(x,
                                  dilation_w,
                                  list(x.shape),
                                  list(w.shape))
+    if algo == -1:
+        algo = conv2d_find_algo(tensor_format,
+                                pad_h,
+                                pad_w,
+                                stride_h,
+                                stride_w,
+                                dilation_h,
+                                dilation_w,
+                                list(x.shape),
+                                list(w.shape),
+                                oshape)
+
     return _api.extern(
         oshape, [x, w],
         lambda ins, outs: _intrin.call_packed(
diff --git a/src/contrib/cudnn/conv_forward.cc b/src/contrib/cudnn/conv_forward.cc
index 480a789303c6..4cd25f0c2fe4 100644
--- a/src/contrib/cudnn/conv_forward.cc
+++ b/src/contrib/cudnn/conv_forward.cc
@@ -153,7 +153,103 @@ TVM_REGISTER_GLOBAL("tvm.contrib.cudnn.conv2d.output_shape")
                                                    static_cast<int*>(out_shape) + 1,
                                                    static_cast<int*>(out_shape) + 2,
                                                    static_cast<int*>(out_shape) + 3));
-  });
+});
+
+
+TVM_REGISTER_GLOBAL("tvm.contrib.cudnn.conv2d.find_algo")
+.set_body([](TVMArgs args, TVMRetValue *ret) {
+  CuDNNThreadEntry* entry_ptr = CuDNNThreadEntry::ThreadLocal();
+  int format = args[0];
+  int pad_h = args[1];
+  int pad_w = args[2];
+  int stride_h = args[3];
+  int stride_w = args[4];
+  int dilation_h = args[5];
+  int dilation_w = args[6];
+  int x_dim0 = args[7];
+  int x_dim1 = args[8];
+  int x_dim2 = args[9];
+  int x_dim3 = args[10];
+  int w_dim0 = args[11];
+  int w_dim1 = args[12];
+  int w_dim2 = args[13];
+  int w_dim3 = args[14];
+  int y_dim0 = args[15];
+  int y_dim1 = args[16];
+  int y_dim2 = args[17];
+  int y_dim3 = args[18];
+
+  // Set Format
+  entry_ptr->conv_entry.tensor_format = static_cast<cudnnTensorFormat_t>(format);
+  // conv desc
+  CUDNN_CALL(cudnnSetConvolution2dDescriptor(entry_ptr->conv_entry.conv_desc,
+                                             pad_h,
+                                             pad_w,
+                                             stride_h,
+                                             stride_w,
+                                             dilation_h,
+                                             dilation_w,
+                                             CUDNN_CROSS_CORRELATION,
+                                             entry_ptr->conv_entry.data_type));
+  // input desc
+  CUDNN_CALL(cudnnSetTensor4dDescriptor(entry_ptr->conv_entry.input_desc,
+                                        entry_ptr->conv_entry.tensor_format,
+                                        CUDNN_DATA_FLOAT,
+                                        x_dim0,
+                                        x_dim1,
+                                        x_dim2,
+                                        x_dim3));
+  // filter desc
+  CUDNN_CALL(cudnnSetFilter4dDescriptor(entry_ptr->conv_entry.filter_desc,
+                                        CUDNN_DATA_FLOAT,
+                                        CUDNN_TENSOR_NCHW,
+                                        w_dim0,
+                                        w_dim1,
+                                        w_dim2,
+                                        w_dim3));
+
+  // output desc
+  CUDNN_CALL(cudnnSetTensor4dDescriptor(entry_ptr->conv_entry.output_desc,
+                                        entry_ptr->conv_entry.tensor_format,
+                                        entry_ptr->conv_entry.data_type,
+                                        y_dim0,
+                                        y_dim1,
+                                        y_dim2,
+                                        y_dim3));
+
+  int returned_algo_count = 0;
+  cudnnConvolutionFwdAlgoPerf_t perf_results[CUDNN_CONVOLUTION_FWD_ALGO_COUNT];
+  CUDNN_CALL(cudnnFindConvolutionForwardAlgorithm(entry_ptr->handle,
+                                                  entry_ptr->conv_entry.input_desc,
+                                                  entry_ptr->conv_entry.filter_desc,
+                                                  entry_ptr->conv_entry.conv_desc,
+                                                  entry_ptr->conv_entry.output_desc,
+                                                  CUDNN_CONVOLUTION_FWD_ALGO_COUNT,
+                                                  &returned_algo_count,
+                                                  perf_results));
+
+  const std::vector<std::string> fwd_algo_names{
+      "CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_GEMM",
+      "CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_PRECOMP_GEMM",
+      "CUDNN_CONVOLUTION_FWD_ALGO_GEMM",
+      "CUDNN_CONVOLUTION_FWD_ALGO_DIRECT",
+      "CUDNN_CONVOLUTION_FWD_ALGO_FFT",
+      "CUDNN_CONVOLUTION_FWD_ALGO_FFT_TILING",
+      "CUDNN_CONVOLUTION_FWD_ALGO_WINOGRAD",
+      "CUDNN_CONVOLUTION_FWD_ALGO_WINOGRAD_NONFUSED"
+  };
+
+  auto best_algo = perf_results[0].algo;
+  LOG(INFO) << "\tCUDNN Found " << returned_algo_count
+            << " fwd algorithms, choosing " << fwd_algo_names[best_algo];
+  for (int i = 0; i < returned_algo_count; ++i) {
+    LOG(INFO) << "\t\t" << i << ") " << fwd_algo_names[perf_results[i].algo]
+              << " - time: " << perf_results[i].time << " ms"
+              << ", Memory: " << perf_results[i].memory;
+  }
+
+  ret[0] = best_algo;
+});
 
 }  // namespace contrib
 }  // namespace tvm
diff --git a/topi/python/topi/cuda/conv2d.py b/topi/python/topi/cuda/conv2d.py
index 62b5642abbab..2641bfe490c4 100644
--- a/topi/python/topi/cuda/conv2d.py
+++ b/topi/python/topi/cuda/conv2d.py
@@ -56,7 +56,7 @@ def conv2d_cuda(data, kernel, stride, padding, layout='NCHW', out_dtype='float32
                                     1,  # dilation_w
                                     conv_mode=1,
                                     tensor_format=tensor_format,
-                                    algo=0)
+                                    algo=-1) # let CUDNN choose the best algo
     elif layout == 'NCHW':
         return topi.nn.conv2d_nchw(data, kernel, stride, padding, out_dtype)
     elif layout == 'HWCN':

From 8ecef3d5ac180c9b78497db058e134d2b2ca654b Mon Sep 17 00:00:00 2001
From: xqdan <danxiaoqiang@126.com>
Date: Fri, 29 Dec 2017 16:56:27 +0800
Subject: [PATCH 065/949] enable partition const loop with build flag (#732)

* [SCHEDULE]enable partition const loop with build flag (#719)

    * enable partition loop with build flag

    * add a testcase, and modify LoopPartition related cases

*     * add document for split_const_loop
---
 include/tvm/build_module.h                    |  3 ++
 include/tvm/ir_pass.h                         |  3 +-
 python/tvm/build_module.py                    |  6 +++-
 src/api/api_pass.cc                           |  2 +-
 src/codegen/build_module.cc                   |  2 +-
 src/pass/loop_partition.cc                    | 13 ++++----
 tests/python/unittest/test_codegen_device.py  |  2 +-
 .../unittest/test_pass_loop_partition.py      | 30 ++++++++++++++-----
 8 files changed, 44 insertions(+), 17 deletions(-)

diff --git a/include/tvm/build_module.h b/include/tvm/build_module.h
index a1563e8e7447..75062e819748 100644
--- a/include/tvm/build_module.h
+++ b/include/tvm/build_module.h
@@ -116,6 +116,9 @@ struct BuildConfig {
   /*! \brief Whether to detect global barrier */
   bool detect_global_barrier = false;
 
+  /*! \brief Whether to partition const loop */
+  bool partition_const_loop = false;
+
   BuildConfig() {
   }
 };
diff --git a/include/tvm/ir_pass.h b/include/tvm/ir_pass.h
index e763a75e7ee0..525b36d1b6b1 100644
--- a/include/tvm/ir_pass.h
+++ b/include/tvm/ir_pass.h
@@ -289,9 +289,10 @@ Stmt StorageRewrite(Stmt stmt);
 /*!
  * \brief partition loops in the stmt
  * \param stmt The stmt to do loop partition
+ * \param split_const_loop flag to enable partition for const loop
  * \return Transformed stmt.
  */
-Stmt LoopPartition(Stmt stmt);
+Stmt LoopPartition(Stmt stmt, bool split_const_loop);
 
 /*!
  * \brief Detect and insert sync points to co-processor.
diff --git a/python/tvm/build_module.py b/python/tvm/build_module.py
index 083074a6676e..fe6b01bb4d8c 100644
--- a/python/tvm/build_module.py
+++ b/python/tvm/build_module.py
@@ -32,6 +32,7 @@ class BuildConfig(object):
         "auto_unroll_max_extent": 0,
         "unroll_explicit": True,
         "detect_global_barrier": False,
+        "partition_const_loop": False,
         "offset_factor": 0,
         "data_alignment": -1,
         "restricted_func": True,
@@ -88,6 +89,9 @@ def build_config(**kwargs):
     detect_global_barrier: bool, default=True
         Whether detect global barrier.
 
+    partition_const_loop: bool, default=False
+        Whether partition const loop
+
     data_alignment: int, optional
         The alignment of data pointer in bytes.
         If -1 is passed, the alignment will be set to TVM's internal default.
@@ -219,7 +223,7 @@ def lower(sch,
         stmt = f(stmt)
     # Phase 2
     if not simple_mode:
-        stmt = ir_pass.LoopPartition(stmt)
+        stmt = ir_pass.LoopPartition(stmt, cfg.partition_const_loop)
     stmt = ir_pass.VectorizeLoop(stmt)
     stmt = ir_pass.InjectVirtualThread(stmt)
     stmt = ir_pass.InjectDoubleBuffer(stmt, cfg.double_buffer_split_loop)
diff --git a/src/api/api_pass.cc b/src/api/api_pass.cc
index 23deb03af482..06c6b621abde 100644
--- a/src/api/api_pass.cc
+++ b/src/api/api_pass.cc
@@ -119,7 +119,7 @@ REGISTER_PASS1(LowerStorageAccessInfo);
 REGISTER_PASS1(InjectVirtualThread);
 REGISTER_PASS1(InjectPrefetch);
 REGISTER_PASS2(InjectDoubleBuffer);
-REGISTER_PASS1(LoopPartition);
+REGISTER_PASS2(LoopPartition);
 REGISTER_PASS1(RemoveNoOp);
 REGISTER_PASS2(SplitPipeline);
 REGISTER_PASS2(LiftAttrScope);
diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc
index d936b873bcd2..2c419d43da08 100644
--- a/src/codegen/build_module.cc
+++ b/src/codegen/build_module.cc
@@ -208,7 +208,7 @@ Stmt BuildStmt(Schedule sch,
   stmt = ir::StorageFlatten(stmt, out_binds, 64);
   stmt = ir::CanonicalSimplify(stmt);
   if (loop_partition) {
-    stmt = ir::LoopPartition(stmt);
+    stmt = ir::LoopPartition(stmt, config.partition_const_loop);
   }
   stmt = ir::VectorizeLoop(stmt);
   stmt = ir::InjectVirtualThread(stmt);
diff --git a/src/pass/loop_partition.cc b/src/pass/loop_partition.cc
index 0834fe6ab3df..ff6103cd6e02 100644
--- a/src/pass/loop_partition.cc
+++ b/src/pass/loop_partition.cc
@@ -45,10 +45,12 @@ bool ExprUseVars(Expr expr, const std::unordered_set<const Variable*>& vars) {
 class CandidateSelector final : public IRVisitor {
  public:
   using VarIsUsed = bool;
-  CandidateSelector() {}
+  explicit CandidateSelector(bool split_const_loop)
+      : split_const_loop_(split_const_loop) {}
 
   void Visit_(const For* op) {
-    if (!is_const(op->min) || !is_const(op->extent)) {
+    // partition const loop when sets split_const_loop_
+    if (!is_const(op->min) || !is_const(op->extent) || split_const_loop_) {
       const Variable* var = op->loop_var.get();
       record_.insert({var, false});
       IRVisitor::Visit_(op);
@@ -67,7 +69,7 @@ class CandidateSelector final : public IRVisitor {
       CHECK(iv);
       Var var = iv->var;
       runtime::ThreadScope scope = runtime::ThreadScope::make(iv->thread_tag);
-      if ((scope.rank == 0) && !is_const(op->value)) {
+      if ((scope.rank == 0) && (!is_const(op->value) || split_const_loop_)) {
         record_.insert({var.get(), false});
         IRVisitor::Visit_(op);
         if (record_.at(var.get()) && !no_split_) {
@@ -115,6 +117,7 @@ class CandidateSelector final : public IRVisitor {
  private:
   bool in_likely_{false};
   bool no_split_{false};
+  bool split_const_loop_{false};
   std::unordered_map<const Variable*, VarIsUsed> record_;
 };
 
@@ -392,8 +395,8 @@ class RemoveLikelyTags : public IRMutator {
   }
 };
 
-Stmt LoopPartition(Stmt stmt) {
-  CandidateSelector selector;
+Stmt LoopPartition(Stmt stmt, bool split_const_loop) {
+  CandidateSelector selector(split_const_loop);
   selector.Visit(stmt);
   stmt = LoopPartitioner(selector.candidates).Mutate(stmt);
   stmt = RemoveLikelyTags().Mutate(stmt);
diff --git a/tests/python/unittest/test_codegen_device.py b/tests/python/unittest/test_codegen_device.py
index 56e3fc81910f..773c2c890ef3 100644
--- a/tests/python/unittest/test_codegen_device.py
+++ b/tests/python/unittest/test_codegen_device.py
@@ -27,7 +27,7 @@ def test_add_pipeline():
     Ab = tvm.decl_buffer(A.shape, A.dtype, name='A')
     Bb = tvm.decl_buffer(B.shape, B.dtype, name='B')
     Db = tvm.decl_buffer(D.shape, D.dtype, name='D')
-    stmt = tvm.ir_pass.LoopPartition(stmt)
+    stmt = tvm.ir_pass.LoopPartition(stmt, False)
     stmt = tvm.ir_pass.StorageFlatten(stmt, {A: Ab, B:Bb, D:Db}, 64)
     stmt = tvm.ir_pass.Simplify(stmt)
     fapi = tvm.ir_pass.MakeAPI(stmt, "myadd", [Ab, Bb, Db], 0, True)
diff --git a/tests/python/unittest/test_pass_loop_partition.py b/tests/python/unittest/test_pass_loop_partition.py
index a1f337b40214..a1025e1f662c 100644
--- a/tests/python/unittest/test_pass_loop_partition.py
+++ b/tests/python/unittest/test_pass_loop_partition.py
@@ -19,7 +19,7 @@ def lower(sch, args):
     sch = sch.normalize()
     bounds = tvm.schedule.InferBound(sch)
     stmt = tvm.schedule.ScheduleOps(sch, bounds)
-    stmt = tvm.ir_pass.LoopPartition(stmt)
+    stmt = tvm.ir_pass.LoopPartition(stmt, False)
     stmt = tvm.ir_pass.StorageFlatten(stmt, binds, 64)
     stmt = tvm.ir_pass.CanonicalSimplify(stmt)
     stmt = tvm.ir_pass.VectorizeLoop(stmt)
@@ -37,7 +37,22 @@ def test_basic():
 
     bounds = tvm.schedule.InferBound(s)
     stmt = tvm.schedule.ScheduleOps(s, bounds)
-    stmt = tvm.ir_pass.LoopPartition(stmt)
+    stmt = tvm.ir_pass.LoopPartition(stmt, False)
+    stmt = tvm.ir_pass.Simplify(stmt)
+    assert('if' not in str(stmt.body.body.body.first))
+
+def test_const_loop():
+    n = 21
+    A = tvm.placeholder((n, ), name='A')
+    B = tvm.placeholder((n, ), name='B')
+
+    T = tvm.compute((n, ), lambda i: A[i]+B[i])
+    s = tvm.create_schedule(T.op)
+    xo, xi = s[T].split(T.op.axis[0], factor=4)
+
+    bounds = tvm.schedule.InferBound(s)
+    stmt = tvm.schedule.ScheduleOps(s, bounds)
+    stmt = tvm.ir_pass.LoopPartition(stmt, True)
     stmt = tvm.ir_pass.Simplify(stmt)
     assert('if' not in str(stmt.body.body.body.first))
 
@@ -53,7 +68,7 @@ def test_multi_loop():
                 with ib.else_scope():
                     ib.emit(tvm.make.Evaluate(n))
     stmt = ib.get()
-    stmt = tvm.ir_pass.LoopPartition(stmt)
+    stmt = tvm.ir_pass.LoopPartition(stmt, False)
     stmt = tvm.ir_pass.Simplify(stmt)
     assert(not any(collect_visit(stmt.body.first, lambda x: isinstance(x, tvm.stmt.IfThenElse))))
 
@@ -73,7 +88,7 @@ def test_multi_if():
                 with ib.else_scope():
                     ib.emit(tvm.make.Evaluate(n))
     stmt = ib.get()
-    stmt = tvm.ir_pass.LoopPartition(stmt)
+    stmt = tvm.ir_pass.LoopPartition(stmt, False)
     stmt = tvm.ir_pass.Simplify(stmt)
     assert('if' not in str(stmt.body.first))
 
@@ -92,7 +107,7 @@ def test_thread_axis():
 
     bounds = tvm.schedule.InferBound(s)
     stmt = tvm.schedule.ScheduleOps(s, bounds)
-    stmt = tvm.ir_pass.LoopPartition(stmt)
+    stmt = tvm.ir_pass.LoopPartition(stmt, False)
     stmt = tvm.ir_pass.Simplify(stmt)
     assert('if' not in str(stmt.body.body.body.first))
 
@@ -127,7 +142,7 @@ def test_select():
         ib.emit(tvm.make.Evaluate(
           tvm.make.Select(ib.likely(i*4+j<n), m, n)))
     stmt = ib.get()
-    stmt = tvm.ir_pass.LoopPartition(stmt)
+    stmt = tvm.ir_pass.LoopPartition(stmt, False)
     stmt = tvm.ir_pass.Simplify(stmt)
     assert(not any(collect_visit(stmt.first, lambda x: isinstance(x, tvm.expr.Select))))
 
@@ -158,12 +173,13 @@ def test_everything_during_deduction():
                 # this guard will produce everything during deduction
                 ib.emit(tvm.make.Evaluate(m))
     stmt = ib.get()
-    stmt = tvm.ir_pass.LoopPartition(stmt)
+    stmt = tvm.ir_pass.LoopPartition(stmt, False)
     stmt = tvm.ir_pass.Simplify(stmt)
     assert(isinstance(stmt.body.body, tvm.stmt.IfThenElse))
 
 if __name__ == "__main__":
     test_basic()
+    test_const_loop()
     test_multi_loop()
     test_multi_if()
     test_thread_axis()

From d661923aa446852c33022c544ec8865557bb3767 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 31 Dec 2017 21:57:23 +0800
Subject: [PATCH 066/949] [WEB] update web runtime to latest emcc (#742)

---
 Makefile                         |  6 ++++--
 python/tvm/contrib/emscripten.py | 16 +++++++++-------
 web/tvm_runtime.js               | 12 +++++++++---
 3 files changed, 22 insertions(+), 12 deletions(-)

diff --git a/Makefile b/Makefile
index 4a6ac7f3c8f3..7b9f99d8ebdb 100644
--- a/Makefile
+++ b/Makefile
@@ -29,8 +29,10 @@ INCLUDE_FLAGS = -Iinclude -I$(DLPACK_PATH)/include -I$(DMLC_CORE_PATH)/include -
 CFLAGS = -std=c++11 -Wall -O2 $(INCLUDE_FLAGS) -fPIC
 FRAMEWORKS =
 OBJCFLAGS = -fno-objc-arc
-EMCC_FLAGS= -s RESERVED_FUNCTION_POINTERS=2 -s NO_EXIT_RUNTIME=1 -s MAIN_MODULE=1 -DDMLC_LOG_STACK_TRACE=0\
-	 -std=c++11 -Oz $(INCLUDE_FLAGS)
+EMCC_FLAGS= -std=c++11 -DDMLC_LOG_STACK_TRACE=0\
+	-Oz -s RESERVED_FUNCTION_POINTERS=2 -s MAIN_MODULE=1 -s NO_EXIT_RUNTIME=1\
+	-s EXTRA_EXPORTED_RUNTIME_METHODS="['cwrap','getValue','setValue','addFunction']"\
+	$(INCLUDE_FLAGS)
 
 # llvm configuration
 ifdef LLVM_CONFIG
diff --git a/python/tvm/contrib/emscripten.py b/python/tvm/contrib/emscripten.py
index d747b5f039f8..d770ce1161f1 100644
--- a/python/tvm/contrib/emscripten.py
+++ b/python/tvm/contrib/emscripten.py
@@ -26,13 +26,16 @@ def create_js(output,
         The compile string.
     """
     cmd = [cc]
-    cmd += ["-s", "RESERVED_FUNCTION_POINTERS=2"]
-    cmd += ["-s", "NO_EXIT_RUNTIME=1"]
     cmd += ["-Oz"]
-    cmd += ["-o", output]
-    if side_module:
+    if not side_module:
+        cmd += ["-s", "RESERVED_FUNCTION_POINTERS=2"]
+        cmd += ["-s", "NO_EXIT_RUNTIME=1"]
+        extra_methods = ['cwrap', 'getValue', 'setValue', 'addFunction']
+        cfg = "[" + (','.join("\'%s\'" % x for x in extra_methods)) + "]"
+        cmd += ["-s", "EXTRA_EXPORTED_RUNTIME_METHODS=" + cfg]
+    else:
         cmd += ["-s", "SIDE_MODULE=1"]
-
+    cmd += ["-o", output]
     objects = [objects] if isinstance(objects, str) else objects
     with_runtime = False
     for obj in objects:
@@ -47,9 +50,8 @@ def create_js(output,
     if options:
         cmd += options
 
-    args = ' '.join(cmd)
     proc = subprocess.Popen(
-        args, shell=True,
+        cmd,
         stdout=subprocess.PIPE,
         stderr=subprocess.STDOUT)
     (out, _) = proc.communicate()
diff --git a/web/tvm_runtime.js b/web/tvm_runtime.js
index 288f6e16014b..f69b1d57a11d 100644
--- a/web/tvm_runtime.js
+++ b/web/tvm_runtime.js
@@ -493,7 +493,8 @@ var tvm_runtime = tvm_runtime || {};
     }
     var fptrInvokeCallback = null;
     var fptrFreeCallback = null;
-    if (typeof Runtime !== "undefined") {
+    if (typeof Runtime !== "undefined" &&
+        typeof Runtime.addFunction !== "undefined") {
       fptrInvokeCallback = Runtime.addFunction(invokeCallback);
       fptrFreeCallback = Runtime.addFunction(freeCallback);
     }
@@ -513,7 +514,8 @@ var tvm_runtime = tvm_runtime || {};
      */
     this.convertFunc = function(f) {
       if (isPackedFunc(f)) return f;
-      CHECK(fptrInvokeCallback !== null, "Emscripten Runtime is not available");
+      CHECK(fptrInvokeCallback !== null,
+            "Emscripten Runtime addFunction is not available");
       var fid;
       if (freeFuncId.length != 0) {
         fid = freeFuncId.pop();
@@ -1086,7 +1088,11 @@ var tvm_runtime = tvm_runtime || {};
   this.create = function(Module) {
     var tvm = {};
     tvm.Module = Module;
-    tvm.Runtime = Module.Runtime;
+    if (typeof Module.addFunction !== "undefined") {
+      tvm.Runtime = Module;
+    } else {
+      tvm.Runtime = Module.Runtime;
+    }
     TVMRuntime.apply(tvm);
     return tvm;
   };

From 86d5de6a166922d1422d24d252d9190f94a32176 Mon Sep 17 00:00:00 2001
From: xqdan <danxiaoqiang@126.com>
Date: Sun, 31 Dec 2017 21:58:20 +0800
Subject: [PATCH 067/949] Support automatically Name Loop Variable in IRBuilder
 (#716) (#741)

* [SCHEDULE]enable partition const loop with build flag (#719)

    * enable partition loop with build flag

    * add a testcase, and modify LoopPartition related cases

*     * add document for split_const_loop

* [IRbuild]Support automatically Name Loop Variable in IRBuilder (#719)

    * add idx_num in class

* using typical index [i, j, k] first, then i_suffix

* keep inputs names

* fix lint

* improve comment of name

* fix lint
---
 python/tvm/ir_builder.py | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/python/tvm/ir_builder.py b/python/tvm/ir_builder.py
index 1888cd9f1d18..dcf5b9d0976b 100644
--- a/python/tvm/ir_builder.py
+++ b/python/tvm/ir_builder.py
@@ -97,6 +97,7 @@ class IRBuilder(object):
     """
     def __init__(self):
         self._seq_stack = [[]]
+        self.nidx = 0
 
     def _pop_seq(self):
         """Pop sequence from stack"""
@@ -167,7 +168,8 @@ def for_range(self, begin, end, name="i", dtype="int32", for_type="serial"):
             The end iteration scope
 
         name : str, optional
-            The name of iteration variable
+            The name of iteration variable, if no input names,
+            using typical index names i, j, k, then i_nidx
 
         dtype : str, optional
             The data type of iteration variable.
@@ -189,6 +191,9 @@ def for_range(self, begin, end, name="i", dtype="int32", for_type="serial"):
             with ib.for_range(1, 10, name="i") as i:
                 x[i] = x[i - 1] + 1
         """
+        if name == 'i':
+            name = chr(ord(name) + self.nidx) if self.nidx < 3 else name + "_" + str(self.nidx - 3)
+            self.nidx += 1
         self._seq_stack.append([])
         loop_var = _api.var(name, dtype=dtype)
         extent = end if begin == 0 else _pass.Simplify(end - begin)

From 94128c7c766a1462c5dff7e645017e18fce6ecf5 Mon Sep 17 00:00:00 2001
From: masahi <masahi129@gmail.com>
Date: Tue, 2 Jan 2018 22:52:53 +0900
Subject: [PATCH 068/949] [CONTRIB] cuBLAS integration (#744)

* add cublas support

* integrate cublas to topi dense

* add cublas error check

* minor fix

* fix lint

* remove topi import from contrib unittest
---
 Makefile                            |  1 +
 make/config.mk                      |  3 ++
 make/contrib/cublas.mk              |  8 +++
 python/tvm/contrib/cblas.py         |  2 +-
 python/tvm/contrib/cublas.py        | 32 ++++++++++++
 python/tvm/target.py                |  3 +-
 src/contrib/cblas/cblas.cc          |  2 +-
 src/contrib/cublas/cublas.cc        | 81 +++++++++++++++++++++++++++++
 tests/python/contrib/test_cublas.py | 33 ++++++++++++
 tests/python/contrib/test_cudnn.py  |  3 +-
 topi/python/topi/cuda/__init__.py   |  2 +-
 topi/python/topi/cuda/dense.py      | 43 +++++++++++++++
 topi/python/topi/nn/dense.py        | 28 ++++++++--
 13 files changed, 232 insertions(+), 9 deletions(-)
 create mode 100644 make/contrib/cublas.mk
 create mode 100644 python/tvm/contrib/cublas.py
 create mode 100644 src/contrib/cublas/cublas.cc
 create mode 100644 tests/python/contrib/test_cublas.py

diff --git a/Makefile b/Makefile
index 7b9f99d8ebdb..875c99b8657d 100644
--- a/Makefile
+++ b/Makefile
@@ -138,6 +138,7 @@ include make/contrib/nnpack.mk
 include make/contrib/cudnn.mk
 include make/contrib/miopen.mk
 include make/contrib/mps.mk
+include make/contrib/cublas.mk
 
 ifdef ADD_CFLAGS
 	CFLAGS += $(ADD_CFLAGS)
diff --git a/make/config.mk b/make/config.mk
index 837db40ebff3..778d52025f92 100644
--- a/make/config.mk
+++ b/make/config.mk
@@ -77,3 +77,6 @@ USE_MIOPEN = 0
 
 # Whether use MPS
 USE_MPS = 0
+
+# Whether use cuBLAS
+USE_CUBLAS = 0
diff --git a/make/contrib/cublas.mk b/make/contrib/cublas.mk
new file mode 100644
index 000000000000..8274dc0bb378
--- /dev/null
+++ b/make/contrib/cublas.mk
@@ -0,0 +1,8 @@
+CUBLAS_CONTRIB_SRC = $(wildcard src/contrib/cublas/*.cc)
+CUBLAS_CONTRIB_OBJ = $(patsubst src/%.cc, build/%.o, $(CUBLAS_CONTRIB_SRC))
+
+ifeq ($(USE_CUBLAS), 1)
+CFLAGS += -DTVM_USE_CUBLAS=1
+ADD_LDFLAGS += -lcublas
+RUNTIME_DEP += $(CUBLAS_CONTRIB_OBJ)
+endif
diff --git a/python/tvm/contrib/cblas.py b/python/tvm/contrib/cblas.py
index ae7b48d82f37..17af941449ea 100644
--- a/python/tvm/contrib/cblas.py
+++ b/python/tvm/contrib/cblas.py
@@ -1,4 +1,4 @@
-"""External function interface to BLAS libraroes."""
+"""External function interface to BLAS libraries."""
 from __future__ import absolute_import as _abs
 
 from .. import api as _api
diff --git a/python/tvm/contrib/cublas.py b/python/tvm/contrib/cublas.py
new file mode 100644
index 000000000000..eda09fead359
--- /dev/null
+++ b/python/tvm/contrib/cublas.py
@@ -0,0 +1,32 @@
+"""External function interface to cuBLAS libraries."""
+from __future__ import absolute_import as _abs
+
+from .. import api as _api
+from .. import intrin as _intrin
+
+def matmul(lhs, rhs, transa=False, transb=False):
+    """Create an extern op that compute matrix mult of A and rhs with cuBLAS
+
+    Parameters
+    ----------
+    lhs : Tensor
+        The left matrix operand
+    rhs : Tensor
+        The right matrix operand
+    transa : bool
+        Whether transpose lhs
+    transb : bool
+        Whether transpose rhs
+
+    Returns
+    -------
+    C : Tensor
+        The result tensor.
+    """
+    n = lhs.shape[1] if transa else lhs.shape[0]
+    m = rhs.shape[0] if transb else rhs.shape[1]
+    return _api.extern(
+        (n, m), [lhs, rhs],
+        lambda ins, outs: _intrin.call_packed(
+            "tvm.contrib.cublas.matmul",
+            ins[0], ins[1], outs[0], transa, transb), name="C")
diff --git a/python/tvm/target.py b/python/tvm/target.py
index 092dbc0d1165..8f5f4bc87852 100644
--- a/python/tvm/target.py
+++ b/python/tvm/target.py
@@ -94,7 +94,8 @@ def __init__(self,
         # Parse device option
         for item in self.options:
             if item.startswith("-libs="):
-                self.libs.append(item.split("=")[1])
+                libs = item.split("=")[1]
+                self.libs += libs.split(",")
             elif item.startswith("-device="):
                 self.device_name = item.split("=")[1]
         # Target query searchs device name first
diff --git a/src/contrib/cblas/cblas.cc b/src/contrib/cblas/cblas.cc
index 9ce85ae4fd95..c2e981ba5d89 100644
--- a/src/contrib/cblas/cblas.cc
+++ b/src/contrib/cblas/cblas.cc
@@ -38,7 +38,7 @@ TVM_REGISTER_GLOBAL("tvm.contrib.cblas.matmul")
                 transa ? CblasTrans : CblasNoTrans,
                 transb ? B->shape[0] : B->shape[1],
                 transa ? A->shape[1] : A->shape[0],
-                transa ? B->shape[1] : B->shape[0],
+                transb ? B->shape[1] : B->shape[0],
                 1.0f,
                 reinterpret_cast<float*>(static_cast<char*>(B->data) + B->byte_offset),
                 B->shape[1],
diff --git a/src/contrib/cublas/cublas.cc b/src/contrib/cublas/cublas.cc
new file mode 100644
index 000000000000..4171aadf6381
--- /dev/null
+++ b/src/contrib/cublas/cublas.cc
@@ -0,0 +1,81 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file Use external cblas library call.
+ */
+#include <tvm/runtime/registry.h>
+#include <tvm/runtime/util.h>
+#include <dmlc/logging.h>
+
+extern "C" {
+#include <cublas_v2.h>
+}
+
+namespace tvm {
+namespace contrib {
+
+using namespace runtime;
+
+#ifndef CHECK_CUBLAS_ERROR
+#define CHECK_CUBLAS_ERROR(error) \
+if (error != CUBLAS_STATUS_SUCCESS) { \
+  fprintf(stderr, "cuBLAS error: "); \
+  if (error == CUBLAS_STATUS_NOT_INITIALIZED) fprintf(stderr, "CUBLAS_STATUS_NOT_INITIALIZED"); \
+  if (error == CUBLAS_STATUS_ALLOC_FAILED) fprintf(stderr, "CUBLAS_STATUS_ALLOC_FAILED"); \
+  if (error == CUBLAS_STATUS_INVALID_VALUE) fprintf(stderr, "CUBLAS_STATUS_INVALID_VALUE"); \
+  if (error == CUBLAS_STATUS_ARCH_MISMATCH) fprintf(stderr, "CUBLAS_STATUS_ARCH_MISMATCH"); \
+  if (error == CUBLAS_STATUS_MAPPING_ERROR) fprintf(stderr, "CUBLAS_STATUS_MAPPING_ERROR"); \
+  if (error == CUBLAS_STATUS_EXECUTION_FAILED) fprintf(stderr, "CUBLAS_STATUS_EXECUTION_FAILED"); \
+  if (error == CUBLAS_STATUS_INTERNAL_ERROR) fprintf(stderr, "CUBLAS_STATUS_INTERNAL_ERROR"); \
+  if (error == CUBLAS_STATUS_NOT_SUPPORTED) fprintf(stderr, "CUBLAS_STATUS_NOT_SUPPORTED"); \
+  if (error == CUBLAS_STATUS_LICENSE_ERROR) fprintf(stderr, "CUBLAS_STATUS_LICENSE_ERROR"); \
+  fprintf(stderr, "\n"); \
+  exit(EXIT_FAILURE); \
+}
+#endif
+
+// matrix multiplication for row major
+TVM_REGISTER_GLOBAL("tvm.contrib.cublas.matmul")
+.set_body([](TVMArgs args, TVMRetValue *ret) {
+    DLTensor* A = args[0];
+    DLTensor* B = args[1];
+    DLTensor* C = args[2];
+    bool transa = args[3];
+    bool transb = args[4];
+    // call gemm for simple compact code.
+    CHECK_EQ(A->ndim, 2);
+    CHECK_EQ(B->ndim, 2);
+    CHECK_EQ(C->ndim, 2);
+    CHECK(C->strides == nullptr);
+    CHECK(B->strides == nullptr);
+    CHECK(A->strides == nullptr);
+    CHECK(TypeMatch(A->dtype, kDLFloat, 32));
+    CHECK(TypeMatch(B->dtype, kDLFloat, 32));
+    CHECK(TypeMatch(C->dtype, kDLFloat, 32));
+
+    cublasHandle_t handle;
+    CHECK_CUBLAS_ERROR(cublasCreate(&handle));
+    float alpha = 1.0;
+    float beta = 0.0;
+    float *A_ptr = reinterpret_cast<float*>(static_cast<char*>(B->data) + B->byte_offset);
+    float *B_ptr = reinterpret_cast<float*>(static_cast<char*>(A->data) + A->byte_offset);
+    float *C_ptr = reinterpret_cast<float*>(static_cast<char*>(C->data) + C->byte_offset);
+
+    CHECK_CUBLAS_ERROR(cublasSgemm(handle,
+                                   transb ? CUBLAS_OP_T : CUBLAS_OP_N,
+                                   transa ? CUBLAS_OP_T : CUBLAS_OP_N,
+                                   transb ? B->shape[0] : B->shape[1],
+                                   transa ? A->shape[1] : A->shape[0],
+                                   transb ? B->shape[1] : B->shape[0],
+                                   &alpha,
+                                   A_ptr,
+                                   B->shape[1],
+                                   B_ptr,
+                                   A->shape[1],
+                                   &beta,
+                                   C_ptr,
+                                   C->shape[1]));
+
+    CHECK_CUBLAS_ERROR(cublasDestroy(handle));
+});
+}  // namespace contrib
+}  // namespace tvm
diff --git a/tests/python/contrib/test_cublas.py b/tests/python/contrib/test_cublas.py
new file mode 100644
index 000000000000..c488c8c680e1
--- /dev/null
+++ b/tests/python/contrib/test_cublas.py
@@ -0,0 +1,33 @@
+import tvm
+import numpy as np
+from tvm.contrib import cublas
+
+def test_matmul_add():
+    n = 1024
+    l = 128
+    m = 235
+    A = tvm.placeholder((n, l), name='A')
+    B = tvm.placeholder((l, m), name='B')
+    C = cublas.matmul(A, B)
+    s = tvm.create_schedule(C.op)
+
+    def verify(target="cuda"):
+        if not tvm.module.enabled(target):
+            print("skip because %s is not enabled..." % target)
+            return
+        if not tvm.get_global_func("tvm.contrib.cublas.matmul", True):
+            print("skip because extern function is not avalable")
+            return
+        ctx = tvm.gpu(0)
+        f = tvm.build(s, [A, B, C], target)
+        a = tvm.nd.array(np.random.uniform(size=(n, l)).astype(A.dtype), ctx)
+        b = tvm.nd.array(np.random.uniform(size=(l, m)).astype(B.dtype), ctx)
+        c = tvm.nd.array(np.zeros((n, m), dtype=C.dtype), ctx)
+        f(a, b, c)
+        np.testing.assert_allclose(
+            c.asnumpy(), np.dot(a.asnumpy(), b.asnumpy()), rtol=1e-5)
+    verify()
+
+
+if __name__ == "__main__":
+    test_matmul_add()
diff --git a/tests/python/contrib/test_cudnn.py b/tests/python/contrib/test_cudnn.py
index 47561c57bcbd..c197f5c5b0c6 100644
--- a/tests/python/contrib/test_cudnn.py
+++ b/tests/python/contrib/test_cudnn.py
@@ -41,8 +41,7 @@ def test_conv2d():
                              tensor_format=0,
                              algo=1)
     yshape = [x.value for x in Y.shape]
-    with tvm.target.create("cuda -libs=cudnn"):
-        s = tvm.create_schedule(Y.op)
+    s =  tvm.create_schedule(Y.op)
     
     def verify():
         ctx = tvm.gpu(0)
diff --git a/topi/python/topi/cuda/__init__.py b/topi/python/topi/cuda/__init__.py
index 314931b4cb42..f829a9895fd2 100644
--- a/topi/python/topi/cuda/__init__.py
+++ b/topi/python/topi/cuda/__init__.py
@@ -11,7 +11,7 @@
 from .reduction import schedule_reduce
 from .softmax import schedule_softmax
 from .injective import schedule_injective, schedule_elemwise, schedule_broadcast
-from .dense import schedule_dense
+from .dense import dense_cuda, schedule_dense
 from .pooling import schedule_pool, schedule_global_pool
 from .conv2d_transpose_nchw import schedule_conv2d_transpose_nchw
 from .extern import schedule_extern
diff --git a/topi/python/topi/cuda/dense.py b/topi/python/topi/cuda/dense.py
index e7b142758295..6207c14220d0 100644
--- a/topi/python/topi/cuda/dense.py
+++ b/topi/python/topi/cuda/dense.py
@@ -2,9 +2,48 @@
 """Schedule for dense operator"""
 from __future__ import absolute_import as _abs
 import tvm
+from tvm.contrib import cublas
+from ..nn.dense import dense, dense_default
 from .. import tag
 from .. import generic
 
+@dense.register("cuda")
+def dense_cuda(data, weight, bias=None):
+    """Dense operator for cuda backend.
+
+    Parameters
+    ----------
+    data : tvm.Tensor
+        2-D with shape [batch, in_dim]
+
+    weight : tvm.Tensor
+        2-D with shape [out_dim, in_dim]
+
+    bias : tvm.Tensor, optional
+        1-D with shape [out_dim]
+
+    Returns
+    -------
+    output : tvm.Tensor
+        2-D with shape [batch, out_dim]
+    """
+    assert len(data.shape) == 2 and len(weight.shape) == 2, \
+        "only support 2-dim dense"
+    if bias is not None:
+        assert len(bias.shape) == 1
+    batch, in_dim = data.shape
+    out_dim, _ = weight.shape
+    target = tvm.target.current_target()
+    if "cublas" in target.libs:
+        matmul = cublas.matmul(data, weight, False, True)
+        if bias is not None:
+            matmul = tvm.compute((batch, out_dim), \
+                                 lambda i, j: matmul[i, j] + bias[j], \
+                                 tag=tag.BROADCAST)
+        return matmul
+    return dense_default(data, weight, bias)
+
+
 @generic.schedule_dense.register(["cuda", "gpu"])
 def schedule_dense(outs):
     """Schedule for dense operator.
@@ -20,6 +59,10 @@ def schedule_dense(outs):
     s: Schedule
         The computation schedule for dense.
     """
+    target = tvm.target.current_target()
+    if target.target_name == "cuda" and "cublas" in target.libs:
+        return generic.schedule_extern(outs)
+
     outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
     s = tvm.create_schedule([x.op for x in outs])
     def _schedule(Dense):
diff --git a/topi/python/topi/nn/dense.py b/topi/python/topi/nn/dense.py
index 333692614bd1..11cc6097c250 100644
--- a/topi/python/topi/nn/dense.py
+++ b/topi/python/topi/nn/dense.py
@@ -3,9 +3,8 @@
 import tvm
 from .. import tag
 
-
-def dense(data, weight, bias=None):
-    """Applies a linear transformation: :math:`Y = XW^T + b`.
+def dense_default(data, weight, bias=None):
+    """The default implementation of dense in topi.
 
     Parameters
     ----------
@@ -38,3 +37,26 @@ def dense(data, weight, bias=None):
                              lambda i, j: matmul[i, j] + bias[j], \
                              tag=tag.BROADCAST)
     return matmul
+
+
+@tvm.target.generic_func
+def dense(data, weight, bias=None):
+    """Applies a linear transformation: :math:`Y = XW^T + b`.
+
+    Parameters
+    ----------
+    data : tvm.Tensor
+        2-D with shape [batch, in_dim]
+
+    weight : tvm.Tensor
+        2-D with shape [out_dim, in_dim]
+
+    bias : tvm.Tensor, optional
+        1-D with shape [out_dim]
+
+    Returns
+    -------
+    output : tvm.Tensor
+        2-D with shape [batch, out_dim]
+    """
+    return dense_default(data, weight, bias)

From 026f996de809715b874a65196f7c6eb34b8605c8 Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Wed, 3 Jan 2018 11:56:08 +0800
Subject: [PATCH 069/949] [CODEGEN] fix & improments in codegen (#745)

* [CODEGEN] update codegen for vector operation

* update comment, fix for metal

* fix some bugs in codegen

* use 'restrict' in every argument

* fix

* fix
---
 src/codegen/codegen_c.cc      | 18 ++++++++++++++++--
 src/codegen/codegen_opencl.cc |  4 ++--
 src/pass/ir_util.h            |  4 ++--
 src/pass/split_host_device.cc |  3 +++
 4 files changed, 23 insertions(+), 6 deletions(-)

diff --git a/src/codegen/codegen_c.cc b/src/codegen/codegen_c.cc
index 1f9890d9a271..05ba1f2f357b 100644
--- a/src/codegen/codegen_c.cc
+++ b/src/codegen/codegen_c.cc
@@ -228,7 +228,7 @@ void CodeGenC::RegisterHandleType(const Variable* buf_var, Type t) {
 void CodeGenC::PrintVecElemLoad(const std::string& vec,
                                 Type t, int i,
                                 std::ostream& os) {  // NOLINT(*)
-  os << vec << ".s" << std::hex << i;
+  os << vec << ".s" << std::hex << i << std::dec;
 }
 
 void CodeGenC::PrintVecElemStore(const std::string& vec,
@@ -236,7 +236,7 @@ void CodeGenC::PrintVecElemStore(const std::string& vec,
                                  const std::string& value) {
   this->PrintIndent();
   stream << vec << ".s" << std::hex << i
-         << " = " << value << ";\n";
+         << " = " << value << ";\n" << std::dec;
 }
 
 std::string CodeGenC::GetVecLoad(
@@ -583,6 +583,13 @@ void CodeGenC::VisitExpr_(const Load* op, std::ostream& os) {  // NOLINT(*)
         std::ostringstream value_temp;
         if (!HandleTypeMatch(op->buffer_var.get(), elem_type)) {
           value_temp << "((";
+          if (op->buffer_var.get()->type.is_handle()) {
+            auto it = alloc_storage_scope_.find(op->buffer_var.get());
+            if (it != alloc_storage_scope_.end()) {
+              PrintStorageScope(it->second, value_temp);
+              value_temp << ' ';
+            }
+          }
           PrintType(elem_type, value_temp);
           value_temp << "*)" << vid << ')';
         } else {
@@ -627,6 +634,13 @@ void CodeGenC::VisitStmt_(const Store* op) {
         Type elem_type = t.element_of();
         if (!HandleTypeMatch(op->buffer_var.get(), elem_type)) {
           stream << "((";
+          if (op->buffer_var.get()->type.is_handle()) {
+            auto it = alloc_storage_scope_.find(op->buffer_var.get());
+            if (it != alloc_storage_scope_.end()) {
+              PrintStorageScope(it->second, stream);
+              stream << ' ';
+            }
+          }
           PrintType(elem_type, stream);
           stream << "*)" << vid << ')';
         } else {
diff --git a/src/codegen/codegen_opencl.cc b/src/codegen/codegen_opencl.cc
index ccd164bcd9e0..d2133f85a08c 100644
--- a/src/codegen/codegen_opencl.cc
+++ b/src/codegen/codegen_opencl.cc
@@ -177,14 +177,14 @@ void CodeGenOpenCL::PrintStorageScope(
 
 void CodeGenOpenCL::VisitExpr_(const Broadcast* op, std::ostream& os) {   // NOLINT(*)
   std::string v = PrintExpr(op->value);
-  os << '(';
+  os << "((";
   PrintType(op->type, os);
   os << ")(";
   for (int i = 0; i < op->lanes; ++i) {
     if (i != 0) os << ", ";
     os << v;
   }
-  os << ')';
+  os << "))";
 }
 }  // namespace codegen
 }  // namespace tvm
diff --git a/src/pass/ir_util.h b/src/pass/ir_util.h
index 082d580a0e45..96a41b120e46 100644
--- a/src/pass/ir_util.h
+++ b/src/pass/ir_util.h
@@ -154,8 +154,8 @@ inline Type APIType(Type t) {
 inline int GetTempAllocaAlignment(Type type, int32_t const_size) {
   int align = runtime::kTempAllocaAlignment;
   if (const_size > 0) {
-    const_size = const_size * type.bits() * type.lanes() / 8;
-    while (align > const_size) {
+    int64_t const_s = static_cast<int64_t>(const_size) * type.bits() * type.lanes() / 8;
+    while (align > const_s) {
       align = align / 2;
     }
   }
diff --git a/src/pass/split_host_device.cc b/src/pass/split_host_device.cc
index 942e70339488..44e9753081db 100644
--- a/src/pass/split_host_device.cc
+++ b/src/pass/split_host_device.cc
@@ -191,6 +191,9 @@ class HostDeviceSplitter : public IRMutator {
         auto it = handle_data_type_.find(v.get());
         if (it != handle_data_type_.end()) {
           n->handle_data_type.Set(v, it->second);
+        } else {
+          // int32 as a placeholder
+          n->handle_data_type.Set(v, make_const(UInt(32), 0));
         }
       }
     }

From 2335fdab80931ac4b187463c4920e1351d0f9f1e Mon Sep 17 00:00:00 2001
From: libing4752 <libing475211023@sjtu.edu.cn>
Date: Thu, 4 Jan 2018 05:37:56 +0800
Subject: [PATCH 070/949] modified schedule_dataflow_rewrite.cc to fix Stale
 Tensor during Dataflow Rewrite #738 (#747)

* modified schedule_dataflow_rewrite.cc to fix losing tensor problem

* modified schedule_dataflow_rewrite.cc for lint scan

* modified schedule_dataflow_rewrite.cc for lint scan

* using tensor's value_index to index output of stage op
---
 src/schedule/schedule_dataflow_rewrite.cc     |  4 +++-
 .../unittest/test_schedule_schedule_ops.py    | 20 +++++++++++++++++++
 2 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/src/schedule/schedule_dataflow_rewrite.cc b/src/schedule/schedule_dataflow_rewrite.cc
index d1a69ecf0203..b58df9d0481f 100644
--- a/src/schedule/schedule_dataflow_rewrite.cc
+++ b/src/schedule/schedule_dataflow_rewrite.cc
@@ -86,7 +86,9 @@ Tensor Schedule::cache_read(const Tensor& tensor,
       return tensor(Array<Expr>(i.begin(), i.end()));
     }, os.str());
   std::unordered_map<Tensor, Tensor> vsub;
-  vsub[tensor] = cache;
+  Stage s = operator[](tensor->op);
+  Tensor sugar_tensor = s->op.output(tensor->value_index);
+  vsub[sugar_tensor] = cache;
 
   std::unordered_map<Tensor, Tensor> vmap;
   for (Operation op : readers) {
diff --git a/tests/python/unittest/test_schedule_schedule_ops.py b/tests/python/unittest/test_schedule_schedule_ops.py
index a85db2a23e86..03b8dbf48c8c 100644
--- a/tests/python/unittest/test_schedule_schedule_ops.py
+++ b/tests/python/unittest/test_schedule_schedule_ops.py
@@ -182,6 +182,25 @@ def test_schedule_cache():
     bounds = tvm.schedule.InferBound(s)
     stmt = tvm.schedule.ScheduleOps(s, bounds)
 
+def test_schedule_middle_cache():
+    m = tvm.var('m')
+    n = tvm.var('n')
+    A = tvm.placeholder((m, n), name='A')
+    B = tvm.placeholder((m, n), name='B')
+
+    C = tvm.compute((m, n), lambda i, j:  A(i, j) * B(i, j), name='C')
+    D = tvm.compute((m, n), lambda i, j:  C(i , j) , name='D')
+
+    s = tvm.create_schedule(D.op)
+    AA = s.cache_read(A, "local", readers=[C])
+    BB = s.cache_read(B, "local", readers=[C])
+    CC = s.cache_read(C, "local", readers=[D])
+    DD = s.cache_write(D, "local")
+    #s[AA].compute_at(s[CC], CC.op.axis[0])
+    bounds = tvm.schedule.InferBound(s)
+    stmt = tvm.schedule.ScheduleOps(s, bounds)
+
+
 
 def test_schedule_cache_relayout1():
     m = tvm.var('m')
@@ -231,6 +250,7 @@ def test_schedule_cache_relayout3():
 
 
 if __name__ == "__main__":
+    test_schedule_middle_cache()
     test_inline_multi_reduce()
     test_schedule_cache_relayout3()
     test_schedule_cache_relayout2()

From 1cd67cd9928875e1a572b8a7147b8ee335676f31 Mon Sep 17 00:00:00 2001
From: masahi <masahi129@gmail.com>
Date: Thu, 4 Jan 2018 06:38:24 +0900
Subject: [PATCH 071/949] [CONTRIB] rocBLAS integration (#751)

* rocblas integration

* fix include

* fix lint
---
 Makefile                             |  1 +
 make/config.mk                       |  3 ++
 make/contrib/rocblas.mk              |  8 +++
 python/tvm/contrib/cblas.py          |  2 +-
 python/tvm/contrib/rocblas.py        | 32 ++++++++++++
 src/contrib/rocblas/rocblas.cc       | 76 ++++++++++++++++++++++++++++
 tests/python/contrib/test_rocblas.py | 33 ++++++++++++
 topi/python/topi/rocm/__init__.py    |  1 +
 topi/python/topi/rocm/dense.py       | 66 ++++++++++++++++++++++++
 9 files changed, 221 insertions(+), 1 deletion(-)
 create mode 100644 make/contrib/rocblas.mk
 create mode 100644 python/tvm/contrib/rocblas.py
 create mode 100644 src/contrib/rocblas/rocblas.cc
 create mode 100644 tests/python/contrib/test_rocblas.py
 create mode 100644 topi/python/topi/rocm/dense.py

diff --git a/Makefile b/Makefile
index 875c99b8657d..7f612f450cb0 100644
--- a/Makefile
+++ b/Makefile
@@ -139,6 +139,7 @@ include make/contrib/cudnn.mk
 include make/contrib/miopen.mk
 include make/contrib/mps.mk
 include make/contrib/cublas.mk
+include make/contrib/rocblas.mk
 
 ifdef ADD_CFLAGS
 	CFLAGS += $(ADD_CFLAGS)
diff --git a/make/config.mk b/make/config.mk
index 778d52025f92..256771ac3220 100644
--- a/make/config.mk
+++ b/make/config.mk
@@ -80,3 +80,6 @@ USE_MPS = 0
 
 # Whether use cuBLAS
 USE_CUBLAS = 0
+
+# Whether use rocBlas
+USE_ROCBLAS = 0
diff --git a/make/contrib/rocblas.mk b/make/contrib/rocblas.mk
new file mode 100644
index 000000000000..ae5663099d53
--- /dev/null
+++ b/make/contrib/rocblas.mk
@@ -0,0 +1,8 @@
+ROCBLAS_CONTRIB_SRC = $(wildcard src/contrib/rocblas/*.cc)
+ROCBLAS_CONTRIB_OBJ = $(patsubst src/%.cc, build/%.o, $(ROCBLAS_CONTRIB_SRC))
+
+ifeq ($(USE_ROCBLAS), 1)
+CFLAGS += -DTVM_USE_ROCBLAS=1
+ADD_LDFLAGS += -lrocblas
+RUNTIME_DEP += $(ROCBLAS_CONTRIB_OBJ)
+endif
diff --git a/python/tvm/contrib/cblas.py b/python/tvm/contrib/cblas.py
index 17af941449ea..eb32cc490347 100644
--- a/python/tvm/contrib/cblas.py
+++ b/python/tvm/contrib/cblas.py
@@ -7,7 +7,7 @@
 def matmul(lhs, rhs, transa=False, transb=False):
     """Create an extern op that compute matrix mult of A and rhs with CrhsLAS
 
-    This function serves as an example on how to calle external libraries.
+    This function serves as an example on how to call external libraries.
 
     Parameters
     ----------
diff --git a/python/tvm/contrib/rocblas.py b/python/tvm/contrib/rocblas.py
new file mode 100644
index 000000000000..470cff662c4c
--- /dev/null
+++ b/python/tvm/contrib/rocblas.py
@@ -0,0 +1,32 @@
+"""External function interface to rocBLAS libraries."""
+from __future__ import absolute_import as _abs
+
+from .. import api as _api
+from .. import intrin as _intrin
+
+def matmul(lhs, rhs, transa=False, transb=False):
+    """Create an extern op that compute matrix mult of A and rhs with rocBLAS
+
+    Parameters
+    ----------
+    lhs : Tensor
+        The left matrix operand
+    rhs : Tensor
+        The right matrix operand
+    transa : bool
+        Whether transpose lhs
+    transb : bool
+        Whether transpose rhs
+
+    Returns
+    -------
+    C : Tensor
+        The result tensor.
+    """
+    n = lhs.shape[1] if transa else lhs.shape[0]
+    m = rhs.shape[0] if transb else rhs.shape[1]
+    return _api.extern(
+        (n, m), [lhs, rhs],
+        lambda ins, outs: _intrin.call_packed(
+            "tvm.contrib.rocblas.matmul",
+            ins[0], ins[1], outs[0], transa, transb), name="C")
diff --git a/src/contrib/rocblas/rocblas.cc b/src/contrib/rocblas/rocblas.cc
new file mode 100644
index 000000000000..1dbf429461eb
--- /dev/null
+++ b/src/contrib/rocblas/rocblas.cc
@@ -0,0 +1,76 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file Use external rocblas library call.
+ */
+#include <tvm/runtime/registry.h>
+#include <tvm/runtime/util.h>
+#include <dmlc/logging.h>
+#include "rocblas.h"
+
+namespace tvm {
+namespace contrib {
+
+using namespace runtime;
+
+#ifndef CHECK_ROCBLAS_ERROR
+#define CHECK_ROCBLAS_ERROR(error) \
+if (error != rocblas_status_success) { \
+  fprintf(stderr, "rocBLAS error: "); \
+  if (error == rocblas_status_invalid_handle) fprintf(stderr, "rocblas_status_invalid_handle"); \
+  if (error == rocblas_status_not_implemented) fprintf(stderr, " rocblas_status_not_implemented"); \
+  if (error == rocblas_status_invalid_pointer) fprintf(stderr, "rocblas_status_invalid_pointer"); \
+  if (error == rocblas_status_invalid_size) fprintf(stderr, "rocblas_status_invalid_size"); \
+  if (error == rocblas_status_memory_error) fprintf(stderr, "rocblas_status_memory_error"); \
+  if (error == rocblas_status_internal_error) fprintf(stderr, "rocblas_status_internal_error"); \
+  fprintf(stderr, "\n"); \
+  exit(EXIT_FAILURE); \
+}
+#endif
+
+
+// matrix multiplication for row major
+TVM_REGISTER_GLOBAL("tvm.contrib.rocblas.matmul")
+.set_body([](TVMArgs args, TVMRetValue *ret) {
+    DLTensor* A = args[0];
+    DLTensor* B = args[1];
+    DLTensor* C = args[2];
+    bool transa = args[3];
+    bool transb = args[4];
+    // call gemm for simple compact code.
+    CHECK_EQ(A->ndim, 2);
+    CHECK_EQ(B->ndim, 2);
+    CHECK_EQ(C->ndim, 2);
+    CHECK(C->strides == nullptr);
+    CHECK(B->strides == nullptr);
+    CHECK(A->strides == nullptr);
+    CHECK(TypeMatch(A->dtype, kDLFloat, 32));
+    CHECK(TypeMatch(B->dtype, kDLFloat, 32));
+    CHECK(TypeMatch(C->dtype, kDLFloat, 32));
+
+    rocblas_handle handle;
+    CHECK_ROCBLAS_ERROR(rocblas_create_handle(&handle));
+    float alpha = 1.0;
+    float beta = 0.0;
+    float *A_ptr = reinterpret_cast<float*>(static_cast<char*>(B->data) + B->byte_offset);
+    float *B_ptr = reinterpret_cast<float*>(static_cast<char*>(A->data) + A->byte_offset);
+    float *C_ptr = reinterpret_cast<float*>(static_cast<char*>(C->data) + C->byte_offset);
+
+    CHECK_ROCBLAS_ERROR(rocblas_sgemm(handle,
+                                      transb ? rocblas_operation_transpose : rocblas_operation_none,
+                                      transa ? rocblas_operation_transpose : rocblas_operation_none,
+                                      transb ? B->shape[0] : B->shape[1],
+                                      transa ? A->shape[1] : A->shape[0],
+                                      transb ? B->shape[1] : B->shape[0],
+                                      &alpha,
+                                      A_ptr,
+                                      B->shape[1],
+                                      B_ptr,
+                                      A->shape[1],
+                                      &beta,
+                                      C_ptr,
+                                      C->shape[1]));
+
+    CHECK_ROCBLAS_ERROR(rocblas_destroy_handle(handle));
+});
+}  // namespace contrib
+}  // namespace tvm
diff --git a/tests/python/contrib/test_rocblas.py b/tests/python/contrib/test_rocblas.py
new file mode 100644
index 000000000000..46350f4d6625
--- /dev/null
+++ b/tests/python/contrib/test_rocblas.py
@@ -0,0 +1,33 @@
+import tvm
+import numpy as np
+from tvm.contrib import rocblas
+
+def test_matmul_add():
+    n = 1024
+    l = 128
+    m = 235
+    A = tvm.placeholder((n, l), name='A')
+    B = tvm.placeholder((l, m), name='B')
+    C = rocblas.matmul(A, B)
+    s = tvm.create_schedule(C.op)
+
+    def verify(target="rocm"):
+        if not tvm.module.enabled(target):
+            print("skip because %s is not enabled..." % target)
+            return
+        if not tvm.get_global_func("tvm.contrib.rocblas.matmul", True):
+            print("skip because extern function is not avalable")
+            return
+        ctx = tvm.rocm(0)
+        f = tvm.build(s, [A, B, C], target)
+        a = tvm.nd.array(np.random.uniform(size=(n, l)).astype(A.dtype), ctx)
+        b = tvm.nd.array(np.random.uniform(size=(l, m)).astype(B.dtype), ctx)
+        c = tvm.nd.array(np.zeros((n, m), dtype=C.dtype), ctx)
+        f(a, b, c)
+        np.testing.assert_allclose(
+            c.asnumpy(), np.dot(a.asnumpy(), b.asnumpy()), rtol=1e-5)
+    verify()
+
+
+if __name__ == "__main__":
+    test_matmul_add()
diff --git a/topi/python/topi/rocm/__init__.py b/topi/python/topi/rocm/__init__.py
index d2d7aaf0fd3d..3fddd53a3b36 100644
--- a/topi/python/topi/rocm/__init__.py
+++ b/topi/python/topi/rocm/__init__.py
@@ -3,3 +3,4 @@
 from __future__ import absolute_import as _abs
 
 from .conv2d import *
+from .dense import *
diff --git a/topi/python/topi/rocm/dense.py b/topi/python/topi/rocm/dense.py
new file mode 100644
index 000000000000..cfeed247a4a1
--- /dev/null
+++ b/topi/python/topi/rocm/dense.py
@@ -0,0 +1,66 @@
+# pylint: disable=invalid-name, unused-variable
+"""Schedule for dense operator"""
+from __future__ import absolute_import as _abs
+import tvm
+from tvm.contrib import rocblas
+import topi
+from ..nn.dense import dense, dense_default
+from .. import tag
+from .. import generic
+
+@dense.register("rocm")
+def dense_rocm(data, weight, bias=None):
+    """Dense operator for rocm backend.
+
+    Parameters
+    ----------
+    data : tvm.Tensor
+        2-D with shape [batch, in_dim]
+
+    weight : tvm.Tensor
+        2-D with shape [out_dim, in_dim]
+
+    bias : tvm.Tensor, optional
+        1-D with shape [out_dim]
+
+    Returns
+    -------
+    output : tvm.Tensor
+        2-D with shape [batch, out_dim]
+    """
+    assert len(data.shape) == 2 and len(weight.shape) == 2, \
+        "only support 2-dim dense"
+    if bias is not None:
+        assert len(bias.shape) == 1
+    batch, in_dim = data.shape
+    out_dim, _ = weight.shape
+    target = tvm.target.current_target()
+    if "rocblas" in target.libs:
+        matmul = rocblas.matmul(data, weight, False, True)
+        if bias is not None:
+            matmul = tvm.compute((batch, out_dim), \
+                                 lambda i, j: matmul[i, j] + bias[j], \
+                                 tag=tag.BROADCAST)
+        return matmul
+    return dense_default(data, weight, bias)
+
+
+@generic.schedule_dense.register(["rocm"])
+def schedule_dense(outs):
+    """Schedule for dense operator.
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+        The computation graph description of dense
+        in the format of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+        The computation schedule for dense.
+    """
+    target = tvm.target.current_target()
+    if target.target_name == "rocm" and "rocblas" in target.libs:
+        return generic.schedule_extern(outs)
+    return topi.cuda.schedule_dense(outs)

From 4cd0ce6ea7b500c62425ebf883ff15bfaacd2d2c Mon Sep 17 00:00:00 2001
From: Yizhi Liu <javelinjs@gmail.com>
Date: Wed, 3 Jan 2018 19:12:15 -0800
Subject: [PATCH 072/949] correct conv2d workload for resnet18 (#750)

---
 topi/tests/python/test_topi_conv2d_nchw.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/topi/tests/python/test_topi_conv2d_nchw.py b/topi/tests/python/test_topi_conv2d_nchw.py
index 86cfc9493cdc..99f8ac93fb83 100644
--- a/topi/tests/python/test_topi_conv2d_nchw.py
+++ b/topi/tests/python/test_topi_conv2d_nchw.py
@@ -56,7 +56,7 @@ def check_device(device):
 
 def test_conv2d_nchw():
     # ResNet18 worklaods
-    verify_conv2d_nchw(1, 3, 224, 64, 7, 3, 2)
+    verify_conv2d_nchw(1, 3, 224, 64, 7, 2, 3)
     verify_conv2d_nchw(1, 64, 56, 64, 3, 1, 1)
     verify_conv2d_nchw(1, 64, 56, 64, 1, 1, 0)
     verify_conv2d_nchw(1, 64, 56, 128, 3, 2, 1)

From f63eaac96459fd0a201f3d21c902b28881e32df9 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 4 Jan 2018 10:50:09 -0800
Subject: [PATCH 073/949] [CODEGEN] use charp for voidp (#753)

* [CODEGEN] use charp for voidp

* fx
---
 src/codegen/llvm/codegen_llvm.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/codegen/llvm/codegen_llvm.cc b/src/codegen/llvm/codegen_llvm.cc
index d274af73ed82..ea1e24c277cd 100644
--- a/src/codegen/llvm/codegen_llvm.cc
+++ b/src/codegen/llvm/codegen_llvm.cc
@@ -599,7 +599,7 @@ llvm::Value* CodeGenLLVM::CreateIntrinsic(const Call* op) {
         addrspace = llvm::dyn_cast<llvm::PointerType>(
           ptr->getType())->getAddressSpace();
     }
-    return builder_->CreatePointerCast(ptr, t_void_->getPointerTo(addrspace));
+    return builder_->CreatePointerCast(ptr, t_char_->getPointerTo(addrspace));
   } else if (op->is_intrinsic(Call::reinterpret) && is_zero(op->args[0])) {
     return llvm::Constant::getNullValue(t_void_p_);
   } else if (op->is_intrinsic(intrinsic::tvm_handle_is_null)) {

From d899f864c47308f02a502ac0fa5bbdfc05d1ae35 Mon Sep 17 00:00:00 2001
From: xqdan <danxiaoqiang@126.com>
Date: Mon, 8 Jan 2018 03:47:46 +0800
Subject: [PATCH 074/949] [SCHEDULE]Improve bound deduce for loop partition
 (#743) (#755)

* [SCHEDULE]enable partition const loop with build flag (#719)

    * enable partition loop with build flag

    * add a testcase, and modify LoopPartition related cases

*     * add document for split_const_loop

* [IRbuild]Support automatically Name Loop Variable in IRBuilder (#719)

    * add idx_num in class

* using typical index [i, j, k] first, then i_suffix

* keep inputs names

* fix lint

* improve comment of name

* fix lint

* [SCHEDULE]Improve bound deduce for loop partition (#743)

    * add divided checking when deducing

    * related testcase

* fix

* * transform LE and GE first
* remove is_equal
* modify testcase for edge cases checking

* * fix comment

* * fix lint

* * apply transformation form LT -> LE, GT -> GE

* * fix lint

* simplify code and testcase

* add negative co-efficient case

* More complicated cases

* add testcase

* simplify testcase

* comment case for now

* fix testcase
---
 src/arithmetic/bound_deducer.cc            | 30 ++++++---
 tests/python/unittest/test_arith_intset.py | 76 ++++++++++++++++++++--
 2 files changed, 92 insertions(+), 14 deletions(-)

diff --git a/src/arithmetic/bound_deducer.cc b/src/arithmetic/bound_deducer.cc
index 7a8c841025fa..c9779bbbe24d 100644
--- a/src/arithmetic/bound_deducer.cc
+++ b/src/arithmetic/bound_deducer.cc
@@ -128,13 +128,25 @@ class BoundDeducer: public IRVisitor {
     }
 
     // always use relax bound
-    result = result / operand + (is_greater ? 1 : -1);
+    bool divided = can_prove(result % operand == 0);
+    result = result / operand;
+    // since system will round down when not divided
+    // eg. 2/4 -> 0; -2/4 -> -1
+    // no need fix for !is_greater:
+    // eg. a <= 2/4 -> a <= 0
+    // eg. a <= 0/4 -> a <= 0
+    // so just fix for not divided and is_greater
+    // eg. a >= 2/4 -> a >= 0 + 1
+    // eg. a >= 0/4 -> a >= 0
+    if (is_greater && !divided) {
+       result += 1;
+    }
+
     Visit(left ? op->a : op->b);
   }
 
   Expr result;
   bool is_greater{true};
-  bool is_equal{true};
   bool success{true};
 
  private:
@@ -178,22 +190,20 @@ void BoundDeducer::Init() {
 void BoundDeducer::Transform() {
   if (const LT* op = expr_.as<LT>()) {
     is_greater = false;
-    is_equal   = false;
     expr_      = op->a;
-    result     = op->b;
+    // a < b -> a <= b - 1
+    result     = op->b - 1;
   } else if (const LE* op = expr_.as<LE>()) {
     is_greater = false;
-    is_equal   = true;
     expr_      = op->a;
     result     = op->b;
   } else if (const GT* op = expr_.as<GT>()) {
     is_greater = true;
-    is_equal   = false;
     expr_      = op->a;
-    result     = op->b;
+    // a > b -> a >= b + 1
+    result     = op->b + 1;
   } else if (const GE* op = expr_.as<GE>()) {
     is_greater = true;
-    is_equal   = true;
     expr_      = op->a;
     result     = op->b;
   } else {
@@ -237,9 +247,9 @@ IntSet DeduceBound(Expr v, Expr e,
   if (!d.success) return IntSet::nothing();
   Expr min = Interval::neg_inf, max = Interval::pos_inf;
   if (d.is_greater) {
-    min = d.is_equal ? d.result : d.result + 1;
+    min = d.result;
   } else {
-    max = d.is_equal ? d.result : d.result - 1;
+    max = d.result;
   }
   return IntSet::interval(min, max);
 }
diff --git a/tests/python/unittest/test_arith_intset.py b/tests/python/unittest/test_arith_intset.py
index c1b4daaada5b..78589cf3af0e 100644
--- a/tests/python/unittest/test_arith_intset.py
+++ b/tests/python/unittest/test_arith_intset.py
@@ -25,12 +25,17 @@ def test_deduce():
 
     e0 = (-b)*a+c-d
     res0 = tvm.arith.DeduceBound(a, e0>=0, {b: b_s, c: c_s, d: d_s}, {})
-    ans0 = (d-c)/(-b)+(-1)
+    ans0 = ((d - c) /(b*-1))
+    assert str(tvm.ir_pass.Simplify(res0.max())) == str(ans0)
+
+    e0 = d*a+c-d
+    res0 = tvm.arith.DeduceBound(a, e0>=0, {b: b_s, c: c_s, d: d_s}, {})
+    ans0 = ((0-c)/d + 1)
     assert str(tvm.ir_pass.Simplify(res0.max())) == str(ans0)
 
     e1 = (a*4+b < c)
     res1 = tvm.arith.DeduceBound(a, e1, {b: b_s, c: c_s, d: d_s}, {})
-    ans1 = (c-b)/4+(-2)
+    ans1 = (((c - b) + -1)/4) 
     assert str(tvm.ir_pass.Simplify(res1.max())) == str(ans1)
 
     e2 = (tvm.max(5, a * 4) < 0)
@@ -59,14 +64,77 @@ def test_check():
 
     # multiple compare operators
     res2 = tvm.arith.DeduceBound(a, (a+b>3)>c , {b: b_s, c: c_s}, {})
-    assert res1.is_nothing()
+    assert res2.is_nothing()
 
     # multiple target variable
     res2 = tvm.arith.DeduceBound(a, a*2-a>b, {b: b_s}, {})
-    assert res1.is_nothing()
+    assert res2.is_nothing()
+
+def test_deduce_basic():
+    def test_basic(a1, a2, coff):
+        a = tvm.var('a')
+        b = tvm.var('b')
+        b_s = tvm.arith.intset_interval(a1, a2)
+        e0 = b + a*coff + 3
+
+        res1 = tvm.arith.DeduceBound(a, e0<17, {b: b_s}, {b: b_s})
+        [x, y] = [res1.max(), b_s.max()] if coff > 0 else [res1.min(), b_s.min()]
+        assert (tvm.ir_pass.Simplify((x * coff + 3 + y) < 17)).value == 1
+
+        res1 = tvm.arith.DeduceBound(a, e0>17, {b: b_s}, {b: b_s})
+        [x, y] = [res1.max(), b_s.max()] if coff < 0 else [res1.min(), b_s.min()]
+        assert (tvm.ir_pass.Simplify((x * coff + 3 + y) > 17)).value == 1
+
+        res1 = tvm.arith.DeduceBound(a, e0<=17, {b: b_s}, {b: b_s})
+        [x, y] = [res1.max(), b_s.max()] if coff > 0 else [res1.min(), b_s.min()]
+        assert (tvm.ir_pass.Simplify((x * coff + 3 + y) <= 17)).value == 1
+      
+        res1 = tvm.arith.DeduceBound(a, e0>=17, {b: b_s}, {b: b_s})
+        [x, y] = [res1.max(), b_s.max()] if coff < 0 else [res1.min(), b_s.min()]
+        assert (tvm.ir_pass.Simplify((x * coff + 3 + y) >= 17)).value == 1
+       
+    test_basic(0, 4, 4)
+    test_basic(1, 5, 4)
+    test_basic(2, 6, 4)
+    test_basic(0, 4, -4)
+    test_basic(1, 5, -4)
+    test_basic(2, 6, -4)
+
+def test_deduce_complex():
+    def test_complex(a1, a2, coff):
+        a = tvm.var('a')
+        b = tvm.var('b')
+        b_s = tvm.arith.intset_interval(a1, a2)
+        e0 = (b*3 + a* coff) * 4
+
+        res1 = tvm.arith.DeduceBound(a, e0<63, {b: b_s}, {b: b_s})
+        [t, x] = [res1.max(), b_s.max()] if coff > 0 else [res1.min(), b_s.min()]
+        assert (tvm.ir_pass.Simplify(((x*3 + t* coff) * 4) < 63)).value == 1
+
+        res1 = tvm.arith.DeduceBound(a, e0<=63, {b: b_s}, {b: b_s})
+        [t, x] = [res1.max(), b_s.max()] if coff > 0 else [res1.min(), b_s.min()]
+        assert (tvm.ir_pass.Simplify(((x*3 + t* coff) * 4) <= 63)).value == 1
+
+        res1 = tvm.arith.DeduceBound(a, e0>63, {b: b_s}, {b: b_s})
+        [t, x] = [res1.max(), b_s.max()] if coff < 0 else [res1.min(), b_s.min()]
+        assert (tvm.ir_pass.Simplify(((x*3 + t* coff) * 4) > 63)).value == 1
+
+        res1 = tvm.arith.DeduceBound(a, e0>=63, {b: b_s}, {b: b_s})
+        [t, x] = [res1.max(), b_s.max()] if coff < 0 else [res1.min(), b_s.min()]
+        assert (tvm.ir_pass.Simplify(((x*3 + t* coff) * 4) >= 63)).value == 1
+
+    test_complex(0, 4, 4)
+    test_complex(0, 4, -4)
+    test_complex(2, 6, 4)
+    test_complex(0, 4, -4)
+    test_complex(1, 5, -4)
+    test_complex(2, 6, -4)
 
 if __name__ == "__main__":
     test_basic()
     test_vector()
     test_deduce()
     test_check()
+    test_deduce_basic()
+    test_deduce_complex()
+

From 67b14a22a0352688bd9da8f9c1a66d6d0ff8d19a Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 7 Jan 2018 16:46:01 -0800
Subject: [PATCH 075/949] [PASS] StorageRewrite Fold Inplace op storage when
 possible (#759)

* [PASS] StorageRewrite Fold Inplace op storage when possible

* update comment to fix typos
---
 include/tvm/ir.h                              |   6 +
 src/op/extern_op.cc                           |   2 +-
 src/pass/storage_rewrite.cc                   | 352 ++++++++++++++----
 tests/python/unittest/test_codegen_extern.py  |   1 +
 .../unittest/test_pass_storage_rewrite.py     |  39 +-
 5 files changed, 325 insertions(+), 75 deletions(-)

diff --git a/include/tvm/ir.h b/include/tvm/ir.h
index 95e01382dd98..989802326ae4 100644
--- a/include/tvm/ir.h
+++ b/include/tvm/ir.h
@@ -152,6 +152,12 @@ constexpr const char* coproc_scope = "coproc_scope";
 constexpr const char* coproc_uop_scope = "coproc_uop_scope";
 /*! \brief Mark the scope as volatile access for certain handle. */
 constexpr const char* volatile_scope = "volatile_scope";
+/*!
+ * \brief Mark the scope as generated by extern primitive.
+ *  such scope can contain arbitrary ir program and we need to be careful
+ *  when make certain assumptions about the structure of the program.
+ */
+constexpr const char* extern_scope = "extern_scope";
 /*!
  * \brief Mark the scope as when computation start to happen
  *  This can hint some code generator to create a new function for compute.
diff --git a/src/op/extern_op.cc b/src/op/extern_op.cc
index 9b302f6e2504..e83f97b14652 100644
--- a/src/op/extern_op.cc
+++ b/src/op/extern_op.cc
@@ -130,7 +130,7 @@ Stmt ExternOpNode::BuildProvide(
     const Stage& stage,
     const std::unordered_map<IterVar, Range>& dom_map) const {
   CHECK_EQ(stage->op.operator->(), this);
-  Stmt ret = this->body;
+  Stmt ret = AttrStmt::make(make_zero(Int(32)), attr::extern_scope, 0, this->body);
   auto f_push_bind = [&ret](Buffer buffer, Tensor tensor) {
     Array<NodeRef> bind_spec;
     Array<Expr> tuple;
diff --git a/src/pass/storage_rewrite.cc b/src/pass/storage_rewrite.cc
index 9d47a64f8837..5e7abdda2112 100644
--- a/src/pass/storage_rewrite.cc
+++ b/src/pass/storage_rewrite.cc
@@ -41,27 +41,32 @@ class LinearAccessPatternFinder final : public IRVisitor {
   struct StmtEntry {
     // The statment
     const Node* stmt;
-    // Scope used for allocation.
-    StorageScope alloc_scope;
+    // The index in the linear_seq_ to point to end of the nested scope.
+    // This is only set to non-zero if stmt is a nested scope.
+    // if offset > 0, means this is the begin, the end entry is current_index + offset
+    // if offset < 0, means this is the end, the begin entry is current_index + offset
+    int64_t scope_pair_offset{0};
     // The buffer variables this statment touched.
     std::vector<const Variable*> touched;
   };
+  // The scope of each allocation
+  struct AllocEntry {
+    // Scope used for allocation.
+    StorageScope storage_scope;
+    // scope level
+    size_t level{0};
+    // allocation stmt
+    const Allocate* alloc{nullptr};
+  };
 
-  // Get linear access pattern.
-  std::vector<StmtEntry> GetLinearSeq(const Stmt& s) {
-    this->Visit(s);
-    return std::move(linear_seq_);
-  }
   void Visit_(const Allocate* op) final {
     size_t level = scope_.size();
     const Variable* buf = op->buffer_var.get();
-    CHECK(!alloc_scope_level_.count(buf));
-    alloc_scope_level_[buf] = level;
-    StmtEntry e;
-    e.stmt = op;
-    e.alloc_scope = GetScope(buf);
-    e.touched.push_back(buf);
-    linear_seq_.emplace_back(std::move(e));
+    auto it = alloc_info_.find(buf);
+    CHECK(it != alloc_info_.end());
+    CHECK(it->second.alloc == nullptr);
+    it->second.alloc = op;
+    it->second.level = level;
     IRVisitor::Visit_(op);
   }
   void Visit_(const Store* op) final {
@@ -70,9 +75,10 @@ class LinearAccessPatternFinder final : public IRVisitor {
     IRVisitor::Visit_(op);
     // Add write access.
     const Variable* buf = op->buffer_var.get();
-    auto it = alloc_scope_level_.find(buf);
-    if (it != alloc_scope_level_.end()) {
-      scope_[it->second].touched.push_back(buf);
+    auto it = alloc_info_.find(buf);
+    if (it != alloc_info_.end() && it->second.alloc) {
+      CHECK_LT(it->second.level, scope_.size());
+      scope_[it->second.level].touched.push_back(buf);
     }
     StmtEntry e = scope_.back();
     scope_.pop_back();
@@ -96,11 +102,11 @@ class LinearAccessPatternFinder final : public IRVisitor {
     // Add write access.
     IRVisitor::Visit_(op);
     const Variable* buf = op->buffer_var.get();
-    auto it = alloc_scope_level_.find(buf);
-    if (it != alloc_scope_level_.end()) {
-      CHECK_LT(it->second, scope_.size())
+    auto it = alloc_info_.find(buf);
+    if (it != alloc_info_.end() && it->second.alloc) {
+      CHECK_LT(it->second.level, scope_.size())
           << "Load memory in places other than store.";
-      scope_[it->second].touched.push_back(buf);
+      scope_[it->second.level].touched.push_back(buf);
     }
   }
   void Visit_(const Call* op) final {
@@ -113,10 +119,11 @@ class LinearAccessPatternFinder final : public IRVisitor {
   }
   void Visit_(const Variable* buf) final {
     // Directly reference to the variable count as a read.
-    auto it = alloc_scope_level_.find(buf);
-    if (it != alloc_scope_level_.end()) {
-      CHECK_LT(it->second, scope_.size()) << " buf=" << buf->name_hint;
-      scope_[it->second].touched.push_back(buf);
+    auto it = alloc_info_.find(buf);
+    if (it != alloc_info_.end() && it->second.alloc) {
+      CHECK_LT(it->second.level, scope_.size())
+          << " buf=" << buf->name_hint;
+      scope_[it->second.level].touched.push_back(buf);
     }
   }
   template<typename T>
@@ -124,13 +131,20 @@ class LinearAccessPatternFinder final : public IRVisitor {
     scope_.push_back(StmtEntry());
     StmtEntry e;
     e.stmt = op;
+    int64_t begin_index =  static_cast<int64_t>(linear_seq_.size());
     // before scope.
     linear_seq_.push_back(e);
     IRVisitor::Visit_(op);
     // after scope.
     e.touched = std::move(scope_.back().touched);
     scope_.pop_back();
+    int64_t end_index =  static_cast<int64_t>(linear_seq_.size());
+    CHECK_GT(end_index, begin_index);
+    e.scope_pair_offset = begin_index - end_index;
     linear_seq_.push_back(e);
+    // record the pointer to end index.
+    CHECK_NE(end_index, 0U);
+    linear_seq_[begin_index].scope_pair_offset = end_index - begin_index;
   }
   void Visit_(const AttrStmt* op) final {
     // Only record the outer most thread extent.
@@ -138,9 +152,11 @@ class LinearAccessPatternFinder final : public IRVisitor {
       in_thread_env_ = true;
       VisitNewScope(op);
       in_thread_env_ = false;
+    } else if (op->attr_key == attr::extern_scope) {
+      VisitNewScope(op);
     } else if (op->attr_key == attr::storage_scope) {
       const Variable* buf = op->node.as<Variable>();
-      storage_scope_[buf] =
+      alloc_info_[buf].storage_scope =
           StorageScope::make(op->value.as<StringImm>()->value);
       IRVisitor::Visit_(op);
     } else {
@@ -155,36 +171,156 @@ class LinearAccessPatternFinder final : public IRVisitor {
     VisitNewScope(op);
   }
 
+  // linearized access sequence.
+  std::vector<StmtEntry> linear_seq_;
+  // The storage scope of each buffer
+  std::unordered_map<const Variable*, AllocEntry> alloc_info_;
+
  private:
-  // Get storage scope of buffer.
-  StorageScope GetScope(const Variable* buf) const {
-    auto it = storage_scope_.find(buf);
-    CHECK(it != storage_scope_.end());
-    return it->second;
-  }
   // Whether already in thread env.
   bool in_thread_env_{false};
-  // linearized access sequence.
-  std::vector<StmtEntry> linear_seq_;
   // The scope stack.
   std::vector<StmtEntry> scope_;
-  // The storage scope of each buffer
-  std::unordered_map<const Variable*, StorageScope> storage_scope_;
-  // buffer -> allocated scope level in the IR.
-  std::unordered_map<const Variable*, size_t> alloc_scope_level_;
+};
+
+// Verify if the statement can be run safely via inplace fashion
+//
+// Detect pattern: dst[index] = f(src[index])
+//
+// WARNING: the current detection algorithm cannot handle the case
+// when a location in an array is written multiple times
+//
+// For example, the following program will pass the check,
+// but we cannot make A and B to be the same array.
+//
+//  A[0] = B[0] + 1
+//  A[0] = B[0] + 1
+//
+// The high level code generator needs to ensure that the generated
+// code only write each location of the target array once.
+//
+// This is the case with IR generated by the current compute schedule.
+// We explicitly return false if we find there is an extern block
+// which can be arbitrary IR.
+//
+// Neve-the-less, inplace detector should be used with care in mind.
+// We may also consider introduce a condition checker that checks
+// if every index only visited once for an absolute sufficient condition.
+//
+// The code after inplace transformation is no longer idempotent.
+//
+class InplaceOpVerifier : public IRVisitor {
+ public:
+  bool Check(const Node* stmt,
+             const Variable* dst,
+             const Variable* src) {
+    dst_ = dst;
+    src_ = src;
+    result_ = true;
+    if (stmt->is_type<AttrStmt>()) {
+      Visit_(static_cast<const AttrStmt*>(stmt));
+    } else if (stmt->is_type<For>()) {
+      Visit_(static_cast<const For*>(stmt));
+    } else if (stmt->is_type<IfThenElse>()) {
+      Visit_(static_cast<const IfThenElse*>(stmt));
+    } else if (stmt->is_type<Store>()) {
+      Visit_(static_cast<const Store*>(stmt));
+    } else {
+      return false;
+    }
+    return result_;
+  }
+
+  using IRVisitor::Visit_;
+
+  void Visit(const NodeRef& e) final {
+    if (!result_) return;
+    IRVisitor::Visit(e);
+  }
+
+  void Visit_(const Variable* op) final {
+    // assume all opaque access is unsafe
+    if (op == dst_ || op == src_) {
+      result_ = false; return;
+    }
+  }
+
+  void Visit_(const Store* op) final {
+    ++mem_nest_;
+    this->Visit(op->index);
+    --mem_nest_;
+    if (op->buffer_var.get() == dst_) {
+      store_ = op;
+      this->Visit(op->value);
+      this->Visit(op->predicate);
+      store_ = nullptr;
+    } else {
+      this->Visit(op->value);
+      this->Visit(op->predicate);
+    }
+  }
+
+  void Visit_(const AttrStmt* op) final {
+    // always reject extern code
+    if (op->attr_key == attr::extern_scope ||
+        op->attr_key == attr::volatile_scope) {
+      result_ = false; return;
+    }
+    IRVisitor::Visit_(op);
+  }
+
+  void Visit_(const Load* op) final {
+    const Variable* buf = op->buffer_var.get();
+    // cannot read from dst_ (no reduction)
+    if (buf == dst_) {
+      result_ = false; return;
+    }
+    // do not allow indirect memory load
+    if (mem_nest_ != 0) {
+      result_ = false; return;
+    }
+    if (src_ == buf) {
+      if (store_ == nullptr ||
+          store_->value.type() != op->type ||
+          !ir::Equal(store_->index, op->index)) {
+        result_ = false; return;
+      }
+    }
+    ++mem_nest_;
+    IRVisitor::Visit_(op);
+    --mem_nest_;
+  }
+
+
+ private:
+  // result of the check
+  bool result_{true};
+  // destination memory
+  const Variable* dst_;
+  // source variable
+  const Variable* src_;
+  // counter of load,
+  // it is not safe to inplace when there is nested load like A[B[i]]
+  int mem_nest_{0};
+  // The current store to be inspected
+  const Store* store_{nullptr};
 };
 
 // Planner to plan and rewrite memory allocation.
 class StoragePlanRewriter : public IRMutator {
  public:
   using StmtEntry = LinearAccessPatternFinder::StmtEntry;
+  using AllocEntry = LinearAccessPatternFinder::AllocEntry;
 
-  Stmt Rewrite(Stmt stmt) {
-    std::vector<StmtEntry> seq =
-       LinearAccessPatternFinder().GetLinearSeq(stmt);
-    this->FindFreeLocation(seq);
-    this->PlanMemory(seq);
+  Stmt Rewrite(Stmt stmt, bool detect_inplace) {
+    detect_inplace_ = detect_inplace;
+    // plan the rewrite
+    LinearAccessPatternFinder finder;
+    finder.Visit(stmt);
+    this->LivenessAnalysis(finder.linear_seq_);
+    this->PlanMemory(finder.linear_seq_, finder.alloc_info_);
     this->PrepareNewAlloc();
+    // start rewrite
     stmt = this->Mutate(stmt);
     if (attach_map_.count(nullptr)) {
       std::vector<Stmt> nest;
@@ -308,7 +444,6 @@ class StoragePlanRewriter : public IRMutator {
   }
 
  private:
-  // Alllocate entry of node.
   struct StorageEntry {
     // The scope that this alloc attaches after
     // For shared/local memory it is beginning of the thread extent.
@@ -332,6 +467,16 @@ class StoragePlanRewriter : public IRMutator {
     // the address becomes alloc_var + sizeof(elem_type) * elem_offset;
     uint64_t elem_offset{0};
   };
+
+  // Alllocate entry of node.
+  // Event entry in liveness analysis
+  struct EventEntry {
+    // variables we generate
+    std::vector<const Variable*> gen;
+    // variables we kill
+    std::vector<const Variable*> kill;
+  };
+
   Stmt MakeAttach(const std::vector<StorageEntry*>& svec,
                   Stmt body) {
     std::vector<Stmt> nest;
@@ -461,16 +606,29 @@ class StoragePlanRewriter : public IRMutator {
           << "Allocation exceed bound of memory tag " << e->scope.to_string();
     }
   }
-  // Find the free location of each varaible.
-  // Just do a reverse linear scan.
-  void FindFreeLocation(const std::vector<StmtEntry>& seq) {
+  // Liveness analysis to find gen and kill point of each variable.
+  void LivenessAnalysis(const std::vector<StmtEntry>& seq) {
+    // find kill point, do a reverse linear scan.
     std::unordered_set<const Variable*> touched;
     for (size_t i = seq.size(); i != 0; --i) {
       const StmtEntry& s = seq[i - 1];
       for (const Variable* buffer : s.touched) {
         if (!touched.count(buffer)) {
           touched.insert(buffer);
-          free_loc_[i - 1].push_back(buffer);
+          event_map_[s.stmt].kill.push_back(buffer);
+        }
+      }
+    }
+    // find gen point, do forward scan
+    touched.clear();
+    for (size_t i = 0; i < seq.size(); ++i) {
+      int64_t offset = seq[i].scope_pair_offset;
+      if (offset < 0) continue;
+      const StmtEntry& s = seq[i + offset];
+      for (const Variable* buffer : s.touched) {
+        if (!touched.count(buffer)) {
+          touched.insert(buffer);
+          event_map_[s.stmt].gen.push_back(buffer);
         }
       }
     }
@@ -500,14 +658,66 @@ class StoragePlanRewriter : public IRMutator {
   }
 
   // Memory plan algorithm
-  void PlanMemory(const std::vector<StmtEntry>& seq) {
+  void PlanMemory(const std::vector<StmtEntry>& seq,
+                  const std::unordered_map<const Variable*, AllocEntry>& alloc_info) {
+    std::unordered_set<const Variable*> inplace_flag;
+
     for (size_t i = 0; i < seq.size(); ++i) {
       const StmtEntry& s = seq[i];
+      auto it = event_map_.find(seq[i].stmt);
+
+      // scope_pair_offset >= 0 means it is either
+      // - leaf stmt(offset = 0)
+      // - beginning of scope(offset < 0)
+      // In both cases, we need to handle the gen event correctly
+      if (it != event_map_.end() && seq[i].scope_pair_offset >= 0) {
+        // Inplace operation detection
+        // specially handle this
+        bool detect_inplace = detect_inplace_ && (it->second.gen.size() <= 2);
+
+        for (const Variable* var : it->second.gen) {
+          CHECK(alloc_info.count(var));
+          const AllocEntry& ae = alloc_info.at(var);
+          StorageEntry* dst_entry = nullptr;
+          // inplace detection
+          if (detect_inplace) {
+            for (const Variable* src : it->second.kill) {
+              if (!inplace_flag.count(src) && alloc_map_.count(src)) {
+                InplaceOpVerifier visitor;
+                StorageEntry* src_entry = alloc_map_.at(src);
+                if (src_entry->scope == ae.storage_scope &&
+                    src_entry->attach_scope_ == thread_scope_ &&
+                    src_entry->elem_type == ae.alloc->type.element_of() &&
+                    visitor.Check(s.stmt, var, src)) {
+                  uint64_t const_nbits = static_cast<uint64_t>(
+                      ae.alloc->constant_allocation_size() *
+                      ae.alloc->type.bits() *
+                      ae.alloc->type.lanes());
+                  if (src_entry->const_nbits == const_nbits) {
+                    // successfully inplace
+                    dst_entry = src_entry;
+                    inplace_flag.insert(src);
+                  }
+                }
+              }
+            }
+          }
+          if (dst_entry == nullptr) {
+            dst_entry = FindAlloc(ae.alloc, thread_scope_, ae.storage_scope);
+          }
+          dst_entry->allocs.emplace_back(ae.alloc);
+          alloc_map_[var] = dst_entry;
+        }
+      }
+      // enter/exit new scope
       if (s.stmt->is_type<AttrStmt>()) {
         const auto* op = static_cast<const AttrStmt*>(s.stmt);
-        CHECK(op->attr_key == attr::thread_extent ||
-              op->attr_key == attr::pragma_scope);
-        PlanNewScope(op);
+        if (op->attr_key == attr::thread_extent ||
+            op->attr_key == attr::pragma_scope) {
+          PlanNewScope(op);
+        } else {
+          CHECK(op->attr_key == attr::extern_scope);
+        }
       } else if (s.stmt->is_type<For>()) {
         const auto* op = static_cast<const For*>(s.stmt);
         if (op->for_type == ForType::Parallel) {
@@ -515,16 +725,17 @@ class StoragePlanRewriter : public IRMutator {
             PlanNewScope(op);
           }
         }
-      } else if (s.stmt->is_type<Allocate>()) {
-        const auto* op = static_cast<const Allocate*>(s.stmt);
-        StorageEntry* e = this->FindAlloc(op, thread_scope_, s.alloc_scope);
-        e->allocs.emplace_back(op);
-        alloc_map_[op->buffer_var.get()] = e;
       }
-      // free list
-      if (free_loc_.count(i)) {
-        for (const Variable* var : free_loc_.at(i)) {
-          this->Free(var);
+      // scope_pair_offset <= 0 means it is either
+      // - leaf stmt(offset = 0)
+      // - end of scope(offset < 0)
+      // In both cases, we need to handle the kill event correctly
+      if (it != event_map_.end() && seq[i].scope_pair_offset <= 0) {
+        for (const Variable* var : it->second.kill) {
+          // skip space which are already replaced by inplace
+          if (!inplace_flag.count(var)) {
+            this->Free(var);
+          }
         }
       }
     }
@@ -534,6 +745,7 @@ class StoragePlanRewriter : public IRMutator {
                          const Node* attach_scope,
                          const StorageScope& scope,
                          size_t const_nbits) {
+    CHECK(op != nullptr);
     // Re-use not successful, allocate a new buffer.
     std::unique_ptr<StorageEntry> entry(new StorageEntry());
     entry->attach_scope_ = attach_scope;
@@ -544,9 +756,11 @@ class StoragePlanRewriter : public IRMutator {
     alloc_vec_.emplace_back(std::move(entry));
     return e;
   }
+
   StorageEntry* FindAlloc(const Allocate* op,
                           const Node* attach_scope,
                           const StorageScope& scope) {
+    CHECK(op != nullptr);
     // skip plan for local variable,
     // compiler can do a better job with register allocation.
     const uint64_t match_range = 16;
@@ -603,6 +817,7 @@ class StoragePlanRewriter : public IRMutator {
     auto it = alloc_map_.find(var);
     CHECK(it != alloc_map_.end());
     StorageEntry* e = it->second;
+    CHECK_NE(e->allocs.size(), 0U);
     // Disable sharing of local memory.
     if (e->scope.rank > 1 || e->allocs[0]->type.is_handle()) return;
     // disable reuse of small arrays
@@ -616,17 +831,18 @@ class StoragePlanRewriter : public IRMutator {
   }
   // thread scope.
   const Node* thread_scope_{nullptr};
+  // whether enable inplace detection.
+  bool detect_inplace_{false};
   // Locations of free ops.
-  std::unordered_map<size_t,
-                     std::vector<const Variable*> > free_loc_;
-  // The allocation attach map
-  std::unordered_map<const Node*, std::vector<StorageEntry*> > attach_map_;
-  // The allocation assign map
-  std::unordered_map<const Variable*, StorageEntry*> alloc_map_;
+  std::unordered_map<const Node*, EventEntry> event_map_;
   // constant size free map.
   std::multimap<uint64_t, StorageEntry*> const_free_map_;
   // symbolic free list, for non constant items.
   std::list<StorageEntry*> sym_free_list_;
+  // The allocation attach map
+  std::unordered_map<const Node*, std::vector<StorageEntry*> > attach_map_;
+  // The allocation assign map
+  std::unordered_map<const Variable*, StorageEntry*> alloc_map_;
   // The allocations
   std::vector<std::unique_ptr<StorageEntry> > alloc_vec_;
 };
@@ -693,7 +909,7 @@ class VectorAllocRewriter : public IRMutator {
 
 
 Stmt StorageRewrite(Stmt stmt) {
-  stmt = StoragePlanRewriter().Rewrite(stmt);
+  stmt = StoragePlanRewriter().Rewrite(stmt, true);
   return VectorAllocRewriter().Mutate(stmt);
 }
 }  // namespace ir
diff --git a/tests/python/unittest/test_codegen_extern.py b/tests/python/unittest/test_codegen_extern.py
index 43736bc46768..1295ed26cce7 100644
--- a/tests/python/unittest/test_codegen_extern.py
+++ b/tests/python/unittest/test_codegen_extern.py
@@ -15,6 +15,7 @@ def extern_generator(ins, outs):
 
     C = tvm.extern(A.shape, [A], extern_generator, name='C')
     s = tvm.create_schedule(C.op)
+    print(tvm.lower(s, [A, C], simple_mode=True))
 
     def check_llvm():
         if not tvm.module.enabled("llvm"):
diff --git a/tests/python/unittest/test_pass_storage_rewrite.py b/tests/python/unittest/test_pass_storage_rewrite.py
index 4d2110319d2c..d3f6307f821f 100644
--- a/tests/python/unittest/test_pass_storage_rewrite.py
+++ b/tests/python/unittest/test_pass_storage_rewrite.py
@@ -19,14 +19,39 @@ def test_storage_share():
     stmt = tvm.ir_pass.CanonicalSimplify(stmt)
     stmt = tvm.ir_pass.Simplify(stmt)
     stmt = tvm.ir_pass.StorageRewrite(stmt)
-    # verify only have two allocations.
-    # verify that the data is folded.
+    # verify only have one allocations.
+    # verify inplace folding works
+    num_alloc = [0]
+    def verify(n):
+        if isinstance(n, tvm.stmt.Allocate):
+            num_alloc[0] += 1
+    tvm.ir_pass.PostOrderVisit(stmt, verify)
+    assert num_alloc[0] == 1
+
+
+def test_inplace_rule():
+    m = 10
+    A = tvm.placeholder((m,), name='A')
+    A0 = tvm.compute((m,), lambda i: A[i], name='A0')
+    A1 = tvm.compute((m,), lambda i: A[i] + 1, name='A1')
+    AA =  tvm.compute((m,), lambda i: A0[i] + A1[i] + A1[0], name='AA')
+    B = tvm.compute((m,), lambda i: AA[i] + 1, name='B')
+    s = tvm.create_schedule(B.op)
+    bounds = tvm.schedule.InferBound(s)
+    assert isinstance(bounds, tvm.container.Map)
+    stmt = tvm.schedule.ScheduleOps(s, bounds)
+    Ab = tvm.decl_buffer(A.shape, A.dtype, name='A')
+    Bb = tvm.decl_buffer(B.shape, B.dtype, name='B')
+    stmt = tvm.ir_pass.StorageFlatten(stmt, {A: Ab, B: Bb}, 64)
+    stmt = tvm.ir_pass.CanonicalSimplify(stmt)
+    stmt = tvm.ir_pass.Simplify(stmt)
+    stmt = tvm.ir_pass.StorageRewrite(stmt)
+    # verify only have one allocations.
+    # verify inplace folding works
     num_alloc = [0]
     def verify(n):
         if isinstance(n, tvm.stmt.Allocate):
             num_alloc[0] += 1
-        elif isinstance(n, tvm.stmt.Store):
-            assert n.buffer_var != n.value.a.buffer_var
     tvm.ir_pass.PostOrderVisit(stmt, verify)
     assert num_alloc[0] == 2
 
@@ -38,7 +63,7 @@ def test_storage_combine():
     B = A
     stages = []
     for t in range(num_stage):
-        B = tvm.compute((n, ), lambda i: B[i] + (t+1), name='A%d' % t)
+        B = tvm.compute((n, ), lambda i: B[i] + B[0] + (t+1), name='A%d' % t)
         stages.append(B)
 
     s = tvm.create_schedule(B.op)
@@ -121,12 +146,14 @@ def test_parallel_alloc():
                 A[j] = A[j] + 2
     body = ib.get()
     body = tvm.ir_pass.StorageRewrite(body)
+
     assert(isinstance(body.body.body.body.body, tvm.stmt.Allocate))
 
 
 
 if __name__ == "__main__":
+    test_inplace_rule()
+    test_storage_share()
     test_parallel_alloc()
     test_storage_combine()
     test_storage_share_gpu()
-    test_storage_share()

From afbb70ba44203061df171dd1126f5b8ed4704a29 Mon Sep 17 00:00:00 2001
From: yuruofeifei <yuruofeifei@gmail.com>
Date: Mon, 8 Jan 2018 15:48:26 -0800
Subject: [PATCH 076/949] [TUTORIAL] Improve opt_gemm tutorial (#757)

* Improve opt_gemm tutorial

* Addressed comments
---
 tutorials/optimize/opt_gemm.py | 194 ++++++++++++++++++++++++---------
 1 file changed, 145 insertions(+), 49 deletions(-)

diff --git a/tutorials/optimize/opt_gemm.py b/tutorials/optimize/opt_gemm.py
index cc9c234a7f87..3f24767ab5ed 100644
--- a/tutorials/optimize/opt_gemm.py
+++ b/tutorials/optimize/opt_gemm.py
@@ -9,8 +9,8 @@
 trying various seemingly promising schedules is time-consuming. With the help of TVM, we can
 try these schedules efficiently to enhance the performance.
 
-In this tutorial, we will demonstrate how square matrix multiplication is optimized step by step by
-writing TVM.
+In this tutorial, we will demonstrate how to use TVM to optimize square matrix multiplication
+and achieve 100 times faster than baseline by simply adding 6 extra lines of code.
 
 There are two important optmizations on intense computation applications executed on CPU:
     1. Increase the cache hit rate of memory access. Both complex numerical computation and hot-spot
@@ -25,8 +25,8 @@
 `repo <https://github.com/flame/how-to-optimize-gemm>`_. Some of them have been applied by TVM
 abstraction automatically, but some of them cannot be simply applied due to TVM constraints.
 
-All the experiment results mentioned below, are executed on 2013's 15' MacBook equiped with
-Intel i7-2760QM CPU. The cache line size should be 64 bytes for all the x86 CPU.
+All the experiment results mentioned below, are executed on 2015's 15' MacBook equiped with
+Intel i7-4770QH CPU. The cache line size should be 64 bytes for all the x86 CPU.
 """
 
 ###############################################################################
@@ -40,7 +40,7 @@
 
 import tvm
 import numpy
-import time
+import timeit
 
 # The size of the square matrix
 N = 1024
@@ -49,7 +49,17 @@
 # Random generated tensor for testing
 a = tvm.nd.array(numpy.random.rand(N, N).astype(dtype), tvm.cpu(0))
 b = tvm.nd.array(numpy.random.rand(N, N).astype(dtype), tvm.cpu(0))
-# The expected answer
+
+np_repeat = 100
+np_runing_time = timeit.timeit(setup='import numpy\n'
+                                     'N = 1024\n'
+                                     'dtype = "float32"\n'
+                                     'a = numpy.random.rand(N, N).astype(dtype)\n'
+                                     'b = numpy.random.rand(N, N).astype(dtype)\n',
+                               stmt='answer = numpy.dot(a, b)',
+                               number=np_repeat)
+print("Numpy running time: %f" % (np_runing_time / np_repeat))
+
 answer = numpy.dot(a.asnumpy(), b.asnumpy())
 
 # Algorithm
@@ -65,49 +75,50 @@
 s = tvm.create_schedule(C.op)
 func = tvm.build(s, [A, B, C], name = 'mmult')
 assert func
-evaluator = func.time_evaluator(func.entry_name, tvm.cpu(0), number = 1)
+
 c = tvm.nd.array(numpy.zeros((N, N), dtype = dtype), tvm.cpu(0))
+func(a, b, c)
+numpy.testing.assert_allclose(c.asnumpy(), answer, rtol=1e-5)
+
+evaluator = func.time_evaluator(func.entry_name, tvm.cpu(0), number=1)
 print('Baseline: %f' % evaluator(a, b, c).mean)
 
+################################################################################################
+# In TVM, we can always inspect lower level IR to debug or optimize our schedule.
+# Here is the generated IR using our baseline schedule.
+
+print(tvm.lower(s, [A, B, C], simple_mode=True))
+
 ################################################################################################
 # Blocking
 # --------
 # A important trick to enhance the cache hit rate is blocking --- data chunck will be computed
 # block by block. The memory access inside the block is a small neighbourhood which is with high
-# meomry locality. In this tutorial, I pick up 8, a relatively small value (8 ints < 64 bytes),
-# as the blocking size.
-#
+# memory locality. In this tutorial, I picked up 32 as the blocking factor. So the block will
+# fill 32 * 32 * sizeof(float) which is 4KB in the cache whose total size is 32KB (L1 data cache)
 
-bn = 8
+bn = 32
+s = tvm.create_schedule(C.op)
 # Blocking by loop tiling
-yo, xo, yi, xi = s[C].tile(C.op.axis[1], C.op.axis[0], bn, bn)
+xo, yo, xi, yi = s[C].tile(C.op.axis[0], C.op.axis[1], bn, bn)
 # Hoist reduction domain outside the blocking loop
-s[C].reorder(yo, xo, k, yi, xi)
+s[C].reorder(xo, yo, k, xi, yi)
 func = tvm.build(s, [A, B, C], name = 'mmult')
 assert func
-# By simply tiling the loop 8x8, and hoisting k outside the blocking loops, we can get nearly 4x
-# speedup compared with the baseline.
-evaluator = func.time_evaluator(func.entry_name, tvm.cpu(0), number = 5)
+
 c = tvm.nd.array(numpy.zeros((N, N), dtype = dtype), tvm.cpu(0))
+func(a, b, c)
+numpy.testing.assert_allclose(c.asnumpy(), answer, rtol=1e-5)
+
+# By simply tiling the loop 32x32, and hoisting k outside the blocking loops, we can see big
+# speedup compared with the baseline.
+evaluator = func.time_evaluator(func.entry_name, tvm.cpu(0), number=5)
 print('Opt1: %f' % evaluator(a, b, c).mean)
 
-###################################################################################################
-# Vectorization
-# -------------
-# Another important trick is vectorization. When the memory access pattern is uniform, the compiler
-# can detect this pattern and pass the continuous memory to vector processor. In TVM, we can use
-# `vectorize` interface to hint the compiler this pattern, so that we can accelerate it vastly.
-#
+################################################################################################
+# Here is the generated IR after blocking.
 
-# After trying different schedule, we finally found that we can benefit from vectorizing
-# the row loop most, i.e. yi.
-s[C].vectorize(yi)
-func = tvm.build(s, [A, B, C], name = 'mmult')
-assert func
-# We can get almost another 4x speedup compared with the previous schedule.
-evaluator = func.time_evaluator(func.entry_name, tvm.cpu(0), number = 5)
-c = tvm.nd.array(numpy.zeros((N, N), dtype = dtype), tvm.cpu(0))
-print('Opt2: %f' % evaluator(a, b, c).mean)
+print(tvm.lower(s, [A, B, C], simple_mode=True))
 
 ###################################################################################################
 # Array Packing
@@ -125,8 +136,8 @@
 ###################################################################################################
 # Just as it is shown in the figure above, after blocking the computations, we can observe the array
 # access pattern of B (after flattening), which is regular but discontinuous. We expect that after
-# some transformation we can get continuous access pattern. We can reorder a [16][16] array to 
-# a [16/4][16][4] array, so that the access pattern of B will be sequential when grabing 
+# some transformation we can get continuous access pattern. We can reorder a [16][16] array to
+# a [16/4][16][4] array, so that the access pattern of B will be sequential when grabing
 # the corresponding value from the packed array.
 #
 
@@ -136,30 +147,115 @@
                 lambda x, y: tvm.sum(A[x, k] * packedB[y / bn, k, y % bn], axis = k),
                 name = 'C')
 
-# Same schedule
 s = tvm.create_schedule(C.op)
-yo, xo, yi, xi = s[C].tile(C.op.axis[1], C.op.axis[0], bn, bn)
-s[C].reorder(yo, xo, k, yi, xi)
-s[C].vectorize(yi)
+xo, yo, xi, yi = s[C].tile(C.op.axis[0], C.op.axis[1], bn, bn)
+s[C].reorder(xo, yo, k, xi, yi)
 
 func = tvm.build(s, [A, B, C], name = 'mmult')
 assert func
-# We can accelerate it almost 3x compared with the previous schedule.
-evaluator = func.time_evaluator(func.entry_name, tvm.cpu(0), number = 5)
+
 c = tvm.nd.array(numpy.zeros((N, N), dtype = dtype), tvm.cpu(0))
+func(a, b, c)
+numpy.testing.assert_allclose(c.asnumpy(), answer, rtol=1e-5)
+
+evaluator = func.time_evaluator(func.entry_name, tvm.cpu(0), number=5)
+print('Opt2: %f' % evaluator(a, b, c).mean)
+
+################################################################################################
+# Here is the generated IR after array packing.
+
+print(tvm.lower(s, [A, B, C], simple_mode=True))
+
+###################################################################################################
+# Vectorization
+# -------------
+# Another important trick is vectorization. When the memory access pattern is uniform,
+# the compiler can detect this pattern and pass the continuous memory to vector processor. In TVM,
+# we can use `vectorize` interface to hint the compiler this pattern, so that we can accelerate it vastly.
+#
+# In this tutorial, we chose to vectorize the inner loop row data since it is cache friendly.
+
+s = tvm.create_schedule(C.op)
+xo, yo, xi, yi = s[C].tile(C.op.axis[0], C.op.axis[1], bn, bn)
+s[C].reorder(xo, yo, k, xi, yi)
+
+# Vectorization
+s[C].vectorize(yi)
+func = tvm.build(s, [A, B, C], name = 'mmult')
+assert func
+
+c = tvm.nd.array(numpy.zeros((N, N), dtype = dtype), tvm.cpu(0))
+func(a, b, c)
+numpy.testing.assert_allclose(c.asnumpy(), answer, rtol=1e-5)
+
+evaluator = func.time_evaluator(func.entry_name, tvm.cpu(0), number=5)
 print('Opt3: %f' % evaluator(a, b, c).mean)
 
+################################################################################################
+# Here is the generated IR after vectorization.
+
+print(tvm.lower(s, [A, B, C], simple_mode=True))
+
+###################################################################################################
+# Loop Permutation
+# -------------
+# If we look at the above IR, we can see the inner loop row data is vectorized and
+# B is transformed into PackedB. The traversal of PackedB is sequential now.
+# So we will look at the access pattern of A. In current schedule, A is accessed column by column
+# which is not cache friendly. If we change the nested loop order of k and inner row index xi,
+# the access pattern for A matrix is more cache friendly.
+
+s = tvm.create_schedule(C.op)
+xo, yo, xi, yi = s[C].tile(C.op.axis[0], C.op.axis[1], bn, bn)
+s[C].reorder(xo, yo, xi, k, yi)
+
+# Vectorization
+s[C].vectorize(yi)
+
+func = tvm.build(s, [A, B, C], name = 'mmult')
+assert func
+
+c = tvm.nd.array(numpy.zeros((N, N), dtype = dtype), tvm.cpu(0))
+func(a, b, c)
+numpy.testing.assert_allclose(c.asnumpy(), answer, rtol=1e-5)
+
+evaluator = func.time_evaluator(func.entry_name, tvm.cpu(0), number=5)
+print('Opt4: %f' % evaluator(a, b, c).mean)
+
+################################################################################################
+# Here is the generated IR after loop permutation.
+
+print(tvm.lower(s, [A, B, C], simple_mode=True))
+
+###################################################################################################
+# Parallel
+# -------------
+# Futhermore, we can also utilize multi-core processors to parallelize computation.
+
+s = tvm.create_schedule(C.op)
+xo, yo, xi, yi = s[C].tile(C.op.axis[0], C.op.axis[1], bn, bn)
+s[C].reorder(xo, yo, xi, k, yi)
+s[C].vectorize(yi)
+
+# parallel
+s[C].parallel(xo)
+
+func = tvm.build(s, [A, B, C], name = 'mmult')
+assert func
+
+c = tvm.nd.array(numpy.zeros((N, N), dtype = dtype), tvm.cpu(0))
+func(a, b, c)
+numpy.testing.assert_allclose(c.asnumpy(), answer, rtol=1e-5)
+
+evaluator = func.time_evaluator(func.entry_name, tvm.cpu(0), number=50)
+opt5_time = evaluator(a, b, c).mean
+print('Opt5: %f' % opt5_time)
+
 ##################################################################################################
 # Summary
 # -------
-# After applying three main tricks, we can achieve almost 90% performance of numpy.
-# Further observation is required to catch up with the performance of numpy.
+# After applying the above simple optimizations with only 6 lines of code,
+# our generated code can achieve 30% of numpy performance with Apple implemented BLAS.
 #
-
-# TODO(Jian Weng): Catch up with the performance of numpy.
-_a = a.asnumpy()
-_b = b.asnumpy()
-now = time.clock()
-answer = numpy.dot(_a, _b)
-print("Numpy: %f" % (time.clock() - now))
+# We can see TVM is very powerful tool to optimize low level computation.
 

From 43b68ddf95d5b1807e972ef990ab1e99a3c3fdbe Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 8 Jan 2018 19:26:08 -0800
Subject: [PATCH 077/949] [PASS] Improve loop partition to remove un-necessary
 warning. (#766)

* [PASS] Improve loop partition to remove un-necessary warning.

* fix comment
---
 src/pass/loop_partition.cc | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/src/pass/loop_partition.cc b/src/pass/loop_partition.cc
index ff6103cd6e02..0de8a88edb00 100644
--- a/src/pass/loop_partition.cc
+++ b/src/pass/loop_partition.cc
@@ -300,8 +300,13 @@ class LoopPartitioner : public IRMutator {
   std::unordered_map<const Variable*, IntSet> relax_map_;
 };
 
-Stmt LoopPartitioner::TryPartition(const Node* node, const Stmt& stmt,
-    VarExpr var, Expr min, Expr max, Stmt body, bool partition_thread_scope) {
+Stmt LoopPartitioner::TryPartition(const Node* node,
+                                   const Stmt& stmt,
+                                   VarExpr var,
+                                   Expr min,
+                                   Expr max,
+                                   Stmt body,
+                                   bool partition_thread_scope) {
   PartitionFinder finder(var, hint_map_, relax_map_);
   finder.Visit(body);
   const auto& partitions = finder.partitions;
@@ -340,7 +345,8 @@ Stmt LoopPartitioner::TryPartition(const Node* node, const Stmt& stmt,
   if (true_itrv.as<arith::IntervalSet>()->i.has_upper_bound()) {
     post_doubt_begin = true_itrv.max() + 1;
     if (!can_prove(true_itrv.max() == max)) {
-      Expr cond = (max - post_doubt_begin >= 0);
+      // require the extent to be non-negative
+      Expr cond = (max - post_doubt_begin + 1 >= 0);
       if (!can_prove(cond)) {
         LOG(WARNING) << "Cannot prove: " << cond
                      << ", when generating the post doubt loop";

From a9c48f60a55eaea6edbea406b3ddfa2ecd49602b Mon Sep 17 00:00:00 2001
From: Yida Wang <yidawa@gmail.com>
Date: Tue, 9 Jan 2018 15:39:35 -0800
Subject: [PATCH 078/949] small fixes on docs (#769)

* small fixs on docs

* add IR output after parallelization
---
 docs/how_to/contribute.md      |  1 -
 tutorials/optimize/opt_gemm.py | 19 +++++++++++++------
 2 files changed, 13 insertions(+), 7 deletions(-)

diff --git a/docs/how_to/contribute.md b/docs/how_to/contribute.md
index a0ba99bdf718..bd31a62e52a9 100644
--- a/docs/how_to/contribute.md
+++ b/docs/how_to/contribute.md
@@ -14,7 +14,6 @@ Everyone is more than welcome to contribute. It is a way to make the project bet
   - [What is the consequence of force push](#what-is-the-consequence-of-force-push)
 * [Document](#document)
 * [Testcases](#testcases)
-* [Examples](#examples)
 * [Core Library](#core-library)
 * [Python Package](#python-package)
 
diff --git a/tutorials/optimize/opt_gemm.py b/tutorials/optimize/opt_gemm.py
index 3f24767ab5ed..9a4264c9d05a 100644
--- a/tutorials/optimize/opt_gemm.py
+++ b/tutorials/optimize/opt_gemm.py
@@ -26,7 +26,7 @@
 abstraction automatically, but some of them cannot be simply applied due to TVM constraints.
 
 All the experiment results mentioned below, are executed on 2015's 15' MacBook equiped with
-Intel i7-4770QH CPU. The cache line size should be 64 bytes for all the x86 CPU.
+Intel i7-4770QH CPU. The cache line size should be 64 bytes for all the x86 CPUs.
 """
 
 ###############################################################################
@@ -230,7 +230,7 @@
 ###################################################################################################
 # Parallel
 # -------------
-# Futhermore, we can also utilize multi-core processors to parallelize computation.
+# Futhermore, we can also utilize multi-core processors to do the thread-level parallelization.
 
 s = tvm.create_schedule(C.op)
 xo, yo, xi, yi = s[C].tile(C.op.axis[0], C.op.axis[1], bn, bn)
@@ -251,11 +251,18 @@
 opt5_time = evaluator(a, b, c).mean
 print('Opt5: %f' % opt5_time)
 
+################################################################################################
+# Here is the generated IR after parallelization.
+
+print(tvm.lower(s, [A, B, C], simple_mode=True))
+
+###################################################################################################
+
 ##################################################################################################
 # Summary
 # -------
 # After applying the above simple optimizations with only 6 lines of code,
-# our generated code can achieve 30% of numpy performance with Apple implemented BLAS.
-#
-# We can see TVM is very powerful tool to optimize low level computation.
-
+# our generated code can achieve 30% of the `numpy` performance with Apple implemented BLAS.
+# Note that the outputs on the webpage reflect the running times on a non-exclusive
+# Docker container, thereby they are *unreliable*. It is highly encouraged to run the
+# tutorial by yourself to observe the performance gain acheived by TVM.

From bfd277188b8e2cb6cda57135e095b6d1d5410101 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 9 Jan 2018 17:39:16 -0800
Subject: [PATCH 079/949] [PASS] Fix storage rewrite merge rule for special tag
 memory (#770)

---
 src/pass/storage_rewrite.cc                   | 28 +++++++++++--------
 .../unittest/test_pass_storage_rewrite.py     | 23 +++++++++++++++
 2 files changed, 40 insertions(+), 11 deletions(-)

diff --git a/src/pass/storage_rewrite.cc b/src/pass/storage_rewrite.cc
index 5e7abdda2112..7215c3f97a43 100644
--- a/src/pass/storage_rewrite.cc
+++ b/src/pass/storage_rewrite.cc
@@ -766,14 +766,15 @@ class StoragePlanRewriter : public IRMutator {
     const uint64_t match_range = 16;
     uint64_t const_nbits = static_cast<uint64_t>(
         op->constant_allocation_size() * op->type.bits() * op->type.lanes());
-    if (scope.rank > 1 || op->type.is_handle()) {
-      return NewAlloc(op, attach_scope, scope, const_nbits);
-    }
     // disable reuse of small arrays, they will be lowered to registers in LLVM
-    if (const_nbits > 0  &&
-        const_nbits <= 32 &&
-        scope.tag.length() == 0) {
-      return NewAlloc(op, attach_scope, scope, const_nbits);
+    // This rules only apply if we are using non special memory
+    if (scope.tag.length() == 0) {
+      if (scope.rank > 1 || op->type.is_handle()) {
+        return NewAlloc(op, attach_scope, scope, const_nbits);
+      }
+      if (const_nbits > 0  &&  const_nbits <= 32) {
+        return NewAlloc(op, attach_scope, scope, const_nbits);
+      }
     }
     if (const_nbits != 0) {
       // constant allocation.
@@ -818,10 +819,15 @@ class StoragePlanRewriter : public IRMutator {
     CHECK(it != alloc_map_.end());
     StorageEntry* e = it->second;
     CHECK_NE(e->allocs.size(), 0U);
-    // Disable sharing of local memory.
-    if (e->scope.rank > 1 || e->allocs[0]->type.is_handle()) return;
-    // disable reuse of small arrays
-    if (e->const_nbits > 0 && e->const_nbits <= 32) return;
+
+    // disable reuse of small arrays, they will be lowered to registers in LLVM
+    // This rules only apply if we are using non special memory
+    if (e->scope.tag.length() == 0) {
+      // Disable sharing of local memory.
+      if (e->scope.rank > 1 || e->allocs[0]->type.is_handle()) return;
+      // disable reuse of small arrays
+      if (e->const_nbits > 0 && e->const_nbits <= 32) return;
+    }
     // normal free.
     if (e->const_nbits != 0) {
       const_free_map_.insert({e->const_nbits, e});
diff --git a/tests/python/unittest/test_pass_storage_rewrite.py b/tests/python/unittest/test_pass_storage_rewrite.py
index d3f6307f821f..1e4dda684eb3 100644
--- a/tests/python/unittest/test_pass_storage_rewrite.py
+++ b/tests/python/unittest/test_pass_storage_rewrite.py
@@ -28,6 +28,28 @@ def verify(n):
     tvm.ir_pass.PostOrderVisit(stmt, verify)
     assert num_alloc[0] == 1
 
+def test_alloc_seq():
+    ib = tvm.ir_builder.create()
+    n = tvm.var("n")
+    with ib.for_range(0, n, name="i") as i:
+        with ib.for_range(0, 10, name="j") as j:
+            A = ib.allocate("float32", 200, name="A", scope="local.L0A")
+            A[j] = 1.2
+        with ib.for_range(0, 10, name="j") as j:
+            A = ib.allocate("float32", 200, name="B", scope="local.L0A")
+            A[j] = 1.3
+
+    body = ib.get()
+    body = tvm.ir_pass.StorageRewrite(body)
+    num_alloc = [0]
+    def verify(n):
+        if isinstance(n, tvm.stmt.Allocate):
+            num_alloc[0] += 1
+            assert n.extents[0].value == 200
+    tvm.ir_pass.PostOrderVisit(body, verify)
+    assert num_alloc[0] == 1
+
+
 
 def test_inplace_rule():
     m = 10
@@ -152,6 +174,7 @@ def test_parallel_alloc():
 
 
 if __name__ == "__main__":
+    test_alloc_seq()
     test_inplace_rule()
     test_storage_share()
     test_parallel_alloc()

From 0a1156f121dbe93e3e6aa9f5db06e59d73df542c Mon Sep 17 00:00:00 2001
From: Yuwei Hu <huyuwei1995@gmail.com>
Date: Fri, 12 Jan 2018 01:05:19 +0800
Subject: [PATCH 080/949] [INTRIN] enable popcount on cuda, opencl, metal
 (#774)

---
 src/codegen/intrin_rule.h              | 12 ++----
 src/codegen/intrin_rule_cuda.cc        | 16 ++++++++
 src/codegen/intrin_rule_metal.cc       | 13 ++++---
 src/codegen/intrin_rule_opencl.cc      | 13 ++++---
 tests/python/integration/test_ewise.py | 51 +++++++++++++++++---------
 5 files changed, 69 insertions(+), 36 deletions(-)

diff --git a/src/codegen/intrin_rule.h b/src/codegen/intrin_rule.h
index e66b55dcc861..c900c9088880 100644
--- a/src/codegen/intrin_rule.h
+++ b/src/codegen/intrin_rule.h
@@ -30,18 +30,14 @@ struct FloatSuffix {
   }
 };
 
-// Add float suffix to the intrinsics
-struct FloatDirect {
+// Return the intrinsic name
+struct Direct {
   std::string operator()(Type t, std::string name) const {
-    if (t.is_float()) {
-      return name;
-    } else {
-      return "";
-    }
+    return name;
   }
 };
 
-// Directly call pure extern function for floats.
+// Call pure extern function.
 template<typename T>
 inline void DispatchExtern(const TVMArgs& args, TVMRetValue* rv) {
   Expr e = args[0];
diff --git a/src/codegen/intrin_rule_cuda.cc b/src/codegen/intrin_rule_cuda.cc
index a2441d597d86..9abb99d7c7c5 100644
--- a/src/codegen/intrin_rule_cuda.cc
+++ b/src/codegen/intrin_rule_cuda.cc
@@ -36,6 +36,19 @@ struct CUDAFastMath : public CUDAMath {
   }
 };
 
+struct CUDAPopcount {
+  std::string operator()(Type t, std::string name) const {
+    if (t.lanes() == 1 && t.is_uint()) {
+      switch (t.bits()) {
+        case 32: return "__popc";
+        case 64: return "__popcll";
+        default: return "";
+      }
+    }
+    return "";
+  }
+};
+
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.cuda.exp")
 .set_body(DispatchExtern<CUDAFastMath>);
 
@@ -51,6 +64,9 @@ TVM_REGISTER_GLOBAL("tvm.intrin.rule.cuda.sqrt")
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.cuda.pow")
 .set_body(DispatchExtern<CUDAMath>);
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.cuda.popcount")
+.set_body(DispatchExtern<CUDAPopcount>);
+
 }  // namespace intrin
 }  // namespace codegen
 }  // namespace tvm
diff --git a/src/codegen/intrin_rule_metal.cc b/src/codegen/intrin_rule_metal.cc
index fbadf3a19bdf..b0e41770ebff 100644
--- a/src/codegen/intrin_rule_metal.cc
+++ b/src/codegen/intrin_rule_metal.cc
@@ -10,19 +10,22 @@ namespace codegen {
 namespace intrin {
 
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.metal.exp")
-.set_body(DispatchExtern<FloatDirect>);
+.set_body(DispatchExtern<Direct>);
 
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.metal.log")
-.set_body(DispatchExtern<FloatDirect>);
+.set_body(DispatchExtern<Direct>);
 
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.metal.tanh")
-.set_body(DispatchExtern<FloatDirect>);
+.set_body(DispatchExtern<Direct>);
 
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.metal.sqrt")
-.set_body(DispatchExtern<FloatDirect>);
+.set_body(DispatchExtern<Direct>);
 
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.metal.pow")
-.set_body(DispatchExtern<FloatDirect>);
+.set_body(DispatchExtern<Direct>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.metal.popcount")
+.set_body(DispatchExtern<Direct>);
 
 }  // namespace intrin
 }  // namespace codegen
diff --git a/src/codegen/intrin_rule_opencl.cc b/src/codegen/intrin_rule_opencl.cc
index a947715acdac..924abcade63f 100644
--- a/src/codegen/intrin_rule_opencl.cc
+++ b/src/codegen/intrin_rule_opencl.cc
@@ -10,19 +10,22 @@ namespace codegen {
 namespace intrin {
 
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.opencl.exp")
-.set_body(DispatchExtern<FloatDirect>);
+.set_body(DispatchExtern<Direct>);
 
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.opencl.log")
-.set_body(DispatchExtern<FloatDirect>);
+.set_body(DispatchExtern<Direct>);
 
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.opencl.tanh")
-.set_body(DispatchExtern<FloatDirect>);
+.set_body(DispatchExtern<Direct>);
 
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.opencl.sqrt")
-.set_body(DispatchExtern<FloatDirect>);
+.set_body(DispatchExtern<Direct>);
 
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.opencl.pow")
-.set_body(DispatchExtern<FloatDirect>);
+.set_body(DispatchExtern<Direct>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.opencl.popcount")
+.set_body(DispatchExtern<Direct>);
 
 }  // namespace intrin
 }  // namespace codegen
diff --git a/tests/python/integration/test_ewise.py b/tests/python/integration/test_ewise.py
index 24adf6ff28af..f8dc43da8d31 100644
--- a/tests/python/integration/test_ewise.py
+++ b/tests/python/integration/test_ewise.py
@@ -60,25 +60,40 @@ def test_log_pow_llvm():
         b.asnumpy(), np.power(np.log(a.asnumpy()), 2.0), rtol=1e-5)
 
 
-def test_popcount_llvm():
-    # graph
-    n = tvm.var('n')
-    A = tvm.placeholder((n,), name='A', dtype="uint32")
-    B = tvm.compute(A.shape, lambda *i: tvm.popcount(A(*i)), name='B')
-    s = tvm.create_schedule(B.op)
+def test_popcount():
+    def run(dtype):
+        # graph
+        n = tvm.convert(1024)
+        A = tvm.placeholder((n,), name='A', dtype=dtype)
+        B = tvm.compute(A.shape, lambda *i: tvm.popcount(A(*i)), name='B')
+        s = tvm.create_schedule(B.op)
+        # simple schedule
+        num_thread = 8
+        bx, tx = s[B].split(B.op.axis[0], factor=num_thread)
 
-    if not tvm.module.enabled("llvm"):
-        return
-    f = tvm.build(s, [A, B], "llvm")
-    ctx = tvm.cpu(0)
-    # launch the kernel.
-    n = 1024
-    a = tvm.nd.array(np.random.randint(low=0, high=1000, size=n, dtype=A.dtype), ctx)
-    b = tvm.nd.array(np.zeros(shape=n, dtype=B.dtype), ctx)
-    f(a, b)
-    np.testing.assert_allclose(
-        b.asnumpy(), list(map(lambda x: bin(x).count('1'), a.asnumpy())), rtol=1e-5)
+        def check_device(device):
+            if not tvm.module.enabled(device):
+                print("skip because %s is not enabled.." % device)
+                return
+            ctx = tvm.context(device, 0)
+            if str(ctx).startswith('gpu'):
+                s[B].bind(bx, tvm.thread_axis("blockIdx.x"))
+                s[B].bind(tx, tvm.thread_axis("threadIdx.x"))
+            func = tvm.build(s, [A, B], device)
+            # launch the kernel.
+            n = 1024
+            a = tvm.nd.array(np.random.randint(low=0, high=1000, size=n, dtype=A.dtype), ctx)
+            b = tvm.nd.array(np.zeros(shape=n, dtype=B.dtype), ctx)
+            func(a, b)
+            np.testing.assert_allclose(
+                b.asnumpy(), list(map(lambda x: bin(x).count('1'), a.asnumpy())), rtol=1e-5)
 
+        check_device("llvm")
+        check_device("cuda")
+        check_device("opencl")
+        check_device("metal")
+    run('uint32')
+    run('uint64')
 
 
 def test_add():
@@ -133,5 +148,5 @@ def check_device(device):
 if __name__ == "__main__":
     test_add()
     test_log_pow_llvm()
-    test_popcount_llvm()
+    test_popcount()
     test_exp()

From 9800fe205340435b8fd72c12f686df1139809c72 Mon Sep 17 00:00:00 2001
From: masahi <masahi129@gmail.com>
Date: Fri, 12 Jan 2018 02:06:36 +0900
Subject: [PATCH 081/949] [TOPI] Upsampling op support (#772)

* add upsampling cpu op

* add upsampling gpu schedule

* add doc for upsampling op

add more doc

* cleanup upsampling test

* add doc

* fix lint

* fix lint

* fix lint

* remove unused import

* remove skimage dependency

* remove skimage import

* remove schedule_upsampling
---
 topi/python/topi/generic/nn.py                |  1 -
 topi/python/topi/nn/__init__.py               |  1 +
 topi/python/topi/nn/upsampling.py             | 28 +++++++++++++
 topi/python/topi/testing/__init__.py          |  1 +
 topi/python/topi/testing/upsampling_python.py | 15 +++++++
 topi/tests/python/test_topi_upsampling.py     | 39 +++++++++++++++++++
 6 files changed, 84 insertions(+), 1 deletion(-)
 create mode 100644 topi/python/topi/nn/upsampling.py
 create mode 100644 topi/python/topi/testing/upsampling_python.py
 create mode 100644 topi/tests/python/test_topi_upsampling.py

diff --git a/topi/python/topi/generic/nn.py b/topi/python/topi/generic/nn.py
index d606213a5270..6f641e99f7dd 100644
--- a/topi/python/topi/generic/nn.py
+++ b/topi/python/topi/generic/nn.py
@@ -177,7 +177,6 @@ def schedule_global_pool(outs):
     """
     return _default_schedule(outs, False)
 
-
 @tvm.target.generic_func
 def schedule_binarize_pack(outs):
     """Schedule for binarize_pack
diff --git a/topi/python/topi/nn/__init__.py b/topi/python/topi/nn/__init__.py
index 3cdf3122e78e..918f399f503c 100644
--- a/topi/python/topi/nn/__init__.py
+++ b/topi/python/topi/nn/__init__.py
@@ -14,3 +14,4 @@
 from .softmax import *
 from .conv2d_transpose import *
 from .bnn import *
+from .upsampling import *
diff --git a/topi/python/topi/nn/upsampling.py b/topi/python/topi/nn/upsampling.py
new file mode 100644
index 000000000000..e1234741e286
--- /dev/null
+++ b/topi/python/topi/nn/upsampling.py
@@ -0,0 +1,28 @@
+"""TVM operator upsampling compute."""
+from __future__ import absolute_import
+import tvm
+
+
+def upsampling(data, scale):
+    """Perform nearest neighbor upsampling on the data.
+       Bilinear upsampling is not supported.
+
+    Parameters
+    ----------
+    data : tvm.Tensor
+        4-D with shape [batch, channel, in_height, in_width]
+
+    scale: int
+        upsampling scaling factor
+
+    Returns
+    -------
+    output : tvm.Tensor
+        4-D with shape [batch, channel, in_height*scale, in_width*scale]
+    """
+    batch, channel, height, width = data.shape
+    out_height = height * scale
+    out_width = width * scale
+
+    return tvm.compute((batch, channel, out_height, out_width), \
+                        lambda n, c, h, w: data[n, c, h/scale, w/scale])
diff --git a/topi/python/topi/testing/__init__.py b/topi/python/topi/testing/__init__.py
index 3a43a04437a1..6a1b361e3097 100644
--- a/topi/python/topi/testing/__init__.py
+++ b/topi/python/topi/testing/__init__.py
@@ -10,3 +10,4 @@
 from .depthwise_conv2d_python import depthwise_conv2d_python_nchw, depthwise_conv2d_python_nhwc
 from .dilate_python import dilate_python
 from .softmax_python import softmax_python, log_softmax_python
+from .upsampling_python import upsampling_python
diff --git a/topi/python/topi/testing/upsampling_python.py b/topi/python/topi/testing/upsampling_python.py
new file mode 100644
index 000000000000..328c7a5a0bc1
--- /dev/null
+++ b/topi/python/topi/testing/upsampling_python.py
@@ -0,0 +1,15 @@
+# pylint: disable=invalid-name, line-too-long, unused-variable, too-many-locals
+"""Upsampling in python"""
+import numpy as np
+
+def upsample_nearest(arr, scale):
+    return arr.repeat(scale, axis=0).repeat(scale, axis=1)
+
+def upsampling_python(data, scale):
+    ishape = data.shape
+    oshape = (ishape[0], ishape[1], ishape[2]*scale, ishape[3]*scale)
+    output_np = np.zeros(oshape, dtype=data.dtype)
+    for b in range(oshape[0]):
+        for c in range(oshape[1]):
+            output_np[b, c, :, :] = upsample_nearest(data[b, c, :, :], scale)
+    return output_np
diff --git a/topi/tests/python/test_topi_upsampling.py b/topi/tests/python/test_topi_upsampling.py
new file mode 100644
index 000000000000..08b8f987694d
--- /dev/null
+++ b/topi/tests/python/test_topi_upsampling.py
@@ -0,0 +1,39 @@
+"""Test code for upsampling"""
+import numpy as np
+import tvm
+import topi
+import math
+
+def verify_upsampling(batch, in_channel, in_height, in_width, scale):
+    A = tvm.placeholder((batch, in_channel, in_height, in_width), name='A')
+    B = topi.nn.upsampling(A, scale)
+    out_shape = (batch, in_channel, in_height*scale, in_width*scale)
+    dtype = A.dtype
+
+    a_np = np.random.uniform(size=(batch, in_channel, in_height, in_width)).astype(dtype)
+    b_np = topi.testing.upsampling_python(a_np, scale)
+
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s = topi.generic.schedule_injective(B)
+        ctx = tvm.context(device, 0)
+        a = tvm.nd.array(a_np, ctx)
+        b = tvm.nd.array(np.zeros(out_shape, dtype=dtype), ctx)
+        f = tvm.build(s, [A, B], device)
+        f(a, b)
+
+        np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+    for device in ['llvm', 'cuda']:
+        check_device(device)
+
+def test_upsampling():
+    verify_upsampling(8, 16, 32, 32, 2)
+    verify_upsampling(12, 32, 64, 64, 3)
+
+if __name__ == "__main__":
+    test_upsampling()

From 33085208adc290ef9511feaa0804b42f8d448a58 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 11 Jan 2018 20:14:17 -0800
Subject: [PATCH 082/949] [LLVM] Enable same target option in JITModule (#778)

* [LLVM] Enable same target option in JITModule

* not set mcpu explicitly
---
 src/codegen/llvm/llvm_common.cc    | 69 +++++++++++++++++++++---------
 src/codegen/llvm/llvm_common.h     | 14 ++++++
 src/codegen/llvm/llvm_module.cc    | 31 ++++++++++++--
 topi/tests/python/test_topi_bnn.py |  6 +--
 4 files changed, 92 insertions(+), 28 deletions(-)

diff --git a/src/codegen/llvm/llvm_common.cc b/src/codegen/llvm/llvm_common.cc
index b34bf0d5ec91..f8b80118cb5d 100644
--- a/src/codegen/llvm/llvm_common.cc
+++ b/src/codegen/llvm/llvm_common.cc
@@ -36,9 +36,11 @@ void InitializeLLVM() {
   }
 }
 
-llvm::TargetMachine*
-GetLLVMTargetMachine(const std::string& target_str,
-                     bool allow_null) {
+void ParseLLVMTargetOptions(const std::string& target_str,
+                            std::string* triple,
+                            std::string* mcpu,
+                            std::string* mattr,
+                            llvm::TargetOptions* options) {
   // setup target triple
   size_t start = 0;
   if (target_str.length() >= 4 &&
@@ -46,9 +48,10 @@ GetLLVMTargetMachine(const std::string& target_str,
     start = 4;
   }
   // simple parser
-  std::string target_triple = "";
-  std::string cpu = "generic";
-  std::string attr = "";
+  triple->resize(0);
+  mcpu->resize(0);
+  mattr->resize(0);
+
   bool soft_float_abi = false;
   std::string key, value;
   std::istringstream is(target_str.substr(start, target_str.length() - start));
@@ -69,11 +72,11 @@ GetLLVMTargetMachine(const std::string& target_str,
     }
     if (key == "-target" ||
         key == "-mtriple") {
-      target_triple = value;
+      *triple = value;
     } else if (key == "-mcpu") {
-      cpu = value;
+      *mcpu = value;
     } else if (key == "-mattr") {
-      attr = value;
+      *mattr = value;
     } else if (key == "-mfloat-abi") {
       if (value == "hard") {
         soft_float_abi = false;
@@ -89,19 +92,13 @@ GetLLVMTargetMachine(const std::string& target_str,
     }
   }
 
-  if (target_triple.length() == 0 ||
-      target_triple == "default") {
-    target_triple = llvm::sys::getDefaultTargetTriple();
-  }
-  std::string err;
-  const llvm::Target* target =
-      llvm::TargetRegistry::lookupTarget(target_triple, err);
-  if (target == nullptr) {
-    CHECK(allow_null) << err << " target_triple=" << target_triple;
-    return nullptr;
+  if (triple->length() == 0 ||
+      *triple == "default") {
+    *triple = llvm::sys::getDefaultTargetTriple();
   }
   // set target option
-  llvm::TargetOptions opt;
+  llvm::TargetOptions& opt = *options;
+  opt = llvm::TargetOptions();
   #if TVM_LLVM_VERSION < 50
   opt.LessPreciseFPMADOption = true;
   #endif
@@ -114,8 +111,38 @@ GetLLVMTargetMachine(const std::string& target_str,
   } else {
     opt.FloatABIType = llvm::FloatABI::Hard;
   }
+}
+
+
+llvm::TargetMachine*
+GetLLVMTargetMachine(const std::string& target_str,
+                     bool allow_null) {
+  std::string target_triple, mcpu, mattr;
+  llvm::TargetOptions opt;
+
+  ParseLLVMTargetOptions(target_str,
+                         &target_triple,
+                         &mcpu,
+                         &mattr,
+                         &opt);
+
+  if (target_triple.length() == 0 ||
+      target_triple == "default") {
+    target_triple = llvm::sys::getDefaultTargetTriple();
+  }
+  if (mcpu.length() == 0) {
+    mcpu = "generic";
+  }
+
+  std::string err;
+  const llvm::Target* target =
+      llvm::TargetRegistry::lookupTarget(target_triple, err);
+  if (target == nullptr) {
+    CHECK(allow_null) << err << " target_triple=" << target_triple;
+    return nullptr;
+  }
   llvm::TargetMachine* tm = target->createTargetMachine(
-      target_triple, cpu, attr, opt, llvm::Reloc::PIC_);
+      target_triple, mcpu, mattr, opt, llvm::Reloc::PIC_);
   return tm;
 }
 
diff --git a/src/codegen/llvm/llvm_common.h b/src/codegen/llvm/llvm_common.h
index 11ff66d8ca38..35403952f21f 100644
--- a/src/codegen/llvm/llvm_common.h
+++ b/src/codegen/llvm/llvm_common.h
@@ -57,6 +57,20 @@ namespace codegen {
  */
 void InitializeLLVM();
 
+/*!
+ * \brief Parse target options
+ * \param target_str Target string, in format "llvm -target=xxx -mcpu=xxx"
+ * \param triple Target triple
+ * \param mcpu cpu info
+ * \param options the options
+ * \param mattr The attributes
+ */
+void ParseLLVMTargetOptions(const std::string& target_str,
+                            std::string* triple,
+                            std::string* mcpu,
+                            std::string* mattr,
+                            llvm::TargetOptions* options);
+
 /*!
  * \brief Get target machine from target_str string.
  * \param target_str Target string, in format "llvm -target=xxx -mcpu=xxx"
diff --git a/src/codegen/llvm/llvm_module.cc b/src/codegen/llvm/llvm_module.cc
index 52c4f8897888..439c132d09a8 100644
--- a/src/codegen/llvm/llvm_module.cc
+++ b/src/codegen/llvm/llvm_module.cc
@@ -120,6 +120,10 @@ class LLVMModuleNode final : public runtime::ModuleNode {
     }
     cg->AddMainFunction(funcs[0]->name);
     module_ = cg->Finish();
+    module_->addModuleFlag(
+        llvm::Module::Warning, "tvm_target",
+        llvm::MDString::get(*ctx_, target));
+    target_ = target;
     mptr_ = module_.get();
   }
 
@@ -133,11 +137,19 @@ class LLVMModuleNode final : public runtime::ModuleNode {
       LOG(FATAL) << "Fail to load ir file " << file_name << "\n"
                  << "line " << err.getLineNo() << ":" << msg;
     }
-    std::string target = module_->getTargetTriple();
+    std::string target_;
+    llvm::Metadata* mtarget = module_->getModuleFlag("tvm_target");
+    if (mtarget != nullptr) {
+      llvm::MDString* pstr = llvm::dyn_cast<llvm::MDString>(mtarget);
+      CHECK(pstr != nullptr);
+      target_ = pstr->getString();
+    } else {
+      std::ostringstream os;
+      os << "llvm -target " << module_->getTargetTriple();
+      target_ = os.str();
+    }
     mptr_ = module_.get();
-    std::ostringstream os;
-    os << "llvm -target " << target;
-    tm_ = GetLLVMTargetMachine(os.str());
+    tm_ = GetLLVMTargetMachine(target_);
   }
 
  private:
@@ -145,8 +157,19 @@ class LLVMModuleNode final : public runtime::ModuleNode {
     CHECK(ee_ == nullptr);
     std::lock_guard<std::mutex> lock(mutex_);
     llvm::EngineBuilder builder(std::move(module_));
+    std::string triple, mcpu, mattr;
+    llvm::TargetOptions opt;
+    ParseLLVMTargetOptions(target_, &triple, &mcpu, &mattr, &opt);
     builder.setEngineKind(llvm::EngineKind::JIT);
     builder.setOptLevel(llvm::CodeGenOpt::Aggressive);
+    if (mcpu.length() != 0) {
+      builder.setMCPU(mcpu);
+    }
+    if (mattr.length() != 0) {
+      std::vector<std::string> mattrs{mattr};
+      builder.setMAttrs(mattrs);
+    }
+    builder.setTargetOptions(opt);
     llvm::TargetMachine *tm = builder.selectTarget();
     llvm::TargetMachine *tm_sys = GetLLVMTargetMachine("llvm");
     if (tm_sys->getTargetTriple().getArch() != tm->getTargetTriple().getArch()) {
diff --git a/topi/tests/python/test_topi_bnn.py b/topi/tests/python/test_topi_bnn.py
index 5e6a11afc602..90abc68e6b68 100644
--- a/topi/tests/python/test_topi_bnn.py
+++ b/topi/tests/python/test_topi_bnn.py
@@ -38,9 +38,9 @@ def get_ref_data():
     bnn_a = tvm.nd.array(np.zeros(get_const_tuple(bnn_A.shape), dtype=bnn_A.dtype), ctx)
     bnn_b = tvm.nd.array(np.zeros(get_const_tuple(bnn_B.shape), dtype=bnn_B.dtype), ctx)
     bnn_c = tvm.nd.array(np.zeros(get_const_tuple(bnn_C.shape), dtype=bnn_C.dtype), ctx)
-    f1 = tvm.build(s1, [A, bnn_A], 'llvm -mcpu=core-avx2')
-    f2 = tvm.build(s2, [B, bnn_B], 'llvm -mcpu=core-avx2')
-    f3 = tvm.build(s3, [bnn_A1, bnn_B1, bnn_C], 'llvm -mcpu=core-avx2')
+    f1 = tvm.build(s1, [A, bnn_A], 'llvm')
+    f2 = tvm.build(s2, [B, bnn_B], 'llvm')
+    f3 = tvm.build(s3, [bnn_A1, bnn_B1, bnn_C], 'llvm')
     f1(a, bnn_a)
     f2(b, bnn_b)
     f3(bnn_a, bnn_b, bnn_c)

From 9060417ab778b9c8bd3a4a62d01157464f9dad47 Mon Sep 17 00:00:00 2001
From: Aman <alachapelle@uchicago.edu>
Date: Mon, 15 Jan 2018 09:23:20 -0800
Subject: [PATCH 083/949] Fix lib64 not found error when building with cuda for
 OSX (#782)

---
 Makefile | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index 7f612f450cb0..fef8b2a08051 100644
--- a/Makefile
+++ b/Makefile
@@ -79,7 +79,11 @@ RUNTIME_DEP = $(RUNTIME_OBJ)
 ifdef CUDA_PATH
 	NVCC=$(CUDA_PATH)/bin/nvcc
 	CFLAGS += -I$(CUDA_PATH)/include
-	LDFLAGS += -L$(CUDA_PATH)/lib64
+	ifeq ($(UNAME_S),Darwin)
+		LDFLAGS += -L$(CUDA_PATH)/lib
+	else
+		LDFLAGS += -L$(CUDA_PATH)/lib64
+	endif
 endif
 
 ifeq ($(USE_CUDA), 1)

From f01dbf08d04f5e6851ed2f8de37f762541f050c7 Mon Sep 17 00:00:00 2001
From: Xingjian Shi <xshiab@ust.hk>
Date: Mon, 15 Jan 2018 12:39:30 -0800
Subject: [PATCH 084/949] try to fix test (#784)

try to fix

fix
---
 topi/tests/python/test_topi_reduce.py | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/topi/tests/python/test_topi_reduce.py b/topi/tests/python/test_topi_reduce.py
index 08e66e1404b6..c8d95df255c6 100644
--- a/topi/tests/python/test_topi_reduce.py
+++ b/topi/tests/python/test_topi_reduce.py
@@ -54,7 +54,7 @@ def check_device(device):
         with tvm.target.create(device):
             s = topi.generic.schedule_reduce(B)
         ctx = tvm.context(device, 0)
-        foo = tvm.build(s, [A, B], device, name="sum")
+        foo = tvm.build(s, [A, B], device, name=type)
         # Test
         in_npy = np.random.uniform(size=in_shape).astype(np.float32)
         in_npy_map = np.sqrt(np.exp(in_npy)).astype(np.float32)
@@ -74,6 +74,21 @@ def check_device(device):
         out_tvm = tvm.nd.empty(shape=out_npy.shape, ctx=ctx, dtype=out_dtype)
         for _ in range(1):
             foo(data_tvm, out_tvm)
+        if type == "argmax" or type == "argmin":
+            out_tvm_indices = out_tvm.asnumpy()
+            if keepdims:
+                out_tvm_indices = np.take(out_tvm_indices, indices=0, axis=axis)
+            if axis is None:
+                out_tvm_val = in_npy_map.ravel()[out_tvm_indices]
+            else:
+                other_indices = tuple(np.indices(in_shape[0:axis] + in_shape[(axis+1):]))
+                sel_indices = other_indices[0:axis] + (out_tvm_indices,) + other_indices[axis:]
+                out_tvm_val = in_npy_map[sel_indices]
+            if type == "argmax":
+                np.testing.assert_allclose(out_tvm_val, in_npy_map.max(axis=axis), 1E-3, 1E-3)
+            elif type == "argmin":
+                np.testing.assert_allclose(out_tvm_val, in_npy_map.min(axis=axis), 1E-3, 1E-3)
+
         np.testing.assert_allclose(out_tvm.asnumpy(), out_npy, 1E-3, 1E-3)
     for device in ["cuda", "opencl", "metal", "llvm", "rocm"]:
         check_device(device)

From 7d5ed8af5d8cae97734b4b49dc76fda92d8b3ee8 Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Wed, 17 Jan 2018 00:20:27 +0800
Subject: [PATCH 085/949] [CODEGEN] fix vector conversion for opencl (#783)

* support more argument type in depthwise_conv2d

* mark all pointer as 'restrict' & fix vector conversion for opencl
---
 src/codegen/codegen_c.cc                      | 22 +++++++++----------
 src/codegen/codegen_c.h                       |  4 ++--
 src/codegen/codegen_opencl.cc                 | 16 ++++++++++++++
 src/codegen/codegen_opencl.h                  |  2 ++
 src/pass/split_host_device.cc                 |  3 ---
 topi/python/topi/nn/depthwise_conv2d.py       | 22 +++++++++++++++----
 .../topi/testing/depthwise_conv2d_python.py   | 12 ++++++++--
 .../python/test_topi_depthwise_conv2d.py      |  7 +++---
 8 files changed, 62 insertions(+), 26 deletions(-)

diff --git a/src/codegen/codegen_c.cc b/src/codegen/codegen_c.cc
index 05ba1f2f357b..403a5525b170 100644
--- a/src/codegen/codegen_c.cc
+++ b/src/codegen/codegen_c.cc
@@ -38,14 +38,17 @@ void CodeGenC::AddFunction(LoweredFunc f) {
     if (i != 0) stream << ", ";
     if (v.type().is_handle()) {
       auto it = alloc_storage_scope_.find(v.get());
-      if (it != alloc_storage_scope_.end()) {
+      if (it != alloc_storage_scope_.end())
         PrintStorageScope(it->second, stream);
-        stream << ' ';
+      stream << ' ';
+
+      if (handle_data_type_.count(v.get())) {
+        PrintType(handle_data_type_.at(v.get()), stream);
+      } else {
+        stream << "void";
       }
-    }
-    if (handle_data_type_.count(v.get())) {
-      PrintType(handle_data_type_.at(v.get()), stream);
       stream << "*";
+
       if (f->is_restricted && restrict_keyword_.length() != 0) {
         stream << ' ' << restrict_keyword_;
       }
@@ -402,12 +405,9 @@ inline void PrintBinaryIntrinsitc(const Call* op,
   }
 }
 void CodeGenC::VisitExpr_(const Cast *op, std::ostream& os) {  // NOLINT(*)
-  os << "(";
-  this->PrintType(op->type, os);
-  os << ")";
-  os << '(';
-  this->PrintExpr(op->value, os);
-  os << ')';
+  std::stringstream value;
+  this->PrintExpr(op->value, value);
+  os << CastFromTo(value.str(), op->value.type(), op->type);
 }
 void CodeGenC::VisitExpr_(const Variable *op, std::ostream& os) {  // NOLINT(*)
   os << GetVarID(op);
diff --git a/src/codegen/codegen_c.h b/src/codegen/codegen_c.h
index 1c68dd18bd68..895e94b8198e 100644
--- a/src/codegen/codegen_c.h
+++ b/src/codegen/codegen_c.h
@@ -142,6 +142,8 @@ class CodeGenC :
   // print store of single element.
   virtual void PrintVecElemStore(
       const std::string& vec, Type t, int i, const std::string& value);
+  // Get a cast type from to
+  virtual std::string CastFromTo(std::string value, Type from, Type target);
 
  protected:
   // Print reference to struct location
@@ -150,8 +152,6 @@ class CodeGenC :
   // print reference to a buffer as type t in index.
   std::string GetBufferRef(
       Type t, const Variable* buffer, Expr index);
-  // Get a cast type from to
-  std::string CastFromTo(std::string value, Type from, Type target);
   /*!
    * \brief If buffer is allocated as type t.
    * \param buf_var The buffer variable.
diff --git a/src/codegen/codegen_opencl.cc b/src/codegen/codegen_opencl.cc
index d2133f85a08c..03f4acdd057c 100644
--- a/src/codegen/codegen_opencl.cc
+++ b/src/codegen/codegen_opencl.cc
@@ -175,6 +175,22 @@ void CodeGenOpenCL::PrintStorageScope(
   }
 }
 
+std::string CodeGenOpenCL::CastFromTo(std::string value, Type from, Type target) {
+  if (from == target) return value;
+  std::ostringstream os;
+  if (target.lanes() == 1) {
+    os << "((";
+    this->PrintType(target, os);
+    os << ")" << value << ")";
+  } else {  // convert vector type
+    os << "(";
+    os << "convert_";
+    this->PrintType(target, os);
+    os << "(" << value << "))";
+  }
+  return os.str();
+}
+
 void CodeGenOpenCL::VisitExpr_(const Broadcast* op, std::ostream& os) {   // NOLINT(*)
   std::string v = PrintExpr(op->value);
   os << "((";
diff --git a/src/codegen/codegen_opencl.h b/src/codegen/codegen_opencl.h
index a10c165ee3a1..424bfa5ae2b3 100644
--- a/src/codegen/codegen_opencl.h
+++ b/src/codegen/codegen_opencl.h
@@ -34,6 +34,8 @@ class CodeGenOpenCL final : public CodeGenC {
   // the address of load/store
   void PrintVecAddr(const Variable* buffer, Type t,
                     Expr base, std::ostream& os);  // NOLINT(*)
+  std::string CastFromTo(std::string value, Type from, Type target); // NOLINT(*)
+
   // overload visitor
   void VisitExpr_(const Broadcast* op, std::ostream& os) final; // NOLINT(*)
 
diff --git a/src/pass/split_host_device.cc b/src/pass/split_host_device.cc
index 44e9753081db..942e70339488 100644
--- a/src/pass/split_host_device.cc
+++ b/src/pass/split_host_device.cc
@@ -191,9 +191,6 @@ class HostDeviceSplitter : public IRMutator {
         auto it = handle_data_type_.find(v.get());
         if (it != handle_data_type_.end()) {
           n->handle_data_type.Set(v, it->second);
-        } else {
-          // int32 as a placeholder
-          n->handle_data_type.Set(v, make_const(UInt(32), 0));
         }
       }
     }
diff --git a/topi/python/topi/nn/depthwise_conv2d.py b/topi/python/topi/nn/depthwise_conv2d.py
index 785bdab27738..2f0aa7ea95ce 100644
--- a/topi/python/topi/nn/depthwise_conv2d.py
+++ b/topi/python/topi/nn/depthwise_conv2d.py
@@ -31,9 +31,14 @@ def depthwise_conv2d_nchw(Input, Filter, stride, padding, out_dtype='float32'):
     Output : tvm.Tensor
         4-D with shape [batch, out_channel, out_height, out_width]
     """
+    out_dtype = Input.dtype
+
     batch, in_channel, in_height, in_width = Input.shape
     filter_channel, channel_multiplier, filter_height, filter_width = Filter.shape
-    stride_h, stride_w = stride
+    if isinstance(stride, int):
+        stride_h = stride_w = stride
+    else:
+        stride_h, stride_w = stride
 
     pad_top, pad_left, pad_down, pad_right = get_pad_tuple(
         padding, (filter_height, filter_width))
@@ -82,7 +87,10 @@ def depthwise_conv2d_nhwc(Input, Filter, stride, padding):
     """
     batch, in_height, in_width, in_channel = Input.shape
     filter_height, filter_width, filter_channel, channel_multiplier = Filter.shape
-    stride_h, stride_w = stride
+    if isinstance(stride, int):
+        stride_h = stride_w = stride
+    else:
+        stride_h, stride_w = stride
 
     pad_top, pad_left, pad_down, pad_right = get_pad_tuple(
         padding, (filter_height, filter_width))
@@ -131,7 +139,10 @@ def depthwise_conv2d_backward_input_nhwc(Filter, Out_grad, oshape, ishape, strid
     batch, in_h, in_w, in_c = ishape
     _, out_h, out_w, out_c = oshape
     filter_h, filter_w, _, channel_multiplier = Filter.shape
-    stride_h, stride_w = stride
+    if isinstance(stride, int):
+        stride_h = stride_w = stride
+    else:
+        stride_h, stride_w = stride
 
     dilated_out_grad = dilate(Out_grad, [1, stride_h, stride_w, 1], name='dilated_out_grad')
 
@@ -186,7 +197,10 @@ def depthwise_conv2d_backward_weight_nhwc(Input, Out_grad, oshape, fshape, strid
     batch, out_h, out_w, out_c = oshape
     filter_h, filter_w, _, channel_multiplier = fshape
     in_c = Input.shape[3].value
-    stride_h, stride_w = stride
+    if isinstance(stride, int):
+        stride_h = stride_w = stride
+    else:
+        stride_h, stride_w = stride
 
     pad_top, pad_left, pad_bottom, pad_right = get_pad_tuple(padding, (filter_h, filter_w))
 
diff --git a/topi/python/topi/testing/depthwise_conv2d_python.py b/topi/python/topi/testing/depthwise_conv2d_python.py
index 84784f97c2b8..d7baf4a1beaf 100644
--- a/topi/python/topi/testing/depthwise_conv2d_python.py
+++ b/topi/python/topi/testing/depthwise_conv2d_python.py
@@ -27,7 +27,11 @@ def depthwise_conv2d_python_nchw(input_np, filter_np, stride, padding):
     """
     batch, in_channel, in_height, in_width = input_np.shape
     _, channel_multiplier, filter_height, filter_width = filter_np.shape
-    stride_h, stride_w = stride
+    if isinstance(stride, int):
+        stride_h = stride_w = stride
+    else:
+        stride_h, stride_w = stride
+
     # calculate output shape
     if padding == 'VALID':
         out_channel = in_channel * channel_multiplier
@@ -84,7 +88,11 @@ def depthwise_conv2d_python_nhwc(input_np, filter_np, stride, padding):
     """
     batch, in_height, in_width, in_channel = input_np.shape
     filter_height, filter_width, _, channel_multiplier = filter_np.shape
-    stride_h, stride_w = stride
+    if isinstance(stride, int):
+        stride_h = stride_w = stride
+    else:
+        stride_h, stride_w = stride
+
     # calculate output shape
     if padding == 'VALID':
         out_channel = in_channel * channel_multiplier
diff --git a/topi/tests/python/test_topi_depthwise_conv2d.py b/topi/tests/python/test_topi_depthwise_conv2d.py
index df9a755c078e..62230857ba02 100644
--- a/topi/tests/python/test_topi_depthwise_conv2d.py
+++ b/topi/tests/python/test_topi_depthwise_conv2d.py
@@ -7,18 +7,17 @@
 from topi.cuda.depthwise_conv2d import schedule_depthwise_conv2d_nhwc
 
 
-def depthwise_conv2d_with_workload_nchw(batch, in_channel, in_height, channel_multiplier, filter_height, stride_h, padding):
+def depthwise_conv2d_with_workload_nchw(batch, in_channel, in_height, channel_multiplier, filter_height, stride, padding):
     in_width = in_height
     filter_channel = in_channel
     filter_width = filter_height
-    stride_w = stride_h
     # placeholder
     Input = tvm.placeholder((batch, in_channel, in_height, in_width), name='Input')
     Filter = tvm.placeholder((filter_channel, channel_multiplier, filter_height, filter_width), name='Filter')
     Scale = tvm.placeholder((in_channel * channel_multiplier,), name='Scale')
     Shift = tvm.placeholder((in_channel * channel_multiplier,), name='Shift')
     # declare
-    DepthwiseConv2d = topi.nn.depthwise_conv2d_nchw(Input, Filter, stride=[stride_h, stride_w], padding=padding)
+    DepthwiseConv2d = topi.nn.depthwise_conv2d_nchw(Input, Filter, stride=stride, padding=padding)
     ScaleShift = topi.nn.scale_shift_nchw(DepthwiseConv2d, Scale, Shift)
     Relu = topi.nn.relu(ScaleShift)
 
@@ -56,7 +55,7 @@ def get_ref_data():
             shift_np = np.random.uniform(size=shift_shape).astype(dtype)
             # correctness with scipy
             depthwise_conv2d_scipy = topi.testing.depthwise_conv2d_python_nchw(
-                input_np, filter_np, stride=[stride_h, stride_w], padding=padding)
+                input_np, filter_np, stride=stride, padding=padding)
             scale_shift_scipy = np.zeros(shape=scale_shift_shape)
             for c in range(in_channel * channel_multiplier):
                 scale_shift_scipy[:,c,:,:] = depthwise_conv2d_scipy[:,c,:,:] * scale_np[c] + shift_np[c]

From 717aa0cfea3cbf85a6246dc20740b05255f4bb0b Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Wed, 17 Jan 2018 01:14:01 +0800
Subject: [PATCH 086/949] [TOPI] add schedule for ARM Mali GPU (#786)

* add schedule for ARM Mali GPU

* fix lint

* fix lint
---
 dmlc-core                                 |   2 +-
 python/tvm/target.py                      |  13 +
 topi/python/topi/__init__.py              |   1 +
 topi/python/topi/mali/__init__.py         |   7 +
 topi/python/topi/mali/conv2d.py           | 479 ++++++++++++++++++++++
 topi/python/topi/mali/dense.py            | 100 +++++
 topi/python/topi/mali/depthwise_conv2d.py | 106 +++++
 7 files changed, 707 insertions(+), 1 deletion(-)
 create mode 100644 topi/python/topi/mali/__init__.py
 create mode 100644 topi/python/topi/mali/conv2d.py
 create mode 100644 topi/python/topi/mali/dense.py
 create mode 100644 topi/python/topi/mali/depthwise_conv2d.py

diff --git a/dmlc-core b/dmlc-core
index 674a662c22b9..c0871823b518 160000
--- a/dmlc-core
+++ b/dmlc-core
@@ -1 +1 @@
-Subproject commit 674a662c22b900b76e8a3c9b77987a2c5563ba71
+Subproject commit c0871823b518093a0d04d6cba0a3291bc7b31401
diff --git a/python/tvm/target.py b/python/tvm/target.py
index 8f5f4bc87852..3437e70e0a6d 100644
--- a/python/tvm/target.py
+++ b/python/tvm/target.py
@@ -264,6 +264,19 @@ def rasp(options=None):
     return Target("llvm", opts)
 
 
+def mali(options=None):
+    """Returns a ARM Mali GPU target.
+
+    Parameters
+    ----------
+    options : list of str
+        Additional options
+    """
+    opts = ["-device=mali"]
+    opts = _merge_opts(opts, options)
+    return Target("opencl", opts)
+
+
 def create(target_str):
     """Get a target given target string.
 
diff --git a/topi/python/topi/__init__.py b/topi/python/topi/__init__.py
index c28dfb34e8b6..9760722798af 100644
--- a/topi/python/topi/__init__.py
+++ b/topi/python/topi/__init__.py
@@ -17,6 +17,7 @@
 from . import x86
 from . import cuda
 from . import rasp
+from . import mali
 from . import testing
 from . import util
 from . import rocm
diff --git a/topi/python/topi/mali/__init__.py b/topi/python/topi/mali/__init__.py
new file mode 100644
index 000000000000..bdd718e043a0
--- /dev/null
+++ b/topi/python/topi/mali/__init__.py
@@ -0,0 +1,7 @@
+# pylint: disable=redefined-builtin, wildcard-import
+"""ARM Mali GPU specific declaration and schedules."""
+from __future__ import absolute_import as _abs
+
+from .conv2d import *
+from .depthwise_conv2d import *
+from .dense import *
diff --git a/topi/python/topi/mali/conv2d.py b/topi/python/topi/mali/conv2d.py
new file mode 100644
index 000000000000..940d64668ac4
--- /dev/null
+++ b/topi/python/topi/mali/conv2d.py
@@ -0,0 +1,479 @@
+# pylint: disable=invalid-name,unused-variable,unused-argument,no-else-return
+"""conv2d schedule on ARM Mali GPU"""
+
+from __future__ import absolute_import as _abs
+import tvm
+
+from .. import generic
+from .. import util
+from .. import tag
+from ..nn import pad
+from ..nn.conv2d import conv2d
+from ..nn.util import get_pad_tuple
+
+##### SCHEDULE UTILITIES #####
+def fuse_and_bind(s, tensor, axis=None, num_thread=None):
+    """ fuse all the axis and bind to GPU threads """
+    axis = axis or s[tensor].op.axis
+    fused = s[tensor].fuse(*axis)
+    max_threads = tvm.target.current_target(allow_none=False).max_num_threads
+    bx, tx = s[tensor].split(fused, num_thread or max_threads)
+    s[tensor].bind(bx, tvm.thread_axis("blockIdx.x"))
+    s[tensor].bind(tx, tvm.thread_axis("threadIdx.x"))
+    return bx, tx
+
+def tile_and_bind(s, tensor, y, x, y_factor, x_factor=None):
+    """ tile and bind to GPU threads """
+    x_factor = x_factor or y_factor
+    yo, xo, yi, xi = s[tensor].tile(y, x, y_factor, x_factor)
+    s[tensor].bind(xo, tvm.thread_axis("blockIdx.x"))
+    s[tensor].bind(xi, tvm.thread_axis("threadIdx.x"))
+    s[tensor].bind(yo, tvm.thread_axis("blockIdx.y"))
+    s[tensor].bind(yi, tvm.thread_axis("threadIdx.y"))
+    return yo, xo, yi, xi
+
+def tile_and_bind3d(s, tensor, z, y, x, z_factor=2, y_factor=None, x_factor=None):
+    """ tile and bind 3d """
+    y_factor = y_factor or z_factor
+    x_factor = x_factor or y_factor
+    zo, zi = s[tensor].split(z, z_factor)
+    yo, yi = s[tensor].split(y, y_factor)
+    xo, xi = s[tensor].split(x, x_factor)
+    s[tensor].bind(zo, tvm.thread_axis("blockIdx.z"))
+    s[tensor].bind(zi, tvm.thread_axis("threadIdx.z"))
+    s[tensor].bind(yo, tvm.thread_axis("blockIdx.y"))
+    s[tensor].bind(yi, tvm.thread_axis("threadIdx.y"))
+    s[tensor].bind(xo, tvm.thread_axis("blockIdx.x"))
+    s[tensor].bind(xi, tvm.thread_axis("threadIdx.x"))
+
+def pack_tensor(s, tensor, factor, readers):
+    """ do transform X[n, m] -> X[n / factor, m, factor] """
+    tmp = s.cache_read(tensor, 'global', readers)
+    y, x = s[tmp].op.axis
+    yo, yi = s[tmp].split(y, factor)
+    s[tmp].reorder(yo, x, yi)
+    s[tmp].compute_inline()
+    return s.cache_write(tmp, 'global')
+
+def transpose(s, tensor, readers):
+    """ do transform X[n, m] -> X[m, n] """
+    tmp = s.cache_read(tensor, 'global', readers)
+    y, x = s[tmp].op.axis
+    s[tmp].reorder(x, y)
+    s[tmp].compute_inline()
+    return s.cache_write(tmp, "global"), tmp
+
+@conv2d.register("mali")
+def decl_conv2d(data, kernel, stride, padding, layout='NCHW', out_dtype='float32'):
+    """Conv2D operator for ARM Mali GPU backend.
+
+    Parameters
+    ----------
+    data : tvm.Tensor
+        4-D with shape [batch, in_channel, in_height, in_width]
+
+    kernel : tvm.Tensor
+        4-D with shape [num_filter, in_channel, filter_height, filter_width]
+
+    stride : int or a list/tuple of two ints
+        stride size, or [stride_height, stride_width]
+
+    padding : int or a list/tuple of two ints
+        padding size, or [pad_height, pad_width]
+
+    layout : str
+        layout of data
+
+    Returns
+    -------
+    output : tvm.Tensor
+        4-D with shape [batch, out_channel, out_height, out_width]
+    """
+    assert layout == 'NCHW', "only support NCHW convolution on mali"
+    assert data.shape[0].value == 1, "only support batch size=1 convolution on mali"
+    assert data.dtype == kernel.dtype, "Do not support inputs with different data types now."
+
+    out_dtype = data.dtype
+    if util.get_const_int(kernel.shape[2]) == 1:
+        return _decl_im2col(data, kernel, stride, padding, layout, out_dtype)
+    else:
+        return _decl_direct(data, kernel, stride, padding, layout, out_dtype)
+
+@generic.schedule_conv2d_nchw.register(["mali"])
+def schedule_conv2d_nchw(outs):
+    """Schedule for conv2d_nchw for ARM Mali GPU
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+        The computation graph description of conv2d_nchw
+        in the format of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+        The computation schedule for conv2d_nchw.
+    """
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    s = tvm.create_schedule([x.op for x in outs])
+
+    def traverse(op):
+        """inline all one-to-one-mapping operators except the last stage (output)"""
+        if tag.is_broadcast(op.tag):
+            if op not in s.outputs:
+                s[op].compute_inline()
+            for tensor in op.input_tensors:
+                if tensor.op.input_tensors:
+                    traverse(tensor.op)
+
+        if 'im2col_conv_output' in op.tag:
+            _schedule_im2col_conv2d(s, op)
+
+        if 'direct_conv_output' in op.tag:
+            _schedule_direct_conv2d(s, op)
+
+    traverse(outs[0].op)
+    return s
+
+def _decl_direct(data, kernel, stride, padding, layout, out_dtype):
+    """declare the direct method (spatial packing) for conv2d"""
+    _, CI, IH, IW = [util.get_const_int(x) for x in data.shape]
+    CO, _, KH, KW = [util.get_const_int(x) for x in kernel.shape]
+    HPAD, WPAD, _, _ = get_pad_tuple(padding, kernel)
+    HCAT, WCAT = KH - 1, KW - 1
+
+    if isinstance(stride, (tuple, list)):
+        HSTR, WSTR = stride
+    else:
+        HSTR, WSTR = stride, stride
+
+    N = 1
+    TH = IH + 2*HPAD
+    TW = IW + 2*WPAD
+    OH = (IH + 2*HPAD - KH) // HSTR + 1
+    OW = (IW + 2*WPAD - KW) // WSTR + 1
+
+    DO_PAD = (HPAD != 0 and WPAD != 0)
+    if DO_PAD:
+        data_pad = pad(data, (0, 0, HPAD, WPAD), name="data_pad")
+    else:
+        data_pad = data
+
+    # set tunable parameters (tile factor, ...)
+    tune_config = getattr(tvm.target.current_target(), "tune_config", None)
+    if tune_config is None:
+        VH = 1
+        VW, VC = 4, 4
+        # correct tile factor
+        if OW % VW != 0:
+            if OW == 14:
+                VW = 2
+                VC = 8
+            elif OW == 7:
+                VW = 7
+    else:
+        VH = tune_config['VH']
+        VW = tune_config['VW']
+        VC = tune_config['VC']
+
+    if data.dtype == 'float16':
+        VC *= 2
+
+    assert CO % VC == 0
+    assert OH % VH == 0, "OH: %d  VH : %d" % (OH, VH)
+    assert OW % VW == 0, "OW: %d  VW : %d" % (OW, VW)
+
+    dvshape = (N, TH//(VH*HSTR), TW//(VW*WSTR), CI, VH*HSTR+HCAT, VW*WSTR+WCAT)
+    kvshape = (CO // VC, CI, KH, KW, VC)
+    ovshape = (N, CO // VC, OH // VH, OW // VW, VH, VW, VC)
+    oshape = (N, CO, OH, OW)
+
+    data_vec = tvm.compute(dvshape, lambda n, h, w, ci, vh, vw:
+                           data_pad[n][ci][h*VH*HSTR+vh][w*VW*WSTR+vw],
+                           name='data_vec')
+
+    kernel_vec = tvm.compute(kvshape, lambda co, ci, kh, kw, vc:
+                             kernel[co*VC+vc][ci][kh][kw],
+                             name='kernel_vec')
+
+    ci = tvm.reduce_axis((0, CI), name='ci')
+    kh = tvm.reduce_axis((0, KH), name='kh')
+    kw = tvm.reduce_axis((0, KW), name='kw')
+
+    conv = tvm.compute(ovshape, lambda n, co, h, w, vh, vw, vc:\
+                tvm.sum(data_vec[n, h, w, ci, vh*HSTR+kh, vw*WSTR+kw].astype(out_dtype) *
+                        kernel_vec[co, ci, kh, kw, vc].astype(out_dtype),
+                        axis=[ci, kh, kw]), name='conv')
+
+    output = tvm.compute(oshape, lambda n, co, h, w:
+                         conv[n][co//VC][h/VH][w//VW][h%VH][w%VW][co%VC],
+                         name='output_unpack', tag='direct_conv_output')
+
+    return output
+
+def _schedule_direct_conv2d(s, op):
+    """schedule the direct method (spatial packing) for conv2d"""
+    # get ops and tensors
+    output = op.output(0)
+    output_height = util.get_const_int(output.shape[2])
+
+    conv = op.input_tensors[0]
+    data_vec = s[conv].op.input_tensors[0]
+    kernel_vec = s[conv].op.input_tensors[1]
+    data = s[data_vec].op.input_tensors[0]
+    kernel = s[kernel_vec].op.input_tensors[0]
+
+    # set tunable parameters (tile factor, ...)
+    tune_config = getattr(tvm.target.current_target(), "tune_config", None)
+    if tune_config is None:
+        num_thread = 8
+
+        out_channel = util.get_const_int(kernel.shape[0])
+        in_channel = util.get_const_int(kernel.shape[1])
+        in_width = util.get_const_int(data.shape[2])
+
+        if in_width >= 224:
+            pass
+        elif in_width >= 112:
+            pass
+        elif in_width >= 56:
+            if out_channel != in_channel:
+                num_thread = 16
+        elif in_width >= 28:
+            if out_channel >= 256:
+                num_thread = 16
+        elif in_width >= 14:
+            if in_channel == out_channel:
+                num_thread = 8
+            else:
+                num_thread = 4
+    else:
+        num_thread = tune_config["num_thread"]
+
+    last = 1
+    if output_height == 28:
+        last = 7
+        num_thread = 32
+
+    if data.dtype == 'float16' and (util.get_const_int(conv.shape[1]) == 4 or output_height == 28):
+        num_thread /= 2
+
+    # schedule padding
+    if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
+        data_pad = data
+        data = data_pad.op.input_tensors[0]
+        s[data_pad].compute_inline()
+
+    # schedule data packing
+    _, h, w, ci, vh, vw = s[data_vec].op.axis
+    tile_and_bind3d(s, data_vec, h, w, ci, 1)
+    s[data_vec].unroll(vw)
+
+    # schedule kernel packing
+    co, ci, kh, kw, vc = s[kernel_vec].op.axis
+    tile_and_bind(s, kernel_vec, co, ci, 1)
+    s[kernel_vec].unroll(kh)
+    s[kernel_vec].unroll(kw)
+    s[kernel_vec].vectorize(vc)
+
+    # schedule convolution
+    _, c, h, w, vh, vw, vc = s[conv].op.axis
+    kc, kh, kw = s[conv].op.reduce_axis
+    s[conv].reorder(_, c, h, w, vh, kc, kh, kw, vw, vc)
+    tile_and_bind3d(s, conv, c, h, w, num_thread, 1, last)
+    s[conv].unroll(kh)
+    s[conv].unroll(kw)
+    s[conv].unroll(vw)
+    s[conv].vectorize(vc)
+
+    # schedule output
+    if output.op not in s.outputs:  # has bias
+        s[output].compute_inline()
+        output = s.outputs[0]
+
+    _, co, oh, ow = s[output].op.axis
+    tile_and_bind3d(s, output, co, oh, ow, num_thread, 1, last)
+
+    #print(tvm.lower(s, [data, kernel, output], simple_mode=True))
+
+def _decl_im2col(data, kernel, stride, padding, layout='NCHW', out_dtype='float32'):
+    """declare the Im2Col method for conv2d"""
+    _, CI, IH, IW = [x.value for x in data.shape]
+    CO, _, KH, KW = [x.value for x in kernel.shape]
+    HPAD, WPAD, _, _ = get_pad_tuple(padding, kernel)
+
+    if isinstance(stride, (tuple, list)):
+        HSTR, WSTR = stride
+    else:
+        HSTR, WSTR = stride, stride
+
+    N = 1
+    OH = (IH + 2*HPAD - KH) // HSTR + 1
+    OW = (IW + 2*WPAD - KW) // WSTR + 1
+
+    DO_PAD = (HPAD != 0 and WPAD != 0)
+    if DO_PAD:
+        data_pad = pad(data, (0, 0, HPAD, WPAD), name="data_pad")
+    else:
+        data_pad = data
+
+    ALIGN = 16
+    def upround(x, align):
+        return (x + align - 1) / align * align
+
+    # A [CO, CI * KH * KW]
+    reduce_len = upround(CI * KH * KW, ALIGN)
+    A = tvm.compute((upround(CO, ALIGN), reduce_len), lambda i, j:
+                    kernel[i][j / KW / KH][j / KW % KH][j % KW], name='A')
+
+    # B [CI * KH * KW, N * OH * OW]
+    B = tvm.compute((reduce_len, upround(N * OH * OW, ALIGN)), lambda i, j:\
+            tvm.select(tvm.all(i < CI * KH * KW, j < N * OH * OW),
+                       data_pad[j / (OH*OW)][i / (KH*KW)][j / OW % OH*HSTR + i / KW % KH]
+                       [j % OW*WSTR + i % KW],
+                       tvm.const(0, data_pad.dtype)), name='B')
+
+    gemm_n, gemm_l, gemm_m = A.shape[0], reduce_len, B.shape[1]
+
+    # C [CO, N * OH * OW]
+    k = tvm.reduce_axis((0, gemm_l), name='k')
+    C = tvm.compute((gemm_n, gemm_m), lambda i, j: tvm.sum(A[i, k] * B[k, j], axis=k), name='C')
+
+    # output
+    # the last term C[gemm_n-1, gemm_m-1] is for enabling the alignment,
+    # otherwise the alignment above will be eliminated by bound inference
+    output = tvm.compute((N, CO, OH, OW), lambda n, co, h, w:\
+                 C[co][n * OW * OW + h * OW + w] + tvm.const(0, C.dtype) * C[gemm_n-1, gemm_m-1],
+                         name='output', tag='im2col_conv_output')
+
+    return output
+
+def _schedule_im2col_conv2d(s, op):
+    """schedule the Im2Col method for conv2d"""
+
+    # get ops and tensors
+    output = op.output(0)
+    C = op.input_tensors[0]
+    A, B = C.op.input_tensors
+    kernel = A.op.input_tensors[0]
+    data = B.op.input_tensors[0]
+
+    # tuning parameter config
+    tune_config = getattr(tvm.target.current_target(), "tune_config", None)
+    if tune_config is None: # use rule
+        bn = 4
+        unroll_step = 16
+
+        total_work = util.get_const_int(C.shape[0] * C.shape[1])
+        reduce_work = util.get_const_int(A.shape[1])
+        if total_work > 200000:
+            last_work = util.get_const_int(C.shape[1])
+            if last_work > 10000:
+                num_thread = 16
+            elif last_work > 3000:
+                num_thread = 8
+            elif reduce_work > 100:
+                num_thread = 4
+            else:
+                num_thread = 2
+
+            if reduce_work < 50 and last_work < 30000:
+                num_thread = 4
+        elif total_work > 150000:
+            num_thread = 8
+        elif total_work > 50000:
+            num_thread = 4
+        else:
+            num_thread = 2
+
+        if num_thread == 4:
+            unroll_step = 2
+    else:
+        bn = tune_config["bn"]
+        num_thread = tune_config["num_thread"]
+        unroll_step = tune_config["unroll_step"]
+
+    bna = bnb = bn
+    num_thread1 = num_thread2 = num_thread
+    if data.dtype == 'float16':
+        bnb *= 2
+        last_work = util.get_const_int(C.shape[1])
+        if last_work % (bnb * num_thread2) != 0:
+            num_thread1 = num_thread * 2
+            num_thread2 = num_thread / 2
+
+    # schedule padding
+    if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
+        data_pad = data
+        s[data_pad].compute_inline()
+
+    ##### SCHEDULE A #####
+    if util.get_const_int(kernel.shape[2]) == 1 and util.get_const_int(kernel.shape[3]) == 1:
+        s[A].compute_inline()
+    else:
+        y, x = s[A].op.axis
+        yo, xo, yi, xi = s[A].tile(y, x, bna, util.get_const_int(kernel.shape[3]))
+        s[A].vectorize(xi)
+        fuse_and_bind(s, A, [yo, xo])
+
+    # pack to vector form
+    packedA = pack_tensor(s, A, bna, [C])
+
+    # vectorize load
+    y, x = s[packedA].op.axis[:2]
+    tmp = s.cache_write(packedA, "local")
+    x, xt = s[packedA].split(x, bna)
+    _, _, _, xi = tile_and_bind(s, packedA, y, x, num_thread)
+    s[tmp].compute_at(s[packedA], xi)
+    s[tmp].vectorize(s[tmp].op.axis[1])
+    s[tmp].unroll(s[tmp].op.axis[2])
+    s[packedA].vectorize(s[packedA].op.axis[2])
+    s[packedA].unroll(xt)
+
+    ##### SCHEDULE B #####
+    y, x = s[B].op.axis
+    yo, xo, yi, xi = s[B].tile(y, x, 1, 1 * bnb)
+    fuse_and_bind(s, B, [yo, xo])
+
+    # transpose and pack to vector form
+    B_transpose, B_tmp = transpose(s, B, [C])
+    s[B_transpose].compute_inline()
+    packedB = pack_tensor(s, B_transpose, bnb, [B_tmp])
+
+    # vectorize load
+    s[packedB].vectorize(s[packedB].op.axis[2])
+    y, x = s[packedB].op.axis[:2]
+    tile_and_bind(s, packedB, y, x, num_thread)
+
+    ##### SCHEDULE C #####
+    # vectorize and unroll dot
+    y, x = s[C].op.axis
+    y, x, yt, xt = s[C].tile(y, x, bna, bnb)
+
+    k = s[C].op.reduce_axis[0]
+    s[C].reorder(k, yt, xt)
+    if unroll_step != 1:
+        k, k_unroll = s[C].split(k, unroll_step)
+        s[C].unroll(k_unroll)
+    s[C].unroll(yt)
+    s[C].vectorize(xt)
+
+    tile_and_bind(s, C, y, x, num_thread1, num_thread2)
+
+    ##### COPY TO OUTPUT #####
+    if output.op in s.outputs:  # no bias
+        output = output
+    else:                       # has bias
+        s[output].compute_inline()
+        output = s.outputs[0]
+
+    n, co, h, w = s[output].op.axis
+    h, w, vh, vw = s[output].tile(h, w, 1, bnb)
+    s[output].unroll(vh)
+    if util.get_const_int(s[output].op.output(0).shape[3]) % bnb != 0:
+        pass
+    else:
+        s[output].vectorize(vw)
+    fuse_and_bind(s, output, [n, co, h, w])
+
+    #print(tvm.lower(s, [data, kernel], simple_mode=True))
diff --git a/topi/python/topi/mali/dense.py b/topi/python/topi/mali/dense.py
new file mode 100644
index 000000000000..d3edeafed3b3
--- /dev/null
+++ b/topi/python/topi/mali/dense.py
@@ -0,0 +1,100 @@
+# pylint: disable=invalid-name,unused-variable
+"""dense schedule on ARM Mali GPU"""
+
+from __future__ import absolute_import as _abs
+
+import tvm
+
+from .. import generic
+from .. import util
+from .. import tag
+
+@generic.schedule_dense.register(["mali"])
+def schedule_dense(outs):
+    """Schedule for dense operator.
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+        The computation graph description of dense
+        in the format of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+        The computation schedule for dense.
+    """
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    s = tvm.create_schedule([x.op for x in outs])
+    def _schedule(dense):
+        data = s[dense].op.input_tensors[0]
+        weight = s[dense].op.input_tensors[1]
+
+        hidden = util.get_const_int(weight.shape[1])
+        out = util.get_const_int(weight.shape[0])
+
+        # set tunable parameter
+        tune_config = getattr(tvm.target.current_target(), "tune_config", None)
+        if tune_config is None:
+            if hidden > 8192:
+                num_thread = 32
+                unroll_step = 32
+            else:
+                if out <= 1024:
+                    num_thread = 32
+                    unroll_step = 16
+                else:
+                    num_thread = 256
+                    unroll_step = 32
+
+            if data.dtype == 'float16':
+                if hidden > 8192:
+                    num_thread = 2
+                    unroll_step = 32
+                else:
+                    num_thread = 8
+                    unroll_step = 256
+        else:
+            num_thread = tune_config['num_thread']
+            unroll_step = tune_config['unroll_step']
+
+        def fuse_and_bind(s, tensor, axis=None, num_thread=None):
+            """ fuse all the axis and bind to GPU threads """
+            axis = axis or s[tensor].op.axis
+            fused = s[tensor].fuse(*axis)
+            max_threads = tvm.target.current_target(allow_none=False).max_num_threads
+            bx, tx = s[tensor].split(fused, num_thread or max_threads)
+            s[tensor].bind(bx, tvm.thread_axis("blockIdx.x"))
+            s[tensor].bind(tx, tvm.thread_axis("threadIdx.x"))
+            return bx, tx
+
+        output = outs[0]
+        bx, tx = fuse_and_bind(s, output, num_thread=num_thread)
+
+        k = s[dense].op.reduce_axis[0]
+        k, k_unroll = s[dense].split(k, unroll_step)
+        s[dense].unroll(k_unroll)
+
+        if dense.op not in s.outputs:
+            s[dense].compute_at(s[output], tx)
+
+#        bias = s[outs[0]].op.input_tensors[1]
+#        print(tvm.lower(s, [data, weight, bias, outs[0]], simple_mode=True))
+
+    def traverse(OP):
+        # inline all one-to-one-mapping operators except the last stage (output)
+        if tag.is_broadcast(OP.tag):
+            if OP not in s.outputs:
+                s[OP].compute_inline()
+            for tensor in OP.input_tensors:
+                if tensor.op.input_tensors:
+                    traverse(tensor.op)
+        # schedule dense
+        elif OP.tag == 'dense':
+            dense = OP.output(0)
+            _schedule(dense)
+        else:
+            raise RuntimeError("Unsupported operator: %s" % OP.tag)
+
+    traverse(outs[0].op)
+    return s
diff --git a/topi/python/topi/mali/depthwise_conv2d.py b/topi/python/topi/mali/depthwise_conv2d.py
new file mode 100644
index 000000000000..46ce7f747def
--- /dev/null
+++ b/topi/python/topi/mali/depthwise_conv2d.py
@@ -0,0 +1,106 @@
+# pylint: disable=invalid-name,unused-variable,unused-argument
+"""depthwise_conv2d schedule on ARM Mali GPU"""
+
+from __future__ import absolute_import as _abs
+import tvm
+
+from .. import generic
+from .. import util
+from .. import tag
+
+@generic.schedule_depthwise_conv2d_nchw.register(["mali"])
+def schedule_depthwise_conv2d_nchw(outs):
+    """Schedule for depthwise_conv2d nchw forward.
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+        The computation graph description of depthwise_conv2d
+        in the format of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+        The computation schedule for depthwise_conv2d nchw.
+    """
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    s = tvm.create_schedule([x.op for x in outs])
+    def _schedule(pad_data, kernel, conv):
+        raw_data = s[pad_data].op.input_tensors[0]
+
+        if conv.op not in s.outputs:  # has bias or relu
+            output = outs[0]
+        else:                         # no bias or relu
+            output = conv
+
+        def tile_and_bind3d(tensor, z, y, x, z_factor=2, y_factor=None, x_factor=None):
+            """ tile and bind 3d """
+            y_factor = y_factor or z_factor
+            x_factor = x_factor or y_factor
+            zo, zi = s[tensor].split(z, z_factor)
+            yo, yi = s[tensor].split(y, y_factor)
+            xo, xi = s[tensor].split(x, x_factor)
+            s[tensor].bind(zo, tvm.thread_axis("blockIdx.z"))
+            s[tensor].bind(zi, tvm.thread_axis("threadIdx.z"))
+            s[tensor].bind(yo, tvm.thread_axis("blockIdx.y"))
+            s[tensor].bind(yi, tvm.thread_axis("threadIdx.y"))
+            s[tensor].bind(xo, tvm.thread_axis("blockIdx.x"))
+            s[tensor].bind(xi, tvm.thread_axis("threadIdx.x"))
+            return zo, zi, yo, yi, xo, xi
+
+        # set tunable parameters
+        VH = 1
+        VW = 1
+        num_thread = 4
+        while util.get_const_int(conv.shape[3]) % (VW * 2) == 0 and VW * 2 <= 4:
+            VW = VW * 2
+        while util.get_const_int(conv.shape[2]) % (VH * 2) == 0 and VH * 2 <= 2:
+            VH = VH * 2
+        if raw_data.dtype == 'float16':
+            if util.get_const_int(conv.shape[3]) % (VW * 2) == 0:
+                VW *= 2
+                num_thread *= 2
+            else:
+                num_thread *= 2
+
+        # schedule padding
+        _, c, y, x = s[pad_data].op.axis
+        tile_and_bind3d(pad_data, c, y, x, num_thread, 1, 1)
+
+        # schedule conv
+        di, dj = s[conv].op.reduce_axis
+        s[conv].unroll(di)
+        s[conv].unroll(dj)
+
+        _, c, y, x = s[output].op.axis
+        y, x, yi, xi = s[output].tile(y, x, VH, VW)
+        s[output].unroll(yi)
+        s[output].vectorize(xi)
+
+        _, _, _, _, _, ji = tile_and_bind3d(output, c, y, x, num_thread, 1, 1)
+
+        if conv.op not in s.outputs:
+            _, c, y, x = s[conv].op.axis
+            y, x, yi, xi = s[conv].tile(y, x, VH, VW)
+            s[conv].unroll(yi)
+            s[conv].vectorize(xi)
+            s[conv].compute_at(s[output], ji)
+
+    def traverse(op):
+        # inline all one-to-one-mapping operators except the last stage (output)
+        if tag.is_broadcast(op.tag):
+            if op not in s.outputs:
+                s[op].compute_inline()
+            for tensor in op.input_tensors:
+                if tensor.op.input_tensors:
+                    traverse(tensor.op)
+
+        # schedule depthwise_conv2d
+        if op.tag == 'depthwise_conv2d_nchw':
+            pad_data = op.input_tensors[0]
+            kernel = op.input_tensors[1]
+            conv = op.output(0)
+            _schedule(pad_data, kernel, conv)
+
+    traverse(outs[0].op)
+    return s

From dd17d5d2e34a21f9601fedf53c85a013c018ae3c Mon Sep 17 00:00:00 2001
From: Xingjian Shi <xshiab@ust.hk>
Date: Tue, 16 Jan 2018 10:30:14 -0800
Subject: [PATCH 087/949] fix (#788)

---
 topi/tests/python/test_topi_reduce.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/topi/tests/python/test_topi_reduce.py b/topi/tests/python/test_topi_reduce.py
index c8d95df255c6..1e40770f5b10 100644
--- a/topi/tests/python/test_topi_reduce.py
+++ b/topi/tests/python/test_topi_reduce.py
@@ -88,8 +88,8 @@ def check_device(device):
                 np.testing.assert_allclose(out_tvm_val, in_npy_map.max(axis=axis), 1E-3, 1E-3)
             elif type == "argmin":
                 np.testing.assert_allclose(out_tvm_val, in_npy_map.min(axis=axis), 1E-3, 1E-3)
-
-        np.testing.assert_allclose(out_tvm.asnumpy(), out_npy, 1E-3, 1E-3)
+        else:
+            np.testing.assert_allclose(out_tvm.asnumpy(), out_npy, 1E-3, 1E-3)
     for device in ["cuda", "opencl", "metal", "llvm", "rocm"]:
         check_device(device)
 

From c44526daa24f778c8110a64b7b6f1db15de237b8 Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Wed, 17 Jan 2018 03:43:43 +0800
Subject: [PATCH 088/949] fix mali topi for python3 (#789)

---
 topi/python/topi/mali/conv2d.py | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/topi/python/topi/mali/conv2d.py b/topi/python/topi/mali/conv2d.py
index 940d64668ac4..ff67e0503f4f 100644
--- a/topi/python/topi/mali/conv2d.py
+++ b/topi/python/topi/mali/conv2d.py
@@ -256,7 +256,7 @@ def _schedule_direct_conv2d(s, op):
         num_thread = 32
 
     if data.dtype == 'float16' and (util.get_const_int(conv.shape[1]) == 4 or output_height == 28):
-        num_thread /= 2
+        num_thread //= 2
 
     # schedule padding
     if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
@@ -319,17 +319,17 @@ def _decl_im2col(data, kernel, stride, padding, layout='NCHW', out_dtype='float3
 
     ALIGN = 16
     def upround(x, align):
-        return (x + align - 1) / align * align
+        return (x + align - 1) // align * align
 
     # A [CO, CI * KH * KW]
     reduce_len = upround(CI * KH * KW, ALIGN)
     A = tvm.compute((upround(CO, ALIGN), reduce_len), lambda i, j:
-                    kernel[i][j / KW / KH][j / KW % KH][j % KW], name='A')
+                    kernel[i][j // KW // KH][j // KW % KH][j % KW], name='A')
 
     # B [CI * KH * KW, N * OH * OW]
     B = tvm.compute((reduce_len, upround(N * OH * OW, ALIGN)), lambda i, j:\
             tvm.select(tvm.all(i < CI * KH * KW, j < N * OH * OW),
-                       data_pad[j / (OH*OW)][i / (KH*KW)][j / OW % OH*HSTR + i / KW % KH]
+                       data_pad[j // (OH*OW)][i // (KH*KW)][j // OW % OH*HSTR + i // KW % KH]
                        [j % OW*WSTR + i % KW],
                        tvm.const(0, data_pad.dtype)), name='B')
 
@@ -400,7 +400,7 @@ def _schedule_im2col_conv2d(s, op):
         last_work = util.get_const_int(C.shape[1])
         if last_work % (bnb * num_thread2) != 0:
             num_thread1 = num_thread * 2
-            num_thread2 = num_thread / 2
+            num_thread2 = num_thread // 2
 
     # schedule padding
     if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:

From 519ea5a2dc8ca0747770bbd4e0a99376db56d031 Mon Sep 17 00:00:00 2001
From: masahi <masahi129@gmail.com>
Date: Wed, 17 Jan 2018 04:48:00 +0900
Subject: [PATCH 089/949] [TOPI] Basic x86 schedules (#775)

* add basic x86 schedules

* parallelize & vectorize batchnorm + relu

* fuse conv into bn + relu

* move rc loop to outer

* add nhwc conv

* change weight layout to hwcf

* conv + bn + relu fusion for nhwc conv

* fix conv_nhwc schedule when no fusion

* clean up default parallel schedules

* simplify elemwise parallel

* fix elemwise parallel for batch == 1

* update nhwc conv test

* fix and add comment

* fix lint

* remove redundant import

* remove default multithreading for some ops

* remove default multithreading for global pool
---
 topi/python/topi/generic/nn.py                | 18 +++++
 topi/python/topi/nn/conv2d.py                 | 51 ++++++++++++++
 topi/python/topi/testing/__init__.py          |  1 +
 .../python/topi/testing/conv2d_nhwc_python.py | 67 ++++++++++++++++++
 topi/python/topi/x86/__init__.py              |  4 +-
 topi/python/topi/x86/conv2d.py                | 68 ++++++++++++++++++-
 topi/python/topi/x86/injective.py             | 35 ++++++++++
 topi/python/topi/x86/nn.py                    | 56 +++++++++++++++
 topi/tests/python/test_topi_conv2d_nhwc.py    | 59 ++++++++++++++++
 9 files changed, 356 insertions(+), 3 deletions(-)
 create mode 100644 topi/python/topi/testing/conv2d_nhwc_python.py
 create mode 100644 topi/python/topi/x86/injective.py
 create mode 100644 topi/python/topi/x86/nn.py
 create mode 100644 topi/tests/python/test_topi_conv2d_nhwc.py

diff --git a/topi/python/topi/generic/nn.py b/topi/python/topi/generic/nn.py
index 6f641e99f7dd..5c580aad24c4 100644
--- a/topi/python/topi/generic/nn.py
+++ b/topi/python/topi/generic/nn.py
@@ -35,6 +35,24 @@ def schedule_conv2d_nchw(outs):
     return _default_schedule(outs, False)
 
 
+@tvm.target.generic_func
+def schedule_conv2d_nhwc(outs):
+    """Schedule for conv2d_nhwc
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+          The computation graph description of conv2d_nchw
+          in the format of an array of tensors.
+
+    Returns
+    -------
+    sch: Schedule
+        The computation schedule for the op.
+    """
+    return _default_schedule(outs, False)
+
+
 @tvm.target.generic_func
 def schedule_conv2d_transpose_nchw(outs):
     """Schedule for conv2d_transpose_nchw
diff --git a/topi/python/topi/nn/conv2d.py b/topi/python/topi/nn/conv2d.py
index 11866aedc101..3bd910e29974 100644
--- a/topi/python/topi/nn/conv2d.py
+++ b/topi/python/topi/nn/conv2d.py
@@ -337,6 +337,57 @@ def conv2d_hwcn(Input, Filter, stride, padding, out_dtype='float32'):
         name="Conv2dOutput", tag="conv2d_hwcn")
     return Output
 
+
+def conv2d_nhwc(Input, Filter, stride, padding, out_dtype='float32'):
+    """Convolution operator in NHWC layout.
+
+    Parameters
+    ----------
+    Input : tvm.Tensor
+        4-D with shape [batch, in_height, in_width, in_channel]
+
+    Filter : tvm.Tensor
+        4-D with shape [filter_height, filter_width, in_channel, num_filter]
+
+    stride : int or a list/tuple of two ints
+        Stride size, or [stride_height, stride_width]
+
+    padding : int or str
+        Padding size, or ['VALID', 'SAME']
+
+    Returns
+    -------
+    output : tvm.Tensor
+        4-D with shape [batch, out_height,  out_width, out_channel]
+    """
+    assert isinstance(stride, int) or len(stride) == 2
+    batch, in_height, in_width, in_channel = Input.shape
+    kernel_h, kernel_w, channel, num_filter = Filter.shape
+    if isinstance(stride, int):
+        stride_h = stride_w = stride
+    else:
+        stride_h, stride_w = stride
+
+    pad_top, pad_left, pad_down, pad_right = get_pad_tuple(
+        padding, (kernel_h, kernel_w))
+    # compute the output shape
+    out_channel = num_filter
+    out_height = simplify((in_height - kernel_h + pad_top + pad_down) // stride_h + 1)
+    out_width = simplify((in_width - kernel_w + pad_left + pad_right) // stride_w + 1)
+    pad_before = [0, pad_top, pad_left, 0]
+    pad_after = [0, pad_down, pad_right, 0]
+    PaddedInput = pad(Input, pad_before, pad_after, name="PaddedInput")
+    rc = tvm.reduce_axis((0, in_channel), name='rc')
+    ry = tvm.reduce_axis((0, kernel_h), name='ry')
+    rx = tvm.reduce_axis((0, kernel_w), name='rx')
+    Output = tvm.compute(
+        (batch, out_height, out_width, out_channel),
+        lambda nn, yy, xx, ff: tvm.sum(
+            PaddedInput[nn, yy * stride_h + ry, xx * stride_w + rx, rc].astype(out_dtype) *
+            Filter[ry, rx, rc, ff].astype(out_dtype), axis=[ry, rx, rc]),
+        name="Conv2dOutput", tag="conv2d_nhwc")
+    return Output
+
 # map from schedule type to declaration function
 _SCH_TO_DECL_FUNC = {
     SpatialPack: _spatial_pack,
diff --git a/topi/python/topi/testing/__init__.py b/topi/python/topi/testing/__init__.py
index 6a1b361e3097..2a20a1c4f622 100644
--- a/topi/python/topi/testing/__init__.py
+++ b/topi/python/topi/testing/__init__.py
@@ -6,6 +6,7 @@
 
 from .conv2d_hwcn_python import conv2d_hwcn_python
 from .conv2d_nchw_python import conv2d_nchw_python
+from .conv2d_nhwc_python import conv2d_nhwc_python
 from .conv2d_transpose_nchw_python import conv2d_transpose_nchw_python
 from .depthwise_conv2d_python import depthwise_conv2d_python_nchw, depthwise_conv2d_python_nhwc
 from .dilate_python import dilate_python
diff --git a/topi/python/topi/testing/conv2d_nhwc_python.py b/topi/python/topi/testing/conv2d_nhwc_python.py
new file mode 100644
index 000000000000..880088a6f89f
--- /dev/null
+++ b/topi/python/topi/testing/conv2d_nhwc_python.py
@@ -0,0 +1,67 @@
+# pylint: disable=invalid-name, line-too-long, unused-variable, too-many-locals
+"""Convolution in python"""
+import numpy as np
+import scipy.signal
+
+
+def conv2d_nhwc_python(a_np, w_np, stride, padding):
+    """Convolution operator in NHWC layout.
+
+    Parameters
+    ----------
+    a_np : numpy.ndarray
+        4-D with shape [batch, in_height, in_width, in_channel]
+
+    w_np : numpy.ndarray
+        4-D with shape [num_filter, filter_height, filter_width, in_channel]
+
+    stride : int or a list/tuple of two ints
+        Stride size, or [stride_height, stride_width]
+
+    padding : int or str
+        Padding size, or ['VALID', 'SAME']
+
+    Returns
+    -------
+    b_np : np.ndarray
+        4-D with shape [out_height, out_width, out_channel, batch]
+    """
+    batch, in_height, in_width, in_channel = a_np.shape
+    kernel_h, kernel_w, _, num_filter = w_np.shape
+    if isinstance(stride, int):
+        stride_h = stride_w = stride
+    else:
+        stride_h, stride_w = stride
+    if isinstance(padding, int):
+        pad_h = pad_w = padding * 2
+    elif padding == 'VALID':
+        pad_h = 0
+        pad_w = 0
+    else: # 'SAME'
+        pad_h = kernel_h - 1
+        pad_w = kernel_w - 1
+    pad_top = int(np.ceil(float(pad_h) / 2))
+    pad_bottom = pad_h - pad_top
+    pad_left = int(np.ceil(float(pad_w) / 2))
+    pad_right = pad_w - pad_left
+    # compute the output shape
+    out_channel = num_filter
+    out_height = (in_height - kernel_h + pad_h) // stride_h + 1
+    out_width = (in_width - kernel_w + pad_w) // stride_w + 1
+    # change the layout from NHWC to NCHW
+    at = a_np.transpose((0, 3, 1, 2))
+    wt = w_np.transpose((3, 2, 0, 1))
+    bt = np.zeros((batch, out_channel, out_height, out_width))
+    # computation
+    for n in range(batch):
+        for f in range(out_channel):
+            for c in range(in_channel):
+                if pad_h > 0:
+                    apad = np.zeros((in_height + pad_h, in_width + pad_w))
+                    apad[pad_top:-pad_bottom, pad_left:-pad_right] = at[n, c]
+                else:
+                    apad = at[n, c]
+                out = scipy.signal.convolve2d(
+                    apad, np.rot90(np.rot90(wt[f, c])), mode='valid')
+                bt[n, f] += out[::stride, ::stride]
+    return bt.transpose((0, 2, 3, 1))
diff --git a/topi/python/topi/x86/__init__.py b/topi/python/topi/x86/__init__.py
index 6ab37b8c03ac..ef227d035fce 100644
--- a/topi/python/topi/x86/__init__.py
+++ b/topi/python/topi/x86/__init__.py
@@ -2,6 +2,8 @@
 """x86 specific declaration and schedules."""
 from __future__ import absolute_import as _abs
 
-from .conv2d import schedule_conv2d
+from .conv2d import schedule_conv2d, schedule_conv2d_nhwc
 from .binarize_pack import schedule_binarize_pack
 from .binary_dense import schedule_binary_dense
+from .nn import *
+from .injective import *
diff --git a/topi/python/topi/x86/conv2d.py b/topi/python/topi/x86/conv2d.py
index 0c91f8c25c88..cb3571d6a91b 100644
--- a/topi/python/topi/x86/conv2d.py
+++ b/topi/python/topi/x86/conv2d.py
@@ -15,6 +15,12 @@ def traverse(op):
         if tag.is_broadcast(op.tag):
             if op not in s.outputs:
                 s[op].compute_inline()
+            else: # inject custom schedule
+                if len(op.axis) == 4: # schedule bias + bn + relu
+                    n, c, h, w = op.axis
+                    fused = s[op].fuse(n, c)
+                    s[op].parallel(fused)
+                    s[op].vectorize(w)
             for tensor in op.input_tensors:
                 if tensor.op.input_tensors:
                     traverse(tensor.op)
@@ -28,10 +34,68 @@ def traverse(op):
                 data_pad = data
                 data = data_pad.op.input_tensors[0]
 
+            n_pad, c_pad, h_pad, w_pad = data_pad.op.axis
+            pad_fused = s[data_pad].fuse(n_pad, c_pad)
+            s[data_pad].parallel(pad_fused)
             C = conv
             n, c, h, w = C.op.axis
-            s[C].parallel(c)
-            s[C].pragma(n, "parallel_launch_point")
+            rc, ry, rx = C.op.reduce_axis
+            fused = s[C].fuse(n, c)
+            s[C].parallel(fused)
+            wo, wi = s[C].split(w, factor=16)
+            s[C].reorder(fused, rc, h, wo, ry, rx, wi) # move rc to outer loop
+            s[C].unroll(rx)
+            s[C].unroll(ry)
+            s[C].vectorize(wi)
 
     traverse(outs[0].op)
     return s
+
+
+@generic.schedule_conv2d_nhwc.register(["cpu"])
+def schedule_conv2d_nhwc(outs):
+    """Create schedule for tensors"""
+    s = tvm.create_schedule([x.op for x in outs])
+    output_op = outs[0].op
+
+    def traverse(op):
+        """Traverse operators from computation graph"""
+        # inline all one-to-one-mapping operators except the last stage (output)
+        if tag.is_broadcast(op.tag):
+            if op not in s.outputs:
+                s[op].compute_inline()
+            else: # inject custom schedule
+                if len(op.axis) == 4: # schedule bias + bn + relu
+                    n, h, w, c = op.axis
+                    fused = s[op].fuse(n, h, w)
+                    s[op].parallel(fused)
+                    s[op].vectorize(c)
+            for tensor in op.input_tensors:
+                if tensor.op.input_tensors:
+                    traverse(tensor.op)
+
+        if 'conv2d_nhwc' in op.tag:
+            conv = op.output(0)
+            kernel = op.input_tensors[1]
+            data = op.input_tensors[0]
+            data_pad = None
+            if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
+                data_pad = data
+                data = data_pad.op.input_tensors[0]
+
+            n_pad, h_pad, w_pad, c_pad = data_pad.op.axis
+            pad_fused = s[data_pad].fuse(n_pad, h_pad)
+            s[data_pad].parallel(pad_fused)
+            C = conv
+            n, h, w, c = C.op.axis
+            ry, rx, rc = C.op.reduce_axis
+            n_out, h_out, w_out, c_out = output_op.axis
+            s[C].vectorize(c)
+            if op != output_op: # fuse bias + bn + relu into conv
+                s[C].compute_at(s[output_op], c_out)
+            else:
+                fused = s[C].fuse(n, h, w)
+                s[C].parallel(fused)
+
+    traverse(output_op)
+    return s
diff --git a/topi/python/topi/x86/injective.py b/topi/python/topi/x86/injective.py
new file mode 100644
index 000000000000..0970b76142ae
--- /dev/null
+++ b/topi/python/topi/x86/injective.py
@@ -0,0 +1,35 @@
+# pylint: disable=invalid-name
+"""x86 declaration and schedules."""
+from __future__ import absolute_import as _abs
+import tvm
+from .. import generic
+
+@generic.schedule_injective.register(["cpu"])
+def schedule_injective(outs):
+    """X86 schedule for injective op.
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+          The computation graph description of injective in the format
+          of an array of tensors.
+
+    Returns
+    -------
+    sch: Schedule
+        The computation schedule for the op.
+    """
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    x = outs[0]
+    s = tvm.create_schedule([x.op for x in outs])
+    tvm.schedule.AutoInlineInjective(s)
+    if len(s[x].op.axis) == 4:
+        n, c, _, _ = s[x].op.axis
+        fused = s[x].fuse(n, c) # for nhwc layout, fuse n and h
+        s[x].parallel(fused)
+    else:
+        s[x].parallel(s[x].op.axis[0])
+    return s
+
+schedule_elemwise = schedule_injective
+schedule_broadcast = schedule_injective
diff --git a/topi/python/topi/x86/nn.py b/topi/python/topi/x86/nn.py
new file mode 100644
index 000000000000..49aa382589d1
--- /dev/null
+++ b/topi/python/topi/x86/nn.py
@@ -0,0 +1,56 @@
+"""x86 nn operators"""
+from __future__ import absolute_import as _abs
+import tvm
+from .. import generic
+
+def _default_schedule(outs, auto_inline):
+    """Default schedule for x86."""
+    x = outs[0]
+    s = tvm.create_schedule([x.op for x in outs])
+    if auto_inline:
+        tvm.schedule.AutoInlineInjective(s)
+        s[x].fuse(s[x].op.axis)
+        return s
+    if len(s[x].op.axis) == 4:
+        n, c, _, _ = s[x].op.axis
+        fused = s[x].fuse(n, c) # for nhwc layout, fuse n and h
+        s[x].parallel(fused)
+    else:
+        s[x].parallel(s[x].op.axis[0])
+    return s
+
+
+@generic.schedule_softmax.register(["cpu"])
+def schedule_softmax(outs):
+    """Schedule for softmax
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+          The computation graph description of softmax
+          in the format of an array of tensors.
+
+    Returns
+    -------
+    sch: Schedule
+        The computation schedule for the op.
+    """
+    return _default_schedule(outs, False)
+
+
+@generic.schedule_pool.register(["cpu"])
+def schedule_pool(outs):
+    """Schedule for pool
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+          The computation graph description of pool
+          in the format of an array of tensors.
+
+    Returns
+    -------
+    sch: Schedule
+        The computation schedule for the op.
+    """
+    return _default_schedule(outs, False)
diff --git a/topi/tests/python/test_topi_conv2d_nhwc.py b/topi/tests/python/test_topi_conv2d_nhwc.py
new file mode 100644
index 000000000000..7fc5b841908f
--- /dev/null
+++ b/topi/tests/python/test_topi_conv2d_nhwc.py
@@ -0,0 +1,59 @@
+"""Example code to do convolution."""
+import os
+import numpy as np
+import tvm
+import topi
+from tvm.contrib.pickle_memoize import memoize
+from topi.util import get_const_tuple
+
+
+def verify_conv2d_nhwc(batch, in_channel, in_size, num_filter, kernel, stride, padding):
+    in_height = in_width = in_size
+
+    A = tvm.placeholder((batch, in_height, in_width, in_channel), name='A')
+    W = tvm.placeholder((kernel, kernel, in_channel, num_filter), name='W')
+    B = topi.nn.conv2d_nhwc(A, W, stride, padding)
+
+    a_shape = get_const_tuple(A.shape)
+    w_shape = get_const_tuple(W.shape)
+    dtype = A.dtype
+
+    @memoize("topi.tests.test_topi_conv2d_nhwc.verify_nhwc")
+    def get_ref_data():
+        a_np = np.random.uniform(size=a_shape).astype(dtype)
+        w_np = np.random.uniform(size=w_shape).astype(dtype)
+        b_np = topi.testing.conv2d_nhwc_python(a_np, w_np, stride, padding)
+        return a_np, w_np, b_np
+    a_np, w_np, b_np = get_ref_data()
+
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s = topi.generic.schedule_conv2d_nhwc([B])
+        ctx = tvm.context(device, 0)
+        a = tvm.nd.array(a_np, ctx)
+        w = tvm.nd.array(w_np, ctx)
+        b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
+        func = tvm.build(s, [A, W, B], device)
+        func(a, w, b)
+        np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+    for device in ['llvm']:
+        check_device(device)
+
+
+def test_conv2d_nhwc():
+    verify_conv2d_nhwc(1, 256, 32, 256, 3, 1, "SAME")
+    verify_conv2d_nhwc(4, 128, 16, 128, 5, 2, "SAME")
+    verify_conv2d_nhwc(4, 128, 16, 256, 5, 2, "SAME")
+    verify_conv2d_nhwc(1, 256, 32, 256, 3, 1, "VALID")
+    verify_conv2d_nhwc(1, 256, 32, 256, 3, 1, "VALID")
+    verify_conv2d_nhwc(4, 128, 16, 128, 5, 2, "VALID")
+    verify_conv2d_nhwc(4, 128, 16, 256, 5, 2, "VALID")
+
+
+if __name__ == "__main__":
+    test_conv2d_nhwc()

From 553a99f5621b6e667b51ef5f83969d159b62be15 Mon Sep 17 00:00:00 2001
From: solin319 <lipengfei19890603@126.com>
Date: Fri, 19 Jan 2018 10:47:18 +0800
Subject: [PATCH 090/949] fix the description of create_shared (#793)

The type of parameter options should be a str list.
---
 python/tvm/contrib/cc.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/python/tvm/contrib/cc.py b/python/tvm/contrib/cc.py
index b941140bfbf0..5764121c59ed 100644
--- a/python/tvm/contrib/cc.py
+++ b/python/tvm/contrib/cc.py
@@ -22,8 +22,8 @@ def create_shared(output,
     objects : list
         List of object files.
 
-    options : str
-        The additional options.
+    options : list
+        The list of additional options string.
 
     cc : str, optional
         The compile string.

From fe33059e41ec7b721ffd3d8c3d00e4da3e87ad45 Mon Sep 17 00:00:00 2001
From: Jammy Zhou <jammy.zhou@gmail.com>
Date: Fri, 19 Jan 2018 17:40:05 +0000
Subject: [PATCH 091/949] Additional mali target support (#794)

* Add Mali target support to tvm.target.create

* Add Mali target support in codegen
---
 include/tvm/build_module.h  |  3 +++
 python/tvm/target.py        |  3 +++
 src/codegen/build_module.cc | 12 +++++++++++-
 3 files changed, 17 insertions(+), 1 deletion(-)

diff --git a/include/tvm/build_module.h b/include/tvm/build_module.h
index 75062e819748..ae7ca3ca681d 100644
--- a/include/tvm/build_module.h
+++ b/include/tvm/build_module.h
@@ -73,6 +73,9 @@ EXPORT Target metal();
 /*! \return A target for rasp */
 EXPORT Target rasp();
 
+/*! \return A target for Mali */
+EXPORT Target mali();
+
 /*! \return A target for stackvm */
 EXPORT Target stackvm();
 
diff --git a/python/tvm/target.py b/python/tvm/target.py
index 3437e70e0a6d..4584c1aa4d23 100644
--- a/python/tvm/target.py
+++ b/python/tvm/target.py
@@ -81,6 +81,7 @@ class Target(object):
     - :any:`tvm.target.rasp` create raspberry pi target
     - :any:`tvm.target.cuda` create CUDA target
     - :any:`tvm.target.rocm` create ROCM target
+    - :any:`tvm.target.mali` create Mali target
     """
     current = None
 
@@ -306,6 +307,8 @@ def create(target_str):
             device_name = item.split("=")[1]
     if device_name == "rasp":
         return rasp(arr[1:])
+    if device_name == "mali":
+        return mali(arr[1:])
     return Target(arr[0], arr[1:])
 
 
diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc
index 2c419d43da08..df71a4a41bec 100644
--- a/src/codegen/build_module.cc
+++ b/src/codegen/build_module.cc
@@ -70,7 +70,8 @@ Target Target::create(const std::string& target_str) {
 
   auto result = device_name == "rasp" ?
     target::rasp() :
-    TargetFromName(target_name);
+    (device_name == "mali" ? target::mali() :
+    TargetFromName(target_name));
 
   std::string item;
   while (ss >> item) {
@@ -116,6 +117,15 @@ Target rasp() {
   return Target("llvm", kDLCPU, 512, 1, keys, options);
 }
 
+Target mali() {
+  std::unordered_set<std::string> keys({ "rocm", "gpu" });
+  std::vector<std::string> options({
+    "-device=mali"
+  });
+  return Target("opencl", kDLOpenCL, 256, 1, keys, options);
+}
+
+
 Target stackvm() {
   std::unordered_set<std::string> keys({ "stackvm", "cpu" });
   std::vector<std::string> options;

From 867495b4e11be4a88ba797f30584dab495603168 Mon Sep 17 00:00:00 2001
From: masahi <masahi129@gmail.com>
Date: Sat, 20 Jan 2018 03:31:51 +0900
Subject: [PATCH 092/949]  simplify expr in get_const_tuple (#795)

* fix upsampling output shape

* simplify expr in get_const_tuple
---
 topi/python/topi/nn/upsampling.py | 5 +++--
 topi/python/topi/util.py          | 5 ++---
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/topi/python/topi/nn/upsampling.py b/topi/python/topi/nn/upsampling.py
index e1234741e286..df77bbdb23c0 100644
--- a/topi/python/topi/nn/upsampling.py
+++ b/topi/python/topi/nn/upsampling.py
@@ -1,6 +1,7 @@
 """TVM operator upsampling compute."""
 from __future__ import absolute_import
 import tvm
+from .. import util
 
 
 def upsampling(data, scale):
@@ -21,8 +22,8 @@ def upsampling(data, scale):
         4-D with shape [batch, channel, in_height*scale, in_width*scale]
     """
     batch, channel, height, width = data.shape
-    out_height = height * scale
-    out_width = width * scale
+    out_height = util.simplify(height * scale)
+    out_width = util.simplify(width * scale)
 
     return tvm.compute((batch, channel, out_height, out_width), \
                         lambda n, c, h, w: data[n, c, h/scale, w/scale])
diff --git a/topi/python/topi/util.py b/topi/python/topi/util.py
index 246d283cd2ed..00c8b9d42e2a 100644
--- a/topi/python/topi/util.py
+++ b/topi/python/topi/util.py
@@ -59,9 +59,8 @@ def get_const_tuple(in_tuple):
     """
     out_tuple = ()
     for elem in in_tuple:
-        if not isinstance(elem, (tvm.expr.IntImm, tvm.expr.UIntImm)):
-            raise ValueError("Element of input tuple should be const int")
-        out_tuple = out_tuple + (elem.value, )
+        value = get_const_int(elem)
+        out_tuple = out_tuple + (value, )
     return out_tuple
 
 

From bc5f1922ab96c215263dbd7ebd3fb00deadb6738 Mon Sep 17 00:00:00 2001
From: xqdan <danxiaoqiang@126.com>
Date: Sat, 20 Jan 2018 11:57:39 +0800
Subject: [PATCH 093/949] Support dump ir for each pass (#693) (#791)

* Support dump ir for each pass(#693)

* expose DumpIR

* fix comments

* fix comments
---
 python/tvm/build_module.py                | 89 ++++++++++++++++++++++-
 tests/python/unittest/test_pass_unroll.py | 19 ++++-
 2 files changed, 105 insertions(+), 3 deletions(-)
 mode change 100644 => 100755 python/tvm/build_module.py

diff --git a/python/tvm/build_module.py b/python/tvm/build_module.py
old mode 100644
new mode 100755
index fe6b01bb4d8c..8b52b11d86e7
--- a/python/tvm/build_module.py
+++ b/python/tvm/build_module.py
@@ -5,18 +5,96 @@
 """
 from __future__ import absolute_import as _abs
 import warnings
+import types
 
 from . import api
 from . import tensor
 from . import schedule
 from . import expr
 from . import ir_pass
+from . import stmt as _stmt
 from . import container
 from . import module
 from . import codegen
 from . import ndarray
 from . import target as _target
 
+class DumpIR(object):
+    """Dump IR for each pass.
+       With it, you can dump ir just like gcc/llvm.
+
+       How to use:
+       -----------
+       .. code-block:: python
+
+          with tvm.build_config(dump_pass_ir=True)
+              run()
+
+    """
+    scope_level = 0
+    def __init__(self):
+        self._pass_id = 0
+        self._recover_list = []
+
+    def decorate(self, func):
+        ''' decorate the pass function'''
+        def dump(*args, **kwargs):
+            '''dump function'''
+            retv = func(*args, **kwargs)
+            if not isinstance(retv, (_stmt.Stmt, container.LoweredFunc, container.Array)):
+                return retv
+            pname = str(self._pass_id) + "_" + func.func_name + "_ir.cc"
+            with open(pname, "a") as f:
+                out = retv.body if isinstance(retv, container.LoweredFunc) else retv
+                f.write(str(out))
+                if isinstance(retv, container.Array):
+                    for x in retv:
+                        out = x.body if isinstance(x, container.LoweredFunc) else x
+                        f.write("---------%s\n%s\n-----------\n"%(x.name, str(out)))
+                self._pass_id += 1
+            return retv
+        return dump
+
+    def decorate_irpass(self):
+        '''decorate ir_pass and ScheduleOps'''
+        self._old_sgpass = schedule.ScheduleOps
+        schedule.ScheduleOps = self.decorate(schedule.ScheduleOps)
+        vset = vars(ir_pass)
+        k = v = 0
+        def recover():
+            vset[k] = v
+        for k, v in vset.items():
+            self._recover_list.append(recover)
+            vset[k] = self.decorate(v) if isinstance(v, types.FunctionType) else v
+
+    def decorate_custompass(self):
+        ''' decorate add_lower_pass pass in BuildConfig'''
+        cfg = BuildConfig.current
+        self._old_custom_pass = cfg.add_lower_pass
+        custom_pass = cfg.add_lower_pass if cfg.add_lower_pass else []
+        pass_list = [(x[0], self.decorate(x[1])) for x in custom_pass]
+        BuildConfig.current.add_lower_pass = pass_list
+
+    def enter(self):
+        '''only decorate outermost nest'''
+        if DumpIR.scope_level > 0:
+            return
+        self.decorate_irpass()
+        self.decorate_custompass()
+        self._pass_id = 0
+        DumpIR.scope_level += 1
+
+    def exit(self):
+        '''recover outermost nest'''
+        if DumpIR.scope_level > 1:
+            return
+        # recover decorated functions
+        for f in self._recover_list:
+            f()
+        schedule.ScheduleOps = self._old_sgpass
+        BuildConfig.current.add_lower_pass = self._old_custom_pass
+        DumpIR.scope_level -= 1
+
 class BuildConfig(object):
     """Configuration scope to set a build config option.
 
@@ -37,10 +115,12 @@ class BuildConfig(object):
         "data_alignment": -1,
         "restricted_func": True,
         "double_buffer_split_loop": 1,
-        "add_lower_pass": None
+        "add_lower_pass": None,
+        "dump_pass_ir": False
     }
     def __init__(self, **kwargs):
         self._old_scope = None
+        self._dump_ir = DumpIR()
         for k, _ in kwargs.items():
             if k not in BuildConfig.defaults:
                 raise ValueError(
@@ -59,10 +139,14 @@ def __enter__(self):
         attr.update(self._attr)
         self._attr = attr
         BuildConfig.current = self
+        if self.dump_pass_ir is True:
+            self._dump_ir.enter()
         return self
 
     def __exit__(self, ptype, value, trace):
         assert self._old_scope
+        if self.dump_pass_ir is True:
+            self._dump_ir.exit()
         BuildConfig.current = self._old_scope
 
 
@@ -115,6 +199,8 @@ def build_config(**kwargs):
         phase contains an integer on which optimization pass we apply the pass.
         Additional lowering passes to be applied before make_api.
 
+    dump_pass_ir: dump ir of each pass into file idx_passname_ir.cc, default=False
+
     Returns
     -------
     config: BuildConfig
@@ -247,7 +333,6 @@ def lower(sch,
         return stmt
     return ir_pass.MakeAPI(stmt, name, arg_list, 0, cfg.restricted_func)
 
-
 def build(sch,
           args=None,
           target=None,
diff --git a/tests/python/unittest/test_pass_unroll.py b/tests/python/unittest/test_pass_unroll.py
index 9e52a455e7da..b158113e6216 100644
--- a/tests/python/unittest/test_pass_unroll.py
+++ b/tests/python/unittest/test_pass_unroll.py
@@ -1,4 +1,5 @@
 import tvm
+import os
 
 def test_unroll_loop():
     dtype = 'int64'
@@ -24,4 +25,20 @@ def test_unroll_loop():
 
 
 if __name__ == "__main__":
-    test_unroll_loop()
+    with tvm.build_config(dump_pass_ir=True):
+        test_unroll_loop()
+
+    def end_with(*suffix):
+        ends = suffix
+        def run(s):
+            f = map(s.endswith, ends)
+            if True in f: return s
+        return run
+
+    file_list = os.listdir('./')
+    cc_file = end_with('.cc')
+    cc_file = filter(cc_file, file_list)
+    assert len(cc_file) == 3
+    for i in cc_file:
+        os.remove(i)
+    

From 700949676ef3cdcc7b830da8b27274e90a4564d5 Mon Sep 17 00:00:00 2001
From: Zhixun Tan <phisiart@gmail.com>
Date: Sat, 20 Jan 2018 16:51:34 -0500
Subject: [PATCH 094/949] [WIP] WebGL Backend (#672)

Basic WebGL Backend
---
 CMakeLists.txt                                |  18 +-
 Jenkinsfile                                   |   2 +
 Makefile                                      |  17 +-
 include/tvm/runtime/c_runtime_api.h           |   4 +-
 include/tvm/runtime/device_api.h              |  11 +-
 include/tvm/schedule.h                        |   5 +
 python/tvm/__init__.py                        |   2 +-
 python/tvm/_ffi/runtime_ctypes.py             |   2 +
 python/tvm/contrib/rpc.py                     |   4 +
 python/tvm/ndarray.py                         |  15 +
 python/tvm/schedule.py                        |   7 +
 python/tvm/target.py                          |   4 +-
 src/api/api_lang.cc                           |   5 +
 src/codegen/build_opengl.cc                   |  35 ++
 src/codegen/codegen_c.h                       |   2 +-
 src/codegen/codegen_opengl.cc                 | 264 +++++++++
 src/codegen/codegen_opengl.h                  |  48 ++
 src/codegen/verilog/vpi_device_api.cc         |   5 +-
 src/runtime/c_runtime_api.cc                  |   6 +-
 src/runtime/cpu_device_api.cc                 |   9 +-
 src/runtime/cuda/cuda_device_api.cc           |   7 +-
 src/runtime/metal/metal_common.h              |   5 +-
 src/runtime/metal/metal_device_api.mm         |   4 +-
 src/runtime/module.cc                         |   2 +
 src/runtime/opencl/opencl_common.h            |   5 +-
 src/runtime/opencl/opencl_device_api.cc       |   2 +-
 src/runtime/opengl/opengl_common.h            | 495 ++++++++++++++++
 src/runtime/opengl/opengl_device_api.cc       | 556 ++++++++++++++++++
 src/runtime/opengl/opengl_module.cc           | 284 +++++++++
 src/runtime/opengl/opengl_module.h            | 148 +++++
 src/runtime/rocm/rocm_device_api.cc           |   7 +-
 src/runtime/rpc/rpc_device_api.cc             |   7 +-
 src/runtime/rpc/rpc_session.cc                |   6 +-
 src/runtime/workspace_pool.cc                 |  25 +-
 src/schedule/schedule_lang.cc                 |  39 ++
 tests/ci_build/Dockerfile.cpu                 |   3 +
 tests/ci_build/Dockerfile.gpu                 |   3 +
 .../ci_build/install/ubuntu_install_opengl.sh |   4 +
 tests/python/unittest/test_runtime_ndarray.py |   6 +-
 tests/scripts/task_python_integration.sh      |   2 +
 tests/webgl/README.md                         |   7 +
 tests/webgl/test_local_gemm.py                |  41 ++
 tests/webgl/test_local_save_load.py           |  35 ++
 tests/webgl/test_remote_save_load.py          |  78 +++
 web/example_rpc.html                          |   4 +
 web/tvm_runtime.js                            |   6 +-
 web/web_runtime.cc                            |   2 +
 47 files changed, 2203 insertions(+), 45 deletions(-)
 create mode 100644 src/codegen/build_opengl.cc
 create mode 100644 src/codegen/codegen_opengl.cc
 create mode 100644 src/codegen/codegen_opengl.h
 create mode 100644 src/runtime/opengl/opengl_common.h
 create mode 100644 src/runtime/opengl/opengl_device_api.cc
 create mode 100644 src/runtime/opengl/opengl_module.cc
 create mode 100644 src/runtime/opengl/opengl_module.h
 create mode 100644 tests/ci_build/install/ubuntu_install_opengl.sh
 create mode 100644 tests/webgl/README.md
 create mode 100644 tests/webgl/test_local_gemm.py
 create mode 100644 tests/webgl/test_local_save_load.py
 create mode 100644 tests/webgl/test_remote_save_load.py

diff --git a/CMakeLists.txt b/CMakeLists.txt
index f42705ae7fda..fd381b9a12e4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -22,6 +22,7 @@ endif()
 
 tvm_option(USE_CUDA "Build with CUDA" OFF)
 tvm_option(USE_OPENCL "Build with OpenCL" OFF)
+tvm_option(USE_OPENGL "Build with OpenGL" OFF)
 tvm_option(USE_METAL "Build with Metal" OFF)
 tvm_option(USE_RPC "Build with RPC" ON)
 tvm_option(USE_GRAPH_RUNTIME "Build with tiny graph runtime" ON)
@@ -61,8 +62,8 @@ if(MSVC)
 else(MSVC)
   include(CheckCXXCompilerFlag)
   check_cxx_compiler_flag("-std=c++11"    SUPPORT_CXX11)
-  set(CMAKE_C_FLAGS "-O3 -Wall -std=c++11 -fPIC")
-  set(CMAKE_CXX_FLAGS ${CMAKE_C_FLAGS})
+  set(CMAKE_C_FLAGS "-O3 -Wall -fPIC")
+  set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -std=c++11")
 endif(MSVC)
 
 # add source group
@@ -87,6 +88,7 @@ file(GLOB RUNTIME_SRCS src/runtime/*.cc)
 file(GLOB COMPILER_LLVM_SRCS src/codegen/llvm/*.cc)
 file(GLOB RUNTIME_CUDA_SRCS src/runtime/cuda/*.cc)
 file(GLOB RUNTIME_OPENCL_SRCS src/runtime/opencl/*.cc)
+file(GLOB RUNTIME_OPENGL_SRCS src/runtime/opengl/*.cc)
 file(GLOB RUNTIME_METAL_SRCS src/runtime/metal/*.mm)
 file(GLOB RUNTIME_RPC_SRCS src/runtime/rpc/*.cc)
 file(GLOB RUNTIME_GRAPH_SRCS src/runtime/graph/*.cc)
@@ -135,6 +137,18 @@ else(USE_OPENCL)
   add_definitions(-DTVM_OPENCL_RUNTIME=0)
 endif(USE_OPENCL)
 
+if(USE_OPENGL)
+  find_package(OpenGL QUIET REQUIRED)
+  find_package(glfw3 QUIET REQUIRED)
+  message(STATUS "Build with OpenGL support")
+  include_directories(${OPENGL_INCLUDE_DIRS})
+  list(APPEND TVM_RUNTIME_LINKER_LIBS ${OpenGL_LIBRARIES} glfw)
+  list(APPEND RUNTIME_SRCS ${RUNTIME_OPENGL_SRCS})
+  add_definitions(-DTVM_OPENGL_RUNTIME=1)
+else(USE_OPENGL)
+  add_definitions(-DTVM_OPENGL_RUNTIME=0)
+endif(USE_OPENGL)
+
 if(USE_METAL)
   find_package(OpenCL QUIET REQUIRED)
   message(STATUS "Build with Metal support")
diff --git a/Jenkinsfile b/Jenkinsfile
index 793bf11f0d4a..4fc2285f507c 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -88,6 +88,7 @@ stage('Build') {
            echo USE_CUDNN=1 >> config.mk
            echo USE_CUDA=1 >> config.mk
            echo USE_OPENCL=1 >> config.mk
+           echo USE_OPENGL=1 >> config.mk
            echo LLVM_CONFIG=llvm-config-4.0 >> config.mk
            echo USE_RPC=1 >> config.mk
            echo USE_GRAPH_RUNTIME=1 >> config.mk
@@ -120,6 +121,7 @@ stage('Build') {
            echo USE_CUDA=0 >> config.mk
            echo USE_OPENCL=0 >> config.mk
            echo USE_RPC=0 >> config.mk
+           echo USE_OPENGL=1 >> config.mk
            echo LLVM_CONFIG=llvm-config-4.0 >> config.mk
            """
         make('cpu', '-j2')
diff --git a/Makefile b/Makefile
index fef8b2a08051..453415de6634 100644
--- a/Makefile
+++ b/Makefile
@@ -32,8 +32,8 @@ OBJCFLAGS = -fno-objc-arc
 EMCC_FLAGS= -std=c++11 -DDMLC_LOG_STACK_TRACE=0\
 	-Oz -s RESERVED_FUNCTION_POINTERS=2 -s MAIN_MODULE=1 -s NO_EXIT_RUNTIME=1\
 	-s EXTRA_EXPORTED_RUNTIME_METHODS="['cwrap','getValue','setValue','addFunction']"\
+	-s USE_GLFW=3 -s USE_WEBGL2=1 -lglfw\
 	$(INCLUDE_FLAGS)
-
 # llvm configuration
 ifdef LLVM_CONFIG
 	LLVM_VERSION=$(shell $(LLVM_CONFIG) --version| cut -b 1,3)
@@ -54,6 +54,7 @@ METAL_SRC = $(wildcard src/runtime/metal/*.mm)
 CUDA_SRC = $(wildcard src/runtime/cuda/*.cc)
 ROCM_SRC = $(wildcard src/runtime/rocm/*.cc)
 OPENCL_SRC = $(wildcard src/runtime/opencl/*.cc)
+OPENGL_SRC = $(wildcard src/runtime/opengl/*.cc)
 RPC_SRC = $(wildcard src/runtime/rpc/*.cc)
 GRAPH_SRC = $(wildcard src/runtime/graph/*.cc)
 RUNTIME_SRC = $(wildcard src/runtime/*.cc)
@@ -65,6 +66,7 @@ METAL_OBJ = $(patsubst src/%.mm, build/%.o, $(METAL_SRC))
 CUDA_OBJ = $(patsubst src/%.cc, build/%.o, $(CUDA_SRC))
 ROCM_OBJ = $(patsubst src/%.cc, build/%.o, $(ROCM_SRC))
 OPENCL_OBJ = $(patsubst src/%.cc, build/%.o, $(OPENCL_SRC))
+OPENGL_OBJ = $(patsubst src/%.cc, build/%.o, $(OPENGL_SRC))
 RPC_OBJ = $(patsubst src/%.cc, build/%.o, $(RPC_SRC))
 GRAPH_OBJ = $(patsubst src/%.cc, build/%.o, $(GRAPH_SRC))
 CC_OBJ = $(patsubst src/%.cc, build/%.o, $(CC_SRC)) $(LLVM_OBJ)
@@ -119,6 +121,19 @@ else
 	CFLAGS += -DTVM_OPENCL_RUNTIME=0
 endif
 
+ifeq ($(USE_OPENGL), 1)
+	CFLAGS += -DTVM_OPENGL_RUNTIME=1
+	EMCC_FLAGS += -DTVM_OPENGL_RUNTIME=1
+	ifeq ($(UNAME_S), Darwin)
+		FRAMEWORKS += -framework OpenGL
+	else
+		LDFLAGS += -lGL -lglfw
+	endif
+	RUNTIME_DEP += $(OPENGL_OBJ)
+else
+	CFLAGS += -DTVM_OPENGL_RUNTIME=0
+endif
+
 ifeq ($(USE_METAL), 1)
 	CFLAGS += -DTVM_METAL_RUNTIME=1
 	LDFLAGS += -lobjc
diff --git a/include/tvm/runtime/c_runtime_api.h b/include/tvm/runtime/c_runtime_api.h
index 91175f671a56..6d680330a659 100644
--- a/include/tvm/runtime/c_runtime_api.h
+++ b/include/tvm/runtime/c_runtime_api.h
@@ -55,9 +55,11 @@ typedef int64_t tvm_index_t;
 
 /*! \brief Extension device types in TVM */
 typedef enum {
+  kOpenGL = 11,
+
   // Extension DRAM type, used for quickly test extension device
   // The device api can differ depending on the xpu driver registered.
-  kExtDev = 12
+  kExtDev = 12,
   // AddExtraTVMType which is not in DLPack here
 } TVMDeviceExtType;
 
diff --git a/include/tvm/runtime/device_api.h b/include/tvm/runtime/device_api.h
index 318a5363726f..9ba08fb86825 100644
--- a/include/tvm/runtime/device_api.h
+++ b/include/tvm/runtime/device_api.h
@@ -55,11 +55,16 @@ class DeviceAPI {
   /*!
    * \brief Allocate a data space on device.
    * \param ctx The device context to perform operation.
-   * \param size The size of the memory
+   * \param nbytes The number of bytes in memory.
    * \param alignment The alignment of the memory.
-   * \return The allocated device pointer
+   * \param type_hint The type of elements. Only needed by certain backends such
+   * as OpenGL, as nbytes & alignment are sufficient for most backends.
+   * \return The allocated device pointer.
    */
-  virtual void* AllocDataSpace(TVMContext ctx, size_t size, size_t alignment) = 0;
+  virtual void* AllocDataSpace(TVMContext ctx,
+                               size_t nbytes,
+                               size_t alignment,
+                               TVMType type_hint) = 0;
   /*!
    * \brief Free a data space on device.
    * \param ctx The device context to perform operation.
diff --git a/include/tvm/schedule.h b/include/tvm/schedule.h
index 3efc31774d40..2f94aedccf3d 100644
--- a/include/tvm/schedule.h
+++ b/include/tvm/schedule.h
@@ -213,6 +213,11 @@ class Stage : public NodeRef {
    * \return reference to self.
    */
   Stage& double_buffer();   // NOLINT(*)
+  /*!
+   * \brief Schedule for OpenGL fragment shader.
+   * \return reference to self.
+   */
+  Stage& opengl(); // NOLINT(*)
   /*!
    * \brief whether the stage has been scheduled.
    * \return whether the stage has been scheduled.
diff --git a/python/tvm/__init__.py b/python/tvm/__init__.py
index e23eed7168dc..fe66271dce5e 100644
--- a/python/tvm/__init__.py
+++ b/python/tvm/__init__.py
@@ -17,7 +17,7 @@
 from . import target
 
 from . import ndarray as nd
-from .ndarray import context, cpu, gpu, opencl, cl, metal, mtl, vpi, rocm, ext_dev
+from .ndarray import context, cpu, gpu, opencl, cl, metal, mtl, vpi, rocm, opengl, ext_dev
 
 from ._ffi.runtime_ctypes import TypeCode
 from ._ffi.function import Function
diff --git a/python/tvm/_ffi/runtime_ctypes.py b/python/tvm/_ffi/runtime_ctypes.py
index cfadd18188f5..e2641fbc7e46 100644
--- a/python/tvm/_ffi/runtime_ctypes.py
+++ b/python/tvm/_ffi/runtime_ctypes.py
@@ -97,6 +97,7 @@ class TVMContext(ctypes.Structure):
         8 : 'metal',
         9 : 'vpi',
         10: 'rocm',
+        11: 'opengl',
         12: 'ext_dev',
     }
     STR2MASK = {
@@ -111,6 +112,7 @@ class TVMContext(ctypes.Structure):
         'metal': 8,
         'vpi': 9,
         'rocm': 10,
+        'opengl': 11,
         'ext_dev': 12,
     }
     def __init__(self, device_type, device_id):
diff --git a/python/tvm/contrib/rpc.py b/python/tvm/contrib/rpc.py
index 3d14f1eb2b6e..3448c4c554d1 100644
--- a/python/tvm/contrib/rpc.py
+++ b/python/tvm/contrib/rpc.py
@@ -285,6 +285,10 @@ def metal(self, dev_id=0):
         """Construct remote Metal device."""
         return self.context(8, dev_id)
 
+    def opengl(self, dev_id=0):
+        """Construct remote OpenGL device."""
+        return self.context(11, dev_id)
+
     def ext_dev(self, dev_id=0):
         """Construct remote extension device."""
         return self.context(12, dev_id)
diff --git a/python/tvm/ndarray.py b/python/tvm/ndarray.py
index 1556c4912a35..0521a69c5f80 100644
--- a/python/tvm/ndarray.py
+++ b/python/tvm/ndarray.py
@@ -120,6 +120,21 @@ def vpi(dev_id=0):
     """
     return TVMContext(9, dev_id)
 
+def opengl(dev_id=0):
+    """Construct a OpenGL device
+
+    Parameters
+    ----------
+    dev_id : int, optional
+        The integer device id
+
+    Returns
+    -------
+    ctx : TVMContext
+        The created context
+    """
+    return TVMContext(11, dev_id)
+
 def ext_dev(dev_id=0):
     """Construct a extension device
 
diff --git a/python/tvm/schedule.py b/python/tvm/schedule.py
index 6abe4aae2f6f..0fc6692d950e 100644
--- a/python/tvm/schedule.py
+++ b/python/tvm/schedule.py
@@ -611,4 +611,11 @@ def double_buffer(self):
         """
         _api_internal._StageDoubleBuffer(self)
 
+    def opengl(self):
+        """The special OpenGL schedule
+
+        Maps each output element to a pixel.
+        """
+        _api_internal._StageOpenGL(self)
+
 _init_api("tvm.schedule")
diff --git a/python/tvm/target.py b/python/tvm/target.py
index 4584c1aa4d23..94cd5457e44f 100644
--- a/python/tvm/target.py
+++ b/python/tvm/target.py
@@ -67,7 +67,7 @@ class Target(object):
 
     Parameters
     ----------
-    target_name : {"llvm", "cuda", "opencl", "metal", "rocm", "stackvm", "ext_dev"}
+    target_name : {"llvm", "cuda", "opencl", "metal", "rocm", "stackvm", "opengl", "ext_dev"}
         The major target name.
 
     options : list of str, optional
@@ -119,6 +119,8 @@ def __init__(self,
         elif target_name in ("metal",):
             self.keys += ("gpu",)
             self.max_num_threads = 256
+        elif target_name in ("opengl",):
+            self.keys += ("opengl",)
         elif target_name in ("stackvm", "ext_dev"):
             # Do not now class for stacvm or ext_dev
             pass
diff --git a/src/api/api_lang.cc b/src/api/api_lang.cc
index 94075b6ec059..37a21cedf3db 100644
--- a/src/api/api_lang.cc
+++ b/src/api/api_lang.cc
@@ -399,6 +399,11 @@ TVM_REGISTER_API("_StageDoubleBuffer")
     args[0].operator Stage().double_buffer();
   });
 
+TVM_REGISTER_API("_StageOpenGL")
+  .set_body([](TVMArgs args, TVMRetValue *ret) {
+    args[0].operator Stage().opengl();
+  });
+
 TVM_REGISTER_API("_ScheduleNormalize")
 .set_body([](TVMArgs args, TVMRetValue* ret) {
     *ret = args[0].operator Schedule()
diff --git a/src/codegen/build_opengl.cc b/src/codegen/build_opengl.cc
new file mode 100644
index 000000000000..5e13676e8111
--- /dev/null
+++ b/src/codegen/build_opengl.cc
@@ -0,0 +1,35 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ *  Build opengl modules from source.
+ * \file build_opengl.cc
+ */
+#include <tvm/base.h>
+#include "./codegen_opengl.h"
+#include "./build_common.h"
+
+namespace tvm {
+namespace codegen {
+
+runtime::Module BuildOpenGL(Array<LoweredFunc> funcs) {
+  bool output_ssa = false;
+  CodeGenOpenGL cg;
+  cg.Init(output_ssa);
+  for (LoweredFunc f : funcs) {
+    cg.AddFunction(f);
+  }
+  auto shaders = cg.Finish();
+#if TVM_OPENGL_RUNTIME
+  return OpenGLModuleCreate(shaders, "gl", ExtractFuncInfo(funcs));
+#else
+  LOG(WARNING) << "OpenGL runtime not enabled, return a source module...";
+  auto data = ToJSON(shaders);
+  return DeviceSourceModuleCreate(data, "gl", ExtractFuncInfo(funcs), "opengl");
+#endif  // TVM_OPENGL_RUNTIME
+}
+
+TVM_REGISTER_API("codegen.build_opengl")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+  *rv = BuildOpenGL(args[0]);
+});
+}  // namespace codegen
+}  // namespace tvm
diff --git a/src/codegen/codegen_c.h b/src/codegen/codegen_c.h
index 895e94b8198e..ce882eda4a18 100644
--- a/src/codegen/codegen_c.h
+++ b/src/codegen/codegen_c.h
@@ -150,7 +150,7 @@ class CodeGenC :
   std::string GetStructRef(
       Type t, const Expr& buffer, const Expr& index, int kind);
   // print reference to a buffer as type t in index.
-  std::string GetBufferRef(
+  virtual std::string GetBufferRef(
       Type t, const Variable* buffer, Expr index);
   /*!
    * \brief If buffer is allocated as type t.
diff --git a/src/codegen/codegen_opengl.cc b/src/codegen/codegen_opengl.cc
new file mode 100644
index 000000000000..e645e7f6c701
--- /dev/null
+++ b/src/codegen/codegen_opengl.cc
@@ -0,0 +1,264 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file codegen_opengl.cc
+ *
+ * We are targeting OpenGL 3.3. The reason of not targeting a recent version
+ * of OpenGL is to have better compatibility of WebGL 2.
+ */
+#include <tvm/runtime/config.h>
+#include <tvm/packed_func_ext.h>
+#include <vector>
+#include <string>
+#include "./codegen_opengl.h"
+#include "../runtime/thread_storage_scope.h"
+
+namespace tvm {
+namespace codegen {
+
+CodeGenOpenGL::CodeGenOpenGL()
+    : output_(nullptr), output_iter_var_(nullptr) {}
+
+void CodeGenOpenGL::InitFuncState(LoweredFunc f) {
+  CodeGenC::InitFuncState(f);
+  output_ = nullptr;
+  inputs_.clear();
+  output_iter_var_ = nullptr;
+  thread_extent_var_ = "";
+}
+
+void CodeGenOpenGL::AddFunction(LoweredFunc f) {
+  // clear previous generated state.
+  this->InitFuncState(f);
+
+  this->decl_stream << "#version 300 es\n";
+  this->decl_stream << "precision highp float;\n";
+
+  // skip the first underscore, so SSA variable starts from _1
+  GetUniqueName("_");
+  // add to alloc buffer type.
+  for (const auto& kv : f->handle_data_type) {
+    RegisterHandleType(kv.first.get(), kv.second.type());
+  }
+
+  // Allocate argument names. Store in `var_idmap_`.
+  for (auto arg : f->args) {
+    auto arg_name = GetUniqueName(arg.get()->name_hint);
+    var_idmap_[arg.get()] = arg_name;
+  }
+
+  thread_extent_var_ = GetUniqueName("thread_extent");
+  this->decl_stream << "uniform int " << thread_extent_var_ << ";\n";
+
+  this->stream << "void main() {\n";
+
+  int func_scope = this->BeginScope();
+  this->PrintStmt(f->body);
+  this->EndScope(func_scope);
+
+  this->PrintIndent();
+  this->stream << "}\n\n";
+
+  // Declare arguments.
+  for (auto arg : f->args) {
+    if (this->inputs_.find(arg.get()) != this->inputs_.cend()) {
+      // Declare input texture.
+      // Format:
+      // - Float: "uniform sampler2D {name};"
+      // - Int: "uniform isampler2D {name};"
+      // - UInt: "uniform usampler2D {name};"
+
+      auto arg_name = GetVarID(arg.get());
+
+      auto type_it = this->handle_data_type_.find(arg.get());
+      CHECK(type_it != this->handle_data_type_.cend()) << "Cannot find type.";
+      auto type = Type2TVMType(type_it->second);
+      CHECK_EQ(type.lanes, 1) << "Vector type not supported.";
+
+      switch (type.code) {
+        case kDLInt:
+          this->decl_stream << "uniform isampler2D " << arg_name << ";\n";
+          break;
+        case kDLUInt:
+          this->decl_stream << "uniform usampler2D " << arg_name << ";\n";
+          break;
+        case kDLFloat:
+          this->decl_stream << "uniform sampler2D " << arg_name << ";\n";
+          break;
+        default:
+          LOG(FATAL) << "Unsupported type code.";
+      }
+
+    } else if (this->output_ == arg.get()) {
+      // Declare output texture.
+      // Format: "out {type} {name};"
+
+      auto arg_name = GetVarID(arg.get());
+
+      auto type_it = this->handle_data_type_.find(arg.get());
+      CHECK(type_it != this->handle_data_type_.cend()) << "Cannot find type.";
+      auto type = type_it->second;
+
+      this->decl_stream << "out ";
+      PrintType(type, this->decl_stream);
+      this->decl_stream << " " << arg_name << ";\n";
+
+    } else {
+      // Declare uniform value.
+      // Format: "uniform {type} {name};"
+
+      auto arg_name = GetVarID(arg.get());
+      auto type = arg.get()->type;
+
+      this->decl_stream << "uniform ";
+      PrintType(type, this->decl_stream);
+      this->decl_stream << " " << arg_name << ";\n";
+    }
+  }
+
+  std::vector<std::string> arg_names;
+  std::vector<runtime::OpenGLArgKind> arg_kinds;
+  for (auto arg : f->args) {
+    std::string name = GetVarID(arg.get());
+
+    runtime::OpenGLArgKind kind;
+    if (inputs_.find(arg.get()) != inputs_.cend()) {
+      kind = runtime::OpenGLArgKind::kInputTexture;
+    } else if (output_ == arg.get()) {
+      kind = runtime::OpenGLArgKind::kOutputTexture;
+    } else {
+      kind = runtime::OpenGLArgKind::kUniform;
+    }
+
+    arg_names.push_back(name);
+    arg_kinds.push_back(kind);
+  }
+
+  shaders_[f->name] = runtime::OpenGLShader(
+      this->decl_stream.str() + this->stream.str(),
+      std::move(arg_names), std::move(arg_kinds),
+      this->thread_extent_var_);
+}
+
+std::unordered_map<std::string, runtime::OpenGLShader> CodeGenOpenGL::Finish() {
+  return shaders_;
+}
+
+void CodeGenOpenGL::BindThreadIndex(const IterVar& iv) {
+  CHECK_EQ(iv->thread_tag, "threadIdx.x") << "Must be threadIdx.x";
+  CHECK(var_idmap_.find(iv->var.get()) == var_idmap_.end())
+    << "Only support one thread iter var";
+  CHECK(output_iter_var_ == nullptr) << "Only support one thread iter var";
+
+  var_idmap_[iv->var.get()] = iv->thread_tag;
+  output_iter_var_ = iv->var.get();
+
+  // Declare threadIdx local variable.
+  this->PrintIndent();
+  this->stream << "ivec2 threadIdx = ivec2(gl_FragCoord.xy);\n";
+
+  // Return directly if threadIdx.x >= thread_extent.
+  this->PrintIndent();
+  this->stream << "if (threadIdx.x >= " << thread_extent_var_ << ") {\n";
+  this->PrintIndent();
+  this->stream << "  return;\n";
+  this->PrintIndent();
+  this->stream << "}\n";
+}
+
+// GLSL texture store is special. We can only store to one output texture, and
+// we must store to the index that matches the current "thread index".
+void CodeGenOpenGL::VisitStmt_(const Store* op) {
+  auto t = op->value.type();
+  auto buffer = op->buffer_var.get();
+  auto index = op->index;
+
+  if (t.lanes() == 1) {
+    // Store to a scalar.
+    CHECK(inputs_.find(buffer) == inputs_.cend())
+      << "Texture has been read from before. Must not store to it.";
+    if (output_ == nullptr) {
+      output_ = buffer;  // Record that this texture is the output.
+    } else {
+      CHECK(output_ == buffer) << "GLSL can only write to 1 texture.";
+    }
+
+    this->PrintIndent();
+    this->stream << GetBufferRef(t, buffer, index) << " = "
+                 << PrintExpr(op->value) << ";\n";
+
+  } else {
+    // Store to a vector.
+    LOG(FATAL) << "Vectorized store not implemented.";
+  }
+}
+
+// texelFetch(tex, ivec2(idx, 0), 0).r
+std::string CodeGenOpenGL::TexelFetch(const Variable* buffer, Expr index) {
+  std::ostringstream os;
+  os << "texelFetch(" << GetVarID(buffer) << ", ivec2(";
+  PrintExpr(index, os);
+  os << ", 0), 0).r";
+  return os.str();
+}
+
+// Print a reference expression to a buffer.
+// Format: texelFetch(buffer, index, 0).r
+std::string CodeGenOpenGL::GetBufferRef(
+    Type t, const Variable* buffer, Expr index) {
+  CHECK_EQ(t.lanes(), 1) << "Vector type not supported.";
+  CHECK(HandleTypeMatch(buffer, t)) << "Type mismatch not supported.";
+
+  if (buffer == this->output_) {
+    // This is the output texture.
+    CHECK_EQ(index.get(), output_iter_var_)
+      << "GLSL must access corresponding elem of output texture.";
+    return GetVarID(buffer);
+  } else {
+    // This is an input texture.
+    this->inputs_.insert(buffer);
+    return TexelFetch(buffer, index);
+  }
+}
+
+void CodeGenOpenGL::PrintType(Type t, std::ostream& os) {
+  switch (t.code()) {
+    case halideir_type_int:
+      CHECK_EQ(t.bits(), 32) << "Only support 32-bit int.";
+      os << "int";
+      break;
+    case halideir_type_uint:
+      CHECK_EQ(t.bits(), 32) << "Only support 32-bit uint.";
+      os << "uint";
+      break;
+    case halideir_type_float:
+      CHECK_EQ(t.bits(), 32) << "Only support 32-bit float.";
+      os << "float";
+      break;
+    default:
+      LOG(FATAL) << "Unsupported type code.";
+  }
+}
+
+// Codegen for immediate values
+
+void CodeGenOpenGL::VisitExpr_(const IntImm* op, std::ostream& os) {
+  CHECK_EQ(op->type, Int(32)) << "GLSL 3.0 only supports 32-bit ints.";
+  CodeGenC::VisitExpr_(op, os);
+}
+
+void CodeGenOpenGL::VisitExpr_(const UIntImm* op, std::ostream& os) {
+  CHECK_EQ(op->type, UInt(32)) << "GLSL 3.0 only supports 32-bit uints.";
+  CodeGenC::VisitExpr_(op, os);
+}
+
+void CodeGenOpenGL::VisitExpr_(const FloatImm* op, std::ostream& os) {
+  CHECK_EQ(op->type, Float(32)) << "GLSL 3.0 only supports 32-bit floats.";
+  CodeGenC::VisitExpr_(op, os);
+}
+
+void CodeGenOpenGL::VisitExpr_(const StringImm*, std::ostream& os) {
+  LOG(FATAL) << "GLSL 3.0 doesn't support strings.";
+}
+
+}  // namespace codegen
+}  // namespace tvm
diff --git a/src/codegen/codegen_opengl.h b/src/codegen/codegen_opengl.h
new file mode 100644
index 000000000000..6ff1f7e9ac95
--- /dev/null
+++ b/src/codegen/codegen_opengl.h
@@ -0,0 +1,48 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file codegen_opengl.h
+ * \brief Generate OpenGL device code.
+ */
+#ifndef TVM_CODEGEN_CODEGEN_OPENGL_H_
+#define TVM_CODEGEN_CODEGEN_OPENGL_H_
+
+#include <tvm/codegen.h>
+#include <tvm/packed_func_ext.h>
+#include <string>
+#include "./codegen_c.h"
+#include "../runtime/opengl/opengl_module.h"
+
+namespace tvm {
+namespace codegen {
+
+class CodeGenOpenGL final : public CodeGenC {
+ public:
+  CodeGenOpenGL();
+  void AddFunction(LoweredFunc f);
+  std::unordered_map<std::string, runtime::OpenGLShader> Finish();
+
+  void InitFuncState(LoweredFunc f) final;
+  void BindThreadIndex(const IterVar& iv) final;
+  void VisitStmt_(const Store* op) final;
+  std::string TexelFetch(const Variable* buffer, Expr index);
+  std::string GetBufferRef(Type t, const Variable* buffer, Expr index) final;
+  void PrintType(Type t, std::ostream& os) final; // NOLINT(*)
+
+  // Codegen for immediate values
+  void VisitExpr_(const IntImm* op, std::ostream& os) final;  // NOLINT(*)
+  void VisitExpr_(const UIntImm* op, std::ostream& os) final;  // NOLINT(*)
+  void VisitExpr_(const FloatImm* op, std::ostream& os) final;  // NOLINT(*)
+  void VisitExpr_(const StringImm* op, std::ostream& os) final;  // NOLINT(*)
+
+ private:
+  const Variable* output_{nullptr};
+  std::unordered_set<const Variable*> inputs_;
+  const Variable* output_iter_var_{nullptr};
+  std::unordered_map<std::string, runtime::OpenGLShader> shaders_;
+  std::string thread_extent_var_;
+};
+
+}  // namespace codegen
+}  // namespace tvm
+
+#endif  // TVM_CODEGEN_CODEGEN_OPENGL_H_
diff --git a/src/codegen/verilog/vpi_device_api.cc b/src/codegen/verilog/vpi_device_api.cc
index 4e0e73eb427b..8efd65785547 100644
--- a/src/codegen/verilog/vpi_device_api.cc
+++ b/src/codegen/verilog/vpi_device_api.cc
@@ -49,7 +49,10 @@ class VPIDeviceAPI final : public runtime::DeviceAPI {
       *rv = 1;
     }
   }
-  void* AllocDataSpace(TVMContext ctx, size_t size, size_t alignment) final {
+  void* AllocDataSpace(TVMContext ctx,
+                       size_t size,
+                       size_t alignment,
+                       TVMType type_hint) final {
     // always align to 32 bytes at least.
     CHECK_LE(alignment, runtime::kAllocAlignment);
     alignment = runtime::kAllocAlignment;
diff --git a/src/runtime/c_runtime_api.cc b/src/runtime/c_runtime_api.cc
index dd8f80bcd72f..0d0e36f239f2 100644
--- a/src/runtime/c_runtime_api.cc
+++ b/src/runtime/c_runtime_api.cc
@@ -31,6 +31,7 @@ inline std::string DeviceName(int type) {
     case kDLMetal: return "metal";
     case kDLVPI: return "vpi";
     case kDLROCM: return "rocm";
+    case kOpenGL: return "opengl";
     case kExtDev: return "ext_dev";
     default: LOG(FATAL) << "unknown type =" << type; return "Unknown";
   }
@@ -95,7 +96,8 @@ DeviceAPI* DeviceAPI::Get(TVMContext ctx, bool allow_missing) {
 }
 
 void* DeviceAPI::AllocWorkspace(TVMContext ctx, size_t size) {
-  return AllocDataSpace(ctx, size, kTempAllocaAlignment);
+  TVMType type_hint{kDLUInt, 8, 1};
+  return AllocDataSpace(ctx, size, kTempAllocaAlignment, type_hint);
 }
 
 void DeviceAPI::FreeWorkspace(TVMContext ctx, void* ptr) {
@@ -365,7 +367,7 @@ int TVMArrayAlloc(const tvm_index_t* shape,
   size_t size = GetDataSize(arr);
   size_t alignment = GetDataAlignment(arr);
   arr->data = DeviceAPIManager::Get(arr->ctx)->AllocDataSpace(
-      arr->ctx, size, alignment);
+      arr->ctx, size, alignment, arr->dtype);
   *out = arr;
   API_END_HANDLE_ERROR(TVMArrayFree_(arr));
 }
diff --git a/src/runtime/cpu_device_api.cc b/src/runtime/cpu_device_api.cc
index 1b2009e98e7f..30c3bb7d52df 100644
--- a/src/runtime/cpu_device_api.cc
+++ b/src/runtime/cpu_device_api.cc
@@ -20,13 +20,16 @@ class CPUDeviceAPI final : public DeviceAPI {
       *rv = 1;
     }
   }
-  void* AllocDataSpace(TVMContext ctx, size_t size, size_t alignment) final {
+  void* AllocDataSpace(TVMContext ctx,
+                       size_t nbytes,
+                       size_t alignment,
+                       TVMType type_hint) final {
     void* ptr;
 #if _MSC_VER
-    ptr = _aligned_malloc(size, alignment);
+    ptr = _aligned_malloc(nbytes, alignment);
     if (ptr == nullptr) throw std::bad_alloc();
 #else
-    int ret = posix_memalign(&ptr, alignment, size);
+    int ret = posix_memalign(&ptr, alignment, nbytes);
     if (ret != 0) throw std::bad_alloc();
 #endif
     return ptr;
diff --git a/src/runtime/cuda/cuda_device_api.cc b/src/runtime/cuda/cuda_device_api.cc
index fd2c54ffd58d..69b485a423c0 100644
--- a/src/runtime/cuda/cuda_device_api.cc
+++ b/src/runtime/cuda/cuda_device_api.cc
@@ -54,12 +54,15 @@ class CUDADeviceAPI final : public DeviceAPI {
     }
     *rv = value;
   }
-  void* AllocDataSpace(TVMContext ctx, size_t size, size_t alignment) final {
+  void* AllocDataSpace(TVMContext ctx,
+                       size_t nbytes,
+                       size_t alignment,
+                       TVMType type_hint) final {
     CUDA_CALL(cudaSetDevice(ctx.device_id));
     CHECK_EQ(256 % alignment, 0U)
         << "CUDA space is aligned at 256 bytes";
     void *ret;
-    CUDA_CALL(cudaMalloc(&ret, size));
+    CUDA_CALL(cudaMalloc(&ret, nbytes));
     return ret;
   }
 
diff --git a/src/runtime/metal/metal_common.h b/src/runtime/metal/metal_common.h
index d7980e64c9a9..7c2975fe7ccc 100644
--- a/src/runtime/metal/metal_common.h
+++ b/src/runtime/metal/metal_common.h
@@ -63,7 +63,10 @@ class MetalWorkspace final : public DeviceAPI {
   // override device API
   void SetDevice(TVMContext ctx) final;
   void GetAttr(TVMContext ctx, DeviceAttrKind kind, TVMRetValue* rv) final;
-  void* AllocDataSpace(TVMContext ctx, size_t size, size_t alignment) final;
+  void* AllocDataSpace(TVMContext ctx,
+                       size_t nbytes,
+                       size_t alignment,
+                       TVMType type_hint) final;
   void FreeDataSpace(TVMContext ctx, void* ptr) final;
   void CopyDataFromTo(const void* from,
                       size_t from_size,
diff --git a/src/runtime/metal/metal_device_api.mm b/src/runtime/metal/metal_device_api.mm
index f66d5b51e64a..82c52a23e036 100644
--- a/src/runtime/metal/metal_device_api.mm
+++ b/src/runtime/metal/metal_device_api.mm
@@ -123,12 +123,12 @@ int GetWarpSize(id<MTLDevice> dev) {
 }
 
 void* MetalWorkspace::AllocDataSpace(
-    TVMContext ctx, size_t size, size_t alignment) {
+    TVMContext ctx, size_t nbytes, size_t alignment, TVMType type_hint) {
   this->Init();
   id<MTLDevice> dev = GetDevice(ctx);
   // allocate buffer in GPU only mode.
   id<MTLBuffer> buf = [
-      dev newBufferWithLength:size
+      dev newBufferWithLength:nbytes
           options:MTLResourceStorageModePrivate];
   CHECK(buf != nil);
   return (__bridge void*)([buf retain]);
diff --git a/src/runtime/module.cc b/src/runtime/module.cc
index 43ad6e523494..3b95137f4fa4 100644
--- a/src/runtime/module.cc
+++ b/src/runtime/module.cc
@@ -115,6 +115,8 @@ bool RuntimeEnabled(const std::string& target) {
     f_name = "device_api.gpu";
   } else if (target == "cl" || target == "opencl") {
     f_name = "device_api.opencl";
+  } else if (target == "gl" || target == "opengl") {
+    f_name = "device_api.opengl";
   } else if (target == "mtl" || target == "metal") {
     f_name = "device_api.metal";
   } else if (target == "stackvm") {
diff --git a/src/runtime/opencl/opencl_common.h b/src/runtime/opencl/opencl_common.h
index e990aeba6a3e..29e205ced4d7 100644
--- a/src/runtime/opencl/opencl_common.h
+++ b/src/runtime/opencl/opencl_common.h
@@ -142,7 +142,10 @@ class OpenCLWorkspace final : public DeviceAPI {
   // override device API
   void SetDevice(TVMContext ctx) final;
   void GetAttr(TVMContext ctx, DeviceAttrKind kind, TVMRetValue* rv) final;
-  void* AllocDataSpace(TVMContext ctx, size_t size, size_t alignment) final;
+  void* AllocDataSpace(TVMContext ctx,
+                       size_t size,
+                       size_t alignment,
+                       TVMType type_hint) final;
   void FreeDataSpace(TVMContext ctx, void* ptr) final;
   void CopyDataFromTo(const void* from,
                       size_t from_offset,
diff --git a/src/runtime/opencl/opencl_device_api.cc b/src/runtime/opencl/opencl_device_api.cc
index 23c897e04825..7518e72f9d9b 100644
--- a/src/runtime/opencl/opencl_device_api.cc
+++ b/src/runtime/opencl/opencl_device_api.cc
@@ -51,7 +51,7 @@ void OpenCLWorkspace::GetAttr(
 }
 
 void* OpenCLWorkspace::AllocDataSpace(
-    TVMContext ctx, size_t size, size_t alignment) {
+    TVMContext ctx, size_t size, size_t alignment, TVMType type_hint) {
   this->Init();
   CHECK(context != nullptr) << "No OpenCL device";
   cl_int err_code;
diff --git a/src/runtime/opengl/opengl_common.h b/src/runtime/opengl/opengl_common.h
new file mode 100644
index 000000000000..80b1d9f95c8e
--- /dev/null
+++ b/src/runtime/opengl/opengl_common.h
@@ -0,0 +1,495 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file opengl_common.h
+ * \brief OpenGL common header
+ */
+#ifndef TVM_RUNTIME_OPENGL_OPENGL_COMMON_H_
+#define TVM_RUNTIME_OPENGL_OPENGL_COMMON_H_
+
+#include <tvm/runtime/config.h>
+#include <tvm/runtime/c_runtime_api.h>
+#include <tvm/runtime/packed_func.h>
+#include <tvm/runtime/device_api.h>
+#include <dmlc/logging.h>
+#include <GL/gl.h>
+#include <GLFW/glfw3.h>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+namespace tvm {
+namespace runtime {
+namespace gl {
+
+// This file contains the following classes.
+class GLFunctionPointers;
+class OpenGLWorkspace;
+class Texture;
+class Program;
+
+inline GLFWglproc GetProcAddress(const char* procname) {
+  GLFWglproc proc = glfwGetProcAddress(procname);
+  CHECK(proc != nullptr) << "Cannot get function \"" << procname << "\"";
+  return proc;
+}
+
+#define SetGLFunctionPointer(NAME) \
+  NAME(decltype(NAME)(GetProcAddress("gl" #NAME)))
+
+/*!
+ * \brief The function pointers of all OpenGL APIs that are used.
+ * Must be constructed after creating an OpenGL context.
+ */
+class GLFunctionPointers {
+ public:
+  GLFunctionPointers()
+      : SetGLFunctionPointer(ActiveTexture),
+        SetGLFunctionPointer(AttachShader),
+        SetGLFunctionPointer(BindBuffer),
+        SetGLFunctionPointer(BindFramebuffer),
+        SetGLFunctionPointer(BindTexture),
+        SetGLFunctionPointer(BindVertexArray),
+        SetGLFunctionPointer(BufferData),
+        SetGLFunctionPointer(CheckFramebufferStatus),
+        SetGLFunctionPointer(Clear),
+        SetGLFunctionPointer(CompileShader),
+        SetGLFunctionPointer(CreateProgram),
+        SetGLFunctionPointer(CreateShader),
+        SetGLFunctionPointer(DeleteFramebuffers),
+        SetGLFunctionPointer(DeleteProgram),
+        SetGLFunctionPointer(DeleteShader),
+        SetGLFunctionPointer(DeleteTextures),
+        SetGLFunctionPointer(DetachShader),
+        SetGLFunctionPointer(DrawArrays),
+        SetGLFunctionPointer(DrawBuffers),
+        SetGLFunctionPointer(EnableVertexAttribArray),
+        SetGLFunctionPointer(Finish),
+        SetGLFunctionPointer(FramebufferTexture2D),
+        SetGLFunctionPointer(GenBuffers),
+        SetGLFunctionPointer(GenFramebuffers),
+        SetGLFunctionPointer(GenTextures),
+        SetGLFunctionPointer(GenVertexArrays),
+        SetGLFunctionPointer(GetAttribLocation),
+        SetGLFunctionPointer(GetError),
+        SetGLFunctionPointer(GetIntegerv),
+        SetGLFunctionPointer(GetProgramInfoLog),
+        SetGLFunctionPointer(GetProgramiv),
+        SetGLFunctionPointer(GetShaderInfoLog),
+        SetGLFunctionPointer(GetShaderiv),
+        SetGLFunctionPointer(GetString),
+        SetGLFunctionPointer(GetUniformLocation),
+        SetGLFunctionPointer(LinkProgram),
+        SetGLFunctionPointer(ReadPixels),
+        SetGLFunctionPointer(ShaderSource),
+        SetGLFunctionPointer(TexImage2D),
+        SetGLFunctionPointer(TexParameteri),
+        SetGLFunctionPointer(TexSubImage2D),
+        SetGLFunctionPointer(Uniform1f),
+        SetGLFunctionPointer(Uniform1i),
+        SetGLFunctionPointer(UseProgram),
+        SetGLFunctionPointer(VertexAttribPointer),
+        SetGLFunctionPointer(Viewport) {}
+
+  void (*ActiveTexture)(GLenum texture);
+  void (*AttachShader)(GLuint program, GLuint shader);
+  void (*BindBuffer)(GLenum target, GLuint buffer);
+  void (*BindFramebuffer)(GLenum target, GLuint framebuffer);
+  void (*BindTexture)(GLenum target, GLuint texture);
+  void (*BindVertexArray)(GLuint array);
+  void (*BufferData)(GLenum target, GLsizeiptr size, const GLvoid* data,
+                     GLenum usage);
+  GLenum (*CheckFramebufferStatus)(GLenum target);
+  void (*Clear)(GLbitfield mask);
+  void (*CompileShader)(GLuint shader);
+  GLuint (*CreateProgram)();
+  GLuint (*CreateShader)(GLenum shader_type);
+  void (*DeleteFramebuffers)(GLsizei n, const GLuint* framebuffers);
+  void (*DeleteProgram)(GLuint program);
+  void (*DeleteShader)(GLuint shader);
+  void (*DeleteTextures)(GLsizei n, const GLuint* textures);
+  void (*DetachShader)(GLuint program, GLuint shader);
+  void (*DrawArrays)(GLenum mode, GLint first, GLsizei count);
+  void (*DrawBuffers)(GLsizei n, const GLenum* bufs);
+  void (*EnableVertexAttribArray)(GLuint index);
+  void (*Finish)();
+  void (*FramebufferTexture2D)(GLenum target, GLenum attachment,
+                               GLenum textarget, GLuint texture, GLint level);
+  void (*GenBuffers)(GLsizei n, GLuint* buffers);
+  void (*GenFramebuffers)(GLsizei n, GLuint* ids);
+  void (*GenTextures)(GLsizei n, GLuint* textures);
+  void (*GenVertexArrays)(GLsizei n, GLuint* arrays);
+  GLint (*GetAttribLocation)(GLuint program, const GLchar* name);
+  GLenum (*GetError)();
+  void (*GetIntegerv)(GLenum pname, GLint* data);
+  void (*GetProgramInfoLog)(GLuint program, GLsizei maxLength, GLsizei* length,
+                            GLchar* info_log);
+  void (*GetProgramiv)(GLuint program, GLenum pname, GLint* params);
+  void (*GetShaderInfoLog)(GLuint shader, GLsizei max_length, GLsizei* length,
+                           GLchar* info_log);
+  void (*GetShaderiv)(GLuint shader, GLenum pname, GLint* params);
+  const GLubyte *(*GetString)(GLenum name);
+  GLint (*GetUniformLocation)(GLuint program, const GLchar* name);
+  void (*LinkProgram)(GLuint program);
+  void (*ReadPixels)(GLint x, GLint y, GLsizei width, GLsizei height,
+                     GLenum format, GLenum type, GLvoid* data);
+  void (*ShaderSource)(GLuint shader, GLsizei count, const GLchar** string,
+                       const GLint* length);
+  void (*TexImage2D)(GLenum target, GLint level, GLint internal_format,
+                     GLsizei width, GLsizei height, GLint border, GLenum format,
+                     GLenum type, const GLvoid* data);
+  void (*TexParameteri)(GLenum target, GLenum pname, GLint param);
+  void (*TexSubImage2D)(GLenum target, GLint level, GLint xoffset,
+                        GLint yoffset, GLsizei width, GLsizei height,
+                        GLenum format, GLenum type, const GLvoid* data);
+  void (*Uniform1f)(GLint location, GLfloat v0);
+  void (*Uniform1i)(GLint location, GLint v0);
+  void (*UseProgram)(GLuint program);
+  void (*VertexAttribPointer)(GLuint index, GLint size, GLenum type,
+                              GLboolean normalized, GLsizei stride,
+                              const GLvoid* pointer);
+  void (*Viewport)(GLint x, GLint y, GLsizei width, GLsizei height);
+};
+
+/*!
+ * \brief Process global OpenGL workspace.
+ */
+class OpenGLWorkspace final : public DeviceAPI {
+ public:
+  ~OpenGLWorkspace() final;
+
+  // override device API
+  void SetDevice(TVMContext ctx) final;
+  void GetAttr(TVMContext ctx, DeviceAttrKind kind, TVMRetValue* rv) final;
+  void* AllocDataSpace(TVMContext ctx,
+                       size_t nbytes,
+                       size_t alignment,
+                       TVMType type_hint) final;
+  void FreeDataSpace(TVMContext ctx, void* ptr) final;
+  void CopyDataFromTo(const void* from,
+                      size_t from_offset,
+                      void* to,
+                      size_t to_offset,
+                      size_t size,
+                      TVMContext ctx_from,
+                      TVMContext ctx_to,
+                      TVMStreamHandle stream) final;
+  void StreamSync(TVMContext ctx, TVMStreamHandle stream) final;
+  void* AllocWorkspace(TVMContext ctx, size_t size) final;
+  void FreeWorkspace(TVMContext ctx, void* data) final;
+
+  /*!
+   * \brief Get the global OpenGL workspace.
+   * \return The global OpenGL workspace.
+   */
+  static const std::shared_ptr<OpenGLWorkspace>& Global();
+
+  /*!
+   * \brief Create an OpenGL program that uses the given fragment shader.
+   * \param fragment_shader The fragment shader **source**.
+   * \return The OpenGL program.
+   */
+  Program CreateProgram(const char* fragment_shader_src);
+
+  /*!
+   * \brief Create an OpenGL texture that stores an array.
+   * \param type Element type.
+   * \param nbytes Number of bytes in the array.
+   * \return The OpenGL texture.
+   */
+  Texture CreateTexture(TVMType type, size_t nbytes);
+
+  /*!
+   * \brief Upload user data into a sub-region of an OpenGL texture.
+   * \param texture The texture to be written to.
+   * \param begin The index of the first element to be written to.
+   * \param nelems The number of elements to be written to.
+   * \param data The user data.
+   */
+  void PutTextureData(Texture* texture,
+                      GLint begin,
+                      GLsizei nelems,
+                      const GLvoid* data);
+  /*!
+   * \brief Download a sub-region of an OpenGL texture.
+   * \param texture The texture to download from.
+   * \param begin The index of first element to download from.
+   * \param nelems The number of elements to download from.
+   * \param data The user buffer.
+   */
+  void GetTextureData(const Texture* texture,
+                      GLint begin,
+                      GLsizei nelems,
+                      GLvoid* data);
+
+  /*!
+   * \brief Set currently used OpenGL program.
+   */
+  void SetCurrentProgram(const Program& program);
+
+  /*!
+   * \brief Set uniform values for an OpenGL program.
+   * Must call SetCurrentProgram before calling this.
+   * \param program The OpenGL program.
+   * \param name The uniform argument name.
+   * \param type The type of the uniform.
+   * \param value The value to pass in.
+   */
+  void SetUniform(const Program& program,
+                  const std::string& name,
+                  TVMType type,
+                  void* value);
+
+  /*!
+   * \brief Set input texture for an OpenGL program.
+   * Must call SetCurrentProgram before calling this.
+   * \param program The OpenGL program.
+   * \param name The texture uniform argument name.
+   * \param unit The texture unit to use. Each input texture must occupy a
+   * different unit.
+   * \param texture The OpenGL texture to pass in.
+   */
+  void SetInputTexture(const Program& program,
+                       const std::string& name,
+                       GLuint unit,
+                       Texture* texture);
+
+  /*!
+   * \brief Render to a texture.
+   * \param output The output texture.
+   */
+  void Render(Texture* output);
+
+ private:
+  friend class Texture;
+  friend class Program;
+
+  // Global singleton. Hide constructor.
+  OpenGLWorkspace();
+
+  GLFWwindow* window_;
+  std::unique_ptr<GLFunctionPointers> gl;
+  GLuint vertex_shader_;
+  static const int kWindowWidth = 640;
+  static const int kWindowHeight = 480;
+  struct Vertex {
+    float x, y;
+  };
+  static constexpr size_t kNumVertices = 6;
+  static const Vertex vertices[kNumVertices];
+  static const char* vertex_shader_text_;
+
+  /*!
+   * \brief Bind a texture to a "texture unit".
+   * After calling this function, the "texture unit" becomes "active", and the
+   * texture is bound to GL_TEXTURE_2D in that "texture unit".
+   * \param unit The texture unit to activate.
+   * \param texture The texture to bind.
+   */
+  void BindTextureUnit(GLuint unit, GLuint texture);
+
+  /*!
+   * \brief Callback in Texture's destructor.
+   */
+  void OnDeleteTexture(GLuint texture);
+
+  /*!
+   * \brief Callback in Program's destructor.
+   */
+  void OnDeleteProgram(GLuint program);
+
+  /*!
+   * \brief Check if there is any outstanding OpenGL error. If there is, crash.
+   */
+  void CheckOpenGLError();
+
+  /*!
+   * \brief Get the maximum number of texture units.
+   */
+  GLuint NumTextureUnits();
+
+  /*!
+   * \brief Create and compile a shader from a source string.
+   * \param shader_kind The kind of shader.
+   * Could be GL_VERTEX_SHADER or GL_FRAGMENT_SHADER.
+   * \param shader_src The source string of the shader.
+   * \return The compiled shader ID.
+   */
+  GLuint CreateShader(GLenum shader_kind, const char* shader_src);
+
+  /*!
+   * \brief Create an OpenGL program that uses the given fragment shader.
+   * \param fragment_shader The **compiled** fragment shader.
+   * \return The OpenGL program.
+   */
+  Program CreateProgram(GLuint fragment_shader);
+};
+
+/*!
+ * \brief An OpenGL program, composed of a vertex shader and a fragment shader.
+ * In TVM, every program has the same vertex shader.
+ * So a program just corresponds to a fragment shader.
+ * A program can only be created by the workspace.
+ * This class is just a wrapper over an OpenGL program ID.
+ */
+class Program {
+ public:
+  // Move constructor.
+  Program(Program&& other) noexcept
+      : workspace_(other.workspace_), program_(other.program_) {
+    other.program_ = kInvalidProgram;
+  }
+
+  // Move assignment.
+  Program& operator=(Program&& other) noexcept {
+    workspace_ = other.workspace_;
+    program_ = other.program_;
+    other.program_ = kInvalidProgram;
+    return *this;
+  }
+
+  // Disallow copy.
+  Program(const Program& other) = delete;
+  Program& operator=(const Program& other) = delete;
+
+  // Destructor.
+  ~Program() {
+    if (program_ != kInvalidProgram) {
+      workspace_->OnDeleteProgram(program_);
+      program_ = kInvalidProgram;
+    }
+  }
+
+ private:
+  friend class OpenGLWorkspace;
+
+  // Only OpenGLWorkspace can create a Program.
+  // We enforce this to make sure OpenGL is initialized.
+  explicit Program(OpenGLWorkspace* workspace, GLuint program)
+      : workspace_(workspace), program_(program) {}
+
+  // The internal OpenGL program ID.
+  GLuint program() const { return program_; }
+
+  static constexpr GLuint kInvalidProgram = static_cast<GLuint>(-1);
+
+  OpenGLWorkspace* workspace_;
+  GLuint program_;
+};
+
+/*!
+ * \brief The storage format of a texture.
+ * The members match the API of glTexImage2D.
+ */
+struct TextureFormat {
+  TextureFormat(GLint internal_format, GLenum format, GLenum type)
+      : internal_format(internal_format), format(format), type(type) {}
+
+  GLsizei elemsz() const {
+    switch (type) {
+      case GL_BYTE: case GL_UNSIGNED_BYTE:
+        return 1;
+      case GL_SHORT: case GL_UNSIGNED_SHORT:
+        return 2;
+      case GL_INT: case GL_UNSIGNED_INT:
+        return 4;
+      case GL_FLOAT:
+        return 4;
+      default:
+        LOG(FATAL) << "Unsupported type";
+        return -1;
+    }
+  }
+
+  bool operator==(const TextureFormat& other) const {
+    return std::make_tuple(internal_format, format, type) ==
+        std::make_tuple(other.internal_format, other.format, other.type);
+  }
+
+  GLint internal_format;  // OpenGL says this is GLint, not GLenum.
+  GLenum format;
+  GLenum type;
+};
+
+/*!
+ * \brief An OpenGL texture represents a chunk of GPU memory.
+ * This is the way we represent tensors.
+ * We always use 2D textures.
+ */
+class Texture {
+ public:
+  // Move constructor.
+  Texture(Texture&& other) noexcept
+      : workspace_(other.workspace_), texture_(other.texture_),
+        format_(other.format_), width_(other.width_), height_(other.height_) {
+    other.texture_ = kInvalidTexture;
+  }
+
+  // Move assignment.
+  Texture& operator=(Texture&& other) noexcept {
+    workspace_ = other.workspace_;
+    texture_ = other.texture_;
+    format_ = other.format_;
+    width_ = other.width_;
+    height_ = other.height_;
+    other.texture_ = kInvalidTexture;
+    return *this;
+  }
+
+  // Disallow copy.
+  Texture(const Texture& other) = delete;
+  Texture& operator=(const Texture& other) = delete;
+
+  // Destructor.
+  ~Texture() {
+    if (texture_ != kInvalidTexture) {
+      workspace_->OnDeleteTexture(texture_);
+      texture_ = kInvalidTexture;
+    }
+  }
+
+  /*!
+   * \brief The width of the texture in number of pixels.
+   */
+  GLsizei width() const { return width_; }
+
+  /*!
+   * \brief The height of the texture in number of pixels.
+   */
+  GLsizei height() const { return height_; }
+
+  /*!
+   * \brief The number of bytes of each element in the array.
+   */
+  GLsizei elemsz() const { return format_.elemsz(); }
+
+ private:
+  friend class OpenGLWorkspace;
+
+  // Only OpenGLWorkspace can create a Texture.
+  // We enforce this to make sure OpenGL is initialized.
+  // Always only use the first dimension of a 2D texture.
+  // The reason is that texelFetch only supports 2D textures.
+  explicit Texture(OpenGLWorkspace* workspace, GLuint texture,
+                   TextureFormat format,
+                   GLsizei width, GLsizei height)
+      : workspace_(workspace), texture_(texture), format_(format),
+        width_(width), height_(height) {}
+
+  // The internal texture ID.
+  GLuint texture() const { return texture_; }
+
+  static constexpr GLuint kInvalidTexture = static_cast<GLuint>(-1);
+
+  OpenGLWorkspace* workspace_;
+  GLuint texture_;
+  TextureFormat format_;
+  GLsizei width_;
+  GLsizei height_;
+};
+
+}  // namespace gl
+}  // namespace runtime
+}  // namespace tvm
+
+#endif  // TVM_RUNTIME_OPENGL_OPENGL_COMMON_H_
diff --git a/src/runtime/opengl/opengl_device_api.cc b/src/runtime/opengl/opengl_device_api.cc
new file mode 100644
index 000000000000..798003af902f
--- /dev/null
+++ b/src/runtime/opengl/opengl_device_api.cc
@@ -0,0 +1,556 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file opengl_device_api.cc
+ */
+#include "./opengl_common.h"
+
+#if TVM_OPENGL_RUNTIME
+
+#include <tvm/runtime/registry.h>
+#include <cstring>
+
+namespace tvm {
+namespace runtime {
+namespace gl {
+
+/*!
+ * \brief Turn OpenGL error enum to string.
+ */
+static const char* GLGetErrorString(GLenum error) {
+  switch (error) {
+    case GL_NO_ERROR:
+      return "GL_NO_ERROR";
+    case GL_INVALID_ENUM:
+      return "GL_INVALID_ENUM";
+    case GL_INVALID_VALUE:
+      return "GL_INVALID_VALUE";
+    case GL_INVALID_OPERATION:
+      return "GL_INVALID_OPERATION";
+    case GL_STACK_OVERFLOW:
+      return "GL_STACK_OVERFLOW";
+    case GL_STACK_UNDERFLOW:
+      return "GL_STACK_UNDERFLOW";
+    case GL_OUT_OF_MEMORY:
+      return "GL_OUT_OF_MEMORY";
+    default:
+      return "Unknown OpenGL error code";
+  }
+}
+
+/*!
+ * \brief Get the latest error.
+ */
+void OpenGLWorkspace::CheckOpenGLError() {
+  GLenum err = gl->GetError();
+  CHECK_EQ(err, GL_NO_ERROR) << "OpenGL error, code=" << err << ": "
+                             << gl::GLGetErrorString(err);
+}
+
+/*!
+ * \brief Protected OpenGL call.
+ * \param func Expression to call.
+ */
+#define OPENGL_CALL(func)                                                      \
+  {                                                                            \
+    (func);                                                                    \
+    CheckOpenGLError();                                                        \
+  }
+
+/*!
+ * \brief The error handling callback passed to GLFW.
+ */
+void GlfwErrorCallback(int err, const char* str) {
+  LOG(FATAL) << "Error: [" << err << "] " << str;
+}
+
+const std::shared_ptr<OpenGLWorkspace>& OpenGLWorkspace::Global() {
+  static std::shared_ptr<OpenGLWorkspace> inst(new OpenGLWorkspace);
+  return inst;
+}
+
+void OpenGLWorkspace::SetDevice(TVMContext ctx) {
+  CHECK_EQ(ctx.device_type, static_cast<int>(kOpenGL))
+    << "Device type must be OpenGL.";
+  CHECK_EQ(ctx.device_id, 0) << "Only support 1 OpenGL \"device\".";
+}
+
+void OpenGLWorkspace::GetAttr(
+    TVMContext ctx, DeviceAttrKind kind, TVMRetValue* rv) {
+  switch (kind) {
+    case kExist: {
+      *rv = static_cast<int>(ctx.device_id == 0);
+      break;
+    }
+    case kMaxThreadsPerBlock: {
+      GLint max_texture_size;
+      OPENGL_CALL(gl->GetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size));
+      break;
+    }
+    case kWarpSize: {
+      *rv = 1;
+      break;
+    }
+    case kComputeVersion: {
+      break;
+    }
+  }
+}
+
+void* OpenGLWorkspace::AllocDataSpace(
+    TVMContext ctx, size_t nbytes, size_t alignment, TVMType type_hint) {
+  return reinterpret_cast<void*>(new Texture(CreateTexture(type_hint, nbytes)));
+}
+
+void OpenGLWorkspace::FreeDataSpace(TVMContext ctx, void* ptr) {
+  delete reinterpret_cast<Texture*>(ptr);
+}
+
+void OpenGLWorkspace::CopyDataFromTo(const void* from,
+                                     size_t from_offset,
+                                     void* to,
+                                     size_t to_offset,
+                                     size_t size,
+                                     TVMContext ctx_from,
+                                     TVMContext ctx_to,
+                                     TVMStreamHandle stream) {
+  CHECK(stream == nullptr);
+
+  // TODO(zhixunt): This is a nasty hack to avoid comparison between
+  // incompatible enums. We should add kOpenGL to dlpack.
+  constexpr int gl_devtype = kOpenGL;
+  std::tuple<int, int> type_from_to(ctx_from.device_type, ctx_to.device_type);
+
+  if (type_from_to == std::make_tuple(gl_devtype, gl_devtype)) {
+    auto from_texture = static_cast<const Texture*>(from);
+    auto to_texture = static_cast<Texture*>(to);
+    auto temp_buffer = std::unique_ptr<char[]>(new char[size]);
+    CHECK(from_texture->format_ == to_texture->format_);
+    auto elemsz = from_texture->elemsz();
+    auto from_begin = static_cast<GLint>(from_offset / elemsz);
+    auto to_begin = static_cast<GLint>(to_offset / elemsz);
+    auto nelems = static_cast<GLsizei>(size / elemsz);
+    GetTextureData(from_texture, from_begin, nelems, temp_buffer.get());
+    PutTextureData(to_texture, to_begin, nelems, temp_buffer.get());
+
+  } else if (type_from_to == std::make_tuple(gl_devtype, kDLCPU)) {
+    auto texture = static_cast<const Texture*>(from);
+    void *data = static_cast<char *>(to) + to_offset;
+    auto elemsz = texture->elemsz();
+    auto begin = static_cast<GLint>(from_offset / elemsz);
+    auto nelems = static_cast<GLsizei>(size / elemsz);
+    GetTextureData(texture, begin, nelems, data);
+
+  } else if (type_from_to == std::make_tuple(kDLCPU, gl_devtype)) {
+    auto texture = reinterpret_cast<Texture*>(to);
+    const void* data = static_cast<const char*>(from) + from_offset;
+    auto elemsz = texture->elemsz();
+    auto begin = static_cast<GLint>(to_offset / elemsz);
+    auto nelems = static_cast<GLsizei>(size / elemsz);
+    PutTextureData(texture, begin, nelems, data);
+
+  } else {
+    LOG(FATAL) << "Expect copy from/to OpenGL or between OpenGL";
+  }
+}
+
+void OpenGLWorkspace::StreamSync(TVMContext ctx, TVMStreamHandle stream) {}
+
+void* OpenGLWorkspace::AllocWorkspace(TVMContext ctx, size_t size) {
+  LOG(FATAL) << "Cannot allocate OpenGL workspace.";
+  return nullptr;
+}
+
+void OpenGLWorkspace::FreeWorkspace(TVMContext ctx, void* data) {
+  LOG(FATAL) << "Cannot free OpenGL workspace.";
+}
+
+OpenGLWorkspace::OpenGLWorkspace() {
+  // Set an error handler.
+  // This can be called before glfwInit().
+  glfwSetErrorCallback(&GlfwErrorCallback);
+
+  // Initialize GLFW.
+  if (glfwInit() != GL_TRUE) {
+    LOG(FATAL) << "glfwInit() failed!";
+  }
+
+  // Create a window.
+  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
+  glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
+  glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
+  glfwWindowHint(GLFW_VISIBLE, GL_FALSE);
+  window_ = glfwCreateWindow(kWindowWidth, kWindowHeight, "", nullptr, nullptr);
+  if (window_ == nullptr) {
+    LOG(FATAL) << "glfwCreateWindow() failed!";
+  }
+
+  // Before using any OpenGL API, we must specify a context.
+  glfwMakeContextCurrent(window_);
+
+  // Load all OpenGL API function pointers.
+  gl = std::unique_ptr<GLFunctionPointers>(new GLFunctionPointers);
+
+  CheckOpenGLError();
+
+  // We always render the same vertices and triangles.
+  GLuint vertex_buffer;
+  OPENGL_CALL(gl->GenBuffers(1, &vertex_buffer));
+  OPENGL_CALL(gl->BindBuffer(GL_ARRAY_BUFFER, vertex_buffer));
+  OPENGL_CALL(gl->BufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices,
+                             GL_STATIC_DRAW));
+
+  GLuint vertex_array;
+  OPENGL_CALL(gl->GenVertexArrays(1, &vertex_array));
+  OPENGL_CALL(gl->BindVertexArray(vertex_array));
+  OPENGL_CALL(gl->BindBuffer(GL_ARRAY_BUFFER, vertex_buffer));
+
+  // We always use the same vertex shader.
+  vertex_shader_ = CreateShader(GL_VERTEX_SHADER, vertex_shader_text_);
+
+  LOG(INFO) << "OpenGL initialized, version = " << gl->GetString(GL_VERSION);
+}
+
+OpenGLWorkspace::~OpenGLWorkspace() {
+  // Paired with glfwCreateWindow().
+  glfwDestroyWindow(window_);
+
+  // Paired with glfwInit().
+  glfwTerminate();
+}
+
+void OpenGLWorkspace::BindTextureUnit(GLuint unit, GLuint texture) {
+  OPENGL_CALL(gl->ActiveTexture(GL_TEXTURE0 + unit));
+  OPENGL_CALL(gl->BindTexture(GL_TEXTURE_2D, texture));
+}
+
+void OpenGLWorkspace::OnDeleteTexture(GLuint texture) {
+  OPENGL_CALL(gl->DeleteTextures(1, &texture));
+}
+
+void OpenGLWorkspace::OnDeleteProgram(GLuint program) {
+  OPENGL_CALL(gl->DeleteProgram(program));
+}
+
+GLuint OpenGLWorkspace::NumTextureUnits() {
+  GLint num_units;
+  OPENGL_CALL(gl->GetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &num_units));
+  return static_cast<GLuint>(num_units);
+}
+
+const OpenGLWorkspace::Vertex OpenGLWorkspace::vertices[OpenGLWorkspace::kNumVertices] = {
+    {-1.f, -1.f},
+    {1.0f, -1.f},
+    {1.0f, 1.0f},
+    {-1.f, -1.f},
+    {-1.f, 1.0f},
+    {1.0f, 1.0f},
+};
+
+// Don't need to change this.
+// The vertex shader only needs to take in the triangle points.
+// No need for point transformations.
+const char* OpenGLWorkspace::vertex_shader_text_ = "#version 300 es\n"
+    "in vec2 point; // input to vertex shader\n"
+    "void main() {\n"
+    "  gl_Position = vec4(point, 0.0, 1.0);\n"
+    "}\n";
+
+Program OpenGLWorkspace::CreateProgram(
+    const char* fragment_shader_src) {
+  // Create and compile the shaders.
+  GLuint fragment_shader = CreateShader(GL_FRAGMENT_SHADER,
+                                        fragment_shader_src);
+
+  // Link the shaders and create the program.
+  Program program = CreateProgram(fragment_shader);
+
+  OPENGL_CALL(gl->DeleteShader(fragment_shader));
+
+  return program;
+}
+
+GLuint OpenGLWorkspace::CreateShader(GLenum shader_kind,
+                                     const char* shader_src) {
+  // Create the shader.
+  GLuint shader = gl->CreateShader(shader_kind);
+  gl->ShaderSource(shader, 1, &shader_src, nullptr);
+  gl->CompileShader(shader);
+
+  // Check compile errors.
+  GLint err;
+  gl->GetShaderiv(shader, GL_COMPILE_STATUS, &err);
+
+  GLint info_log_len;
+  gl->GetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_log_len);
+
+  if (err != GL_TRUE) {
+    std::unique_ptr<char[]> err_msg(new char[info_log_len + 1]);
+    gl->GetShaderInfoLog(shader, info_log_len, nullptr, err_msg.get());
+    LOG(FATAL) << err_msg.get();
+    assert(false);
+  }
+
+  CheckOpenGLError();
+
+  return shader;
+}
+
+static TextureFormat GetTextureFormat(TVMType type) {
+  CHECK_EQ(type.lanes, 1) << "Not supporting multi-lane types.";
+
+  switch (type.code) {
+    case kDLInt: {
+      switch (type.bits) {
+        case 8:
+          return {GL_R8I, GL_RED_INTEGER, GL_BYTE};
+        case 16:
+          return {GL_R16I, GL_RED_INTEGER, GL_SHORT};
+        case 32:
+          return {GL_R32I, GL_RED_INTEGER, GL_INT};
+        default:
+          LOG(FATAL) << "Unsupported type bits " << type.bits;
+      }
+    }
+    case kDLUInt: {
+      switch (type.bits) {
+        case 8:
+          return {GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE};
+        case 16:
+          return {GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT};
+        case 32:
+          return {GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT};
+        default:
+          LOG(FATAL) << "Unsupported type bits " << type.bits;
+      }
+    }
+    case kDLFloat: {
+      switch (type.bits) {
+        case 32:
+          return {GL_R32F, GL_RED, GL_FLOAT};
+        default:
+          LOG(FATAL) << "Unsupported type bits " << type.bits;
+      }
+    }
+    default:
+      LOG(FATAL) << "Unsupported type code" << type.code;
+  }
+  assert(false);
+}
+
+Texture OpenGLWorkspace::CreateTexture(TVMType type, size_t nbytes) {
+  // Create a texture.
+  GLuint texture;
+  OPENGL_CALL(gl->GenTextures(1, &texture));
+
+  BindTextureUnit(NumTextureUnits() - 1, texture);
+
+  // Use glTexImage2D with nullptr data to specify GPU data storage.
+  auto texture_format = GetTextureFormat(type);
+  auto width = static_cast<GLsizei>(nbytes / (type.bits / 8));
+  auto height = GLsizei(1);
+  OPENGL_CALL(gl->TexImage2D(GL_TEXTURE_2D, /*level=*/0,
+                             texture_format.internal_format,
+                             width, height, /*border=*/0,
+                             texture_format.format, texture_format.type,
+                             /*data=*/nullptr));
+
+  OPENGL_CALL(
+      gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
+  OPENGL_CALL(
+      gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
+  OPENGL_CALL(
+      gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
+  OPENGL_CALL(
+      gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
+
+  return Texture(this, texture, texture_format, width, height);
+}
+
+Program OpenGLWorkspace::CreateProgram(GLuint fragment_shader) {
+  // Create the program and link the shaders.
+  GLuint program = gl->CreateProgram();
+  gl->AttachShader(program, vertex_shader_);
+  gl->AttachShader(program, fragment_shader);
+  gl->LinkProgram(program);
+
+  // Check link errors.
+  GLint err;
+  gl->GetProgramiv(program, GL_LINK_STATUS, &err);
+
+  GLint info_log_len;
+  gl->GetProgramiv(program, GL_INFO_LOG_LENGTH, &info_log_len);
+
+  if (err != GL_TRUE) {
+    std::unique_ptr<char[]> err_msg(new char[info_log_len + 1]);
+    gl->GetProgramInfoLog(program, info_log_len, nullptr, err_msg.get());
+    LOG(FATAL) << err_msg.get();
+    assert(false);
+  }
+
+  CheckOpenGLError();
+
+  OPENGL_CALL(gl->DetachShader(program, vertex_shader_));
+  OPENGL_CALL(gl->DetachShader(program, fragment_shader));
+
+  auto point_attrib = GLuint(gl->GetAttribLocation(program, "point"));
+  OPENGL_CALL(gl->EnableVertexAttribArray(point_attrib));
+
+  OPENGL_CALL(gl->VertexAttribPointer(point_attrib, 2, GL_FLOAT, GL_FALSE,
+                                      sizeof(Vertex), nullptr));
+
+  return Program(this, program);
+}
+
+void OpenGLWorkspace::PutTextureData(Texture *texture,
+                                     GLint begin,
+                                     GLsizei nelems,
+                                     const GLvoid* data) {
+  // Bind to temporary unit.
+  BindTextureUnit(NumTextureUnits() - 1, texture->texture());
+
+  // Similar to cudaMemcpy.
+  OPENGL_CALL(gl->TexSubImage2D(GL_TEXTURE_2D, /*level=*/0,
+                                /*xoffset=*/begin, /*yoffset=*/0,
+                                /*width=*/nelems, /*height=*/1,
+                                texture->format_.format, texture->format_.type,
+                                data));
+}
+
+void OpenGLWorkspace::GetTextureData(const Texture *texture,
+                                     GLint begin,
+                                     GLsizei nelems,
+                                     GLvoid* data) {
+  BindTextureUnit(NumTextureUnits() - 1, texture->texture());
+
+  // Create frame buffer.
+  GLuint frame_buffer;
+  OPENGL_CALL(gl->GenFramebuffers(1, &frame_buffer));
+  OPENGL_CALL(gl->BindFramebuffer(GL_FRAMEBUFFER, frame_buffer));
+
+  // Bind texture to framebuffer's attachment 0.
+  OPENGL_CALL(gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                                       GL_TEXTURE_2D, texture->texture(), 0));
+
+  // Always check that our framebuffer is okay.
+  if (gl->CheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+    LOG(FATAL) << "Framebuffer not complete.";
+  }
+
+#ifdef __EMSCRIPTEN__
+  // WebGL2's glReadPixels API doesn't allow GL_RED user buffer format.
+  // Instead, We must use GL_RGBA. This means the data we retrieve has useless
+  // GBA channels. Here we are applying a dirty hack.
+  // TODO(zhixunt): We really want to utilize all RGBA channels in textures.
+  //
+  // WebGL2's glReadPixels API also doesn't allow GL_RED_INTEGER or
+  // GL_RGB_INTEGER user buffer format, which means we cannot retrieve integer
+  // texture data? (need to confirm)
+
+  CHECK_EQ(texture->format_.internal_format, GL_R32F)
+      << "Retrieving integer texture not supported yet.";
+  auto elemsz = texture->format_.elemsz();
+  auto nchannels = 4;
+  auto padded_data_size = nchannels * nelems * elemsz;
+  auto padded_data = std::unique_ptr<char[]>(new char[padded_data_size]);
+  OPENGL_CALL(gl->ReadPixels(/*x=*/begin, /*y=*/0, /*width=*/nelems,
+                             /*height=*/1, GL_RGBA, GL_FLOAT,
+                             padded_data.get()));
+  for (GLsizei i = 0; i != nelems; ++i) {
+    auto dst = reinterpret_cast<char *>(data) + i * elemsz;
+    auto src = padded_data.get() + nchannels * i * elemsz;
+    std::memcpy(dst, src, elemsz);
+  }
+#else
+  OPENGL_CALL(gl->ReadPixels(/*x=*/begin, /*y=*/0, /*width=*/nelems,
+                             /*height=*/1, texture->format_.format,
+                             texture->format_.type, data));
+#endif
+
+  OPENGL_CALL(gl->DeleteFramebuffers(1, &frame_buffer));
+}
+
+void OpenGLWorkspace::SetCurrentProgram(const Program& program) {
+  OPENGL_CALL(gl->UseProgram(program.program()));
+}
+
+void OpenGLWorkspace::SetUniform(const Program& program,
+                                 const std::string& name,
+                                 TVMType type,
+                                 void* value) {
+  GLint location = gl->GetUniformLocation(program.program(), name.c_str());
+  switch (type.code) {
+    case kDLInt: {
+      CHECK_EQ(type.bits, 32) << "Only support 32-bit int for uniform.";
+      GLint uniform_value = *reinterpret_cast<GLint*>(value);
+      OPENGL_CALL(gl->Uniform1i(location, uniform_value));
+      break;
+    }
+    case kDLUInt: {
+      LOG(FATAL) << "Strangely, emcc WebGL does not support glUniform1ui.";
+      break;
+    }
+    case kDLFloat: {
+      CHECK_EQ(type.bits, 32) << "Only support 32-bit float for uniform.";
+      GLfloat uniform_value = *reinterpret_cast<GLfloat*>(value);
+      OPENGL_CALL(gl->Uniform1f(location, uniform_value));
+      break;
+    }
+    default: {
+      LOG(FATAL) << "Unsupported type code for uniform.";
+      break;
+    }
+  }
+}
+
+void OpenGLWorkspace::SetInputTexture(const Program& program,
+                                      const std::string& name,
+                                      GLuint unit,
+                                      Texture* texture) {
+  // We always use the last texture unit as temporary.
+  // Therefore, we can have "NumTextureUnits() - 1" input textures.
+  CHECK_LT(unit, NumTextureUnits() - 1) << "Too many textures.";
+
+  BindTextureUnit(unit, texture->texture());
+  GLint location = gl->GetUniformLocation(program.program_, name.c_str());
+  OPENGL_CALL(gl->Uniform1i(location, unit));
+}
+
+void OpenGLWorkspace::Render(Texture* output) {
+  // Create frame buffer.
+  GLuint frame_buffer;
+  OPENGL_CALL(gl->GenFramebuffers(1, &frame_buffer));
+  OPENGL_CALL(gl->BindFramebuffer(GL_FRAMEBUFFER, frame_buffer));
+
+  // Set "renderedTexture" as our colour attachement 0.
+  OPENGL_CALL(gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                                       GL_TEXTURE_2D, output->texture(), 0));
+
+  // Specify that we will render to color attachment 0.
+  GLenum DrawBuffers[1] = {GL_COLOR_ATTACHMENT0};
+  OPENGL_CALL(gl->DrawBuffers(1, DrawBuffers));
+
+  // Always check that our framebuffer is okay.
+  if (gl->CheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+    LOG(FATAL) << "Framebuffer not complete.";
+  }
+
+  // Perform rendering.
+  OPENGL_CALL(gl->Viewport(0, 0, output->width(), output->height()));
+  OPENGL_CALL(gl->Clear(GL_COLOR_BUFFER_BIT));
+  OPENGL_CALL(gl->DrawArrays(GL_TRIANGLES, 0, 6));
+
+  OPENGL_CALL(gl->DeleteFramebuffers(1, &frame_buffer));
+}
+
+TVM_REGISTER_GLOBAL("device_api.opengl")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+  DeviceAPI* ptr = OpenGLWorkspace::Global().get();
+  *rv = static_cast<void*>(ptr);
+});
+
+}  // namespace gl
+}  // namespace runtime
+}  // namespace tvm
+
+#endif  // TVM_OPENGL_RUNTIME
diff --git a/src/runtime/opengl/opengl_module.cc b/src/runtime/opengl/opengl_module.cc
new file mode 100644
index 000000000000..6793bd6d29b6
--- /dev/null
+++ b/src/runtime/opengl/opengl_module.cc
@@ -0,0 +1,284 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file opengl_module.cc
+ */
+#include <utility>
+
+#include "./opengl_common.h"
+#include "./opengl_module.h"
+
+#if TVM_OPENGL_RUNTIME
+
+#include <tvm/runtime/registry.h>
+#include "../pack_args.h"
+#include "../thread_storage_scope.h"
+#include "../file_util.h"
+
+namespace tvm {
+namespace runtime {
+
+class OpenGLModuleNode final : public ModuleNode {
+ public:
+  OpenGLModuleNode(std::unordered_map<std::string, OpenGLShader> shaders,
+                   std::string fmt,
+                   std::unordered_map<std::string, FunctionInfo> fmap);
+
+  ~OpenGLModuleNode() override = default;
+
+  const char* type_key() const final { return "opengl"; }
+
+  PackedFunc GetFunction(const std::string& name,
+                         const std::shared_ptr<ModuleNode>& sptr_to_self) final;
+
+  std::string GetSource(const std::string& format) final;
+
+  void SaveToFile(const std::string& file_name,
+                  const std::string& format) final;
+
+  void SaveToBinary(dmlc::Stream* stream) final;
+
+  const gl::Program& GetProgram(const std::string& func_name) const;
+
+  const OpenGLShader& GetShader(const std::string& func_name) const;
+
+  const FunctionInfo& GetFunctionInfo(const std::string& func_name) const;
+
+  gl::OpenGLWorkspace& workspace() const { return *workspace_; }
+
+ private:
+  std::shared_ptr<gl::OpenGLWorkspace> workspace_;
+  std::unordered_map<std::string, OpenGLShader> shaders_;
+  std::string fmt_;
+  std::unordered_map<std::string, FunctionInfo> fmap_;
+  std::unordered_map<std::string, gl::Program> programs_;
+
+  DISALLOW_COPY_AND_ASSIGN(OpenGLModuleNode);
+};
+
+class OpenGLWrappedFunc {
+ public:
+  OpenGLWrappedFunc(OpenGLModuleNode* m,
+                    std::shared_ptr<ModuleNode> sptr,
+                    std::string func_name,
+                    std::vector<size_t> arg_size,
+                    const std::vector<std::string>& thread_axis_tags);
+
+  void operator()(TVMArgs args, TVMRetValue* rv, void** void_args) const;
+
+ private:
+  // The module
+  OpenGLModuleNode* m_;
+  // resource handle
+  std::shared_ptr<ModuleNode> sptr_;
+  // The name of the function.
+  std::string func_name_;
+  // convert code for void argument
+  std::vector<size_t> arg_size_;
+  // thread axis config
+  ThreadAxisConfig thread_axis_cfg_;
+};
+
+OpenGLModuleNode::OpenGLModuleNode(
+    std::unordered_map<std::string, OpenGLShader> shaders,
+    std::string fmt,
+    std::unordered_map<std::string, FunctionInfo> fmap)
+    : workspace_(gl::OpenGLWorkspace::Global()), shaders_(std::move(shaders)),
+      fmt_(std::move(fmt)), fmap_(std::move(fmap)), programs_() {
+  CHECK_EQ(fmt_, "gl") << "Unknown OpenGL format " << fmt_;
+  for (auto &pair : shaders_) {
+    auto &func_name = pair.first;
+    auto &shader = pair.second;
+    programs_.emplace(func_name,
+                      workspace_->CreateProgram(shader.source.c_str()));
+  }
+}
+
+PackedFunc OpenGLModuleNode::GetFunction(
+    const std::string& name,
+    const std::shared_ptr<ModuleNode>& sptr_to_self) {
+  CHECK_EQ(sptr_to_self.get(), this);
+  CHECK_NE(name, symbol::tvm_module_main) << "Device function do not have main";
+
+  auto func_info_it = fmap_.find(name);
+  if (func_info_it == fmap_.end()) { return PackedFunc(); }
+  auto &func_info = func_info_it->second;
+
+  std::vector<size_t> arg_size(func_info.arg_types.size());
+  for (size_t i = 0; i < func_info.arg_types.size(); ++i) {
+    TVMType t = func_info.arg_types[i];
+    CHECK_EQ(t.lanes, 1U);
+    uint32_t bits = t.bits;
+    CHECK_EQ(bits % 8, 0U);
+    arg_size[i] = bits / 8;
+  }
+
+  // Initialize the wrapped func.
+  OpenGLWrappedFunc f(this, sptr_to_self, name, arg_size,
+                      func_info.thread_axis_tags);
+  return PackFuncVoidAddr(f, func_info.arg_types);
+}
+
+std::string OpenGLModuleNode::GetSource(const std::string& format) {
+  if (format != fmt_ && fmt_ != "gl") { return ""; }
+
+  std::ostringstream os;
+  for (auto &pair : shaders_) {
+    auto &name = pair.first;
+    auto &shader = pair.second;
+    os << "[" << name << "]" << "\n";
+    os << shader.source <<"\n";
+  }
+  return os.str();
+}
+
+void OpenGLModuleNode::SaveToFile(const std::string& file_name,
+                                  const std::string& format) {
+  std::string fmt = GetFileFormat(file_name, format);
+  CHECK_EQ(fmt, fmt_) << "Can only save to format=" << fmt_;
+  std::string meta_file = GetMetaFilePath(file_name);
+  SaveMetaDataToFile(meta_file, fmap_);
+  SaveBinaryToFile(file_name, ToJSON(shaders_));
+}
+
+void OpenGLModuleNode::SaveToBinary(dmlc::Stream* stream) {
+  stream->Write(fmt_);
+  stream->Write(fmap_);
+  stream->Write(ToJSON(shaders_));
+}
+
+const gl::Program& OpenGLModuleNode::GetProgram(
+    const std::string& func_name) const {
+  auto it = programs_.find(func_name);
+  if (it == programs_.end()) {
+    LOG(FATAL) << "Cannot find program";
+  }
+  return it->second;
+}
+
+const OpenGLShader& OpenGLModuleNode::GetShader(
+    const std::string& func_name) const {
+  auto it = shaders_.find(func_name);
+  if (it == shaders_.end()) {
+    LOG(FATAL) << "Cannot find shader";
+  }
+  return it->second;
+}
+
+const FunctionInfo& OpenGLModuleNode::GetFunctionInfo(
+    const std::string& func_name) const {
+  auto it = fmap_.find(func_name);
+  if (it == fmap_.end()) {
+    LOG(FATAL) << "Cannot find shader";
+  }
+  return it->second;
+}
+
+OpenGLWrappedFunc::OpenGLWrappedFunc(
+    OpenGLModuleNode* m,
+    std::shared_ptr<ModuleNode> sptr,
+    std::string func_name,
+    std::vector<size_t> arg_size,
+    const std::vector<std::string>& thread_axis_tags)
+    : m_(m), sptr_(std::move(sptr)), func_name_(std::move(func_name)),
+      arg_size_(std::move(arg_size)) {
+  thread_axis_cfg_.Init(arg_size_.size(), thread_axis_tags);
+}
+
+void OpenGLWrappedFunc::operator()(TVMArgs args, TVMRetValue* rv,
+                                   void** void_args) const {
+  auto &shader = m_->GetShader(func_name_);
+  auto &program = m_->GetProgram(func_name_);
+  auto &func_info = m_->GetFunctionInfo(func_name_);
+  size_t nargs = shader.arg_kinds.size();
+
+  // Must call this function before setting uniforms & input textures.
+  m_->workspace().SetCurrentProgram(program);
+
+  // Set all arguments.
+  GLuint texture_unit = 0;
+  gl::Texture* output = nullptr;
+  for (size_t i = 0; i != nargs; ++i) {
+    auto &name = shader.arg_names.at(i);
+    auto kind = shader.arg_kinds.at(i);
+    auto type = func_info.arg_types.at(i);
+    switch (kind) {
+      case OpenGLArgKind::kUniform: {
+        m_->workspace().SetUniform(program, name, type, void_args[i]);
+        break;
+      }
+      case OpenGLArgKind::kInputTexture: {
+        CHECK_EQ(type.code, kHandle) << "Type is not handle?";
+        auto texture = *static_cast<gl::Texture**>(void_args[i]);
+        m_->workspace().SetInputTexture(program, name, texture_unit, texture);
+        ++texture_unit;
+        break;
+      }
+      case OpenGLArgKind::kOutputTexture: {
+        CHECK_EQ(type.code, kHandle) << "Type is not handle?";
+        CHECK(output == nullptr) << "Can only have one output texture.";
+        output = *static_cast<gl::Texture**>(void_args[i]);
+        break;
+      }
+    }
+  }
+
+  // Set "thread_extent" uniform.
+  ThreadWorkLoad wl = thread_axis_cfg_.Extract(args);
+  std::unique_ptr<GLint> thread_extent(new GLint(wl.block_dim(0)));
+  m_->workspace().SetUniform(program, shader.thread_extent_var,
+                             TVMType{kDLInt, 32, 1},
+                             static_cast<void*>(thread_extent.get()));
+
+  m_->workspace().Render(output);
+}
+
+Module OpenGLModuleCreate(std::unordered_map<std::string, OpenGLShader> shaders,
+                          std::string fmt,
+                          std::unordered_map<std::string, FunctionInfo> fmap) {
+  auto n = std::make_shared<OpenGLModuleNode>(std::move(shaders),
+                                              std::move(fmt),
+                                              std::move(fmap));
+  return Module(n);
+}
+
+Module OpenGLModuleLoadFile(const std::string& file_name,
+                            const std::string& format) {
+  std::string data;
+  std::unordered_map<std::string, FunctionInfo> fmap;
+  std::string fmt = GetFileFormat(file_name, format);
+  std::string meta_file = GetMetaFilePath(file_name);
+  LoadBinaryFromFile(file_name, &data);
+  LoadMetaDataFromFile(meta_file, &fmap);
+  return OpenGLModuleCreate(FromJSON(data), fmt, fmap);
+}
+
+Module OpenGLModuleLoadBinary(void* strm) {
+  auto stream = static_cast<dmlc::Stream*>(strm);
+  std::string data;
+  std::unordered_map<std::string, FunctionInfo> fmap;
+  std::string fmt;
+  stream->Read(&fmt);
+  stream->Read(&fmap);
+  stream->Read(&data);
+  return OpenGLModuleCreate(FromJSON(data), fmt, fmap);
+}
+
+TVM_REGISTER_GLOBAL("module.loadfile_gl")
+  .set_body([](TVMArgs args, TVMRetValue* rv) {
+    *rv = OpenGLModuleLoadFile(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("module.loadfile_glbin")
+  .set_body([](TVMArgs args, TVMRetValue* rv) {
+    *rv = OpenGLModuleLoadFile(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("module.loadbinary_opengl")
+  .set_body([](TVMArgs args, TVMRetValue* rv) {
+    *rv = OpenGLModuleLoadBinary(args[0]);
+  });
+
+}  // namespace runtime
+}  // namespace tvm
+
+#endif  // TVM_OPENGL_RUNTIME
diff --git a/src/runtime/opengl/opengl_module.h b/src/runtime/opengl/opengl_module.h
new file mode 100644
index 000000000000..1913878c3d68
--- /dev/null
+++ b/src/runtime/opengl/opengl_module.h
@@ -0,0 +1,148 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file opengl_module.h
+ * \brief Execution handling of OpenGL kernels
+ */
+#ifndef TVM_RUNTIME_OPENGL_OPENGL_MODULE_H_
+#define TVM_RUNTIME_OPENGL_OPENGL_MODULE_H_
+
+#include <tvm/runtime/config.h>
+#include <tvm/runtime/packed_func.h>
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <vector>
+#include "../meta_data.h"
+
+namespace tvm {
+namespace runtime {
+
+/*!
+ * \brief Determines how we supply arguments.
+ */
+enum class OpenGLArgKind {
+  kInputTexture = 0,   // Bind to "gsampler2D" in GLSL.
+  kOutputTexture = 1,  // Bind to "out" in GLSL.
+  kUniform = 2,        // Bind to "uniform" in GLSL.
+};
+
+std::string OpenGLArgKind2String(OpenGLArgKind kind);
+OpenGLArgKind String2OpenGLArgKind(const std::string& str);
+
+/*!
+ * \brief The output of OpenGL codegen.
+ * Contains necessary information to build a fragment shader and bind arguments.
+ */
+struct OpenGLShader {
+  OpenGLShader() = default;
+  OpenGLShader(std::string source,
+               std::vector<std::string> arg_names,
+               std::vector<OpenGLArgKind> arg_kinds,
+               std::string thread_extent_var)
+      : source(std::move(source)), arg_names(std::move(arg_names)),
+        arg_kinds(std::move(arg_kinds)),
+        thread_extent_var(std::move(thread_extent_var)) {
+    CHECK_EQ(this->arg_names.size(), this->arg_kinds.size()) << "Invalid input";
+  }
+
+  std::string source;
+  std::vector<std::string> arg_names;    // Matches FunctionInfo.
+  std::vector<OpenGLArgKind> arg_kinds;  // Matches FunctionInfo.
+  std::string thread_extent_var;         // Stores the output length.
+
+  void Save(dmlc::JSONWriter* writer) const;
+  void Load(dmlc::JSONReader* reader);
+};
+
+std::string ToJSON(const std::unordered_map<std::string, OpenGLShader>& shaders);
+std::unordered_map<std::string, OpenGLShader> FromJSON(const std::string& str);
+
+/*!
+ * \brief Create an OpenGL module from data.
+ *
+ * \param data The module data.
+ * \param fmt The format of the data,
+ * \param fmap The map function information map of each function.
+ */
+Module OpenGLModuleCreate(std::unordered_map<std::string, OpenGLShader> shaders,
+                          std::string fmt,
+                          std::unordered_map<std::string, FunctionInfo> fmap);
+
+inline std::string OpenGLArgKind2String(OpenGLArgKind kind) {
+  switch (kind) {
+    case OpenGLArgKind::kOutputTexture:
+      return "output_texture";
+    case OpenGLArgKind::kInputTexture:
+      return "input_texture";
+    case OpenGLArgKind::kUniform:
+      return "uniform";
+  }
+  assert(false);
+}
+
+inline OpenGLArgKind String2OpenGLArgKind(const std::string& str) {
+  if (str == "output_texture") {
+    return OpenGLArgKind::kOutputTexture;
+  } else if (str == "input_texture") {
+    return OpenGLArgKind::kInputTexture;
+  } else if (str == "uniform") {
+    return OpenGLArgKind::kUniform;
+  } else {
+    LOG(FATAL) << "Invalid OpenGL arg kind.";
+    assert(false);
+  }
+}
+
+inline void OpenGLShader::Save(dmlc::JSONWriter* writer) const {
+  std::vector<std::string> arg_kind_strs;
+  for (auto kind : arg_kinds) {
+    arg_kind_strs.push_back(OpenGLArgKind2String(kind));
+  }
+
+  writer->BeginObject();
+  writer->WriteObjectKeyValue("arg_names", arg_names);
+  writer->WriteObjectKeyValue("arg_kinds", arg_kind_strs);
+  writer->WriteObjectKeyValue("source", source);
+  writer->WriteObjectKeyValue("thread_extent_var", thread_extent_var);
+  writer->EndObject();
+}
+
+inline void OpenGLShader::Load(dmlc::JSONReader* reader) {
+  std::vector<std::string> arg_kind_strs;
+  dmlc::JSONObjectReadHelper helper;
+  helper.DeclareField("arg_names", &arg_names);
+  helper.DeclareField("arg_kinds", &arg_kind_strs);
+  helper.DeclareField("source", &source);
+  helper.DeclareField("thread_extent_var", &thread_extent_var);
+  helper.ReadAllFields(reader);
+
+  arg_kinds.clear();
+  for (auto& str : arg_kind_strs) {
+    arg_kinds.push_back(String2OpenGLArgKind(str));
+  }
+}
+
+inline std::string ToJSON(
+    const std::unordered_map<std::string, OpenGLShader>& shaders) {
+  std::ostringstream os;
+  dmlc::JSONWriter writer(&os);
+  writer.BeginObject();
+  writer.WriteObjectKeyValue("shaders", shaders);
+  writer.EndObject();
+  return os.str();
+}
+
+inline std::unordered_map<std::string, OpenGLShader> FromJSON(
+    const std::string& str) {
+  std::unordered_map<std::string, OpenGLShader> shaders;
+  std::istringstream is(str);
+  dmlc::JSONReader reader(&is);
+  dmlc::JSONObjectReadHelper helper;
+  helper.DeclareField("shaders", &shaders);
+  helper.ReadAllFields(&reader);
+  return shaders;
+}
+
+}  // namespace runtime
+}  // namespace tvm
+#endif  // TVM_RUNTIME_OPENGL_OPENGL_MODULE_H_
diff --git a/src/runtime/rocm/rocm_device_api.cc b/src/runtime/rocm/rocm_device_api.cc
index 1e6154163b35..443d76b76eb6 100644
--- a/src/runtime/rocm/rocm_device_api.cc
+++ b/src/runtime/rocm/rocm_device_api.cc
@@ -52,12 +52,15 @@ class ROCMDeviceAPI final : public DeviceAPI {
     }
     *rv = value;
   }
-  void* AllocDataSpace(TVMContext ctx, size_t size, size_t alignment) final {
+  void* AllocDataSpace(TVMContext ctx,
+                       size_t nbytes,
+                       size_t alignment,
+                       TVMType type_hint) final {
     ROCM_CALL(hipSetDevice(ctx.device_id));
     CHECK_EQ(256 % alignment, 0U)
         << "ROCM space is aligned at 256 bytes";
     void *ret;
-    ROCM_CALL(hipMalloc(&ret, size));
+    ROCM_CALL(hipMalloc(&ret, nbytes));
     return ret;
   }
 
diff --git a/src/runtime/rpc/rpc_device_api.cc b/src/runtime/rpc/rpc_device_api.cc
index 7674fa3e2334..bd222b5c272e 100644
--- a/src/runtime/rpc/rpc_device_api.cc
+++ b/src/runtime/rpc/rpc_device_api.cc
@@ -20,10 +20,13 @@ class RPCDeviceAPI final : public DeviceAPI {
     *rv = GetSess(ctx)->CallRemote(
         RPCCode::kDevGetAttr, ctx, static_cast<int>(kind));
   }
-  void* AllocDataSpace(TVMContext ctx, size_t size, size_t alignment) final {
+  void* AllocDataSpace(TVMContext ctx,
+                       size_t nbytes,
+                       size_t alignment,
+                       TVMType type_hint) final {
     auto sess = GetSess(ctx);
     void *data = sess->CallRemote(
-            RPCCode::kDevAllocData, ctx, size, alignment);
+            RPCCode::kDevAllocData, ctx, nbytes, alignment, type_hint);
     RemoteSpace* space = new RemoteSpace();
     space->data = data;
     space->sess = std::move(sess);
diff --git a/src/runtime/rpc/rpc_session.cc b/src/runtime/rpc/rpc_session.cc
index 0fa021918ed2..3bff73afe9bd 100644
--- a/src/runtime/rpc/rpc_session.cc
+++ b/src/runtime/rpc/rpc_session.cc
@@ -887,9 +887,11 @@ void RPCDevGetAttr(TVMArgs args, TVMRetValue *rv) {
 
 void RPCDevAllocData(TVMArgs args, TVMRetValue *rv) {
   TVMContext ctx = args[0];
-  uint64_t size = args[1];
+  uint64_t nbytes = args[1];
   uint64_t alignment = args[2];
-  void* data = DeviceAPI::Get(ctx)->AllocDataSpace(ctx, size, alignment);
+  TVMType type_hint = args[3];
+  void* data = DeviceAPI::Get(ctx)->AllocDataSpace(
+      ctx, nbytes, alignment, type_hint);
   *rv = data;
 }
 
diff --git a/src/runtime/workspace_pool.cc b/src/runtime/workspace_pool.cc
index 494927979a0f..24035faedaa7 100644
--- a/src/runtime/workspace_pool.cc
+++ b/src/runtime/workspace_pool.cc
@@ -23,28 +23,29 @@ class WorkspacePool::Pool {
     allocated_.push_back(e);
   }
   // allocate from pool
-  void* Alloc(TVMContext ctx, DeviceAPI* device, size_t size) {
+  void* Alloc(TVMContext ctx, DeviceAPI* device, size_t nbytes) {
     // Allocate align to page.
-    size = (size + (kWorkspacePageSize - 1)) / kWorkspacePageSize * kWorkspacePageSize;
-    if (size == 0) size = kWorkspacePageSize;
+    nbytes = (nbytes + (kWorkspacePageSize - 1)) / kWorkspacePageSize * kWorkspacePageSize;
+    if (nbytes == 0) nbytes = kWorkspacePageSize;
     Entry e;
+    TVMType type = {.code = kDLUInt, .bits = 8, .lanes = 1};
     if (free_list_.size() == 2) {
       e = free_list_.back();
       free_list_.pop_back();
-      if (e.size < size) {
+      if (e.size < nbytes) {
         // resize the page
         device->FreeDataSpace(ctx, e.data);
-        e.data = device->AllocDataSpace(ctx, size, kTempAllocaAlignment);
-        e.size = size;
+        e.data = device->AllocDataSpace(ctx, nbytes, kTempAllocaAlignment, type);
+        e.size = nbytes;
       }
     } else if (free_list_.size() == 1) {
-      e.data = device->AllocDataSpace(ctx, size, kTempAllocaAlignment);
-      e.size = size;
+      e.data = device->AllocDataSpace(ctx, nbytes, kTempAllocaAlignment, type);
+      e.size = nbytes;
     } else {
-      if (free_list_.back().size >= size) {
+      if (free_list_.back().size >= nbytes) {
         // find smallest fit
         auto it = free_list_.end() - 2;
-        for (; it->size >= size; --it) {}
+        for (; it->size >= nbytes; --it) {}
         e = *(it + 1);
         free_list_.erase(it + 1);
       } else {
@@ -52,8 +53,8 @@ class WorkspacePool::Pool {
         e = free_list_.back();
         free_list_.pop_back();
         device->FreeDataSpace(ctx, e.data);
-        e.data = device->AllocDataSpace(ctx, size, kTempAllocaAlignment);
-        e.size = size;
+        e.data = device->AllocDataSpace(ctx, nbytes, kTempAllocaAlignment, type);
+        e.size = nbytes;
       }
     }
     allocated_.push_back(e);
diff --git a/src/schedule/schedule_lang.cc b/src/schedule/schedule_lang.cc
index f8fcb8b0c744..59bc3f242b03 100644
--- a/src/schedule/schedule_lang.cc
+++ b/src/schedule/schedule_lang.cc
@@ -397,6 +397,45 @@ Stage& Stage::double_buffer() {
   return *this;
 }
 
+Stage& Stage::opengl() {
+  CHECK(!is_scheduled()) << "Must be a fresh schedule";
+  StageNode *self = operator->();
+
+  auto all_iter_vars = self->all_iter_vars;  // curr version of all_iter_vars
+  CHECK(!all_iter_vars.empty()) << "At least one iter var";
+
+  // Fuse all data parallel dimensions to 1.
+  IterVar fused = all_iter_vars[0];
+  for (size_t i = 1; i != all_iter_vars.size(); ++i) {
+    auto iter_var = all_iter_vars[i];
+    switch (iter_var->iter_type) {
+      case IterVarType::kDataPar: {
+        fuse(fused, all_iter_vars[i], &fused);
+        break;
+      }
+      case IterVarType::kThreadIndex: {
+        LOG(ERROR) << "A fresh schedule shouldn't have thread index iter var";
+        break;
+      }
+      case IterVarType::kCommReduce:
+      case IterVarType::kOrdered:
+      case IterVarType::kOpaque: {
+        break;
+      }
+      default: {
+        LOG(ERROR) << "Invalid iter var type "
+                   << IterVarType2String(iter_var->iter_type);
+        break;
+      }
+    }
+  }
+
+  // Bind the only dimension to threadIdx.x.
+  bind(fused, thread_axis(Range(nullptr), "threadIdx.x"));
+
+  return *this;
+}
+
 Stage CopyStage(const Stage& s) {
   std::shared_ptr<StageNode> n =
       std::make_shared<StageNode>(*s.operator->());
diff --git a/tests/ci_build/Dockerfile.cpu b/tests/ci_build/Dockerfile.cpu
index 2c7510365f9d..57fb7e04552b 100644
--- a/tests/ci_build/Dockerfile.cpu
+++ b/tests/ci_build/Dockerfile.cpu
@@ -20,3 +20,6 @@ RUN bash /install/ubuntu_install_java.sh
 
 COPY install/ubuntu_install_llvm.sh /install/ubuntu_install_llvm.sh
 RUN bash /install/ubuntu_install_llvm.sh
+
+COPY install/ubuntu_install_opengl.sh /install/ubuntu_install_opengl.sh
+RUN bash /install/ubuntu_install_opengl.sh
diff --git a/tests/ci_build/Dockerfile.gpu b/tests/ci_build/Dockerfile.gpu
index 9dff84e84635..b71b4cb118ec 100644
--- a/tests/ci_build/Dockerfile.gpu
+++ b/tests/ci_build/Dockerfile.gpu
@@ -37,6 +37,9 @@ RUN bash /install/ubuntu_install_nodejs.sh
 COPY install/ubuntu_install_rocm.sh /install/ubuntu_install_rocm.sh
 RUN bash /install/ubuntu_install_rocm.sh
 
+COPY install/ubuntu_install_opengl.sh /install/ubuntu_install_opengl.sh
+RUN bash /install/ubuntu_install_opengl.sh
+
 # Enable doxygen for c++ doc build
 RUN apt-get install -y doxygen graphviz
 
diff --git a/tests/ci_build/install/ubuntu_install_opengl.sh b/tests/ci_build/install/ubuntu_install_opengl.sh
new file mode 100644
index 000000000000..f8be6e351581
--- /dev/null
+++ b/tests/ci_build/install/ubuntu_install_opengl.sh
@@ -0,0 +1,4 @@
+apt-get update --fix-missing
+
+apt-get install -y --no-install-recommends --force-yes \
+        libgl1-mesa-dev libglfw3-dev
\ No newline at end of file
diff --git a/tests/python/unittest/test_runtime_ndarray.py b/tests/python/unittest/test_runtime_ndarray.py
index 698f877d2504..5edf43337de5 100644
--- a/tests/python/unittest/test_runtime_ndarray.py
+++ b/tests/python/unittest/test_runtime_ndarray.py
@@ -7,7 +7,8 @@ def enabled_ctx_list():
                 ('cl', tvm.opencl(0)),
                 ('metal', tvm.metal(0)),
                 ('rocm', tvm.rocm(0)),
-                ('vpi', tvm.vpi(0))]
+                ('vpi', tvm.vpi(0)),
+                ('opengl', tvm.opengl(0))]
     for k, v  in ctx_list:
         assert tvm.context(k, 0) == v
     ctx_list = [x[1] for x in ctx_list if x[1].exist]
@@ -19,7 +20,8 @@ def enabled_ctx_list():
 
 def test_nd_create():
     for ctx in ENABLED_CTX_LIST:
-        for dtype in ["float32", "int8", "uint16"]:
+        for dtype in ["uint8", "int8", "uint16", "int16", "uint32", "int32",
+                      "float32"]:
             x = np.random.randint(0, 10, size=(3, 4))
             x = np.array(x, dtype=dtype)
             y = tvm.nd.array(x, ctx=ctx)
diff --git a/tests/scripts/task_python_integration.sh b/tests/scripts/task_python_integration.sh
index 757f2429ad32..70c2919f6fd0 100755
--- a/tests/scripts/task_python_integration.sh
+++ b/tests/scripts/task_python_integration.sh
@@ -17,3 +17,5 @@ TVM_FFI=cython python -m nose -v tests/python/integration || exit -1
 TVM_FFI=ctypes python3 -m nose -v tests/python/integration || exit -1
 TVM_FFI=cython python -m nose -v tests/python/contrib || exit -1
 TVM_FFI=ctypes python3 -m nose -v tests/python/contrib || exit -1
+TVM_FFI=cython python -m nose -v tests/webgl || exit -1
+TVM_FFI=ctypes python3 -m nose -v tests/webgl || exit -1
diff --git a/tests/webgl/README.md b/tests/webgl/README.md
new file mode 100644
index 000000000000..c9f2ae3d2272
--- /dev/null
+++ b/tests/webgl/README.md
@@ -0,0 +1,7 @@
+## Test cases for the WebGL backend
+
+Any test case with name `test_local_...` tests the C++ OpenGL backend on the
+local OS, which can be executed automatically.
+
+Any test case with name `test_remote_...` tests the WebGL backend within the
+browser, which must be run manually. See instruction within the test.
diff --git a/tests/webgl/test_local_gemm.py b/tests/webgl/test_local_gemm.py
new file mode 100644
index 000000000000..18d2d1d8bf34
--- /dev/null
+++ b/tests/webgl/test_local_gemm.py
@@ -0,0 +1,41 @@
+import tvm
+import numpy as np
+
+def test_local_gemm():
+    if not tvm.module.enabled("opengl"):
+        return
+    if not tvm.module.enabled("llvm"):
+        return
+
+    nn = 2
+    n = tvm.var('n')
+    n = tvm.convert(nn)
+    m = n
+    l = n
+    A = tvm.placeholder((n, l), name='A', dtype='int32')
+    B = tvm.placeholder((m, l), name='B', dtype='int32')
+    k = tvm.reduce_axis((0, l), name='k')
+    C = tvm.compute((n, m), lambda ii, jj: tvm.sum(A[ii, k] * B[jj, k], axis=k),
+                    name='CC')
+
+    s = tvm.create_schedule(C.op)
+    s[C].opengl()
+    print(tvm.lower(s, [A, B, C], simple_mode=True))
+
+    f = tvm.build(s, [A, B, C], "opengl", name="gemm")
+    print("------opengl code------")
+    print(f.imported_modules[0].get_source(fmt="gl"))
+
+    ctx = tvm.opengl()
+    n, m, l = nn, nn, nn
+    a_np = np.random.uniform(low=0, high=10, size=(n, l)).astype(A.dtype)
+    b_np = np.random.uniform(low=0, high=10, size=(m, l)).astype(B.dtype)
+    a = tvm.nd.array(a_np, ctx)
+    b = tvm.nd.array(b_np, ctx)
+    c = tvm.nd.array(np.zeros((n, m), dtype=C.dtype), ctx)
+    f(a, b, c)
+
+    np.testing.assert_allclose(c.asnumpy(), np.dot(a_np, b_np.T))
+
+if __name__ == "__main__":
+    test_local_gemm()
diff --git a/tests/webgl/test_local_save_load.py b/tests/webgl/test_local_save_load.py
new file mode 100644
index 000000000000..9dca0d3acfca
--- /dev/null
+++ b/tests/webgl/test_local_save_load.py
@@ -0,0 +1,35 @@
+import numpy as np
+import tvm
+from tvm.contrib import rpc, util, emscripten
+
+def test_local_save_load():
+    if not tvm.module.enabled("opengl"):
+        return
+    if not tvm.module.enabled("llvm"):
+        return
+
+    n = tvm.var("n")
+    A = tvm.placeholder((n,), name='A', dtype='int32')
+    B = tvm.placeholder((n,), name='B', dtype='int32')
+    C = tvm.compute(A.shape, lambda i: A[i] + B[i], name="C")
+    s = tvm.create_schedule(C.op)
+    s[C].opengl()
+
+    f = tvm.build(s, [A, B, C], "opengl", target_host="llvm", name="myadd")
+
+    ctx = tvm.opengl(0)
+    n = 10
+    a = tvm.nd.array(np.random.uniform(high=10, size=(n)).astype(A.dtype), ctx)
+    b = tvm.nd.array(np.random.uniform(high=10, size=(n)).astype(B.dtype), ctx)
+    c = tvm.nd.array(np.zeros((n), dtype=C.dtype), ctx)
+    f(a, b, c)
+
+    temp = util.tempdir()
+    path_so = temp.relpath("myadd.so")
+    f.export_library(path_so)
+    f1 = tvm.module.load(path_so)
+    f1(a, b, c)
+    np.testing.assert_allclose(c.asnumpy(), a.asnumpy() + b.asnumpy())
+
+if __name__ == "__main__":
+    test_local_save_load()
diff --git a/tests/webgl/test_remote_save_load.py b/tests/webgl/test_remote_save_load.py
new file mode 100644
index 000000000000..507616ee9f2e
--- /dev/null
+++ b/tests/webgl/test_remote_save_load.py
@@ -0,0 +1,78 @@
+"""
+The following instruction is based on web/README.md.
+
+Setup an RPC server:
+$ python -m tvm.exec.rpc_proxy --example-rpc=1
+
+Go to http://localhost:9190 in browser.
+
+Click "Connect To Proxy".
+
+Run this test script:
+$ python tests/webgl/test_remote_save_load.py
+"""
+
+import numpy as np
+import tvm
+from tvm.contrib import rpc, util, emscripten
+
+proxy_host = "localhost"
+proxy_port = 9090
+
+def try_remote_save_load():
+    if not tvm.module.enabled("rpc"):
+        return
+    if not tvm.module.enabled("opengl"):
+        return
+    if not tvm.module.enabled("llvm"):
+        return
+
+    # Build the module.
+    n = tvm.var("n")
+    A = tvm.placeholder((n,), name='A')
+    B = tvm.placeholder((n,), name='B')
+    C = tvm.compute(A.shape, lambda i: A[i] + B[i], name="C")
+    s = tvm.create_schedule(C.op)
+    s[C].opengl()
+    target_host = "llvm -target=asmjs-unknown-emscripten -system-lib"
+    f = tvm.build(s, [A, B, C], "opengl", target_host=target_host, name="myadd")
+
+    remote = rpc.connect(proxy_host, proxy_port, key="js")
+
+    temp = util.tempdir()
+    ctx = remote.opengl(0)
+    path_obj = temp.relpath("myadd.bc")
+    path_dso = temp.relpath("myadd.js")
+    path_gl = temp.relpath("myadd.gl")
+    path_json = temp.relpath("myadd.tvm_meta.json")
+
+    f.save(path_obj)
+    emscripten.create_js(path_dso, path_obj, side_module=True)
+    f.imported_modules[0].save(path_gl)
+
+    remote.upload(path_dso, "myadd.dso")
+    remote.upload(path_gl)
+    remote.upload(path_json)
+
+    remote.download("myadd.dso")
+    remote.download("myadd.gl")
+    remote.download("myadd.tvm_meta.json")
+
+    print('Loading myadd.dso')
+    fhost = remote.load_module("myadd.dso")
+
+    print('Loading myadd.gl')
+    fdev = remote.load_module("myadd.gl")
+
+    print('import_module')
+    fhost.import_module(fdev)
+
+    print('running...')
+    a = tvm.nd.array(np.random.uniform(size=16).astype(A.dtype), ctx)
+    b = tvm.nd.array(np.zeros(16, dtype=A.dtype), ctx)
+    c = tvm.nd.array(np.zeros(16, dtype=C.dtype), ctx)
+    fhost(a, b, c)
+    np.testing.assert_allclose(c.asnumpy(), a.asnumpy() + b.asnumpy())
+
+if __name__ == "__main__":
+    try_remote_save_load()
diff --git a/web/example_rpc.html b/web/example_rpc.html
index bcccbef7358f..b23ecda8e017 100644
--- a/web/example_rpc.html
+++ b/web/example_rpc.html
@@ -36,5 +36,9 @@ <h2>Options</h2>
    <button onclick="connect_rpc()">Connect To Proxy</button>
    <button onclick="clear_log()">Clear Log</button>
    <div id="log"></div>
+   <canvas id="canvas"></canvas>
+   <script>
+    Module["canvas"] = document.getElementById("canvas");
+   </script>
  </body>
 </html>
diff --git a/web/tvm_runtime.js b/web/tvm_runtime.js
index f69b1d57a11d..c23c1f9da796 100644
--- a/web/tvm_runtime.js
+++ b/web/tvm_runtime.js
@@ -686,7 +686,8 @@ var tvm_runtime = tvm_runtime || {};
       2 : "gpu",
       4 : "opencl",
       8 : "metal",
-      9 : "vpi"
+      9 : "vpi",
+      11 : "opengl",
     };
     var CTX_STR2MASK = {
       "cpu": 1,
@@ -695,7 +696,8 @@ var tvm_runtime = tvm_runtime || {};
       "cl": 4,
       "opencl": 4,
       "metal": 8,
-      "vpi": 9
+      "vpi": 9,
+      "opengl": 11,
     };
     TVMContext.prototype = {
       toString : function() {
diff --git a/web/web_runtime.cc b/web/web_runtime.cc
index 56538733025b..b8db8cce7006 100644
--- a/web/web_runtime.cc
+++ b/web/web_runtime.cc
@@ -18,6 +18,8 @@
 #include "../src/runtime/rpc/rpc_event_impl.cc"
 #include "../src/runtime/rpc/rpc_server_env.cc"
 #include "../src/runtime/graph/graph_runtime.cc"
+#include "../src/runtime/opengl/opengl_device_api.cc"
+#include "../src/runtime/opengl/opengl_module.cc"
 
 namespace tvm {
 namespace contrib {

From 8356388c86df2b324ca234e18746a37fa7c394dd Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 21 Jan 2018 15:17:26 -0800
Subject: [PATCH 095/949] Disable OpenGL test temporary (#801)

---
 tests/scripts/task_python_integration.sh | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/tests/scripts/task_python_integration.sh b/tests/scripts/task_python_integration.sh
index 70c2919f6fd0..d10c9a6b127f 100755
--- a/tests/scripts/task_python_integration.sh
+++ b/tests/scripts/task_python_integration.sh
@@ -17,5 +17,7 @@ TVM_FFI=cython python -m nose -v tests/python/integration || exit -1
 TVM_FFI=ctypes python3 -m nose -v tests/python/integration || exit -1
 TVM_FFI=cython python -m nose -v tests/python/contrib || exit -1
 TVM_FFI=ctypes python3 -m nose -v tests/python/contrib || exit -1
-TVM_FFI=cython python -m nose -v tests/webgl || exit -1
-TVM_FFI=ctypes python3 -m nose -v tests/webgl || exit -1
+
+# Do not enabke OpenGL
+# TVM_FFI=cython python -m nose -v tests/webgl || exit -1
+# TVM_FFI=ctypes python3 -m nose -v tests/webgl || exit -1

From b8128aabc7f960b37096a26f1d3fdb9dd78a1b83 Mon Sep 17 00:00:00 2001
From: Zhixun Tan <phisiart@gmail.com>
Date: Mon, 22 Jan 2018 12:58:48 -0500
Subject: [PATCH 096/949] temporarily disable opengl in test_runtime_ndarray.py
 (#804)

---
 tests/python/unittest/test_runtime_ndarray.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/tests/python/unittest/test_runtime_ndarray.py b/tests/python/unittest/test_runtime_ndarray.py
index 5edf43337de5..a03a33b7af08 100644
--- a/tests/python/unittest/test_runtime_ndarray.py
+++ b/tests/python/unittest/test_runtime_ndarray.py
@@ -7,8 +7,7 @@ def enabled_ctx_list():
                 ('cl', tvm.opencl(0)),
                 ('metal', tvm.metal(0)),
                 ('rocm', tvm.rocm(0)),
-                ('vpi', tvm.vpi(0)),
-                ('opengl', tvm.opengl(0))]
+                ('vpi', tvm.vpi(0))]
     for k, v  in ctx_list:
         assert tvm.context(k, 0) == v
     ctx_list = [x[1] for x in ctx_list if x[1].exist]

From 9651bc9378d3eff2694d60bb927b7e583f6fe202 Mon Sep 17 00:00:00 2001
From: Siju Samuel <sijusamuel@gmail.com>
Date: Mon, 22 Jan 2018 23:29:15 +0530
Subject: [PATCH 097/949] Update inject_virtual_thread.cc (#806)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This compilation warning is fixed.
src/pass/inject_virtual_thread.cc:43:19: warning: ‘rw_mask’ may be used uninitialized in this function [-Wmaybe-uninitialized]
       if (rw_mask & 2) {
           ~~~~~~~~^~~
---
 src/pass/inject_virtual_thread.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/pass/inject_virtual_thread.cc b/src/pass/inject_virtual_thread.cc
index bcf0e3d9fbaf..c0e7b4b3ff9e 100644
--- a/src/pass/inject_virtual_thread.cc
+++ b/src/pass/inject_virtual_thread.cc
@@ -32,7 +32,7 @@ class ExprTouched final : public IRVisitor {
   }
   void Visit_(const Call *op) final {
     if (op->is_intrinsic(intrinsic::tvm_access_ptr)) {
-      int rw_mask;
+      int rw_mask = 0;
       CHECK(arith::GetConstInt(op->args[4], &rw_mask));
       const Variable* buffer_var = op->args[1].as<Variable>();
       CHECK(buffer_var);

From 883df333a326f2ad0b2265411268c34b6f12745e Mon Sep 17 00:00:00 2001
From: Clouds <Clouds.Flowing@gmail.com>
Date: Tue, 23 Jan 2018 02:00:08 +0800
Subject: [PATCH 098/949] Update setup.py (#803)

fix errors when running `python3 setup.py sdist bdist_wheel`
---
 python/setup.py | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/python/setup.py b/python/setup.py
index 5a87325e9a1a..188938d76c3d 100644
--- a/python/setup.py
+++ b/python/setup.py
@@ -18,11 +18,12 @@
     from setuptools import setup
     from setuptools.extension import Extension
 
+CURRENT_DIR = os.path.dirname(__file__)
+
 def get_lib_path():
     """Get library path, name and version"""
     # We can not import `libinfo.py` in setup.py directly since __init__.py
     # Will be invoked which introduces dependences
-    CURRENT_DIR = os.path.dirname(__file__)
     libinfo_py = os.path.join(CURRENT_DIR, './tvm/_ffi/libinfo.py')
     libinfo = {'__file__': libinfo_py}
     exec(compile(open(libinfo_py, "rb").read(), libinfo_py, 'exec'), libinfo, libinfo)
@@ -126,4 +127,4 @@ def is_pure(self):
     os.remove("MANIFEST.in")
     for path in LIB_LIST:
         _, libname = os.path.split(path)
-        os.remove("tvm/%s" % LIB_NAME)
+        os.remove("tvm/%s" % libname)

From 78e00410e5bdf5f4428e39285cac75592233a813 Mon Sep 17 00:00:00 2001
From: Siva <sivar.b@huawei.com>
Date: Mon, 22 Jan 2018 23:30:47 +0530
Subject: [PATCH 099/949] [DOC] Generalize the get_started script for beginners
 with different environments. (#798)

---
 tutorials/get_started.py | 66 ++++++++++++++++++++++++----------------
 1 file changed, 40 insertions(+), 26 deletions(-)

diff --git a/tutorials/get_started.py b/tutorials/get_started.py
index 3dce21a64950..2c6165940d05 100644
--- a/tutorials/get_started.py
+++ b/tutorials/get_started.py
@@ -13,6 +13,12 @@
 import tvm
 import numpy as np
 
+# Global declarations of environment.
+
+tgt_host="llvm"
+# Change it to respective GPU if gpu is enabled Ex: cuda, opencl
+tgt="cuda"
+
 ######################################################################
 # Vector Add Example
 # ------------------
@@ -88,8 +94,9 @@
 # compute grid. These are GPU specific constructs that allows us
 # to generate code that runs on GPU.
 #
-s[C].bind(bx, tvm.thread_axis("blockIdx.x"))
-s[C].bind(tx, tvm.thread_axis("threadIdx.x"))
+if tgt == "cuda":
+  s[C].bind(bx, tvm.thread_axis("blockIdx.x"))
+  s[C].bind(tx, tvm.thread_axis("threadIdx.x"))
 
 ######################################################################
 # Compilation
@@ -103,12 +110,12 @@
 # function(including the inputs and outputs) as well as target language
 # we want to compile to.
 #
-# The result of compilation fadd is a CUDA device function that can
-# as well as a host wrapper that calls into the CUDA function.
+# The result of compilation fadd is a GPU device function(if GPU is involved) 
+# that can as well as a host wrapper that calls into the GPU function.
 # fadd is the generated host wrapper function, it contains reference
 # to the generated device function internally.
 #
-fadd_cuda = tvm.build(s, [A, B, C], "cuda", target_host="llvm", name="myadd")
+fadd = tvm.build(s, [A, B, C], tgt, target_host=tgt_host, name="myadd")
 
 ######################################################################
 # Run the Function
@@ -124,12 +131,13 @@
 # - fadd runs the actual computation.
 # - asnumpy() copies the gpu array back to cpu and we can use this to verify correctness
 #
-ctx = tvm.gpu(0)
+ctx = tvm.context(tgt, 0)
+
 n = 1024
 a = tvm.nd.array(np.random.uniform(size=n).astype(A.dtype), ctx)
 b = tvm.nd.array(np.random.uniform(size=n).astype(B.dtype), ctx)
 c = tvm.nd.array(np.zeros(n, dtype=C.dtype), ctx)
-fadd_cuda(a, b, c)
+fadd(a, b, c)
 np.testing.assert_allclose(c.asnumpy(), a.asnumpy() + b.asnumpy())
 
 ######################################################################
@@ -137,13 +145,16 @@
 # --------------------------
 # You can inspect the generated code in TVM. The result of tvm.build
 # is a tvm Module. fadd is the host module that contains the host wrapper,
-# it also contains a device module for the CUDA function.
+# it also contains a device module for the CUDA (GPU) function.
 #
 # The following code fetches the device module and prints the content code.
 #
-dev_module = fadd_cuda.imported_modules[0]
-print("-----CUDA code-----")
-print(dev_module.get_source())
+if tgt == "cuda":
+    dev_module = fadd.imported_modules[0]
+    print("-----GPU code-----")
+    print(dev_module.get_source())
+else:
+    print(fadd.get_source())
 
 ######################################################################
 # .. note:: Code Specialization
@@ -179,8 +190,9 @@
 from tvm.contrib import util
 
 temp = util.tempdir()
-fadd_cuda.save(temp.relpath("myadd.o"))
-fadd_cuda.imported_modules[0].save(temp.relpath("myadd.ptx"))
+fadd.save(temp.relpath("myadd.o"))
+if tgt == "cuda":
+    fadd.imported_modules[0].save(temp.relpath("myadd.ptx"))
 cc.create_shared(temp.relpath("myadd.so"), [temp.relpath("myadd.o")])
 print(temp.listdir())
 
@@ -201,8 +213,9 @@
 # re-link them together. We can verify that the newly loaded function works.
 #
 fadd1 = tvm.module.load(temp.relpath("myadd.so"))
-fadd1_dev = tvm.module.load(temp.relpath("myadd.ptx"))
-fadd1.import_module(fadd1_dev)
+if tgt == "cuda":
+    fadd1_dev = tvm.module.load(temp.relpath("myadd.ptx"))
+    fadd1.import_module(fadd1_dev)
 fadd1(a, b, c)
 np.testing.assert_allclose(c.asnumpy(), a.asnumpy() + b.asnumpy())
 
@@ -215,7 +228,7 @@
 # them together with the host code.
 # Currently we support packing of Metal, OpenCL and CUDA modules.
 #
-fadd_cuda.export_library(temp.relpath("myadd_pack.so"))
+fadd.export_library(temp.relpath("myadd_pack.so"))
 fadd2 = tvm.module.load(temp.relpath("myadd_pack.so"))
 fadd2(a, b, c)
 np.testing.assert_allclose(c.asnumpy(), a.asnumpy() + b.asnumpy())
@@ -241,16 +254,17 @@
 # The following codeblocks generate opencl code, creates array on opencl
 # device, and verifies the correctness of the code.
 #
-fadd_cl = tvm.build(s, [A, B, C], "opencl", name="myadd")
-print("------opencl code------")
-print(fadd_cl.imported_modules[0].get_source())
-ctx = tvm.cl(0)
-n = 1024
-a = tvm.nd.array(np.random.uniform(size=n).astype(A.dtype), ctx)
-b = tvm.nd.array(np.random.uniform(size=n).astype(B.dtype), ctx)
-c = tvm.nd.array(np.zeros(n, dtype=C.dtype), ctx)
-fadd_cl(a, b, c)
-np.testing.assert_allclose(c.asnumpy(), a.asnumpy() + b.asnumpy())
+if tgt == "opencl":
+    fadd_cl = tvm.build(s, [A, B, C], "opencl", name="myadd")
+    print("------opencl code------")
+    print(fadd_cl.imported_modules[0].get_source())
+    ctx = tvm.cl(0)
+    n = 1024
+    a = tvm.nd.array(np.random.uniform(size=n).astype(A.dtype), ctx)
+    b = tvm.nd.array(np.random.uniform(size=n).astype(B.dtype), ctx)
+    c = tvm.nd.array(np.zeros(n, dtype=C.dtype), ctx)
+    fadd_cl(a, b, c)
+    np.testing.assert_allclose(c.asnumpy(), a.asnumpy() + b.asnumpy())
 
 ######################################################################
 # Summary

From 4077c9ff21353d19ed563dbf42512417984df00a Mon Sep 17 00:00:00 2001
From: Siju Samuel <sijusamuel@gmail.com>
Date: Tue, 23 Jan 2018 09:35:30 +0530
Subject: [PATCH 100/949] [Compilation Warning Fix] comparison between signed
 and unsigned integer expressions (#807)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The compilation warning is fixed.
src/runtime/graph/graph_runtime.cc:392:24: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
   CHECK(data_byte_size == size)
         ~~~~~~~~~~~~~~~^~~~
/mnt/D_DRIVE/work/nnvm_22_Jan/nnvm_latest/tvm/dmlc-core/include/dmlc/logging.h:109:9: note: in definition of macro ‘CHECK’
   if (!(x))                                                \
         ^
---
 src/runtime/graph/graph_runtime.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/runtime/graph/graph_runtime.cc b/src/runtime/graph/graph_runtime.cc
index ed833d40848c..d244fe5f028e 100644
--- a/src/runtime/graph/graph_runtime.cc
+++ b/src/runtime/graph/graph_runtime.cc
@@ -386,7 +386,7 @@ void GraphRuntime::LoadDLTensor(dmlc::Stream* strm, DLTensor* dst) {
   for (int i = 0; i < dst->ndim; ++i) {
     size *= dst->shape[i];
   }
-  int64_t data_byte_size;
+  uint64_t data_byte_size;
   CHECK(strm->Read(&data_byte_size, sizeof(data_byte_size)))
       << "Invalid DLTensor file format";
   CHECK(data_byte_size == size)

From 16cedd504386e41871036e2157aad8f56b0c2d01 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 23 Jan 2018 12:08:29 -0800
Subject: [PATCH 101/949] [OPENCL] Fix 32bit pointer size in OpenCL runtime
 (#809)

---
 src/runtime/opencl/opencl_module.cc | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/src/runtime/opencl/opencl_module.cc b/src/runtime/opencl/opencl_module.cc
index 5a585a19cccf..bde7e6b27418 100644
--- a/src/runtime/opencl/opencl_module.cc
+++ b/src/runtime/opencl/opencl_module.cc
@@ -254,9 +254,14 @@ PackedFunc OpenCLModuleNode::GetFunction(
   for (size_t i = 0; i < info.arg_types.size(); ++i) {
     TVMType t = info.arg_types[i];
     CHECK_EQ(t.lanes, 1U);
-    uint32_t bits = t.bits;
-    CHECK_EQ(bits % 8, 0U);
-    arg_size[i] = bits / 8;
+    if (t.code == kHandle) {
+      // specially store pointer type size in OpenCL driver
+      arg_size[i] = sizeof(void*);
+    } else {
+      uint32_t bits = t.bits;
+      CHECK_EQ(bits % 8, 0U);
+      arg_size[i] = bits / 8;
+    }
   }
   // initialize the wrapped func.
   f.Init(this, sptr_to_self, kid_map_.at(name),

From 430b7ede756fc25d274ec72c81eeb830255b1b17 Mon Sep 17 00:00:00 2001
From: xqdan <danxiaoqiang@126.com>
Date: Wed, 24 Jan 2018 06:07:14 +0800
Subject: [PATCH 102/949] fix #802, create cache based on sugar tensor (#808)

---
 src/schedule/schedule_dataflow_rewrite.cc     |  6 +--
 .../unittest/test_pass_storage_rewrite.py     | 43 ++++++++++++++++++-
 2 files changed, 45 insertions(+), 4 deletions(-)

diff --git a/src/schedule/schedule_dataflow_rewrite.cc b/src/schedule/schedule_dataflow_rewrite.cc
index b58df9d0481f..59d425287be0 100644
--- a/src/schedule/schedule_dataflow_rewrite.cc
+++ b/src/schedule/schedule_dataflow_rewrite.cc
@@ -82,12 +82,12 @@ Tensor Schedule::cache_read(const Tensor& tensor,
   }
   os << "." << scope;
 
-  Tensor cache = compute(tensor->shape, [&tensor](const Array<Var>& i) {
-      return tensor(Array<Expr>(i.begin(), i.end()));
-    }, os.str());
   std::unordered_map<Tensor, Tensor> vsub;
   Stage s = operator[](tensor->op);
   Tensor sugar_tensor = s->op.output(tensor->value_index);
+  Tensor cache = compute(sugar_tensor->shape, [&sugar_tensor](const Array<Var>& i) {
+      return sugar_tensor(Array<Expr>(i.begin(), i.end()));
+    }, os.str());
   vsub[sugar_tensor] = cache;
 
   std::unordered_map<Tensor, Tensor> vmap;
diff --git a/tests/python/unittest/test_pass_storage_rewrite.py b/tests/python/unittest/test_pass_storage_rewrite.py
index 1e4dda684eb3..d044db12686f 100644
--- a/tests/python/unittest/test_pass_storage_rewrite.py
+++ b/tests/python/unittest/test_pass_storage_rewrite.py
@@ -171,7 +171,47 @@ def test_parallel_alloc():
 
     assert(isinstance(body.body.body.body.body, tvm.stmt.Allocate))
 
-
+def test_inplace_rule2():
+    #Test Buffer
+    scope_tb = "local_TB"
+    @tvm.register_func("tvm.info.mem.%s" % scope_tb)
+    def mem_info_inp_buffer():
+        return tvm.make.node("MemoryInfo",
+                        unit_bits= 16,
+                        max_simd_bits=32,
+                        max_num_bits=1024*1024*1024,
+                        head_address=None)
+    m = 10
+    A = tvm.placeholder((m,), name='A')
+    C = tvm.placeholder((m,), name='C')
+    D = tvm.placeholder((m,), name='D')
+    A0 = tvm.compute((m,), lambda i: A[i] + C[i], name='A0')
+    A1 = tvm.compute((m,), lambda i: D[i] * D[i], name='A1')
+    A2 = tvm.compute((m,), lambda i: A0[i] + A1[i], name='A2')
+    B = tvm.compute((m,), lambda i: A2[i], name='B')
+    s = tvm.create_schedule(B.op)
+    A0L = s.cache_read(A0, scope_tb, [A2])
+    A1L = s.cache_read(A1, scope_tb, [A2])
+    A2L = s.cache_read(A2, scope_tb, [B])
+    bounds = tvm.schedule.InferBound(s)
+    assert isinstance(bounds, tvm.container.Map)
+    stmt = tvm.schedule.ScheduleOps(s, bounds)
+    Ab = tvm.decl_buffer(A.shape, A.dtype, name='A')
+    Bb = tvm.decl_buffer(B.shape, B.dtype, name='B')
+    Cc = tvm.decl_buffer(C.shape, B.dtype, name='C')
+    Dd = tvm.decl_buffer(D.shape, B.dtype, name='D')
+    stmt = tvm.ir_pass.StorageFlatten(stmt, {A: Ab, B: Bb, C: Cc, D:Dd}, 64)
+    stmt = tvm.ir_pass.CanonicalSimplify(stmt)
+    stmt = tvm.ir_pass.Simplify(stmt)
+    stmt = tvm.ir_pass.StorageRewrite(stmt)
+    # verify only have one allocations.
+    # verify inplace folding works
+    num_alloc = [0]
+    def verify(n):
+        if isinstance(n, tvm.stmt.Allocate):
+            num_alloc[0] += 1
+    tvm.ir_pass.PostOrderVisit(stmt, verify)
+    assert num_alloc[0] == 2
 
 if __name__ == "__main__":
     test_alloc_seq()
@@ -180,3 +220,4 @@ def test_parallel_alloc():
     test_parallel_alloc()
     test_storage_combine()
     test_storage_share_gpu()
+    test_inplace_rule2()

From 9111082625456c07ad317b953c04ea989d8ad72d Mon Sep 17 00:00:00 2001
From: yuruofeifei <yuruofeifei@gmail.com>
Date: Tue, 23 Jan 2018 14:32:37 -0800
Subject: [PATCH 103/949] Improve gemm tutorial (#800)

---
 tutorials/optimize/opt_gemm.py | 272 ++++++++++++++++++++++-----------
 1 file changed, 182 insertions(+), 90 deletions(-)

diff --git a/tutorials/optimize/opt_gemm.py b/tutorials/optimize/opt_gemm.py
index 9a4264c9d05a..715408380b15 100644
--- a/tutorials/optimize/opt_gemm.py
+++ b/tutorials/optimize/opt_gemm.py
@@ -1,7 +1,8 @@
 """
 How to optimize GEMM on CPU
 ===========================
-**Author**: `Jian Weng <https://github.com/were>`_
+**Author**: `Jian Weng <https://github.com/were>`_, \
+            `Ruofei Yu <https://github.com/yuruofeifei>`_
 
 (TL;DR) TVM provides abstract interfaces which allows users to depict an algorithm and the
 algorithm's implementing organization (the so-called schedule) separately. Typically, writing
@@ -10,7 +11,7 @@
 try these schedules efficiently to enhance the performance.
 
 In this tutorial, we will demonstrate how to use TVM to optimize square matrix multiplication
-and achieve 100 times faster than baseline by simply adding 6 extra lines of code.
+and achieve 200 times faster than baseline by simply adding 18 extra lines of code.
 
 There are two important optmizations on intense computation applications executed on CPU:
     1. Increase the cache hit rate of memory access. Both complex numerical computation and hot-spot
@@ -26,36 +27,46 @@
 abstraction automatically, but some of them cannot be simply applied due to TVM constraints.
 
 All the experiment results mentioned below, are executed on 2015's 15' MacBook equiped with
-Intel i7-4770QH CPU. The cache line size should be 64 bytes for all the x86 CPUs.
+Intel i7-4770HQ CPU. The cache line size should be 64 bytes for all the x86 CPUs.
 """
 
-###############################################################################
+################################################################################################
 # Preparation and Baseline
 # ------------------------
-# In this tutorial we assume all the matrix tensors are square and fix-bounded.
-# We use 1024x1024 float32 matrix in demonstration. Before actually demonstrating,
-# we first define these variables. Then we write a baseline implementation,
-# the simplest way to write a matrix mulplication in TVM.
-#
+# In this tutorial, we will demo how to use TVM to optimize matrix multiplication.
+# Before actually demonstrating, we first define these variables.
+# Then we write a baseline implementation, the simplest way to write a matrix multiplication in TVM.
 
 import tvm
 import numpy
 import timeit
 
-# The size of the square matrix
+# The size of the matrix
+# (M, K) x (K, N)
+# You are free to try out different shapes, sometimes TVM optimization outperforms numpy with MKL.
+M = 1024
+K = 1024
 N = 1024
+
 # The default tensor type in tvm
 dtype = "float32"
+
+# using Intel AVX2(Advanced Vector Extensions) ISA for SIMD
+target = 'llvm -mcpu=core-avx2'
+ctx = tvm.context(target, 0)
+
 # Random generated tensor for testing
-a = tvm.nd.array(numpy.random.rand(N, N).astype(dtype), tvm.cpu(0))
-b = tvm.nd.array(numpy.random.rand(N, N).astype(dtype), tvm.cpu(0))
+a = tvm.nd.array(numpy.random.rand(M, K).astype(dtype), ctx)
+b = tvm.nd.array(numpy.random.rand(K, N).astype(dtype), ctx)
 
 np_repeat = 100
 np_runing_time = timeit.timeit(setup='import numpy\n'
-                                     'N = 1024\n'
+                                     'M = ' + str(M) + '\n'
+                                     'K = ' + str(K) + '\n'
+                                     'N = ' + str(N) + '\n'
                                      'dtype = "float32"\n'
-                                     'a = numpy.random.rand(N, N).astype(dtype)\n'
-                                     'b = numpy.random.rand(N, N).astype(dtype)\n',
+                                     'a = numpy.random.rand(M, K).astype(dtype)\n'
+                                     'b = numpy.random.rand(K, N).astype(dtype)\n',
                                stmt='answer = numpy.dot(a, b)',
                                number=np_repeat)
 print("Numpy running time: %f" % (np_runing_time / np_repeat))
@@ -63,24 +74,24 @@
 answer = numpy.dot(a.asnumpy(), b.asnumpy())
 
 # Algorithm
-k = tvm.reduce_axis((0, N), 'k')
-A = tvm.placeholder((N, N), name = 'A')
-B = tvm.placeholder((N, N), name = 'B')
+k = tvm.reduce_axis((0, K), 'k')
+A = tvm.placeholder((M, K), name='A')
+B = tvm.placeholder((K, N), name='B')
 C = tvm.compute(
-           A.shape,
-           lambda x, y: tvm.sum(A[x, k] * B[k, y], axis = k),
-           name = 'C')
+           (M, N),
+           lambda x, y: tvm.sum(A[x, k] * B[k, y], axis=k),
+           name='C')
 
 # Default schedule
 s = tvm.create_schedule(C.op)
-func = tvm.build(s, [A, B, C], name = 'mmult')
+func = tvm.build(s, [A, B, C], target=target, name='mmult')
 assert func
 
-c = tvm.nd.array(numpy.zeros((N, N), dtype = dtype), tvm.cpu(0))
+c = tvm.nd.array(numpy.zeros((M, N), dtype=dtype), ctx)
 func(a, b, c)
 numpy.testing.assert_allclose(c.asnumpy(), answer, rtol=1e-5)
 
-evaluator = func.time_evaluator(func.entry_name, tvm.cpu(0), number=1)
+evaluator = func.time_evaluator(func.entry_name, ctx, number=1)
 print('Baseline: %f' % evaluator(a, b, c).mean)
 
 ################################################################################################
@@ -92,27 +103,32 @@
 ################################################################################################
 # Blocking
 # --------
-# A important trick to enhance the cache hit rate is blocking --- data chunck will be computed
+# A important trick to enhance the cache hit rate is blocking --- data chunk will be computed
 # block by block. The memory access inside the block is a small neighbourhood which is with high
 # memory locality. In this tutorial, I picked up 32 as the blocking factor. So the block will
 # fill 32 * 32 * sizeof(float) which is 4KB in the cache whose total size is 32KB (L1 data cache)
 
 bn = 32
 s = tvm.create_schedule(C.op)
+
 # Blocking by loop tiling
 xo, yo, xi, yi = s[C].tile(C.op.axis[0], C.op.axis[1], bn, bn)
+k, = s[C].op.reduce_axis
+ko, ki = s[C].split(k, factor=4)
+
 # Hoist reduction domain outside the blocking loop
-s[C].reorder(xo, yo, k, xi, yi)
-func = tvm.build(s, [A, B, C], name = 'mmult')
+s[C].reorder(xo, yo, ko, ki, xi, yi)
+
+func = tvm.build(s, [A, B, C], target=target, name='mmult')
 assert func
 
-c = tvm.nd.array(numpy.zeros((N, N), dtype = dtype), tvm.cpu(0))
+c = tvm.nd.array(numpy.zeros((M, N), dtype = dtype), ctx)
 func(a, b, c)
 numpy.testing.assert_allclose(c.asnumpy(), answer, rtol=1e-5)
 
-# By simply tiling the loop 32x32, and hoisting k outside the blocking loops, we can see big
-# speedup compared with the baseline.
-evaluator = func.time_evaluator(func.entry_name, tvm.cpu(0), number=5)
+# By simply tiling the loop 32x32, and hoisting ko, ki outside the blocking loops,
+# we can see big speedup compared with the baseline.
+evaluator = func.time_evaluator(func.entry_name, ctx, number=10)
 print('Opt1: %f' % evaluator(a, b, c).mean)
 
 ################################################################################################
@@ -120,6 +136,73 @@
 
 print(tvm.lower(s, [A, B, C], simple_mode=True))
 
+###################################################################################################
+# Vectorization
+# -------------
+# Another important trick is vectorization. When the memory access pattern is uniform,
+# the compiler can detect this pattern and pass the continuous memory to vector processor. In TVM,
+# we can use `vectorize` interface to hint the compiler this pattern, so that we can accelerate it vastly.
+#
+# In this tutorial, we chose to vectorize the inner loop row data since it is cache friendly.
+
+s = tvm.create_schedule(C.op)
+xo, yo, xi, yi = s[C].tile(C.op.axis[0], C.op.axis[1], bn, bn)
+k, = s[C].op.reduce_axis
+ko, ki = s[C].split(k, factor=4)
+
+s[C].reorder(xo, yo, ko, ki, xi, yi)
+
+# Vectorization
+s[C].vectorize(yi)
+
+func = tvm.build(s, [A, B, C], target=target, name='mmult')
+assert func
+
+c = tvm.nd.array(numpy.zeros((M, N), dtype = dtype), ctx)
+func(a, b, c)
+numpy.testing.assert_allclose(c.asnumpy(), answer, rtol=1e-5)
+
+evaluator = func.time_evaluator(func.entry_name, ctx, number=10)
+print('Opt2: %f' % evaluator(a, b, c).mean)
+
+################################################################################################
+# Here is the generated IR after vectorization.
+
+print(tvm.lower(s, [A, B, C], simple_mode=True))
+
+###################################################################################################
+# Loop Permutation
+# -------------
+# If we look at the above IR, we can see the inner loop row data is vectorized and
+# B is transformed into PackedB. The traversal of PackedB is sequential now.
+# So we will look at the access pattern of A. In current schedule, A is accessed column by column
+# which is not cache friendly. If we change the nested loop order of ki and inner axes xi,
+# the access pattern for A matrix is more cache friendly.
+
+s = tvm.create_schedule(C.op)
+xo, yo, xi, yi = s[C].tile(C.op.axis[0], C.op.axis[1], bn, bn)
+k, = s[C].op.reduce_axis
+ko, ki = s[C].split(k, factor=4)
+
+# re-ordering
+s[C].reorder(xo, yo, ko, xi, ki, yi)
+s[C].vectorize(yi)
+
+func = tvm.build(s, [A, B, C], target=target, name='mmult')
+assert func
+
+c = tvm.nd.array(numpy.zeros((M, N), dtype = dtype), ctx)
+func(a, b, c)
+numpy.testing.assert_allclose(c.asnumpy(), answer, rtol=1e-5)
+
+evaluator = func.time_evaluator(func.entry_name, ctx, number=10)
+print('Opt3: %f' % evaluator(a, b, c).mean)
+
+################################################################################################
+# Here is the generated IR after loop permutation.
+
+print(tvm.lower(s, [A, B, C], simple_mode=True))
+
 ###################################################################################################
 # Array Packing
 # -------------
@@ -142,88 +225,82 @@
 #
 
 # We have to re-write the algorithm slightly.
-packedB = tvm.compute((N / bn, N, bn), lambda x, y, z: B[y, x * bn + z], name = 'packedB')
-C = tvm.compute(A.shape,
-                lambda x, y: tvm.sum(A[x, k] * packedB[y / bn, k, y % bn], axis = k),
+packedB = tvm.compute((N / bn, K, bn), lambda x, y, z: B[y, x * bn + z], name='packedB')
+C = tvm.compute((M, N),
+                lambda x, y: tvm.sum(A[x, k] * packedB[y / bn, k, y % bn], axis=k),
                 name = 'C')
 
 s = tvm.create_schedule(C.op)
+
 xo, yo, xi, yi = s[C].tile(C.op.axis[0], C.op.axis[1], bn, bn)
-s[C].reorder(xo, yo, k, xi, yi)
+k, = s[C].op.reduce_axis
+ko, ki = s[C].split(k, factor=4)
 
-func = tvm.build(s, [A, B, C], name = 'mmult')
+s[C].reorder(xo, yo, ko, xi, ki, yi)
+s[C].vectorize(yi)
+
+x, y, z = s[packedB].op.axis
+s[packedB].vectorize(z)
+s[packedB].parallel(x)
+
+func = tvm.build(s, [A, B, C], target=target, name='mmult')
 assert func
 
-c = tvm.nd.array(numpy.zeros((N, N), dtype = dtype), tvm.cpu(0))
+c = tvm.nd.array(numpy.zeros((M, N), dtype = dtype), ctx)
 func(a, b, c)
 numpy.testing.assert_allclose(c.asnumpy(), answer, rtol=1e-5)
 
-evaluator = func.time_evaluator(func.entry_name, tvm.cpu(0), number=5)
-print('Opt2: %f' % evaluator(a, b, c).mean)
+evaluator = func.time_evaluator(func.entry_name, ctx, number=10)
+print('Opt4: %f' % evaluator(a, b, c).mean)
 
 ################################################################################################
 # Here is the generated IR after array packing.
 
 print(tvm.lower(s, [A, B, C], simple_mode=True))
 
-###################################################################################################
-# Vectorization
-# -------------
-# Another important trick is vectorization. When the memory access pattern is uniform,
-# the compiler can detect this pattern and pass the continuous memory to vector processor. In TVM,
-# we can use `vectorize` interface to hint the compiler this pattern, so that we can accelerate it vastly.
+################################################################################################
+# Write cache for blocks
+# --------
+# After blocking, the program will write result to C block by block, the access pattern
+# is not sequential. So we can use a sequential cache array to hold the block results and
+# write to C when all the block results are ready.
 #
-# In this tutorial, we chose to vectorize the inner loop row data since it is cache friendly.
 
 s = tvm.create_schedule(C.op)
-xo, yo, xi, yi = s[C].tile(C.op.axis[0], C.op.axis[1], bn, bn)
-s[C].reorder(xo, yo, k, xi, yi)
 
-# Vectorization
-s[C].vectorize(yi)
-func = tvm.build(s, [A, B, C], name = 'mmult')
-assert func
-
-c = tvm.nd.array(numpy.zeros((N, N), dtype = dtype), tvm.cpu(0))
-func(a, b, c)
-numpy.testing.assert_allclose(c.asnumpy(), answer, rtol=1e-5)
+# Allocate write cache
+CC = s.cache_write(C, 'global')
 
-evaluator = func.time_evaluator(func.entry_name, tvm.cpu(0), number=5)
-print('Opt3: %f' % evaluator(a, b, c).mean)
+xo, yo, xi, yi = s[C].tile(C.op.axis[0], C.op.axis[1], bn, bn)
 
-################################################################################################
-# Here is the generated IR after vectorization.
+# Write cache is computed at yo
+s[CC].compute_at(s[C], yo)
 
-print(tvm.lower(s, [A, B, C], simple_mode=True))
+# New inner axes
+xc, yc = s[CC].op.axis
 
-###################################################################################################
-# Loop Permutation
-# -------------
-# If we look at the above IR, we can see the inner loop row data is vectorized and
-# B is transformed into PackedB. The traversal of PackedB is sequential now.
-# So we will look at the access pattern of A. In current schedule, A is accessed column by column
-# which is not cache friendly. If we change the nested loop order of k and inner row index xi,
-# the access pattern for A matrix is more cache friendly.
+k, = s[CC].op.reduce_axis
+ko, ki = s[CC].split(k, factor=4)
+s[CC].reorder(ko, xc, ki, yc)
+s[CC].unroll(ki)
+s[CC].vectorize(yc)
 
-s = tvm.create_schedule(C.op)
-xo, yo, xi, yi = s[C].tile(C.op.axis[0], C.op.axis[1], bn, bn)
-s[C].reorder(xo, yo, xi, k, yi)
+x, y, z = s[packedB].op.axis
+s[packedB].vectorize(z)
+s[packedB].parallel(x)
 
-# Vectorization
-s[C].vectorize(yi)
-
-func = tvm.build(s, [A, B, C], name = 'mmult')
+func = tvm.build(s, [A, B, C], target=target, name='mmult')
 assert func
 
-c = tvm.nd.array(numpy.zeros((N, N), dtype = dtype), tvm.cpu(0))
+c = tvm.nd.array(numpy.zeros((M, N), dtype = dtype), ctx)
 func(a, b, c)
 numpy.testing.assert_allclose(c.asnumpy(), answer, rtol=1e-5)
 
-evaluator = func.time_evaluator(func.entry_name, tvm.cpu(0), number=5)
-print('Opt4: %f' % evaluator(a, b, c).mean)
+evaluator = func.time_evaluator(func.entry_name, ctx, number=10)
+print('Opt5: %f' % evaluator(a, b, c).mean)
 
 ################################################################################################
-# Here is the generated IR after loop permutation.
+# Here is the generated IR after blocking.
 
 print(tvm.lower(s, [A, B, C], simple_mode=True))
 
@@ -233,23 +310,38 @@
 # Futhermore, we can also utilize multi-core processors to do the thread-level parallelization.
 
 s = tvm.create_schedule(C.op)
+
+CC = s.cache_write(C, 'global')
+
 xo, yo, xi, yi = s[C].tile(C.op.axis[0], C.op.axis[1], bn, bn)
-s[C].reorder(xo, yo, xi, k, yi)
-s[C].vectorize(yi)
+
+s[CC].compute_at(s[C], yo)
+
+xc, yc = s[CC].op.axis
+
+k, = s[CC].op.reduce_axis
+ko, ki = s[CC].split(k, factor=4)
+s[CC].reorder(ko, xc, ki, yc)
+s[CC].unroll(ki)
+s[CC].vectorize(yc)
 
 # parallel
 s[C].parallel(xo)
 
-func = tvm.build(s, [A, B, C], name = 'mmult')
+x, y, z = s[packedB].op.axis
+s[packedB].vectorize(z)
+s[packedB].parallel(x)
+
+func = tvm.build(s, [A, B, C], target=target, name = 'mmult')
 assert func
 
-c = tvm.nd.array(numpy.zeros((N, N), dtype = dtype), tvm.cpu(0))
+c = tvm.nd.array(numpy.zeros((M, N), dtype = dtype), ctx)
 func(a, b, c)
 numpy.testing.assert_allclose(c.asnumpy(), answer, rtol=1e-5)
 
-evaluator = func.time_evaluator(func.entry_name, tvm.cpu(0), number=50)
-opt5_time = evaluator(a, b, c).mean
-print('Opt5: %f' % opt5_time)
+evaluator = func.time_evaluator(func.entry_name, ctx, number=50)
+opt6_time = evaluator(a, b, c).mean
+print('Opt6: %f' % opt6_time)
 
 ################################################################################################
 # Here is the generated IR after parallelization.
@@ -261,8 +353,8 @@
 ##################################################################################################
 # Summary
 # -------
-# After applying the above simple optimizations with only 6 lines of code,
-# our generated code can achieve 30% of the `numpy` performance with Apple implemented BLAS.
-# Note that the outputs on the webpage reflect the running times on a non-exclusive
+# After applying the above simple optimizations with only 18 lines of code,
+# our generated code can achieve 60% of the `numpy` performance with MKL.
+# Note that the outputs on the web page reflect the running times on a non-exclusive
 # Docker container, thereby they are *unreliable*. It is highly encouraged to run the
 # tutorial by yourself to observe the performance gain acheived by TVM.

From ebb67c42701b9808d1ad7ada51d3eb48dd3afcc5 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 23 Jan 2018 15:41:30 -0800
Subject: [PATCH 104/949] fix gemm tutorial for env that may not have right
 instruction (#810)

---
 tutorials/optimize/opt_gemm.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/tutorials/optimize/opt_gemm.py b/tutorials/optimize/opt_gemm.py
index 715408380b15..f1060bf46f45 100644
--- a/tutorials/optimize/opt_gemm.py
+++ b/tutorials/optimize/opt_gemm.py
@@ -52,7 +52,9 @@
 dtype = "float32"
 
 # using Intel AVX2(Advanced Vector Extensions) ISA for SIMD
-target = 'llvm -mcpu=core-avx2'
+# To get the best performance, please change the following line
+# to llvm -mcpu=core-avx2, or specific type of CPU you use
+target = 'llvm'
 ctx = tvm.context(target, 0)
 
 # Random generated tensor for testing

From 814b46dccb3c340be7611d113a303a546f7b2b2a Mon Sep 17 00:00:00 2001
From: libing4752 <libing475211023@sjtu.edu.cn>
Date: Wed, 24 Jan 2018 10:35:59 +0800
Subject: [PATCH 105/949] [PASS] enhance storage_rewrite to support different
 dtypes for unified buffer (#805)

* modified schedule_dataflow_rewrite.cc to fix losing tensor problem

* modified schedule_dataflow_rewrite.cc for lint scan

* modified schedule_dataflow_rewrite.cc for lint scan

* using tensor's value_index to index output of stage op

* repare address offset for different kinds of dtype

* bc

* aaa

* aaaaa

* repare address for different dtypes

* remove nonsense files

* add whitespace of line 581

* use base alloc elem_type

* enhance the testcast of basic buffer is 64bits,32bits,16bits,8bits

* use extends[0]->type() as dtype of offset

* clear program writes
---
 src/pass/storage_rewrite.cc                   | 26 +++++-----
 .../unittest/test_pass_storage_rewrite.py     | 48 ++++++++++++++++++-
 2 files changed, 60 insertions(+), 14 deletions(-)

diff --git a/src/pass/storage_rewrite.cc b/src/pass/storage_rewrite.cc
index 7215c3f97a43..f052a9b05b90 100644
--- a/src/pass/storage_rewrite.cc
+++ b/src/pass/storage_rewrite.cc
@@ -576,33 +576,33 @@ class StoragePlanRewriter : public IRMutator {
     // allocate with element type.
     CHECK_NE(e->const_nbits, 0U);
     MemoryInfo info = GetMemoryInfo(e->scope.to_string());
+    uint64_t total_bits = e->const_nbits;
     size_t align = 1;
     if (info.defined()) {
-      align = (info->max_simd_bits + e->elem_type.bits() - 1) / e->elem_type.bits();
+      align = info->max_simd_bits;
     }
-    uint64_t total_elem = e->const_nbits / e->elem_type.bits();
-    if (total_elem % align != 0) {
-      total_elem += align  - (total_elem % align);
+    if (total_bits % align != 0) {
+      total_bits += align  - (total_bits % align);
     }
     e->alloc_var = e->allocs[0]->buffer_var;
     for (StorageEntry* child : e->merged_children) {
-      CHECK_NE(e->const_nbits, 0U);
-      CHECK_NE(total_elem, 0U);
-      size_t num_elem = child->const_nbits / child->elem_type.bits();
-      child->elem_offset = total_elem;
+      CHECK_NE(child->const_nbits, 0U);
+      CHECK_NE(total_bits, 0U);
+      child->elem_offset = total_bits / child->elem_type.bits();
       child->alloc_var = e->alloc_var;
-      total_elem += num_elem;
-      if (total_elem % align != 0) {
-        total_elem += align  - (total_elem % align);
+      total_bits += child->const_nbits;
+      if (total_bits % align != 0) {
+        total_bits += align  - (total_bits % align);
       }
     }
+    uint64_t type_bits = e->elem_type.bits() * e->elem_type.lanes();
     Expr alloc_size = make_const(e->allocs[0]->extents[0].type(),
-                                 total_elem);
+                                 (total_bits + type_bits - 1) / type_bits);
     e->new_alloc = Allocate::make(
         e->alloc_var, e->elem_type, {alloc_size}, const_true(),
         Evaluate::make(0));
     if (info.defined()) {
-      CHECK_LE(total_elem * e->elem_type.bits(), info->max_num_bits)
+      CHECK_LE(total_bits, info->max_num_bits)
           << "Allocation exceed bound of memory tag " << e->scope.to_string();
     }
   }
diff --git a/tests/python/unittest/test_pass_storage_rewrite.py b/tests/python/unittest/test_pass_storage_rewrite.py
index d044db12686f..6b6ff71810bb 100644
--- a/tests/python/unittest/test_pass_storage_rewrite.py
+++ b/tests/python/unittest/test_pass_storage_rewrite.py
@@ -49,6 +49,52 @@ def verify(n):
     tvm.ir_pass.PostOrderVisit(body, verify)
     assert num_alloc[0] == 1
 
+def test_alloc_different_dtypes():
+    def stmt_generater(dtype_list, length):
+        ib = tvm.ir_builder.create()
+        base_dtype = dtype_list[0]
+        global_a = tvm.placeholder((length,), name = "global_a", dtype = base_dtype)
+        for index, dtype in enumerate(dtype_list):
+            with ib.for_range(0, length, name="j") as j:
+                A = ib.allocate(dtype, length, name="A_" + str(index), scope="local.L0A")
+                A[j] = tvm.const(1, dtype = dtype)
+        return ib.get()
+    
+    def dtype_bit_len(dtype):
+        index = 0
+        for i in dtype:
+            if i.isdigit():
+                break
+            index += 1
+        return int(dtype[index:])
+
+    def offset_generater(dtype_list, length):
+        dtype_len_list = [dtype_bit_len(i) for i in dtype_list]
+        base_len = dtype_len_list[0]
+        return sum([i * length / base_len for i in dtype_len_list])
+
+    def dtype_test(dtype_list, length):
+        def verify(n):
+            if isinstance(n, tvm.stmt.Allocate):
+                assert n.extents[0].value == offset
+
+        body = stmt_generater(dtype_list, length)
+        offset = offset_generater(dtype_list, length)
+        body = tvm.ir_pass.StorageRewrite(body)
+        tvm.ir_pass.PostOrderVisit(body, verify)
+
+    length = 1024
+    dtype_list = ["float16", "int32", "uint16", "int8"]
+    dtype_test(dtype_list, length)
+
+    dtype_list = ["float32", "int32", "uint16", "int8"]
+    dtype_test(dtype_list, length)
+
+    dtype_list = ["float64", "int32", "uint16", "int8"]
+    dtype_test(dtype_list, length)
+    
+    dtype_list = ["int8", "int32", "uint16", "uint8"]
+    dtype_test(dtype_list, length)
 
 
 def test_inplace_rule():
@@ -91,7 +137,6 @@ def test_storage_combine():
     s = tvm.create_schedule(B.op)
     for S in stages[:-1]:
         s[S].set_scope("global:tag")
-
     bounds = tvm.schedule.InferBound(s)
     assert isinstance(bounds, tvm.container.Map)
     stmt = tvm.schedule.ScheduleOps(s, bounds)
@@ -215,6 +260,7 @@ def verify(n):
 
 if __name__ == "__main__":
     test_alloc_seq()
+    test_alloc_different_dtypes()
     test_inplace_rule()
     test_storage_share()
     test_parallel_alloc()

From 4627c17e3030fe24f70ae961c3c4d5b7fa77fd2b Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 24 Jan 2018 13:59:30 -0800
Subject: [PATCH 106/949] [CODE COMMENT] Comment BindBufferScope (#815)

---
 src/pass/storage_flatten.cc | 35 ++++++++++++++++++++++++++++++++++-
 1 file changed, 34 insertions(+), 1 deletion(-)

diff --git a/src/pass/storage_flatten.cc b/src/pass/storage_flatten.cc
index 45ad081f86cb..bc380d473791 100644
--- a/src/pass/storage_flatten.cc
+++ b/src/pass/storage_flatten.cc
@@ -296,7 +296,40 @@ class StorageFlattener : public IRMutator {
   }
 
  private:
-  // Start bind
+  // The specific tensor data layout is not determined before
+  // StorageFlatten pass. We use buffer_bind_scope
+  // to specify before hand we want to bind a subregion
+  // of tensor to a symbolic buffer, which get used in extern.
+  //
+  // Example:
+  //
+  // realize A in range [i*4, extent=10) {
+  //   bind Ab to A in [i*4+1, extent=4) {
+  //     call_func(Ab.ptr, Ab.shape[0])
+  //   }
+  // }
+  //
+  // After StorageFlatten
+  //
+  // alloc A[10]
+  //   call(A + 1,  4)
+  //
+  // Buffer is a protocol to declare specific
+  // data layout and shape we expect.
+  // So this function need to check:
+  // - If the bind range is within the realize range
+  // - If we can match the requirement of buffer
+  // - Remap variables such as Ab.ptr to the actual value.
+  //
+  // Here are a few possible failure cases:
+  // - Buffer is declared to have constant shape,
+  //   but we try to bind it to a different one.
+  // - Buffer is declared to be compact(no strides)
+  //   but this binded region is a subregion of
+  //   a matrix(tensor), which means it requires strides.
+  //
+  // We do support a few relaxed case, such as bindingx
+  // region with shape [1, 1, n, m] to buffer with shape [n, m]
   Stmt HandleBufferBindScope(const AttrStmt* op) {
     Array<NodeRef> arr(op->node.node_);
     CHECK_EQ(arr.size(), 2U);

From 225c8064e58e21d6ac4aa516b2e57efb0adcb324 Mon Sep 17 00:00:00 2001
From: Mu Li <mli@amazon.com>
Date: Wed, 24 Jan 2018 14:00:08 -0800
Subject: [PATCH 107/949] Update cross_compilation_and_rpc.py (#816)

---
 .../deployment/cross_compilation_and_rpc.py   | 40 ++++++++++++++-----
 1 file changed, 31 insertions(+), 9 deletions(-)

diff --git a/tutorials/deployment/cross_compilation_and_rpc.py b/tutorials/deployment/cross_compilation_and_rpc.py
index bfd77f287336..b31263f9ed44 100644
--- a/tutorials/deployment/cross_compilation_and_rpc.py
+++ b/tutorials/deployment/cross_compilation_and_rpc.py
@@ -14,11 +14,6 @@
 In this tutorial, I will take Raspberry Pi as our target platform
 for example.
 """
-from __future__ import absolute_import, print_function
-
-import tvm
-import numpy as np
-from tvm.contrib import rpc, util
 
 ######################################################################
 # Build TVM Runtime on Device
@@ -27,6 +22,12 @@
 # There're some prerequisites: similar as compiling TVM on your
 # local machine, we need build runtime on remote device.
 #
+# .. note::
+# 
+#   All instructions in both this section and next section should be 
+#   executed on the target device, e.g. Raspberry Pi. And we assume it 
+#   has Linux running. 
+# 
 # To get started, clone tvm repo from github. It is important to clone
 # the submodules along, with --recursive option (Assuming you are in
 # your home directory):
@@ -106,8 +107,19 @@
 ######################################################################
 # Declare and Cross Compile Kernel on Local Machine
 # -------------------------------------------------
-# Here we will declare a simple kernel with TVM on the local machine:
 #
+# .. note::
+#
+#   Now we back to the local machine, which has a full TVM installed. 
+#
+# Here we will declare a simple kernel with TVM on the local machine:
+
+from __future__ import absolute_import, print_function
+
+import tvm
+import numpy as np
+from tvm.contrib import rpc, util
+
 n = tvm.convert(1024)
 A = tvm.placeholder((n,), name='A')
 B = tvm.compute(A.shape, lambda *i: A(*i) + 1.0, name='B')
@@ -136,7 +148,8 @@
 #   Pi. Here we use :code:`'llvm'` directly to make the tutorial runable.
 #
 #   Usually, you can query the target by execute :code:`gcc -v` on your
-#   device, although it may be still a loose configuration.
+#   device, and look for the line starting with :code:`Target:` 
+#   (Though it may be still a loose configuration.)
 #
 #   Besides :code:`-target`, you can also set other compilation options
 #   like:
@@ -171,9 +184,15 @@
 # Run CPU Kernel Remotely by RPC
 # ------------------------------
 # Here we will show you how to run the kernel on the remote device:
+#
+# .. note:: 
+#    In order to have this tutorial runs locally to build the nice HTML, we 
+#    start a RPC server on the local machine. You can ignore it if you already 
+#    started the server on the target device. And then change host IP properly.
 
-# replace host with the ip address of your device
-host = '0.0.0.0'
+# Can be ignored if you already started the RPC server 
+server = rpc.Server(host='0.0.0.0', port=9090, use_popen=True) 
+host = '0.0.0.0'  # Change to your target device IP 
 port = 9090
 # connect the remote device
 remote = rpc.connect(host, port)
@@ -213,9 +232,12 @@
 # on remote compiler to re-link them.
 #
 # .. note::
+#
 #    Raspberry Pi does not support OpenCL, the following code is tested on
 #    Firefly-RK3399. The target_host should be 'llvm -target=aarch64-linux-gnu'.
 #    But here we set 'llvm' to enable this tutorial to run locally.
+#
+#    Also we need to build the runtime with the flag `USE_OPENCL=1`. 
 
 # build kernel (different from cpu, we need bind axis for OpenCL)
 s = tvm.create_schedule(B.op)

From c2dd9273e9dc47d92238876385e164af4b81c50d Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 24 Jan 2018 16:37:47 -0800
Subject: [PATCH 108/949] fix rpc tutorial (#818)

---
 .../deployment/cross_compilation_and_rpc.py   | 40 +++++++++----------
 1 file changed, 20 insertions(+), 20 deletions(-)

diff --git a/tutorials/deployment/cross_compilation_and_rpc.py b/tutorials/deployment/cross_compilation_and_rpc.py
index b31263f9ed44..eec2f9aeb34b 100644
--- a/tutorials/deployment/cross_compilation_and_rpc.py
+++ b/tutorials/deployment/cross_compilation_and_rpc.py
@@ -23,11 +23,11 @@
 # local machine, we need build runtime on remote device.
 #
 # .. note::
-# 
-#   All instructions in both this section and next section should be 
-#   executed on the target device, e.g. Raspberry Pi. And we assume it 
-#   has Linux running. 
-# 
+#
+#   All instructions in both this section and next section should be
+#   executed on the target device, e.g. Raspberry Pi. And we assume it
+#   has Linux running.
+#
 # To get started, clone tvm repo from github. It is important to clone
 # the submodules along, with --recursive option (Assuming you are in
 # your home directory):
@@ -102,6 +102,12 @@
 # same machine, for demonstration. This line can be omitted if we
 # started an remote server.
 #
+from __future__ import absolute_import, print_function
+
+import tvm
+import numpy as np
+from tvm.contrib import rpc, util
+
 server = rpc.Server(host='0.0.0.0', port=9090, use_popen=True)
 
 ######################################################################
@@ -110,16 +116,10 @@
 #
 # .. note::
 #
-#   Now we back to the local machine, which has a full TVM installed. 
+#   Now we back to the local machine, which has a full TVM installed.
 #
 # Here we will declare a simple kernel with TVM on the local machine:
 
-from __future__ import absolute_import, print_function
-
-import tvm
-import numpy as np
-from tvm.contrib import rpc, util
-
 n = tvm.convert(1024)
 A = tvm.placeholder((n,), name='A')
 B = tvm.compute(A.shape, lambda *i: A(*i) + 1.0, name='B')
@@ -148,7 +148,7 @@
 #   Pi. Here we use :code:`'llvm'` directly to make the tutorial runable.
 #
 #   Usually, you can query the target by execute :code:`gcc -v` on your
-#   device, and look for the line starting with :code:`Target:` 
+#   device, and look for the line starting with :code:`Target:`
 #   (Though it may be still a loose configuration.)
 #
 #   Besides :code:`-target`, you can also set other compilation options
@@ -185,14 +185,14 @@
 # ------------------------------
 # Here we will show you how to run the kernel on the remote device:
 #
-# .. note:: 
-#    In order to have this tutorial runs locally to build the nice HTML, we 
-#    start a RPC server on the local machine. You can ignore it if you already 
+# .. note::
+#    In order to have this tutorial runs locally to build the nice HTML, we
+#    start a RPC server on the local machine. You can ignore it if you already
 #    started the server on the target device. And then change host IP properly.
 
-# Can be ignored if you already started the RPC server 
-server = rpc.Server(host='0.0.0.0', port=9090, use_popen=True) 
-host = '0.0.0.0'  # Change to your target device IP 
+# Can be ignored if you already started the RPC server
+server = rpc.Server(host='0.0.0.0', port=9090, use_popen=True)
+host = '0.0.0.0'  # Change to your target device IP
 port = 9090
 # connect the remote device
 remote = rpc.connect(host, port)
@@ -237,7 +237,7 @@
 #    Firefly-RK3399. The target_host should be 'llvm -target=aarch64-linux-gnu'.
 #    But here we set 'llvm' to enable this tutorial to run locally.
 #
-#    Also we need to build the runtime with the flag `USE_OPENCL=1`. 
+#    Also we need to build the runtime with the flag `USE_OPENCL=1`.
 
 # build kernel (different from cpu, we need bind axis for OpenCL)
 s = tvm.create_schedule(B.op)

From 9fb25327dd29af681af1f63fa557feb3b8d5f852 Mon Sep 17 00:00:00 2001
From: Zhixun Tan <phisiart@gmail.com>
Date: Thu, 25 Jan 2018 13:35:24 -0500
Subject: [PATCH 109/949] [OpenGL] Let OpenGL texture always be 1024 x nrows.
 (#817)

* OpenGL texture is always 1024 x nrows.

* Address review comments.
---
 src/codegen/codegen_opengl.cc           | 11 +--
 src/runtime/opengl/opengl_device_api.cc | 91 +++++++++++++++++++++----
 src/runtime/opengl/opengl_module.h      | 13 ++++
 tests/webgl/test_local_gemm.py          |  2 +-
 4 files changed, 98 insertions(+), 19 deletions(-)

diff --git a/src/codegen/codegen_opengl.cc b/src/codegen/codegen_opengl.cc
index e645e7f6c701..496b15b34bfa 100644
--- a/src/codegen/codegen_opengl.cc
+++ b/src/codegen/codegen_opengl.cc
@@ -154,7 +154,8 @@ void CodeGenOpenGL::BindThreadIndex(const IterVar& iv) {
 
   // Declare threadIdx local variable.
   this->PrintIndent();
-  this->stream << "ivec2 threadIdx = ivec2(gl_FragCoord.xy);\n";
+  this->stream << "ivec2 threadIdx = ivec2(" << runtime::kTextureRowSize
+               << " * int(gl_FragCoord.y) + int(gl_FragCoord.x), 0);\n";
 
   // Return directly if threadIdx.x >= thread_extent.
   this->PrintIndent();
@@ -192,12 +193,14 @@ void CodeGenOpenGL::VisitStmt_(const Store* op) {
   }
 }
 
-// texelFetch(tex, ivec2(idx, 0), 0).r
+// texelFetch(tex, ivec2(idx & kTextureRowMask, idx >> kTextureRowBits), 0).r
 std::string CodeGenOpenGL::TexelFetch(const Variable* buffer, Expr index) {
   std::ostringstream os;
-  os << "texelFetch(" << GetVarID(buffer) << ", ivec2(";
+  os << "texelFetch(" << GetVarID(buffer) << ", ivec2(int(";
   PrintExpr(index, os);
-  os << ", 0), 0).r";
+  os << ") & " << runtime::kTextureRowMask << ", int(";
+  PrintExpr(index, os);
+  os << ") >> " << runtime::kTextureRowBits << "), 0).r";
   return os.str();
 }
 
diff --git a/src/runtime/opengl/opengl_device_api.cc b/src/runtime/opengl/opengl_device_api.cc
index 798003af902f..d90d12034ae6 100644
--- a/src/runtime/opengl/opengl_device_api.cc
+++ b/src/runtime/opengl/opengl_device_api.cc
@@ -3,6 +3,7 @@
  * \file opengl_device_api.cc
  */
 #include "./opengl_common.h"
+#include "./opengl_module.h"
 
 #if TVM_OPENGL_RUNTIME
 
@@ -347,8 +348,9 @@ Texture OpenGLWorkspace::CreateTexture(TVMType type, size_t nbytes) {
 
   // Use glTexImage2D with nullptr data to specify GPU data storage.
   auto texture_format = GetTextureFormat(type);
-  auto width = static_cast<GLsizei>(nbytes / (type.bits / 8));
-  auto height = GLsizei(1);
+  auto nelems = static_cast<GLsizei>(nbytes / (type.bits / 8));
+  auto height = (nelems + kTextureRowSize - 1) / kTextureRowSize;
+  auto width = (height == 1) ? nelems : kTextureRowSize;
   OPENGL_CALL(gl->TexImage2D(GL_TEXTURE_2D, /*level=*/0,
                              texture_format.internal_format,
                              width, height, /*border=*/0,
@@ -402,6 +404,51 @@ Program OpenGLWorkspace::CreateProgram(GLuint fragment_shader) {
   return Program(this, program);
 }
 
+/*!
+ * \brief Visit a 1D range of an OpenGL texture-backed TVM array.
+ * When getting/setting a sub image of a texture, we can only specify a 2D
+ * block (xbeg, ybeg, width, height).
+ * Since we are storing all TVM arrays using (kTextureRowSize x nrows) 2D
+ * textures (row-major), a range in an array does not necessarily map to a 2D
+ * block.
+ * This function split a 1D range into 3 2D blocks.
+ * \param beg The index of the first element in the 1D range.
+ * \param end The index of the last + 1 element in the 1D range.
+ * \param on_2d_block Callback for each 2D block. Must have interface
+ * void(GLint xbeg, GLint ybeg, GLsizei width, GLsizei height).
+ */
+template <typename F>
+static void Visit1DRange(GLint beg, GLint end, F&& on_2d_block) {
+  CHECK_LE(beg, end) << "Invalid range.";
+
+  //           xbeg         kTextureRowSize
+  // ybeg  ....************
+  //       ****************
+  //       ****************
+  // ylast *********.......
+  //           xlast
+  GLint xbeg = beg % kTextureRowSize;
+  GLint ybeg = beg / kTextureRowSize;
+  GLint xlast = (end - 1) % kTextureRowSize;
+  GLint ylast = (end - 1) / kTextureRowSize;
+
+  if (ybeg == ylast) {  // Only one row.
+    on_2d_block(xbeg, ybeg, end - beg, 1);
+    return;
+  }
+
+  // First row.
+  on_2d_block(xbeg, ybeg, kTextureRowSize - xbeg, 1);
+
+  // Middle block.
+  if (ylast - ybeg > 1) {
+    on_2d_block(0, ybeg + 1, kTextureRowSize, ylast - ybeg - 1);
+  }
+
+  // Last row.
+  on_2d_block(0, ylast, xlast + 1, 1);
+}
+
 void OpenGLWorkspace::PutTextureData(Texture *texture,
                                      GLint begin,
                                      GLsizei nelems,
@@ -409,12 +456,17 @@ void OpenGLWorkspace::PutTextureData(Texture *texture,
   // Bind to temporary unit.
   BindTextureUnit(NumTextureUnits() - 1, texture->texture());
 
-  // Similar to cudaMemcpy.
-  OPENGL_CALL(gl->TexSubImage2D(GL_TEXTURE_2D, /*level=*/0,
-                                /*xoffset=*/begin, /*yoffset=*/0,
-                                /*width=*/nelems, /*height=*/1,
-                                texture->format_.format, texture->format_.type,
-                                data));
+  Visit1DRange(begin, begin + nelems, [&](GLint xbeg, GLint ybeg,
+                                          GLsizei width, GLsizei height) {
+    auto offset = (ybeg * kTextureRowSize + xbeg - begin) * texture->elemsz();
+    const GLvoid* ptr = static_cast<const char*>(data) + offset;
+
+    // Similar to cudaMemcpy.
+    OPENGL_CALL(gl->TexSubImage2D(GL_TEXTURE_2D, /*level=*/0,
+                                  xbeg, ybeg, width, height,
+                                  texture->format_.format,
+                                  texture->format_.type, ptr));
+  });
 }
 
 void OpenGLWorkspace::GetTextureData(const Texture *texture,
@@ -453,18 +505,29 @@ void OpenGLWorkspace::GetTextureData(const Texture *texture,
   auto nchannels = 4;
   auto padded_data_size = nchannels * nelems * elemsz;
   auto padded_data = std::unique_ptr<char[]>(new char[padded_data_size]);
-  OPENGL_CALL(gl->ReadPixels(/*x=*/begin, /*y=*/0, /*width=*/nelems,
-                             /*height=*/1, GL_RGBA, GL_FLOAT,
-                             padded_data.get()));
+  Visit1DRange(begin, begin + nelems, [&](GLint xbeg, GLint ybeg,
+                                          GLsizei width, GLsizei height) {
+    auto data_offset = (ybeg * kTextureRowSize + xbeg - begin) * elemsz;
+    auto padded_data_offset = data_offset * nchannels;
+    OPENGL_CALL(gl->ReadPixels(xbeg, ybeg, width, height,
+                               GL_RGBA, GL_FLOAT,
+                               padded_data.get() + padded_data_offset));
+  });
   for (GLsizei i = 0; i != nelems; ++i) {
     auto dst = reinterpret_cast<char *>(data) + i * elemsz;
     auto src = padded_data.get() + nchannels * i * elemsz;
     std::memcpy(dst, src, elemsz);
   }
 #else
-  OPENGL_CALL(gl->ReadPixels(/*x=*/begin, /*y=*/0, /*width=*/nelems,
-                             /*height=*/1, texture->format_.format,
-                             texture->format_.type, data));
+  Visit1DRange(begin, begin + nelems, [&](GLint xbeg, GLint ybeg,
+                                          GLsizei width, GLsizei height) {
+    auto offset = (ybeg * kTextureRowSize + xbeg - begin) * texture->elemsz();
+    GLvoid* ptr = static_cast<char*>(data) + offset;
+
+    OPENGL_CALL(gl->ReadPixels(xbeg, ybeg, width, height,
+                               texture->format_.format, texture->format_.type,
+                               ptr));
+  });
 #endif
 
   OPENGL_CALL(gl->DeleteFramebuffers(1, &frame_buffer));
diff --git a/src/runtime/opengl/opengl_module.h b/src/runtime/opengl/opengl_module.h
index 1913878c3d68..a4cfa20bd734 100644
--- a/src/runtime/opengl/opengl_module.h
+++ b/src/runtime/opengl/opengl_module.h
@@ -17,6 +17,19 @@
 namespace tvm {
 namespace runtime {
 
+/*!
+ * \brief The fixed row size of all OpenGL textures in TVM.
+ *
+ * OpenGL has texture size limit on each dimension. Suppose we have a limit of
+ * 1024, then we can have a 2D texture of size (2^10 x 2^10) but not (2^20 x 1).
+ * This means we don't want to just use (n x 1) 2D textures for all arrays,
+ * because that would limit our array size to be 1024. Here we use (1024 x m)
+ * 2D textures. Then we can have arrays of size up to 2^20.
+ */
+static constexpr int kTextureRowBits = 10;
+static constexpr int kTextureRowSize = 1 << kTextureRowBits;
+static constexpr int kTextureRowMask = kTextureRowSize - 1;
+
 /*!
  * \brief Determines how we supply arguments.
  */
diff --git a/tests/webgl/test_local_gemm.py b/tests/webgl/test_local_gemm.py
index 18d2d1d8bf34..0dd1c0fc7376 100644
--- a/tests/webgl/test_local_gemm.py
+++ b/tests/webgl/test_local_gemm.py
@@ -7,7 +7,7 @@ def test_local_gemm():
     if not tvm.module.enabled("llvm"):
         return
 
-    nn = 2
+    nn = 1024
     n = tvm.var('n')
     n = tvm.convert(nn)
     m = n

From 2725b898211a8e59e1bff551862cacddc8c09041 Mon Sep 17 00:00:00 2001
From: Yida Wang <yidawa@gmail.com>
Date: Fri, 26 Jan 2018 13:02:57 -0800
Subject: [PATCH 110/949] minor tweak of the runtime doc to fix some
 grammatical and expression issues (#828)

---
 docs/dev/runtime.md | 54 ++++++++++++++++++++++-----------------------
 1 file changed, 27 insertions(+), 27 deletions(-)

diff --git a/docs/dev/runtime.md b/docs/dev/runtime.md
index b9cc81186200..d601801a435e 100644
--- a/docs/dev/runtime.md
+++ b/docs/dev/runtime.md
@@ -1,7 +1,7 @@
 # TVM Runtime System
 
-TVM support multiple programming languages for compiler stack development and deployment.
-In this note, we explain the key element of TVM runtime.
+TVM supports multiple programming languages for the compiler stack development and deployment.
+In this note, we explain the key elements of the TVM runtime.
 
 ![](http://www.tvmlang.org/images/release/tvm_flexible.png)
 
@@ -9,10 +9,10 @@ We need to satisfy quite a few interesting requirements
 
 - Deployment: invoke the compiled function from python/javascript/c++ language.
 - Debug: define a function in python and call that from a compiled function.
-- Link: write driver code to call device specific code(CUDA) and call it from compiled host function.
+- Link: write driver code to call device specific code (CUDA) and call it from compiled host function.
 - Prototype: define an IR pass from python and call that from C++ backend.
-- Expose:  compiler stack developed in c++ to front-end (i.e, python)
-- Experiment: ship a compiled function to an embedded device directly run there.
+- Expose: compiler stack developed in c++ to front-end (i.e, python)
+- Experiment: ship a compiled function to an embedded device to directly run there.
 
 We want to be able to define a function from any language and call from another.
 We also want the runtime core to be minimal to deploy to embedded devices.
@@ -41,11 +41,11 @@ void CallPacked() {
 ```
 In the above codeblock, we defined a PackedFunc MyAdd. It takes two arguments
 : ```args``` represents input arguments and ```rv``` represents return value.
-The function is type-erased, which means the function signature does not restrict which input type to pass in or type to return.
+The function is type-erased, which means that the function signature does not restrict which input type to pass in or type to return.
 Under the hood, when we call a PackedFunc, it packs the input arguments to TVMArgs on stack,
-and get the result back via TVMRetValue.
+and gets the result back via TVMRetValue.
 
-Thanks to template tricks in C++, we can call a PackedFunc just like a  normal function. Because of its type-erased nature, we can call a PackedFunc from dynamic languages like python, without additional glue code for each new type function created.
+Thanks to template tricks in C++, we can call a PackedFunc just like a normal function. Because of its type-erased nature, we can call a PackedFunc from dynamic languages like python, without additional glue code for each new type function created.
 The following example registers PackedFunc in C++ and calls from python.
 
 ```c++
@@ -74,7 +74,7 @@ The restriction makes the implementation simple without the need of serializatio
 Despite being minimum, the PackedFunc is sufficient for the use-case of deep learning deployment as
 most functions only take DLTensor or numbers.
 
-Since one PackedFunc can take another PackedFunc as argument,
+Since one PackedFunc can take another PackedFunc as an argument,
 we can pass functions from python(as PackedFunc) to C++.
 ```c++
 TVM_REGISTER_GLOBAL("callhello")
@@ -97,15 +97,15 @@ callhello(f)
 ```
 
 TVM provides a [minimum C API](https://github.com/dmlc/tvm/blob/master/include/tvm/runtime/c_runtime_api.h),
-that allows us to embedded the PackedFunc into any languages. Besides python, so far we supported
+which allows us to embed the PackedFunc into any languages. Besides python, so far we supported
 [java](https://github.com/dmlc/tvm/tree/master/jvm) and [javascript](https://github.com/dmlc/tvm/tree/master/web).
-This philosophy of embedded API is very like Lua, except that we don't have a new language and uses C++.
+This philosophy of embedded API is very like Lua, except that we don't have a new language but use C++.
 
 One fun fact about PackedFunc is that we use it for both compiler and deployment stack.
 - All TVM's compiler pass functions are exposed to frontend as PackedFunc, see [here](https://github.com/dmlc/tvm/tree/master/src/api)
-- The compiled modules also returns compiled function as PackedFunc
+- The compiled module also returns the compiled function as PackedFunc
 
-To keep the runtime minimum, we isolated the IR Node support from the deployment runtime. The resulting runtime takes around 200K - 600K depending on how many runtime driver modules(e.g., CUDA) get included.
+To keep the runtime minimum, we isolated the IR Node support from the deployment runtime. The resulting runtime takes around 200K - 600K depending on how many runtime driver modules (e.g., CUDA) get included.
 
 The overhead of calling into PackedFunc vs. a normal function is small, as it is only saving a few values on the stack.
 So it is OK as long as we don't wrap small functions.
@@ -113,9 +113,9 @@ In summary, the PackedFunc is the universal glue in TVM where we use it extensiv
 
 ## Module
 
-Since TVM support multiple types of devices, we need to support different type of drivers.
-We have to use driver API to load the kernel, set up the argument in packed format and perform kernel launch.
-We also need to patch up the driver API so that the exposed functions is threadsafe.
+Since TVM supports multiple types of devices, we need to support different type of drivers.
+We have to use the driver API to load the kernel, set up the argument in packed format and perform kernel launch.
+We also need to patch up the driver API so that the exposed functions are threadsafe.
 So we often need to implement these driver glues in C++ and expose them to the user.
 We can certainly not do it for each type of functions, so again PackedFunc is our answer.
 
@@ -130,32 +130,32 @@ of new device easy, and we do not need to redo the host code generation for each
 ## Remote Deployment
 
 The PackedFunc and Module system also makes it easy to ship the function into remote devices directly.
-Under the hood, we have a RPCModule that serializes the arguments and do the data movement and launches the computation on the remote.
+Under the hood, we have an RPCModule that serializes the arguments to do the data movement and launches the computation on the remote.
 
 ![](http://www.tvmlang.org/images/release/tvm_rpc.png)
 
 The RPC server itself is minimum and can be bundled into the runtime. We can start a minimum TVM
-RPC server on iPhone/android/raspberry pi or even your browser. The cross compilation on server and shipping of the module for testing can be done in the same script. Checkout
+RPC server on iPhone/android/raspberry pi or even the browser. The cross compilation on server and shipping of the module for testing can be done in the same script. Checkout
 [Cross compilation and RPC tutorial](http://docs.tvmlang.org/tutorials/deployment/cross_compilation_and_rpc.html#sphx-glr-tutorials-deployment-cross-compilation-and-rpc-py)  for more details.
 
-This instant feedback gives us a lot of advantages. For example, to test the correctness of generated code on iPhone, we no longer have to write test-cases in swift/objective-c from scratch -- We can use RPC to execute on iPhone copy the result back and do verification on my host via numpy. We can also do the profiling using the same script.
+This instant feedback gives us a lot of advantages. For example, to test the correctness of generated code on iPhone, we no longer have to write test-cases in swift/objective-c from scratch -- We can use RPC to execute on iPhone, copy the result back and do verification on the host via numpy. We can also do the profiling using the same script.
 
 ## TVM Node and Compiler Stack
 
 As we mentioned earlier, we build compiler stack API on top of the PackedFunc runtime system.
-We faced a constant changing the compiler API for the need of research. We need a new language object or IR node from now and then when we want to test out new primitives.
+We faced a constant changing of the compiler API for the need of research. We need a new language object or IR node whenever we want to test out new primitives.
 However, we don't want to change our API from time to time. Besides that, we also want to
 
 - be able to serialize any language object and IRs
 - be able to explore, print, and manipulate the IR objects in front-end language to do quick prototyping.
 
 We introduced a base class, called [Node](https://github.com/dmlc/HalideIR/blob/master/src/tvm/node.h#L52) to solve this problem.
-All the language object in compiler stack is a subclass of Node. Each node contains a string type_key that uniquely identifies
-the type of object. We choose string instead of int as type key so new Node class can be added in decentralized fashion without
+All the language object in the compiler stack is a subclass of Node. Each node contains a string type_key that uniquely identifies
+the type of object. We choose string instead of int as type key so new Node class can be added in the decentralized fashion without
 adding the code back to the central repo. To ease the speed of dispatching, we allocate an integer type_index at runtime for each type_key.
 
-Since usually one Node object could be referenced in multiple places in the language. We use a shared_ptr to keep
-track of reference. We use NodeRef class to represents a reference to the Node.
+Since usually one Node object could be referenced in multiple places in the language, we use a shared_ptr to keep
+track of reference. We use NodeRef class to represent a reference to the Node.
 We can roughly view NodeRef class as shared_ptr to the Node container.
 We can also define subclass NodeRef to hold each subtypes of Node. Each Node class needs to define the VisitAttr function.
 
@@ -206,7 +206,7 @@ class TensorNode : public Node {
 ```
 In the above examples, both ```Operation``` and ```Array<Expr>``` are NodeRef.
 The VisitAttrs gives us a reflection API to visit each member of the object.
-We can use this function to visit the node any serialize any language object recursively.
+We can use this function to visit the node and serialize any language object recursively.
 It also allows us to get members of an object easily in front-end language.
 For example, in the following code, we accessed the op field of the TensorNode.
 
@@ -220,13 +220,13 @@ print(x.op.name)
 
 New Node can be added to C++ without changing the front-end runtime, making it easy to make extensions to the compiler stack.
 Note that this is not the fastest way to expose members to front-end language, but might be one of the simplest
-approach possible. We also find it fits our purposes as we mainly use python for testing and prototyping and still use c++
+approaches possible. We also find that it fits our purposes as we mainly use python for testing and prototyping and still use c++
 to do the heavy lifting job.
 
 ## Implementation Details
 
 Each argument in PackedFunc contains a union value [TVMValue](https://github.com/dmlc/tvm/blob/master/include/tvm/runtime/c_runtime_api.h#L122)
-and a type code. This design allows the dynamically typed language to convert to the corresponding type directly, and statically typed language
+and a type code. This design allows the dynamically typed language to convert to the corresponding type directly, and statically typed language to
 do runtime type checking during conversion.
 
 The relevant files are

From 7e9b9f9babbf412d6a75e0327a9c35ec134f3059 Mon Sep 17 00:00:00 2001
From: kun-zh <32951065+kun-zh@users.noreply.github.com>
Date: Sun, 28 Jan 2018 01:10:42 +0800
Subject: [PATCH 111/949] support using pointer with an original offset (#826)

* when there is no intrin func, using body for initialization. For issue 714.

* Refine code per review comments, and add a test case.

* Fix lint issues.

* Re-organize the tensorize test cases, and add a new case for none-reset
mode.

* Fix a typo.

* Delete the unit case because merged it into test_schedule_tensorize.py already.

* always use new tensor in its stage when rewrite for cache read

* revert previous changes to sync up with master

* support using the ptr with an original offset

* update test case and fix CI error
---
 include/tvm/buffer.h                      |  3 ++-
 python/tvm/schedule.py                    |  8 ++++++--
 src/api/api_lang.cc                       |  2 +-
 src/lang/buffer.cc                        |  4 ++--
 tests/python/unittest/test_lang_buffer.py | 10 ++++++++++
 5 files changed, 21 insertions(+), 6 deletions(-)

diff --git a/include/tvm/buffer.h b/include/tvm/buffer.h
index f2790f6df7d1..d737341e1c0e 100644
--- a/include/tvm/buffer.h
+++ b/include/tvm/buffer.h
@@ -52,9 +52,10 @@ class Buffer : public NodeRef {
    * \param access_mask The access mask
    * \param ptr_type The type of the pointer.
    * \param content_lanes The number of lanes for the (data) type.
+   * \param offset The offset of ptr.
    */
   TVM_DLL Expr access_ptr(int access_mask, Type ptr_type = Handle(),
-                          int content_lanes = 1) const;
+                          int content_lanes = 1, int offset = 0) const;
   /*!
    * \brief Create an Expr that does a vector load at begin index.
    * \param begin The beginning index
diff --git a/python/tvm/schedule.py b/python/tvm/schedule.py
index 0fc6692d950e..dda5f67d1b89 100644
--- a/python/tvm/schedule.py
+++ b/python/tvm/schedule.py
@@ -25,7 +25,7 @@ class Buffer(NodeBase):
     READ = 1
     WRITE = 2
 
-    def access_ptr(self, access_mask, ptr_type="handle", content_lanes=1):
+    def access_ptr(self, access_mask, ptr_type="handle", content_lanes=1, offset=0):
         """Get an access pointer to the head of buffer.
 
         This is the recommended method to get buffer data
@@ -45,6 +45,10 @@ def access_ptr(self, access_mask, ptr_type="handle", content_lanes=1):
             The number of lanes for the data type. This value
             is greater than one for vector types.
 
+        offset: int, optional
+            The offset of pointer. We can use it to offset by
+            the number of elements from the address of ptr.
+
         Examples
         --------
         .. code-block:: python
@@ -68,7 +72,7 @@ def access_ptr(self, access_mask, ptr_type="handle", content_lanes=1):
                     raise ValueError("Unknown access_mask %s" % access_mask)
             access_mask = mask
         return _api_internal._BufferAccessPtr(self, access_mask, ptr_type,
-                                              content_lanes)
+                                              content_lanes, offset)
 
     def vload(self, begin, dtype=None):
         """Generate an Expr that loads dtype from begin index.
diff --git a/src/api/api_lang.cc b/src/api/api_lang.cc
index 37a21cedf3db..3b5916ea5fec 100644
--- a/src/api/api_lang.cc
+++ b/src/api/api_lang.cc
@@ -159,7 +159,7 @@ TVM_REGISTER_API("_Buffer")
 TVM_REGISTER_API("_BufferAccessPtr")
 .set_body([](TVMArgs args,  TVMRetValue* ret) {
     *ret = args[0].operator Buffer()
-        .access_ptr(args[1], args[2], args[3]);
+        .access_ptr(args[1], args[2], args[3], args[4]);
   });
 
 TVM_REGISTER_API("_BufferVLoad")
diff --git a/src/lang/buffer.cc b/src/lang/buffer.cc
index af76dcc94f71..07e455e25384 100644
--- a/src/lang/buffer.cc
+++ b/src/lang/buffer.cc
@@ -335,7 +335,7 @@ Buffer Buffer::MakeSlice(Array<Expr> begins, Array<Expr> extents) const {
                           0);
 }
 
-Expr Buffer::access_ptr(int access_mask, Type ptr_type, int content_lanes) const {
+Expr Buffer::access_ptr(int access_mask, Type ptr_type, int content_lanes, int offset) const {
   const BufferNode* self = operator->();
   Expr e_dtype;
   Expr extent;
@@ -348,7 +348,7 @@ Expr Buffer::access_ptr(int access_mask, Type ptr_type, int content_lanes) const
   } else {
     extent = arith::ComputeReduce<ir::Mul>(self->shape, Expr());
   }
-  Expr elem_offset = self->elem_offset;
+  Expr elem_offset = self->elem_offset + offset;
   if (content_lanes > 1) {
     e_dtype = make_zero(self->dtype.with_lanes(content_lanes));
     extent = extent / make_const(self->elem_offset.type(), content_lanes);
diff --git a/tests/python/unittest/test_lang_buffer.py b/tests/python/unittest/test_lang_buffer.py
index c3f00ac2f166..fe0f1f0b759c 100644
--- a/tests/python/unittest/test_lang_buffer.py
+++ b/tests/python/unittest/test_lang_buffer.py
@@ -23,6 +23,15 @@ def test_buffer_access_ptr():
     aptr = Ab.access_ptr("w")
     assert aptr.args[4].value == Buffer.WRITE
 
+def test_buffer_access_ptr_offset():
+    m = tvm.var('m')
+    n = tvm.var('n')
+    Ab = tvm.decl_buffer((m, n), tvm.float32)
+    aptr = Ab.access_ptr("rw", offset=100)
+    offset = tvm.ir_pass.Simplify(aptr.args[2])
+    assert tvm.ir_pass.Equal(offset, 100)
+    assert aptr.args[4].value == Buffer.READ | Buffer.WRITE
+
 def test_buffer_index_merge_mult_mod():
     m = tvm.var('m')
     n = tvm.var('n')
@@ -57,4 +66,5 @@ def assert_simplified_equal(index_simplified, index_direct):
 if __name__ == "__main__":
     test_buffer()
     test_buffer_access_ptr()
+    test_buffer_access_ptr_offset()
     test_buffer_index_merge_mult_mod()

From c73b5901b5714dec30ddd75c7bf89d43a1132a8d Mon Sep 17 00:00:00 2001
From: Mu Li <mli@amazon.com>
Date: Sat, 27 Jan 2018 09:56:02 -0800
Subject: [PATCH 112/949] add the link for how to setup rk3399 opencl driver
 (#827)

---
 tutorials/deployment/cross_compilation_and_rpc.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/tutorials/deployment/cross_compilation_and_rpc.py b/tutorials/deployment/cross_compilation_and_rpc.py
index eec2f9aeb34b..ccaf9e79e3de 100644
--- a/tutorials/deployment/cross_compilation_and_rpc.py
+++ b/tutorials/deployment/cross_compilation_and_rpc.py
@@ -234,7 +234,10 @@
 # .. note::
 #
 #    Raspberry Pi does not support OpenCL, the following code is tested on
-#    Firefly-RK3399. The target_host should be 'llvm -target=aarch64-linux-gnu'.
+#    Firefly-RK3399. You may follow this `tutorial <https://gist.github.com/mli/585aed2cec0b5178b1a510f9f236afa2>`_
+#    to setup the RK3399 OS and OpenCL driver.
+#
+#    The target_host should be 'llvm -target=aarch64-linux-gnu'.
 #    But here we set 'llvm' to enable this tutorial to run locally.
 #
 #    Also we need to build the runtime with the flag `USE_OPENCL=1`.

From 737b35597299da9ac4f92f5d1a8dfc037f5d6d09 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 27 Jan 2018 10:45:28 -0800
Subject: [PATCH 113/949] [TIMER] Enhance time evaluator to create multiple
 results (#830)

---
 python/tvm/module.py                   | 22 ++++++++++-----
 src/runtime/rpc/rpc_module.cc          |  9 ++++---
 src/runtime/rpc/rpc_session.cc         | 37 ++++++++++++++++----------
 src/runtime/rpc/rpc_session.h          | 11 +++++---
 tests/python/integration/test_ewise.py |  9 ++++---
 5 files changed, 56 insertions(+), 32 deletions(-)

diff --git a/python/tvm/module.py b/python/tvm/module.py
index 055ae7308987..d8b018b824f0 100644
--- a/python/tvm/module.py
+++ b/python/tvm/module.py
@@ -1,13 +1,14 @@
 """Container of compiled functions of TVM."""
 from __future__ import absolute_import as _abs
 
+import struct
 from collections import namedtuple
 
 from ._ffi.function import ModuleBase, _set_class_module
 from ._ffi.function import _init_api
 from .contrib import cc as _cc, tar as _tar, util as _util
 
-ProfileResult = namedtuple("ProfileResult", ["mean"])
+ProfileResult = namedtuple("ProfileResult", ["mean", "results"])
 
 
 class Module(ModuleBase):
@@ -110,7 +111,7 @@ def export_library(self,
                 fcompile = _cc.create_shared
         fcompile(file_name, files, **kwargs)
 
-    def time_evaluator(self, func_name, ctx, number):
+    def time_evaluator(self, func_name, ctx, number, repeat=1):
         """Get an evaluator that measures time cost of running function.
 
         Parameters
@@ -122,11 +123,15 @@ def time_evaluator(self, func_name, ctx, number):
             The context we should run this function on.
 
         number: int
-            The number of repeative times to run evaluation.
+            The number of steps used in measuring each time interval
+
+        repeat: int, optional
+            Number of times to run the timer measurement
+            If repeat equals 3, then we will get 3 numbers in the ProfileResult.
 
         Note
         ----
-        The function will be invoked number + 1 times,
+        The function will be invoked  repeat * number + 1 times,
         with the first call discarded in case there is lazy initialization.
 
         Returns
@@ -137,13 +142,16 @@ def time_evaluator(self, func_name, ctx, number):
         """
         try:
             feval = _RPCTimeEvaluator(
-                self, func_name, ctx.device_type, ctx.device_id, number)
+                self, func_name, ctx.device_type, ctx.device_id, number, repeat)
 
             def evaluator(*args):
                 """Internal wrapped evaluator."""
                 # Wrap feval so we can add more stats in future.
-                mean = feval(*args)
-                return ProfileResult(mean=mean)
+                blob = feval(*args)
+                fmt = "@" + ("d" * repeat)
+                results = struct.unpack(fmt, blob)
+                mean = sum(results) / float(repeat)
+                return ProfileResult(mean=mean, results=results)
 
             return evaluator
         except NameError:
diff --git a/src/runtime/rpc/rpc_module.cc b/src/runtime/rpc/rpc_module.cc
index af2e0647871d..9b4b3658c1be 100644
--- a/src/runtime/rpc/rpc_module.cc
+++ b/src/runtime/rpc/rpc_module.cc
@@ -77,10 +77,11 @@ class RPCModuleNode final : public ModuleNode {
 
   PackedFunc GetTimeEvaluator(const std::string& name,
                               TVMContext ctx,
-                              int nstep) {
+                              int number,
+                              int repeat) {
     RPCFuncHandle handle = GetFuncHandle(name);
     if (handle == nullptr) return PackedFunc();
-    handle = sess_->GetTimeEvaluator(handle, ctx, nstep);
+    handle = sess_->GetTimeEvaluator(handle, ctx, number, repeat);
     return WrapRemote(handle);
   }
 
@@ -148,10 +149,10 @@ TVM_REGISTER_GLOBAL("module._RPCTimeEvaluator")
     ctx.device_id = args[3];
     if (tkey == "rpc") {
       *rv = static_cast<RPCModuleNode*>(m.operator->())
-          ->GetTimeEvaluator(args[1], ctx, args[4]);
+          ->GetTimeEvaluator(args[1], ctx, args[4], args[5]);
     } else {
       *rv = WrapTimeEvaluator(
-          m.GetFunction(args[1], false), ctx, args[4]);
+          m.GetFunction(args[1], false), ctx, args[4], args[5]);
     }
   });
 
diff --git a/src/runtime/rpc/rpc_session.cc b/src/runtime/rpc/rpc_session.cc
index 3bff73afe9bd..3d00371c034c 100644
--- a/src/runtime/rpc/rpc_session.cc
+++ b/src/runtime/rpc/rpc_session.cc
@@ -844,8 +844,9 @@ void RPCSession::CopyFromRemote(void* from,
 }
 
 RPCFuncHandle RPCSession::GetTimeEvaluator(
-    RPCFuncHandle fhandle, TVMContext ctx, int nstep) {
-  return this->CallRemote(RPCCode::kGetTimeEvaluator, fhandle, ctx, nstep);
+    RPCFuncHandle fhandle, TVMContext ctx, int number, int repeat) {
+  return this->CallRemote(
+      RPCCode::kGetTimeEvaluator, fhandle, ctx, number, repeat);
 }
 
 // Event handler functions
@@ -973,7 +974,7 @@ void RPCModuleGetSource(TVMArgs args, TVMRetValue *rv) {
 
 void RPCGetTimeEvaluator(TVMArgs args, TVMRetValue *rv) {
   PackedFunc *pf = static_cast<PackedFunc*>(args[0].operator void*());
-  void *fhandle = new PackedFunc(WrapTimeEvaluator(*pf, args[1], args[2]));
+  void *fhandle = new PackedFunc(WrapTimeEvaluator(*pf, args[1], args[2], args[3]));
   delete pf;
   *rv = fhandle;
 }
@@ -1024,23 +1025,31 @@ void RPCSession::EventHandler::HandlePackedCall() {
   CHECK_EQ(state_, kRecvCode);
 }
 
-PackedFunc WrapTimeEvaluator(PackedFunc pf, TVMContext ctx, int nstep) {
-  auto ftimer = [pf, ctx, nstep](TVMArgs args, TVMRetValue *rv) {
+PackedFunc WrapTimeEvaluator(PackedFunc pf, TVMContext ctx, int number, int repeat) {
+  auto ftimer = [pf, ctx, number, repeat](TVMArgs args, TVMRetValue *rv) {
     TVMRetValue temp;
+    std::ostringstream os;
     // skip first time call, to activate lazy compilation components.
     pf.CallPacked(args, &temp);
     DeviceAPI::Get(ctx)->StreamSync(ctx, nullptr);
-    // start timing
-    auto tbegin = std::chrono::high_resolution_clock::now();
-    for (int i = 0; i < nstep; ++i) {
-      pf.CallPacked(args, &temp);
+    for (int i = 0; i < repeat; ++i) {
+      // start timing
+      auto tbegin = std::chrono::high_resolution_clock::now();
+      for (int i = 0; i < number; ++i) {
+        pf.CallPacked(args, &temp);
+      }
+      DeviceAPI::Get(ctx)->StreamSync(ctx, nullptr);
+      auto tend = std::chrono::high_resolution_clock::now();
+      double speed = std::chrono::duration_cast<std::chrono::duration<double> >(
+          tend - tbegin).count() / number;
+      os.write(reinterpret_cast<char*>(&speed), sizeof(speed));
     }
-    DeviceAPI::Get(ctx)->StreamSync(ctx, nullptr);
-    auto tend = std::chrono::high_resolution_clock::now();
-    double speed = std::chrono::duration_cast<std::chrono::duration<double> >(
-        tend - tbegin).count() / nstep;
+    std::string blob = os.str();
+    TVMByteArray arr;
+    arr.size = blob.length();
+    arr.data = blob.data();
     // return the time.
-    *rv = speed;
+    *rv = arr;
   };
   return PackedFunc(ftimer);
 }
diff --git a/src/runtime/rpc/rpc_session.h b/src/runtime/rpc/rpc_session.h
index 80dde9171401..59c1c1016ae2 100644
--- a/src/runtime/rpc/rpc_session.h
+++ b/src/runtime/rpc/rpc_session.h
@@ -146,12 +146,14 @@ class RPCSession {
    *
    * \param fhandle The function handle.
    * \param ctx The ctx to run measurement on.
-   * \param nstep Number of steps to run.
+   * \param number How many steps to run in each time evaluation
+   * \param repeat How many times to repeat the timer
    * \return A remote timer function
    */
   RPCFuncHandle GetTimeEvaluator(RPCFuncHandle fhandle,
                                  TVMContext ctx,
-                                 int nstep);
+                                 int number,
+                                 int repeat);
   /*!
    * \brief Call a remote defined system function with arguments.
    * \param fcode The function code.
@@ -212,9 +214,10 @@ class RPCSession {
  * \brief Wrap a timer function for a given packed function.
  * \param f The function argument.
  * \param ctx The context.
- * \param nstep Number of repeative steps.
+ * \param number Number of steps in the inner iteration
+ * \param repeat How many steps to repeat the time evaluation.
  */
-PackedFunc WrapTimeEvaluator(PackedFunc f, TVMContext ctx, int nstep);
+PackedFunc WrapTimeEvaluator(PackedFunc f, TVMContext ctx, int number, int repeat);
 
 /*!
  * \brief Create a Global RPC module that refers to the session.
diff --git a/tests/python/integration/test_ewise.py b/tests/python/integration/test_ewise.py
index f8dc43da8d31..e56fac6734bb 100644
--- a/tests/python/integration/test_ewise.py
+++ b/tests/python/integration/test_ewise.py
@@ -55,7 +55,10 @@ def test_log_pow_llvm():
     n = 1028
     a = tvm.nd.array(np.random.uniform(size=n).astype(A.dtype), ctx)
     b = tvm.nd.array(np.zeros(n, dtype=B.dtype), ctx)
-    flog(a, b)
+    repeat = 10
+    ftimer = flog.time_evaluator(flog.entry_name, ctx, number=1, repeat=repeat)
+    res = ftimer(a, b)
+    assert(len(res.results) == repeat)
     np.testing.assert_allclose(
         b.asnumpy(), np.power(np.log(a.asnumpy()), 2.0), rtol=1e-5)
 
@@ -146,7 +149,7 @@ def check_device(device):
 
 
 if __name__ == "__main__":
-    test_add()
     test_log_pow_llvm()
-    test_popcount()
     test_exp()
+    test_add()
+    test_popcount()

From 7b9285b8b5a8980bbc8caebf01e7468484a5ba04 Mon Sep 17 00:00:00 2001
From: Siva <sivar.b@huawei.com>
Date: Sun, 28 Jan 2018 11:20:04 +0530
Subject: [PATCH 114/949] [DEBUG] get_node_output : To retrieve out put of any
 node - for debug purpose. (#820)

---
 Makefile                            |  4 +++
 make/config.mk                      |  3 ++
 python/tvm/contrib/graph_runtime.py | 21 +++++++++++++
 src/runtime/graph/graph_runtime.cc  | 47 +++++++++++++++++++++++++++++
 4 files changed, 75 insertions(+)

diff --git a/Makefile b/Makefile
index 453415de6634..44a500d26ce3 100644
--- a/Makefile
+++ b/Makefile
@@ -151,6 +151,10 @@ ifeq ($(USE_GRAPH_RUNTIME), 1)
 	RUNTIME_DEP += $(GRAPH_OBJ)
 endif
 
+ifeq ($(USE_GRAPH_RUNTIME_DEBUG), 1)
+	CFLAGS += -DTVM_GRAPH_RUNTIME_DEBUG
+endif
+
 include make/contrib/cblas.mk
 include make/contrib/random.mk
 include make/contrib/nnpack.mk
diff --git a/make/config.mk b/make/config.mk
index 256771ac3220..eee96ac12360 100644
--- a/make/config.mk
+++ b/make/config.mk
@@ -50,6 +50,9 @@ USE_RPC = 1
 # Whether enable tiny embedded graph runtime.
 USE_GRAPH_RUNTIME = 1
 
+#  Whether enable additional graph debug functions
+USE_GRAPH_RUNTIME_DEBUG = 0
+
 # whether build with LLVM support
 # Requires LLVM version >= 4.0
 # Set LLVM_CONFIG to your version, uncomment to build with llvm support
diff --git a/python/tvm/contrib/graph_runtime.py b/python/tvm/contrib/graph_runtime.py
index 7e919586b0c0..ddabac004993 100644
--- a/python/tvm/contrib/graph_runtime.py
+++ b/python/tvm/contrib/graph_runtime.py
@@ -72,6 +72,10 @@ def __init__(self, module, ctx):
         self._set_input = module["set_input"]
         self._run = module["run"]
         self._get_output = module["get_output"]
+        try:
+            self._debug_get_output = module["debug_get_output"]
+        except AttributeError:
+            pass
         self._load_params = module["load_params"]
         self.ctx = ctx
 
@@ -121,6 +125,23 @@ def get_output(self, index, out):
         self._get_output(index, out)
         return out
 
+    def debug_get_output(self, node, out):
+        """Run graph upto node and get the output to out
+
+        Parameters
+        ----------
+        node : int / str
+            The node index or name
+
+        out : NDArray
+            The output array container
+        """
+        if hasattr(self, '_debug_get_output'):
+            self._debug_get_output(node, out)
+        else:
+            raise RuntimeError("Please compile runtime with USE_GRAPH_RUNTIME_DEBUG = 0")
+        return out
+
     def load_params(self, params_bytes):
         """Load parameters from serialized byte array of parameter dict.
 
diff --git a/src/runtime/graph/graph_runtime.cc b/src/runtime/graph/graph_runtime.cc
index d244fe5f028e..bf07a8c38927 100644
--- a/src/runtime/graph/graph_runtime.cc
+++ b/src/runtime/graph/graph_runtime.cc
@@ -107,7 +107,44 @@ class GraphRuntime : public ModuleNode {
     uint32_t eid = this->entry_id(outputs_[index]);
     TVM_CCALL(TVMArrayCopyFromTo(&data_entry_[eid], data_out, nullptr));
   }
+#ifdef TVM_GRAPH_RUNTIME_DEBUG
+  /*!
+   * \brief Get the node index given the name of node.
+   * \param name The name of the node.
+   * \return The index of node.
+   */
+  int GetNodeIndex(const std::string& name) {
+    for (uint32_t nid = 0; nid< nodes_.size(); ++nid) {
+      if (nodes_[nid].name == name) {
+        return static_cast<int>(nid);
+      }
+    }
+    LOG(FATAL) << "cannot find " << name << " among nodex";
+    return -1;
+  }
 
+  /*!
+   * \brief Copy index-th node to data_out.
+   *
+   * This method will do a partial run of the the graph
+   * from begining upto the index-th node and return output of index-th node.
+   * This is costly operation and suggest to use only for debug porpose.
+   *
+   * \param index: The  index of the node.
+   * \param data_out the node data.
+   */
+  void DebugGetNodeOutput(int index, DLTensor* data_out) {
+    CHECK_LT(static_cast<size_t>(index), nodes_.size());
+    uint32_t eid = index;
+
+    for (size_t i = 0; i < op_execs_.size(); ++i) {
+      if (static_cast<int>(i) == index) break;
+      if (op_execs_[i]) op_execs_[i]();
+    }
+
+    TVM_CCALL(TVMArrayCopyFromTo(&data_entry_[eid], data_out, nullptr));
+  }
+#endif
   /*!
    * \brief Load parameters from binary stream
    * \param strm The input stream.
@@ -556,6 +593,16 @@ PackedFunc GraphRuntime::GetFunction(
     return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
         this->GetOutput(args[0], args[1]);
       });
+#ifdef TVM_GRAPH_RUNTIME_DEBUG
+  } else if (name == "debug_get_output") {
+    return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
+        if (args[0].type_code() == kStr) {
+          this->DebugGetNodeOutput(this->GetNodeIndex(args[0]), args[1]);
+        } else {
+          this->DebugGetNodeOutput(args[0], args[1]);
+        }
+      });
+#endif
   } else if (name == "run") {
     return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
         this->Run();

From 7d60a4b2cd18de997d064b9f9252f3889c5812a8 Mon Sep 17 00:00:00 2001
From: Zhixun Tan <phisiart@gmail.com>
Date: Sun, 28 Jan 2018 00:50:52 -0500
Subject: [PATCH 115/949] Add type code and bits to AllocWorkspace. (#831)

---
 include/tvm/runtime/c_backend_api.h      | 10 ++++++--
 include/tvm/runtime/device_api.h         |  8 +++++--
 src/codegen/codegen_opengl.cc            |  2 ++
 src/codegen/stack_vm/codegen_stack_vm.cc |  4 +++-
 src/codegen/stack_vm/stack_vm.cc         | 15 +++++++-----
 src/pass/lower_tvm_builtin.cc            | 20 +++++++++-------
 src/pass/split_host_device.cc            |  5 ++++
 src/runtime/c_runtime_api.cc             | 21 +++++++++++++----
 src/runtime/cpu_device_api.cc            |  6 +++--
 src/runtime/cuda/cuda_device_api.cc      |  2 +-
 src/runtime/metal/metal_common.h         |  2 +-
 src/runtime/metal/metal_device_api.mm    |  4 +++-
 src/runtime/opencl/opencl_common.h       |  2 +-
 src/runtime/opencl/opencl_device_api.cc  |  4 +++-
 src/runtime/opengl/opengl_common.h       |  2 --
 src/runtime/opengl/opengl_device_api.cc  |  9 -------
 src/runtime/rocm/rocm_device_api.cc      |  2 +-
 tests/webgl/test_local_multi_stage.py    | 30 ++++++++++++++++++++++++
 18 files changed, 105 insertions(+), 43 deletions(-)
 create mode 100644 tests/webgl/test_local_multi_stage.py

diff --git a/include/tvm/runtime/c_backend_api.h b/include/tvm/runtime/c_backend_api.h
index e512921c969e..079ab1efb040 100644
--- a/include/tvm/runtime/c_backend_api.h
+++ b/include/tvm/runtime/c_backend_api.h
@@ -44,14 +44,20 @@ TVM_DLL int TVMBackendRegisterSystemLibSymbol(const char* name, void* ptr);
  *
  * \note The result allocate spaced is ensured to be aligned to kTempAllocaAlignment.
  *
- * \param size The size of the space requested.
+ * \param nbytes The size of the space requested.
  * \param device_type The device type which the space will be allocated.
  * \param device_id The device id which the space will be allocated.
+ * \param dtype_code_hint The type code of the array elements. Only used in
+ * certain backends such as OpenGL.
+ * \param dtype_bits_hint The type bits of the array elements. Only used in
+ * certain backends such as OpenGL.
  * \return nullptr when error is thrown, a valid ptr if success
  */
 TVM_DLL void* TVMBackendAllocWorkspace(int device_type,
                                        int device_id,
-                                       uint64_t size);
+                                       uint64_t nbytes,
+                                       int dtype_code_hint,
+                                       int dtype_bits_hint);
 
 /*!
  * \brief Backend function to free temporal workspace.
diff --git a/include/tvm/runtime/device_api.h b/include/tvm/runtime/device_api.h
index 9ba08fb86825..45009f1d3af3 100644
--- a/include/tvm/runtime/device_api.h
+++ b/include/tvm/runtime/device_api.h
@@ -114,9 +114,13 @@ class DeviceAPI {
    *  - Workspace should not overlap between different threads(i.e. be threadlocal)
    *
    * \param ctx The context of allocation.
-   * \param size The size to be allocated.
+   * \param nbytes The size to be allocated.
+   * \param type_hint The type of elements. Only needed by certain backends such
+   * as OpenGL, as nbytes is sufficient for most backends.
    */
-  TVM_DLL virtual void* AllocWorkspace(TVMContext ctx, size_t size);
+  TVM_DLL virtual void* AllocWorkspace(TVMContext ctx,
+                                       size_t nbytes,
+                                       TVMType type_hint = {});
   /*!
    * \brief Free temporal workspace in backend execution.
    *
diff --git a/src/codegen/codegen_opengl.cc b/src/codegen/codegen_opengl.cc
index 496b15b34bfa..696082749a37 100644
--- a/src/codegen/codegen_opengl.cc
+++ b/src/codegen/codegen_opengl.cc
@@ -24,6 +24,8 @@ void CodeGenOpenGL::InitFuncState(LoweredFunc f) {
   inputs_.clear();
   output_iter_var_ = nullptr;
   thread_extent_var_ = "";
+  this->decl_stream.str("");
+  this->stream.str("");
 }
 
 void CodeGenOpenGL::AddFunction(LoweredFunc f) {
diff --git a/src/codegen/stack_vm/codegen_stack_vm.cc b/src/codegen/stack_vm/codegen_stack_vm.cc
index 5b01dae7100a..168e411fa6e2 100644
--- a/src/codegen/stack_vm/codegen_stack_vm.cc
+++ b/src/codegen/stack_vm/codegen_stack_vm.cc
@@ -197,10 +197,12 @@ void CodeGenStackVM::VisitExpr_(const Call* op) {
     vm_.stack_size += size;
     this->PushOp(StackVM::TVM_STACK_ALLOCA_BY_8BYTE, static_cast<int>(size));
   } else if (op->name == "TVMBackendAllocWorkspace") {
-    CHECK_EQ(op->args.size(), 3U);
+    CHECK_EQ(op->args.size(), 5U);
     this->Push(op->args[0]);
     this->Push(op->args[1]);
     this->Push(op->args[2]);
+    this->Push(op->args[3]);
+    this->Push(op->args[4]);
     this->PushOp(StackVM::TVM_DEVICE_ALLOCA);
   } else if (op->name == "TVMBackendFreeWorkspace") {
     CHECK_EQ(op->args.size(), 3U);
diff --git a/src/codegen/stack_vm/stack_vm.cc b/src/codegen/stack_vm/stack_vm.cc
index a133c9797b1b..95feeae3679e 100644
--- a/src/codegen/stack_vm/stack_vm.cc
+++ b/src/codegen/stack_vm/stack_vm.cc
@@ -455,12 +455,15 @@ void StackVM::Run(State* s) const {
         break;
       }
       case TVM_DEVICE_ALLOCA: {
-        int device_type = static_cast<int>(stack[sp - 2].v_int64);
-        int device_id = static_cast<int>(stack[sp - 1].v_int64);
-        size_t nbytes = static_cast<size_t>(stack[sp].v_int64);
-        void* ptr = TVMBackendAllocWorkspace(device_type, device_id, nbytes);
-        stack[sp - 2].v_handle = ptr;
-        sp = sp - 2;
+        int device_type = static_cast<int>(stack[sp - 4].v_int64);
+        int device_id = static_cast<int>(stack[sp - 3].v_int64);
+        size_t nbytes = static_cast<size_t>(stack[sp - 2].v_int64);
+        int dtype_code_hint = static_cast<int>(stack[sp - 1].v_int64);
+        int dtype_bits_hint = static_cast<int>(stack[sp].v_int64);
+        void* ptr = TVMBackendAllocWorkspace(device_type, device_id, nbytes,
+                                             dtype_code_hint, dtype_bits_hint);
+        stack[sp - 4].v_handle = ptr;
+        sp = sp - 4;
         pc = pc + 1;
         break;
       }
diff --git a/src/pass/lower_tvm_builtin.cc b/src/pass/lower_tvm_builtin.cc
index 105d58b95829..a63fef07bd12 100644
--- a/src/pass/lower_tvm_builtin.cc
+++ b/src/pass/lower_tvm_builtin.cc
@@ -96,14 +96,18 @@ class BuiltinLower : public IRMutator {
                                     {op->buffer_var}, Call::PureIntrinsic),
                          throw_last_error),
         op->body);
-    Stmt alloca = LetStmt::make(op->buffer_var,
-                                Call::make(op->buffer_var.type(),
-                                           "TVMBackendAllocWorkspace",
-                                           {cast(Int(32), device_type_),
-                                                 cast(Int(32), device_id_),
-                                                 cast(UInt(64), total_bytes)},
-                                           Call::Extern),
-                                body);
+
+    Stmt alloca = LetStmt::make(
+        op->buffer_var,
+        Call::make(op->buffer_var.type(),
+                   "TVMBackendAllocWorkspace",
+                   {cast(Int(32), device_type_),
+                    cast(Int(32), device_id_),
+                    cast(UInt(64), total_bytes),
+                    IntImm::make(Int(32), op->type.code()),
+                    IntImm::make(Int(32), op->type.bits())},
+                   Call::Extern),
+        body);
 
     Expr free_op = Call::make(Int(32),
                               "TVMBackendFreeWorkspace",
diff --git a/src/pass/split_host_device.cc b/src/pass/split_host_device.cc
index 942e70339488..dc326f3cb2f1 100644
--- a/src/pass/split_host_device.cc
+++ b/src/pass/split_host_device.cc
@@ -146,6 +146,11 @@ class IRUseDefAnalysis : public IRMutator {
 
 class HostDeviceSplitter : public IRMutator {
  public:
+  Stmt Mutate_(const Allocate* op, const Stmt& s) final {
+    handle_data_type_[op->buffer_var.get()] = make_const(op->type, 0);
+    return IRMutator::Mutate_(op, s);
+  }
+
   Stmt Mutate_(const AttrStmt *op, const Stmt& s) final {
     if (op->attr_key == attr::thread_extent ||
         op->attr_key == attr::pipeline_exec_scope) {
diff --git a/src/runtime/c_runtime_api.cc b/src/runtime/c_runtime_api.cc
index 0d0e36f239f2..2177fc344889 100644
--- a/src/runtime/c_runtime_api.cc
+++ b/src/runtime/c_runtime_api.cc
@@ -95,8 +95,9 @@ DeviceAPI* DeviceAPI::Get(TVMContext ctx, bool allow_missing) {
       static_cast<int>(ctx.device_type), allow_missing);
 }
 
-void* DeviceAPI::AllocWorkspace(TVMContext ctx, size_t size) {
-  TVMType type_hint{kDLUInt, 8, 1};
+void* DeviceAPI::AllocWorkspace(TVMContext ctx,
+                                size_t size,
+                                TVMType type_hint) {
   return AllocDataSpace(ctx, size, kTempAllocaAlignment, type_hint);
 }
 
@@ -220,12 +221,22 @@ int TVMBackendGetFuncFromEnv(void* mod_node,
 }
 
 void* TVMBackendAllocWorkspace(int device_type,
-                             int device_id,
-                             uint64_t size) {
+                               int device_id,
+                               uint64_t size,
+                               int dtype_code_hint,
+                               int dtype_bits_hint) {
   TVMContext ctx;
   ctx.device_type = static_cast<DLDeviceType>(device_type);
   ctx.device_id = device_id;
-  return DeviceAPIManager::Get(ctx)->AllocWorkspace(ctx, static_cast<size_t>(size));
+
+  TVMType type_hint;
+  type_hint.code = static_cast<decltype(type_hint.code)>(dtype_code_hint);
+  type_hint.bits = static_cast<decltype(type_hint.bits)>(dtype_bits_hint);
+  type_hint.lanes = 1;
+
+  return DeviceAPIManager::Get(ctx)->AllocWorkspace(ctx,
+                                                    static_cast<size_t>(size),
+                                                    type_hint);
 }
 
 int TVMBackendFreeWorkspace(int device_type,
diff --git a/src/runtime/cpu_device_api.cc b/src/runtime/cpu_device_api.cc
index 30c3bb7d52df..7486f20a6ae1 100644
--- a/src/runtime/cpu_device_api.cc
+++ b/src/runtime/cpu_device_api.cc
@@ -59,7 +59,7 @@ class CPUDeviceAPI final : public DeviceAPI {
   void StreamSync(TVMContext ctx, TVMStreamHandle stream) final {
   }
 
-  void* AllocWorkspace(TVMContext ctx, size_t size) final;
+  void* AllocWorkspace(TVMContext ctx, size_t size, TVMType type_hint) final;
   void FreeWorkspace(TVMContext ctx, void* data) final;
 
   static const std::shared_ptr<CPUDeviceAPI>& Global() {
@@ -74,7 +74,9 @@ struct CPUWorkspacePool : public WorkspacePool {
       WorkspacePool(kDLCPU, CPUDeviceAPI::Global()) {}
 };
 
-void* CPUDeviceAPI::AllocWorkspace(TVMContext ctx, size_t size) {
+void* CPUDeviceAPI::AllocWorkspace(TVMContext ctx,
+                                   size_t size,
+                                   TVMType type_hint) {
   return dmlc::ThreadLocalStore<CPUWorkspacePool>::Get()
       ->AllocWorkspace(ctx, size);
 }
diff --git a/src/runtime/cuda/cuda_device_api.cc b/src/runtime/cuda/cuda_device_api.cc
index 69b485a423c0..7885aa7705ed 100644
--- a/src/runtime/cuda/cuda_device_api.cc
+++ b/src/runtime/cuda/cuda_device_api.cc
@@ -112,7 +112,7 @@ class CUDADeviceAPI final : public DeviceAPI {
         ->stream = static_cast<cudaStream_t>(stream);
   }
 
-  void* AllocWorkspace(TVMContext ctx, size_t size) final {
+  void* AllocWorkspace(TVMContext ctx, size_t size, TVMType type_hint) final {
     return CUDAThreadEntry::ThreadLocal()->pool.AllocWorkspace(ctx, size);
   }
 
diff --git a/src/runtime/metal/metal_common.h b/src/runtime/metal/metal_common.h
index 7c2975fe7ccc..fa73b8250c33 100644
--- a/src/runtime/metal/metal_common.h
+++ b/src/runtime/metal/metal_common.h
@@ -77,7 +77,7 @@ class MetalWorkspace final : public DeviceAPI {
                       TVMContext ctx_to,
                       TVMStreamHandle stream) final;
   void StreamSync(TVMContext ctx, TVMStreamHandle stream) final;
-  void* AllocWorkspace(TVMContext ctx, size_t size) final;
+  void* AllocWorkspace(TVMContext ctx, size_t size, TVMType type_hint) final;
   void FreeWorkspace(TVMContext ctx, void* data) final;
   // get the global workspace
   static const std::shared_ptr<MetalWorkspace>& Global();
diff --git a/src/runtime/metal/metal_device_api.mm b/src/runtime/metal/metal_device_api.mm
index 82c52a23e036..6d376d3144ac 100644
--- a/src/runtime/metal/metal_device_api.mm
+++ b/src/runtime/metal/metal_device_api.mm
@@ -228,7 +228,9 @@ int GetWarpSize(id<MTLDevice> dev) {
   [cb waitUntilCompleted];
 }
 
-void* MetalWorkspace::AllocWorkspace(TVMContext ctx, size_t size) {
+void* MetalWorkspace::AllocWorkspace(TVMContext ctx,
+                                     size_t size,
+                                     TVMType type_hint) {
   return MetalThreadEntry::ThreadLocal()->pool.AllocWorkspace(ctx, size);
 }
 
diff --git a/src/runtime/opencl/opencl_common.h b/src/runtime/opencl/opencl_common.h
index 29e205ced4d7..67934a078665 100644
--- a/src/runtime/opencl/opencl_common.h
+++ b/src/runtime/opencl/opencl_common.h
@@ -156,7 +156,7 @@ class OpenCLWorkspace final : public DeviceAPI {
                       TVMContext ctx_to,
                       TVMStreamHandle stream) final;
   void StreamSync(TVMContext ctx, TVMStreamHandle stream) final;
-  void* AllocWorkspace(TVMContext ctx, size_t size) final;
+  void* AllocWorkspace(TVMContext ctx, size_t size, TVMType type_hint) final;
   void FreeWorkspace(TVMContext ctx, void* data) final;
   // get the global workspace
   static const std::shared_ptr<OpenCLWorkspace>& Global();
diff --git a/src/runtime/opencl/opencl_device_api.cc b/src/runtime/opencl/opencl_device_api.cc
index 7518e72f9d9b..a07fe15f805f 100644
--- a/src/runtime/opencl/opencl_device_api.cc
+++ b/src/runtime/opencl/opencl_device_api.cc
@@ -108,7 +108,9 @@ void OpenCLWorkspace::StreamSync(TVMContext ctx, TVMStreamHandle stream) {
   OPENCL_CALL(clFinish(this->GetQueue(ctx)));
 }
 
-void* OpenCLWorkspace::AllocWorkspace(TVMContext ctx, size_t size) {
+void* OpenCLWorkspace::AllocWorkspace(TVMContext ctx,
+                                      size_t size,
+                                      TVMType type_hint) {
   return OpenCLThreadEntry::ThreadLocal()->pool.AllocWorkspace(ctx, size);
 }
 
diff --git a/src/runtime/opengl/opengl_common.h b/src/runtime/opengl/opengl_common.h
index 80b1d9f95c8e..661c987e4b3c 100644
--- a/src/runtime/opengl/opengl_common.h
+++ b/src/runtime/opengl/opengl_common.h
@@ -175,8 +175,6 @@ class OpenGLWorkspace final : public DeviceAPI {
                       TVMContext ctx_to,
                       TVMStreamHandle stream) final;
   void StreamSync(TVMContext ctx, TVMStreamHandle stream) final;
-  void* AllocWorkspace(TVMContext ctx, size_t size) final;
-  void FreeWorkspace(TVMContext ctx, void* data) final;
 
   /*!
    * \brief Get the global OpenGL workspace.
diff --git a/src/runtime/opengl/opengl_device_api.cc b/src/runtime/opengl/opengl_device_api.cc
index d90d12034ae6..df2947db6255 100644
--- a/src/runtime/opengl/opengl_device_api.cc
+++ b/src/runtime/opengl/opengl_device_api.cc
@@ -156,15 +156,6 @@ void OpenGLWorkspace::CopyDataFromTo(const void* from,
 
 void OpenGLWorkspace::StreamSync(TVMContext ctx, TVMStreamHandle stream) {}
 
-void* OpenGLWorkspace::AllocWorkspace(TVMContext ctx, size_t size) {
-  LOG(FATAL) << "Cannot allocate OpenGL workspace.";
-  return nullptr;
-}
-
-void OpenGLWorkspace::FreeWorkspace(TVMContext ctx, void* data) {
-  LOG(FATAL) << "Cannot free OpenGL workspace.";
-}
-
 OpenGLWorkspace::OpenGLWorkspace() {
   // Set an error handler.
   // This can be called before glfwInit().
diff --git a/src/runtime/rocm/rocm_device_api.cc b/src/runtime/rocm/rocm_device_api.cc
index 443d76b76eb6..877907c7e092 100644
--- a/src/runtime/rocm/rocm_device_api.cc
+++ b/src/runtime/rocm/rocm_device_api.cc
@@ -110,7 +110,7 @@ class ROCMDeviceAPI final : public DeviceAPI {
         ->stream = static_cast<hipStream_t>(stream);
   }
 
-  void* AllocWorkspace(TVMContext ctx, size_t size) final {
+  void* AllocWorkspace(TVMContext ctx, size_t size, TVMType type_hint) final {
     return ROCMThreadEntry::ThreadLocal()->pool.AllocWorkspace(ctx, size);
   }
 
diff --git a/tests/webgl/test_local_multi_stage.py b/tests/webgl/test_local_multi_stage.py
new file mode 100644
index 000000000000..47fa5c76c7aa
--- /dev/null
+++ b/tests/webgl/test_local_multi_stage.py
@@ -0,0 +1,30 @@
+import tvm
+import numpy as np
+
+def test_local_multi_stage():
+    if not tvm.module.enabled("opengl"):
+        return
+    if not tvm.module.enabled("llvm"):
+        return
+
+    n = tvm.var("n")
+    A = tvm.placeholder((n,), name='A', dtype="int32")
+    B = tvm.compute((n,), lambda i: A[i] + 1, name="B")
+    C = tvm.compute((n,), lambda i: B[i] * 2, name="C")
+
+    s = tvm.create_schedule(C.op)
+    s[B].opengl()
+    s[C].opengl()
+
+    f = tvm.build(s, [A, C], "opengl", name="multi_stage")
+
+    ctx = tvm.opengl(0)
+    n = 10
+    a = tvm.nd.array(np.random.uniform(size=(n,)).astype(A.dtype), ctx)
+    c = tvm.nd.array(np.random.uniform(size=(n,)).astype(B.dtype), ctx)
+    f(a, c)
+
+    np.testing.assert_allclose(c.asnumpy(), (a.asnumpy() + 1) * 2)
+
+if __name__ == "__main__":
+    test_local_multi_stage()

From fda2fa143c7a819cd543a0717da2e4cec957cc0f Mon Sep 17 00:00:00 2001
From: alex-weaver <awsweaver@gmail.com>
Date: Sun, 28 Jan 2018 05:57:13 +0000
Subject: [PATCH 116/949]  Porting schedules (except convolutions) to C++
 (#763)

* Ported injective schedules to C++. Added some elementwise ops.

* Fix lint errors

* Added reduction ops and schedules

* Fix lint errors

* Fix lint errors

* Fix lint errors

* Added transform ops

* Fix lint errors

* Fix lint errors

* Added softmax, log_softmax, leaky_relu and flatten ops.
Fixed issue where TVM_DECLARE_INTRIN_UNARY used the PureExtern flag
instead of PureIntrinsic.
Added softmax CUDA schedule.

* Fix lint

* Fix lint

* Added binary_dense, batch_norm_inference, dense, dilate, scale_shift_*,
global_pool and pool ops.
Extended pad to allow specifying pad_value.
Fixed issue where pad would throw if padding was zero in all dimensions.

* Fix lint

* Fix lint

* Added CUDA schedules for dense, pool and global_pool

* Added extern schedules for generic and CUDA

* Fix lint

* Added x86 binary schedules

* Fix lint

* Added rocm dense schedule. Added rocBLAS and cuBLAS support to dense ops

* Added pow ops. Added x86 default and injective schedules

* Fix lint

* Fix lint

* Fix lint

* Fix lint

* Fix lint

* Fix indent

* Removed schedules directory

* Changed left_shift, right_shift to operators. Changed pad_value in pad() to remove pointer usage

* Fixed usage of pad in nn/pooling.h. Fixed declaration of operator>>

* Fixed comments for shift operators

* Added comments to utility functions

* Added TOPI C++ library, exporting broadcast_add op

* Fix lint

* Share libinfo.py with TVM

* Fix lint

* Add other broadcast ops

* Fix lint

* Fix imports in topi

* Fix lib names

* Fixed build issue where windows builds don't apply correct definitions

* Removed TVM_EXPORTS from topi library

* Attempted CI build fix

* Add topi lib to tvm_multilib

* Fix Jenkinsfile

* Added TOPI build target to Makefile

* Fix nn op namespaces.

* Fix lint

* Renamed TOPI lib to libtvm_topi

* Removed _ffi/base.py

* Remove _ffi from topi, now shared with tvm.

* Make libtvm_topi loading optional

* Fix compiler warnings

* Fix lint

* Fix lint

* Fix lint

* Fix build error by making new libs argument to Target optional

* Added C++ Target type interop. Added registration of remaining C++ ops and schedules. Added test of broadcast ops

* Fix lint

* Fix lint

* Fix compile error

* Fix compiler warnings

* Fix compiler warnings

* Fixed int vector interop. Fixed argmin incorrectly invoking argmax. Fixed corner case in default schedules of attempting to fuse 0 length axes. Added tests for reduce ops.

* Refactored reduce builders

* Fixed typos in topi.cc. Added basic test.

* Fixed padding size error. Added dense, dilate, pooling tests

* Fixed issue where clip would output a different dtype to the input. Added split_sections op to cover the other mode of the python split op. Added tests.

* Changed extension type numbers to avoid clash with NNVM

* Fix lint

* Fix compiler warnings

* Removed use of std::vector from the public TOPI API

* Fix lint

* Add TOPI C++ tests to CI

* Fixed detail namespacing. Improved comments.
---
 CMakeLists.txt                               |  15 +-
 Jenkinsfile                                  |   7 +-
 Makefile                                     |  19 +-
 include/tvm/build_module.h                   |   8 +-
 include/tvm/expr.h                           |   2 +-
 include/tvm/ir_operator.h                    |   8 +-
 include/tvm/ir_pass.h                        |   4 +-
 include/tvm/operation.h                      |   2 +-
 include/tvm/schedule.h                       |  44 +-
 include/tvm/schedule_pass.h                  |   2 +-
 python/tvm/_ffi/function.py                  |   5 +-
 python/tvm/_ffi/libinfo.py                   |  22 +-
 src/codegen/build_module.cc                  |  12 +-
 src/lang/ir_operator.cc                      |   1 +
 tests/scripts/task_cpp_topi.sh               |   4 +
 tests/travis/run_test.sh                     |   1 +
 topi/include/topi/broadcast.h                |  63 ++-
 topi/include/topi/contrib/cublas.h           |  49 ++
 topi/include/topi/contrib/rocblas.h          |  48 ++
 topi/include/topi/cuda/dense.h               | 134 ++++++
 topi/include/topi/cuda/extern.h              |  68 +++
 topi/include/topi/cuda/injective.h           |  58 +++
 topi/include/topi/cuda/pooling.h             | 163 +++++++
 topi/include/topi/cuda/reduction.h           | 181 ++++++++
 topi/include/topi/cuda/softmax.h             |  62 +++
 topi/include/topi/detail/array_utils.h       |  35 ++
 topi/include/topi/detail/broadcast.h         |   6 +-
 topi/include/topi/detail/constant_utils.h    |  70 +++
 topi/include/topi/detail/extern.h            | 137 ++++++
 topi/include/topi/detail/fuse.h              |  37 ++
 topi/include/topi/detail/pad_utils.h         |  38 ++
 topi/include/topi/detail/ravel_unravel.h     |  60 +++
 topi/include/topi/elemwise.h                 | 138 ++++++
 topi/include/topi/generic/default.h          |  46 ++
 topi/include/topi/generic/extern.h           |  37 ++
 topi/include/topi/generic/injective.h        |  42 ++
 topi/include/topi/nn.h                       |  50 ++-
 topi/include/topi/nn/batch_norm.h            |  65 +++
 topi/include/topi/nn/bnn.h                   | 110 +++++
 topi/include/topi/nn/dense.h                 |  61 +++
 topi/include/topi/nn/dilate.h                |  87 ++++
 topi/include/topi/nn/flatten.h               |  63 +++
 topi/include/topi/nn/mapping.h               |  66 +++
 topi/include/topi/nn/pooling.h               | 161 +++++++
 topi/include/topi/nn/softmax.h               |  87 ++++
 topi/include/topi/reduction.h                | 379 ++++++++++++++++
 topi/include/topi/rocm/dense.h               |  82 ++++
 topi/include/topi/tags.h                     |  18 +
 topi/include/topi/transform.h                | 362 +++++++++++++++
 topi/include/topi/x86/bnn.h                  | 110 +++++
 topi/include/topi/x86/default.h              |  58 +++
 topi/include/topi/x86/injective.h            |  50 +++
 topi/python/setup.py                         |  39 +-
 topi/python/topi/__init__.py                 |   3 +
 topi/python/topi/cpp.py                      |  86 ++++
 topi/src/topi.cc                             | 448 +++++++++++++++++++
 topi/tests/python_cpp/test_topi_basic.py     |  31 ++
 topi/tests/python_cpp/test_topi_bnn.py       |  55 +++
 topi/tests/python_cpp/test_topi_broadcast.py | 114 +++++
 topi/tests/python_cpp/test_topi_clip.py      |  45 ++
 topi/tests/python_cpp/test_topi_dense.py     |  61 +++
 topi/tests/python_cpp/test_topi_dilate.py    |  34 ++
 topi/tests/python_cpp/test_topi_pooling.py   | 120 +++++
 topi/tests/python_cpp/test_topi_reduce.py    | 120 +++++
 topi/tests/python_cpp/test_topi_relu.py      |  62 +++
 topi/tests/python_cpp/test_topi_softmax.py   |  81 ++++
 topi/tests/python_cpp/test_topi_transform.py | 216 +++++++++
 67 files changed, 4891 insertions(+), 61 deletions(-)
 create mode 100644 tests/scripts/task_cpp_topi.sh
 create mode 100644 topi/include/topi/contrib/cublas.h
 create mode 100644 topi/include/topi/contrib/rocblas.h
 create mode 100644 topi/include/topi/cuda/dense.h
 create mode 100644 topi/include/topi/cuda/extern.h
 create mode 100644 topi/include/topi/cuda/injective.h
 create mode 100644 topi/include/topi/cuda/pooling.h
 create mode 100644 topi/include/topi/cuda/reduction.h
 create mode 100644 topi/include/topi/cuda/softmax.h
 create mode 100644 topi/include/topi/detail/array_utils.h
 create mode 100644 topi/include/topi/detail/constant_utils.h
 create mode 100644 topi/include/topi/detail/extern.h
 create mode 100644 topi/include/topi/detail/fuse.h
 create mode 100644 topi/include/topi/detail/pad_utils.h
 create mode 100644 topi/include/topi/detail/ravel_unravel.h
 create mode 100644 topi/include/topi/generic/default.h
 create mode 100644 topi/include/topi/generic/extern.h
 create mode 100644 topi/include/topi/generic/injective.h
 create mode 100644 topi/include/topi/nn/batch_norm.h
 create mode 100644 topi/include/topi/nn/bnn.h
 create mode 100644 topi/include/topi/nn/dense.h
 create mode 100644 topi/include/topi/nn/dilate.h
 create mode 100644 topi/include/topi/nn/flatten.h
 create mode 100644 topi/include/topi/nn/mapping.h
 create mode 100644 topi/include/topi/nn/pooling.h
 create mode 100644 topi/include/topi/nn/softmax.h
 create mode 100644 topi/include/topi/reduction.h
 create mode 100644 topi/include/topi/rocm/dense.h
 create mode 100644 topi/include/topi/transform.h
 create mode 100644 topi/include/topi/x86/bnn.h
 create mode 100644 topi/include/topi/x86/default.h
 create mode 100644 topi/include/topi/x86/injective.h
 create mode 100644 topi/python/topi/cpp.py
 create mode 100644 topi/src/topi.cc
 create mode 100644 topi/tests/python_cpp/test_topi_basic.py
 create mode 100644 topi/tests/python_cpp/test_topi_bnn.py
 create mode 100644 topi/tests/python_cpp/test_topi_broadcast.py
 create mode 100644 topi/tests/python_cpp/test_topi_clip.py
 create mode 100644 topi/tests/python_cpp/test_topi_dense.py
 create mode 100644 topi/tests/python_cpp/test_topi_dilate.py
 create mode 100644 topi/tests/python_cpp/test_topi_pooling.py
 create mode 100644 topi/tests/python_cpp/test_topi_reduce.py
 create mode 100644 topi/tests/python_cpp/test_topi_relu.py
 create mode 100644 topi/tests/python_cpp/test_topi_softmax.py
 create mode 100644 topi/tests/python_cpp/test_topi_transform.py

diff --git a/CMakeLists.txt b/CMakeLists.txt
index fd381b9a12e4..62a564c2b656 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -44,9 +44,7 @@ if(MSVC)
   add_definitions(-DWIN32_LEAN_AND_MEAN)
   add_definitions(-D_CRT_SECURE_NO_WARNINGS)
   add_definitions(-D_SCL_SECURE_NO_WARNINGS)
-  add_definitions(-DTVM_EXPORTS)
   add_definitions(-DHalide_SHARED)
-  add_definitions(-DHalide_EXPORTS)
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc")
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
   set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /bigobj")
@@ -82,6 +80,10 @@ file(GLOB COMPILER_SRCS
     src/op/*.cc
     src/schedule/*.cc
 )
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/topi/include)
+file(GLOB TOPI_SRCS
+    topi/src/*.cc
+)
 file(GLOB_RECURSE HALIDEIR_SRCS HalideIR/src/*.cpp)
 list(APPEND COMPILER_SRCS ${HALIDEIR_SRCS})
 file(GLOB RUNTIME_SRCS src/runtime/*.cc)
@@ -209,8 +211,10 @@ endif()
 
 list(APPEND RUNTIME_SRCS ${GROUP_Include})
 add_library(tvm SHARED ${COMPILER_SRCS} ${RUNTIME_SRCS})
+add_library(tvm_topi SHARED ${TOPI_SRCS})
 add_library(tvm_runtime SHARED ${RUNTIME_SRCS})
 target_link_libraries(tvm ${TVM_LINKER_LIBS} ${TVM_RUNTIME_LINKER_LIBS})
+target_link_libraries(tvm_topi tvm ${TVM_LINKER_LIBS} ${TVM_RUNTIME_LINKER_LIBS})
 target_link_libraries(tvm_runtime  ${TVM_RUNTIME_LINKER_LIBS})
 install(TARGETS tvm_runtime DESTINATION lib${LIB_SUFFIX})
 if (INSTALL_DEV)
@@ -242,3 +246,10 @@ else(INSTALL_DEV)
     PATTERN "*.h"
   )
 endif(INSTALL_DEV)
+
+if(MSVC)
+  target_compile_definitions(tvm PRIVATE -DHalide_EXPORTS)
+  target_compile_definitions(tvm_runtime PRIVATE -DHalide_EXPORTS)
+  target_compile_definitions(tvm PRIVATE -DTVM_EXPORTS)
+  target_compile_definitions(tvm_runtime PRIVATE -DTVM_EXPORTS)
+endif()
\ No newline at end of file
diff --git a/Jenkinsfile b/Jenkinsfile
index 4fc2285f507c..85d38d3dfcad 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -4,10 +4,11 @@
 // See documents at https://jenkins.io/doc/book/pipeline/jenkinsfile/
 
 // tvm libraries
-tvm_runtime = "lib/libtvm_runtime.so, config.mk"
-tvm_lib = "lib/libtvm.so, " + tvm_runtime
+topi_lib = "lib/libtopi.so"
+tvm_runtime = "lib/libtvm_runtime.so, config.mk, "
+tvm_lib = "lib/libtvm.so, " + tvm_runtime + topi_lib
 // LLVM upstream lib
-tvm_multilib = "lib/libtvm_llvm40.so, lib/libtvm_llvm50.so, lib/libtvm_llvm60.so, " + tvm_runtime
+tvm_multilib = "lib/libtvm_llvm40.so, lib/libtvm_llvm50.so, lib/libtvm_llvm60.so, " + tvm_runtime + topi_lib
 
 // command to start a docker container
 docker_run = 'tests/ci_build/ci_build.sh'
diff --git a/Makefile b/Makefile
index 44a500d26ce3..332d6ebe86f2 100644
--- a/Makefile
+++ b/Makefile
@@ -58,6 +58,7 @@ OPENGL_SRC = $(wildcard src/runtime/opengl/*.cc)
 RPC_SRC = $(wildcard src/runtime/rpc/*.cc)
 GRAPH_SRC = $(wildcard src/runtime/graph/*.cc)
 RUNTIME_SRC = $(wildcard src/runtime/*.cc)
+TOPI_SRC = $(wildcard topi/src/*.cc)
 
 # Objectives
 LLVM_BUILD = build/llvm${LLVM_VERSION}
@@ -71,11 +72,13 @@ RPC_OBJ = $(patsubst src/%.cc, build/%.o, $(RPC_SRC))
 GRAPH_OBJ = $(patsubst src/%.cc, build/%.o, $(GRAPH_SRC))
 CC_OBJ = $(patsubst src/%.cc, build/%.o, $(CC_SRC)) $(LLVM_OBJ)
 RUNTIME_OBJ = $(patsubst src/%.cc, build/%.o, $(RUNTIME_SRC))
+TOPI_OBJ = $(patsubst topi/%.cc, build/%.o, $(TOPI_SRC))
 CONTRIB_OBJ =
 
 # Deps
 ALL_DEP = $(CC_OBJ) $(CONTRIB_OBJ) $(LIB_HALIDEIR)
 RUNTIME_DEP = $(RUNTIME_OBJ)
+TOPI_DEP = $(TOPI_OBJ)
 
 # Dependency specific rules
 ifdef CUDA_PATH
@@ -198,10 +201,11 @@ else
 	JVM_PKG_PROFILE := $(JVM_PKG_PROFILE)-cpu
 endif
 
-BUILD_TARGETS ?= lib/libtvm.$(SHARED_LIBRARY_SUFFIX) lib/libtvm_runtime.$(SHARED_LIBRARY_SUFFIX)
+BUILD_TARGETS ?= lib/libtvm.$(SHARED_LIBRARY_SUFFIX) lib/libtvm_runtime.$(SHARED_LIBRARY_SUFFIX) lib/libtvm_topi.$(SHARED_LIBRARY_SUFFIX)
 all: ${BUILD_TARGETS}
 runtime: lib/libtvm_runtime.$(SHARED_LIBRARY_SUFFIX)
 web: lib/libtvm_web_runtime.js lib/libtvm_web_runtime.bc
+topi: lib/libtvm_topi.$(SHARED_LIBRARY_SUFFIX)
 
 include tests/cpp/unittest.mk
 
@@ -226,10 +230,19 @@ build/%.o: src/%.cc
 	$(CXX) $(CFLAGS) -MM -MT build/$*.o $< >build/$*.d
 	$(CXX) -c $(CFLAGS) -c $< -o $@
 
+build/src/%.o: topi/src/%.cc
+	@mkdir -p $(@D)
+	$(CXX) $(CFLAGS) -MM -MT build/src/$*.o $< >build/src/$*.d
+	$(CXX) -c $(CFLAGS) -c $< -o $@
+
 lib/libtvm.dylib: $(ALL_DEP) $(RUNTIME_DEP)
 	@mkdir -p $(@D)
 	$(CXX) $(CFLAGS) $(FRAMEWORKS) -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
 
+lib/libtvm_topi.dylib: lib/libtvm.so $(TOPI_DEP)
+	@mkdir -p $(@D)
+	$(CXX) $(CFLAGS) $(FRAMEWORKS) -L./lib -ltvm -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
+
 lib/libtvm_runtime.dylib: $(RUNTIME_DEP)
 	@mkdir -p $(@D)
 	$(CXX) $(CFLAGS) $(FRAMEWORKS) -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
@@ -238,6 +251,10 @@ lib/libtvm.so: $(ALL_DEP) $(RUNTIME_DEP)
 	@mkdir -p $(@D)
 	$(CXX) $(CFLAGS) $(FRAMEWORKS) -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
 
+lib/libtvm_topi.so: lib/libtvm.so $(TOPI_DEP)
+	@mkdir -p $(@D)
+	$(CXX) $(CFLAGS) $(FRAMEWORKS) -L./lib -ltvm -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
+
 lib/libtvm_runtime.so: $(RUNTIME_DEP)
 	@mkdir -p $(@D)
 	$(CXX) $(CFLAGS) $(FRAMEWORKS) -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
diff --git a/include/tvm/build_module.h b/include/tvm/build_module.h
index ae7ca3ca681d..391901730e57 100644
--- a/include/tvm/build_module.h
+++ b/include/tvm/build_module.h
@@ -31,19 +31,23 @@ struct Target {
   std::unordered_set<std::string> keys;
   /*! \brief Options for this target */
   std::vector<std::string> options;
+  /*! \brief Set of imported libs */
+  std::unordered_set<std::string> libs;
 
   Target(const std::string& target_name,
          DLDeviceType device_type,
          int max_num_threads,
          int thread_warp_size,
          const std::unordered_set<std::string>& keys,
-         const std::vector<std::string>& options) :
+         const std::vector<std::string>& options,
+         const std::unordered_set<std::string>& libs = {}) :
     target_name(target_name),
     device_type(device_type),
     max_num_threads(max_num_threads),
     thread_warp_size(thread_warp_size),
     keys(keys),
-    options(options) {
+    options(options),
+    libs(libs) {
   }
 
   /*! \return the full device string to pass to codegen::Build */
diff --git a/include/tvm/expr.h b/include/tvm/expr.h
index 5d35728c24b4..d2816358be0b 100644
--- a/include/tvm/expr.h
+++ b/include/tvm/expr.h
@@ -73,7 +73,7 @@ inline int GetVectorBytes(Type dtype) {
 /*! \brief a named variable in TVM */
 class Var : public HalideIR::VarExpr {
  public:
-  explicit Var(const std::string& name_hint = "v",
+  EXPORT explicit Var(const std::string& name_hint = "v",
                Type t = Int(32)) : VarExpr(name_hint, t) {}
   explicit Var(std::shared_ptr<Node> n) : VarExpr(n) {}
   explicit Var(VarExpr v) : VarExpr(v) {}
diff --git a/include/tvm/ir_operator.h b/include/tvm/ir_operator.h
index 8b27389db0d8..a2cd01042db5 100644
--- a/include/tvm/ir_operator.h
+++ b/include/tvm/ir_operator.h
@@ -45,13 +45,19 @@ TVM_DLL Expr min(Expr source, Array<IterVar> axis);
 // Unary intrinsic operators
 #define TVM_DECLARE_INTRIN_UNARY(OpName)                                \
   inline Expr OpName(Expr x) {                                          \
-    return ir::Call::make(x.type(), #OpName, {x}, ir::Call::PureExtern); \
+    return ir::Call::make(x.type(), #OpName, {x}, ir::Call::PureIntrinsic); \
   }                                                                     \
 
 TVM_DECLARE_INTRIN_UNARY(exp);
 TVM_DECLARE_INTRIN_UNARY(tanh);
 TVM_DECLARE_INTRIN_UNARY(sigmoid);
 TVM_DECLARE_INTRIN_UNARY(sqrt);
+TVM_DECLARE_INTRIN_UNARY(log);
+
+inline Expr pow(Expr x, Expr y) {
+  return ir::Call::make(x.type(), "pow", { x, y }, ir::Call::PureIntrinsic);
+}
+
 }  // namespace tvm
 
 #endif  // TVM_IR_OPERATOR_H_
diff --git a/include/tvm/ir_pass.h b/include/tvm/ir_pass.h
index 525b36d1b6b1..76a060345be1 100644
--- a/include/tvm/ir_pass.h
+++ b/include/tvm/ir_pass.h
@@ -28,7 +28,7 @@ namespace ir {
  * \param vrange The range information about the variable.
  * \return Canonicalized statement.
  */
-Expr Simplify(Expr expr, Map<Var, Range> vrange = Map<Var, Range>());
+EXPORT Expr Simplify(Expr expr, Map<Var, Range> vrange = Map<Var, Range>());
 
 /*!
  * \brief Simplify the statement.
@@ -62,7 +62,7 @@ Expr CanonicalSimplify(Expr expr,
  * \param rhs The right operand
  * \return The comparison result.
  */
-bool Equal(const Expr& lhs, const Expr& rhs);
+EXPORT bool Equal(const Expr& lhs, const Expr& rhs);
 
 /*!
  * \brief Deep compare lhs and rhs
diff --git a/include/tvm/operation.h b/include/tvm/operation.h
index 8242bfbeefb4..d598df8d21b1 100644
--- a/include/tvm/operation.h
+++ b/include/tvm/operation.h
@@ -353,7 +353,7 @@ class ExternOpNode : public OperationNode {
     v->Visit("inputs", &inputs);
     v->Visit("body", &body);
   }
-  static Operation make(std::string name,
+  EXPORT static Operation make(std::string name,
                         std::string tag,
                         Array<Tensor> inputs,
                         Array<Buffer> input_placeholders,
diff --git a/include/tvm/schedule.h b/include/tvm/schedule.h
index 2f94aedccf3d..003555132789 100644
--- a/include/tvm/schedule.h
+++ b/include/tvm/schedule.h
@@ -56,24 +56,24 @@ class Stage : public NodeRef {
    * \brief set the memory scope of the stage
    * \param scope The memory scope.
    */
-  Stage& set_scope(std::string scope);  // NOLINT(*)
+  EXPORT Stage& set_scope(std::string scope);  // NOLINT(*)
   /*!
    * \brief specify the schedule to be computed at the parent schedule's scope.
    * \param parent The parent schedule.
    * \param scope The iteration point to carry the schedule.
    * \return reference to self.
    */
-  Stage& compute_at(Stage parent, IterVar scope);   // NOLINT(*)
+  EXPORT Stage& compute_at(Stage parent, IterVar scope);   // NOLINT(*)
   /*!
    * \brief Compute the function inline.
    * \return reference to self.
    */
-  Stage& compute_inline();   // NOLINT(*)
+  EXPORT Stage& compute_inline();   // NOLINT(*)
   /*!
    * \brief Compute the function at group root.
    * \return reference to self.
    */
-  Stage& compute_root();  // NOLINT(*)
+  EXPORT Stage& compute_root();  // NOLINT(*)
   /*!
    * \brief Bind the ivar to thread index.
    *
@@ -92,7 +92,7 @@ class Stage : public NodeRef {
    * \param predicate The condition to be checked.
    * \return reference to self.
    */
-  Stage& set_store_predicate(Expr predicate);
+  EXPORT Stage& set_store_predicate(Expr predicate);
   /*!
    * \brief Specify environment threads that launched around the group's scope.
    *  This can only be used in group stage.
@@ -101,7 +101,7 @@ class Stage : public NodeRef {
    *    This is a beta feature.
    * \return reference to self.
    */
-  Stage& env_threads(Array<IterVar> threads);
+  EXPORT Stage& env_threads(Array<IterVar> threads);
   /*!
    * \brief Split the parent by factor, generate
    * \param parent The parent iteration domain.
@@ -120,7 +120,7 @@ class Stage : public NodeRef {
    * \param p_inner The result inner domain.
    * \return reference to self.
    */
-  Stage& split_by_nparts(IterVar parent, Expr nparts, IterVar* p_outer, IterVar* p_inner);   // NOLINT(*)
+  EXPORT Stage& split_by_nparts(IterVar parent, Expr nparts, IterVar* p_outer, IterVar* p_inner);   // NOLINT(*)
   /*!
    * \brief Fuse the inner outer domain to the target
    * \param outer The outer domain to be fused.
@@ -128,13 +128,13 @@ class Stage : public NodeRef {
    * \param p_target The result target domain.
    * \return reference to self.
    */
-  Stage& fuse(IterVar outer, IterVar inner, IterVar* p_target);  // NOLINT(*)
+  EXPORT Stage& fuse(IterVar outer, IterVar inner, IterVar* p_target);  // NOLINT(*)
   /*!
    * \brief Reorder the iteration
    * \param order The order of iteration variable.
    * \return reference to self.
    */
-  Stage& reorder(const Array<IterVar>& order);   // NOLINT(*)
+  EXPORT Stage& reorder(const Array<IterVar>& order);   // NOLINT(*)
   /*!
    * \brief Perform tiling on two dimensions
    *  The final loop order from outmost to inner most are
@@ -150,7 +150,7 @@ class Stage : public NodeRef {
    * \param p_y_inner Inner axis of y dimension
    * \return reference to self.
    */
-  Stage& tile(IterVar x_parent, IterVar y_parent,   // NOLINT(*)
+  EXPORT Stage& tile(IterVar x_parent, IterVar y_parent,   // NOLINT(*)
               Expr x_factor, Expr y_factor,
               IterVar* p_x_outer, IterVar* p_y_outer,
               IterVar* p_x_inner, IterVar* p_y_inner);
@@ -159,7 +159,7 @@ class Stage : public NodeRef {
    * \param var The axis to be vectorized.
    * \return reference to self.
    */
-  Stage& vectorize(IterVar var);   // NOLINT(*)
+  EXPORT Stage& vectorize(IterVar var);   // NOLINT(*)
   /*!
    * \brief Replace computation of the current stage by tensor intrinsic f.
    * \param var The axis marks beginning of tensorization.
@@ -167,19 +167,19 @@ class Stage : public NodeRef {
    * \param f The Tensor compute intrinsics.
    * \return reference to self.
    */
-  Stage& tensorize(IterVar var, TensorIntrin f);   // NOLINT(*)
+  EXPORT Stage& tensorize(IterVar var, TensorIntrin f);   // NOLINT(*)
   /*!
    * \brief Unroll iteration.
    * \param var The axis to be unrolled.
    * \return reference to self.
    */
-  Stage& unroll(IterVar var);   // NOLINT(*)
+  EXPORT Stage& unroll(IterVar var);   // NOLINT(*)
   /*!
    * \brief Parallelize iteration.
    * \param var The axis to be parallelized.
    * \return reference to self.
    */
-  Stage& parallel(IterVar var);   // NOLINT(*)
+  EXPORT Stage& parallel(IterVar var);   // NOLINT(*)
   /*!
    * \brief Annotate the iteration with pragma
    *
@@ -188,7 +188,7 @@ class Stage : public NodeRef {
    *
    * \return reference to self.
    */
-  Stage& pragma(IterVar var, const std::string& pragma_type);   // NOLINT(*)
+  EXPORT Stage& pragma(IterVar var, const std::string& pragma_type);   // NOLINT(*)
   /*!
    * \brief Fetch data in advance.
    * \param domain the tensor to be prefetched
@@ -196,7 +196,7 @@ class Stage : public NodeRef {
    * \param offset the number of iterations be to fetched in advance
    * \return reference to self
    */
-  Stage& prefetch(const Tensor &domain, IterVar var, Expr offset); //NOLINT(*)
+  EXPORT Stage& prefetch(const Tensor &domain, IterVar var, Expr offset); //NOLINT(*)
   /*!
    * \brief Set alignment requirement for specific dimension.
    *
@@ -207,12 +207,12 @@ class Stage : public NodeRef {
    * \param offset The required offset factor.
    * \return reference to self
    */
-  Stage& storage_align(IterVar axis, int factor, int offset); //NOLINT(*)
+  EXPORT Stage& storage_align(IterVar axis, int factor, int offset); //NOLINT(*)
   /*!
    * \brief Compute current stage with double buffering.
    * \return reference to self.
    */
-  Stage& double_buffer();   // NOLINT(*)
+  EXPORT Stage& double_buffer();   // NOLINT(*)
   /*!
    * \brief Schedule for OpenGL fragment shader.
    * \return reference to self.
@@ -271,7 +271,7 @@ class Schedule : public NodeRef {
    * \param include_inputs Whether include inputs if they are reachable from outputs.
    * \return The new grouped stage.
    */
-  Stage create_group(const Array<Tensor>& outputs,
+  EXPORT Stage create_group(const Array<Tensor>& outputs,
                      const Array<Tensor>& inputs,
                      bool include_inputs = false);
   /*!
@@ -283,7 +283,7 @@ class Schedule : public NodeRef {
    * \param readers The readers to redirect to the tensor.
    * \return The created tensor.
    */
-  Tensor cache_read(const Tensor& tensor,
+  EXPORT Tensor cache_read(const Tensor& tensor,
                     const std::string& scope,
                     const Array<Operation>& readers);
   /*!
@@ -302,7 +302,7 @@ class Schedule : public NodeRef {
    * \param scope The scope of the storage.
    * \return The created tensor.
    */
-  Tensor cache_write(const Tensor& tensor, const std::string& scope);
+  EXPORT Tensor cache_write(const Tensor& tensor, const std::string& scope);
   /*!
    * \brief Factor a reduction axis in tensor's schedule to be an explicit axis.
    * This will create a new stage that generated the new tensor with axis
@@ -315,7 +315,7 @@ class Schedule : public NodeRef {
    * \param axis The reduction axis in tensor's schedule to be factored.
    * \return The created factored tensors.
    */
-  Array<Tensor> rfactor(const Tensor& tensor,
+  EXPORT Array<Tensor> rfactor(const Tensor& tensor,
                         const IterVar& axis);
   /*!
    * \brief Normalize the schedule.
diff --git a/include/tvm/schedule_pass.h b/include/tvm/schedule_pass.h
index 189b999a253d..719448513fb8 100644
--- a/include/tvm/schedule_pass.h
+++ b/include/tvm/schedule_pass.h
@@ -48,7 +48,7 @@ void AutoInlineElemWise(Schedule sch);
  *
  * \param sch The schedule to be inlined.
  */
-void AutoInlineInjective(Schedule sch);
+EXPORT void AutoInlineInjective(Schedule sch);
 
 }  // namespace schedule
 }  // namespace tvm
diff --git a/python/tvm/_ffi/function.py b/python/tvm/_ffi/function.py
index b89da713a1ad..2edb355fb721 100644
--- a/python/tvm/_ffi/function.py
+++ b/python/tvm/_ffi/function.py
@@ -261,9 +261,12 @@ def _init_api(namespace):
     mod : str
        The name of the module.
     """
-    module = sys.modules[namespace]
     assert namespace.startswith("tvm.")
     prefix = namespace[4:]
+    _init_api_prefix(namespace, prefix)
+
+def _init_api_prefix(module_name, prefix):
+    module = sys.modules[module_name]
 
     for name in list_global_func_names():
         if prefix == "api":
diff --git a/python/tvm/_ffi/libinfo.py b/python/tvm/_ffi/libinfo.py
index f3ed174c0d44..46d5f536a94e 100644
--- a/python/tvm/_ffi/libinfo.py
+++ b/python/tvm/_ffi/libinfo.py
@@ -2,9 +2,9 @@
 from __future__ import absolute_import
 import sys
 import os
+import warnings
 
-
-def find_lib_path(name=None, search_path=None):
+def find_lib_path(name=None, search_path=None, optional=False):
     """Find dynamic library files.
 
     Parameters
@@ -56,7 +56,12 @@ def find_lib_path(name=None, search_path=None):
         else:
             dll_path.append(search_path)
     if name is not None:
-        lib_dll_path = [os.path.join(p, name) for p in dll_path]
+        if isinstance(name, list):
+            lib_dll_path = []
+            for n in name:
+                lib_dll_path += [os.path.join(p, n) for p in dll_path]
+        else:
+            lib_dll_path = [os.path.join(p, name) for p in dll_path]
         runtime_dll_path = []
     else:
         if sys.platform.startswith('win32'):
@@ -81,9 +86,14 @@ def find_lib_path(name=None, search_path=None):
         lib_found = [p for p in runtime_dll_path if os.path.exists(p) and os.path.isfile(p)]
 
     if not lib_found:
-        raise RuntimeError('Cannot find the files.\n' +
-                           'List of candidates:\n' +
-                           str('\n'.join(lib_dll_path + runtime_dll_path)))
+        message = ('Cannot find the files.\n' +
+                   'List of candidates:\n' +
+                   str('\n'.join(lib_dll_path + runtime_dll_path)))
+        if not optional:
+            raise RuntimeError(message)
+        else:
+            warnings.warn(message)
+            return None
 
     if use_runtime:
         sys.stderr.write("Loading runtime library %s... exec only\n" % lib_found[0])
diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc
index df71a4a41bec..53aef46a9751 100644
--- a/src/codegen/build_module.cc
+++ b/src/codegen/build_module.cc
@@ -85,25 +85,25 @@ namespace target {
 Target llvm() {
   std::unordered_set<std::string> keys({ "llvm", "cpu" });
   std::vector<std::string> options;
-  return Target("llvm", kDLCPU, 512, 1, keys, options);
+  return Target("llvm", kDLCPU, 512, 1, keys, options, {});
 }
 
 Target cuda() {
   std::unordered_set<std::string> keys({ "cuda", "gpu" });
   std::vector<std::string> options;
-  return Target("cuda", kDLGPU, 512, 32, keys, options);
+  return Target("cuda", kDLGPU, 512, 32, keys, options, {});
 }
 
 Target rocm() {
   std::unordered_set<std::string> keys({ "rocm", "gpu" });
   std::vector<std::string> options;
-  return Target("rocm", kDLROCM, 256, 1, keys, options);
+  return Target("rocm", kDLROCM, 256, 1, keys, options, {});
 }
 
 Target metal() {
   std::unordered_set<std::string> keys({ "gpu" });
   std::vector<std::string> options;
-  return Target("metal", kDLMetal, 256, 1, keys, options);
+  return Target("metal", kDLMetal, 256, 1, keys, options, {});
 }
 
 Target rasp() {
@@ -114,7 +114,7 @@ Target rasp() {
     "-mcpu=cortex-a53",
     "-mattr=+neon"
   });
-  return Target("llvm", kDLCPU, 512, 1, keys, options);
+  return Target("llvm", kDLCPU, 512, 1, keys, options, {});
 }
 
 Target mali() {
@@ -129,7 +129,7 @@ Target mali() {
 Target stackvm() {
   std::unordered_set<std::string> keys({ "stackvm", "cpu" });
   std::vector<std::string> options;
-  return Target("stackvm", kDLCPU, 512, 1, keys, options);
+  return Target("stackvm", kDLCPU, 512, 1, keys, options, {});
 }
 }  // namespace target
 
diff --git a/src/lang/ir_operator.cc b/src/lang/ir_operator.cc
index f06ccc060165..ded27bbdce7e 100644
--- a/src/lang/ir_operator.cc
+++ b/src/lang/ir_operator.cc
@@ -4,6 +4,7 @@
  */
 #include <tvm/base.h>
 #include <tvm/ir.h>
+#include <tvm/ir_operator.h>
 
 namespace tvm {
 
diff --git a/tests/scripts/task_cpp_topi.sh b/tests/scripts/task_cpp_topi.sh
new file mode 100644
index 000000000000..c005b0e56ef4
--- /dev/null
+++ b/tests/scripts/task_cpp_topi.sh
@@ -0,0 +1,4 @@
+export PYTHONPATH=python:topi/python
+
+python -m nose -v topi/tests/python_cpp || exit -1
+python3 -m nose -v topi/tests/python_cpp || exit -1
diff --git a/tests/travis/run_test.sh b/tests/travis/run_test.sh
index bb23e2df058a..24a2ac9265aa 100755
--- a/tests/travis/run_test.sh
+++ b/tests/travis/run_test.sh
@@ -32,6 +32,7 @@ fi
 if [ ${TASK} == "cpp_test" ] || [ ${TASK} == "all_test" ]; then
     make -f dmlc-core/scripts/packages.mk gtest
     ./tests/scripts/task_cpp_unittest.sh || exit -1
+    ./tests/scripts/task_cpp_topi.sh || exit -1
 fi
 
 if [ ${TASK} == "python_test" ] || [ ${TASK} == "all_test" ]; then
diff --git a/topi/include/topi/broadcast.h b/topi/include/topi/broadcast.h
index ff0c5ce4ded1..14f09d893f67 100644
--- a/topi/include/topi/broadcast.h
+++ b/topi/include/topi/broadcast.h
@@ -33,7 +33,7 @@ inline tvm::Tensor broadcast_to(const tvm::Tensor& t,
       << output_shape << "\nvs\ninput: " << t;
   auto bh = detail::BroadcastShape(output_shape, t->shape);
   CHECK_EQ(output_shape.size(), bh.common_shape.size());
-  for (int i = 0; i < output_shape.size(); ++i) {
+  for (size_t i = 0; i < output_shape.size(); ++i) {
     CHECK(tvm::ir::Equal(output_shape[i], bh.common_shape[i]));
   }
   auto l = [&](tvm::Array<tvm::Var> ovars) {
@@ -147,6 +147,67 @@ inline tvm::Tensor broadcast_mod(const tvm::Tensor& A,
   return detail::WithBroadcast(l, A, B, name, tag);
 }
 
+/*!
+* \brief Creates an operation that performs pointwise maximum of 2 tensors
+* and broadcasts them into a common compatible shape where necessary,
+* according to numpy's rules
+*
+* \param A The first tensor
+* \param B The second tensor
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is a pointwise maximum with broadcast
+*/
+inline tvm::Tensor broadcast_maximum(const tvm::Tensor& A,
+                                 const tvm::Tensor& B,
+                                 std::string name = "tensor",
+                                 std::string tag = kBroadcast) {
+  auto l = [&](tvm::Expr a, tvm::Expr b) { return tvm::max(a, b); };  // NOLINT(*)
+  return detail::WithBroadcast(l, A, B, name, tag);
+}
+
+/*!
+* \brief Creates an operation that performs pointwise minimum of 2 tensors
+* and broadcasts them into a common compatible shape where necessary,
+* according to numpy's rules
+*
+* \param A The first tensor
+* \param B The second tensor
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is a pointwise minimum with broadcast
+*/
+inline tvm::Tensor broadcast_minimum(const tvm::Tensor& A,
+                                 const tvm::Tensor& B,
+                                 std::string name = "tensor",
+                                 std::string tag = kBroadcast) {
+  auto l = [&](tvm::Expr a, tvm::Expr b) { return tvm::min(a, b); };  // NOLINT(*)
+  return detail::WithBroadcast(l, A, B, name, tag);
+}
+
+/*!
+* \brief Creates an operation that raises one tensor to the power of another
+* pointwise and broadcasts them into a common compatible shape where necessary,
+* according to numpy's rules
+*
+* \param A The first tensor
+* \param B The second tensor to compute pow(A, B)
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is a pointwise pow with
+* broadcast
+*/
+inline tvm::Tensor broadcast_pow(const tvm::Tensor& A,
+                                 const tvm::Tensor& B,
+                                 std::string name = "tensor",
+                                 std::string tag = kBroadcast) {
+  auto l = [&](tvm::Expr a, tvm::Expr b) { return tvm::pow(a, b); };
+  return detail::WithBroadcast(l, A, B, name, tag);
+}
+
 }  // namespace topi
 
 #endif  // TOPI_BROADCAST_H_
diff --git a/topi/include/topi/contrib/cublas.h b/topi/include/topi/contrib/cublas.h
new file mode 100644
index 000000000000..0ce09e313d37
--- /dev/null
+++ b/topi/include/topi/contrib/cublas.h
@@ -0,0 +1,49 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \brief External function interface to cuBLAS libraries
+ * \file tags.h
+ */
+#ifndef TOPI_CONTRIB_CUBLAS_H_
+#define TOPI_CONTRIB_CUBLAS_H_
+
+#include "tvm/tvm.h"
+#include "topi/detail/extern.h"
+
+namespace topi {
+namespace contrib {
+using namespace tvm;
+using namespace topi::detail;
+/*!
+* \brief Create an op that multiplies lhs and rhs with cuBLAS
+*
+* \param lhs The left matrix operand
+* \param rhs The right matrix operand
+* \param transa Whether to transpose lhs
+* \param transb Whether to transpose rhs
+*
+* \return The output tensor
+*/
+inline Tensor cublas_matmul(const Tensor& lhs,
+                            const Tensor& rhs,
+                            bool transa,
+                            bool transb) {
+  auto n = transa ? lhs->shape[1] : lhs->shape[0];
+  auto m = transb ? rhs->shape[0] : rhs->shape[1];
+
+  return make_extern(
+    { { n, m } }, { lhs->dtype }, { lhs, rhs },
+    [&](Array<Buffer> ins, Array<Buffer> outs) {
+      return call_packed({
+        Expr("tvm.contrib.cublas.matmul"),
+        pack_buffer(ins[0]),
+        pack_buffer(ins[1]),
+        pack_buffer(outs[0]),
+        transa,
+        transb });
+    }, "C", "")[0];
+}
+
+}  // namespace contrib
+}  // namespace topi
+
+#endif  // TOPI_CONTRIB_CUBLAS_H_
diff --git a/topi/include/topi/contrib/rocblas.h b/topi/include/topi/contrib/rocblas.h
new file mode 100644
index 000000000000..dbadaf10b60b
--- /dev/null
+++ b/topi/include/topi/contrib/rocblas.h
@@ -0,0 +1,48 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \brief External function interface to rocBLAS libraries
+ * \file tags.h
+ */
+#ifndef TOPI_CONTRIB_ROCBLAS_H_
+#define TOPI_CONTRIB_ROCBLAS_H_
+
+#include "tvm/tvm.h"
+#include "topi/detail/extern.h"
+
+namespace topi {
+namespace contrib {
+using namespace tvm;
+/*!
+* \brief Create an op that multiplies lhs and rhs with rocBLAS
+*
+* \param lhs The left matrix operand
+* \param rhs The right matrix operand
+* \param transa Whether to transpose lhs
+* \param transb Whether to transpose rhs
+*
+* \return The output tensor
+*/
+inline Tensor rocblas_matmul(const Tensor& lhs,
+                             const Tensor& rhs,
+                             bool transa,
+                             bool transb) {
+  auto n = transa ? lhs->shape[1] : lhs->shape[0];
+  auto m = transb ? rhs->shape[0] : rhs->shape[1];
+
+  return make_extern(
+    { { n, m } }, { lhs->dtype }, { lhs, rhs },
+    [&](Array<Buffer> ins, Array<Buffer> outs) {
+      return call_packed({
+        Expr("tvm.contrib.rocblas.matmul"),
+        pack_buffer(ins[0]),
+        pack_buffer(ins[1]),
+        pack_buffer(outs[0]),
+        transa,
+        transb });
+    }, "C", "")[0];
+}
+
+}  // namespace contrib
+}  // namespace topi
+
+#endif  // TOPI_CONTRIB_ROCBLAS_H_
diff --git a/topi/include/topi/cuda/dense.h b/topi/include/topi/cuda/dense.h
new file mode 100644
index 000000000000..3504ef3bfd17
--- /dev/null
+++ b/topi/include/topi/cuda/dense.h
@@ -0,0 +1,134 @@
+/*!
+*  Copyright (c) 2017 by Contributors
+* \file cuda/dense.h
+* \brief CUDA schedule for dense operation
+*/
+#ifndef TOPI_CUDA_DENSE_H_
+#define TOPI_CUDA_DENSE_H_
+
+#include "tvm/tvm.h"
+#include "tvm/build_module.h"
+#include "topi/tags.h"
+#include "topi/detail/array_utils.h"
+#include "topi/nn/dense.h"
+#include "topi/contrib/cublas.h"
+#include "topi/generic/extern.h"
+
+namespace topi {
+using namespace tvm;
+
+namespace cuda {
+/*!
+* \brief Implementation of dense for CUDA backend
+*
+* \param target The target device
+* \param data Tensor with shape [batch, in_dim]
+* \param weight Tensor with shape [out_dim, in_dim]
+* \param bias Tensor with shape [out_dim] (optional)
+*
+* \return Tensor with shape [batch, out_dim]
+*/
+inline tvm::Tensor dense_cuda(const Target& target,
+                              const tvm::Tensor& data,
+                              const tvm::Tensor& weight,
+                              tvm::Tensor* bias) {
+  CHECK_EQ(data->shape.size(), 2) << "dense requires 2-D data";
+  CHECK_EQ(weight->shape.size(), 2) << "dense requires 2-D weight";
+  if (bias != nullptr) {
+    CHECK_EQ((*bias)->shape.size(), 1) << "dense requires 1-D bias";
+  }
+
+  auto batch = data->shape[0];
+  auto in_dim = data->shape[1];
+  auto out_dim = weight->shape[0];
+
+  if (target.libs.count("cublas") > 0) {
+    auto mm = topi::contrib::cublas_matmul(data, weight, false, true);
+    if (bias != nullptr) {
+      auto bias_val = *bias;
+      mm = tvm::compute({ batch, out_dim },
+                        [&](Var i, Var j) {
+                          return mm(i, j) + bias_val(j);
+                        }, "tensor", kBroadcast);
+    }
+
+    return mm;
+  } else {
+    return topi::nn::dense(data, weight, bias);
+  }
+}
+
+/*!
+* \brief Create a CUDA schedule for dense
+*
+* \param target The target to generate a schedule for.
+* \param outs The output tensors.
+*
+* \return A schedule for the given ops.
+*/
+Schedule schedule_dense(const Target &target, const Array<Tensor>& outs) {
+  if (target.target_name == "cuda" &&
+    target.libs.count("cublas") > 0) {
+    return topi::generic::schedule_extern(target, outs);
+  }
+
+  Array<Operation> out_ops;
+  for (auto t : outs) {
+    out_ops.push_back(t->op);
+  }
+  auto s = create_schedule(out_ops);
+
+  auto _schedule = [&](const Tensor& dense) {
+    auto num_thread = 64;
+    auto k = dense->op.as<ComputeOpNode>()->reduce_axis[0];
+    IterVar ko, kf;
+    s[dense].split(k, num_thread, &ko, &kf);
+    auto dense_f = s.rfactor(dense, kf)[0];
+
+    Tensor out;
+    if (contains(s->outputs, dense->op)) {
+      out = dense;
+    } else {
+      out = outs[0]->op.output(0);
+      s[dense].compute_at(s[out], s[out]->op.as<ComputeOpNode>()->axis[1]);
+    }
+    s[out].bind(s[out]->op.as<ComputeOpNode>()->axis[0], tvm::thread_axis(Range(), "blockIdx.y"));
+    s[out].bind(s[out]->op.as<ComputeOpNode>()->axis[1], tvm::thread_axis(Range(), "blockIdx.x"));
+
+    auto tx = s[dense]->op.as<ComputeOpNode>()->reduce_axis[0];
+    auto thread_x = tvm::thread_axis(Range(), "threadIdx.x");
+    s[dense].bind(tx, thread_x);
+    s[dense_f].compute_at(s[dense], tx);
+    s[dense].set_store_predicate(static_cast<Expr>(thread_x) == 0);
+    s[out].set_store_predicate(static_cast<Expr>(thread_x) == 0);
+  };
+
+  std::function<void(Operation)> traverse;
+  traverse = [&](const Operation& op) {
+    // Inline all one-to-one-mapping operators except the last stage (output)
+    if (is_broadcast(op->tag)) {
+      if (!contains(s->outputs, op)) {
+        s[op].compute_inline();
+      }
+      for (auto tensor : op->InputTensors()) {
+        if (tensor->op->InputTensors().size() > 0) {
+          traverse(tensor->op);
+        }
+      }
+    } else if (op->tag == "dense") {
+      // If tag starts with global_pool
+      auto dense = op.output(0);
+      _schedule(dense);
+    } else {
+      LOG(ERROR) << "Unsupported operator " << op->tag;
+    }
+  };
+
+  traverse(outs[0]->op);
+  return s;
+}
+
+}  // namespace cuda
+}  // namespace topi
+#endif  // TOPI_CUDA_DENSE_H_
+
diff --git a/topi/include/topi/cuda/extern.h b/topi/include/topi/cuda/extern.h
new file mode 100644
index 000000000000..27380b3aa77d
--- /dev/null
+++ b/topi/include/topi/cuda/extern.h
@@ -0,0 +1,68 @@
+/*!
+*  Copyright (c) 2017 by Contributors
+* \file cuda/extern.h
+* \brief CUDA schedule for extern followed by injective operations
+*/
+#ifndef TOPI_CUDA_EXTERN_H_
+#define TOPI_CUDA_EXTERN_H_
+
+#include "topi/tags.h"
+#include "topi/detail/fuse.h"
+#include "tvm/tvm.h"
+#include "tvm/build_module.h"
+
+namespace topi {
+using namespace tvm;
+
+namespace cuda {
+/*!
+ * \brief Schedule a given operation representing one of the outputs of an
+ * external function which is followed by injective operations.
+ *
+ * \param target The target to generate a schedule for.
+ * \param op The operation representing the output followed by injective operations.
+ * \param sch The schedule to apply this scheduling to
+ *
+ * \return The schedule given by sch
+ */
+Schedule ScheduleOutputForExtern(Target target, Operation op, Schedule sch) {
+  auto x = op.output(0);
+  auto fused = Fuse(sch[x], sch[x]->op.as<ComputeOpNode>()->axis);
+  auto num_thread = target.max_num_threads;
+  IterVar bx, tx;
+  sch[x].split(fused, num_thread, &bx, &tx);
+  sch[x].bind(bx, tvm::thread_axis(Range(), "blockIdx.x"));
+  sch[x].bind(tx, tvm::thread_axis(Range(), "threadIdx.x"));
+  return sch;
+}
+
+/*!
+* \brief Schedule an extern op followed by injective operations.
+* For example, cudnn kernel + bias add + relu
+*
+* \param target The target to generate a schedule for.
+* \param outs The output tensors.
+*
+* \return A schedule for the op.
+*/
+Schedule schedule_extern(const Target& target, Array<Tensor> outs) {
+  Array<Operation> out_ops;
+  for (auto t : outs) {
+    out_ops.push_back(t->op);
+  }
+  auto s = create_schedule(out_ops);
+
+  tvm::schedule::AutoInlineInjective(s);
+  for (auto out : outs) {
+    if (out->op->derived_from<ExternOpNode>()) {
+      continue;
+    }
+    ScheduleOutputForExtern(target, out->op, s);
+  }
+
+  return s;
+}
+
+}  // namespace cuda
+}  // namespace topi
+#endif  // TOPI_CUDA_EXTERN_H_
diff --git a/topi/include/topi/cuda/injective.h b/topi/include/topi/cuda/injective.h
new file mode 100644
index 000000000000..31ff5e5383d1
--- /dev/null
+++ b/topi/include/topi/cuda/injective.h
@@ -0,0 +1,58 @@
+/*!
+*  Copyright (c) 2017 by Contributors
+* \file cuda/injective.h
+* \brief CUDA schedule for injective operations
+*/
+#ifndef TOPI_CUDA_INJECTIVE_H_
+#define TOPI_CUDA_INJECTIVE_H_
+
+#include "topi/tags.h"
+#include "topi/detail/fuse.h"
+#include "tvm/tvm.h"
+#include "tvm/build_module.h"
+
+namespace topi {
+using namespace tvm;
+
+namespace cuda {
+/*!
+* \brief Schedule a given injective operation.
+*
+* \param target The target to generate a schedule for.
+* \param op The operation representing the injective operation.
+* \param s The schedule to apply this scheduling to
+*/
+void ScheduleInjectiveOp(const Target &target, Operation op, Schedule s) {
+  auto x = op.output(0);
+  auto fused = Fuse(s[x], s[x]->op.as<ComputeOpNode>()->axis);
+  auto num_thread = target.max_num_threads;
+  IterVar bx, tx;
+  s[x].split(fused, num_thread, &bx, &tx);
+  s[x].bind(bx, thread_axis(Range(), "blockIdx.x"));
+  s[x].bind(tx, thread_axis(Range(), "threadIdx.x"));
+}
+
+/*!
+ * \brief Create a CUDA schedule for the given output tensors.
+ *
+ * \param target The target to generate a schedule for.
+ * \param outs The output tensors.
+ *
+ * \return A schedule for the given ops.
+ */
+Schedule schedule_injective(const Target &target, const Array<Tensor>& outs) {
+  Array<Operation> out_ops;
+  for (auto t : outs) {
+    out_ops.push_back(t->op);
+  }
+  auto s = create_schedule(out_ops);
+  tvm::schedule::AutoInlineInjective(s);
+  for (auto out : outs) {
+    ScheduleInjectiveOp(target, out->op, s);
+  }
+  return s;
+}
+
+}  // namespace cuda
+}  // namespace topi
+#endif  // TOPI_CUDA_INJECTIVE_H_
diff --git a/topi/include/topi/cuda/pooling.h b/topi/include/topi/cuda/pooling.h
new file mode 100644
index 000000000000..934e845e335d
--- /dev/null
+++ b/topi/include/topi/cuda/pooling.h
@@ -0,0 +1,163 @@
+/*!
+*  Copyright (c) 2017 by Contributors
+* \file cuda/pooling.h
+* \brief CUDA schedule for pooling operations
+*/
+#ifndef TOPI_CUDA_POOLING_H_
+#define TOPI_CUDA_POOLING_H_
+
+#include "topi/tags.h"
+#include "topi/detail/fuse.h"
+#include "topi/detail/array_utils.h"
+#include "tvm/tvm.h"
+#include "tvm/build_module.h"
+
+namespace topi {
+using namespace tvm;
+
+namespace cuda {
+
+/*!
+* \brief Create a CUDA schedule for pool
+*
+* \param target The target to generate a schedule for.
+* \param outs The output tensors.
+*
+* \return A schedule for the given ops.
+*/
+Schedule schedule_pool(const Target &target, const Array<Tensor>& outs) {
+  Array<Operation> out_ops;
+  for (auto t : outs) {
+    out_ops.push_back(t->op);
+  }
+  auto s = create_schedule(out_ops);
+
+  auto _schedule = [&](const Tensor& padded_input, const Tensor& pool) {
+    s[padded_input].compute_inline();
+    auto num_thread = target.max_num_threads;
+    Tensor out;
+    Tensor OL;
+    if (contains(s->outputs, pool->op)) {
+      out = pool;
+      OL = s.cache_write(pool, "local");
+    } else {
+      out = outs[0]->op.output(0);
+      s[pool].set_scope("local");
+    }
+    auto fused = Fuse(s[out], s[out]->op.as<ComputeOpNode>()->axis);
+    IterVar bx, tx;
+    s[out].split(fused, num_thread, &bx, &tx);
+    s[out].bind(bx, tvm::thread_axis(Range(), "blockIdx.x"));
+    s[out].bind(tx, tvm::thread_axis(Range(), "threadIdx.x"));
+    if (contains(s->outputs, pool->op)) {
+      s[OL].compute_at(s[out], tx);
+    } else {
+      s[pool].compute_at(s[out], tx);
+    }
+  };
+
+  std::function<void(Operation)> traverse;
+  traverse = [&](const Operation& op) {
+    // Inline all one-to-one-mapping operators except the last stage (output)
+    if (is_broadcast(op->tag)) {
+      if (!contains(s->outputs, op)) {
+        s[op].compute_inline();
+      }
+      for (auto tensor : op->InputTensors()) {
+        if (tensor->op->InputTensors().size() > 0) {
+          traverse(tensor->op);
+        }
+      }
+    } else if (op->tag.rfind("pool", 0) == 0) {
+      // If tag starts with pool
+      auto padded_input = op->InputTensors()[0];
+      auto pool = op.output(0);
+      _schedule(padded_input, pool);
+    } else {
+      LOG(ERROR) << "Unsupported operator " << op->tag;
+    }
+  };
+
+  traverse(outs[0]->op);
+  return s;
+}
+
+/*!
+* \brief Create a CUDA schedule for global_pool
+*
+* \param target The target to generate a schedule for.
+* \param outs The output tensors.
+*
+* \return A schedule for the given ops.
+*/
+Schedule schedule_global_pool(const Target &target, const Array<Tensor>& outs) {
+  Array<Operation> out_ops;
+  for (auto t : outs) {
+    out_ops.push_back(t->op);
+  }
+  auto s = create_schedule(out_ops);
+
+  auto _schedule = [&](const Tensor& pool) {
+    auto num_thread = 8;
+    auto block_x = tvm::thread_axis(Range(), "blockIdx.x");
+    auto block_y = tvm::thread_axis(Range(), "blockIdx.y");
+    auto thread_x = tvm::thread_axis(Range(0, num_thread), "threadIdx.x");
+    auto thread_y = tvm::thread_axis(Range(0, num_thread), "threadIdx.y");
+    Tensor out;
+    Tensor OL;
+    if (contains(s->outputs, pool->op)) {
+      out = pool;
+      OL = s.cache_write(pool, "local");
+    } else {
+      out = outs[0]->op.output(0);
+      s[pool].set_scope("local");
+    }
+
+    auto i = s[out]->op.as<ComputeOpNode>()->axis[0];
+    auto c = s[out]->op.as<ComputeOpNode>()->axis[1];
+
+    IterVar by, ty;
+    s[out].split(i, num_thread, &by, &ty);
+    IterVar bx, tx;
+    s[out].split(c, num_thread, &bx, &tx);
+    s[out].reorder({ by, bx, ty, tx });
+    s[out].bind(ty, thread_y);
+    s[out].bind(tx, thread_x);
+    s[out].bind(by, block_y);
+    s[out].bind(bx, block_x);
+
+    if (contains(s->outputs, pool->op)) {
+      s[OL].compute_at(s[out], tx);
+    } else {
+      s[pool].compute_at(s[out], tx);
+    }
+  };
+
+  std::function<void(Operation)> traverse;
+  traverse = [&](const Operation& op) {
+    // Inline all one-to-one-mapping operators except the last stage (output)
+    if (is_broadcast(op->tag)) {
+      if (!contains(s->outputs, op)) {
+        s[op].compute_inline();
+      }
+      for (auto tensor : op->InputTensors()) {
+        if (tensor->op->InputTensors().size() > 0) {
+          traverse(tensor->op);
+        }
+      }
+    } else if (op->tag.rfind("global_pool", 0) == 0) {
+      // If tag starts with global_pool
+      auto pool = op.output(0);
+      _schedule(pool);
+    } else {
+      LOG(ERROR) << "Unsupported operator " << op->tag;
+    }
+  };
+
+  traverse(outs[0]->op);
+  return s;
+}
+
+}  // namespace cuda
+}  // namespace topi
+#endif  // TOPI_CUDA_POOLING_H_
diff --git a/topi/include/topi/cuda/reduction.h b/topi/include/topi/cuda/reduction.h
new file mode 100644
index 000000000000..554224a9036c
--- /dev/null
+++ b/topi/include/topi/cuda/reduction.h
@@ -0,0 +1,181 @@
+/*!
+*  Copyright (c) 2017 by Contributors
+* \file cuda/reduction.h
+* \brief CUDA schedule for reduction operations
+*/
+#ifndef TOPI_CUDA_REDUCTION_H_
+#define TOPI_CUDA_REDUCTION_H_
+
+#include "topi/tags.h"
+#include "topi/detail/fuse.h"
+#include "tvm/tvm.h"
+#include "tvm/build_module.h"
+
+namespace topi {
+using namespace tvm;
+
+namespace cuda {
+/*!
+ * \brief Schedule a given reduce operation.
+ *
+ * \param target The target to generate a schedule for.
+ * \param op The operation representing the injective operation.
+ * \param sch The schedule to apply this scheduling to
+ * \param is_idx_reduce Pass true to schedule a reduce op that returns
+ * an index, such as argmax or argmin.
+ *
+ * \return The schedule given by sch
+*/
+Schedule ScheduleReduce(const Target& target,
+                        Operation op,
+                        Schedule sch,
+                        bool is_idx_reduce = false) {
+  Tensor data_out;
+  Tensor data_in;
+
+  if (!is_idx_reduce) {
+    data_in = op->InputTensors()[0];
+    data_out = op.output(0);
+  } else {
+    data_out = op->InputTensors()[0];
+  }
+
+  auto out_stage = sch[data_out];
+  CHECK_GT(out_stage->op.as<ComputeOpNode>()->reduce_axis.size(), 0) <<
+    "reduce_axis must be greater than zero";
+
+  bool all_reduce;
+  int num_thread;
+  IterVar block_x, thread_x, thread_y;
+
+  if (out_stage->op.as<ComputeOpNode>()->axis.size() > 0) {
+    all_reduce = false;
+    num_thread = 32;
+    if (target.target_name == "opencl") {
+      // Without this, CL_INVALID_WORK_GROUP_SIZE occurs with python tests.
+      // Don't know why.
+      num_thread = 16;
+    }
+    block_x = tvm::thread_axis(Range(), "blockIdx.x");
+    thread_x = tvm::thread_axis(Range(0, num_thread), "threadIdx.x");
+    thread_y = tvm::thread_axis(Range(0, num_thread), "threadIdx.y");
+  } else {
+    all_reduce = true;
+    num_thread = target.max_num_threads;
+    thread_x = tvm::thread_axis(Range(0, num_thread), "threadIdx.x");
+  }
+
+  auto fused_reduce = Fuse(out_stage, out_stage->op.as<ComputeOpNode>()->reduce_axis);
+
+  IterVar ko, ki;
+  out_stage.split(fused_reduce, num_thread, &ko, &ki);
+  auto data_out_rf = sch.rfactor(data_out, ki)[0];
+  auto tx = out_stage->op.as<ComputeOpNode>()->reduce_axis[0];
+  out_stage.bind(tx, thread_x);
+  sch[data_out_rf].compute_at(out_stage, tx);
+
+  Tensor real_output;
+  Tensor temp_idx_input, temp_val_input;
+  if (is_idx_reduce) {
+    real_output = op.output(0);
+    temp_idx_input = data_out->op.output(0);
+    temp_val_input = data_out->op.output(1);
+  } else {
+    real_output = data_out;
+  }
+
+  auto stage_real = sch[real_output];
+  if (!all_reduce) {
+    // Fuse and split the axis
+    auto fused_outer = Fuse(stage_real, stage_real->op.as<ComputeOpNode>()->axis);
+    IterVar bx, outer_in;
+    stage_real.split(fused_outer, num_thread, &bx, &outer_in);
+
+    // Bind the axes to threads and blocks
+    stage_real.bind(outer_in, thread_y);
+    stage_real.bind(bx, block_x);
+    if (is_idx_reduce) {
+      sch[temp_idx_input].compute_at(stage_real, outer_in);
+      sch[temp_val_input].compute_at(stage_real, outer_in);
+    }
+  } else {
+    if (is_idx_reduce) {
+      sch[temp_idx_input].compute_at(stage_real,
+                                     stage_real->op.as<ComputeOpNode>()->axis[0]);
+      sch[temp_val_input].compute_at(stage_real,
+                                     stage_real->op.as<ComputeOpNode>()->axis[0]);
+    }
+  }
+
+  stage_real.set_store_predicate(static_cast<Expr>(thread_x) == 0);
+  return sch;
+}
+
+/*!
+ * \brief Recursively traverse operator inputs, setting injective inputs
+ * to be computed inline.
+ *
+ * \param s The schedule we are building
+ * \param op The current op in the traversal
+ */
+void TraverseBeforeReduce(Schedule s, Operation op) {
+  if (op->derived_from<PlaceholderOpNode>()) {
+    return;
+  } else if (is_injective(op->tag)) {
+    s[op].compute_inline();
+    for (auto tensor : op->InputTensors()) {
+      TraverseBeforeReduce(s, tensor->op);
+    }
+  } else {
+    LOG(ERROR) << "Unsupported operator " << op->tag;
+  }
+}
+
+/*!
+* \brief Schedule a reduce op, then invoke TraverseBeforeReduce on each
+* of the op's inputs.
+*
+* \param target The target to generate a schedule for.
+* \param s The schedule we are building
+* \param op The reduce op
+*/
+void TraverseAfterReduce(const Target& target, Schedule s, Operation op) {
+  if (is_broadcast(op->tag)) {
+    LOG(ERROR) << "Elementwise op after reduce is not yet supported";
+  } else if (op->tag == kCommReduce) {
+    ScheduleReduce(target, op, s, false);
+    for (auto tensor : op->InputTensors()) {
+      TraverseBeforeReduce(s, tensor->op);
+    }
+  } else if (op->tag == kCommReduceIdx) {
+    ScheduleReduce(target, op, s, true);
+    for (auto tensor : op->InputTensors()[0]->op->InputTensors()) {
+      TraverseBeforeReduce(s, tensor->op);
+    }
+  } else {
+    LOG(ERROR) << "Unsupported operator " << op->tag;
+  }
+}
+
+/*!
+* \brief Create a CUDA schedule for a reduce operation.
+*
+* \param target The target to generate a schedule for.
+* \param outs The output tensors.
+*
+* \return A schedule for the given ops.
+*/
+Schedule schedule_reduce(const Target& target, Array<Tensor> outs) {
+  CHECK_EQ(outs.size(), 1) << "outs must have size 1";
+  Array<Operation> out_ops;
+  for (auto t : outs) {
+    out_ops.push_back(t->op);
+  }
+  auto s = create_schedule(out_ops);
+  TraverseAfterReduce(target, s, outs[0]->op);
+  return s;
+}
+
+}  // namespace cuda
+}  // namespace topi
+#endif  // TOPI_CUDA_REDUCTION_H_
diff --git a/topi/include/topi/cuda/softmax.h b/topi/include/topi/cuda/softmax.h
new file mode 100644
index 000000000000..77121c20be69
--- /dev/null
+++ b/topi/include/topi/cuda/softmax.h
@@ -0,0 +1,62 @@
+/*!
+*  Copyright (c) 2017 by Contributors
+* \file cuda/injective.h
+* \brief CUDA schedule for injective operations
+*/
+#ifndef TOPI_CUDA_SOFTMAX_H_
+#define TOPI_CUDA_SOFTMAX_H_
+
+#include "topi/tags.h"
+#include "topi/detail/fuse.h"
+#include "tvm/tvm.h"
+#include "tvm/build_module.h"
+
+namespace topi {
+using namespace tvm;
+
+namespace cuda {
+
+/*!
+ * \brief Create a CUDA schedule for the given softmax output tensors.
+ *
+ * \param target The target to generate a schedule for.
+ * \param outs The output tensors.
+ *
+ * \return A schedule for the given ops.
+ */
+Schedule schedule_softmax(const Target &target, const Array<Tensor>& outs) {
+  Array<Operation> out_ops;
+  for (auto t : outs) {
+    out_ops.push_back(t->op);
+  }
+  auto s = create_schedule(out_ops);
+
+  auto softmax = outs[0];
+  auto max_elem = softmax->op->InputTensors()[1];
+  auto expsum = softmax->op->InputTensors()[2];
+
+  int num_thread = 64;
+  auto block_x = tvm::thread_axis(Range(), "blockIdx.x");
+  auto thread_x = tvm::thread_axis(Range(0, num_thread), "threadIdx.x");
+
+  s[max_elem].bind(max_elem->op.as<ComputeOpNode>()->axis[0], block_x);
+
+  auto k = expsum->op.as<ComputeOpNode>()->reduce_axis[0];
+  IterVar ko, ki;
+  s[expsum].split(k, num_thread, &ko, &ki);
+  auto EF = s.rfactor(expsum, ki)[0];
+  s[expsum].bind(s[expsum]->op.as<ComputeOpNode>()->axis[0], block_x);
+  s[expsum].bind(s[expsum]->op.as<ComputeOpNode>()->reduce_axis[0], thread_x);
+  s[EF].compute_at(s[expsum], s[expsum]->op.as<ComputeOpNode>()->reduce_axis[0]);
+  s[expsum].set_store_predicate(thread_x->var == 0);
+
+  IterVar tx, xi;
+  s[softmax].split_by_nparts(softmax->op.as<ComputeOpNode>()->axis[1], num_thread, &tx, &xi);
+  s[softmax].bind(tx, thread_x);
+
+  return s;
+}
+
+}  // namespace cuda
+}  // namespace topi
+#endif  // TOPI_CUDA_SOFTMAX_H_
diff --git a/topi/include/topi/detail/array_utils.h b/topi/include/topi/detail/array_utils.h
new file mode 100644
index 000000000000..ca94bd6a1202
--- /dev/null
+++ b/topi/include/topi/detail/array_utils.h
@@ -0,0 +1,35 @@
+/*!
+*  Copyright (c) 2017 by Contributors
+* \file array_utils.h
+* \brief Utility functions for handling arrays
+*/
+#ifndef TOPI_DETAIL_ARRAY_UTILS_H_
+#define TOPI_DETAIL_ARRAY_UTILS_H_
+
+#include "tvm/tvm.h"
+
+namespace topi {
+namespace detail {
+using namespace tvm;
+
+/*!
+ * \brief Search an array for a specific item
+ *
+ * \param array The array to search
+ * \param item The item to search for
+ *
+ * \return True iff the given array contains the given item.
+ */
+template<typename T>
+bool contains(Array<T> array, T item) {
+  for (auto& i : array) {
+    if (i == item) {
+      return true;
+    }
+  }
+  return false;
+}
+
+}  // namespace detail
+}  // namespace topi
+#endif  // TOPI_DETAIL_ARRAY_UTILS_H_
diff --git a/topi/include/topi/detail/broadcast.h b/topi/include/topi/detail/broadcast.h
index 19f074661734..ba7193cd0647 100644
--- a/topi/include/topi/detail/broadcast.h
+++ b/topi/include/topi/detail/broadcast.h
@@ -69,10 +69,10 @@ inline tvm::Array<tvm::Expr> InputIndexFromBroadcast(
   tvm::Array<tvm::Expr> ivars;
   CHECK_EQ(ovars.size(), all_vars.size());
   // N^2, could use a map but NBD..
-  int expected_dims = T->shape.size();
-  for (int i = 0; i < ovars.size(); ++i) {
+  size_t expected_dims = T->shape.size();
+  for (size_t i = 0; i < ovars.size(); ++i) {
     bool found = false;
-    for (int j = 0; j < my_vars.size(); ++j) {
+    for (size_t j = 0; j < my_vars.size(); ++j) {
     if (all_vars[i].same_as(my_vars[j])) {
         ivars.push_back(ovars[i]);
         found = true;
diff --git a/topi/include/topi/detail/constant_utils.h b/topi/include/topi/detail/constant_utils.h
new file mode 100644
index 000000000000..eb3219e9b25a
--- /dev/null
+++ b/topi/include/topi/detail/constant_utils.h
@@ -0,0 +1,70 @@
+/*!
+*  Copyright (c) 2017 by Contributors
+* \file constant_utils.h
+* \brief Utility functions for handling constants in TVM expressions
+*/
+#ifndef TOPI_DETAIL_CONSTANT_UTILS_H_
+#define TOPI_DETAIL_CONSTANT_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include "tvm/tvm.h"
+
+namespace topi {
+namespace detail {
+using namespace tvm;
+
+/*!
+ * \brief Test whether the given Expr is a constant integer
+ *
+ * \param expr the Expr to query
+ *
+ * \return true if the given expr is a constant int or uint, false otherwise.
+ */
+bool IsConstInt(Expr expr) {
+  return
+    expr->derived_from<tvm::ir::IntImm>() ||
+    expr->derived_from<tvm::ir::UIntImm>();
+}
+
+/*!
+ * \brief Get the value of the given constant integer expression. An error
+ * is logged if the given expression is not a constant integer.
+ *
+ * \param expr The expression to get the value of
+ *
+ * \return The integer value.
+ */
+int64_t GetConstInt(Expr expr) {
+  if (expr->derived_from<tvm::ir::IntImm>()) {
+    return expr.as<tvm::ir::IntImm>()->value;
+  }
+  if (expr->derived_from<tvm::ir::UIntImm>()) {
+    return expr.as<tvm::ir::UIntImm>()->value;
+  }
+  LOG(ERROR) << "expr must be a constant integer";
+  return -1;
+}
+
+/*!
+ * \brief Get the value of all the constant integer expressions in the given array
+ *
+ * \param exprs The array of expressions to get the values of
+ * \param var_name The name to be used when logging an error in the event that any
+ * of the expressions are not constant integers.
+ *
+ * \return A vector of the integer values
+ */
+std::vector<int> GetConstIntValues(Array<Expr> exprs, const std::string& var_name) {
+  std::vector<int> result;
+  for (auto expr : exprs) {
+    CHECK(IsConstInt(expr)) << "All elements of " << var_name << " must be constant integers";
+    result.push_back(GetConstInt(expr));
+  }
+  return result;
+}
+
+}  // namespace detail
+}  // namespace topi
+#endif  // TOPI_DETAIL_CONSTANT_UTILS_H_
diff --git a/topi/include/topi/detail/extern.h b/topi/include/topi/detail/extern.h
new file mode 100644
index 000000000000..12cc65d72530
--- /dev/null
+++ b/topi/include/topi/detail/extern.h
@@ -0,0 +1,137 @@
+/*!
+*  Copyright (c) 2017 by Contributors
+* \file detail/extern.h
+* \brief Helpers for using external functions
+*/
+#ifndef TOPI_DETAIL_EXTERN_H_
+#define TOPI_DETAIL_EXTERN_H_
+
+#include <vector>
+#include <string>
+
+#include "tvm/tvm.h"
+
+namespace topi {
+namespace detail {
+using namespace tvm;
+
+/*!
+ * \brief Construct a buffer to pass to an external function
+ *
+ * \param shape The shape of the buffer
+ * \param dtype The type of the buffer elements
+ * \param name The name of the buffer
+ *
+ * \return The Buffer object
+ */
+Buffer DeclExternBuffer(Array<Expr> shape,
+                        Type dtype,
+                        std::string name) {
+  auto data = var(name, Handle());
+  auto elem_offset = Expr();
+  return BufferNode::make(data, dtype, shape, Array<Expr>(), elem_offset, name, "",
+                          -1, 0);
+}
+
+/*!
+ * \brief A function which constructs an Expr representing the invocation of an external
+ * function. The function expects two arguments: an array of Buffers holding the input
+ * tensor values, and a pre-allocated array of Buffers to be filled with the outputs.
+ */
+using FExtern = std::function<Expr(Array<Buffer>, Array<Buffer>)>;
+
+/*!
+ * \brief Create tensors representing the result of invoking an external function.
+ * This function will create the necessary buffers to hold input and output tensor values.
+ *
+ * \param out_shapes An array where each element is the shape of the corresponding output tensor.
+ * \param out_types An array where each element is the dtype of the corresponding output tensor.
+ * \param inputs An array of input Tensors
+ * \param fextern A function that constructs an Expr representing the invocation of
+ * the external function given the input and output buffers.
+ * \param name The name of the operation
+ * \param tag The tag to mark the operation
+ *
+ * \return An array of Tensors representing the outputs of the function invocation. There will
+ * be one output Tensor for each element of out_shapes, with dtype equal to the corresponding
+ * element of out_types.
+ */
+Array<Tensor> make_extern(const Array< Array<Expr> >& out_shapes,
+                          const std::vector<Type>& out_types,
+                          const Array<Tensor>& inputs,
+                          FExtern fextern,
+                          std::string name,
+                          std::string tag) {
+  CHECK_EQ(out_shapes.size(), out_types.size())
+    << "make_extern: out_shapes and out_types must have equal size";
+
+  Array<Buffer> input_placeholders;
+  for (auto t : inputs) {
+    input_placeholders.push_back(DeclExternBuffer(t->shape, t->dtype, t->op->name));
+  }
+  Array<Buffer> output_placeholders;
+  for (size_t i = 0; i < out_shapes.size(); ++i) {
+    output_placeholders.push_back(DeclExternBuffer(out_shapes[i], out_types[i], name));
+  }
+
+  auto body = fextern(input_placeholders, output_placeholders);
+  auto body_stmt = tvm::ir::Evaluate::make(body);
+
+  auto op = ExternOpNode::make(
+    name, tag, inputs, input_placeholders, output_placeholders, body_stmt);
+
+  Array<Tensor> outputs;
+  for (size_t i = 0; i < output_placeholders.size(); ++i) {
+    outputs.push_back(op.output(i));
+  }
+  return outputs;
+}
+
+/*!
+ * \brief This function is used to create a DLTensor structure on the stack to
+ * be able to pass a symbolic buffer as arguments to TVM PackedFunc
+ *
+ * \param buf The buffer to pack
+ *
+ * \return An expression representing the pack operation
+ */
+Expr pack_buffer(Buffer buf) {
+  CHECK_GT(buf->shape.size(), 0) << "buf shape must have at least one element";
+  auto shape = tvm::ir::Call::make(Handle(), tvm::ir::intrinsic::tvm_stack_make_shape,
+                                   buf->shape, tvm::ir::Call::CallType::Intrinsic);
+  Expr strides;
+  if (buf->strides.size() > 0) {
+    strides = tvm::ir::Call::make(Handle(), tvm::ir::intrinsic::tvm_stack_make_shape,
+                                  buf->shape, tvm::ir::Call::CallType::Intrinsic);
+  } else {
+    strides = 0;
+  }
+  Array<Expr> pack_args{
+    buf->data,
+    shape,
+    strides,
+    make_const(Int(32), buf->shape.size()),
+    make_const(buf->dtype, 0),
+    buf->elem_offset
+  };
+  return tvm::ir::Call::make(Handle(), tvm::ir::intrinsic::tvm_stack_make_array,
+                             pack_args, tvm::ir::Call::CallType::Intrinsic);
+}
+
+/*!
+ * \brief Construct an Expr representing the invocation of a PackedFunc
+ *
+ * \param args An array containing the registered name of the PackedFunc followed
+ * by the arguments to pass to the PackedFunc when called. The first element of the
+ * array must be a constant string expression.
+ *
+ * \return An expression representing the invocation 
+ */
+Expr call_packed(Array<Expr> args) {
+  return tvm::ir::Call::make(Int(32), tvm::ir::intrinsic::tvm_call_packed,
+                             args, tvm::ir::Call::CallType::Intrinsic);
+}
+
+}  // namespace detail
+}  // namespace topi
+#endif  // TOPI_DETAIL_EXTERN_H_
diff --git a/topi/include/topi/detail/fuse.h b/topi/include/topi/detail/fuse.h
new file mode 100644
index 000000000000..8ace8bf97fd3
--- /dev/null
+++ b/topi/include/topi/detail/fuse.h
@@ -0,0 +1,37 @@
+/*!
+*  Copyright (c) 2017 by Contributors
+* \file fuse.h
+* \brief Fuse operation
+*/
+#ifndef TOPI_DETAIL_FUSE_H_
+#define TOPI_DETAIL_FUSE_H_
+
+#include "tvm/tvm.h"
+
+namespace topi {
+namespace detail {
+using namespace tvm;
+
+/*!
+ * \brief Fuse all of the given args
+ * 
+ * \param stage The stage in which to apply the fuse
+ * \param args The iteration variables to be fused
+ *
+ * \return The fused iteration variable
+ */
+IterVar Fuse(Stage stage, const Array<IterVar>& args) {
+  CHECK_GE(args.size(), 1) << "Fuse requires at least 1 arg";
+
+  auto fused = args[0];
+  for (size_t i = 1; i < args.size(); ++i) {
+    IterVar out;
+    stage.fuse(fused, args[i], &out);
+    fused = out;
+  }
+  return fused;
+}
+
+}  // namespace detail
+}  // namespace topi
+#endif  // TOPI_DETAIL_FUSE_H_
diff --git a/topi/include/topi/detail/pad_utils.h b/topi/include/topi/detail/pad_utils.h
new file mode 100644
index 000000000000..e284859c1e50
--- /dev/null
+++ b/topi/include/topi/detail/pad_utils.h
@@ -0,0 +1,38 @@
+/*!
+*  Copyright (c) 2017 by Contributors
+* \file pad_utils.h
+* \brief Padding helpers
+*/
+#ifndef TOPI_DETAIL_PAD_UTILS_H_
+#define TOPI_DETAIL_PAD_UTILS_H_
+
+#include <vector>
+
+#include "tvm/tvm.h"
+
+namespace topi {
+namespace detail {
+using namespace tvm;
+
+/*!
+ * \brief Get padding size for each side given padding height and width
+ *
+ * \param pad_h The amount to pad each of the top and bottom sides
+ * \param pad_w The amount to pad each of the left and right sides
+ *
+ * \return An array of 4 elements, representing padding sizes for
+ * each individual side. The array is in the order { top, left, bottom, right }
+ */
+Array<Expr> GetPadTuple(Expr pad_h, Expr pad_w) {
+  pad_h *= 2;
+  pad_w *= 2;
+
+  auto pad_top = (pad_h + 1) / 2;
+  auto pad_left = (pad_w + 1) / 2;
+
+  return { pad_top, pad_left, pad_h - pad_top, pad_w - pad_left };
+}
+
+}  // namespace detail
+}  // namespace topi
+#endif  // TOPI_DETAIL_PAD_UTILS_H_
diff --git a/topi/include/topi/detail/ravel_unravel.h b/topi/include/topi/detail/ravel_unravel.h
new file mode 100644
index 000000000000..b9774153efc7
--- /dev/null
+++ b/topi/include/topi/detail/ravel_unravel.h
@@ -0,0 +1,60 @@
+/*!
+*  Copyright (c) 2017 by Contributors
+* \file ravel_unravel.h
+* \brief Index ravel and unraval operations
+*/
+#ifndef TOPI_DETAIL_RAVEL_UNRAVEL_H_
+#define TOPI_DETAIL_RAVEL_UNRAVEL_H_
+
+#include <vector>
+
+#include "tvm/tvm.h"
+
+namespace topi {
+namespace detail {
+using namespace tvm;
+
+/*!
+* \brief Flatten the indices to 1D
+*
+* \param indices The input coordinates
+* \param shape Shape of the tensor
+*
+* \return The index after flattening
+*/
+inline Expr RavelIndex(Array<Var> indices, Array<Expr> shape) {
+  CHECK_EQ(indices.size(), shape.size()) << "indices and shape must have equal size";
+  CHECK_GT(indices.size(), 0) << "indices must not be empty";
+  Expr idx;
+  for (size_t i = 0; i < indices.size(); ++i) {
+    if (i == 0) {
+      idx = indices[i];
+    } else {
+      idx = idx * shape[i] + indices[i];
+    }
+  }
+  return idx;
+}
+
+/*!
+* \brief Convert flattened index to coordinate array
+*
+* \param idx The 1D index
+* \param shape Shape of the tensor
+*
+* \return The coordinate corresponding to the 1D index
+*/
+inline Array<Expr> UnavelIndex(Expr idx, Array<Expr> shape) {
+  std::vector<Expr> indices;
+
+  for (int i = static_cast<int>(shape.size()) - 1; i >= 0; --i) {
+    indices.push_back(idx % shape[i]);
+    idx = idx / shape[i];
+  }
+  std::reverse(indices.begin(), indices.end());
+  return indices;
+}
+
+}  // namespace detail
+}  // namespace topi
+#endif  // TOPI_DETAIL_RAVEL_UNRAVEL_H_
diff --git a/topi/include/topi/elemwise.h b/topi/include/topi/elemwise.h
index e133833a790b..c3797197710b 100644
--- a/topi/include/topi/elemwise.h
+++ b/topi/include/topi/elemwise.h
@@ -28,6 +28,144 @@ TOPI_DECLARE_UNARY_OP(exp);
 TOPI_DECLARE_UNARY_OP(tanh);
 TOPI_DECLARE_UNARY_OP(sigmoid);
 TOPI_DECLARE_UNARY_OP(sqrt);
+TOPI_DECLARE_UNARY_OP(log);
+
+/*!
+* \brief Creates an operation that returns identity of a given tensor
+*
+* \param x The input tensor
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the identity operation
+*/
+inline Tensor identity(const Tensor& x,
+                       std::string name = "tensor",
+                       std::string tag = kElementWise) {
+  return compute(x->shape, [&](const Array<Var>& i) {
+    return x(i);
+  }, name, tag);
+}
+
+/*!
+* \brief Creates an operation that returns the negation of a given tensor
+*
+* \param x The input tensor
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the negation operation
+*/
+inline Tensor negative(const Tensor& x,
+                       std::string name = "tensor",
+                       std::string tag = kElementWise) {
+  return compute(x->shape, [&](const Array<Var>& i) {
+    return -x(i);
+  }, name, tag);
+}
+
+/*!
+* \brief Creates an operation that raises each element of tensor x to power y
+*
+* \param x The input tensor
+* \param y The exponent
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the pow operation
+*/
+inline Tensor pow(const Tensor& x,
+                  const Expr& y,
+                  std::string name = "tensor",
+                  std::string tag = kElementWise) {
+  return compute(x->shape, [&](const Array<Var>& i) {
+    return tvm::pow(x(i), y);
+  }, name, tag);
+}
+
+/*!
+* \brief Creates an operation that performs pointwise left shift by n bits
+*
+* \param x The input tensor
+* \param n The number of bits to shift by
+*
+* \return A Tensor whose op member is the left shift operation
+*/
+inline Tensor operator<<(const Tensor& x,
+                         const Expr& n) {
+  return compute(x->shape, [&](const Array<Var>& i) {
+    return x(i) << n;
+  }, "tensor", kElementWise);
+}
+
+/*!
+* \brief Creates an operation that performs pointwise right shift by n bits
+*
+* \param x The input tensor
+* \param n The number of bits to shift by
+*
+* \return A Tensor whose op member is the right shift operation
+*/
+inline Tensor operator>>(const Tensor& x,
+                         const Expr& n) {
+  return compute(x->shape, [&](const Array<Var>& i) {
+    return x(i) >> n;
+  }, "tensor", kElementWise);
+}
+
+/*!
+* \brief Creates an operation that clips each element of a tensor to
+* the interval [a_min, a_max]
+*
+* \param x The input tensor
+* \param a_min The inclusive lower bound of the interval
+* \param a_max The inclusive upper bound of the interval
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the clip operation
+*/
+inline Tensor clip(const Tensor& x,
+                   const Expr& a_min,
+                   const Expr& a_max,
+                   std::string name = "tensor",
+                   std::string tag = kElementWise) {
+  return compute(x->shape, [&](const Array<Var>& i) {
+    auto min_val = tvm::cast(x->dtype, a_min);
+    auto max_val = tvm::cast(x->dtype, a_max);
+    return tvm::max(tvm::min(x(i), max_val), min_val);  // NOLINT(*)
+  }, name, tag);
+}
+
+/*!
+ * \brief Cast each element of x to the given type. If expr is
+ * scalar and type is a corresponding vector type, a
+ * Broadcast is generated, otherwise a Cast is generated.
+ *
+ * \param x The input tensor
+ * \param type The type to cast to
+ * \param name The name of the operation
+ * \param tag The tag to mark the operation
+ *
+ * \return A Tensor whose op member is the cast operation
+ */
+inline Tensor cast(const Tensor& x,
+                   Type type,
+                   std::string name = "tensor",
+                   std::string tag = kElementWise) {
+  return compute(x->shape, [&](const Array<Var>& i) {
+    auto expr = x(i);
+    if (expr.type().code() == type.code() && expr.type().bits() == type.bits()) {
+      if (expr.type().lanes() == type.lanes()) {
+        return expr;
+      } else if (expr.type().lanes() == 1 && type.lanes() > 1) {
+        return tvm::ir::Broadcast::make(expr, type.lanes());
+      }
+    }
+
+    return tvm::cast(type, x(i));
+  }, name, tag);
+}
 
 }  // namespace topi
 #endif  // TOPI_ELEMWISE_H_
diff --git a/topi/include/topi/generic/default.h b/topi/include/topi/generic/default.h
new file mode 100644
index 000000000000..5dd93030f95b
--- /dev/null
+++ b/topi/include/topi/generic/default.h
@@ -0,0 +1,46 @@
+/*!
+*  Copyright (c) 2017 by Contributors
+* \file generic/default.h
+* \brief Generic default schedule
+*/
+#ifndef TOPI_GENERIC_DEFAULT_H_
+#define TOPI_GENERIC_DEFAULT_H_
+
+#include "topi/tags.h"
+#include "topi/detail/fuse.h"
+#include "tvm/tvm.h"
+#include "tvm/build_module.h"
+
+namespace topi {
+using namespace tvm;
+
+namespace generic {
+/*!
+ * \brief Create a generic default schedule for the given output tensors.
+ *
+ * \param target The target to generate a schedule for.
+ * \param outs The output tensors.
+ * \param auto_inline Whether to apply the auto inline step.
+ *
+ * \return A schedule for the given ops.
+ */
+Schedule default_schedule(const Target& target, Array<Tensor> outs, bool auto_inline) {
+  Array<Operation> out_ops;
+  for (auto t : outs) {
+    out_ops.push_back(t->op);
+  }
+  auto s = create_schedule(out_ops);
+  if (auto_inline) {
+    auto x = outs[0];
+    tvm::schedule::AutoInlineInjective(s);
+    auto axis = s[x]->op.as<ComputeOpNode>()->axis;
+    if (axis.size() > 0) {
+      Fuse(s[x], axis);
+    }
+  }
+  return s;
+}
+
+}  // namespace generic
+}  // namespace topi
+#endif  // TOPI_GENERIC_DEFAULT_H_
diff --git a/topi/include/topi/generic/extern.h b/topi/include/topi/generic/extern.h
new file mode 100644
index 000000000000..21852d108239
--- /dev/null
+++ b/topi/include/topi/generic/extern.h
@@ -0,0 +1,37 @@
+/*!
+*  Copyright (c) 2017 by Contributors
+* \file generic/extern.h
+* \brief Schedule for extern followed by injective ops
+*/
+#ifndef TOPI_GENERIC_EXTERN_H_
+#define TOPI_GENERIC_EXTERN_H_
+
+#include "topi/tags.h"
+#include "topi/detail/fuse.h"
+#include "tvm/tvm.h"
+#include "tvm/build_module.h"
+
+namespace topi {
+using namespace tvm;
+
+namespace generic {
+/*!
+* \brief Schedule an extern op followed by injective operations
+*
+* \param target The target to generate a schedule for.
+* \param outs The output tensors.
+*
+* \return A schedule for the op.
+*/
+Schedule schedule_extern(const Target& target, Array<Tensor> outs) {
+  Array<Operation> out_ops;
+  for (auto t : outs) {
+    out_ops.push_back(t->op);
+  }
+  auto s = create_schedule(out_ops);
+  return s;
+}
+
+}  // namespace generic
+}  // namespace topi
+#endif  // TOPI_GENERIC_EXTERN_H_
diff --git a/topi/include/topi/generic/injective.h b/topi/include/topi/generic/injective.h
new file mode 100644
index 000000000000..f05f6243485d
--- /dev/null
+++ b/topi/include/topi/generic/injective.h
@@ -0,0 +1,42 @@
+/*!
+*  Copyright (c) 2017 by Contributors
+* \file generic/injective.h
+* \brief Generic schedule for injective operations
+*/
+#ifndef TOPI_GENERIC_INJECTIVE_H_
+#define TOPI_GENERIC_INJECTIVE_H_
+
+#include "topi/tags.h"
+#include "topi/detail/fuse.h"
+#include "tvm/tvm.h"
+#include "tvm/build_module.h"
+
+namespace topi {
+using namespace tvm;
+
+namespace generic {
+
+/*!
+ * \brief Create a generic schedule for the given injective ops.
+ *
+ * \param target The target to generate a schedule for.
+ * \param outs The output tensors.
+ *
+ * \return A schedule for the given ops.
+ */
+Schedule schedule_injective(const Target &target, const Array<Tensor>& outs) {
+  Array<Operation> out_ops;
+  for (auto t : outs) {
+    out_ops.push_back(t->op);
+  }
+  auto s = create_schedule(out_ops);
+  tvm::schedule::AutoInlineInjective(s);
+  auto x = outs[0];
+  Fuse(s[x], s[x]->op.as<ComputeOpNode>()->axis);
+
+  return s;
+}
+
+}  // namespace generic
+}  // namespace topi
+#endif  // TOPI_GENERIC_INJECTIVE_H_
diff --git a/topi/include/topi/nn.h b/topi/include/topi/nn.h
index cfca85d1b704..5bb4ebf2ac8f 100644
--- a/topi/include/topi/nn.h
+++ b/topi/include/topi/nn.h
@@ -21,7 +21,7 @@ template <typename T>
 tvm::Expr Map(const tvm::Array<tvm::Expr>& exprs, T op) {
   CHECK_GE(exprs.size(), 1);
   tvm::Expr res = exprs[0];
-  for (int i = 1; i < exprs.size(); ++i) {
+  for (size_t i = 1; i < exprs.size(); ++i) {
     res = op(res, exprs[i]);
   }
   return res;
@@ -51,6 +51,34 @@ inline tvm::Tensor relu(const tvm::Tensor& t,
       tag);
 }
 
+/*!
+* \brief Creates an operation that performs a leaky rectified linear unit
+*
+* \param t The input tensor
+* \param threshold The relu threshold (default 0)
+* \param alpha The slope for the small gradient when t < threshold
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the relu operation
+*/
+template <typename T>
+inline tvm::Tensor leaky_relu(const tvm::Tensor& t,
+                              T threshold = static_cast<T>(0),
+                              T alpha = static_cast<T>(0.1),
+                              std::string name = "tensor",
+                              std::string tag = kElementWise) {
+  return tvm::compute(
+    t->shape,
+    [&](const tvm::Array<tvm::Var>& i) {
+      auto value = t(i);
+      auto calpha = tvm::make_const(value.type(), alpha);
+      return tvm::select(value > 0, value, value * alpha);
+    },
+    name,
+    tag);
+}
+
 /*!
  * \brief Creates an operation that performs padding
  *
@@ -59,10 +87,11 @@ inline tvm::Tensor relu(const tvm::Tensor& t,
  * respective iterator
  * \param pad_after An Array of Expr describing the padding after the
  * respective iterator
+ * \param pad_value The value to fill padding elements with
  * \param name The name of the operation
  * \param tag The tag to mark the operation
  *
- * \return A Tensor whose op member is the relu operation
+ * \return A Tensor whose op member is the padding operation
  *
  * \note
  *  The pad_after Array must either be empty or have the same length as
@@ -86,17 +115,18 @@ inline tvm::Tensor relu(const tvm::Tensor& t,
 inline tvm::Tensor pad(const tvm::Tensor& t,
                        const tvm::Array<tvm::Expr>& pad_before,
                        tvm::Array<tvm::Expr> pad_after = tvm::Array<tvm::Expr>(),
+                       Expr pad_value = Expr(),
                        std::string name = "tensor",
                        std::string tag = kElementWise) {
   if (pad_after.size() < pad_before.size()) {
-    for (int i = pad_after.size(); i < pad_before.size(); ++i) {
+    for (size_t i = pad_after.size(); i < pad_before.size(); ++i) {
       pad_after.push_back(pad_before[i]);
     }
   }
   CHECK_GE(pad_before.size(), 1);
   CHECK_EQ(pad_before.size(), pad_after.size());
   tvm::Array<tvm::Expr> output_shape;
-  for (int i = 0; i < t->shape.size(); ++i) {
+  for (size_t i = 0; i < t->shape.size(); ++i) {
     if (i >= pad_before.size()) {
       output_shape.push_back(t->shape[i]);
     } else {
@@ -104,10 +134,15 @@ inline tvm::Tensor pad(const tvm::Tensor& t,
           tvm::ir::Simplify(t->shape[i] + pad_before[i] + pad_after[i]));
     }
   }
+
+  if (!pad_value.defined()) {
+    pad_value = tvm::make_const(t->dtype, 0);
+  }
+
   auto l = [&](tvm::Array<tvm::Var> ovars) {
     tvm::Array<tvm::Expr> indices;
     tvm::Array<tvm::Expr> sel;
-    for (int i = 0; i < t->shape.size(); ++i) {
+    for (size_t i = 0; i < t->shape.size(); ++i) {
       if (i >= pad_before.size()) {
         indices.push_back(ovars[i]);
         continue;
@@ -122,7 +157,10 @@ inline tvm::Tensor pad(const tvm::Tensor& t,
         sel.push_back(tvm::ir::Simplify(ovars[i] < pad_before[i] + t->shape[i]));
       }
     }
-    return tvm::select(detail::Map(sel, tvm::ir::And::make), t(indices), 0);
+    if (sel.size() != 0) {
+      return tvm::select(detail::Map(sel, tvm::ir::And::make), t(indices), pad_value);
+    }
+    return t(indices);
   };
   return tvm::compute(output_shape, l, name, tag);
 }
diff --git a/topi/include/topi/nn/batch_norm.h b/topi/include/topi/nn/batch_norm.h
new file mode 100644
index 000000000000..be3e31d216d0
--- /dev/null
+++ b/topi/include/topi/nn/batch_norm.h
@@ -0,0 +1,65 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \brief Batch normalization op constructions
+ * \file nn/batch_norm.h
+ */
+#ifndef TOPI_NN_BATCH_NORM_H_
+#define TOPI_NN_BATCH_NORM_H_
+
+#include <string>
+
+#include "topi/tags.h"
+#include "tvm/tvm.h"
+
+namespace topi {
+namespace nn {
+using namespace tvm;
+
+/*!
+* \brief Batch normalization inference operator with NCHW layout
+*
+* \param x The input tensor. 4-D with shape [batch, channel, height, width]
+* \param gamma 1-D with shape [channel]
+* \param beta 1-D with shape [channel]
+* \param moving_mean 1-D with shape [channel]
+* \param moving_var 1-D with shape [channel]
+* \param eps Epsilon to prevent div by 0
+* \param fix_gamma Fix gamma while training
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the batch normalization operation
+*/
+inline Tensor batch_norm_inference(const Tensor& x,
+                                   const Tensor& gamma,
+                                   const Tensor& beta,
+                                   const Tensor& moving_mean,
+                                   const Tensor& moving_var,
+                                   float eps,
+                                   bool fix_gamma,
+                                   std::string name = "tensor",
+                                   std::string tag = kBroadcast) {
+  CHECK_EQ(x->shape.size(), 4) << "Batch norm requires 4-D input";
+
+  Tensor out;
+  if (fix_gamma) {
+    out = tvm::compute(
+      x->shape,
+      [&](const Array<Var>& indices) {
+        auto c = Array<Var>({ indices[1] });
+        return (x(indices) - moving_mean(c)) / tvm::sqrt(moving_var(c) + eps) + beta(c);
+      }, name, tag);
+  } else {
+    out = tvm::compute(
+      x->shape,
+      [&](const Array<Var>& indices) {
+        auto c = Array<Var>({ indices[1] });
+        return (x(indices) - moving_mean(c)) / tvm::sqrt(moving_var(c) + eps) * gamma(c) + beta(c);
+      }, name, tag);
+  }
+  return out;
+}
+
+}  // namespace nn
+}  // namespace topi
+#endif  // TOPI_NN_BATCH_NORM_H_
diff --git a/topi/include/topi/nn/bnn.h b/topi/include/topi/nn/bnn.h
new file mode 100644
index 000000000000..f7b1b860d461
--- /dev/null
+++ b/topi/include/topi/nn/bnn.h
@@ -0,0 +1,110 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \brief Binary op constructions
+ * \file nn/bnn.h
+ */
+#ifndef TOPI_NN_BNN_H_
+#define TOPI_NN_BNN_H_
+
+#include <string>
+
+#include "tvm/tvm.h"
+#include "tvm/ir_pass.h"
+#include "topi/tags.h"
+#include "topi/detail/constant_utils.h"
+
+namespace topi {
+namespace nn {
+using namespace tvm;
+
+/*!
+* \brief Binarization and bit-packing along a certain axis.
+*
+* \param data N-D tensor, can be any layout
+* \param axis The axis along which to do binarization and bit-packing. This axis
+* must have a size equal to an integer multiple of 32.
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return Output tensor with dtype uint32
+*/
+inline tvm::Tensor binarize_pack(const tvm::Tensor& data,
+                                 int axis,
+                                 std::string name = "PackedInput",
+                                 std::string tag = "binarize_pack") {
+  auto ishape = data->shape;
+  CHECK_EQ(GetConstInt(ishape[axis]) % 32, 0)
+    << "binarize_pack: axis size must be a multiple of 32";
+
+  auto n = ishape.size();
+  Array<Expr> oshape;
+  for (size_t i = 0; i < n; ++i) {
+    oshape.push_back(i == static_cast<size_t>(axis) ?
+                     tvm::ir::Simplify(ishape[i] / 32) :
+                     ishape[i]);
+  }
+
+  return tvm::compute(
+    oshape,
+    [&](const Array<Var>& indices) {
+      Array<Expr> start_idx;
+      for (size_t i = 0; i < n; ++i) {
+        start_idx.push_back(i == static_cast<size_t>(axis) ?
+                            indices[i] * 32 :
+                            static_cast<Expr>(indices[i]));
+      }
+      auto packed = make_const(UInt(32), 0);
+      for (size_t j = 0; j < 32; ++j) {
+        Array<Expr> idx;
+        for (size_t i = 0; i < n; ++i) {
+          idx.push_back(i == static_cast<size_t>(axis) ?
+                        start_idx[i] + static_cast<int>(j) :
+                        start_idx[i]);
+        }
+        auto sign = tvm::cast(UInt(32), data(idx) >= 0);
+        packed = (packed | sign);
+        if (j == 31) {
+          return packed;
+        }
+        packed = packed << 1;
+      }
+      return packed;  // never reached, but suppress compiler warning
+    }, name, tag);
+}
+
+/*!
+* \brief Binary matrix multiplication using xor and bit-count
+*
+* \param data Tensor with shape [batch, in_dim], dtype is uint32
+* \param weight Tensor with shape [out_dim, in_dim], dtype is uint32
+*
+* \return Tensor with shape [batch, out_dim], dtype is float32
+*/
+inline tvm::Tensor binary_dense(const tvm::Tensor& data,
+                                const tvm::Tensor& weight) {
+  CHECK_EQ(data->shape.size(), 2) << "binary_dense requires 2-D data";
+  CHECK_EQ(weight->shape.size(), 2) << "binary_dense requires 2-D weight";
+  CHECK_EQ(data->dtype, UInt(32)) << "binary_dense requires uint32 data";
+  CHECK_EQ(weight->dtype, UInt(32)) << "binary_dense requires uint32 weight";
+
+  auto batch = data->shape[0];
+  auto in_dim = data->shape[1];
+  auto out_dim = weight->shape[0];
+
+  auto k = tvm::reduce_axis(Range(0, in_dim), "k");
+  auto matmul = tvm::compute(
+    { batch, out_dim },
+    [&](Var i, Var j) {
+      return tvm::sum(popcount(data(i, k) ^ weight(j, k)), { k });
+    }, "tensor", "binary_dense");
+
+  return tvm::compute(
+    { batch, out_dim },
+    [&](Var i, Var j) {
+      return 32 * in_dim - 2.0f * matmul(i, j);
+    }, "tensor", kElementWise);
+}
+
+}  // namespace nn
+}  // namespace topi
+#endif  // TOPI_NN_BNN_H_
diff --git a/topi/include/topi/nn/dense.h b/topi/include/topi/nn/dense.h
new file mode 100644
index 000000000000..cdc7fde158a6
--- /dev/null
+++ b/topi/include/topi/nn/dense.h
@@ -0,0 +1,61 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \brief Dense op constructions
+ * \file nn/dense.h
+ */
+#ifndef TOPI_NN_DENSE_H_
+#define TOPI_NN_DENSE_H_
+
+#include <string>
+
+#include "topi/tags.h"
+#include "tvm/tvm.h"
+
+namespace topi {
+namespace nn {
+using namespace tvm;
+
+/*!
+* \brief Creates an operation that calculates data * weight^T + bias
+*
+* \param data Tensor with shape [batch, in_dim]
+* \param weight Tensor with shape [out_dim, in_dim]
+* \param bias Tensor with shape [out_dim] (optional)
+*
+* \return Tensor with shape [batch, out_dim]
+*/
+inline tvm::Tensor dense(const tvm::Tensor& data,
+                         const tvm::Tensor& weight,
+                         tvm::Tensor* bias) {
+  CHECK_EQ(data->shape.size(), 2) << "dense requires 2-D data";
+  CHECK_EQ(weight->shape.size(), 2) << "dense requires 2-D weight";
+  if (bias != nullptr) {
+    CHECK_EQ((*bias)->shape.size(), 1) << "dense requires 1-D bias";
+  }
+
+  auto batch = data->shape[0];
+  auto in_dim = data->shape[1];
+  auto out_dim = weight->shape[0];
+
+  auto k = tvm::reduce_axis(Range(0, in_dim), "k");
+  auto matmul = tvm::compute(
+    { batch, out_dim },
+    [&](Var i, Var j) {
+      return tvm::sum(data(i, k) * weight(j, k), { k });
+    }, "tensor", "dense");
+
+  if (bias != nullptr) {
+    auto bias_val = *bias;
+    matmul = tvm::compute(
+      { batch, out_dim },
+      [&](Var i, Var j) {
+        return matmul(i, j) + bias_val(j);
+      }, "tensor", kBroadcast);
+  }
+
+  return matmul;
+}
+
+}  // namespace nn
+}  // namespace topi
+#endif  // TOPI_NN_DENSE_H_
diff --git a/topi/include/topi/nn/dilate.h b/topi/include/topi/nn/dilate.h
new file mode 100644
index 000000000000..f4638f4b6b8a
--- /dev/null
+++ b/topi/include/topi/nn/dilate.h
@@ -0,0 +1,87 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \brief Dilate op constructions
+ * \file nn/dilate.h
+ */
+#ifndef TOPI_NN_DILATE_H_
+#define TOPI_NN_DILATE_H_
+
+#include <string>
+
+#include "tvm/tvm.h"
+#include "tvm/ir_pass.h"
+#include "topi/tags.h"
+
+namespace topi {
+namespace nn {
+using namespace tvm;
+
+/*!
+* \brief Create a new expression of the logical and of all
+* conditions in the arguments.
+*
+* \param args The arguments to find the logical conjunction of
+*
+* \return The logical conjunction expression
+*/
+Expr all(Array<Expr> args) {
+  CHECK_GT(args.size(), 0) << "all requires at least one argument";
+
+  Expr ret = args[0];
+  for (size_t i = 1; i < args.size(); ++i) {
+    ret = ret && args[i];
+  }
+  return ret;
+}
+
+/*!
+* \brief Dilate data with zeros
+*
+* \param x The input tensor, this can have any number of
+* dimensions and any layout.
+* \param strides Dilation stride for each dimension. Stride 1
+* means no dilation.
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return The output tensor.
+*/
+inline Tensor dilate(const Tensor& x,
+                     Array<Expr> strides,
+                     std::string name = "tensor",
+                     std::string tag = kInjective) {
+  auto n = x->shape.size();
+  CHECK_EQ(n, strides.size())
+    << "strides size (" << strides.size()
+    << ") must match dimension of x (" << n << ")";
+
+  Array<Expr> out_shape;
+  for (size_t i = 0; i < n; ++i) {
+    out_shape.push_back(tvm::ir::Simplify(
+      (x->shape[i] - 1) * strides[i] + 1));
+  }
+
+  return tvm::compute(
+    out_shape,
+    [&](const Array<Var>& indices) {
+      Array<Expr> not_zero;
+      Array<Expr> index_tuple;
+      for (size_t i = 0; i < n; ++i) {
+        if (IsConstInt(strides[i]) && GetConstInt(strides[i]) == 1) {
+          index_tuple.push_back(indices[i]);
+        } else {
+          index_tuple.push_back(indices[i] / strides[i]);
+          not_zero.push_back((indices[i] % strides[i]) == 0);
+        }
+      }
+      if (not_zero.size() > 0) {
+        auto all_not_zero = all(not_zero);
+        return tvm::select(all_not_zero, x(index_tuple), make_const(x->dtype, 0));
+      }
+      return x(index_tuple);
+    }, name, tag);
+}
+
+}  // namespace nn
+}  // namespace topi
+#endif  // TOPI_NN_DILATE_H_
diff --git a/topi/include/topi/nn/flatten.h b/topi/include/topi/nn/flatten.h
new file mode 100644
index 000000000000..a74b390eeb0c
--- /dev/null
+++ b/topi/include/topi/nn/flatten.h
@@ -0,0 +1,63 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \brief Softmax op constructions
+ * \file nn/flatten.h
+ */
+#ifndef TOPI_NN_FLATTEN_H_
+#define TOPI_NN_FLATTEN_H_
+
+#include <string>
+#include <vector>
+
+#include "topi/tags.h"
+#include "topi/detail/constant_utils.h"
+#include "tvm/tvm.h"
+
+namespace topi {
+namespace nn {
+using namespace tvm;
+
+/*!
+* \brief Flattens the input tensor into a 2-D tensor by collapsing higher dimensions.
+* This requires the input tensor to have constant sized dimensions.
+*
+* \param x The input tensor.
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A 2-D tensor.
+*/
+inline Tensor flatten(const Tensor& x,
+                      std::string name = "tensor",
+                      std::string tag = kInjective) {
+  auto ishape = x->shape;
+  int dim = 1;
+  for (size_t i = 1; i < ishape.size(); ++i) {
+    dim = dim * static_cast<int>(GetConstInt(ishape[i]));
+  }
+
+  Array<Expr> oshape({ ishape[0], dim });
+
+  std::vector<Expr> extra_shape;
+  for (size_t i = 1; i < ishape.size(); ++i) {
+    extra_shape.push_back(ishape[i]);
+  }
+  std::reverse(extra_shape.begin(), extra_shape.end());
+
+  return tvm::compute(
+    oshape, [&](Var i, Var j) {
+      Expr idx = j;
+      std::vector<Expr> index;
+      for (auto s : extra_shape) {
+        index.push_back(idx % s);
+        idx = idx / s;
+      }
+      index.push_back(i);
+      std::reverse(index.begin(), index.end());
+      return x(index);
+    });
+}
+
+}  // namespace nn
+}  // namespace topi
+#endif  // TOPI_NN_FLATTEN_H_
diff --git a/topi/include/topi/nn/mapping.h b/topi/include/topi/nn/mapping.h
new file mode 100644
index 000000000000..60cd6d6310a7
--- /dev/null
+++ b/topi/include/topi/nn/mapping.h
@@ -0,0 +1,66 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \brief Mapping op constructions
+ * \file nn/mapping.h
+ */
+#ifndef TOPI_NN_MAPPING_H_
+#define TOPI_NN_MAPPING_H_
+
+#include <string>
+
+#include "topi/tags.h"
+#include "tvm/tvm.h"
+
+namespace topi {
+namespace nn {
+using namespace tvm;
+
+/*!
+* \brief Scale and shift with NCHW order
+*
+* \param x The input tensor.
+* \param scale Scale tensor, 1-D of size channel
+* \param shift Shift tensor, 1-D of size channel
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the scale shift operation
+*/
+inline Tensor scale_shift_nchw(const Tensor& x,
+                               const Tensor& scale,
+                               const Tensor& shift,
+                               std::string name = "ScaleShift",
+                               std::string tag = kBroadcast) {
+  return tvm::compute(
+    x->shape,
+    [&](Var b, Var c, Var h, Var w) {
+      return x(b, c, h, w) * scale(c) + shift(w);
+    }, name, tag);
+}
+
+/*!
+* \brief Scale and shift with NHWC order
+*
+* \param x The input tensor.
+* \param scale Scale tensor, 1-D of size channel
+* \param shift Shift tensor, 1-D of size channel
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the scale shift operation
+*/
+inline Tensor scale_shift_nhwc(const Tensor& x,
+                               const Tensor& scale,
+                               const Tensor& shift,
+                               std::string name = "ScaleShift",
+                               std::string tag = kBroadcast) {
+  return tvm::compute(
+    x->shape,
+    [&](Var b, Var h, Var w, Var c) {
+      return x(b, h, w, c) * scale(c) + shift(w);
+    }, name, tag);
+}
+
+}  // namespace nn
+}  // namespace topi
+#endif  // TOPI_NN_MAPPING_H_
diff --git a/topi/include/topi/nn/pooling.h b/topi/include/topi/nn/pooling.h
new file mode 100644
index 000000000000..4333f2749573
--- /dev/null
+++ b/topi/include/topi/nn/pooling.h
@@ -0,0 +1,161 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \brief Pooling op constructions
+ * \file nn/pooling.h
+ */
+#ifndef TOPI_NN_POOLING_H_
+#define TOPI_NN_POOLING_H_
+
+#include <string>
+
+#include "tvm/tvm.h"
+#include "tvm/ir_pass.h"
+#include "topi/tags.h"
+#include "topi/detail/pad_utils.h"
+#include "topi/nn.h"
+
+namespace topi {
+namespace nn {
+using namespace tvm;
+
+/*! \brief Pooling type */
+enum PoolType : int {
+  kAvgPool,
+  kMaxPool,
+};
+
+/*!
+* \brief Perform pooling on data in NCHW order
+*
+* \param x The input tensor in NCHW order
+* \param kernel_size Vector of two ints: {kernel_height, kernel_width}
+* \param stride_size Vector of two ints: {stride_height, stride_width}
+* \param padding_size Vector of two ints: {padding_height, padding_width}
+* \param pool_type The type of pooling operator
+* \param ceil_mode Whether to use ceil when calculating the output size
+*
+* \return The output tensor in NCHW order
+*/
+inline Tensor pool(const Tensor& x,
+                   const Array<Expr>& kernel_size,
+                   const Array<Expr>& stride_size,
+                   const Array<Expr>& padding_size,
+                   PoolType pool_type,
+                   bool ceil_mode) {
+  CHECK_EQ(x->shape.size(), 4) << "Pooling input must be 4-D";
+  CHECK_EQ(kernel_size.size(), 2) << "Pooling kernel_size must have 2 elements";
+  CHECK_EQ(stride_size.size(), 2) << "Pooling stride_size must have 2 elements";
+  CHECK_EQ(padding_size.size(), 2) << "Pooling padding_size must have 2 elements";
+
+  auto kernel_height = kernel_size[0];
+  auto kernel_width = kernel_size[1];
+  auto stride_height = stride_size[0];
+  auto stride_width = stride_size[1];
+  auto padding_height = padding_size[0];
+  auto padding_width = padding_size[1];
+
+  auto batch = x->shape[0];
+  auto channel = x->shape[1];
+  auto height = x->shape[2];
+  auto width = x->shape[3];
+
+  auto pad_tuple = detail::GetPadTuple(padding_height, padding_width);
+  auto pad_top = pad_tuple[0];
+  auto pad_left = pad_tuple[1];
+  auto pad_down = pad_tuple[2];
+  auto pad_right = pad_tuple[3];
+
+  if (ceil_mode) {
+    // Additional padding to ensure we do ceil instead of floor when
+    // dividing by stride.
+    pad_down += stride_height - 1;
+    pad_right += stride_width - 1;
+  }
+
+  Array<Expr> pad_before{ 0, 0, pad_top, pad_left };
+  Array<Expr> pad_after{ 0, 0, pad_down, pad_right };
+
+  auto out_height = tvm::ir::Simplify(
+    (height - kernel_height + pad_top + pad_down) / stride_height + 1);
+  auto out_width = tvm::ir::Simplify(
+    (width - kernel_width + pad_left + pad_right) / stride_width + 1);
+
+  auto dheight = tvm::reduce_axis(Range(0, kernel_height));
+  auto dwidth = tvm::reduce_axis(Range(0, kernel_width));
+
+  if (pool_type == kMaxPool) {
+    auto temp = pad(x, pad_before, pad_after, x->dtype.min(), "pad_temp");
+    return tvm::compute(
+      { batch, channel, out_height, out_width },
+      [&](Var n, Var c, Var h, Var w) {
+        return tvm::max(temp(n, c, h * stride_height + dheight, w * stride_width + dwidth),
+        { dheight, dwidth });
+      }, "tensor", "pool_max");
+  } else if (pool_type == kAvgPool) {
+    auto temp = pad(x, pad_before, pad_after, 0, "pad_temp");
+
+    auto tsum = tvm::compute(
+      { batch, channel, out_height, out_width },
+      [&](Var n, Var c, Var h, Var w) {
+        return tvm::sum(temp(n, c, h * stride_height + dheight, w * stride_width + dwidth),
+        { dheight, dwidth });
+      }, "tensor", "pool_avg");
+
+    return tvm::compute(
+      { batch, channel, out_height, out_width },
+      [&](Var n, Var c, Var h, Var w) {
+        return tsum(n, c, h, w) / (kernel_height * kernel_width);
+      }, "tensor", kElementWise);
+  } else {
+    LOG(ERROR) << "Unrecognized pool_type: " << pool_type;
+    return x;
+  }
+}
+
+/*!
+* \brief Perform global pooling on data in NCHW order
+*
+* \param x The input tensor in NCHW order
+* \param pool_type The type of pooling operator
+*
+* \return The output tensor with shape [batch, channel, 1, 1]
+*/
+inline Tensor global_pool(const Tensor& x,
+                          PoolType pool_type) {
+  CHECK_EQ(x->shape.size(), 4) << "Pooling input must be 4-D";
+
+  auto batch = x->shape[0];
+  auto channel = x->shape[1];
+  auto height = x->shape[2];
+  auto width = x->shape[3];
+
+  auto dheight = tvm::reduce_axis(Range(0, height));
+  auto dwidth = tvm::reduce_axis(Range(0, width));
+
+  if (pool_type == kMaxPool) {
+    return tvm::compute(
+      { batch, channel, 1, 1 },
+      [&](Var n, Var c, Var h, Var w) {
+        return tvm::max(x(n, c, dheight, dwidth), { dheight, dwidth });  // NOLINT(*)
+      }, "tensor", "global_pool_max");
+  } else if (pool_type == kAvgPool) {
+    auto tsum = tvm::compute(
+      { batch, channel, 1, 1 },
+      [&](Var n, Var c, Var h, Var w) {
+        return tvm::sum(x(n, c, dheight, dwidth), { dheight, dwidth });
+      }, "tensor", "global_pool_sum");
+
+    return tvm::compute(
+      { batch, channel, 1, 1 },
+      [&](Var n, Var c, Var h, Var w) {
+        return tsum(n, c, h, w) / tvm::cast(x->dtype, height * width);
+      }, "tensor", kElementWise);
+  } else {
+    LOG(ERROR) << "Unrecognized pool_type: " << pool_type;
+    return x;
+  }
+}
+
+}  // namespace nn
+}  // namespace topi
+#endif  // TOPI_NN_POOLING_H_
diff --git a/topi/include/topi/nn/softmax.h b/topi/include/topi/nn/softmax.h
new file mode 100644
index 000000000000..273bac4ff76d
--- /dev/null
+++ b/topi/include/topi/nn/softmax.h
@@ -0,0 +1,87 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \brief Softmax op constructions
+ * \file nn/softmax.h
+ */
+#ifndef TOPI_NN_SOFTMAX_H_
+#define TOPI_NN_SOFTMAX_H_
+
+#include <algorithm>
+#include <string>
+
+#include "topi/tags.h"
+#include "tvm/tvm.h"
+
+namespace topi {
+namespace nn {
+using namespace tvm;
+
+/*!
+* \brief Softmax activation
+*
+* \param x The input tensor. 2-D where softmax is performed along the second dimension
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the softmax operation
+*/
+inline Tensor softmax(const Tensor& x,
+                      std::string name = "tensor",
+                      std::string tag = "softmax_output") {
+  CHECK_EQ(x->shape.size(), 2) << "Softmax requires 2-D input";
+
+  Expr m = x->shape[0];
+  Expr n = x->shape[1];
+
+  auto k = tvm::reduce_axis(Range(0, n), "k");
+  auto max_elem = tvm::compute(
+    { m }, [&](Var i) {
+      return tvm::max(x(i, k), Array<IterVar>{ k }); });
+  k = tvm::reduce_axis(Range(0, n), "k");
+
+  auto expsum = tvm::compute(
+    { m }, [&](Var i) {
+      return tvm::sum(tvm::exp(x(i, k) - max_elem(i)), { k }); });
+
+  return tvm::compute(
+    x->shape, [&](Var i, Var j) {
+      return tvm::exp(x(i, j) - max_elem(i)) / expsum(i);
+    });
+}
+
+/*!
+* \brief Log softmax activation
+*
+* \param x The input tensor. 2-D where log softmax is performed along the second dimension
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the log softmax operation
+*/
+inline Tensor log_softmax(const Tensor& x,
+                          std::string name = "tensor",
+                          std::string tag = "log_softmax_output") {
+  CHECK_EQ(x->shape.size(), 2) << "Log softmax requires 2-D input";
+
+  Expr m = x->shape[0];
+  Expr n = x->shape[1];
+
+  auto k = tvm::reduce_axis(Range(0, n), "k");
+  auto max_elem = tvm::compute(
+    { m }, [&](Var i) {
+      return tvm::max(x(i, k), Array<IterVar>{ k }); });
+  k = tvm::reduce_axis(Range(0, n), "k");
+
+  auto expsum = tvm::compute(
+    { m }, [&](Var i) {
+      return tvm::sum(tvm::exp(x(i, k) - max_elem(i)), { k }); });
+
+  return tvm::compute(
+    x->shape, [&](Var i, Var j) {
+      return x(i, j) - max_elem(i) - tvm::log(expsum(i));
+    });
+}
+
+}  // namespace nn
+}  // namespace topi
+#endif  // TOPI_NN_SOFTMAX_H_
diff --git a/topi/include/topi/reduction.h b/topi/include/topi/reduction.h
new file mode 100644
index 000000000000..3fda17130fce
--- /dev/null
+++ b/topi/include/topi/reduction.h
@@ -0,0 +1,379 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file topi/reduction.h
+ * \brief Reduction op constructors
+ */
+#ifndef TOPI_REDUCTION_H_
+#define TOPI_REDUCTION_H_
+
+#include <string>
+#include <set>
+#include <vector>
+#include <iterator>
+
+#include "topi/tags.h"
+#include "topi/detail/ravel_unravel.h"
+#include "topi/detail/constant_utils.h"
+#include "tvm/tvm.h"
+
+namespace topi {
+using namespace tvm;
+
+/*! \brief The operation to use for CommReduce */
+using FReduce = std::function<Expr(Expr source, const Array<IterVar>& axis)>;
+
+/*! \brief The operation to use for CommReduceIdx */
+using FCommReduce = std::function<
+  Array<Expr>(Array<Expr> exprs, const Array<IterVar>& axis, Expr* condition)>;
+
+/*!
+* \brief Convert a reduction axis which could be empty or have negative
+* elements into a real axis with valid dimension indices.
+*
+* \return A non-empty sorted array of valid dimension indices, with no duplicates.
+* If the input axis is empty, the result will be an axis including all dimensions.
+* If any input element is negative, it will be treated as an offset from the
+* last dimension (same as python indexing rules).
+*/
+std::vector<int> GetRealAxis(int ndim, const std::vector<int>& axis) {
+  std::vector<int> real_axis;
+  if (axis.size() == 0) {
+    for (int i = 0; i < ndim; ++i) {
+      real_axis.push_back(i);
+    }
+  } else {
+    // Use a set so duplicates are removed and the dims are sorted
+    std::set<int> dims;
+    for (auto ele : axis) {
+      if (ele < 0) {
+        ele += ndim;
+      }
+      if (ele >= ndim) {
+        LOG(ERROR) << ele << " exceeds the maximum dimension " << ndim;
+      }
+      dims.emplace(ele);
+    }
+    std::copy(dims.begin(), dims.end(), std::back_inserter(real_axis));
+  }
+  return real_axis;
+}
+
+/*! \brief Enumerate the axes for a reduce op */
+Array<IterVar> MakeReduceAxes(const std::vector<int>& real_axis, const Tensor& data) {
+  Array<IterVar> reduce_axes;
+  for (auto i : real_axis) {
+    std::string name = "k" + std::to_string(i);
+    reduce_axes.push_back(
+      tvm::reduce_axis(Range(0, data->shape[i]), name));
+  }
+  return reduce_axes;
+}
+
+/*! \brief Calculate the target shape for a reduce op */
+Array<Expr> MakeReduceTargetShape(const std::vector<int>& real_axis,
+                                  const Tensor& data,
+                                  bool keepdims) {
+  auto ndim = data->shape.size();
+  Array<Expr> target_shape;
+  if (keepdims) {
+    for (size_t i = 0; i < ndim; ++i) {
+      if (std::find(real_axis.begin(), real_axis.end(), i) != real_axis.end()) {
+        // real_axis contains i
+        target_shape.push_back(1);
+      } else {
+        target_shape.push_back(data->shape[i]);
+      }
+    }
+  } else {
+    for (size_t i = 0; i < ndim; ++i) {
+      if (std::find(real_axis.begin(), real_axis.end(), i) == real_axis.end()) {
+        // real_axis does not contain i
+        target_shape.push_back(data->shape[i]);
+      }
+    }
+  }
+  return target_shape;
+}
+
+/*!
+ * \brief Create a reduction operation.
+ *
+ * \param data The input tensor.
+ * \param axis The axes along which the reduction is performed.
+ * \param func The reduction function eg. tvm::sum
+ * \param keepdims If this is set to true, the axes which are reduced are
+ * left in the result as dimensions with size one. This enables the result
+ * to broadcast correctly against the input array.
+ *
+ * \return The result tensor.
+ */
+Tensor CommReduce(const Tensor& data,
+                  const Array<Expr>& axis,
+                  FReduce func,
+                  bool keepdims = false) {
+  auto ndim = data->shape.size();
+  CHECK_NE(ndim, 0) << "Cannot reduce a 0 dim Tensor";
+  auto axis_val = detail::GetConstIntValues(axis, "axis");
+  auto real_axis = GetRealAxis(static_cast<int>(ndim), axis_val);
+  auto reduce_axes = MakeReduceAxes(real_axis, data);
+  auto target_shape = MakeReduceTargetShape(real_axis, data, keepdims);
+
+  auto compute = [ndim, keepdims, &real_axis, &reduce_axes, &func, &data]
+  (const Array<Var>& indices) {
+    Array<Expr> eval_range;
+    Array<Var> eval_indices;
+    int arg_counter = 0;
+    int red_counter = 0;
+
+    for (size_t i = 0; i < ndim; ++i) {
+      if (std::find(real_axis.begin(), real_axis.end(), i) != real_axis.end()) {
+        // real_axis contains i
+        eval_range.push_back(reduce_axes[red_counter]);
+        eval_indices.push_back(reduce_axes[red_counter]->var);
+        red_counter++;
+      } else {
+        if (!keepdims) {
+          eval_range.push_back(indices[arg_counter]);
+          arg_counter++;
+        } else {
+          eval_range.push_back(indices[i]);
+        }
+      }
+    }
+
+    return func(data(eval_range), reduce_axes);
+  };
+
+  return tvm::compute(target_shape, compute, data->op->name + "_red", kCommReduce);
+}
+
+/*!
+* \brief Create an index reduction operation.
+*
+* \param data The input tensor.
+* \param axis The axes along which the reduction is performed.
+* \param func The reduction function
+* \param keepdims If this is set to true, the axes which are reduced are
+* left in the result as dimensions with size one. This enables the result
+* to broadcast correctly against the input array.
+*
+* \return The result tensor.
+*/
+Tensor CommReduceIdx(const Tensor& data,
+                     const Array<Expr>& axis,
+                     FCommReduce func,
+                     bool keepdims = false) {
+  auto ndim = data->shape.size();
+  CHECK_NE(ndim, 0) << "Cannot reduce a 0 dim Tensor";
+  auto axis_val = detail::GetConstIntValues(axis, "axis");
+  auto real_axis = GetRealAxis(static_cast<int>(ndim), axis_val);
+  auto reduce_axes = MakeReduceAxes(real_axis, data);
+  auto target_shape = MakeReduceTargetShape(real_axis, data, keepdims);
+
+  auto compute = [ndim, keepdims, &real_axis, &reduce_axes, &func, &data]
+  (const Array<Var>& indices) {
+    Array<Expr> eval_range;
+    Array<Var> eval_indices;
+    int arg_counter = 0;
+    int red_counter = 0;
+
+    for (size_t i = 0; i < ndim; ++i) {
+      if (std::find(real_axis.begin(), real_axis.end(), i) != real_axis.end()) {
+        // real_axis contains i
+        eval_range.push_back(reduce_axes[red_counter]);
+        eval_indices.push_back(reduce_axes[red_counter]->var);
+        red_counter++;
+      } else {
+        if (!keepdims) {
+          eval_range.push_back(indices[arg_counter]);
+          arg_counter++;
+        } else {
+          eval_range.push_back(indices[i]);
+        }
+      }
+    }
+
+    Array<Expr> ravel_shape;
+    for (auto i : real_axis) {
+      ravel_shape.push_back(data->shape[i]);
+    }
+    auto idx = detail::RavelIndex(eval_indices, ravel_shape);
+    return func({ idx, data(eval_range) }, reduce_axes, nullptr);
+  };
+
+  auto temp_idx_val = tvm::compute(target_shape, compute,
+                                   data->op->name + "_red_temp", kCommReduceIdx);
+  auto temp_idx = temp_idx_val[0];
+  auto temp_val = temp_idx_val[1];
+  return tvm::compute(
+    target_shape,
+    [&temp_idx](const Array<Var>& indices) { return temp_idx(indices); },
+    data->op->name + "_red",
+    kCommReduceIdx);
+}
+
+/*! \brief A combiner function for a reduction */
+using FCombine = std::function<Array<Expr>(Array<Var> lhs, Array<Var> rhs)>;
+
+/*! \brief An initializer function for a reduction */
+using FIdentity = std::function<Array<Expr>(std::vector<Type> types)>;
+
+/*!
+ * \brief Create a commutative reducer for a reduction
+ *
+ * \param fcombine A function to combine exprs
+ * \param fidentity A function to initialize elements
+ * \param name The name of the operation
+ *
+ * \return A reducer function which creates a reduce expression over an axis.
+ */
+FCommReduce MakeCommReducer(FCombine fcombine,
+                            FIdentity fidentity,
+                            std::string name = "reduce") {
+  return [fcombine, fidentity, &name]
+  (Array<Expr> exprs, const Array<IterVar>& axis, Expr* condition) {
+    Array<Var> lhs, rhs;
+    std::vector<Type> dtypes;
+
+    for (size_t i = 0; i < exprs.size(); ++i) {
+      auto dtype = exprs[i].type();
+      dtypes.push_back(dtype);
+      lhs.push_back(var("lhs_" + std::to_string(i), dtype));
+      rhs.push_back(var("rhs_" + std::to_string(i), dtype));
+    }
+
+    auto result = fcombine(lhs, rhs);
+    auto id_elem = fidentity(dtypes);
+    auto cond = condition != nullptr ? *condition : tvm::const_true();
+
+    auto combiner = tvm::ir::CommReducerNode::make(lhs, rhs, result, id_elem);
+    Array<Expr> outputs;
+    for (size_t i = 0; i < exprs.size(); ++i) {
+      outputs.push_back(tvm::ir::Reduce::make(combiner, exprs, axis, cond, static_cast<int>(i)));
+    }
+    return outputs;
+  };
+}
+
+/*! \brief Wrap tvm::min to ensure we get the correct overload */
+inline Expr MinOp(Expr source, Array<IterVar> axis) {
+  return tvm::min(source, axis);
+}
+
+/*! \brief Wrap tvm::max to ensure we get the correct overload */
+inline Expr MaxOp(Expr source, Array<IterVar> axis) {
+  return tvm::max(source, axis);  // NOLINT(*)
+}
+
+/*!
+* \brief Creates an operation that sums array elements over a given axis
+*
+* \param data The input tensor
+* \param axis The axis to sum over. If axis is empty, the operation will
+* sum over all elements of the array.
+* \param keepdims If this is set to true, the axes which are reduced are
+* left in the result as dimensions with size one. This enables the result
+* to broadcast correctly against the input array.
+*
+* \return A Tensor whose op member is the sum operation
+*/
+Tensor sum(const Tensor& data, Array<Expr> axis, bool keepdims = false) {
+  return CommReduce(data, axis, tvm::sum, keepdims);
+}
+
+/*!
+* \brief Creates an operation that finds the minimum of elements over
+* a given axis.
+*
+* \param data The input tensor
+* \param axis The axis to find the minimum over. If axis is empty, the
+* operation will find the minimum over all elements of the array.
+* \param keepdims If this is set to true, the axes which are reduced are
+* left in the result as dimensions with size one. This enables the result
+* to broadcast correctly against the input array.
+*
+* \return A Tensor whose op member is the min operation
+*/
+Tensor min(const Tensor& data, Array<Expr> axis, bool keepdims = false) {
+  return CommReduce(data, axis, MinOp, keepdims);
+}
+
+/*!
+* \brief Creates an operation that finds the maximum of elements over
+* a given axis.
+*
+* \param data The input tensor
+* \param axis The axis to find the maximum over. If axis is empty, the
+* operation will find the maximum over all elements of the array.
+* \param keepdims If this is set to true, the axes which are reduced are
+* left in the result as dimensions with size one. This enables the result
+* to broadcast correctly against the input array.
+*
+* \return A Tensor whose op member is the max operation
+*/
+Tensor max(const Tensor& data, Array<Expr> axis, bool keepdims = false) {  // NOLINT(*)
+  return CommReduce(data, axis, MaxOp, keepdims);
+}
+
+/*!
+* \brief Creates an operation that finds the indices of the minimum
+* values over a given axis.
+*
+* \param data The input tensor
+* \param axis The axis along which the argmin is performed. If axis is empty,
+* the operation will find the minimum index over all elements of the array.
+* \param keepdims If this is set to true, the axes which are reduced are
+* left in the result as dimensions with size one. This enables the result
+* to broadcast correctly against the input array.
+*
+* \return A Tensor whose op member is the argmin operation
+*/
+Tensor argmin(const Tensor& data, Array<Expr> axis, bool keepdims = false) {
+  auto fcombine = [](Array<Var> lhs, Array<Var> rhs) {
+    Array<Expr> result;
+    result.push_back(tvm::select(lhs[1] <= rhs[1], lhs[0], rhs[0]));  // idx
+    result.push_back(tvm::select(lhs[1] <= rhs[1], lhs[1], rhs[1]));  // val
+    return result;
+  };
+  auto fidentity = [](std::vector<Type> types) {
+    Array<Expr> result;
+    result.push_back(tvm::make_const(types[0], -1));  // idx
+    result.push_back(types[1].max());  // val
+    return result;
+  };
+  auto func = MakeCommReducer(fcombine, fidentity, "argmin");
+  return CommReduceIdx(data, axis, func, keepdims);
+}
+
+/*!
+* \brief Creates an operation that finds the indices of the maximum
+* values over a given axis.
+*
+* \param data The input tensor
+* \param axis The axis along which the argmax is performed. If axis is empty,
+* the operation will find the maximum index over all elements of the array.
+* \param keepdims If this is set to true, the axes which are reduced are
+* left in the result as dimensions with size one. This enables the result
+* to broadcast correctly against the input array.
+*
+* \return A Tensor whose op member is the argmax operation
+*/
+Tensor argmax(const Tensor& data, Array<Expr> axis, bool keepdims = false) {
+  auto fcombine = [](Array<Var> lhs, Array<Var> rhs) {
+    Array<Expr> result;
+    result.push_back(tvm::select(lhs[1] >= rhs[1], lhs[0], rhs[0]));  // idx
+    result.push_back(tvm::select(lhs[1] >= rhs[1], lhs[1], rhs[1]));  // val
+    return result;
+  };
+  auto fidentity = [](std::vector<Type> types) {
+    Array<Expr> result;
+    result.push_back(tvm::make_const(types[0], -1));  // idx
+    result.push_back(types[1].min());  // val
+    return result;
+  };
+  auto func = MakeCommReducer(fcombine, fidentity, "argmax");
+  return CommReduceIdx(data, axis, func, keepdims);
+}
+
+}  // namespace topi
+#endif  // TOPI_REDUCTION_H_
diff --git a/topi/include/topi/rocm/dense.h b/topi/include/topi/rocm/dense.h
new file mode 100644
index 000000000000..cf3c3f60b751
--- /dev/null
+++ b/topi/include/topi/rocm/dense.h
@@ -0,0 +1,82 @@
+/*!
+*  Copyright (c) 2017 by Contributors
+* \file rocm/dense.h
+* \brief rocm schedule for dense operation
+*/
+#ifndef TOPI_ROCM_DENSE_H_
+#define TOPI_ROCM_DENSE_H_
+
+#include "tvm/tvm.h"
+#include "tvm/build_module.h"
+#include "topi/tags.h"
+#include "topi/detail/array_utils.h"
+#include "topi/nn/dense.h"
+#include "topi/contrib/rocblas.h"
+#include "topi/generic/extern.h"
+#include "topi/cuda/dense.h"
+
+namespace topi {
+using namespace tvm;
+
+namespace rocm {
+/*!
+* \brief Implementation of dense for rocm backend
+*
+* \param target The target device
+* \param data Tensor with shape [batch, in_dim]
+* \param weight Tensor with shape [out_dim, in_dim]
+* \param bias Tensor with shape [out_dim] (optional)
+*
+* \return Tensor with shape [batch, out_dim]
+*/
+inline tvm::Tensor dense_rocm(const Target& target,
+                              const tvm::Tensor& data,
+                              const tvm::Tensor& weight,
+                              tvm::Tensor* bias) {
+  CHECK_EQ(data->shape.size(), 2) << "dense requires 2-D data";
+  CHECK_EQ(weight->shape.size(), 2) << "dense requires 2-D weight";
+  if (bias != nullptr) {
+    CHECK_EQ((*bias)->shape.size(), 1) << "dense requires 1-D bias";
+  }
+
+  auto batch = data->shape[0];
+  auto in_dim = data->shape[1];
+  auto out_dim = weight->shape[0];
+
+  if (target.libs.count("rocblas") > 0) {
+    auto mm = topi::contrib::rocblas_matmul(data, weight, false, true);
+    if (bias != nullptr) {
+      auto bias_val = *bias;
+      mm = tvm::compute({ batch, out_dim },
+                        [&](Var i, Var j) {
+                          return mm(i, j) + bias_val(j);
+                        }, "tensor", kBroadcast);
+    }
+
+    return mm;
+  } else {
+    return topi::nn::dense(data, weight, bias);
+  }
+}
+
+/*!
+* \brief Create a rocm schedule for dense
+*
+* \param target The target to generate a schedule for.
+* \param outs The output tensors.
+*
+* \return A schedule for the given ops.
+*/
+Schedule schedule_dense(const Target &target, const Array<Tensor>& outs) {
+  if (target.target_name == "rocm" &&
+    target.libs.count("rocblas") > 0) {
+    return topi::generic::schedule_extern(target, outs);
+  }
+
+  return topi::cuda::schedule_dense(target, outs);
+}
+
+}  // namespace rocm
+}  // namespace topi
+#endif  // TOPI_ROCM_DENSE_H_
+
diff --git a/topi/include/topi/tags.h b/topi/include/topi/tags.h
index 17882f877697..8ba9955be050 100644
--- a/topi/include/topi/tags.h
+++ b/topi/include/topi/tags.h
@@ -6,9 +6,14 @@
 #ifndef TOPI_TAGS_H_
 #define TOPI_TAGS_H_
 
+#include <string>
+
 namespace topi {
 
 constexpr auto kElementWise = "elemwise";
+constexpr auto kInjective = "injective";
+constexpr auto kCommReduce = "comm_reduce";
+constexpr auto kCommReduceIdx = "comm_reduce_idx";
 constexpr auto kBroadcast = "broadcast";
 constexpr auto kMatMult = "matmult";
 constexpr auto kConv2dNCHW = "conv2d_nchw";
@@ -19,6 +24,19 @@ constexpr auto kDepthwiseConv2dBackInputNHWC = "depthwise_conv2d_back_input_nhwc
 constexpr auto kDepthwiseConv2dBackWeightNHWC = "depthwise_conv2d_back_weight_nhwc";
 constexpr auto kGroupConv2d = "group_conv2d";
 
+inline bool is_broadcast(std::string tag) {
+  return
+    tag.rfind(kElementWise, 0) == 0 ||
+    tag.rfind(kBroadcast, 0) == 0;
+}
+
+inline bool is_injective(std::string tag) {
+  return
+    tag.rfind(kElementWise, 0) == 0 ||
+    tag.rfind(kBroadcast, 0) == 0 ||
+    tag.rfind(kInjective, 0) == 0;
+}
+
 }  // namespace topi
 
 #endif  // TOPI_TAGS_H_
diff --git a/topi/include/topi/transform.h b/topi/include/topi/transform.h
new file mode 100644
index 000000000000..9e4c8b06366f
--- /dev/null
+++ b/topi/include/topi/transform.h
@@ -0,0 +1,362 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file topi/transform.h
+ * \brief Transform op constructors
+ */
+#ifndef TOPI_TRANSFORM_H_
+#define TOPI_TRANSFORM_H_
+
+#include <string>
+#include <vector>
+#include <iterator>
+#include <algorithm>
+
+#include "topi/tags.h"
+#include "topi/detail/ravel_unravel.h"
+#include "topi/detail/constant_utils.h"
+#include "tvm/tvm.h"
+
+namespace topi {
+using namespace tvm;
+using namespace topi::detail;
+
+/*!
+* \brief Creates an operation to insert new dimensions of length 1
+*
+* \param x The input tensor
+* \param axis The index of the first new dimension (allows negative
+* indices as offsets from the last dimension)
+* \param num_newaxis The number of new dimensions to insert
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the dim expansion operation
+*/
+inline Tensor expand_dims(const Tensor& x,
+                          int axis,
+                          int num_newaxis = 1,
+                          std::string name = "tensor",
+                          std::string tag = kBroadcast) {
+  if (axis < 0) {
+    // Calculate offset from last dimension
+    axis = static_cast<int>(x->shape.size()) + axis + 1;
+  }
+
+  Array<Expr> new_shape;
+  for (size_t i = 0; i < static_cast<size_t>(axis); ++i) {
+    new_shape.push_back(x->shape[i]);
+  }
+  for (size_t i = 0; i < static_cast<size_t>(num_newaxis); ++i) {
+    new_shape.push_back(1);
+  }
+  for (size_t i = axis; i < x->shape.size(); ++i) {
+    new_shape.push_back(x->shape[i]);
+  }
+
+  return compute(
+    new_shape, [&](const Array<Var>& indices) {
+      Array<Expr> idx;
+      for (size_t i = 0; i < static_cast<size_t>(axis); ++i) {
+        idx.push_back(indices[i]);
+      }
+      for (size_t i = axis + num_newaxis; i < indices.size(); ++i) {
+        idx.push_back(indices[i]);
+      }
+      return x(idx);
+    }, name, tag);
+}
+
+/*!
+* \brief Permute the dimensions of an array
+*
+* \param x The input tensor
+* \param axes The indices of the permutation. If this is empty,
+* the dimensions will be reversed.
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the transpose operation
+*/
+inline Tensor transpose(const Tensor& x,
+                        Array<Expr> axes,
+                        std::string name = "tensor",
+                        std::string tag = kInjective) {
+  if (axes.size() == 0) {
+    axes = Array<Expr>();
+    for (int i = static_cast<int>(x->shape.size()) - 1; i >= 0; --i) {
+      axes.push_back(i);
+    }
+  }
+
+  auto axes_val = GetConstIntValues(axes, "axes");
+
+  Array<Expr> new_shape;
+  for (size_t i = 0; i < axes_val.size(); ++i) {
+    new_shape.push_back(x->shape[axes_val[i]]);
+  }
+  return compute(
+    new_shape, [&](const Array<Var>& indices) {
+      std::vector<Expr> idx;
+      for (size_t i = 0; i < axes_val.size(); ++i) {
+        idx.push_back(1);
+      }
+      for (size_t i = 0; i < axes_val.size(); ++i) {
+        idx[axes_val[i]] = indices[i];
+      }
+      return x(idx);
+    }, name, tag);
+}
+
+
+/*!
+* \brief Reshape a tensor
+*
+* \param x The input tensor
+* \param newshape The new shape
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the reshape operation
+*/
+inline Tensor reshape(const Tensor& x,
+                      Array<Expr> newshape,
+                      std::string name = "tensor",
+                      std::string tag = kInjective) {
+  auto x_shape = x->shape;
+  return compute(
+    newshape, [&](const Array<Var>& indices) {
+      return x(UnavelIndex(RavelIndex(indices, newshape), x_shape));
+    }, name, tag);
+}
+
+/*!
+* \brief Remove size 1 dimensions from the shape of a tensor.
+* The removed dimensions must have a constant size of 1.
+*
+* \param x The input tensor
+* \param axis Indices of the dimensions to remove. If this is empty,
+* all entries with a constant size of 1 will be removed.
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the squeeze operation
+*/
+inline Tensor squeeze(const Tensor& x,
+                      Array<Expr> axis,
+                      std::string name = "tensor",
+                      std::string tag = kInjective) {
+  auto axis_val = GetConstIntValues(axis, "axis");
+  auto ndim = x->shape.size();
+  if (axis_val.size() == 0) {
+    for (size_t i = 0; i < ndim; ++i) {
+      if (IsConstInt(x->shape[i]) && GetConstInt(x->shape[i]) == 1) {
+        axis_val.push_back(static_cast<int>(i));
+      }
+    }
+  } else {
+    for (size_t i = 0; i < axis_val.size(); ++i) {
+      if (axis_val[i] < 0) {
+        axis_val[i] += static_cast<int>(x->shape.size());
+      }
+      CHECK_EQ(GetConstInt(x->shape[axis_val[i]]), 1) <<
+        "Dimension " << axis[i] << " must have size 1";
+    }
+  }
+
+  std::unordered_set<int> axis_set(axis_val.begin(), axis_val.end());
+
+  Array<Expr> out_shape;
+  for (size_t i = 0; i < ndim; ++i) {
+    if (axis_set.count(static_cast<int>(i)) == 0) {
+      out_shape.push_back(x->shape[i]);
+    }
+  }
+  if (out_shape.size() == 0) {
+    out_shape.push_back(1);
+  }
+
+  return compute(
+    out_shape, [&](const Array<Var>& indices) {
+      Array<Expr> real_indices;
+      int flag = 0;
+      for (size_t i = 0; i < ndim; ++i) {
+        if (axis_set.count(static_cast<int>(i)) == 0) {
+          real_indices.push_back(indices[i - flag]);
+        } else {
+          real_indices.push_back(0);
+          flag += 1;
+        }
+      }
+      return x(real_indices);
+    }, name, tag);
+}
+
+/*!
+* \brief Join a sequence of tensors along an existing axis
+*
+* \param inputs The input tensors
+* \param axis The axis along which the tensors will be joined
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the concatenate operation
+*/
+inline Tensor concatenate(const Array<Tensor>& inputs,
+                          int axis = 0,
+                          std::string name = "tensor",
+                          std::string tag = kInjective) {
+  if (axis < 0) {
+    axis += static_cast<int>(inputs[0]->shape.size());
+  }
+  CHECK_LT(axis, inputs[0]->shape.size()) <<
+    "axis out of bounds";
+
+  Array<Expr> axis_sizes;
+  for (auto t : inputs) {
+    axis_sizes.push_back(t->shape[axis]);
+  }
+
+  Expr join_size = axis_sizes[0];
+  for (size_t i = 1; i < axis_sizes.size(); ++i) {
+    join_size += axis_sizes[i];
+  }
+  Array<Expr> out_shape;
+  for (size_t i = 0; i < inputs[0]->shape.size(); ++i) {
+    out_shape.push_back(i == static_cast<size_t>(axis) ? join_size : inputs[0]->shape[i]);
+  }
+
+  return compute(
+    out_shape, [&](const Array<Var>& indices) {
+      auto ret = inputs[0](indices);
+      auto ind = indices[axis];
+      for (size_t i = 0; i < inputs.size() - 1; ++i) {
+        ind -= axis_sizes[i];
+
+        Array<Expr> idx;
+        for (size_t i = 0; i < static_cast<size_t>(axis); ++i) {
+          idx.push_back(indices[i]);
+        }
+        idx.push_back(ind);
+        for (size_t i = axis + 1; i < indices.size(); ++i) {
+          idx.push_back(indices[i]);
+        }
+
+        ret = tvm::select(ind >= 0,
+                          inputs[i + 1](idx),
+                          ret);
+      }
+      return ret;
+    }, name, tag);
+}
+
+/*!
+* \brief Split a tensor into multiple sub-tensors
+*
+* \param x The input tensor
+* \param split_indices The indices to split the input at. This must be in ascending
+* order.
+* \param axis The axis to split along.
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the split operation
+*/
+inline Array<Tensor> split(const Tensor& x,
+                           Array<Expr> split_indices,
+                           int axis,
+                           std::string name = "tensor",
+                           std::string tag = kInjective) {
+  if (axis < 0) {
+    axis += static_cast<int>(x->shape.size());
+  }
+  auto src_axis_size = static_cast<int>(GetConstInt(x->shape[axis]));
+
+  auto split_indices_val = GetConstIntValues(split_indices, "split_indices");
+  CHECK(std::is_sorted(split_indices_val.begin(), split_indices_val.end())) <<
+    "split_indices must be sorted";
+
+  std::vector<int> begin_ids;
+  begin_ids.push_back(0);
+  std::copy(split_indices_val.begin(), split_indices_val.end(), std::back_inserter(begin_ids));
+
+  Array< Array<Expr> > out_shapes;
+  for (size_t i = 0; i < begin_ids.size(); ++i) {
+    int out_axis_size;
+    if (i == begin_ids.size() - 1) {
+      out_axis_size = src_axis_size - begin_ids[i];
+    } else {
+      out_axis_size = begin_ids[i + 1] - begin_ids[i];
+    }
+
+    Array<Expr> shape;
+    for (size_t i = 0; i < static_cast<size_t>(axis); ++i) {
+      shape.push_back(x->shape[i]);
+    }
+    shape.push_back(out_axis_size);
+    for (size_t i = axis + 1; i < x->shape.size(); ++i) {
+      shape.push_back(x->shape[i]);
+    }
+
+    out_shapes.push_back(shape);
+  }
+
+  Array<Tensor> result;
+  for (size_t i = 0; i < begin_ids.size(); ++i) {
+    result.push_back(
+      compute(
+        out_shapes[i], [&](const Array<Var>& indices) {
+          auto begin = begin_ids[i];
+          Array<Expr> real_indices;
+          for (size_t j = 0; j < static_cast<size_t>(axis); ++j) {
+            real_indices.push_back(indices[j]);
+          }
+          real_indices.push_back(indices[axis] + begin);
+          for (size_t j = axis + 1; j < indices.size(); ++j) {
+            real_indices.push_back(indices[j]);
+          }
+
+          return x(real_indices);
+        }, name, tag));
+  }
+
+  return result;
+}
+
+/*!
+* \brief Split a tensor into a number of sub-tensors
+*
+* \param x The input tensor
+* \param num_sections The number of sections to split the tensor into.
+* this must be an integer factor of the size of the axis being split.
+* \param axis The axis to split along.
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the split operation
+*/
+inline Array<Tensor> split_sections(const Tensor& x,
+                           int num_sections,
+                           int axis,
+                           std::string name = "tensor",
+                           std::string tag = kInjective) {
+  auto src_axis_size = static_cast<int>(GetConstInt(x->shape[axis]));
+
+  CHECK_GT(num_sections, 0) << "Slice count must be > 0";
+  CHECK_EQ(src_axis_size % num_sections, 0)
+    << "num_sections must be an integer factor of the size of axis " << axis
+    << " (" << src_axis_size << ")";
+
+  Array<Expr> split_indices;
+  auto seg_size = src_axis_size / num_sections;
+  for (int i = 0; i < num_sections; ++i) {
+    // region at index 0 is added by split()
+    if (i != 0) {
+      split_indices.push_back(seg_size * i);
+    }
+  }
+
+  return split(x, split_indices, axis, name, tag);
+}
+
+}  // namespace topi
+#endif  // TOPI_TRANSFORM_H_
diff --git a/topi/include/topi/x86/bnn.h b/topi/include/topi/x86/bnn.h
new file mode 100644
index 000000000000..a5046b0e79a9
--- /dev/null
+++ b/topi/include/topi/x86/bnn.h
@@ -0,0 +1,110 @@
+/*!
+*  Copyright (c) 2017 by Contributors
+* \file x86/bnn.h
+* \brief x86 schedule for binary operations
+*/
+#ifndef TOPI_X86_BNN_H_
+#define TOPI_X86_BNN_H_
+
+#include "topi/tags.h"
+#include "topi/detail/fuse.h"
+#include "tvm/tvm.h"
+#include "tvm/build_module.h"
+
+namespace topi {
+using namespace tvm;
+
+namespace x86 {
+/*!
+* \brief Create a generic schedule for binarize_pack
+*
+* \param target The target to generate a schedule for.
+* \param outs The output tensors.
+*
+* \return A schedule for the given ops.
+*/
+Schedule schedule_binarize_pack(const Target &target, const Array<Tensor>& outs) {
+  Array<Operation> out_ops;
+  for (auto t : outs) {
+    out_ops.push_back(t->op);
+  }
+  auto s = create_schedule(out_ops);
+
+  auto _schedule = [&](const Tensor& out) {
+    s[out].parallel(out->op.as<ComputeOpNode>()->axis[0]);
+  };
+
+  std::function<void(Operation)> traverse;
+  traverse = [&](const Operation& op) {
+    if (op->tag == "binarize_pack") {
+      _schedule(op.output(0));
+    } else {
+      LOG(ERROR) << "Unsupported operator " << op->tag;
+    }
+  };
+
+  traverse(outs[0]->op);
+  return s;
+}
+
+/*!
+* \brief Create a generic schedule for binary_dense
+*
+* \param target The target to generate a schedule for.
+* \param outs The output tensors.
+*
+* \return A schedule for the given ops.
+*/
+Schedule schedule_binary_dense(const Target &target, const Array<Tensor>& outs) {
+  Array<Operation> out_ops;
+  for (auto t : outs) {
+    out_ops.push_back(t->op);
+  }
+  auto s = create_schedule(out_ops);
+
+  auto _schedule = [&](const Tensor& A, const Tensor& B, const Tensor& C) {
+    IterVar co, ci;
+    s[C].split(s[C]->op.as<ComputeOpNode>()->reduce_axis[0], 8, &co, &ci);
+    s[C].parallel(s[C]->op.as<ComputeOpNode>()->axis[0]);
+
+    Tensor out;
+    if (contains(s->outputs, C->op)) {
+      out = C;
+    } else {
+      out = outs[0]->op.output(0);
+    }
+
+    IterVar xo, xi;
+    s[out].split(out->op.as<ComputeOpNode>()->axis[1], 8, &xo, &xi);
+    s[out].vectorize(xi);
+  };
+
+  std::function<void(Operation)> traverse;
+  traverse = [&](const Operation& op) {
+    // Inline all one-to-one-mapping operators except the last stage (output)
+    if (is_broadcast(op->tag)) {
+      if (!contains(s->outputs, op)) {
+        s[op].compute_inline();
+      }
+      for (auto tensor : op->InputTensors()) {
+        if (tensor->op->InputTensors().size() > 0) {
+          traverse(tensor->op);
+        }
+      }
+    } else if (op->tag == "binary_dense") {
+      auto output = op.output(0);
+      auto data = op->InputTensors()[0];
+      auto weight = op->InputTensors()[1];
+      _schedule(data, weight, output);
+    } else {
+      LOG(ERROR) << "Unsupported operator " << op->tag;
+    }
+  };
+
+  traverse(outs[0]->op);
+  return s;
+}
+
+}  // namespace x86
+}  // namespace topi
+#endif  // TOPI_X86_BNN_H_
diff --git a/topi/include/topi/x86/default.h b/topi/include/topi/x86/default.h
new file mode 100644
index 000000000000..76df67b79169
--- /dev/null
+++ b/topi/include/topi/x86/default.h
@@ -0,0 +1,58 @@
+/*!
+*  Copyright (c) 2017 by Contributors
+* \file x86/default.h
+* \brief default x86 schedule
+*/
+#ifndef TOPI_X86_DEFAULT_H_
+#define TOPI_X86_DEFAULT_H_
+
+#include "topi/tags.h"
+#include "topi/detail/fuse.h"
+#include "tvm/tvm.h"
+#include "tvm/build_module.h"
+
+namespace topi {
+using namespace tvm;
+
+namespace x86 {
+/*!
+* \brief Create a default x86 schedule for the given ops.
+*
+* \param target The target to generate a schedule for.
+* \param outs The output tensors.
+* \param auto_inline Whether to apply the auto inline step.
+*
+* \return A schedule for the given ops.
+*/
+Schedule default_schedule(const Target &target, const Array<Tensor>& outs, bool auto_inline) {
+  Array<Operation> out_ops;
+  for (auto t : outs) {
+    out_ops.push_back(t->op);
+  }
+  auto s = create_schedule(out_ops);
+  auto x = outs[0];
+  auto axis = s[x]->op.as<ComputeOpNode>()->axis;
+
+  if (auto_inline) {
+    tvm::schedule::AutoInlineInjective(s);
+    if (axis.size() > 0) {
+      Fuse(s[x], axis);
+    }
+    return s;
+  }
+
+  if (axis.size() == 4) {
+    auto n = axis[0];
+    auto c = axis[1];
+    auto fused = Fuse(s[x], { n, c });  // for nhwc layout, fuse n and h
+    s[x].parallel(fused);
+  } else {
+    s[x].parallel(axis[0]);
+  }
+
+  return s;
+}
+
+}  // namespace x86
+}  // namespace topi
+#endif  // TOPI_X86_DEFAULT_H_
diff --git a/topi/include/topi/x86/injective.h b/topi/include/topi/x86/injective.h
new file mode 100644
index 000000000000..8609beb56aa0
--- /dev/null
+++ b/topi/include/topi/x86/injective.h
@@ -0,0 +1,50 @@
+/*!
+*  Copyright (c) 2017 by Contributors
+* \file x86/injective.h
+* \brief x86 schedule for injective ops
+*/
+#ifndef TOPI_X86_INJECTIVE_H_
+#define TOPI_X86_INJECTIVE_H_
+
+#include "topi/tags.h"
+#include "topi/detail/fuse.h"
+#include "tvm/tvm.h"
+#include "tvm/build_module.h"
+
+namespace topi {
+using namespace tvm;
+
+namespace x86 {
+/*!
+* \brief Create an x86 schedule for the given injective ops.
+*
+* \param target The target to generate a schedule for.
+* \param outs The output tensors.
+*
+* \return A schedule for the given ops.
+*/
+Schedule schedule_injective(const Target &target, const Array<Tensor>& outs) {
+  Array<Operation> out_ops;
+  for (auto t : outs) {
+    out_ops.push_back(t->op);
+  }
+  auto s = create_schedule(out_ops);
+  tvm::schedule::AutoInlineInjective(s);
+
+  auto x = outs[0];
+  auto axis = s[x]->op.as<ComputeOpNode>()->axis;
+  if (axis.size() == 4) {
+    auto n = axis[0];
+    auto c = axis[1];
+    auto fused = Fuse(s[x], { n, c });  // for nhwc layout, fuse n and h
+    s[x].parallel(fused);
+  } else {
+    s[x].parallel(axis[0]);
+  }
+
+  return s;
+}
+
+}  // namespace x86
+}  // namespace topi
+#endif  // TOPI_X86_INJECTIVE_H_
diff --git a/topi/python/setup.py b/topi/python/setup.py
index e2a8a5d77a4f..6967051acbd8 100644
--- a/topi/python/setup.py
+++ b/topi/python/setup.py
@@ -2,6 +2,7 @@
 """Setup TOPI package."""
 from __future__ import absolute_import
 import sys
+import os
 
 from setuptools import find_packages
 from setuptools.dist import Distribution
@@ -13,7 +14,40 @@
     from setuptools import setup
     from setuptools.extension import Extension
 
-__version__ = "0.1.0"
+def get_lib_names():
+    if sys.platform.startswith('win32'):
+        return ['libtvm_topi.dll', 'tvm_topi.dll']
+    if sys.platform.startswith('darwin'):
+        return ['libtvm_topi.dylib', 'tvm_topi.dylib']
+    return ['libtvm_topi.so', 'tvm_topi.so']
+
+def get_lib_path():
+    """Get library path, name and version"""
+    # We can not import `libinfo.py` in setup.py directly since __init__.py
+    # Will be invoked which introduces dependences
+    CURRENT_DIR = os.path.dirname(__file__)
+    libinfo_py = os.path.join(CURRENT_DIR, '../../python/tvm/_ffi/libinfo.py')
+    libinfo = {'__file__': libinfo_py}
+    exec(compile(open(libinfo_py, "rb").read(), libinfo_py, 'exec'), libinfo, libinfo)
+    lib_path = libinfo['find_lib_path'](get_lib_names())
+    version = libinfo['__version__']
+    libs = [lib_path[0]]
+    if libs[0].find("runtime") == -1:
+        for name in lib_path[1:]:
+            if name.find("runtime") != -1:
+                libs.append(name)
+                break
+    return libs, version
+
+LIB_LIST, __version__ = get_lib_path()
+
+curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
+for i, path in enumerate(LIB_LIST):
+    LIB_LIST[i] = os.path.relpath(path, curr_path)
+setup_kwargs = {
+    "include_package_data": True,
+    "data_files": [('topi', LIB_LIST)]
+}
 
 setup(name='topi',
       version=__version__,
@@ -23,4 +57,5 @@
         "decorator",
         ],
       packages=find_packages(),
-      url='https://github.com/dmlc/tvm')
+      url='https://github.com/dmlc/tvm',
+      **setup_kwargs)
diff --git a/topi/python/topi/__init__.py b/topi/python/topi/__init__.py
index 9760722798af..4979bdbaac8d 100644
--- a/topi/python/topi/__init__.py
+++ b/topi/python/topi/__init__.py
@@ -9,6 +9,8 @@
 """
 from __future__ import absolute_import as _abs
 
+from tvm._ffi.libinfo import __version__
+
 from .math import *
 from .reduction import *
 from .transform import *
@@ -21,3 +23,4 @@
 from . import testing
 from . import util
 from . import rocm
+from . import cpp
diff --git a/topi/python/topi/cpp.py b/topi/python/topi/cpp.py
new file mode 100644
index 000000000000..c59d6d6bccfa
--- /dev/null
+++ b/topi/python/topi/cpp.py
@@ -0,0 +1,86 @@
+"""FFI for C++ TOPI ops and schedules"""
+import sys
+import os
+import ctypes
+from imp import new_module as _new_module
+from tvm._ffi.function import _init_api_prefix
+from tvm._ffi import libinfo
+import tvm as _tvm
+
+def _get_lib_names():
+    if sys.platform.startswith('win32'):
+        return ['libtvm_topi.dll', 'tvm_topi.dll']
+    if sys.platform.startswith('darwin'):
+        return ['libtvm_topi.dylib', 'tvm_topi.dylib']
+    return ['libtvm_topi.so', 'tvm_topi.so']
+
+def _load_lib():
+    """Load libary by searching possible path."""
+    curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
+    lib_search = curr_path
+    lib_path = libinfo.find_lib_path(_get_lib_names(), lib_search, optional=True)
+    if lib_path is None:
+        return None, None
+    lib = ctypes.CDLL(lib_path[0], ctypes.RTLD_GLOBAL)
+    return lib, os.path.basename(lib_path[0])
+
+_LIB, _LIB_NAME = _load_lib()
+
+_init_api_prefix("topi.cpp", "topi")
+
+def _create_module(name):
+    fullname = __name__ + "." + name
+    mod = _new_module(fullname)
+    sys.modules[fullname] = mod
+    return mod
+
+# pylint: disable-msg=C0103
+
+nn = _create_module("nn")
+_init_api_prefix("topi.cpp.nn", "topi.nn")
+generic = _create_module("generic")
+_init_api_prefix("topi.cpp.generic", "topi.generic")
+cuda = _create_module("cuda")
+_init_api_prefix("topi.cpp.cuda", "topi.cuda")
+rocm = _create_module("rocm")
+_init_api_prefix("topi.cpp.rocm", "topi.rocm")
+x86 = _create_module("x86")
+_init_api_prefix("topi.cpp.x86", "topi.x86")
+
+class IntVector(object):
+    """Handle to std::vector<int> instance """
+    _tvm_tcode = 27
+
+    def __init__(self, handle):
+        self.handle = handle
+
+    def __del__(self):
+        _tvm.nd.free_extension_handle(self.handle, 27)
+
+    @property
+    def _tvm_handle(self):
+        return self.handle.value
+
+    def __getitem__(self, idx):
+        return ivec_get(self, idx)
+
+_tvm.register_extension(IntVector, IntVector)
+
+class Target(object):
+    """Handle to C++ Target instance """
+    _tvm_tcode = 28
+
+    def __init__(self, handle):
+        self.handle = handle
+
+    def __del__(self):
+        _tvm.nd.free_extension_handle(self.handle, 28)
+
+    @property
+    def _tvm_handle(self):
+        return self.handle.value
+
+    def __getitem__(self, idx):
+        return ivec_get(self, idx)
+
+_tvm.register_extension(Target, Target)
diff --git a/topi/src/topi.cc b/topi/src/topi.cc
new file mode 100644
index 000000000000..03dd004bd42c
--- /dev/null
+++ b/topi/src/topi.cc
@@ -0,0 +1,448 @@
+/*!
+*  Copyright (c) 2017 by Contributors
+* \brief Registration of TVM operators and schedules
+* \file topi.cc
+*/
+#include <tvm/runtime/packed_func.h>
+#include <tvm/runtime/module.h>
+#include <tvm/runtime/registry.h>
+#include <tvm/packed_func_ext.h>
+#include <tvm/build_module.h>
+
+#include <topi/broadcast.h>
+#include <topi/elemwise.h>
+#include <topi/nn.h>
+#include <topi/reduction.h>
+#include <topi/transform.h>
+
+#include <topi/nn/batch_norm.h>
+#include <topi/nn/bnn.h>
+#include <topi/nn/dense.h>
+#include <topi/nn/dilate.h>
+#include <topi/nn/flatten.h>
+#include <topi/nn/mapping.h>
+#include <topi/nn/pooling.h>
+#include <topi/nn/softmax.h>
+
+#include <topi/generic/default.h>
+#include <topi/generic/extern.h>
+#include <topi/generic/injective.h>
+
+#include <topi/cuda/dense.h>
+#include <topi/cuda/extern.h>
+#include <topi/cuda/injective.h>
+#include <topi/cuda/pooling.h>
+#include <topi/cuda/reduction.h>
+#include <topi/cuda/softmax.h>
+
+#include <topi/x86/bnn.h>
+#include <topi/x86/default.h>
+#include <topi/x86/injective.h>
+
+#include <topi/rocm/dense.h>
+
+namespace tvm {
+namespace runtime {
+template<>
+struct extension_class_info<tvm::Target> {
+  static const int code = 28;
+};
+}  // namespace tvm
+} // namespace runtime
+
+namespace topi {
+using namespace tvm;
+using namespace tvm::runtime;
+
+TVM_REGISTER_EXT_TYPE(tvm::Target);
+
+/*! \brief Canonicalize an argument that may be Array<Expr> or int to Array<Expr> */
+Array<Expr> ArrayOrInt(TVMArgValue arg) {
+  if (arg.type_code() == kDLInt || arg.type_code() == kDLUInt) {
+    Array<Expr> result;
+    result.push_back(arg.operator int());
+    return result;
+  } else {
+    return arg;
+  }
+}
+
+TVM_REGISTER_GLOBAL("topi.TEST_create_target")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = tvm::Target::create(args[0]);
+  });
+
+/* Ops from broadcast.h */
+TVM_REGISTER_GLOBAL("topi.broadcast_to")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = broadcast_to(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.broadcast_add")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = broadcast_add(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.broadcast_sub")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = broadcast_sub(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.broadcast_mul")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = broadcast_mul(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.broadcast_div")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = broadcast_div(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.broadcast_maximum")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = broadcast_maximum(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.broadcast_minimum")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = broadcast_minimum(args[0], args[1]);
+   });
+
+TVM_REGISTER_GLOBAL("topi.broadcast_pow")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = broadcast_pow(args[0], args[1]);
+  });
+
+/* Ops from elemwise.h */
+TVM_REGISTER_GLOBAL("topi.exp")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = exp(args[0]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.tanh")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = tanh(args[0]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.sigmoid")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = sigmoid(args[0]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.sqrt")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = sqrt(args[0]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.log")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = log(args[0]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.identity")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = identity(args[0]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.negative")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = negative(args[0]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.pow")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = pow(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.left_shift")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  Tensor lhs = args[0];
+  Expr rhs = args[1];
+  *rv = lhs >> rhs;
+  });
+
+TVM_REGISTER_GLOBAL("topi.right_shift")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  Tensor lhs = args[0];
+  Expr rhs = args[1];
+  *rv = lhs << rhs;
+  });
+
+TVM_REGISTER_GLOBAL("topi.clip")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = clip(args[0], args[1], args[2]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.cast")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = cast(args[0], args[1]);
+  });
+
+/* Ops from nn.h */
+TVM_REGISTER_GLOBAL("topi.nn.relu")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = relu<float>(args[0]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.nn.leaky_relu")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = leaky_relu<float>(args[0]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.nn.pad")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = pad(args[0], args[1], args[2], args[3]);
+  });
+
+/* Ops from reduction.h */
+TVM_REGISTER_GLOBAL("topi.sum")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::sum(args[0], ArrayOrInt(args[1]), args[2]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.min")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::min(args[0], ArrayOrInt(args[1]), args[2]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.max")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::max(args[0], ArrayOrInt(args[1]), args[2]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.argmin")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::argmin(args[0], ArrayOrInt(args[1]), args[2]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.argmax")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::argmax(args[0], ArrayOrInt(args[1]), args[2]);
+  });
+
+/* Ops from transform.h */
+TVM_REGISTER_GLOBAL("topi.expand_dims")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = expand_dims(args[0], args[1], args[2]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.transpose")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = transpose(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.reshape")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = reshape(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.squeeze")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = squeeze(args[0], ArrayOrInt(args[1]));
+  });
+
+TVM_REGISTER_GLOBAL("topi.concatenate")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = concatenate(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.split")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  if (args[1].type_code() == kDLInt || args[1].type_code() == kDLUInt) {
+    *rv = split_sections(args[0], args[1], args[2]);
+  } else {
+    *rv = split(args[0], args[1], args[2]);
+  }
+  });
+
+/* Ops from nn/batch_norm.h */
+TVM_REGISTER_GLOBAL("topi.nn.batch_norm_inference")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = nn::batch_norm_inference(args[0],
+                                 args[1],
+                                 args[2],
+                                 args[3],
+                                 args[4],
+                                 static_cast<double>(args[5]),
+                                 args[6]);
+  });
+
+/* Ops from nn/bnn.h */
+TVM_REGISTER_GLOBAL("topi.nn.binarize_pack")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = nn::binarize_pack(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.nn.binary_dense")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = nn::binary_dense(args[0], args[1]);
+  });
+
+/* Ops from nn/dense.h */
+TVM_REGISTER_GLOBAL("topi.nn.dense")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  Tensor bias_val;
+  Tensor *bias;
+  if (args[2].type_code() == kNull) {
+    bias = nullptr;
+  } else {
+    bias_val = args[2];
+    bias = &bias_val;
+  }
+  *rv = nn::dense(args[0], args[1], bias);
+  });
+
+/* Ops from nn/dilate.h */
+TVM_REGISTER_GLOBAL("topi.nn.dilate")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = nn::dilate(args[0], args[1]);
+  });
+
+/* Ops from nn/flatten.h */
+TVM_REGISTER_GLOBAL("topi.nn.flatten")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = nn::flatten(args[0]);
+  });
+
+/* Ops from nn/mapping.h */
+TVM_REGISTER_GLOBAL("topi.nn.scale_shift_nchw")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = nn::scale_shift_nchw(args[0], args[1], args[2]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.nn.scale_shift_nhwc")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = nn::scale_shift_nhwc(args[0], args[1], args[2]);
+  });
+
+/* Ops from nn/pooling.h */
+TVM_REGISTER_GLOBAL("topi.nn.pool")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = nn::pool(args[0], args[1], args[2], args[3],
+                 static_cast<nn::PoolType>(static_cast<int>(args[4])),
+                 args[5]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.nn.global_pool")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = nn::global_pool(args[0],
+                        static_cast<nn::PoolType>(static_cast<int>(args[1])));
+  });
+
+/* Ops from nn/softmax.h */
+TVM_REGISTER_GLOBAL("topi.nn.softmax")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = nn::softmax(args[0]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.nn.log_softmax")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = nn::log_softmax(args[0]);
+  });
+
+/* Generic schedules */
+TVM_REGISTER_GLOBAL("topi.generic.default_schedule")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::generic::default_schedule(args[0], args[1], args[2]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.generic.schedule_extern")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::generic::schedule_extern(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.generic.schedule_injective")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::generic::schedule_injective(args[0], args[1]);
+  });
+
+/* x86 schedules */
+TVM_REGISTER_GLOBAL("topi.x86.schedule_binarize_pack")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::x86::schedule_binarize_pack(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.x86.schedule_binary_dense")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::x86::schedule_binary_dense(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.x86.default_schedule")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::x86::default_schedule(args[0], args[1], args[2]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.x86.schedule_injective")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::x86::schedule_injective(args[0], args[1]);
+  });
+
+/* ROCm schedules */
+TVM_REGISTER_GLOBAL("topi.rocm.dense_cuda")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  Tensor bias_val;
+  Tensor *bias;
+  if (args[3].type_code() == kNull) {
+    bias = nullptr;
+  } else {
+    bias_val = args[3];
+    bias = &bias_val;
+  }
+  *rv = rocm::dense_rocm(args[0], args[1], args[2], bias);
+  });
+
+TVM_REGISTER_GLOBAL("topi.rocm.schedule_dense")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::rocm::schedule_dense(args[0], args[1]);
+  });
+
+/* CUDA schedules */
+TVM_REGISTER_GLOBAL("topi.cuda.dense_cuda")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  Tensor bias_val;
+  Tensor *bias;
+  if (args[3].type_code() == kNull) {
+    bias = nullptr;
+  } else {
+    bias_val = args[3];
+    bias = &bias_val;
+  }
+  *rv = cuda::dense_cuda(args[0], args[1], args[2], bias);
+  });
+
+TVM_REGISTER_GLOBAL("topi.cuda.schedule_dense")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::cuda::schedule_dense(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.cuda.schedule_extern")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::cuda::schedule_extern(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.cuda.schedule_injective")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::cuda::schedule_injective(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.cuda.schedule_pool")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::cuda::schedule_pool(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.cuda.schedule_global_pool")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::cuda::schedule_global_pool(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.cuda.schedule_reduce")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::cuda::schedule_reduce(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.cuda.schedule_softmax")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::cuda::schedule_softmax(args[0], args[1]);
+  });
+
+}  // namespace topi
diff --git a/topi/tests/python_cpp/test_topi_basic.py b/topi/tests/python_cpp/test_topi_basic.py
new file mode 100644
index 000000000000..1faba588b877
--- /dev/null
+++ b/topi/tests/python_cpp/test_topi_basic.py
@@ -0,0 +1,31 @@
+import tvm
+import topi
+from topi import util
+
+
+def test_util():
+    x = tvm.const(100)
+    assert util.get_const_int(x) == 100
+    assert util.get_const_tuple((x, x)) == (100, 100)
+
+
+def test_ewise():
+    m = tvm.var('m')
+    l = tvm.var('l')
+    A = tvm.placeholder((m, l), name='A')
+
+    def test_apply(func, name):
+        B = func(A)
+        assert tuple(B.shape) == tuple(A.shape)
+        assert B.op.body[0].name == name
+
+    test_apply(topi.cpp.exp, "exp")
+    test_apply(topi.cpp.tanh, "tanh")
+    test_apply(topi.cpp.sigmoid, "sigmoid")
+    test_apply(topi.cpp.log, "log")
+    test_apply(topi.cpp.sqrt, "sqrt")
+
+
+if __name__ == "__main__":
+    test_util()
+    test_ewise()
diff --git a/topi/tests/python_cpp/test_topi_bnn.py b/topi/tests/python_cpp/test_topi_bnn.py
new file mode 100644
index 000000000000..3fa5cfc4a0a7
--- /dev/null
+++ b/topi/tests/python_cpp/test_topi_bnn.py
@@ -0,0 +1,55 @@
+"""Test code for binary neural network operators."""
+import numpy as np
+import tvm
+import topi
+from topi.util import get_const_tuple
+from tvm.contrib.pickle_memoize import memoize
+
+
+def verify_binary_dense(batch, in_dim, out_dim):
+    A = tvm.placeholder((batch, in_dim), name='A')
+    B = tvm.placeholder((out_dim, in_dim), name='B')
+    bnn_A = topi.cpp.nn.binarize_pack(A, 1)
+    bnn_B = topi.cpp.nn.binarize_pack(B, 1)
+    # binary dense
+    bnn_A1 = tvm.placeholder(bnn_A.shape, dtype=bnn_A.dtype)
+    bnn_B1 = tvm.placeholder(bnn_B.shape, dtype=bnn_B.dtype)
+    bnn_C = topi.cpp.nn.binary_dense(bnn_A1, bnn_B1)
+    # schedule
+    target = topi.cpp.TEST_create_target("llvm")
+    s1 = topi.cpp.x86.schedule_binarize_pack(target, [bnn_A])
+    s2 = topi.cpp.x86.schedule_binarize_pack(target, [bnn_B])
+    s3 = topi.cpp.x86.schedule_binary_dense(target, [bnn_C])
+
+    dtype = A.dtype
+    @memoize("topi.tests.test_topi_binary_dense")
+    def get_ref_data():
+        # generate random matrix of +1 or -1 value
+        a_np = (np.random.randint(2, size=(batch, in_dim)) * 2 - 1).astype(dtype)
+        b_np = (np.random.randint(2, size=(out_dim, in_dim)) * 2 - 1).astype(dtype)
+        c_np = np.dot(a_np, b_np.T)
+        return (a_np, b_np, c_np)
+
+    a_np, b_np, c_np = get_ref_data()
+
+    ctx = tvm.cpu(0)
+    a = tvm.nd.array(a_np, ctx)
+    b = tvm.nd.array(b_np, ctx)
+    bnn_a = tvm.nd.array(np.zeros(get_const_tuple(bnn_A.shape), dtype=bnn_A.dtype), ctx)
+    bnn_b = tvm.nd.array(np.zeros(get_const_tuple(bnn_B.shape), dtype=bnn_B.dtype), ctx)
+    bnn_c = tvm.nd.array(np.zeros(get_const_tuple(bnn_C.shape), dtype=bnn_C.dtype), ctx)
+    f1 = tvm.build(s1, [A, bnn_A], 'llvm')
+    f2 = tvm.build(s2, [B, bnn_B], 'llvm')
+    f3 = tvm.build(s3, [bnn_A1, bnn_B1, bnn_C], 'llvm')
+    f1(a, bnn_a)
+    f2(b, bnn_b)
+    f3(bnn_a, bnn_b, bnn_c)
+    np.testing.assert_allclose(bnn_c.asnumpy(), c_np, rtol=1e-5)
+
+def test_binary_dense():
+    verify_binary_dense(1, 4096, 1024)
+    verify_binary_dense(1, 1024, 1000)
+
+
+if __name__ == "__main__":
+    test_binary_dense()
diff --git a/topi/tests/python_cpp/test_topi_broadcast.py b/topi/tests/python_cpp/test_topi_broadcast.py
new file mode 100644
index 000000000000..301751c25517
--- /dev/null
+++ b/topi/tests/python_cpp/test_topi_broadcast.py
@@ -0,0 +1,114 @@
+"""Test code for broadcasting operators."""
+import os
+import numpy as np
+import tvm
+import topi
+
+def verify_broadcast_to_ele(in_shape, out_shape):
+    # Build the logic and compile the function
+    A = tvm.placeholder(shape=in_shape, name="A")
+    B = topi.cpp.broadcast_to(A, out_shape)
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        target = topi.cpp.TEST_create_target(device)
+        s = topi.cpp.cuda.schedule_injective(target, [B])
+        ctx = tvm.context(device, 0)
+        foo = tvm.build(s, [A, B], device, name="broadcast_to")
+        data_npy = np.random.uniform(size=in_shape).astype(A.dtype)
+        out_npy = np.broadcast_to(data_npy, out_shape)
+        data_nd = tvm.nd.array(data_npy, ctx)
+        out_nd = tvm.nd.array(np.empty(out_shape).astype(B.dtype), ctx)
+        for _ in range(1):
+            foo(data_nd, out_nd)
+        np.testing.assert_allclose(out_nd.asnumpy(), out_npy)
+
+    check_device("opencl")
+    check_device("cuda")
+    #check_device("metal")
+    #check_device("rocm")
+
+
+def verify_broadcast_binary_ele(lhs_shape, rhs_shape, typ="add"):
+    # Build the logic and compile the function
+    A = tvm.placeholder(shape=lhs_shape, name="A")
+    B = tvm.placeholder(shape=rhs_shape, name="B")
+    if typ == "add":
+        C = topi.cpp.broadcast_add(A, B)
+    elif typ == "sub":
+        C = topi.cpp.broadcast_sub(A, B)
+    elif typ == "div":
+        C = topi.cpp.broadcast_div(A, B)
+    elif typ == "mul":
+        C = topi.cpp.broadcast_mul(A, B)
+    elif typ == "maximum":
+        C = topi.cpp.broadcast_maximum(A, B)
+    elif typ == "minimum":
+        C = topi.cpp.broadcast_minimum(A, B)
+    elif typ == "pow":
+        C = topi.cpp.broadcast_pow(A, B)
+    else:
+        raise NotImplementedError
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        target = topi.cpp.TEST_create_target(device)
+        s = topi.cpp.cuda.schedule_injective(target, [C])
+        ctx = tvm.context(device, 0)
+        foo = tvm.build(s, [A, B, C], device, name="broadcast_binary" + "_" + typ)
+        lhs_npy = np.random.uniform(size=lhs_shape).astype(A.dtype)
+        rhs_npy = np.random.uniform(size=rhs_shape).astype(A.dtype)
+        if typ == "add":
+            out_npy = lhs_npy + rhs_npy
+        elif typ == "sub":
+            out_npy = lhs_npy - rhs_npy
+        elif typ == "div":
+            rhs_npy = np.abs(rhs_npy) + 0.001
+            out_npy = lhs_npy / rhs_npy
+        elif typ == "mul":
+            out_npy = lhs_npy * rhs_npy
+        elif typ == "maximum":
+            out_npy = np.maximum(lhs_npy, rhs_npy)
+        elif typ == "minimum":
+            out_npy = np.minimum(lhs_npy, rhs_npy)
+        elif typ == "pow":
+            out_npy = lhs_npy ** rhs_npy
+        else:
+            raise NotImplementedError
+        lhs_nd = tvm.nd.array(lhs_npy, ctx)
+        rhs_nd = tvm.nd.array(rhs_npy, ctx)
+        out_nd = tvm.nd.array(np.empty(out_npy.shape).astype(B.dtype), ctx)
+        for _ in range(1):
+            foo(lhs_nd, rhs_nd, out_nd)
+        np.testing.assert_allclose(out_nd.asnumpy(), out_npy, rtol=1E-4, atol=1E-4)
+
+    check_device("opencl")
+    check_device("cuda")
+    #check_device("metal")
+    #check_device("rocm")
+
+def test_broadcast_to():
+    verify_broadcast_to_ele((1,), (10,))
+    verify_broadcast_to_ele((), (10,))
+    verify_broadcast_to_ele((1, 1, 5, 4), (3, 4, 4, 4, 5, 4))
+    verify_broadcast_to_ele((1, 128, 1, 32), (64, 128, 64, 32))
+
+
+def test_broadcast_binary():
+    verify_broadcast_binary_ele((5, 2, 3), (2, 1), typ="add")
+    verify_broadcast_binary_ele((5, 2, 3), (), typ="add")
+    verify_broadcast_binary_ele((5, 64, 128), (2, 5, 64, 1), typ="mul")
+    verify_broadcast_binary_ele((2, 3, 1, 32), (64, 32), typ="div")
+    verify_broadcast_binary_ele((1, 32), (64, 32), typ="sub")
+    verify_broadcast_binary_ele((32,), (64, 32), typ="maximum")
+    verify_broadcast_binary_ele((1, 2, 2, 1, 32), (64, 32), typ="minimum")
+    verify_broadcast_binary_ele((1, 32), (64, 32), typ="pow")
+
+
+if __name__ == "__main__":
+    test_broadcast_to()
+    test_broadcast_binary()
diff --git a/topi/tests/python_cpp/test_topi_clip.py b/topi/tests/python_cpp/test_topi_clip.py
new file mode 100644
index 000000000000..a059464375ac
--- /dev/null
+++ b/topi/tests/python_cpp/test_topi_clip.py
@@ -0,0 +1,45 @@
+"""Test code for clip operator"""
+import numpy as np
+import tvm
+import topi
+from topi.util import get_const_tuple
+from tvm.contrib.pickle_memoize import memoize
+from util import make_vector
+
+
+def verify_clip(N, a_min, a_max, dtype):
+    A = tvm.placeholder((N, N), dtype=dtype, name='A')
+    B = topi.cpp.clip(A, a_min, a_max)
+
+    # use memoize to pickle the test data for next time use
+    @memoize("topi.tests.test_topi_clip")
+    def get_ref_data():
+        a_np = np.random.uniform(a_min*2, a_max*2, size=(N, N)).astype(dtype)
+        b_np = np.clip(a_np, a_min, a_max)
+        return a_np, b_np
+    a_np, b_np = get_ref_data()
+
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        target = topi.cpp.TEST_create_target(device)
+        s = topi.cpp.generic.default_schedule(target, [B], False)
+        ctx = tvm.cpu(0) if device == "llvm" else tvm.gpu(0)
+        a = tvm.nd.array(a_np, ctx)
+        b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=dtype), ctx)
+        f = tvm.build(s, [A, B], device, name="clip")
+        f(a, b)
+        np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+    for device in ['llvm']:
+        check_device(device)
+
+def test_clip():
+    verify_clip(1024, -127, 127, 'int8')
+    verify_clip(1024, -127, 127, 'int16')
+    verify_clip(1024, -127, 127, 'float32')
+
+
+if __name__ == "__main__":
+    test_clip()
diff --git a/topi/tests/python_cpp/test_topi_dense.py b/topi/tests/python_cpp/test_topi_dense.py
new file mode 100644
index 000000000000..6ebd6948ee71
--- /dev/null
+++ b/topi/tests/python_cpp/test_topi_dense.py
@@ -0,0 +1,61 @@
+"""Test code for dense operator"""
+import numpy as np
+import tvm
+import topi
+from topi.util import get_const_tuple
+from tvm.contrib.pickle_memoize import memoize
+
+
+def verify_dense(batch, in_dim, out_dim, use_bias=True):
+    A = tvm.placeholder((batch, in_dim), name='A')
+    B = tvm.placeholder((out_dim, in_dim), name='B')
+    C = tvm.placeholder((out_dim,), name='C')
+    D = topi.cpp.nn.dense(A, B, C if use_bias else None)
+    D = topi.cpp.nn.relu(D)
+    dtype = A.dtype
+
+    # use memoize to pickle the test data for next time use
+    @memoize("topi.tests.test_topi_dense")
+    def get_ref_data():
+        a_np = np.random.uniform(size=(batch, in_dim)).astype(dtype)
+        b_np = np.random.uniform(size=(out_dim, in_dim)).astype(dtype)
+        c_np = np.random.uniform(size=(out_dim,)).astype(dtype)
+        if use_bias:
+            d_np = np.maximum(np.dot(a_np, b_np.T) + c_np, 0.0)
+        else:
+            d_np = np.maximum(np.dot(a_np, b_np.T), 0.0)
+        return (a_np, b_np, c_np, d_np)
+    # get the test data
+    a_np, b_np, c_np, d_np = get_ref_data()
+
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        target = topi.cpp.TEST_create_target(device)
+        if device == "llvm":
+            s = topi.cpp.generic.schedule_dense(target, [D])
+        elif device == "rocm":
+            s = topi.cpp.rocm.schedule_dense(target, [D])
+        else:
+            s = topi.cpp.cuda.schedule_dense(target, [D])
+        ctx = tvm.context(device, 0)
+        a = tvm.nd.array(a_np, ctx)
+        b = tvm.nd.array(b_np, ctx)
+        c = tvm.nd.array(c_np, ctx)
+        d = tvm.nd.array(np.zeros(get_const_tuple(D.shape), dtype=dtype), ctx)
+        f = tvm.build(s, [A, B, C, D], device, name="dense")
+        f(a, b, c, d)
+        np.testing.assert_allclose(d.asnumpy(), d_np, rtol=1e-5)
+
+    for device in ['cuda', 'opencl', 'metal', 'rocm']:
+        check_device(device)
+
+def test_dense():
+    verify_dense(1, 1024, 1000, use_bias=True)
+    verify_dense(1, 1024, 1000, use_bias=False)
+
+
+if __name__ == "__main__":
+    test_dense()
diff --git a/topi/tests/python_cpp/test_topi_dilate.py b/topi/tests/python_cpp/test_topi_dilate.py
new file mode 100644
index 000000000000..177ddbc3cfd7
--- /dev/null
+++ b/topi/tests/python_cpp/test_topi_dilate.py
@@ -0,0 +1,34 @@
+import tvm
+import topi
+import numpy as np
+
+def test_dilate():
+    target = 'llvm'
+    ctx = tvm.cpu(0)
+
+    def _test_dilate(input_size, strides):
+        Input = tvm.placeholder((input_size))
+        Output = topi.cpp.nn.dilate(Input, strides)
+        tgt = topi.cpp.TEST_create_target(target)
+        schedule = topi.cpp.generic.default_schedule(tgt, [Output], True)
+        input_np = np.random.uniform(size=input_size).astype(Input.dtype)
+        output_np = topi.testing.dilate_python(input_np, strides)
+        input_tvm = tvm.nd.array(input_np, ctx=ctx)
+        output_size = topi.util.get_const_tuple(Output.shape)
+        output_tvm = tvm.nd.array(np.zeros(shape=output_size).astype(Output.dtype), ctx=ctx)
+        f = tvm.build(schedule, [Input, Output], target)
+        f(input_tvm, output_tvm)
+        np.testing.assert_allclose(output_tvm.asnumpy(), output_np, rtol=1e-5)
+
+    _test_dilate((32,), (2,))
+    _test_dilate((32,32), (2,2))
+    _test_dilate((1,3,32,32), (1,1,1,1))
+    _test_dilate((1,3,32,32), (2,2,2,2))
+    _test_dilate((1,32,32,3,3), (1,1,1,1,1))
+    _test_dilate((1,32,32,3,3), (2,2,2,2,2))
+    _test_dilate((1,32,32,32,3,3), (1,1,1,2,2,2))
+    _test_dilate((1,32,32,32,3,3), (2,2,2,1,1,1))
+
+
+if __name__ == "__main__":
+    test_dilate()
diff --git a/topi/tests/python_cpp/test_topi_pooling.py b/topi/tests/python_cpp/test_topi_pooling.py
new file mode 100644
index 000000000000..6132fcd36469
--- /dev/null
+++ b/topi/tests/python_cpp/test_topi_pooling.py
@@ -0,0 +1,120 @@
+"""Test code for pooling"""
+import numpy as np
+import tvm
+import topi
+import math
+from topi.util import get_const_tuple
+
+pool_code = {
+    "avg": 0,
+    "max": 1
+}
+def verify_pool(n, ic, ih, kh, sh, padding, pool_type, ceil_mode):
+    iw = ih
+    kw = kh
+    sw = sh
+    ph, pw = padding
+    A = tvm.placeholder((n, ic, ih, iw), name='A')
+    B = topi.cpp.nn.pool(A, [kh, kw], [sh, sw], padding,
+                     pool_code[pool_type], ceil_mode)
+    B = topi.cpp.nn.relu(B)
+    dtype = A.dtype
+
+    bshape = get_const_tuple(B.shape)
+    ashape = get_const_tuple(A.shape)
+    if ceil_mode:
+        assert bshape[2] == int(math.ceil(float(ashape[2] - kh + ph * 2) / sh) + 1)
+        assert bshape[3] == int(math.ceil(float(ashape[3] - kw + pw * 2) / sw) + 1)
+    else:
+        assert bshape[2] == int(math.floor(float(ashape[2] - kh + ph * 2) / sh) + 1)
+        assert bshape[3] == int(math.floor(float(ashape[3] - kw + pw * 2) / sw) + 1)
+
+
+    a_np = np.random.uniform(size=(n, ic, ih, iw)).astype(dtype)
+    pad_np = np.zeros(shape=(n, ic, ih+2*ph, iw+2*pw)).astype(dtype)
+    no_zero = (range(n), range(ic), (range(ph, ih+ph)), (range(pw, iw+pw)))
+    pad_np[np.ix_(*no_zero)] = a_np
+    _, oc, oh, ow = get_const_tuple(B.shape)
+    b_np = np.zeros(shape=(n, oc, oh, ow)).astype(dtype)
+
+    if pool_type == 'avg':
+        for i in range(oh):
+            for j in range(ow):
+                b_np[:,:,i,j] = np.mean(pad_np[:, :, i*sh:i*sh+kh, j*sw:j*sw+kw], axis=(2,3))
+    elif pool_type =='max':
+        for i in range(oh):
+            for j in range(ow):
+                b_np[:,:,i,j] = np.max(pad_np[:, :, i*sh:i*sh+kh, j*sw:j*sw+kw], axis=(2,3))
+    b_np = np.maximum(b_np, 0.0)
+
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        target = topi.cpp.TEST_create_target(device)
+        if device == "llvm":
+            s = topi.cpp.generic.default_schedule(target, [B], False)
+        else:
+            s = topi.cpp.cuda.schedule_pool(target, [B])
+        ctx = tvm.context(device, 0)
+        a = tvm.nd.array(a_np, ctx)
+        b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=dtype), ctx)
+        f = tvm.build(s, [A, B], device)
+        f(a, b)
+        np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+    for device in ['cuda', 'opencl', 'metal', 'rocm']:
+        check_device(device)
+
+def test_pool():
+    verify_pool(1, 256, 32, 2, 2, [0, 0], 'avg', False)
+    verify_pool(1, 256, 31, 3, 3, [1, 2], 'avg', False)
+    verify_pool(1, 256, 32, 2, 2, [0, 0], 'max', False)
+    verify_pool(1, 256, 31, 3, 3, [2, 1], 'max', False)
+    verify_pool(1, 256, 31, 3, 3, [2, 1], 'max', True)
+
+
+
+def verify_global_pool(n, c, h, w, pool_type):
+    A = tvm.placeholder((n, c, h, w), name='A')
+    B = topi.cpp.nn.global_pool(A, pool_code[pool_type])
+    B = topi.cpp.nn.relu(B)
+
+    a_np = np.random.uniform(size=get_const_tuple(A.shape)).astype(A.dtype)
+    if pool_type == 'avg':
+        b_np = np.mean(a_np, axis=(2,3), keepdims=True)
+    elif pool_type =='max':
+        b_np = np.max(a_np, axis=(2,3), keepdims=True)
+    b_np = np.maximum(b_np, 0.0)
+
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        target = topi.cpp.TEST_create_target(device)
+        if device == "llvm":
+            s = topi.cpp.generic.default_schedule(target, [B], False)
+        else:
+            s = topi.cpp.cuda.schedule_global_pool(target, [B])
+        ctx = tvm.context(device, 0)
+        a = tvm.nd.array(a_np, ctx)
+        b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
+        f = tvm.build(s, [A, B], device)
+        f(a, b)
+        np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+    for device in ['cuda', 'opencl', 'metal', 'rocm']:
+        check_device(device)
+
+def test_global_pool():
+    verify_global_pool(1, 1024, 7, 7, 'avg')
+    verify_global_pool(4, 1024, 7, 7, 'avg')
+    verify_global_pool(1, 1024, 7, 7, 'max')
+    verify_global_pool(4, 1024, 7, 7, 'max')
+
+
+if __name__ == "__main__":
+    test_pool()
+    test_global_pool()
diff --git a/topi/tests/python_cpp/test_topi_reduce.py b/topi/tests/python_cpp/test_topi_reduce.py
new file mode 100644
index 000000000000..adfe18ba4ef9
--- /dev/null
+++ b/topi/tests/python_cpp/test_topi_reduce.py
@@ -0,0 +1,120 @@
+"""Test code for reduce."""
+import os
+import numpy as np
+import tvm
+import topi
+
+def _my_npy_argmax(arr, axis, keepdims):
+    if not keepdims:
+        return arr.argmax(axis=axis)
+    else:
+        if axis is not None:
+            out_shape = list(arr.shape)
+            out_shape[axis] = 1
+        else:
+            out_shape = [1 for _ in range(len(arr.shape))]
+        return arr.argmax(axis=axis).reshape(out_shape)
+
+
+def _my_npy_argmin(arr, axis, keepdims):
+    if not keepdims:
+        return arr.argmin(axis=axis)
+    else:
+        out_shape = list(arr.shape)
+        out_shape[axis] = 1
+        return arr.argmin(axis=axis).reshape(out_shape)
+
+def verify_reduce_map_ele(in_shape, axis, keepdims, type="sum"):
+    # Build the logic and compile the function
+    dat_dtype = "float32"
+    A = tvm.placeholder(shape=in_shape, name="A", dtype=dat_dtype)
+    A1 = topi.cpp.sqrt(topi.cpp.exp(A))
+    out_dtype = "float32"
+    if type == "sum":
+        B = topi.cpp.sum(A1, axis, keepdims)
+    elif type == "max":
+        B = topi.cpp.max(A1, axis, keepdims)
+    elif type == "min":
+        B = topi.cpp.min(A1, axis, keepdims)
+    elif type == "argmax":
+        B = topi.cpp.argmax(A1, axis, keepdims)
+        out_dtype = "int32"
+    elif type == "argmin":
+        B = topi.cpp.argmin(A1, axis, keepdims)
+        out_dtype = "int32"
+    else:
+        raise NotImplementedError
+
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        target = topi.cpp.TEST_create_target(device)
+        if device == "llvm":
+            s = topi.cpp.generic.default_schedule(target, [B], True)
+        else:
+            s = topi.cpp.cuda.schedule_reduce(target, [B])
+
+        ctx = tvm.context(device, 0)
+        foo = tvm.build(s, [A, B], device, name="sum")
+        # Test
+        in_npy = np.random.uniform(size=in_shape).astype(np.float32)
+        in_npy_map = np.sqrt(np.exp(in_npy)).astype(np.float32)
+        if type == "sum":
+            out_npy = in_npy_map.sum(axis=axis, keepdims=keepdims)
+        elif type == "max":
+            out_npy = in_npy_map.max(axis=axis, keepdims=keepdims)
+        elif type == "min":
+            out_npy = in_npy_map.min(axis=axis, keepdims=keepdims)
+        elif type == "argmax":
+            out_npy = _my_npy_argmax(in_npy_map, axis=axis, keepdims=keepdims)
+        elif type == "argmin":
+            out_npy = _my_npy_argmin(in_npy_map, axis=axis, keepdims=keepdims)
+        else:
+            raise NotImplementedError
+        data_tvm = tvm.nd.array(in_npy, ctx=ctx)
+        out_tvm = tvm.nd.empty(shape=out_npy.shape, ctx=ctx, dtype=out_dtype)
+        for _ in range(1):
+            foo(data_tvm, out_tvm)
+        np.testing.assert_allclose(out_tvm.asnumpy(), out_npy, 1E-3, 1E-3)
+    for device in ["cuda", "opencl", "metal", "llvm", "rocm"]:
+        check_device(device)
+
+
+def test_reduce_map():
+    verify_reduce_map_ele(in_shape=(128, 24, 128, 24),
+                        axis=(1, 2, 3),
+                        keepdims=True,
+                        type="sum")
+    verify_reduce_map_ele(in_shape=(128, 24 * 128 * 24),
+                        axis=(1,),
+                        keepdims=False,
+                        type="max")
+    verify_reduce_map_ele(in_shape=(32, 128, 24),
+                        axis=None,
+                        keepdims=True,
+                        type="sum")
+    verify_reduce_map_ele(in_shape=(128, 24, 128, 24),
+                        axis=(0, 2),
+                        keepdims=False,
+                        type="min")
+    verify_reduce_map_ele(in_shape=(32, 128),
+                          axis=1,
+                          keepdims=True,
+                          type="argmax")
+    verify_reduce_map_ele(in_shape=(32, 24, 32, 24),
+                          axis=2,
+                          keepdims=False,
+                          type="argmin")
+    verify_reduce_map_ele(in_shape=(31, 21, 15),
+                          axis=None,
+                          keepdims=True,
+                          type="argmax")
+    verify_reduce_map_ele(in_shape=(31, 21, 15),
+                          axis=None,
+                          keepdims=False,
+                          type="sum")
+
+if __name__ == "__main__":
+    test_reduce_map()
diff --git a/topi/tests/python_cpp/test_topi_relu.py b/topi/tests/python_cpp/test_topi_relu.py
new file mode 100644
index 000000000000..7322f8925517
--- /dev/null
+++ b/topi/tests/python_cpp/test_topi_relu.py
@@ -0,0 +1,62 @@
+"""Test code for relu activation"""
+import os
+import numpy as np
+import tvm
+import topi
+from topi.util import get_const_tuple
+
+def verify_relu(m, n):
+    A = tvm.placeholder((m, n), name='A')
+    B = topi.cpp.nn.relu(A)
+
+    a_np = np.random.uniform(size=get_const_tuple(A.shape)).astype(A.dtype)
+    b_np = a_np * (a_np > 0)
+
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        target = topi.cpp.TEST_create_target(device)
+        if device == "llvm":
+            s = topi.cpp.generic.schedule_injective(target, [B])
+        else:
+            s = topi.cpp.cuda.schedule_injective(target, [B])
+        ctx = tvm.context(device, 0)
+        a = tvm.nd.array(a_np, ctx)
+        b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
+        foo = tvm.build(s, [A, B], device, name="relu")
+        foo(a, b)
+        np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+    for device in ['cuda', 'opencl', 'metal', 'rocm']:
+        check_device(device)
+
+
+def verify_leaky_relu(m, alpha):
+    A = tvm.placeholder((m,), name='A')
+    B = topi.cpp.nn.leaky_relu(A, alpha)
+    device = "llvm"
+    target = topi.cpp.TEST_create_target(device)
+    s = topi.cpp.generic.schedule_injective(target, [B])
+
+    a_np = np.random.uniform(size=get_const_tuple(A.shape)).astype(A.dtype)
+    b_np = a_np * (a_np > 0) + a_np * (a_np < 0) * alpha
+    ctx = tvm.cpu(0)
+    a = tvm.nd.array(a_np, ctx)
+    b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
+    foo = tvm.build(s, [A, B], device, name="leaky_relu")
+    foo(a, b)
+    np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+
+def test_relu():
+    verify_relu(10, 128)
+
+def test_leaky_relu():
+    verify_leaky_relu(100, 0.1)
+
+
+if __name__ == "__main__":
+    test_relu()
+    test_leaky_relu()
diff --git a/topi/tests/python_cpp/test_topi_softmax.py b/topi/tests/python_cpp/test_topi_softmax.py
new file mode 100644
index 000000000000..2c2f62c18f20
--- /dev/null
+++ b/topi/tests/python_cpp/test_topi_softmax.py
@@ -0,0 +1,81 @@
+"""Test code for softmax"""
+import os
+import numpy as np
+import tvm
+import topi
+import logging
+from topi.util import get_const_tuple
+
+def verify_softmax(m, n):
+    A = tvm.placeholder((m, n), name='A')
+    B = topi.cpp.nn.softmax(A)
+    # confirm lower works
+    s = tvm.create_schedule([B.op])
+    tvm.lower(s, [A, B], simple_mode=True)
+
+    a_np = np.random.uniform(size=get_const_tuple(A.shape)).astype(A.dtype)
+    b_np = topi.testing.softmax_python(a_np)
+
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        target = topi.cpp.TEST_create_target(device)
+        if device == "llvm":
+            s = topi.cpp.generic.default_schedule(target, [B], False)
+        else:
+            s = topi.cpp.cuda.schedule_softmax(target, [B])
+        ctx = tvm.context(device, 0)
+        a = tvm.nd.array(a_np, ctx)
+        b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
+        foo = tvm.build(s, [A, B], device, name="softmax")
+        foo(a, b)
+        np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+    for device in ['cuda', 'opencl', 'metal', 'rocm']:
+        check_device(device)
+
+def test_softmax():
+    verify_softmax(32, 10)
+    verify_softmax(3, 4)
+
+
+def verify_log_softmax(m, n):
+    A = tvm.placeholder((m, n), name='A')
+    B = topi.cpp.nn.log_softmax(A)
+    # confirm lower works
+    s = tvm.create_schedule([B.op])
+    tvm.lower(s, [A, B], simple_mode=True)
+    a_np = np.random.uniform(size=get_const_tuple(A.shape)).astype(A.dtype)
+    b_np = topi.testing.log_softmax_python(a_np)
+
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        target = topi.cpp.TEST_create_target(device)
+        if device == "llvm":
+            s = topi.cpp.generic.default_schedule(target, [B], False)
+        else:
+            s = topi.cpp.cuda.schedule_softmax(target, [B])
+        ctx = tvm.context(device, 0)
+        a = tvm.nd.array(a_np, ctx)
+        b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
+        foo = tvm.build(s, [A, B], device, name="log_softmax")
+        foo(a, b)
+        np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+    for device in ["cuda", "opencl", "metal", "rocm"]:
+        check_device(device)
+
+
+def test_log_softmax():
+    verify_log_softmax(32, 10)
+    verify_log_softmax(3, 4)
+
+if __name__ == "__main__":
+    logging.basicConfig(level=logging.DEBUG)
+    test_softmax()
+    test_log_softmax()
diff --git a/topi/tests/python_cpp/test_topi_transform.py b/topi/tests/python_cpp/test_topi_transform.py
new file mode 100644
index 000000000000..68fad8dae707
--- /dev/null
+++ b/topi/tests/python_cpp/test_topi_transform.py
@@ -0,0 +1,216 @@
+"""Test code for broadcasting operators."""
+import numpy as np
+import tvm
+import topi
+
+def verify_expand_dims(in_shape, out_shape, axis, num_newaxis):
+    A = tvm.placeholder(shape=in_shape, name="A")
+    B = topi.cpp.expand_dims(A, axis, num_newaxis)
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        target = topi.cpp.TEST_create_target(device)
+        if device == "llvm":
+            s = topi.cpp.generic.schedule_injective(target, [B])
+        else:
+            s = topi.cpp.cuda.schedule_injective(target, [B])
+        ctx = tvm.context(device, 0)
+        foo = tvm.build(s, [A, B], device, name="expand_dims")
+        data_npy = np.random.uniform(size=in_shape).astype(A.dtype)
+        out_npy = data_npy.reshape(out_shape)
+        data_nd = tvm.nd.array(data_npy, ctx)
+        out_nd = tvm.nd.array(np.empty(out_shape).astype(B.dtype), ctx)
+        foo(data_nd, out_nd)
+        np.testing.assert_allclose(out_nd.asnumpy(), out_npy)
+
+    for device in ["llvm", "nvptx", "cuda", "opencl", "metal", "rocm"]:
+        check_device(device)
+
+
+def verify_tranpose(in_shape, axes):
+    A = tvm.placeholder(shape=in_shape, name="A")
+    B = topi.cpp.transpose(A, axes)
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        target = topi.cpp.TEST_create_target(device)
+        if device == "llvm":
+            s = topi.cpp.generic.schedule_injective(target, [B])
+        else:
+            s = topi.cpp.cuda.schedule_injective(target, [B])
+        ctx = tvm.context(device, 0)
+        foo = tvm.build(s, [A, B], device, name="tranpose")
+        data_npy = np.arange(np.prod(in_shape)).reshape(in_shape).astype(A.dtype)
+        out_npy = data_npy.transpose(axes)
+        data_nd = tvm.nd.array(data_npy, ctx)
+        out_nd = tvm.nd.empty(out_npy.shape, ctx=ctx, dtype=B.dtype)
+        foo(data_nd, out_nd)
+        np.testing.assert_allclose(out_nd.asnumpy(), out_npy)
+
+    for device in ["llvm", "nvptx", "cuda", "opencl", "metal", "rocm"]:
+        check_device(device)
+
+
+def verify_reshape(src_shape, dst_shape):
+    A = tvm.placeholder(shape=src_shape, name="A")
+    B = topi.cpp.reshape(A, dst_shape)
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        target = topi.cpp.TEST_create_target(device)
+        if device == "llvm":
+            s = topi.cpp.generic.schedule_injective(target, [B])
+        else:
+            s = topi.cpp.cuda.schedule_injective(target, [B])
+        ctx = tvm.context(device, 0)
+        foo = tvm.build(s, [A, B], device, name="reshape")
+        data_npy = np.random.normal(size=src_shape).astype(A.dtype)
+        out_npy = np.reshape(data_npy, newshape=dst_shape)
+        data_nd = tvm.nd.array(data_npy, ctx)
+        out_nd = tvm.nd.empty(dst_shape, ctx=ctx, dtype=B.dtype)
+        foo(data_nd, out_nd)
+        np.testing.assert_allclose(out_nd.asnumpy(), out_npy)
+
+    for device in ["llvm", "nvptx", "cuda", "opencl", "metal", "rocm"]:
+        check_device(device)
+
+
+def verify_squeeze(src_shape, axis):
+    A = tvm.placeholder(shape=src_shape, name="A")
+    B = topi.cpp.squeeze(A, axis)
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        target = topi.cpp.TEST_create_target(device)
+        if device == "llvm":
+            s = topi.cpp.generic.schedule_injective(target, [B])
+        else:
+            s = topi.cpp.cuda.schedule_injective(target, [B])
+        ctx = tvm.context(device, 0)
+        foo = tvm.build(s, [A, B], device, name="squeeze")
+        data_npy = np.random.normal(size=src_shape).astype(A.dtype)
+        out_npy = np.squeeze(data_npy, axis=axis)
+        data_nd = tvm.nd.array(data_npy, ctx)
+        if out_npy.shape == ():
+            out_nd_shape = (1,)
+        else:
+            out_nd_shape = out_npy.shape
+        out_nd = tvm.nd.empty(out_nd_shape, ctx=ctx, dtype=B.dtype)
+        foo(data_nd, out_nd)
+        np.testing.assert_allclose(out_nd.asnumpy(), out_npy)
+
+    for device in ["llvm", "nvptx", "cuda", "opencl", "metal", "rocm"]:
+        check_device(device)
+
+def verify_concatenate(shapes, axis):
+    tensor_l = []
+    for i, shape in enumerate(shapes):
+        tensor_l.append(tvm.placeholder(shape, name="A" + str(i)))
+    out_tensor = topi.cpp.concatenate(tensor_l, axis)
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        target = topi.cpp.TEST_create_target(device)
+        if device == "llvm":
+            s = topi.cpp.generic.schedule_injective(target, [out_tensor])
+        else:
+            s = topi.cpp.cuda.schedule_injective(target, [out_tensor])
+        ctx = tvm.context(device, 0)
+        foo = tvm.build(s, tensor_l + [out_tensor], device, name="concatenate")
+        data_npys = [np.random.normal(size=shape).astype(tensor_l[0].dtype) for shape in shapes]
+        out_npy = np.concatenate(data_npys, axis=axis)
+        data_nds = [tvm.nd.array(data_npy, ctx) for data_npy in data_npys]
+        out_nd = tvm.nd.empty(out_npy.shape, ctx=ctx, dtype=out_tensor.dtype)
+        foo(*(data_nds + [out_nd]))
+        np.testing.assert_allclose(out_nd.asnumpy(), out_npy)
+
+    for device in ["llvm", "nvptx", "cuda", "opencl", "metal", "rocm"]:
+        check_device(device)
+
+
+def verify_split(src_shape, indices_or_sections, axis):
+    A = tvm.placeholder(shape=src_shape, name="A")
+    tensor_l = topi.cpp.split(A, indices_or_sections, axis)
+    tensor_l = list(tensor_l)
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        target = topi.cpp.TEST_create_target(device)
+        if device == "llvm":
+            s = topi.cpp.generic.schedule_injective(target, tensor_l)
+        else:
+            s = topi.cpp.cuda.schedule_injective(target, tensor_l)
+        ctx = tvm.context(device, 0)
+        foo = tvm.build(s, [A] + tensor_l, device, name="split")
+        data_npy = np.random.normal(size=src_shape).astype(A.dtype)
+        out_npys = np.split(data_npy, indices_or_sections, axis=axis)
+        data_nd = tvm.nd.array(data_npy, ctx)
+        out_nds = [tvm.nd.empty(out_npy.shape, ctx=ctx, dtype=tensor_l[0].dtype) for out_npy in out_npys]
+        foo(*([data_nd] + out_nds))
+        for out_nd, out_npy in zip(out_nds, out_npys):
+            np.testing.assert_allclose(out_nd.asnumpy(), out_npy)
+
+    for device in ["llvm", "nvptx", "cuda", "opencl", "metal", "rocm"]:
+        check_device(device)
+
+
+def test_expand_dims():
+    verify_expand_dims((3, 10), (3, 10, 1, 1), 2, 2)
+    verify_expand_dims((3, 10), (1, 3, 10), -3, 1)
+
+
+def test_tranpose():
+    verify_tranpose((3, 10, 2), (1, 0, 2))
+    verify_tranpose((3, 10, 5), (2, 0, 1))
+    verify_tranpose((3, 10), None)
+
+
+def test_reshape():
+    verify_reshape((1, 2, 3, 4), (2, 3, 4))
+    verify_reshape((4, 2, 3, 4), (2, 4, 12))
+    verify_reshape((4, 2, 3, 4), (2, 48))
+    verify_reshape((16, ), (2, 2, 2, 2))
+
+
+def test_squeeze():
+    verify_squeeze((1, 2, 3, 4), 0)
+    verify_squeeze((1, 2, 1, 4), None)
+    verify_squeeze((1, 1, 1, 4), (1, 2))
+    verify_squeeze((1, 1, 1, 1), None)
+
+
+def test_concatenate():
+    verify_concatenate([(2,), (2,), (2,)], 0)
+    verify_concatenate([(2, 3, 4), (2, 2, 4), (2, 5, 4)], 1)
+    verify_concatenate([(1, 2, 4), (1, 2, 3), (1, 2, 7), (1, 2, 8), (1, 2, 1)], -1)
+    verify_concatenate([(5, 6, 7, 3),
+                        (16, 6, 7, 3),
+                        (12, 6, 7, 3),
+                        (8, 6, 7, 3),
+                        (2, 6, 7, 3)], 0)
+
+
+def test_split():
+    verify_split((2, 12, 3), 3, 1)
+    verify_split((2, 12, 3), [2, 4], 1)
+    verify_split((10, 12, 24), [5, 7, 9], -1)
+
+if __name__ == "__main__":
+    test_concatenate()
+    test_tranpose()
+    test_expand_dims()
+    test_reshape()
+    test_squeeze()
+    test_split()

From a5dbf4a0f22be5bc0fd4658d1a222a2fb80f6e55 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 28 Jan 2018 09:55:22 -0800
Subject: [PATCH 117/949] Fix Jenkins pipeline (#835)

---
 Jenkinsfile | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/Jenkinsfile b/Jenkinsfile
index 85d38d3dfcad..4fc2285f507c 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -4,11 +4,10 @@
 // See documents at https://jenkins.io/doc/book/pipeline/jenkinsfile/
 
 // tvm libraries
-topi_lib = "lib/libtopi.so"
-tvm_runtime = "lib/libtvm_runtime.so, config.mk, "
-tvm_lib = "lib/libtvm.so, " + tvm_runtime + topi_lib
+tvm_runtime = "lib/libtvm_runtime.so, config.mk"
+tvm_lib = "lib/libtvm.so, " + tvm_runtime
 // LLVM upstream lib
-tvm_multilib = "lib/libtvm_llvm40.so, lib/libtvm_llvm50.so, lib/libtvm_llvm60.so, " + tvm_runtime + topi_lib
+tvm_multilib = "lib/libtvm_llvm40.so, lib/libtvm_llvm50.so, lib/libtvm_llvm60.so, " + tvm_runtime
 
 // command to start a docker container
 docker_run = 'tests/ci_build/ci_build.sh'

From 3a91764ec919b7a0034e104c7fac40ed5a840d12 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 28 Jan 2018 14:43:17 -0800
Subject: [PATCH 118/949] [TOPI] Fix compiler warning in topi cpp (#837)

---
 topi/include/topi/detail/broadcast.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/topi/include/topi/detail/broadcast.h b/topi/include/topi/detail/broadcast.h
index ba7193cd0647..d76aa7d0ec56 100644
--- a/topi/include/topi/detail/broadcast.h
+++ b/topi/include/topi/detail/broadcast.h
@@ -55,7 +55,7 @@ inline BroadcastHelper BroadcastShape(const tvm::Array<tvm::Expr>& shape1,
   auto max_size = std::max(s1_size, s2_size);
   auto& shape = (s1_size > s2_size) ? shape1 : shape2;
   auto& vars = (s1_size > s2_size) ? bh.vars1 : bh.vars2;
-  for (i = i; i <= max_size; ++i) {
+  for (; i <= max_size; ++i) {
     bh.all_vars.push_front(tvm::Var());
     bh.common_shape.push_front(shape[max_size - i]);
     vars.push_front(bh.all_vars[0]);

From f04454fc8759279ffe90307e6259a721cf53dcb9 Mon Sep 17 00:00:00 2001
From: Clouds <Clouds.Flowing@gmail.com>
Date: Tue, 30 Jan 2018 01:31:42 +0800
Subject: [PATCH 119/949] fix opengl runtime to use OpenGL/gl3.h for macOS
 (#833)

* fix opengl to OpenGL/gl3.h for APPLE

* use glfw3 to include gl.h header
---
 src/runtime/opengl/opengl_common.h      | 4 +++-
 src/runtime/opengl/opengl_device_api.cc | 2 ++
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/runtime/opengl/opengl_common.h b/src/runtime/opengl/opengl_common.h
index 661c987e4b3c..0d247edfade4 100644
--- a/src/runtime/opengl/opengl_common.h
+++ b/src/runtime/opengl/opengl_common.h
@@ -11,7 +11,9 @@
 #include <tvm/runtime/packed_func.h>
 #include <tvm/runtime/device_api.h>
 #include <dmlc/logging.h>
-#include <GL/gl.h>
+#if defined(__APPLE__)
+#define GLFW_INCLUDE_GLCOREARB
+#endif
 #include <GLFW/glfw3.h>
 #include <string>
 #include <tuple>
diff --git a/src/runtime/opengl/opengl_device_api.cc b/src/runtime/opengl/opengl_device_api.cc
index df2947db6255..5f629fcf1a1f 100644
--- a/src/runtime/opengl/opengl_device_api.cc
+++ b/src/runtime/opengl/opengl_device_api.cc
@@ -27,10 +27,12 @@ static const char* GLGetErrorString(GLenum error) {
       return "GL_INVALID_VALUE";
     case GL_INVALID_OPERATION:
       return "GL_INVALID_OPERATION";
+#if !defined(__APPLE__)
     case GL_STACK_OVERFLOW:
       return "GL_STACK_OVERFLOW";
     case GL_STACK_UNDERFLOW:
       return "GL_STACK_UNDERFLOW";
+#endif
     case GL_OUT_OF_MEMORY:
       return "GL_OUT_OF_MEMORY";
     default:

From 68b9aa9a05b5914a7c5cf2fce9fda13f4ea6f503 Mon Sep 17 00:00:00 2001
From: ZhiWei Zhang <971749411@qq.com>
Date: Tue, 30 Jan 2018 12:49:28 +0800
Subject: [PATCH 120/949] fixed #841 (#845)

* Update workspace_pool.cc

* Update workspace_pool.cc
---
 src/runtime/workspace_pool.cc | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/runtime/workspace_pool.cc b/src/runtime/workspace_pool.cc
index 24035faedaa7..c903a8621206 100644
--- a/src/runtime/workspace_pool.cc
+++ b/src/runtime/workspace_pool.cc
@@ -28,7 +28,10 @@ class WorkspacePool::Pool {
     nbytes = (nbytes + (kWorkspacePageSize - 1)) / kWorkspacePageSize * kWorkspacePageSize;
     if (nbytes == 0) nbytes = kWorkspacePageSize;
     Entry e;
-    TVMType type = {.code = kDLUInt, .bits = 8, .lanes = 1};
+    TVMType type;
+    type.code = kDLUInt;
+    type.bits = 8;
+    type.lanes = 1;
     if (free_list_.size() == 2) {
       e = free_list_.back();
       free_list_.pop_back();

From 5379b1c7975cd5fa7eff50425de59718a6b9c446 Mon Sep 17 00:00:00 2001
From: xqdan <danxiaoqiang@126.com>
Date: Tue, 30 Jan 2018 22:19:25 -0800
Subject: [PATCH 121/949] [PASS] Improve storage rewrite(#846) (#847)

* fix #802, create cache based on sugar tensor

* [Pass] Improve storage rewrite

* fix ci

* fix comment

* fix comment
---
 src/pass/storage_rewrite.cc                   |  5 +-
 .../unittest/test_pass_storage_rewrite.py     | 87 ++++++++++++++++++-
 2 files changed, 90 insertions(+), 2 deletions(-)

diff --git a/src/pass/storage_rewrite.cc b/src/pass/storage_rewrite.cc
index f052a9b05b90..00674153bb35 100644
--- a/src/pass/storage_rewrite.cc
+++ b/src/pass/storage_rewrite.cc
@@ -681,6 +681,8 @@ class StoragePlanRewriter : public IRMutator {
           StorageEntry* dst_entry = nullptr;
           // inplace detection
           if (detect_inplace) {
+            // only one inplace var for s.stmt
+            bool inplace_found = false;
             for (const Variable* src : it->second.kill) {
               if (!inplace_flag.count(src) && alloc_map_.count(src)) {
                 InplaceOpVerifier visitor;
@@ -693,10 +695,11 @@ class StoragePlanRewriter : public IRMutator {
                       ae.alloc->constant_allocation_size() *
                       ae.alloc->type.bits() *
                       ae.alloc->type.lanes());
-                  if (src_entry->const_nbits == const_nbits) {
+                  if (src_entry->const_nbits == const_nbits && !inplace_found) {
                     // successfully inplace
                     dst_entry = src_entry;
                     inplace_flag.insert(src);
+                    inplace_found = true;
                   }
                 }
               }
diff --git a/tests/python/unittest/test_pass_storage_rewrite.py b/tests/python/unittest/test_pass_storage_rewrite.py
index 6b6ff71810bb..c9999f312300 100644
--- a/tests/python/unittest/test_pass_storage_rewrite.py
+++ b/tests/python/unittest/test_pass_storage_rewrite.py
@@ -218,7 +218,7 @@ def test_parallel_alloc():
 
 def test_inplace_rule2():
     #Test Buffer
-    scope_tb = "local_TB"
+    scope_tb = "local_TB2"
     @tvm.register_func("tvm.info.mem.%s" % scope_tb)
     def mem_info_inp_buffer():
         return tvm.make.node("MemoryInfo",
@@ -258,6 +258,90 @@ def verify(n):
     tvm.ir_pass.PostOrderVisit(stmt, verify)
     assert num_alloc[0] == 2
 
+def test_inplace_rule3():
+    #Test Buffer
+    scope_tb = "local_TB3"
+    @tvm.register_func("tvm.info.mem.%s" % scope_tb)
+    def mem_info_inp_buffer():
+        return tvm.make.node("MemoryInfo",
+                        unit_bits= 16,
+                        max_simd_bits=32,
+                        max_num_bits=1024*1024*1024,
+                        head_address=None)
+    m = 10
+    B0 = tvm.placeholder((m,), name='B0')
+    B1 = tvm.placeholder((m,), name='B1')
+    B2 = tvm.placeholder((m,), name='B2')
+    B3 = tvm.placeholder((m,), name='B3')
+    B4 = tvm.placeholder((m,), name='B4')
+    B5 = tvm.placeholder((m,), name='B5')
+
+    B6 = tvm.compute((m,), lambda i: B1[i] * B5[i], name='B6')
+    B7 = tvm.compute((m,), lambda i: B2[i] * B4[i], name='B7')
+    B8 = tvm.compute((m,), lambda i: B6[i] - B7[i], name='B8')
+
+    B9 = tvm.compute((m,), lambda i: B2[i] * B3[i], name='B9')
+    B10 = tvm.compute((m,), lambda i: B0[i] * B5[i], name='B10')
+    B11 = tvm.compute((m,), lambda i: B9[i] - B10[i], name='B11')
+
+    B12 = tvm.compute((m,), lambda i: B0[i] * B4[i], name='B12')
+    B13 = tvm.compute((m,), lambda i: B1[i] * B3[i], name='B13')
+    B14 = tvm.compute((m,), lambda i: B12[i] - B13[i], name='B14')
+
+    B = tvm.compute((m,), lambda i: B8[i] * B11[i] + B14[i], name='B')
+    s = tvm.create_schedule(B.op)
+
+    B1L = s.cache_read(B1, scope_tb, [B6, B13])
+    B5L = s.cache_read(B5, scope_tb, [B6, B10])
+    B2L = s.cache_read(B2, scope_tb, [B7, B9])
+    B4L = s.cache_read(B4, scope_tb, [B7, B12])
+    B3L = s.cache_read(B3, scope_tb, [B9, B13])
+    B0L = s.cache_read(B0, scope_tb, [B10, B12])   
+
+    B8L = s.cache_write(B8, scope_tb)
+    B11L = s.cache_write(B11, scope_tb)
+    B14L = s.cache_write(B14, scope_tb)
+    B6L = s.cache_write(B6, scope_tb)
+    B7L = s.cache_write(B7, scope_tb)
+    B9L = s.cache_write(B9, scope_tb)
+    B10L = s.cache_write(B10, scope_tb)
+    B12L = s.cache_write(B12, scope_tb)
+    B13L = s.cache_write(B13, scope_tb)
+	
+    s[B12].compute_inline()
+    s[B13].compute_inline()
+    s[B8].compute_inline()
+    s[B11].compute_inline()
+    s[B14].compute_inline()
+    s[B6].compute_inline()
+    s[B7].compute_inline()
+    s[B9].compute_inline()
+    s[B10].compute_inline()
+   
+    s = s.normalize() 
+    bounds = tvm.schedule.InferBound(s)
+    assert isinstance(bounds, tvm.container.Map)
+    stmt = tvm.schedule.ScheduleOps(s, bounds)
+    
+    B0a = tvm.decl_buffer(B0.shape, B0.dtype, name='B0')
+    B1a = tvm.decl_buffer(B1.shape, B1.dtype, name='B1')
+    B2a = tvm.decl_buffer(B2.shape, B2.dtype, name='B2')
+    B3a = tvm.decl_buffer(B3.shape, B3.dtype, name='B3')
+    B4a = tvm.decl_buffer(B4.shape, B4.dtype, name='B4')
+    B5a = tvm.decl_buffer(B5.shape, B5.dtype, name='B5')
+
+    Bb = tvm.decl_buffer(B.shape, B.dtype, name='B')
+    stmt = tvm.ir_pass.StorageFlatten(stmt, {B0: B0a, B1: B1a, B2: B2a, B3: B2a, B4: B4a, B5: B5a, B: Bb}, 64)
+    stmt = tvm.ir_pass.CanonicalSimplify(stmt)
+    stmt = tvm.ir_pass.Simplify(stmt)
+    stmt = tvm.ir_pass.StorageRewrite(stmt)
+    # verify only have one allocations.
+    # verify inplace folding works
+    def verify(n):
+        if isinstance(n, tvm.stmt.Allocate):
+            assert n.extents[0].value == 70
+    tvm.ir_pass.PostOrderVisit(stmt, verify)
+
 if __name__ == "__main__":
     test_alloc_seq()
     test_alloc_different_dtypes()
@@ -267,3 +351,4 @@ def verify(n):
     test_storage_combine()
     test_storage_share_gpu()
     test_inplace_rule2()
+    test_inplace_rule3()

From 4252d4e661eb7c349be61b31d2c28bca2896f8a1 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 31 Jan 2018 10:20:57 -0800
Subject: [PATCH 122/949] [TOPI] Fix cpp library dependency on MAC (#852)

---
 Makefile                                | 29 +++++++++++--------------
 python/tvm/_ffi/libinfo.py              |  6 ++---
 topi/include/topi/detail/extern.h       |  4 ++--
 topi/tests/python_cpp/test_topi_clip.py |  1 -
 4 files changed, 17 insertions(+), 23 deletions(-)

diff --git a/Makefile b/Makefile
index 332d6ebe86f2..2361b505fbf3 100644
--- a/Makefile
+++ b/Makefile
@@ -27,6 +27,7 @@ LLVM_CFLAGS= -fno-rtti -DDMLC_ENABLE_RTTI=0 -DDMLC_USE_FOPEN64=0
 LDFLAGS = -pthread -lm -ldl
 INCLUDE_FLAGS = -Iinclude -I$(DLPACK_PATH)/include -I$(DMLC_CORE_PATH)/include -IHalideIR/src -Itopi/include
 CFLAGS = -std=c++11 -Wall -O2 $(INCLUDE_FLAGS) -fPIC
+PKG_LDFLAGS =
 FRAMEWORKS =
 OBJCFLAGS = -fno-objc-arc
 EMCC_FLAGS= -std=c++11 -DDMLC_LOG_STACK_TRACE=0\
@@ -80,6 +81,10 @@ ALL_DEP = $(CC_OBJ) $(CONTRIB_OBJ) $(LIB_HALIDEIR)
 RUNTIME_DEP = $(RUNTIME_OBJ)
 TOPI_DEP = $(TOPI_OBJ)
 
+ifeq ($(UNAME_S), Darwin)
+	PKG_LDFLAGS += -undefined dynamic_lookup
+endif
+
 # Dependency specific rules
 ifdef CUDA_PATH
 	NVCC=$(CUDA_PATH)/bin/nvcc
@@ -201,7 +206,10 @@ else
 	JVM_PKG_PROFILE := $(JVM_PKG_PROFILE)-cpu
 endif
 
-BUILD_TARGETS ?= lib/libtvm.$(SHARED_LIBRARY_SUFFIX) lib/libtvm_runtime.$(SHARED_LIBRARY_SUFFIX) lib/libtvm_topi.$(SHARED_LIBRARY_SUFFIX)
+BUILD_TARGETS ?= lib/libtvm.$(SHARED_LIBRARY_SUFFIX) \
+	lib/libtvm_runtime.$(SHARED_LIBRARY_SUFFIX) \
+  lib/libtvm_topi.$(SHARED_LIBRARY_SUFFIX)
+
 all: ${BUILD_TARGETS}
 runtime: lib/libtvm_runtime.$(SHARED_LIBRARY_SUFFIX)
 web: lib/libtvm_web_runtime.js lib/libtvm_web_runtime.bc
@@ -235,29 +243,18 @@ build/src/%.o: topi/src/%.cc
 	$(CXX) $(CFLAGS) -MM -MT build/src/$*.o $< >build/src/$*.d
 	$(CXX) -c $(CFLAGS) -c $< -o $@
 
-lib/libtvm.dylib: $(ALL_DEP) $(RUNTIME_DEP)
+lib/libtvm.${SHARED_LIBRARY_SUFFIX}: $(ALL_DEP) $(RUNTIME_DEP)
 	@mkdir -p $(@D)
 	$(CXX) $(CFLAGS) $(FRAMEWORKS) -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
 
-lib/libtvm_topi.dylib: lib/libtvm.so $(TOPI_DEP)
-	@mkdir -p $(@D)
-	$(CXX) $(CFLAGS) $(FRAMEWORKS) -L./lib -ltvm -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
-
-lib/libtvm_runtime.dylib: $(RUNTIME_DEP)
+lib/libtvm_topi.${SHARED_LIBRARY_SUFFIX}: $(TOPI_DEP)
 	@mkdir -p $(@D)
-	$(CXX) $(CFLAGS) $(FRAMEWORKS) -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
+	$(CXX) $(CFLAGS) $(FRAMEWORKS) -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS) $(PKG_LDFLAGS)
 
-lib/libtvm.so: $(ALL_DEP) $(RUNTIME_DEP)
+lib/libtvm_runtime.${SHARED_LIBRARY_SUFFIX}: $(RUNTIME_DEP)
 	@mkdir -p $(@D)
 	$(CXX) $(CFLAGS) $(FRAMEWORKS) -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
 
-lib/libtvm_topi.so: lib/libtvm.so $(TOPI_DEP)
-	@mkdir -p $(@D)
-	$(CXX) $(CFLAGS) $(FRAMEWORKS) -L./lib -ltvm -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
-
-lib/libtvm_runtime.so: $(RUNTIME_DEP)
-	@mkdir -p $(@D)
-	$(CXX) $(CFLAGS) $(FRAMEWORKS) -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
 
 lib/libtvm_web_runtime.bc: web/web_runtime.cc
 	@mkdir -p build/web
diff --git a/python/tvm/_ffi/libinfo.py b/python/tvm/_ffi/libinfo.py
index 46d5f536a94e..169756e43cd5 100644
--- a/python/tvm/_ffi/libinfo.py
+++ b/python/tvm/_ffi/libinfo.py
@@ -2,7 +2,7 @@
 from __future__ import absolute_import
 import sys
 import os
-import warnings
+
 
 def find_lib_path(name=None, search_path=None, optional=False):
     """Find dynamic library files.
@@ -91,9 +91,7 @@ def find_lib_path(name=None, search_path=None, optional=False):
                    str('\n'.join(lib_dll_path + runtime_dll_path)))
         if not optional:
             raise RuntimeError(message)
-        else:
-            warnings.warn(message)
-            return None
+        return None
 
     if use_runtime:
         sys.stderr.write("Loading runtime library %s... exec only\n" % lib_found[0])
diff --git a/topi/include/topi/detail/extern.h b/topi/include/topi/detail/extern.h
index 12cc65d72530..3c5e09edae01 100644
--- a/topi/include/topi/detail/extern.h
+++ b/topi/include/topi/detail/extern.h
@@ -110,7 +110,7 @@ Expr pack_buffer(Buffer buf) {
     buf->data,
     shape,
     strides,
-    make_const(Int(32), buf->shape.size()),
+    make_const(Int(32), static_cast<int64_t>(buf->shape.size())),
     make_const(buf->dtype, 0),
     buf->elem_offset
   };
@@ -125,7 +125,7 @@ Expr pack_buffer(Buffer buf) {
  * by the arguments to pass to the PackedFunc when called. The first element of the
  * array must be a constant string expression.
  *
- * \return An expression representing the invocation 
+ * \return An expression representing the invocation
  */
 Expr call_packed(Array<Expr> args) {
   return tvm::ir::Call::make(Int(32), tvm::ir::intrinsic::tvm_call_packed,
diff --git a/topi/tests/python_cpp/test_topi_clip.py b/topi/tests/python_cpp/test_topi_clip.py
index a059464375ac..fe00408642f5 100644
--- a/topi/tests/python_cpp/test_topi_clip.py
+++ b/topi/tests/python_cpp/test_topi_clip.py
@@ -4,7 +4,6 @@
 import topi
 from topi.util import get_const_tuple
 from tvm.contrib.pickle_memoize import memoize
-from util import make_vector
 
 
 def verify_clip(N, a_min, a_max, dtype):

From 9e675779974dc6985ce31bd66f171a8e5db5668c Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 31 Jan 2018 11:58:43 -0800
Subject: [PATCH 123/949] [RELEASE] Release note for 0.2 (#853)

---
 NEWS.md                    | 42 ++++++++++++++++++++++++++++++++++++--
 python/tvm/_ffi/libinfo.py |  2 +-
 2 files changed, 41 insertions(+), 3 deletions(-)

diff --git a/NEWS.md b/NEWS.md
index 6bc97b163ab1..dd165b75c0e0 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -3,11 +3,49 @@ TVM Change Log
 
 This file records the changes in TVM library in reverse chronological order.
 
+## On-going version
 
-## On onging verison
+Refer to the Roadmap issue for complete list on on-going version features.
+If you check in something that is not reflected in Roadmap issue, please reply
+to that issue so it can get added.
+
+## 0.2
+
+This release comes with a complete set of TOPI support for NNVM compiler, which allows compilation of end to end workloads.
+We also make major improvements in supporting new backends: ROCm for AMDGPUs and ARM GPU.
+
+- Backend support
+   - Support LLVM mainline(4.0, 5.0, 6.0)
+   - Support ROCM stack for AMD GPUs
+   - More robust OpenCL support for ARM GPUs
+- Android RPC runtime
+- Multi-threading optimization for ARM
+   - multi-threaded depthwise
+   - multi-threaded conv2d
+- New schedule primitives
+   - storage_align for shared memory alignment
+   - double_buffer
 - UnrollLoop : more robust version of unroll loop, count maximum steps that can be unrolled.
+- Full set of TOPI operators
+   - Introduce tvm.target to specify target options for compilation better.
+   - broadcast/ reduction operators
+   - pooling and global pooling
+   - Generic target support for topi
+   - schedule with external libraries
+- End to end deep learning pipelines for CPU, GPU, ARM GPU
+- Tutorials
+  - How to load compiled module in any language runtime
+  -  How to use java runtime
+- Contrib library: MIOpen, CuDNN
+- Ongoing items that contains functioning pieces
+  - WebGL backend
+  - C++ compiler support
+  - MPS DNN
+  - low bit support, introduced popcount
+
+
+## 0.1
 
-## 0.1rc
 - Language runtime
     - python
     - javascript
diff --git a/python/tvm/_ffi/libinfo.py b/python/tvm/_ffi/libinfo.py
index 169756e43cd5..b17d0041f54d 100644
--- a/python/tvm/_ffi/libinfo.py
+++ b/python/tvm/_ffi/libinfo.py
@@ -100,4 +100,4 @@ def find_lib_path(name=None, search_path=None, optional=False):
 
 
 # current version
-__version__ = "0.1.0"
+__version__ = "0.2.0"

From 208946e3b080fe6c7809a4451a026bbcd0e37f58 Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@cs.washington.edu>
Date: Wed, 31 Jan 2018 15:56:14 -0800
Subject: [PATCH 124/949] copy intrinsic now can include typecast (#855)

---
 src/pass/inject_copy_intrin.cc | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/pass/inject_copy_intrin.cc b/src/pass/inject_copy_intrin.cc
index a7151ed0aeb5..cafcddcb9dde 100644
--- a/src/pass/inject_copy_intrin.cc
+++ b/src/pass/inject_copy_intrin.cc
@@ -51,12 +51,17 @@ class CopyIntrinInjector : public IRMutator {
     const Store* store = body.as<Store>();
     if (store == nullptr) return false;
     const Select* select = store->value.as<Select>();
+    const Cast* cast = store->value.as<Cast>();
     const Load* load = store->value.as<Load>();
 
     // for now only support true condition matching
     if (select != nullptr) {
       load = select->true_value.as<Load>();
     }
+    // cast can be part of the pattern
+    if (cast != nullptr) {
+      load = cast->value.as<Load>();
+    }
     if (load == nullptr) return false;
     if (load->type.lanes() != 1) return false;
     Array<Var> loop_vars;
@@ -114,7 +119,7 @@ class CopyIntrinInjector : public IRMutator {
     Array<Expr> dst_strides(store_strides.begin(), store_strides.begin() + loop_vars.size());
     Buffer dst = BufferNode::make(
         Var(store->buffer_var.node_),
-        load->type,
+        store->value.type(),
         dst_shape,
         dst_strides,
         store_strides[loop_vars.size()],

From 91222e66a532a383fc8d7eb6bb54b6b0bacac2c9 Mon Sep 17 00:00:00 2001
From: alex-weaver <awsweaver@gmail.com>
Date: Thu, 1 Feb 2018 18:03:28 +0000
Subject: [PATCH 125/949] Update HalideIR submodule to include
 TVM_STATIC_IR_FUNCTOR_REGISTER (#857)

* Update HalideIR commit to include TVM_STATIC_IR_FUNCTOR_REGISTER

* Fix HalideIR to point to the right commit

* Add missing using to C++ TOPI nn.h

* Update HalideIR to include compiler error fix

* Fixed error where broadcast_to fails if shape is tuple of IntImm

* Change get_const_int to support int as input
---
 HalideIR                      | 2 +-
 topi/include/topi/nn.h        | 1 +
 topi/python/topi/broadcast.py | 3 ++-
 topi/python/topi/util.py      | 4 +++-
 4 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/HalideIR b/HalideIR
index aadbf02d6bd7..87b089a0ba20 160000
--- a/HalideIR
+++ b/HalideIR
@@ -1 +1 @@
-Subproject commit aadbf02d6bd7a545edbf6652494a7b07a97a06c1
+Subproject commit 87b089a0ba20f2e8257038ee9211d6816088ce95
diff --git a/topi/include/topi/nn.h b/topi/include/topi/nn.h
index 5bb4ebf2ac8f..f5c6b3d2a05f 100644
--- a/topi/include/topi/nn.h
+++ b/topi/include/topi/nn.h
@@ -15,6 +15,7 @@
 #include "tvm/tvm.h"
 
 namespace topi {
+using namespace tvm;
 namespace detail {
 
 template <typename T>
diff --git a/topi/python/topi/broadcast.py b/topi/python/topi/broadcast.py
index f75a65bb969a..cf76fe93b600 100644
--- a/topi/python/topi/broadcast.py
+++ b/topi/python/topi/broadcast.py
@@ -3,7 +3,7 @@
 from __future__ import absolute_import as _abs
 import tvm
 from .import tag
-from .util import get_const_tuple, equal_const_int
+from .util import get_const_tuple, equal_const_int, get_const_int
 
 def _get_bcast_info(original_shape, target_shape):
     """Get the broadcasting info.
@@ -106,6 +106,7 @@ def _bcast_to_arg_eval(data, bcast_info, *indices):
                 indices_tuple.append(0)
         return data[tuple(indices_tuple)]
     original_shape = data.shape
+    shape = [get_const_int(i) for i in shape]
     bcast_info = _get_bcast_info(original_shape=original_shape, target_shape=shape)
     ret = tvm.compute(shape,
                       lambda *indices: _bcast_to_arg_eval(data,
diff --git a/topi/python/topi/util.py b/topi/python/topi/util.py
index 00c8b9d42e2a..a093bfd8a35c 100644
--- a/topi/python/topi/util.py
+++ b/topi/python/topi/util.py
@@ -7,7 +7,7 @@ def get_const_int(expr):
 
     Parameters
     ----------
-    expr : tvm.Expr
+    expr : tvm.Expr or int
         The input expression.
 
     Returns
@@ -15,6 +15,8 @@ def get_const_int(expr):
     out_value : int
         The output.
     """
+    if isinstance(expr, int):
+        return expr
     if not isinstance(expr, (tvm.expr.IntImm, tvm.expr.UIntImm)):
         expr = tvm.ir_pass.Simplify(expr)
     if not isinstance(expr, (tvm.expr.IntImm, tvm.expr.UIntImm)):

From 4a28ef5d4ac1c61c06e1c4240796ff3ff7859989 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 1 Feb 2018 22:24:57 -0800
Subject: [PATCH 126/949] [BACKEND] Vulkan Runtime and SPIRV Codegen (#861)

* [BACKEND] Vulkan Runtime and SPIRV Codegen

* fix doc
---
 CMakeLists.txt                                |  25 +-
 HalideIR                                      |   2 +-
 Makefile                                      |  16 +
 include/tvm/ir_pass.h                         |  12 +
 include/tvm/runtime/c_runtime_api.h           |   2 +-
 python/tvm/__init__.py                        |   3 +-
 python/tvm/_ffi/runtime_ctypes.py             |   2 +
 python/tvm/contrib/spirv.py                   |  43 ++
 python/tvm/ndarray.py                         |  18 +
 python/tvm/target.py                          |   2 +-
 src/codegen/codegen_c.cc                      |   2 +
 src/codegen/codegen_common.h                  |  59 ++
 src/codegen/llvm/codegen_llvm.cc              |  31 +-
 src/codegen/spirv/build_vulkan.cc             |  95 +++
 src/codegen/spirv/codegen_spirv.cc            | 638 ++++++++++++++++
 src/codegen/spirv/codegen_spirv.h             | 133 ++++
 src/codegen/spirv/intrin_rule_spirv.cc        |  50 ++
 src/codegen/spirv/ir_builder.cc               | 548 ++++++++++++++
 src/codegen/spirv/ir_builder.h                | 597 +++++++++++++++
 src/pass/rewrite_unsafe_select.cc             |   4 +-
 src/pass/storage_rewrite.cc                   |  30 +-
 src/runtime/c_runtime_api.cc                  |   1 +
 src/runtime/module.cc                         |   2 +
 src/runtime/rocm/rocm_device_api.cc           |   3 +-
 src/runtime/vulkan/vulkan_common.h            | 284 ++++++++
 src/runtime/vulkan/vulkan_device_api.cc       | 681 ++++++++++++++++++
 src/runtime/vulkan/vulkan_module.cc           | 424 +++++++++++
 src/runtime/vulkan/vulkan_module.h            |  64 ++
 tests/python/integration/test_ewise.py        |  23 +-
 tests/python/integration/test_gemm.py         |   8 +-
 tests/python/integration/test_reduce.py       |  23 +-
 tests/python/integration/test_scan.py         |   5 +-
 tests/python/unittest/test_codegen_device.py  |  24 +-
 tests/python/unittest/test_module_load.py     |   5 +-
 tests/python/unittest/test_runtime_ndarray.py |   1 +
 topi/recipe/gemm/cuda_gemm_square.py          |  10 +-
 topi/tests/python/test_topi_broadcast.py      |  12 +-
 topi/tests/python/test_topi_conv2d_hwcn.py    |   6 +-
 topi/tests/python/test_topi_conv2d_nchw.py    |   6 +-
 .../python/test_topi_conv2d_transpose_nchw.py |   6 +-
 topi/tests/python/test_topi_dense.py          |   6 +-
 .../python/test_topi_depthwise_conv2d.py      |  11 +-
 .../test_topi_depthwise_conv2d_back_input.py  |   5 +-
 .../test_topi_depthwise_conv2d_back_weight.py |   5 +-
 topi/tests/python/test_topi_pooling.py        |  13 +-
 topi/tests/python/test_topi_reduce.py         |   7 +-
 topi/tests/python/test_topi_relu.py           |   7 +-
 topi/tests/python/test_topi_softmax.py        |  13 +-
 topi/tests/python/test_topi_transform.py      |  39 +-
 topi/tests/python/test_topi_upsampling.py     |   6 +-
 50 files changed, 3869 insertions(+), 143 deletions(-)
 create mode 100644 python/tvm/contrib/spirv.py
 create mode 100644 src/codegen/codegen_common.h
 create mode 100644 src/codegen/spirv/build_vulkan.cc
 create mode 100644 src/codegen/spirv/codegen_spirv.cc
 create mode 100644 src/codegen/spirv/codegen_spirv.h
 create mode 100644 src/codegen/spirv/intrin_rule_spirv.cc
 create mode 100644 src/codegen/spirv/ir_builder.cc
 create mode 100644 src/codegen/spirv/ir_builder.h
 create mode 100644 src/runtime/vulkan/vulkan_common.h
 create mode 100644 src/runtime/vulkan/vulkan_device_api.cc
 create mode 100644 src/runtime/vulkan/vulkan_module.cc
 create mode 100644 src/runtime/vulkan/vulkan_module.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 62a564c2b656..60826a304491 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.5)
+cmake_minimum_required(VERSION 3.7)
 project(tvm C CXX)
 
 if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/build/private/local_config.cmake)
@@ -22,6 +22,7 @@ endif()
 
 tvm_option(USE_CUDA "Build with CUDA" OFF)
 tvm_option(USE_OPENCL "Build with OpenCL" OFF)
+tvm_option(USE_VULKAN "Build with Vulkan" OFF)
 tvm_option(USE_OPENGL "Build with OpenGL" OFF)
 tvm_option(USE_METAL "Build with Metal" OFF)
 tvm_option(USE_RPC "Build with RPC" ON)
@@ -88,9 +89,11 @@ file(GLOB_RECURSE HALIDEIR_SRCS HalideIR/src/*.cpp)
 list(APPEND COMPILER_SRCS ${HALIDEIR_SRCS})
 file(GLOB RUNTIME_SRCS src/runtime/*.cc)
 file(GLOB COMPILER_LLVM_SRCS src/codegen/llvm/*.cc)
+file(GLOB COMPILER_VULKAN_SRCS src/codegen/spirv/*.cc)
 file(GLOB RUNTIME_CUDA_SRCS src/runtime/cuda/*.cc)
 file(GLOB RUNTIME_OPENCL_SRCS src/runtime/opencl/*.cc)
 file(GLOB RUNTIME_OPENGL_SRCS src/runtime/opengl/*.cc)
+file(GLOB RUNTIME_VULKAN_SRCS src/runtime/vulkan/*.cc)
 file(GLOB RUNTIME_METAL_SRCS src/runtime/metal/*.mm)
 file(GLOB RUNTIME_RPC_SRCS src/runtime/rpc/*.cc)
 file(GLOB RUNTIME_GRAPH_SRCS src/runtime/graph/*.cc)
@@ -151,6 +154,22 @@ else(USE_OPENGL)
   add_definitions(-DTVM_OPENGL_RUNTIME=0)
 endif(USE_OPENGL)
 
+if(USE_VULKAN)
+  find_package(Vulkan REQUIRED)
+  message(STATUS "Build with VULKAN support")
+  include_directories(${Vulkan_INCLUDE_DIRS})
+  list(APPEND TVM_RUNTIME_LINKER_LIBS ${Vulkan_LIBRARIES})
+  list(APPEND RUNTIME_SRCS ${RUNTIME_VULKAN_SRCS})
+  list(APPEND COMPILER_SRCS ${COMPILER_VULKAN_SRCS})
+  get_filename_component(VULKAN_LIB_PATH ${Vulkan_LIBRARY} DIRECTORY)
+  find_library(SPIRV_TOOLS_LIB SPIRV-Tools
+               ${VULKAN_LIB_PATH}/spirv-tools)
+  list(APPEND TVM_LINKER_LIBS ${SPIRV_TOOLS_LIB})
+  add_definitions(-DTVM_VULKAN_RUNTIME=1)
+else(USE_VULKAN)
+  add_definitions(-DTVM_VULKAN_RUNTIME=0)
+endif(USE_VULKAN)
+
 if(USE_METAL)
   find_package(OpenCL QUIET REQUIRED)
   message(STATUS "Build with Metal support")
@@ -174,7 +193,7 @@ if(USE_GRAPH_RUNTIME)
 endif(USE_GRAPH_RUNTIME)
 
 if(USE_LLVM)
-  find_package(LLVM CONFIG REQUIRED)
+  find_spackage(LLVM CONFIG REQUIRED)
   include_directories(${LLVM_INCLUDE_DIRS})
   add_definitions(${LLVM_DEFINITIONS})
   set(TVM_LLVM_VERSION ${LLVM_VERSION_MAJOR}${LLVM_VERSION_MINOR})
@@ -252,4 +271,4 @@ if(MSVC)
   target_compile_definitions(tvm_runtime PRIVATE -DHalide_EXPORTS)
   target_compile_definitions(tvm PRIVATE -DTVM_EXPORTS)
   target_compile_definitions(tvm_runtime PRIVATE -DTVM_EXPORTS)
-endif()
\ No newline at end of file
+endif()
diff --git a/HalideIR b/HalideIR
index 87b089a0ba20..aadbf02d6bd7 160000
--- a/HalideIR
+++ b/HalideIR
@@ -1 +1 @@
-Subproject commit 87b089a0ba20f2e8257038ee9211d6816088ce95
+Subproject commit aadbf02d6bd7a545edbf6652494a7b07a97a06c1
diff --git a/Makefile b/Makefile
index 2361b505fbf3..7ed46fd14b05 100644
--- a/Makefile
+++ b/Makefile
@@ -56,6 +56,7 @@ CUDA_SRC = $(wildcard src/runtime/cuda/*.cc)
 ROCM_SRC = $(wildcard src/runtime/rocm/*.cc)
 OPENCL_SRC = $(wildcard src/runtime/opencl/*.cc)
 OPENGL_SRC = $(wildcard src/runtime/opengl/*.cc)
+VULKAN_SRC = $(wildcard src/runtime/vulkan/*.cc)
 RPC_SRC = $(wildcard src/runtime/rpc/*.cc)
 GRAPH_SRC = $(wildcard src/runtime/graph/*.cc)
 RUNTIME_SRC = $(wildcard src/runtime/*.cc)
@@ -69,6 +70,7 @@ CUDA_OBJ = $(patsubst src/%.cc, build/%.o, $(CUDA_SRC))
 ROCM_OBJ = $(patsubst src/%.cc, build/%.o, $(ROCM_SRC))
 OPENCL_OBJ = $(patsubst src/%.cc, build/%.o, $(OPENCL_SRC))
 OPENGL_OBJ = $(patsubst src/%.cc, build/%.o, $(OPENGL_SRC))
+VULKAN_OBJ = $(patsubst src/%.cc, build/%.o, $(VULKAN_SRC))
 RPC_OBJ = $(patsubst src/%.cc, build/%.o, $(RPC_SRC))
 GRAPH_OBJ = $(patsubst src/%.cc, build/%.o, $(GRAPH_SRC))
 CC_OBJ = $(patsubst src/%.cc, build/%.o, $(CC_SRC)) $(LLVM_OBJ)
@@ -129,6 +131,20 @@ else
 	CFLAGS += -DTVM_OPENCL_RUNTIME=0
 endif
 
+ifdef VULKAN_SDK
+	CFLAGS += -I$(VULKAN_SDK)/include
+	LDFLAGS += -L$(VULKAN_SDK)/lib
+	LDFLAGS += -L$(VULKAN_SDK)/lib/spirv-tools
+endif
+
+ifeq ($(USE_VULKAN), 1)
+	CFLAGS += -DTVM_VULKAN_RUNTIME=1
+	LDFLAGS += -lvulkan -lSPIRV-Tools
+	RUNTIME_DEP += $(VULKAN_OBJ)
+else
+	CFLAGS += -DTVM_VULKAN_RUNTIME=0
+endif
+
 ifeq ($(USE_OPENGL), 1)
 	CFLAGS += -DTVM_OPENGL_RUNTIME=1
 	EMCC_FLAGS += -DTVM_OPENGL_RUNTIME=1
diff --git a/include/tvm/ir_pass.h b/include/tvm/ir_pass.h
index 76a060345be1..d7032ffdb1ae 100644
--- a/include/tvm/ir_pass.h
+++ b/include/tvm/ir_pass.h
@@ -421,6 +421,18 @@ LoweredFunc LowerTVMBuiltin(LoweredFunc f);
  */
 LoweredFunc CombineContextCall(LoweredFunc f);
 
+/*!
+ * \brief Rewrite the pointer content type of arguments,
+ *  as well as Alloc internal to the function to use
+ *  the most frequently accessed type for load/store
+ *  to avoid pointer casting in backend when possible.
+ *
+ * \note implemeneted in storage_rewrite.cc
+ * \param f The function to be trasnformed
+ * \return Transformed function.
+ */
+LoweredFunc PointerValueTypeRewrite(LoweredFunc f);
+
 /*!
  * \brief Lower intrinsic function calls.
  * \param f The device function to be lowered.
diff --git a/include/tvm/runtime/c_runtime_api.h b/include/tvm/runtime/c_runtime_api.h
index 6d680330a659..edade00c7ed3 100644
--- a/include/tvm/runtime/c_runtime_api.h
+++ b/include/tvm/runtime/c_runtime_api.h
@@ -55,8 +55,8 @@ typedef int64_t tvm_index_t;
 
 /*! \brief Extension device types in TVM */
 typedef enum {
+  kDLVulkan = 7,
   kOpenGL = 11,
-
   // Extension DRAM type, used for quickly test extension device
   // The device api can differ depending on the xpu driver registered.
   kExtDev = 12,
diff --git a/python/tvm/__init__.py b/python/tvm/__init__.py
index fe66271dce5e..1119dd3fcfc0 100644
--- a/python/tvm/__init__.py
+++ b/python/tvm/__init__.py
@@ -17,7 +17,8 @@
 from . import target
 
 from . import ndarray as nd
-from .ndarray import context, cpu, gpu, opencl, cl, metal, mtl, vpi, rocm, opengl, ext_dev
+from .ndarray import context, cpu, gpu, opencl, cl, vulkan, metal, mtl
+from .ndarray import vpi, rocm, opengl, ext_dev
 
 from ._ffi.runtime_ctypes import TypeCode
 from ._ffi.function import Function
diff --git a/python/tvm/_ffi/runtime_ctypes.py b/python/tvm/_ffi/runtime_ctypes.py
index e2641fbc7e46..23af85bf26df 100644
--- a/python/tvm/_ffi/runtime_ctypes.py
+++ b/python/tvm/_ffi/runtime_ctypes.py
@@ -94,6 +94,7 @@ class TVMContext(ctypes.Structure):
         1 : 'cpu',
         2 : 'gpu',
         4 : 'opencl',
+        7 : 'vulkan',
         8 : 'metal',
         9 : 'vpi',
         10: 'rocm',
@@ -109,6 +110,7 @@ class TVMContext(ctypes.Structure):
         'nvptx': 2,
         'cl': 4,
         'opencl': 4,
+        'vulkan': 7,
         'metal': 8,
         'vpi': 9,
         'rocm': 10,
diff --git a/python/tvm/contrib/spirv.py b/python/tvm/contrib/spirv.py
new file mode 100644
index 000000000000..eed6240bc76c
--- /dev/null
+++ b/python/tvm/contrib/spirv.py
@@ -0,0 +1,43 @@
+"""Utility for Interacting with SPIRV Tools"""
+import subprocess
+import os
+from . import util
+
+
+def optimize(spv_bin):
+    """Optimize SPIRV using spirv-opt via CLI
+
+    Note that the spirv-opt is still experimental.
+
+    Parameters
+    ----------
+    spv_bin : bytearray
+        The spirv file
+
+    Return
+    ------
+    cobj_bin : bytearray
+        The HSA Code Object
+    """
+
+    tmp_dir = util.tempdir()
+    tmp_in = tmp_dir.relpath("input.spv")
+    tmp_out = tmp_dir.relpath("output.spv")
+    with open(tmp_in, "wb") as out_file:
+        out_file.write(bytes(spv_bin))
+
+    sdk = os.environ.get("VULKAN_SDK", None)
+    cmd = os.path.join(sdk, "bin/spirv-opt") if sdk else "spirv-opt"
+    args = [cmd, "-O", tmp_in, "-o", tmp_out]
+    proc = subprocess.Popen(
+        args,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.STDOUT)
+    (out, _) = proc.communicate()
+
+    if proc.returncode != 0:
+        msg = "Opitmizationerror using spirv-opt:\n"
+        msg += str(out)
+        raise RuntimeError(msg)
+
+    return bytearray(open(tmp_out, "rb").read())
diff --git a/python/tvm/ndarray.py b/python/tvm/ndarray.py
index 0521a69c5f80..18e958973d94 100644
--- a/python/tvm/ndarray.py
+++ b/python/tvm/ndarray.py
@@ -120,6 +120,23 @@ def vpi(dev_id=0):
     """
     return TVMContext(9, dev_id)
 
+
+def vulkan(dev_id=0):
+    """Construct a Vulkan device
+
+    Parameters
+    ----------
+    dev_id : int, optional
+        The integer device id
+
+    Returns
+    -------
+    ctx : TVMContext
+        The created context
+    """
+    return TVMContext(7, dev_id)
+
+
 def opengl(dev_id=0):
     """Construct a OpenGL device
 
@@ -135,6 +152,7 @@ def opengl(dev_id=0):
     """
     return TVMContext(11, dev_id)
 
+
 def ext_dev(dev_id=0):
     """Construct a extension device
 
diff --git a/python/tvm/target.py b/python/tvm/target.py
index 94cd5457e44f..5196241cd7cf 100644
--- a/python/tvm/target.py
+++ b/python/tvm/target.py
@@ -116,7 +116,7 @@ def __init__(self,
             # For now assume rocm schedule for opencl
             self.keys += ("rocm", "gpu")
             self.max_num_threads = 256
-        elif target_name in ("metal",):
+        elif target_name in ("metal", "vulkan"):
             self.keys += ("gpu",)
             self.max_num_threads = 256
         elif target_name in ("opengl",):
diff --git a/src/codegen/codegen_c.cc b/src/codegen/codegen_c.cc
index 403a5525b170..e00cd82abe48 100644
--- a/src/codegen/codegen_c.cc
+++ b/src/codegen/codegen_c.cc
@@ -666,6 +666,8 @@ void CodeGenC::VisitExpr_(const Let* op, std::ostream& os) {  // NOLINT(*)
 }
 
 void CodeGenC::VisitExpr_(const Ramp* op, std::ostream& os) {  // NOLINT(*)
+  // constraint of current logic
+  CHECK_EQ(op->base.type(), Int(32));
   os << "((int" << op->lanes << ")(";
   for (int i = 0; i < op->lanes; i++) {
     os << "(" << PrintExpr(op->base) << ")" << "+(" << PrintExpr(op->stride) << "*" << i <<")";
diff --git a/src/codegen/codegen_common.h b/src/codegen/codegen_common.h
new file mode 100644
index 000000000000..5e76af12e583
--- /dev/null
+++ b/src/codegen/codegen_common.h
@@ -0,0 +1,59 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file codegen_common.h
+ * \brief Common utility for codegen.
+ */
+#ifndef TVM_CODEGEN_CODEGEN_COMMON_H_
+#define TVM_CODEGEN_CODEGEN_COMMON_H_
+
+#include <tvm/arithmetic.h>
+#include "../arithmetic/compute_expr.h"
+
+namespace tvm {
+namespace codegen {
+
+/*!
+ * \brief Visit AssertStmt recursively, update align_map from condition.
+ * \param op The AssertStmt
+ * \param align_map The alignmap
+ * \param fvisit The recursive visitor
+ * \tparam FVisit the recursive visitor
+ */
+template<typename FVisit>
+inline void VisitAssert(
+    const ir::AssertStmt* op,
+    std::unordered_map<const Variable*, arith::ModularEntry>* align_map,
+    FVisit fvisit) {
+  using namespace ir;
+  auto& align_map_ = *align_map;
+  // Detect useful invariant pattern and use them to visit child.
+  // Pattern: Var % const  == 0
+  // TODO(tqchen) merge these pattern to a generic scope info visitor.
+  if (const EQ* eq = op->condition.as<EQ>()) {
+    const Mod* mod = eq->a.as<Mod>();
+    int64_t factor = 0, offset = 0;
+    if (mod && arith::GetConst(eq->b, &offset)) {
+      const Variable *var = mod->a.as<Variable>();
+      if (var && arith::GetConst(mod->b, &factor)) {
+        arith::ModularEntry old = align_map_[var];
+        if (factor > old.coeff) {
+          arith::ModularEntry e;
+          e.coeff = static_cast<int>(factor);
+          e.base = static_cast<int>(offset);
+          // new alignment info,
+          align_map_[var] = e;
+          fvisit(op->body);
+          // restore old info
+          align_map_[var] = old;
+          return;
+        }
+      }
+    }
+  }
+  fvisit(op->body);
+}
+
+}  // namespace codegen
+}  // namespace tvm
+
+#endif  // TVM_CODEGEN_CODEGEN_COMMON_H_
diff --git a/src/codegen/llvm/codegen_llvm.cc b/src/codegen/llvm/codegen_llvm.cc
index ea1e24c277cd..e95407a5c057 100644
--- a/src/codegen/llvm/codegen_llvm.cc
+++ b/src/codegen/llvm/codegen_llvm.cc
@@ -9,6 +9,7 @@
 #include <tvm/runtime/c_runtime_api.h>
 #include "./codegen_llvm.h"
 #include "./codegen_cpu.h"
+#include "../codegen_common.h"
 #include "../../pass/ir_util.h"
 #include "../../arithmetic/compute_expr.h"
 
@@ -341,7 +342,7 @@ void CodeGenLLVM::GetAlignment(Type t,
   int align_bits = t.bits();
   while (align_bits < max_align_bits &&
          me.base % 2  == 0 &&
-         me.coeff %2 == 0) {
+         me.coeff % 2 == 0) {
     me.base =  me.base / 2;
     me.coeff =  me.coeff / 2;
     align_bits *= 2;
@@ -1026,31 +1027,9 @@ void CodeGenLLVM::VisitStmt_(const AttrStmt* op) {
 }
 
 void CodeGenLLVM::VisitStmt_(const AssertStmt* op) {
-  // Detect useful invariant pattern and use them to visit child.
-  // Pattern: Var % const  == 0
-  // TODO(tqchen) move these pattern to a generic scope info visitor.
-  if (const EQ* eq = op->condition.as<EQ>()) {
-    const Mod* mod = eq->a.as<Mod>();
-    int64_t factor = 0, offset = 0;
-    if (mod && arith::GetConst(eq->b, &offset)) {
-      const Variable *var = mod->a.as<Variable>();
-      if (var && arith::GetConst(mod->b, &factor)) {
-        arith::ModularEntry old = align_map_[var];
-        if (factor > old.coeff) {
-          arith::ModularEntry e;
-          e.coeff = static_cast<int>(factor);
-          e.base = static_cast<int>(offset);
-          // new alignment info,
-          align_map_[var] = e;
-          this->VisitStmt(op->body);
-          // restore old info
-          align_map_[var] = old;
-          return;
-        }
-      }
-    }
-  }
-  this->VisitStmt(op->body);
+  VisitAssert(op, &align_map_, [this](const Stmt& body) {
+      this->VisitStmt(body);
+    });
 }
 
 void CodeGenLLVM::VisitStmt_(const LetStmt* op) {
diff --git a/src/codegen/spirv/build_vulkan.cc b/src/codegen/spirv/build_vulkan.cc
new file mode 100644
index 000000000000..719b1f30e267
--- /dev/null
+++ b/src/codegen/spirv/build_vulkan.cc
@@ -0,0 +1,95 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file build_vulkan.cc
+ * \brief Build SPIRV block
+ */
+#if TVM_VULKAN_RUNTIME
+
+// Use libspirv for parsing and validating code.
+#include <vulkan/libspirv.h>
+#include <dmlc/memory_io.h>
+#include <tvm/ir_pass.h>
+
+#include "./codegen_spirv.h"
+#include "../build_common.h"
+#include "../../runtime/vulkan/vulkan_module.h"
+
+namespace tvm {
+namespace codegen {
+
+class SPIRVTools {
+ public:
+  SPIRVTools() {
+    ctx_ = spvContextCreate(SPV_ENV_VULKAN_1_0);
+  }
+  ~SPIRVTools() {
+    spvContextDestroy(ctx_);
+  }
+  std::string BinaryToText(const std::vector<uint32_t>& bin) {
+    spv_text text = nullptr;
+    spv_diagnostic diagnostic;
+    spv_const_binary_t spv_bin{bin.data(), bin.size()};
+    spv_result_t res;
+
+    res = spvBinaryToText(
+       ctx_, spv_bin.code, spv_bin.wordCount,
+      SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES |
+           SPV_BINARY_TO_TEXT_OPTION_INDENT,
+        &text, &diagnostic);
+
+    CHECK_EQ(res, SPV_SUCCESS)
+        << " line=" << diagnostic->position.line
+        << " column=" << diagnostic->position.column
+        << " index=" << diagnostic->position.index
+        << " error:" << diagnostic->error;
+
+    std::string ret(text->str);
+    spvTextDestroy(text);
+    return ret;
+  }
+
+ private:
+  spv_context ctx_;
+};
+
+runtime::Module BuildSPIRV(Array<LoweredFunc> funcs) {
+  using tvm::runtime::Registry;
+  using tvm::runtime::VulkanShader;
+
+  std::ostringstream code_data;
+  static SPIRVTools spirv_tools;
+  std::unordered_map<std::string, VulkanShader> smap;
+
+  const auto* postproc = Registry::Get("tvm_callback_vulkan_postproc");
+
+  CodeGenSPIRV cg;
+  for (LoweredFunc f : funcs) {
+    f = PointerValueTypeRewrite(f);
+    VulkanShader shader;
+    shader.data = cg.BuildFunction(f);
+
+    if (postproc != nullptr) {
+      TVMByteArray arr;
+      arr.data = reinterpret_cast<const char*>(dmlc::BeginPtr(shader.data));
+      arr.size = shader.data.size() * sizeof(uint32_t);
+      std::string transformed = (*postproc)(arr);
+      CHECK_EQ(transformed.length() % 4U, 0U);
+      shader.data.resize(transformed.size() / 4U);
+      std::copy(transformed.begin(), transformed.end(),
+                reinterpret_cast<char*>(dmlc::BeginPtr(shader.data)));
+    }
+    code_data << spirv_tools.BinaryToText(shader.data);
+    smap[f->name] = std::move(shader);
+  }
+  return runtime::VulkanModuleCreate(
+     smap, ExtractFuncInfo(funcs), code_data.str());
+}
+
+TVM_REGISTER_API("codegen.build_vulkan")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    *rv = BuildSPIRV(args[0]);
+  });
+
+}  // namespace codegen
+}  // namespace tvm
+#endif  // TVM_VULKAN_RUNTIME
diff --git a/src/codegen/spirv/codegen_spirv.cc b/src/codegen/spirv/codegen_spirv.cc
new file mode 100644
index 000000000000..4d7a9b21ba5b
--- /dev/null
+++ b/src/codegen/spirv/codegen_spirv.cc
@@ -0,0 +1,638 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file codegen_spirv.cc
+ * \brief Generate SPIRV block
+ */
+
+#if TVM_VULKAN_RUNTIME
+
+#include <tvm/ir.h>
+#include <tvm/ir_pass.h>
+#include "../codegen_common.h"
+#include "./codegen_spirv.h"
+
+namespace tvm {
+namespace codegen {
+
+std::vector<uint32_t> CodeGenSPIRV::BuildFunction(const LoweredFunc& f) {
+  this->InitFuncState();
+  CHECK(f->is_restricted)
+      << "SPIRV only takes restricted memory model";
+  std::vector<Var> pod_args;
+  uint32_t num_buffer = 0;
+  for (Var arg : f->args) {
+    Type t = arg.type();
+    if (t.is_handle()) {
+      auto it = f->handle_data_type.find(arg);
+      if (it != f->handle_data_type.end()) {
+        Type value_type = (*it).second.type();
+        spirv::Value arg_value = builder_->BufferArgument(
+            builder_->GetSType(value_type), 0, num_buffer);
+        storage_info_[arg.get()].UpdateContentType(value_type);
+        var_map_[arg.get()] = arg_value;
+      } else {
+        LOG(FATAL) << "require all handles to be typed";
+      }
+      ++num_buffer;
+    } else {
+      pod_args.push_back(arg);
+    }
+  }
+  spirv::Value func_ptr = builder_->DeclareKenrelFunction(f->name);
+  builder_->StartFunction(func_ptr);
+
+  // All the POD arguments are passed in through PushConstant
+  if (pod_args.size() != 0) {
+    std::vector<spirv::SType> value_types;
+    for (size_t i = 0; i < pod_args.size(); ++i) {
+      value_types.push_back(builder_->GetSType(pod_args[i].type()));
+    }
+    spirv::Value ptr = builder_->DeclarePushConstant(value_types);
+    for (size_t i = 0; i < pod_args.size(); ++i) {
+      spirv::Value value = builder_->GetPushConstant(
+          ptr, value_types[i], static_cast<uint32_t>(i));
+      var_map_[pod_args[i].get()] = value;
+    }
+  }
+  this->VisitStmt(f->body);
+  builder_->SetLocalSize(func_ptr, workgroup_size_);
+  builder_->MakeInst(spv::OpReturn);
+  builder_->MakeInst(spv::OpFunctionEnd);
+
+  return builder_->Finalize();
+}
+
+void CodeGenSPIRV::InitFuncState() {
+  std::fill(workgroup_size_, workgroup_size_ + 3, 1);
+  var_map_.clear();
+  storage_info_.clear();
+  align_map_.clear();
+  builder_.reset(new spirv::IRBuilder());
+  builder_->InitHeader();
+}
+
+spirv::Value CodeGenSPIRV::GetThreadIndex(
+    const IterVar& iv, const Expr& extent) {
+  runtime::ThreadScope ts = runtime::ThreadScope::make(iv->thread_tag);
+  spirv::Value v;
+  if (ts.rank == 1) {
+    v = builder_->GetLocalID(ts.dim_index);
+    int size;
+    CHECK(arith::GetConstInt(extent, &size))
+        << "SPIRV only allows constant thread group size " << " get " << extent;
+    CHECK_LT(ts.dim_index, 3);
+    workgroup_size_[ts.dim_index] = static_cast<uint32_t>(size);
+  } else {
+    v = builder_->GetWorkgroupID(ts.dim_index);
+  }
+  return builder_->Cast(builder_->GetSType(iv->var.type()), v);
+}
+
+spirv::Value CodeGenSPIRV::CreateStorageSync(const Call* op) {
+  const std::string& sync = op->args[0].as<StringImm>()->value;
+  spirv::Value value;
+  if (sync == "warp") {
+    return value;
+  } else if (sync == "shared") {
+    builder_->MakeInst(
+        spv::OpControlBarrier,
+        spv::ScopeWorkgroup,
+        spv::ScopeWorkgroup,
+        spv::MemorySemanticsSequentiallyConsistentMask |
+        spv::MemorySemanticsWorkgroupMemoryMask);
+  } else {
+    LOG(FATAL) << "Do not support sync " << sync;
+  }
+  return value;
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const Variable* op) {
+  auto it = var_map_.find(op);
+  CHECK(it != var_map_.end()) << "cannot find variable " << op->name_hint;
+  return it->second;
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const IntImm* op) {
+  return builder_->IntImm(builder_->GetSType(op->type), op->value);
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const UIntImm* op) {
+  return builder_->UIntImm(builder_->GetSType(op->type), op->value);
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const FloatImm* op) {
+  return builder_->FloatImm(builder_->GetSType(op->type), op->value);
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const StringImm* op) {
+  LOG(FATAL) << "StringImm is not supported in Device code";
+  return spirv::Value();
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const Cast* op) {
+  return builder_->Cast(builder_->GetSType(op->type), MakeValue(op->value));
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const Add* op) {
+  return builder_->Add(MakeValue(op->a), MakeValue(op->b));
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const Sub* op) {
+  return builder_->Sub(MakeValue(op->a), MakeValue(op->b));
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const Mul* op) {
+  return builder_->Mul(MakeValue(op->a), MakeValue(op->b));
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const Div* op) {
+  return builder_->Div(MakeValue(op->a), MakeValue(op->b));
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const Mod* op) {
+  return builder_->Mod(MakeValue(op->a), MakeValue(op->b));
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const Min* op) {
+  spirv::Value a = MakeValue(op->a);
+  spirv::Value b = MakeValue(op->b);
+  return builder_->Select(builder_->LT(a, b), a, b);
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const Max* op) {
+  spirv::Value a = MakeValue(op->a);
+  spirv::Value b = MakeValue(op->b);
+  return builder_->Select(builder_->GT(a, b), a, b);
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const LT* op) {
+  return builder_->LT(MakeValue(op->a), MakeValue(op->b));
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const LE* op) {
+  return builder_->LE(MakeValue(op->a), MakeValue(op->b));
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const GT* op) {
+  return builder_->GT(MakeValue(op->a), MakeValue(op->b));
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const GE* op) {
+  return builder_->GE(MakeValue(op->a), MakeValue(op->b));
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const EQ* op) {
+  return builder_->EQ(MakeValue(op->a), MakeValue(op->b));
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const NE* op) {
+  return builder_->NE(MakeValue(op->a), MakeValue(op->b));
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const And* op) {
+  spirv::Value a = MakeValue(op->a);
+  spirv::Value b = MakeValue(op->b);
+  return builder_->MakeValue(spv::OpLogicalAnd, a.stype, a, b);
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const Or* op) {
+  spirv::Value a = MakeValue(op->a);
+  spirv::Value b = MakeValue(op->b);
+  return builder_->MakeValue(spv::OpLogicalOr, a.stype, a, b);
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const Not* op) {
+  spirv::Value a = MakeValue(op->a);
+  return builder_->MakeValue(spv::OpLogicalNot, a.stype, a);
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const Select* op) {
+  return builder_->Select(MakeValue(op->condition),
+                          MakeValue(op->true_value),
+                          MakeValue(op->false_value));
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const Let* op) {
+  CHECK(!var_map_.count(op->var.get()));
+  var_map_[op->var.get()] = MakeValue(op->value);
+  align_map_[op->var.get()] = EvalModular(op->value, align_map_);
+  return MakeValue(op->body);
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const Call* op) {
+  if (op->is_intrinsic("spirv_glsl450")) {
+    CHECK_GE(op->args.size(), 2U);
+    uint32_t inst_id = op->args[0].as<UIntImm>()->value;
+    std::vector<spirv::Value> values;
+    for (size_t i = 1; i < op->args.size(); ++i) {
+      values.push_back(MakeValue(op->args[i]));
+    }
+    return builder_->CallGLSL450(
+        builder_->GetSType(op->type), inst_id, values);
+  } else if (op->is_intrinsic(Call::bitwise_and)) {
+    CHECK_EQ(op->args.size(), 2U);
+    spirv::Value a = MakeValue(op->args[0]);
+    spirv::Value b = MakeValue(op->args[1]);
+    return builder_->MakeValue(spv::OpBitwiseAnd, a.stype, a, b);
+  } else if (op->is_intrinsic(Call::bitwise_xor)) {
+    CHECK_EQ(op->args.size(), 2U);
+    spirv::Value a = MakeValue(op->args[0]);
+    spirv::Value b = MakeValue(op->args[1]);
+    return builder_->MakeValue(spv::OpBitwiseXor, a.stype, a, b);
+  } else if (op->is_intrinsic(Call::bitwise_or)) {
+    CHECK_EQ(op->args.size(), 2U);
+    spirv::Value a = MakeValue(op->args[0]);
+    spirv::Value b = MakeValue(op->args[1]);
+    return builder_->MakeValue(spv::OpBitwiseOr, a.stype, a, b);
+  } else if (op->is_intrinsic(Call::bitwise_not)) {
+    CHECK_EQ(op->args.size(), 1U);
+    spirv::Value a = MakeValue(op->args[0]);
+    return builder_->MakeValue(spv::OpNot, a.stype, a);
+  } else if (op->is_intrinsic(Call::shift_left)) {
+    CHECK_EQ(op->args.size(), 2U);
+    spirv::Value a = MakeValue(op->args[0]);
+    spirv::Value b = MakeValue(op->args[1]);
+    return builder_->MakeValue(spv::OpShiftLeftLogical, a.stype, a, b);
+  } else if (op->is_intrinsic(Call::shift_right)) {
+    CHECK_EQ(op->args.size(), 2U);
+    spirv::Value a = MakeValue(op->args[0]);
+    spirv::Value b = MakeValue(op->args[1]);
+    if (op->args[0].type().is_int()) {
+      return builder_->MakeValue(spv::OpShiftRightArithmetic, a.stype, a, b);
+    } else {
+      return builder_->MakeValue(spv::OpShiftRightLogical, a.stype, a, b);
+    }
+  } else if (op->is_intrinsic(intrinsic::tvm_storage_sync)) {
+    return this->CreateStorageSync(op);
+  } else if (op->is_intrinsic(intrinsic::tvm_if_then_else)) {
+    CHECK_EQ(op->args.size(), 3U);
+    spirv::Value cond = MakeValue(op->args[0]);
+    spirv::Label then_label = builder_->NewLabel();
+    spirv::Label else_label = builder_->NewLabel();
+    spirv::Label merge_label = builder_->NewLabel();
+    builder_->MakeInst(
+        spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone);
+    builder_->MakeInst(
+        spv::OpBranchConditional, cond, then_label, else_label);
+    // then block, must get label after we see the value
+    builder_->StartLabel(then_label);
+    spirv::Value then_value = MakeValue(op->args[1]);
+    spirv::Label then_value_label = builder_->CurrentLabel();
+    builder_->MakeInst(spv::OpBranch, merge_label);
+    // else block
+    builder_->StartLabel(else_label);
+    spirv::Value else_value = MakeValue(op->args[2]);
+    spirv::Label else_value_label = builder_->CurrentLabel();
+    builder_->MakeInst(spv::OpBranch, merge_label);
+    // merge block
+    builder_->StartLabel(merge_label);
+    spirv::PhiValue phi = builder_->MakePhi(then_value.stype, 2);
+    phi.SetIncoming(0, then_value, then_value_label);
+    phi.SetIncoming(1, else_value, else_value_label);
+    return phi;
+  } else if (op->is_intrinsic("popcount")) {
+    return builder_->MakeValue(
+        spv::OpBitCount,
+        builder_->GetSType(op->type),
+        MakeValue(op->args[0]));
+  } else {
+    if (op->call_type == Call::Intrinsic ||
+        op->call_type == Call::PureIntrinsic) {
+      LOG(FATAL) << "Unresolved intrinsic " << op->name
+                 << " with return type " << op->type;
+    } else if (op->call_type == Call::Extern ||
+               op->call_type == Call::PureExtern) {
+      LOG(FATAL) << "Unresolved extern " << op->name
+                 << " with return type " << op->type;
+    } else {
+      LOG(FATAL) << "Unresolved call type " << op->call_type;
+    }
+    return spirv::Value();
+  }
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const Ramp* op) {
+  std::vector<spirv::Value> values;
+  spirv::Value base = MakeValue(op->base);
+  for (int i = 0; i < op->lanes; ++i) {
+    spirv::Value v = base;
+    if (i != 0) {
+      spirv::Value offset = MakeValue(
+          arith::ComputeExpr<Mul>(make_const(op->stride.type(), i), op->stride));
+      v = builder_->Add(v, offset);
+    }
+    values.push_back(v);
+  }
+  return builder_->Concat(values);
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const Broadcast* op) {
+  std::vector<spirv::Value> values;
+  spirv::Value v = MakeValue(op->value);
+  for (int i = 0; i < op->lanes; i++) {
+    values.push_back(v);
+  }
+  return builder_->Concat(values);
+}
+
+spirv::Value CodeGenSPIRV::VisitExpr_(const Load* op) {
+  CHECK(is_one(op->predicate));
+  auto it = storage_info_.find(op->buffer_var.get());
+  CHECK(it != storage_info_.end());
+  StorageInfo& info = it->second;
+  if (!info.content_fixed) {
+    info.UpdateContentType(op->type);
+  }
+
+  spirv::SType content_type = builder_->GetSType(info.content_type);
+  spirv::Value buffer = MakeValue(op->buffer_var);
+  spirv::SType ptr_type = builder_->GetPointerType(
+      content_type, buffer.stype.storage_class);
+
+  uint32_t mask = spv::MemoryAccessMaskNone;
+  if (info.is_volatile) {
+    mask |= spv::MemoryAccessVolatileMask;
+  }
+  if (op->type.lanes() == 1) {
+    CHECK_EQ(info.content_type, op->type)
+        << "Vulkan only allow one type access to the same buffer";
+    spirv::Value index = MakeValue(op->index);
+    spirv::Value ptr = builder_->StructArrayAccess(
+        ptr_type, buffer, index);
+    return builder_->MakeValue(spv::OpLoad, content_type, ptr, mask);
+  } else {
+    if (op->type.element_of() == info.content_type) {
+      // because content type is element type, we can only do scalarize load.
+      std::vector<spirv::Value> values;
+      auto f = [&](int i, spirv::Value index) {
+        spirv::Value ptr = builder_->StructArrayAccess(
+            ptr_type, buffer, index);
+        values.emplace_back(
+            builder_->MakeValue(spv::OpLoad, content_type, ptr, mask));
+      };
+      this->Scalarize(op->index, f);
+      return builder_->Concat(values);
+    } else {
+      if (const Ramp* ramp = op->index.as<Ramp>()) {
+        if (is_one(ramp->stride)) {
+          CHECK_EQ(ramp->lanes, op->type.lanes());
+          arith::ModularEntry me = arith::EvalModular(ramp->base, align_map_);
+          CHECK((me.coeff % ramp->lanes) == 0 &&
+                (me.base % ramp->lanes)  == 0)
+              << "Only aligned vector access is allowed in SPIRV";
+          Expr vec_index = ir::Simplify(
+              ramp->base / make_const(ramp->base.type(), ramp->lanes));
+          spirv::Value ptr = builder_->StructArrayAccess(
+              ptr_type, buffer, MakeValue(vec_index));
+          return builder_->MakeValue(spv::OpLoad, content_type, ptr, mask);
+        }
+      }
+    }
+    LOG(FATAL) << "Only aligned continuous vector access is allowed in SPIRV";
+  }
+  LOG(FATAL) << "Only aligned continuous vector access is allowed in SPIRV";
+  return spirv::Value();
+}
+
+void CodeGenSPIRV::Scalarize(const Expr& e,
+                             std::function<void(int i, spirv::Value v)> f) {
+  if (const Ramp* ramp = e.as<Ramp>()) {
+    for (int i = 0; i < ramp->type.lanes(); ++i) {
+      Expr offset = arith::ComputeExpr<Add>(
+          ramp->base,
+          arith::ComputeExpr<Mul>(ramp->stride, i));
+      f(i, MakeValue(offset));
+    }
+  } else {
+    spirv::SType etype = builder_->GetSType(e.type().element_of());
+    spirv::Value value = MakeValue(e);
+    for (int i = 0; i < e.type().lanes(); ++i) {
+      f(i, builder_->MakeValue(
+          spv::OpCompositeExtract, etype, value, i));
+    }
+  }
+}
+
+void CodeGenSPIRV::VisitStmt_(const Store* op) {
+  CHECK(is_one(op->predicate));
+  auto it = storage_info_.find(op->buffer_var.get());
+  CHECK(it != storage_info_.end());
+  StorageInfo& info = it->second;
+
+  if (!info.content_fixed) {
+    info.UpdateContentType(op->value.type());
+  }
+
+  spirv::SType content_type = builder_->GetSType(info.content_type);
+  spirv::Value buffer = MakeValue(op->buffer_var);
+  spirv::Value value = MakeValue(op->value);
+  spirv::SType ptr_type = builder_->GetPointerType(
+      content_type, buffer.stype.storage_class);
+
+  uint32_t mask = spv::MemoryAccessMaskNone;
+  if (info.is_volatile) {
+    mask |= spv::MemoryAccessVolatileMask;
+  }
+
+  if (op->value.type().lanes() == 1) {
+    CHECK_EQ(info.content_type, op->value.type())
+        << "Vulkan only allow one type access to the same buffer";
+    spirv::Value index = MakeValue(op->index);
+    spirv::Value ptr = builder_->StructArrayAccess(
+        ptr_type, buffer, index);
+    builder_->MakeInst(spv::OpStore, ptr, value, mask);
+  } else {
+    if (op->value.type().element_of() == info.content_type) {
+      // because content type is element type, we can only do scalarize load.
+      auto f = [&](int i, spirv::Value index) {
+        spirv::Value elem = builder_->MakeValue(
+            spv::OpCompositeExtract, content_type, value, i);
+        spirv::Value ptr = builder_->StructArrayAccess(
+            ptr_type, buffer, index);
+        builder_->MakeInst(spv::OpStore, ptr, elem, mask);
+      };
+      this->Scalarize(op->index, f);
+    } else {
+      if (const Ramp* ramp = op->index.as<Ramp>()) {
+        if (is_one(ramp->stride)) {
+          CHECK_EQ(ramp->lanes, op->value.type().lanes());
+          arith::ModularEntry me = arith::EvalModular(ramp->base, align_map_);
+          CHECK((me.coeff % ramp->lanes) == 0 &&
+                (me.base % ramp->lanes)  == 0)
+              << "Only aligned vector access is allowed in SPIRV";
+          Expr vec_index = ir::Simplify(
+              ramp->base / make_const(ramp->base.type(), ramp->lanes));
+          spirv::Value ptr = builder_->StructArrayAccess(
+              ptr_type, buffer, MakeValue(vec_index));
+          builder_->MakeInst(spv::OpStore, ptr, value, mask);
+          return;
+        }
+      }
+      LOG(FATAL) << "Only aligned continuous vector access is allowed in SPIRV";
+    }
+  }
+}
+
+void CodeGenSPIRV::VisitStmt_(const For* op) {
+  CHECK(is_zero(op->min));
+  spirv::Value init_value = MakeValue(op->min);
+  spirv::Value extent_value = MakeValue(op->extent);
+  // Must get init label after making value(to make sure they are correct)
+  spirv::Label init_label = builder_->CurrentLabel();
+  spirv::Label head_label = builder_->NewLabel();
+  spirv::Label body_label = builder_->NewLabel();
+  spirv::Label continue_label = builder_->NewLabel();
+  spirv::Label merge_label = builder_->NewLabel();
+  builder_->MakeInst(spv::OpBranch, head_label);
+
+  // Loop head
+  builder_->StartLabel(head_label);
+  spirv::PhiValue loop_var = builder_->MakePhi(init_value.stype, 2);
+  loop_var.SetIncoming(0, init_value, init_label);
+  spirv::Value loop_cond = builder_->LT(loop_var, extent_value);
+  uint32_t control = (
+      op->for_type == ForType::Unrolled ?
+      spv::LoopControlUnrollMask : spv::LoopControlMaskNone);
+  builder_->MakeInst(
+      spv::OpLoopMerge, merge_label, continue_label, control);
+  builder_->MakeInst(
+      spv::OpBranchConditional, loop_cond, body_label, merge_label,
+      weight_likely_branch_, 1);
+
+  // loop body
+  builder_->StartLabel(body_label);
+  var_map_[op->loop_var.get()] = spirv::Value(loop_var);
+  this->VisitStmt(op->body);
+  builder_->MakeInst(spv::OpBranch, continue_label);
+
+  // loop continue
+  builder_->StartLabel(continue_label);
+  spirv::Value one =
+      op->loop_var.type().is_int() ?
+      builder_->IntImm(loop_var.stype, 1) :
+      builder_->UIntImm(loop_var.stype, 1);
+  spirv::Value next_value = builder_->Add(loop_var, one);
+  loop_var.SetIncoming(1, next_value, builder_->CurrentLabel());
+  builder_->MakeInst(spv::OpBranch, head_label);
+  // loop merge
+  builder_->StartLabel(merge_label);
+}
+
+void CodeGenSPIRV::VisitStmt_(const IfThenElse* op) {
+  spirv::Value cond = MakeValue(op->condition);
+  spirv::Label then_label = builder_->NewLabel();
+  spirv::Label merge_label = builder_->NewLabel();
+  if (op->else_case.defined()) {
+    spirv::Label else_label = builder_->NewLabel();
+    builder_->MakeInst(
+        spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone);
+    builder_->MakeInst(
+        spv::OpBranchConditional, cond, then_label, else_label);
+    // then block
+    builder_->StartLabel(then_label);
+    this->VisitStmt(op->then_case);
+    builder_->MakeInst(spv::OpBranch, merge_label);
+    // else block
+    builder_->StartLabel(else_label);
+    this->VisitStmt(op->else_case);
+    builder_->MakeInst(spv::OpBranch, merge_label);
+  } else {
+    builder_->MakeInst(
+        spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone);
+    builder_->MakeInst(
+        spv::OpBranchConditional, cond, then_label, merge_label,
+        weight_likely_branch_, 1);
+    // then block
+    builder_->StartLabel(then_label);
+    this->VisitStmt(op->then_case);
+    builder_->MakeInst(spv::OpBranch, merge_label);
+  }
+  // start merge label;
+  builder_->StartLabel(merge_label);
+}
+
+void CodeGenSPIRV::VisitStmt_(const Allocate* op) {
+  CHECK(!is_zero(op->condition));
+  CHECK(!op->new_expr.defined());
+  CHECK(!op->type.is_handle());
+  int32_t constant_size = op->constant_allocation_size();
+  CHECK_GT(constant_size, 0)
+      << "Can only handle constant size stack allocation in GPU";
+  spirv::Value buf;
+  StorageInfo& info = storage_info_[op->buffer_var.get()];
+  spirv::SType etype = builder_->GetSType(op->type);
+  if (info.scope.rank == 2) {
+    buf = builder_->Allocate(
+        etype, static_cast<uint32_t>(constant_size),
+        spv::StorageClassFunction);
+  } else {
+    // shared memory
+    CHECK_EQ(info.scope.rank, 1)
+        << "Can only allocate shared or local memory inside kernel";
+    // Shared memory
+    buf = builder_->Allocate(
+        etype, static_cast<uint32_t>(constant_size),
+        spv::StorageClassWorkgroup);
+  }
+  CHECK(!info.content_fixed);
+  info.UpdateContentType(op->type);
+  CHECK(!var_map_.count(op->buffer_var.get()));
+  var_map_[op->buffer_var.get()] = buf;
+  this->VisitStmt(op->body);
+}
+
+void CodeGenSPIRV::VisitStmt_(const AttrStmt* op) {
+  if (op->attr_key == attr::thread_extent) {
+    IterVar iv(op->node.node_);
+    if (iv->thread_tag.length() != 0) {
+      if (!var_map_.count(iv->var.get())) {
+        var_map_[iv->var.get()] = GetThreadIndex(iv, op->value);
+      }
+    }
+  } else if (op->attr_key == ir::attr::storage_scope) {
+    const Variable* v = op->node.as<Variable>();
+    CHECK(v);
+    storage_info_[v].scope =
+        runtime::StorageScope::make(op->value.as<StringImm>()->value);
+  } else if (op->attr_key == ir::attr::volatile_scope) {
+    const Variable* v = op->node.as<Variable>();
+    CHECK(v);
+    storage_info_[v].is_volatile = true;
+  }
+  this->VisitStmt(op->body);
+}
+
+void CodeGenSPIRV::VisitStmt_(const AssertStmt* op) {
+  VisitAssert(op, &align_map_, [this](const Stmt& body) {
+      this->VisitStmt(body);
+    });
+}
+
+void CodeGenSPIRV::VisitStmt_(const LetStmt* op) {
+  CHECK(!var_map_.count(op->var.get()));
+  CHECK(!align_map_.count(op->var.get()));
+  CHECK(!op->var.type().is_handle());
+  var_map_[op->var.get()] = MakeValue(op->value);
+  align_map_[op->var.get()] = EvalModular(op->value, align_map_);
+  this->VisitStmt(op->body);
+}
+
+void CodeGenSPIRV::VisitStmt_(const Block* op) {
+  VisitStmt(op->first);
+  if (op->rest.defined()) {
+    this->VisitStmt(op->rest);
+  }
+}
+
+void CodeGenSPIRV::VisitStmt_(const Evaluate* op) {
+  MakeValue(op->value);
+}
+
+void CodeGenSPIRV::VisitStmt_(const ProducerConsumer* op) {
+  this->VisitStmt(op->body);
+}
+
+}  // namespace codegen
+}  // namespace tvm
+
+#endif  // TVM_VULKAN_RUNTIME
diff --git a/src/codegen/spirv/codegen_spirv.h b/src/codegen/spirv/codegen_spirv.h
new file mode 100644
index 000000000000..a6c09362ddf7
--- /dev/null
+++ b/src/codegen/spirv/codegen_spirv.h
@@ -0,0 +1,133 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file ir_builder.h
+ * \brief Utility for building SPIRV code block
+ */
+#ifndef TVM_CODEGEN_SPIRV_CODEGEN_SPIRV_H_
+#define TVM_CODEGEN_SPIRV_CODEGEN_SPIRV_H_
+
+#include <tvm/ir.h>
+#include <tvm/ir_functor_ext.h>
+#include <tvm/lowered_func.h>
+
+#include <vector>
+
+#include "./ir_builder.h"
+#include "../../runtime/thread_storage_scope.h"
+
+namespace tvm {
+namespace codegen {
+
+using namespace ir;
+
+/*!
+ * \brief Code generator into SPIRV
+ */
+class CodeGenSPIRV:
+      public ExprFunctor<spirv::Value(const Expr&)>,
+      public StmtFunctor<void(const Stmt&)> {
+ public:
+  /*!
+   * \brief Compile and add function f to the current module.
+   * \param f The function to be added.
+   * \return The final spirv module.
+   */
+  virtual std::vector<uint32_t> BuildFunction(const LoweredFunc& f);
+  /*!
+   * \brief Create Value for expression e
+   * \param e The expression to be created value for.
+   * \return created value.
+   */
+  spirv::Value MakeValue(const Expr& e) {
+    return VisitExpr(e);
+  }
+  // override codegen
+  spirv::Value VisitExpr_(const Variable* op) override;
+  spirv::Value VisitExpr_(const Cast* op) override;
+  spirv::Value VisitExpr_(const IntImm* op) override;
+  spirv::Value VisitExpr_(const UIntImm* op) override;
+  spirv::Value VisitExpr_(const FloatImm* op) override;
+  spirv::Value VisitExpr_(const StringImm* op) override;
+  spirv::Value VisitExpr_(const Add* op) override;
+  spirv::Value VisitExpr_(const Sub* op) override;
+  spirv::Value VisitExpr_(const Mul* op) override;
+  spirv::Value VisitExpr_(const Div* op) override;
+  spirv::Value VisitExpr_(const Mod* op) override;
+  spirv::Value VisitExpr_(const Min* op) override;
+  spirv::Value VisitExpr_(const Max* op) override;
+  spirv::Value VisitExpr_(const LT* op) override;
+  spirv::Value VisitExpr_(const LE* op) override;
+  spirv::Value VisitExpr_(const GT* op) override;
+  spirv::Value VisitExpr_(const GE* op) override;
+  spirv::Value VisitExpr_(const EQ* op) override;
+  spirv::Value VisitExpr_(const NE* op) override;
+  spirv::Value VisitExpr_(const And* op) override;
+  spirv::Value VisitExpr_(const Or* op) override;
+  spirv::Value VisitExpr_(const Not* op) override;
+  spirv::Value VisitExpr_(const Select* op) override;
+  spirv::Value VisitExpr_(const Let* op) override;
+  spirv::Value VisitExpr_(const Call* op) override;
+  spirv::Value VisitExpr_(const Ramp* op) override;
+  spirv::Value VisitExpr_(const Broadcast* op) override;
+  spirv::Value VisitExpr_(const Load* op) override;
+  // stmt
+  void VisitStmt_(const Store* op) override;
+  void VisitStmt_(const For* op) override;
+  void VisitStmt_(const IfThenElse* op) override;
+  void VisitStmt_(const Allocate* op) override;
+  void VisitStmt_(const AttrStmt* op) override;
+  void VisitStmt_(const AssertStmt* op) override;
+  void VisitStmt_(const LetStmt* op) override;
+  void VisitStmt_(const Block* op) override;
+  void VisitStmt_(const Evaluate* op) override;
+  void VisitStmt_(const ProducerConsumer* op) override;
+
+ protected:
+  /*! \brief The storage information */
+  struct StorageInfo {
+    /*! \brief The storage scope */
+    runtime::StorageScope scope;
+    /*! \brief Whether it is volatile */
+    bool is_volatile{false};
+    /*! \brief Whether it is volatile */
+    bool content_fixed{false};
+    /*! \brief Current content type */
+    Type content_type{Handle()};
+
+    // Update content type if it hasn't beenupdated.
+    void UpdateContentType(Type type) {
+      if (content_fixed) {
+        CHECK_EQ(type, content_type)
+            << "Cannot use two different content type in GLSL model";
+      } else {
+        this->content_type = type;
+        content_fixed = true;
+      }
+    }
+  };
+  // Reset the state so it works for a new function.
+  void InitFuncState();
+  // Get the thread index
+  spirv::Value GetThreadIndex(const IterVar& iv, const Expr& extent);
+  spirv::Value CreateStorageSync(const Call* op);
+  void Scalarize(const Expr& e,
+                 std::function<void(int i, spirv::Value v)> f);
+  // The builder
+  std::unique_ptr<spirv::IRBuilder> builder_;
+  // Work group size of three
+  uint32_t workgroup_size_[3];
+  // Likely branch
+  uint32_t weight_likely_branch_{128};
+  // the storage scope of allocation
+  std::unordered_map<const Variable*, StorageInfo> storage_info_;
+  // The definition of local variable.
+  std::unordered_map<const Variable*, spirv::Value> var_map_;
+  // The alignment information
+  std::unordered_map<const Variable*, arith::ModularEntry> align_map_;
+};
+
+}  // namespace codegen
+}  // namespace tvm
+
+
+#endif  // TVM_CODEGEN_SPIRV_CODEGEN_SPIRV_H_
diff --git a/src/codegen/spirv/intrin_rule_spirv.cc b/src/codegen/spirv/intrin_rule_spirv.cc
new file mode 100644
index 000000000000..cef98266ca9e
--- /dev/null
+++ b/src/codegen/spirv/intrin_rule_spirv.cc
@@ -0,0 +1,50 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file intrin_rule_spirv.cc
+ */
+#if TVM_VULKAN_RUNTIME
+
+#include <tvm/packed_func_ext.h>
+#include <tvm/ir.h>
+#include <vulkan/GLSL.std.450.h>
+
+namespace tvm {
+namespace codegen {
+namespace spirv {
+
+using namespace runtime;
+
+// num_signature means number of arguments used to query signature
+template<unsigned id>
+inline void DispatchGLSLPureIntrin(const TVMArgs& targs, TVMRetValue* rv) {
+  Expr e = targs[0];
+  const ir::Call* call = e.as<ir::Call>();
+  CHECK(call != nullptr);
+  Array<Expr> cargs;
+  // intrin id.
+  cargs.push_back(ir::UIntImm::make(UInt(32), id));
+
+  for (Expr arg : call->args) {
+    cargs.push_back(arg);
+  }
+  *rv = ir::Call::make(
+      call->type, "spirv_glsl450", cargs, ir::Call::PureIntrinsic);
+}
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.vulkan.exp")
+.set_body(DispatchGLSLPureIntrin<GLSLstd450Exp>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.vulkan.log")
+.set_body(DispatchGLSLPureIntrin<GLSLstd450Log>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.vulkan.sqrt")
+.set_body(DispatchGLSLPureIntrin<GLSLstd450Sqrt>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.vulkan.pow")
+.set_body(DispatchGLSLPureIntrin<GLSLstd450Pow>);
+
+}  // namespace spirv
+}  // namespace codegen
+}  // namespace tvm
+
+#endif  // TVM_VULKAN_RUNTIME
diff --git a/src/codegen/spirv/ir_builder.cc b/src/codegen/spirv/ir_builder.cc
new file mode 100644
index 000000000000..137ad75c6b10
--- /dev/null
+++ b/src/codegen/spirv/ir_builder.cc
@@ -0,0 +1,548 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file ir_builder.cc
+ * \brief IRBuilder for SPIRV block
+ */
+
+#if TVM_VULKAN_RUNTIME
+
+#include "./ir_builder.h"
+
+namespace tvm {
+namespace codegen {
+namespace spirv {
+
+// implementations
+
+void IRBuilder::InitHeader() {
+  CHECK_EQ(header_.size(), 0U);
+  header_.push_back(spv::MagicNumber);
+  header_.push_back(spv::Version);
+  // generator: set to 0, unknown
+  header_.push_back(0U);
+  // Bound: set during Finalize
+  header_.push_back(0U);
+  // Schema: reserved
+  header_.push_back(0U);
+  // shader
+  ib_.Begin(spv::OpCapability).Add(spv::CapabilityShader).Commit(&header_);
+  // memory model
+  ib_.Begin(spv::OpMemoryModel).AddSeq(
+        spv::AddressingModelLogical,
+        spv::MemoryModelGLSL450).Commit(&entry_);
+  this->InitPreDefs();
+}
+
+void IRBuilder::InitPreDefs() {
+  ext_glsl450_ = ExtInstImport("GLSL.std.450");
+  t_int32_ = DeclareType(Int(32));
+  t_uint32_ = DeclareType(UInt(32));
+  t_bool_ = DeclareType(UInt(1));
+  t_fp32_ = DeclareType(Float(32));
+  const_i32_zero_ = IntImm(t_int32_, 0);
+  // declare void, and void functions
+  t_void_.id = id_counter_++;
+  ib_.Begin(spv::OpTypeVoid).Add(t_void_).Commit(&global_);
+  t_void_func_.id = id_counter_++;
+  ib_.Begin(spv::OpTypeFunction)
+      .AddSeq(t_void_func_, t_void_).Commit(&global_);
+}
+
+SType IRBuilder::GetSType(const Type& dtype) {
+  if (dtype == Int(32)) {
+    return t_int32_;
+  } else if (dtype == UInt(1)) {
+    return t_bool_;
+  } else if (dtype == Float(32)) {
+    return t_fp32_;
+  } else if (dtype == UInt(32)) {
+    return t_uint32_;
+  }
+  uint32_t type_key;
+  type_key = static_cast<uint32_t>(dtype.code());
+  type_key |= static_cast<uint32_t>(dtype.bits()) << 8U;
+  type_key |= static_cast<uint32_t>(dtype.lanes()) << 16U;
+  auto it = pod_type_tbl_.find(type_key);
+  if (it != pod_type_tbl_.end()) {
+    return it->second;
+  }
+  SType t = DeclareType(dtype);
+  pod_type_tbl_[type_key] = t;
+  return t;
+}
+
+SType IRBuilder::GetPointerType(const SType& value_type,
+                                spv::StorageClass storage_class) {
+  CHECK_NE(storage_class, spv::StorageClassMax);
+  auto key = std::make_pair(value_type.id, storage_class);
+  auto it = pointer_type_tbl_.find(key);
+  if (it != pointer_type_tbl_.end()) {
+    return it->second;
+  }
+  SType t;
+  t.id = id_counter_++;
+  t.type = Handle();
+  t.element_type_id = value_type.id;
+  t.storage_class = storage_class;
+  ib_.Begin(spv::OpTypePointer)
+      .AddSeq(t, storage_class, value_type).Commit(&global_);
+  pointer_type_tbl_[key] = t;
+  return t;
+}
+
+SType IRBuilder::GetStructArrayType(const SType& value_type,
+                                    uint32_t num_elems) {
+  auto key = std::make_pair(value_type.id, num_elems);
+  auto it = struct_array_type_tbl_.find(key);
+  if (it != struct_array_type_tbl_.end()) {
+    return it->second;
+  }
+
+  SType arr_type;
+  arr_type.id = id_counter_++;
+  arr_type.type = Handle();
+  arr_type.element_type_id = value_type.id;
+
+  if (num_elems != 0) {
+    Value length = UIntImm(GetSType(UInt(32)), num_elems);
+    ib_.Begin(spv::OpTypeArray)
+        .AddSeq(arr_type, value_type, length).Commit(&global_);
+  } else {
+    ib_.Begin(spv::OpTypeRuntimeArray)
+        .AddSeq(arr_type, value_type).Commit(&global_);
+  }
+  int nbits = value_type.type.bits() * value_type.type.lanes();
+  CHECK_EQ(nbits % 8, 0);
+  uint32_t nbytes = static_cast<uint32_t>(nbits) / 8;
+  // decorate the array type.
+  this->Decorate(spv::OpDecorate,
+                 arr_type, spv::DecorationArrayStride, nbytes);
+  // declare struct of array
+  SType struct_type;
+  struct_type.id = id_counter_++;
+  struct_type.type = Handle();
+  struct_type.element_type_id = value_type.id;
+  ib_.Begin(spv::OpTypeStruct)
+      .AddSeq(struct_type, arr_type).Commit(&global_);
+  // decorate the array type.
+  ib_.Begin(spv::OpMemberDecorate)
+      .AddSeq(struct_type, 0, spv::DecorationOffset, 0)
+      .Commit(&decorate_);
+  // runtime array are always decorated as BufferBlock(shader storage buffer)
+  if (num_elems == 0) {
+    this->Decorate(spv::OpDecorate,
+                   struct_type, spv::DecorationBufferBlock);
+  }
+  struct_array_type_tbl_[key] = struct_type;
+  return struct_type;
+}
+
+Value IRBuilder::StructArrayAccess(const SType& res_type,
+                                   Value buffer,
+                                   Value index) {
+  CHECK(buffer.flag == kStructArrayPtr);
+  return MakeValue(spv::OpInBoundsAccessChain,
+                   res_type, buffer,
+                   const_i32_zero_, index);
+}
+
+Value IRBuilder::IntImm(const SType& dtype, int64_t value) {
+  return GetConst_(dtype, reinterpret_cast<uint64_t*>(&value));
+}
+
+Value IRBuilder::UIntImm(const SType& dtype, uint64_t value) {
+  return GetConst_(dtype, &value);
+}
+
+Value IRBuilder::FloatImm(const SType& dtype, double value) {
+  if (dtype.type.bits() == 64) {
+    return GetConst_(dtype, reinterpret_cast<uint64_t*>(&value));
+  } else if (dtype.type.bits() == 32) {
+    float fvalue = static_cast<float>(value);
+    uint64_t data =  reinterpret_cast<uint32_t*>(&fvalue)[0];
+    return GetConst_(dtype, &data);
+  } else {
+    CHECK_EQ(dtype.type.bits(), 16);
+    return Cast(dtype,
+                FloatImm(GetSType(Float(32)), value));
+  }
+}
+
+Value IRBuilder::BufferArgument(const SType& value_type,
+                                uint32_t descriptor_set,
+                                uint32_t binding) {
+  SType sarr_type = GetStructArrayType(value_type, 0);
+  SType ptr_type = GetPointerType(sarr_type, spv::StorageClassUniform);
+  Value val = NewValue(ptr_type, kStructArrayPtr);
+  ib_.Begin(spv::OpVariable)
+      .AddSeq(ptr_type, val, spv::StorageClassUniform).Commit(&global_);
+  this->Decorate(spv::OpDecorate,
+                 val, spv::DecorationDescriptorSet, descriptor_set);
+  this->Decorate(spv::OpDecorate,
+                 val, spv::DecorationBinding, binding);
+  return val;
+}
+
+Value IRBuilder::DeclarePushConstant(const std::vector<SType>& value_types) {
+  CHECK_EQ(push_const_.id, 0);
+  SType struct_type;
+  struct_type.id = id_counter_++;
+  struct_type.type = Handle();
+  ib_.Begin(spv::OpTypeStruct).Add(struct_type);
+  for (const SType& vtype : value_types) {
+    ib_.Add(vtype);
+  }
+  ib_.Commit(&global_);
+
+  uint32_t offset = 0;
+  for (uint32_t i = 0; i < value_types.size(); ++i) {
+    ib_.Begin(spv::OpMemberDecorate)
+        .AddSeq(struct_type, i, spv::DecorationOffset, offset)
+        .Commit(&decorate_);
+    Type t = value_types[i].type;
+    uint32_t nbits = t.bits() * t.lanes();
+    CHECK_EQ(nbits % 8 , 0);
+    offset += nbits / 8;
+  }
+  // Decorate push constants as UBO
+  this->Decorate(spv::OpDecorate, struct_type, spv::DecorationBlock);
+
+  SType ptr_type = GetPointerType(
+      struct_type, spv::StorageClassPushConstant);
+  Value val = NewValue(ptr_type, kPushConstantPtr);
+  ib_.Begin(spv::OpVariable)
+      .AddSeq(ptr_type, val, spv::StorageClassPushConstant).Commit(&global_);
+  return val;
+}
+
+Value IRBuilder::GetPushConstant(
+    Value ptr_push_const, const SType& v_type, uint32_t index) {
+  SType ptr_vtype = this->GetPointerType(v_type, spv::StorageClassPushConstant);
+  Value ptr = this->MakeValue(
+      spv::OpAccessChain, ptr_vtype, ptr_push_const,
+      IntImm(t_int32_, static_cast<int64_t>(index)));
+  return this->MakeValue(spv::OpLoad, v_type, ptr);
+}
+
+Value IRBuilder::DeclareKenrelFunction(const std::string& name) {
+  Value val = NewValue(t_void_func_, kFunction);
+  ib_.Begin(spv::OpEntryPoint)
+      .AddSeq(spv::ExecutionModelGLCompute, val, name)
+      .Commit(&entry_);
+  return val;
+}
+
+void IRBuilder::StartFunction(const Value& func) {
+  CHECK_EQ(func.flag, kFunction);
+  this->MakeInst(
+      spv::OpFunction, t_void_, func, 0, t_void_func_);
+  spirv::Label start_label = this->NewLabel();
+  this->StartLabel(start_label);
+}
+
+void IRBuilder::SetLocalSize(const Value& func,
+                             uint32_t local_size[3]) {
+  CHECK_EQ(func.flag, kFunction);
+  ib_.Begin(spv::OpExecutionMode)
+      .AddSeq(func, spv::ExecutionModeLocalSize,
+              local_size[0], local_size[1], local_size[2])
+      .Commit(&exec_mode_);
+}
+
+Value IRBuilder::Allocate(const SType& value_type,
+                          uint32_t num_elems,
+                          spv::StorageClass storage_class) {
+  CHECK_NE(num_elems, 0U);
+  SType sarr_type = GetStructArrayType(value_type, num_elems);
+  SType ptr_type = GetPointerType(sarr_type, storage_class);
+  Value val = NewValue(ptr_type, kStructArrayPtr);
+  if (storage_class == spv::StorageClassFunction) {
+    ib_.Begin(spv::OpVariable)
+        .AddSeq(ptr_type, val, storage_class).Commit(&function_);
+  } else {
+    ib_.Begin(spv::OpVariable)
+        .AddSeq(ptr_type, val, storage_class).Commit(&global_);
+  }
+  return val;
+}
+
+Value IRBuilder::GetWorkgroupID(uint32_t dim_index) {
+  if (workgroup_id_.id == 0) {
+    SType vec3_type = this->GetSType(Int(32).with_lanes(3));
+    SType ptr_type = this->GetPointerType(
+        vec3_type, spv::StorageClassInput);
+    workgroup_id_ = NewValue(ptr_type, kVectorPtr);
+    ib_.Begin(spv::OpVariable)
+        .AddSeq(ptr_type, workgroup_id_, spv::StorageClassInput)
+        .Commit(&global_);
+    this->Decorate(spv::OpDecorate, workgroup_id_,
+                   spv::DecorationBuiltIn, spv::BuiltInWorkgroupId);
+  }
+  SType pint_type = this->GetPointerType(t_int32_, spv::StorageClassInput);
+  Value ptr = this->MakeValue(
+      spv::OpAccessChain, pint_type, workgroup_id_,
+      IntImm(t_int32_, static_cast<int64_t>(dim_index)));
+  return this->MakeValue(spv::OpLoad, t_int32_, ptr);
+}
+
+Value IRBuilder::GetLocalID(uint32_t dim_index) {
+  if (local_id_.id == 0) {
+    SType vec3_type = this->GetSType(Int(32).with_lanes(3));
+    SType ptr_type = this->GetPointerType(vec3_type, spv::StorageClassInput);
+    local_id_ = NewValue(ptr_type, kVectorPtr);
+    ib_.Begin(spv::OpVariable)
+        .AddSeq(ptr_type, local_id_, spv::StorageClassInput)
+        .Commit(&global_);
+    this->Decorate(spv::OpDecorate, local_id_,
+                   spv::DecorationBuiltIn, spv::BuiltInLocalInvocationId);
+  }
+  SType pint_type = this->GetPointerType(t_int32_, spv::StorageClassInput);
+  Value ptr = this->MakeValue(
+      spv::OpAccessChain, pint_type, local_id_,
+      UIntImm(t_int32_, static_cast<int64_t>(dim_index)));
+  return this->MakeValue(spv::OpLoad, t_int32_, ptr);
+}
+
+Value IRBuilder::GetConst_(const SType& dtype, const uint64_t* pvalue) {
+  auto key = std::make_pair(dtype.id, pvalue[0]);
+  auto it = const_tbl_.find(key);
+  if (it != const_tbl_.end()) {
+    return it->second;
+  }
+  CHECK_LE(dtype.type.bits(), 64);
+  Value ret = NewValue(dtype, kConstant);
+  ib_.Begin(spv::OpConstant).AddSeq(dtype, ret);
+  uint64_t mask = 0xFFFFFFFFUL;
+  ib_.Add(static_cast<uint32_t>(pvalue[0] & mask));
+  if (dtype.type.bits() > 32) {
+    if (dtype.type.is_int()) {
+      int64_t sign_mask = 0xFFFFFFFFL;
+      const int64_t* sign_ptr =
+          reinterpret_cast<const int64_t*>(pvalue);
+        ib_.Add(static_cast<uint32_t>((sign_ptr[0] >> 32L) & sign_mask));
+    } else {
+      ib_.Add(static_cast<uint32_t>((pvalue[0] >> 32UL) & mask));
+    }
+  }
+  ib_.Commit(&global_);
+  const_tbl_[key] = ret;
+  return ret;
+}
+
+SType IRBuilder::DeclareType(const Type& dtype) {
+  if (dtype.lanes() == 1) {
+    SType t;
+    t.id = id_counter_++;
+    t.type = dtype;
+    if (dtype.bits() == 1) {
+      CHECK(dtype.is_uint());
+      ib_.Begin(spv::OpTypeBool).Add(t).Commit(&global_);
+    } else if (dtype.is_int()) {
+      ib_.Begin(spv::OpTypeInt).AddSeq(t, dtype.bits(), 1).Commit(&global_);
+    } else if (dtype.is_uint()) {
+      ib_.Begin(spv::OpTypeInt).AddSeq(t, dtype.bits(), 0).Commit(&global_);
+    } else if (dtype.is_float()) {
+      ib_.Begin(spv::OpTypeFloat).AddSeq(t, dtype.bits()).Commit(&global_);
+    } else {
+      LOG(FATAL) << "declare type do not support handle";
+    }
+    return t;
+  } else {
+    SType t;
+    t.id = id_counter_++;
+    t.type = dtype;
+    SType base_type = GetSType(dtype.element_of());
+    ib_.Begin(spv::OpTypeVector).AddSeq(
+        t, base_type, dtype.lanes()).Commit(&global_);
+    return t;
+  }
+}
+
+PhiValue IRBuilder::MakePhi(const SType& out_type, uint32_t num_incoming) {
+  Value val = NewValue(out_type, kNormal);
+  ib_.Begin(spv::OpPhi).AddSeq(out_type, val);
+  for (uint32_t i = 0; i < 2 * num_incoming; ++i) {
+    ib_.Add(0);
+  }
+  PhiValue phi;
+  phi.id = val.id;
+  phi.stype = out_type;
+  phi.flag = kNormal;
+  phi.instr = ib_.Commit(&function_);
+  CHECK_EQ(phi.instr.WordCount(), 2 * num_incoming + 3);
+  return phi;
+}
+
+Value IRBuilder::CallGLSL450(const SType& ret_type,
+                             uint32_t inst_id,
+                             const std::vector<Value>& args) {
+  Value val = NewValue(ret_type, kNormal);
+  ib_.Begin(spv::OpExtInst)
+      .AddSeq(ret_type, val, ext_glsl450_, inst_id);
+  for (const Value& v : args) {
+    ib_.Add(v);
+  }
+  ib_.Commit(&function_);
+  return val;
+}
+
+Value IRBuilder::Concat(const std::vector<Value>& vec) {
+  bool is_const = vec[0].flag == kConstant;
+  Type etype = vec[0].stype.type;
+  int lanes = etype.lanes();
+  for (size_t i = 1; i < vec.size(); ++i) {
+    CHECK_EQ(etype, vec[i].stype.type.element_of())
+        << "Cannot concat vector of different element type";
+    lanes += vec[i].stype.type.lanes();
+    is_const = is_const && (vec[i].flag == kConstant);
+  }
+  Value ret = NewValue(GetSType(etype.with_lanes(lanes)), kNormal);
+  if (is_const && vec.size() == static_cast<size_t>(lanes)) {
+    ib_.Begin(spv::OpConstantComposite);
+    ib_.AddSeq(ret.stype, ret);
+    for (const Value& v : vec) {
+      ib_.Add(v);
+    }
+    ib_.Commit(&global_);
+  } else {
+    ib_.Begin(spv::OpCompositeConstruct);
+    ib_.AddSeq(ret.stype, ret);
+    for (const Value& v : vec) {
+      ib_.Add(v);
+    }
+    ib_.Commit(&function_);
+  }
+  return ret;
+}
+
+Value IRBuilder::Cast(const SType& dst_type, spirv::Value value) {
+  CHECK_NE(value.stype.id, 0U);
+  if (value.stype.id == dst_type.id) return value;
+  const tvm::Type& from = value.stype.type;
+  const tvm::Type& to = dst_type.type;
+  CHECK_EQ(from.lanes(), to.lanes());
+
+  if (from.is_int() && to.is_int()) {
+    return MakeValue(spv::OpSConvert, dst_type, value);
+  } else if (from.is_uint() && to.is_uint()) {
+    return MakeValue(spv::OpUConvert, dst_type, value);
+  } else if (from.is_uint() && to.is_int()) {
+    if (from.bits() != to.bits()) {
+      value = MakeValue(
+          spv::OpUConvert, GetSType(from.with_bits(to.bits())), value);
+    }
+    return MakeValue(spv::OpBitcast, dst_type, value);
+  } else if (from.is_int() && to.is_uint()) {
+    if (from.bits() != to.bits()) {
+      value = MakeValue(
+          spv::OpSConvert, GetSType(from.with_bits(to.bits())), value);
+    }
+    return MakeValue(spv::OpBitcast, dst_type, value);
+  } else if (from.is_float() && to.is_int()) {
+    return MakeValue(spv::OpConvertFToS, dst_type, value);
+  } else if (from.is_float() && to.is_uint()) {
+    return MakeValue(spv::OpConvertFToU, dst_type, value);
+  } else if (from.is_int() && to.is_float()) {
+    return MakeValue(spv::OpConvertSToF, dst_type, value);
+  } else if (from.is_uint() && to.is_float()) {
+    return MakeValue(spv::OpConvertUToF, dst_type, value);
+  } else if (from.is_float() && to.is_float()) {
+    return MakeValue(spv::OpFConvert, dst_type, value);
+  } else {
+    LOG(FATAL) << "do not support type cast from "
+               << from << " to " << to;
+    return Value();
+  }
+}
+
+#define DEFINE_BUILDER_BINARY_USIGN_OP(_OpName, _Op)              \
+  Value IRBuilder::_OpName(Value a, Value b) {                    \
+    CHECK_EQ(a.stype.id, b.stype.id);                             \
+    if (a.stype.type.is_int() || a.stype.type.is_uint()) {        \
+      return MakeValue(spv::OpI ## _Op, a.stype, a, b);           \
+    } else {                                                      \
+      CHECK(a.stype.type.is_float());                             \
+      return MakeValue(spv::OpF ## _Op, a.stype, a, b);           \
+    }                                                             \
+  }
+
+#define DEFINE_BUILDER_BINARY_SIGN_OP(_OpName, _Op)               \
+  Value IRBuilder::_OpName(Value a, Value b) {                    \
+    CHECK_EQ(a.stype.id, b.stype.id);                             \
+    if (a.stype.type.is_int()) {                                   \
+      return MakeValue(spv::OpS ## _Op, a.stype, a, b);            \
+    } else if (a.stype.type.is_uint()) {                           \
+      return MakeValue(spv::OpU ## _Op, a.stype, a, b);            \
+    } else {                                                       \
+      CHECK(a.stype.type.is_float());                              \
+      return MakeValue(spv::OpF ## _Op, a.stype, a, b);            \
+    }                                                              \
+  }
+
+DEFINE_BUILDER_BINARY_USIGN_OP(Add, Add);
+DEFINE_BUILDER_BINARY_USIGN_OP(Sub, Sub);
+DEFINE_BUILDER_BINARY_USIGN_OP(Mul, Mul);
+DEFINE_BUILDER_BINARY_SIGN_OP(Div, Div);
+
+Value IRBuilder::Mod(Value a, Value b) {
+  CHECK_EQ(a.stype.id, b.stype.id);
+  if (a.stype.type.is_int()) {
+    return MakeValue(spv::OpSRem, a.stype, a, b);
+  } else if (a.stype.type.is_uint()) {
+    return MakeValue(spv::OpUMod, a.stype, a, b);
+  } else {
+    CHECK(a.stype.type.is_float());
+    return MakeValue(spv::OpFRem, a.stype, a, b);
+  }
+}
+
+
+#define DEFINE_BUILDER_CMP_OP(_OpName, _Op)                        \
+  Value IRBuilder:: _OpName(Value a, Value b) {                    \
+    CHECK_EQ(a.stype.id, b.stype.id);                              \
+    if (t_bool_.id == 0) {                                         \
+      t_bool_ = DeclareType(UInt(1));                              \
+    }                                                              \
+    if (a.stype.type.is_int()) {                                   \
+      return MakeValue(spv::OpS ## _Op, t_bool_, a, b);            \
+    } else if (a.stype.type.is_uint()) {                           \
+      return MakeValue(spv::OpU ## _Op, t_bool_, a, b);            \
+    } else {                                                       \
+      CHECK(a.stype.type.is_float());                              \
+      return MakeValue(spv::OpFOrd ## _Op, t_bool_, a, b);         \
+    }                                                              \
+  }
+
+DEFINE_BUILDER_CMP_OP(LT, LessThan);
+DEFINE_BUILDER_CMP_OP(LE, LessThanEqual);
+DEFINE_BUILDER_CMP_OP(GT, GreaterThan);
+DEFINE_BUILDER_CMP_OP(GE, GreaterThanEqual);
+
+#define DEFINE_BUILDER_CMP_UOP(_OpName, _Op)                       \
+  Value IRBuilder:: _OpName(Value a, Value b) {                    \
+    CHECK_EQ(a.stype.id, b.stype.id);                              \
+    if (t_bool_.id == 0) {                                         \
+      t_bool_ = DeclareType(UInt(1));                              \
+    }                                                              \
+    if (a.stype.type.is_int() || a.stype.type.is_uint()) {         \
+      return MakeValue(spv::OpI ## _Op, t_bool_, a, b);            \
+    } else {                                                       \
+      CHECK(a.stype.type.is_float());                              \
+      return MakeValue(spv::OpFOrd ## _Op, t_bool_, a, b);         \
+    }                                                              \
+  }
+
+DEFINE_BUILDER_CMP_UOP(EQ, Equal);
+DEFINE_BUILDER_CMP_UOP(NE, NotEqual);
+
+Value IRBuilder::Select(Value cond, Value a, Value b) {
+  CHECK_EQ(a.stype.id, b.stype.id);
+  CHECK_EQ(cond.stype.type, UInt(1));
+  return MakeValue(spv::OpSelect, a.stype, cond, a, b);
+}
+
+}  // namespace spirv
+}  // namespace codegen
+}  // namespace tvm
+
+#endif  // TVM_VULKAN_RUNTIME
diff --git a/src/codegen/spirv/ir_builder.h b/src/codegen/spirv/ir_builder.h
new file mode 100644
index 000000000000..bdb320154062
--- /dev/null
+++ b/src/codegen/spirv/ir_builder.h
@@ -0,0 +1,597 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file ir_builder.h
+ * \brief Utility for building SPIRV code block
+ */
+#ifndef TVM_CODEGEN_SPIRV_IR_BUILDER_H_
+#define TVM_CODEGEN_SPIRV_IR_BUILDER_H_
+
+#include <tvm/runtime/packed_func.h>
+#include <tvm/ir.h>
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+#include <string>
+#include <map>
+
+#include <vulkan/spirv.hpp>
+
+namespace tvm {
+namespace codegen {
+namespace spirv {
+
+/*! \brief Represent the SPIRV Type */
+struct SType {
+  /*! \brief The Id to represent type */
+  uint32_t id{0};
+  /*! \brief corresponding TVM type */
+  tvm::Type type;
+  /*! \brief content type id if it is a pointer/struct-array class */
+  uint32_t element_type_id{0};
+  /*! \brief The storage class, if it is a pointer */
+  spv::StorageClass storage_class{spv::StorageClassMax};
+};
+
+enum ValueKind {
+  kNormal,
+  kConstant,
+  kVectorPtr,
+  kStructArrayPtr,
+  kPushConstantPtr,
+  kFunction,
+  kExtInst
+};
+
+/*! \brief Represent the SPIRV Value */
+struct Value {
+  /*! \brief The Id to represent value */
+  uint32_t id{0};
+  /*! \brief The data type */
+  SType stype;
+  /*! \brief additional flags about the value */
+  ValueKind flag{kNormal};
+};
+
+/*! \brief Represent the SPIRV Label */
+struct Label {
+  /*! \brief The Id to represent label */
+  uint32_t id{0};
+};
+
+/*!
+ * \brief A SPIRV instruction,
+ *  can be used as handle to modify its content later
+ */
+class Instr {
+ public:
+  /*! \return the word count */
+  uint32_t WordCount() const {
+    return word_count_;
+  }
+  /*!
+   * \brief Access idx-th word of instruction
+   * \param idx The index
+   * \return reference to idx-th word.
+   */
+  uint32_t& operator[](uint32_t idx) {
+    CHECK_LT(idx, word_count_);
+    return (*data_)[begin_ + idx];
+  }
+
+ private:
+  friend class InstrBuilder;
+  /*!
+   * \brief the data that backs this instruction
+   *  Have to use vector reference because
+   *  vector can change.
+   */
+  std::vector<uint32_t>* data_{nullptr};
+  /*! \brief begin location of instruction */
+  uint32_t begin_{0};
+  /*! \brief work count */
+  uint32_t word_count_{0};
+};
+
+/*! \brief Representation of phi value */
+struct PhiValue : public Value {
+  /*! \brief The corresponding instr */
+  Instr instr;
+  /*!
+   * \brief Add incoming information of a PhiValue
+   * \param index The location of Phi
+   * \param value The value to come
+   * \param parent The parent label.
+   */
+  void SetIncoming(uint32_t index,
+                   const Value& value,
+                   const Label& parent) {
+    CHECK_EQ(this->stype.id, value.stype.id);
+    instr[3 + index * 2] = value.id;
+    instr[3 + index * 2 + 1] = parent.id;
+  }
+};
+
+/*!
+ * \brief Helper class to build SPIRV instruction.
+ *
+ * \code
+ *
+ *  std::vector<uint32_t> func_seg_vec_;
+ *  InstrBuilder ib;
+ *
+ *  // construct and append to the end of func_seg_vec_;
+ *  ib.Begin(spv::OpIAdd)
+ *    .Add(result).Add(v1).Add(v2)
+ *    .Commit(&func_seg_vec_);
+ *
+ * \endcode
+ */
+class InstrBuilder {
+ public:
+  /*!
+   * \brief Begin construction of instruction.
+   * \param op The op code
+   * \return reference to self.
+   */
+  InstrBuilder& Begin(spv::Op op) {  // NOLINT(*);
+    // finish previous build
+    CHECK_EQ(data_.size(), 0U);
+    op_ = op;
+    data_.push_back(0);
+    return *this;
+  }
+  /*!
+   * \brief Add v to end of instruction.
+   * \param v The value to be appended to the instruction.
+   * \return reference to self.
+   */
+  InstrBuilder& Add(const Value& v) {
+    data_.push_back(v.id);
+    return *this;
+  }
+  /*!
+   * \brief Add v to end of instruction.
+   * \param v The type to be appended to the instruction.
+   * \return reference to self.
+   */
+  InstrBuilder& Add(const SType& v) {
+    data_.push_back(v.id);
+    return *this;
+  }
+  /*!
+   * \brief Add v to end of instruction.
+   * \param v The label to be appended to the instruction.
+   * \return reference to self.
+   */
+  InstrBuilder& Add(const Label& v) {
+    data_.push_back(v.id);
+    return *this;
+  }
+  /*!
+   * \brief Add a word to end of instruction.
+   * \param v The value to be added.
+   * \return reference to self.
+   */
+  InstrBuilder& Add(const uint32_t& v) {
+    data_.push_back(v);
+    return *this;
+  }
+  /*!
+   * \brief Add string literal of end of instruction.
+   * \param v The string literal to be appended.
+   * \return reference to self.
+   */
+  InstrBuilder& Add(const std::string& v) {
+    const uint32_t kWordSize = sizeof(uint32_t);
+    uint32_t nwords =
+        (static_cast<uint32_t>(v.length()) + kWordSize) / kWordSize;
+    size_t begin = data_.size();
+    data_.resize(begin + nwords, 0U);
+    std::copy(v.begin(), v.end(),
+              reinterpret_cast<char*>(&data_[begin]));
+    return *this;
+  }
+  /*!
+   * \brief add sequence of values to instruction
+   * \param args The instruction sequence
+   * \return reference to self.
+   * \tparams Args The positional arguments
+   */
+  template<typename... Args>
+  InstrBuilder& AddSeq(Args&& ...args) {
+    AddSeqHelper helper;
+    helper.builder = this;
+    runtime::detail::for_each(helper, std::forward<Args>(args)...);
+    return *this;
+  }
+  /*!
+   * \brief Finish build, commit the current
+   *   instruction to the end of seg.
+   *
+   * \param seg The code segment to commit to
+   * \return The result instruction.
+   */
+  Instr Commit(std::vector<uint32_t>* seg) {
+    Instr ret;
+    ret.data_ = seg;
+    ret.begin_ = seg->size();
+    ret.word_count_ = static_cast<uint32_t>(data_.size());
+    data_[0] = op_ | (ret.word_count_ << spv::WordCountShift);
+    seg->insert(seg->end(), data_.begin(), data_.end());
+    data_.clear();
+    return ret;
+  }
+
+ private:
+  // current op code.
+  spv::Op op_;
+  // The internal data to store code
+  std::vector<uint32_t> data_;
+  // helper class to support variadic arguments
+  struct AddSeqHelper {
+    // The reference to builder
+    InstrBuilder* builder;
+    // invoke function
+    template<typename T>
+    void operator()(size_t, const T& v) const {
+      builder->Add(v);
+    }
+  };
+};
+
+/*!
+ * \brief Builder to build up a single SPIR-V module
+ *
+ *  This is a thin wrapper to build SPIRV binary.
+ *  SPIRV adopts structure control-flow.
+ *  We can build the code by always appending to the end of the
+ *  binary code block and revisit some
+ *
+ *  This IRBuilder did not introduce concept of BasicBlock.
+ *  instead instructions are append to end of each segment.
+ */
+class IRBuilder {
+ public:
+  /*! \brief Initialize header */
+  void InitHeader();
+  /*! \brief Initialize the predefined contents */
+  void InitPreDefs();
+  /*!
+   * \brief Import additional extension libraries.
+   * \param name The name of the library.
+   * \return The finalized binary instruction.
+   */
+  Value ExtInstImport(const std::string& name) {
+    Value val = NewValue(SType(), kExtInst);
+    ib_.Begin(spv::OpExtInstImport).AddSeq(val, name).Commit(&header_);
+    return val;
+  }
+  /*!
+   * \brief Get the final binary built from the builder
+   * \return The finalized binary instruction.
+   */
+  std::vector<uint32_t> Finalize() {
+    std::vector<uint32_t> data;
+    // set bound
+    const int kBoundLoc = 3;
+    header_[kBoundLoc] = id_counter_;
+    data.insert(data.end(), header_.begin(), header_.end());
+    data.insert(data.end(), entry_.begin(), entry_.end());
+    data.insert(data.end(), exec_mode_.begin(), exec_mode_.end());
+    data.insert(data.end(), debug_.begin(), debug_.end());
+    data.insert(data.end(), decorate_.begin(), decorate_.end());
+    data.insert(data.end(), global_.begin(), global_.end());
+    data.insert(data.end(), function_.begin(), function_.end());
+    return data;
+  }
+  /*!
+   * \brief Create new label
+   * \return The created new label
+   */
+  Label NewLabel() {
+    Label label;
+    label.id = id_counter_++;
+    return label;
+  }
+  /*!
+   * \brief Start a new block with given label
+   * \param label The label we use.
+   */
+  void StartLabel(Label label) {
+    MakeInst(spv::OpLabel, label);
+    curr_label_ = label;
+  }
+  /*! \return The current label */
+  Label CurrentLabel() const {
+    return curr_label_;
+  }
+  /*!
+   * \brief Add code to debug segment.
+   * \param op The operator
+   * \param args The instruction sequence
+   * \tparams Args The positional arguments
+   */
+  template<typename... Args>
+  void Debug(spv::Op op, Args&& ...args) {
+    ib_.Begin(op).AddSeq(std::forward<Args>(args)...).Commit(&debug_);
+  }
+  /*!
+   * \brief Add Execution mode to a function.
+   * \param func The function value
+   * \param args The instruction sequence
+   * \tparams Args The positional arguments
+   */
+  template<typename... Args>
+  void ExecutionMode(Value func, Args&& ...args) {
+    ib_.Begin(spv::OpExecutionMode).AddSeq(
+        func, std::forward<Args>(args)...).Commit(&exec_mode_);
+  }
+  /*!
+   * \brief Add code to decorate segment.
+   * \param op The operator
+   * \param args The instruction sequence
+   * \tparams Args The positional arguments
+   */
+  template<typename... Args>
+  void Decorate(spv::Op op, Args&& ...args) {
+    ib_.Begin(op).AddSeq(std::forward<Args>(args)...).Commit(&decorate_);
+  }
+  /*!
+   * \brief Add code to global segment.
+   * \param op The operator
+   * \param args The instruction sequence
+   * \tparams Args The positional arguments
+   */
+  template<typename... Args>
+  Value DeclareGlobal(spv::Op op, Args&& ...args) {
+    ib_.Begin(op).AddSeq(std::forward<Args>(args)...).Commit(&decorate_);
+  }
+  /*!
+   * \brief Make a new instruction and append it to end of function segment.
+   *
+   * \param op The operator
+   * \param args The instruction sequence
+   * \return The result SSA value.
+   * \tparams Args The positional arguments
+   */
+  template<typename... Args>
+  Instr MakeInst(spv::Op op, Args&& ...args) {
+    return ib_.Begin(op).AddSeq(std::forward<Args>(args)...).Commit(&function_);
+  }
+  /*!
+   * \brief Make a new SSA value,
+   *
+   * \param op The operator.
+   * \param out_type The result type.
+   * \param args The instruction sequence
+   * \return The result SSA value.
+   * \tparams Args The positional arguments
+   */
+  template<typename... Args>
+  Value MakeValue(spv::Op op, const SType& out_type, Args&& ...args) {
+    Value val = NewValue(out_type, kNormal);
+    MakeInst(op, out_type, val, std::forward<Args>(args)...);
+    return val;
+  }
+  /*!
+   * \brief Make a phi value.
+   *
+   * \param out_type The output data type.
+   * \param num_incoming number of incoming blocks.
+   * \return The result Phi value.
+   */
+  PhiValue MakePhi(const SType& out_type, uint32_t num_incoming);
+  /*!
+   * \brief Create a GLSL450 call
+   *
+   * \param ret_type The result type.
+   * \param inst_id The instance id of the function.
+   * \param args The arguments
+   * \return The result value.
+   */
+  Value CallGLSL450(const SType& ret_type,
+                    uint32_t inst_id,
+                    const std::vector<Value>& args);
+  /*!
+   * \brief Build vector by concatenating components
+   *
+   * \param vec The vector component
+   * \tparams Args The positional arguments
+   */
+  Value Concat(const std::vector<Value>& vec);
+  /*!
+   * \brief Get the spirv type for a given tvm data type.
+   * \param dtype The data type.
+   * \return The corresponding spirv type.
+   */
+  SType GetSType(const tvm::Type& dtype);
+  /*!
+   * \brief Get the pointer type that points to value_type
+   * \param value_type.
+   * \param storage_class The storage class
+   * \return The corresponding spirv type.
+   */
+  SType GetPointerType(const SType& value_type,
+                       spv::StorageClass storage_class);
+  /*!
+   * \brief Get a struct{ value_type[num_elems] } type.
+   * \param value_type the content value type.
+   * \param num_elems number of elements in array
+   *   num_elems = 0 means runtime array with BufferBlock Decoration
+   *
+   * \return The corresponding spirv type.
+   */
+  SType GetStructArrayType(const SType& value_type,
+                           uint32_t num_elems);
+  /*!
+   * \brief Get a struct array access with a given index.
+   * \param ptr_type The pointer type.
+   * \param buffer The buffer ptr to struct array
+   * \param index The array index.
+   */
+  Value StructArrayAccess(const SType& ptr_type,
+                          Value buffer,
+                          Value index);
+  /*!
+   * \brief Create a cast that cast value to dst_type
+   * \param dst_type The target type.
+   * \param value the source value.
+   * \return The result value
+   */
+  Value Cast(const SType& dst_type, Value value);
+  /*
+   * \brief Create a const integer.
+   * \param dtype The content data type.
+   * \param value The data value.
+   */
+  Value IntImm(const SType& dtype, int64_t value);
+  /*
+   * \brief Create a const unsigned integer.
+   * \param dtype The content data type.
+   * \param value The data value.
+   */
+  Value UIntImm(const SType& dtype, uint64_t value);
+  /*
+   * \brief Create a const float.
+   * \param dtype The content data type.
+   * \param value The data value.
+   */
+  Value FloatImm(const SType& dtype, double value);
+  /*
+   * \brief Declare buffer argument of function
+   *
+   * \param arg_type The type of argument.
+   * \param descriptor_set The descriptor set we want to use.
+   * \param binding The binding locaiton in descriptor set.
+   * \param The argument type.
+   */
+  Value BufferArgument(const SType& value_type,
+                       uint32_t descriptor_set,
+                       uint32_t binding);
+  /*!
+   * \brief Declare POD arguments through push constants.
+   *
+   * \note Only call this function once!
+   * \param value_types The values in the push constant
+   * \return reference to self.
+   */
+  Value DeclarePushConstant(const std::vector<SType>& value_types);
+  /*!
+   * \brief Get i-th push constant
+   * \param v_type The value type
+   * \param index The push constant index
+   * \return the value of push constant
+   */
+  Value GetPushConstant(Value ptr_push_const, const SType& v_type, uint32_t index);
+  /*!
+   * \brief Declare a kernel function
+   * \param name Name of the entry point.
+   * \return The created function ID.
+   */
+  Value DeclareKenrelFunction(const std::string& name);
+  /*!
+   * \brief Start function scope.
+   * \param func function to be started.
+   */
+  void StartFunction(const Value& func);
+  /*!
+   * \brief Set the local size of the function
+   * \param func function of interest
+   * \param local_size The local workgroup_size
+   */
+  void SetLocalSize(const Value& func, uint32_t local_size[3]);
+  /*
+   * \brief Allocate space
+   * \param value_type The content value type
+   * \param num_elems Number of elements to allocate.
+   * \param storage_class The storage class we want to store to.
+   */
+  Value Allocate(const SType& value_type,
+                 uint32_t num_elems,
+                 spv::StorageClass storage_class);
+  /*
+   * \brief Get the i-th workgroup id.
+   * \return The value representing the workgroup id.
+   */
+  Value GetWorkgroupID(uint32_t dim_index);
+  /*
+   * \brief Get the i-th local id.
+   * \return The value representing the local id.
+   */
+  Value GetLocalID(uint32_t dim_index);
+  // Expressions
+  Value Add(Value a, Value b);
+  Value Sub(Value a, Value b);
+  Value Mul(Value a, Value b);
+  Value Div(Value a, Value b);
+  Value Mod(Value a, Value b);
+  Value EQ(Value a, Value b);
+  Value NE(Value a, Value b);
+  Value LT(Value a, Value b);
+  Value LE(Value a, Value b);
+  Value GT(Value a, Value b);
+  Value GE(Value a, Value b);
+  Value Select(Value cond, Value a, Value b);
+
+ private:
+  /*!
+   * \brief Create new value
+   * \return The created new label
+   */
+  Value NewValue(const SType& stype, ValueKind flag) {
+    Value val;
+    val.id = id_counter_++;
+    val.stype = stype;
+    val.flag = flag;
+    return val;
+  }
+  // get constant given value encoded in uint64_t
+  Value GetConst_(const SType& dtype, const uint64_t* pvalue);
+  // declare type
+  SType DeclareType(const Type& dtype);
+  /*! \brief internal instruction builder  */
+  InstrBuilder ib_;
+  /*! \brief Current label */
+  Label curr_label_;
+  /*! \brief The current maximum id */
+  uint32_t id_counter_{1};
+  /*! \brief glsl 450 extension */
+  Value ext_glsl450_;
+  /*! \brief Special cache int32, fp32, void*/
+  SType t_bool_, t_int32_, t_uint32_, t_fp32_, t_void_, t_void_func_;
+  /*! \brief quick cache for const one i32 */
+  Value const_i32_zero_;
+  /*! \brief cache value for workgroup_id, local_id */
+  Value workgroup_id_, local_id_;
+  /*! \brief whether push constant is defined */
+  Value push_const_;
+  /*! \brief map from type code to the type */
+  std::unordered_map<uint32_t, SType> pod_type_tbl_;
+  /*! \brief map from value to array type */
+  std::map<std::pair<uint32_t, uint32_t>, SType> struct_array_type_tbl_;
+  /*! \brief map from value to its pointer type */
+  std::map<std::pair<uint32_t, spv::StorageClass>, SType> pointer_type_tbl_;
+  /*! \brief map from constant int to its value */
+  std::map<std::pair<uint32_t, uint64_t>, Value> const_tbl_;
+  /*! \brief Header segment, include import */
+  std::vector<uint32_t> header_;
+  /*! \brief engtry point segment */
+  std::vector<uint32_t> entry_;
+  /*! \brief Header segment */
+  std::vector<uint32_t> exec_mode_;
+  /*! \brief Debug segment */
+  std::vector<uint32_t> debug_;
+  /*! \brief Annotation segment */
+  std::vector<uint32_t> decorate_;
+    /*! \brief Global segment: types, variables, types */
+  std::vector<uint32_t> global_;
+  /*! \brief Function segment */
+  std::vector<uint32_t> function_;
+};
+
+}  // namespace spirv
+}  // namespace codegen
+}  // namespace tvm
+
+#endif  // TVM_CODEGEN_SPIRV_IR_BUILDER_H_
diff --git a/src/pass/rewrite_unsafe_select.cc b/src/pass/rewrite_unsafe_select.cc
index da72722bdeae..ecafe8eba06c 100644
--- a/src/pass/rewrite_unsafe_select.cc
+++ b/src/pass/rewrite_unsafe_select.cc
@@ -59,7 +59,7 @@ class UnsafeExprDetector : public ExprFunctor<bool(const Expr& n)> {
     return VisitExpr(op->a);
   }
   bool VisitExpr_(const Let* op) final {
-    return VisitExpr(op->body) && VisitExpr(op->value);
+    return VisitExpr(op->body) || VisitExpr(op->value);
   }
   bool VisitExpr_(const Cast* op) final {
     return VisitExpr(op->value);
@@ -84,7 +84,7 @@ class UnsafeExprDetector : public ExprFunctor<bool(const Expr& n)> {
  private:
   template<typename T>
   bool BinaryOp(const T* op) {
-    return VisitExpr(op->a) && VisitExpr(op->b);
+    return VisitExpr(op->a) || VisitExpr(op->b);
   }
 };
 
diff --git a/src/pass/storage_rewrite.cc b/src/pass/storage_rewrite.cc
index 00674153bb35..5a0ea067d433 100644
--- a/src/pass/storage_rewrite.cc
+++ b/src/pass/storage_rewrite.cc
@@ -903,20 +903,42 @@ class VectorAllocRewriter : public IRMutator {
     return stmt;
   }
 
-
- private:
   void UpdateTypeMap(const Variable* buffer, Type t) {
     auto& tvec = acc_map_[buffer];
     if (std::find(tvec.begin(), tvec.end(), t) == tvec.end()) {
       tvec.push_back(t);
     }
   }
+
   // Internal access map
-  std::unordered_map<const Variable*,
-                     std::vector<Type> > acc_map_;
+  std::unordered_map<const Variable*, std::vector<Type> > acc_map_;
 };
 
 
+LoweredFunc PointerValueTypeRewrite(LoweredFunc f) {
+  std::shared_ptr<LoweredFuncNode> n =
+      std::make_shared<LoweredFuncNode>(*f.operator->());
+  VectorAllocRewriter rewriter;
+  n->body = rewriter.Mutate(n->body);
+  for (Var arg : f->args) {
+    if (arg.type().is_handle()) {
+      const auto& tvec = rewriter.acc_map_[arg.get()];
+      if (tvec.size() == 1) {
+        Expr dtype = make_const(tvec[0], 0);
+        n->handle_data_type.Set(arg, dtype);
+      } else {
+        // always set data type to be non vectorized so
+        // load/store can still work via scalarization
+        if (tvec.size() != 0 && !n->handle_data_type.count(arg)) {
+          Expr dtype = make_const(tvec[0].with_lanes(1), 0);
+          n->handle_data_type.Set(arg, dtype);
+        }
+      }
+    }
+  }
+  return LoweredFunc(n);
+}
+
 Stmt StorageRewrite(Stmt stmt) {
   stmt = StoragePlanRewriter().Rewrite(stmt, true);
   return VectorAllocRewriter().Mutate(stmt);
diff --git a/src/runtime/c_runtime_api.cc b/src/runtime/c_runtime_api.cc
index 2177fc344889..5e3b3e8034ce 100644
--- a/src/runtime/c_runtime_api.cc
+++ b/src/runtime/c_runtime_api.cc
@@ -28,6 +28,7 @@ inline std::string DeviceName(int type) {
     case kDLCPU: return "cpu";
     case kDLGPU: return "gpu";
     case kDLOpenCL: return "opencl";
+    case kDLVulkan: return "vulkan";
     case kDLMetal: return "metal";
     case kDLVPI: return "vpi";
     case kDLROCM: return "rocm";
diff --git a/src/runtime/module.cc b/src/runtime/module.cc
index 3b95137f4fa4..c0796a61a20d 100644
--- a/src/runtime/module.cc
+++ b/src/runtime/module.cc
@@ -119,6 +119,8 @@ bool RuntimeEnabled(const std::string& target) {
     f_name = "device_api.opengl";
   } else if (target == "mtl" || target == "metal") {
     f_name = "device_api.metal";
+  } else if (target == "vulkan") {
+    f_name = "device_api.vulkan";
   } else if (target == "stackvm") {
     f_name = "codegen.build_stackvm";
   } else if (target == "rpc") {
diff --git a/src/runtime/rocm/rocm_device_api.cc b/src/runtime/rocm/rocm_device_api.cc
index 877907c7e092..6a950069192c 100644
--- a/src/runtime/rocm/rocm_device_api.cc
+++ b/src/runtime/rocm/rocm_device_api.cc
@@ -44,11 +44,12 @@ class ROCMDeviceAPI final : public DeviceAPI {
         value = 64;
         break;
       }
-      case kComputeVersion:
+      case kComputeVersion: {
         hipDeviceProp_t prop;
         ROCM_CALL(hipGetDeviceProperties(&prop, ctx.device_id));
         *rv = prop.gcnArch;
         return;
+      }
     }
     *rv = value;
   }
diff --git a/src/runtime/vulkan/vulkan_common.h b/src/runtime/vulkan/vulkan_common.h
new file mode 100644
index 000000000000..49d60925e0ba
--- /dev/null
+++ b/src/runtime/vulkan/vulkan_common.h
@@ -0,0 +1,284 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file vulkan_common.h
+ * \brief Vulkan common header
+ */
+#ifndef TVM_RUNTIME_VULKAN_VULKAN_COMMON_H_
+#define TVM_RUNTIME_VULKAN_VULKAN_COMMON_H_
+
+#include <tvm/runtime/config.h>
+#include <tvm/runtime/c_runtime_api.h>
+#include <tvm/runtime/packed_func.h>
+#include <tvm/runtime/device_api.h>
+#include <dmlc/logging.h>
+
+#if TVM_VULKAN_RUNTIME
+
+#include <vulkan/vulkan.h>
+#include <mutex>
+#include <string>
+#include <vector>
+#include "../workspace_pool.h"
+
+namespace tvm {
+namespace runtime {
+namespace vulkan {
+
+inline const char* VKGetErrorString(VkResult error) {
+  switch (error) {
+    case VK_SUCCESS: return "VK_SUCCESS";
+    case VK_NOT_READY: return "VK_NOT_READY";
+    case VK_TIMEOUT: return "VK_TIMEOUT";
+    case VK_EVENT_SET: return "VK_EVENT_SET";
+    case VK_EVENT_RESET: return "VK_EVENT_RESET";
+    case VK_INCOMPLETE: return "VK_INCOMPLETE";
+    case VK_ERROR_OUT_OF_HOST_MEMORY: return "VK_ERROR_OUT_OF_HOST_MEMORY";
+    case VK_ERROR_OUT_OF_DEVICE_MEMORY: return "VK_ERROR_OUT_OF_DEVICE_MEMORY";
+    case VK_ERROR_INITIALIZATION_FAILED: return "VK_ERROR_INITIALIZATION_FAILED";
+    case VK_ERROR_DEVICE_LOST: return "VK_ERROR_DEVICE_LOST";
+    case VK_ERROR_MEMORY_MAP_FAILED: return "VK_ERROR_MEMORY_MAP_FAILED";
+    case VK_ERROR_LAYER_NOT_PRESENT: return "VK_ERROR_LAYER_NOT_PRESENT";
+    case VK_ERROR_EXTENSION_NOT_PRESENT: return "VK_ERROR_EXTENSION_NOT_PRESENT";
+    case VK_ERROR_FEATURE_NOT_PRESENT: return "VK_ERROR_FEATURE_NOT_PRESENT";
+    case VK_ERROR_INCOMPATIBLE_DRIVER: return "VK_ERROR_INCOMPATIBLE_DRIVER";
+    case VK_ERROR_TOO_MANY_OBJECTS: return "VK_ERROR_TOO_MANY_OBJECTS";
+    case VK_ERROR_FORMAT_NOT_SUPPORTED: return "VK_ERROR_FORMAT_NOT_SUPPORTED";
+    case VK_ERROR_FRAGMENTED_POOL: return "VK_ERROR_FRAGMENTED_POOL";
+    default: return "Unknown Vulkan error code";
+  }
+}
+
+/*!
+ * \brief Protected Vulkan call
+ * \param func Expression to call.
+ */
+#define VULKAN_CHECK_ERROR(__e)                                         \
+  {                                                                     \
+    CHECK(__e == VK_SUCCESS)                                            \
+        << "Vulan Error, code=" << __e << ": " << vulkan::VKGetErrorString(__e); \
+  }
+
+#define VULKAN_CALL(func)                                             \
+  {                                                                   \
+    VkResult __e = (func);                                            \
+    VULKAN_CHECK_ERROR(__e);                                          \
+  }
+
+/*! \brief Auxiliary context structure for vulkan */
+struct VulkanContext {
+  // phyiscal device
+  VkPhysicalDevice phy_device{nullptr};
+  // Phyiscal device property
+  VkPhysicalDeviceProperties phy_device_prop;
+  // Memory type index for staging.
+  uint32_t staging_mtype_index{0};
+  // whether staging is coherent
+  bool coherent_staging{false};
+  // Memory type index for compute
+  uint32_t compute_mtype_index{0};
+  // The logical device
+  VkDevice device{nullptr};
+  // command queue
+  VkQueue queue{nullptr};
+  // queue family_index;
+  uint32_t queue_family_index{0};
+  // Queue family index.
+  VkQueueFamilyProperties queue_prop;
+};
+
+/*! \brief The buffer object */
+struct VulkanBuffer {
+  /*! \brief underlying buffer */
+  VkBuffer buffer{nullptr};
+  /*! \brief underlying buffer */
+  VkDeviceMemory memory{nullptr};
+};
+
+/*! \brief Buffer only used for stagging */
+struct VulkanStagingBuffer {
+  /*! \brief the corresponding device */
+  VkDevice device{nullptr};
+  /*! \brief underlying buffer */
+  VkBuffer buffer{nullptr};
+  /*! \brief underlying buffer */
+  VkDeviceMemory memory{nullptr};
+  /*! \brief host address */
+  void* host_addr{nullptr};
+  /*! \brief size of the memory */
+  size_t size{0};
+};
+
+/*!
+ * \brief Process global Vulkan workspace.
+ */
+class VulkanWorkspace final : public DeviceAPI {
+ public:
+  // global mutex
+  std::mutex mu;
+  // whether the workspace it initialized.
+  bool initialized_{false};
+  // vulkan instance
+  VkInstance instance_{nullptr};
+  // The physical devices, have 1 to 1 mapping to devices
+  std::vector<VulkanContext> context_;
+  // Destructor
+  ~VulkanWorkspace();
+  // Initialize workspace
+  // Return false if already initialized, otherwise return true.
+  void Init();
+  // override device API
+  void SetDevice(TVMContext ctx) final;
+  void GetAttr(TVMContext ctx, DeviceAttrKind kind, TVMRetValue* rv) final;
+  void* AllocDataSpace(TVMContext ctx,
+                       size_t nbytes,
+                       size_t alignment,
+                       TVMType type_hint) final;
+  void FreeDataSpace(TVMContext ctx, void* ptr) final;
+  void CopyDataFromTo(const void* from,
+                      size_t from_size,
+                      void* to,
+                      size_t to_size,
+                      size_t size,
+                      TVMContext ctx_from,
+                      TVMContext ctx_to,
+                      TVMStreamHandle stream) final;
+  void StreamSync(TVMContext ctx, TVMStreamHandle stream) final;
+  void* AllocWorkspace(TVMContext ctx, size_t size, TVMType type_hint) final;
+  void FreeWorkspace(TVMContext ctx, void* data) final;
+  // get the global workspace
+  static const std::shared_ptr<VulkanWorkspace>& Global();
+};
+
+/*! \brief Helper command buffer resource */
+struct VulkanCommandBuffer {
+  /*! \brief fence to signal the resource is ready to use */
+  VkFence fence{nullptr};
+  /*! \brief The internal command buffer */
+  VkCommandBuffer cmd_buffer{nullptr};
+  /*! \brief Descriptor set used to bind arguments */
+  VkDescriptorSet descriptor_set{nullptr};
+  /*! \brief Internal utilities for write command */
+  VkWriteDescriptorSet write_descriptor_set;
+
+  VulkanCommandBuffer() {
+    write_descriptor_set.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+    write_descriptor_set.pNext = nullptr;
+    write_descriptor_set.dstSet = nullptr;
+    write_descriptor_set.dstBinding = 0;
+    write_descriptor_set.dstArrayElement = 0;
+    write_descriptor_set.descriptorCount = 1;
+    write_descriptor_set.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+    write_descriptor_set.pImageInfo = nullptr;
+    write_descriptor_set.pBufferInfo = nullptr;
+    write_descriptor_set.pTexelBufferView = nullptr;
+  }
+};
+
+/*!
+ * \brief Command pool backed by a fixed size ring buffer.
+ *
+ *  Vulkan requires us not to reuse command buffer until
+ *  All its corresponding jobs have finished.
+ *
+ *  This class to faciliate automatic management
+ *  of the command buffers. A fence is created
+ *  for each launch of command buffer jobs
+ *  and when we try to reuse the same entry
+ *  in the ring, we need to make sure that
+ *  the previous pending job already finishes.
+ *
+ */
+class VulkanCommandPool {
+ public:
+  /*! \brief Maximum number of pending jobs in the pool */
+  static constexpr const int kMaxPending = 4;
+  /*! \brief Maximum number of pending jobs in the pool */
+  static constexpr const int kMaxNumArgs = 16;
+  /*!
+   * \brief constructor
+   * \param vctx The corresponding vulkan context.
+   */
+  explicit VulkanCommandPool(const VulkanContext& vctx);
+  /*! \brief destructor */
+  ~VulkanCommandPool();
+  /*!
+   * \brief Allocate a new command buffer entry
+   *
+   *  The caller must only submit the entry once
+   *  with the given fence in the entry,
+   *  before calling next Alloc.
+   *
+   *  This function may block to wait for a
+   *  previously unfinished command when
+   *  there is more than kMaxPending jobs.
+   *
+   * \returns The allocated entry.
+   */
+  VulkanCommandBuffer* Alloc();
+
+  /*!
+   * \brief Allocate a new command buffer entry
+   * \param dlayout the descriptor layout.
+   *
+   * \returns The allocated entry.
+   */
+  VulkanCommandBuffer* Alloc(const VkDescriptorSetLayout* dlayout);
+
+ private:
+  /*! \brief Local ring buffer */
+  std::vector<VulkanCommandBuffer> ring_;
+  /*! \brief clock pointer */
+  size_t clock_ptr_{0};
+  /*! \brief the corresponding device*/
+  VkDevice device_{nullptr};
+  /*! \brief internal command buffer pool */
+  VkCommandPool cmd_pool_{nullptr};
+  /*! \brief Descriptor pool */
+  VkDescriptorPool descriptor_pool_{nullptr};
+};
+
+/*! \brief Thread local workspace */
+class VulkanThreadEntry {
+ public:
+  /*! \brief The current context */
+  TVMContext context;
+  /*! \brief workspace pool */
+  WorkspacePool pool;
+  /*! \brief The staging buffers */
+  std::vector<VulkanStagingBuffer> staging_buffer_;
+  /*!
+   * \brief Get the command pool of corresponding device;
+   * \param device_id The device id
+   * \return The corresponding command buffer.
+   */
+  VulkanCommandPool* CommandPool(int device_id);
+  /*!
+   * \brief Get the stagging buffer.
+   * \param device_id The device id
+   * \return The corresponding stagging buffer.
+   */
+  VulkanStagingBuffer* StagingBuffer(int device_id, size_t size);
+
+  // constructor
+  VulkanThreadEntry()
+      : pool(static_cast<DLDeviceType>(kDLVulkan), VulkanWorkspace::Global()) {
+    context.device_id = 0;
+    context.device_type = static_cast<DLDeviceType>(kDLVulkan);
+  }
+  ~VulkanThreadEntry();
+  // get the global workspace
+  static VulkanThreadEntry* ThreadLocal();
+
+ private:
+  /*! \brief the command pools */
+  std::vector<std::unique_ptr<VulkanCommandPool> > pool_;
+};
+
+// inline implementation
+
+
+}  // namespace vulkan
+}  // namespace runtime
+}  // namespace tvm
+#endif  // TVM_VULKAN_RUNTIME
+#endif  // TVM_RUNTIME_VULKAN_VULKAN_COMMON_H_
diff --git a/src/runtime/vulkan/vulkan_device_api.cc b/src/runtime/vulkan/vulkan_device_api.cc
new file mode 100644
index 000000000000..99b308d338f9
--- /dev/null
+++ b/src/runtime/vulkan/vulkan_device_api.cc
@@ -0,0 +1,681 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file vulkan_device_api.cc
+ */
+#include "./vulkan_common.h"
+
+#if TVM_VULKAN_RUNTIME
+
+#include <tvm/runtime/registry.h>
+#include <dmlc/thread_local.h>
+#include <cstring>
+
+
+namespace tvm {
+namespace runtime {
+namespace vulkan {
+
+VulkanWorkspace::~VulkanWorkspace() {
+  for (VulkanContext& ctx : context_) {
+    vkDestroyDevice(ctx.device, nullptr);
+  }
+  if (instance_ != nullptr) {
+    vkDestroyInstance(instance_, nullptr);
+  }
+}
+
+const std::shared_ptr<VulkanWorkspace>& VulkanWorkspace::Global() {
+  static std::shared_ptr<VulkanWorkspace> inst = std::make_shared<VulkanWorkspace>();
+  return inst;
+}
+
+void VulkanWorkspace::SetDevice(TVMContext ctx) {
+  VulkanThreadEntry::ThreadLocal()->context.device_id = ctx.device_id;
+}
+
+void VulkanWorkspace::GetAttr(
+    TVMContext ctx, DeviceAttrKind kind, TVMRetValue* rv) {
+  this->Init();
+  size_t index = static_cast<size_t>(ctx.device_id);
+  if (kind == kExist) {
+    *rv = static_cast<int>(index< context_.size());
+    return;
+  }
+  CHECK_LT(index, context_.size())
+      << "Invalid device id " << index;
+  switch (kind) {
+    case kMaxThreadsPerBlock: {
+      VkPhysicalDeviceProperties phy_prop;
+      vkGetPhysicalDeviceProperties(context_[ctx.device_id].phy_device, &phy_prop);
+      int64_t value = phy_prop.limits.maxComputeWorkGroupSize[0];
+      *rv = value;
+      break;
+    }
+    case kWarpSize: {
+      *rv = 1;
+      break;
+    }
+    case kComputeVersion: {
+      VkPhysicalDeviceProperties phy_prop;
+      vkGetPhysicalDeviceProperties(context_[ctx.device_id].phy_device, &phy_prop);
+      int64_t value = phy_prop.apiVersion;
+      std::ostringstream os;
+      os << VK_VERSION_MAJOR(value)
+         << "." << VK_VERSION_MINOR(value)
+         << "." << VK_VERSION_PATCH(value);
+      *rv = os.str();
+      break;
+    }
+    case kExist: break;
+  }
+}
+
+void* VulkanWorkspace::AllocDataSpace(
+    TVMContext ctx, size_t size, size_t alignment, TVMType type_hint) {
+  this->Init();
+
+  VulkanContext& vctx = context_[ctx.device_id];
+
+  VkBufferCreateInfo info;
+  info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+  info.pNext = nullptr;
+  info.flags = 0;
+  info.size = size;
+  info.queueFamilyIndexCount = 1;
+  info.pQueueFamilyIndices = &(vctx.queue_family_index);
+  info.usage =
+      VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
+      VK_BUFFER_USAGE_TRANSFER_DST_BIT |
+      VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
+  // create buffer
+  VkBuffer buffer;
+  VULKAN_CALL(vkCreateBuffer(vctx.device, &info, nullptr, &buffer));
+  // bind to memory
+  VkMemoryAllocateInfo minfo;
+  minfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+  minfo.pNext = nullptr;
+  minfo.allocationSize = size;
+  minfo.memoryTypeIndex = vctx.compute_mtype_index;
+  VkDeviceMemory memory;
+  VULKAN_CALL(vkAllocateMemory(vctx.device, &minfo, nullptr, &memory));
+  VULKAN_CALL(vkBindBufferMemory(vctx.device, buffer, memory, 0));
+
+  VulkanBuffer* pbuf = new VulkanBuffer();
+  pbuf->memory = memory;
+  pbuf->buffer = buffer;
+  return pbuf;
+}
+
+void VulkanWorkspace::FreeDataSpace(TVMContext ctx, void* ptr) {
+  VulkanContext& vctx = context_[ctx.device_id];
+  VulkanBuffer* pbuf = static_cast<VulkanBuffer*>(ptr);
+  vkDestroyBuffer(vctx.device, pbuf->buffer, nullptr);
+  vkFreeMemory(vctx.device, pbuf->memory, nullptr);
+  delete pbuf;
+}
+
+void VulkanWorkspace::CopyDataFromTo(const void* from,
+                                     size_t from_offset,
+                                     void* to,
+                                     size_t to_offset,
+                                     size_t size,
+                                     TVMContext ctx_from,
+                                     TVMContext ctx_to,
+                                     TVMStreamHandle stream) {
+  this->Init();
+  CHECK(stream == nullptr);
+  TVMContext ctx = ctx_from;
+  if (ctx_from.device_type == kDLCPU) ctx = ctx_to;
+  VulkanThreadEntry* tls = VulkanThreadEntry::ThreadLocal();
+  VulkanCommandBuffer* cmd = tls->CommandPool(ctx.device_id)->Alloc();
+
+  VkCommandBufferBeginInfo cb_begin;
+  cb_begin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+  cb_begin.pNext = nullptr;
+  cb_begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+  cb_begin.pInheritanceInfo = 0;
+
+  VkSubmitInfo cb_submit;
+  cb_submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+  cb_submit.pNext = nullptr;
+  cb_submit.waitSemaphoreCount = 0;
+  cb_submit.pWaitSemaphores = nullptr;
+  cb_submit.pWaitDstStageMask = 0;
+  cb_submit.commandBufferCount = 1;
+  cb_submit.pCommandBuffers = &(cmd->cmd_buffer);
+  cb_submit.signalSemaphoreCount = 0;
+  cb_submit.pSignalSemaphores = nullptr;
+
+
+  int from_dev_type = static_cast<int>(ctx_from.device_type);
+  int to_dev_type = static_cast<int>(ctx_to.device_type);
+
+  if (from_dev_type == kDLVulkan && to_dev_type == kDLVulkan) {
+    CHECK_EQ(ctx_from.device_id, ctx_to.device_id)
+        << "Vulkan disallow cross device copy.";
+    const VulkanContext& vctx = context_[ctx_from.device_id];
+    const VulkanBuffer* from_buf = static_cast<const VulkanBuffer*>(from);
+    VulkanBuffer* to_buf = static_cast<VulkanBuffer*>(to);
+    // The assumption is that subsequence ops only perform compute/transfer
+    // 0: begin
+    VULKAN_CALL(vkBeginCommandBuffer(cmd->cmd_buffer, &cb_begin));
+    // 1: copy
+    VkBufferCopy copy_info;
+    copy_info.srcOffset = from_offset;
+    copy_info.dstOffset = to_offset;
+    copy_info.size = size;
+    vkCmdCopyBuffer(cmd->cmd_buffer, from_buf->buffer, to_buf->buffer, 1, &copy_info);
+    // 2: barrier(transfer-> compute|transfer)
+    VkMemoryBarrier barrier_info;
+    barrier_info.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
+    barrier_info.pNext = nullptr;
+    barrier_info.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+    barrier_info.dstAccessMask =
+        (VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT |
+         VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
+    vkCmdPipelineBarrier(
+        cmd->cmd_buffer,
+        VK_PIPELINE_STAGE_TRANSFER_BIT,
+        VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+        0, 1, &barrier_info, 0, nullptr, 0, nullptr);
+    // 3: end
+    VULKAN_CALL(vkEndCommandBuffer(cmd->cmd_buffer));
+    // 4: submit with cmd->fence
+    VULKAN_CALL(vkQueueSubmit(vctx.queue, 1, &cb_submit, cmd->fence));
+  } else if (from_dev_type == kDLVulkan && to_dev_type == kDLCPU) {
+    const VulkanContext& vctx = context_[ctx_from.device_id];
+    const VulkanBuffer* from_buf = static_cast<const VulkanBuffer*>(from);
+    VulkanStagingBuffer* temp = tls->StagingBuffer(ctx_from.device_id, size);
+    // 0: begin
+    VULKAN_CALL(vkBeginCommandBuffer(cmd->cmd_buffer, &cb_begin));
+    // 1: copy
+    VkBufferCopy copy_info;
+    copy_info.srcOffset = from_offset;
+    copy_info.dstOffset = 0;
+    copy_info.size = size;
+    vkCmdCopyBuffer(cmd->cmd_buffer,
+                    from_buf->buffer,
+                    temp->buffer,
+                    1, &copy_info);
+    // 2: end
+    VULKAN_CALL(vkEndCommandBuffer(cmd->cmd_buffer));
+    // 4: submit with cmd->fence
+    VULKAN_CALL(vkQueueSubmit(vctx.queue, 1, &cb_submit, cmd->fence));
+    // Block until done, to make sure temp can be reused later.
+    VULKAN_CALL(vkQueueWaitIdle(vctx.queue));
+    // host side invalidation if access is not coherent.
+    // so writes from GPU is visible to CPU
+    if (!vctx.coherent_staging) {
+      VkMappedMemoryRange mrange;
+      mrange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
+      mrange.pNext = nullptr;
+      mrange.memory = temp->memory;
+      mrange.offset = 0;
+      mrange.size = size;
+      VULKAN_CALL(vkInvalidateMappedMemoryRanges(
+          vctx.device, 1, &mrange));
+    }
+    memcpy(static_cast<char*>(to) + to_offset,
+           static_cast<char*>(temp->host_addr),
+           size);
+  } else if (from_dev_type == kDLCPU && to_dev_type == kDLVulkan) {
+    const VulkanContext& vctx = context_[ctx_to.device_id];
+    const VulkanBuffer* to_buf = static_cast<const VulkanBuffer*>(to);
+    VulkanStagingBuffer* temp = tls->StagingBuffer(ctx_to.device_id, size);
+    memcpy(temp->host_addr,
+           static_cast<const char*>(from) + from_offset,
+           size);
+    // host side flush if access is not coherent.
+    // so writes from CPU is visible to GPU
+    if (!vctx.coherent_staging) {
+      VkMappedMemoryRange mrange;
+      mrange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
+      mrange.pNext = nullptr;
+      mrange.memory = temp->memory;
+      mrange.offset = 0;
+      mrange.size = size;
+      VULKAN_CALL(vkFlushMappedMemoryRanges(vctx.device, 1, &mrange));
+    }
+    VULKAN_CALL(vkBeginCommandBuffer(cmd->cmd_buffer, &cb_begin));
+    // 0: barrier(host->transfer)
+    VkMemoryBarrier barrier_info;
+    barrier_info.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
+    barrier_info.pNext = nullptr;
+    barrier_info.srcAccessMask = 0;
+    barrier_info.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+    vkCmdPipelineBarrier(cmd->cmd_buffer,
+                         VK_PIPELINE_STAGE_HOST_BIT,
+                         VK_PIPELINE_STAGE_TRANSFER_BIT,
+                         0, 1, &barrier_info,
+                         0, nullptr, 0, nullptr);
+    // 1: copy
+    VkBufferCopy copy_info;
+    copy_info.srcOffset = 0;
+    copy_info.dstOffset = to_offset;
+    copy_info.size = size;
+    vkCmdCopyBuffer(cmd->cmd_buffer,
+                    temp->buffer,
+                    to_buf->buffer,
+                    1, &copy_info);
+    // 2: end
+    VULKAN_CALL(vkEndCommandBuffer(cmd->cmd_buffer));
+    // 4: submit with cmd->fence
+    VULKAN_CALL(vkQueueSubmit(vctx.queue, 1, &cb_submit, cmd->fence));
+    // wait until copy finishes, so we can reuse temp next time.
+    VULKAN_CALL(vkQueueWaitIdle(vctx.queue));
+  } else {
+    LOG(FATAL) << "Expect copy from/to Metal or between Metal"
+               << ", from=" << from_dev_type
+               << ", to=" << to_dev_type;
+  }
+}
+
+void VulkanWorkspace::StreamSync(TVMContext ctx, TVMStreamHandle stream) {
+  CHECK(stream == nullptr);
+  VulkanContext& vctx = context_[ctx.device_id];
+  VULKAN_CALL(vkQueueWaitIdle(vctx.queue));
+}
+
+void* VulkanWorkspace::AllocWorkspace(TVMContext ctx, size_t size, TVMType type_hint) {
+  return VulkanThreadEntry::ThreadLocal()->pool.AllocWorkspace(ctx, size);
+}
+
+void VulkanWorkspace::FreeWorkspace(TVMContext ctx, void* data) {
+  VulkanThreadEntry::ThreadLocal()->pool.FreeWorkspace(ctx, data);
+}
+
+// VulkanCommandPool
+VulkanCommandPool::VulkanCommandPool(const VulkanContext& vctx) {
+  ring_.resize(kMaxPending, VulkanCommandBuffer());
+  device_ = vctx.device;
+  {
+    // create command pool
+    VkCommandPoolCreateInfo cmd_pool_cinfo;
+    cmd_pool_cinfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+    cmd_pool_cinfo.pNext = nullptr;
+    cmd_pool_cinfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+    cmd_pool_cinfo.queueFamilyIndex = vctx.queue_family_index;
+    VULKAN_CALL(vkCreateCommandPool(device_, &cmd_pool_cinfo, nullptr, &cmd_pool_));
+  }
+  {
+    // create descriptor pool
+    VkDescriptorPoolSize pool_size;
+    pool_size.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+    pool_size.descriptorCount = kMaxPending * kMaxNumArgs;
+    VkDescriptorPoolCreateInfo descrip_pool_cinfo;
+    descrip_pool_cinfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+    descrip_pool_cinfo.pNext = nullptr;
+    descrip_pool_cinfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
+    descrip_pool_cinfo.maxSets = kMaxPending + 2;
+    descrip_pool_cinfo.poolSizeCount = 1;
+    descrip_pool_cinfo.pPoolSizes = &pool_size;
+    VULKAN_CALL(vkCreateDescriptorPool(
+        device_, &descrip_pool_cinfo, nullptr, &descriptor_pool_));
+  }
+  VkCommandBufferAllocateInfo buffer_alloc_info;
+  buffer_alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+  buffer_alloc_info.pNext = nullptr;
+  buffer_alloc_info.commandPool = cmd_pool_;
+  buffer_alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+  buffer_alloc_info.commandBufferCount = 1;
+
+  VkFenceCreateInfo fence_cinfo;
+  fence_cinfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+  fence_cinfo.pNext = nullptr;
+  fence_cinfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
+
+  for (size_t i = 0; i < ring_.size(); ++i) {
+    VULKAN_CALL(vkAllocateCommandBuffers(
+        device_, &buffer_alloc_info, &(ring_[i].cmd_buffer)));
+    VULKAN_CALL(vkCreateFence(
+        device_, &fence_cinfo, nullptr, &(ring_[i].fence)));
+  }
+}
+
+VulkanCommandPool::~VulkanCommandPool() {
+  // wait device to be idle so we know we can recycle buffers
+  VULKAN_CALL(vkDeviceWaitIdle(device_));
+  // start recycling.
+  for (size_t i = 0; i < ring_.size(); ++i) {
+    if (ring_[i].cmd_buffer != nullptr) {
+      vkFreeCommandBuffers(device_, cmd_pool_, 1, &(ring_[i].cmd_buffer));
+      ring_[i].cmd_buffer = nullptr;
+    }
+    if (ring_[i].fence != nullptr) {
+      vkDestroyFence(device_, ring_[i].fence, nullptr);
+    }
+  }
+  // delete cmd_pool and descriptor pool
+  vkDestroyCommandPool(device_, cmd_pool_, nullptr);
+  vkDestroyDescriptorPool(device_, descriptor_pool_, nullptr);
+}
+
+VulkanCommandBuffer* VulkanCommandPool::Alloc() {
+  return Alloc(nullptr);
+}
+
+VulkanCommandBuffer* VulkanCommandPool::Alloc(
+    const VkDescriptorSetLayout* dlayout) {
+  // always allocate resource in round robin manner
+  VulkanCommandBuffer* e = &(ring_[clock_ptr_]);
+  clock_ptr_ = (clock_ptr_ + 1) % ring_.size();
+  // Wait until previous usage of commad buffer is finished.
+  uint64_t timeout = 1UL << 30UL;
+  VkResult res;
+  res = vkWaitForFences(device_, 1, &(e->fence), 0, timeout);
+  while (res == VK_TIMEOUT) {
+    res = vkWaitForFences(device_, 1, &(e->fence), 0, timeout);
+  }
+  VULKAN_CHECK_ERROR(res);
+  vkResetFences(device_, 1, (&e->fence));
+  if (e->descriptor_set != nullptr) {
+    VULKAN_CALL(vkFreeDescriptorSets(
+        device_, descriptor_pool_, 1, &(e->descriptor_set)));
+    e->descriptor_set = nullptr;
+  }
+  if (dlayout != nullptr) {
+    VkDescriptorSetAllocateInfo alloc_info;
+    alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+    alloc_info.pNext = nullptr;
+    alloc_info.descriptorPool = descriptor_pool_;
+    alloc_info.descriptorSetCount = 1;
+    alloc_info.pSetLayouts = dlayout;
+    VULKAN_CALL(vkAllocateDescriptorSets(
+        device_, &alloc_info, &(e->descriptor_set)));
+  }
+  return e;
+}
+
+// VulkanThreadEntry
+typedef dmlc::ThreadLocalStore<VulkanThreadEntry> VulkanThreadStore;
+
+VulkanThreadEntry* VulkanThreadEntry::ThreadLocal() {
+  return VulkanThreadStore::Get();
+}
+
+VulkanCommandPool* VulkanThreadEntry::CommandPool(int device_id) {
+  while (pool_.size() <= static_cast<size_t>(device_id)) {
+    pool_.emplace_back(std::unique_ptr<VulkanCommandPool>());
+  }
+  if (pool_[device_id] == nullptr) {
+    const VulkanContext& vctx =
+        VulkanWorkspace::Global()->context_[device_id];
+    pool_[device_id].reset(new VulkanCommandPool(vctx));
+  }
+  return pool_[device_id].get();
+}
+
+VulkanStagingBuffer*
+VulkanThreadEntry::StagingBuffer(int device_id, size_t size) {
+  if (staging_buffer_.size() <= static_cast<size_t>(device_id)) {
+    staging_buffer_.resize(device_id + 1, VulkanStagingBuffer());
+  }
+  VulkanStagingBuffer& buf = staging_buffer_[device_id];
+
+  if (buf.device != nullptr && buf.size < size) {
+    // free previous buffer
+    if (buf.host_addr != nullptr) {
+      vkUnmapMemory(buf.device, buf.memory);
+    }
+    if (buf.memory != nullptr) {
+      vkFreeMemory(buf.device, buf.memory, nullptr);
+    }
+    if (buf.buffer != nullptr) {
+      vkDestroyBuffer(buf.device, buf.buffer, nullptr);
+    }
+    buf.host_addr = nullptr;
+    buf.memory = nullptr;
+    buf.buffer = nullptr;
+  }
+  const VulkanContext& vctx =
+      VulkanWorkspace::Global()->context_[device_id];
+
+  if (buf.device == nullptr) {
+    buf.device = vctx.device;
+  }
+  if (buf.memory == nullptr) {
+    // allocate the stagging buffer memory if necessary
+    VkBufferCreateInfo info;
+    info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+    info.pNext = nullptr;
+    info.flags = 0;
+    info.size = size;
+    info.queueFamilyIndexCount = 1;
+    info.pQueueFamilyIndices = &(vctx.queue_family_index);
+    info.usage =
+        VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
+        VK_BUFFER_USAGE_TRANSFER_DST_BIT;
+    VULKAN_CALL(vkCreateBuffer(vctx.device, &info, nullptr, &(buf.buffer)));
+    VkMemoryAllocateInfo minfo;
+    minfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+    minfo.pNext = nullptr;
+    minfo.allocationSize = size;
+    minfo.memoryTypeIndex = vctx.staging_mtype_index;
+    VULKAN_CALL(vkAllocateMemory(vctx.device, &minfo, nullptr, &(buf.memory)));
+    VULKAN_CALL(vkBindBufferMemory(vctx.device, (buf.buffer), buf.memory, 0));
+    VULKAN_CALL(vkMapMemory(vctx.device, buf.memory, 0, size, 0, &(buf.host_addr)));
+    buf.size = size;
+  }
+  memset(buf.host_addr, 0, size);
+  return &buf;
+}
+
+VulkanThreadEntry::~VulkanThreadEntry() {
+  // Because the thread entry refers to Device API
+  // The command buffer always will be destroyed before
+  // the instance and device get destroyed.
+  // The destruction need to be manually called
+  // to ensure the destruction order.
+  pool_.clear();
+  for (VulkanStagingBuffer buf : staging_buffer_) {
+    if (buf.host_addr != nullptr) {
+      vkUnmapMemory(buf.device, buf.memory);
+    }
+    if (buf.memory != nullptr) {
+      vkFreeMemory(buf.device, buf.memory, nullptr);
+    }
+    if (buf.buffer != nullptr) {
+      vkDestroyBuffer(buf.device, buf.buffer, nullptr);
+    }
+  }
+}
+
+VkInstance CreateInstance() {
+  VkApplicationInfo app_info;
+  app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+  app_info.pNext = nullptr;
+  app_info.pApplicationName = "TVM";
+  app_info.applicationVersion = 0;
+  app_info.pEngineName = "";
+  app_info.engineVersion = 0;
+  app_info.apiVersion = VK_MAKE_VERSION(1, 0, 65);
+
+  VkInstanceCreateInfo inst_info;
+  inst_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+  inst_info.pNext = nullptr;
+  inst_info.flags = 0;
+  inst_info.pApplicationInfo = &app_info;
+  inst_info.enabledLayerCount = 0;
+  inst_info.ppEnabledLayerNames = nullptr;
+  inst_info.enabledExtensionCount = 0;
+  inst_info.ppEnabledExtensionNames = nullptr;
+
+  VkInstance inst;
+  VULKAN_CALL(vkCreateInstance(&inst_info, nullptr, &inst));
+  return inst;
+}
+
+// find suitable mem_type_index for staging and compute
+void FindMemoryTypeIndex(VulkanContext* vctx) {
+  // Find suitable compute index.
+  VkBuffer buffer;
+  VkMemoryRequirements req_staging, req_compute;
+  VkBufferCreateInfo info;
+  info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+  info.pNext = nullptr;
+  info.flags = 0;
+  info.size = 1024;
+  info.queueFamilyIndexCount = 1;
+  info.pQueueFamilyIndices = &(vctx->queue_family_index);
+
+  // get staging requirement
+  info.usage =
+      VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
+      VK_BUFFER_USAGE_TRANSFER_DST_BIT;
+  VULKAN_CALL(vkCreateBuffer(vctx->device, &info, nullptr, &buffer));
+  vkGetBufferMemoryRequirements(vctx->device, buffer, &req_staging);
+  vkDestroyBuffer(vctx->device, buffer, nullptr);
+  // get compute requirement
+  info.usage =
+      VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
+      VK_BUFFER_USAGE_TRANSFER_DST_BIT |
+      VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
+  VULKAN_CALL(vkCreateBuffer(vctx->device, &info, nullptr, &buffer));
+  vkGetBufferMemoryRequirements(vctx->device, buffer, &req_compute);
+  vkDestroyBuffer(vctx->device, buffer, nullptr);
+
+  // Query phyiscal device property
+  // find a memory that is host visible, no need to be consistent
+  int win_rank = -1;
+  VkPhysicalDeviceMemoryProperties prop;
+  vkGetPhysicalDeviceMemoryProperties(vctx->phy_device, &prop);
+
+  for (uint32_t k = 0; k < prop.memoryTypeCount; ++k) {
+    VkMemoryType ty = prop.memoryTypes[k];
+    size_t heap_size = prop.memoryHeaps[ty.heapIndex].size;
+    // host visible
+    if (!(ty.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) continue;
+    // match copy requirment
+    if (!(req_staging.memoryTypeBits & (1 << k))) continue;
+    if (heap_size < 1024) continue;
+    int rank = 0;
+    rank += ty.propertyFlags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
+    if (rank > win_rank) {
+      win_rank = rank;
+      vctx->staging_mtype_index = k;
+      vctx->coherent_staging =
+          ty.propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
+    }
+  }
+  CHECK_GE(win_rank, 0) << "Cannot find suitable staging memory on device.";
+
+  win_rank = -1;
+  for (uint32_t k = 0; k < prop.memoryTypeCount; ++k) {
+    VkMemoryType ty = prop.memoryTypes[k];
+    size_t heap_size = prop.memoryHeaps[ty.heapIndex].size;
+    // host visible
+    if (!(ty.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) continue;
+    // match copy requirment
+    if (!(req_staging.memoryTypeBits & (1 << k))) continue;
+    if (heap_size < 1024) continue;
+    int rank = 0;
+    // prefer not host visible
+    rank += !(ty.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
+    if (rank > win_rank) {
+      win_rank = rank;
+      vctx->compute_mtype_index = k;
+    }
+  }
+  CHECK_GE(win_rank, 0) << "Cannot find suitable staging memory on device.";
+}
+
+// Get all logic devices that support compute
+std::vector<VulkanContext> GetContext(VkInstance instance) {
+  std::vector<VulkanContext> result;
+  uint32_t phy_dev_count = 0;
+  VULKAN_CALL(vkEnumeratePhysicalDevices(
+      instance, &phy_dev_count, nullptr));
+  std::vector<VkPhysicalDevice> all_phy_devs(phy_dev_count);
+  VULKAN_CALL(vkEnumeratePhysicalDevices(
+      instance, &phy_dev_count, dmlc::BeginPtr(all_phy_devs)));
+  for (VkPhysicalDevice phy_dev : all_phy_devs) {
+    uint32_t queue_prop_count = 0;
+    vkGetPhysicalDeviceQueueFamilyProperties(
+        phy_dev, &queue_prop_count, nullptr);
+    std::vector<VkQueueFamilyProperties> queue_props(queue_prop_count);
+    vkGetPhysicalDeviceQueueFamilyProperties(
+        phy_dev, &queue_prop_count, dmlc::BeginPtr(queue_props));
+    uint32_t queue_family_index = 0;
+    std::vector<VkDeviceQueueCreateInfo> queue_create_info;
+
+    for (uint32_t i = 0; i < queue_props.size(); i++) {
+      // find queues that support compute
+      if (VK_QUEUE_COMPUTE_BIT & queue_props[i].queueFlags) {
+        float priority = 1.0f;
+
+        VkDeviceQueueCreateInfo info;
+        info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+        info.pNext = nullptr;
+        info.flags = 0;
+        info.queueFamilyIndex = i;
+        info.queueCount = 1;
+        info.pQueuePriorities = &priority;
+
+        queue_create_info.push_back(info);
+        // only use the first available queue for now
+        if (queue_create_info.size() == 0) {
+          queue_family_index = i;
+        }
+      }
+    }
+    if (queue_create_info.size() == 0) continue;
+
+    VkDeviceCreateInfo device_create_info;
+    device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+    device_create_info.pNext = nullptr;
+    device_create_info.flags = 0;
+    device_create_info.queueCreateInfoCount
+        = static_cast<uint32_t>(queue_create_info.size());
+    device_create_info.pQueueCreateInfos = queue_create_info.data();
+    device_create_info.enabledLayerCount = 0;
+    device_create_info.ppEnabledLayerNames = nullptr;
+    device_create_info.enabledExtensionCount = 0;
+    device_create_info.ppEnabledExtensionNames = nullptr;
+    device_create_info.pEnabledFeatures = nullptr;
+
+    VulkanContext ctx;
+    // setup context
+    ctx.phy_device = phy_dev;
+    vkGetPhysicalDeviceProperties(ctx.phy_device, &(ctx.phy_device_prop));
+    VULKAN_CALL(vkCreateDevice(
+        phy_dev, &device_create_info, nullptr, &(ctx.device)));
+    vkGetDeviceQueue(ctx.device, queue_family_index, 0, &(ctx.queue));
+    ctx.queue_family_index = queue_family_index;
+    FindMemoryTypeIndex(&ctx);
+    // Find suitable memory type for staging and compute
+    result.push_back(ctx);
+  }
+  return result;
+}
+
+void VulkanWorkspace::Init() {
+  if (initialized_) return;
+  std::lock_guard<std::mutex>(this->mu);
+  if (initialized_) return;
+  initialized_ = true;
+  instance_ = CreateInstance();
+  context_ = GetContext(instance_);
+  LOG(INFO) << "Initialzie Vulkan with " << context_.size() << " devices..";
+  for (size_t i = 0; i < context_.size(); ++i) {
+    LOG(INFO) << "vulkan(" << i
+              <<  ")=\'" << context_[i].phy_device_prop.deviceName
+              << "\' phy_dev_id=" << context_[i].phy_device;
+  }
+}
+
+bool InitVulkan(TVMArgs args, TVMRetValue* rv) {
+  vulkan::VulkanWorkspace::Global()->Init();
+  return true;
+}
+
+TVM_REGISTER_GLOBAL("device_api.vulkan")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    DeviceAPI* ptr = VulkanWorkspace::Global().get();
+    *rv = static_cast<void*>(ptr);
+  });
+
+}  // namespace vulkan
+}  // namespace runtime
+}  // namespace tvm
+
+#endif  // TVM_VULKAN_RUNTIME
diff --git a/src/runtime/vulkan/vulkan_module.cc b/src/runtime/vulkan/vulkan_module.cc
new file mode 100644
index 000000000000..50ed744c512c
--- /dev/null
+++ b/src/runtime/vulkan/vulkan_module.cc
@@ -0,0 +1,424 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file vulkan_module.cc
+ */
+#include "./vulkan_module.h"
+
+#if TVM_VULKAN_RUNTIME
+
+#include <dmlc/memory_io.h>
+#include <tvm/runtime/registry.h>
+#include <tvm/runtime/module.h>
+#include <array>
+#include <string>
+#include <mutex>
+#include "./vulkan_common.h"
+#include "../pack_args.h"
+#include "../thread_storage_scope.h"
+#include "../meta_data.h"
+#include "../file_util.h"
+
+
+namespace tvm {
+namespace runtime {
+
+void VulkanShader::Save(dmlc::Stream* writer) const {
+  writer->Write(flag);
+  writer->Write(data);
+}
+
+bool VulkanShader::Load(dmlc::Stream* reader) {
+  if (!reader->Read(&flag)) return false;
+  if (!reader->Read(&data)) return false;
+  return true;
+}
+
+// Multi-device enabled module.
+class VulkanModuleNode final :public runtime::ModuleNode {
+ public:
+  // Pipeline cache states
+  struct PipelineEntry {
+    VkShaderModule shader{nullptr};
+    VkPipelineLayout pipeline_layout{nullptr};
+    VkDescriptorSetLayout descriptor_layout{nullptr};
+    VkPipeline pipeline{nullptr};
+  };
+  // constructor
+  explicit VulkanModuleNode(std::unordered_map<std::string, VulkanShader> smap,
+                            std::unordered_map<std::string, FunctionInfo> fmap,
+                            std::string source)
+      : smap_(smap), fmap_(fmap), source_(source) {
+  }
+
+  ~VulkanModuleNode() {
+    // cleanup vulkan related caches.
+    for (DeviceEntry& e : finfo_) {
+      if (e.device == nullptr) continue;
+      for (auto &kv : e.smap) {
+        PipelineEntry& pe = kv.second;
+        vkDestroyShaderModule(e.device, pe.shader, nullptr);
+        vkDestroyDescriptorSetLayout(e.device, pe.descriptor_layout, nullptr);
+        vkDestroyPipelineLayout(e.device, pe.pipeline_layout, nullptr);
+        vkDestroyPipeline(e.device, pe.pipeline, nullptr);
+      }
+    }
+  }
+  const char* type_key() const final {
+    return "vulkan";
+  }
+
+  PackedFunc GetFunction(
+      const std::string& name,
+      const std::shared_ptr<ModuleNode>& sptr_to_self) final;
+
+  void SaveToFile(const std::string& file_name,
+                  const std::string& format) final {
+    std::string fmt = GetFileFormat(file_name, format);
+    CHECK_EQ(fmt, fmt_)
+        << "Can only save to customized format vulkan";
+    std::string meta_file = GetMetaFilePath(file_name);
+    SaveMetaDataToFile(meta_file, fmap_);
+    std::string data_bin;
+    dmlc::MemoryStringStream fs(&data_bin);
+    dmlc::Stream* stream = &fs;
+    uint32_t magic = kVulkanModuleMagic;
+    stream->Write(magic);
+    stream->Write(smap_);
+    SaveBinaryToFile(file_name, data_bin);
+  }
+
+  void SaveToBinary(dmlc::Stream* stream) final {
+    stream->Write(fmt_);
+    stream->Write(fmap_);
+    stream->Write(smap_);
+  }
+  std::string GetSource(const std::string& format) final {
+    // can only return source code.
+    return source_;
+  }
+
+  // get a from primary context in device_id
+  PipelineEntry GetPipeline(size_t device_id,
+                            const std::string& func_name,
+                            size_t num_pack_args) {
+    vulkan::VulkanWorkspace* w = vulkan::VulkanWorkspace::Global().get();
+    CHECK_LT(device_id, w->context_.size());
+    // start lock scope.
+    std::lock_guard<std::mutex> lock(mutex_);
+    if (finfo_.size() <= device_id) {
+      finfo_.resize(device_id + 1, DeviceEntry());
+    }
+    DeviceEntry& e = finfo_[device_id];
+    auto it = e.smap.find(func_name);
+    if (it != e.smap.end()) return it->second;
+    PipelineEntry pe;
+    if (e.device == nullptr) {
+      e.device = w->context_[device_id].device;
+    }
+    {
+      // create shader
+      auto sit = smap_.find(func_name);
+      CHECK(sit != smap_.end());
+      const std::vector<uint32_t>& data = sit->second.data;
+      VkShaderModuleCreateInfo shader_cinfo;
+      shader_cinfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+      shader_cinfo.pNext = nullptr;
+      shader_cinfo.flags = 0;
+      shader_cinfo.codeSize = data.size() * sizeof(uint32_t);
+      shader_cinfo.pCode = data.data();
+      VULKAN_CALL(vkCreateShaderModule(
+          e.device, &shader_cinfo, nullptr, &(pe.shader)));
+    }
+    std::vector<VkDescriptorSetLayoutBinding> arg_binding;
+    uint32_t num_pod = 0, num_buffer = 0;
+    {
+      auto fit = fmap_.find(func_name);
+      CHECK(fit != fmap_.end());
+      for (TVMType arg_type : fit->second.arg_types) {
+        if (arg_type.code == kHandle) {
+          VkDescriptorSetLayoutBinding bd;
+          bd.binding = num_buffer;
+          bd.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+          bd.descriptorCount = 1;
+        bd.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+        bd.pImmutableSamplers = nullptr;
+        arg_binding.push_back(bd);
+        ++num_buffer;
+        } else {
+          ++num_pod;
+        }
+      }
+    }
+
+    VkDescriptorSetLayoutCreateInfo descrip_cinfo;
+    descrip_cinfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+    descrip_cinfo.pNext = nullptr;
+    descrip_cinfo.flags = 0;
+    descrip_cinfo.bindingCount = arg_binding.size();
+    descrip_cinfo.pBindings = arg_binding.data();
+    VULKAN_CALL(vkCreateDescriptorSetLayout(
+        e.device, &descrip_cinfo, nullptr, &(pe.descriptor_layout)));
+
+    VkPushConstantRange crange;
+    crange.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+    crange.offset = 0;
+    crange.size = sizeof(ArgUnion) * num_pack_args;
+
+    VkPipelineLayoutCreateInfo playout_cinfo;
+    playout_cinfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+    playout_cinfo.pNext = nullptr;
+    playout_cinfo.flags = 0;
+    playout_cinfo.setLayoutCount = 1;
+    playout_cinfo.pSetLayouts = &(pe.descriptor_layout);
+
+    if (num_pack_args != 0) {
+      playout_cinfo.pushConstantRangeCount = 1;
+      playout_cinfo.pPushConstantRanges = &crange;
+      CHECK_LE(crange.size,
+               w->context_[device_id].phy_device_prop.limits.maxPushConstantsSize);
+    } else {
+      playout_cinfo.pushConstantRangeCount = 0;
+      playout_cinfo.pPushConstantRanges = nullptr;
+    }
+
+    VULKAN_CALL(vkCreatePipelineLayout(
+        e.device, &playout_cinfo, nullptr, &(pe.pipeline_layout)));
+    VkComputePipelineCreateInfo pipeline_cinfo;
+    pipeline_cinfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
+    pipeline_cinfo.pNext = nullptr;
+    pipeline_cinfo.flags = 0;
+    pipeline_cinfo.stage.sType =
+        VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+    pipeline_cinfo.stage.pNext = nullptr;
+    pipeline_cinfo.stage.flags = 0;
+    pipeline_cinfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
+    pipeline_cinfo.stage.module = pe.shader;
+    pipeline_cinfo.stage.pName = func_name.c_str();
+    pipeline_cinfo.stage.pSpecializationInfo = nullptr;
+    pipeline_cinfo.layout = pe.pipeline_layout;
+    pipeline_cinfo.basePipelineHandle = nullptr;
+    pipeline_cinfo.basePipelineIndex = 0;
+    VULKAN_CALL(vkCreateComputePipelines(
+        e.device, nullptr, 1, &pipeline_cinfo, nullptr, &(pe.pipeline)));
+    e.smap[func_name] = pe;
+    return pe;
+  }
+
+ private:
+  // device specific entry
+  struct DeviceEntry {
+    VkDevice device{nullptr};
+    std::unordered_map<std::string, PipelineEntry> smap;
+  };
+  // the binary data
+  std::vector<uint32_t> data_;
+  // function information table.
+  std::unordered_map<std::string, VulkanShader> smap_;
+  // function information table.
+  std::unordered_map<std::string, FunctionInfo> fmap_;
+  // The format
+  std::string fmt_{"vulkan"};
+  // The source
+  std::string source_;
+  // device local pipeline information.
+  std::vector<DeviceEntry> finfo_;
+  // internal mutex when updating the module
+  std::mutex mutex_;
+};
+
+// a wrapped function class to get packed fucn.
+class VulkanWrappedFunc {
+ public:
+  // initialize the VULKAN function.
+  void Init(VulkanModuleNode* m,
+            std::shared_ptr<ModuleNode> sptr,
+            const std::string& func_name,
+            size_t num_buffer_args,
+            size_t num_pack_args,
+            const std::vector<std::string>& thread_axis_tags) {
+    w_ = vulkan::VulkanWorkspace::Global().get();
+    m_ = m;
+    sptr_ = sptr;
+    func_name_ = func_name;
+    num_buffer_args_ = num_buffer_args;
+    num_pack_args_ = num_pack_args;
+    thread_axis_cfg_.Init(num_buffer_args + num_pack_args, thread_axis_tags);
+  }
+  // invoke the function with void arguments
+  void operator()(TVMArgs args,
+                  TVMRetValue* rv,
+                  const ArgUnion* pack_args) const {
+    vulkan::VulkanThreadEntry* tls = vulkan::VulkanThreadEntry::ThreadLocal();
+    int device_id = tls->context.device_id;
+    CHECK_LT(device_id, kVulkanMaxNumDevice);
+    const vulkan::VulkanContext& vctx = w_->context_[device_id];
+    VulkanModuleNode::PipelineEntry& pe = scache_[device_id];
+    if (pe.pipeline == nullptr) {
+      pe = m_->GetPipeline(device_id, func_name_, num_pack_args_);
+    }
+    ThreadWorkLoad wl = thread_axis_cfg_.Extract(args);
+    vulkan::VulkanCommandBuffer* cmd = tls->CommandPool(device_id)->Alloc(
+        &(pe.descriptor_layout));
+
+    cmd->write_descriptor_set.dstSet = cmd->descriptor_set;
+
+    // setup descriptors
+    for (uint32_t i = 0; i < num_buffer_args_; ++i) {
+      void* buf = args[static_cast<int>(i)];
+      VkDescriptorBufferInfo binfo;
+      binfo.buffer = static_cast<vulkan::VulkanBuffer*>(buf)->buffer;
+      binfo.offset = 0;
+      binfo.range = VK_WHOLE_SIZE;
+      cmd->write_descriptor_set.dstBinding = i;
+      cmd->write_descriptor_set.pBufferInfo = &binfo;
+      vkUpdateDescriptorSets(
+          vctx.device, 1, &(cmd->write_descriptor_set), 0, nullptr);
+    }
+
+    // dispatch
+    VkCommandBufferBeginInfo cb_begin;
+    cb_begin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+    cb_begin.pNext = nullptr;
+    cb_begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+    cb_begin.pInheritanceInfo = 0;
+
+    VkSubmitInfo cb_submit;
+    cb_submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+    cb_submit.pNext = nullptr;
+    cb_submit.waitSemaphoreCount = 0;
+    cb_submit.pWaitSemaphores = nullptr;
+    cb_submit.pWaitDstStageMask = 0;
+    cb_submit.commandBufferCount = 1;
+    cb_submit.pCommandBuffers = &(cmd->cmd_buffer);
+    cb_submit.signalSemaphoreCount = 0;
+    cb_submit.pSignalSemaphores = nullptr;
+    // 0: begin
+    VULKAN_CALL(vkBeginCommandBuffer(cmd->cmd_buffer, &cb_begin));
+    // 1: dispatch
+    vkCmdBindPipeline(
+        cmd->cmd_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pe.pipeline);
+    vkCmdBindDescriptorSets(
+        cmd->cmd_buffer, VK_PIPELINE_BIND_POINT_COMPUTE,
+        pe.pipeline_layout, 0, 1, &(cmd->descriptor_set), 0, nullptr);
+    // bind push constant if necessary
+    if (num_pack_args_ != 0) {
+      vkCmdPushConstants(
+          cmd->cmd_buffer,
+          pe.pipeline_layout,
+          VK_SHADER_STAGE_COMPUTE_BIT,
+          0, num_pack_args_ * sizeof(ArgUnion),
+          pack_args);
+    }
+    vkCmdDispatch(
+        cmd->cmd_buffer, wl.grid_dim(0), wl.grid_dim(1), wl.grid_dim(2));
+    // 2: barrier(compute->compute|transfer)
+    VkMemoryBarrier barrier_info;
+    barrier_info.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
+    barrier_info.pNext = nullptr;
+    barrier_info.srcAccessMask =
+        VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT;
+    barrier_info.dstAccessMask =
+        (VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT |
+         VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
+    vkCmdPipelineBarrier(
+        cmd->cmd_buffer,
+        VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+        VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+        0, 1, &barrier_info, 0, nullptr, 0, nullptr);
+    // 3: end
+    VULKAN_CALL(vkEndCommandBuffer(cmd->cmd_buffer));
+    // 4: submit with cmd->fence
+    VULKAN_CALL(vkQueueSubmit(vctx.queue, 1, &cb_submit, cmd->fence));
+  }
+
+ private:
+  // Reference to global workspace.
+  vulkan::VulkanWorkspace* w_;
+  // internal module
+  VulkanModuleNode* m_;
+  // the resource holder
+  std::shared_ptr<ModuleNode> sptr_;
+  // The name of the function.
+  std::string func_name_;
+  // Number of buffer arguments
+  size_t num_buffer_args_;
+  // number of packed arguments.
+  size_t num_pack_args_;
+  // Device state cache per device.
+  // mark as mutable, to enable lazy initialization
+  mutable std::array<VulkanModuleNode::PipelineEntry, kVulkanMaxNumDevice> scache_;
+  // thread axis configuration
+  ThreadAxisConfig thread_axis_cfg_;
+};
+
+PackedFunc VulkanModuleNode::GetFunction(
+      const std::string& name,
+      const std::shared_ptr<ModuleNode>& sptr_to_self) {
+  CHECK_EQ(sptr_to_self.get(), this);
+  CHECK_NE(name, symbol::tvm_module_main)
+      << "Device function do not have main";
+  auto it = fmap_.find(name);
+  if (it == fmap_.end()) return PackedFunc();
+  const FunctionInfo& info = it->second;
+  VulkanWrappedFunc f;
+  size_t num_buffer_args = NumBufferArgs(info.arg_types);
+  f.Init(this, sptr_to_self, name,
+         num_buffer_args, info.arg_types.size() - num_buffer_args,
+         info.thread_axis_tags);
+  return PackFuncNonBufferArg(f, info.arg_types);
+}
+
+Module VulkanModuleCreate(
+    std::unordered_map<std::string, VulkanShader> smap,
+    std::unordered_map<std::string, FunctionInfo> fmap,
+    std::string source) {
+  vulkan::VulkanWorkspace::Global()->Init();
+  std::shared_ptr<VulkanModuleNode> n =
+      std::make_shared<VulkanModuleNode>(smap, fmap, source);
+  return Module(n);
+}
+
+// Load module from module.
+Module VulkanModuleLoadFile(const std::string& file_name,
+                            const std::string& format) {
+  std::string data;
+  std::unordered_map<std::string, VulkanShader> smap;
+  std::unordered_map<std::string, FunctionInfo> fmap;
+  std::string fmt = GetFileFormat(file_name, format);
+  std::string meta_file = GetMetaFilePath(file_name);
+  LoadBinaryFromFile(file_name, &data);
+  LoadMetaDataFromFile(meta_file, &fmap);
+  dmlc::MemoryStringStream fs(&data);
+  dmlc::Stream* stream = &fs;
+  uint32_t magic;
+  stream->Read(&magic);
+  CHECK_EQ(magic, kVulkanModuleMagic)
+      << "VulkanModule Magic mismatch";
+  stream->Read(&smap);
+  return VulkanModuleCreate(smap, fmap, "");
+}
+
+Module VulkanModuleLoadBinary(void* strm) {
+  dmlc::Stream* stream = static_cast<dmlc::Stream*>(strm);
+  std::unordered_map<std::string, VulkanShader> smap;
+  std::unordered_map<std::string, FunctionInfo> fmap;
+
+  std::string fmt;
+  stream->Read(&fmt);
+  stream->Read(&fmap);
+  stream->Read(&smap);
+  return VulkanModuleCreate(smap, fmap, "");
+}
+
+TVM_REGISTER_GLOBAL("module.loadfile_vulkan")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    *rv = VulkanModuleLoadFile(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("module.loadbinary_vulkan")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    *rv = VulkanModuleLoadBinary(args[0]);
+  });
+}  // namespace runtime
+}  // namespace tvm
+#endif  // TVM_VULKAN_RUNTIME
diff --git a/src/runtime/vulkan/vulkan_module.h b/src/runtime/vulkan/vulkan_module.h
new file mode 100644
index 000000000000..65b6d24c8ad6
--- /dev/null
+++ b/src/runtime/vulkan/vulkan_module.h
@@ -0,0 +1,64 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file metal_module.h
+ * \brief Execution handling of Metal kernels
+ */
+#ifndef TVM_RUNTIME_VULKAN_VULKAN_MODULE_H_
+#define TVM_RUNTIME_VULKAN_VULKAN_MODULE_H_
+
+#include <tvm/runtime/config.h>
+#include <tvm/runtime/packed_func.h>
+#include <dmlc/type_traits.h>
+#include <memory>
+#include <vector>
+#include <string>
+#include <unordered_map>
+#include "../meta_data.h"
+
+namespace tvm {
+namespace runtime {
+/*! \brief Maximum number of GPU supported in VulkanModule. */
+static constexpr const int kVulkanMaxNumDevice = 8;
+
+/*! \brief TVM Vulkan binary pack magic number */
+static constexpr const int kVulkanModuleMagic = 0x02700027;
+
+/*!
+ * \brief A single VK shader program
+ *
+ *  Due to the global resource declaration.
+ *  Current SPIRV only allows one entry program per shader,
+ *  making it less useful for a Module like system.
+ *
+ *  Instead we pass in map of str->VulkanShader until
+ *  there is a native solution available.
+ */
+struct VulkanShader {
+  /*! \brief header flag */
+  uint32_t flag{0};
+  /*! \brief Data segment */
+  std::vector<uint32_t> data;
+
+  void Save(dmlc::Stream *writer) const;
+  bool Load(dmlc::Stream *reader);
+};
+
+/*!
+ * \brief create a metal module from data.
+ *
+ * \param pmap The program map.
+ * \param fmap The function information map.
+ * \param source Optional, source code.
+ */
+Module VulkanModuleCreate(
+    std::unordered_map<std::string, VulkanShader> smap,
+    std::unordered_map<std::string, FunctionInfo> fmap,
+    std::string source);
+}  // namespace runtime
+}  // namespace tvm
+
+namespace dmlc {
+DMLC_DECLARE_TRAITS(has_saveload, ::tvm::runtime::VulkanShader, true);
+}  // namespace dmlc
+
+#endif  // TVM_RUNTIME_VULKAN_VULKAN_MODULE_H_
diff --git a/tests/python/integration/test_ewise.py b/tests/python/integration/test_ewise.py
index e56fac6734bb..414b1ff008fe 100644
--- a/tests/python/integration/test_ewise.py
+++ b/tests/python/integration/test_ewise.py
@@ -18,7 +18,8 @@ def test_exp():
     def check_device(device, host="stackvm"):
         if not tvm.module.enabled(host):
             return
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             return
         fexp = tvm.build(s, [A, B],
                          device, host,
@@ -33,6 +34,7 @@ def check_device(device, host="stackvm"):
             b.asnumpy(), np.exp(a.asnumpy()), rtol=1e-5)
 
     check_device("cuda", "llvm")
+    check_device("vulkan")
     check_device("opencl")
 
 
@@ -75,11 +77,12 @@ def run(dtype):
         bx, tx = s[B].split(B.op.axis[0], factor=num_thread)
 
         def check_device(device):
-            if not tvm.module.enabled(device):
+            ctx = tvm.context(device, 0)
+            if not ctx.exist:
                 print("skip because %s is not enabled.." % device)
                 return
-            ctx = tvm.context(device, 0)
-            if str(ctx).startswith('gpu'):
+            target = tvm.target.create(device)
+            if "cpu" not in target.keys:
                 s[B].bind(bx, tvm.thread_axis("blockIdx.x"))
                 s[B].bind(tx, tvm.thread_axis("threadIdx.x"))
             func = tvm.build(s, [A, B], device)
@@ -95,6 +98,8 @@ def check_device(device):
         check_device("cuda")
         check_device("opencl")
         check_device("metal")
+        if dtype == "uint32":
+            check_device("vulkan")
     run('uint32')
     run('uint64')
 
@@ -121,14 +126,14 @@ def run(dtype):
 
         # one line to build the function.
         def check_device(device):
-            if not tvm.module.enabled(device):
+            ctx = tvm.context(device, 0)
+            if not ctx.exist:
                 print("skip because %s is not enabled.." % device)
                 return
             fadd = tvm.build(s, [A, B, C],
                              device,
                              name="myadd")
-            print(fadd.imported_modules[0].get_source())
-            ctx = tvm.context(device, 0)
+
             # launch the kernel.
             n = 1024
             a = tvm.nd.array((np.random.uniform(size=n) * 256).astype(A.dtype), ctx)
@@ -142,6 +147,8 @@ def check_device(device):
         check_device("opencl")
         check_device("metal")
         check_device("cuda")
+        check_device("vulkan")
+
     run("float32")
     run("int32")
     run("int64")
@@ -149,7 +156,7 @@ def check_device(device):
 
 
 if __name__ == "__main__":
+    test_add()
     test_log_pow_llvm()
     test_exp()
-    test_add()
     test_popcount()
diff --git a/tests/python/integration/test_gemm.py b/tests/python/integration/test_gemm.py
index fea342baade6..6e74052d8283 100644
--- a/tests/python/integration/test_gemm.py
+++ b/tests/python/integration/test_gemm.py
@@ -2,6 +2,7 @@
 import numpy as np
 import time
 
+
 def test_gemm():
     # graph
     nn = 1024
@@ -64,13 +65,14 @@ def test_gemm():
 
     # one line to build the function.
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("skip because %s is not enabled.." % device)
             return
 
         with tvm.target.create(device):
             f = tvm.build(s, [A, B, C])
-        ctx = tvm.context(device, 0)
+
         # launch the kernel.
         n = nn
         m = n
@@ -86,12 +88,12 @@ def check_device(device):
         np.testing.assert_allclose(
             c.asnumpy(), np.dot(a_np, b_np.T), rtol=1e-5)
 
+    check_device("vulkan")
     check_device("nvptx -mcpu=sm_20")
     check_device("rocm")
     check_device("metal")
     check_device("opencl")
     check_device("cuda")
-    #check_device("nvptx -mcpu=sm_20")
 
 if __name__ == "__main__":
     test_gemm()
diff --git a/tests/python/integration/test_reduce.py b/tests/python/integration/test_reduce.py
index 95c8bc6f24e8..228786a7de61 100644
--- a/tests/python/integration/test_reduce.py
+++ b/tests/python/integration/test_reduce.py
@@ -1,6 +1,7 @@
 import tvm
 import numpy as np
 
+
 def test_reduce_prims():
     def test_prim(reducer, np_reducer):
         # graph
@@ -21,12 +22,12 @@ def test_prim(reducer, np_reducer):
 
         # one line to build the function.
         def check_device(device, host="stackvm"):
+            ctx = tvm.context(device, 0)
             if not tvm.module.enabled(host):
                 return
-            if not tvm.module.enabled(device):
+            if not ctx.exist:
                 print("skip because %s is not enabled.." % device)
                 return
-            ctx = tvm.context(device, 0)
             freduce = tvm.build(s,
                              args=[A, B],
                              target=device, target_host=host,
@@ -44,6 +45,7 @@ def check_device(device, host="stackvm"):
             np.testing.assert_allclose(npy, res, rtol=1e-4)
 
         check_device("metal")
+        check_device("vulkan")
         check_device("cuda")
         check_device("opencl")
     test_prim(tvm.sum, np.sum)
@@ -106,10 +108,11 @@ def test_rfactor_threads():
 
     # one line to build the function.
     def check_target(device, host="stackvm"):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("skip because %s is not enabled.." % device)
             return
-        ctx = tvm.context(device, 0)
+
         fapi = tvm.lower(s, args=[A, B])
         fsum = tvm.build(fapi,
                          target=device,
@@ -125,6 +128,7 @@ def check_target(device, host="stackvm"):
         np.testing.assert_allclose(
             b.asnumpy(), res, rtol=1e-4)
 
+    check_target("vulkan")
     check_target("cuda")
     check_target("metal")
     check_target("opencl")
@@ -159,15 +163,14 @@ def test_rfactor_elemwise_threads():
 
     # one line to build the function.
     def check_target(device, host="stackvm"):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("skip because %s is not enabled.." % device)
             return
-        ctx = tvm.context(device, 0)
         fapi = tvm.lower(s, args=[A, C])
         fsum = tvm.build(fapi,
                          target=device,
                          name="mysum")
-        print(fsum.imported_modules[0].get_source())
         # launch the kernel.
         a = tvm.nd.array(np.random.uniform(size=(m, n)).astype(A.dtype), ctx)
         b  = tvm.nd.array(np.zeros(m, dtype=B.dtype), ctx)
@@ -176,6 +179,7 @@ def check_target(device, host="stackvm"):
         np.testing.assert_allclose(
             b.asnumpy(), res, rtol=1e-4)
 
+    check_target("vulkan")
     check_target("cuda")
     check_target("metal")
     check_target("opencl")
@@ -264,10 +268,10 @@ def fidentity(t0, t1):
     s[B0].set_store_predicate(thread_x.var.equal(0))
 
     def check_target(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("skip because %s is not enabled.." % device)
             return
-        ctx = tvm.context(device, 0)
         fapi = tvm.lower(s, args=[A0, A1, B0, B1])
         fargmax = tvm.build(fapi,
                             target=device,
@@ -285,6 +289,7 @@ def check_target(device):
         np.testing.assert_allclose(np_res, nd_res0.asnumpy())
 
     check_target("cuda")
+    check_target("vulkan")
 
 if __name__ == "__main__":
     test_rfactor_elemwise_threads()
diff --git a/tests/python/integration/test_scan.py b/tests/python/integration/test_scan.py
index 2f9d29e9437b..855f3e072133 100644
--- a/tests/python/integration/test_scan.py
+++ b/tests/python/integration/test_scan.py
@@ -24,13 +24,13 @@ def test_scan():
 
     # one line to build the function.
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("skip because %s is not enabled.." % device)
             return
         fscan = tvm.build(s, [X, res],
                           device,
                           name="myscan")
-        ctx = tvm.context(device, 0)
         # launch the kernel.
         n = 1024
         m = 10
@@ -41,6 +41,7 @@ def check_device(device):
         np.testing.assert_allclose(
             b.asnumpy(), np.cumsum(a_np, axis=0))
 
+    check_device("vulkan")
     check_device("cuda")
     check_device("metal")
     check_device("opencl")
diff --git a/tests/python/unittest/test_codegen_device.py b/tests/python/unittest/test_codegen_device.py
index 773c2c890ef3..0bb072ebf0bd 100644
--- a/tests/python/unittest/test_codegen_device.py
+++ b/tests/python/unittest/test_codegen_device.py
@@ -13,12 +13,12 @@ def test_add_pipeline():
     # GPU schedule have to split by gridIdx and threadIdx
     num_thread = 256
     xo, xi = s[C].split(C.op.axis[0], factor=num_thread)
-    s[C].bind(xo, tvm.thread_axis("threadIdx.x"))
-    s[C].bind(xi, tvm.thread_axis("blockIdx.x"))
+    s[C].bind(xi, tvm.thread_axis("threadIdx.x"))
+    s[C].bind(xo, tvm.thread_axis("blockIdx.x"))
 
     xo, xi = s[D].split(D.op.axis[0], factor=num_thread)
-    s[D].bind(xo, tvm.thread_axis("threadIdx.x"))
-    s[D].bind(xi, tvm.thread_axis("blockIdx.x"))
+    s[D].bind(xi, tvm.thread_axis("threadIdx.x"))
+    s[D].bind(xo, tvm.thread_axis("blockIdx.x"))
 
     # compile to IR
     s = s.normalize()
@@ -35,11 +35,11 @@ def test_add_pipeline():
     fsplits[0] = tvm.ir_pass.LowerTVMBuiltin(fsplits[0])
 
     def check_target(device, host="stackvm"):
-        if not tvm.module.enabled(host):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             return
-        if not tvm.module.enabled(device):
+        if not tvm.module.enabled(host):
             return
-        ctx = tvm.context(device, 0)
         mhost = tvm.codegen.build_module(fsplits[0], host)
         mdev = tvm.codegen.build_module(fsplits[1:], device)
         mhost.import_module(mdev)
@@ -55,12 +55,12 @@ def check_target(device, host="stackvm"):
             d.asnumpy(), a.asnumpy() + b.asnumpy() + 1)
 
     def check_module_save(device, host="stackvm"):
-        if not tvm.module.enabled(host):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             return
-        if not tvm.module.enabled(device):
+        if not tvm.module.enabled(host):
             return
-        ctx = tvm.context(device, 0)
-        fmt = "ptx" if device == "cuda" else "cl"
+        fmt = "ptx" if device == "cuda" else device
         mhost = tvm.codegen.build_module(fsplits[0], host)
         mdev = tvm.codegen.build_module(fsplits[1:], device)
         temp = util.tempdir()
@@ -82,7 +82,9 @@ def check_module_save(device, host="stackvm"):
     check_target("cuda", host="llvm")
     check_module_save("cuda", host="stackvm")
     check_target("nvptx", host="llvm")
+    check_target("vulkan", host="llvm")
     check_target("rocm", host="llvm")
+    check_module_save("vulkan", host="stackvm")
 
 
 if __name__ == "__main__":
diff --git a/tests/python/unittest/test_module_load.py b/tests/python/unittest/test_module_load.py
index 210ef954464c..1b239a357f66 100644
--- a/tests/python/unittest/test_module_load.py
+++ b/tests/python/unittest/test_module_load.py
@@ -95,7 +95,7 @@ def check_device(device):
             f = tvm.build(s, [A, B], device, "llvm", name=name)
         else:
             raise ValueError("Unsupported platform")
-       
+
         path_dso = temp.relpath("dev_lib.so")
         f.export_library(path_dso)
 
@@ -110,6 +110,7 @@ def check_device(device):
             np.testing.assert_equal(b.asnumpy(), a.asnumpy() + 1)
 
     check_device("cuda")
+    check_device("vulkan")
     check_device("opencl")
     check_device("metal")
 
@@ -172,7 +173,7 @@ def check_system_lib():
         np.testing.assert_equal(b.asnumpy(), a.asnumpy() + 1)
         mm['myadd2'](a, b)
         np.testing.assert_equal(b.asnumpy(), a.asnumpy() + 1)
-    
+
     if sys.platform != "win32":
         check_system_lib()
     check_llvm()
diff --git a/tests/python/unittest/test_runtime_ndarray.py b/tests/python/unittest/test_runtime_ndarray.py
index a03a33b7af08..9f33e2aabfd8 100644
--- a/tests/python/unittest/test_runtime_ndarray.py
+++ b/tests/python/unittest/test_runtime_ndarray.py
@@ -7,6 +7,7 @@ def enabled_ctx_list():
                 ('cl', tvm.opencl(0)),
                 ('metal', tvm.metal(0)),
                 ('rocm', tvm.rocm(0)),
+                ('vulkan', tvm.vulkan(0)),
                 ('vpi', tvm.vpi(0))]
     for k, v  in ctx_list:
         assert tvm.context(k, 0) == v
diff --git a/topi/recipe/gemm/cuda_gemm_square.py b/topi/recipe/gemm/cuda_gemm_square.py
index c06686bc46a6..f2cabb26bb66 100644
--- a/topi/recipe/gemm/cuda_gemm_square.py
+++ b/topi/recipe/gemm/cuda_gemm_square.py
@@ -2,6 +2,7 @@
 import tvm
 import os
 from tvm.contrib import nvcc
+from tvm.contrib import spirv
 import numpy as np
 
 TASK="gemm"
@@ -25,6 +26,7 @@ def tvm_callback_cuda_postproc(code):
         code = open("perf/%s_manual.cu" % TASK).read()
     return code
 
+
 def test_gemm():
     # graph
     nn = 2048
@@ -101,12 +103,12 @@ def test_gemm():
     s[BB].double_buffer()
     # correctness
     def check_device(device):
-        print("Device %s" % device)
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
+        print("Device %s" % device)
         f = tvm.build(s, [A, B, C], device)
-        ctx = tvm.context(device, 0)
         # launch the kernel.
         n, m, l = nn, nn, nn
         a_np = np.random.uniform(size=(n, l)).astype(A.dtype)
@@ -126,7 +128,7 @@ def check_device(device):
         GFLOPS = num_flops / (t * 1e3) / 1e6
         print("average time cost of %d runs = %g ms, %g GFLOPS." % (num_runs, t * 1e3, GFLOPS))
 
-    for device in ["cuda", "opencl", "rocm", "nvptx"]:
+    for device in ["cuda", "opencl", "rocm", "nvptx", "vulkan"]:
         with tvm.build_config(auto_unroll_max_step=128,
                               unroll_explicit=(device != "cuda")):
             check_device(device)
diff --git a/topi/tests/python/test_topi_broadcast.py b/topi/tests/python/test_topi_broadcast.py
index 28a9e721a4ea..13a859063f50 100644
--- a/topi/tests/python/test_topi_broadcast.py
+++ b/topi/tests/python/test_topi_broadcast.py
@@ -9,13 +9,13 @@ def verify_broadcast_to_ele(in_shape, out_shape):
     A = tvm.placeholder(shape=in_shape, name="A")
     B = topi.broadcast_to(A, out_shape)
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
             s = topi.generic.schedule_broadcast(B)
-        ctx = tvm.context(device, 0)
         foo = tvm.build(s, [A, B], device, name="broadcast_to")
         data_npy = np.random.uniform(size=in_shape).astype(A.dtype)
         out_npy = np.broadcast_to(data_npy, out_shape)
@@ -25,6 +25,7 @@ def check_device(device):
             foo(data_nd, out_nd)
         np.testing.assert_allclose(out_nd.asnumpy(), out_npy)
 
+    check_device("vulkan")
     check_device("opencl")
     check_device("cuda")
     check_device("metal")
@@ -50,13 +51,13 @@ def verify_broadcast_binary_ele(lhs_shape, rhs_shape, typ="add"):
     else:
         raise NotImplementedError
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
             s = topi.generic.schedule_broadcast(C)
-        ctx = tvm.context(device, 0)
         foo = tvm.build(s, [A, B, C], device, name="broadcast_binary" + "_" + typ)
         lhs_npy = np.random.uniform(size=lhs_shape).astype(A.dtype)
         rhs_npy = np.random.uniform(size=rhs_shape).astype(A.dtype)
@@ -82,6 +83,7 @@ def check_device(device):
             foo(lhs_nd, rhs_nd, out_nd)
         np.testing.assert_allclose(out_nd.asnumpy(), out_npy, rtol=1E-4, atol=1E-4)
 
+    check_device("vulkan")
     check_device("opencl")
     check_device("cuda")
     check_device("metal")
@@ -105,5 +107,5 @@ def test_broadcast_binary():
 
 
 if __name__ == "__main__":
-    test_broadcast_to()
     test_broadcast_binary()
+    test_broadcast_to()
diff --git a/topi/tests/python/test_topi_conv2d_hwcn.py b/topi/tests/python/test_topi_conv2d_hwcn.py
index 9e6da1a58f0e..7761a90a0eed 100644
--- a/topi/tests/python/test_topi_conv2d_hwcn.py
+++ b/topi/tests/python/test_topi_conv2d_hwcn.py
@@ -31,11 +31,11 @@ def get_ref_data():
     a_np, w_np, b_np, c_np = get_ref_data()
 
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
-        ctx = tvm.context(device, 0)
         a = tvm.nd.array(a_np, ctx)
         w = tvm.nd.array(w_np, ctx)
         b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
@@ -49,7 +49,7 @@ def check_device(device):
             np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
             np.testing.assert_allclose(c.asnumpy(), c_np, rtol=1e-5)
 
-    for device in ['cuda', 'opencl', 'metal', 'rocm']:
+    for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan']:
         check_device(device)
 
 
diff --git a/topi/tests/python/test_topi_conv2d_nchw.py b/topi/tests/python/test_topi_conv2d_nchw.py
index 99f8ac93fb83..8b88bc3ba387 100644
--- a/topi/tests/python/test_topi_conv2d_nchw.py
+++ b/topi/tests/python/test_topi_conv2d_nchw.py
@@ -29,14 +29,14 @@ def get_ref_data():
     a_np, w_np, b_np, c_np = get_ref_data()
 
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
             s1 = topi.generic.schedule_conv2d_nchw([B])
             s2 = topi.generic.schedule_conv2d_nchw([C])
-        ctx = tvm.context(device, 0)
         a = tvm.nd.array(a_np, ctx)
         w = tvm.nd.array(w_np, ctx)
         b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
@@ -50,7 +50,7 @@ def check_device(device):
             np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
             np.testing.assert_allclose(c.asnumpy(), c_np, rtol=1e-5)
 
-    for device in ['cuda', 'opencl', 'metal', 'rocm']:
+    for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan']:
         check_device(device)
 
 
diff --git a/topi/tests/python/test_topi_conv2d_transpose_nchw.py b/topi/tests/python/test_topi_conv2d_transpose_nchw.py
index 8aa86e6f38be..712f64ca2c87 100644
--- a/topi/tests/python/test_topi_conv2d_transpose_nchw.py
+++ b/topi/tests/python/test_topi_conv2d_transpose_nchw.py
@@ -29,14 +29,14 @@ def get_ref_data():
     a_np, w_np, b_np, c_np = get_ref_data()
 
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
             s1 = topi.generic.schedule_conv2d_transpose_nchw([B])
             s2 = topi.generic.schedule_conv2d_transpose_nchw([C])
-        ctx = tvm.context(device, 0)
         a = tvm.nd.array(a_np, ctx)
         w = tvm.nd.array(w_np, ctx)
         b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
@@ -50,7 +50,7 @@ def check_device(device):
             np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
             np.testing.assert_allclose(c.asnumpy(), c_np, rtol=1e-5)
 
-    for device in ['cuda', 'opencl', 'metal', 'rocm']:
+    for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan']:
         check_device(device)
 
 
diff --git a/topi/tests/python/test_topi_dense.py b/topi/tests/python/test_topi_dense.py
index 0e5984afc011..cbae2055a3cd 100644
--- a/topi/tests/python/test_topi_dense.py
+++ b/topi/tests/python/test_topi_dense.py
@@ -29,13 +29,13 @@ def get_ref_data():
     a_np, b_np, c_np, d_np = get_ref_data()
 
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
             s = topi.generic.schedule_dense(D)
-        ctx = tvm.context(device, 0)
         a = tvm.nd.array(a_np, ctx)
         b = tvm.nd.array(b_np, ctx)
         c = tvm.nd.array(c_np, ctx)
@@ -44,7 +44,7 @@ def check_device(device):
         f(a, b, c, d)
         np.testing.assert_allclose(d.asnumpy(), d_np, rtol=1e-5)
 
-    for device in ['cuda', 'opencl', 'metal', 'rocm']:
+    for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan']:
         check_device(device)
 
 def test_dense():
diff --git a/topi/tests/python/test_topi_depthwise_conv2d.py b/topi/tests/python/test_topi_depthwise_conv2d.py
index 62230857ba02..8ecfdd063a94 100644
--- a/topi/tests/python/test_topi_depthwise_conv2d.py
+++ b/topi/tests/python/test_topi_depthwise_conv2d.py
@@ -23,7 +23,8 @@ def depthwise_conv2d_with_workload_nchw(batch, in_channel, in_height, channel_mu
 
 
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
@@ -32,7 +33,6 @@ def check_device(device):
             s1 = topi.generic.schedule_depthwise_conv2d_nchw(DepthwiseConv2d)
             s2 = topi.generic.schedule_depthwise_conv2d_nchw(ScaleShift)
             s3 = topi.generic.schedule_depthwise_conv2d_nchw(Relu)
-        ctx = tvm.context(device, 0)
         # build the kernels
         f1 = tvm.build(s1, [Input, Filter, DepthwiseConv2d], device)
         f2 = tvm.build(s2, [Input, Filter, Scale, Shift, ScaleShift], device)
@@ -90,6 +90,8 @@ def get_ref_data():
     check_device("cuda")
     check_device("metal")
     check_device("rocm")
+    check_device("vulkan")
+
 
 def depthwise_conv2d_with_workload_nhwc(batch, in_channel, in_height, channel_multiplier, filter_height, stride_h, padding):
     in_width = in_height
@@ -108,7 +110,8 @@ def depthwise_conv2d_with_workload_nhwc(batch, in_channel, in_height, channel_mu
     # schedule
 
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
@@ -117,7 +120,6 @@ def check_device(device):
             s1 = topi.generic.schedule_depthwise_conv2d_nhwc(DepthwiseConv2d)
             s2 = topi.generic.schedule_depthwise_conv2d_nhwc(ScaleShift)
             s3 = topi.generic.schedule_depthwise_conv2d_nhwc(Relu)
-        ctx = tvm.context(device, 0)
         # build the kernels
         f1 = tvm.build(s1, [Input, Filter, DepthwiseConv2d], device)
         f2 = tvm.build(s2, [Input, Filter, Scale, Shift, ScaleShift], device)
@@ -177,6 +179,7 @@ def get_ref_data():
     check_device("cuda")
     check_device("metal")
     check_device("rocm")
+    check_device("vulkan")
 
 def test_depthwise_conv2d():
     print("testing nchw")
diff --git a/topi/tests/python/test_topi_depthwise_conv2d_back_input.py b/topi/tests/python/test_topi_depthwise_conv2d_back_input.py
index 732e31e7e166..4280e92bdcb9 100644
--- a/topi/tests/python/test_topi_depthwise_conv2d_back_input.py
+++ b/topi/tests/python/test_topi_depthwise_conv2d_back_input.py
@@ -32,11 +32,11 @@ def verify_depthwise_conv2d_back_input(batch, in_channel, in_h, channel_multipli
     schedule = schedule_depthwise_conv2d_backward_input_nhwc(In_grad)
 
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
-        ctx = tvm.context(device, 0)
         # build the kernel
         f = tvm.build(schedule, [Filter, Out_grad, In_grad], device)
         # prepare pod type for test data closure
@@ -85,6 +85,7 @@ def get_ref_data():
     check_device("cuda")
     check_device("metal")
     check_device("rocm")
+    check_device("vulkan")
 
 def test_topi_depthwise_conv2d_backward_input_nhwc():
     verify_depthwise_conv2d_back_input(16, 256, 56, 1, 3, 1, 1)
diff --git a/topi/tests/python/test_topi_depthwise_conv2d_back_weight.py b/topi/tests/python/test_topi_depthwise_conv2d_back_weight.py
index b9a0fcbb3ddf..5aa65d2c4039 100644
--- a/topi/tests/python/test_topi_depthwise_conv2d_back_weight.py
+++ b/topi/tests/python/test_topi_depthwise_conv2d_back_weight.py
@@ -32,11 +32,11 @@ def verify_depthwise_conv2d_back_weight(batch, in_channel, in_h, channel_multipl
     schedule = schedule_depthwise_conv2d_backward_weight_nhwc(Weight_grad)
 
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
-        ctx = tvm.context(device, 0)
         # build the kernel
         f = tvm.build(schedule, [Input, Out_grad, Weight_grad], device)
         # prepare pod type for test data closure
@@ -78,6 +78,7 @@ def get_ref_data():
     check_device("cuda")
     check_device("metal")
     check_device("rocm")
+    check_device("vulkan")
 
 def test_topi_depthwise_conv2d_backward_weight_nhwc():
     verify_depthwise_conv2d_back_weight(16, 256, 56, 1, 3, 1, 1)
diff --git a/topi/tests/python/test_topi_pooling.py b/topi/tests/python/test_topi_pooling.py
index 08aade5213cd..52ae3a0fd06e 100644
--- a/topi/tests/python/test_topi_pooling.py
+++ b/topi/tests/python/test_topi_pooling.py
@@ -44,20 +44,21 @@ def verify_pool(n, ic, ih, kh, sh, padding, pool_type, ceil_mode):
     b_np = np.maximum(b_np, 0.0)
 
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
             s = topi.generic.schedule_pool(B)
-        ctx = tvm.context(device, 0)
+
         a = tvm.nd.array(a_np, ctx)
         b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=dtype), ctx)
         f = tvm.build(s, [A, B], device)
         f(a, b)
         np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
 
-    for device in ['cuda', 'opencl', 'metal', 'rocm']:
+    for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan']:
         check_device(device)
 
 def test_pool():
@@ -82,20 +83,20 @@ def verify_global_pool(n, c, h, w, pool_type):
     b_np = np.maximum(b_np, 0.0)
 
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
             s = topi.generic.schedule_global_pool(B)
-        ctx = tvm.context(device, 0)
         a = tvm.nd.array(a_np, ctx)
         b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
         f = tvm.build(s, [A, B], device)
         f(a, b)
         np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
 
-    for device in ['cuda', 'opencl', 'metal', 'rocm']:
+    for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan']:
         check_device(device)
 
 def test_global_pool():
diff --git a/topi/tests/python/test_topi_reduce.py b/topi/tests/python/test_topi_reduce.py
index 1e40770f5b10..597274a987b0 100644
--- a/topi/tests/python/test_topi_reduce.py
+++ b/topi/tests/python/test_topi_reduce.py
@@ -47,13 +47,14 @@ def verify_reduce_map_ele(in_shape, axis, keepdims, type="sum"):
         raise NotImplementedError
 
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
             s = topi.generic.schedule_reduce(B)
-        ctx = tvm.context(device, 0)
+
         foo = tvm.build(s, [A, B], device, name=type)
         # Test
         in_npy = np.random.uniform(size=in_shape).astype(np.float32)
@@ -90,7 +91,7 @@ def check_device(device):
                 np.testing.assert_allclose(out_tvm_val, in_npy_map.min(axis=axis), 1E-3, 1E-3)
         else:
             np.testing.assert_allclose(out_tvm.asnumpy(), out_npy, 1E-3, 1E-3)
-    for device in ["cuda", "opencl", "metal", "llvm", "rocm"]:
+    for device in ["cuda", "opencl", "metal", "llvm", "rocm", "vulkan"]:
         check_device(device)
 
 
diff --git a/topi/tests/python/test_topi_relu.py b/topi/tests/python/test_topi_relu.py
index f70f48c50110..994ed491411e 100644
--- a/topi/tests/python/test_topi_relu.py
+++ b/topi/tests/python/test_topi_relu.py
@@ -13,20 +13,21 @@ def verify_relu(m, n):
     b_np = a_np * (a_np > 0)
 
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
             s = topi.generic.schedule_elemwise(B)
-        ctx = tvm.context(device, 0)
+
         a = tvm.nd.array(a_np, ctx)
         b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
         foo = tvm.build(s, [A, B], device, name="relu")
         foo(a, b)
         np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
 
-    for device in ['cuda', 'opencl', 'metal', 'rocm']:
+    for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan']:
         check_device(device)
 
 
diff --git a/topi/tests/python/test_topi_softmax.py b/topi/tests/python/test_topi_softmax.py
index 72d86a43271d..c8a0f9549803 100644
--- a/topi/tests/python/test_topi_softmax.py
+++ b/topi/tests/python/test_topi_softmax.py
@@ -17,20 +17,21 @@ def verify_softmax(m, n):
     b_np = topi.testing.softmax_python(a_np)
 
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
             s = topi.generic.schedule_softmax(B)
-        ctx = tvm.context(device, 0)
+
         a = tvm.nd.array(a_np, ctx)
         b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
         foo = tvm.build(s, [A, B], device, name="softmax")
         foo(a, b)
         np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
 
-    for device in ['cuda', 'opencl', 'metal', 'rocm']:
+    for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan']:
         check_device(device)
 
 def test_softmax():
@@ -48,20 +49,20 @@ def verify_log_softmax(m, n):
     b_np = topi.testing.log_softmax_python(a_np)
 
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
             s = topi.generic.schedule_softmax(B)
-        ctx = tvm.context(device, 0)
         a = tvm.nd.array(a_np, ctx)
         b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
         foo = tvm.build(s, [A, B], device, name="log_softmax")
         foo(a, b)
         np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
 
-    for device in ["cuda", "opencl", "metal", "rocm"]:
+    for device in ["cuda", "opencl", "metal", "rocm", "vulkan"]:
         check_device(device)
 
 
diff --git a/topi/tests/python/test_topi_transform.py b/topi/tests/python/test_topi_transform.py
index b8f44b5633c3..46c860fb4c53 100644
--- a/topi/tests/python/test_topi_transform.py
+++ b/topi/tests/python/test_topi_transform.py
@@ -7,13 +7,13 @@ def verify_expand_dims(in_shape, out_shape, axis, num_newaxis):
     A = tvm.placeholder(shape=in_shape, name="A")
     B = topi.expand_dims(A, axis, num_newaxis)
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
             s = topi.generic.schedule_broadcast(B)
-        ctx = tvm.context(device, 0)
         foo = tvm.build(s, [A, B], device, name="expand_dims")
         data_npy = np.random.uniform(size=in_shape).astype(A.dtype)
         out_npy = data_npy.reshape(out_shape)
@@ -22,7 +22,7 @@ def check_device(device):
         foo(data_nd, out_nd)
         np.testing.assert_allclose(out_nd.asnumpy(), out_npy)
 
-    for device in ["llvm", "nvptx", "cuda", "opencl", "metal", "rocm"]:
+    for device in ["llvm", "nvptx", "cuda", "opencl", "metal", "rocm", "vulkan"]:
         check_device(device)
 
 
@@ -30,13 +30,13 @@ def verify_tranpose(in_shape, axes):
     A = tvm.placeholder(shape=in_shape, name="A")
     B = topi.transpose(A, axes)
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
             s = topi.generic.schedule_injective(B)
-        ctx = tvm.context(device, 0)
         foo = tvm.build(s, [A, B], device, name="tranpose")
         data_npy = np.arange(np.prod(in_shape)).reshape(in_shape).astype(A.dtype)
         out_npy = data_npy.transpose(axes)
@@ -45,7 +45,7 @@ def check_device(device):
         foo(data_nd, out_nd)
         np.testing.assert_allclose(out_nd.asnumpy(), out_npy)
 
-    for device in ["llvm", "nvptx", "cuda", "opencl", "metal", "rocm"]:
+    for device in ["llvm", "nvptx", "cuda", "opencl", "metal", "rocm", "vulkan"]:
         check_device(device)
 
 
@@ -53,13 +53,13 @@ def verify_reshape(src_shape, dst_shape):
     A = tvm.placeholder(shape=src_shape, name="A")
     B = topi.reshape(A, dst_shape)
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
             s = topi.generic.schedule_injective(B)
-        ctx = tvm.context(device, 0)
         foo = tvm.build(s, [A, B], device, name="reshape")
         data_npy = np.random.normal(size=src_shape).astype(A.dtype)
         out_npy = np.reshape(data_npy, newshape=dst_shape)
@@ -68,7 +68,7 @@ def check_device(device):
         foo(data_nd, out_nd)
         np.testing.assert_allclose(out_nd.asnumpy(), out_npy)
 
-    for device in ["llvm", "nvptx", "cuda", "opencl", "metal", "rocm"]:
+    for device in ["llvm", "nvptx", "cuda", "opencl", "metal", "rocm", "vulkan"]:
         check_device(device)
 
 
@@ -76,13 +76,14 @@ def verify_squeeze(src_shape, axis):
     A = tvm.placeholder(shape=src_shape, name="A")
     B = topi.squeeze(A, axis=axis)
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
             s = topi.generic.schedule_injective(B)
-        ctx = tvm.context(device, 0)
+
         foo = tvm.build(s, [A, B], device, name="squeeze")
         data_npy = np.random.normal(size=src_shape).astype(A.dtype)
         out_npy = np.squeeze(data_npy, axis=axis)
@@ -95,7 +96,7 @@ def check_device(device):
         foo(data_nd, out_nd)
         np.testing.assert_allclose(out_nd.asnumpy(), out_npy)
 
-    for device in ["llvm", "nvptx", "cuda", "opencl", "metal", "rocm"]:
+    for device in ["llvm", "nvptx", "cuda", "opencl", "metal", "rocm", "vulkan"]:
         check_device(device)
 
 def verify_concatenate(shapes, axis):
@@ -104,13 +105,14 @@ def verify_concatenate(shapes, axis):
         tensor_l.append(tvm.placeholder(shape, name="A" + str(i)))
     out_tensor = topi.concatenate(a_tuple=tensor_l, axis=axis)
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
             s = topi.generic.schedule_injective(out_tensor)
-        ctx = tvm.context(device, 0)
+
         foo = tvm.build(s, tensor_l + [out_tensor], device, name="concatenate")
         data_npys = [np.random.normal(size=shape).astype(tensor_l[0].dtype) for shape in shapes]
         out_npy = np.concatenate(data_npys, axis=axis)
@@ -119,7 +121,7 @@ def check_device(device):
         foo(*(data_nds + [out_nd]))
         np.testing.assert_allclose(out_nd.asnumpy(), out_npy)
 
-    for device in ["llvm", "nvptx", "cuda", "opencl", "metal", "rocm"]:
+    for device in ["llvm", "nvptx", "cuda", "opencl", "metal", "rocm", "vulkan"]:
         check_device(device)
 
 
@@ -127,13 +129,14 @@ def verify_split(src_shape, indices_or_sections, axis):
     A = tvm.placeholder(shape=src_shape, name="A")
     tensor_l = topi.split(A, indices_or_sections, axis=axis)
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
             s = topi.generic.schedule_injective(tensor_l)
-        ctx = tvm.context(device, 0)
+
         foo = tvm.build(s, [A] + tensor_l, device, name="split")
         data_npy = np.random.normal(size=src_shape).astype(A.dtype)
         out_npys = np.split(data_npy, indices_or_sections, axis=axis)
@@ -143,7 +146,7 @@ def check_device(device):
         for out_nd, out_npy in zip(out_nds, out_npys):
             np.testing.assert_allclose(out_nd.asnumpy(), out_npy)
 
-    for device in ["llvm", "nvptx", "cuda", "opencl", "metal", "rocm"]:
+    for device in ["llvm", "nvptx", "cuda", "opencl", "metal", "rocm", "vulkan"]:
         check_device(device)
 
 
diff --git a/topi/tests/python/test_topi_upsampling.py b/topi/tests/python/test_topi_upsampling.py
index 08b8f987694d..20c349c81832 100644
--- a/topi/tests/python/test_topi_upsampling.py
+++ b/topi/tests/python/test_topi_upsampling.py
@@ -14,13 +14,13 @@ def verify_upsampling(batch, in_channel, in_height, in_width, scale):
     b_np = topi.testing.upsampling_python(a_np, scale)
 
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
             s = topi.generic.schedule_injective(B)
-        ctx = tvm.context(device, 0)
         a = tvm.nd.array(a_np, ctx)
         b = tvm.nd.array(np.zeros(out_shape, dtype=dtype), ctx)
         f = tvm.build(s, [A, B], device)
@@ -28,7 +28,7 @@ def check_device(device):
 
         np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
 
-    for device in ['llvm', 'cuda']:
+    for device in ['llvm', 'cuda', 'vulkan']:
         check_device(device)
 
 def test_upsampling():

From eedd784700bc8202b88467592ab367545ba0bf29 Mon Sep 17 00:00:00 2001
From: PENGUINLIONG <admin@penguinliong.moe>
Date: Sat, 3 Feb 2018 01:00:12 +0800
Subject: [PATCH 127/949] Fix boolean type (#862)

---
 src/codegen/spirv/ir_builder.cc | 30 ++++++++++++++++++++----------
 1 file changed, 20 insertions(+), 10 deletions(-)

diff --git a/src/codegen/spirv/ir_builder.cc b/src/codegen/spirv/ir_builder.cc
index 137ad75c6b10..26f851031e5a 100644
--- a/src/codegen/spirv/ir_builder.cc
+++ b/src/codegen/spirv/ir_builder.cc
@@ -311,17 +311,27 @@ Value IRBuilder::GetConst_(const SType& dtype, const uint64_t* pvalue) {
   }
   CHECK_LE(dtype.type.bits(), 64);
   Value ret = NewValue(dtype, kConstant);
-  ib_.Begin(spv::OpConstant).AddSeq(dtype, ret);
-  uint64_t mask = 0xFFFFFFFFUL;
-  ib_.Add(static_cast<uint32_t>(pvalue[0] & mask));
-  if (dtype.type.bits() > 32) {
-    if (dtype.type.is_int()) {
-      int64_t sign_mask = 0xFFFFFFFFL;
-      const int64_t* sign_ptr =
-          reinterpret_cast<const int64_t*>(pvalue);
-        ib_.Add(static_cast<uint32_t>((sign_ptr[0] >> 32L) & sign_mask));
+  if (1 == dtype.type.bits() && dtype.is_uint()) {
+    // Boolean types.
+    if (*pvalue) {
+      ib_.Begin(spv::OpConstantTrue).AddSeq(ret);
     } else {
-      ib_.Add(static_cast<uint32_t>((pvalue[0] >> 32UL) & mask));
+      ib_.Begin(spv::OpConstantFalse).AddSeq(ret);
+    }
+  } else {
+    // Integral/floating-point types.
+    ib_.Begin(spv::OpConstant).AddSeq(dtype, ret);
+    uint64_t mask = 0xFFFFFFFFUL;
+    ib_.Add(static_cast<uint32_t>(pvalue[0] & mask));
+    if (dtype.type.bits() > 32) {
+      if (dtype.type.is_int()) {
+        int64_t sign_mask = 0xFFFFFFFFL;
+        const int64_t* sign_ptr =
+            reinterpret_cast<const int64_t*>(pvalue);
+          ib_.Add(static_cast<uint32_t>((sign_ptr[0] >> 32L) & sign_mask));
+      } else {
+        ib_.Add(static_cast<uint32_t>((pvalue[0] >> 32UL) & mask));
+      }
     }
   }
   ib_.Commit(&global_);

From bc8406910e166421b60172da7521857863538c09 Mon Sep 17 00:00:00 2001
From: alex-weaver <awsweaver@gmail.com>
Date: Fri, 2 Feb 2018 17:00:36 +0000
Subject: [PATCH 128/949] TOPI C++ bugfixes (#864)

* TOPI C++ bugfixes

* Fix lint
---
 topi/include/topi/cuda/dense.h            | 2 +-
 topi/include/topi/cuda/extern.h           | 4 ++--
 topi/include/topi/cuda/injective.h        | 4 ++--
 topi/include/topi/cuda/pooling.h          | 4 ++--
 topi/include/topi/cuda/softmax.h          | 2 +-
 topi/include/topi/detail/array_utils.h    | 2 +-
 topi/include/topi/detail/constant_utils.h | 6 +++---
 topi/include/topi/detail/extern.h         | 8 ++++----
 topi/include/topi/detail/fuse.h           | 2 +-
 topi/include/topi/detail/pad_utils.h      | 2 +-
 topi/include/topi/generic/default.h       | 2 +-
 topi/include/topi/generic/extern.h        | 2 +-
 topi/include/topi/generic/injective.h     | 2 +-
 topi/include/topi/nn/flatten.h            | 2 +-
 topi/include/topi/rocm/dense.h            | 2 +-
 topi/include/topi/x86/bnn.h               | 4 ++--
 topi/include/topi/x86/default.h           | 4 +++-
 topi/include/topi/x86/injective.h         | 2 +-
 18 files changed, 29 insertions(+), 27 deletions(-)

diff --git a/topi/include/topi/cuda/dense.h b/topi/include/topi/cuda/dense.h
index 3504ef3bfd17..b012f108fd93 100644
--- a/topi/include/topi/cuda/dense.h
+++ b/topi/include/topi/cuda/dense.h
@@ -66,7 +66,7 @@ inline tvm::Tensor dense_cuda(const Target& target,
 *
 * \return A schedule for the given ops.
 */
-Schedule schedule_dense(const Target &target, const Array<Tensor>& outs) {
+inline Schedule schedule_dense(const Target &target, const Array<Tensor>& outs) {
   if (target.target_name == "cuda" &&
     target.libs.count("cublas") > 0) {
     return topi::generic::schedule_extern(target, outs);
diff --git a/topi/include/topi/cuda/extern.h b/topi/include/topi/cuda/extern.h
index 27380b3aa77d..b97c33943010 100644
--- a/topi/include/topi/cuda/extern.h
+++ b/topi/include/topi/cuda/extern.h
@@ -25,7 +25,7 @@ namespace cuda {
  *
  * \return The schedule given by sch
  */
-Schedule ScheduleOutputForExtern(Target target, Operation op, Schedule sch) {
+inline Schedule ScheduleOutputForExtern(Target target, Operation op, Schedule sch) {
   auto x = op.output(0);
   auto fused = Fuse(sch[x], sch[x]->op.as<ComputeOpNode>()->axis);
   auto num_thread = target.max_num_threads;
@@ -45,7 +45,7 @@ Schedule ScheduleOutputForExtern(Target target, Operation op, Schedule sch) {
 *
 * \return A schedule for the op.
 */
-Schedule schedule_extern(const Target& target, Array<Tensor> outs) {
+inline Schedule schedule_extern(const Target& target, Array<Tensor> outs) {
   Array<Operation> out_ops;
   for (auto t : outs) {
     out_ops.push_back(t->op);
diff --git a/topi/include/topi/cuda/injective.h b/topi/include/topi/cuda/injective.h
index 31ff5e5383d1..c213002cc52f 100644
--- a/topi/include/topi/cuda/injective.h
+++ b/topi/include/topi/cuda/injective.h
@@ -22,7 +22,7 @@ namespace cuda {
 * \param op The operation representing the injective operation.
 * \param s The schedule to apply this scheduling to
 */
-void ScheduleInjectiveOp(const Target &target, Operation op, Schedule s) {
+inline void ScheduleInjectiveOp(const Target &target, Operation op, Schedule s) {
   auto x = op.output(0);
   auto fused = Fuse(s[x], s[x]->op.as<ComputeOpNode>()->axis);
   auto num_thread = target.max_num_threads;
@@ -40,7 +40,7 @@ void ScheduleInjectiveOp(const Target &target, Operation op, Schedule s) {
  *
  * \return A schedule for the given ops.
  */
-Schedule schedule_injective(const Target &target, const Array<Tensor>& outs) {
+inline Schedule schedule_injective(const Target &target, const Array<Tensor>& outs) {
   Array<Operation> out_ops;
   for (auto t : outs) {
     out_ops.push_back(t->op);
diff --git a/topi/include/topi/cuda/pooling.h b/topi/include/topi/cuda/pooling.h
index 934e845e335d..e8fb9546a241 100644
--- a/topi/include/topi/cuda/pooling.h
+++ b/topi/include/topi/cuda/pooling.h
@@ -25,7 +25,7 @@ namespace cuda {
 *
 * \return A schedule for the given ops.
 */
-Schedule schedule_pool(const Target &target, const Array<Tensor>& outs) {
+inline Schedule schedule_pool(const Target &target, const Array<Tensor>& outs) {
   Array<Operation> out_ops;
   for (auto t : outs) {
     out_ops.push_back(t->op);
@@ -90,7 +90,7 @@ Schedule schedule_pool(const Target &target, const Array<Tensor>& outs) {
 *
 * \return A schedule for the given ops.
 */
-Schedule schedule_global_pool(const Target &target, const Array<Tensor>& outs) {
+inline Schedule schedule_global_pool(const Target &target, const Array<Tensor>& outs) {
   Array<Operation> out_ops;
   for (auto t : outs) {
     out_ops.push_back(t->op);
diff --git a/topi/include/topi/cuda/softmax.h b/topi/include/topi/cuda/softmax.h
index 77121c20be69..8d22f4b4b46d 100644
--- a/topi/include/topi/cuda/softmax.h
+++ b/topi/include/topi/cuda/softmax.h
@@ -24,7 +24,7 @@ namespace cuda {
  *
  * \return A schedule for the given ops.
  */
-Schedule schedule_softmax(const Target &target, const Array<Tensor>& outs) {
+inline Schedule schedule_softmax(const Target &target, const Array<Tensor>& outs) {
   Array<Operation> out_ops;
   for (auto t : outs) {
     out_ops.push_back(t->op);
diff --git a/topi/include/topi/detail/array_utils.h b/topi/include/topi/detail/array_utils.h
index ca94bd6a1202..3e77650cda6f 100644
--- a/topi/include/topi/detail/array_utils.h
+++ b/topi/include/topi/detail/array_utils.h
@@ -21,7 +21,7 @@ using namespace tvm;
  * \return True iff the given array contains the given item.
  */
 template<typename T>
-bool contains(Array<T> array, T item) {
+inline bool contains(Array<T> array, T item) {
   for (auto& i : array) {
     if (i == item) {
       return true;
diff --git a/topi/include/topi/detail/constant_utils.h b/topi/include/topi/detail/constant_utils.h
index eb3219e9b25a..062f9f473762 100644
--- a/topi/include/topi/detail/constant_utils.h
+++ b/topi/include/topi/detail/constant_utils.h
@@ -22,7 +22,7 @@ using namespace tvm;
  *
  * \return true if the given expr is a constant int or uint, false otherwise.
  */
-bool IsConstInt(Expr expr) {
+inline bool IsConstInt(Expr expr) {
   return
     expr->derived_from<tvm::ir::IntImm>() ||
     expr->derived_from<tvm::ir::UIntImm>();
@@ -36,7 +36,7 @@ bool IsConstInt(Expr expr) {
  *
  * \return The integer value.
  */
-int64_t GetConstInt(Expr expr) {
+inline int64_t GetConstInt(Expr expr) {
   if (expr->derived_from<tvm::ir::IntImm>()) {
     return expr.as<tvm::ir::IntImm>()->value;
   }
@@ -56,7 +56,7 @@ int64_t GetConstInt(Expr expr) {
  *
  * \return A vector of the integer values
  */
-std::vector<int> GetConstIntValues(Array<Expr> exprs, const std::string& var_name) {
+inline std::vector<int> GetConstIntValues(Array<Expr> exprs, const std::string& var_name) {
   std::vector<int> result;
   for (auto expr : exprs) {
     CHECK(IsConstInt(expr)) << "All elements of " << var_name << " must be constant integers";
diff --git a/topi/include/topi/detail/extern.h b/topi/include/topi/detail/extern.h
index 3c5e09edae01..97b7a4655f0a 100644
--- a/topi/include/topi/detail/extern.h
+++ b/topi/include/topi/detail/extern.h
@@ -24,7 +24,7 @@ using namespace tvm;
  *
  * \return The Buffer object
  */
-Buffer DeclExternBuffer(Array<Expr> shape,
+inline Buffer DeclExternBuffer(Array<Expr> shape,
                         Type dtype,
                         std::string name) {
   auto data = var(name, Handle());
@@ -56,7 +56,7 @@ using FExtern = std::function<Expr(Array<Buffer>, Array<Buffer>)>;
  * be one output Tensor for each element of out_shapes, with dtype equal to the corresponding
  * element of out_types.
  */
-Array<Tensor> make_extern(const Array< Array<Expr> >& out_shapes,
+inline Array<Tensor> make_extern(const Array< Array<Expr> >& out_shapes,
                           const std::vector<Type>& out_types,
                           const Array<Tensor>& inputs,
                           FExtern fextern,
@@ -95,7 +95,7 @@ Array<Tensor> make_extern(const Array< Array<Expr> >& out_shapes,
  *
  * \return An expression representing the pack operation
  */
-Expr pack_buffer(Buffer buf) {
+inline Expr pack_buffer(Buffer buf) {
   CHECK_GT(buf->shape.size(), 0) << "buf shape must have at least one element";
   auto shape = tvm::ir::Call::make(Handle(), tvm::ir::intrinsic::tvm_stack_make_shape,
                                    buf->shape, tvm::ir::Call::CallType::Intrinsic);
@@ -127,7 +127,7 @@ Expr pack_buffer(Buffer buf) {
  *
  * \return An expression representing the invocation
  */
-Expr call_packed(Array<Expr> args) {
+inline Expr call_packed(Array<Expr> args) {
   return tvm::ir::Call::make(Int(32), tvm::ir::intrinsic::tvm_call_packed,
                              args, tvm::ir::Call::CallType::Intrinsic);
 }
diff --git a/topi/include/topi/detail/fuse.h b/topi/include/topi/detail/fuse.h
index 8ace8bf97fd3..9ee7fbd1cffd 100644
--- a/topi/include/topi/detail/fuse.h
+++ b/topi/include/topi/detail/fuse.h
@@ -20,7 +20,7 @@ using namespace tvm;
  *
  * \return The fused iteration variable
  */
-IterVar Fuse(Stage stage, const Array<IterVar>& args) {
+inline IterVar Fuse(Stage stage, const Array<IterVar>& args) {
   CHECK_GE(args.size(), 1) << "Fuse requires at least 1 arg";
 
   auto fused = args[0];
diff --git a/topi/include/topi/detail/pad_utils.h b/topi/include/topi/detail/pad_utils.h
index e284859c1e50..f982f35844fa 100644
--- a/topi/include/topi/detail/pad_utils.h
+++ b/topi/include/topi/detail/pad_utils.h
@@ -23,7 +23,7 @@ using namespace tvm;
  * \return An array of 4 elements, representing padding sizes for
  * each individual side. The array is in the order { top, left, bottom, right }
  */
-Array<Expr> GetPadTuple(Expr pad_h, Expr pad_w) {
+inline Array<Expr> GetPadTuple(Expr pad_h, Expr pad_w) {
   pad_h *= 2;
   pad_w *= 2;
 
diff --git a/topi/include/topi/generic/default.h b/topi/include/topi/generic/default.h
index 5dd93030f95b..0deadb07e241 100644
--- a/topi/include/topi/generic/default.h
+++ b/topi/include/topi/generic/default.h
@@ -24,7 +24,7 @@ namespace generic {
  *
  * \return A schedule for the given ops.
  */
-Schedule default_schedule(const Target& target, Array<Tensor> outs, bool auto_inline) {
+inline Schedule default_schedule(const Target& target, Array<Tensor> outs, bool auto_inline) {
   Array<Operation> out_ops;
   for (auto t : outs) {
     out_ops.push_back(t->op);
diff --git a/topi/include/topi/generic/extern.h b/topi/include/topi/generic/extern.h
index 21852d108239..e14054f29b68 100644
--- a/topi/include/topi/generic/extern.h
+++ b/topi/include/topi/generic/extern.h
@@ -23,7 +23,7 @@ namespace generic {
 *
 * \return A schedule for the op.
 */
-Schedule schedule_extern(const Target& target, Array<Tensor> outs) {
+inline Schedule schedule_extern(const Target& target, Array<Tensor> outs) {
   Array<Operation> out_ops;
   for (auto t : outs) {
     out_ops.push_back(t->op);
diff --git a/topi/include/topi/generic/injective.h b/topi/include/topi/generic/injective.h
index f05f6243485d..8c0a12d23b60 100644
--- a/topi/include/topi/generic/injective.h
+++ b/topi/include/topi/generic/injective.h
@@ -24,7 +24,7 @@ namespace generic {
  *
  * \return A schedule for the given ops.
  */
-Schedule schedule_injective(const Target &target, const Array<Tensor>& outs) {
+inline Schedule schedule_injective(const Target &target, const Array<Tensor>& outs) {
   Array<Operation> out_ops;
   for (auto t : outs) {
     out_ops.push_back(t->op);
diff --git a/topi/include/topi/nn/flatten.h b/topi/include/topi/nn/flatten.h
index a74b390eeb0c..20a9b2cbfe78 100644
--- a/topi/include/topi/nn/flatten.h
+++ b/topi/include/topi/nn/flatten.h
@@ -33,7 +33,7 @@ inline Tensor flatten(const Tensor& x,
   auto ishape = x->shape;
   int dim = 1;
   for (size_t i = 1; i < ishape.size(); ++i) {
-    dim = dim * static_cast<int>(GetConstInt(ishape[i]));
+    dim = dim * static_cast<int>(topi::detail::GetConstInt(ishape[i]));
   }
 
   Array<Expr> oshape({ ishape[0], dim });
diff --git a/topi/include/topi/rocm/dense.h b/topi/include/topi/rocm/dense.h
index cf3c3f60b751..8256ba983eee 100644
--- a/topi/include/topi/rocm/dense.h
+++ b/topi/include/topi/rocm/dense.h
@@ -67,7 +67,7 @@ inline tvm::Tensor dense_rocm(const Target& target,
 *
 * \return A schedule for the given ops.
 */
-Schedule schedule_dense(const Target &target, const Array<Tensor>& outs) {
+inline Schedule schedule_dense(const Target &target, const Array<Tensor>& outs) {
   if (target.target_name == "rocm" &&
     target.libs.count("rocblas") > 0) {
     return topi::generic::schedule_extern(target, outs);
diff --git a/topi/include/topi/x86/bnn.h b/topi/include/topi/x86/bnn.h
index a5046b0e79a9..8e50c5bac124 100644
--- a/topi/include/topi/x86/bnn.h
+++ b/topi/include/topi/x86/bnn.h
@@ -23,7 +23,7 @@ namespace x86 {
 *
 * \return A schedule for the given ops.
 */
-Schedule schedule_binarize_pack(const Target &target, const Array<Tensor>& outs) {
+inline Schedule schedule_binarize_pack(const Target &target, const Array<Tensor>& outs) {
   Array<Operation> out_ops;
   for (auto t : outs) {
     out_ops.push_back(t->op);
@@ -55,7 +55,7 @@ Schedule schedule_binarize_pack(const Target &target, const Array<Tensor>& outs)
 *
 * \return A schedule for the given ops.
 */
-Schedule schedule_binary_dense(const Target &target, const Array<Tensor>& outs) {
+inline Schedule schedule_binary_dense(const Target &target, const Array<Tensor>& outs) {
   Array<Operation> out_ops;
   for (auto t : outs) {
     out_ops.push_back(t->op);
diff --git a/topi/include/topi/x86/default.h b/topi/include/topi/x86/default.h
index 76df67b79169..4a3f36e4ae66 100644
--- a/topi/include/topi/x86/default.h
+++ b/topi/include/topi/x86/default.h
@@ -24,7 +24,9 @@ namespace x86 {
 *
 * \return A schedule for the given ops.
 */
-Schedule default_schedule(const Target &target, const Array<Tensor>& outs, bool auto_inline) {
+inline Schedule default_schedule(const Target &target,
+                                 const Array<Tensor>& outs,
+                                 bool auto_inline) {
   Array<Operation> out_ops;
   for (auto t : outs) {
     out_ops.push_back(t->op);
diff --git a/topi/include/topi/x86/injective.h b/topi/include/topi/x86/injective.h
index 8609beb56aa0..40045b4920ce 100644
--- a/topi/include/topi/x86/injective.h
+++ b/topi/include/topi/x86/injective.h
@@ -23,7 +23,7 @@ namespace x86 {
 *
 * \return A schedule for the given ops.
 */
-Schedule schedule_injective(const Target &target, const Array<Tensor>& outs) {
+inline Schedule schedule_injective(const Target &target, const Array<Tensor>& outs) {
   Array<Operation> out_ops;
   for (auto t : outs) {
     out_ops.push_back(t->op);

From edc4265907d56f401e5753b0c8ac0f88c387088d Mon Sep 17 00:00:00 2001
From: alex-weaver <awsweaver@gmail.com>
Date: Fri, 2 Feb 2018 18:30:58 +0000
Subject: [PATCH 129/949] Change HalideIR back to most recent commit (#865)

---
 HalideIR | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/HalideIR b/HalideIR
index aadbf02d6bd7..87b089a0ba20 160000
--- a/HalideIR
+++ b/HalideIR
@@ -1 +1 @@
-Subproject commit aadbf02d6bd7a545edbf6652494a7b07a97a06c1
+Subproject commit 87b089a0ba20f2e8257038ee9211d6816088ce95

From eb74a861e358f2c8efecc627c7274b305b9d8464 Mon Sep 17 00:00:00 2001
From: yuruofeifei <yuruofeifei@gmail.com>
Date: Fri, 2 Feb 2018 14:33:29 -0800
Subject: [PATCH 130/949] [TOPI] Add compute for more operators (#849)

* [TOPI] Add compute for more operators

* Remove device except llvm

* Address comments

* Remove matmul compute

* Add outtype to boolean operator

* Address coments
---
 python/tvm/api.py                        |   1 +
 topi/python/topi/__init__.py             |   2 +-
 topi/python/topi/tensor.py               | 116 ++++++++++++++++++++
 topi/python/topi/transform.py            |  55 ++++++++++
 topi/tests/python/test_topi_math.py      |  31 ++++++
 topi/tests/python/test_topi_tensor.py    | 129 +++++++++++++++++++++++
 topi/tests/python/test_topi_transform.py |  44 ++++++++
 7 files changed, 377 insertions(+), 1 deletion(-)
 create mode 100644 topi/python/topi/tensor.py
 create mode 100644 topi/tests/python/test_topi_math.py
 create mode 100644 topi/tests/python/test_topi_tensor.py

diff --git a/python/tvm/api.py b/python/tvm/api.py
index 08b3d95dca27..7c90b0ec9481 100644
--- a/python/tvm/api.py
+++ b/python/tvm/api.py
@@ -19,6 +19,7 @@
 from . import container as _container
 from . import tag as _tag
 
+int8 = "int8"
 int32 = "int32"
 float32 = "float32"
 handle = "handle"
diff --git a/topi/python/topi/__init__.py b/topi/python/topi/__init__.py
index 4979bdbaac8d..1648de33bb98 100644
--- a/topi/python/topi/__init__.py
+++ b/topi/python/topi/__init__.py
@@ -12,6 +12,7 @@
 from tvm._ffi.libinfo import __version__
 
 from .math import *
+from .tensor import *
 from .reduction import *
 from .transform import *
 from .broadcast import *
@@ -23,4 +24,3 @@
 from . import testing
 from . import util
 from . import rocm
-from . import cpp
diff --git a/topi/python/topi/tensor.py b/topi/python/topi/tensor.py
new file mode 100644
index 000000000000..3e2e0abb46a3
--- /dev/null
+++ b/topi/python/topi/tensor.py
@@ -0,0 +1,116 @@
+# pylint: disable=invalid-name,consider-using-enumerate,unused-argument,len-as-condition
+"""Elementwise operators"""
+from __future__ import absolute_import as _abs
+import tvm
+from . import tag
+
+@tvm.tag_scope(tag=tag.ELEMWISE)
+def elemwise_sum(xs, num_args):
+    """Perform element-wise sum on inputs
+
+    Parameters
+    ----------
+    xs : list of tvm.Tensor
+        Input arguments.
+    num_args : int
+        Number of arguments
+
+    Returns
+    -------
+    y : tvm.Tensor
+        The result.
+    """
+    assert len(xs) > 0, "elemwise sum must have at least one input tensor."
+
+    def _compute(*i):
+        return sum([x(*i) for x in xs])
+
+    return tvm.compute(xs[0].shape, _compute)
+
+@tvm.tag_scope(tag=tag.ELEMWISE)
+def full(shape, dtype, fill_value):
+    """Fill tensor with fill_value
+
+    Parameters
+    ----------
+    shape : tuple
+        Input tensor shape.
+    dtype : str
+        Data type
+    fill_value : float
+        Value to be filled
+
+    Returns
+    -------
+    y : tvm.Tensor
+        The result.
+    """
+    return tvm.compute(shape, lambda *i: tvm.const(fill_value, dtype))
+
+@tvm.tag_scope(tag=tag.ELEMWISE)
+def full_like(x, fill_value):
+    """Construct a tensor with same shape as input tensor,
+       then fill tensor with fill_value.
+
+    Parameters
+    ----------
+    x : tvm.Tensor
+        Input argument.
+    fill_value : float
+        Value to be filled
+
+    Returns
+    -------
+    y : tvm.Tensor
+        The result.
+    """
+    dtype = x.dtype
+    return tvm.compute(x.shape, lambda *i: tvm.const(fill_value, dtype))
+
+@tvm.tag_scope(tag=tag.ELEMWISE)
+def greater(lhs, rhs, out_type=tvm.int8):
+    """Compare two input tensors element-wise and return an mask tensor
+       which contains 1 if lhs > rhs holds else 0
+
+    Parameters
+    ----------
+    lhs : tvm.Tensor
+        Left input argument.
+    rhs : tvm.Tensor
+        Right argument.
+    out_type: str
+        Output data type. Default is int8
+
+    Returns
+    -------
+    y : tvm.Tensor
+        The result.
+    """
+    return tvm.compute(lhs.shape,
+                       lambda *i: tvm.select(lhs(*i) > rhs(*i),
+                                             tvm.const(1, out_type),
+                                             tvm.const(0, out_type)))
+
+@tvm.tag_scope(tag=tag.ELEMWISE)
+def less(lhs, rhs, out_type=tvm.int8):
+    """Compare two input tensors element-wise and return an mask tensor
+       which contains 1 if lhs < rhs holds else 0
+
+    Parameters
+    ----------
+    lhs : tvm.Tensor
+        Left input argument.
+    rhs : tvm.Tensor
+        Right argument.
+    out_type: str
+        Output data type. Default is int8
+
+    Returns
+    -------
+    y : tvm.Tensor
+        The result.
+    """
+    return tvm.compute(lhs.shape,
+                       lambda *i: tvm.select(lhs(*i) < rhs(*i),
+                                             tvm.const(1, out_type),
+                                             tvm.const(0, out_type)))
diff --git a/topi/python/topi/transform.py b/topi/python/topi/transform.py
index 3194a5601688..46998ace2099 100644
--- a/topi/python/topi/transform.py
+++ b/topi/python/topi/transform.py
@@ -2,6 +2,7 @@
 """Injective transformation operators"""
 from __future__ import absolute_import as _abs
 import tvm
+import topi
 from . import tag
 from .util import ravel_index, unravel_index, get_const_int, get_const_tuple
 
@@ -29,6 +30,60 @@ def _compute(*indices):
     return tvm.compute(new_shape, _compute)
 
 
+@tvm.tag_scope(tag=tag.BROADCAST)
+def expand_like(a, shape_like, axis):
+    """Expand an input array with the shape of second array.
+    This operation can always be composed of unsqueezing and
+    expanding dims on those unsqueezed axes.
+
+    Examples::
+    input = [ 12.  19.  27.]
+    input.shape = (3,)
+
+    new_shape_array = [[[1,2],[2,3],[1,3]],
+                      [[1,4],[4,3],[5,2]],
+                      [[7,1],[7,2],[7,3]]]
+    new_shape_array.shape = (3, 3, 2)
+
+    expand_like(input, [1,2], new_shape_array) =
+                      [[[12,12],[12,12],[12,12]],
+                      [[19,19],[19,19],[19,19]],
+                      [[27,27],[27,27],[27,27]]]
+
+    Parameters
+    ----------
+    a : tvm.Tensor
+        The tensor to be expanded.
+    shape_like : tvm.Tensor
+        The tensor to with target shape.
+    axis: list of int
+        axis to be expanded on
+    Returns
+    -------
+    ret : tvm.Tensor
+    """
+    odim = len(axis) + len(a.shape)
+    if odim != len(shape_like.shape):
+        raise ValueError("shape inconsistent when expand_like ({}, {}, {})".format(
+            len(axis), len(a.shape), len(shape_like.shape)))
+
+    real_axis = topi.reduction._get_real_axis(len(shape_like.shape), axis)
+    real_axis = sorted(real_axis)
+
+    if not real_axis:
+        return a
+
+    def _compute(*idxs):
+        indices = []
+        axis_index = 0
+        for i in range(0, len(idxs)):
+            if i not in real_axis:
+                indices.append(idxs[i])
+                axis_index += 1
+        return a(*indices)
+    return tvm.compute(shape_like.shape, _compute)
+
+
 @tvm.tag_scope(tag=tag.INJECTIVE)
 def transpose(a, axes=None):
     """Permute the dimensions of an array.
diff --git a/topi/tests/python/test_topi_math.py b/topi/tests/python/test_topi_math.py
new file mode 100644
index 000000000000..937639785b0b
--- /dev/null
+++ b/topi/tests/python/test_topi_math.py
@@ -0,0 +1,31 @@
+import numpy as np
+import tvm
+import topi
+from topi import util
+
+
+def test_util():
+    x = tvm.const(100)
+    assert util.get_const_int(x) == 100
+    assert util.get_const_tuple((x, x)) == (100, 100)
+
+
+def test_ewise():
+    m = tvm.var('m')
+    l = tvm.var('l')
+    A = tvm.placeholder((m, l), name='A')
+
+    def test_apply(func, name):
+        B = func(A)
+        assert tuple(B.shape) == tuple(A.shape)
+        assert B.op.body[0].name == name
+
+    test_apply(topi.exp, "exp")
+    test_apply(topi.tanh, "tanh")
+    test_apply(topi.sigmoid, "sigmoid")
+    test_apply(topi.log, "log")
+    test_apply(topi.sqrt, "sqrt")
+
+if __name__ == "__main__":
+    test_util()
+    test_ewise()
diff --git a/topi/tests/python/test_topi_tensor.py b/topi/tests/python/test_topi_tensor.py
new file mode 100644
index 000000000000..1489ad073f1e
--- /dev/null
+++ b/topi/tests/python/test_topi_tensor.py
@@ -0,0 +1,129 @@
+"""Test code for tensor operator"""
+import numpy as np
+import tvm
+import topi
+from tvm.contrib.pickle_memoize import memoize
+
+def verify_elemwise_sum(num_args, dtype):
+    shape = (3,5,4)
+
+    tvm_placeholders = []
+    for i in range(num_args):
+        tvm_placeholders.append(
+            tvm.placeholder(shape, name="data"+str(i), dtype=dtype))
+    esum = topi.elemwise_sum(tvm_placeholders, num_args=num_args)
+    s = tvm.create_schedule([esum.op])
+
+    @memoize("topi.tests.test_topi_elemwise_sum")
+    def get_ref_data():
+        np_nd = [np.random.uniform(0, 10, size=shape).astype(dtype)
+                 for i in range(num_args)]
+        return np_nd
+    np_nd = get_ref_data()
+
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+
+        ctx = tvm.context(device, 0)
+        out = tvm.nd.array(np.zeros(shape, dtype=dtype), ctx)
+        f = tvm.build(s, tvm_placeholders + [esum], device, name="elemwise_sum")
+        tvm_nd = [tvm.nd.array(nd, ctx) for nd in np_nd] + [out]
+        f(*tvm_nd)
+        np_out = np.sum(np.array(np_nd), axis=0)
+        np.testing.assert_allclose(out.asnumpy(), np_out, rtol=1e-5)
+
+    for device in ["llvm"]:
+        check_device(device)
+
+
+def verify_full(shape, dtype, fill_value):
+    A = tvm.placeholder(shape, dtype=dtype, name="A")
+    B = topi.full_like(A, fill_value=fill_value)
+    C = topi.full(shape=shape, dtype=dtype, fill_value=fill_value)
+    s1 = tvm.create_schedule([B.op])
+    s2 = tvm.create_schedule([C.op])
+
+    @memoize("topi.tests.test_topi_full")
+    def get_ref_data():
+        return np.full(shape, fill_value, dtype)
+    np_nd = get_ref_data()
+
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+
+        ctx = tvm.context(device, 0)
+        out = tvm.nd.array(np.zeros(shape, dtype=dtype), ctx)
+        f = tvm.build(s1, [A, B], device, name="full_like")
+        f(tvm.nd.array(np.zeros(shape, dtype), ctx), out)
+        np.testing.assert_allclose(out.asnumpy(), np_nd, rtol=1e-5)
+
+        f = tvm.build(s2, [C], device, name="full")
+        f(out)
+        np.testing.assert_allclose(out.asnumpy(), np_nd, rtol=1e-5)
+
+    for device in ["llvm"]:
+        check_device(device)
+
+
+def verify_comparator(shape, dtype, out_type='int8'):
+    A = tvm.placeholder(shape, dtype, name="A")
+    B = tvm.placeholder(shape, dtype, name="B")
+    C = topi.less(A, B)
+    s_less = tvm.create_schedule([C.op])
+
+    D = tvm.placeholder(shape, dtype, name="D")
+    E = tvm.placeholder(shape, dtype, name="E")
+    F = topi.greater(D, E, out_type)
+    s_greater = tvm.create_schedule([F.op])
+
+    @memoize("topi.tests.test_topi_indicator")
+    def get_ref_data():
+        return [np.random.uniform(0, 10, size=shape).astype(dtype),
+                np.random.uniform(0, 10, size=shape).astype(dtype)]
+    [np_l, np_r] = get_ref_data()
+
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+
+        ctx = tvm.context(device, 0)
+        out = tvm.nd.array(np.zeros(shape, dtype=out_type), ctx)
+        tvm_l = tvm.nd.array(np_l, ctx)
+        tvm_r = tvm.nd.array(np_r, ctx)
+
+        f = tvm.build(s_less, [A, B, C], device, name="less")
+        f(tvm_l, tvm_r, out)
+        np.testing.assert_allclose(out.asnumpy(), np.less(np_l, np_r).astype(out_type), rtol=1e-5)
+
+        f = tvm.build(s_greater, [D, E, F], device, name="greater")
+        f(tvm_l, tvm_r, out)
+        np.testing.assert_allclose(out.asnumpy(), np.greater(np_l, np_r).astype(out_type), rtol=1e-5)
+
+    for device in ["llvm"]:
+        check_device(device)
+
+def test_elemwise_sum():
+    verify_elemwise_sum(1, "float32")
+    verify_elemwise_sum(5, "float32")
+    verify_elemwise_sum(4, "int32")
+
+
+def test_full():
+    verify_full((3,4,5), "float32", 3.14)
+    verify_full((10,), "int32", 7)
+
+
+def test_comparator():
+    verify_comparator((3,4,5), "float32")
+    verify_comparator((7,), "int32")
+    verify_comparator((3,4,5), "float32", "int8")
+
+if __name__ == "__main__":
+    test_elemwise_sum()
+    test_full()
+    test_comparator()
diff --git a/topi/tests/python/test_topi_transform.py b/topi/tests/python/test_topi_transform.py
index 46c860fb4c53..f1f39816bcbd 100644
--- a/topi/tests/python/test_topi_transform.py
+++ b/topi/tests/python/test_topi_transform.py
@@ -150,6 +150,41 @@ def check_device(device):
         check_device(device)
 
 
+def verify_expand_like(in_shape, out_shape, axis):
+    A = tvm.placeholder(shape=in_shape, name="A")
+    B = tvm.placeholder(shape=out_shape, name="B")
+    C = topi.expand_like(A, B, axis)
+    s = tvm.create_schedule([C.op])
+
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+
+        ctx = tvm.context(device, 0)
+        f = tvm.build(s, [A, B, C], device, name="expand_like")
+        input = np.random.uniform(size=in_shape).astype(A.dtype)
+        tvm_input = tvm.nd.array(input, ctx)
+
+        odim = len(out_shape)
+        real_axis = [x if x >= 0 else x + odim for x in axis]
+        real_axis = sorted(real_axis)
+        for x in real_axis:
+            input = np.expand_dims(input, x).astype(A.dtype)
+        for x in real_axis:
+            input = np.concatenate([input]*out_shape[x], axis=x).astype(A.dtype)
+        assert input.shape == out_shape
+
+        tvm_shape_like = tvm.nd.array(np.zeros(out_shape).astype(B.dtype), ctx)
+        out = tvm.nd.array(np.zeros(out_shape).astype(A.dtype), ctx)
+        f(tvm_input, tvm_shape_like, out)
+        np.testing.assert_allclose(out.asnumpy(), input)
+
+    for device in ["llvm"]:
+        check_device(device)
+
+
 def test_expand_dims():
     verify_expand_dims((3, 10), (3, 10, 1, 1), 2, 2)
     verify_expand_dims((3, 10), (1, 3, 10), -3, 1)
@@ -191,6 +226,14 @@ def test_split():
     verify_split((2, 12, 3), [2, 4], 1)
     verify_split((10, 12, 24), [5, 7, 9], -1)
 
+
+def test_expand_like():
+    verify_expand_like((3,), (2, 3), [0])
+    verify_expand_like((2,), (2, 3), [1])
+    verify_expand_like((3, 4), (3, 5, 4), [1])
+    verify_expand_like((5, 7), (5, 6, 7, 8), [1, 3])
+
+
 if __name__ == "__main__":
     test_concatenate()
     test_tranpose()
@@ -198,3 +241,4 @@ def test_split():
     test_reshape()
     test_squeeze()
     test_split()
+    test_expand_like()

From 492087ce7d1b5b12b6c264a57a2ef5b46eda8ace Mon Sep 17 00:00:00 2001
From: PENGUINLIONG <admin@penguinliong.moe>
Date: Sun, 4 Feb 2018 02:10:50 +0800
Subject: [PATCH 131/949] Note about CodeGenC's Range of Use (#866)

---
 src/codegen/codegen_c.h | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/codegen/codegen_c.h b/src/codegen/codegen_c.h
index ce882eda4a18..bc44e3812484 100644
--- a/src/codegen/codegen_c.h
+++ b/src/codegen/codegen_c.h
@@ -24,6 +24,11 @@ using namespace ir;
  * \brief A base class to generate C code.
  *
  *  CodeGenC have two modes: generate SSA formed C code or normal form.
+ *
+ * **NOTE** CodeGenC does not aim at generating C codes consumed by MSVC or GCC,
+ * Rather, it's providing infrastructural abstraction for C variants like CUDA
+ * and OpenCL-C. You might find some odd variant features, e.g., type `int3` for
+ * a vector of 3 `int`s. For native C code generator, see `CodeGenLLVM`.
  */
 class CodeGenC :
       public ExprFunctor<void(const Expr&, std::ostream&)>,

From 01c088cad983604c38fad78778901e36da6d5b0f Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 3 Feb 2018 15:47:46 -0800
Subject: [PATCH 132/949] Fix Vulkan Build, add tanh to llvm instrinsic, fix
 halideIR (#868)

* Fix Vulkan Build, add tanh to llvm instrinsic, fix halideIR

* fix llvm tanh
---
 HalideIR                                      |  2 +-
 Jenkinsfile                                   |  1 +
 src/codegen/llvm/intrin_rule_llvm.cc          | 21 ++++++++++++
 src/codegen/spirv/ir_builder.cc               |  4 +--
 tests/ci_build/Dockerfile.gpu                 | 12 +++++++
 .../ci_build/install/ubuntu_install_vulkan.sh |  9 +++++
 topi/tests/python/test_topi_math.py           | 34 +++++++++++++++----
 7 files changed, 74 insertions(+), 9 deletions(-)
 create mode 100644 tests/ci_build/install/ubuntu_install_vulkan.sh

diff --git a/HalideIR b/HalideIR
index 87b089a0ba20..e20e5e9abb3a 160000
--- a/HalideIR
+++ b/HalideIR
@@ -1 +1 @@
-Subproject commit 87b089a0ba20f2e8257038ee9211d6816088ce95
+Subproject commit e20e5e9abb3aa43147a90a4ffb3e190f62862970
diff --git a/Jenkinsfile b/Jenkinsfile
index 4fc2285f507c..d74a20716829 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -107,6 +107,7 @@ stage('Build') {
         sh """
            echo USE_ROCM=1 >> config.mk
            echo ROCM_PATH=/opt/rocm >> config.mk
+           echo USE_VULKAN=1 >> config.mk
            """
         make('gpu', '-j2')
       }
diff --git a/src/codegen/llvm/intrin_rule_llvm.cc b/src/codegen/llvm/intrin_rule_llvm.cc
index 0338f007cac6..2e6bf061eb5e 100644
--- a/src/codegen/llvm/intrin_rule_llvm.cc
+++ b/src/codegen/llvm/intrin_rule_llvm.cc
@@ -25,6 +25,27 @@ TVM_REGISTER_GLOBAL("tvm.intrin.rule.llvm.log")
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.llvm.sqrt")
 .set_body(DispatchLLVMPureIntrin<::llvm::Intrinsic::sqrt, 1>);
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.llvm.tanh")
+.set_body([](const TVMArgs& targs, TVMRetValue* rv) {
+  Expr e = targs[0];
+  const ir::Call* call = e.as<ir::Call>();
+  CHECK(call != nullptr);
+  const Expr& x = call->args[0];
+  Expr one = make_const(x.type(), 1);
+  Expr two = make_const(x.type(), 2);
+  Expr neg_two = make_const(x.type(), -2);
+
+  Expr exp_neg2x = ir::Call::make(
+      x.type(), "exp", {neg_two * x}, ir::Call::PureIntrinsic);
+  Expr exp_pos2x = ir::Call::make(
+      x.type(), "exp", {two * x}, ir::Call::PureIntrinsic);
+
+  Expr tanh_pos = (one - exp_neg2x) / (one + exp_neg2x);
+  Expr tanh_neg = (exp_pos2x - one) / (exp_pos2x + one);
+  *rv = ir::Select::make(
+      x >= make_zero(x.type()), tanh_pos, tanh_neg);
+});
+
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.llvm.pow")
 .set_body(DispatchLLVMPureIntrin<::llvm::Intrinsic::pow, 1>);
 
diff --git a/src/codegen/spirv/ir_builder.cc b/src/codegen/spirv/ir_builder.cc
index 26f851031e5a..500c88ae832d 100644
--- a/src/codegen/spirv/ir_builder.cc
+++ b/src/codegen/spirv/ir_builder.cc
@@ -311,8 +311,8 @@ Value IRBuilder::GetConst_(const SType& dtype, const uint64_t* pvalue) {
   }
   CHECK_LE(dtype.type.bits(), 64);
   Value ret = NewValue(dtype, kConstant);
-  if (1 == dtype.type.bits() && dtype.is_uint()) {
-    // Boolean types.
+  if (dtype.type == UInt(1)) {
+    // bool types.
     if (*pvalue) {
       ib_.Begin(spv::OpConstantTrue).AddSeq(ret);
     } else {
diff --git a/tests/ci_build/Dockerfile.gpu b/tests/ci_build/Dockerfile.gpu
index b71b4cb118ec..e49e498b8d40 100644
--- a/tests/ci_build/Dockerfile.gpu
+++ b/tests/ci_build/Dockerfile.gpu
@@ -43,6 +43,11 @@ RUN bash /install/ubuntu_install_opengl.sh
 # Enable doxygen for c++ doc build
 RUN apt-get install -y doxygen graphviz
 
+# Install vulkan
+COPY install/ubuntu_install_vulkan.sh /install/ubuntu_install_vulkan.sh
+RUN bash /install/ubuntu_install_vulkan.sh
+
+
 # Environment variables
 ENV PATH=/node_modules/.bin:${PATH}
 ENV PATH=/usr/local/nvidia/bin:${PATH}
@@ -53,3 +58,10 @@ ENV C_INCLUDE_PATH=/usr/local/cuda/include:${C_INCLUDE_PATH}
 ENV LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/nvidia/lib64:${LIBRARY_PATH}
 ENV LD_LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/nvidia/lib64:${LD_LIBRARY_PATH}
 ENV LD_LIBRARY_PATH=/opt/rocm/lib:${LD_LIBRARY_PATH}
+
+ENV PATH=/node_modules/.bin:${PATH}
+
+ENV VULKAN_SDK=/usr/local/VulkanSDK/1.0.65.0/x86_64
+ENV PATH=${PATH}:${VULKAN_SDK}/bin
+ENV LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${VULKAN_SDK}/lib
+ENV VK_LAYER_PATH=${VULKAN_SDK}/etc/explicit_layer.d
diff --git a/tests/ci_build/install/ubuntu_install_vulkan.sh b/tests/ci_build/install/ubuntu_install_vulkan.sh
new file mode 100644
index 000000000000..a4155da49651
--- /dev/null
+++ b/tests/ci_build/install/ubuntu_install_vulkan.sh
@@ -0,0 +1,9 @@
+#/bin/bash
+
+wget https://sdk.lunarg.com/sdk/download/1.0.65.0/linux/vulkansdk-linux-x86_64-1.0.65.0.run
+
+bash vulkansdk-linux-x86_64-1.0.65.0.run
+mv VulkanSDK /usr/local/VulkanSDK
+cd /usr/local/VulkanSDK/1.0.65.0
+./build_tools.sh
+./build_samples.sh
diff --git a/topi/tests/python/test_topi_math.py b/topi/tests/python/test_topi_math.py
index 937639785b0b..b1b989179f08 100644
--- a/topi/tests/python/test_topi_math.py
+++ b/topi/tests/python/test_topi_math.py
@@ -15,16 +15,38 @@ def test_ewise():
     l = tvm.var('l')
     A = tvm.placeholder((m, l), name='A')
 
-    def test_apply(func, name):
+    shape = (20, 3)
+
+    def test_apply(func, name, f_numpy):
         B = func(A)
         assert tuple(B.shape) == tuple(A.shape)
         assert B.op.body[0].name == name
+        a_np = np.random.uniform(size=shape).astype(A.dtype)
+        a_np = np.abs(a_np)
+        b_np = f_numpy(a_np)
+
+        def check_device(device):
+            ctx = tvm.context(device, 0)
+            if not ctx.exist:
+                print("Skip because %s is not enabled" % device)
+                return
+            print("Running on target: %s" % device)
+            with tvm.target.create(device):
+                s = topi.generic.schedule_injective(B)
+            a = tvm.nd.array(a_np, ctx)
+            b = tvm.nd.array(np.zeros_like(b_np), ctx)
+            foo = tvm.build(s, [A, B], device, name=name)
+            foo(a, b)
+            np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+        for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan', 'llvm']:
+            check_device(device)
 
-    test_apply(topi.exp, "exp")
-    test_apply(topi.tanh, "tanh")
-    test_apply(topi.sigmoid, "sigmoid")
-    test_apply(topi.log, "log")
-    test_apply(topi.sqrt, "sqrt")
+    test_apply(topi.exp, "exp", np.exp)
+    test_apply(topi.tanh, "tanh", np.tanh)
+    test_apply(topi.sigmoid, "sigmoid", lambda x:1/(1+np.exp(-x)))
+    test_apply(topi.log, "log", np.log)
+    test_apply(topi.sqrt, "sqrt", np.sqrt)
 
 if __name__ == "__main__":
     test_util()

From 633f7f28ccb2bd48db5b96d0d786b5cc31720984 Mon Sep 17 00:00:00 2001
From: alex-weaver <awsweaver@gmail.com>
Date: Sun, 4 Feb 2018 01:50:32 +0000
Subject: [PATCH 133/949] Fix bugs with C++ TOPI flatten and relu (#869)

* Fix bugs with C++ TOPI flatten and relu

* Added regression tests. Fixed typo in CMakeLists.txt. Fixed topi cpp import removed.
---
 CMakeLists.txt                           | 2 +-
 topi/include/topi/nn.h                   | 5 ++++-
 topi/include/topi/nn/flatten.h           | 2 +-
 topi/python/topi/__init__.py             | 1 +
 topi/tests/python_cpp/test_topi_basic.py | 5 +++++
 topi/tests/python_cpp/test_topi_relu.py  | 8 +++++---
 6 files changed, 17 insertions(+), 6 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 60826a304491..e072ced6fda1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -193,7 +193,7 @@ if(USE_GRAPH_RUNTIME)
 endif(USE_GRAPH_RUNTIME)
 
 if(USE_LLVM)
-  find_spackage(LLVM CONFIG REQUIRED)
+  find_package(LLVM CONFIG REQUIRED)
   include_directories(${LLVM_INCLUDE_DIRS})
   add_definitions(${LLVM_DEFINITIONS})
   set(TVM_LLVM_VERSION ${LLVM_VERSION_MAJOR}${LLVM_VERSION_MINOR})
diff --git a/topi/include/topi/nn.h b/topi/include/topi/nn.h
index f5c6b3d2a05f..df4b73bdd6f3 100644
--- a/topi/include/topi/nn.h
+++ b/topi/include/topi/nn.h
@@ -47,7 +47,10 @@ inline tvm::Tensor relu(const tvm::Tensor& t,
                         std::string tag = kElementWise) {
   return tvm::compute(
       t->shape,
-      [&](const tvm::Array<tvm::Var>& i) { return tvm::max(t(i), threshold); },
+      [&](const tvm::Array<tvm::Var>& i) {
+        auto threshold_const = tvm::make_const(t->dtype, threshold);
+        return tvm::max(t(i), threshold_const);
+      },
       name,
       tag);
 }
diff --git a/topi/include/topi/nn/flatten.h b/topi/include/topi/nn/flatten.h
index 20a9b2cbfe78..d9577be36e5d 100644
--- a/topi/include/topi/nn/flatten.h
+++ b/topi/include/topi/nn/flatten.h
@@ -55,7 +55,7 @@ inline Tensor flatten(const Tensor& x,
       index.push_back(i);
       std::reverse(index.begin(), index.end());
       return x(index);
-    });
+    }, name, tag);
 }
 
 }  // namespace nn
diff --git a/topi/python/topi/__init__.py b/topi/python/topi/__init__.py
index 1648de33bb98..d7875cdbe521 100644
--- a/topi/python/topi/__init__.py
+++ b/topi/python/topi/__init__.py
@@ -24,3 +24,4 @@
 from . import testing
 from . import util
 from . import rocm
+from . import cpp
diff --git a/topi/tests/python_cpp/test_topi_basic.py b/topi/tests/python_cpp/test_topi_basic.py
index 1faba588b877..1057f746b004 100644
--- a/topi/tests/python_cpp/test_topi_basic.py
+++ b/topi/tests/python_cpp/test_topi_basic.py
@@ -25,7 +25,12 @@ def test_apply(func, name):
     test_apply(topi.cpp.log, "log")
     test_apply(topi.cpp.sqrt, "sqrt")
 
+def test_flatten_tag():
+    A = tvm.placeholder((3, 4), name='A')
+    B = topi.cpp.nn.flatten(A)
+    assert B.op.tag == topi.tag.INJECTIVE
 
 if __name__ == "__main__":
     test_util()
     test_ewise()
+    test_flatten_tag()
diff --git a/topi/tests/python_cpp/test_topi_relu.py b/topi/tests/python_cpp/test_topi_relu.py
index 7322f8925517..f21426635121 100644
--- a/topi/tests/python_cpp/test_topi_relu.py
+++ b/topi/tests/python_cpp/test_topi_relu.py
@@ -5,9 +5,10 @@
 import topi
 from topi.util import get_const_tuple
 
-def verify_relu(m, n):
-    A = tvm.placeholder((m, n), name='A')
+def verify_relu(m, n, dtype):
+    A = tvm.placeholder((m, n), name='A', dtype=dtype)
     B = topi.cpp.nn.relu(A)
+    assert B.dtype == dtype
 
     a_np = np.random.uniform(size=get_const_tuple(A.shape)).astype(A.dtype)
     b_np = a_np * (a_np > 0)
@@ -51,7 +52,8 @@ def verify_leaky_relu(m, alpha):
 
 
 def test_relu():
-    verify_relu(10, 128)
+    for dtype in ['float32', 'float64', 'int32', 'int16', 'int8', 'int64']:
+        verify_relu(10, 128, dtype)
 
 def test_leaky_relu():
     verify_leaky_relu(100, 0.1)

From f14e7ac44fe471c6e203a0ddf6b7801e0eaa45b9 Mon Sep 17 00:00:00 2001
From: libing4752 <libing475211023@sjtu.edu.cn>
Date: Mon, 5 Feb 2018 01:07:30 +0800
Subject: [PATCH 134/949] enhance pragma to support single point copy  (#863)

* modified schedule_dataflow_rewrite.cc to fix losing tensor problem

* modified schedule_dataflow_rewrite.cc for lint scan

* modified schedule_dataflow_rewrite.cc for lint scan

* using tensor's value_index to index output of stage op

* repare address offset for different kinds of dtype

* bc

* aaa

* aaaaa

* repare address for different dtypes

* remove nonsense files

* add whitespace of line 581

* use base alloc elem_type

* enhance the testcast of basic buffer is 64bits,32bits,16bits,8bits

* use extends[0]->type() as dtype of offset

* clear program writes

* enhance inject_copy_intin to support of pragma stmt with no loops

* fix cpplint errors

* fix cpplint error of !

* enhance detectLinearEquation to support with no loop vars

* fix cpplint errors
---
 src/arithmetic/detect_linear_equation.cc      | 31 ++++++++++---------
 src/pass/inject_copy_intrin.cc                | 26 +++++++++++-----
 .../unittest/test_pass_inject_copy_intrin.py  | 20 ++++++++++++
 3 files changed, 55 insertions(+), 22 deletions(-)

diff --git a/src/arithmetic/detect_linear_equation.cc b/src/arithmetic/detect_linear_equation.cc
index 63f582160312..642a866866d2 100644
--- a/src/arithmetic/detect_linear_equation.cc
+++ b/src/arithmetic/detect_linear_equation.cc
@@ -123,25 +123,28 @@ class LinearEqDetector
 };
 
 Array<Expr> DetectLinearEquation(const Expr& e, const Array<Var>& vars) {
-  CHECK_GE(vars.size(), 1U);
   Expr base = e;
   Array<Expr> coeff;
 
-  for (Var v : vars) {
-    LinearEqEntry ret;
-    if (!LinearEqDetector(v).Detect(base, &ret)) {
-      return Array<Expr>();
+  if (0 == vars.size()) {
+    coeff.push_back(make_const(Int(32), 1));
+  } else {
+    for (Var v : vars) {
+      LinearEqEntry ret;
+      if (!LinearEqDetector(v).Detect(base, &ret)) {
+        return Array<Expr>();
+      }
+      coeff.push_back(ret.coeff);
+      base = std::move(ret.base);
     }
-    coeff.push_back(ret.coeff);
-    base = std::move(ret.base);
-  }
 
-  std::unordered_set<const Variable*> vset;
-  for (size_t i = vars.size(); i != 1; --i) {
-    vset.insert(vars[i - 1].get());
-    // The previous coeff contains the variable
-    if (ExprUseVar(coeff[i - 2], vset)) {
-      return Array<Expr>();
+    std::unordered_set<const Variable*> vset;
+    for (size_t i = vars.size(); i != 1; --i) {
+      vset.insert(vars[i - 1].get());
+      // The previous coeff contains the variable
+      if (ExprUseVar(coeff[i - 2], vset)) {
+        return Array<Expr>();
+      }
     }
   }
   coeff.push_back(base);
diff --git a/src/pass/inject_copy_intrin.cc b/src/pass/inject_copy_intrin.cc
index cafcddcb9dde..ba44253a0cd5 100644
--- a/src/pass/inject_copy_intrin.cc
+++ b/src/pass/inject_copy_intrin.cc
@@ -40,6 +40,7 @@ class CopyIntrinInjector : public IRMutator {
  private:
   bool MatchCopyPattern(Stmt stmt, Stmt *out) {
     Stmt body = stmt;
+    bool is_single_point_copy = false;
 
     // strip the loops
     std::vector<const For*> loops;
@@ -53,7 +54,10 @@ class CopyIntrinInjector : public IRMutator {
     const Select* select = store->value.as<Select>();
     const Cast* cast = store->value.as<Cast>();
     const Load* load = store->value.as<Load>();
-
+    if (0 == loops.size()) {
+      is_single_point_copy = true;
+      CHECK(select == nullptr);
+    }
     // for now only support true condition matching
     if (select != nullptr) {
       load = select->true_value.as<Load>();
@@ -74,13 +78,19 @@ class CopyIntrinInjector : public IRMutator {
         arith::DetectLinearEquation(load->index, loop_vars);
     if (load_strides.size()  == 0 || store_strides.size() == 0) return false;
     Array<Expr> dst_shape;
-    for (const For* op : loops) {
-      dst_shape.push_back(op->extent);
+    auto loop_var_size = loop_vars.size();
+    if (is_single_point_copy) {
+      loop_var_size = 1;
+      dst_shape.push_back(make_const(Int(32), 1));
+    } else {
+      for (const For* op : loops) {
+        dst_shape.push_back(op->extent);
+      }
     }
     Array<Expr> src_shape = dst_shape;
     Array<Expr> pad_before, pad_after;
     Expr pad_value;
-    Expr src_elem_offset = load_strides[loop_vars.size()];
+    Expr src_elem_offset = load_strides[loop_var_size];
     if (select != nullptr) {
       Array<Expr> clip_bound =
           arith::DetectClipBound(select->condition, loop_vars);
@@ -114,15 +124,15 @@ class CopyIntrinInjector : public IRMutator {
       src_elem_offset = Simplify(src_elem_offset);
     }
     CHECK_EQ(load_strides.size(), store_strides.size());
-    CHECK_EQ(load_strides.size(), loop_vars.size() + 1);
-    Array<Expr> src_strides(load_strides.begin(), load_strides.begin() + loop_vars.size());
-    Array<Expr> dst_strides(store_strides.begin(), store_strides.begin() + loop_vars.size());
+    CHECK_EQ(load_strides.size(), loop_var_size + 1);
+    Array<Expr> src_strides(load_strides.begin(), load_strides.begin() + loop_var_size);
+    Array<Expr> dst_strides(store_strides.begin(), store_strides.begin() + loop_var_size);
     Buffer dst = BufferNode::make(
         Var(store->buffer_var.node_),
         store->value.type(),
         dst_shape,
         dst_strides,
-        store_strides[loop_vars.size()],
+        store_strides[loop_var_size],
         store->buffer_var->name_hint,
         GetStorageScope(store->buffer_var.get()),
         0, 0);
diff --git a/tests/python/unittest/test_pass_inject_copy_intrin.py b/tests/python/unittest/test_pass_inject_copy_intrin.py
index 08477895b322..c6ed19d65b69 100644
--- a/tests/python/unittest/test_pass_inject_copy_intrin.py
+++ b/tests/python/unittest/test_pass_inject_copy_intrin.py
@@ -44,6 +44,25 @@ def cb(src, dst, pad_before, pad_after, pad_value):
         return tvm.make.Evaluate(0)
     stmt = tvm.ir_pass.InjectCopyIntrin(stmt, "memcpy", cb)
 
+def test_single_point_test():
+    A = tvm.placeholder((1,), name='A')
+    B = tvm.compute((1,), lambda i:
+                    A[i], name='B')
+    s = tvm.create_schedule(B.op)
+    s[B].pragma(B.op.axis[0], "memcpy")
+    bounds = tvm.schedule.InferBound(s)
+    stmt = tvm.schedule.ScheduleOps(s, bounds)
+    Ab = tvm.decl_buffer(A.shape, A.dtype, name='A')
+    Bb = tvm.decl_buffer(B.shape, B.dtype, name='B')
+    stmt = tvm.ir_pass.StorageFlatten(stmt, {A: Ab, B: Bb}, 64)
+    def cb(src, dst, pad_before, pad_after, pad_value):
+        assert tvm.ir_pass.Simplify(src.elem_offset).value == 0
+        assert tvm.ir_pass.Simplify(dst.elem_offset).value == 0
+        assert tvm.ir_pass.Simplify(src.strides[0]).value == 1
+        assert tvm.ir_pass.Simplify(dst.strides[0]).value == 1
+        return tvm.make.Evaluate(0)
+    stmt = tvm.ir_pass.InjectCopyIntrin(stmt, "memcpy", cb)
+
 def assert_expr_equal(a, b):
     assert tvm.ir_pass.Simplify(a - b).value == 0
 
@@ -80,3 +99,4 @@ def cb(src, dst, pad_before, pad_after, pad_value):
     test_copy2d()
     test_copy_pad()
     test_copy_pad_split()
+    test_single_point_test()

From 9f0f5c497c3ffc357be403ae40883c1a4af9443d Mon Sep 17 00:00:00 2001
From: alex-weaver <awsweaver@gmail.com>
Date: Mon, 5 Feb 2018 01:06:30 +0000
Subject: [PATCH 135/949] Run C++ TOPI tests in Jenkins build (#870)

* Added +x permission to task_cpp_topi.sh. Added C++ topi tests to Jenkinsfile

* Fixed test_topi_math.py

* Minor style fix
---
 Jenkinsfile                         | 3 +++
 tests/scripts/task_cpp_topi.sh      | 0
 topi/tests/python/test_topi_math.py | 4 ++--
 3 files changed, 5 insertions(+), 2 deletions(-)
 mode change 100644 => 100755 tests/scripts/task_cpp_topi.sh

diff --git a/Jenkinsfile b/Jenkinsfile
index d74a20716829..75d55787ad9a 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -224,6 +224,9 @@ stage('Unit Test') {
         timeout(time: max_time, unit: 'MINUTES') {
           sh "${docker_run} cpu ./tests/scripts/task_cpp_unittest.sh"
         }
+        timeout(time: max_time, unit: 'MINUTES') {
+          sh "${docker_run} cpu ./tests/scripts/task_cpp_topi.sh"
+        }
       }
     }
   },
diff --git a/tests/scripts/task_cpp_topi.sh b/tests/scripts/task_cpp_topi.sh
old mode 100644
new mode 100755
diff --git a/topi/tests/python/test_topi_math.py b/topi/tests/python/test_topi_math.py
index b1b989179f08..2c31d0fea62c 100644
--- a/topi/tests/python/test_topi_math.py
+++ b/topi/tests/python/test_topi_math.py
@@ -21,7 +21,7 @@ def test_apply(func, name, f_numpy):
         B = func(A)
         assert tuple(B.shape) == tuple(A.shape)
         assert B.op.body[0].name == name
-        a_np = np.random.uniform(size=shape).astype(A.dtype)
+        a_np = np.random.uniform(low=1e-5, size=shape).astype(A.dtype)
         a_np = np.abs(a_np)
         b_np = f_numpy(a_np)
 
@@ -37,7 +37,7 @@ def check_device(device):
             b = tvm.nd.array(np.zeros_like(b_np), ctx)
             foo = tvm.build(s, [A, B], device, name=name)
             foo(a, b)
-            np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+            np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5, atol=1e-5)
 
         for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan', 'llvm']:
             check_device(device)

From 9ef95bbc6f67b16ba5f368d8a99b5a9623da33a3 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 4 Feb 2018 22:40:59 -0800
Subject: [PATCH 136/949] Try fix cpp topi test (#871)

* Try fix cpp topi test

* move cpp test to another stage

* update
---
 Jenkinsfile | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/Jenkinsfile b/Jenkinsfile
index 75d55787ad9a..0506caac850d 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -5,9 +5,9 @@
 
 // tvm libraries
 tvm_runtime = "lib/libtvm_runtime.so, config.mk"
-tvm_lib = "lib/libtvm.so, " + tvm_runtime
+tvm_lib = "lib/libtvm.so " + tvm_runtime
 // LLVM upstream lib
-tvm_multilib = "lib/libtvm_llvm40.so, lib/libtvm_llvm50.so, lib/libtvm_llvm60.so, " + tvm_runtime
+tvm_multilib = "lib/libtvm_llvm40.so, lib/libtvm_llvm50.so, lib/libtvm_llvm60.so, lib/libtvm_topi.so, " + tvm_runtime
 
 // command to start a docker container
 docker_run = 'tests/ci_build/ci_build.sh'
@@ -224,9 +224,6 @@ stage('Unit Test') {
         timeout(time: max_time, unit: 'MINUTES') {
           sh "${docker_run} cpu ./tests/scripts/task_cpp_unittest.sh"
         }
-        timeout(time: max_time, unit: 'MINUTES') {
-          sh "${docker_run} cpu ./tests/scripts/task_cpp_topi.sh"
-        }
       }
     }
   },
@@ -254,6 +251,7 @@ stage('Integration Test') {
         timeout(time: max_time, unit: 'MINUTES') {
           sh "${docker_run} gpu ./tests/scripts/task_python_integration.sh"
           sh "${docker_run} gpu ./tests/scripts/task_python_topi.sh"
+          sh "${docker_run} gpu ./tests/scripts/task_cpp_topi.sh"
         }
       }
     }

From 229d94b2fddd242be7fad98c079f25ddbbe4c622 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 4 Feb 2018 23:06:46 -0800
Subject: [PATCH 137/949] [CODEGEN] Fix vector element access in metal (#872)

---
 apps/ios_rpc/tests/ios_rpc_test.py |  1 +
 python/tvm/contrib/xcode.py        |  8 +++++++-
 python/tvm/target.py               |  2 +-
 src/codegen/codegen_metal.cc       | 14 ++++++++++++++
 src/codegen/codegen_metal.h        |  6 ++++++
 5 files changed, 29 insertions(+), 2 deletions(-)

diff --git a/apps/ios_rpc/tests/ios_rpc_test.py b/apps/ios_rpc/tests/ios_rpc_test.py
index a3df1d3a9043..80317a6e350d 100644
--- a/apps/ios_rpc/tests/ios_rpc_test.py
+++ b/apps/ios_rpc/tests/ios_rpc_test.py
@@ -59,6 +59,7 @@ def test_rpc_module():
     # Start RPC test server that contains the compiled library.
     server = xcode.popen_test_rpc(proxy_host, proxy_port, key,
                                   destination=destination,
+                                  options=['-quiet'],
                                   libs=[path_dso1, path_dso2])
 
     # connect to the proxy
diff --git a/python/tvm/contrib/xcode.py b/python/tvm/contrib/xcode.py
index 7f9b53c5b532..e05df7181544 100644
--- a/python/tvm/contrib/xcode.py
+++ b/python/tvm/contrib/xcode.py
@@ -201,5 +201,11 @@ def popen_test_rpc(host,
     if options:
         cmd += options
     cmd += ["test"]
-    proc = subprocess.Popen(cmd)
+    if "-quiet" in options:
+        with open(os.devnull, 'w') as devnull:
+            proc = subprocess.Popen(cmd,
+                                    stderr=subprocess.STDOUT,
+                                    stdout=devnull)
+    else:
+        proc = subprocess.Popen(cmd)
     return proc
diff --git a/python/tvm/target.py b/python/tvm/target.py
index 5196241cd7cf..a24eefcb5ba7 100644
--- a/python/tvm/target.py
+++ b/python/tvm/target.py
@@ -117,7 +117,7 @@ def __init__(self,
             self.keys += ("rocm", "gpu")
             self.max_num_threads = 256
         elif target_name in ("metal", "vulkan"):
-            self.keys += ("gpu",)
+            self.keys += (target_name, "gpu",)
             self.max_num_threads = 256
         elif target_name in ("opengl",):
             self.keys += ("opengl",)
diff --git a/src/codegen/codegen_metal.cc b/src/codegen/codegen_metal.cc
index 0df63aad49a2..fe8486668da0 100644
--- a/src/codegen/codegen_metal.cc
+++ b/src/codegen/codegen_metal.cc
@@ -186,6 +186,20 @@ void CodeGenMetal::PrintStorageSync(const Call* op) {
   }
 }
 
+void CodeGenMetal::PrintVecElemLoad(const std::string& vec,
+                                    Type t, int i,
+                                    std::ostream& os) {  // NOLINT(*)
+  os << vec << "[" << i << "]";
+}
+
+void CodeGenMetal::PrintVecElemStore(const std::string& vec,
+                                     Type t, int i,
+                                     const std::string& value) {
+  this->PrintIndent();
+  stream << vec << "[" << i << "]"
+         << " = " << value << ";\n";
+}
+
 void CodeGenMetal::PrintStorageScope(
     const std::string& scope, std::ostream& os) { // NOLINT(*)
   if (scope == "global") {
diff --git a/src/codegen/codegen_metal.h b/src/codegen/codegen_metal.h
index 80efef67332e..6f8bef64bbcf 100644
--- a/src/codegen/codegen_metal.h
+++ b/src/codegen/codegen_metal.h
@@ -25,6 +25,12 @@ class CodeGenMetal final : public CodeGenC {
   void PrintStorageSync(const Call* op) final;  // NOLINT(*)
   void PrintType(Type t, std::ostream& os) final; // NOLINT(*)
   void BindThreadIndex(const IterVar& iv) final;  // NOLINT(*)
+  // print load of single element
+  void PrintVecElemLoad(
+      const std::string& vec, Type t, int i, std::ostream& os) final;  // NOLINT(*)
+  // print store of single element.
+  void PrintVecElemStore(
+      const std::string& vec, Type t, int i, const std::string& value) final;
   // overload visitor
   void VisitExpr_(const Broadcast* op, std::ostream& os) final; // NOLINT(*)
 

From 0d2a9e8bf5a06d3f57f3b450a2c83d8f23433131 Mon Sep 17 00:00:00 2001
From: alex-weaver <awsweaver@gmail.com>
Date: Mon, 5 Feb 2018 19:11:54 +0000
Subject: [PATCH 138/949] Fixed namespacing issues in schedules (#873)

* Fixed namespacing issues in schedules

* Fixed compile error
---
 topi/include/topi/cuda/dense.h        |  4 +--
 topi/include/topi/cuda/extern.h       |  2 +-
 topi/include/topi/cuda/injective.h    |  2 +-
 topi/include/topi/cuda/pooling.h      | 14 ++++----
 topi/include/topi/cuda/reduction.h    |  4 +--
 topi/include/topi/generic/default.h   | 47 ++++++++++++++++++---------
 topi/include/topi/generic/injective.h |  2 +-
 topi/include/topi/x86/bnn.h           |  4 +--
 topi/include/topi/x86/default.h       | 36 ++++++++++++++++----
 topi/include/topi/x86/injective.h     |  2 +-
 topi/src/topi.cc                      | 12 +++++--
 11 files changed, 88 insertions(+), 41 deletions(-)

diff --git a/topi/include/topi/cuda/dense.h b/topi/include/topi/cuda/dense.h
index b012f108fd93..a07aafea6e30 100644
--- a/topi/include/topi/cuda/dense.h
+++ b/topi/include/topi/cuda/dense.h
@@ -86,7 +86,7 @@ inline Schedule schedule_dense(const Target &target, const Array<Tensor>& outs)
     auto dense_f = s.rfactor(dense, kf)[0];
 
     Tensor out;
-    if (contains(s->outputs, dense->op)) {
+    if (detail::contains(s->outputs, dense->op)) {
       out = dense;
     } else {
       out = outs[0]->op.output(0);
@@ -107,7 +107,7 @@ inline Schedule schedule_dense(const Target &target, const Array<Tensor>& outs)
   traverse = [&](const Operation& op) {
     // Inline all one-to-one-mapping operators except the last stage (output)
     if (is_broadcast(op->tag)) {
-      if (!contains(s->outputs, op)) {
+      if (!detail::contains(s->outputs, op)) {
         s[op].compute_inline();
       }
       for (auto tensor : op->InputTensors()) {
diff --git a/topi/include/topi/cuda/extern.h b/topi/include/topi/cuda/extern.h
index b97c33943010..1c2f9a79ab00 100644
--- a/topi/include/topi/cuda/extern.h
+++ b/topi/include/topi/cuda/extern.h
@@ -27,7 +27,7 @@ namespace cuda {
  */
 inline Schedule ScheduleOutputForExtern(Target target, Operation op, Schedule sch) {
   auto x = op.output(0);
-  auto fused = Fuse(sch[x], sch[x]->op.as<ComputeOpNode>()->axis);
+  auto fused = detail::Fuse(sch[x], sch[x]->op.as<ComputeOpNode>()->axis);
   auto num_thread = target.max_num_threads;
   IterVar bx, tx;
   sch[x].split(fused, num_thread, &bx, &tx);
diff --git a/topi/include/topi/cuda/injective.h b/topi/include/topi/cuda/injective.h
index c213002cc52f..e8e60fb6809e 100644
--- a/topi/include/topi/cuda/injective.h
+++ b/topi/include/topi/cuda/injective.h
@@ -24,7 +24,7 @@ namespace cuda {
 */
 inline void ScheduleInjectiveOp(const Target &target, Operation op, Schedule s) {
   auto x = op.output(0);
-  auto fused = Fuse(s[x], s[x]->op.as<ComputeOpNode>()->axis);
+  auto fused = detail::Fuse(s[x], s[x]->op.as<ComputeOpNode>()->axis);
   auto num_thread = target.max_num_threads;
   IterVar bx, tx;
   s[x].split(fused, num_thread, &bx, &tx);
diff --git a/topi/include/topi/cuda/pooling.h b/topi/include/topi/cuda/pooling.h
index e8fb9546a241..d7536f315dba 100644
--- a/topi/include/topi/cuda/pooling.h
+++ b/topi/include/topi/cuda/pooling.h
@@ -37,19 +37,19 @@ inline Schedule schedule_pool(const Target &target, const Array<Tensor>& outs) {
     auto num_thread = target.max_num_threads;
     Tensor out;
     Tensor OL;
-    if (contains(s->outputs, pool->op)) {
+    if (detail::contains(s->outputs, pool->op)) {
       out = pool;
       OL = s.cache_write(pool, "local");
     } else {
       out = outs[0]->op.output(0);
       s[pool].set_scope("local");
     }
-    auto fused = Fuse(s[out], s[out]->op.as<ComputeOpNode>()->axis);
+    auto fused = detail::Fuse(s[out], s[out]->op.as<ComputeOpNode>()->axis);
     IterVar bx, tx;
     s[out].split(fused, num_thread, &bx, &tx);
     s[out].bind(bx, tvm::thread_axis(Range(), "blockIdx.x"));
     s[out].bind(tx, tvm::thread_axis(Range(), "threadIdx.x"));
-    if (contains(s->outputs, pool->op)) {
+    if (detail::contains(s->outputs, pool->op)) {
       s[OL].compute_at(s[out], tx);
     } else {
       s[pool].compute_at(s[out], tx);
@@ -60,7 +60,7 @@ inline Schedule schedule_pool(const Target &target, const Array<Tensor>& outs) {
   traverse = [&](const Operation& op) {
     // Inline all one-to-one-mapping operators except the last stage (output)
     if (is_broadcast(op->tag)) {
-      if (!contains(s->outputs, op)) {
+      if (!detail::contains(s->outputs, op)) {
         s[op].compute_inline();
       }
       for (auto tensor : op->InputTensors()) {
@@ -105,7 +105,7 @@ inline Schedule schedule_global_pool(const Target &target, const Array<Tensor>&
     auto thread_y = tvm::thread_axis(Range(0, num_thread), "threadIdx.y");
     Tensor out;
     Tensor OL;
-    if (contains(s->outputs, pool->op)) {
+    if (detail::contains(s->outputs, pool->op)) {
       out = pool;
       OL = s.cache_write(pool, "local");
     } else {
@@ -126,7 +126,7 @@ inline Schedule schedule_global_pool(const Target &target, const Array<Tensor>&
     s[out].bind(by, block_y);
     s[out].bind(bx, block_x);
 
-    if (contains(s->outputs, pool->op)) {
+    if (detail::contains(s->outputs, pool->op)) {
       s[OL].compute_at(s[out], tx);
     } else {
       s[pool].compute_at(s[out], tx);
@@ -137,7 +137,7 @@ inline Schedule schedule_global_pool(const Target &target, const Array<Tensor>&
   traverse = [&](const Operation& op) {
     // Inline all one-to-one-mapping operators except the last stage (output)
     if (is_broadcast(op->tag)) {
-      if (!contains(s->outputs, op)) {
+      if (!detail::contains(s->outputs, op)) {
         s[op].compute_inline();
       }
       for (auto tensor : op->InputTensors()) {
diff --git a/topi/include/topi/cuda/reduction.h b/topi/include/topi/cuda/reduction.h
index 554224a9036c..e7a41d9274fa 100644
--- a/topi/include/topi/cuda/reduction.h
+++ b/topi/include/topi/cuda/reduction.h
@@ -65,7 +65,7 @@ Schedule ScheduleReduce(const Target& target,
     thread_x = tvm::thread_axis(Range(0, num_thread), "threadIdx.x");
   }
 
-  auto fused_reduce = Fuse(out_stage, out_stage->op.as<ComputeOpNode>()->reduce_axis);
+  auto fused_reduce = detail::Fuse(out_stage, out_stage->op.as<ComputeOpNode>()->reduce_axis);
 
   IterVar ko, ki;
   out_stage.split(fused_reduce, num_thread, &ko, &ki);
@@ -87,7 +87,7 @@ Schedule ScheduleReduce(const Target& target,
   auto stage_real = sch[real_output];
   if (!all_reduce) {
     // Fuse and split the axis
-    auto fused_outer = Fuse(stage_real, stage_real->op.as<ComputeOpNode>()->axis);
+    auto fused_outer = detail::Fuse(stage_real, stage_real->op.as<ComputeOpNode>()->axis);
     IterVar bx, outer_in;
     stage_real.split(fused_outer, num_thread, &bx, &outer_in);
 
diff --git a/topi/include/topi/generic/default.h b/topi/include/topi/generic/default.h
index 0deadb07e241..5e0615742a14 100644
--- a/topi/include/topi/generic/default.h
+++ b/topi/include/topi/generic/default.h
@@ -16,27 +16,42 @@ using namespace tvm;
 
 namespace generic {
 /*!
- * \brief Create a generic default schedule for the given output tensors.
- *
- * \param target The target to generate a schedule for.
- * \param outs The output tensors.
- * \param auto_inline Whether to apply the auto inline step.
- *
- * \return A schedule for the given ops.
- */
-inline Schedule default_schedule(const Target& target, Array<Tensor> outs, bool auto_inline) {
+* \brief Create a generic default schedule for the given output tensors.
+*
+* \param target The target to generate a schedule for.
+* \param outs The output tensors.
+*
+* \return A schedule for the given ops.
+*/
+inline Schedule default_schedule(const Target& target, Array<Tensor> outs) {
+  Array<Operation> out_ops;
+  for (auto t : outs) {
+    out_ops.push_back(t->op);
+  }
+  auto s = create_schedule(out_ops);
+  return s;
+}
+
+/*!
+* \brief Create a generic default schedule for the given output tensors, and apply
+* auto inline
+*
+* \param target The target to generate a schedule for.
+* \param outs The output tensors.
+*
+* \return A schedule for the given ops.
+*/
+inline Schedule default_schedule_auto_inline(const Target& target, Array<Tensor> outs) {
   Array<Operation> out_ops;
   for (auto t : outs) {
     out_ops.push_back(t->op);
   }
   auto s = create_schedule(out_ops);
-  if (auto_inline) {
-    auto x = outs[0];
-    tvm::schedule::AutoInlineInjective(s);
-    auto axis = s[x]->op.as<ComputeOpNode>()->axis;
-    if (axis.size() > 0) {
-      Fuse(s[x], axis);
-    }
+  auto x = outs[0];
+  tvm::schedule::AutoInlineInjective(s);
+  auto axis = s[x]->op.as<ComputeOpNode>()->axis;
+  if (axis.size() > 0) {
+    detail::Fuse(s[x], axis);
   }
   return s;
 }
diff --git a/topi/include/topi/generic/injective.h b/topi/include/topi/generic/injective.h
index 8c0a12d23b60..c1d63eac6af7 100644
--- a/topi/include/topi/generic/injective.h
+++ b/topi/include/topi/generic/injective.h
@@ -32,7 +32,7 @@ inline Schedule schedule_injective(const Target &target, const Array<Tensor>& ou
   auto s = create_schedule(out_ops);
   tvm::schedule::AutoInlineInjective(s);
   auto x = outs[0];
-  Fuse(s[x], s[x]->op.as<ComputeOpNode>()->axis);
+  detail::Fuse(s[x], s[x]->op.as<ComputeOpNode>()->axis);
 
   return s;
 }
diff --git a/topi/include/topi/x86/bnn.h b/topi/include/topi/x86/bnn.h
index 8e50c5bac124..f379ada8a516 100644
--- a/topi/include/topi/x86/bnn.h
+++ b/topi/include/topi/x86/bnn.h
@@ -68,7 +68,7 @@ inline Schedule schedule_binary_dense(const Target &target, const Array<Tensor>&
     s[C].parallel(s[C]->op.as<ComputeOpNode>()->axis[0]);
 
     Tensor out;
-    if (contains(s->outputs, C->op)) {
+    if (detail::contains(s->outputs, C->op)) {
       out = C;
     } else {
       out = outs[0]->op.output(0);
@@ -83,7 +83,7 @@ inline Schedule schedule_binary_dense(const Target &target, const Array<Tensor>&
   traverse = [&](const Operation& op) {
     // Inline all one-to-one-mapping operators except the last stage (output)
     if (is_broadcast(op->tag)) {
-      if (!contains(s->outputs, op)) {
+      if (!detail::contains(s->outputs, op)) {
         s[op].compute_inline();
       }
       for (auto tensor : op->InputTensors()) {
diff --git a/topi/include/topi/x86/default.h b/topi/include/topi/x86/default.h
index 4a3f36e4ae66..5d71855a8c94 100644
--- a/topi/include/topi/x86/default.h
+++ b/topi/include/topi/x86/default.h
@@ -16,7 +16,7 @@ using namespace tvm;
 
 namespace x86 {
 /*!
-* \brief Create a default x86 schedule for the given ops.
+* \brief Helper to create a default x86 schedule for the given ops.
 *
 * \param target The target to generate a schedule for.
 * \param outs The output tensors.
@@ -24,9 +24,9 @@ namespace x86 {
 *
 * \return A schedule for the given ops.
 */
-inline Schedule default_schedule(const Target &target,
-                                 const Array<Tensor>& outs,
-                                 bool auto_inline) {
+inline Schedule MakeDefaultSchedule(const Target &target,
+                                    const Array<Tensor>& outs,
+                                    bool auto_inline) {
   Array<Operation> out_ops;
   for (auto t : outs) {
     out_ops.push_back(t->op);
@@ -38,7 +38,7 @@ inline Schedule default_schedule(const Target &target,
   if (auto_inline) {
     tvm::schedule::AutoInlineInjective(s);
     if (axis.size() > 0) {
-      Fuse(s[x], axis);
+      detail::Fuse(s[x], axis);
     }
     return s;
   }
@@ -46,7 +46,7 @@ inline Schedule default_schedule(const Target &target,
   if (axis.size() == 4) {
     auto n = axis[0];
     auto c = axis[1];
-    auto fused = Fuse(s[x], { n, c });  // for nhwc layout, fuse n and h
+    auto fused = detail::Fuse(s[x], { n, c });  // for nhwc layout, fuse n and h
     s[x].parallel(fused);
   } else {
     s[x].parallel(axis[0]);
@@ -55,6 +55,30 @@ inline Schedule default_schedule(const Target &target,
   return s;
 }
 
+/*!
+* \brief Create a default x86 schedule for the given ops.
+*
+* \param target The target to generate a schedule for.
+* \param outs The output tensors.
+*
+* \return A schedule for the given ops.
+*/
+inline Schedule default_schedule(const Target &target, const Array<Tensor>& outs) {
+  return MakeDefaultSchedule(target, outs, false);
+}
+
+/*!
+* \brief Create a default x86 schedule for the given ops, with auto inline
+*
+* \param target The target to generate a schedule for.
+* \param outs The output tensors.
+*
+* \return A schedule for the given ops.
+*/
+inline Schedule default_schedule_auto_inline(const Target &target, const Array<Tensor>& outs) {
+  return MakeDefaultSchedule(target, outs, true);
+}
+
 }  // namespace x86
 }  // namespace topi
 #endif  // TOPI_X86_DEFAULT_H_
diff --git a/topi/include/topi/x86/injective.h b/topi/include/topi/x86/injective.h
index 40045b4920ce..9e5a603af908 100644
--- a/topi/include/topi/x86/injective.h
+++ b/topi/include/topi/x86/injective.h
@@ -36,7 +36,7 @@ inline Schedule schedule_injective(const Target &target, const Array<Tensor>& ou
   if (axis.size() == 4) {
     auto n = axis[0];
     auto c = axis[1];
-    auto fused = Fuse(s[x], { n, c });  // for nhwc layout, fuse n and h
+    auto fused = detail::Fuse(s[x], { n, c });  // for nhwc layout, fuse n and h
     s[x].parallel(fused);
   } else {
     s[x].parallel(axis[0]);
diff --git a/topi/src/topi.cc b/topi/src/topi.cc
index 03dd004bd42c..970e982276f6 100644
--- a/topi/src/topi.cc
+++ b/topi/src/topi.cc
@@ -343,7 +343,11 @@ TVM_REGISTER_GLOBAL("topi.nn.log_softmax")
 /* Generic schedules */
 TVM_REGISTER_GLOBAL("topi.generic.default_schedule")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
-  *rv = topi::generic::default_schedule(args[0], args[1], args[2]);
+  if (args[2]) {
+    *rv = topi::generic::default_schedule_auto_inline(args[0], args[1]);
+  } else {
+    *rv = topi::generic::default_schedule(args[0], args[1]);
+  }
   });
 
 TVM_REGISTER_GLOBAL("topi.generic.schedule_extern")
@@ -369,7 +373,11 @@ TVM_REGISTER_GLOBAL("topi.x86.schedule_binary_dense")
 
 TVM_REGISTER_GLOBAL("topi.x86.default_schedule")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
-  *rv = topi::x86::default_schedule(args[0], args[1], args[2]);
+  if (args[2]) {
+    *rv = topi::x86::default_schedule_auto_inline(args[0], args[1]);
+  } else {
+    *rv = topi::x86::default_schedule(args[0], args[1]);
+  }
   });
 
 TVM_REGISTER_GLOBAL("topi.x86.schedule_injective")

From 1b0f53cc927145ca845aeb31254ee5aa6a435882 Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Wed, 7 Feb 2018 01:23:19 +0800
Subject: [PATCH 139/949] support to keep trivial loops with extent of 1 (#877)

---
 include/tvm/operation.h          | 16 +++++++++++-----
 include/tvm/schedule_pass.h      |  3 ++-
 src/api/api_schedule.cc          |  9 ++++++++-
 src/codegen/build_module.cc      |  2 +-
 src/op/compute_op.cc             | 21 ++++++++++++---------
 src/op/compute_op.h              | 12 +++++++++---
 src/op/cross_thread_reduction.cc |  5 +++--
 src/op/extern_op.cc              |  3 ++-
 src/op/op_util.cc                |  7 ++++---
 src/op/op_util.h                 |  4 +++-
 src/op/placeholder_op.cc         |  3 ++-
 src/op/scan_op.cc                |  5 +++--
 src/op/tensorize.cc              |  5 +++--
 src/schedule/schedule_ops.cc     | 13 +++++++------
 14 files changed, 70 insertions(+), 38 deletions(-)

diff --git a/include/tvm/operation.h b/include/tvm/operation.h
index d598df8d21b1..9b950c3d544f 100644
--- a/include/tvm/operation.h
+++ b/include/tvm/operation.h
@@ -117,11 +117,13 @@ class OperationNode : public FunctionBaseNode {
    * \brief Build the statement that provide the output tensors.
    * \param stage The schedule stage of the op.
    * \param dom_map The domain map of all iteration domains.
+   * \param del_trivial_loop Whether eliminate trivial loop with extent of 1
    * \return A statement that add production and wraps consumer.
    */
   virtual Stmt BuildProvide(
       const Stage& stage,
-      const std::unordered_map<IterVar, Range>& dom_map) const = 0;
+      const std::unordered_map<IterVar, Range>& dom_map,
+      bool del_trivial_loop) const = 0;
 
   static constexpr const char* _type_key = "Operation";
 
@@ -160,7 +162,8 @@ class PlaceholderOpNode : public OperationNode {
       const Stmt& body) const final;
   Stmt BuildProvide(
       const Stage& stage,
-      const std::unordered_map<IterVar, Range>& dom_map) const final;
+      const std::unordered_map<IterVar, Range>& dom_map,
+      bool del_trivial_loop) const final;
 
   void VisitAttrs(AttrVisitor* v) final {
     v->Visit("name", &name);
@@ -211,7 +214,8 @@ class ComputeOpNode : public OperationNode {
       const Stmt& body) const final;
   Stmt BuildProvide(
       const Stage& stage,
-      const std::unordered_map<IterVar, Range>& dom_map) const final;
+      const std::unordered_map<IterVar, Range>& dom_map,
+      bool del_trivial_loop) const final;
 
   void VisitAttrs(AttrVisitor* v) final {
     v->Visit("name", &name);
@@ -282,7 +286,8 @@ class ScanOpNode : public OperationNode {
       const Stmt& body) const final;
   Stmt BuildProvide(
       const Stage& stage,
-      const std::unordered_map<IterVar, Range>& dom_map) const final;
+      const std::unordered_map<IterVar, Range>& dom_map,
+      bool del_trivial_loop) const final;
 
   void VisitAttrs(AttrVisitor* v) final {
     v->Visit("name", &name);
@@ -345,7 +350,8 @@ class ExternOpNode : public OperationNode {
       const Stmt& body) const final;
   Stmt BuildProvide(
       const Stage& stage,
-      const std::unordered_map<IterVar, Range>& dom_map) const final;
+      const std::unordered_map<IterVar, Range>& dom_map,
+      bool del_trivial_loop) const final;
 
   void VisitAttrs(AttrVisitor* v) final {
     v->Visit("name", &name);
diff --git a/include/tvm/schedule_pass.h b/include/tvm/schedule_pass.h
index 719448513fb8..011c7510ced9 100644
--- a/include/tvm/schedule_pass.h
+++ b/include/tvm/schedule_pass.h
@@ -29,9 +29,10 @@ Map<IterVar, Range> InferBound(const Schedule& sch);
  *
  * \param s The schedule to be realized
  * \param dom_map The domain of each iter vars.
+ * \param del_trivial_loop Whether delete trivial loops with extent of 1
  * \return the result Stmt
  */
-Stmt ScheduleOps(Schedule s, Map<IterVar, Range> dom_map);
+Stmt ScheduleOps(Schedule s, Map<IterVar, Range> dom_map, bool del_trivial_loop);
 
 /*!
  * \brief To automatically inline the element-wise operations.
diff --git a/src/api/api_schedule.cc b/src/api/api_schedule.cc
index 0b5ef251503c..b1a6729ec662 100644
--- a/src/api/api_schedule.cc
+++ b/src/api/api_schedule.cc
@@ -24,6 +24,14 @@ TVM_REGISTER_API("schedule.AutoInlineInjective")
     AutoInlineInjective(args[0]);
   });
 
+TVM_REGISTER_API("schedule.ScheduleOps")
+.set_body([](TVMArgs args, TVMRetValue* ret) {
+  if (args.size() == 2)
+    *ret = ScheduleOps(args[0], args[1], true);
+  else
+    *ret = ScheduleOps(args[0], args[1], args[2]);
+});
+
 #define REGISTER_SCHEDULE_PASS1(PassName)                         \
   TVM_REGISTER_API("schedule."#PassName)                          \
   .set_body([](TVMArgs args,  TVMRetValue *ret) {                 \
@@ -43,7 +51,6 @@ REGISTER_SCHEDULE_PASS2(PostDFSOrder);
 REGISTER_SCHEDULE_PASS1(CreateAttachPath);
 REGISTER_SCHEDULE_PASS1(ScanGetBody);
 REGISTER_SCHEDULE_PASS1(ScanFixPointAnalysis);
-REGISTER_SCHEDULE_PASS2(ScheduleOps);
 
 }  // namespace schedule
 }  // namespace tvm
diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc
index 53aef46a9751..2e8e5bb278eb 100644
--- a/src/codegen/build_module.cc
+++ b/src/codegen/build_module.cc
@@ -211,7 +211,7 @@ Stmt BuildStmt(Schedule sch,
 
   // Phase 0
   auto bounds = schedule::InferBound(sch);
-  auto stmt = schedule::ScheduleOps(sch, bounds);
+  auto stmt = schedule::ScheduleOps(sch, bounds, true);
   stmt = ir::InjectPrefetch(stmt);
 
   // Phase 1
diff --git a/src/op/compute_op.cc b/src/op/compute_op.cc
index 9e7db1deea45..8b8bfbfe602e 100644
--- a/src/op/compute_op.cc
+++ b/src/op/compute_op.cc
@@ -305,9 +305,10 @@ Stmt MakeProvide(const ComputeOpNode* op,
 
 Stmt MakeComputeStmt(const ComputeOpNode* self,
                      const Stage& stage,
-                     const std::unordered_map<IterVar, Range>& dom_map) {
+                     const std::unordered_map<IterVar, Range>& dom_map,
+                     bool del_trivial_loop) {
   // grab the nest structure
-  ComputeLoopNest n = ComputeLoopNest::make(self, stage, dom_map);
+  ComputeLoopNest n = ComputeLoopNest::make(self, stage, dom_map, del_trivial_loop);
   // Normal loop structure
   n.init_nest.emplace_back(op::MakeIfNest(n.init_predicates));
   n.main_nest.emplace_back(op::MakeIfNest(n.main_predicates));
@@ -387,28 +388,30 @@ ComputeType DetectComputeType(const ComputeOpNode* self,
 // implement the provide utility.
 Stmt ComputeOpNode::BuildProvide(
     const Stage& stage,
-    const std::unordered_map<IterVar, Range>& dom_map) const {
+    const std::unordered_map<IterVar, Range>& dom_map,
+    bool del_trivial_loop) const {
   CHECK_EQ(stage->op.operator->(), this);
   ComputeType ctype = DetectComputeType(this, stage);
   if (ctype == ComputeType::kCrossThreadReduction) {
     // specially handle cross thread reduction.
-    return MakeCrossThreadReduction(this, stage, dom_map);
+    return MakeCrossThreadReduction(this, stage, dom_map, del_trivial_loop);
   } else if (ctype == ComputeType::kTensorize) {
-    return MakeTensorize(this, stage, dom_map);
+    return MakeTensorize(this, stage, dom_map, del_trivial_loop);
   } else {
-    return MakeComputeStmt(this, stage, dom_map);
+    return MakeComputeStmt(this, stage, dom_map, del_trivial_loop);
   }
 }
 
 ComputeLoopNest ComputeLoopNest::make(
     const ComputeOpNode* self,
     const Stage& stage,
-    const std::unordered_map<IterVar, Range>& dom_map) {
+    const std::unordered_map<IterVar, Range>& dom_map,
+    bool del_trivial_loop) {
   CHECK_EQ(stage->op.operator->(), self);
   ComputeLoopNest ret;
   // make main loop nest
   ret.main_nest = op::MakeLoopNest(
-      stage, dom_map, 0, false, std::unordered_set<IterVar>(), &ret.main_vmap);
+      stage, dom_map, 0, false, std::unordered_set<IterVar>(), &ret.main_vmap, del_trivial_loop);
   ret.main_predicates = schedule::MakeBoundCheck(
       stage, dom_map, ret.main_vmap, false,
       std::unordered_set<IterVar>());
@@ -450,7 +453,7 @@ ComputeLoopNest ComputeLoopNest::make(
     }
     ret.init_nest = op::MakeLoopNest(
         stage, dom_map, begin_loop, true,
-        skip_iter, &(ret.init_vmap));
+        skip_iter, &(ret.init_vmap), del_trivial_loop);
     ret.init_predicates = schedule::MakeBoundCheck(
         stage, dom_map, ret.init_vmap, true, skip_iter);
     for (auto& e : ret.init_predicates) {
diff --git a/src/op/compute_op.h b/src/op/compute_op.h
index 95dc0f44d8d4..2164feee6988 100644
--- a/src/op/compute_op.h
+++ b/src/op/compute_op.h
@@ -37,12 +37,14 @@ struct ComputeLoopNest {
    * \param self The pointer to compute op.
    * \param stage The scxhedule stage.
    * \param dom_map The domain map.
+   * \param del_trivial_loop Whether eliminate trivial loops with extent of 1
    * \return The constructed loop nest
    */
   static ComputeLoopNest make(
       const ComputeOpNode* self,
       const Stage& stage,
-      const std::unordered_map<IterVar, Range>& dom_map);
+      const std::unordered_map<IterVar, Range>& dom_map,
+      bool del_trivial_loop);
 };
 
 /*!
@@ -50,23 +52,27 @@ struct ComputeLoopNest {
  * \param self The pointer to ComputeOpNode
  * \param stage The schedule stage.
  * \param dom_map The domain map.
+ * \param del_trivial_loop Wheter eliminate trivial loops with extent of 1
  * \return The created statement.
  */
 Stmt MakeCrossThreadReduction(
     const ComputeOpNode* self,
     const Stage& stage,
-    const std::unordered_map<IterVar, Range>& dom_map);
+    const std::unordered_map<IterVar, Range>& dom_map,
+    bool del_trivial_loop);
 
 /*!
  * \brief Build body of compute for tensorization.
  * \param self The pointer to ComputeOpNode
  * \param stage The schedule stage.
  * \param dom_map The domain map.
+ * \param del_trivial_loop Wheter eliminate trivial loops with extent of 1
  * \return The created statement.
  */
 Stmt MakeTensorize(const ComputeOpNode* self,
                    const Stage& stage,
-                   const std::unordered_map<IterVar, Range>& dom_map);
+                   const std::unordered_map<IterVar, Range>& dom_map,
+                   bool del_trivial_loop);
 }  // namespace tvm
 
 #endif  // TVM_OP_COMPUTE_OP_H_
diff --git a/src/op/cross_thread_reduction.cc b/src/op/cross_thread_reduction.cc
index 6eec3bd69d6a..e32b3dcd4407 100644
--- a/src/op/cross_thread_reduction.cc
+++ b/src/op/cross_thread_reduction.cc
@@ -13,14 +13,15 @@ using namespace ir;
 Stmt MakeCrossThreadReduction(
     const ComputeOpNode* self,
     const Stage& stage,
-    const std::unordered_map<IterVar, Range>& dom_map) {
+    const std::unordered_map<IterVar, Range>& dom_map,
+    bool del_trivial_loop) {
   Array<Expr>  args;
   for (IterVar iv : self->axis) {
     args.push_back(iv->var);
   }
   std::unordered_map<IterVar, Expr> value_map;
   auto nest = op::MakeLoopNest(
-      stage, dom_map, 0, false, std::unordered_set<IterVar>(), &value_map);
+      stage, dom_map, 0, false, std::unordered_set<IterVar>(), &value_map, del_trivial_loop);
   auto conds = schedule::MakeBoundCheck(
       stage, dom_map, value_map, false,
       std::unordered_set<IterVar>());
diff --git a/src/op/extern_op.cc b/src/op/extern_op.cc
index e83f97b14652..df3a32d50fe7 100644
--- a/src/op/extern_op.cc
+++ b/src/op/extern_op.cc
@@ -128,7 +128,8 @@ Stmt ExternOpNode::BuildRealize(
 
 Stmt ExternOpNode::BuildProvide(
     const Stage& stage,
-    const std::unordered_map<IterVar, Range>& dom_map) const {
+    const std::unordered_map<IterVar, Range>& dom_map,
+    bool del_trivial_loop) const {
   CHECK_EQ(stage->op.operator->(), this);
   Stmt ret = AttrStmt::make(make_zero(Int(32)), attr::extern_scope, 0, this->body);
   auto f_push_bind = [&ret](Buffer buffer, Tensor tensor) {
diff --git a/src/op/op_util.cc b/src/op/op_util.cc
index 78e092ca844e..ef7af85bf079 100644
--- a/src/op/op_util.cc
+++ b/src/op/op_util.cc
@@ -23,7 +23,8 @@ MakeLoopNest(const Stage& stage,
              size_t begin_iter_pos,
              bool new_loop_var,
              const std::unordered_set<IterVar>& skip_iter,
-             std::unordered_map<IterVar, Expr>* p_value_map) {
+             std::unordered_map<IterVar, Expr>* p_value_map,
+             bool del_trivial_loop) {
   auto leaf_iter_vars = stage->leaf_iter_vars;
   Stmt no_op = Evaluate::make(0);
   // create the loop nest
@@ -75,7 +76,7 @@ MakeLoopNest(const Stage& stage,
               AttrStmt::make(iv, ir::attr::pragma_scope, p, no_op));
         }
       }
-      if (is_one(dom->extent)) {
+      if (del_trivial_loop && is_one(dom->extent)) {
         nest[i + 1].emplace_back(
             LetStmt::make(var, dom->min, no_op));
         value_map[iv] = dom->min;
@@ -130,7 +131,7 @@ MakeLoopNest(const Stage& stage,
       // annotate the extent of the IterVar
       nest[i + 1].emplace_back(
           AttrStmt::make(bind_iv, ir::attr::thread_extent, dom->extent, no_op));
-      if (is_one(dom->extent)) {
+      if (del_trivial_loop && is_one(dom->extent)) {
         value_map[iv] = dom->min;
       } else {
         value_map[iv] = var;
diff --git a/src/op/op_util.h b/src/op/op_util.h
index 783fbb989422..9b8f7dc629bd 100644
--- a/src/op/op_util.h
+++ b/src/op/op_util.h
@@ -29,6 +29,7 @@ using ir::MergeNest;
  * \param new_loop_var Whether create new loop variable.
  * \param skip_iter Whether skip certain iteration.
  * \param p_value_map The result value of each IterVar.
+ * \param del_trivial_loop Whether eliminate trivial loops with extent of 1
  */
 std::vector<std::vector<Stmt> >
 MakeLoopNest(const Stage& stage,
@@ -36,7 +37,8 @@ MakeLoopNest(const Stage& stage,
              size_t begin_iter_pos,
              bool new_loop_var,
              const std::unordered_set<IterVar>& skip_iter,
-             std::unordered_map<IterVar, Expr>* p_value_map);
+             std::unordered_map<IterVar, Expr>* p_value_map,
+             bool del_trivial_loop);
 
 /*!
  * \brief Create a nest of if checking the predicates.
diff --git a/src/op/placeholder_op.cc b/src/op/placeholder_op.cc
index 4e9d1d094d74..27c1fa9c5001 100644
--- a/src/op/placeholder_op.cc
+++ b/src/op/placeholder_op.cc
@@ -78,7 +78,8 @@ Stmt PlaceholderOpNode::BuildRealize(
 
 Stmt PlaceholderOpNode::BuildProvide(
     const Stage& stage,
-    const std::unordered_map<IterVar, Range>& dom_map) const {
+    const std::unordered_map<IterVar, Range>& dom_map,
+    bool del_trivial_loop) const {
   return Stmt();
 }
 }  // namespace tvm
diff --git a/src/op/scan_op.cc b/src/op/scan_op.cc
index 94e3a4aa6586..5c61eae0f183 100644
--- a/src/op/scan_op.cc
+++ b/src/op/scan_op.cc
@@ -252,7 +252,8 @@ Stmt ScanOpNode::BuildRealize(
 
 Stmt ScanOpNode::BuildProvide(
     const Stage& stage,
-    const std::unordered_map<IterVar, Range>& dom_map) const {
+    const std::unordered_map<IterVar, Range>& dom_map,
+    bool del_trivial_loop) const {
   CHECK_EQ(stage->op.operator->(), this);
   Stmt provide = AttrStmt::make(
       stage->op, attr::scan_update_scope, this->scan_axis->var,
@@ -270,7 +271,7 @@ Stmt ScanOpNode::BuildProvide(
   std::unordered_map<IterVar, Expr> vmap;
   std::unordered_set<IterVar> empty;
   auto nest = op::MakeLoopNest(
-      stage, dom_map, 0, false, empty, &vmap);
+      stage, dom_map, 0, false, empty, &vmap, del_trivial_loop);
   nest[begin_scan].push_back(init);
   nest.push_back(
       op::MakeIfNest(
diff --git a/src/op/tensorize.cc b/src/op/tensorize.cc
index 6fa5459829fc..1f03ec9c0ebb 100644
--- a/src/op/tensorize.cc
+++ b/src/op/tensorize.cc
@@ -369,14 +369,15 @@ Stmt TransformUpdate(const Stage& stage,
 
 Stmt MakeTensorize(const ComputeOpNode* self,
                    const Stage& stage,
-                   const std::unordered_map<IterVar, Range>& dom_map) {
+                   const std::unordered_map<IterVar, Range>& dom_map,
+                   bool del_trivial_loop) {
   std::unordered_map<IterVar, Range> out_dom;
   std::unordered_map<Tensor, Array<Range> > in_region;
   size_t tloc = InferTensorizeRegion(self, stage, dom_map, &out_dom, &in_region);
   TensorIntrin intrin = stage->iter_var_attrs.at(
       stage->leaf_iter_vars[tloc])->tensor_intrin;
   CHECK(intrin.defined());
-  ComputeLoopNest n = ComputeLoopNest::make(self, stage, dom_map);
+  ComputeLoopNest n = ComputeLoopNest::make(self, stage, dom_map, del_trivial_loop);
   VerifyTensorizeLoopNest(self, stage, n, tloc);
   VerifyTensorizeBody(self, stage, out_dom, in_region, intrin);
   // Start bind data.
diff --git a/src/schedule/schedule_ops.cc b/src/schedule/schedule_ops.cc
index 875df556466a..e0dc3321b1fc 100644
--- a/src/schedule/schedule_ops.cc
+++ b/src/schedule/schedule_ops.cc
@@ -22,8 +22,9 @@ using namespace ir;
 
 Stmt MakePipeline(const Stage& s,
                   const std::unordered_map<IterVar, Range>& dom_map,
-                  Stmt consumer) {
-  Stmt producer = s->op->BuildProvide(s, dom_map);
+                  Stmt consumer,
+                  bool del_trivial_loop) {
+  Stmt producer = s->op->BuildProvide(s, dom_map, del_trivial_loop);
   if (producer.defined()) {
     producer = ProducerConsumer::make(s->op, true, producer);
   }
@@ -68,7 +69,7 @@ class InjectAttach : public IRMutator {
         found_attach = true;
         stmt = AttrStmt::make(
             op->node, op->attr_key, op->value,
-            MakePipeline(stage_, dom_map_, op->body));
+            MakePipeline(stage_, dom_map_, op->body, true));
       }
     }
     return stmt;
@@ -107,7 +108,7 @@ class InjectScanStep : public IRMutator {
         found_attach = true;
         stmt = AttrStmt::make(
             op->node, op->attr_key, op->value,
-            MakePipeline(stage_, dom_map_, op->body));
+            MakePipeline(stage_, dom_map_, op->body, true));
       }
     }
     return stmt;
@@ -324,7 +325,7 @@ class SchedulePostProc : public IRMutator {
 };
 
 Stmt ScheduleOps(
-    Schedule sch, Map<IterVar, Range> dom_map_) {
+    Schedule sch, Map<IterVar, Range> dom_map_, bool del_trivial_loop) {
   Stmt body = Stmt();
   std::unordered_map<IterVar, Range> dom_map = as_unordered_map(dom_map_);
   // scan init and scan updates
@@ -374,7 +375,7 @@ Stmt ScheduleOps(
       // do nothing
     } else if (attach_spec->attach_type == kGroupRoot) {
       CHECK(!s->group.defined());
-      body = MakePipeline(s, dom_map, body);
+      body = MakePipeline(s, dom_map, body, del_trivial_loop);
     } else {
       CHECK_EQ(attach_spec->attach_type, kScope);
       CHECK(body.defined());

From b78338290e09f86ca4ab91ec4f580f64b669c90a Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Wed, 7 Feb 2018 09:51:03 +0800
Subject: [PATCH 140/949] [CONTRIB] add peak test (#878)

* add peak test

* fix error for lanes=16

* update doc

* fix names

* fix names
---
 python/tvm/contrib/peak.py      | 325 ++++++++++++++++++++++++++++++++
 python/tvm/exec/measure_peak.py |  32 ++++
 2 files changed, 357 insertions(+)
 create mode 100644 python/tvm/contrib/peak.py
 create mode 100644 python/tvm/exec/measure_peak.py

diff --git a/python/tvm/contrib/peak.py b/python/tvm/contrib/peak.py
new file mode 100644
index 000000000000..36bfaaee7662
--- /dev/null
+++ b/python/tvm/contrib/peak.py
@@ -0,0 +1,325 @@
+# pylint: disable=invalid-name
+"""measure bandwidth and compute peak"""
+
+import logging
+
+import tvm
+from tvm.contrib import rpc, util
+
+def _convert_to_remote(func, remote):
+    """ convert module function to remote rpc function"""
+    temp = util.tempdir()
+    path_dso = temp.relpath("tmp_func.tar")
+    func.export_library(path_dso)
+
+    remote.upload(path_dso)
+    func = remote.load_module("tmp_func.tar")
+    return func
+
+def measure_bandwidth_sum(total_item, item_per_thread, stride,
+                          base_type, bits, lanes,
+                          target, target_host, remote, ctx, n_times):
+    """ measure memory bandwidth of gpu by product reduction for a given type
+
+    The IR for measurement is
+
+    for each thread
+        for i in 1..num_per_thread:
+            y[global_id] = y[global_id] * x[base + i * stride]
+
+    Parameters
+    ----------
+    total_item: int
+        number of elements in input array
+    item_per_thread: int
+        number of elements each thread accumulates
+    stride: int
+        stride in memory access
+    base_type: str
+        can be "int", "float"
+    bits: int
+        can be 16, 32
+    lanes: int
+       lane of the vector type, can be 1, 2, 4, 8, 16
+    target: :any:`tvm.target.Target`
+        the target and option of the compilation.
+    target_host : str or :any:`tvm.target.Target`
+        host compilation target
+    ctx: TVMcontext
+        the context of array
+    remote: tvm.contrib.rpc.RPCSession
+        remote rpc session
+    n_times: int
+        number of runs for taking mean
+
+    Returns
+    -------
+    GBPS: float
+         gigabyte per second
+    """
+    n, m = total_item, item_per_thread
+    n //= lanes
+
+    base_type = str(base_type) + str(bits)
+    dtype = base_type if lanes == 1 else base_type + "x" + str(lanes)
+
+    k = tvm.reduce_axis((0, m), name="k")
+
+    x = tvm.placeholder((n,), dtype=dtype, name="x")
+    op = tvm.comm_reducer(lambda x, y: x*y, lambda t: tvm.const(1, dtype=t), name="sum")
+    y = tvm.compute((n // m,),
+                    lambda i: op(x[i // stride * stride * m + i % stride + k * stride], axis=k))
+    s = tvm.create_schedule(y.op)
+
+    yo, yi = s[y].split(y.op.axis[0], target.max_num_threads)
+    s[y].bind(yo, tvm.thread_axis("blockIdx.x"))
+    s[y].bind(yi, tvm.thread_axis("threadIdx.x"))
+    s[y].unroll(k)
+
+    try:
+        func = tvm.build(s, [x, y], target, target_host=target_host)
+
+        x = tvm.nd.empty((n,), dtype=dtype, ctx=ctx)
+        y = tvm.nd.empty((n // m,), dtype=dtype, ctx=ctx)
+
+        func = _convert_to_remote(func, remote)
+        time_f = func.time_evaluator(func.entry_name, ctx, number=n_times)
+        time = time_f(x, y).mean
+    except tvm._ffi.base.TVMError:
+        # build error (occur when device does not support half)
+        return -1
+
+    return 1.0 * (total_item * bits / 8) / 1e9 / time
+
+def measure_bandwidth_all_types(total_item, item_per_thread, n_times,
+                                target, target_host, remote, ctx, verbose=True):
+    """ measure memory bandwidth for all types
+
+    Parameters
+    ----------
+    total_item: int
+        number of elements in input array
+    item_per_thread: int
+        number of elements each thread accmulates
+    n_times: int
+        number of runs for averaging
+    target: :any:`tvm.target.Target`
+        the target and option of the compilation.
+    target_host : str or :any:`tvm.target.Target`
+        host compilation target
+    remote: tvm.contrib.rpc.RPCSession
+        remote rpc session
+    ctx: TVMcontext
+        the context of array
+    verbose: bool
+        whether outputs immediate result
+
+    Returns
+    -------
+    result: list
+        a list of (type_name, GBPS) pairs
+    """
+    max_threads = target.max_num_threads
+
+    result = []
+    for base_type in ["float"]:
+        for bits in [32]:
+            for lanes in [1, 2, 4, 8, 16]:
+                max_speed = -1e9
+                # try different strides
+                for stride in [max_threads, total_item // (lanes * item_per_thread)]:
+                    speed = measure_bandwidth_sum(total_item, item_per_thread, stride,
+                                                  base_type, bits, lanes, target,
+                                                  target_host, remote, ctx, n_times)
+                    max_speed = max(max_speed, speed)
+                type_name = base_type + str(bits)
+                result.append(["%sx%d" % (type_name, lanes), max_speed])
+                if verbose:
+                    logging.info("\t%-10s %.2f GBPS", result[-1][0], result[-1][1])
+    return result
+
+def measure_compute_mad(total_item, item_per_thread, base_type, bits, lanes,
+                        target, target_host, remote, ctx, n_times):
+    """ measure peak compute speed by computing mad for a type
+
+    The IR for measurement is
+
+    for each thread
+        for i in 1..item_per_thread
+            x = mad(x, x, y)
+            y = mad(y, y, x)
+
+    Parameters
+    ----------
+    total_item: int
+        number of elements in input array
+    item_per_thread: int
+        number of operations each thread does
+    base_type: str
+        can be "int", "float"
+    bits: int
+        can be 16, 32
+    lanes: int
+       lane of the vector type, can be 1, 2, 4, 8, 16
+    target: :any:`tvm.target.Target`
+        the target and option of the compilation.
+    target_host : str or :any:`tvm.target.Target`
+        host compilation target
+    remote: tvm.contrib.rpc.RPCSession
+        if it is not None, use remote rpc session
+    ctx: TVMcontext
+        the context of array
+    n_times: int
+        number of runs for taking mean
+
+    Returns
+    -------
+    GOPS: float
+         giga operation per second
+    """
+
+    n = total_item
+
+    if bits >= 64 or lanes >= 16:
+        n //= 2
+
+    max_threads = target.max_num_threads
+
+    base_type = str(base_type) + str(bits)
+    dtype = base_type if lanes == 1 else base_type + "x" + str(lanes)
+
+    def extern(ins, outs):
+        # pylint: disable=unused-argument
+        """construct measurement function by building IR directly"""
+        ib = tvm.ir_builder.create()
+
+        bx = tvm.thread_axis("blockIdx.x")
+        tx = tvm.thread_axis("threadIdx.x")
+
+        ib.scope_attr(bx, "thread_extent", n // max_threads)
+        ib.scope_attr(tx, "thread_extent", max_threads)
+
+        idx = bx.var * max_threads + tx.var
+
+        a = ib.allocate(dtype, (1), name='a', scope='local')
+        b = ib.allocate(dtype, (1), name='b', scope='local')
+
+        a[0] = outs[0].vload(idx, dtype)
+        b[0] = outs[0].vload(idx, dtype)
+
+        if base_type.find('float') != -1:
+            mad_func = lambda x, y: (x * x + y)
+        else:
+            mad_func = lambda x, y: y * y + x
+
+        for _ in range(item_per_thread // 4 // lanes):
+            a[0] = mad_func(a[0], b[0])
+            b[0] = mad_func(b[0], a[0])
+
+        ib.emit(outs[0].vstore(idx, b[0]))
+        return ib.get()
+
+    y = tvm.extern((n,), [], extern, name="y", dtype=dtype)
+    s = tvm.create_schedule(y.op)
+
+    try:
+        func = tvm.build(s, [y], target, target_host=target_host)
+        func = _convert_to_remote(func, remote)
+        time_f = func.time_evaluator(func.entry_name, ctx, number=n_times)
+        y = tvm.nd.empty((n,), dtype=dtype, ctx=ctx)
+        time = time_f(y).mean
+    except tvm._ffi.base.TVMError:
+        # build error (occur when device does not support half)
+        return -1
+
+    return 1.0 * (n * item_per_thread) / 1e9 / time
+
+def measure_compute_all_types(total_item, item_per_thread, n_times,
+                              target, target_host, remote, ctx, verbose=True):
+    """ measure peak flops for all types
+
+    Parameters
+    ----------
+    total_item: int
+        number of elements in input array
+    item_per_thread: int
+        number of elements each thread accmulates
+    n_times: int
+        number of runs for averaging
+    target: :any:`tvm.target.Target`
+        the target and option of the compilation.
+    target_host : str or :any:`tvm.target.Target`
+        host compilation target
+    remote: tvm.contrib.rpc.RPCSession
+        remote rpc session
+    ctx: TVMcontext
+        the context of array
+    verbose: bool
+        whether outputs immediate result
+
+    Returns
+    -------
+    result: list
+        a list of (type_name, GFLOPS/GIOPS) pairs
+    """
+    result = []
+    for base_type in ["float", "int"]:
+        for bits in [16, 32, 64]:
+            for lanes in [1, 2, 4, 8, 16]:
+                if base_type == 'int' and bits != 32:  # only measure int32
+                    continue
+
+                max_speed = -1e9
+                for per_thread in [item_per_thread//2, item_per_thread, item_per_thread*2]:
+                    speed = measure_compute_mad(total_item, per_thread,
+                                                base_type, bits, lanes, target,
+                                                target_host, remote, ctx, n_times)
+                    max_speed = max(max_speed, speed)
+                type_name = base_type + str(bits)
+                result.append(["%sx%d" % (type_name, lanes), max_speed])
+
+                unit = "GFLOPS" if base_type == "float" else "GIOPS"
+
+                if verbose:
+                    logging.info("\t%-10s %.2f %s", result[-1][0], result[-1][1], unit)
+
+    return result
+
+
+def measure_peak_all(target, target_host, host, port):
+    """measure memory bandwidth and peak compute for gpu devices
+
+    Parameters
+    ----------
+    target: str or :any:`tvm.target.Target`
+    target_host: str
+    host: str
+    port: int
+    """
+
+    target = tvm.target.create(target)
+    remote = rpc.connect(host, port)
+    n_times = 20
+
+    bandwidth_total_item = 1 << 25
+    bandwidth_item_per_thread = 32
+
+    compute_total_item = 1 << 21
+    compute_item_per_thread = 4096
+
+    if str(target).startswith("opencl"):
+        ctx = remote.cl()
+    elif str(target).startswith("cuda"):
+        ctx = remote.gpu()
+    elif str(target).startswith("metal"):
+        ctx = remote.metal()
+    else:
+        raise RuntimeError("Unsupported target")
+
+    logging.info("========== measure memory bandwidth ==========")
+    measure_bandwidth_all_types(bandwidth_total_item, bandwidth_item_per_thread,
+                                n_times, target, target_host, remote, ctx)
+
+    logging.info("========== measure peak compute ==========")
+    measure_compute_all_types(compute_total_item, compute_item_per_thread,
+                              n_times, target, target_host, remote, ctx)
diff --git a/python/tvm/exec/measure_peak.py b/python/tvm/exec/measure_peak.py
new file mode 100644
index 000000000000..b477ac23d3af
--- /dev/null
+++ b/python/tvm/exec/measure_peak.py
@@ -0,0 +1,32 @@
+"""measure bandwidth and compute peak
+
+e.g.
+python3 -m tvm.exec.measure_peak --target cuda --rpc-host 0.0.0.0 --rpc-port 9090
+python3 -m tvm.exec.measure_peak --target opencl --target-host "llvm -target=aarch64-linux-gnu" \
+        --rpc-host $TVM_OPENCL_DEVICE_HOST --rpc-port 9090
+"""
+
+import argparse
+import logging
+
+from ..contrib.peak import measure_peak_all
+
+def main():
+    """Main funciton"""
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--target', type=str, default="llvm",
+                        help='The build target')
+    parser.add_argument('--target-host', type=str, default=None,
+                        help='The host code compilation target')
+    parser.add_argument('--rpc-host', type=str, default="0.0.0.0",
+                        help='the hostname of the server')
+    parser.add_argument('--rpc-port', type=int, default=9090,
+                        help='The port of the PRC')
+
+    args = parser.parse_args()
+    logging.basicConfig(level=logging.INFO)
+
+    measure_peak_all(args.target, args.target_host, args.rpc_host, args.rpc_port)
+
+if __name__ == "__main__":
+    main()

From 9c27998a256e14d99b2b7a2a101a7361899fad69 Mon Sep 17 00:00:00 2001
From: Zhixun Tan <phisiart@gmail.com>
Date: Wed, 7 Feb 2018 15:57:00 -0500
Subject: [PATCH 141/949] [WIP] Add OpenGL topi. (#836)

[TOPI][GL] OpenGL topi.
---
 include/tvm/ir.h                           |   7 ++
 include/tvm/schedule.h                     |   3 +
 python/tvm/target.py                       |  11 ++
 src/codegen/codegen_opengl.cc              |  56 +++++-----
 src/codegen/codegen_opengl.h               |   3 +
 src/codegen/intrin_rule_opengl.cc          |  32 ++++++
 src/pass/storage_flatten.cc                |  18 +++-
 src/runtime/opengl/opengl_device_api.cc    |   2 +-
 src/schedule/schedule_lang.cc              |   3 +
 src/schedule/schedule_ops.cc               |   5 +
 tests/webgl/test_local_topi_conv2d_nchw.py |  82 +++++++++++++++
 tests/webgl/test_local_topi_dense.py       |  59 +++++++++++
 tests/webgl/test_local_topi_pooling.py     | 115 +++++++++++++++++++++
 tests/webgl/test_local_topi_softmax.py     |  79 ++++++++++++++
 topi/python/topi/__init__.py               |   1 +
 topi/python/topi/opengl/__init__.py        |   9 ++
 topi/python/topi/opengl/conv2d_nchw.py     |  50 +++++++++
 topi/python/topi/opengl/dense.py           |  49 +++++++++
 topi/python/topi/opengl/injective.py       |  36 +++++++
 topi/python/topi/opengl/pooling.py         |  94 +++++++++++++++++
 topi/python/topi/opengl/softmax.py         |  29 ++++++
 21 files changed, 714 insertions(+), 29 deletions(-)
 create mode 100644 src/codegen/intrin_rule_opengl.cc
 create mode 100644 tests/webgl/test_local_topi_conv2d_nchw.py
 create mode 100644 tests/webgl/test_local_topi_dense.py
 create mode 100644 tests/webgl/test_local_topi_pooling.py
 create mode 100644 tests/webgl/test_local_topi_softmax.py
 create mode 100644 topi/python/topi/opengl/__init__.py
 create mode 100644 topi/python/topi/opengl/conv2d_nchw.py
 create mode 100644 topi/python/topi/opengl/dense.py
 create mode 100644 topi/python/topi/opengl/injective.py
 create mode 100644 topi/python/topi/opengl/pooling.py
 create mode 100644 topi/python/topi/opengl/softmax.py

diff --git a/include/tvm/ir.h b/include/tvm/ir.h
index 989802326ae4..f36d914e621f 100644
--- a/include/tvm/ir.h
+++ b/include/tvm/ir.h
@@ -226,6 +226,13 @@ constexpr const char* channel_write_advance = "channel_write_advance";
 constexpr const char* pipeline_stage_scope = "pipeline_stage_scope";
 /*! \brief pipeline execution scope, implies the scope can be pipelined. */
 constexpr const char* pipeline_exec_scope = "pipeline_exec_scope";
+/*!
+ * \brief Mark that this stage is an OpenGL shader. Since OpenGL shader only
+ * allows writing out to one element of the output texture, the Provide node
+ * gets translated to a special Call::glsl_texture_store statement instead of a
+ * Store statement.
+ */
+constexpr const char* opengl_stage_scope = "opengl_stage_scope";
 }  // namespace attr
 
 /*! \brief namespace of TVM Intrinsic functions */
diff --git a/include/tvm/schedule.h b/include/tvm/schedule.h
index 003555132789..51e27a9e94bf 100644
--- a/include/tvm/schedule.h
+++ b/include/tvm/schedule.h
@@ -427,6 +427,8 @@ class StageNode : public Node {
   std::string scope;
   /*! \brief Whether this is an output stage */
   bool is_output{false};
+  /*! \brief Whether this is an OpenGL stage */
+  bool is_opengl{false};
   /*! \brief Whether apply double buffer optimization to this stage */
   bool double_buffer{false};
   /*!
@@ -450,6 +452,7 @@ class StageNode : public Node {
     v->Visit("attach_stage", &attach_stage);
     v->Visit("scope", &scope);
     v->Visit("is_output", &is_output);
+    v->Visit("is_opengl", &is_opengl);
     v->Visit("double_buffer", &double_buffer);
     v->Visit("group", &group);
     v->Visit("num_child_stages", &num_child_stages);
diff --git a/python/tvm/target.py b/python/tvm/target.py
index a24eefcb5ba7..5966326c71eb 100644
--- a/python/tvm/target.py
+++ b/python/tvm/target.py
@@ -280,6 +280,17 @@ def mali(options=None):
     return Target("opencl", opts)
 
 
+def opengl(options=None):
+    """Returns a OpenGL target.
+
+    Parameters
+    ----------
+    options : list of str
+        Additional options
+    """
+    return Target("opengl", options)
+
+
 def create(target_str):
     """Get a target given target string.
 
diff --git a/src/codegen/codegen_opengl.cc b/src/codegen/codegen_opengl.cc
index 696082749a37..0e9e274b6bba 100644
--- a/src/codegen/codegen_opengl.cc
+++ b/src/codegen/codegen_opengl.cc
@@ -168,31 +168,9 @@ void CodeGenOpenGL::BindThreadIndex(const IterVar& iv) {
   this->stream << "}\n";
 }
 
-// GLSL texture store is special. We can only store to one output texture, and
-// we must store to the index that matches the current "thread index".
 void CodeGenOpenGL::VisitStmt_(const Store* op) {
-  auto t = op->value.type();
-  auto buffer = op->buffer_var.get();
-  auto index = op->index;
-
-  if (t.lanes() == 1) {
-    // Store to a scalar.
-    CHECK(inputs_.find(buffer) == inputs_.cend())
-      << "Texture has been read from before. Must not store to it.";
-    if (output_ == nullptr) {
-      output_ = buffer;  // Record that this texture is the output.
-    } else {
-      CHECK(output_ == buffer) << "GLSL can only write to 1 texture.";
-    }
-
-    this->PrintIndent();
-    this->stream << GetBufferRef(t, buffer, index) << " = "
-                 << PrintExpr(op->value) << ";\n";
-
-  } else {
-    // Store to a vector.
-    LOG(FATAL) << "Vectorized store not implemented.";
-  }
+  LOG(FATAL) << "Store statement not supported in OpenGL."
+             << " Texture store should be a Call statement.";
 }
 
 // texelFetch(tex, ivec2(idx & kTextureRowMask, idx >> kTextureRowBits), 0).r
@@ -215,8 +193,6 @@ std::string CodeGenOpenGL::GetBufferRef(
 
   if (buffer == this->output_) {
     // This is the output texture.
-    CHECK_EQ(index.get(), output_iter_var_)
-      << "GLSL must access corresponding elem of output texture.";
     return GetVarID(buffer);
   } else {
     // This is an input texture.
@@ -265,5 +241,33 @@ void CodeGenOpenGL::VisitExpr_(const StringImm*, std::ostream& os) {
   LOG(FATAL) << "GLSL 3.0 doesn't support strings.";
 }
 
+void CodeGenOpenGL::VisitStmt_(const Evaluate* op) {
+  auto call = op->value.as<Call>();
+  if (call == nullptr || call->name != Call::glsl_texture_store) {
+    // Fallback to normal logic.
+    CodeGenC::VisitStmt_(op);
+  }
+
+  CHECK_EQ(call->args.size(), 2);
+  auto buffer = call->args[0].as<Variable>();
+  auto value = call->args[1];
+
+  // Doesn't support store to vector.
+  auto type = value.type();
+  CHECK_EQ(type.lanes(), 1)
+    << "Vectorized store not implemented, type = " << type;
+
+  CHECK(inputs_.find(buffer) == inputs_.cend())
+    << "Texture has been read from before. Must not store to it.";
+  if (output_ == nullptr) {
+    output_ = buffer;  // Record that this texture is the output.
+  } else {
+    CHECK(output_ == buffer) << "GLSL can only write to 1 texture.";
+  }
+
+  this->PrintIndent();
+  this->stream << GetVarID(buffer) << " = " << PrintExpr(value) << ";\n";
+}
+
 }  // namespace codegen
 }  // namespace tvm
diff --git a/src/codegen/codegen_opengl.h b/src/codegen/codegen_opengl.h
index 6ff1f7e9ac95..3cae1e323ec4 100644
--- a/src/codegen/codegen_opengl.h
+++ b/src/codegen/codegen_opengl.h
@@ -34,6 +34,9 @@ class CodeGenOpenGL final : public CodeGenC {
   void VisitExpr_(const FloatImm* op, std::ostream& os) final;  // NOLINT(*)
   void VisitExpr_(const StringImm* op, std::ostream& os) final;  // NOLINT(*)
 
+  // Match glsl_texture_store Call.
+  void VisitStmt_(const Evaluate* op) final;  // NOLINT(*)
+
  private:
   const Variable* output_{nullptr};
   std::unordered_set<const Variable*> inputs_;
diff --git a/src/codegen/intrin_rule_opengl.cc b/src/codegen/intrin_rule_opengl.cc
new file mode 100644
index 000000000000..6ae2ee5d2b4e
--- /dev/null
+++ b/src/codegen/intrin_rule_opengl.cc
@@ -0,0 +1,32 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file intrin_rule_opencl.cc
+ * \brief OpenCL intrinsic rules.
+ */
+#include "./intrin_rule.h"
+
+namespace tvm {
+namespace codegen {
+namespace intrin {
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.opengl.exp")
+.set_body(DispatchExtern<Direct>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.opengl.log")
+.set_body(DispatchExtern<Direct>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.opengl.tanh")
+.set_body(DispatchExtern<Direct>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.opengl.sqrt")
+.set_body(DispatchExtern<Direct>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.opengl.pow")
+.set_body(DispatchExtern<Direct>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.opengl.popcount")
+.set_body(DispatchExtern<Direct>);
+
+}  // namespace intrin
+}  // namespace codegen
+}  // namespace tvm
diff --git a/src/pass/storage_flatten.cc b/src/pass/storage_flatten.cc
index bc380d473791..94332ff5cb7e 100644
--- a/src/pass/storage_flatten.cc
+++ b/src/pass/storage_flatten.cc
@@ -29,7 +29,8 @@ using intrinsic::tvm_address_of;
 
 class StorageFlattener : public IRMutator {
  public:
-  explicit StorageFlattener(Map<Tensor, Buffer> extern_buffer, int cache_line_size) {
+  explicit StorageFlattener(Map<Tensor, Buffer> extern_buffer,
+                            int cache_line_size) {
     for (auto kv : extern_buffer) {
       BufferEntry e;
       e.buffer = kv.second;
@@ -38,6 +39,7 @@ class StorageFlattener : public IRMutator {
     }
     cache_line_size_ = cache_line_size;
   }
+
   Stmt Mutate_(const Store* op, const Stmt& s) final {
     Stmt stmt = IRMutator::Mutate_(op, s);
     op = stmt.as<Store>();
@@ -90,6 +92,8 @@ class StorageFlattener : public IRMutator {
       vinfo[dim].align_factor = tuple->args[1].as<IntImm>()->value;
       vinfo[dim].align_offset = tuple->args[2].as<IntImm>()->value;
       return this->Mutate(op->body);
+    } else if (op->attr_key == attr::opengl_stage_scope) {
+      is_opengl_ = true;
     }
     return IRMutator::Mutate_(op, s);
   }
@@ -104,7 +108,15 @@ class StorageFlattener : public IRMutator {
     const BufferEntry& e = it->second;
     CHECK(!e.released)
         << "Read a buffer that is already out of scope";
-    return e.buffer.vstore(e.RelIndex(op->args), op->value);
+    if (is_opengl_) {
+      return Evaluate::make(Call::make(
+          Type(),
+          Call::glsl_texture_store,
+          {e.buffer->data, op->value},
+          Call::Intrinsic));
+    } else {
+      return e.buffer.vstore(e.RelIndex(op->args), op->value);
+    }
   }
 
   Stmt Mutate_(const Realize* op, const Stmt& s) final {
@@ -421,6 +433,8 @@ class StorageFlattener : public IRMutator {
   std::vector<ThreadScope> curr_thread_scope_;
   // The size of cacheline
   int cache_line_size_;
+  // The current stage is an OpenGL shader.
+  bool is_opengl_{false};
 };
 
 Stmt StorageFlatten(Stmt stmt,
diff --git a/src/runtime/opengl/opengl_device_api.cc b/src/runtime/opengl/opengl_device_api.cc
index 5f629fcf1a1f..c47446388836 100644
--- a/src/runtime/opengl/opengl_device_api.cc
+++ b/src/runtime/opengl/opengl_device_api.cc
@@ -281,7 +281,7 @@ GLuint OpenGLWorkspace::CreateShader(GLenum shader_kind,
   if (err != GL_TRUE) {
     std::unique_ptr<char[]> err_msg(new char[info_log_len + 1]);
     gl->GetShaderInfoLog(shader, info_log_len, nullptr, err_msg.get());
-    LOG(FATAL) << err_msg.get();
+    LOG(FATAL) << err_msg.get() << "\n" << shader_src;
     assert(false);
   }
 
diff --git a/src/schedule/schedule_lang.cc b/src/schedule/schedule_lang.cc
index 59bc3f242b03..0bdae67f60a6 100644
--- a/src/schedule/schedule_lang.cc
+++ b/src/schedule/schedule_lang.cc
@@ -433,6 +433,9 @@ Stage& Stage::opengl() {
   // Bind the only dimension to threadIdx.x.
   bind(fused, thread_axis(Range(nullptr), "threadIdx.x"));
 
+  // Mark this stage as OpenGL.
+  (*this)->is_opengl = true;
+
   return *this;
 }
 
diff --git a/src/schedule/schedule_ops.cc b/src/schedule/schedule_ops.cc
index e0dc3321b1fc..1fbffb61fc7f 100644
--- a/src/schedule/schedule_ops.cc
+++ b/src/schedule/schedule_ops.cc
@@ -44,6 +44,11 @@ Stmt MakePipeline(const Stage& s,
       s->op, ir::attr::realize_scope,
       StringImm::make(s->scope),
       pipeline);
+
+  if (s->is_opengl) {
+    pipeline = AttrStmt::make(
+        s->op, ir::attr::opengl_stage_scope, StringImm::make(""), pipeline);
+  }
   return pipeline;
 }
 
diff --git a/tests/webgl/test_local_topi_conv2d_nchw.py b/tests/webgl/test_local_topi_conv2d_nchw.py
new file mode 100644
index 000000000000..106534505694
--- /dev/null
+++ b/tests/webgl/test_local_topi_conv2d_nchw.py
@@ -0,0 +1,82 @@
+"""Example code to do convolution.
+Copied from topi/tests/python/test_topi_conv2d_nchw.py.
+Should be removed once we fix OpenGL testing on Jenkins."""
+import os
+import numpy as np
+import tvm
+import topi
+from tvm.contrib.pickle_memoize import memoize
+from topi.util import get_const_tuple
+
+def verify_conv2d_nchw(batch, in_channel, in_size, num_filter, kernel, stride, padding):
+    in_height = in_width = in_size
+
+    A = tvm.placeholder((batch, in_channel, in_height, in_width), name='A')
+    W = tvm.placeholder((num_filter, in_channel, kernel, kernel), name='W')
+    B = topi.nn.conv2d_nchw(A, W, stride, padding)
+    C = topi.nn.relu(B)
+
+    a_shape = get_const_tuple(A.shape)
+    w_shape = get_const_tuple(W.shape)
+    dtype = A.dtype
+
+    @memoize("topi.tests.test_topi_conv2d.verify_con2d_nchw")
+    def get_ref_data():
+        a_np = np.random.uniform(size=a_shape).astype(dtype)
+        w_np = np.random.uniform(size=w_shape).astype(dtype)
+        b_np = topi.testing.conv2d_nchw_python(a_np, w_np, stride, padding)
+        c_np = np.maximum(b_np, 0)
+        return a_np, w_np, b_np, c_np
+
+    a_np, w_np, b_np, c_np = get_ref_data()
+
+    def check_device(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s1 = topi.generic.schedule_conv2d_nchw([B])
+            s2 = topi.generic.schedule_conv2d_nchw([C])
+        a = tvm.nd.array(a_np, ctx)
+        w = tvm.nd.array(w_np, ctx)
+        b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
+        c = tvm.nd.array(np.zeros(get_const_tuple(C.shape), dtype=C.dtype), ctx)
+        with tvm.build_config(auto_unroll_max_step=1400,
+                              unroll_explicit=(device != "cuda")):
+            func1 = tvm.build(s1, [A, W, B], device, name="conv2d_%d_%d_%d_%d_%d_%d_%d" % (batch, in_channel, in_size, num_filter, kernel, stride, padding))
+            func2 = tvm.build(s2, [A, W, C], device, name="relu_%d_%d_%d_%d_%d_%d_%d" % (batch, in_channel, in_size, num_filter, kernel, stride, padding))
+            func1(a, w, b)
+            func2(a, w, c)
+            np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+            np.testing.assert_allclose(c.asnumpy(), c_np, rtol=1e-5)
+
+    for device in ['opengl']:
+        check_device(device)
+
+
+def test_conv2d_nchw():
+    # ResNet18 worklaods
+    verify_conv2d_nchw(1, 3, 224, 64, 7, 2, 3)
+    verify_conv2d_nchw(1, 64, 56, 64, 3, 1, 1)
+    verify_conv2d_nchw(1, 64, 56, 64, 1, 1, 0)
+    verify_conv2d_nchw(1, 64, 56, 128, 3, 2, 1)
+    verify_conv2d_nchw(1, 64, 56, 128, 1, 2, 0)
+    verify_conv2d_nchw(1, 128, 28, 128, 3, 1, 1)
+    verify_conv2d_nchw(1, 128, 28, 256, 3, 2, 1)
+    verify_conv2d_nchw(1, 128, 28, 256, 1, 2, 0)
+    verify_conv2d_nchw(1, 256, 14, 256, 3, 1, 1)
+    verify_conv2d_nchw(1, 256, 14, 512, 3, 2, 1)
+    verify_conv2d_nchw(1, 256, 14, 512, 1, 2, 0)
+    verify_conv2d_nchw(1, 512, 7, 512, 3, 1, 1)
+    # Vgg16 workloads
+    verify_conv2d_nchw(1, 128, 122, 128, 3, 1, 1)
+    # Super resolution workloads
+    verify_conv2d_nchw(1, 1, 224, 64, 5, 1, 2)
+    verify_conv2d_nchw(1, 64, 224, 64, 3, 1, 1)
+    verify_conv2d_nchw(1, 64, 224, 32, 3, 1, 1)
+    verify_conv2d_nchw(1, 32, 224, 9, 3, 1, 1)
+
+if __name__ == "__main__":
+    test_conv2d_nchw()
diff --git a/tests/webgl/test_local_topi_dense.py b/tests/webgl/test_local_topi_dense.py
new file mode 100644
index 000000000000..f2e7dfc1331c
--- /dev/null
+++ b/tests/webgl/test_local_topi_dense.py
@@ -0,0 +1,59 @@
+"""Test code for dense operator
+Copied from topi/tests/python/test_topi_dense.py.
+Should be removed once we fix OpenGL testing on Jenkins.
+"""
+import numpy as np
+import tvm
+import topi
+from topi.util import get_const_tuple
+from tvm.contrib.pickle_memoize import memoize
+
+
+def verify_dense(batch, in_dim, out_dim, use_bias=True):
+    A = tvm.placeholder((batch, in_dim), name='A')
+    B = tvm.placeholder((out_dim, in_dim), name='B')
+    C = tvm.placeholder((out_dim,), name='C')
+    D = topi.nn.dense(A, B, C if use_bias else None)
+    D = topi.nn.relu(D)
+    dtype = A.dtype
+
+    # use memoize to pickle the test data for next time use
+    @memoize("topi.tests.test_topi_dense")
+    def get_ref_data():
+        a_np = np.random.uniform(size=(batch, in_dim)).astype(dtype)
+        b_np = np.random.uniform(size=(out_dim, in_dim)).astype(dtype)
+        c_np = np.random.uniform(size=(out_dim,)).astype(dtype)
+        if use_bias:
+            d_np = np.maximum(np.dot(a_np, b_np.T) + c_np, 0.0)
+        else:
+            d_np = np.maximum(np.dot(a_np, b_np.T), 0.0)
+        return (a_np, b_np, c_np, d_np)
+    # get the test data
+    a_np, b_np, c_np, d_np = get_ref_data()
+
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s = topi.generic.schedule_dense(D)
+        ctx = tvm.context(device, 0)
+        a = tvm.nd.array(a_np, ctx)
+        b = tvm.nd.array(b_np, ctx)
+        c = tvm.nd.array(c_np, ctx)
+        d = tvm.nd.array(np.zeros(get_const_tuple(D.shape), dtype=dtype), ctx)
+        f = tvm.build(s, [A, B, C, D], device, name="dense")
+        f(a, b, c, d)
+        np.testing.assert_allclose(d.asnumpy(), d_np, rtol=1e-5)
+
+    for device in ['opengl']:
+        check_device(device)
+
+def test_dense():
+    verify_dense(1, 1024, 1000, use_bias=True)
+    verify_dense(1, 1024, 1000, use_bias=False)
+
+
+if __name__ == "__main__":
+    test_dense()
diff --git a/tests/webgl/test_local_topi_pooling.py b/tests/webgl/test_local_topi_pooling.py
new file mode 100644
index 000000000000..813fcd227e2f
--- /dev/null
+++ b/tests/webgl/test_local_topi_pooling.py
@@ -0,0 +1,115 @@
+"""Test code for pooling
+Copied from topi/tests/python/test_topi_pooling.py.
+Should be removed once we fix OpenGL testing on Jenkins.
+"""
+import numpy as np
+import tvm
+import topi
+import math
+from topi.util import get_const_tuple
+
+def verify_pool(n, ic, ih, kh, sh, padding, pool_type, ceil_mode):
+    iw = ih
+    kw = kh
+    sw = sh
+    ph, pw = padding
+    A = tvm.placeholder((n, ic, ih, iw), name='A')
+    B = topi.nn.pool(A, kernel=[kh, kw], stride=[sh, sw], padding=padding,
+                     pool_type=pool_type, ceil_mode=ceil_mode)
+    B = topi.nn.relu(B)
+    dtype = A.dtype
+
+    bshape = get_const_tuple(B.shape)
+    ashape = get_const_tuple(A.shape)
+    if ceil_mode:
+        assert bshape[2] == int(math.ceil(float(ashape[2] - kh + ph * 2) / sh) + 1)
+        assert bshape[3] == int(math.ceil(float(ashape[3] - kw + pw * 2) / sw) + 1)
+    else:
+        assert bshape[2] == int(math.floor(float(ashape[2] - kh + ph * 2) / sh) + 1)
+        assert bshape[3] == int(math.floor(float(ashape[3] - kw + pw * 2) / sw) + 1)
+
+
+    a_np = np.random.uniform(size=(n, ic, ih, iw)).astype(dtype)
+    pad_np = np.zeros(shape=(n, ic, ih+2*ph, iw+2*pw)).astype(dtype)
+    no_zero = (range(n), range(ic), (range(ph, ih+ph)), (range(pw, iw+pw)))
+    pad_np[np.ix_(*no_zero)] = a_np
+    _, oc, oh, ow = get_const_tuple(B.shape)
+    b_np = np.zeros(shape=(n, oc, oh, ow)).astype(dtype)
+
+    if pool_type == 'avg':
+        for i in range(oh):
+            for j in range(ow):
+                b_np[:,:,i,j] = np.mean(pad_np[:, :, i*sh:i*sh+kh, j*sw:j*sw+kw], axis=(2,3))
+    elif pool_type =='max':
+        for i in range(oh):
+            for j in range(ow):
+                b_np[:,:,i,j] = np.max(pad_np[:, :, i*sh:i*sh+kh, j*sw:j*sw+kw], axis=(2,3))
+    b_np = np.maximum(b_np, 0.0)
+
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s = topi.generic.schedule_pool(B)
+        ctx = tvm.context(device, 0)
+        a = tvm.nd.array(a_np, ctx)
+        b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=dtype), ctx)
+        print(tvm.lower(s, [A, B], simple_mode=True))
+
+        f = tvm.build(s, [A, B], device)
+        f(a, b)
+        np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+    for device in ['opengl']:
+        check_device(device)
+
+def test_pool():
+    verify_pool(1, 256, 32, 2, 2, [0, 0], 'avg', False)
+    verify_pool(1, 256, 31, 3, 3, [1, 2], 'avg', False)
+    verify_pool(1, 256, 32, 2, 2, [0, 0], 'max', False)
+    verify_pool(1, 256, 31, 3, 3, [2, 1], 'max', False)
+    verify_pool(1, 256, 31, 3, 3, [2, 1], 'max', True)
+
+
+
+def verify_global_pool(n, c, h, w, pool_type):
+    A = tvm.placeholder((n, c, h, w), name='A')
+    B = topi.nn.global_pool(A, pool_type=pool_type)
+    B = topi.nn.relu(B)
+
+    a_np = np.random.uniform(size=get_const_tuple(A.shape)).astype(A.dtype)
+    if pool_type == 'avg':
+        b_np = np.mean(a_np, axis=(2,3), keepdims=True)
+    elif pool_type =='max':
+        b_np = np.max(a_np, axis=(2,3), keepdims=True)
+    b_np = np.maximum(b_np, 0.0)
+
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s = topi.generic.schedule_global_pool(B)
+        ctx = tvm.context(device, 0)
+        a = tvm.nd.array(a_np, ctx)
+        b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
+        f = tvm.build(s, [A, B], device)
+        f(a, b)
+        np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+    for device in ['opengl']:
+        check_device(device)
+
+def test_global_pool():
+    verify_global_pool(1, 1024, 7, 7, 'avg')
+    verify_global_pool(4, 1024, 7, 7, 'avg')
+    verify_global_pool(1, 1024, 7, 7, 'max')
+    verify_global_pool(4, 1024, 7, 7, 'max')
+
+
+if __name__ == "__main__":
+    test_pool()
+    test_global_pool()
diff --git a/tests/webgl/test_local_topi_softmax.py b/tests/webgl/test_local_topi_softmax.py
new file mode 100644
index 000000000000..34f8bfb8d8f5
--- /dev/null
+++ b/tests/webgl/test_local_topi_softmax.py
@@ -0,0 +1,79 @@
+"""Test code for softmax
+Copied from topi/tests/python/test_topi_softmax.py.
+Should be removed once we fix OpenGL testing on Jenkins.
+"""
+
+import os
+import numpy as np
+import tvm
+import topi
+import logging
+from topi.util import get_const_tuple
+
+def verify_softmax(m, n):
+    A = tvm.placeholder((m, n), name='A')
+    B = topi.nn.softmax(A)
+    # confirm lower works
+    s = tvm.create_schedule([B.op])
+    tvm.lower(s, [A, B], simple_mode=True)
+
+    a_np = np.random.uniform(size=get_const_tuple(A.shape)).astype(A.dtype)
+    b_np = topi.testing.softmax_python(a_np)
+
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s = topi.generic.schedule_softmax(B)
+        ctx = tvm.context(device, 0)
+        a = tvm.nd.array(a_np, ctx)
+        b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
+        foo = tvm.build(s, [A, B], device, name="softmax")
+        foo(a, b)
+        np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+    for device in ["opengl"]:
+        check_device(device)
+
+def test_softmax():
+    verify_softmax(32, 10)
+    verify_softmax(3, 4)
+
+
+def verify_log_softmax(m, n):
+    A = tvm.placeholder((m, n), name='A')
+    B = topi.nn.log_softmax(A)
+    # confirm lower works
+    s = tvm.create_schedule([B.op])
+    tvm.lower(s, [A, B], simple_mode=True)
+    a_np = np.random.uniform(size=get_const_tuple(A.shape)).astype(A.dtype)
+    b_np = topi.testing.log_softmax_python(a_np)
+
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s = topi.generic.schedule_softmax(B)
+        ctx = tvm.context(device, 0)
+        a = tvm.nd.array(a_np, ctx)
+        b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
+        foo = tvm.build(s, [A, B], device, name="log_softmax")
+        foo(a, b)
+        np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+    for device in ["opengl"]:
+        check_device(device)
+
+
+def test_log_softmax():
+    verify_log_softmax(32, 10)
+    verify_log_softmax(3, 4)
+
+if __name__ == "__main__":
+    logging.basicConfig(level=logging.DEBUG)
+    test_softmax()
+    test_log_softmax()
diff --git a/topi/python/topi/__init__.py b/topi/python/topi/__init__.py
index d7875cdbe521..f7a922c5c379 100644
--- a/topi/python/topi/__init__.py
+++ b/topi/python/topi/__init__.py
@@ -21,6 +21,7 @@
 from . import cuda
 from . import rasp
 from . import mali
+from . import opengl
 from . import testing
 from . import util
 from . import rocm
diff --git a/topi/python/topi/opengl/__init__.py b/topi/python/topi/opengl/__init__.py
new file mode 100644
index 000000000000..c8f20b9825a7
--- /dev/null
+++ b/topi/python/topi/opengl/__init__.py
@@ -0,0 +1,9 @@
+# pylint: disable=redefined-builtin, wildcard-import
+"""CUDA specific declaration and schedules."""
+from __future__ import absolute_import as _abs
+
+from .conv2d_nchw import schedule_conv2d_nchw
+from .injective import schedule_injective, schedule_elemwise, schedule_broadcast
+from .softmax import schedule_softmax
+from .dense import schedule_dense
+from .pooling import schedule_pool, schedule_global_pool
diff --git a/topi/python/topi/opengl/conv2d_nchw.py b/topi/python/topi/opengl/conv2d_nchw.py
new file mode 100644
index 000000000000..c633d8a21e6e
--- /dev/null
+++ b/topi/python/topi/opengl/conv2d_nchw.py
@@ -0,0 +1,50 @@
+#pylint: disable=invalid-name, no-member, too-many-locals, too-many-statements, too-many-arguments, too-many-branches, line-too-long
+"""Schedule for conv2d_nchw with auto fusion"""
+import tvm
+from .. import tag
+from .. import generic
+
+@generic.schedule_conv2d_nchw.register(["opengl"])
+def schedule_conv2d_nchw(outs):
+    """Schedule for conv2d_nchw.
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+        The computation graph description of conv2d_nchw
+        in the format of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+        The computation schedule for conv2d_nchw.
+    """
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    s = tvm.create_schedule([x.op for x in outs])
+    def _schedule(conv2d, data):
+        if conv2d.op in s.outputs:
+            Out = conv2d
+        else:
+            Out = outs[0].op.output(0)
+            s[conv2d].opengl()
+        s[Out].opengl()
+        s[data].opengl()
+
+    def traverse(OP):
+        # inline all one-to-one-mapping operators except the last stage (output)
+        if tag.is_broadcast(OP.tag):
+            if OP not in s.outputs:
+                s[OP].opengl()
+            for tensor in OP.input_tensors:
+                if tensor.op.input_tensors:
+                    traverse(tensor.op)
+        # schedule conv2d_nchw
+        elif OP.tag.startswith('conv2d_nchw'):
+            conv2d = OP.output(0)
+            data = OP.input_tensors[0]
+            _schedule(conv2d, data)
+        else:
+            raise RuntimeError("Unsupported operator: %s" % OP.tag)
+
+    traverse(outs[0].op)
+    return s
diff --git a/topi/python/topi/opengl/dense.py b/topi/python/topi/opengl/dense.py
new file mode 100644
index 000000000000..e7cf008ae240
--- /dev/null
+++ b/topi/python/topi/opengl/dense.py
@@ -0,0 +1,49 @@
+# pylint: disable=invalid-name, unused-variable
+"""Schedule for dense operator"""
+from __future__ import absolute_import as _abs
+import tvm
+from .. import tag
+from .. import generic
+
+@generic.schedule_dense.register(["opengl"])
+def schedule_dense(outs):
+    """Schedule for dense operator.
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+        The computation graph description of dense
+        in the format of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+        The computation schedule for dense.
+    """
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    s = tvm.create_schedule([x.op for x in outs])
+    def _schedule(Dense):
+        if Dense.op in s.outputs:
+            Out = Dense
+        else:
+            Out = outs[0].op.output(0)
+            s[Dense].opengl()
+        s[Out].opengl()
+
+    def traverse(OP):
+        # inline all one-to-one-mapping operators except the last stage (output)
+        if tag.is_broadcast(OP.tag):
+            if OP not in s.outputs:
+                s[OP].compute_inline()
+            for tensor in OP.input_tensors:
+                if tensor.op.input_tensors:
+                    traverse(tensor.op)
+        # schedule dense
+        elif OP.tag == 'dense':
+            Dense = OP.output(0)
+            _schedule(Dense)
+        else:
+            raise RuntimeError("Unsupported operator: %s" % OP.tag)
+
+    traverse(outs[0].op)
+    return s
diff --git a/topi/python/topi/opengl/injective.py b/topi/python/topi/opengl/injective.py
new file mode 100644
index 000000000000..9665e330864f
--- /dev/null
+++ b/topi/python/topi/opengl/injective.py
@@ -0,0 +1,36 @@
+# pylint: disable=invalid-name, unused-variable,
+"""Schedule for composition of injective operator"""
+import tvm
+from .. import generic
+
+def _schedule_injective(op, sch):
+    x = op.output(0)
+    sch[x].opengl()
+    return sch
+
+
+@generic.schedule_injective.register(["opengl"])
+def schedule_injective(outs):
+    """Schedule for injective op.
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+          The computation graph description of reduce in the format
+          of an array of tensors.
+
+    Returns
+    -------
+    sch: Schedule
+        The computation schedule for the op.
+    """
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    s = tvm.create_schedule([x.op for x in outs])
+
+    tvm.schedule.AutoInlineInjective(s)
+    for out in outs:
+        _schedule_injective(out.op, s)
+    return s
+
+schedule_elemwise = schedule_injective
+schedule_broadcast = schedule_injective
diff --git a/topi/python/topi/opengl/pooling.py b/topi/python/topi/opengl/pooling.py
new file mode 100644
index 000000000000..5c26c56bb1ac
--- /dev/null
+++ b/topi/python/topi/opengl/pooling.py
@@ -0,0 +1,94 @@
+# pylint: disable=invalid-name, unused-variable
+"""Schedule for pooling operators"""
+import tvm
+from .. import tag
+from .. import generic
+
+@generic.schedule_global_pool.register(["opengl"])
+def schedule_global_pool(outs):
+    """Schedule for global_pool.
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+        The computation graph description of global_pool
+        in the format of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+        The computation schedule for global_pool.
+    """
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    s = tvm.create_schedule([x.op for x in outs])
+    def _schedule(Pool):
+        if Pool.op in s.outputs:
+            Out = Pool
+        else:
+            Out = outs[0].op.output(0)
+            s[Pool].opengl()
+        s[Out].opengl()
+
+    def traverse(OP):
+        # inline all one-to-one-mapping operators except the last stage (output)
+        if tag.is_broadcast(OP.tag):
+            if OP not in s.outputs:
+                s[OP].opengl()
+            for tensor in OP.input_tensors:
+                if tensor.op.input_tensors:
+                    traverse(tensor.op)
+        # schedule global_pool
+        elif OP.tag.startswith('global_pool'):
+            Pool = OP.output(0)
+            _schedule(Pool)
+        else:
+            raise RuntimeError("Unsupported operator: %s" % OP.tag)
+
+    traverse(outs[0].op)
+    return s
+
+
+@generic.schedule_pool.register(["opengl"])
+def schedule_pool(outs):
+    """Schedule for pool.
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+        The computation graph description of pool
+        in the format of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+        The computation schedule for pool.
+    """
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    s = tvm.create_schedule([x.op for x in outs])
+    def _schedule(PaddedInput, Pool):
+        s[PaddedInput].opengl()
+        if Pool.op in s.outputs:
+            Out = Pool
+        else:
+            Out = outs[0].op.output(0)
+            s[Pool].opengl()
+        s[Out].opengl()
+
+    def traverse(OP):
+        # inline all one-to-one-mapping operators except the last stage (output)
+        if tag.is_broadcast(OP.tag):
+            if OP not in s.outputs:
+                s[OP].compute_inline()
+            for tensor in OP.input_tensors:
+                if tensor.op.input_tensors:
+                    traverse(tensor.op)
+        # schedule pool
+        elif OP.tag.startswith('pool'):
+            PaddedInput = OP.input_tensors[0]
+            Pool = OP.output(0)
+            _schedule(PaddedInput, Pool)
+        else:
+            raise RuntimeError("Unsupported operator: %s" % OP.tag)
+
+    traverse(outs[0].op)
+    return s
diff --git a/topi/python/topi/opengl/softmax.py b/topi/python/topi/opengl/softmax.py
new file mode 100644
index 000000000000..a5bf4371eb13
--- /dev/null
+++ b/topi/python/topi/opengl/softmax.py
@@ -0,0 +1,29 @@
+# pylint: disable=invalid-name, unused-variable, trailing-whitespace
+"""Schedule for softmax operator"""
+import tvm
+from .. import generic
+
+@generic.schedule_softmax.register(["opengl"])
+def schedule_softmax(outs):
+    """Schedule for softmax op.
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+          The computation graph description of reduce in the format
+          of an array of tensors.
+
+    Returns
+    -------
+    sch: Schedule
+        The computation schedule for the op.
+    """
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    s = tvm.create_schedule([x.op for x in outs])
+    softmax = outs[0]
+    max_elem = softmax.op.input_tensors[1]
+    expsum = softmax.op.input_tensors[2]
+    s[max_elem].opengl()
+    s[expsum].opengl()
+    s[softmax].opengl()
+    return s

From 732e67abf2b744082fe0a11a3dc4ea659127fd84 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 7 Feb 2018 13:40:30 -0800
Subject: [PATCH 142/949] [PASS] Enable StorageRewrite before virtual thread
 lowering (#880)

* [PASS] Enable StorageRewrite before virtual thread lowering

* update

* fix testcase
---
 src/pass/storage_rewrite.cc               | 20 ++++++++++++--------
 topi/tests/python_cpp/test_topi_reduce.py | 17 ++++++++++++++++-
 2 files changed, 28 insertions(+), 9 deletions(-)

diff --git a/src/pass/storage_rewrite.cc b/src/pass/storage_rewrite.cc
index 5a0ea067d433..fe4295feb982 100644
--- a/src/pass/storage_rewrite.cc
+++ b/src/pass/storage_rewrite.cc
@@ -154,6 +154,8 @@ class LinearAccessPatternFinder final : public IRVisitor {
       in_thread_env_ = false;
     } else if (op->attr_key == attr::extern_scope) {
       VisitNewScope(op);
+    } else if (op->attr_key == attr::virtual_thread) {
+      VisitNewScope(op);
     } else if (op->attr_key == attr::storage_scope) {
       const Variable* buf = op->node.as<Variable>();
       alloc_info_[buf].storage_scope =
@@ -395,11 +397,10 @@ class StoragePlanRewriter : public IRMutator {
   }
 
   Stmt Mutate_(const AttrStmt* op, const Stmt& s) final {
-    CHECK(op->attr_key != attr::virtual_thread)
-        << "InjectVirtualThread before StoragePlan";
     if (op->attr_key == attr::storage_scope) {
       return this->Mutate(op->body);
     } else if (op->attr_key == attr::thread_extent ||
+               op->attr_key == attr::virtual_thread ||
                op->attr_key == attr::pragma_scope) {
       // remake all the allocation at the attach scope.
       if (attach_map_.count(op)) {
@@ -481,11 +482,13 @@ class StoragePlanRewriter : public IRMutator {
                   Stmt body) {
     std::vector<Stmt> nest;
     for (StorageEntry* e : svec) {
-      nest.emplace_back(AttrStmt::make(
-          e->alloc_var, attr::storage_scope,
-          StringImm::make(e->scope.to_string()),
-          Evaluate::make(0)));
-      nest.push_back(e->new_alloc);
+      if (e->new_alloc.defined()) {
+        nest.emplace_back(AttrStmt::make(
+            e->alloc_var, attr::storage_scope,
+            StringImm::make(e->scope.to_string()),
+            Evaluate::make(0)));
+        nest.push_back(e->new_alloc);
+      }
     }
     return MergeNest(nest, body);
   }
@@ -716,7 +719,8 @@ class StoragePlanRewriter : public IRMutator {
       if (s.stmt->is_type<AttrStmt>()) {
         const auto* op = static_cast<const AttrStmt*>(s.stmt);
         if (op->attr_key == attr::thread_extent ||
-            op->attr_key == attr::pragma_scope) {
+            op->attr_key == attr::pragma_scope ||
+            op->attr_key == attr::virtual_thread) {
           PlanNewScope(op);
         } else {
           CHECK(op->attr_key == attr::extern_scope);
diff --git a/topi/tests/python_cpp/test_topi_reduce.py b/topi/tests/python_cpp/test_topi_reduce.py
index adfe18ba4ef9..b4c630395f60 100644
--- a/topi/tests/python_cpp/test_topi_reduce.py
+++ b/topi/tests/python_cpp/test_topi_reduce.py
@@ -77,7 +77,22 @@ def check_device(device):
         out_tvm = tvm.nd.empty(shape=out_npy.shape, ctx=ctx, dtype=out_dtype)
         for _ in range(1):
             foo(data_tvm, out_tvm)
-        np.testing.assert_allclose(out_tvm.asnumpy(), out_npy, 1E-3, 1E-3)
+        if type == "argmax" or type == "argmin":
+            out_tvm_indices = out_tvm.asnumpy()
+            if keepdims:
+                out_tvm_indices = np.take(out_tvm_indices, indices=0, axis=axis)
+            if axis is None:
+                out_tvm_val = in_npy_map.ravel()[out_tvm_indices]
+            else:
+                other_indices = tuple(np.indices(in_shape[0:axis] + in_shape[(axis+1):]))
+                sel_indices = other_indices[0:axis] + (out_tvm_indices,) + other_indices[axis:]
+                out_tvm_val = in_npy_map[sel_indices]
+            if type == "argmax":
+                np.testing.assert_allclose(out_tvm_val, in_npy_map.max(axis=axis), 1E-3, 1E-3)
+            elif type == "argmin":
+                np.testing.assert_allclose(out_tvm_val, in_npy_map.min(axis=axis), 1E-3, 1E-3)
+        else:
+            np.testing.assert_allclose(out_tvm.asnumpy(), out_npy, 1E-3, 1E-3)
     for device in ["cuda", "opencl", "metal", "llvm", "rocm"]:
         check_device(device)
 

From 90dd507e82f38ca5c5a63e6bdc2d101cf3a5519f Mon Sep 17 00:00:00 2001
From: Yizhi Liu <liuyizhi@apache.org>
Date: Thu, 8 Feb 2018 12:24:24 -0800
Subject: [PATCH 143/949] [TOPI] conv2d avx (#883)

* conv2d schedules for Intel CPU (AVX2 & AVX512)

* fix lint

* remove override register
---
 CODEOWNERS                                 |   2 +-
 CONTRIBUTORS.md                            |   2 +-
 topi/python/topi/x86/conv2d.py             | 146 ++++++++++++++++----
 topi/python/topi/x86/conv2d_avx_1x1.py     | 135 ++++++++++++++++++
 topi/python/topi/x86/conv2d_avx_common.py  | 151 +++++++++++++++++++++
 topi/tests/python/test_topi_conv2d_nchw.py |   4 +-
 6 files changed, 413 insertions(+), 27 deletions(-)
 create mode 100644 topi/python/topi/x86/conv2d_avx_1x1.py
 create mode 100644 topi/python/topi/x86/conv2d_avx_common.py

diff --git a/CODEOWNERS b/CODEOWNERS
index 0e22cff91e0b..4af7a7e70c67 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -11,7 +11,7 @@ src/llvm/*          @aatluri
 src/runtime/rocm/*    @aatluri
 
 # JVM language
-jvm/*   @javelinjs
+jvm/*   @yzhliu
 
 # TOPI
 topi/python/topi/*  @Laurawly @Huyuwei
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index a62c11e5fa4d..13b5ee1c5dd3 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -26,7 +26,7 @@ and are qualified to lead development and review changes of the owned module.
 - [Aditya Atluri](https://github.com/adityaatluri) ROCM
 - [Leyuan Wang](https://github.com/Laurawly) TOPI
 - [Yuwei Hu](https://github.com/Huyuwei) TOPI
-- [Yizhi Liu](https://github.com/javelinjs) JVM package
+- [Yizhi Liu](https://github.com/yzhliu) JVM package
 
 List of Contributors
 --------------------
diff --git a/topi/python/topi/x86/conv2d.py b/topi/python/topi/x86/conv2d.py
index cb3571d6a91b..7457deab4c17 100644
--- a/topi/python/topi/x86/conv2d.py
+++ b/topi/python/topi/x86/conv2d.py
@@ -1,13 +1,90 @@
 # pylint: disable=invalid-name,unused-variable,invalid-name
 """Conv2D schedule on x86"""
 import tvm
-from .. import generic
-from .. import tag
+from .. import generic, tag
+from .. import nn
+from ..nn.util import infer_pad, infer_stride
+from ..nn.conv2d import conv2d, _get_workload, _get_schedule, _WORKLOADS
+
+from . import conv2d_avx_1x1, conv2d_avx_common
+from .conv2d_avx_common import AVXConvCommonFwd
+from .conv2d_avx_1x1 import AVXConv1x1Fwd
+
+_AVX_SCH_TO_DECL_FUNC = {
+    AVXConvCommonFwd: conv2d_avx_common._declaration_conv,
+    AVXConv1x1Fwd: conv2d_avx_1x1._declaration_conv
+}
+
+_AVX_SCH_TO_SCH_FUNC = {
+    AVXConvCommonFwd: conv2d_avx_common._schedule_conv,
+    AVXConv1x1Fwd: conv2d_avx_1x1._schedule_conv
+}
+
+@_get_schedule.register("cpu")
+def _get_schedule_conv(wkl):
+    if wkl not in _WORKLOADS:
+        raise ValueError("no schedule for such workload: {}".format(wkl))
+    idx = _WORKLOADS.index(wkl)
+
+    fp32_vec_len = 8
+    target = tvm.target.current_target(allow_none=False)
+    for opt in target.options:
+        if opt == '-mcpu=skylake-avx512':
+            fp32_vec_len = 16
+
+    _SCHEDULES_AVX_NCHW = [
+        # float32 resnet-18
+        AVXConvCommonFwd(3, fp32_vec_len, 28, False),
+        AVXConvCommonFwd(16, fp32_vec_len, 28, False),
+        AVXConv1x1Fwd(16, fp32_vec_len, 1, 28),
+        AVXConvCommonFwd(16, fp32_vec_len, 28, False),
+        AVXConv1x1Fwd(16, fp32_vec_len, 1, 28),
+        AVXConvCommonFwd(16, fp32_vec_len, 28, False),
+        AVXConvCommonFwd(16, fp32_vec_len, 14, False),
+        AVXConv1x1Fwd(16, fp32_vec_len, 2, 14),
+        AVXConvCommonFwd(16, fp32_vec_len, 14, True),
+        AVXConvCommonFwd(16, 32, 7, True),
+        AVXConv1x1Fwd(16, fp32_vec_len, 1, 7),
+        AVXConvCommonFwd(16, fp32_vec_len, 7, True),
+        # float32 mobilenet
+        AVXConvCommonFwd(3, fp32_vec_len, 28, False),
+        AVXConv1x1Fwd(16, fp32_vec_len, 1, 28),
+        AVXConv1x1Fwd(16, fp32_vec_len, 1, 28),
+        AVXConv1x1Fwd(16, fp32_vec_len, 1, 28),
+        AVXConv1x1Fwd(16, fp32_vec_len, 1, 28),
+        AVXConv1x1Fwd(16, fp32_vec_len, 1, 28),
+        AVXConv1x1Fwd(16, fp32_vec_len, 2, 14),
+        AVXConv1x1Fwd(16, fp32_vec_len, 2, 14),
+        AVXConv1x1Fwd(16, fp32_vec_len, 1, 7),
+        AVXConv1x1Fwd(16, fp32_vec_len, 1, 7),
+    ]
+
+    sch = _SCHEDULES_AVX_NCHW[idx]
+    return sch
+
+
+@conv2d.register("cpu")
+def _declaration_conv(data, kernel, stride, padding, layout, out_dtype):
+    target = tvm.target.current_target(allow_none=False)
+    if 'avx' in str(target) and layout == 'NCHW':
+        wkl = _get_workload(data, kernel, stride, padding, out_dtype)
+        sch = _get_schedule(wkl)
+        return _AVX_SCH_TO_DECL_FUNC[type(sch)](data, kernel, stride, padding, layout, out_dtype)
+    elif layout == 'NCHW':
+        return nn.conv2d_nchw(data, kernel, stride, padding, out_dtype)
+    elif layout == 'HWCN':
+        return nn.conv2d_hwcn(data, kernel, stride, padding, out_dtype)
+    elif layout == 'NHWC':
+        return nn.conv2d_nhwc(data, kernel, stride, padding, out_dtype)
+    else:
+        raise ValueError("not support this layout {} yet".format(layout))
+
 
 @generic.schedule_conv2d_nchw.register(["cpu"])
 def schedule_conv2d(outs):
     """Create schedule for tensors"""
     s = tvm.create_schedule([x.op for x in outs])
+    target = tvm.target.current_target(allow_none=False)
 
     def traverse(op):
         """Traverse operators from computation graph"""
@@ -16,7 +93,7 @@ def traverse(op):
             if op not in s.outputs:
                 s[op].compute_inline()
             else: # inject custom schedule
-                if len(op.axis) == 4: # schedule bias + bn + relu
+                if len(op.axis) == 4 and 'avx' not in str(target): # schedule bias + bn + relu
                     n, c, h, w = op.axis
                     fused = s[op].fuse(n, c)
                     s[op].parallel(fused)
@@ -26,27 +103,50 @@ def traverse(op):
                     traverse(tensor.op)
 
         if 'conv2d_nchw' in op.tag:
-            conv = op.output(0)
-            kernel = op.input_tensors[1]
-            data = op.input_tensors[0]
-            data_pad = None
-            if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
-                data_pad = data
-                data = data_pad.op.input_tensors[0]
+            if 'avx' in str(target):
+                output = op.output(0)
+                conv_out = op.input_tensors[0]
+                kernel_vec = conv_out.op.input_tensors[1]
+                kernel = kernel_vec.op.input_tensors[0]
+                data_vec = conv_out.op.input_tensors[0]
+                data = data_vec.op.input_tensors[0]
+                data_pad = None
+                if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
+                    data_pad = data
+                    data = data_pad.op.input_tensors[0]
 
-            n_pad, c_pad, h_pad, w_pad = data_pad.op.axis
-            pad_fused = s[data_pad].fuse(n_pad, c_pad)
-            s[data_pad].parallel(pad_fused)
-            C = conv
-            n, c, h, w = C.op.axis
-            rc, ry, rx = C.op.reduce_axis
-            fused = s[C].fuse(n, c)
-            s[C].parallel(fused)
-            wo, wi = s[C].split(w, factor=16)
-            s[C].reorder(fused, rc, h, wo, ry, rx, wi) # move rc to outer loop
-            s[C].unroll(rx)
-            s[C].unroll(ry)
-            s[C].vectorize(wi)
+                padding = infer_pad(data, data_pad)
+                if data_pad is None:
+                    stride = infer_stride(data, kernel, output)
+                else:
+                    stride = infer_stride(data_pad, kernel, output)
+
+                wkl = _get_workload(data, kernel, stride, padding, output.dtype)
+                sch = _get_schedule(wkl)
+                _AVX_SCH_TO_SCH_FUNC[type(sch)](s, data, data_pad, data_vec,
+                                                kernel, kernel_vec, conv_out, output, outs[0])
+            else:
+                conv = op.output(0)
+                kernel = op.input_tensors[1]
+                data = op.input_tensors[0]
+                data_pad = None
+                if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
+                    data_pad = data
+                    data = data_pad.op.input_tensors[0]
+
+                n_pad, c_pad, h_pad, w_pad = data_pad.op.axis
+                pad_fused = s[data_pad].fuse(n_pad, c_pad)
+                s[data_pad].parallel(pad_fused)
+                C = conv
+                n, c, h, w = C.op.axis
+                rc, ry, rx = C.op.reduce_axis
+                fused = s[C].fuse(n, c)
+                s[C].parallel(fused)
+                wo, wi = s[C].split(w, factor=16)
+                s[C].reorder(fused, rc, h, wo, ry, rx, wi)  # move rc to outer loop
+                s[C].unroll(rx)
+                s[C].unroll(ry)
+                s[C].vectorize(wi)
 
     traverse(outs[0].op)
     return s
diff --git a/topi/python/topi/x86/conv2d_avx_1x1.py b/topi/python/topi/x86/conv2d_avx_1x1.py
new file mode 100644
index 000000000000..cc264d04ac24
--- /dev/null
+++ b/topi/python/topi/x86/conv2d_avx_1x1.py
@@ -0,0 +1,135 @@
+# pylint: disable=invalid-name,unused-variable,invalid-name
+"""1x1 Conv2D schedule on for Intel CPU"""
+from __future__ import absolute_import as _abs
+from collections import namedtuple
+import tvm
+
+from ..util import get_const_tuple
+from ..nn.conv2d import _get_schedule, _get_workload
+from ..nn.util import infer_pad, infer_stride
+from ..nn.pad import pad
+
+AVXConv1x1Fwd = namedtuple('AVXConv1x1Fwd', ['ic_bn', 'oc_bn', 'oh_factor', 'ow_factor'])
+
+def _declaration_conv(data, kernel, stride, padding, layout, out_dtype):
+    assert layout == 'NCHW', "only support NCHW convolution for AVX"
+    wkl = _get_workload(data, kernel, stride, padding, out_dtype)
+    sch = _get_schedule(wkl)
+
+    HPAD, WPAD = wkl.hpad, wkl.wpad
+    HSTR, WSTR = wkl.hstride, wkl.wstride
+
+    batch_size, in_channel, in_height, in_width = get_const_tuple(data.shape)
+    num_filter, _, kernel_height, kernel_width = get_const_tuple(kernel.shape)
+
+    pad_height = in_height + 2 * HPAD
+    pad_width = in_width + 2 * WPAD
+
+    out_height = (in_height + 2 * HPAD - kernel_height) // HSTR + 1
+    out_width = (in_width + 2 * WPAD - kernel_width) // WSTR + 1
+
+    DOPAD = (HPAD != 0 and WPAD != 0)
+    if DOPAD:
+        data_pad = pad(data, (0, 0, HPAD, WPAD), name="data_pad")
+    else:
+        data_pad = data
+    shape = (batch_size, in_channel // sch.ic_bn, pad_height, pad_width, sch.ic_bn)
+    data_vec = tvm.compute(shape, lambda n, C, h, w, c: data_pad[n, C * sch.ic_bn + c, h, w])
+
+    shape = (num_filter // sch.oc_bn, in_channel // sch.ic_bn, sch.ic_bn, sch.oc_bn, 1, 1)
+    kernel_vec = tvm.compute(shape, lambda CO, CI, ci, co, h, w:
+                             kernel[CO * sch.oc_bn + co, CI * sch.ic_bn + ci, h, w],
+                             name='kernel_vec')
+
+    oshape = (batch_size, num_filter // sch.oc_bn, out_height, out_width, sch.oc_bn)
+    ic = tvm.reduce_axis((0, in_channel), name='ic')
+    conv = tvm.compute(oshape, lambda n, oc_chunk, oh, ow, oc_block:
+                       tvm.sum(data_vec[n, ic//sch.ic_bn, oh*HSTR, ow*WSTR, ic%sch.ic_bn] *
+                               kernel_vec[oc_chunk, ic//sch.ic_bn, ic%sch.ic_bn, oc_block, 0, 0],
+                               axis=[ic]), name='conv')
+
+    oshape = (batch_size, num_filter, out_height, out_width)
+    unpack = tvm.compute(oshape, lambda n, oc, oh, ow:
+                         conv[n, oc // sch.oc_bn, oh, ow, oc % sch.oc_bn],
+                         tag='conv2d_nchw')
+    return unpack
+
+
+def _schedule_conv(s, data, data_pad, data_vec, kernel, kernel_vec, conv_out, output, last):
+    # no stride and padding info here
+    padding = infer_pad(data, data_pad)
+    if data_pad is None:
+        stride = infer_stride(data, kernel, output)
+    else:
+        stride = infer_stride(data_pad, kernel, output)
+
+    wkl = _get_workload(data, kernel, stride, padding, output.dtype)
+    sch = _get_schedule(wkl)
+
+    HPAD, WPAD = wkl.hpad, wkl.wpad
+    DOPAD = (HPAD != 0 and WPAD != 0)
+
+    A, W = data, kernel_vec
+    A0, A1 = data_pad, data_vec
+    # schedule data
+    if DOPAD:
+        s[A0].compute_inline()
+    batch, ic_chunk, ih, ic_block, iw = s[A1].op.axis
+    parallel_axis = s[A1].fuse(ic_chunk, ih)
+    s[A1].parallel(parallel_axis)
+    s[A1].pragma(batch, "parallel_launch_point")
+    s[A1].pragma(parallel_axis, "parallel_stride_pattern")
+    s[A1].pragma(batch, "parallel_barrier_when_finish")
+
+    # schedule kernel pack
+    oc_chunk, ic_chunk, oh, ow, ic_block, oc_block = s[W].op.axis
+    s[W].reorder(oc_chunk, oh, ic_chunk, ow, ic_block, oc_block)
+    if sch.oc_bn > 1:
+        s[W].vectorize(oc_block)
+    parallel_axis = s[W].fuse(oc_chunk, oh)
+    s[W].parallel(parallel_axis)
+    s[W].pragma(parallel_axis, "parallel_launch_point")
+    s[W].pragma(parallel_axis, "parallel_stride_pattern")
+    s[W].pragma(parallel_axis, "parallel_barrier_when_finish")
+
+    C, O0, O = conv_out, output, last
+    CC = s.cache_write(C, 'global')
+
+    batch, oc_chunk, oh, ow, oc_block = s[C].op.axis
+    oh_outer, oh_inner = s[C].split(oh, factor=sch.oh_factor)
+    s[C].vectorize(oc_block)
+
+    s[CC].compute_at(s[C], oh_outer)
+    _, oc_chunk, oh, ow, oc_block = s[CC].op.axis
+    ic, = s[CC].op.reduce_axis
+
+    ic_chunk, ic_block = s[CC].split(ic, factor=sch.ic_bn)
+
+    oh_outer, oh_inner = s[CC].split(oh, factor=sch.oh_factor)
+    ow_outer, ow_inner = s[CC].split(ow, factor=sch.ow_factor)
+
+    s[CC].reorder(oc_chunk, oh_outer, ow_outer, ic_chunk, ic_block, oh_inner, ow_inner, oc_block)
+    s[CC].vectorize(oc_block)
+
+    s[CC].unroll(ow_inner)
+    s[CC].unroll(oh_inner)
+
+    if O0 != O:
+        s[O0].compute_inline()
+    batch, oc, oh, ow = s[O].op.axis
+
+    oc_chunk, oc_block = s[O].split(oc, factor=sch.oc_bn)
+    oh_outer, oh_inner = s[O].split(oh, factor=sch.oh_factor)
+    ow_outer, ow_inner = s[O].split(ow, factor=sch.ow_factor)
+    s[O].reorder(oc_chunk, oh_outer, ow_outer, oh_inner, ow_inner, oc_block)
+
+    parallel_axis = s[O].fuse(oc_chunk, oh_outer)
+    s[C].compute_at(s[O], parallel_axis)
+    s[O].vectorize(oc_block)
+
+    s[O].parallel(parallel_axis)
+    s[O].pragma(batch, "parallel_launch_point")
+    s[O].pragma(parallel_axis, "parallel_stride_pattern")
+    s[O].pragma(batch, "parallel_barrier_when_finish")
+
+    return s
diff --git a/topi/python/topi/x86/conv2d_avx_common.py b/topi/python/topi/x86/conv2d_avx_common.py
new file mode 100644
index 000000000000..4f5be019f45a
--- /dev/null
+++ b/topi/python/topi/x86/conv2d_avx_common.py
@@ -0,0 +1,151 @@
+# pylint: disable=invalid-name,unused-variable,invalid-name
+"""Conv2D schedule on for Intel CPU"""
+from __future__ import absolute_import as _abs
+from collections import namedtuple
+import tvm
+
+from ..util import get_const_tuple
+from ..nn.conv2d import _get_schedule, _get_workload
+from ..nn.util import infer_pad, infer_stride
+from ..nn.pad import pad
+
+AVXConvCommonFwd = namedtuple('AVXConvCommonFwd', ['ic_bn', 'oc_bn', 'reg_n', 'unroll_kw'])
+
+def _declaration_conv(data, kernel, stride, padding, layout, out_dtype):
+    assert layout == 'NCHW', "only support NCHW convolution for AVX"
+    wkl = _get_workload(data, kernel, stride, padding, out_dtype)
+    sch = _get_schedule(wkl)
+
+    HPAD, WPAD = wkl.hpad, wkl.wpad
+    HSTR, WSTR = wkl.hstride, wkl.wstride
+
+    batch_size, in_channel, in_height, in_width = get_const_tuple(data.shape)
+    num_filter, _, kernel_height, kernel_width = get_const_tuple(kernel.shape)
+
+    pad_height = in_height + 2 * HPAD
+    pad_width = in_width + 2 * WPAD
+
+    out_height = (in_height + 2 * HPAD - kernel_height) // HSTR + 1
+    out_width = (in_width + 2 * WPAD - kernel_width) // WSTR + 1
+
+    # pack data
+    DOPAD = (HPAD != 0 and WPAD != 0)
+    if DOPAD:
+        data_pad = pad(data, (0, 0, HPAD, WPAD), name="data_pad")
+    else:
+        data_pad = data
+
+    shape = (batch_size, in_channel // sch.ic_bn, pad_height, sch.ic_bn, pad_width)
+    data_vec = tvm.compute(shape,
+                           lambda n, C, h, c, w: data_pad[n, C * sch.ic_bn + c, h, w],
+                           name='data_vec')
+
+    # pack kernel
+    shape = (num_filter//sch.oc_bn, in_channel//sch.ic_bn,
+             kernel_height, kernel_width, sch.ic_bn, sch.oc_bn)
+    kernel_vec = tvm.compute(shape, lambda CO, CI, h, w, ci, co:
+                             kernel[CO * sch.oc_bn + co, CI * sch.ic_bn + ci, h, w],
+                             name='kernel_vec')
+
+    # convolution
+    oshape = (batch_size, num_filter//sch.oc_bn, out_height, out_width, sch.oc_bn)
+    unpack_shape = (batch_size, num_filter, out_height, out_width)
+
+    ic = tvm.reduce_axis((0, in_channel), name='ic')
+    kh = tvm.reduce_axis((0, kernel_height), name='kh')
+    kw = tvm.reduce_axis((0, kernel_width), name='kw')
+
+    conv = tvm.compute(oshape, lambda n, oc_chunk, oh, ow, oc_block:
+                       tvm.sum(data_vec[n, ic//sch.ic_bn, oh*HSTR+kh, ic%sch.ic_bn, ow*WSTR+kw] *
+                               kernel_vec[oc_chunk, ic//sch.ic_bn, kh, kw, ic%sch.ic_bn, oc_block],
+                               axis=[ic, kh, kw]),
+                       name='conv')
+
+    unpack = tvm.compute(unpack_shape,
+                         lambda n, c, h, w: conv[n, c // sch.oc_bn, h, w, c % sch.oc_bn],
+                         name='output_unpack',
+                         tag='conv2d_nchw')
+    return unpack
+
+
+def _schedule_conv(s, data, data_pad, data_vec, kernel, kernel_vec, conv_out, output, last):
+    # no stride and padding info here
+    padding = infer_pad(data, data_pad)
+    if data_pad is None:
+        stride = infer_stride(data, kernel, output)
+    else:
+        stride = infer_stride(data_pad, kernel, output)
+    wkl = _get_workload(data, kernel, stride, padding, output.dtype)
+    sch = _get_schedule(wkl)
+
+    HPAD, WPAD = wkl.hpad, wkl.wpad
+    DOPAD = (HPAD != 0 and WPAD != 0)
+
+    A, W = data, kernel_vec
+    A0, A1 = data_pad, data_vec
+
+    # schedule data
+    if DOPAD:
+        s[A0].compute_inline()
+    batch, ic_chunk, ih, ic_block, iw = s[A1].op.axis
+    parallel_axis = s[A1].fuse(ic_chunk, ih)
+    s[A1].parallel(parallel_axis)
+    s[A1].pragma(batch, "parallel_launch_point")
+    s[A1].pragma(parallel_axis, "parallel_stride_pattern")
+    s[A1].pragma(batch, "parallel_barrier_when_finish")
+
+    # schedule kernel pack
+    oc_chunk, ic_chunk, oh, ow, ic_block, oc_block = s[W].op.axis
+    s[W].reorder(oc_chunk, oh, ic_chunk, ow, ic_block, oc_block)
+    if sch.oc_bn > 1:
+        s[W].vectorize(oc_block)
+    parallel_axis = s[W].fuse(oc_chunk, oh)
+    s[W].parallel(parallel_axis)
+    s[W].pragma(parallel_axis, "parallel_launch_point")
+    s[W].pragma(parallel_axis, "parallel_stride_pattern")
+    s[W].pragma(parallel_axis, "parallel_barrier_when_finish")
+
+    # schedule conv
+    C, O0, O = conv_out, output, last
+    CC = s.cache_write(C, 'global')
+
+    _, oc_chunk, oh, ow, oc_block = s[C].op.axis
+    ow_chunk, ow_block = s[C].split(ow, factor=sch.reg_n)
+    s[C].reorder(oc_chunk, oh, ow_chunk, ow_block, oc_block)
+    s[C].fuse(oc_chunk, oh)
+    s[C].vectorize(oc_block)
+
+    s[CC].compute_at(s[C], ow_chunk)
+    _, oc_chunk, oh, ow, oc_block = s[CC].op.axis
+    ic, kh, kw = s[CC].op.reduce_axis
+
+    ow_chunk, ow_block = s[CC].split(ow, factor=sch.reg_n)
+    ic_chunk, ic_block = s[CC].split(ic, factor=sch.ic_bn)
+
+    if sch.unroll_kw:
+        s[CC].reorder(oc_chunk, oh, ow_chunk, ic_chunk, kh, ic_block, kw, ow_block, oc_block)
+        s[CC].unroll(kw)
+    else:
+        s[CC].reorder(oc_chunk, oh, ow_chunk, ic_chunk, kh, kw, ic_block, ow_block, oc_block)
+
+    s[CC].fuse(oc_chunk, oh)
+    s[CC].vectorize(oc_block)
+    s[CC].unroll(ow_block)
+
+    if O0 != O:
+        s[O0].compute_inline()
+
+    batch, oc, oh, ow = s[O].op.axis
+    ow_chunk, ow_block = s[O].split(ow, factor=sch.reg_n)
+    oc_chunk, oc_block = s[O].split(oc, factor=sch.oc_bn)
+    s[O].reorder(oc_chunk, oh, ow_chunk, ow_block, oc_block)
+    parallel_axis = s[O].fuse(oc_chunk, oh)
+    s[C].compute_at(s[O], parallel_axis)
+    s[O].vectorize(oc_block)
+
+    s[O].parallel(parallel_axis)
+    s[O].pragma(batch, "parallel_launch_point")
+    s[O].pragma(parallel_axis, "parallel_stride_pattern")
+    s[O].pragma(batch, "parallel_barrier_when_finish")
+
+    return s
diff --git a/topi/tests/python/test_topi_conv2d_nchw.py b/topi/tests/python/test_topi_conv2d_nchw.py
index 8b88bc3ba387..e5a674709009 100644
--- a/topi/tests/python/test_topi_conv2d_nchw.py
+++ b/topi/tests/python/test_topi_conv2d_nchw.py
@@ -11,8 +11,6 @@ def verify_conv2d_nchw(batch, in_channel, in_size, num_filter, kernel, stride, p
 
     A = tvm.placeholder((batch, in_channel, in_height, in_width), name='A')
     W = tvm.placeholder((num_filter, in_channel, kernel, kernel), name='W')
-    B = topi.nn.conv2d_nchw(A, W, stride, padding)
-    C = topi.nn.relu(B)
 
     a_shape = get_const_tuple(A.shape)
     w_shape = get_const_tuple(W.shape)
@@ -35,6 +33,8 @@ def check_device(device):
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
+            B = topi.nn.conv2d(A, W, stride, padding, layout='NCHW')
+            C = topi.nn.relu(B)
             s1 = topi.generic.schedule_conv2d_nchw([B])
             s2 = topi.generic.schedule_conv2d_nchw([C])
         a = tvm.nd.array(a_np, ctx)

From bb298af593aab90ade73834e982b394890edb68d Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 8 Feb 2018 17:26:32 -0800
Subject: [PATCH 144/949] [PASS] Prepare storage rewrite for unified buffer
 (#885)

* [PASS] Prepare storage rewrite for unified buffer

* more comments
---
 src/pass/storage_rewrite.cc | 39 ++++++++++++++++++++++++-------------
 1 file changed, 26 insertions(+), 13 deletions(-)

diff --git a/src/pass/storage_rewrite.cc b/src/pass/storage_rewrite.cc
index fe4295feb982..452b1a7b6abf 100644
--- a/src/pass/storage_rewrite.cc
+++ b/src/pass/storage_rewrite.cc
@@ -363,7 +363,7 @@ class StoragePlanRewriter : public IRMutator {
   Expr Mutate_(const Variable* op, const Expr& e) final {
     auto it = alloc_map_.find(op);
     if (it != alloc_map_.end()) {
-      if (it->second->elem_offset != 0) {
+      if (it->second->bits_offset != 0) {
         LOG(WARNING) << "Use a merged buffer variable address, could cause error";
       }
       return it->second->alloc_var;
@@ -381,11 +381,10 @@ class StoragePlanRewriter : public IRMutator {
        const StorageEntry* se = it->second;
        Expr offset = Mutate(op->args[2]);
        Expr extent = Mutate(op->args[3]);
-       CHECK_EQ(se->elem_type, dtype.element_of())
-           << " buffer=" << buffer->name_hint;
-       CHECK_EQ(se->elem_offset % dtype.lanes(), 0);
-       if (se->elem_offset != 0) {
-         offset = make_const(offset.type(), se->elem_offset / dtype.lanes()) + offset;
+       uint64_t elem_bits = dtype.bits() * dtype.lanes();
+       CHECK_EQ(se->bits_offset % elem_bits, 0U);
+       if (se->bits_offset != 0) {
+         offset = make_const(offset.type(), se->bits_offset / elem_bits) + offset;
        }
        return Call::make(
            op->type, op->name,
@@ -465,8 +464,17 @@ class StoragePlanRewriter : public IRMutator {
     // The allocation element type.
     Type elem_type;
     // This is non-zero if this allocate is folded into another one
-    // the address becomes alloc_var + sizeof(elem_type) * elem_offset;
-    uint64_t elem_offset{0};
+    // the address(in bits) becomes alloc_var + bits_offset;
+    // can be effectively converted to the element type.
+    // We need to convert bit_offset to offset of specific element type later.
+    //
+    // We use bits(instead of bytes) to support non-conventional indexing in hardware.
+    // When we are merging buffer together, the bits_offset are set to be aligned
+    // to certain value given by the max_simd_bits property of the special memory.
+    //
+    // This allows effective sharing among different types as long as their alignment
+    // requirement fits into the max_simd_bits.
+    uint64_t bits_offset{0};
   };
 
   // Alllocate entry of node.
@@ -495,8 +503,10 @@ class StoragePlanRewriter : public IRMutator {
   // Remap the index
   Expr RemapIndex(Type dtype, Expr index, StorageEntry* e) {
     CHECK_EQ(dtype.element_of(), e->elem_type);
-    if (e->elem_offset == 0) return index;
-    return make_const(index.type(), e->elem_offset) + index;
+    if (e->bits_offset == 0) return index;
+    uint64_t elem_bits = dtype.bits() * dtype.lanes();
+    CHECK_EQ(e->bits_offset % elem_bits, 0U);
+    return make_const(index.type(), e->bits_offset / elem_bits) + index;
   }
   // Prepare the new allocations
   void PrepareNewAlloc() {
@@ -526,7 +536,7 @@ class StoragePlanRewriter : public IRMutator {
       for (size_t i = 0; i < vec.size(); ++i) {
         StorageEntry* e = vec[i];
         // already merged
-        if (e->elem_offset != 0) continue;
+        if (e->bits_offset != 0) continue;
         if (e->merged_children.size() != 0) {
           NewAllocTagMerged(e); continue;
         }
@@ -580,10 +590,13 @@ class StoragePlanRewriter : public IRMutator {
     CHECK_NE(e->const_nbits, 0U);
     MemoryInfo info = GetMemoryInfo(e->scope.to_string());
     uint64_t total_bits = e->const_nbits;
-    size_t align = 1;
+    // By default, align to 32 bits.
+    size_t align = 32;
     if (info.defined()) {
       align = info->max_simd_bits;
     }
+    // Always align to max_simd_bits
+    // so we can remap types by keeping this property
     if (total_bits % align != 0) {
       total_bits += align  - (total_bits % align);
     }
@@ -591,7 +604,7 @@ class StoragePlanRewriter : public IRMutator {
     for (StorageEntry* child : e->merged_children) {
       CHECK_NE(child->const_nbits, 0U);
       CHECK_NE(total_bits, 0U);
-      child->elem_offset = total_bits / child->elem_type.bits();
+      child->bits_offset = total_bits;
       child->alloc_var = e->alloc_var;
       total_bits += child->const_nbits;
       if (total_bits % align != 0) {

From 8c804c072246fcf18ac7c670831bc4b8ff12fcd3 Mon Sep 17 00:00:00 2001
From: masahi <masahi129@gmail.com>
Date: Fri, 9 Feb 2018 11:03:23 +0900
Subject: [PATCH 145/949] allow fallback path to non imagenet workloads (#886)

---
 topi/python/topi/x86/conv2d.py | 94 ++++++++++++++++++----------------
 1 file changed, 50 insertions(+), 44 deletions(-)

diff --git a/topi/python/topi/x86/conv2d.py b/topi/python/topi/x86/conv2d.py
index 7457deab4c17..1cbf2f2cadfd 100644
--- a/topi/python/topi/x86/conv2d.py
+++ b/topi/python/topi/x86/conv2d.py
@@ -66,8 +66,8 @@ def _get_schedule_conv(wkl):
 @conv2d.register("cpu")
 def _declaration_conv(data, kernel, stride, padding, layout, out_dtype):
     target = tvm.target.current_target(allow_none=False)
-    if 'avx' in str(target) and layout == 'NCHW':
-        wkl = _get_workload(data, kernel, stride, padding, out_dtype)
+    wkl = _get_workload(data, kernel, stride, padding, out_dtype)
+    if wkl in _WORKLOADS and 'avx' in str(target) and layout == 'NCHW':
         sch = _get_schedule(wkl)
         return _AVX_SCH_TO_DECL_FUNC[type(sch)](data, kernel, stride, padding, layout, out_dtype)
     elif layout == 'NCHW':
@@ -86,6 +86,30 @@ def schedule_conv2d(outs):
     s = tvm.create_schedule([x.op for x in outs])
     target = tvm.target.current_target(allow_none=False)
 
+    def default_schedule(op):
+        """NCHW conv2d schedule for non imagenet workloads"""
+        conv = op.output(0)
+        kernel = op.input_tensors[1]
+        data = op.input_tensors[0]
+        data_pad = None
+        if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
+            data_pad = data
+            data = data_pad.op.input_tensors[0]
+
+        n_pad, c_pad, h_pad, w_pad = data_pad.op.axis
+        pad_fused = s[data_pad].fuse(n_pad, c_pad)
+        s[data_pad].parallel(pad_fused)
+        C = conv
+        n, c, h, w = C.op.axis
+        rc, ry, rx = C.op.reduce_axis
+        fused = s[C].fuse(n, c)
+        s[C].parallel(fused)
+        wo, wi = s[C].split(w, factor=16)
+        s[C].reorder(fused, rc, h, wo, ry, rx, wi)  # move rc to outer loop
+        s[C].unroll(rx)
+        s[C].unroll(ry)
+        s[C].vectorize(wi)
+
     def traverse(op):
         """Traverse operators from computation graph"""
         # inline all one-to-one-mapping operators except the last stage (output)
@@ -104,49 +128,31 @@ def traverse(op):
 
         if 'conv2d_nchw' in op.tag:
             if 'avx' in str(target):
-                output = op.output(0)
-                conv_out = op.input_tensors[0]
-                kernel_vec = conv_out.op.input_tensors[1]
-                kernel = kernel_vec.op.input_tensors[0]
-                data_vec = conv_out.op.input_tensors[0]
-                data = data_vec.op.input_tensors[0]
-                data_pad = None
-                if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
-                    data_pad = data
-                    data = data_pad.op.input_tensors[0]
-
-                padding = infer_pad(data, data_pad)
-                if data_pad is None:
-                    stride = infer_stride(data, kernel, output)
-                else:
-                    stride = infer_stride(data_pad, kernel, output)
-
-                wkl = _get_workload(data, kernel, stride, padding, output.dtype)
-                sch = _get_schedule(wkl)
-                _AVX_SCH_TO_SCH_FUNC[type(sch)](s, data, data_pad, data_vec,
-                                                kernel, kernel_vec, conv_out, output, outs[0])
+                try:
+                    output = op.output(0)
+                    conv_out = op.input_tensors[0]
+                    kernel_vec = conv_out.op.input_tensors[1]
+                    kernel = kernel_vec.op.input_tensors[0]
+                    data_vec = conv_out.op.input_tensors[0]
+                    data = data_vec.op.input_tensors[0]
+                    data_pad = None
+                    if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
+                        data_pad = data
+                        data = data_pad.op.input_tensors[0]
+                    padding = infer_pad(data, data_pad)
+                    if data_pad is None:
+                        stride = infer_stride(data, kernel, output)
+                    else:
+                        stride = infer_stride(data_pad, kernel, output)
+
+                    wkl = _get_workload(data, kernel, stride, padding, output.dtype)
+                    sch = _get_schedule(wkl)
+                    _AVX_SCH_TO_SCH_FUNC[type(sch)](s, data, data_pad, data_vec,
+                                                    kernel, kernel_vec, conv_out, output, outs[0])
+                except IndexError:
+                    default_schedule(op)
             else:
-                conv = op.output(0)
-                kernel = op.input_tensors[1]
-                data = op.input_tensors[0]
-                data_pad = None
-                if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
-                    data_pad = data
-                    data = data_pad.op.input_tensors[0]
-
-                n_pad, c_pad, h_pad, w_pad = data_pad.op.axis
-                pad_fused = s[data_pad].fuse(n_pad, c_pad)
-                s[data_pad].parallel(pad_fused)
-                C = conv
-                n, c, h, w = C.op.axis
-                rc, ry, rx = C.op.reduce_axis
-                fused = s[C].fuse(n, c)
-                s[C].parallel(fused)
-                wo, wi = s[C].split(w, factor=16)
-                s[C].reorder(fused, rc, h, wo, ry, rx, wi)  # move rc to outer loop
-                s[C].unroll(rx)
-                s[C].unroll(ry)
-                s[C].vectorize(wi)
+                default_schedule(op)
 
     traverse(outs[0].op)
     return s

From 1b0d9f58019de07d0060cb80e33a74e7f8500c08 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 9 Feb 2018 14:33:25 -0800
Subject: [PATCH 146/949] Update jenkins (#890)

---
 Jenkinsfile | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Jenkinsfile b/Jenkinsfile
index 0506caac850d..b667359f0f2b 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -5,7 +5,7 @@
 
 // tvm libraries
 tvm_runtime = "lib/libtvm_runtime.so, config.mk"
-tvm_lib = "lib/libtvm.so " + tvm_runtime
+tvm_lib = "lib/libtvm.so, " + tvm_runtime
 // LLVM upstream lib
 tvm_multilib = "lib/libtvm_llvm40.so, lib/libtvm_llvm50.so, lib/libtvm_llvm60.so, lib/libtvm_topi.so, " + tvm_runtime
 
@@ -217,7 +217,7 @@ stage('Unit Test') {
     }
   },
   'cpp': {
-    node('linux') {
+    node('CPU' && 'linux') {
       ws('workspace/tvm/ut-cpp') {
         init_git()
         unpack_lib('cpu', tvm_lib)

From 469fba311bf33d9585c7015dc49458c865643446 Mon Sep 17 00:00:00 2001
From: eqy <eqy@cs.washington.edu>
Date: Fri, 9 Feb 2018 14:47:51 -0800
Subject: [PATCH 147/949] ignore model option in target (#889)

---
 src/codegen/llvm/llvm_common.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/codegen/llvm/llvm_common.cc b/src/codegen/llvm/llvm_common.cc
index f8b80118cb5d..03ac6f0ff227 100644
--- a/src/codegen/llvm/llvm_common.cc
+++ b/src/codegen/llvm/llvm_common.cc
@@ -85,7 +85,7 @@ void ParseLLVMTargetOptions(const std::string& target_str,
       } else {
         LOG(FATAL) << "invalid -mfloat-abi option " << value;
       }
-    } else if (key == "-device" || key == "-libs") {
+    } else if (key == "-device" || key == "-libs" || key == "-model") {
       // pass
     } else {
       LOG(FATAL) << "unknown option " << key;

From 726c0ebb234206a6051c448abd82441dd4edb0ce Mon Sep 17 00:00:00 2001
From: masahi <masahi129@gmail.com>
Date: Sat, 10 Feb 2018 11:14:34 +0900
Subject: [PATCH 148/949] [TOPI] Initial NHWC layout support (#882)

* add 4 dim softmax

* update for NHWC layout

* remove layout param from softmax

* fix typo

* minor fix to pool

support axis=1 ndims=5 softmax.

add softmax axis

* few fix for softmax

* fix typo

* add more doc

* minor doc fix

* fix upsampling output shape

* fix lint

* cleanup softmax

* minor fix

* raise exception instead of assert, handles negative axis

* check axis after axis transformation
---
 topi/python/topi/nn/conv2d.py     |   2 +
 topi/python/topi/nn/pooling.py    | 118 +++++++++++++++++++++++++++++-
 topi/python/topi/nn/softmax.py    |  47 +++++++++---
 topi/python/topi/nn/upsampling.py |  53 +++++++++++++-
 4 files changed, 206 insertions(+), 14 deletions(-)

diff --git a/topi/python/topi/nn/conv2d.py b/topi/python/topi/nn/conv2d.py
index 3bd910e29974..d6488b164d04 100644
--- a/topi/python/topi/nn/conv2d.py
+++ b/topi/python/topi/nn/conv2d.py
@@ -82,6 +82,8 @@ def conv2d(data, kernel, stride, padding, layout='NCHW', out_dtype='float32'):
         return conv2d_nchw(data, kernel, stride, padding, out_dtype)
     elif layout == 'HWCN':
         return conv2d_hwcn(data, kernel, stride, padding, out_dtype)
+    elif layout == 'NHWC':
+        return conv2d_nhwc(data, kernel, stride, padding, out_dtype)
     else:
         raise ValueError("not support this layout {} yet".format(layout))
 
diff --git a/topi/python/topi/nn/pooling.py b/topi/python/topi/nn/pooling.py
index 99b15e18e4e1..0519471eaa7e 100644
--- a/topi/python/topi/nn/pooling.py
+++ b/topi/python/topi/nn/pooling.py
@@ -44,9 +44,50 @@ def global_pool(data, pool_type):
         raise ValueError("Pool type should be 'avg' or 'max'.")
 
 
-def pool(data, kernel, stride, padding, pool_type, ceil_mode=False):
+def pool(data, kernel, stride, padding, pool_type, ceil_mode=False, layout="NCHW"):
     """Perform pooling on the data
 
+    Parameters
+    ----------
+    data : tvm.Tensor
+        4-D with shape [batch, channel, in_height, in_width]
+        or  [batch, in_height, in_width, channel]
+
+    kernel : list/tuple of two ints
+        Kernel size, [kernel_height, kernel_width]
+
+    stride : list/tuple of two ints
+        Stride size, [stride_height, stride_width]
+
+    paddding : list/tuple of two ints
+        Pad size, [pad_height, pad_width]
+
+    pool_type : str
+        Pool type, 'max' or 'avg'
+
+    ceil_mode : bool
+        Whether to use ceil when caculate output size.
+
+    layout: string
+        either "NCHW" or "NHWC"
+
+    Returns
+    -------
+    output : tvm.Tensor
+        4-D with shape [batch, channel, out_height, out_width]
+        or [batch, out_height, out_width, channel]
+    """
+    if layout == "NCHW":
+        return pool_nchw(data, kernel, stride, padding, pool_type, ceil_mode=ceil_mode)
+    elif layout == "NHWC":
+        return pool_nhwc(data, kernel, stride, padding, pool_type, ceil_mode=ceil_mode)
+    else:
+        raise ValueError("not support this layout {} yet".format(layout))
+
+
+def pool_nchw(data, kernel, stride, padding, pool_type, ceil_mode=False):
+    """Perform pooling on the data in NCHW layout
+
     Parameters
     ----------
     data : tvm.Tensor
@@ -117,3 +158,78 @@ def pool(data, kernel, stride, padding, pool_type, ceil_mode=False):
                             tag=tag.ELEMWISE)
     else:
         raise ValueError("Pool type should be 'avg' or 'max'.")
+
+
+def pool_nhwc(data, kernel, stride, padding, pool_type, ceil_mode=False):
+    """Perform pooling on the data in NHWC layout
+
+    Parameters
+    ----------
+    data : tvm.Tensor
+        4-D with shape [batch, in_height, in_width, channel]
+
+    kernel : list/tuple of two ints
+        Kernel size, [kernel_height, kernel_width]
+
+    stride : list/tuple of two ints
+        Stride size, [stride_height, stride_width]
+
+    paddding : list/tuple of two ints
+        Pad size, [pad_height, pad_width]
+
+    pool_type : str
+        Pool type, 'max' or 'avg'
+
+    ceil_mode : bool
+        Whether to use ceil when caculate output size.
+
+    Returns
+    -------
+    output : tvm.Tensor
+        4-D with shape [batch, channel, out_height, out_width]
+    """
+    assert len(data.shape) == 4, "only support 4-dim pooling"
+    assert len(stride) == 2, "only support 2-dim stride"
+    kernel_height, kernel_width = kernel
+    stride_height, stride_width = stride
+    batch, height, width, channel = data.shape
+
+    pad_top, pad_left, pad_down, pad_right = get_pad_tuple(
+        padding, (kernel_height, kernel_width))
+
+    if ceil_mode:
+        # Additional padding to ensure we do ceil instead of floor when divide stride.
+        pad_down += stride_height -1
+        pad_right += stride_width - 1
+
+    pad_before = [0, pad_top, pad_left, 0]
+    pad_after = [0, pad_down, pad_right, 0]
+
+    out_height = util.simplify((height - kernel_height + pad_top + pad_down) // stride_height + 1)
+    out_width = util.simplify((width - kernel_width + pad_left + pad_right) // stride_width + 1)
+
+    dheight = tvm.reduce_axis((0, kernel_height))
+    dwidth = tvm.reduce_axis((0, kernel_width))
+
+    if pool_type == 'max':
+        temp = pad(data, pad_before, pad_after, name="pad_temp", \
+            pad_value=tvm.min_value(data.dtype))
+        return tvm.compute((batch, out_height, out_width, channel), \
+                            lambda n, h, w, c: \
+                            tvm.max(temp[n, h*stride_height+dheight, w*stride_width+dwidth, c], \
+                                axis=[dheight, dwidth]), \
+                            tag="pool_max")
+    elif pool_type == 'avg':
+        temp = pad(data, pad_before, pad_after, name="pad_temp", \
+            pad_value=tvm.const(0.).astype(data.dtype))
+        tsum = tvm.compute((batch, out_height, out_width, channel, ), \
+                            lambda n, h, w, c: \
+                            tvm.sum(temp[n, h*stride_height+dheight, w*stride_width+dwidth, c], \
+                                axis=[dheight, dwidth]), \
+                            tag="pool_avg")
+        return tvm.compute((batch, out_height, out_width, channel), \
+                            lambda n, h, w, c: \
+                            tsum[n, h, w, c] / (kernel_height*kernel_width), \
+                            tag=tag.ELEMWISE)
+    else:
+        raise ValueError("Pool type should be 'avg' or 'max'.")
diff --git a/topi/python/topi/nn/softmax.py b/topi/python/topi/nn/softmax.py
index 9060a31f532b..5e73f7633f4d 100644
--- a/topi/python/topi/nn/softmax.py
+++ b/topi/python/topi/nn/softmax.py
@@ -4,28 +4,51 @@
 import tvm
 
 @tvm.tag_scope(tag='softmax_output')
-def softmax(x):
+def softmax(x, axis=-1):
     """Perform softmax activation on the data
 
     Parameters
     ----------
     data : tvm.Tensor
-        2-D input data
+        can be any dimension
+
+    axis : int
+        channel axis
 
     Returns
     -------
     output : tvm.Tensor
-        2-D output with same shape
+        output shape is the same as input
     """
-    assert len(x.shape) == 2, "only support 2-dim softmax"
-    m, n = x.shape
-    k = tvm.reduce_axis((0, n), name='k')
-    max_elem = tvm.compute((m, ), lambda i: tvm.max(x[i, k], axis=k))
-    k = tvm.reduce_axis((0, n), name='k')
-    expsum = tvm.compute(
-        (m, ), lambda i: tvm.sum(tvm.exp(x[i, k] - max_elem[i]), axis=k))
-    return tvm.compute(
-        x.shape, lambda i, j: tvm.exp(x[i, j] - max_elem[i]) / expsum[i])
+    shape = x.shape
+    if axis < 0:
+        axis = len(shape) + axis
+    if axis >= len(shape):
+        ValueError("axis parameter should be less than input dim")
+
+    k1 = tvm.reduce_axis((0, shape[axis]), name='k')
+    k2 = tvm.reduce_axis((0, shape[axis]), name='k')
+
+    def insert_reduce_index(indices, reduce_index):
+        return indices[:axis] + (reduce_index,) + indices[axis:]
+
+    def _compute_max(*indices):
+        eval_range = insert_reduce_index(indices, k1)
+        return tvm.max(x[eval_range], axis=k1)
+
+    def _compute_expsum(max_elem, *indices):
+        eval_range = insert_reduce_index(indices, k2)
+        return tvm.sum(tvm.exp(x[eval_range] - max_elem[indices]), axis=k2)
+
+    def _normalize(max_elem, expsum, *indices):
+        non_reduce_indices = tuple([var for (i, var) in enumerate(indices) if i != axis])
+        return tvm.exp(x[indices] - max_elem[non_reduce_indices]) / expsum[non_reduce_indices]
+
+    reduced_shape = tuple([dim for (i, dim) in enumerate(shape) if i != axis])
+    max_elem = tvm.compute(reduced_shape, _compute_max)
+    expsum = tvm.compute(reduced_shape, lambda *indices: _compute_expsum(max_elem, *indices))
+    return tvm.compute(shape, lambda *indices: _normalize(max_elem, expsum, *indices))
+
 
 @tvm.tag_scope(tag='log_softmax_output')
 def log_softmax(x):
diff --git a/topi/python/topi/nn/upsampling.py b/topi/python/topi/nn/upsampling.py
index df77bbdb23c0..9297eb4ad06b 100644
--- a/topi/python/topi/nn/upsampling.py
+++ b/topi/python/topi/nn/upsampling.py
@@ -4,10 +4,40 @@
 from .. import util
 
 
-def upsampling(data, scale):
+def upsampling(data, scale, layout="NCHW"):
     """Perform nearest neighbor upsampling on the data.
        Bilinear upsampling is not supported.
 
+    Parameters
+    ----------
+    data : tvm.Tensor
+        4-D with shape [batch, channel, in_height, in_width]
+        or  [batch, in_height, in_width, channel]
+
+    scale: int
+        upsampling scaling factor
+
+    layout: string
+        either "NCHW" or "NHWC"
+
+    Returns
+    -------
+    output : tvm.Tensor
+        4-D with shape [batch, channel, in_height*scale, in_width*scale]
+        or [batch, in_height*scale, in_width*scale, channel]
+    """
+
+    if layout == "NCHW":
+        return upsampling_nchw(data, scale)
+    elif layout == "NHWC":
+        return upsampling_nhwc(data, scale)
+    else:
+        raise ValueError("not support this layout {} yet".format(layout))
+
+
+def upsampling_nchw(data, scale):
+    """Perform nearest neighor upsampling on NCHW layout input.
+
     Parameters
     ----------
     data : tvm.Tensor
@@ -27,3 +57,24 @@ def upsampling(data, scale):
 
     return tvm.compute((batch, channel, out_height, out_width), \
                         lambda n, c, h, w: data[n, c, h/scale, w/scale])
+
+
+def upsampling_nhwc(data, scale):
+    """Perform nearest neighor upsampling on NHWC layout input.
+
+    Parameters
+    ----------
+    data : tvm.Tensor
+        4-D with shape [batch, in_height, in_width, channel]
+
+    scale: int
+        upsampling scaling factor
+
+    """
+
+    batch, height, width, channel = data.shape
+    out_height = util.simplify(height * scale)
+    out_width = util.simplify(width * scale)
+
+    return tvm.compute((batch, out_height, out_width, channel), \
+                        lambda n, h, w, c: data[n, h/scale, w/scale, c])

From 902e58532233f36e9ca3ee3a7e96486e557ff341 Mon Sep 17 00:00:00 2001
From: alex-weaver <awsweaver@gmail.com>
Date: Sat, 10 Feb 2018 20:39:34 +0000
Subject: [PATCH 149/949] Convert BuildModule to use TVM node system (#879)

* Make python BuildConfig serializable/deserializable to/from string

* Make C++ BuildConfig serializable/deserializable to/from string

* Revert "Make python BuildConfig serializable/deserializable to/from string"

This reverts commit a5e1fb3ff63a161cc0d63475d2a32816cc4c3666.

* Revert "Make C++ BuildConfig serializable/deserializable to/from string"

This reverts commit ec0c2c54543050fe6f264d06eebff33dee70370b.

* Converted BuildConfig to use TVM node system

* Fix lint

* Fix lint

* Added code to set node attributes through the C API

* Fixed bug in build_config()

* Fix lint

* Fix lint

* Fix test errors

* Reduced scope of node __setattr__ to apply only to BuildConfig

* Fix lint

* Fix lint

* Changed python BuildConfig to be immutable, with values set once on construction.

* Fix lint

* Fix C++ test

* Fixed BuildConfig setting python-side args

* Fix lint

* Removed dependency on reflection.cc to construct BuildConfig (allow use in runtime library)

* Fix lint

* Revert "Fix lint"

This reverts commit 16ed6d7a1ca5e551b035bad46e8361ea487cd45b.

* Revert "Removed dependency on reflection.cc to construct BuildConfig (allow use in runtime library)"

This reverts commit 43817c97a2ee045791e0c031d962fa97636ce8f6.

* Avoid accessing BuildConfig when using runtime lib

* Fix missing import

* Fix error running under cython (root cause: node handle is not valid until after __init__ has returned, so cannot call __dir__ during __init__

* Fix error where BuildConfig._node_defaults was not copied in build_config()

* Fix lint

* Fix lint

* Fix lint

* Fix lint

* Add comments to python BuildConfig
---
 include/tvm/build_module.h     | 28 ++++++++++++-
 python/tvm/build_module.py     | 75 ++++++++++++++++++++++------------
 src/api/api_lang.cc            |  1 +
 src/codegen/build_module.cc    | 37 +++++++++++++----
 tests/cpp/build_module_test.cc |  2 +-
 5 files changed, 106 insertions(+), 37 deletions(-)

diff --git a/include/tvm/build_module.h b/include/tvm/build_module.h
index 391901730e57..d4186e8f8167 100644
--- a/include/tvm/build_module.h
+++ b/include/tvm/build_module.h
@@ -85,10 +85,13 @@ EXPORT Target stackvm();
 
 }  // namespace target
 
+class BuildConfig;
+
 /*!
 * \brief Container for build configuration options
 */
-struct BuildConfig {
+class BuildConfigNode : public Node {
+ public:
   /*!
    * \brief The data alignment to use when constructing buffers. If this is set to
    * -1, then TVM's internal default will be used
@@ -126,10 +129,31 @@ struct BuildConfig {
   /*! \brief Whether to partition const loop */
   bool partition_const_loop = false;
 
-  BuildConfig() {
+  void VisitAttrs(AttrVisitor* v) final {
+    v->Visit("data_alignment", &data_alignment);
+    v->Visit("offset_factor", &offset_factor);
+    v->Visit("double_buffer_split_loop", &double_buffer_split_loop);
+    v->Visit("auto_unroll_max_step", &auto_unroll_max_step);
+    v->Visit("auto_unroll_max_depth", &auto_unroll_max_depth);
+    v->Visit("auto_unroll_max_extent", &auto_unroll_max_extent);
+    v->Visit("unroll_explicit", &unroll_explicit);
+    v->Visit("restricted_func", &restricted_func);
+    v->Visit("detect_global_barrier", &detect_global_barrier);
+    v->Visit("partition_const_loop", &partition_const_loop);
   }
+
+  static constexpr const char* _type_key = "BuildConfig";
+  TVM_DECLARE_NODE_TYPE_INFO(BuildConfigNode, Node);
 };
 
+TVM_DEFINE_NODE_REF(BuildConfig, BuildConfigNode);
+
+/*!
+* \brief Construct a BuildConfig containing a new BuildConfigNode
+* \return The new BuildConfig
+*/
+EXPORT BuildConfig build_config();
+
 /*!
 * \brief Build a LoweredFunc given a schedule, args and binds
 * \param sch The schedule to lower.
diff --git a/python/tvm/build_module.py b/python/tvm/build_module.py
index 8b52b11d86e7..9c442a07425d 100755
--- a/python/tvm/build_module.py
+++ b/python/tvm/build_module.py
@@ -6,7 +6,9 @@
 from __future__ import absolute_import as _abs
 import warnings
 import types
+import os
 
+from ._ffi.node import NodeBase, register_node
 from . import api
 from . import tensor
 from . import schedule
@@ -18,6 +20,7 @@
 from . import codegen
 from . import ndarray
 from . import target as _target
+from . import make
 
 class DumpIR(object):
     """Dump IR for each pass.
@@ -95,16 +98,23 @@ def exit(self):
         BuildConfig.current.add_lower_pass = self._old_custom_pass
         DumpIR.scope_level -= 1
 
-class BuildConfig(object):
+@register_node
+class BuildConfig(NodeBase):
     """Configuration scope to set a build config option.
 
-    Parameters
-    ----------
-    kwargs
-        Keyword arguments of configurations to set.
+    Note
+    ----
+    This object is backed by node system in C++, with arguments that can be
+    exchanged between python and C++.
+
+    Do not construct directly, use build_config instead.
+
+    The fields that are backed by the C++ node are immutable once an instance
+    is constructed. See _node_defaults for the fields.
     """
+
     current = None
-    defaults = {
+    _node_defaults = {
         "auto_unroll_max_step": 0,
         "auto_unroll_max_depth": 8,
         "auto_unroll_max_extent": 0,
@@ -114,30 +124,28 @@ class BuildConfig(object):
         "offset_factor": 0,
         "data_alignment": -1,
         "restricted_func": True,
-        "double_buffer_split_loop": 1,
-        "add_lower_pass": None,
-        "dump_pass_ir": False
+        "double_buffer_split_loop": 1
     }
-    def __init__(self, **kwargs):
+
+    # pylint: disable=no-member
+    def __init__(self, handle):
+        """Initialize the function with handle
+
+        Parameters
+        ----------
+        handle : SymbolHandle
+            the handle to the underlying C++ Symbol
+        """
+        super(BuildConfig, self).__init__(handle)
+        self.handle = handle
         self._old_scope = None
         self._dump_ir = DumpIR()
-        for k, _ in kwargs.items():
-            if k not in BuildConfig.defaults:
-                raise ValueError(
-                    "invalid argument %s, candidates are %s" % (k, BuildConfig.defaults.keys()))
-        self._attr = kwargs
-
-    def __getattr__(self, name):
-        if name not in self._attr:
-            return BuildConfig.defaults[name]
-        return self._attr[name]
+        self.dump_pass_ir = False
+        self.add_lower_pass = None
 
     def __enter__(self):
         # pylint: disable=protected-access
         self._old_scope = BuildConfig.current
-        attr = BuildConfig.current._attr.copy()
-        attr.update(self._attr)
-        self._attr = attr
         BuildConfig.current = self
         if self.dump_pass_ir is True:
             self._dump_ir.enter()
@@ -149,8 +157,11 @@ def __exit__(self, ptype, value, trace):
             self._dump_ir.exit()
         BuildConfig.current = self._old_scope
 
-
-BuildConfig.current = BuildConfig()
+    def __setattr__(self, name, value):
+        if name in BuildConfig._node_defaults:
+            raise AttributeError(
+                "'%s' object cannot set attribute '%s'" % (str(type(self)), name))
+        return super(BuildConfig, self).__setattr__(name, value)
 
 def build_config(**kwargs):
     """Configure the build behavior by setting config variables.
@@ -206,8 +217,18 @@ def build_config(**kwargs):
     config: BuildConfig
         The build configuration
     """
-    return BuildConfig(**kwargs)
-
+    node_args = {k: v if k not in kwargs else kwargs[k]
+                 for k, v in BuildConfig._node_defaults.items()}
+    config = make.node("BuildConfig", **node_args)
+
+    for k in kwargs:
+        if not k in node_args:
+            setattr(config, k, kwargs[k])
+    return config
+
+if not os.environ.get("TVM_USE_RUNTIME_LIB", False):
+    # BuildConfig is not available in tvm_runtime
+    BuildConfig.current = build_config()
 
 def get_binds(args, binds=None):
     """Internal function to get binds and arg_list given arguments.
diff --git a/src/api/api_lang.cc b/src/api/api_lang.cc
index 3b5916ea5fec..de388cf0b51f 100644
--- a/src/api/api_lang.cc
+++ b/src/api/api_lang.cc
@@ -10,6 +10,7 @@
 #include <tvm/buffer.h>
 #include <tvm/schedule.h>
 #include <tvm/api_registry.h>
+#include <tvm/build_module.h>
 
 namespace tvm {
 
diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc
index 2e8e5bb278eb..cca09a966e21 100644
--- a/src/codegen/build_module.cc
+++ b/src/codegen/build_module.cc
@@ -179,7 +179,7 @@ void GetBinds(const Array<Tensor>& args,
   for (const auto &x : args) {
     if (out_binds->find(x) == out_binds->end()) {
       auto buf = BufferWithOffsetAlignment(x->shape, x->dtype, x->op->name,
-        config.data_alignment, config.offset_factor);
+        config->data_alignment, config->offset_factor);
       out_binds->Set(x, buf);
       out_arg_list->push_back(buf);
     } else {
@@ -218,14 +218,14 @@ Stmt BuildStmt(Schedule sch,
   stmt = ir::StorageFlatten(stmt, out_binds, 64);
   stmt = ir::CanonicalSimplify(stmt);
   if (loop_partition) {
-    stmt = ir::LoopPartition(stmt, config.partition_const_loop);
+    stmt = ir::LoopPartition(stmt, config->partition_const_loop);
   }
   stmt = ir::VectorizeLoop(stmt);
   stmt = ir::InjectVirtualThread(stmt);
-  stmt = ir::InjectDoubleBuffer(stmt, config.double_buffer_split_loop);
+  stmt = ir::InjectDoubleBuffer(stmt, config->double_buffer_split_loop);
   stmt = ir::StorageRewrite(stmt);
-  stmt = ir::UnrollLoop(stmt, config.auto_unroll_max_step, config.auto_unroll_max_depth,
-    config.auto_unroll_max_extent, config.unroll_explicit);
+  stmt = ir::UnrollLoop(stmt, config->auto_unroll_max_step, config->auto_unroll_max_depth,
+    config->auto_unroll_max_extent, config->unroll_explicit);
 
   // Phase 2
   stmt = ir::Simplify(stmt);
@@ -243,7 +243,7 @@ Array<LoweredFunc> lower(Schedule sch,
                          const BuildConfig& config) {
   Array<NodeRef> out_arg_list;
   auto stmt = BuildStmt(sch, args, binds, true, &out_arg_list, config);
-  return Array<LoweredFunc>({ ir::MakeAPI(stmt, name, out_arg_list, 0, config.restricted_func) });
+  return Array<LoweredFunc>({ ir::MakeAPI(stmt, name, out_arg_list, 0, config->restricted_func) });
 }
 
 runtime::Module build(const Array<LoweredFunc>& funcs,
@@ -266,7 +266,7 @@ runtime::Module build(const Array<LoweredFunc>& funcs,
   for (const auto &x : funcs) {
     if (x->func_type == kMixedFunc) {
       auto func = x;
-      if (config.detect_global_barrier) {
+      if (config->detect_global_barrier) {
         func = ir::ThreadSync(func, "global");
       }
 
@@ -321,4 +321,27 @@ runtime::Module build(const Array<LoweredFunc>& funcs,
 
   return mhost;
 }
+
+BuildConfig build_config() {
+  return BuildConfig(std::make_shared<BuildConfigNode>());
+}
+
+TVM_REGISTER_NODE_TYPE(BuildConfigNode);
+
+TVM_STATIC_IR_FUNCTOR(IRPrinter, vtable)
+.set_dispatch<BuildConfigNode>([](const BuildConfigNode *op, IRPrinter *p) {
+  p->stream << "build_config(";
+  p->stream << "data_alignment=" << op->data_alignment << ", ";
+  p->stream << "offset_factor=" << op->offset_factor << ", ";
+  p->stream << "double_buffer_split_loop=" << op->double_buffer_split_loop << ", ";
+  p->stream << "auto_unroll_max_step=" << op->auto_unroll_max_step << ", ";
+  p->stream << "auto_unroll_max_depth=" << op->auto_unroll_max_depth << ", ";
+  p->stream << "auto_unroll_max_extent=" << op->auto_unroll_max_extent << ", ";
+  p->stream << "unroll_explicit=" << op->unroll_explicit << ", ";
+  p->stream << "restricted_func=" << op->restricted_func << ", ";
+  p->stream << "detect_global_barrier=" << op->detect_global_barrier << ", ";
+  p->stream << "partition_const_loop=" << op->partition_const_loop;
+  p->stream << ")";
+});
+
 }  // namespace tvm
diff --git a/tests/cpp/build_module_test.cc b/tests/cpp/build_module_test.cc
index fc3f6ac9324d..fe0a9151cc2c 100644
--- a/tests/cpp/build_module_test.cc
+++ b/tests/cpp/build_module_test.cc
@@ -27,7 +27,7 @@ TEST(BuildModule, Basic) {
   auto args = Array<Tensor>({ A, B, C });
   std::unordered_map<Tensor, Buffer> binds;
 
-  BuildConfig config;
+  auto config = build_config();
   auto target = target::llvm();
 
   auto lowered = lower(s, args, "func", binds, config);

From 71f1b2d76b3d30924adeb67a0221d72977946e08 Mon Sep 17 00:00:00 2001
From: Siju Samuel <sijusamuel@gmail.com>
Date: Mon, 12 Feb 2018 11:26:08 +0530
Subject: [PATCH 150/949] [Documentation Changes] Parameter description change
 for reduction apis (#881)

* [Documentation Changes] Parameter description change for reduction api

The parameter description is updated for max, min, argmax and argmin

* Lint changes on patch-3 of reduction.py
---
 topi/python/topi/reduction.py | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/topi/python/topi/reduction.py b/topi/python/topi/reduction.py
index 997ec8e9ba95..9f88953bb770 100644
--- a/topi/python/topi/reduction.py
+++ b/topi/python/topi/reduction.py
@@ -186,9 +186,9 @@ def max(data, axis=None, keepdims=False):
         The input tvm tensor
 
     axis : None or int or tuple of int
-        Axis or axes along which a sum is performed.
-        The default, axis=None, will sum all of the elements of the input array.
-        If axis is negative it counts from the last to the first axis.
+        Axis or axes along which the max operation is performed.
+        The default, axis=None, will find the max element from all of the elements of the input
+        array. If axis is negative it counts from the last to the first axis.
 
     keepdims : bool
         If this is set to True, the axes which are reduced are left in the result as dimensions
@@ -212,9 +212,9 @@ def min(data, axis=None, keepdims=False):
         The input tvm tensor
 
     axis : None or int or tuple of int
-        Axis or axes along which a sum is performed.
-        The default, axis=None, will sum all of the elements of the input array.
-        If axis is negative it counts from the last to the first axis.
+        Axis or axes along which a minimum operation is performed.
+        The default, axis=None, will find the minimum element from all of the elements of the
+        input array. If axis is negative it counts from the last to the first axis.
 
     keepdims : bool
         If this is set to True, the axes which are reduced are left in the result as dimensions
@@ -238,9 +238,9 @@ def argmax(data, axis=None, keepdims=False):
         The input tvm tensor
 
     axis : None or int or tuple of int
-        Axis or axes along which a sum is performed.
-        The default, axis=None, will sum all of the elements of the input array.
-        If axis is negative it counts from the last to the first axis.
+        Axis or axes along which a argmax operation is performed.
+        The default, axis=None, will find the indices of the maximum element of the elements of
+        the input array. If axis is negative it counts from the last to the first axis.
 
     keepdims : bool
         If this is set to True, the axes which are reduced are left in the result as dimensions
@@ -265,9 +265,9 @@ def argmin(data, axis=None, keepdims=False):
         The input tvm tensor
 
     axis : None or int or tuple of int
-        Axis or axes along which a sum is performed.
-        The default, axis=None, will sum all of the elements of the input array.
-        If axis is negative it counts from the last to the first axis.
+        Axis or axes along which a argmin operation is performed.
+        The default, axis=None, will find the indices of minimum element all of the elements of
+        the input array. If axis is negative it counts from the last to the first axis.
 
     keepdims : bool
         If this is set to True, the axes which are reduced are left in the result as dimensions

From 6224779dc03ee7b3e36274a1265daff8f5690787 Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Wed, 14 Feb 2018 00:55:23 +0800
Subject: [PATCH 151/949] [TOPI] Add winograd for mali (#898)

* add winograd for mali

* fix lint

* add padding

* fix comment
---
 topi/python/topi/mali/conv2d.py | 225 ++++++++++++++++++++++++++++++--
 1 file changed, 212 insertions(+), 13 deletions(-)

diff --git a/topi/python/topi/mali/conv2d.py b/topi/python/topi/mali/conv2d.py
index ff67e0503f4f..5b4cf5bae6ff 100644
--- a/topi/python/topi/mali/conv2d.py
+++ b/topi/python/topi/mali/conv2d.py
@@ -2,6 +2,8 @@
 """conv2d schedule on ARM Mali GPU"""
 
 from __future__ import absolute_import as _abs
+
+import numpy as np
 import tvm
 
 from .. import generic
@@ -63,7 +65,23 @@ def transpose(s, tensor, readers):
     s[tmp].compute_inline()
     return s.cache_write(tmp, "global"), tmp
 
-@conv2d.register("mali")
+def const_array(data, name):
+    """ convert an const array to tvm tensor"""
+    row, col = data.shape
+    dtype = str(data.dtype)
+
+    def select_array(i, j):
+        now = tvm.const(0.0, dtype)
+        for ii in range(row):
+            for jj in range(col):
+                now = tvm.select(tvm.all(i % row == ii, j % col == jj),
+                                 tvm.const(data[ii][jj], dtype),
+                                 now)
+        return now
+    return tvm.compute(data.shape, select_array, name=name)
+
+
+@conv2d.register(["mali"])
 def decl_conv2d(data, kernel, stride, padding, layout='NCHW', out_dtype='float32'):
     """Conv2D operator for ARM Mali GPU backend.
 
@@ -94,10 +112,20 @@ def decl_conv2d(data, kernel, stride, padding, layout='NCHW', out_dtype='float32
     assert data.dtype == kernel.dtype, "Do not support inputs with different data types now."
 
     out_dtype = data.dtype
-    if util.get_const_int(kernel.shape[2]) == 1:
+    HPAD, WPAD, _, _ = get_pad_tuple(padding, kernel)
+    kernel_shape = util.get_const_tuple(kernel.shape)
+    if isinstance(stride, (tuple, list)):
+        HSTR, WSTR = stride
+    else:
+        HSTR, WSTR = stride, stride
+
+    if (kernel_shape[2:4] == (3, 3) and (HPAD, WPAD) == (1, 1) and kernel_shape[0] >= 64 and
+            (HSTR, WSTR) == (1, 1)):
+        return _decl_winograd(data, kernel, stride, padding, layout, out_dtype)
+    elif kernel_shape[2:4] == (1, 1):
         return _decl_im2col(data, kernel, stride, padding, layout, out_dtype)
     else:
-        return _decl_direct(data, kernel, stride, padding, layout, out_dtype)
+        return _decl_spatialpack(data, kernel, stride, padding, layout, out_dtype)
 
 @generic.schedule_conv2d_nchw.register(["mali"])
 def schedule_conv2d_nchw(outs):
@@ -129,14 +157,17 @@ def traverse(op):
         if 'im2col_conv_output' in op.tag:
             _schedule_im2col_conv2d(s, op)
 
-        if 'direct_conv_output' in op.tag:
-            _schedule_direct_conv2d(s, op)
+        if 'spatialpack_conv_output' in op.tag:
+            _schedule_spatialpack_conv2d(s, op)
+
+        if 'winograd_conv_output' in op.tag:
+            _schedule_winograd(s, op)
 
     traverse(outs[0].op)
     return s
 
-def _decl_direct(data, kernel, stride, padding, layout, out_dtype):
-    """declare the direct method (spatial packing) for conv2d"""
+def _decl_spatialpack(data, kernel, stride, padding, layout, out_dtype):
+    """declare the spatialpack method (spatial packing) for conv2d"""
     _, CI, IH, IW = [util.get_const_int(x) for x in data.shape]
     CO, _, KH, KW = [util.get_const_int(x) for x in kernel.shape]
     HPAD, WPAD, _, _ = get_pad_tuple(padding, kernel)
@@ -207,12 +238,12 @@ def _decl_direct(data, kernel, stride, padding, layout, out_dtype):
 
     output = tvm.compute(oshape, lambda n, co, h, w:
                          conv[n][co//VC][h/VH][w//VW][h%VH][w%VW][co%VC],
-                         name='output_unpack', tag='direct_conv_output')
+                         name='output_unpack', tag='spatialpack_conv_output')
 
     return output
 
-def _schedule_direct_conv2d(s, op):
-    """schedule the direct method (spatial packing) for conv2d"""
+def _schedule_spatialpack_conv2d(s, op):
+    """schedule the spatialpack method (spatial packing) for conv2d"""
     # get ops and tensors
     output = op.output(0)
     output_height = util.get_const_int(output.shape[2])
@@ -294,8 +325,6 @@ def _schedule_direct_conv2d(s, op):
     _, co, oh, ow = s[output].op.axis
     tile_and_bind3d(s, output, co, oh, ow, num_thread, 1, last)
 
-    #print(tvm.lower(s, [data, kernel, output], simple_mode=True))
-
 def _decl_im2col(data, kernel, stride, padding, layout='NCHW', out_dtype='float32'):
     """declare the Im2Col method for conv2d"""
     _, CI, IH, IW = [x.value for x in data.shape]
@@ -476,4 +505,174 @@ def _schedule_im2col_conv2d(s, op):
         s[output].vectorize(vw)
     fuse_and_bind(s, output, [n, co, h, w])
 
-    #print(tvm.lower(s, [data, kernel], simple_mode=True))
+def _decl_winograd(data, kernel, stride, padding, layout, out_dtype):
+    """declare winograd fast convolution F(2x2, 3x3) for conv2d"""
+    N, CI, H, W = [util.get_const_int(x) for x in data.shape]
+    CO, CI, KH, KW = [util.get_const_int(x) for x in kernel.shape]
+    HPAD, WPAD, _, _ = get_pad_tuple(padding, kernel)
+    if isinstance(stride, (tuple, list)):
+        HSTR, WSTR = stride
+    else:
+        HSTR, WSTR = stride, stride
+
+    assert HSTR == 1 and WSTR == 1 and HPAD == 1 and WPAD == 1 and KH == 3 and KW == 3
+    data_pad = pad(data, (0, 0, HPAD, WPAD), name="data_pad")
+
+    B_data = np.array([
+        [1, 0, 0, 0],
+        [0, 1, -1, 1],
+        [-1, 1, 1, 0],
+        [0, 0, 0, -1]
+    ], out_dtype)
+
+    G_data = np.array([
+        [1, 0, 0],
+        [1.0/2, 1.0/2, 1.0/2],
+        [1.0/2, -1.0/2, 1.0/2],
+        [0, 0, 1],
+    ], out_dtype)
+
+    A_data = np.array([
+        [1, 0],
+        [1, 1],
+        [1, -1],
+        [0, -1],
+    ], out_dtype)
+
+    m = 2
+    r = 3
+    alpha = m + r - 1
+    K = CO
+    C = CI
+
+    nH, nW = (H + m-1) // m, (W + m-1) // m
+    P = N * nH * nW
+
+    bna, bnb = 4, 4
+    if data.dtype == 'float16':
+        bnb *= 2
+    P_round = (P + bnb - 1) // bnb * bnb
+    assert K % bna == 0 and P_round % bnb == 0
+
+    # pack input tile
+    input_tile = tvm.compute((C, P_round // bnb, alpha, alpha, bnb),
+                             lambda c, b, eps, nu, bb:
+                             tvm.select(b * bnb + bb < P,\
+                             data_pad[(b*bnb+bb) // (nH*nW)][c][(b*bnb+bb) // nW % nH * m + eps]\
+                             [(b*bnb+bb) % nW * m + nu], tvm.const(0, data_pad.dtype)),
+                             name='d')
+
+    # transform kernel
+    G = const_array(G_data, 'G')
+    r_kh = tvm.reduce_axis((0, KH), 'r_kh')
+    r_kw = tvm.reduce_axis((0, KW), 'r_kw')
+    U = tvm.compute((alpha, alpha, K // bna, C, bna), lambda eps, nu, k, c, kk:
+                    tvm.sum(kernel[k * bna + kk][c][r_kh][r_kw] * G[eps][r_kh] * G[nu][r_kw],
+                            axis=[r_kh, r_kw]), name='U')
+
+    # transform image
+    B = const_array(B_data, 'B')
+    r_eps = tvm.reduce_axis((0, alpha), 'r_eps')
+    r_nu = tvm.reduce_axis((0, alpha), 'r_nu')
+    V = tvm.compute((alpha, alpha, P_round // bnb, C, bnb), lambda eps, nu, b, c, bb:
+                    tvm.sum(input_tile[c][b][r_eps][r_nu][bb] * B[r_eps][eps] * B[r_nu][nu],
+                            axis=[r_eps, r_nu]), name='V')
+
+    # batch gemm
+    c = tvm.reduce_axis((0, C), name='c')
+    M = tvm.compute((alpha, alpha, K, P_round), lambda eps, nu, k, b:
+                    tvm.sum(U[eps][nu][k // bna][c][k % bna] *
+                            V[eps][nu][b // bnb][c][b % bnb], axis=c), name='M')
+
+    # inverse transform
+    A = const_array(A_data, 'A')
+    r_eps = tvm.reduce_axis((0, alpha), 'r_eps')
+    r_nu = tvm.reduce_axis((0, alpha), 'r_nu')
+    Y = tvm.compute((K, P, m, m), lambda k, b, vh, vw:
+                    tvm.sum(M[r_eps][r_nu][k][b] * A[r_eps][vh] * A[r_nu][vw],
+                            axis=[r_eps, r_nu]), name='Y')
+
+    # unpack output
+    output = tvm.compute((N, K, H, W), lambda n, k, h, w:
+                         Y[k][n * nH * nW + (h//m) * nW + w//m][h % m][w % m]
+                         # thw following term is used to make the padding effective,
+                         # otherwise the padding will be eliminated by bound inference
+                         + tvm.const(0, out_dtype) * M[alpha-1][alpha-1][K-1][P_round-1],
+                         name='output', tag='winograd_conv_output')
+
+    return output
+
+def _schedule_winograd(s, op):
+    """schedule winograd fast convolution F(2x2, 3x3) for conv2d"""
+
+    # get ops and tensors
+    output = op.output(0)
+
+    Y = op.input_tensors[0]
+    M, A = s[Y].op.input_tensors
+    U, V = s[M].op.input_tensors
+    kernel, G = s[U].op.input_tensors
+    d, B = s[V].op.input_tensors
+    data_pad = s[d].op.input_tensors[0]
+    data = s[data_pad].op.input_tensors[0]
+
+    # padding
+    s[data_pad].compute_inline()
+
+    # pack input tiles
+    c, b, eps, nu, bb = s[d].op.axis
+    s[d].reorder(eps, nu, bb)
+    aha = s[d].fuse(eps, nu)
+    s[d].unroll(bb)
+    tile_and_bind3d(s, d, c, b, aha, 4, 1, 1)
+
+    # transform kernel
+    s[G].compute_inline()
+    eps, nu, k, c, kk, = s[U].op.axis
+    r_kh, r_kw = s[U].op.reduce_axis
+    s[U].reorder(k, c, kk, eps, nu, r_kh, r_kw)
+    _ = [s[U].unroll(x) for x in [eps, nu, r_kh, r_kw]]
+    s[U].vectorize(kk)
+    tile_and_bind(s, U, k, c, 1, 256)
+
+    # transform image
+    s[B].compute_inline()
+    eps, nu, b, c, bb = s[V].op.axis
+    r_eps, r_nu = s[V].op.reduce_axis
+    s[V].reorder(b, c, bb, eps, nu, r_nu, r_eps)
+    _ = [s[V].unroll(x) for x in [eps, nu, r_eps, r_nu]]
+    s[V].vectorize(bb)
+    tile_and_bind(s, V, b, c, 2, 1)
+
+    # batch gemm
+    bna, bnb = 4, 4
+    if data.dtype == 'float16':
+        bnb *= 2
+
+    eps, nu, k, b = s[M].op.axis
+    c = s[M].op.reduce_axis[0]
+    yo, xo, yi, xi = s[M].tile(k, b, bna, bnb)
+    s[M].reorder(c, yi, xi)
+    c, c_unroll = s[M].split(c, 2)
+    s[M].unroll(c_unroll)
+    s[M].unroll(yi)
+    s[M].vectorize(xi)
+    z = s[M].fuse(eps, nu)
+    tile_and_bind3d(s, M, z, yo, xo, 1, 8, 1)
+
+    # inverse transform
+    s[A].compute_inline()
+    k, b, vh, vw = s[Y].op.axis
+    r_eps, r_nu = s[Y].op.reduce_axis
+    _ = [s[Y].unroll(x) for x in [vh, vw, r_eps, r_nu]]
+    tile_and_bind(s, Y, k, b, 4, 1)
+
+    # schedule output
+    if output.op in s.outputs:  # no bias
+        output = output
+    else:                       # has bias
+        s[output].compute_inline()
+        output = s.outputs[0]
+
+    _, k, h, w = s[output].op.axis
+    tile_and_bind3d(s, output, k, h, w, 1, 2, 2)

From 1ae02f1e03510b742383fa70eeb72fdca8eaf397 Mon Sep 17 00:00:00 2001
From: Siva <sivar.b@huawei.com>
Date: Tue, 13 Feb 2018 22:32:21 +0530
Subject: [PATCH 152/949] [Graph Debug] bug fix (#897)

Need to break after executing intended operation (not before).
---
 src/runtime/graph/graph_runtime.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/runtime/graph/graph_runtime.cc b/src/runtime/graph/graph_runtime.cc
index bf07a8c38927..0c642577da59 100644
--- a/src/runtime/graph/graph_runtime.cc
+++ b/src/runtime/graph/graph_runtime.cc
@@ -138,8 +138,8 @@ class GraphRuntime : public ModuleNode {
     uint32_t eid = index;
 
     for (size_t i = 0; i < op_execs_.size(); ++i) {
-      if (static_cast<int>(i) == index) break;
       if (op_execs_[i]) op_execs_[i]();
+      if (static_cast<int>(i) == index) break;
     }
 
     TVM_CCALL(TVMArrayCopyFromTo(&data_entry_[eid], data_out, nullptr));

From 4818cb2b1a829673e89bf4937215aa3f06018e54 Mon Sep 17 00:00:00 2001
From: Zhixun Tan <phisiart@gmail.com>
Date: Thu, 15 Feb 2018 12:44:52 -0500
Subject: [PATCH 153/949] Change CodeGenCPU::GetPackedFuncHandle to generate
 global variable with InternalLinkage. (#901)

Emscripten seems to not have done initialization properly.
---
 src/codegen/llvm/codegen_cpu.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/codegen/llvm/codegen_cpu.cc b/src/codegen/llvm/codegen_cpu.cc
index 876d1e12d8a2..54aa50427413 100644
--- a/src/codegen/llvm/codegen_cpu.cc
+++ b/src/codegen/llvm/codegen_cpu.cc
@@ -483,7 +483,7 @@ llvm::Value* CodeGenCPU::GetPackedFuncHandle(const std::string& fname) {
     // create the function handle
     hptr = new llvm::GlobalVariable(
         *module_, t_tvm_func_handle_, false,
-        llvm::GlobalValue::LinkOnceAnyLinkage, 0, ".tvm_func." + fname);
+        llvm::GlobalValue::InternalLinkage, nullptr, ".tvm_func." + fname);
     hptr->setAlignment(align);
     hptr->setInitializer(llvm::Constant::getNullValue(t_tvm_func_handle_));
     func_handle_map_[fname] = hptr;

From d419aaf4039b098fc786a9377b8239ce1e2ef478 Mon Sep 17 00:00:00 2001
From: Yao Wang <kevinthesunwy@gmail.com>
Date: Thu, 15 Feb 2018 10:03:11 -0800
Subject: [PATCH 154/949] Add dense base scheduler (#887)

* Add basic dense scheduler

* Revert to put back cpp dense registration

* Fix lint
---
 topi/python/topi/x86/nn.py | 60 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 60 insertions(+)

diff --git a/topi/python/topi/x86/nn.py b/topi/python/topi/x86/nn.py
index 49aa382589d1..4c886b48d9c2 100644
--- a/topi/python/topi/x86/nn.py
+++ b/topi/python/topi/x86/nn.py
@@ -1,7 +1,9 @@
+# pylint: disable=invalid-name,too-many-locals
 """x86 nn operators"""
 from __future__ import absolute_import as _abs
 import tvm
 from .. import generic
+from .. import tag
 
 def _default_schedule(outs, auto_inline):
     """Default schedule for x86."""
@@ -54,3 +56,61 @@ def schedule_pool(outs):
         The computation schedule for the op.
     """
     return _default_schedule(outs, False)
+
+
+@generic.schedule_dense.register(["cpu"])
+def schedule_dense(outs):
+    """Schedule for dense
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+          The computation graph description of pool
+          in the format of an array of tensors.
+
+    Returns
+    -------
+    sch: Schedule
+        The computation schedule for the op.
+    """
+
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    s = tvm.create_schedule([x.op for x in outs])
+
+    def traverse(op):
+        """Traverse operators from computation graph"""
+        # inline all one-to-one-mapping operators except the last stage (output)
+        if tag.is_broadcast(op.tag):
+            if op not in s.outputs:
+                s[op].compute_inline()
+            for tensor in op.input_tensors:
+                if tensor.op.input_tensors:
+                    traverse(tensor.op)
+
+        if 'dense' in op.tag:
+            C = op.output(0)
+            x, y = C.op.axis
+
+            # Write cache for blocks
+            CC = s.cache_write(C, 'global')
+
+            # Tile
+            bnx = 1
+            bny = 4
+            _, yo, _, yi = s[C].tile(x, y, bnx, bny)
+            s[CC].compute_at(s[C], yo)
+            xc, yc = s[CC].op.axis
+            k, = s[CC].op.reduce_axis
+            ko, ki = s[CC].split(k, factor=4)
+            s[CC].reorder(ko, xc, ki, yc)
+            s[CC].unroll(ki)
+            s[CC].vectorize(yc)
+
+            # Vectorization
+            s[C].vectorize(yi)
+
+            # Parallelization
+            s[C].parallel(yo)
+
+    traverse(outs[0].op)
+    return s

From 4b8e1c21e3ca1949f30585c23a968f026d2b9f1f Mon Sep 17 00:00:00 2001
From: Zhixun Tan <phisiart@gmail.com>
Date: Sun, 18 Feb 2018 11:45:44 -0500
Subject: [PATCH 155/949] Set total memory of emcc module to 1GB (#906)

---
 Makefile | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Makefile b/Makefile
index 7ed46fd14b05..9e15d60e8c9b 100644
--- a/Makefile
+++ b/Makefile
@@ -32,6 +32,7 @@ FRAMEWORKS =
 OBJCFLAGS = -fno-objc-arc
 EMCC_FLAGS= -std=c++11 -DDMLC_LOG_STACK_TRACE=0\
 	-Oz -s RESERVED_FUNCTION_POINTERS=2 -s MAIN_MODULE=1 -s NO_EXIT_RUNTIME=1\
+	-s TOTAL_MEMORY=1073741824\
 	-s EXTRA_EXPORTED_RUNTIME_METHODS="['cwrap','getValue','setValue','addFunction']"\
 	-s USE_GLFW=3 -s USE_WEBGL2=1 -lglfw\
 	$(INCLUDE_FLAGS)

From b24f3104d983929cf299c093b6b0782ea0e98dca Mon Sep 17 00:00:00 2001
From: masahi <masahi129@gmail.com>
Date: Mon, 19 Feb 2018 03:21:47 +0900
Subject: [PATCH 156/949] [TOPI] update c++ pool and softmax (#905)

* update c++ pool and softmax

* clean up reduce axis
---
 topi/include/topi/nn/pooling.h             | 130 ++++++++++++++++++++-
 topi/include/topi/nn/softmax.h             |  71 ++++++++---
 topi/src/topi.cc                           |   4 +-
 topi/tests/python_cpp/test_topi_pooling.py |   2 +-
 topi/tests/python_cpp/test_topi_softmax.py |   2 +-
 5 files changed, 182 insertions(+), 27 deletions(-)

diff --git a/topi/include/topi/nn/pooling.h b/topi/include/topi/nn/pooling.h
index 4333f2749573..5d68510c3842 100644
--- a/topi/include/topi/nn/pooling.h
+++ b/topi/include/topi/nn/pooling.h
@@ -36,12 +36,13 @@ enum PoolType : int {
 *
 * \return The output tensor in NCHW order
 */
-inline Tensor pool(const Tensor& x,
-                   const Array<Expr>& kernel_size,
-                   const Array<Expr>& stride_size,
-                   const Array<Expr>& padding_size,
-                   PoolType pool_type,
-                   bool ceil_mode) {
+
+inline Tensor pool_nchw(const Tensor& x,
+                        const Array<Expr>& kernel_size,
+                        const Array<Expr>& stride_size,
+                        const Array<Expr>& padding_size,
+                        PoolType pool_type,
+                        bool ceil_mode) {
   CHECK_EQ(x->shape.size(), 4) << "Pooling input must be 4-D";
   CHECK_EQ(kernel_size.size(), 2) << "Pooling kernel_size must have 2 elements";
   CHECK_EQ(stride_size.size(), 2) << "Pooling stride_size must have 2 elements";
@@ -112,6 +113,123 @@ inline Tensor pool(const Tensor& x,
   }
 }
 
+/*!
+* \brief Perform pooling on data in NHWC order
+*
+* \param x The input tensor in NHWC order
+* \param kernel_size Vector of two ints: {kernel_height, kernel_width}
+* \param stride_size Vector of two ints: {stride_height, stride_width}
+* \param padding_size Vector of two ints: {padding_height, padding_width}
+* \param pool_type The type of pooling operator
+* \param ceil_mode Whether to use ceil when calculating the output size
+*
+* \return The output tensor in NCHW order
+*/
+
+inline Tensor pool_nhwc(const Tensor& x,
+                        const Array<Expr>& kernel_size,
+                        const Array<Expr>& stride_size,
+                        const Array<Expr>& padding_size,
+                        PoolType pool_type,
+                        bool ceil_mode) {
+  CHECK_EQ(x->shape.size(), 4) << "Pooling input must be 4-D";
+  CHECK_EQ(kernel_size.size(), 2) << "Pooling kernel_size must have 2 elements";
+  CHECK_EQ(stride_size.size(), 2) << "Pooling stride_size must have 2 elements";
+  CHECK_EQ(padding_size.size(), 2) << "Pooling padding_size must have 2 elements";
+
+  auto kernel_height = kernel_size[0];
+  auto kernel_width = kernel_size[1];
+  auto stride_height = stride_size[0];
+  auto stride_width = stride_size[1];
+  auto padding_height = padding_size[0];
+  auto padding_width = padding_size[1];
+
+  auto batch = x->shape[0];
+  auto height = x->shape[1];
+  auto width = x->shape[2];
+  auto channel = x->shape[3];
+
+  auto pad_tuple = detail::GetPadTuple(padding_height, padding_width);
+  auto pad_top = pad_tuple[0];
+  auto pad_left = pad_tuple[1];
+  auto pad_down = pad_tuple[2];
+  auto pad_right = pad_tuple[3];
+
+  if (ceil_mode) {
+    // Additional padding to ensure we do ceil instead of floor when
+    // dividing by stride.
+    pad_down += stride_height - 1;
+    pad_right += stride_width - 1;
+  }
+
+  Array<Expr> pad_before{ 0, pad_top, pad_left, 0};
+  Array<Expr> pad_after{ 0, pad_down, pad_right, 0};
+
+  auto out_height = tvm::ir::Simplify(
+    (height - kernel_height + pad_top + pad_down) / stride_height + 1);
+  auto out_width = tvm::ir::Simplify(
+    (width - kernel_width + pad_left + pad_right) / stride_width + 1);
+
+  auto dheight = tvm::reduce_axis(Range(0, kernel_height));
+  auto dwidth = tvm::reduce_axis(Range(0, kernel_width));
+
+  if (pool_type == kMaxPool) {
+    auto temp = pad(x, pad_before, pad_after, x->dtype.min(), "pad_temp");
+    return tvm::compute(
+     { batch, out_height, out_width, channel },
+      [&](Var n, Var h, Var w, Var c) {
+        return tvm::max(temp(n, h * stride_height + dheight, w * stride_width + dwidth, c),
+        { dheight, dwidth });
+      }, "tensor", "pool_max");
+  } else if (pool_type == kAvgPool) {
+    auto temp = pad(x, pad_before, pad_after, 0, "pad_temp");
+
+    auto tsum = tvm::compute(
+     { batch, out_height, out_width, channel },
+      [&](Var n, Var h, Var w, Var c) {
+        return tvm::sum(temp(n, h * stride_height + dheight, w * stride_width + dwidth, c),
+        { dheight, dwidth });
+      }, "tensor", "pool_avg");
+
+    return tvm::compute(
+     { batch, out_height, out_width, channel },
+     [&](Var n, Var h, Var w, Var c) {
+       return tsum(n, h, w, c) / (kernel_height * kernel_width);
+      }, "tensor", kElementWise);
+  } else {
+    LOG(ERROR) << "Unrecognized pool_type: " << pool_type;
+    return x;
+  }
+}
+
+/*!
+* \brief Perform pooling on data
+*
+* \param x The input tensor in NCHW or NHWC order
+* \param kernel_size Vector of two ints: {kernel_height, kernel_width}
+* \param stride_size Vector of two ints: {stride_height, stride_width}
+* \param padding_size Vector of two ints: {padding_height, padding_width}
+* \param pool_type The type of pooling operator
+* \param ceil_mode Whether to use ceil when calculating the output size
+* \param layout The input layout
+*
+* \return The output tensor in NCHW order
+*/
+
+inline Tensor pool(const Tensor& x,
+                   const Array<Expr>& kernel_size,
+                   const Array<Expr>& stride_size,
+                   const Array<Expr>& padding_size,
+                   PoolType pool_type,
+                   bool ceil_mode,
+                   const std::string& layout = "NCHW") {
+  CHECK(layout == "NCHW" || layout == "NHWC") << "Unsupported layout.";
+  if (layout == "NCHW")
+    return pool_nchw(x, kernel_size, stride_size, padding_size, pool_type, ceil_mode);
+  else
+    return pool_nhwc(x, kernel_size, stride_size, padding_size, pool_type, ceil_mode);
+}
+
 /*!
 * \brief Perform global pooling on data in NCHW order
 *
diff --git a/topi/include/topi/nn/softmax.h b/topi/include/topi/nn/softmax.h
index 273bac4ff76d..d2348c9f230a 100644
--- a/topi/include/topi/nn/softmax.h
+++ b/topi/include/topi/nn/softmax.h
@@ -9,6 +9,7 @@
 #include <algorithm>
 #include <string>
 
+#include "topi/reduction.h"
 #include "topi/tags.h"
 #include "tvm/tvm.h"
 
@@ -19,34 +20,70 @@ using namespace tvm;
 /*!
 * \brief Softmax activation
 *
-* \param x The input tensor. 2-D where softmax is performed along the second dimension
+* \param x The input tensor. Can be any dimension
+* \param axis The channel axis along which softmax is performed
 * \param name The name of the operation
 * \param tag The tag to mark the operation
 *
 * \return A Tensor whose op member is the softmax operation
 */
-inline Tensor softmax(const Tensor& x,
+inline Tensor softmax(const Tensor &x,
+                      int axis = -1,
                       std::string name = "tensor",
                       std::string tag = "softmax_output") {
-  CHECK_EQ(x->shape.size(), 2) << "Softmax requires 2-D input";
+  auto input_shape = x->shape;
+  auto ndim = input_shape.size();
+  if (axis < 0) {
+    axis = ndim + axis;
+  }
+  CHECK_LT(axis, ndim) << "axis parameter should be less than input dim";
 
-  Expr m = x->shape[0];
-  Expr n = x->shape[1];
+  auto k1 = tvm::reduce_axis(Range(0, input_shape[axis]), "k1");
+  auto k2 = tvm::reduce_axis(Range(0, input_shape[axis]), "k2");
+  auto reduced_shape = MakeReduceTargetShape({axis}, x, false);
 
-  auto k = tvm::reduce_axis(Range(0, n), "k");
-  auto max_elem = tvm::compute(
-    { m }, [&](Var i) {
-      return tvm::max(x(i, k), Array<IterVar>{ k }); });
-  k = tvm::reduce_axis(Range(0, n), "k");
+  auto insert_reduce_index = [axis, ndim](const Array<Var> &indices,
+                                          const IterVar &reduce_index) {
+    Array<Expr> eval_range;
+    int arg_counter = 0;
+    for (size_t i = 0; i < ndim; ++i) {
+      if (i == axis)
+        eval_range.push_back(reduce_index);
+      else
+        eval_range.push_back(indices[arg_counter++]);
+    }
+    return eval_range;
+  };
 
-  auto expsum = tvm::compute(
-    { m }, [&](Var i) {
-      return tvm::sum(tvm::exp(x(i, k) - max_elem(i)), { k }); });
+  auto _compute_max = [&](const Array<Var> &indices) {
+    auto eval_range = insert_reduce_index(indices, k1);
+    return topi::MaxOp(x(eval_range), {k1});
+  };
 
-  return tvm::compute(
-    x->shape, [&](Var i, Var j) {
-      return tvm::exp(x(i, j) - max_elem(i)) / expsum(i);
-    });
+  auto _compute_expsum = [&](const Tensor &max_elem,
+                             const Array<Var> &indices) {
+    auto eval_range = insert_reduce_index(indices, k2);
+    return tvm::sum(tvm::exp(x(eval_range) - max_elem(indices)), {k2});
+  };
+
+  auto _normalize = [&](const Tensor &max_elem, const Tensor &expsum,
+                        const Array<Var> &indices) {
+    Array<Expr> non_reduce_indices;
+    for (size_t i = 0; i < ndim; ++i) {
+      if (i != axis)
+        non_reduce_indices.push_back(indices[i]);
+    }
+    return tvm::exp(x(indices) - max_elem(non_reduce_indices)) /
+           expsum(non_reduce_indices);
+  };
+
+  auto max_elem = tvm::compute(reduced_shape, _compute_max);
+  auto expsum = tvm::compute(reduced_shape, [&](const Array<Var> &indices) {
+      return _compute_expsum(max_elem, indices);
+  });
+  return tvm::compute(input_shape, [&](const Array<Var> &indices) {
+      return _normalize(max_elem, expsum, indices);
+  });
 }
 
 /*!
diff --git a/topi/src/topi.cc b/topi/src/topi.cc
index 970e982276f6..d6b67c74bacc 100644
--- a/topi/src/topi.cc
+++ b/topi/src/topi.cc
@@ -320,7 +320,7 @@ TVM_REGISTER_GLOBAL("topi.nn.pool")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
   *rv = nn::pool(args[0], args[1], args[2], args[3],
                  static_cast<nn::PoolType>(static_cast<int>(args[4])),
-                 args[5]);
+                 args[5], args[6]);
   });
 
 TVM_REGISTER_GLOBAL("topi.nn.global_pool")
@@ -332,7 +332,7 @@ TVM_REGISTER_GLOBAL("topi.nn.global_pool")
 /* Ops from nn/softmax.h */
 TVM_REGISTER_GLOBAL("topi.nn.softmax")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
-  *rv = nn::softmax(args[0]);
+  *rv = nn::softmax(args[0], args[1]);
   });
 
 TVM_REGISTER_GLOBAL("topi.nn.log_softmax")
diff --git a/topi/tests/python_cpp/test_topi_pooling.py b/topi/tests/python_cpp/test_topi_pooling.py
index 6132fcd36469..e45b53dc0dec 100644
--- a/topi/tests/python_cpp/test_topi_pooling.py
+++ b/topi/tests/python_cpp/test_topi_pooling.py
@@ -16,7 +16,7 @@ def verify_pool(n, ic, ih, kh, sh, padding, pool_type, ceil_mode):
     ph, pw = padding
     A = tvm.placeholder((n, ic, ih, iw), name='A')
     B = topi.cpp.nn.pool(A, [kh, kw], [sh, sw], padding,
-                     pool_code[pool_type], ceil_mode)
+                         pool_code[pool_type], ceil_mode, "NCHW")
     B = topi.cpp.nn.relu(B)
     dtype = A.dtype
 
diff --git a/topi/tests/python_cpp/test_topi_softmax.py b/topi/tests/python_cpp/test_topi_softmax.py
index 2c2f62c18f20..2a6baaafe2f1 100644
--- a/topi/tests/python_cpp/test_topi_softmax.py
+++ b/topi/tests/python_cpp/test_topi_softmax.py
@@ -8,7 +8,7 @@
 
 def verify_softmax(m, n):
     A = tvm.placeholder((m, n), name='A')
-    B = topi.cpp.nn.softmax(A)
+    B = topi.cpp.nn.softmax(A, 1)
     # confirm lower works
     s = tvm.create_schedule([B.op])
     tvm.lower(s, [A, B], simple_mode=True)

From 2d132be71660b2bbb33d112575af8a8bc3d7df58 Mon Sep 17 00:00:00 2001
From: Meghan Cowan <cowanmeg@cs.washington.edu>
Date: Sun, 18 Feb 2018 17:02:40 -0800
Subject: [PATCH 157/949] Add UIntImm to select rewrite (#909)

---
 src/pass/rewrite_unsafe_select.cc | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/pass/rewrite_unsafe_select.cc b/src/pass/rewrite_unsafe_select.cc
index ecafe8eba06c..3224f47907a3 100644
--- a/src/pass/rewrite_unsafe_select.cc
+++ b/src/pass/rewrite_unsafe_select.cc
@@ -77,6 +77,7 @@ class UnsafeExprDetector : public ExprFunctor<bool(const Expr& n)> {
     return false;
   }
   bool VisitExpr_(const Variable* op) final { return false; }
+  bool VisitExpr_(const UIntImm* op) final { return false; }
   bool VisitExpr_(const IntImm* op) final { return false; }
   bool VisitExpr_(const FloatImm* op) final { return false; }
   bool VisitExpr_(const StringImm* op) final { return false; }

From a7dc0def2be8e23494cd47a2776b57afe8bf0abb Mon Sep 17 00:00:00 2001
From: masahi <masahi129@gmail.com>
Date: Tue, 20 Feb 2018 05:46:36 +0900
Subject: [PATCH 158/949] add missing inline (#910)

---
 topi/include/topi/reduction.h | 42 +++++++++++++++++------------------
 1 file changed, 21 insertions(+), 21 deletions(-)

diff --git a/topi/include/topi/reduction.h b/topi/include/topi/reduction.h
index 3fda17130fce..a4a3e4e999ac 100644
--- a/topi/include/topi/reduction.h
+++ b/topi/include/topi/reduction.h
@@ -35,7 +35,7 @@ using FCommReduce = std::function<
 * If any input element is negative, it will be treated as an offset from the
 * last dimension (same as python indexing rules).
 */
-std::vector<int> GetRealAxis(int ndim, const std::vector<int>& axis) {
+inline std::vector<int> GetRealAxis(int ndim, const std::vector<int>& axis) {
   std::vector<int> real_axis;
   if (axis.size() == 0) {
     for (int i = 0; i < ndim; ++i) {
@@ -59,7 +59,7 @@ std::vector<int> GetRealAxis(int ndim, const std::vector<int>& axis) {
 }
 
 /*! \brief Enumerate the axes for a reduce op */
-Array<IterVar> MakeReduceAxes(const std::vector<int>& real_axis, const Tensor& data) {
+inline Array<IterVar> MakeReduceAxes(const std::vector<int>& real_axis, const Tensor& data) {
   Array<IterVar> reduce_axes;
   for (auto i : real_axis) {
     std::string name = "k" + std::to_string(i);
@@ -70,9 +70,9 @@ Array<IterVar> MakeReduceAxes(const std::vector<int>& real_axis, const Tensor& d
 }
 
 /*! \brief Calculate the target shape for a reduce op */
-Array<Expr> MakeReduceTargetShape(const std::vector<int>& real_axis,
-                                  const Tensor& data,
-                                  bool keepdims) {
+inline Array<Expr> MakeReduceTargetShape(const std::vector<int>& real_axis,
+                                         const Tensor& data,
+                                         bool keepdims) {
   auto ndim = data->shape.size();
   Array<Expr> target_shape;
   if (keepdims) {
@@ -107,10 +107,10 @@ Array<Expr> MakeReduceTargetShape(const std::vector<int>& real_axis,
  *
  * \return The result tensor.
  */
-Tensor CommReduce(const Tensor& data,
-                  const Array<Expr>& axis,
-                  FReduce func,
-                  bool keepdims = false) {
+inline Tensor CommReduce(const Tensor& data,
+                         const Array<Expr>& axis,
+                         FReduce func,
+                         bool keepdims = false) {
   auto ndim = data->shape.size();
   CHECK_NE(ndim, 0) << "Cannot reduce a 0 dim Tensor";
   auto axis_val = detail::GetConstIntValues(axis, "axis");
@@ -159,10 +159,10 @@ Tensor CommReduce(const Tensor& data,
 *
 * \return The result tensor.
 */
-Tensor CommReduceIdx(const Tensor& data,
-                     const Array<Expr>& axis,
-                     FCommReduce func,
-                     bool keepdims = false) {
+inline Tensor CommReduceIdx(const Tensor& data,
+                            const Array<Expr>& axis,
+                            FCommReduce func,
+                            bool keepdims = false) {
   auto ndim = data->shape.size();
   CHECK_NE(ndim, 0) << "Cannot reduce a 0 dim Tensor";
   auto axis_val = detail::GetConstIntValues(axis, "axis");
@@ -227,9 +227,9 @@ using FIdentity = std::function<Array<Expr>(std::vector<Type> types)>;
  *
  * \return A reducer function which creates a reduce expression over an axis.
  */
-FCommReduce MakeCommReducer(FCombine fcombine,
-                            FIdentity fidentity,
-                            std::string name = "reduce") {
+inline FCommReduce MakeCommReducer(FCombine fcombine,
+                                   FIdentity fidentity,
+                                   std::string name = "reduce") {
   return [fcombine, fidentity, &name]
   (Array<Expr> exprs, const Array<IterVar>& axis, Expr* condition) {
     Array<Var> lhs, rhs;
@@ -277,7 +277,7 @@ inline Expr MaxOp(Expr source, Array<IterVar> axis) {
 *
 * \return A Tensor whose op member is the sum operation
 */
-Tensor sum(const Tensor& data, Array<Expr> axis, bool keepdims = false) {
+inline Tensor sum(const Tensor& data, Array<Expr> axis, bool keepdims = false) {
   return CommReduce(data, axis, tvm::sum, keepdims);
 }
 
@@ -294,7 +294,7 @@ Tensor sum(const Tensor& data, Array<Expr> axis, bool keepdims = false) {
 *
 * \return A Tensor whose op member is the min operation
 */
-Tensor min(const Tensor& data, Array<Expr> axis, bool keepdims = false) {
+inline Tensor min(const Tensor& data, Array<Expr> axis, bool keepdims = false) {
   return CommReduce(data, axis, MinOp, keepdims);
 }
 
@@ -311,7 +311,7 @@ Tensor min(const Tensor& data, Array<Expr> axis, bool keepdims = false) {
 *
 * \return A Tensor whose op member is the max operation
 */
-Tensor max(const Tensor& data, Array<Expr> axis, bool keepdims = false) {  // NOLINT(*)
+inline Tensor max(const Tensor& data, Array<Expr> axis, bool keepdims = false) {  // NOLINT(*)
   return CommReduce(data, axis, MaxOp, keepdims);
 }
 
@@ -328,7 +328,7 @@ Tensor max(const Tensor& data, Array<Expr> axis, bool keepdims = false) {  // NO
 *
 * \return A Tensor whose op member is the argmin operation
 */
-Tensor argmin(const Tensor& data, Array<Expr> axis, bool keepdims = false) {
+inline Tensor argmin(const Tensor& data, Array<Expr> axis, bool keepdims = false) {
   auto fcombine = [](Array<Var> lhs, Array<Var> rhs) {
     Array<Expr> result;
     result.push_back(tvm::select(lhs[1] <= rhs[1], lhs[0], rhs[0]));  // idx
@@ -358,7 +358,7 @@ Tensor argmin(const Tensor& data, Array<Expr> axis, bool keepdims = false) {
 *
 * \return A Tensor whose op member is the argmax operation
 */
-Tensor argmax(const Tensor& data, Array<Expr> axis, bool keepdims = false) {
+inline Tensor argmax(const Tensor& data, Array<Expr> axis, bool keepdims = false) {
   auto fcombine = [](Array<Var> lhs, Array<Var> rhs) {
     Array<Expr> result;
     result.push_back(tvm::select(lhs[1] >= rhs[1], lhs[0], rhs[0]));  // idx

From 7e1aaa3c2a5b9cf7528e4e640af60e541b8b86c7 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 20 Feb 2018 16:02:50 -0800
Subject: [PATCH 159/949] [RUNTIME] Support nop (#913)

---
 src/runtime/graph/graph_runtime.cc | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/runtime/graph/graph_runtime.cc b/src/runtime/graph/graph_runtime.cc
index 0c642577da59..34d6b3af155a 100644
--- a/src/runtime/graph/graph_runtime.cc
+++ b/src/runtime/graph/graph_runtime.cc
@@ -564,6 +564,9 @@ std::function<void()> GraphRuntime::CreateTVMOp(
       t->shape = &(arg_ptr->shape_data[i]);
     }
   }
+  if (param.func_name == "__nop") {
+    return [](){};
+  }
   // get compiled function from module.
   tvm::runtime::PackedFunc pf = module_.GetFunction(param.func_name, false);
   CHECK(pf != nullptr) << "no such function in module: " << param.func_name;

From e4c2af9abdcb3c7aabafba8084414d7739c17c4c Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 20 Feb 2018 16:56:59 -0800
Subject: [PATCH 160/949] [RUNTIME] More reliable runtime only detection (#914)

* [RUNTIME] More reliable runtime only detection

* fix lint
---
 python/tvm/_ffi/base.py    | 4 ++++
 python/tvm/build_module.py | 4 ++--
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/python/tvm/_ffi/base.py b/python/tvm/_ffi/base.py
index b17043c2911d..49348f3110ad 100644
--- a/python/tvm/_ffi/base.py
+++ b/python/tvm/_ffi/base.py
@@ -41,6 +41,10 @@ def _load_lib():
 __version__ = libinfo.__version__
 # library instance of nnvm
 _LIB, _LIB_NAME = _load_lib()
+
+# Whether we are runtime only
+_RUNTIME_ONLY = "runtime" in _LIB_NAME
+
 # The FFI mode of TVM
 _FFI_MODE = os.environ.get("TVM_FFI", "auto")
 
diff --git a/python/tvm/build_module.py b/python/tvm/build_module.py
index 9c442a07425d..86d150c08e5f 100755
--- a/python/tvm/build_module.py
+++ b/python/tvm/build_module.py
@@ -6,9 +6,9 @@
 from __future__ import absolute_import as _abs
 import warnings
 import types
-import os
 
 from ._ffi.node import NodeBase, register_node
+from ._ffi.base import _RUNTIME_ONLY
 from . import api
 from . import tensor
 from . import schedule
@@ -226,7 +226,7 @@ def build_config(**kwargs):
             setattr(config, k, kwargs[k])
     return config
 
-if not os.environ.get("TVM_USE_RUNTIME_LIB", False):
+if not _RUNTIME_ONLY:
     # BuildConfig is not available in tvm_runtime
     BuildConfig.current = build_config()
 

From 0951915ace479af2e28c1cada37bfd56e680acae Mon Sep 17 00:00:00 2001
From: libing4752 <libing475211023@sjtu.edu.cn>
Date: Fri, 23 Feb 2018 01:28:23 +0800
Subject: [PATCH 161/949] [SCHEDULE] Add factor_axis to rfactor (#895)

---
 include/tvm/schedule.h                      |  4 ++-
 python/tvm/schedule.py                      |  6 ++--
 src/api/api_lang.cc                         |  2 +-
 src/schedule/schedule_dataflow_rewrite.cc   | 30 +++++++++++++++-----
 tests/python/integration/test_reduce.py     | 31 +++++++++++++++++++++
 tests/python/unittest/test_lang_schedule.py | 10 +++++++
 6 files changed, 72 insertions(+), 11 deletions(-)

diff --git a/include/tvm/schedule.h b/include/tvm/schedule.h
index 51e27a9e94bf..568af8252f4a 100644
--- a/include/tvm/schedule.h
+++ b/include/tvm/schedule.h
@@ -313,10 +313,12 @@ class Schedule : public NodeRef {
    *
    * \param tensor The tensor to be factored.
    * \param axis The reduction axis in tensor's schedule to be factored.
+   * \param factor_axis The position where the new axis is placed.
    * \return The created factored tensors.
    */
   EXPORT Array<Tensor> rfactor(const Tensor& tensor,
-                        const IterVar& axis);
+                        const IterVar& axis,
+                        int factor_axis = 0);
   /*!
    * \brief Normalize the schedule.
    *  This is needed before bound inference.
diff --git a/python/tvm/schedule.py b/python/tvm/schedule.py
index dda5f67d1b89..b04945292adf 100644
--- a/python/tvm/schedule.py
+++ b/python/tvm/schedule.py
@@ -279,7 +279,7 @@ def cache_write(self, tensor, scope):
         """
         return _api_internal._ScheduleCacheWrite(self, tensor, scope)
 
-    def rfactor(self, tensor, axis):
+    def rfactor(self, tensor, axis, factor_axis=0):
         """ Factor a reduction axis in tensor's schedule to be an explicit axis.
 
         This will create a new stage that generated the new tensor with axis
@@ -292,13 +292,15 @@ def rfactor(self, tensor, axis):
             The tensor to be factored.
         axis : IterVar
             The reduction axis in the schedule to be factored.
+        factor_axis : int
+            The position where the new axis is placed.
 
         Returns
         -------
         tfactor : Tensor or Array of Tensor
             The created factored tensor.
         """
-        factored = _api_internal._ScheduleRFactor(self, tensor, axis)
+        factored = _api_internal._ScheduleRFactor(self, tensor, axis, factor_axis)
         return factored[0] if len(factored) == 1 else factored
 
 
diff --git a/src/api/api_lang.cc b/src/api/api_lang.cc
index de388cf0b51f..d1994340702d 100644
--- a/src/api/api_lang.cc
+++ b/src/api/api_lang.cc
@@ -432,7 +432,7 @@ TVM_REGISTER_API("_ScheduleCacheWrite")
 TVM_REGISTER_API("_ScheduleRFactor")
 .set_body([](TVMArgs args, TVMRetValue* ret) {
     *ret = args[0].operator Schedule()
-        .rfactor(args[1], args[2]);
+        .rfactor(args[1], args[2], args[3]);
   });
 
 TVM_REGISTER_API("_CommReducerCombine")
diff --git a/src/schedule/schedule_dataflow_rewrite.cc b/src/schedule/schedule_dataflow_rewrite.cc
index 59d425287be0..562eff417dd2 100644
--- a/src/schedule/schedule_dataflow_rewrite.cc
+++ b/src/schedule/schedule_dataflow_rewrite.cc
@@ -395,7 +395,8 @@ Schedule Schedule::normalize() {
 
 // Handle reduction factor.
 Array<Tensor> Schedule::rfactor(const Tensor& tensor,
-                                const IterVar& axis) {
+                                const IterVar& axis,
+                                int factor_axis) {
   (*this)->InvalidateCache();
   using ir::Reduce;
   CHECK_EQ(axis->iter_type, kCommReduce)
@@ -448,6 +449,9 @@ Array<Tensor> Schedule::rfactor(const Tensor& tensor,
       reduce_stage, dom_map, value_map, true, skip_bound_check);
 
   // Get the factored op node.
+  const int factor_axis_pos = \
+      factor_axis >= 0 ? factor_axis : static_cast<int>(compute_op->axis.size() + 1) + factor_axis;
+  CHECK_LE(factor_axis_pos, compute_op->axis.size());
   auto n = std::make_shared<ComputeOpNode>();
   n->name = compute_op->name + ".rf";
   {
@@ -458,10 +462,16 @@ Array<Tensor> Schedule::rfactor(const Tensor& tensor,
         << "Can only factor reduction domain starting from 0";
     iv_node->var = axis->var;
     iv_node->iter_type = kDataPar;
-    n->axis.push_back(IterVar(iv_node));
 
-    for (IterVar iv : compute_op->axis) {
-      n->axis.push_back(iv);
+    const int size = compute_op->axis.size();
+    for (int idx = 0; idx < size; ++idx) {
+      if (factor_axis_pos == idx) {
+        n->axis.push_back(IterVar(iv_node));
+      }
+      n->axis.push_back(compute_op->axis[idx]);
+    }
+    if (factor_axis_pos == size) {
+      n->axis.push_back(IterVar(iv_node));
     }
   }
   // predicate generation, copy not touched axis.
@@ -548,9 +558,15 @@ Array<Tensor> Schedule::rfactor(const Tensor& tensor,
   Array<Tensor> repl_tensors = compute(old_tensors[0]->shape,
     [&](const Array<Var>& i) {
       Array<Expr> indices;
-      indices.push_back(repl_red_axis->var);
-      for (Var v : i) {
-        indices.push_back(v);
+      const int idx_size = static_cast<int>(i.size());
+      for (int idx = 0; idx < idx_size; ++idx) {
+        if (factor_axis_pos == idx) {
+          indices.push_back(repl_red_axis->var);
+        }
+        indices.push_back(i[idx]);
+      }
+      if (factor_axis_pos == idx_size) {
+          indices.push_back(repl_red_axis->var);
       }
       Array<Expr> factor_exprs;
       for (int idx = 0; idx < size; ++idx) {
diff --git a/tests/python/integration/test_reduce.py b/tests/python/integration/test_reduce.py
index 228786a7de61..c8fb98746bf6 100644
--- a/tests/python/integration/test_reduce.py
+++ b/tests/python/integration/test_reduce.py
@@ -83,6 +83,36 @@ def check_target(target="llvm"):
 
     check_target()
 
+def test_rfactor_factor_axis():
+    n = tvm.convert(1027)
+    A = tvm.placeholder((n,), name='A')
+    k = tvm.reduce_axis((0, n))
+    B = tvm.compute((1,), lambda i: tvm.sum(A[k], axis=k), name='B')
+    # schedule
+    s = tvm.create_schedule(B.op)
+    kf, ki = s[B].split(k, nparts=4)
+    BF = s.rfactor(B, kf, 1)
+    s[BF].parallel(BF.op.axis[0])
+    # one line to build the function.
+    def check_target(target="llvm"):
+        if not tvm.module.enabled(target):
+            return
+        ctx = tvm.cpu(0)
+        fapi = tvm.lower(s, args=[A, B])
+        fsum = tvm.build(fapi,
+                         target=target,
+                         name="mysum")
+        # launch the kernel.
+        n = 1027
+        a = tvm.nd.array(np.random.uniform(size=(n,)).astype(A.dtype), ctx)
+        b  = tvm.nd.array(np.zeros(1, dtype=B.dtype), ctx)
+        fsum(a, b)
+        res = np.sum(a.asnumpy(), axis=0)
+        np.testing.assert_allclose(
+            b.asnumpy(), res, rtol=1e-4)
+
+    check_target()
+
 
 def test_rfactor_threads():
     nn = 1027
@@ -294,6 +324,7 @@ def check_target(device):
 if __name__ == "__main__":
     test_rfactor_elemwise_threads()
     test_rfactor_threads()
+    test_rfactor_factor_axis()
     test_rfactor()
     test_reduce_prims()
     test_argmax()
diff --git a/tests/python/unittest/test_lang_schedule.py b/tests/python/unittest/test_lang_schedule.py
index 6c29f1067632..b29ebec180df 100644
--- a/tests/python/unittest/test_lang_schedule.py
+++ b/tests/python/unittest/test_lang_schedule.py
@@ -137,6 +137,16 @@ def test_rfactor():
     assert(BF.op.body[0].axis[0] ==  k2)
     assert(BF.op.body[0].axis[1].var ==  ko.var)
     assert(s[B].op.body[0].axis[0].dom.extent.value == 4)
+    # schedule with factor_axis
+    s = tvm.create_schedule(B.op)
+    ko, ki = s[B].split(k1, factor=4)
+    xo, xi = s[B].split(B.op.axis[0], factor=8)
+    BF = s.rfactor(B, ki, 1)
+    assert(n == BF.shape[0])
+    assert(BF.shape[1].value == 4)
+    assert(BF.op.body[0].axis[0] ==  k2)
+    assert(BF.op.body[0].axis[1].var ==  ko.var)
+    assert(s[B].op.body[0].axis[0].dom.extent.value == 4)
 
 def test_tensor_intrin():
     n = 16

From 4cd2352171c145fe5eeb9b54cfa9e8d262f2133b Mon Sep 17 00:00:00 2001
From: xqdan <danxiaoqiang@126.com>
Date: Fri, 23 Feb 2018 01:30:52 +0800
Subject: [PATCH 162/949] [PASS] Support buffer reuse for different types
 (#891)

[PASS] Support buffer reuse for different types
---
 src/pass/storage_rewrite.cc                   | 28 ++++---
 .../unittest/test_pass_storage_rewrite.py     | 80 ++++++++++++++++++-
 2 files changed, 94 insertions(+), 14 deletions(-)

diff --git a/src/pass/storage_rewrite.cc b/src/pass/storage_rewrite.cc
index 452b1a7b6abf..ac3ca2561e01 100644
--- a/src/pass/storage_rewrite.cc
+++ b/src/pass/storage_rewrite.cc
@@ -502,7 +502,6 @@ class StoragePlanRewriter : public IRMutator {
   }
   // Remap the index
   Expr RemapIndex(Type dtype, Expr index, StorageEntry* e) {
-    CHECK_EQ(dtype.element_of(), e->elem_type);
     if (e->bits_offset == 0) return index;
     uint64_t elem_bits = dtype.bits() * dtype.lanes();
     CHECK_EQ(e->bits_offset % elem_bits, 0U);
@@ -564,17 +563,22 @@ class StoragePlanRewriter : public IRMutator {
           Expr combo_size;
           for (const Allocate* op : e->allocs) {
             Expr sz = arith::ComputeReduce<Mul>(op->extents, make_const(Int(32), 1));
-            if (alloc_type.lanes() != op->type.lanes()) {
-              sz = (sz * make_const(sz.type(), op->type.lanes()) +
-                    make_const(sz.type(), alloc_type.lanes() - 1)) /
-                  make_const(sz.type(), alloc_type.lanes());
-            }
+            // transform to bits
+            auto sz_nbits = sz * (op->type.bits() * op->type.lanes());
             if (combo_size.defined()) {
-              combo_size = max(combo_size, sz);
+              combo_size = max(combo_size, sz_nbits);
             } else {
-              combo_size = sz;
+              combo_size = sz_nbits;
             }
           }
+          // transform to alloc bytes
+          auto type_bits = alloc_type.bits() * alloc_type.lanes();
+          bool divided = can_prove(combo_size % type_bits == 0);
+          combo_size = combo_size / type_bits;
+          // round up for can not divided
+          if (!divided) {
+             combo_size += make_const(Int(32), 1);
+          }
           combo_size = ir::Simplify(combo_size);
           e->new_alloc = Allocate::make(
               e->alloc_var, alloc_type, {combo_size}, const_true(),
@@ -784,8 +788,9 @@ class StoragePlanRewriter : public IRMutator {
     // skip plan for local variable,
     // compiler can do a better job with register allocation.
     const uint64_t match_range = 16;
+    uint64_t op_elem_bits = op->type.bits() * op->type.lanes();
     uint64_t const_nbits = static_cast<uint64_t>(
-        op->constant_allocation_size() * op->type.bits() * op->type.lanes());
+        op->constant_allocation_size() * op_elem_bits);
     // disable reuse of small arrays, they will be lowered to registers in LLVM
     // This rules only apply if we are using non special memory
     if (scope.tag.length() == 0) {
@@ -801,15 +806,18 @@ class StoragePlanRewriter : public IRMutator {
       auto begin = const_free_map_.lower_bound(const_nbits / match_range);
       auto mid = const_free_map_.lower_bound(const_nbits);
       auto end = const_free_map_.upper_bound(const_nbits * match_range);
+      // start looking at the buffer that is bigger than the required size first
       for (auto it = mid; it != end; ++it) {
         StorageEntry *e = it->second;
         if (e->attach_scope_ != attach_scope) continue;
         if (e->scope != scope) continue;
-        if (e->elem_type != op->type.element_of()) continue;
+        // when not divided, no reuse, eg, float4 vs float3
+        if (e->bits_offset % op_elem_bits != 0) continue;
         e->const_nbits = std::max(const_nbits, e->const_nbits);
         const_free_map_.erase(it);
         return e;
       }
+      // then start looking at smaller buffers.
       for (auto it = mid; it != begin;) {
         --it;
         StorageEntry *e = it->second;
diff --git a/tests/python/unittest/test_pass_storage_rewrite.py b/tests/python/unittest/test_pass_storage_rewrite.py
index c9999f312300..9613a61b9b39 100644
--- a/tests/python/unittest/test_pass_storage_rewrite.py
+++ b/tests/python/unittest/test_pass_storage_rewrite.py
@@ -54,10 +54,27 @@ def stmt_generater(dtype_list, length):
         ib = tvm.ir_builder.create()
         base_dtype = dtype_list[0]
         global_a = tvm.placeholder((length,), name = "global_a", dtype = base_dtype)
-        for index, dtype in enumerate(dtype_list):
-            with ib.for_range(0, length, name="j") as j:
-                A = ib.allocate(dtype, length, name="A_" + str(index), scope="local.L0A")
-                A[j] = tvm.const(1, dtype = dtype)
+        assert len(dtype_list) == 4
+        with ib.for_range(0, length, name="j") as j:
+            dtype = dtype_list[0]
+            A = ib.allocate(dtype, length, name="A", scope="local.L0A")
+            A[j] = tvm.const(1, dtype = dtype)
+        with ib.for_range(0, length, name="j") as j:
+            dtype = dtype_list[1]
+            B = ib.allocate(dtype, length, name="B", scope="local.L0A")
+            B[j] = tvm.const(1, dtype = dtype)
+        with ib.for_range(0, length, name="j") as j:
+            dtype = dtype_list[2]
+            C = ib.allocate(dtype, length, name="C", scope="local.L0A")
+            C[j] = tvm.const(1, dtype = dtype)
+        with ib.for_range(0, length, name="j") as j:
+            dtype = dtype_list[3]
+            D = ib.allocate(dtype, length, name="D", scope="local.L0A")
+            D[j] = tvm.const(1, dtype = dtype)
+        with ib.for_range(0, length, name="j") as j:
+            dtype = "int8"
+            E = ib.allocate(dtype, length, name="E", scope="local.L0A")
+            E[j] = A[j].astype(dtype) + B[j].astype(dtype) + C[j].astype(dtype) + D[j].astype(dtype)
         return ib.get()
     
     def dtype_bit_len(dtype):
@@ -342,6 +359,58 @@ def verify(n):
             assert n.extents[0].value == 70
     tvm.ir_pass.PostOrderVisit(stmt, verify)
 
+def test_alloc_seq_type():
+    ib = tvm.ir_builder.create()
+    n = tvm.var("n")
+    with ib.for_range(0, n, name="i") as i:
+        with ib.for_range(0, 10, name="j") as j:
+            A = ib.allocate("float32", 200, name="A", scope="local.L0A")
+            A1 = ib.allocate("float32", 200, name="A1", scope="local.L0A")
+            A[j] = 1.2
+            A1[j] = 1.3
+            B = ib.allocate("int16", 200, name="B", scope="local.L0A")
+            B[j] = tvm.const(1, "int16")
+            C = ib.allocate("int16", 200, name="C", scope="local.L0A")
+            C[j] = tvm.const(1, "int16")
+            D = ib.allocate("int16", 200, name="D", scope="local.L0A")
+            D[j] = B[j] + C[j]
+            A2 = ib.allocate("float32", 200, name="A2", scope="local.L0A")
+            A2[j] = A[j]
+
+    body = ib.get()
+    body = tvm.ir_pass.StorageRewrite(body)
+    num_alloc = [0]
+    def verify(n):
+        if isinstance(n, tvm.stmt.Allocate):
+            num_alloc[0] += 1
+            assert n.extents[0].value == 500
+    tvm.ir_pass.PostOrderVisit(body, verify)
+    assert num_alloc[0] == 1
+
+def test_alloc_seq_type2():
+    ib = tvm.ir_builder.create()
+    n = tvm.var("n")
+    with ib.for_range(0, n, name="i") as i:
+        with ib.for_range(0, 10, name="j") as j:
+            A = ib.allocate("float32", 200, name="A", scope="local.L0A")
+            A[j] = 1.2
+        with ib.for_range(0, 20, name="j") as j:
+            B = ib.allocate("int16", 400, name="B", scope="local.L0A")
+            B[j] = tvm.const(1, "int16")
+        with ib.for_range(0, 10, name="j") as j:
+            C = ib.allocate("float32", 200, name="C", scope="local.L0A")
+            C[j] = 1.2
+
+    body = ib.get()
+    body = tvm.ir_pass.StorageRewrite(body)
+    num_alloc = [0]
+    def verify(n):
+        if isinstance(n, tvm.stmt.Allocate):
+            num_alloc[0] += 1
+            assert n.extents[0].value == 200
+    tvm.ir_pass.PostOrderVisit(body, verify)
+    assert num_alloc[0] == 1
+
 if __name__ == "__main__":
     test_alloc_seq()
     test_alloc_different_dtypes()
@@ -352,3 +421,6 @@ def verify(n):
     test_storage_share_gpu()
     test_inplace_rule2()
     test_inplace_rule3()
+    test_alloc_seq_type()
+    test_alloc_seq_type2()
+

From 12d15704d7f5d30cff7540f1fd16be64c6baca68 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 22 Feb 2018 18:26:32 -0800
Subject: [PATCH 163/949] [RUNTIME] Refactor extension type handling, now it is
 header only (#924)

* [RUNTIME] Refactor extension type handling, now it is header only
---
 apps/extension/src/tvm_ext.cc            |  20 +++-
 apps/extension/tests/test_ext.py         |   6 ++
 include/tvm/runtime/c_runtime_api.h      |  18 ++++
 include/tvm/runtime/module.h             |  12 ++-
 include/tvm/runtime/packed_func.h        | 122 ++++++++++++-----------
 python/tvm/_ffi/function.py              |  25 +++++
 python/tvm/api.py                        |   2 +-
 python/tvm/build_module.py               |  32 +++---
 src/runtime/c_runtime_api.cc             |   8 ++
 src/runtime/module.cc                    |  13 ---
 src/runtime/registry.cc                  |  29 ------
 tests/cpp/packed_func_test.cc            |   3 -
 tests/scripts/task_python_integration.sh |   1 +
 topi/src/topi.cc                         |   2 -
 14 files changed, 166 insertions(+), 127 deletions(-)

diff --git a/apps/extension/src/tvm_ext.cc b/apps/extension/src/tvm_ext.cc
index 6d7f4bdf7533..8b086863f7ed 100644
--- a/apps/extension/src/tvm_ext.cc
+++ b/apps/extension/src/tvm_ext.cc
@@ -22,13 +22,10 @@ struct extension_class_info<tvm_ext::IntVector> {
 }  // namespace tvm
 }  // namespace runtime
 
-
-namespace tvm_ext {
-
 using namespace tvm;
 using namespace tvm::runtime;
 
-TVM_REGISTER_EXT_TYPE(IntVector);
+namespace tvm_ext {
 
 TVM_REGISTER_GLOBAL("tvm_ext.ivec_create")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
@@ -66,3 +63,18 @@ TVM_REGISTER_GLOBAL("device_api.ext_dev")
     *rv = (*tvm::runtime::Registry::Get("device_api.cpu"))();
   });
 }  // namespace tvm_ext
+
+// This callback approach allows extension allows tvm to extract
+// This way can be helpful when we want to use a header only
+// minimum version of TVM Runtime.
+extern "C" int TVMExtDeclare(TVMFunctionHandle pregister) {
+  const PackedFunc& fregister =
+      *static_cast<PackedFunc*>(pregister);
+  auto mul = [](TVMArgs args, TVMRetValue *rv) {
+    int x = args[0];
+    int y = args[1];
+    *rv = x * y;
+  };
+  fregister("mul", PackedFunc(mul));
+  return 0;
+}
diff --git a/apps/extension/tests/test_ext.py b/apps/extension/tests/test_ext.py
index 0bbfff14eeef..628602f0baea 100644
--- a/apps/extension/tests/test_ext.py
+++ b/apps/extension/tests/test_ext.py
@@ -44,8 +44,14 @@ def ivec_cb(v2):
 
     tvm.convert(ivec_cb)(ivec)
 
+def test_extract_ext():
+    fdict = tvm.extract_ext_funcs(tvm_ext._LIB.TVMExtDeclare)
+    assert fdict["mul"](3, 4) == 12
+
+
 if __name__ == "__main__":
     test_ext_dev()
     test_ext_vec()
     test_bind_add()
     test_sym_add()
+    test_extract_ext()
diff --git a/include/tvm/runtime/c_runtime_api.h b/include/tvm/runtime/c_runtime_api.h
index edade00c7ed3..e4a06b39d04e 100644
--- a/include/tvm/runtime/c_runtime_api.h
+++ b/include/tvm/runtime/c_runtime_api.h
@@ -24,6 +24,13 @@
 #define TVM_EXTERN_C
 #endif
 
+// Macros to do weak linking
+#ifdef _MSC_VER
+#define TVM_WEAK __declspec(selectany)
+#else
+#define TVM_WEAK __attribute__((weak))
+#endif
+
 #ifdef __EMSCRIPTEN__
 #include <emscripten/emscripten.h>
 #define TVM_DLL EMSCRIPTEN_KEEPALIVE
@@ -313,6 +320,17 @@ typedef int (*TVMPackedCFunc)(
  */
 typedef void (*TVMPackedCFuncFinalizer)(void* resource_handle);
 
+/*!
+ * \brief Signature for extension function declarer.
+ *
+ *  TVM call this function to get the extension functions
+ *  The declarer will call register_func to register function and their name.
+ *
+ * \param resource_func_handle The register function
+ * \return 0 if success, -1 if failure happens
+ */
+typedef int (*TVMExtensionFuncDeclarer)(TVMFunctionHandle register_func_handle);
+
 /*!
  * \brief Wrap a TVMPackedCFunc to become a FunctionHandle.
  *
diff --git a/include/tvm/runtime/module.h b/include/tvm/runtime/module.h
index 3d0991034c41..f8e5069f56c5 100644
--- a/include/tvm/runtime/module.h
+++ b/include/tvm/runtime/module.h
@@ -38,8 +38,14 @@ class Module {
    * \param query_imports Whether also query dependency modules.
    * \return The result function.
    *  This function will return PackedFunc(nullptr) if function do not exist.
+   * \note Implemented in packed_func.cc
    */
-  TVM_DLL PackedFunc GetFunction(const std::string& name, bool query_imports = false);
+  inline PackedFunc GetFunction(const std::string& name, bool query_imports = false);
+  /*! \return internal container */
+  inline ModuleNode* operator->();
+  /*! \return internal container */
+  inline const ModuleNode* operator->() const;
+  // The following functions requires link with runtime.
   /*!
    * \brief Import another module into this module.
    * \param other The module to be imported.
@@ -57,10 +63,6 @@ class Module {
    */
   TVM_DLL static Module LoadFromFile(const std::string& file_name,
                                      const std::string& format = "");
-  /*! \return internal container */
-  inline ModuleNode* operator->();
-  /*! \return internal container */
-  inline const ModuleNode* operator->() const;
 
  private:
   std::shared_ptr<ModuleNode> node_;
diff --git a/include/tvm/runtime/packed_func.h b/include/tvm/runtime/packed_func.h
index b01e662b97ab..ca2e020ba703 100644
--- a/include/tvm/runtime/packed_func.h
+++ b/include/tvm/runtime/packed_func.h
@@ -183,31 +183,17 @@ struct extension_class_info {
 };
 
 /*!
- * \brief Runtime function table about extension type.
+ * \brief Capsule structure holding extension types
+ *  Capsule is self-contained and include
+ *  all the information to clone and destroy the type.
  */
-class ExtTypeVTable {
- public:
+struct TVMExtTypeCapsule {
+  /*! \brief The pointer to the object */
+  void* ptr;
   /*! \brief function to be called to delete a handle */
   void (*destroy)(void* handle);
   /*! \brief function to be called when clone a handle */
   void* (*clone)(void* handle);
-  /*!
-   * \brief Register type
-   * \tparam T The type to be register.
-   * \return The registered vtable.
-   */
-  template <typename T>
-  static inline ExtTypeVTable* Register_();
-  /*!
-   * \brief Get a vtable based on type code.
-   * \param type_code The type code
-   * \return The registered vtable.
-   */
-  TVM_DLL static ExtTypeVTable* Get(int type_code);
-
- private:
-  // Internal registration function.
-  TVM_DLL static ExtTypeVTable* RegisterInternal(int type_code, const ExtTypeVTable& vt);
 };
 
 /*!
@@ -255,8 +241,9 @@ class TVMPODValue_ {
   }
   template<typename TExtension>
   const TExtension& AsExtension() const {
-    CHECK_LT(type_code_, kExtEnd);
-    return static_cast<TExtension*>(value_.v_handle)[0];
+    CHECK_EQ(type_code_, extension_class_info<TExtension>::code);
+    return static_cast<TExtension*>(
+        static_cast<TVMExtTypeCapsule*>(value_.v_handle)->ptr)[0];
   }
   int type_code() const {
     return type_code_;
@@ -488,14 +475,6 @@ class TVMRetValue : public TVMPODValue_ {
     this->Assign(other);
     return *this;
   }
-  template<typename T,
-           typename = typename std::enable_if<
-             extension_class_info<T>::code != 0>::type>
-  TVMRetValue& operator=(const T& other) {
-    this->SwitchToClass<T>(
-        extension_class_info<T>::code, other);
-    return *this;
-  }
   /*!
    * \brief Move the value back to front-end via C API.
    *  This marks the current container as null.
@@ -521,6 +500,11 @@ class TVMRetValue : public TVMPODValue_ {
           type_code_ != kStr) << "TVMRetValue.value can only be used for POD data";
     return value_;
   }
+  // assign extension
+  template<typename T,
+           typename = typename std::enable_if<
+             extension_class_info<T>::code != 0>::type>
+  inline TVMRetValue& operator=(const T& other);
   // NodeRef related extenstions: in tvm/packed_func_ext.h
   template<typename T,
            typename = typename std::enable_if<
@@ -564,11 +548,9 @@ class TVMRetValue : public TVMPODValue_ {
           SwitchToPOD(other.type_code());
           value_ = other.value_;
         } else {
-          this->Clear();
-          type_code_ = other.type_code();
-          value_.v_handle =
-              (*(ExtTypeVTable::Get(other.type_code())->clone))(
-                  other.value().v_handle);
+          TVMExtTypeCapsule cap = *other.template ptr<TVMExtTypeCapsule>();
+          cap.ptr = cap.clone(cap.ptr);
+          SwitchToClass<TVMExtTypeCapsule>(other.type_code(), cap);
         }
         break;
       }
@@ -600,7 +582,9 @@ class TVMRetValue : public TVMPODValue_ {
       case kNodeHandle: delete ptr<std::shared_ptr<Node> >(); break;
     }
     if (type_code_ > kExtBegin) {
-      (*(ExtTypeVTable::Get(type_code_)->destroy))(value_.v_handle);
+      TVMExtTypeCapsule *cap = ptr<TVMExtTypeCapsule>();
+      cap->destroy(cap->ptr);
+      delete cap;
     }
     type_code_ = kNull;
   }
@@ -716,8 +700,10 @@ inline void for_each(const F& f, Args&&... args) {  // NOLINT(*)
 /* \brief argument settter to PackedFunc */
 class TVMArgsSetter {
  public:
-  TVMArgsSetter(TVMValue* values, int* type_codes)
-      : values_(values), type_codes_(type_codes) {}
+  TVMArgsSetter(TVMValue* values,
+                int* type_codes,
+                TVMExtTypeCapsule* exts)
+      : values_(values), type_codes_(type_codes), exts_(exts) {}
   // setters for POD types
   template<typename T,
            typename = typename std::enable_if<
@@ -807,15 +793,21 @@ class TVMArgsSetter {
   TVMValue* values_;
   /*! \brief The type code fields */
   int* type_codes_;
+  /*! \brief Temporary storage for extension types */
+  TVMExtTypeCapsule* exts_;
 };
 
 template<typename... Args>
 inline TVMRetValue PackedFunc::operator()(Args&& ...args) const {
   const int kNumArgs = sizeof...(Args);
+  // Compiler will remove an static array when it is not touched.
   const int kArraySize = kNumArgs > 0 ? kNumArgs : 1;
   TVMValue values[kArraySize];
   int type_codes[kArraySize];
-  detail::for_each(TVMArgsSetter(values, type_codes),
+  // If the function call does not contain extension type,
+  // exts will get optimized away by compiler.
+  TVMExtTypeCapsule exts[kArraySize];
+  detail::for_each(TVMArgsSetter(values, type_codes, exts),
                    std::forward<Args>(args)...);
   TVMRetValue rv;
   body_(TVMArgs(values, type_codes, kNumArgs), &rv);
@@ -853,14 +845,6 @@ inline TVMRetValue::operator T() const {
       ::Apply(this);
 }
 
-template<typename T, typename>
-inline void TVMArgsSetter::operator()(size_t i, const T& value) const {
-  static_assert(extension_class_info<T>::code != 0,
-                "Need to have extesion code");
-  type_codes_[i] = extension_class_info<T>::code;
-  values_[i].v_handle = const_cast<T*>(&value);
-}
-
 // extension type handling
 template<typename T>
 struct ExtTypeInfo {
@@ -872,16 +856,42 @@ struct ExtTypeInfo {
   }
 };
 
-template<typename T>
-inline ExtTypeVTable* ExtTypeVTable::Register_() {
-  const int code = extension_class_info<T>::code;
-  static_assert(code != 0,
-                "require extension_class_info traits to be declared with non-zero code");
-  ExtTypeVTable vt;
-  vt.clone = ExtTypeInfo<T>::clone;
-  vt.destroy = ExtTypeInfo<T>::destroy;
-  return ExtTypeVTable::RegisterInternal(code, vt);
+template<typename T, typename>
+inline TVMRetValue& TVMRetValue::operator=(const T& other) {
+  TVMExtTypeCapsule cap;
+  cap.clone = ExtTypeInfo<T>::clone;
+  cap.destroy = ExtTypeInfo<T>::destroy;
+  cap.ptr = new T(other);
+  SwitchToClass<TVMExtTypeCapsule>(
+      extension_class_info<T>::code, cap);
+  return *this;
+}
+
+template<typename T, typename>
+inline void TVMArgsSetter::operator()(size_t i, const T& value) const {
+  static_assert(extension_class_info<T>::code != 0,
+                "Need to have extesion code");
+  type_codes_[i] = extension_class_info<T>::code;
+  exts_[i].clone = ExtTypeInfo<T>::clone;
+  exts_[i].destroy = ExtTypeInfo<T>::destroy;
+  exts_[i].ptr = const_cast<T*>(&value);
+  values_[i].v_handle = &exts_[i];
 }
+
+// Implement Module::GetFunction
+// Put implementation in this file so we have seen the PackedFunc
+inline PackedFunc Module::GetFunction(const std::string& name, bool query_imports) {
+  PackedFunc pf = node_->GetFunction(name, node_);
+  if (pf != nullptr) return pf;
+  if (query_imports) {
+    for (const Module& m : node_->imports_) {
+      pf = m.node_->GetFunction(name, m.node_);
+      if (pf != nullptr) return pf;
+    }
+  }
+  return pf;
+}
+
 }  // namespace runtime
 }  // namespace tvm
 #endif  // TVM_RUNTIME_PACKED_FUNC_H_
diff --git a/python/tvm/_ffi/function.py b/python/tvm/_ffi/function.py
index 2edb355fb721..526d972f6d28 100644
--- a/python/tvm/_ffi/function.py
+++ b/python/tvm/_ffi/function.py
@@ -234,6 +234,31 @@ def list_global_func_names():
     return fnames
 
 
+def extract_ext_funcs(finit):
+    """
+    Extract the extension PackedFuncs from a C module.
+
+    Parameters
+    ----------
+    finit : ctypes function
+        a ctypes that takes signature of TVMExtensionDeclarer
+
+    Returns
+    -------
+    fdict : dict of str to Function
+        The extracted functions
+    """
+    fdict = {}
+    def _list(name, func):
+        fdict[name] = func
+    myf = convert_to_tvm_func(_list)
+    ret = finit(myf.handle)
+    _ = myf
+    if ret != 0:
+        raise RuntimeError("cannot initialize with %s" % finit)
+    return fdict
+
+
 def _get_api(f):
     flocal = f
     flocal.is_global = True
diff --git a/python/tvm/api.py b/python/tvm/api.py
index 7c90b0ec9481..66c154bc9f00 100644
--- a/python/tvm/api.py
+++ b/python/tvm/api.py
@@ -8,7 +8,7 @@
 from ._ffi.node import register_node, NodeBase
 from ._ffi.node import convert_to_node as _convert_to_node
 from ._ffi.function import Function
-from ._ffi.function import _init_api, register_func, get_global_func
+from ._ffi.function import _init_api, register_func, get_global_func, extract_ext_funcs
 from ._ffi.function import convert_to_tvm_func as _convert_tvm_func
 from ._ffi.runtime_ctypes import TVMType
 from . import _api_internal
diff --git a/python/tvm/build_module.py b/python/tvm/build_module.py
index 86d150c08e5f..d868e2e0df12 100755
--- a/python/tvm/build_module.py
+++ b/python/tvm/build_module.py
@@ -23,16 +23,16 @@
 from . import make
 
 class DumpIR(object):
-    """Dump IR for each pass.
-       With it, you can dump ir just like gcc/llvm.
-
-       How to use:
-       -----------
-       .. code-block:: python
+    """
+    Dump IR for each pass.
+    With it, you can dump ir just like gcc/llvm.
 
-          with tvm.build_config(dump_pass_ir=True)
-              run()
+    How to use:
+    -----------
+    .. code-block:: python
 
+    with tvm.build_config(dump_pass_ir=True)
+        run()
     """
     scope_level = 0
     def __init__(self):
@@ -40,9 +40,9 @@ def __init__(self):
         self._recover_list = []
 
     def decorate(self, func):
-        ''' decorate the pass function'''
+        """ decorate the pass function"""
         def dump(*args, **kwargs):
-            '''dump function'''
+            """dump function"""
             retv = func(*args, **kwargs)
             if not isinstance(retv, (_stmt.Stmt, container.LoweredFunc, container.Array)):
                 return retv
@@ -59,7 +59,7 @@ def dump(*args, **kwargs):
         return dump
 
     def decorate_irpass(self):
-        '''decorate ir_pass and ScheduleOps'''
+        """decorate ir_pass and ScheduleOps"""
         self._old_sgpass = schedule.ScheduleOps
         schedule.ScheduleOps = self.decorate(schedule.ScheduleOps)
         vset = vars(ir_pass)
@@ -71,7 +71,7 @@ def recover():
             vset[k] = self.decorate(v) if isinstance(v, types.FunctionType) else v
 
     def decorate_custompass(self):
-        ''' decorate add_lower_pass pass in BuildConfig'''
+        """ decorate add_lower_pass pass in BuildConfig"""
         cfg = BuildConfig.current
         self._old_custom_pass = cfg.add_lower_pass
         custom_pass = cfg.add_lower_pass if cfg.add_lower_pass else []
@@ -79,7 +79,7 @@ def decorate_custompass(self):
         BuildConfig.current.add_lower_pass = pass_list
 
     def enter(self):
-        '''only decorate outermost nest'''
+        """only decorate outermost nest"""
         if DumpIR.scope_level > 0:
             return
         self.decorate_irpass()
@@ -88,7 +88,7 @@ def enter(self):
         DumpIR.scope_level += 1
 
     def exit(self):
-        '''recover outermost nest'''
+        """recover outermost nest"""
         if DumpIR.scope_level > 1:
             return
         # recover decorated functions
@@ -163,6 +163,7 @@ def __setattr__(self, name, value):
                 "'%s' object cannot set attribute '%s'" % (str(type(self)), name))
         return super(BuildConfig, self).__setattr__(name, value)
 
+
 def build_config(**kwargs):
     """Configure the build behavior by setting config variables.
 
@@ -226,6 +227,7 @@ def build_config(**kwargs):
             setattr(config, k, kwargs[k])
     return config
 
+
 if not _RUNTIME_ONLY:
     # BuildConfig is not available in tvm_runtime
     BuildConfig.current = build_config()
@@ -352,8 +354,10 @@ def lower(sch,
         stmt = f(stmt)
     if simple_mode:
         return stmt
+
     return ir_pass.MakeAPI(stmt, name, arg_list, 0, cfg.restricted_func)
 
+
 def build(sch,
           args=None,
           target=None,
diff --git a/src/runtime/c_runtime_api.cc b/src/runtime/c_runtime_api.cc
index 5e3b3e8034ce..9a7005f2e6c7 100644
--- a/src/runtime/c_runtime_api.cc
+++ b/src/runtime/c_runtime_api.cc
@@ -347,6 +347,14 @@ int TVMFuncCreateFromCFunc(TVMPackedCFunc func,
   API_END();
 }
 
+int TVMExtTypeFree(void* handle, int type_code) {
+  API_BEGIN();
+  TVMExtTypeCapsule* cap = static_cast<TVMExtTypeCapsule*>(handle);
+  cap->destroy(cap->ptr);
+  delete cap;
+  API_END();
+}
+
 int TVMArrayAlloc(const tvm_index_t* shape,
                   int ndim,
                   int dtype_code,
diff --git a/src/runtime/module.cc b/src/runtime/module.cc
index c0796a61a20d..d5ece65606b9 100644
--- a/src/runtime/module.cc
+++ b/src/runtime/module.cc
@@ -13,19 +13,6 @@
 namespace tvm {
 namespace runtime {
 
-PackedFunc Module::GetFunction(
-    const std::string& name, bool query_imports) {
-  PackedFunc pf = node_->GetFunction(name, node_);
-  if (pf != nullptr) return pf;
-  if (query_imports) {
-    for (const Module& m : node_->imports_) {
-      pf = m.node_->GetFunction(name, m.node_);
-      if (pf != nullptr) return pf;
-    }
-  }
-  return pf;
-}
-
 void Module::Import(Module other) {
   // specially handle rpc
   if (!std::strcmp((*this)->type_key(), "rpc")) {
diff --git a/src/runtime/registry.cc b/src/runtime/registry.cc
index d7587b6ce1a5..563731a606cf 100644
--- a/src/runtime/registry.cc
+++ b/src/runtime/registry.cc
@@ -22,15 +22,10 @@ struct Registry::Manager {
   // and the resource can become invalid because of indeterminstic order of destruction.
   // The resources will only be recycled during program exit.
   std::unordered_map<std::string, Registry*> fmap;
-  // vtable for extension type
-  std::array<ExtTypeVTable, kExtEnd> ext_vtable;
   // mutex
   std::mutex mutex;
 
   Manager() {
-    for (auto& x : ext_vtable) {
-      x.destroy = nullptr;
-    }
   }
 
   static Manager* Global() {
@@ -88,24 +83,6 @@ std::vector<std::string> Registry::ListNames() {
   return keys;
 }
 
-ExtTypeVTable* ExtTypeVTable::Get(int type_code) {
-  CHECK(type_code > kExtBegin && type_code < kExtEnd);
-  Registry::Manager* m = Registry::Manager::Global();
-  ExtTypeVTable* vt = &(m->ext_vtable[type_code]);
-  CHECK(vt->destroy != nullptr)
-      << "Extension type not registered";
-  return vt;
-}
-
-ExtTypeVTable* ExtTypeVTable::RegisterInternal(
-    int type_code, const ExtTypeVTable& vt) {
-  CHECK(type_code > kExtBegin && type_code < kExtEnd);
-  Registry::Manager* m = Registry::Manager::Global();
-  std::lock_guard<std::mutex>(m->mutex);
-  ExtTypeVTable* pvt = &(m->ext_vtable[type_code]);
-  pvt[0] = vt;
-  return pvt;
-}
 }  // namespace runtime
 }  // namespace tvm
 
@@ -120,12 +97,6 @@ struct TVMFuncThreadLocalEntry {
 /*! \brief Thread local store that can be used to hold return values. */
 typedef dmlc::ThreadLocalStore<TVMFuncThreadLocalEntry> TVMFuncThreadLocalStore;
 
-int TVMExtTypeFree(void* handle, int type_code) {
-  API_BEGIN();
-  tvm::runtime::ExtTypeVTable::Get(type_code)->destroy(handle);
-  API_END();
-}
-
 int TVMFuncRegisterGlobal(
     const char* name, TVMFunctionHandle f, int override) {
   API_BEGIN();
diff --git a/tests/cpp/packed_func_test.cc b/tests/cpp/packed_func_test.cc
index 00e428f258a9..8771a04e5940 100644
--- a/tests/cpp/packed_func_test.cc
+++ b/tests/cpp/packed_func_test.cc
@@ -126,9 +126,6 @@ struct extension_class_info<test::IntVector> {
 }  // runtime
 }  // tvm
 
-// do registration, this need to be in cc file
-TVM_REGISTER_EXT_TYPE(test::IntVector);
-
 TEST(PackedFunc, ExtensionType) {
   using namespace tvm;
   using namespace tvm::runtime;
diff --git a/tests/scripts/task_python_integration.sh b/tests/scripts/task_python_integration.sh
index d10c9a6b127f..7cdade714aa5 100755
--- a/tests/scripts/task_python_integration.sh
+++ b/tests/scripts/task_python_integration.sh
@@ -6,6 +6,7 @@ rm -rf python/tvm/*.pyc python/tvm/*/*.pyc
 
 # Test TVM
 make cython || exit -1
+make cython3 || exit -1
 
 # Test extern package package
 cd apps/extension
diff --git a/topi/src/topi.cc b/topi/src/topi.cc
index d6b67c74bacc..1d73a8fc68dd 100644
--- a/topi/src/topi.cc
+++ b/topi/src/topi.cc
@@ -54,8 +54,6 @@ namespace topi {
 using namespace tvm;
 using namespace tvm::runtime;
 
-TVM_REGISTER_EXT_TYPE(tvm::Target);
-
 /*! \brief Canonicalize an argument that may be Array<Expr> or int to Array<Expr> */
 Array<Expr> ArrayOrInt(TVMArgValue arg) {
   if (arg.type_code() == kDLInt || arg.type_code() == kDLUInt) {

From 3895d3ce258b9c68eb737e2745faca52e53e8ee8 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 22 Feb 2018 20:18:08 -0800
Subject: [PATCH 164/949] Revert "[RUNTIME] Refactor extension type handling,
 now it is header only (#924)" (#925)

This reverts commit 12d15704d7f5d30cff7540f1fd16be64c6baca68.
---
 apps/extension/src/tvm_ext.cc            |  20 +---
 apps/extension/tests/test_ext.py         |   6 --
 include/tvm/runtime/c_runtime_api.h      |  18 ----
 include/tvm/runtime/module.h             |  12 +--
 include/tvm/runtime/packed_func.h        | 122 +++++++++++------------
 python/tvm/_ffi/function.py              |  25 -----
 python/tvm/api.py                        |   2 +-
 python/tvm/build_module.py               |  32 +++---
 src/runtime/c_runtime_api.cc             |   8 --
 src/runtime/module.cc                    |  13 +++
 src/runtime/registry.cc                  |  29 ++++++
 tests/cpp/packed_func_test.cc            |   3 +
 tests/scripts/task_python_integration.sh |   1 -
 topi/src/topi.cc                         |   2 +
 14 files changed, 127 insertions(+), 166 deletions(-)

diff --git a/apps/extension/src/tvm_ext.cc b/apps/extension/src/tvm_ext.cc
index 8b086863f7ed..6d7f4bdf7533 100644
--- a/apps/extension/src/tvm_ext.cc
+++ b/apps/extension/src/tvm_ext.cc
@@ -22,10 +22,13 @@ struct extension_class_info<tvm_ext::IntVector> {
 }  // namespace tvm
 }  // namespace runtime
 
+
+namespace tvm_ext {
+
 using namespace tvm;
 using namespace tvm::runtime;
 
-namespace tvm_ext {
+TVM_REGISTER_EXT_TYPE(IntVector);
 
 TVM_REGISTER_GLOBAL("tvm_ext.ivec_create")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
@@ -63,18 +66,3 @@ TVM_REGISTER_GLOBAL("device_api.ext_dev")
     *rv = (*tvm::runtime::Registry::Get("device_api.cpu"))();
   });
 }  // namespace tvm_ext
-
-// This callback approach allows extension allows tvm to extract
-// This way can be helpful when we want to use a header only
-// minimum version of TVM Runtime.
-extern "C" int TVMExtDeclare(TVMFunctionHandle pregister) {
-  const PackedFunc& fregister =
-      *static_cast<PackedFunc*>(pregister);
-  auto mul = [](TVMArgs args, TVMRetValue *rv) {
-    int x = args[0];
-    int y = args[1];
-    *rv = x * y;
-  };
-  fregister("mul", PackedFunc(mul));
-  return 0;
-}
diff --git a/apps/extension/tests/test_ext.py b/apps/extension/tests/test_ext.py
index 628602f0baea..0bbfff14eeef 100644
--- a/apps/extension/tests/test_ext.py
+++ b/apps/extension/tests/test_ext.py
@@ -44,14 +44,8 @@ def ivec_cb(v2):
 
     tvm.convert(ivec_cb)(ivec)
 
-def test_extract_ext():
-    fdict = tvm.extract_ext_funcs(tvm_ext._LIB.TVMExtDeclare)
-    assert fdict["mul"](3, 4) == 12
-
-
 if __name__ == "__main__":
     test_ext_dev()
     test_ext_vec()
     test_bind_add()
     test_sym_add()
-    test_extract_ext()
diff --git a/include/tvm/runtime/c_runtime_api.h b/include/tvm/runtime/c_runtime_api.h
index e4a06b39d04e..edade00c7ed3 100644
--- a/include/tvm/runtime/c_runtime_api.h
+++ b/include/tvm/runtime/c_runtime_api.h
@@ -24,13 +24,6 @@
 #define TVM_EXTERN_C
 #endif
 
-// Macros to do weak linking
-#ifdef _MSC_VER
-#define TVM_WEAK __declspec(selectany)
-#else
-#define TVM_WEAK __attribute__((weak))
-#endif
-
 #ifdef __EMSCRIPTEN__
 #include <emscripten/emscripten.h>
 #define TVM_DLL EMSCRIPTEN_KEEPALIVE
@@ -320,17 +313,6 @@ typedef int (*TVMPackedCFunc)(
  */
 typedef void (*TVMPackedCFuncFinalizer)(void* resource_handle);
 
-/*!
- * \brief Signature for extension function declarer.
- *
- *  TVM call this function to get the extension functions
- *  The declarer will call register_func to register function and their name.
- *
- * \param resource_func_handle The register function
- * \return 0 if success, -1 if failure happens
- */
-typedef int (*TVMExtensionFuncDeclarer)(TVMFunctionHandle register_func_handle);
-
 /*!
  * \brief Wrap a TVMPackedCFunc to become a FunctionHandle.
  *
diff --git a/include/tvm/runtime/module.h b/include/tvm/runtime/module.h
index f8e5069f56c5..3d0991034c41 100644
--- a/include/tvm/runtime/module.h
+++ b/include/tvm/runtime/module.h
@@ -38,14 +38,8 @@ class Module {
    * \param query_imports Whether also query dependency modules.
    * \return The result function.
    *  This function will return PackedFunc(nullptr) if function do not exist.
-   * \note Implemented in packed_func.cc
    */
-  inline PackedFunc GetFunction(const std::string& name, bool query_imports = false);
-  /*! \return internal container */
-  inline ModuleNode* operator->();
-  /*! \return internal container */
-  inline const ModuleNode* operator->() const;
-  // The following functions requires link with runtime.
+  TVM_DLL PackedFunc GetFunction(const std::string& name, bool query_imports = false);
   /*!
    * \brief Import another module into this module.
    * \param other The module to be imported.
@@ -63,6 +57,10 @@ class Module {
    */
   TVM_DLL static Module LoadFromFile(const std::string& file_name,
                                      const std::string& format = "");
+  /*! \return internal container */
+  inline ModuleNode* operator->();
+  /*! \return internal container */
+  inline const ModuleNode* operator->() const;
 
  private:
   std::shared_ptr<ModuleNode> node_;
diff --git a/include/tvm/runtime/packed_func.h b/include/tvm/runtime/packed_func.h
index ca2e020ba703..b01e662b97ab 100644
--- a/include/tvm/runtime/packed_func.h
+++ b/include/tvm/runtime/packed_func.h
@@ -183,17 +183,31 @@ struct extension_class_info {
 };
 
 /*!
- * \brief Capsule structure holding extension types
- *  Capsule is self-contained and include
- *  all the information to clone and destroy the type.
+ * \brief Runtime function table about extension type.
  */
-struct TVMExtTypeCapsule {
-  /*! \brief The pointer to the object */
-  void* ptr;
+class ExtTypeVTable {
+ public:
   /*! \brief function to be called to delete a handle */
   void (*destroy)(void* handle);
   /*! \brief function to be called when clone a handle */
   void* (*clone)(void* handle);
+  /*!
+   * \brief Register type
+   * \tparam T The type to be register.
+   * \return The registered vtable.
+   */
+  template <typename T>
+  static inline ExtTypeVTable* Register_();
+  /*!
+   * \brief Get a vtable based on type code.
+   * \param type_code The type code
+   * \return The registered vtable.
+   */
+  TVM_DLL static ExtTypeVTable* Get(int type_code);
+
+ private:
+  // Internal registration function.
+  TVM_DLL static ExtTypeVTable* RegisterInternal(int type_code, const ExtTypeVTable& vt);
 };
 
 /*!
@@ -241,9 +255,8 @@ class TVMPODValue_ {
   }
   template<typename TExtension>
   const TExtension& AsExtension() const {
-    CHECK_EQ(type_code_, extension_class_info<TExtension>::code);
-    return static_cast<TExtension*>(
-        static_cast<TVMExtTypeCapsule*>(value_.v_handle)->ptr)[0];
+    CHECK_LT(type_code_, kExtEnd);
+    return static_cast<TExtension*>(value_.v_handle)[0];
   }
   int type_code() const {
     return type_code_;
@@ -475,6 +488,14 @@ class TVMRetValue : public TVMPODValue_ {
     this->Assign(other);
     return *this;
   }
+  template<typename T,
+           typename = typename std::enable_if<
+             extension_class_info<T>::code != 0>::type>
+  TVMRetValue& operator=(const T& other) {
+    this->SwitchToClass<T>(
+        extension_class_info<T>::code, other);
+    return *this;
+  }
   /*!
    * \brief Move the value back to front-end via C API.
    *  This marks the current container as null.
@@ -500,11 +521,6 @@ class TVMRetValue : public TVMPODValue_ {
           type_code_ != kStr) << "TVMRetValue.value can only be used for POD data";
     return value_;
   }
-  // assign extension
-  template<typename T,
-           typename = typename std::enable_if<
-             extension_class_info<T>::code != 0>::type>
-  inline TVMRetValue& operator=(const T& other);
   // NodeRef related extenstions: in tvm/packed_func_ext.h
   template<typename T,
            typename = typename std::enable_if<
@@ -548,9 +564,11 @@ class TVMRetValue : public TVMPODValue_ {
           SwitchToPOD(other.type_code());
           value_ = other.value_;
         } else {
-          TVMExtTypeCapsule cap = *other.template ptr<TVMExtTypeCapsule>();
-          cap.ptr = cap.clone(cap.ptr);
-          SwitchToClass<TVMExtTypeCapsule>(other.type_code(), cap);
+          this->Clear();
+          type_code_ = other.type_code();
+          value_.v_handle =
+              (*(ExtTypeVTable::Get(other.type_code())->clone))(
+                  other.value().v_handle);
         }
         break;
       }
@@ -582,9 +600,7 @@ class TVMRetValue : public TVMPODValue_ {
       case kNodeHandle: delete ptr<std::shared_ptr<Node> >(); break;
     }
     if (type_code_ > kExtBegin) {
-      TVMExtTypeCapsule *cap = ptr<TVMExtTypeCapsule>();
-      cap->destroy(cap->ptr);
-      delete cap;
+      (*(ExtTypeVTable::Get(type_code_)->destroy))(value_.v_handle);
     }
     type_code_ = kNull;
   }
@@ -700,10 +716,8 @@ inline void for_each(const F& f, Args&&... args) {  // NOLINT(*)
 /* \brief argument settter to PackedFunc */
 class TVMArgsSetter {
  public:
-  TVMArgsSetter(TVMValue* values,
-                int* type_codes,
-                TVMExtTypeCapsule* exts)
-      : values_(values), type_codes_(type_codes), exts_(exts) {}
+  TVMArgsSetter(TVMValue* values, int* type_codes)
+      : values_(values), type_codes_(type_codes) {}
   // setters for POD types
   template<typename T,
            typename = typename std::enable_if<
@@ -793,21 +807,15 @@ class TVMArgsSetter {
   TVMValue* values_;
   /*! \brief The type code fields */
   int* type_codes_;
-  /*! \brief Temporary storage for extension types */
-  TVMExtTypeCapsule* exts_;
 };
 
 template<typename... Args>
 inline TVMRetValue PackedFunc::operator()(Args&& ...args) const {
   const int kNumArgs = sizeof...(Args);
-  // Compiler will remove an static array when it is not touched.
   const int kArraySize = kNumArgs > 0 ? kNumArgs : 1;
   TVMValue values[kArraySize];
   int type_codes[kArraySize];
-  // If the function call does not contain extension type,
-  // exts will get optimized away by compiler.
-  TVMExtTypeCapsule exts[kArraySize];
-  detail::for_each(TVMArgsSetter(values, type_codes, exts),
+  detail::for_each(TVMArgsSetter(values, type_codes),
                    std::forward<Args>(args)...);
   TVMRetValue rv;
   body_(TVMArgs(values, type_codes, kNumArgs), &rv);
@@ -845,6 +853,14 @@ inline TVMRetValue::operator T() const {
       ::Apply(this);
 }
 
+template<typename T, typename>
+inline void TVMArgsSetter::operator()(size_t i, const T& value) const {
+  static_assert(extension_class_info<T>::code != 0,
+                "Need to have extesion code");
+  type_codes_[i] = extension_class_info<T>::code;
+  values_[i].v_handle = const_cast<T*>(&value);
+}
+
 // extension type handling
 template<typename T>
 struct ExtTypeInfo {
@@ -856,42 +872,16 @@ struct ExtTypeInfo {
   }
 };
 
-template<typename T, typename>
-inline TVMRetValue& TVMRetValue::operator=(const T& other) {
-  TVMExtTypeCapsule cap;
-  cap.clone = ExtTypeInfo<T>::clone;
-  cap.destroy = ExtTypeInfo<T>::destroy;
-  cap.ptr = new T(other);
-  SwitchToClass<TVMExtTypeCapsule>(
-      extension_class_info<T>::code, cap);
-  return *this;
-}
-
-template<typename T, typename>
-inline void TVMArgsSetter::operator()(size_t i, const T& value) const {
-  static_assert(extension_class_info<T>::code != 0,
-                "Need to have extesion code");
-  type_codes_[i] = extension_class_info<T>::code;
-  exts_[i].clone = ExtTypeInfo<T>::clone;
-  exts_[i].destroy = ExtTypeInfo<T>::destroy;
-  exts_[i].ptr = const_cast<T*>(&value);
-  values_[i].v_handle = &exts_[i];
-}
-
-// Implement Module::GetFunction
-// Put implementation in this file so we have seen the PackedFunc
-inline PackedFunc Module::GetFunction(const std::string& name, bool query_imports) {
-  PackedFunc pf = node_->GetFunction(name, node_);
-  if (pf != nullptr) return pf;
-  if (query_imports) {
-    for (const Module& m : node_->imports_) {
-      pf = m.node_->GetFunction(name, m.node_);
-      if (pf != nullptr) return pf;
-    }
-  }
-  return pf;
+template<typename T>
+inline ExtTypeVTable* ExtTypeVTable::Register_() {
+  const int code = extension_class_info<T>::code;
+  static_assert(code != 0,
+                "require extension_class_info traits to be declared with non-zero code");
+  ExtTypeVTable vt;
+  vt.clone = ExtTypeInfo<T>::clone;
+  vt.destroy = ExtTypeInfo<T>::destroy;
+  return ExtTypeVTable::RegisterInternal(code, vt);
 }
-
 }  // namespace runtime
 }  // namespace tvm
 #endif  // TVM_RUNTIME_PACKED_FUNC_H_
diff --git a/python/tvm/_ffi/function.py b/python/tvm/_ffi/function.py
index 526d972f6d28..2edb355fb721 100644
--- a/python/tvm/_ffi/function.py
+++ b/python/tvm/_ffi/function.py
@@ -234,31 +234,6 @@ def list_global_func_names():
     return fnames
 
 
-def extract_ext_funcs(finit):
-    """
-    Extract the extension PackedFuncs from a C module.
-
-    Parameters
-    ----------
-    finit : ctypes function
-        a ctypes that takes signature of TVMExtensionDeclarer
-
-    Returns
-    -------
-    fdict : dict of str to Function
-        The extracted functions
-    """
-    fdict = {}
-    def _list(name, func):
-        fdict[name] = func
-    myf = convert_to_tvm_func(_list)
-    ret = finit(myf.handle)
-    _ = myf
-    if ret != 0:
-        raise RuntimeError("cannot initialize with %s" % finit)
-    return fdict
-
-
 def _get_api(f):
     flocal = f
     flocal.is_global = True
diff --git a/python/tvm/api.py b/python/tvm/api.py
index 66c154bc9f00..7c90b0ec9481 100644
--- a/python/tvm/api.py
+++ b/python/tvm/api.py
@@ -8,7 +8,7 @@
 from ._ffi.node import register_node, NodeBase
 from ._ffi.node import convert_to_node as _convert_to_node
 from ._ffi.function import Function
-from ._ffi.function import _init_api, register_func, get_global_func, extract_ext_funcs
+from ._ffi.function import _init_api, register_func, get_global_func
 from ._ffi.function import convert_to_tvm_func as _convert_tvm_func
 from ._ffi.runtime_ctypes import TVMType
 from . import _api_internal
diff --git a/python/tvm/build_module.py b/python/tvm/build_module.py
index d868e2e0df12..86d150c08e5f 100755
--- a/python/tvm/build_module.py
+++ b/python/tvm/build_module.py
@@ -23,16 +23,16 @@
 from . import make
 
 class DumpIR(object):
-    """
-    Dump IR for each pass.
-    With it, you can dump ir just like gcc/llvm.
+    """Dump IR for each pass.
+       With it, you can dump ir just like gcc/llvm.
+
+       How to use:
+       -----------
+       .. code-block:: python
 
-    How to use:
-    -----------
-    .. code-block:: python
+          with tvm.build_config(dump_pass_ir=True)
+              run()
 
-    with tvm.build_config(dump_pass_ir=True)
-        run()
     """
     scope_level = 0
     def __init__(self):
@@ -40,9 +40,9 @@ def __init__(self):
         self._recover_list = []
 
     def decorate(self, func):
-        """ decorate the pass function"""
+        ''' decorate the pass function'''
         def dump(*args, **kwargs):
-            """dump function"""
+            '''dump function'''
             retv = func(*args, **kwargs)
             if not isinstance(retv, (_stmt.Stmt, container.LoweredFunc, container.Array)):
                 return retv
@@ -59,7 +59,7 @@ def dump(*args, **kwargs):
         return dump
 
     def decorate_irpass(self):
-        """decorate ir_pass and ScheduleOps"""
+        '''decorate ir_pass and ScheduleOps'''
         self._old_sgpass = schedule.ScheduleOps
         schedule.ScheduleOps = self.decorate(schedule.ScheduleOps)
         vset = vars(ir_pass)
@@ -71,7 +71,7 @@ def recover():
             vset[k] = self.decorate(v) if isinstance(v, types.FunctionType) else v
 
     def decorate_custompass(self):
-        """ decorate add_lower_pass pass in BuildConfig"""
+        ''' decorate add_lower_pass pass in BuildConfig'''
         cfg = BuildConfig.current
         self._old_custom_pass = cfg.add_lower_pass
         custom_pass = cfg.add_lower_pass if cfg.add_lower_pass else []
@@ -79,7 +79,7 @@ def decorate_custompass(self):
         BuildConfig.current.add_lower_pass = pass_list
 
     def enter(self):
-        """only decorate outermost nest"""
+        '''only decorate outermost nest'''
         if DumpIR.scope_level > 0:
             return
         self.decorate_irpass()
@@ -88,7 +88,7 @@ def enter(self):
         DumpIR.scope_level += 1
 
     def exit(self):
-        """recover outermost nest"""
+        '''recover outermost nest'''
         if DumpIR.scope_level > 1:
             return
         # recover decorated functions
@@ -163,7 +163,6 @@ def __setattr__(self, name, value):
                 "'%s' object cannot set attribute '%s'" % (str(type(self)), name))
         return super(BuildConfig, self).__setattr__(name, value)
 
-
 def build_config(**kwargs):
     """Configure the build behavior by setting config variables.
 
@@ -227,7 +226,6 @@ def build_config(**kwargs):
             setattr(config, k, kwargs[k])
     return config
 
-
 if not _RUNTIME_ONLY:
     # BuildConfig is not available in tvm_runtime
     BuildConfig.current = build_config()
@@ -354,10 +352,8 @@ def lower(sch,
         stmt = f(stmt)
     if simple_mode:
         return stmt
-
     return ir_pass.MakeAPI(stmt, name, arg_list, 0, cfg.restricted_func)
 
-
 def build(sch,
           args=None,
           target=None,
diff --git a/src/runtime/c_runtime_api.cc b/src/runtime/c_runtime_api.cc
index 9a7005f2e6c7..5e3b3e8034ce 100644
--- a/src/runtime/c_runtime_api.cc
+++ b/src/runtime/c_runtime_api.cc
@@ -347,14 +347,6 @@ int TVMFuncCreateFromCFunc(TVMPackedCFunc func,
   API_END();
 }
 
-int TVMExtTypeFree(void* handle, int type_code) {
-  API_BEGIN();
-  TVMExtTypeCapsule* cap = static_cast<TVMExtTypeCapsule*>(handle);
-  cap->destroy(cap->ptr);
-  delete cap;
-  API_END();
-}
-
 int TVMArrayAlloc(const tvm_index_t* shape,
                   int ndim,
                   int dtype_code,
diff --git a/src/runtime/module.cc b/src/runtime/module.cc
index d5ece65606b9..c0796a61a20d 100644
--- a/src/runtime/module.cc
+++ b/src/runtime/module.cc
@@ -13,6 +13,19 @@
 namespace tvm {
 namespace runtime {
 
+PackedFunc Module::GetFunction(
+    const std::string& name, bool query_imports) {
+  PackedFunc pf = node_->GetFunction(name, node_);
+  if (pf != nullptr) return pf;
+  if (query_imports) {
+    for (const Module& m : node_->imports_) {
+      pf = m.node_->GetFunction(name, m.node_);
+      if (pf != nullptr) return pf;
+    }
+  }
+  return pf;
+}
+
 void Module::Import(Module other) {
   // specially handle rpc
   if (!std::strcmp((*this)->type_key(), "rpc")) {
diff --git a/src/runtime/registry.cc b/src/runtime/registry.cc
index 563731a606cf..d7587b6ce1a5 100644
--- a/src/runtime/registry.cc
+++ b/src/runtime/registry.cc
@@ -22,10 +22,15 @@ struct Registry::Manager {
   // and the resource can become invalid because of indeterminstic order of destruction.
   // The resources will only be recycled during program exit.
   std::unordered_map<std::string, Registry*> fmap;
+  // vtable for extension type
+  std::array<ExtTypeVTable, kExtEnd> ext_vtable;
   // mutex
   std::mutex mutex;
 
   Manager() {
+    for (auto& x : ext_vtable) {
+      x.destroy = nullptr;
+    }
   }
 
   static Manager* Global() {
@@ -83,6 +88,24 @@ std::vector<std::string> Registry::ListNames() {
   return keys;
 }
 
+ExtTypeVTable* ExtTypeVTable::Get(int type_code) {
+  CHECK(type_code > kExtBegin && type_code < kExtEnd);
+  Registry::Manager* m = Registry::Manager::Global();
+  ExtTypeVTable* vt = &(m->ext_vtable[type_code]);
+  CHECK(vt->destroy != nullptr)
+      << "Extension type not registered";
+  return vt;
+}
+
+ExtTypeVTable* ExtTypeVTable::RegisterInternal(
+    int type_code, const ExtTypeVTable& vt) {
+  CHECK(type_code > kExtBegin && type_code < kExtEnd);
+  Registry::Manager* m = Registry::Manager::Global();
+  std::lock_guard<std::mutex>(m->mutex);
+  ExtTypeVTable* pvt = &(m->ext_vtable[type_code]);
+  pvt[0] = vt;
+  return pvt;
+}
 }  // namespace runtime
 }  // namespace tvm
 
@@ -97,6 +120,12 @@ struct TVMFuncThreadLocalEntry {
 /*! \brief Thread local store that can be used to hold return values. */
 typedef dmlc::ThreadLocalStore<TVMFuncThreadLocalEntry> TVMFuncThreadLocalStore;
 
+int TVMExtTypeFree(void* handle, int type_code) {
+  API_BEGIN();
+  tvm::runtime::ExtTypeVTable::Get(type_code)->destroy(handle);
+  API_END();
+}
+
 int TVMFuncRegisterGlobal(
     const char* name, TVMFunctionHandle f, int override) {
   API_BEGIN();
diff --git a/tests/cpp/packed_func_test.cc b/tests/cpp/packed_func_test.cc
index 8771a04e5940..00e428f258a9 100644
--- a/tests/cpp/packed_func_test.cc
+++ b/tests/cpp/packed_func_test.cc
@@ -126,6 +126,9 @@ struct extension_class_info<test::IntVector> {
 }  // runtime
 }  // tvm
 
+// do registration, this need to be in cc file
+TVM_REGISTER_EXT_TYPE(test::IntVector);
+
 TEST(PackedFunc, ExtensionType) {
   using namespace tvm;
   using namespace tvm::runtime;
diff --git a/tests/scripts/task_python_integration.sh b/tests/scripts/task_python_integration.sh
index 7cdade714aa5..d10c9a6b127f 100755
--- a/tests/scripts/task_python_integration.sh
+++ b/tests/scripts/task_python_integration.sh
@@ -6,7 +6,6 @@ rm -rf python/tvm/*.pyc python/tvm/*/*.pyc
 
 # Test TVM
 make cython || exit -1
-make cython3 || exit -1
 
 # Test extern package package
 cd apps/extension
diff --git a/topi/src/topi.cc b/topi/src/topi.cc
index 1d73a8fc68dd..d6b67c74bacc 100644
--- a/topi/src/topi.cc
+++ b/topi/src/topi.cc
@@ -54,6 +54,8 @@ namespace topi {
 using namespace tvm;
 using namespace tvm::runtime;
 
+TVM_REGISTER_EXT_TYPE(tvm::Target);
+
 /*! \brief Canonicalize an argument that may be Array<Expr> or int to Array<Expr> */
 Array<Expr> ArrayOrInt(TVMArgValue arg) {
   if (arg.type_code() == kDLInt || arg.type_code() == kDLUInt) {

From 1a51fe12639a6b628484827e804ba00b8880bab6 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 23 Feb 2018 10:24:17 -0800
Subject: [PATCH 165/949] [EXT] Allow easy extraction of extern module (#926)

---
 apps/extension/src/tvm_ext.cc            | 20 ++++++++++++++---
 apps/extension/tests/test_ext.py         |  6 +++++
 include/tvm/runtime/c_runtime_api.h      | 18 +++++++++++++++
 include/tvm/runtime/module.h             | 12 +++++-----
 include/tvm/runtime/packed_func.h        | 27 +++++++++++++++++++++++
 python/tvm/_ffi/function.py              | 25 +++++++++++++++++++++
 python/tvm/api.py                        |  2 +-
 python/tvm/build_module.py               | 28 ++++++++++++------------
 src/runtime/module.cc                    | 13 -----------
 tests/scripts/task_python_integration.sh |  1 +
 10 files changed, 116 insertions(+), 36 deletions(-)

diff --git a/apps/extension/src/tvm_ext.cc b/apps/extension/src/tvm_ext.cc
index 6d7f4bdf7533..bb8b4b694187 100644
--- a/apps/extension/src/tvm_ext.cc
+++ b/apps/extension/src/tvm_ext.cc
@@ -22,12 +22,11 @@ struct extension_class_info<tvm_ext::IntVector> {
 }  // namespace tvm
 }  // namespace runtime
 
-
-namespace tvm_ext {
-
 using namespace tvm;
 using namespace tvm::runtime;
 
+namespace tvm_ext {
+
 TVM_REGISTER_EXT_TYPE(IntVector);
 
 TVM_REGISTER_GLOBAL("tvm_ext.ivec_create")
@@ -66,3 +65,18 @@ TVM_REGISTER_GLOBAL("device_api.ext_dev")
     *rv = (*tvm::runtime::Registry::Get("device_api.cpu"))();
   });
 }  // namespace tvm_ext
+
+// This callback approach allows extension allows tvm to extract
+// This way can be helpful when we want to use a header only
+// minimum version of TVM Runtime.
+extern "C" int TVMExtDeclare(TVMFunctionHandle pregister) {
+  const PackedFunc& fregister =
+      *static_cast<PackedFunc*>(pregister);
+  auto mul = [](TVMArgs args, TVMRetValue *rv) {
+    int x = args[0];
+    int y = args[1];
+    *rv = x * y;
+  };
+  fregister("mul", PackedFunc(mul));
+  return 0;
+}
diff --git a/apps/extension/tests/test_ext.py b/apps/extension/tests/test_ext.py
index 0bbfff14eeef..628602f0baea 100644
--- a/apps/extension/tests/test_ext.py
+++ b/apps/extension/tests/test_ext.py
@@ -44,8 +44,14 @@ def ivec_cb(v2):
 
     tvm.convert(ivec_cb)(ivec)
 
+def test_extract_ext():
+    fdict = tvm.extract_ext_funcs(tvm_ext._LIB.TVMExtDeclare)
+    assert fdict["mul"](3, 4) == 12
+
+
 if __name__ == "__main__":
     test_ext_dev()
     test_ext_vec()
     test_bind_add()
     test_sym_add()
+    test_extract_ext()
diff --git a/include/tvm/runtime/c_runtime_api.h b/include/tvm/runtime/c_runtime_api.h
index edade00c7ed3..e4a06b39d04e 100644
--- a/include/tvm/runtime/c_runtime_api.h
+++ b/include/tvm/runtime/c_runtime_api.h
@@ -24,6 +24,13 @@
 #define TVM_EXTERN_C
 #endif
 
+// Macros to do weak linking
+#ifdef _MSC_VER
+#define TVM_WEAK __declspec(selectany)
+#else
+#define TVM_WEAK __attribute__((weak))
+#endif
+
 #ifdef __EMSCRIPTEN__
 #include <emscripten/emscripten.h>
 #define TVM_DLL EMSCRIPTEN_KEEPALIVE
@@ -313,6 +320,17 @@ typedef int (*TVMPackedCFunc)(
  */
 typedef void (*TVMPackedCFuncFinalizer)(void* resource_handle);
 
+/*!
+ * \brief Signature for extension function declarer.
+ *
+ *  TVM call this function to get the extension functions
+ *  The declarer will call register_func to register function and their name.
+ *
+ * \param resource_func_handle The register function
+ * \return 0 if success, -1 if failure happens
+ */
+typedef int (*TVMExtensionFuncDeclarer)(TVMFunctionHandle register_func_handle);
+
 /*!
  * \brief Wrap a TVMPackedCFunc to become a FunctionHandle.
  *
diff --git a/include/tvm/runtime/module.h b/include/tvm/runtime/module.h
index 3d0991034c41..f8e5069f56c5 100644
--- a/include/tvm/runtime/module.h
+++ b/include/tvm/runtime/module.h
@@ -38,8 +38,14 @@ class Module {
    * \param query_imports Whether also query dependency modules.
    * \return The result function.
    *  This function will return PackedFunc(nullptr) if function do not exist.
+   * \note Implemented in packed_func.cc
    */
-  TVM_DLL PackedFunc GetFunction(const std::string& name, bool query_imports = false);
+  inline PackedFunc GetFunction(const std::string& name, bool query_imports = false);
+  /*! \return internal container */
+  inline ModuleNode* operator->();
+  /*! \return internal container */
+  inline const ModuleNode* operator->() const;
+  // The following functions requires link with runtime.
   /*!
    * \brief Import another module into this module.
    * \param other The module to be imported.
@@ -57,10 +63,6 @@ class Module {
    */
   TVM_DLL static Module LoadFromFile(const std::string& file_name,
                                      const std::string& format = "");
-  /*! \return internal container */
-  inline ModuleNode* operator->();
-  /*! \return internal container */
-  inline const ModuleNode* operator->() const;
 
  private:
   std::shared_ptr<ModuleNode> node_;
diff --git a/include/tvm/runtime/packed_func.h b/include/tvm/runtime/packed_func.h
index b01e662b97ab..e6cc3cddecd1 100644
--- a/include/tvm/runtime/packed_func.h
+++ b/include/tvm/runtime/packed_func.h
@@ -24,6 +24,11 @@ struct Type;
 struct Expr;
 }
 
+// Whether use TVM runtime in header only mode.
+#ifndef TVM_RUNTIME_HEADER_ONLY
+#define TVM_RUNTIME_HEADER_ONLY 0
+#endif
+
 namespace tvm {
 // Forward declare NodeRef and Node for extensions.
 // This header works fine without depend on NodeRef
@@ -564,11 +569,15 @@ class TVMRetValue : public TVMPODValue_ {
           SwitchToPOD(other.type_code());
           value_ = other.value_;
         } else {
+#if TVM_RUNTIME_HEADER_ONLY
+          LOG(FATAL) << "Header only mode do not support ext type";
+#else
           this->Clear();
           type_code_ = other.type_code();
           value_.v_handle =
               (*(ExtTypeVTable::Get(other.type_code())->clone))(
                   other.value().v_handle);
+#endif
         }
         break;
       }
@@ -600,7 +609,11 @@ class TVMRetValue : public TVMPODValue_ {
       case kNodeHandle: delete ptr<std::shared_ptr<Node> >(); break;
     }
     if (type_code_ > kExtBegin) {
+#if TVM_RUNTIME_HEADER_ONLY
+          LOG(FATAL) << "Header only mode do not support ext type";
+#else
       (*(ExtTypeVTable::Get(type_code_)->destroy))(value_.v_handle);
+#endif
     }
     type_code_ = kNull;
   }
@@ -882,6 +895,20 @@ inline ExtTypeVTable* ExtTypeVTable::Register_() {
   vt.destroy = ExtTypeInfo<T>::destroy;
   return ExtTypeVTable::RegisterInternal(code, vt);
 }
+
+// Implement Module::GetFunction
+// Put implementation in this file so we have seen the PackedFunc
+inline PackedFunc Module::GetFunction(const std::string& name, bool query_imports) {
+  PackedFunc pf = node_->GetFunction(name, node_);
+  if (pf != nullptr) return pf;
+  if (query_imports) {
+    for (const Module& m : node_->imports_) {
+      pf = m.node_->GetFunction(name, m.node_);
+      if (pf != nullptr) return pf;
+    }
+  }
+  return pf;
+}
 }  // namespace runtime
 }  // namespace tvm
 #endif  // TVM_RUNTIME_PACKED_FUNC_H_
diff --git a/python/tvm/_ffi/function.py b/python/tvm/_ffi/function.py
index 2edb355fb721..526d972f6d28 100644
--- a/python/tvm/_ffi/function.py
+++ b/python/tvm/_ffi/function.py
@@ -234,6 +234,31 @@ def list_global_func_names():
     return fnames
 
 
+def extract_ext_funcs(finit):
+    """
+    Extract the extension PackedFuncs from a C module.
+
+    Parameters
+    ----------
+    finit : ctypes function
+        a ctypes that takes signature of TVMExtensionDeclarer
+
+    Returns
+    -------
+    fdict : dict of str to Function
+        The extracted functions
+    """
+    fdict = {}
+    def _list(name, func):
+        fdict[name] = func
+    myf = convert_to_tvm_func(_list)
+    ret = finit(myf.handle)
+    _ = myf
+    if ret != 0:
+        raise RuntimeError("cannot initialize with %s" % finit)
+    return fdict
+
+
 def _get_api(f):
     flocal = f
     flocal.is_global = True
diff --git a/python/tvm/api.py b/python/tvm/api.py
index 7c90b0ec9481..66c154bc9f00 100644
--- a/python/tvm/api.py
+++ b/python/tvm/api.py
@@ -8,7 +8,7 @@
 from ._ffi.node import register_node, NodeBase
 from ._ffi.node import convert_to_node as _convert_to_node
 from ._ffi.function import Function
-from ._ffi.function import _init_api, register_func, get_global_func
+from ._ffi.function import _init_api, register_func, get_global_func, extract_ext_funcs
 from ._ffi.function import convert_to_tvm_func as _convert_tvm_func
 from ._ffi.runtime_ctypes import TVMType
 from . import _api_internal
diff --git a/python/tvm/build_module.py b/python/tvm/build_module.py
index 86d150c08e5f..7b6fa7715a7b 100755
--- a/python/tvm/build_module.py
+++ b/python/tvm/build_module.py
@@ -23,16 +23,16 @@
 from . import make
 
 class DumpIR(object):
-    """Dump IR for each pass.
-       With it, you can dump ir just like gcc/llvm.
-
-       How to use:
-       -----------
-       .. code-block:: python
+    """
+    Dump IR for each pass.
+    With it, you can dump ir just like gcc/llvm.
 
-          with tvm.build_config(dump_pass_ir=True)
-              run()
+    How to use:
+    -----------
+    .. code-block:: python
 
+        with tvm.build_config(dump_pass_ir=True)
+            run()
     """
     scope_level = 0
     def __init__(self):
@@ -40,9 +40,9 @@ def __init__(self):
         self._recover_list = []
 
     def decorate(self, func):
-        ''' decorate the pass function'''
+        """ decorate the pass function"""
         def dump(*args, **kwargs):
-            '''dump function'''
+            """dump function"""
             retv = func(*args, **kwargs)
             if not isinstance(retv, (_stmt.Stmt, container.LoweredFunc, container.Array)):
                 return retv
@@ -59,7 +59,7 @@ def dump(*args, **kwargs):
         return dump
 
     def decorate_irpass(self):
-        '''decorate ir_pass and ScheduleOps'''
+        """decorate ir_pass and ScheduleOps"""
         self._old_sgpass = schedule.ScheduleOps
         schedule.ScheduleOps = self.decorate(schedule.ScheduleOps)
         vset = vars(ir_pass)
@@ -71,7 +71,7 @@ def recover():
             vset[k] = self.decorate(v) if isinstance(v, types.FunctionType) else v
 
     def decorate_custompass(self):
-        ''' decorate add_lower_pass pass in BuildConfig'''
+        """ decorate add_lower_pass pass in BuildConfig"""
         cfg = BuildConfig.current
         self._old_custom_pass = cfg.add_lower_pass
         custom_pass = cfg.add_lower_pass if cfg.add_lower_pass else []
@@ -79,7 +79,7 @@ def decorate_custompass(self):
         BuildConfig.current.add_lower_pass = pass_list
 
     def enter(self):
-        '''only decorate outermost nest'''
+        """only decorate outermost nest"""
         if DumpIR.scope_level > 0:
             return
         self.decorate_irpass()
@@ -88,7 +88,7 @@ def enter(self):
         DumpIR.scope_level += 1
 
     def exit(self):
-        '''recover outermost nest'''
+        """recover outermost nest"""
         if DumpIR.scope_level > 1:
             return
         # recover decorated functions
diff --git a/src/runtime/module.cc b/src/runtime/module.cc
index c0796a61a20d..d5ece65606b9 100644
--- a/src/runtime/module.cc
+++ b/src/runtime/module.cc
@@ -13,19 +13,6 @@
 namespace tvm {
 namespace runtime {
 
-PackedFunc Module::GetFunction(
-    const std::string& name, bool query_imports) {
-  PackedFunc pf = node_->GetFunction(name, node_);
-  if (pf != nullptr) return pf;
-  if (query_imports) {
-    for (const Module& m : node_->imports_) {
-      pf = m.node_->GetFunction(name, m.node_);
-      if (pf != nullptr) return pf;
-    }
-  }
-  return pf;
-}
-
 void Module::Import(Module other) {
   // specially handle rpc
   if (!std::strcmp((*this)->type_key(), "rpc")) {
diff --git a/tests/scripts/task_python_integration.sh b/tests/scripts/task_python_integration.sh
index d10c9a6b127f..7cdade714aa5 100755
--- a/tests/scripts/task_python_integration.sh
+++ b/tests/scripts/task_python_integration.sh
@@ -6,6 +6,7 @@ rm -rf python/tvm/*.pyc python/tvm/*/*.pyc
 
 # Test TVM
 make cython || exit -1
+make cython3 || exit -1
 
 # Test extern package package
 cd apps/extension

From d9e4ccce51dd9ade60b5d638548e42d48331e9d4 Mon Sep 17 00:00:00 2001
From: Jammy Zhou <jammy.zhou@gmail.com>
Date: Sun, 25 Feb 2018 00:34:03 +0000
Subject: [PATCH 166/949] Fix a typo for function registration (#927)

---
 python/tvm/target.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/python/tvm/target.py b/python/tvm/target.py
index 5966326c71eb..ab02619f6deb 100644
--- a/python/tvm/target.py
+++ b/python/tvm/target.py
@@ -212,7 +212,7 @@ def _do_reg(myf):
                 dispatch_dict[k] = myf
             return myf
         if func:
-            return _do_reg(myf)
+            return _do_reg(func)
         return _do_reg
 
     def dispatch_func(func, *args, **kwargs):

From 30eaf463e34d7c301357c31a010945d11df16537 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 25 Feb 2018 11:45:45 -0800
Subject: [PATCH 167/949] MXNet NDArray bridge. (#930)

* MXNet NDArray bridge.
Support convert a tvm Function as MXNet's async NDArray function.

* fix lint

* update comment
---
 python/tvm/contrib/mxnet.py               | 59 +++++++++++++++++++++++
 src/api/api_base.cc                       |  5 ++
 tests/python/contrib/test_mxnet_bridge.py | 48 ++++++++++++++++++
 3 files changed, 112 insertions(+)
 create mode 100644 python/tvm/contrib/mxnet.py
 create mode 100644 tests/python/contrib/test_mxnet_bridge.py

diff --git a/python/tvm/contrib/mxnet.py b/python/tvm/contrib/mxnet.py
new file mode 100644
index 000000000000..3a6c92f1b880
--- /dev/null
+++ b/python/tvm/contrib/mxnet.py
@@ -0,0 +1,59 @@
+"""MXNet bridge wrap Function MXNet's async function."""
+from __future__ import absolute_import as _abs
+
+from .. import api, _api_internal, ndarray
+from ..module import Module
+
+# pylint: disable=invalid-name
+_wrap_async = None
+
+
+def to_mxnet_func(func, const_loc=None):
+    """Wrap a TVM function as MXNet function
+
+    MXNet function runs asynchrously via its engine.
+
+    Parameters
+    ----------
+    func : Function
+        A TVM function that can take positional arguments
+
+    const_loc : list of int
+        List of integers indicating the argument position
+        of read only NDArray argument.
+        The NDArray argument location that are not annotated
+        will be viewed as mutable arrays in MXNet's engine.
+
+    Returns
+    -------
+    async_func : Function
+        A function that can take MXNet NDArray as argument
+        in places that used to expect TVM NDArray.
+        Run asynchrously in MXNet's async engine.
+    """
+    # only import mxnet when wrap get called.
+    # pylint: disable=import-self
+    import mxnet
+    if isinstance(func, Module):
+        func = func.entry_func
+
+    def _get_bridge_func():
+        """Get MXNet bridge function"""
+        if not mxnet.base._LIB.MXTVMBridge:
+            raise RuntimeError(
+                "MXTVMBridge not exist in mxnet package,"
+                " please update to latest version")
+
+        fdict = api.extract_ext_funcs(mxnet.base._LIB.MXTVMBridge)
+        ret = fdict["WrapAsyncCall"]
+        ret.is_global = True
+        return ret
+    global _wrap_async
+
+    if _wrap_async is None:
+        # Register extension type in first time
+        _wrap_async = _get_bridge_func()
+        ndarray.register_extension(mxnet.nd.NDArray)
+
+    const_loc = const_loc if const_loc else []
+    return _wrap_async(func, _api_internal._TVMSetStream, len(const_loc), *const_loc)
diff --git a/src/api/api_base.cc b/src/api/api_base.cc
index df8469903533..cc76f6a8f50b 100644
--- a/src/api/api_base.cc
+++ b/src/api/api_base.cc
@@ -36,4 +36,9 @@ TVM_REGISTER_API("_load_json")
 TVM_REGISTER_API("_nop")
 .set_body([](TVMArgs args,  TVMRetValue *ret) {
   });
+
+TVM_REGISTER_API("_TVMSetStream")
+.set_body([](TVMArgs args,  TVMRetValue *ret) {
+    TVMSetStream(args[0], args[1], args[2]);
+  });
 }  // namespace tvm
diff --git a/tests/python/contrib/test_mxnet_bridge.py b/tests/python/contrib/test_mxnet_bridge.py
new file mode 100644
index 000000000000..2228f7305c6b
--- /dev/null
+++ b/tests/python/contrib/test_mxnet_bridge.py
@@ -0,0 +1,48 @@
+def mxnet_check():
+    """This is a simple test function for MXNet bridge
+
+    It is not included as nosetests, because of its dependency on mxnet
+
+    User can directly run this script to verify correctness.
+    """
+    import mxnet as mx
+    import topi
+    import tvm
+    import numpy as np
+    from tvm.contrib.mxnet import to_mxnet_func
+
+    # build a TVM function through topi
+    n = 20
+    shape = (20,)
+    scale = tvm.var("scale", dtype="float32")
+    x = tvm.placeholder(shape)
+    y = tvm.placeholder(shape)
+    z = topi.broadcast_add(x, y)
+    zz = tvm.compute(shape, lambda *i: z(*i) * scale)
+
+    target = tvm.target.cuda()
+
+    # build the function
+    with target:
+        s = topi.generic.schedule_injective(zz)
+        f = tvm.build(s, [x, y, zz, scale])
+
+    # get a mxnet version
+    mxf = to_mxnet_func(f, const_loc=[0, 1])
+
+    ctx = mx.gpu(0)
+    xx = mx.nd.uniform(shape=shape, ctx=ctx)
+    yy = mx.nd.uniform(shape=shape, ctx=ctx)
+    zz = mx.nd.empty(shape=shape, ctx=ctx)
+
+    # invoke myf: this runs in mxnet engine
+    mxf(xx, yy, zz, 10.0)
+    mxf(xx, yy, zz, 10.0)
+
+
+    np.testing.assert_allclose(
+        zz.asnumpy(), (xx.asnumpy() + yy.asnumpy()) * 10)
+
+
+if __name__ == "__main__":
+    mxnet_check()

From 31013393255d77ab4e479e0403560d075bc2a934 Mon Sep 17 00:00:00 2001
From: Zhixun Tan <phisiart@gmail.com>
Date: Mon, 26 Feb 2018 18:42:11 -0800
Subject: [PATCH 168/949] Append null terminator when converting JS string to c
 string. (#931)

---
 web/tvm_runtime.js | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/web/tvm_runtime.js b/web/tvm_runtime.js
index c23c1f9da796..df9cba94aa3a 100644
--- a/web/tvm_runtime.js
+++ b/web/tvm_runtime.js
@@ -291,10 +291,11 @@ var tvm_runtime = tvm_runtime || {};
     }
 
     function StringToUint8Array(str) {
-      var arr = new Uint8Array(str.length);
+      var arr = new Uint8Array(str.length + 1);
       for(var i = 0; i < str.length; ++i) {
         arr[i] = str.charCodeAt(i);
       }
+      arr[str.length] = 0;
       return arr;
     }
     //-----------------------------------------
@@ -599,7 +600,7 @@ var tvm_runtime = tvm_runtime || {};
         Module.setValue(this.value + index * SIZEOF_TVMVALUE, value, "*");
       },
       setString : function(index, value) {
-        var sdata = new CBuffer(value.length);
+        var sdata = new CBuffer(value.length + 1);
         Module.HEAPU8.set(StringToUint8Array(value), sdata.data);
         this.temp.push(sdata);
         Module.setValue(this.tcode + index * SIZEOF_INT, kStr, "i32");

From 83cc348f7a2ba3e922b5fa9d16e393b666ef5d23 Mon Sep 17 00:00:00 2001
From: nhynes <nhynes@berkeley.edu>
Date: Tue, 27 Feb 2018 11:36:57 -0800
Subject: [PATCH 169/949] TVM SGX (#919)

* Update DMLC core most recent version

* Modify runtime to minimize io when building for SGX

* Add SGX example app

* Prefer streaming versions of packed_func function
---
 apps/sgx/.gitignore               |   2 +
 apps/sgx/Makefile                 | 110 +++++++++++++++++++++++++
 apps/sgx/README.md                |  34 ++++++++
 apps/sgx/app.cc                   | 129 ++++++++++++++++++++++++++++++
 apps/sgx/enclave.cc               |  62 ++++++++++++++
 apps/sgx/enclave_config.xml       |  11 +++
 apps/sgx/enclave_private.pem      |  39 +++++++++
 apps/sgx/prepare_test_libs.py     |  24 ++++++
 apps/sgx/run_example.sh           |   8 ++
 apps/sgx/test_addone.edl          |   7 ++
 apps/sgx/tvm_runtime_pack.cc      |  17 ++++
 dmlc-core                         |   2 +-
 include/tvm/runtime/packed_func.h |  24 +++++-
 src/runtime/c_runtime_api.cc      |  12 +--
 src/runtime/cpu_device_api.cc     |   3 +
 src/runtime/module.cc             |   6 ++
 src/runtime/module_util.cc        |   6 ++
 17 files changed, 485 insertions(+), 11 deletions(-)
 create mode 100644 apps/sgx/.gitignore
 create mode 100644 apps/sgx/Makefile
 create mode 100644 apps/sgx/README.md
 create mode 100644 apps/sgx/app.cc
 create mode 100644 apps/sgx/enclave.cc
 create mode 100644 apps/sgx/enclave_config.xml
 create mode 100644 apps/sgx/enclave_private.pem
 create mode 100644 apps/sgx/prepare_test_libs.py
 create mode 100755 apps/sgx/run_example.sh
 create mode 100644 apps/sgx/test_addone.edl
 create mode 100644 apps/sgx/tvm_runtime_pack.cc

diff --git a/apps/sgx/.gitignore b/apps/sgx/.gitignore
new file mode 100644
index 000000000000..25f8f1058cab
--- /dev/null
+++ b/apps/sgx/.gitignore
@@ -0,0 +1,2 @@
+lib/
+bin/
diff --git a/apps/sgx/Makefile b/apps/sgx/Makefile
new file mode 100644
index 000000000000..6a1eeb5b8b1e
--- /dev/null
+++ b/apps/sgx/Makefile
@@ -0,0 +1,110 @@
+# Makefile for example to deploy TVM modules in SGX.
+
+TVM_ROOT := $(shell cd ../..; pwd)
+NNVM_PATH := nnvm
+DMLC_CORE := ${TVM_ROOT}/dmlc-core
+
+SGX_SDK ?= /opt/sgxsdk
+SGX_MODE ?= SIM
+SGX_ARCH ?= x64
+SGX_DEBUG ?= 1
+
+sgx_edger8r := $(SGX_SDK)/bin/x64/sgx_edger8r
+sgx_enclave_signer := $(SGX_SDK)/bin/x64/sgx_sign
+
+ifneq ($(SGX_MODE), HW)
+	sgx_sim := _sim
+endif
+urts_library_name := sgx_urts$(sgx_sim)
+trts_library_name := sgx_trts$(sgx_sim)
+tservice_library_name := sgx_tservice$(sgx_sim)
+uservice_library_name := sgx_uae_service$(sgx_sim)
+
+pkg_cflags := -std=c++11 -O2 -fPIC\
+	-I${TVM_ROOT}/include\
+	-I${DMLC_CORE}/include\
+	-I${TVM_ROOT}/dlpack/include\
+	-I.\
+	-DDMLC_LOG_STACK_TRACE=0\
+
+pkg_ldflags := -L${TVM_ROOT}/lib
+
+enclave_include_paths := -I$(SGX_SDK)/include\
+	-I$(SGX_SDK)/include/tlibc\
+	-I$(SGX_SDK)/include/libcxx\
+	-I$(SGX_SDK)/include/stdc++\
+
+enclave_cflags := -static -nostdinc\
+	-fvisibility=hidden -fpie -fstack-protector-strong\
+	-ffunction-sections -fdata-sections\
+	-DDMLC_CXX11_THREAD_LOCAL=0\
+	$(enclave_include_paths)\
+
+enclave_cxxflags := -nostdinc++ $(enclave_cflags)
+
+enclave_ldflags :=\
+	-Wl,--no-undefined -nostdlib -nodefaultlibs -nostartfiles -L$(SGX_SDK)/lib64\
+	-Wl,--whole-archive -l$(trts_library_name) -Wl,--no-whole-archive\
+	-Wl,--start-group\
+	-lsgx_tstdc -lsgx_tstdcxx -lsgx_tcxx -lsgx_tcrypto -lsgx_tkey_exchange -l$(tservice_library_name)\
+	-Wl,--end-group\
+	-Wl,-Bstatic -Wl,-Bsymbolic -Wl,--no-undefined\
+	-Wl,-pie,-eenclave_entry -Wl,--export-dynamic\
+	-Wl,--defsym,__ImageBase=0 -Wl,--gc-sections
+
+app_cflags := -I$(SGX_SDK)/include -Ilib
+
+app_ldflags := -L$(SGX_SDK)/lib64\
+	-l$(urts_library_name) -l$(uservice_library_name) -lpthread\
+
+.PHONY: clean all
+
+all: lib/test_addone.signed.so bin/test_addone
+
+# Build rule for all-in-one TVM package library
+lib/tvm_runtime_pack.o: tvm_runtime_pack.cc
+	@mkdir -p $(@D)
+	$(CXX) -c $< -o $@ $(pkg_cflags) $(pkg_ldflags) $(enclave_cxxflags) -g
+
+# The code library built by TVM
+lib/test_addone_sys.o: prepare_test_libs.py
+	python prepare_test_libs.py
+
+# EDL files
+lib/test_addone_u.c: $(sgx_edger8r) test_addone.edl
+	$(sgx_edger8r) --untrusted test_addone.edl --untrusted-dir lib --search-path $(SGX_SDK)/include
+
+lib/test_addone_u.o: lib/test_addone_u.c
+	$(CC) $(enclave_cflags) -c $< -o $@
+
+lib/test_addone_t.c: test_addone.edl
+	$(sgx_edger8r) --trusted $< --trusted-dir lib --search-path $(SGX_SDK)/include
+
+lib/test_addone_t.o: lib/test_addone_t.c
+	$(CC) $(enclave_cflags) -c $< -o $@
+
+# The enclave library
+lib/test_addone.so: enclave.cc lib/tvm_runtime_pack.o lib/test_addone_t.o lib/test_addone_sys.o
+	$(CXX) $^ -o $@ $(pkg_cflags) $(pkg_ldflags) $(enclave_cxxflags) $(enclave_ldflags) -g
+
+# The signed enclave
+lib/test_addone.signed.so: lib/test_addone.so enclave_config.xml
+	$(sgx_enclave_signer) sign -key enclave_private.pem -enclave $< -out $@ -config enclave_config.xml
+
+# An app that runs the enclave
+bin/test_addone: app.cc lib/test_addone_u.o
+	@mkdir -p $(@D)
+	$(CXX) $^ -o $@ $(app_cflags) $(app_ldflags)
+
+# Debugging runtime pack built without SGX (c.f. howto_deploy/tvm_runtime_pack.cc)
+lib/tvm_runtime_pack_nosgx.o: tvm_runtime_pack.cc
+	@mkdir -p $(@D)
+	$(CXX) -c $< -o $@ $(pkg_cflags) $(pkg_ldflags) -g
+
+# Debugging binary that runs TVM without SGX
+bin/addone_nosgx: enclave.cc lib/tvm_runtime_pack_nosgx.o lib/test_addone_sys.o
+	@mkdir -p $(@D)
+	$(CXX) $^ -o $@ $(pkg_cflags) $(pkg_ldflags) -g
+
+clean:
+	rm -rf lib bin
diff --git a/apps/sgx/README.md b/apps/sgx/README.md
new file mode 100644
index 000000000000..c041c2b6c94b
--- /dev/null
+++ b/apps/sgx/README.md
@@ -0,0 +1,34 @@
+# TVM in Intel SGX Example
+
+This application demonstrates the use of a simple TVM model in the [Intel SGX](https://software.intel.com/en-us/blogs/2013/09/26/protecting-application-secrets-with-intel-sgx) trusted computing environment.
+
+## Prerequisites
+
+1. A GNU/Linux environment
+2. TVM compiled with LLVM and the `tvm` Python module
+3. The [Linux SGX SDK](https://github.com/intel/linux-sgx) [link to pre-built libraries](https://01.org/intel-software-guard-extensions/downloads)
+
+## Running the example
+
+`SGX_SDK=/path/to/sgxsdk bash run_example.sh`
+
+If everything goes well, you should see a lot of build messages and below them
+the text `It works!`.
+
+## High-level overview
+
+First of all, it helps to think of an SGX enclave as a library that can be called
+to perform trusted computation.
+In this library, one can use other libraries like TVM.
+
+Building this example performs the following steps:
+
+1. Creates a simple TVM module that computes `x + 1` and save it as a system library.
+2. Builds a minimal TVM runtime pack that can load the module.
+3. Links the TVM module into an SGX enclave along with some code that runs the module.
+4. Compiles and runs an executable that loads the enclave and calls a function
+   which invokes the TVM module.
+
+For more information on building, please refer to the `Makefile`.  
+For more information on the TVM module, please refer to `../howto_deploy`.  
+For more in formation on SGX enclaves, please refer to the [SGX Enclave Demo](https://github.com/intel/linux-sgx/tree/master/SampleCode/SampleEnclave/)
diff --git a/apps/sgx/app.cc b/apps/sgx/app.cc
new file mode 100644
index 000000000000..1516e8b4e925
--- /dev/null
+++ b/apps/sgx/app.cc
@@ -0,0 +1,129 @@
+#include <cstdio>
+
+#include "sgx_urts.h"
+#include "sgx_eid.h"
+#include "test_addone_u.h"
+
+#define TOKEN_FILENAME   "bin/test_addone.token"
+#define ENCLAVE_FILENAME "lib/test_addone.signed.so"
+
+sgx_enclave_id_t global_eid = 0;  // global EID shared by multiple threads
+
+typedef struct _sgx_errlist_t {
+  sgx_status_t err;
+  const char *msg;
+} sgx_errlist_t;
+
+/* Error code returned by sgx_create_enclave */
+static sgx_errlist_t sgx_errlist[] = {
+  { SGX_ERROR_DEVICE_BUSY, "SGX device was busy." },
+  { SGX_ERROR_ENCLAVE_FILE_ACCESS, "Can't open enclave file." },
+  { SGX_ERROR_ENCLAVE_LOST, "Power transition occurred." },
+  { SGX_ERROR_INVALID_ATTRIBUTE, "Enclave was not authorized." },
+  { SGX_ERROR_INVALID_ENCLAVE, "Invalid enclave image." },
+  { SGX_ERROR_INVALID_ENCLAVE_ID, "Invalid enclave identification." },
+  { SGX_ERROR_INVALID_METADATA, "Invalid enclave metadata." },
+  { SGX_ERROR_INVALID_PARAMETER, "Invalid parameter." },
+  { SGX_ERROR_INVALID_SIGNATURE, "Invalid enclave signature." },
+  { SGX_ERROR_INVALID_VERSION, "Enclave version was invalid." },
+  { SGX_ERROR_MEMORY_MAP_CONFLICT, "Memory map conflicted." },
+  { SGX_ERROR_NO_DEVICE, "Invalid SGX device." },
+  { SGX_ERROR_OUT_OF_EPC, "Out of EPC memory." },
+  { SGX_ERROR_OUT_OF_MEMORY, "Out of memory." },
+  { SGX_ERROR_UNEXPECTED, "Unexpected error occurred." },
+};
+
+/* Check error conditions for loading enclave */
+void print_error_message(sgx_status_t status)
+{
+  size_t idx = 0;
+  size_t ttl = sizeof sgx_errlist/sizeof sgx_errlist[0];
+
+  for (idx = 0; idx < ttl; idx++) {
+    if(status == sgx_errlist[idx].err) {
+      printf("Error: %s\n", sgx_errlist[idx].msg);
+      break;
+    }
+  }
+
+  if (idx == ttl)
+    printf("Error code is 0x%X. Please refer to the \"Intel SGX SDK Developer Reference\" for more details.\n", status);
+}
+
+/* Initialize the enclave:
+ *   Step 1: try to retrieve the launch token saved by last transaction
+ *   Step 2: call sgx_create_enclave to initialize an enclave instance
+ *   Step 3: save the launch token if it is updated
+ */
+int initialize_enclave(void)
+{
+  sgx_launch_token_t token = {0};
+  sgx_status_t sgx_status = SGX_ERROR_UNEXPECTED;
+  int updated = 0;
+
+  /* Step 1: try to retrieve the launch token saved by last transaction
+   *     if there is no token, then create a new one.
+   */
+  FILE *fp = fopen(TOKEN_FILENAME, "rb");
+  if (fp == NULL && (fp = fopen(TOKEN_FILENAME, "wb")) == NULL) {
+    printf("Warning: Failed to create/open the launch token file \"%s\".\n", TOKEN_FILENAME);
+    return -1;
+  }
+
+  /* read the token from saved file */
+  size_t read_num = fread(token, 1, sizeof(sgx_launch_token_t), fp);
+  if (read_num != 0 && read_num != sizeof(sgx_launch_token_t)) {
+    /* if token is invalid, clear the buffer */
+    memset(&token, 0x0, sizeof(sgx_launch_token_t));
+    printf("Warning: Invalid launch token read from \"%s\".\n", TOKEN_FILENAME);
+  }
+
+  /* Step 2: call sgx_create_enclave to initialize an enclave instance */
+  /* Debug Support: set 2nd parameter to 1 */
+  sgx_status = sgx_create_enclave(ENCLAVE_FILENAME, SGX_DEBUG_FLAG, &token, &updated, &global_eid, NULL);
+  if (sgx_status != SGX_SUCCESS) {
+    print_error_message(sgx_status);
+    if (fp != NULL) fclose(fp);
+    return -1;
+  }
+
+  /* Step 3: save the launch token if it is updated */
+  if (updated == 0 || fp == NULL) {
+    /* if the token is not updated, or file handler is invalid, do not perform saving */
+    if (fp != NULL) fclose(fp);
+    return 0;
+  }
+
+  /* reopen the file with write capablity */
+  fp = freopen(TOKEN_FILENAME, "wb", fp);
+  if (fp == NULL) return 0;
+  size_t write_num = fwrite(token, 1, sizeof(sgx_launch_token_t), fp);
+  if (write_num != sizeof(sgx_launch_token_t))
+    printf("Warning: Failed to save launch token to \"%s\".\n", TOKEN_FILENAME);
+  fclose(fp);
+  return 0;
+}
+
+int SGX_CDECL main(int argc, char *argv[]) {
+  if(initialize_enclave() < 0){
+    printf("Failed to initialize enclave.\n");
+    return -1;
+  }
+
+  /* Run TVM within the enclave */
+  int addone_status;
+  sgx_status_t sgx_status = SGX_ERROR_UNEXPECTED;
+  sgx_status = enclave_main(global_eid, &addone_status);
+  if (sgx_status != SGX_SUCCESS) {
+    print_error_message(sgx_status);
+  }
+
+  sgx_destroy_enclave(global_eid);
+
+  if (addone_status == 1) {
+    printf("It works!");
+    return 0;
+  }
+  printf("It doesn't work.");
+  return -1;
+}
diff --git a/apps/sgx/enclave.cc b/apps/sgx/enclave.cc
new file mode 100644
index 000000000000..7588455543e9
--- /dev/null
+++ b/apps/sgx/enclave.cc
@@ -0,0 +1,62 @@
+#include <dlpack/dlpack.h>
+#include <tvm/runtime/module.h>
+#include <tvm/runtime/registry.h>
+#include <tvm/runtime/packed_func.h>
+#ifndef _LIBCPP_SGX_CONFIG
+#include <iostream>
+#endif
+
+/* This function mirrors the one in howto_deploy except without the iostream */
+int Verify(tvm::runtime::Module mod, std::string fname) {
+  // Get the function from the module.
+  tvm::runtime::PackedFunc f = mod.GetFunction(fname);
+
+  // Allocate the DLPack data structures.
+  DLTensor* x;
+  DLTensor* y;
+  int ndim = 1;
+  int dtype_code = kDLFloat;
+  int dtype_bits = 32;
+  int dtype_lanes = 1;
+  int device_type = kDLCPU;
+  int device_id = 0;
+  int64_t shape[1] = {10};
+  TVMArrayAlloc(shape, ndim, dtype_code, dtype_bits, dtype_lanes,
+                device_type, device_id, &x);
+  TVMArrayAlloc(shape, ndim, dtype_code, dtype_bits, dtype_lanes,
+                device_type, device_id, &y);
+  for (int i = 0; i < shape[0]; ++i) {
+    static_cast<float*>(x->data)[i] = i;
+  }
+
+  // Invoke the function
+  f(x, y);
+
+  // check the output
+  bool all_eq = true;
+  for (int i = 0; i < shape[0]; ++i) {
+    all_eq = all_eq && static_cast<float*>(y->data)[i] == i + 1.0f;
+  }
+
+  return all_eq;
+}
+
+
+extern "C" {
+int enclave_main() {
+  tvm::runtime::Module mod_syslib = (*tvm::runtime::Registry::Get("module._GetSystemLib"))();
+  return Verify(mod_syslib, "addonesys");
+}
+}
+
+#ifndef _LIBCPP_SGX_CONFIG
+int main(void) {
+  tvm::runtime::Module mod_syslib = (*tvm::runtime::Registry::Get("module._GetSystemLib"))();
+  if (Verify(mod_syslib, "addonesys")) {
+    std::cout << "It works!" << std::endl;
+    return 0;
+  }
+  std::cerr << "It doesn't work." << std::endl;
+  return -1;
+}
+#endif
diff --git a/apps/sgx/enclave_config.xml b/apps/sgx/enclave_config.xml
new file mode 100644
index 000000000000..d24da1882981
--- /dev/null
+++ b/apps/sgx/enclave_config.xml
@@ -0,0 +1,11 @@
+<EnclaveConfiguration>
+  <ProdID>0</ProdID>
+  <ISVSVN>0</ISVSVN>
+  <StackMaxSize>0x2000</StackMaxSize>
+  <HeapMaxSize>0x1000</HeapMaxSize>
+  <TCSNum>1</TCSNum>
+  <TCSPolicy>1</TCSPolicy>
+  <DisableDebug>0</DisableDebug>
+  <MiscSelect>0</MiscSelect>
+  <MiscMask>0xFFFFFFFF</MiscMask>
+</EnclaveConfiguration>
diff --git a/apps/sgx/enclave_private.pem b/apps/sgx/enclave_private.pem
new file mode 100644
index 000000000000..529d07be3574
--- /dev/null
+++ b/apps/sgx/enclave_private.pem
@@ -0,0 +1,39 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIG4gIBAAKCAYEAroOogvsj/fZDZY8XFdkl6dJmky0lRvnWMmpeH41Bla6U1qLZ
+AmZuyIF+mQC/cgojIsrBMzBxb1kKqzATF4+XwPwgKz7fmiddmHyYz2WDJfAjIveJ
+ZjdMjM4+EytGlkkJ52T8V8ds0/L2qKexJ+NBLxkeQLfV8n1mIk7zX7jguwbCG1Pr
+nEMdJ3Sew20vnje+RsngAzdPChoJpVsWi/K7cettX/tbnre1DL02GXc5qJoQYk7b
+3zkmhz31TgFrd9VVtmUGyFXAysuSAb3EN+5VnHGr0xKkeg8utErea2FNtNIgua8H
+ONfm9Eiyaav1SVKzPHlyqLtcdxH3I8Wg7yqMsaprZ1n5A1v/levxnL8+It02KseD
+5HqV4rf/cImSlCt3lpRg8U5E1pyFQ2IVEC/XTDMiI3c+AR+w2jSRB3Bwn9zJtFlW
+KHG3m1xGI4ck+Lci1JvWWLXQagQSPtZTsubxTQNx1gsgZhgv1JHVZMdbVlAbbRMC
+1nSuJNl7KPAS/VfzAgEDAoIBgHRXxaynbVP5gkO0ug6Qw/E27wzIw4SmjsxG6Wpe
+K7kfDeRskKxESdsA/xCrKkwGwhcx1iIgS5+Qscd1Yg+1D9X9asd/P7waPmWoZd+Z
+AhlKwhdPsO7PiF3e1AzHhGQwsUTt/Y/aSI1MpHBvy2/s1h9mFCslOUxTmWw0oj/Q
+ldIEgWeNR72CE2+jFIJIyml6ftnb6qzPiga8Bm48ubKh0kvySOqnkmnPzgh+JBD6
+JnBmtZbfPT97bwTT+N6rnPqOOApvfHPf15kWI8yDbprG1l4OCUaIUH1AszxLd826
+5IPM+8gINLRDP1MA6azECPjTyHXhtnSIBZCyWSVkc05vYmNXYUNiXWMajcxW9M02
+wKzFELO8NCEAkaTPxwo4SCyIjUxiK1LbQ9h8PSy4c1+gGP4LAMR8xqP4QKg6zdu9
+osUGG/xRe/uufgTBFkcjqBHtK5L5VI0jeNIUAgW/6iNbYXjBMJ0GfauLs+g1VsOm
+WfdgXzsb9DYdMa0OXXHypmV4GwKBwQDUwQj8RKJ6c8cT4vcWCoJvJF00+RFL+P3i
+Gx2DLERxRrDa8AVGfqaCjsR+3vLgG8V/py+z+dxZYSqeB80Qeo6PDITcRKoeAYh9
+xlT3LJOS+k1cJcEmlbbO2IjLkTmzSwa80fWexKu8/Xv6vv15gpqYl1ngYoqJM3pd
+vzmTIOi7MKSZ0WmEQavrZj8zK4endE3v0eAEeQ55j1GImbypSf7Idh7wOXtjZ7WD
+Dg6yWDrri+AP/L3gClMj8wsAxMV4ZR8CgcEA0fzDHkFa6raVOxWnObmRoDhAtE0a
+cjUj976NM5yyfdf2MrKy4/RhdTiPZ6b08/lBC/+xRfV3xKVGzacm6QjqjZrUpgHC
+0LKiZaMtccCJjLtPwQd0jGQEnKfMFaPsnhOc5y8qVkCzVOSthY5qhz0XNotHHFmJ
+gffVgB0iqrMTvSL7IA2yqqpOqNRlhaYhNl8TiFP3gIeMtVa9rZy31JPgT2uJ+kfo
+gV7sdTPEjPWZd7OshGxWpT6QfVDj/T9T7L6tAoHBAI3WBf2DFvxNL2KXT2QHAZ9t
+k3imC4f7U+wSE6zILaDZyzygA4RUbwG0gv8/TJVn2P/Eynf76DuWHGlaiLWnCbSz
+Az2DHBQBBaku409zDQym3j1ugMRjzzSQWzJg0SIyBH3hTmnYcn3+Uqcp/lEBvGW6
+O+rsXFt3pukqJmIV8HzLGGaLm62BHUeZf3dyWm+i3p/hQAL7Xvu04QW70xuGqdr5
+afV7p5eaeQIJXyGQJ0eylV/90+qxjMKiB1XYg6WYvwKBwQCL/ddpgOdHJGN8uRom
+e7Zq0Csi3hGheMKlKbN3vcxT5U7MdyHtTZZOJbTvxKNNUNYH/8uD+PqDGNneb29G
+BfGzvI3EASyLIcGZF3OhKwZd0jUrWk2y7Vhob91jwp2+t73vdMbkKyI4mHOuXvGv
+fg95si9oO7EBT+Oqvhccd2J+F1IVXncccYnF4u5ZGWt5lLewN/pVr7MjjykeaHqN
+t+rfnQam2psA6fL4zS2zTmZPzR2tnY8Y1GBTi0Ko1OKd1HMCgcAb5cB/7/AQlhP9
+yQa04PLH9ygQkKKptZp7dy5WcWRx0K/hAHRoi2aw1wZqfm7VBNu2SLcs90kCCCxp
+6C5sfJi6b8NpNbIPC+sc9wsFr7pGo9SFzQ78UlcWYK2Gu2FxlMjonhka5hvo4zvg
+WxlpXKEkaFt3gLd92m/dMqBrHfafH7VwOJY2zT3WIpjwuk0ZzmRg5p0pG/svVQEH
+NZmwRwlopysbR69B/n1nefJ84UO50fLh5s5Zr3gBRwbWNZyzhXk=
+-----END RSA PRIVATE KEY-----
diff --git a/apps/sgx/prepare_test_libs.py b/apps/sgx/prepare_test_libs.py
new file mode 100644
index 000000000000..1fa9d74ef1c9
--- /dev/null
+++ b/apps/sgx/prepare_test_libs.py
@@ -0,0 +1,24 @@
+"""Script to prepare test_addone_sys.o"""
+
+from os import path as osp
+
+import tvm
+
+CWD = osp.dirname(osp.abspath(osp.expanduser(__file__)))
+
+def prepare_test_libs(base_path):
+    n = tvm.var('n')
+    A = tvm.placeholder((n,), name='A')
+    B = tvm.compute(A.shape, lambda *i: A(*i) + 1, name='B')
+    s = tvm.create_schedule(B.op)
+
+    # Compile library in system library mode
+    fadd_syslib = tvm.build(s, [A, B], 'llvm --system-lib', name='addonesys')
+    syslib_path = osp.join(base_path, 'test_addone_sys.o')
+    fadd_syslib.save(syslib_path)
+
+def main():
+    prepare_test_libs(osp.join(CWD, 'lib'))
+
+if __name__ == '__main__':
+    main()
diff --git a/apps/sgx/run_example.sh b/apps/sgx/run_example.sh
new file mode 100755
index 000000000000..3658e0bafb73
--- /dev/null
+++ b/apps/sgx/run_example.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+sgx_sdk=${SGX_SDK:=/opt/sgxsdk}
+
+mkdir -p bin lib
+make
+echo "========================="
+LD_LIBRARY_PATH="$sgx_sdk/lib64":${LD_LIBRARY_PATH} bin/test_addone
diff --git a/apps/sgx/test_addone.edl b/apps/sgx/test_addone.edl
new file mode 100644
index 000000000000..58341a727a6b
--- /dev/null
+++ b/apps/sgx/test_addone.edl
@@ -0,0 +1,7 @@
+enclave {
+    from "sgx_tstdc.edl" import sgx_thread_wait_untrusted_event_ocall, sgx_thread_set_untrusted_event_ocall, sgx_thread_setwait_untrusted_events_ocall, sgx_thread_set_multiple_untrusted_events_ocall;
+
+    trusted {
+        public int enclave_main();
+    };
+};
diff --git a/apps/sgx/tvm_runtime_pack.cc b/apps/sgx/tvm_runtime_pack.cc
new file mode 100644
index 000000000000..52aa372f7789
--- /dev/null
+++ b/apps/sgx/tvm_runtime_pack.cc
@@ -0,0 +1,17 @@
+/*!
+ * \brief This is an all in one TVM runtime file for use in an SGX enclave.
+ *
+ *   The files included here will be statically linked into the enclave.
+ *   Please refer to the Makefile (rule lib/tvm_runtime_pack.o) for how to build.
+ *
+ */
+#include "../../src/runtime/c_runtime_api.cc"
+#include "../../src/runtime/cpu_device_api.cc"
+#include "../../src/runtime/workspace_pool.cc"
+#include "../../src/runtime/module_util.cc"
+#include "../../src/runtime/module.cc"
+#include "../../src/runtime/registry.cc"
+#include "../../src/runtime/system_lib_module.cc"
+#ifndef _LIBCPP_SGX_CONFIG
+#include "../../src/runtime/file_util.cc"
+#endif
diff --git a/dmlc-core b/dmlc-core
index c0871823b518..7e84e8b036a3 160000
--- a/dmlc-core
+++ b/dmlc-core
@@ -1 +1 @@
-Subproject commit c0871823b518093a0d04d6cba0a3291bc7b31401
+Subproject commit 7e84e8b036a3ff5c0104a3da1f4c7eebf94396ec
diff --git a/include/tvm/runtime/packed_func.h b/include/tvm/runtime/packed_func.h
index e6cc3cddecd1..8101ecdc175c 100644
--- a/include/tvm/runtime/packed_func.h
+++ b/include/tvm/runtime/packed_func.h
@@ -640,6 +640,7 @@ inline const char* TypeCode2Str(int type_code) {
   }
 }
 
+#ifndef _LIBCPP_SGX_NO_IOSTREAMS
 inline std::ostream& operator<<(std::ostream& os, TVMType t) {  // NOLINT(*)
   os << TypeCode2Str(t.code);
   if (t.code == kHandle) return os;
@@ -649,11 +650,23 @@ inline std::ostream& operator<<(std::ostream& os, TVMType t) {  // NOLINT(*)
   }
   return os;
 }
+#endif
 
 inline std::string TVMType2String(TVMType t) {
+#ifndef _LIBCPP_SGX_NO_IOSTREAMS
   std::ostringstream os;
   os << t;
   return os.str();
+#else
+  std::string repr = "";
+  repr += TypeCode2Str(t.code);
+  if (t.code == kHandle) return repr;
+  repr += std::to_string(static_cast<int>(t.bits));
+  if (t.lanes != 1) {
+    repr += "x" + std::to_string(static_cast<int>(t.lanes));
+  }
+  return repr;
+#endif
 }
 
 inline TVMType String2TVMType(std::string s) {
@@ -674,10 +687,13 @@ inline TVMType String2TVMType(std::string s) {
     scan = s.c_str();
     LOG(FATAL) << "unknown type " << s;
   }
-  unsigned bits = t.bits, lanes = t.lanes;
-  sscanf(scan, "%ux%u", &bits, &lanes);
-  t.bits = static_cast<uint8_t>(bits);
-  t.lanes = static_cast<uint16_t>(lanes);
+  char* xdelim;  // emulate sscanf("%ux%u", bits, lanes)
+  unsigned bits = strtoul(scan, &xdelim, 10);
+  if (bits != 0) t.bits = static_cast<uint8_t>(bits);
+  if (*xdelim == 'x') {
+    unsigned lanes = strtoul(xdelim + 1, nullptr, 10);
+    t.lanes = static_cast<uint16_t>(lanes);
+  }
   return t;
 }
 
diff --git a/src/runtime/c_runtime_api.cc b/src/runtime/c_runtime_api.cc
index 5e3b3e8034ce..37902b448021 100644
--- a/src/runtime/c_runtime_api.cc
+++ b/src/runtime/c_runtime_api.cc
@@ -324,9 +324,9 @@ int TVMFuncCreateFromCFunc(TVMPackedCFunc func,
           int ret = func((TVMValue*)args.values, (int*)args.type_codes, // NOLINT(*)
                          args.num_args, rv, resource_handle);
           if (ret != 0) {
-            std::ostringstream os;
-            os << "TVMCall CFunc Error:\n" << TVMGetLastError();
-            throw dmlc::Error(os.str());
+            std::string err = "TVMCall CFunc Error:\n";
+            err += TVMGetLastError();
+            throw dmlc::Error(err);
           }
         });
   } else {
@@ -338,9 +338,9 @@ int TVMFuncCreateFromCFunc(TVMPackedCFunc func,
           int ret = func((TVMValue*)args.values, (int*)args.type_codes, // NOLINT(*)
                          args.num_args, rv, rpack.get());
           if (ret != 0) {
-            std::ostringstream os;
-            os << "TVMCall CFunc Error:\n" << TVMGetLastError();
-            throw dmlc::Error(os.str());
+            std::string err = "TVMCall CFunc Error:\n";
+            err += TVMGetLastError();
+            throw dmlc::Error(err);
           }
       });
   }
diff --git a/src/runtime/cpu_device_api.cc b/src/runtime/cpu_device_api.cc
index 7486f20a6ae1..8199988f3419 100644
--- a/src/runtime/cpu_device_api.cc
+++ b/src/runtime/cpu_device_api.cc
@@ -28,6 +28,9 @@ class CPUDeviceAPI final : public DeviceAPI {
 #if _MSC_VER
     ptr = _aligned_malloc(nbytes, alignment);
     if (ptr == nullptr) throw std::bad_alloc();
+#elif defined(_LIBCPP_SGX_CONFIG)
+    ptr = memalign(alignment, nbytes);
+    if (ptr == nullptr) throw std::bad_alloc();
 #else
     int ret = posix_memalign(&ptr, alignment, nbytes);
     if (ret != 0) throw std::bad_alloc();
diff --git a/src/runtime/module.cc b/src/runtime/module.cc
index d5ece65606b9..b3cdd9c95ba6 100644
--- a/src/runtime/module.cc
+++ b/src/runtime/module.cc
@@ -8,7 +8,9 @@
 #include <tvm/runtime/packed_func.h>
 #include <unordered_set>
 #include <cstring>
+#ifndef _LIBCPP_SGX_CONFIG
 #include "./file_util.h"
+#endif
 
 namespace tvm {
 namespace runtime {
@@ -44,6 +46,7 @@ void Module::Import(Module other) {
 
 Module Module::LoadFromFile(const std::string& file_name,
                             const std::string& format) {
+#ifndef _LIBCPP_SGX_CONFIG
   std::string fmt = GetFileFormat(file_name, format);
   CHECK(fmt.length() != 0)
       << "Cannot deduce format of file " << file_name;
@@ -57,6 +60,9 @@ Module Module::LoadFromFile(const std::string& file_name,
       << load_f_name << ") is not presented.";
   Module m = (*f)(file_name, format);
   return m;
+#else
+  LOG(FATAL) << "SGX does not support LoadFromFile";
+#endif
 }
 
 void ModuleNode::SaveToFile(const std::string& file_name,
diff --git a/src/runtime/module_util.cc b/src/runtime/module_util.cc
index 825e0e459b08..95da78d23f09 100644
--- a/src/runtime/module_util.cc
+++ b/src/runtime/module_util.cc
@@ -3,7 +3,9 @@
  * \file module_util.cc
  * \brief Utilities for module.
  */
+#ifndef _LIBCPP_SGX_CONFIG
 #include <dmlc/memory_io.h>
+#endif
 #include <tvm/runtime/module.h>
 #include <tvm/runtime/registry.h>
 #include "./module_util.h"
@@ -12,6 +14,7 @@ namespace tvm {
 namespace runtime {
 
 void ImportModuleBlob(const char* mblob, std::vector<Module>* mlist) {
+#ifndef _LIBCPP_SGX_CONFIG
   CHECK(mblob != nullptr);
   uint64_t nbytes = 0;
   for (size_t i = 0; i < sizeof(nbytes); ++i) {
@@ -34,6 +37,9 @@ void ImportModuleBlob(const char* mblob, std::vector<Module>* mlist) {
     Module m = (*f)(static_cast<void*>(stream));
     mlist->push_back(m);
   }
+#else
+  LOG(FATAL) << "SGX does not support ImportModuleBlob";
+#endif
 }
 
 PackedFunc WrapPackedFunc(BackendPackedCFunc faddr,

From 3921b938c2a14017c2624f149983e86a7f9a4e94 Mon Sep 17 00:00:00 2001
From: Yige Hu <yige-hu@users.noreply.github.com>
Date: Tue, 27 Feb 2018 17:14:58 -0600
Subject: [PATCH 170/949] Fixed a g++ explicit constructor compatibility error
 for unordered_set. (#935)

* Fixed a g++ explicit constructor compatibility error for unordered_set.

* Change std::unordered_set<std::basic_string<char>>() to
std::unordered_set<std::string>().
---
 include/tvm/build_module.h  |  3 ++-
 src/codegen/build_module.cc | 18 ++++++++++++------
 2 files changed, 14 insertions(+), 7 deletions(-)

diff --git a/include/tvm/build_module.h b/include/tvm/build_module.h
index d4186e8f8167..3fb55ae169ce 100644
--- a/include/tvm/build_module.h
+++ b/include/tvm/build_module.h
@@ -40,7 +40,8 @@ struct Target {
          int thread_warp_size,
          const std::unordered_set<std::string>& keys,
          const std::vector<std::string>& options,
-         const std::unordered_set<std::string>& libs = {}) :
+         const std::unordered_set<std::string>& libs =
+           std::unordered_set<std::string>()) :
     target_name(target_name),
     device_type(device_type),
     max_num_threads(max_num_threads),
diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc
index cca09a966e21..e7f0cd41bb7b 100644
--- a/src/codegen/build_module.cc
+++ b/src/codegen/build_module.cc
@@ -85,25 +85,29 @@ namespace target {
 Target llvm() {
   std::unordered_set<std::string> keys({ "llvm", "cpu" });
   std::vector<std::string> options;
-  return Target("llvm", kDLCPU, 512, 1, keys, options, {});
+  return Target("llvm", kDLCPU, 512, 1, keys, options,
+           std::unordered_set<std::string>());
 }
 
 Target cuda() {
   std::unordered_set<std::string> keys({ "cuda", "gpu" });
   std::vector<std::string> options;
-  return Target("cuda", kDLGPU, 512, 32, keys, options, {});
+  return Target("cuda", kDLGPU, 512, 32, keys, options,
+           std::unordered_set<std::string>());
 }
 
 Target rocm() {
   std::unordered_set<std::string> keys({ "rocm", "gpu" });
   std::vector<std::string> options;
-  return Target("rocm", kDLROCM, 256, 1, keys, options, {});
+  return Target("rocm", kDLROCM, 256, 1, keys, options,
+           std::unordered_set<std::string>());
 }
 
 Target metal() {
   std::unordered_set<std::string> keys({ "gpu" });
   std::vector<std::string> options;
-  return Target("metal", kDLMetal, 256, 1, keys, options, {});
+  return Target("metal", kDLMetal, 256, 1, keys, options,
+           std::unordered_set<std::string>());
 }
 
 Target rasp() {
@@ -114,7 +118,8 @@ Target rasp() {
     "-mcpu=cortex-a53",
     "-mattr=+neon"
   });
-  return Target("llvm", kDLCPU, 512, 1, keys, options, {});
+  return Target("llvm", kDLCPU, 512, 1, keys, options,
+           std::unordered_set<std::string>());
 }
 
 Target mali() {
@@ -129,7 +134,8 @@ Target mali() {
 Target stackvm() {
   std::unordered_set<std::string> keys({ "stackvm", "cpu" });
   std::vector<std::string> options;
-  return Target("stackvm", kDLCPU, 512, 1, keys, options, {});
+  return Target("stackvm", kDLCPU, 512, 1, keys, options,
+           std::unordered_set<std::string>());
 }
 }  // namespace target
 

From 2ca47e2078b427f273ec26719ccf432a6d90d6f4 Mon Sep 17 00:00:00 2001
From: Zhixun Tan <phisiart@gmail.com>
Date: Tue, 27 Feb 2018 17:08:44 -0800
Subject: [PATCH 171/949] Name all the lock guards. (#938)

---
 src/codegen/llvm/llvm_common.cc         |  2 +-
 src/runtime/metal/metal_device_api.mm   |  2 +-
 src/runtime/opencl/opencl_device_api.cc |  2 +-
 src/runtime/registry.cc                 | 10 +++++-----
 src/runtime/vulkan/vulkan_device_api.cc |  2 +-
 5 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/src/codegen/llvm/llvm_common.cc b/src/codegen/llvm/llvm_common.cc
index 03ac6f0ff227..01f2c8869dc1 100644
--- a/src/codegen/llvm/llvm_common.cc
+++ b/src/codegen/llvm/llvm_common.cc
@@ -24,7 +24,7 @@ struct LLVMEnv {
 void InitializeLLVM() {
   LLVMEnv* e = LLVMEnv::Global();
   if (!e->all_initialized) {
-    std::lock_guard<std::mutex>(e->mu);
+    std::lock_guard<std::mutex> lock(e->mu);
     if (!e->all_initialized) {
       e->all_initialized = true;
       llvm::InitializeAllTargetInfos();
diff --git a/src/runtime/metal/metal_device_api.mm b/src/runtime/metal/metal_device_api.mm
index 6d376d3144ac..d87a9eac4f72 100644
--- a/src/runtime/metal/metal_device_api.mm
+++ b/src/runtime/metal/metal_device_api.mm
@@ -96,7 +96,7 @@ int GetWarpSize(id<MTLDevice> dev) {
 
 void MetalWorkspace::Init() {
   if (initialized_) return;
-  std::lock_guard<std::mutex>(this->mutex);
+  std::lock_guard<std::mutex> lock(this->mutex);
   if (initialized_) return;
   initialized_ = true;
   if (devices.size() != 0) return;
diff --git a/src/runtime/opencl/opencl_device_api.cc b/src/runtime/opencl/opencl_device_api.cc
index a07fe15f805f..4c3d206b9354 100644
--- a/src/runtime/opencl/opencl_device_api.cc
+++ b/src/runtime/opencl/opencl_device_api.cc
@@ -180,7 +180,7 @@ bool MatchPlatformInfo(
 
 void OpenCLWorkspace::Init() {
   if (initialized_) return;
-  std::lock_guard<std::mutex>(this->mu);
+  std::lock_guard<std::mutex> lock(this->mu);
   if (initialized_) return;
   initialized_ = true;
   if (context != nullptr) return;
diff --git a/src/runtime/registry.cc b/src/runtime/registry.cc
index d7587b6ce1a5..3f72828390ee 100644
--- a/src/runtime/registry.cc
+++ b/src/runtime/registry.cc
@@ -46,7 +46,7 @@ Registry& Registry::set_body(PackedFunc f) {  // NOLINT(*)
 
 Registry& Registry::Register(const std::string& name, bool override) {  // NOLINT(*)
   Manager* m = Manager::Global();
-  std::lock_guard<std::mutex>(m->mutex);
+  std::lock_guard<std::mutex> lock(m->mutex);
   auto it = m->fmap.find(name);
   if (it == m->fmap.end()) {
     Registry* r = new Registry();
@@ -62,7 +62,7 @@ Registry& Registry::Register(const std::string& name, bool override) {  // NOLIN
 
 bool Registry::Remove(const std::string& name) {
   Manager* m = Manager::Global();
-  std::lock_guard<std::mutex>(m->mutex);
+  std::lock_guard<std::mutex> lock(m->mutex);
   auto it = m->fmap.find(name);
   if (it == m->fmap.end()) return false;
   m->fmap.erase(it);
@@ -71,7 +71,7 @@ bool Registry::Remove(const std::string& name) {
 
 const PackedFunc* Registry::Get(const std::string& name) {
   Manager* m = Manager::Global();
-  std::lock_guard<std::mutex>(m->mutex);
+  std::lock_guard<std::mutex> lock(m->mutex);
   auto it = m->fmap.find(name);
   if (it == m->fmap.end()) return nullptr;
   return &(it->second->func_);
@@ -79,7 +79,7 @@ const PackedFunc* Registry::Get(const std::string& name) {
 
 std::vector<std::string> Registry::ListNames() {
   Manager* m = Manager::Global();
-  std::lock_guard<std::mutex>(m->mutex);
+  std::lock_guard<std::mutex> lock(m->mutex);
   std::vector<std::string> keys;
   keys.reserve(m->fmap.size());
   for (const auto &kv : m->fmap) {
@@ -101,7 +101,7 @@ ExtTypeVTable* ExtTypeVTable::RegisterInternal(
     int type_code, const ExtTypeVTable& vt) {
   CHECK(type_code > kExtBegin && type_code < kExtEnd);
   Registry::Manager* m = Registry::Manager::Global();
-  std::lock_guard<std::mutex>(m->mutex);
+  std::lock_guard<std::mutex> lock(m->mutex);
   ExtTypeVTable* pvt = &(m->ext_vtable[type_code]);
   pvt[0] = vt;
   return pvt;
diff --git a/src/runtime/vulkan/vulkan_device_api.cc b/src/runtime/vulkan/vulkan_device_api.cc
index 99b308d338f9..ef97e99431c2 100644
--- a/src/runtime/vulkan/vulkan_device_api.cc
+++ b/src/runtime/vulkan/vulkan_device_api.cc
@@ -650,7 +650,7 @@ std::vector<VulkanContext> GetContext(VkInstance instance) {
 
 void VulkanWorkspace::Init() {
   if (initialized_) return;
-  std::lock_guard<std::mutex>(this->mu);
+  std::lock_guard<std::mutex> lock(this->mu);
   if (initialized_) return;
   initialized_ = true;
   instance_ = CreateInstance();

From 9503f663b67bf0b6b7fc58db7ed05b27a859a5b2 Mon Sep 17 00:00:00 2001
From: Zhixun Tan <phisiart@gmail.com>
Date: Tue, 27 Feb 2018 20:20:16 -0800
Subject: [PATCH 172/949] Add test case: Create a static WebGL library and run
 it in the browser. (#932)

* Add test case: Create a static WebGL library and run it in the browser.

* Add documentation for loadModuleFromFile

* Modify emscripten.createjs
---
 python/tvm/contrib/emscripten.py           |  2 +
 python/tvm/module.py                       |  8 +++-
 src/runtime/system_lib_module.cc           | 22 +++++++--
 tests/webgl/test_static_webgl_library.html | 55 ++++++++++++++++++++++
 tests/webgl/test_static_webgl_library.py   | 49 +++++++++++++++++++
 web/tvm_runtime.js                         | 37 +++++++++++++++
 6 files changed, 169 insertions(+), 4 deletions(-)
 create mode 100644 tests/webgl/test_static_webgl_library.html
 create mode 100644 tests/webgl/test_static_webgl_library.py

diff --git a/python/tvm/contrib/emscripten.py b/python/tvm/contrib/emscripten.py
index d770ce1161f1..d263e472cff0 100644
--- a/python/tvm/contrib/emscripten.py
+++ b/python/tvm/contrib/emscripten.py
@@ -60,3 +60,5 @@ def create_js(output,
         msg = "Compilation error:\n"
         msg += out
         raise RuntimeError(msg)
+
+create_js.object_format = "bc"
diff --git a/python/tvm/module.py b/python/tvm/module.py
index d8b018b824f0..6459733fa15e 100644
--- a/python/tvm/module.py
+++ b/python/tvm/module.py
@@ -84,6 +84,8 @@ def export_library(self,
 
         fcompile : function(target, file_list, kwargs), optional
             Compilation function to use create dynamic library.
+            If fcompile has attribute object_format, will compile host library
+            to that format. Otherwise, will use default format "o".
 
         kwargs : dict, optiona;
             Additional arguments passed to fcompile
@@ -95,7 +97,11 @@ def export_library(self,
         if self.type_key != "llvm":
             raise ValueError("Module[%s]: Only llvm support export shared" % self.type_key)
         temp = _util.tempdir()
-        path_obj = temp.relpath("lib.o")
+        if fcompile is not None and hasattr(fcompile, "object_format"):
+            object_format = fcompile.object_format
+        else:
+            object_format = "o"
+        path_obj = temp.relpath("lib." + object_format)
         self.save(path_obj)
         files = [path_obj]
         is_system_lib = self.get_function("__tvm_is_system_module")()
diff --git a/src/runtime/system_lib_module.cc b/src/runtime/system_lib_module.cc
index db06f57e8422..1f9c8ac8e152 100644
--- a/src/runtime/system_lib_module.cc
+++ b/src/runtime/system_lib_module.cc
@@ -13,8 +13,8 @@ namespace runtime {
 
 class SystemLibModuleNode : public ModuleNode {
  public:
-  SystemLibModuleNode() {
-  }
+  SystemLibModuleNode() = default;
+
   const char* type_key() const final {
     return "system_lib";
   }
@@ -23,6 +23,13 @@ class SystemLibModuleNode : public ModuleNode {
       const std::string& name,
       const std::shared_ptr<ModuleNode>& sptr_to_self) final {
     std::lock_guard<std::mutex> lock(mutex_);
+
+    if (module_blob_ != nullptr) {
+      // If we previously recorded submodules, load them now.
+      ImportModuleBlob(reinterpret_cast<const char*>(module_blob_), &imports_);
+      module_blob_ = nullptr;
+    }
+
     auto it = tbl_.find(name);
     if (it != tbl_.end()) {
       return WrapPackedFunc(
@@ -38,7 +45,14 @@ class SystemLibModuleNode : public ModuleNode {
       void** ctx_addr = reinterpret_cast<void**>(ptr);
       *ctx_addr = this;
     } else if (name == symbol::tvm_dev_mblob) {
-      ImportModuleBlob(reinterpret_cast<const char*>(ptr), &imports_);
+      // Record pointer to content of submodules to be loaded.
+      // We defer loading submodules to the first call to GetFunction().
+      // The reason is that RegisterSymbol() gets called when initializing the
+      // syslib (i.e. library loading time), and the registeries aren't ready
+      // yet. Therefore, we might not have the functionality to load submodules
+      // now.
+      CHECK(module_blob_ == nullptr) << "Resetting mobule blob?";
+      module_blob_ = ptr;
     } else {
       auto it = tbl_.find(name);
       if (it != tbl_.end()) {
@@ -65,6 +79,8 @@ class SystemLibModuleNode : public ModuleNode {
   std::mutex mutex_;
   // Internal symbol table
   std::unordered_map<std::string, void*> tbl_;
+  // Module blob to be imported
+  void* module_blob_{nullptr};
 };
 
 TVM_REGISTER_GLOBAL("module._GetSystemLib")
diff --git a/tests/webgl/test_static_webgl_library.html b/tests/webgl/test_static_webgl_library.html
new file mode 100644
index 000000000000..39bcb5fff8c7
--- /dev/null
+++ b/tests/webgl/test_static_webgl_library.html
@@ -0,0 +1,55 @@
+<html>
+
+<head>
+  <meta charset="UTF-8">
+  <title>TVM RPC Test Page</title>
+</head>
+
+<body>
+  <h1>TVM Test Page</h1>
+  <div id="log"></div>
+  <canvas id="canvas"></canvas>
+  <script>
+    var Module = {};
+    Module["canvas"] = document.getElementById("canvas");
+  </script>
+  <script src="identity_static.js"></script>
+  <script src="tvm_runtime.js"></script>
+  <script>
+    var tvm = tvm_runtime.create(Module);
+    tvm.logger = function (message) {
+      console.log(message);
+      var d = document.createElement("div");
+      d.innerHTML = message;
+      document.getElementById("log").appendChild(d);
+    };
+
+    function randomArray(length, max) {
+      return Array.apply(null, Array(length)).map(function () {
+        return Math.random() * max;
+      });
+    }
+
+    setTimeout(function () {
+      this.syslib = tvm.systemLib();
+      this.identity = this.syslib.getFunction("identity");
+
+      this.n = 16;
+      this.a = randomArray(this.n, 1);
+      this.ctx = tvm.context("opengl", 0);
+      this.A = tvm.empty(this.n, "float32", ctx).copyFrom(this.a);
+      this.B = tvm.empty(this.n, "float32", ctx);
+      identity(this.A, this.B);
+
+      this.a = this.A.asArray();
+      this.b = this.B.asArray();
+      for (var i = 0; i < n; ++i) {
+        tvm.assert(this.a[i] == this.b[i]);
+      }
+      this.identity.release();
+    }, 1000);
+
+  </script>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/tests/webgl/test_static_webgl_library.py b/tests/webgl/test_static_webgl_library.py
new file mode 100644
index 000000000000..262416c42506
--- /dev/null
+++ b/tests/webgl/test_static_webgl_library.py
@@ -0,0 +1,49 @@
+"""Create a static WebGL library and run it in the browser."""
+
+from __future__ import absolute_import, print_function
+
+import os, shutil, SimpleHTTPServer, SocketServer
+import tvm
+from tvm.contrib import emscripten, util
+import numpy as np
+
+def try_static_webgl_library():
+    curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
+
+    # Change to lib/ which contains "libtvm_runtime.bc".
+    os.chdir(os.path.join(curr_path, "../../lib"))
+
+    # Create OpenGL module.
+    n = tvm.var("n")
+    A = tvm.placeholder((n,), name='A', dtype="float")
+    B = tvm.compute((n,), lambda *i: A[i], name="B")
+
+    s = tvm.create_schedule(B.op)
+    s[B].opengl()
+
+    target_host = "llvm -target=asmjs-unknown-emscripten -system-lib"
+    f = tvm.build(s, [A, B], name="identity", target="opengl",
+                  target_host=target_host)
+
+    # Create a JS library that contains both the module and the tvm runtime.
+    path_dso = "identity_static.js"
+    f.export_library(path_dso, emscripten.create_js, options=[
+        "-s", "USE_GLFW=3",
+        "-s", "USE_WEBGL2=1",
+        "-lglfw",
+    ])
+
+    # Create "tvm_runtime.js" and "identity_static.html" in lib/
+    shutil.copyfile(os.path.join(curr_path, "../../web/tvm_runtime.js"),
+                    "tvm_runtime.js")
+    shutil.copyfile(os.path.join(curr_path, "test_static_webgl_library.html"),
+                    "identity_static.html")
+
+    port = 8080
+    handler = SimpleHTTPServer.SimpleHTTPRequestHandler
+    httpd = SocketServer.TCPServer(("", port), handler)
+    print("Please open http://localhost:" + str(port) + "/identity_static.html")
+    httpd.serve_forever()
+
+if __name__ == "__main__":
+    try_static_webgl_library()
diff --git a/web/tvm_runtime.js b/web/tvm_runtime.js
index df9cba94aa3a..347532f5bdbf 100644
--- a/web/tvm_runtime.js
+++ b/web/tvm_runtime.js
@@ -229,6 +229,14 @@ var tvm_runtime = tvm_runtime || {};
       "number"  // size_t nbytes
      ]);
 
+    var TVMModLoadFromFile = Module.cwrap
+    ("TVMModLoadFromFile",
+     "number",
+     ["string", // const char* file_name
+      "string", // const char* format
+      "number"  // TVMModuleHandle* out
+     ])
+
     //-----------------------------------------
     // Static utility functions
     // ----------------------------------------
@@ -940,6 +948,35 @@ var tvm_runtime = tvm_runtime || {};
       }
       return new RPCServer(counter);
     };
+
+    /**
+     * Load a TVM module from a library file.
+     * The file must be present in the Emscripten virtual file system.
+     * For example, you can pass "--preload-file file" or "--preload-file dir/"
+     * to "emcc" when compiling the TVM library, in order to populate files into
+     * the file system.
+     * For more detail, see:
+     * https://kripken.github.io/emscripten-site/docs/porting/files/packaging_files
+     * @param {string} file_name Path of the file to be loaded. The path refers
+     * to the Emscripten virtual file system.
+     * @param {string} format The format of the file.
+     * @return {tvm.TVMModule} The loaded module.
+     */
+    this.loadModuleFromFile = function (file_name, format) {
+      // alloc
+      var out = new RefTVMValue();
+      TVM_CALL(TVMModLoadFromFile(file_name, format, out.data));
+      var out_handle = out.asHandle();
+      // release
+      out.release();
+      if (out_handle != 0) {
+        return new TVMModule(out_handle);
+      } else {
+        return null;
+      }
+    };
+    var loadModuleFromFile = this.loadModuleFromFile;
+
     //-----------------------------------------
     // Class defintions
     // ----------------------------------------

From 3cb6152ca1d9f2364a0ee49d52298dc057ff9bcf Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Thu, 1 Mar 2018 00:54:58 +0800
Subject: [PATCH 173/949] add exclusive mode for rpc server (#941)

---
 python/tvm/contrib/rpc.py     | 21 ++++++++++++++++-----
 python/tvm/exec/rpc_server.py |  5 ++++-
 2 files changed, 20 insertions(+), 6 deletions(-)

diff --git a/python/tvm/contrib/rpc.py b/python/tvm/contrib/rpc.py
index 3448c4c554d1..831481509c76 100644
--- a/python/tvm/contrib/rpc.py
+++ b/python/tvm/contrib/rpc.py
@@ -74,10 +74,16 @@ def _recvall(sock, nbytes):
     return b"".join(res)
 
 
-def _listen_loop(sock):
+def _listen_loop(sock, exclusive):
     """Lisenting loop"""
+    last_proc = None
     while True:
         conn, addr = sock.accept()
+
+        if last_proc and last_proc.is_alive() and exclusive:
+            logging.info("Kill last call")
+            last_proc.terminate()
+
         logging.info("RPCServer: connection from %s", addr)
         magic = struct.unpack("@i", _recvall(conn, 4))[0]
         if magic != RPC_MAGIC:
@@ -90,9 +96,11 @@ def _listen_loop(sock):
         else:
             conn.sendall(struct.pack("@i", RPC_MAGIC))
         logging.info("Connection from %s", addr)
+
         process = multiprocessing.Process(target=_serve_loop, args=(conn, addr))
         process.deamon = True
         process.start()
+        last_proc = process
         # close from our side.
         conn.close()
 
@@ -158,6 +166,11 @@ class Server(object):
         This is recommended to switch on if we want to do local RPC demonstration
         for GPU devices to avoid fork safety issues.
 
+    exclusive : bool, optional
+        If this is enabled, the server will kill old connection
+        when new connection comes. This can make sure the current call
+        monopolize the hardware resource.
+
     key : str, optional
         The key used to identify the server in Proxy connection.
     """
@@ -167,6 +180,7 @@ def __init__(self,
                  port_end=9199,
                  is_proxy=False,
                  use_popen=False,
+                 exclusive=False,
                  key=""):
         self.host = host
         self.port = port
@@ -201,7 +215,7 @@ def __init__(self,
             sock.listen(1)
             self.sock = sock
             self.proc = multiprocessing.Process(
-                target=_listen_loop, args=(self.sock,))
+                target=_listen_loop, args=(self.sock, exclusive))
             self.proc.deamon = True
             self.proc.start()
         else:
@@ -210,8 +224,6 @@ def __init__(self,
             self.proc.deamon = True
             self.proc.start()
 
-
-
     def terminate(self):
         """Terminate the server process"""
         if self.proc:
@@ -222,7 +234,6 @@ def __del__(self):
         self.terminate()
 
 
-
 class RPCSession(object):
     """RPC Client session module
 
diff --git a/python/tvm/exec/rpc_server.py b/python/tvm/exec/rpc_server.py
index 432860f58d1e..deb830bdc583 100644
--- a/python/tvm/exec/rpc_server.py
+++ b/python/tvm/exec/rpc_server.py
@@ -21,6 +21,9 @@ def main():
                         help="Whether to load executor runtime")
     parser.add_argument('--load-library', type=str, default="",
                         help="Additional library to load")
+    parser.add_argument('--exclusive', action='store_true',
+                        help="If this is enabled, the server will kill old connection"
+                             "when new connection comes")
     args = parser.parse_args()
 
     logging.basicConfig(level=logging.INFO)
@@ -35,7 +38,7 @@ def main():
         libs.append(ctypes.CDLL(file_name, ctypes.RTLD_GLOBAL))
         logging.info("Load additional library %s", file_name)
 
-    server = rpc.Server(args.host, args.port, args.port_end)
+    server = rpc.Server(args.host, args.port, args.port_end, exclusive=args.exclusive)
     server.libs += libs
     server.proc.join()
 

From aff6692af662c3f7e8c60bdea53ced6408ce8022 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 28 Feb 2018 08:55:07 -0800
Subject: [PATCH 174/949] Fix compiler warnings (#939)

---
 src/codegen/spirv/ir_builder.cc | 3 ++-
 src/codegen/spirv/ir_builder.h  | 2 +-
 topi/include/topi/nn/softmax.h  | 4 ++--
 3 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/src/codegen/spirv/ir_builder.cc b/src/codegen/spirv/ir_builder.cc
index 500c88ae832d..2bd1d0d5da7c 100644
--- a/src/codegen/spirv/ir_builder.cc
+++ b/src/codegen/spirv/ir_builder.cc
@@ -159,7 +159,8 @@ Value IRBuilder::FloatImm(const SType& dtype, double value) {
     return GetConst_(dtype, reinterpret_cast<uint64_t*>(&value));
   } else if (dtype.type.bits() == 32) {
     float fvalue = static_cast<float>(value);
-    uint64_t data =  reinterpret_cast<uint32_t*>(&fvalue)[0];
+    uint32_t* ptr = reinterpret_cast<uint32_t*>(&fvalue);
+    uint64_t data = ptr[0];
     return GetConst_(dtype, &data);
   } else {
     CHECK_EQ(dtype.type.bits(), 16);
diff --git a/src/codegen/spirv/ir_builder.h b/src/codegen/spirv/ir_builder.h
index bdb320154062..e652a0068278 100644
--- a/src/codegen/spirv/ir_builder.h
+++ b/src/codegen/spirv/ir_builder.h
@@ -344,7 +344,7 @@ class IRBuilder {
    * \tparams Args The positional arguments
    */
   template<typename... Args>
-  Value DeclareGlobal(spv::Op op, Args&& ...args) {
+  void DeclareGlobal(spv::Op op, Args&& ...args) {
     ib_.Begin(op).AddSeq(std::forward<Args>(args)...).Commit(&decorate_);
   }
   /*!
diff --git a/topi/include/topi/nn/softmax.h b/topi/include/topi/nn/softmax.h
index d2348c9f230a..f542f0eb0256 100644
--- a/topi/include/topi/nn/softmax.h
+++ b/topi/include/topi/nn/softmax.h
@@ -47,7 +47,7 @@ inline Tensor softmax(const Tensor &x,
     Array<Expr> eval_range;
     int arg_counter = 0;
     for (size_t i = 0; i < ndim; ++i) {
-      if (i == axis)
+      if (static_cast<int>(i) == axis)
         eval_range.push_back(reduce_index);
       else
         eval_range.push_back(indices[arg_counter++]);
@@ -70,7 +70,7 @@ inline Tensor softmax(const Tensor &x,
                         const Array<Var> &indices) {
     Array<Expr> non_reduce_indices;
     for (size_t i = 0; i < ndim; ++i) {
-      if (i != axis)
+      if (static_cast<int>(i) != axis)
         non_reduce_indices.push_back(indices[i]);
     }
     return tvm::exp(x(indices) - max_elem(non_reduce_indices)) /

From 90c827a7b9d2910f7581fb6555871bedb3d5c490 Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Thu, 1 Mar 2018 09:12:50 +0530
Subject: [PATCH 175/949] Spelling mistake corrected (#945)

---
 python/tvm/container.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/python/tvm/container.py b/python/tvm/container.py
index b0ec44c695cf..d1d4546fd86a 100644
--- a/python/tvm/container.py
+++ b/python/tvm/container.py
@@ -20,7 +20,7 @@ def __getitem__(self, i):
             return [self[idx] for idx in range(start, stop, step)]
 
         if i >= len(self):
-            raise IndexError("array index out ot range")
+            raise IndexError("array index out of range")
         return _api_internal._ArrayGetItem(self, i)
 
     def __len__(self):

From 279a778b36b05dc4b64ade0b0863905b328e5be7 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 1 Mar 2018 10:59:58 -0800
Subject: [PATCH 176/949] Better error message handling for contrib (#946)

* Better error message handling for contrib

* fix lint

* fix testcase

* fix test
---
 python/tvm/contrib/cc.py                   | 9 +++++----
 python/tvm/contrib/emscripten.py           | 4 +++-
 python/tvm/contrib/ndk.py                  | 4 +++-
 python/tvm/contrib/nvcc.py                 | 2 +-
 python/tvm/contrib/rocm.py                 | 3 ++-
 python/tvm/contrib/spirv.py                | 4 ++--
 python/tvm/contrib/tar.py                  | 5 +++--
 python/tvm/contrib/xcode.py                | 6 ++++--
 topi/python/topi/__init__.py               | 5 ++++-
 topi/tests/python/test_topi_dilate.py      | 1 +
 topi/tests/python/test_topi_softmax.py     | 1 +
 topi/tests/python/test_topi_upsampling.py  | 1 +
 topi/tests/python_cpp/test_topi_dilate.py  | 1 +
 topi/tests/python_cpp/test_topi_softmax.py | 1 +
 14 files changed, 32 insertions(+), 15 deletions(-)

diff --git a/python/tvm/contrib/cc.py b/python/tvm/contrib/cc.py
index 5764121c59ed..0ffa6c420243 100644
--- a/python/tvm/contrib/cc.py
+++ b/python/tvm/contrib/cc.py
@@ -3,8 +3,9 @@
 from __future__ import absolute_import as _abs
 import sys
 import subprocess
-
 import os
+
+from .._ffi.base import py_str
 from .util import tempdir
 
 
@@ -53,7 +54,7 @@ def _linux_shared(output, objects, options, cc="g++"):
     (out, _) = proc.communicate()
     if proc.returncode != 0:
         msg = "Compilation error:\n"
-        msg += str(out)
+        msg += py_str(out)
         raise RuntimeError(msg)
 
 
@@ -88,7 +89,7 @@ def _windows_shared(output, objects, options):
                            "please run this in Vistual Studio Command Prompt.")
     if proc.returncode != 0:
         msg = "Compilation error:\n"
-        msg += str(out)
+        msg += py_str(out)
         raise RuntimeError(msg)
     link_cmd = ["link"]
     link_cmd += ["-dll", "-FORCE:MULTIPLE"]
@@ -114,6 +115,6 @@ def _windows_shared(output, objects, options):
                            "please run this in Vistual Studio Command Prompt.")
     if proc.returncode != 0:
         msg = "Compilation error:\n"
-        msg += str(out)
+        msg += py_str(out)
 
         raise RuntimeError(msg)
diff --git a/python/tvm/contrib/emscripten.py b/python/tvm/contrib/emscripten.py
index d263e472cff0..a722a0d673d9 100644
--- a/python/tvm/contrib/emscripten.py
+++ b/python/tvm/contrib/emscripten.py
@@ -1,7 +1,9 @@
 """Util to invoke emscripten compilers in the system."""
 # pylint: disable=invalid-name
 from __future__ import absolute_import as _abs
+
 import subprocess
+from .._ffi.base import py_str
 from .._ffi.libinfo import find_lib_path
 
 def create_js(output,
@@ -58,7 +60,7 @@ def create_js(output,
 
     if proc.returncode != 0:
         msg = "Compilation error:\n"
-        msg += out
+        msg += py_str(out)
         raise RuntimeError(msg)
 
 create_js.object_format = "bc"
diff --git a/python/tvm/contrib/ndk.py b/python/tvm/contrib/ndk.py
index 3751a4574b47..a79aae96163d 100644
--- a/python/tvm/contrib/ndk.py
+++ b/python/tvm/contrib/ndk.py
@@ -1,8 +1,10 @@
 """Util to invoke NDK compiler toolchain."""
 # pylint: disable=invalid-name
 from __future__ import absolute_import as _abs
+
 import subprocess
 import os
+from .._ffi.base import py_str
 
 def create_shared(output,
                   objects,
@@ -43,5 +45,5 @@ def create_shared(output,
 
     if proc.returncode != 0:
         msg = "Compilation error:\n"
-        msg += out
+        msg += py_str(out)
         raise RuntimeError(msg)
diff --git a/python/tvm/contrib/nvcc.py b/python/tvm/contrib/nvcc.py
index ac8dbf65b2bc..e3c2b7895da7 100644
--- a/python/tvm/contrib/nvcc.py
+++ b/python/tvm/contrib/nvcc.py
@@ -72,7 +72,7 @@ def compile_cuda(code,
 
     if proc.returncode != 0:
         msg = "Compilation error:\n"
-        msg += out
+        msg += py_str(out)
         raise RuntimeError(msg)
 
     return bytearray(open(file_target, "rb").read())
diff --git a/python/tvm/contrib/rocm.py b/python/tvm/contrib/rocm.py
index 0f72a4694a82..10cfaed83e68 100644
--- a/python/tvm/contrib/rocm.py
+++ b/python/tvm/contrib/rocm.py
@@ -2,6 +2,7 @@
 import subprocess
 from os.path import join
 from . import util
+from .._ffi.base import py_str
 from ..api import register_func, convert
 
 def rocm_link(in_file, out_file):
@@ -24,7 +25,7 @@ def rocm_link(in_file, out_file):
 
     if proc.returncode != 0:
         msg = "Linking error using ld.lld:\n"
-        msg += str(out)
+        msg += py_str(out)
         raise RuntimeError(msg)
 
 
diff --git a/python/tvm/contrib/spirv.py b/python/tvm/contrib/spirv.py
index eed6240bc76c..586da1a7487f 100644
--- a/python/tvm/contrib/spirv.py
+++ b/python/tvm/contrib/spirv.py
@@ -2,7 +2,7 @@
 import subprocess
 import os
 from . import util
-
+from .._ffi.base import py_str
 
 def optimize(spv_bin):
     """Optimize SPIRV using spirv-opt via CLI
@@ -37,7 +37,7 @@ def optimize(spv_bin):
 
     if proc.returncode != 0:
         msg = "Opitmizationerror using spirv-opt:\n"
-        msg += str(out)
+        msg += py_str(out)
         raise RuntimeError(msg)
 
     return bytearray(open(tmp_out, "rb").read())
diff --git a/python/tvm/contrib/tar.py b/python/tvm/contrib/tar.py
index ca3bf3478840..7e075d7a5697 100644
--- a/python/tvm/contrib/tar.py
+++ b/python/tvm/contrib/tar.py
@@ -6,6 +6,7 @@
 import shutil
 import subprocess
 from . import util
+from .._ffi.base import py_str
 
 def tar(output, files):
     """Create tarball containing all files in root.
@@ -38,7 +39,7 @@ def tar(output, files):
 
     if proc.returncode != 0:
         msg = "Tar error:\n"
-        msg += out
+        msg += py_str(out)
         raise RuntimeError(msg)
 
 
@@ -64,5 +65,5 @@ def untar(tar_file, directory):
 
     if proc.returncode != 0:
         msg = "Tar error:\n"
-        msg += out
+        msg += py_str(out)
         raise RuntimeError(msg)
diff --git a/python/tvm/contrib/xcode.py b/python/tvm/contrib/xcode.py
index e05df7181544..3456bdf1964a 100644
--- a/python/tvm/contrib/xcode.py
+++ b/python/tvm/contrib/xcode.py
@@ -1,9 +1,11 @@
 # pylint: disable=invalid-name
 """Utility to invoke Xcode compiler toolchain"""
 from __future__ import absolute_import as _abs
+
 import os
 import sys
 import subprocess
+from .._ffi.base import py_str
 from . import util
 
 def xcrun(cmd):
@@ -49,7 +51,7 @@ def codesign(lib):
     (out, _) = proc.communicate()
     if proc.returncode != 0:
         msg = "Codesign error:\n"
-        msg += out
+        msg += py_str(out)
         raise RuntimeError(msg)
 
 
@@ -92,7 +94,7 @@ def create_dylib(output, objects, arch, sdk="macosx"):
 
     if proc.returncode != 0:
         msg = "Compilation error:\n"
-        msg += out
+        msg += py_str(out)
         raise RuntimeError(msg)
 
 
diff --git a/topi/python/topi/__init__.py b/topi/python/topi/__init__.py
index f7a922c5c379..ae02211a9d6c 100644
--- a/topi/python/topi/__init__.py
+++ b/topi/python/topi/__init__.py
@@ -22,7 +22,10 @@
 from . import rasp
 from . import mali
 from . import opengl
-from . import testing
 from . import util
 from . import rocm
 from . import cpp
+# not import testing by default
+# because testing can have extra deps that are not necessary
+# we can import them from test cases explicitly
+# from . import testing
diff --git a/topi/tests/python/test_topi_dilate.py b/topi/tests/python/test_topi_dilate.py
index 778c0ba5e9c4..9cc44719745a 100644
--- a/topi/tests/python/test_topi_dilate.py
+++ b/topi/tests/python/test_topi_dilate.py
@@ -1,5 +1,6 @@
 import tvm
 import topi
+import topi.testing
 import numpy as np
 
 
diff --git a/topi/tests/python/test_topi_softmax.py b/topi/tests/python/test_topi_softmax.py
index c8a0f9549803..c768c395a74f 100644
--- a/topi/tests/python/test_topi_softmax.py
+++ b/topi/tests/python/test_topi_softmax.py
@@ -3,6 +3,7 @@
 import numpy as np
 import tvm
 import topi
+import topi.testing
 import logging
 from topi.util import get_const_tuple
 
diff --git a/topi/tests/python/test_topi_upsampling.py b/topi/tests/python/test_topi_upsampling.py
index 20c349c81832..7421dd4151e6 100644
--- a/topi/tests/python/test_topi_upsampling.py
+++ b/topi/tests/python/test_topi_upsampling.py
@@ -2,6 +2,7 @@
 import numpy as np
 import tvm
 import topi
+import topi.testing
 import math
 
 def verify_upsampling(batch, in_channel, in_height, in_width, scale):
diff --git a/topi/tests/python_cpp/test_topi_dilate.py b/topi/tests/python_cpp/test_topi_dilate.py
index 177ddbc3cfd7..f1924239cc77 100644
--- a/topi/tests/python_cpp/test_topi_dilate.py
+++ b/topi/tests/python_cpp/test_topi_dilate.py
@@ -1,5 +1,6 @@
 import tvm
 import topi
+import topi.testing
 import numpy as np
 
 def test_dilate():
diff --git a/topi/tests/python_cpp/test_topi_softmax.py b/topi/tests/python_cpp/test_topi_softmax.py
index 2a6baaafe2f1..4d4ac387bccf 100644
--- a/topi/tests/python_cpp/test_topi_softmax.py
+++ b/topi/tests/python_cpp/test_topi_softmax.py
@@ -4,6 +4,7 @@
 import tvm
 import topi
 import logging
+import topi.testing
 from topi.util import get_const_tuple
 
 def verify_softmax(m, n):

From e3e288fb0cb0ca76746535e069d9d4408f9158a6 Mon Sep 17 00:00:00 2001
From: Yida Wang <yidawa@gmail.com>
Date: Thu, 1 Mar 2018 12:14:44 -0800
Subject: [PATCH 177/949] remove the pragma primitives for better performance
 when the threads are binded (#949)

---
 topi/python/topi/x86/conv2d_avx_1x1.py    | 9 ---------
 topi/python/topi/x86/conv2d_avx_common.py | 9 ---------
 2 files changed, 18 deletions(-)

diff --git a/topi/python/topi/x86/conv2d_avx_1x1.py b/topi/python/topi/x86/conv2d_avx_1x1.py
index cc264d04ac24..afd0be2e2ded 100644
--- a/topi/python/topi/x86/conv2d_avx_1x1.py
+++ b/topi/python/topi/x86/conv2d_avx_1x1.py
@@ -77,9 +77,6 @@ def _schedule_conv(s, data, data_pad, data_vec, kernel, kernel_vec, conv_out, ou
     batch, ic_chunk, ih, ic_block, iw = s[A1].op.axis
     parallel_axis = s[A1].fuse(ic_chunk, ih)
     s[A1].parallel(parallel_axis)
-    s[A1].pragma(batch, "parallel_launch_point")
-    s[A1].pragma(parallel_axis, "parallel_stride_pattern")
-    s[A1].pragma(batch, "parallel_barrier_when_finish")
 
     # schedule kernel pack
     oc_chunk, ic_chunk, oh, ow, ic_block, oc_block = s[W].op.axis
@@ -88,9 +85,6 @@ def _schedule_conv(s, data, data_pad, data_vec, kernel, kernel_vec, conv_out, ou
         s[W].vectorize(oc_block)
     parallel_axis = s[W].fuse(oc_chunk, oh)
     s[W].parallel(parallel_axis)
-    s[W].pragma(parallel_axis, "parallel_launch_point")
-    s[W].pragma(parallel_axis, "parallel_stride_pattern")
-    s[W].pragma(parallel_axis, "parallel_barrier_when_finish")
 
     C, O0, O = conv_out, output, last
     CC = s.cache_write(C, 'global')
@@ -128,8 +122,5 @@ def _schedule_conv(s, data, data_pad, data_vec, kernel, kernel_vec, conv_out, ou
     s[O].vectorize(oc_block)
 
     s[O].parallel(parallel_axis)
-    s[O].pragma(batch, "parallel_launch_point")
-    s[O].pragma(parallel_axis, "parallel_stride_pattern")
-    s[O].pragma(batch, "parallel_barrier_when_finish")
 
     return s
diff --git a/topi/python/topi/x86/conv2d_avx_common.py b/topi/python/topi/x86/conv2d_avx_common.py
index 4f5be019f45a..f4c0e453e643 100644
--- a/topi/python/topi/x86/conv2d_avx_common.py
+++ b/topi/python/topi/x86/conv2d_avx_common.py
@@ -90,9 +90,6 @@ def _schedule_conv(s, data, data_pad, data_vec, kernel, kernel_vec, conv_out, ou
     batch, ic_chunk, ih, ic_block, iw = s[A1].op.axis
     parallel_axis = s[A1].fuse(ic_chunk, ih)
     s[A1].parallel(parallel_axis)
-    s[A1].pragma(batch, "parallel_launch_point")
-    s[A1].pragma(parallel_axis, "parallel_stride_pattern")
-    s[A1].pragma(batch, "parallel_barrier_when_finish")
 
     # schedule kernel pack
     oc_chunk, ic_chunk, oh, ow, ic_block, oc_block = s[W].op.axis
@@ -101,9 +98,6 @@ def _schedule_conv(s, data, data_pad, data_vec, kernel, kernel_vec, conv_out, ou
         s[W].vectorize(oc_block)
     parallel_axis = s[W].fuse(oc_chunk, oh)
     s[W].parallel(parallel_axis)
-    s[W].pragma(parallel_axis, "parallel_launch_point")
-    s[W].pragma(parallel_axis, "parallel_stride_pattern")
-    s[W].pragma(parallel_axis, "parallel_barrier_when_finish")
 
     # schedule conv
     C, O0, O = conv_out, output, last
@@ -144,8 +138,5 @@ def _schedule_conv(s, data, data_pad, data_vec, kernel, kernel_vec, conv_out, ou
     s[O].vectorize(oc_block)
 
     s[O].parallel(parallel_axis)
-    s[O].pragma(batch, "parallel_launch_point")
-    s[O].pragma(parallel_axis, "parallel_stride_pattern")
-    s[O].pragma(batch, "parallel_barrier_when_finish")
 
     return s

From 68aac7f89a945385c2c47400aa92bb41c3713f5b Mon Sep 17 00:00:00 2001
From: Atsushi Nukariya <34906252+AtuNuka@users.noreply.github.com>
Date: Fri, 2 Mar 2018 14:07:35 +0900
Subject: [PATCH 178/949] Fix a typo for Target class (#951)

---
 python/tvm/target.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/python/tvm/target.py b/python/tvm/target.py
index ab02619f6deb..7fa8998bfbe1 100644
--- a/python/tvm/target.py
+++ b/python/tvm/target.py
@@ -99,7 +99,7 @@ def __init__(self,
                 self.libs += libs.split(",")
             elif item.startswith("-device="):
                 self.device_name = item.split("=")[1]
-        # Target query searchs device name first
+        # Target query searches device name first
         if self.device_name:
             self.keys = (self.device_name,)
         else:
@@ -122,7 +122,7 @@ def __init__(self,
         elif target_name in ("opengl",):
             self.keys += ("opengl",)
         elif target_name in ("stackvm", "ext_dev"):
-            # Do not now class for stacvm or ext_dev
+            # Do not now class for stackvm or ext_dev
             pass
         else:
             raise ValueError("Unknown target name %s" % target_name)

From adba4011adad5a531bf7d06cbba5e36c04a03a4b Mon Sep 17 00:00:00 2001
From: Zhixun Tan <phisiart@gmail.com>
Date: Fri, 2 Mar 2018 08:39:15 -0800
Subject: [PATCH 179/949] Add the equivalence of graph_runtime.py in
 tvm_runtime.js (#950)

---
 web/tvm_runtime.js | 103 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 103 insertions(+)

diff --git a/web/tvm_runtime.js b/web/tvm_runtime.js
index 347532f5bdbf..bc12f4f39c14 100644
--- a/web/tvm_runtime.js
+++ b/web/tvm_runtime.js
@@ -656,6 +656,8 @@ var tvm_runtime = tvm_runtime || {};
             v = convertFunc(v);
             this.temp.push(v);
             this.setHandle(i, v._tvm_function.handle, kFuncHandle);
+          } else if (v instanceof TVMModule) {
+            this.setHandle(i, v.handle, kModuleHandle);
           } else {
             throwError("Unsupported argument type " + tp);
           }
@@ -977,6 +979,107 @@ var tvm_runtime = tvm_runtime || {};
     };
     var loadModuleFromFile = this.loadModuleFromFile;
 
+    /**
+     * Wrapper runtime module.
+     * Wraps around set_input, load_params, run, and get_output.
+     *
+     * @class
+     * @memberof tvm
+     */
+    function GraphModule(tvm_graph_module, ctx) {
+      CHECK(tvm_graph_module instanceof TVMModule,
+            "tvm_graph_module must be TVMModule");
+      CHECK(ctx instanceof TVMContext, "ctx must be TVMContext");
+
+      this.tvm_graph_module = tvm_graph_module;
+      this.ctx = ctx;
+      this._set_input = tvm_graph_module.getFunction("set_input");
+      this._load_params = tvm_graph_module.getFunction("load_params");
+      this._run = tvm_graph_module.getFunction("run");
+      this._get_output = tvm_graph_module.getFunction("get_output");
+    };
+
+    GraphModule.prototype = {
+      /**
+       * Set input to graph module.
+       *
+       * @param {string} key The name of the input.
+       * @param {NDArray} value The input value.
+       */
+      "set_input" : function(key, value) {
+        CHECK(typeof key == "string", "key must be string");
+        CHECK(value instanceof NDArray, "value must be NDArray");
+        this._set_input(key, value);
+      },
+
+      /**
+       * Load parameters from serialized byte array of parameter dict.
+       * 
+       * @param {Uint8Array} params The serialized parameter dict.
+       */
+      "load_params" : function(params) {
+        CHECK(params instanceof Uint8Array, "params must be Uint8Array");
+        this._load_params(params);
+      },
+
+      /**
+       * Load parameters from serialized base64 string of parameter dict.
+       * 
+       * @param {string} base64_params The serialized parameter dict.
+       */
+      "load_base64_params" : function(base64_params) {
+        CHECK(typeof base64_params == "string", "base64_params must be string");
+        var decoded_string = atob(base64_params);
+        var decoded_u8 = new Uint8Array(decoded_string.length);
+        for (var i = 0; i < decoded_string.length; i++) {
+          decoded_u8[i] = decoded_string[i].charCodeAt(0);
+        }
+        this.load_params(decoded_u8);
+      },
+
+      /**
+       * Run forward execution of the graph.
+       */
+      "run" : function() {
+        this._run();
+      },
+
+      /**
+       * Get index-th output to out.
+       * 
+       * @param {NDArray} out The output array container.
+       * @return {NDArray} The output array container.
+       */
+      "get_output" : function(index, out) {
+        CHECK(typeof index == "number", "index must be number");
+        CHECK(out instanceof NDArray, "out must be NDArray");
+        this._get_output(new TVMConstant(index, "int32"), out);
+        return out;
+      }
+    };
+
+    /**
+     * Create a runtime executor module given a graph and a module.
+     * @param {string} graph_json_str The Json string of the graph.
+     * @param {TVMModule} libmod The TVM module.
+     * @param {TVMContext} ctx The context to deploy the module.
+     * @return {GraphModule} Runtime graph module for executing the graph.
+     */
+    this.createGraphRuntime = function(graph_json_str, libmod, ctx) {
+      CHECK(typeof graph_json_str == "string", "graph_json_str must be string");
+      CHECK(libmod instanceof TVMModule, "libmod must be TVMModule");
+      CHECK(ctx instanceof TVMContext, "ctx must be TVMContext");
+
+      var fcreate = getGlobalFunc("tvm.graph_runtime.create");
+      CHECK(fcreate != null, "Cannot find tvm.graph_runtime.create");
+
+      var tvm_graph_module = fcreate(graph_json_str, libmod,
+                                     new TVMConstant(ctx.device_type, "int32"),
+                                     new TVMConstant(ctx.device_id, "int32"));
+      
+      return new GraphModule(tvm_graph_module, ctx);
+    };
+
     //-----------------------------------------
     // Class defintions
     // ----------------------------------------

From 9c850a145c26dbe7753cfee9478369f1c153597c Mon Sep 17 00:00:00 2001
From: Chris Nuernberger <cnuernber@gmail.com>
Date: Fri, 2 Mar 2018 12:59:22 -0700
Subject: [PATCH 180/949] [RUNTIME] Stream API  (#953)

---
 CONTRIBUTORS.md                         |  1 +
 include/tvm/c_dsl_api.h                 |  2 +-
 include/tvm/runtime/c_backend_api.h     |  2 +-
 include/tvm/runtime/c_runtime_api.h     | 42 +++++++++++++++++++----
 include/tvm/runtime/device_api.h        | 33 +++++++++++++++++-
 src/runtime/c_runtime_api.cc            | 45 +++++++++++++++++++++++++
 src/runtime/cuda/cuda_device_api.cc     | 26 +++++++++++++-
 src/runtime/opencl/opencl_device_api.cc |  4 +--
 8 files changed, 142 insertions(+), 13 deletions(-)

diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 13b5ee1c5dd3..7a7f52b5ee82 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -37,3 +37,4 @@ List of Contributors
 - [Masahiro Masuda](https://github.com/masahi)
 - [Haolong Zhang](https://github.com/haolongzhangm)
 - [Cody Hao Yu](https://github.com/comaniac)
+- [Chris Nuernberger](https://github.com/cnuernber)
diff --git a/include/tvm/c_dsl_api.h b/include/tvm/c_dsl_api.h
index f81018a7610e..0ba34ed5ccee 100644
--- a/include/tvm/c_dsl_api.h
+++ b/include/tvm/c_dsl_api.h
@@ -17,7 +17,7 @@
 #include "./runtime/c_runtime_api.h"
 
 #ifdef __cplusplus
-TVM_EXTERN_C {
+extern "C" {
 #endif
 
 /*! \brief handle to node */
diff --git a/include/tvm/runtime/c_backend_api.h b/include/tvm/runtime/c_backend_api.h
index 079ab1efb040..01e8a9486655 100644
--- a/include/tvm/runtime/c_backend_api.h
+++ b/include/tvm/runtime/c_backend_api.h
@@ -13,7 +13,7 @@
 #include "./c_runtime_api.h"
 
 #ifdef __cplusplus
-TVM_EXTERN_C {
+extern "C" {
 #endif
 
 // Backend related functions.
diff --git a/include/tvm/runtime/c_runtime_api.h b/include/tvm/runtime/c_runtime_api.h
index e4a06b39d04e..f6d22534e8ab 100644
--- a/include/tvm/runtime/c_runtime_api.h
+++ b/include/tvm/runtime/c_runtime_api.h
@@ -18,12 +18,6 @@
 #ifndef TVM_RUNTIME_C_RUNTIME_API_H_
 #define TVM_RUNTIME_C_RUNTIME_API_H_
 
-#ifdef __cplusplus
-#define TVM_EXTERN_C extern "C"
-#else
-#define TVM_EXTERN_C
-#endif
-
 // Macros to do weak linking
 #ifdef _MSC_VER
 #define TVM_WEAK __declspec(selectany)
@@ -52,7 +46,7 @@
 #include <dlpack/dlpack.h>
 
 #ifdef __cplusplus
-TVM_EXTERN_C {
+extern "C" {
 #endif
 #include <stdint.h>
 #include <stddef.h>
@@ -443,6 +437,26 @@ TVM_DLL int TVMArrayCopyFromTo(TVMArrayHandle from,
                                TVMArrayHandle to,
                                TVMStreamHandle stream);
 
+/*!
+ * \brief Create a new runtime stream.  
+ *
+ * \param device_type The device type of context
+ * \param device_id The device id of context
+ * \param out The new stream handle
+ * \return 0 when success, -1 when failure happens
+ */
+TVM_DLL int TVMStreamCreate(int device_type, int device_id, TVMStreamHandle* out);
+
+/*!
+ * \brief Free a created stream handle.
+ *
+ * \param device_type The device type of context
+ * \param device_id The device id of context
+ * \param stream The stream to be freed
+ * \return 0 when success, -1 when failure happens
+ */
+TVM_DLL int TVMStreamFree(int device_type, int device_id, TVMStreamHandle stream);
+
 /*!
  * \brief Set the runtime stream of current thread to be stream.
  *  The subsequent calls to the same device_type
@@ -466,6 +480,20 @@ TVM_DLL int TVMSetStream(int device_type, int device_id, TVMStreamHandle handle)
  */
 TVM_DLL int TVMSynchronize(int device_type, int device_id, TVMStreamHandle stream);
 
+/*!
+ * \brief Synchronize two streams of execution.
+ *
+ * \param device_type The device type of context
+ * \param device_id The device id of context
+ * \param src The source stream to synchronize.
+ * \param dst The destination stream to synchronize.
+ * \return 0 when success, -1 when failure happens
+ */
+TVM_DLL int TVMStreamStreamSynchronize(int device_type,
+                                       int device_id,
+                                       TVMStreamHandle src,
+                                       TVMStreamHandle dst);
+
 #ifdef __cplusplus
 }  // TVM_EXTERN_C
 #endif
diff --git a/include/tvm/runtime/device_api.h b/include/tvm/runtime/device_api.h
index 45009f1d3af3..ff3fe8062920 100644
--- a/include/tvm/runtime/device_api.h
+++ b/include/tvm/runtime/device_api.h
@@ -19,7 +19,7 @@ enum DeviceAttrKind : int {
   kExist = 0,
   kMaxThreadsPerBlock = 1,
   kWarpSize = 2,
-  kComputeVersion = 3
+  kComputeVersion = 3,
 };
 
 /*! \brief Number of bytes each allocation must align to */
@@ -90,6 +90,21 @@ class DeviceAPI {
                               TVMContext ctx_from,
                               TVMContext ctx_to,
                               TVMStreamHandle stream) = 0;
+    /*!
+   * \brief Create a new stream of execution.
+   *
+   * \param ctx The context of allocation.
+   */
+  TVM_DLL virtual TVMStreamHandle CreateStream(TVMContext ctx);
+
+  /*!
+   * \brief Free a stream of execution
+   *
+   * \param ctx The context of the stream
+   * \param stream The pointer to be freed.
+   */
+  TVM_DLL virtual void FreeStream(TVMContext ctx, TVMStreamHandle stream);
+
   /*!
    * \brief Synchronize the stream
    * \param ctx The context to perform operation.
@@ -102,6 +117,21 @@ class DeviceAPI {
    * \param stream The stream to be set.
    */
   virtual void SetStream(TVMContext ctx, TVMStreamHandle stream) {}
+  /*!
+   * \brief Synchronize 2 streams of execution.
+   *
+   * An event is created in event_src stream that the second then 
+   * stream waits on.  Neither event_src or event_dst need to be of
+   * the same device ID as the context, but they must be of the same
+   * device type.
+   *
+   * \param ctx The context of the streams.
+   * \param event_src The source stream to synchronize.
+   * \param event_dst The destination stream to synchronize.
+   */
+  TVM_DLL virtual void SyncStreamFromTo(TVMContext ctx,
+                                        TVMStreamHandle event_src,
+                                        TVMStreamHandle event_dst);
   /*!
    * \brief Allocate temporal workspace for backend execution.
    *
@@ -128,6 +158,7 @@ class DeviceAPI {
    * \param ptr The pointer to be freed.
    */
   TVM_DLL virtual void FreeWorkspace(TVMContext ctx, void* ptr);
+
   /*!
    * \brief Get device API base don context.
    * \param ctx The context
diff --git a/src/runtime/c_runtime_api.cc b/src/runtime/c_runtime_api.cc
index 37902b448021..7d65a04bc040 100644
--- a/src/runtime/c_runtime_api.cc
+++ b/src/runtime/c_runtime_api.cc
@@ -106,6 +106,21 @@ void DeviceAPI::FreeWorkspace(TVMContext ctx, void* ptr) {
   FreeDataSpace(ctx, ptr);
 }
 
+TVMStreamHandle DeviceAPI::CreateStream(TVMContext ctx) {
+  LOG(FATAL) << "Device does not support stream api.";
+  return 0;
+}
+
+void DeviceAPI::FreeStream(TVMContext ctx, TVMStreamHandle stream) {
+  LOG(FATAL) << "Device does not support stream api.";
+}
+
+void DeviceAPI::SyncStreamFromTo(TVMContext ctx,
+                                 TVMStreamHandle event_src,
+                                 TVMStreamHandle event_dst) {
+  LOG(FATAL) << "Device does not support stream api.";
+}
+
 inline TVMArray* TVMArrayCreate_() {
   TVMArray* arr = new TVMArray();
   arr->shape = nullptr;
@@ -448,6 +463,24 @@ int TVMArrayCopyToBytes(TVMArrayHandle handle,
   API_END();
 }
 
+int TVMStreamCreate(int device_type, int device_id, TVMStreamHandle* out) {
+  API_BEGIN();
+  TVMContext ctx;
+  ctx.device_type = static_cast<DLDeviceType>(device_type);
+  ctx.device_id = device_id;
+  *out = DeviceAPIManager::Get(ctx)->CreateStream(ctx);
+  API_END();
+}
+
+int TVMStreamFree(int device_type, int device_id, TVMStreamHandle stream) {
+  API_BEGIN();
+  TVMContext ctx;
+  ctx.device_type = static_cast<DLDeviceType>(device_type);
+  ctx.device_id = device_id;
+  DeviceAPIManager::Get(ctx)->FreeStream(ctx, stream);
+  API_END();
+}
+
 int TVMSetStream(int device_type, int device_id, TVMStreamHandle stream) {
   API_BEGIN();
   TVMContext ctx;
@@ -466,6 +499,18 @@ int TVMSynchronize(int device_type, int device_id, TVMStreamHandle stream) {
   API_END();
 }
 
+int TVMStreamStreamSynchronize(int device_type,
+                               int device_id,
+                               TVMStreamHandle src,
+                               TVMStreamHandle dst) {
+  API_BEGIN();
+  TVMContext ctx;
+  ctx.device_type = static_cast<DLDeviceType>(device_type);
+  ctx.device_id = device_id;
+  DeviceAPIManager::Get(ctx)->SyncStreamFromTo(ctx, src, dst);
+  API_END();
+}
+
 int TVMCbArgToReturn(TVMValue* value, int code) {
   API_BEGIN();
   tvm::runtime::TVMRetValue rv;
diff --git a/src/runtime/cuda/cuda_device_api.cc b/src/runtime/cuda/cuda_device_api.cc
index 7885aa7705ed..9b0fee5023f1 100644
--- a/src/runtime/cuda/cuda_device_api.cc
+++ b/src/runtime/cuda/cuda_device_api.cc
@@ -30,7 +30,7 @@ class CUDADeviceAPI final : public DeviceAPI {
                 &value, cudaDevAttrMaxThreadsPerBlock, ctx.device_id)
             == cudaSuccess);
         break;
-      case kMaxThreadsPerBlock: {
+    case kMaxThreadsPerBlock: {
         CUDA_CALL(cudaDeviceGetAttribute(
             &value, cudaDevAttrMaxThreadsPerBlock, ctx.device_id));
         break;
@@ -102,6 +102,30 @@ class CUDADeviceAPI final : public DeviceAPI {
     }
   }
 
+  TVMStreamHandle CreateStream(TVMContext ctx) {
+    CUDA_CALL(cudaSetDevice(ctx.device_id));
+    cudaStream_t retval;
+    CUDA_CALL(cudaStreamCreate(&retval));
+    return static_cast<TVMStreamHandle>(retval);
+  }
+
+  void FreeStream(TVMContext ctx, TVMStreamHandle stream) {
+    CUDA_CALL(cudaSetDevice(ctx.device_id));
+    cudaStream_t cu_stream = static_cast<cudaStream_t>(stream);
+    CUDA_CALL(cudaStreamDestroy(cu_stream));
+  }
+
+  void SyncStreamFromTo(TVMContext ctx, TVMStreamHandle event_src, TVMStreamHandle event_dst) {
+    CUDA_CALL(cudaSetDevice(ctx.device_id));
+    cudaStream_t src_stream = static_cast<cudaStream_t>(event_src);
+    cudaStream_t dst_stream = static_cast<cudaStream_t>(event_dst);
+    cudaEvent_t evt;
+    CUDA_CALL(cudaEventCreate(&evt));
+    CUDA_CALL(cudaEventRecord(evt, src_stream));
+    CUDA_CALL(cudaStreamWaitEvent(dst_stream, evt, 0));
+    CUDA_CALL(cudaEventDestroy(evt));
+  }
+
   void StreamSync(TVMContext ctx, TVMStreamHandle stream) final {
     CUDA_CALL(cudaSetDevice(ctx.device_id));
     CUDA_CALL(cudaStreamSynchronize(static_cast<cudaStream_t>(stream)));
diff --git a/src/runtime/opencl/opencl_device_api.cc b/src/runtime/opencl/opencl_device_api.cc
index 4c3d206b9354..6341c9f4b83d 100644
--- a/src/runtime/opencl/opencl_device_api.cc
+++ b/src/runtime/opencl/opencl_device_api.cc
@@ -45,8 +45,8 @@ void OpenCLWorkspace::GetAttr(
       *rv = 1;
       break;
     }
-    case kComputeVersion: return;
-    case kExist: break;
+  case kComputeVersion: return;
+  case kExist: break;
   }
 }
 

From 8c5bc873838ba89e3e1c11205ed70a20be781f2a Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 2 Mar 2018 13:22:20 -0800
Subject: [PATCH 181/949] [CODEGEN] Fix alignment generation (#955)

---
 src/codegen/llvm/codegen_llvm.cc           | 13 +++++++++----
 tests/python/unittest/test_codegen_llvm.py | 15 +++++++++++++++
 2 files changed, 24 insertions(+), 4 deletions(-)

diff --git a/src/codegen/llvm/codegen_llvm.cc b/src/codegen/llvm/codegen_llvm.cc
index e95407a5c057..934398d9ce09 100644
--- a/src/codegen/llvm/codegen_llvm.cc
+++ b/src/codegen/llvm/codegen_llvm.cc
@@ -339,6 +339,7 @@ void CodeGenLLVM::GetAlignment(Type t,
   }
 
   arith::ModularEntry me = arith::EvalModular(index, align_map_);
+
   int align_bits = t.bits();
   while (align_bits < max_align_bits &&
          me.base % 2  == 0 &&
@@ -814,13 +815,13 @@ llvm::Value* CodeGenLLVM::VisitExpr_(const Let* op) {
 
 llvm::Value* CodeGenLLVM::VisitExpr_(const Load* op) {
   Type t = op->type;
-  int alignment, native_bits;
   bool is_volatile = volatile_buf_.count(op->buffer_var.get());
-  GetAlignment(t, op->buffer_var.get(), op->index, &alignment, &native_bits);
   llvm::Value* buffer = MakeValue(op->buffer_var);
   llvm::Value* index = MakeValue(op->index);
 
   if (t.lanes() == 1) {
+    int alignment, native_bits;
+    GetAlignment(t, op->buffer_var.get(), op->index, &alignment, &native_bits);
     llvm::Value* ptr = CreateBufferPtr(t, buffer, index);
     llvm::LoadInst* load = builder_->CreateAlignedLoad(ptr, alignment, is_volatile);
     AddAliasInfo(load, op->buffer_var.get(), op->index, t);
@@ -831,6 +832,8 @@ llvm::Value* CodeGenLLVM::VisitExpr_(const Load* op) {
       buffer->getType())->getAddressSpace();
     if (const Ramp* ramp = op->index.as<Ramp>()) {
       if (is_one(ramp->stride)) {
+        int alignment, native_bits;
+        GetAlignment(t, op->buffer_var.get(), ramp->base, &alignment, &native_bits);
         CHECK_EQ(ramp->lanes, t.lanes());
         llvm::Value* ptr = CreateBufferPtr(
             t.element_of(), buffer, MakeValue(ramp->base));
@@ -885,14 +888,14 @@ llvm::Value* CodeGenLLVM::VisitExpr_(const Broadcast* op) {
 void CodeGenLLVM::VisitStmt_(const Store* op) {
   CHECK(is_one(op->predicate));
   Type t = op->value.type();
-  int alignment, native_bits;
   bool is_volatile = volatile_buf_.count(op->buffer_var.get());
-  GetAlignment(t, op->buffer_var.get(), op->index, &alignment, &native_bits);
   llvm::Value* buffer = MakeValue(op->buffer_var);
   llvm::Value* index = MakeValue(op->index);
   llvm::Value* value = MakeValue(op->value);
 
   if (t.lanes() == 1) {
+    int alignment, native_bits;
+    GetAlignment(t, op->buffer_var.get(), op->index, &alignment, &native_bits);
     llvm::Value* ptr = CreateBufferPtr(t, buffer, index);
     llvm::StoreInst* store = builder_->CreateAlignedStore(value, ptr, alignment, is_volatile);
     AddAliasInfo(store, op->buffer_var.get(), op->index, op->value.type());
@@ -903,6 +906,8 @@ void CodeGenLLVM::VisitStmt_(const Store* op) {
         buffer->getType())->getAddressSpace();
     if (const Ramp* ramp = op->index.as<Ramp>()) {
       if (is_one(ramp->stride)) {
+        int alignment, native_bits;
+        GetAlignment(t, op->buffer_var.get(), ramp->base, &alignment, &native_bits);
         CHECK_EQ(ramp->lanes, t.lanes());
         llvm::Value* ptr = CreateBufferPtr(
             t.element_of(), buffer, MakeValue(ramp->base));
diff --git a/tests/python/unittest/test_codegen_llvm.py b/tests/python/unittest/test_codegen_llvm.py
index 24996c842249..f05fad10d273 100644
--- a/tests/python/unittest/test_codegen_llvm.py
+++ b/tests/python/unittest/test_codegen_llvm.py
@@ -297,7 +297,22 @@ def check_llvm(n):
     check_llvm(64)
 
 
+def test_alignment():
+    n = tvm.convert(1024)
+    A = tvm.placeholder((n,), name='A')
+    B = tvm.compute(A.shape, lambda i: A[i] * 3, name='B')
+    s = tvm.create_schedule(B.op)
+    bx, tx = s[B].split(B.op.axis[0], factor=8)
+    s[B].vectorize(tx)
+    f = tvm.build(s, [A, B], "llvm")
+
+    for l in f.get_source().split("\n"):
+        if "align" in l and "4 x float" in l:
+            assert "align 32" in l
+
+
 if __name__ == "__main__":
+    test_alignment()
     test_rank_zero()
     test_llvm_bool()
     test_llvm_persist_parallel()

From e2bbd9f46c6796e08fe60479450178e632182aea Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 2 Mar 2018 15:19:13 -0800
Subject: [PATCH 182/949] explicit import testing (#956)

* explicit import testing

* Enable init api for extension modules
---
 python/tvm/_ffi/function.py                   | 20 +++++++++++++------
 topi/tests/python/test_topi_conv2d.py         |  1 +
 topi/tests/python/test_topi_conv2d_hwcn.py    |  1 +
 topi/tests/python/test_topi_conv2d_nhwc.py    |  1 +
 .../python/test_topi_conv2d_transpose_nchw.py |  1 +
 topi/tests/python/test_topi_dense.py          |  1 +
 .../python/test_topi_depthwise_conv2d.py      |  1 +
 .../test_topi_depthwise_conv2d_back_input.py  |  1 +
 .../test_topi_depthwise_conv2d_back_weight.py |  1 +
 topi/tests/python/test_topi_math.py           |  1 +
 10 files changed, 23 insertions(+), 6 deletions(-)

diff --git a/python/tvm/_ffi/function.py b/python/tvm/_ffi/function.py
index 526d972f6d28..90a0c18dec5d 100644
--- a/python/tvm/_ffi/function.py
+++ b/python/tvm/_ffi/function.py
@@ -280,15 +280,23 @@ def my_api_func(*args):
         return flocal(*args)
     return my_api_func
 
-def _init_api(namespace):
+def _init_api(namespace, target_module_name=None):
     """Initialize api for a given module name
 
-    mod : str
-       The name of the module.
+    namespace : str
+       The namespace of the source registry
+
+    target_module_name : str
+       The target module name if different from namespace
     """
-    assert namespace.startswith("tvm.")
-    prefix = namespace[4:]
-    _init_api_prefix(namespace, prefix)
+    target_module_name = (
+        target_module_name if target_module_name else namespace)
+    if namespace.startswith("tvm."):
+        prefix = namespace[4:]
+        _init_api_prefix(target_module_name, namespace[4:])
+    else:
+        _init_api_prefix(target_module_name, namespace)
+
 
 def _init_api_prefix(module_name, prefix):
     module = sys.modules[module_name]
diff --git a/topi/tests/python/test_topi_conv2d.py b/topi/tests/python/test_topi_conv2d.py
index 3e6978752b29..e7ea956eea78 100644
--- a/topi/tests/python/test_topi_conv2d.py
+++ b/topi/tests/python/test_topi_conv2d.py
@@ -3,6 +3,7 @@
 import numpy as np
 import tvm
 import topi
+import topi.testing
 from tvm.contrib.pickle_memoize import memoize
 from topi.util import get_const_tuple
 
diff --git a/topi/tests/python/test_topi_conv2d_hwcn.py b/topi/tests/python/test_topi_conv2d_hwcn.py
index 7761a90a0eed..84962a0b4789 100644
--- a/topi/tests/python/test_topi_conv2d_hwcn.py
+++ b/topi/tests/python/test_topi_conv2d_hwcn.py
@@ -3,6 +3,7 @@
 import numpy as np
 import tvm
 import topi
+import topi.testing
 from tvm.contrib.pickle_memoize import memoize
 from topi.util import get_const_tuple
 
diff --git a/topi/tests/python/test_topi_conv2d_nhwc.py b/topi/tests/python/test_topi_conv2d_nhwc.py
index 7fc5b841908f..40aa3e550a86 100644
--- a/topi/tests/python/test_topi_conv2d_nhwc.py
+++ b/topi/tests/python/test_topi_conv2d_nhwc.py
@@ -3,6 +3,7 @@
 import numpy as np
 import tvm
 import topi
+import topi.testing
 from tvm.contrib.pickle_memoize import memoize
 from topi.util import get_const_tuple
 
diff --git a/topi/tests/python/test_topi_conv2d_transpose_nchw.py b/topi/tests/python/test_topi_conv2d_transpose_nchw.py
index 712f64ca2c87..dcdddca9107b 100644
--- a/topi/tests/python/test_topi_conv2d_transpose_nchw.py
+++ b/topi/tests/python/test_topi_conv2d_transpose_nchw.py
@@ -2,6 +2,7 @@
 import numpy as np
 import tvm
 import topi
+import topi.testing
 from tvm.contrib.pickle_memoize import memoize
 from topi.util import get_const_tuple
 
diff --git a/topi/tests/python/test_topi_dense.py b/topi/tests/python/test_topi_dense.py
index cbae2055a3cd..779e1af1f35e 100644
--- a/topi/tests/python/test_topi_dense.py
+++ b/topi/tests/python/test_topi_dense.py
@@ -2,6 +2,7 @@
 import numpy as np
 import tvm
 import topi
+import topi.testing
 from topi.util import get_const_tuple
 from tvm.contrib.pickle_memoize import memoize
 
diff --git a/topi/tests/python/test_topi_depthwise_conv2d.py b/topi/tests/python/test_topi_depthwise_conv2d.py
index 8ecfdd063a94..5a12d7abb208 100644
--- a/topi/tests/python/test_topi_depthwise_conv2d.py
+++ b/topi/tests/python/test_topi_depthwise_conv2d.py
@@ -1,5 +1,6 @@
 import tvm
 import topi
+import topi.testing
 import numpy as np
 from scipy import signal
 from topi.util import get_const_tuple
diff --git a/topi/tests/python/test_topi_depthwise_conv2d_back_input.py b/topi/tests/python/test_topi_depthwise_conv2d_back_input.py
index 4280e92bdcb9..663364c5345b 100644
--- a/topi/tests/python/test_topi_depthwise_conv2d_back_input.py
+++ b/topi/tests/python/test_topi_depthwise_conv2d_back_input.py
@@ -5,6 +5,7 @@
 from scipy import signal
 from topi.util import get_const_tuple
 from topi.nn.util import get_pad_tuple
+import topi.testing
 from topi.cuda.depthwise_conv2d import schedule_depthwise_conv2d_backward_input_nhwc
 
 
diff --git a/topi/tests/python/test_topi_depthwise_conv2d_back_weight.py b/topi/tests/python/test_topi_depthwise_conv2d_back_weight.py
index 5aa65d2c4039..25d08e1b1882 100644
--- a/topi/tests/python/test_topi_depthwise_conv2d_back_weight.py
+++ b/topi/tests/python/test_topi_depthwise_conv2d_back_weight.py
@@ -1,5 +1,6 @@
 import tvm
 import topi
+import topi.testing
 import numpy as np
 from tvm.contrib.pickle_memoize import memoize
 from scipy import signal
diff --git a/topi/tests/python/test_topi_math.py b/topi/tests/python/test_topi_math.py
index 2c31d0fea62c..dc3c015d4f25 100644
--- a/topi/tests/python/test_topi_math.py
+++ b/topi/tests/python/test_topi_math.py
@@ -1,6 +1,7 @@
 import numpy as np
 import tvm
 import topi
+import topi.testing
 from topi import util
 
 

From e38500f43c66021520a87df301315c26e075465d Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 2 Mar 2018 15:39:52 -0800
Subject: [PATCH 183/949] Update function.py

---
 python/tvm/_ffi/function.py | 1 -
 1 file changed, 1 deletion(-)

diff --git a/python/tvm/_ffi/function.py b/python/tvm/_ffi/function.py
index 90a0c18dec5d..e0f85be6f1a9 100644
--- a/python/tvm/_ffi/function.py
+++ b/python/tvm/_ffi/function.py
@@ -292,7 +292,6 @@ def _init_api(namespace, target_module_name=None):
     target_module_name = (
         target_module_name if target_module_name else namespace)
     if namespace.startswith("tvm."):
-        prefix = namespace[4:]
         _init_api_prefix(target_module_name, namespace[4:])
     else:
         _init_api_prefix(target_module_name, namespace)

From 17fd2ad90f23b8c1e8c8a1150a035c1274d7822c Mon Sep 17 00:00:00 2001
From: Chris Nuernberger <cnuernber@gmail.com>
Date: Sun, 4 Mar 2018 20:45:06 -0700
Subject: [PATCH 184/949] Small refactor for clarity in arraycopyfromto (#960)

---
 src/runtime/c_runtime_api.cc | 27 +++++++++++++++------------
 1 file changed, 15 insertions(+), 12 deletions(-)

diff --git a/src/runtime/c_runtime_api.cc b/src/runtime/c_runtime_api.cc
index 7d65a04bc040..c7fd84362f47 100644
--- a/src/runtime/c_runtime_api.cc
+++ b/src/runtime/c_runtime_api.cc
@@ -413,19 +413,22 @@ int TVMArrayCopyFromTo(TVMArrayHandle from,
   size_t from_size = GetDataSize(from);
   size_t to_size = GetDataSize(to);
   CHECK_EQ(from_size, to_size)
-      << "TVMArrayCopyFromTo: The size must exactly match";
-  TVMContext ctx = from->ctx;
-  if (ctx.device_type == kDLCPU) {
-    ctx = to->ctx;
-  } else {
-    CHECK(to->ctx.device_type == kDLCPU ||
-          to->ctx.device_type == from->ctx.device_type)
-        << "Can not copy across different ctx types directly";
-  }
+    << "TVMArrayCopyFromTo: The size must exactly match";
+
+  CHECK(from->ctx.device_type == to->ctx.device_type
+        || from->ctx.device_type == kDLCPU
+        || to->ctx.device_type == kDLCPU)
+    << "Can not copy across different ctx types directly";
+
+  // Use the context that is *not* a cpu context to get the correct device
+  // api manager.
+  TVMContext ctx = from->ctx.device_type != kDLCPU ? from->ctx : to->ctx;
+
   DeviceAPIManager::Get(ctx)->CopyDataFromTo(
-      from->data, static_cast<size_t>(from->byte_offset),
-      to->data, static_cast<size_t>(to->byte_offset),
-      from_size, from->ctx, to->ctx, stream);
+    from->data, static_cast<size_t>(from->byte_offset),
+    to->data, static_cast<size_t>(to->byte_offset),
+    from_size, from->ctx, to->ctx, stream);
+
   API_END();
 }
 

From 5fc0e69df7856ff07fd378117b10628b50eff832 Mon Sep 17 00:00:00 2001
From: nhynes <nhynes@berkeley.edu>
Date: Mon, 5 Mar 2018 10:58:22 -0800
Subject: [PATCH 185/949] SGXify graph runtime (#937)

---
 dmlc-core                          |  2 +-
 src/runtime/graph/graph_runtime.cc | 20 ++++++++------------
 2 files changed, 9 insertions(+), 13 deletions(-)

diff --git a/dmlc-core b/dmlc-core
index 7e84e8b036a3..d3f7fbb53e5b 160000
--- a/dmlc-core
+++ b/dmlc-core
@@ -1 +1 @@
-Subproject commit 7e84e8b036a3ff5c0104a3da1f4c7eebf94396ec
+Subproject commit d3f7fbb53e5b037c0f5bf6bd21871ccc720690cc
diff --git a/src/runtime/graph/graph_runtime.cc b/src/runtime/graph/graph_runtime.cc
index 34d6b3af155a..89bb3a052b0f 100644
--- a/src/runtime/graph/graph_runtime.cc
+++ b/src/runtime/graph/graph_runtime.cc
@@ -64,7 +64,11 @@ class GraphRuntime : public ModuleNode {
   void Init(const std::string& graph_json,
             tvm::runtime::Module module,
             TVMContext ctx) {
+#ifndef _LIBCPP_SGX_NO_IOSTREAMS
     std::istringstream is(graph_json);
+#else
+    std::string is = graph_json;
+#endif
     dmlc::JSONReader reader(&is);
     this->Load(&reader);
     module_ = module;
@@ -198,27 +202,19 @@ class GraphRuntime : public ModuleNode {
       std::string key, value;
       reader->BeginObject();
       while (reader->NextObjectItem(&key)) {
+        reader->Read(&value);
         if (key == "func_name") {
-          reader->Read(&value);
           param->func_name = value;
           bitmask |= 1;
         } else if (key == "num_inputs") {
-          reader->Read(&value);
-          std::istringstream is(value);
-          is >> param->num_inputs;
+          param->num_inputs = strtoul(value.c_str(), nullptr, 10);
           bitmask |= 2;
         } else if (key == "num_outputs") {
-          reader->Read(&value);
-          std::istringstream is(value);
-          is >> param->num_outputs;
+          param->num_outputs = strtoul(value.c_str(), nullptr, 10);
           bitmask |= 4;
         } else if (key == "flatten_data") {
-          reader->Read(&value);
-          std::istringstream is(value);
-          is >> param->flatten_data;
+          param->flatten_data = strtoul(value.c_str(), nullptr, 10);
           bitmask |= 8;
-        } else {
-          reader->Read(&value);
         }
       }
       CHECK_EQ(bitmask, 1|2|4|8) << "invalid format";

From 9b62760f3f5a37035e460ba15a67dab035cde308 Mon Sep 17 00:00:00 2001
From: eqy <eqy@cs.washington.edu>
Date: Mon, 5 Mar 2018 13:00:22 -0800
Subject: [PATCH 186/949] prevent starting of RPC server w/o RPC support (#962)

* prevent starting of RPC server w/o RPC support

* fix indent
---
 python/tvm/contrib/rpc.py | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/python/tvm/contrib/rpc.py b/python/tvm/contrib/rpc.py
index 831481509c76..56f9fb1b4058 100644
--- a/python/tvm/contrib/rpc.py
+++ b/python/tvm/contrib/rpc.py
@@ -182,6 +182,11 @@ def __init__(self,
                  use_popen=False,
                  exclusive=False,
                  key=""):
+        try:
+            if _ServerLoop is None:
+                raise RuntimeError("Please compile with USE_RPC=1")
+        except NameError:
+            raise RuntimeError("Please compile with USE_RPC=1")
         self.host = host
         self.port = port
         self.libs = []

From 678b3b3f71eedb68bb611c2c540539612dc914eb Mon Sep 17 00:00:00 2001
From: nhynes <nhynes@berkeley.edu>
Date: Tue, 6 Mar 2018 08:57:18 -0800
Subject: [PATCH 187/949] Add SGX runtime (#963)

---
 apps/sgx/tvm_runtime_pack.cc |  8 +-------
 sgx/sgx_runtime.cc           | 25 +++++++++++++++++++++++++
 2 files changed, 26 insertions(+), 7 deletions(-)
 create mode 100644 sgx/sgx_runtime.cc

diff --git a/apps/sgx/tvm_runtime_pack.cc b/apps/sgx/tvm_runtime_pack.cc
index 52aa372f7789..709386b78931 100644
--- a/apps/sgx/tvm_runtime_pack.cc
+++ b/apps/sgx/tvm_runtime_pack.cc
@@ -5,13 +5,7 @@
  *   Please refer to the Makefile (rule lib/tvm_runtime_pack.o) for how to build.
  *
  */
-#include "../../src/runtime/c_runtime_api.cc"
-#include "../../src/runtime/cpu_device_api.cc"
-#include "../../src/runtime/workspace_pool.cc"
-#include "../../src/runtime/module_util.cc"
-#include "../../src/runtime/module.cc"
-#include "../../src/runtime/registry.cc"
-#include "../../src/runtime/system_lib_module.cc"
+#include "../../sgx/sgx_runtime.cc"
 #ifndef _LIBCPP_SGX_CONFIG
 #include "../../src/runtime/file_util.cc"
 #endif
diff --git a/sgx/sgx_runtime.cc b/sgx/sgx_runtime.cc
new file mode 100644
index 000000000000..0bc6a3189e7a
--- /dev/null
+++ b/sgx/sgx_runtime.cc
@@ -0,0 +1,25 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file sgx_runtime.cc
+ */
+#include "../../src/runtime/c_runtime_api.cc"
+#include "../../src/runtime/cpu_device_api.cc"
+#include "../../src/runtime/workspace_pool.cc"
+#include "../../src/runtime/module_util.cc"
+#include "../../src/runtime/module.cc"
+#include "../../src/runtime/registry.cc"
+#include "../../src/runtime/system_lib_module.cc"
+
+// dummy parallel runtime
+int TVMBackendParallelLaunch(
+    FTVMParallelLambda flambda,
+    void* cdata,
+    int num_task) {
+  TVMAPISetLastError("Parallel is not (yet) supported in SGX runtime");
+  return -1;
+}
+
+int TVMBackendParallelBarrier(int task_id, TVMParallelGroupEnv* penv) {
+  return 0;
+}
+

From 0c8c97ea8aa5b6cf308d2bb0f8b1cad751462eac Mon Sep 17 00:00:00 2001
From: Leyuan Wang <laurawly@gmail.com>
Date: Tue, 6 Mar 2018 17:51:13 -0800
Subject: [PATCH 188/949] MPS conv (#822)

---
 make/contrib/mps.mk                   |  10 +-
 python/tvm/contrib/mps.py             |  44 ++++++-
 python/tvm/contrib/rpc_proxy.py       |   2 +
 src/contrib/mps/conv.mm               | 154 +++++++++++++++++++++++++
 src/contrib/mps/gemm.mm               | 158 +++++++++++++-------------
 src/contrib/mps/mps_utils.cc          |  58 ----------
 src/contrib/mps/mps_utils.h           |  15 ++-
 src/contrib/mps/mps_utils.mm          |  80 +++++++++++++
 src/runtime/metal/metal_device_api.mm |  12 +-
 tests/python/contrib/test_mps.py      |  90 +++++++++++----
 10 files changed, 448 insertions(+), 175 deletions(-)
 create mode 100644 src/contrib/mps/conv.mm
 delete mode 100644 src/contrib/mps/mps_utils.cc
 create mode 100644 src/contrib/mps/mps_utils.mm

diff --git a/make/contrib/mps.mk b/make/contrib/mps.mk
index 501e62b2a671..0fe8a7f12889 100644
--- a/make/contrib/mps.mk
+++ b/make/contrib/mps.mk
@@ -1,4 +1,4 @@
-MPS_CONTRIB_SRC = $(wildcard src/contrib/mps/*.mm, src/contrib/mps/*.cc)
+MPS_CONTRIB_SRC = $(wildcard src/contrib/mps/*.mm)
 MPS_CONTRIB_OBJ = $(patsubst src/%.mm, build/%.o, $(MPS_CONTRIB_SRC))
 
 ifeq ($(USE_MPS), 1)
@@ -6,9 +6,15 @@ FRAMEWORKS += -framework MetalPerformanceShaders
 CFLAGS += 
 ADD_LDFLAGS += 
 RUNTIME_DEP += $(MPS_CONTRIB_OBJ)
+CONTRIB_OBJ += $(MPS_CONTRIB_OBJ)
 endif
 
-build/contrib/mps/%.o: src/contrib/mps/%.mm src/contrib/mps/%.cc
+build/contrib/mps/%.o: src/contrib/mps/%.mm
+	@mkdir -p $(@D)
+	$(CXX) $(OBJCFLAGS) $(CFLAGS) -MM -MT build/contrib/mps/$*.o $< >build/contrib/mps/$*.d
+	$(CXX) $(OBJCFLAGS) -c $(CFLAGS) -c $< -o $@
+
+build/contrib/mps/%.o: src/contrib/mps/%.cc
 	@mkdir -p $(@D)
 	$(CXX) $(OBJCFLAGS) $(CFLAGS) -MM -MT build/contrib/mps/$*.o $< >build/contrib/mps/$*.d
 	$(CXX) $(OBJCFLAGS) -c $(CFLAGS) -c $< -o $@
diff --git a/python/tvm/contrib/mps.py b/python/tvm/contrib/mps.py
index d214d4b93631..43b3b9fb48db 100644
--- a/python/tvm/contrib/mps.py
+++ b/python/tvm/contrib/mps.py
@@ -1,9 +1,9 @@
 """External function interface to MPS libraroes."""
 from __future__ import absolute_import as _abs
-
 from .. import api as _api
 from .. import intrin as _intrin
 
+# pylint: disable=C0103,W0612
 
 def matmul(lhs, rhs, transa=False, transb=False):
     """Create an extern op that compute matrix mult of A and rhs with CrhsLAS
@@ -26,10 +26,46 @@ def matmul(lhs, rhs, transa=False, transb=False):
     C : Tensor
         The result tensor.
     """
-    m = lhs.shape[0]
-    n = rhs.shape[1]
+    m = lhs.shape[0] if transa is False else lhs.shape[1]
+    n = rhs.shape[1] if transb is False else rhs.shape[0]
+    if transa:
+        m = b
+    if transb:
+        n = c
     return _api.extern(
-        (n, m), [lhs, rhs],
+        (m, n), [lhs, rhs],
         lambda ins, outs: _intrin.call_packed(
             "tvm.contrib.mps.matmul", ins[0], ins[1], outs[0], transa, transb),
         name="C")
+
+def conv2d(data, weight, pad='SAME', stride=1):
+    """
+    Create an extern op that compute data * weight and return result in output
+
+    Parameters:
+    ----------
+    data: Tensor
+        The input data, format NHWC
+    weight: Tensor
+        The conv weight, format output_feature * kH * kW * input_feature
+    pad: str
+        Padding method, 'SAME' or 'VALID'
+    stride: int
+        convolution stride
+
+    Returns
+    -------
+    output: Tensor
+        The result tensor
+    """
+    n, hi, wi, ci = data.shape
+    co, kh, kw, ciw = weight.shape
+    padding = 0 if pad == 'SAME' else 1
+    ho = hi // stride
+    wo = wi // stride
+
+    return _api.extern(
+        (n, ho, wo, co), [data, weight],
+        lambda ins, outs: _intrin.call_packed(
+            "tvm.contrib.mps.conv2d", ins[0], ins[1], outs[0], padding, stride),
+        name="C")
diff --git a/python/tvm/contrib/rpc_proxy.py b/python/tvm/contrib/rpc_proxy.py
index fe289935e7cb..9634c258b39f 100644
--- a/python/tvm/contrib/rpc_proxy.py
+++ b/python/tvm/contrib/rpc_proxy.py
@@ -70,6 +70,7 @@ def on_start(self):
         ProxyServerHandler.current.handler_ready(self)
 
     def on_data(self, message):
+        """on data"""
         assert isinstance(message, bytes)
         if self.forward_proxy:
             self.forward_proxy.send_data(message)
@@ -98,6 +99,7 @@ def close_pair(self):
         self.close()
 
     def on_close_event(self):
+        """on close event"""
         assert not self._done
         logging.info("RPCProxy:on_close %s ...", self.name())
         self._done = True
diff --git a/src/contrib/mps/conv.mm b/src/contrib/mps/conv.mm
new file mode 100644
index 000000000000..fa279bd5cc95
--- /dev/null
+++ b/src/contrib/mps/conv.mm
@@ -0,0 +1,154 @@
+#include "mps_utils.h"
+
+namespace tvm {
+namespace contrib {
+
+using namespace runtime;
+
+TVM_REGISTER_GLOBAL("tvm.contrib.mps.buffer2img")
+.set_body([](TVMArgs args, TVMRetValue *ret) {
+  DLTensor *buf = args[0];
+  DLTensor *img = args[1];
+  // copy to temp
+  id<MTLBuffer> mtlbuf = (__bridge id<MTLBuffer>)(buf->data);
+  MetalThreadEntry *entry_ptr = MetalThreadEntry::ThreadLocal();
+  runtime::metal::MetalThreadEntry *rt =
+      runtime::metal::MetalThreadEntry::ThreadLocal();
+  id<MTLDevice> dev = entry_ptr->metal_api->GetDevice(buf->ctx);
+  id<MTLBuffer> temp = rt->GetTempBuffer(buf->ctx, [mtlbuf length]);
+  entry_ptr->metal_api->CopyDataFromTo(
+      (__bridge void *)mtlbuf, 0, (__bridge void *)temp, 0, [mtlbuf length],
+      buf->ctx, buf->ctx, nullptr
+  );
+
+  MPSImageDescriptor *desc = [MPSImageDescriptor
+      imageDescriptorWithChannelFormat:MPSImageFeatureChannelFormatFloat32
+                                 width:buf->shape[2]
+                                height:buf->shape[1]
+                       featureChannels:buf->shape[3]];
+
+  MPSImage *mpsimg = entry_ptr->AllocMPSImage(dev, desc);
+
+  [mpsimg writeBytes:[temp contents]
+          dataLayout:MPSDataLayoutHeightxWidthxFeatureChannels
+          imageIndex:0];
+
+  img->data = (__bridge void *)mpsimg;
+
+  [mpsimg readBytes:[temp contents]
+         dataLayout:MPSDataLayoutHeightxWidthxFeatureChannels
+         imageIndex:0];
+  
+  });
+
+TVM_REGISTER_GLOBAL("tvm.contrib.mps.img2buffer")
+.set_body([](TVMArgs args, TVMRetValue *ret) {
+  DLTensor *img = args[0];
+  DLTensor *buf = args[1];
+  id<MTLBuffer> mtlbuf = (__bridge id<MTLBuffer>)(buf->data);
+  MPSImage *mpsimg = (__bridge MPSImage *)(img->data);
+  MetalThreadEntry *entry_ptr = MetalThreadEntry::ThreadLocal();
+  runtime::metal::MetalThreadEntry *rt =
+      runtime::metal::MetalThreadEntry::ThreadLocal();
+  id<MTLBuffer> temp = rt->GetTempBuffer(buf->ctx, [mtlbuf length]);
+
+  [mpsimg readBytes:[temp contents]
+         dataLayout:MPSDataLayoutHeightxWidthxFeatureChannels
+         imageIndex:0];
+
+  entry_ptr->metal_api->CopyDataFromTo(
+      (__bridge void *)temp, 0, (__bridge void *)mtlbuf, 0, [mtlbuf length],
+      buf->ctx, buf->ctx, nullptr);
+
+    });
+
+TVM_REGISTER_GLOBAL("tvm.contrib.mps.conv2d")
+.set_body([](TVMArgs args, TVMRetValue *ret) {
+  // MPS-NHWC
+  DLTensor *data = args[0];
+  DLTensor *weight = args[1];
+  DLTensor *output = args[2];
+  int pad = args[3];
+  int stride = args[4];
+
+  CHECK_EQ(data->ndim, 4);
+  CHECK_EQ(weight->ndim, 4);
+  CHECK_EQ(output->ndim, 4);
+  CHECK(output->strides == nullptr);
+  CHECK(weight->strides == nullptr);
+  CHECK(data->strides == nullptr);
+
+  CHECK_EQ(data->shape[0], 1);
+  CHECK_EQ(output->shape[0], 1);
+
+  int oCh = weight->shape[0];
+  int kH = weight->shape[1];
+  int kW = weight->shape[2];
+  int iCh = weight->shape[3];
+
+  auto f_buf2img = runtime::Registry::Get("tvm.contrib.mps.buffer2img");
+  auto f_img2buf = runtime::Registry::Get("tvm.contrib.mps.img2buffer");
+  // Get Metal device API
+  MetalThreadEntry *entry_ptr = MetalThreadEntry::ThreadLocal();
+  runtime::metal::MetalThreadEntry *rt =
+      runtime::metal::MetalThreadEntry::ThreadLocal();
+  id<MTLDevice> dev = entry_ptr->metal_api->GetDevice(data->ctx);
+  id<MTLCommandQueue> queue =
+      entry_ptr->metal_api->GetCommandQueue(data->ctx);
+  id<MTLCommandBuffer> cb = [queue commandBuffer];
+  // data to MPSImage
+  DLTensor tmp_in;
+  (*f_buf2img)(data, &tmp_in);
+  MPSImage *tempA = (__bridge MPSImage *)tmp_in.data;
+  // weight to temp memory
+  id<MTLBuffer> bufB = (__bridge id<MTLBuffer>)(weight->data);
+  id<MTLBuffer> tempB = rt->GetTempBuffer(weight->ctx, [bufB length]);
+  entry_ptr->metal_api->CopyDataFromTo(
+      (__bridge void *)bufB, 0, (__bridge void *)tempB, 0, [bufB length],
+      weight->ctx, weight->ctx, nullptr);
+  float *ptr_w = (float *)[tempB contents];
+  // output to MPSImage
+  DLTensor tmp_out;
+  (*f_buf2img)(output, &tmp_out);
+  MPSImage *tempC = (__bridge MPSImage *)tmp_out.data;
+  // conv desc
+
+  MPSCNNConvolutionDescriptor *conv_desc = [MPSCNNConvolutionDescriptor
+      cnnConvolutionDescriptorWithKernelWidth:kW
+                                 kernelHeight:kH
+                         inputFeatureChannels:iCh
+                        outputFeatureChannels:oCh];
+  [conv_desc setStrideInPixelsX:stride];
+  [conv_desc setStrideInPixelsY:stride];
+
+  MPSCNNConvolution *conv =
+      [[MPSCNNConvolution alloc] initWithDevice:dev
+                          convolutionDescriptor:conv_desc
+                                  kernelWeights:ptr_w
+                                      biasTerms:nil
+                                          flags:MPSCNNConvolutionFlagsNone];
+  if (pad == 0) {
+    conv.padding = [MPSNNDefaultPadding
+        paddingWithMethod:MPSNNPaddingMethodAddRemainderToTopLeft |
+                          MPSNNPaddingMethodAlignCentered |
+                          MPSNNPaddingMethodSizeSame];
+  } else if (pad == 1) {
+    conv.padding = [MPSNNDefaultPadding
+        paddingWithMethod:MPSNNPaddingMethodAddRemainderToTopLeft |
+                          MPSNNPaddingMethodAlignCentered |
+                          MPSNNPaddingMethodSizeValidOnly];
+  }
+  [conv encodeToCommandBuffer:cb sourceImage:tempA destinationImage:tempC];
+
+  [cb commit];
+  id<MTLBlitCommandEncoder> encoder = [cb blitCommandEncoder];
+  [encoder synchronizeResource:tempC.texture];
+  [encoder endEncoding];
+  [cb waitUntilCompleted];
+
+  (*f_img2buf)(&tmp_out, output);
+  
+  });
+
+} // namespace contrib
+} // namespace tvm
diff --git a/src/contrib/mps/gemm.mm b/src/contrib/mps/gemm.mm
index f877cb8b0ea1..1d92ad2851d0 100644
--- a/src/contrib/mps/gemm.mm
+++ b/src/contrib/mps/gemm.mm
@@ -1,9 +1,5 @@
-#include "../../runtime/metal/metal_common.h"
-#include <MetalPerformanceShaders/MetalPerformanceShaders.h>
-#include <dmlc/logging.h>
-#include <tvm/runtime/device_api.h>
-#include <tvm/runtime/registry.h>
-#include <tvm/runtime/util.h>
+
+#include "mps_utils.h"
 
 namespace tvm {
 namespace contrib {
@@ -11,83 +7,81 @@
 using namespace runtime;
 
 TVM_REGISTER_GLOBAL("tvm.contrib.mps.matmul")
-    .set_body([](TVMArgs args, TVMRetValue *ret) {
-      DLTensor *A = args[0];
-      DLTensor *B = args[1];
-      DLTensor *C = args[2];
-      bool transa = args[3];
-      bool transb = args[4];
-      // call gemm for simple compact code.
-      CHECK_EQ(A->ndim, 2);
-      CHECK_EQ(B->ndim, 2);
-      CHECK_EQ(C->ndim, 2);
-      CHECK(C->strides == nullptr);
-      CHECK(B->strides == nullptr);
-      CHECK(A->strides == nullptr);
-      CHECK(TypeMatch(A->dtype, kDLFloat, 32));
-      CHECK(TypeMatch(B->dtype, kDLFloat, 32));
-      CHECK(TypeMatch(C->dtype, kDLFloat, 32));
-      // Get Metal device API
-      MetalThreadEntry* entry_ptr = MetalThreadEntry::ThreadLocal();
-      CHECK_EQ(A->ctx, B->ctx);
-      CHECK_EQ(A->ctx, C->ctx);
-      id<MTLDevice> dev = entry_ptr->metal_api->GetDevice(A->ctx);
-      id<MTLCommandQueue> queue = entry_ptr->metal_api->GetCommandQueue(A->ctx);
-      id<MTLCommandBuffer> cb = [queue commandBuffer];
-      NSUInteger M = A->shape[0 + transa?1:0];
-      NSUInteger N = B->shape[1 - transb?1:0];
-      NSUInteger K = B->shape[0 + transb?1:0];
-      CHECK_EQ(A->shape[1-transa?1:0], K);
-      // mps a
-      MPSDataType dtype = MPSType::DLTypeToMPSType(A->dtype);
-      MPSMatrixDescriptor *descA = [MPSMatrixDescriptor
-          matrixDescriptorWithDimensions:M
-                                 columns:K
-                                rowBytes:M * sizeof(dtype)
-                                dataType:dtype];
-      id<MTLBuffer> bufA = (__bridge id<MTLBuffer>)(A->data);
-      MPSMatrix *matrixA =
-          [[MPSMatrix alloc] initWithBuffer:bufA descriptor:descA];
-      // mps b
-      MPSMatrixDescriptor *descB = [MPSMatrixDescriptor
-          matrixDescriptorWithDimensions:K
-                                 columns:N
-                                rowBytes:K * sizeof(dtype)
-                                dataType:dtype];
-      id<MTLBuffer> bufB = (__bridge id<MTLBuffer>)(B->data);
-      MPSMatrix *matrixB =
-          [[MPSMatrix alloc] initWithBuffer:bufB descriptor:descB];
-      // mps c
-      MPSMatrixDescriptor *descC = [MPSMatrixDescriptor
-          matrixDescriptorWithDimensions:M
-                                 columns:N
-                                rowBytes:M * sizeof(dtype)
-                                dataType:dtype];
-      id<MTLBuffer> bufC = (__bridge id<MTLBuffer>)(C->data);
-      MPSMatrix *matrixC =
-          [[MPSMatrix alloc] initWithBuffer:bufC descriptor:descC];
-      // kernel
+.set_body([](TVMArgs args, TVMRetValue *ret) {
+  DLTensor *A = args[0];
+  DLTensor *B = args[1];
+  DLTensor *C = args[2];
+  bool transa = args[3];
+  bool transb = args[4];
+  // call gemm for simple compact code.
+  CHECK_EQ(A->ndim, 2);
+  CHECK_EQ(B->ndim, 2);
+  CHECK_EQ(C->ndim, 2);
+  CHECK(C->strides == nullptr);
+  CHECK(B->strides == nullptr);
+  CHECK(A->strides == nullptr);
+  CHECK(TypeMatch(A->dtype, kDLFloat, 32));
+  CHECK(TypeMatch(B->dtype, kDLFloat, 32));
+  CHECK(TypeMatch(C->dtype, kDLFloat, 32));
+  // Get Metal device API
+  MetalThreadEntry *entry_ptr = MetalThreadEntry::ThreadLocal();
+  // CHECK_EQ(A->ctx, B->ctx);
+  // CHECK_EQ(A->ctx, C->ctx);
+  id<MTLDevice> dev = entry_ptr->metal_api->GetDevice(A->ctx);
+  id<MTLCommandQueue> queue = entry_ptr->metal_api->GetCommandQueue(A->ctx);
+  id<MTLCommandBuffer> cb = [queue commandBuffer];
+  NSUInteger M = A->shape[0 + (transa ? 1 : 0)];
+  NSUInteger N = B->shape[1 - (transb ? 1 : 0)];
+  NSUInteger K = B->shape[0 + (transb ? 1 : 0)];
+
+  CHECK_EQ(A->shape[1 - (transa ? 1 : 0)], K);
+  // mps a
+  MPSDataType dtype = MPSType::DLTypeToMPSType(A->dtype);
+  MPSMatrixDescriptor *descA = [MPSMatrixDescriptor
+      matrixDescriptorWithDimensions:M
+                             columns:K
+                            rowBytes:K * sizeof(MPSDataTypeFloat32)
+                            dataType:MPSDataTypeFloat32];
+  id<MTLBuffer> bufA = (__bridge id<MTLBuffer>)(A->data);
+  MPSMatrix *matrixA =
+      [[MPSMatrix alloc] initWithBuffer:bufA descriptor:descA];
+  // mps b
+  MPSMatrixDescriptor *descB =
+      [MPSMatrixDescriptor matrixDescriptorWithDimensions:K
+                                                  columns:N
+                                                  rowBytes:N * sizeof(dtype)
+                                                  dataType:dtype];
+  id<MTLBuffer> bufB = (__bridge id<MTLBuffer>)(B->data);
+  MPSMatrix *matrixB =
+      [[MPSMatrix alloc] initWithBuffer:bufB descriptor:descB];
+  // mps c
+  MPSMatrixDescriptor *descC =
+      [MPSMatrixDescriptor matrixDescriptorWithDimensions:M
+                                                  columns:N
+                                                 rowBytes:N * sizeof(dtype)
+                                                 dataType:dtype];
+  id<MTLBuffer> bufC = (__bridge id<MTLBuffer>)(C->data);
+  MPSMatrix *matrixC =
+      [[MPSMatrix alloc] initWithBuffer:bufC descriptor:descC];
+  // kernel
 
-      MPSMatrixMultiplication *mul_obj = [[MPSMatrixMultiplication alloc] init];
-      MPSMatrixMultiplication *sgemm = [mul_obj initWithDevice:dev
-                                                 transposeLeft:transa
-                                                transposeRight:transb
-                                                    resultRows:M
-                                                 resultColumns:N
-                                               interiorColumns:K
-                                                         alpha:1.0f
-                                                          beta:0.0f];
-      CHECK(sgemm != nil);
-      [sgemm encodeToCommandBuffer:cb
-                        leftMatrix:matrixA
-                       rightMatrix:matrixB
-                      resultMatrix:matrixC];
-      [cb commit];
-      [mul_obj dealloc];
-      [matrixA dealloc];
-      [matrixB dealloc];
-      [matrixC dealloc];
-    });
+  MPSMatrixMultiplication *mul_obj = [[MPSMatrixMultiplication alloc] init];
+  MPSMatrixMultiplication *sgemm = [mul_obj initWithDevice:dev
+                                             transposeLeft:transa
+                                            transposeRight:transb
+                                                resultRows:M
+                                             resultColumns:N
+                                           interiorColumns:K
+                                                     alpha:1.0f
+                                                      beta:0.0f];
+  CHECK(sgemm != nil);
+  [sgemm encodeToCommandBuffer:cb
+                    leftMatrix:matrixA
+                   rightMatrix:matrixB
+                  resultMatrix:matrixC];
+  [cb commit];
+  
+  });
 
 } // namespace contrib
 } // namespace tvm
diff --git a/src/contrib/mps/mps_utils.cc b/src/contrib/mps/mps_utils.cc
deleted file mode 100644
index 2e3ca6218bb4..000000000000
--- a/src/contrib/mps/mps_utils.cc
+++ /dev/null
@@ -1,58 +0,0 @@
-/*!
- *  Copyright (c) 2017 by Contributors
- * \file Use external mps utils function
- */
-#include "mps_utils.h"
-#include <MetalPerformanceShaders/MetalPerformanceShaders.h>
-#include <dmlc/thread_local.h>
-#include <tvm/runtime/registry.h>
-
-
-namespace tvm {
-namespace contrib {
-
-// MPS Data Type
-MPSDataType MPSType::DLTypeToMPSType(const DLDataType &dtype) {
-  switch (dtype.code) {
-      case kDLInt:
-        if (dtype.bits == 8 && dtype.lanes == 1) return MPSDataTypeInt8;
-        else if (dtype.bits == 16 && dtype.lanes == 1) return MPSDataTypeInt16;
-        else
-          LOG(FATAL) << "Unsupported type";
-        break;
-      case kDLUInt:
-        if (dtype.bits == 8 && dtype.lanes == 1) return MPSDataTypeUInt8;
-        else if (dtype.bits == 16 && dtype.lanes == 1) return MPSDataTypeUInt16;
-        else if (dtype.bits == 32 && dtype.lanes == 1) return MPSDataTypeUInt32;
-        LOG(FATAL) << "Unsupported type";
-        break;
-      case kDLFloat:
-        if (dtype.bits == 16 && dtype.lanes == 1) return MPSDataTypeFloat16;
-        else if (dtype.bits == 32 && dtype.lanes == 1) return MPSDataTypeFloat32;
-        else
-          LOG(FATAL) << "Unsupported type";
-        break;
-      default:
-        LOG(FATAL) << "Unsupported type";
-    }
-}
-
-// MetalThreadEntry
-
-MetalThreadEntry::MetalThreadEntry() {
-  auto func = runtime::Registry::Get("device_api.metal");
-  void *ret = (*func)();
-  metal_api = static_cast<runtime::metal::MetalWorkspace *>(ret);
-}
-
-MetalThreadEntry::~MetalThreadEntry() {
-}
-
-typedef dmlc::ThreadLocalStore<MetalThreadEntry> MetalThreadStore;
-
-MetalThreadEntry* MetalThreadEntry::ThreadLocal() {
-  return MetalThreadStore::Get();
-}
-
-}  // namespace contrib
-}  // namespace tvm
diff --git a/src/contrib/mps/mps_utils.h b/src/contrib/mps/mps_utils.h
index 91336ce44edd..f07156a252a3 100644
--- a/src/contrib/mps/mps_utils.h
+++ b/src/contrib/mps/mps_utils.h
@@ -6,11 +6,15 @@
 #ifndef TVM_CONTRIB_MPS_MPS_UTILS_H_
 #define TVM_CONTRIB_MPS_MPS_UTILS_H_
 
+#import <MetalPerformanceShaders/MetalPerformanceShaders.h>
 #include <dmlc/logging.h>
+#include <dmlc/thread_local.h>
 #include <tvm/runtime/device_api.h>
+#include <tvm/runtime/registry.h>
+#include <tvm/runtime/util.h>
+#include <vector>
 #include "../../runtime/metal/metal_common.h"
 
-
 namespace tvm {
 namespace contrib {
 
@@ -19,12 +23,15 @@ struct MPSType {
   static MPSDataType DLTypeToMPSType(const DLDataType &dtype);
 };  // struct MPSType
 
-
 struct MetalThreadEntry {
   MetalThreadEntry();
   ~MetalThreadEntry();
-  runtime::MetalWorkspace *metal_api{nullptr};
-  static MetalThreadEntry* ThreadLocal();
+  MPSImage *AllocMPSImage(id<MTLDevice> dev, MPSImageDescriptor *desc);
+  MPSTemporaryImage *AllocTempImage(id<MTLCommandBuffer> cb,
+                                    MPSImageDescriptor *desc);
+  runtime::metal::MetalWorkspace *metal_api{nullptr};
+  static MetalThreadEntry *ThreadLocal();
+  std::vector<MPSImage *> img_table;
 };  // MetalThreadEntry
 
 }  // namespace contrib
diff --git a/src/contrib/mps/mps_utils.mm b/src/contrib/mps/mps_utils.mm
new file mode 100644
index 000000000000..bed8278a1d50
--- /dev/null
+++ b/src/contrib/mps/mps_utils.mm
@@ -0,0 +1,80 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file Use external mps utils function
+ */
+#include "mps_utils.h"
+
+namespace tvm {
+namespace contrib {
+
+// MPS Data Type
+MPSDataType MPSType::DLTypeToMPSType(const DLDataType &dtype) {
+  switch (dtype.code) {
+  case kDLInt:
+    if (dtype.bits == 8 && dtype.lanes == 1)
+      return MPSDataTypeInt8;
+    else if (dtype.bits == 16 && dtype.lanes == 1)
+      return MPSDataTypeInt16;
+    else
+      LOG(FATAL) << "Unsupported type";
+    break;
+  case kDLUInt:
+    if (dtype.bits == 8 && dtype.lanes == 1)
+      return MPSDataTypeUInt8;
+    else if (dtype.bits == 16 && dtype.lanes == 1)
+      return MPSDataTypeUInt16;
+    else if (dtype.bits == 32 && dtype.lanes == 1)
+      return MPSDataTypeUInt32;
+    LOG(FATAL) << "Unsupported type";
+    break;
+  case kDLFloat:
+    if (dtype.bits == 16 && dtype.lanes == 1)
+      return MPSDataTypeFloat16;
+    else if (dtype.bits == 32 && dtype.lanes == 1)
+      return MPSDataTypeFloat32;
+    else
+      LOG(FATAL) << "Unsupported type";
+    break;
+  default:
+    LOG(FATAL) << "Unsupported type";
+  }
+  return MPSDataTypeFloat32;
+}
+
+// MetalThreadEntry
+
+MPSImage *MetalThreadEntry::AllocMPSImage(id<MTLDevice> dev,
+                                          MPSImageDescriptor *desc) {
+  MPSImage *mpsimg = [[MPSImage alloc] initWithDevice:dev imageDescriptor:desc];
+  img_table.push_back(mpsimg);
+  return mpsimg;
+}
+
+MPSTemporaryImage *MetalThreadEntry::AllocTempImage(id<MTLCommandBuffer> cb,
+                                                    MPSImageDescriptor *desc) {
+  MPSTemporaryImage *mpsimg =
+      [MPSTemporaryImage temporaryImageWithCommandBuffer:cb
+                                         imageDescriptor:desc];
+  return mpsimg;
+}
+
+MetalThreadEntry::MetalThreadEntry() {
+  auto func = runtime::Registry::Get("device_api.metal");
+  void *ret = (*func)();
+  metal_api = static_cast<runtime::metal::MetalWorkspace *>(ret);
+}
+
+MetalThreadEntry::~MetalThreadEntry() {
+  for (int i = 0; i < img_table.size(); ++i) {
+    [img_table[i] dealloc];
+  }
+}
+
+typedef dmlc::ThreadLocalStore<MetalThreadEntry> MetalThreadStore;
+
+MetalThreadEntry *MetalThreadEntry::ThreadLocal() {
+  return MetalThreadStore::Get();
+}
+
+} // namespace contrib
+} // namespace tvm
diff --git a/src/runtime/metal/metal_device_api.mm b/src/runtime/metal/metal_device_api.mm
index d87a9eac4f72..1768d6334b5c 100644
--- a/src/runtime/metal/metal_device_api.mm
+++ b/src/runtime/metal/metal_device_api.mm
@@ -126,10 +126,18 @@ int GetWarpSize(id<MTLDevice> dev) {
     TVMContext ctx, size_t nbytes, size_t alignment, TVMType type_hint) {
   this->Init();
   id<MTLDevice> dev = GetDevice(ctx);
-  // allocate buffer in GPU only mode.
+  // GPU memory only
+  MTLResourceOptions storage_mode = MTLResourceStorageModePrivate;
+  /*
+  #if TARGET_OS_IPHONE
+  storage_mode = MTLResourceStorageModeShared;
+  #else
+  storage_mode = MTLResourceStorageModeManaged;
+  #endif
+  */
   id<MTLBuffer> buf = [
       dev newBufferWithLength:nbytes
-          options:MTLResourceStorageModePrivate];
+          options:storage_mode];
   CHECK(buf != nil);
   return (__bridge void*)([buf retain]);
 }
diff --git a/tests/python/contrib/test_mps.py b/tests/python/contrib/test_mps.py
index 68dcb135e908..25437605525b 100644
--- a/tests/python/contrib/test_mps.py
+++ b/tests/python/contrib/test_mps.py
@@ -2,39 +2,83 @@
 import numpy as np
 from tvm.contrib import mps
 
-def test_matmul_add():
+def test_matmul():
+    if not tvm.module.enabled("metal"):
+        print("skip because %s is not enabled..." % "metal")
+        return
     n = 1024
     l = 128
-    m = 235
-    bias = tvm.var('bias', dtype=tvm.float32)
+    m = 256
     A = tvm.placeholder((n, l), name='A')
     B = tvm.placeholder((l, m), name='B')
-    C1 = mps.matmul(A, B)
-    C2 = mps.matmul(B, A, True, True)
-    D1 = tvm.compute(C1.shape, lambda i, j: C1[i,j] + bias, name="D1")
-    D2 = tvm.compute(C2.shape, lambda i, j: C2[i,j] + bias, name="D2")
-    s1 = tvm.create_schedule(D1.op)
-    s2 = tvm.create_schedule(D2.op)
-
-    def verify(A, B, D, s, bias, target="llvm"):
-        if not tvm.module.enabled(target):
-            print("skip because %s is not enabled..." % target)
-            return
+    C = mps.matmul(A, B)
+    D = tvm.compute(
+        C.shape,
+        lambda *i: C(*i) + 1.
+    )
+    s = tvm.create_schedule(D.op)
+    yo, xo = D.op.axis
+    block_y = tvm.thread_axis("blockIdx.y")
+    block_x = tvm.thread_axis("blockIdx.x")
+    thread_y = tvm.thread_axis("threadIdx.y")
+    thread_x = tvm.thread_axis("threadIdx.x")
+    by, ty = s[D].split(yo, factor=16)
+    bx, tx = s[D].split(xo, factor=16)
+    s[D].bind(by, block_y)
+    s[D].bind(bx, block_x)
+    s[D].bind(ty, thread_y)
+    s[D].bind(tx, thread_x)
+
+
+
+    def verify(A, B, D, s, target="metal"):
         if not tvm.get_global_func("tvm.contrib.mps.matmul", True):
             print("skip because extern function is not avalable")
             return
-        ctx = tvm.cpu(0)
-        f = tvm.build(s, [A, B, D, bias], target)
+        ctx = tvm.metal(0)
+        f = tvm.build(s, [A, B, D], "metal")
         a = tvm.nd.array(np.random.uniform(size=(n, l)).astype(A.dtype), ctx)
         b = tvm.nd.array(np.random.uniform(size=(l, m)).astype(B.dtype), ctx)
-        d = tvm.nd.array(np.zeros((n, m), dtype=D.dtype), ctx)
-        bb = 10.0
-        f(a, b, d, bb)
+        c = tvm.nd.array(np.zeros((n, m), dtype=C.dtype), ctx)
+        f(a, b, c)
         np.testing.assert_allclose(
-            d.asnumpy(), np.dot(a.asnumpy(), b.asnumpy()) + bb, rtol=1e-5)
-    verify(A, B, D1, s1, bias)
-    verify(A, B, D2, s2, bias)
+            c.asnumpy(), np.dot(a.asnumpy(), b.asnumpy()) + 1, rtol=1e-5)
+    verify(A, B, D, s)
+
+def test_conv2d():
+    if not tvm.module.enabled("metal"):
+        print("skip because %s is not enabled..." % "metal")
+        return
+    n = 1
+    h = 14
+    w = 14
+    ci = 2
+    co = 4
+    kh = 3
+    kw = 3
+    stride = 2
+    A = tvm.placeholder((n, h, w, ci), name="x")
+    B = tvm.placeholder((co, kh, kw, ci), name="w")
+    C = mps.conv2d(A, B, 'SAME', 2)
+    s1 = tvm.create_schedule(C.op)
+
+    def verify(A, B, C, target="llvm"):
+        if not tvm.get_global_func("tvm.contrib.mps.conv2d", True):
+            print("skip because extern function is not avalable")
+            return
+        ctx = tvm.metal(0)
+        f = tvm.build(s1, [A, B, C], "metal")
+        a = tvm.nd.array(np.random.uniform(size=(n, h, w, ci)).astype(A.dtype), ctx)
+        b = tvm.nd.array(np.random.uniform(size=(co, kh, kw, ci)).astype(B.dtype), ctx)
+        c = tvm.nd.array(np.zeros((n, h // stride, w // stride, co), dtype=C.dtype), ctx)
+        f(a, b, c)
+        # print(c.asnumpy())
+        # print(c.shape)
+        
+    verify(A, B, C, s1)
 
 
 if __name__ == "__main__":
-    test_matmul_add()
+    #test_matmul()
+    test_conv2d()
+

From ac771c19caeffe8e7f69cceb5300a0fc3c3ff304 Mon Sep 17 00:00:00 2001
From: libing4752 <libing475211023@sjtu.edu.cn>
Date: Thu, 8 Mar 2018 12:04:21 +0800
Subject: [PATCH 189/949] enhance access_ptr that args can support Expr (#970)

---
 include/tvm/buffer.h                      |  2 +-
 python/tvm/schedule.py                    | 29 +++++++++++++++++++++--
 src/lang/buffer.cc                        |  2 +-
 tests/python/unittest/test_lang_buffer.py |  9 +++++++
 4 files changed, 38 insertions(+), 4 deletions(-)

diff --git a/include/tvm/buffer.h b/include/tvm/buffer.h
index d737341e1c0e..8b04bf550cb2 100644
--- a/include/tvm/buffer.h
+++ b/include/tvm/buffer.h
@@ -55,7 +55,7 @@ class Buffer : public NodeRef {
    * \param offset The offset of ptr.
    */
   TVM_DLL Expr access_ptr(int access_mask, Type ptr_type = Handle(),
-                          int content_lanes = 1, int offset = 0) const;
+                          int content_lanes = 1, Expr offset = make_const(Int(32), 0)) const;
   /*!
    * \brief Create an Expr that does a vector load at begin index.
    * \param begin The beginning index
diff --git a/python/tvm/schedule.py b/python/tvm/schedule.py
index b04945292adf..236570c2417c 100644
--- a/python/tvm/schedule.py
+++ b/python/tvm/schedule.py
@@ -2,12 +2,34 @@
 from __future__ import absolute_import as _abs
 from ._ffi.base import string_types
 from ._ffi.node import NodeBase, register_node
-from ._ffi.function import _init_api
+from ._ffi.node import convert_to_node as _convert_to_node
+from ._ffi.function import _init_api, Function
+from ._ffi.function import convert_to_tvm_func as _convert_tvm_func
 from . import _api_internal
 from . import tensor as _tensor
 from . import expr as _expr
 from . import container as _container
 
+def convert(value):
+    """Convert value to TVM node or function.
+
+    Parameters
+    ----------
+    value : python value
+
+    Returns
+    -------
+    tvm_val : Node or Function
+        Converted value in TVM
+    """
+    if isinstance(value, (Function, NodeBase)):
+        return value
+
+    if callable(value):
+        return _convert_tvm_func(value)
+
+    return _convert_to_node(value)
+
 @register_node
 class Buffer(NodeBase):
     """Symbolic data buffer in TVM.
@@ -45,7 +67,7 @@ def access_ptr(self, access_mask, ptr_type="handle", content_lanes=1, offset=0):
             The number of lanes for the data type. This value
             is greater than one for vector types.
 
-        offset: int, optional
+        offset: Expr, optional
             The offset of pointer. We can use it to offset by
             the number of elements from the address of ptr.
 
@@ -60,6 +82,8 @@ def access_ptr(self, access_mask, ptr_type="handle", content_lanes=1, offset=0):
           buffer.access_ptr(Buffer.READ | Buffer.WRITE)
           # Get access ptr for read/write with str flag
           buffer.access_ptr("rw")
+          # Get access ptr for read with offset
+          buffer.access_ptr("r", offset = 100)
         """
         if isinstance(access_mask, string_types):
             mask = 0
@@ -71,6 +95,7 @@ def access_ptr(self, access_mask, ptr_type="handle", content_lanes=1, offset=0):
                 else:
                     raise ValueError("Unknown access_mask %s" % access_mask)
             access_mask = mask
+        offset = convert(offset)
         return _api_internal._BufferAccessPtr(self, access_mask, ptr_type,
                                               content_lanes, offset)
 
diff --git a/src/lang/buffer.cc b/src/lang/buffer.cc
index 07e455e25384..39566df45ae6 100644
--- a/src/lang/buffer.cc
+++ b/src/lang/buffer.cc
@@ -335,7 +335,7 @@ Buffer Buffer::MakeSlice(Array<Expr> begins, Array<Expr> extents) const {
                           0);
 }
 
-Expr Buffer::access_ptr(int access_mask, Type ptr_type, int content_lanes, int offset) const {
+Expr Buffer::access_ptr(int access_mask, Type ptr_type, int content_lanes, Expr offset) const {
   const BufferNode* self = operator->();
   Expr e_dtype;
   Expr extent;
diff --git a/tests/python/unittest/test_lang_buffer.py b/tests/python/unittest/test_lang_buffer.py
index fe0f1f0b759c..a5a8f5d065a6 100644
--- a/tests/python/unittest/test_lang_buffer.py
+++ b/tests/python/unittest/test_lang_buffer.py
@@ -31,6 +31,15 @@ def test_buffer_access_ptr_offset():
     offset = tvm.ir_pass.Simplify(aptr.args[2])
     assert tvm.ir_pass.Equal(offset, 100)
     assert aptr.args[4].value == Buffer.READ | Buffer.WRITE
+    v = tvm.var('int32')
+    aptr = Ab.access_ptr("rw", offset=100 + 100 + v)
+    offset = tvm.ir_pass.Simplify(aptr.args[2])
+    assert tvm.ir_pass.Equal(offset, 200 + v)
+    assert aptr.args[4].value == Buffer.READ | Buffer.WRITE
+    aptr = Ab.access_ptr("rw", offset=tvm.call_extern('int32', "test_call", 100 + 100 + v))
+    offset = tvm.ir_pass.Simplify(aptr.args[2])
+    assert tvm.ir_pass.Equal(offset, tvm.call_extern('int32', "test_call", 200 + v))
+    assert aptr.args[4].value == Buffer.READ | Buffer.WRITE
 
 def test_buffer_index_merge_mult_mod():
     m = tvm.var('m')

From e1fd9206e0048ff35b0e0a7121cb11be265a58b5 Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Thu, 8 Mar 2018 23:27:21 +0530
Subject: [PATCH 190/949] Yolo2 operators (#911)

---
 topi/python/topi/__init__.py                |  1 +
 topi/python/topi/cuda/__init__.py           |  1 +
 topi/python/topi/cuda/vision.py             | 65 +++++++++++++++++++
 topi/python/topi/generic/__init__.py        |  1 +
 topi/python/topi/generic/vision.py          | 67 ++++++++++++++++++++
 topi/python/topi/rocm/__init__.py           |  1 +
 topi/python/topi/rocm/vision.py             | 22 +++++++
 topi/python/topi/testing/__init__.py        |  3 +
 topi/python/topi/testing/region_python.py   | 69 +++++++++++++++++++++
 topi/python/topi/testing/reorg_python.py    | 42 +++++++++++++
 topi/python/topi/testing/shortcut_python.py | 47 ++++++++++++++
 topi/python/topi/vision/__init__.py         |  7 +++
 topi/python/topi/vision/reorg.py            | 39 ++++++++++++
 topi/python/topi/vision/shortcut.py         | 45 ++++++++++++++
 topi/python/topi/vision/yolo2/__init__.py   |  5 ++
 topi/python/topi/vision/yolo2/region.py     | 63 +++++++++++++++++++
 topi/tests/python/test_topi_region.py       | 44 +++++++++++++
 topi/tests/python/test_topi_reorg.py        | 47 ++++++++++++++
 topi/tests/python/test_topi_shortcut.py     | 48 ++++++++++++++
 19 files changed, 617 insertions(+)
 create mode 100644 topi/python/topi/cuda/vision.py
 create mode 100644 topi/python/topi/generic/vision.py
 create mode 100644 topi/python/topi/rocm/vision.py
 create mode 100644 topi/python/topi/testing/region_python.py
 create mode 100644 topi/python/topi/testing/reorg_python.py
 create mode 100644 topi/python/topi/testing/shortcut_python.py
 create mode 100644 topi/python/topi/vision/__init__.py
 create mode 100644 topi/python/topi/vision/reorg.py
 create mode 100644 topi/python/topi/vision/shortcut.py
 create mode 100644 topi/python/topi/vision/yolo2/__init__.py
 create mode 100644 topi/python/topi/vision/yolo2/region.py
 create mode 100644 topi/tests/python/test_topi_region.py
 create mode 100644 topi/tests/python/test_topi_reorg.py
 create mode 100644 topi/tests/python/test_topi_shortcut.py

diff --git a/topi/python/topi/__init__.py b/topi/python/topi/__init__.py
index ae02211a9d6c..c9a9b7bc01b6 100644
--- a/topi/python/topi/__init__.py
+++ b/topi/python/topi/__init__.py
@@ -25,6 +25,7 @@
 from . import util
 from . import rocm
 from . import cpp
+from . import vision
 # not import testing by default
 # because testing can have extra deps that are not necessary
 # we can import them from test cases explicitly
diff --git a/topi/python/topi/cuda/__init__.py b/topi/python/topi/cuda/__init__.py
index f829a9895fd2..174a37b1d451 100644
--- a/topi/python/topi/cuda/__init__.py
+++ b/topi/python/topi/cuda/__init__.py
@@ -15,3 +15,4 @@
 from .pooling import schedule_pool, schedule_global_pool
 from .conv2d_transpose_nchw import schedule_conv2d_transpose_nchw
 from .extern import schedule_extern
+from .vision import schedule_region
diff --git a/topi/python/topi/cuda/vision.py b/topi/python/topi/cuda/vision.py
new file mode 100644
index 000000000000..bfd453f807c2
--- /dev/null
+++ b/topi/python/topi/cuda/vision.py
@@ -0,0 +1,65 @@
+# pylint: disable=invalid-name, unused-variable
+"""Schedule for vision operators"""
+from __future__ import absolute_import as _abs
+import tvm
+from .. import tag
+from .. import generic
+
+@generic.schedule_region.register(["cuda", "gpu"])
+def schedule_region(outs):
+    """Schedule for region operator.
+    Parameters
+    ----------
+    outs: Array of Tensor
+        The computation graph description of region
+        in the format of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+        The computation schedule for region.
+    """
+
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    s = tvm.create_schedule([x.op for x in outs])
+    output = outs[0].op.output(0)
+    #thread = 64 for higher size tensors, give resource_unavailable error for higher values
+    num_thread = 64
+    def _schedule_softmax(softmax_op):
+        softmax = softmax_op.input_tensors[0]
+        max_elem = softmax_op.input_tensors[1]
+        expsum = softmax_op.input_tensors[2]
+        block_x = tvm.thread_axis("blockIdx.x")
+        thread_x = tvm.thread_axis((0, num_thread), "threadIdx.x")
+        s[max_elem].bind(max_elem.op.axis[0], block_x)
+        k = expsum.op.reduce_axis[0]
+        ko, ki = s[expsum].split(k, factor=num_thread)
+        ef = s.rfactor(expsum, ki)
+        s[expsum].bind(s[expsum].op.axis[0], block_x)
+        s[expsum].bind(s[expsum].op.reduce_axis[0], thread_x)
+        s[ef].compute_at(s[expsum], s[expsum].op.reduce_axis[0])
+        s[expsum].set_store_predicate(thread_x.var.equal(0))
+        tx, xi = s[softmax_op].split(softmax_op.axis[1], nparts=num_thread)
+        s[softmax_op].bind(softmax_op.axis[0], block_x)
+        s[softmax_op].bind(tx, thread_x)
+        return max_elem.op.input_tensors[0]
+
+    def _traverse(op):
+        if tag.is_injective(op.tag):
+            if op not in s.outputs:
+                s[op].compute_inline()
+            for tensor in op.input_tensors:
+                if tensor.op.input_tensors:
+                    _traverse(tensor.op)
+        elif op.tag == 'softmax_output':
+            tensor = _schedule_softmax(op)
+            if tensor.op.input_tensors:
+                _traverse(tensor.op)
+        else:
+            raise RuntimeError("Unsupported operator: %s" % op.tag)
+    _traverse(outs[0].op)
+    k = output.op.axis[0]
+    bx, tx = s[output].split(k, factor=num_thread)
+    s[output].bind(bx, tvm.thread_axis("blockIdx.x"))
+    s[output].bind(tx, tvm.thread_axis("threadIdx.x"))
+    return s
diff --git a/topi/python/topi/generic/__init__.py b/topi/python/topi/generic/__init__.py
index 8fc9143c3f86..8450e2d4c4e2 100644
--- a/topi/python/topi/generic/__init__.py
+++ b/topi/python/topi/generic/__init__.py
@@ -18,3 +18,4 @@
 from .nn import *
 from .injective import *
 from .extern import *
+from .vision import *
diff --git a/topi/python/topi/generic/vision.py b/topi/python/topi/generic/vision.py
new file mode 100644
index 000000000000..139491b6213e
--- /dev/null
+++ b/topi/python/topi/generic/vision.py
@@ -0,0 +1,67 @@
+"""Generic vision operators"""
+from __future__ import absolute_import as _abs
+import tvm
+
+def _default_schedule(outs, auto_inline):
+    """Default schedule for llvm."""
+    target = tvm.target.current_target(allow_none=False)
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    if target.target_name != "llvm":
+        raise RuntimeError("schedule not registered for '%s'" % target)
+    s = tvm.create_schedule([x.op for x in outs])
+    if auto_inline:
+        x = outs[0]
+        tvm.schedule.AutoInlineInjective(s)
+        s[x].fuse(s[x].op.axis)
+    return s
+
+@tvm.target.generic_func
+def schedule_shortcut(outs):
+    """Schedule for shortcut
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+      The computation graph description of shortcut
+      in the format of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+      The computation schedule for the op.
+    """
+    return _default_schedule(outs, False)
+
+@tvm.target.generic_func
+def schedule_reorg(outs):
+    """Schedule for reorg
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+      The computation graph description of reorg
+      in the format of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+      The computation schedule for the op.
+    """
+    return _default_schedule(outs, False)
+
+@tvm.target.generic_func
+def schedule_region(outs):
+    """Schedule for region
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+      The computation graph description of region
+      in the format of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+      The computation schedule for the op.
+    """
+    return _default_schedule(outs, False)
diff --git a/topi/python/topi/rocm/__init__.py b/topi/python/topi/rocm/__init__.py
index 3fddd53a3b36..a5b4ee30dc37 100644
--- a/topi/python/topi/rocm/__init__.py
+++ b/topi/python/topi/rocm/__init__.py
@@ -4,3 +4,4 @@
 
 from .conv2d import *
 from .dense import *
+from .vision import *
diff --git a/topi/python/topi/rocm/vision.py b/topi/python/topi/rocm/vision.py
new file mode 100644
index 000000000000..465bfd1e9cce
--- /dev/null
+++ b/topi/python/topi/rocm/vision.py
@@ -0,0 +1,22 @@
+# pylint: disable=invalid-name, unused-variable
+"""Schedule for vision operator"""
+from __future__ import absolute_import as _abs
+import topi
+from .. import generic
+
+@generic.schedule_region.register(["rocm"])
+def schedule_region(outs):
+    """Schedule for region operator.
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+        The computation graph description of region
+        in the format of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+        The computation schedule for region.
+    """
+    return topi.cuda.schedule_region(outs)
diff --git a/topi/python/topi/testing/__init__.py b/topi/python/topi/testing/__init__.py
index 2a20a1c4f622..66a865724dc3 100644
--- a/topi/python/topi/testing/__init__.py
+++ b/topi/python/topi/testing/__init__.py
@@ -12,3 +12,6 @@
 from .dilate_python import dilate_python
 from .softmax_python import softmax_python, log_softmax_python
 from .upsampling_python import upsampling_python
+from .reorg_python import reorg_python
+from .region_python import region_python
+from .shortcut_python import shortcut_python
diff --git a/topi/python/topi/testing/region_python.py b/topi/python/topi/testing/region_python.py
new file mode 100644
index 000000000000..3bab53892607
--- /dev/null
+++ b/topi/python/topi/testing/region_python.py
@@ -0,0 +1,69 @@
+# pylint: disable=invalid-name, line-too-long, unused-variable, too-many-locals
+"""Region in python"""
+import numpy as np
+
+def entry_index(batch, w, h, outputs, classes, coords, location, entry):
+    n = int(location/(w*h))
+    loc = location%(w*h)
+    return batch*outputs + n*w*h*(coords+classes+1) + entry*w*h + loc
+
+def region_python(a_np, N, classes, coords, background, softmax):
+    """Region operator
+    Parameters
+    ----------
+    a_np : numpy.ndarray
+        4-D with shape [batch, in_channel, in_height, in_width]
+
+    N : int
+        Darknet layer parameter n
+
+    classes : int
+        Darknet layer parameter classes
+
+    coords : int
+        Darknet layer parameter coords
+
+    background : int
+        Darknet layer parameter background
+
+    softmax : int
+        Darknet layer parameter softmax
+
+    Returns
+    -------
+    b_np : np.ndarray
+        4-D with shape [batch, out_channel, out_height, out_width]
+    """
+
+    batch, in_channel, in_height, in_width = a_np.shape
+    a_np_temp = np.reshape(a_np, batch*in_channel*in_height*in_width)
+    outputs = batch*in_channel*in_height*in_width
+    b_np = np.zeros(batch*in_channel*in_height*in_width)
+    for i in range(batch*in_channel*in_height*in_width):
+        b_np[i] = a_np_temp[i]
+    for b in range(batch):
+        for n in range(N):
+            index = entry_index(b, in_width, in_height, outputs, classes, coords, n*in_width*in_height, 0)
+            b_np[index: index+2*in_width*in_height] = 1/(1+np.exp(-1*b_np[index: index+2*in_width*in_height]))
+            index = entry_index(b, in_width, in_height, outputs, classes, coords, n*in_width*in_height, coords)
+            if not background:
+                b_np[index: index+in_width*in_height] = 1/(1+np.exp(-1*b_np[index: index+in_width*in_height]))
+
+    b_np = np.reshape(b_np, (batch, in_channel, in_height, in_width))
+    def local_softmax(data_in):
+        data_c, data_h, data_w = data_in.shape
+        largest = np.max(data_in, axis=1)
+        data_out = np.zeros((data_c, data_h, data_w))
+        for i in range(data_h):
+            for j in range(data_w):
+                data_out[:, i, j] = np.exp(data_in[:, i, j] - largest[i, j])
+        return data_out/data_out.sum(axis=0)
+
+    if softmax:
+        index = coords + int(not background)
+        for b in range(batch):
+            for i in range(N):
+                b_np_index = int(i*(in_channel/N) + index)
+                b_np[b, b_np_index: b_np_index + classes+background, :, :] = local_softmax(b_np[b, b_np_index:b_np_index + classes+background, :, :])
+
+    return b_np
diff --git a/topi/python/topi/testing/reorg_python.py b/topi/python/topi/testing/reorg_python.py
new file mode 100644
index 000000000000..185e5566e5bc
--- /dev/null
+++ b/topi/python/topi/testing/reorg_python.py
@@ -0,0 +1,42 @@
+# pylint: disable=invalid-name, line-too-long, unused-variable, too-many-locals
+"""Reorg in python"""
+import numpy as np
+
+def reorg_python(a_np, stride):
+    """Reorg operator
+
+    Parameters
+    ----------
+    a_np : numpy.ndarray
+        4-D with shape [batch, in_channel, in_height, in_width]
+
+    stride : int
+        Stride size
+
+    Returns
+    -------
+    b_np : np.ndarray
+        4-D with shape [batch, out_channel, out_height, out_width]
+    """
+
+    batch, in_channel, in_height, in_width = a_np.shape
+    a_np = np.reshape(a_np, batch*in_channel*in_height*in_width)
+    out_c = int(in_channel/(stride*stride))
+    out_channel = in_channel*stride*stride
+    out_height = int(in_height/stride)
+    out_width = int(in_width/stride)
+    b_np = np.zeros(batch*out_channel*out_height*out_width)
+    cnt = 0
+    for b in range(batch):
+        for k in range(in_channel):
+            for j in range(in_height):
+                for i in range(in_width):
+                    c2 = k % out_c
+                    offset = int(k / out_c)
+                    w2 = int(i*stride + offset % stride)
+                    h2 = int(j*stride + offset / stride)
+                    out_index = int(w2 + in_width*stride*(h2 + in_height*stride*(c2 + out_c*b)))
+                    b_np[cnt] = a_np[int(out_index)]
+                    cnt = cnt+1
+    b_np = np.reshape(b_np, (batch, out_channel, out_height, out_width))
+    return b_np
diff --git a/topi/python/topi/testing/shortcut_python.py b/topi/python/topi/testing/shortcut_python.py
new file mode 100644
index 000000000000..575c28b61c2c
--- /dev/null
+++ b/topi/python/topi/testing/shortcut_python.py
@@ -0,0 +1,47 @@
+# pylint: disable=invalid-name, line-too-long, unused-variable, too-many-locals
+"""Shortcut in python"""
+import numpy as np
+
+def shortcut_python(a_np1, a_np2):
+    """Reorg operator
+
+    Parameters
+    ----------
+    a_np1 : numpy.ndarray
+        4-D with shape [batch1, in_channel1, in_height1, in_width1]
+
+    a_np2 : numpy.ndarray
+        4-D with shape [batch2, in_channel2, in_height2, in_width2]
+
+    Returns
+    -------
+    b_np : np.ndarray
+        4-D with shape [batch1, out_channel1, out_height1, out_width1]
+    """
+
+    batch1, in_channel1, in_height1, in_width1 = a_np1.shape
+    batch2, in_channel2, in_height2, in_width2 = a_np2.shape
+    a_np1_temp = np.reshape(a_np1, batch1*in_channel1*in_height1*in_width1)
+    a_np2_temp = np.reshape(a_np2, batch2*in_channel2*in_height2*in_width2)
+    b_np = np.zeros(batch1*in_channel1*in_height1*in_width1)
+    stride = int(in_width1/in_width2)
+    sample = int(in_width2/in_width1)
+    if stride < 1:
+        stride = 1
+    if sample < 1:
+        sample = 1
+    minw = min(in_width1, in_width2)
+    minh = min(in_height1, in_height2)
+    minc = min(in_channel1, in_channel2)
+
+    for i in range((batch1*in_channel1*in_height1*in_width1)):
+        b_np[i] = a_np1_temp[i]
+    for b in range(batch1):
+        for k in range(minc):
+            for j in range(minh):
+                for i in range(minw):
+                    out_index = i*sample + in_width2*(j*sample + in_height2*(k + in_channel2*b))
+                    add_index = i*stride + in_width1*(j*stride + in_height1*(k + in_channel1*b))
+                    b_np[out_index] = a_np1_temp[out_index] + a_np2_temp[add_index]
+    b_np = np.reshape(b_np, (batch1, in_channel1, in_height1, in_width1))
+    return b_np
diff --git a/topi/python/topi/vision/__init__.py b/topi/python/topi/vision/__init__.py
new file mode 100644
index 000000000000..0f164fdafc2b
--- /dev/null
+++ b/topi/python/topi/vision/__init__.py
@@ -0,0 +1,7 @@
+# pylint: disable=wildcard-import
+"""VISION network operators"""
+from __future__ import absolute_import as _abs
+
+from . import yolo2
+from .shortcut import *
+from .reorg import *
diff --git a/topi/python/topi/vision/reorg.py b/topi/python/topi/vision/reorg.py
new file mode 100644
index 000000000000..761a1718fd23
--- /dev/null
+++ b/topi/python/topi/vision/reorg.py
@@ -0,0 +1,39 @@
+"""
+REORG Operator
+====================
+Reorg operator, used in darknet.
+"""
+from __future__ import absolute_import as _abs
+import tvm
+from .. import util
+from .. import transform
+
+@tvm.target.generic_func
+def reorg(data, stride):
+    """Reorg forward operators.
+
+    Parameters
+    ----------
+    Input : tvm.Tensor
+        4-D with shape [batch, in_channel, in_height, in_width]
+
+    stride : int
+        Stride value for reorganization
+
+    Returns
+    -------
+    Output : tvm.Tensor
+        4-D with shape [batch, out_channel, out_height, out_width]
+    """
+    batch, c_in, h_in, w_in = util.get_const_tuple(data.shape)
+    out_c = int(c_in / (stride * stride))
+    out = tvm.compute((batch, c_in, h_in, w_in), lambda b, k, j, i:
+                      data[b * stride * stride,
+                           (k % out_c) * stride * stride,
+                           (j*stride + (k / out_c) / stride) * stride,
+                           (i*stride + (k / out_c) % stride)],
+                      tag="reorg")
+    out_c = int(c_in * stride * stride)
+    out_h = int(h_in / stride)
+    out_w = int(w_in / stride)
+    return transform.reshape(out, (batch, out_c, out_h, out_w))
diff --git a/topi/python/topi/vision/shortcut.py b/topi/python/topi/vision/shortcut.py
new file mode 100644
index 000000000000..529360190a4e
--- /dev/null
+++ b/topi/python/topi/vision/shortcut.py
@@ -0,0 +1,45 @@
+"""Shortcut operators (short-cut connections)."""
+from __future__ import absolute_import as _abs
+import tvm
+from .. import util
+from .. import transform
+
+@tvm.target.generic_func
+def shortcut(inp1, inp2):
+    """Shortcut forward operators.
+
+    Parameters
+    ----------
+    First Input : tvm.Tensor
+        4-D with shape [batch, in_channel, in_height, in_width]
+
+    Second Input : tvm.Tensor
+        4-D with shape [batch, in_channel, in_height, in_width]
+
+    Returns
+    -------
+    Output : tvm.Tensor
+        4-D with shape [batch, out_channel, out_height, out_width]
+    """
+
+    _, inp1_c, inp1_h, inp1_w = util.get_const_tuple(inp1.shape)
+    batch, inp2_c, inp2_h, inp2_w = util.get_const_tuple(inp2.shape)
+
+    stride = int(max(inp2_w / inp1_w, 1))
+    sample = int(max(inp1_w / inp2_w, 1))
+    minc = min(inp2_c, inp1_c)
+    minh = min(inp2_h, inp1_h)
+    minw = min(inp2_w, inp1_w)
+
+    out = tvm.compute((batch, minc, minh, minw), lambda b, c, h, w:
+                      inp1[b, c, h * sample, w * sample] +
+                      inp2[b, c, h * stride, w * stride],
+                      tag="shortcut")
+
+    split_indices = int(inp1_c / minc)
+    if split_indices > 1:
+        split_res = transform.split(inp1, split_indices, 1)
+        split_res[0] = out
+        out = transform.concatenate(split_res, 1)
+
+    return out
diff --git a/topi/python/topi/vision/yolo2/__init__.py b/topi/python/topi/vision/yolo2/__init__.py
new file mode 100644
index 000000000000..c0e9899a41aa
--- /dev/null
+++ b/topi/python/topi/vision/yolo2/__init__.py
@@ -0,0 +1,5 @@
+# pylint: disable=wildcard-import
+"""VISION network operators"""
+from __future__ import absolute_import as _abs
+
+from .region import *
diff --git a/topi/python/topi/vision/yolo2/region.py b/topi/python/topi/vision/yolo2/region.py
new file mode 100644
index 000000000000..8f31d88fa941
--- /dev/null
+++ b/topi/python/topi/vision/yolo2/region.py
@@ -0,0 +1,63 @@
+# pylint: disable=invalid-name, unused-variable
+"""
+REGION Operator
+====================
+Region operator, used in darknet.
+"""
+from __future__ import absolute_import as _abs
+import tvm
+from ... import transform
+from ... import util
+from ... import math
+from ... import nn
+
+@tvm.target.generic_func
+def region(data, num, classes, coords, background, softmax=True):
+    """Region forward operators.
+    Parameters
+    ----------
+    data : tvm.Tensor
+        4-D with shape [batch, c_in, h_in, w_in]
+
+    num : int
+        Darknet layer parameter n
+
+    classes : int
+        Darknet layer parameter classes
+
+    coords : int
+        Darknet layer parameter coords
+
+    background : int
+        Darknet layer parameter background
+
+    softmax : boolean
+        Darknet layer parameter softmax
+
+    Returns
+    -------
+    out : tvm.Tensor
+        4-D with shape [batch, c_in, h_in, w_in]
+    """
+
+    batch, c_in, h_in, w_in = util.get_const_tuple(data.shape)
+    split_indices = classes+coords+1
+    data_block = transform.reshape(data, (batch, num, split_indices, h_in, w_in))
+    split_res = transform.split(data_block, split_indices, 2)
+    split_res[0] = math.sigmoid(split_res[0])
+    split_res[1] = math.sigmoid(split_res[1])
+    if not background:
+        split_res[coords] = math.sigmoid(split_res[coords])
+
+    if softmax:
+        offset = coords + int(not background)
+        data_block_1 = []
+        data_block_1.append(transform.concatenate(split_res[0:offset], 2))
+        temp_out = transform.concatenate(split_res[offset:split_indices], 2)
+        temp_out = nn.softmax(temp_out, axis=2)
+        data_block_1.append(temp_out)
+        split_res = data_block_1
+
+    out = transform.concatenate(split_res, 2)
+    out = transform.reshape(out, data.shape)
+    return out
diff --git a/topi/tests/python/test_topi_region.py b/topi/tests/python/test_topi_region.py
new file mode 100644
index 000000000000..b2ef02530ba6
--- /dev/null
+++ b/topi/tests/python/test_topi_region.py
@@ -0,0 +1,44 @@
+"""Example code to do region."""
+import numpy as np
+import topi
+from topi.util import get_const_tuple
+import tvm
+def verify_region(batch, in_size, in_channel, n, classes, coords, background, l_softmax):
+    '''Verify region operator by comparing outputs from tvm and numpy implementation'''
+    in_height = in_width = in_size
+
+    A = tvm.placeholder((batch, in_channel, in_height, in_width), name='A')
+    B = topi.vision.yolo2.region(A, n, classes, coords, background, l_softmax)
+
+    a_shape = get_const_tuple(A.shape)
+    dtype = A.dtype
+
+    def get_ref_data_region():
+        a_np = np.random.uniform(size=a_shape).astype(dtype)
+        b_np = topi.testing.region_python(a_np, n, classes, coords, background, l_softmax)
+        return a_np, b_np
+
+    a_np, b_np = get_ref_data_region()
+    def check_device(device):
+        '''Cheching devices is enabled or not'''
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s = topi.generic.vision.schedule_region([B])
+        a = tvm.nd.array(a_np, ctx)
+        b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
+        func = tvm.build(s, [A, B], device)
+        func(a, b)
+        np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+    for device in ['llvm', 'cuda']:
+        check_device(device)
+
+def test_region():
+    verify_region(1, 19, 425, 5, 80, 4, 0, 1)
+
+if __name__ == "__main__":
+    test_region()
diff --git a/topi/tests/python/test_topi_reorg.py b/topi/tests/python/test_topi_reorg.py
new file mode 100644
index 000000000000..36a4d71cb5d4
--- /dev/null
+++ b/topi/tests/python/test_topi_reorg.py
@@ -0,0 +1,47 @@
+"""Example code to do reorg."""
+import numpy as np
+import topi
+from topi.util import get_const_tuple
+import tvm
+
+def verify_reorg(batch, in_size, in_channel, stride):
+    '''Verify reorg operator by comparing outputs from tvm and numpy implementation'''
+    in_height = in_width = in_size
+
+    A = tvm.placeholder((batch, in_channel, in_height, in_width), name='A')
+    B = topi.vision.reorg(A, stride)
+
+    a_shape = get_const_tuple(A.shape)
+    dtype = A.dtype
+
+    def get_ref_data_reorg():
+        a_np = np.random.uniform(size=a_shape).astype(dtype)
+        b_np = topi.testing.reorg_python(a_np, stride)
+        return a_np, b_np
+
+    a_np, b_np = get_ref_data_reorg()
+
+    def check_device(device):
+        '''Cheching devices is enabled or not'''
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s = topi.generic.schedule_injective([B])
+
+        a = tvm.nd.array(a_np, ctx)
+        b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
+        func = tvm.build(s, [A, B], device)
+        func(a, b)
+        np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+    for device in ['llvm', 'cuda']:
+        check_device(device)
+
+def test_reorg():
+    verify_reorg(1, 20, 8, 2)
+
+if __name__ == "__main__":
+    test_reorg()
diff --git a/topi/tests/python/test_topi_shortcut.py b/topi/tests/python/test_topi_shortcut.py
new file mode 100644
index 000000000000..b5840fe8e7b2
--- /dev/null
+++ b/topi/tests/python/test_topi_shortcut.py
@@ -0,0 +1,48 @@
+"""Example code to do shortcut."""
+import numpy as np
+import topi
+from topi.util import get_const_tuple
+import tvm
+
+def verify_shortcut(batch, in_size, in_channel):
+    '''Verify shortcut operator by comparing outputs from tvm and numpy implementation'''
+    in_height = in_width = in_size
+
+    A1 = tvm.placeholder((batch, in_channel, in_height, in_width), name='A1')
+    A2 = tvm.placeholder((batch, in_channel, in_height, in_width), name='A2')
+    B = topi.vision.shortcut(A1, A2)
+
+    a_shape = get_const_tuple(A1.shape)
+    dtype = A1.dtype
+    def get_ref_data_shortcut():
+        a_np1 = np.random.uniform(size=a_shape).astype(dtype)
+        a_np2 = np.random.uniform(size=a_shape).astype(dtype)
+        b_np = topi.testing.shortcut_python(a_np1, a_np2)
+        return a_np1, a_np2, b_np
+
+    a_np1, a_np2, b_np = get_ref_data_shortcut()
+    def check_device(device):
+        '''Cheching devices is enabled or not'''
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s = topi.generic.schedule_injective([B])
+
+        a1 = tvm.nd.array(a_np1, ctx)
+        a2 = tvm.nd.array(a_np2, ctx)
+        b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
+        func = tvm.build(s, [A1, A2, B], device)
+        func(a1, a2, b)
+        np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+    for device in ['llvm', 'cuda']:
+        check_device(device)
+
+def test_shortcut():
+    verify_shortcut(1, 144, 32)
+
+if __name__ == "__main__":
+    test_shortcut()

From 74e2ab433cfeb741410aa59e0658a1bc46444e7d Mon Sep 17 00:00:00 2001
From: nhynes <nhynes@berkeley.edu>
Date: Thu, 8 Mar 2018 16:00:44 -0800
Subject: [PATCH 191/949] Use single-threaded SGX parallel (#975)

---
 sgx/sgx_runtime.cc | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/sgx/sgx_runtime.cc b/sgx/sgx_runtime.cc
index 0bc6a3189e7a..6a0d0dfb224c 100644
--- a/sgx/sgx_runtime.cc
+++ b/sgx/sgx_runtime.cc
@@ -10,13 +10,13 @@
 #include "../../src/runtime/registry.cc"
 #include "../../src/runtime/system_lib_module.cc"
 
-// dummy parallel runtime
+// dummy parallel runtime (for now)
 int TVMBackendParallelLaunch(
     FTVMParallelLambda flambda,
     void* cdata,
     int num_task) {
-  TVMAPISetLastError("Parallel is not (yet) supported in SGX runtime");
-  return -1;
+  TVMParallelGroupEnv env = { nullptr /* sync_handle */, 1 /* num_task */ };
+  return flambda(0 /* task_id */, &env, cdata);
 }
 
 int TVMBackendParallelBarrier(int task_id, TVMParallelGroupEnv* penv) {

From 65ad02eb3fb615d2aad578e14086b65e8c133cea Mon Sep 17 00:00:00 2001
From: Chris Nuernberger <cnuernber@gmail.com>
Date: Thu, 8 Mar 2018 22:14:46 -0300
Subject: [PATCH 192/949] Assert dont crash on null strides (#976)

---
 src/pass/arg_binder.cc | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/src/pass/arg_binder.cc b/src/pass/arg_binder.cc
index cdd344670725..8139a5644bb6 100644
--- a/src/pass/arg_binder.cc
+++ b/src/pass/arg_binder.cc
@@ -194,6 +194,9 @@ void ArgBinder::BindDLTensor(const Buffer& buffer,
   init_nest_.emplace_back(LetStmt::make(
       v_strides, TVMArrayGet(Handle(), handle, intrinsic::kArrStrides),
       nop));
+  Expr is_null = Call::make(
+    Bool(1), intrinsic::tvm_handle_is_null,
+    {v_strides}, Call::PureIntrinsic);
   if (buffer->strides.size() == 0) {
     // Assert the buffer is compact
     Type stype = buffer->DefaultIndexType();
@@ -215,13 +218,14 @@ void ArgBinder::BindDLTensor(const Buffer& buffer,
       Stmt check =
           AssertStmt::make(arith::ComputeReduce<ir::And>(conds, Expr()),
                            stride_err_msg.str(), Evaluate::make(0));
-      Expr is_null = Call::make(
-          Bool(1), intrinsic::tvm_handle_is_null,
-          {v_strides}, Call::PureIntrinsic);
       check = IfThenElse::make(Not::make(is_null), check, Stmt());
       init_nest_.emplace_back(Block::make(check, Evaluate::make(0)));
     }
   } else {
+    std::ostringstream stride_null_err_msg;
+    stride_null_err_msg << arg_name << ".strides: expected non-null strides.";
+    asserts_.emplace_back(AssertStmt::make(Not::make(is_null), stride_null_err_msg.str(), nop));
+
     for (size_t k = 0; k < buffer->strides.size(); ++k) {
       std::ostringstream field_name;
       field_name << v_strides->name_hint << '[' << k << ']';

From 5aa0c74c56c34a7d4659cd2fd63c95c59acc1f72 Mon Sep 17 00:00:00 2001
From: Yida Wang <yidawa@gmail.com>
Date: Thu, 8 Mar 2018 20:08:31 -0800
Subject: [PATCH 193/949] [RUNTIME] Better scalability for multi-thread
 parallelization of CPUs (#971)

---
 src/runtime/thread_pool.cc | 211 ++++++++++++++++++++++++-------------
 1 file changed, 139 insertions(+), 72 deletions(-)

diff --git a/src/runtime/thread_pool.cc b/src/runtime/thread_pool.cc
index 7caedaa15538..bde16d8f56a8 100644
--- a/src/runtime/thread_pool.cc
+++ b/src/runtime/thread_pool.cc
@@ -17,6 +17,11 @@
 #include <cstring>
 #include <memory>
 #include <sstream>
+#if defined(__linux__)
+#include <sched.h>
+#endif
+
+const constexpr int kL1CacheBytes = 64;
 
 namespace tvm {
 namespace runtime {
@@ -127,99 +132,124 @@ class ParallelLauncher {
   std::vector<std::string> par_errors_;
 };
 
-/*! \brief Working queue for each thread */
-class ParallelTaskQueue {
+/*! \brief Lock-free single-producer-single-consumer queue for each thread */
+class SpscTaskQueue {
  public:
   /*! \brief The task entry */
   struct Task {
     ParallelLauncher* launcher;
     int32_t task_id;
   };
-  ParallelTaskQueue() {
-    ring_.resize(2);
+
+  SpscTaskQueue() :
+    buffer_(new Task[kRingSize]),
+    head_(0),
+    tail_(0) {
   }
-  /*!
-   * \brief Signal to kill the job.
-   */
-  void SignalForKill() {
-    std::lock_guard<std::mutex> lock(mutex_);
-    exit_now_.store(true);
-    cv_.notify_all();
+
+  ~SpscTaskQueue() {
+    delete[] buffer_;
   }
+
   /*!
-   * \brief Push task into the queue.
-   * \param task The task to be pushed.
+   * \brief Push a task into the queue and notify the comsumer if it is on wait.
+   * \param input The task to be dequeued.
    */
-  void Push(Task task) {
-    std::unique_lock<std::mutex> lock(mutex_);
-    if (num_pending_ < ring_.size()) {
-      CHECK_NE(ring_.size(), 0U);
-      ring_[(head_ + num_pending_) % ring_.size()] = task;
-      ++num_pending_;
-    } else {
-      size_t old_size = ring_.size();
-      ring_.resize(old_size * 2);
-      if (head_ + num_pending_ > old_size) {
-        // copy the ring overflow part into the tail.
-        size_t ncopy = head_ + num_pending_ - old_size;
-        memcpy(&ring_[0] + old_size, &ring_[0], ncopy * sizeof(Task));
-      }
-      ring_[(head_ + num_pending_) % ring_.size()] = task;
-      ++num_pending_;
+  void Push(const Task& input) {
+    while (!Enqueue(input)) {
+      std::this_thread::yield();
     }
-    if (nwait_consumer_ != 0) {
-      lock.unlock();
+    if (pending_.fetch_add(1) == -1) {
+      std::unique_lock<std::mutex> lock(mutex_);
       cv_.notify_one();
     }
   }
+
   /*!
-   * \brief Pop task from the queue
-   * \param task The task to be poped.
-   * \param timeout The number of cycles to spin before sleep.
-   * \return Whether pop is successful or we need to exit now.
+   * \brief Pop a task out of the queue and condition wait if no tasks.
+   * \param output The pointer to the task to be dequeued.
+   * \param spin_count The number of iterations to spin before sleep.
+   * \return Whether pop is successful (true) or we need to exit now (false).
    */
-  bool Pop(Task* task, int timeout = 10) {
-    std::unique_lock<std::mutex> lock(mutex_);
-    if (num_pending_ != 0) {
-      *task = ring_[head_];
-      head_ = (head_ + 1) % ring_.size();
-      --num_pending_;
-      if (exit_now_.load()) return false;
-    } else {
-      lock.unlock();
-      // do a bit spin and busy waiting before sleep.
-      for (int i = 0; i < timeout && num_pending_ == 0; ++i) {
+  bool Pop(Task* output, uint32_t spin_count = 300000) {
+    // Busy wait a bit when the queue is empty.
+    // If a new task comes to the queue quickly, this wait avoid the worker from sleeping.
+    // The default spin count is set by following the typical omp convention
+    for (uint32_t i = 0; i < spin_count && pending_.load() == 0; ++i) {
         std::this_thread::yield();
       }
-      lock.lock();
-      ++nwait_consumer_;
+    if (pending_.fetch_sub(1) == 0) {
+      std::unique_lock<std::mutex> lock(mutex_);
       cv_.wait(lock, [this] {
-          return num_pending_ != 0 || exit_now_.load();
+          return pending_.load() >= 0 || exit_now_.load();
         });
-      --nwait_consumer_;
-      *task = ring_[head_];
-      head_ = (head_ + 1) % ring_.size();
-      --num_pending_;
-      if (exit_now_.load()) return false;
     }
+    if (exit_now_.load(std::memory_order_relaxed)) {
+      return false;
+    }
+    const uint32_t head = head_.load(std::memory_order_relaxed);
+    // sanity check if the queue is empty
+    CHECK(tail_.load(std::memory_order_acquire) != head);
+    *output = buffer_[head];
+    head_.store((head + 1) % kRingSize, std::memory_order_release);
     return true;
   }
 
+  /*!
+   * \brief Signal to terminate the worker.
+   */
+  void SignalForKill() {
+    std::lock_guard<std::mutex> lock(mutex_);
+    exit_now_.store(true);
+    cv_.notify_all();
+  }
+
  private:
-  // Number of the elments in the queue
-  uint32_t num_pending_{0};
-  // Queue head
-  uint32_t head_{0};
-  // Number of consumers to wait.
-  uint32_t nwait_consumer_{0};
+  /*!
+   * \brief Lock-free enqueue.
+   * \param input The task to be enqueued.
+   * \return Whether the task is enqueued.
+   */
+  bool Enqueue(const Task& input) {
+    const uint32_t tail = tail_.load(std::memory_order_relaxed);
+
+    if ((tail + 1) % kRingSize != (head_.load(std::memory_order_acquire))) {
+      buffer_[tail] = input;
+      tail_.store((tail + 1) % kRingSize, std::memory_order_release);
+      return true;
+    }
+    return false;
+  }
+
+  // the cache line paddings are used for avoid false sharing between atomic variables
+  typedef char cache_line_pad_t[kL1CacheBytes];
+  cache_line_pad_t pad0_;
+  // size of the queue, the queue can host size_ - 1 items at most
+  // define it as a constant for better compiler optimization
+  static constexpr const int kRingSize = 2;
+  // pointer to access the item
+  Task* const buffer_;
+
+  cache_line_pad_t pad1_;
+  // queue head, where one gets a task from the queue
+  std::atomic<uint32_t> head_;
+
+  cache_line_pad_t pad2_;
+  // queue tail, when one puts a task to the queue
+  std::atomic<uint32_t> tail_;
+
+  cache_line_pad_t pad3_;
+  // pending tasks in the queue
+  std::atomic<int8_t> pending_{0};
+
+  cache_line_pad_t pad4_;
+  // signal for exit now
+  std::atomic<bool> exit_now_{false};
+
   // internal mutex
   std::mutex mutex_;
   // cv for consumer
   std::condition_variable cv_;
-  // signal for exit now
-  std::atomic<bool> exit_now_{false};
-  // The internal ring.
-  std::vector<Task> ring_;
 };
 
 // The thread pool
@@ -244,7 +274,7 @@ class ThreadPool {
     this->Init();
   }
   ~ThreadPool() {
-    for (std::unique_ptr<ParallelTaskQueue>& q : queues_) {
+    for (std::unique_ptr<SpscTaskQueue>& q : queues_) {
       q->SignalForKill();
     }
     for (std::thread& t : threads_) {
@@ -267,13 +297,14 @@ class ThreadPool {
           << " workers=" << num_workers_ << " request=" << num_task;
     }
     launcher->Init(flambda, cdata, num_task, need_sync != 0);
-    ParallelTaskQueue::Task tsk;
+    SpscTaskQueue::Task tsk;
     tsk.launcher = launcher;
     for (int i = 0; i < num_task; ++i) {
       tsk.task_id = i;
       queues_[i]->Push(tsk);
     }
-    return launcher->WaitForJobs();
+    int res = launcher->WaitForJobs();
+    return res;
   }
 
   static ThreadPool* Global() {
@@ -285,8 +316,9 @@ class ThreadPool {
   // Initialize the pool.
   void Init() {
     for (int i = 0; i < num_workers_; ++i) {
+      // The SpscTaskQueue only host ONE item at a time
       queues_.emplace_back(
-          std::unique_ptr<ParallelTaskQueue>(new ParallelTaskQueue()));
+          std::unique_ptr<SpscTaskQueue>(new SpscTaskQueue()));
     }
     threads_.resize(num_workers_);
     for (int i = 0; i < num_workers_; ++i) {
@@ -294,10 +326,20 @@ class ThreadPool {
           this->RunWorker(queues_[i].get());
         });
     }
+    const char *val = getenv("TVM_BIND_THREADS");
+    if (val == nullptr || atoi(val) == 1) {
+      if (num_workers_ <= std::thread::hardware_concurrency()) {
+        SetThreadAffinity();
+      } else {
+        LOG(WARNING)
+          << "The thread affinity cannot be set when the number of workers is larger "
+          << "than the number of available cores in the system.";
+      }
+    }
   }
   // Internal worker function.
-  void RunWorker(ParallelTaskQueue* queue) {
-    ParallelTaskQueue::Task task;
+  void RunWorker(SpscTaskQueue* queue) {
+    SpscTaskQueue::Task task;
     ParallelLauncher::ThreadLocal()->is_worker = true;
     while (queue->Pop(&task)) {
       CHECK(task.launcher != nullptr);
@@ -310,9 +352,33 @@ class ThreadPool {
       }
     }
   }
+  // bind worker threads to disjoint cores
+  void SetThreadAffinity() {
+#if defined(__ANDROID__)
+  #define CPU_SETSIZE 1024
+  #define __NCPUBITS (8 * sizeof (uint64_t))
+  typedef struct {
+    uint64_t __bits[CPU_SETSIZE / __NCPUBITS];
+  } cpu_set_t;
+
+  #define CPU_SET(cpu, cpusetp) \
+    ((cpusetp)->__bits[(cpu)/__NCPUBITS] |= (1UL << ((cpu) % __NCPUBITS)))
+  #define CPU_ZERO(cpusetp) \
+    memset((cpusetp), 0, sizeof(cpu_set_t))
+#endif
+    for (int i=0; i < num_workers_; ++i) {
+#if defined(__linux__) || defined(__ANDROID__)
+      cpu_set_t cpuset;
+      CPU_ZERO(&cpuset);
+      CPU_SET(i, &cpuset);
+      pthread_setaffinity_np(threads_[i].native_handle(),
+        sizeof(cpu_set_t), &cpuset);
+#endif
+    }
+  }
   // Number of workers
   int num_workers_;
-  std::vector<std::unique_ptr<ParallelTaskQueue> > queues_;
+  std::vector<std::unique_ptr<SpscTaskQueue> > queues_;
   std::vector<std::thread> threads_;
 };
 
@@ -323,8 +389,9 @@ int TVMBackendParallelLaunch(
     FTVMParallelLambda flambda,
     void* cdata,
     int num_task) {
-  return tvm::runtime::ThreadPool::Global()->Launch(
+  int res = tvm::runtime::ThreadPool::Global()->Launch(
       flambda, cdata, num_task, 1);
+  return res;
 }
 
 int TVMBackendParallelBarrier(int task_id, TVMParallelGroupEnv* penv) {

From d5c42b0c29ac4793c9cd0f09d8a733981b56eb50 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 9 Mar 2018 11:12:57 -0800
Subject: [PATCH 194/949] [IOS] Improve the iOS RPC with exclusive filesys lock
 (#981)

---
 .gitignore                          |  1 +
 apps/ios_rpc/tests/ios_rpc_test.py  |  1 -
 include/tvm/runtime/c_runtime_api.h |  2 +-
 include/tvm/runtime/packed_func.h   |  7 +++---
 python/tvm/contrib/rpc_proxy.py     |  2 +-
 python/tvm/contrib/util.py          | 39 ++++++++++++++++++++++++++++-
 python/tvm/contrib/xcode.py         | 36 ++++++++++++++++++++------
 src/runtime/graph/graph_runtime.cc  |  4 +--
 8 files changed, 74 insertions(+), 18 deletions(-)

diff --git a/.gitignore b/.gitignore
index b645f6693b09..4f44aeb8d952 100644
--- a/.gitignore
+++ b/.gitignore
@@ -139,6 +139,7 @@ xcuserdata/
 .DS_Store
 tags
 cscope*
+*.lock
 
 # vim temporary files
 *.swp
diff --git a/apps/ios_rpc/tests/ios_rpc_test.py b/apps/ios_rpc/tests/ios_rpc_test.py
index 80317a6e350d..a3df1d3a9043 100644
--- a/apps/ios_rpc/tests/ios_rpc_test.py
+++ b/apps/ios_rpc/tests/ios_rpc_test.py
@@ -59,7 +59,6 @@ def test_rpc_module():
     # Start RPC test server that contains the compiled library.
     server = xcode.popen_test_rpc(proxy_host, proxy_port, key,
                                   destination=destination,
-                                  options=['-quiet'],
                                   libs=[path_dso1, path_dso2])
 
     # connect to the proxy
diff --git a/include/tvm/runtime/c_runtime_api.h b/include/tvm/runtime/c_runtime_api.h
index f6d22534e8ab..af89860b5784 100644
--- a/include/tvm/runtime/c_runtime_api.h
+++ b/include/tvm/runtime/c_runtime_api.h
@@ -320,7 +320,7 @@ typedef void (*TVMPackedCFuncFinalizer)(void* resource_handle);
  *  TVM call this function to get the extension functions
  *  The declarer will call register_func to register function and their name.
  *
- * \param resource_func_handle The register function
+ * \param register_func_handle The register function
  * \return 0 if success, -1 if failure happens
  */
 typedef int (*TVMExtensionFuncDeclarer)(TVMFunctionHandle register_func_handle);
diff --git a/include/tvm/runtime/packed_func.h b/include/tvm/runtime/packed_func.h
index 8101ecdc175c..c5c86535cee2 100644
--- a/include/tvm/runtime/packed_func.h
+++ b/include/tvm/runtime/packed_func.h
@@ -688,11 +688,10 @@ inline TVMType String2TVMType(std::string s) {
     LOG(FATAL) << "unknown type " << s;
   }
   char* xdelim;  // emulate sscanf("%ux%u", bits, lanes)
-  unsigned bits = strtoul(scan, &xdelim, 10);
-  if (bits != 0) t.bits = static_cast<uint8_t>(bits);
+  uint8_t bits = static_cast<uint8_t>(strtoul(scan, &xdelim, 10));
+  if (bits != 0) t.bits = bits;
   if (*xdelim == 'x') {
-    unsigned lanes = strtoul(xdelim + 1, nullptr, 10);
-    t.lanes = static_cast<uint16_t>(lanes);
+    t.lanes = static_cast<uint16_t>(strtoul(xdelim + 1, nullptr, 10));
   }
   return t;
 }
diff --git a/python/tvm/contrib/rpc_proxy.py b/python/tvm/contrib/rpc_proxy.py
index 9634c258b39f..5d67556ad1ce 100644
--- a/python/tvm/contrib/rpc_proxy.py
+++ b/python/tvm/contrib/rpc_proxy.py
@@ -392,7 +392,7 @@ def __init__(self,
                  port=9091,
                  port_end=9199,
                  web_port=0,
-                 timeout_client=240,
+                 timeout_client=600,
                  timeout_server=600,
                  index_page=None,
                  resource_files=None):
diff --git a/python/tvm/contrib/util.py b/python/tvm/contrib/util.py
index 93d5b897c7c1..338567d6f619 100644
--- a/python/tvm/contrib/util.py
+++ b/python/tvm/contrib/util.py
@@ -2,6 +2,7 @@
 from __future__ import absolute_import as _abs
 import os
 import tempfile
+import fcntl
 import shutil
 
 class TempDirectory(object):
@@ -38,7 +39,7 @@ def relpath(self, name):
         return os.path.join(self.temp_dir, name)
 
     def listdir(self):
-        """"List contents in the dir.
+        """List contents in the dir.
 
         Returns
         -------
@@ -47,6 +48,7 @@ def listdir(self):
         """
         return os.listdir(self.temp_dir)
 
+
 def tempdir():
     """Create temp dir which deletes the contents when exit.
 
@@ -56,3 +58,38 @@ def tempdir():
         The temp directory object
     """
     return TempDirectory()
+
+
+class FileLock(object):
+    """File lock object
+
+    Parameters
+    ----------
+    path : str
+        The path to the lock
+    """
+    def __init__(self, path):
+        self.lock_file = open(path, "w")
+        fcntl.lockf(self.lock_file, fcntl.LOCK_EX)
+
+
+    def release(self):
+        """Release the lock"""
+        if self.lock_file:
+            fcntl.lockf(self.lock_file, fcntl.LOCK_UN)
+            self.lock_file.close()
+            self.lock_file = None
+
+def filelock(path):
+    """Create a file lock which locks on path
+
+    Parameters
+    ----------
+    path : str
+        The path to the lock
+
+    Returns
+    -------
+    lock : File lock object
+    """
+    return FileLock(path)
diff --git a/python/tvm/contrib/xcode.py b/python/tvm/contrib/xcode.py
index 3456bdf1964a..63fbad2a58cf 100644
--- a/python/tvm/contrib/xcode.py
+++ b/python/tvm/contrib/xcode.py
@@ -146,6 +146,28 @@ def compile_metal(code, path_target=None, sdk="macosx"):
     return libbin
 
 
+class XCodeRPCServer(object):
+    """Wrapper for RPC server
+
+    Parameters
+    ----------
+    cmd : list of str
+       The command to run
+
+    lock: FileLock
+       Lock on the path
+    """
+    def __init__(self, cmd, lock):
+        self.proc = subprocess.Popen(cmd)
+        self.lock = lock
+
+    def join(self):
+        """Wait server to finish and release its resource
+        """
+        self.proc.wait()
+        self.lock.release()
+
+
 def popen_test_rpc(host,
                    port,
                    key,
@@ -190,6 +212,10 @@ def popen_test_rpc(host,
     if not os.path.exists(proj_path):
         raise RuntimeError("Cannot find tvmrpc.xcodeproj in %s," +
                            (" please set env TVM_IOS_RPC_ROOT correctly" % rpc_root))
+
+    # Lock the path so only one file can run
+    lock = util.filelock(os.path.join(rpc_root, "ios_rpc.lock"))
+
     with open(os.path.join(rpc_root, "rpc_config.txt"), "w") as fo:
         fo.write("%s %d %s\n" % (host, port, key))
         libs = libs if libs else []
@@ -203,11 +229,5 @@ def popen_test_rpc(host,
     if options:
         cmd += options
     cmd += ["test"]
-    if "-quiet" in options:
-        with open(os.devnull, 'w') as devnull:
-            proc = subprocess.Popen(cmd,
-                                    stderr=subprocess.STDOUT,
-                                    stdout=devnull)
-    else:
-        proc = subprocess.Popen(cmd)
-    return proc
+
+    return XCodeRPCServer(cmd, lock)
diff --git a/src/runtime/graph/graph_runtime.cc b/src/runtime/graph/graph_runtime.cc
index 89bb3a052b0f..b692ab729566 100644
--- a/src/runtime/graph/graph_runtime.cc
+++ b/src/runtime/graph/graph_runtime.cc
@@ -57,7 +57,7 @@ class GraphRuntime : public ModuleNode {
   }
   /*!
    * \brief Initialize the graph executor with graph and context.
-   * \param graph The execution graph.
+   * \param graph_json The execution graph.
    * \param module The module containing the compiled functions.
    * \param ctx The context where the graph should sit on
    */
@@ -94,7 +94,7 @@ class GraphRuntime : public ModuleNode {
   /*!
    * \brief set index-th input to the graph.
    * \param index The input index.
-   * \param data The input data.
+   * \param data_in The input data.
    */
   void SetInput(int index, DLTensor* data_in) {
     CHECK_LT(static_cast<size_t>(index), input_nodes_.size());

From e4ead4a33288ce5ec4abbeab2465baa896092271 Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Sat, 10 Mar 2018 03:39:38 +0800
Subject: [PATCH 195/949] fix keeping trivial loop (#982)

---
 src/schedule/schedule_ops.cc | 25 ++++++++++++++++---------
 1 file changed, 16 insertions(+), 9 deletions(-)

diff --git a/src/schedule/schedule_ops.cc b/src/schedule/schedule_ops.cc
index 1fbffb61fc7f..b9b02050a556 100644
--- a/src/schedule/schedule_ops.cc
+++ b/src/schedule/schedule_ops.cc
@@ -57,8 +57,10 @@ class InjectAttach : public IRMutator {
  public:
   InjectAttach(const Stage& stage,
                const Stage& attach_spec,
-               const std::unordered_map<IterVar, Range>& dom_map)
-      : stage_(stage), attach_spec_(attach_spec), dom_map_(dom_map) {}
+               const std::unordered_map<IterVar, Range>& dom_map,
+               bool del_trivial_loop)
+      : stage_(stage), attach_spec_(attach_spec), dom_map_(dom_map),
+        del_trivial_loop_(del_trivial_loop) {}
 
   Stmt Mutate(Stmt stmt) final {
     CHECK(stmt.defined());
@@ -74,7 +76,7 @@ class InjectAttach : public IRMutator {
         found_attach = true;
         stmt = AttrStmt::make(
             op->node, op->attr_key, op->value,
-            MakePipeline(stage_, dom_map_, op->body, true));
+            MakePipeline(stage_, dom_map_, op->body, del_trivial_loop_));
       }
     }
     return stmt;
@@ -89,6 +91,8 @@ class InjectAttach : public IRMutator {
   const Stage& attach_spec_;
   // domain map
   const std::unordered_map<IterVar, Range>& dom_map_;
+  // whether delete trivial loops with extent of 1
+  bool del_trivial_loop_;
 };
 
 // inject the operator's realization on the stmt.
@@ -97,9 +101,10 @@ class InjectScanStep : public IRMutator {
   InjectScanStep(const Stage& stage,
                  const Operation& scan_op,
                  const std::unordered_map<IterVar, Range>& dom_map,
-                 bool is_init)
+                 bool is_init,
+                 bool del_trivial_loop)
       : stage_(stage), scan_op_(scan_op),
-        dom_map_(dom_map), is_init_(is_init) {}
+        dom_map_(dom_map), is_init_(is_init), del_trivial_loop_(del_trivial_loop) {}
 
   Stmt Mutate(Stmt stmt) final {
     CHECK(stmt.defined());
@@ -113,7 +118,7 @@ class InjectScanStep : public IRMutator {
         found_attach = true;
         stmt = AttrStmt::make(
             op->node, op->attr_key, op->value,
-            MakePipeline(stage_, dom_map_, op->body, true));
+            MakePipeline(stage_, dom_map_, op->body, del_trivial_loop_));
       }
     }
     return stmt;
@@ -130,6 +135,8 @@ class InjectScanStep : public IRMutator {
   const std::unordered_map<IterVar, Range>& dom_map_;
   // whether it is init.
   bool is_init_;
+  // whether delete trivial loops with extent of 1
+  bool del_trivial_loop_;
 };
 
 // Postprocessing of schedule op
@@ -365,14 +372,14 @@ Stmt ScheduleOps(
 
     if (scan_init.count(s->op)) {
       CHECK(body.defined());
-      InjectScanStep mu(s, scan_init.at(s->op), dom_map, true);
+      InjectScanStep mu(s, scan_init.at(s->op), dom_map, true, del_trivial_loop);
       body = mu.Mutate(body);
       CHECK(mu.found_attach)
           << "did not find attachment point for scan.init";
     } else if (attach_spec->attach_type == kScanUpdate) {
       // Handle scan update
       CHECK(body.defined());
-      InjectScanStep mu(s, attach_spec->attach_stage->op, dom_map, false);
+      InjectScanStep mu(s, attach_spec->attach_stage->op, dom_map, false, del_trivial_loop);
       body = mu.Mutate(body);
       CHECK(mu.found_attach)
           << "did not find attachment point for scan.update";
@@ -384,7 +391,7 @@ Stmt ScheduleOps(
     } else {
       CHECK_EQ(attach_spec->attach_type, kScope);
       CHECK(body.defined());
-      InjectAttach mutator(s, attach_spec, dom_map);
+      InjectAttach mutator(s, attach_spec, dom_map, del_trivial_loop);
       body = mutator.Mutate(body);
       CHECK(mutator.found_attach)
           << "did not find attachment point for " << s << " in "

From ba143fb8a64c82a39d1965f59b92b527bf0a8901 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 9 Mar 2018 11:39:58 -0800
Subject: [PATCH 196/949] prevent aggressive unrolling in vthread (#983)

---
 src/pass/inject_virtual_thread.cc | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/pass/inject_virtual_thread.cc b/src/pass/inject_virtual_thread.cc
index c0e7b4b3ff9e..833513756053 100644
--- a/src/pass/inject_virtual_thread.cc
+++ b/src/pass/inject_virtual_thread.cc
@@ -418,7 +418,8 @@ class VTInjector : public IRMutator {
     // reset the flags after processing.
     vt_loop_injected_ = false;
     visit_touched_var_ = false;
-    if (max_loop_depth_ == 0) {
+    // only unroll if number of vthreads are small
+    if (max_loop_depth_ == 0 && num_threads_ < 16) {
       // do unrolling if it is inside innermost content.
       Stmt blk = Substitute(stmt, {{var_, make_zero(var_.type())}});
       for (int i = 1; i < num_threads_; ++i) {

From 7c29d0ad61bb55de4ee761c9af10699b679f6f15 Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Sat, 10 Mar 2018 23:10:26 +0530
Subject: [PATCH 197/949]     Bug fix for Android platforms
 (https://github.com/dmlc/tvm/pull/971) (#986)

---
 src/runtime/thread_pool.cc | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/runtime/thread_pool.cc b/src/runtime/thread_pool.cc
index bde16d8f56a8..74795f004811 100644
--- a/src/runtime/thread_pool.cc
+++ b/src/runtime/thread_pool.cc
@@ -355,6 +355,7 @@ class ThreadPool {
   // bind worker threads to disjoint cores
   void SetThreadAffinity() {
 #if defined(__ANDROID__)
+#ifndef CPU_SET
   #define CPU_SETSIZE 1024
   #define __NCPUBITS (8 * sizeof (uint64_t))
   typedef struct {
@@ -365,14 +366,19 @@ class ThreadPool {
     ((cpusetp)->__bits[(cpu)/__NCPUBITS] |= (1UL << ((cpu) % __NCPUBITS)))
   #define CPU_ZERO(cpusetp) \
     memset((cpusetp), 0, sizeof(cpu_set_t))
+#endif
 #endif
     for (int i=0; i < num_workers_; ++i) {
 #if defined(__linux__) || defined(__ANDROID__)
       cpu_set_t cpuset;
       CPU_ZERO(&cpuset);
       CPU_SET(i, &cpuset);
+#if defined(__ANDROID__)
+      sched_setaffinity(threads_[i].native_handle(), sizeof(cpu_set_t), &cpuset);
+#else
       pthread_setaffinity_np(threads_[i].native_handle(),
         sizeof(cpu_set_t), &cpuset);
+#endif
 #endif
     }
   }

From f5e6431f0d3a793158c61662a5f8a1ddbb0865ed Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 10 Mar 2018 09:40:52 -0800
Subject: [PATCH 198/949] [FFI] Fix global free destruction (#985)

---
 python/tvm/_ffi/_ctypes/function.py | 2 +-
 python/tvm/_ffi/_ctypes/node.py     | 3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/python/tvm/_ffi/_ctypes/function.py b/python/tvm/_ffi/_ctypes/function.py
index ec278bc2045c..189d9964baf5 100644
--- a/python/tvm/_ffi/_ctypes/function.py
+++ b/python/tvm/_ffi/_ctypes/function.py
@@ -165,7 +165,7 @@ def __init__(self, handle, is_global):
         self.is_global = is_global
 
     def __del__(self):
-        if not self.is_global:
+        if not self.is_global and _LIB is not None:
             check_call(_LIB.TVMFuncFree(self.handle))
 
     def __call__(self, *args):
diff --git a/python/tvm/_ffi/_ctypes/node.py b/python/tvm/_ffi/_ctypes/node.py
index 08efc3913084..cb32b83291d1 100644
--- a/python/tvm/_ffi/_ctypes/node.py
+++ b/python/tvm/_ffi/_ctypes/node.py
@@ -44,7 +44,8 @@ def __init__(self, handle):
         self.handle = handle
 
     def __del__(self):
-        check_call(_LIB.TVMNodeFree(self.handle))
+        if _LIB is not None:
+            check_call(_LIB.TVMNodeFree(self.handle))
 
     def __getattr__(self, name):
         ret_val = TVMValue()

From eb86aa8ec1016883309f104e8bc956af45cacda9 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 10 Mar 2018 14:25:45 -0800
Subject: [PATCH 199/949] [CYTHON] Correct backtrace print for python3 (#989)

---
 python/tvm/_ffi/_cython/base.pxi | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/python/tvm/_ffi/_cython/base.pxi b/python/tvm/_ffi/_cython/base.pxi
index 0bbcc7064960..b84391648f35 100644
--- a/python/tvm/_ffi/_cython/base.pxi
+++ b/python/tvm/_ffi/_cython/base.pxi
@@ -37,7 +37,7 @@ cdef extern from "tvm/runtime/c_runtime_api.h":
         DLDataType dtype
         int64_t* shape
         int64_t* strides
-        uint64_t byte_offset;
+        uint64_t byte_offset
 
     ctypedef struct TVMValue:
         int64_t v_int64
@@ -128,7 +128,7 @@ cdef inline c_str(pystr):
 
 cdef inline CALL(int ret):
     if ret != 0:
-        raise TVMError(TVMGetLastError())
+        raise TVMError(py_str(TVMGetLastError()))
 
 
 cdef inline object ctypes_handle(void* chandle):

From 6287a99b9c65ae3a2453047a69d5db73838bd932 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 10 Mar 2018 15:32:32 -0800
Subject: [PATCH 200/949] [RUNTIME] Update graph runtime to rely on smarter
 planner, add get_input (#990)

---
 python/tvm/contrib/graph_runtime.py | 15 +++++++++++++++
 src/runtime/graph/graph_runtime.cc  | 26 ++++++++++++++++++--------
 src/runtime/thread_pool.cc          |  2 +-
 3 files changed, 34 insertions(+), 9 deletions(-)

diff --git a/python/tvm/contrib/graph_runtime.py b/python/tvm/contrib/graph_runtime.py
index ddabac004993..a303895ceb1d 100644
--- a/python/tvm/contrib/graph_runtime.py
+++ b/python/tvm/contrib/graph_runtime.py
@@ -72,6 +72,7 @@ def __init__(self, module, ctx):
         self._set_input = module["set_input"]
         self._run = module["run"]
         self._get_output = module["get_output"]
+        self._get_input = module["get_input"]
         try:
             self._debug_get_output = module["debug_get_output"]
         except AttributeError:
@@ -111,6 +112,20 @@ def run(self, **input_dict):
             self.set_input(**input_dict)
         self._run()
 
+    def get_input(self, index, out):
+        """Get index-th input to out
+
+        Parameters
+        ----------
+        index : int
+            The input index
+
+        out : NDArray
+            The output array container
+        """
+        self._get_input(index, out)
+        return out
+
     def get_output(self, index, out):
         """Get index-th output to out
 
diff --git a/src/runtime/graph/graph_runtime.cc b/src/runtime/graph/graph_runtime.cc
index b692ab729566..892967b310e2 100644
--- a/src/runtime/graph/graph_runtime.cc
+++ b/src/runtime/graph/graph_runtime.cc
@@ -101,6 +101,16 @@ class GraphRuntime : public ModuleNode {
     uint32_t eid = this->entry_id(input_nodes_[index], 0);
     TVM_CCALL(TVMArrayCopyFromTo(data_in, &data_entry_[eid], nullptr));
   }
+  /*!
+   * \brief Copy index-th input to data_out
+   * \param index The input index.
+   * \param data_out The output
+   */
+  void GetInput(int index, DLTensor* data_out) {
+    CHECK_LT(static_cast<size_t>(index), input_nodes_.size());
+    uint32_t eid = this->entry_id(input_nodes_[index], 0);
+    TVM_CCALL(TVMArrayCopyFromTo(&data_entry_[eid], data_out, nullptr));
+  }
   /*!
    * \brief Copy index-th output to data_out.
    * \param index The output index.
@@ -463,14 +473,6 @@ void GraphRuntime::SetupStorage() {
     vtype.push_back(tvm::runtime::String2TVMType(s_type));
   }
   data_entry_.resize(num_node_entries());
-  // Find the maximum space size.
-  int max_id = 0;
-  for (size_t i = 0; i < attrs_.shape.size(); ++i) {
-    max_id = std::max(attrs_.storage_id[i] + 1, max_id);
-  }
-  for (uint32_t nid : input_nodes_) {
-    attrs_.storage_id[this->entry_id(nid, 0)] = max_id++;
-  }
   // size of each storage pool entry
   std::vector<size_t> pool_entry_bytes;
   // Find the maximum space size.
@@ -592,6 +594,14 @@ PackedFunc GraphRuntime::GetFunction(
     return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
         this->GetOutput(args[0], args[1]);
       });
+  } else if (name == "get_input") {
+    return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
+        if (args[0].type_code() == kStr) {
+          this->GetInput(this->GetInputIndex(args[0]), args[1]);
+        } else {
+          this->GetInput(args[0], args[1]);
+        }
+      });
 #ifdef TVM_GRAPH_RUNTIME_DEBUG
   } else if (name == "debug_get_output") {
     return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
diff --git a/src/runtime/thread_pool.cc b/src/runtime/thread_pool.cc
index 74795f004811..4e13fdd14151 100644
--- a/src/runtime/thread_pool.cc
+++ b/src/runtime/thread_pool.cc
@@ -204,7 +204,7 @@ class SpscTaskQueue {
     cv_.notify_all();
   }
 
- private:
+ protected:
   /*!
    * \brief Lock-free enqueue.
    * \param input The task to be enqueued.

From a333b4a538cc4028f7831893479d6129567db7bb Mon Sep 17 00:00:00 2001
From: Atsushi Nukariya <34906252+AtuNuka@users.noreply.github.com>
Date: Tue, 13 Mar 2018 13:59:56 +0900
Subject: [PATCH 201/949] [BUILD] Windows build with cuDNN support (#999)

---
 CMakeLists.txt | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index e072ced6fda1..eb52d1f82723 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -32,6 +32,8 @@ tvm_option(USE_RTTI "Build with RTTI" ON)
 tvm_option(USE_MSVC_MT "Build with MT" OFF)
 tvm_option(INSTALL_DEV "Install compiler infrastructure" OFF)
 
+tvm_option(USE_CUDNN "Build with cuDNN" OFF)
+
 include_directories("include")
 include_directories("HalideIR/src")
 include_directories("dlpack/include")
@@ -126,6 +128,24 @@ find_library(CUDA_NVRTC_LIBRARIES nvrtc
       ${CUDA_TOOLKIT_ROOT_DIR}/lib)
     list(APPEND TVM_LINKER_LIBS ${CUDA_NVRTC_LIB})
   endif(MSVC)
+
+  if(USE_CUDNN)
+    message(STATUS "Build with cuDNN support")
+    file(GLOB CONTRIB_CUDNN_SRCS src/contrib/cudnn/*.cc)
+    list(APPEND RUNTIME_SRCS ${CONTRIB_CUDNN_SRCS})
+    if(MSVC)
+      find_library(CUDA_CUDNN_LIB cudnn
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib/x64
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib/win32)
+      list(APPEND TVM_RUNTIME_LINKER_LIBS ${CUDA_CUDNN_LIB})
+    else(MSVC)
+      find_library(CUDA_CUDNN_LIB cudnn
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib64
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib)
+      list(APPEND TVM_RUNTIME_LINKER_LIBS ${CUDA_CUDNN_LIB})
+    endif(MSVC)
+  endif(USE_CUDNN)
+
   add_definitions(-DTVM_CUDA_RUNTIME=1)
 else(USE_CUDA)
   add_definitions(-DTVM_CUDA_RUNTIME=0)

From 71c2086c777cfa5424871a5cfc9ba38b018d924c Mon Sep 17 00:00:00 2001
From: Ding <37059654+dingobye@users.noreply.github.com>
Date: Wed, 14 Mar 2018 02:52:21 +1100
Subject: [PATCH 202/949] [PASS] Add VerifyMemory pass and test cases (#410)
 (#993)

---
 include/tvm/ir_pass.h                         |  14 ++
 python/tvm/build_module.py                    |   6 +-
 src/api/api_pass.cc                           |   1 +
 src/codegen/build_module.cc                   |   6 +-
 src/pass/verify_memory.cc                     | 168 ++++++++++++++++++
 .../unittest/test_pass_verify_memory.py       |  96 ++++++++++
 6 files changed, 289 insertions(+), 2 deletions(-)
 create mode 100644 src/pass/verify_memory.cc
 create mode 100644 tests/python/unittest/test_pass_verify_memory.py

diff --git a/include/tvm/ir_pass.h b/include/tvm/ir_pass.h
index d7032ffdb1ae..572385d9a895 100644
--- a/include/tvm/ir_pass.h
+++ b/include/tvm/ir_pass.h
@@ -440,6 +440,20 @@ LoweredFunc PointerValueTypeRewrite(LoweredFunc f);
  * \return Transformed function.
  */
 LoweredFunc LowerIntrin(LoweredFunc f, const std::string& target);
+
+/*!
+ * \brief Verify if memory accesses are legal for a specific target device type.
+ *
+ *  In the case that tgt is cuda, if not all workload is bound with
+ *  threads, CPU code is generated that tries to access GPU memory,
+ *  which is illegal. This pass performs verification for this case.
+ *
+ * \param func The function to be verified.
+ * \param device_type The target device type.
+ * \return Success of memory verification.
+ */
+bool VerifyMemory(LoweredFunc func, int device_type);
+
 }  // namespace ir
 }  // namespace tvm
 
diff --git a/python/tvm/build_module.py b/python/tvm/build_module.py
index 7b6fa7715a7b..03a79860e9ee 100755
--- a/python/tvm/build_module.py
+++ b/python/tvm/build_module.py
@@ -424,10 +424,15 @@ def build(sch,
 
     target = _target.current_target() if target is None else target
     target = _target.create(target) if target else _target.create("llvm")
+    device_type = ndarray.context(target.target_name, 0).device_type
 
     fhost = []
     fdevice = []
     for func in flist:
+        if not ir_pass.VerifyMemory(func, device_type):
+            raise ValueError(
+                "Direct host side access to device memory is detected in %s. "
+                "Did you forget to bind?" % func.name)
         if func.func_type == container.LoweredFunc.MixedFunc:
             if BuildConfig.current.detect_global_barrier:
                 func = ir_pass.ThreadSync(func, "global")
@@ -449,7 +454,6 @@ def build(sch,
         warnings.warn(
             "Specified target %s, but cannot find device code, did you do bind?" % target)
 
-    device_type = ndarray.context(target.target_name, 0).device_type
     fhost = [ir_pass.BindDeviceType(x, device_type) for x in fhost]
     fhost = [ir_pass.LowerTVMBuiltin(x) for x in fhost]
 
diff --git a/src/api/api_pass.cc b/src/api/api_pass.cc
index 06c6b621abde..7ec6ef4009e4 100644
--- a/src/api/api_pass.cc
+++ b/src/api/api_pass.cc
@@ -128,5 +128,6 @@ REGISTER_PASS2(LowerThreadAllreduce);
 REGISTER_PASS2(LowerIntrin);
 REGISTER_PASS1(LowerTVMBuiltin);
 REGISTER_PASS1(CombineContextCall);
+REGISTER_PASS2(VerifyMemory);
 }  // namespace ir
 }  // namespace tvm
diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc
index e7f0cd41bb7b..dc0971b2d4c3 100644
--- a/src/codegen/build_module.cc
+++ b/src/codegen/build_module.cc
@@ -269,7 +269,11 @@ runtime::Module build(const Array<LoweredFunc>& funcs,
   Array<LoweredFunc> fhost;
   Array<LoweredFunc> fdevice;
 
-  for (const auto &x : funcs) {
+  for (const auto& x : funcs) {
+    CHECK(ir::VerifyMemory(x, target.device_type))
+        << "Direct host side access to device memory is detected in " << x->func_name()
+        << ". Did you forget to bind?";
+
     if (x->func_type == kMixedFunc) {
       auto func = x;
       if (config->detect_global_barrier) {
diff --git a/src/pass/verify_memory.cc b/src/pass/verify_memory.cc
new file mode 100644
index 000000000000..e928bedf266c
--- /dev/null
+++ b/src/pass/verify_memory.cc
@@ -0,0 +1,168 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file verify_memory.cc
+ * \brief Pass to check if memory accesses are legal.
+ */
+#include <tvm/ir.h>
+#include <tvm/ir_visitor.h>
+#include <tvm/ir_pass.h>
+
+namespace tvm {
+namespace ir {
+namespace {
+
+/*!
+ * \brief Verify if memory accesses are legal.
+ *
+ *  In the case that tgt is cuda, if workload is not bound with
+ *  threads, CPU code is generated that tries to access GPU memory,
+ *  which is illegal.
+ *
+ *  This pass performs such verification by checking if all Producer/Consumer
+ *  with memory accesses are bound with threads when device type is GPU.
+ */
+class MemoryAccessVerifier final : protected IRVisitor {
+ public:
+  /// Special member functions
+  //@{
+  explicit MemoryAccessVerifier(LoweredFunc f, int device_type)
+      : func_(f), dev_type_(device_type) {}
+  virtual ~MemoryAccessVerifier() = default;
+  MemoryAccessVerifier(const MemoryAccessVerifier &) = delete;
+  MemoryAccessVerifier(MemoryAccessVerifier &&) = delete;
+  MemoryAccessVerifier &operator=(const MemoryAccessVerifier &) = delete;
+  MemoryAccessVerifier &operator=(MemoryAccessVerifier &&) = delete;
+  //@}
+
+  /// Interface to perform memory access verification
+  void Run() {
+    if (!IsGPUDevice(dev_type_)) return;
+    IRVisitor::Visit(func_->body);
+  }
+
+  /// Verification result
+  bool Failed() const { return failure_; }
+
+ protected:
+  /// Visitor implementation
+  //@{
+  void Visit(const NodeRef &n) final {
+    if (Failed()) return;
+    IRVisitor::Visit(n);
+  }
+
+  void Visit_(const LetStmt *op) final {
+    // Book keep definitions
+    defs_[op->var.get()] = op->value;
+    return IRVisitor::Visit_(op);
+  }
+
+  void Visit_(const AttrStmt *op) final {
+    if (!InThreadEnv() && (op->attr_key == attr::thread_extent ||
+                           op->attr_key == attr::pipeline_exec_scope)) {
+      EnterThreadEnv();
+      IRVisitor::Visit_(op);
+      ExitThreadEnv();
+    } else {
+      IRVisitor::Visit_(op);
+    }
+  }
+
+  void Visit_(const ProducerConsumer *op) final {
+    EnterProducerConsumer(op);
+    IRVisitor::Visit_(op);
+    ExitProducerConsumer();
+  }
+
+  void Visit_(const Load *op) final {
+    HandleLoadStoreToVariable(op->buffer_var);
+    return IRVisitor::Visit_(op);
+  }
+
+  void Visit_(const Store *op) final {
+    HandleLoadStoreToVariable(op->buffer_var);
+    return IRVisitor::Visit_(op);
+  }
+  //@}
+
+  /// Check if the value of a Variable comes from function argument.
+  bool IsFromFunctionArgs(const Variable *var) const {
+    const Variable *V = var;
+    while (true) {
+      CHECK(V) << "Invalid Variable\n";
+
+      // Variable is from function args. Return true.
+      if (V == func_->args[0].node_.get()) return true;
+
+      // The value is expected to come from a tvm_struct_get Call.
+      // Get the first argument of tvm_struct_get, and continue.
+      const auto &iter = defs_.find(V);
+      if (iter == defs_.end()) return false;
+      const Call *C = iter->second.as<const Call>();
+      if (!C || C->name != intrinsic::tvm_struct_get) return false;
+      V = C->args[0].as<Variable>();
+    }
+    return false;
+  }
+
+  /// Handle memory access to a Variable
+  void HandleLoadStoreToVariable(const VarExpr &var) {
+    // We skip the access within thread env.
+    if (InThreadEnv()) return;
+
+    // We only check access within a producer/consumer.
+    // Because for load/store out side of producer/consumer,
+    // they don't have to be in thread env to stay legal (e.g. Load of args).
+    if (!InProducerConsumer()) return;
+
+    // We only handle the variable from function argument.
+    // If it does not come from args, then it could be allocated internally,
+    // it may possibly be in host or device address space.
+    // We do not handle this case, and skip it conservatively.
+    if (!IsFromFunctionArgs(var.get())) return;
+
+    // The verification fails in this case.
+    SetFailure();
+  }
+
+  /// Status getter/setter
+  //@{
+  bool InThreadEnv() const { return in_thread_env_; }
+  void EnterThreadEnv() { in_thread_env_ = true; }
+  void ExitThreadEnv() { in_thread_env_ = false; }
+  bool InProducerConsumer() const { return pc_ != nullptr; }
+  const ProducerConsumer *GetCurrentProducerConsumer() const { return pc_; }
+  void EnterProducerConsumer(const ProducerConsumer *pc) { this->pc_ = pc; }
+  void ExitProducerConsumer() { pc_ = nullptr; }
+  void SetFailure() { failure_ = true; }
+  //@}
+
+  /// Check if a given DLDeviceType/TVMDeviceExtType value denotes GPU device.
+  static bool IsGPUDevice(int dev_type) {
+    return kDLGPU == dev_type || kDLOpenCL == dev_type ||
+           kDLVulkan == dev_type || kDLMetal == dev_type ||
+           kDLROCM == dev_type || kOpenGL == dev_type;
+  }
+
+ private:
+  /// Status of visitor
+  //@{
+  bool in_thread_env_{false};
+  const ProducerConsumer *pc_{nullptr};
+  bool failure_{false};  ///< If the verification fails (i.e. has illegal access)
+  //@}
+  LoweredFunc func_{nullptr};  ///< Function to be verified.
+  int dev_type_{kDLCPU};       ///< Device type
+  std::unordered_map<const Variable *, Expr> defs_;  ///< Variable definitions
+};
+}  // namespace
+
+/// Interface of VerifyMemory pass
+bool VerifyMemory(LoweredFunc func, int device_type) {
+  MemoryAccessVerifier v(func, device_type);
+  v.Run();
+  return !v.Failed();
+}
+
+}  // namespace ir
+}  // namespace tvm
diff --git a/tests/python/unittest/test_pass_verify_memory.py b/tests/python/unittest/test_pass_verify_memory.py
new file mode 100644
index 000000000000..d1f5d4326621
--- /dev/null
+++ b/tests/python/unittest/test_pass_verify_memory.py
@@ -0,0 +1,96 @@
+import tvm
+
+# The following DLDeviceType/TVMDeviceExtType values
+# are originally defined in dlpack.h and c_runtime_api.h.
+gpu_devices = [2, 4, 7, 8, 10, 11]
+other_devices = [1, 3, 9, 12]
+
+
+def lower(sch, args):
+    binds = {}
+    arg_list = []
+    for x in args:
+        if isinstance(x, tvm.tensor.Tensor):
+            buf = tvm.decl_buffer(x.shape, dtype=x.dtype, name=x.name)
+            assert x not in binds
+            binds[x] = buf
+            arg_list.append(buf)
+        else:
+            raise ValueError("args must be Tensor, Buffer or Var")
+    sch = sch.normalize()
+    bounds = tvm.schedule.InferBound(sch)
+    stmt = tvm.schedule.ScheduleOps(sch, bounds)
+    stmt = tvm.ir_pass.LoopPartition(stmt, False)
+    stmt = tvm.ir_pass.StorageFlatten(stmt, binds, 64)
+    func = tvm.ir_pass.MakeAPI(stmt, "myadd", arg_list, 0, True)
+    return func
+
+
+# All computations are bound. 
+# So VerifyMemory pass is expected to succeed.
+#
+def test_verify_memory_all_bind():
+  n = tvm.var("n")
+  A = tvm.placeholder((n,), name='A')
+  B = tvm.compute(A.shape, lambda i: A[i] + 1.0, name="B")
+
+  # B is bound to threads.
+  s = tvm.create_schedule(B.op)
+  bx, tx = s[B].split(B.op.axis[0], factor=64)
+  s[B].bind(bx, tvm.thread_axis("blockIdx.x"))
+  s[B].bind(tx, tvm.thread_axis("threadIdx.x"))
+
+  func = lower(s, [A, B])
+  
+  for dev_type in gpu_devices + other_devices:
+    assert tvm.ir_pass.VerifyMemory(func, dev_type)
+
+
+# Computations are not bound. 
+# So VerifyMemory pass fails when device type is GPU.
+#
+def test_verify_memory_not_bind():
+  n = tvm.var("n")
+  A = tvm.placeholder((n,), name='A')
+  B = tvm.compute(A.shape, lambda i: A[i] + 1.0, name="B")
+
+  # B is not bound to threads.
+  s = tvm.create_schedule(B.op)
+
+  func = lower(s, [A, B])  
+
+  for dev_type in gpu_devices:
+    assert not tvm.ir_pass.VerifyMemory(func, dev_type)
+  for dev_type in other_devices:
+    assert tvm.ir_pass.VerifyMemory(func, dev_type)
+
+
+# Computations are partially bound. 
+# So VerifyMemory pass fails when device type is GPU.
+#
+def test_verify_memory_partially_bind():
+  n = tvm.var("n")
+  A = tvm.placeholder((n,), name='A')
+  B = tvm.compute(A.shape, lambda i: A[i] + 1.0, name="B")
+  C = tvm.compute(B.shape, lambda i: B[i] + 2.0, name="C")
+  D = tvm.compute(C.shape, lambda i: C[i] + 2.0, name="D")
+
+  # C is bound to threads, but B and D are not.
+  s = tvm.create_schedule([B.op, C.op, D.op])
+  bx, tx = s[C].split(C.op.axis[0], factor=64)
+  s[C].bind(bx, tvm.thread_axis("blockIdx.x"))
+  s[C].bind(tx, tvm.thread_axis("threadIdx.x"))
+
+  func = lower(s, [A, B, C, D])  
+
+  for dev_type in gpu_devices:
+    assert not tvm.ir_pass.VerifyMemory(func, dev_type)
+  for dev_type in other_devices:
+    assert tvm.ir_pass.VerifyMemory(func, dev_type)
+
+
+if __name__ == "__main__":
+  test_verify_memory_all_bind()
+  test_verify_memory_not_bind()
+  test_verify_memory_partially_bind()
+  

From 1d6df5a187127f514ea48f6a8a74c77ff59c89f5 Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Wed, 14 Mar 2018 09:20:31 +0530
Subject: [PATCH 203/949] Updated documentation error (#1001)

---
 docs/how_to/install.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/how_to/install.md b/docs/how_to/install.md
index 54db42281623..6e81c0407550 100644
--- a/docs/how_to/install.md
+++ b/docs/how_to/install.md
@@ -58,7 +58,7 @@ The configuration of tvm can be modified by ```config.mk```
   any local modification will be ignored by git, then modify the according flags.
 - TVM optionally depends on LLVM. LLVM is required for CPU codegen that needs LLVM.
   - LLVM 4.0 or higher is needed for build with LLVM. Note that verison of LLVM from default apt may lower than 4.0.
-  - Since LLVM takes long time to build from source, you can download pre-built version of LLVM frorm
+  - Since LLVM takes long time to build from source, you can download pre-built version of LLVM from
     [LLVM Download Page](http://releases.llvm.org/download.html).
     - Unzip to a certain location, modify ```config.mk``` to add ```LLVM_CONFIG=/path/to/your/llvm/bin/llvm-config```
   - You can also use [LLVM Nightly Ubuntu Build](https://apt.llvm.org/)

From 19add5f9d8a969152cc4b5934d4074ecabdecbcf Mon Sep 17 00:00:00 2001
From: nhynes <nhynes@berkeley.edu>
Date: Thu, 15 Mar 2018 19:06:46 -0700
Subject: [PATCH 204/949] Pluggable Thread Launching Mechanism (#991)

---
 apps/howto_deploy/tvm_runtime_pack.cc   |   1 +
 apps/sgx/Makefile                       |   9 +-
 apps/sgx/app.cc                         |  21 +++--
 apps/sgx/enclave.cc                     |   6 +-
 apps/sgx/enclave_config.xml             |   6 +-
 apps/sgx/prepare_test_libs.py           |   2 +
 apps/sgx/test_addone.edl                |   6 +-
 apps/sgx/tvm_runtime_pack.cc            |   6 +-
 include/tvm/runtime/threading_backend.h |  65 +++++++++++++
 sgx/{sgx_runtime.cc => runtime_t.cc}    |  20 ++--
 sgx/runtime_u.cc                        |  34 +++++++
 sgx/threading_backend.cc                |  71 ++++++++++++++
 sgx/tvm.edl                             |  15 +++
 src/runtime/thread_pool.cc              | 120 ++++++------------------
 src/runtime/threading_backend.cc        | 113 ++++++++++++++++++++++
 15 files changed, 374 insertions(+), 121 deletions(-)
 create mode 100644 include/tvm/runtime/threading_backend.h
 rename sgx/{sgx_runtime.cc => runtime_t.cc} (52%)
 create mode 100644 sgx/runtime_u.cc
 create mode 100644 sgx/threading_backend.cc
 create mode 100644 sgx/tvm.edl
 create mode 100644 src/runtime/threading_backend.cc

diff --git a/apps/howto_deploy/tvm_runtime_pack.cc b/apps/howto_deploy/tvm_runtime_pack.cc
index 9a090d863729..445768128413 100644
--- a/apps/howto_deploy/tvm_runtime_pack.cc
+++ b/apps/howto_deploy/tvm_runtime_pack.cc
@@ -25,6 +25,7 @@
 #include "../../src/runtime/module.cc"
 #include "../../src/runtime/registry.cc"
 #include "../../src/runtime/file_util.cc"
+#include "../../src/runtime/threading_backend.cc"
 #include "../../src/runtime/thread_pool.cc"
 
 // NOTE: all the files after this are optional modules
diff --git a/apps/sgx/Makefile b/apps/sgx/Makefile
index 6a1eeb5b8b1e..fd1d0cc8f2d5 100644
--- a/apps/sgx/Makefile
+++ b/apps/sgx/Makefile
@@ -26,6 +26,7 @@ pkg_cflags := -std=c++11 -O2 -fPIC\
 	-I${TVM_ROOT}/dlpack/include\
 	-I.\
 	-DDMLC_LOG_STACK_TRACE=0\
+	-fmax-errors=4
 
 pkg_ldflags := -L${TVM_ROOT}/lib
 
@@ -40,7 +41,7 @@ enclave_cflags := -static -nostdinc\
 	-DDMLC_CXX11_THREAD_LOCAL=0\
 	$(enclave_include_paths)\
 
-enclave_cxxflags := -nostdinc++ $(enclave_cflags)
+enclave_cxxflags := -nostdinc++ $(enclave_cflags) -DTVM_SGX_MAX_CONCURRENCY=4
 
 enclave_ldflags :=\
 	-Wl,--no-undefined -nostdlib -nodefaultlibs -nostartfiles -L$(SGX_SDK)/lib64\
@@ -62,7 +63,7 @@ app_ldflags := -L$(SGX_SDK)/lib64\
 all: lib/test_addone.signed.so bin/test_addone
 
 # Build rule for all-in-one TVM package library
-lib/tvm_runtime_pack.o: tvm_runtime_pack.cc
+lib/tvm_runtime_pack.o: tvm_runtime_pack.cc lib/test_addone_t.o
 	@mkdir -p $(@D)
 	$(CXX) -c $< -o $@ $(pkg_cflags) $(pkg_ldflags) $(enclave_cxxflags) -g
 
@@ -94,7 +95,7 @@ lib/test_addone.signed.so: lib/test_addone.so enclave_config.xml
 # An app that runs the enclave
 bin/test_addone: app.cc lib/test_addone_u.o
 	@mkdir -p $(@D)
-	$(CXX) $^ -o $@ $(app_cflags) $(app_ldflags)
+	$(CXX) $^ -o $@ $(app_cflags) $(app_ldflags) $(pkg_cflags) -g
 
 # Debugging runtime pack built without SGX (c.f. howto_deploy/tvm_runtime_pack.cc)
 lib/tvm_runtime_pack_nosgx.o: tvm_runtime_pack.cc
@@ -104,7 +105,7 @@ lib/tvm_runtime_pack_nosgx.o: tvm_runtime_pack.cc
 # Debugging binary that runs TVM without SGX
 bin/addone_nosgx: enclave.cc lib/tvm_runtime_pack_nosgx.o lib/test_addone_sys.o
 	@mkdir -p $(@D)
-	$(CXX) $^ -o $@ $(pkg_cflags) $(pkg_ldflags) -g
+	$(CXX) $^ -o $@ $(pkg_cflags) $(pkg_ldflags) -g -lpthread
 
 clean:
 	rm -rf lib bin
diff --git a/apps/sgx/app.cc b/apps/sgx/app.cc
index 1516e8b4e925..d008bfb3777d 100644
--- a/apps/sgx/app.cc
+++ b/apps/sgx/app.cc
@@ -1,13 +1,15 @@
 #include <cstdio>
+#include <iostream>
 
 #include "sgx_urts.h"
 #include "sgx_eid.h"
 #include "test_addone_u.h"
+#include "../../sgx/runtime_u.cc"
 
 #define TOKEN_FILENAME   "bin/test_addone.token"
 #define ENCLAVE_FILENAME "lib/test_addone.signed.so"
 
-sgx_enclave_id_t global_eid = 0;  // global EID shared by multiple threads
+sgx_enclave_id_t tvm_sgx_eid;
 
 typedef struct _sgx_errlist_t {
   sgx_status_t err;
@@ -80,7 +82,7 @@ int initialize_enclave(void)
 
   /* Step 2: call sgx_create_enclave to initialize an enclave instance */
   /* Debug Support: set 2nd parameter to 1 */
-  sgx_status = sgx_create_enclave(ENCLAVE_FILENAME, SGX_DEBUG_FLAG, &token, &updated, &global_eid, NULL);
+  sgx_status = sgx_create_enclave(ENCLAVE_FILENAME, SGX_DEBUG_FLAG, &token, &updated, &tvm_sgx_eid, NULL);
   if (sgx_status != SGX_SUCCESS) {
     print_error_message(sgx_status);
     if (fp != NULL) fclose(fp);
@@ -105,7 +107,7 @@ int initialize_enclave(void)
 }
 
 int SGX_CDECL main(int argc, char *argv[]) {
-  if(initialize_enclave() < 0){
+  if(initialize_enclave() < 0) {
     printf("Failed to initialize enclave.\n");
     return -1;
   }
@@ -113,12 +115,13 @@ int SGX_CDECL main(int argc, char *argv[]) {
   /* Run TVM within the enclave */
   int addone_status;
   sgx_status_t sgx_status = SGX_ERROR_UNEXPECTED;
-  sgx_status = enclave_main(global_eid, &addone_status);
+  sgx_status = tvm_ecall_run_module(tvm_sgx_eid, nullptr, &addone_status);
   if (sgx_status != SGX_SUCCESS) {
     print_error_message(sgx_status);
   }
-
-  sgx_destroy_enclave(global_eid);
+  tvm_ecall_shutdown(tvm_sgx_eid);
+  tvm::runtime::sgx::Shutdown();
+  sgx_destroy_enclave(tvm_sgx_eid);
 
   if (addone_status == 1) {
     printf("It works!");
@@ -127,3 +130,9 @@ int SGX_CDECL main(int argc, char *argv[]) {
   printf("It doesn't work.");
   return -1;
 }
+
+extern "C" {
+void ocall_println(const char* str) {
+  std::cout << "Enclave says: " << str << std::endl;
+}
+}
diff --git a/apps/sgx/enclave.cc b/apps/sgx/enclave.cc
index 7588455543e9..d43107288ecb 100644
--- a/apps/sgx/enclave.cc
+++ b/apps/sgx/enclave.cc
@@ -6,6 +6,8 @@
 #include <iostream>
 #endif
 
+extern void Shutdown();
+
 /* This function mirrors the one in howto_deploy except without the iostream */
 int Verify(tvm::runtime::Module mod, std::string fname) {
   // Get the function from the module.
@@ -43,9 +45,9 @@ int Verify(tvm::runtime::Module mod, std::string fname) {
 
 
 extern "C" {
-int enclave_main() {
+void tvm_ecall_run_module(const void* tvm_args, void* tvm_return_value) {
   tvm::runtime::Module mod_syslib = (*tvm::runtime::Registry::Get("module._GetSystemLib"))();
-  return Verify(mod_syslib, "addonesys");
+  *(int*)tvm_return_value = Verify(mod_syslib, "addonesys");
 }
 }
 
diff --git a/apps/sgx/enclave_config.xml b/apps/sgx/enclave_config.xml
index d24da1882981..f7fc129d6e63 100644
--- a/apps/sgx/enclave_config.xml
+++ b/apps/sgx/enclave_config.xml
@@ -1,9 +1,9 @@
 <EnclaveConfiguration>
   <ProdID>0</ProdID>
   <ISVSVN>0</ISVSVN>
-  <StackMaxSize>0x2000</StackMaxSize>
-  <HeapMaxSize>0x1000</HeapMaxSize>
-  <TCSNum>1</TCSNum>
+  <StackMaxSize>0x100000</StackMaxSize>
+  <HeapMaxSize>0x100000</HeapMaxSize>
+  <TCSNum>5</TCSNum>
   <TCSPolicy>1</TCSPolicy>
   <DisableDebug>0</DisableDebug>
   <MiscSelect>0</MiscSelect>
diff --git a/apps/sgx/prepare_test_libs.py b/apps/sgx/prepare_test_libs.py
index 1fa9d74ef1c9..715880e61c31 100644
--- a/apps/sgx/prepare_test_libs.py
+++ b/apps/sgx/prepare_test_libs.py
@@ -11,6 +11,8 @@ def prepare_test_libs(base_path):
     A = tvm.placeholder((n,), name='A')
     B = tvm.compute(A.shape, lambda *i: A(*i) + 1, name='B')
     s = tvm.create_schedule(B.op)
+    s[B].parallel(s[B].op.axis[0])
+    print(tvm.lower(s, [A, B], simple_mode=True))
 
     # Compile library in system library mode
     fadd_syslib = tvm.build(s, [A, B], 'llvm --system-lib', name='addonesys')
diff --git a/apps/sgx/test_addone.edl b/apps/sgx/test_addone.edl
index 58341a727a6b..0127a581803a 100644
--- a/apps/sgx/test_addone.edl
+++ b/apps/sgx/test_addone.edl
@@ -1,7 +1,7 @@
 enclave {
-    from "sgx_tstdc.edl" import sgx_thread_wait_untrusted_event_ocall, sgx_thread_set_untrusted_event_ocall, sgx_thread_setwait_untrusted_events_ocall, sgx_thread_set_multiple_untrusted_events_ocall;
+    from "../../sgx/tvm.edl" import *;
 
-    trusted {
-        public int enclave_main();
+    untrusted {
+        void ocall_println([in, string] const char *str);
     };
 };
diff --git a/apps/sgx/tvm_runtime_pack.cc b/apps/sgx/tvm_runtime_pack.cc
index 709386b78931..0d88af03a65d 100644
--- a/apps/sgx/tvm_runtime_pack.cc
+++ b/apps/sgx/tvm_runtime_pack.cc
@@ -5,7 +5,11 @@
  *   Please refer to the Makefile (rule lib/tvm_runtime_pack.o) for how to build.
  *
  */
-#include "../../sgx/sgx_runtime.cc"
+#ifdef _LIBCPP_SGX_CONFIG
+#include "lib/test_addone_t.h"
+#endif
+#include "../../sgx/runtime_t.cc"
+
 #ifndef _LIBCPP_SGX_CONFIG
 #include "../../src/runtime/file_util.cc"
 #endif
diff --git a/include/tvm/runtime/threading_backend.h b/include/tvm/runtime/threading_backend.h
new file mode 100644
index 000000000000..6c8c4f5eb084
--- /dev/null
+++ b/include/tvm/runtime/threading_backend.h
@@ -0,0 +1,65 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file threading_backend.h
+ * \brief Utilities for manipulating thread pool threads.
+ */
+#ifndef TVM_RUNTIME_THREADING_BACKEND_H_
+#define TVM_RUNTIME_THREADING_BACKEND_H_
+
+#include <functional>
+#include <memory>
+#include <vector>
+
+namespace tvm {
+namespace runtime {
+namespace threading {
+
+/*!
+ * \brief A platform-agnostic abstraction for managing a collection of
+ *        thread pool threads.
+ */
+class ThreadGroup {
+ public:
+  class Impl;
+
+   /*!
+    * \brief Creates a collection of threads which run a provided function.
+    *
+    * \param num_workers The total number of worker threads in this group.
+             Includes main thread if `exclude_worker0 = true`
+    * \param worker_callback A callback which is run in its own thread.
+             Receives the worker_id as an argument.
+    * \param exclude_worker0 Whether to use the main thread as a worker.
+    *        If  `true`, worker0 will not be launched in a new thread and
+    *        `worker_callback` will only be called for values >= 1. This
+    *        allows use of the main thread as a worker.
+    */
+  ThreadGroup(int num_workers,
+              std::function<void(int)> worker_callback,
+              bool exclude_worker0 = false);
+  ~ThreadGroup();
+
+   /*!
+    * \brief Blocks until all non-main threads in the pool finish.
+    */
+  void Join();
+
+ private:
+  Impl* impl_;
+};
+
+/*!
+ * \brief Platform-agnostic no-op.
+ */
+void Yield();
+
+/*!
+ * \return the maximum number of effective workers for this system.
+ */
+int MaxConcurrency();
+
+}  // namespace threading
+}  // namespace runtime
+}  // namespace tvm
+
+#endif  // TVM_RUNTIME_THREADING_BACKEND_H_
diff --git a/sgx/sgx_runtime.cc b/sgx/runtime_t.cc
similarity index 52%
rename from sgx/sgx_runtime.cc
rename to sgx/runtime_t.cc
index 6a0d0dfb224c..5f280ffce8e4 100644
--- a/sgx/sgx_runtime.cc
+++ b/sgx/runtime_t.cc
@@ -9,17 +9,15 @@
 #include "../../src/runtime/module.cc"
 #include "../../src/runtime/registry.cc"
 #include "../../src/runtime/system_lib_module.cc"
+#ifndef _LIBCPP_SGX_CONFIG
+#include "../../src/runtime/threading_backend.cc"
+#else
+#include "threading_backend.cc"
+#endif
+#include "../../src/runtime/thread_pool.cc"
 
-// dummy parallel runtime (for now)
-int TVMBackendParallelLaunch(
-    FTVMParallelLambda flambda,
-    void* cdata,
-    int num_task) {
-  TVMParallelGroupEnv env = { nullptr /* sync_handle */, 1 /* num_task */ };
-  return flambda(0 /* task_id */, &env, cdata);
+extern "C" {
+void tvm_ecall_shutdown() {
+  tvm::runtime::ThreadPool::Global()->Shutdown();
 }
-
-int TVMBackendParallelBarrier(int task_id, TVMParallelGroupEnv* penv) {
-  return 0;
 }
-
diff --git a/sgx/runtime_u.cc b/sgx/runtime_u.cc
new file mode 100644
index 000000000000..0acccf614b40
--- /dev/null
+++ b/sgx/runtime_u.cc
@@ -0,0 +1,34 @@
+#include <tvm/runtime/threading_backend.h>
+#include "../../src/runtime/threading_backend.cc"
+#include <iostream>
+
+extern sgx_enclave_id_t tvm_sgx_eid;
+extern "C" {
+sgx_status_t tvm_ecall_run_worker(sgx_enclave_id_t eid, const void* cb);
+}
+
+namespace tvm {
+namespace runtime {
+namespace sgx {
+
+static std::unique_ptr<tvm::runtime::threading::ThreadGroup> sgx_thread_group;
+
+extern "C" {
+void tvm_ocall_thread_pool_launch(int num_tasks, void* cb) {
+  std::function<void(int)> runner = [cb](int _worker_id) {
+    sgx_status_t sgx_status = SGX_ERROR_UNEXPECTED;
+    sgx_status = tvm_ecall_run_worker(tvm_sgx_eid, cb);
+    CHECK(sgx_status == SGX_SUCCESS) << "SGX Error: " << sgx_status;
+  };
+  sgx_thread_group.reset(new tvm::runtime::threading::ThreadGroup(
+        num_tasks, runner, false /* include_main_thread */));
+}
+}
+
+void Shutdown() {
+  sgx_thread_group->Join();
+}
+
+}  // namespace sgx
+}  // namespace runtime
+}  // namespace tvm
diff --git a/sgx/threading_backend.cc b/sgx/threading_backend.cc
new file mode 100644
index 000000000000..7f820ab51581
--- /dev/null
+++ b/sgx/threading_backend.cc
@@ -0,0 +1,71 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file sgx/threading_backend.cc
+ * \brief SGX threading backend
+ */
+#include <tvm/runtime/threading_backend.h>
+#include <dmlc/logging.h>
+#include <sgx_edger8r.h>
+#include <sgx_trts.h>
+#include <atomic>
+
+extern "C" {
+sgx_status_t SGX_CDECL tvm_ocall_thread_pool_launch(int num_workers, void* cb);
+}
+
+#ifndef TVM_SGX_MAX_CONCURRENCY
+#define TVM_SGX_MAX_CONCURRENCY 1
+#endif
+
+namespace tvm {
+namespace runtime {
+namespace threading {
+
+class ThreadGroup::Impl {
+ public:
+  Impl(int num_workers, std::function<void(int)> worker_callback,
+       bool exclude_worker0)
+      : num_workers_(num_workers),
+        worker_callback_(worker_callback),
+        next_task_id_(exclude_worker0) {
+    CHECK(num_workers <= TVM_SGX_MAX_CONCURRENCY)
+      << "Tried spawning more threads than allowed by TVM_SGX_MAX_CONCURRENCY.";
+    sgx_status_t sgx_status = SGX_ERROR_UNEXPECTED;
+    sgx_status = tvm_ocall_thread_pool_launch(num_workers, this);
+    CHECK(sgx_status == SGX_SUCCESS) << "SGX Error: " << sgx_status;
+  }
+
+  void RunTask() {
+    int task_id = next_task_id_++;
+    CHECK(task_id < num_workers_)
+      << "More workers entered enclave than allowed by TVM_SGX_MAX_CONCURRENCY";
+    worker_callback_(task_id);
+  }
+
+ private:
+  int num_workers_;
+  std::function<void(int)> worker_callback_;
+  std::atomic<int> next_task_id_;
+};
+
+ThreadGroup::ThreadGroup(int num_workers,
+                         std::function<void(int)> worker_callback,
+                         bool exclude_worker0)
+  : impl_(new ThreadGroup::Impl(num_workers, worker_callback, exclude_worker0)) {}
+void ThreadGroup::Join() {}
+ThreadGroup::~ThreadGroup() { delete impl_; }
+
+void Yield() {}
+
+int MaxConcurrency() { return TVM_SGX_MAX_CONCURRENCY; }
+
+extern "C" {
+void tvm_ecall_run_worker(const void* impl) {
+  if (!sgx_is_within_enclave(impl, sizeof(ThreadGroup::Impl))) return;
+  ((ThreadGroup::Impl*)impl)->RunTask();
+}
+}
+
+}  // namespace threading
+}  // namespace runtime
+}  // namespace tvm
diff --git a/sgx/tvm.edl b/sgx/tvm.edl
new file mode 100644
index 000000000000..e88ac0ac7f9c
--- /dev/null
+++ b/sgx/tvm.edl
@@ -0,0 +1,15 @@
+enclave {
+    from "sgx_tstdc.edl" import *;
+
+    trusted {
+        public void tvm_ecall_run_module([user_check] const void* tvm_args,
+                                         [user_check] void* tvm_ret_value);
+        public void tvm_ecall_run_worker([user_check] const void* cb);
+        public void tvm_ecall_shutdown();
+    };
+
+    untrusted {
+        void tvm_ocall_thread_pool_launch(int num_workers, [user_check] void* cb);
+    };
+};
+
diff --git a/src/runtime/thread_pool.cc b/src/runtime/thread_pool.cc
index 4e13fdd14151..ac42a0c032c5 100644
--- a/src/runtime/thread_pool.cc
+++ b/src/runtime/thread_pool.cc
@@ -5,6 +5,7 @@
  */
 #include <tvm/runtime/c_runtime_api.h>
 #include <tvm/runtime/c_backend_api.h>
+#include <tvm/runtime/threading_backend.h>
 #include <dmlc/thread_local.h>
 #include <dmlc/logging.h>
 #include <thread>
@@ -17,9 +18,6 @@
 #include <cstring>
 #include <memory>
 #include <sstream>
-#if defined(__linux__)
-#include <sched.h>
-#endif
 
 const constexpr int kL1CacheBytes = 64;
 
@@ -73,14 +71,14 @@ class ParallelLauncher {
         return num_pending_ == 0;
       });
     if (!has_error_) return 0;
-    std::ostringstream os;
+    std::string err("");
     for (size_t i = 0; i < par_errors_.size(); ++i) {
       if (par_errors_[i].length() != 0) {
-        os << "Task " << i << " error: " << par_errors_[i] << '\n';
+        err += "Task " + std::to_string(i) + " error: " + par_errors_[i] + '\n';
         par_errors_[i].clear();
       }
     }
-    TVMAPISetLastError(os.str().c_str());
+    TVMAPISetLastError(err.c_str());
     return -1;
   }
   // Signal that one job has finished.
@@ -157,7 +155,7 @@ class SpscTaskQueue {
    */
   void Push(const Task& input) {
     while (!Enqueue(input)) {
-      std::this_thread::yield();
+      tvm::runtime::threading::Yield();
     }
     if (pending_.fetch_add(1) == -1) {
       std::unique_lock<std::mutex> lock(mutex_);
@@ -176,8 +174,8 @@ class SpscTaskQueue {
     // If a new task comes to the queue quickly, this wait avoid the worker from sleeping.
     // The default spin count is set by following the typical omp convention
     for (uint32_t i = 0; i < spin_count && pending_.load() == 0; ++i) {
-        std::this_thread::yield();
-      }
+      tvm::runtime::threading::Yield();
+    }
     if (pending_.fetch_sub(1) == 0) {
       std::unique_lock<std::mutex> lock(mutex_);
       cv_.wait(lock, [this] {
@@ -211,6 +209,8 @@ class SpscTaskQueue {
    * \return Whether the task is enqueued.
    */
   bool Enqueue(const Task& input) {
+    if (exit_now_.load(std::memory_order_relaxed)) return false;
+
     const uint32_t tail = tail_.load(std::memory_order_relaxed);
 
     if ((tail + 1) % kRingSize != (head_.load(std::memory_order_acquire))) {
@@ -255,32 +255,17 @@ class SpscTaskQueue {
 // The thread pool
 class ThreadPool {
  public:
-  ThreadPool() {
-    const char *val = getenv("TVM_NUM_THREADS");
-    if (val == nullptr) {
-      val = getenv("OMP_NUM_THREADS");
-    }
-    if (val != nullptr) {
-      num_workers_ = atoi(val);
-    } else {
-#if defined(_M_X64) || defined(__x86_64__)
-      // Half to not count hyper threading.
-      num_workers_ = std::thread::hardware_concurrency() / 2;
-#else
-      num_workers_ = std::thread::hardware_concurrency();
-#endif
-    }
-    num_workers_ = std::max(num_workers_, 1);
-    this->Init();
-  }
-  ~ThreadPool() {
-    for (std::unique_ptr<SpscTaskQueue>& q : queues_) {
-      q->SignalForKill();
-    }
-    for (std::thread& t : threads_) {
-      t.join();
+  ThreadPool(): num_workers_(tvm::runtime::threading::MaxConcurrency()) {
+    for (int i = 0; i < num_workers_; ++i) {
+      // The SpscTaskQueue only host ONE item at a time
+      queues_.emplace_back(std::unique_ptr<SpscTaskQueue>(new SpscTaskQueue()));
     }
+    threads_ = std::unique_ptr<tvm::runtime::threading::ThreadGroup>(
+        new tvm::runtime::threading::ThreadGroup(
+          num_workers_, [this](int worker_id) { this->RunWorker(worker_id); },
+          false /* include_main_thread */));
   }
+  ~ThreadPool() { Shutdown(); }
   int Launch(FTVMParallelLambda flambda,
              void* cdata,
              int num_task,
@@ -307,38 +292,22 @@ class ThreadPool {
     return res;
   }
 
+  void Shutdown() {
+    for (std::unique_ptr<SpscTaskQueue>& q : queues_) {
+      q->SignalForKill();
+    }
+    threads_.reset();
+  }
+
   static ThreadPool* Global() {
     static ThreadPool inst;
     return &inst;
   }
 
  private:
-  // Initialize the pool.
-  void Init() {
-    for (int i = 0; i < num_workers_; ++i) {
-      // The SpscTaskQueue only host ONE item at a time
-      queues_.emplace_back(
-          std::unique_ptr<SpscTaskQueue>(new SpscTaskQueue()));
-    }
-    threads_.resize(num_workers_);
-    for (int i = 0; i < num_workers_; ++i) {
-      threads_[i] = std::thread([this, i] {
-          this->RunWorker(queues_[i].get());
-        });
-    }
-    const char *val = getenv("TVM_BIND_THREADS");
-    if (val == nullptr || atoi(val) == 1) {
-      if (num_workers_ <= std::thread::hardware_concurrency()) {
-        SetThreadAffinity();
-      } else {
-        LOG(WARNING)
-          << "The thread affinity cannot be set when the number of workers is larger "
-          << "than the number of available cores in the system.";
-      }
-    }
-  }
   // Internal worker function.
-  void RunWorker(SpscTaskQueue* queue) {
+  void RunWorker(int worker_id) {
+    SpscTaskQueue* queue = queues_[worker_id].get();
     SpscTaskQueue::Task task;
     ParallelLauncher::ThreadLocal()->is_worker = true;
     while (queue->Pop(&task)) {
@@ -352,40 +321,9 @@ class ThreadPool {
       }
     }
   }
-  // bind worker threads to disjoint cores
-  void SetThreadAffinity() {
-#if defined(__ANDROID__)
-#ifndef CPU_SET
-  #define CPU_SETSIZE 1024
-  #define __NCPUBITS (8 * sizeof (uint64_t))
-  typedef struct {
-    uint64_t __bits[CPU_SETSIZE / __NCPUBITS];
-  } cpu_set_t;
-
-  #define CPU_SET(cpu, cpusetp) \
-    ((cpusetp)->__bits[(cpu)/__NCPUBITS] |= (1UL << ((cpu) % __NCPUBITS)))
-  #define CPU_ZERO(cpusetp) \
-    memset((cpusetp), 0, sizeof(cpu_set_t))
-#endif
-#endif
-    for (int i=0; i < num_workers_; ++i) {
-#if defined(__linux__) || defined(__ANDROID__)
-      cpu_set_t cpuset;
-      CPU_ZERO(&cpuset);
-      CPU_SET(i, &cpuset);
-#if defined(__ANDROID__)
-      sched_setaffinity(threads_[i].native_handle(), sizeof(cpu_set_t), &cpuset);
-#else
-      pthread_setaffinity_np(threads_[i].native_handle(),
-        sizeof(cpu_set_t), &cpuset);
-#endif
-#endif
-    }
-  }
-  // Number of workers
   int num_workers_;
   std::vector<std::unique_ptr<SpscTaskQueue> > queues_;
-  std::vector<std::thread> threads_;
+  std::unique_ptr<tvm::runtime::threading::ThreadGroup> threads_;
 };
 
 }  // namespace runtime
@@ -411,7 +349,7 @@ int TVMBackendParallelBarrier(int task_id, TVMParallelGroupEnv* penv) {
     if (i != task_id) {
       while (sync_counter[i * kSyncStride].load(
                  std::memory_order_relaxed) <= old_counter) {
-        std::this_thread::yield();
+        tvm::runtime::threading::Yield();
       }
     }
   }
diff --git a/src/runtime/threading_backend.cc b/src/runtime/threading_backend.cc
new file mode 100644
index 000000000000..19ba9bf2d776
--- /dev/null
+++ b/src/runtime/threading_backend.cc
@@ -0,0 +1,113 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file threading_backend.cc
+ * \brief Native threading backend
+ */
+#include <tvm/runtime/threading_backend.h>
+#include <dmlc/logging.h>
+#include <thread>
+#if defined(__linux__)
+#include <sched.h>
+#endif
+
+namespace tvm {
+namespace runtime {
+namespace threading {
+
+class ThreadGroup::Impl {
+ public:
+  Impl(int num_workers,
+       std::function<void(int)> worker_callback,
+       bool exclude_worker0)
+      : num_workers_(num_workers) {
+    CHECK_GE(num_workers, 1)
+      << "Requested a non-positive number of worker threads.";
+    for (int i = exclude_worker0; i < num_workers_; ++i) {
+      threads_.emplace_back([worker_callback, i] { worker_callback(i); });
+    }
+    const char *val = getenv("TVM_BIND_THREADS");
+    if (val == nullptr || atoi(val) == 1) {
+      if (num_workers_ <= std::thread::hardware_concurrency()) {
+        SetAffinity();
+      } else {
+        LOG(WARNING)
+          << "The thread affinity cannot be set when the number of workers"
+          << "is larger than the number of available cores in the system.";
+      }
+    }
+  }
+  ~Impl() { Join(); }
+
+  void Join() {
+    for (auto& t : threads_) {
+      if (t.joinable()) t.join();
+    }
+  }
+
+ private:
+  // bind worker threads to disjoint cores
+  void SetAffinity() {
+#if defined(__ANDROID__)
+#ifndef CPU_SET
+#define CPU_SETSIZE 1024
+#define __NCPUBITS (8 * sizeof (uint64_t))
+    typedef struct {
+      uint64_t __bits[CPU_SETSIZE / __NCPUBITS];
+    } cpu_set_t;
+
+#define CPU_SET(cpu, cpusetp) \
+    ((cpusetp)->__bits[(cpu)/__NCPUBITS] |= (1UL << ((cpu) % __NCPUBITS)))
+#define CPU_ZERO(cpusetp) \
+    memset((cpusetp), 0, sizeof(cpu_set_t))
+#endif
+#endif
+    for (unsigned i=0; i < threads_.size(); ++i) {
+#if defined(__linux__) || defined(__ANDROID__)
+      cpu_set_t cpuset;
+      CPU_ZERO(&cpuset);
+      CPU_SET(i, &cpuset);
+#if defined(__ANDROID__)
+      sched_setaffinity(threads_[i].native_handle(), sizeof(cpu_set_t), &cpuset);
+#else
+      pthread_setaffinity_np(threads_[i].native_handle(),
+          sizeof(cpu_set_t), &cpuset);
+#endif
+#endif
+    }
+  }
+
+  int num_workers_;
+  std::vector<std::thread> threads_;
+};
+
+ThreadGroup::ThreadGroup(int num_workers,
+                         std::function<void(int)> worker_callback,
+                         bool exclude_worker0)
+  : impl_(new ThreadGroup::Impl(num_workers, worker_callback, exclude_worker0)) {}
+ThreadGroup::~ThreadGroup() { delete impl_; }
+void ThreadGroup::Join() { impl_->Join(); }
+
+void Yield() {
+  std::this_thread::yield();
+}
+
+int MaxConcurrency() {
+  int max_concurrency = 1;
+  const char *val = getenv("TVM_NUM_THREADS");
+  if (val == nullptr) {
+    val = getenv("OMP_NUM_THREADS");
+  }
+  if (val != nullptr) {
+    max_concurrency = atoi(val);
+  } else {
+    max_concurrency = std::thread::hardware_concurrency();
+#if defined(_M_X64) || defined(__x86_64__)
+    max_concurrency /= 2;  // ignore hyper-threading
+#endif
+  }
+  return std::max(max_concurrency, 1);
+}
+
+}  // namespace threading
+}  // namespace runtime
+}  // namespace tvm

From df66a8581794d3ead389b929028160df7fea60af Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 15 Mar 2018 19:16:45 -0700
Subject: [PATCH 205/949] [CONTRIB] windows compatiblity (#1009)

---
 python/tvm/contrib/util.py | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/python/tvm/contrib/util.py b/python/tvm/contrib/util.py
index 338567d6f619..e2b4011c4c0a 100644
--- a/python/tvm/contrib/util.py
+++ b/python/tvm/contrib/util.py
@@ -2,8 +2,12 @@
 from __future__ import absolute_import as _abs
 import os
 import tempfile
-import fcntl
 import shutil
+try:
+    import fcntl
+except ImportError:
+    fcntl = None
+
 
 class TempDirectory(object):
     """Helper object to manage temp directory during testing.
@@ -70,13 +74,15 @@ class FileLock(object):
     """
     def __init__(self, path):
         self.lock_file = open(path, "w")
-        fcntl.lockf(self.lock_file, fcntl.LOCK_EX)
+        if fcntl:
+            fcntl.lockf(self.lock_file, fcntl.LOCK_EX)
 
 
     def release(self):
         """Release the lock"""
         if self.lock_file:
-            fcntl.lockf(self.lock_file, fcntl.LOCK_UN)
+            if fcntl:
+                fcntl.lockf(self.lock_file, fcntl.LOCK_UN)
             self.lock_file.close()
             self.lock_file = None
 

From eafd0c00a3c940219e4c7ae927ab64e432fea32b Mon Sep 17 00:00:00 2001
From: Ding <37059654+dingobye@users.noreply.github.com>
Date: Fri, 16 Mar 2018 13:30:20 +1100
Subject: [PATCH 206/949] [LANGUAGE] Verify Compute with respect to Reduce
 operations (#1006)

---
 src/op/compute_op.cc                          | 94 ++++++++++++++++---
 .../unittest/test_lang_verify_compute.py      | 64 +++++++++++++
 2 files changed, 143 insertions(+), 15 deletions(-)
 create mode 100644 tests/python/unittest/test_lang_verify_compute.py

diff --git a/src/op/compute_op.cc b/src/op/compute_op.cc
index 8b8bfbfe602e..f3f8335c195c 100644
--- a/src/op/compute_op.cc
+++ b/src/op/compute_op.cc
@@ -24,6 +24,9 @@ TVM_STATIC_IR_FUNCTOR(IRPrinter, vtable)
 
 TVM_REGISTER_NODE_TYPE(ComputeOpNode);
 
+/// Verify if ComputeOp is valid with respect to Reduce operations.
+static void VerifyComputeOp(const ComputeOpNode *op);
+
 inline bool ReduceEqual(const ir::Reduce* a, const ir::Reduce* b) {
   return (a->combiner.same_as(b->combiner)) &&
          (a->source.same_as(b->source)) &&
@@ -116,15 +119,9 @@ Operation ComputeOpNode::make(std::string name,
   n->body = body;
   if (n->body[0]->is_type<ir::Reduce>()) {
     const ir::Reduce* reduce = n->body[0].as<ir::Reduce>();
-    for (size_t i = 1; i < n->body.size(); ++i) {
-      const ir::Reduce* reduce_ = n->body[i].as<ir::Reduce>();
-      CHECK(reduce_);
-      CHECK(ReduceEqual(reduce_, reduce))
-        << "The Reduce inputs of ComputeOp should "
-        << "have the same attribute except value_index";
-    }
     n->reduce_axis = reduce->axis;
   }
+  VerifyComputeOp(n.get());
   return Operation(n);
 }
 
@@ -151,18 +148,11 @@ Operation ComputeOpNode::ReplaceInputs(
     const Operation& self,
     const std::unordered_map<Tensor, Tensor>& rmap) const {
   CHECK_EQ(self.operator->(), this);
+  VerifyComputeOp(this);
   Array<Expr> arr;
   if (this->body[0]->is_type<ir::Reduce>()) {
     // Specially handle reduce so the replaced op
     // still share all the components
-    const ir::Reduce* reduce = this->body[0].as<ir::Reduce>();
-    for (size_t i = 1; i < this->body.size(); ++i) {
-      const ir::Reduce* reduce_ = this->body[i].as<ir::Reduce>();
-      CHECK(reduce_);
-      CHECK(ReduceEqual(reduce_, reduce))
-        << "The Reduce inputs of ComputeOp should "
-        << "have the same attribute except value_index";
-    }\
     Expr new_reduce = op::ReplaceTensor(this->body[0], rmap);
     if (!new_reduce.same_as(this->body[0])) {
       const ir::Reduce* r = new_reduce.as<ir::Reduce>();
@@ -466,4 +456,78 @@ ComputeLoopNest ComputeLoopNest::make(
   // copy elison here.
   return ret;
 }
+
+namespace {
+/*!
+ * \brief Verify if ComputeOp is valid with respect to Reduce operations.
+ *
+ *  The following two properties are verified:
+ *  (1) All Reduce operations must exist at top level.
+ *  (2) For a list of operations, if one is Reduce, then the others
+ *      must be Reduce as well; and their inputs should have the
+ *      same attribute except value_index.
+ */
+class ComputeVerifier final : protected ir::IRVisitor {
+ public:
+  /// Special member functions
+  //@{
+  explicit ComputeVerifier(const ComputeOpNode* compute)
+      : compute_(compute), reduce_(compute->body[0].as<ir::Reduce>()) {}
+  virtual ~ComputeVerifier() = default;
+  ComputeVerifier(const ComputeVerifier&) = delete;
+  ComputeVerifier(ComputeVerifier&&) = delete;
+  ComputeVerifier& operator=(const ComputeVerifier&) = delete;
+  ComputeVerifier& operator=(ComputeVerifier&&) = delete;
+  //@}
+
+  /// Interface to perform compute verification
+  void Run() {
+    for (const Expr e : compute_->body) {
+      // Check for consistency of top level reductions
+      const ir::Reduce* reduce = e.as<ir::Reduce>();
+      CHECK((reduce && reduce_) || (!reduce && !reduce_))
+          << "All ComputeOp should be consistent "
+          << "with being Reduce operation or not.";
+
+      if (reduce && reduce_) {
+        CHECK(ReduceEqual(reduce, reduce_))
+            << "The Reduce inputs of ComputeOp should "
+            << "have the same attribute except value_index";
+      }
+
+      level_ = 0;
+      ir::IRVisitor::Visit(e);
+    }
+  }
+
+ protected:
+  /// Visitor implementation
+  //@{
+  void Visit(const NodeRef& n) final {
+    ++level_;
+    ir::IRVisitor::Visit(n);
+    --level_;
+  }
+
+  void Visit_(const ir::Reduce* op) final {
+    // Check for non top level reductions
+    CHECK(0 == level_)
+        << "Reductions are only allowed at the top level of compute. "
+        << "Please create another tensor for further composition.";
+  }
+  //@}
+
+ private:
+  const ComputeOpNode* compute_{nullptr};  ///< ComputeOpNode to verify
+  const ir::Reduce* reduce_{nullptr};      ///< Top level Reduce operation
+  int level_{0};                           ///< Level of op being processed
+};
+}  // namespace
+
+/// Verify if ComputeOp is valid with respect to Reduce operations.
+static void VerifyComputeOp(const ComputeOpNode* op) {
+  ComputeVerifier v(op);
+  v.Run();
+}
+
 }  // namespace tvm
diff --git a/tests/python/unittest/test_lang_verify_compute.py b/tests/python/unittest/test_lang_verify_compute.py
new file mode 100644
index 000000000000..1b9ecf453267
--- /dev/null
+++ b/tests/python/unittest/test_lang_verify_compute.py
@@ -0,0 +1,64 @@
+import tvm
+
+def test_verify_compute():
+  n = tvm.var("n")
+  m = tvm.var("m")
+  A = tvm.placeholder((n, m), name='A')
+  k = tvm.reduce_axis((0, m), "k")
+  k_ = tvm.reduce_axis((0, m-1), "k_")
+  f1 = lambda i: tvm.sum(A[i, k], axis=k)
+  f2 = lambda i: A[i,0] + 1
+  f3 = lambda i: tvm.sum(A[i, k], axis=k) + 1
+  f4 = lambda i: A[i,0] * (tvm.sum(A[i, k], axis=k) + 1)
+  f5 = lambda i: (tvm.sum(A[i, k], axis=k), A[i,0] + 1)
+  f6 = lambda i: (tvm.sum(A[i, k], axis=k), tvm.sum(A[i, k_], axis=k_))
+
+  #
+  # Valid compute
+  try:
+    B = tvm.compute((n,), f1, name="B")
+  except tvm._ffi.base.TVMError as ex:
+    assert False
+
+  #
+  # Valid compute
+  try:
+    B = tvm.compute((n,), f2, name="B")
+  except tvm._ffi.base.TVMError as ex:
+    assert False
+
+  #
+  # Invalid compute with non top level reduction
+  try:
+    B = tvm.compute((n,), f3, name="B")
+    assert False
+  except tvm._ffi.base.TVMError as ex:
+    pass
+
+  #
+  # Invalid compute with non top level reduction
+  try:
+    B = tvm.compute((n,), f4, name="B")
+    assert False
+  except tvm._ffi.base.TVMError as ex:
+    pass
+
+  #
+  # Invalid compute with reduction and non-reduction batch ops
+  try:
+    B0, B1 = tvm.compute((n,), f5, name="B")
+    assert False
+  except tvm._ffi.base.TVMError as ex:
+    pass
+
+  #
+  # Invalid compute with unequal batch reduction ops
+  try:
+    B0, B1 = tvm.compute((n,), f6, name="B")
+    assert False
+  except tvm._ffi.base.TVMError as ex:
+    pass
+
+
+if __name__ == "__main__":
+  test_verify_compute()
\ No newline at end of file

From 3f5d090948613ee8cc55a14b670d28d044728d97 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 15 Mar 2018 19:50:55 -0700
Subject: [PATCH 207/949] Update contributor guide (#1010)

---
 CONTRIBUTORS.md | 29 ++++++++++++++++++++++++-----
 1 file changed, 24 insertions(+), 5 deletions(-)

diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 7a7f52b5ee82..e261aebd7245 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -1,14 +1,32 @@
 Contributors of TVM
 ===================
-TVM adopts Apache style committer model. The package is developed and used by the community.
+TVM adopts Apache style committer model.
+The package is developed and used by the community.
+
+We actively seek committers that come from community contributors who:
+- Made substantial contributions to the project.
+  - All forms of contributions are valued (see detail in next section).
+- Willing to spend time on maintaining and lead the project.
+
+Contributions
+-------------
+We value all forms of contributions, here is a non-comprehensive
+list of contributions that are welcomed
+
+- Documentation and usage examples
+- Community participation, answering questions and issues.
+- Code readability and developer guide
+  - We welcome contributions that add code comments
+    to improve readability
+  - We also welcome contributions to docs to explain the
+    design choices of the internal.
+- Test cases to make the codebase more robust
+- Tutorials, blog posts, talks that promote the project.
 
-We actively seek committers that comes from contributors who:
-- Made substantial contribution to the project.
-- Willing to spent time on maintaining and lead the project.
 
 How to Contribute
 -----------------
-See [Contributor guide](docs/how_to/contribute.md) on how to contribute
+See [Contributor guide](docs/how_to/contribute.md) on how to contribute.
 
 Committers
 ----------
@@ -28,6 +46,7 @@ and are qualified to lead development and review changes of the owned module.
 - [Yuwei Hu](https://github.com/Huyuwei) TOPI
 - [Yizhi Liu](https://github.com/yzhliu) JVM package
 
+
 List of Contributors
 --------------------
 - [Full List of Contributors](https://github.com/dmlc/tvm/graphs/contributors)

From 8f883d88335a8340d9d4ab5f7f0c3691f5a5875c Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 15 Mar 2018 21:22:29 -0700
Subject: [PATCH 208/949] [PASS] Fix reuse small buffer in storage rewrite
 (#1012)

---
 src/pass/storage_rewrite.cc                   |  1 +
 .../unittest/test_pass_storage_rewrite.py     | 48 +++++++++++++++----
 2 files changed, 41 insertions(+), 8 deletions(-)

diff --git a/src/pass/storage_rewrite.cc b/src/pass/storage_rewrite.cc
index ac3ca2561e01..0a8782366193 100644
--- a/src/pass/storage_rewrite.cc
+++ b/src/pass/storage_rewrite.cc
@@ -824,6 +824,7 @@ class StoragePlanRewriter : public IRMutator {
         if (e->attach_scope_ != attach_scope) continue;
         if (e->scope != scope) continue;
         if (e->elem_type != op->type.element_of()) continue;
+        e->const_nbits = std::max(const_nbits, e->const_nbits);
         const_free_map_.erase(it);
         return e;
       }
diff --git a/tests/python/unittest/test_pass_storage_rewrite.py b/tests/python/unittest/test_pass_storage_rewrite.py
index 9613a61b9b39..994db4ce5f15 100644
--- a/tests/python/unittest/test_pass_storage_rewrite.py
+++ b/tests/python/unittest/test_pass_storage_rewrite.py
@@ -76,7 +76,7 @@ def stmt_generater(dtype_list, length):
             E = ib.allocate(dtype, length, name="E", scope="local.L0A")
             E[j] = A[j].astype(dtype) + B[j].astype(dtype) + C[j].astype(dtype) + D[j].astype(dtype)
         return ib.get()
-    
+
     def dtype_bit_len(dtype):
         index = 0
         for i in dtype:
@@ -109,7 +109,7 @@ def verify(n):
 
     dtype_list = ["float64", "int32", "uint16", "int8"]
     dtype_test(dtype_list, length)
-    
+
     dtype_list = ["int8", "int32", "uint16", "uint8"]
     dtype_test(dtype_list, length)
 
@@ -313,7 +313,7 @@ def mem_info_inp_buffer():
     B2L = s.cache_read(B2, scope_tb, [B7, B9])
     B4L = s.cache_read(B4, scope_tb, [B7, B12])
     B3L = s.cache_read(B3, scope_tb, [B9, B13])
-    B0L = s.cache_read(B0, scope_tb, [B10, B12])   
+    B0L = s.cache_read(B0, scope_tb, [B10, B12])
 
     B8L = s.cache_write(B8, scope_tb)
     B11L = s.cache_write(B11, scope_tb)
@@ -324,7 +324,7 @@ def mem_info_inp_buffer():
     B10L = s.cache_write(B10, scope_tb)
     B12L = s.cache_write(B12, scope_tb)
     B13L = s.cache_write(B13, scope_tb)
-	
+
     s[B12].compute_inline()
     s[B13].compute_inline()
     s[B8].compute_inline()
@@ -334,12 +334,12 @@ def mem_info_inp_buffer():
     s[B7].compute_inline()
     s[B9].compute_inline()
     s[B10].compute_inline()
-   
-    s = s.normalize() 
+
+    s = s.normalize()
     bounds = tvm.schedule.InferBound(s)
     assert isinstance(bounds, tvm.container.Map)
     stmt = tvm.schedule.ScheduleOps(s, bounds)
-    
+
     B0a = tvm.decl_buffer(B0.shape, B0.dtype, name='B0')
     B1a = tvm.decl_buffer(B1.shape, B1.dtype, name='B1')
     B2a = tvm.decl_buffer(B2.shape, B2.dtype, name='B2')
@@ -411,6 +411,38 @@ def verify(n):
     tvm.ir_pass.PostOrderVisit(body, verify)
     assert num_alloc[0] == 1
 
+
+def test_reuse_small_buffer():
+    ib = tvm.ir_builder.create()
+    n = tvm.var("n")
+    with ib.for_range(0, n, name="i") as i:
+        with ib.for_range(0, 10, name="j") as j:
+            A = ib.allocate("int16", 200, name="A", scope="local.L0A")
+            A[j] = tvm.const(1, "int16")
+            B = ib.allocate("int16", 200, name="B", scope="local.L0A")
+            B[j] = tvm.const(1, "int16")
+            B1 = ib.allocate("int16", 200, name="B1", scope="local.L0A")
+            B1[j] = A[j] + B[j]
+            C = ib.allocate("int16", 400, name="C", scope="local.L0A")
+            C[j] = tvm.const(1, "int16")
+            D = ib.allocate("int16", 400, name="D", scope="local.L0A")
+            D[j] = tvm.const(1, "int16")
+            E = ib.allocate("int16", 400, name="E", scope="local.L0A")
+            E[j] = C[j]
+
+    body = ib.get()
+    body = tvm.ir_pass.StorageRewrite(body)
+
+    num_alloc = [0]
+
+    def verify(n):
+        if isinstance(n, tvm.stmt.Allocate):
+            num_alloc[0] += 1
+            assert n.extents[0].value == 800
+    tvm.ir_pass.PostOrderVisit(body, verify)
+    assert num_alloc[0] == 1
+
+
 if __name__ == "__main__":
     test_alloc_seq()
     test_alloc_different_dtypes()
@@ -423,4 +455,4 @@ def verify(n):
     test_inplace_rule3()
     test_alloc_seq_type()
     test_alloc_seq_type2()
-
+    test_reuse_small_buffer()

From 82c563424be3cda45ee26edb47da87b87bca2e39 Mon Sep 17 00:00:00 2001
From: nhynes <nhynes@berkeley.edu>
Date: Fri, 16 Mar 2018 10:36:14 -0700
Subject: [PATCH 209/949] Simplify enclave lifecycle management (#1013)

---
 apps/sgx/app.cc            |  3 +--
 apps/sgx/enclave.cc        |  2 --
 sgx/runtime_t.cc           |  6 ------
 sgx/runtime_u.cc           |  8 +++++---
 sgx/threading_backend.cc   |  9 +++++++--
 sgx/tvm.edl                |  4 ++--
 src/runtime/thread_pool.cc | 14 ++++++--------
 7 files changed, 21 insertions(+), 25 deletions(-)

diff --git a/apps/sgx/app.cc b/apps/sgx/app.cc
index d008bfb3777d..c130b1d2f3e9 100644
--- a/apps/sgx/app.cc
+++ b/apps/sgx/app.cc
@@ -119,8 +119,7 @@ int SGX_CDECL main(int argc, char *argv[]) {
   if (sgx_status != SGX_SUCCESS) {
     print_error_message(sgx_status);
   }
-  tvm_ecall_shutdown(tvm_sgx_eid);
-  tvm::runtime::sgx::Shutdown();
+
   sgx_destroy_enclave(tvm_sgx_eid);
 
   if (addone_status == 1) {
diff --git a/apps/sgx/enclave.cc b/apps/sgx/enclave.cc
index d43107288ecb..342e5984e0f9 100644
--- a/apps/sgx/enclave.cc
+++ b/apps/sgx/enclave.cc
@@ -6,8 +6,6 @@
 #include <iostream>
 #endif
 
-extern void Shutdown();
-
 /* This function mirrors the one in howto_deploy except without the iostream */
 int Verify(tvm::runtime::Module mod, std::string fname) {
   // Get the function from the module.
diff --git a/sgx/runtime_t.cc b/sgx/runtime_t.cc
index 5f280ffce8e4..8ab326f862c0 100644
--- a/sgx/runtime_t.cc
+++ b/sgx/runtime_t.cc
@@ -15,9 +15,3 @@
 #include "threading_backend.cc"
 #endif
 #include "../../src/runtime/thread_pool.cc"
-
-extern "C" {
-void tvm_ecall_shutdown() {
-  tvm::runtime::ThreadPool::Global()->Shutdown();
-}
-}
diff --git a/sgx/runtime_u.cc b/sgx/runtime_u.cc
index 0acccf614b40..08ea2acf643c 100644
--- a/sgx/runtime_u.cc
+++ b/sgx/runtime_u.cc
@@ -14,7 +14,8 @@ namespace sgx {
 static std::unique_ptr<tvm::runtime::threading::ThreadGroup> sgx_thread_group;
 
 extern "C" {
-void tvm_ocall_thread_pool_launch(int num_tasks, void* cb) {
+
+void tvm_ocall_thread_group_launch(int num_tasks, void* cb) {
   std::function<void(int)> runner = [cb](int _worker_id) {
     sgx_status_t sgx_status = SGX_ERROR_UNEXPECTED;
     sgx_status = tvm_ecall_run_worker(tvm_sgx_eid, cb);
@@ -23,12 +24,13 @@ void tvm_ocall_thread_pool_launch(int num_tasks, void* cb) {
   sgx_thread_group.reset(new tvm::runtime::threading::ThreadGroup(
         num_tasks, runner, false /* include_main_thread */));
 }
-}
 
-void Shutdown() {
+void tvm_ocall_thread_group_join() {
   sgx_thread_group->Join();
 }
 
+}
+
 }  // namespace sgx
 }  // namespace runtime
 }  // namespace tvm
diff --git a/sgx/threading_backend.cc b/sgx/threading_backend.cc
index 7f820ab51581..c26cf08b95e2 100644
--- a/sgx/threading_backend.cc
+++ b/sgx/threading_backend.cc
@@ -10,7 +10,8 @@
 #include <atomic>
 
 extern "C" {
-sgx_status_t SGX_CDECL tvm_ocall_thread_pool_launch(int num_workers, void* cb);
+sgx_status_t SGX_CDECL tvm_ocall_thread_group_launch(int num_workers, void* cb);
+sgx_status_t SGX_CDECL tvm_ocall_thread_group_join();
 }
 
 #ifndef TVM_SGX_MAX_CONCURRENCY
@@ -31,10 +32,14 @@ class ThreadGroup::Impl {
     CHECK(num_workers <= TVM_SGX_MAX_CONCURRENCY)
       << "Tried spawning more threads than allowed by TVM_SGX_MAX_CONCURRENCY.";
     sgx_status_t sgx_status = SGX_ERROR_UNEXPECTED;
-    sgx_status = tvm_ocall_thread_pool_launch(num_workers, this);
+    sgx_status = tvm_ocall_thread_group_launch(num_workers, this);
     CHECK(sgx_status == SGX_SUCCESS) << "SGX Error: " << sgx_status;
   }
 
+  ~Impl() {
+    tvm_ocall_thread_group_join();
+  }
+
   void RunTask() {
     int task_id = next_task_id_++;
     CHECK(task_id < num_workers_)
diff --git a/sgx/tvm.edl b/sgx/tvm.edl
index e88ac0ac7f9c..f580fecfec89 100644
--- a/sgx/tvm.edl
+++ b/sgx/tvm.edl
@@ -5,11 +5,11 @@ enclave {
         public void tvm_ecall_run_module([user_check] const void* tvm_args,
                                          [user_check] void* tvm_ret_value);
         public void tvm_ecall_run_worker([user_check] const void* cb);
-        public void tvm_ecall_shutdown();
     };
 
     untrusted {
-        void tvm_ocall_thread_pool_launch(int num_workers, [user_check] void* cb);
+        void tvm_ocall_thread_group_launch(int num_workers, [user_check] void* cb);
+        void tvm_ocall_thread_group_join();
     };
 };
 
diff --git a/src/runtime/thread_pool.cc b/src/runtime/thread_pool.cc
index ac42a0c032c5..d70f03e08b64 100644
--- a/src/runtime/thread_pool.cc
+++ b/src/runtime/thread_pool.cc
@@ -265,7 +265,12 @@ class ThreadPool {
           num_workers_, [this](int worker_id) { this->RunWorker(worker_id); },
           false /* include_main_thread */));
   }
-  ~ThreadPool() { Shutdown(); }
+  ~ThreadPool() {
+    for (std::unique_ptr<SpscTaskQueue>& q : queues_) {
+      q->SignalForKill();
+    }
+    threads_.reset();
+  }
   int Launch(FTVMParallelLambda flambda,
              void* cdata,
              int num_task,
@@ -292,13 +297,6 @@ class ThreadPool {
     return res;
   }
 
-  void Shutdown() {
-    for (std::unique_ptr<SpscTaskQueue>& q : queues_) {
-      q->SignalForKill();
-    }
-    threads_.reset();
-  }
-
   static ThreadPool* Global() {
     static ThreadPool inst;
     return &inst;

From 86afdd4c0e83cbe0f020c913b40bf5c436b55b97 Mon Sep 17 00:00:00 2001
From: Salem Derisavi <33945117+derisavi-huawei@users.noreply.github.com>
Date: Fri, 16 Mar 2018 18:09:38 -0700
Subject: [PATCH 210/949] file include/tvm/logging.h from AutoTensorize work
 (#1015)

---
 include/tvm/logging.h | 99 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 99 insertions(+)
 create mode 100644 include/tvm/logging.h

diff --git a/include/tvm/logging.h b/include/tvm/logging.h
new file mode 100644
index 000000000000..fdd063082a88
--- /dev/null
+++ b/include/tvm/logging.h
@@ -0,0 +1,99 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file logging.h
+ * \brief logging utilities on top of dmlc-core
+ */
+#ifndef TVM_LOGGING_H_
+#define TVM_LOGGING_H_
+
+// a technique that enables overriding macro names on the number of parameters. This is used
+// to define other macros below
+#define GET_MACRO(_1, _2, _3, _4, _5, NAME, ...) NAME
+
+/*!
+ * \brief COND_X calls COND_X_N where N is the number of parameters passed to COND_X
+ * X can be any of CHECK_GE, CHECK_EQ, CHECK, or LOG (defined dmlc-core/include/dmlc/logging.h.)
+ * COND_X (but not COND_X_N) are supposed to be used outside this file.
+ * The first parameter of COND_X (and therefore, COND_X_N), which we call 'quit_on_assert',
+ * is a boolean. The rest of the parameters of COND_X is the same as the parameters of X.
+ * quit_on_assert determines the overall behaviour of COND_X. If it's true COND_X
+ * quits the program on assertion failure. If it's false, then it moves on and somehow reports
+ * the assertion failure back to the macro caller in an appropriate manner (e.g, 'return false'
+ * in a function, or 'continue' or 'break' in a loop)
+ * The default behavior when quit_on_assertion is false, is to 'return false'. If this is not
+ * desirable, the macro caller can pass one more last parameter to COND_X to tell COND_X what
+ * to do when when quit_on_assertion is false and the assertion fails.
+ *
+ * Rationale: These macros were designed to implement functions that have two behaviours
+ * in a concise way. Those behaviours are quitting on assertion failures, or trying to
+ * move on from assertion failures. Note that these macros hide lots of control flow in them,
+ * and therefore, makes the logic of the whole code slightly harder to understand. However,
+ * in pieces of code that use these macros frequently, it will significantly shorten the
+ * amount of code needed to be read, and we won't need to clutter the main logic of the
+ * function by repetitive control flow structure. The first problem
+ * mentioned will be improved over time as the developer gets used to the macro.
+ *
+ * Here is an example of how to use it
+ * \code
+ * bool f(..., bool quit_on_assertion) {
+ *   int a = 0, b = 0;
+ *   ...
+ *   a = ...
+ *   b = ...
+ *   // if quit_on_assertion is true, if a==b, continue, otherwise quit.
+ *   // if quit_on_assertion is false, if a==b, continue, otherwise 'return false' (default behaviour)
+ *   COND_CHECK_EQ(quit_on_assertion, a, b) << "some error message when  quiting"
+ *   ...
+ *   for (int i = 0; i < N; i++) {
+ *     a = ...
+ *     b = ...
+ *     // if quit_on_assertion is true, if a==b, continue, otherwise quit.
+ *     // if quit_on_assertion is false, if a==b, continue, otherwise 'break' (non-default
+ *     // behaviour, therefore, has to be explicitly specified)
+ *     COND_CHECK_EQ(quit_on_assertion, a, b, break) << "some error message when  quiting"
+ *   }
+ * }
+ * \endcode
+ */
+#define COND_CHECK_GE(...) \
+  GET_MACRO(__VA_ARGS__, COND_CHECK_GE_5, COND_CHECK_GE_4, COND_CHECK_GE_3)(__VA_ARGS__)
+#define COND_CHECK_EQ(...) \
+  GET_MACRO(__VA_ARGS__, COND_CHECK_EQ_5, COND_CHECK_EQ_4, COND_CHECK_EQ_3)(__VA_ARGS__)
+#define COND_CHECK(...) \
+  GET_MACRO(__VA_ARGS__, COND_CHECK_5, COND_CHECK_4, COND_CHECK_3, COND_CHECK_2)(__VA_ARGS__)
+#define COND_LOG(...) \
+  GET_MACRO(__VA_ARGS__, COND_LOG_5, COND_LOG_4, COND_LOG_3, COND_LOG_2)(__VA_ARGS__)
+
+// Not supposed to be used by users directly.
+#define COND_CHECK_OP(quit_on_assert, x, y, what, op) \
+  if (!quit_on_assert) { \
+    if (!((x) op (y))) \
+      what; \
+  } \
+  else /* NOLINT(*) */ \
+    CHECK_##op(x, y)
+
+#define COND_CHECK_EQ_4(quit_on_assert, x, y, what) COND_CHECK_OP(quit_on_assert, x, y, what, ==)
+#define COND_CHECK_GE_4(quit_on_assert, x, y, what) COND_CHECK_OP(quit_on_assert, x, y, what, >=)
+
+#define COND_CHECK_3(quit_on_assert, x, what) \
+  if (!quit_on_assert) { \
+    if (!(x)) \
+      what; \
+  } \
+  else /* NOLINT(*) */ \
+    CHECK(x)
+
+#define COND_LOG_3(quit_on_assert, x, what) \
+  if (!quit_on_assert) { \
+    what; \
+  } \
+  else /* NOLINT(*) */ \
+    LOG(x)
+
+#define COND_CHECK_EQ_3(quit_on_assert, x, y) COND_CHECK_EQ_4(quit_on_assert, x, y, return false)
+#define COND_CHECK_GE_3(quit_on_assert, x, y) COND_CHECK_GE_4(quit_on_assert, x, y, return false)
+#define COND_CHECK_2(quit_on_assert, x) COND_CHECK_3(quit_on_assert, x, return false)
+#define COND_LOG_2(quit_on_assert, x) COND_LOG_3(quit_on_assert, x, return false)
+
+#endif   // TVM_LOGGING_H_

From 5c06ffa825ee867f94543523c78f661c0416a129 Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Sat, 17 Mar 2018 11:15:17 +0530
Subject: [PATCH 211/949] Documentation issues fixed (#1016)

---
 tutorials/optimize/opt_gemm.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/tutorials/optimize/opt_gemm.py b/tutorials/optimize/opt_gemm.py
index f1060bf46f45..44ee53a73399 100644
--- a/tutorials/optimize/opt_gemm.py
+++ b/tutorials/optimize/opt_gemm.py
@@ -13,9 +13,9 @@
 In this tutorial, we will demonstrate how to use TVM to optimize square matrix multiplication
 and achieve 200 times faster than baseline by simply adding 18 extra lines of code.
 
-There are two important optmizations on intense computation applications executed on CPU:
+There are two important optimizations on intense computation applications executed on CPU:
     1. Increase the cache hit rate of memory access. Both complex numerical computation and hot-spot
-       memory access can be acclerated from high cache hit rate. This requires us to transform the
+       memory access can be accelerated from high cache hit rate. This requires us to transform the
        origin memory access pattern to the pattern fits the cache policy.
     2. SIMD (Single instruction multi-data), or we call it vector processing unit. Every time, a
        small batch of data, rather than a single grid, will be processed. This requires us to
@@ -26,7 +26,7 @@
 `repo <https://github.com/flame/how-to-optimize-gemm>`_. Some of them have been applied by TVM
 abstraction automatically, but some of them cannot be simply applied due to TVM constraints.
 
-All the experiment results mentioned below, are executed on 2015's 15' MacBook equiped with
+All the experiment results mentioned below, are executed on 2015's 15' MacBook equipped with
 Intel i7-4770HQ CPU. The cache line size should be 64 bytes for all the x86 CPUs.
 """
 

From 8595d19261a9434fffbaeca906d3d4c58f9e70d2 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 17 Mar 2018 12:22:10 -0700
Subject: [PATCH 212/949] [RUNTIME] More reliable thread enumeration (#1017)

---
 src/codegen/llvm/codegen_amdgpu.cc |  4 +--
 src/codegen/llvm/codegen_nvptx.cc  |  4 +--
 src/codegen/spirv/codegen_spirv.cc |  4 +--
 src/pass/storage_access.cc         |  3 +-
 src/pass/storage_access.h          |  1 +
 src/pass/storage_flatten.cc        |  4 ++-
 src/pass/storage_rewrite.cc        |  6 ++--
 src/pass/storage_sync.cc           | 17 ++++-----
 src/runtime/thread_storage_scope.h | 55 ++++++++++++++++++++++++++----
 src/schedule/bound.cc              | 12 ++++---
 10 files changed, 80 insertions(+), 30 deletions(-)

diff --git a/src/codegen/llvm/codegen_amdgpu.cc b/src/codegen/llvm/codegen_amdgpu.cc
index 9b8995bf5516..fa42cefa07a7 100644
--- a/src/codegen/llvm/codegen_amdgpu.cc
+++ b/src/codegen/llvm/codegen_amdgpu.cc
@@ -44,7 +44,7 @@ class CodeGenAMDGPU : public CodeGenLLVM {
       if (info.alignment > 16) {
         info.alignment = 16;
       }
-      if (info.scope.rank == 2) {
+      if (info.scope.rank == runtime::StorageRank::kLocal) {
         // const int local_address_space = 5;
         // TODO(tqchen): for higher version of LLVM, local address space can be set.
         llvm::AllocaInst* alloca = builder_->CreateAlloca(
@@ -54,7 +54,7 @@ class CodeGenAMDGPU : public CodeGenLLVM {
         }
         buf = alloca;
       } else {
-        CHECK_EQ(info.scope.rank, 1)
+        CHECK(info.scope.rank == runtime::StorageRank::kShared)
             << "Can only allocate shared or local memory inside kernel";
         // Shared memory: address space  == 3
         const unsigned shared_address_space = 3;
diff --git a/src/codegen/llvm/codegen_nvptx.cc b/src/codegen/llvm/codegen_nvptx.cc
index c0002873d5fc..d354e3b9eaf0 100644
--- a/src/codegen/llvm/codegen_nvptx.cc
+++ b/src/codegen/llvm/codegen_nvptx.cc
@@ -47,7 +47,7 @@ class CodeGenNVPTX : public CodeGenLLVM {
       if (info.alignment > 16) {
         info.alignment = 16;
       }
-      if (info.scope.rank == 2) {
+      if (info.scope.rank == runtime::StorageRank::kLocal) {
         // const int local_address_space = 5;
         // TODO(tqchen): for higher version of LLVM, local address space can be set.
         llvm::AllocaInst* alloca = builder_->CreateAlloca(
@@ -57,7 +57,7 @@ class CodeGenNVPTX : public CodeGenLLVM {
         }
         buf = alloca;
       } else {
-        CHECK_EQ(info.scope.rank, 1)
+        CHECK(info.scope.rank == runtime::StorageRank::kShared)
             << "Can only allocate shared or local memory inside kernel";
         // Shared memory: address space  == 3
         const unsigned shared_address_space = 3;
diff --git a/src/codegen/spirv/codegen_spirv.cc b/src/codegen/spirv/codegen_spirv.cc
index 4d7a9b21ba5b..d844a7b11390 100644
--- a/src/codegen/spirv/codegen_spirv.cc
+++ b/src/codegen/spirv/codegen_spirv.cc
@@ -561,13 +561,13 @@ void CodeGenSPIRV::VisitStmt_(const Allocate* op) {
   spirv::Value buf;
   StorageInfo& info = storage_info_[op->buffer_var.get()];
   spirv::SType etype = builder_->GetSType(op->type);
-  if (info.scope.rank == 2) {
+  if (info.scope.rank == runtime::StorageRank::kLocal) {
     buf = builder_->Allocate(
         etype, static_cast<uint32_t>(constant_size),
         spv::StorageClassFunction);
   } else {
     // shared memory
-    CHECK_EQ(info.scope.rank, 1)
+    CHECK(info.scope.rank == runtime::StorageRank::kShared)
         << "Can only allocate shared or local memory inside kernel";
     // Shared memory
     buf = builder_->Allocate(
diff --git a/src/pass/storage_access.cc b/src/pass/storage_access.cc
index 9211f3f71de0..09be1a53da42 100644
--- a/src/pass/storage_access.cc
+++ b/src/pass/storage_access.cc
@@ -210,7 +210,8 @@ void StorageAccessVisitor::Visit_(const Call* op) {
 
 StorageScope StorageAccessVisitor::GetScope(const Variable* buf) const {
   auto it = storage_scope_.find(buf);
-  StorageScope s; s.rank = 0;
+  StorageScope s;
+  s.rank = StorageRank::kGlobal;
   if (it == storage_scope_.end()) return s;
   return it->second;
 }
diff --git a/src/pass/storage_access.h b/src/pass/storage_access.h
index 7268bb668342..4f313f8e7c24 100644
--- a/src/pass/storage_access.h
+++ b/src/pass/storage_access.h
@@ -17,6 +17,7 @@ namespace tvm {
 namespace ir {
 
 using runtime::StorageScope;
+using runtime::StorageRank;
 /*!
  * \brief Base class of storage access analysis
  */
diff --git a/src/pass/storage_flatten.cc b/src/pass/storage_flatten.cc
index 94332ff5cb7e..f5cb98495ff9 100644
--- a/src/pass/storage_flatten.cc
+++ b/src/pass/storage_flatten.cc
@@ -23,6 +23,7 @@ namespace tvm {
 namespace ir {
 
 using HalideIR::Internal::Region;
+using runtime::StorageRank;
 using runtime::StorageScope;
 using runtime::ThreadScope;
 using intrinsic::tvm_address_of;
@@ -141,7 +142,8 @@ class StorageFlattener : public IRMutator {
       const std::string& strkey = it->second;
       if (strkey.length() == 0) {
         if (curr_thread_scope_.size() != 0) {
-          skey.rank = curr_thread_scope_.back().rank + 1;
+          skey.rank = runtime::DefaultStorageRank(
+              curr_thread_scope_.back().rank);
         }
       } else {
         skey = StorageScope::make(strkey);
diff --git a/src/pass/storage_rewrite.cc b/src/pass/storage_rewrite.cc
index 0a8782366193..998df034e5a1 100644
--- a/src/pass/storage_rewrite.cc
+++ b/src/pass/storage_rewrite.cc
@@ -19,6 +19,7 @@
 namespace tvm {
 namespace ir {
 
+using runtime::StorageRank;
 using runtime::StorageScope;
 
 // Find a linear pattern of storage acess
@@ -794,7 +795,7 @@ class StoragePlanRewriter : public IRMutator {
     // disable reuse of small arrays, they will be lowered to registers in LLVM
     // This rules only apply if we are using non special memory
     if (scope.tag.length() == 0) {
-      if (scope.rank > 1 || op->type.is_handle()) {
+      if (scope.rank >= StorageRank::kWarp || op->type.is_handle()) {
         return NewAlloc(op, attach_scope, scope, const_nbits);
       }
       if (const_nbits > 0  &&  const_nbits <= 32) {
@@ -853,7 +854,8 @@ class StoragePlanRewriter : public IRMutator {
     // This rules only apply if we are using non special memory
     if (e->scope.tag.length() == 0) {
       // Disable sharing of local memory.
-      if (e->scope.rank > 1 || e->allocs[0]->type.is_handle()) return;
+      if (e->scope.rank >= StorageRank::kWarp ||
+          e->allocs[0]->type.is_handle()) return;
       // disable reuse of small arrays
       if (e->const_nbits > 0 && e->const_nbits <= 32) return;
     }
diff --git a/src/pass/storage_sync.cc b/src/pass/storage_sync.cc
index af3dc1f128e5..6e2d1020a6b5 100644
--- a/src/pass/storage_sync.cc
+++ b/src/pass/storage_sync.cc
@@ -189,7 +189,7 @@ class ThreadSyncInserter : public IRMutator {
     if (syncs_.size() == 0) return stmt;
     if (syncs_.count(stmt.get())) {
       Stmt barrier;
-      if (sync_scope_.rank == 0) {
+      if (sync_scope_.rank == StorageRank::kGlobal) {
         barrier = MakeGlobalBarrier();
       } else {
         barrier = Evaluate::make(
@@ -206,15 +206,15 @@ class ThreadSyncInserter : public IRMutator {
     return stmt;
   }
   Expr Mutate_(const Load* op, const Expr& e) final {
-    if (sync_scope_.rank == 0 &&
-        GetScope(op->buffer_var.get()).rank == 0) {
+    if (sync_scope_.rank == StorageRank::kGlobal &&
+        GetScope(op->buffer_var.get()).rank == StorageRank::kGlobal) {
       ++rw_stats_[op->buffer_var].read_count;
     }
     return IRMutator::Mutate_(op, e);
   }
   Stmt Mutate_(const Store* op, const Stmt& s) final {
-    if (sync_scope_.rank == 0 &&
-        GetScope(op->buffer_var.get()).rank == 0) {
+    if (sync_scope_.rank == StorageRank::kGlobal &&
+        GetScope(op->buffer_var.get()).rank == StorageRank::kGlobal) {
       ++rw_stats_[op->buffer_var].write_count;
     }
     return IRMutator::Mutate_(op, s);
@@ -228,7 +228,7 @@ class ThreadSyncInserter : public IRMutator {
       thread_extents_.pop_back();
       std::swap(temp, in_thread_env_);
       // first thread scope.
-      if (!in_thread_env_ && sync_scope_.rank == 0) {
+      if (!in_thread_env_ && sync_scope_.rank == StorageRank::kGlobal) {
         ret = InitGlobalBarrier(ret.as<AttrStmt>());
         num_blocks_ = Expr();
         is_lead_ = Expr();
@@ -253,7 +253,8 @@ class ThreadSyncInserter : public IRMutator {
   // Get current storage scope.
   StorageScope GetScope(const Variable* buf) const {
     auto it = storage_scope_.find(buf);
-    StorageScope s; s.rank = 0;
+    StorageScope s;
+    s.rank = StorageRank::kGlobal;
     if (it == storage_scope_.end()) return s;
     return it->second;
   }
@@ -279,7 +280,7 @@ class ThreadSyncInserter : public IRMutator {
     return Block::make(prep, body);
   }
   Stmt MakeGlobalBarrier() {
-    CHECK_EQ(sync_scope_.rank, 0);
+    CHECK(sync_scope_.rank == StorageRank::kGlobal);
     if (!num_blocks_.defined()) {
       CHECK(!is_lead_.defined());
       num_work_dim_ = thread_extents_.size();
diff --git a/src/runtime/thread_storage_scope.h b/src/runtime/thread_storage_scope.h
index 48b5e8f1ef16..647bbb82ea34 100644
--- a/src/runtime/thread_storage_scope.h
+++ b/src/runtime/thread_storage_scope.h
@@ -13,10 +13,47 @@
 namespace tvm {
 namespace runtime {
 
+/*!
+ * \brief Memory hierachy rank in the storage system
+ * \note The global rank and shared rank have one to one
+ *       correspondence to the thread rank.
+ */
+enum class StorageRank {
+  /*! \brief global memory */
+  kGlobal = 0,
+  /*! \brief shared memory among thread group */
+  kShared = 1,
+  /*!
+   * \brief reserved for warp memory.
+   *  This is only used by programming model.
+   *  There is no such memory usually in GPU.
+   *  Instead, we can simulate it by registers and shuffle.
+   */
+  kWarp = 2,
+  /*! \brief thread local memory */
+  kLocal = 3
+};
+
+/*!
+ * \param thread_scope_rank The thread scope rank
+ * \return default storage rank given the thread scope
+ */
+inline StorageRank DefaultStorageRank(int thread_scope_rank) {
+  switch (thread_scope_rank) {
+    case -1: return StorageRank::kGlobal;
+    case 0: return StorageRank::kShared;
+    case 1: return StorageRank::kLocal;
+    default: {
+      LOG(FATAL) << "unknown rank";
+      return StorageRank::kGlobal;
+    }
+  }
+}
+
 /*! \brief class to represent storage scope */
 struct StorageScope {
   /*! \brief The rank of the storage */
-  int rank{0};
+  StorageRank rank{StorageRank::kGlobal};
   /*! \brief tag for special purpose memory. */
   std::string tag;
   // comparator
@@ -29,9 +66,10 @@ struct StorageScope {
   inline std::string to_string() const {
     std::string ret;
     switch (rank) {
-      case 0: return "global" + tag;
-      case 1: return "shared" + tag;
-      case 2: return "local" + tag;
+      case StorageRank::kGlobal: return "global" + tag;
+      case StorageRank::kShared: return "shared" + tag;
+      case StorageRank::kWarp: return "warp" + tag;
+      case StorageRank::kLocal: return "local" + tag;
       default: LOG(FATAL) << "unknown storage scope"; return "";
     }
   }
@@ -43,13 +81,16 @@ struct StorageScope {
   static StorageScope make(const std::string& s) {
     StorageScope r;
     if (s.compare(0, 6, "global")  == 0) {
-      r.rank = 0;
+      r.rank = StorageRank::kGlobal;
       r.tag = s.substr(6, std::string::npos);
     } else if (s.compare(0, 6, "shared") == 0) {
-      r.rank = 1;
+      r.rank = StorageRank::kShared;
       r.tag = s.substr(6, std::string::npos);
+    } else if (s.compare(0, 4, "warp") == 0) {
+      r.rank = StorageRank::kWarp;
+      r.tag = s.substr(4, std::string::npos);
     } else if (s.compare(0, 5, "local") == 0) {
-      r.rank = 2;
+      r.rank = StorageRank::kLocal;
       r.tag = s.substr(5, std::string::npos);
     } else {
       LOG(FATAL) << "unknown storage scope " << s;
diff --git a/src/schedule/bound.cc b/src/schedule/bound.cc
index 1a06970e52e4..908b579ec9a4 100644
--- a/src/schedule/bound.cc
+++ b/src/schedule/bound.cc
@@ -16,8 +16,9 @@
 namespace tvm {
 namespace schedule {
 
-using runtime::ThreadScope;
+using runtime::StorageRank;
 using runtime::StorageScope;
+using runtime::ThreadScope;
 
 /*! \brief The graph context used during bound inference. */
 struct GraphContext {
@@ -41,7 +42,7 @@ bool NeedRelax(const IterVar& iv,
   if (tag.length() == 0 || tag == "pipeline") {
     return !found_attach;
   }
-  return scope.rank <= ThreadScope::make(tag).rank;
+  return static_cast<int>(scope.rank) <= ThreadScope::make(tag).rank;
 }
 
 // infer storage scope, if not given
@@ -50,16 +51,17 @@ StorageScope InferStorageScope(
   if (stage->scope.length() != 0) {
     return StorageScope::make(stage->scope);
   }
-  int max_rank = 0;
+  int max_rank = -1;
   for (IterVar iv : ctx.attach_path.at(stage->op)) {
     auto it = ctx.bind_map.find(iv);
     const std::string& tag = (
         it != ctx.bind_map.end() ? it->second->thread_tag : iv->thread_tag);
     if (tag != "pipeline" && tag.length() != 0) {
-      max_rank = std::max(max_rank, ThreadScope::make(tag).rank + 1);
+      max_rank = std::max(max_rank, ThreadScope::make(tag).rank);
     }
   }
-  StorageScope s; s.rank = max_rank;
+  StorageScope s;
+  s.rank = runtime::DefaultStorageRank(max_rank);
   return s;
 }
 

From 4afc2f9bcb30e3f744ad6a7653bf295a42b50dcc Mon Sep 17 00:00:00 2001
From: alex-weaver <awsweaver@gmail.com>
Date: Mon, 19 Mar 2018 07:17:25 +0000
Subject: [PATCH 213/949] Implement C++ registry to back Python
 target.generic_func (#892)

---
 include/tvm/build_module.h                | 273 ++++++++++++--
 python/tvm/target.py                      | 284 +++++++++-----
 src/codegen/build_module.cc               | 441 ++++++++++++++++++----
 src/runtime/threading_backend.cc          |   1 +
 tests/cpp/build_module_test.cc            |   2 +-
 tests/python/unittest/test_lang_target.py |  15 +-
 topi/include/topi/cuda/dense.h            |  19 +-
 topi/include/topi/cuda/extern.h           |   2 +-
 topi/include/topi/cuda/injective.h        |   2 +-
 topi/include/topi/cuda/pooling.h          |   2 +-
 topi/include/topi/cuda/reduction.h        |   4 +-
 topi/include/topi/nn/dense.h              |  13 +-
 topi/include/topi/rocm/dense.h            |  19 +-
 topi/python/topi/__init__.py              |   5 +-
 topi/python/topi/generic/injective.py     |   2 +-
 topi/python/topi/generic/nn.py            |  14 +-
 topi/python/topi/nn/dense.py              |   2 +-
 topi/src/topi.cc                          | 133 +++++--
 18 files changed, 954 insertions(+), 279 deletions(-)

diff --git a/include/tvm/build_module.h b/include/tvm/build_module.h
index 3fb55ae169ce..0cbc97d71e46 100644
--- a/include/tvm/build_module.h
+++ b/include/tvm/build_module.h
@@ -8,81 +8,146 @@
 
 #include <string>
 #include <vector>
-#include "./tvm/runtime/packed_func.h"
-#include "./tvm/schedule_pass.h"
-#include "./tvm/lowered_func.h"
+#include "./runtime/packed_func.h"
+#include "./schedule_pass.h"
+#include "./lowered_func.h"
 
 namespace tvm {
+using namespace tvm::runtime;
 
 /*!
 * \brief Container for target device information.
 * Use target::llvm, target::cuda etc functions instead of constructing directly.
 */
-struct Target {
+class TargetNode : public Node {
+ public:
   /*! \brief The name of the target device */
   std::string target_name;
   /*! \brief The type of the target device */
-  DLDeviceType device_type;
+  int device_type;
   /*! \brief The maximum threads that a schedule should use for this device */
   int max_num_threads = 1;
   /*! \brief The warp size that should be used by the LowerThreadAllreduce pass */
   int thread_warp_size = 1;
   /*! \brief Keys for this target */
-  std::unordered_set<std::string> keys;
+  Array<Expr> keys_array;
   /*! \brief Options for this target */
-  std::vector<std::string> options;
-  /*! \brief Set of imported libs */
-  std::unordered_set<std::string> libs;
-
-  Target(const std::string& target_name,
-         DLDeviceType device_type,
-         int max_num_threads,
-         int thread_warp_size,
-         const std::unordered_set<std::string>& keys,
-         const std::vector<std::string>& options,
-         const std::unordered_set<std::string>& libs =
-           std::unordered_set<std::string>()) :
-    target_name(target_name),
-    device_type(device_type),
-    max_num_threads(max_num_threads),
-    thread_warp_size(thread_warp_size),
-    keys(keys),
-    options(options),
-    libs(libs) {
-  }
+  Array<Expr> options_array;
+  /*! \brief Collection of imported libs */
+  Array<Expr> libs_array;
 
   /*! \return the full device string to pass to codegen::Build */
   EXPORT std::string str() const;
 
+  void VisitAttrs(AttrVisitor* v) final {
+    v->Visit("target_name", &target_name);
+    v->Visit("device_type", &device_type);
+    v->Visit("max_num_threads", &max_num_threads);
+    v->Visit("thread_warp_size", &thread_warp_size);
+    v->Visit("keys_array", &keys_array);
+    v->Visit("options_array", &options_array);
+    v->Visit("libs_array", &libs_array);
+  }
+
+  /*! \brief Get the keys for this target as a vector of string */
+  EXPORT std::vector<std::string> keys() const;
+
+  /*! \brief Get the options for this target as a vector of string */
+  EXPORT std::vector<std::string> options() const;
+
+  /*! \brief Get the keys for this target as an unordered_set of string */
+  EXPORT std::unordered_set<std::string> libs() const;
+
+  static constexpr const char* _type_key = "Target";
+  TVM_DECLARE_NODE_TYPE_INFO(TargetNode, Node);
+};
+
+class Target : public NodeRef {
+ public:
+  Target() {}
+  explicit Target(std::shared_ptr<Node> n) : NodeRef(n) {}
+
   /*!
-   * \brief Create a Target given a string
-   * \param target_str the string to parse
-   */
+  * \brief Create a Target given a string
+  * \param target_str the string to parse
+  */
   EXPORT static Target create(const std::string& target_str);
+
+  /*!
+  * \brief Push a new target context onto the thread local stack. The Target on top of
+  * the stack is used to determine which specialization to use when invoking a GenericFunc.
+  * \param target The target to set as the current context.
+  */
+  EXPORT static void EnterTargetScope(const tvm::Target& target);
+
+  /*!
+  * \brief Pop a target off the thread local context stack, restoring the previous target
+  * as the current context.
+  */
+  EXPORT static void ExitTargetScope();
+
+  /*!
+  * \brief Get the current target context from thread local storage.
+  * \param allow_not_defined If the context stack is empty and this is set to true, an
+  * undefined Target will be returned. Otherwise, an empty context stack will cause a
+  * runtime error.
+  * \return The target that is the current context. The target may not be defined if
+  * allow_not_defined is true.
+  */
+  EXPORT static tvm::Target current_target(bool allow_not_defined = true);
+
+  inline const TargetNode* operator->() const {
+      return static_cast<const TargetNode*>(node_.get());
+  }
+
+  using ContainerType = TargetNode;
+};
+
+/*!
+ * \brief RAII container to provide a scoped target context. Pushes a target onto the
+ * context stack when constructed, and pops it when destructed.
+ */
+struct TargetContext {
+  /*!
+   * \brief Enter a new target context. The given target becomes the new current context.
+   * When the TargetContext is destructed, the previous context is restored.
+   * \param target The target to set as the new current context.
+   */
+  explicit TargetContext(const tvm::Target& target) {
+    Target::EnterTargetScope(target);
+  }
+
+  /*! \brief Destructor. Pops the context off the thread local stack. */
+  ~TargetContext() {
+    Target::ExitTargetScope();
+  }
 };
 
 /*! \brief This namespace provides functions to construct Target instances */
 namespace target {
 /*! \return A target for LLVM */
-EXPORT Target llvm();
+EXPORT Target llvm(const std::unordered_set<std::string>& options = {});
 
 /*! \return A target for CUDA */
-EXPORT Target cuda();
+EXPORT Target cuda(const std::unordered_set<std::string>& options = {});
 
 /*! \return A target for ROCm */
-EXPORT Target rocm();
+EXPORT Target rocm(const std::unordered_set<std::string>& options = {});
+
+/*! \return A target for OpenCL */
+EXPORT Target opencl(const std::unordered_set<std::string>& options = {});
 
 /*! \return A target for Metal */
-EXPORT Target metal();
+EXPORT Target metal(const std::unordered_set<std::string>& options = {});
 
 /*! \return A target for rasp */
-EXPORT Target rasp();
+EXPORT Target rasp(const std::unordered_set<std::string>& options = {});
 
 /*! \return A target for Mali */
-EXPORT Target mali();
+EXPORT Target mali(const std::unordered_set<std::string>& options = {});
 
 /*! \return A target for stackvm */
-EXPORT Target stackvm();
+EXPORT Target stackvm(const std::unordered_set<std::string>& options = {});
 
 }  // namespace target
 
@@ -174,15 +239,147 @@ EXPORT Array<LoweredFunc> lower(Schedule sch,
 * \brief Build a device and host module for a specific target from an array of lowered functions.
 * \param funcs The functions to be built.
 * \param target The target device to build for.
-* \param target_host The target for building host code. If null, a suitable default will be used.
+* \param target_host The target for building host code. To use the default, pass Target()
 * \param config The build configuration.
 * \return The built module.
 */
 EXPORT runtime::Module build(const Array<LoweredFunc>& funcs,
                              const Target& target,
-                             Target* target_host,
+                             const Target& target_host,
                              const BuildConfig& config);
 
+class GenericFuncNode;
+
+/*!
+ * \brief Generic function that can be specialized on a per-target basis.
+ */
+class GenericFunc : public NodeRef {
+ public:
+  GenericFunc() {}
+  explicit GenericFunc(std::shared_ptr<Node> n) : NodeRef(n) {}
+
+  /*!
+   * \brief Set the default function implementaiton.
+   * \param value The default function
+   * \param allow_override If true, this call may override a previously registered function. If
+   * false, an error will be logged if the call would override a previously registered function.
+   * \return reference to self.
+   */
+  TVM_DLL GenericFunc& set_default(const PackedFunc value,
+                                   bool allow_override = false);
+  /*!
+   * \brief Register a specialized function
+   * \param tags The tags for this specialization
+   * \param value The specialized function
+   * \param allow_override If true, this call may override previously registered tags. If false,
+   * an error will be logged if the call would override previously registered tags.
+   * \return reference to self.
+   */
+  TVM_DLL GenericFunc& register_func(const std::vector<std::string>& tags,
+                                     const PackedFunc value,
+                                     bool allow_override = false);
+  /*!
+   * \brief Call generic function by directly passing in unpacked format.
+   * \param args Arguments to be passed.
+   * \tparam Args arguments to be passed.
+   *
+   * \code
+   *   // Example code on how to call generic function
+   *   void CallGeneirc(GenericFunc f) {
+   *     // call like normal functions by pass in arguments
+   *     // return value is automatically converted back
+   *     int rvalue = f(1, 2.0);
+   *   }
+   * \endcode
+   */
+  template<typename... Args>
+  inline TVMRetValue operator()(Args&& ...args) const;
+  /*!
+   * \brief Invoke the relevant function for the current target context, set by set_target_context.
+   * Arguments are passed in packed format.
+   * \param args The arguments to pass to the function.
+   * \param ret The return value
+   */
+  TVM_DLL void CallPacked(TVMArgs args, TVMRetValue* ret) const;
+
+  /*!
+   * \brief Find or register the GenericFunc instance corresponding to the give name
+   * \param name The name of the registered GenericFunc
+   * \return The GenericFunc instance
+   */
+  TVM_DLL static GenericFunc Get(const std::string& name);
+
+  /*!
+   * \brief Add a GenericFunc instance to the registry
+   * \param func The GenericFunc instance
+   * \param name The name of the registered GenericFunc
+   */
+  TVM_DLL static void RegisterGenericFunc(GenericFunc func, const std::string& name);
+
+  /*!
+   * \brief access the internal node container
+   * \return the pointer to the internal node container
+   */
+  inline GenericFuncNode* operator->();
+
+  // declare container type
+  using ContainerType = GenericFuncNode;
+
+  // Internal class.
+  struct Manager;
+
+ private:
+  friend struct Manager;
+};
+
+template<typename... Args>
+inline TVMRetValue GenericFunc::operator()(Args&& ...args) const {
+  const int kNumArgs = sizeof...(Args);
+  const int kArraySize = kNumArgs > 0 ? kNumArgs : 1;
+  TVMValue values[kArraySize];
+  int type_codes[kArraySize];
+  detail::for_each(TVMArgsSetter(values, type_codes),
+    std::forward<Args>(args)...);
+  TVMRetValue rv;
+  CallPacked(TVMArgs(values, type_codes, kNumArgs), &rv);
+  return rv;
+}
+
+/*!
+ * \brief Represents a generic function that can be specialized on a per-target basis.
+ */
+class GenericFuncNode : public Node {
+ public:
+  /*! \brief name of the function */
+  std::string name_;
+  /* \brief the generic builder */
+  PackedFunc generic_func_;
+  /* \brief map from keys to registered functions */
+  std::unordered_map<std::string, PackedFunc> dispatch_dict_;
+
+  static constexpr const char* _type_key = "GenericFunc";
+  TVM_DECLARE_NODE_TYPE_INFO(GenericFuncNode, Node);
+};
+
+inline GenericFuncNode* GenericFunc::operator->() {
+  return static_cast<GenericFuncNode*>(node_.get());
+}
+
+#define TVM_GENERIC_FUNC_REG_VAR_DEF                               \
+  static TVM_ATTRIBUTE_UNUSED ::tvm::GenericFunc& __mk_ ## TVM
+
+/*!
+ * \def TVM_REGISTER_GENERIC_FUNC
+ * \brief Register a new generic function, or set a device-specific variant
+ * of the corresponding function.
+ *
+ * \param name The name of the function
+ */
+#define TVM_REGISTER_GENERIC_FUNC(name)                           \
+  TVM_STR_CONCAT(TVM_GENERIC_FUNC_REG_VAR_DEF, __COUNTER__) =     \
+      ::tvm::GenericFunc::Get(#name)
+
+
 }  // namespace tvm
 
 #endif  // TVM_BUILD_MODULE_H_
diff --git a/python/tvm/target.py b/python/tvm/target.py
index 7fa8998bfbe1..77fa50efe01c 100644
--- a/python/tvm/target.py
+++ b/python/tvm/target.py
@@ -40,8 +40,9 @@
 """
 from __future__ import absolute_import
 
-import warnings
 from ._ffi.base import _LIB_NAME
+from ._ffi.node import NodeBase, register_node
+from . import _api_internal
 
 try:
     from decorator import decorate
@@ -62,17 +63,10 @@ def _merge_opts(opts, new_opts):
     return opts
 
 
-class Target(object):
+@register_node
+class Target(NodeBase):
     """Target device information, use through TVM API.
 
-    Parameters
-    ----------
-    target_name : {"llvm", "cuda", "opencl", "metal", "rocm", "stackvm", "opengl", "ext_dev"}
-        The major target name.
-
-    options : list of str, optional
-        Additional arguments appended to the target.
-
     Note
     ----
     Do not use class constructor, you can create target using the following functions
@@ -83,68 +77,190 @@ class Target(object):
     - :any:`tvm.target.rocm` create ROCM target
     - :any:`tvm.target.mali` create Mali target
     """
-    current = None
-
-    def __init__(self,
-                 target_name,
-                 options=None):
-        self.target_name = target_name
-        self.options = _merge_opts([], options)
-        self.device_name = ""
-        self.libs = []
-        # Parse device option
-        for item in self.options:
-            if item.startswith("-libs="):
-                libs = item.split("=")[1]
-                self.libs += libs.split(",")
-            elif item.startswith("-device="):
-                self.device_name = item.split("=")[1]
-        # Target query searches device name first
-        if self.device_name:
-            self.keys = (self.device_name,)
-        else:
-            self.keys = ()
-        # Target configuration handling
-        self.thread_warp_size = 1
-        if target_name in ("llvm", ):
-            self.keys += ("cpu",)
-        elif target_name in ("cuda", "nvptx"):
-            self.keys += ("cuda", "gpu")
-            self.max_num_threads = 512
-            self.thread_warp_size = 32
-        elif target_name in ("rocm", "opencl"):
-            # For now assume rocm schedule for opencl
-            self.keys += ("rocm", "gpu")
-            self.max_num_threads = 256
-        elif target_name in ("metal", "vulkan"):
-            self.keys += (target_name, "gpu",)
-            self.max_num_threads = 256
-        elif target_name in ("opengl",):
-            self.keys += ("opengl",)
-        elif target_name in ("stackvm", "ext_dev"):
-            # Do not now class for stackvm or ext_dev
-            pass
-        else:
-            raise ValueError("Unknown target name %s" % target_name)
-
-    def __str__(self):
-        return " ".join([self.target_name] + self.options)
-
-    def __repr__(self):
-        return self.__str__()
+    def __init__(self, handle):
+        super(Target, self).__init__(handle)
+        self._keys = None
+        self._options = None
+        self._libs = None
+
+    @property
+    def keys(self):
+        if not self._keys:
+            self._keys = [k.value for k in self.keys_array]
+        return self._keys
+
+    @property
+    def options(self):
+        if not self._options:
+            self._options = [o.value for o in self.options_array]
+        return self._options
+
+    @property
+    def libs(self):
+        if not self._libs:
+            self._libs = [l.value for l in self.libs_array]
+        return self._libs
 
     def __enter__(self):
-        self._old_target = Target.current
-        if self._old_target is not None and str(self) != str(self._old_target):
-            warnings.warn(
-                "Override target '%s' with new target scope '%s'" % (
-                    self._old_target, self))
-        Target.current = self
+        _api_internal._EnterTargetScope(self)
         return self
 
     def __exit__(self, ptype, value, trace):
-        Target.current = self._old_target
+        _api_internal._ExitTargetScope()
+
+@register_node
+class GenericFunc(NodeBase):
+    """GenericFunc node reference. This represents a generic function
+    that may be specialized for different targets. When this object is
+    called, a specialization is chosen based on the current target.
+
+    Note
+    ----
+    Do not construct an instance of this object, it should only ever be
+    used as a return value from calling into C++.
+    """
+    def __call__(self, *args):
+        return _api_internal._GenericFuncCallFunc(self, *args)
+
+    def set_default(self, func, allow_override=False):
+        """Set the default function to be used if no specializations match
+        the current target.
+
+        Parameters
+        ----------
+        func : function
+            The default function
+
+        allow_override : bool
+            Whether to allow the current default to be overridden
+        """
+        _api_internal._GenericFuncSetDefault(self, func, allow_override)
+
+    def register(self, func, key_list, allow_override=False):
+        """Register a specialization for this GenericFunc.
+
+        Parameters
+        ----------
+        func : function
+            The function to be registered.
+
+        key : str or list of str
+            The key to be registered.
+
+        allow_override : bool, optional
+            Whether to allow existing keys to be overridden.
+        """
+        key_list = [key_list] if isinstance(key_list, str) else key_list
+        _api_internal._GenericFuncRegisterFunc(self, func, key_list, allow_override)
+
+def get_native_generic_func(name):
+    """Get a generic function from the global registry. If no
+    function is registered under the given name, a new generic
+    function is created.
+
+    Parameters
+    ----------
+    name : string
+        The name of the generic function to get
+
+    Returns
+    -------
+    func : GenericFunc
+        The generic function for the given name
+    """
+    return _api_internal._GenericFuncGetGlobal(name)
+
+def override_native_generic_func(func_name):
+    """Override a generic function defined in C++
+
+    Generic function allows registration of further functions
+    that can be dispatched on current target context.
+    If no registered dispatch is matched, the fdefault will be called.
+
+    Parameters
+    ----------
+    func_name : string
+        The name of the generic func to be overridden
+
+    Returns
+    -------
+    fgeneric : function
+        A wrapped generic function.
+
+    Example
+    -------
+    .. code-block:: python
+
+    import tvm
+    # wrap function as target generic
+    @tvm.target.override_native_generic_func("my_func")
+    def my_func(a):
+        return a + 1
+    # register specialization of my_func under target cuda
+    @my_func.register("cuda")
+    def my_func_cuda(a):
+        return a + 2
+    # displays 3, because my_func is called
+    print(my_func(2))
+    # displays 4, because my_func_cuda is called
+    with tvm.target.cuda():
+        print(my_func(2))
+    """
+    generic_func_node = get_native_generic_func(func_name)
+
+    def fdecorate(fdefault):
+        """Wrap a target generic function, overriding the previous
+        default that was set for the generic function.
+
+        Parameters
+        ----------
+        fdefault : function
+            The default function.
+
+        Returns
+        -------
+        fgeneric : function
+            A wrapped generic function.
 
+        """
+        generic_func_node.set_default(fdefault, allow_override=True)
+
+        def register(key, func=None, override=True):
+            """Register function to be the dispatch function.
+
+            Parameters
+            ----------
+            key : str or list of str
+                The key to be registered.
+
+            func : function
+                The function to be registered.
+
+            override : bool, optional
+                Whether override existing registration.
+
+            Returns
+            -------
+            The register function is necessary.
+            """
+            def _do_reg(myf):
+                generic_func_node.register(myf, key, override)
+                return myf
+            if func:
+                return _do_reg(func)
+            return _do_reg
+
+        def dispatch_func(func, *args, **kwargs):
+            #pylint: disable=unused-argument
+            """The wrapped dispath function"""
+            if kwargs:
+                raise RuntimeError(
+                    "Keyword arguments cannot be used when invoking generic_func %s" % func_name)
+            return generic_func_node(*args)
+        fresult = decorate(fdefault, dispatch_func)
+        fresult.register = register
+        return fresult
+    return fdecorate
 
 def generic_func(fdefault):
     """Wrap a target generic function.
@@ -228,7 +344,6 @@ def dispatch_func(func, *args, **kwargs):
     fdecorate.register = register
     return fdecorate
 
-
 def cuda(options=None):
     """Returns a cuda target.
 
@@ -237,7 +352,8 @@ def cuda(options=None):
     options : list of str
         Additional options
     """
-    return Target("cuda", options)
+    options = options if options else []
+    return _api_internal._TargetCreate("cuda", *options)
 
 
 def rocm(options=None):
@@ -248,7 +364,8 @@ def rocm(options=None):
     options : list of str
         Additional options
     """
-    return Target("rocm", options)
+    options = options if options else []
+    return _api_internal._TargetCreate("rocm", *options)
 
 
 def rasp(options=None):
@@ -264,7 +381,7 @@ def rasp(options=None):
             "-mcpu=cortex-a53",
             "-mattr=+neon"]
     opts = _merge_opts(opts, options)
-    return Target("llvm", opts)
+    return _api_internal._TargetCreate("llvm", *opts)
 
 
 def mali(options=None):
@@ -277,7 +394,7 @@ def mali(options=None):
     """
     opts = ["-device=mali"]
     opts = _merge_opts(opts, options)
-    return Target("opencl", opts)
+    return _api_internal._TargetCreate("opencl", *opts)
 
 
 def opengl(options=None):
@@ -288,7 +405,8 @@ def opengl(options=None):
     options : list of str
         Additional options
     """
-    return Target("opengl", options)
+    options = options if options else []
+    return _api_internal._TargetCreate("opengl", *options)
 
 
 def create(target_str):
@@ -312,17 +430,8 @@ def create(target_str):
         return target_str
     if not isinstance(target_str, str):
         raise ValueError("target_str has to be string type")
-    arr = target_str.split()
-    # Parse device option
-    device_name = ""
-    for item in arr[1:]:
-        if item.startswith("-device="):
-            device_name = item.split("=")[1]
-    if device_name == "rasp":
-        return rasp(arr[1:])
-    if device_name == "mali":
-        return mali(arr[1:])
-    return Target(arr[0], arr[1:])
+
+    return _api_internal._TargetFromString(target_str)
 
 
 def current_target(allow_none=True):
@@ -337,10 +446,5 @@ def current_target(allow_none=True):
     ------
     ValueError if current target is not set.
     """
-    if Target.current:
-        return Target.current
-    if not allow_none:
-        raise RuntimeError(
-            "Requires a current target in generic function, but it is not set. "
-            "Please set it using `with TargetObject:`")
-    return Target.current
+    target_str = _api_internal._GetCurrentTarget(allow_none)
+    return create(target_str) if target_str is not None else None
diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc
index dc0971b2d4c3..43bc3e32aad6 100644
--- a/src/codegen/build_module.cc
+++ b/src/codegen/build_module.cc
@@ -3,40 +3,147 @@
  *  Compile executable modules.
  * \file build_module.cc
  */
+#include <dmlc/thread_local.h>
 #include <tvm/build_module.h>
 #include <tvm/operation.h>
 #include <tvm/ir_pass.h>
 #include <tvm/codegen.h>
 
+#include <algorithm>
+#include <mutex>
+#include <stack>
 
 namespace tvm {
 
-std::string Target::str() const {
+TVM_REGISTER_NODE_TYPE(TargetNode);
+
+TVM_STATIC_IR_FUNCTOR(IRPrinter, vtable)
+.set_dispatch<TargetNode>([](const TargetNode *op, IRPrinter *p) {
+  p->stream << op->str();
+  });
+
+
+/*!
+* \brief Construct a Target node from the given name and options.
+* \param target_name The major target name. Should be one of
+* {"llvm", "cuda", "opencl", "metal", "rocm", "stackvm", "opengl", "ext_dev"}
+* \param options Additional options appended to the target
+* \return The constructed Target
+*/
+Target CreateTarget(const std::string& target_name,
+                    const std::unordered_set<std::string>& options) {
+  auto target = Target(std::make_shared<TargetNode>());
+  auto t = static_cast<TargetNode*>(target.node_.get());
+
+  t->target_name = target_name;
+
+  std::string device_name = "";
+
+  std::string libs_flag = "-libs=";
+  std::string device_flag = "-device=";
+  for (auto& item : options) {
+    t->options_array.push_back(ir::StringImm::make(item));
+
+    if (item.find(libs_flag) == 0) {
+      std::stringstream ss(item.substr(libs_flag.length()));
+      std::string lib_item;
+      while (std::getline(ss, lib_item, ',')) {
+        t->libs_array.push_back(ir::StringImm::make(lib_item));
+      }
+    } else if (item.find(device_flag) == 0) {
+      device_name = item.substr(device_flag.length());
+    }
+  }
+
+  if (device_name.length() > 0) {
+    t->keys_array.push_back(ir::StringImm::make(device_name));
+  }
+
+  t->device_type = kDLCPU;
+  t->thread_warp_size = 1;
+  if (target_name == "llvm") {
+    t->keys_array.push_back(ir::StringImm::make("cpu"));
+  } else if (target_name == "cuda" || target_name == "nvptx") {
+    t->device_type = kDLGPU;
+    t->keys_array.push_back(ir::StringImm::make("cuda"));
+    t->keys_array.push_back(ir::StringImm::make("gpu"));
+    t->max_num_threads = 512;
+    t->thread_warp_size = 32;
+  } else if (target_name == "rocm" || target_name == "opencl") {
+    // For now assume rocm schedule for opencl
+    t->device_type = static_cast<int>(target_name == "rocm" ? kDLROCM : kDLOpenCL);
+    t->keys_array.push_back(ir::StringImm::make("rocm"));
+    t->keys_array.push_back(ir::StringImm::make("gpu"));
+    t->max_num_threads = 256;
+  } else if (target_name == "metal" || target_name == "vulkan") {
+    t->device_type = static_cast<int>(target_name == "metal" ? kDLMetal : kDLVulkan);
+    t->keys_array.push_back(ir::StringImm::make(target_name));
+    t->keys_array.push_back(ir::StringImm::make("gpu"));
+    t->max_num_threads = 256;
+  } else if (target_name == "opengl") {
+    t->device_type = kDLGPU;
+    t->keys_array.push_back(ir::StringImm::make("opengl"));
+  } else if (target_name == "stackvm" || target_name == "ext_dev") {
+  } else {
+    LOG(ERROR) << "Unknown target name " << target_name;
+    return target::stackvm();
+  }
+
+  return target;
+}
+
+TVM_REGISTER_API("_TargetCreate")
+.set_body([](TVMArgs args, TVMRetValue* ret) {
+  std::string target_name = args[0];
+  std::unordered_set<std::string> options;
+  for (int i = 1; i < args.num_args; ++i) {
+    std::string arg = args[i];
+    options.insert(arg);
+  }
+
+  *ret = CreateTarget(target_name, options);
+  });
+
+TVM_REGISTER_API("_TargetFromString")
+.set_body([](TVMArgs args, TVMRetValue* ret) {
+  std::string target_str = args[0];
+
+  *ret = Target::create(target_str);
+  });
+
+std::vector<std::string> TargetNode::keys() const {
+  std::vector<std::string> result;
+  for (auto& expr : keys_array) {
+    result.push_back(expr.as<ir::StringImm>()->value);
+  }
+  return result;
+}
+
+std::vector<std::string> TargetNode::options() const {
+  std::vector<std::string> result;
+  for (auto& expr : options_array) {
+    result.push_back(expr.as<ir::StringImm>()->value);
+  }
+  return result;
+}
+
+std::unordered_set<std::string> TargetNode::libs() const {
+  std::unordered_set<std::string> result;
+  for (auto& expr : libs_array) {
+    result.insert(expr.as<ir::StringImm>()->value);
+  }
+  return result;
+}
+
+std::string TargetNode::str() const {
   std::ostringstream result;
   result << target_name;
-  for (const auto &x : options) {
+  for (const auto &x : options()) {
     result << " " << x;
   }
   return result.str();
 }
 
-Target TargetFromName(const std::string& name) {
-  if (name == "llvm") {
-    return target::llvm();
-  } else if (name == "cuda" || name == "nvptx") {
-    return target::cuda();
-  } else if (name == "rocm" || name == "opencl") {
-    /* For now, assume rocm schedule for opencl */
-    return target::rocm();
-  } else if (name == "metal") {
-    return target::metal();
-  } else if (name == "stackvm" || name == "ext_dev") {
-    return target::stackvm();
-  } else {
-    LOG(ERROR) << "Unknown target name " << name;
-    return target::stackvm();
-  }
-}
 
 bool StartsWith(const std::string& str, const std::string& pattern) {
   return str.compare(0, pattern.length(), pattern) == 0;
@@ -68,74 +175,99 @@ Target Target::create(const std::string& target_str) {
   ss >> target_name;
   auto device_name = GetDeviceName(target_str);
 
-  auto result = device_name == "rasp" ?
-    target::rasp() :
-    (device_name == "mali" ? target::mali() :
-    TargetFromName(target_name));
-
+  std::unordered_set<std::string> options;
   std::string item;
   while (ss >> item) {
-    result.options.push_back(item);
+    options.insert(item);
   }
 
-  return result;
+  if (device_name == "rasp") {
+    return target::rasp(options);
+  } else if (device_name == "mail") {
+    return target::mali(options);
+  } else {
+    return CreateTarget(target_name, options);
+  }
+}
+
+/*! \brief Entry to hold the Target context stack. */
+struct TVMTargetThreadLocalEntry {
+  /*! \brief The current target context */
+  std::stack<tvm::Target> context_stack;
+
+  TVMTargetThreadLocalEntry() {
+  }
+};
+
+/*! \brief Thread local store to hold the Target context stack. */
+typedef dmlc::ThreadLocalStore<TVMTargetThreadLocalEntry> TVMTargetThreadLocalStore;
+
+void Target::EnterTargetScope(const tvm::Target& target) {
+  TVMTargetThreadLocalEntry *entry = TVMTargetThreadLocalStore::Get();
+  entry->context_stack.push(target);
+}
+
+void Target::ExitTargetScope() {
+  TVMTargetThreadLocalEntry *entry = TVMTargetThreadLocalStore::Get();
+  entry->context_stack.pop();
+}
+
+tvm::Target Target::current_target(bool allow_not_defined) {
+  TVMTargetThreadLocalEntry *entry = TVMTargetThreadLocalStore::Get();
+  if (entry->context_stack.size() > 0) {
+    return entry->context_stack.top();
+  }
+  CHECK(allow_not_defined)
+    << "Target context required. Please set it by constructing a TargetContext";
+
+  return Target();
 }
 
 namespace target {
-Target llvm() {
-  std::unordered_set<std::string> keys({ "llvm", "cpu" });
-  std::vector<std::string> options;
-  return Target("llvm", kDLCPU, 512, 1, keys, options,
-           std::unordered_set<std::string>());
+std::unordered_set<std::string> MergeOptions(std::unordered_set<std::string> opts,
+                                             const std::unordered_set<std::string>& new_opts) {
+  opts.insert(new_opts.begin(), new_opts.end());
+  return opts;
+}
+
+Target llvm(const std::unordered_set<std::string>& options) {
+  return CreateTarget("llvm", options);
 }
 
-Target cuda() {
-  std::unordered_set<std::string> keys({ "cuda", "gpu" });
-  std::vector<std::string> options;
-  return Target("cuda", kDLGPU, 512, 32, keys, options,
-           std::unordered_set<std::string>());
+Target cuda(const std::unordered_set<std::string>& options) {
+  return CreateTarget("cuda", options);
 }
 
-Target rocm() {
-  std::unordered_set<std::string> keys({ "rocm", "gpu" });
-  std::vector<std::string> options;
-  return Target("rocm", kDLROCM, 256, 1, keys, options,
-           std::unordered_set<std::string>());
+Target rocm(const std::unordered_set<std::string>& options) {
+  return CreateTarget("rocm", options);
 }
 
-Target metal() {
-  std::unordered_set<std::string> keys({ "gpu" });
-  std::vector<std::string> options;
-  return Target("metal", kDLMetal, 256, 1, keys, options,
-           std::unordered_set<std::string>());
+Target opencl(const std::unordered_set<std::string>& options) {
+  return CreateTarget("opencl", options);
 }
 
-Target rasp() {
-  std::unordered_set<std::string> keys({ "llvm", "cpu" });
-  std::vector<std::string> options({
+Target metal(const std::unordered_set<std::string>& options) {
+  return CreateTarget("metal", options);
+}
+
+Target rasp(const std::unordered_set<std::string>& options) {
+  return CreateTarget("llvm", MergeOptions(options, {
     "-device=rasp",
     "-mtriple=armv7l-none-linux-gnueabihf",
     "-mcpu=cortex-a53",
     "-mattr=+neon"
-  });
-  return Target("llvm", kDLCPU, 512, 1, keys, options,
-           std::unordered_set<std::string>());
+  }));
 }
 
-Target mali() {
-  std::unordered_set<std::string> keys({ "rocm", "gpu" });
-  std::vector<std::string> options({
+Target mali(const std::unordered_set<std::string>& options) {
+  return CreateTarget("opencl", MergeOptions(options, {
     "-device=mali"
-  });
-  return Target("opencl", kDLOpenCL, 256, 1, keys, options);
+  }));
 }
 
 
-Target stackvm() {
-  std::unordered_set<std::string> keys({ "stackvm", "cpu" });
-  std::vector<std::string> options;
-  return Target("stackvm", kDLCPU, 512, 1, keys, options,
-           std::unordered_set<std::string>());
+Target stackvm(const std::unordered_set<std::string>& options) {
+  return CreateTarget("stackvm", options);
 }
 }  // namespace target
 
@@ -146,7 +278,7 @@ bool LLVMEnabled() {
 
 /*! \return The default host target for a given device target */
 Target DefaultTargetHost(Target target) {
-  if (target.device_type == kDLCPU) {
+  if (target->device_type == kDLCPU) {
     return target;
   } else {
     if (LLVMEnabled()) {
@@ -254,7 +386,7 @@ Array<LoweredFunc> lower(Schedule sch,
 
 runtime::Module build(const Array<LoweredFunc>& funcs,
                       const Target& target,
-                      Target* target_host,
+                      const Target& target_host,
                       const BuildConfig& config) {
   std::unordered_set<std::string> all_names;
   for (const auto &x : funcs) {
@@ -262,15 +394,13 @@ runtime::Module build(const Array<LoweredFunc>& funcs,
     all_names.insert(x->name);
   }
 
-  Target target_host_val = target_host == nullptr ?
-    DefaultTargetHost(target) :
-    *target_host;
+  auto target_host_val = target_host.defined() ? target_host : DefaultTargetHost(target);
 
   Array<LoweredFunc> fhost;
   Array<LoweredFunc> fdevice;
 
   for (const auto& x : funcs) {
-    CHECK(ir::VerifyMemory(x, target.device_type))
+    CHECK(ir::VerifyMemory(x, target->device_type))
         << "Direct host side access to device memory is detected in " << x->func_name()
         << ". Did you forget to bind?";
 
@@ -281,7 +411,7 @@ runtime::Module build(const Array<LoweredFunc>& funcs,
       }
 
       func = ir::ThreadSync(func, "shared");
-      func = ir::LowerThreadAllreduce(func, target.thread_warp_size);
+      func = ir::LowerThreadAllreduce(func, target->thread_warp_size);
       auto fsplits = ir::SplitHostDevice(func);
       fhost.push_back(fsplits[0]);
       for (auto f = fsplits.begin() + 1; f != fsplits.end(); ++f) {
@@ -296,14 +426,17 @@ runtime::Module build(const Array<LoweredFunc>& funcs,
     }
   }
 
-  if (target.keys.count("gpu") > 0 && fdevice.size() == 0) {
-    LOG(WARNING) << "Specified target " + target.str() +
+  auto keys = target->keys();
+  bool target_is_gpu =
+    std::find(keys.begin(), keys.end(), "gpu") != keys.end();
+  if (target_is_gpu && fdevice.size() == 0) {
+    LOG(WARNING) << "Specified target " + target->str() +
       " but cannot find device code. Did you forget to bind?";
   }
 
   for (size_t i = 0; i < fhost.size(); ++i) {
     auto func = fhost[i];
-    func = ir::BindDeviceType(func, target.device_type);
+    func = ir::BindDeviceType(func, target->device_type);
     func = ir::LowerTVMBuiltin(func);
     fhost.Set(i, func);
   }
@@ -311,21 +444,21 @@ runtime::Module build(const Array<LoweredFunc>& funcs,
 
   for (size_t i = 0; i < fdevice.size(); ++i) {
     auto func = fdevice[i];
-    func = ir::LowerIntrin(func, target.target_name);
+    func = ir::LowerIntrin(func, target->target_name);
     fdevice.Set(i, func);
   }
 
   for (size_t i = 0; i < fhost.size(); ++i) {
     auto func = fhost[i];
-    func = ir::LowerIntrin(func, target_host_val.target_name);
+    func = ir::LowerIntrin(func, target_host_val->target_name);
     func = ir::CombineContextCall(func);
     fhost.Set(i, func);
   }
 
-  auto mhost = codegen::Build(fhost, target_host_val.str());
+  auto mhost = codegen::Build(fhost, target_host_val->str());
 
   if (fdevice.size() > 0) {
-    auto mdev = codegen::Build(fdevice, target.str());
+    auto mdev = codegen::Build(fdevice, target->str());
     mhost.Import(mdev);
   }
 
@@ -354,4 +487,160 @@ TVM_STATIC_IR_FUNCTOR(IRPrinter, vtable)
   p->stream << ")";
 });
 
+struct GenericFunc::Manager {
+  std::unordered_map<std::string, std::shared_ptr<Node> > fmap;
+  // mutex
+  std::mutex mutex;
+
+  Manager() {
+  }
+
+  static Manager* Global() {
+    static Manager inst;
+    return &inst;
+  }
+};
+
+GenericFunc GenericFunc::Get(const std::string& name) {
+  Manager* m = Manager::Global();
+  std::lock_guard<std::mutex>(m->mutex);
+  auto it = m->fmap.find(name);
+  if (it == m->fmap.end()) {
+    auto f = std::make_shared<GenericFuncNode>();
+    f->name_ = name;
+    m->fmap[name] = f;
+    return GenericFunc(f);
+  } else {
+    return GenericFunc(it->second);
+  }
+}
+
+void GenericFunc::RegisterGenericFunc(GenericFunc func, const std::string& name) {
+  Manager* m = Manager::Global();
+  std::lock_guard<std::mutex>(m->mutex);
+  auto it = m->fmap.find(name);
+  CHECK(it == m->fmap.end()) << "GenericFunc already registered " << name;
+  func->name_ = name;
+  m->fmap[name] = func.node_;
+}
+
+GenericFunc& GenericFunc::set_default(const PackedFunc value,
+                                           bool allow_override) {
+  auto node = static_cast<GenericFuncNode*>(node_.get());
+  if (!allow_override) {
+    CHECK(node->generic_func_ == nullptr)
+      << "Generic function already registered for " << node->name_;
+  }
+  node->generic_func_ = value;
+  return *this;
+}
+
+GenericFunc& GenericFunc::register_func(const std::vector<std::string>& tags,
+                                        const PackedFunc value,
+                                        bool allow_override) {
+  for (auto &t : tags) {
+    if (!allow_override) {
+      auto iter = (*this)->dispatch_dict_.find(t);
+      CHECK(iter == (*this)->dispatch_dict_.end())
+        << "Tag " << t << " already registered for schedule factory " << (*this)->name_;
+    }
+    (*this)->dispatch_dict_[t] = value;
+  }
+  return *this;
+}
+
+void GenericFunc::CallPacked(TVMArgs args, TVMRetValue* ret) const {
+  auto node = static_cast<GenericFuncNode*>(node_.get());
+  auto target = Target::current_target(true);
+  PackedFunc func;
+
+  if (target.defined()) {
+    for (auto &k : target->keys()) {
+      auto iter = node->dispatch_dict_.find(k);
+      if (iter != node->dispatch_dict_.end()) {
+        func = iter->second;
+        break;
+      }
+    }
+  }
+
+  if (func == nullptr) {
+    CHECK(node->generic_func_ != nullptr) << "No generic function registered for " << node->name_;
+    func = node->generic_func_;
+  }
+
+  func.CallPacked(args, ret);
+}
+
+
+TVM_REGISTER_API("_GenericFuncCreate")
+.set_body([](TVMArgs args, TVMRetValue* ret) {
+  *ret = GenericFunc(std::make_shared<GenericFuncNode>());
+  });
+
+TVM_REGISTER_API("_GenericFuncGetGlobal")
+.set_body([](TVMArgs args, TVMRetValue* ret) {
+  std::string func_name = args[0];
+  *ret = GenericFunc::Get(func_name);
+  });
+
+TVM_REGISTER_API("_GenericFuncSetDefault")
+.set_body([](TVMArgs args, TVMRetValue* ret) {
+  GenericFunc generic_func = args[0];
+  // Intentionally copy and not de-allocate it, to avoid free pyobject during shutdown
+  PackedFunc* func = new PackedFunc(args[1].operator PackedFunc());
+  bool allow_override = args[2];
+
+  generic_func
+    .set_default(*func, allow_override);
+  });
+
+TVM_REGISTER_API("_GenericFuncRegisterFunc")
+.set_body([](TVMArgs args, TVMRetValue* ret) {
+  GenericFunc generic_func = args[0];
+  // Intentionally copy and not de-allocate it, to avoid free pyobject during shutdown
+  PackedFunc* func = new PackedFunc(args[1].operator PackedFunc());
+  Array<Expr> tags = args[2];
+  bool allow_override = args[3];
+
+  std::vector<std::string> tags_vector;
+  for (auto& tag : tags) {
+    tags_vector.push_back(tag.as<tvm::ir::StringImm>()->value);
+  }
+
+  generic_func
+    .register_func(tags_vector, *func, allow_override);
+  });
+
+TVM_REGISTER_API("_GenericFuncCallFunc")
+.set_body([](TVMArgs args, TVMRetValue* ret) {
+  GenericFunc generic_func = args[0];
+  TVMArgs func_args(&args.values[1], &args.type_codes[1], args.num_args - 1);
+
+  generic_func
+    .CallPacked(func_args, ret);
+  });
+
+TVM_REGISTER_API("_GetCurrentTarget")
+.set_body([](TVMArgs args, TVMRetValue* ret) {
+  bool allow_not_defined = args[0];
+  *ret = Target::current_target(allow_not_defined);
+  });
+
+TVM_REGISTER_API("_EnterTargetScope")
+.set_body([](TVMArgs args, TVMRetValue* ret) {
+  Target target = args[0];
+  auto current = Target::current_target();
+  if (current.defined() && target->str() != current->str()) {
+    LOG(WARNING) << "Overriding target " << current->str()
+      << " with new target scope " << target->str();
+  }
+  Target::EnterTargetScope(target);
+  });
+
+TVM_REGISTER_API("_ExitTargetScope")
+.set_body([](TVMArgs args, TVMRetValue* ret) {
+  Target::ExitTargetScope();
+  });
+
 }  // namespace tvm
diff --git a/src/runtime/threading_backend.cc b/src/runtime/threading_backend.cc
index 19ba9bf2d776..c91796891e24 100644
--- a/src/runtime/threading_backend.cc
+++ b/src/runtime/threading_backend.cc
@@ -6,6 +6,7 @@
 #include <tvm/runtime/threading_backend.h>
 #include <dmlc/logging.h>
 #include <thread>
+#include <algorithm>
 #if defined(__linux__)
 #include <sched.h>
 #endif
diff --git a/tests/cpp/build_module_test.cc b/tests/cpp/build_module_test.cc
index fe0a9151cc2c..a1aed5b00b96 100644
--- a/tests/cpp/build_module_test.cc
+++ b/tests/cpp/build_module_test.cc
@@ -31,7 +31,7 @@ TEST(BuildModule, Basic) {
   auto target = target::llvm();
 
   auto lowered = lower(s, args, "func", binds, config);
-  auto module = build(lowered, target, nullptr, config);
+  auto module = build(lowered, target, Target(), config);
 }
 
 
diff --git a/tests/python/unittest/test_lang_target.py b/tests/python/unittest/test_lang_target.py
index 4e13c76d5f39..812da0fd2710 100644
--- a/tests/python/unittest/test_lang_target.py
+++ b/tests/python/unittest/test_lang_target.py
@@ -34,11 +34,16 @@ def test_target_dispatch():
     with tvm.target.create("metal"):
         assert mygeneric(1) == 3
 
-    try:
-        mygeneric(0)
-        raise RuntimeError("not reached")
-    except RuntimeError:
-        pass
+    assert tvm.target.current_target() == None
+
+def test_target_string_parse():
+    target = tvm.target.create("cuda -libs=cublas,cudnn")
+
+    assert target.target_name == "cuda"
+    assert target.options == ['-libs=cublas,cudnn']
+    assert target.keys == ['cuda', 'gpu']
+    assert target.libs == ['cublas', 'cudnn']
 
 if __name__ == "__main__":
     test_target_dispatch()
+    test_target_string_parse()
diff --git a/topi/include/topi/cuda/dense.h b/topi/include/topi/cuda/dense.h
index a07aafea6e30..77e29fc7fdbb 100644
--- a/topi/include/topi/cuda/dense.h
+++ b/topi/include/topi/cuda/dense.h
@@ -24,31 +24,30 @@ namespace cuda {
 * \param target The target device
 * \param data Tensor with shape [batch, in_dim]
 * \param weight Tensor with shape [out_dim, in_dim]
-* \param bias Tensor with shape [out_dim] (optional)
+* \param bias Tensor with shape [out_dim]. Optional; to omit bias, pass Tensor()
 *
 * \return Tensor with shape [batch, out_dim]
 */
 inline tvm::Tensor dense_cuda(const Target& target,
                               const tvm::Tensor& data,
                               const tvm::Tensor& weight,
-                              tvm::Tensor* bias) {
+                              const tvm::Tensor& bias) {
   CHECK_EQ(data->shape.size(), 2) << "dense requires 2-D data";
   CHECK_EQ(weight->shape.size(), 2) << "dense requires 2-D weight";
-  if (bias != nullptr) {
-    CHECK_EQ((*bias)->shape.size(), 1) << "dense requires 1-D bias";
+  if (bias.defined()) {
+    CHECK_EQ(bias->shape.size(), 1) << "dense requires 1-D bias";
   }
 
   auto batch = data->shape[0];
   auto in_dim = data->shape[1];
   auto out_dim = weight->shape[0];
 
-  if (target.libs.count("cublas") > 0) {
+  if (target->libs().count("cublas")) {
     auto mm = topi::contrib::cublas_matmul(data, weight, false, true);
-    if (bias != nullptr) {
-      auto bias_val = *bias;
+    if (bias.defined()) {
       mm = tvm::compute({ batch, out_dim },
                         [&](Var i, Var j) {
-                          return mm(i, j) + bias_val(j);
+                          return mm(i, j) + bias(j);
                         }, "tensor", kBroadcast);
     }
 
@@ -67,8 +66,8 @@ inline tvm::Tensor dense_cuda(const Target& target,
 * \return A schedule for the given ops.
 */
 inline Schedule schedule_dense(const Target &target, const Array<Tensor>& outs) {
-  if (target.target_name == "cuda" &&
-    target.libs.count("cublas") > 0) {
+  if (target->target_name == "cuda" &&
+    target->libs().count("cublas")) {
     return topi::generic::schedule_extern(target, outs);
   }
 
diff --git a/topi/include/topi/cuda/extern.h b/topi/include/topi/cuda/extern.h
index 1c2f9a79ab00..475ab6ba8a19 100644
--- a/topi/include/topi/cuda/extern.h
+++ b/topi/include/topi/cuda/extern.h
@@ -28,7 +28,7 @@ namespace cuda {
 inline Schedule ScheduleOutputForExtern(Target target, Operation op, Schedule sch) {
   auto x = op.output(0);
   auto fused = detail::Fuse(sch[x], sch[x]->op.as<ComputeOpNode>()->axis);
-  auto num_thread = target.max_num_threads;
+  auto num_thread = target->max_num_threads;
   IterVar bx, tx;
   sch[x].split(fused, num_thread, &bx, &tx);
   sch[x].bind(bx, tvm::thread_axis(Range(), "blockIdx.x"));
diff --git a/topi/include/topi/cuda/injective.h b/topi/include/topi/cuda/injective.h
index e8e60fb6809e..91c6df3a2a3c 100644
--- a/topi/include/topi/cuda/injective.h
+++ b/topi/include/topi/cuda/injective.h
@@ -25,7 +25,7 @@ namespace cuda {
 inline void ScheduleInjectiveOp(const Target &target, Operation op, Schedule s) {
   auto x = op.output(0);
   auto fused = detail::Fuse(s[x], s[x]->op.as<ComputeOpNode>()->axis);
-  auto num_thread = target.max_num_threads;
+  auto num_thread = target->max_num_threads;
   IterVar bx, tx;
   s[x].split(fused, num_thread, &bx, &tx);
   s[x].bind(bx, thread_axis(Range(), "blockIdx.x"));
diff --git a/topi/include/topi/cuda/pooling.h b/topi/include/topi/cuda/pooling.h
index d7536f315dba..f8730c9d0299 100644
--- a/topi/include/topi/cuda/pooling.h
+++ b/topi/include/topi/cuda/pooling.h
@@ -34,7 +34,7 @@ inline Schedule schedule_pool(const Target &target, const Array<Tensor>& outs) {
 
   auto _schedule = [&](const Tensor& padded_input, const Tensor& pool) {
     s[padded_input].compute_inline();
-    auto num_thread = target.max_num_threads;
+    auto num_thread = target->max_num_threads;
     Tensor out;
     Tensor OL;
     if (detail::contains(s->outputs, pool->op)) {
diff --git a/topi/include/topi/cuda/reduction.h b/topi/include/topi/cuda/reduction.h
index e7a41d9274fa..a1670873cc31 100644
--- a/topi/include/topi/cuda/reduction.h
+++ b/topi/include/topi/cuda/reduction.h
@@ -51,7 +51,7 @@ Schedule ScheduleReduce(const Target& target,
   if (out_stage->op.as<ComputeOpNode>()->axis.size() > 0) {
     all_reduce = false;
     num_thread = 32;
-    if (target.target_name == "opencl") {
+    if (target->target_name == "opencl") {
       // Without this, CL_INVALID_WORK_GROUP_SIZE occurs with python tests.
       // Don't know why.
       num_thread = 16;
@@ -61,7 +61,7 @@ Schedule ScheduleReduce(const Target& target,
     thread_y = tvm::thread_axis(Range(0, num_thread), "threadIdx.y");
   } else {
     all_reduce = true;
-    num_thread = target.max_num_threads;
+    num_thread = target->max_num_threads;
     thread_x = tvm::thread_axis(Range(0, num_thread), "threadIdx.x");
   }
 
diff --git a/topi/include/topi/nn/dense.h b/topi/include/topi/nn/dense.h
index cdc7fde158a6..695b8e187856 100644
--- a/topi/include/topi/nn/dense.h
+++ b/topi/include/topi/nn/dense.h
@@ -20,17 +20,17 @@ using namespace tvm;
 *
 * \param data Tensor with shape [batch, in_dim]
 * \param weight Tensor with shape [out_dim, in_dim]
-* \param bias Tensor with shape [out_dim] (optional)
+* \param bias Tensor with shape [out_dim]. Optional; to omit bias, pass Tensor()
 *
 * \return Tensor with shape [batch, out_dim]
 */
 inline tvm::Tensor dense(const tvm::Tensor& data,
                          const tvm::Tensor& weight,
-                         tvm::Tensor* bias) {
+                         const tvm::Tensor& bias) {
   CHECK_EQ(data->shape.size(), 2) << "dense requires 2-D data";
   CHECK_EQ(weight->shape.size(), 2) << "dense requires 2-D weight";
-  if (bias != nullptr) {
-    CHECK_EQ((*bias)->shape.size(), 1) << "dense requires 1-D bias";
+  if (bias.defined()) {
+    CHECK_EQ(bias->shape.size(), 1) << "dense requires 1-D bias";
   }
 
   auto batch = data->shape[0];
@@ -44,12 +44,11 @@ inline tvm::Tensor dense(const tvm::Tensor& data,
       return tvm::sum(data(i, k) * weight(j, k), { k });
     }, "tensor", "dense");
 
-  if (bias != nullptr) {
-    auto bias_val = *bias;
+  if (bias.defined()) {
     matmul = tvm::compute(
       { batch, out_dim },
       [&](Var i, Var j) {
-        return matmul(i, j) + bias_val(j);
+        return matmul(i, j) + bias(j);
       }, "tensor", kBroadcast);
   }
 
diff --git a/topi/include/topi/rocm/dense.h b/topi/include/topi/rocm/dense.h
index 8256ba983eee..6f171f6780fc 100644
--- a/topi/include/topi/rocm/dense.h
+++ b/topi/include/topi/rocm/dense.h
@@ -25,31 +25,30 @@ namespace rocm {
 * \param target The target device
 * \param data Tensor with shape [batch, in_dim]
 * \param weight Tensor with shape [out_dim, in_dim]
-* \param bias Tensor with shape [out_dim] (optional)
+* \param bias Tensor with shape [out_dim]. Optional; to omit bias, pass Tensor()
 *
 * \return Tensor with shape [batch, out_dim]
 */
 inline tvm::Tensor dense_rocm(const Target& target,
                               const tvm::Tensor& data,
                               const tvm::Tensor& weight,
-                              tvm::Tensor* bias) {
+                              const tvm::Tensor& bias) {
   CHECK_EQ(data->shape.size(), 2) << "dense requires 2-D data";
   CHECK_EQ(weight->shape.size(), 2) << "dense requires 2-D weight";
-  if (bias != nullptr) {
-    CHECK_EQ((*bias)->shape.size(), 1) << "dense requires 1-D bias";
+  if (bias.defined()) {
+    CHECK_EQ(bias->shape.size(), 1) << "dense requires 1-D bias";
   }
 
   auto batch = data->shape[0];
   auto in_dim = data->shape[1];
   auto out_dim = weight->shape[0];
 
-  if (target.libs.count("rocblas") > 0) {
+  if (target->libs().count("rocblas")) {
     auto mm = topi::contrib::rocblas_matmul(data, weight, false, true);
-    if (bias != nullptr) {
-      auto bias_val = *bias;
+    if (bias.defined()) {
       mm = tvm::compute({ batch, out_dim },
                         [&](Var i, Var j) {
-                          return mm(i, j) + bias_val(j);
+                          return mm(i, j) + bias(j);
                         }, "tensor", kBroadcast);
     }
 
@@ -68,8 +67,8 @@ inline tvm::Tensor dense_rocm(const Target& target,
 * \return A schedule for the given ops.
 */
 inline Schedule schedule_dense(const Target &target, const Array<Tensor>& outs) {
-  if (target.target_name == "rocm" &&
-    target.libs.count("rocblas") > 0) {
+  if (target->target_name == "rocm" &&
+    target->libs().count("rocblas")) {
     return topi::generic::schedule_extern(target, outs);
   }
 
diff --git a/topi/python/topi/__init__.py b/topi/python/topi/__init__.py
index c9a9b7bc01b6..9666b88b80eb 100644
--- a/topi/python/topi/__init__.py
+++ b/topi/python/topi/__init__.py
@@ -11,6 +11,10 @@
 
 from tvm._ffi.libinfo import __version__
 
+# Ensure C++ schedules get registered first, so python schedules can
+# override them.
+from . import cpp
+
 from .math import *
 from .tensor import *
 from .reduction import *
@@ -24,7 +28,6 @@
 from . import opengl
 from . import util
 from . import rocm
-from . import cpp
 from . import vision
 # not import testing by default
 # because testing can have extra deps that are not necessary
diff --git a/topi/python/topi/generic/injective.py b/topi/python/topi/generic/injective.py
index 765f4e4f518d..0a9e394661af 100644
--- a/topi/python/topi/generic/injective.py
+++ b/topi/python/topi/generic/injective.py
@@ -4,7 +4,7 @@
 
 import tvm
 
-@tvm.target.generic_func
+@tvm.target.override_native_generic_func("schedule_injective")
 def schedule_injective(outs):
     """Schedule for injective op.
 
diff --git a/topi/python/topi/generic/nn.py b/topi/python/topi/generic/nn.py
index 5c580aad24c4..7fe76e1739f0 100644
--- a/topi/python/topi/generic/nn.py
+++ b/topi/python/topi/generic/nn.py
@@ -106,7 +106,7 @@ def schedule_depthwise_conv2d_nhwc(outs):
     return _default_schedule(outs, False)
 
 
-@tvm.target.generic_func
+@tvm.target.override_native_generic_func("schedule_reduce")
 def schedule_reduce(outs):
     """Schedule for reduction
 
@@ -124,7 +124,7 @@ def schedule_reduce(outs):
     return _default_schedule(outs, True)
 
 
-@tvm.target.generic_func
+@tvm.target.override_native_generic_func("schedule_softmax")
 def schedule_softmax(outs):
     """Schedule for softmax
 
@@ -142,7 +142,7 @@ def schedule_softmax(outs):
     return _default_schedule(outs, False)
 
 
-@tvm.target.generic_func
+@tvm.target.override_native_generic_func("schedule_dense")
 def schedule_dense(outs):
     """Schedule for dense
 
@@ -160,7 +160,7 @@ def schedule_dense(outs):
     return _default_schedule(outs, False)
 
 
-@tvm.target.generic_func
+@tvm.target.override_native_generic_func("schedule_pool")
 def schedule_pool(outs):
     """Schedule for pool
 
@@ -178,7 +178,7 @@ def schedule_pool(outs):
     return _default_schedule(outs, False)
 
 
-@tvm.target.generic_func
+@tvm.target.override_native_generic_func("schedule_global_pool")
 def schedule_global_pool(outs):
     """Schedule for global pool
 
@@ -195,7 +195,7 @@ def schedule_global_pool(outs):
     """
     return _default_schedule(outs, False)
 
-@tvm.target.generic_func
+@tvm.target.override_native_generic_func("schedule_binarize_pack")
 def schedule_binarize_pack(outs):
     """Schedule for binarize_pack
 
@@ -213,7 +213,7 @@ def schedule_binarize_pack(outs):
     return _default_schedule(outs, False)
 
 
-@tvm.target.generic_func
+@tvm.target.override_native_generic_func("schedule_binary_dense")
 def schedule_binary_dense(outs):
     """Schedule for binary_dense
 
diff --git a/topi/python/topi/nn/dense.py b/topi/python/topi/nn/dense.py
index 11cc6097c250..abd424a64aeb 100644
--- a/topi/python/topi/nn/dense.py
+++ b/topi/python/topi/nn/dense.py
@@ -39,7 +39,7 @@ def dense_default(data, weight, bias=None):
     return matmul
 
 
-@tvm.target.generic_func
+@tvm.target.override_native_generic_func("dense")
 def dense(data, weight, bias=None):
     """Applies a linear transformation: :math:`Y = XW^T + b`.
 
diff --git a/topi/src/topi.cc b/topi/src/topi.cc
index d6b67c74bacc..2b0191cd72d7 100644
--- a/topi/src/topi.cc
+++ b/topi/src/topi.cc
@@ -51,6 +51,7 @@ struct extension_class_info<tvm::Target> {
 } // namespace runtime
 
 namespace topi {
+
 using namespace tvm;
 using namespace tvm::runtime;
 
@@ -281,15 +282,7 @@ TVM_REGISTER_GLOBAL("topi.nn.binary_dense")
 /* Ops from nn/dense.h */
 TVM_REGISTER_GLOBAL("topi.nn.dense")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
-  Tensor bias_val;
-  Tensor *bias;
-  if (args[2].type_code() == kNull) {
-    bias = nullptr;
-  } else {
-    bias_val = args[2];
-    bias = &bias_val;
-  }
-  *rv = nn::dense(args[0], args[1], bias);
+  *rv = nn::dense(args[0], args[1], args[2]);
   });
 
 /* Ops from nn/dilate.h */
@@ -388,15 +381,7 @@ TVM_REGISTER_GLOBAL("topi.x86.schedule_injective")
 /* ROCm schedules */
 TVM_REGISTER_GLOBAL("topi.rocm.dense_cuda")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
-  Tensor bias_val;
-  Tensor *bias;
-  if (args[3].type_code() == kNull) {
-    bias = nullptr;
-  } else {
-    bias_val = args[3];
-    bias = &bias_val;
-  }
-  *rv = rocm::dense_rocm(args[0], args[1], args[2], bias);
+  *rv = rocm::dense_rocm(args[0], args[1], args[2], args[3]);
   });
 
 TVM_REGISTER_GLOBAL("topi.rocm.schedule_dense")
@@ -407,15 +392,7 @@ TVM_REGISTER_GLOBAL("topi.rocm.schedule_dense")
 /* CUDA schedules */
 TVM_REGISTER_GLOBAL("topi.cuda.dense_cuda")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
-  Tensor bias_val;
-  Tensor *bias;
-  if (args[3].type_code() == kNull) {
-    bias = nullptr;
-  } else {
-    bias_val = args[3];
-    bias = &bias_val;
-  }
-  *rv = cuda::dense_cuda(args[0], args[1], args[2], bias);
+  *rv = cuda::dense_cuda(args[0], args[1], args[2], args[3]);
   });
 
 TVM_REGISTER_GLOBAL("topi.cuda.schedule_dense")
@@ -453,4 +430,106 @@ TVM_REGISTER_GLOBAL("topi.cuda.schedule_softmax")
   *rv = topi::cuda::schedule_softmax(args[0], args[1]);
   });
 
+/*! \brief Builder function for instantiating schedules. */
+using FTVMScheduleBuilder = std::function<
+  tvm::Schedule(const tvm::Target& target, const tvm::Array<tvm::Tensor>& outs)>;
+
+/*!
+ * \brief Helper function for registering generic functions matching the
+ * FTVMScheduleBuilder signature. The schedule builder function is wrapped
+ * with a PackedFunc suitable for passing to a tvm::GenericFunc.
+ *
+ * \param builder The schedule builder to wrap.
+ *
+ * \return The wrapped schedule builder
+ */
+inline PackedFunc WrapSchedule(FTVMScheduleBuilder builder) {
+  return PackedFunc([builder](TVMArgs args, TVMRetValue* ret) {
+    auto target = Target::current_target(false);
+    Array<Tensor> outs;
+    NodeRef argNodeRef = args[0];
+    if (argNodeRef->type_index() == outs->type_index()) {
+      outs = args[0];
+    } else {
+      outs = Array<Tensor> { args[0] };
+    }
+
+    *ret = builder(target, outs);
+  });
+}
+
+TVM_REGISTER_GENERIC_FUNC(schedule_injective)
+.set_default(WrapSchedule(topi::generic::schedule_injective))
+.register_func({ "cpu" }, WrapSchedule(topi::x86::schedule_injective))
+.register_func({ "cuda", "gpu" }, WrapSchedule(topi::cuda::schedule_injective));
+
+TVM_REGISTER_GENERIC_FUNC(schedule_softmax)
+.set_default(WrapSchedule(topi::generic::default_schedule))
+.register_func({ "cpu" }, WrapSchedule(topi::x86::default_schedule))
+.register_func({ "cuda", "gpu" }, WrapSchedule(topi::cuda::schedule_softmax));
+
+TVM_REGISTER_GENERIC_FUNC(schedule_dense)
+.set_default(WrapSchedule(topi::generic::default_schedule))
+.register_func({ "cuda", "gpu" }, WrapSchedule(topi::cuda::schedule_dense))
+.register_func({ "rocm" }, WrapSchedule(topi::rocm::schedule_dense));
+
+TVM_REGISTER_GENERIC_FUNC(schedule_pool)
+.set_default(WrapSchedule(topi::generic::default_schedule))
+.register_func({ "cpu" }, WrapSchedule(topi::x86::default_schedule))
+.register_func({ "cuda", "gpu" }, WrapSchedule(topi::cuda::schedule_pool));
+
+TVM_REGISTER_GENERIC_FUNC(schedule_global_pool)
+.set_default(WrapSchedule(topi::generic::default_schedule))
+.register_func({ "cpu" }, WrapSchedule(topi::x86::default_schedule))
+.register_func({ "cuda", "gpu" }, WrapSchedule(topi::cuda::schedule_global_pool));
+
+TVM_REGISTER_GENERIC_FUNC(schedule_reduce)
+.set_default(WrapSchedule(topi::generic::default_schedule_auto_inline))
+.register_func({ "cpu" }, WrapSchedule(topi::x86::default_schedule_auto_inline))
+.register_func({ "cuda", "gpu" }, WrapSchedule(topi::cuda::schedule_reduce));
+
+TVM_REGISTER_GENERIC_FUNC(schedule_binarize_pack)
+.set_default(WrapSchedule(topi::generic::default_schedule))
+.register_func({ "cpu" }, WrapSchedule(topi::x86::schedule_binarize_pack));
+
+TVM_REGISTER_GENERIC_FUNC(schedule_binary_dense)
+.set_default(WrapSchedule(topi::generic::default_schedule))
+.register_func({ "cpu" }, WrapSchedule(topi::x86::schedule_binary_dense));
+
+/*! \brief Builder function for instantiating dense ops. */
+using FTVMDenseOpBuilder = std::function<tvm::Tensor(const Target& target,
+                                                     const tvm::Tensor& data,
+                                                     const tvm::Tensor& weight,
+                                                     const tvm::Tensor& bias)>;
+
+/*!
+* \brief Helper function for registering dense ops matching the
+* FTVMDenseOpBuilder signature. The op builder function is wrapped
+* with a PackedFunc suitable for passing to a tvm::GenericFunc.
+*
+* \param builder The op builder to wrap.
+*
+* \return The wrapped op builder
+*/
+inline PackedFunc WrapDenseOp(FTVMDenseOpBuilder builder) {
+  return PackedFunc([builder](TVMArgs args, TVMRetValue* ret) {
+    auto target = Target::current_target(false);
+    Tensor data = args[0];
+    Tensor weight = args[1];
+    Tensor bias = args[2];
+
+    *ret = builder(target, data, weight, bias);
+  });
+}
+
+TVM_REGISTER_GENERIC_FUNC(dense)
+.set_default(WrapDenseOp([](const Target& target,
+                            const tvm::Tensor& data,
+                            const tvm::Tensor& weight,
+                            const tvm::Tensor& bias) {
+  return topi::nn::dense(data, weight, bias);
+}))
+.register_func({ "cuda", "gpu" }, WrapDenseOp(topi::cuda::dense_cuda))
+.register_func({ "rocm" }, WrapDenseOp(topi::rocm::dense_rocm));
+
 }  // namespace topi

From 56d71d3dac3e73e619cb34307ab97f6663d0743f Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Tue, 20 Mar 2018 01:03:00 +0530
Subject: [PATCH 214/949] JVM NDArray fatal exception fix (#1022)

---
 jvm/core/src/main/java/ml/dmlc/tvm/NDArray.java | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/NDArray.java b/jvm/core/src/main/java/ml/dmlc/tvm/NDArray.java
index 3b9a4bb12e5a..1aea1a35c96d 100644
--- a/jvm/core/src/main/java/ml/dmlc/tvm/NDArray.java
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/NDArray.java
@@ -53,7 +53,7 @@ public void copyFrom(double[] sourceArray) {
     }
     NDArray tmpArr = empty(shape(), this.dtype);
     Base.checkCall(Base._LIB.tvmArrayCopyFromJArray(nativeArr, tmpArr.handle, handle));
-    Base.checkCall(Base._LIB.tvmArrayFree(tmpArr.handle));
+    tmpArr.release();
   }
 
   /**
@@ -72,7 +72,7 @@ public void copyFrom(float[] sourceArray) {
     }
     NDArray tmpArr = empty(shape(), this.dtype);
     Base.checkCall(Base._LIB.tvmArrayCopyFromJArray(nativeArr, tmpArr.handle, handle));
-    Base.checkCall(Base._LIB.tvmArrayFree(tmpArr.handle));
+    tmpArr.release();
   }
 
   /**
@@ -91,7 +91,7 @@ public void copyFrom(long[] sourceArray) {
     }
     NDArray tmpArr = empty(shape(), this.dtype);
     Base.checkCall(Base._LIB.tvmArrayCopyFromJArray(nativeArr, tmpArr.handle, handle));
-    Base.checkCall(Base._LIB.tvmArrayFree(tmpArr.handle));
+    tmpArr.release();
   }
 
   /**
@@ -110,7 +110,7 @@ public void copyFrom(int[] sourceArray) {
     }
     NDArray tmpArr = empty(shape(), this.dtype);
     Base.checkCall(Base._LIB.tvmArrayCopyFromJArray(nativeArr, tmpArr.handle, handle));
-    Base.checkCall(Base._LIB.tvmArrayFree(tmpArr.handle));
+    tmpArr.release();
   }
 
   /**
@@ -129,7 +129,7 @@ public void copyFrom(short[] sourceArray) {
     }
     NDArray tmpArr = empty(shape(), this.dtype);
     Base.checkCall(Base._LIB.tvmArrayCopyFromJArray(nativeArr, tmpArr.handle, handle));
-    Base.checkCall(Base._LIB.tvmArrayFree(tmpArr.handle));
+    tmpArr.release();
   }
 
   /**
@@ -161,7 +161,7 @@ public void copyFrom(char[] sourceArray) {
     }
     NDArray tmpArr = empty(shape(), this.dtype);
     Base.checkCall(Base._LIB.tvmArrayCopyFromJArray(nativeArr, tmpArr.handle, handle));
-    Base.checkCall(Base._LIB.tvmArrayFree(tmpArr.handle));
+    tmpArr.release();
   }
 
   private void checkCopySize(int sourceLength) {
@@ -179,7 +179,7 @@ private void checkCopySize(int sourceLength) {
   public void copyFromRaw(byte[] sourceArray) {
     NDArray tmpArr = empty(shape(), this.dtype);
     Base.checkCall(Base._LIB.tvmArrayCopyFromJArray(sourceArray, tmpArr.handle, handle));
-    Base.checkCall(Base._LIB.tvmArrayFree(tmpArr.handle));
+    tmpArr.release();
   }
 
   /**

From 13e63a418cf62d1d97868900f4b900fb5934deb4 Mon Sep 17 00:00:00 2001
From: Yida Wang <yidawa@gmail.com>
Date: Tue, 20 Mar 2018 03:13:31 -0700
Subject: [PATCH 215/949] [RUNTIME] better parallel launcher and task
 distribution (#1026)

---
 src/runtime/thread_pool.cc       | 56 +++++++++++++++-----------------
 src/runtime/threading_backend.cc | 20 +++++++++---
 2 files changed, 41 insertions(+), 35 deletions(-)

diff --git a/src/runtime/thread_pool.cc b/src/runtime/thread_pool.cc
index d70f03e08b64..316baedff425 100644
--- a/src/runtime/thread_pool.cc
+++ b/src/runtime/thread_pool.cc
@@ -37,12 +37,11 @@ class ParallelLauncher {
             void* cdata,
             int num_task,
             bool need_sync) {
-    std::lock_guard<std::mutex> lock(mutex_);
-    num_pending_ = num_task;
+    num_pending_.store(num_task);
     this->cdata = cdata;
     this->flambda = flambda;
     this->env.num_task = num_task;
-    has_error_ = false;
+    has_error_.store(false);
     // reshape
     if (static_cast<size_t>(num_task) > par_errors_.size()) {
       par_errors_.resize(num_task + 1);
@@ -66,11 +65,10 @@ class ParallelLauncher {
   }
   // Wait n jobs to finish
   int WaitForJobs() {
-    std::unique_lock<std::mutex> lock(mutex_);
-    cv_.wait(lock, [this] {
-        return num_pending_ == 0;
-      });
-    if (!has_error_) return 0;
+    while (num_pending_.load() != 0) {
+      tvm::runtime::threading::Yield();
+    }
+    if (!has_error_.load()) return 0;
     std::string err("");
     for (size_t i = 0; i < par_errors_.size(); ++i) {
       if (par_errors_[i].length() != 0) {
@@ -83,23 +81,13 @@ class ParallelLauncher {
   }
   // Signal that one job has finished.
   void SignalJobError(int task_id) {
-    std::unique_lock<std::mutex> lock(mutex_);
-    --num_pending_;
+    num_pending_.fetch_sub(1);
     par_errors_[task_id] = TVMGetLastError();
-    has_error_ = true;
-    if (num_pending_ == 0) {
-      lock.unlock();
-      cv_.notify_one();
-    }
+    has_error_.store(true);
   }
   // Signal that one job has finished.
   void SignalJobFinish() {
-    std::unique_lock<std::mutex> lock(mutex_);
-    --num_pending_;
-    if (num_pending_ == 0) {
-      lock.unlock();
-      cv_.notify_one();
-    }
+    num_pending_.fetch_sub(1);
   }
   // Get thread local version of the store.
   static ParallelLauncher* ThreadLocal() {
@@ -116,14 +104,10 @@ class ParallelLauncher {
   bool is_worker{false};
 
  private:
-  // The mutex to access local env.
-  std::mutex mutex_;
-  // The conditional variable.
-  std::condition_variable cv_;
   // The pending jobs.
-  uint32_t num_pending_;
+  std::atomic<int32_t> num_pending_;
   // Whether error has been countered.
-  bool has_error_;
+  std::atomic<bool> has_error_;
   // The counter page.
   std::atomic<int32_t>* sync_counter_{nullptr};
   // The error message
@@ -257,13 +241,13 @@ class ThreadPool {
  public:
   ThreadPool(): num_workers_(tvm::runtime::threading::MaxConcurrency()) {
     for (int i = 0; i < num_workers_; ++i) {
-      // The SpscTaskQueue only host ONE item at a time
+      // The SpscTaskQueue only hosts ONE item at a time
       queues_.emplace_back(std::unique_ptr<SpscTaskQueue>(new SpscTaskQueue()));
     }
     threads_ = std::unique_ptr<tvm::runtime::threading::ThreadGroup>(
         new tvm::runtime::threading::ThreadGroup(
           num_workers_, [this](int worker_id) { this->RunWorker(worker_id); },
-          false /* include_main_thread */));
+          exclude_worker0_ /* include_main_thread */));
   }
   ~ThreadPool() {
     for (std::unique_ptr<SpscTaskQueue>& q : queues_) {
@@ -289,10 +273,20 @@ class ThreadPool {
     launcher->Init(flambda, cdata, num_task, need_sync != 0);
     SpscTaskQueue::Task tsk;
     tsk.launcher = launcher;
-    for (int i = 0; i < num_task; ++i) {
+    // if worker0 is taken by the master, queues_[0] is abandoned
+    for (int i = exclude_worker0_; i < num_task; ++i) {
       tsk.task_id = i;
       queues_[i]->Push(tsk);
     }
+    // use the master thread to run task 0
+    if (exclude_worker0_) {
+      TVMParallelGroupEnv* penv = &(tsk.launcher->env);
+      if ((*tsk.launcher->flambda)(0, penv, cdata) == 0) {
+        tsk.launcher->SignalJobFinish();
+      } else {
+        tsk.launcher->SignalJobError(tsk.task_id);
+      }
+    }
     int res = launcher->WaitForJobs();
     return res;
   }
@@ -320,6 +314,8 @@ class ThreadPool {
     }
   }
   int num_workers_;
+  // if excluding worker 0 and using master to run task 0
+  bool exclude_worker0_{true};
   std::vector<std::unique_ptr<SpscTaskQueue> > queues_;
   std::unique_ptr<tvm::runtime::threading::ThreadGroup> threads_;
 };
diff --git a/src/runtime/threading_backend.cc b/src/runtime/threading_backend.cc
index c91796891e24..11df12837860 100644
--- a/src/runtime/threading_backend.cc
+++ b/src/runtime/threading_backend.cc
@@ -29,7 +29,7 @@ class ThreadGroup::Impl {
     const char *val = getenv("TVM_BIND_THREADS");
     if (val == nullptr || atoi(val) == 1) {
       if (num_workers_ <= std::thread::hardware_concurrency()) {
-        SetAffinity();
+        SetAffinity(exclude_worker0);
       } else {
         LOG(WARNING)
           << "The thread affinity cannot be set when the number of workers"
@@ -47,7 +47,9 @@ class ThreadGroup::Impl {
 
  private:
   // bind worker threads to disjoint cores
-  void SetAffinity() {
+  // if worker 0 is offloaded to master, i.e. exclude_worker0 is true,
+  // the master thread is bound to core 0.
+  void SetAffinity(bool exclude_worker0) {
 #if defined(__ANDROID__)
 #ifndef CPU_SET
 #define CPU_SETSIZE 1024
@@ -62,19 +64,27 @@ class ThreadGroup::Impl {
     memset((cpusetp), 0, sizeof(cpu_set_t))
 #endif
 #endif
-    for (unsigned i=0; i < threads_.size(); ++i) {
 #if defined(__linux__) || defined(__ANDROID__)
+    for (unsigned i = 0; i < threads_.size(); ++i) {
+      unsigned core_id = i + exclude_worker0;
       cpu_set_t cpuset;
       CPU_ZERO(&cpuset);
-      CPU_SET(i, &cpuset);
+      CPU_SET(core_id, &cpuset);
 #if defined(__ANDROID__)
       sched_setaffinity(threads_[i].native_handle(), sizeof(cpu_set_t), &cpuset);
 #else
       pthread_setaffinity_np(threads_[i].native_handle(),
           sizeof(cpu_set_t), &cpuset);
-#endif
 #endif
     }
+    if (exclude_worker0) {  // bind the master thread to core 0
+      cpu_set_t cpuset;
+      CPU_ZERO(&cpuset);
+      CPU_SET(0, &cpuset);
+      pthread_setaffinity_np(pthread_self(),
+        sizeof(cpu_set_t), &cpuset);
+    }
+#endif
   }
 
   int num_workers_;

From ebac59b4ed8dd559b995c372d560a2b43441d896 Mon Sep 17 00:00:00 2001
From: cjjia <33363657+jiacunjiang1215@users.noreply.github.com>
Date: Fri, 23 Mar 2018 16:59:30 +0800
Subject: [PATCH 216/949] Fix the issue #1033 (#1037)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Fix the issue #1033

fix the issue #1033 "converting to ‘const std::unordered_set<std::basic_string<char> >’from initializer"

* Fix the issue #1033

fix the issue #1033 "converting to ‘const std::unordered_set<std::basic_string >’from initializer".
---
 include/tvm/build_module.h | 24 ++++++++++++++++--------
 1 file changed, 16 insertions(+), 8 deletions(-)

diff --git a/include/tvm/build_module.h b/include/tvm/build_module.h
index 0cbc97d71e46..d2f21c9c87ce 100644
--- a/include/tvm/build_module.h
+++ b/include/tvm/build_module.h
@@ -126,28 +126,36 @@ struct TargetContext {
 /*! \brief This namespace provides functions to construct Target instances */
 namespace target {
 /*! \return A target for LLVM */
-EXPORT Target llvm(const std::unordered_set<std::string>& options = {});
+EXPORT Target llvm(const std::unordered_set<std::string>& options =
+                   std::unordered_set<std::string>());
 
 /*! \return A target for CUDA */
-EXPORT Target cuda(const std::unordered_set<std::string>& options = {});
+EXPORT Target cuda(const std::unordered_set<std::string>& options =
+                   std::unordered_set<std::string>());
 
 /*! \return A target for ROCm */
-EXPORT Target rocm(const std::unordered_set<std::string>& options = {});
+EXPORT Target rocm(const std::unordered_set<std::string>& options =
+                   std::unordered_set<std::string>());
 
 /*! \return A target for OpenCL */
-EXPORT Target opencl(const std::unordered_set<std::string>& options = {});
+EXPORT Target opencl(const std::unordered_set<std::string>& options =
+                     std::unordered_set<std::string>());
 
 /*! \return A target for Metal */
-EXPORT Target metal(const std::unordered_set<std::string>& options = {});
+EXPORT Target metal(const std::unordered_set<std::string>& options =
+                    std::unordered_set<std::string>());
 
 /*! \return A target for rasp */
-EXPORT Target rasp(const std::unordered_set<std::string>& options = {});
+EXPORT Target rasp(const std::unordered_set<std::string>& options =
+                   std::unordered_set<std::string>());
 
 /*! \return A target for Mali */
-EXPORT Target mali(const std::unordered_set<std::string>& options = {});
+EXPORT Target mali(const std::unordered_set<std::string>& options =
+                   std::unordered_set<std::string>());
 
 /*! \return A target for stackvm */
-EXPORT Target stackvm(const std::unordered_set<std::string>& options = {});
+EXPORT Target stackvm(const std::unordered_set<std::string>& options =
+                      std::unordered_set<std::string>());
 
 }  // namespace target
 

From 5922b7e19178887f82cebaed79dd70f2efd93b7a Mon Sep 17 00:00:00 2001
From: ZhangXinqian <zhangxinqian2012@126.com>
Date: Sat, 24 Mar 2018 10:42:05 -0500
Subject: [PATCH 217/949] Fix the issue #1036 (#1040)

---
 apps/howto_deploy/tvm_runtime_pack.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/apps/howto_deploy/tvm_runtime_pack.cc b/apps/howto_deploy/tvm_runtime_pack.cc
index 445768128413..e35aa2f90561 100644
--- a/apps/howto_deploy/tvm_runtime_pack.cc
+++ b/apps/howto_deploy/tvm_runtime_pack.cc
@@ -56,7 +56,7 @@
 
 // Uncomment the following lines to enable CUDA
 // #include "../../src/runtime/cuda/cuda_device_api.cc"
-// #include "../../src/runtime/cuda/cuda_runtime.cc"
+// #include "../../src/runtime/cuda/cuda_module.cc"
 
 // Uncomment the following lines to enable OpenCL
 // #include "../../src/runtime/opencl/opencl_device_api.cc"

From 8a61bb1f13d8843586ae8a848b240c6cd1307fb8 Mon Sep 17 00:00:00 2001
From: Kirill Mavreshko <kimavr@gmail.com>
Date: Sat, 24 Mar 2018 22:12:36 +0500
Subject: [PATCH 218/949] Fix fatal error <CL/opencl.h: No such file> when
 building with CMake (#1045)

---
 CMakeLists.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index eb52d1f82723..ff74458f27d2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -154,7 +154,7 @@ endif(USE_CUDA)
 if(USE_OPENCL)
   find_package(OpenCL QUIET REQUIRED)
   message(STATUS "Build with OpenCL support")
-  include_directories(${OPENCL_INCLUDE_DIRS})
+  include_directories(${OpenCL_INCLUDE_DIRS})
   list(APPEND TVM_RUNTIME_LINKER_LIBS ${OpenCL_LIBRARIES})
   list(APPEND RUNTIME_SRCS ${RUNTIME_OPENCL_SRCS})
   add_definitions(-DTVM_OPENCL_RUNTIME=1)

From b59683841f648f483808482a9778f27ec2a64377 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 24 Mar 2018 18:34:53 -0700
Subject: [PATCH 219/949] [CYTHON] Fix exception propagation for cython3
 (#1046)

---
 python/tvm/_ffi/_cython/base.pxi     | 4 ++--
 python/tvm/_ffi/_cython/function.pxi | 9 +++++----
 2 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/python/tvm/_ffi/_cython/base.pxi b/python/tvm/_ffi/_cython/base.pxi
index b84391648f35..c027c723de08 100644
--- a/python/tvm/_ffi/_cython/base.pxi
+++ b/python/tvm/_ffi/_cython/base.pxi
@@ -64,8 +64,8 @@ ctypedef int (*TVMPackedCFunc)(
 ctypedef void (*TVMPackedCFuncFinalizer)(void* resource_handle)
 
 cdef extern from "tvm/runtime/c_runtime_api.h":
-    void TVMAPISetLastError(const char* msg);
-    const char *TVMGetLastError();
+    void TVMAPISetLastError(const char* msg)
+    const char *TVMGetLastError()
     int TVMFuncCall(TVMFunctionHandle func,
                     TVMValue* arg_values,
                     int* type_codes,
diff --git a/python/tvm/_ffi/_cython/function.pxi b/python/tvm/_ffi/_cython/function.pxi
index 7cadf22a1cc7..50f89a5f4aaa 100644
--- a/python/tvm/_ffi/_cython/function.pxi
+++ b/python/tvm/_ffi/_cython/function.pxi
@@ -74,10 +74,10 @@ def convert_to_tvm_func(object pyfunc):
     return ret
 
 
-cdef inline void make_arg(object arg,
-                          TVMValue* value,
-                          int* tcode,
-                          list temp_args):
+cdef inline int make_arg(object arg,
+                         TVMValue* value,
+                         int* tcode,
+                         list temp_args) except -1:
     """Pack arguments into c args tvm call accept"""
     cdef unsigned long long ptr
     if isinstance(arg, NodeBase):
@@ -152,6 +152,7 @@ cdef inline void make_arg(object arg,
         temp_args.append(arg)
     else:
         raise TypeError("Don't know how to handle type %s" % type(arg))
+    return 0
 
 cdef inline bytearray make_ret_bytes(void* chandle):
     handle = ctypes_handle(chandle)

From 9907c080a6f46a39cfae646bb33369a6572f8757 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 24 Mar 2018 18:58:55 -0700
Subject: [PATCH 220/949] Fix verilog testcase (#1047)

---
 tests/verilog/integration/test_codegen_verilog.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/verilog/integration/test_codegen_verilog.py b/tests/verilog/integration/test_codegen_verilog.py
index e25ff6d9dff4..26c0a9e36c9d 100644
--- a/tests/verilog/integration/test_codegen_verilog.py
+++ b/tests/verilog/integration/test_codegen_verilog.py
@@ -18,7 +18,7 @@ def lower(s, args, name):
     stmt = tvm.ir_pass.CanonicalSimplify(stmt)
     stmt = tvm.ir_pass.Simplify(stmt)
     stmt = tvm.ir_pass.SplitPipeline(stmt, True)
-    fapi = tvm.ir_pass.MakeAPI(stmt, name, arg_list, 0)
+    fapi = tvm.ir_pass.MakeAPI(stmt, name, arg_list, 0, True)
     return fapi
 
 @tvm.register_func

From 2d46d88fb6bc73305737d9fc88eab1d5bfd20006 Mon Sep 17 00:00:00 2001
From: HungMingWu <u9089000@gmail.com>
Date: Sun, 25 Mar 2018 21:32:56 +0800
Subject: [PATCH 221/949] LLVM 7.0 support (#1048)

---
 src/codegen/llvm/llvm_module.cc | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/src/codegen/llvm/llvm_module.cc b/src/codegen/llvm/llvm_module.cc
index 439c132d09a8..33fbf569726c 100644
--- a/src/codegen/llvm/llvm_module.cc
+++ b/src/codegen/llvm/llvm_module.cc
@@ -68,7 +68,11 @@ class LLVMModuleNode final : public runtime::ModuleNode {
     CHECK_EQ(ecode.value(), 0) << "Cannot open file: " << file_name
                                << " " << ecode.message();
     if (fmt == "o" || fmt == "obj") {
+#if TVM_LLVM_VERSION <= 60
       std::unique_ptr<llvm::Module> m = llvm::CloneModule(mptr_);
+#else
+      std::unique_ptr<llvm::Module> m = llvm::CloneModule(*mptr_);
+#endif
       llvm::legacy::PassManager pass;
       CHECK(tm_);
       CHECK(tm_->addPassesToEmitFile(
@@ -76,7 +80,11 @@ class LLVMModuleNode final : public runtime::ModuleNode {
           << "Cannot emit target CGFT_ObjectFile";
       pass.run(*m);
     } else if (fmt == "s" || fmt == "asm") {
+#if TVM_LLVM_VERSION <= 60
       std::unique_ptr<llvm::Module> m = llvm::CloneModule(mptr_);
+#else
+      std::unique_ptr<llvm::Module> m = llvm::CloneModule(*mptr_);
+#endif
       llvm::legacy::PassManager pass;
       CHECK(tm_);
       CHECK(tm_->addPassesToEmitFile(
@@ -86,7 +94,11 @@ class LLVMModuleNode final : public runtime::ModuleNode {
     } else if (fmt == "ll") {
       mptr_->print(dest, nullptr);
     } else if (fmt == "bc") {
+#if TVM_LLVM_VERSION <= 60
       llvm::WriteBitcodeToFile(mptr_, dest);
+#else
+      llvm::WriteBitcodeToFile(*mptr_, dest);
+#endif
     } else {
       LOG(FATAL) << "Do not know how to save file "
                  << file_name << " with format=\'"<< format << "\'";

From 567a10bb0947180b067f39a97c76d7fe7a3ca1f2 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 25 Mar 2018 15:07:57 -0400
Subject: [PATCH 222/949] [CONTRIB] Patch nnvcc to generate error when build
 the empty result (#1049)

---
 README.md                  | 2 +-
 python/tvm/contrib/nvcc.py | 7 +++++--
 2 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 07e550d76043..a25246245c66 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-TVM: Tensor IR Stack for Deep Learning Systems
+<img src=https://raw.githubusercontent.com/tqchen/tvmlang.org/master/images/logo/tvm-logo-small.png width=128/> Tensor IR Stack for Deep Learning Systems
 ==============================================
 
 [![GitHub license](http://dmlc.github.io/img/apache2.svg)](./LICENSE)
diff --git a/python/tvm/contrib/nvcc.py b/python/tvm/contrib/nvcc.py
index e3c2b7895da7..f08d639cf887 100644
--- a/python/tvm/contrib/nvcc.py
+++ b/python/tvm/contrib/nvcc.py
@@ -75,8 +75,11 @@ def compile_cuda(code,
         msg += py_str(out)
         raise RuntimeError(msg)
 
-    return bytearray(open(file_target, "rb").read())
-
+    data = bytearray(open(file_target, "rb").read())
+    if not data:
+        raise RuntimeError(
+            "Compilation error: empty result is generated")
+    return data
 
 def find_cuda_path():
     """Utility function to find cuda path

From bbe4974ef4c5b2c4e4f11c9dd4396c4c0e8e669b Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 26 Mar 2018 10:43:40 -0400
Subject: [PATCH 223/949] [SCHEDULE][PASS] Enable Warp memory and lower to
 shuffle (#1050)

* [SCHEDULE][PASS] Enable Warp memory and lower to shuffle

* OpenCL dispatches for now to intel shuffle
---
 include/tvm/ir.h                              |   8 +
 include/tvm/ir_pass.h                         |   9 +
 python/tvm/build_module.py                    |   4 +
 src/api/api_pass.cc                           |   1 +
 src/codegen/codegen_c.cc                      |  14 +-
 src/codegen/intrin_rule_cuda.cc               |  10 +
 src/codegen/intrin_rule_opencl.cc             |  11 +
 src/pass/ir_util.h                            |  17 +
 src/pass/lower_warp_memory.cc                 | 317 ++++++++++++++++++
 src/schedule/bound.cc                         |  11 +-
 tests/python/integration/test_ewise.py        |  40 +++
 .../unittest/test_pass_lower_warp_memory.py   |  27 ++
 .../unittest/test_schedule_bound_inference.py |  24 ++
 13 files changed, 481 insertions(+), 12 deletions(-)
 create mode 100644 src/pass/lower_warp_memory.cc
 create mode 100644 tests/python/unittest/test_pass_lower_warp_memory.py

diff --git a/include/tvm/ir.h b/include/tvm/ir.h
index f36d914e621f..9e3c8cbc2be1 100644
--- a/include/tvm/ir.h
+++ b/include/tvm/ir.h
@@ -411,6 +411,14 @@ constexpr const char* tvm_call_packed_lowered = "tvm_call_packed_lowered";
  *  }
  */
 constexpr const char* tvm_storage_sync = "tvm_storage_sync";
+/*!
+ * \brief See pseudo code
+ *
+ *  Type tvm_warp_shuffle(Type value, warp_id) {
+ *     return (value passed in by warp indicated by warp_id);
+ *  }
+ */
+constexpr const char* tvm_warp_shuffle = "tvm_warp_shuffle";
 /*!
  * \brief Initialize the global barrier.
  *  Call this at beginning of kernel that need global barrier.
diff --git a/include/tvm/ir_pass.h b/include/tvm/ir_pass.h
index 572385d9a895..1ae41032cbb8 100644
--- a/include/tvm/ir_pass.h
+++ b/include/tvm/ir_pass.h
@@ -407,6 +407,15 @@ LoweredFunc ThreadSync(LoweredFunc stmt, std::string storage_scope);
  */
 LoweredFunc LowerThreadAllreduce(LoweredFunc f, int warp_size);
 
+/*!
+ * \brief Lower warp memory in stmt.
+ * \param f The device function to be lowered.
+ * \param warp_size the size of warp where no sync is needed.
+ *        this function will only take in effect if warp_size is bigger than one.
+ * \return Transformed function.
+ */
+LoweredFunc LowerWarpMemory(LoweredFunc f, int warp_size);
+
 /*!
  * \brief Lower packed function call.
  * \param f The function to be lowered.
diff --git a/python/tvm/build_module.py b/python/tvm/build_module.py
index 03a79860e9ee..0b86cde626af 100755
--- a/python/tvm/build_module.py
+++ b/python/tvm/build_module.py
@@ -450,6 +450,10 @@ def build(sch,
         else:
             raise ValueError("unknown function type %d" % func.func_type)
 
+    for i, func in enumerate(fdevice):
+        warp_size = target.thread_warp_size
+        fdevice[i] = ir_pass.LowerWarpMemory(func, warp_size)
+
     if "gpu" in target.keys and not fdevice:
         warnings.warn(
             "Specified target %s, but cannot find device code, did you do bind?" % target)
diff --git a/src/api/api_pass.cc b/src/api/api_pass.cc
index 7ec6ef4009e4..6d59cb3ae505 100644
--- a/src/api/api_pass.cc
+++ b/src/api/api_pass.cc
@@ -125,6 +125,7 @@ REGISTER_PASS2(SplitPipeline);
 REGISTER_PASS2(LiftAttrScope);
 REGISTER_PASS1(NarrowChannelAccess);
 REGISTER_PASS2(LowerThreadAllreduce);
+REGISTER_PASS2(LowerWarpMemory);
 REGISTER_PASS2(LowerIntrin);
 REGISTER_PASS1(LowerTVMBuiltin);
 REGISTER_PASS1(CombineContextCall);
diff --git a/src/codegen/codegen_c.cc b/src/codegen/codegen_c.cc
index e00cd82abe48..9732f0ef65af 100644
--- a/src/codegen/codegen_c.cc
+++ b/src/codegen/codegen_c.cc
@@ -5,6 +5,7 @@
 #include <iomanip>
 #include <cctype>
 #include "./codegen_c.h"
+#include "../pass/ir_util.h"
 #include "../arithmetic/compute_expr.h"
 
 namespace tvm {
@@ -544,15 +545,6 @@ void CodeGenC::PrintVecBinaryOp(
   }
 }
 
-inline bool TryGetRamp1Base(Expr index, int lanes, Expr *base) {
-  const Ramp* r = index.as<Ramp>();
-  if (!r) return false;
-  if (!is_one(r->stride)) return false;
-  CHECK_EQ(r->lanes, lanes);
-  *base = r->base;
-  return true;
-}
-
 void CodeGenC::VisitExpr_(const Load* op, std::ostream& os) {  // NOLINT(*)
   int lanes = op->type.lanes();
   // delcare type.
@@ -563,7 +555,7 @@ void CodeGenC::VisitExpr_(const Load* op, std::ostream& os) {  // NOLINT(*)
     CHECK(is_one(op->predicate))
         << "predicated load is not supported";
     Expr base;
-    if (TryGetRamp1Base(op->index, op->type.lanes(), &base)) {
+    if (GetRamp1Base(op->index, op->type.lanes(), &base)) {
       std::string ref = GetVecLoad(op->type, op->buffer_var.get(), base);
       os << ref;
     } else {
@@ -617,7 +609,7 @@ void CodeGenC::VisitStmt_(const Store* op) {
     CHECK(is_one(op->predicate))
         << "Predicated store is not supported";
     Expr base;
-    if (TryGetRamp1Base(op->index, t.lanes(), &base)) {
+    if (GetRamp1Base(op->index, t.lanes(), &base)) {
       std::string value = this->PrintExpr(op->value);
       this->PrintVecStore(op->buffer_var.get(), t, base, value);
     } else {
diff --git a/src/codegen/intrin_rule_cuda.cc b/src/codegen/intrin_rule_cuda.cc
index 9abb99d7c7c5..1d199fe5af28 100644
--- a/src/codegen/intrin_rule_cuda.cc
+++ b/src/codegen/intrin_rule_cuda.cc
@@ -49,6 +49,12 @@ struct CUDAPopcount {
   }
 };
 
+struct CUDAShuffle {
+  std::string operator()(Type t, std::string name) const {
+    return "__shfl";
+  }
+};
+
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.cuda.exp")
 .set_body(DispatchExtern<CUDAFastMath>);
 
@@ -67,6 +73,10 @@ TVM_REGISTER_GLOBAL("tvm.intrin.rule.cuda.pow")
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.cuda.popcount")
 .set_body(DispatchExtern<CUDAPopcount>);
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.cuda.tvm_warp_shuffle")
+.set_body(DispatchExtern<CUDAShuffle>);
+
+
 }  // namespace intrin
 }  // namespace codegen
 }  // namespace tvm
diff --git a/src/codegen/intrin_rule_opencl.cc b/src/codegen/intrin_rule_opencl.cc
index 924abcade63f..b8b2412215d1 100644
--- a/src/codegen/intrin_rule_opencl.cc
+++ b/src/codegen/intrin_rule_opencl.cc
@@ -27,6 +27,17 @@ TVM_REGISTER_GLOBAL("tvm.intrin.rule.opencl.pow")
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.opencl.popcount")
 .set_body(DispatchExtern<Direct>);
 
+// There is no warp shuffle instruction in standard OpenCL
+// When shuffle is used, we assume it is intel's shuffle extension
+struct IntelShuffle {
+  std::string operator()(Type t, std::string name) const {
+    return "intel_sub_group_shuffle";
+  }
+};
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.opencl.tvm_warp_shuffle")
+.set_body(DispatchExtern<IntelShuffle>);
+
 }  // namespace intrin
 }  // namespace codegen
 }  // namespace tvm
diff --git a/src/pass/ir_util.h b/src/pass/ir_util.h
index 96a41b120e46..f871133fb74f 100644
--- a/src/pass/ir_util.h
+++ b/src/pass/ir_util.h
@@ -161,6 +161,23 @@ inline int GetTempAllocaAlignment(Type type, int32_t const_size) {
   }
   return align;
 }
+
+/*!
+ * \brief Pattern match index to Ramp with stride=1
+ *        This is a common pattern in continuous memory load.
+ * \param index The index formula
+ * \param lanes number of lanes in the ramp
+ * \param base The result base.
+ * \return true if pattern match success and store the base to base.
+ */
+inline bool GetRamp1Base(Expr index, int lanes, Expr *base) {
+  const Ramp* r = index.as<Ramp>();
+  if (!r) return false;
+  if (!is_one(r->stride)) return false;
+  CHECK_EQ(r->lanes, lanes);
+  *base = r->base;
+  return true;
+}
 }  // namespace ir
 }  // namespace tvm
 #endif  // TVM_PASS_IR_UTIL_H_
diff --git a/src/pass/lower_warp_memory.cc b/src/pass/lower_warp_memory.cc
new file mode 100644
index 000000000000..dbd073c0b14c
--- /dev/null
+++ b/src/pass/lower_warp_memory.cc
@@ -0,0 +1,317 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ *
+ * Lower warp memory to use local memory
+ * and shuffle intrinsics.
+ *
+ * \file lower_warp_memory.cc
+ */
+// Thanks to Andrew Adams and Vinod Grover for
+// explaining the concept of warp shuffle.
+#include <tvm/ir.h>
+#include <tvm/ir_mutator.h>
+#include <tvm/ir_visitor.h>
+#include <tvm/ir_pass.h>
+#include <unordered_set>
+#include "./ir_util.h"
+#include "../arithmetic/compute_expr.h"
+#include "../runtime/thread_storage_scope.h"
+
+namespace tvm {
+namespace ir {
+
+// Rewrite Rule
+//
+// There is no special warp memory in most GPUs.
+// Instead, we can stripe the data into threads
+// and store the data into local memory.
+//
+// This requires us to do the following rewriting:
+// - Rewrite allocation to use local memory.
+// - Rewrite store of warp memory to local store.
+// - Rewrite load of waro memory to local plus a shuffle.
+//
+// Define a generic shuffle instrinsic warp_shuffle(data, warp_index).
+// We can use the following rewriting rule
+//
+// Before rewrite,
+//
+//   alloc warp warp_mem[n * warp_size * m]
+//   store warp_mem[m * warp_index + (warp_size * m) * y + x]
+//   load warp_mem[m * z + (warp_size * m) * y + x]
+//   subject to x \in [0, m), y \in [0, n)
+//
+// After rewrite:
+//
+//   alloc local local_mem[n * m]
+//   store warp_mem[m * y + x]
+//   warp_shuffle(load warp_mem[m * y + x], z)
+//   subject to (m * y + x) is invariant to warp_index
+
+// Algorithm
+//
+// To implement this rewrite rule, we can do the follow step:
+// For each warp memory alloc
+// - Use linear pattern detector on load index to find m
+// - Deduce n given warp_size and alloc size
+// - Now that we have m, n, warp_size, we can proceed with the rewrite
+
+// Visitor to find m in pattern
+// store warp_mem[m * warp_index + (warp_size * m) * y + x]
+class WarpStoreCoeffFinder : private IRVisitor {
+ public:
+  WarpStoreCoeffFinder(const Variable* buffer,
+                       Var warp_index)
+      : buffer_(buffer), warp_index_(warp_index) {
+  }
+  // find the warp co-efficient in the statement given the warp size
+  int Find(const Stmt& stmt) {
+    this->Visit(stmt);
+    return warp_coeff_;
+  }
+
+ private:
+  /// Visitor implementation
+  void Visit_(const Store *op) final {
+    if (op->buffer_var.get() == buffer_) {
+      if (op->value.type().lanes() == 1) {
+        UpdatePattern(op->index);
+      } else {
+        Expr base;
+        CHECK(GetRamp1Base(op->index, op->value.type().lanes(), &base))
+            << "LowerWarpMemory failed due to store index=" << op->index
+            << ", can only handle continuous store";
+        UpdatePattern(base);
+      }
+    } else {
+      IRVisitor::Visit_(op);
+    }
+  }
+
+  void UpdatePattern(const Expr& index) {
+    Array<Expr> m =
+        arith::DetectLinearEquation(index, {warp_index_});
+    CHECK_EQ(m.size(), 2U)
+        << "LowerWarpMemory failed due to store index=" << index;
+    int coeff;
+    CHECK(arith::GetConstInt(ir::Simplify(m[0]), &coeff) && coeff > 0)
+        << "LowerWarpMemory failed due to store index=" << index
+        << ", require positive constant coefficient on warp index";
+    if (warp_coeff_ != 0) {
+      CHECK_EQ(warp_coeff_, coeff)
+          << "LowerWarpMemory failed due to two different store coefficient to warp index";
+    } else {
+      warp_coeff_ = coeff;
+    }
+  }
+
+  // The buffer variable
+  const Variable* buffer_;
+  // the warp index
+  Var warp_index_;
+  // the coefficient
+  int warp_coeff_{0};
+};
+
+
+// Visitor to find the warp index
+class WarpIndexFinder : private IRVisitor {
+ public:
+  explicit WarpIndexFinder(int warp_size)
+      : warp_size_(warp_size) {
+  }
+  // find the warp co-efficient in the statement given the warp size
+  IterVar Find(const Stmt& stmt) {
+    this->Visit(stmt);
+    CHECK(warp_index_.defined())
+        << "Cannot find warp index(threadIdx.x) within the scope of warp memory";
+    return warp_index_;
+  }
+
+ private:
+  void Visit(const NodeRef &node) final {
+    if (warp_index_.defined()) return;
+    IRVisitor::Visit(node);
+  }
+
+  /// Visitor implementation
+  void Visit_(const AttrStmt *op) final {
+    if (op->attr_key == attr::thread_extent) {
+      IterVar iv(op->node.node_);
+      if (iv->thread_tag == "threadIdx.x") {
+        int value;
+        CHECK(arith::GetConstInt(op->value, &value) &&
+              value == warp_size_)
+            << "Expect threadIdx.x 's size to be equal to warp size("
+            << warp_size_ << ")" << " to enable warp memory"
+            << " but get " << op->value << " instead";
+        warp_index_ = iv;
+      }
+    }
+    IRVisitor::Visit_(op);
+  }
+  // warp size
+  int warp_size_{0};
+  // the warp index
+  IterVar warp_index_{nullptr};
+};
+// Mutator to change the read pattern
+class WarpAccessRewriter : protected IRMutator {
+ public:
+  explicit WarpAccessRewriter(int warp_size)
+      : warp_size_(warp_size) {}
+  // Rewrite the allocate statement which transforms
+  // warp memory to local memory.
+  Stmt Rewrite(const Allocate* op, const Stmt& stmt) {
+    buffer_ = op->buffer_var.get();
+    int alloc_size = op->constant_allocation_size();
+    CHECK_GT(alloc_size, 0)
+        << "warp memory only support constant alloc size";
+    alloc_size *= op->type.lanes();
+    warp_index_ = WarpIndexFinder(warp_size_).Find(op->body)->var;
+    warp_coeff_ = WarpStoreCoeffFinder(
+        buffer_, warp_index_).Find(op->body);
+    CHECK_EQ(alloc_size % (warp_size_ * warp_coeff_), 0)
+        << "Warp memory must be multiple of warp size";
+    warp_group_ = alloc_size / (warp_size_ * warp_coeff_);
+    return Allocate::make(
+        op->buffer_var,
+        op->type,
+        {make_const(Int(32), alloc_size / warp_size_)},
+        op->condition,
+        this->Mutate(op->body));
+  }
+
+ protected:
+  Expr Mutate_(const Variable* op, const Expr& expr) {
+    CHECK(op != buffer_)
+        << "Cannot access address of warp memory directly";
+    return IRMutator::Mutate_(op, expr);
+  }
+
+  Stmt Mutate_(const Store* op, const Stmt& stmt) {
+    if (op->buffer_var.get() == buffer_) {
+      Expr local_index, group;
+      std::tie(local_index, group) = SplitIndexByGroup(op->index);
+      return Store::make(op->buffer_var, op->value, local_index, op->predicate);
+    } else {
+      return IRMutator::Mutate_(op, stmt);
+    }
+  }
+
+  Expr Mutate_(const Load* op, const Expr& expr) {
+    if (op->buffer_var.get() == buffer_) {
+      Expr local_index, group;
+      std::tie(local_index, group) = SplitIndexByGroup(op->index);
+      // invariance: local index must do not contain warp id
+      CHECK(!ExprUseVar(local_index, {warp_index_.get()}))
+          << "LowerWarpMemory failed to rewrite load to shuffle for index "
+          << op->index << " local_index=" << local_index;
+      Expr load_value = Load::make(
+          op->type, op->buffer_var, local_index, op->predicate);
+      return Call::make(load_value.type(),
+                        intrinsic::tvm_warp_shuffle,
+                        {load_value, group},
+                        Call::Intrinsic);
+    } else {
+      return IRMutator::Mutate_(op, expr);
+    }
+  }
+  // Split the index to the two component
+  // <local_index, source_index>
+  // local index is the index in the local
+  // source index is the corresponding source index
+  // in this access pattern.
+  std::pair<Expr, Expr> SplitIndexByGroup(const Expr& index) {
+    if (index.type().lanes() != 1) {
+      Expr base, local_index, group;
+      CHECK(GetRamp1Base(index, index.type().lanes(), &base));
+      std::tie(local_index, group) = SplitIndexByGroup(base);
+      local_index =
+          Ramp::make(local_index, make_const(local_index.type(), 1), index.type().lanes());
+      return std::make_pair(local_index, group);
+    }
+    Expr m = make_const(index.type(), warp_coeff_);
+    Range rng = Range::make_by_min_extent(
+        make_zero(index.type()), make_const(index.type(), warp_size_));
+    Map<Var, Range> vrange({{warp_index_, rng}});
+
+    // simple case, warp index is on the highest.
+    if (warp_group_ == 1) {
+      Expr x = Simplify(index % m, vrange);
+      Expr z = Simplify(index / m, vrange);
+      return std::make_pair(x, z);
+    } else {
+      Expr x = Simplify(index % m, vrange);
+      Expr y = index / make_const(index.type(), warp_coeff_ * warp_size_);
+      y = y * m + x;
+      Expr z = index % make_const(index.type(), warp_coeff_ * warp_size_) / m;
+      return std::make_pair(Simplify(y, vrange), Simplify(z, vrange));
+    }
+  }
+
+ private:
+  // the warp size
+  int warp_size_{0};
+  // The buffer variable
+  const Variable* buffer_;
+  // Warp index
+  Var warp_index_;
+  // the coefficient m
+  int warp_coeff_{0};
+  // the coefficient n
+  int warp_group_{0};
+};
+
+// Mutator to change the read pattern
+class WarpMemoryRewriter : private IRMutator {
+ public:
+  explicit WarpMemoryRewriter(int warp_size)
+      : warp_size_(warp_size) {
+  }
+
+  Stmt Rewrite(Stmt stmt) {
+    if (warp_size_ == 1) return stmt;
+    return this->Mutate(stmt);
+  }
+
+ private:
+  Stmt Mutate_(const Allocate* op, const Stmt& stmt) {
+    if (warp_buffer_.count(op->buffer_var.get())) {
+      WarpAccessRewriter rewriter(warp_size_);
+      return rewriter.Rewrite(op, stmt);
+    } else {
+      return IRMutator::Mutate_(op, stmt);
+    }
+  }
+
+  Stmt Mutate_(const AttrStmt* op, const Stmt& stmt) {
+    using runtime::StorageScope;
+    if (op->attr_key == attr::storage_scope) {
+      const Variable* buf = op->node.as<Variable>();
+      StorageScope scope = StorageScope::make(op->value.as<StringImm>()->value);
+      if (scope.rank == runtime::StorageRank::kWarp) {
+        warp_buffer_.insert(buf);
+        Stmt ret = IRMutator::Mutate_(op, stmt);
+        op = ret.as<AttrStmt>();
+        return AttrStmt::make(
+            op->node, op->attr_key, StringImm::make("local"), op->body);
+      }
+    }
+    return IRMutator::Mutate_(op, stmt);
+  }
+
+  int warp_size_{0};
+  std::unordered_set<const Variable*> warp_buffer_;
+};
+
+LoweredFunc
+LowerWarpMemory(LoweredFunc f, int warp_size) {
+  CHECK_EQ(f->func_type, kDeviceFunc);
+  auto n = std::make_shared<LoweredFuncNode>(*f.operator->());
+  n->body = WarpMemoryRewriter(warp_size).Rewrite(n->body);
+  return LoweredFunc(n);
+}
+
+}  // namespace ir
+}  // namespace tvm
diff --git a/src/schedule/bound.cc b/src/schedule/bound.cc
index 908b579ec9a4..7929969a8502 100644
--- a/src/schedule/bound.cc
+++ b/src/schedule/bound.cc
@@ -42,7 +42,16 @@ bool NeedRelax(const IterVar& iv,
   if (tag.length() == 0 || tag == "pipeline") {
     return !found_attach;
   }
-  return static_cast<int>(scope.rank) <= ThreadScope::make(tag).rank;
+  ThreadScope ts = ThreadScope::make(tag);
+
+  // When there is warp memory
+  // threadIdx.x must be set to be warp index.
+  if (scope.rank == StorageRank::kWarp &&
+      ts.rank == 1 &&
+      ts.dim_index == 0) {
+    return true;
+  }
+  return static_cast<int>(scope.rank) <= ts.rank;
 }
 
 // infer storage scope, if not given
diff --git a/tests/python/integration/test_ewise.py b/tests/python/integration/test_ewise.py
index 414b1ff008fe..ee880ed1d9fb 100644
--- a/tests/python/integration/test_ewise.py
+++ b/tests/python/integration/test_ewise.py
@@ -1,4 +1,5 @@
 import tvm
+from tvm.contrib import nvcc
 import numpy as np
 import time
 
@@ -155,7 +156,46 @@ def check_device(device):
     run("uint64")
 
 
+def try_warp_memory():
+    """skip this in default test because it require higher arch"""
+    m = 128
+    A = tvm.placeholder((m,), name='A')
+    B = tvm.compute((m,), lambda i: A[i] + 3, name='B')
+    warp_size = 32
+    s = tvm.create_schedule(B.op)
+    AA = s.cache_read(A, "warp", [B])
+    xo, xi = s[B].split(B.op.axis[0], warp_size * 2)
+    xi0, xi1 = s[B].split(xi, factor=warp_size)
+    tx = tvm.thread_axis("threadIdx.x")
+    s[B].bind(xi1, tx)
+    s[B].bind(xo, tvm.thread_axis("blockIdx.x"))
+    s[AA].compute_at(s[B], xo)
+    xo, xi = s[AA].split(s[AA].op.axis[0], warp_size)
+    s[AA].bind(xi, tx)
+
+    @tvm.register_func
+    def tvm_callback_cuda_compile(code):
+        ptx =  nvcc.compile_cuda(code, target="ptx")
+        return ptx
+
+    # one line to build the function.
+    def check_device(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
+            print("skip because %s is not enabled.." % device)
+            return
+        f = tvm.build(s, [A, B], device)
+        a = tvm.nd.array((np.random.uniform(size=m) * 256).astype(A.dtype), ctx)
+        b = tvm.nd.array(np.zeros(m, dtype=B.dtype), ctx)
+        f(a, b)
+        np.testing.assert_allclose(
+            b.asnumpy(), a.asnumpy() + 3, rtol=1e-6)
+
+    check_device("cuda")
+
+
 if __name__ == "__main__":
+    try_warp_memory()
     test_add()
     test_log_pow_llvm()
     test_exp()
diff --git a/tests/python/unittest/test_pass_lower_warp_memory.py b/tests/python/unittest/test_pass_lower_warp_memory.py
new file mode 100644
index 000000000000..9793b21371bd
--- /dev/null
+++ b/tests/python/unittest/test_pass_lower_warp_memory.py
@@ -0,0 +1,27 @@
+import tvm
+
+def test_lower_warp_mem():
+    m = 128
+    A = tvm.placeholder((m,), name='A')
+    B = tvm.compute((m,), lambda i: A[i] + 3, name='B')
+
+    s = tvm.create_schedule(B.op)
+    AA = s.cache_read(A, "warp", [B])
+    xo, xi = s[B].split(B.op.axis[0], 32)
+    xi0, xi1 = s[B].split(xi, factor=16)
+    tx = tvm.thread_axis("threadIdx.x")
+    s[B].bind(xi1, tx)
+    s[B].bind(xo, tvm.thread_axis("blockIdx.x"))
+    s[AA].compute_at(s[B], xo)
+    xo, xi = s[AA].split(s[AA].op.axis[0], 16)
+    s[AA].bind(xi, tx)
+
+    f = tvm.lower(s, [A, B])
+    fhost, fdevice = tvm.ir_pass.SplitHostDevice(f)
+    fdevice = tvm.ir_pass.LowerWarpMemory(fdevice, 16)
+    assert(fdevice.body.body.value.value == "local")
+    assert(fdevice.body.body.body.extents[0].value == 2)
+
+
+if __name__ == "__main__":
+    test_lower_warp_mem()
diff --git a/tests/python/unittest/test_schedule_bound_inference.py b/tests/python/unittest/test_schedule_bound_inference.py
index 3601833de08d..30be3783bbb3 100644
--- a/tests/python/unittest/test_schedule_bound_inference.py
+++ b/tests/python/unittest/test_schedule_bound_inference.py
@@ -53,6 +53,29 @@ def test_bound3():
     assert(bounds[A1.op.axis[0]].extent.value==32)
     assert(bounds[A1.op.axis[1]].extent.value==16)
 
+
+def test_bound_warp():
+    m = tvm.var('m')
+    l = tvm.var('l')
+    A = tvm.placeholder((m, l), name='A')
+    A1 = tvm.compute((m, l), lambda i, j: A[i, j], name='A1')
+    A2 = tvm.compute((m, l), lambda i, j: A1[i, j] + 3, name='A2')
+
+    s = tvm.create_schedule(A2.op)
+    s[A1].set_scope("warp")
+    xo, xi = s[A2].split(A2.op.axis[0], 32)
+    xi0, xi1 = s[A2].split(xi, factor=16)
+    tx = tvm.thread_axis("threadIdx.x")
+    s[A2].bind(xi1, tx)
+    s[A2].bind(xi0, tvm.thread_axis("threadIdx.y"))
+    y = s[A2].op.axis[1]
+    s[A1].compute_at(s[A2], y)
+    xo, xi = s[A1].split(s[A1].op.axis[0], factor=16)
+    s[A1].bind(xi, tx)
+    bounds = tvm.schedule.InferBound(s)
+    assert isinstance(bounds, tvm.container.Map)
+    assert(bounds[A1.op.axis[0]].extent.value==16)
+
 def test_bound_scan():
     m = tvm.var("m")
     n = tvm.var("n")
@@ -249,3 +272,4 @@ def test_gemm_bound():
     test_bound_conv1d()
     test_bound2()
     test_gemm_bound()
+    test_bound_warp()

From f3b6a0bc2c001771b9036dd780c7edb45eb1cc76 Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Tue, 27 Mar 2018 22:24:49 +0530
Subject: [PATCH 224/949] Update softmax.h (#1057)

---
 topi/include/topi/nn/softmax.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/topi/include/topi/nn/softmax.h b/topi/include/topi/nn/softmax.h
index f542f0eb0256..d17f93046e72 100644
--- a/topi/include/topi/nn/softmax.h
+++ b/topi/include/topi/nn/softmax.h
@@ -83,7 +83,7 @@ inline Tensor softmax(const Tensor &x,
   });
   return tvm::compute(input_shape, [&](const Array<Var> &indices) {
       return _normalize(max_elem, expsum, indices);
-  });
+  }, name, tag);
 }
 
 /*!
@@ -116,7 +116,7 @@ inline Tensor log_softmax(const Tensor& x,
   return tvm::compute(
     x->shape, [&](Var i, Var j) {
       return x(i, j) - max_elem(i) - tvm::log(expsum(i));
-    });
+    }, name, tag);
 }
 
 }  // namespace nn

From 7ea69dbf399bf38d114ab4516501c34920f4e925 Mon Sep 17 00:00:00 2001
From: Ding <37059654+dingobye@users.noreply.github.com>
Date: Wed, 28 Mar 2018 03:57:21 +1100
Subject: [PATCH 225/949]  [TOPI] Overload operators of Tensor when TOPI is
 imported (#1029)

---
 python/tvm/__init__.py                        |   1 +
 python/tvm/expr.py                            |  16 +-
 python/tvm/generic.py                         |  81 ++++++
 .../unittest/test_lang_tensor_overload_op.py  | 249 ++++++++++++++++++
 topi/python/topi/__init__.py                  |   1 +
 topi/python/topi/generic_op_impl.py           | 123 +++++++++
 6 files changed, 464 insertions(+), 7 deletions(-)
 create mode 100644 python/tvm/generic.py
 create mode 100644 tests/python/unittest/test_lang_tensor_overload_op.py
 create mode 100644 topi/python/topi/generic_op_impl.py

diff --git a/python/tvm/__init__.py b/python/tvm/__init__.py
index 1119dd3fcfc0..e4068cca3b9f 100644
--- a/python/tvm/__init__.py
+++ b/python/tvm/__init__.py
@@ -15,6 +15,7 @@
 from . import node
 from . import ir_builder
 from . import target
+from . import generic
 
 from . import ndarray as nd
 from .ndarray import context, cpu, gpu, opencl, cl, vulkan, metal, mtl
diff --git a/python/tvm/expr.py b/python/tvm/expr.py
index 750e78909c7a..5ba2928306e8 100644
--- a/python/tvm/expr.py
+++ b/python/tvm/expr.py
@@ -18,32 +18,34 @@
 from __future__ import absolute_import as _abs
 from ._ffi.node import NodeBase, NodeGeneric, register_node
 from . import make as _make
+from . import generic as _generic
 from . import _api_internal
 
+
 class ExprOp(object):
     def __add__(self, other):
-        return _make.Add(self, other)
+        return _generic.add(self, other)
 
     def __radd__(self, other):
         return self.__add__(other)
 
     def __sub__(self, other):
-        return _make.Sub(self, other)
+        return _generic.subtract(self, other)
 
     def __rsub__(self, other):
-        return _make.Sub(other, self)
+        return _generic.subtract(other, self)
 
     def __mul__(self, other):
-        return _make.Mul(self, other)
+        return _generic.multiply(self, other)
 
     def __rmul__(self, other):
-        return _make.Mul(other, self)
+        return _generic.multiply(other, self)
 
     def __div__(self, other):
-        return _make.Div(self, other)
+        return _generic.divide(self, other)
 
     def __rdiv__(self, other):
-        return _make.Div(other, self)
+        return _generic.divide(other, self)
 
     def __truediv__(self, other):
         return self.__div__(other)
diff --git a/python/tvm/generic.py b/python/tvm/generic.py
new file mode 100644
index 000000000000..7e70d66e432a
--- /dev/null
+++ b/python/tvm/generic.py
@@ -0,0 +1,81 @@
+"""Generic opertors in TVM.
+We follow the numpy naming convention for this interface
+(e.g., tvm.generic.multitply ~ numpy.multiply).
+The default implementation is used by tvm.ExprOp.
+"""
+# pylint: disable=unused-argument
+from . import make as _make
+
+#Operator precedence used when overloading.
+__op_priority__ = 0
+
+def add(lhs, rhs):
+    """Generic add operator.
+
+    Parameters
+    ----------
+    lhs : object
+        The left operand.
+    rhs : object
+        The right operand.
+
+    Returns
+    -------
+    op : tvm.Expr
+        The result Expr of add operaton.
+    """
+    return _make.Add(lhs, rhs)
+
+
+def subtract(lhs, rhs):
+    """Generic subtract operator.
+
+    Parameters
+    ----------
+    lhs : object
+        The left operand.
+    rhs : object
+        The right operand.
+
+    Returns
+    -------
+    op : tvm.Expr
+        The result Expr of subtract operaton.
+    """
+    return _make.Sub(lhs, rhs)
+
+
+def multiply(lhs, rhs):
+    """Generic multiply operator.
+
+    Parameters
+    ----------
+    lhs : object
+        The left operand.
+    rhs : object
+        The right operand.
+
+    Returns
+    -------
+    op : tvm.Expr
+        The result Expr of multiply operaton.
+    """
+    return _make.Mul(lhs, rhs)
+
+
+def divide(lhs, rhs):
+    """Generic divide operator.
+
+    Parameters
+    ----------
+    lhs : object
+        The left operand.
+    rhs : object
+        The right operand.
+
+    Returns
+    -------
+    op : tvm.Expr
+        The result Expr of divide operaton.
+    """
+    return _make.Div(lhs, rhs)
diff --git a/tests/python/unittest/test_lang_tensor_overload_op.py b/tests/python/unittest/test_lang_tensor_overload_op.py
new file mode 100644
index 000000000000..c1d860b89b61
--- /dev/null
+++ b/tests/python/unittest/test_lang_tensor_overload_op.py
@@ -0,0 +1,249 @@
+import numpy as np
+import tvm
+import topi
+import topi.testing
+from topi.util import get_const_tuple
+
+
+def test_operator_type_and_tags():
+    k = 1
+    n = tvm.var('n')
+    A = tvm.placeholder((), name='A')
+    B = tvm.placeholder((10, 5), name='B')
+    B1 = B[0]
+    B2 = B[0,0]
+
+    assert isinstance(k + n, tvm.expr.Expr)
+    assert isinstance(n + n, tvm.expr.Expr)
+    assert isinstance(k + A, tvm.expr.Expr)
+    assert isinstance(A + k, tvm.expr.Expr)
+    assert isinstance(n + A, tvm.expr.Expr)
+    assert isinstance(A + n, tvm.expr.Expr)
+    assert isinstance(A + A, tvm.expr.Expr)
+
+    assert isinstance(k + B, tvm.tensor.Tensor)
+    assert isinstance(B + k, tvm.tensor.Tensor)
+    assert isinstance(n + B, tvm.tensor.Tensor)
+    assert isinstance(B + n, tvm.tensor.Tensor)
+    assert isinstance(A + B, tvm.tensor.Tensor)
+    assert isinstance(B + A, tvm.tensor.Tensor)
+    assert isinstance(B + B, tvm.tensor.Tensor)
+
+    assert (k + B).op.tag == topi.tag.ELEMWISE
+    assert (B + k).op.tag == topi.tag.ELEMWISE
+    assert (n + B).op.tag == topi.tag.ELEMWISE
+    assert (B + n).op.tag == topi.tag.ELEMWISE
+    assert (A + B).op.tag == topi.tag.ELEMWISE
+    assert (B + A).op.tag == topi.tag.ELEMWISE
+    assert (B + B).op.tag == topi.tag.BROADCAST
+
+    assert isinstance(k + B2, tvm.expr.Expr)
+    assert isinstance(B2 + k, tvm.expr.Expr)
+    assert isinstance(n + B2, tvm.expr.Expr)
+    assert isinstance(B2 + n, tvm.expr.Expr)
+    assert isinstance(B2 + B2, tvm.expr.Expr)
+    assert isinstance(B2 + A, tvm.expr.Expr)
+    assert isinstance(A + B2, tvm.expr.Expr)
+    assert isinstance(B2 + B, tvm.tensor.Tensor)
+    assert isinstance(B + B2, tvm.tensor.Tensor)
+
+
+def test_combination():
+    k = 3
+    n = 5
+    m = 10
+    x = tvm.var('x')
+    A = tvm.placeholder((n, m), name='A')
+    B = tvm.placeholder((n, m), name='B')
+    C = tvm.placeholder((n, m), name='C')
+    D = k + A - B * C / x
+    s = tvm.create_schedule(D.op)
+    foo = tvm.build(s, [x, A, B, C, D], "llvm")
+    ctx = tvm.cpu(0)
+    x = 2
+    a = tvm.nd.array(np.random.uniform(size=(n, m)).astype(A.dtype), ctx)
+    b = tvm.nd.array(np.random.uniform(size=(n, m)).astype(B.dtype), ctx)
+    c = tvm.nd.array(np.random.uniform(size=(n, m)).astype(C.dtype), ctx)
+    d = tvm.nd.array(np.zeros((n, m), dtype=D.dtype), ctx)
+    foo(x, a, b, c, d)
+    np.testing.assert_allclose(d.asnumpy(), k + a.asnumpy() - b.asnumpy() * c.asnumpy() / x)
+
+
+def verify_tensor_scalar_bop(shape, typ="add"):
+    """Verify non-constant Tensor and scalar binary operations."""
+    sh = [tvm.var('n%d' % i) for i in range(0, len(shape))]
+    k = tvm.var('k')
+    A = tvm.placeholder(sh, name='A')
+    if typ == "add":
+        B = A + k
+    elif typ == "sub":
+        B = A - k
+    elif typ == "mul":
+        B = A * k
+    elif typ == "div":
+        B = A / k
+    else:
+        raise NotImplementedError()
+
+    def check_device(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s = topi.generic.schedule_elemwise(B)
+
+        k_ = 2
+        foo = tvm.build(s, [A, B, k] + sh, device, name="tensor_scalar_" + typ)
+        a_npy = np.random.uniform(size=shape).astype(A.dtype)
+        if typ == "add":
+            b_npy = a_npy + k_
+        elif typ == "sub":
+            b_npy = a_npy - k_
+        elif typ == "mul":
+            b_npy = a_npy * k_
+        elif typ == "div":
+            b_npy = a_npy / k_
+        else:
+            raise NotImplementedError()
+
+        a_nd = tvm.nd.array(a_npy, ctx)
+        b_nd = tvm.nd.array(np.empty(b_npy.shape).astype(B.dtype), ctx)
+        foo(a_nd, b_nd, k_, *shape)
+        np.testing.assert_allclose(b_nd.asnumpy(), b_npy, rtol=1e-5)
+
+    for device in ['llvm', 'cuda', 'opencl', 'metal', 'rocm', 'vulkan']:
+        check_device(device)
+
+
+def verify_broadcast_bop(lhs_shape, rhs_shape, typ="add"):
+    A = tvm.placeholder(shape=lhs_shape, name="A")
+    B = tvm.placeholder(shape=rhs_shape, name="B")
+    if typ == "add":
+        C = A + B
+    elif typ == "sub":
+        C = A - B
+    elif typ == "mul":
+        C = A * B
+    elif typ == "div":
+        C = A / B
+    else:
+        raise NotImplementedError()
+
+    def check_device(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s = topi.generic.schedule_broadcast(C)
+
+        foo = tvm.build(s, [A, B, C], device, name="broadcast_binary" + "_" + typ)
+        lhs_npy = np.random.uniform(size=lhs_shape).astype(A.dtype)
+        rhs_npy = np.random.uniform(size=rhs_shape).astype(A.dtype)
+        if typ == "add":
+            out_npy = lhs_npy + rhs_npy
+        elif typ == "sub":
+            out_npy = lhs_npy - rhs_npy
+        elif typ == "mul":
+            out_npy = lhs_npy * rhs_npy
+        elif typ == "div":
+            rhs_npy = np.abs(rhs_npy) + 0.001
+            out_npy = lhs_npy / rhs_npy
+        else:
+            raise NotImplementedError()
+
+        lhs_nd = tvm.nd.array(lhs_npy, ctx)
+        rhs_nd = tvm.nd.array(rhs_npy, ctx)
+        out_nd = tvm.nd.array(np.empty(out_npy.shape).astype(B.dtype), ctx)
+        for _ in range(1):
+            foo(lhs_nd, rhs_nd, out_nd)
+        np.testing.assert_allclose(out_nd.asnumpy(), out_npy, rtol=1E-4, atol=1E-4)
+
+    for device in ['llvm', 'cuda', 'opencl', 'metal', 'rocm', 'vulkan']:
+        check_device(device)
+
+
+def verify_conv2d_scalar_bop(batch, in_size, in_channel, num_filter, kernel, stride, padding, typ="add"):
+    def check_device(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+
+        k = 10.0
+        with tvm.target.create(device):
+            A = tvm.placeholder((batch, in_channel, in_size, in_size), name='A')
+            W = tvm.placeholder((num_filter, in_channel, kernel, kernel), name='W')
+            B = topi.nn.conv2d(A, W, stride, padding)
+            if typ == "add":
+                C = B + k
+            elif typ == "sub":
+                C = B - k
+            elif typ == "mul":
+                C = B * k
+            elif typ == "div":
+                C = B / k
+            else:
+                raise NotImplementedError()
+            s = topi.generic.schedule_conv2d_nchw([C])
+
+        foo = tvm.build(s, [A, W, B, C], device, name="conv2d_scalar_" + typ)
+
+        a_npy = np.random.uniform(size=get_const_tuple(A.shape)).astype(A.dtype)
+        w_npy = np.random.uniform(size=get_const_tuple(W.shape)).astype(W.dtype)
+        b_npy = topi.testing.conv2d_nchw_python(a_npy, w_npy, stride, padding)
+        c_npy = np.random.uniform(size=get_const_tuple(B.shape)).astype(B.dtype)
+        if typ == "add":
+            c_npy = b_npy + k
+        elif typ == "sub":
+            c_npy = b_npy - k
+        elif typ == "mul":
+            c_npy = b_npy * k
+        elif typ == "div":
+            c_npy = b_npy / k
+        else:
+            raise NotImplementedError()
+
+        a_nd = tvm.nd.array(a_npy, ctx)
+        w_nd = tvm.nd.array(w_npy, ctx)
+        b_nd = tvm.nd.array(np.empty(b_npy.shape).astype(B.dtype), ctx)
+        c_nd = tvm.nd.array(np.empty(c_npy.shape).astype(C.dtype), ctx)
+        foo(a_nd, w_nd, b_nd, c_nd)
+        np.testing.assert_allclose(c_nd.asnumpy(), c_npy, rtol=1E-4, atol=1E-4)
+
+    for device in ['llvm', 'cuda', 'opencl', 'metal', 'rocm', 'vulkan']:
+        check_device(device)
+
+
+def test_tensor_scalar_bop():
+    verify_tensor_scalar_bop((1,), typ="add")
+    verify_tensor_scalar_bop((3, 5), typ="sub")
+    verify_tensor_scalar_bop((1, 3, 5), typ="mul")
+    verify_tensor_scalar_bop((2, 3, 1, 32), typ="div")
+
+
+def test_broadcast_bop():
+    verify_broadcast_bop((2, 3), (), typ="add")
+    verify_broadcast_bop((5, 2, 3), (1,), typ="add")
+    verify_broadcast_bop((1, 32), (64, 32), typ="sub")
+    verify_broadcast_bop((5, 64, 128), (2, 5, 64, 1), typ="mul")
+    verify_broadcast_bop((2, 3, 1, 32), (64, 32), typ="div")
+
+
+def test_conv2d_scalar_bop():
+    verify_conv2d_scalar_bop(1, 16, 4, 4, 3, 1, 1, typ="add")
+    verify_conv2d_scalar_bop(1, 32, 2, 1, 3, 1, 1, typ="sub")
+    verify_conv2d_scalar_bop(1, 32, 1, 1, 3, 1, 1, typ="mul")
+    verify_conv2d_scalar_bop(1, 16, 2, 1, 3, 1, 1, typ="div")
+
+
+if __name__ == "__main__":
+    test_operator_type_and_tags()
+    test_combination()
+    test_tensor_scalar_bop()
+    test_broadcast_bop()
+    test_conv2d_scalar_bop()
\ No newline at end of file
diff --git a/topi/python/topi/__init__.py b/topi/python/topi/__init__.py
index 9666b88b80eb..f82a40f6b42e 100644
--- a/topi/python/topi/__init__.py
+++ b/topi/python/topi/__init__.py
@@ -17,6 +17,7 @@
 
 from .math import *
 from .tensor import *
+from .generic_op_impl import *
 from .reduction import *
 from .transform import *
 from .broadcast import *
diff --git a/topi/python/topi/generic_op_impl.py b/topi/python/topi/generic_op_impl.py
new file mode 100644
index 000000000000..16a79c095783
--- /dev/null
+++ b/topi/python/topi/generic_op_impl.py
@@ -0,0 +1,123 @@
+"""Implementation of generic operators in the presence of Tensor"""
+# pylint: disable=invalid-name, too-many-arguments
+from __future__ import absolute_import as _abs
+import tvm
+from . import broadcast as _broadcast
+from . import tag
+
+
+def _make_bop(elementwise_bop, broadcast_bop, orig_bop):
+    """Make a specific overloaded binary operator of Tensor when applicable;
+    apply the original operator if it is not supposed to be overloaded.
+
+    Consider the following scenario:
+    OP :   + | - | * | /
+    R0 :   int | float | Expr | TensorSlice | Tensor (rank zero)
+    R1 :   Tensor (positive rank)
+
+    In terms of (LHS OP RHS), we apply the following overloading rules:
+    (1) We use broadcast_OP(LHS, RHS), when both LHS and RHS are R1.
+    (2) We perform element-wise operation of Tensor and scalar,
+        when one of LHS and RHS is R1 and another is R0.
+    (3) We do not overload OP (i.e. stick to orig_bop) otherwise.
+
+    Parameters
+    ----------
+    elementwise_bop : operator function
+        Operator for element-wise tensor-scalar operation, for rule (2).
+
+    broadcast_bop : operator function
+        Operator for broadcast tensor-tensor operation, for rule (1).
+
+    orig_bop: operator function
+        Operator before overloading, for rule (3).
+
+    Returns
+    -------
+    ret : operator function
+        The overloaded operator function if applicable or orig_bop otherwise.
+    """
+
+    name = orig_bop.__name__
+
+    def _tensor_bop_impl(lhs, rhs):
+        """Overloaded {op} operator.
+
+        If both operands are non-zero-rank Tensors, it performs
+        tensor-tensor {op} operation, and broadcasts inputs when necessary.
+
+        If one operand is non-zero-rank Tensor, while the other operand is
+        scalar like type (e.g., numeric types, Expr, or TensorSlice),
+        it performs tensor-scalar {op} operation on an element-wise basis.
+
+        Otherwise, it performs default generic.{op} operation, as defined
+        in tvm.generic module.
+
+        Parameters
+        ----------
+        lhs : object
+            Left operand.
+        rhs : object
+            Right operand.
+
+        Returns
+        -------
+        ret : tvm.Tensor (if at least one operand is non-zero-rank Tensor)
+              tvm.Expr (otherwise)
+            The result of {op} operation.
+        """
+
+        def _get_rank(x):
+            """Get the rank of a value.
+            If x is Tensor, then return its rank;
+            if x is scalar_like (i.e., numeric types, Expr, or TensorSlice), return 0;
+            otherwise, return -1.
+            """
+            if isinstance(x, tvm.tensor.Tensor):
+                return len(x.shape)
+            elif isinstance(x, (int, float, tvm.expr.Expr, tvm.tensor.TensorSlice)):
+                return 0
+            return -1
+
+        rl = _get_rank(lhs)
+        rr = _get_rank(rhs)
+        if rl == -1 or rr == -1 or (rl == 0 and rr == 0):
+            return orig_bop(lhs, rhs)
+        elif rl > 0 and rr > 0:
+            return broadcast_bop(lhs, rhs)
+        elif rl == 0:
+            f = lambda *i: elementwise_bop(lhs, rhs(*i))
+            with tvm.tag_scope(tag=tag.ELEMWISE):
+                return tvm.compute(rhs.shape, f, "tensor_" + name)
+        elif rr == 0:
+            f = lambda *i: elementwise_bop(lhs(*i), rhs)
+            with tvm.tag_scope(tag=tag.ELEMWISE):
+                return tvm.compute(lhs.shape, f, "tensor_" + name)
+        else:
+            raise AssertionError("Cannot reach this line.")
+
+    _tensor_bop_impl.__doc__ = _tensor_bop_impl.__doc__.format(op=name)
+    return _tensor_bop_impl
+
+
+def _bind_generic_ops():
+    """Bind generic operators for Tensor."""
+    # Check __op_priority__ to make sure the binding happens only once.
+    __op_priority__ = 1
+    if __op_priority__ > tvm.generic.__op_priority__:
+        tvm.generic.__op_priority__ = __op_priority__
+        tvm.generic.add = _make_bop(lambda x, y: x + y,
+                                    _broadcast.broadcast_add,
+                                    tvm.generic.add)
+        tvm.generic.subtract = _make_bop(lambda x, y: x - y,
+                                         _broadcast.broadcast_sub,
+                                         tvm.generic.subtract)
+        tvm.generic.multiply = _make_bop(lambda x, y: x * y,
+                                         _broadcast.broadcast_mul,
+                                         tvm.generic.multiply)
+        tvm.generic.divide = _make_bop(lambda x, y: x / y,
+                                       _broadcast.broadcast_div,
+                                       tvm.generic.divide)
+
+
+_bind_generic_ops()

From 100fd8bd88472424bb9cc178607e96f7d3913c8d Mon Sep 17 00:00:00 2001
From: nhynes <nhynes@berkeley.edu>
Date: Tue, 27 Mar 2018 09:58:54 -0700
Subject: [PATCH 226/949] Only warn when unable to find a graph input (#1052)

---
 src/runtime/graph/graph_runtime.cc | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/runtime/graph/graph_runtime.cc b/src/runtime/graph/graph_runtime.cc
index 892967b310e2..ce1c51ba58a6 100644
--- a/src/runtime/graph/graph_runtime.cc
+++ b/src/runtime/graph/graph_runtime.cc
@@ -88,7 +88,7 @@ class GraphRuntime : public ModuleNode {
         return static_cast<int>(i);
       }
     }
-    LOG(FATAL) << "cannot find " << name << " among input";
+    LOG(WARNING) << "Warning: cannot find \"" << name << "\" among input";
     return -1;
   }
   /*!
@@ -459,7 +459,8 @@ void GraphRuntime::LoadParams(dmlc::Stream* strm) {
   CHECK(size == names.size())
       << "Invalid parameters file format";
   for (size_t i = 0; i < size; ++i) {
-    uint32_t in_idx = GetInputIndex(names[i]);
+    int in_idx = GetInputIndex(names[i]);
+    CHECK_GE(in_idx, 0) << "Found param for non-existent input: " << names[i];
     uint32_t eid = this->entry_id(input_nodes_[in_idx], 0);
     CHECK_LT(eid, data_entry_.size());
     LoadDLTensor(strm, &data_entry_[eid]);
@@ -585,7 +586,8 @@ PackedFunc GraphRuntime::GetFunction(
   if (name == "set_input") {
     return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
         if (args[0].type_code() == kStr) {
-          this->SetInput(this->GetInputIndex(args[0]), args[1]);
+          int in_idx = this->GetInputIndex(args[0]);
+          if (in_idx >= 0) this->SetInput(in_idx, args[1]);
         } else {
           this->SetInput(args[0], args[1]);
         }
@@ -597,7 +599,9 @@ PackedFunc GraphRuntime::GetFunction(
   } else if (name == "get_input") {
     return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
         if (args[0].type_code() == kStr) {
-          this->GetInput(this->GetInputIndex(args[0]), args[1]);
+          int in_idx = this->GetInputIndex(args[0]);
+          CHECK_GE(in_idx, 0);
+          this->GetInput(in_idx, args[1]);
         } else {
           this->GetInput(args[0], args[1]);
         }

From abe4cf969595600271bf4e8825b5e0e2ef502702 Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Tue, 27 Mar 2018 23:49:52 +0530
Subject: [PATCH 227/949] [TOPI] PReLU Support (#1008)

---
 topi/include/topi/nn.h                  | 35 +++++++++++++++++++++++++
 topi/python/topi/nn/elemwise.py         | 34 ++++++++++++++++++++++++
 topi/src/topi.cc                        |  5 ++++
 topi/tests/python/test_topi_relu.py     | 25 ++++++++++++++++++
 topi/tests/python_cpp/test_topi_relu.py | 25 ++++++++++++++++++
 5 files changed, 124 insertions(+)

diff --git a/topi/include/topi/nn.h b/topi/include/topi/nn.h
index df4b73bdd6f3..07e12252a357 100644
--- a/topi/include/topi/nn.h
+++ b/topi/include/topi/nn.h
@@ -10,6 +10,7 @@
 #include <string>
 
 #include "topi/tags.h"
+#include "topi/detail/constant_utils.h"
 #include "tvm/ir.h"
 #include "tvm/ir_pass.h"
 #include "tvm/tvm.h"
@@ -83,6 +84,40 @@ inline tvm::Tensor leaky_relu(const tvm::Tensor& t,
     tag);
 }
 
+/*!
+ * \brief Creates an operation that performs a parametric rectified linear unit
+ *
+ * \param x The input data tensor
+ * \param slope The channel-wise slope tensor
+ * \param axis The axis where the channel data needs to be applied
+ * \param name The name of the operation
+ * \param tag The tag to mark the operation
+ *
+ * \return A Tensor whose op member is the relu operation
+ */
+template <typename T>
+inline tvm::Tensor prelu(const tvm::Tensor &x,
+                         const tvm::Tensor &slope,
+                         const int axis = 1,
+                         std::string name = "tensor",
+                         std::string tag = kBroadcast) {
+  CHECK_EQ(4, x->shape.size());
+  CHECK((size_t)axis < x->shape.size()) <<
+        "Wrong axis ("  << axis << ")value. ";
+  CHECK(topi::detail::GetConstInt(slope->shape[0]) ==
+        topi::detail::GetConstInt(x->shape[axis]))
+        << "Wrong slope shape received.";
+
+  return tvm::compute(x->shape,
+                     [&](const tvm::Array<tvm::Var> &indices) {
+                        return tvm::select(x(indices) > 0,
+                                           x(indices),
+                                           x(indices) * slope(indices[axis]));
+                      },
+                      name,
+                      tag);
+}
+
 /*!
  * \brief Creates an operation that performs padding
  *
diff --git a/topi/python/topi/nn/elemwise.py b/topi/python/topi/nn/elemwise.py
index 856652da02e8..b4132b2f1b7a 100644
--- a/topi/python/topi/nn/elemwise.py
+++ b/topi/python/topi/nn/elemwise.py
@@ -2,6 +2,7 @@
 from __future__ import absolute_import as _abs
 import tvm
 from .. import tag
+from ..util import get_const_int
 
 @tvm.tag_scope(tag=tag.ELEMWISE)
 def relu(x):
@@ -42,3 +43,36 @@ def _compute(*indices):
         calpha = tvm.const(alpha, value.dtype)
         return tvm.select(value > 0, value, value * calpha)
     return tvm.compute(x.shape, _compute)
+
+@tvm.tag_scope(tag=tag.BROADCAST)
+def prelu(x, slope, axis=1):
+    """ PReLU.
+    It accepts two arguments: an input ``x`` and a weight array ``W``
+    and computes the output as :math:`PReLU(x) y = x > 0 ? x : W * x`,
+    where :math:`*` is an elementwise multiplication for each sample in the
+    batch.
+    Arguments:
+    x : tvm.Tensor
+        Input argument.
+
+    slope : tvm.Tensor
+        Channelised slope tensor for prelu
+
+    axis : int
+        The axis where the channel data needs to be applied
+
+    Returns:
+    y : tvm.Tensor
+        The result.
+
+    Links:
+        [http://arxiv.org/pdf/1502.01852v1.pdf]
+    """
+
+    assert len(x.shape) == 4 and len(slope.shape) == 1
+    assert axis < len(x.shape)
+    assert get_const_int(slope.shape[0]) == get_const_int(x.shape[axis])
+
+    def _compute_channelwise(*indices):
+        return tvm.select(x(*indices) > 0, x(*indices), x(*indices) * slope(indices[axis]))
+    return tvm.compute(x.shape, _compute_channelwise)
diff --git a/topi/src/topi.cc b/topi/src/topi.cc
index 2b0191cd72d7..a31a7809c667 100644
--- a/topi/src/topi.cc
+++ b/topi/src/topi.cc
@@ -190,6 +190,11 @@ TVM_REGISTER_GLOBAL("topi.nn.leaky_relu")
   *rv = leaky_relu<float>(args[0]);
   });
 
+TVM_REGISTER_GLOBAL("topi.nn.prelu")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = prelu<float>(args[0], args[1]);
+  });
+
 TVM_REGISTER_GLOBAL("topi.nn.pad")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
   *rv = pad(args[0], args[1], args[2], args[3]);
diff --git a/topi/tests/python/test_topi_relu.py b/topi/tests/python/test_topi_relu.py
index 994ed491411e..2a70789cf7b0 100644
--- a/topi/tests/python/test_topi_relu.py
+++ b/topi/tests/python/test_topi_relu.py
@@ -46,13 +46,38 @@ def verify_leaky_relu(m, alpha):
     np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
 
 
+def verify_prelu(x, w):
+    X = tvm.placeholder((x), name='X')
+    W = tvm.placeholder((w), name='W')
+    x_np = np.random.uniform(low=-1.0, high=1.0, size=get_const_tuple(X.shape)).astype(X.dtype)
+    w_np = np.random.uniform(low=-1.0, high=1.0, size=get_const_tuple(W.shape)).astype(W.dtype)
+
+    def _prelu_numpy(x, W):
+        return (x < 0) * (x *W.reshape(3, 1, 1)) + (x>=0) * x
+
+    B = topi.nn.prelu(X, W)
+    s = tvm.create_schedule([B.op])
+
+    ctx = tvm.cpu(0)
+    x_tvm = tvm.nd.array(x_np, ctx)
+    w_tvm = tvm.nd.array(w_np, ctx)
+
+    b = tvm.nd.array(np.zeros(get_const_tuple(X.shape), dtype=B.dtype), ctx)
+    foo = tvm.build(s, [X, W, B], "llvm", name="prelu")
+    foo(x_tvm, w_tvm, b)
+    out_np = _prelu_numpy(x_np, w_np)
+    np.testing.assert_allclose(b.asnumpy(), out_np, rtol=1e-5)
+
 def test_relu():
     verify_relu(10, 128)
 
 def test_leaky_relu():
     verify_leaky_relu(100, 0.1)
 
+def test_prelu():
+    verify_prelu((1, 3, 2, 2), (3,))
 
 if __name__ == "__main__":
     test_relu()
     test_leaky_relu()
+    test_prelu()
diff --git a/topi/tests/python_cpp/test_topi_relu.py b/topi/tests/python_cpp/test_topi_relu.py
index f21426635121..6b80c20dd63c 100644
--- a/topi/tests/python_cpp/test_topi_relu.py
+++ b/topi/tests/python_cpp/test_topi_relu.py
@@ -50,6 +50,28 @@ def verify_leaky_relu(m, alpha):
     foo(a, b)
     np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
 
+def verify_prelu(x, w):
+    X = tvm.placeholder((x), name='X')
+    W = tvm.placeholder((w), name='W')
+    x_np = np.random.uniform(low=-1.0, high=1.0, size=get_const_tuple(X.shape)).astype(X.dtype)
+    w_np = np.random.uniform(low=-1.0, high=1.0, size=get_const_tuple(W.shape)).astype(W.dtype)
+    def _prelu_numpy(x, W):
+        return (x < 0) * (x *W.reshape(3, 1, 1)) + (x>=0) * x
+
+    out_np = _prelu_numpy(x_np, w_np)
+    B = topi.cpp.nn.prelu(X, W)
+    device = "llvm"
+    target = topi.cpp.TEST_create_target(device)
+    s = topi.cpp.generic.schedule_injective(target, [B])
+
+    ctx = tvm.cpu(0)
+    x_tvm = tvm.nd.array(x_np, ctx)
+    w_tvm = tvm.nd.array(w_np, ctx)
+
+    b = tvm.nd.array(np.zeros(get_const_tuple(X.shape), dtype=B.dtype), ctx)
+    foo = tvm.build(s, [X, W, B], "llvm", name="prelu")
+    foo(x_tvm, w_tvm, b)
+    np.testing.assert_allclose(b.asnumpy(), out_np, rtol=1e-5)
 
 def test_relu():
     for dtype in ['float32', 'float64', 'int32', 'int16', 'int8', 'int64']:
@@ -58,7 +80,10 @@ def test_relu():
 def test_leaky_relu():
     verify_leaky_relu(100, 0.1)
 
+def test_prelu():
+    verify_prelu((1, 3, 2, 2), (3,))
 
 if __name__ == "__main__":
     test_relu()
     test_leaky_relu()
+    test_prelu()

From 1c49f7cbc8f895412298b4049127f33a5972ce78 Mon Sep 17 00:00:00 2001
From: eqy <eqy@cs.washington.edu>
Date: Tue, 27 Mar 2018 18:01:01 -0700
Subject: [PATCH 228/949] fix Android build w/ threading_backend (#1059)

---
 apps/android_rpc/app/src/main/jni/tvm_runtime.h | 2 +-
 src/runtime/threading_backend.cc                | 5 +++++
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/apps/android_rpc/app/src/main/jni/tvm_runtime.h b/apps/android_rpc/app/src/main/jni/tvm_runtime.h
index fc384a8fcd72..0bb1ff553947 100644
--- a/apps/android_rpc/app/src/main/jni/tvm_runtime.h
+++ b/apps/android_rpc/app/src/main/jni/tvm_runtime.h
@@ -21,7 +21,7 @@
 #include "../src/runtime/rpc/rpc_module.cc"
 #include "../src/runtime/rpc/rpc_socket_impl.cc"
 #include "../src/runtime/thread_pool.cc"
-
+#include "../src/runtime/threading_backend.cc"
 #include "../src/runtime/graph/graph_runtime.cc"
 
 #ifdef TVM_OPENCL_RUNTIME
diff --git a/src/runtime/threading_backend.cc b/src/runtime/threading_backend.cc
index 11df12837860..413054750682 100644
--- a/src/runtime/threading_backend.cc
+++ b/src/runtime/threading_backend.cc
@@ -81,8 +81,13 @@ class ThreadGroup::Impl {
       cpu_set_t cpuset;
       CPU_ZERO(&cpuset);
       CPU_SET(0, &cpuset);
+#if defined(__ANDROID__)
+      sched_setaffinity(pthread_self(),
+        sizeof(cpu_set_t), &cpuset);
+#else
       pthread_setaffinity_np(pthread_self(),
         sizeof(cpu_set_t), &cpuset);
+#endif
     }
 #endif
   }

From 85b903aee5f2d888318683659330c24a37472d37 Mon Sep 17 00:00:00 2001
From: cjjia <33363657+jiacunjiang1215@users.noreply.github.com>
Date: Thu, 29 Mar 2018 02:12:09 +0800
Subject: [PATCH 229/949] fix warning (#1041)

---
 src/runtime/threading_backend.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/runtime/threading_backend.cc b/src/runtime/threading_backend.cc
index 413054750682..aa15c229cfca 100644
--- a/src/runtime/threading_backend.cc
+++ b/src/runtime/threading_backend.cc
@@ -28,7 +28,7 @@ class ThreadGroup::Impl {
     }
     const char *val = getenv("TVM_BIND_THREADS");
     if (val == nullptr || atoi(val) == 1) {
-      if (num_workers_ <= std::thread::hardware_concurrency()) {
+      if (static_cast<size_t>(num_workers_) <= std::thread::hardware_concurrency()) {
         SetAffinity(exclude_worker0);
       } else {
         LOG(WARNING)

From 1680a49f36b0d19077850ec713a06e8ac68cfbda Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 28 Mar 2018 11:24:25 -0700
Subject: [PATCH 230/949] Add Community Page (#1063)

---
 README.md | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index a25246245c66..74213e8041cd 100644
--- a/README.md
+++ b/README.md
@@ -10,16 +10,17 @@
 [Operator Inventory](topi) |
 [FAQ](docs/faq.md) |
 [Contributors](CONTRIBUTORS.md) |
+[Community](http://tvmlang.org/community.html) |
 [Release Notes](NEWS.md)
 
 TVM is a Tensor intermediate representation(IR) stack for deep learning systems. It is designed to close the gap between the
 productivity-focused deep learning frameworks, and the performance- and efficiency-focused hardware backends.
 TVM works with deep learning frameworks to provide end to end compilation to different backends.
-Checkout our [announcement](http://tvmlang.org/2017/08/17/tvm-release-announcement.html) for more details.
+Checkout the [tvm stack homepage](http://tvmlang.org/)  for more information.
 
 License
 -------
-© Contributors, 2017. Licensed under an [Apache-2.0](https://github.com/dmlc/tvm/blob/master/LICENSE) license.
+© Contributors Licensed under an [Apache-2.0](https://github.com/dmlc/tvm/blob/master/LICENSE) license.
 
 Contribute to TVM
 -----------------

From 7f92ba15b32e4e0d969e9b87af0fd60660bfeab2 Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Wed, 28 Mar 2018 14:30:07 -0700
Subject: [PATCH 231/949] delete init part when keeping trivial loop (#1031)

---
 include/tvm/operation.h          | 12 +++++------
 include/tvm/schedule_pass.h      |  7 +++++--
 src/api/api_schedule.cc          |  2 +-
 src/codegen/build_module.cc      |  2 +-
 src/op/compute_op.cc             | 25 +++++++++++++---------
 src/op/compute_op.h              | 12 +++++------
 src/op/cross_thread_reduction.cc |  4 ++--
 src/op/extern_op.cc              |  2 +-
 src/op/op_util.cc                |  6 +++---
 src/op/op_util.h                 |  4 ++--
 src/op/placeholder_op.cc         |  2 +-
 src/op/scan_op.cc                |  4 ++--
 src/op/tensorize.cc              |  4 ++--
 src/schedule/schedule_ops.cc     | 36 +++++++++++++++++---------------
 14 files changed, 66 insertions(+), 56 deletions(-)

diff --git a/include/tvm/operation.h b/include/tvm/operation.h
index 9b950c3d544f..3c819ff02ab8 100644
--- a/include/tvm/operation.h
+++ b/include/tvm/operation.h
@@ -117,13 +117,13 @@ class OperationNode : public FunctionBaseNode {
    * \brief Build the statement that provide the output tensors.
    * \param stage The schedule stage of the op.
    * \param dom_map The domain map of all iteration domains.
-   * \param del_trivial_loop Whether eliminate trivial loop with extent of 1
+   * \param debug_keep_trivial_loop Whether keep trivial loops with extent of 1
    * \return A statement that add production and wraps consumer.
    */
   virtual Stmt BuildProvide(
       const Stage& stage,
       const std::unordered_map<IterVar, Range>& dom_map,
-      bool del_trivial_loop) const = 0;
+      bool debug_keep_trivial_loop) const = 0;
 
   static constexpr const char* _type_key = "Operation";
 
@@ -163,7 +163,7 @@ class PlaceholderOpNode : public OperationNode {
   Stmt BuildProvide(
       const Stage& stage,
       const std::unordered_map<IterVar, Range>& dom_map,
-      bool del_trivial_loop) const final;
+      bool debug_keep_trivial_loop) const final;
 
   void VisitAttrs(AttrVisitor* v) final {
     v->Visit("name", &name);
@@ -215,7 +215,7 @@ class ComputeOpNode : public OperationNode {
   Stmt BuildProvide(
       const Stage& stage,
       const std::unordered_map<IterVar, Range>& dom_map,
-      bool del_trivial_loop) const final;
+      bool debug_keep_trivial_loop) const final;
 
   void VisitAttrs(AttrVisitor* v) final {
     v->Visit("name", &name);
@@ -287,7 +287,7 @@ class ScanOpNode : public OperationNode {
   Stmt BuildProvide(
       const Stage& stage,
       const std::unordered_map<IterVar, Range>& dom_map,
-      bool del_trivial_loop) const final;
+      bool debug_keep_trivial_loop) const final;
 
   void VisitAttrs(AttrVisitor* v) final {
     v->Visit("name", &name);
@@ -351,7 +351,7 @@ class ExternOpNode : public OperationNode {
   Stmt BuildProvide(
       const Stage& stage,
       const std::unordered_map<IterVar, Range>& dom_map,
-      bool del_trivial_loop) const final;
+      bool debug_keep_trivial_loop) const final;
 
   void VisitAttrs(AttrVisitor* v) final {
     v->Visit("name", &name);
diff --git a/include/tvm/schedule_pass.h b/include/tvm/schedule_pass.h
index 011c7510ced9..3f0334facc2b 100644
--- a/include/tvm/schedule_pass.h
+++ b/include/tvm/schedule_pass.h
@@ -29,10 +29,13 @@ Map<IterVar, Range> InferBound(const Schedule& sch);
  *
  * \param s The schedule to be realized
  * \param dom_map The domain of each iter vars.
- * \param del_trivial_loop Whether delete trivial loops with extent of 1
+ * \param debug_keep_trivial_loop Whether keep trivial loops with extent of 1 during lowering.
+ *                                This is a debug feature for dataflow/axis analysis.
+ *                                Note: If this is true, The lowered IR may be incorrect,
+ *                                because we will also delete the init part of reduction
  * \return the result Stmt
  */
-Stmt ScheduleOps(Schedule s, Map<IterVar, Range> dom_map, bool del_trivial_loop);
+Stmt ScheduleOps(Schedule s, Map<IterVar, Range> dom_map, bool debug_keep_trivial_loop);
 
 /*!
  * \brief To automatically inline the element-wise operations.
diff --git a/src/api/api_schedule.cc b/src/api/api_schedule.cc
index b1a6729ec662..14191d79ec02 100644
--- a/src/api/api_schedule.cc
+++ b/src/api/api_schedule.cc
@@ -27,7 +27,7 @@ TVM_REGISTER_API("schedule.AutoInlineInjective")
 TVM_REGISTER_API("schedule.ScheduleOps")
 .set_body([](TVMArgs args, TVMRetValue* ret) {
   if (args.size() == 2)
-    *ret = ScheduleOps(args[0], args[1], true);
+    *ret = ScheduleOps(args[0], args[1], false);
   else
     *ret = ScheduleOps(args[0], args[1], args[2]);
 });
diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc
index 43bc3e32aad6..210f3661372a 100644
--- a/src/codegen/build_module.cc
+++ b/src/codegen/build_module.cc
@@ -349,7 +349,7 @@ Stmt BuildStmt(Schedule sch,
 
   // Phase 0
   auto bounds = schedule::InferBound(sch);
-  auto stmt = schedule::ScheduleOps(sch, bounds, true);
+  auto stmt = schedule::ScheduleOps(sch, bounds, false);
   stmt = ir::InjectPrefetch(stmt);
 
   // Phase 1
diff --git a/src/op/compute_op.cc b/src/op/compute_op.cc
index f3f8335c195c..0dadf4ab3dd5 100644
--- a/src/op/compute_op.cc
+++ b/src/op/compute_op.cc
@@ -296,9 +296,9 @@ Stmt MakeProvide(const ComputeOpNode* op,
 Stmt MakeComputeStmt(const ComputeOpNode* self,
                      const Stage& stage,
                      const std::unordered_map<IterVar, Range>& dom_map,
-                     bool del_trivial_loop) {
+                     bool debug_keep_trivial_loop) {
   // grab the nest structure
-  ComputeLoopNest n = ComputeLoopNest::make(self, stage, dom_map, del_trivial_loop);
+  ComputeLoopNest n = ComputeLoopNest::make(self, stage, dom_map, debug_keep_trivial_loop);
   // Normal loop structure
   n.init_nest.emplace_back(op::MakeIfNest(n.init_predicates));
   n.main_nest.emplace_back(op::MakeIfNest(n.main_predicates));
@@ -319,7 +319,11 @@ Stmt MakeComputeStmt(const ComputeOpNode* self,
         n.main_nest.begin() + n.num_common_loop + 1, n.main_nest.end());
     provide = op::Substitute(provide, n.main_vmap);
     provide = MergeNest(reduce, provide);
-    return MergeNest(common, Block::make(init, provide));
+    if (debug_keep_trivial_loop) {
+      return MergeNest(common, provide);
+    } else {
+      return MergeNest(common, Block::make(init, provide));
+    }
   } else {
     std::vector<Stmt> provides;
     for (size_t i = 0; i < self->body.size(); ++i) {
@@ -379,16 +383,16 @@ ComputeType DetectComputeType(const ComputeOpNode* self,
 Stmt ComputeOpNode::BuildProvide(
     const Stage& stage,
     const std::unordered_map<IterVar, Range>& dom_map,
-    bool del_trivial_loop) const {
+    bool debug_keep_trivial_loop) const {
   CHECK_EQ(stage->op.operator->(), this);
   ComputeType ctype = DetectComputeType(this, stage);
   if (ctype == ComputeType::kCrossThreadReduction) {
     // specially handle cross thread reduction.
-    return MakeCrossThreadReduction(this, stage, dom_map, del_trivial_loop);
+    return MakeCrossThreadReduction(this, stage, dom_map, debug_keep_trivial_loop);
   } else if (ctype == ComputeType::kTensorize) {
-    return MakeTensorize(this, stage, dom_map, del_trivial_loop);
+    return MakeTensorize(this, stage, dom_map, debug_keep_trivial_loop);
   } else {
-    return MakeComputeStmt(this, stage, dom_map, del_trivial_loop);
+    return MakeComputeStmt(this, stage, dom_map, debug_keep_trivial_loop);
   }
 }
 
@@ -396,12 +400,13 @@ ComputeLoopNest ComputeLoopNest::make(
     const ComputeOpNode* self,
     const Stage& stage,
     const std::unordered_map<IterVar, Range>& dom_map,
-    bool del_trivial_loop) {
+    bool debug_keep_trivial_loop) {
   CHECK_EQ(stage->op.operator->(), self);
   ComputeLoopNest ret;
   // make main loop nest
   ret.main_nest = op::MakeLoopNest(
-      stage, dom_map, 0, false, std::unordered_set<IterVar>(), &ret.main_vmap, del_trivial_loop);
+      stage, dom_map, 0, false, std::unordered_set<IterVar>(), &ret.main_vmap,
+      debug_keep_trivial_loop);
   ret.main_predicates = schedule::MakeBoundCheck(
       stage, dom_map, ret.main_vmap, false,
       std::unordered_set<IterVar>());
@@ -443,7 +448,7 @@ ComputeLoopNest ComputeLoopNest::make(
     }
     ret.init_nest = op::MakeLoopNest(
         stage, dom_map, begin_loop, true,
-        skip_iter, &(ret.init_vmap), del_trivial_loop);
+        skip_iter, &(ret.init_vmap), debug_keep_trivial_loop);
     ret.init_predicates = schedule::MakeBoundCheck(
         stage, dom_map, ret.init_vmap, true, skip_iter);
     for (auto& e : ret.init_predicates) {
diff --git a/src/op/compute_op.h b/src/op/compute_op.h
index 2164feee6988..996764c6cdc1 100644
--- a/src/op/compute_op.h
+++ b/src/op/compute_op.h
@@ -37,14 +37,14 @@ struct ComputeLoopNest {
    * \param self The pointer to compute op.
    * \param stage The scxhedule stage.
    * \param dom_map The domain map.
-   * \param del_trivial_loop Whether eliminate trivial loops with extent of 1
+   * \param debug_keep_trivial_loop Whether keep trivial loops with extent of 1
    * \return The constructed loop nest
    */
   static ComputeLoopNest make(
       const ComputeOpNode* self,
       const Stage& stage,
       const std::unordered_map<IterVar, Range>& dom_map,
-      bool del_trivial_loop);
+      bool debug_keep_trivial_loop);
 };
 
 /*!
@@ -52,27 +52,27 @@ struct ComputeLoopNest {
  * \param self The pointer to ComputeOpNode
  * \param stage The schedule stage.
  * \param dom_map The domain map.
- * \param del_trivial_loop Wheter eliminate trivial loops with extent of 1
+ * \param debug_keep_trivial_loop Whether keep trivial loops with extent of 1
  * \return The created statement.
  */
 Stmt MakeCrossThreadReduction(
     const ComputeOpNode* self,
     const Stage& stage,
     const std::unordered_map<IterVar, Range>& dom_map,
-    bool del_trivial_loop);
+    bool debug_keep_trivial_loop);
 
 /*!
  * \brief Build body of compute for tensorization.
  * \param self The pointer to ComputeOpNode
  * \param stage The schedule stage.
  * \param dom_map The domain map.
- * \param del_trivial_loop Wheter eliminate trivial loops with extent of 1
+ * \param debug_keep_trivial_loop Whether keep trivial loops with extent of 1
  * \return The created statement.
  */
 Stmt MakeTensorize(const ComputeOpNode* self,
                    const Stage& stage,
                    const std::unordered_map<IterVar, Range>& dom_map,
-                   bool del_trivial_loop);
+                   bool debug_keep_trivial_loop);
 }  // namespace tvm
 
 #endif  // TVM_OP_COMPUTE_OP_H_
diff --git a/src/op/cross_thread_reduction.cc b/src/op/cross_thread_reduction.cc
index e32b3dcd4407..eb320388860a 100644
--- a/src/op/cross_thread_reduction.cc
+++ b/src/op/cross_thread_reduction.cc
@@ -14,14 +14,14 @@ Stmt MakeCrossThreadReduction(
     const ComputeOpNode* self,
     const Stage& stage,
     const std::unordered_map<IterVar, Range>& dom_map,
-    bool del_trivial_loop) {
+    bool debug_keep_trivial_loop) {
   Array<Expr>  args;
   for (IterVar iv : self->axis) {
     args.push_back(iv->var);
   }
   std::unordered_map<IterVar, Expr> value_map;
   auto nest = op::MakeLoopNest(
-      stage, dom_map, 0, false, std::unordered_set<IterVar>(), &value_map, del_trivial_loop);
+      stage, dom_map, 0, false, std::unordered_set<IterVar>(), &value_map, debug_keep_trivial_loop);
   auto conds = schedule::MakeBoundCheck(
       stage, dom_map, value_map, false,
       std::unordered_set<IterVar>());
diff --git a/src/op/extern_op.cc b/src/op/extern_op.cc
index df3a32d50fe7..8c372966cd09 100644
--- a/src/op/extern_op.cc
+++ b/src/op/extern_op.cc
@@ -129,7 +129,7 @@ Stmt ExternOpNode::BuildRealize(
 Stmt ExternOpNode::BuildProvide(
     const Stage& stage,
     const std::unordered_map<IterVar, Range>& dom_map,
-    bool del_trivial_loop) const {
+    bool debug_keep_trivial_loop) const {
   CHECK_EQ(stage->op.operator->(), this);
   Stmt ret = AttrStmt::make(make_zero(Int(32)), attr::extern_scope, 0, this->body);
   auto f_push_bind = [&ret](Buffer buffer, Tensor tensor) {
diff --git a/src/op/op_util.cc b/src/op/op_util.cc
index ef7af85bf079..daa489593298 100644
--- a/src/op/op_util.cc
+++ b/src/op/op_util.cc
@@ -24,7 +24,7 @@ MakeLoopNest(const Stage& stage,
              bool new_loop_var,
              const std::unordered_set<IterVar>& skip_iter,
              std::unordered_map<IterVar, Expr>* p_value_map,
-             bool del_trivial_loop) {
+             bool debug_keep_trivial_loop) {
   auto leaf_iter_vars = stage->leaf_iter_vars;
   Stmt no_op = Evaluate::make(0);
   // create the loop nest
@@ -76,7 +76,7 @@ MakeLoopNest(const Stage& stage,
               AttrStmt::make(iv, ir::attr::pragma_scope, p, no_op));
         }
       }
-      if (del_trivial_loop && is_one(dom->extent)) {
+      if (!debug_keep_trivial_loop && is_one(dom->extent)) {
         nest[i + 1].emplace_back(
             LetStmt::make(var, dom->min, no_op));
         value_map[iv] = dom->min;
@@ -131,7 +131,7 @@ MakeLoopNest(const Stage& stage,
       // annotate the extent of the IterVar
       nest[i + 1].emplace_back(
           AttrStmt::make(bind_iv, ir::attr::thread_extent, dom->extent, no_op));
-      if (del_trivial_loop && is_one(dom->extent)) {
+      if (!debug_keep_trivial_loop && is_one(dom->extent)) {
         value_map[iv] = dom->min;
       } else {
         value_map[iv] = var;
diff --git a/src/op/op_util.h b/src/op/op_util.h
index 9b8f7dc629bd..558e8d4e7324 100644
--- a/src/op/op_util.h
+++ b/src/op/op_util.h
@@ -29,7 +29,7 @@ using ir::MergeNest;
  * \param new_loop_var Whether create new loop variable.
  * \param skip_iter Whether skip certain iteration.
  * \param p_value_map The result value of each IterVar.
- * \param del_trivial_loop Whether eliminate trivial loops with extent of 1
+ * \param debug_keep_trivial_loop Whether keep trivial loops with extent of 1
  */
 std::vector<std::vector<Stmt> >
 MakeLoopNest(const Stage& stage,
@@ -38,7 +38,7 @@ MakeLoopNest(const Stage& stage,
              bool new_loop_var,
              const std::unordered_set<IterVar>& skip_iter,
              std::unordered_map<IterVar, Expr>* p_value_map,
-             bool del_trivial_loop);
+             bool debug_keep_trivial_loop);
 
 /*!
  * \brief Create a nest of if checking the predicates.
diff --git a/src/op/placeholder_op.cc b/src/op/placeholder_op.cc
index 27c1fa9c5001..a2cd0eb2d81f 100644
--- a/src/op/placeholder_op.cc
+++ b/src/op/placeholder_op.cc
@@ -79,7 +79,7 @@ Stmt PlaceholderOpNode::BuildRealize(
 Stmt PlaceholderOpNode::BuildProvide(
     const Stage& stage,
     const std::unordered_map<IterVar, Range>& dom_map,
-    bool del_trivial_loop) const {
+    bool debug_keep_trivial_loop) const {
   return Stmt();
 }
 }  // namespace tvm
diff --git a/src/op/scan_op.cc b/src/op/scan_op.cc
index 5c61eae0f183..a3ccd70fa763 100644
--- a/src/op/scan_op.cc
+++ b/src/op/scan_op.cc
@@ -253,7 +253,7 @@ Stmt ScanOpNode::BuildRealize(
 Stmt ScanOpNode::BuildProvide(
     const Stage& stage,
     const std::unordered_map<IterVar, Range>& dom_map,
-    bool del_trivial_loop) const {
+    bool debug_keep_trivial_loop) const {
   CHECK_EQ(stage->op.operator->(), this);
   Stmt provide = AttrStmt::make(
       stage->op, attr::scan_update_scope, this->scan_axis->var,
@@ -271,7 +271,7 @@ Stmt ScanOpNode::BuildProvide(
   std::unordered_map<IterVar, Expr> vmap;
   std::unordered_set<IterVar> empty;
   auto nest = op::MakeLoopNest(
-      stage, dom_map, 0, false, empty, &vmap, del_trivial_loop);
+      stage, dom_map, 0, false, empty, &vmap, debug_keep_trivial_loop);
   nest[begin_scan].push_back(init);
   nest.push_back(
       op::MakeIfNest(
diff --git a/src/op/tensorize.cc b/src/op/tensorize.cc
index 1f03ec9c0ebb..148ad0f90fe7 100644
--- a/src/op/tensorize.cc
+++ b/src/op/tensorize.cc
@@ -370,14 +370,14 @@ Stmt TransformUpdate(const Stage& stage,
 Stmt MakeTensorize(const ComputeOpNode* self,
                    const Stage& stage,
                    const std::unordered_map<IterVar, Range>& dom_map,
-                   bool del_trivial_loop) {
+                   bool debug_keep_trivial_loop) {
   std::unordered_map<IterVar, Range> out_dom;
   std::unordered_map<Tensor, Array<Range> > in_region;
   size_t tloc = InferTensorizeRegion(self, stage, dom_map, &out_dom, &in_region);
   TensorIntrin intrin = stage->iter_var_attrs.at(
       stage->leaf_iter_vars[tloc])->tensor_intrin;
   CHECK(intrin.defined());
-  ComputeLoopNest n = ComputeLoopNest::make(self, stage, dom_map, del_trivial_loop);
+  ComputeLoopNest n = ComputeLoopNest::make(self, stage, dom_map, debug_keep_trivial_loop);
   VerifyTensorizeLoopNest(self, stage, n, tloc);
   VerifyTensorizeBody(self, stage, out_dom, in_region, intrin);
   // Start bind data.
diff --git a/src/schedule/schedule_ops.cc b/src/schedule/schedule_ops.cc
index b9b02050a556..6fd2496aeabe 100644
--- a/src/schedule/schedule_ops.cc
+++ b/src/schedule/schedule_ops.cc
@@ -23,8 +23,8 @@ using namespace ir;
 Stmt MakePipeline(const Stage& s,
                   const std::unordered_map<IterVar, Range>& dom_map,
                   Stmt consumer,
-                  bool del_trivial_loop) {
-  Stmt producer = s->op->BuildProvide(s, dom_map, del_trivial_loop);
+                  bool debug_keep_trivial_loop) {
+  Stmt producer = s->op->BuildProvide(s, dom_map, debug_keep_trivial_loop);
   if (producer.defined()) {
     producer = ProducerConsumer::make(s->op, true, producer);
   }
@@ -58,9 +58,9 @@ class InjectAttach : public IRMutator {
   InjectAttach(const Stage& stage,
                const Stage& attach_spec,
                const std::unordered_map<IterVar, Range>& dom_map,
-               bool del_trivial_loop)
+               bool debug_keep_trivial_loop)
       : stage_(stage), attach_spec_(attach_spec), dom_map_(dom_map),
-        del_trivial_loop_(del_trivial_loop) {}
+        debug_keep_trivial_loop_(debug_keep_trivial_loop) {}
 
   Stmt Mutate(Stmt stmt) final {
     CHECK(stmt.defined());
@@ -76,7 +76,7 @@ class InjectAttach : public IRMutator {
         found_attach = true;
         stmt = AttrStmt::make(
             op->node, op->attr_key, op->value,
-            MakePipeline(stage_, dom_map_, op->body, del_trivial_loop_));
+            MakePipeline(stage_, dom_map_, op->body, debug_keep_trivial_loop_));
       }
     }
     return stmt;
@@ -91,8 +91,9 @@ class InjectAttach : public IRMutator {
   const Stage& attach_spec_;
   // domain map
   const std::unordered_map<IterVar, Range>& dom_map_;
-  // whether delete trivial loops with extent of 1
-  bool del_trivial_loop_;
+  // Whether keep trivial loops with extent of 1 during lowering.
+  // This is a debug feature for dataflow/axis analysis
+  bool debug_keep_trivial_loop_;
 };
 
 // inject the operator's realization on the stmt.
@@ -102,9 +103,9 @@ class InjectScanStep : public IRMutator {
                  const Operation& scan_op,
                  const std::unordered_map<IterVar, Range>& dom_map,
                  bool is_init,
-                 bool del_trivial_loop)
+                 bool debug_keep_trivial_loop)
       : stage_(stage), scan_op_(scan_op),
-        dom_map_(dom_map), is_init_(is_init), del_trivial_loop_(del_trivial_loop) {}
+        dom_map_(dom_map), is_init_(is_init), debug_keep_trivial_loop_(debug_keep_trivial_loop) {}
 
   Stmt Mutate(Stmt stmt) final {
     CHECK(stmt.defined());
@@ -118,7 +119,7 @@ class InjectScanStep : public IRMutator {
         found_attach = true;
         stmt = AttrStmt::make(
             op->node, op->attr_key, op->value,
-            MakePipeline(stage_, dom_map_, op->body, del_trivial_loop_));
+            MakePipeline(stage_, dom_map_, op->body, debug_keep_trivial_loop_));
       }
     }
     return stmt;
@@ -135,8 +136,9 @@ class InjectScanStep : public IRMutator {
   const std::unordered_map<IterVar, Range>& dom_map_;
   // whether it is init.
   bool is_init_;
-  // whether delete trivial loops with extent of 1
-  bool del_trivial_loop_;
+  // Whether keep trivial loops with extent of 1 during lowering.
+  // This is a debug feature for dataflow/axis analysis
+  bool debug_keep_trivial_loop_;
 };
 
 // Postprocessing of schedule op
@@ -337,7 +339,7 @@ class SchedulePostProc : public IRMutator {
 };
 
 Stmt ScheduleOps(
-    Schedule sch, Map<IterVar, Range> dom_map_, bool del_trivial_loop) {
+    Schedule sch, Map<IterVar, Range> dom_map_, bool debug_keep_trivial_loop) {
   Stmt body = Stmt();
   std::unordered_map<IterVar, Range> dom_map = as_unordered_map(dom_map_);
   // scan init and scan updates
@@ -372,14 +374,14 @@ Stmt ScheduleOps(
 
     if (scan_init.count(s->op)) {
       CHECK(body.defined());
-      InjectScanStep mu(s, scan_init.at(s->op), dom_map, true, del_trivial_loop);
+      InjectScanStep mu(s, scan_init.at(s->op), dom_map, true, debug_keep_trivial_loop);
       body = mu.Mutate(body);
       CHECK(mu.found_attach)
           << "did not find attachment point for scan.init";
     } else if (attach_spec->attach_type == kScanUpdate) {
       // Handle scan update
       CHECK(body.defined());
-      InjectScanStep mu(s, attach_spec->attach_stage->op, dom_map, false, del_trivial_loop);
+      InjectScanStep mu(s, attach_spec->attach_stage->op, dom_map, false, debug_keep_trivial_loop);
       body = mu.Mutate(body);
       CHECK(mu.found_attach)
           << "did not find attachment point for scan.update";
@@ -387,11 +389,11 @@ Stmt ScheduleOps(
       // do nothing
     } else if (attach_spec->attach_type == kGroupRoot) {
       CHECK(!s->group.defined());
-      body = MakePipeline(s, dom_map, body, del_trivial_loop);
+      body = MakePipeline(s, dom_map, body, debug_keep_trivial_loop);
     } else {
       CHECK_EQ(attach_spec->attach_type, kScope);
       CHECK(body.defined());
-      InjectAttach mutator(s, attach_spec, dom_map, del_trivial_loop);
+      InjectAttach mutator(s, attach_spec, dom_map, debug_keep_trivial_loop);
       body = mutator.Mutate(body);
       CHECK(mutator.found_attach)
           << "did not find attachment point for " << s << " in "

From 525cb3bc6025c47b8b56fb3c73c3b82b88bc0a50 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 28 Mar 2018 16:35:17 -0700
Subject: [PATCH 232/949] [DOCS] Add logo and tracker to docs (#1064)

---
 docs/_static/css/tvm_theme.css      |  10 ++++++++++
 docs/_static/img/README             |   2 ++
 docs/_static/img/tvm-logo-small.png | Bin 0 -> 6683 bytes
 docs/conf.py                        |   8 ++++++++
 4 files changed, 20 insertions(+)
 create mode 100644 docs/_static/img/README
 create mode 100644 docs/_static/img/tvm-logo-small.png

diff --git a/docs/_static/css/tvm_theme.css b/docs/_static/css/tvm_theme.css
index 5e0838abf6cb..274589887b3f 100644
--- a/docs/_static/css/tvm_theme.css
+++ b/docs/_static/css/tvm_theme.css
@@ -9,3 +9,13 @@
 nav .hidden-section {
     display: inherit;
 }
+
+.wy-side-nav-search {
+    background-color: #fff;
+    color: #333;
+}
+
+.version{
+    color: #404040 !important;
+}
+
diff --git a/docs/_static/img/README b/docs/_static/img/README
new file mode 100644
index 000000000000..414328cc729d
--- /dev/null
+++ b/docs/_static/img/README
@@ -0,0 +1,2 @@
+The logo file in this repo is an exception due to the need of sphinx.
+By default we avoid to put large binary blobs into this repo.
\ No newline at end of file
diff --git a/docs/_static/img/tvm-logo-small.png b/docs/_static/img/tvm-logo-small.png
new file mode 100644
index 0000000000000000000000000000000000000000..c3519fece55b24bddca3dcf6d825fcfdef5576b1
GIT binary patch
diff --git a/docs/conf.py b/docs/conf.py
index 4a42fb0fedb0..96ed7b239c7c 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -137,6 +137,14 @@
 # so a file named "default.css" will overwrite the builtin "default.css".
 html_static_path = ['_static']
 
+html_theme_options = {
+    'analytics_id': 'UA-75982049-2',
+    'logo_only': True,
+}
+
+html_logo = "_static/img/tvm-logo-small.png"
+
+
 # Output file base name for HTML help builder.
 htmlhelp_basename = project + 'doc'
 

From 043ed4be1786242ff4e01e5ed932c07089630e9b Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Thu, 29 Mar 2018 22:09:38 +0530
Subject: [PATCH 233/949] [WIP]    Linux/Android native deploy (#980)

---
 Makefile                                      |   4 +
 apps/android_deploy/.gitignore                |   9 +
 apps/android_deploy/README.md                 | 119 ++++
 apps/android_deploy/app/.gitignore            |   1 +
 apps/android_deploy/app/build.gradle          |  56 ++
 .../android_deploy/app/download-models.gradle |  64 ++
 .../app/src/main/AndroidManifest.xml          |  37 +
 .../dmlc/tvm/android/demo/MainActivity.java   | 633 ++++++++++++++++++
 .../app/src/main/jni/Android.mk               |  42 ++
 .../app/src/main/jni/Application.mk           |  16 +
 apps/android_deploy/app/src/main/jni/build.sh |   9 +
 .../app/src/main/jni/make/config.mk           |  26 +
 .../app/src/main/jni/tvm_runtime.h            |  26 +
 .../app/src/main/res/layout/activity_main.xml |  27 +
 .../app/src/main/res/layout/content_main.xml  |  46 ++
 .../app/src/main/res/values/colors.xml        |   6 +
 .../app/src/main/res/values/strings.xml       |   3 +
 .../app/src/main/res/values/styles.xml        |  17 +
 .../app/src/main/res/xml/provider_paths.xml   |   4 +
 apps/android_deploy/build.gradle              |  29 +
 apps/android_deploy/dev_tools/gen_keystore.sh |   3 +
 apps/android_deploy/dev_tools/sign_apk.sh     |   7 +
 apps/android_deploy/settings.gradle           |   1 +
 docs/how_to/deploy_android.md                 |  40 ++
 24 files changed, 1225 insertions(+)
 create mode 100644 apps/android_deploy/.gitignore
 create mode 100644 apps/android_deploy/README.md
 create mode 100644 apps/android_deploy/app/.gitignore
 create mode 100644 apps/android_deploy/app/build.gradle
 create mode 100644 apps/android_deploy/app/download-models.gradle
 create mode 100644 apps/android_deploy/app/src/main/AndroidManifest.xml
 create mode 100644 apps/android_deploy/app/src/main/java/ml/dmlc/tvm/android/demo/MainActivity.java
 create mode 100644 apps/android_deploy/app/src/main/jni/Android.mk
 create mode 100644 apps/android_deploy/app/src/main/jni/Application.mk
 create mode 100644 apps/android_deploy/app/src/main/jni/build.sh
 create mode 100644 apps/android_deploy/app/src/main/jni/make/config.mk
 create mode 100644 apps/android_deploy/app/src/main/jni/tvm_runtime.h
 create mode 100644 apps/android_deploy/app/src/main/res/layout/activity_main.xml
 create mode 100644 apps/android_deploy/app/src/main/res/layout/content_main.xml
 create mode 100644 apps/android_deploy/app/src/main/res/values/colors.xml
 create mode 100644 apps/android_deploy/app/src/main/res/values/strings.xml
 create mode 100644 apps/android_deploy/app/src/main/res/values/styles.xml
 create mode 100644 apps/android_deploy/app/src/main/res/xml/provider_paths.xml
 create mode 100644 apps/android_deploy/build.gradle
 create mode 100644 apps/android_deploy/dev_tools/gen_keystore.sh
 create mode 100644 apps/android_deploy/dev_tools/sign_apk.sh
 create mode 100644 apps/android_deploy/settings.gradle
 create mode 100644 docs/how_to/deploy_android.md

diff --git a/Makefile b/Makefile
index 9e15d60e8c9b..1732ed5d9623 100644
--- a/Makefile
+++ b/Makefile
@@ -128,6 +128,10 @@ ifeq ($(USE_OPENCL), 1)
 		LDFLAGS += -lOpenCL
 	endif
 	RUNTIME_DEP += $(OPENCL_OBJ)
+ifdef OPENCL_PATH
+        CFLAGS += -I$(OPENCL_PATH)/include
+        LDFLAGS += -L$(OPENCL_PATH)/lib
+endif
 else
 	CFLAGS += -DTVM_OPENCL_RUNTIME=0
 endif
diff --git a/apps/android_deploy/.gitignore b/apps/android_deploy/.gitignore
new file mode 100644
index 000000000000..39fb081a42a8
--- /dev/null
+++ b/apps/android_deploy/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/apps/android_deploy/README.md b/apps/android_deploy/README.md
new file mode 100644
index 000000000000..9620a2f1efde
--- /dev/null
+++ b/apps/android_deploy/README.md
@@ -0,0 +1,119 @@
+# Android TVM Demo
+
+This folder contains Android Demo app that allows us to show how to deploy model using TVM runtime api on a Android phone.
+
+You will need [JDK](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html), [Android SDK](https://developer.android.com/studio/index.html), [Android NDK](https://developer.android.com/ndk) and an Android device to use this.
+
+## Build and Installation
+
+### Build APK
+
+We use [Gradle](https://gradle.org) to build. Please follow [the installation instruction](https://gradle.org/install) for your operating system.
+
+Before you build the Android application, please refer to [TVM4J Installation Guide](https://github.com/dmlc/tvm/blob/master/jvm/README.md) and install tvm4j-core to your local maven repository. You can find tvm4j dependency declare in `app/build.gradle`. Modify it if it is necessary.
+
+```
+dependencies {
+    compile fileTree(dir: 'libs', include: ['*.jar'])
+    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+        exclude group: 'com.android.support', module: 'support-annotations'
+    })
+    compile 'com.android.support:appcompat-v7:26.0.1'
+    compile 'com.android.support.constraint:constraint-layout:1.0.2'
+    compile 'com.android.support:design:26.0.1'
+    compile 'ml.dmlc.tvm:tvm4j-core:0.0.1-SNAPSHOT'
+    testCompile 'junit:junit:4.12'
+}
+```
+
+Application default has CPU version TVM runtime flavor and follow below instruction to setup.
+In `app/src/main/jni/make` you will find JNI Makefile config `config.mk` and copy it to `app/src/main/jni` and modify it.
+
+```bash
+cd apps/android_deploy/app/src/main/jni
+cp make/config.mk .
+```
+
+Here's a piece of example for `config.mk`.
+
+```makefile
+APP_ABI = arm64-v8a
+
+APP_PLATFORM = android-17
+
+# whether enable OpenCL during compile
+USE_OPENCL = 0
+```
+
+Now use Gradle to compile JNI, resolve Java dependencies and build the Android application together with tvm4j. Run following script to generate the apk file.
+
+```bash
+export ANDROID_HOME=[Path to your Android SDK, e.g., ~/Android/sdk]
+cd apps/android_deploy
+gradle clean build
+```
+
+In `app/build/outputs/apk` you'll find `app-release-unsigned.apk`, use `dev_tools/gen_keystore.sh` to generate a signature and use `dev_tools/sign_apk.sh` to get the signed apk file `app/build/outputs/apk/tvmdemo-release.apk`.
+
+Upload `tvmdemo-release.apk` to your Android device and install it.
+
+### Build with OpenCL
+
+Application does not link with OpenCL library unless you configure it to. Modify JNI Makefile config `app/src/main/jni` with proper target OpenCL configuration.
+
+Here's a piece of example for `config.mk`.
+
+```makefile
+APP_ABI = arm64-v8a
+ 
+APP_PLATFORM = android-17
+ 
+# whether enable OpenCL during compile
+USE_OPENCL = 1
+ 
+# the additional include headers you want to add, e.g., SDK_PATH/adrenosdk/Development/Inc
+ADD_C_INCLUDES = /opt/adrenosdk-osx/Development/Inc
+ 
+# the additional link libs you want to add, e.g., ANDROID_LIB_PATH/libOpenCL.so
+ADD_LDLIBS = libOpenCL.so
+```
+
+Note that you should specify the correct GPU development headers for your android device. Run `adb shell dumpsys | grep GLES` to find out what GPU your android device uses. It is very likely the library (libOpenCL.so) is already present on the mobile device. For instance, I found it under `/system/vendor/lib64`. You can do `adb pull /system/vendor/lib64/libOpenCL.so ./` to get the file to your desktop.
+
+After you setup the `config.mk`, follow the instructions in [Build APK](#buildapk) to build the Android package with OpenCL flavor.
+
+## Cross Compile and Run on Android Devices
+
+### Architecture and Android Standalone Toolchain
+
+In order to cross compile a shared library (.so) for your android device, you have to know the target triple for the device. (Refer to [Cross-compilation using Clang](https://clang.llvm.org/docs/CrossCompilation.html) for more information). Run `adb shell cat /proc/cpuinfo` to list the device's CPU information.
+
+Now use NDK to generate standalone toolchain for your device. For my test device, I use following command.
+
+```bash
+cd /opt/android-ndk/build/tools/
+./make-standalone-toolchain.sh --platform=android-24 --use-llvm --arch=arm64 --install-dir=/opt/android-toolchain-arm64
+```
+
+If everything goes well, you will find compile tools in `/opt/android-toolchain-arm64/bin`. For example, `bin/aarch64-linux-android-g++` can be used to compile C++ source codes and create shared libraries for arm64 Android devices.
+
+### Place compiled model on Android application assets folder
+
+Follow instruction to get compiled version model for android target [here.](https://github.com/dmlc/tvm/blob/master/docs/how_to/deploy_android.md#build-model-for-android-target)
+
+Copied these compiled model deploy_lib.so, deploy_graph.json and deploy_param.params to apps/android_deploy/app/src/main/assets/ and modify TVM flavor changes on [java](https://github.com/dmlc/tvm/blob/master/apps/android_deploy/app/src/main/java/ml/dmlc/tvm/android/demo/MainActivity.java#L81)
+
+`CPU Verison flavor`
+```
+    private static final boolean EXE_GPU            = false;
+```
+
+`OpenCL Verison flavor`
+```
+    private static final boolean EXE_GPU            = true;
+```
+
+
+Install compiled android application on phone and enjoy the image classifier demo using extraction model
+
+You can define your own TVM operators and deploy via this demo application on your Android device to find the most optimized TVM schedule.
diff --git a/apps/android_deploy/app/.gitignore b/apps/android_deploy/app/.gitignore
new file mode 100644
index 000000000000..796b96d1c402
--- /dev/null
+++ b/apps/android_deploy/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/apps/android_deploy/app/build.gradle b/apps/android_deploy/app/build.gradle
new file mode 100644
index 000000000000..6790308a9ec4
--- /dev/null
+++ b/apps/android_deploy/app/build.gradle
@@ -0,0 +1,56 @@
+// import DownloadModels task
+project.ext.ASSET_DIR = projectDir.toString() + '/src/main/assets'
+project.ext.TMP_DIR   = project.buildDir.toString() + '/downloads'
+
+// Download default models(darknet framework extraction model compiled version);
+// if you wish to use your own models then place them in the "assets" directory 
+// and comment out this line.
+apply from: "download-models.gradle"
+
+apply plugin: 'com.android.application'
+
+task buildJni(type: Exec, description: 'Build JNI libs') {
+    commandLine 'sh', 'src/main/jni/build.sh'
+}
+
+tasks.withType(JavaCompile) {
+    compileTask -> compileTask.dependsOn buildJni
+}
+
+android {
+    compileSdkVersion 26
+    buildToolsVersion "26.0.1"
+    defaultConfig {
+        applicationId "ml.dmlc.tvm.android.demo"
+        minSdkVersion 17
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    sourceSets {
+        main {
+            jni.srcDirs = []
+            jniLibs.srcDirs = ['src/main/libs']
+            assets.srcDirs = [project.ext.ASSET_DIR]
+        }
+    }
+}
+
+dependencies {
+    compile fileTree(dir: 'libs', include: ['*.jar'])
+    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+        exclude group: 'com.android.support', module: 'support-annotations'
+    })
+    compile 'com.android.support:appcompat-v7:26.0.1'
+    compile 'com.android.support.constraint:constraint-layout:1.0.2'
+    compile 'com.android.support:design:26.0.1'
+    compile 'ml.dmlc.tvm:tvm4j-core:0.0.1-SNAPSHOT'
+    testCompile 'junit:junit:4.12'
+}
diff --git a/apps/android_deploy/app/download-models.gradle b/apps/android_deploy/app/download-models.gradle
new file mode 100644
index 000000000000..5b0509fbca2b
--- /dev/null
+++ b/apps/android_deploy/app/download-models.gradle
@@ -0,0 +1,64 @@
+/*
+ * download-models.gradle
+ *     Downloads model files from ${MODEL_URL} into application's asset folder
+ * Input:
+ *     project.ext.TMP_DIR: absolute path to hold downloaded zip files
+ *     project.ext.ASSET_DIR: absolute path to save unzipped model files
+ * Output:
+ *     3 model files will be downloaded into given folder of ext.ASSET_DIR
+ */
+// hard coded model files
+def models = ['extraction.zip']
+
+// Root URL for model archives
+def MODEL_URL = 'https://github.com/PariksheetPinjari909/TVM_models/blob/master/extraction_model'
+buildscript {
+    repositories {
+        jcenter()
+    }
+    dependencies {
+        classpath 'de.undercouch:gradle-download-task:3.2.0'
+    }
+}
+
+import de.undercouch.gradle.tasks.download.Download
+task downloadFile(type: Download){
+    for (f in models) {
+        src "${MODEL_URL}/" + f + "?raw=true"
+        dest new File(project.ext.TMP_DIR + "/" + f)
+    }
+    overwrite true
+}
+
+task extractModels(type: Copy) {
+    def needDownload = false
+    for (f in models) {
+        def localFile = f.split("/")[-1]
+        if (!(new File(project.ext.TMP_DIR + '/' + localFile)).exists()) {
+            needDownload = true
+        }
+    }
+
+    if (needDownload) {
+        dependsOn downloadFile
+    }
+
+    for (f in models) {
+        def localFile = f.split("/")[-1]
+        from zipTree(project.ext.TMP_DIR + '/' + localFile)
+    }
+
+    into file(project.ext.ASSET_DIR)
+    fileMode  0644
+    exclude '**/LICENSE'
+}
+
+tasks.whenTaskAdded { task ->
+    if (task.name == 'assembleDebug') {
+        task.dependsOn 'extractModels'
+    }
+    if (task.name == 'assembleRelease') {
+        task.dependsOn 'extractModels'
+    }
+}
+
diff --git a/apps/android_deploy/app/src/main/AndroidManifest.xml b/apps/android_deploy/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000000..bac82ee90faa
--- /dev/null
+++ b/apps/android_deploy/app/src/main/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="ml.dmlc.tvm.android.demo" >
+
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+
+    <application
+        android:allowBackup="true"
+        android:label="@string/app_name"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name=".MainActivity"
+            android:label="@string/app_name"
+            android:theme="@style/AppTheme.NoActionBar"
+            android:screenOrientation="portrait">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <provider
+            android:name="android.support.v4.content.FileProvider"
+            android:authorities="${applicationId}.provider"
+            android:exported="false"
+            android:grantUriPermissions="true">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/provider_paths"/>
+        </provider>
+    </application>
+
+    <uses-permission android:name="android.permission.INTERNET" />
+
+</manifest>
\ No newline at end of file
diff --git a/apps/android_deploy/app/src/main/java/ml/dmlc/tvm/android/demo/MainActivity.java b/apps/android_deploy/app/src/main/java/ml/dmlc/tvm/android/demo/MainActivity.java
new file mode 100644
index 000000000000..f3cdefe1c2ff
--- /dev/null
+++ b/apps/android_deploy/app/src/main/java/ml/dmlc/tvm/android/demo/MainActivity.java
@@ -0,0 +1,633 @@
+/*
+ * 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.
+ */
+
+package ml.dmlc.tvm.android.demo;
+
+import android.Manifest;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.AssetManager;
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.DialogInterface;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.SystemClock;
+import android.provider.MediaStore;
+import android.support.v4.content.FileProvider;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Vector;
+
+import ml.dmlc.tvm.Function;
+import ml.dmlc.tvm.Module;
+import ml.dmlc.tvm.NDArray;
+import ml.dmlc.tvm.TVMContext;
+import ml.dmlc.tvm.TVMValue;
+import ml.dmlc.tvm.TVMType;
+
+public class MainActivity extends AppCompatActivity {
+    private static final String TAG = MainActivity.class.getSimpleName();
+
+    private static final int PERMISSIONS_REQUEST    = 100;
+    private static final int PICTURE_FROM_GALLERY   = 101;
+    private static final int PICTURE_FROM_CAMERA    = 102;
+    private static final int IMAGE_PREVIEW_WIDTH    = 960;
+    private static final int IMAGE_PREVIEW_HEIGHT   = 720;
+
+    // TVM constants
+    private static final int OUTPUT_INDEX           = 0;
+    private static final int IMG_CHANNEL            = 3;
+    private static final String INPUT_NAME          = "data";
+
+    // Configuration values for extraction model. Note that the graph, lib and params is not
+    // included with TVM and must be manually placed in the assets/ directory by the user.
+    // Graphs and models downloaded from https://github.com/pjreddie/darknet/blob/ may be
+    // converted e.g. via  define_and_compile_model.py.
+    private static final boolean EXE_GPU            = false;
+    private static final int MODEL_INPUT_SIZE       = 224;
+    private static final String MODEL_CL_LIB_FILE   = "file:///android_asset/deploy_lib_opencl.so";
+    private static final String MODEL_CPU_LIB_FILE  = "file:///android_asset/deploy_lib_cpu.so";
+    private static final String MODEL_GRAPH_FILE    = "file:///android_asset/deploy_graph.json";
+    private static final String MODEL_PARAM_FILE    = "file:///android_asset/deploy_param.params";
+    private static final String MODEL_LABEL_FILE    = "file:///android_asset/imagenet.shortnames.list";
+
+    private Uri mCameraImageUri;
+    private ImageView mImageView;
+    private TextView mResultView;
+    private AssetManager assetManager;
+    private Module graphRuntimeModule;
+    private Vector<String> labels = new Vector<String>();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        Toolbar toolbar = findViewById(R.id.toolbar);
+        setSupportActionBar(toolbar);
+        assetManager = getAssets();
+
+        mImageView = (ImageView) findViewById(R.id.imageView);
+        mResultView = (TextView) findViewById(R.id.resultTextView);
+        findViewById(R.id.btnPickImage).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                showPictureDialog();
+            }
+        });
+
+        if (hasPermission()) {
+            // instantiate tvm runtime and setup environment on background after application begin
+            new LoadModleAsyncTask().execute();
+        } else {
+            requestPermission();
+        }
+    }
+
+    /*
+        Load precompiled model on TVM graph runtime and init the system.
+     */
+    private class LoadModleAsyncTask extends AsyncTask<Void, Void, Integer> {
+        ProgressDialog dialog = new ProgressDialog(MainActivity.this);
+
+        @Override
+        protected Integer doInBackground(Void... args) {
+
+            // load synset name
+            String lableFilename = MODEL_LABEL_FILE.split("file:///android_asset/")[1];
+            Log.i(TAG, "Reading synset name from: " + lableFilename);
+            try {
+                String labelsContent = new String(getBytesFromFile(assetManager, lableFilename));
+                for (String line : labelsContent.split("\\r?\\n")) {
+                    labels.add(line);
+                }
+            } catch (IOException e) {
+                Log.e(TAG, "Problem reading synset name file!" + e);
+                return -1;//failure
+            }
+
+            // load json graph
+            String modelGraph = null;
+            String graphFilename = MODEL_GRAPH_FILE.split("file:///android_asset/")[1];
+            Log.i(TAG, "Reading json graph from: " + graphFilename);
+            try {
+                modelGraph = new String(getBytesFromFile(assetManager, graphFilename));
+            } catch (IOException e) {
+                Log.e(TAG, "Problem reading json graph file!" + e);
+                return -1;//failure
+            }
+
+            // upload tvm compiled function on application cache folder
+            String libCacheFilePath = null;
+            String libFilename = EXE_GPU ? MODEL_CL_LIB_FILE.split("file:///android_asset/")[1] :
+                    MODEL_CPU_LIB_FILE.split("file:///android_asset/")[1];
+            Log.i(TAG, "Uploading compiled function to cache folder");
+            try {
+                libCacheFilePath = getTempLibFilePath(libFilename);
+                byte[] modelLibByte = getBytesFromFile(assetManager, libFilename);
+                FileOutputStream fos = new FileOutputStream(libCacheFilePath);
+                fos.write(modelLibByte);
+                fos.close();
+            } catch (IOException e) {
+                Log.e(TAG, "Problem uploading compiled function!" + e);
+                return -1;//failure
+            }
+
+            // load parameters
+            byte[] modelParams = null;
+            String paramFilename = MODEL_PARAM_FILE.split("file:///android_asset/")[1];
+            try {
+                modelParams = getBytesFromFile(assetManager, paramFilename);
+            } catch (IOException e) {
+                Log.e(TAG, "Problem reading params file!" + e);
+                return -1;//failure
+            }
+
+            // create java tvm context
+            TVMContext tvmCtx = EXE_GPU ? TVMContext.opencl() : TVMContext.cpu();
+
+            // tvm module for compiled functions
+            Module modelLib = Module.load(libCacheFilePath);
+
+            // get global function module for graph runtime
+            Function runtimeCreFun = Function.getFunction("tvm.graph_runtime.create");
+            TVMValue runtimeCreFunRes = runtimeCreFun.pushArg(modelGraph)
+                    .pushArg(modelLib)
+                    .pushArg(tvmCtx.deviceType)
+                    .pushArg(tvmCtx.deviceId)
+                    .invoke();
+            graphRuntimeModule = runtimeCreFunRes.asModule();
+
+            // get the function from the module(load parameters)
+            Function loadParamFunc = graphRuntimeModule.getFunction("load_params");
+            loadParamFunc.pushArg(modelParams).invoke();
+
+            // release tvm local variables
+            modelLib.release();
+            loadParamFunc.release();
+            runtimeCreFun.release();
+
+            return 0;//success
+        }
+
+        @Override
+        protected void onPreExecute() {
+            dialog.setCancelable(false);
+            dialog.setMessage("Loading Model...");
+            dialog.show();
+            super.onPreExecute();
+        }
+
+        @Override
+        protected void onPostExecute(Integer status) {
+            if (dialog != null && dialog.isShowing()) {
+                dialog.dismiss();
+            }
+            if (status != 0) {
+                showDialog("Error", "Fail to initialized model, check compiled model");
+            }
+        }
+    }
+
+    /*
+        Execute prediction for processed decode input bitmap image content on TVM graph runtime.
+     */
+    private class ModelRunAsyncTask extends AsyncTask<Bitmap, Void, Integer> {
+        ProgressDialog dialog = new ProgressDialog(MainActivity.this);
+
+        @Override
+        protected Integer doInBackground(Bitmap... bitmaps) {
+            if (null != graphRuntimeModule) {
+                int count  = bitmaps.length;
+                for (int i = 0 ; i < count ; i++) {
+                    long processingTimeMs = SystemClock.uptimeMillis();
+                    Log.i(TAG, "Decode JPEG image content");
+
+                    // extract the jpeg content
+                    ByteArrayOutputStream stream = new ByteArrayOutputStream();
+                    bitmaps[i].compress(Bitmap.CompressFormat.JPEG,100,stream);
+                    byte[] byteArray = stream.toByteArray();
+                    Bitmap imageBitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length);
+
+                    // crop input image at centre to model input size
+                    // commecial deploy note:: instead of cropying image do resize
+                    // image to model input size so we never lost the image content
+                    Bitmap cropImageBitmap = Bitmap.createBitmap(MODEL_INPUT_SIZE, MODEL_INPUT_SIZE, Bitmap.Config.ARGB_8888);
+                    Matrix frameToCropTransform = getTransformationMatrix(imageBitmap.getWidth(), imageBitmap.getHeight(),
+                            MODEL_INPUT_SIZE, MODEL_INPUT_SIZE, 0, true);
+                    Canvas canvas = new Canvas(cropImageBitmap);
+                    canvas.drawBitmap(imageBitmap, frameToCropTransform, null);
+
+                    // image pixel int values
+                    int[] pixelValues = new int[MODEL_INPUT_SIZE * MODEL_INPUT_SIZE];
+                    // image RGB float values
+                    float[] imgRgbValues = new float[MODEL_INPUT_SIZE * MODEL_INPUT_SIZE * IMG_CHANNEL];
+                    // image RGB transpose float values
+                    float[] imgRgbTranValues = new float[MODEL_INPUT_SIZE * MODEL_INPUT_SIZE * IMG_CHANNEL];
+
+                    // pre-process the image data from 0-255 int to normalized float based on the
+                    // provided parameters.
+                    cropImageBitmap.getPixels(pixelValues, 0, MODEL_INPUT_SIZE, 0, 0, MODEL_INPUT_SIZE, MODEL_INPUT_SIZE);
+                    for (int j = 0; j < pixelValues.length; ++j) {
+                        imgRgbValues[j * 3 + 0] = ((pixelValues[j] >> 16) & 0xFF)/255.0f;
+                        imgRgbValues[j * 3 + 1] = ((pixelValues[j] >> 8) & 0xFF)/255.0f;
+                        imgRgbValues[j * 3 + 2] = (pixelValues[j] & 0xFF)/255.0f;
+                    }
+
+                    // pre-process the image rgb data transpose based on the provided parameters.
+                    for (int k = 0; k < IMG_CHANNEL; ++k) {
+                        for (int l = 0; l < MODEL_INPUT_SIZE; ++l) {
+                            for (int m = 0; m < MODEL_INPUT_SIZE; ++m) {
+                                int dst_index = m + MODEL_INPUT_SIZE*l + MODEL_INPUT_SIZE*MODEL_INPUT_SIZE*k;
+                                int src_index = k + IMG_CHANNEL*m + IMG_CHANNEL*MODEL_INPUT_SIZE*l;
+                                imgRgbTranValues[dst_index] = imgRgbValues[src_index];
+                            }
+                        }
+                    }
+
+                    // get the function from the module(set input data)
+                    Log.i(TAG, "set input data");
+                    NDArray inputNdArray = NDArray.empty(new long[]{1, IMG_CHANNEL, MODEL_INPUT_SIZE, MODEL_INPUT_SIZE}, new TVMType("float32"));;
+                    inputNdArray.copyFrom(imgRgbTranValues);
+                    Function setInputFunc = graphRuntimeModule.getFunction("set_input");
+                    setInputFunc.pushArg(INPUT_NAME).pushArg(inputNdArray).invoke();
+                    // release tvm local variables
+                    inputNdArray.release();
+                    setInputFunc.release();
+
+                    // get the function from the module(run it)
+                    Log.i(TAG, "run function on target");
+                    Function runFunc = graphRuntimeModule.getFunction("run");
+                    runFunc.invoke();
+                    // release tvm local variables
+                    runFunc.release();
+
+                    // get the function from the module(get output data)
+                    Log.i(TAG, "get output data");
+                    NDArray outputNdArray = NDArray.empty(new long[]{1000}, new TVMType("float32"));
+                    Function getOutputFunc = graphRuntimeModule.getFunction("get_output");
+                    getOutputFunc.pushArg(OUTPUT_INDEX).pushArg(outputNdArray).invoke();
+                    float[] output = outputNdArray.asFloatArray();
+                    // release tvm local variables
+                    outputNdArray.release();
+                    getOutputFunc.release();
+
+                    // display the result from extracted output data
+                    if (null != output) {
+                        int maxPosition = -1;
+                        float maxValue = 0;
+                        for (int j = 0; j < output.length; ++j) {
+                            if (output[j] > maxValue) {
+                                maxValue = output[j];
+                                maxPosition = j;
+                            }
+                        }
+                        processingTimeMs = SystemClock.uptimeMillis() - processingTimeMs;
+                        String label = "Prediction Result : ";
+                        label += labels.size() > maxPosition ? labels.get(maxPosition) : "unknown";
+                        label += "\nPrediction Time : " + processingTimeMs + "ms";
+                        mResultView.setText(label);
+                    }
+                    Log.i(TAG, "prediction finished");
+                }
+                return 0;
+            }
+            return -1;
+        }
+
+        @Override
+        protected void onPreExecute() {
+            dialog.setCancelable(false);
+            dialog.setMessage("Prediction running on image...");
+            dialog.show();
+            super.onPreExecute();
+        }
+
+        @Override
+        protected void onPostExecute(Integer status) {
+            if (dialog != null && dialog.isShowing()) {
+                dialog.dismiss();
+            }
+            if (status != 0) {
+                showDialog("Error", "Fail to predict image, GraphRuntime exception");
+            }
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        // release tvm local variables
+        if (null != graphRuntimeModule)
+            graphRuntimeModule.release();
+        super.onDestroy();
+    }
+
+    /**
+     * Read file from assets and return byte array.
+     *
+     * @param assets The asset manager to be used to load assets.
+     * @param fileName The filepath of read file.
+     * @return byte[] file content
+     * @throws IOException
+     */
+    private byte[] getBytesFromFile(AssetManager assets, String fileName) throws IOException {
+        InputStream is = assets.open(fileName);
+        int length = is.available();
+        byte[] bytes = new byte[length];
+        // Read in the bytes
+        int offset = 0;
+        int numRead = 0;
+        try {
+            while (offset < bytes.length
+                    && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
+                offset += numRead;
+            }
+        } finally {
+            is.close();
+        }
+        // Ensure all the bytes have been read in
+        if (offset < bytes.length) {
+            throw new IOException("Could not completely read file " + fileName);
+        }
+        return bytes;
+    }
+
+    /**
+     * Dialog show pick option for select image from Gallery or Camera.
+     */
+    private void showPictureDialog(){
+        AlertDialog.Builder pictureDialog = new AlertDialog.Builder(this);
+        pictureDialog.setTitle("Select Action");
+        String[] pictureDialogItems = {
+                "Select photo from gallery",
+                "Capture photo from camera" };
+        pictureDialog.setItems(pictureDialogItems,
+                new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        switch (which) {
+                            case 0:
+                                choosePhotoFromGallery();
+                                break;
+                            case 1:
+                                takePhotoFromCamera();
+                                break;
+                        }
+                    }
+                });
+        pictureDialog.show();
+    }
+
+    /**
+     * Request to pick image from Gallery.
+     */
+    public void choosePhotoFromGallery() {
+        Intent galleryIntent = new Intent(Intent.ACTION_PICK,
+                android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+
+        startActivityForResult(galleryIntent, PICTURE_FROM_GALLERY);
+    }
+
+    /**
+     * Request to capture image from Camera.
+     */
+    private void takePhotoFromCamera() {
+        Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
+
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+            mCameraImageUri = Uri.fromFile(createImageFile());
+        } else {
+            File file = new File(createImageFile().getPath());
+            mCameraImageUri = FileProvider.getUriForFile(getApplicationContext(), getApplicationContext().getPackageName() + ".provider", file);
+        }
+
+        intent.putExtra(MediaStore.EXTRA_OUTPUT, mCameraImageUri);
+        startActivityForResult(intent, PICTURE_FROM_CAMERA);
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (resultCode == this.RESULT_CANCELED) {
+            return;
+        }
+        Uri contentURI = null;
+        if (requestCode == PICTURE_FROM_GALLERY) {
+            if (data != null) {
+                contentURI = data.getData();
+            }
+        } else if (requestCode == PICTURE_FROM_CAMERA) {
+            contentURI = mCameraImageUri;
+        }
+        if (null != contentURI) {
+            try {
+                Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), contentURI);
+                Bitmap scaled = Bitmap.createScaledBitmap(bitmap, IMAGE_PREVIEW_HEIGHT, IMAGE_PREVIEW_WIDTH, true);
+                mImageView.setImageBitmap(scaled);
+                new ModelRunAsyncTask().execute(scaled);
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * Get application cache path where to place compiled functions.
+     *
+     * @param fileName library file name.
+     * @return String application cache folder path
+     * @throws IOException
+     */
+    private final String getTempLibFilePath(String fileName) throws IOException {
+        File tempDir = File.createTempFile("tvm4j_demo_", "");
+        if (!tempDir.delete() || !tempDir.mkdir()) {
+            throw new IOException("Couldn't create directory " + tempDir.getAbsolutePath());
+        }
+        return (tempDir + File.separator + fileName);
+    }
+
+    /**
+     * Create image file under storage where camera application save captured image.
+     *
+     * @return File image file under sdcard where camera can save image
+     */
+    private File createImageFile() {
+        // Create an image file name
+        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
+        String imageFileName = "JPEG_" + timeStamp + "_";
+        File storageDir = Environment.getExternalStoragePublicDirectory(
+                Environment.DIRECTORY_PICTURES);
+        try {
+            File image = File.createTempFile(
+                    imageFileName,  // prefix
+                    ".jpg",         // suffix
+                    storageDir      // directory
+            );
+            return image;
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * Show dialog to user.
+     *
+     * @param title dialog display title
+     * @param msg dialog display message
+     */
+    private void showDialog(String title, String msg) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        builder.setTitle(title);
+        builder.setMessage(msg);
+        builder.setCancelable(true);
+        builder.setNeutralButton(android.R.string.ok,
+                new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int id) {
+                        dialog.cancel();
+                        finish();
+                    }
+                });
+        builder.create().show();
+    }
+
+    @Override
+    public void onRequestPermissionsResult (final int requestCode, final String[] permissions, final int[] grantResults){
+        if (requestCode == PERMISSIONS_REQUEST) {
+            if (grantResults.length > 0
+                    && grantResults[0] == PackageManager.PERMISSION_GRANTED
+                    && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
+                // instantiate tvm runtime and setup environment on background after application begin
+                new LoadModleAsyncTask().execute();
+            } else {
+                requestPermission();
+            }
+        }
+    }
+
+    /**
+     * Whether application has required mandatory permissions to run.
+     */
+    private boolean hasPermission() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            return checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED &&
+                    checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * Request required mandatory permission for application to run.
+     */
+    private void requestPermission() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) ||
+                    shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+                Toast.makeText(this,
+                        "Camera AND storage permission are required for this demo", Toast.LENGTH_LONG).show();
+            }
+            requestPermissions(new String[] {Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSIONS_REQUEST);
+        }
+    }
+
+    /**
+     * Returns a transformation matrix from one reference frame into another.
+     * Handles cropping (if maintaining aspect ratio is desired) and rotation.
+     *
+     * @param srcWidth Width of source frame.
+     * @param srcHeight Height of source frame.
+     * @param dstWidth Width of destination frame.
+     * @param dstHeight Height of destination frame.
+     * @param applyRotation Amount of rotation to apply from one frame to another.
+     *  Must be a multiple of 90.
+     * @param maintainAspectRatio If true, will ensure that scaling in x and y remains constant,
+     * cropping the image if necessary.
+     * @return The transformation fulfilling the desired requirements.
+     */
+    public static Matrix getTransformationMatrix(
+            final int srcWidth,
+            final int srcHeight,
+            final int dstWidth,
+            final int dstHeight,
+            final int applyRotation,
+            final boolean maintainAspectRatio) {
+        final Matrix matrix = new Matrix();
+
+        if (applyRotation != 0) {
+            if (applyRotation % 90 != 0) {
+                Log.w(TAG, "Rotation of %d % 90 != 0 " + applyRotation);
+            }
+
+            // Translate so center of image is at origin.
+            matrix.postTranslate(-srcWidth / 2.0f, -srcHeight / 2.0f);
+
+            // Rotate around origin.
+            matrix.postRotate(applyRotation);
+        }
+
+        // Account for the already applied rotation, if any, and then determine how
+        // much scaling is needed for each axis.
+        final boolean transpose = (Math.abs(applyRotation) + 90) % 180 == 0;
+
+        final int inWidth = transpose ? srcHeight : srcWidth;
+        final int inHeight = transpose ? srcWidth : srcHeight;
+
+        // Apply scaling if necessary.
+        if (inWidth != dstWidth || inHeight != dstHeight) {
+            final float scaleFactorX = dstWidth / (float) inWidth;
+            final float scaleFactorY = dstHeight / (float) inHeight;
+
+            if (maintainAspectRatio) {
+                // Scale by minimum factor so that dst is filled completely while
+                // maintaining the aspect ratio. Some image may fall off the edge.
+                final float scaleFactor = Math.max(scaleFactorX, scaleFactorY);
+                matrix.postScale(scaleFactor, scaleFactor);
+            } else {
+                // Scale exactly to fill dst from src.
+                matrix.postScale(scaleFactorX, scaleFactorY);
+            }
+        }
+
+        if (applyRotation != 0) {
+            // Translate back from origin centered reference to destination frame.
+            matrix.postTranslate(dstWidth / 2.0f, dstHeight / 2.0f);
+        }
+
+        return matrix;
+    }
+}
\ No newline at end of file
diff --git a/apps/android_deploy/app/src/main/jni/Android.mk b/apps/android_deploy/app/src/main/jni/Android.mk
new file mode 100644
index 000000000000..a99517f90332
--- /dev/null
+++ b/apps/android_deploy/app/src/main/jni/Android.mk
@@ -0,0 +1,42 @@
+LOCAL_PATH := $(call my-dir)
+MY_PATH := $(LOCAL_PATH)
+
+include $(CLEAR_VARS)
+
+LOCAL_PATH := $(MY_PATH)
+ROOT_PATH := $(MY_PATH)/../../../../../..
+
+ifndef config
+	ifneq ("$(wildcard ./config.mk)","")
+	  config ?= config.mk
+	else
+	  config ?= make/config.mk
+	endif
+endif
+
+include $(config)
+
+LOCAL_SRC_FILES := ml_dmlc_tvm_native_c_api.cc
+LOCAL_LDFLAGS := -L$(SYSROOT)/usr/lib/ -llog
+
+LOCAL_C_INCLUDES := $(ROOT_PATH)/include \
+                    $(ROOT_PATH)/dlpack/include \
+                    $(ROOT_PATH)/dmlc-core/include \
+                    $(ROOT_PATH)/HalideIR/src \
+                    $(ROOT_PATH)/topi/include
+
+LOCAL_MODULE = tvm4j_runtime_packed
+
+LOCAL_CPP_FEATURES += exceptions
+LOCAL_LDLIBS += -latomic
+LOCAL_ARM_MODE := arm
+
+ifdef ADD_C_INCLUDES
+	LOCAL_C_INCLUDES += $(ADD_C_INCLUDES)
+endif
+
+ifdef ADD_LDLIBS
+	LOCAL_LDLIBS += $(ADD_LDLIBS)
+endif
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/apps/android_deploy/app/src/main/jni/Application.mk b/apps/android_deploy/app/src/main/jni/Application.mk
new file mode 100644
index 000000000000..01cad9b783a7
--- /dev/null
+++ b/apps/android_deploy/app/src/main/jni/Application.mk
@@ -0,0 +1,16 @@
+ifndef config
+	ifneq ("$(wildcard ./config.mk)","")
+	  config ?= config.mk
+	else
+	  config ?= make/config.mk
+	endif
+endif
+
+include $(config)
+
+APP_STL := gnustl_static
+
+APP_CPPFLAGS += -DDMLC_LOG_STACK_TRACE=0 -DTVM4J_ANDROID=1 -std=c++11 -Oz -frtti
+ifeq ($(USE_OPENCL), 1)                                                                                                                                             
+	APP_CPPFLAGS += -DTVM_OPENCL_RUNTIME=1
+endif
diff --git a/apps/android_deploy/app/src/main/jni/build.sh b/apps/android_deploy/app/src/main/jni/build.sh
new file mode 100644
index 000000000000..1ca38ae5bd12
--- /dev/null
+++ b/apps/android_deploy/app/src/main/jni/build.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+PATH="$PATH:/usr/local/bin"
+CURR_DIR=$(cd `dirname $0`; pwd)
+ROOT_DIR="$CURR_DIR/../../../../../.."
+javah -o $CURR_DIR/ml_dmlc_tvm_native_c_api.h -cp "$ROOT_DIR/jvm/core/target/*" ml.dmlc.tvm.LibInfo || exit -1
+cp -f $ROOT_DIR/jvm/native/src/main/native/ml_dmlc_tvm_native_c_api.cc $CURR_DIR/ || exit -1
+cp -f $ROOT_DIR/jvm/native/src/main/native/jni_helper_func.h $CURR_DIR/ || exit -1
+rm -rf $CURR_DIR/../libs
+ndk-build --directory=$CURR_DIR
diff --git a/apps/android_deploy/app/src/main/jni/make/config.mk b/apps/android_deploy/app/src/main/jni/make/config.mk
new file mode 100644
index 000000000000..8d6f5a56dd5b
--- /dev/null
+++ b/apps/android_deploy/app/src/main/jni/make/config.mk
@@ -0,0 +1,26 @@
+#-------------------------------------------------------------------------------
+#  Template configuration for compiling
+#
+#  If you want to change the configuration, please use the following
+#  steps. Assume you are on the root directory. First copy the this
+#  file so that any local changes will be ignored by git
+#
+#  cp make/config.mk .
+#
+#  Next modify the according entries, and then compile by
+#
+#  ./build.sh
+#
+#-------------------------------------------------------------------------------
+APP_ABI = all
+
+APP_PLATFORM = android-17
+
+# whether enable OpenCL during compile
+USE_OPENCL = 0
+
+# the additional include headers you want to add, e.g., SDK_PATH/adrenosdk/Development/Inc
+ADD_C_INCLUDES =
+
+# the additional link libs you want to add, e.g., ANDROID_LIB_PATH/libOpenCL.so
+ADD_LDLIBS =
diff --git a/apps/android_deploy/app/src/main/jni/tvm_runtime.h b/apps/android_deploy/app/src/main/jni/tvm_runtime.h
new file mode 100644
index 000000000000..69b1f5c83f1f
--- /dev/null
+++ b/apps/android_deploy/app/src/main/jni/tvm_runtime.h
@@ -0,0 +1,26 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file tvm_runtime.h
+ * \brief Pack all tvm runtime source files
+ */
+#include <sys/stat.h>
+#include <fstream>
+
+#include "../src/runtime/c_runtime_api.cc"
+#include "../src/runtime/cpu_device_api.cc"
+#include "../src/runtime/workspace_pool.cc"
+#include "../src/runtime/module_util.cc"
+#include "../src/runtime/system_lib_module.cc"
+#include "../src/runtime/module.cc"
+#include "../src/runtime/registry.cc"
+#include "../src/runtime/file_util.cc"
+#include "../src/runtime/dso_module.cc"
+#include "../src/runtime/thread_pool.cc"
+#include "../src/runtime/threading_backend.cc"
+
+#include "../src/runtime/graph/graph_runtime.cc"
+
+#ifdef TVM_OPENCL_RUNTIME
+#include "../src/runtime/opencl/opencl_device_api.cc"
+#include "../src/runtime/opencl/opencl_module.cc"
+#endif
diff --git a/apps/android_deploy/app/src/main/res/layout/activity_main.xml b/apps/android_deploy/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 000000000000..b16a5c2548a6
--- /dev/null
+++ b/apps/android_deploy/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="ml.dmlc.tvm.android.demo.MainActivity">
+
+    <android.support.design.widget.AppBarLayout
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:theme="@style/AppTheme.AppBarOverlay">
+
+        <android.support.v7.widget.Toolbar
+            android:id="@+id/toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/actionBarSize"
+            android:background="?attr/colorPrimary"
+            app:popupTheme="@style/AppTheme.PopupOverlay" />
+
+    </android.support.design.widget.AppBarLayout>
+
+    <include layout="@layout/content_main"/>
+
+</android.support.design.widget.CoordinatorLayout>
+
diff --git a/apps/android_deploy/app/src/main/res/layout/content_main.xml b/apps/android_deploy/app/src/main/res/layout/content_main.xml
new file mode 100644
index 000000000000..34de93843645
--- /dev/null
+++ b/apps/android_deploy/app/src/main/res/layout/content_main.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              xmlns:tools="http://schemas.android.com/tools"
+              xmlns:app="http://schemas.android.com/apk/res-auto"
+              android:orientation="vertical"
+              android:layout_width="fill_parent"
+              android:layout_height="wrap_content"
+              app:layout_behavior="@string/appbar_scrolling_view_behavior"
+              tools:showIn="@layout/activity_main">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="horizontal">
+
+        <Button
+            android:id="@+id/btnPickImage"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="Select or Capture picture" />
+
+    </LinearLayout>
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="4dp" />
+    <TextView
+        android:id="@+id/resultTextView"
+        android:layout_width="match_parent"
+        android:layout_height="60dp"
+        android:layout_weight="1"
+        android:textColor="@color/colorPrimary"
+        android:textSize="15sp" />
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="4dp" />
+    <ImageView
+        android:id="@+id/imageView"
+        android:layout_width="match_parent"
+        android:layout_height="375dp"
+        android:layout_weight="1" />
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="10dp" />
+</LinearLayout>
diff --git a/apps/android_deploy/app/src/main/res/values/colors.xml b/apps/android_deploy/app/src/main/res/values/colors.xml
new file mode 100644
index 000000000000..3bdabdf11d00
--- /dev/null
+++ b/apps/android_deploy/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#3F51B5</color>
+    <color name="colorPrimaryDark">#303F9F</color>
+    <color name="colorAccent">#06d467</color>
+</resources>
diff --git a/apps/android_deploy/app/src/main/res/values/strings.xml b/apps/android_deploy/app/src/main/res/values/strings.xml
new file mode 100644
index 000000000000..cf1fa24069a1
--- /dev/null
+++ b/apps/android_deploy/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">TVM Android Demo</string>
+</resources>
\ No newline at end of file
diff --git a/apps/android_deploy/app/src/main/res/values/styles.xml b/apps/android_deploy/app/src/main/res/values/styles.xml
new file mode 100644
index 000000000000..44f664f202f9
--- /dev/null
+++ b/apps/android_deploy/app/src/main/res/values/styles.xml
@@ -0,0 +1,17 @@
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+    <style name="AppTheme.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+    </style>
+    <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
+    <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
+
+</resources>
diff --git a/apps/android_deploy/app/src/main/res/xml/provider_paths.xml b/apps/android_deploy/app/src/main/res/xml/provider_paths.xml
new file mode 100644
index 000000000000..74a5cde1d8fd
--- /dev/null
+++ b/apps/android_deploy/app/src/main/res/xml/provider_paths.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+    <external-path name="external_files" path="."/>
+</paths>
\ No newline at end of file
diff --git a/apps/android_deploy/build.gradle b/apps/android_deploy/build.gradle
new file mode 100644
index 000000000000..f7bbe2641c9d
--- /dev/null
+++ b/apps/android_deploy/build.gradle
@@ -0,0 +1,29 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:2.3.3'
+        classpath 'org.apache.httpcomponents:httpclient:4.5.4'
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        jcenter()
+        maven {
+          url 'https://maven.google.com'
+        }
+        mavenLocal()
+        mavenCentral()
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
diff --git a/apps/android_deploy/dev_tools/gen_keystore.sh b/apps/android_deploy/dev_tools/gen_keystore.sh
new file mode 100644
index 000000000000..e91cd05ad957
--- /dev/null
+++ b/apps/android_deploy/dev_tools/gen_keystore.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+CURR_DIR=$(cd `dirname $0`; pwd)
+keytool -genkey -keystore $CURR_DIR/tvmdemo.keystore -alias tvmdemo -keyalg RSA -validity 10000
diff --git a/apps/android_deploy/dev_tools/sign_apk.sh b/apps/android_deploy/dev_tools/sign_apk.sh
new file mode 100644
index 000000000000..314f82cdb76c
--- /dev/null
+++ b/apps/android_deploy/dev_tools/sign_apk.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+CURR_DIR=$(cd `dirname $0`; pwd)
+APK_DIR=$CURR_DIR/../app/build/outputs/apk
+UNSIGNED_APK=$APK_DIR/app-release-unsigned.apk
+SIGNED_APK=$APK_DIR/tvmdemo-release.apk
+jarsigner -verbose -keystore $CURR_DIR/tvmdemo.keystore -signedjar $SIGNED_APK $UNSIGNED_APK 'tvmdemo'
+echo $SIGNED_APK
diff --git a/apps/android_deploy/settings.gradle b/apps/android_deploy/settings.gradle
new file mode 100644
index 000000000000..e7b4def49cb5
--- /dev/null
+++ b/apps/android_deploy/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/docs/how_to/deploy_android.md b/docs/how_to/deploy_android.md
new file mode 100644
index 000000000000..7a38f24a7e0d
--- /dev/null
+++ b/docs/how_to/deploy_android.md
@@ -0,0 +1,40 @@
+# How to deploy and use compiled model on Android
+
+
+This tutorial explain below aspects (Unlike the android_rpc approach we already have)
+
+  * Build a model for android target
+  * TVM run on Android using Java API
+
+As an example here is a reference block diagram.
+
+![](http://www.tvmlang.org/images/release/tvm_flexible.png)
+
+## Build model for Android Target
+
+NNVM compilation of model for android target could follow same approach like android_rpc.
+
+An reference exampe can be found at [chainer-nnvm-example](https://github.com/tkat0/chainer-nnvm-example)
+
+Above example will directly run the compiled model on RPC target. Below modification at [rum_mobile.py](https://github.com/tkat0/chainer-nnvm-example/blob/5b97fd4d41aa4dde4b0aceb0be311054fb5de451/run_mobile.py#L64) will save the compilation output which is required on android target.
+
+```
+lib.export_library("deploy_lib.so", ndk.create_shared)
+with open("deploy_graph.json", "w") as fo:
+    fo.write(graph.json())
+with open("deploy_param.params", "wb") as fo:
+    fo.write(nnvm.compiler.save_param_dict(params))
+```
+
+deploy_lib.so, deploy_graph.json, deploy_param.params will go to android target.
+
+## TVM run on Android using Java API
+### TVM Runtime for android Target
+
+Refer [here](https://github.com/dmlc/tvm/blob/master/apps/android_deploy/README.md#build-and-installation) to build CPU/OpenCL version flavor TVM runtime for android target.
+
+### Android Native API Reference
+
+From android java TVM API to load model & execute can be refered at this [java](https://github.com/dmlc/tvm/blob/master/apps/android_deploy/app/src/main/java/ml/dmlc/tvm/android/demo/MainActivity.java) sample source.
+
+

From a8197fcae779ca73d8c4167439c6b5c1509d311b Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 29 Mar 2018 10:57:03 -0700
Subject: [PATCH 234/949] [PASS] More reliable error message for lower warp
 (#1065)

---
 src/pass/lower_warp_memory.cc | 23 +++++++++++++++--------
 1 file changed, 15 insertions(+), 8 deletions(-)

diff --git a/src/pass/lower_warp_memory.cc b/src/pass/lower_warp_memory.cc
index dbd073c0b14c..d0412adc08df 100644
--- a/src/pass/lower_warp_memory.cc
+++ b/src/pass/lower_warp_memory.cc
@@ -94,9 +94,13 @@ class WarpStoreCoeffFinder : private IRVisitor {
     CHECK_EQ(m.size(), 2U)
         << "LowerWarpMemory failed due to store index=" << index;
     int coeff;
-    CHECK(arith::GetConstInt(ir::Simplify(m[0]), &coeff) && coeff > 0)
+    Expr mcoeff = ir::Simplify(m[0]);
+
+    CHECK(arith::GetConstInt(mcoeff, &coeff) && coeff > 0)
         << "LowerWarpMemory failed due to store index=" << index
-        << ", require positive constant coefficient on warp index";
+        << ", require positive constant coefficient on warp index " << warp_index_
+        << " but get " << mcoeff;
+
     if (warp_coeff_ != 0) {
       CHECK_EQ(warp_coeff_, coeff)
           << "LowerWarpMemory failed due to two different store coefficient to warp index";
@@ -129,11 +133,6 @@ class WarpIndexFinder : private IRVisitor {
   }
 
  private:
-  void Visit(const NodeRef &node) final {
-    if (warp_index_.defined()) return;
-    IRVisitor::Visit(node);
-  }
-
   /// Visitor implementation
   void Visit_(const AttrStmt *op) final {
     if (op->attr_key == attr::thread_extent) {
@@ -145,7 +144,15 @@ class WarpIndexFinder : private IRVisitor {
             << "Expect threadIdx.x 's size to be equal to warp size("
             << warp_size_ << ")" << " to enable warp memory"
             << " but get " << op->value << " instead";
-        warp_index_ = iv;
+        if (warp_index_.defined()) {
+          CHECK(warp_index_.same_as(iv))
+              << "Find two instance of " << warp_index_->thread_tag
+              << " in the same kernel. "
+              << "Please create it using thread_axis once and reuse the axis "
+              << "across multiple binds in the same kernel";
+        } else {
+          warp_index_ = iv;
+        }
       }
     }
     IRVisitor::Visit_(op);

From 5079feb6e1144c6defdcdc11f81109f04738ff79 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 30 Mar 2018 17:46:10 -0700
Subject: [PATCH 235/949] [DOCS] Try upgrade build (#1066)

---
 Jenkinsfile                                   | 39 +---------
 python/tvm/_ffi/function.py                   |  6 +-
 python/tvm/api.py                             |  1 +
 tests/ci_build/Dockerfile.emscripten          |  6 +-
 tests/ci_build/Dockerfile.gpu                 |  9 ++-
 tests/ci_build/install/ubuntu_install_core.sh |  2 +-
 tests/ci_build/install/ubuntu_install_llvm.sh |  5 ++
 .../ci_build/install/ubuntu_install_opencl.sh |  6 +-
 .../ci_build/install/ubuntu_install_sphinx.sh |  2 +-
 .../python/topi/cuda/conv2d_transpose_nchw.py |  1 +
 topi/python/topi/cuda/dense.py                |  1 +
 topi/python/topi/cuda/depthwise_conv2d.py     |  2 +
 topi/python/topi/cuda/pooling.py              |  2 +
 topi/python/topi/cuda/reduction.py            |  2 +
 topi/python/topi/mali/dense.py                |  1 +
 topi/python/topi/mali/depthwise_conv2d.py     |  1 +
 topi/python/topi/nn/bnn.py                    |  1 +
 topi/python/topi/opengl/conv2d_nchw.py        |  1 +
 topi/python/topi/opengl/dense.py              |  1 +
 topi/python/topi/opengl/pooling.py            |  2 +
 topi/python/topi/rasp/depthwise_conv2d.py     |  1 +
 topi/python/topi/x86/binary_dense.py          |  1 +
 .../deployment/cross_compilation_and_rpc.py   | 71 ++++++++++---------
 23 files changed, 78 insertions(+), 86 deletions(-)

diff --git a/Jenkinsfile b/Jenkinsfile
index b667359f0f2b..7fb8e6b8b5d1 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -87,7 +87,6 @@ stage('Build') {
            cp make/config.mk .
            echo USE_CUDNN=1 >> config.mk
            echo USE_CUDA=1 >> config.mk
-           echo USE_OPENCL=1 >> config.mk
            echo USE_OPENGL=1 >> config.mk
            echo LLVM_CONFIG=llvm-config-4.0 >> config.mk
            echo USE_RPC=1 >> config.mk
@@ -105,6 +104,7 @@ stage('Build') {
         sh "mv lib/libtvm.so lib/libtvm_llvm60.so"
         pack_lib('gpu', tvm_multilib)
         sh """
+           echo USE_OPENCL=1 >> config.mk
            echo USE_ROCM=1 >> config.mk
            echo ROCM_PATH=/opt/rocm >> config.mk
            echo USE_VULKAN=1 >> config.mk
@@ -152,31 +152,6 @@ stage('Build') {
         pack_lib('i386', tvm_multilib)
       }
     }
-  },
-  'web': {
-    node('emcc') {
-      ws('workspace/tvm/build-weblib') {
-        init_git()
-        sh """
-           cp make/config.mk .
-           echo USE_CUDA=0 >> config.mk
-           echo USE_OPENCL=0 >> config.mk
-           echo LLVM_CONFIG=llvm-config >> config.mk
-           echo USE_RPC=0 >> config.mk
-           """
-        sh "${docker_run} emscripten echo testing javascript..."
-        timeout(time: max_time, unit: 'MINUTES') {
-          try {
-            sh "${docker_run} emscripten ./tests/scripts/task_web_build.sh"
-          } catch (exc) {
-            echo 'Incremental compilation failed. Fall back to build from scratch'
-            sh "${docker_run} emscripten make clean"
-            sh "${docker_run} emscripten ./tests/scripts/task_web_build.sh"
-         }
-        }
-        pack_lib('weblib', tvm_lib)
-      }
-    }
   }
 }
 
@@ -256,18 +231,6 @@ stage('Integration Test') {
       }
     }
   },
-  'web': {
-    node('emcc') {
-      ws('workspace/tvm/it-weblib') {
-        init_git()
-        unpack_lib('weblib', tvm_lib)
-        sh "${docker_run} emscripten echo testing javascript..."
-        timeout(time: max_time, unit: 'MINUTES') {
-          sh "${docker_run} emscripten ./tests/scripts/task_web_test.sh"
-        }
-      }
-    }
-  },
   'docs': {
     node('GPU' && 'linux') {
       ws('workspace/tvm/docs-python-gpu') {
diff --git a/python/tvm/_ffi/function.py b/python/tvm/_ffi/function.py
index e0f85be6f1a9..cfda2a35f9b9 100644
--- a/python/tvm/_ffi/function.py
+++ b/python/tvm/_ffi/function.py
@@ -181,10 +181,10 @@ def register(myf):
             myf = convert_to_tvm_func(myf)
         check_call(_LIB.TVMFuncRegisterGlobal(
             c_str(func_name), myf.handle, ioverride))
+        return myf
     if f:
-        register(f)
-    else:
-        return register
+        return register(f)
+    return register
 
 
 def get_global_func(name, allow_missing=False):
diff --git a/python/tvm/api.py b/python/tvm/api.py
index 66c154bc9f00..b827cce72896 100644
--- a/python/tvm/api.py
+++ b/python/tvm/api.py
@@ -652,6 +652,7 @@ def _make_reduce(expr, axis, where=None):
                         for i in range(size))
         return outputs[0] if size == 1 else outputs
 
+    # pylint: disable=keyword-arg-before-vararg
     def reducer(expr, axis, where=None, *args):
         if isinstance(axis, (_schedule.IterVar, list, tuple)):
             assert not args
diff --git a/tests/ci_build/Dockerfile.emscripten b/tests/ci_build/Dockerfile.emscripten
index 59bf02ea7d2c..b4d5a63c52ef 100644
--- a/tests/ci_build/Dockerfile.emscripten
+++ b/tests/ci_build/Dockerfile.emscripten
@@ -15,4 +15,8 @@ RUN bash /install/ubuntu_install_emscripten.sh
 COPY install/ubuntu_install_python_package.sh /install/ubuntu_install_python_package.sh
 RUN bash /install/ubuntu_install_python_package.sh
 
-RUN cp /root/.emscripten /emsdk-portable/
\ No newline at end of file
+RUN chmod a+rwx -R /emsdk-portable
+RUN cp -r /emsdk-portable  /emsdk-portable-backup
+RUN mv /emsdk-portable  /emsdk-portable-x
+RUN mv /emsdk-portable-backup /emsdk-portable
+RUN cp /root/.emscripten /emsdk-portable/
diff --git a/tests/ci_build/Dockerfile.gpu b/tests/ci_build/Dockerfile.gpu
index e49e498b8d40..4b461ebf19c6 100644
--- a/tests/ci_build/Dockerfile.gpu
+++ b/tests/ci_build/Dockerfile.gpu
@@ -1,7 +1,6 @@
 FROM nvidia/cuda:8.0-cudnn7-devel
 
 # Base scripts
-RUN apt-get update --fix-missing
 
 COPY install/ubuntu_install_core.sh /install/ubuntu_install_core.sh
 RUN bash /install/ubuntu_install_core.sh
@@ -12,9 +11,6 @@ RUN bash /install/ubuntu_install_python.sh
 COPY install/ubuntu_install_llvm.sh /install/ubuntu_install_llvm.sh
 RUN bash /install/ubuntu_install_llvm.sh
 
-COPY install/ubuntu_install_opencl.sh /install/ubuntu_install_opencl.sh
-RUN bash /install/ubuntu_install_opencl.sh
-
 COPY install/ubuntu_install_iverilog.sh /install/ubuntu_install_iverilog.sh
 RUN bash /install/ubuntu_install_iverilog.sh
 
@@ -40,8 +36,11 @@ RUN bash /install/ubuntu_install_rocm.sh
 COPY install/ubuntu_install_opengl.sh /install/ubuntu_install_opengl.sh
 RUN bash /install/ubuntu_install_opengl.sh
 
+COPY install/ubuntu_install_opencl.sh /install/ubuntu_install_opencl.sh
+RUN bash /install/ubuntu_install_opencl.sh
+
 # Enable doxygen for c++ doc build
-RUN apt-get install -y doxygen graphviz
+RUN apt-get update && apt-get install -y doxygen graphviz
 
 # Install vulkan
 COPY install/ubuntu_install_vulkan.sh /install/ubuntu_install_vulkan.sh
diff --git a/tests/ci_build/install/ubuntu_install_core.sh b/tests/ci_build/install/ubuntu_install_core.sh
index 9823ae0788ac..efc69c946b97 100644
--- a/tests/ci_build/install/ubuntu_install_core.sh
+++ b/tests/ci_build/install/ubuntu_install_core.sh
@@ -1,5 +1,5 @@
 # install libraries for building c++ core on ubuntu
-apt-get install -y --no-install-recommends --force-yes \
+apt-get update && apt-get install -y --no-install-recommends --force-yes \
         git make libgtest-dev cmake wget unzip libtinfo-dev libz-dev\
         libcurl4-openssl-dev libopenblas-dev g++ sudo
 
diff --git a/tests/ci_build/install/ubuntu_install_llvm.sh b/tests/ci_build/install/ubuntu_install_llvm.sh
index e5b28b911f61..ba0afcd18cc9 100644
--- a/tests/ci_build/install/ubuntu_install_llvm.sh
+++ b/tests/ci_build/install/ubuntu_install_llvm.sh
@@ -8,6 +8,11 @@ echo deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-5.0 main\
 echo deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial-5.0 main\
      >> /etc/apt/sources.list.d/llvm.list
 
+echo deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-6.0 main\
+     >> /etc/apt/sources.list.d/llvm.list
+echo deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial-6.0 main\
+     >> /etc/apt/sources.list.d/llvm.list
+
 echo deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial main\
      >> /etc/apt/sources.list.d/llvm.list
 echo deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial main\
diff --git a/tests/ci_build/install/ubuntu_install_opencl.sh b/tests/ci_build/install/ubuntu_install_opencl.sh
index 636236539a98..ca4d1d04fd5c 100644
--- a/tests/ci_build/install/ubuntu_install_opencl.sh
+++ b/tests/ci_build/install/ubuntu_install_opencl.sh
@@ -1,8 +1,8 @@
 # Install OpenCL runtime in nvidia docker.
-apt-get install -y --no-install-recommends --force-yes \
-        ocl-icd-libopencl1 \
+apt-get update && apt-get install -y --no-install-recommends --force-yes \
+        ocl-icd-opencl-dev \
         clinfo && \
-        rm -rf /var/lib/apt/lists/*
+    rm -rf /var/lib/apt/lists/*
 
 mkdir -p /etc/OpenCL/vendors && \
     echo "libnvidia-opencl.so.1" > /etc/OpenCL/vendors/nvidia.icd
diff --git a/tests/ci_build/install/ubuntu_install_sphinx.sh b/tests/ci_build/install/ubuntu_install_sphinx.sh
index 767643f10488..30d4596aabf2 100644
--- a/tests/ci_build/install/ubuntu_install_sphinx.sh
+++ b/tests/ci_build/install/ubuntu_install_sphinx.sh
@@ -1 +1 @@
-pip install sphinx==1.6.2 sphinx-gallery sphinx_rtd_theme matplotlib Image commonmark>=0.7.3 docutils>=0.11
+pip install sphinx sphinx-gallery sphinx_rtd_theme matplotlib Image commonmark>=0.7.3 docutils>=0.11
diff --git a/topi/python/topi/cuda/conv2d_transpose_nchw.py b/topi/python/topi/cuda/conv2d_transpose_nchw.py
index edd255a8fac0..0d439bfdfdea 100644
--- a/topi/python/topi/cuda/conv2d_transpose_nchw.py
+++ b/topi/python/topi/cuda/conv2d_transpose_nchw.py
@@ -74,6 +74,7 @@ def schedule(temp, Filter, Output):
             conv2d_56_64_64(s, Filter, temp_S, Filter_S, Out, Out_L)
 
     def traverse(OP):
+        """Internal travserse function"""
         # inline all one-to-one-mapping operators except the last stage (output)
         if tag.is_injective(OP.tag):
             if OP not in s.outputs:
diff --git a/topi/python/topi/cuda/dense.py b/topi/python/topi/cuda/dense.py
index 6207c14220d0..7c62fab743f5 100644
--- a/topi/python/topi/cuda/dense.py
+++ b/topi/python/topi/cuda/dense.py
@@ -87,6 +87,7 @@ def _schedule(Dense):
         s[Out].set_store_predicate(thread_x.var.equal(0))
 
     def traverse(OP):
+        """Internal travserse function"""
         # inline all one-to-one-mapping operators except the last stage (output)
         if tag.is_broadcast(OP.tag):
             if OP not in s.outputs:
diff --git a/topi/python/topi/cuda/depthwise_conv2d.py b/topi/python/topi/cuda/depthwise_conv2d.py
index 851a00db0a48..3045774296d0 100644
--- a/topi/python/topi/cuda/depthwise_conv2d.py
+++ b/topi/python/topi/cuda/depthwise_conv2d.py
@@ -102,6 +102,7 @@ def _schedule(PaddedInput, Filter, DepthwiseConv2d):
         s[FS].bind(tx, thread_x)
 
     def traverse(OP):
+        """Internal travserse function"""
         # inline all one-to-one-mapping operators except the last stage (output)
         if tag.is_broadcast(OP.tag):
             if OP not in s.outputs:
@@ -178,6 +179,7 @@ def _schedule(temp, Filter, DepthwiseConv2d):
         s[FS].bind(fused, thread_x)
 
     def traverse(OP):
+        """Internal travserse function"""
         # inline all one-to-one-mapping operators except the last stage (output)
         if tag.is_broadcast(OP.tag):
             if OP not in s.outputs:
diff --git a/topi/python/topi/cuda/pooling.py b/topi/python/topi/cuda/pooling.py
index 4ed5ae66c19b..95ef3ce67c5c 100644
--- a/topi/python/topi/cuda/pooling.py
+++ b/topi/python/topi/cuda/pooling.py
@@ -47,6 +47,7 @@ def _schedule(Pool):
             s[Pool].compute_at(s[Out], tx)
 
     def traverse(OP):
+        """Internal travserse function"""
         # inline all one-to-one-mapping operators except the last stage (output)
         if tag.is_broadcast(OP.tag):
             if OP not in s.outputs:
@@ -101,6 +102,7 @@ def _schedule(PaddedInput, Pool):
             s[Pool].compute_at(s[Out], tx)
 
     def traverse(OP):
+        """Internal travserse function"""
         # inline all one-to-one-mapping operators except the last stage (output)
         if tag.is_broadcast(OP.tag):
             if OP not in s.outputs:
diff --git a/topi/python/topi/cuda/reduction.py b/topi/python/topi/cuda/reduction.py
index 932f2aae3098..601d11ab9a2f 100644
--- a/topi/python/topi/cuda/reduction.py
+++ b/topi/python/topi/cuda/reduction.py
@@ -87,6 +87,7 @@ def schedule_reduce(outs):
     sch = tvm.create_schedule([x.op for x in outs])
 
     def traverse_before_reduce(operator):
+        """Internal travserse function"""
         if isinstance(operator, tvm.tensor.PlaceholderOp):
             return
         elif tag.is_injective(operator.tag):
@@ -97,6 +98,7 @@ def traverse_before_reduce(operator):
             raise RuntimeError("Unsupported operator: %s" % operator.tag)
 
     def traverse_after_reduce(operator):
+        """Internal travserse function"""
         if tag.is_broadcast(operator.tag):
             raise RuntimeError("Not yet support ewise after reduce")
         elif operator.tag == 'comm_reduce':
diff --git a/topi/python/topi/mali/dense.py b/topi/python/topi/mali/dense.py
index d3edeafed3b3..ff88ce51866f 100644
--- a/topi/python/topi/mali/dense.py
+++ b/topi/python/topi/mali/dense.py
@@ -82,6 +82,7 @@ def fuse_and_bind(s, tensor, axis=None, num_thread=None):
 #        print(tvm.lower(s, [data, weight, bias, outs[0]], simple_mode=True))
 
     def traverse(OP):
+        """Internal travserse function"""
         # inline all one-to-one-mapping operators except the last stage (output)
         if tag.is_broadcast(OP.tag):
             if OP not in s.outputs:
diff --git a/topi/python/topi/mali/depthwise_conv2d.py b/topi/python/topi/mali/depthwise_conv2d.py
index 46ce7f747def..428140550528 100644
--- a/topi/python/topi/mali/depthwise_conv2d.py
+++ b/topi/python/topi/mali/depthwise_conv2d.py
@@ -87,6 +87,7 @@ def tile_and_bind3d(tensor, z, y, x, z_factor=2, y_factor=None, x_factor=None):
             s[conv].compute_at(s[output], ji)
 
     def traverse(op):
+        """Internal travserse function"""
         # inline all one-to-one-mapping operators except the last stage (output)
         if tag.is_broadcast(op.tag):
             if op not in s.outputs:
diff --git a/topi/python/topi/nn/bnn.py b/topi/python/topi/nn/bnn.py
index 39b9d2a15a1b..591a082e7d30 100644
--- a/topi/python/topi/nn/bnn.py
+++ b/topi/python/topi/nn/bnn.py
@@ -43,6 +43,7 @@ def _binarize_pack(*indices):
             if j == 31:
                 return packed
             packed = packed << 1
+        raise RuntimeError("not resach")
 
     return tvm.compute(oshape, _binarize_pack, name=name, tag='binarize_pack')
 
diff --git a/topi/python/topi/opengl/conv2d_nchw.py b/topi/python/topi/opengl/conv2d_nchw.py
index c633d8a21e6e..7e8b7275f75d 100644
--- a/topi/python/topi/opengl/conv2d_nchw.py
+++ b/topi/python/topi/opengl/conv2d_nchw.py
@@ -31,6 +31,7 @@ def _schedule(conv2d, data):
         s[data].opengl()
 
     def traverse(OP):
+        """Internal travserse function"""
         # inline all one-to-one-mapping operators except the last stage (output)
         if tag.is_broadcast(OP.tag):
             if OP not in s.outputs:
diff --git a/topi/python/topi/opengl/dense.py b/topi/python/topi/opengl/dense.py
index e7cf008ae240..e4d327afa4d6 100644
--- a/topi/python/topi/opengl/dense.py
+++ b/topi/python/topi/opengl/dense.py
@@ -31,6 +31,7 @@ def _schedule(Dense):
         s[Out].opengl()
 
     def traverse(OP):
+        """Internal travserse function"""
         # inline all one-to-one-mapping operators except the last stage (output)
         if tag.is_broadcast(OP.tag):
             if OP not in s.outputs:
diff --git a/topi/python/topi/opengl/pooling.py b/topi/python/topi/opengl/pooling.py
index 5c26c56bb1ac..dc4f47609f92 100644
--- a/topi/python/topi/opengl/pooling.py
+++ b/topi/python/topi/opengl/pooling.py
@@ -30,6 +30,7 @@ def _schedule(Pool):
         s[Out].opengl()
 
     def traverse(OP):
+        """Internal travserse function"""
         # inline all one-to-one-mapping operators except the last stage (output)
         if tag.is_broadcast(OP.tag):
             if OP not in s.outputs:
@@ -75,6 +76,7 @@ def _schedule(PaddedInput, Pool):
         s[Out].opengl()
 
     def traverse(OP):
+        """Internal travserse function"""
         # inline all one-to-one-mapping operators except the last stage (output)
         if tag.is_broadcast(OP.tag):
             if OP not in s.outputs:
diff --git a/topi/python/topi/rasp/depthwise_conv2d.py b/topi/python/topi/rasp/depthwise_conv2d.py
index a6fd691f843f..b7cb6570987a 100644
--- a/topi/python/topi/rasp/depthwise_conv2d.py
+++ b/topi/python/topi/rasp/depthwise_conv2d.py
@@ -164,6 +164,7 @@ def schedule_depthwise_conv2d(outs):
     s = tvm.create_schedule([x.op for x in outs])
 
     def traverse(op):
+        """Internal travserse function"""
         # inline all one-to-one-mapping operators except the last stage (output)
         if tag.is_broadcast(op.tag):
             if op not in s.outputs:
diff --git a/topi/python/topi/x86/binary_dense.py b/topi/python/topi/x86/binary_dense.py
index 11fccba7b644..8b28dd728842 100644
--- a/topi/python/topi/x86/binary_dense.py
+++ b/topi/python/topi/x86/binary_dense.py
@@ -35,6 +35,7 @@ def _schedule(A, B, C):
         s[Out].vectorize(xi)
 
     def traverse(OP):
+        """Internal travserse function"""
         # inline all one-to-one-mapping operators except the last stage (output)
         if tag.is_broadcast(OP.tag):
             if OP not in s.outputs:
diff --git a/tutorials/deployment/cross_compilation_and_rpc.py b/tutorials/deployment/cross_compilation_and_rpc.py
index ccaf9e79e3de..f06bbfca6407 100644
--- a/tutorials/deployment/cross_compilation_and_rpc.py
+++ b/tutorials/deployment/cross_compilation_and_rpc.py
@@ -108,8 +108,6 @@
 import numpy as np
 from tvm.contrib import rpc, util
 
-server = rpc.Server(host='0.0.0.0', port=9090, use_popen=True)
-
 ######################################################################
 # Declare and Cross Compile Kernel on Local Machine
 # -------------------------------------------------
@@ -241,47 +239,52 @@
 #    But here we set 'llvm' to enable this tutorial to run locally.
 #
 #    Also we need to build the runtime with the flag `USE_OPENCL=1`.
-
 # build kernel (different from cpu, we need bind axis for OpenCL)
-s = tvm.create_schedule(B.op)
-xo, xi = s[B].split(B.op.axis[0], factor=32)
-s[B].bind(xo, tvm.thread_axis("blockIdx.x"))
-s[B].bind(xi, tvm.thread_axis("threadIdx.x"))
-f = tvm.build(s, [A, B], "opencl", target_host="llvm", name="myadd")
+#
+# The following functions shows how we can deploy CL
+def deploy_cl():
+    s = tvm.create_schedule(B.op)
+    xo, xi = s[B].split(B.op.axis[0], factor=32)
+    s[B].bind(xo, tvm.thread_axis("blockIdx.x"))
+    s[B].bind(xi, tvm.thread_axis("threadIdx.x"))
+    f = tvm.build(s, [A, B], "opencl", target_host="llvm", name="myadd")
 
-# save files
-path_o = temp.relpath("myadd.o")
-path_cl = temp.relpath("myadd.cl")
-path_json = temp.relpath("myadd.tvm_meta.json")
-f.save(path_o)
-f.imported_modules[0].save(path_cl)
+    # save files
+    path_o = temp.relpath("myadd.o")
+    path_cl = temp.relpath("myadd.cl")
+    path_json = temp.relpath("myadd.tvm_meta.json")
+    f.save(path_o)
+    f.imported_modules[0].save(path_cl)
 
-# upload files
-remote.upload(path_o)
-remote.upload(path_cl)
-remote.upload(path_json)
+    # upload files
+    remote.upload(path_o)
+    remote.upload(path_cl)
+    remote.upload(path_json)
 
-# load files on remote device
-fhost = remote.load_module("myadd.o")
-fdev = remote.load_module("myadd.cl")
-fhost.import_module(fdev)
+    # load files on remote device
+    fhost = remote.load_module("myadd.o")
+    fdev = remote.load_module("myadd.cl")
+    fhost.import_module(fdev)
+
+    # run
+    ctx = remote.cl(0)
+    a = tvm.nd.array(np.random.uniform(size=1024).astype(A.dtype), ctx)
+    b = tvm.nd.array(np.zeros(1024, dtype=A.dtype), ctx)
+    fhost(a, b)
+    np.testing.assert_equal(b.asnumpy(), a.asnumpy() + 1)
 
-# run
-ctx = remote.cl(0)
-a = tvm.nd.array(np.random.uniform(size=1024).astype(A.dtype), ctx)
-b = tvm.nd.array(np.zeros(1024, dtype=A.dtype), ctx)
-fhost(a, b)
-np.testing.assert_equal(b.asnumpy(), a.asnumpy() + 1)
 
 #####################################################################
 # Instead of uploading files separately, there is a more convinient way.
 # You can export libraray as a tar ball.
-path_tar = temp.relpath("myadd.tar")
-f.export_library(path_tar)
-remote.upload(path_tar)
-fhost = remote.load_module("myadd.tar")
-fhost(a, b)
-np.testing.assert_equal(b.asnumpy(), a.asnumpy() + 1)
+# The following functions shows how we can deploy by tar ball
+def deploy_cl_by_tar():
+    path_tar = temp.relpath("myadd.tar")
+    f.export_library(path_tar)
+    remote.upload(path_tar)
+    fhost = remote.load_module("myadd.tar")
+    fhost(a, b)
+    np.testing.assert_equal(b.asnumpy(), a.asnumpy() + 1)
 
 # terminate the server after experiment
 server.terminate()

From 84ad5fe8188a17252b84a1c866e011d0fe3bbd12 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 30 Mar 2018 22:17:53 -0700
Subject: [PATCH 236/949] [DOCS] Code quality comment (#1069)

---
 CONTRIBUTORS.md           | 7 +++++--
 docs/how_to/contribute.md | 6 +++++-
 2 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index e261aebd7245..3254f5442f06 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -6,6 +6,9 @@ The package is developed and used by the community.
 We actively seek committers that come from community contributors who:
 - Made substantial contributions to the project.
   - All forms of contributions are valued (see detail in next section).
+- Maintain quality standards in contributions
+  - e.g. consistently made contributions without requiring extensive
+    rewrites prior to being merged.
 - Willing to spend time on maintaining and lead the project.
 
 Contributions
@@ -23,10 +26,10 @@ list of contributions that are welcomed
 - Test cases to make the codebase more robust
 - Tutorials, blog posts, talks that promote the project.
 
-
 How to Contribute
 -----------------
-See [Contributor guide](docs/how_to/contribute.md) on how to contribute.
+See [Contributor guide](docs/how_to/contribute.md) on tips for contributions.
+
 
 Committers
 ----------
diff --git a/docs/how_to/contribute.md b/docs/how_to/contribute.md
index bd31a62e52a9..9818f829a170 100644
--- a/docs/how_to/contribute.md
+++ b/docs/how_to/contribute.md
@@ -12,7 +12,7 @@ Everyone is more than welcome to contribute. It is a way to make the project bet
   - [How to resolve conflict with master](#how-to-resolve-conflict-with-master)
   - [How to combine multiple commits into one](#how-to-combine-multiple-commits-into-one)
   - [What is the consequence of force push](#what-is-the-consequence-of-force-push)
-* [Document](#document)
+* [Code Quality](#code-quality)
 * [Testcases](#testcases)
 * [Core Library](#core-library)
 * [Python Package](#python-package)
@@ -85,6 +85,10 @@ git push --force
 The previous two tips requires force push, this is because we altered the path of the commits.
 It is fine to force push to your own fork, as long as the commits changed are only yours.
 
+## Code Quality
+
+Each patch we made can also become a potential technical debt in the future. We value high-quality code that can be understood and maintained by the community. We encourage contributors to submit coverage test cases, write or contribute comments on their code to explain the logic and provide reviews feedbacks to hold these standards.
+
 ## Testcases
 - All the testcases are in tests
 

From 5b764c37146907d698aab05e45802fdd59590933 Mon Sep 17 00:00:00 2001
From: ziheng <ziheng@apache.org>
Date: Sat, 31 Mar 2018 13:19:11 +0800
Subject: [PATCH 237/949]  [TOPI] Enhance Conv2D for More Data Type (#922)

---
 topi/python/topi/nn/conv2d.py             | 47 ++++++++++++++++++++---
 topi/python/topi/rasp/conv2d.py           | 28 ++++++++++++++
 topi/python/topi/rasp/depthwise_conv2d.py | 18 +++++++++
 3 files changed, 87 insertions(+), 6 deletions(-)

diff --git a/topi/python/topi/nn/conv2d.py b/topi/python/topi/nn/conv2d.py
index d6488b164d04..81263912bc9b 100644
--- a/topi/python/topi/nn/conv2d.py
+++ b/topi/python/topi/nn/conv2d.py
@@ -45,13 +45,37 @@
     Workload('float32', 'float32', 14, 14, 512, 512, 1, 1, 0, 0, 1, 1),
     Workload('float32', 'float32', 7, 7, 512, 1024, 1, 1, 0, 0, 1, 1),
     Workload('float32', 'float32', 7, 7, 1024, 1024, 1, 1, 0, 0, 1, 1),
+    # workloads of resnet18 on imagenet (int16->int32 version)
+    Workload('int16', 'int32', 224, 224, 3, 64, 7, 7, 3, 3, 2, 2),
+    Workload('int16', 'int32', 56, 56, 64, 64, 3, 3, 1, 1, 1, 1),
+    Workload('int16', 'int32', 56, 56, 64, 64, 1, 1, 0, 0, 1, 1),
+    Workload('int16', 'int32', 56, 56, 64, 128, 3, 3, 1, 1, 2, 2),
+    Workload('int16', 'int32', 56, 56, 64, 128, 1, 1, 0, 0, 2, 2),
+    Workload('int16', 'int32', 28, 28, 128, 128, 3, 3, 1, 1, 1, 1),
+    Workload('int16', 'int32', 28, 28, 128, 256, 3, 3, 1, 1, 2, 2),
+    Workload('int16', 'int32', 28, 28, 128, 256, 1, 1, 0, 0, 2, 2),
+    Workload('int16', 'int32', 14, 14, 256, 256, 3, 3, 1, 1, 1, 1),
+    Workload('int16', 'int32', 14, 14, 256, 512, 3, 3, 1, 1, 2, 2),
+    Workload('int16', 'int32', 14, 14, 256, 512, 1, 1, 0, 0, 2, 2),
+    Workload('int16', 'int32', 7, 7, 512, 512, 3, 3, 1, 1, 1, 1),
+    # workloads of mobile net on imagenet (int16->int32 version)
+    Workload('int16', 'int32', 224, 224, 3, 32, 3, 3, 1, 1, 2, 2),
+    Workload('int16', 'int32', 112, 112, 32, 64, 1, 1, 0, 0, 1, 1),
+    Workload('int16', 'int32', 56, 56, 64, 128, 1, 1, 0, 0, 1, 1),
+    Workload('int16', 'int32', 56, 56, 128, 128, 1, 1, 0, 0, 1, 1),
+    Workload('int16', 'int32', 28, 28, 128, 256, 1, 1, 0, 0, 1, 1),
+    Workload('int16', 'int32', 28, 28, 256, 256, 1, 1, 0, 0, 1, 1),
+    Workload('int16', 'int32', 14, 14, 256, 512, 1, 1, 0, 0, 1, 1),
+    Workload('int16', 'int32', 14, 14, 512, 512, 1, 1, 0, 0, 1, 1),
+    Workload('int16', 'int32', 7, 7, 512, 1024, 1, 1, 0, 0, 1, 1),
+    Workload('int16', 'int32', 7, 7, 1024, 1024, 1, 1, 0, 0, 1, 1),
 ]
 
 # platform specific schedule
 _CONV_SCHEDULE = {}
 
 @tvm.target.generic_func
-def conv2d(data, kernel, stride, padding, layout='NCHW', out_dtype='float32'):
+def conv2d(data, kernel, stride, padding, layout='NCHW', out_dtype=None):
     """Conv2D operator.
 
     Parameters
@@ -97,7 +121,9 @@ def _get_workload(data, kernel, stride, padding, out_dtype):
         HSTR, WSTR = stride
     else:
         HSTR, WSTR = stride, stride
-    assert data.dtype == kernel.dtype, "Do not support inputs with different data types now."
+    assert data.dtype == kernel.dtype, \
+        "Do not support inputs with different data types now. ' \
+        '{} vs. {}".format(data.dtype, kernel.dtype)
     return Workload(data.dtype, out_dtype, IH, IW, CI, CO, KH, KW, HPAD, WPAD, HSTR, WSTR)
 
 
@@ -111,8 +137,11 @@ def _get_schedule(wkl):
     # This return has no use, merely to supress pylint warning
     return wkl
 
-def _spatial_pack(data, kernel, stride, padding, out_dtype):
+
+def _spatial_pack(data, kernel, stride, padding, out_dtype=None):
     """ Compute convolution with pack on spatial axes. """
+    if out_dtype is None:
+        out_dtype = data.dtype
     assert data.shape[0].value == 1, "spatial pack convolution only support batch size=1"
     wkl = _get_workload(data, kernel, stride, padding, out_dtype)
     sch = _get_schedule(wkl)
@@ -172,8 +201,10 @@ def _spatial_pack(data, kernel, stride, padding, out_dtype):
     return output
 
 
-def _im2col_pack(data, kernel, stride, padding, out_dtype):
+def _im2col_pack(data, kernel, stride, padding, out_dtype=None):
     """ Compute convolution with im2col pack layout. """
+    if out_dtype is None:
+        out_dtype = data.dtype
     assert data.shape[0].value == 1, "im2col pack convolution only support batch size=1"
     wkl = _get_workload(data, kernel, stride, padding, out_dtype)
     sch = _get_schedule(wkl)
@@ -238,7 +269,7 @@ def _im2col_pack(data, kernel, stride, padding, out_dtype):
     return output
 
 
-def conv2d_nchw(Input, Filter, stride, padding, out_dtype='float32'):
+def conv2d_nchw(Input, Filter, stride, padding, out_dtype=None):
     """Convolution operator in NCHW layout.
 
     Parameters
@@ -260,6 +291,8 @@ def conv2d_nchw(Input, Filter, stride, padding, out_dtype='float32'):
     Output : tvm.Tensor
         4-D with shape [batch, out_channel, out_height, out_width]
     """
+    if out_dtype is None:
+        out_dtype = Input.dtype
     assert isinstance(stride, int) or len(stride) == 2
     batch, in_channel, in_height, in_width = Input.shape
     num_filter, channel, kernel_h, kernel_w = Filter.shape
@@ -289,7 +322,7 @@ def conv2d_nchw(Input, Filter, stride, padding, out_dtype='float32'):
             axis=[rc, ry, rx]), tag="conv2d_nchw")
 
 
-def conv2d_hwcn(Input, Filter, stride, padding, out_dtype='float32'):
+def conv2d_hwcn(Input, Filter, stride, padding, out_dtype=None):
     """Convolution operator in HWCN layout.
 
     Parameters
@@ -311,6 +344,8 @@ def conv2d_hwcn(Input, Filter, stride, padding, out_dtype='float32'):
     output : tvm.Tensor
         4-D with shape [out_height, out_width, out_channel, batch]
     """
+    if out_dtype is None:
+        out_dtype = Input.dtype
     assert isinstance(stride, int) or len(stride) == 2
     in_height, in_width, in_channel, batch = Input.shape
     kernel_h, kernel_w, channel, num_filter = Filter.shape
diff --git a/topi/python/topi/rasp/conv2d.py b/topi/python/topi/rasp/conv2d.py
index 6e5a1b335ccf..73953c016a57 100644
--- a/topi/python/topi/rasp/conv2d.py
+++ b/topi/python/topi/rasp/conv2d.py
@@ -37,6 +37,32 @@
     SpatialPack(2, 2, 8, 1, 8, False),
     Im2ColPack(7, 4, 1, 16, False),
     Im2ColPack(7, 4, 1, 4, True),
+
+    # int8 imagenet
+    SpatialPack(2, 2, 4, 19, 8, False),
+    SpatialPack(2, 2, 8, 1, 4, True),
+    SpatialPack(2, 2, 8, 7, 4, False),
+    SpatialPack(2, 4, 4, 7, 16, False),
+    SpatialPack(1, 7, 4, 14, 4, True),
+    SpatialPack(2, 2, 8, 5, 1, False),
+    SpatialPack(1, 2, 16, 3, 8, True),
+    SpatialPack(1, 7, 4, 1, 16, True),
+    SpatialPack(2, 2, 8, 2, 16, True),
+    SpatialPack(1, 1, 8, 4, 4, True),
+    SpatialPack(1, 1, 4, 1, 8, False),
+    SpatialPack(1, 1, 8, 1, 16, True),
+
+    # int8 mobilenet
+    SpatialPack(2, 2, 8, 8, 1, True),
+    SpatialPack(1, 7, 4, 16, 4, True),
+    SpatialPack(1, 4, 8, 1, 1, True),
+    SpatialPack(1, 4, 8, 1, 1, True),
+    SpatialPack(1, 4, 8, 4, 8, True),
+    SpatialPack(1, 4, 8, 7, 1, True),
+    SpatialPack(1, 2, 8, 2, 32, True),
+    SpatialPack(1, 2, 16, 2, 16, True),
+    SpatialPack(1, 1, 32, 1, 16, False),
+    SpatialPack(1, 1, 16, 1, 32, True),
 ]
 
 @_get_schedule.register("rasp")
@@ -50,6 +76,8 @@ def _schedule_conv2d(wkl):
 
 @conv2d.register("rasp")
 def _declaration_conv2d(data, kernel, stride, padding, layout, out_dtype):
+    if out_dtype is None:
+        out_dtype = data.dtype
     assert layout == 'NCHW', "only support NCHW convolution on rasp"
     assert data.shape[0].value == 1, "only support batch size=1 convolution on rasp"
     wkl = _get_workload(data, kernel, stride, padding, out_dtype)
diff --git a/topi/python/topi/rasp/depthwise_conv2d.py b/topi/python/topi/rasp/depthwise_conv2d.py
index b7cb6570987a..34dc1f6caef9 100644
--- a/topi/python/topi/rasp/depthwise_conv2d.py
+++ b/topi/python/topi/rasp/depthwise_conv2d.py
@@ -24,6 +24,15 @@
     _Workload('float32', 'float32', 14, 14, 512, 1, 3, 3, 1, 1, 1, 1),
     _Workload('float32', 'float32', 14, 14, 512, 1, 3, 3, 1, 1, 2, 2),
     _Workload('float32', 'float32', 7, 7, 1024, 1, 3, 3, 1, 1, 1, 1),
+    _Workload('int16', 'int32', 112, 112, 32, 1, 3, 3, 1, 1, 1, 1),
+    _Workload('int16', 'int32', 112, 112, 64, 1, 3, 3, 1, 1, 2, 2),
+    _Workload('int16', 'int32', 56, 56, 128, 1, 3, 3, 1, 1, 1, 1),
+    _Workload('int16', 'int32', 56, 56, 128, 1, 3, 3, 1, 1, 2, 2),
+    _Workload('int16', 'int32', 28, 28, 256, 1, 3, 3, 1, 1, 1, 1),
+    _Workload('int16', 'int32', 28, 28, 256, 1, 3, 3, 1, 1, 2, 2),
+    _Workload('int16', 'int32', 14, 14, 512, 1, 3, 3, 1, 1, 1, 1),
+    _Workload('int16', 'int32', 14, 14, 512, 1, 3, 3, 1, 1, 2, 2),
+    _Workload('int16', 'int32', 7, 7, 1024, 1, 3, 3, 1, 1, 1, 1),
 ]
 
 _SCHEDULES = [
@@ -36,6 +45,15 @@
     _Schedule(1, 1, 8, 8, True),
     _Schedule(1, 1, 4, 1, False),
     _Schedule(1, 1, 4, 4, False),
+    _Schedule(2, 4, 4, 2, False),
+    _Schedule(2, 7, 4, 1, True),
+    _Schedule(2, 4, 4, 4, False),
+    _Schedule(2, 2, 4, 4, False),
+    _Schedule(2, 2, 8, 4, False),
+    _Schedule(2, 2, 4, 4, True),
+    _Schedule(2, 2, 8, 4, False),
+    _Schedule(1, 2, 8, 4, True),
+    _Schedule(1, 1, 4, 8, True),
 ]
 
 def _get_workload(data, kernel, stride, padding, out_dtype):

From 42031ef1e09670c8da1b7315052a3c4c4edaac17 Mon Sep 17 00:00:00 2001
From: kun-zh <32951065+kun-zh@users.noreply.github.com>
Date: Sun, 1 Apr 2018 01:17:39 +0800
Subject: [PATCH 238/949] Fix an issue in ReplaceDataFlow for issue 1043
 (#1062)

---
 src/schedule/schedule_dataflow_rewrite.cc     | 24 +++++++++++++++----
 .../unittest/test_pass_storage_rewrite.py     | 14 +++++++++++
 2 files changed, 33 insertions(+), 5 deletions(-)

diff --git a/src/schedule/schedule_dataflow_rewrite.cc b/src/schedule/schedule_dataflow_rewrite.cc
index 562eff417dd2..1d9dfed4e5e1 100644
--- a/src/schedule/schedule_dataflow_rewrite.cc
+++ b/src/schedule/schedule_dataflow_rewrite.cc
@@ -57,13 +57,21 @@ Expr InjectPredicate(const Array<Expr>& predicates,
 
 // Replace data flow appears in all stages given the tensor change.
 // Also update vmap if subsequent dataflow need to be replaced.
+// Need to keep an update to the date transitive closure property on the vmap by a reverse map.
 void ReplaceDataFlow(const Array<Stage>& stages,
-                     std::unordered_map<Tensor, Tensor>* vmap) {
+                     std::unordered_map<Tensor, Tensor>* vmap,
+                     std::unordered_map<Tensor, Tensor>* rvmap) {
   for (Stage s : stages) {
     Operation op = s->op->ReplaceInputs(s->op, *vmap);
     if (!op.same_as(s->op)) {
       for (int i = 0; i < op->num_outputs(); ++i) {
-        (*vmap)[s->op.output(i)] = op.output(i);
+        auto it = rvmap->find(s->op.output(i));
+        if (it != rvmap->end()) {
+          (*vmap)[it->second] = op.output(i);
+        } else {
+          (*vmap)[s->op.output(i)] = op.output(i);
+          (*rvmap)[op.output(i)] = s->op.output(i);
+        }
       }
       s->op = op;
     }
@@ -91,6 +99,7 @@ Tensor Schedule::cache_read(const Tensor& tensor,
   vsub[sugar_tensor] = cache;
 
   std::unordered_map<Tensor, Tensor> vmap;
+  std::unordered_map<Tensor, Tensor> rvmap;
   for (Operation op : readers) {
     Stage s = operator[](op);
     Operation repl_op = s->op->ReplaceInputs(s->op, vsub);
@@ -98,9 +107,10 @@ Tensor Schedule::cache_read(const Tensor& tensor,
         << "Cannot find " << tensor
         << " in the inputs of " << s->op;
     vmap[s->op.output(0)] = repl_op.output(0);
+    rvmap[repl_op.output(0)] = s->op.output(0);
     s->op = repl_op;
   }
-  ReplaceDataFlow((*this)->stages, &vmap);
+  ReplaceDataFlow((*this)->stages, &vmap, &rvmap);
   ArrayNode* stages = (*this)->stages.CopyOnWrite();
   Stage op_stage = operator[](tensor->op);
   size_t pos = FindNodeRef(stages, op_stage);
@@ -197,8 +207,10 @@ Tensor CacheWriteWithReLayout(Schedule sch,
       {cache_tensor(args)});
   // The replace of the dataflow
   std::unordered_map<Tensor, Tensor> vmap;
+  std::unordered_map<Tensor, Tensor> rvmap;
   vmap[orig_stage->op.output(0)] = orig_new_op.output(0);
-  ReplaceDataFlow(sch->stages, &vmap);
+  rvmap[orig_new_op.output(0)] = orig_stage->op.output(0);
+  ReplaceDataFlow(sch->stages, &vmap, &rvmap);
   // mutate orig stage
   orig_stage->op = orig_new_op;
   orig_stage->all_iter_vars = orig_stage->op->root_iter_vars();
@@ -583,10 +595,12 @@ Array<Tensor> Schedule::rfactor(const Tensor& tensor,
     }, reduce_stage->op->name + ".repl");
 
   std::unordered_map<Tensor, Tensor> vmap;
+  std::unordered_map<Tensor, Tensor> rvmap;
   for (int idx = 0; idx < size; ++idx) {
     vmap[old_tensors[idx]] = repl_tensors[idx];
+    rvmap[repl_tensors[idx]] = old_tensors[idx];
   }
-  ReplaceDataFlow((*this)->stages, &vmap);
+  ReplaceDataFlow((*this)->stages, &vmap, &rvmap);
   // revamp the reduction stage.
   reduce_stage->op = repl_tensors[0]->op;
   reduce_stage->all_iter_vars = repl_tensors[0]->op->root_iter_vars();
diff --git a/tests/python/unittest/test_pass_storage_rewrite.py b/tests/python/unittest/test_pass_storage_rewrite.py
index 994db4ce5f15..2bb02998982f 100644
--- a/tests/python/unittest/test_pass_storage_rewrite.py
+++ b/tests/python/unittest/test_pass_storage_rewrite.py
@@ -442,6 +442,19 @@ def verify(n):
     tvm.ir_pass.PostOrderVisit(body, verify)
     assert num_alloc[0] == 1
 
+def test_replace_dataflow():
+    shape = (255,)
+    A = tvm.placeholder(shape, name = "A")
+    B = tvm.compute(shape, lambda i: A[i] + A[i], name = "B")
+    C = tvm.compute(shape, lambda i: A[i] + B[i], name = "C")
+    D = tvm.compute(shape, lambda i: A[i] + C[i], name = "D")
+    E = tvm.compute(shape, lambda i: A[i] + D[i], name = "E")
+
+    s = tvm.create_schedule(E.op)
+    s.cache_read(A, "local", [B, C, D, E])
+    bounds = tvm.schedule.InferBound(s)
+    assert isinstance(bounds, tvm.container.Map)
+
 
 if __name__ == "__main__":
     test_alloc_seq()
@@ -456,3 +469,4 @@ def verify(n):
     test_alloc_seq_type()
     test_alloc_seq_type2()
     test_reuse_small_buffer()
+    test_replace_dataflow()

From 16ac9345cf82bfb3854c7f68e0bb99c4dab007fc Mon Sep 17 00:00:00 2001
From: eqy <eqy@cs.washington.edu>
Date: Sat, 31 Mar 2018 10:18:11 -0700
Subject: [PATCH 239/949] preserve input option order (#1068)

---
 include/tvm/build_module.h     | 32 ++++++++++++++++----------------
 src/codegen/build_module.cc    | 33 ++++++++++++++++-----------------
 tests/cpp/build_module_test.cc |  3 +++
 3 files changed, 35 insertions(+), 33 deletions(-)

diff --git a/include/tvm/build_module.h b/include/tvm/build_module.h
index d2f21c9c87ce..80b1d59674cf 100644
--- a/include/tvm/build_module.h
+++ b/include/tvm/build_module.h
@@ -126,36 +126,36 @@ struct TargetContext {
 /*! \brief This namespace provides functions to construct Target instances */
 namespace target {
 /*! \return A target for LLVM */
-EXPORT Target llvm(const std::unordered_set<std::string>& options =
-                   std::unordered_set<std::string>());
+EXPORT Target llvm(const std::vector<std::string>& options =
+                   std::vector<std::string>());
 
 /*! \return A target for CUDA */
-EXPORT Target cuda(const std::unordered_set<std::string>& options =
-                   std::unordered_set<std::string>());
+EXPORT Target cuda(const std::vector<std::string>& options =
+                   std::vector<std::string>());
 
 /*! \return A target for ROCm */
-EXPORT Target rocm(const std::unordered_set<std::string>& options =
-                   std::unordered_set<std::string>());
+EXPORT Target rocm(const std::vector<std::string>& options =
+                   std::vector<std::string>());
 
 /*! \return A target for OpenCL */
-EXPORT Target opencl(const std::unordered_set<std::string>& options =
-                     std::unordered_set<std::string>());
+EXPORT Target opencl(const std::vector<std::string>& options =
+                     std::vector<std::string>());
 
 /*! \return A target for Metal */
-EXPORT Target metal(const std::unordered_set<std::string>& options =
-                    std::unordered_set<std::string>());
+EXPORT Target metal(const std::vector<std::string>& options =
+                    std::vector<std::string>());
 
 /*! \return A target for rasp */
-EXPORT Target rasp(const std::unordered_set<std::string>& options =
-                   std::unordered_set<std::string>());
+EXPORT Target rasp(const std::vector<std::string>& options =
+                   std::vector<std::string>());
 
 /*! \return A target for Mali */
-EXPORT Target mali(const std::unordered_set<std::string>& options =
-                   std::unordered_set<std::string>());
+EXPORT Target mali(const std::vector<std::string>& options =
+                   std::vector<std::string>());
 
 /*! \return A target for stackvm */
-EXPORT Target stackvm(const std::unordered_set<std::string>& options =
-                      std::unordered_set<std::string>());
+EXPORT Target stackvm(const std::vector<std::string>& options =
+                      std::vector<std::string>());
 
 }  // namespace target
 
diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc
index 210f3661372a..ecc949a8a44b 100644
--- a/src/codegen/build_module.cc
+++ b/src/codegen/build_module.cc
@@ -31,7 +31,7 @@ TVM_STATIC_IR_FUNCTOR(IRPrinter, vtable)
 * \return The constructed Target
 */
 Target CreateTarget(const std::string& target_name,
-                    const std::unordered_set<std::string>& options) {
+                    const std::vector<std::string>& options) {
   auto target = Target(std::make_shared<TargetNode>());
   auto t = static_cast<TargetNode*>(target.node_.get());
 
@@ -58,7 +58,6 @@ Target CreateTarget(const std::string& target_name,
   if (device_name.length() > 0) {
     t->keys_array.push_back(ir::StringImm::make(device_name));
   }
-
   t->device_type = kDLCPU;
   t->thread_warp_size = 1;
   if (target_name == "llvm") {
@@ -95,10 +94,10 @@ Target CreateTarget(const std::string& target_name,
 TVM_REGISTER_API("_TargetCreate")
 .set_body([](TVMArgs args, TVMRetValue* ret) {
   std::string target_name = args[0];
-  std::unordered_set<std::string> options;
+  std::vector<std::string> options;
   for (int i = 1; i < args.num_args; ++i) {
     std::string arg = args[i];
-    options.insert(arg);
+    options.push_back(arg);
   }
 
   *ret = CreateTarget(target_name, options);
@@ -175,10 +174,10 @@ Target Target::create(const std::string& target_str) {
   ss >> target_name;
   auto device_name = GetDeviceName(target_str);
 
-  std::unordered_set<std::string> options;
+  std::vector<std::string> options;
   std::string item;
   while (ss >> item) {
-    options.insert(item);
+    options.push_back(item);
   }
 
   if (device_name == "rasp") {
@@ -224,33 +223,33 @@ tvm::Target Target::current_target(bool allow_not_defined) {
 }
 
 namespace target {
-std::unordered_set<std::string> MergeOptions(std::unordered_set<std::string> opts,
-                                             const std::unordered_set<std::string>& new_opts) {
-  opts.insert(new_opts.begin(), new_opts.end());
+std::vector<std::string> MergeOptions(std::vector<std::string> opts,
+                                             const std::vector<std::string>& new_opts) {
+  opts.insert(opts.end(), new_opts.begin(), new_opts.end());
   return opts;
 }
 
-Target llvm(const std::unordered_set<std::string>& options) {
+Target llvm(const std::vector<std::string>& options) {
   return CreateTarget("llvm", options);
 }
 
-Target cuda(const std::unordered_set<std::string>& options) {
+Target cuda(const std::vector<std::string>& options) {
   return CreateTarget("cuda", options);
 }
 
-Target rocm(const std::unordered_set<std::string>& options) {
+Target rocm(const std::vector<std::string>& options) {
   return CreateTarget("rocm", options);
 }
 
-Target opencl(const std::unordered_set<std::string>& options) {
+Target opencl(const std::vector<std::string>& options) {
   return CreateTarget("opencl", options);
 }
 
-Target metal(const std::unordered_set<std::string>& options) {
+Target metal(const std::vector<std::string>& options) {
   return CreateTarget("metal", options);
 }
 
-Target rasp(const std::unordered_set<std::string>& options) {
+Target rasp(const std::vector<std::string>& options) {
   return CreateTarget("llvm", MergeOptions(options, {
     "-device=rasp",
     "-mtriple=armv7l-none-linux-gnueabihf",
@@ -259,14 +258,14 @@ Target rasp(const std::unordered_set<std::string>& options) {
   }));
 }
 
-Target mali(const std::unordered_set<std::string>& options) {
+Target mali(const std::vector<std::string>& options) {
   return CreateTarget("opencl", MergeOptions(options, {
     "-device=mali"
   }));
 }
 
 
-Target stackvm(const std::unordered_set<std::string>& options) {
+Target stackvm(const std::vector<std::string>& options) {
   return CreateTarget("stackvm", options);
 }
 }  // namespace target
diff --git a/tests/cpp/build_module_test.cc b/tests/cpp/build_module_test.cc
index a1aed5b00b96..aa6e81e860f7 100644
--- a/tests/cpp/build_module_test.cc
+++ b/tests/cpp/build_module_test.cc
@@ -32,6 +32,9 @@ TEST(BuildModule, Basic) {
 
   auto lowered = lower(s, args, "func", binds, config);
   auto module = build(lowered, target, Target(), config);
+
+  auto mali_target = Target::create("opencl -model=Mali-T860MP4@800Mhz -device=mali");
+  CHECK_EQ(mali_target->str(), "opencl -model=Mali-T860MP4@800Mhz -device=mali"); 
 }
 
 

From da7413fe3e33db9c4b59888efd6eadd66cc67cb2 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 31 Mar 2018 10:42:19 -0700
Subject: [PATCH 240/949] Add phisiart to code owner of webgl module (#1070)

---
 CODEOWNERS      | 6 +++++-
 CONTRIBUTORS.md | 1 +
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/CODEOWNERS b/CODEOWNERS
index 4af7a7e70c67..e010c93878f5 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -5,7 +5,7 @@
 *        @dmlc/tvm-committers
 
 # LLVM backends
-src/llvm/*          @aatluri
+src/codegen/llvm/*          @aatluri
 
 # ROCM runtime
 src/runtime/rocm/*    @aatluri
@@ -13,5 +13,9 @@ src/runtime/rocm/*    @aatluri
 # JVM language
 jvm/*   @yzhliu
 
+# WebGL backends
+src/runtime/opengl/*    @phisiart
+src/codegen/*opengl*    @phisiart
+
 # TOPI
 topi/python/topi/*  @Laurawly @Huyuwei
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 3254f5442f06..064edd39e46a 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -48,6 +48,7 @@ and are qualified to lead development and review changes of the owned module.
 - [Leyuan Wang](https://github.com/Laurawly) TOPI
 - [Yuwei Hu](https://github.com/Huyuwei) TOPI
 - [Yizhi Liu](https://github.com/yzhliu) JVM package
+- [Zhixun Tan](https://github.com/phisiart) OpenGL/WebGL backend
 
 
 List of Contributors

From 985e58b38b8c0ac4787392ecae73b9a9047b4701 Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Sun, 1 Apr 2018 06:48:05 +0800
Subject: [PATCH 241/949] support string option when create cuda/rocm/...
 target (#1071)

---
 python/tvm/target.py                      | 16 ++++++++--------
 tests/python/unittest/test_lang_target.py |  1 +
 2 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/python/tvm/target.py b/python/tvm/target.py
index 77fa50efe01c..869b3f10bce3 100644
--- a/python/tvm/target.py
+++ b/python/tvm/target.py
@@ -349,10 +349,10 @@ def cuda(options=None):
 
     Parameters
     ----------
-    options : list of str
+    options : str or list of str
         Additional options
     """
-    options = options if options else []
+    options = _merge_opts([], options)
     return _api_internal._TargetCreate("cuda", *options)
 
 
@@ -361,10 +361,10 @@ def rocm(options=None):
 
     Parameters
     ----------
-    options : list of str
+    options : str or list of str
         Additional options
     """
-    options = options if options else []
+    options = _merge_opts([], options)
     return _api_internal._TargetCreate("rocm", *options)
 
 
@@ -373,7 +373,7 @@ def rasp(options=None):
 
     Parameters
     ----------
-    options : list of str
+    options : str or list of str
         Additional options
     """
     opts = ["-device=rasp",
@@ -389,7 +389,7 @@ def mali(options=None):
 
     Parameters
     ----------
-    options : list of str
+    options : str or list of str
         Additional options
     """
     opts = ["-device=mali"]
@@ -402,10 +402,10 @@ def opengl(options=None):
 
     Parameters
     ----------
-    options : list of str
+    options : str or list of str
         Additional options
     """
-    options = options if options else []
+    options = _merge_opts([], options)
     return _api_internal._TargetCreate("opengl", *options)
 
 
diff --git a/tests/python/unittest/test_lang_target.py b/tests/python/unittest/test_lang_target.py
index 812da0fd2710..ab9c51394dfc 100644
--- a/tests/python/unittest/test_lang_target.py
+++ b/tests/python/unittest/test_lang_target.py
@@ -43,6 +43,7 @@ def test_target_string_parse():
     assert target.options == ['-libs=cublas,cudnn']
     assert target.keys == ['cuda', 'gpu']
     assert target.libs == ['cublas', 'cudnn']
+    assert str(target) == str(tvm.target.cuda("-libs=cublas,cudnn"))
 
 if __name__ == "__main__":
     test_target_dispatch()

From fdba6cc9bd3bec9ccd0592fa3900b7fe25d6cb97 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 31 Mar 2018 19:19:40 -0700
Subject: [PATCH 242/949] [TOPI] Fix x86 schedule for conv out_dtype (#1072)

---
 topi/python/topi/x86/conv2d.py            | 1 +
 topi/python/topi/x86/conv2d_avx_common.py | 1 +
 2 files changed, 2 insertions(+)

diff --git a/topi/python/topi/x86/conv2d.py b/topi/python/topi/x86/conv2d.py
index 1cbf2f2cadfd..beee95b13d48 100644
--- a/topi/python/topi/x86/conv2d.py
+++ b/topi/python/topi/x86/conv2d.py
@@ -65,6 +65,7 @@ def _get_schedule_conv(wkl):
 
 @conv2d.register("cpu")
 def _declaration_conv(data, kernel, stride, padding, layout, out_dtype):
+    out_dtype = data.dtype if out_dtype is None else out_dtype
     target = tvm.target.current_target(allow_none=False)
     wkl = _get_workload(data, kernel, stride, padding, out_dtype)
     if wkl in _WORKLOADS and 'avx' in str(target) and layout == 'NCHW':
diff --git a/topi/python/topi/x86/conv2d_avx_common.py b/topi/python/topi/x86/conv2d_avx_common.py
index f4c0e453e643..03c1021be409 100644
--- a/topi/python/topi/x86/conv2d_avx_common.py
+++ b/topi/python/topi/x86/conv2d_avx_common.py
@@ -12,6 +12,7 @@
 AVXConvCommonFwd = namedtuple('AVXConvCommonFwd', ['ic_bn', 'oc_bn', 'reg_n', 'unroll_kw'])
 
 def _declaration_conv(data, kernel, stride, padding, layout, out_dtype):
+    out_dtype = data.dtype if out_dtype is None else out_dtype
     assert layout == 'NCHW', "only support NCHW convolution for AVX"
     wkl = _get_workload(data, kernel, stride, padding, out_dtype)
     sch = _get_schedule(wkl)

From 228e9cc2373edf9a532f6bb96544d41de4d79e5e Mon Sep 17 00:00:00 2001
From: alex-weaver <awsweaver@gmail.com>
Date: Sun, 1 Apr 2018 18:42:54 +0100
Subject: [PATCH 243/949] Move BuildConfig context stack to C++ (#1025)

---
 include/tvm/build_module.h                | 66 ++++++++++++++++-
 python/tvm/build_module.py                | 79 +++++++++++---------
 python/tvm/tensor_intrin.py               |  4 +-
 src/codegen/build_module.cc               | 87 ++++++++++++++++++++++-
 tests/python/unittest/test_pass_unroll.py |  1 +
 5 files changed, 198 insertions(+), 39 deletions(-)

diff --git a/include/tvm/build_module.h b/include/tvm/build_module.h
index 80b1d59674cf..dc724cc49ceb 100644
--- a/include/tvm/build_module.h
+++ b/include/tvm/build_module.h
@@ -8,6 +8,7 @@
 
 #include <string>
 #include <vector>
+#include <utility>
 #include "./runtime/packed_func.h"
 #include "./schedule_pass.h"
 #include "./lowered_func.h"
@@ -203,6 +204,12 @@ class BuildConfigNode : public Node {
   /*! \brief Whether to partition const loop */
   bool partition_const_loop = false;
 
+  /*! \brief Whether to dump the IR of each pass (only when building from python) */
+  std::vector< std::pair<int, PackedFunc> > add_lower_pass;
+
+  /*! \brief Whether to dump the IR of each pass (only when building from python) */
+  bool dump_pass_ir = false;
+
   void VisitAttrs(AttrVisitor* v) final {
     v->Visit("data_alignment", &data_alignment);
     v->Visit("offset_factor", &offset_factor);
@@ -214,13 +221,70 @@ class BuildConfigNode : public Node {
     v->Visit("restricted_func", &restricted_func);
     v->Visit("detect_global_barrier", &detect_global_barrier);
     v->Visit("partition_const_loop", &partition_const_loop);
+    v->Visit("dump_pass_ir", &dump_pass_ir);
   }
 
   static constexpr const char* _type_key = "BuildConfig";
   TVM_DECLARE_NODE_TYPE_INFO(BuildConfigNode, Node);
 };
 
-TVM_DEFINE_NODE_REF(BuildConfig, BuildConfigNode);
+/*!
+* \brief Container for build configuration options
+*/
+class BuildConfig : public ::tvm::NodeRef {
+ public:
+  BuildConfig() {}
+  explicit BuildConfig(std::shared_ptr<::tvm::Node> n) : NodeRef(n) {}
+
+  const BuildConfigNode* operator->() const {
+    return static_cast<const BuildConfigNode*>(node_.get());
+  }
+
+  BuildConfigNode* operator->() {
+    return static_cast<BuildConfigNode*>(node_.get());
+  }
+
+  /*!
+   * \brief Push a new BuildConfig context onto the thread local stack.
+   * \param build_config The configuration to set as the current context.
+   */
+  EXPORT static void EnterBuildConfigScope(const tvm::BuildConfig& build_config);
+
+  /*!
+   * \brief Pop a build config off the thread local context stack, restoring the previous
+   * configuration as the current context.
+   */
+  EXPORT static void ExitBuildConfigScope();
+
+  /*!
+   * \brief Get the current BuildConfig context from thread local storage, or a default
+   * configuration if a BuildConfig scope has not been entered.
+   * \return The configuration that is the current context.
+   */
+  EXPORT static tvm::BuildConfig Current();
+
+  using ContainerType = BuildConfigNode;
+};
+
+/*!
+ * \brief RAII container to provide a scoped BuildConfig context. Pushes a configuration onto the
+ * context stack when constructed, and pops it when destructed.
+ */
+struct BuildConfigContext {
+  /*!
+   * \brief Enter a new BuildConfig context. The given BuildConfig becomes the new current
+   * context. When the BuildConfigContext is destructed, the previous context is restored.
+   * \param build_config The BuildConfig to set as the new current context.
+   */
+  explicit BuildConfigContext(const tvm::BuildConfig& build_config) {
+    BuildConfig::EnterBuildConfigScope(build_config);
+  }
+
+  /*! \brief Destructor. Pops the context off the thread local stack. */
+  ~BuildConfigContext() {
+    BuildConfig::ExitBuildConfigScope();
+  }
+};
 
 /*!
 * \brief Construct a BuildConfig containing a new BuildConfigNode
diff --git a/python/tvm/build_module.py b/python/tvm/build_module.py
index 0b86cde626af..6f2e2776a586 100755
--- a/python/tvm/build_module.py
+++ b/python/tvm/build_module.py
@@ -8,8 +8,8 @@
 import types
 
 from ._ffi.node import NodeBase, register_node
-from ._ffi.base import _RUNTIME_ONLY
 from . import api
+from . import _api_internal
 from . import tensor
 from . import schedule
 from . import expr
@@ -46,7 +46,8 @@ def dump(*args, **kwargs):
             retv = func(*args, **kwargs)
             if not isinstance(retv, (_stmt.Stmt, container.LoweredFunc, container.Array)):
                 return retv
-            pname = str(self._pass_id) + "_" + func.func_name + "_ir.cc"
+            fname = func.func_name if hasattr(func, 'func_name') else func.__name__
+            pname = str(self._pass_id) + "_" + fname + "_ir.cc"
             with open(pname, "a") as f:
                 out = retv.body if isinstance(retv, container.LoweredFunc) else retv
                 f.write(str(out))
@@ -70,20 +71,20 @@ def recover():
             self._recover_list.append(recover)
             vset[k] = self.decorate(v) if isinstance(v, types.FunctionType) else v
 
-    def decorate_custompass(self):
-        """ decorate add_lower_pass pass in BuildConfig"""
-        cfg = BuildConfig.current
-        self._old_custom_pass = cfg.add_lower_pass
-        custom_pass = cfg.add_lower_pass if cfg.add_lower_pass else []
-        pass_list = [(x[0], self.decorate(x[1])) for x in custom_pass]
-        BuildConfig.current.add_lower_pass = pass_list
+    def decorate_custompass(self, custom_pass):
+        """decorate given list of custom passes, and return decorated passes"""
+        custom_pass = custom_pass if custom_pass else []
+        pass_list = []
+        for idx, x in enumerate(custom_pass):
+            x[1].__name__ = "custom{}_phase{}".format(idx, x[0])
+            pass_list += [(x[0], self.decorate(x[1]))]
+        return pass_list
 
     def enter(self):
         """only decorate outermost nest"""
         if DumpIR.scope_level > 0:
             return
         self.decorate_irpass()
-        self.decorate_custompass()
         self._pass_id = 0
         DumpIR.scope_level += 1
 
@@ -95,7 +96,6 @@ def exit(self):
         for f in self._recover_list:
             f()
         schedule.ScheduleOps = self._old_sgpass
-        BuildConfig.current.add_lower_pass = self._old_custom_pass
         DumpIR.scope_level -= 1
 
 @register_node
@@ -113,7 +113,6 @@ class BuildConfig(NodeBase):
     is constructed. See _node_defaults for the fields.
     """
 
-    current = None
     _node_defaults = {
         "auto_unroll_max_step": 0,
         "auto_unroll_max_depth": 8,
@@ -124,8 +123,10 @@ class BuildConfig(NodeBase):
         "offset_factor": 0,
         "data_alignment": -1,
         "restricted_func": True,
-        "double_buffer_split_loop": 1
+        "double_buffer_split_loop": 1,
+        "dump_pass_ir": False
     }
+    _dump_ir = DumpIR()
 
     # pylint: disable=no-member
     def __init__(self, handle):
@@ -138,24 +139,28 @@ def __init__(self, handle):
         """
         super(BuildConfig, self).__init__(handle)
         self.handle = handle
-        self._old_scope = None
-        self._dump_ir = DumpIR()
-        self.dump_pass_ir = False
-        self.add_lower_pass = None
+
+    @property
+    def add_lower_pass(self):
+        size = _api_internal._BuildConfigGetAddLowerPassInfo(self)
+        result = []
+        for i in range(size):
+            phase = _api_internal._BuildConfigGetAddLowerPassInfo(self, i, True)
+            func = _api_internal._BuildConfigGetAddLowerPassInfo(self, i, False)
+            result += [(phase, func)]
+        return result
 
     def __enter__(self):
         # pylint: disable=protected-access
-        self._old_scope = BuildConfig.current
-        BuildConfig.current = self
-        if self.dump_pass_ir is True:
-            self._dump_ir.enter()
+        _api_internal._EnterBuildConfigScope(self)
+        if self.dump_pass_ir:
+            BuildConfig._dump_ir.enter()
         return self
 
     def __exit__(self, ptype, value, trace):
-        assert self._old_scope
-        if self.dump_pass_ir is True:
-            self._dump_ir.exit()
-        BuildConfig.current = self._old_scope
+        if self.dump_pass_ir:
+            BuildConfig._dump_ir.exit()
+        _api_internal._ExitBuildConfigScope()
 
     def __setattr__(self, name, value):
         if name in BuildConfig._node_defaults:
@@ -163,6 +168,9 @@ def __setattr__(self, name, value):
                 "'%s' object cannot set attribute '%s'" % (str(type(self)), name))
         return super(BuildConfig, self).__setattr__(name, value)
 
+def current_build_config():
+    return _api_internal._GetCurrentBuildConfig()
+
 def build_config(**kwargs):
     """Configure the build behavior by setting config variables.
 
@@ -221,14 +229,13 @@ def build_config(**kwargs):
                  for k, v in BuildConfig._node_defaults.items()}
     config = make.node("BuildConfig", **node_args)
 
-    for k in kwargs:
-        if not k in node_args:
-            setattr(config, k, kwargs[k])
-    return config
+    if "add_lower_pass" in kwargs:
+        add_lower_pass_args = []
+        for x in kwargs["add_lower_pass"]:
+            add_lower_pass_args += [x[0], x[1]]
+        _api_internal._BuildConfigSetAddLowerPass(config, *add_lower_pass_args)
 
-if not _RUNTIME_ONLY:
-    # BuildConfig is not available in tvm_runtime
-    BuildConfig.current = build_config()
+    return config
 
 def get_binds(args, binds=None):
     """Internal function to get binds and arg_list given arguments.
@@ -252,7 +259,7 @@ def get_binds(args, binds=None):
         The list of symbolic buffers of arguments.
     """
     binds = {} if binds is None else binds.copy()
-    cfg = BuildConfig.current
+    cfg = current_build_config()
     arg_list = []
     for x in args:
         if isinstance(x, tensor.Tensor):
@@ -309,8 +316,10 @@ def lower(sch,
        Then the Stmt before make api is returned.
     """
     binds, arg_list = get_binds(args, binds)
-    cfg = BuildConfig.current
+    cfg = current_build_config()
     add_lower_pass = cfg.add_lower_pass if cfg.add_lower_pass else []
+    if cfg.dump_pass_ir:
+        add_lower_pass = BuildConfig._dump_ir.decorate_custompass(add_lower_pass)
     lower_phase0 = [x[1] for x in add_lower_pass if x[0] == 0]
     lower_phase1 = [x[1] for x in add_lower_pass if x[0] == 1]
     lower_phase2 = [x[1] for x in add_lower_pass if x[0] == 2]
@@ -434,7 +443,7 @@ def build(sch,
                 "Direct host side access to device memory is detected in %s. "
                 "Did you forget to bind?" % func.name)
         if func.func_type == container.LoweredFunc.MixedFunc:
-            if BuildConfig.current.detect_global_barrier:
+            if current_build_config().detect_global_barrier:
                 func = ir_pass.ThreadSync(func, "global")
             func = ir_pass.ThreadSync(func, "shared")
             warp_size = target.thread_warp_size
diff --git a/python/tvm/tensor_intrin.py b/python/tvm/tensor_intrin.py
index e166f3706a7b..62f8c8897d10 100644
--- a/python/tvm/tensor_intrin.py
+++ b/python/tvm/tensor_intrin.py
@@ -6,7 +6,7 @@
 from . import stmt as _stmt
 from . import make as _make
 from . import tensor as _tensor
-from .build_module import BuildConfig
+from .build_module import current_build_config
 from ._ffi.node import NodeBase, register_node
 
 @register_node
@@ -74,7 +74,7 @@ def decl_tensor_intrin(op,
         if not isinstance(t.op, _tensor.PlaceholderOp):
             raise ValueError("Donot yet support composition op")
 
-    cfg = BuildConfig.current
+    cfg = current_build_config()
     for t in tensors:
         buf = (binds[t] if t in binds else
                _api.decl_buffer(t.shape, t.dtype, t.op.name,
diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc
index ecc949a8a44b..ceeb3c0b1110 100644
--- a/src/codegen/build_module.cc
+++ b/src/codegen/build_module.cc
@@ -468,6 +468,41 @@ BuildConfig build_config() {
   return BuildConfig(std::make_shared<BuildConfigNode>());
 }
 
+/*! \brief Entry to hold the BuildConfig context stack. */
+struct TVMBuildConfigThreadLocalEntry {
+  /*! \brief The default build config if the stack is empty */
+  tvm::BuildConfig default_config;
+
+  /*! \brief The current build config context */
+  std::stack<tvm::BuildConfig> context_stack;
+
+  TVMBuildConfigThreadLocalEntry() :
+    default_config(build_config()) {
+  }
+};
+
+/*! \brief Thread local store to hold the BuildConfig context stack. */
+typedef dmlc::ThreadLocalStore<TVMBuildConfigThreadLocalEntry> TVMBuildConfigThreadLocalStore;
+
+void BuildConfig::EnterBuildConfigScope(const tvm::BuildConfig& build_config) {
+  TVMBuildConfigThreadLocalEntry *entry = TVMBuildConfigThreadLocalStore::Get();
+  entry->context_stack.push(build_config);
+}
+
+void BuildConfig::ExitBuildConfigScope() {
+  TVMBuildConfigThreadLocalEntry *entry = TVMBuildConfigThreadLocalStore::Get();
+  entry->context_stack.pop();
+}
+
+tvm::BuildConfig BuildConfig::Current() {
+  TVMBuildConfigThreadLocalEntry *entry = TVMBuildConfigThreadLocalStore::Get();
+  if (entry->context_stack.size() > 0) {
+    return entry->context_stack.top();
+  }
+
+  return entry->default_config;
+}
+
 TVM_REGISTER_NODE_TYPE(BuildConfigNode);
 
 TVM_STATIC_IR_FUNCTOR(IRPrinter, vtable)
@@ -482,7 +517,8 @@ TVM_STATIC_IR_FUNCTOR(IRPrinter, vtable)
   p->stream << "unroll_explicit=" << op->unroll_explicit << ", ";
   p->stream << "restricted_func=" << op->restricted_func << ", ";
   p->stream << "detect_global_barrier=" << op->detect_global_barrier << ", ";
-  p->stream << "partition_const_loop=" << op->partition_const_loop;
+  p->stream << "partition_const_loop=" << op->partition_const_loop << ", ";
+  p->stream << "dump_pass_ir=" << op->dump_pass_ir;
   p->stream << ")";
 });
 
@@ -571,6 +607,55 @@ void GenericFunc::CallPacked(TVMArgs args, TVMRetValue* ret) const {
   func.CallPacked(args, ret);
 }
 
+TVM_REGISTER_API("_GetCurrentBuildConfig")
+.set_body([](TVMArgs args, TVMRetValue* ret) {
+  *ret = BuildConfig::Current();
+  });
+
+TVM_REGISTER_API("_EnterBuildConfigScope")
+.set_body([](TVMArgs args, TVMRetValue* ret) {
+  BuildConfig target = args[0];
+  BuildConfig::EnterBuildConfigScope(target);
+  });
+
+TVM_REGISTER_API("_ExitBuildConfigScope")
+.set_body([](TVMArgs args, TVMRetValue* ret) {
+  BuildConfig::ExitBuildConfigScope();
+  });
+
+TVM_REGISTER_API("_BuildConfigSetAddLowerPass")
+.set_body([](TVMArgs args, TVMRetValue* ret) {
+  BuildConfig cfg = args[0];
+  std::vector< std::pair<int, PackedFunc> > add_lower_pass;
+  CHECK_EQ(args.size() % 2, 1);
+  for (int i = 1; i < args.size(); i += 2) {
+    add_lower_pass.push_back(std::make_pair(
+      args[i].operator int(),
+      args[i + 1].operator tvm::runtime::PackedFunc()));
+  }
+  cfg->add_lower_pass = add_lower_pass;
+  });
+
+TVM_REGISTER_API("_BuildConfigGetAddLowerPassInfo")
+.set_body([](TVMArgs args, TVMRetValue* ret) {
+  // Return one of the following:
+  //  * Size of add_lower_pass if num_args == 1
+  //  * Phase index of pass if args are (config, index, true)
+  //  * Function of pass if args are (config, index, false)
+  BuildConfig cfg = args[0];
+  if (args.num_args == 1) {
+    *ret = static_cast<int64_t>(cfg->add_lower_pass.size());
+  } else {
+    int index = args[1];
+    bool get_phase = args[2];
+    auto item = cfg->add_lower_pass[index];
+    if (get_phase) {
+      *ret = item.first;
+    } else {
+      *ret = item.second;
+    }
+  }
+  });
 
 TVM_REGISTER_API("_GenericFuncCreate")
 .set_body([](TVMArgs args, TVMRetValue* ret) {
diff --git a/tests/python/unittest/test_pass_unroll.py b/tests/python/unittest/test_pass_unroll.py
index b158113e6216..b0a11b9a9f33 100644
--- a/tests/python/unittest/test_pass_unroll.py
+++ b/tests/python/unittest/test_pass_unroll.py
@@ -38,6 +38,7 @@ def run(s):
     file_list = os.listdir('./')
     cc_file = end_with('.cc')
     cc_file = filter(cc_file, file_list)
+    cc_file = [f for f in cc_file]
     assert len(cc_file) == 3
     for i in cc_file:
         os.remove(i)

From d38551c60920e49083a52961876ced2636c789ff Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Thu, 5 Apr 2018 05:31:24 +0800
Subject: [PATCH 244/949] mix fix (#1079)

---
 python/tvm/build_module.py  | 4 ++--
 python/tvm/contrib/cudnn.py | 8 ++++----
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/python/tvm/build_module.py b/python/tvm/build_module.py
index 6f2e2776a586..5666cf6e23b0 100755
--- a/python/tvm/build_module.py
+++ b/python/tvm/build_module.py
@@ -180,7 +180,7 @@ def build_config(**kwargs):
         Threshold of number of steps in the loop to be automatically unrolled.
         This takes inner loop count into consideration.
 
-    auto_unroll_max_depth: int, default=4
+    auto_unroll_max_depth: int, default=8
         The maximum nested level of loops that can be automatically unrolled.
 
     unroll_explicit: bool, default=True
@@ -214,7 +214,7 @@ def build_config(**kwargs):
         It it is bigger than one, the logic will do a split with factor equals the integer
         and unroll the inner loop. This allows the buffer fetching won't contain condition.
 
-    add_lower_pass: list of tuiple (phase, function(Stmt->Stmt)), default=None
+    add_lower_pass: list of tuple (phase, function(Stmt->Stmt)), default=None
         phase contains an integer on which optimization pass we apply the pass.
         Additional lowering passes to be applied before make_api.
 
diff --git a/python/tvm/contrib/cudnn.py b/python/tvm/contrib/cudnn.py
index 5200f3193079..8f6708b145ad 100644
--- a/python/tvm/contrib/cudnn.py
+++ b/python/tvm/contrib/cudnn.py
@@ -278,10 +278,10 @@ def conv2d_find_algo(tensor_format,
                 w_shape[1].value,
                 w_shape[2].value,
                 w_shape[3].value,
-                y_shape[0],
-                y_shape[1],
-                y_shape[2],
-                y_shape[3])
+                int(y_shape[0]),
+                int(y_shape[1]),
+                int(y_shape[2]),
+                int(y_shape[3]))
 
 
 def conv2d_forward(x,

From 9a649d32ca0f020fdd5aaf2e878a5cbdb80a6282 Mon Sep 17 00:00:00 2001
From: Yuanbo Ye <yeyuanbo@pinecone.net>
Date: Thu, 5 Apr 2018 05:32:10 +0800
Subject: [PATCH 245/949] [TOPI] fix bug of leaky_relu type issue (#1076)

Signed-off-by: Janboe Ye <yeyuanbo@xiaomi.com>
---
 topi/include/topi/nn.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/topi/include/topi/nn.h b/topi/include/topi/nn.h
index 07e12252a357..62f4e5dbdec0 100644
--- a/topi/include/topi/nn.h
+++ b/topi/include/topi/nn.h
@@ -78,7 +78,7 @@ inline tvm::Tensor leaky_relu(const tvm::Tensor& t,
     [&](const tvm::Array<tvm::Var>& i) {
       auto value = t(i);
       auto calpha = tvm::make_const(value.type(), alpha);
-      return tvm::select(value > 0, value, value * alpha);
+      return tvm::select(value > 0, value, value * calpha);
     },
     name,
     tag);

From 11fe532cb18819825cd0018aaee0c4f66c343191 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 4 Apr 2018 20:40:41 -0700
Subject: [PATCH 246/949] [RPC] Refactor, introduce tracker (#1080)

* [RPC] Refactor, introduce tracker

* [RPC] Change RPC hand shake convention, always get remote key.

* fix lint
---
 Makefile                                      |   2 +-
 apps/ios_rpc/tvmrpc/TVMRuntime.h              |   5 +-
 apps/ios_rpc/tvmrpc/TVMRuntime.mm             |   6 +-
 apps/ios_rpc/tvmrpc/ViewController.mm         |   3 +-
 .../tvm/rpc/ConnectProxyServerProcessor.java  |   3 +
 .../tvm/rpc/StandaloneServerProcessor.java    |   5 +
 .../src/test/scripts/test_rpc_proxy_server.py |   5 +-
 python/tvm/contrib/graph_runtime.py           |  10 +-
 python/tvm/contrib/rpc.py                     | 396 ------------------
 python/tvm/contrib/rpc/__init__.py            |  13 +
 python/tvm/contrib/rpc/base.py                | 136 ++++++
 python/tvm/contrib/rpc/client.py              | 254 +++++++++++
 .../contrib/{rpc_proxy.py => rpc/proxy.py}    | 153 +++----
 python/tvm/contrib/rpc/server.py              | 304 ++++++++++++++
 python/tvm/contrib/rpc/tornado_util.py        | 102 +++++
 python/tvm/contrib/rpc/tracker.py             | 331 +++++++++++++++
 python/tvm/exec/rpc_proxy.py                  |   2 +-
 python/tvm/exec/rpc_server.py                 |  23 +-
 python/tvm/exec/rpc_tracker.py                |  21 +
 src/runtime/rpc/rpc_event_impl.cc             |   9 +-
 src/runtime/rpc/rpc_module.cc                 |   2 +
 src/runtime/rpc/rpc_session.cc                |  47 ++-
 src/runtime/rpc/rpc_session.h                 |  11 +-
 src/runtime/rpc/rpc_socket_impl.cc            |  11 +-
 tests/python/contrib/test_rpc_proxy.py        |   6 +-
 tests/python/contrib/test_rpc_tracker.py      |  59 +++
 tests/python/unittest/test_runtime_rpc.py     |   5 +-
 web/tvm_runtime.js                            |  12 +-
 28 files changed, 1398 insertions(+), 538 deletions(-)
 delete mode 100644 python/tvm/contrib/rpc.py
 create mode 100644 python/tvm/contrib/rpc/__init__.py
 create mode 100644 python/tvm/contrib/rpc/base.py
 create mode 100644 python/tvm/contrib/rpc/client.py
 rename python/tvm/contrib/{rpc_proxy.py => rpc/proxy.py} (78%)
 create mode 100644 python/tvm/contrib/rpc/server.py
 create mode 100644 python/tvm/contrib/rpc/tornado_util.py
 create mode 100644 python/tvm/contrib/rpc/tracker.py
 create mode 100644 python/tvm/exec/rpc_tracker.py
 create mode 100644 tests/python/contrib/test_rpc_tracker.py

diff --git a/Makefile b/Makefile
index 1732ed5d9623..55db629f44d5 100644
--- a/Makefile
+++ b/Makefile
@@ -280,7 +280,7 @@ lib/libtvm_runtime.${SHARED_LIBRARY_SUFFIX}: $(RUNTIME_DEP)
 lib/libtvm_web_runtime.bc: web/web_runtime.cc
 	@mkdir -p build/web
 	@mkdir -p $(@D)
-	$(CXX) $(CFLAGS) -MM -MT lib/libtvm_web_runtime.bc $< >build/web/web_runtime.d
+	emcc $(EMCC_FLAGS) -MM -MT lib/libtvm_web_runtime.bc $< >build/web/web_runtime.d
 	emcc $(EMCC_FLAGS) -o $@ web/web_runtime.cc
 
 lib/libtvm_web_runtime.js: lib/libtvm_web_runtime.bc
diff --git a/apps/ios_rpc/tvmrpc/TVMRuntime.h b/apps/ios_rpc/tvmrpc/TVMRuntime.h
index a758f9454460..fec351e7b22b 100644
--- a/apps/ios_rpc/tvmrpc/TVMRuntime.h
+++ b/apps/ios_rpc/tvmrpc/TVMRuntime.h
@@ -31,9 +31,12 @@ using FEventHandler = std::function<int(const std::string& in_bytes, int event_f
  *
  * \param outputStream The output stream used to send outputs.
  * \param name The name of the server.
+ * \param remote_key The remote key
  * \return The event handler.
  */
-FEventHandler CreateServerEventHandler(NSOutputStream *outputStream, std::string name);
+FEventHandler CreateServerEventHandler(NSOutputStream *outputStream,
+                                       std::string name,
+                                       std::string remote_key);
 
 }  // namespace runtime
 }  // namespace tvm
diff --git a/apps/ios_rpc/tvmrpc/TVMRuntime.mm b/apps/ios_rpc/tvmrpc/TVMRuntime.mm
index 881c82ddb05e..23cf3e27900d 100644
--- a/apps/ios_rpc/tvmrpc/TVMRuntime.mm
+++ b/apps/ios_rpc/tvmrpc/TVMRuntime.mm
@@ -8,6 +8,7 @@
 #include "../../src/runtime/cpu_device_api.cc"
 #include "../../src/runtime/workspace_pool.cc"
 #include "../../src/runtime/thread_pool.cc"
+#include "../../src/runtime/threading_backend.cc"
 #include "../../src/runtime/module_util.cc"
 #include "../../src/runtime/system_lib_module.cc"
 #include "../../src/runtime/module.cc"
@@ -59,9 +60,10 @@ size_t Recv(void* data, size_t size) final {
   NSOutputStream* stream_;
 };
 
-FEventHandler CreateServerEventHandler(NSOutputStream *outputStream, std::string name) {
+FEventHandler CreateServerEventHandler(
+    NSOutputStream *outputStream, std::string name, std::string remote_key) {
   std::unique_ptr<NSStreamChannel> ch(new NSStreamChannel(outputStream));
-  std::shared_ptr<RPCSession> sess = RPCSession::Create(std::move(ch), name);
+  std::shared_ptr<RPCSession> sess = RPCSession::Create(std::move(ch), name, remote_key);
   return [sess](const std::string& in_bytes, int flag) {
     return sess->ServerEventHandler(in_bytes, flag);
   };
diff --git a/apps/ios_rpc/tvmrpc/ViewController.mm b/apps/ios_rpc/tvmrpc/ViewController.mm
index f25501809b01..98527bd67b50 100644
--- a/apps/ios_rpc/tvmrpc/ViewController.mm
+++ b/apps/ios_rpc/tvmrpc/ViewController.mm
@@ -143,7 +143,7 @@ - (void)open {
   [outputStream_ scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
   [outputStream_ open];
   [inputStream_ open];
-  handler_ = tvm::runtime::CreateServerEventHandler(outputStream_, key_);
+  handler_ = tvm::runtime::CreateServerEventHandler(outputStream_, key_, "%toinit");
   CHECK(handler_ != nullptr);
   self.infoText.text = @"";
   self.statusLabel.text = @"Connecting...";
@@ -169,7 +169,6 @@ - (IBAction)connect:(id)sender {
 }
 
 - (IBAction)disconnect:(id)sender {
-
   [self close];
 }
 
diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/ConnectProxyServerProcessor.java b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/ConnectProxyServerProcessor.java
index 8f634b0deff1..208efc773b02 100644
--- a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/ConnectProxyServerProcessor.java
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/ConnectProxyServerProcessor.java
@@ -74,6 +74,9 @@ public ConnectProxyServerProcessor(String host, int port, String key,
       } else if (magic != RPC.RPC_MAGIC) {
         throw new RuntimeException(address + " is not RPC Proxy");
       }
+      // Get key from remote
+      int keylen = Utils.wrapBytes(Utils.recvAll(in, 4)).getInt();
+      String remoteKey = Utils.decodeToStr(Utils.recvAll(in, keylen));
       System.err.println("RPCProxy connected to " + address);
 
       final int sockFd = socketFileDescriptorGetter.get(currSocket);
diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/StandaloneServerProcessor.java b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/StandaloneServerProcessor.java
index 94b40af6d0d8..06e3303d1523 100644
--- a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/StandaloneServerProcessor.java
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/StandaloneServerProcessor.java
@@ -60,7 +60,12 @@ public StandaloneServerProcessor(int serverPort,
         out.write(Utils.toBytes(RPC.RPC_MAGIC + 2));
       } else {
         out.write(Utils.toBytes(RPC.RPC_MAGIC));
+        // send server key to the client
+        String serverKey = "server:java";
+        out.write(Utils.toBytes(serverKey.length()));
+        out.write(Utils.toBytes(serverKey));
       }
+
       System.err.println("Connection from " + socket.getRemoteSocketAddress().toString());
       final int sockFd = socketFileDescriptorGetter.get(socket);
       if (sockFd != -1) {
diff --git a/jvm/core/src/test/scripts/test_rpc_proxy_server.py b/jvm/core/src/test/scripts/test_rpc_proxy_server.py
index 3f1f6466c715..9ad5a33f23dd 100644
--- a/jvm/core/src/test/scripts/test_rpc_proxy_server.py
+++ b/jvm/core/src/test/scripts/test_rpc_proxy_server.py
@@ -1,8 +1,8 @@
 import time
-from tvm.contrib import rpc_proxy
+from tvm.contrib.rpc import proxy
 
 def start_proxy_server(port, timeout):
-    prox = rpc_proxy.Proxy("localhost", port=port, port_end=port+1)
+    prox = proxy.Proxy("localhost", port=port, port_end=port+1)
     if timeout > 0:
         import time
         time.sleep(timeout)
@@ -17,4 +17,3 @@ def start_proxy_server(port, timeout):
     port = int(sys.argv[1])
     timeout = 0 if len(sys.argv) == 2 else float(sys.argv[2])
     start_proxy_server(port, timeout)
-
diff --git a/python/tvm/contrib/graph_runtime.py b/python/tvm/contrib/graph_runtime.py
index a303895ceb1d..3c1e2766aa47 100644
--- a/python/tvm/contrib/graph_runtime.py
+++ b/python/tvm/contrib/graph_runtime.py
@@ -1,7 +1,7 @@
 """Minimum graph runtime that executes graph containing TVM PackedFunc."""
-from . import rpc
 from .._ffi.base import string_types
 from .._ffi.function import get_global_func
+from .rpc import base as rpc_base
 from .. import ndarray as nd
 
 
@@ -33,12 +33,12 @@ def create(graph_json_str, libmod, ctx):
             raise ValueError("Type %s is not supported" % type(graph_json_str))
     device_type = ctx.device_type
     device_id = ctx.device_id
-    if device_type >= rpc.RPC_SESS_MASK:
+    if device_type >= rpc_base.RPC_SESS_MASK:
         assert libmod.type_key == "rpc"
-        assert rpc._SessTableIndex(libmod) == ctx._rpc_sess._tbl_index
-        hmod = rpc._ModuleHandle(libmod)
+        assert rpc_base._SessTableIndex(libmod) == ctx._rpc_sess._tbl_index
+        hmod = rpc_base._ModuleHandle(libmod)
         fcreate = ctx._rpc_sess.get_function("tvm.graph_runtime.remote_create")
-        device_type = device_type % rpc.RPC_SESS_MASK
+        device_type = device_type % rpc_base.RPC_SESS_MASK
         return GraphModule(fcreate(graph_json_str, hmod, device_type, device_id), ctx)
     fcreate = get_global_func("tvm.graph_runtime.create")
     return GraphModule(fcreate(graph_json_str, libmod, device_type, device_id), ctx)
diff --git a/python/tvm/contrib/rpc.py b/python/tvm/contrib/rpc.py
deleted file mode 100644
index 56f9fb1b4058..000000000000
--- a/python/tvm/contrib/rpc.py
+++ /dev/null
@@ -1,396 +0,0 @@
-"""RPC interface for easy testing.
-
-RPC enables connect to a remote server, upload and launch functions.
-This is useful to for cross-compile and remote testing,
-The compiler stack runs on local server, while we use RPC server
-to run on remote runtime which don't have a compiler available.
-
-The test program compiles the program on local server,
-upload and run remote RPC server, get the result back to verify correctness.
-"""
-from __future__ import absolute_import
-
-import os
-import socket
-import struct
-import logging
-import multiprocessing
-import subprocess
-import time
-from . import util, cc, tar
-from ..module import load as _load_module
-from .._ffi.function import _init_api, register_func
-from .._ffi.ndarray import context as _context
-from .._ffi.base import py_str
-
-RPC_MAGIC = 0xff271
-RPC_SESS_MASK = 128
-
-def _server_env():
-    """Server environment function return temp dir"""
-    temp = util.tempdir()
-    # pylint: disable=unused-variable
-    @register_func("tvm.contrib.rpc.server.workpath")
-    def get_workpath(path):
-        return temp.relpath(path)
-
-    @register_func("tvm.contrib.rpc.server.load_module", override=True)
-    def load_module(file_name):
-        """Load module from remote side."""
-        path = temp.relpath(file_name)
-        # Try create a shared library in remote
-        if path.endswith(".o"):
-            logging.info("Create shared library based on %s", path)
-            cc.create_shared(path + ".so", path)
-            path += ".so"
-        elif path.endswith(".tar"):
-            tar_temp = util.tempdir()
-            tar.untar(path, tar_temp.temp_dir)
-            files = [tar_temp.relpath(x) for x in tar_temp.listdir()]
-            cc.create_shared(path + ".so", files)
-            path += ".so"
-        m = _load_module(path)
-        logging.info("load_module %s", path)
-        return m
-    return temp
-
-
-def _serve_loop(sock, addr):
-    """Server loop"""
-    sockfd = sock.fileno()
-    temp = _server_env()
-    _ServerLoop(sockfd)
-    temp.remove()
-    logging.info("Finish serving %s", addr)
-
-
-def _recvall(sock, nbytes):
-    res = []
-    nread = 0
-    while nread < nbytes:
-        chunk = sock.recv(min(nbytes - nread, 1024))
-        nread += len(chunk)
-        res.append(chunk)
-    return b"".join(res)
-
-
-def _listen_loop(sock, exclusive):
-    """Lisenting loop"""
-    last_proc = None
-    while True:
-        conn, addr = sock.accept()
-
-        if last_proc and last_proc.is_alive() and exclusive:
-            logging.info("Kill last call")
-            last_proc.terminate()
-
-        logging.info("RPCServer: connection from %s", addr)
-        magic = struct.unpack("@i", _recvall(conn, 4))[0]
-        if magic != RPC_MAGIC:
-            conn.close()
-            continue
-        keylen = struct.unpack("@i", _recvall(conn, 4))[0]
-        key = py_str(_recvall(conn, keylen))
-        if not key.startswith("client:"):
-            conn.sendall(struct.pack("@i", RPC_MAGIC + 2))
-        else:
-            conn.sendall(struct.pack("@i", RPC_MAGIC))
-        logging.info("Connection from %s", addr)
-
-        process = multiprocessing.Process(target=_serve_loop, args=(conn, addr))
-        process.deamon = True
-        process.start()
-        last_proc = process
-        # close from our side.
-        conn.close()
-
-
-def _connect_proxy_loop(addr, key):
-    key = "server:" + key
-    while True:
-        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        sock.connect(addr)
-        sock.sendall(struct.pack("@i", RPC_MAGIC))
-        sock.sendall(struct.pack("@i", len(key)))
-        sock.sendall(key.encode("utf-8"))
-        magic = struct.unpack("@i", _recvall(sock, 4))[0]
-        if magic == RPC_MAGIC + 1:
-            raise RuntimeError("key: %s has already been used in proxy" % key)
-        elif magic == RPC_MAGIC + 2:
-            logging.info("RPCProxy do not have matching client key %s", key)
-        elif magic != RPC_MAGIC:
-            raise RuntimeError("%s is not RPC Proxy" % str(addr))
-        logging.info("RPCProxy connected to %s", str(addr))
-        process = multiprocessing.Process(target=_serve_loop, args=(sock, addr))
-        process.deamon = True
-        process.start()
-        process.join()
-
-
-def _popen(cmd):
-    proc = subprocess.Popen(cmd,
-                            stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
-                            env=os.environ)
-    (out, _) = proc.communicate()
-    if proc.returncode != 0:
-        msg = "Server invoke error:\n"
-        msg += out
-        raise RuntimeError(msg)
-
-
-class Server(object):
-    """Start RPC server on a seperate process.
-
-    This is a simple python implementation based on multi-processing.
-    It is also possible to implement a similar C based sever with
-    TVM runtime which does not depend on the python.
-
-    Parameters
-    ----------
-    host : str
-        The host url of the server.
-
-    port : int
-        The port to be bind to
-
-    port_end : int, optional
-        The end port to search
-
-    is_proxy : bool, optional
-        Whether the address specified is a proxy.
-        If this is true, the host and port actually corresponds to the
-        address of the proxy server.
-
-    use_popen : bool, optional
-        Whether to use Popen to start a fresh new process instead of fork.
-        This is recommended to switch on if we want to do local RPC demonstration
-        for GPU devices to avoid fork safety issues.
-
-    exclusive : bool, optional
-        If this is enabled, the server will kill old connection
-        when new connection comes. This can make sure the current call
-        monopolize the hardware resource.
-
-    key : str, optional
-        The key used to identify the server in Proxy connection.
-    """
-    def __init__(self,
-                 host,
-                 port=9091,
-                 port_end=9199,
-                 is_proxy=False,
-                 use_popen=False,
-                 exclusive=False,
-                 key=""):
-        try:
-            if _ServerLoop is None:
-                raise RuntimeError("Please compile with USE_RPC=1")
-        except NameError:
-            raise RuntimeError("Please compile with USE_RPC=1")
-        self.host = host
-        self.port = port
-        self.libs = []
-
-        if use_popen:
-            cmd = ["python",
-                   "-m", "tvm.exec.rpc_server",
-                   "--host=%s" % host,
-                   "--port=%s" % port]
-            self.proc = multiprocessing.Process(
-                target=subprocess.check_call, args=(cmd,))
-            self.proc.deamon = True
-            self.proc.start()
-            time.sleep(1)
-        elif not is_proxy:
-            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-            self.port = None
-            for my_port in range(port, port_end):
-                try:
-                    sock.bind((host, my_port))
-                    self.port = my_port
-                    break
-                except socket.error as sock_err:
-                    if sock_err.errno in [98, 48]:
-                        continue
-                    else:
-                        raise sock_err
-                if not self.port:
-                    raise ValueError("cannot bind to any port in [%d, %d)" % (port, port_end))
-            logging.info("RPCServer: bind to %s:%d", host, self.port)
-            sock.listen(1)
-            self.sock = sock
-            self.proc = multiprocessing.Process(
-                target=_listen_loop, args=(self.sock, exclusive))
-            self.proc.deamon = True
-            self.proc.start()
-        else:
-            self.proc = multiprocessing.Process(
-                target=_connect_proxy_loop, args=((host, port), key))
-            self.proc.deamon = True
-            self.proc.start()
-
-    def terminate(self):
-        """Terminate the server process"""
-        if self.proc:
-            self.proc.terminate()
-            self.proc = None
-
-    def __del__(self):
-        self.terminate()
-
-
-class RPCSession(object):
-    """RPC Client session module
-
-    Do not directly create the obhect, call connect
-    """
-    # pylint: disable=invalid-name
-    def __init__(self, sess):
-        self._sess = sess
-        self._tbl_index = _SessTableIndex(sess)
-        self._remote_funcs = {}
-
-    def get_function(self, name):
-        """Get function from the session.
-
-        Parameters
-        ----------
-        name : str
-            The name of the function
-
-        Returns
-        -------
-        f : Function
-            The result function.
-        """
-        return self._sess.get_function(name)
-
-    def context(self, dev_type, dev_id=0):
-        """Construct a remote context.
-
-        Parameters
-        ----------
-        dev_type: int or str
-
-        dev_id: int, optional
-
-        Returns
-        -------
-        ctx: TVMContext
-            The corresponding encoded remote context.
-        """
-        ctx = _context(dev_type, dev_id)
-        encode = (self._tbl_index + 1) * RPC_SESS_MASK
-        ctx.device_type += encode
-        ctx._rpc_sess = self
-        return ctx
-
-    def cpu(self, dev_id=0):
-        """Construct remote CPU device."""
-        return self.context(1, dev_id)
-
-    def gpu(self, dev_id=0):
-        """Construct remote GPU device."""
-        return self.context(2, dev_id)
-
-    def cl(self, dev_id=0):
-        """Construct remote OpenCL device."""
-        return self.context(4, dev_id)
-
-    def metal(self, dev_id=0):
-        """Construct remote Metal device."""
-        return self.context(8, dev_id)
-
-    def opengl(self, dev_id=0):
-        """Construct remote OpenGL device."""
-        return self.context(11, dev_id)
-
-    def ext_dev(self, dev_id=0):
-        """Construct remote extension device."""
-        return self.context(12, dev_id)
-
-    def upload(self, data, target=None):
-        """Upload file to remote runtime temp folder
-
-        Parameters
-        ----------
-        data : str or bytearray
-            The file name or binary in local to upload.
-
-        target : str, optional
-            The path in remote
-        """
-        if isinstance(data, bytearray):
-            if not target:
-                raise ValueError("target must present when file is a bytearray")
-            blob = data
-        else:
-            blob = bytearray(open(data, "rb").read())
-            if not target:
-                target = os.path.basename(data)
-
-        if "upload" not in self._remote_funcs:
-            self._remote_funcs["upload"] = self.get_function(
-                "tvm.contrib.rpc.server.upload")
-        self._remote_funcs["upload"](target, blob)
-
-    def download(self, path):
-        """Download file from remote temp folder.
-
-        Parameters
-        ----------
-        path : str
-            The relative location to remote temp folder.
-
-        Returns
-        -------
-        blob : bytearray
-            The result blob from the file.
-        """
-        if "download" not in self._remote_funcs:
-            self._remote_funcs["download"] = self.get_function(
-                "tvm.contrib.rpc.server.download")
-        return self._remote_funcs["download"](path)
-
-    def load_module(self, path):
-        """Load a remote module, the file need to be uploaded first.
-
-        Parameters
-        ----------
-        path : str
-            The relative location to remote temp folder.
-
-        Returns
-        -------
-        m : Module
-            The remote module containing remote function.
-        """
-        return _LoadRemoteModule(self._sess, path)
-
-
-def connect(url, port, key=""):
-    """Connect to RPC Server
-
-    Parameters
-    ----------
-    url : str
-        The url of the host
-
-    port : int
-        The port to connect to
-
-    key : str, optional
-        Additional key to match server
-
-    Returns
-    -------
-    sess : RPCSession
-        The connected session.
-    """
-    try:
-        sess = _Connect(url, port, key)
-    except NameError:
-        raise RuntimeError("Please compile with USE_RPC=1")
-    return RPCSession(sess)
-
-_init_api("tvm.contrib.rpc")
diff --git a/python/tvm/contrib/rpc/__init__.py b/python/tvm/contrib/rpc/__init__.py
new file mode 100644
index 000000000000..3335240ac5d5
--- /dev/null
+++ b/python/tvm/contrib/rpc/__init__.py
@@ -0,0 +1,13 @@
+"""Lightweight TVM RPC module.
+
+RPC enables connect to a remote server, upload and launch functions.
+This is useful to for cross-compile and remote testing,
+The compiler stack runs on local server, while we use RPC server
+to run on remote runtime which don't have a compiler available.
+
+The test program compiles the program on local server,
+upload and run remote RPC server, get the result back to verify correctness.
+"""
+
+from .server import Server
+from .client import RPCSession, connect, connect_tracker
diff --git a/python/tvm/contrib/rpc/base.py b/python/tvm/contrib/rpc/base.py
new file mode 100644
index 000000000000..9a39967efcf0
--- /dev/null
+++ b/python/tvm/contrib/rpc/base.py
@@ -0,0 +1,136 @@
+"""Base definitions for RPC."""
+from __future__ import absolute_import
+
+import socket
+import time
+import json
+import errno
+import struct
+import random
+import logging
+
+from ..._ffi.function import _init_api
+from ..._ffi.base import py_str
+
+# Magic header for RPC data plane
+RPC_MAGIC = 0xff271
+# magic header for RPC tracker(control plane)
+RPC_TRACKER_MAGIC = 0x2f271
+# sucess response
+RPC_CODE_SUCCESS = RPC_MAGIC + 0
+# duplicate key in proxy
+RPC_CODE_DUPLICATE = RPC_MAGIC + 1
+# cannot found matched key in server
+RPC_CODE_MISMATCH = RPC_MAGIC + 2
+
+
+class TrackerCode(object):
+    """Enumeration code for the RPC tracker"""
+    FAIL = -1
+    SUCCESS = 0
+    PING = 1
+    STOP = 2
+    PUT = 3
+    REQUEST = 4
+
+
+RPC_SESS_MASK = 128
+
+
+def recvall(sock, nbytes):
+    """Receive all nbytes from socket.
+
+    Parameters
+    ----------
+    sock: Socket
+       The socket
+
+    nbytes : int
+       Number of bytes to be received.
+    """
+    res = []
+    nread = 0
+    while nread < nbytes:
+        chunk = sock.recv(min(nbytes - nread, 1024))
+        if not chunk:
+            raise IOError("connection reset")
+        nread += len(chunk)
+        res.append(chunk)
+    return b"".join(res)
+
+
+def sendjson(sock, data):
+    """send a python value to remote via json
+
+    Parameters
+    ----------
+    sock : Socket
+        The socket
+
+    data : object
+        Python value to be sent.
+    """
+    data = json.dumps(data)
+    sock.sendall(struct.pack("@i", len(data)))
+    sock.sendall(data.encode("utf-8"))
+
+
+def recvjson(sock):
+    """receive python value from remote via json
+
+    Parameters
+    ----------
+    sock : Socket
+        The socket
+
+    Returns
+    -------
+    value : object
+        The value received.
+    """
+    size = struct.unpack("@i", recvall(sock, 4))[0]
+    data = json.loads(py_str(recvall(sock, size)))
+    return data
+
+
+def random_key():
+    """Generate a random key n"""
+    return str(random.random())
+
+
+def connect_with_retry(addr, timeout=60, retry_period=5):
+    """Connect to a TPC address with retry
+
+    This function is only reliable to short period of server restart.
+
+    Parameters
+    ----------
+    addr : tuple
+        address tuple
+
+    timeout : float
+         Timeout during retry
+
+    retry_period : float
+         Number of seconds before we retry again.
+    """
+    tstart = time.time()
+    while True:
+        try:
+            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            sock.connect(addr)
+            return sock
+        except socket.error as sock_err:
+            if sock_err.args[0] not in (errno.ECONNREFUSED,):
+                raise sock_err
+            period = time.time() - tstart
+            if period > timeout:
+                raise RuntimeError(
+                    "Failed to connect to server %s" % str(addr))
+            logging.info("Cannot connect to tracker%s, retry in %g secs...",
+                         str(addr), retry_period)
+            time.sleep(retry_period)
+
+
+# Still use tvm.contrib.rpc for the foreign functions
+_init_api("tvm.contrib.rpc", "tvm.contrib.rpc.base")
diff --git a/python/tvm/contrib/rpc/client.py b/python/tvm/contrib/rpc/client.py
new file mode 100644
index 000000000000..981b6aef793b
--- /dev/null
+++ b/python/tvm/contrib/rpc/client.py
@@ -0,0 +1,254 @@
+"""RPC client tools"""
+from __future__ import absolute_import
+
+import os
+import socket
+import struct
+
+from . import base
+from ..._ffi.base import TVMError
+from ..._ffi.ndarray import context as _context
+
+
+class RPCSession(object):
+    """RPC Client session module
+
+    Do not directly create the obhect, call connect
+    """
+    # pylint: disable=invalid-name
+    def __init__(self, sess):
+        self._sess = sess
+        self._tbl_index = base._SessTableIndex(sess)
+        self._remote_funcs = {}
+
+    def get_function(self, name):
+        """Get function from the session.
+
+        Parameters
+        ----------
+        name : str
+            The name of the function
+
+        Returns
+        -------
+        f : Function
+            The result function.
+        """
+        return self._sess.get_function(name)
+
+    def context(self, dev_type, dev_id=0):
+        """Construct a remote context.
+
+        Parameters
+        ----------
+        dev_type: int or str
+
+        dev_id: int, optional
+
+        Returns
+        -------
+        ctx: TVMContext
+            The corresponding encoded remote context.
+        """
+        ctx = _context(dev_type, dev_id)
+        encode = (self._tbl_index + 1) * base.RPC_SESS_MASK
+        ctx.device_type += encode
+        ctx._rpc_sess = self
+        return ctx
+
+    def cpu(self, dev_id=0):
+        """Construct remote CPU device."""
+        return self.context(1, dev_id)
+
+    def gpu(self, dev_id=0):
+        """Construct remote GPU device."""
+        return self.context(2, dev_id)
+
+    def cl(self, dev_id=0):
+        """Construct remote OpenCL device."""
+        return self.context(4, dev_id)
+
+    def metal(self, dev_id=0):
+        """Construct remote Metal device."""
+        return self.context(8, dev_id)
+
+    def opengl(self, dev_id=0):
+        """Construct remote OpenGL device."""
+        return self.context(11, dev_id)
+
+    def ext_dev(self, dev_id=0):
+        """Construct remote extension device."""
+        return self.context(12, dev_id)
+
+    def upload(self, data, target=None):
+        """Upload file to remote runtime temp folder
+
+        Parameters
+        ----------
+        data : str or bytearray
+            The file name or binary in local to upload.
+
+        target : str, optional
+            The path in remote
+        """
+        if isinstance(data, bytearray):
+            if not target:
+                raise ValueError("target must present when file is a bytearray")
+            blob = data
+        else:
+            blob = bytearray(open(data, "rb").read())
+            if not target:
+                target = os.path.basename(data)
+
+        if "upload" not in self._remote_funcs:
+            self._remote_funcs["upload"] = self.get_function(
+                "tvm.contrib.rpc.server.upload")
+        self._remote_funcs["upload"](target, blob)
+
+    def download(self, path):
+        """Download file from remote temp folder.
+
+        Parameters
+        ----------
+        path : str
+            The relative location to remote temp folder.
+
+        Returns
+        -------
+        blob : bytearray
+            The result blob from the file.
+        """
+        if "download" not in self._remote_funcs:
+            self._remote_funcs["download"] = self.get_function(
+                "tvm.contrib.rpc.server.download")
+        return self._remote_funcs["download"](path)
+
+    def load_module(self, path):
+        """Load a remote module, the file need to be uploaded first.
+
+        Parameters
+        ----------
+        path : str
+            The relative location to remote temp folder.
+
+        Returns
+        -------
+        m : Module
+            The remote module containing remote function.
+        """
+        return base._LoadRemoteModule(self._sess, path)
+
+
+class TrackerSession(object):
+    """Tracker client session.
+
+    Parameters
+    ----------
+    addr : tuple
+        The address tuple
+    """
+    def __init__(self, addr):
+        self._addr = addr
+        self._sock = None
+        self._max_request_retry = 5
+        self._connect()
+
+    def __del__(self):
+        self.close()
+
+    def _connect(self):
+        self._sock = base.connect_with_retry(self._addr)
+        self._sock.sendall(struct.pack("@i", base.RPC_TRACKER_MAGIC))
+        magic = struct.unpack("@i", base.recvall(self._sock, 4))[0]
+        if magic != base.RPC_TRACKER_MAGIC:
+            raise RuntimeError("%s is not RPC Tracker" % str(self._addr))
+
+    def close(self):
+        """Close the tracker connection."""
+        if self._sock:
+            self._sock.close()
+            self._sock = None
+
+    def request(self, key, priority=1, session_timeout=0):
+        """Request a new connection from the tracker.
+
+        Parameters
+        ----------
+        key : str
+            The type key of the device.
+
+        priority : int, optional
+            The priority of the request.
+
+        session_timeout : float, optional
+            The duration of the session, allows server to kill
+            the connection when duration is longer than this value.
+            When duration is zero, it means the request must always be kept alive.
+        """
+        for _ in range(self._max_request_retry):
+            try:
+                if self._sock is None:
+                    self._connect()
+                base.sendjson(self._sock,
+                              [base.TrackerCode.REQUEST, key, "", priority])
+                value = base.recvjson(self._sock)
+                if value[0] != base.TrackerCode.SUCCESS:
+                    raise RuntimeError("Invalid return value %s" % str(value))
+                url, port, matchkey = value[1]
+                return connect(url, port, key + matchkey, session_timeout)
+            except socket.error:
+                self.close()
+            except TVMError:
+                pass
+
+
+def connect(url, port, key="", session_timeout=0):
+    """Connect to RPC Server
+
+    Parameters
+    ----------
+    url : str
+        The url of the host
+
+    port : int
+        The port to connect to
+
+    key : str, optional
+        Additional key to match server
+
+    session_timeout : float, optional
+        The duration of the session, allows server to kill
+        the connection when duration is longer than this value.
+        When duration is zero, it means the request must always be kept alive.
+
+    Returns
+    -------
+    sess : RPCSession
+        The connected session.
+    """
+    try:
+        if session_timeout:
+            key += " -timeout=%s" % str(session_timeout)
+        sess = base._Connect(url, port, key)
+    except NameError:
+        raise RuntimeError("Please compile with USE_RPC=1")
+    return RPCSession(sess)
+
+
+def connect_tracker(url, port):
+    """Connect to a RPC tracker
+
+    Parameters
+    ----------
+    url : str
+        The url of the host
+
+    port : int
+        The port to connect to
+
+    Returns
+    -------
+    sess : TrackerSession
+        The connected tracker session.
+    """
+    return TrackerSession((url, port))
diff --git a/python/tvm/contrib/rpc_proxy.py b/python/tvm/contrib/rpc/proxy.py
similarity index 78%
rename from python/tvm/contrib/rpc_proxy.py
rename to python/tvm/contrib/rpc/proxy.py
index 5d67556ad1ce..a917ec4972bc 100644
--- a/python/tvm/contrib/rpc_proxy.py
+++ b/python/tvm/contrib/rpc/proxy.py
@@ -14,17 +14,20 @@
 import multiprocessing
 import errno
 import struct
+
 try:
     import tornado
     from tornado import gen
     from tornado import websocket
     from tornado import ioloop
-    from tornado import websocket
+    from . import tornado_util
 except ImportError as error_msg:
     raise ImportError("RPCProxy module requires tornado package %s" % error_msg)
-from . import rpc
-from .rpc import RPC_MAGIC, _server_env
-from .._ffi.base import py_str
+
+from . import base
+from .base import RPC_MAGIC, RPC_CODE_DUPLICATE, RPC_CODE_SUCCESS, RPC_CODE_MISMATCH
+from .server import _server_env
+from ..._ffi.base import py_str
 
 class ForwardHandler(object):
     """Forward handler to forward the message."""
@@ -102,102 +105,38 @@ def on_close_event(self):
         """on close event"""
         assert not self._done
         logging.info("RPCProxy:on_close %s ...", self.name())
-        self._done = True
-        self.forward_proxy = None
         if self.rpc_key:
-            key = self.rpc_key[6:]
+            key = self.rpc_key[7:]
             if ProxyServerHandler.current._client_pool.get(key, None) == self:
                 ProxyServerHandler.current._client_pool.pop(key)
             if ProxyServerHandler.current._server_pool.get(key, None) == self:
                 ProxyServerHandler.current._server_pool.pop(key)
+        self._done = True
+        self.forward_proxy = None
 
 
-class TCPHandler(ForwardHandler):
+class TCPHandler(tornado_util.TCPHandler, ForwardHandler):
     """Event driven TCP handler."""
     def __init__(self, sock, addr):
+        super(TCPHandler, self).__init__(sock)
         self._init_handler()
-        self.sock = sock
-        assert self.sock
         self.addr = addr
-        self.loop = ioloop.IOLoop.current()
-        self.sock.setblocking(0)
-        self.pending_write = []
-        self._signal_close = False
-        def event_handler(_, events):
-            self._on_event(events)
-        ioloop.IOLoop.current().add_handler(
-            self.sock.fileno(), event_handler, self.loop.READ | self.loop.ERROR)
 
     def name(self):
         return "TCPSocket: %s:%s"  % (str(self.addr), self.rpc_key)
 
     def send_data(self, message, binary=True):
-        assert binary
-        self.pending_write.append(message)
-        self._on_write()
-
-    def _on_write(self):
-        while self.pending_write:
-            try:
-                msg = self.pending_write[0]
-                nsend = self.sock.send(msg)
-                if nsend != len(msg):
-                    self.pending_write[0] = msg[nsend:]
-                else:
-                    del self.pending_write[0]
-            except socket.error as err:
-                if err.args[0] in (errno.EAGAIN, errno.EWOULDBLOCK):
-                    break
-                else:
-                    self.on_error(err)
-        if self.pending_write:
-            self.loop.update_handler(
-                self.sock.fileno(), self.loop.READ | self.loop.ERROR | self.loop.WRITE)
-        else:
-            if self._signal_close:
-                self.close()
-            else:
-                self.loop.update_handler(
-                    self.sock.fileno(), self.loop.READ | self.loop.ERROR)
+        self.write_message(message, True)
 
-    def _on_read(self):
-        try:
-            msg = bytes(self.sock.recv(4096))
-            if msg:
-                self.on_data(msg)
-                return True
-            else:
-                self.close_pair()
-        except socket.error as err:
-            if err.args[0] in (errno.EAGAIN, errno.EWOULDBLOCK):
-                pass
-            else:
-                self.on_error(e)
-        return False
-
-    def _on_event(self, events):
-        if (events & self.loop.ERROR) or (events & self.loop.READ):
-            if self._on_read() and (events & self.loop.WRITE):
-                self._on_write()
-        elif events & self.loop.WRITE:
-            self._on_write()
-
-    def signal_close(self):
-        if not self.pending_write:
-            self.close()
-        else:
-            self._signal_close = True
+    def on_message(self, message):
+        self.on_data(message)
 
-    def close(self):
-        if self.sock is not None:
-            logging.info("%s Close socket..", self.name())
-            try:
-                ioloop.IOLoop.current().remove_handler(self.sock.fileno())
-                self.sock.close()
-            except socket.error:
-                pass
-            self.sock = None
-            self.on_close_event()
+    def on_close(self):
+        if self.forward_proxy:
+            self.forward_proxy.signal_close()
+            self.forward_proxy = None
+        logging.info("%s Close socket..", self.name())
+        self.on_close_event()
 
 
 class WebSocketHandler(websocket.WebSocketHandler, ForwardHandler):
@@ -207,7 +146,7 @@ def __init__(self, *args, **kwargs):
         self._init_handler()
 
     def name(self):
-        return "WebSocketProxy"
+        return "WebSocketProxy: %s" % (self.rpc_key)
 
     def on_message(self, message):
         self.on_data(message)
@@ -234,12 +173,16 @@ def signal_close(self):
 class RequestHandler(tornado.web.RequestHandler):
     """Handles html request."""
     def __init__(self, *args, **kwargs):
-        self.page = open(kwargs.pop("file_path")).read()
-        web_port = kwargs.pop("rpc_web_port", None)
-        if web_port:
-            self.page = self.page.replace(
-                "ws://localhost:9190/ws",
-                "ws://localhost:%d/ws" % web_port)
+        file_path = kwargs.pop("file_path")
+        if file_path.endswith("html"):
+            self.page = open(file_path).read()
+            web_port = kwargs.pop("rpc_web_port", None)
+            if web_port:
+                self.page = self.page.replace(
+                    "ws://localhost:9190/ws",
+                    "ws://localhost:%d/ws" % web_port)
+        else:
+            self.page = open(file_path, "rb").read()
         super(RequestHandler, self).__init__(*args, **kwargs)
 
     def data_received(self, _):
@@ -303,14 +246,22 @@ def _on_event(self, _):
     def _pair_up(self, lhs, rhs):
         lhs.forward_proxy = rhs
         rhs.forward_proxy = lhs
-        lhs.send_data(struct.pack('@i', RPC_MAGIC))
-        rhs.send_data(struct.pack('@i', RPC_MAGIC))
+
+        lhs.send_data(struct.pack('@i', RPC_CODE_SUCCESS))
+        lhs.send_data(struct.pack('@i', len(rhs.rpc_key)))
+        lhs.send_data(rhs.rpc_key.encode("utf-8"))
+
+        rhs.send_data(struct.pack('@i', RPC_CODE_SUCCESS))
+        rhs.send_data(struct.pack('@i', len(lhs.rpc_key)))
+        rhs.send_data(lhs.rpc_key.encode("utf-8"))
+
         logging.info("Pairup connect %s  and %s", lhs.name(), rhs.name())
 
     def handler_ready(self, handler):
         """Report handler to be ready."""
         logging.info("Handler ready %s", handler.name())
-        key = handler.rpc_key[6:]
+        key = handler.rpc_key[7:]
+
         if handler.rpc_key.startswith("server:"):
             pool_src, pool_dst = self._client_pool, self._server_pool
             timeout = self.timeout_server
@@ -329,18 +280,19 @@ def cleanup():
                     logging.info("Timeout client connection %s, cannot find match key=%s",
                                  handler.name(), key)
                     pool_dst.pop(key)
-                    handler.send_data(struct.pack('@i', RPC_MAGIC + 2))
+                    handler.send_data(struct.pack('@i', RPC_CODE_MISMATCH))
                     handler.signal_close()
             self.loop.call_later(timeout, cleanup)
         else:
             logging.info("Duplicate connection with same key=%s", key)
-            handler.send_data(struct.pack('@i', RPC_MAGIC + 1))
+            handler.send_data(struct.pack('@i', RPC_CODE_DUPLICATE))
             handler.signal_close()
 
     def run(self):
         """Run the proxy server"""
         ioloop.IOLoop.current().start()
 
+
 def _proxy_server(listen_sock,
                   web_port,
                   timeout_client,
@@ -446,7 +398,8 @@ def _fsend(data):
             data = bytes(data)
             conn.write_message(data, binary=True)
             return len(data)
-        on_message = rpc._CreateEventDrivenServer(_fsend, "WebSocketProxyServer")
+        on_message = base._CreateEventDrivenServer(
+            _fsend, "WebSocketProxyServer", "%toinit")
         return on_message
 
     @gen.coroutine
@@ -462,14 +415,16 @@ def _connect(key):
         msg = yield conn.read_message()
         assert len(msg) >= 4
         magic = struct.unpack('@i', msg[:4])[0]
-        if magic == RPC_MAGIC + 1:
+        if magic == RPC_CODE_DUPLICATE:
             raise RuntimeError("key: %s has already been used in proxy" % key)
-        elif magic == RPC_MAGIC + 2:
+        elif magic == RPC_CODE_MISMATCH:
             logging.info("RPCProxy do not have matching client key %s", key)
-        elif magic != RPC_MAGIC:
+        elif magic != RPC_CODE_SUCCESS:
             raise RuntimeError("%s is not RPC Proxy" % url)
-        logging.info("Connection established")
         msg = msg[4:]
+
+        logging.info("Connection established with remote")
+
         if msg:
             on_message(bytearray(msg), 3)
 
diff --git a/python/tvm/contrib/rpc/server.py b/python/tvm/contrib/rpc/server.py
new file mode 100644
index 000000000000..f066e01bad6c
--- /dev/null
+++ b/python/tvm/contrib/rpc/server.py
@@ -0,0 +1,304 @@
+"""RPC server implementation.
+
+Note
+----
+Server is TCP based with the following protocol:
+- Initial handshake to the peer
+  - [RPC_MAGIC, keysize(int32), key-bytes]
+- The key is in format
+   - {server|client}:device-type[:matchkey] [-timeout=timeout]
+"""
+from __future__ import absolute_import
+
+import os
+import socket
+import select
+import struct
+import logging
+import multiprocessing
+import subprocess
+import time
+
+from ..._ffi.function import register_func
+from ..._ffi.base import py_str
+from ...module import load as _load_module
+from .. import util, cc, tar
+from . import base
+from . base import TrackerCode
+
+def _server_env():
+    """Server environment function return temp dir"""
+    temp = util.tempdir()
+    # pylint: disable=unused-variable
+    @register_func("tvm.contrib.rpc.server.workpath")
+    def get_workpath(path):
+        return temp.relpath(path)
+
+    @register_func("tvm.contrib.rpc.server.load_module", override=True)
+    def load_module(file_name):
+        """Load module from remote side."""
+        path = temp.relpath(file_name)
+        # Try create a shared library in remote
+        if path.endswith(".o"):
+            logging.info("Create shared library based on %s", path)
+            cc.create_shared(path + ".so", path)
+            path += ".so"
+        elif path.endswith(".tar"):
+            tar_temp = util.tempdir()
+            tar.untar(path, tar_temp.temp_dir)
+            files = [tar_temp.relpath(x) for x in tar_temp.listdir()]
+            cc.create_shared(path + ".so", files)
+            path += ".so"
+        m = _load_module(path)
+        logging.info("load_module %s", path)
+        return m
+    return temp
+
+
+def _serve_loop(sock, addr):
+    """Server loop"""
+    sockfd = sock.fileno()
+    temp = _server_env()
+    base._ServerLoop(sockfd)
+    temp.remove()
+    logging.info("Finish serving %s", addr)
+
+
+def _parse_server_opt(opts):
+    # parse client options
+    ret = {}
+    for kv in opts:
+        if kv.startswith("-timeout="):
+            ret["timeout"] = float(kv[9:])
+    return ret
+
+
+def _listen_loop(sock, port, rpc_key, tracker_addr):
+    """Lisenting loop of the server master."""
+    def _accept_conn(listen_sock, tracker_conn, ping_period=0.1):
+        """Accept connection from the other places.
+
+        Parameters
+        ----------
+        listen_sock: Socket
+            The socket used by listening process.
+
+        tracker_conn : connnection to tracker
+            Tracker connection
+
+        ping_period : float, optional
+            ping tracker every k seconds if no connection is accepted.
+        """
+        # Report resource to tracker
+        if tracker_conn:
+            matchkey = ":" + base.random_key()
+            base.sendjson(tracker_conn,
+                          [TrackerCode.PUT, rpc_key, (port, matchkey)])
+            assert base.recvjson(tracker_conn) == TrackerCode.SUCCESS
+        else:
+            matchkey = ""
+
+        # Wait until we get a valid connection
+        while True:
+            if tracker_conn:
+                trigger = select.select([listen_sock], [], [], ping_period)
+                if not listen_sock in trigger[0]:
+                    base.sendjson(tracker_conn, [TrackerCode.PING])
+                    assert base.recvjson(tracker_conn) == TrackerCode.SUCCESS
+                    continue
+            conn, addr = listen_sock.accept()
+            magic = struct.unpack("@i", base.recvall(conn, 4))[0]
+            if magic != base.RPC_MAGIC:
+                conn.close()
+                continue
+            keylen = struct.unpack("@i", base.recvall(conn, 4))[0]
+            key = py_str(base.recvall(conn, keylen))
+            arr = key.split()
+            expect_header = "client:" + rpc_key + matchkey
+            server_key = "server:" + rpc_key
+            if arr[0] != expect_header:
+                conn.sendall(struct.pack("@i", base.RPC_CODE_MISMATCH))
+                conn.close()
+                logging.info("RPCServer: mismatch key from %s", addr)
+                continue
+            else:
+                conn.sendall(struct.pack("@i", base.RPC_CODE_SUCCESS))
+                conn.sendall(struct.pack("@i", len(server_key)))
+                conn.sendall(server_key.encode("utf-8"))
+                return conn, addr, _parse_server_opt(arr[1:])
+
+    # Server logic
+    tracker_conn = None
+    while True:
+        # step 1: setup tracker and report to tracker
+        if tracker_addr and tracker_conn is None:
+            tracker_conn = base.connect_with_retry(tracker_addr)
+            tracker_conn.sendall(struct.pack("@i", base.RPC_TRACKER_MAGIC))
+            magic = struct.unpack("@i", base.recvall(tracker_conn, 4))[0]
+            if magic != base.RPC_TRACKER_MAGIC:
+                raise RuntimeError("%s is not RPC Tracker" % str(tracker_addr))
+        try:
+            # step 2: wait for in-coming connections
+            conn, addr, opts = _accept_conn(sock, tracker_conn)
+        except (socket.error, IOError):
+            # retry when tracker is dropped
+            tracker_conn.close()
+            tracker_conn = None
+            continue
+
+        # step 3: serving
+        logging.info("RPCServer: connection from %s", addr)
+        server_proc = multiprocessing.Process(target=_serve_loop, args=(conn, addr))
+        server_proc.deamon = True
+        server_proc.start()
+        # close from our side.
+        conn.close()
+        # wait until server process finish or timeout
+        server_proc.join(opts.get("timeout", None))
+        if server_proc.is_alive():
+            logging.info("Timeout in RPC session, kill..")
+            server_proc.terminate()
+
+
+def _connect_proxy_loop(addr, key):
+    key = "server:" + key
+    while True:
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        sock.connect(addr)
+        sock.sendall(struct.pack("@i", base.RPC_MAGIC))
+        sock.sendall(struct.pack("@i", len(key)))
+        sock.sendall(key.encode("utf-8"))
+        magic = struct.unpack("@i", base.recvall(sock, 4))[0]
+        if magic == base.RPC_CODE_DUPLICATE:
+            raise RuntimeError("key: %s has already been used in proxy" % key)
+        elif magic == base.RPC_CODE_MISMATCH:
+            logging.info("RPCProxy do not have matching client key %s", key)
+        elif magic != base.RPC_CODE_SUCCESS:
+            raise RuntimeError("%s is not RPC Proxy" % str(addr))
+        keylen = struct.unpack("@i", base.recvall(sock, 4))[0]
+        remote_key = py_str(base.recvall(sock, keylen))
+        opts = _parse_server_opt(remote_key.split()[1:])
+
+        logging.info("RPCProxy connected to %s", str(addr))
+        process = multiprocessing.Process(
+            target=_serve_loop, args=(sock, addr))
+        process.deamon = True
+        process.start()
+        sock.close()
+        process.join(opts.get("timeout", None))
+        if process.is_alive():
+            logging.info("Timeout in RPC session, kill..")
+            process.terminate()
+
+
+def _popen(cmd):
+    proc = subprocess.Popen(cmd,
+                            stdout=subprocess.PIPE,
+                            stderr=subprocess.STDOUT,
+                            env=os.environ)
+    (out, _) = proc.communicate()
+    if proc.returncode != 0:
+        msg = "Server invoke error:\n"
+        msg += out
+        raise RuntimeError(msg)
+
+
+class Server(object):
+    """Start RPC server on a seperate process.
+
+    This is a simple python implementation based on multi-processing.
+    It is also possible to implement a similar C based sever with
+    TVM runtime which does not depend on the python.
+
+    Parameters
+    ----------
+    host : str
+        The host url of the server.
+
+    port : int
+        The port to be bind to
+
+    port_end : int, optional
+        The end port to search
+
+    is_proxy : bool, optional
+        Whether the address specified is a proxy.
+        If this is true, the host and port actually corresponds to the
+        address of the proxy server.
+
+    use_popen : bool, optional
+        Whether to use Popen to start a fresh new process instead of fork.
+        This is recommended to switch on if we want to do local RPC demonstration
+        for GPU devices to avoid fork safety issues.
+
+    key : str, optional
+        The key used to identify the server in Proxy connection.
+    """
+    def __init__(self,
+                 host,
+                 port=9091,
+                 port_end=9199,
+                 is_proxy=False,
+                 use_popen=False,
+                 tracker_addr=None,
+                 key=""):
+        try:
+            if base._ServerLoop is None:
+                raise RuntimeError("Please compile with USE_RPC=1")
+        except NameError:
+            raise RuntimeError("Please compile with USE_RPC=1")
+        self.host = host
+        self.port = port
+        self.libs = []
+
+        if use_popen:
+            cmd = ["python",
+                   "-m", "tvm.exec.rpc_server",
+                   "--host=%s" % host,
+                   "--port=%s" % port]
+            if tracker_addr:
+                assert key
+                cmd += ["--tracker=%s:%d" % tracker_addr,
+                        "--key=%s" % key]
+            self.proc = multiprocessing.Process(
+                target=subprocess.check_call, args=(cmd,))
+            self.proc.deamon = True
+            self.proc.start()
+            time.sleep(1)
+        elif not is_proxy:
+            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            self.port = None
+            for my_port in range(port, port_end):
+                try:
+                    sock.bind((host, my_port))
+                    self.port = my_port
+                    break
+                except socket.error as sock_err:
+                    if sock_err.errno in [98, 48]:
+                        continue
+                    else:
+                        raise sock_err
+                if not self.port:
+                    raise ValueError("cannot bind to any port in [%d, %d)" % (port, port_end))
+            logging.info("RPCServer: bind to %s:%d", host, self.port)
+            sock.listen(1)
+            self.sock = sock
+            self.proc = multiprocessing.Process(
+                target=_listen_loop, args=(
+                    self.sock, self.port, key, tracker_addr))
+            self.proc.deamon = True
+            self.proc.start()
+        else:
+            self.proc = multiprocessing.Process(
+                target=_connect_proxy_loop, args=((host, port), key))
+            self.proc.deamon = True
+            self.proc.start()
+
+    def terminate(self):
+        """Terminate the server process"""
+        if self.proc:
+            self.proc.terminate()
+            self.proc = None
+
+    def __del__(self):
+        self.terminate()
diff --git a/python/tvm/contrib/rpc/tornado_util.py b/python/tvm/contrib/rpc/tornado_util.py
new file mode 100644
index 000000000000..f2dbb4aadb26
--- /dev/null
+++ b/python/tvm/contrib/rpc/tornado_util.py
@@ -0,0 +1,102 @@
+"""Utilities used in tornado."""
+
+import socket
+import errno
+from tornado import ioloop
+
+class TCPHandler(object):
+    """TCP socket handler backed tornado event loop.
+
+    Parameters
+    ----------
+    sock : Socket
+        The TCP socket, will set it to non-blocking mode.
+    """
+    def __init__(self, sock):
+        self._sock = sock
+        self._ioloop = ioloop.IOLoop.current()
+        self._sock.setblocking(0)
+        self._pending_write = []
+        self._signal_close = False
+        def _event_handler(_, events):
+            self._event_handler(events)
+        self._ioloop.add_handler(
+            self._sock.fileno(), _event_handler,
+            self._ioloop.READ | self._ioloop.ERROR)
+
+    def signal_close(self):
+        """Signal the handler to close.
+
+        The handler will be closed after the existing
+        pending message are sent to the peer.
+        """
+        if not self._pending_write:
+            self.close()
+        else:
+            self._signal_close = True
+
+    def close(self):
+        """Close the socket"""
+        if self._sock is not None:
+            try:
+                self._ioloop.remove_handler(self._sock.fileno())
+                self._sock.close()
+            except socket.error:
+                pass
+            self._sock = None
+            self.on_close()
+
+    def write_message(self, message, binary=True):
+        assert binary
+        self._pending_write.append(message)
+        self._update_write()
+
+    def _event_handler(self, events):
+        """centeral event handler"""
+        if (events & self._ioloop.ERROR) or (events & self._ioloop.READ):
+            if self._update_read() and (events & self._ioloop.WRITE):
+                self._update_write()
+        elif events & self._ioloop.WRITE:
+            self._update_write()
+
+    def _update_write(self):
+        """Update the state on write"""
+        while self._pending_write:
+            try:
+                msg = self._pending_write[0]
+                nsend = self._sock.send(msg)
+                if nsend != len(msg):
+                    self._pending_write[0] = msg[nsend:]
+                else:
+                    self._pending_write.pop(0)
+            except socket.error as err:
+                if err.args[0] in (errno.EAGAIN, errno.EWOULDBLOCK):
+                    break
+                else:
+                    self.on_error(err)
+        if self._pending_write:
+            self._ioloop.update_handler(
+                self._sock.fileno(), self._ioloop.READ | self._ioloop.ERROR | self._ioloop.WRITE)
+        else:
+            if self._signal_close:
+                self.close()
+            else:
+                self._ioloop.update_handler(
+                    self._sock.fileno(), self._ioloop.READ | self._ioloop.ERROR)
+
+    def _update_read(self):
+        """Update state when there is read event"""
+        try:
+            msg = bytes(self._sock.recv(4096))
+            if msg:
+                self.on_message(msg)
+                return True
+            else:
+                # normal close, remote is closed
+                self.close()
+        except socket.error as err:
+            if err.args[0] in (errno.EAGAIN, errno.EWOULDBLOCK):
+                pass
+            else:
+                self.on_error(err)
+        return False
diff --git a/python/tvm/contrib/rpc/tracker.py b/python/tvm/contrib/rpc/tracker.py
new file mode 100644
index 000000000000..6718ee5e7b5a
--- /dev/null
+++ b/python/tvm/contrib/rpc/tracker.py
@@ -0,0 +1,331 @@
+"""RPC Tracker, tracks and distributes the TVM RPC resources.
+
+This folder implemements the tracker server logic.
+
+Note
+----
+Tracker is a TCP based rest api with the following protocol:
+- Initial handshake to the peer
+  - RPC_TRACKER_MAGIC
+- Normal message: [size(int32), json-data]
+- Each message is initiated by the client, and the tracker replies with a json.
+
+List of available APIs:
+
+- PING: check if tracker is alive
+  - input: [TrackerCode.PING]
+  - return: TrackerCode.SUCCESS
+- PUT: report resource to tracker
+  - input: [TrackerCode.PUT, [port, match-key]]
+  - return: TrackerCode.SUCCESS
+  - note: match-key is a randomly generated identify the resource during connection.
+- REQUEST: request a new resource from tracker
+  - input: [TrackerCode.REQUEST, [key, user, priority]]
+  - return: [TrackerCode.SUCCESS, [url, port, match-key]]
+"""
+import heapq
+import time
+import logging
+import socket
+import multiprocessing
+import errno
+import struct
+import json
+
+try:
+    from tornado import ioloop
+    from . import tornado_util
+except ImportError as error_msg:
+    raise ImportError(
+        "RPCTracker module requires tornado package %s" % error_msg)
+
+from ..._ffi.base import py_str
+from . import base
+from .base import RPC_TRACKER_MAGIC, TrackerCode
+
+
+class Scheduler(object):
+    """Abstratc interface of scheduler."""
+    def put(self, value):
+        """Push a resource into the scheduler.
+
+        This function can trigger callbacks in the scheduler.
+
+        Parameters
+        ----------
+        value : object
+            The resource to be put in the scheduler.
+        """
+        raise NotImplementedError()
+
+    def request(self, user, priority, callback):
+        """Request a resource.
+
+        Parameters
+        ----------
+        user : str
+            The user who is requesting the resource.
+
+        priority : int
+            The job priority
+
+        callback : function: value->bool
+            Callback function to receive an resource when ready
+            returns True if the resource is consumed.
+        """
+        raise NotImplementedError()
+
+
+class PriorityScheduler(Scheduler):
+    """Priority based scheduler, FIFO based on time"""
+    def __init__(self):
+        self._values = []
+        self._requests = []
+
+    def _schedule(self):
+        while self._requests and self._values:
+            value = self._values.pop(0)
+            item = heapq.heappop(self._requests)
+            callback = item[-1]
+            if not callback(value):
+                self._values.append(value)
+
+    def put(self, value):
+        self._values.append(value)
+        self._schedule()
+
+    def request(self, user, priority, callback):
+        heapq.heappush(self._requests, (-priority, time.time(), callback))
+        self._schedule()
+
+
+class TCPEventHandler(tornado_util.TCPHandler):
+    """Base asynchronize message handler.
+
+    The tracker and client follows a simple message protocol.
+    The message is in form [nbytes(int32)] [json-str].
+    All the information is packed in json-str
+    """
+    def __init__(self, tracker, sock, addr):
+        super(TCPEventHandler, self).__init__(sock)
+        self._data = bytearray()
+        self._tracker = tracker
+        self._msg_size = 0
+        self._addr = addr
+        self._init_req_nbytes = 4
+        self._tracker._connections.add(self)
+
+    def name(self):
+        """name of connection"""
+        return "TCPSocket: %s" % str(self._addr)
+
+    def _init_conn(self, message):
+        """Initialie the connection"""
+        if len(message) != 4:
+            logging.info("Invalid connection from %s", self.name())
+            self.close()
+        magic = struct.unpack('@i', message)[0]
+        if magic != RPC_TRACKER_MAGIC:
+            logging.info("Invalid magic from %s", self.name())
+            self.close()
+        self.write_message(struct.pack('@i', RPC_TRACKER_MAGIC), binary=True)
+        self._init_req_nbytes = 0
+
+    def on_message(self, message):
+        """Callback when a message is received.
+
+        Parameters
+        ----------
+        message : bytearray
+            The bytes received
+        """
+        assert isinstance(message, bytes)
+        if self._init_req_nbytes:
+            self._init_conn(message)
+            return
+
+        self._data += message
+
+        while True:
+            if self._msg_size == 0:
+                if len(self._data) >= 4:
+                    self._msg_size = struct.unpack('@i', self._data[:4])[0]
+                else:
+                    return
+            if self._msg_size != 0 and len(self._data) >= self._msg_size + 4:
+                msg = py_str(bytes(self._data[4:4 + self._msg_size]))
+                del self._data[:4 + self._msg_size]
+                self._msg_size = 0
+                # pylint: disable=broad-except
+                self.call_handler(json.loads(msg))
+            else:
+                return
+
+    def ret_value(self, data):
+        """return value to the output"""
+        data = json.dumps(data)
+        self.write_message(
+            struct.pack('@i', len(data)), binary=True)
+        self.write_message(data.encode("utf-8"), binary=True)
+
+    def call_handler(self, args):
+        """Event handler when json request arrives."""
+        code = args[0]
+        if code == TrackerCode.PUT:
+            key = args[1]
+            port, matchkey = args[2]
+            self._tracker.put(key, (self._addr[0], port, matchkey))
+            self.ret_value(TrackerCode.SUCCESS)
+        elif code == TrackerCode.REQUEST:
+            key = args[1]
+            user = args[2]
+            priority = args[3]
+            def _cb(value):
+                self.ret_value([TrackerCode.SUCCESS, value])
+                return True
+            self._tracker.request(key, user, priority, _cb)
+        elif code == TrackerCode.PING:
+            self.ret_value(TrackerCode.SUCCESS)
+        elif code == TrackerCode.STOP:
+            # safe stop tracker
+            if self._tracker._stop_key == args[1]:
+                self.ret_value(TrackerCode.SUCCESS)
+                self._tracker.stop()
+            else:
+                self.ret_value(TrackerCode.FAIL)
+        else:
+            logging.info("Unknown tracker code %d", code)
+            self.close()
+
+    def on_close(self):
+        self._tracker._connections.remove(self)
+
+    def on_error(self, err):
+        logging.info("%s: Error in RPC Tracker: %s", self.name(), err)
+        self.close()
+
+
+class TrackerServerHandler(object):
+    """Tracker that tracks the resources."""
+    def __init__(self, sock, stop_key):
+        self._scheduler_map = {}
+        self._sock = sock
+        self._sock.setblocking(0)
+        self._ioloop = ioloop.IOLoop.current()
+        self._stop_key = stop_key
+        self._connections = set()
+        def _event_handler(_, events):
+            self._on_event(events)
+        self._ioloop.add_handler(
+            self._sock.fileno(), _event_handler, self._ioloop.READ)
+
+    def _on_event(self, _):
+        while True:
+            try:
+                conn, addr = self._sock.accept()
+                TCPEventHandler(self, conn, addr)
+            except socket.error as err:
+                if err.args[0] in (errno.EAGAIN, errno.EWOULDBLOCK):
+                    break
+
+    def create_scheduler(self, key):
+        """Create a new scheduler."""
+        _ = key
+        return PriorityScheduler()
+
+    def put(self, key, value):
+        """Report a new resource to the tracker."""
+        if key not in self._scheduler_map:
+            self._scheduler_map[key] = self.create_scheduler(key)
+        self._scheduler_map[key].put(value)
+
+    def request(self, key, user, priority, callback):
+        """Request a new resource."""
+        if key not in self._scheduler_map:
+            self._scheduler_map[key] = self.create_scheduler(key)
+        self._scheduler_map[key].request(user, priority, callback)
+
+    def stop(self):
+        """Safely stop tracker."""
+        for conn in list(self._connections):
+            conn.close()
+        self._sock.close()
+        self._ioloop.stop()
+
+    def run(self):
+        """Run the tracker server"""
+        self._ioloop.start()
+
+def _tracker_server(listen_sock, stop_key):
+    handler = TrackerServerHandler(listen_sock, stop_key)
+    handler.run()
+    logging.info("Tracker Stop signal received, terminating...")
+
+
+class Tracker(object):
+    """Start RPC tracker on a seperate process.
+
+    Python implementation based on multi-processing.
+
+    Parameters
+    ----------
+    host : str
+        The host url of the server.
+
+    port : int
+        The TCP port to be bind to
+
+    port_end : int, optional
+        The end TCP port to search
+    """
+    def __init__(self,
+                 host,
+                 port=9190,
+                 port_end=9199):
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.port = None
+        self.stop_key = base.random_key()
+        for my_port in range(port, port_end):
+            try:
+                sock.bind((host, my_port))
+                self.port = my_port
+                break
+            except socket.error as sock_err:
+                if sock_err.errno in [98, 48]:
+                    continue
+                else:
+                    raise sock_err
+        if not self.port:
+            raise ValueError("cannot bind to any port in [%d, %d)" % (port, port_end))
+        logging.info("RPCTracker: bind to %s:%d", host, self.port)
+        sock.listen(1)
+        self.proc = multiprocessing.Process(
+            target=_tracker_server, args=(sock, self.stop_key))
+        self.proc.start()
+        self.host = host
+        # close the socket on this process
+        sock.close()
+
+    def _stop_tracker(self):
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        sock.connect((self.host, self.port))
+        sock.sendall(struct.pack("@i", base.RPC_TRACKER_MAGIC))
+        magic = struct.unpack("@i", base.recvall(sock, 4))[0]
+        assert magic == base.RPC_TRACKER_MAGIC
+        base.sendjson(sock, [TrackerCode.STOP, self.stop_key])
+        assert base.recvjson(sock) == TrackerCode.SUCCESS
+        sock.close()
+
+    def terminate(self):
+        """Terminate the server process"""
+        if self.proc:
+            if self.proc.is_alive():
+                self._stop_tracker()
+                self.proc.join(1)
+            if self.proc.is_alive():
+                logging.info("Terminating Tracker Server...")
+                self.proc.terminate()
+            self.proc = None
+
+    def __del__(self):
+        self.terminate()
diff --git a/python/tvm/exec/rpc_proxy.py b/python/tvm/exec/rpc_proxy.py
index 52fbda818596..1ccd406bd0b3 100644
--- a/python/tvm/exec/rpc_proxy.py
+++ b/python/tvm/exec/rpc_proxy.py
@@ -4,7 +4,7 @@
 import logging
 import argparse
 import os
-from ..contrib.rpc_proxy import Proxy
+from ..contrib.rpc.proxy import Proxy
 
 def find_example_resource():
     """Find resource examples."""
diff --git a/python/tvm/exec/rpc_server.py b/python/tvm/exec/rpc_server.py
index deb830bdc583..7cbe17722bf5 100644
--- a/python/tvm/exec/rpc_server.py
+++ b/python/tvm/exec/rpc_server.py
@@ -17,13 +17,14 @@ def main():
                         help='The port of the PRC')
     parser.add_argument('--port-end', type=int, default=9199,
                         help='The end search port of the PRC')
+    parser.add_argument('--key', type=str, default="",
+                        help="RPC key used to identify the connection type.")
     parser.add_argument('--with-executor', type=bool, default=False,
                         help="Whether to load executor runtime")
     parser.add_argument('--load-library', type=str, default="",
                         help="Additional library to load")
-    parser.add_argument('--exclusive', action='store_true',
-                        help="If this is enabled, the server will kill old connection"
-                             "when new connection comes")
+    parser.add_argument('--tracker', type=str, default="",
+                        help="Report to RPC tracker")
     args = parser.parse_args()
 
     logging.basicConfig(level=logging.INFO)
@@ -38,7 +39,21 @@ def main():
         libs.append(ctypes.CDLL(file_name, ctypes.RTLD_GLOBAL))
         logging.info("Load additional library %s", file_name)
 
-    server = rpc.Server(args.host, args.port, args.port_end, exclusive=args.exclusive)
+    if args.tracker:
+        url, port = args.tracker.split(":")
+        port = int(port)
+        tracker_addr = (url, port)
+        if not args.key:
+            raise RuntimeError(
+                "Need key to present type of resource when tracker is available")
+    else:
+        tracker_addr = None
+
+    server = rpc.Server(args.host,
+                        args.port,
+                        args.port_end,
+                        key=args.key,
+                        tracker_addr=tracker_addr)
     server.libs += libs
     server.proc.join()
 
diff --git a/python/tvm/exec/rpc_tracker.py b/python/tvm/exec/rpc_tracker.py
new file mode 100644
index 000000000000..90264ad78f91
--- /dev/null
+++ b/python/tvm/exec/rpc_tracker.py
@@ -0,0 +1,21 @@
+"""RPC web proxy, allows redirect to websocket based RPC servers(browsers)"""
+from __future__ import absolute_import
+
+import logging
+import argparse
+from ..contrib.rpc.tracker import Tracker
+
+def main():
+    """Main funciton"""
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--host', type=str, default="0.0.0.0",
+                        help='the hostname of the server')
+    parser.add_argument('--port', type=int, default=9190,
+                        help='The port of the PRC')
+    args = parser.parse_args()
+    logging.basicConfig(level=logging.INFO)
+    tracker = Tracker(args.host, port=args.port)
+    tracker.proc.join()
+
+if __name__ == "__main__":
+    main()
diff --git a/src/runtime/rpc/rpc_event_impl.cc b/src/runtime/rpc/rpc_event_impl.cc
index df90bee56c26..7c3379434b9f 100644
--- a/src/runtime/rpc/rpc_event_impl.cc
+++ b/src/runtime/rpc/rpc_event_impl.cc
@@ -32,9 +32,12 @@ class CallbackChannel final : public RPCChannel {
   PackedFunc fsend_;
 };
 
-PackedFunc CreateEventDrivenServer(PackedFunc fsend, std::string name) {
+PackedFunc CreateEventDrivenServer(PackedFunc fsend,
+                                   std::string name,
+                                   std::string remote_key) {
   std::unique_ptr<CallbackChannel> ch(new CallbackChannel(fsend));
-  std::shared_ptr<RPCSession> sess = RPCSession::Create(std::move(ch), name);
+  std::shared_ptr<RPCSession> sess =
+      RPCSession::Create(std::move(ch), name, remote_key);
   return PackedFunc([sess](TVMArgs args, TVMRetValue* rv) {
       int ret = sess->ServerEventHandler(args[0], args[1]);
       *rv = ret;
@@ -43,7 +46,7 @@ PackedFunc CreateEventDrivenServer(PackedFunc fsend, std::string name) {
 
 TVM_REGISTER_GLOBAL("contrib.rpc._CreateEventDrivenServer")
 .set_body([](TVMArgs args, TVMRetValue* rv) {
-    *rv = CreateEventDrivenServer(args[0], args[1]);
+    *rv = CreateEventDrivenServer(args[0], args[1], args[2]);
   });
 }  // namespace runtime
 }  // namespace tvm
diff --git a/src/runtime/rpc/rpc_module.cc b/src/runtime/rpc/rpc_module.cc
index 9b4b3658c1be..81cb874f6c27 100644
--- a/src/runtime/rpc/rpc_module.cc
+++ b/src/runtime/rpc/rpc_module.cc
@@ -49,6 +49,7 @@ class RPCModuleNode final : public ModuleNode {
   ~RPCModuleNode() {
     if (module_handle_ != nullptr) {
       sess_->CallRemote(RPCCode::kModuleFree, module_handle_);
+      module_handle_ = nullptr;
     }
   }
 
@@ -198,5 +199,6 @@ TVM_REGISTER_GLOBAL("contrib.rpc._SessTableIndex")
     CHECK_EQ(tkey, "rpc");
     *rv = static_cast<RPCModuleNode*>(m.operator->())->sess()->table_index();
   });
+
 }  // namespace runtime
 }  // namespace tvm
diff --git a/src/runtime/rpc/rpc_session.cc b/src/runtime/rpc/rpc_session.cc
index 3d00371c034c..2c38294b967d 100644
--- a/src/runtime/rpc/rpc_session.cc
+++ b/src/runtime/rpc/rpc_session.cc
@@ -49,11 +49,19 @@ class RPCSession::EventHandler {
   EventHandler(common::RingBuffer* reader,
                common::RingBuffer* writer,
                int rpc_sess_table_index,
-               std::string name)
-      : reader_(reader), writer_(writer),
+               std::string name,
+               std::string* remote_key)
+      : reader_(reader),
+        writer_(writer),
         rpc_sess_table_index_(rpc_sess_table_index),
-        name_(name) {
+        name_(name),
+        remote_key_(remote_key) {
     this->Clear();
+    if (*remote_key == "%toinit") {
+      state_ = kInitHeader;
+      remote_key_->resize(0);
+      pending_request_bytes_ = sizeof(int32_t);
+    }
   }
   // Bytes needed to fulfill current request
   size_t BytesNeeded() {
@@ -75,6 +83,7 @@ class RPCSession::EventHandler {
     std::swap(client_mode_, client_mode);
     while (this->Ready()) {
       switch (state_) {
+        case kInitHeader: HandleInitHeader(); break;
         case kRecvCode: HandleRecvCode(); break;
         case kRecvCallHandle: {
           this->Read(&call_handle_, sizeof(call_handle_));
@@ -223,6 +232,7 @@ class RPCSession::EventHandler {
 
  protected:
   enum State {
+    kInitHeader,
     kRecvCode,
     kRecvCallHandle,
     kRecvPackedSeqNumArgs,
@@ -240,6 +250,8 @@ class RPCSession::EventHandler {
   RPCCode code_;
   // Handle for the remote function call.
   uint64_t call_handle_;
+  // Initialize remote header
+  bool init_header_step_{0};
   // Number of packed arguments.
   int num_packed_args_;
   // Current argument index.
@@ -266,6 +278,10 @@ class RPCSession::EventHandler {
         << "state=" << state;
     state_ = state;
     switch (state) {
+      case kInitHeader: {
+        LOG(FATAL) << "cannot switch to init header";
+        break;
+      }
       case kRecvCode: {
         this->RequestBytes(sizeof(RPCCode));
         break;
@@ -438,6 +454,21 @@ class RPCSession::EventHandler {
       this->SwitchToState(kRecvPackedSeqArg);
     }
   }
+  // handler for initial header read
+  void HandleInitHeader() {
+    if (init_header_step_ == 0) {
+      int32_t len;
+      this->Read(&len, sizeof(len));
+      remote_key_->resize(len);
+      init_header_step_ = 1;
+      this->RequestBytes(len);
+      return;
+    } else {
+      CHECK_EQ(init_header_step_, 1);
+      this->Read(dmlc::BeginPtr(*remote_key_), remote_key_->length());
+      this->SwitchToState(kRecvCode);
+    }
+  }
   // Handler for read code.
   void HandleRecvCode() {
     this->Read(&code_, sizeof(code_));
@@ -633,6 +664,8 @@ class RPCSession::EventHandler {
   int rpc_sess_table_index_;
   // Name of session.
   std::string name_;
+  // remote key
+  std::string* remote_key_;
 };
 
 struct RPCSessTable {
@@ -699,7 +732,8 @@ RPCCode RPCSession::HandleUntilReturnEvent(
 
 void RPCSession::Init() {
   // Event handler
-  handler_ = std::make_shared<EventHandler>(&reader_, &writer_, table_index_, name_);
+  handler_ = std::make_shared<EventHandler>(
+      &reader_, &writer_, table_index_, name_, &remote_key_);
   // Quick function to call remote.
   call_remote_ = PackedFunc([this](TVMArgs args, TVMRetValue* rv) {
       handler_->SendPackedSeq(args.values, args.type_codes, args.num_args);
@@ -709,10 +743,13 @@ void RPCSession::Init() {
 }
 
 std::shared_ptr<RPCSession> RPCSession::Create(
-    std::unique_ptr<RPCChannel> channel, std::string name) {
+    std::unique_ptr<RPCChannel> channel,
+    std::string name,
+    std::string remote_key) {
   std::shared_ptr<RPCSession> sess = std::make_shared<RPCSession>();
   sess->channel_ = std::move(channel);
   sess->name_ = std::move(name);
+  sess->remote_key_ = std::move(remote_key);
   sess->table_index_ = RPCSessTable::Global()->Insert(sess);
   sess->Init();
   return sess;
diff --git a/src/runtime/rpc/rpc_session.h b/src/runtime/rpc/rpc_session.h
index 59c1c1016ae2..5fe453924534 100644
--- a/src/runtime/rpc/rpc_session.h
+++ b/src/runtime/rpc/rpc_session.h
@@ -171,12 +171,15 @@ class RPCSession {
   /*!
    * \brief Create a RPC session with given channel.
    * \param channel The communication channel.
-   * \param name The name of the session, used for debug
-   * \return The session.
+   * \param name The local name of the session, used for debug
+   * \param remote_key The remote key of the session
+   *   if remote_key equals "%toinit", we need to re-intialize
+   *   it by event handler.
    */
   static std::shared_ptr<RPCSession> Create(
       std::unique_ptr<RPCChannel> channel,
-      std::string name);
+      std::string name,
+      std::string remote_key);
   /*!
    * \brief Try get session from the global session table by table index.
    * \param table_index The table index of the session.
@@ -208,6 +211,8 @@ class RPCSession {
   int table_index_{0};
   // The name of the session.
   std::string name_;
+  // The remote key
+  std::string remote_key_;
 };
 
 /*!
diff --git a/src/runtime/rpc/rpc_socket_impl.cc b/src/runtime/rpc/rpc_socket_impl.cc
index e2e089f25734..8348475d016e 100644
--- a/src/runtime/rpc/rpc_socket_impl.cc
+++ b/src/runtime/rpc/rpc_socket_impl.cc
@@ -68,7 +68,14 @@ RPCConnect(std::string url, int port, std::string key) {
     sock.Close();
     LOG(FATAL) << "URL " << url << ":" << port << " is not TVM RPC server";
   }
-  return RPCSession::Create(std::unique_ptr<SockChannel>(new SockChannel(sock)), key);
+  CHECK_EQ(sock.RecvAll(&keylen, sizeof(keylen)), sizeof(keylen));
+  std::string remote_key;
+  if (keylen != 0) {
+    remote_key.resize(keylen);
+    CHECK_EQ(sock.RecvAll(&remote_key[0], keylen), keylen);
+  }
+  return RPCSession::Create(
+      std::unique_ptr<SockChannel>(new SockChannel(sock)), key, remote_key);
 }
 
 Module RPCClientConnect(std::string url, int port, std::string key) {
@@ -80,7 +87,7 @@ void RPCServerLoop(int sockfd) {
       static_cast<common::TCPSocket::SockType>(sockfd));
   RPCSession::Create(
       std::unique_ptr<SockChannel>(new SockChannel(sock)),
-                     "SockServerLoop")->ServerLoop();
+      "SockServerLoop", "")->ServerLoop();
 }
 
 TVM_REGISTER_GLOBAL("contrib.rpc._Connect")
diff --git a/tests/python/contrib/test_rpc_proxy.py b/tests/python/contrib/test_rpc_proxy.py
index 74bc2a585abb..1cbc0ba759e8 100644
--- a/tests/python/contrib/test_rpc_proxy.py
+++ b/tests/python/contrib/test_rpc_proxy.py
@@ -17,9 +17,9 @@ def rpc_proxy_check():
     """
 
     try:
-        from tvm.contrib import rpc_proxy
+        from tvm.contrib.rpc import proxy
         web_port = 8888
-        prox = rpc_proxy.Proxy("localhost", web_port=web_port)
+        prox = proxy.Proxy("localhost", web_port=web_port)
         def check():
             if not tvm.module.enabled("rpc"):
                 return
@@ -30,7 +30,7 @@ def addone(x):
             def addone(name, x):
                 return "%s:%d" % (name, x)
             server = multiprocessing.Process(
-                target=rpc_proxy.websocket_proxy_server,
+                target=proxy.websocket_proxy_server,
                 args=("ws://localhost:%d/ws" % web_port,"x1"))
             # Need to make sure that the connection start after proxy comes up
             time.sleep(0.1)
diff --git a/tests/python/contrib/test_rpc_tracker.py b/tests/python/contrib/test_rpc_tracker.py
new file mode 100644
index 000000000000..336a4a9421d1
--- /dev/null
+++ b/tests/python/contrib/test_rpc_tracker.py
@@ -0,0 +1,59 @@
+import tvm
+import logging
+import numpy as np
+import time
+import multiprocessing
+from tvm.contrib import rpc
+
+def check_server_drop():
+    """test when server drops"""
+    try:
+        from tvm.contrib.rpc import tracker, base
+        from tvm.contrib.rpc.base import TrackerCode
+
+        @tvm.register_func("rpc.test2.addone")
+        def addone(x):
+            return x + 1
+
+        def _put(tclient, value):
+            base.sendjson(tclient._sock, value)
+            base.recvjson(tclient._sock)
+
+        tserver = tracker.Tracker("localhost", 8888)
+        tclient = rpc.connect_tracker("localhost", tserver.port)
+
+        server1 = rpc.Server(
+            "localhost", port=9099,
+            tracker_addr=("localhost", tserver.port),
+            key="xyz")
+        server2 = rpc.Server(
+            "localhost", port=9099,
+            tracker_addr=("localhost", tserver.port),
+            key="xyz")
+
+        # Fault tolerence to stale worker value
+        _put(tclient, [TrackerCode.PUT, "xyz", (server1.port, "abc")])
+        _put(tclient, [TrackerCode.PUT, "xyz", (server1.port, "abcxxx")])
+        _put(tclient, [TrackerCode.PUT, "xyz", (server2.port, "abcxxx11")])
+
+        # Fault tolerence server timeout
+        def check_timeout(timeout, sleeptime):
+            try:
+                remote = tclient.request("xyz", priority=0, session_timeout=timeout)
+                remote2 = tclient.request("xyz", session_timeout=timeout)
+                time.sleep(sleeptime)
+                f1 = remote.get_function("rpc.test2.addone")
+                assert f1(10) == 11
+                f1 = remote2.get_function("rpc.test2.addone")
+                assert f1(10) == 11
+            except tvm.TVMError as e:
+                pass
+        check_timeout(0.01, 0.1)
+        check_timeout(2, 0)
+    except ImportError:
+        print("Skip because tornado is not available")
+
+
+if __name__ == "__main__":
+    logging.basicConfig(level=logging.INFO)
+    check_server_drop()
diff --git a/tests/python/unittest/test_runtime_rpc.py b/tests/python/unittest/test_runtime_rpc.py
index cae65176efd5..46e84d887762 100644
--- a/tests/python/unittest/test_runtime_rpc.py
+++ b/tests/python/unittest/test_runtime_rpc.py
@@ -18,7 +18,7 @@ def strcat(name, x):
     def remotethrow(name):
         raise ValueError("%s" % name)
 
-    server = rpc.Server("localhost")
+    server = rpc.Server("localhost", key="x1")
     client = rpc.connect(server.host, server.port, key="x1")
     f1 = client.get_function("rpc.test.addone")
     assert f1(10) == 11
@@ -41,7 +41,6 @@ def remote_array_func(y):
         np.testing.assert_equal(y.asnumpy(), x)
     server = rpc.Server("localhost")
     remote = rpc.connect(server.host, server.port)
-    print("second connect")
     r_cpu = tvm.nd.array(x, remote.cpu(0))
     assert str(r_cpu.context).startswith("remote")
     np.testing.assert_equal(r_cpu.asnumpy(), x)
@@ -141,7 +140,7 @@ def test_rpc_return_func():
     @tvm.register_func("rpc.test.remote_func")
     def addone(x):
         return lambda y: x+y
-    server = rpc.Server("localhost")
+    server = rpc.Server("localhost", key="x1")
     client = rpc.connect(server.host, server.port, key="x1")
     f1 = client.get_function("rpc.test.remote_func")
     fadd = f1(10)
diff --git a/web/tvm_runtime.js b/web/tvm_runtime.js
index bc12f4f39c14..a0fdd57bc205 100644
--- a/web/tvm_runtime.js
+++ b/web/tvm_runtime.js
@@ -851,6 +851,7 @@ var tvm_runtime = tvm_runtime || {};
       }
       // Node js, import websocket
       var bkey = StringToUint8Array("server:" + key);
+      bkey = bkey.slice(0, bkey.length - 1);
       var server_name = "WebSocketRPCServer[" + key + "]";
       var RPC_MAGIC = 0xff271;
       function checkEndian() {
@@ -895,7 +896,7 @@ var tvm_runtime = tvm_runtime || {};
             } else {
               return new TVMConstant(0, "int32");
             }
-          } , server_name);
+          } , server_name, "%toinit");
 
         function on_open(event) {
           var intbuf = new Int32Array(1);
@@ -912,6 +913,7 @@ var tvm_runtime = tvm_runtime || {};
             var msg = new Uint8Array(event.data);
             CHECK(msg.length >= 4, "Need message header to be bigger than 4");
             var magic = new Int32Array(event.data)[0];
+
             if (magic == RPC_MAGIC + 1) {
               throwError("key: " + key + " has already been used in proxy");
             } else if (magic == RPC_MAGIC + 2) {
@@ -1014,7 +1016,7 @@ var tvm_runtime = tvm_runtime || {};
 
       /**
        * Load parameters from serialized byte array of parameter dict.
-       * 
+       *
        * @param {Uint8Array} params The serialized parameter dict.
        */
       "load_params" : function(params) {
@@ -1024,7 +1026,7 @@ var tvm_runtime = tvm_runtime || {};
 
       /**
        * Load parameters from serialized base64 string of parameter dict.
-       * 
+       *
        * @param {string} base64_params The serialized parameter dict.
        */
       "load_base64_params" : function(base64_params) {
@@ -1046,7 +1048,7 @@ var tvm_runtime = tvm_runtime || {};
 
       /**
        * Get index-th output to out.
-       * 
+       *
        * @param {NDArray} out The output array container.
        * @return {NDArray} The output array container.
        */
@@ -1076,7 +1078,7 @@ var tvm_runtime = tvm_runtime || {};
       var tvm_graph_module = fcreate(graph_json_str, libmod,
                                      new TVMConstant(ctx.device_type, "int32"),
                                      new TVMConstant(ctx.device_id, "int32"));
-      
+
       return new GraphModule(tvm_graph_module, ctx);
     };
 

From f8590066776d53a114aef1edb34a21655df54361 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 5 Apr 2018 13:47:48 -0700
Subject: [PATCH 247/949] [RPC] Tracker status query (#1081)

---
 python/tvm/contrib/rpc/base.py           |   3 +-
 python/tvm/contrib/rpc/client.py         | 100 +++++++++++++++++++++--
 python/tvm/contrib/rpc/server.py         |   5 ++
 python/tvm/contrib/rpc/tracker.py        |  39 ++++++++-
 python/tvm/exec/query_rpc_tracker.py     |  32 ++++++++
 python/tvm/exec/rpc_tracker.py           |   4 +-
 tests/python/contrib/test_rpc_tracker.py |  12 +++
 7 files changed, 183 insertions(+), 12 deletions(-)
 create mode 100644 python/tvm/exec/query_rpc_tracker.py

diff --git a/python/tvm/contrib/rpc/base.py b/python/tvm/contrib/rpc/base.py
index 9a39967efcf0..0819f7b89968 100644
--- a/python/tvm/contrib/rpc/base.py
+++ b/python/tvm/contrib/rpc/base.py
@@ -32,7 +32,8 @@ class TrackerCode(object):
     STOP = 2
     PUT = 3
     REQUEST = 4
-
+    UPDATE_INFO = 5
+    SUMMARY = 6
 
 RPC_SESS_MASK = 128
 
diff --git a/python/tvm/contrib/rpc/client.py b/python/tvm/contrib/rpc/client.py
index 981b6aef793b..1ea94eeb68ed 100644
--- a/python/tvm/contrib/rpc/client.py
+++ b/python/tvm/contrib/rpc/client.py
@@ -4,6 +4,7 @@
 import os
 import socket
 import struct
+import time
 
 from . import base
 from ..._ffi.base import TVMError
@@ -150,7 +151,6 @@ class TrackerSession(object):
     def __init__(self, addr):
         self._addr = addr
         self._sock = None
-        self._max_request_retry = 5
         self._connect()
 
     def __del__(self):
@@ -169,7 +169,38 @@ def close(self):
             self._sock.close()
             self._sock = None
 
-    def request(self, key, priority=1, session_timeout=0):
+    def summary(self):
+        """Get the summary dict of the tracker."""
+        base.sendjson(self._sock, [base.TrackerCode.SUMMARY])
+        value = base.recvjson(self._sock)
+        if value[0] != base.TrackerCode.SUCCESS:
+            raise RuntimeError("Invalid return value %s" % str(value))
+        return value[1]
+
+    def text_summary(self):
+        """Get a text summary of the tracker."""
+        data = self.summary()
+        res = ""
+        res += "Server List\n"
+        res += "----------------------------\n"
+        res += "server-address\tkey\n"
+        res += "----------------------------\n"
+        for item in data["server_info"]:
+            addr = item["addr"]
+            res += addr[0] + ":" + str(addr[1])+ "\t"
+            res += item["key"] + "\n"
+        res += "----------------------------\n"
+        res += "\n"
+        res += "Queue Status\n"
+        res += "----------------------------\n"
+        res += "key\tfree\tpending\n"
+        res += "----------------------------\n"
+        for k, v in data["queue_info"].items():
+            res += "%s\t%d\t%g\n" % (k, v["free"], v["pending"])
+        res += "----------------------------\n"
+        return res
+
+    def request(self, key, priority=1, session_timeout=0, max_retry=5):
         """Request a new connection from the tracker.
 
         Parameters
@@ -184,8 +215,12 @@ def request(self, key, priority=1, session_timeout=0):
             The duration of the session, allows server to kill
             the connection when duration is longer than this value.
             When duration is zero, it means the request must always be kept alive.
+
+        max_retry : int, optional
+            Maximum number of times to retry before give up.
         """
-        for _ in range(self._max_request_retry):
+        last_err = None
+        for _ in range(max_retry):
             try:
                 if self._sock is None:
                     self._connect()
@@ -196,10 +231,63 @@ def request(self, key, priority=1, session_timeout=0):
                     raise RuntimeError("Invalid return value %s" % str(value))
                 url, port, matchkey = value[1]
                 return connect(url, port, key + matchkey, session_timeout)
-            except socket.error:
+            except socket.error as err:
                 self.close()
-            except TVMError:
-                pass
+                last_err = err
+            except TVMError as err:
+                last_err = err
+        raise RuntimeError(
+            "Cannot request %s after %d retry, last_error:%s" % (
+                key, max_retry, str(last_err)))
+
+    def request_and_run(self,
+                        key,
+                        func,
+                        priority=1,
+                        session_timeout=0,
+                        max_retry=2):
+        """Request a resource from tracker and run the func.
+
+        This function safe-guard rare server node dropout during execution.
+        In such case, a new resource will be requested and func will be ran again.
+
+        Parameters
+        ----------
+        key : str
+            The type key of the device.
+
+        func : function of session -> value
+            A stateless function
+
+        priority : int, optional
+            The priority of the request.
+
+        session_timeout : float, optional
+            The duration of the session, allows server to kill
+            the connection when duration is longer than this value.
+            When duration is zero, it means the request must always be kept alive.
+
+        max_retry : int, optional
+            Maximum number of times to retry the function before give up.
+        """
+        last_err = None
+        for _ in range(max_retry):
+            try:
+                sess = self.request(key,
+                                    priority=priority,
+                                    session_timeout=session_timeout)
+                tstart = time.time()
+                return func(sess)
+            except TVMError as err:
+                duration = time.time() - tstart
+                # roughly estimate if the error is due to timeout termination
+                if session_timeout and duration >= session_timeout * 0.95:
+                    raise RuntimeError(
+                        "Session timeout when running %s" % func.__name__)
+                last_err = err
+        raise RuntimeError(
+            "Failed to run on %s after %d retry, last_error:%s" % (
+                key, max_retry, str(last_err)))
 
 
 def connect(url, port, key="", session_timeout=0):
diff --git a/python/tvm/contrib/rpc/server.py b/python/tvm/contrib/rpc/server.py
index f066e01bad6c..f819d29fd454 100644
--- a/python/tvm/contrib/rpc/server.py
+++ b/python/tvm/contrib/rpc/server.py
@@ -137,6 +137,11 @@ def _accept_conn(listen_sock, tracker_conn, ping_period=0.1):
             magic = struct.unpack("@i", base.recvall(tracker_conn, 4))[0]
             if magic != base.RPC_TRACKER_MAGIC:
                 raise RuntimeError("%s is not RPC Tracker" % str(tracker_addr))
+            # report status of current queue
+            cinfo = {"key" : "server:" + rpc_key}
+            base.sendjson(tracker_conn,
+                          [TrackerCode.UPDATE_INFO, cinfo])
+            assert base.recvjson(tracker_conn) == TrackerCode.SUCCESS
         try:
             # step 2: wait for in-coming connections
             conn, addr, opts = _accept_conn(sock, tracker_conn)
diff --git a/python/tvm/contrib/rpc/tracker.py b/python/tvm/contrib/rpc/tracker.py
index 6718ee5e7b5a..31d52bc85fc1 100644
--- a/python/tvm/contrib/rpc/tracker.py
+++ b/python/tvm/contrib/rpc/tracker.py
@@ -75,10 +75,15 @@ def request(self, user, priority, callback):
         """
         raise NotImplementedError()
 
+    def summary(self):
+        """Get summary information of the scheduler."""
+        raise NotImplementedError()
+
 
 class PriorityScheduler(Scheduler):
     """Priority based scheduler, FIFO based on time"""
-    def __init__(self):
+    def __init__(self, key):
+        self._key = key
         self._values = []
         self._requests = []
 
@@ -98,6 +103,11 @@ def request(self, user, priority, callback):
         heapq.heappush(self._requests, (-priority, time.time(), callback))
         self._schedule()
 
+    def summary(self):
+        """Get summary information of the scheduler."""
+        return {"free": len(self._values),
+                "pending": len(self._requests)}
+
 
 class TCPEventHandler(tornado_util.TCPHandler):
     """Base asynchronize message handler.
@@ -113,12 +123,17 @@ def __init__(self, tracker, sock, addr):
         self._msg_size = 0
         self._addr = addr
         self._init_req_nbytes = 4
+        self._info = {"addr": addr}
         self._tracker._connections.add(self)
 
     def name(self):
         """name of connection"""
         return "TCPSocket: %s" % str(self._addr)
 
+    def summary(self):
+        """Summary of this connection"""
+        return self._info
+
     def _init_conn(self, message):
         """Initialie the connection"""
         if len(message) != 4:
@@ -193,6 +208,12 @@ def _cb(value):
                 self._tracker.stop()
             else:
                 self.ret_value(TrackerCode.FAIL)
+        elif code == TrackerCode.UPDATE_INFO:
+            self._info.update(args[1])
+            self.ret_value(TrackerCode.SUCCESS)
+        elif code == TrackerCode.SUMMARY:
+            status = self._tracker.summary()
+            self.ret_value([TrackerCode.SUCCESS, status])
         else:
             logging.info("Unknown tracker code %d", code)
             self.close()
@@ -230,8 +251,7 @@ def _on_event(self, _):
 
     def create_scheduler(self, key):
         """Create a new scheduler."""
-        _ = key
-        return PriorityScheduler()
+        return PriorityScheduler(key)
 
     def put(self, key, value):
         """Report a new resource to the tracker."""
@@ -252,6 +272,19 @@ def stop(self):
         self._sock.close()
         self._ioloop.stop()
 
+    def summary(self):
+        """Return a dict summarizing current status."""
+        qinfo = {}
+        for k, v in self._scheduler_map.items():
+            qinfo[k] = v.summary()
+        cinfo = []
+        # ignore client connections without key
+        for conn in self._connections:
+            res = conn.summary()
+            if res.get("key", "").startswith("server"):
+                cinfo.append(res)
+        return {"queue_info": qinfo, "server_info": cinfo}
+
     def run(self):
         """Run the tracker server"""
         self._ioloop.start()
diff --git a/python/tvm/exec/query_rpc_tracker.py b/python/tvm/exec/query_rpc_tracker.py
new file mode 100644
index 000000000000..061d1606db8e
--- /dev/null
+++ b/python/tvm/exec/query_rpc_tracker.py
@@ -0,0 +1,32 @@
+"""Tool to query RPC tracker status"""
+from __future__ import absolute_import
+
+import logging
+import argparse
+import os
+from ..contrib import rpc
+
+def main():
+    """Main funciton"""
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--host', type=str, default="",
+                        help='the hostname of the tracker')
+    parser.add_argument('--port', type=int, default=None,
+                        help='The port of the PRC')
+    args = parser.parse_args()
+    logging.basicConfig(level=logging.INFO)
+
+    # default to local host or environment variable
+    if not args.host:
+        args.host = os.environ.get("TVM_TRACKER_HOST", "localhost")
+
+    if not args.port:
+        args.port = int(os.environ.get("TVM_TRACKER_PORT", "9190"))
+
+    conn = rpc.connect_tracker(args.host, args.port)
+    # pylint: disable=superfluous-parens
+    print("Tracker address %s:%d\n" % (args.host, args.port))
+    print("%s" % conn.text_summary())
+
+if __name__ == "__main__":
+    main()
diff --git a/python/tvm/exec/rpc_tracker.py b/python/tvm/exec/rpc_tracker.py
index 90264ad78f91..a5b063f37908 100644
--- a/python/tvm/exec/rpc_tracker.py
+++ b/python/tvm/exec/rpc_tracker.py
@@ -1,4 +1,4 @@
-"""RPC web proxy, allows redirect to websocket based RPC servers(browsers)"""
+"""Tool to start RPC tracker"""
 from __future__ import absolute_import
 
 import logging
@@ -9,7 +9,7 @@ def main():
     """Main funciton"""
     parser = argparse.ArgumentParser()
     parser.add_argument('--host', type=str, default="0.0.0.0",
-                        help='the hostname of the server')
+                        help='the hostname of the tracker')
     parser.add_argument('--port', type=int, default=9190,
                         help='The port of the PRC')
     args = parser.parse_args()
diff --git a/tests/python/contrib/test_rpc_tracker.py b/tests/python/contrib/test_rpc_tracker.py
index 336a4a9421d1..5bcd982bb44c 100644
--- a/tests/python/contrib/test_rpc_tracker.py
+++ b/tests/python/contrib/test_rpc_tracker.py
@@ -38,6 +38,15 @@ def _put(tclient, value):
 
         # Fault tolerence server timeout
         def check_timeout(timeout, sleeptime):
+            def myfunc(remote):
+                time.sleep(sleeptime)
+                f1 = remote.get_function("rpc.test2.addone")
+                assert f1(10) == 11
+            try:
+                tclient.request_and_run("xyz", myfunc, session_timeout=timeout)
+            except RuntimeError:
+                pass
+            print(tclient.text_summary())
             try:
                 remote = tclient.request("xyz", priority=0, session_timeout=timeout)
                 remote2 = tclient.request("xyz", session_timeout=timeout)
@@ -48,8 +57,11 @@ def check_timeout(timeout, sleeptime):
                 assert f1(10) == 11
             except tvm.TVMError as e:
                 pass
+
         check_timeout(0.01, 0.1)
         check_timeout(2, 0)
+
+
     except ImportError:
         print("Skip because tornado is not available")
 

From 39047131c75ab8d5291f67f2390ee4c8e52a50a9 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 5 Apr 2018 21:44:07 -0700
Subject: [PATCH 248/949] [RPC] support tracker in proxy (#1082)

---
 python/tvm/contrib/rpc/base.py           |  26 +++-
 python/tvm/contrib/rpc/proxy.py          | 157 ++++++++++++++++++-----
 python/tvm/contrib/rpc/server.py         |  92 +++++++------
 python/tvm/contrib/rpc/tracker.py        |   2 +-
 python/tvm/exec/rpc_proxy.py             |  26 +++-
 tests/python/contrib/test_rpc_tracker.py |  20 ++-
 6 files changed, 235 insertions(+), 88 deletions(-)

diff --git a/python/tvm/contrib/rpc/base.py b/python/tvm/contrib/rpc/base.py
index 0819f7b89968..799d99e53d54 100644
--- a/python/tvm/contrib/rpc/base.py
+++ b/python/tvm/contrib/rpc/base.py
@@ -94,9 +94,29 @@ def recvjson(sock):
     return data
 
 
-def random_key():
-    """Generate a random key n"""
-    return str(random.random())
+def random_key(prefix, cmap=None):
+    """Generate a random key
+
+    Parameters
+    ----------
+    prefix : str
+        The string prefix
+
+    cmap : dict
+        Conflict map
+
+    Returns
+    -------
+    key : str
+        The generated random key
+    """
+    if cmap:
+        while True:
+            key = prefix + str(random.random())
+            if key not in cmap:
+                return key
+    else:
+        return prefix + str(random.random())
 
 
 def connect_with_retry(addr, timeout=60, retry_period=5):
diff --git a/python/tvm/contrib/rpc/proxy.py b/python/tvm/contrib/rpc/proxy.py
index a917ec4972bc..6ae18789f9d2 100644
--- a/python/tvm/contrib/rpc/proxy.py
+++ b/python/tvm/contrib/rpc/proxy.py
@@ -25,22 +25,26 @@
     raise ImportError("RPCProxy module requires tornado package %s" % error_msg)
 
 from . import base
-from .base import RPC_MAGIC, RPC_CODE_DUPLICATE, RPC_CODE_SUCCESS, RPC_CODE_MISMATCH
+from .base import TrackerCode
 from .server import _server_env
 from ..._ffi.base import py_str
 
+
 class ForwardHandler(object):
     """Forward handler to forward the message."""
     def _init_handler(self):
         """Initialize handler."""
         self._init_message = bytes()
         self._init_req_nbytes = 4
-        self.forward_proxy = None
         self._magic = None
         self.timeout = None
         self._rpc_key_length = None
-        self.rpc_key = None
         self._done = False
+        self._proxy = ProxyServerHandler.current
+        assert self._proxy
+        self.rpc_key = None
+        self.match_key = None
+        self.forward_proxy = None
 
     def __del__(self):
         logging.info("Delete %s...", self.name())
@@ -53,7 +57,7 @@ def _init_step(self, message):
         if self._magic is None:
             assert len(message) == 4
             self._magic = struct.unpack('@i', message)[0]
-            if self._magic != RPC_MAGIC:
+            if self._magic != base.RPC_MAGIC:
                 logging.info("Invalid RPC magic from %s", self.name())
                 self.close()
             self._init_req_nbytes = 4
@@ -64,13 +68,15 @@ def _init_step(self, message):
         elif self.rpc_key is None:
             assert len(message) == self._rpc_key_length
             self.rpc_key = py_str(message)
+            # match key is used to do the matching
+            self.match_key = self.rpc_key[7:].split()[0]
             self.on_start()
         else:
             assert False
 
     def on_start(self):
         """Event when the initialization is completed"""
-        ProxyServerHandler.current.handler_ready(self)
+        self._proxy.handler_ready(self)
 
     def on_data(self, message):
         """on data"""
@@ -105,12 +111,12 @@ def on_close_event(self):
         """on close event"""
         assert not self._done
         logging.info("RPCProxy:on_close %s ...", self.name())
-        if self.rpc_key:
-            key = self.rpc_key[7:]
-            if ProxyServerHandler.current._client_pool.get(key, None) == self:
-                ProxyServerHandler.current._client_pool.pop(key)
-            if ProxyServerHandler.current._server_pool.get(key, None) == self:
-                ProxyServerHandler.current._server_pool.pop(key)
+        if self.match_key:
+            key = self.match_key
+            if self._proxy._client_pool.get(key, None) == self:
+                self._proxy._client_pool.pop(key)
+            if self._proxy._server_pool.get(key, None) == self:
+                self._proxy._server_pool.pop(key)
         self._done = True
         self.forward_proxy = None
 
@@ -123,7 +129,7 @@ def __init__(self, sock, addr):
         self.addr = addr
 
     def name(self):
-        return "TCPSocket: %s:%s"  % (str(self.addr), self.rpc_key)
+        return "TCPSocketProxy:%s:%s"  % (str(self.addr[0]), self.rpc_key)
 
     def send_data(self, message, binary=True):
         self.write_message(message, True)
@@ -146,7 +152,7 @@ def __init__(self, *args, **kwargs):
         self._init_handler()
 
     def name(self):
-        return "WebSocketProxy: %s" % (self.rpc_key)
+        return "WebSocketProxy:%s" % (self.rpc_key)
 
     def on_message(self, message):
         self.on_data(message)
@@ -192,15 +198,16 @@ def get(self, *args, **kwargs):
         self.write(self.page)
 
 
-
 class ProxyServerHandler(object):
     """Internal proxy server handler class."""
     current = None
     def __init__(self,
                  sock,
+                 listen_port,
                  web_port,
                  timeout_client,
                  timeout_server,
+                 tracker_addr,
                  index_page=None,
                  resource_files=None):
         assert ProxyServerHandler.current is None
@@ -232,6 +239,14 @@ def event_handler(_, events):
         self._server_pool = {}
         self.timeout_client = timeout_client
         self.timeout_server = timeout_server
+        # tracker information
+        self._listen_port = listen_port
+        self._tracker_addr = tracker_addr
+        self._tracker_conn = None
+        self._tracker_pending_puts = []
+        self._key_set = set()
+        if tracker_addr:
+            logging.info("Tracker address:%s", str(tracker_addr))
         logging.info("RPCProxy: Websock port bind to %d", web_port)
 
     def _on_event(self, _):
@@ -247,21 +262,85 @@ def _pair_up(self, lhs, rhs):
         lhs.forward_proxy = rhs
         rhs.forward_proxy = lhs
 
-        lhs.send_data(struct.pack('@i', RPC_CODE_SUCCESS))
+        lhs.send_data(struct.pack('@i', base.RPC_CODE_SUCCESS))
         lhs.send_data(struct.pack('@i', len(rhs.rpc_key)))
         lhs.send_data(rhs.rpc_key.encode("utf-8"))
 
-        rhs.send_data(struct.pack('@i', RPC_CODE_SUCCESS))
+        rhs.send_data(struct.pack('@i', base.RPC_CODE_SUCCESS))
         rhs.send_data(struct.pack('@i', len(lhs.rpc_key)))
         rhs.send_data(lhs.rpc_key.encode("utf-8"))
-
         logging.info("Pairup connect %s  and %s", lhs.name(), rhs.name())
 
-    def handler_ready(self, handler):
-        """Report handler to be ready."""
-        logging.info("Handler ready %s", handler.name())
-        key = handler.rpc_key[7:]
-
+    def _update_tracker(self):
+        """Update information on tracker."""
+        try:
+            if self._tracker_conn is None:
+                self._tracker_conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+                self._tracker_conn.connect(self._tracker_addr)
+                self._tracker_conn.sendall(struct.pack("@i", base.RPC_TRACKER_MAGIC))
+                magic = struct.unpack("@i", base.recvall(self._tracker_conn, 4))[0]
+                if magic != base.RPC_TRACKER_MAGIC:
+                    self.loop.stop()
+                    raise RuntimeError("%s is not RPC Tracker" % str(self._tracker_addr))
+                # just connect to tracker, need to update all keys
+                self._tracker_pending_puts = self._server_pool.keys()
+
+            need_update_info = False
+            # report new connections
+            for key in self._tracker_pending_puts:
+                rpc_key, match_key = key.split(":")
+                base.sendjson(self._tracker_conn,
+                              [TrackerCode.PUT, rpc_key,
+                               (self._listen_port, ":" + match_key)])
+                assert base.recvjson(self._tracker_conn) == TrackerCode.SUCCESS
+                if rpc_key not in self._key_set:
+                    self._key_set.add(rpc_key)
+                    need_update_info = True
+
+            if need_update_info:
+                keylist = "[" + ",".join(self._key_set) + "]"
+                cinfo = {"key": "server:proxy" + keylist}
+                base.sendjson(self._tracker_conn,
+                              [TrackerCode.UPDATE_INFO, cinfo])
+                assert base.recvjson(self._tracker_conn) == TrackerCode.SUCCESS
+            self._tracker_pending_puts = []
+        except (socket.error, IOError) as err:
+            retry_period = 5
+            logging.info(
+                "Lost tracker connection: %s, try reconnect in %g sec",
+                str(err), retry_period)
+            self._tracker_conn.close()
+            self._tracker_conn = None
+            new_pool = {}
+            keyset = set(self._server_pool.keys())
+            # re-generate the server match key, so old information is invalidated.
+            for key, handle in self._server_pool.items():
+                rpc_key, _ = key.split(":")
+                key = base.random_key(rpc_key + ":", keyset)
+                new_pool[key] = handle
+                keyset.add(key)
+            self._server_pool = new_pool
+            def _callback():
+                self._update_tracker()
+            self.loop.call_later(retry_period, _callback)
+
+    def _handler_ready_tracker_mode(self, handler):
+        """tracker mode to handle handler ready."""
+        if handler.rpc_key.startswith("server:"):
+            key = base.random_key(handler.match_key + ":", self._server_pool)
+            handler.match_key = key
+            self._server_pool[key] = handler
+            self._tracker_pending_puts.append(key)
+            self._update_tracker()
+        else:
+            if handler.match_key in self._server_pool:
+                self._pair_up(self._server_pool.pop(handler.match_key), handler)
+            else:
+                handler.send_data(struct.pack('@i', base.RPC_CODE_MISMATCH))
+                handler.signal_close()
+
+    def _handler_ready_proxy_mode(self, handler):
+        """Normal proxy mode when handler is ready."""
         if handler.rpc_key.startswith("server:"):
             pool_src, pool_dst = self._client_pool, self._server_pool
             timeout = self.timeout_server
@@ -269,6 +348,7 @@ def handler_ready(self, handler):
             pool_src, pool_dst = self._server_pool, self._client_pool
             timeout = self.timeout_client
 
+        key = handler.match_key
         if key in pool_src:
             self._pair_up(pool_src.pop(key), handler)
             return
@@ -280,29 +360,41 @@ def cleanup():
                     logging.info("Timeout client connection %s, cannot find match key=%s",
                                  handler.name(), key)
                     pool_dst.pop(key)
-                    handler.send_data(struct.pack('@i', RPC_CODE_MISMATCH))
+                    handler.send_data(struct.pack('@i', base.RPC_CODE_MISMATCH))
                     handler.signal_close()
             self.loop.call_later(timeout, cleanup)
         else:
             logging.info("Duplicate connection with same key=%s", key)
-            handler.send_data(struct.pack('@i', RPC_CODE_DUPLICATE))
+            handler.send_data(struct.pack('@i', base.RPC_CODE_DUPLICATE))
             handler.signal_close()
 
+    def handler_ready(self, handler):
+        """Report handler to be ready."""
+        logging.info("Handler ready %s", handler.name())
+        if self._tracker_addr:
+            self._handler_ready_tracker_mode(handler)
+        else:
+            self._handler_ready_proxy_mode(handler)
+
     def run(self):
         """Run the proxy server"""
         ioloop.IOLoop.current().start()
 
 
 def _proxy_server(listen_sock,
+                  listen_port,
                   web_port,
                   timeout_client,
                   timeout_server,
+                  tracker_addr,
                   index_page,
                   resource_files):
     handler = ProxyServerHandler(listen_sock,
+                                 listen_port,
                                  web_port,
                                  timeout_client,
                                  timeout_server,
+                                 tracker_addr,
                                  index_page,
                                  resource_files)
     handler.run()
@@ -346,6 +438,7 @@ def __init__(self,
                  web_port=0,
                  timeout_client=600,
                  timeout_server=600,
+                 tracker_addr=None,
                  index_page=None,
                  resource_files=None):
         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -365,10 +458,12 @@ def __init__(self,
         logging.info("RPCProxy: client port bind to %s:%d", host, self.port)
         sock.listen(1)
         self.proc = multiprocessing.Process(
-            target=_proxy_server, args=(sock, web_port,
-                                        timeout_client, timeout_server,
-                                        index_page, resource_files))
+            target=_proxy_server,
+            args=(sock, self.port, web_port,
+                  timeout_client, timeout_server,
+                  tracker_addr, index_page, resource_files))
         self.proc.start()
+        sock.close()
         self.host = host
 
     def terminate(self):
@@ -408,18 +503,18 @@ def _connect(key):
         on_message = create_on_message(conn)
         temp = _server_env()
         # Start connecton
-        conn.write_message(struct.pack('@i', RPC_MAGIC), binary=True)
+        conn.write_message(struct.pack('@i', base.RPC_MAGIC), binary=True)
         key = "server:" + key
         conn.write_message(struct.pack('@i', len(key)), binary=True)
         conn.write_message(key.encode("utf-8"), binary=True)
         msg = yield conn.read_message()
         assert len(msg) >= 4
         magic = struct.unpack('@i', msg[:4])[0]
-        if magic == RPC_CODE_DUPLICATE:
+        if magic == base.RPC_CODE_DUPLICATE:
             raise RuntimeError("key: %s has already been used in proxy" % key)
-        elif magic == RPC_CODE_MISMATCH:
+        elif magic == base.RPC_CODE_MISMATCH:
             logging.info("RPCProxy do not have matching client key %s", key)
-        elif magic != RPC_CODE_SUCCESS:
+        elif magic != base.RPC_CODE_SUCCESS:
             raise RuntimeError("%s is not RPC Proxy" % url)
         msg = msg[4:]
 
diff --git a/python/tvm/contrib/rpc/server.py b/python/tvm/contrib/rpc/server.py
index f819d29fd454..18f777dd5ef9 100644
--- a/python/tvm/contrib/rpc/server.py
+++ b/python/tvm/contrib/rpc/server.py
@@ -91,7 +91,7 @@ def _accept_conn(listen_sock, tracker_conn, ping_period=0.1):
         """
         # Report resource to tracker
         if tracker_conn:
-            matchkey = ":" + base.random_key()
+            matchkey = base.random_key(":")
             base.sendjson(tracker_conn,
                           [TrackerCode.PUT, rpc_key, (port, matchkey)])
             assert base.recvjson(tracker_conn) == TrackerCode.SUCCESS
@@ -130,19 +130,20 @@ def _accept_conn(listen_sock, tracker_conn, ping_period=0.1):
     # Server logic
     tracker_conn = None
     while True:
-        # step 1: setup tracker and report to tracker
-        if tracker_addr and tracker_conn is None:
-            tracker_conn = base.connect_with_retry(tracker_addr)
-            tracker_conn.sendall(struct.pack("@i", base.RPC_TRACKER_MAGIC))
-            magic = struct.unpack("@i", base.recvall(tracker_conn, 4))[0]
-            if magic != base.RPC_TRACKER_MAGIC:
-                raise RuntimeError("%s is not RPC Tracker" % str(tracker_addr))
-            # report status of current queue
-            cinfo = {"key" : "server:" + rpc_key}
-            base.sendjson(tracker_conn,
-                          [TrackerCode.UPDATE_INFO, cinfo])
-            assert base.recvjson(tracker_conn) == TrackerCode.SUCCESS
         try:
+            # step 1: setup tracker and report to tracker
+            if tracker_addr and tracker_conn is None:
+                tracker_conn = base.connect_with_retry(tracker_addr)
+                tracker_conn.sendall(struct.pack("@i", base.RPC_TRACKER_MAGIC))
+                magic = struct.unpack("@i", base.recvall(tracker_conn, 4))[0]
+                if magic != base.RPC_TRACKER_MAGIC:
+                    raise RuntimeError("%s is not RPC Tracker" % str(tracker_addr))
+                # report status of current queue
+                cinfo = {"key" : "server:" + rpc_key}
+                base.sendjson(tracker_conn,
+                              [TrackerCode.UPDATE_INFO, cinfo])
+                assert base.recvjson(tracker_conn) == TrackerCode.SUCCESS
+
             # step 2: wait for in-coming connections
             conn, addr, opts = _accept_conn(sock, tracker_conn)
         except (socket.error, IOError):
@@ -161,40 +162,49 @@ def _accept_conn(listen_sock, tracker_conn, ping_period=0.1):
         # wait until server process finish or timeout
         server_proc.join(opts.get("timeout", None))
         if server_proc.is_alive():
-            logging.info("Timeout in RPC session, kill..")
+            logging.info("RPCServer: Timeout in RPC session, kill..")
             server_proc.terminate()
 
 
 def _connect_proxy_loop(addr, key):
     key = "server:" + key
+    retry_count = 0
+    max_retry = 5
+    retry_period = 5
     while True:
-        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        sock.connect(addr)
-        sock.sendall(struct.pack("@i", base.RPC_MAGIC))
-        sock.sendall(struct.pack("@i", len(key)))
-        sock.sendall(key.encode("utf-8"))
-        magic = struct.unpack("@i", base.recvall(sock, 4))[0]
-        if magic == base.RPC_CODE_DUPLICATE:
-            raise RuntimeError("key: %s has already been used in proxy" % key)
-        elif magic == base.RPC_CODE_MISMATCH:
-            logging.info("RPCProxy do not have matching client key %s", key)
-        elif magic != base.RPC_CODE_SUCCESS:
-            raise RuntimeError("%s is not RPC Proxy" % str(addr))
-        keylen = struct.unpack("@i", base.recvall(sock, 4))[0]
-        remote_key = py_str(base.recvall(sock, keylen))
-        opts = _parse_server_opt(remote_key.split()[1:])
-
-        logging.info("RPCProxy connected to %s", str(addr))
-        process = multiprocessing.Process(
-            target=_serve_loop, args=(sock, addr))
-        process.deamon = True
-        process.start()
-        sock.close()
-        process.join(opts.get("timeout", None))
-        if process.is_alive():
-            logging.info("Timeout in RPC session, kill..")
-            process.terminate()
-
+        try:
+            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            sock.connect(addr)
+            sock.sendall(struct.pack("@i", base.RPC_MAGIC))
+            sock.sendall(struct.pack("@i", len(key)))
+            sock.sendall(key.encode("utf-8"))
+            magic = struct.unpack("@i", base.recvall(sock, 4))[0]
+            if magic == base.RPC_CODE_DUPLICATE:
+                raise RuntimeError("key: %s has already been used in proxy" % key)
+            elif magic == base.RPC_CODE_MISMATCH:
+                logging.info("RPCProxy do not have matching client key %s", key)
+            elif magic != base.RPC_CODE_SUCCESS:
+                raise RuntimeError("%s is not RPC Proxy" % str(addr))
+            keylen = struct.unpack("@i", base.recvall(sock, 4))[0]
+            remote_key = py_str(base.recvall(sock, keylen))
+            opts = _parse_server_opt(remote_key.split()[1:])
+            logging.info("RPCProxy connected to %s", str(addr))
+            process = multiprocessing.Process(
+                target=_serve_loop, args=(sock, addr))
+            process.deamon = True
+            process.start()
+            sock.close()
+            process.join(opts.get("timeout", None))
+            if process.is_alive():
+                logging.info("RPCProxyServer: Timeout in RPC session, kill..")
+                process.terminate()
+            retry_count = 0
+        except (socket.error, IOError) as err:
+            retry_count += 1
+            logging.info("Error encountered %s, retry in %g sec", str(err), retry_period)
+            if retry_count > max_retry:
+                raise RuntimeError("Maximum retry error: last error: %s" % str(err))
+            time.sleep(retry_period)
 
 def _popen(cmd):
     proc = subprocess.Popen(cmd,
diff --git a/python/tvm/contrib/rpc/tracker.py b/python/tvm/contrib/rpc/tracker.py
index 31d52bc85fc1..1049fd76dc3b 100644
--- a/python/tvm/contrib/rpc/tracker.py
+++ b/python/tvm/contrib/rpc/tracker.py
@@ -317,7 +317,7 @@ def __init__(self,
                  port_end=9199):
         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         self.port = None
-        self.stop_key = base.random_key()
+        self.stop_key = base.random_key("tracker")
         for my_port in range(port, port_end):
             try:
                 sock.bind((host, my_port))
diff --git a/python/tvm/exec/rpc_proxy.py b/python/tvm/exec/rpc_proxy.py
index 1ccd406bd0b3..1a1bdfcb09b8 100644
--- a/python/tvm/exec/rpc_proxy.py
+++ b/python/tvm/exec/rpc_proxy.py
@@ -29,19 +29,35 @@ def main():
                         help='the hostname of the server')
     parser.add_argument('--port', type=int, default=9090,
                         help='The port of the PRC')
-    parser.add_argument('--web-port', type=int, default=9190,
+    parser.add_argument('--web-port', type=int, default=8888,
                         help='The port of the http/websocket server')
     parser.add_argument('--example-rpc', type=bool, default=False,
                         help='Whether to switch on example rpc mode')
+    parser.add_argument('--tracker', type=str, default="",
+                        help="Report to RPC tracker")
     args = parser.parse_args()
     logging.basicConfig(level=logging.INFO)
+
+    if args.tracker:
+        url, port = args.tracker.split(":")
+        port = int(port)
+        tracker_addr = (url, port)
+    else:
+        tracker_addr = None
+
     if args.example_rpc:
         index, js_files = find_example_resource()
-        prox = Proxy(args.host, port=args.port,
-                     web_port=args.web_port, index_page=index,
-                     resource_files=js_files)
+        prox = Proxy(args.host,
+                     port=args.port,
+                     web_port=args.web_port,
+                     index_page=index,
+                     resource_files=js_files,
+                     tracker_addr=tracker_addr)
     else:
-        prox = Proxy(args.host, port=args.port, web_port=args.web_port)
+        prox = Proxy(args.host,
+                     port=args.port,
+                     web_port=args.web_port,
+                     tracker_addr=tracker_addr)
     prox.proc.join()
 
 if __name__ == "__main__":
diff --git a/tests/python/contrib/test_rpc_tracker.py b/tests/python/contrib/test_rpc_tracker.py
index 5bcd982bb44c..7668b150e80a 100644
--- a/tests/python/contrib/test_rpc_tracker.py
+++ b/tests/python/contrib/test_rpc_tracker.py
@@ -8,7 +8,7 @@
 def check_server_drop():
     """test when server drops"""
     try:
-        from tvm.contrib.rpc import tracker, base
+        from tvm.contrib.rpc import tracker, proxy, base
         from tvm.contrib.rpc.base import TrackerCode
 
         @tvm.register_func("rpc.test2.addone")
@@ -20,21 +20,24 @@ def _put(tclient, value):
             base.recvjson(tclient._sock)
 
         tserver = tracker.Tracker("localhost", 8888)
+        tproxy = proxy.Proxy("localhost", 8881,
+                             tracker_addr=("localhost", tserver.port))
         tclient = rpc.connect_tracker("localhost", tserver.port)
-
         server1 = rpc.Server(
             "localhost", port=9099,
             tracker_addr=("localhost", tserver.port),
             key="xyz")
         server2 = rpc.Server(
-            "localhost", port=9099,
-            tracker_addr=("localhost", tserver.port),
+            "localhost", tproxy.port, is_proxy=True,
             key="xyz")
+        server3 = rpc.Server(
+            "localhost", tproxy.port, is_proxy=True,
+            key="xyz1")
 
         # Fault tolerence to stale worker value
         _put(tclient, [TrackerCode.PUT, "xyz", (server1.port, "abc")])
         _put(tclient, [TrackerCode.PUT, "xyz", (server1.port, "abcxxx")])
-        _put(tclient, [TrackerCode.PUT, "xyz", (server2.port, "abcxxx11")])
+        _put(tclient, [TrackerCode.PUT, "xyz", (tproxy.port, "abcxxx11")])
 
         # Fault tolerence server timeout
         def check_timeout(timeout, sleeptime):
@@ -60,8 +63,11 @@ def myfunc(remote):
 
         check_timeout(0.01, 0.1)
         check_timeout(2, 0)
-
-
+        tserver.terminate()
+        server2.terminate()
+        server1.terminate()
+        server3.terminate()
+        tproxy.terminate()
     except ImportError:
         print("Skip because tornado is not available")
 

From aac110984007438c60372f02846aec580d39c30a Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Sat, 7 Apr 2018 01:11:48 +0800
Subject: [PATCH 249/949] add query for shared memory size (#1083)

---
 include/tvm/runtime/device_api.h    | 3 ++-
 python/tvm/_ffi/runtime_ctypes.py   | 8 +++++++-
 src/runtime/cuda/cuda_device_api.cc | 5 +++++
 3 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/include/tvm/runtime/device_api.h b/include/tvm/runtime/device_api.h
index ff3fe8062920..f21be567021d 100644
--- a/include/tvm/runtime/device_api.h
+++ b/include/tvm/runtime/device_api.h
@@ -19,7 +19,8 @@ enum DeviceAttrKind : int {
   kExist = 0,
   kMaxThreadsPerBlock = 1,
   kWarpSize = 2,
-  kComputeVersion = 3,
+  kMaxSharedMemoryPerBlock = 3,
+  kComputeVersion = 4,
 };
 
 /*! \brief Number of bytes each allocation must align to */
diff --git a/python/tvm/_ffi/runtime_ctypes.py b/python/tvm/_ffi/runtime_ctypes.py
index 23af85bf26df..2fdde23803b3 100644
--- a/python/tvm/_ffi/runtime_ctypes.py
+++ b/python/tvm/_ffi/runtime_ctypes.py
@@ -140,6 +140,12 @@ def warp_size(self):
         return _api_internal._GetDeviceAttr(
             self.device_type, self.device_id, 2)
 
+    @property
+    def max_shared_memory_per_block(self):
+        """Total amount of shared memory per block in bytes"""
+        return _api_internal._GetDeviceAttr(
+            self.device_type, self.device_id, 3)
+
     @property
     def compute_version(self):
         """Get compute verison number in string.
@@ -152,7 +158,7 @@ def compute_version(self):
             The version string in `major.minor` format.
         """
         return _api_internal._GetDeviceAttr(
-            self.device_type, self.device_id, 3)
+            self.device_type, self.device_id, 4)
 
     def sync(self):
         """Synchronize until jobs finished at the context."""
diff --git a/src/runtime/cuda/cuda_device_api.cc b/src/runtime/cuda/cuda_device_api.cc
index 9b0fee5023f1..8de06d902449 100644
--- a/src/runtime/cuda/cuda_device_api.cc
+++ b/src/runtime/cuda/cuda_device_api.cc
@@ -40,6 +40,11 @@ class CUDADeviceAPI final : public DeviceAPI {
             &value, cudaDevAttrWarpSize, ctx.device_id));
         break;
       }
+      case kMaxSharedMemoryPerBlock: {
+        CUDA_CALL(cudaDeviceGetAttribute(
+            &value, cudaDevAttrMaxSharedMemoryPerBlock, ctx.device_id));
+        break;
+      }
       case kComputeVersion: {
         std::ostringstream os;
         CUDA_CALL(cudaDeviceGetAttribute(

From 10d299e4245716d94e65e0643967571942fe617f Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 6 Apr 2018 14:18:44 -0700
Subject: [PATCH 250/949] [RPC] More robust tracker protocol (#1085)

* [RPC] More robust tracker protocol

* fix normal rpc
---
 python/tvm/contrib/rpc/base.py           |  1 +
 python/tvm/contrib/rpc/client.py         |  2 +-
 python/tvm/contrib/rpc/proxy.py          | 67 ++++++++++++++++++------
 python/tvm/contrib/rpc/server.py         | 32 ++++++++---
 python/tvm/contrib/rpc/tornado_util.py   |  2 +
 python/tvm/contrib/rpc/tracker.py        | 19 +++++--
 tests/python/contrib/test_rpc_tracker.py | 18 ++++++-
 7 files changed, 113 insertions(+), 28 deletions(-)

diff --git a/python/tvm/contrib/rpc/base.py b/python/tvm/contrib/rpc/base.py
index 799d99e53d54..a9fd232f5507 100644
--- a/python/tvm/contrib/rpc/base.py
+++ b/python/tvm/contrib/rpc/base.py
@@ -34,6 +34,7 @@ class TrackerCode(object):
     REQUEST = 4
     UPDATE_INFO = 5
     SUMMARY = 6
+    GET_PENDING_MATCHKEYS = 7
 
 RPC_SESS_MASK = 128
 
diff --git a/python/tvm/contrib/rpc/client.py b/python/tvm/contrib/rpc/client.py
index 1ea94eeb68ed..24b3b9582831 100644
--- a/python/tvm/contrib/rpc/client.py
+++ b/python/tvm/contrib/rpc/client.py
@@ -230,7 +230,7 @@ def request(self, key, priority=1, session_timeout=0, max_retry=5):
                 if value[0] != base.TrackerCode.SUCCESS:
                     raise RuntimeError("Invalid return value %s" % str(value))
                 url, port, matchkey = value[1]
-                return connect(url, port, key + matchkey, session_timeout)
+                return connect(url, port, matchkey, session_timeout)
             except socket.error as err:
                 self.close()
                 last_err = err
diff --git a/python/tvm/contrib/rpc/proxy.py b/python/tvm/contrib/rpc/proxy.py
index 6ae18789f9d2..ed9d89dc9b68 100644
--- a/python/tvm/contrib/rpc/proxy.py
+++ b/python/tvm/contrib/rpc/proxy.py
@@ -14,6 +14,7 @@
 import multiprocessing
 import errno
 import struct
+import time
 
 try:
     import tornado
@@ -45,6 +46,7 @@ def _init_handler(self):
         self.rpc_key = None
         self.match_key = None
         self.forward_proxy = None
+        self.alloc_time = None
 
     def __del__(self):
         logging.info("Delete %s...", self.name())
@@ -237,6 +239,7 @@ def event_handler(_, events):
             self.sock.fileno(), event_handler, self.loop.READ)
         self._client_pool = {}
         self._server_pool = {}
+        self.timeout_alloc = 5
         self.timeout_client = timeout_client
         self.timeout_server = timeout_server
         # tracker information
@@ -245,8 +248,12 @@ def event_handler(_, events):
         self._tracker_conn = None
         self._tracker_pending_puts = []
         self._key_set = set()
+        self.update_tracker_period = 2
         if tracker_addr:
             logging.info("Tracker address:%s", str(tracker_addr))
+            def _callback():
+                self._update_tracker(True)
+            self.loop.call_later(self.update_tracker_period, _callback)
         logging.info("RPCProxy: Websock port bind to %d", web_port)
 
     def _on_event(self, _):
@@ -271,7 +278,22 @@ def _pair_up(self, lhs, rhs):
         rhs.send_data(lhs.rpc_key.encode("utf-8"))
         logging.info("Pairup connect %s  and %s", lhs.name(), rhs.name())
 
-    def _update_tracker(self):
+    def _regenerate_server_keys(self, keys):
+        """Regenerate keys for server pool"""
+        keyset = set(self._server_pool.keys())
+        new_keys = []
+        # re-generate the server match key, so old information is invalidated.
+        for key in keys:
+            rpc_key, _ = key.split(":")
+            handle = self._server_pool[key]
+            del self._server_pool[key]
+            new_key = base.random_key(rpc_key + ":", keyset)
+            self._server_pool[new_key] = handle
+            keyset.add(new_key)
+            new_keys.append(new_key)
+        return new_keys
+
+    def _update_tracker(self, period_update=False):
         """Update information on tracker."""
         try:
             if self._tracker_conn is None:
@@ -285,13 +307,33 @@ def _update_tracker(self):
                 # just connect to tracker, need to update all keys
                 self._tracker_pending_puts = self._server_pool.keys()
 
+            if self._tracker_conn and period_update:
+                # periodically update tracker information
+                # regenerate key if the key is not in tracker anymore
+                # and there is no in-coming connection after timeout_alloc
+                base.sendjson(self._tracker_conn, [TrackerCode.GET_PENDING_MATCHKEYS])
+                pending_keys = set(base.recvjson(self._tracker_conn))
+                update_keys = []
+                for k, v in self._server_pool.items():
+                    if k not in pending_keys:
+                        if v.alloc_time is None:
+                            v.alloc_time = time.time()
+                        elif time.time() - v.alloc_time > self.timeout_alloc:
+                            update_keys.append(k)
+                            v.alloc_time = None
+                if update_keys:
+                    logging.info("RPCProxy: No incoming conn on %s, regenerate keys...",
+                                 str(update_keys))
+                    new_keys = self._regenerate_server_keys(update_keys)
+                    self._tracker_pending_puts += new_keys
+
             need_update_info = False
             # report new connections
             for key in self._tracker_pending_puts:
-                rpc_key, match_key = key.split(":")
+                rpc_key = key.split(":")[0]
                 base.sendjson(self._tracker_conn,
                               [TrackerCode.PUT, rpc_key,
-                               (self._listen_port, ":" + match_key)])
+                               (self._listen_port, key)])
                 assert base.recvjson(self._tracker_conn) == TrackerCode.SUCCESS
                 if rpc_key not in self._key_set:
                     self._key_set.add(rpc_key)
@@ -305,24 +347,17 @@ def _update_tracker(self):
                 assert base.recvjson(self._tracker_conn) == TrackerCode.SUCCESS
             self._tracker_pending_puts = []
         except (socket.error, IOError) as err:
-            retry_period = 5
             logging.info(
                 "Lost tracker connection: %s, try reconnect in %g sec",
-                str(err), retry_period)
+                str(err), self.update_tracker_period)
             self._tracker_conn.close()
             self._tracker_conn = None
-            new_pool = {}
-            keyset = set(self._server_pool.keys())
-            # re-generate the server match key, so old information is invalidated.
-            for key, handle in self._server_pool.items():
-                rpc_key, _ = key.split(":")
-                key = base.random_key(rpc_key + ":", keyset)
-                new_pool[key] = handle
-                keyset.add(key)
-            self._server_pool = new_pool
+            self._regenerate_server_keys(self._server_pool.keys())
+
+        if period_update:
             def _callback():
-                self._update_tracker()
-            self.loop.call_later(retry_period, _callback)
+                self._update_tracker(True)
+            self.loop.call_later(self.update_tracker_period, _callback)
 
     def _handler_ready_tracker_mode(self, handler):
         """tracker mode to handle handler ready."""
diff --git a/python/tvm/contrib/rpc/server.py b/python/tvm/contrib/rpc/server.py
index 18f777dd5ef9..26e377f0524a 100644
--- a/python/tvm/contrib/rpc/server.py
+++ b/python/tvm/contrib/rpc/server.py
@@ -6,7 +6,7 @@
 - Initial handshake to the peer
   - [RPC_MAGIC, keysize(int32), key-bytes]
 - The key is in format
-   - {server|client}:device-type[:matchkey] [-timeout=timeout]
+   - {server|client}:device-type[:random-key] [-timeout=timeout]
 """
 from __future__ import absolute_import
 
@@ -75,7 +75,7 @@ def _parse_server_opt(opts):
 
 def _listen_loop(sock, port, rpc_key, tracker_addr):
     """Lisenting loop of the server master."""
-    def _accept_conn(listen_sock, tracker_conn, ping_period=0.1):
+    def _accept_conn(listen_sock, tracker_conn, ping_period=2):
         """Accept connection from the other places.
 
         Parameters
@@ -89,22 +89,40 @@ def _accept_conn(listen_sock, tracker_conn, ping_period=0.1):
         ping_period : float, optional
             ping tracker every k seconds if no connection is accepted.
         """
+        old_keyset = set()
         # Report resource to tracker
         if tracker_conn:
-            matchkey = base.random_key(":")
+            matchkey = base.random_key(rpc_key + ":")
             base.sendjson(tracker_conn,
                           [TrackerCode.PUT, rpc_key, (port, matchkey)])
             assert base.recvjson(tracker_conn) == TrackerCode.SUCCESS
         else:
-            matchkey = ""
+            matchkey = rpc_key
 
+        unmatch_period_count = 0
+        unmatch_timeout = 4
         # Wait until we get a valid connection
         while True:
             if tracker_conn:
                 trigger = select.select([listen_sock], [], [], ping_period)
                 if not listen_sock in trigger[0]:
-                    base.sendjson(tracker_conn, [TrackerCode.PING])
-                    assert base.recvjson(tracker_conn) == TrackerCode.SUCCESS
+                    base.sendjson(tracker_conn, [TrackerCode.GET_PENDING_MATCHKEYS])
+                    pending_keys = base.recvjson(tracker_conn)
+                    old_keyset.add(matchkey)
+                    # if match key not in pending key set
+                    # it means the key is aqquired by a client but not used.
+                    if matchkey not in pending_keys:
+                        unmatch_period_count += 1
+                    else:
+                        unmatch_period_count = 0
+                    # regenerate match key if key is aqquired but not used for a while
+                    if unmatch_period_count * ping_period > unmatch_timeout + ping_period:
+                        logging.info("RPCServer: no incoming connections, regenerate key ...")
+                        matchkey = base.random_key(rpc_key + ":", old_keyset)
+                        base.sendjson(tracker_conn,
+                                      [TrackerCode.PUT, rpc_key, (port, matchkey)])
+                        assert base.recvjson(tracker_conn) == TrackerCode.SUCCESS
+                        unmatch_period_count = 0
                     continue
             conn, addr = listen_sock.accept()
             magic = struct.unpack("@i", base.recvall(conn, 4))[0]
@@ -114,7 +132,7 @@ def _accept_conn(listen_sock, tracker_conn, ping_period=0.1):
             keylen = struct.unpack("@i", base.recvall(conn, 4))[0]
             key = py_str(base.recvall(conn, keylen))
             arr = key.split()
-            expect_header = "client:" + rpc_key + matchkey
+            expect_header = "client:" + matchkey
             server_key = "server:" + rpc_key
             if arr[0] != expect_header:
                 conn.sendall(struct.pack("@i", base.RPC_CODE_MISMATCH))
diff --git a/python/tvm/contrib/rpc/tornado_util.py b/python/tvm/contrib/rpc/tornado_util.py
index f2dbb4aadb26..00e1fd13865b 100644
--- a/python/tvm/contrib/rpc/tornado_util.py
+++ b/python/tvm/contrib/rpc/tornado_util.py
@@ -48,6 +48,8 @@ def close(self):
 
     def write_message(self, message, binary=True):
         assert binary
+        if self._sock is None:
+            raise IOError("socket is already closed")
         self._pending_write.append(message)
         self._update_write()
 
diff --git a/python/tvm/contrib/rpc/tracker.py b/python/tvm/contrib/rpc/tracker.py
index 1049fd76dc3b..5dd425ea954a 100644
--- a/python/tvm/contrib/rpc/tracker.py
+++ b/python/tvm/contrib/rpc/tracker.py
@@ -92,7 +92,9 @@ def _schedule(self):
             value = self._values.pop(0)
             item = heapq.heappop(self._requests)
             callback = item[-1]
-            if not callback(value):
+            if callback(value[1:]):
+                value[0].pending_matchkeys.remove(value[-1])
+            else:
                 self._values.append(value)
 
     def put(self, value):
@@ -124,6 +126,8 @@ def __init__(self, tracker, sock, addr):
         self._addr = addr
         self._init_req_nbytes = 4
         self._info = {"addr": addr}
+        # list of pending match keys that has not been used.
+        self.pending_matchkeys = set()
         self._tracker._connections.add(self)
 
     def name(self):
@@ -189,18 +193,27 @@ def call_handler(self, args):
         if code == TrackerCode.PUT:
             key = args[1]
             port, matchkey = args[2]
-            self._tracker.put(key, (self._addr[0], port, matchkey))
+            self.pending_matchkeys.add(matchkey)
+            self._tracker.put(key, (self, self._addr[0], port, matchkey))
             self.ret_value(TrackerCode.SUCCESS)
         elif code == TrackerCode.REQUEST:
             key = args[1]
             user = args[2]
             priority = args[3]
             def _cb(value):
-                self.ret_value([TrackerCode.SUCCESS, value])
+                # if the connection is already closed
+                if not self._sock:
+                    return False
+                try:
+                    self.ret_value([TrackerCode.SUCCESS, value])
+                except (socket.sock_error, IOError):
+                    return False
                 return True
             self._tracker.request(key, user, priority, _cb)
         elif code == TrackerCode.PING:
             self.ret_value(TrackerCode.SUCCESS)
+        elif code == TrackerCode.GET_PENDING_MATCHKEYS:
+            self.ret_value(list(self.pending_matchkeys))
         elif code == TrackerCode.STOP:
             # safe stop tracker
             if self._tracker._stop_key == args[1]:
diff --git a/tests/python/contrib/test_rpc_tracker.py b/tests/python/contrib/test_rpc_tracker.py
index 7668b150e80a..1ff23dbd4868 100644
--- a/tests/python/contrib/test_rpc_tracker.py
+++ b/tests/python/contrib/test_rpc_tracker.py
@@ -23,6 +23,11 @@ def _put(tclient, value):
         tproxy = proxy.Proxy("localhost", 8881,
                              tracker_addr=("localhost", tserver.port))
         tclient = rpc.connect_tracker("localhost", tserver.port)
+
+        server0 = rpc.Server(
+            "localhost", port=9099,
+            tracker_addr=("localhost", tserver.port),
+            key="abc")
         server1 = rpc.Server(
             "localhost", port=9099,
             tracker_addr=("localhost", tserver.port),
@@ -34,6 +39,10 @@ def _put(tclient, value):
             "localhost", tproxy.port, is_proxy=True,
             key="xyz1")
 
+        # Fault tolerence to un-handled requested value
+        _put(tclient, [TrackerCode.REQUEST, "abc", "", 1])
+        _put(tclient, [TrackerCode.REQUEST, "xyz1", "", 1])
+
         # Fault tolerence to stale worker value
         _put(tclient, [TrackerCode.PUT, "xyz", (server1.port, "abc")])
         _put(tclient, [TrackerCode.PUT, "xyz", (server1.port, "abcxxx")])
@@ -58,14 +67,21 @@ def myfunc(remote):
                 assert f1(10) == 11
                 f1 = remote2.get_function("rpc.test2.addone")
                 assert f1(10) == 11
+
             except tvm.TVMError as e:
                 pass
+            remote3 = tclient.request("abc")
+            f1 = remote3.get_function("rpc.test2.addone")
+            remote3 = tclient.request("xyz1")
+            f1 = remote3.get_function("rpc.test2.addone")
+            assert f1(10) == 11
 
         check_timeout(0.01, 0.1)
         check_timeout(2, 0)
         tserver.terminate()
-        server2.terminate()
+        server0.terminate()
         server1.terminate()
+        server2.terminate()
         server3.terminate()
         tproxy.terminate()
     except ImportError:

From 1c55f8556751ee48b82a421900b8fa9eacf477d7 Mon Sep 17 00:00:00 2001
From: Leyuan Wang <laurawly@gmail.com>
Date: Fri, 6 Apr 2018 20:48:43 -0700
Subject: [PATCH 251/949] Intel target added, sub group sync added (#1084)

---
 include/tvm/build_module.h    |  4 ++++
 python/tvm/target.py          | 14 ++++++++++++++
 src/codegen/build_module.cc   | 10 ++++++++--
 src/codegen/codegen_opencl.cc |  3 ++-
 4 files changed, 28 insertions(+), 3 deletions(-)

diff --git a/include/tvm/build_module.h b/include/tvm/build_module.h
index dc724cc49ceb..f8a20ddaefac 100644
--- a/include/tvm/build_module.h
+++ b/include/tvm/build_module.h
@@ -154,6 +154,10 @@ EXPORT Target rasp(const std::vector<std::string>& options =
 EXPORT Target mali(const std::vector<std::string>& options =
                    std::vector<std::string>());
 
+/*! \return A target for Intel GPU */
+EXPORT Target intel_gpu(const std::vector<std::string>& options =
+                   std::vector<std::string>());
+
 /*! \return A target for stackvm */
 EXPORT Target stackvm(const std::vector<std::string>& options =
                       std::vector<std::string>());
diff --git a/python/tvm/target.py b/python/tvm/target.py
index 869b3f10bce3..3ca72bafdc85 100644
--- a/python/tvm/target.py
+++ b/python/tvm/target.py
@@ -76,6 +76,7 @@ class Target(NodeBase):
     - :any:`tvm.target.cuda` create CUDA target
     - :any:`tvm.target.rocm` create ROCM target
     - :any:`tvm.target.mali` create Mali target
+    - :any:`tvm.target.intel_gpu` create Intel GPU target
     """
     def __init__(self, handle):
         super(Target, self).__init__(handle)
@@ -397,6 +398,19 @@ def mali(options=None):
     return _api_internal._TargetCreate("opencl", *opts)
 
 
+def intel_gpu(options=None):
+    """Returns an Intel GPU target.
+
+    Parameters
+    ----------
+    options : str or list of str
+        Additional options
+    """
+    opts = ["-device=intel_gpu"]
+    opts = _merge_opts(opts, options)
+    return _api_internal._TargetCreate("opencl", *opts)
+
+
 def opengl(options=None):
     """Returns a OpenGL target.
 
diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc
index ceeb3c0b1110..e11ee773f1e0 100644
--- a/src/codegen/build_module.cc
+++ b/src/codegen/build_module.cc
@@ -74,6 +74,9 @@ Target CreateTarget(const std::string& target_name,
     t->keys_array.push_back(ir::StringImm::make("rocm"));
     t->keys_array.push_back(ir::StringImm::make("gpu"));
     t->max_num_threads = 256;
+    if (device_name == "intel_gpu") {
+      t->thread_warp_size = 16;
+    }
   } else if (target_name == "metal" || target_name == "vulkan") {
     t->device_type = static_cast<int>(target_name == "metal" ? kDLMetal : kDLVulkan);
     t->keys_array.push_back(ir::StringImm::make(target_name));
@@ -182,8 +185,6 @@ Target Target::create(const std::string& target_str) {
 
   if (device_name == "rasp") {
     return target::rasp(options);
-  } else if (device_name == "mail") {
-    return target::mali(options);
   } else {
     return CreateTarget(target_name, options);
   }
@@ -264,6 +265,11 @@ Target mali(const std::vector<std::string>& options) {
   }));
 }
 
+Target intel_gpu(const std::vector<std::string>& options) {
+  return CreateTarget("opencl", MergeOptions(options, {
+    "-device=intel_gpu"
+  }));
+}
 
 Target stackvm(const std::vector<std::string>& options) {
   return CreateTarget("stackvm", options);
diff --git a/src/codegen/codegen_opencl.cc b/src/codegen/codegen_opencl.cc
index 03f4acdd057c..dd8bf7081a25 100644
--- a/src/codegen/codegen_opencl.cc
+++ b/src/codegen/codegen_opencl.cc
@@ -157,7 +157,8 @@ void CodeGenOpenCL::PrintVecStore(const Variable* buffer,
 void CodeGenOpenCL::PrintStorageSync(const Call* op) {
   const std::string& sync = op->args[0].as<StringImm>()->value;
   if (sync == "warp") {
-    LOG(FATAL) << "warp sync not supported in opencl";
+    this->PrintIndent();
+    this->stream << "sub_group_barrier(CLK_LOCAL_MEM_FENCE);\n";
   } else if (sync == "shared") {
     this->PrintIndent();
     this->stream << "barrier(CLK_LOCAL_MEM_FENCE);\n";

From 20c06f2e5e1c7ca2fdd834087bba38c0dde0b5ca Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 7 Apr 2018 11:27:21 -0700
Subject: [PATCH 252/949] [RPC] safe destructor when remote server get killed
 (#1086)

---
 src/runtime/rpc/rpc_device_api.cc |  8 ++++++--
 src/runtime/rpc/rpc_module.cc     | 12 ++++++++++--
 2 files changed, 16 insertions(+), 4 deletions(-)

diff --git a/src/runtime/rpc/rpc_device_api.cc b/src/runtime/rpc/rpc_device_api.cc
index bd222b5c272e..1eff8e7466d3 100644
--- a/src/runtime/rpc/rpc_device_api.cc
+++ b/src/runtime/rpc/rpc_device_api.cc
@@ -34,8 +34,12 @@ class RPCDeviceAPI final : public DeviceAPI {
   }
   void FreeDataSpace(TVMContext ctx, void* ptr) final {
     RemoteSpace* space = static_cast<RemoteSpace*>(ptr);
-    GetSess(ctx)->CallRemote(
-        RPCCode::kDevFreeData, ctx, space->data);
+    try {
+      GetSess(ctx)->CallRemote(
+          RPCCode::kDevFreeData, ctx, space->data);
+    } catch (const dmlc::Error& e) {
+      // fault tolerance to remote close.
+    }
     delete space;
   }
   void CopyDataFromTo(const void* from,
diff --git a/src/runtime/rpc/rpc_module.cc b/src/runtime/rpc/rpc_module.cc
index 81cb874f6c27..0714261bba8f 100644
--- a/src/runtime/rpc/rpc_module.cc
+++ b/src/runtime/rpc/rpc_module.cc
@@ -26,7 +26,11 @@ struct RPCWrappedFunc {
     sess_->CallFunc(handle_, args, rv, &fwrap_);
   }
   ~RPCWrappedFunc() {
-    sess_->CallRemote(RPCCode::kFreeFunc, handle_);
+    try {
+      sess_->CallRemote(RPCCode::kFreeFunc, handle_);
+    } catch (const dmlc::Error& e) {
+      // fault tolerance to remote close
+    }
   }
 
   static void WrapRemote(std::shared_ptr<RPCSession> sess,
@@ -48,7 +52,11 @@ class RPCModuleNode final : public ModuleNode {
   }
   ~RPCModuleNode() {
     if (module_handle_ != nullptr) {
-      sess_->CallRemote(RPCCode::kModuleFree, module_handle_);
+      try {
+        sess_->CallRemote(RPCCode::kModuleFree, module_handle_);
+      } catch (const dmlc::Error& e) {
+        // fault tolerance to remote close
+      }
       module_handle_ = nullptr;
     }
   }

From 1df5e4c8b6789ba454433e3b3f55d26d269277ad Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 7 Apr 2018 20:56:00 -0700
Subject: [PATCH 253/949] Update halideIR, add more device query for shared
 memory (#1087)

---
 HalideIR                                |  2 +-
 src/codegen/build_module.cc             | 19 +++++++++++++++----
 src/runtime/metal/metal_device_api.mm   |  1 +
 src/runtime/opencl/opencl_device_api.cc | 14 +++++++++++---
 src/runtime/rocm/rocm_device_api.cc     |  1 +
 src/runtime/vulkan/vulkan_device_api.cc |  7 +++++++
 tests/cpp/expr_test.cc                  |  9 +++++++++
 7 files changed, 45 insertions(+), 8 deletions(-)

diff --git a/HalideIR b/HalideIR
index e20e5e9abb3a..a3698398faff 160000
--- a/HalideIR
+++ b/HalideIR
@@ -1 +1 @@
-Subproject commit e20e5e9abb3aa43147a90a4ffb3e190f62862970
+Subproject commit a3698398faff7fec1c0fa4e4479357651382db75
diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc
index e11ee773f1e0..352644a6f0c9 100644
--- a/src/codegen/build_module.cc
+++ b/src/codegen/build_module.cc
@@ -70,7 +70,11 @@ Target CreateTarget(const std::string& target_name,
     t->thread_warp_size = 32;
   } else if (target_name == "rocm" || target_name == "opencl") {
     // For now assume rocm schedule for opencl
-    t->device_type = static_cast<int>(target_name == "rocm" ? kDLROCM : kDLOpenCL);
+    if (target_name == "opencl") {
+      t->device_type = kDLOpenCL;
+    } else {
+      t->device_type = kDLROCM;
+    }
     t->keys_array.push_back(ir::StringImm::make("rocm"));
     t->keys_array.push_back(ir::StringImm::make("gpu"));
     t->max_num_threads = 256;
@@ -78,14 +82,21 @@ Target CreateTarget(const std::string& target_name,
       t->thread_warp_size = 16;
     }
   } else if (target_name == "metal" || target_name == "vulkan") {
-    t->device_type = static_cast<int>(target_name == "metal" ? kDLMetal : kDLVulkan);
+    if (target_name == "metal") {
+      t->device_type = kDLMetal;
+    } else {
+      t->device_type = kDLVulkan;
+    }
     t->keys_array.push_back(ir::StringImm::make(target_name));
     t->keys_array.push_back(ir::StringImm::make("gpu"));
     t->max_num_threads = 256;
   } else if (target_name == "opengl") {
-    t->device_type = kDLGPU;
+    t->device_type = kOpenGL;
     t->keys_array.push_back(ir::StringImm::make("opengl"));
-  } else if (target_name == "stackvm" || target_name == "ext_dev") {
+  } else if (target_name == "stackvm") {
+    t->device_type = kDLCPU;
+  } else if (target_name == "ext_dev") {
+    t->device_type = kExtDev;
   } else {
     LOG(ERROR) << "Unknown target name " << target_name;
     return target::stackvm();
diff --git a/src/runtime/metal/metal_device_api.mm b/src/runtime/metal/metal_device_api.mm
index 1768d6334b5c..3e055cd0079c 100644
--- a/src/runtime/metal/metal_device_api.mm
+++ b/src/runtime/metal/metal_device_api.mm
@@ -39,6 +39,7 @@
       *rv = 1;
       break;
     }
+    case kMaxSharedMemoryPerBlock: return;
     case kComputeVersion: return;
     case kExist: break;
   }
diff --git a/src/runtime/opencl/opencl_device_api.cc b/src/runtime/opencl/opencl_device_api.cc
index 6341c9f4b83d..1fb52b945f9a 100644
--- a/src/runtime/opencl/opencl_device_api.cc
+++ b/src/runtime/opencl/opencl_device_api.cc
@@ -32,9 +32,9 @@ void OpenCLWorkspace::GetAttr(
   }
   CHECK_LT(index, devices.size())
       << "Invalid device id " << index;
-  size_t value;
   switch (kind) {
     case kMaxThreadsPerBlock: {
+      size_t value;
       OPENCL_CALL(clGetDeviceInfo(
           devices[index],  CL_DEVICE_MAX_WORK_GROUP_SIZE,
           sizeof(size_t), &value, nullptr));
@@ -45,8 +45,16 @@ void OpenCLWorkspace::GetAttr(
       *rv = 1;
       break;
     }
-  case kComputeVersion: return;
-  case kExist: break;
+    case kMaxSharedMemoryPerBlock: {
+      cl_ulong value;
+      OPENCL_CALL(clGetDeviceInfo(
+          devices[index], CL_DEVICE_LOCAL_MEM_SIZE,
+          sizeof(cl_ulong), &value, nullptr));
+      *rv = static_cast<int64_t>(value);
+      break;
+    }
+    case kComputeVersion: return;
+    case kExist: break;
   }
 }
 
diff --git a/src/runtime/rocm/rocm_device_api.cc b/src/runtime/rocm/rocm_device_api.cc
index 6a950069192c..a1d77b66c251 100644
--- a/src/runtime/rocm/rocm_device_api.cc
+++ b/src/runtime/rocm/rocm_device_api.cc
@@ -44,6 +44,7 @@ class ROCMDeviceAPI final : public DeviceAPI {
         value = 64;
         break;
       }
+      case kMaxSharedMemoryPerBlock: return;
       case kComputeVersion: {
         hipDeviceProp_t prop;
         ROCM_CALL(hipGetDeviceProperties(&prop, ctx.device_id));
diff --git a/src/runtime/vulkan/vulkan_device_api.cc b/src/runtime/vulkan/vulkan_device_api.cc
index ef97e99431c2..03de48f86fab 100644
--- a/src/runtime/vulkan/vulkan_device_api.cc
+++ b/src/runtime/vulkan/vulkan_device_api.cc
@@ -51,6 +51,13 @@ void VulkanWorkspace::GetAttr(
       *rv = value;
       break;
     }
+    case kMaxSharedMemoryPerBlock: {
+      VkPhysicalDeviceProperties phy_prop;
+      vkGetPhysicalDeviceProperties(context_[ctx.device_id].phy_device, &phy_prop);
+      int64_t value = phy_prop.limits.maxComputeSharedMemorySize;
+      *rv = value;
+      break;
+    }
     case kWarpSize: {
       *rv = 1;
       break;
diff --git a/tests/cpp/expr_test.cc b/tests/cpp/expr_test.cc
index fb8685695013..9cdfef7f6a01 100644
--- a/tests/cpp/expr_test.cc
+++ b/tests/cpp/expr_test.cc
@@ -15,6 +15,15 @@ TEST(Expr, Basic) {
 }
 
 
+TEST(ExprNodeRef, Basic) {
+  using namespace tvm;
+  Var x("x");
+  Expr z = max(x + 1 + 2, 100);
+  const ir::Max* op = z.as<ir::Max>();
+  CHECK(op->GetNodeRef().same_as(z));
+}
+
+
 int main(int argc, char ** argv) {
   testing::InitGoogleTest(&argc, argv);
   testing::FLAGS_gtest_death_test_style = "threadsafe";

From 8ef4fdef229fcce37f9c71bd4b916811471fea9d Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 7 Apr 2018 21:12:49 -0700
Subject: [PATCH 254/949] Make target and build module more pythonic (#1089)

---
 include/tvm/build_module.h                |  3 +++
 python/tvm/build_module.py                | 15 +++++++++++----
 python/tvm/contrib/rpc/server.py          |  5 +++--
 src/codegen/build_module.cc               | 10 ++++------
 tests/python/unittest/test_lang_target.py |  4 ++++
 5 files changed, 25 insertions(+), 12 deletions(-)

diff --git a/include/tvm/build_module.h b/include/tvm/build_module.h
index f8a20ddaefac..9c54ab7b45fb 100644
--- a/include/tvm/build_module.h
+++ b/include/tvm/build_module.h
@@ -24,6 +24,8 @@ class TargetNode : public Node {
  public:
   /*! \brief The name of the target device */
   std::string target_name;
+  /*! \brief The name of the target device */
+  std::string device_name;
   /*! \brief The type of the target device */
   int device_type;
   /*! \brief The maximum threads that a schedule should use for this device */
@@ -42,6 +44,7 @@ class TargetNode : public Node {
 
   void VisitAttrs(AttrVisitor* v) final {
     v->Visit("target_name", &target_name);
+    v->Visit("device_name", &device_name);
     v->Visit("device_type", &device_type);
     v->Visit("max_num_threads", &max_num_threads);
     v->Visit("thread_warp_size", &thread_warp_size);
diff --git a/python/tvm/build_module.py b/python/tvm/build_module.py
index 5666cf6e23b0..ffb9bd4c7844 100755
--- a/python/tvm/build_module.py
+++ b/python/tvm/build_module.py
@@ -150,6 +150,13 @@ def add_lower_pass(self):
             result += [(phase, func)]
         return result
 
+    @add_lower_pass.setter
+    def add_lower_pass(self, value):
+        add_lower_pass_args = []
+        for x in value:
+            add_lower_pass_args += [x[0], x[1]]
+        _api_internal._BuildConfigSetAddLowerPass(self, *add_lower_pass_args)
+
     def __enter__(self):
         # pylint: disable=protected-access
         _api_internal._EnterBuildConfigScope(self)
@@ -168,9 +175,12 @@ def __setattr__(self, name, value):
                 "'%s' object cannot set attribute '%s'" % (str(type(self)), name))
         return super(BuildConfig, self).__setattr__(name, value)
 
+
 def current_build_config():
+    """Get the current build configuration."""
     return _api_internal._GetCurrentBuildConfig()
 
+
 def build_config(**kwargs):
     """Configure the build behavior by setting config variables.
 
@@ -230,10 +240,7 @@ def build_config(**kwargs):
     config = make.node("BuildConfig", **node_args)
 
     if "add_lower_pass" in kwargs:
-        add_lower_pass_args = []
-        for x in kwargs["add_lower_pass"]:
-            add_lower_pass_args += [x[0], x[1]]
-        _api_internal._BuildConfigSetAddLowerPass(config, *add_lower_pass_args)
+        config.add_lower_pass = kwargs["add_lower_pass"]
 
     return config
 
diff --git a/python/tvm/contrib/rpc/server.py b/python/tvm/contrib/rpc/server.py
index 26e377f0524a..8cd1c4ee883f 100644
--- a/python/tvm/contrib/rpc/server.py
+++ b/python/tvm/contrib/rpc/server.py
@@ -166,8 +166,9 @@ def _accept_conn(listen_sock, tracker_conn, ping_period=2):
             conn, addr, opts = _accept_conn(sock, tracker_conn)
         except (socket.error, IOError):
             # retry when tracker is dropped
-            tracker_conn.close()
-            tracker_conn = None
+            if tracker_conn:
+                tracker_conn.close()
+                tracker_conn = None
             continue
 
         # step 3: serving
diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc
index 352644a6f0c9..318e231fc67c 100644
--- a/src/codegen/build_module.cc
+++ b/src/codegen/build_module.cc
@@ -37,8 +37,6 @@ Target CreateTarget(const std::string& target_name,
 
   t->target_name = target_name;
 
-  std::string device_name = "";
-
   std::string libs_flag = "-libs=";
   std::string device_flag = "-device=";
   for (auto& item : options) {
@@ -51,12 +49,12 @@ Target CreateTarget(const std::string& target_name,
         t->libs_array.push_back(ir::StringImm::make(lib_item));
       }
     } else if (item.find(device_flag) == 0) {
-      device_name = item.substr(device_flag.length());
+      t->device_name = item.substr(device_flag.length());
     }
   }
 
-  if (device_name.length() > 0) {
-    t->keys_array.push_back(ir::StringImm::make(device_name));
+  if (t->device_name.length() > 0) {
+    t->keys_array.push_back(ir::StringImm::make(t->device_name));
   }
   t->device_type = kDLCPU;
   t->thread_warp_size = 1;
@@ -78,7 +76,7 @@ Target CreateTarget(const std::string& target_name,
     t->keys_array.push_back(ir::StringImm::make("rocm"));
     t->keys_array.push_back(ir::StringImm::make("gpu"));
     t->max_num_threads = 256;
-    if (device_name == "intel_gpu") {
+    if (t->device_name == "intel_gpu") {
       t->thread_warp_size = 16;
     }
   } else if (target_name == "metal" || target_name == "vulkan") {
diff --git a/tests/python/unittest/test_lang_target.py b/tests/python/unittest/test_lang_target.py
index ab9c51394dfc..71b4f79a58a2 100644
--- a/tests/python/unittest/test_lang_target.py
+++ b/tests/python/unittest/test_lang_target.py
@@ -36,6 +36,7 @@ def test_target_dispatch():
 
     assert tvm.target.current_target() == None
 
+
 def test_target_string_parse():
     target = tvm.target.create("cuda -libs=cublas,cudnn")
 
@@ -45,6 +46,9 @@ def test_target_string_parse():
     assert target.libs == ['cublas', 'cudnn']
     assert str(target) == str(tvm.target.cuda("-libs=cublas,cudnn"))
 
+
+    assert tvm.target.intel_gpu().device_name == "intel_gpu"
+
 if __name__ == "__main__":
     test_target_dispatch()
     test_target_string_parse()

From 1df3640b683f261b054cc46013c1a55c0d642bd7 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 8 Apr 2018 17:55:13 -0700
Subject: [PATCH 255/949] [RPC] callback option rpc server starts (#1092)

---
 src/runtime/rpc/rpc_session.cc | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/runtime/rpc/rpc_session.cc b/src/runtime/rpc/rpc_session.cc
index 2c38294b967d..923194cf4f1b 100644
--- a/src/runtime/rpc/rpc_session.cc
+++ b/src/runtime/rpc/rpc_session.cc
@@ -783,6 +783,9 @@ void RPCSession::Shutdown() {
 
 void RPCSession::ServerLoop() {
   std::lock_guard<std::recursive_mutex> lock(mutex_);
+  if (const auto* f = Registry::Get("tvm.contrib.rpc.server.start")) {
+    (*f)();
+  }
   TVMRetValue rv;
   CHECK(HandleUntilReturnEvent(&rv, false, nullptr) == RPCCode::kShutdown);
   LOG(INFO) << "Shutdown...";

From 9b2c643d87ece937c71ecb40b797565294077564 Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Tue, 10 Apr 2018 01:15:28 +0800
Subject: [PATCH 256/949] add device name to context attribute (#1090)

* add device name to context attribute

* update for other backends
---
 include/tvm/runtime/device_api.h        | 1 +
 python/tvm/_ffi/runtime_ctypes.py       | 8 +++++++-
 src/runtime/cuda/cuda_device_api.cc     | 8 +++++++-
 src/runtime/metal/metal_device_api.mm   | 1 +
 src/runtime/opencl/opencl_device_api.cc | 8 ++++++++
 src/runtime/opengl/opengl_device_api.cc | 2 ++
 src/runtime/rocm/rocm_device_api.cc     | 1 +
 src/runtime/vulkan/vulkan_device_api.cc | 1 +
 8 files changed, 28 insertions(+), 2 deletions(-)

diff --git a/include/tvm/runtime/device_api.h b/include/tvm/runtime/device_api.h
index f21be567021d..aa8b4322368a 100644
--- a/include/tvm/runtime/device_api.h
+++ b/include/tvm/runtime/device_api.h
@@ -21,6 +21,7 @@ enum DeviceAttrKind : int {
   kWarpSize = 2,
   kMaxSharedMemoryPerBlock = 3,
   kComputeVersion = 4,
+  kDeviceName = 5
 };
 
 /*! \brief Number of bytes each allocation must align to */
diff --git a/python/tvm/_ffi/runtime_ctypes.py b/python/tvm/_ffi/runtime_ctypes.py
index 2fdde23803b3..3e947cb6366c 100644
--- a/python/tvm/_ffi/runtime_ctypes.py
+++ b/python/tvm/_ffi/runtime_ctypes.py
@@ -142,7 +142,7 @@ def warp_size(self):
 
     @property
     def max_shared_memory_per_block(self):
-        """Total amount of shared memory per block in bytes"""
+        """Total amount of shared memory per block in bytes."""
         return _api_internal._GetDeviceAttr(
             self.device_type, self.device_id, 3)
 
@@ -160,6 +160,12 @@ def compute_version(self):
         return _api_internal._GetDeviceAttr(
             self.device_type, self.device_id, 4)
 
+    @property
+    def device_name(self):
+        """Return the string name of device."""
+        return _api_internal._GetDeviceAttr(
+            self.device_type, self.device_id, 5)
+
     def sync(self):
         """Synchronize until jobs finished at the context."""
         check_call(_LIB.TVMSynchronize(self.device_type, self.device_id, None))
diff --git a/src/runtime/cuda/cuda_device_api.cc b/src/runtime/cuda/cuda_device_api.cc
index 8de06d902449..fe13e466ba80 100644
--- a/src/runtime/cuda/cuda_device_api.cc
+++ b/src/runtime/cuda/cuda_device_api.cc
@@ -30,7 +30,7 @@ class CUDADeviceAPI final : public DeviceAPI {
                 &value, cudaDevAttrMaxThreadsPerBlock, ctx.device_id)
             == cudaSuccess);
         break;
-    case kMaxThreadsPerBlock: {
+      case kMaxThreadsPerBlock: {
         CUDA_CALL(cudaDeviceGetAttribute(
             &value, cudaDevAttrMaxThreadsPerBlock, ctx.device_id));
         break;
@@ -56,6 +56,12 @@ class CUDADeviceAPI final : public DeviceAPI {
         *rv = os.str();
         return;
       }
+      case kDeviceName: {
+        cudaDeviceProp props;
+        CUDA_CALL(cudaGetDeviceProperties(&props, ctx.device_id));
+        *rv = std::string(props.name);
+        return;
+      }
     }
     *rv = value;
   }
diff --git a/src/runtime/metal/metal_device_api.mm b/src/runtime/metal/metal_device_api.mm
index 3e055cd0079c..8a2d1fb37069 100644
--- a/src/runtime/metal/metal_device_api.mm
+++ b/src/runtime/metal/metal_device_api.mm
@@ -41,6 +41,7 @@
     }
     case kMaxSharedMemoryPerBlock: return;
     case kComputeVersion: return;
+    case kDeviceName: return;
     case kExist: break;
   }
 }
diff --git a/src/runtime/opencl/opencl_device_api.cc b/src/runtime/opencl/opencl_device_api.cc
index 1fb52b945f9a..40f34a652830 100644
--- a/src/runtime/opencl/opencl_device_api.cc
+++ b/src/runtime/opencl/opencl_device_api.cc
@@ -54,6 +54,14 @@ void OpenCLWorkspace::GetAttr(
       break;
     }
     case kComputeVersion: return;
+    case kDeviceName: {
+      char value[128] = {0};
+      OPENCL_CALL(clGetDeviceInfo(
+          devices[index], CL_DEVICE_NAME,
+          sizeof(value) - 1, value, nullptr));
+      *rv = std::string(value);
+      break;
+    }
     case kExist: break;
   }
 }
diff --git a/src/runtime/opengl/opengl_device_api.cc b/src/runtime/opengl/opengl_device_api.cc
index c47446388836..0c6354e231cf 100644
--- a/src/runtime/opengl/opengl_device_api.cc
+++ b/src/runtime/opengl/opengl_device_api.cc
@@ -93,9 +93,11 @@ void OpenGLWorkspace::GetAttr(
       *rv = 1;
       break;
     }
+    case kMaxSharedMemoryPerBlock: return;
     case kComputeVersion: {
       break;
     }
+    case kDeviceName: return;
   }
 }
 
diff --git a/src/runtime/rocm/rocm_device_api.cc b/src/runtime/rocm/rocm_device_api.cc
index a1d77b66c251..256c715e92b8 100644
--- a/src/runtime/rocm/rocm_device_api.cc
+++ b/src/runtime/rocm/rocm_device_api.cc
@@ -51,6 +51,7 @@ class ROCMDeviceAPI final : public DeviceAPI {
         *rv = prop.gcnArch;
         return;
       }
+      case kDeviceName: return;
     }
     *rv = value;
   }
diff --git a/src/runtime/vulkan/vulkan_device_api.cc b/src/runtime/vulkan/vulkan_device_api.cc
index 03de48f86fab..ce2cea65b5cd 100644
--- a/src/runtime/vulkan/vulkan_device_api.cc
+++ b/src/runtime/vulkan/vulkan_device_api.cc
@@ -73,6 +73,7 @@ void VulkanWorkspace::GetAttr(
       *rv = os.str();
       break;
     }
+    case kDeviceName: return;
     case kExist: break;
   }
 }

From cdfdda4e01d90f6ded9208ff2de9cc699f1beea8 Mon Sep 17 00:00:00 2001
From: nhynes <nhynes@berkeley.edu>
Date: Mon, 9 Apr 2018 22:24:19 -0700
Subject: [PATCH 257/949] Add SGXModule (#1019)

---
 Makefile                                      |  31 +++
 apps/sgx/.gitignore                           |   1 -
 apps/sgx/Makefile                             |  49 +----
 apps/sgx/README.md                            |   2 +-
 apps/sgx/app.cc                               | 137 ------------
 apps/sgx/enclave.cc                           |  62 ------
 apps/sgx/enclave_config.xml                   |   4 +-
 apps/sgx/prepare_test_libs.py                 |   2 +-
 apps/sgx/run_example.sh                       |   4 +-
 apps/sgx/test_addone.edl                      |   7 -
 apps/sgx/test_addone.py                       |  13 ++
 apps/sgx/tvm_runtime_pack.cc                  |  15 --
 include/tvm/runtime/registry.h                |   2 +-
 make/config.mk                                |   4 +
 sgx/runtime_t.cc                              |  17 --
 sgx/runtime_u.cc                              |  36 ---
 sgx/tvm.edl                                   |  15 --
 src/codegen/llvm/llvm_module.cc               |   8 +-
 src/runtime/c_runtime_api.cc                  |   7 +
 src/runtime/file_util.cc                      |  19 ++
 src/runtime/file_util.h                       |  13 ++
 src/runtime/sgx/common.h                      |  23 ++
 src/runtime/sgx/trusted/ecall_registry.h      |  91 ++++++++
 src/runtime/sgx/trusted/runtime.cc            |  75 +++++++
 src/runtime/sgx/trusted/runtime.h             |  43 ++++
 .../runtime/sgx/trusted}/threading_backend.cc |  25 +--
 src/runtime/sgx/tvm.edl                       |  28 +++
 src/runtime/sgx/untrusted/sgx_module.cc       | 207 ++++++++++++++++++
 src/runtime/system_lib_module.cc              |  14 +-
 src/runtime/thread_pool.cc                    |   4 +
 30 files changed, 591 insertions(+), 367 deletions(-)
 delete mode 100644 apps/sgx/app.cc
 delete mode 100644 apps/sgx/enclave.cc
 delete mode 100644 apps/sgx/test_addone.edl
 create mode 100644 apps/sgx/test_addone.py
 delete mode 100644 apps/sgx/tvm_runtime_pack.cc
 delete mode 100644 sgx/runtime_t.cc
 delete mode 100644 sgx/runtime_u.cc
 delete mode 100644 sgx/tvm.edl
 create mode 100644 src/runtime/sgx/common.h
 create mode 100644 src/runtime/sgx/trusted/ecall_registry.h
 create mode 100644 src/runtime/sgx/trusted/runtime.cc
 create mode 100644 src/runtime/sgx/trusted/runtime.h
 rename {sgx => src/runtime/sgx/trusted}/threading_backend.cc (74%)
 create mode 100644 src/runtime/sgx/tvm.edl
 create mode 100644 src/runtime/sgx/untrusted/sgx_module.cc

diff --git a/Makefile b/Makefile
index 55db629f44d5..02017b656906 100644
--- a/Makefile
+++ b/Makefile
@@ -58,6 +58,7 @@ ROCM_SRC = $(wildcard src/runtime/rocm/*.cc)
 OPENCL_SRC = $(wildcard src/runtime/opencl/*.cc)
 OPENGL_SRC = $(wildcard src/runtime/opengl/*.cc)
 VULKAN_SRC = $(wildcard src/runtime/vulkan/*.cc)
+SGX_SRC = $(wildcard src/runtime/sgx/untrusted/*.cc)
 RPC_SRC = $(wildcard src/runtime/rpc/*.cc)
 GRAPH_SRC = $(wildcard src/runtime/graph/*.cc)
 RUNTIME_SRC = $(wildcard src/runtime/*.cc)
@@ -72,6 +73,7 @@ ROCM_OBJ = $(patsubst src/%.cc, build/%.o, $(ROCM_SRC))
 OPENCL_OBJ = $(patsubst src/%.cc, build/%.o, $(OPENCL_SRC))
 OPENGL_OBJ = $(patsubst src/%.cc, build/%.o, $(OPENGL_SRC))
 VULKAN_OBJ = $(patsubst src/%.cc, build/%.o, $(VULKAN_SRC))
+SGX_OBJ = $(patsubst src/%.cc, build/%.o, $(SGX_SRC)) build/runtime/sgx/untrusted/tvm_u.o
 RPC_OBJ = $(patsubst src/%.cc, build/%.o, $(RPC_SRC))
 GRAPH_OBJ = $(patsubst src/%.cc, build/%.o, $(GRAPH_SRC))
 CC_OBJ = $(patsubst src/%.cc, build/%.o, $(CC_SRC)) $(LLVM_OBJ)
@@ -172,6 +174,20 @@ else
 	CFLAGS += -DTVM_METAL_RUNTIME=0
 endif
 
+ifeq ($(USE_SGX), 1)
+	EDGER8R = $(SGX_SDK)/bin/x64/sgx_edger8r
+	ifneq ($(SGX_MODE), HW)
+		sgx_sim := _sim
+	endif
+	urts_library_name := sgx_urts$(sgx_sim)
+	CFLAGS += -DTVM_SGX_RUNTIME=1
+	SGX_CFLAGS = -include "build/runtime/sgx/untrusted/tvm_u.h" -I$(SGX_SDK)/include
+	LDFLAGS += -L$(SGX_SDK)/lib64 -l$(urts_library_name)
+	RUNTIME_DEP += $(SGX_OBJ)
+else
+	CFLAGS += -DTVM_SGX_RUNTIME=0
+endif
+
 ifeq ($(USE_RPC), 1)
 	RUNTIME_DEP += $(RPC_OBJ)
 endif
@@ -254,6 +270,21 @@ build/runtime/metal/%.o: src/runtime/metal/%.mm
 	$(CXX) $(OBJCFLAGS) $(CFLAGS) -MM -MT build/runtime/metal/$*.o $< >build/runtime/metal/$*.d
 	$(CXX) $(OBJCFLAGS) -c $(CFLAGS) -c $< -o $@
 
+build/runtime/sgx/untrusted/tvm_u.h: src/runtime/sgx/tvm.edl
+	@mkdir -p $(@D)
+	$(EDGER8R) $< --untrusted --untrusted-dir $(@D) --search-path $(SGX_SDK)/include
+	mv $@ $@.in
+	awk 'NR==4{print "#include <tvm/runtime/c_runtime_api.h>"}1' $@.in > $@
+
+build/runtime/sgx/untrusted/tvm_u.c: build/runtime/sgx/untrusted/tvm_u.h
+
+build/runtime/sgx/untrusted/tvm_u.o: build/runtime/sgx/untrusted/tvm_u.c
+	$(CC) $(CFLAGS) $(SGX_CFLAGS) -c $< -o $@
+
+build/runtime/sgx/untrusted/%.o: src/runtime/sgx/untrusted/%.cc build/runtime/sgx/untrusted/tvm_u.h
+	$(CXX) $(CFLAGS) $(SGX_CFLAGS) -MM -MT build/$*.o $< >build/$*.d
+	$(CXX) -c $(CFLAGS) $(SGX_CFLAGS) -c $< -o $@
+
 build/%.o: src/%.cc
 	@mkdir -p $(@D)
 	$(CXX) $(CFLAGS) -MM -MT build/$*.o $< >build/$*.d
diff --git a/apps/sgx/.gitignore b/apps/sgx/.gitignore
index 25f8f1058cab..c3af857904eb 100644
--- a/apps/sgx/.gitignore
+++ b/apps/sgx/.gitignore
@@ -1,2 +1 @@
 lib/
-bin/
diff --git a/apps/sgx/Makefile b/apps/sgx/Makefile
index fd1d0cc8f2d5..e06f8a8ebfe6 100644
--- a/apps/sgx/Makefile
+++ b/apps/sgx/Makefile
@@ -39,6 +39,7 @@ enclave_cflags := -static -nostdinc\
 	-fvisibility=hidden -fpie -fstack-protector-strong\
 	-ffunction-sections -fdata-sections\
 	-DDMLC_CXX11_THREAD_LOCAL=0\
+	-include "lib/tvm_t.h"\
 	$(enclave_include_paths)\
 
 enclave_cxxflags := -nostdinc++ $(enclave_cflags) -DTVM_SGX_MAX_CONCURRENCY=4
@@ -53,59 +54,31 @@ enclave_ldflags :=\
 	-Wl,-pie,-eenclave_entry -Wl,--export-dynamic\
 	-Wl,--defsym,__ImageBase=0 -Wl,--gc-sections
 
-app_cflags := -I$(SGX_SDK)/include -Ilib
-
-app_ldflags := -L$(SGX_SDK)/lib64\
-	-l$(urts_library_name) -l$(uservice_library_name) -lpthread\
-
 .PHONY: clean all
 
-all: lib/test_addone.signed.so bin/test_addone
-
-# Build rule for all-in-one TVM package library
-lib/tvm_runtime_pack.o: tvm_runtime_pack.cc lib/test_addone_t.o
-	@mkdir -p $(@D)
-	$(CXX) -c $< -o $@ $(pkg_cflags) $(pkg_ldflags) $(enclave_cxxflags) -g
+all: lib/test_addone.signed.so
 
 # The code library built by TVM
 lib/test_addone_sys.o: prepare_test_libs.py
 	python prepare_test_libs.py
 
-# EDL files
-lib/test_addone_u.c: $(sgx_edger8r) test_addone.edl
-	$(sgx_edger8r) --untrusted test_addone.edl --untrusted-dir lib --search-path $(SGX_SDK)/include
-
-lib/test_addone_u.o: lib/test_addone_u.c
-	$(CC) $(enclave_cflags) -c $< -o $@
-
-lib/test_addone_t.c: test_addone.edl
+lib/tvm_t.h: ../../src/runtime/sgx/tvm.edl
 	$(sgx_edger8r) --trusted $< --trusted-dir lib --search-path $(SGX_SDK)/include
+	mv $@ $@.in
+	awk 'NR==4{print "#include <tvm/runtime/c_runtime_api.h>"}1' $@.in > $@
 
-lib/test_addone_t.o: lib/test_addone_t.c
-	$(CC) $(enclave_cflags) -c $< -o $@
+lib/tvm_t.c: lib/tvm_t.h
+
+lib/tvm_t.o: lib/tvm_t.c
+	$(CC) $(enclave_cflags) $(pkg_cflags) -c $< -o $@ -include $(TVM_ROOT)/include/tvm/runtime/c_runtime_api.h
 
 # The enclave library
-lib/test_addone.so: enclave.cc lib/tvm_runtime_pack.o lib/test_addone_t.o lib/test_addone_sys.o
+lib/test_addone.so: $(TVM_ROOT)/src/runtime/sgx/trusted/runtime.cc lib/tvm_t.o lib/test_addone_sys.o
 	$(CXX) $^ -o $@ $(pkg_cflags) $(pkg_ldflags) $(enclave_cxxflags) $(enclave_ldflags) -g
 
 # The signed enclave
 lib/test_addone.signed.so: lib/test_addone.so enclave_config.xml
 	$(sgx_enclave_signer) sign -key enclave_private.pem -enclave $< -out $@ -config enclave_config.xml
 
-# An app that runs the enclave
-bin/test_addone: app.cc lib/test_addone_u.o
-	@mkdir -p $(@D)
-	$(CXX) $^ -o $@ $(app_cflags) $(app_ldflags) $(pkg_cflags) -g
-
-# Debugging runtime pack built without SGX (c.f. howto_deploy/tvm_runtime_pack.cc)
-lib/tvm_runtime_pack_nosgx.o: tvm_runtime_pack.cc
-	@mkdir -p $(@D)
-	$(CXX) -c $< -o $@ $(pkg_cflags) $(pkg_ldflags) -g
-
-# Debugging binary that runs TVM without SGX
-bin/addone_nosgx: enclave.cc lib/tvm_runtime_pack_nosgx.o lib/test_addone_sys.o
-	@mkdir -p $(@D)
-	$(CXX) $^ -o $@ $(pkg_cflags) $(pkg_ldflags) -g -lpthread
-
 clean:
-	rm -rf lib bin
+	rm -rf lib
diff --git a/apps/sgx/README.md b/apps/sgx/README.md
index c041c2b6c94b..565519d457ce 100644
--- a/apps/sgx/README.md
+++ b/apps/sgx/README.md
@@ -5,7 +5,7 @@ This application demonstrates the use of a simple TVM model in the [Intel SGX](h
 ## Prerequisites
 
 1. A GNU/Linux environment
-2. TVM compiled with LLVM and the `tvm` Python module
+2. TVM compiled with LLVM and SGX; and the `tvm` Python module
 3. The [Linux SGX SDK](https://github.com/intel/linux-sgx) [link to pre-built libraries](https://01.org/intel-software-guard-extensions/downloads)
 
 ## Running the example
diff --git a/apps/sgx/app.cc b/apps/sgx/app.cc
deleted file mode 100644
index c130b1d2f3e9..000000000000
--- a/apps/sgx/app.cc
+++ /dev/null
@@ -1,137 +0,0 @@
-#include <cstdio>
-#include <iostream>
-
-#include "sgx_urts.h"
-#include "sgx_eid.h"
-#include "test_addone_u.h"
-#include "../../sgx/runtime_u.cc"
-
-#define TOKEN_FILENAME   "bin/test_addone.token"
-#define ENCLAVE_FILENAME "lib/test_addone.signed.so"
-
-sgx_enclave_id_t tvm_sgx_eid;
-
-typedef struct _sgx_errlist_t {
-  sgx_status_t err;
-  const char *msg;
-} sgx_errlist_t;
-
-/* Error code returned by sgx_create_enclave */
-static sgx_errlist_t sgx_errlist[] = {
-  { SGX_ERROR_DEVICE_BUSY, "SGX device was busy." },
-  { SGX_ERROR_ENCLAVE_FILE_ACCESS, "Can't open enclave file." },
-  { SGX_ERROR_ENCLAVE_LOST, "Power transition occurred." },
-  { SGX_ERROR_INVALID_ATTRIBUTE, "Enclave was not authorized." },
-  { SGX_ERROR_INVALID_ENCLAVE, "Invalid enclave image." },
-  { SGX_ERROR_INVALID_ENCLAVE_ID, "Invalid enclave identification." },
-  { SGX_ERROR_INVALID_METADATA, "Invalid enclave metadata." },
-  { SGX_ERROR_INVALID_PARAMETER, "Invalid parameter." },
-  { SGX_ERROR_INVALID_SIGNATURE, "Invalid enclave signature." },
-  { SGX_ERROR_INVALID_VERSION, "Enclave version was invalid." },
-  { SGX_ERROR_MEMORY_MAP_CONFLICT, "Memory map conflicted." },
-  { SGX_ERROR_NO_DEVICE, "Invalid SGX device." },
-  { SGX_ERROR_OUT_OF_EPC, "Out of EPC memory." },
-  { SGX_ERROR_OUT_OF_MEMORY, "Out of memory." },
-  { SGX_ERROR_UNEXPECTED, "Unexpected error occurred." },
-};
-
-/* Check error conditions for loading enclave */
-void print_error_message(sgx_status_t status)
-{
-  size_t idx = 0;
-  size_t ttl = sizeof sgx_errlist/sizeof sgx_errlist[0];
-
-  for (idx = 0; idx < ttl; idx++) {
-    if(status == sgx_errlist[idx].err) {
-      printf("Error: %s\n", sgx_errlist[idx].msg);
-      break;
-    }
-  }
-
-  if (idx == ttl)
-    printf("Error code is 0x%X. Please refer to the \"Intel SGX SDK Developer Reference\" for more details.\n", status);
-}
-
-/* Initialize the enclave:
- *   Step 1: try to retrieve the launch token saved by last transaction
- *   Step 2: call sgx_create_enclave to initialize an enclave instance
- *   Step 3: save the launch token if it is updated
- */
-int initialize_enclave(void)
-{
-  sgx_launch_token_t token = {0};
-  sgx_status_t sgx_status = SGX_ERROR_UNEXPECTED;
-  int updated = 0;
-
-  /* Step 1: try to retrieve the launch token saved by last transaction
-   *     if there is no token, then create a new one.
-   */
-  FILE *fp = fopen(TOKEN_FILENAME, "rb");
-  if (fp == NULL && (fp = fopen(TOKEN_FILENAME, "wb")) == NULL) {
-    printf("Warning: Failed to create/open the launch token file \"%s\".\n", TOKEN_FILENAME);
-    return -1;
-  }
-
-  /* read the token from saved file */
-  size_t read_num = fread(token, 1, sizeof(sgx_launch_token_t), fp);
-  if (read_num != 0 && read_num != sizeof(sgx_launch_token_t)) {
-    /* if token is invalid, clear the buffer */
-    memset(&token, 0x0, sizeof(sgx_launch_token_t));
-    printf("Warning: Invalid launch token read from \"%s\".\n", TOKEN_FILENAME);
-  }
-
-  /* Step 2: call sgx_create_enclave to initialize an enclave instance */
-  /* Debug Support: set 2nd parameter to 1 */
-  sgx_status = sgx_create_enclave(ENCLAVE_FILENAME, SGX_DEBUG_FLAG, &token, &updated, &tvm_sgx_eid, NULL);
-  if (sgx_status != SGX_SUCCESS) {
-    print_error_message(sgx_status);
-    if (fp != NULL) fclose(fp);
-    return -1;
-  }
-
-  /* Step 3: save the launch token if it is updated */
-  if (updated == 0 || fp == NULL) {
-    /* if the token is not updated, or file handler is invalid, do not perform saving */
-    if (fp != NULL) fclose(fp);
-    return 0;
-  }
-
-  /* reopen the file with write capablity */
-  fp = freopen(TOKEN_FILENAME, "wb", fp);
-  if (fp == NULL) return 0;
-  size_t write_num = fwrite(token, 1, sizeof(sgx_launch_token_t), fp);
-  if (write_num != sizeof(sgx_launch_token_t))
-    printf("Warning: Failed to save launch token to \"%s\".\n", TOKEN_FILENAME);
-  fclose(fp);
-  return 0;
-}
-
-int SGX_CDECL main(int argc, char *argv[]) {
-  if(initialize_enclave() < 0) {
-    printf("Failed to initialize enclave.\n");
-    return -1;
-  }
-
-  /* Run TVM within the enclave */
-  int addone_status;
-  sgx_status_t sgx_status = SGX_ERROR_UNEXPECTED;
-  sgx_status = tvm_ecall_run_module(tvm_sgx_eid, nullptr, &addone_status);
-  if (sgx_status != SGX_SUCCESS) {
-    print_error_message(sgx_status);
-  }
-
-  sgx_destroy_enclave(tvm_sgx_eid);
-
-  if (addone_status == 1) {
-    printf("It works!");
-    return 0;
-  }
-  printf("It doesn't work.");
-  return -1;
-}
-
-extern "C" {
-void ocall_println(const char* str) {
-  std::cout << "Enclave says: " << str << std::endl;
-}
-}
diff --git a/apps/sgx/enclave.cc b/apps/sgx/enclave.cc
deleted file mode 100644
index 342e5984e0f9..000000000000
--- a/apps/sgx/enclave.cc
+++ /dev/null
@@ -1,62 +0,0 @@
-#include <dlpack/dlpack.h>
-#include <tvm/runtime/module.h>
-#include <tvm/runtime/registry.h>
-#include <tvm/runtime/packed_func.h>
-#ifndef _LIBCPP_SGX_CONFIG
-#include <iostream>
-#endif
-
-/* This function mirrors the one in howto_deploy except without the iostream */
-int Verify(tvm::runtime::Module mod, std::string fname) {
-  // Get the function from the module.
-  tvm::runtime::PackedFunc f = mod.GetFunction(fname);
-
-  // Allocate the DLPack data structures.
-  DLTensor* x;
-  DLTensor* y;
-  int ndim = 1;
-  int dtype_code = kDLFloat;
-  int dtype_bits = 32;
-  int dtype_lanes = 1;
-  int device_type = kDLCPU;
-  int device_id = 0;
-  int64_t shape[1] = {10};
-  TVMArrayAlloc(shape, ndim, dtype_code, dtype_bits, dtype_lanes,
-                device_type, device_id, &x);
-  TVMArrayAlloc(shape, ndim, dtype_code, dtype_bits, dtype_lanes,
-                device_type, device_id, &y);
-  for (int i = 0; i < shape[0]; ++i) {
-    static_cast<float*>(x->data)[i] = i;
-  }
-
-  // Invoke the function
-  f(x, y);
-
-  // check the output
-  bool all_eq = true;
-  for (int i = 0; i < shape[0]; ++i) {
-    all_eq = all_eq && static_cast<float*>(y->data)[i] == i + 1.0f;
-  }
-
-  return all_eq;
-}
-
-
-extern "C" {
-void tvm_ecall_run_module(const void* tvm_args, void* tvm_return_value) {
-  tvm::runtime::Module mod_syslib = (*tvm::runtime::Registry::Get("module._GetSystemLib"))();
-  *(int*)tvm_return_value = Verify(mod_syslib, "addonesys");
-}
-}
-
-#ifndef _LIBCPP_SGX_CONFIG
-int main(void) {
-  tvm::runtime::Module mod_syslib = (*tvm::runtime::Registry::Get("module._GetSystemLib"))();
-  if (Verify(mod_syslib, "addonesys")) {
-    std::cout << "It works!" << std::endl;
-    return 0;
-  }
-  std::cerr << "It doesn't work." << std::endl;
-  return -1;
-}
-#endif
diff --git a/apps/sgx/enclave_config.xml b/apps/sgx/enclave_config.xml
index f7fc129d6e63..07be0d7a7ad2 100644
--- a/apps/sgx/enclave_config.xml
+++ b/apps/sgx/enclave_config.xml
@@ -1,8 +1,8 @@
 <EnclaveConfiguration>
   <ProdID>0</ProdID>
   <ISVSVN>0</ISVSVN>
-  <StackMaxSize>0x100000</StackMaxSize>
-  <HeapMaxSize>0x100000</HeapMaxSize>
+  <StackMaxSize>0x2000</StackMaxSize>
+  <HeapMaxSize>0x2000</HeapMaxSize>
   <TCSNum>5</TCSNum>
   <TCSPolicy>1</TCSPolicy>
   <DisableDebug>0</DisableDebug>
diff --git a/apps/sgx/prepare_test_libs.py b/apps/sgx/prepare_test_libs.py
index 715880e61c31..6c848fe316b9 100644
--- a/apps/sgx/prepare_test_libs.py
+++ b/apps/sgx/prepare_test_libs.py
@@ -15,7 +15,7 @@ def prepare_test_libs(base_path):
     print(tvm.lower(s, [A, B], simple_mode=True))
 
     # Compile library in system library mode
-    fadd_syslib = tvm.build(s, [A, B], 'llvm --system-lib', name='addonesys')
+    fadd_syslib = tvm.build(s, [A, B], 'llvm --system-lib')
     syslib_path = osp.join(base_path, 'test_addone_sys.o')
     fadd_syslib.save(syslib_path)
 
diff --git a/apps/sgx/run_example.sh b/apps/sgx/run_example.sh
index 3658e0bafb73..9334b260cbf3 100755
--- a/apps/sgx/run_example.sh
+++ b/apps/sgx/run_example.sh
@@ -1,8 +1,6 @@
 #!/bin/bash
 
 sgx_sdk=${SGX_SDK:=/opt/sgxsdk}
-
-mkdir -p bin lib
 make
 echo "========================="
-LD_LIBRARY_PATH="$sgx_sdk/lib64":${LD_LIBRARY_PATH} bin/test_addone
+LD_LIBRARY_PATH="$sgx_sdk/lib64":${LD_LIBRARY_PATH} TVM_CACHE_DIR=/tmp python test_addone.py
diff --git a/apps/sgx/test_addone.edl b/apps/sgx/test_addone.edl
deleted file mode 100644
index 0127a581803a..000000000000
--- a/apps/sgx/test_addone.edl
+++ /dev/null
@@ -1,7 +0,0 @@
-enclave {
-    from "../../sgx/tvm.edl" import *;
-
-    untrusted {
-        void ocall_println([in, string] const char *str);
-    };
-};
diff --git a/apps/sgx/test_addone.py b/apps/sgx/test_addone.py
new file mode 100644
index 000000000000..5ddccfa425cc
--- /dev/null
+++ b/apps/sgx/test_addone.py
@@ -0,0 +1,13 @@
+import tvm
+import numpy as np
+
+ctx = tvm.context('cpu', 0)
+fadd1 = tvm.module.load('lib/test_addone.signed.so')
+
+n = 10
+x = tvm.nd.array(np.random.uniform(size=n).astype('float32'), ctx)
+y = tvm.nd.array(np.zeros(n, dtype='float32'), ctx)
+fadd1(x, y)
+
+np.testing.assert_allclose(y.asnumpy(), x.asnumpy() + 1)
+print("It works!")
diff --git a/apps/sgx/tvm_runtime_pack.cc b/apps/sgx/tvm_runtime_pack.cc
deleted file mode 100644
index 0d88af03a65d..000000000000
--- a/apps/sgx/tvm_runtime_pack.cc
+++ /dev/null
@@ -1,15 +0,0 @@
-/*!
- * \brief This is an all in one TVM runtime file for use in an SGX enclave.
- *
- *   The files included here will be statically linked into the enclave.
- *   Please refer to the Makefile (rule lib/tvm_runtime_pack.o) for how to build.
- *
- */
-#ifdef _LIBCPP_SGX_CONFIG
-#include "lib/test_addone_t.h"
-#endif
-#include "../../sgx/runtime_t.cc"
-
-#ifndef _LIBCPP_SGX_CONFIG
-#include "../../src/runtime/file_util.cc"
-#endif
diff --git a/include/tvm/runtime/registry.h b/include/tvm/runtime/registry.h
index 61feccadf748..5b0cc788de06 100644
--- a/include/tvm/runtime/registry.h
+++ b/include/tvm/runtime/registry.h
@@ -76,7 +76,7 @@ class Registry {
   // Internal class.
   struct Manager;
 
- private:
+ protected:
   /*! \brief name of the function */
   std::string name_;
   /*! \brief internal packed function */
diff --git a/make/config.mk b/make/config.mk
index eee96ac12360..e5b0219ec205 100644
--- a/make/config.mk
+++ b/make/config.mk
@@ -44,6 +44,10 @@ USE_OPENCL = 0
 # whether enable Metal during compile
 USE_METAL = 0
 
+# whether enable SGX during compile
+USE_SGX = 0
+SGX_SDK = /opt/sgxsdk
+
 # Whether enable RPC during compile
 USE_RPC = 1
 
diff --git a/sgx/runtime_t.cc b/sgx/runtime_t.cc
deleted file mode 100644
index 8ab326f862c0..000000000000
--- a/sgx/runtime_t.cc
+++ /dev/null
@@ -1,17 +0,0 @@
-/*!
- *  Copyright (c) 2018 by Contributors
- * \file sgx_runtime.cc
- */
-#include "../../src/runtime/c_runtime_api.cc"
-#include "../../src/runtime/cpu_device_api.cc"
-#include "../../src/runtime/workspace_pool.cc"
-#include "../../src/runtime/module_util.cc"
-#include "../../src/runtime/module.cc"
-#include "../../src/runtime/registry.cc"
-#include "../../src/runtime/system_lib_module.cc"
-#ifndef _LIBCPP_SGX_CONFIG
-#include "../../src/runtime/threading_backend.cc"
-#else
-#include "threading_backend.cc"
-#endif
-#include "../../src/runtime/thread_pool.cc"
diff --git a/sgx/runtime_u.cc b/sgx/runtime_u.cc
deleted file mode 100644
index 08ea2acf643c..000000000000
--- a/sgx/runtime_u.cc
+++ /dev/null
@@ -1,36 +0,0 @@
-#include <tvm/runtime/threading_backend.h>
-#include "../../src/runtime/threading_backend.cc"
-#include <iostream>
-
-extern sgx_enclave_id_t tvm_sgx_eid;
-extern "C" {
-sgx_status_t tvm_ecall_run_worker(sgx_enclave_id_t eid, const void* cb);
-}
-
-namespace tvm {
-namespace runtime {
-namespace sgx {
-
-static std::unique_ptr<tvm::runtime::threading::ThreadGroup> sgx_thread_group;
-
-extern "C" {
-
-void tvm_ocall_thread_group_launch(int num_tasks, void* cb) {
-  std::function<void(int)> runner = [cb](int _worker_id) {
-    sgx_status_t sgx_status = SGX_ERROR_UNEXPECTED;
-    sgx_status = tvm_ecall_run_worker(tvm_sgx_eid, cb);
-    CHECK(sgx_status == SGX_SUCCESS) << "SGX Error: " << sgx_status;
-  };
-  sgx_thread_group.reset(new tvm::runtime::threading::ThreadGroup(
-        num_tasks, runner, false /* include_main_thread */));
-}
-
-void tvm_ocall_thread_group_join() {
-  sgx_thread_group->Join();
-}
-
-}
-
-}  // namespace sgx
-}  // namespace runtime
-}  // namespace tvm
diff --git a/sgx/tvm.edl b/sgx/tvm.edl
deleted file mode 100644
index f580fecfec89..000000000000
--- a/sgx/tvm.edl
+++ /dev/null
@@ -1,15 +0,0 @@
-enclave {
-    from "sgx_tstdc.edl" import *;
-
-    trusted {
-        public void tvm_ecall_run_module([user_check] const void* tvm_args,
-                                         [user_check] void* tvm_ret_value);
-        public void tvm_ecall_run_worker([user_check] const void* cb);
-    };
-
-    untrusted {
-        void tvm_ocall_thread_group_launch(int num_workers, [user_check] void* cb);
-        void tvm_ocall_thread_group_join();
-    };
-};
-
diff --git a/src/codegen/llvm/llvm_module.cc b/src/codegen/llvm/llvm_module.cc
index 33fbf569726c..3d0632f9230e 100644
--- a/src/codegen/llvm/llvm_module.cc
+++ b/src/codegen/llvm/llvm_module.cc
@@ -51,13 +51,7 @@ class LLVMModuleNode final : public runtime::ModuleNode {
     BackendPackedCFunc faddr =
         reinterpret_cast<BackendPackedCFunc>(GetFunctionAddr(fname));
     if (faddr == nullptr) return PackedFunc();
-    return PackedFunc([faddr, sptr_to_self](TVMArgs args, TVMRetValue* rv) {
-        int ret = (*faddr)(
-            (void*)args.values, // NOLINT(*)
-            (int*)args.type_codes, // NOLINT(*)
-            args.num_args);
-        CHECK_EQ(ret, 0) << TVMGetLastError();
-      });
+    return WrapPackedFunc(faddr, sptr_to_self);
   }
 
   void SaveToFile(const std::string& file_name,
diff --git a/src/runtime/c_runtime_api.cc b/src/runtime/c_runtime_api.cc
index c7fd84362f47..5c4ac57fefa1 100644
--- a/src/runtime/c_runtime_api.cc
+++ b/src/runtime/c_runtime_api.cc
@@ -10,6 +10,9 @@
 #include <tvm/runtime/module.h>
 #include <tvm/runtime/registry.h>
 #include <tvm/runtime/device_api.h>
+#ifdef _LIBCPP_SGX_CONFIG
+#include "sgx/trusted/runtime.h"
+#endif
 #include <array>
 #include <algorithm>
 #include <string>
@@ -186,7 +189,11 @@ const char *TVMGetLastError() {
 }
 
 void TVMAPISetLastError(const char* msg) {
+#ifndef _LIBCPP_SGX_CONFIG
   TVMAPIRuntimeStore::Get()->last_error = msg;
+#else
+  sgx::OCallPackedFunc("__sgx_set_last_error__", msg);
+#endif
 }
 
 int TVMModLoadFromFile(const char* file_name,
diff --git a/src/runtime/file_util.cc b/src/runtime/file_util.cc
index 3d7370944fe2..cfd2d72f15a1 100644
--- a/src/runtime/file_util.cc
+++ b/src/runtime/file_util.cc
@@ -53,6 +53,7 @@ std::string GetFileFormat(const std::string& file_name,
                           const std::string& format) {
   std::string fmt = format;
   if (fmt.length() == 0) {
+    if (file_name.find(".signed.so") != std::string::npos) return "sgx";
     size_t pos = file_name.find_last_of(".");
     if (pos != std::string::npos) {
       return file_name.substr(pos + 1, file_name.length() - pos - 1);
@@ -64,6 +65,24 @@ std::string GetFileFormat(const std::string& file_name,
   }
 }
 
+std::string GetCacheDir() {
+  char* env_cache_dir;
+  if ((env_cache_dir = getenv("TVM_CACHE_DIR"))) return env_cache_dir;
+  if ((env_cache_dir = getenv("XDG_CACHE_HOME"))) {
+    return std::string(env_cache_dir) + "/tvm";
+  }
+  if ((env_cache_dir = getenv("HOME"))) {
+    return std::string(env_cache_dir) + "/.cache/tvm";
+  }
+  return ".";
+}
+
+std::string GetFileBasename(const std::string& file_name) {
+  size_t last_slash = file_name.find_last_of("/");
+  if (last_slash == std::string::npos) return file_name;
+  return file_name.substr(last_slash + 1);
+}
+
 std::string GetMetaFilePath(const std::string& file_name) {
   size_t pos  = file_name.find_last_of(".");
   if (pos != std::string::npos) {
diff --git a/src/runtime/file_util.h b/src/runtime/file_util.h
index 18b9ec6866a1..b3357271856e 100644
--- a/src/runtime/file_util.h
+++ b/src/runtime/file_util.h
@@ -19,12 +19,25 @@ namespace runtime {
 std::string GetFileFormat(const std::string& file_name,
                           const std::string& format);
 
+/*!
+ * \return the directory in which TVM stores cached files.
+ *         May be set using TVM_CACHE_DIR; defaults to system locations.
+ */
+std::string GetCacheDir();
+
 /*!
  * \brief Get meta file path given file name and format.
  * \param file_name The name of the file.
  */
 std::string GetMetaFilePath(const std::string& file_name);
 
+/*!
+ * \brief Get file basename (i.e. without leading directories)
+ * \param file_name The name of the file.
+ * \return the base name
+ */
+std::string GetFileBasename(const std::string& file_name);
+
 /*!
  * \brief Load binary file into a in-memory buffer.
  * \param file_name The name of the file.
diff --git a/src/runtime/sgx/common.h b/src/runtime/sgx/common.h
new file mode 100644
index 000000000000..a36b33d48b1a
--- /dev/null
+++ b/src/runtime/sgx/common.h
@@ -0,0 +1,23 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file common.h
+ * \brief TVM SGX common API.
+ */
+#ifndef TVM_RUNTIME_SGX_COMMON_H_
+#define TVM_RUNTIME_SGX_COMMON_H_
+
+namespace tvm {
+namespace runtime {
+namespace sgx {
+
+#define TVM_SGX_CHECKED_CALL(Function)                                         \
+  sgx_status_t TVM_STR_CONCAT(__sgx_status_, __LINE__) = SGX_ERROR_UNEXPECTED; \
+  TVM_STR_CONCAT(__sgx_status_, __LINE__) = Function;                          \
+  CHECK_EQ(TVM_STR_CONCAT(__sgx_status_, __LINE__), SGX_SUCCESS)               \
+    << "SGX Error: " << TVM_STR_CONCAT(__sgx_status_, __LINE__);
+
+}  // namespace sgx
+}  // namespace runtime
+}  // namespace tvm
+
+#endif  // TVM_RUNTIME_SGX_COMMON_H_
diff --git a/src/runtime/sgx/trusted/ecall_registry.h b/src/runtime/sgx/trusted/ecall_registry.h
new file mode 100644
index 000000000000..841b9fb7c172
--- /dev/null
+++ b/src/runtime/sgx/trusted/ecall_registry.h
@@ -0,0 +1,91 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file ecall_registry.h
+ * \brief The global registry of packed functions available via ecall_packed_func.
+ */
+#ifndef TVM_RUNTIME_SGX_TRUSTED_ECALL_REGISTRY_H_
+#define TVM_RUNTIME_SGX_TRUSTED_ECALL_REGISTRY_H_
+
+#include <dmlc/logging.h>
+#include <tvm/runtime/registry.h>
+#include <string>
+#include <algorithm>
+#include <vector>
+
+namespace tvm {
+namespace runtime {
+namespace sgx {
+
+class ECallRegistry: public Registry {
+ public:
+  explicit ECallRegistry(std::string name) {
+    name_ = name;
+  }
+
+  Registry& set_body(PackedFunc f) {
+     func_ = f;
+     return *this;
+  }
+
+  Registry& set_body(PackedFunc::FType f) {  // NOLINT(*)
+    return set_body(PackedFunc(f));
+  }
+
+  static Registry& Register(const std::string& name, bool override = false) {
+    for (auto& r : exports_) {
+      if (r.name_ == name) {
+        CHECK(override) << "ecall " << name << " is already registered";
+        return r;
+      }
+    }
+    TVM_SGX_CHECKED_CALL(
+        tvm_ocall_register_export(name.c_str(), exports_.size()));
+    exports_.emplace_back(name);
+    return exports_.back();
+  }
+
+  static bool Remove(const std::string& name) {
+    LOG(FATAL) << "Removing enclave exports is not supported.";
+  }
+
+  static const PackedFunc* Get(const std::string& name) {
+    for (const auto& r : exports_) {
+      if (r.name_ == name) return &r.func_;
+    }
+    return nullptr;
+  }
+
+  static const PackedFunc* Get(unsigned func_id) {
+    return func_id >= exports_.size() ? nullptr : &exports_[func_id].func_;
+  }
+
+  static std::vector<std::string> ListNames() {
+    std::vector<std::string> names;
+    names.resize(exports_.size());
+    std::transform(exports_.begin(), exports_.end(), names.begin(),
+                   [](ECallRegistry r) { return r.name_; });
+    return names;
+  }
+
+  static std::vector<ECallRegistry> exports_;
+};
+
+std::vector<ECallRegistry> ECallRegistry::exports_;
+
+/*!
+ * \brief Register a function callable via ecall_packed_func
+ * \code
+ *   TVM_REGISTER_ENCLAVE_FUNC("DoThing")
+ *   .set_body([](TVMArgs args, TVMRetValue* rv) {
+ *   });
+ * \endcode
+ */
+#define TVM_REGISTER_ENCLAVE_FUNC(OpName)                              \
+  TVM_STR_CONCAT(TVM_FUNC_REG_VAR_DEF, __COUNTER__) =                  \
+      ::tvm::runtime::sgx::ECallRegistry::Register(OpName)
+
+}  // namespace sgx
+}  // namespace runtime
+}  // namespace tvm
+
+#endif  // TVM_RUNTIME_SGX_TRUSTED_ECALL_REGISTRY_H_
diff --git a/src/runtime/sgx/trusted/runtime.cc b/src/runtime/sgx/trusted/runtime.cc
new file mode 100644
index 000000000000..314f2625e251
--- /dev/null
+++ b/src/runtime/sgx/trusted/runtime.cc
@@ -0,0 +1,75 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file runtime_t.cc
+ */
+#include <tvm/runtime/c_runtime_api.h>
+#include <tvm/runtime/packed_func.h>
+#include "../../c_runtime_api.cc"
+#include "../../cpu_device_api.cc"
+#include "../../module.cc"
+#include "../../module_util.cc"
+#include "../../registry.cc"
+#include "../../system_lib_module.cc"
+#include "../../thread_pool.cc"
+#include "../../workspace_pool.cc"
+#include "./ecall_registry.h"
+#include "./runtime.h"
+#include "./threading_backend.cc"
+
+namespace tvm {
+namespace runtime {
+namespace sgx {
+
+extern "C" {
+
+void tvm_ecall_init(TVMRetValueHandle ret) {}
+
+void tvm_ecall_packed_func(int func_id,
+                           const TVMValue* arg_values,
+                           const int* type_codes,
+                           int num_args,
+                           TVMRetValueHandle ret) {
+  const PackedFunc* f = ECallRegistry::Get(func_id);
+  CHECK(f != nullptr) << "ecall function not found.";
+
+  TVMRetValue rv;
+  f->CallPacked(TVMArgs(arg_values, type_codes, num_args), &rv);
+
+  int ret_type_code = rv.type_code();
+  if (ret_type_code == kNull) return;
+
+  TVMValue ret_value;
+  if (ret_type_code == kBytes || ret_type_code == kStr) {
+    // allocate a buffer in untrusted, copy the values in
+    std::string bytes = rv;
+
+    void* ret_buf;
+    TVM_SGX_CHECKED_CALL(tvm_ocall_reserve_space(
+          &ret_buf, bytes.size() + sizeof(TVMByteArray)));
+
+    char* data_buf = static_cast<char*>(ret_buf) + sizeof(TVMByteArray);
+    memcpy(data_buf, bytes.data(), bytes.size());
+
+    TVMByteArray* arr = static_cast<TVMByteArray*>(ret_buf);
+    arr->data = data_buf;
+    arr->size = bytes.size();
+
+    ret_value = TVMValue{.v_handle = arr};
+    ret_type_code = kBytes;
+  } else {
+    rv.MoveToCHost(&ret_value, &ret_type_code);
+  }
+  TVM_SGX_CHECKED_CALL(tvm_ocall_set_return(ret, &ret_value, &ret_type_code, 1));
+}
+
+}  // extern "C"
+
+TVM_REGISTER_ENCLAVE_FUNC("__tvm_main__")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+  Module mod = (*Registry::Get("module._GetSystemLib"))();
+  mod.GetFunction("default_function").CallPacked(args, rv);
+});
+
+}  // namespace sgx
+}  // namespace runtime
+}  // namespace tvm
diff --git a/src/runtime/sgx/trusted/runtime.h b/src/runtime/sgx/trusted/runtime.h
new file mode 100644
index 000000000000..ded76fd52d54
--- /dev/null
+++ b/src/runtime/sgx/trusted/runtime.h
@@ -0,0 +1,43 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file trusted/runtime.h
+ * \brief TVM SGX trusted API.
+ */
+#ifndef TVM_RUNTIME_SGX_TRUSTED_RUNTIME_H_
+#define TVM_RUNTIME_SGX_TRUSTED_RUNTIME_H_
+
+#include <sgx_edger8r.h>
+#include <tvm/runtime/packed_func.h>
+#include <string>
+#include "../common.h"
+
+namespace tvm {
+namespace runtime {
+namespace sgx {
+
+template<typename... Args>
+inline TVMRetValue OCallPackedFunc(std::string name, Args&& ...args) {
+  const int kNumArgs = sizeof...(Args);
+  const int kArraySize = kNumArgs > 0 ? kNumArgs : 1;
+  TVMValue values[kArraySize];
+  int type_codes[kArraySize];
+  detail::for_each(TVMArgsSetter(values, type_codes),
+                   std::forward<Args>(args)...);
+  TVMValue ret_val;
+  int ret_type_code;
+  TVM_SGX_CHECKED_CALL(tvm_ocall_packed_func(name.c_str(),
+                                             values,
+                                             type_codes,
+                                             kNumArgs,
+                                             &ret_val,
+                                             &ret_type_code));
+  TVMRetValue* rv = new TVMRetValue();
+  *rv = TVMArgValue(ret_val, ret_type_code);
+  return *rv;
+}
+
+}  // namespace sgx
+}  // namespace runtime
+}  // namespace tvm
+
+#endif  // TVM_RUNTIME_SGX_TRUSTED_RUNTIME_H_
diff --git a/sgx/threading_backend.cc b/src/runtime/sgx/trusted/threading_backend.cc
similarity index 74%
rename from sgx/threading_backend.cc
rename to src/runtime/sgx/trusted/threading_backend.cc
index c26cf08b95e2..662e824f5078 100644
--- a/sgx/threading_backend.cc
+++ b/src/runtime/sgx/trusted/threading_backend.cc
@@ -8,11 +8,7 @@
 #include <sgx_edger8r.h>
 #include <sgx_trts.h>
 #include <atomic>
-
-extern "C" {
-sgx_status_t SGX_CDECL tvm_ocall_thread_group_launch(int num_workers, void* cb);
-sgx_status_t SGX_CDECL tvm_ocall_thread_group_join();
-}
+#include "runtime.h"
 
 #ifndef TVM_SGX_MAX_CONCURRENCY
 #define TVM_SGX_MAX_CONCURRENCY 1
@@ -31,13 +27,12 @@ class ThreadGroup::Impl {
         next_task_id_(exclude_worker0) {
     CHECK(num_workers <= TVM_SGX_MAX_CONCURRENCY)
       << "Tried spawning more threads than allowed by TVM_SGX_MAX_CONCURRENCY.";
-    sgx_status_t sgx_status = SGX_ERROR_UNEXPECTED;
-    sgx_status = tvm_ocall_thread_group_launch(num_workers, this);
-    CHECK(sgx_status == SGX_SUCCESS) << "SGX Error: " << sgx_status;
+    sgx::OCallPackedFunc("__sgx_thread_group_launch__",
+        num_workers_, reinterpret_cast<void*>(this));
   }
 
   ~Impl() {
-    tvm_ocall_thread_group_join();
+    sgx::OCallPackedFunc("__sgx_thread_group_join__");
   }
 
   void RunTask() {
@@ -64,12 +59,12 @@ void Yield() {}
 
 int MaxConcurrency() { return TVM_SGX_MAX_CONCURRENCY; }
 
-extern "C" {
-void tvm_ecall_run_worker(const void* impl) {
-  if (!sgx_is_within_enclave(impl, sizeof(ThreadGroup::Impl))) return;
-  ((ThreadGroup::Impl*)impl)->RunTask();
-}
-}
+TVM_REGISTER_ENCLAVE_FUNC("__tvm_run_worker__")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    void* tg = args[0];
+    if (!sgx_is_within_enclave(tg, sizeof(ThreadGroup::Impl))) return;
+    reinterpret_cast<ThreadGroup::Impl*>(tg)->RunTask();
+  });
 
 }  // namespace threading
 }  // namespace runtime
diff --git a/src/runtime/sgx/tvm.edl b/src/runtime/sgx/tvm.edl
new file mode 100644
index 000000000000..1593e9186ec3
--- /dev/null
+++ b/src/runtime/sgx/tvm.edl
@@ -0,0 +1,28 @@
+enclave {
+    from "sgx_tstdc.edl" import *;
+
+    trusted {
+        public void tvm_ecall_init([isptr, user_check] TVMRetValueHandle ret);
+        public void tvm_ecall_packed_func(int func_id,
+                                          [in, count=num_args] const TVMValue* arg_values,
+                                          [in, count=num_args] const int* type_codes,
+                                          int num_args,
+                                          [isptr, user_check] TVMRetValueHandle ret);
+    };
+
+    untrusted {
+        void tvm_ocall_packed_func([in, string] const char* name,
+                                   [in, count=num_args] const TVMValue* arg_values,
+                                   [in, count=num_args] const int* type_codes,
+                                   int num_args,
+                                   [out] TVMValue* ret_val,
+                                   [out] int* ret_type_code);
+        void tvm_ocall_set_return([isptr, user_check] TVMRetValueHandle ret,
+                                   [in, count=num_ret] const TVMValue* value,
+                                   [in, count=num_ret] const int* type_code,
+                                   int num_ret);
+        void tvm_ocall_register_export([in, string] const char* name, int func_id);
+        void* tvm_ocall_reserve_space(size_t num_bytes);
+    };
+};
+
diff --git a/src/runtime/sgx/untrusted/sgx_module.cc b/src/runtime/sgx/untrusted/sgx_module.cc
new file mode 100644
index 000000000000..68bfa6d4cbb1
--- /dev/null
+++ b/src/runtime/sgx/untrusted/sgx_module.cc
@@ -0,0 +1,207 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file sgx_module.cc
+ * \brief SGX enclave module.
+ */
+#include <dmlc/logging.h>
+#include <tvm/runtime/c_runtime_api.h>
+#include <tvm/runtime/registry.h>
+#include <tvm/runtime/threading_backend.h>
+#include <sgx_urts.h>
+#include <algorithm>
+#include <fstream>
+#include <iostream>
+#include <iterator>
+#include <sstream>
+#include <string>
+#include <unordered_map>
+#include "../common.h"
+#include "../../file_util.h"
+
+namespace tvm {
+namespace runtime {
+
+class SGXModuleNode;
+
+namespace sgx {
+
+class EnclaveContext {
+ public:
+  explicit EnclaveContext(SGXModuleNode* mod) {
+    CHECK(Context()->mod_ == nullptr)
+      << "Tried overriding existing enclave context.";
+    CHECK(mod != nullptr) << "Tried setting null enclave context.";
+    Context()->mod_ = mod;
+  }
+  ~EnclaveContext() {
+    Context()->mod_ = nullptr;
+  }
+
+  static SGXModuleNode* GetModule() {
+    SGXModuleNode* ctx = Context()->mod_;
+    CHECK(ctx != nullptr) << "No current enclave context";
+    return ctx;
+  }
+
+ private:
+  EnclaveContext() {}
+  SGXModuleNode* mod_;
+
+  static EnclaveContext* Context() {
+    static thread_local EnclaveContext inst;
+    return &inst;
+  }
+};
+
+}  // namespace sgx
+
+class SGXModuleNode : public ModuleNode {
+ public:
+  ~SGXModuleNode() {
+    if (eid_) {
+      sgx::EnclaveContext ctx(this);
+      sgx_destroy_enclave(eid_);
+    }
+  }
+
+  void Init(const std::string& enclave_file) {
+    std::string token_file = GetCacheDir() + "/" +
+                             GetFileBasename(enclave_file) + ".token";
+    sgx_launch_token_t token = {0};
+    int token_updated = 0;
+
+    try {
+      std::ifstream ifs(token_file, std::fstream::in | std::fstream::binary);
+      ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit);
+      ifs >> token;
+    } catch (std::ifstream::failure e) {
+      memset(&token, 0x0, sizeof(sgx_launch_token_t));
+    }
+
+    TVM_SGX_CHECKED_CALL(sgx_create_enclave(
+        enclave_file.c_str(), SGX_DEBUG_FLAG, &token, &token_updated, &eid_, NULL));
+
+    sgx::EnclaveContext ctx(this);
+    TVMRetValue rv;
+    TVM_SGX_CHECKED_CALL(tvm_ecall_init(eid_, &rv));
+
+    if (!token_updated) return;
+
+    try {
+      std::ofstream ofs(token_file, std::fstream::trunc | std::fstream::binary);
+      ofs.exceptions(std::ifstream::failbit | std::ifstream::badbit);
+      ofs << token;
+    } catch (std::ifstream::failure e) {
+      LOG(INFO) << "Could not save SGX launch token to " << token_file;
+    }
+  }
+
+  const char* type_key() const final {
+    return "sgx";
+  }
+
+  PackedFunc GetFunction(
+      const std::string& name,
+      const std::shared_ptr<ModuleNode>& sptr_to_self) final {
+    auto exported = exports_.find(name);
+    if (exported == exports_.end()) return PackedFunc();
+    int func_id = exported->second;
+    return PackedFunc([this, func_id](TVMArgs args, TVMRetValue* rv) {
+        sgx::EnclaveContext ctx(this);
+        TVM_SGX_CHECKED_CALL(tvm_ecall_packed_func(eid_, func_id,
+              args.values, args.type_codes, args.num_args, rv));
+      });
+  }
+
+  void RunWorkers(int num_tasks, void* tg) {
+    std::function<void(int)> runner = [this, tg](int _worker_id) {
+      this->GetFunction("__tvm_run_worker__",
+                        std::shared_ptr<SGXModuleNode>(nullptr))(tg);
+    };
+    thread_group_.reset(new tvm::runtime::threading::ThreadGroup(
+          num_tasks, runner, false /* include_main_thread */));
+  }
+
+  void JoinThreads() {
+    thread_group_->Join();
+  }
+
+  void RegisterExport(std::string name, int func_id) {
+    exports_[name] = func_id;
+  }
+
+ private:
+  // ID of the loaded enclave
+  sgx_enclave_id_t eid_;
+  // Names and IDs of functions exported by the enclave module
+  std::unordered_map<std::string, int> exports_;
+  std::unique_ptr<tvm::runtime::threading::ThreadGroup> thread_group_;
+};
+
+namespace sgx {
+
+TVM_REGISTER_GLOBAL("__sgx_thread_group_launch__")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+  EnclaveContext::GetModule()->RunWorkers(args[0], args[1]);
+});
+
+TVM_REGISTER_GLOBAL("__sgx_thread_group_join__")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+  EnclaveContext::GetModule()->JoinThreads();
+});
+
+TVM_REGISTER_GLOBAL("__sgx_set_last_error__")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+  std::string err = args[0];
+  TVMAPISetLastError(err.c_str());
+});
+
+extern "C" {
+
+void tvm_ocall_register_export(const char* name, int func_id) {
+  EnclaveContext::GetModule()->RegisterExport(name, func_id);
+}
+
+void tvm_ocall_packed_func(const char* name,
+                           const TVMValue* arg_values,
+                           const int* type_codes,
+                           int num_args,
+                           TVMValue* ret_val,
+                           int* ret_type_code) {
+  const PackedFunc* f = Registry::Get(name);
+  CHECK(f != nullptr) << "ocall to nonexistent function \"" << name << "\"";
+  TVMRetValue rv;
+  f->CallPacked(TVMArgs(arg_values, type_codes, num_args), &rv);
+  rv.MoveToCHost(ret_val, ret_type_code);
+}
+
+// Allocates space for return values. The returned pointer is only valid between
+// successive calls to `tvm_ocall_reserve_space`.
+void* tvm_ocall_reserve_space(size_t num_bytes) {
+  static thread_local std::vector<uint64_t> buf;
+  buf.reserve(num_bytes);
+  return buf.data();
+}
+
+void tvm_ocall_set_return(TVMRetValueHandle ret,
+                           const TVMValue* value,
+                           const int* type_code,
+                           int num_ret) {
+  CHECK_EQ(num_ret, 1) << "Only one return value is currently supported.";
+  CHECK(type_code[0] != kStr) << "Return kBytes, not kStr.";
+  TVMRetValue* rv = static_cast<TVMRetValue*>(ret);
+  *rv = TVMArgValue(value[0], type_code[0]);
+}
+
+}  // extern "C"
+}  // namespace sgx
+
+TVM_REGISTER_GLOBAL("module.loadfile_sgx")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+  std::shared_ptr<SGXModuleNode> node = std::make_shared<SGXModuleNode>();
+  node->Init(args[0]);
+  *rv = runtime::Module(node);
+});
+
+}  // namespace runtime
+}  // namespace tvm
diff --git a/src/runtime/system_lib_module.cc b/src/runtime/system_lib_module.cc
index 1f9c8ac8e152..01ff99d7da87 100644
--- a/src/runtime/system_lib_module.cc
+++ b/src/runtime/system_lib_module.cc
@@ -55,16 +55,12 @@ class SystemLibModuleNode : public ModuleNode {
       module_blob_ = ptr;
     } else {
       auto it = tbl_.find(name);
-      if (it != tbl_.end()) {
-        if (ptr != it->second) {
-          LOG(WARNING) << "SystemLib symbol " << name
-                       << " get overriden to a different address "
-                   << ptr << "->" << it->second;
-          tbl_[name] = ptr;
-        }
-      } else {
-        tbl_[name] = ptr;
+      if (it != tbl_.end() && ptr != it->second) {
+        LOG(WARNING) << "SystemLib symbol " << name
+                     << " get overriden to a different address "
+                     << ptr << "->" << it->second;
       }
+      tbl_[name] = ptr;
     }
   }
 
diff --git a/src/runtime/thread_pool.cc b/src/runtime/thread_pool.cc
index 316baedff425..71052626ae5a 100644
--- a/src/runtime/thread_pool.cc
+++ b/src/runtime/thread_pool.cc
@@ -315,7 +315,11 @@ class ThreadPool {
   }
   int num_workers_;
   // if excluding worker 0 and using master to run task 0
+#ifndef _LIBCPP_SGX_CONFIG
   bool exclude_worker0_{true};
+#else
+  bool exclude_worker0_{false};
+#endif
   std::vector<std::unique_ptr<SpscTaskQueue> > queues_;
   std::unique_ptr<tvm::runtime::threading::ThreadGroup> threads_;
 };

From 21172b0355ea11d2e05fbb8f7cc65ae6a09ff32c Mon Sep 17 00:00:00 2001
From: Leyuan Wang <laurawly@gmail.com>
Date: Tue, 10 Apr 2018 20:37:01 -0700
Subject: [PATCH 258/949] Fix device name and Issue #874 (#1096)

---
 src/runtime/metal/metal_device_api.mm | 2 +-
 src/runtime/metal/metal_module.mm     | 6 +++++-
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/runtime/metal/metal_device_api.mm b/src/runtime/metal/metal_device_api.mm
index 8a2d1fb37069..077d2546c151 100644
--- a/src/runtime/metal/metal_device_api.mm
+++ b/src/runtime/metal/metal_device_api.mm
@@ -114,7 +114,7 @@ int GetWarpSize(id<MTLDevice> dev) {
       devices.push_back([d retain]);
       queues.push_back([[d newCommandQueue] retain]);
       LOG(INFO) << "Intializing Metal device " << i
-                <<  ", name=" << d.name;
+                <<  ", name=" << [d.name UTF8String];
       warp_size.push_back(GetWarpSize(d));
     }
 #endif
diff --git a/src/runtime/metal/metal_module.mm b/src/runtime/metal/metal_module.mm
index bf7f606c952d..4814d986b908 100644
--- a/src/runtime/metal/metal_module.mm
+++ b/src/runtime/metal/metal_module.mm
@@ -82,11 +82,15 @@ void SaveToBinary(dmlc::Stream* stream) final {
     NSError* err_msg = nil;
     if (e.lib == nil) {
       if (fmt_ == "metal") {
+        MTLCompileOptions *opts = [MTLCompileOptions alloc];
+        opts.languageVersion = MTLLanguageVersion2_0;
+        opts.fastMathEnabled = YES;
         e.lib = [
             w->devices[device_id]
              newLibraryWithSource:[NSString stringWithUTF8String:data_.c_str()]
-             options:nil
+             options:opts
              error:&err_msg];
+        [opts dealloc];
         if (err_msg != nil || e.lib == nil) {
           LOG(FATAL) << "Fail to compile metal lib:"
                      << [[err_msg localizedDescription] UTF8String];

From 1accb6d9457af18783f00bd1dfb38be3bf2b35e5 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 11 Apr 2018 11:43:38 -0700
Subject: [PATCH 259/949] [PASS] More simplifier for mod and div (#1100)

* [PASS] More simplifier for mod and div

* fix testcase
---
 src/arithmetic/canonical.cc                  | 103 +++++++++++++++----
 src/pass/lower_warp_memory.cc                |   4 +-
 tests/python/unittest/test_arith_simplify.py |  20 ++++
 tests/python/unittest/test_pass_simplify.py  |   2 +-
 4 files changed, 108 insertions(+), 21 deletions(-)

diff --git a/src/arithmetic/canonical.cc b/src/arithmetic/canonical.cc
index 736b8dad78c7..ed6239961a3b 100644
--- a/src/arithmetic/canonical.cc
+++ b/src/arithmetic/canonical.cc
@@ -312,9 +312,23 @@ class Canonical::Internal : public IRMutator {
       return e;
     }
   }
-  // binary ops
+  // Div operator
   Expr Mutate_(const Div* op, const Expr& e) final {
-    return Binary(op, e);
+    if (!EnableOpt(op->type)) {
+      return Binary(op, e);
+    }
+    CacheEntry a = Produce(op->a);
+    CacheEntry b = Produce(op->b);
+    if (a.has_side_effect || b.has_side_effect) {
+      return Binary_(op, e, a.value, b.value);
+    }
+    if (is_const(a.value) && is_const(b.value)) {
+      return ComputeExpr<Div>(a.value, b.value);
+    } else if (is_const(b.value)) {
+      return SumDivConst(a.AsSum(), b.value);
+    } else {
+      return Binary(op, e);
+    }
   }
   // Mod operator
   Expr Mutate_(const Mod* op, const Expr& e) final {
@@ -445,29 +459,80 @@ class Canonical::Internal : public IRMutator {
     }
     return value;
   }
-  // subroutine to do produce a % v
-  Expr SumModConst(ComExpr a, Expr v) {
-    int64_t value = GetConstIntValue(v);
-    std::shared_ptr<ComExprNode> n = std::make_shared<ComExprNode>();
-    int mod_level = 0;
-    n->base = a->base % value;
-    if (n->base != 0) mod_level = 1;
-    for (auto e : a->elem) {
-      if (e.scale % value == 0) continue;
-      e.scale = e.scale % value;
-      if (!EvalSet(v - e.value, var_range_).can_prove_positive()) {
-        mod_level = 2;
+  // Detect if a = x * coeff + y, where y \in [0, coeff), x >= 0
+  // return true if such detection is successful
+  // return false if it is not.
+  std::vector<ComExpr> TryLinearEquation(const ComExpr& a,
+                                         const Expr& coeff) {
+    Type type = coeff.type();
+    int64_t value = GetConstIntValue(coeff);
+    if (value < 0) return {};
+    std::shared_ptr<ComExprNode> xnode = std::make_shared<ComExprNode>();
+    std::shared_ptr<ComExprNode> ynode = std::make_shared<ComExprNode>();
+    if (a->base % value == 0) {
+      xnode->base = a->base;
+    } else {
+      ynode->base = a->base;
+    }
+    for (const auto& e : a->elem) {
+      if (e.scale % value == 0) {
+        xnode->elem.push_back(e);
       } else {
-        ++mod_level;
+        ynode->elem.push_back(e);
       }
-      n->elem.push_back(e);
     }
-    // cannot remove mode because there are more than two parts
-    if (mod_level >= 2) {
+    Expr yres = Sum2Expr(ComExpr(ynode), type);
+    IntSet yset = EvalSet(yres, var_range_);
+    // This relies on the integer division rounds down
+    // Most cases it is good for integer division.
+    if (yset.min().type() == type &&
+        can_prove(yset.min() >= make_zero(type)) &&
+        yset.max().type() == type &&
+        can_prove(yset.max() < coeff)) {
+      xnode->base /= value;
+      for (auto &e : xnode->elem) {
+        e.scale /= value;
+      }
+      return {ComExpr(xnode), ComExpr(ynode)};
+    } else {
+      return {};
+    }
+  }
+  // subroutine to do produce a % v
+  Expr SumModConst(ComExpr a, Expr v) {
+    std::vector<ComExpr> pair = TryLinearEquation(a, v);
+    if (pair.size() == 0) {
+      int64_t value = GetConstIntValue(v);
+      std::shared_ptr<ComExprNode> n = std::make_shared<ComExprNode>();
+      n->base = a->base % value;
+      for (auto e : a->elem) {
+        if (e.scale % value == 0) continue;
+        e.scale = e.scale % value;
+        n->elem.push_back(e);
+      }
       Expr ret = Sum2Expr(ComExpr(n), v.type()) % v;
       return Binary(ret.as<Mod>(), ret);
     }
-    ret_entry_.sum = ComExpr(n);
+    ret_entry_.sum = pair[1];
+    ret_entry_.max_level = stack_.back().max_level;
+    ret_entry_.has_side_effect = stack_.back().has_side_effect;
+    auto it = cache_sum_.find(ret_entry_.sum);
+    if (it != cache_sum_.end()) {
+      ret_entry_ = it->second;
+    } else {
+      ret_entry_.value = Sum2Expr(ret_entry_.sum, v.type());
+      cache_sum_[ret_entry_.sum] = ret_entry_;
+    }
+    return ret_entry_.value;
+  }
+  // subroutine to do produce a % v
+  Expr SumDivConst(ComExpr a, Expr v) {
+    std::vector<ComExpr> pair = TryLinearEquation(a, v);
+    if (pair.size() == 0) {
+      Expr ret = Sum2Expr(a, v.type()) / v;
+      return Binary(ret.as<Div>(), ret);
+    }
+    ret_entry_.sum = pair[0];
     ret_entry_.max_level = stack_.back().max_level;
     ret_entry_.has_side_effect = stack_.back().has_side_effect;
     auto it = cache_sum_.find(ret_entry_.sum);
diff --git a/src/pass/lower_warp_memory.cc b/src/pass/lower_warp_memory.cc
index d0412adc08df..8f153fd61188 100644
--- a/src/pass/lower_warp_memory.cc
+++ b/src/pass/lower_warp_memory.cc
@@ -279,7 +279,9 @@ class WarpMemoryRewriter : private IRMutator {
 
   Stmt Rewrite(Stmt stmt) {
     if (warp_size_ == 1) return stmt;
-    return this->Mutate(stmt);
+    stmt = this->Mutate(stmt);
+    stmt = CanonicalSimplify(stmt);
+    return stmt;
   }
 
  private:
diff --git a/tests/python/unittest/test_arith_simplify.py b/tests/python/unittest/test_arith_simplify.py
index 8ce1773ee3c4..e6689dddf9d0 100644
--- a/tests/python/unittest/test_arith_simplify.py
+++ b/tests/python/unittest/test_arith_simplify.py
@@ -37,6 +37,26 @@ def test_simplify_mod():
     assert index == j
 
 
+def test_modular():
+    rx = tvm.var("rx")
+    ry = tvm.var("ry")
+    y = tvm.var("y")
+    x = tvm.var("x")
+    vmap = {rx: tvm.Range(tvm.const(0), tvm.const(3)),
+            ry: tvm.Range(tvm.const(0), tvm.const(3)),
+            y: tvm.Range(tvm.const(0), tvm.const(2)),
+            x: tvm.Range(tvm.const(0), tvm.const(14))}
+    idx = ry * 16 + rx + y * 16 + x
+    z1 = tvm.ir_pass.CanonicalSimplify(idx // 16, vmap)
+    z2 = tvm.ir_pass.CanonicalSimplify(idx % 16, vmap)
+    assert tvm.ir_pass.CanonicalSimplify(z1 - (ry + y)).value == 0
+    assert tvm.ir_pass.CanonicalSimplify(z2 - (rx + x)).value == 0
+
+
+
+
+
 if __name__ == "__main__":
     test_simplify_mod()
+    test_modular()
     test_simplify()
diff --git a/tests/python/unittest/test_pass_simplify.py b/tests/python/unittest/test_pass_simplify.py
index 29b5b3a8450d..c38083822fe2 100644
--- a/tests/python/unittest/test_pass_simplify.py
+++ b/tests/python/unittest/test_pass_simplify.py
@@ -33,7 +33,6 @@ def test_bound():
     ret = tvm.ir_pass.Simplify(m % 10, vrange)
     assert ret == m
 
-
 def test_canonical():
     x = tvm.var("x")
     z = tvm.const(3)
@@ -54,6 +53,7 @@ def test_canonical():
     assert (tvm.ir_pass.Equal(ret1, ret2))
 
 if __name__ == "__main__":
+    test_modular()
     test_bound()
     test_basic()
     test_simplify()

From 647a48bf12507214abd3742d091270b53fc10297 Mon Sep 17 00:00:00 2001
From: kun-zh <32951065+kun-zh@users.noreply.github.com>
Date: Thu, 12 Apr 2018 10:12:24 +0800
Subject: [PATCH 260/949] Generate Lower Bound Conditions for issue 1014
 (#1091)

---
 src/schedule/message_passing.cc                     |  9 +++++++--
 .../python/unittest/test_pass_inject_copy_intrin.py |  1 +
 tests/python/unittest/test_schedule_schedule_ops.py | 13 +++++++++++++
 3 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/src/schedule/message_passing.cc b/src/schedule/message_passing.cc
index a144e7fc40d1..3cea560318d8 100644
--- a/src/schedule/message_passing.cc
+++ b/src/schedule/message_passing.cc
@@ -477,9 +477,14 @@ std::vector<Expr> MakeBoundCheck(
     CHECK(iv->dom.defined());
     if (!skip_ivar_domain && !iv->dom.same_as(dom)) {
       Expr value = ComputeExpr<Sub>(value_map.at(iv), iv->dom->min);
-      Expr vmax = EvalSet(value, iset_dmap).max();
+      IntSet s = EvalSet(value, iset_dmap);
+      Expr vmin = s.min();
+      Expr vmax = s.max();
+      if (vmin.type() != value.type() || !can_prove(vmin >= iv->dom->min)) {
+        preds.emplace_back(value >= 0);
+      }
       if (vmax.type() != value.type() || !can_prove(vmax < iv->dom->extent)) {
-        preds.emplace_back(value < iv->dom->extent);
+        preds.emplace_back(value < (iv->dom->extent - iv->dom->min));
       }
     }
   }
diff --git a/tests/python/unittest/test_pass_inject_copy_intrin.py b/tests/python/unittest/test_pass_inject_copy_intrin.py
index c6ed19d65b69..a44f3899c282 100644
--- a/tests/python/unittest/test_pass_inject_copy_intrin.py
+++ b/tests/python/unittest/test_pass_inject_copy_intrin.py
@@ -82,6 +82,7 @@ def test_copy_pad_split():
     Ab = tvm.decl_buffer(A.shape, A.dtype, name='A')
     Bb = tvm.decl_buffer(B.shape, B.dtype, name='B')
     stmt = tvm.ir_pass.StorageFlatten(stmt, {A: Ab, B: Bb}, 64)
+    stmt = tvm.ir_pass.Simplify(stmt)
     stmt = tvm.ir_pass.CanonicalSimplify(stmt)
     def cb(src, dst, pad_before, pad_after, pad_value):
         assert(dst.elem_offset.value == 0)
diff --git a/tests/python/unittest/test_schedule_schedule_ops.py b/tests/python/unittest/test_schedule_schedule_ops.py
index 03b8dbf48c8c..dd57c35c39b6 100644
--- a/tests/python/unittest/test_schedule_schedule_ops.py
+++ b/tests/python/unittest/test_schedule_schedule_ops.py
@@ -249,6 +249,18 @@ def test_schedule_cache_relayout3():
     stmt = tvm.schedule.ScheduleOps(s, bounds)
 
 
+def test_schedule_bound_condition():
+   A = tvm.placeholder((64,), name='A', dtype="float32")
+   Apad = tvm.compute((66,), lambda i: tvm.select(tvm.all(i>0, i < 65), A[i-1], tvm.const(0.)), name='Apad')
+   Apad2 = tvm.compute((66,), lambda i: Apad[i]*2, name='Apad2')
+   s = tvm.create_schedule(Apad2.op)
+   AL1 = s.cache_read(A,"local",[Apad])
+   s = s.normalize()
+   bounds = tvm.schedule.InferBound(s)
+   stmt = tvm.schedule.ScheduleOps(s, bounds)
+   stmt = tvm.ir_pass.Simplify(stmt)
+   assert (isinstance(stmt.body.body.first.body.body.then_case, tvm.stmt.IfThenElse))
+
 if __name__ == "__main__":
     test_schedule_middle_cache()
     test_inline_multi_reduce()
@@ -265,3 +277,4 @@ def test_schedule_cache_relayout3():
     test_schedule1()
     test_schedule2()
     test_schedule_cache()
+    test_schedule_bound_condition()

From a20e7ff1a3f18747b638a241ba8c81aeace6530c Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Fri, 13 Apr 2018 05:30:41 +0530
Subject: [PATCH 261/949] RPC session connection request information persist
 app locally (#1098)

---
 .../java/ml/dmlc/tvm/tvmrpc/MainActivity.java | 23 +++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/MainActivity.java b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/MainActivity.java
index 62c877e8b34c..3fc3fc3f1381 100644
--- a/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/MainActivity.java
+++ b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/MainActivity.java
@@ -19,7 +19,9 @@
 
 import android.annotation.SuppressLint;
 import android.app.AlertDialog;
+import android.content.Context;
 import android.content.DialogInterface;
+import android.content.SharedPreferences;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
@@ -86,6 +88,7 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
         }
       }
     });
+    enableInputView(true);
   }
 
   @Override
@@ -104,6 +107,13 @@ private void connectProxy() {
     final String key = edAppKey.getText().toString();
 
     tvmServerWorker.connect(proxyHost, proxyPort, key);
+
+    SharedPreferences pref = getApplicationContext().getSharedPreferences("RPCProxyPreference", Context.MODE_PRIVATE);
+    SharedPreferences.Editor editor = pref.edit();
+    editor.putString("input_address", proxyHost);
+    editor.putString("input_port", edProxyPort.getText().toString());
+    editor.putString("input_key", key);
+    editor.commit();
   }
 
   private void disconnect() {
@@ -118,5 +128,18 @@ private void enableInputView(boolean enable) {
     edProxyAddress.setEnabled(enable);
     edProxyPort.setEnabled(enable);
     edAppKey.setEnabled(enable);
+
+    if (enable) {
+    SharedPreferences pref = getApplicationContext().getSharedPreferences("RPCProxyPreference", Context.MODE_PRIVATE);
+    String inputAddress = pref.getString("input_address", null);
+    if (null != inputAddress)
+        edProxyAddress.setText(inputAddress);
+    String inputPort = pref.getString("input_port", null);
+    if (null != inputPort)
+        edProxyPort.setText(inputPort);
+    String inputKey = pref.getString("input_key", null);
+    if (null != inputKey)
+        edAppKey.setText(inputKey);
+    }
   }
 }

From 3ff0d6c434fc546d58cd64992d624fbda84e1c8a Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 12 Apr 2018 17:23:20 -0700
Subject: [PATCH 262/949] [RPC] LocalSession to provide RPCSession back by
 local env (#1102)

---
 python/tvm/contrib/rpc/__init__.py        |  2 +-
 python/tvm/contrib/rpc/client.py          | 86 ++++++++++++++++-------
 python/tvm/contrib/rpc/server.py          | 13 +---
 python/tvm/module.py                      | 19 ++++-
 tests/python/unittest/test_runtime_rpc.py | 26 +++++--
 5 files changed, 102 insertions(+), 44 deletions(-)

diff --git a/python/tvm/contrib/rpc/__init__.py b/python/tvm/contrib/rpc/__init__.py
index 3335240ac5d5..c3fbf5be2928 100644
--- a/python/tvm/contrib/rpc/__init__.py
+++ b/python/tvm/contrib/rpc/__init__.py
@@ -10,4 +10,4 @@
 """
 
 from .server import Server
-from .client import RPCSession, connect, connect_tracker
+from .client import RPCSession, LocalSession, connect, connect_tracker
diff --git a/python/tvm/contrib/rpc/client.py b/python/tvm/contrib/rpc/client.py
index 24b3b9582831..f409b2f72288 100644
--- a/python/tvm/contrib/rpc/client.py
+++ b/python/tvm/contrib/rpc/client.py
@@ -7,8 +7,11 @@
 import time
 
 from . import base
+from .. import util
 from ..._ffi.base import TVMError
-from ..._ffi.ndarray import context as _context
+from ..._ffi import function as function
+from ..._ffi import ndarray as nd
+from ...module import load as _load_module
 
 
 class RPCSession(object):
@@ -51,36 +54,12 @@ def context(self, dev_type, dev_id=0):
         ctx: TVMContext
             The corresponding encoded remote context.
         """
-        ctx = _context(dev_type, dev_id)
+        ctx = nd.context(dev_type, dev_id)
         encode = (self._tbl_index + 1) * base.RPC_SESS_MASK
         ctx.device_type += encode
         ctx._rpc_sess = self
         return ctx
 
-    def cpu(self, dev_id=0):
-        """Construct remote CPU device."""
-        return self.context(1, dev_id)
-
-    def gpu(self, dev_id=0):
-        """Construct remote GPU device."""
-        return self.context(2, dev_id)
-
-    def cl(self, dev_id=0):
-        """Construct remote OpenCL device."""
-        return self.context(4, dev_id)
-
-    def metal(self, dev_id=0):
-        """Construct remote Metal device."""
-        return self.context(8, dev_id)
-
-    def opengl(self, dev_id=0):
-        """Construct remote OpenGL device."""
-        return self.context(11, dev_id)
-
-    def ext_dev(self, dev_id=0):
-        """Construct remote extension device."""
-        return self.context(12, dev_id)
-
     def upload(self, data, target=None):
         """Upload file to remote runtime temp folder
 
@@ -139,6 +118,61 @@ def load_module(self, path):
         """
         return base._LoadRemoteModule(self._sess, path)
 
+    def cpu(self, dev_id=0):
+        """Construct CPU device."""
+        return self.context(1, dev_id)
+
+    def gpu(self, dev_id=0):
+        """Construct GPU device."""
+        return self.context(2, dev_id)
+
+    def cl(self, dev_id=0):
+        """Construct OpenCL device."""
+        return self.context(4, dev_id)
+
+    def metal(self, dev_id=0):
+        """Construct Metal device."""
+        return self.context(8, dev_id)
+
+    def opengl(self, dev_id=0):
+        """Construct OpenGL device."""
+        return self.context(11, dev_id)
+
+    def ext_dev(self, dev_id=0):
+        """Construct extension device."""
+        return self.context(12, dev_id)
+
+
+class LocalSession(RPCSession):
+    """RPCSession interface backed by local environment.
+
+    This class can be used to implement functions that
+    need to be ran both locally and remotely.
+    """
+    def __init__(self):
+        # pylint: disable=super-init-not-called
+        self.context = nd.context
+        self.get_function = function.get_global_func
+        self._temp = util.tempdir()
+
+    def upload(self, data, target=None):
+        if isinstance(data, bytearray):
+            if not target:
+                raise ValueError("target must present when file is a bytearray")
+            blob = data
+        else:
+            blob = bytearray(open(data, "rb").read())
+            if not target:
+                target = os.path.basename(data)
+        with open(self._temp.relpath(target), "wb") as f:
+            f.write(blob)
+
+    def download(self, path):
+        return bytearray(open(self._temp.relpath(path), "rb").read())
+
+    def load_module(self, path):
+        return _load_module(self._temp.relpath(path))
+
 
 class TrackerSession(object):
     """Tracker client session.
diff --git a/python/tvm/contrib/rpc/server.py b/python/tvm/contrib/rpc/server.py
index 8cd1c4ee883f..6759f13b6753 100644
--- a/python/tvm/contrib/rpc/server.py
+++ b/python/tvm/contrib/rpc/server.py
@@ -22,7 +22,7 @@
 from ..._ffi.function import register_func
 from ..._ffi.base import py_str
 from ...module import load as _load_module
-from .. import util, cc, tar
+from .. import util
 from . import base
 from . base import TrackerCode
 
@@ -38,17 +38,6 @@ def get_workpath(path):
     def load_module(file_name):
         """Load module from remote side."""
         path = temp.relpath(file_name)
-        # Try create a shared library in remote
-        if path.endswith(".o"):
-            logging.info("Create shared library based on %s", path)
-            cc.create_shared(path + ".so", path)
-            path += ".so"
-        elif path.endswith(".tar"):
-            tar_temp = util.tempdir()
-            tar.untar(path, tar_temp.temp_dir)
-            files = [tar_temp.relpath(x) for x in tar_temp.listdir()]
-            cc.create_shared(path + ".so", files)
-            path += ".so"
         m = _load_module(path)
         logging.info("load_module %s", path)
         return m
diff --git a/python/tvm/module.py b/python/tvm/module.py
index 6459733fa15e..1b83c9b26243 100644
--- a/python/tvm/module.py
+++ b/python/tvm/module.py
@@ -186,7 +186,7 @@ def system_lib():
 
 
 def load(path, fmt=""):
-    """Load module from file
+    """Load module from file.
 
     Parameters
     ----------
@@ -201,7 +201,24 @@ def load(path, fmt=""):
     -------
     module : Module
         The loaded module
+
+    Note
+    ----
+    This function will automatically call
+    cc.create_shared if the path is in format .o or .tar
     """
+    # High level handling for .o and .tar file.
+    # We support this to be consistent with RPC module load.
+    if path.endswith(".o"):
+        _cc.create_shared(path + ".so", path)
+        path += ".so"
+    elif path.endswith(".tar"):
+        tar_temp = _util.tempdir()
+        _tar.untar(path, tar_temp.temp_dir)
+        files = [tar_temp.relpath(x) for x in tar_temp.listdir()]
+        _cc.create_shared(path + ".so", files)
+        path += ".so"
+    # Redirect to the load API
     return _LoadFromFile(path, fmt)
 
 
diff --git a/tests/python/unittest/test_runtime_rpc.py b/tests/python/unittest/test_runtime_rpc.py
index 46e84d887762..581890efe64f 100644
--- a/tests/python/unittest/test_runtime_rpc.py
+++ b/tests/python/unittest/test_runtime_rpc.py
@@ -61,14 +61,14 @@ def test_rpc_remote_module():
     if not tvm.module.enabled("rpc"):
         return
     server = rpc.Server("localhost")
-    remote = rpc.connect(server.host, server.port)
+    client = rpc.connect(server.host, server.port)
     # graph
     n = tvm.convert(1024)
     A = tvm.placeholder((n,), name='A')
     B = tvm.compute(A.shape, lambda *i: A(*i) + 1.0, name='B')
     s = tvm.create_schedule(B.op)
 
-    def check_remote():
+    def check_remote(remote):
         if not tvm.module.enabled("llvm"):
             print("Skip because llvm is not enabled")
             return
@@ -86,7 +86,7 @@ def check_remote():
         print('%g secs/op' % cost)
         np.testing.assert_equal(b.asnumpy(), a.asnumpy() + 1)
 
-    def check_remote_link_cl():
+    def check_remote_link_cl(remote):
         """Test function to run remote code such as cl
 
         This is not enabled because there is forking issue
@@ -134,7 +134,9 @@ def check_remote_link_cl():
         fhost(a, b)
         np.testing.assert_equal(b.asnumpy(), a.asnumpy() + 1)
 
-    check_remote()
+    check_remote(client)
+    check_remote(rpc.LocalSession())
+
 
 def test_rpc_return_func():
     @tvm.register_func("rpc.test.remote_func")
@@ -147,6 +149,21 @@ def addone(x):
     assert fadd(12) == 22
 
 
+def test_local_func():
+    @tvm.register_func("rpc.test.remote_func2")
+    def addone(x):
+        return lambda y: x+y
+    client = rpc.LocalSession()
+    f1 = client.get_function("rpc.test.remote_func2")
+    fadd = f1(10)
+    assert fadd(12) == 22
+
+    blob = bytearray(np.random.randint(0, 10, size=(10)))
+    client.upload(blob, "dat.bin")
+    rev = client.download("dat.bin")
+    assert rev == blob
+
+
 if __name__ == "__main__":
     logging.basicConfig(level=logging.INFO)
     test_rpc_remote_module()
@@ -154,3 +171,4 @@ def addone(x):
     test_rpc_file_exchange()
     test_rpc_array()
     test_rpc_simple()
+    test_local_func()

From b242b946ae08180d59ab90f78419347be84b98d6 Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Fri, 13 Apr 2018 22:23:08 +0530
Subject: [PATCH 263/949] [TOPI] LRN & L2norm Operator (#1051)

---
 topi/python/topi/cuda/__init__.py          |  1 +
 topi/python/topi/cuda/nn.py                | 91 +++++++++++++++++++++
 topi/python/topi/generic/nn.py             | 36 ++++++++
 topi/python/topi/nn/__init__.py            |  2 +
 topi/python/topi/nn/l2_norm.py             | 35 ++++++++
 topi/python/topi/nn/local_response_norm.py | 68 ++++++++++++++++
 topi/python/topi/rocm/__init__.py          |  1 +
 topi/python/topi/rocm/nn.py                | 13 +++
 topi/tests/python/test_topi_l2norm.py      | 70 ++++++++++++++++
 topi/tests/python/test_topi_lrn.py         | 95 ++++++++++++++++++++++
 10 files changed, 412 insertions(+)
 create mode 100644 topi/python/topi/cuda/nn.py
 create mode 100644 topi/python/topi/nn/l2_norm.py
 create mode 100644 topi/python/topi/nn/local_response_norm.py
 create mode 100644 topi/python/topi/rocm/nn.py
 create mode 100644 topi/tests/python/test_topi_l2norm.py
 create mode 100644 topi/tests/python/test_topi_lrn.py

diff --git a/topi/python/topi/cuda/__init__.py b/topi/python/topi/cuda/__init__.py
index 174a37b1d451..74e92384a9e0 100644
--- a/topi/python/topi/cuda/__init__.py
+++ b/topi/python/topi/cuda/__init__.py
@@ -16,3 +16,4 @@
 from .conv2d_transpose_nchw import schedule_conv2d_transpose_nchw
 from .extern import schedule_extern
 from .vision import schedule_region
+from .nn import schedule_lrn, schedule_l2norm
diff --git a/topi/python/topi/cuda/nn.py b/topi/python/topi/cuda/nn.py
new file mode 100644
index 000000000000..e8757970505b
--- /dev/null
+++ b/topi/python/topi/cuda/nn.py
@@ -0,0 +1,91 @@
+# pylint: disable=invalid-name
+"""scheduler functions for cuda backend"""
+from __future__ import absolute_import as _abs
+
+import tvm
+from .. import generic
+from .. import tag
+from .reduction import _schedule_reduce
+
+@generic.schedule_lrn.register(["cuda"])
+def schedule_lrn(outs):
+    """Schedule for LRN
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+          The computation graph description of LRN
+          in the format of an array of tensors.
+
+    Returns
+    -------
+    sch: Schedule
+        The computation schedule for the op.
+    """
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    s = tvm.create_schedule([x.op for x in outs])
+    num_thread = 64
+    block_x = tvm.thread_axis("blockIdx.x")
+    thread_x = tvm.thread_axis((0, num_thread), "threadIdx.x")
+
+    lrn = outs[0]
+    sqr_sum_up = lrn.op.input_tensors[1]
+    sqr_sum = sqr_sum_up.op.input_tensors[0]
+    set_pad = sqr_sum.op.input_tensors[0]
+    s[set_pad].bind(set_pad.op.axis[0], block_x)
+    rxk = sqr_sum.op.reduce_axis[0]
+    _, xki = s[sqr_sum].split(rxk, factor=num_thread)
+    srf = s.rfactor(sqr_sum, xki)
+    s[sqr_sum].bind(s[sqr_sum].op.axis[0], block_x)
+    s[sqr_sum].bind(s[sqr_sum].op.reduce_axis[0], thread_x)
+    s[srf].compute_at(s[sqr_sum], s[sqr_sum].op.reduce_axis[0])
+    s[sqr_sum_up].bind(sqr_sum_up.op.axis[0], block_x)
+    xto, _ = s[lrn].split(lrn.op.axis[1], nparts=num_thread)
+    s[lrn].bind(lrn.op.axis[0], block_x)
+    s[lrn].bind(xto, thread_x)
+    return s
+
+@generic.schedule_l2norm.register(["cuda"])
+def schedule_l2norm(outs):
+    """Schedule for L2norm
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+          The computation graph description of L2norm
+          in the format of an array of tensors.
+
+    Returns
+    -------
+    sch: Schedule
+        The computation schedule for the op.
+    """
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    s = tvm.create_schedule([x.op for x in outs])
+
+    def traverse(OP):
+        '''inline all one-to-one-mapping operators
+        except the last stage (output)'''
+        if tag.is_injective(OP.tag) or OP.tag == 'l2norm':
+            if OP not in s.outputs:
+                s[OP].compute_inline()
+            for tensor in OP.input_tensors:
+                if tensor.op.input_tensors:
+                    traverse(tensor.op)
+        elif OP.tag == 'comm_reduce':
+            _schedule_reduce(OP, s, is_idx_reduce=False)
+            for tensor in OP.input_tensors:
+                traverse(tensor.op)
+        else:
+            raise RuntimeError("Unsupported operator tag: %s" % OP.tag)
+    traverse(outs[0].op)
+
+    num_thread = 64
+    l2norm = outs[0]
+    block_x = tvm.thread_axis("blockIdx.x")
+    thread_x = tvm.thread_axis((0, num_thread), "threadIdx.x")
+    xto, _ = s[l2norm].split(l2norm.op.axis[1], nparts=num_thread)
+    s[l2norm].bind(l2norm.op.axis[0], block_x)
+    s[l2norm].bind(xto, thread_x)
+
+    return s
diff --git a/topi/python/topi/generic/nn.py b/topi/python/topi/generic/nn.py
index 7fe76e1739f0..7252a23b90c3 100644
--- a/topi/python/topi/generic/nn.py
+++ b/topi/python/topi/generic/nn.py
@@ -229,3 +229,39 @@ def schedule_binary_dense(outs):
         The computation schedule for the op.
     """
     return _default_schedule(outs, False)
+
+
+@tvm.target.generic_func
+def schedule_lrn(outs):
+    """Schedule for lrn
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+          The computation graph description of lrn
+          in the format of an array of tensors.
+
+    Returns
+    -------
+    sch: Schedule
+        The computation schedule for the op.
+    """
+    return _default_schedule(outs, False)
+
+
+@tvm.target.generic_func
+def schedule_l2norm(outs):
+    """Schedule for l2norm
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+          The computation graph description of l2norm
+          in the format of an array of tensors.
+
+    Returns
+    -------
+    sch: Schedule
+        The computation schedule for the op.
+    """
+    return _default_schedule(outs, False)
diff --git a/topi/python/topi/nn/__init__.py b/topi/python/topi/nn/__init__.py
index 918f399f503c..056d1a76339a 100644
--- a/topi/python/topi/nn/__init__.py
+++ b/topi/python/topi/nn/__init__.py
@@ -15,3 +15,5 @@
 from .conv2d_transpose import *
 from .bnn import *
 from .upsampling import *
+from .local_response_norm import *
+from .l2_norm import *
diff --git a/topi/python/topi/nn/l2_norm.py b/topi/python/topi/nn/l2_norm.py
new file mode 100644
index 000000000000..6b5381a85599
--- /dev/null
+++ b/topi/python/topi/nn/l2_norm.py
@@ -0,0 +1,35 @@
+# pylint: disable=invalid-name
+"""TVM operator for l2norm"""
+from __future__ import absolute_import
+import tvm
+import topi
+
+@tvm.target.generic_func
+def l2norm_instance(data, eps, axis=None):
+    """Perform L2norm on the input data
+
+    For axis=None, y(i, j) = x(i, j) / sqrt(max(sum(x^2), eps))
+
+    Parameters
+    ----------
+    data : tvm.Tensor
+        4-D with NCHW or NHWC layout
+
+    eps : float
+        epsilon value
+
+    axis : list of int
+        axis over the normalization applied
+
+    Returns
+    -------
+    output : tvm.Tensor
+        4-D output with same shape
+    """
+    assert len(data.shape) == 4, "only support 4-dim lrn"
+    dot_value = topi.cpp.pow(data, 2.0)
+    sum_value = topi.sum(dot_value, axis=axis, keepdims=True)
+    expand_sum = topi.broadcast_to(sum_value, data.shape)
+    return topi.broadcast_div(data, topi.sqrt(\
+                tvm.compute(expand_sum.shape, lambda i, j, k, l:\
+                tvm.max(expand_sum[i, j, k, l], eps), tag='l2norm')))
diff --git a/topi/python/topi/nn/local_response_norm.py b/topi/python/topi/nn/local_response_norm.py
new file mode 100644
index 000000000000..b44e02214acc
--- /dev/null
+++ b/topi/python/topi/nn/local_response_norm.py
@@ -0,0 +1,68 @@
+# pylint: disable=invalid-name
+"""TVM operator for local response norm compute."""
+from __future__ import absolute_import
+import tvm
+import topi
+from .pad import pad
+
+@tvm.target.generic_func
+def lrn(data, size, axis=1, alpha=0.0001, beta=0.75, bias=2):
+    """Perform the across channels local response normalisation
+    on the input data.
+
+    sum_sqr_up^i{x, y} = (bias+((alpha/size)* \
+                                {sum_{j=max(0, i-size/2)}^{min(N-1,i+size/2)} \
+                                     (data^j{x,y})^2}))^beta
+    output^i{x, y} = data^i{x, y}/sum_sqr_up^i{x, y}
+    N is the number for input channels
+
+    Parameters
+    ----------
+    data : tvm.Tensor
+        4-D with shape [batch, channel, height, width]
+
+    size : int
+        normalisation window size
+
+    axis : int
+        input data layout channel axis
+        default value is 1 for NCHW format
+
+    bias : float
+        offset to avoid dividing by 0
+
+    alpha : float
+        to be divided
+
+    beta : float
+        exponent
+
+    Returns
+    -------
+    output : tvm.Tensor
+        4-D output with same shape
+    """
+    assert len(data.shape) == 4, "only support 4-dim lrn"
+    assert (size % 2) == 1, "size should be odd number"
+    assert (axis == 1) or (axis == 3), "axis should 1 or 3 for NCHW and NHWC"
+    ##Add padding on left & right of size radius first
+    pad_after = pad_before = [0, 0, 0, 0]
+    pad_after[axis] = pad_before[axis] = (size//2)
+    pad_data = pad(data, pad_before, pad_after, name="pad_data")
+
+    rxs = tvm.reduce_axis((0, size), name='rxs')
+    if axis == 1:
+        #NCHW layout
+        sqr_sum = tvm.compute(data.shape, lambda i, j, k, l: tvm.sum(
+            pad_data[i, j + rxs, k, l] * pad_data[i, j + rxs, k, l],
+            axis=rxs))
+    elif axis == 3:
+        #NHWC layout
+        sqr_sum = tvm.compute(data.shape, lambda i, j, k, l: tvm.sum(
+            pad_data[i, j, k, l + rxs] * pad_data[i, j, k, l + rxs],
+            axis=rxs))
+
+    sqr_sum_up = tvm.compute(data.shape, lambda i, j, k, l: tvm.power(
+        (bias + (alpha * sqr_sum[i, j, k, l] / size)), beta))
+
+    return topi.broadcast_div(data, sqr_sum_up)
diff --git a/topi/python/topi/rocm/__init__.py b/topi/python/topi/rocm/__init__.py
index a5b4ee30dc37..96a04794c680 100644
--- a/topi/python/topi/rocm/__init__.py
+++ b/topi/python/topi/rocm/__init__.py
@@ -5,3 +5,4 @@
 from .conv2d import *
 from .dense import *
 from .vision import *
+from .nn import *
diff --git a/topi/python/topi/rocm/nn.py b/topi/python/topi/rocm/nn.py
new file mode 100644
index 000000000000..d9c529155f7b
--- /dev/null
+++ b/topi/python/topi/rocm/nn.py
@@ -0,0 +1,13 @@
+"""scheduler for normalization functions on rocm backend"""
+from __future__ import absolute_import as _abs
+
+import topi
+from .. import generic
+
+@generic.schedule_lrn.register(["rocm", "gpu"])
+def schedule_lrn(outs):
+    return topi.cuda.schedule_lrn(outs)
+
+@generic.schedule_l2norm.register(["rocm", "gpu"])
+def schedule_l2norm(outs):
+    return topi.cuda.schedule_l2norm(outs)
diff --git a/topi/tests/python/test_topi_l2norm.py b/topi/tests/python/test_topi_l2norm.py
new file mode 100644
index 000000000000..aa7970125b44
--- /dev/null
+++ b/topi/tests/python/test_topi_l2norm.py
@@ -0,0 +1,70 @@
+"""Test code for L2 norm"""
+import numpy as np
+import tvm
+import topi
+from topi.util import get_const_tuple
+
+def l2norm_instance_python(a_np, eps, axis=None):
+    """L2 norm operator in NCHW layout.
+
+    Parameters
+    ----------
+    a_np : numpy.ndarray
+        4-D with shape [batch, in_channel, in_height, in_width]
+
+    eps : float
+        epsilon constant value
+    axis : list of int
+        axis over the normalization applied
+
+    Returns
+    -------
+    l2norm_out : np.ndarray
+        4-D with shape [batch, out_channel, out_height, out_width]
+    """
+    batch, axis1, axis2, axis3 = a_np.shape
+    sqr_sum = np.zeros(shape=(batch,)).astype(a_np.dtype)
+    sqrt_sum = np.zeros(shape=(batch,)).astype(a_np.dtype)
+    l2norm_out = np.zeros(shape=a_np.shape).astype(a_np.dtype)
+    dot_value = np.power(a_np, 2.0)
+    sqr_sum = np.sum(dot_value, axis, keepdims=True)
+    sqrt_sum = np.sqrt(np.maximum(np.broadcast_to(sqr_sum, a_np.shape), eps))
+    return np.divide(a_np, sqrt_sum)
+
+def verify_l2norm(n, c, h, w, eps, axis=None):
+
+    A = tvm.placeholder((n, c, h, w), name='A')
+    B = topi.nn.l2norm_instance(A, eps, axis)
+    dtype = A.dtype
+
+    a_np = np.random.uniform(size=(n, c, h, w)).astype(dtype)
+    b_np = l2norm_instance_python(a_np, eps, axis)
+
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s = topi.generic.schedule_l2norm(B)
+        ctx = tvm.context(device, 0)
+        a = tvm.nd.array(a_np, ctx)
+        b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=dtype), ctx)
+        f = tvm.build(s, [A, B], device)
+        f(a, b)
+        np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+    for device in ['llvm', 'cuda', 'opencl', 'metal', 'rocm', 'vulkan']:
+        check_device(device)
+
+def test_l2norm():
+    verify_l2norm(1, 3, 20, 20, 0.001)
+    verify_l2norm(1, 3, 20, 20, 0.001, 1)
+    verify_l2norm(1, 3, 20, 20, 0.001, (1, 2))
+    verify_l2norm(1, 3, 20, 20, 0.001, (2, 3))
+    verify_l2norm(1, 3, 20, 20, 0.001, (0, 3))
+    verify_l2norm(1, 3, 20, 20, 0.001, (0, 2, 3))
+
+
+if __name__ == "__main__":
+    test_l2norm()
diff --git a/topi/tests/python/test_topi_lrn.py b/topi/tests/python/test_topi_lrn.py
new file mode 100644
index 000000000000..6c4714077e1e
--- /dev/null
+++ b/topi/tests/python/test_topi_lrn.py
@@ -0,0 +1,95 @@
+"""Test code for local response normalization"""
+import numpy as np
+import tvm
+import topi
+from topi.util import get_const_tuple
+
+def lrn_python(a_np, size, axis, bias, alpha, beta):
+    """Local response norm operator in NCHW layout.
+
+    Parameters
+    ----------
+    a_np : numpy.ndarray
+        4-D with shape [batch, in_channel, in_height, in_width]
+
+    size : int
+        normalisation window size
+
+    axis : int
+        input data layout channel axis
+
+    bias : float
+        offset to avoid dividing by 0. constant value
+
+    alpha : float
+        contant valie
+
+    beta : float
+        exponent constant value
+
+    Returns
+    -------
+    b_np : np.ndarray
+        4-D with shape [batch, out_channel, out_height, out_width]
+    """
+    axis0, axis1, axis2, axis3 = a_np.shape
+    radius = size // 2
+    sqr_sum = np.zeros(shape=a_np.shape).astype(a_np.dtype)
+    sqr_sum_up = np.zeros(shape=a_np.shape).astype(a_np.dtype)
+    lrn_out = np.zeros(shape=a_np.shape).astype(a_np.dtype)
+    def sum_dot_values(i, j, k, l):
+        axis_size = a_np.shape[axis]
+        if (axis == 1):
+            #NCHW layout
+            sum_start = j-radius if j-radius >= 0 else 0
+            sum_end = j+radius+1 if j+radius+1 < axis_size else axis_size
+            sqr_sum[i, j, k, l] = sum(a_np[i, sum_start:sum_end, k, l] * \
+                                      a_np[i, sum_start:sum_end, k, l])
+        elif (axis == 3):
+            #NHWC layout
+            sum_start = l-radius if l-radius >= 0 else 0
+            sum_end = l+radius+1 if l+radius+1 < axis_size else axis_size
+            sqr_sum[i, j, k, l] = sum(a_np[i, j, k, sum_start:sum_end] * \
+                                      a_np[i, j, k, sum_start:sum_end])
+
+    for i in range(axis0):
+        for j in range(axis1):
+            for k in range(axis2):
+                for l in range(axis3):
+                    sum_dot_values(i, j, k, l)
+
+    sqr_sum_up = np.power((bias + (alpha * sqr_sum /size)), beta)
+    return np.divide(a_np, sqr_sum_up)
+
+def verify_lrn(shape, size, axis, bias, alpha, beta):
+    A = tvm.placeholder(shape, name='A')
+    B = topi.nn.lrn(A, size, axis, alpha, beta, bias)
+    dtype = A.dtype
+
+    a_np = np.random.uniform(size=shape).astype(dtype)
+    b_np = lrn_python(a_np, size, axis, bias, alpha, beta)
+
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s = topi.generic.schedule_lrn(B)
+        ctx = tvm.context(device, 0)
+        a = tvm.nd.array(a_np, ctx)
+        b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=dtype), ctx)
+        f = tvm.build(s, [A, B], device)
+        f(a, b)
+        np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+    for device in ['llvm', 'cuda', 'opencl', 'metal', 'rocm', 'vulkan']:
+        check_device(device)
+
+def test_lrn():
+    verify_lrn((1, 3, 5, 5), 3, 1, 1, 1, 0.5)
+    verify_lrn((1, 3, 5, 5), 3, 3, 1, 1, 0.5)
+    verify_lrn((1, 3, 20, 20), 3, 1, 2, 1, 0.75)
+
+if __name__ == "__main__":
+    test_lrn()

From d64ff0f19049a34a37d8a17d73bb9b90cfa6d14f Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 13 Apr 2018 10:23:08 -0700
Subject: [PATCH 264/949] Fix metal backward compatibility (#1105)

---
 src/runtime/metal/metal_module.mm      | 7 +++++--
 tests/python/integration/test_ewise.py | 7 ++++---
 2 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/src/runtime/metal/metal_module.mm b/src/runtime/metal/metal_module.mm
index 4814d986b908..4d9ffec84e45 100644
--- a/src/runtime/metal/metal_module.mm
+++ b/src/runtime/metal/metal_module.mm
@@ -83,8 +83,11 @@ void SaveToBinary(dmlc::Stream* stream) final {
     if (e.lib == nil) {
       if (fmt_ == "metal") {
         MTLCompileOptions *opts = [MTLCompileOptions alloc];
-        opts.languageVersion = MTLLanguageVersion2_0;
-        opts.fastMathEnabled = YES;
+        // Use the default setting for now.
+        // by default most recent version is used.
+        // opts.languageVersion = MTLLanguageVersion2_0;
+        // opts.fastMathEnabled = YES;
+        opts = nil;
         e.lib = [
             w->devices[device_id]
              newLibraryWithSource:[NSString stringWithUTF8String:data_.c_str()]
diff --git a/tests/python/integration/test_ewise.py b/tests/python/integration/test_ewise.py
index ee880ed1d9fb..3ad464f75faa 100644
--- a/tests/python/integration/test_ewise.py
+++ b/tests/python/integration/test_ewise.py
@@ -98,8 +98,8 @@ def check_device(device):
         check_device("llvm")
         check_device("cuda")
         check_device("opencl")
-        check_device("metal")
         if dtype == "uint32":
+            check_device("metal")
             check_device("vulkan")
     run('uint32')
     run('uint64')
@@ -146,9 +146,10 @@ def check_device(device):
                 c.asnumpy(), a.asnumpy() + b.asnumpy(), rtol=1e-6)
 
         check_device("opencl")
-        check_device("metal")
         check_device("cuda")
-        check_device("vulkan")
+        if dtype == "float32":
+            check_device("metal")
+            check_device("vulkan")
 
     run("float32")
     run("int32")

From cb734f98a33d38a296b209a17b970c7c01fa2cb2 Mon Sep 17 00:00:00 2001
From: nhynes <nhynes@berkeley.edu>
Date: Fri, 13 Apr 2018 14:24:07 -0700
Subject: [PATCH 265/949] Improve SGXModule (#1104)

* Improve SGXModule

* Address code review comments
---
 src/runtime/sgx/trusted/ecall_registry.h |  2 +-
 src/runtime/sgx/trusted/runtime.cc       |  2 +-
 src/runtime/sgx/tvm.edl                  |  2 +-
 src/runtime/sgx/untrusted/sgx_module.cc  | 39 +++++++++++++++++++++---
 4 files changed, 38 insertions(+), 7 deletions(-)

diff --git a/src/runtime/sgx/trusted/ecall_registry.h b/src/runtime/sgx/trusted/ecall_registry.h
index 841b9fb7c172..4bc476aaef61 100644
--- a/src/runtime/sgx/trusted/ecall_registry.h
+++ b/src/runtime/sgx/trusted/ecall_registry.h
@@ -82,7 +82,7 @@ std::vector<ECallRegistry> ECallRegistry::exports_;
  */
 #define TVM_REGISTER_ENCLAVE_FUNC(OpName)                              \
   TVM_STR_CONCAT(TVM_FUNC_REG_VAR_DEF, __COUNTER__) =                  \
-      ::tvm::runtime::sgx::ECallRegistry::Register(OpName)
+      ::tvm::runtime::sgx::ECallRegistry::Register(OpName, true)
 
 }  // namespace sgx
 }  // namespace runtime
diff --git a/src/runtime/sgx/trusted/runtime.cc b/src/runtime/sgx/trusted/runtime.cc
index 314f2625e251..a863327f956c 100644
--- a/src/runtime/sgx/trusted/runtime.cc
+++ b/src/runtime/sgx/trusted/runtime.cc
@@ -45,7 +45,7 @@ void tvm_ecall_packed_func(int func_id,
 
     void* ret_buf;
     TVM_SGX_CHECKED_CALL(tvm_ocall_reserve_space(
-          &ret_buf, bytes.size() + sizeof(TVMByteArray)));
+          &ret_buf, bytes.size() + sizeof(TVMByteArray), sizeof(uint64_t)));
 
     char* data_buf = static_cast<char*>(ret_buf) + sizeof(TVMByteArray);
     memcpy(data_buf, bytes.data(), bytes.size());
diff --git a/src/runtime/sgx/tvm.edl b/src/runtime/sgx/tvm.edl
index 1593e9186ec3..b4d9852f8499 100644
--- a/src/runtime/sgx/tvm.edl
+++ b/src/runtime/sgx/tvm.edl
@@ -22,7 +22,7 @@ enclave {
                                    [in, count=num_ret] const int* type_code,
                                    int num_ret);
         void tvm_ocall_register_export([in, string] const char* name, int func_id);
-        void* tvm_ocall_reserve_space(size_t num_bytes);
+        void* tvm_ocall_reserve_space(size_t num_bytes, size_t alignment);
     };
 };
 
diff --git a/src/runtime/sgx/untrusted/sgx_module.cc b/src/runtime/sgx/untrusted/sgx_module.cc
index 68bfa6d4cbb1..8dd696349b05 100644
--- a/src/runtime/sgx/untrusted/sgx_module.cc
+++ b/src/runtime/sgx/untrusted/sgx_module.cc
@@ -5,6 +5,7 @@
  */
 #include <dmlc/logging.h>
 #include <tvm/runtime/c_runtime_api.h>
+#include <tvm/runtime/device_api.h>
 #include <tvm/runtime/registry.h>
 #include <tvm/runtime/threading_backend.h>
 #include <sgx_urts.h>
@@ -156,6 +157,26 @@ TVM_REGISTER_GLOBAL("__sgx_set_last_error__")
   TVMAPISetLastError(err.c_str());
 });
 
+TVM_REGISTER_GLOBAL("__sgx_println__")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+  std::ostringstream msg;
+  for (int i = 0; i < args.num_args; ++i) {
+    switch (args.type_codes[i]) {
+    case kDLInt: msg << static_cast<int64_t>(args[i]); break;
+    case kDLUInt: msg << static_cast<uint64_t>(args[i]); break;
+    case kDLFloat: msg << static_cast<double>(args[i]); break;
+    case kStr:
+    case kBytes: {
+      std::string val = args[i];
+      msg << val;
+    }
+    break;
+    }
+    msg << " ";
+  }
+  LOG(INFO) << msg.str();
+});
+
 extern "C" {
 
 void tvm_ocall_register_export(const char* name, int func_id) {
@@ -177,10 +198,20 @@ void tvm_ocall_packed_func(const char* name,
 
 // Allocates space for return values. The returned pointer is only valid between
 // successive calls to `tvm_ocall_reserve_space`.
-void* tvm_ocall_reserve_space(size_t num_bytes) {
-  static thread_local std::vector<uint64_t> buf;
-  buf.reserve(num_bytes);
-  return buf.data();
+void* tvm_ocall_reserve_space(size_t num_bytes, size_t alignment) {
+  static TVMContext ctx = { kDLCPU, 0 };
+  static thread_local void* buf = nullptr;
+  static thread_local size_t buf_size = 0;
+  static thread_local size_t buf_align = 0;
+
+  if (buf_size >= num_bytes && buf_align >= alignment) return buf;
+
+  DeviceAPI::Get(ctx)->FreeDataSpace(ctx, buf);
+  buf = DeviceAPI::Get(ctx)->AllocDataSpace(ctx, num_bytes, alignment, {});
+  buf_size = num_bytes;
+  buf_align = alignment;
+
+  return buf;
 }
 
 void tvm_ocall_set_return(TVMRetValueHandle ret,

From 958563fc522b3b9472fbc746124d6acd94206a63 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 13 Apr 2018 14:59:42 -0700
Subject: [PATCH 266/949] [RUNTIME] Fault tolerant vulkan init error (#1107)

---
 src/runtime/vulkan/vulkan_device_api.cc | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)

diff --git a/src/runtime/vulkan/vulkan_device_api.cc b/src/runtime/vulkan/vulkan_device_api.cc
index ce2cea65b5cd..aaf658bbac4b 100644
--- a/src/runtime/vulkan/vulkan_device_api.cc
+++ b/src/runtime/vulkan/vulkan_device_api.cc
@@ -661,13 +661,18 @@ void VulkanWorkspace::Init() {
   std::lock_guard<std::mutex> lock(this->mu);
   if (initialized_) return;
   initialized_ = true;
-  instance_ = CreateInstance();
-  context_ = GetContext(instance_);
-  LOG(INFO) << "Initialzie Vulkan with " << context_.size() << " devices..";
-  for (size_t i = 0; i < context_.size(); ++i) {
-    LOG(INFO) << "vulkan(" << i
-              <<  ")=\'" << context_[i].phy_device_prop.deviceName
-              << "\' phy_dev_id=" << context_[i].phy_device;
+  try {
+    instance_ = CreateInstance();
+    context_ = GetContext(instance_);
+    LOG(INFO) << "Initialzie Vulkan with " << context_.size() << " devices..";
+    for (size_t i = 0; i < context_.size(); ++i) {
+      LOG(INFO) << "vulkan(" << i
+                <<  ")=\'" << context_[i].phy_device_prop.deviceName
+                << "\' phy_dev_id=" << context_[i].phy_device;
+    }
+  } catch (const dmlc::Error& err) {
+    LOG(INFO) << "Cannot initialize vulkan: " << err.what() << "\n"
+              << "You can still compile vulkan module but cannot run locally";
   }
 }
 

From 4a2e17ff7e7233ff0f88b4cf95de9cc1694e3aac Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Sat, 14 Apr 2018 09:35:59 +0800
Subject: [PATCH 267/949] register depthwise conv2d as generic function (#1108)

---
 topi/python/topi/nn/depthwise_conv2d.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/topi/python/topi/nn/depthwise_conv2d.py b/topi/python/topi/nn/depthwise_conv2d.py
index 2f0aa7ea95ce..38e7c7bb9dde 100644
--- a/topi/python/topi/nn/depthwise_conv2d.py
+++ b/topi/python/topi/nn/depthwise_conv2d.py
@@ -9,6 +9,7 @@
 from ..util import simplify
 
 
+@tvm.target.generic_func
 def depthwise_conv2d_nchw(Input, Filter, stride, padding, out_dtype='float32'):
     """Depthwise convolution nchw forward operator.
 
@@ -63,6 +64,7 @@ def depthwise_conv2d_nchw(Input, Filter, stride, padding, out_dtype='float32'):
     return Output
 
 
+@tvm.target.generic_func
 def depthwise_conv2d_nhwc(Input, Filter, stride, padding):
     """Depthwise convolution nhwc forward operator.
 

From 790fbc662314c293db7b337df3ec995072d8991c Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 13 Apr 2018 18:36:33 -0700
Subject: [PATCH 268/949] [RPC] Fix tracker fault handling (#1109)

---
 python/tvm/contrib/rpc/tracker.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/python/tvm/contrib/rpc/tracker.py b/python/tvm/contrib/rpc/tracker.py
index 5dd425ea954a..5e5620042aa4 100644
--- a/python/tvm/contrib/rpc/tracker.py
+++ b/python/tvm/contrib/rpc/tracker.py
@@ -206,7 +206,7 @@ def _cb(value):
                     return False
                 try:
                     self.ret_value([TrackerCode.SUCCESS, value])
-                except (socket.sock_error, IOError):
+                except (socket.error, IOError):
                     return False
                 return True
             self._tracker.request(key, user, priority, _cb)

From 5ff23c763c0250a40e0f6d71c911f554adaa56b7 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 13 Apr 2018 20:06:08 -0700
Subject: [PATCH 269/949] Remove cython init messaging. (#1110)

---
 python/tvm/_ffi/_cython/function.pxi | 1 -
 1 file changed, 1 deletion(-)

diff --git a/python/tvm/_ffi/_cython/function.pxi b/python/tvm/_ffi/_cython/function.pxi
index 50f89a5f4aaa..06cda82624b9 100644
--- a/python/tvm/_ffi/_cython/function.pxi
+++ b/python/tvm/_ffi/_cython/function.pxi
@@ -6,7 +6,6 @@ from ..base import string_types
 from ..node_generic import convert_to_node, NodeGeneric
 from ..runtime_ctypes import TVMType, TVMContext, TVMByteArray
 
-print("TVM: Initializing cython mode...")
 
 cdef void tvm_callback_finalize(void* fhandle):
     local_pyfunc = <object>(fhandle)

From f2db5958cba8f0d867d3abafe71cb5baf8a80c0a Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 14 Apr 2018 20:06:40 -0700
Subject: [PATCH 270/949] pickle memoize no longer print message (#1111)

---
 python/tvm/contrib/pickle_memoize.py      | 2 +-
 topi/python/topi/rasp/__init__.py         | 4 ++--
 topi/python/topi/rasp/conv2d.py           | 8 ++++----
 topi/python/topi/rasp/depthwise_conv2d.py | 2 +-
 4 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/python/tvm/contrib/pickle_memoize.py b/python/tvm/contrib/pickle_memoize.py
index 097eb0cb6784..32edd4025e84 100644
--- a/python/tvm/contrib/pickle_memoize.py
+++ b/python/tvm/contrib/pickle_memoize.py
@@ -5,6 +5,7 @@
 import atexit
 from decorator import decorate
 from .._ffi.base import string_types
+
 try:
     import cPickle as pickle
 except ImportError:
@@ -79,7 +80,6 @@ def _memoized_f(func, *args, **kwargs):
                 else:
                     assert isinstance(arg, allow_types)
             if key in cache.cache:
-                print("Use memoize {0}{1}".format(fkey, key))
                 return cache.cache[key]
             res = func(*args)
             cache.cache[key] = res
diff --git a/topi/python/topi/rasp/__init__.py b/topi/python/topi/rasp/__init__.py
index 2ac059128c60..31ecea5aba4e 100644
--- a/topi/python/topi/rasp/__init__.py
+++ b/topi/python/topi/rasp/__init__.py
@@ -2,5 +2,5 @@
 """Raspberry pi specific declaration and schedules."""
 from __future__ import absolute_import as _abs
 
-from .conv2d import *
-from .depthwise_conv2d import *
+from .conv2d import schedule_conv2d_nchw
+from .depthwise_conv2d import schedule_depthwise_conv2d_nchw
diff --git a/topi/python/topi/rasp/conv2d.py b/topi/python/topi/rasp/conv2d.py
index 73953c016a57..6b2ce832e676 100644
--- a/topi/python/topi/rasp/conv2d.py
+++ b/topi/python/topi/rasp/conv2d.py
@@ -4,7 +4,7 @@
 import tvm
 from tvm import target as _target
 from .. import tag
-from ..nn.conv2d import conv2d, _get_schedule
+from ..nn.conv2d import conv2d as _conv2d, _get_schedule
 from ..nn.conv2d import SpatialPack, Im2ColPack
 from ..nn.conv2d import _WORKLOADS, _SCH_TO_DECL_FUNC
 from ..nn.conv2d import _get_workload
@@ -66,7 +66,7 @@
 ]
 
 @_get_schedule.register("rasp")
-def _schedule_conv2d(wkl):
+def _get_schedule_conv2d(wkl):
     if wkl not in _WORKLOADS:
         raise ValueError("no schedule for such workload: {}".format(wkl))
     idx = _WORKLOADS.index(wkl)
@@ -74,7 +74,7 @@ def _schedule_conv2d(wkl):
     return sch
 
 
-@conv2d.register("rasp")
+@_conv2d.register("rasp")
 def _declaration_conv2d(data, kernel, stride, padding, layout, out_dtype):
     if out_dtype is None:
         out_dtype = data.dtype
@@ -309,7 +309,7 @@ def _schedule_im2col_conv2d(s, data, data_pad, data_col, data_vec,
     return s
 
 @generic.schedule_conv2d_nchw.register(["rasp"])
-def schedule_conv2d(outs):
+def schedule_conv2d_nchw(outs):
     """Create schedule for tensors"""
     s = tvm.create_schedule([x.op for x in outs])
 
diff --git a/topi/python/topi/rasp/depthwise_conv2d.py b/topi/python/topi/rasp/depthwise_conv2d.py
index 34dc1f6caef9..bb8ebed3465f 100644
--- a/topi/python/topi/rasp/depthwise_conv2d.py
+++ b/topi/python/topi/rasp/depthwise_conv2d.py
@@ -164,7 +164,7 @@ def _schedule(s, data, data_pad, kernel, output, last):
 
 
 @generic.schedule_depthwise_conv2d_nchw.register(["cpu", "rasp"])
-def schedule_depthwise_conv2d(outs):
+def schedule_depthwise_conv2d_nchw(outs):
     """Schedule for depthwise_conv2d nchw forward.
 
     Parameters

From 206319aefba79bf8aac1a486eb40ebc49321a014 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 15 Apr 2018 21:12:55 -0700
Subject: [PATCH 271/949] [IR] Change pragma  convention, enable pass unroll
 option via pragma (#1112)

* [IR] Change pragma scope convention, enable pass unroll option via pragma

* add coverage test

* add explicit unroll as option
---
 include/tvm/ir.h                              | 14 +++++-
 include/tvm/schedule.h                        | 16 +++++--
 python/tvm/schedule.py                        |  7 ++-
 src/api/api_lang.cc                           |  2 +-
 src/codegen/llvm/codegen_cpu.cc               | 11 ++---
 src/codegen/spirv/codegen_spirv.cc            |  2 +-
 src/op/op_util.cc                             | 10 +++-
 src/pass/inject_copy_intrin.cc                | 15 +++---
 src/pass/remove_no_op.cc                      |  7 +--
 src/pass/storage_rewrite.cc                   |  6 +--
 src/pass/unroll_loop.cc                       | 21 +++++++++
 src/schedule/schedule_lang.cc                 | 12 +++--
 tests/python/unittest/test_lang_schedule.py   |  2 +-
 tests/python/unittest/test_pass_unroll.py     | 47 +++++++++----------
 .../unittest/test_schedule_schedule_ops.py    |  1 +
 15 files changed, 107 insertions(+), 66 deletions(-)

diff --git a/include/tvm/ir.h b/include/tvm/ir.h
index 9e3c8cbc2be1..6c7bbda0bc27 100644
--- a/include/tvm/ir.h
+++ b/include/tvm/ir.h
@@ -177,8 +177,8 @@ constexpr const char* device_context_type = "device_context_type";
 constexpr const char* loop_scope = "loop_scope";
 /*! \brief Mark of reduce scope */
 constexpr const char* reduce_scope = "reduce_scope";
-/*! \brief Mark region is guarded by the pragma */
-constexpr const char* pragma_scope = "pragma_scope";
+/*! \brief Mark region is guarded by the pragma extension */
+constexpr const char* pragma_scope_prefix = "pragma_";
 /*!
  * \brief Mark of prefetch scope, value=offset,
  *  run prefetch of Tensor on the current loop scope
@@ -233,6 +233,16 @@ constexpr const char* pipeline_exec_scope = "pipeline_exec_scope";
  * Store statement.
  */
 constexpr const char* opengl_stage_scope = "opengl_stage_scope";
+
+/*!
+ * \brief Check if attr_key is a pragma key extension
+ * \param attr_key The attr key to be compared
+ * \return true if it is a pragma key
+ */
+inline bool IsPragmaKey(const std::string& attr_key) {
+  return attr_key.compare(0, 7, "pragma_") == 0;
+}
+
 }  // namespace attr
 
 /*! \brief namespace of TVM Intrinsic functions */
diff --git a/include/tvm/schedule.h b/include/tvm/schedule.h
index 568af8252f4a..6284906c1a4e 100644
--- a/include/tvm/schedule.h
+++ b/include/tvm/schedule.h
@@ -185,10 +185,13 @@ class Stage : public NodeRef {
    *
    * \param var The axis to be parallelized.
    * \param pragma_type The pragma type.
+   * \param pragma_value The pragma value
    *
    * \return reference to self.
    */
-  EXPORT Stage& pragma(IterVar var, const std::string& pragma_type);   // NOLINT(*)
+  EXPORT Stage& pragma(IterVar var,
+                       const std::string& pragma_type,
+                       const Expr& pragma_value = Expr());   // NOLINT(*)
   /*!
    * \brief Fetch data in advance.
    * \param domain the tensor to be prefetched
@@ -539,9 +542,13 @@ class IterVarAttrNode : public Node {
   /*! \brief Alignment offset of buffer dimension */
   int dim_align_offset{0};
   /*!
-   * \brief Additional pragmas, array of StringImm
+   * \brief Additional pragma keys, array of StringImm
    */
-  Array<Expr> pragmas;
+  Array<Expr> pragma_keys;
+  /*!
+   * \brief Additional values of pragma, if any
+   */
+  Array<Expr> pragma_values;
 
   void VisitAttrs(AttrVisitor* v) final {
     v->Visit("iter_type", &iter_type);
@@ -551,7 +558,8 @@ class IterVarAttrNode : public Node {
     v->Visit("tensor_intrin", &tensor_intrin);
     v->Visit("dim_align_factor", &dim_align_factor);
     v->Visit("dim_align_offset", &dim_align_offset);
-    v->Visit("pragmas", &pragmas);
+    v->Visit("pragma_keys", &pragma_keys);
+    v->Visit("pragma_values", &pragma_values);
   }
 
   static constexpr const char* _type_key = "IterVarAttr";
diff --git a/python/tvm/schedule.py b/python/tvm/schedule.py
index 236570c2417c..6305cc1d5e69 100644
--- a/python/tvm/schedule.py
+++ b/python/tvm/schedule.py
@@ -552,7 +552,7 @@ def parallel(self, var):
         """
         _api_internal._StageParallel(self, var)
 
-    def pragma(self, var, pragma_type):
+    def pragma(self, var, pragma_type, pragma_value=None):
         """Annotate the iteration with pragma
 
         This will translate to a pragma_scope surrounding
@@ -567,6 +567,9 @@ def pragma(self, var, pragma_type):
         pragma_type : str
              The pragma string to be annotated
 
+        pragma_value : Expr, optional
+             The pragma value to pass along the pragma
+
         Note
         ----
         Most pragmas are advanced/experimental features
@@ -597,7 +600,7 @@ def pragma(self, var, pragma_type):
           :code:`for (int i = task_id; i < end; i += num_task)`
 
         """
-        _api_internal._StagePragma(self, var, pragma_type)
+        _api_internal._StagePragma(self, var, pragma_type, pragma_value)
 
     def prefetch(self, tensor, var, offset):
         """Prefetch the specified variable
diff --git a/src/api/api_lang.cc b/src/api/api_lang.cc
index d1994340702d..03d085aa8d59 100644
--- a/src/api/api_lang.cc
+++ b/src/api/api_lang.cc
@@ -380,7 +380,7 @@ TVM_REGISTER_API("_StageParallel")
 TVM_REGISTER_API("_StagePragma")
   .set_body([](TVMArgs args, TVMRetValue* ret) {
     args[0].operator Stage()
-        .pragma(args[1], args[2]);
+        .pragma(args[1], args[2], args[3]);
   });
 
 TVM_REGISTER_API("_StagePrefetch")
diff --git a/src/codegen/llvm/codegen_cpu.cc b/src/codegen/llvm/codegen_cpu.cc
index 54aa50427413..bdf7d2114368 100644
--- a/src/codegen/llvm/codegen_cpu.cc
+++ b/src/codegen/llvm/codegen_cpu.cc
@@ -683,16 +683,15 @@ void CodeGenCPU::VisitStmt_(const AttrStmt* op) {
     this->CreateStaticInit(op->value.as<StringImm>()->value, op->body);
   } else  if (op->attr_key == ir::attr::compute_scope) {
     this->CreateComputeScope(op);
-  } else if (op->attr_key == ir::attr::pragma_scope) {
-    const std::string& pname = op->value.as<StringImm>()->value;
-    if (pname == "parallel_stride_pattern") {
+  } else if (attr::IsPragmaKey(op->attr_key)) {
+    if (op->attr_key == "pragma_parallel_stride_pattern") {
       CHECK(parallel_env_.penv != nullptr)
           << "Pragma parallel_stride_pattern only valid in parallel launch";
       parallel_env_.stride_pattern = true;
       this->VisitStmt(op->body);
-    } else if (pname == "parallel_launch_point") {
+    } else if (op->attr_key == "pragma_parallel_launch_point") {
       CreateParallelLaunch(op->body, 0);
-    } else if (pname == "parallel_barrier_when_finish") {
+    } else if (op->attr_key == "pragma_parallel_barrier_when_finish") {
       CHECK(parallel_env_.penv != nullptr)
           << "Cannot run barrier without parallel environment";
       CHECK(!parallel_env_.in_parallel_loop)
@@ -703,7 +702,7 @@ void CodeGenCPU::VisitStmt_(const AttrStmt* op) {
           RuntimeTVMParallelBarrier(),
           {MakeValue(parallel_env_.task_id),  parallel_env_.penv});
     } else {
-      LOG(WARNING) << "Unknown pragma " << pname;
+      LOG(WARNING) << "Unknown pragma " << op->attr_key;
       this->VisitStmt(op->body);
     }
   } else {
diff --git a/src/codegen/spirv/codegen_spirv.cc b/src/codegen/spirv/codegen_spirv.cc
index d844a7b11390..d9a06ab13d34 100644
--- a/src/codegen/spirv/codegen_spirv.cc
+++ b/src/codegen/spirv/codegen_spirv.cc
@@ -77,7 +77,7 @@ spirv::Value CodeGenSPIRV::GetThreadIndex(
   spirv::Value v;
   if (ts.rank == 1) {
     v = builder_->GetLocalID(ts.dim_index);
-    int size;
+    int size = 0;
     CHECK(arith::GetConstInt(extent, &size))
         << "SPIRV only allows constant thread group size " << " get " << extent;
     CHECK_LT(ts.dim_index, 3);
diff --git a/src/op/op_util.cc b/src/op/op_util.cc
index daa489593298..4f34d8d972ce 100644
--- a/src/op/op_util.cc
+++ b/src/op/op_util.cc
@@ -71,9 +71,15 @@ MakeLoopNest(const Stage& stage,
                               << it_attr->iter_type
                               << " in the iter_var_attrs";
         }
-        for (Expr p : it_attr->pragmas) {
+        CHECK_EQ(it_attr->pragma_keys.size(), it_attr->pragma_values.size());
+        for (size_t k = 0; k < it_attr->pragma_keys.size(); ++k) {
+          const std::string& pkey = it_attr->pragma_keys[k].as<StringImm>()->value;
+          Expr pvalue = it_attr->pragma_values[k];
+          if (!pvalue.defined()) {
+            pvalue = make_const(Int(32), 1);
+          }
           nest[i + 1].emplace_back(
-              AttrStmt::make(iv, ir::attr::pragma_scope, p, no_op));
+              AttrStmt::make(iv, ir::attr::pragma_scope_prefix + pkey, pvalue, no_op));
         }
       }
       if (!debug_keep_trivial_loop && is_one(dom->extent)) {
diff --git a/src/pass/inject_copy_intrin.cc b/src/pass/inject_copy_intrin.cc
index ba44253a0cd5..909d46095649 100644
--- a/src/pass/inject_copy_intrin.cc
+++ b/src/pass/inject_copy_intrin.cc
@@ -17,7 +17,7 @@ class CopyIntrinInjector : public IRMutator {
  public:
   CopyIntrinInjector(const std::string& pragma_key,
                      const PackedFunc& flower_copy_fromto)
-      : pragma_key_(pragma_key),
+      : pragma_key_(attr::pragma_scope_prefix+  pragma_key),
         flower_copy_fromto_(flower_copy_fromto) {
   }
 
@@ -25,14 +25,11 @@ class CopyIntrinInjector : public IRMutator {
     if (op->attr_key == attr::storage_scope) {
       const Variable* buf = op->node.as<Variable>();
       storage_scope_[buf] = op->value.as<StringImm>()->value;
-    } else if (op->attr_key == ir::attr::pragma_scope) {
-      const std::string& pname = op->value.as<StringImm>()->value;
-      if (pname == pragma_key_) {
-        Stmt ret;
-        CHECK(MatchCopyPattern(op->body, &ret))
-            << "Cannot match copy pattern of " << op->body;
-        return ret;
-      }
+    } else if (op->attr_key == pragma_key_) {
+      Stmt ret;
+      CHECK(MatchCopyPattern(op->body, &ret))
+          << "Cannot match copy pattern of " << op->body;
+      return ret;
     }
     return IRMutator::Mutate_(op, s);
   }
diff --git a/src/pass/remove_no_op.cc b/src/pass/remove_no_op.cc
index 9547eda076cd..148be28d4715 100644
--- a/src/pass/remove_no_op.cc
+++ b/src/pass/remove_no_op.cc
@@ -20,11 +20,8 @@ class NoOpRemover : public IRMutator {
     return is_no_op(op->body) ? MakeEvaluate(op->value) : stmt;
   }
   Stmt Mutate_(const AttrStmt* op, const Stmt& s) final {
-    if (op->attr_key == ir::attr::pragma_scope) {
-      const std::string& pname = op->value.as<StringImm>()->value;
-      if (pname == "debug_skip_region") {
-        return MakeEvaluate(0);
-      }
+    if (op->attr_key == "pragma_debug_skip_region") {
+      return MakeEvaluate(0);
     }
     Stmt stmt = IRMutator::Mutate_(op, s);
     op = stmt.as<AttrStmt>();
diff --git a/src/pass/storage_rewrite.cc b/src/pass/storage_rewrite.cc
index 998df034e5a1..0170499e1491 100644
--- a/src/pass/storage_rewrite.cc
+++ b/src/pass/storage_rewrite.cc
@@ -401,7 +401,7 @@ class StoragePlanRewriter : public IRMutator {
       return this->Mutate(op->body);
     } else if (op->attr_key == attr::thread_extent ||
                op->attr_key == attr::virtual_thread ||
-               op->attr_key == attr::pragma_scope) {
+               attr::IsPragmaKey(op->attr_key)) {
       // remake all the allocation at the attach scope.
       if (attach_map_.count(op)) {
         auto& svec = attach_map_[op];
@@ -737,8 +737,8 @@ class StoragePlanRewriter : public IRMutator {
       if (s.stmt->is_type<AttrStmt>()) {
         const auto* op = static_cast<const AttrStmt*>(s.stmt);
         if (op->attr_key == attr::thread_extent ||
-            op->attr_key == attr::pragma_scope ||
-            op->attr_key == attr::virtual_thread) {
+            op->attr_key == attr::virtual_thread ||
+            attr::IsPragmaKey(op->attr_key)) {
           PlanNewScope(op);
         } else {
           CHECK(op->attr_key == attr::extern_scope);
diff --git a/src/pass/unroll_loop.cc b/src/pass/unroll_loop.cc
index 22fd389454af..09844cb75531 100644
--- a/src/pass/unroll_loop.cc
+++ b/src/pass/unroll_loop.cc
@@ -27,6 +27,27 @@ class LoopUnroller : public IRMutator {
         explicit_unroll_(explicit_unroll) {
   }
 
+  Stmt Mutate_(const AttrStmt* op, const Stmt& stmt) final {
+    if (op->attr_key == "pragma_auto_unroll_max_step") {
+      int value;
+      CHECK(arith::GetConstInt(op->value, &value));
+      std::swap(value, auto_max_step_);
+      Stmt ret = this->Mutate(op->body);
+      std::swap(value, auto_max_step_);
+      return ret;
+    } else if (op->attr_key == "pragma_unroll_explicit") {
+      int value;
+      CHECK(arith::GetConstInt(op->value, &value));
+      bool explicit_unroll = value;
+      std::swap(explicit_unroll, explicit_unroll_);
+      Stmt ret = this->Mutate(op->body);
+      std::swap(explicit_unroll, explicit_unroll_);
+      return ret;
+    } else {
+      return IRMutator::Mutate_(op, stmt);
+    }
+  }
+
   Stmt Mutate_(const For* op, const Stmt& s) {
     Stmt stmt = IRMutator::Mutate_(op, s);
     op = stmt.as<For>();
diff --git a/src/schedule/schedule_lang.cc b/src/schedule/schedule_lang.cc
index 0bdae67f60a6..5af8f0ac810a 100644
--- a/src/schedule/schedule_lang.cc
+++ b/src/schedule/schedule_lang.cc
@@ -350,15 +350,19 @@ Stage& Stage::parallel(IterVar var) {   // NOLINT(*)
   return *this;
 }
 
-Stage& Stage::pragma(IterVar var, const std::string& pragma_type) {   // NOLINT(*)
+Stage& Stage::pragma(IterVar var,
+                     const std::string& pragma_type,
+                     const Expr& pragma_value) {   // NOLINT(*)
   if (pragma_type == "unroll") {
     this->unroll(var);
   } else if (pragma_type == "vectorize") {
     this->vectorize(var);
   } else {
-    UpdateIterVarAttr(operator->(), var, [pragma_type](IterVarAttrNode* n) {
-        n->pragmas.push_back(ir::StringImm::make(pragma_type));
-      });
+    UpdateIterVarAttr(
+        operator->(), var, [pragma_type, pragma_value](IterVarAttrNode* n) {
+          n->pragma_keys.push_back(ir::StringImm::make(pragma_type));
+          n->pragma_values.push_back(pragma_value);
+        });
   }
   return *this;
 }
diff --git a/tests/python/unittest/test_lang_schedule.py b/tests/python/unittest/test_lang_schedule.py
index b29ebec180df..07e5d98d044f 100644
--- a/tests/python/unittest/test_lang_schedule.py
+++ b/tests/python/unittest/test_lang_schedule.py
@@ -110,7 +110,7 @@ def test_pragma():
     s[T].pragma(xo, "pragma1")
     s[T].pragma(xi, "vectorize")
     VECTORIZE = tvm.schedule.IterVar.Vectorized
-    assert s[T].iter_var_attrs[xo].pragmas[0].value == "pragma1"
+    assert s[T].iter_var_attrs[xo].pragma_keys[0].value == "pragma1"
     assert s[T].iter_var_attrs[xi].iter_type == VECTORIZE
 
 
diff --git a/tests/python/unittest/test_pass_unroll.py b/tests/python/unittest/test_pass_unroll.py
index b0a11b9a9f33..dda3fdad166c 100644
--- a/tests/python/unittest/test_pass_unroll.py
+++ b/tests/python/unittest/test_pass_unroll.py
@@ -1,19 +1,19 @@
 import tvm
 import os
 
+
 def test_unroll_loop():
+    ib = tvm.ir_builder.create()
     dtype = 'int64'
     n = tvm.var('n')
     Ab = tvm.decl_buffer((n, ), dtype)
-    i = tvm.var('i')
-    j = tvm.var('j')
+    Aptr = ib.buffer_ptr(Ab)
     # for i in 0 to n-1:
-    stmt = tvm.make.For(
-        i, n, 2, 0, 0,
-        tvm.make.For(j, 0, 8, 3, 0,
-                     tvm.make.Store(Ab.data,
-                                    tvm.make.Load(dtype, Ab.data, i) + 1,
-                                    j + 1)))
+    with ib.for_range(n, n + 2, name="i") as i:
+        with ib.for_range(0, 8, name="i", for_type="unroll") as j:
+            Aptr[j + 1] = Aptr[i] + 1
+
+    stmt = ib.get()
     assert isinstance(stmt, tvm.stmt.For)
     ret = tvm.ir_pass.UnrollLoop(stmt, 16, 8, 0, True)
     assert not isinstance(ret, tvm.stmt.For)
@@ -23,23 +23,18 @@ def test_unroll_loop():
     assert isinstance(ret, tvm.stmt.For)
     assert ret.for_type == tvm.stmt.For.Unrolled
 
+    ib = tvm.ir_builder.create()
+    ib.scope_attr(tvm.const(0), "pragma_auto_unroll_max_step", 16)
+    ib.emit(stmt)
+    wrapped = ib.get()
+    wrapped = tvm.make.Block(wrapped, stmt)
+    assert isinstance(ret, tvm.stmt.For)
+    ret = tvm.ir_pass.UnrollLoop(wrapped, 0, 8, 0, False)
+    assert isinstance(ret.first, tvm.stmt.For)
+    assert ret.first.for_type == tvm.stmt.For.Unrolled
+    assert isinstance(ret.rest, tvm.stmt.For)
+    assert ret.rest.for_type != tvm.stmt.For.Unrolled
 
-if __name__ == "__main__":
-    with tvm.build_config(dump_pass_ir=True):
-        test_unroll_loop()
-
-    def end_with(*suffix):
-        ends = suffix
-        def run(s):
-            f = map(s.endswith, ends)
-            if True in f: return s
-        return run
 
-    file_list = os.listdir('./')
-    cc_file = end_with('.cc')
-    cc_file = filter(cc_file, file_list)
-    cc_file = [f for f in cc_file]
-    assert len(cc_file) == 3
-    for i in cc_file:
-        os.remove(i)
-    
+if __name__ == "__main__":
+    test_unroll_loop()
diff --git a/tests/python/unittest/test_schedule_schedule_ops.py b/tests/python/unittest/test_schedule_schedule_ops.py
index dd57c35c39b6..ecb9c0acb7f8 100644
--- a/tests/python/unittest/test_schedule_schedule_ops.py
+++ b/tests/python/unittest/test_schedule_schedule_ops.py
@@ -20,6 +20,7 @@ def test_schedule1():
 
     s = tvm.create_schedule(A1.op)
     xo, xi = s[A1].split(A1.op.axis[0], 8)
+    s[A1].pragma(xo, "auto_unroll_max_step", 10)
     bounds = tvm.schedule.InferBound(s)
     assert isinstance(bounds, tvm.container.Map)
     stmt = tvm.schedule.ScheduleOps(s, bounds)

From e79c9291ddfe3f8a8f834607bdb255b6aaaffaa8 Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Wed, 18 Apr 2018 00:53:38 +0800
Subject: [PATCH 272/949] Add issue template  (#1118)

---
 .github/ISSUE_TEMPLATE.md | 7 +++++++
 1 file changed, 7 insertions(+)
 create mode 100644 .github/ISSUE_TEMPLATE.md

diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 000000000000..4d2a7d6785a8
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,7 @@
+TVM Github Issue Guideline
+---------------------------
+
+We are moving the question and discussion issues to [TVM Discuss forum](https://discuss.tvmlang.org) and only use issue tracker for development purposes.
+
+* For feature request or but report, please open an issue on Github
+* For other general question, please open a post on our forum.

From 240642ba3274e58a97e759108d3357989ec33fbb Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 17 Apr 2018 10:07:01 -0700
Subject: [PATCH 273/949] Update ISSUE_TEMPLATE.md

---
 .github/ISSUE_TEMPLATE.md | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 4d2a7d6785a8..418549bef1bd 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -1,7 +1,11 @@
 TVM Github Issue Guideline
 ---------------------------
 
-We are moving the question and discussion issues to [TVM Discuss forum](https://discuss.tvmlang.org) and only use issue tracker for development purposes.
+Thanks for participating TVM community! We use [TVM Discuss forum](https://discuss.tvmlang.org) for any general questions and discussions. When in doubt, you are always welcomed to post on the forum first :)
+
+The issue tracker is used for actionable items such as feature proposals, roadmaps and bug tracking. We close issues that are inactive for a period of time. We adopt this policy so that we won't lost track of actionable issues that may fall at the bottom of the pile. Feel free to reopen a new one if you feel there is an additional problem that need attention when an old one get closed.
+
+For bug reports, to help the developer act on the issues, please include description of your environment, possibly a minimum script to reproduce the problem.
+
+For feature proposals, list clear, small actionable items so we can track the progress of the change.
 
-* For feature request or but report, please open an issue on Github
-* For other general question, please open a post on our forum.

From ff863101e2a80e708f9ec060c03ae6832219a644 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 17 Apr 2018 10:08:22 -0700
Subject: [PATCH 274/949] Update ISSUE_TEMPLATE.md

---
 .github/ISSUE_TEMPLATE.md | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 418549bef1bd..0478bc21d0fd 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -1,11 +1,10 @@
 TVM Github Issue Guideline
 ---------------------------
 
-Thanks for participating TVM community! We use [TVM Discuss forum](https://discuss.tvmlang.org) for any general questions and discussions. When in doubt, you are always welcomed to post on the forum first :)
+Thanks for participating in the TVM community! We use [TVM Discuss forum](https://discuss.tvmlang.org) for any general questions and discussions. When in doubt, you are always welcomed to post on the forum first :)
 
-The issue tracker is used for actionable items such as feature proposals, roadmaps and bug tracking. We close issues that are inactive for a period of time. We adopt this policy so that we won't lost track of actionable issues that may fall at the bottom of the pile. Feel free to reopen a new one if you feel there is an additional problem that need attention when an old one get closed.
+The issue tracker is used for actionable items such as feature proposals, roadmaps, and bug tracking. We close issues that are inactive for a period of time. We adopt this policy so that we won't lose track of actionable issues that may fall at the bottom of the pile. Feel free to reopen a new one if you feel there is an additional problem that needs attention when an old one gets closed.
 
-For bug reports, to help the developer act on the issues, please include description of your environment, possibly a minimum script to reproduce the problem.
+For bug reports, to help the developer act on the issues, please include a description of your environment, preferably a minimum script to reproduce the problem.
 
 For feature proposals, list clear, small actionable items so we can track the progress of the change.
-

From 9f908a0604d581041c6c877a02f31aa9770650be Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 17 Apr 2018 10:09:12 -0700
Subject: [PATCH 275/949] Update ISSUE_TEMPLATE.md

---
 .github/ISSUE_TEMPLATE.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 0478bc21d0fd..8b7474a8207c 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -3,7 +3,7 @@ TVM Github Issue Guideline
 
 Thanks for participating in the TVM community! We use [TVM Discuss forum](https://discuss.tvmlang.org) for any general questions and discussions. When in doubt, you are always welcomed to post on the forum first :)
 
-The issue tracker is used for actionable items such as feature proposals, roadmaps, and bug tracking. We close issues that are inactive for a period of time. We adopt this policy so that we won't lose track of actionable issues that may fall at the bottom of the pile. Feel free to reopen a new one if you feel there is an additional problem that needs attention when an old one gets closed.
+The issue tracker is used for actionable items such as feature proposals, roadmaps, and bug tracking. Issues that are inactive for a period of time may get closed. We adopt this policy so that we won't lose track of actionable issues that may fall at the bottom of the pile. Feel free to reopen a new one if you feel there is an additional problem that needs attention when an old one gets closed.
 
 For bug reports, to help the developer act on the issues, please include a description of your environment, preferably a minimum script to reproduce the problem.
 

From 2ee2b6d1f739bc32edbd9fceb1dd6f869834a5a3 Mon Sep 17 00:00:00 2001
From: nhynes <nhynes@berkeley.edu>
Date: Wed, 18 Apr 2018 20:43:32 -0700
Subject: [PATCH 276/949] Add SGX random engine (#1113)

---
 make/contrib/random.mk                  |  2 +-
 src/contrib/random/mt_random_engine.cc  | 86 +++++++++++++++++++++++++
 src/contrib/random/random.cc            | 58 ++---------------
 src/contrib/random/sgx_random_engine.cc | 75 +++++++++++++++++++++
 src/runtime/sgx/common.h                |  2 +
 src/runtime/sgx/trusted/runtime.h       |  1 -
 6 files changed, 169 insertions(+), 55 deletions(-)
 create mode 100644 src/contrib/random/mt_random_engine.cc
 create mode 100644 src/contrib/random/sgx_random_engine.cc

diff --git a/make/contrib/random.mk b/make/contrib/random.mk
index aea6770101d4..3f8c03f5e4e8 100644
--- a/make/contrib/random.mk
+++ b/make/contrib/random.mk
@@ -1,4 +1,4 @@
-RANDOM_CONTRIB_SRC = $(wildcard src/contrib/random/*.cc)
+RANDOM_CONTRIB_SRC = $(wildcard src/contrib/random/random.cc)
 RANDOM_CONTRIB_OBJ = $(patsubst src/%.cc, build/%.o, $(RANDOM_CONTRIB_SRC))
 
 ifeq ($(USE_RANDOM), 1)
diff --git a/src/contrib/random/mt_random_engine.cc b/src/contrib/random/mt_random_engine.cc
new file mode 100644
index 000000000000..b7f8db4b8596
--- /dev/null
+++ b/src/contrib/random/mt_random_engine.cc
@@ -0,0 +1,86 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file random/mt_random_engine.cc
+ * \brief mt19937 random engine
+ */
+#include <dmlc/logging.h>
+#include <algorithm>
+#include <ctime>
+#include <random>
+
+namespace tvm {
+namespace contrib {
+
+/*!
+ * \brief An interface for generating [tensors of] random numbers.
+ */
+class RandomEngine {
+ public:
+   /*!
+    * \brief Creates a RandomEngine using a default seed.
+    */
+  RandomEngine() {
+    this->Seed(time(0));
+  }
+
+   /*!
+    * \brief Creates a RandomEngine, suggesting the use of a provided seed.
+    */
+  explicit RandomEngine(unsigned seed) {
+    this->Seed(seed);
+  }
+
+   /*!
+    * \brief Seeds the underlying RNG, if possible.
+    */
+  inline void Seed(unsigned seed) {
+    rnd_engine_.seed(seed);
+    this->rseed_ = static_cast<unsigned>(seed);
+  }
+
+   /*!
+    * \return the seed associated with the underlying RNG.
+    */
+  inline unsigned GetSeed() const {
+    return rseed_;
+  }
+
+   /*!
+    * \return a random integer sampled from the RNG.
+    */
+  inline unsigned GetRandInt() {
+    return rnd_engine_();
+  }
+
+   /*!
+    * \brief Fills a tensor with values drawn from Unif(low, high)
+    */
+  void SampleUniform(DLTensor* data, float low, float high) {
+    CHECK_GT(high, low) << "high must be bigger than low";
+    CHECK(data->strides == nullptr);
+
+    DLDataType dtype = data->dtype;
+    int64_t size = 1;
+    for (int i = 0; i < data->ndim; ++i) {
+      size *= data->shape[i];
+    }
+
+    CHECK(dtype.code == kDLFloat && dtype.bits == 32 && dtype.lanes == 1);
+
+    if (data->ctx.device_type == kDLCPU) {
+      std::uniform_real_distribution<float> uniform_dist(low, high);
+      std::generate_n(static_cast<float*>(data->data), size, [&] () {
+        return uniform_dist(rnd_engine_);
+      });
+    } else {
+      LOG(FATAL) << "Do not support random.randint on this device yet";
+    }
+  }
+
+ private:
+  std::mt19937 rnd_engine_;
+  unsigned rseed_;
+};
+
+}  // namespace contrib
+}  // namespace tvm
diff --git a/src/contrib/random/random.cc b/src/contrib/random/random.cc
index d0bcb18cb76b..de60da906adc 100644
--- a/src/contrib/random/random.cc
+++ b/src/contrib/random/random.cc
@@ -7,8 +7,11 @@
 #include <dmlc/logging.h>
 #include <dmlc/thread_local.h>
 #include <algorithm>
-#include <random>
-#include <ctime>
+#ifndef _LIBCPP_SGX_CONFIG
+#include "./mt_random_engine.cc"
+#else
+#include "./sgx_random_engine.cc"
+#endif
 
 #define DLPACK_INTEGER_TYPE_SWITCH(type, DType, ...)    \
   if (type.code == kDLInt && type.bits == 32) {         \
@@ -38,57 +41,6 @@ namespace contrib {
 
 using namespace runtime;
 
-class RandomEngine {
- public:
-  RandomEngine() {
-    this->Seed(time(0));
-  }
-  explicit RandomEngine(int seed) {
-    this->Seed(seed);
-  }
-
-  ~RandomEngine() {}
-
-  inline void Seed(int seed) {
-    rnd_engine_.seed(seed);
-    this->rseed_ = static_cast<unsigned>(seed);
-  }
-
-  inline unsigned GetSeed() const {
-    return rseed_;
-  }
-
-  inline unsigned GetRandInt() {
-    return rnd_engine_();
-  }
-
-  void SampleUniform(DLTensor* data, float low, float high) {
-    CHECK_GT(high, low) << "high must be bigger than low";
-    CHECK(data->strides == nullptr);
-
-    DLDataType dtype = data->dtype;
-    int64_t size = 1;
-    for (int i = 0; i < data->ndim; ++i) {
-      size *= data->shape[i];
-    }
-
-    CHECK(dtype.code == kDLFloat && dtype.bits == 32 && dtype.lanes == 1);
-
-    if (data->ctx.device_type == kDLCPU) {
-      std::uniform_real_distribution<float> uniform_dist(low, high);
-      std::generate_n(static_cast<float*>(data->data), size, [&] () {
-        return uniform_dist(rnd_engine_);
-      });
-    } else {
-      LOG(FATAL) << "Do not support random.randint on this device yet";
-    }
-  }
-
- private:
-  std::mt19937 rnd_engine_;
-  unsigned rseed_;
-};
-
 struct RandomThreadLocalEntry {
   RandomEngine random_engine;
   static RandomThreadLocalEntry* ThreadLocal();
diff --git a/src/contrib/random/sgx_random_engine.cc b/src/contrib/random/sgx_random_engine.cc
new file mode 100644
index 000000000000..2bdcf529ba1e
--- /dev/null
+++ b/src/contrib/random/sgx_random_engine.cc
@@ -0,0 +1,75 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file random/sgx_random_engine.h
+ * \brief SGX trusted random engine
+ */
+#include <dmlc/logging.h>
+#include <sgx_trts.h>
+#include <algorithm>
+#include "../../runtime/sgx/common.h"
+
+namespace tvm {
+namespace contrib {
+
+/*!
+ * \brief An interface for generating [tensors of] random numbers.
+ */
+class RandomEngine {
+ public:
+   /*!
+    * \brief Creates a RandomEngine, suggesting the use of a provided seed.
+    */
+  explicit RandomEngine(unsigned seed) {
+    LOG(WARNING) << "SGX RandomEngine does not support seeding.";
+  }
+
+   /*!
+    * \brief Seeds the underlying RNG, if possible.
+    */
+  inline void Seed(unsigned seed) {
+    LOG(WARNING) << "SGX RandomEngine does not support seeding.";
+  }
+
+   /*!
+    * \return the seed associated with the underlying RNG.
+    */
+  inline unsigned GetSeed() const {
+    LOG(WARNING) << "SGX RandomEngine does not support seeding.";
+    return 0;
+  }
+
+   /*!
+    * \return a random integer sampled from the RNG.
+    */
+  inline unsigned GetRandInt() {
+    int rand_int;
+    TVM_SGX_CHECKED_CALL(
+        sgx_read_rand(reinterpret_cast<unsigned char*>(&rand_int), sizeof(int)));
+    return rand_int;
+  }
+
+   /*!
+    * \brief Fills a tensor with values drawn from Unif(low, high)
+    */
+  void SampleUniform(DLTensor* data, float low, float high) {
+    CHECK_GT(high, low) << "high must be bigger than low";
+    CHECK(data->strides == nullptr);
+
+    DLDataType dtype = data->dtype;
+    int64_t size = 1;
+    for (int i = 0; i < data->ndim; ++i) {
+      size *= data->shape[i];
+    }
+
+    CHECK(dtype.code == kDLFloat && dtype.bits == 32 && dtype.lanes == 1);
+
+    std::generate_n(static_cast<float*>(data->data), size, [&] () {
+      float max_int = static_cast<float>(std::numeric_limits<unsigned>::max());
+      float unif01 = GetRandInt() / max_int;
+      return low + unif01 * (high - low);
+    });
+  }
+};
+
+}  // namespace contrib
+}  // namespace tvm
diff --git a/src/runtime/sgx/common.h b/src/runtime/sgx/common.h
index a36b33d48b1a..a375bcd21dd2 100644
--- a/src/runtime/sgx/common.h
+++ b/src/runtime/sgx/common.h
@@ -6,6 +6,8 @@
 #ifndef TVM_RUNTIME_SGX_COMMON_H_
 #define TVM_RUNTIME_SGX_COMMON_H_
 
+#include <sgx_error.h>
+
 namespace tvm {
 namespace runtime {
 namespace sgx {
diff --git a/src/runtime/sgx/trusted/runtime.h b/src/runtime/sgx/trusted/runtime.h
index ded76fd52d54..9bd834e0513f 100644
--- a/src/runtime/sgx/trusted/runtime.h
+++ b/src/runtime/sgx/trusted/runtime.h
@@ -6,7 +6,6 @@
 #ifndef TVM_RUNTIME_SGX_TRUSTED_RUNTIME_H_
 #define TVM_RUNTIME_SGX_TRUSTED_RUNTIME_H_
 
-#include <sgx_edger8r.h>
 #include <tvm/runtime/packed_func.h>
 #include <string>
 #include "../common.h"

From 3b4b91ecb4c1bef44841dfe332dbd2fb9a6683e3 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 19 Apr 2018 11:36:06 -0700
Subject: [PATCH 277/949] [PASS] Remap thread axis. (#1122)

---
 include/tvm/build_module.h             |  6 ++
 include/tvm/ir_pass.h                  | 14 +++++
 python/tvm/build_module.py             |  8 +++
 python/tvm/target.py                   |  1 +
 src/api/api_pass.cc                    |  1 +
 src/codegen/build_module.cc            |  2 +
 src/pass/remap_thread_axis.cc          | 83 ++++++++++++++++++++++++++
 tests/python/integration/test_ewise.py |  5 +-
 8 files changed, 118 insertions(+), 2 deletions(-)
 create mode 100644 src/pass/remap_thread_axis.cc

diff --git a/include/tvm/build_module.h b/include/tvm/build_module.h
index 9c54ab7b45fb..e9d6123b5e06 100644
--- a/include/tvm/build_module.h
+++ b/include/tvm/build_module.h
@@ -32,6 +32,11 @@ class TargetNode : public Node {
   int max_num_threads = 1;
   /*! \brief The warp size that should be used by the LowerThreadAllreduce pass */
   int thread_warp_size = 1;
+  /*!
+   * \brief The thread index that is the lowest(correspond to warp)
+   *  In cuda it is threadIdx.x, but can be different in some platform.
+   */
+  int thread_warp_index = 0;
   /*! \brief Keys for this target */
   Array<Expr> keys_array;
   /*! \brief Options for this target */
@@ -48,6 +53,7 @@ class TargetNode : public Node {
     v->Visit("device_type", &device_type);
     v->Visit("max_num_threads", &max_num_threads);
     v->Visit("thread_warp_size", &thread_warp_size);
+    v->Visit("thread_warp_index", &thread_warp_index);
     v->Visit("keys_array", &keys_array);
     v->Visit("options_array", &options_array);
     v->Visit("libs_array", &libs_array);
diff --git a/include/tvm/ir_pass.h b/include/tvm/ir_pass.h
index 1ae41032cbb8..44a77aebbfd8 100644
--- a/include/tvm/ir_pass.h
+++ b/include/tvm/ir_pass.h
@@ -416,6 +416,20 @@ LoweredFunc LowerThreadAllreduce(LoweredFunc f, int warp_size);
  */
 LoweredFunc LowerWarpMemory(LoweredFunc f, int warp_size);
 
+/*!
+ * \brief Remap the thread axis
+ *
+ *  This can be used to get equivalent program which uses
+ *  threadIdx.y in place of threadIdx.x by passing
+ *  {"threadIdx.x": thread_axis("threadIdx.y")}
+ *
+ *
+ * \param f The device function to be lowered.
+ * \param axis_map The map from StringImm -> ItrVar
+ * \return Transformed function.
+ */
+LoweredFunc RemapThreadAxis(LoweredFunc f, Map<Expr, IterVar> axis_map);
+
 /*!
  * \brief Lower packed function call.
  * \param f The function to be lowered.
diff --git a/python/tvm/build_module.py b/python/tvm/build_module.py
index ffb9bd4c7844..6364b32fd64c 100755
--- a/python/tvm/build_module.py
+++ b/python/tvm/build_module.py
@@ -98,6 +98,7 @@ def exit(self):
         schedule.ScheduleOps = self._old_sgpass
         DumpIR.scope_level -= 1
 
+
 @register_node
 class BuildConfig(NodeBase):
     """Configuration scope to set a build config option.
@@ -469,6 +470,13 @@ def build(sch,
     for i, func in enumerate(fdevice):
         warp_size = target.thread_warp_size
         fdevice[i] = ir_pass.LowerWarpMemory(func, warp_size)
+        warp_index = target.thread_warp_index
+        if warp_index != 0:
+            assert warp_index == 2
+            # swap z and x
+            tmap = {api.convert("threadIdx.z"): api.thread_axis("threadIdx.x"),
+                    api.convert("threadIdx.x"): api.thread_axis("threadIdx.z")}
+            fdevice[i] = ir_pass.RemapThreadAxis(func, tmap)
 
     if "gpu" in target.keys and not fdevice:
         warnings.warn(
diff --git a/python/tvm/target.py b/python/tvm/target.py
index 3ca72bafdc85..1dabc862a0ee 100644
--- a/python/tvm/target.py
+++ b/python/tvm/target.py
@@ -109,6 +109,7 @@ def __enter__(self):
     def __exit__(self, ptype, value, trace):
         _api_internal._ExitTargetScope()
 
+
 @register_node
 class GenericFunc(NodeBase):
     """GenericFunc node reference. This represents a generic function
diff --git a/src/api/api_pass.cc b/src/api/api_pass.cc
index 6d59cb3ae505..64c922559229 100644
--- a/src/api/api_pass.cc
+++ b/src/api/api_pass.cc
@@ -126,6 +126,7 @@ REGISTER_PASS2(LiftAttrScope);
 REGISTER_PASS1(NarrowChannelAccess);
 REGISTER_PASS2(LowerThreadAllreduce);
 REGISTER_PASS2(LowerWarpMemory);
+REGISTER_PASS2(RemapThreadAxis);
 REGISTER_PASS2(LowerIntrin);
 REGISTER_PASS1(LowerTVMBuiltin);
 REGISTER_PASS1(CombineContextCall);
diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc
index 318e231fc67c..0bb2dfcf0206 100644
--- a/src/codegen/build_module.cc
+++ b/src/codegen/build_module.cc
@@ -78,6 +78,8 @@ Target CreateTarget(const std::string& target_name,
     t->max_num_threads = 256;
     if (t->device_name == "intel_gpu") {
       t->thread_warp_size = 16;
+      // use threadIdx.z for index
+      t->thread_warp_index = 2;
     }
   } else if (target_name == "metal" || target_name == "vulkan") {
     if (target_name == "metal") {
diff --git a/src/pass/remap_thread_axis.cc b/src/pass/remap_thread_axis.cc
new file mode 100644
index 000000000000..94e4819a1d71
--- /dev/null
+++ b/src/pass/remap_thread_axis.cc
@@ -0,0 +1,83 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file remap_thread_axis.cc
+ */
+#include <tvm/ir.h>
+#include <tvm/ir_mutator.h>
+#include <tvm/ir_visitor.h>
+#include <tvm/ir_pass.h>
+#include <unordered_map>
+
+
+namespace tvm {
+namespace ir {
+
+// Mutator to change the read pattern
+class ThreadAxisRewriter : private IRMutator {
+ public:
+  explicit ThreadAxisRewriter(
+      const std::unordered_map<std::string, IterVar>& tmap)
+      : tmap_(tmap) {
+  }
+
+  Stmt Rewrite(Stmt stmt) {
+    return Mutate(stmt);
+  }
+
+ private:
+  Stmt Mutate_(const AttrStmt* op, const Stmt& stmt) final {
+    if (op->attr_key == attr::thread_extent) {
+      IterVar iv(op->node.node_);
+      CHECK_NE(iv->thread_tag.length(), 0U);
+      auto it = tmap_.find(iv->thread_tag);
+      if (it != tmap_.end()) {
+        const IterVar& new_iv = it->second;
+        const Variable* v = iv->var.get();
+        if (!vmap_.count(v)) {
+          vmap_[v] = new_iv->var;
+        } else {
+          CHECK(vmap_[v].same_as(new_iv->var));
+        }
+        Stmt body = this->Mutate(op->body);
+        return AttrStmt::make(
+            new_iv, op->attr_key, op->value, body);
+      }
+    }
+    return IRMutator::Mutate_(op, stmt);
+  }
+
+  Expr Mutate_(const Variable* op, const Expr& expr) final {
+    auto it = vmap_.find(op);
+    if (it != vmap_.end()) return it->second;
+    return IRMutator::Mutate_(op, expr);
+  }
+  // The thread map
+  const std::unordered_map<std::string, IterVar>& tmap_;
+  // variable map
+  std::unordered_map<const Variable*, Var> vmap_;
+};
+
+LoweredFunc
+RemapThreadAxis(LoweredFunc f, Map<Expr, IterVar> thread_map) {
+  std::unordered_map<std::string, IterVar> tmap;
+  for (const auto& kv : thread_map) {
+    const StringImm* str = kv.first.as<StringImm>();
+    CHECK(str != nullptr);
+    tmap[str->value] = kv.second;
+  }
+
+  CHECK_EQ(f->func_type, kDeviceFunc);
+  auto n = std::make_shared<LoweredFuncNode>(*f.operator->());
+  // replace the thread axis
+  for (size_t i = 0; i < n->thread_axis.size(); ++i) {
+    auto it = tmap.find(n->thread_axis[i]->thread_tag);
+    if (it != tmap.end()) {
+      n->thread_axis.Set(i, it->second);
+    }
+  }
+  n->body = ThreadAxisRewriter(tmap).Rewrite(n->body);
+  return LoweredFunc(n);
+}
+
+}  // namespace ir
+}  // namespace tvm
diff --git a/tests/python/integration/test_ewise.py b/tests/python/integration/test_ewise.py
index 3ad464f75faa..cebacdc2b9a5 100644
--- a/tests/python/integration/test_ewise.py
+++ b/tests/python/integration/test_ewise.py
@@ -34,9 +34,10 @@ def check_device(device, host="stackvm"):
         np.testing.assert_allclose(
             b.asnumpy(), np.exp(a.asnumpy()), rtol=1e-5)
 
+    check_device("opencl -device=intel_gpu")
     check_device("cuda", "llvm")
     check_device("vulkan")
-    check_device("opencl")
+
 
 
 def test_log_pow_llvm():
@@ -196,8 +197,8 @@ def check_device(device):
 
 
 if __name__ == "__main__":
+    test_exp()
     try_warp_memory()
     test_add()
     test_log_pow_llvm()
-    test_exp()
     test_popcount()

From e8a2d65603b62412213eed3cfecf433b058dfc16 Mon Sep 17 00:00:00 2001
From: Yizhi Liu <liuyizhi@apache.org>
Date: Thu, 19 Apr 2018 11:37:39 -0700
Subject: [PATCH 278/949] Generalize pooling to support arbitrary layout
 (#1103)

* generalize pool2d to arbitrary layout

* explain more the layout support for pool

* allow missing factor size for pooling

* explain what factor size is used for

* fix typo

* name idx -> axis
---
 topi/include/topi/cuda/pooling.h   |   4 +-
 topi/include/topi/nn/pooling.h     | 298 ++++++++++++++---------------
 topi/python/topi/cuda/pooling.py   |   3 +-
 topi/python/topi/nn/pooling.py     | 238 +++++------------------
 topi/python/topi/opengl/pooling.py |   3 +-
 topi/python/topi/x86/injective.py  |   4 +
 6 files changed, 202 insertions(+), 348 deletions(-)

diff --git a/topi/include/topi/cuda/pooling.h b/topi/include/topi/cuda/pooling.h
index f8730c9d0299..d1c006e3a215 100644
--- a/topi/include/topi/cuda/pooling.h
+++ b/topi/include/topi/cuda/pooling.h
@@ -33,7 +33,9 @@ inline Schedule schedule_pool(const Target &target, const Array<Tensor>& outs) {
   auto s = create_schedule(out_ops);
 
   auto _schedule = [&](const Tensor& padded_input, const Tensor& pool) {
-    s[padded_input].compute_inline();
+    if (padded_input->op->is_type<ComputeOpNode>()) {
+      s[padded_input].compute_inline();
+    }
     auto num_thread = target->max_num_threads;
     Tensor out;
     Tensor OL;
diff --git a/topi/include/topi/nn/pooling.h b/topi/include/topi/nn/pooling.h
index 5d68510c3842..8613045b168f 100644
--- a/topi/include/topi/nn/pooling.h
+++ b/topi/include/topi/nn/pooling.h
@@ -7,6 +7,7 @@
 #define TOPI_NN_POOLING_H_
 
 #include <string>
+#include <vector>
 
 #include "tvm/tvm.h"
 #include "tvm/ir_pass.h"
@@ -25,25 +26,28 @@ enum PoolType : int {
 };
 
 /*!
-* \brief Perform pooling on data in NCHW order
+* \brief Perform pooling on height and width dimension of data.
 *
-* \param x The input tensor in NCHW order
+* \param x The input tensor
 * \param kernel_size Vector of two ints: {kernel_height, kernel_width}
 * \param stride_size Vector of two ints: {stride_height, stride_width}
 * \param padding_size Vector of two ints: {padding_height, padding_width}
 * \param pool_type The type of pooling operator
 * \param ceil_mode Whether to use ceil when calculating the output size
+* \param height_axis index of the height dimension
+* \param width_axis index of the width dimension
 *
-* \return The output tensor in NCHW order
+* \return The output tensor in same layout order
 */
-
-inline Tensor pool_nchw(const Tensor& x,
+inline Tensor pool_impl(const Tensor& x,
                         const Array<Expr>& kernel_size,
                         const Array<Expr>& stride_size,
                         const Array<Expr>& padding_size,
                         PoolType pool_type,
-                        bool ceil_mode) {
-  CHECK_EQ(x->shape.size(), 4) << "Pooling input must be 4-D";
+                        bool ceil_mode,
+                        const size_t height_axis,
+                        const size_t width_axis) {
+  CHECK(x->shape.size() >= 2) << "Pooling input must >= 2-D (H, W)";
   CHECK_EQ(kernel_size.size(), 2) << "Pooling kernel_size must have 2 elements";
   CHECK_EQ(stride_size.size(), 2) << "Pooling stride_size must have 2 elements";
   CHECK_EQ(padding_size.size(), 2) << "Pooling padding_size must have 2 elements";
@@ -55,10 +59,8 @@ inline Tensor pool_nchw(const Tensor& x,
   auto padding_height = padding_size[0];
   auto padding_width = padding_size[1];
 
-  auto batch = x->shape[0];
-  auto channel = x->shape[1];
-  auto height = x->shape[2];
-  auto width = x->shape[3];
+  auto height = x->shape[height_axis];
+  auto width = x->shape[width_axis];
 
   auto pad_tuple = detail::GetPadTuple(padding_height, padding_width);
   auto pad_top = pad_tuple[0];
@@ -73,8 +75,13 @@ inline Tensor pool_nchw(const Tensor& x,
     pad_right += stride_width - 1;
   }
 
-  Array<Expr> pad_before{ 0, 0, pad_top, pad_left };
-  Array<Expr> pad_after{ 0, 0, pad_down, pad_right };
+  Array<Expr> pad_before(std::vector<Expr>(x->shape.size(), 0));
+  pad_before.Set(height_axis, pad_top);
+  pad_before.Set(width_axis, pad_left);
+
+  Array<Expr> pad_after(std::vector<Expr>(x->shape.size(), 0));
+  pad_after.Set(height_axis, pad_down);
+  pad_after.Set(width_axis, pad_right);
 
   auto out_height = tvm::ir::Simplify(
     (height - kernel_height + pad_top + pad_down) / stride_height + 1);
@@ -84,138 +91,94 @@ inline Tensor pool_nchw(const Tensor& x,
   auto dheight = tvm::reduce_axis(Range(0, kernel_height));
   auto dwidth = tvm::reduce_axis(Range(0, kernel_width));
 
-  if (pool_type == kMaxPool) {
-    auto temp = pad(x, pad_before, pad_after, x->dtype.min(), "pad_temp");
-    return tvm::compute(
-      { batch, channel, out_height, out_width },
-      [&](Var n, Var c, Var h, Var w) {
-        return tvm::max(temp(n, c, h * stride_height + dheight, w * stride_width + dwidth),
-        { dheight, dwidth });
-      }, "tensor", "pool_max");
-  } else if (pool_type == kAvgPool) {
-    auto temp = pad(x, pad_before, pad_after, 0, "pad_temp");
+  Array<Expr> out_shape = x->shape;
+  out_shape.Set(height_axis, out_height);
+  out_shape.Set(width_axis, out_width);
 
-    auto tsum = tvm::compute(
-      { batch, channel, out_height, out_width },
-      [&](Var n, Var c, Var h, Var w) {
-        return tvm::sum(temp(n, c, h * stride_height + dheight, w * stride_width + dwidth),
-        { dheight, dwidth });
-      }, "tensor", "pool_avg");
+  const int64_t *padding_h = HalideIR::Internal::as_const_int(padding_height);
+  const int64_t *padding_w = HalideIR::Internal::as_const_int(padding_width);
+  const bool do_pad = ((padding_h && *padding_h) || (padding_w && *padding_w));
 
-    return tvm::compute(
-      { batch, channel, out_height, out_width },
-      [&](Var n, Var c, Var h, Var w) {
-        return tsum(n, c, h, w) / (kernel_height * kernel_width);
-      }, "tensor", kElementWise);
+  if (pool_type == kMaxPool) {
+    auto temp = do_pad ? pad(x, pad_before, pad_after, x->dtype.min(), "pad_temp") : x;
+    return tvm::compute(out_shape, [&](const Array<Var>& output) {
+      Array<Expr> indices;
+      for (const Var& var : output) indices.push_back(var);
+      indices.Set(height_axis, output[height_axis] * stride_height + dheight);
+      indices.Set(width_axis, output[width_axis] * stride_width + dwidth);
+      return tvm::max(temp(indices), { dheight, dwidth });
+    }, "tensor", "pool_max");
+  } else if (pool_type == kAvgPool) {
+    auto temp = do_pad ? pad(x, pad_before, pad_after, 0, "pad_temp") : x;
+    auto tsum = tvm::compute(out_shape, [&](const Array<Var>& output) {
+      Array<Expr> indices;
+      for (const Var& var : output) indices.push_back(var);
+      indices.Set(height_axis, output[height_axis] * stride_height + dheight);
+      indices.Set(width_axis, output[width_axis] * stride_width + dwidth);
+      return tvm::sum(temp(indices), { dheight, dwidth });
+    }, "tensor", "pool_avg");
+
+    return tvm::compute(out_shape,
+    [&](const Array<Var>& output) {
+      return tsum(output) / (kernel_height * kernel_width);
+    }, "tensor", kElementWise);
   } else {
     LOG(ERROR) << "Unrecognized pool_type: " << pool_type;
     return x;
   }
 }
 
-/*!
-* \brief Perform pooling on data in NHWC order
-*
-* \param x The input tensor in NHWC order
-* \param kernel_size Vector of two ints: {kernel_height, kernel_width}
-* \param stride_size Vector of two ints: {stride_height, stride_width}
-* \param padding_size Vector of two ints: {padding_height, padding_width}
-* \param pool_type The type of pooling operator
-* \param ceil_mode Whether to use ceil when calculating the output size
-*
-* \return The output tensor in NCHW order
-*/
-
-inline Tensor pool_nhwc(const Tensor& x,
-                        const Array<Expr>& kernel_size,
-                        const Array<Expr>& stride_size,
-                        const Array<Expr>& padding_size,
-                        PoolType pool_type,
-                        bool ceil_mode) {
-  CHECK_EQ(x->shape.size(), 4) << "Pooling input must be 4-D";
-  CHECK_EQ(kernel_size.size(), 2) << "Pooling kernel_size must have 2 elements";
-  CHECK_EQ(stride_size.size(), 2) << "Pooling stride_size must have 2 elements";
-  CHECK_EQ(padding_size.size(), 2) << "Pooling padding_size must have 2 elements";
-
-  auto kernel_height = kernel_size[0];
-  auto kernel_width = kernel_size[1];
-  auto stride_height = stride_size[0];
-  auto stride_width = stride_size[1];
-  auto padding_height = padding_size[0];
-  auto padding_width = padding_size[1];
-
-  auto batch = x->shape[0];
-  auto height = x->shape[1];
-  auto width = x->shape[2];
-  auto channel = x->shape[3];
-
-  auto pad_tuple = detail::GetPadTuple(padding_height, padding_width);
-  auto pad_top = pad_tuple[0];
-  auto pad_left = pad_tuple[1];
-  auto pad_down = pad_tuple[2];
-  auto pad_right = pad_tuple[3];
-
-  if (ceil_mode) {
-    // Additional padding to ensure we do ceil instead of floor when
-    // dividing by stride.
-    pad_down += stride_height - 1;
-    pad_right += stride_width - 1;
-  }
-
-  Array<Expr> pad_before{ 0, pad_top, pad_left, 0};
-  Array<Expr> pad_after{ 0, pad_down, pad_right, 0};
-
-  auto out_height = tvm::ir::Simplify(
-    (height - kernel_height + pad_top + pad_down) / stride_height + 1);
-  auto out_width = tvm::ir::Simplify(
-    (width - kernel_width + pad_left + pad_right) / stride_width + 1);
-
-  auto dheight = tvm::reduce_axis(Range(0, kernel_height));
-  auto dwidth = tvm::reduce_axis(Range(0, kernel_width));
-
-  if (pool_type == kMaxPool) {
-    auto temp = pad(x, pad_before, pad_after, x->dtype.min(), "pad_temp");
-    return tvm::compute(
-     { batch, out_height, out_width, channel },
-      [&](Var n, Var h, Var w, Var c) {
-        return tvm::max(temp(n, h * stride_height + dheight, w * stride_width + dwidth, c),
-        { dheight, dwidth });
-      }, "tensor", "pool_max");
-  } else if (pool_type == kAvgPool) {
-    auto temp = pad(x, pad_before, pad_after, 0, "pad_temp");
-
-    auto tsum = tvm::compute(
-     { batch, out_height, out_width, channel },
-      [&](Var n, Var h, Var w, Var c) {
-        return tvm::sum(temp(n, h * stride_height + dheight, w * stride_width + dwidth, c),
-        { dheight, dwidth });
-      }, "tensor", "pool_avg");
-
-    return tvm::compute(
-     { batch, out_height, out_width, channel },
-     [&](Var n, Var h, Var w, Var c) {
-       return tsum(n, h, w, c) / (kernel_height * kernel_width);
-      }, "tensor", kElementWise);
-  } else {
-    LOG(ERROR) << "Unrecognized pool_type: " << pool_type;
-    return x;
+inline bool find_height_width(const std::string& layout,
+                              int* height_axis,
+                              int* width_axis) {
+  *height_axis = -1, *width_axis = -1;
+  int curr_idx = 0;
+  for (size_t i = 0; i < layout.size(); ++i) {
+    if ((layout[i] >= 'A' && layout[i] <= 'Z') ||
+        (layout[i] >= 'a' && layout[i] <= 'z')) {
+      if (layout[i] == 'H') {
+        if (*height_axis != -1) return false;
+        *height_axis = curr_idx;
+      } else if (layout[i] == 'W') {
+        if (*width_axis != -1) return false;
+        *width_axis = curr_idx;
+      } else if (layout[i] == 'h' || layout[i] == 'w') {
+        // do not support split on height or width, e.g., NCHW16w
+        return false;
+      }
+      ++curr_idx;
+    }
   }
+  if (*height_axis == -1 || *width_axis == -1) return false;
+  return true;
 }
 
 /*!
-* \brief Perform pooling on data
-*
-* \param x The input tensor in NCHW or NHWC order
+* \brief Perform pooling on height and width dimension of data.
+*        It decides the height and width dimension according to the layout string,
+*        in which 'W' and 'H' means width and height respectively.
+*        Width and height dimension cannot be split.
+*        For example, NCHW, NCHW16c, etc. are valid for pool,
+*        while NCHW16w, NCHW16h are not.
+*        See \a layout for more information of the layout string convention.
+* \param x The input tensor.
 * \param kernel_size Vector of two ints: {kernel_height, kernel_width}
 * \param stride_size Vector of two ints: {stride_height, stride_width}
 * \param padding_size Vector of two ints: {padding_height, padding_width}
 * \param pool_type The type of pooling operator
 * \param ceil_mode Whether to use ceil when calculating the output size
-* \param layout The input layout
-*
-* \return The output tensor in NCHW order
+* \param layout The input layout. Pooling supports any layout as long as 'H' and 'W' appear.
+*        The layout is supposed to be composed of upper cases, lower cases and (optional) numbers,
+*        where upper case indicates a dimension and
+*        the corresponding lower case (with factor size) indicates the split dimension.
+*        For example, NCHW16c can describe a 5-D tensor of
+*        [batch_size, channel, height, width, channel_block].
+*        (in which factor size `16` will not be used in pooling but for other operators,
+*        it can be used to decide the output shape).
+*        Since pooling does not care about the factor size of dimensions
+*        other than `H` and `W`, one can pass `NCHWc` as well.
+* \return The output tensor in the same layout
 */
-
 inline Tensor pool(const Tensor& x,
                    const Array<Expr>& kernel_size,
                    const Array<Expr>& stride_size,
@@ -223,50 +186,79 @@ inline Tensor pool(const Tensor& x,
                    PoolType pool_type,
                    bool ceil_mode,
                    const std::string& layout = "NCHW") {
-  CHECK(layout == "NCHW" || layout == "NHWC") << "Unsupported layout.";
-  if (layout == "NCHW")
-    return pool_nchw(x, kernel_size, stride_size, padding_size, pool_type, ceil_mode);
-  else
-    return pool_nhwc(x, kernel_size, stride_size, padding_size, pool_type, ceil_mode);
+  int height_axis = -1, width_axis = -1;
+  CHECK(find_height_width(layout, &height_axis, &width_axis))
+    << "Unsupported layout " << layout;
+  return pool_impl(x, kernel_size, stride_size, padding_size,
+                   pool_type, ceil_mode, height_axis, width_axis);
 }
 
 /*!
-* \brief Perform global pooling on data in NCHW order
+* \brief Perform global pooling on height and width dimension of data.
+*        It decides the height and width dimension according to the layout string,
+*        in which 'W' and 'H' means width and height respectively.
+*        Width and height dimension cannot be split.
+*        For example, NCHW, NCHW16c, ... are valid for global_pool,
+*        while NCHW16w, NCHW16h are not.
+*        See \a layout for more information of the layout string convention.
 *
-* \param x The input tensor in NCHW order
+* \param x The input tensor represent as layout
 * \param pool_type The type of pooling operator
+* \param layout The input layout. global-pooling supports any layout as long as 'H' and 'W' appear.
+*        The layout is supposed to be composed of upper cases, lower cases and (optional) numbers,
+*        where upper case indicates a dimension and
+*        the corresponding lower case (with factor size) indicates the sub-dimension.
+*        For example, `NCHW16c` can describe a 5-D tensor of
+*        [batch_size, channel, height, width, channel_block].
+*        (in which factor size `16` will not be used in pooling but for other operators,
+*        it can be used to decide the output shape).
+*        Since pooling does not care about the factor size of
+*        dimensions other than `H` and `W`, one can pass `NCHWc` as well.
 *
-* \return The output tensor with shape [batch, channel, 1, 1]
+* \return The output tensor in same layout with height and width dimension size of 1.
+*         e.g., for NCHW, the output shape will be [batch, channel, 1, 1]
 */
 inline Tensor global_pool(const Tensor& x,
-                          PoolType pool_type) {
-  CHECK_EQ(x->shape.size(), 4) << "Pooling input must be 4-D";
+                          PoolType pool_type,
+                          const std::string& layout = "NCHW") {
+  CHECK(x->shape.size() >= 2) << "Pooling input must >= 2-D (H, W)";
+
+  int height_axis = -1, width_axis = -1;
+  CHECK(find_height_width(layout, &height_axis, &width_axis))
+    << "Unsupported layout " << layout;
+
+  Array<Expr> out_shape = x->shape;
+  out_shape.Set(height_axis, 1);
+  out_shape.Set(width_axis, 1);
 
-  auto batch = x->shape[0];
-  auto channel = x->shape[1];
-  auto height = x->shape[2];
-  auto width = x->shape[3];
+  auto height = x->shape[height_axis];
+  auto width = x->shape[width_axis];
 
   auto dheight = tvm::reduce_axis(Range(0, height));
   auto dwidth = tvm::reduce_axis(Range(0, width));
 
   if (pool_type == kMaxPool) {
-    return tvm::compute(
-      { batch, channel, 1, 1 },
-      [&](Var n, Var c, Var h, Var w) {
-        return tvm::max(x(n, c, dheight, dwidth), { dheight, dwidth });  // NOLINT(*)
+    return tvm::compute(out_shape,
+      [&](const Array<Var>& output) {
+        Array<Expr> indices;
+        for (const Var& var : output) indices.push_back(var);
+        indices.Set(height_axis, dheight);
+        indices.Set(width_axis, dwidth);
+        return tvm::max(x(indices), { dheight, dwidth });  // NOLINT(*)
       }, "tensor", "global_pool_max");
   } else if (pool_type == kAvgPool) {
-    auto tsum = tvm::compute(
-      { batch, channel, 1, 1 },
-      [&](Var n, Var c, Var h, Var w) {
-        return tvm::sum(x(n, c, dheight, dwidth), { dheight, dwidth });
+    auto tsum = tvm::compute(out_shape,
+      [&](const Array<Var>& output) {
+        Array<Expr> indices;
+        for (const Var& var : output) indices.push_back(var);
+        indices.Set(height_axis, dheight);
+        indices.Set(width_axis, dwidth);
+        return tvm::sum(x(indices), { dheight, dwidth });
       }, "tensor", "global_pool_sum");
 
-    return tvm::compute(
-      { batch, channel, 1, 1 },
-      [&](Var n, Var c, Var h, Var w) {
-        return tsum(n, c, h, w) / tvm::cast(x->dtype, height * width);
+    return tvm::compute(out_shape,
+      [&](const Array<Var>& output) {
+        return tsum(output) / tvm::cast(x->dtype, height * width);
       }, "tensor", kElementWise);
   } else {
     LOG(ERROR) << "Unrecognized pool_type: " << pool_type;
diff --git a/topi/python/topi/cuda/pooling.py b/topi/python/topi/cuda/pooling.py
index 95ef3ce67c5c..017408f14b89 100644
--- a/topi/python/topi/cuda/pooling.py
+++ b/topi/python/topi/cuda/pooling.py
@@ -84,7 +84,8 @@ def schedule_pool(outs):
     outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
     s = tvm.create_schedule([x.op for x in outs])
     def _schedule(PaddedInput, Pool):
-        s[PaddedInput].compute_inline()
+        if isinstance(PaddedInput.op, tvm.tensor.ComputeOp):
+            s[PaddedInput].compute_inline()
         num_thread = tvm.target.current_target(allow_none=False).max_num_threads
         if Pool.op in s.outputs:
             Out = Pool
diff --git a/topi/python/topi/nn/pooling.py b/topi/python/topi/nn/pooling.py
index 0519471eaa7e..c51ffa93810c 100644
--- a/topi/python/topi/nn/pooling.py
+++ b/topi/python/topi/nn/pooling.py
@@ -1,57 +1,60 @@
 """TVM operator pooling compute."""
 from __future__ import absolute_import
-import tvm
-from .pad import pad
-from .util import get_pad_tuple
-from .. import util
-from .. import tag
-
-
-def global_pool(data, pool_type):
-    """Perform global pooling on the data
+from .. import cpp
+
+POOL_TYPE_CODE = {
+    "avg": 0,
+    "max": 1
+}
+
+def global_pool(data, pool_type, layout="NCHW"):
+    """Perform global pooling on height and width dimension of data.
+       It decides the height and width dimension according to the layout string,
+       in which 'W' and 'H' means width and height respectively.
+       Width and height dimension cannot be split.
+       For example, NCHW, NCHW16c, etc. are valid for pool,
+       while NCHW16w, NCHW16h are not.
+       See parameter `layout` for more information of the layout string convention.
 
     Parameters
     ----------
     data : tvm.Tensor
-        4-D with shape [batch, channel, in_height, in_width]
+        n-D with shape of layout
 
     pool_type : str
         Pool type, 'max' or 'avg'
 
+    layout : str
+        Layout of the input data.
+        The layout is supposed to be composed of upper cases, lower cases and numbers,
+        where upper case indicates a dimension and
+        the corresponding lower case with factor size indicates the split dimension.
+        For example, NCHW16c can describe a 5-D tensor of
+        [batch_size, channel, height, width, channel_block],
+        in which channel_block=16 is a split of dimension channel.
+
     Returns
     -------
     output : tvm.Tensor
-        4-D with shape [batch, channel, 1, 1]
+        n-D in same layout with height and width dimension size of 1.
+        e.g., for NCHW, the output shape will be [batch, channel, 1, 1]
     """
-    assert len(data.shape) == 4, "only support 4-dim pooling"
-    batch, channel, height, width = data.shape
-
-    dheight = tvm.reduce_axis((0, height))
-    dwidth = tvm.reduce_axis((0, width))
-
-    if pool_type == 'max':
-        return tvm.compute((batch, channel, 1, 1), lambda n, c, h, w: \
-                            tvm.max(data[n, c, dheight, dwidth], axis=[dheight, dwidth]), \
-                            tag="global_pool_max")
-    elif pool_type == 'avg':
-        tsum = tvm.compute((batch, channel, 1, 1), lambda n, c, h, w: \
-                            tvm.sum(data[n, c, dheight, dwidth], axis=[dheight, dwidth]), \
-                            tag="global_pool_sum")
-        return tvm.compute((batch, channel, 1, 1), lambda n, c, h, w: \
-                            tsum[n, c, h, w] / (height*width).astype(tsum.dtype), \
-                            tag=tag.ELEMWISE)
-    else:
-        raise ValueError("Pool type should be 'avg' or 'max'.")
+    return cpp.nn.global_pool(data, POOL_TYPE_CODE[pool_type], layout)
 
 
 def pool(data, kernel, stride, padding, pool_type, ceil_mode=False, layout="NCHW"):
-    """Perform pooling on the data
+    """Perform pooling on height and width dimension of data.
+       It decides the height and width dimension according to the layout string,
+       in which 'W' and 'H' means width and height respectively.
+       Width and height dimension cannot be split.
+       For example, NCHW, NCHW16c, etc. are valid for pool,
+       while NCHW16w, NCHW16h are not.
+       See parameter `layout` for more information of the layout string convention.
 
     Parameters
     ----------
     data : tvm.Tensor
-        4-D with shape [batch, channel, in_height, in_width]
-        or  [batch, in_height, in_width, channel]
+        n-D with shape of layout
 
     kernel : list/tuple of two ints
         Kernel size, [kernel_height, kernel_width]
@@ -69,167 +72,18 @@ def pool(data, kernel, stride, padding, pool_type, ceil_mode=False, layout="NCHW
         Whether to use ceil when caculate output size.
 
     layout: string
-        either "NCHW" or "NHWC"
-
-    Returns
-    -------
-    output : tvm.Tensor
-        4-D with shape [batch, channel, out_height, out_width]
-        or [batch, out_height, out_width, channel]
-    """
-    if layout == "NCHW":
-        return pool_nchw(data, kernel, stride, padding, pool_type, ceil_mode=ceil_mode)
-    elif layout == "NHWC":
-        return pool_nhwc(data, kernel, stride, padding, pool_type, ceil_mode=ceil_mode)
-    else:
-        raise ValueError("not support this layout {} yet".format(layout))
-
-
-def pool_nchw(data, kernel, stride, padding, pool_type, ceil_mode=False):
-    """Perform pooling on the data in NCHW layout
-
-    Parameters
-    ----------
-    data : tvm.Tensor
-        4-D with shape [batch, channel, in_height, in_width]
-
-    kernel : list/tuple of two ints
-        Kernel size, [kernel_height, kernel_width]
-
-    stride : list/tuple of two ints
-        Stride size, [stride_height, stride_width]
-
-    paddding : list/tuple of two ints
-        Pad size, [pad_height, pad_width]
-
-    pool_type : str
-        Pool type, 'max' or 'avg'
-
-    ceil_mode : bool
-        Whether to use ceil when caculate output size.
-
-    Returns
-    -------
-    output : tvm.Tensor
-        4-D with shape [batch, channel, out_height, out_width]
-    """
-    assert len(data.shape) == 4, "only support 4-dim pooling"
-    assert len(stride) == 2, "only support 2-dim stride"
-    kernel_height, kernel_width = kernel
-    stride_height, stride_width = stride
-    batch, channel, height, width = data.shape
-
-    pad_top, pad_left, pad_down, pad_right = get_pad_tuple(
-        padding, (kernel_height, kernel_width))
-
-    if ceil_mode:
-        # Additional padding to ensure we do ceil instead of floor when divide stride.
-        pad_down += stride_height -1
-        pad_right += stride_width - 1
-
-    pad_before = [0, 0, pad_top, pad_left]
-    pad_after = [0, 0, pad_down, pad_right]
-
-    out_height = util.simplify((height - kernel_height + pad_top + pad_down) // stride_height + 1)
-    out_width = util.simplify((width - kernel_width + pad_left + pad_right) // stride_width + 1)
-
-    dheight = tvm.reduce_axis((0, kernel_height))
-    dwidth = tvm.reduce_axis((0, kernel_width))
-
-    if pool_type == 'max':
-        temp = pad(data, pad_before, pad_after, name="pad_temp", \
-            pad_value=tvm.min_value(data.dtype))
-        return tvm.compute((batch, channel, out_height, out_width), \
-                            lambda n, c, h, w: \
-                            tvm.max(temp[n, c, h*stride_height+dheight, w*stride_width+dwidth], \
-                                axis=[dheight, dwidth]), \
-                            tag="pool_max")
-    elif pool_type == 'avg':
-        temp = pad(data, pad_before, pad_after, name="pad_temp", \
-            pad_value=tvm.const(0.).astype(data.dtype))
-        tsum = tvm.compute((batch, channel, out_height, out_width), \
-                            lambda n, c, h, w: \
-                            tvm.sum(temp[n, c, h*stride_height+dheight, w*stride_width+dwidth], \
-                                axis=[dheight, dwidth]), \
-                            tag="pool_avg")
-        return tvm.compute((batch, channel, out_height, out_width), \
-                            lambda n, c, h, w: \
-                            tsum[n, c, h, w] / (kernel_height*kernel_width), \
-                            tag=tag.ELEMWISE)
-    else:
-        raise ValueError("Pool type should be 'avg' or 'max'.")
-
-
-def pool_nhwc(data, kernel, stride, padding, pool_type, ceil_mode=False):
-    """Perform pooling on the data in NHWC layout
-
-    Parameters
-    ----------
-    data : tvm.Tensor
-        4-D with shape [batch, in_height, in_width, channel]
-
-    kernel : list/tuple of two ints
-        Kernel size, [kernel_height, kernel_width]
-
-    stride : list/tuple of two ints
-        Stride size, [stride_height, stride_width]
-
-    paddding : list/tuple of two ints
-        Pad size, [pad_height, pad_width]
-
-    pool_type : str
-        Pool type, 'max' or 'avg'
-
-    ceil_mode : bool
-        Whether to use ceil when caculate output size.
+        Layout of the input data.
+        The layout is supposed to be composed of upper cases, lower cases and numbers,
+        where upper case indicates a dimension and
+        the corresponding lower case with factor size indicates the split dimension.
+        For example, NCHW16c can describe a 5-D tensor of
+        [batch_size, channel, height, width, channel_block],
+        in which channel_block=16 is a split of dimension channel.
 
     Returns
     -------
     output : tvm.Tensor
-        4-D with shape [batch, channel, out_height, out_width]
+        n-D in the same layout
     """
-    assert len(data.shape) == 4, "only support 4-dim pooling"
-    assert len(stride) == 2, "only support 2-dim stride"
-    kernel_height, kernel_width = kernel
-    stride_height, stride_width = stride
-    batch, height, width, channel = data.shape
-
-    pad_top, pad_left, pad_down, pad_right = get_pad_tuple(
-        padding, (kernel_height, kernel_width))
-
-    if ceil_mode:
-        # Additional padding to ensure we do ceil instead of floor when divide stride.
-        pad_down += stride_height -1
-        pad_right += stride_width - 1
-
-    pad_before = [0, pad_top, pad_left, 0]
-    pad_after = [0, pad_down, pad_right, 0]
-
-    out_height = util.simplify((height - kernel_height + pad_top + pad_down) // stride_height + 1)
-    out_width = util.simplify((width - kernel_width + pad_left + pad_right) // stride_width + 1)
-
-    dheight = tvm.reduce_axis((0, kernel_height))
-    dwidth = tvm.reduce_axis((0, kernel_width))
-
-    if pool_type == 'max':
-        temp = pad(data, pad_before, pad_after, name="pad_temp", \
-            pad_value=tvm.min_value(data.dtype))
-        return tvm.compute((batch, out_height, out_width, channel), \
-                            lambda n, h, w, c: \
-                            tvm.max(temp[n, h*stride_height+dheight, w*stride_width+dwidth, c], \
-                                axis=[dheight, dwidth]), \
-                            tag="pool_max")
-    elif pool_type == 'avg':
-        temp = pad(data, pad_before, pad_after, name="pad_temp", \
-            pad_value=tvm.const(0.).astype(data.dtype))
-        tsum = tvm.compute((batch, out_height, out_width, channel, ), \
-                            lambda n, h, w, c: \
-                            tvm.sum(temp[n, h*stride_height+dheight, w*stride_width+dwidth, c], \
-                                axis=[dheight, dwidth]), \
-                            tag="pool_avg")
-        return tvm.compute((batch, out_height, out_width, channel), \
-                            lambda n, h, w, c: \
-                            tsum[n, h, w, c] / (kernel_height*kernel_width), \
-                            tag=tag.ELEMWISE)
-    else:
-        raise ValueError("Pool type should be 'avg' or 'max'.")
+    return cpp.nn.pool(data, kernel, stride, padding,
+                       POOL_TYPE_CODE[pool_type], ceil_mode, layout)
diff --git a/topi/python/topi/opengl/pooling.py b/topi/python/topi/opengl/pooling.py
index dc4f47609f92..0bc17885548a 100644
--- a/topi/python/topi/opengl/pooling.py
+++ b/topi/python/topi/opengl/pooling.py
@@ -67,7 +67,8 @@ def schedule_pool(outs):
     outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
     s = tvm.create_schedule([x.op for x in outs])
     def _schedule(PaddedInput, Pool):
-        s[PaddedInput].opengl()
+        if isinstance(PaddedInput.op, tvm.tensor.ComputeOp):
+            s[PaddedInput].opengl()
         if Pool.op in s.outputs:
             Out = Pool
         else:
diff --git a/topi/python/topi/x86/injective.py b/topi/python/topi/x86/injective.py
index 0970b76142ae..7a69aee8ea2e 100644
--- a/topi/python/topi/x86/injective.py
+++ b/topi/python/topi/x86/injective.py
@@ -27,6 +27,10 @@ def schedule_injective(outs):
         n, c, _, _ = s[x].op.axis
         fused = s[x].fuse(n, c) # for nhwc layout, fuse n and h
         s[x].parallel(fused)
+    elif len(s[x].op.axis) == 5:
+        n, C, h, _, _ = s[x].op.axis
+        fused = s[x].fuse(n, C, h)
+        s[x].parallel(fused)
     else:
         s[x].parallel(s[x].op.axis[0])
     return s

From 5d53f0f9ecb490245f8dba542437b5b70b7ba87d Mon Sep 17 00:00:00 2001
From: Qiao Zhang <qiao@cs.washington.edu>
Date: Thu, 19 Apr 2018 17:26:50 -0700
Subject: [PATCH 279/949] Expose tvm.ndarray.empty in doc. (#1125)

Expose tvm.ndarray.empty which has already been implemented, just not yet documented.
---
 docs/api/python/ndarray.rst | 1 +
 1 file changed, 1 insertion(+)

diff --git a/docs/api/python/ndarray.rst b/docs/api/python/ndarray.rst
index a06117e05543..2c8f0c292a43 100644
--- a/docs/api/python/ndarray.rst
+++ b/docs/api/python/ndarray.rst
@@ -14,5 +14,6 @@ tvm.ndarray
 .. autofunction:: tvm.opencl
 .. autofunction:: tvm.metal
 .. autofunction:: tvm.ndarray.array
+.. autofunction:: tvm.ndarray.empty
 
 .. autofunction:: tvm.register_extension

From cd12f7566b7e4b73c5ee7970bc7de0e002241d2e Mon Sep 17 00:00:00 2001
From: libing4752 <libing475211023@sjtu.edu.cn>
Date: Sat, 21 Apr 2018 00:00:20 +0800
Subject: [PATCH 280/949] enhance cache write to support multiple tensors
 generated by ONE computeOp (#1042)

---
 include/tvm/schedule.h                        | 17 ++++
 python/tvm/schedule.py                        |  4 +-
 src/api/api_lang.cc                           |  9 +-
 src/schedule/schedule_dataflow_rewrite.cc     | 93 +++++++++++++++----
 tests/python/integration/test_ewise.py        | 44 +++++++++
 .../unittest/test_schedule_schedule_ops.py    | 15 +++
 6 files changed, 158 insertions(+), 24 deletions(-)

diff --git a/include/tvm/schedule.h b/include/tvm/schedule.h
index 6284906c1a4e..594e8b8a7c16 100644
--- a/include/tvm/schedule.h
+++ b/include/tvm/schedule.h
@@ -301,6 +301,23 @@ class Schedule : public NodeRef {
    *  User can further call compute_inline to inline the original layout and keep
    *  the data stored in the transformed layout.
    *
+   * \param tensor The tensors to be produced.
+   * \param scope The scope of the storage.
+   * \return The created tensor.
+   */
+  EXPORT Array<Tensor> cache_write(const Array<Tensor>& tensor, const std::string& scope);
+  /*!
+   * \brief Create a cache write tensor for producing tensor.
+   *  The the tensor will take over body of original tensor op.
+   *
+   *  This function can be used to do data layout transformation.
+   *  If there is a split/fuse/reorder on the data parallel axis of tensor
+   *  before cache_write is called. The intermediate cache stores
+   *  the data in the layout as the iteration order of leave axis.
+   *  The data will be transformed back to the original layout in the original tensor.
+   *  User can further call compute_inline to inline the original layout and keep
+   *  the data stored in the transformed layout.
+   * 
    * \param tensor The tensor to be produced.
    * \param scope The scope of the storage.
    * \return The created tensor.
diff --git a/python/tvm/schedule.py b/python/tvm/schedule.py
index 6305cc1d5e69..f54ae188c130 100644
--- a/python/tvm/schedule.py
+++ b/python/tvm/schedule.py
@@ -292,8 +292,8 @@ def cache_write(self, tensor, scope):
 
         Parameters
         ----------
-        tensor : Tensor
-            The tensor to be feed to.
+        tensor : Tensor, list or tuple
+            The tensors to be feed to. All the tensors must be produced by one computeOp
         scope : str
             The scope of cached
 
diff --git a/src/api/api_lang.cc b/src/api/api_lang.cc
index 03d085aa8d59..ba158a7c3f79 100644
--- a/src/api/api_lang.cc
+++ b/src/api/api_lang.cc
@@ -425,8 +425,13 @@ TVM_REGISTER_API("_ScheduleCacheRead")
 
 TVM_REGISTER_API("_ScheduleCacheWrite")
 .set_body([](TVMArgs args, TVMRetValue* ret) {
-    *ret = args[0].operator Schedule()
-        .cache_write(args[1], args[2]);
+    if (args[1].IsNodeType<Tensor>()) {
+      *ret = args[0].operator Schedule()
+          .cache_write(args[1].operator Tensor(), args[2]);
+    } else {
+      *ret = args[0].operator Schedule()
+          .cache_write(args[1].operator Array<Tensor>(), args[2]);
+    }
   });
 
 TVM_REGISTER_API("_ScheduleRFactor")
diff --git a/src/schedule/schedule_dataflow_rewrite.cc b/src/schedule/schedule_dataflow_rewrite.cc
index 1d9dfed4e5e1..89390dcc048d 100644
--- a/src/schedule/schedule_dataflow_rewrite.cc
+++ b/src/schedule/schedule_dataflow_rewrite.cc
@@ -78,6 +78,13 @@ void ReplaceDataFlow(const Array<Stage>& stages,
   }
 }
 
+inline bool ReduceEqual(const ir::Reduce* a, const ir::Reduce* b) {
+  return (a->combiner.same_as(b->combiner)) &&
+         (a->source.same_as(b->source)) &&
+         (a->axis.same_as(b->axis)) &&
+         (a->condition.same_as(b->condition));
+}
+
 Tensor Schedule::cache_read(const Tensor& tensor,
                             const std::string& scope,
                             const Array<Operation>& readers) {
@@ -128,15 +135,15 @@ Tensor Schedule::cache_read(const Tensor& tensor,
   return cache;
 }
 
-
 // Cache write and relayout the data according to loop pattern
-Tensor CacheWriteWithReLayout(Schedule sch,
-                              const Tensor& tensor,
+Array<Tensor> CacheWriteWithReLayout(Schedule sch,
+                              const Array<Tensor>& tensor_array,
                               const std::string& scope) {
+  size_t tensor_size = tensor_array.size();
   sch->InvalidateCache();
+  Tensor tensor = tensor_array[0];
   Stage orig_stage = sch[tensor->op];
   const ComputeOpNode* compute = orig_stage->op.as<ComputeOpNode>();
-
   std::unordered_set<IterVar> red_axis;
   for (IterVar iv : compute->reduce_axis) {
     red_axis.insert(iv);
@@ -182,9 +189,34 @@ Tensor CacheWriteWithReLayout(Schedule sch,
       vsub[iv->var.get()] = value_map.at(iv);
     }
   }
-  Expr body = VarReplacer(vsub).Mutate(compute->body[tensor->value_index]);
-  body = InjectPredicate(predicates, body);
-  body = VarReplacer(vsub2newvar).Mutate(body);
+
+  Expr body;
+  Array<Expr> body_list;
+  const ir::Reduce* first_reduce = nullptr;
+  for (auto cbody : compute->body) {
+    body = VarReplacer(vsub).Mutate(cbody);
+    body = InjectPredicate(predicates, body);
+    body = VarReplacer(vsub2newvar).Mutate(body);
+    // Reduce nodes in ONE computeOp must be the same except value_index
+    // This is right only if the oringinal body ensures Reduce nodes are the same
+    if (body->is_type<ir::Reduce>()) {
+      const ir::Reduce* reduce_body = body.as<ir::Reduce>();
+      if (first_reduce != nullptr) {
+        CHECK(ReduceEqual(reduce_body, first_reduce));
+        body = ir::Reduce::make(first_reduce->combiner,
+                                first_reduce->source,
+                                first_reduce->axis,
+                                first_reduce->condition,
+                                reduce_body->value_index);
+      } else {
+        first_reduce = reduce_body;
+      }
+    } else {
+      CHECK(first_reduce == nullptr)
+        << "cannot mix reduce and other node in ONE compute bodys";
+    }
+    body_list.push_back(body);
+  }
   // The reader args
   Array<Expr> args;
   {
@@ -200,16 +232,25 @@ Tensor CacheWriteWithReLayout(Schedule sch,
     }
   }
   Operation cache_op = ComputeOpNode::make(
-      compute->name + "." + scope, compute->tag, new_axis, {body});
-  Tensor cache_tensor = cache_op.output(0);
+      compute->name + "." + scope, compute->tag, new_axis, body_list);
+  Array<Tensor> cache_tensor_list;
+  Array<Expr> cache_expr_list;
+  for (size_t i = 0; i < tensor_size; i++) {
+    Tensor cache_tensor = cache_op.output(i);
+    cache_tensor_list.push_back(cache_tensor);
+    cache_expr_list.push_back(cache_tensor(args));
+  }
   Operation orig_new_op = ComputeOpNode::make(
-      compute->name, compute->tag, compute->axis,
-      {cache_tensor(args)});
+      compute->name, compute->tag, compute->axis, cache_expr_list);
   // The replace of the dataflow
   std::unordered_map<Tensor, Tensor> vmap;
   std::unordered_map<Tensor, Tensor> rvmap;
   vmap[orig_stage->op.output(0)] = orig_new_op.output(0);
   rvmap[orig_new_op.output(0)] = orig_stage->op.output(0);
+  for (size_t i = 0; i < tensor_size; i++) {
+    vmap[orig_stage->op.output(0)] = orig_new_op.output(0);
+    rvmap[orig_new_op.output(0)] = orig_stage->op.output(0);
+  }
   ReplaceDataFlow(sch->stages, &vmap, &rvmap);
   // mutate orig stage
   orig_stage->op = orig_new_op;
@@ -230,7 +271,26 @@ Tensor CacheWriteWithReLayout(Schedule sch,
   if (cache_stage->group.defined()) {
     ++cache_stage->group->num_child_stages;
   }
-  return cache_tensor;
+  return cache_tensor_list;
+}
+
+Array<Tensor> Schedule::cache_write(const Array<Tensor>& tensor_array,
+                             const std::string& scope) {
+  (*this)->InvalidateCache();
+  CHECK(tensor_array.size() > 0)
+      << "size of tensor_array must be greater than 0";
+  Tensor tensor = tensor_array[0];
+  Stage orig_stage = operator[](tensor->op);
+  const ComputeOpNode* compute = tensor->op.as<ComputeOpNode>();
+  CHECK(static_cast<size_t>(compute->num_outputs()) == tensor_array.size())
+      << "size of input tensor list must be same as number of stage outputs";
+  for (size_t i = 1; i < tensor_array.size(); i++) {
+    Stage tmp_stage = operator[](tensor_array[i]->op);
+    CHECK(orig_stage.same_as(tmp_stage))
+        << "Input tensor list must be generated by ONE computeOp";
+  }
+
+  return CacheWriteWithReLayout(*this, tensor_array, scope);
 }
 
 Tensor Schedule::cache_write(const Tensor& tensor,
@@ -243,7 +303,7 @@ Tensor Schedule::cache_write(const Tensor& tensor,
   CHECK_EQ(compute->num_outputs(), 1)
       << "cache write only support single output ComputeOp";
 
-  return CacheWriteWithReLayout(*this, tensor, scope);
+  return (CacheWriteWithReLayout(*this, {tensor}, scope))[0];
 }
 
 void RebaseNonZeroMinLoop(const Schedule& sch) {
@@ -289,13 +349,6 @@ void RebaseNonZeroMinLoop(const Schedule& sch) {
   }
 }
 
-inline bool ReduceEqual(const ir::Reduce* a, const ir::Reduce* b) {
-  return (a->combiner.same_as(b->combiner)) &&
-         (a->source.same_as(b->source)) &&
-         (a->axis.same_as(b->axis)) &&
-         (a->condition.same_as(b->condition));
-}
-
 void InjectInline(ScheduleNode* sch) {
   sch->InvalidateCache();
 
diff --git a/tests/python/integration/test_ewise.py b/tests/python/integration/test_ewise.py
index cebacdc2b9a5..e9c69bb57d9f 100644
--- a/tests/python/integration/test_ewise.py
+++ b/tests/python/integration/test_ewise.py
@@ -39,6 +39,49 @@ def check_device(device, host="stackvm"):
     check_device("vulkan")
 
 
+def test_multiple_cache_write():
+    # graph
+    n = tvm.convert(1024)
+    A0 = tvm.placeholder((n,), name='A0', dtype = "float32")
+    A1 = tvm.placeholder((n,), name='A1', dtype = "float32")
+    B0, B1 = tvm.compute((n,), 
+            lambda *i: (A0(*i) + A1(*i), A0(*i) * A1(*i)), 
+            name='B')
+    C = tvm.compute((n,), lambda *i: B0(*i) + B1(*i), 
+            name='C')
+    s = tvm.create_schedule(C.op)
+    # create iter var and assign them tags.
+    num_thread = 8
+    B0_cache, B1_cache = s.cache_write([B0, B1], "local")
+    bx, tx = s[C].split(C.op.axis[0], factor=num_thread)
+    s[B0].compute_at(s[C], bx)
+    s[B0_cache].compute_at(s[C], bx)
+    s[C].bind(bx, tvm.thread_axis("blockIdx.x"))
+    s[C].bind(tx, tvm.thread_axis("threadIdx.x"))
+    # one line to build the function.
+    def check_device(device, host="stackvm"):
+        if not tvm.module.enabled(host):
+            return
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
+            return
+        func = tvm.build(s, [A0, A1, C],
+                         device, host,
+                         name="multiple_cache_write")
+        ctx = tvm.context(device, 0)
+        # launch the kernel.
+        n = 1024
+        a0 = tvm.nd.array(np.random.uniform(size=n).astype(A0.dtype), ctx)
+        a1 = tvm.nd.array(np.random.uniform(size=n).astype(A1.dtype), ctx)
+        c = tvm.nd.array(np.zeros(n, dtype=C.dtype), ctx)
+        func(a0, a1, c)
+        np.testing.assert_allclose(
+            c.asnumpy(), a0.asnumpy() + a1.asnumpy() + (a0.asnumpy() * a1.asnumpy()), 
+            rtol=1e-5)
+
+    check_device("cuda", "llvm")
+    check_device("vulkan")
+    check_device("opencl")
 
 def test_log_pow_llvm():
     # graph
@@ -199,6 +242,7 @@ def check_device(device):
 if __name__ == "__main__":
     test_exp()
     try_warp_memory()
+    test_multiple_cache_write()
     test_add()
     test_log_pow_llvm()
     test_popcount()
diff --git a/tests/python/unittest/test_schedule_schedule_ops.py b/tests/python/unittest/test_schedule_schedule_ops.py
index ecb9c0acb7f8..8e6f4090d403 100644
--- a/tests/python/unittest/test_schedule_schedule_ops.py
+++ b/tests/python/unittest/test_schedule_schedule_ops.py
@@ -249,6 +249,20 @@ def test_schedule_cache_relayout3():
     bounds = tvm.schedule.InferBound(s)
     stmt = tvm.schedule.ScheduleOps(s, bounds)
 
+def test_schedule_cache_relayout4():
+    def _compute(*indice):
+        return A(*indice) + 1, B(*indice) / 2
+    m = tvm.var('m')
+    n = tvm.var('n')
+    A = tvm.placeholder((m*4, n), name='A')
+    B = tvm.placeholder((m*4, n), name='B')
+    C1, C2 = tvm.compute(A.shape, _compute, name='C')
+    s = tvm.create_schedule([C1.op, C2.op])
+    C1_cache, C2_cache = s.cache_write([C1, C2], "local")
+    s = s.normalize()
+    bounds = tvm.schedule.InferBound(s)
+    stmt = tvm.schedule.ScheduleOps(s, bounds)
+
 
 def test_schedule_bound_condition():
    A = tvm.placeholder((64,), name='A', dtype="float32")
@@ -265,6 +279,7 @@ def test_schedule_bound_condition():
 if __name__ == "__main__":
     test_schedule_middle_cache()
     test_inline_multi_reduce()
+    test_schedule_cache_relayout4()
     test_schedule_cache_relayout3()
     test_schedule_cache_relayout2()
     test_schedule_cache_relayout1()

From e0ea6b5cac5b3fcfe6c09969348961a21b61fee2 Mon Sep 17 00:00:00 2001
From: Yida Wang <yidawa@gmail.com>
Date: Fri, 20 Apr 2018 20:39:31 -0700
Subject: [PATCH 281/949] add two more device properties (#1124)

---
 include/tvm/runtime/device_api.h        |  4 +++-
 python/tvm/_ffi/runtime_ctypes.py       | 12 ++++++++++++
 src/runtime/cuda/cuda_device_api.cc     | 10 ++++++++++
 src/runtime/metal/metal_device_api.mm   |  2 ++
 src/runtime/opencl/opencl_device_api.cc | 21 +++++++++++++++++++++
 src/runtime/opencl/opencl_module.cc     |  2 +-
 src/runtime/opengl/opengl_device_api.cc |  2 ++
 src/runtime/rocm/rocm_device_api.cc     |  2 ++
 src/runtime/vulkan/vulkan_device_api.cc |  2 ++
 9 files changed, 55 insertions(+), 2 deletions(-)

diff --git a/include/tvm/runtime/device_api.h b/include/tvm/runtime/device_api.h
index aa8b4322368a..e43cc1f318ec 100644
--- a/include/tvm/runtime/device_api.h
+++ b/include/tvm/runtime/device_api.h
@@ -21,7 +21,9 @@ enum DeviceAttrKind : int {
   kWarpSize = 2,
   kMaxSharedMemoryPerBlock = 3,
   kComputeVersion = 4,
-  kDeviceName = 5
+  kDeviceName = 5,
+  kMaxClockRate = 6,
+  kMultiProcessorCount = 7
 };
 
 /*! \brief Number of bytes each allocation must align to */
diff --git a/python/tvm/_ffi/runtime_ctypes.py b/python/tvm/_ffi/runtime_ctypes.py
index 3e947cb6366c..3fc020c8781b 100644
--- a/python/tvm/_ffi/runtime_ctypes.py
+++ b/python/tvm/_ffi/runtime_ctypes.py
@@ -166,6 +166,18 @@ def device_name(self):
         return _api_internal._GetDeviceAttr(
             self.device_type, self.device_id, 5)
 
+    @property
+    def max_clock_rate(self):
+        """Return the max clock frequency of device."""
+        return _api_internal._GetDeviceAttr(
+            self.device_type, self.device_id, 6)
+
+    @property
+    def multi_processor_count(self):
+        """Return the number of compute units of device."""
+        return _api_internal._GetDeviceAttr(
+            self.device_type, self.device_id, 7)
+
     def sync(self):
         """Synchronize until jobs finished at the context."""
         check_call(_LIB.TVMSynchronize(self.device_type, self.device_id, None))
diff --git a/src/runtime/cuda/cuda_device_api.cc b/src/runtime/cuda/cuda_device_api.cc
index fe13e466ba80..3f697faab72c 100644
--- a/src/runtime/cuda/cuda_device_api.cc
+++ b/src/runtime/cuda/cuda_device_api.cc
@@ -62,6 +62,16 @@ class CUDADeviceAPI final : public DeviceAPI {
         *rv = std::string(props.name);
         return;
       }
+      case kMaxClockRate: {
+        CUDA_CALL(cudaDeviceGetAttribute(
+            &value, cudaDevAttrClockRate, ctx.device_id));
+        break;
+      }
+      case kMultiProcessorCount: {
+        CUDA_CALL(cudaDeviceGetAttribute(
+            &value, cudaDevAttrMultiProcessorCount, ctx.device_id));
+        break;
+      }
     }
     *rv = value;
   }
diff --git a/src/runtime/metal/metal_device_api.mm b/src/runtime/metal/metal_device_api.mm
index 077d2546c151..6d225ea7f888 100644
--- a/src/runtime/metal/metal_device_api.mm
+++ b/src/runtime/metal/metal_device_api.mm
@@ -42,6 +42,8 @@
     case kMaxSharedMemoryPerBlock: return;
     case kComputeVersion: return;
     case kDeviceName: return;
+    case kMaxClockRate: return;
+    case kMultiProcessorCount: return;
     case kExist: break;
   }
 }
diff --git a/src/runtime/opencl/opencl_device_api.cc b/src/runtime/opencl/opencl_device_api.cc
index 40f34a652830..da527c76134d 100644
--- a/src/runtime/opencl/opencl_device_api.cc
+++ b/src/runtime/opencl/opencl_device_api.cc
@@ -42,6 +42,11 @@ void OpenCLWorkspace::GetAttr(
       break;
     }
     case kWarpSize: {
+      /* TODO: the warp size of OpenCL device is not always 1
+               e.g. Intel GPU has a sub group concept which contains 8 - 32 work items,
+               corresponding to the number of SIMD entries the heardware configures.
+               We need to figure out a way to query this information from the hardware.
+      */
       *rv = 1;
       break;
     }
@@ -62,6 +67,22 @@ void OpenCLWorkspace::GetAttr(
       *rv = std::string(value);
       break;
     }
+    case kMaxClockRate: {
+      cl_uint value;
+      OPENCL_CALL(clGetDeviceInfo(
+          devices[index], CL_DEVICE_MAX_CLOCK_FREQUENCY,
+          sizeof(cl_uint), &value, nullptr));
+      *rv = static_cast<int32_t>(value);
+      break;
+    }
+    case kMultiProcessorCount: {
+      cl_uint value;
+      OPENCL_CALL(clGetDeviceInfo(
+          devices[index], CL_DEVICE_MAX_COMPUTE_UNITS,
+          sizeof(cl_uint), &value, nullptr));
+      *rv = static_cast<int32_t>(value);
+      break;
+    }
     case kExist: break;
   }
 }
diff --git a/src/runtime/opencl/opencl_module.cc b/src/runtime/opencl/opencl_module.cc
index bde7e6b27418..d8831880fd8d 100644
--- a/src/runtime/opencl/opencl_module.cc
+++ b/src/runtime/opencl/opencl_module.cc
@@ -176,7 +176,7 @@ class OpenCLModuleNode : public ModuleNode {
 
 class OpenCLWrappedFunc {
  public:
-  // initialize the CUDA function.
+  // initialize the OpenCL function.
   void Init(OpenCLModuleNode* m,
             std::shared_ptr<ModuleNode> sptr,
             OpenCLModuleNode::KTRefEntry entry,
diff --git a/src/runtime/opengl/opengl_device_api.cc b/src/runtime/opengl/opengl_device_api.cc
index 0c6354e231cf..e925f863df9d 100644
--- a/src/runtime/opengl/opengl_device_api.cc
+++ b/src/runtime/opengl/opengl_device_api.cc
@@ -98,6 +98,8 @@ void OpenGLWorkspace::GetAttr(
       break;
     }
     case kDeviceName: return;
+    case kMaxClockRate: return;
+    case kMultiProcessorCount: return;
   }
 }
 
diff --git a/src/runtime/rocm/rocm_device_api.cc b/src/runtime/rocm/rocm_device_api.cc
index 256c715e92b8..55272561c739 100644
--- a/src/runtime/rocm/rocm_device_api.cc
+++ b/src/runtime/rocm/rocm_device_api.cc
@@ -52,6 +52,8 @@ class ROCMDeviceAPI final : public DeviceAPI {
         return;
       }
       case kDeviceName: return;
+      case kMaxClockRate: return;
+      case kMultiProcessorCount: return;
     }
     *rv = value;
   }
diff --git a/src/runtime/vulkan/vulkan_device_api.cc b/src/runtime/vulkan/vulkan_device_api.cc
index aaf658bbac4b..a3e4fc294e60 100644
--- a/src/runtime/vulkan/vulkan_device_api.cc
+++ b/src/runtime/vulkan/vulkan_device_api.cc
@@ -74,6 +74,8 @@ void VulkanWorkspace::GetAttr(
       break;
     }
     case kDeviceName: return;
+    case kMaxClockRate: return;
+    case kMultiProcessorCount: return;
     case kExist: break;
   }
 }

From 8d314b22953292fb4f14d088f480afaaccd69ae5 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 20 Apr 2018 21:18:40 -0700
Subject: [PATCH 282/949] [PASS] Revert the change of intel gpu warp index
 (#1127)

---
 include/tvm/build_module.h     | 6 ------
 python/tvm/build_module.py     | 7 -------
 src/codegen/build_module.cc    | 2 --
 src/pass/inject_copy_intrin.cc | 2 +-
 4 files changed, 1 insertion(+), 16 deletions(-)

diff --git a/include/tvm/build_module.h b/include/tvm/build_module.h
index e9d6123b5e06..9c54ab7b45fb 100644
--- a/include/tvm/build_module.h
+++ b/include/tvm/build_module.h
@@ -32,11 +32,6 @@ class TargetNode : public Node {
   int max_num_threads = 1;
   /*! \brief The warp size that should be used by the LowerThreadAllreduce pass */
   int thread_warp_size = 1;
-  /*!
-   * \brief The thread index that is the lowest(correspond to warp)
-   *  In cuda it is threadIdx.x, but can be different in some platform.
-   */
-  int thread_warp_index = 0;
   /*! \brief Keys for this target */
   Array<Expr> keys_array;
   /*! \brief Options for this target */
@@ -53,7 +48,6 @@ class TargetNode : public Node {
     v->Visit("device_type", &device_type);
     v->Visit("max_num_threads", &max_num_threads);
     v->Visit("thread_warp_size", &thread_warp_size);
-    v->Visit("thread_warp_index", &thread_warp_index);
     v->Visit("keys_array", &keys_array);
     v->Visit("options_array", &options_array);
     v->Visit("libs_array", &libs_array);
diff --git a/python/tvm/build_module.py b/python/tvm/build_module.py
index 6364b32fd64c..5728c4f28c8f 100755
--- a/python/tvm/build_module.py
+++ b/python/tvm/build_module.py
@@ -470,13 +470,6 @@ def build(sch,
     for i, func in enumerate(fdevice):
         warp_size = target.thread_warp_size
         fdevice[i] = ir_pass.LowerWarpMemory(func, warp_size)
-        warp_index = target.thread_warp_index
-        if warp_index != 0:
-            assert warp_index == 2
-            # swap z and x
-            tmap = {api.convert("threadIdx.z"): api.thread_axis("threadIdx.x"),
-                    api.convert("threadIdx.x"): api.thread_axis("threadIdx.z")}
-            fdevice[i] = ir_pass.RemapThreadAxis(func, tmap)
 
     if "gpu" in target.keys and not fdevice:
         warnings.warn(
diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc
index 0bb2dfcf0206..318e231fc67c 100644
--- a/src/codegen/build_module.cc
+++ b/src/codegen/build_module.cc
@@ -78,8 +78,6 @@ Target CreateTarget(const std::string& target_name,
     t->max_num_threads = 256;
     if (t->device_name == "intel_gpu") {
       t->thread_warp_size = 16;
-      // use threadIdx.z for index
-      t->thread_warp_index = 2;
     }
   } else if (target_name == "metal" || target_name == "vulkan") {
     if (target_name == "metal") {
diff --git a/src/pass/inject_copy_intrin.cc b/src/pass/inject_copy_intrin.cc
index 909d46095649..094a5932400b 100644
--- a/src/pass/inject_copy_intrin.cc
+++ b/src/pass/inject_copy_intrin.cc
@@ -156,7 +156,7 @@ class CopyIntrinInjector : public IRMutator {
     }
   }
   // pragma key
-  const std::string& pragma_key_;
+  std::string pragma_key_;
   // function to lower copy intrinsics.
   const PackedFunc& flower_copy_fromto_;
   // Storage scope

From 1788f8ac7592f0d66414010b21c8fa1b73ee9b2e Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 21 Apr 2018 14:11:16 -0700
Subject: [PATCH 283/949] [MAINTAINER] Add Nick Hynes to CodeOwner of SGX
 (#1128)

---
 CODEOWNERS      | 6 ++++++
 CONTRIBUTORS.md | 1 +
 2 files changed, 7 insertions(+)

diff --git a/CODEOWNERS b/CODEOWNERS
index e010c93878f5..5d0e94533bf4 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -10,6 +10,10 @@ src/codegen/llvm/*          @aatluri
 # ROCM runtime
 src/runtime/rocm/*    @aatluri
 
+# SGX support
+src/runtime/sgx/*       @nhynes
+apps/sgx/*              @nhynes
+
 # JVM language
 jvm/*   @yzhliu
 
@@ -19,3 +23,5 @@ src/codegen/*opengl*    @phisiart
 
 # TOPI
 topi/python/topi/*  @Laurawly @Huyuwei
+
+
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 064edd39e46a..08f240fecfd5 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -49,6 +49,7 @@ and are qualified to lead development and review changes of the owned module.
 - [Yuwei Hu](https://github.com/Huyuwei) TOPI
 - [Yizhi Liu](https://github.com/yzhliu) JVM package
 - [Zhixun Tan](https://github.com/phisiart) OpenGL/WebGL backend
+- [Nick Hynes](https://github.com/nhynes) SGX and secured computing
 
 
 List of Contributors

From 035b6254e681027ca12d4ee66a2502a3e23c54a5 Mon Sep 17 00:00:00 2001
From: Yizhi Liu <liuyizhi@apache.org>
Date: Mon, 23 Apr 2018 10:11:23 -0700
Subject: [PATCH 284/949] [TOPI] parallel schedule improve for x86 &
 layout_transform support (#1130)

* add layout_transform. add schedule support for ndim>=5 for x86

* fix lint
---
 topi/include/topi/nn.h            | 22 +++++++
 topi/python/topi/generic/nn.py    | 27 +++++++++
 topi/python/topi/x86/__init__.py  |  1 +
 topi/python/topi/x86/injective.py | 10 ++--
 topi/python/topi/x86/nn.py        | 49 +++++-----------
 topi/python/topi/x86/pooling.py   | 95 +++++++++++++++++++++++++++++++
 6 files changed, 162 insertions(+), 42 deletions(-)
 create mode 100644 topi/python/topi/x86/pooling.py

diff --git a/topi/include/topi/nn.h b/topi/include/topi/nn.h
index 62f4e5dbdec0..d6079d0066a5 100644
--- a/topi/include/topi/nn.h
+++ b/topi/include/topi/nn.h
@@ -482,5 +482,27 @@ inline tvm::Tensor group_conv2d_ngchw(const tvm::Tensor& I,
   return tvm::compute(output_shape, l, name, tag);
 }
 
+using FLayoutIndicesTransform = std::function<Array<Expr>(const Array<Var>& indices)>;
+/*!
+ * \brief Transform the layout according to the mapping function \p to_src_indices.
+ * \param src the source input.
+ * \param dst_shape the output shape.
+ * \param to_src_indices the mapping function from input index to output index.
+ * \param name output tensor name.
+ * \param tag output tensor tag.
+ * \return A tensor with shape \p dst_shape.
+ */
+inline Tensor layout_transform(const Tensor& src,
+                               const Array<Expr>& dst_shape,
+                               const FLayoutIndicesTransform& to_src_indices,
+                               const std::string name = "layout_transform",
+                               const std::string tag = kInjective) {
+  auto src_shape = src->shape;
+  return compute(
+  dst_shape, [&](const Array<Var>& dst_indices) {
+    return src(to_src_indices(dst_indices));
+  }, name, tag);
+}
+
 }  // namespace topi
 #endif  // TOPI_NN_H_
diff --git a/topi/python/topi/generic/nn.py b/topi/python/topi/generic/nn.py
index 7252a23b90c3..5a16d12206a3 100644
--- a/topi/python/topi/generic/nn.py
+++ b/topi/python/topi/generic/nn.py
@@ -1,3 +1,4 @@
+# pylint: disable=invalid-name,unused-argument
 """Generic nn operators"""
 from __future__ import absolute_import as _abs
 import tvm
@@ -53,6 +54,32 @@ def schedule_conv2d_nhwc(outs):
     return _default_schedule(outs, False)
 
 
+@tvm.target.generic_func
+def schedule_conv2d_NCHWc(num_filter, kernel_size, strides, padding, outs):
+    """Schedule for conv2d_NCHW[x]c
+
+    Parameters
+    ----------
+    num_filter: int
+                The number of filter, i.e., the output channel.
+    kernel_size: tuple of int
+                 (kernel_height, kernel_width)
+    strides: tuple of int
+             (stride_of_height, stride_of_width)
+    padding: tuple of int
+             (pad_of_height, pad_of_width)
+    outs: Array of Tensor
+          The computation graph description of conv2d_NCHWc
+          in the format of an array of tensors.
+
+    Returns
+    -------
+    sch: Schedule
+        The computation schedule for the op.
+    """
+    return _default_schedule(outs, False)
+
+
 @tvm.target.generic_func
 def schedule_conv2d_transpose_nchw(outs):
     """Schedule for conv2d_transpose_nchw
diff --git a/topi/python/topi/x86/__init__.py b/topi/python/topi/x86/__init__.py
index ef227d035fce..d001b5fdca57 100644
--- a/topi/python/topi/x86/__init__.py
+++ b/topi/python/topi/x86/__init__.py
@@ -7,3 +7,4 @@
 from .binary_dense import schedule_binary_dense
 from .nn import *
 from .injective import *
+from .pooling import schedule_pool, schedule_global_pool
diff --git a/topi/python/topi/x86/injective.py b/topi/python/topi/x86/injective.py
index 7a69aee8ea2e..b43ebb98b82f 100644
--- a/topi/python/topi/x86/injective.py
+++ b/topi/python/topi/x86/injective.py
@@ -23,13 +23,11 @@ def schedule_injective(outs):
     x = outs[0]
     s = tvm.create_schedule([x.op for x in outs])
     tvm.schedule.AutoInlineInjective(s)
-    if len(s[x].op.axis) == 4:
-        n, c, _, _ = s[x].op.axis
-        fused = s[x].fuse(n, c) # for nhwc layout, fuse n and h
+    if len(s[x].op.axis) >= 5:
+        fused = s[x].fuse(s[x].op.axis[0], s[x].op.axis[1], s[x].op.axis[2])
         s[x].parallel(fused)
-    elif len(s[x].op.axis) == 5:
-        n, C, h, _, _ = s[x].op.axis
-        fused = s[x].fuse(n, C, h)
+    elif len(s[x].op.axis) >= 3:
+        fused = s[x].fuse(s[x].op.axis[0], s[x].op.axis[1])
         s[x].parallel(fused)
     else:
         s[x].parallel(s[x].op.axis[0])
diff --git a/topi/python/topi/x86/nn.py b/topi/python/topi/x86/nn.py
index 4c886b48d9c2..ccb9f01471a1 100644
--- a/topi/python/topi/x86/nn.py
+++ b/topi/python/topi/x86/nn.py
@@ -5,23 +5,6 @@
 from .. import generic
 from .. import tag
 
-def _default_schedule(outs, auto_inline):
-    """Default schedule for x86."""
-    x = outs[0]
-    s = tvm.create_schedule([x.op for x in outs])
-    if auto_inline:
-        tvm.schedule.AutoInlineInjective(s)
-        s[x].fuse(s[x].op.axis)
-        return s
-    if len(s[x].op.axis) == 4:
-        n, c, _, _ = s[x].op.axis
-        fused = s[x].fuse(n, c) # for nhwc layout, fuse n and h
-        s[x].parallel(fused)
-    else:
-        s[x].parallel(s[x].op.axis[0])
-    return s
-
-
 @generic.schedule_softmax.register(["cpu"])
 def schedule_softmax(outs):
     """Schedule for softmax
@@ -37,25 +20,19 @@ def schedule_softmax(outs):
     sch: Schedule
         The computation schedule for the op.
     """
-    return _default_schedule(outs, False)
-
-
-@generic.schedule_pool.register(["cpu"])
-def schedule_pool(outs):
-    """Schedule for pool
-
-    Parameters
-    ----------
-    outs: Array of Tensor
-          The computation graph description of pool
-          in the format of an array of tensors.
-
-    Returns
-    -------
-    sch: Schedule
-        The computation schedule for the op.
-    """
-    return _default_schedule(outs, False)
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    x = outs[0]
+    s = tvm.create_schedule([x.op for x in outs])
+    tvm.schedule.AutoInlineInjective(s)
+    if len(s[x].op.axis) >= 5:
+        fused = s[x].fuse(s[x].op.axis[0], s[x].op.axis[1], s[x].op.axis[2])
+        s[x].parallel(fused)
+    elif len(s[x].op.axis) >= 3:
+        fused = s[x].fuse(s[x].op.axis[0], s[x].op.axis[1])
+        s[x].parallel(fused)
+    else:
+        s[x].parallel(s[x].op.axis[0])
+    return s
 
 
 @generic.schedule_dense.register(["cpu"])
diff --git a/topi/python/topi/x86/pooling.py b/topi/python/topi/x86/pooling.py
new file mode 100644
index 000000000000..0dedd3801587
--- /dev/null
+++ b/topi/python/topi/x86/pooling.py
@@ -0,0 +1,95 @@
+# pylint: disable=invalid-name, unused-variable
+"""Schedule for pooling operators"""
+import tvm
+from .. import generic
+from .. import tag
+
+def _parallel_sch(sch):
+    if len(sch.op.axis) >= 5:
+        fused = sch.fuse(sch.op.axis[0], sch.op.axis[1], sch.op.axis[2])
+        sch.parallel(fused)
+    elif len(sch.op.axis) >= 3:
+        n, c, _, _ = sch.op.axis
+        fused = sch.fuse(n, c) # for nhwc layout, fuse n and h
+        sch.parallel(fused)
+    else:
+        sch.parallel(sch.op.axis[0])
+
+
+@generic.schedule_pool.register(["cpu"])
+def schedule_pool(outs):
+    """Schedule for pool
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+          The computation graph description of pool
+          in the format of an array of tensors.
+
+    Returns
+    -------
+    sch: Schedule
+        The computation schedule for the op.
+    """
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    s = tvm.create_schedule([x.op for x in outs])
+
+    def _schedule(PaddedInput, Pool):
+        if isinstance(PaddedInput.op, tvm.tensor.ComputeOp):
+            s[PaddedInput].compute_inline()
+        _parallel_sch(s[Pool])
+
+    def traverse(OP):
+        """Internal travserse function"""
+        # inline all one-to-one-mapping operators except the last stage (output)
+        if tag.is_broadcast(OP.tag):
+            if OP not in s.outputs:
+                s[OP].compute_inline()
+            for tensor in OP.input_tensors:
+                if tensor.op.input_tensors:
+                    traverse(tensor.op)
+        # schedule pool
+        elif OP.tag.startswith('pool'):
+            PaddedInput = OP.input_tensors[0]
+            Pool = OP.output(0)
+            _schedule(PaddedInput, Pool)
+        else:
+            raise RuntimeError("Unsupported operator: %s" % OP.tag)
+    traverse(outs[0].op)
+    return s
+
+
+@generic.schedule_global_pool.register(["cpu"])
+def schedule_global_pool(outs):
+    """Schedule for global pool
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+          The computation graph description of pool
+          in the format of an array of tensors.
+
+    Returns
+    -------
+    sch: Schedule
+        The computation schedule for the op.
+    """
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    s = tvm.create_schedule([x.op for x in outs])
+    def traverse(OP):
+        """Internal travserse function"""
+        # inline all one-to-one-mapping operators except the last stage (output)
+        if tag.is_broadcast(OP.tag):
+            if OP not in s.outputs:
+                s[OP].compute_inline()
+            for tensor in OP.input_tensors:
+                if tensor.op.input_tensors:
+                    traverse(tensor.op)
+        # schedule pool
+        elif OP.tag.startswith('global_pool'):
+            Pool = OP.output(0)
+            _parallel_sch(s[Pool])
+        else:
+            raise RuntimeError("Unsupported operator: %s" % OP.tag)
+    traverse(outs[0].op)
+    return s

From d1cf60751e9d5034cee810a3646d87935e622a65 Mon Sep 17 00:00:00 2001
From: Yuwei Hu <huyuwei1995@gmail.com>
Date: Wed, 25 Apr 2018 12:03:58 -0400
Subject: [PATCH 285/949] [TOPI] support dilated conv2d and depthwise_conv2d
 (#1129)

* support dilation in conv2d and depthwise_conv2d

* handle dilated conv in extern libs (cudnn, miopen)
---
 topi/python/topi/cuda/conv2d.py               | 28 +++++++++++++++----
 topi/python/topi/cuda/conv2d_hwcn.py          |  2 ++
 topi/python/topi/cuda/conv2d_nchw.py          |  2 ++
 topi/python/topi/cuda/depthwise_conv2d.py     |  4 +++
 topi/python/topi/generic/extern.py            |  2 +-
 topi/python/topi/mali/conv2d.py               | 12 ++++++++
 topi/python/topi/mali/depthwise_conv2d.py     |  2 ++
 topi/python/topi/nn/dilate.py                 |  2 +-
 topi/python/topi/nn/pad.py                    |  2 +-
 topi/python/topi/opengl/conv2d_nchw.py        |  3 ++
 topi/python/topi/rasp/conv2d.py               |  2 ++
 topi/python/topi/rasp/depthwise_conv2d.py     |  2 ++
 topi/python/topi/rocm/conv2d.py               | 21 +++++++++++---
 topi/python/topi/x86/conv2d.py                |  7 +++++
 topi/tests/python/test_topi_conv2d_hwcn.py    | 11 +++++---
 topi/tests/python/test_topi_conv2d_nchw.py    | 17 +++++++----
 topi/tests/python/test_topi_conv2d_nhwc.py    | 10 +++++--
 .../python/test_topi_depthwise_conv2d.py      | 21 +++++++++-----
 18 files changed, 118 insertions(+), 32 deletions(-)

diff --git a/topi/python/topi/cuda/conv2d.py b/topi/python/topi/cuda/conv2d.py
index 2641bfe490c4..3c494cdeb0fa 100644
--- a/topi/python/topi/cuda/conv2d.py
+++ b/topi/python/topi/cuda/conv2d.py
@@ -4,6 +4,7 @@
 from tvm.contrib import cudnn
 import topi
 from ..nn.conv2d import conv2d
+from ..util import get_const_int
 
 @conv2d.register("cuda")
 def conv2d_cuda(data, kernel, stride, padding, layout='NCHW', out_dtype='float32'):
@@ -40,6 +41,23 @@ def conv2d_cuda(data, kernel, stride, padding, layout='NCHW', out_dtype='float32
         pad_h = pad_w = padding
     else:
         pad_h, pad_w = padding
+    # handle dilation
+    dilation_h = dilation_w = 1
+    kernel_tvm = kernel
+    kernel_cudnn = kernel
+    if isinstance(kernel.op, tvm.tensor.ComputeOp) and "dilate" in kernel.op.tag:
+        kernel_before_dilation = kernel.op.input_tensors[0]
+        kernel_cudnn = kernel_before_dilation
+        if layout == 'NCHW':
+            dilation_h = (get_const_int(kernel.shape[2]) + get_const_int(kernel_before_dilation.shape[2]) - 1) \
+                // get_const_int(kernel_before_dilation.shape[2])
+            dilation_w = (get_const_int(kernel.shape[3]) + get_const_int(kernel_before_dilation.shape[3]) - 1) \
+                // get_const_int(kernel_before_dilation.shape[2])
+        elif layout == 'NHWC':
+            dilation_h = (get_const_int(kernel.shape[1]) + get_const_int(kernel_before_dilation.shape[1]) - 1) \
+                // get_const_int(kernel_before_dilation.shape[1])
+            dilation_w = (get_const_int(kernel.shape[2]) + get_const_int(kernel_before_dilation.shape[2]) - 1) \
+                // get_const_int(kernel_before_dilation.shape[2])
     target = tvm.target.current_target()
     if "cudnn" in target.libs:
         assert layout != 'HWCN', "HWCN layout not supported with CUDNN."
@@ -47,19 +65,19 @@ def conv2d_cuda(data, kernel, stride, padding, layout='NCHW', out_dtype='float32
         if layout == 'NHWC':
             tensor_format = 1 # CUDNN_TENSOR_NHWC
         return cudnn.conv2d_forward(data,
-                                    kernel,
+                                    kernel_cudnn,
                                     stride_h,
                                     stride_w,
                                     pad_h,
                                     pad_w,
-                                    1,  # dilation_h
-                                    1,  # dilation_w
+                                    dilation_h,
+                                    dilation_w,
                                     conv_mode=1,
                                     tensor_format=tensor_format,
                                     algo=-1) # let CUDNN choose the best algo
     elif layout == 'NCHW':
-        return topi.nn.conv2d_nchw(data, kernel, stride, padding, out_dtype)
+        return topi.nn.conv2d_nchw(data, kernel_tvm, stride, padding, out_dtype)
     elif layout == 'HWCN':
-        return topi.nn.conv2d_hwcn(data, kernel, stride, padding, out_dtype)
+        return topi.nn.conv2d_hwcn(data, kernel_tvm, stride, padding, out_dtype)
     else:
         raise ValueError("not support this layout {} yet".format(layout))
diff --git a/topi/python/topi/cuda/conv2d_hwcn.py b/topi/python/topi/cuda/conv2d_hwcn.py
index ec902585508c..082966a3c05a 100644
--- a/topi/python/topi/cuda/conv2d_hwcn.py
+++ b/topi/python/topi/cuda/conv2d_hwcn.py
@@ -110,6 +110,8 @@ def traverse(operator):
         elif operator.tag == 'conv2d_hwcn':
             Apad = operator.input_tensors[0]
             W = operator.input_tensors[1]
+            if isinstance(W.op, tvm.tensor.ComputeOp) and 'dilate' in W.op.tag:
+                sch[W].compute_inline()
             B = operator.output(0)
             schedule(Apad, W, B)
         else:
diff --git a/topi/python/topi/cuda/conv2d_nchw.py b/topi/python/topi/cuda/conv2d_nchw.py
index e313029e74b0..71104c59a9aa 100644
--- a/topi/python/topi/cuda/conv2d_nchw.py
+++ b/topi/python/topi/cuda/conv2d_nchw.py
@@ -495,6 +495,8 @@ def traverse(OP):
         if 'conv2d_nchw' in OP.tag:
             temp = OP.input_tensors[0]
             Filter = OP.input_tensors[1]
+            if isinstance(Filter.op, tvm.tensor.ComputeOp) and 'dilate' in Filter.op.tag:
+                s[Filter].compute_inline()
             Output = OP.output(0)
             schedule(temp, Filter, Output)
 
diff --git a/topi/python/topi/cuda/depthwise_conv2d.py b/topi/python/topi/cuda/depthwise_conv2d.py
index 3045774296d0..1901fcbb7cfc 100644
--- a/topi/python/topi/cuda/depthwise_conv2d.py
+++ b/topi/python/topi/cuda/depthwise_conv2d.py
@@ -114,6 +114,8 @@ def traverse(OP):
         if OP.tag == 'depthwise_conv2d_nchw':
             PaddedInput = OP.input_tensors[0]
             Filter = OP.input_tensors[1]
+            if isinstance(Filter.op, tvm.tensor.ComputeOp) and 'dilate' in Filter.op.tag:
+                s[Filter].compute_inline()
             DepthwiseConv2d = OP.output(0)
             _schedule(PaddedInput, Filter, DepthwiseConv2d)
 
@@ -191,6 +193,8 @@ def traverse(OP):
         if OP.tag == 'depthwise_conv2d_nhwc':
             PaddedInput = OP.input_tensors[0]
             Filter = OP.input_tensors[1]
+            if isinstance(Filter.op, tvm.tensor.ComputeOp) and 'dilate' in Filter.op.tag:
+                s[Filter].compute_inline()
             DepthwiseConv2d = OP.output(0)
             _schedule(PaddedInput, Filter, DepthwiseConv2d)
 
diff --git a/topi/python/topi/generic/extern.py b/topi/python/topi/generic/extern.py
index 082c1bca83ea..92a47f8a9630 100644
--- a/topi/python/topi/generic/extern.py
+++ b/topi/python/topi/generic/extern.py
@@ -21,6 +21,6 @@ def schedule_extern(outs):
     """
     target = tvm.target.current_target(allow_none=False)
     if target.target_name != "llvm":
-        raise RuntimeError("schedule_injective not registered for '%s'" % target)
+        raise RuntimeError("schedule_extern not registered for '%s'" % target)
     outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
     return tvm.create_schedule([x.op for x in outs])
diff --git a/topi/python/topi/mali/conv2d.py b/topi/python/topi/mali/conv2d.py
index 5b4cf5bae6ff..ad1dfbe61740 100644
--- a/topi/python/topi/mali/conv2d.py
+++ b/topi/python/topi/mali/conv2d.py
@@ -289,6 +289,10 @@ def _schedule_spatialpack_conv2d(s, op):
     if data.dtype == 'float16' and (util.get_const_int(conv.shape[1]) == 4 or output_height == 28):
         num_thread //= 2
 
+    # schedule dilation
+    if isinstance(kernel.op, tvm.tensor.ComputeOp) and "dilate" in kernel.op.tag:
+        s[kernel].compute_inline()
+
     # schedule padding
     if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
         data_pad = data
@@ -431,6 +435,10 @@ def _schedule_im2col_conv2d(s, op):
             num_thread1 = num_thread * 2
             num_thread2 = num_thread // 2
 
+    # schedule dilation
+    if isinstance(kernel.op, tvm.tensor.ComputeOp) and "dilate" in kernel.op.tag:
+        s[kernel].compute_inline()
+
     # schedule padding
     if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
         data_pad = data
@@ -616,6 +624,10 @@ def _schedule_winograd(s, op):
     data_pad = s[d].op.input_tensors[0]
     data = s[data_pad].op.input_tensors[0]
 
+    # dilation
+    if isinstance(kernel.op, tvm.tensor.ComputeOp) and "dilate" in kernel.op.tag:
+        s[kernel].compute_inline()
+
     # padding
     s[data_pad].compute_inline()
 
diff --git a/topi/python/topi/mali/depthwise_conv2d.py b/topi/python/topi/mali/depthwise_conv2d.py
index 428140550528..61ec6334e0d2 100644
--- a/topi/python/topi/mali/depthwise_conv2d.py
+++ b/topi/python/topi/mali/depthwise_conv2d.py
@@ -100,6 +100,8 @@ def traverse(op):
         if op.tag == 'depthwise_conv2d_nchw':
             pad_data = op.input_tensors[0]
             kernel = op.input_tensors[1]
+            if isinstance(kernel.op, tvm.tensor.ComputeOp) and 'dilate' in kernel.op.tag:
+                s[kernel].compute_inline()
             conv = op.output(0)
             _schedule(pad_data, kernel, conv)
 
diff --git a/topi/python/topi/nn/dilate.py b/topi/python/topi/nn/dilate.py
index 07433280cfb3..8ea2808781a5 100644
--- a/topi/python/topi/nn/dilate.py
+++ b/topi/python/topi/nn/dilate.py
@@ -5,7 +5,7 @@
 from .. import util
 from .. import tag
 
-@tvm.tag_scope(tag=tag.INJECTIVE)
+@tvm.tag_scope(tag=tag.INJECTIVE+",dilate")
 def dilate(data, strides, name="DilatedInput"):
     """Dilate data with zeros.
 
diff --git a/topi/python/topi/nn/pad.py b/topi/python/topi/nn/pad.py
index 9420f8a70308..7ebbc566c3a3 100644
--- a/topi/python/topi/nn/pad.py
+++ b/topi/python/topi/nn/pad.py
@@ -6,7 +6,7 @@
 
 @tvm.tag_scope(tag=tag.INJECTIVE+",pad")
 def pad(data, pad_before, pad_after=None, pad_value=0.0, name="PadInput"):
-    """Dilate Input with zeros.
+    """Pad Input with zeros.
 
     Parameters
     ----------
diff --git a/topi/python/topi/opengl/conv2d_nchw.py b/topi/python/topi/opengl/conv2d_nchw.py
index 7e8b7275f75d..573270c375a0 100644
--- a/topi/python/topi/opengl/conv2d_nchw.py
+++ b/topi/python/topi/opengl/conv2d_nchw.py
@@ -43,6 +43,9 @@ def traverse(OP):
         elif OP.tag.startswith('conv2d_nchw'):
             conv2d = OP.output(0)
             data = OP.input_tensors[0]
+            kernel = OP.input_tensors[1]
+            if isinstance(kernel.op, tvm.tensor.ComputeOp) and "dilate" in kernel.op.tag:
+                s[kernel].compute_inline()
             _schedule(conv2d, data)
         else:
             raise RuntimeError("Unsupported operator: %s" % OP.tag)
diff --git a/topi/python/topi/rasp/conv2d.py b/topi/python/topi/rasp/conv2d.py
index 6b2ce832e676..13af1b93705e 100644
--- a/topi/python/topi/rasp/conv2d.py
+++ b/topi/python/topi/rasp/conv2d.py
@@ -328,6 +328,8 @@ def traverse(op):
             conv_out = op.input_tensors[0]
             kernel_vec = conv_out.op.input_tensors[1]
             kernel = kernel_vec.op.input_tensors[0]
+            if isinstance(kernel.op, tvm.tensor.ComputeOp) and "dilate" in kernel.op.tag:
+                s[kernel].compute_inline()
             data_vec = conv_out.op.input_tensors[0]
             data = data_vec.op.input_tensors[0]
             data_pad = None
diff --git a/topi/python/topi/rasp/depthwise_conv2d.py b/topi/python/topi/rasp/depthwise_conv2d.py
index bb8ebed3465f..b2ff78e46d88 100644
--- a/topi/python/topi/rasp/depthwise_conv2d.py
+++ b/topi/python/topi/rasp/depthwise_conv2d.py
@@ -194,6 +194,8 @@ def traverse(op):
         if op.tag == 'depthwise_conv2d_nchw':
             output = op.output(0)
             kernel = op.input_tensors[1]
+            if isinstance(kernel.op, tvm.tensor.ComputeOp) and "dilate" in kernel.op.tag:
+                s[kernel].compute_inline()
             data = op.input_tensors[0]
             data_pad = None
             if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
diff --git a/topi/python/topi/rocm/conv2d.py b/topi/python/topi/rocm/conv2d.py
index 4dd5e5fd0735..1aa125f8f68f 100644
--- a/topi/python/topi/rocm/conv2d.py
+++ b/topi/python/topi/rocm/conv2d.py
@@ -5,6 +5,8 @@
 import topi
 from .. import generic
 from ..nn.conv2d import conv2d
+from ..util import get_const_int
+
 
 @conv2d.register("rocm")
 def conv2d_rocm(data, kernel, stride, padding, layout='NCHW', out_dtype='float32'):
@@ -42,18 +44,29 @@ def conv2d_rocm(data, kernel, stride, padding, layout='NCHW', out_dtype='float32
         pad_h = pad_w = padding
     else:
         pad_h, pad_w = padding
+    # handle dilation
+    dilation_h = dilation_w = 1
+    kernel_tvm = kernel
+    kernel_cudnn = kernel
+    if isinstance(kernel.op, tvm.tensor.ComputeOp) and "dilate" in kernel.op.tag:
+        kernel_before_dilation = kernel.op.input_tensors[0]
+        kernel_cudnn = kernel_before_dilation
+        dilation_h = (get_const_int(kernel.shape[2]) + get_const_int(kernel_before_dilation.shape[2]) - 1) \
+            // get_const_int(kernel_before_dilation.shape[2])
+        dilation_w = (get_const_int(kernel.shape[3]) + get_const_int(kernel_before_dilation.shape[3]) - 1) \
+            // get_const_int(kernel_before_dilation.shape[2])
     target = tvm.target.current_target()
     if "miopen" in target.libs:
         return miopen.conv2d_forward(data,
-                                     kernel,
+                                     kernel_cudnn,
                                      stride_h,
                                      stride_w,
                                      pad_h,
                                      pad_w,
-                                     1,  # dilation_h
-                                     1,  # dilation_w
+                                     dilation_h,
+                                     dilation_w,
                                      conv_mode=0)
-    return topi.nn.conv2d_nchw(data, kernel, stride, padding, out_dtype)
+    return topi.nn.conv2d_nchw(data, kernel_tvm, stride, padding, out_dtype)
 
 
 @generic.schedule_conv2d_nchw.register(["rocm"])
diff --git a/topi/python/topi/x86/conv2d.py b/topi/python/topi/x86/conv2d.py
index beee95b13d48..7e9632ed9622 100644
--- a/topi/python/topi/x86/conv2d.py
+++ b/topi/python/topi/x86/conv2d.py
@@ -91,6 +91,8 @@ def default_schedule(op):
         """NCHW conv2d schedule for non imagenet workloads"""
         conv = op.output(0)
         kernel = op.input_tensors[1]
+        if isinstance(kernel.op, tvm.tensor.ComputeOp) and "dilate" in kernel.op.tag:
+            s[kernel].compute_inline()
         data = op.input_tensors[0]
         data_pad = None
         if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
@@ -134,6 +136,8 @@ def traverse(op):
                     conv_out = op.input_tensors[0]
                     kernel_vec = conv_out.op.input_tensors[1]
                     kernel = kernel_vec.op.input_tensors[0]
+                    if isinstance(kernel.op, tvm.tensor.ComputeOp) and "dilate" in kernel.op.tag:
+                        s[kernel].compute_inline()
                     data_vec = conv_out.op.input_tensors[0]
                     data = data_vec.op.input_tensors[0]
                     data_pad = None
@@ -184,6 +188,9 @@ def traverse(op):
         if 'conv2d_nhwc' in op.tag:
             conv = op.output(0)
             kernel = op.input_tensors[1]
+            if isinstance(kernel.op, tvm.tensor.ComputeOp) and "dilate" in kernel.op.tag:
+                s[kernel].compute_inline()
+
             data = op.input_tensors[0]
             data_pad = None
             if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
diff --git a/topi/tests/python/test_topi_conv2d_hwcn.py b/topi/tests/python/test_topi_conv2d_hwcn.py
index 84962a0b4789..645e18702946 100644
--- a/topi/tests/python/test_topi_conv2d_hwcn.py
+++ b/topi/tests/python/test_topi_conv2d_hwcn.py
@@ -8,12 +8,13 @@
 from topi.util import get_const_tuple
 
 
-def verify_conv2d_hwcn(batch, in_channel, in_size, num_filter, kernel, stride, padding):
+def verify_conv2d_hwcn(batch, in_channel, in_size, num_filter, kernel, stride, padding, dilation=1):
     in_height = in_width = in_size
 
     A = tvm.placeholder((in_height, in_width, in_channel, batch), name='A')
     W = tvm.placeholder((kernel, kernel, in_channel, num_filter), name='W')
-    B = topi.nn.conv2d_hwcn(A, W, stride, padding)
+    dW = topi.nn.dilate(W, (dilation, dilation, 1, 1))
+    B = topi.nn.conv2d_hwcn(A, dW, stride, padding)
     C = topi.nn.relu(B)
     s1 = topi.cuda.schedule_conv2d_hwcn([B])
     s2 = topi.cuda.schedule_conv2d_hwcn([C])
@@ -26,7 +27,8 @@ def verify_conv2d_hwcn(batch, in_channel, in_size, num_filter, kernel, stride, p
     def get_ref_data():
         a_np = np.random.uniform(size=a_shape).astype(dtype)
         w_np = np.random.uniform(size=w_shape).astype(dtype)
-        b_np = topi.testing.conv2d_hwcn_python(a_np, w_np, stride, padding)
+        dw_np = topi.testing.dilate_python(w_np, (dilation, dilation, 1, 1))
+        b_np = topi.testing.conv2d_hwcn_python(a_np, dw_np, stride, padding)
         c_np = np.maximum(b_np, 0)
         return a_np, w_np, b_np, c_np
     a_np, w_np, b_np, c_np = get_ref_data()
@@ -63,7 +65,8 @@ def test_conv2d_hwcn():
     verify_conv2d_hwcn(1, 256, 32, 256, 3, 1, "VALID")
     verify_conv2d_hwcn(4, 128, 16, 128, 5, 2, "VALID")
     verify_conv2d_hwcn(4, 128, 16, 256, 5, 2, "VALID")
-
+    # dilation = 2
+    verify_conv2d_hwcn(1, 256, 32, 256, 3, 1, "SAME", dilation=2)
 
 if __name__ == "__main__":
     test_conv2d_hwcn()
diff --git a/topi/tests/python/test_topi_conv2d_nchw.py b/topi/tests/python/test_topi_conv2d_nchw.py
index e5a674709009..ed8fab524904 100644
--- a/topi/tests/python/test_topi_conv2d_nchw.py
+++ b/topi/tests/python/test_topi_conv2d_nchw.py
@@ -3,10 +3,11 @@
 import numpy as np
 import tvm
 import topi
+import topi.testing
 from tvm.contrib.pickle_memoize import memoize
 from topi.util import get_const_tuple
 
-def verify_conv2d_nchw(batch, in_channel, in_size, num_filter, kernel, stride, padding):
+def verify_conv2d_nchw(batch, in_channel, in_size, num_filter, kernel, stride, padding, dilation=1):
     in_height = in_width = in_size
 
     A = tvm.placeholder((batch, in_channel, in_height, in_width), name='A')
@@ -16,11 +17,12 @@ def verify_conv2d_nchw(batch, in_channel, in_size, num_filter, kernel, stride, p
     w_shape = get_const_tuple(W.shape)
     dtype = A.dtype
 
-    @memoize("topi.tests.test_topi_conv2d.verify_con2d_nchw")
+    @memoize("topi.tests.test_topi_conv2d_nchw.verify_conv2d_nchw")
     def get_ref_data():
         a_np = np.random.uniform(size=a_shape).astype(dtype)
         w_np = np.random.uniform(size=w_shape).astype(dtype)
-        b_np = topi.testing.conv2d_nchw_python(a_np, w_np, stride, padding)
+        dw_np = topi.testing.dilate_python(w_np, (1, 1, dilation, dilation))
+        b_np = topi.testing.conv2d_nchw_python(a_np, dw_np, stride, padding)
         c_np = np.maximum(b_np, 0)
         return a_np, w_np, b_np, c_np
 
@@ -33,7 +35,8 @@ def check_device(device):
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
-            B = topi.nn.conv2d(A, W, stride, padding, layout='NCHW')
+            dW = topi.nn.dilate(W, (1, 1, dilation, dilation))
+            B = topi.nn.conv2d(A, dW, stride, padding, layout='NCHW')
             C = topi.nn.relu(B)
             s1 = topi.generic.schedule_conv2d_nchw([B])
             s2 = topi.generic.schedule_conv2d_nchw([C])
@@ -43,8 +46,8 @@ def check_device(device):
         c = tvm.nd.array(np.zeros(get_const_tuple(C.shape), dtype=C.dtype), ctx)
         with tvm.build_config(auto_unroll_max_step=1400,
                               unroll_explicit=(device != "cuda")):
-            func1 = tvm.build(s1, [A, W, B], device, name="conv2d_%d_%d_%d_%d_%d_%d_%d" % (batch, in_channel, in_size, num_filter, kernel, stride, padding))
-            func2 = tvm.build(s2, [A, W, C], device, name="relu_%d_%d_%d_%d_%d_%d_%d" % (batch, in_channel, in_size, num_filter, kernel, stride, padding))
+            func1 = tvm.build(s1, [A, W, B], device, name="conv2d_%d_%d_%d_%d_%d_%d_%d_%d" % (batch, in_channel, in_size, num_filter, kernel, stride, padding, dilation))
+            func2 = tvm.build(s2, [A, W, C], device, name="relu_%d_%d_%d_%d_%d_%d_%d_%d" % (batch, in_channel, in_size, num_filter, kernel, stride, padding, dilation))
             func1(a, w, b)
             func2(a, w, c)
             np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
@@ -75,6 +78,8 @@ def test_conv2d_nchw():
     verify_conv2d_nchw(1, 64, 224, 64, 3, 1, 1)
     verify_conv2d_nchw(1, 64, 224, 32, 3, 1, 1)
     verify_conv2d_nchw(1, 32, 224, 9, 3, 1, 1)
+    # dilation = 2
+    verify_conv2d_nchw(1, 128, 122, 128, 3, 1, 1, dilation=2)
 
 if __name__ == "__main__":
     test_conv2d_nchw()
diff --git a/topi/tests/python/test_topi_conv2d_nhwc.py b/topi/tests/python/test_topi_conv2d_nhwc.py
index 40aa3e550a86..7e41517c5d61 100644
--- a/topi/tests/python/test_topi_conv2d_nhwc.py
+++ b/topi/tests/python/test_topi_conv2d_nhwc.py
@@ -8,12 +8,13 @@
 from topi.util import get_const_tuple
 
 
-def verify_conv2d_nhwc(batch, in_channel, in_size, num_filter, kernel, stride, padding):
+def verify_conv2d_nhwc(batch, in_channel, in_size, num_filter, kernel, stride, padding, dilation=1):
     in_height = in_width = in_size
 
     A = tvm.placeholder((batch, in_height, in_width, in_channel), name='A')
     W = tvm.placeholder((kernel, kernel, in_channel, num_filter), name='W')
-    B = topi.nn.conv2d_nhwc(A, W, stride, padding)
+    dW = topi.nn.dilate(W, (1, dilation, dilation, 1))
+    B = topi.nn.conv2d_nhwc(A, dW, stride, padding)
 
     a_shape = get_const_tuple(A.shape)
     w_shape = get_const_tuple(W.shape)
@@ -23,7 +24,8 @@ def verify_conv2d_nhwc(batch, in_channel, in_size, num_filter, kernel, stride, p
     def get_ref_data():
         a_np = np.random.uniform(size=a_shape).astype(dtype)
         w_np = np.random.uniform(size=w_shape).astype(dtype)
-        b_np = topi.testing.conv2d_nhwc_python(a_np, w_np, stride, padding)
+        dw_np = topi.testing.dilate_python(w_np, (1, dilation, dilation, 1))
+        b_np = topi.testing.conv2d_nhwc_python(a_np, dw_np, stride, padding)
         return a_np, w_np, b_np
     a_np, w_np, b_np = get_ref_data()
 
@@ -54,6 +56,8 @@ def test_conv2d_nhwc():
     verify_conv2d_nhwc(1, 256, 32, 256, 3, 1, "VALID")
     verify_conv2d_nhwc(4, 128, 16, 128, 5, 2, "VALID")
     verify_conv2d_nhwc(4, 128, 16, 256, 5, 2, "VALID")
+    # dilation = 2
+    verify_conv2d_nhwc(1, 256, 32, 256, 3, 1, "SAME", dilation=2)
 
 
 if __name__ == "__main__":
diff --git a/topi/tests/python/test_topi_depthwise_conv2d.py b/topi/tests/python/test_topi_depthwise_conv2d.py
index 5a12d7abb208..3e9794ac3ec9 100644
--- a/topi/tests/python/test_topi_depthwise_conv2d.py
+++ b/topi/tests/python/test_topi_depthwise_conv2d.py
@@ -8,21 +8,21 @@
 from topi.cuda.depthwise_conv2d import schedule_depthwise_conv2d_nhwc
 
 
-def depthwise_conv2d_with_workload_nchw(batch, in_channel, in_height, channel_multiplier, filter_height, stride, padding):
+def depthwise_conv2d_with_workload_nchw(batch, in_channel, in_height, channel_multiplier, filter_height, stride, padding, dilation=1):
     in_width = in_height
     filter_channel = in_channel
     filter_width = filter_height
     # placeholder
     Input = tvm.placeholder((batch, in_channel, in_height, in_width), name='Input')
     Filter = tvm.placeholder((filter_channel, channel_multiplier, filter_height, filter_width), name='Filter')
+    DilatedFilter = topi.nn.dilate(Filter, (1, 1, dilation, dilation), name='DilatedFilter')
     Scale = tvm.placeholder((in_channel * channel_multiplier,), name='Scale')
     Shift = tvm.placeholder((in_channel * channel_multiplier,), name='Shift')
     # declare
-    DepthwiseConv2d = topi.nn.depthwise_conv2d_nchw(Input, Filter, stride=stride, padding=padding)
+    DepthwiseConv2d = topi.nn.depthwise_conv2d_nchw(Input, DilatedFilter, stride=stride, padding=padding)
     ScaleShift = topi.nn.scale_shift_nchw(DepthwiseConv2d, Scale, Shift)
     Relu = topi.nn.relu(ScaleShift)
 
-
     def check_device(device):
         ctx = tvm.context(device, 0)
         if not ctx.exist:
@@ -52,11 +52,12 @@ def check_device(device):
         def get_ref_data():
             input_np = np.random.uniform(size=input_shape).astype(dtype)
             filter_np = np.random.uniform(size=filter_shape).astype(dtype)
+            dilated_filter_np = topi.testing.dilate_python(filter_np, (1, 1, dilation, dilation))
             scale_np = np.random.uniform(size=scale_shape).astype(dtype)
             shift_np = np.random.uniform(size=shift_shape).astype(dtype)
             # correctness with scipy
             depthwise_conv2d_scipy = topi.testing.depthwise_conv2d_python_nchw(
-                input_np, filter_np, stride=stride, padding=padding)
+                input_np, dilated_filter_np, stride=stride, padding=padding)
             scale_shift_scipy = np.zeros(shape=scale_shift_shape)
             for c in range(in_channel * channel_multiplier):
                 scale_shift_scipy[:,c,:,:] = depthwise_conv2d_scipy[:,c,:,:] * scale_np[c] + shift_np[c]
@@ -94,7 +95,7 @@ def get_ref_data():
     check_device("vulkan")
 
 
-def depthwise_conv2d_with_workload_nhwc(batch, in_channel, in_height, channel_multiplier, filter_height, stride_h, padding):
+def depthwise_conv2d_with_workload_nhwc(batch, in_channel, in_height, channel_multiplier, filter_height, stride_h, padding, dilation=1):
     in_width = in_height
     filter_channel = in_channel
     filter_width = filter_height
@@ -102,10 +103,11 @@ def depthwise_conv2d_with_workload_nhwc(batch, in_channel, in_height, channel_mu
     # placeholder
     Input = tvm.placeholder((batch, in_height, in_width, in_channel), name='Input')
     Filter = tvm.placeholder((filter_height, filter_width,filter_channel, channel_multiplier), name='Filter')
+    DilatedFilter = topi.nn.dilate(Filter, (1, 1, dilation, dilation), name='DilatedFilter')
     Scale = tvm.placeholder((in_channel * channel_multiplier,), name='Scale')
     Shift = tvm.placeholder((in_channel * channel_multiplier,), name='Shift')
     # declare
-    DepthwiseConv2d = topi.nn.depthwise_conv2d_nhwc(Input, Filter, stride=[stride_h, stride_w], padding=padding)
+    DepthwiseConv2d = topi.nn.depthwise_conv2d_nhwc(Input, DilatedFilter, stride=[stride_h, stride_w], padding=padding)
     ScaleShift = topi.nn.scale_shift_nhwc(DepthwiseConv2d, Scale, Shift)
     Relu = topi.nn.relu(ScaleShift)
     # schedule
@@ -139,11 +141,12 @@ def check_device(device):
         def get_ref_data():
             input_np = np.random.uniform(size=input_shape).astype(dtype)
             filter_np = np.random.uniform(size=filter_shape).astype(dtype)
+            dilated_filter_np = topi.testing.dilate_python(filter_np, (1, 1, dilation, dilation))
             scale_np = np.random.uniform(size=scale_shape).astype(dtype)
             shift_np = np.random.uniform(size=shift_shape).astype(dtype)
             # correctness with scipy
             depthwise_conv2d_scipy = topi.testing.depthwise_conv2d_python_nhwc(
-                input_np, filter_np, stride=[stride_h, stride_w], padding=padding)
+                input_np, dilated_filter_np, stride=[stride_h, stride_w], padding=padding)
             scale_shift_scipy = np.zeros(shape=scale_shift_shape)
             for c in range(in_channel * channel_multiplier):
                 scale_shift_scipy[:,:,:,c] = depthwise_conv2d_scipy[:,:,:,c] * scale_np[c] + shift_np[c]
@@ -192,6 +195,8 @@ def test_depthwise_conv2d():
     depthwise_conv2d_with_workload_nchw(1, 728, 32, 1, 3, 1, "VALID")
     depthwise_conv2d_with_workload_nchw(4, 256, 64, 2, 5, 2, "VALID")
     depthwise_conv2d_with_workload_nchw(4, 256, 32, 2, 5, 2, "VALID")
+    # dilation = 2
+    depthwise_conv2d_with_workload_nchw(1, 728, 64, 1, 3, 1, "SAME", dilation=2)
     print("testing nhwc")
     depthwise_conv2d_with_workload_nhwc(1, 728, 64, 1, 3, 1, "SAME")
     depthwise_conv2d_with_workload_nhwc(1, 728, 32, 1, 3, 1, "SAME")
@@ -201,6 +206,8 @@ def test_depthwise_conv2d():
     depthwise_conv2d_with_workload_nhwc(1, 728, 32, 1, 3, 1, "VALID")
     depthwise_conv2d_with_workload_nhwc(4, 256, 64, 2, 5, 2, "VALID")
     depthwise_conv2d_with_workload_nhwc(4, 256, 32, 2, 5, 2, "VALID")
+    # dilation = 2
+    depthwise_conv2d_with_workload_nhwc(1, 728, 64, 1, 3, 1, "SAME", dilation=2)
 
 if __name__ == "__main__":
     test_depthwise_conv2d()

From 977ec97a34717c2a3c247d7725405fe38203a223 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 25 Apr 2018 10:41:34 -0700
Subject: [PATCH 286/949] Update README.md

---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 74213e8041cd..93dbb9ac3534 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-<img src=https://raw.githubusercontent.com/tqchen/tvmlang.org/master/images/logo/tvm-logo-small.png width=128/> Tensor IR Stack for Deep Learning Systems
+<img src=https://raw.githubusercontent.com/tqchen/tvmlang.org/master/images/logo/tvm-logo-small.png width=128/> Open Deep Learning Compiler Stack
 ==============================================
 
 [![GitHub license](http://dmlc.github.io/img/apache2.svg)](./LICENSE)
@@ -13,7 +13,7 @@
 [Community](http://tvmlang.org/community.html) |
 [Release Notes](NEWS.md)
 
-TVM is a Tensor intermediate representation(IR) stack for deep learning systems. It is designed to close the gap between the
+TVM is a compiler stack for deep learning systems. It is designed to close the gap between the
 productivity-focused deep learning frameworks, and the performance- and efficiency-focused hardware backends.
 TVM works with deep learning frameworks to provide end to end compilation to different backends.
 Checkout the [tvm stack homepage](http://tvmlang.org/)  for more information.

From 9175b47c16691824b1a54aa26dc1ddb21c981612 Mon Sep 17 00:00:00 2001
From: Yizhi Liu <liuyizhi@apache.org>
Date: Wed, 25 Apr 2018 11:16:06 -0700
Subject: [PATCH 287/949] fix x86 pooling topi schedule (#1132)

---
 topi/python/topi/x86/pooling.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/topi/python/topi/x86/pooling.py b/topi/python/topi/x86/pooling.py
index 0dedd3801587..946ecd2c7697 100644
--- a/topi/python/topi/x86/pooling.py
+++ b/topi/python/topi/x86/pooling.py
@@ -9,8 +9,7 @@ def _parallel_sch(sch):
         fused = sch.fuse(sch.op.axis[0], sch.op.axis[1], sch.op.axis[2])
         sch.parallel(fused)
     elif len(sch.op.axis) >= 3:
-        n, c, _, _ = sch.op.axis
-        fused = sch.fuse(n, c) # for nhwc layout, fuse n and h
+        fused = sch.fuse(sch.op.axis[0], sch.op.axis[1])
         sch.parallel(fused)
     else:
         sch.parallel(sch.op.axis[0])

From 2c49fa4b9636ee3291a302002a1889b75fbcc81b Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Fri, 27 Apr 2018 12:19:03 +0800
Subject: [PATCH 288/949] fix default out type of depthwise conv2d (#1134)

---
 topi/python/topi/nn/depthwise_conv2d.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/topi/python/topi/nn/depthwise_conv2d.py b/topi/python/topi/nn/depthwise_conv2d.py
index 38e7c7bb9dde..8d348d461f82 100644
--- a/topi/python/topi/nn/depthwise_conv2d.py
+++ b/topi/python/topi/nn/depthwise_conv2d.py
@@ -10,7 +10,7 @@
 
 
 @tvm.target.generic_func
-def depthwise_conv2d_nchw(Input, Filter, stride, padding, out_dtype='float32'):
+def depthwise_conv2d_nchw(Input, Filter, stride, padding, out_dtype=None):
     """Depthwise convolution nchw forward operator.
 
     Parameters

From 65705124ab4799e8114ec9508503a0a9f019545c Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 26 Apr 2018 22:09:45 -0700
Subject: [PATCH 289/949] [DOCS] Integration guideline (#1135)

---
 docs/how_to/deploy.md         |  5 ++++
 docs/how_to/deploy_android.md |  3 ---
 docs/how_to/integrate.md      | 51 +++++++++++++++++++++++++++++++++++
 docs/index.rst                |  1 +
 4 files changed, 57 insertions(+), 3 deletions(-)
 create mode 100644 docs/how_to/integrate.md

diff --git a/docs/how_to/deploy.md b/docs/how_to/deploy.md
index b9f219acc335..dd5614b4ef95 100644
--- a/docs/how_to/deploy.md
+++ b/docs/how_to/deploy.md
@@ -34,3 +34,8 @@ on how to generate the library and [cpp_deploy.cc](https://github.com/dmlc/tvm/t
 - Bundle the compiled library into your project in system module mode.
 
 Dynamic loading is more flexible and can load new modules on the fly. System module is a more ```static``` approach.  We can use system module in places where dynamic library loading is banned.
+
+
+How to Deploy on Android
+------------------------
+- [How to deploy on android](deploy_android.md)
\ No newline at end of file
diff --git a/docs/how_to/deploy_android.md b/docs/how_to/deploy_android.md
index 7a38f24a7e0d..59f6605c6e69 100644
--- a/docs/how_to/deploy_android.md
+++ b/docs/how_to/deploy_android.md
@@ -1,6 +1,5 @@
 # How to deploy and use compiled model on Android
 
-
 This tutorial explain below aspects (Unlike the android_rpc approach we already have)
 
   * Build a model for android target
@@ -36,5 +35,3 @@ Refer [here](https://github.com/dmlc/tvm/blob/master/apps/android_deploy/README.
 ### Android Native API Reference
 
 From android java TVM API to load model & execute can be refered at this [java](https://github.com/dmlc/tvm/blob/master/apps/android_deploy/app/src/main/java/ml/dmlc/tvm/android/demo/MainActivity.java) sample source.
-
-
diff --git a/docs/how_to/integrate.md b/docs/how_to/integrate.md
new file mode 100644
index 000000000000..9819d4add40e
--- /dev/null
+++ b/docs/how_to/integrate.md
@@ -0,0 +1,51 @@
+Integrate TVM into Your Project
+===============================
+
+TVM's runtime is designed to be lightweight and portable.
+There are several ways you can integrate TVM into your project.
+If you are looking for minimum deployment of a compiled module, take a look at [deployment guide](deploy.md)
+
+This article introduces possible ways to integrate TVM
+as a JIT compiler to generate functions on your system.
+
+
+## DLPack Support
+
+TVM's generated function follows the PackedFunc convention.
+It is a function that can take positional arguments including
+standard types such as float, integer, string.
+The PackedFunc takes DLTensor pointer in [dlpack](https://github.com/dmlc/dlpack) convention.
+So the only thing you need to solve is to create a corresponding DLTensor object.
+
+
+
+## Integrate User Defined C++ Array
+
+The only thing we have to do in C++ is to convert your array to DLTensor and pass in its address as
+```DLTensor*``` to the generated function.
+
+
+## Integrate User Defined Python Array
+
+Assume you have a python object ```MyArray```. There are three things that you need to do
+
+- Add ```_tvm_tcode``` field to your array which returns ```tvm.TypeCode.ARRAY_HANDLE```
+- Support ```_tvm_handle``` property in your object, which returns the address of DLTensor in python integer
+- Register this class by ```tvm.register_extension```
+
+```python
+# Example code
+import tvm
+
+class MyArray(object):
+    _tvm_tcode = tvm.TypeCode.ARRAY_HANDLE
+
+    @property
+    def _tvm_handle(self):
+        dltensor_addr = self.get_dltensor_addr()
+        return dltensor_addr
+
+# You can put registration step in a separate file mypkg.tvm.py
+# and only optionally import that if you only want optional dependency.
+tvm.register_extension(MyArray)
+```
diff --git a/docs/index.rst b/docs/index.rst
index 9fa690e00fd9..3a5c8d7715c9 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -15,6 +15,7 @@ Contents
    tutorials/index
    faq
    how_to/deploy
+   how_to/integrate
    how_to/contribute
    api/python/index
    dev/index

From 27230692c14cfee63db69f9aab42a1fbb1910ef6 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 27 Apr 2018 17:59:11 -0700
Subject: [PATCH 290/949] [DOCS] API doc update (#1136)

---
 docs/api/python/bridge.rst         |  7 +++
 docs/api/python/contrib.rst        | 89 +++++++++++++++++++++++++-----
 docs/api/python/graph_runtime.rst  |  4 ++
 docs/api/python/index.rst          |  3 +
 docs/api/python/rpc.rst            | 22 ++++++++
 docs/api/python/target.rst         |  9 ---
 docs/api/python/topi.rst           | 38 +++++++++++++
 docs/api/python/tvm.rst            |  2 +
 docs/conf.py                       |  1 +
 docs/how_to/deploy.md              | 26 ++++++++-
 docs/how_to/deploy_android.md      | 37 -------------
 python/tvm/contrib/rpc/__init__.py |  2 +-
 python/tvm/target.py               | 31 ++++++-----
 topi/python/topi/tensor.py         |  3 +
 topi/python/topi/transform.py      |  2 +-
 15 files changed, 196 insertions(+), 80 deletions(-)
 create mode 100644 docs/api/python/bridge.rst
 create mode 100644 docs/api/python/graph_runtime.rst
 create mode 100644 docs/api/python/rpc.rst
 delete mode 100644 docs/how_to/deploy_android.md

diff --git a/docs/api/python/bridge.rst b/docs/api/python/bridge.rst
new file mode 100644
index 000000000000..afc7dc298652
--- /dev/null
+++ b/docs/api/python/bridge.rst
@@ -0,0 +1,7 @@
+Framework Bridge APIs
+---------------------
+
+tvm.contrib.mxnet
+~~~~~~~~~~~~~~~~~
+.. automodule:: tvm.contrib.mxnet
+    :members:
diff --git a/docs/api/python/contrib.rst b/docs/api/python/contrib.rst
index ed04230deb8d..de0946e29538 100644
--- a/docs/api/python/contrib.rst
+++ b/docs/api/python/contrib.rst
@@ -1,38 +1,97 @@
-Contrib APIs
-------------
+Additional Contrib APIs
+-----------------------
 .. automodule:: tvm.contrib
 
-tvm.contrib.nvcc
-~~~~~~~~~~~~~~~~
-.. automodule:: tvm.contrib.nvcc
+tvm.contrib.cblas
+~~~~~~~~~~~~~~~~~
+.. automodule:: tvm.contrib.cc
     :members:
 
+
 tvm.contrib.cc
 ~~~~~~~~~~~~~~
 .. automodule:: tvm.contrib.cc
     :members:
 
-tvm.contrib.xcode
-~~~~~~~~~~~~~~~~~
-.. automodule:: tvm.contrib.xcode
+
+tvm.contrib.cublas
+~~~~~~~~~~~~~~~~~~
+.. automodule:: tvm.contrib.cublas
     :members:
 
-tvm.contrib.rpc
+
+tvm.contrib.emscripten
+~~~~~~~~~~~~~~~~~~~~~~
+.. automodule:: tvm.contrib.emscripten
+    :members:
+
+tvm.contrib.miopen
+~~~~~~~~~~~~~~~~~~
+.. automodule:: tvm.contrib.miopen
+    :members:
+
+tvm.contrib.ndk
 ~~~~~~~~~~~~~~~
-.. automodule:: tvm.contrib.rpc
+.. automodule:: tvm.contrib.ndk
+    :members:
+
+
+tvm.contrib.nnpack
+~~~~~~~~~~~~~~~~~~
+.. automodule:: tvm.contrib.nnpack
+    :members:
+
+
+tvm.contrib.nvcc
+~~~~~~~~~~~~~~~~
+.. automodule:: tvm.contrib.nvcc
     :members:
 
-tvm.contrib.graph_runtime
-~~~~~~~~~~~~~~~~~~~~~~~~~
-.. automodule:: tvm.contrib.graph_runtime
+
+tvm.contrib.pickle_memoize
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. automodule:: tvm.contrib.pickle_memoize
+    :members:
+
+
+tvm.contrib.random
+~~~~~~~~~~~~~~~~~~
+.. automodule:: tvm.contrib.random
+    :members:
+
+
+tvm.contrib.rocblas
+~~~~~~~~~~~~~~~~~~~
+.. automodule:: tvm.contrib.rocblas
     :members:
 
+
+tvm.contrib.rocm
+~~~~~~~~~~~~~~~~
+.. automodule:: tvm.contrib.rocm
+    :members:
+
+
+tvm.contrib.spirv
+~~~~~~~~~~~~~~~~~
+.. automodule:: tvm.contrib.spirv
+    :members:
+
+
+tvm.contrib.tar
+~~~~~~~~~~~~~~~~
+.. automodule:: tvm.contrib.tar
+    :members:
+
+
 tvm.contrib.util
 ~~~~~~~~~~~~~~~~
 .. automodule:: tvm.contrib.util
     :members:
 
-tvm.contrib.cblas
+
+
+tvm.contrib.xcode
 ~~~~~~~~~~~~~~~~~
-.. automodule:: tvm.contrib.cblas
+.. automodule:: tvm.contrib.xcode
     :members:
diff --git a/docs/api/python/graph_runtime.rst b/docs/api/python/graph_runtime.rst
new file mode 100644
index 000000000000..89a223323e14
--- /dev/null
+++ b/docs/api/python/graph_runtime.rst
@@ -0,0 +1,4 @@
+tvm.contrib.graph_runtime
+-------------------------
+.. automodule:: tvm.contrib.graph_runtime
+    :members:
diff --git a/docs/api/python/index.rst b/docs/api/python/index.rst
index ee830e031462..7b9afaca32f5 100644
--- a/docs/api/python/index.rst
+++ b/docs/api/python/index.rst
@@ -14,6 +14,9 @@ Python API
    ndarray
    container
    function
+   graph_runtime
+   rpc
+   bridge
    contrib
    dev
    topi
diff --git a/docs/api/python/rpc.rst b/docs/api/python/rpc.rst
new file mode 100644
index 000000000000..4d46bda1ab24
--- /dev/null
+++ b/docs/api/python/rpc.rst
@@ -0,0 +1,22 @@
+tvm.contrib.rpc
+---------------
+.. automodule:: tvm.contrib.rpc
+
+.. autofunction:: tvm.contrib.rpc.connect
+.. autofunction:: tvm.contrib.rpc.connect_tracker
+
+.. autoclass:: tvm.contrib.rpc.TrackerSession
+    :members:
+    :inherited-members:
+
+.. autoclass:: tvm.contrib.rpc.RPCSession
+    :members:
+    :inherited-members:
+
+.. autoclass:: tvm.contrib.rpc.LocalSession
+    :members:
+    :inherited-members:
+
+.. autoclass:: tvm.contrib.rpc.Server
+    :members:
+    :inherited-members:
diff --git a/docs/api/python/target.rst b/docs/api/python/target.rst
index 0f824324d0c5..e5723349b5c0 100644
--- a/docs/api/python/target.rst
+++ b/docs/api/python/target.rst
@@ -1,13 +1,4 @@
 tvm.target
 ----------
 .. automodule:: tvm.target
-
-.. autofunction:: tvm.target.generic_func
-
-.. autoclass:: tvm.target.Target
     :members:
-
-.. autofunction:: tvm.target.cuda
-.. autofunction:: tvm.target.rocm
-.. autofunction:: tvm.target.rasp
-.. autofunction:: tvm.target.create
diff --git a/docs/api/python/topi.rst b/docs/api/python/topi.rst
index 4f7d8cace31b..ffdb076a2260 100644
--- a/docs/api/python/topi.rst
+++ b/docs/api/python/topi.rst
@@ -9,16 +9,33 @@ Index
 
 .. autosummary::
 
+   topi.identity
+   topi.negative
    topi.exp
    topi.tanh
    topi.log
    topi.sqrt
    topi.sigmoid
+   topi.clip
+   topi.cast
    topi.transpose
    topi.expand_dims
+   topi.reshape
+   topi.squeeze
+   topi.concatenate
+   topi.split
+   topi.full
+   topi.full_like
+   topi.greater
+   topi.less
    topi.nn.relu
    topi.nn.leaky_relu
    topi.nn.dilate
+   topi.nn.pool
+   topi.nn.global_pool
+   topi.nn.upsampling
+   topi.nn.softmax
+   topi.nn.log_softmax
    topi.nn.conv2d_nchw
    topi.nn.conv2d_hwcn
    topi.nn.depthwise_conv2d_nchw
@@ -31,6 +48,8 @@ Index
    topi.broadcast_sub
    topi.broadcast_mul
    topi.broadcast_div
+   topi.broadcast_maximum
+   topi.broadcast_minimum
 
 
 **List of schedules**
@@ -45,13 +64,25 @@ Index
 
 topi
 ~~~~
+.. autofunction:: topi.negative
+.. autofunction:: topi.identity
 .. autofunction:: topi.exp
 .. autofunction:: topi.tanh
 .. autofunction:: topi.log
 .. autofunction:: topi.sqrt
 .. autofunction:: topi.sigmoid
+.. autofunction:: topi.clip
+.. autofunction:: topi.cast
 .. autofunction:: topi.transpose
 .. autofunction:: topi.expand_dims
+.. autofunction:: topi.reshape
+.. autofunction:: topi.squeeze
+.. autofunction:: topi.concatenate
+.. autofunction:: topi.split
+.. autofunction:: topi.full
+.. autofunction:: topi.full_like
+.. autofunction:: topi.greater
+.. autofunction:: topi.less
 .. autofunction:: topi.max
 .. autofunction:: topi.sum
 .. autofunction:: topi.min
@@ -60,6 +91,8 @@ topi
 .. autofunction:: topi.broadcast_sub
 .. autofunction:: topi.broadcast_mul
 .. autofunction:: topi.broadcast_div
+.. autofunction:: topi.broadcast_maximum
+.. autofunction:: topi.broadcast_minimum
 
 
 topi.nn
@@ -67,6 +100,11 @@ topi.nn
 .. autofunction:: topi.nn.relu
 .. autofunction:: topi.nn.leaky_relu
 .. autofunction:: topi.nn.dilate
+.. autofunction:: topi.nn.pool
+.. autofunction:: topi.nn.global_pool
+.. autofunction:: topi.nn.upsampling
+.. autofunction:: topi.nn.softmax
+.. autofunction:: topi.nn.log_softmax
 .. autofunction:: topi.nn.conv2d_nchw
 .. autofunction:: topi.nn.conv2d_hwcn
 .. autofunction:: topi.nn.depthwise_conv2d_nchw
diff --git a/docs/api/python/tvm.rst b/docs/api/python/tvm.rst
index 8700da38273b..6522df3ae9d3 100644
--- a/docs/api/python/tvm.rst
+++ b/docs/api/python/tvm.rst
@@ -15,6 +15,7 @@ The user facing API for computation declaration.
    tvm.extern
    tvm.decl_buffer
    tvm.reduce_axis
+   tvm.select
    tvm.thread_axis
    tvm.comm_reducer
    tvm.sum
@@ -33,6 +34,7 @@ The user facing API for computation declaration.
 .. autofunction:: tvm.extern
 .. autofunction:: tvm.decl_buffer
 .. autofunction:: tvm.reduce_axis
+.. autofunction:: tvm.select
 .. autofunction:: tvm.thread_axis
 .. autofunction:: tvm.comm_reducer
 .. autofunction:: tvm.sum
diff --git a/docs/conf.py b/docs/conf.py
index 96ed7b239c7c..77a04bd64c77 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -189,6 +189,7 @@ def run_doxygen(folder):
      '../tutorials/optimize',
      '../tutorials/deployment'])
 
+
 def generate_doxygen_xml(app):
     """Run the doxygen make commands if we're on the ReadTheDocs server"""
     run_doxygen('..')
diff --git a/docs/how_to/deploy.md b/docs/how_to/deploy.md
index dd5614b4ef95..841b9d54489d 100644
--- a/docs/how_to/deploy.md
+++ b/docs/how_to/deploy.md
@@ -35,7 +35,27 @@ on how to generate the library and [cpp_deploy.cc](https://github.com/dmlc/tvm/t
 
 Dynamic loading is more flexible and can load new modules on the fly. System module is a more ```static``` approach.  We can use system module in places where dynamic library loading is banned.
 
+Deploy to Android
+-----------------
+### Build model for Android Target
 
-How to Deploy on Android
-------------------------
-- [How to deploy on android](deploy_android.md)
\ No newline at end of file
+NNVM compilation of model for android target could follow same approach like android_rpc.
+
+An reference exampe can be found at [chainer-nnvm-example](https://github.com/tkat0/chainer-nnvm-example)
+
+Above example will directly run the compiled model on RPC target. Below modification at [rum_mobile.py](https://github.com/tkat0/chainer-nnvm-example/blob/5b97fd4d41aa4dde4b0aceb0be311054fb5de451/run_mobile.py#L64) will save the compilation output which is required on android target.
+
+```
+lib.export_library("deploy_lib.so", ndk.create_shared)
+with open("deploy_graph.json", "w") as fo:
+    fo.write(graph.json())
+with open("deploy_param.params", "wb") as fo:
+    fo.write(nnvm.compiler.save_param_dict(params))
+```
+
+deploy_lib.so, deploy_graph.json, deploy_param.params will go to android target.
+
+### TVM Runtime for Android Target
+
+Refer [here](https://github.com/dmlc/tvm/blob/master/apps/android_deploy/README.md#build-and-installation) to build CPU/OpenCL version flavor TVM runtime for android target.
+From android java TVM API to load model & execute can be refered at this [java](https://github.com/dmlc/tvm/blob/master/apps/android_deploy/app/src/main/java/ml/dmlc/tvm/android/demo/MainActivity.java) sample source.
diff --git a/docs/how_to/deploy_android.md b/docs/how_to/deploy_android.md
deleted file mode 100644
index 59f6605c6e69..000000000000
--- a/docs/how_to/deploy_android.md
+++ /dev/null
@@ -1,37 +0,0 @@
-# How to deploy and use compiled model on Android
-
-This tutorial explain below aspects (Unlike the android_rpc approach we already have)
-
-  * Build a model for android target
-  * TVM run on Android using Java API
-
-As an example here is a reference block diagram.
-
-![](http://www.tvmlang.org/images/release/tvm_flexible.png)
-
-## Build model for Android Target
-
-NNVM compilation of model for android target could follow same approach like android_rpc.
-
-An reference exampe can be found at [chainer-nnvm-example](https://github.com/tkat0/chainer-nnvm-example)
-
-Above example will directly run the compiled model on RPC target. Below modification at [rum_mobile.py](https://github.com/tkat0/chainer-nnvm-example/blob/5b97fd4d41aa4dde4b0aceb0be311054fb5de451/run_mobile.py#L64) will save the compilation output which is required on android target.
-
-```
-lib.export_library("deploy_lib.so", ndk.create_shared)
-with open("deploy_graph.json", "w") as fo:
-    fo.write(graph.json())
-with open("deploy_param.params", "wb") as fo:
-    fo.write(nnvm.compiler.save_param_dict(params))
-```
-
-deploy_lib.so, deploy_graph.json, deploy_param.params will go to android target.
-
-## TVM run on Android using Java API
-### TVM Runtime for android Target
-
-Refer [here](https://github.com/dmlc/tvm/blob/master/apps/android_deploy/README.md#build-and-installation) to build CPU/OpenCL version flavor TVM runtime for android target.
-
-### Android Native API Reference
-
-From android java TVM API to load model & execute can be refered at this [java](https://github.com/dmlc/tvm/blob/master/apps/android_deploy/app/src/main/java/ml/dmlc/tvm/android/demo/MainActivity.java) sample source.
diff --git a/python/tvm/contrib/rpc/__init__.py b/python/tvm/contrib/rpc/__init__.py
index c3fbf5be2928..6a356e2d64ff 100644
--- a/python/tvm/contrib/rpc/__init__.py
+++ b/python/tvm/contrib/rpc/__init__.py
@@ -10,4 +10,4 @@
 """
 
 from .server import Server
-from .client import RPCSession, LocalSession, connect, connect_tracker
+from .client import RPCSession, LocalSession, TrackerSession, connect, connect_tracker
diff --git a/python/tvm/target.py b/python/tvm/target.py
index 1dabc862a0ee..d62a7be7aac8 100644
--- a/python/tvm/target.py
+++ b/python/tvm/target.py
@@ -155,6 +155,7 @@ def register(self, func, key_list, allow_override=False):
         key_list = [key_list] if isinstance(key_list, str) else key_list
         _api_internal._GenericFuncRegisterFunc(self, func, key_list, allow_override)
 
+
 def get_native_generic_func(name):
     """Get a generic function from the global registry. If no
     function is registered under the given name, a new generic
@@ -172,6 +173,7 @@ def get_native_generic_func(name):
     """
     return _api_internal._GenericFuncGetGlobal(name)
 
+
 def override_native_generic_func(func_name):
     """Override a generic function defined in C++
 
@@ -193,20 +195,20 @@ def override_native_generic_func(func_name):
     -------
     .. code-block:: python
 
-    import tvm
-    # wrap function as target generic
-    @tvm.target.override_native_generic_func("my_func")
-    def my_func(a):
-        return a + 1
-    # register specialization of my_func under target cuda
-    @my_func.register("cuda")
-    def my_func_cuda(a):
-        return a + 2
-    # displays 3, because my_func is called
-    print(my_func(2))
-    # displays 4, because my_func_cuda is called
-    with tvm.target.cuda():
-        print(my_func(2))
+      import tvm
+      # wrap function as target generic
+      @tvm.target.override_native_generic_func("my_func")
+      def my_func(a):
+          return a + 1
+      # register specialization of my_func under target cuda
+      @my_func.register("cuda")
+      def my_func_cuda(a):
+          return a + 2
+      # displays 3, because my_func is called
+      print(my_func(2))
+      # displays 4, because my_func_cuda is called
+      with tvm.target.cuda():
+          print(my_func(2))
     """
     generic_func_node = get_native_generic_func(func_name)
 
@@ -346,6 +348,7 @@ def dispatch_func(func, *args, **kwargs):
     fdecorate.register = register
     return fdecorate
 
+
 def cuda(options=None):
     """Returns a cuda target.
 
diff --git a/topi/python/topi/tensor.py b/topi/python/topi/tensor.py
index 3e2e0abb46a3..74adff2cd9c0 100644
--- a/topi/python/topi/tensor.py
+++ b/topi/python/topi/tensor.py
@@ -27,6 +27,7 @@ def _compute(*i):
 
     return tvm.compute(xs[0].shape, _compute)
 
+
 @tvm.tag_scope(tag=tag.ELEMWISE)
 def full(shape, dtype, fill_value):
     """Fill tensor with fill_value
@@ -47,6 +48,7 @@ def full(shape, dtype, fill_value):
     """
     return tvm.compute(shape, lambda *i: tvm.const(fill_value, dtype))
 
+
 @tvm.tag_scope(tag=tag.ELEMWISE)
 def full_like(x, fill_value):
     """Construct a tensor with same shape as input tensor,
@@ -67,6 +69,7 @@ def full_like(x, fill_value):
     dtype = x.dtype
     return tvm.compute(x.shape, lambda *i: tvm.const(fill_value, dtype))
 
+
 @tvm.tag_scope(tag=tag.ELEMWISE)
 def greater(lhs, rhs, out_type=tvm.int8):
     """Compare two input tensors element-wise and return an mask tensor
diff --git a/topi/python/topi/transform.py b/topi/python/topi/transform.py
index 46998ace2099..33ed6c89c357 100644
--- a/topi/python/topi/transform.py
+++ b/topi/python/topi/transform.py
@@ -254,7 +254,7 @@ def _compute(begin, *indices):
             "Should be sorted, recieved %s" % str(indices_or_sections)
         begin_ids = [0] + list(indices_or_sections)
     else:
-        raise NotImplementedError
+        raise NotImplementedError()
     out_shapes = []
     for i in range(len(begin_ids)):
         if i == len(begin_ids) - 1:

From 5b21afb1aef2b5b07c55a0edbe641c0c9b085c9e Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Tue, 1 May 2018 23:20:24 +0800
Subject: [PATCH 291/949] register conv2d_transpose as generic function (#1137)

---
 topi/python/topi/nn/conv2d_transpose.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/topi/python/topi/nn/conv2d_transpose.py b/topi/python/topi/nn/conv2d_transpose.py
index c783360b8808..270409ad099b 100644
--- a/topi/python/topi/nn/conv2d_transpose.py
+++ b/topi/python/topi/nn/conv2d_transpose.py
@@ -9,6 +9,7 @@
 from ..util import simplify
 
 
+@tvm.target.generic_func
 def conv2d_transpose_nchw(Input, Filter, strides, padding):
     """Transposed 2D convolution nchw forward operator.
 

From b0da1ac22cd97a845aa905ecc103c3477a71261c Mon Sep 17 00:00:00 2001
From: Leyuan Wang <laurawly@gmail.com>
Date: Thu, 3 May 2018 08:41:14 -0700
Subject: [PATCH 292/949] [Metal] Correct handle warning info (#1140)

---
 src/runtime/metal/metal_module.mm | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/src/runtime/metal/metal_module.mm b/src/runtime/metal/metal_module.mm
index 4d9ffec84e45..ed7cba6e75a5 100644
--- a/src/runtime/metal/metal_module.mm
+++ b/src/runtime/metal/metal_module.mm
@@ -83,21 +83,24 @@ void SaveToBinary(dmlc::Stream* stream) final {
     if (e.lib == nil) {
       if (fmt_ == "metal") {
         MTLCompileOptions *opts = [MTLCompileOptions alloc];
-        // Use the default setting for now.
-        // by default most recent version is used.
-        // opts.languageVersion = MTLLanguageVersion2_0;
-        // opts.fastMathEnabled = YES;
-        opts = nil;
+        // Use the Metal 1.2 for now.
+        opts.languageVersion = MTLLanguageVersion1_2;
+        opts.fastMathEnabled = YES;
+        // opts = nil;
         e.lib = [
             w->devices[device_id]
              newLibraryWithSource:[NSString stringWithUTF8String:data_.c_str()]
              options:opts
              error:&err_msg];
         [opts dealloc];
-        if (err_msg != nil || e.lib == nil) {
+        if (e.lib == nil) {
           LOG(FATAL) << "Fail to compile metal lib:"
                      << [[err_msg localizedDescription] UTF8String];
         }
+        if (err_msg != nil) {
+          LOG(INFO) << "Warning: "
+                    << [[err_msg localizedDescription] UTF8String];
+        }
       } else {
         // Build from library.
         auto q = dispatch_queue_create("q", DISPATCH_QUEUE_SERIAL);

From b4f0afb8a93a20f4076fb26a386ac8acf66356ec Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Tue, 8 May 2018 20:57:28 +0530
Subject: [PATCH 293/949] CPP support for region and reorg operators (#1115)

---
 topi/include/topi/cuda/vision.h           | 95 +++++++++++++++++++++++
 topi/include/topi/rocm/vision.h           | 33 ++++++++
 topi/include/topi/vision/reorg.h          | 63 +++++++++++++++
 topi/include/topi/vision/yolo2/region.h   | 81 +++++++++++++++++++
 topi/python/topi/cpp.py                   |  4 +
 topi/python/topi/cuda/__init__.py         |  1 +
 topi/python/topi/cuda/vision.py           | 67 ++++++----------
 topi/python/topi/generic/vision.py        |  9 ++-
 topi/python/topi/rocm/vision.py           |  7 +-
 topi/python/topi/vision/reorg.py          | 16 +---
 topi/python/topi/vision/yolo2/region.py   | 28 +------
 topi/src/topi.cc                          | 21 +++++
 topi/tests/python/test_topi_region.py     |  7 +-
 topi/tests/python/test_topi_reorg.py      |  7 +-
 topi/tests/python_cpp/test_topi_region.py | 52 +++++++++++++
 topi/tests/python_cpp/test_topi_reorg.py  | 52 +++++++++++++
 16 files changed, 451 insertions(+), 92 deletions(-)
 create mode 100644 topi/include/topi/cuda/vision.h
 create mode 100644 topi/include/topi/rocm/vision.h
 create mode 100644 topi/include/topi/vision/reorg.h
 create mode 100644 topi/include/topi/vision/yolo2/region.h
 create mode 100644 topi/tests/python_cpp/test_topi_region.py
 create mode 100644 topi/tests/python_cpp/test_topi_reorg.py

diff --git a/topi/include/topi/cuda/vision.h b/topi/include/topi/cuda/vision.h
new file mode 100644
index 000000000000..4dd8b7cee15d
--- /dev/null
+++ b/topi/include/topi/cuda/vision.h
@@ -0,0 +1,95 @@
+/*!
+*  Copyright (c) 2018 by Contributors
+* \file cuda/vision.h
+* \brief CUDA schedule for vision operations
+*/
+#ifndef TOPI_CUDA_VISION_H_
+#define TOPI_CUDA_VISION_H_
+
+#include "tvm/tvm.h"
+#include "tvm/build_module.h"
+#include "topi/tags.h"
+#include "topi/detail/array_utils.h"
+#include "topi/contrib/cublas.h"
+#include "topi/generic/extern.h"
+
+namespace topi {
+using namespace tvm;
+namespace cuda {
+/*!
+* \brief Create a CUDA schedule for region
+*
+* \param target The target to generate a schedule for.
+* \param outs The output tensors.
+*
+* \return A schedule for the given ops.
+*/
+inline Schedule schedule_region(const Target &target, const Array<Tensor>& outs) {
+  Array<Operation> out_ops;
+  for (auto t : outs) {
+    out_ops.push_back(t->op);
+  }
+  auto s = create_schedule(out_ops);
+  auto output = outs[0]->op.output(0);
+  auto num_thread = 64;
+
+  auto _schedule_softmax = [&](const Operation& softmax_op) {
+    auto softmax_inputs = softmax_op->InputTensors();
+    auto softmax = softmax_inputs[0];
+    auto max_elem = softmax_inputs[1];
+    auto expsum = softmax_inputs[2];
+
+    auto block_x = tvm::thread_axis(Range(), "blockIdx.x");
+    auto thread_x = tvm::thread_axis(Range(0, num_thread), "threadIdx.x");
+
+    s[max_elem].bind(max_elem->op.as<ComputeOpNode>()->axis[0], block_x);
+    auto k = expsum->op.as<ComputeOpNode>()->reduce_axis[0];
+    IterVar ko, ki;
+    s[expsum].split(k, num_thread, &ko, &ki);
+    auto ef = s.rfactor(expsum, ki)[0];
+
+    s[expsum].bind(s[expsum]->op.as<ComputeOpNode>()->axis[0], block_x);
+    s[expsum].bind(s[expsum]->op.as<ComputeOpNode>()->reduce_axis[0], thread_x);
+    s[ef].compute_at(s[expsum], s[expsum]->op.as<ComputeOpNode>()->reduce_axis[0]);
+
+    s[expsum].set_store_predicate(static_cast<Expr>(thread_x) == 0);
+    IterVar tx, xi;
+    s[softmax_op].split_by_nparts(softmax_op.as<ComputeOpNode>()->axis[1], num_thread, &tx, &xi);
+    s[softmax_op].bind(tx, thread_x);
+
+    return max_elem->op.as<ComputeOpNode>()->InputTensors()[0];
+  };
+
+  std::function<void(Operation)> traverse;
+  traverse = [&](const Operation& op) {
+    // Inline all one-to-one-mapping operators except the last stage (output)
+    if (is_injective(op->tag)) {
+      if (!detail::contains(s->outputs, op)) {
+        s[op].compute_inline();
+      }
+      for (auto tensor : op->InputTensors()) {
+        if (tensor->op->InputTensors().size() > 0) {
+          traverse(tensor->op);
+        }
+      }
+    } else if (op->tag == "softmax_output") {
+      auto tensor = _schedule_softmax(op);
+      if (tensor->op->InputTensors().size() > 0) {
+        traverse(tensor->op);
+      }
+    } else {
+      LOG(ERROR) << "Unsupported operator " << op->tag;
+    }
+  };
+
+  traverse(outs[0]->op);
+  auto k = output->op.as<ComputeOpNode>()->axis[0];
+  IterVar bx, tx;
+  s[output].split(k, num_thread, &bx, &tx);
+  s[output].bind(bx, tvm::thread_axis(Range(), "blockIdx.x"));
+  s[output].bind(tx, tvm::thread_axis(Range(), "threadIdx.x"));
+  return s;
+}
+}  // namespace cuda
+}  // namespace topi
+#endif  // TOPI_CUDA_VISION_H_
diff --git a/topi/include/topi/rocm/vision.h b/topi/include/topi/rocm/vision.h
new file mode 100644
index 000000000000..4178a180deb4
--- /dev/null
+++ b/topi/include/topi/rocm/vision.h
@@ -0,0 +1,33 @@
+/*!
+*  Copyright (c) 2018 by Contributors
+* \file rocm/vision.h
+* \brief rocm schedule for region operation
+*/
+#ifndef TOPI_ROCM_VISION_H_
+#define TOPI_ROCM_VISION_H_
+
+#include "tvm/tvm.h"
+#include "tvm/build_module.h"
+#include "topi/tags.h"
+#include "topi/detail/array_utils.h"
+#include "topi/contrib/rocblas.h"
+#include "topi/generic/extern.h"
+#include "topi/cuda/vision.h"
+
+namespace topi {
+using namespace tvm;
+namespace rocm {
+/*!
+* \brief Create a rocm schedule for region
+*
+* \param target The target to generate a schedule for.
+* \param outs The output tensors.
+*
+* \return A schedule for the given ops.
+*/
+inline Schedule schedule_region(const Target &target, const Array<Tensor>& outs) {
+  return topi::cuda::schedule_region(target, outs);
+}
+}  // namespace rocm
+}  // namespace topi
+#endif  // TOPI_ROCM_VISION_H_
diff --git a/topi/include/topi/vision/reorg.h b/topi/include/topi/vision/reorg.h
new file mode 100644
index 000000000000..dd73b940682d
--- /dev/null
+++ b/topi/include/topi/vision/reorg.h
@@ -0,0 +1,63 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \brief Reorg op constructions
+ * \file vision/reorg.h
+ */
+#ifndef TOPI_VISION_REORG_H_
+#define TOPI_VISION_REORG_H_
+
+#include <algorithm>
+#include <string>
+
+#include "topi/detail/constant_utils.h"
+#include "topi/reduction.h"
+#include "topi/tags.h"
+#include "topi/transform.h"
+#include "tvm/tvm.h"
+
+namespace topi {
+namespace vision {
+using namespace tvm;
+
+/*!
+* \brief Reorg operation
+*
+* \param data The input tensor. Can be any dimension
+* \param stride The input integer used as stride in reorg operation
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the reorg operation
+*/
+inline Tensor reorg(const Tensor &data,
+                    int stride = 1,
+                    std::string name = "tensor",
+                    std::string tag = "reorg_output") {
+  auto input_shape = data->shape;
+
+  int batch = GetConstInt(input_shape[0]);
+  int c_in = GetConstInt(input_shape[1]);
+  int h_in = GetConstInt(input_shape[2]);
+  int w_in = GetConstInt(input_shape[3]);
+  int out_c = c_in / (stride * stride);
+
+  auto out = tvm::compute(input_shape,
+                          [&](Var b, Var k, Var j, Var i) {
+                          return data(b * stride * stride,
+                                      (k % out_c) * stride * stride,
+                                      (j*stride + (k / out_c) / stride) * stride,
+                                      (i*stride + (k / out_c) % stride));
+                          },
+                          name,
+                          tag);
+
+  out_c = c_in * stride * stride;
+  int out_h = h_in / stride;
+  int out_w = w_in / stride;
+
+  Array<Expr> out_shape = {batch, out_c, out_h, out_w};
+  return reshape(out, out_shape);
+}
+}  // namespace vision
+}  // namespace topi
+#endif  // TOPI_VISION_REORG_H_
diff --git a/topi/include/topi/vision/yolo2/region.h b/topi/include/topi/vision/yolo2/region.h
new file mode 100644
index 000000000000..a77ff49b5429
--- /dev/null
+++ b/topi/include/topi/vision/yolo2/region.h
@@ -0,0 +1,81 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \brief Region op constructions
+ * \file vision/yolo2/region.h
+ */
+#ifndef TOPI_VISION_YOLO2_REGION_H_
+#define TOPI_VISION_YOLO2_REGION_H_
+
+#include <algorithm>
+#include <string>
+
+#include "topi/detail/constant_utils.h"
+#include "topi/reduction.h"
+#include "topi/tags.h"
+#include "topi/transform.h"
+#include "topi/nn/softmax.h"
+#include "tvm/tvm.h"
+
+
+namespace topi {
+namespace vision {
+namespace yolo2 {
+using namespace tvm;
+using namespace nn;
+
+/*!
+* \brief region operation
+*
+* \param data The input tensor. Can be any dimension
+* \param num Darknet layer parameter n
+* \param classes number of classes in the yolo model
+* \param coords Darknet layer parameter coords
+* \param background Darknet layer parameter background
+* \param l_softmax if true apply softmax
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the region operation
+*/
+inline Tensor region(const Tensor &data,
+                     int num,
+                     int classes,
+                     int coords,
+                     int background,
+                     int l_softmax,
+                     std::string name = "tensor",
+                     std::string tag = "region_output") {
+  auto input_shape = data->shape;
+  int split_size = classes + coords + 1;
+  Array <Expr> intermediate_shape = {input_shape[0],
+                                     num,
+                                     split_size,
+                                     input_shape[2],
+                                     input_shape[3]};
+  auto data_block = reshape(data, intermediate_shape);
+  Array <Expr> split_indices;
+  for (int i = 1; i < split_size; ++i) {
+    split_indices.push_back(i);
+  }
+  Array <Tensor> split_res = split(data_block, split_indices, 2);
+  split_res.Set(0, sigmoid(split_res[0]));
+  split_res.Set(1, sigmoid(split_res[1]));
+  if (!background) {
+    split_res.Set(coords, sigmoid(split_res[coords]));
+  }
+
+  if (l_softmax) {
+    int offset = coords + static_cast<int>(!background);
+    Array <Tensor> softmax_input(split_res.begin() + offset, split_res.end());
+    auto softmax_output = softmax(concatenate(softmax_input, 2), 2);
+    Array <Tensor> data_block_1(split_res.begin(), split_res.begin() + offset);
+    data_block_1.push_back(softmax_output);
+    split_res = data_block_1;
+  }
+  Tensor out = concatenate(split_res, 2);
+  return reshape(out, input_shape);
+}
+}  // namespace yolo2
+}  // namespace vision
+}  // namespace topi
+#endif  // TOPI_VISION_YOLO2_REGION_H_
diff --git a/topi/python/topi/cpp.py b/topi/python/topi/cpp.py
index c59d6d6bccfa..ae19484ac985 100644
--- a/topi/python/topi/cpp.py
+++ b/topi/python/topi/cpp.py
@@ -46,6 +46,10 @@ def _create_module(name):
 _init_api_prefix("topi.cpp.rocm", "topi.rocm")
 x86 = _create_module("x86")
 _init_api_prefix("topi.cpp.x86", "topi.x86")
+vision = _create_module("vision")
+_init_api_prefix("topi.cpp.vision", "topi.vision")
+yolo2 = _create_module("vision.yolo2")
+_init_api_prefix("topi.cpp.vision.yolo2", "topi.vision.yolo2")
 
 class IntVector(object):
     """Handle to std::vector<int> instance """
diff --git a/topi/python/topi/cuda/__init__.py b/topi/python/topi/cuda/__init__.py
index 74e92384a9e0..3b0e38c4d3f4 100644
--- a/topi/python/topi/cuda/__init__.py
+++ b/topi/python/topi/cuda/__init__.py
@@ -16,4 +16,5 @@
 from .conv2d_transpose_nchw import schedule_conv2d_transpose_nchw
 from .extern import schedule_extern
 from .vision import schedule_region
+from .vision import schedule_reorg
 from .nn import schedule_lrn, schedule_l2norm
diff --git a/topi/python/topi/cuda/vision.py b/topi/python/topi/cuda/vision.py
index bfd453f807c2..e946edca619a 100644
--- a/topi/python/topi/cuda/vision.py
+++ b/topi/python/topi/cuda/vision.py
@@ -2,8 +2,26 @@
 """Schedule for vision operators"""
 from __future__ import absolute_import as _abs
 import tvm
-from .. import tag
 from .. import generic
+from .. import cpp
+
+@generic.schedule_reorg.register(["cuda", "gpu"])
+def schedule_reorg(outs):
+    """Schedule for reorg operator.
+    Parameters
+    ----------
+    outs: Array of Tensor
+        The computation graph description of reorg
+        in the format of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+        The computation schedule for reorg.
+    """
+    target = tvm.target.current_target(allow_none=False)
+    cpp_target = cpp.TEST_create_target(target.target_name)
+    return cpp.cuda.schedule_injective(cpp_target, outs)
 
 @generic.schedule_region.register(["cuda", "gpu"])
 def schedule_region(outs):
@@ -19,47 +37,6 @@ def schedule_region(outs):
     s: Schedule
         The computation schedule for region.
     """
-
-    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
-    s = tvm.create_schedule([x.op for x in outs])
-    output = outs[0].op.output(0)
-    #thread = 64 for higher size tensors, give resource_unavailable error for higher values
-    num_thread = 64
-    def _schedule_softmax(softmax_op):
-        softmax = softmax_op.input_tensors[0]
-        max_elem = softmax_op.input_tensors[1]
-        expsum = softmax_op.input_tensors[2]
-        block_x = tvm.thread_axis("blockIdx.x")
-        thread_x = tvm.thread_axis((0, num_thread), "threadIdx.x")
-        s[max_elem].bind(max_elem.op.axis[0], block_x)
-        k = expsum.op.reduce_axis[0]
-        ko, ki = s[expsum].split(k, factor=num_thread)
-        ef = s.rfactor(expsum, ki)
-        s[expsum].bind(s[expsum].op.axis[0], block_x)
-        s[expsum].bind(s[expsum].op.reduce_axis[0], thread_x)
-        s[ef].compute_at(s[expsum], s[expsum].op.reduce_axis[0])
-        s[expsum].set_store_predicate(thread_x.var.equal(0))
-        tx, xi = s[softmax_op].split(softmax_op.axis[1], nparts=num_thread)
-        s[softmax_op].bind(softmax_op.axis[0], block_x)
-        s[softmax_op].bind(tx, thread_x)
-        return max_elem.op.input_tensors[0]
-
-    def _traverse(op):
-        if tag.is_injective(op.tag):
-            if op not in s.outputs:
-                s[op].compute_inline()
-            for tensor in op.input_tensors:
-                if tensor.op.input_tensors:
-                    _traverse(tensor.op)
-        elif op.tag == 'softmax_output':
-            tensor = _schedule_softmax(op)
-            if tensor.op.input_tensors:
-                _traverse(tensor.op)
-        else:
-            raise RuntimeError("Unsupported operator: %s" % op.tag)
-    _traverse(outs[0].op)
-    k = output.op.axis[0]
-    bx, tx = s[output].split(k, factor=num_thread)
-    s[output].bind(bx, tvm.thread_axis("blockIdx.x"))
-    s[output].bind(tx, tvm.thread_axis("threadIdx.x"))
-    return s
+    target = tvm.target.current_target(allow_none=False)
+    cpp_target = cpp.TEST_create_target(target.target_name)
+    return cpp.cuda.schedule_region(cpp_target, outs)
diff --git a/topi/python/topi/generic/vision.py b/topi/python/topi/generic/vision.py
index 139491b6213e..a162c0ee2de9 100644
--- a/topi/python/topi/generic/vision.py
+++ b/topi/python/topi/generic/vision.py
@@ -1,6 +1,7 @@
 """Generic vision operators"""
 from __future__ import absolute_import as _abs
 import tvm
+from .. import cpp
 
 def _default_schedule(outs, auto_inline):
     """Default schedule for llvm."""
@@ -47,7 +48,9 @@ def schedule_reorg(outs):
     s: Schedule
       The computation schedule for the op.
     """
-    return _default_schedule(outs, False)
+    target = tvm.target.current_target(allow_none=False)
+    cpp_target = cpp.TEST_create_target(target.target_name)
+    return cpp.generic.default_schedule(cpp_target, outs, False)
 
 @tvm.target.generic_func
 def schedule_region(outs):
@@ -64,4 +67,6 @@ def schedule_region(outs):
     s: Schedule
       The computation schedule for the op.
     """
-    return _default_schedule(outs, False)
+    target = tvm.target.current_target(allow_none=False)
+    cpp_target = cpp.TEST_create_target(target.target_name)
+    return cpp.generic.default_schedule(cpp_target, outs, False)
diff --git a/topi/python/topi/rocm/vision.py b/topi/python/topi/rocm/vision.py
index 465bfd1e9cce..84ae436e3531 100644
--- a/topi/python/topi/rocm/vision.py
+++ b/topi/python/topi/rocm/vision.py
@@ -1,8 +1,9 @@
 # pylint: disable=invalid-name, unused-variable
 """Schedule for vision operator"""
 from __future__ import absolute_import as _abs
-import topi
+import tvm
 from .. import generic
+from .. import cpp
 
 @generic.schedule_region.register(["rocm"])
 def schedule_region(outs):
@@ -19,4 +20,6 @@ def schedule_region(outs):
     s: Schedule
         The computation schedule for region.
     """
-    return topi.cuda.schedule_region(outs)
+    target = tvm.target.current_target(allow_none=False)
+    cpp_target = cpp.TEST_create_target(target.target_name)
+    return cpp.rocm.schedule_region(cpp_target, outs)
diff --git a/topi/python/topi/vision/reorg.py b/topi/python/topi/vision/reorg.py
index 761a1718fd23..9f4fa70a71f6 100644
--- a/topi/python/topi/vision/reorg.py
+++ b/topi/python/topi/vision/reorg.py
@@ -5,8 +5,7 @@
 """
 from __future__ import absolute_import as _abs
 import tvm
-from .. import util
-from .. import transform
+from .. import cpp
 
 @tvm.target.generic_func
 def reorg(data, stride):
@@ -25,15 +24,4 @@ def reorg(data, stride):
     Output : tvm.Tensor
         4-D with shape [batch, out_channel, out_height, out_width]
     """
-    batch, c_in, h_in, w_in = util.get_const_tuple(data.shape)
-    out_c = int(c_in / (stride * stride))
-    out = tvm.compute((batch, c_in, h_in, w_in), lambda b, k, j, i:
-                      data[b * stride * stride,
-                           (k % out_c) * stride * stride,
-                           (j*stride + (k / out_c) / stride) * stride,
-                           (i*stride + (k / out_c) % stride)],
-                      tag="reorg")
-    out_c = int(c_in * stride * stride)
-    out_h = int(h_in / stride)
-    out_w = int(w_in / stride)
-    return transform.reshape(out, (batch, out_c, out_h, out_w))
+    return cpp.vision.reorg(data, stride)
diff --git a/topi/python/topi/vision/yolo2/region.py b/topi/python/topi/vision/yolo2/region.py
index 8f31d88fa941..79dfd6961a3a 100644
--- a/topi/python/topi/vision/yolo2/region.py
+++ b/topi/python/topi/vision/yolo2/region.py
@@ -6,10 +6,7 @@
 """
 from __future__ import absolute_import as _abs
 import tvm
-from ... import transform
-from ... import util
-from ... import math
-from ... import nn
+from ... import cpp
 
 @tvm.target.generic_func
 def region(data, num, classes, coords, background, softmax=True):
@@ -39,25 +36,4 @@ def region(data, num, classes, coords, background, softmax=True):
     out : tvm.Tensor
         4-D with shape [batch, c_in, h_in, w_in]
     """
-
-    batch, c_in, h_in, w_in = util.get_const_tuple(data.shape)
-    split_indices = classes+coords+1
-    data_block = transform.reshape(data, (batch, num, split_indices, h_in, w_in))
-    split_res = transform.split(data_block, split_indices, 2)
-    split_res[0] = math.sigmoid(split_res[0])
-    split_res[1] = math.sigmoid(split_res[1])
-    if not background:
-        split_res[coords] = math.sigmoid(split_res[coords])
-
-    if softmax:
-        offset = coords + int(not background)
-        data_block_1 = []
-        data_block_1.append(transform.concatenate(split_res[0:offset], 2))
-        temp_out = transform.concatenate(split_res[offset:split_indices], 2)
-        temp_out = nn.softmax(temp_out, axis=2)
-        data_block_1.append(temp_out)
-        split_res = data_block_1
-
-    out = transform.concatenate(split_res, 2)
-    out = transform.reshape(out, data.shape)
-    return out
+    return cpp.yolo2.region(data, num, classes, coords, background, softmax)
diff --git a/topi/src/topi.cc b/topi/src/topi.cc
index a31a7809c667..ae928cd907da 100644
--- a/topi/src/topi.cc
+++ b/topi/src/topi.cc
@@ -24,6 +24,8 @@
 #include <topi/nn/pooling.h>
 #include <topi/nn/softmax.h>
 
+#include <topi/vision/reorg.h>
+#include <topi/vision/yolo2/region.h>
 #include <topi/generic/default.h>
 #include <topi/generic/extern.h>
 #include <topi/generic/injective.h>
@@ -34,12 +36,14 @@
 #include <topi/cuda/pooling.h>
 #include <topi/cuda/reduction.h>
 #include <topi/cuda/softmax.h>
+#include <topi/cuda/vision.h>
 
 #include <topi/x86/bnn.h>
 #include <topi/x86/default.h>
 #include <topi/x86/injective.h>
 
 #include <topi/rocm/dense.h>
+#include <topi/rocm/vision.h>
 
 namespace tvm {
 namespace runtime {
@@ -338,6 +342,14 @@ TVM_REGISTER_GLOBAL("topi.nn.log_softmax")
   *rv = nn::log_softmax(args[0]);
   });
 
+TVM_REGISTER_GLOBAL("topi.vision.reorg")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = vision::reorg(args[0], args[1]);
+  });
+TVM_REGISTER_GLOBAL("topi.vision.yolo2.region")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = vision::yolo2::region(args[0], args[1], args[2], args[3], args[4], args[5]);
+  });
 /* Generic schedules */
 TVM_REGISTER_GLOBAL("topi.generic.default_schedule")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
@@ -394,6 +406,10 @@ TVM_REGISTER_GLOBAL("topi.rocm.schedule_dense")
   *rv = topi::rocm::schedule_dense(args[0], args[1]);
   });
 
+TVM_REGISTER_GLOBAL("topi.rocm.schedule_region")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::rocm::schedule_region(args[0], args[1]);
+  });
 /* CUDA schedules */
 TVM_REGISTER_GLOBAL("topi.cuda.dense_cuda")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
@@ -435,6 +451,11 @@ TVM_REGISTER_GLOBAL("topi.cuda.schedule_softmax")
   *rv = topi::cuda::schedule_softmax(args[0], args[1]);
   });
 
+TVM_REGISTER_GLOBAL("topi.cuda.schedule_region")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::cuda::schedule_region(args[0], args[1]);
+  });
+
 /*! \brief Builder function for instantiating schedules. */
 using FTVMScheduleBuilder = std::function<
   tvm::Schedule(const tvm::Target& target, const tvm::Array<tvm::Tensor>& outs)>;
diff --git a/topi/tests/python/test_topi_region.py b/topi/tests/python/test_topi_region.py
index b2ef02530ba6..f83001535e35 100644
--- a/topi/tests/python/test_topi_region.py
+++ b/topi/tests/python/test_topi_region.py
@@ -3,6 +3,8 @@
 import topi
 from topi.util import get_const_tuple
 import tvm
+import topi.testing
+
 def verify_region(batch, in_size, in_channel, n, classes, coords, background, l_softmax):
     '''Verify region operator by comparing outputs from tvm and numpy implementation'''
     in_height = in_width = in_size
@@ -27,7 +29,10 @@ def check_device(device):
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
-            s = topi.generic.vision.schedule_region([B])
+            if device == 'llvm':
+                s = topi.generic.vision.schedule_region([B])
+            else:
+                s = topi.cuda.vision.schedule_region([B])
         a = tvm.nd.array(a_np, ctx)
         b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
         func = tvm.build(s, [A, B], device)
diff --git a/topi/tests/python/test_topi_reorg.py b/topi/tests/python/test_topi_reorg.py
index 36a4d71cb5d4..5b15b9f6c5aa 100644
--- a/topi/tests/python/test_topi_reorg.py
+++ b/topi/tests/python/test_topi_reorg.py
@@ -3,6 +3,7 @@
 import topi
 from topi.util import get_const_tuple
 import tvm
+import topi.testing
 
 def verify_reorg(batch, in_size, in_channel, stride):
     '''Verify reorg operator by comparing outputs from tvm and numpy implementation'''
@@ -29,8 +30,10 @@ def check_device(device):
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
-            s = topi.generic.schedule_injective([B])
-
+            if device == 'llvm':
+                s = topi.generic.schedule_reorg([B])
+            else:
+                s = topi.cuda.schedule_reorg([B])
         a = tvm.nd.array(a_np, ctx)
         b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
         func = tvm.build(s, [A, B], device)
diff --git a/topi/tests/python_cpp/test_topi_region.py b/topi/tests/python_cpp/test_topi_region.py
new file mode 100644
index 000000000000..8d40fa1e6b3a
--- /dev/null
+++ b/topi/tests/python_cpp/test_topi_region.py
@@ -0,0 +1,52 @@
+"""Test code for region"""
+import logging
+import numpy as np
+import tvm
+import topi
+import topi.testing
+from topi.util import get_const_tuple
+
+def verify_region(batch, in_size, in_channel, n, classes, coords, background, l_softmax):
+    '''Verify region operator by comparing outputs from tvm and numpy implementation'''
+    in_height = in_width = in_size
+
+    A = tvm.placeholder((batch, in_channel, in_height, in_width), name='A')
+    B = topi.cpp.yolo2.region(A, n, classes, coords, background, l_softmax)
+
+    a_shape = get_const_tuple(A.shape)
+    dtype = A.dtype
+
+    def get_ref_data_region():
+        '''Randomly initialize the data variables and get refernce output for the region operation'''
+        a_np = np.random.uniform(size=a_shape).astype(dtype)
+        b_np = topi.testing.region_python(a_np, n, classes, coords, background, l_softmax)
+        return a_np, b_np
+
+    a_np, b_np = get_ref_data_region()
+    def check_device(device):
+        '''Check the device is available and if so, build and run the program'''
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        target = topi.cpp.TEST_create_target(device)
+        if device == "llvm":
+            s = topi.cpp.generic.default_schedule(target, [B], False)
+        else:
+            s = topi.cpp.rocm.schedule_region(target, [B])
+        ctx = tvm.context(device, 0)
+        a = tvm.nd.array(a_np, ctx)
+        b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
+        func = tvm.build(s, [A, B], device, name="region")
+        func(a, b)
+        np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+    for device in ['cuda', 'opencl', 'metal', 'rocm', 'llvm', 'vulkan']:
+        check_device(device)
+
+def test_region():
+    verify_region(1, 19, 425, 5, 80, 4, 0, 1)
+
+if __name__ == "__main__":
+    logging.basicConfig(level=logging.DEBUG)
+    test_region()
diff --git a/topi/tests/python_cpp/test_topi_reorg.py b/topi/tests/python_cpp/test_topi_reorg.py
new file mode 100644
index 000000000000..e5b8aa7f8b31
--- /dev/null
+++ b/topi/tests/python_cpp/test_topi_reorg.py
@@ -0,0 +1,52 @@
+"""Test code for reorg"""
+import logging
+import numpy as np
+import tvm
+import topi
+import topi.testing
+from topi.util import get_const_tuple
+
+def verify_reorg(batch, in_size, in_channel, stride):
+    '''Verify reorg operator by comparing outputs from tvm and numpy implementation'''
+    in_height = in_width = in_size
+
+    A = tvm.placeholder((batch, in_channel, in_height, in_width), name='A')
+    B = topi.cpp.vision.reorg(A, stride)
+
+    a_shape = get_const_tuple(A.shape)
+    dtype = A.dtype
+
+    def get_ref_data_reorg():
+        '''Randomly initialize the data variables and get refernce output for the reorg operation'''
+        a_np = np.random.uniform(size=a_shape).astype(dtype)
+        b_np = topi.testing.reorg_python(a_np, stride)
+        return a_np, b_np
+
+    a_np, b_np = get_ref_data_reorg()
+    def check_device(device):
+        '''Check the device is available and if so, build and run the program'''
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        target = topi.cpp.TEST_create_target(device)
+        if device == "llvm":
+            s = topi.cpp.generic.default_schedule(target, [B], False)
+        else:
+            s = topi.cpp.cuda.schedule_injective(target, [B])
+        ctx = tvm.context(device, 0)
+        a = tvm.nd.array(a_np, ctx)
+        b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
+        func = tvm.build(s, [A, B], device, name="reorg")
+        func(a, b)
+        np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+    for device in ['cuda', 'opencl', 'metal', 'rocm', 'llvm', 'vulkan']:
+        check_device(device)
+
+def test_reorg():
+    verify_reorg(1, 38, 64, 2)
+
+if __name__ == "__main__":
+    logging.basicConfig(level=logging.DEBUG)
+    test_reorg()

From 339db24322647cf314144c0e7c320c1c7744a8a6 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 8 May 2018 09:23:13 -0700
Subject: [PATCH 294/949] Update jenkins file first to use sort (#1147)

---
 Jenkinsfile | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/Jenkinsfile b/Jenkinsfile
index 7fb8e6b8b5d1..0f1e937a02ae 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -90,6 +90,7 @@ stage('Build') {
            echo USE_OPENGL=1 >> config.mk
            echo LLVM_CONFIG=llvm-config-4.0 >> config.mk
            echo USE_RPC=1 >> config.mk
+           echo USE_SORT=1 >> config.mk
            echo USE_GRAPH_RUNTIME=1 >> config.mk
            echo USE_BLAS=openblas >> config.mk
            rm -f lib/libtvm_runtime.so lib/libtvm.so
@@ -122,6 +123,7 @@ stage('Build') {
            echo USE_CUDA=0 >> config.mk
            echo USE_OPENCL=0 >> config.mk
            echo USE_RPC=0 >> config.mk
+           echo USE_SORT=1 >> config.mk
            echo USE_OPENGL=1 >> config.mk
            echo LLVM_CONFIG=llvm-config-4.0 >> config.mk
            """
@@ -140,6 +142,7 @@ stage('Build') {
            echo USE_OPENCL=0 >> config.mk
            echo LLVM_CONFIG=llvm-config-4.0 >> config.mk
            echo USE_RPC=1 >> config.mk
+           echo USE_SORT=1 >> config.mk
            """
         make('i386', '-j2')
         sh "mv lib/libtvm.so lib/libtvm_llvm40.so"

From daa80b973ab84f21ff240d7e438889fa7dd9e2c5 Mon Sep 17 00:00:00 2001
From: MORITA Kazutaka <morita.kazutaka@gmail.com>
Date: Fri, 11 May 2018 02:07:13 +0900
Subject: [PATCH 295/949] fix relu tests (#1149)

---
 topi/tests/python/test_topi_relu.py     | 2 +-
 topi/tests/python_cpp/test_topi_relu.py | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/topi/tests/python/test_topi_relu.py b/topi/tests/python/test_topi_relu.py
index 2a70789cf7b0..6360155c5012 100644
--- a/topi/tests/python/test_topi_relu.py
+++ b/topi/tests/python/test_topi_relu.py
@@ -9,7 +9,7 @@ def verify_relu(m, n):
     A = tvm.placeholder((m, n), name='A')
     B = topi.nn.relu(A)
 
-    a_np = np.random.uniform(size=get_const_tuple(A.shape)).astype(A.dtype)
+    a_np = np.random.uniform(low=-1.0, high=1.0, size=get_const_tuple(A.shape)).astype(A.dtype)
     b_np = a_np * (a_np > 0)
 
     def check_device(device):
diff --git a/topi/tests/python_cpp/test_topi_relu.py b/topi/tests/python_cpp/test_topi_relu.py
index 6b80c20dd63c..44890e908d42 100644
--- a/topi/tests/python_cpp/test_topi_relu.py
+++ b/topi/tests/python_cpp/test_topi_relu.py
@@ -10,7 +10,7 @@ def verify_relu(m, n, dtype):
     B = topi.cpp.nn.relu(A)
     assert B.dtype == dtype
 
-    a_np = np.random.uniform(size=get_const_tuple(A.shape)).astype(A.dtype)
+    a_np = np.random.uniform(low=-1.0, high=1.0, size=get_const_tuple(A.shape)).astype(A.dtype)
     b_np = a_np * (a_np > 0)
 
     def check_device(device):

From 929768f5f94911c619295342789a5f3cf3fa60d7 Mon Sep 17 00:00:00 2001
From: eqy <eqy@cs.washington.edu>
Date: Thu, 10 May 2018 15:32:01 -0700
Subject: [PATCH 296/949] register loadbinary_hip (#1151)

---
 src/runtime/rocm/rocm_module.cc | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/runtime/rocm/rocm_module.cc b/src/runtime/rocm/rocm_module.cc
index 90f7c5c2239a..d93862747bba 100644
--- a/src/runtime/rocm/rocm_module.cc
+++ b/src/runtime/rocm/rocm_module.cc
@@ -229,6 +229,12 @@ TVM_REGISTER_GLOBAL("module.loadbinary_hsaco")
 .set_body([](TVMArgs args, TVMRetValue* rv) {
     *rv = ROCMModuleLoadBinary(args[0]);
   });
+
+
+TVM_REGISTER_GLOBAL("module.loadbinary_hip")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    *rv = ROCMModuleLoadBinary(args[0]);
+  });
 }  // namespace runtime
 }  // namespace tvm
 #endif  // TVM_ROCM_RUNTIME

From fe265c27743beb7ce6a729d85b41a76024bbd0b9 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 10 May 2018 19:27:55 -0700
Subject: [PATCH 297/949] [CODEGEN] Enable cross compile of AMDGPU without
 rocm, update rpc  (#1154)

---
 python/tvm/contrib/rpc/proxy.py              |  2 +-
 python/tvm/contrib/rpc/server.py             | 36 ++++++++++-----
 python/tvm/exec/rpc_server.py                | 22 +---------
 src/codegen/codegen_source_base.h            | 11 +++--
 src/codegen/llvm/codegen_amdgpu.cc           | 46 +++++++++++++++-----
 src/codegen/source_module.cc                 | 45 ++++++++++++++-----
 src/runtime/module.cc                        |  4 +-
 topi/tests/python/test_topi_l2norm.py        |  4 +-
 topi/tests/python/test_topi_lrn.py           |  4 +-
 topi/tests/python_cpp/test_topi_dense.py     |  4 +-
 topi/tests/python_cpp/test_topi_pooling.py   |  4 +-
 topi/tests/python_cpp/test_topi_reduce.py    |  4 +-
 topi/tests/python_cpp/test_topi_transform.py | 22 +++++-----
 13 files changed, 129 insertions(+), 79 deletions(-)

diff --git a/python/tvm/contrib/rpc/proxy.py b/python/tvm/contrib/rpc/proxy.py
index ed9d89dc9b68..857e69433a1c 100644
--- a/python/tvm/contrib/rpc/proxy.py
+++ b/python/tvm/contrib/rpc/proxy.py
@@ -536,7 +536,7 @@ def _fsend(data):
     def _connect(key):
         conn = yield websocket.websocket_connect(url)
         on_message = create_on_message(conn)
-        temp = _server_env()
+        temp = _server_env(None)
         # Start connecton
         conn.write_message(struct.pack('@i', base.RPC_MAGIC), binary=True)
         key = "server:" + key
diff --git a/python/tvm/contrib/rpc/server.py b/python/tvm/contrib/rpc/server.py
index 6759f13b6753..b49a6900b295 100644
--- a/python/tvm/contrib/rpc/server.py
+++ b/python/tvm/contrib/rpc/server.py
@@ -11,6 +11,7 @@
 from __future__ import absolute_import
 
 import os
+import ctypes
 import socket
 import select
 import struct
@@ -21,12 +22,13 @@
 
 from ..._ffi.function import register_func
 from ..._ffi.base import py_str
+from ..._ffi.libinfo import find_lib_path
 from ...module import load as _load_module
 from .. import util
 from . import base
 from . base import TrackerCode
 
-def _server_env():
+def _server_env(load_library):
     """Server environment function return temp dir"""
     temp = util.tempdir()
     # pylint: disable=unused-variable
@@ -41,13 +43,21 @@ def load_module(file_name):
         m = _load_module(path)
         logging.info("load_module %s", path)
         return m
+
+    libs = []
+    load_library = load_library.split(":") if load_library else []
+    for file_name in load_library:
+        file_name = find_lib_path(file_name)[0]
+        libs.append(ctypes.CDLL(file_name, ctypes.RTLD_GLOBAL))
+        logging.info("Load additional library %s", file_name)
+    temp.libs = libs
     return temp
 
 
-def _serve_loop(sock, addr):
+def _serve_loop(sock, addr, load_library):
     """Server loop"""
     sockfd = sock.fileno()
-    temp = _server_env()
+    temp = _server_env(load_library)
     base._ServerLoop(sockfd)
     temp.remove()
     logging.info("Finish serving %s", addr)
@@ -62,7 +72,7 @@ def _parse_server_opt(opts):
     return ret
 
 
-def _listen_loop(sock, port, rpc_key, tracker_addr):
+def _listen_loop(sock, port, rpc_key, tracker_addr, load_library):
     """Lisenting loop of the server master."""
     def _accept_conn(listen_sock, tracker_conn, ping_period=2):
         """Accept connection from the other places.
@@ -162,7 +172,7 @@ def _accept_conn(listen_sock, tracker_conn, ping_period=2):
 
         # step 3: serving
         logging.info("RPCServer: connection from %s", addr)
-        server_proc = multiprocessing.Process(target=_serve_loop, args=(conn, addr))
+        server_proc = multiprocessing.Process(target=_serve_loop, args=(conn, addr, load_library))
         server_proc.deamon = True
         server_proc.start()
         # close from our side.
@@ -174,7 +184,7 @@ def _accept_conn(listen_sock, tracker_conn, ping_period=2):
             server_proc.terminate()
 
 
-def _connect_proxy_loop(addr, key):
+def _connect_proxy_loop(addr, key, load_library):
     key = "server:" + key
     retry_count = 0
     max_retry = 5
@@ -198,7 +208,7 @@ def _connect_proxy_loop(addr, key):
             opts = _parse_server_opt(remote_key.split()[1:])
             logging.info("RPCProxy connected to %s", str(addr))
             process = multiprocessing.Process(
-                target=_serve_loop, args=(sock, addr))
+                target=_serve_loop, args=(sock, addr, load_library))
             process.deamon = True
             process.start()
             sock.close()
@@ -256,6 +266,9 @@ class Server(object):
 
     key : str, optional
         The key used to identify the server in Proxy connection.
+
+    load_library : str, optional
+        List of additional libraries to be loaded during execution.
     """
     def __init__(self,
                  host,
@@ -264,7 +277,8 @@ def __init__(self,
                  is_proxy=False,
                  use_popen=False,
                  tracker_addr=None,
-                 key=""):
+                 key="",
+                 load_library=None):
         try:
             if base._ServerLoop is None:
                 raise RuntimeError("Please compile with USE_RPC=1")
@@ -283,6 +297,8 @@ def __init__(self,
                 assert key
                 cmd += ["--tracker=%s:%d" % tracker_addr,
                         "--key=%s" % key]
+            if load_library:
+                cmd += ["--load-libary", load_library]
             self.proc = multiprocessing.Process(
                 target=subprocess.check_call, args=(cmd,))
             self.proc.deamon = True
@@ -308,12 +324,12 @@ def __init__(self,
             self.sock = sock
             self.proc = multiprocessing.Process(
                 target=_listen_loop, args=(
-                    self.sock, self.port, key, tracker_addr))
+                    self.sock, self.port, key, tracker_addr, load_library))
             self.proc.deamon = True
             self.proc.start()
         else:
             self.proc = multiprocessing.Process(
-                target=_connect_proxy_loop, args=((host, port), key))
+                target=_connect_proxy_loop, args=((host, port), key, load_library))
             self.proc.deamon = True
             self.proc.start()
 
diff --git a/python/tvm/exec/rpc_server.py b/python/tvm/exec/rpc_server.py
index 7cbe17722bf5..84bad0f5422e 100644
--- a/python/tvm/exec/rpc_server.py
+++ b/python/tvm/exec/rpc_server.py
@@ -1,12 +1,8 @@
 """Start an RPC server"""
 from __future__ import absolute_import
 
-import logging
 import argparse
-import os
-import ctypes
 from ..contrib import rpc
-from .._ffi.libinfo import find_lib_path
 
 def main():
     """Main funciton"""
@@ -19,26 +15,12 @@ def main():
                         help='The end search port of the PRC')
     parser.add_argument('--key', type=str, default="",
                         help="RPC key used to identify the connection type.")
-    parser.add_argument('--with-executor', type=bool, default=False,
-                        help="Whether to load executor runtime")
     parser.add_argument('--load-library', type=str, default="",
                         help="Additional library to load")
     parser.add_argument('--tracker', type=str, default="",
                         help="Report to RPC tracker")
     args = parser.parse_args()
 
-    logging.basicConfig(level=logging.INFO)
-    load_library = [lib for lib in args.load_library.split(":") if len(lib) != 0]
-    curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
-    apps_path = os.path.join(curr_path, "../../../apps/graph_executor/lib/")
-    libs = []
-    if args.with_executor:
-        load_library += ["libtvm_graph_exec.so"]
-    for file_name in load_library:
-        file_name = find_lib_path(file_name, apps_path)[0]
-        libs.append(ctypes.CDLL(file_name, ctypes.RTLD_GLOBAL))
-        logging.info("Load additional library %s", file_name)
-
     if args.tracker:
         url, port = args.tracker.split(":")
         port = int(port)
@@ -53,8 +35,8 @@ def main():
                         args.port,
                         args.port_end,
                         key=args.key,
-                        tracker_addr=tracker_addr)
-    server.libs += libs
+                        tracker_addr=tracker_addr,
+                        load_library=args.load_library)
     server.proc.join()
 
 if __name__ == "__main__":
diff --git a/src/codegen/codegen_source_base.h b/src/codegen/codegen_source_base.h
index bc99eeeb1d33..89c5bbc05ce4 100644
--- a/src/codegen/codegen_source_base.h
+++ b/src/codegen/codegen_source_base.h
@@ -10,6 +10,7 @@
 #include <tvm/codegen.h>
 #include <string>
 #include <vector>
+#include <functional>
 #include <unordered_map>
 #include "../runtime/meta_data.h"
 
@@ -111,17 +112,19 @@ class CodeGenSourceBase {
 runtime::Module SourceModuleCreate(std::string code, std::string fmt);
 
 /*!
- * \brief Create a source module for viewing and limited saving
- * \param code The code to be viewed.
+ * \brief Create a source module for viewing and limited saving for device.
+ * \param data The code data to be viewed.
  * \param fmt The code. format.
  * \param fmap The map function information map of each function.
  * \param type_key The type_key of the runtime module of this source code
+ * \param fget_source a closure to replace default get source behavior.
  */
 runtime::Module DeviceSourceModuleCreate(
-  std::string code,
+  std::string data,
   std::string fmt,
   std::unordered_map<std::string, runtime::FunctionInfo> fmap,
-  std::string type_key);
+  std::string type_key,
+  std::function<std::string(const std::string&)> fget_source = nullptr);
 }  // namespace codegen
 }  // namespace tvm
 #endif  // TVM_CODEGEN_CODEGEN_SOURCE_BASE_H_
diff --git a/src/codegen/llvm/codegen_amdgpu.cc b/src/codegen/llvm/codegen_amdgpu.cc
index fa42cefa07a7..97858079faaa 100644
--- a/src/codegen/llvm/codegen_amdgpu.cc
+++ b/src/codegen/llvm/codegen_amdgpu.cc
@@ -4,15 +4,18 @@
  * \brief AMDGPU code generator.
  */
 #ifdef TVM_LLVM_VERSION
-#if TVM_ROCM_RUNTIME
 
 #include <tvm/runtime/device_api.h>
 #include <tvm/runtime/c_runtime_api.h>
 #include <tvm/runtime/registry.h>
 #include "./codegen_llvm.h"
 #include "../build_common.h"
+#include "../codegen_source_base.h"
 #include "../../pass/ir_util.h"
+
+#if TVM_ROCM_RUNTIME
 #include "../../runtime/rocm/rocm_module.h"
+#endif   // TVM_ROCM_RUNTIME
 
 namespace tvm {
 namespace codegen {
@@ -131,19 +134,27 @@ class CodeGenAMDGPU : public CodeGenLLVM {
   }
 };
 
-inline int DetectROCMComputeVersion() {
+inline int DetectROCMComputeVersion(const std::string& target) {
+  size_t pos = target.find("=gfx");
+  if (pos != std::string::npos) {
+    int value;
+    std::stringstream is(target.substr(pos + 4));
+    if (is >> value) return value;
+  }
   TVMContext tvm_ctx;
   tvm_ctx.device_type = kDLROCM;
   tvm_ctx.device_id = 0;
-  TVMRetValue val;
-  tvm::runtime::DeviceAPI::Get(tvm_ctx)->GetAttr(
-      tvm_ctx, tvm::runtime::kExist, &val);
-  if (val.operator int() == 1) {
-    tvm::runtime::DeviceAPI::Get(tvm_ctx)->GetAttr(tvm_ctx, tvm::runtime::kComputeVersion, &val);
-    return val.operator int();
-  } else {
-    return 803;
+  tvm::runtime::DeviceAPI* api = tvm::runtime::DeviceAPI::Get(tvm_ctx, true);
+  if (api != nullptr) {
+    TVMRetValue val;
+    api->GetAttr(tvm_ctx, tvm::runtime::kExist, &val);
+    if (val.operator int() == 1) {
+      tvm::runtime::DeviceAPI::Get(tvm_ctx)->GetAttr(tvm_ctx, tvm::runtime::kComputeVersion, &val);
+      return val.operator int();
+    }
   }
+  LOG(WARNING) << "Cannot find -mcpu to specify rocm compute version assume gfx803";
+  return 803;
 }
 
 runtime::Module BuildAMDGPU(Array<LoweredFunc> funcs, std::string target) {
@@ -151,7 +162,7 @@ runtime::Module BuildAMDGPU(Array<LoweredFunc> funcs, std::string target) {
         target.substr(0, 4) == "rocm");
   std::ostringstream config;
   config << "-mtriple=amdgcn-amd-amdhsa-hcc -mcpu=gfx"
-         << DetectROCMComputeVersion()
+         << DetectROCMComputeVersion(target)
          << target.substr(4, target.length() - 4);
   llvm::TargetMachine* tm = GetLLVMTargetMachine(config.str());
   std::unique_ptr<CodeGenAMDGPU> cg(new CodeGenAMDGPU());
@@ -216,7 +227,19 @@ runtime::Module BuildAMDGPU(Array<LoweredFunc> funcs, std::string target) {
   std::string hsaco = (*f)(arr);
   std::string ll(data_ll.begin(), data_ll.end());
 
+#if TVM_ROCM_RUNTIME
   return ROCMModuleCreate(hsaco, "hsaco", ExtractFuncInfo(funcs), ll, assembly);
+#else
+  LOG(WARNING) << "ROCM runtime is not enabled, return a source module...";
+  auto fget_source = [ll, assembly](const std::string& format) {
+    if (format.length() == 0) return assembly;
+    if (format == "ll" || format == "llvm") return format;
+    if (format == "asm") return assembly;
+    return std::string("");
+  };
+  return DeviceSourceModuleCreate(
+      hsaco, "hsaco", ExtractFuncInfo(funcs), "hsaco", fget_source);
+#endif   // TVM_ROCM_RUNTIME
 }
 
 TVM_REGISTER_API("codegen.build_rocm")
@@ -226,5 +249,4 @@ TVM_REGISTER_API("codegen.build_rocm")
 
 }  // namespace codegen
 }  // namespace tvm
-#endif   // TVM_ROCM_RUNTIME
 #endif  // TVM_LLVM_VERSION
diff --git a/src/codegen/source_module.cc b/src/codegen/source_module.cc
index 23c0cbd8466e..69dbda49976b 100644
--- a/src/codegen/source_module.cc
+++ b/src/codegen/source_module.cc
@@ -54,46 +54,71 @@ runtime::Module SourceModuleCreate(std::string code, std::string fmt) {
 }
 
 // supports limited save without cross compile
-class DeviceSourceModuleNode final : public SourceModuleNode {
+class DeviceSourceModuleNode final : public runtime::ModuleNode {
  public:
-  DeviceSourceModuleNode(std::string code,
+  DeviceSourceModuleNode(std::string data,
                          std::string fmt,
                          std::unordered_map<std::string, FunctionInfo> fmap,
-                         std::string type_key)
-      : SourceModuleNode(code, fmt), fmap_(fmap), type_key_(type_key) {}
+                         std::string type_key,
+                         std::function<std::string(const std::string&)> fget_source)
+    : data_(data),
+      fmt_(fmt),
+      fmap_(fmap),
+      type_key_(type_key),
+      fget_source_(fget_source) {}
+
+  PackedFunc GetFunction(
+        const std::string& name,
+        const std::shared_ptr<ModuleNode>& sptr_to_self) final {
+    LOG(FATAL) << "Source module cannot execute, to get executable module"
+               << " build TVM with \'" << fmt_ << "\' runtime support";
+    return PackedFunc();
+  }
+
+  std::string GetSource(const std::string& format) final {
+    if (fget_source_ != nullptr) {
+      return fget_source_(format);
+    } else {
+      return data_;
+    }
+  }
 
   const char* type_key() const {
     return type_key_.c_str();
   }
 
   void SaveToFile(const std::string& file_name,
-                const std::string& format) final {
+                  const std::string& format) final {
     std::string fmt = GetFileFormat(file_name, format);
     CHECK_EQ(fmt, fmt_)
         << "Can only save to format=" << fmt_;
     std::string meta_file = GetMetaFilePath(file_name);
     SaveMetaDataToFile(meta_file, fmap_);
-    SaveBinaryToFile(file_name, code_);
+    SaveBinaryToFile(file_name, data_);
   }
 
   void SaveToBinary(dmlc::Stream* stream) final {
     stream->Write(fmt_);
     stream->Write(fmap_);
-    stream->Write(code_);
+    stream->Write(data_);
   }
 
  private:
+  std::string data_;
+  std::string fmt_;
   std::unordered_map<std::string, FunctionInfo> fmap_;
   std::string type_key_;
+  std::function<std::string(const std::string&)> fget_source_;
 };
 
 runtime::Module DeviceSourceModuleCreate(
-    std::string code,
+    std::string data,
     std::string fmt,
     std::unordered_map<std::string, FunctionInfo> fmap,
-    std::string type_key) {
+    std::string type_key,
+    std::function<std::string(const std::string&)> fget_source) {
   std::shared_ptr<DeviceSourceModuleNode> n =
-      std::make_shared<DeviceSourceModuleNode>(code, fmt, fmap, type_key);
+      std::make_shared<DeviceSourceModuleNode>(data, fmt, fmap, type_key, fget_source);
   return runtime::Module(n);
 }
 
diff --git a/src/runtime/module.cc b/src/runtime/module.cc
index b3cdd9c95ba6..d800d6a42ee7 100644
--- a/src/runtime/module.cc
+++ b/src/runtime/module.cc
@@ -121,9 +121,9 @@ bool RuntimeEnabled(const std::string& target) {
   } else if (target == "vpi" || target == "verilog") {
     f_name = "device_api.vpi";
   } else if (target.length() >= 5 && target.substr(0, 5) == "nvptx") {
-    f_name = "codegen.build_nvptx";
+    f_name = "device_api.gpu";
   } else if (target.length() >= 4 && target.substr(0, 4) == "rocm") {
-    f_name = "codegen.build_rocm";
+    f_name = "device_api.rocm";
   } else if (target.length() >= 4 && target.substr(0, 4) == "llvm") {
     const PackedFunc* pf = runtime::Registry::Get("codegen.llvm_target_enabled");
     if (pf == nullptr) return false;
diff --git a/topi/tests/python/test_topi_l2norm.py b/topi/tests/python/test_topi_l2norm.py
index aa7970125b44..182099ff9367 100644
--- a/topi/tests/python/test_topi_l2norm.py
+++ b/topi/tests/python/test_topi_l2norm.py
@@ -41,13 +41,13 @@ def verify_l2norm(n, c, h, w, eps, axis=None):
     b_np = l2norm_instance_python(a_np, eps, axis)
 
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
             s = topi.generic.schedule_l2norm(B)
-        ctx = tvm.context(device, 0)
         a = tvm.nd.array(a_np, ctx)
         b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=dtype), ctx)
         f = tvm.build(s, [A, B], device)
diff --git a/topi/tests/python/test_topi_lrn.py b/topi/tests/python/test_topi_lrn.py
index 6c4714077e1e..596e5747a6c5 100644
--- a/topi/tests/python/test_topi_lrn.py
+++ b/topi/tests/python/test_topi_lrn.py
@@ -70,13 +70,13 @@ def verify_lrn(shape, size, axis, bias, alpha, beta):
     b_np = lrn_python(a_np, size, axis, bias, alpha, beta)
 
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
             s = topi.generic.schedule_lrn(B)
-        ctx = tvm.context(device, 0)
         a = tvm.nd.array(a_np, ctx)
         b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=dtype), ctx)
         f = tvm.build(s, [A, B], device)
diff --git a/topi/tests/python_cpp/test_topi_dense.py b/topi/tests/python_cpp/test_topi_dense.py
index 6ebd6948ee71..f2369af4319a 100644
--- a/topi/tests/python_cpp/test_topi_dense.py
+++ b/topi/tests/python_cpp/test_topi_dense.py
@@ -29,7 +29,8 @@ def get_ref_data():
     a_np, b_np, c_np, d_np = get_ref_data()
 
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
@@ -40,7 +41,6 @@ def check_device(device):
             s = topi.cpp.rocm.schedule_dense(target, [D])
         else:
             s = topi.cpp.cuda.schedule_dense(target, [D])
-        ctx = tvm.context(device, 0)
         a = tvm.nd.array(a_np, ctx)
         b = tvm.nd.array(b_np, ctx)
         c = tvm.nd.array(c_np, ctx)
diff --git a/topi/tests/python_cpp/test_topi_pooling.py b/topi/tests/python_cpp/test_topi_pooling.py
index e45b53dc0dec..ce7e65343e2c 100644
--- a/topi/tests/python_cpp/test_topi_pooling.py
+++ b/topi/tests/python_cpp/test_topi_pooling.py
@@ -48,7 +48,8 @@ def verify_pool(n, ic, ih, kh, sh, padding, pool_type, ceil_mode):
     b_np = np.maximum(b_np, 0.0)
 
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
@@ -57,7 +58,6 @@ def check_device(device):
             s = topi.cpp.generic.default_schedule(target, [B], False)
         else:
             s = topi.cpp.cuda.schedule_pool(target, [B])
-        ctx = tvm.context(device, 0)
         a = tvm.nd.array(a_np, ctx)
         b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=dtype), ctx)
         f = tvm.build(s, [A, B], device)
diff --git a/topi/tests/python_cpp/test_topi_reduce.py b/topi/tests/python_cpp/test_topi_reduce.py
index b4c630395f60..b4904d5380ca 100644
--- a/topi/tests/python_cpp/test_topi_reduce.py
+++ b/topi/tests/python_cpp/test_topi_reduce.py
@@ -46,7 +46,8 @@ def verify_reduce_map_ele(in_shape, axis, keepdims, type="sum"):
         raise NotImplementedError
 
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
@@ -56,7 +57,6 @@ def check_device(device):
         else:
             s = topi.cpp.cuda.schedule_reduce(target, [B])
 
-        ctx = tvm.context(device, 0)
         foo = tvm.build(s, [A, B], device, name="sum")
         # Test
         in_npy = np.random.uniform(size=in_shape).astype(np.float32)
diff --git a/topi/tests/python_cpp/test_topi_transform.py b/topi/tests/python_cpp/test_topi_transform.py
index 68fad8dae707..0f46be6a7b2e 100644
--- a/topi/tests/python_cpp/test_topi_transform.py
+++ b/topi/tests/python_cpp/test_topi_transform.py
@@ -7,7 +7,8 @@ def verify_expand_dims(in_shape, out_shape, axis, num_newaxis):
     A = tvm.placeholder(shape=in_shape, name="A")
     B = topi.cpp.expand_dims(A, axis, num_newaxis)
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
@@ -16,7 +17,6 @@ def check_device(device):
             s = topi.cpp.generic.schedule_injective(target, [B])
         else:
             s = topi.cpp.cuda.schedule_injective(target, [B])
-        ctx = tvm.context(device, 0)
         foo = tvm.build(s, [A, B], device, name="expand_dims")
         data_npy = np.random.uniform(size=in_shape).astype(A.dtype)
         out_npy = data_npy.reshape(out_shape)
@@ -33,7 +33,8 @@ def verify_tranpose(in_shape, axes):
     A = tvm.placeholder(shape=in_shape, name="A")
     B = topi.cpp.transpose(A, axes)
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
@@ -59,7 +60,8 @@ def verify_reshape(src_shape, dst_shape):
     A = tvm.placeholder(shape=src_shape, name="A")
     B = topi.cpp.reshape(A, dst_shape)
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
@@ -68,7 +70,6 @@ def check_device(device):
             s = topi.cpp.generic.schedule_injective(target, [B])
         else:
             s = topi.cpp.cuda.schedule_injective(target, [B])
-        ctx = tvm.context(device, 0)
         foo = tvm.build(s, [A, B], device, name="reshape")
         data_npy = np.random.normal(size=src_shape).astype(A.dtype)
         out_npy = np.reshape(data_npy, newshape=dst_shape)
@@ -85,7 +86,8 @@ def verify_squeeze(src_shape, axis):
     A = tvm.placeholder(shape=src_shape, name="A")
     B = topi.cpp.squeeze(A, axis)
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
@@ -94,7 +96,6 @@ def check_device(device):
             s = topi.cpp.generic.schedule_injective(target, [B])
         else:
             s = topi.cpp.cuda.schedule_injective(target, [B])
-        ctx = tvm.context(device, 0)
         foo = tvm.build(s, [A, B], device, name="squeeze")
         data_npy = np.random.normal(size=src_shape).astype(A.dtype)
         out_npy = np.squeeze(data_npy, axis=axis)
@@ -116,7 +117,8 @@ def verify_concatenate(shapes, axis):
         tensor_l.append(tvm.placeholder(shape, name="A" + str(i)))
     out_tensor = topi.cpp.concatenate(tensor_l, axis)
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
@@ -125,7 +127,6 @@ def check_device(device):
             s = topi.cpp.generic.schedule_injective(target, [out_tensor])
         else:
             s = topi.cpp.cuda.schedule_injective(target, [out_tensor])
-        ctx = tvm.context(device, 0)
         foo = tvm.build(s, tensor_l + [out_tensor], device, name="concatenate")
         data_npys = [np.random.normal(size=shape).astype(tensor_l[0].dtype) for shape in shapes]
         out_npy = np.concatenate(data_npys, axis=axis)
@@ -143,7 +144,8 @@ def verify_split(src_shape, indices_or_sections, axis):
     tensor_l = topi.cpp.split(A, indices_or_sections, axis)
     tensor_l = list(tensor_l)
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)

From 7233ce1671b7838e88396d09f8e264ffd32dbdfb Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 10 May 2018 20:24:35 -0700
Subject: [PATCH 298/949] [APP] ROCM RPC (#1155)

---
 apps/rocm_rpc/Makefile             | 23 +++++++++++++++++
 apps/rocm_rpc/README.md            | 41 ++++++++++++++++++++++++++++++
 apps/rocm_rpc/rocm_runtime_pack.cc | 15 +++++++++++
 apps/rocm_rpc/start_rpc_server.sh  |  5 ++++
 python/tvm/exec/rpc_server.py      |  2 ++
 5 files changed, 86 insertions(+)
 create mode 100644 apps/rocm_rpc/Makefile
 create mode 100644 apps/rocm_rpc/README.md
 create mode 100644 apps/rocm_rpc/rocm_runtime_pack.cc
 create mode 100755 apps/rocm_rpc/start_rpc_server.sh

diff --git a/apps/rocm_rpc/Makefile b/apps/rocm_rpc/Makefile
new file mode 100644
index 000000000000..b4e527980941
--- /dev/null
+++ b/apps/rocm_rpc/Makefile
@@ -0,0 +1,23 @@
+# Makefile Example to deploy TVM modules.
+ROCM_PATH=/opt/rocm
+
+TVM_ROOT=$(shell cd ../..; pwd)
+NNVM_PATH=nnvm
+DMLC_CORE=${TVM_ROOT}/dmlc-core
+
+PKG_CFLAGS = -std=c++11 -O2 -fPIC\
+	-I${TVM_ROOT}/include\
+	-I${DMLC_CORE}/include\
+	-I${TVM_ROOT}/dlpack/include\
+	-I${ROCM_PATH}/include
+
+PKG_LDFLAGS = -L${ROCM_PATH}/lib -L${TVM_ROOT}/lib -ldl -lpthread -lhip_hcc -lMIOpen
+
+.PHONY: clean all
+
+all: lib/libtvm_runtime_rocm.so
+
+# Build rule for all in one TVM package library
+lib/libtvm_runtime_rocm.so: rocm_runtime_pack.cc
+	@mkdir -p $(@D)
+	$(CXX) $(PKG_CFLAGS) -shared -o $@ $(filter %.cc %.o %.a, $^) $(PKG_LDFLAGS)
diff --git a/apps/rocm_rpc/README.md b/apps/rocm_rpc/README.md
new file mode 100644
index 000000000000..d4ebc1334975
--- /dev/null
+++ b/apps/rocm_rpc/README.md
@@ -0,0 +1,41 @@
+# TVM ROCm RPC
+
+This folder contains a simple recipe to make RPC work together with ROCm.TVM's RPC server relies on process
+fork to create a new process for each incoming session.
+Like CUDA, opencl driver, the runtime ROCm runtime is not fork-safe.
+A typical CUDA or opencl driver will initialize lazily
+and we can use normal TVM RPC server because we won't touch the driver API before we fork a new session.
+However, the current ROCm runtime eagerly initialize during startup and will directly cause error during fork.
+This folder provides a workaround to this problem.
+
+## Usage
+- Build tvm **without** rocm (it is important to exclude rocm from runtime)
+- Modify the ROCM_PATH to be the correct path the current [Makefile](Makefile)
+- Type make to build lib/libtvm_runtime_rocm.so, which is a standalone dll module
+- Use [start_rpc_server.sh](start_rpc_server.sh) to start the RPC server
+
+## How it works
+- The RPC server starts without ROCm dependency.
+- lib/libtvm_runtim_rocm.so is dynamically loaded only after the fork.
+
+## Note
+With ROCm RPC, we can build AMDGPU program from a machine without AMD GPU
+and remotely upload and execute on a AMDGPU machine.
+Please note that you will need to set the gfx version correctly(via ```-model```` or ```-mcpu```)
+because we can no longer query the GPU version dynamically during runtime.
+
+
+```python
+import tvm
+from tvm.contrib import rpc
+
+# set mcpu explicitly to be the gpu version.
+target = "rocm -mcpu=gfx900"
+remote = rpc.connect(server_host, server_port)
+mod = tvm.build(s, args, target)
+mod.export_library("mylib.so")
+
+remote.upload("mylib.so")
+foo = remote.load_module("mylib.so")
+# same as normal RPC
+```
diff --git a/apps/rocm_rpc/rocm_runtime_pack.cc b/apps/rocm_rpc/rocm_runtime_pack.cc
new file mode 100644
index 000000000000..174d9f0a8270
--- /dev/null
+++ b/apps/rocm_rpc/rocm_runtime_pack.cc
@@ -0,0 +1,15 @@
+/*!
+ * \brief This is an all in one file for ROCM runtime library.
+ *
+ * This is used to create a RPC module library that can be
+ * safely passed to rocm
+ */
+
+#define TVM_ROCM_RUNTIME 1
+#define TVM_USE_MIOPEN 1
+#define __HIP_PLATFORM_HCC__ 1
+
+#include "../../src/runtime/rocm/rocm_device_api.cc"
+#include "../../src/runtime/rocm/rocm_module.cc"
+#include "../../src/contrib/miopen/conv_forward.cc"
+#include "../../src/contrib/miopen/miopen_utils.cc"
diff --git a/apps/rocm_rpc/start_rpc_server.sh b/apps/rocm_rpc/start_rpc_server.sh
new file mode 100755
index 000000000000..e082d9d63ee6
--- /dev/null
+++ b/apps/rocm_rpc/start_rpc_server.sh
@@ -0,0 +1,5 @@
+#/bin/bash
+PROJ_ROOT=$(realpath $(dirname "$0")/../..)
+export PYTHONPATH=${PROJ_ROOT}/python:${PYTHONPATH}
+
+python -m tvm.exec.rpc_server "$@" --load-library=${PROJ_ROOT}/apps/rocm_rpc/lib/libtvm_runtime_rocm.so
diff --git a/python/tvm/exec/rpc_server.py b/python/tvm/exec/rpc_server.py
index 84bad0f5422e..46e7877a2803 100644
--- a/python/tvm/exec/rpc_server.py
+++ b/python/tvm/exec/rpc_server.py
@@ -2,6 +2,7 @@
 from __future__ import absolute_import
 
 import argparse
+import logging
 from ..contrib import rpc
 
 def main():
@@ -31,6 +32,7 @@ def main():
     else:
         tracker_addr = None
 
+    logging.basicConfig(level=logging.INFO)
     server = rpc.Server(args.host,
                         args.port,
                         args.port_end,

From 469962c4063474fc8e1e1e4fb7283f7b3ea969c7 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 10 May 2018 20:38:37 -0700
Subject: [PATCH 299/949] Update README.md

---
 apps/rocm_rpc/README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/apps/rocm_rpc/README.md b/apps/rocm_rpc/README.md
index d4ebc1334975..70ce9780a31d 100644
--- a/apps/rocm_rpc/README.md
+++ b/apps/rocm_rpc/README.md
@@ -21,7 +21,7 @@ This folder provides a workaround to this problem.
 ## Note
 With ROCm RPC, we can build AMDGPU program from a machine without AMD GPU
 and remotely upload and execute on a AMDGPU machine.
-Please note that you will need to set the gfx version correctly(via ```-model```` or ```-mcpu```)
+Please note that you will need to set the gfx version correctly(via ```-model``` or ```-mcpu```)
 because we can no longer query the GPU version dynamically during runtime.
 
 

From f4e5ba8986b3b7bf709095cf454ec960ab3c9d8d Mon Sep 17 00:00:00 2001
From: Yida Wang <yidawa@gmail.com>
Date: Fri, 11 May 2018 15:38:54 -0700
Subject: [PATCH 300/949] make ThreadPool thread local (#1152)

* make ThreadPool thread local

* add return
---
 src/runtime/thread_pool.cc | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/runtime/thread_pool.cc b/src/runtime/thread_pool.cc
index 71052626ae5a..b7ea6f5b81d3 100644
--- a/src/runtime/thread_pool.cc
+++ b/src/runtime/thread_pool.cc
@@ -291,9 +291,8 @@ class ThreadPool {
     return res;
   }
 
-  static ThreadPool* Global() {
-    static ThreadPool inst;
-    return &inst;
+  static ThreadPool* ThreadLocal() {
+    return dmlc::ThreadLocalStore<ThreadPool>::Get();
   }
 
  private:
@@ -331,7 +330,7 @@ int TVMBackendParallelLaunch(
     FTVMParallelLambda flambda,
     void* cdata,
     int num_task) {
-  int res = tvm::runtime::ThreadPool::Global()->Launch(
+  int res = tvm::runtime::ThreadPool::ThreadLocal()->Launch(
       flambda, cdata, num_task, 1);
   return res;
 }

From dcabd6a4ad00079d3aaabc536023ddf999f20ddd Mon Sep 17 00:00:00 2001
From: Yao Wang <kevinthesunwy@gmail.com>
Date: Sat, 12 May 2018 08:56:35 -0700
Subject: [PATCH 301/949] Add ssd op with ir builder (#1095)

---
 Jenkinsfile                             |   1 +
 Makefile                                |   1 +
 make/config.mk                          |   3 +
 make/contrib/sort.mk                    |   6 +
 python/tvm/api.py                       |  39 ++-
 src/contrib/sort/sort.cc                |  93 +++++++
 tests/python/contrib/test_sort.py       |  62 +++++
 topi/python/topi/cuda/vision.py         |  36 ++-
 topi/python/topi/generic/vision.py      |  70 ++++++
 topi/python/topi/nn/util.py             |   6 +-
 topi/python/topi/vision/__init__.py     |   3 +-
 topi/python/topi/vision/nms.py          | 191 +++++++++++++++
 topi/python/topi/vision/ssd/__init__.py |   5 +
 topi/python/topi/vision/ssd/multibox.py | 307 ++++++++++++++++++++++++
 topi/tests/python/test_topi_vision.py   | 151 ++++++++++++
 15 files changed, 963 insertions(+), 11 deletions(-)
 create mode 100644 make/contrib/sort.mk
 create mode 100644 src/contrib/sort/sort.cc
 create mode 100644 tests/python/contrib/test_sort.py
 create mode 100644 topi/python/topi/vision/nms.py
 create mode 100644 topi/python/topi/vision/ssd/__init__.py
 create mode 100644 topi/python/topi/vision/ssd/multibox.py
 create mode 100644 topi/tests/python/test_topi_vision.py

diff --git a/Jenkinsfile b/Jenkinsfile
index 0f1e937a02ae..13636d108379 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -126,6 +126,7 @@ stage('Build') {
            echo USE_SORT=1 >> config.mk
            echo USE_OPENGL=1 >> config.mk
            echo LLVM_CONFIG=llvm-config-4.0 >> config.mk
+           echo USE_SORT=1 >> config.mk
            """
         make('cpu', '-j2')
         pack_lib('cpu', tvm_lib)
diff --git a/Makefile b/Makefile
index 02017b656906..ef4c7b1e91ab 100644
--- a/Makefile
+++ b/Makefile
@@ -201,6 +201,7 @@ ifeq ($(USE_GRAPH_RUNTIME_DEBUG), 1)
 endif
 
 include make/contrib/cblas.mk
+include make/contrib/sort.mk
 include make/contrib/random.mk
 include make/contrib/nnpack.mk
 include make/contrib/cudnn.mk
diff --git a/make/config.mk b/make/config.mk
index e5b0219ec205..01ab0c4e8a70 100644
--- a/make/config.mk
+++ b/make/config.mk
@@ -90,3 +90,6 @@ USE_CUBLAS = 0
 
 # Whether use rocBlas
 USE_ROCBLAS = 0
+
+# Whether use contrib sort
+USE_SORT = 0
diff --git a/make/contrib/sort.mk b/make/contrib/sort.mk
new file mode 100644
index 000000000000..40afd1c581dc
--- /dev/null
+++ b/make/contrib/sort.mk
@@ -0,0 +1,6 @@
+SORT_CONTRIB_SRC = $(wildcard src/contrib/sort/*.cc)
+SORT_CONTRIB_OBJ = $(patsubst src/%.cc, build/%.o, $(SORT_CONTRIB_SRC))
+
+ifeq ($(USE_SORT), 1)
+    RUNTIME_DEP += $(SORT_CONTRIB_OBJ)
+endif
diff --git a/python/tvm/api.py b/python/tvm/api.py
index b827cce72896..71ae62a3b222 100644
--- a/python/tvm/api.py
+++ b/python/tvm/api.py
@@ -300,7 +300,14 @@ def scan(init, update, state_placeholder, inputs=None, name="scan", tag=""):
     return res[0] if len(res) == 1 else res
 
 
-def extern(shape, inputs, fcompute, name="extern", dtype=None, tag=""):
+def extern(shape,
+           inputs,
+           fcompute,
+           name="extern",
+           dtype=None,
+           in_buffers=None,
+           out_buffers=None,
+           tag=""):
     """Compute several tensor via extern function.
 
     Parameters
@@ -332,6 +339,12 @@ def extern(shape, inputs, fcompute, name="extern", dtype=None, tag=""):
         The data types of outputs,
         by default dtype will be same as inputs.
 
+    in_buffers: Buffer or list of Buffer, optional
+        Input buffers.
+
+    out_buffers: Buffer or list of Buffers, optional
+        Output buffers.
+
     Returns
     -------
     tensor: Tensor or list of Tensors
@@ -357,14 +370,25 @@ def extern(shape, inputs, fcompute, name="extern", dtype=None, tag=""):
         tag = _tag.TagScope.current.tag
     shape = (shape,) if isinstance(shape, (_expr.Expr, _Integral)) else shape
     shape = [shape] if isinstance(shape[0], (_expr.Expr, _Integral)) else shape
-    input_placeholders = []
-    output_placeholders = []
+    if in_buffers is not None:
+        in_buffers = [in_buffers] if not isinstance(in_buffers, list) else in_buffers
+        if len(inputs) != len(in_buffers):
+            raise RuntimeError("Number of inputs and in_buffers mismatch: %d vs %d."
+                               % (len(inputs), len(in_buffers)))
+    if out_buffers is not None:
+        out_buffers = [out_buffers] if not isinstance(out_buffers, list) else out_buffers
+        if len(shape) != len(out_buffers):
+            raise RuntimeError("Number of outputs and out_buffers mismatch: %d vs %d."
+                               % (len(shape), len(out_buffers)))
+    input_placeholders = in_buffers or []
+    output_placeholders = out_buffers or []
     types = set()
     for t in inputs:
         if not isinstance(t, _tensor.Tensor):
             raise ValueError("expect inputs to be tensor")
-        input_placeholders.append(
-            decl_buffer(t.shape, t.dtype, t.op.name))
+        if in_buffers is None:
+            input_placeholders.append(
+                decl_buffer(t.shape, t.dtype, t.op.name))
         types.add(t.dtype)
 
     if dtype is None:
@@ -375,8 +399,9 @@ def extern(shape, inputs, fcompute, name="extern", dtype=None, tag=""):
     if isinstance(dtype, str):
         dtype = [dtype]
 
-    for shp, dt in zip(shape, dtype):
-        output_placeholders.append(decl_buffer(shp, dt, name))
+    if out_buffers is None:
+        for shp, dt in zip(shape, dtype):
+            output_placeholders.append(decl_buffer(shp, dt, name))
     body = fcompute(input_placeholders, output_placeholders)
     if isinstance(body, _expr.Expr):
         body = _make.Evaluate(body)
diff --git a/src/contrib/sort/sort.cc b/src/contrib/sort/sort.cc
new file mode 100644
index 000000000000..ced5021fd1aa
--- /dev/null
+++ b/src/contrib/sort/sort.cc
@@ -0,0 +1,93 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file Use standard C library call.
+ */
+
+#include <tvm/runtime/registry.h>
+#include <tvm/runtime/util.h>
+#include <dlpack/dlpack.h>
+#include <algorithm>
+#include <vector>
+
+namespace tvm {
+namespace contrib {
+
+using namespace runtime;
+
+template<typename DType>
+bool CompareAscend(const std::pair<int32_t, DType>& lhs,
+                   const std::pair<int32_t, DType>& rhs) {
+  return lhs.second < rhs.second;
+}
+
+template<typename DType>
+bool CompareDescend(const std::pair<int32_t, DType>& lhs,
+                    const std::pair<int32_t, DType>& rhs) {
+  return lhs.second > rhs.second;
+}
+
+
+// Argsort implemented C library sort.
+// Return indices of sorted tensor.
+// By default, the last axis will be used to sort.
+// sort_num specify the number of elements to be sorted.
+// If input tensor has dimension (d0, d1, ..., d(k-1), dk, d(k+1), ..., d(n-1))
+// and sort axis is dk. sort_num should have dimension of
+// (d1, d2, ..., d(k-1), d(k+1), ..., dn).
+TVM_REGISTER_GLOBAL("tvm.contrib.sort.argsort")
+.set_body([](TVMArgs args, TVMRetValue *ret) {
+  DLTensor *input = args[0];
+  DLTensor *sort_num = args[1];
+  DLTensor *output = args[2];
+  int32_t axis = args[3];
+  bool is_descend = args[4];
+
+  auto dtype = input->dtype;
+  auto data_ptr = static_cast<float *>(input->data);
+  auto sort_num_ptr = static_cast<int32_t *>(sort_num->data);
+  std::vector<std::pair<int32_t, float>> sorter;
+  int64_t axis_mul_before = 1;
+  int64_t axis_mul_after = 1;
+
+  if (axis < 0) {
+    axis = input->ndim + axis;
+  }
+
+  // Currently only supports input dtype to be float32.
+  CHECK_EQ(dtype.code, 2) << "Currently only supports input dtype "
+      "to be float32.";
+  CHECK_EQ(dtype.bits, 32) << "Currently only supports input dtype "
+      "to be float32.";
+  CHECK_LT(axis, input->ndim) << "Axis out of boundary for "
+      "input ndim " << input->ndim;
+
+  for (int i = 0; i < input->ndim; ++i) {
+    if (i < axis) {
+      axis_mul_before *= input->shape[i];
+    } else if (i > axis) {
+      axis_mul_after *= input->shape[i];
+    }
+  }
+
+  for (int64_t i = 0 ; i < axis_mul_before; ++i) {
+    for (int64_t j = 0 ; j < axis_mul_after; ++j) {
+      sorter.clear();
+      int32_t current_sort_num = *(sort_num_ptr + i * axis_mul_after + j);
+      int64_t base_idx = i * input->shape[axis] * axis_mul_after + j;
+      for (int64_t k = 0; k < current_sort_num; ++k) {
+        int64_t full_idx = base_idx + k * axis_mul_after;
+        sorter.emplace_back(std::make_pair(k, *(data_ptr + full_idx)));
+      }
+      std::stable_sort(sorter.begin(), sorter.end(),
+                       is_descend ? CompareDescend<float>
+                                  : CompareAscend<float>);
+      for (int32_t k = 0; k < input->shape[axis]; ++k) {
+        *(static_cast<int32_t *>(output->data) + base_idx + k * axis_mul_after)
+          = k < sorter.size() ? sorter[k].first : k;
+      }
+    }
+  }
+});
+
+}  // namespace contrib
+}  // namespace tvm
diff --git a/tests/python/contrib/test_sort.py b/tests/python/contrib/test_sort.py
new file mode 100644
index 000000000000..3a99779e58f0
--- /dev/null
+++ b/tests/python/contrib/test_sort.py
@@ -0,0 +1,62 @@
+import tvm
+import numpy as np
+
+def test_sort():
+    n = 2
+    l = 5
+    m = 3
+    data = tvm.placeholder((n, l, m), name='data')
+    sort_num = tvm.placeholder((n, m), name="sort_num", dtype="int32")
+    axis = 1
+    is_descend = True
+    out = tvm.extern(data.shape, [data, sort_num],
+                     lambda ins, outs: tvm.call_packed(
+                         "tvm.contrib.sort.argsort", ins[0],
+                         ins[1], outs[0], axis, is_descend),
+                     dtype='int32', name="sort_tensor")
+    input = [[[1, 2, 3], [2, 4.5, 3.5], [1.1, 0.5, 1], [3.2, -5, 0.5], [1.5, 0, 0]],
+             [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [13, 14, 15]]]
+    sort_num_input = [[1, 2, 3], [4, 5, 5]]
+    sorted_index = [[[0, 1, 1], [1, 0, 0], [2, 2, 2], [3, 3, 3], [4, 4, 4]],
+                    [[3, 4, 4], [2, 3, 3], [1, 2, 2], [0, 1, 1], [4, 0, 0]]]
+
+    ctx = tvm.cpu(0)
+    target = "llvm"
+    s = tvm.create_schedule(out.op)
+    f = tvm.build(s, [data, sort_num, out], target)
+    a = tvm.nd.array(np.array(input).astype(data.dtype), ctx)
+    b = tvm.nd.array(np.array(sort_num_input).astype(sort_num.dtype), ctx)
+    c = tvm.nd.array(np.zeros(a.shape, dtype=out.dtype), ctx)
+    f(a, b, c)
+    np.testing.assert_allclose(c.asnumpy(), np.array(sorted_index).astype(out.dtype), rtol=1e-5)
+
+def test_sort_np():
+    dshape = (1, 2, 3, 4, 5, 6)
+    axis = 4
+    reduced_shape = (1, 2, 3, 4, 6)
+    is_descend = False
+    data = tvm.placeholder(dshape, name='data')
+    sort_num = tvm.placeholder(reduced_shape, name="sort_num", dtype="int32")
+    out = tvm.extern(data.shape, [data, sort_num],
+                     lambda ins, outs: tvm.call_packed(
+                         "tvm.contrib.sort.argsort", ins[0],
+                         ins[1], outs[0], axis, is_descend),
+                     dtype='int32', name="sort_tensor")
+
+    ctx = tvm.cpu(0)
+    target = "llvm"
+    s = tvm.create_schedule(out.op)
+    f = tvm.build(s, [data, sort_num, out], target)
+
+    np_data = np.random.uniform(size=dshape)
+    np_out = np.argsort(np_data, axis=axis)
+    sort_num_input = np.full(reduced_shape, dshape[axis])
+    a = tvm.nd.array(np.array(np_data).astype(data.dtype), ctx)
+    b = tvm.nd.array(np.array(sort_num_input).astype(sort_num.dtype), ctx)
+    c = tvm.nd.array(np.zeros(a.shape, dtype=out.dtype), ctx)
+    f(a, b, c)
+    np.testing.assert_allclose(c.asnumpy(), np_out, rtol=1e-5)
+
+if __name__ == "__main__":
+    test_sort()
+    test_sort_np()
diff --git a/topi/python/topi/cuda/vision.py b/topi/python/topi/cuda/vision.py
index e946edca619a..106d13665ad8 100644
--- a/topi/python/topi/cuda/vision.py
+++ b/topi/python/topi/cuda/vision.py
@@ -1,4 +1,4 @@
-# pylint: disable=invalid-name, unused-variable
+# pylint: disable=invalid-name, unused-variable, unused-argument
 """Schedule for vision operators"""
 from __future__ import absolute_import as _abs
 import tvm
@@ -40,3 +40,37 @@ def schedule_region(outs):
     target = tvm.target.current_target(allow_none=False)
     cpp_target = cpp.TEST_create_target(target.target_name)
     return cpp.cuda.schedule_region(cpp_target, outs)
+
+@generic.schedule_multibox_prior.register(["cuda", "gpu"])
+def schedule_multibox_prior(out):
+    """Schedule for multibox_prior operator.
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+        The computation graph description of multibox_prior
+        in the format of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+        The computation schedule for multibox_prior.
+    """
+    raise RuntimeError("Currently multibox_prior only supports CPU.")
+
+@generic.schedule_multibox_detection.register(["cuda", "gpu"])
+def schedule_multibox_detection(out):
+    """Schedule for multibox_detection operator.
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+        The computation graph description of multibox_detection
+        in the format of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+        The computation schedule for multibox_detection.
+    """
+    raise RuntimeError("Currently multibox_detection only supports CPU.")
diff --git a/topi/python/topi/generic/vision.py b/topi/python/topi/generic/vision.py
index a162c0ee2de9..75f32ce19cbe 100644
--- a/topi/python/topi/generic/vision.py
+++ b/topi/python/topi/generic/vision.py
@@ -1,3 +1,4 @@
+# pylint: disable=invalid-name, no-member
 """Generic vision operators"""
 from __future__ import absolute_import as _abs
 import tvm
@@ -70,3 +71,72 @@ def schedule_region(outs):
     target = tvm.target.current_target(allow_none=False)
     cpp_target = cpp.TEST_create_target(target.target_name)
     return cpp.generic.default_schedule(cpp_target, outs, False)
+
+@tvm.target.generic_func
+def schedule_nms(outs):
+    """Schedule for non-maximum suppression
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+      The computation graph description of nms
+      in the format of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+      The computation schedule for the op.
+    """
+    return _default_schedule(outs, False)
+
+@tvm.target.generic_func
+def schedule_multibox_prior(outs):
+    """Schedule for multibox_prior
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+      The computation graph description of multibox_prior
+      in the format of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+      The computation schedule for the op.
+    """
+    return _default_schedule(outs, False)
+
+@tvm.target.generic_func
+def schedule_multibox_transform_loc(outs):
+    """Schedule for multibox_transform_loc
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+      The computation graph description of
+      multibox_transform_loc in the format
+      of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+      The computation schedule for the op.
+    """
+    return _default_schedule(outs, False)
+
+@tvm.target.generic_func
+def schedule_multibox_detection(outs):
+    """Schedule for multibox_detection
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+      The computation graph description of multibox_detection
+      in the format of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+      The computation schedule for the op.
+    """
+    return _default_schedule(outs, False)
diff --git a/topi/python/topi/nn/util.py b/topi/python/topi/nn/util.py
index c18cabd32dc0..6264ced76953 100644
--- a/topi/python/topi/nn/util.py
+++ b/topi/python/topi/nn/util.py
@@ -1,6 +1,8 @@
 # pylint: disable=invalid-name, unused-variable
 """NN operator common utilities"""
 from __future__ import absolute_import
+
+import tvm
 from ..util import get_const_int
 
 def infer_pad(data, data_pad):
@@ -53,8 +55,8 @@ def infer_stride(data, kernel, out):
     _, _, IH, IW = data.shape
     _, _, KH, KW = kernel.shape
     _, _, OH, OW = out.shape
-    hstride = (IH - KH) // (OH - 1)
-    wstride = (IW - KW) // (OW - 1)
+    hstride = (IH - KH) // tvm.make.Max(OH - 1, 1) + tvm.select(OH == 1, 1, 0)
+    wstride = (IW - KW) // tvm.make.Max(OW - 1, 1) + tvm.select(OW == 1, 1, 0)
     return get_const_int(hstride), get_const_int(wstride)
 
 
diff --git a/topi/python/topi/vision/__init__.py b/topi/python/topi/vision/__init__.py
index 0f164fdafc2b..9b6d9bf5e3dc 100644
--- a/topi/python/topi/vision/__init__.py
+++ b/topi/python/topi/vision/__init__.py
@@ -2,6 +2,7 @@
 """VISION network operators"""
 from __future__ import absolute_import as _abs
 
-from . import yolo2
+from . import yolo2, ssd
 from .shortcut import *
 from .reorg import *
+from .nms import *
diff --git a/topi/python/topi/vision/nms.py b/topi/python/topi/vision/nms.py
new file mode 100644
index 000000000000..71431dd01f2c
--- /dev/null
+++ b/topi/python/topi/vision/nms.py
@@ -0,0 +1,191 @@
+# pylint: disable=invalid-name, no-member, too-many-locals, too-many-arguments
+"""Non-maximum suppression operator"""
+import tvm
+
+from tvm import api
+
+def nms_ir(data, sort_result, valid_count, out, nms_threshold, force_suppress, nms_topk):
+    """Low level IR routing for transform location in multibox_detection operator.
+
+    Parameters
+    ----------
+    data: Buffer
+        Buffer of output boxes with class and score.
+
+    sort_result : Buffer
+        Buffer of output box indexes sorted by score.
+
+    valid_count : Buffer
+        Buffer of number of valid output boxes.
+
+    out : Buffer
+        Output buffer.
+
+    nms_threshold : float
+        Non-maximum suppression threshold.
+
+    force_suppress : boolean
+        Whether to suppress all detections regardless of class_id.
+
+    nms_topk : int
+        Keep maximum top k detections before nms, -1 for no limit.
+
+    Returns
+    -------
+    stmt : Stmt
+        The result IR statement.
+    """
+    def calculate_overlap(out_tensor, box_a_idx, box_b_idx):
+        """Calculate overlap of two boxes.
+        """
+        w = tvm.make.Max(0.0, tvm.make.Min(out_tensor[box_a_idx + 2], out_tensor[box_b_idx + 2])
+                         - tvm.make.Max(out_tensor[box_a_idx], out_tensor[box_b_idx]))
+        h = tvm.make.Max(0.0, tvm.make.Min(out_tensor[box_a_idx + 3], out_tensor[box_b_idx + 3])
+                         - tvm.make.Max(out_tensor[box_a_idx + 1], out_tensor[box_b_idx + 1]))
+        i = w * h
+        u = (out_tensor[box_a_idx + 2] - out_tensor[box_a_idx]) * \
+            (out_tensor[box_a_idx + 3] - out_tensor[box_a_idx + 1]) + \
+            (out_tensor[box_b_idx + 2] - out_tensor[box_b_idx]) * \
+            (out_tensor[box_b_idx + 3] - out_tensor[box_b_idx + 1]) - i
+        return tvm.select(u <= 0.0, 0.0, i / u)
+
+    ib = tvm.ir_builder.create()
+    p_data = ib.buffer_ptr(data)
+    p_sort_result = ib.buffer_ptr(sort_result)
+    p_valid_count = ib.buffer_ptr(valid_count)
+    p_out = ib.buffer_ptr(out)
+    batch_size = out.shape[0]
+    num_anchors = out.shape[1]
+
+    nms_threshold_node = tvm.make.node("FloatImm", dtype="float32", value=nms_threshold)
+    nms_topk_node = tvm.make.node("IntImm", dtype="int32", value=nms_topk)
+    force_suppress_node = tvm.make.node("IntImm", dtype="int32", value=1 if force_suppress else 0)
+    with ib.for_range(0, batch_size, for_type="parallel", name="n") as n:
+        with ib.if_scope(tvm.all(nms_threshold_node > 0, nms_threshold_node < 1,
+                                 p_valid_count[0] > 0)):
+            # Reorder output
+            nkeep = tvm.select(tvm.all(nms_topk_node > 0, nms_topk < p_valid_count[n]),
+                               nms_topk, p_valid_count[n])
+            with ib.for_range(0, nkeep, name="l") as l:
+                with ib.for_range(0, 6, name="m") as m:
+                    p_out[(n * num_anchors * 6
+                           + l * 6 + m)] = p_data[(n * num_anchors * 6
+                                                   + p_sort_result[n * num_anchors + l] * 6 + m)]
+            with ib.if_scope(tvm.all(nms_topk_node > 0, nms_topk < p_valid_count[n])):
+                with ib.for_range(0, p_valid_count[n] - nkeep, name="l") as l:
+                    with ib.for_range(0, 6, name="m") as m:
+                        p_out[(n * num_anchors * 6
+                               + (l + nkeep) * 6 + m)] = p_data[(n * num_anchors * 6
+                                                                 + (l + nkeep) * 6 + m)]
+            # Apply nms
+            with ib.for_range(0, p_valid_count[n], name="l") as l:
+                offset_l = l * 6
+                with ib.if_scope(p_out[n * num_anchors * 6 + offset_l] >= 0):
+                    with ib.for_range(0, p_valid_count[n], name="m") as m:
+                        offset_m = m * 6
+                        with ib.if_scope(tvm.all(m > l, p_out[n * num_anchors * 6
+                                                              + offset_m] >= 0)):
+                            with ib.if_scope(tvm.any(force_suppress_node > 0,
+                                                     p_out[n * num_anchors * 6 + offset_l] ==
+                                                     p_out[n * num_anchors * 6 + offset_m])):
+                                # When force_suppress == True or class_id equals
+                                iou = calculate_overlap(p_out, n * num_anchors * 6 + offset_l + 2,
+                                                        n * num_anchors * 6 + offset_m + 2)
+                                with ib.if_scope(iou >= nms_threshold):
+                                    p_out[n * num_anchors * 6 + offset_m] = -1.0
+        with ib.else_scope():
+            with ib.for_range(0, p_valid_count[n], name="l") as l:
+                with ib.for_range(0, 6, name="m") as m:
+                    p_out[(n * num_anchors * 6
+                           + l * 6 + m)] = p_data[n * num_anchors * 6 + l * 6 + m]
+        # Set invalid entry to be -1
+        with ib.for_range(0, num_anchors - p_valid_count[n], name="l") as l:
+            with ib.for_range(0, 6, name="m") as m:
+                p_out[n * num_anchors * 6 + (l + p_valid_count[n]) * 6 + m] = -1.0
+    return ib.get()
+
+
+@tvm.target.generic_func
+def nms(data, valid_count, nms_threshold=0.5, force_suppress=False, nms_topk=-1):
+    """Non-maximum suppression operator for object detection.
+
+    Parameters
+    ----------
+    data: tvm.Tensor
+        3-D tensor with shape [batch_size, num_anchors, 6].
+        The last dimension should be in format of
+        [class_id, score, box_left, box_top, box_right, box_bottom].
+
+    valid_count : tvm.Tensor
+        1-D tensor for valid number of boxes.
+
+    nms_threshold : float
+        Non-maximum suppression threshold.
+
+    force_suppress : boolean
+        Whether to suppress all detections regardless of class_id.
+
+    nms_topk : int
+        Keep maximum top k detections before nms, -1 for no limit.
+
+    Returns
+    -------
+    out : tvm.Tensor
+        3-D tensor with shape [batch_size, num_anchors, 6].
+
+    Example
+    --------
+    .. code-block:: python
+
+        # An example to use nms
+        dshape = (1, 5, 6)
+        data = tvm.placeholder(dshape, name="data")
+        valid_count = tvm.placeholder((dshape[0],), dtype="int32", name="valid_count")
+        nms_threshold = 0.7
+        force_suppress = True
+        nms_topk = -1
+        out = nms(data, valid_count, nms_threshold, force_suppress, nms_topk)
+        np_data = np.random.uniform(dshape)
+        np_valid_count = np.array([4])
+        s = topi.generic.schedule_nms(out)
+        f = tvm.build(s, [data, valid_count, out], "llvm")
+        ctx = tvm.cpu()
+        tvm_data = tvm.nd.array(np_data, ctx)
+        tvm_valid_count = tvm.nd.array(np_valid_count, ctx)
+        tvm_out = tvm.nd.array(np.zeros(dshape, dtype=data.dtype), ctx)
+        f(tvm_data, tvm_valid_count, tvm_out)
+    """
+    batch_size = data.shape[0]
+    num_anchors = data.shape[1]
+    valid_count_dtype = "int32"
+    valid_count_buf = api.decl_buffer(valid_count.shape, valid_count_dtype,
+                                      "valid_count_buf", data_alignment=4)
+    data_buf = api.decl_buffer(data.shape, data.dtype, "data_buf", data_alignment=8)
+    score_axis = 1
+    score_shape = (batch_size, num_anchors)
+    score_tensor = tvm.compute(score_shape, lambda i, j: data[i, j, score_axis])
+    score_tensor_buf = api.decl_buffer(score_tensor.shape, data.dtype,
+                                       "score_tensor_buf", data_alignment=8)
+    sort_tensor_dtype = "int32"
+    sort_tensor_buf = api.decl_buffer(score_shape, sort_tensor_dtype,
+                                      "sort_tensor_buf", data_alignment=8)
+    sort_tensor = \
+        tvm.extern(score_shape,
+                   [score_tensor, valid_count],
+                   lambda ins, outs: tvm.call_packed(
+                       "tvm.contrib.sort.argsort", ins[0], ins[1],
+                       outs[0], score_axis, True),
+                   dtype=sort_tensor_dtype,
+                   in_buffers=[score_tensor_buf, valid_count_buf],
+                   out_buffers=sort_tensor_buf,
+                   name="nms_sort")
+    out = \
+        tvm.extern(data.shape,
+                   [data, sort_tensor, valid_count],
+                   lambda ins, outs: nms_ir(
+                       ins[0], ins[1], ins[2], outs[0], nms_threshold,
+                       force_suppress, nms_topk),
+                   dtype="float32",
+                   in_buffers=[data_buf, sort_tensor_buf, valid_count_buf],
+                   tag="nms")
+    return out
diff --git a/topi/python/topi/vision/ssd/__init__.py b/topi/python/topi/vision/ssd/__init__.py
new file mode 100644
index 000000000000..d680c578e7aa
--- /dev/null
+++ b/topi/python/topi/vision/ssd/__init__.py
@@ -0,0 +1,5 @@
+# pylint: disable=wildcard-import
+"""VISION network operators"""
+from __future__ import absolute_import as _abs
+
+from .multibox import *
diff --git a/topi/python/topi/vision/ssd/multibox.py b/topi/python/topi/vision/ssd/multibox.py
new file mode 100644
index 000000000000..31a090e345d5
--- /dev/null
+++ b/topi/python/topi/vision/ssd/multibox.py
@@ -0,0 +1,307 @@
+# pylint: disable=invalid-name, no-member, too-many-locals, too-many-arguments
+"""SSD multibox operators"""
+from __future__ import absolute_import as _abs
+import math
+import tvm
+
+from tvm import api
+
+import topi
+
+from ..nms import nms
+
+def multibox_prior_ir(data, out, sizes, ratios, steps, offsets):
+    """Low level IR routing for multibox_prior operator.
+
+    Parameters
+    ----------
+    data : Buffer
+        Input data buffer.
+
+    out : Buffer
+        Output buffer.
+
+    sizes : tuple of float
+        Tuple of sizes for anchor boxes.
+
+    ratios : tuple of float
+        Tuple of ratios for anchor boxes.
+
+    steps : Tuple of int
+        Priorbox step across y and x, -1 for auto calculation.
+
+    offsets : tuple of int
+        Priorbox center offsets, y and x respectively.
+
+    Returns
+    -------
+    stmt : Stmt
+        The result IR statement.
+    """
+    ib = tvm.ir_builder.create()
+    p_out = ib.buffer_ptr(out)
+    in_height = data.shape[2]
+    in_width = data.shape[3]
+    num_sizes = len(sizes)
+    num_ratios = len(ratios)
+    size_ratio_concat = sizes + ratios
+    steps_h = steps[0] if steps[0] > 0 else 1.0 / in_height
+    steps_w = steps[1] if steps[1] > 0 else 1.0 / in_width
+    offset_h = offsets[0]
+    offset_w = offsets[1]
+
+    with ib.for_range(0, in_height, for_type="parallel", name="i") as i:
+        center_h = (i + offset_h) * steps_h
+        with ib.for_range(0, in_width, name="j") as j:
+            center_w = (j + offset_w) * steps_w
+            for k in range(num_sizes + num_ratios - 1):
+                w = tvm.select(k < num_sizes,
+                               size_ratio_concat[k] * in_height / in_width / 2.0,
+                               size_ratio_concat[0] * in_height / in_width *
+                               math.sqrt(size_ratio_concat[k + 1]) / 2.0)
+                h = tvm.select(k < num_sizes, size_ratio_concat[k] / 2.0,
+                               size_ratio_concat[0] / math.sqrt(size_ratio_concat[k + 1]) / 2.0)
+                count = (i * in_width * (num_sizes + num_ratios - 1) +
+                         j * (num_sizes + num_ratios - 1) + k) * 4
+                p_out[count] = center_w - w
+                p_out[count + 1] = center_h - h
+                p_out[count + 2] = center_w + w
+                p_out[count + 3] = center_h + h
+
+    return ib.get()
+
+
+@tvm.target.generic_func
+def multibox_prior(data, sizes=(1,), ratios=(1,), steps=(-1, -1), offsets=(0.5, 0.5), clip=False):
+    """Generate prior(anchor) boxes from data, sizes and ratios.
+
+    Parameters
+    ----------
+    data : tvm.Tensor
+        4-D with shape [batch, c_in, h_in, w_in]]
+
+    sizes : tuple of float
+        Tuple of sizes for anchor boxes.
+
+    ratios : tuple of float
+        Tuple of ratios for anchor boxes.
+
+    steps : Tuple of int
+        Priorbox step across y and x, -1 for auto calculation.
+
+    offsets : tuple of int
+        Priorbox center offsets, y and x respectively.
+
+    clip : boolean
+        Whether to clip out-of-boundary boxes.
+
+    Returns
+    -------
+    out : tvm.Tensor
+        3-D tensor with shape [1, h_in * w_in * (num_sizes + num_ratios - 1), 4]
+    """
+    num_sizes = len(sizes)
+    num_ratios = len(ratios)
+    oshape = (1, data.shape[2] * data.shape[3] * (num_sizes + num_ratios - 1), 4)
+    out = tvm.extern(oshape, [data], lambda ins, outs:
+                     multibox_prior_ir(ins[0], outs[0], sizes, ratios, steps, offsets),
+                     tag="multibox_prior")
+    if clip:
+        out = topi.clip(out, 0, 1)
+    return out
+
+
+def transform_loc_ir(cls_prob, loc_pred, anchor, valid_count, out, clip, threshold, variances):
+    """Low level IR routing for transform location in multibox_detection operator.
+
+    Parameters
+    ----------
+    cls_prob : Buffer
+        Buffer of class probabilities.
+
+    loc_pred : Buffer
+        Buffer of location regression predictions.
+
+    anchor : Buffer
+        Buffer of prior anchor boxes.
+
+    valid_count : Buffer
+        Buffer of number of valid output boxes.
+
+    out : Buffer
+        Output buffer.
+
+    clip : boolean
+        Whether to clip out-of-boundary boxes.
+
+    threshold : float
+        Threshold to be a positive prediction.
+
+    variances : tuple of float
+        Variances to be decoded from box regression output.
+
+    Returns
+    -------
+    stmt : Stmt
+        The result IR statement.
+    """
+    def transform_loc(loc, loc_base_idx, anchor, anchor_base_idx, clip, vx, vy, vw, vh):
+        """Transform prior anchor box to output box through location predictions.
+        """
+        al = anchor[anchor_base_idx]
+        at = anchor[anchor_base_idx + 1]
+        ar = anchor[anchor_base_idx + 2]
+        ab = anchor[anchor_base_idx + 3]
+        aw = ar - al
+        ah = ab - at
+        ax = (al + ar) / 2.0
+        ay = (at + ab) / 2.0
+        px = loc[loc_base_idx]
+        py = loc[loc_base_idx + 1]
+        pw = loc[loc_base_idx + 2]
+        ph = loc[loc_base_idx + 3]
+        ox = px * vx * aw + ax
+        oy = py * vy * ah + ay
+        ow = tvm.exp(pw * vw) * aw / 2.0
+        oh = tvm.exp(ph * vh) * ah / 2.0
+        return tvm.select(clip, tvm.make.Max(0, tvm.make.Min(1, ox - ow)), ox - ow), \
+               tvm.select(clip, tvm.make.Max(0, tvm.make.Min(1, oy - oh)), oy - oh), \
+               tvm.select(clip, tvm.make.Max(0, tvm.make.Min(1, ox + ow)), ox + ow), \
+               tvm.select(clip, tvm.make.Max(0, tvm.make.Min(1, oy + oh)), oy + oh)
+
+    batch_size = cls_prob.shape[0]
+    num_classes = cls_prob.shape[1]
+    num_anchors = cls_prob.shape[2]
+
+    ib = tvm.ir_builder.create()
+    p_cls_prob = ib.buffer_ptr(cls_prob)
+    p_loc_pred = ib.buffer_ptr(loc_pred)
+    p_anchor = ib.buffer_ptr(anchor)
+    p_valid_count = ib.buffer_ptr(valid_count)
+    p_out = ib.buffer_ptr(out)
+    with ib.for_range(0, batch_size, for_type="parallel", name="n") as n:
+        p_valid_count[n] = 0
+        with ib.for_range(0, num_anchors, name="i") as i:
+            # Find the predicted class id and probability
+            score = ib.allocate('float32', (1,), name="score", scope="local")
+            cls_id = ib.allocate('int32', (1,), name="id", scope="local")
+            score[0] = -1.0
+            cls_id[0] = 0
+            with ib.for_range(0, num_classes, name="j") as j:
+                with ib.if_scope(j > 0):
+                    temp = p_cls_prob[n * num_anchors * num_classes + j * num_anchors + i]
+                    cls_id[0] = tvm.select(temp > score[0], j, cls_id[0])
+                    score[0] = tvm.make.Max(temp, score[0])
+            with ib.if_scope(tvm.all(cls_id[0] > 0, score[0] < threshold)):
+                cls_id[0] = 0
+            # [id, prob, xmin, ymin, xmax, ymax]
+            # Remove background, restore original id
+            with ib.if_scope(cls_id[0] > 0):
+                out_base_idx = n * num_anchors * 6 + p_valid_count[n] * 6
+                p_out[out_base_idx] = cls_id[0] - 1.0
+                p_out[out_base_idx + 1] = score[0]
+                offset = i * 4
+                p_out[out_base_idx + 2], p_out[out_base_idx + 3], p_out[out_base_idx + 4], \
+                p_out[out_base_idx + 5] = transform_loc(p_loc_pred, n * num_anchors * 4 + offset,
+                                                        p_anchor, offset, clip, variances[0],
+                                                        variances[1], variances[2], variances[3])
+                p_valid_count[n] += 1
+
+    return ib.get()
+
+
+@tvm.target.generic_func
+def mutibox_transform_loc(cls_prob, loc_pred, anchor, clip=True, threshold=0.01,
+                          variances=(0.1, 0.1, 0.2, 0.2)):
+    """Location transformation for multibox detection
+
+    Parameters
+    ----------
+    cls_prob : tvm.Tensor
+        Class probabilities.
+
+    loc_pred : tvm.Tensor
+        Location regression predictions.
+
+    anchor : tvm.Tensor
+        Prior anchor boxes.
+
+    clip : boolean
+        Whether to clip out-of-boundary boxes.
+
+    threshold : float
+        Threshold to be a positive prediction.
+
+    variances : tuple of float
+        Variances to be decoded from box regression output.
+
+    Returns
+    -------
+    out : tvm.Tensor
+        3-D tensor with shape (batch_size, num_anchors, 6)
+
+    valid_count : tvm.Tensor
+        1-D tensor with shape (batch_size,), number of valid anchor boxes.
+    """
+    batch_size = cls_prob.shape[0]
+    num_anchors = anchor.shape[1]
+    oshape = (batch_size, num_anchors, 6)
+    # Define data alignment for intermediate buffer
+    valid_count_dtype = "int32"
+    valid_count_buf = api.decl_buffer((batch_size,), valid_count_dtype,
+                                      "valid_count_buf", data_alignment=4)
+    out_buf = api.decl_buffer(oshape, cls_prob.dtype, "out_buf", data_alignment=8)
+    valid_count, out = \
+        tvm.extern([(batch_size,), oshape],
+                   [cls_prob, loc_pred, anchor],
+                   lambda ins, outs: transform_loc_ir(
+                       ins[0], ins[1], ins[2], outs[0], outs[1], clip, threshold, variances),
+                   dtype=[valid_count_dtype, cls_prob.dtype],
+                   out_buffers=[valid_count_buf, out_buf],
+                   tag="multibox_transform_loc")
+    return out, valid_count
+
+
+@tvm.target.generic_func
+def multibox_detection(cls_prob, loc_pred, anchor, clip=True, threshold=0.01, nms_threshold=0.5,
+                       force_suppress=False, variances=(0.1, 0.1, 0.2, 0.2), nms_topk=-1):
+    """Convert multibox detection predictions.
+
+    Parameters
+    ----------
+    cls_prob : tvm.Tensor
+        Class probabilities.
+
+    loc_pred : tvm.Tensor
+        Location regression predictions.
+
+    anchor : tvm.Tensor
+        Prior anchor boxes.
+
+    clip : boolean
+        Whether to clip out-of-boundary boxes.
+
+    nms_threshold : float
+        Non-maximum suppression threshold.
+
+    force_suppress : boolean
+        Whether to suppress all detections regardless of class_id.
+
+    threshold : float
+        Threshold to be a positive prediction.
+
+    variances : tuple of float
+        Variances to be decoded from box regression output.
+
+    nms_topk : int
+        Keep maximum top k detections before nms, -1 for no limit.
+
+    Returns
+    -------
+    out : tvm.Tensor
+        3-D tensor with shape (batch_size, num_anchors, 6)
+    """
+    inter_out, valid_count = mutibox_transform_loc(cls_prob, loc_pred, anchor,
+                                                   clip, threshold, variances)
+    out = nms(inter_out, valid_count, nms_threshold, force_suppress, nms_topk)
+    return out
diff --git a/topi/tests/python/test_topi_vision.py b/topi/tests/python/test_topi_vision.py
new file mode 100644
index 000000000000..3c624726d562
--- /dev/null
+++ b/topi/tests/python/test_topi_vision.py
@@ -0,0 +1,151 @@
+"""Test code for vision package"""
+import numpy as np
+import tvm
+import topi
+import math
+
+from topi.vision import ssd, nms
+
+
+def test_nms():
+    dshape = (1, 5, 6)
+    data = tvm.placeholder(dshape, name="data")
+    valid_count = tvm.placeholder((dshape[0],), dtype="int32", name="valid_count")
+    nms_threshold = 0.7
+    force_suppress = True
+    nms_topk = 2
+    out = nms(data, valid_count, nms_threshold, force_suppress, nms_topk)
+
+    np_data = np.array([[[0, 0.8, 1, 20, 25, 45], [1, 0.7, 30, 60, 50, 80],
+                         [0, 0.4, 4, 21, 19, 40], [2, 0.9, 35, 61, 52, 79],
+                         [1, 0.5, 100, 60, 70, 110]]]).astype(data.dtype)
+    np_valid_count = np.array([4]).astype(valid_count.dtype)
+    np_result = np.array([[[2, 0.9, 35, 61, 52, 79], [0, 0.8, 1, 20, 25, 45],
+                           [0, 0.4, 4, 21, 19, 40], [-1, 0.9, 35, 61, 52, 79],
+                           [-1, -1, -1, -1, -1, -1]]])
+
+    def check_device(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s = topi.generic.schedule_nms(out)
+
+        tvm_data = tvm.nd.array(np_data, ctx)
+        tvm_valid_count = tvm.nd.array(np_valid_count, ctx)
+        tvm_out = tvm.nd.array(np.zeros(dshape, dtype=data.dtype), ctx)
+        f = tvm.build(s, [data, valid_count, out], device)
+        f(tvm_data, tvm_valid_count, tvm_out)
+        np.testing.assert_allclose(tvm_out.asnumpy(), np_result, rtol=1e-4)
+
+    for device in ['llvm']:
+        check_device(device)
+
+
+def verify_multibox_prior(dshape, sizes=(1,), ratios=(1,), steps=(-1, -1), offsets=(0.5, 0.5), clip=False):
+    data = tvm.placeholder(dshape, name="data")
+    out = ssd.multibox_prior(data, sizes, ratios, steps, offsets, clip)
+
+    dtype = data.dtype
+    input_data = np.random.uniform(size=dshape).astype(dtype)
+
+    in_height = data.shape[2].value
+    in_width = data.shape[3].value
+    num_sizes = len(sizes)
+    num_ratios = len(ratios)
+    size_ratio_concat = sizes + ratios
+    steps_h = steps[0] if steps[0] > 0 else 1.0 / in_height
+    steps_w = steps[1] if steps[1] > 0 else 1.0 / in_width
+    offset_h = offsets[0]
+    offset_w = offsets[1]
+
+    oshape = (1, in_height * in_width * (num_sizes + num_ratios - 1), 4)
+    np_out = np.zeros(oshape).astype(dtype)
+
+    for i in range(in_height):
+        center_h = (i + offset_h) * steps_h
+        for j in range(in_width):
+            center_w = (j + offset_w) * steps_w
+            for k in range(num_sizes + num_ratios - 1):
+                w = size_ratio_concat[k] * in_height / in_width / 2.0 if k < num_sizes else \
+                    size_ratio_concat[0] * in_height / in_width * math.sqrt(size_ratio_concat[k + 1]) / 2.0
+                h = size_ratio_concat[k] / 2.0 if k < num_sizes else \
+                    size_ratio_concat[0] / math.sqrt(size_ratio_concat[k + 1]) / 2.0
+                count = i * in_width * (num_sizes + num_ratios - 1) + j * (num_sizes + num_ratios - 1) + k
+                np_out[0][count][0] = center_w - w
+                np_out[0][count][1] = center_h - h
+                np_out[0][count][2] = center_w + w
+                np_out[0][count][3] = center_h + h
+    if clip:
+        np_out = np.clip(np_out, 0, 1)
+
+    def check_device(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s = topi.generic.schedule_multibox_prior(out)
+
+        tvm_input_data = tvm.nd.array(input_data, ctx)
+        tvm_out = tvm.nd.array(np.zeros(oshape, dtype=dtype), ctx)
+        f = tvm.build(s, [data, out], device)
+        f(tvm_input_data, tvm_out)
+        np.testing.assert_allclose(tvm_out.asnumpy(), np_out, rtol=1e-4)
+
+    for device in ['llvm']:
+        check_device(device)
+
+
+def test_multibox_prior():
+    verify_multibox_prior((1, 3, 50, 50))
+    verify_multibox_prior((1, 3, 224, 224), sizes=(0.5, 0.25, 0.1), ratios=(1, 2, 0.5))
+    verify_multibox_prior((1, 32, 32, 32), sizes=(0.5, 0.25), ratios=(1, 2), steps=(2, 2), clip=True)
+
+
+def test_multibox_detection():
+    batch_size = 1
+    num_anchors = 3
+    num_classes = 3
+    cls_prob = tvm.placeholder((batch_size, num_anchors, num_classes), name="cls_prob")
+    loc_preds = tvm.placeholder((batch_size, num_anchors * 4), name="loc_preds")
+    anchors = tvm.placeholder((1, num_anchors, 4), name="anchors")
+    out = ssd.multibox_detection(cls_prob, loc_preds, anchors)
+
+    # Manually create test case
+    np_cls_prob = np.array([[[0.2, 0.5, 0.3], [0.25, 0.3, 0.45], [0.7, 0.1, 0.2]]])
+    np_loc_preds = np.array([[0.1, -0.2, 0.3, 0.2, 0.2, 0.4, 0.5, -0.3, 0.7, -0.2, -0.4, -0.8]])
+    np_anchors = np.array([[[-0.1, -0.1, 0.1, 0.1], [-0.2, -0.2, 0.2, 0.2], [1.2, 1.2, 1.5, 1.5]]])
+
+    expected_np_out = np.array([[[1, 0.69999999, 0, 0, 0.10818365, 0.10008108],
+                                 [0, 0.44999999, 1, 1, 1, 1],
+                                 [0, 0.30000001, 0, 0, 0.22903419, 0.20435292]]])
+
+    def check_device(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s = topi.generic.schedule_multibox_detection(out)
+
+        tvm_cls_prob = tvm.nd.array(np_cls_prob.astype(cls_prob.dtype), ctx)
+        tvm_loc_preds = tvm.nd.array(np_loc_preds.astype(loc_preds.dtype), ctx)
+        tvm_anchors = tvm.nd.array(np_anchors.astype(anchors.dtype), ctx)
+        tvm_out = tvm.nd.array(np.zeros((batch_size, num_anchors, 6)).astype(out.dtype), ctx)
+        f = tvm.build(s, [cls_prob, loc_preds, anchors, out], device)
+        f(tvm_cls_prob, tvm_loc_preds, tvm_anchors, tvm_out)
+        np.testing.assert_allclose(tvm_out.asnumpy(), expected_np_out, rtol=1e-4)
+
+    for device in ['llvm']:
+        check_device(device)
+
+
+if __name__ == "__main__":
+    test_nms()
+    test_multibox_prior()
+    test_multibox_detection()
\ No newline at end of file

From a3f16305cf26c0c8520c115c8b636f4de04c2586 Mon Sep 17 00:00:00 2001
From: Yizhi Liu <liuyizhi@apache.org>
Date: Sun, 13 May 2018 20:08:58 -0700
Subject: [PATCH 302/949] AVX schedule for conv_NCHW[x]c (#1143)

* add conv2d_NCHWc compute template

* add conv_NCHWc compute decl and schedules

* allow default avx schedule

* fix lint

* remove unused schedule

* remove import nnvm.reg

* pass schedule object to compute and schedule
---
 topi/python/topi/nn/conv2d.py             |  55 ++++++
 topi/python/topi/x86/conv2d.py            | 221 ++++++++++++++++++----
 topi/python/topi/x86/conv2d_avx_1x1.py    | 107 ++++++++++-
 topi/python/topi/x86/conv2d_avx_common.py | 110 ++++++++++-
 4 files changed, 451 insertions(+), 42 deletions(-)

diff --git a/topi/python/topi/nn/conv2d.py b/topi/python/topi/nn/conv2d.py
index 81263912bc9b..e0cee9526d14 100644
--- a/topi/python/topi/nn/conv2d.py
+++ b/topi/python/topi/nn/conv2d.py
@@ -112,6 +112,23 @@ def conv2d(data, kernel, stride, padding, layout='NCHW', out_dtype=None):
         raise ValueError("not support this layout {} yet".format(layout))
 
 
+@tvm.target.generic_func
+def conv2d_alter_layout(attrs, inputs, tinfos):
+    """Change Conv2D layout.
+
+    Parameters
+    ----------
+    attrs : nnvm.top.AttrDict
+        Attributes of current convolution
+    inputs : nnvm.symbol
+        Grouped input symbols
+    tinfos : list
+        Input shape and dtype
+    """
+    # not to change by default
+    return None
+
+
 def _get_workload(data, kernel, stride, padding, out_dtype):
     """ Get the workload structure. """
     _, CI, IH, IW = [x.value for x in data.shape]
@@ -425,6 +442,44 @@ def conv2d_nhwc(Input, Filter, stride, padding, out_dtype='float32'):
         name="Conv2dOutput", tag="conv2d_nhwc")
     return Output
 
+@tvm.target.generic_func
+def conv2d_NCHWc(data, kernel, num_filter, kernel_size, stride, padding, out_dtype='float32'):
+    """Conv2D operator for nChw[x]c layout.
+
+    Parameters
+    ----------
+    data : tvm.Tensor
+        5-D with shape [batch, in_channel_chunk, in_height, in_width, in_channel_block]
+
+    kernel : tvm.Tensor
+        6-D with shape
+        [num_filter_chunk, in_channel_chunk, filter_height, filter_width,
+        in_channel_block, num_filter_block]
+
+    num_filter : int
+        number of filters, i.e., output channel size
+
+    kernel_size : tuple of two ints
+       [kernel_height, kernel_width]
+
+    stride : int or a list/tuple of two ints
+        stride size, or [stride_height, stride_width]
+
+    padding : int or a list/tuple of two ints
+        padding size, or [pad_height, pad_width]
+
+    out_dtype : str
+        output data type
+
+    Returns
+    -------
+    output : tvm.Tensor
+        5-D with shape [batch, out_channel_chunk, out_height, out_width, out_channel_block]
+    """
+    # search platform specific declaration first
+    # default declaration
+    raise ValueError("missing register for topi.nn.conv2d_NCHWc")
+
 # map from schedule type to declaration function
 _SCH_TO_DECL_FUNC = {
     SpatialPack: _spatial_pack,
diff --git a/topi/python/topi/x86/conv2d.py b/topi/python/topi/x86/conv2d.py
index 7e9632ed9622..59495d75f42d 100644
--- a/topi/python/topi/x86/conv2d.py
+++ b/topi/python/topi/x86/conv2d.py
@@ -4,27 +4,50 @@
 from .. import generic, tag
 from .. import nn
 from ..nn.util import infer_pad, infer_stride
-from ..nn.conv2d import conv2d, _get_workload, _get_schedule, _WORKLOADS
+from ..nn.conv2d import conv2d, conv2d_NCHWc, conv2d_alter_layout, \
+                        _get_workload, _get_schedule, Workload
 
 from . import conv2d_avx_1x1, conv2d_avx_common
 from .conv2d_avx_common import AVXConvCommonFwd
 from .conv2d_avx_1x1 import AVXConv1x1Fwd
 
-_AVX_SCH_TO_DECL_FUNC = {
-    AVXConvCommonFwd: conv2d_avx_common._declaration_conv,
-    AVXConv1x1Fwd: conv2d_avx_1x1._declaration_conv
-}
-
-_AVX_SCH_TO_SCH_FUNC = {
-    AVXConvCommonFwd: conv2d_avx_common._schedule_conv,
-    AVXConv1x1Fwd: conv2d_avx_1x1._schedule_conv
-}
-
 @_get_schedule.register("cpu")
 def _get_schedule_conv(wkl):
-    if wkl not in _WORKLOADS:
-        raise ValueError("no schedule for such workload: {}".format(wkl))
-    idx = _WORKLOADS.index(wkl)
+    _WORKLOADS_AVX = [
+        # workloads of resnet18_v1 on imagenet
+        Workload('float32', 'float32', 224, 224, 3, 64, 7, 7, 3, 3, 2, 2),
+        Workload('float32', 'float32', 56, 56, 64, 64, 3, 3, 1, 1, 1, 1),
+        Workload('float32', 'float32', 56, 56, 64, 64, 1, 1, 0, 0, 1, 1),
+        Workload('float32', 'float32', 56, 56, 64, 128, 3, 3, 1, 1, 2, 2),
+        Workload('float32', 'float32', 56, 56, 64, 128, 1, 1, 0, 0, 2, 2),
+        Workload('float32', 'float32', 28, 28, 128, 128, 3, 3, 1, 1, 1, 1),
+        Workload('float32', 'float32', 28, 28, 128, 256, 3, 3, 1, 1, 2, 2),
+        Workload('float32', 'float32', 28, 28, 128, 256, 1, 1, 0, 0, 2, 2),
+        Workload('float32', 'float32', 14, 14, 256, 256, 3, 3, 1, 1, 1, 1),
+        Workload('float32', 'float32', 14, 14, 256, 512, 3, 3, 1, 1, 2, 2),
+        Workload('float32', 'float32', 14, 14, 256, 512, 1, 1, 0, 0, 2, 2),
+        Workload('float32', 'float32', 7, 7, 512, 512, 3, 3, 1, 1, 1, 1),
+        # workloads of resnet34_v1 on imagenet, no extra workload required
+        # workloads of resnet50_v1 on imagenet
+        Workload('float32', 'float32', 56, 56, 64, 256, 1, 1, 0, 0, 1, 1),
+        Workload('float32', 'float32', 56, 56, 256, 64, 1, 1, 0, 0, 1, 1),
+        Workload('float32', 'float32', 56, 56, 256, 128, 1, 1, 0, 0, 2, 2),
+        Workload('float32', 'float32', 28, 28, 128, 512, 1, 1, 0, 0, 1, 1),
+        Workload('float32', 'float32', 56, 56, 256, 512, 1, 1, 0, 0, 2, 2),
+        Workload('float32', 'float32', 28, 28, 512, 128, 1, 1, 0, 0, 1, 1),
+        Workload('float32', 'float32', 28, 28, 512, 256, 1, 1, 0, 0, 2, 2),
+        Workload('float32', 'float32', 14, 14, 256, 1024, 1, 1, 0, 0, 1, 1),
+        Workload('float32', 'float32', 28, 28, 512, 1024, 1, 1, 0, 0, 2, 2),
+        Workload('float32', 'float32', 14, 14, 1024, 256, 1, 1, 0, 0, 1, 1),
+        Workload('float32', 'float32', 14, 14, 1024, 512, 1, 1, 0, 0, 2, 2),
+        Workload('float32', 'float32', 7, 7, 512, 2048, 1, 1, 0, 0, 1, 1),
+        Workload('float32', 'float32', 14, 14, 1024, 2048, 1, 1, 0, 0, 2, 2),
+        Workload('float32', 'float32', 7, 7, 2048, 512, 1, 1, 0, 0, 1, 1),
+        # workloads of resnet101_v1 on imagenet, no extra workload required
+        # workloads of resnet152_v1 on imagenet, no extra workload required
+        # workloads of resnet18_v2 on imagenet, no extra workload required
+        # workloads of resnet34_v2 on imagenet, no extra workload required
+    ]
 
     fp32_vec_len = 8
     target = tvm.target.current_target(allow_none=False)
@@ -32,43 +55,61 @@ def _get_schedule_conv(wkl):
         if opt == '-mcpu=skylake-avx512':
             fp32_vec_len = 16
 
-    _SCHEDULES_AVX_NCHW = [
-        # float32 resnet-18
+    _SCHEDULES_AVX = [
+        # workloads of resnet18_v1 on imagenet
         AVXConvCommonFwd(3, fp32_vec_len, 28, False),
-        AVXConvCommonFwd(16, fp32_vec_len, 28, False),
-        AVXConv1x1Fwd(16, fp32_vec_len, 1, 28),
-        AVXConvCommonFwd(16, fp32_vec_len, 28, False),
-        AVXConv1x1Fwd(16, fp32_vec_len, 1, 28),
-        AVXConvCommonFwd(16, fp32_vec_len, 28, False),
-        AVXConvCommonFwd(16, fp32_vec_len, 14, False),
-        AVXConv1x1Fwd(16, fp32_vec_len, 2, 14),
-        AVXConvCommonFwd(16, fp32_vec_len, 14, True),
-        AVXConvCommonFwd(16, 32, 7, True),
-        AVXConv1x1Fwd(16, fp32_vec_len, 1, 7),
-        AVXConvCommonFwd(16, fp32_vec_len, 7, True),
-        # float32 mobilenet
-        AVXConvCommonFwd(3, fp32_vec_len, 28, False),
-        AVXConv1x1Fwd(16, fp32_vec_len, 1, 28),
-        AVXConv1x1Fwd(16, fp32_vec_len, 1, 28),
-        AVXConv1x1Fwd(16, fp32_vec_len, 1, 28),
-        AVXConv1x1Fwd(16, fp32_vec_len, 1, 28),
-        AVXConv1x1Fwd(16, fp32_vec_len, 1, 28),
-        AVXConv1x1Fwd(16, fp32_vec_len, 2, 14),
-        AVXConv1x1Fwd(16, fp32_vec_len, 2, 14),
-        AVXConv1x1Fwd(16, fp32_vec_len, 1, 7),
-        AVXConv1x1Fwd(16, fp32_vec_len, 1, 7),
+        AVXConvCommonFwd(fp32_vec_len, fp32_vec_len, 28, False),
+        AVXConv1x1Fwd(fp32_vec_len, fp32_vec_len, 1, 28),
+        AVXConvCommonFwd(fp32_vec_len, fp32_vec_len, 28, False),
+        AVXConv1x1Fwd(fp32_vec_len, fp32_vec_len, 1, 28),
+        AVXConvCommonFwd(fp32_vec_len, fp32_vec_len, 28, False),
+        AVXConvCommonFwd(fp32_vec_len, fp32_vec_len, 14, False),
+        AVXConv1x1Fwd(fp32_vec_len, fp32_vec_len, 2, 14),
+        AVXConvCommonFwd(fp32_vec_len, fp32_vec_len, 14, True),
+        AVXConvCommonFwd(fp32_vec_len, fp32_vec_len, 7, True),
+        AVXConv1x1Fwd(fp32_vec_len, fp32_vec_len, 1, 7),
+        AVXConvCommonFwd(fp32_vec_len, fp32_vec_len, 7, True),
+        # workloads of resnet34_v1 on imagenet, no extra workload required
+        # workloads of resnet50_v1 on imagenet
+        AVXConv1x1Fwd(fp32_vec_len, fp32_vec_len, 2, 28),
+        AVXConv1x1Fwd(fp32_vec_len, fp32_vec_len, 2, 28),
+        AVXConv1x1Fwd(fp32_vec_len, fp32_vec_len, 2, 28),
+        AVXConv1x1Fwd(fp32_vec_len, fp32_vec_len, 2, 28),
+        AVXConv1x1Fwd(fp32_vec_len, fp32_vec_len, 2, 28),
+        AVXConv1x1Fwd(fp32_vec_len, fp32_vec_len, 2, 28),
+        AVXConv1x1Fwd(fp32_vec_len, fp32_vec_len, 2, 14),
+        AVXConv1x1Fwd(fp32_vec_len, fp32_vec_len, 2, 14),
+        AVXConv1x1Fwd(fp32_vec_len, fp32_vec_len, 2, 14),
+        AVXConv1x1Fwd(fp32_vec_len, fp32_vec_len, 2, 14),
+        AVXConv1x1Fwd(fp32_vec_len, fp32_vec_len, 2, 7),
+        AVXConv1x1Fwd(fp32_vec_len, fp32_vec_len, 2, 7),
+        AVXConv1x1Fwd(fp32_vec_len, fp32_vec_len, 2, 7),
+        AVXConv1x1Fwd(fp32_vec_len, fp32_vec_len, 2, 7),
+        # workloads of resnet101_v1 on imagenet, no extra workload required
+        # workloads of resnet152_v1 on imagenet, no extra workload required
+        # workloads of resnet18_v2 on imagenet, no extra workload required
+        # workloads of resnet34_v2 on imagenet, no extra workload required
     ]
 
-    sch = _SCHEDULES_AVX_NCHW[idx]
+    if wkl not in _WORKLOADS_AVX:
+        if wkl.hkernel == 1 and wkl.wkernel == 1:
+            return conv2d_avx_1x1._get_default_schedule(wkl, fp32_vec_len)
+        return conv2d_avx_common._get_default_schedule(wkl, fp32_vec_len)
+    idx = _WORKLOADS_AVX.index(wkl)
+    sch = _SCHEDULES_AVX[idx]
     return sch
 
 
 @conv2d.register("cpu")
 def _declaration_conv(data, kernel, stride, padding, layout, out_dtype):
+    _AVX_SCH_TO_DECL_FUNC = {
+        AVXConvCommonFwd: conv2d_avx_common._declaration_conv,
+        AVXConv1x1Fwd: conv2d_avx_1x1._declaration_conv
+    }
     out_dtype = data.dtype if out_dtype is None else out_dtype
     target = tvm.target.current_target(allow_none=False)
     wkl = _get_workload(data, kernel, stride, padding, out_dtype)
-    if wkl in _WORKLOADS and 'avx' in str(target) and layout == 'NCHW':
+    if 'avx' in str(target) and layout == 'NCHW':
         sch = _get_schedule(wkl)
         return _AVX_SCH_TO_DECL_FUNC[type(sch)](data, kernel, stride, padding, layout, out_dtype)
     elif layout == 'NCHW':
@@ -81,9 +122,63 @@ def _declaration_conv(data, kernel, stride, padding, layout, out_dtype):
         raise ValueError("not support this layout {} yet".format(layout))
 
 
+@conv2d_alter_layout.register("cpu")
+def _alter_conv2d_layout(attrs, inputs, tinfos):
+    import nnvm.symbol as sym
+    copy_inputs = [s for s in inputs]
+    new_attrs = {k : attrs[k] for k in attrs.keys()}
+    # only optimize for NCHW, groups=1 conv
+    if attrs['layout'] != 'NCHW' or attrs.get_int("groups") != 1:
+        return None
+
+    data = tinfos[0]
+    kernel = tinfos[1]
+
+    import ast
+    padding = ast.literal_eval(attrs['padding'])
+    stride = ast.literal_eval(attrs['strides'])
+
+    wkl = _get_workload(data, kernel, stride, padding, data.dtype)
+    sch = _get_schedule_conv(wkl)
+    is_kernel_1x1 = isinstance(sch, AVXConv1x1Fwd)
+    ic_bn, oc_bn = sch.ic_bn, sch.oc_bn
+
+    new_attrs['layout'] = 'NCHW%dc' % ic_bn
+    new_attrs['out_layout'] = 'NCHW%dc' % oc_bn
+
+    if is_kernel_1x1:
+        # (oc, ic, h, w) -> (OC, IC, ic, oc, h, w)
+        new_attrs['kernel_layout'] = 'OI%di%doHW' % (ic_bn, oc_bn)
+    else:
+        # (oc, ic, h, w) -> (OC, IC, h, w, ic, oc)
+        new_attrs['kernel_layout'] = 'OIHW%di%do' % (ic_bn, oc_bn)
+
+    return sym.contrib.conv2d_NCHWc(*copy_inputs, **new_attrs)
+
+
+@conv2d_NCHWc.register("cpu")
+def _declaration_conv_NCHWc(data, kernel, num_filter, kernel_size, stride, padding, out_dtype):
+    _AVX_SCH_TO_DECL_FUNC = {
+        AVXConvCommonFwd: conv2d_avx_common._declaration_conv_NCHWc,
+        AVXConv1x1Fwd: conv2d_avx_1x1._declaration_conv_NCHWc
+    }
+    n, ic_chunk, h, w, ic_block = [x.value for x in data.shape]
+    ic = ic_chunk * ic_block
+    kh, kw = kernel_size
+    wkl = _get_workload(tvm.placeholder((n, ic, h, w), dtype=out_dtype),
+                        tvm.placeholder((num_filter, ic, kh, kw), dtype=out_dtype),
+                        stride, padding, out_dtype)
+    sch = _get_schedule(wkl)
+    return _AVX_SCH_TO_DECL_FUNC[type(sch)](wkl, sch, data, kernel)
+
+
 @generic.schedule_conv2d_nchw.register(["cpu"])
 def schedule_conv2d(outs):
     """Create schedule for tensors"""
+    _AVX_SCH_TO_SCH_FUNC = {
+        AVXConvCommonFwd: conv2d_avx_common._schedule_conv,
+        AVXConv1x1Fwd: conv2d_avx_1x1._schedule_conv
+    }
     s = tvm.create_schedule([x.op for x in outs])
     target = tvm.target.current_target(allow_none=False)
 
@@ -213,3 +308,49 @@ def traverse(op):
 
     traverse(output_op)
     return s
+
+
+@generic.schedule_conv2d_NCHWc.register(["cpu"])
+def schedule_conv2d_NCHWc(num_filter, kernel_size, stride, padding, outs):
+    """Create schedule for tensors"""
+    _AVX_SCH_TO_SCH_FUNC = {
+        AVXConvCommonFwd: conv2d_avx_common._schedule_conv_NCHWc,
+        AVXConv1x1Fwd: conv2d_avx_1x1._schedule_conv_NCHWc
+    }
+    s = tvm.create_schedule([x.op for x in outs])
+
+    def traverse(op):
+        """Traverse operators from computation graph"""
+        # inline all one-to-one-mapping operators except the last stage (output)
+        if tag.is_broadcast(op.tag):
+            if op not in s.outputs:
+                s[op].compute_inline()
+            for tensor in op.input_tensors:
+                if tensor.op.input_tensors:
+                    traverse(tensor.op)
+
+        if 'conv2d_NCHWc' in op.tag:
+            conv_out = op.output(0)
+            kernel = conv_out.op.input_tensors[1]
+            data_vec = conv_out.op.input_tensors[0]
+            data = data_vec.op.input_tensors[0] \
+                if isinstance(data_vec.op, tvm.tensor.ComputeOp) and "pad" not in data_vec.op.tag \
+                else data_vec
+            if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
+                data_pad = data
+                data = data_pad.op.input_tensors[0]
+
+            n, ic_chunk, h, w, ic_block = [x.value for x in data.shape]
+            ic = ic_chunk * ic_block
+            original_data = tvm.placeholder((n, ic, h, w), dtype=conv_out.dtype)
+
+            kh, kw = kernel_size
+            original_kernel = tvm.placeholder((num_filter, ic, kh, kw), dtype=conv_out.dtype)
+
+            wkl = _get_workload(original_data, original_kernel, stride, padding, conv_out.dtype)
+            sch = _get_schedule(wkl)
+            _AVX_SCH_TO_SCH_FUNC[type(sch)](s, wkl, sch, data_vec,
+                                            kernel, conv_out, outs[0])
+
+    traverse(outs[0].op)
+    return s
diff --git a/topi/python/topi/x86/conv2d_avx_1x1.py b/topi/python/topi/x86/conv2d_avx_1x1.py
index afd0be2e2ded..ecbae0cc3128 100644
--- a/topi/python/topi/x86/conv2d_avx_1x1.py
+++ b/topi/python/topi/x86/conv2d_avx_1x1.py
@@ -1,4 +1,4 @@
-# pylint: disable=invalid-name,unused-variable,invalid-name
+# pylint: disable=invalid-name,unused-variable,unused-argument,invalid-name
 """1x1 Conv2D schedule on for Intel CPU"""
 from __future__ import absolute_import as _abs
 from collections import namedtuple
@@ -11,6 +11,34 @@
 
 AVXConv1x1Fwd = namedtuple('AVXConv1x1Fwd', ['ic_bn', 'oc_bn', 'oh_factor', 'ow_factor'])
 
+
+def _get_default_schedule(wkl, simd_width):
+    HPAD, WPAD = wkl.hpad, wkl.wpad
+    HSTR, WSTR = wkl.hstride, wkl.wstride
+    out_height = (wkl.height + 2 * HPAD - wkl.hkernel) // HSTR + 1
+    out_width = (wkl.width + 2 * WPAD - wkl.wkernel) // WSTR + 1
+
+    oc_bn = 1
+    for bn in range(simd_width, 0, -1):
+        if wkl.out_filter % bn == 0:
+            oc_bn = bn
+            break
+
+    ic_bn = 1
+    for bn in range(oc_bn, 0, -1):
+        if wkl.in_filter % bn == 0:
+            ic_bn = bn
+            break
+
+    for ow_factor in range(out_width, 0, -1):
+        if out_width % ow_factor == 0:
+            for oh_factor in range(out_height, 0, -1):
+                if out_height % oh_factor == 0 and ow_factor * oh_factor < 32:
+                    return AVXConv1x1Fwd(ic_bn, oc_bn, oh_factor, ow_factor)
+
+    raise ValueError("cannot decide default schedule for workload: {}".format(wkl))
+
+
 def _declaration_conv(data, kernel, stride, padding, layout, out_dtype):
     assert layout == 'NCHW', "only support NCHW convolution for AVX"
     wkl = _get_workload(data, kernel, stride, padding, out_dtype)
@@ -124,3 +152,80 @@ def _schedule_conv(s, data, data_pad, data_vec, kernel, kernel_vec, conv_out, ou
     s[O].parallel(parallel_axis)
 
     return s
+
+
+def _declaration_conv_NCHWc(wkl, sch, data, kernel):
+    out_dtype = wkl.out_dtype
+    HPAD, WPAD = wkl.hpad, wkl.wpad
+    HSTR, WSTR = wkl.hstride, wkl.wstride
+
+    batch_size = data.shape[0]
+    out_height = (wkl.height + 2 * HPAD - wkl.hkernel) // HSTR + 1
+    out_width = (wkl.width + 2 * WPAD - wkl.wkernel) // WSTR + 1
+
+    DOPAD = (HPAD != 0 and WPAD != 0)
+    if DOPAD:
+        data_pad = pad(data, (0, 0, HPAD, WPAD, 0), name="data_pad")
+    else:
+        data_pad = data
+
+    oshape = (batch_size, wkl.out_filter//sch.oc_bn, out_height, out_width, sch.oc_bn)
+    ic = tvm.reduce_axis((0, wkl.in_filter), name='ic')
+    conv = tvm.compute(oshape, lambda n, oc_chunk, oh, ow, oc_block:
+                       tvm.sum(data_pad[n, ic//sch.ic_bn, oh*HSTR, ow*WSTR, ic%sch.ic_bn]
+                               .astype(out_dtype) *
+                               kernel[oc_chunk, ic // sch.ic_bn, ic % sch.ic_bn, oc_block, 0, 0],
+                               axis=[ic]), name='conv2d_NCHWc', tag='conv2d_NCHWc')
+
+    return conv
+
+
+def _schedule_conv_NCHWc(s, wkl, sch, data, kernel, conv_out, last):
+    # schedule data
+    A = data
+    if isinstance(s[A].op, tvm.tensor.ComputeOp):
+        batch, ic_chunk, ih, iw, ic_block = s[A].op.axis
+        parallel_axis = s[A].fuse(ic_chunk, ih)
+        s[A].parallel(parallel_axis)
+
+    C, O = conv_out, last
+    CC = s.cache_write(C, 'global')
+
+    batch, oc_chunk, oh, ow, oc_block = s[C].op.axis
+    oh_outer, oh_inner = s[C].split(oh, factor=sch.oh_factor)
+    ow_outer, ow_inner = s[C].split(ow, factor=sch.ow_factor)
+    s[C].reorder(oc_chunk, oh_outer, ow_outer, oh_inner, ow_inner, oc_block)
+    s[C].vectorize(oc_block)
+
+    parallel_axis = s[C].fuse(oc_chunk, oh_outer)
+    s[CC].compute_at(s[C], parallel_axis)
+    if C == O:
+        s[C].parallel(parallel_axis)
+
+    _, oc_chunk, oh, ow, oc_block = s[CC].op.axis
+    ic, = s[CC].op.reduce_axis
+
+    ic_chunk, ic_block = s[CC].split(ic, factor=sch.ic_bn)
+
+    oh_outer, oh_inner = s[CC].split(oh, factor=sch.oh_factor)
+    ow_outer, ow_inner = s[CC].split(ow, factor=sch.ow_factor)
+
+    s[CC].reorder(oc_chunk, oh_outer, ow_outer, ic_chunk, ic_block, oh_inner, ow_inner, oc_block)
+    s[CC].fuse(oc_chunk, oh_outer)
+    s[CC].vectorize(oc_block)
+
+    s[CC].unroll(ow_inner)
+    s[CC].unroll(oh_inner)
+
+    if C != O:
+        batch, oc_chunk, oh, ow, oc_block = s[O].op.axis
+        oh_outer, oh_inner = s[O].split(oh, factor=sch.oh_factor)
+        ow_outer, ow_inner = s[O].split(ow, factor=sch.ow_factor)
+        s[O].reorder(oc_chunk, oh_outer, ow_outer, oh_inner, ow_inner, oc_block)
+
+        parallel_axis = s[O].fuse(oc_chunk, oh_outer)
+        s[C].compute_at(s[O], parallel_axis)
+        s[O].vectorize(oc_block)
+        s[O].parallel(parallel_axis)
+
+    return s
diff --git a/topi/python/topi/x86/conv2d_avx_common.py b/topi/python/topi/x86/conv2d_avx_common.py
index 03c1021be409..063759998108 100644
--- a/topi/python/topi/x86/conv2d_avx_common.py
+++ b/topi/python/topi/x86/conv2d_avx_common.py
@@ -1,4 +1,4 @@
-# pylint: disable=invalid-name,unused-variable,invalid-name
+# pylint: disable=invalid-name,unused-variable,unused-argument,invalid-name
 """Conv2D schedule on for Intel CPU"""
 from __future__ import absolute_import as _abs
 from collections import namedtuple
@@ -11,6 +11,34 @@
 
 AVXConvCommonFwd = namedtuple('AVXConvCommonFwd', ['ic_bn', 'oc_bn', 'reg_n', 'unroll_kw'])
 
+
+def _get_default_schedule(wkl, simd_width):
+    HPAD, WPAD = wkl.hpad, wkl.wpad
+    HSTR, WSTR = wkl.hstride, wkl.wstride
+    out_height = (wkl.height + 2 * HPAD - wkl.hkernel) // HSTR + 1
+    out_width = (wkl.width + 2 * WPAD - wkl.wkernel) // WSTR + 1
+
+    oc_bn = 1
+    for bn in range(simd_width, 0, -1):
+        if wkl.out_filter % bn == 0:
+            oc_bn = bn
+            break
+
+    ic_bn = 1
+    for bn in range(oc_bn, 0, -1):
+        if wkl.in_filter % bn == 0:
+            ic_bn = bn
+            break
+
+    reg_n = 1
+    for n in range(31, 0, -1):
+        if out_width % n == 0:
+            reg_n = n
+            break
+
+    return AVXConvCommonFwd(ic_bn, oc_bn, reg_n, False)
+
+
 def _declaration_conv(data, kernel, stride, padding, layout, out_dtype):
     out_dtype = data.dtype if out_dtype is None else out_dtype
     assert layout == 'NCHW', "only support NCHW convolution for AVX"
@@ -141,3 +169,83 @@ def _schedule_conv(s, data, data_pad, data_vec, kernel, kernel_vec, conv_out, ou
     s[O].parallel(parallel_axis)
 
     return s
+
+
+def _declaration_conv_NCHWc(wkl, sch, data, kernel):
+    out_dtype = wkl.out_dtype
+    HPAD, WPAD = wkl.hpad, wkl.wpad
+    HSTR, WSTR = wkl.hstride, wkl.wstride
+
+    batch_size = data.shape[0]
+    out_height = (wkl.height + 2 * HPAD - wkl.hkernel) // HSTR + 1
+    out_width = (wkl.width + 2 * WPAD - wkl.wkernel) // WSTR + 1
+
+    # pack data
+    DOPAD = (HPAD != 0 and WPAD != 0)
+    if DOPAD:
+        data_pad = pad(data, (0, 0, HPAD, WPAD, 0), name="data_pad")
+    else:
+        data_pad = data
+
+    # convolution
+    oshape = (batch_size, wkl.out_filter//sch.oc_bn, out_height, out_width, sch.oc_bn)
+
+    ic = tvm.reduce_axis((0, wkl.in_filter), name='ic')
+    kh = tvm.reduce_axis((0, wkl.hkernel), name='kh')
+    kw = tvm.reduce_axis((0, wkl.wkernel), name='kw')
+
+    conv = tvm.compute(oshape, lambda n, oc_chunk, oh, ow, oc_block:
+                       tvm.sum(data_pad[n, ic//sch.ic_bn, oh*HSTR+kh, ow*WSTR+kw, ic%sch.ic_bn]
+                               .astype(out_dtype) *
+                               kernel[oc_chunk, ic//sch.ic_bn, kh, kw, ic%sch.ic_bn, oc_block],
+                               axis=[ic, kh, kw]), name='conv2d_NCHWc', tag="conv2d_NCHWc")
+
+    return conv
+
+
+def _schedule_conv_NCHWc(s, wkl, sch, data, kernel, conv_out, last):
+    # schedule data
+    A = data
+    if isinstance(s[A].op, tvm.tensor.ComputeOp):
+        batch, ic_chunk, ih, iw, ic_block = s[A].op.axis
+        parallel_axis = s[A].fuse(ic_chunk, ih)
+        s[A].parallel(parallel_axis)
+
+    # schedule 5-D NCHW[x]c conv
+    C, O = conv_out, last
+    CC = s.cache_write(C, 'global')
+
+    _, oc_chunk, oh, ow, oc_block = s[C].op.axis
+    ow_chunk, ow_block = s[C].split(ow, factor=sch.reg_n)
+    s[C].reorder(oc_chunk, oh, ow_chunk, ow_block, oc_block)
+    parallel_axis = s[C].fuse(oc_chunk, oh)
+    s[C].vectorize(oc_block)
+    if C == O:
+        s[C].parallel(parallel_axis)
+
+    s[CC].compute_at(s[C], ow_chunk)
+    _, oc_chunk, oh, ow, oc_block = s[CC].op.axis
+    ic, kh, kw = s[CC].op.reduce_axis
+
+    ow_chunk, ow_block = s[CC].split(ow, factor=sch.reg_n)
+    ic_chunk, ic_block = s[CC].split(ic, factor=sch.ic_bn)
+
+    if sch.unroll_kw:
+        s[CC].reorder(oc_chunk, oh, ow_chunk, ic_chunk, kh, ic_block, kw, ow_block, oc_block)
+        s[CC].unroll(kw)
+    else:
+        s[CC].reorder(oc_chunk, oh, ow_chunk, ic_chunk, kh, kw, ic_block, ow_block, oc_block)
+
+    s[CC].vectorize(oc_block)
+    s[CC].unroll(ow_block)
+
+    if C != O:
+        batch, oc_chunk, oh, ow, oc_block = s[O].op.axis
+        ow_chunk, ow_block = s[O].split(ow, factor=sch.reg_n)
+        s[O].reorder(oc_chunk, oh, ow_chunk, ow_block, oc_block)
+        parallel_axis = s[O].fuse(oc_chunk, oh)
+        s[C].compute_at(s[O], parallel_axis)
+        s[O].vectorize(oc_block)
+        s[O].parallel(parallel_axis)
+
+    return s

From 785c9420c1604dbaf85195c31be625f496e5776f Mon Sep 17 00:00:00 2001
From: Tatsuya Nishiyama <nishi-t@users.noreply.github.com>
Date: Tue, 15 May 2018 01:08:00 +0900
Subject: [PATCH 303/949] Delete adding a invlid path from Dockerfile.gpu
 (#1160)

---
 tests/ci_build/Dockerfile.gpu | 1 -
 1 file changed, 1 deletion(-)

diff --git a/tests/ci_build/Dockerfile.gpu b/tests/ci_build/Dockerfile.gpu
index 4b461ebf19c6..19418ed97e8c 100644
--- a/tests/ci_build/Dockerfile.gpu
+++ b/tests/ci_build/Dockerfile.gpu
@@ -50,7 +50,6 @@ RUN bash /install/ubuntu_install_vulkan.sh
 # Environment variables
 ENV PATH=/node_modules/.bin:${PATH}
 ENV PATH=/usr/local/nvidia/bin:${PATH}
-ENV PATH=/usr/clang+llvm-4.0.0-x86_64-linux-gnu-ubuntu-14.04/bin:${PATH}
 ENV PATH=/usr/local/cuda/bin:${PATH}
 ENV CPLUS_INCLUDE_PATH=/usr/local/cuda/include:${CPLUS_INCLUDE_PATH}
 ENV C_INCLUDE_PATH=/usr/local/cuda/include:${C_INCLUDE_PATH}

From 64b3d19fffdb99e211ff3408b504ed06bc9b7f00 Mon Sep 17 00:00:00 2001
From: Tatsuya Nishiyama <nishi-t@users.noreply.github.com>
Date: Wed, 16 May 2018 08:16:25 +0900
Subject: [PATCH 304/949] Fix error during running on nvptx with cuda9 (#1162)

---
 python/tvm/contrib/nvcc.py | 53 ++++++++++++++++++++++++++++++--------
 1 file changed, 42 insertions(+), 11 deletions(-)

diff --git a/python/tvm/contrib/nvcc.py b/python/tvm/contrib/nvcc.py
index f08d639cf887..5655d3965098 100644
--- a/python/tvm/contrib/nvcc.py
+++ b/python/tvm/contrib/nvcc.py
@@ -104,6 +104,28 @@ def find_cuda_path():
     raise RuntimeError("Cannot find cuda path")
 
 
+def get_cuda_version(cuda_path):
+    """Utility function to get cuda version
+
+    Parameters
+    ----------
+    cuda_path : str
+        Path to cuda root.
+
+    Returns
+    -------
+    version : float
+        The cuda version
+    """
+    version_file_path = os.path.join(cuda_path, "version.txt")
+    try:
+        with open(version_file_path) as f:
+            version_str = f.readline().replace('\n', '').replace('\r', '')
+            return float(version_str.split(" ")[2][:2])
+    except:
+        raise RuntimeError("Cannot read cuda version file")
+
+
 @register_func("tvm_callback_libdevice_path")
 def find_libdevice_path(arch):
     """Utility function to find libdevice
@@ -112,22 +134,31 @@ def find_libdevice_path(arch):
     ----------
     arch : int
         The compute architecture in int
+
+    Returns
+    -------
+    path : str
+        Path to libdevice.
     """
     cuda_path = find_cuda_path()
     lib_path = os.path.join(cuda_path, "nvvm/libdevice")
     selected_ver = 0
     selected_path = None
-
-    for fn in os.listdir(lib_path):
-        if not fn.startswith("libdevice"):
-            continue
-        ver = int(fn.split(".")[-3].split("_")[-1])
-        if ver > selected_ver and ver <= arch:
-            selected_ver = ver
-            selected_path = fn
-    if selected_path is None:
-        raise RuntimeError("Cannot find libdevice for arch {}".format(arch))
-    return os.path.join(lib_path, selected_path)
+    cuda_ver = get_cuda_version(cuda_path)
+    if cuda_ver == 9.0 or cuda_ver == 9.1:
+        path = os.path.join(lib_path, "libdevice.10.bc")
+    else:
+        for fn in os.listdir(lib_path):
+            if not fn.startswith("libdevice"):
+                continue
+            ver = int(fn.split(".")[-3].split("_")[-1])
+            if ver > selected_ver and ver <= arch:
+                selected_ver = ver
+                selected_path = fn
+        if selected_path is None:
+            raise RuntimeError("Cannot find libdevice for arch {}".format(arch))
+        path = os.path.join(lib_path, selected_path)
+    return path
 
 
 def callback_libdevice_path(arch):

From 3327ac458cdf58c9da31ac47591ffaf42b73ecd0 Mon Sep 17 00:00:00 2001
From: Tatsuya Nishiyama <nishi-t@users.noreply.github.com>
Date: Fri, 18 May 2018 01:30:12 +0900
Subject: [PATCH 305/949] Add count_include_pad support to AvgPool (#1163)

* Add count_include_pad support to AvgPool

* Fix python_cpp/test_topi_pooling.py

* Change auto to explicitly type, and fix format.
---
 CONTRIBUTORS.md                            |  1 +
 topi/include/topi/nn/pooling.h             | 27 ++++++++++++++++++----
 topi/python/topi/nn/pooling.py             | 14 +++++++++--
 topi/src/topi.cc                           |  2 +-
 topi/tests/python/test_topi_pooling.py     | 20 +++++++++++-----
 topi/tests/python_cpp/test_topi_pooling.py | 18 +++++++++++----
 6 files changed, 64 insertions(+), 18 deletions(-)

diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 08f240fecfd5..106a390169cd 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -62,3 +62,4 @@ List of Contributors
 - [Haolong Zhang](https://github.com/haolongzhangm)
 - [Cody Hao Yu](https://github.com/comaniac)
 - [Chris Nuernberger](https://github.com/cnuernber)
+- [Tatsuya Nishiyama](https://github.com/nishi-t)
diff --git a/topi/include/topi/nn/pooling.h b/topi/include/topi/nn/pooling.h
index 8613045b168f..20a7ffe975bf 100644
--- a/topi/include/topi/nn/pooling.h
+++ b/topi/include/topi/nn/pooling.h
@@ -36,6 +36,7 @@ enum PoolType : int {
 * \param ceil_mode Whether to use ceil when calculating the output size
 * \param height_axis index of the height dimension
 * \param width_axis index of the width dimension
+* \param count_include_pad Whether include padding in the calculation
 *
 * \return The output tensor in same layout order
 */
@@ -46,7 +47,8 @@ inline Tensor pool_impl(const Tensor& x,
                         PoolType pool_type,
                         bool ceil_mode,
                         const size_t height_axis,
-                        const size_t width_axis) {
+                        const size_t width_axis,
+                        bool count_include_pad) {
   CHECK(x->shape.size() >= 2) << "Pooling input must >= 2-D (H, W)";
   CHECK_EQ(kernel_size.size(), 2) << "Pooling kernel_size must have 2 elements";
   CHECK_EQ(stride_size.size(), 2) << "Pooling stride_size must have 2 elements";
@@ -120,7 +122,19 @@ inline Tensor pool_impl(const Tensor& x,
 
     return tvm::compute(out_shape,
     [&](const Array<Var>& output) {
-      return tsum(output) / (kernel_height * kernel_width);
+      if (count_include_pad) {
+        return tsum(output) / (kernel_height * kernel_width);
+      } else {
+        Expr h_start = output[height_axis] * stride_height - padding_height;
+        Expr w_start = output[width_axis] * stride_width - padding_width;
+        Expr h_end = ir::Min::make(h_start + kernel_height, height);
+        Expr w_end = ir::Min::make(w_start + kernel_width, width);
+        h_start = ir::Max::make(h_start, make_const(Int(32), 0));
+        w_start = ir::Max::make(w_start, make_const(Int(32), 0));
+        Expr divide_factor = ir::Max::make((h_end - h_start) * (w_end - w_start),
+                                           make_const(Int(32), 1));
+        return tsum(output) / divide_factor;
+      }
     }, "tensor", kElementWise);
   } else {
     LOG(ERROR) << "Unrecognized pool_type: " << pool_type;
@@ -177,6 +191,9 @@ inline bool find_height_width(const std::string& layout,
 *        it can be used to decide the output shape).
 *        Since pooling does not care about the factor size of dimensions
 *        other than `H` and `W`, one can pass `NCHWc` as well.
+* \param  count_include_pad Whether include padding in the calculation when pool_type is 'avg'
+*        
+*
 * \return The output tensor in the same layout
 */
 inline Tensor pool(const Tensor& x,
@@ -185,12 +202,14 @@ inline Tensor pool(const Tensor& x,
                    const Array<Expr>& padding_size,
                    PoolType pool_type,
                    bool ceil_mode,
-                   const std::string& layout = "NCHW") {
+                   const std::string& layout = "NCHW",
+                   bool count_include_pad = true) {
   int height_axis = -1, width_axis = -1;
   CHECK(find_height_width(layout, &height_axis, &width_axis))
     << "Unsupported layout " << layout;
   return pool_impl(x, kernel_size, stride_size, padding_size,
-                   pool_type, ceil_mode, height_axis, width_axis);
+                   pool_type, ceil_mode, height_axis, width_axis,
+                   count_include_pad);
 }
 
 /*!
diff --git a/topi/python/topi/nn/pooling.py b/topi/python/topi/nn/pooling.py
index c51ffa93810c..910b556a5c9d 100644
--- a/topi/python/topi/nn/pooling.py
+++ b/topi/python/topi/nn/pooling.py
@@ -42,7 +42,14 @@ def global_pool(data, pool_type, layout="NCHW"):
     return cpp.nn.global_pool(data, POOL_TYPE_CODE[pool_type], layout)
 
 
-def pool(data, kernel, stride, padding, pool_type, ceil_mode=False, layout="NCHW"):
+def pool(data,
+         kernel,
+         stride,
+         padding,
+         pool_type,
+         ceil_mode=False,
+         layout="NCHW",
+         count_include_pad=True):
     """Perform pooling on height and width dimension of data.
        It decides the height and width dimension according to the layout string,
        in which 'W' and 'H' means width and height respectively.
@@ -80,10 +87,13 @@ def pool(data, kernel, stride, padding, pool_type, ceil_mode=False, layout="NCHW
         [batch_size, channel, height, width, channel_block],
         in which channel_block=16 is a split of dimension channel.
 
+    count_include_pad: bool
+        Whether include padding in the calculation when pool_type is 'avg'
+
     Returns
     -------
     output : tvm.Tensor
         n-D in the same layout
     """
     return cpp.nn.pool(data, kernel, stride, padding,
-                       POOL_TYPE_CODE[pool_type], ceil_mode, layout)
+                       POOL_TYPE_CODE[pool_type], ceil_mode, layout, count_include_pad)
diff --git a/topi/src/topi.cc b/topi/src/topi.cc
index ae928cd907da..f30f11afe557 100644
--- a/topi/src/topi.cc
+++ b/topi/src/topi.cc
@@ -322,7 +322,7 @@ TVM_REGISTER_GLOBAL("topi.nn.pool")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
   *rv = nn::pool(args[0], args[1], args[2], args[3],
                  static_cast<nn::PoolType>(static_cast<int>(args[4])),
-                 args[5], args[6]);
+                 args[5], args[6], args[7]);
   });
 
 TVM_REGISTER_GLOBAL("topi.nn.global_pool")
diff --git a/topi/tests/python/test_topi_pooling.py b/topi/tests/python/test_topi_pooling.py
index 52ae3a0fd06e..14a67f11bfe1 100644
--- a/topi/tests/python/test_topi_pooling.py
+++ b/topi/tests/python/test_topi_pooling.py
@@ -5,14 +5,14 @@
 import math
 from topi.util import get_const_tuple
 
-def verify_pool(n, ic, ih, kh, sh, padding, pool_type, ceil_mode):
+def verify_pool(n, ic, ih, kh, sh, padding, pool_type, ceil_mode, count_include_pad=True):
     iw = ih
     kw = kh
     sw = sh
     ph, pw = padding
     A = tvm.placeholder((n, ic, ih, iw), name='A')
     B = topi.nn.pool(A, kernel=[kh, kw], stride=[sh, sw], padding=padding,
-                     pool_type=pool_type, ceil_mode=ceil_mode)
+                     pool_type=pool_type, ceil_mode=ceil_mode, count_include_pad=count_include_pad)
     B = topi.nn.relu(B)
     dtype = A.dtype
 
@@ -26,7 +26,7 @@ def verify_pool(n, ic, ih, kh, sh, padding, pool_type, ceil_mode):
         assert bshape[3] == int(math.floor(float(ashape[3] - kw + pw * 2) / sw) + 1)
 
 
-    a_np = np.random.uniform(size=(n, ic, ih, iw)).astype(dtype)
+    a_np = np.random.uniform(low=0.001, size=(n, ic, ih, iw)).astype(dtype)
     pad_np = np.zeros(shape=(n, ic, ih+2*ph, iw+2*pw)).astype(dtype)
     no_zero = (range(n), range(ic), (range(ph, ih+ph)), (range(pw, iw+pw)))
     pad_np[np.ix_(*no_zero)] = a_np
@@ -36,7 +36,12 @@ def verify_pool(n, ic, ih, kh, sh, padding, pool_type, ceil_mode):
     if pool_type == 'avg':
         for i in range(oh):
             for j in range(ow):
-                b_np[:,:,i,j] = np.mean(pad_np[:, :, i*sh:i*sh+kh, j*sw:j*sw+kw], axis=(2,3))
+                if count_include_pad:
+                    b_np[:,:,i,j] = np.mean(pad_np[:, :, i*sh:i*sh+kh, j*sw:j*sw+kw], axis=(2,3))
+                else:
+                    pad_count = np.sum(pad_np[:, :, i*sh:i*sh+kh, j*sw:j*sw+kw] > 0, axis=(2,3))
+                    b_np[:,:,i,j] = np.sum(pad_np[:, :, i*sh:i*sh+kh, j*sw:j*sw+kw], axis=(2,3)) / np.maximum(pad_count, 1)
+
     elif pool_type =='max':
         for i in range(oh):
             for j in range(ow):
@@ -62,8 +67,11 @@ def check_device(device):
         check_device(device)
 
 def test_pool():
-    verify_pool(1, 256, 32, 2, 2, [0, 0], 'avg', False)
-    verify_pool(1, 256, 31, 3, 3, [1, 2], 'avg', False)
+    verify_pool(1, 256, 32, 2, 2, [0, 0], 'avg', False, True)
+    verify_pool(1, 256, 31, 3, 3, [1, 2], 'avg', False, True)
+    verify_pool(1, 256, 32, 2, 2, [1, 2], 'avg', False, False)
+    verify_pool(1, 256, 31, 4, 4, [3, 3], 'avg', False, False)
+    verify_pool(1, 256, 31, 4, 4, [0, 0], 'avg', False, False)
     verify_pool(1, 256, 32, 2, 2, [0, 0], 'max', False)
     verify_pool(1, 256, 31, 3, 3, [2, 1], 'max', False)
     verify_pool(1, 256, 31, 3, 3, [2, 1], 'max', True)
diff --git a/topi/tests/python_cpp/test_topi_pooling.py b/topi/tests/python_cpp/test_topi_pooling.py
index ce7e65343e2c..a2bbc3227d94 100644
--- a/topi/tests/python_cpp/test_topi_pooling.py
+++ b/topi/tests/python_cpp/test_topi_pooling.py
@@ -9,14 +9,14 @@
     "avg": 0,
     "max": 1
 }
-def verify_pool(n, ic, ih, kh, sh, padding, pool_type, ceil_mode):
+def verify_pool(n, ic, ih, kh, sh, padding, pool_type, ceil_mode, count_include_pad=True):
     iw = ih
     kw = kh
     sw = sh
     ph, pw = padding
     A = tvm.placeholder((n, ic, ih, iw), name='A')
     B = topi.cpp.nn.pool(A, [kh, kw], [sh, sw], padding,
-                         pool_code[pool_type], ceil_mode, "NCHW")
+                         pool_code[pool_type], ceil_mode, "NCHW", count_include_pad)
     B = topi.cpp.nn.relu(B)
     dtype = A.dtype
 
@@ -40,7 +40,12 @@ def verify_pool(n, ic, ih, kh, sh, padding, pool_type, ceil_mode):
     if pool_type == 'avg':
         for i in range(oh):
             for j in range(ow):
-                b_np[:,:,i,j] = np.mean(pad_np[:, :, i*sh:i*sh+kh, j*sw:j*sw+kw], axis=(2,3))
+                if count_include_pad:
+                    b_np[:,:,i,j] = np.mean(pad_np[:, :, i*sh:i*sh+kh, j*sw:j*sw+kw], axis=(2,3))
+                else:
+                    pad_count = np.sum(pad_np[:, :, i*sh:i*sh+kh, j*sw:j*sw+kw] > 0, axis=(2,3))
+                    b_np[:,:,i,j] = np.sum(pad_np[:, :, i*sh:i*sh+kh, j*sw:j*sw+kw], axis=(2,3)) / np.maximum(pad_count, 1)
+
     elif pool_type =='max':
         for i in range(oh):
             for j in range(ow):
@@ -68,8 +73,11 @@ def check_device(device):
         check_device(device)
 
 def test_pool():
-    verify_pool(1, 256, 32, 2, 2, [0, 0], 'avg', False)
-    verify_pool(1, 256, 31, 3, 3, [1, 2], 'avg', False)
+    verify_pool(1, 256, 32, 2, 2, [0, 0], 'avg', False, True)
+    verify_pool(1, 256, 31, 3, 3, [1, 2], 'avg', False, True)
+    verify_pool(1, 256, 32, 2, 2, [1, 2], 'avg', False, False)
+    verify_pool(1, 256, 31, 4, 4, [3, 3], 'avg', False, False)
+    verify_pool(1, 256, 31, 4, 4, [0, 0], 'avg', False, False)
     verify_pool(1, 256, 32, 2, 2, [0, 0], 'max', False)
     verify_pool(1, 256, 31, 3, 3, [2, 1], 'max', False)
     verify_pool(1, 256, 31, 3, 3, [2, 1], 'max', True)

From a5c0b56d0b82c689c79d9abcb5ff1a758e85dd3a Mon Sep 17 00:00:00 2001
From: wfu <wfu@college.harvard.edu>
Date: Fri, 18 May 2018 23:04:11 +0000
Subject: [PATCH 306/949] [llvm] fixed issue with llvm 5 vs 6 (#1167)

---
 src/codegen/llvm/codegen_amdgpu.cc | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/codegen/llvm/codegen_amdgpu.cc b/src/codegen/llvm/codegen_amdgpu.cc
index 97858079faaa..de18b2d60b86 100644
--- a/src/codegen/llvm/codegen_amdgpu.cc
+++ b/src/codegen/llvm/codegen_amdgpu.cc
@@ -200,8 +200,13 @@ runtime::Module BuildAMDGPU(Array<LoweredFunc> funcs, std::string target) {
   dest_ll.SetUnbuffered();
   destAsm.SetUnbuffered();
   module->print(dest_ll, nullptr);
+#if TVM_LLVM_VERSION <= 60
   std::unique_ptr<llvm::Module> mAsm = llvm::CloneModule(module.get());
   std::unique_ptr<llvm::Module> mObj = llvm::CloneModule(module.get());
+#else
+  std::unique_ptr<llvm::Module> mAsm = llvm::CloneModule(*module.get());
+  std::unique_ptr<llvm::Module> mObj = llvm::CloneModule(*module.get());
+#endif
   llvm::legacy::PassManager pass;
 
   CHECK(tm->addPassesToEmitFile(

From c9321b6dbb37be5276dc5abeb8c86ae7ecdd5735 Mon Sep 17 00:00:00 2001
From: webberg <webbery@foxmail.com>
Date: Sat, 19 May 2018 07:05:27 +0800
Subject: [PATCH 307/949] #1159 bug fixed (#1164)

* #1159 bug fixed

* #1159 #1164 fixed
---
 include/tvm/operation.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/include/tvm/operation.h b/include/tvm/operation.h
index 3c819ff02ab8..67dfe138cae6 100644
--- a/include/tvm/operation.h
+++ b/include/tvm/operation.h
@@ -181,7 +181,7 @@ class PlaceholderOpNode : public OperationNode {
 /*!
  * \brief A Compute op that compute a tensor on certain domain.
  */
-class ComputeOpNode : public OperationNode {
+class TVM_DLL ComputeOpNode : public OperationNode {
  public:
   /*! \brief IterVar on each axis */
   Array<IterVar> axis;

From 89ee18cea161c857bdb49f62f3c142274a614613 Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Sat, 19 May 2018 06:36:29 +0530
Subject: [PATCH 308/949] [TOPI] flip (#1161)

---
 topi/include/topi/transform.h            | 40 ++++++++++++++++++++++++
 topi/python/topi/transform.py            | 18 +++++++++++
 topi/src/topi.cc                         |  5 +++
 topi/tests/python/test_topi_transform.py | 30 ++++++++++++++++++
 4 files changed, 93 insertions(+)

diff --git a/topi/include/topi/transform.h b/topi/include/topi/transform.h
index 9e4c8b06366f..2b4ba9a751f6 100644
--- a/topi/include/topi/transform.h
+++ b/topi/include/topi/transform.h
@@ -107,6 +107,46 @@ inline Tensor transpose(const Tensor& x,
     }, name, tag);
 }
 
+/*!
+* \brief flip/reverse elements of an array in a particular axis
+*
+* \param x The input tensor
+* \param axis The axis along which the tensors will be reveresed
+* (allows negative indices)
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the reverse operation
+*/
+inline Tensor flip(const Tensor& x,
+                   int axis = 0,
+                   std::string name = "tensor",
+                   std::string tag = kInjective) {
+  size_t src_tensor_dim = x->shape.size();
+  int axis_inp = axis;
+
+  if (axis < 0) {
+    axis = static_cast<int>(x->shape.size()) + axis;
+  }
+
+  CHECK((0 <= axis) && (axis < static_cast<int>(x->shape.size())))
+    << "axis=" << axis_inp << " is invalid for the "
+    << static_cast<int>(x->shape.size()) << "-dimensional input tensor";
+
+  // Reverse the Input Tensor in the axis specified
+  return compute(
+    x->shape, [&](const Array<Var>& indices) {
+      Array<Expr> real_indices;
+      for (size_t i = 0; i < src_tensor_dim; ++i) {
+        if (i == static_cast<size_t>(axis)) {
+          real_indices.push_back(x->shape[i] - indices[i] - 1);
+        } else {
+          real_indices.push_back(indices[i]);
+        }
+      }
+      return x(real_indices);
+    }, name, tag);
+}
 
 /*!
 * \brief Reshape a tensor
diff --git a/topi/python/topi/transform.py b/topi/python/topi/transform.py
index 33ed6c89c357..774255ca8617 100644
--- a/topi/python/topi/transform.py
+++ b/topi/python/topi/transform.py
@@ -5,6 +5,7 @@
 import topi
 from . import tag
 from .util import ravel_index, unravel_index, get_const_int, get_const_tuple
+from . import cpp
 
 @tvm.tag_scope(tag=tag.BROADCAST)
 def expand_dims(a, axis, num_newaxis=1):
@@ -110,6 +111,23 @@ def _compute(*indices):
         return a(*idx)
     return tvm.compute(new_shape, _compute)
 
+@tvm.tag_scope(tag=tag.INJECTIVE)
+def flip(a, axis=0):
+    """Flip/reverse elements of an array in a particular axis.
+
+    Parameters
+    ----------
+    a : tvm.Tensor
+        The tensor to be expanded.
+
+    axis : int, optional
+        The axis along which the tensors will be reveresed.
+
+    Returns
+    -------
+    ret : tvm.Tensor
+    """
+    return cpp.flip(a, axis)
 
 @tvm.tag_scope(tag=tag.INJECTIVE)
 def reshape(a, newshape):
diff --git a/topi/src/topi.cc b/topi/src/topi.cc
index f30f11afe557..65a29fee596e 100644
--- a/topi/src/topi.cc
+++ b/topi/src/topi.cc
@@ -241,6 +241,11 @@ TVM_REGISTER_GLOBAL("topi.transpose")
   *rv = transpose(args[0], args[1]);
   });
 
+TVM_REGISTER_GLOBAL("topi.flip")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = flip(args[0], args[1]);
+  });
+
 TVM_REGISTER_GLOBAL("topi.reshape")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
   *rv = reshape(args[0], args[1]);
diff --git a/topi/tests/python/test_topi_transform.py b/topi/tests/python/test_topi_transform.py
index f1f39816bcbd..3c54dbbf16d3 100644
--- a/topi/tests/python/test_topi_transform.py
+++ b/topi/tests/python/test_topi_transform.py
@@ -184,6 +184,28 @@ def check_device(device):
     for device in ["llvm"]:
         check_device(device)
 
+def verify_flip(in_shape, axis):
+    A = tvm.placeholder(shape=in_shape, name="A")
+    B = topi.flip(A, axis) + 1
+    def check_device(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s = topi.generic.schedule_injective(B)
+
+        foo = tvm.build(s, [A, B], device, name="reverse")
+        x_np = np.random.uniform(size=in_shape).astype(A.dtype)
+        out_npy = np.flip(x_np, axis) + 1
+        data_nd = tvm.nd.array(x_np, ctx)
+        out_nd = tvm.nd.empty(out_npy.shape, ctx=ctx, dtype=A.dtype)
+        foo(data_nd, out_nd)
+        np.testing.assert_allclose(out_nd.asnumpy(), out_npy)
+
+    for device in ["llvm", "cuda", "opencl"]:
+        check_device(device)
 
 def test_expand_dims():
     verify_expand_dims((3, 10), (3, 10, 1, 1), 2, 2)
@@ -226,6 +248,13 @@ def test_split():
     verify_split((2, 12, 3), [2, 4], 1)
     verify_split((10, 12, 24), [5, 7, 9], -1)
 
+def test_flip():
+    verify_flip((3, 4, 3), 1)
+    verify_flip((3, 4, 3), 0)
+    verify_flip((3, 4, 3), 2)
+    verify_flip((3, 4, 3), -1)
+    verify_flip((3, 4, 3), -3)
+    verify_flip((3, 4, 3), -2)
 
 def test_expand_like():
     verify_expand_like((3,), (2, 3), [0])
@@ -241,4 +270,5 @@ def test_expand_like():
     test_reshape()
     test_squeeze()
     test_split()
+    test_flip()
     test_expand_like()

From 4472b078d0abb20ff7f7bdab5ef2e42c33e90f87 Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Sat, 19 May 2018 09:21:11 +0530
Subject: [PATCH 309/949] [TOPI] add take (#1158)

---
 topi/include/topi/transform.h                | 81 ++++++++++++++++++++
 topi/python/topi/transform.py                | 25 ++++++
 topi/src/topi.cc                             | 10 +++
 topi/tests/python/test_topi_transform.py     | 50 ++++++++++++
 topi/tests/python_cpp/test_topi_transform.py | 50 ++++++++++++
 5 files changed, 216 insertions(+)

diff --git a/topi/include/topi/transform.h b/topi/include/topi/transform.h
index 2b4ba9a751f6..822a8e26e5fd 100644
--- a/topi/include/topi/transform.h
+++ b/topi/include/topi/transform.h
@@ -398,5 +398,86 @@ inline Array<Tensor> split_sections(const Tensor& x,
   return split(x, split_indices, axis, name, tag);
 }
 
+/*!
+* \brief Take elements from an flattened input array when axis is None.
+*
+* \param a The source array.
+* \param indices The indices of the values to extract.
+* \param name The name of the operation.
+* \param tag The tag to mark the operation.
+*
+* \return A Tensor whose op member is the take operation
+*/
+inline Tensor take(const Tensor& a,
+                   const Tensor& indices,
+                   std::string name = "tensor",
+                   std::string tag = kInjective) {
+  Array<Expr> a_shape = a->shape;
+  Array<Expr> out_shape;
+  for (size_t j = 0; j < indices->shape.size(); ++j) {
+    out_shape.push_back(indices->shape[j]);
+  }
+
+  return compute(
+        out_shape, [&](const Array<Var>& out_index) {
+          Array<Expr> indices_position;
+          for (size_t j = 0; j < indices->shape.size(); ++j) {
+            indices_position.push_back(out_index[j]);
+          }
+          return a(UnavelIndex(indices(indices_position), a_shape));
+        }, name, tag);
+}
+
+/*!
+* \brief Take elements from an array along an axis.
+*
+* \param a The source array.
+* \param indices The indices of the values to extract.
+* \param axis The axis over which to select values. By default,
+* the flattened input array is used.
+* \param name The name of the operation.
+* \param tag The tag to mark the operation.
+*
+* \return A Tensor whose op member is the take operation
+*/
+inline Tensor take(const Tensor& a,
+                   const Tensor& indices,
+                   int axis,
+                   std::string name = "tensor",
+                   std::string tag = kInjective) {
+  if (axis < 0) {
+    axis += static_cast<int>(a->shape.size());
+  }
+  CHECK_LT(axis, a->shape.size()) << "axis out of bounds";
+
+  int indices_len = static_cast<int>(indices->shape.size());
+  Array<Expr> out_shape;
+  for (size_t i = 0; i < a->shape.size(); ++i) {
+    if (axis == static_cast<int>(i)) {
+      for (size_t j = 0; j < indices->shape.size(); ++j) {
+        out_shape.push_back(indices->shape[j]);
+      }
+    } else {
+      out_shape.push_back(a->shape[i]);
+    }
+  }
+  return compute(
+        out_shape, [&](const Array<Var>& out_index) {
+          Array<Expr> indices_position;
+          for (size_t j = axis; j < static_cast<size_t>(axis+indices_len); ++j) {
+            indices_position.push_back(out_index[j]);
+          }
+          Array<Expr> real_indices;
+          for (size_t j = 0; j < static_cast<size_t>(axis); ++j) {
+            real_indices.push_back(out_index[j]);
+          }
+          real_indices.push_back(indices(indices_position));
+          for (size_t j = axis + indices_len; j < out_index.size(); ++j) {
+            real_indices.push_back(out_index[j]);
+          }
+          return a(real_indices);
+        }, name, tag);
+}
+
 }  // namespace topi
 #endif  // TOPI_TRANSFORM_H_
diff --git a/topi/python/topi/transform.py b/topi/python/topi/transform.py
index 774255ca8617..2fba99f793c5 100644
--- a/topi/python/topi/transform.py
+++ b/topi/python/topi/transform.py
@@ -286,3 +286,28 @@ def _compute(begin, *indices):
                         lambda *indices: _compute(begin_id, *indices), name="s%d" %i)
             for i, (out_shape, begin_id) in enumerate(zip(out_shapes, begin_ids))]
     # pylint: enable=cell-var-from-loop
+
+
+@tvm.tag_scope(tag=tag.INJECTIVE)
+def take(a, indices, axis=None):
+    """Take elements from an array along an axis.
+
+    Parameters
+    ----------
+    a : tvm.Tensor
+        The source array.
+
+    indices : tvm.Tensor
+        The indices of the values to extract.
+
+    axis : int, optional
+        The axis over which to select values. By default,
+        the flattened input array is used.
+
+    Returns
+    -------
+    ret : tvm.Tensor
+    """
+    if axis is None:
+        return cpp.take(a, indices)
+    return cpp.take(a, indices, int(axis))
diff --git a/topi/src/topi.cc b/topi/src/topi.cc
index 65a29fee596e..f4feafb043f1 100644
--- a/topi/src/topi.cc
+++ b/topi/src/topi.cc
@@ -270,6 +270,16 @@ TVM_REGISTER_GLOBAL("topi.split")
   }
   });
 
+TVM_REGISTER_GLOBAL("topi.take")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  if (args.size() == 2) {
+    *rv = take(args[0], args[1]);
+  } else {
+    int axis = args[2];
+    *rv = take(args[0], args[1], axis);
+  }
+  });
+
 /* Ops from nn/batch_norm.h */
 TVM_REGISTER_GLOBAL("topi.nn.batch_norm_inference")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
diff --git a/topi/tests/python/test_topi_transform.py b/topi/tests/python/test_topi_transform.py
index 3c54dbbf16d3..0454e69f2694 100644
--- a/topi/tests/python/test_topi_transform.py
+++ b/topi/tests/python/test_topi_transform.py
@@ -207,6 +207,46 @@ def check_device(device):
     for device in ["llvm", "cuda", "opencl"]:
         check_device(device)
 
+def verify_take(src_shape, indices_src, axis=None):
+    src_dtype = "float32"
+    indices_dtype = "int32"
+    indices_src = np.array(indices_src, dtype=indices_dtype)
+    A = tvm.placeholder(shape=src_shape, dtype=src_dtype, name="A")
+    indices = tvm.placeholder(shape=indices_src.shape, dtype=indices_dtype, name="indices")
+    if axis is None:
+        out_tensor = topi.take(a=A, indices=indices)
+    else:
+        out_tensor = topi.take(a=A, indices=indices, axis=axis)
+
+    def check_device(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s = topi.generic.schedule_injective(out_tensor)
+
+        foo = tvm.build(s, [A] + [indices] + [out_tensor] , device, name="take")
+        shape_size = 1
+        for i in range(len(src_shape)):
+            shape_size = shape_size * src_shape[i]
+        data_npy = np.arange(shape_size, dtype=src_dtype).reshape((src_shape))
+
+        if axis is None:
+            out_npys = np.take(data_npy, indices_src)
+        else:
+            out_npys = np.take(data_npy, indices_src, axis=axis)
+        data_nd = tvm.nd.array(data_npy, ctx)
+        indices_nd = tvm.nd.array(indices_src, ctx)
+        out_nd = tvm.nd.empty(out_npys.shape, ctx=ctx, dtype=src_dtype)
+        foo(data_nd, indices_nd, out_nd)
+        np.testing.assert_allclose(out_nd.asnumpy(), out_npys)
+
+    for device in ["llvm", "opencl"]:
+        check_device(device)
+
+
 def test_expand_dims():
     verify_expand_dims((3, 10), (3, 10, 1, 1), 2, 2)
     verify_expand_dims((3, 10), (1, 3, 10), -3, 1)
@@ -262,6 +302,15 @@ def test_expand_like():
     verify_expand_like((3, 4), (3, 5, 4), [1])
     verify_expand_like((5, 7), (5, 6, 7, 8), [1, 3])
 
+def test_take():
+    verify_take((4,), [1])
+    verify_take((4,), [[0,1,2,3]])
+    verify_take((3,3,3), [[11,25]])
+    verify_take((4,), [[0,1],[2,3]])
+    verify_take((4,), [1], 0)
+    verify_take((2,2), [[[1,0],[0,1]]], 0)
+    verify_take((2,2), [[[1,0],[0,1]]], 1)
+    verify_take((4,3,5,6), [[2,1,0,0]], -2)
 
 if __name__ == "__main__":
     test_concatenate()
@@ -272,3 +321,4 @@ def test_expand_like():
     test_split()
     test_flip()
     test_expand_like()
+    test_take()
diff --git a/topi/tests/python_cpp/test_topi_transform.py b/topi/tests/python_cpp/test_topi_transform.py
index 0f46be6a7b2e..2f1aa14b2296 100644
--- a/topi/tests/python_cpp/test_topi_transform.py
+++ b/topi/tests/python_cpp/test_topi_transform.py
@@ -167,6 +167,45 @@ def check_device(device):
     for device in ["llvm", "nvptx", "cuda", "opencl", "metal", "rocm"]:
         check_device(device)
 
+def verify_take(src_shape, indices_src, axis=None):
+    src_dtype = "float32"
+    indices_dtype = "int32"
+    indices_src = np.array(indices_src, dtype=indices_dtype)
+    A = tvm.placeholder(shape=src_shape, dtype=src_dtype, name="A")
+    indices = tvm.placeholder(shape=indices_src.shape, dtype=indices_dtype, name="indices")
+    if axis is None:
+        out_tensor = topi.cpp.take(A, indices)
+    else:
+        out_tensor = topi.cpp.take(A, indices, axis)
+
+    def check_device(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s = topi.generic.schedule_injective(out_tensor)
+
+        foo = tvm.build(s, [A] + [indices] + [out_tensor] , device, name="take")
+        shape_size = 1
+        for i in range(len(src_shape)):
+            shape_size = shape_size * src_shape[i]
+        data_npy = np.arange(shape_size, dtype=src_dtype).reshape((src_shape))
+
+        if axis is None:
+            out_npys = np.take(data_npy, indices_src)
+        else:
+            out_npys = np.take(data_npy, indices_src, axis=axis)
+        data_nd = tvm.nd.array(data_npy, ctx)
+        indices_nd = tvm.nd.array(indices_src, ctx)
+        out_nd = tvm.nd.empty(out_npys.shape, ctx=ctx, dtype=src_dtype)
+        foo(data_nd, indices_nd, out_nd)
+        np.testing.assert_allclose(out_nd.asnumpy(), out_npys)
+
+    for device in ["llvm", "opencl"]:
+        check_device(device)
+
 
 def test_expand_dims():
     verify_expand_dims((3, 10), (3, 10, 1, 1), 2, 2)
@@ -209,6 +248,16 @@ def test_split():
     verify_split((2, 12, 3), [2, 4], 1)
     verify_split((10, 12, 24), [5, 7, 9], -1)
 
+def test_take():
+    verify_take((4,), [1])
+    verify_take((4,), [[0,1,2,3]])
+    verify_take((3,3,3), [[11,25]])
+    verify_take((4,), [[0,1],[2,3]])
+    verify_take((4,), [1], 0)
+    verify_take((2,2), [[[1,0],[0,1]]], 0)
+    verify_take((2,2), [[[1,0],[0,1]]], 1)
+    verify_take((4,3,5,6), [[2,1,0,0]], -2)
+
 if __name__ == "__main__":
     test_concatenate()
     test_tranpose()
@@ -216,3 +265,4 @@ def test_split():
     test_reshape()
     test_squeeze()
     test_split()
+    test_take()
\ No newline at end of file

From cd9801cecf5e7777316c89de1e2d1599f231a063 Mon Sep 17 00:00:00 2001
From: Masahiro Sakai <masahiro.sakai@gmail.com>
Date: Mon, 21 May 2018 12:59:40 +0900
Subject: [PATCH 310/949] fix wrong type declaration of float64 "log" in
 intrin_math.py (#1169)

---
 tutorials/language/intrin_math.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tutorials/language/intrin_math.py b/tutorials/language/intrin_math.py
index 50668dab8812..44c0692c04fa 100644
--- a/tutorials/language/intrin_math.py
+++ b/tutorials/language/intrin_math.py
@@ -116,7 +116,7 @@ def my_cuda_mylog_rule(op):
     if op.dtype == "float32":
         return tvm.call_pure_extern("float32", "logf", op.args[0])
     elif op.dtype == "float64":
-        return tvm.call_pure_extern("float32", "log", op.args[0])
+        return tvm.call_pure_extern("float64", "log", op.args[0])
     else:
         return op
 tvm.register_intrin_rule("cuda", "mylog", my_cuda_mylog_rule, override=True)

From 573c6c448d3e869a38d244f5ef8b45b76680fc2e Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 20 May 2018 21:18:26 -0700
Subject: [PATCH 311/949] Release 0.3 (#1171)

---
 NEWS.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 55 insertions(+)

diff --git a/NEWS.md b/NEWS.md
index dd165b75c0e0..567aabf3fcbd 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -9,6 +9,61 @@ Refer to the Roadmap issue for complete list on on-going version features.
 If you check in something that is not reflected in Roadmap issue, please reply
 to that issue so it can get added.
 
+## 0.3
+
+This release features numerous improvements in TOPI and backends. We make the first step toward object detection support in TOPI, featuring operators necessary for YOLO and SSDs. The topi now supports numpy-style API and operator overloading. RPC is significantly improved to support resource allocation and using a pool of devices. We are adding two new backends: WebGL for running GPUs on the browser, and Vulkan for running on next-generation graphics API.
+
+- TOPI Vision operators
+   - SSD support
+   - YOLO support
+   - NMS operator support in vision
+- TOPI general numpy-style operators
+   - numpy style operator overload in topi
+   - more operators: flip, take
+   - dilation support on conv2d and depthwise
+- 8bit support
+    - ARM 8bit gemm
+    - ARM 8bit conv
+- Low bit operator support
+    - popcount intrinsics
+    - 1-bit fully connected
+- Contrib: MPSDNN fully-connected and conv2d support
+- Better RPC support
+   - RPC Tracker support to allow centralized resource management
+   - RPC protocol upgrade (this is a non-backward compatible change) to support timeout in the proxy
+     - This is a breaking change, need to use the latest version of TVM runtime with the RPC
+   - Fault-tolerant to early server termination with correct exception propagated
+   - RPC support enabled for ROCm AMDGPUs
+- Tutorials and docs
+  - How to deploy to android devices.
+- Optimizations for hardware backends
+  - intel CPU (AVX and AVX512)
+- Schedule Primitives
+   - rfactor now support factor_axis to specify the factored dimension in the result
+   - cache_write now support multiple output operators
+   - enable warp memory which generates shuffle instructions
+- Framework bridge
+  - MXNet bridge supported
+- C++ compiler API support
+   - build migration
+   - topi migration to c++
+   - Target system in c++
+- WebGL backend
+   - runtime and codegen
+   - topi integration
+   - end to end pipeline on the browser
+- Vulkan backend
+   - vulkan runtime
+   - spirv code generator
+- Security
+    - intel SGX runtime support
+    - multi-threaded SGX runtime
+- LLVM 7.0 support
+- Robustness
+   - VerifyMemory to verify incorrect GPU schedules that writes into GPU memory from cpu
+   - Verify compute formulas
+- Better CPU parallel runtime
+
 ## 0.2
 
 This release comes with a complete set of TOPI support for NNVM compiler, which allows compilation of end to end workloads.

From bff56325cc74ee9d0b6cb1885f9ebb2b885f363c Mon Sep 17 00:00:00 2001
From: Leyuan Wang <laurawly@gmail.com>
Date: Mon, 21 May 2018 15:44:04 -0700
Subject: [PATCH 312/949] barrier fence added for warp mem (#1174)

---
 python/tvm/build_module.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/python/tvm/build_module.py b/python/tvm/build_module.py
index 5728c4f28c8f..72b89af020d7 100755
--- a/python/tvm/build_module.py
+++ b/python/tvm/build_module.py
@@ -454,6 +454,7 @@ def build(sch,
             if current_build_config().detect_global_barrier:
                 func = ir_pass.ThreadSync(func, "global")
             func = ir_pass.ThreadSync(func, "shared")
+            func = ir_pass.ThreadSync(func, "warp")
             warp_size = target.thread_warp_size
             func = ir_pass.LowerThreadAllreduce(func, warp_size)
             fsplits = [s for s in ir_pass.SplitHostDevice(func)]

From ecfdec54f0cd94ba8af833008f225c288dda63d6 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 21 May 2018 15:57:31 -0700
Subject: [PATCH 313/949] change version number (#1175)

---
 python/tvm/_ffi/libinfo.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/python/tvm/_ffi/libinfo.py b/python/tvm/_ffi/libinfo.py
index b17d0041f54d..25d111d1f74a 100644
--- a/python/tvm/_ffi/libinfo.py
+++ b/python/tvm/_ffi/libinfo.py
@@ -100,4 +100,4 @@ def find_lib_path(name=None, search_path=None, optional=False):
 
 
 # current version
-__version__ = "0.2.0"
+__version__ = "0.3.0"

From 40029601cc35b7390566c757a5d37e54e2807b95 Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Tue, 22 May 2018 09:59:18 +0530
Subject: [PATCH 314/949] Added equality check and upgraded concatenate op
 (#1172)

---
 topi/include/topi/broadcast.h                |  3 +-
 topi/include/topi/detail/broadcast.h         |  9 +--
 topi/include/topi/detail/constant_utils.h    | 18 +++++
 topi/include/topi/nn.h                       |  4 +-
 topi/include/topi/transform.h                |  2 +
 topi/python/topi/transform.py                |  1 +
 topi/tests/python_cpp/test_topi_transform.py | 76 +++++++++++++++++++-
 7 files changed, 105 insertions(+), 8 deletions(-)

diff --git a/topi/include/topi/broadcast.h b/topi/include/topi/broadcast.h
index 14f09d893f67..031baff7f983 100644
--- a/topi/include/topi/broadcast.h
+++ b/topi/include/topi/broadcast.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "topi/detail/broadcast.h"
+#include "topi/detail/constant_utils.h"
 #include "topi/tags.h"
 
 namespace topi {
@@ -34,7 +35,7 @@ inline tvm::Tensor broadcast_to(const tvm::Tensor& t,
   auto bh = detail::BroadcastShape(output_shape, t->shape);
   CHECK_EQ(output_shape.size(), bh.common_shape.size());
   for (size_t i = 0; i < output_shape.size(); ++i) {
-    CHECK(tvm::ir::Equal(output_shape[i], bh.common_shape[i]));
+    CHECK(topi::detail::EqualCheck(output_shape[i], bh.common_shape[i]));
   }
   auto l = [&](tvm::Array<tvm::Var> ovars) {
     return t(detail::InputIndexFromBroadcast(ovars, t, bh.vars2, bh.all_vars));
diff --git a/topi/include/topi/detail/broadcast.h b/topi/include/topi/detail/broadcast.h
index d76aa7d0ec56..19ada244f28e 100644
--- a/topi/include/topi/detail/broadcast.h
+++ b/topi/include/topi/detail/broadcast.h
@@ -12,6 +12,7 @@
 
 #include "tvm/ir_pass.h"
 #include "tvm/tvm.h"
+#include "topi/detail/constant_utils.h"
 
 namespace topi {
 namespace detail {
@@ -32,15 +33,15 @@ inline BroadcastHelper BroadcastShape(const tvm::Array<tvm::Expr>& shape1,
   int i;
   for (i = 1; i <= std::min(s1_size, s2_size); ++i) {
     bh.all_vars.push_front(tvm::Var());
-    if (tvm::ir::Equal(shape1[s1_size - i], shape2[s2_size - i])) {
+    if (topi::detail::EqualCheck(shape1[s1_size - i], shape2[s2_size - i])) {
       bh.common_shape.push_front(shape1[s1_size - i]);
       bh.vars1.push_front(bh.all_vars[0]);
       bh.vars2.push_front(bh.all_vars[0]);
-    } else if (tvm::ir::Equal(one, shape1[s1_size - i])) {
-      CHECK(!tvm::ir::Equal(one, shape2[s2_size - i]));
+    } else if (topi::detail::EqualCheck(one, shape1[s1_size - i])) {
+      CHECK(!topi::detail::EqualCheck(one, shape2[s2_size - i]));
       bh.common_shape.push_front(shape2[s2_size - i]);
       bh.vars2.push_front(bh.all_vars[0]);
-    } else if (tvm::ir::Equal(one, shape2[s2_size - i])) {
+    } else if (topi::detail::EqualCheck(one, shape2[s2_size - i])) {
       bh.common_shape.push_front(shape1[s1_size - i]);
       bh.vars1.push_front(bh.all_vars[0]);
     } else {
diff --git a/topi/include/topi/detail/constant_utils.h b/topi/include/topi/detail/constant_utils.h
index 062f9f473762..2631b2181b3a 100644
--- a/topi/include/topi/detail/constant_utils.h
+++ b/topi/include/topi/detail/constant_utils.h
@@ -65,6 +65,24 @@ inline std::vector<int> GetConstIntValues(Array<Expr> exprs, const std::string&
   return result;
 }
 
+/*!
+ * \brief Check weather the two expressions are equal or not, if not simplify the expressions and check again
+ * \note This is stronger equality check than tvm::ir::Equal
+ *
+ * \param lhs First expreesion
+ * \param rhs Second expreesion
+ *
+ * \return result True if both expressions are equal, else false
+ */
+inline bool EqualCheck(Expr lhs, Expr rhs) {
+  bool result = tvm::ir::Equal(lhs, rhs);
+  if (!result) {
+    Expr zero(0);
+    result = tvm::ir::Equal(tvm::ir::CanonicalSimplify(lhs-rhs), zero);
+  }
+  return result;
+}
+
 }  // namespace detail
 }  // namespace topi
 #endif  // TOPI_DETAIL_CONSTANT_UTILS_H_
diff --git a/topi/include/topi/nn.h b/topi/include/topi/nn.h
index d6079d0066a5..d2aac839b7c6 100644
--- a/topi/include/topi/nn.h
+++ b/topi/include/topi/nn.h
@@ -186,13 +186,13 @@ inline tvm::Tensor pad(const tvm::Tensor& t,
         indices.push_back(ovars[i]);
         continue;
       }
-      if (!tvm::ir::Equal(pad_before[i], 0)) {
+      if (!topi::detail::EqualCheck(pad_before[i], 0)) {
         sel.push_back(ovars[i] >= pad_before[i]);
         indices.push_back(ovars[i] - pad_before[i]);
       } else {
         indices.push_back(ovars[i]);
       }
-      if (!tvm::ir::Equal(pad_after[i], 0)) {
+      if (!topi::detail::EqualCheck(pad_after[i], 0)) {
         sel.push_back(tvm::ir::Simplify(ovars[i] < pad_before[i] + t->shape[i]));
       }
     }
diff --git a/topi/include/topi/transform.h b/topi/include/topi/transform.h
index 822a8e26e5fd..3c1b47f545d7 100644
--- a/topi/include/topi/transform.h
+++ b/topi/include/topi/transform.h
@@ -15,6 +15,7 @@
 #include "topi/detail/ravel_unravel.h"
 #include "topi/detail/constant_utils.h"
 #include "tvm/tvm.h"
+#include "tvm/ir_pass.h"
 
 namespace topi {
 using namespace tvm;
@@ -260,6 +261,7 @@ inline Tensor concatenate(const Array<Tensor>& inputs,
   for (size_t i = 1; i < axis_sizes.size(); ++i) {
     join_size += axis_sizes[i];
   }
+  join_size = tvm::ir::Simplify(join_size);
   Array<Expr> out_shape;
   for (size_t i = 0; i < inputs[0]->shape.size(); ++i) {
     out_shape.push_back(i == static_cast<size_t>(axis) ? join_size : inputs[0]->shape[i]);
diff --git a/topi/python/topi/transform.py b/topi/python/topi/transform.py
index 2fba99f793c5..807b210bee36 100644
--- a/topi/python/topi/transform.py
+++ b/topi/python/topi/transform.py
@@ -226,6 +226,7 @@ def concatenate(a_tuple, axis=0):
     axis_sizes = [a_tuple[i].shape[axis] for i in range(len(a_tuple))]
     out_shape = [a_tuple[0].shape[i] for i in range(0, axis)] + [sum(axis_sizes)]\
                 + [a_tuple[0].shape[i] for i in range(axis + 1, len(a_tuple[0].shape))]
+    out_shape[axis] = tvm.ir_pass.Simplify(out_shape[axis])
 
     def _compute(*indices):
         ret = a_tuple[0](*indices)
diff --git a/topi/tests/python_cpp/test_topi_transform.py b/topi/tests/python_cpp/test_topi_transform.py
index 2f1aa14b2296..6579c7791de0 100644
--- a/topi/tests/python_cpp/test_topi_transform.py
+++ b/topi/tests/python_cpp/test_topi_transform.py
@@ -206,6 +206,70 @@ def check_device(device):
     for device in ["llvm", "opencl"]:
         check_device(device)
 
+def verify_concatenate_split(shapes, axis, indices_or_sections):
+    tensor_l_concatenate = []
+    for i, shape in enumerate(shapes):
+        tensor_l_concatenate.append(tvm.placeholder(shape, name="A" + str(i)))
+    out_tensor = topi.cpp.concatenate(tensor_l_concatenate, axis)
+    tensor_l = topi.cpp.split(out_tensor, indices_or_sections, axis)
+    tensor_l = list(tensor_l)
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        target = topi.cpp.TEST_create_target(device)
+        if device == "llvm":
+            s = topi.cpp.generic.schedule_injective(target, tensor_l)
+        else:
+            s = topi.cpp.cuda.schedule_injective(target, tensor_l)
+        ctx = tvm.context(device, 0)
+        foo = tvm.build(s, tensor_l_concatenate + tensor_l, device, name="concatenate_split")
+        data_npys = [np.random.normal(size=shape).astype(tensor_l_concatenate[0].dtype) for shape in shapes]
+        out_npy_conc = np.concatenate(data_npys, axis=axis)
+        out_npys_split = np.split(out_npy_conc, indices_or_sections, axis=axis)
+        data_nds = [tvm.nd.array(data_npy, ctx) for data_npy in data_npys]
+        out_nds = [tvm.nd.empty(out_npy.shape, ctx=ctx, dtype=tensor_l[0].dtype) for out_npy in out_npys_split]
+        foo(*(data_nds + out_nds))
+        for out_nd, out_npy in zip(out_nds, out_npys_split):
+            np.testing.assert_allclose(out_nd.asnumpy(), out_npy)
+
+    for device in ["llvm", "cuda", "opencl", "metal", "rocm"]:
+        check_device(device)
+
+def verify_concatenate_broadcast(shapes, axis, rhs_shape):
+    B = tvm.placeholder(shape=rhs_shape, name="B")
+    tensor_l = []
+    for i, shape in enumerate(shapes):
+        tensor_l.append(tvm.placeholder(shape, name="A" + str(i)))
+    out_tensor = topi.cpp.concatenate(tensor_l, axis)
+    C = topi.cpp.broadcast_add(out_tensor, B)
+    def check_device(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        target = topi.cpp.TEST_create_target(device)
+        if device == "llvm":
+            s = topi.cpp.generic.schedule_injective(target, [C])
+        else:
+            s = topi.cpp.cuda.schedule_injective(target, [C])
+        ctx = tvm.context(device, 0)
+        foo = tvm.build(s, tensor_l + [B, C], device, name="broadcast_binary_add")
+        data_npys = [np.random.normal(size=shape).astype(tensor_l[0].dtype) for shape in shapes]
+        lhs_npy = np.concatenate(data_npys, axis=axis)
+        rhs_npy = np.random.uniform(size=rhs_shape).astype(B.dtype)
+        out_npy = lhs_npy + rhs_npy
+        data_nds = [tvm.nd.array(data_npy, ctx) for data_npy in data_npys]
+        rhs_nd = tvm.nd.array(rhs_npy, ctx)
+        out_nd = tvm.nd.array(np.empty(out_npy.shape).astype(B.dtype), ctx)
+        for _ in range(1):
+            foo(*(data_nds + [rhs_nd] + [out_nd]))
+        np.testing.assert_allclose(out_nd.asnumpy(), out_npy, rtol=1E-4, atol=1E-4)
+
+    for device in ["llvm", "cuda", "opencl", "metal", "rocm"]:
+        check_device(device)
 
 def test_expand_dims():
     verify_expand_dims((3, 10), (3, 10, 1, 1), 2, 2)
@@ -258,6 +322,14 @@ def test_take():
     verify_take((2,2), [[[1,0],[0,1]]], 1)
     verify_take((4,3,5,6), [[2,1,0,0]], -2)
 
+def test_regression_1():
+    verify_concatenate_split([(2, 3, 4), (2, 2, 4), (2, 5, 4)], 1, [3, 7])
+    verify_concatenate_split([(3, 4), (2, 4), (3, 4)], 0, [1, 2, 3, 4])
+
+def test_regression_2():
+    verify_concatenate_broadcast([(5, 1, 3), (5, 1, 3)], 1, [2, 1])
+    verify_concatenate_broadcast([(5, 1, 2), (5, 1, 3)], 2, [1, 5])
+
 if __name__ == "__main__":
     test_concatenate()
     test_tranpose()
@@ -265,4 +337,6 @@ def test_take():
     test_reshape()
     test_squeeze()
     test_split()
-    test_take()
\ No newline at end of file
+    test_take()
+    test_regression_1()
+    test_regression_2()

From c5dedb5db64d07a805fe249ce90078763d7debaa Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Tue, 22 May 2018 21:01:27 +0530
Subject: [PATCH 315/949] Issue in nnvm is fixed (#1176)

---
 topi/include/topi/detail/constant_utils.h | 1 +
 topi/include/topi/transform.h             | 1 -
 2 files changed, 1 insertion(+), 1 deletion(-)

diff --git a/topi/include/topi/detail/constant_utils.h b/topi/include/topi/detail/constant_utils.h
index 2631b2181b3a..adbb84421bfa 100644
--- a/topi/include/topi/detail/constant_utils.h
+++ b/topi/include/topi/detail/constant_utils.h
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "tvm/tvm.h"
+#include "tvm/ir_pass.h"
 
 namespace topi {
 namespace detail {
diff --git a/topi/include/topi/transform.h b/topi/include/topi/transform.h
index 3c1b47f545d7..75d7a2a32c92 100644
--- a/topi/include/topi/transform.h
+++ b/topi/include/topi/transform.h
@@ -15,7 +15,6 @@
 #include "topi/detail/ravel_unravel.h"
 #include "topi/detail/constant_utils.h"
 #include "tvm/tvm.h"
-#include "tvm/ir_pass.h"
 
 namespace topi {
 using namespace tvm;

From 168f099155106d1188dbc54ac00acc02900a3c6f Mon Sep 17 00:00:00 2001
From: MORITA Kazutaka <morita.kazutaka@lab.ntt.co.jp>
Date: Wed, 23 May 2018 02:37:31 +0900
Subject: [PATCH 316/949] axis bounds checking in split (#1178)

* Add kazum to contributers

* Add axis bounds checking in split
---
 CONTRIBUTORS.md               | 1 +
 topi/include/topi/transform.h | 2 ++
 2 files changed, 3 insertions(+)

diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 106a390169cd..630e05953398 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -63,3 +63,4 @@ List of Contributors
 - [Cody Hao Yu](https://github.com/comaniac)
 - [Chris Nuernberger](https://github.com/cnuernber)
 - [Tatsuya Nishiyama](https://github.com/nishi-t)
+- [Kazutaka Morita](https://github.com/kazum)
diff --git a/topi/include/topi/transform.h b/topi/include/topi/transform.h
index 75d7a2a32c92..941c5dbc5b60 100644
--- a/topi/include/topi/transform.h
+++ b/topi/include/topi/transform.h
@@ -310,6 +310,8 @@ inline Array<Tensor> split(const Tensor& x,
   if (axis < 0) {
     axis += static_cast<int>(x->shape.size());
   }
+  CHECK_LT(axis, x->shape.size()) << "axis out of bounds";
+
   auto src_axis_size = static_cast<int>(GetConstInt(x->shape[axis]));
 
   auto split_indices_val = GetConstIntValues(split_indices, "split_indices");

From 11318966571f654f4e8bc550bfd9a293303e3000 Mon Sep 17 00:00:00 2001
From: acmore <hezj07+github@gmail.com>
Date: Sat, 26 May 2018 00:17:30 +0800
Subject: [PATCH 317/949] Fix build break on Windows. (#1179)

---
 include/tvm/ir_pass.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/include/tvm/ir_pass.h b/include/tvm/ir_pass.h
index 44a77aebbfd8..a5bcecda16b6 100644
--- a/include/tvm/ir_pass.h
+++ b/include/tvm/ir_pass.h
@@ -53,8 +53,8 @@ Stmt CanonicalSimplify(Stmt stmt,
  * \param vrange The range information about the variable.
  * \return Canonicalized expression.
  */
-Expr CanonicalSimplify(Expr expr,
-                       Map<Var, Range> vrange = Map<Var, Range>());
+EXPORT Expr CanonicalSimplify(Expr expr,
+                              Map<Var, Range> vrange = Map<Var, Range>());
 
 /*!
  * \brief Deep compare lhs and rhs

From 2799c00b39bacffe3f02d24344151e98d35ae861 Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Fri, 25 May 2018 16:53:58 -0700
Subject: [PATCH 318/949] ok

---
 nnvm/.gitkeep | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 nnvm/.gitkeep

diff --git a/nnvm/.gitkeep b/nnvm/.gitkeep
new file mode 100644
index 000000000000..e69de29bb2d1

From a336472ff904252a0f2ebfcbd9dd4ae03d114645 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 22 Jun 2016 20:57:03 -0700
Subject: [PATCH 319/949] Initial commit

---
 nnvm/LICENSE   | 201 +++++++++++++++++++++++++++++++++++++++++++++++++
 nnvm/README.md |   2 +
 2 files changed, 203 insertions(+)
 create mode 100644 nnvm/LICENSE
 create mode 100644 nnvm/README.md

diff --git a/nnvm/LICENSE b/nnvm/LICENSE
new file mode 100644
index 000000000000..8dada3edaf50
--- /dev/null
+++ b/nnvm/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   Licensed 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.
diff --git a/nnvm/README.md b/nnvm/README.md
new file mode 100644
index 000000000000..4d58ac60e398
--- /dev/null
+++ b/nnvm/README.md
@@ -0,0 +1,2 @@
+# mxnngraph
+Prototype of graph optimizer and construction API for next generation core engine in MXNet

From 6a19f36378b0285592c4f6243e51426d025ec2e6 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 22 Jun 2016 20:58:22 -0700
Subject: [PATCH 320/949] Update README.md

---
 nnvm/README.md | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/nnvm/README.md b/nnvm/README.md
index 4d58ac60e398..d3a85dd0e289 100644
--- a/nnvm/README.md
+++ b/nnvm/README.md
@@ -1,2 +1,7 @@
 # mxnngraph
 Prototype of graph optimizer and construction API for next generation core engine in MXNet
+
+## Related Projects
+- https://github.com/dmlc/mxnet
+- https://github.com/ajtulloch/dnngraph
+

From f448900ca6ee9141a24861e2f7743e3965806210 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 22 Jun 2016 20:59:49 -0700
Subject: [PATCH 321/949] Update README.md

---
 nnvm/README.md | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/nnvm/README.md b/nnvm/README.md
index d3a85dd0e289..378598ccbee3 100644
--- a/nnvm/README.md
+++ b/nnvm/README.md
@@ -1,5 +1,10 @@
 # mxnngraph
-Prototype of graph optimizer and construction API for next generation core engine in MXNet
+Prototype of graph optimizer and construction API for next generation core engine in MXNet and beyond
+
+## Goal
+- Construct graph easily
+- Pluggable inference, optimization algorithms
+- Can be used as input graph execution in various settings, specifically be able to support mxnet's symbolic execution API
 
 ## Related Projects
 - https://github.com/dmlc/mxnet

From 78809f17099eeaced9fa28d095f907385ce74f13 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 22 Jun 2016 21:00:50 -0700
Subject: [PATCH 322/949] Update README.md first init

---
 nnvm/Makefile                     | 31 ++++++++++++++
 nnvm/README.md                    |  3 --
 nnvm/include/nngraph/attr_frame.h | 16 ++++++++
 nnvm/include/nngraph/base.h       | 42 +++++++++++++++++++
 nnvm/include/nngraph/graph.h      | 42 +++++++++++++++++++
 nnvm/include/nngraph/node.h       | 67 +++++++++++++++++++++++++++++++
 nnvm/include/nngraph/op_prop.h    | 26 ++++++++++++
 nnvm/src/nngraph.cc               |  7 ++++
 8 files changed, 231 insertions(+), 3 deletions(-)
 create mode 100644 nnvm/Makefile
 create mode 100644 nnvm/include/nngraph/attr_frame.h
 create mode 100644 nnvm/include/nngraph/base.h
 create mode 100644 nnvm/include/nngraph/graph.h
 create mode 100644 nnvm/include/nngraph/node.h
 create mode 100644 nnvm/include/nngraph/op_prop.h
 create mode 100644 nnvm/src/nngraph.cc

diff --git a/nnvm/Makefile b/nnvm/Makefile
new file mode 100644
index 000000000000..a96f16249886
--- /dev/null
+++ b/nnvm/Makefile
@@ -0,0 +1,31 @@
+export LDFLAGS= -pthread -lm
+export CFLAGS=  -std=c++11 -Wall -O3 -msse2  -Wno-unknown-pragmas -funroll-loops\
+	 -Iinclude -Idmlc-core/include -fPIC
+
+# specify tensor path
+.PHONY: clean all test
+
+
+all: lib/libnngraph.so test
+
+SRC = $(wildcard src/*.cc src/*/*.cc)
+ALL_OBJ = $(patsubst src/%.cc, build/%.o, $(SRC)) $(PLUGIN_OBJS)
+ALL_DEP = $(ALL_OBJ)
+
+build/%.o: src/%.cc
+	@mkdir -p $(@D)
+	$(CXX) $(CFLAGS) -MM -MT build/$*.o $< >build/$*.d
+	$(CXX) -c $(CFLAGS) -c $< -o $@
+
+lib/libnngraph.so: $(ALL_DEP)
+	@mkdir -p $(@D)
+	$(CXX) $(CFLAGS) -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
+
+test: $(ALL_DEP)
+	$(CXX) $(CFLAGS)  -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
+
+clean:
+	$(RM) -rf build lib bin *~ */*~ */*/*~ */*/*/*~ */*.o */*/*.o */*/*/*.o xgboost
+
+-include build/*.d
+-include build/*/*.d
diff --git a/nnvm/README.md b/nnvm/README.md
index 378598ccbee3..75e7a81c5f6e 100644
--- a/nnvm/README.md
+++ b/nnvm/README.md
@@ -6,7 +6,4 @@ Prototype of graph optimizer and construction API for next generation core engin
 - Pluggable inference, optimization algorithms
 - Can be used as input graph execution in various settings, specifically be able to support mxnet's symbolic execution API
 
-## Related Projects
-- https://github.com/dmlc/mxnet
-- https://github.com/ajtulloch/dnngraph
 
diff --git a/nnvm/include/nngraph/attr_frame.h b/nnvm/include/nngraph/attr_frame.h
new file mode 100644
index 000000000000..ebfdc7d3e743
--- /dev/null
+++ b/nnvm/include/nngraph/attr_frame.h
@@ -0,0 +1,16 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file attr_frame.h
+ * \brief Attribute frame data structure for properties in the graph.
+ *   This data structure is inspired by data_frame for general.
+ */
+#include "./base.h"
+
+namespace nngraph {
+
+
+struct AttrFrame {
+  std::unique_ptr<std::unordered_map<std::string, any> > info;
+};
+
+}  // namespace nngraph
diff --git a/nnvm/include/nngraph/base.h b/nnvm/include/nngraph/base.h
new file mode 100644
index 000000000000..f279c5795030
--- /dev/null
+++ b/nnvm/include/nngraph/base.h
@@ -0,0 +1,42 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file base.h
+ * \brief Configuation of nngraph as well as basic data structure.
+ */
+#ifndef NNGRAPH_BASE_H_
+#define NNGRAPH_BASE_H_
+
+#include <dmlc/base.h>
+#include <dmlc/any.h>
+#include <dmlc/logging.h>
+
+namespace nngraph {
+
+/*! \brief any type */
+using any = dmlc::any;
+
+/*!
+ * \brief get reference of type T stored in src.
+ * \param src The source container
+ * \return the reference to the type.
+ * \tparam T The type to be fetched.
+ */
+template<typename T>
+inline T& get(any& src) {  // NOLINT(*)
+  return dmlc::get<T>(src);
+}
+
+/*!
+ * \brief get const reference of type T stored in src.
+ * \param src The source container
+ * \return the reference to the type.
+ * \tparam T The type to be fetched.
+ */
+
+template<typename T>
+inline const T& get(const any& src) {
+  return dmlc::get<T>(src);
+}
+
+}  // namespace nngraph
+#endif // NNGRAPH_BASE_H_
diff --git a/nnvm/include/nngraph/graph.h b/nnvm/include/nngraph/graph.h
new file mode 100644
index 000000000000..c3cdfb4d9cac
--- /dev/null
+++ b/nnvm/include/nngraph/graph.h
@@ -0,0 +1,42 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file graph.h
+ * \brief Configuation of nngraph as well as basic data structure.
+ */
+#ifndef NNGRAPH_GRAPH_H_
+#define NNGRAPH_GRAPH_H_
+
+#include <vector>
+#include "./node.h"
+#include "./attr_frame.h"
+
+namespace nngraph {
+/*!
+ * \brief Symbolic computation graph.
+ */
+class Graph {
+ public:
+  /*!
+   * \brief get the index th element from the returned tuple.
+   * \param index index of multi output
+   * \return the symbol corresponds to the indexed element.
+   */
+  Graph operator[] (size_t index) const;
+  /*!
+   * \brief get number of outputs of this symbol
+   * \return number of outputs
+   */
+  inline size_t outputs_size() const {
+    return outputs_.size();
+  }
+
+ private:
+  /*! \brief outputs of the graph. */
+  std::vector<NodeEntry> outputs_;
+  /*! \brief additional internal attribute */
+  AttrFrame attr_frame_;
+};
+
+}  // namespace nngraph
+
+#endif  // NNGRAPH_GRAPH_H_
diff --git a/nnvm/include/nngraph/node.h b/nnvm/include/nngraph/node.h
new file mode 100644
index 000000000000..0fd50e9c1b0c
--- /dev/null
+++ b/nnvm/include/nngraph/node.h
@@ -0,0 +1,67 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file base.h
+ * \brief Configuation of nngraph as well as basic data structure.
+ */
+#ifndef NNGRAPH_NODE_H_
+#define NNGRAPH_NODE_H_
+
+#include <memory>
+#include <unordered_map>
+#include "./op_prop.h"
+
+namespace nngraph {
+// Forward declare node.
+struct Node;
+
+/*! \brief an entry that represents output data from a node */
+struct NodeEntry {
+  /*! \brief the source node of this data */
+  std::shared_ptr<Node> node;
+  /*! \brief index of output from the source. */
+  uint32_t index;
+};
+
+/*!
+ * \brief Node represents an operation in a computation graph.
+ */
+struct Node {
+  /*! \brief name of the node */
+  std::string name;
+  /*! \brief the operator this node is pointing at */
+  const OpProperty *op;
+  /*! \brief inputs to this node */
+  std::vector<NodeEntry> inputs;
+  /*!
+   * \brief additional attributes about the node,
+   *  Use pointer to save space, as attr can be accessed in a slow way,
+   *  not every node will have attributes.
+   */
+  std::unordered_map<std::string, std::string> attr;
+
+  ~Node() {
+    if (inputs.size() != 0) {
+      // explicit deletion via DFS
+      // this is used to avoid stackoverflow caused by chain of deletions
+      std::vector<Node*> stack{this};
+      std::vector<std::shared_ptr<Node> > to_delete;
+      while (!stack.empty()) {
+        Node* n = stack.back();
+        stack.pop_back();
+        for (NodeEntry& e: n->inputs) {
+          if (e.node.unique()) {
+            stack.push_back(e.node.get());
+            to_delete.emplace_back(std::move(e.node));
+          } else {
+            e.node.reset();
+          }
+        }
+        n->inputs.clear();
+      }
+    }
+  }
+};
+
+}  // namespace nngraph
+
+#endif  // NNGRAPH_NODE_H_
diff --git a/nnvm/include/nngraph/op_prop.h b/nnvm/include/nngraph/op_prop.h
new file mode 100644
index 000000000000..99595b2853e7
--- /dev/null
+++ b/nnvm/include/nngraph/op_prop.h
@@ -0,0 +1,26 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file op_prop.h
+ * \brief Data structure about property of operators
+ */
+#ifndef NNGRAPH_OP_PROP_H_
+#define NNGRAPH_OP_PROP_H_
+
+namespace nngraph {
+
+/*!
+ * \brief operator specific data structure
+ */
+struct OpProperty {
+  /*! \brief name of the operator */
+  std::string name;
+  /*! \brief number of inputs to the operator */
+  int num_inputs;
+  /*! \brief number of outputs to the operator */
+  int num_outputs;
+};
+
+
+}  // namespace nngraph
+
+#endif  // NNGRAPH_OP_PROP_H_
diff --git a/nnvm/src/nngraph.cc b/nnvm/src/nngraph.cc
new file mode 100644
index 000000000000..adcf5858107c
--- /dev/null
+++ b/nnvm/src/nngraph.cc
@@ -0,0 +1,7 @@
+#include <nngraph/graph.h>
+
+int main() {
+  nngraph::any a = 1;
+  LOG(INFO) << nngraph::get<int>(a);
+  return 0;
+}

From b01facc5823df71d5f7f995810764f3afb49c40b Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Sat, 2 Jul 2016 11:21:10 -0700
Subject: [PATCH 323/949] [OP] Finalize Op registry

---
 nnvm/Makefile                     |   14 +-
 nnvm/docs/.gitignore              |    1 +
 nnvm/docs/Doxyfile                | 2353 +++++++++++++++++++++++++++++
 nnvm/include/nngraph/attr_frame.h |   16 -
 nnvm/include/nngraph/base.h       |    4 +-
 nnvm/include/nngraph/graph.h      |    4 +-
 nnvm/include/nngraph/node.h       |   62 +-
 nnvm/include/nngraph/op.h         |  343 +++++
 nnvm/include/nngraph/op_prop.h    |   26 -
 nnvm/src/core/node.cc             |   38 +
 nnvm/src/core/op.cc               |   69 +
 nnvm/src/example/operator.cc      |   14 +
 nnvm/src/nngraph.cc               |    7 -
 nnvm/src/test_main.cc             |   11 +
 14 files changed, 2874 insertions(+), 88 deletions(-)
 create mode 100644 nnvm/docs/.gitignore
 create mode 100644 nnvm/docs/Doxyfile
 delete mode 100644 nnvm/include/nngraph/attr_frame.h
 create mode 100644 nnvm/include/nngraph/op.h
 delete mode 100644 nnvm/include/nngraph/op_prop.h
 create mode 100644 nnvm/src/core/node.cc
 create mode 100644 nnvm/src/core/op.cc
 create mode 100644 nnvm/src/example/operator.cc
 delete mode 100644 nnvm/src/nngraph.cc
 create mode 100644 nnvm/src/test_main.cc

diff --git a/nnvm/Makefile b/nnvm/Makefile
index a96f16249886..23199fd6ba9a 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -3,13 +3,13 @@ export CFLAGS=  -std=c++11 -Wall -O3 -msse2  -Wno-unknown-pragmas -funroll-loops
 	 -Iinclude -Idmlc-core/include -fPIC
 
 # specify tensor path
-.PHONY: clean all test
+.PHONY: clean all test lint doc
 
 
 all: lib/libnngraph.so test
 
-SRC = $(wildcard src/*.cc src/*/*.cc)
-ALL_OBJ = $(patsubst src/%.cc, build/%.o, $(SRC)) $(PLUGIN_OBJS)
+SRC = $(wildcard src/*.cc src/*/*.cc example/*.cc)
+ALL_OBJ = $(patsubst src/%.cc, build/%.o, $(SRC))
 ALL_DEP = $(ALL_OBJ)
 
 build/%.o: src/%.cc
@@ -24,8 +24,14 @@ lib/libnngraph.so: $(ALL_DEP)
 test: $(ALL_DEP)
 	$(CXX) $(CFLAGS)  -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
 
+lint:
+	python2 dmlc-core/scripts/lint.py nngraph cpp include src
+
+doc:
+	doxygen docs/Doxyfile
+
 clean:
-	$(RM) -rf build lib bin *~ */*~ */*/*~ */*/*/*~ */*.o */*/*.o */*/*/*.o xgboost
+	$(RM) -rf build lib bin *~ */*~ */*/*~ */*/*/*~ */*.o */*/*.o */*/*/*.o test
 
 -include build/*.d
 -include build/*/*.d
diff --git a/nnvm/docs/.gitignore b/nnvm/docs/.gitignore
new file mode 100644
index 000000000000..8e786536c590
--- /dev/null
+++ b/nnvm/docs/.gitignore
@@ -0,0 +1 @@
+doxygen
diff --git a/nnvm/docs/Doxyfile b/nnvm/docs/Doxyfile
new file mode 100644
index 000000000000..c1f5ffcb040c
--- /dev/null
+++ b/nnvm/docs/Doxyfile
@@ -0,0 +1,2353 @@
+# Doxyfile 1.8.8
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all text
+# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
+# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
+# for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME           = "mxnngraph"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER         =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          =
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is included in
+# the documentation. The maximum height of the logo should not exceed 55 pixels
+# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo
+# to the output directory.
+
+PROJECT_LOGO           =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = docs/doxygen
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS         = NO
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+#ALLOW_UNICODE_NAMES    = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF       =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES        = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH        =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a
+# new page for each member. If set to NO, the documentation of a member will be
+# part of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines.
+
+ALIASES                =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
+
+TCL_SUBST              =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
+# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
+# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
+# Fortran. In the later case the parser tries to guess whether the code is fixed
+# or free formatted code, this is the default for Fortran type files), VHDL. For
+# instance to make doxygen treat .inc files as Fortran files (default is PHP),
+# and .f files as C (default is Fortran), use: inc=Fortran f=C.
+#
+# Note For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+#MARKDOWN_SUPPORT       = YES
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by by putting a % sign in front of the word
+# or globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+#AUTOLINK_SUPPORT       = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS  = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL            = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+#EXTRACT_PACKAGE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO these classes will be included in the various overviews. This option has
+# no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# (class|struct|union) declarations. If set to NO these declarations will be
+# included in the documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+#SHOW_GROUPED_MEMB_INC  = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the
+# todo list. This list is created by putting \todo commands in the
+# documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the
+# test list. This list is created by putting \test commands in the
+# documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES the list
+# will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES        = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE            =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES         =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS               = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO doxygen will only warn about wrong or incomplete parameter
+# documentation, but not about the absence of documentation.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC       = YES
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces.
+# Note: If this tag is empty the current directory is searched.
+
+INPUT                  = include
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: http://www.gnu.org/software/libiconv) for the list of
+# possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank the
+# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii,
+# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp,
+# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown,
+# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf,
+# *.qsf, *.as and *.js.
+
+FILE_PATTERNS          = *.h
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       = */test/* \
+                         logging.h
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS       =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER ) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+#USE_MDFILE_AS_MAINPAGE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# function all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES, then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+#SOURCE_TOOLTIPS        = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see http://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS       = YES
+
+# If the CLANG_ASSISTED_PARSING tag is set to YES, then doxygen will use the
+# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the
+# cost of reduced performance. This can be particularly helpful with template
+# rich C++ code for which doxygen's built-in parser lacks the necessary type
+# information.
+# Note: The availability of this option depends on whether or not doxygen was
+# compiled with the --with-libclang option.
+# The default value is: NO.
+
+#CLANG_ASSISTED_PARSING = NO
+
+# If clang assisted parsing is enabled you can provide the compiler with command
+# line options that you would normally use when invoking the compiler. Note that
+# the include paths will already be set by doxygen for the files and directories
+# specified with INPUT and INCLUDE_PATH.
+# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
+
+#CLANG_OPTIONS          =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX     = YES
+
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefor more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra stylesheet files is of importance (e.g. the last
+# stylesheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+#HTML_EXTRA_STYLESHEET  =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the stylesheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP         = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+#HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: http://developer.apple.com/tools/xcode/), introduced with
+# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET        = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP      = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE               =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler ( hhc.exe). If non-empty
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION           =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated (
+# YES) or that it should be included in the master .chm file ( NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI           = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING     =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated (
+# YES) or a normal table of contents ( NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
+# folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX          = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW      = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH         = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT    = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# http://www.mathjax.org) which uses client side Javascript for the rendering
+# instead of using prerendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX            = NO
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+#MATHJAX_FORMAT         = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from http://www.mathjax.org before deployment.
+# The default value is: http://cdn.mathjax.org/mathjax/latest.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH        = http://www.mathjax.org/mathjax
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS     =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+#MATHJAX_CODEFILE       =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE           = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using Javascript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH    = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer ( doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+#EXTERNAL_SEARCH        = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer ( doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+#SEARCHENGINE_URL       =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+#SEARCHDATA_FILE        = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+#EXTERNAL_SEARCH_ID     =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+#EXTRA_SEARCH_MAPPINGS  =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX         = YES
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when enabling USE_PDFLATEX this option is only used for generating
+# bitmaps for formulas in the HTML output, but not in the Makefile that is
+# written to the output directory.
+# The default file is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE             = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. To get the times font for
+# instance you can specify
+# EXTRA_PACKAGES=times
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empy string,
+# for the replacement values of the other commands the user is refered to
+# HTML_HEADER.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER           =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+#LATEX_EXTRA_FILES      =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES to get a
+# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE        = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES     = NO
+
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_SOURCE_CODE      = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE        = plain
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's config
+# file, i.e. a series of assignments. You only have to provide replacements,
+# missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's config file. A template extensions file can be generated
+# using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE    =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION          = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+#MAN_SUBDIR             =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML           = YES
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT             = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+#GENERATE_DOCBOOK       = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+#DOCBOOK_OUTPUT         = docbook
+
+# If the DOCBOOK_PROGRAMLISTING tag is set to YES doxygen will include the
+# program listings (including syntax highlighting and cross-referencing
+# information) to the DOCBOOK output. Note that enabling this will significantly
+# increase the size of the DOCBOOK output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+#DOCBOOK_PROGRAMLISTING = NO
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES doxygen will generate an AutoGen
+# Definitions (see http://autogen.sf.net) file that captures the structure of
+# the code including all documentation. Note that this feature is still
+# experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES doxygen will expand all macro names
+# in the source code. If set to NO only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES the includes files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED             = DMLC_USE_CXX11
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES all external class will be listed in the
+# class index. If set to NO only the inherited external classes will be listed.
+# The default value is: NO.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed in
+# the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS        = YES
+
+# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+#EXTERNAL_PAGES         = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of 'which perl').
+# The default file (with absolute path) is: /usr/bin/perl.
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see:
+# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+#DIA_PATH               =
+
+# If set to YES, the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: YES.
+
+HAVE_DOT               = YES
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS        = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK               = YES
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+#UML_LIMIT_NUM_FIELDS   = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH          = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot.
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd,
+# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo,
+# gif:cairo:gd, gif:gd, gif:gd:gd and svg.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT       = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG        = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS           =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+#DIAFILE_DIRS           =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+#PLANTUML_JAR_PATH      =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS      = YES
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_CLEANUP            = YES
diff --git a/nnvm/include/nngraph/attr_frame.h b/nnvm/include/nngraph/attr_frame.h
deleted file mode 100644
index ebfdc7d3e743..000000000000
--- a/nnvm/include/nngraph/attr_frame.h
+++ /dev/null
@@ -1,16 +0,0 @@
-/*!
- *  Copyright (c) 2016 by Contributors
- * \file attr_frame.h
- * \brief Attribute frame data structure for properties in the graph.
- *   This data structure is inspired by data_frame for general.
- */
-#include "./base.h"
-
-namespace nngraph {
-
-
-struct AttrFrame {
-  std::unique_ptr<std::unordered_map<std::string, any> > info;
-};
-
-}  // namespace nngraph
diff --git a/nnvm/include/nngraph/base.h b/nnvm/include/nngraph/base.h
index f279c5795030..19827aa71ea1 100644
--- a/nnvm/include/nngraph/base.h
+++ b/nnvm/include/nngraph/base.h
@@ -9,6 +9,7 @@
 #include <dmlc/base.h>
 #include <dmlc/any.h>
 #include <dmlc/logging.h>
+#include <dmlc/registry.h>
 
 namespace nngraph {
 
@@ -39,4 +40,5 @@ inline const T& get(const any& src) {
 }
 
 }  // namespace nngraph
-#endif // NNGRAPH_BASE_H_
+
+#endif  // NNGRAPH_BASE_H_
diff --git a/nnvm/include/nngraph/graph.h b/nnvm/include/nngraph/graph.h
index c3cdfb4d9cac..5c4509f4a63c 100644
--- a/nnvm/include/nngraph/graph.h
+++ b/nnvm/include/nngraph/graph.h
@@ -8,9 +8,9 @@
 
 #include <vector>
 #include "./node.h"
-#include "./attr_frame.h"
 
 namespace nngraph {
+
 /*!
  * \brief Symbolic computation graph.
  */
@@ -33,8 +33,6 @@ class Graph {
  private:
   /*! \brief outputs of the graph. */
   std::vector<NodeEntry> outputs_;
-  /*! \brief additional internal attribute */
-  AttrFrame attr_frame_;
 };
 
 }  // namespace nngraph
diff --git a/nnvm/include/nngraph/node.h b/nnvm/include/nngraph/node.h
index 0fd50e9c1b0c..e0823fa009ce 100644
--- a/nnvm/include/nngraph/node.h
+++ b/nnvm/include/nngraph/node.h
@@ -1,18 +1,21 @@
 /*!
  *  Copyright (c) 2016 by Contributors
  * \file base.h
- * \brief Configuation of nngraph as well as basic data structure.
+ * \brief Graph node data structure.
  */
 #ifndef NNGRAPH_NODE_H_
 #define NNGRAPH_NODE_H_
 
 #include <memory>
+#include <string>
+#include <vector>
 #include <unordered_map>
-#include "./op_prop.h"
+#include "./op.h"
 
 namespace nngraph {
+
 // Forward declare node.
-struct Node;
+class Node;
 
 /*! \brief an entry that represents output data from a node */
 struct NodeEntry {
@@ -22,44 +25,41 @@ struct NodeEntry {
   uint32_t index;
 };
 
+/*!
+ * \brief The attributes of the current operation node.
+ *  Usually are additional parameters like axis,
+ */
+struct NodeAttrs {
+  /*! \brief The dictionary representation of attributes */
+  std::unordered_map<std::string, std::string> dict;
+  /*!
+   * \brief A parsed version of attributes,
+   * This is generated if OpProperty.attr_parser is registered.
+   * The object can be used to quickly access attributes.
+   */
+  any parsed;
+};
+
 /*!
  * \brief Node represents an operation in a computation graph.
  */
-struct Node {
+class Node {
+ public:
   /*! \brief name of the node */
   std::string name;
   /*! \brief the operator this node is pointing at */
-  const OpProperty *op;
+  const Op *op;
   /*! \brief inputs to this node */
   std::vector<NodeEntry> inputs;
+  /*! \brief The attributes in the node. */
+  NodeAttrs attrs;
+  /*! \brief destructor of node */
+  ~Node();
   /*!
-   * \brief additional attributes about the node,
-   *  Use pointer to save space, as attr can be accessed in a slow way,
-   *  not every node will have attributes.
+   * \brief create a new empty shared_ptr of Node.
+   * \return a created empty node.
    */
-  std::unordered_map<std::string, std::string> attr;
-
-  ~Node() {
-    if (inputs.size() != 0) {
-      // explicit deletion via DFS
-      // this is used to avoid stackoverflow caused by chain of deletions
-      std::vector<Node*> stack{this};
-      std::vector<std::shared_ptr<Node> > to_delete;
-      while (!stack.empty()) {
-        Node* n = stack.back();
-        stack.pop_back();
-        for (NodeEntry& e: n->inputs) {
-          if (e.node.unique()) {
-            stack.push_back(e.node.get());
-            to_delete.emplace_back(std::move(e.node));
-          } else {
-            e.node.reset();
-          }
-        }
-        n->inputs.clear();
-      }
-    }
-  }
+  static std::shared_ptr<Node> Create();
 };
 
 }  // namespace nngraph
diff --git a/nnvm/include/nngraph/op.h b/nnvm/include/nngraph/op.h
new file mode 100644
index 000000000000..1f768d95e9a0
--- /dev/null
+++ b/nnvm/include/nngraph/op.h
@@ -0,0 +1,343 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file op.h
+ * \brief Operator information structor.
+ */
+#ifndef NNGRAPH_OP_H_
+#define NNGRAPH_OP_H_
+
+#include <string>
+#include <vector>
+#include <utility>
+#include <typeinfo>
+#include <functional>
+#include "./base.h"
+
+namespace nngraph {
+
+// forward declarations
+class Node;
+struct NodeAttrs;
+template<typename ValueType>
+class OpMap;
+class OpRegistryEntry;
+
+/*! \brief constant to indicate variable length inout and output */
+static const int kVarg = -1;
+
+/*!
+ * \brief Operator structure.
+ *
+ *  Besides the fields in the structure,
+ *  arbitary additional information can be associated with each op.
+ *  See function GetAttr for details.
+ *
+ * \code
+ *  // Example usage of Op
+ *
+ *  // registeration of oeprators
+ *  // NOTE that the attr function can register any
+ *  // additional attributes to the operator
+ *  NNGRAPH_REGISTER_OP(add)
+ *  .describe("add two inputs together")
+ *  .set_num_inputs(2)
+ *  .attr<OpKernel>("gpu_kernel", AddKernel);
+ *
+ *  NNGRAPH_REGISTER_OP(sub)
+ *  .describe("substract one tensor from another")
+ *  .set_num_inputs(2);
+ *
+ *  // Can call regster multiple times in different files
+ *  // to register different part of information
+ *  NNGRAPH_REGISTER_OP(sub)
+ *  .attr<OpKernel>("gpu_kernel", SubKernel);
+ *
+ *  // get operators from registry.
+ *  void my_function() {
+ *    const Op* add = Op::Get("add");
+ *    const Op* sub = Op::Get("sub");
+ *    // query basic information about each operator.
+ *    assert(op->name == "plus");
+ *    assert(op->num_inputs == 2);
+ *
+ *    // get additional registered information,
+ *    // Assume user registered a OpKernel type attribute as gpu_kernel on each operator.
+ *    const OpMap<OpKernel>& kernel = Op::GetAttr<OpKernel>("gpu_kernel");
+ *    // we can get the kernel functions by using operator as key.
+ *    auto add_kernel = kernel[add];
+ *    auto sub_kernel = kernel[sub];
+ *    // subsequent code can make use of the queried kernel functions.
+ * }
+ * \endcode
+ */
+class Op {
+ public:
+  /*! \brief name of the operator */
+  std::string name;
+  /*! \brief detailed description of the operator */
+  std::string description;
+  /*!
+   * \brief number of inputs to the operator,
+   * -1 means it is variable length
+   */
+  int num_inputs = 0;
+  /*!
+   * \brief number of outputs of the operator
+   *  -1 means it is variable length
+   *  The number of outputs will be decided by
+   *  get_num_outputs function
+   * \sa get_num_outputs
+   */
+  int num_outputs = 1;
+  /*!
+   * \brief get number of outputs given information about the node.
+   *  This is only valid when num_outputs == -1.
+   * \param node The constructed node.
+   * \return number of outputs.
+   */
+  int (*get_num_outputs)(const Node& node) = nullptr;
+  /*!
+   * \brief Attribute parser to parse the NodeAttrs information.
+   *
+   * This can help to get quick access to a parsed attribute
+   * object
+   *
+   * \code
+   *  // Example usage of attr_parser.
+   *
+   *  // Suppose we want to register operator sum.
+   *  // The parameters about sum operator
+   *  struct SumParam {
+   *    int axis;
+   *  };
+   *  // The parser function
+   *  void SumAttrParser(NodeAttrs* attrs) {
+   *     // This will be invoked during node construction.
+   *     SumParam param;
+   *     // parse axis string to integer
+   *     param.axis = atoi(attrs->dict["axis"].c_str());
+   *     // set the parsed parameter
+   *     attrs->parsed = std::move(param);
+   *  }
+   *  // The other function that can utilize the parsed result.
+   *  TShape SumInferShape(const NodeAttrs& attrs,
+   *                       const std::vector<TShape>& ishapes) {
+   *     // we can use the parsed version of param
+   *     // without repeatively parsing the parameter
+   *     const SumParam& param = nngraph::get<SumParam>(attrs.parsed);
+   *  }
+   * \endcode
+   */
+  void (*attr_parser)(NodeAttrs* attrs) = nullptr;
+
+  // function fields.
+  /*!
+   * \brief setter function during registration
+   *  Set the description of operator
+   * \param descr the description string.
+   * \return reference to self.
+   */
+  inline Op& describe(const std::string& descr);  // NOLINT(*)
+  /*!
+   * \brief Set the num_inputs
+   * \param n The number of inputs to be set.
+   * \return reference to self.
+   */
+  inline Op& set_num_inputs(int n);  // NOLINT(*)
+  /*!
+   * \brief Set the num_outputs
+   * \param n The number of outputs to be set.
+   * \return reference to self.
+   */
+  inline Op& set_num_outputs(int n);  // NOLINT(*)
+  /*!
+   * \brief Set the get_num_outputs function.
+   * \param fn The function to be set.
+   * \return reference to self.
+   */
+  inline Op& set_num_outputs(int (*fn)(const Node& node));  // NOLINT(*)
+  /*!
+   * \brief Set the attr_parser function.
+   * \param fn The number of outputs to be set.
+   * \return reference to self.
+   */
+  inline Op& set_attr_parser(void (*fn)(NodeAttrs* attrs));  // NOLINT(*)
+  /*!
+   * \brief Register additional attributes to operator.
+   * \param attr_name The name of the attribute.
+   * \param value The value to be set.
+   * \tparam ValueType The type of the value to be set.
+   */
+  template<typename ValueType>
+  inline Op& attr(const std::string& attr_name,  // NOLINT(*)
+                  const ValueType& value);
+  /*!
+   * \brief Get an Op for a given operator name.
+   *  Will raise an error if the op has not been registered.
+   * \param op_name Name of the operator.
+   * \return Pointer to a Op, valid throughout program lifetime.
+   */
+  static const Op* Get(const std::string& op_name);
+  /*!
+   * \brief Get additional registered attribute about operators.
+   * \param attr_name The name of the attribute.
+   * \return An OpMap of specified attr_name.
+   * \tparam ValueType The type of the attribute.
+   */
+  template<typename ValueType>
+  static const OpMap<ValueType>& GetAttr(const std::string& attr_name);
+
+ private:
+  template<typename ValueType>
+  friend class OpMap;
+  friend class dmlc::Registry<Op>;
+  // Program internal unique index of operator.
+  // Used to help index the program.
+  uint32_t index_{0};
+  // internal constructor
+  Op();
+  // get const reference to certain attribute
+  static const any& GetAttrMap(const std::string& key);
+  // update the attribute OpMap
+  static void UpdateAttrMap(const std::string& key,
+                            std::function<void(any*)> updater);
+};
+
+/*!
+ * \brief A map data structure that takes Op* as key
+ *  and returns ValueType
+ * \tparam ValueType The type of the value stored in map.
+ */
+template<typename ValueType>
+class OpMap {
+ public:
+  /*!
+   * \brief get the corresponding value element at op
+   * \param op The key to the map
+   * \return the const reference to the content value.
+   */
+  inline const ValueType& operator[](const Op* op) const;
+  /*!
+   * \brief Check if the map has op as key.
+   * \param op The key to the map
+   * \return 1 if op is contained in map, 0 otherwise.
+   */
+  inline int count(const Op* op) const;
+
+ private:
+  friend class Op;
+  // internal attribute name
+  std::string attr_name_;
+  // internal data
+  std::vector<std::pair<ValueType, int> > data_;
+  OpMap() = default;
+};
+
+// internal macros to make
+#define NNGRAPH_STR_CONCAT_(__x, __y) __x##__y
+#define NNGRAPH_STR_CONCAT(__x, __y) NNGRAPH_STR_CONCAT_(__x, __y)
+#define NNGRAPH_REGISTER_VAR_DEF(OpName)                              \
+  static ::nngraph::Op & __make_ ## NNGraphOp ## _ ## OpName
+
+/*!
+ * \def NNGRAPH_REGISTER_OP
+ * \brief Register
+ * This macro must be used under namespace dmlc, and only used once in cc file.
+ * \param OpName The name of registry
+ *
+ * \code
+ *
+ *  NNGRAPH_REGISTER_OP(add)
+ *  .describe("add two inputs together")
+ *  .set_num_inputs(2)
+ *  .attr<OpKernel>("gpu_kernel", AddKernel);
+ *
+ * \endcode
+ */
+#define NNGRAPH_REGISTER_OP(OpName)                                     \
+  NNGRAPH_STR_CONCAT(NNGRAPH_REGISTER_VAR_DEF(OpName), __COUNTER__) =   \
+      ::dmlc::Registry<::nngraph::Op>::Get()->__REGISTER_OR_GET__(#OpName)
+
+// implementations of template functions after this.
+// member function of Op
+template<typename ValueType>
+inline const OpMap<ValueType>& Op::GetAttr(const std::string& key) {
+  const any& ref = GetAttrMap(key);
+  return nngraph::get<OpMap<ValueType> >(ref);
+}
+
+template<typename ValueType>
+inline Op& Op::attr(  // NOLINT(*)
+    const std::string& attr_name, const ValueType& value) {
+  UpdateAttrMap(attr_name, [this, attr_name, value](any* pmap) {
+      if (pmap->empty()) {
+        OpMap<ValueType> pm;
+        pm.attr_name_ = attr_name;
+        *pmap = pm;
+      }
+      CHECK_EQ(pmap->type(), typeid(OpMap<ValueType>))
+          << "Attribute " << attr_name
+          << " of operator " << this->name
+          << " is registered as inconsistent types"
+          << " previously " << pmap->type().name()
+          << " current " << typeid(OpMap<ValueType>).name();
+      std::vector<std::pair<ValueType, int> >& vec =
+          nngraph::get<OpMap<ValueType> >(*pmap).data_;
+      // resize the value type.
+      vec.resize(index_ + 1,
+                 std::make_pair(ValueType(), 0));
+      std::pair<ValueType, int>& p = vec[index_];
+      CHECK(p.second == 0 || p.first == value)
+          << "Attribute " << attr_name
+          << " of operator " << this->name
+          << " is already registered to a different value";
+          vec[index_] = std::make_pair(value, 1);
+    });
+  return *this;
+}
+
+inline Op& Op::describe(const std::string& descr) {  // NOLINT(*)
+  this->description = descr;
+  return *this;
+}
+
+inline Op& Op::set_num_inputs(int n) {  // NOLINT(*)
+  this->num_inputs = n;
+  return *this;
+}
+
+inline Op& Op::set_num_outputs(int n) {  // NOLINT(*)
+  this->num_outputs = n;
+  return *this;
+}
+
+inline Op& Op::set_num_outputs(int (*fn)(const Node& node)) {  // NOLINT(*)
+  this->num_outputs = kVarg;
+  this->get_num_outputs = fn;
+  return *this;
+}
+
+inline Op& Op::set_attr_parser(void (*fn)(NodeAttrs* attrs)) {  // NOLINT(*)
+  this->attr_parser = fn;
+  return *this;
+}
+
+// member functions of OpMap
+template<typename ValueType>
+inline int OpMap<ValueType>::count(const Op* op) const {
+  const uint32_t idx = op->index_;
+  return idx < data_.size() ? data_[idx].second : 0;
+}
+
+template<typename ValueType>
+inline const ValueType& OpMap<ValueType>::operator[](const Op* op) const {
+  const uint32_t idx = op->index_;
+  CHECK(idx < data_.size() && data_[idx].second)
+        << "Attribute " << attr_name_
+        << " has not been registered for Operator " << op->name;
+  return data_[idx].first;
+}
+
+}  // namespace nngraph
+
+#endif  // NNGRAPH_OP_H_
diff --git a/nnvm/include/nngraph/op_prop.h b/nnvm/include/nngraph/op_prop.h
deleted file mode 100644
index 99595b2853e7..000000000000
--- a/nnvm/include/nngraph/op_prop.h
+++ /dev/null
@@ -1,26 +0,0 @@
-/*!
- *  Copyright (c) 2016 by Contributors
- * \file op_prop.h
- * \brief Data structure about property of operators
- */
-#ifndef NNGRAPH_OP_PROP_H_
-#define NNGRAPH_OP_PROP_H_
-
-namespace nngraph {
-
-/*!
- * \brief operator specific data structure
- */
-struct OpProperty {
-  /*! \brief name of the operator */
-  std::string name;
-  /*! \brief number of inputs to the operator */
-  int num_inputs;
-  /*! \brief number of outputs to the operator */
-  int num_outputs;
-};
-
-
-}  // namespace nngraph
-
-#endif  // NNGRAPH_OP_PROP_H_
diff --git a/nnvm/src/core/node.cc b/nnvm/src/core/node.cc
new file mode 100644
index 000000000000..deccb208f00e
--- /dev/null
+++ b/nnvm/src/core/node.cc
@@ -0,0 +1,38 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file node.cc
+ * \brief Graph node data structure.
+ */
+#include <nngraph/node.h>
+
+namespace nngraph {
+
+Node::~Node() {
+  if (inputs.size() != 0) {
+    // explicit deletion via DFS
+    // this is used to avoid stackoverflow caused by chain of deletions
+    std::vector<Node*> stack{this};
+    std::vector<std::shared_ptr<Node> > to_delete;
+    while (!stack.empty()) {
+      Node* n = stack.back();
+      stack.pop_back();
+      for (NodeEntry& e : n->inputs) {
+        if (e.node.unique()) {
+          stack.push_back(e.node.get());
+          to_delete.emplace_back(std::move(e.node));
+        } else {
+          e.node.reset();
+        }
+      }
+      n->inputs.clear();
+    }
+  }
+}
+
+std::shared_ptr<Node> Node::Create() {
+  // NOTE: possible change to thread local memory pool
+  // via std::allocate_shared instead for faster allocation.
+  return std::make_shared<Node>();
+}
+
+}  // namespace nngraph
diff --git a/nnvm/src/core/op.cc b/nnvm/src/core/op.cc
new file mode 100644
index 000000000000..9705f57b1034
--- /dev/null
+++ b/nnvm/src/core/op.cc
@@ -0,0 +1,69 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file op.cc
+ * \brief Support for operator registry.
+ */
+#include <nngraph/base.h>
+#include <nngraph/op.h>
+
+#include <atomic>
+#include <mutex>
+
+namespace dmlc {
+// enable registry
+DMLC_REGISTRY_ENABLE(nngraph::Op);
+}  // namespace dmlc
+
+namespace nngraph {
+
+// single manager of operator information.
+struct OpManager {
+  // mutex to avoid registration from multiple threads.
+  std::mutex mutex;
+  // global operator counter
+  std::atomic<int> op_counter{0};
+  // storage of additional attribute table.
+  std::unordered_map<std::string, any> attr;
+  // get singleton of the
+  static OpManager* Global() {
+    static OpManager inst;
+    return &inst;
+  }
+};
+
+// constructor
+Op::Op() {
+  OpManager* mgr = OpManager::Global();
+  index_ = mgr->op_counter++;
+}
+
+// find operator by name
+const Op* Op::Get(const std::string& name) {
+  const Op* op = dmlc::Registry<Op>::Find(name);
+  CHECK(op != nullptr)
+      << "Operator " << name << " is not registered";
+  return op;
+}
+
+// Get attribute map by key
+const any& Op::GetAttrMap(const std::string& key) {
+  // assume no operator registration during
+  // the execution phase.
+  const auto& dict = OpManager::Global()->attr;
+  auto it = dict.find(key);
+  CHECK(it != dict.end() && it->first == key)
+      << "Cannot find Operator attribute " << key
+      << " for any operator";
+  return it->second;
+}
+
+// update attribute map by updater function.
+void Op::UpdateAttrMap(const std::string& key,
+                       std::function<void(any*)> updater) {
+  OpManager* mgr = OpManager::Global();
+  std::lock_guard<std::mutex>(mgr->mutex);
+  any& value = mgr->attr[key];
+  updater(&value);
+}
+
+}  // namespace nngraph
diff --git a/nnvm/src/example/operator.cc b/nnvm/src/example/operator.cc
new file mode 100644
index 000000000000..a33307e59c80
--- /dev/null
+++ b/nnvm/src/example/operator.cc
@@ -0,0 +1,14 @@
+// Copyright (c) 2016 by Contributors
+// This is an example on how we can register operator information to NNGRAPH
+
+#include <nngraph/op.h>
+#include <utility>
+
+NNGRAPH_REGISTER_OP(add)
+.describe("add two data together")
+.set_num_inputs(2)
+.attr("inplace_pair", std::make_pair(0, 0));
+
+
+NNGRAPH_REGISTER_OP(add)
+.attr<std::string>("nick_name", "plus");
diff --git a/nnvm/src/nngraph.cc b/nnvm/src/nngraph.cc
deleted file mode 100644
index adcf5858107c..000000000000
--- a/nnvm/src/nngraph.cc
+++ /dev/null
@@ -1,7 +0,0 @@
-#include <nngraph/graph.h>
-
-int main() {
-  nngraph::any a = 1;
-  LOG(INFO) << nngraph::get<int>(a);
-  return 0;
-}
diff --git a/nnvm/src/test_main.cc b/nnvm/src/test_main.cc
new file mode 100644
index 000000000000..52883d387c35
--- /dev/null
+++ b/nnvm/src/test_main.cc
@@ -0,0 +1,11 @@
+// Copyright (c) 2016 by Contributors
+#include <nngraph/op.h>
+#include <nngraph/graph.h>
+
+int main() {
+  using namespace nngraph;
+  auto add = Op::Get("add");
+  auto nick = Op::GetAttr<std::string>("nick_name");
+  LOG(INFO) << "nick=" << nick[add];
+  return 0;
+}

From 3c69e2803131d491f2ab719f63b1847a56a053af Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Mon, 4 Jul 2016 17:27:24 -0700
Subject: [PATCH 324/949] Add TShape and Tuple to nngraph

---
 nnvm/include/nngraph/graph.h |  23 +--
 nnvm/include/nngraph/node.h  |  21 ++-
 nnvm/include/nngraph/tuple.h | 334 +++++++++++++++++++++++++++++++++++
 nnvm/src/core/node.cc        |   7 +
 nnvm/src/test_main.cc        |  29 ++-
 5 files changed, 395 insertions(+), 19 deletions(-)
 create mode 100644 nnvm/include/nngraph/tuple.h

diff --git a/nnvm/include/nngraph/graph.h b/nnvm/include/nngraph/graph.h
index 5c4509f4a63c..df69d1ebdcab 100644
--- a/nnvm/include/nngraph/graph.h
+++ b/nnvm/include/nngraph/graph.h
@@ -7,6 +7,8 @@
 #define NNGRAPH_GRAPH_H_
 
 #include <vector>
+#include <string>
+#include <unordered_map>
 #include "./node.h"
 
 namespace nngraph {
@@ -16,23 +18,10 @@ namespace nngraph {
  */
 class Graph {
  public:
-  /*!
-   * \brief get the index th element from the returned tuple.
-   * \param index index of multi output
-   * \return the symbol corresponds to the indexed element.
-   */
-  Graph operator[] (size_t index) const;
-  /*!
-   * \brief get number of outputs of this symbol
-   * \return number of outputs
-   */
-  inline size_t outputs_size() const {
-    return outputs_.size();
-  }
-
- private:
-  /*! \brief outputs of the graph. */
-  std::vector<NodeEntry> outputs_;
+  /*! \brief outputs of the computation graph. */
+  std::vector<NodeEntry> outputs;
+  /*! \brief attributes of a graph */
+  std::unordered_map<std::string, any> attrs;
 };
 
 }  // namespace nngraph
diff --git a/nnvm/include/nngraph/node.h b/nnvm/include/nngraph/node.h
index e0823fa009ce..7a3817bdb282 100644
--- a/nnvm/include/nngraph/node.h
+++ b/nnvm/include/nngraph/node.h
@@ -47,14 +47,28 @@ class Node {
  public:
   /*! \brief name of the node */
   std::string name;
-  /*! \brief the operator this node is pointing at */
+  /*!
+   * \brief The operator this node uses.
+   *  For place holder variable, op == nullptr.
+   */
   const Op *op;
   /*! \brief inputs to this node */
   std::vector<NodeEntry> inputs;
+  /*!
+   * \brief Optional control flow dependencies
+   *  Gives operation must be performed before this operation.
+   */
+  std::vector<std::shared_ptr<Node> > control_deps;
   /*! \brief The attributes in the node. */
   NodeAttrs attrs;
   /*! \brief destructor of node */
   ~Node();
+  /*!
+   * \brief return whether node is placeholder variable.
+   *  This is equivalent to op == nullptr
+   * \return whether node is placeholder input variable
+   */
+  inline bool is_variable() const;
   /*!
    * \brief create a new empty shared_ptr of Node.
    * \return a created empty node.
@@ -62,6 +76,11 @@ class Node {
   static std::shared_ptr<Node> Create();
 };
 
+// implementation of functions.
+inline bool Node::is_variable() const {
+  return this->op == nullptr;
+}
+
 }  // namespace nngraph
 
 #endif  // NNGRAPH_NODE_H_
diff --git a/nnvm/include/nngraph/tuple.h b/nnvm/include/nngraph/tuple.h
new file mode 100644
index 000000000000..742b5757c390
--- /dev/null
+++ b/nnvm/include/nngraph/tuple.h
@@ -0,0 +1,334 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file tuple.h
+ * \brief Data structure Tuple and TShape to store dynamic sized shapes.
+ */
+#ifndef NNGRAPH_TUPLE_H_
+#define NNGRAPH_TUPLE_H_
+
+#include <vector>
+#include <type_traits>
+#include <algorithm>
+#include <iostream>
+
+namespace nngraph {
+
+/*! \brief data type to store array index */
+typedef uint32_t index_t;
+
+/*!
+ * \brief A dynamic sized array data strcuture
+ *  that is optimized for storing small number of elements with same type.
+ *  Data will be stored in stack when number of elements is small.
+ *
+ *  It is suitable to hold Shape of Tensor.
+ *
+ * \tparam ValueType The type of data stored inside tuple.
+ * \sa TShape
+ */
+template<typename ValueType>
+class Tuple {
+ public:
+  // Tuple requires the content to be simple data type.
+  static_assert(std::is_pod<ValueType>::value,
+                "Tuple only support simple data type like int");
+  /*! \brief default constructor */
+  Tuple() = default;
+  /*! \brief destructor */
+  inline ~Tuple() {
+    delete [] data_heap_;
+  }
+  /*!
+   * \brief copy constructor from another tuple
+   * \param s the source tuple
+   */
+  inline Tuple(const Tuple<ValueType>& s) {
+    this->assign(s.begin(), s.end());
+  }
+  /*!
+   * \brief constructor from initializer list
+   * \param init the initializer_list
+   */
+  inline Tuple(std::initializer_list<ValueType> init) {
+    this->assign(init.begin(), init.end());
+  }
+  /*!
+   * \brief move constructor from Tuple
+   * \param src the source shape
+   */
+  inline Tuple(Tuple<ValueType>&& src) {
+    this->swap(src);
+  }
+  /*!
+   * \brief construct an Tuple to fill the value with v.
+   * \param ndim the number of dimension of the Tuple
+   * \param v The value to fill.
+   */
+  inline Tuple(index_t ndim, ValueType v) {
+    this->SetDim(ndim);
+    std::fill_n(begin(), ndim, v);
+  }
+  /*!
+   * \brief construct the Tuple from content of iterator
+   * \param begin the beginning of iterator
+   * \param end end the end of the iterator
+   * \tparam RandomAccessIterator iterator type
+   */
+  template<typename RandomAccessIterator>
+  inline Tuple(RandomAccessIterator begin,
+               RandomAccessIterator end) {
+    this->assign(begin, end);
+  }
+  /*!
+   * \brief Assign content to tuple from iterator.
+   * \param begin the beginning of iteratro
+   * \param end end the end of the iterator
+   * \tparam RandomAccessIterator iterator type
+   */
+  template<typename RandomAccessIterator>
+  inline void assign(RandomAccessIterator begin,
+                     RandomAccessIterator end) {
+    this->SetDim(end - begin);
+    std::copy(begin, end, this->begin());
+  }
+  /*!
+   * \brief Swap current object with other
+   * \param other another object to be swapped.
+   */
+  inline void swap(Tuple<ValueType>& other) noexcept {  // NOLINT(*)
+    std::swap(ndim_, other.ndim_);
+    std::swap(num_heap_allocated_, other.num_heap_allocated_);
+    std::swap(data_stack_, other.data_stack_);
+    std::swap(data_heap_, other.data_heap_);
+  }
+  /*!
+   * \brief assignment from another tuple.
+   * \param src source tuple
+   * \return reference of self
+   */
+  inline Tuple<ValueType>& operator=(const Tuple<ValueType>& src) {
+    this->assign(src.begin(), src.end());
+    return *this;
+  }
+  /*!
+   * \brief assignment from rvalue of another tuple.
+   * \param src source tuple
+   * \return reference of self
+   */
+  inline Tuple<ValueType>& operator=(Tuple<ValueType>&& src) {
+    Tuple<ValueType>(std::move(src)).swap(*this);
+    return *this;
+  }
+  /*!
+   * \brief assignment from initializer list
+   * \param init the source initializer list
+   * \return reference of self
+   */
+  inline Tuple<ValueType> &operator=(std::initializer_list<ValueType> init) {
+    this->assign(init.begin(), init.end());
+    return *this;
+  }
+  /*!
+   * \return whether two tuple equals
+   * \param s the tuple to compare against
+   */
+  inline bool operator==(const Tuple<ValueType> &s) const {
+    if (ndim_ != s.ndim_) return false;
+    return std::equal(begin(), end(), s.begin());
+  }
+  /*!
+   * \return whether two tuple not equal
+   * \param s the tuple to compare against
+   */
+  inline bool operator!=(const Tuple<ValueType> &s) const {
+    return !(*this == s);
+  }
+  /*! \return the begin data pointer to content of the tuple */
+  inline const ValueType *begin() const {
+    return ndim_ <= kStackCache ? data_stack_ : data_heap_;
+  }
+  /*! \return the begin data pointer to content of the tuple */
+  inline ValueType *begin() {
+    return ndim_ <= kStackCache ? data_stack_ : data_heap_;
+  }
+  /*! \return the data pointer to end of the tuple */
+  inline const ValueType* end() const {
+    return ndim_ <= kStackCache ? (data_stack_ + ndim_): (data_heap_ + ndim_);
+  }
+  /*! \return the data pointer to end the tuple */
+  inline ValueType* end() {
+    return ndim_ <= kStackCache ? (data_stack_ + ndim_): (data_heap_ + ndim_);
+  }
+  /*! \return number of dimension of the tuple */
+  inline index_t ndim() const {
+    return ndim_;
+  }
+  /*!
+   * \brief get corresponding index
+   * \param i dimension index
+   * \return the corresponding dimension size
+   */
+  inline ValueType& operator[](index_t i) {
+    return begin()[i];
+  }
+  /*!
+   * \brief get corresponding index
+   * \param i dimension index
+   * \return the corresponding dimension size
+   */
+  inline const ValueType& operator[](index_t i) const {
+    return begin()[i];
+  }
+
+  /*!
+   * \brief allow output string of tuple to ostream
+   * \param os the output stream
+   * \param t the tuple
+   * \return the ostream
+   */
+  friend std::ostream &operator<<(std::ostream &os, const Tuple<ValueType> &t) {
+    os << '(';
+    const ValueType* begin = t.begin();
+    const ValueType* end = t.end();
+    for (const ValueType* it = begin; it != end; ++it) {
+      if (it != begin) os << ',';
+      os << *it;
+    }
+    // python style tuple
+    if (t.ndim() == 1) os << ',';
+    os << ')';
+    return os;
+  }
+  /*!
+   * \brief read tuple from the istream
+   * \param is the input stream
+   * \param t The tuple
+   * \return the istream
+   */
+  friend std::istream &operator>>(std::istream &is, Tuple<ValueType> &t) {
+    // get (
+    while (true) {
+      char ch = is.peek();
+      if (isdigit(ch)) {
+        ValueType idx;
+        if (is >> idx) {
+          t.assign(&idx, &idx + 1);
+        }
+        return is;
+      }
+      is.get();
+      if (ch == '(') break;
+      if (!isspace(ch)) {
+        is.setstate(std::ios::failbit);
+        return is;
+    }
+    }
+    index_t idx;
+    std::vector<ValueType> tmp;
+    while (is >> idx) {
+      tmp.push_back(idx);
+      char ch;
+      do {
+        ch = is.get();
+      } while (isspace(ch));
+      if (std::is_integral<ValueType>::value && ch == 'L') {
+        ch = is.get();
+      }
+      if (ch == ',') {
+        while (true) {
+          ch = is.peek();
+          if (isspace(ch)) {
+            is.get(); continue;
+          }
+          if (ch == ')') {
+            is.get(); break;
+          }
+          break;
+        }
+        if (ch == ')') break;
+      } else if (ch == ')') {
+        break;
+      } else {
+        is.setstate(std::ios::failbit);
+        return is;
+      }
+    }
+    t.assign(tmp.begin(), tmp.end());
+    return is;
+  }
+
+ private:
+  // stack cache size
+  static const uint32_t kStackCache = 4;
+  /*! \brief number of dimension of the tuple */
+  index_t ndim_{0};
+  /*! \brief number of cells allocated in data_heap_ */
+  index_t num_heap_allocated_{0};
+  /*! \brief in stack space used to store shape when it is small */
+  ValueType data_stack_[kStackCache];
+  /*! \brief space to store shape when dimension is big*/
+  ValueType* data_heap_{nullptr};
+  // internal function to change the dimension
+  inline void SetDim(index_t dim) {
+    if (dim > kStackCache &&
+        dim > num_heap_allocated_) {
+      delete [] data_heap_;
+      data_heap_ = new ValueType[dim];
+      num_heap_allocated_ = dim;
+    }
+    ndim_ = dim;
+  }
+};
+
+/*!
+ * \brief A Shape class that is used to represent shape of each tensor.
+ */
+class TShape : public Tuple<index_t> {
+ public:
+  // inheritate other constructors from Tuple
+  using Tuple<index_t>::Tuple;
+  /*!
+   * \brief copy constructor of TShape
+   * \param s source shape.
+   */
+  inline TShape(const Tuple<index_t>& s)  // NOLINT(*)
+      : Tuple<index_t>(s) {}
+  /*!
+   * \brief move constructor.
+   * \param s source shape.
+   */
+  inline TShape(Tuple<index_t>&& s) {  // NOLINT(*)
+    this->swap(s);
+  }
+  /*!
+   * \brief assignment function from tshape
+   * \param src source shape.
+   * \return self.
+   */
+  inline TShape& operator=(const Tuple<index_t>& src) {
+    this->assign(src.begin(), src.end());
+    return *this;
+  }
+  /*!
+   * \brief move assignment function from tshape
+   * \param src source shape.
+   * \return self.
+   */
+  inline TShape& operator=(Tuple<index_t>&& src) {  // NOLINT(*)
+    TShape(std::move(src)).swap(*this);  // NOLINT(*)
+    return *this;
+  }
+  /*! \return total number of elements in the shape */
+  inline size_t Size() const {
+    size_t size = 1;
+    const index_t* start = begin(), *fin = end();
+    for (const index_t* it = start; it != fin; ++it) {
+      size *= *it;
+    }
+    return size;
+  }
+};
+
+}  // namespace nngraph
+
+#endif  // NNGRAPH_TUPLE_H_
diff --git a/nnvm/src/core/node.cc b/nnvm/src/core/node.cc
index deccb208f00e..5051c1eb3259 100644
--- a/nnvm/src/core/node.cc
+++ b/nnvm/src/core/node.cc
@@ -24,6 +24,13 @@ Node::~Node() {
           e.node.reset();
         }
       }
+      for (std::shared_ptr<Node>& sp : n->control_deps) {
+        if (sp.unique()) {
+          stack.push_back(sp.get());
+        } else {
+          sp.reset();
+        }
+      }
       n->inputs.clear();
     }
   }
diff --git a/nnvm/src/test_main.cc b/nnvm/src/test_main.cc
index 52883d387c35..8bb1e5252a23 100644
--- a/nnvm/src/test_main.cc
+++ b/nnvm/src/test_main.cc
@@ -1,11 +1,38 @@
 // Copyright (c) 2016 by Contributors
 #include <nngraph/op.h>
 #include <nngraph/graph.h>
+#include <nngraph/tuple.h>
+#include <string>
 
-int main() {
+void test_op() {
   using namespace nngraph;
   auto add = Op::Get("add");
   auto nick = Op::GetAttr<std::string>("nick_name");
   LOG(INFO) << "nick=" << nick[add];
+}
+
+void test_tuple() {
+  using nngraph::Tuple;
+  using nngraph::TShape;
+  Tuple<int> x{1, 2, 3};
+  Tuple<int> y{1, 2, 3, 5, 6};
+  x = std::move(y);
+
+  CHECK_EQ(x.ndim(), 5);
+  Tuple<int> z{1, 2, 3, 5, 6};
+  std::ostringstream os;
+  os << z;
+  CHECK_EQ(os.str(), "(1,2,3,5,6)");
+  std::istringstream is(os.str());
+  is >> y;
+  CHECK_EQ(x, y);
+  Tuple<nngraph::index_t> ss{1, 2, 3};
+  TShape s = ss;
+  s = std::move(ss);
+  CHECK((s == TShape{1, 2, 3}));
+}
+
+int main() {
+  test_tuple();
   return 0;
 }

From 6bdbaf4fc7a7023d9720c2b8dc5f7b1429b13162 Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Tue, 5 Jul 2016 11:23:49 -0700
Subject: [PATCH 325/949] Add unittest

---
 nnvm/Makefile                | 21 ++++++++++++++-------
 nnvm/tests/cpp/.gitignore    |  3 +++
 nnvm/tests/cpp/op_test.cc    | 21 +++++++++++++++++++++
 nnvm/tests/cpp/tuple_test.cc | 24 ++++++++++++++++++++++++
 nnvm/tests/cpp/unittest.mk   | 12 ++++++++++++
 5 files changed, 74 insertions(+), 7 deletions(-)
 create mode 100644 nnvm/tests/cpp/.gitignore
 create mode 100644 nnvm/tests/cpp/op_test.cc
 create mode 100644 nnvm/tests/cpp/tuple_test.cc
 create mode 100644 nnvm/tests/cpp/unittest.mk

diff --git a/nnvm/Makefile b/nnvm/Makefile
index 23199fd6ba9a..6c98ee342e15 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -1,16 +1,19 @@
-export LDFLAGS= -pthread -lm
-export CFLAGS=  -std=c++11 -Wall -O3 -msse2  -Wno-unknown-pragmas -funroll-loops\
+export LDFLAGS = -pthread -lm
+export CFLAGS =  -std=c++11 -Wall -O3 -msse2  -Wno-unknown-pragmas -funroll-loops\
 	 -Iinclude -Idmlc-core/include -fPIC
 
 # specify tensor path
 .PHONY: clean all test lint doc
 
-
-all: lib/libnngraph.so test
+all: lib/libnngraph.so lib/libnngraph.a cli_test
 
 SRC = $(wildcard src/*.cc src/*/*.cc example/*.cc)
 ALL_OBJ = $(patsubst src/%.cc, build/%.o, $(SRC))
-ALL_DEP = $(ALL_OBJ)
+ALL_DEP = $(filter-out build/test_main.o, $(ALL_OBJ))
+
+include tests/cpp/unittest.mk
+
+test: $(TEST)
 
 build/%.o: src/%.cc
 	@mkdir -p $(@D)
@@ -21,7 +24,11 @@ lib/libnngraph.so: $(ALL_DEP)
 	@mkdir -p $(@D)
 	$(CXX) $(CFLAGS) -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
 
-test: $(ALL_DEP)
+lib/libnngraph.a: $(ALL_DEP)
+	@mkdir -p $(@D)
+	ar crv $@ $(filter %.o, $?)
+
+cli_test: $(ALL_DEP) build/test_main.o
 	$(CXX) $(CFLAGS)  -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
 
 lint:
@@ -31,7 +38,7 @@ doc:
 	doxygen docs/Doxyfile
 
 clean:
-	$(RM) -rf build lib bin *~ */*~ */*/*~ */*/*/*~ */*.o */*/*.o */*/*/*.o test
+	$(RM) -rf build lib bin *~ */*~ */*/*~ */*/*/*~ */*.o */*/*.o */*/*/*.o cli_test
 
 -include build/*.d
 -include build/*/*.d
diff --git a/nnvm/tests/cpp/.gitignore b/nnvm/tests/cpp/.gitignore
new file mode 100644
index 000000000000..9da7ef4d4178
--- /dev/null
+++ b/nnvm/tests/cpp/.gitignore
@@ -0,0 +1,3 @@
+unittest
+*.d
+*_test
\ No newline at end of file
diff --git a/nnvm/tests/cpp/op_test.cc b/nnvm/tests/cpp/op_test.cc
new file mode 100644
index 000000000000..c0a8913f4aef
--- /dev/null
+++ b/nnvm/tests/cpp/op_test.cc
@@ -0,0 +1,21 @@
+#include <dmlc/logging.h>
+#include <gtest/gtest.h>
+#include <nngraph/op.h>
+#include <utility>
+
+NNGRAPH_REGISTER_OP(add)
+.describe("add two data together")
+.set_num_inputs(2)
+.attr("inplace_pair", std::make_pair(0, 0));
+
+NNGRAPH_REGISTER_OP(add)
+.attr<std::string>("nick_name", "plus");
+
+
+TEST(Op, GetAttr) {
+  using namespace nngraph;
+  auto add = Op::Get("add");
+  auto nick = Op::GetAttr<std::string>("nick_name");
+
+  CHECK_EQ(nick[add], "plus");
+}
diff --git a/nnvm/tests/cpp/tuple_test.cc b/nnvm/tests/cpp/tuple_test.cc
new file mode 100644
index 000000000000..4750e01d2541
--- /dev/null
+++ b/nnvm/tests/cpp/tuple_test.cc
@@ -0,0 +1,24 @@
+#include <dmlc/logging.h>
+#include <gtest/gtest.h>
+#include <nngraph/tuple.h>
+
+TEST(Tuple, Basic) {
+  using nngraph::Tuple;
+  using nngraph::TShape;
+  Tuple<int> x{1, 2, 3};
+  Tuple<int> y{1, 2, 3, 5, 6};
+  x = std::move(y);
+
+  CHECK_EQ(x.ndim(), 5);
+  Tuple<int> z{1, 2, 3, 5, 6};
+  std::ostringstream os;
+  os << z;
+  CHECK_EQ(os.str(), "(1,2,3,5,6)");
+  std::istringstream is(os.str());
+  is >> y;
+  CHECK_EQ(x, y);
+  Tuple<nngraph::index_t> ss{1, 2, 3};
+  TShape s = ss;
+  s = std::move(ss);
+  CHECK((s == TShape{1, 2, 3}));
+}
diff --git a/nnvm/tests/cpp/unittest.mk b/nnvm/tests/cpp/unittest.mk
new file mode 100644
index 000000000000..ade9f93947bd
--- /dev/null
+++ b/nnvm/tests/cpp/unittest.mk
@@ -0,0 +1,12 @@
+GTEST_LIB=$(GTEST_PATH)/lib/
+GTEST_INC=$(GTEST_PATH)/include/
+
+TEST_SRC = $(wildcard tests/cpp/*_test.cc)
+TEST = $(patsubst tests/cpp/%_test.cc, tests/cpp/%_test, $(TEST_SRC))
+
+tests/cpp/%_test: tests/cpp/%_test.cc lib/libnngraph.a
+	$(CXX) -std=c++11 $(CFLAGS) -MM -MT tests/cpp/$* $< >tests/cpp/$*.d
+	$(CXX) -std=c++11 $(CFLAGS) -I$(GTEST_INC) -o $@ $(filter %.cc %.a, $^)  \
+		-L$(GTEST_LIB)  $(LDFLAGS) -lgtest -lgtest_main
+
+-include tests/cpp/*.d

From 275269c7dd2d271eea9d217c4429d8cd6170cea1 Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Tue, 5 Jul 2016 18:35:25 -0700
Subject: [PATCH 326/949] checkin dfs visit from min

---
 nnvm/include/nngraph/base.h             |   8 ++
 nnvm/include/nngraph/graph.h            |  70 ++++++++++++++
 nnvm/include/nngraph/graph_attr_types.h | 122 ++++++++++++++++++++++++
 nnvm/include/nngraph/tuple.h            |   2 +-
 nnvm/src/test_main.cc                   |   7 ++
 nnvm/tests/cpp/.gitignore               |   2 +-
 6 files changed, 209 insertions(+), 2 deletions(-)
 create mode 100644 nnvm/include/nngraph/graph_attr_types.h

diff --git a/nnvm/include/nngraph/base.h b/nnvm/include/nngraph/base.h
index 19827aa71ea1..155f688165c4 100644
--- a/nnvm/include/nngraph/base.h
+++ b/nnvm/include/nngraph/base.h
@@ -10,12 +10,20 @@
 #include <dmlc/any.h>
 #include <dmlc/logging.h>
 #include <dmlc/registry.h>
+#include <dmlc/array_view.h>
 
 namespace nngraph {
 
 /*! \brief any type */
 using any = dmlc::any;
 
+/*!
+ * \brief array_veiw type
+ * \tparam ValueType The value content of array view.
+ */
+template<typename ValueType>
+using array_view = dmlc::array_view<ValueType>;
+
 /*!
  * \brief get reference of type T stored in src.
  * \param src The source container
diff --git a/nnvm/include/nngraph/graph.h b/nnvm/include/nngraph/graph.h
index df69d1ebdcab..91522279cadd 100644
--- a/nnvm/include/nngraph/graph.h
+++ b/nnvm/include/nngraph/graph.h
@@ -8,7 +8,10 @@
 
 #include <vector>
 #include <string>
+#include <utility>
+#include <algorithm>
 #include <unordered_map>
+#include <unordered_set>
 #include "./node.h"
 
 namespace nngraph {
@@ -22,8 +25,75 @@ class Graph {
   std::vector<NodeEntry> outputs;
   /*! \brief attributes of a graph */
   std::unordered_map<std::string, any> attrs;
+
+  /*!
+   * \brief perform a Post Order DFS visit to each node in the graph.
+   *  This order is deterministic and is also topoligical sorted.
+   * \param fvisit a function of type std::function<void(const std::shared_ptr<Node>&)>
+   * \tparam FVisit The function type to perform the visit.
+   */
+  template<typename FVisit>
+  inline void DFSVisit(FVisit fvisit) const;
 };
 
+// inline function implementations
+template <typename GNode, typename HashType,
+           typename FVisit, typename HashFunc,
+          typename InDegree, typename GetInput>
+void PostOrderDFSVisit(const std::vector<GNode>& heads,
+                       FVisit fvisit,
+                       HashFunc hash,
+                       InDegree indegree,
+                       GetInput getinput) {
+  std::vector<std::pair<GNode, uint32_t> > stack;
+  std::unordered_set<HashType> visited;
+  for (auto& head : heads) {
+    HashType head_hash = hash(head);
+    if (visited.count(head_hash) == 0) {
+      stack.push_back(std::make_pair(head, 0));
+      visited.insert(head_hash);
+    }
+    while (!stack.empty()) {
+      std::pair<GNode, uint32_t>& back = stack.back();
+      if (back.second == indegree(back.first)) {
+        fvisit(back.first);
+        stack.pop_back();
+      } else {
+        const GNode& input = getinput(back.first, back.second++);
+        HashType input_hash = hash(input);
+        if (visited.count(input_hash) == 0) {
+          stack.push_back(std::make_pair(input, 0));
+          visited.insert(input_hash);
+        }
+      }
+    }
+  }
+}
+
+template<typename FVisit>
+inline void Graph::DFSVisit(FVisit fvisit) const {
+  typedef const std::shared_ptr<Node>* GNode;
+  std::vector<GNode> head_nodes(outputs.size());
+  std::transform(outputs.begin(), outputs.end(), head_nodes.begin(),
+                 [](const NodeEntry& e)->GNode {
+                   return &e.node;
+                 });
+  PostOrderDFSVisit<GNode, Node*>(
+      head_nodes,
+      [fvisit](GNode n) { fvisit(*n); },  // FVisit
+      [](GNode n)->Node* { return n->get(); },  // HashFunc
+      [](GNode n)->uint32_t {  // InDegree
+        return (*n)->inputs.size() + (*n)->control_deps.size();
+      },
+      [](GNode n, uint32_t index)->GNode {  // GetInput
+        if (index < (*n)->inputs.size()) {
+          return &(*n)->inputs.at(index).node;
+        } else {
+          return &(*n)->control_deps.at(index - (*n)->inputs.size());
+        }
+      });
+}
+
 }  // namespace nngraph
 
 #endif  // NNGRAPH_GRAPH_H_
diff --git a/nnvm/include/nngraph/graph_attr_types.h b/nnvm/include/nngraph/graph_attr_types.h
new file mode 100644
index 000000000000..558bdf94707c
--- /dev/null
+++ b/nnvm/include/nngraph/graph_attr_types.h
@@ -0,0 +1,122 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file graph_attr_types.h
+ * \brief Data structures that can appear in graph attributes.
+ */
+#ifndef NNGRAPH_GRAPH_ATTR_TYPES_H_
+#define NNGRAPH_GRAPH_ATTR_TYPES_H_
+
+#include <vector>
+#include <unordered_map>
+#include "./graph.h"
+
+namespace nngraph {
+
+/*!
+ * \brief Index to the graph.
+ *  Maps pointers to Node to consecutive integers.
+ *
+ *  This is an Auxililary data structure that can be used
+ *  to iterate over the graph in a more efficient manner.
+ *  It also allows storing
+ */
+struct IndexedGraph {
+ public:
+  /*! \brief represents a data in the graph */
+  struct NodeEntry {
+    /*! \brief the source node id in the computation graph */
+    uint32_t node_id;
+    /*! \brief index of output from the source. */
+    uint32_t index;
+    /*!
+     * \brief compare equality
+     * \param other the other entry to compare
+     * \return whether two entries equals to each other
+     */
+    inline bool operator==(const NodeEntry& other) const {
+      return node_id == other.node_id && index == other.index;
+    }
+  };
+  /*! \brief Node data structure in IndexedGraph */
+  struct Node {
+    /*! \brief pointer to the source node */
+    const nngraph::Node* source;
+    /*! \brief inputs to the node */
+    array_view<NodeEntry> inputs;
+    /*! \brief control flow dependencies to the node */
+    array_view<uint32_t> control_deps;
+  };
+  /*! \return number of nodes in the graph */
+  inline size_t num_nodes() const {
+    return nodes_.size();
+  }
+  /*! \return total number of NodeEntry in the graph */
+  inline size_t num_node_entries() const {
+    return entry_rptr_.back();
+  }
+  /*!
+   * \brief Get a unique entry id between 0 to num_node_entries()
+   *  for a given IndexedGraph::NodeEntry
+   * \param e The entry to query for index.
+   * \return the unique index.
+   */
+  inline uint32_t entry_id(const NodeEntry& e) const {
+    return entry_rptr_[e.node_id] + e.index;
+  }
+  /*!
+   * \brief Get a unique entry id between 0 to num_node_entries()
+   *  for a given NodeEntry.
+   * \param e The entry to query for index.
+   * \return the unique index.
+   */
+  inline uint32_t entry_id(const nngraph::NodeEntry& e) const {
+    return entry_rptr_[node_id(e.node.get())] + e.index;
+  }
+  /*!
+   * \brief Get the corresponding node id for a given Node in the IndexedGraph.
+   * \param node The Node to query for index.
+   * \return the node index.
+   */
+  inline uint32_t node_id(const nngraph::Node* node) const {
+    return node2index_.at(node);
+  }
+  /*!
+   * \brief Get the corresponding Node structure for a given node_id.
+   * \param node_id The node id
+   * \return const reference to the corresponding IndexedGraph::Node
+   */
+  inline const Node& operator[](uint32_t node_id) const {
+    return nodes_[node_id];
+  }
+  /*!
+   * \brief Get the corresponding Node structure
+   * \param node The pointer to the Node structure
+   * \return const reference to the corresponding IndexedGraph::Node
+   */
+  inline const Node& operator[](const nngraph::Node* node) const {
+    return nodes_[node_id(node)];
+  }
+  /*!
+   * \brief Constructor an IndexedGraph from normal Graph
+   * \param other The source graph.
+   */
+  explicit IndexedGraph(const Graph& other);
+  // disallow copy assign
+  IndexedGraph(const IndexedGraph& other) = delete;
+
+ private:
+  // node pointers in CSR structure.
+  std::vector<Node> nodes_;
+  // mapping from node to index.
+  std::unordered_map<const nngraph::Node*, uint32_t> node2index_;
+  // CSR pointer of node entries
+  std::vector<size_t> entry_rptr_;
+  // space to store input entries of each
+  std::vector<NodeEntry> input_entries_;
+  // control flow dependencies
+  std::vector<uint32_t> control_deps_;
+};
+
+}  // namespace nngraph
+
+#endif  // NNGRAPH_GRAPH_ATTR_TYPES_H_
diff --git a/nnvm/include/nngraph/tuple.h b/nnvm/include/nngraph/tuple.h
index 742b5757c390..f3e541b7a8ba 100644
--- a/nnvm/include/nngraph/tuple.h
+++ b/nnvm/include/nngraph/tuple.h
@@ -60,7 +60,7 @@ class Tuple {
     this->swap(src);
   }
   /*!
-   * \brief construct an Tuple to fill the value with v.
+
    * \param ndim the number of dimension of the Tuple
    * \param v The value to fill.
    */
diff --git a/nnvm/src/test_main.cc b/nnvm/src/test_main.cc
index 8bb1e5252a23..f459c9e2fbd8 100644
--- a/nnvm/src/test_main.cc
+++ b/nnvm/src/test_main.cc
@@ -2,6 +2,7 @@
 #include <nngraph/op.h>
 #include <nngraph/graph.h>
 #include <nngraph/tuple.h>
+#include <nngraph/graph_attr_types.h>
 #include <string>
 
 void test_op() {
@@ -32,6 +33,12 @@ void test_tuple() {
   CHECK((s == TShape{1, 2, 3}));
 }
 
+
+void test_graph() {
+  nngraph::Graph g;
+  g.DFSVisit([](const std::shared_ptr<nngraph::Node>& n){
+    });
+}
 int main() {
   test_tuple();
   return 0;
diff --git a/nnvm/tests/cpp/.gitignore b/nnvm/tests/cpp/.gitignore
index 9da7ef4d4178..de6dea3ebbec 100644
--- a/nnvm/tests/cpp/.gitignore
+++ b/nnvm/tests/cpp/.gitignore
@@ -1,3 +1,3 @@
 unittest
 *.d
-*_test
\ No newline at end of file
+*_test

From a2c2c53055559e6fa2149e8bc6e07d34490e667f Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Tue, 5 Jul 2016 19:41:41 -0700
Subject: [PATCH 327/949] [GRAPH] checkin the constructor of indexed graph

---
 nnvm/include/nngraph/graph_attr_types.h |  6 +++
 nnvm/include/nngraph/node.h             | 11 +++++
 nnvm/src/core/graph_attr_types.cc       | 61 +++++++++++++++++++++++++
 3 files changed, 78 insertions(+)
 create mode 100644 nnvm/src/core/graph_attr_types.cc

diff --git a/nnvm/include/nngraph/graph_attr_types.h b/nnvm/include/nngraph/graph_attr_types.h
index 558bdf94707c..3bb69e609e4b 100644
--- a/nnvm/include/nngraph/graph_attr_types.h
+++ b/nnvm/include/nngraph/graph_attr_types.h
@@ -96,6 +96,10 @@ struct IndexedGraph {
   inline const Node& operator[](const nngraph::Node* node) const {
     return nodes_[node_id(node)];
   }
+  /*! \return list of argument nodes */
+  inline const std::vector<uint32_t>& arg_nodes() const {
+    return arg_nodes_;
+  }
   /*!
    * \brief Constructor an IndexedGraph from normal Graph
    * \param other The source graph.
@@ -107,6 +111,8 @@ struct IndexedGraph {
  private:
   // node pointers in CSR structure.
   std::vector<Node> nodes_;
+  // index to argument nodes
+  std::vector<uint32_t> arg_nodes_;
   // mapping from node to index.
   std::unordered_map<const nngraph::Node*, uint32_t> node2index_;
   // CSR pointer of node entries
diff --git a/nnvm/include/nngraph/node.h b/nnvm/include/nngraph/node.h
index 7a3817bdb282..12f6921adaad 100644
--- a/nnvm/include/nngraph/node.h
+++ b/nnvm/include/nngraph/node.h
@@ -69,6 +69,8 @@ class Node {
    * \return whether node is placeholder input variable
    */
   inline bool is_variable() const;
+  /*! \return number of outputs from this node */
+  inline uint32_t num_outputs() const;
   /*!
    * \brief create a new empty shared_ptr of Node.
    * \return a created empty node.
@@ -81,6 +83,15 @@ inline bool Node::is_variable() const {
   return this->op == nullptr;
 }
 
+inline uint32_t Node::num_outputs() const {
+  if (is_variable()) return 1;
+  if (this->op->num_outputs >= 0) {
+    return static_cast<uint32_t>(this->op->num_outputs);
+  } else {
+    return this->op->get_num_outputs(*this);
+  }
+}
+
 }  // namespace nngraph
 
 #endif  // NNGRAPH_NODE_H_
diff --git a/nnvm/src/core/graph_attr_types.cc b/nnvm/src/core/graph_attr_types.cc
new file mode 100644
index 000000000000..acb165798c79
--- /dev/null
+++ b/nnvm/src/core/graph_attr_types.cc
@@ -0,0 +1,61 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file graph_attr_types.cc
+ * \brief Graph node data structure.
+ */
+#include <nngraph/graph_attr_types.h>
+#include <limits>
+
+namespace nngraph {
+
+// implement constructor from graph
+IndexedGraph::IndexedGraph(const Graph &g) {
+  entry_rptr_.push_back(0);
+  std::vector<size_t> inputs_rptr{0}, control_rptr{0};
+
+  g.DFSVisit([this, &inputs_rptr, &control_rptr]
+             (const std::shared_ptr<nngraph::Node>& n) {
+      CHECK_LT(nodes_.size(), std::numeric_limits<uint32_t>::max());
+      uint32_t nid = static_cast<uint32_t>(nodes_.size());
+      // nodes_
+      IndexedGraph::Node new_node;
+      new_node.source = n.get();
+      nodes_.emplace_back(std::move(new_node));
+      // arg_nodes_
+      if (n->is_variable()) {
+        arg_nodes_.push_back(nid);
+      }
+      // node2index_
+      node2index_[n.get()] = nid;
+      // entry rptr
+      entry_rptr_.push_back(entry_rptr_.back() + n->num_outputs());
+      // input entries
+      for (const auto& e : n->inputs) {
+        auto it = node2index_.find(e.node.get());
+        CHECK(it != node2index_.end() && it->first == e.node.get());
+        input_entries_.emplace_back(NodeEntry{it->second, e.index});
+      }
+      inputs_rptr.push_back(input_entries_.size());
+      // control deps
+      for (const auto& nptr : n->control_deps) {
+        auto it = node2index_.find(nptr.get());
+        CHECK(it != node2index_.end() && it->first == nptr.get());
+        control_deps_.push_back(it->second);
+      }
+      control_rptr.push_back(control_deps_.size());
+  });
+
+  // setup array view
+  const NodeEntry* iptr = dmlc::BeginPtr(input_entries_);
+  for (size_t nid = 0; nid < nodes_.size(); ++nid) {
+    nodes_[nid].inputs = array_view<NodeEntry>(
+        iptr + inputs_rptr[nid], iptr + inputs_rptr[nid + 1]);
+  }
+  const uint32_t* cptr = dmlc::BeginPtr(control_deps_);
+  for (size_t nid = 0; nid < nodes_.size(); ++nid) {
+    nodes_[nid].control_deps = array_view<uint32_t>(
+        cptr + control_rptr[nid], cptr + control_rptr[nid + 1]);
+  }
+}
+
+}  // namespace nngraph

From bc3f48e23b826d11cb28cdf02bcde755547cb79b Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Tue, 5 Jul 2016 21:28:28 -0700
Subject: [PATCH 328/949] check in pass

---
 nnvm/include/nngraph/graph.h            |  10 ++-
 nnvm/include/nngraph/graph_attr_types.h |  11 ++-
 nnvm/include/nngraph/node.h             |   1 +
 nnvm/include/nngraph/pass.h             | 113 ++++++++++++++++++++++++
 nnvm/src/core/graph_attr_types.cc       |   1 +
 nnvm/src/core/pass.cc                   |  57 ++++++++++++
 nnvm/src/test_main.cc                   |   2 +-
 7 files changed, 185 insertions(+), 10 deletions(-)
 create mode 100644 nnvm/include/nngraph/pass.h
 create mode 100644 nnvm/src/core/pass.cc

diff --git a/nnvm/include/nngraph/graph.h b/nnvm/include/nngraph/graph.h
index 91522279cadd..2650ca2ee0f6 100644
--- a/nnvm/include/nngraph/graph.h
+++ b/nnvm/include/nngraph/graph.h
@@ -12,6 +12,7 @@
 #include <algorithm>
 #include <unordered_map>
 #include <unordered_set>
+#include "./base.h"
 #include "./node.h"
 
 namespace nngraph {
@@ -23,9 +24,12 @@ class Graph {
  public:
   /*! \brief outputs of the computation graph. */
   std::vector<NodeEntry> outputs;
-  /*! \brief attributes of a graph */
-  std::unordered_map<std::string, any> attrs;
-
+  /*!
+   * \brief attributes of a graph
+   *  Each attribute is immutable,
+   *  and can be shared across multiple Instance of graph
+   */
+  std::unordered_map<std::string, std::shared_ptr<const any> > attrs;
   /*!
    * \brief perform a Post Order DFS visit to each node in the graph.
    *  This order is deterministic and is also topoligical sorted.
diff --git a/nnvm/include/nngraph/graph_attr_types.h b/nnvm/include/nngraph/graph_attr_types.h
index 3bb69e609e4b..f856ecf9c1f2 100644
--- a/nnvm/include/nngraph/graph_attr_types.h
+++ b/nnvm/include/nngraph/graph_attr_types.h
@@ -13,12 +13,11 @@
 namespace nngraph {
 
 /*!
- * \brief Index to the graph.
- *  Maps pointers to Node to consecutive integers.
- *
- *  This is an Auxililary data structure that can be used
- *  to iterate over the graph in a more efficient manner.
- *  It also allows storing
+ * \brief Auxililary data structure to index a graph.
+ *  It maps Nodes in the graph to consecutive integers node_id.
+ *  It also maps IndexedGraph::NodeEntry to consecutive integer entry_id.
+ *  This allows storing properties of Node and NodeEntry into
+ *  compact vector and quickly access them without resorting to hashmap.
  */
 struct IndexedGraph {
  public:
diff --git a/nnvm/include/nngraph/node.h b/nnvm/include/nngraph/node.h
index 12f6921adaad..2caeb2f5a293 100644
--- a/nnvm/include/nngraph/node.h
+++ b/nnvm/include/nngraph/node.h
@@ -10,6 +10,7 @@
 #include <string>
 #include <vector>
 #include <unordered_map>
+#include "./base.h"
 #include "./op.h"
 
 namespace nngraph {
diff --git a/nnvm/include/nngraph/pass.h b/nnvm/include/nngraph/pass.h
new file mode 100644
index 000000000000..48a16f7d9320
--- /dev/null
+++ b/nnvm/include/nngraph/pass.h
@@ -0,0 +1,113 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file pass.h
+ * \brief Pass that can be applied to a graph.
+ */
+#ifndef NNGRAPH_PASS_H_
+#define NNGRAPH_PASS_H_
+
+#include <vector>
+#include <functional>
+#include "./base.h"
+#include "./graph.h"
+
+namespace nngraph {
+
+/*!
+ * \brief A PassFunction is a basic "Operator on Graph"
+ *  It takes a source graph
+ *
+ *  A pass function can either change the graph structure of g,
+ *  generating a new Graph, or add new attributes to the graph.
+ *
+ * \param src The graph to be transformed.
+ * \return The generated graph.
+ */
+typedef std::function<Graph (const Graph& src)> PassFunction;
+
+/*!
+ * \brief Apply a series of pass transformations on g.
+ * \param src The graph to be transformed.
+ * \param pass The name of pass to be applied.
+ * \return The transformed graph
+ */
+Graph ApplyPass(const Graph& src,
+                const std::vector<std::string>& pass);
+
+/*!
+ * \brief Registry entry for DataIterator factory functions.
+ */
+struct PassFunctionReg
+    : public dmlc::FunctionRegEntryBase<PassFunctionReg,
+                                        PassFunction> {
+  /*!
+   * \brief Whether the pass will change graph structure
+   *  If this is false, the pass will only change attributes.
+   */
+  bool change_graph{false};
+  /*! \brief dependencies on operator attributes */
+  std::vector<std::string> op_attr_dependency;
+  /*! \brief dependencies on attributes in the graph */
+  std::vector<std::string> graph_attr_dependency;
+  /*! \brief generated targets of graph attributes */
+  std::vector<std::string> graph_attr_targets;
+  /*!
+   * \brief set whether this pass will change graph structure.
+   * \param v the value to set
+   * \return reference to self.
+   */
+  PassFunctionReg& set_change_graph(bool v) {  // NOLINT(*)
+    change_graph = v;
+    return *this;
+  }
+  /*!
+   * \brief Declare this pass require operator attribute attr_name to be available.
+   * \param attr_name Name of the attribute.
+   * \return reference to self.
+   */
+  PassFunctionReg& provide_graph_attr(const std::string& attr_name) {  // NOLINT(*)
+    graph_attr_targets.push_back(attr_name);
+    return *this;
+  }
+  /*!
+   * \brief declare this pass require operator attribute attr_name to be available.
+   * \param attr_name Name of the attribute.
+   * \return reference to self.
+   */
+  PassFunctionReg& depend_op_attr(const std::string& attr_name) {  // NOLINT(*)
+    op_attr_dependency.push_back(attr_name);
+    return *this;
+  }
+  /*!
+   * \brief declare this pass require graph attribute attr_name to be available.
+   * \param attr_name Name of the attribute.
+   * \return reference to self.
+   */
+  PassFunctionReg& depend_graph_attr(const std::string& attr_name) {  // NOLINT(*)
+    graph_attr_dependency.push_back(attr_name);
+    return *this;
+  }
+};
+
+/*!
+ * \def NNGRAPH_REGISTER_PASS
+ * \brief Macro to register pass fuctions.
+ *
+ * \code
+ * // example of registering a shape inference pass
+ * NNGRAPH_REGISTER_PASS(InferShape)
+ * .describe("Shape Inference function, generate graph attributes")
+ * .provide_graph_attr("data_shape")
+ * .depend_graph_attr("indexed_graph")
+ * .depend_op_attr("infer_shape")
+ * .set_body([](const Graph& g) {
+ *     // shape inference logic
+ *   });
+ * \endcode
+ */
+#define NNGRAPH_REGISTER_PASS(name)                                     \
+  DMLC_REGISTRY_REGISTER(::nngraph::PassFunctionReg, PassFunctionReg, name)
+
+}  // namespace nngraph
+
+#endif  // NNGRAPH_PASS_H_
diff --git a/nnvm/src/core/graph_attr_types.cc b/nnvm/src/core/graph_attr_types.cc
index acb165798c79..ee01ed5c74cd 100644
--- a/nnvm/src/core/graph_attr_types.cc
+++ b/nnvm/src/core/graph_attr_types.cc
@@ -46,6 +46,7 @@ IndexedGraph::IndexedGraph(const Graph &g) {
   });
 
   // setup array view
+  // input_entries_ and control_rptr must not change after this step.
   const NodeEntry* iptr = dmlc::BeginPtr(input_entries_);
   for (size_t nid = 0; nid < nodes_.size(); ++nid) {
     nodes_[nid].inputs = array_view<NodeEntry>(
diff --git a/nnvm/src/core/pass.cc b/nnvm/src/core/pass.cc
new file mode 100644
index 000000000000..06a5dbcdf864
--- /dev/null
+++ b/nnvm/src/core/pass.cc
@@ -0,0 +1,57 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file pass.cc
+ * \brief Support for pass registry.
+ */
+#include <nngraph/pass.h>
+#include <algorithm>
+
+namespace dmlc {
+// enable registry
+DMLC_REGISTRY_ENABLE(nngraph::PassFunctionReg);
+}  // namespace dmlc
+
+namespace nngraph {
+
+const PassFunctionReg* FindPassDep(const std::string&attr_name) {
+  for (auto* r : dmlc::Registry<PassFunctionReg>::List()) {
+    for (auto& s : r->graph_attr_targets) {
+      if (s == attr_name) return r;
+    }
+  }
+  return nullptr;
+}
+
+Graph ApplyPass(const Graph& src,
+                const std::vector<std::string>& pass) {
+  std::vector<const PassFunctionReg*> fpass;
+  for (auto& name : pass) {
+    auto* reg = dmlc::Registry<PassFunctionReg>::Find(name);
+    CHECK(reg != nullptr)
+        << "Cannot find pass " << name << " in the registry";
+    fpass.push_back(reg);
+  }
+
+  Graph g;
+  const Graph* s = &src;
+  for (auto r : fpass) {
+    for (auto& dep : r->graph_attr_dependency) {
+      if (s->attrs.count(dep) == 0) {
+        auto* pass_dep = FindPassDep(dep);
+        std::string msg;
+        if (pass_dep != nullptr) {
+          msg = " The attribute is provided by pass " + pass_dep->name;
+        }
+        LOG(FATAL) << "Graph attr dependency " << dep
+                   << " is required by pass " << r->name
+                   << " but is not available "
+                   << msg;
+      }
+    }
+    g = r->body(*s);
+    s = &g;
+  }
+  return g;
+}
+
+}  // namespace nngraph
diff --git a/nnvm/src/test_main.cc b/nnvm/src/test_main.cc
index f459c9e2fbd8..377519f9f386 100644
--- a/nnvm/src/test_main.cc
+++ b/nnvm/src/test_main.cc
@@ -36,7 +36,7 @@ void test_tuple() {
 
 void test_graph() {
   nngraph::Graph g;
-  g.DFSVisit([](const std::shared_ptr<nngraph::Node>& n){
+  g.DFSVisit([](const std::shared_ptr<const nngraph::Node>& n){
     });
 }
 int main() {

From f4fdecb64bdac4bb8b0478405298a82a3ac89a83 Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Thu, 7 Jul 2016 11:17:59 -0700
Subject: [PATCH 329/949] change project to NNVM

---
 nnvm/LICENSE                                  | 208 +-----------------
 nnvm/README.md                                |  43 +++-
 nnvm/include/{nngraph => nnvm}/base.h         |  12 +-
 nnvm/include/{nngraph => nnvm}/graph.h        |  12 +-
 .../{nngraph => nnvm}/graph_attr_types.h      |  20 +-
 nnvm/include/{nngraph => nnvm}/node.h         |  12 +-
 nnvm/include/{nngraph => nnvm}/op.h           |  40 ++--
 nnvm/include/{nngraph => nnvm}/pass.h         |  18 +-
 nnvm/include/{nngraph => nnvm}/tuple.h        |  10 +-
 nnvm/src/core/graph_attr_types.cc             |   8 +-
 nnvm/src/core/node.cc                         |   6 +-
 nnvm/src/core/op.cc                           |  10 +-
 nnvm/src/core/pass.cc                         |   8 +-
 nnvm/src/example/operator.cc                  |   8 +-
 nnvm/src/test_main.cc                         |  20 +-
 15 files changed, 139 insertions(+), 296 deletions(-)
 rename nnvm/include/{nngraph => nnvm}/base.h (83%)
 rename nnvm/include/{nngraph => nnvm}/graph.h (94%)
 rename nnvm/include/{nngraph => nnvm}/graph_attr_types.h (88%)
 rename nnvm/include/{nngraph => nnvm}/node.h (94%)
 rename nnvm/include/{nngraph => nnvm}/op.h (91%)
 rename nnvm/include/{nngraph => nnvm}/pass.h (90%)
 rename nnvm/include/{nngraph => nnvm}/tuple.h (98%)

diff --git a/nnvm/LICENSE b/nnvm/LICENSE
index 8dada3edaf50..3908a9e0dbed 100644
--- a/nnvm/LICENSE
+++ b/nnvm/LICENSE
@@ -1,201 +1,13 @@
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
+Copyright (c) 2016 by Contributors
 
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+Licensed 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
 
-   1. Definitions.
+   http://www.apache.org/licenses/LICENSE-2.0
 
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "{}"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright {yyyy} {name of copyright owner}
-
-   Licensed 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.
+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.
diff --git a/nnvm/README.md b/nnvm/README.md
index 75e7a81c5f6e..a20f40d95fb0 100644
--- a/nnvm/README.md
+++ b/nnvm/README.md
@@ -1,9 +1,40 @@
-# mxnngraph
-Prototype of graph optimizer and construction API for next generation core engine in MXNet and beyond
+# NNVM: Build deep learning system by parts
 
-## Goal
-- Construct graph easily
-- Pluggable inference, optimization algorithms
-- Can be used as input graph execution in various settings, specifically be able to support mxnet's symbolic execution API
+NNVM is not a deep learning library. It is a modular, lightweight library to
+help build deep learning libraries efficiently.
 
+## What is it
 
+While most deep learning systems offer end to end solutions,
+it is interesting to ask if we can actually assemble a deep learning system by parts.
+The goal is to enable hackers can customize optimizations, target platforms and set of operators they care about.
+We believe that the modular system is an interesting direction.
+The hope is that effective parts can be assembled together just like you assemble your own desktops.
+So the customized deep learning solution can be minimax, minimum in terms of dependencies,
+while maxiziming the users' need.
+
+NNVM offers one such part, it provides a generic to do generic
+computation graph optimization such as memory reduction, device allocation,
+operator fusion while being agnostic to the operator
+interface defintion and how operators are executed.
+NNVM is inspired by LLVM, aiming to be an intermediate representation library
+for neural nets and computation graphs in general.
+
+## Deep learning system by parts
+
+This is one way to divide the deep learning system into common parts.
+Each can be isolated to a modular part.
+
+- Computation graph definition, manipulation.
+- Computation graph intermediate optimization.
+- Computation graph execution.
+- Operator kernel libraries.
+- Imperative task scheduling and parallel task coordination.
+
+We hope that there will be more modular parts in the future,
+so system building can be fun and rewarding.
+
+## Links
+
+[MXNet](https://github.com/dmlc/mxnet) will be using NNVM as its intermediate
+representation layer for symbolic graphs.
diff --git a/nnvm/include/nngraph/base.h b/nnvm/include/nnvm/base.h
similarity index 83%
rename from nnvm/include/nngraph/base.h
rename to nnvm/include/nnvm/base.h
index 155f688165c4..92175cf17a8f 100644
--- a/nnvm/include/nngraph/base.h
+++ b/nnvm/include/nnvm/base.h
@@ -1,10 +1,10 @@
 /*!
  *  Copyright (c) 2016 by Contributors
  * \file base.h
- * \brief Configuation of nngraph as well as basic data structure.
+ * \brief Configuation of nnvm as well as basic data structure.
  */
-#ifndef NNGRAPH_BASE_H_
-#define NNGRAPH_BASE_H_
+#ifndef NNVM_BASE_H_
+#define NNVM_BASE_H_
 
 #include <dmlc/base.h>
 #include <dmlc/any.h>
@@ -12,7 +12,7 @@
 #include <dmlc/registry.h>
 #include <dmlc/array_view.h>
 
-namespace nngraph {
+namespace nnvm {
 
 /*! \brief any type */
 using any = dmlc::any;
@@ -47,6 +47,6 @@ inline const T& get(const any& src) {
   return dmlc::get<T>(src);
 }
 
-}  // namespace nngraph
+}  // namespace nnvm
 
-#endif  // NNGRAPH_BASE_H_
+#endif  // NNVM_BASE_H_
diff --git a/nnvm/include/nngraph/graph.h b/nnvm/include/nnvm/graph.h
similarity index 94%
rename from nnvm/include/nngraph/graph.h
rename to nnvm/include/nnvm/graph.h
index 2650ca2ee0f6..296f62bf839d 100644
--- a/nnvm/include/nngraph/graph.h
+++ b/nnvm/include/nnvm/graph.h
@@ -1,10 +1,10 @@
 /*!
  *  Copyright (c) 2016 by Contributors
  * \file graph.h
- * \brief Configuation of nngraph as well as basic data structure.
+ * \brief Configuation of nnvm as well as basic data structure.
  */
-#ifndef NNGRAPH_GRAPH_H_
-#define NNGRAPH_GRAPH_H_
+#ifndef NNVM_GRAPH_H_
+#define NNVM_GRAPH_H_
 
 #include <vector>
 #include <string>
@@ -15,7 +15,7 @@
 #include "./base.h"
 #include "./node.h"
 
-namespace nngraph {
+namespace nnvm {
 
 /*!
  * \brief Symbolic computation graph.
@@ -98,6 +98,6 @@ inline void Graph::DFSVisit(FVisit fvisit) const {
       });
 }
 
-}  // namespace nngraph
+}  // namespace nnvm
 
-#endif  // NNGRAPH_GRAPH_H_
+#endif  // NNVM_GRAPH_H_
diff --git a/nnvm/include/nngraph/graph_attr_types.h b/nnvm/include/nnvm/graph_attr_types.h
similarity index 88%
rename from nnvm/include/nngraph/graph_attr_types.h
rename to nnvm/include/nnvm/graph_attr_types.h
index f856ecf9c1f2..ab14185724f1 100644
--- a/nnvm/include/nngraph/graph_attr_types.h
+++ b/nnvm/include/nnvm/graph_attr_types.h
@@ -3,14 +3,14 @@
  * \file graph_attr_types.h
  * \brief Data structures that can appear in graph attributes.
  */
-#ifndef NNGRAPH_GRAPH_ATTR_TYPES_H_
-#define NNGRAPH_GRAPH_ATTR_TYPES_H_
+#ifndef NNVM_GRAPH_ATTR_TYPES_H_
+#define NNVM_GRAPH_ATTR_TYPES_H_
 
 #include <vector>
 #include <unordered_map>
 #include "./graph.h"
 
-namespace nngraph {
+namespace nnvm {
 
 /*!
  * \brief Auxililary data structure to index a graph.
@@ -39,7 +39,7 @@ struct IndexedGraph {
   /*! \brief Node data structure in IndexedGraph */
   struct Node {
     /*! \brief pointer to the source node */
-    const nngraph::Node* source;
+    const nnvm::Node* source;
     /*! \brief inputs to the node */
     array_view<NodeEntry> inputs;
     /*! \brief control flow dependencies to the node */
@@ -68,7 +68,7 @@ struct IndexedGraph {
    * \param e The entry to query for index.
    * \return the unique index.
    */
-  inline uint32_t entry_id(const nngraph::NodeEntry& e) const {
+  inline uint32_t entry_id(const nnvm::NodeEntry& e) const {
     return entry_rptr_[node_id(e.node.get())] + e.index;
   }
   /*!
@@ -76,7 +76,7 @@ struct IndexedGraph {
    * \param node The Node to query for index.
    * \return the node index.
    */
-  inline uint32_t node_id(const nngraph::Node* node) const {
+  inline uint32_t node_id(const nnvm::Node* node) const {
     return node2index_.at(node);
   }
   /*!
@@ -92,7 +92,7 @@ struct IndexedGraph {
    * \param node The pointer to the Node structure
    * \return const reference to the corresponding IndexedGraph::Node
    */
-  inline const Node& operator[](const nngraph::Node* node) const {
+  inline const Node& operator[](const nnvm::Node* node) const {
     return nodes_[node_id(node)];
   }
   /*! \return list of argument nodes */
@@ -113,7 +113,7 @@ struct IndexedGraph {
   // index to argument nodes
   std::vector<uint32_t> arg_nodes_;
   // mapping from node to index.
-  std::unordered_map<const nngraph::Node*, uint32_t> node2index_;
+  std::unordered_map<const nnvm::Node*, uint32_t> node2index_;
   // CSR pointer of node entries
   std::vector<size_t> entry_rptr_;
   // space to store input entries of each
@@ -122,6 +122,6 @@ struct IndexedGraph {
   std::vector<uint32_t> control_deps_;
 };
 
-}  // namespace nngraph
+}  // namespace nnvm
 
-#endif  // NNGRAPH_GRAPH_ATTR_TYPES_H_
+#endif  // NNVM_GRAPH_ATTR_TYPES_H_
diff --git a/nnvm/include/nngraph/node.h b/nnvm/include/nnvm/node.h
similarity index 94%
rename from nnvm/include/nngraph/node.h
rename to nnvm/include/nnvm/node.h
index 2caeb2f5a293..d6eb5817abc7 100644
--- a/nnvm/include/nngraph/node.h
+++ b/nnvm/include/nnvm/node.h
@@ -1,10 +1,10 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file base.h
+ * \file node.h
  * \brief Graph node data structure.
  */
-#ifndef NNGRAPH_NODE_H_
-#define NNGRAPH_NODE_H_
+#ifndef NNVM_NODE_H_
+#define NNVM_NODE_H_
 
 #include <memory>
 #include <string>
@@ -13,7 +13,7 @@
 #include "./base.h"
 #include "./op.h"
 
-namespace nngraph {
+namespace nnvm {
 
 // Forward declare node.
 class Node;
@@ -93,6 +93,6 @@ inline uint32_t Node::num_outputs() const {
   }
 }
 
-}  // namespace nngraph
+}  // namespace nnvm
 
-#endif  // NNGRAPH_NODE_H_
+#endif  // NNVM_NODE_H_
diff --git a/nnvm/include/nngraph/op.h b/nnvm/include/nnvm/op.h
similarity index 91%
rename from nnvm/include/nngraph/op.h
rename to nnvm/include/nnvm/op.h
index 1f768d95e9a0..a3467c5a6bcf 100644
--- a/nnvm/include/nngraph/op.h
+++ b/nnvm/include/nnvm/op.h
@@ -3,8 +3,8 @@
  * \file op.h
  * \brief Operator information structor.
  */
-#ifndef NNGRAPH_OP_H_
-#define NNGRAPH_OP_H_
+#ifndef NNVM_OP_H_
+#define NNVM_OP_H_
 
 #include <string>
 #include <vector>
@@ -13,7 +13,7 @@
 #include <functional>
 #include "./base.h"
 
-namespace nngraph {
+namespace nnvm {
 
 // forward declarations
 class Node;
@@ -38,18 +38,18 @@ static const int kVarg = -1;
  *  // registeration of oeprators
  *  // NOTE that the attr function can register any
  *  // additional attributes to the operator
- *  NNGRAPH_REGISTER_OP(add)
+ *  NNVM_REGISTER_OP(add)
  *  .describe("add two inputs together")
  *  .set_num_inputs(2)
  *  .attr<OpKernel>("gpu_kernel", AddKernel);
  *
- *  NNGRAPH_REGISTER_OP(sub)
+ *  NNVM_REGISTER_OP(sub)
  *  .describe("substract one tensor from another")
  *  .set_num_inputs(2);
  *
  *  // Can call regster multiple times in different files
  *  // to register different part of information
- *  NNGRAPH_REGISTER_OP(sub)
+ *  NNVM_REGISTER_OP(sub)
  *  .attr<OpKernel>("gpu_kernel", SubKernel);
  *
  *  // get operators from registry.
@@ -124,7 +124,7 @@ class Op {
    *                       const std::vector<TShape>& ishapes) {
    *     // we can use the parsed version of param
    *     // without repeatively parsing the parameter
-   *     const SumParam& param = nngraph::get<SumParam>(attrs.parsed);
+   *     const SumParam& param = nnvm::get<SumParam>(attrs.parsed);
    *  }
    * \endcode
    */
@@ -234,36 +234,36 @@ class OpMap {
 };
 
 // internal macros to make
-#define NNGRAPH_STR_CONCAT_(__x, __y) __x##__y
-#define NNGRAPH_STR_CONCAT(__x, __y) NNGRAPH_STR_CONCAT_(__x, __y)
-#define NNGRAPH_REGISTER_VAR_DEF(OpName)                              \
-  static ::nngraph::Op & __make_ ## NNGraphOp ## _ ## OpName
+#define NNVM_STR_CONCAT_(__x, __y) __x##__y
+#define NNVM_STR_CONCAT(__x, __y) NNVM_STR_CONCAT_(__x, __y)
+#define NNVM_REGISTER_VAR_DEF(OpName)                              \
+  static ::nnvm::Op & __make_ ## NnvmOp ## _ ## OpName
 
 /*!
- * \def NNGRAPH_REGISTER_OP
+ * \def NNVM_REGISTER_OP
  * \brief Register
  * This macro must be used under namespace dmlc, and only used once in cc file.
  * \param OpName The name of registry
  *
  * \code
  *
- *  NNGRAPH_REGISTER_OP(add)
+ *  NNVM_REGISTER_OP(add)
  *  .describe("add two inputs together")
  *  .set_num_inputs(2)
  *  .attr<OpKernel>("gpu_kernel", AddKernel);
  *
  * \endcode
  */
-#define NNGRAPH_REGISTER_OP(OpName)                                     \
-  NNGRAPH_STR_CONCAT(NNGRAPH_REGISTER_VAR_DEF(OpName), __COUNTER__) =   \
-      ::dmlc::Registry<::nngraph::Op>::Get()->__REGISTER_OR_GET__(#OpName)
+#define NNVM_REGISTER_OP(OpName)                                     \
+  NNVM_STR_CONCAT(NNVM_REGISTER_VAR_DEF(OpName), __COUNTER__) =   \
+      ::dmlc::Registry<::nnvm::Op>::Get()->__REGISTER_OR_GET__(#OpName)
 
 // implementations of template functions after this.
 // member function of Op
 template<typename ValueType>
 inline const OpMap<ValueType>& Op::GetAttr(const std::string& key) {
   const any& ref = GetAttrMap(key);
-  return nngraph::get<OpMap<ValueType> >(ref);
+  return nnvm::get<OpMap<ValueType> >(ref);
 }
 
 template<typename ValueType>
@@ -282,7 +282,7 @@ inline Op& Op::attr(  // NOLINT(*)
           << " previously " << pmap->type().name()
           << " current " << typeid(OpMap<ValueType>).name();
       std::vector<std::pair<ValueType, int> >& vec =
-          nngraph::get<OpMap<ValueType> >(*pmap).data_;
+          nnvm::get<OpMap<ValueType> >(*pmap).data_;
       // resize the value type.
       vec.resize(index_ + 1,
                  std::make_pair(ValueType(), 0));
@@ -338,6 +338,6 @@ inline const ValueType& OpMap<ValueType>::operator[](const Op* op) const {
   return data_[idx].first;
 }
 
-}  // namespace nngraph
+}  // namespace nnvm
 
-#endif  // NNGRAPH_OP_H_
+#endif  // NNVM_OP_H_
diff --git a/nnvm/include/nngraph/pass.h b/nnvm/include/nnvm/pass.h
similarity index 90%
rename from nnvm/include/nngraph/pass.h
rename to nnvm/include/nnvm/pass.h
index 48a16f7d9320..c1936941b331 100644
--- a/nnvm/include/nngraph/pass.h
+++ b/nnvm/include/nnvm/pass.h
@@ -3,15 +3,15 @@
  * \file pass.h
  * \brief Pass that can be applied to a graph.
  */
-#ifndef NNGRAPH_PASS_H_
-#define NNGRAPH_PASS_H_
+#ifndef NNVM_PASS_H_
+#define NNVM_PASS_H_
 
 #include <vector>
 #include <functional>
 #include "./base.h"
 #include "./graph.h"
 
-namespace nngraph {
+namespace nnvm {
 
 /*!
  * \brief A PassFunction is a basic "Operator on Graph"
@@ -90,12 +90,12 @@ struct PassFunctionReg
 };
 
 /*!
- * \def NNGRAPH_REGISTER_PASS
+ * \def NNVM_REGISTER_PASS
  * \brief Macro to register pass fuctions.
  *
  * \code
  * // example of registering a shape inference pass
- * NNGRAPH_REGISTER_PASS(InferShape)
+ * NNVM_REGISTER_PASS(InferShape)
  * .describe("Shape Inference function, generate graph attributes")
  * .provide_graph_attr("data_shape")
  * .depend_graph_attr("indexed_graph")
@@ -105,9 +105,9 @@ struct PassFunctionReg
  *   });
  * \endcode
  */
-#define NNGRAPH_REGISTER_PASS(name)                                     \
-  DMLC_REGISTRY_REGISTER(::nngraph::PassFunctionReg, PassFunctionReg, name)
+#define NNVM_REGISTER_PASS(name)                                     \
+  DMLC_REGISTRY_REGISTER(::nnvm::PassFunctionReg, PassFunctionReg, name)
 
-}  // namespace nngraph
+}  // namespace nnvm
 
-#endif  // NNGRAPH_PASS_H_
+#endif  // NNVM_PASS_H_
diff --git a/nnvm/include/nngraph/tuple.h b/nnvm/include/nnvm/tuple.h
similarity index 98%
rename from nnvm/include/nngraph/tuple.h
rename to nnvm/include/nnvm/tuple.h
index f3e541b7a8ba..8898fc054ca7 100644
--- a/nnvm/include/nngraph/tuple.h
+++ b/nnvm/include/nnvm/tuple.h
@@ -3,15 +3,15 @@
  * \file tuple.h
  * \brief Data structure Tuple and TShape to store dynamic sized shapes.
  */
-#ifndef NNGRAPH_TUPLE_H_
-#define NNGRAPH_TUPLE_H_
+#ifndef NNVM_TUPLE_H_
+#define NNVM_TUPLE_H_
 
 #include <vector>
 #include <type_traits>
 #include <algorithm>
 #include <iostream>
 
-namespace nngraph {
+namespace nnvm {
 
 /*! \brief data type to store array index */
 typedef uint32_t index_t;
@@ -329,6 +329,6 @@ class TShape : public Tuple<index_t> {
   }
 };
 
-}  // namespace nngraph
+}  // namespace nnvm
 
-#endif  // NNGRAPH_TUPLE_H_
+#endif  // NNVM_TUPLE_H_
diff --git a/nnvm/src/core/graph_attr_types.cc b/nnvm/src/core/graph_attr_types.cc
index ee01ed5c74cd..43351f544835 100644
--- a/nnvm/src/core/graph_attr_types.cc
+++ b/nnvm/src/core/graph_attr_types.cc
@@ -3,10 +3,10 @@
  * \file graph_attr_types.cc
  * \brief Graph node data structure.
  */
-#include <nngraph/graph_attr_types.h>
+#include <nnvm/graph_attr_types.h>
 #include <limits>
 
-namespace nngraph {
+namespace nnvm {
 
 // implement constructor from graph
 IndexedGraph::IndexedGraph(const Graph &g) {
@@ -14,7 +14,7 @@ IndexedGraph::IndexedGraph(const Graph &g) {
   std::vector<size_t> inputs_rptr{0}, control_rptr{0};
 
   g.DFSVisit([this, &inputs_rptr, &control_rptr]
-             (const std::shared_ptr<nngraph::Node>& n) {
+             (const std::shared_ptr<nnvm::Node>& n) {
       CHECK_LT(nodes_.size(), std::numeric_limits<uint32_t>::max());
       uint32_t nid = static_cast<uint32_t>(nodes_.size());
       // nodes_
@@ -59,4 +59,4 @@ IndexedGraph::IndexedGraph(const Graph &g) {
   }
 }
 
-}  // namespace nngraph
+}  // namespace nnvm
diff --git a/nnvm/src/core/node.cc b/nnvm/src/core/node.cc
index 5051c1eb3259..72cea0479ddf 100644
--- a/nnvm/src/core/node.cc
+++ b/nnvm/src/core/node.cc
@@ -3,9 +3,9 @@
  * \file node.cc
  * \brief Graph node data structure.
  */
-#include <nngraph/node.h>
+#include <nnvm/node.h>
 
-namespace nngraph {
+namespace nnvm {
 
 Node::~Node() {
   if (inputs.size() != 0) {
@@ -42,4 +42,4 @@ std::shared_ptr<Node> Node::Create() {
   return std::make_shared<Node>();
 }
 
-}  // namespace nngraph
+}  // namespace nnvm
diff --git a/nnvm/src/core/op.cc b/nnvm/src/core/op.cc
index 9705f57b1034..57514aa7fbcb 100644
--- a/nnvm/src/core/op.cc
+++ b/nnvm/src/core/op.cc
@@ -3,18 +3,18 @@
  * \file op.cc
  * \brief Support for operator registry.
  */
-#include <nngraph/base.h>
-#include <nngraph/op.h>
+#include <nnvm/base.h>
+#include <nnvm/op.h>
 
 #include <atomic>
 #include <mutex>
 
 namespace dmlc {
 // enable registry
-DMLC_REGISTRY_ENABLE(nngraph::Op);
+DMLC_REGISTRY_ENABLE(nnvm::Op);
 }  // namespace dmlc
 
-namespace nngraph {
+namespace nnvm {
 
 // single manager of operator information.
 struct OpManager {
@@ -66,4 +66,4 @@ void Op::UpdateAttrMap(const std::string& key,
   updater(&value);
 }
 
-}  // namespace nngraph
+}  // namespace nnvm
diff --git a/nnvm/src/core/pass.cc b/nnvm/src/core/pass.cc
index 06a5dbcdf864..f94acf50c8d3 100644
--- a/nnvm/src/core/pass.cc
+++ b/nnvm/src/core/pass.cc
@@ -3,15 +3,15 @@
  * \file pass.cc
  * \brief Support for pass registry.
  */
-#include <nngraph/pass.h>
+#include <nnvm/pass.h>
 #include <algorithm>
 
 namespace dmlc {
 // enable registry
-DMLC_REGISTRY_ENABLE(nngraph::PassFunctionReg);
+DMLC_REGISTRY_ENABLE(nnvm::PassFunctionReg);
 }  // namespace dmlc
 
-namespace nngraph {
+namespace nnvm {
 
 const PassFunctionReg* FindPassDep(const std::string&attr_name) {
   for (auto* r : dmlc::Registry<PassFunctionReg>::List()) {
@@ -54,4 +54,4 @@ Graph ApplyPass(const Graph& src,
   return g;
 }
 
-}  // namespace nngraph
+}  // namespace nnvm
diff --git a/nnvm/src/example/operator.cc b/nnvm/src/example/operator.cc
index a33307e59c80..8152b117c2cb 100644
--- a/nnvm/src/example/operator.cc
+++ b/nnvm/src/example/operator.cc
@@ -1,14 +1,14 @@
 // Copyright (c) 2016 by Contributors
-// This is an example on how we can register operator information to NNGRAPH
+// This is an example on how we can register operator information to NNVM
 
-#include <nngraph/op.h>
+#include <nnvm/op.h>
 #include <utility>
 
-NNGRAPH_REGISTER_OP(add)
+NNVM_REGISTER_OP(add)
 .describe("add two data together")
 .set_num_inputs(2)
 .attr("inplace_pair", std::make_pair(0, 0));
 
 
-NNGRAPH_REGISTER_OP(add)
+NNVM_REGISTER_OP(add)
 .attr<std::string>("nick_name", "plus");
diff --git a/nnvm/src/test_main.cc b/nnvm/src/test_main.cc
index 377519f9f386..6a6946171b38 100644
--- a/nnvm/src/test_main.cc
+++ b/nnvm/src/test_main.cc
@@ -1,20 +1,20 @@
 // Copyright (c) 2016 by Contributors
-#include <nngraph/op.h>
-#include <nngraph/graph.h>
-#include <nngraph/tuple.h>
-#include <nngraph/graph_attr_types.h>
+#include <nnvm/op.h>
+#include <nnvm/graph.h>
+#include <nnvm/tuple.h>
+#include <nnvm/graph_attr_types.h>
 #include <string>
 
 void test_op() {
-  using namespace nngraph;
+  using namespace nnvm;
   auto add = Op::Get("add");
   auto nick = Op::GetAttr<std::string>("nick_name");
   LOG(INFO) << "nick=" << nick[add];
 }
 
 void test_tuple() {
-  using nngraph::Tuple;
-  using nngraph::TShape;
+  using nnvm::Tuple;
+  using nnvm::TShape;
   Tuple<int> x{1, 2, 3};
   Tuple<int> y{1, 2, 3, 5, 6};
   x = std::move(y);
@@ -27,7 +27,7 @@ void test_tuple() {
   std::istringstream is(os.str());
   is >> y;
   CHECK_EQ(x, y);
-  Tuple<nngraph::index_t> ss{1, 2, 3};
+  Tuple<nnvm::index_t> ss{1, 2, 3};
   TShape s = ss;
   s = std::move(ss);
   CHECK((s == TShape{1, 2, 3}));
@@ -35,8 +35,8 @@ void test_tuple() {
 
 
 void test_graph() {
-  nngraph::Graph g;
-  g.DFSVisit([](const std::shared_ptr<const nngraph::Node>& n){
+  nnvm::Graph g;
+  g.DFSVisit([](const std::shared_ptr<const nnvm::Node>& n){
     });
 }
 int main() {

From 3c1ac2a40e1d145b306968aebfd45b47c925a005 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 8 Jul 2016 14:26:14 -0700
Subject: [PATCH 330/949] [PASS] Add save/load json (#1)

---
 nnvm/include/nnvm/node.h       |   6 +-
 nnvm/src/pass/saveload_json.cc | 202 +++++++++++++++++++++++++++++++++
 2 files changed, 205 insertions(+), 3 deletions(-)
 create mode 100644 nnvm/src/pass/saveload_json.cc

diff --git a/nnvm/include/nnvm/node.h b/nnvm/include/nnvm/node.h
index d6eb5817abc7..51e7a6049001 100644
--- a/nnvm/include/nnvm/node.h
+++ b/nnvm/include/nnvm/node.h
@@ -31,6 +31,8 @@ struct NodeEntry {
  *  Usually are additional parameters like axis,
  */
 struct NodeAttrs {
+  /*! \brief name of the node */
+  std::string name;
   /*! \brief The dictionary representation of attributes */
   std::unordered_map<std::string, std::string> dict;
   /*!
@@ -46,13 +48,11 @@ struct NodeAttrs {
  */
 class Node {
  public:
-  /*! \brief name of the node */
-  std::string name;
   /*!
    * \brief The operator this node uses.
    *  For place holder variable, op == nullptr.
    */
-  const Op *op;
+  const Op *op{nullptr};
   /*! \brief inputs to this node */
   std::vector<NodeEntry> inputs;
   /*!
diff --git a/nnvm/src/pass/saveload_json.cc b/nnvm/src/pass/saveload_json.cc
new file mode 100644
index 000000000000..9e82f1bd59cc
--- /dev/null
+++ b/nnvm/src/pass/saveload_json.cc
@@ -0,0 +1,202 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file saveload_json.cc
+ * \brief Passes that defines save and load graph to/from JSON file.
+ */
+#include <nnvm/pass.h>
+#include <dmlc/json.h>
+#include <algorithm>
+
+namespace dmlc {
+namespace json {
+// overload handler for shared ptr
+template<>
+struct Handler<std::shared_ptr<const any> > {
+  inline static void Write(JSONWriter *writer, const std::shared_ptr<const any> &data) {
+    writer->Write(*data);
+  }
+  inline static void Read(JSONReader *reader, std::shared_ptr<const any> *data) {
+    any v;
+    reader->Read(&v);
+    *data = std::make_shared<any>(std::move(v));
+  }
+};
+}  // namespace json
+}  // namespace dmlc
+
+namespace nnvm {
+namespace pass {
+
+// auxiliary node structure for serialization.
+struct JSONNode {
+  // the node entry structure in serialized format
+  typedef std::pair<uint32_t, uint32_t> Entry;
+  // pointer to the graph node
+  std::shared_ptr<Node> node;
+  // inputs
+  std::vector<Entry> inputs;
+  // control flow dependencies
+  std::vector<uint32_t> control_deps;
+
+  // function to save JSON node.
+  void Save(dmlc::JSONWriter *writer) const {
+    writer->BeginObject();
+    if (node->op != nullptr) {
+      writer->WriteObjectKeyValue("op", node->op->name);
+      writer->WriteObjectKeyValue("attr", node->attrs.dict);
+    } else {
+      std::string json_null = "null";
+      writer->WriteObjectKeyValue("op", json_null);
+    }
+    writer->WriteObjectKeyValue("name", node->attrs.name);
+    writer->WriteObjectKeyValue("inputs", inputs);
+    writer->WriteObjectKeyValue("control_deps", control_deps);
+    writer->EndObject();
+  }
+
+  void Load(dmlc::JSONReader *reader) {
+    node = std::move(Node::Create());
+    control_deps.clear();
+    dmlc::JSONObjectReadHelper helper;
+    std::string op_type_str;
+    helper.DeclareField("op", &op_type_str);
+    helper.DeclareField("name", &(node->attrs.name));
+    helper.DeclareField("inputs", &inputs);
+    helper.DeclareOptionalField("attr", &(node->attrs.dict));
+    helper.DeclareOptionalField("control_deps", &control_deps);
+    // backward compatible code with mxnet graph.
+    int backward_source_id;
+    std::unordered_map<std::string, std::string> param;
+    helper.DeclareOptionalField("param", &param);
+    helper.DeclareOptionalField("backward_source_id", &backward_source_id);
+    node->attrs.dict.insert(param.begin(), param.end());
+    helper.ReadAllFields(reader);
+
+    if (op_type_str != "null") {
+      try {
+        node->op = Op::Get(op_type_str);
+      } catch (const dmlc::Error &err) {
+        std::ostringstream os;
+        os << "Failed loading Op " << node->attrs.name
+           << " of type " << op_type_str << ": " << err.what();
+        throw dmlc::Error(os.str());
+      }
+    } else {
+      node->op = nullptr;
+    }
+  }
+};
+
+// graph structure to help read/save JSON.
+struct JSONGraph {
+  std::vector<JSONNode> nodes;
+  std::vector<uint32_t> arg_nodes;
+  std::vector<JSONNode::Entry> heads;
+  std::unordered_map<std::string, std::shared_ptr<const any> > attrs;
+
+  void Save(dmlc::JSONWriter *writer) const {
+    writer->BeginObject();
+    writer->WriteObjectKeyValue("nodes", nodes);
+    writer->WriteObjectKeyValue("arg_nodes", arg_nodes);
+    writer->WriteObjectKeyValue("heads", heads);
+    if (attrs.size() != 0) {
+      writer->WriteObjectKeyValue("attrs", attrs);
+    }
+    writer->EndObject();
+  }
+
+  void Load(dmlc::JSONReader *reader) {
+    attrs.clear();
+    dmlc::JSONObjectReadHelper helper;
+    helper.DeclareField("nodes", &nodes);
+    helper.DeclareField("arg_nodes", &arg_nodes);
+    helper.DeclareField("heads", &heads);
+    helper.DeclareOptionalField("attrs", &attrs);
+    helper.ReadAllFields(reader);
+  }
+};
+
+// Load a graph from JSON file.
+Graph LoadJSON(const Graph& src) {
+  CHECK_NE(src.attrs.count("json"), 0)
+      << "Load JSON require json to be presented.";
+  const std::string &json_str =
+      nnvm::get<std::string>(*src.attrs.at("json"));
+  std::istringstream is(json_str);
+  dmlc::JSONReader reader(&is);
+  JSONGraph jgraph;
+  // load in json graph.
+  jgraph.Load(&reader);
+  // connects the nodes
+  for (JSONNode &n : jgraph.nodes) {
+    n.node->inputs.reserve(n.inputs.size());
+    for (const JSONNode::Entry &e : n.inputs) {
+      n.node->inputs.emplace_back(
+          NodeEntry{jgraph.nodes[e.first].node, e.second});
+    }
+    n.node->control_deps.reserve(n.control_deps.size());
+    for (uint32_t nid : n.control_deps) {
+      n.node->control_deps.push_back(jgraph.nodes[nid].node);
+    }
+  }
+  // consistent check
+  for (uint32_t nid : jgraph.arg_nodes) {
+    CHECK(jgraph.nodes[nid].node->is_variable());
+  }
+  // return the graph
+  Graph ret;
+  ret.attrs = std::move(jgraph.attrs);
+  ret.outputs.reserve(jgraph.heads.size());
+  for (const JSONNode::Entry &e : jgraph.heads) {
+    ret.outputs.emplace_back(
+        NodeEntry{jgraph.nodes[e.first].node, e.second});
+  }
+  return ret;
+}
+
+// save a graph to json
+Graph SaveJSON(const Graph& src) {
+  JSONGraph jgraph;
+  std::unordered_map<Node*, uint32_t> node2index;
+  src.DFSVisit([&node2index, &jgraph](const std::shared_ptr<Node>& n) {
+      uint32_t nid = static_cast<uint32_t>(jgraph.nodes.size());
+      node2index[n.get()] = nid;
+      if (n->is_variable()) {
+        jgraph.arg_nodes.push_back(nid);
+      }
+      JSONNode jnode;
+      jnode.node = n;
+      jnode.inputs.reserve(n->inputs.size());
+      for (const NodeEntry& e : n->inputs) {
+        jnode.inputs.emplace_back(
+            std::make_pair(node2index.at(e.node.get()), e.index));
+      }
+      for (const std::shared_ptr<Node>& c : n->control_deps) {
+        jnode.control_deps.push_back(node2index.at(c.get()));
+      }
+      jgraph.nodes.emplace_back(std::move(jnode));
+    });
+
+  std::ostringstream os;
+  dmlc::JSONWriter writer(&os);
+  jgraph.Save(&writer);
+  Graph ret;
+  ret.attrs["json"] = std::make_shared<any>(os.str());
+  return ret;
+}
+
+// register pass
+NNVM_REGISTER_PASS(LoadJSON)
+.describe("Return a new Graph, loaded from src.attrs[\"json\"]")
+.set_body(LoadJSON)
+.set_change_graph(true)
+.depend_graph_attr("json");
+
+NNVM_REGISTER_PASS(SaveJSON)
+.describe("Return a new empty Graph. Save graph to ret.attrs[\"json\"]")
+.set_body(SaveJSON)
+.set_change_graph(true)
+.provide_graph_attr("json");
+
+}  // namespace pass
+}  // namespace nnvm

From f3fdd9d656e514a56310d14035cfb009be9bc694 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 9 Jul 2016 12:03:29 -0700
Subject: [PATCH 331/949] [SYMBOLIC] Add symbolic API (#2)

* [SYMBOLIC] Add symbolic API

* Update Testcase to nnvm
---
 nnvm/include/nnvm/graph.h         |  27 ++-
 nnvm/include/nnvm/node.h          |  17 +-
 nnvm/include/nnvm/op.h            |  85 +++++--
 nnvm/include/nnvm/op_attr_types.h |  42 ++++
 nnvm/include/nnvm/symbolic.h      | 148 ++++++++++++
 nnvm/src/core/graph_attr_types.cc |   2 +-
 nnvm/src/core/op.cc               |  25 +-
 nnvm/src/core/symbolic.cc         | 369 ++++++++++++++++++++++++++++++
 nnvm/src/pass/saveload_json.cc    |   2 +-
 nnvm/src/test_main.cc             |   6 +-
 nnvm/tests/cpp/op_test.cc         |   8 +-
 nnvm/tests/cpp/tuple_test.cc      |   8 +-
 12 files changed, 680 insertions(+), 59 deletions(-)
 create mode 100644 nnvm/include/nnvm/op_attr_types.h
 create mode 100644 nnvm/include/nnvm/symbolic.h
 create mode 100644 nnvm/src/core/symbolic.cc

diff --git a/nnvm/include/nnvm/graph.h b/nnvm/include/nnvm/graph.h
index 296f62bf839d..5269f8c33e19 100644
--- a/nnvm/include/nnvm/graph.h
+++ b/nnvm/include/nnvm/graph.h
@@ -14,11 +14,13 @@
 #include <unordered_set>
 #include "./base.h"
 #include "./node.h"
+#include "./symbolic.h"
 
 namespace nnvm {
 
 /*!
  * \brief Symbolic computation graph.
+ *  This is the intermediate representation for optimization pass.
  */
 class Graph {
  public:
@@ -30,16 +32,18 @@ class Graph {
    *  and can be shared across multiple Instance of graph
    */
   std::unordered_map<std::string, std::shared_ptr<const any> > attrs;
-  /*!
-   * \brief perform a Post Order DFS visit to each node in the graph.
-   *  This order is deterministic and is also topoligical sorted.
-   * \param fvisit a function of type std::function<void(const std::shared_ptr<Node>&)>
-   * \tparam FVisit The function type to perform the visit.
-   */
-  template<typename FVisit>
-  inline void DFSVisit(FVisit fvisit) const;
 };
 
+/*!
+ * \brief perform a Post Order DFS visit to each node in the graph.
+ *  This order is deterministic and is also topoligical sorted.
+ * \param heads The heads in the graph.
+ * \param fvisit a function of type std::function<void(const std::shared_ptr<Node>&)>
+ * \tparam FVisit The function type to perform the visit.
+ */
+template<typename FVisit>
+inline void DFSVisit(const std::vector<NodeEntry>& heads, FVisit fvisit);
+
 // inline function implementations
 template <typename GNode, typename HashType,
            typename FVisit, typename HashFunc,
@@ -75,10 +79,11 @@ void PostOrderDFSVisit(const std::vector<GNode>& heads,
 }
 
 template<typename FVisit>
-inline void Graph::DFSVisit(FVisit fvisit) const {
+inline void DFSVisit(const std::vector<NodeEntry>& heads,
+                     FVisit fvisit) {
   typedef const std::shared_ptr<Node>* GNode;
-  std::vector<GNode> head_nodes(outputs.size());
-  std::transform(outputs.begin(), outputs.end(), head_nodes.begin(),
+  std::vector<GNode> head_nodes(heads.size());
+  std::transform(heads.begin(), heads.end(), head_nodes.begin(),
                  [](const NodeEntry& e)->GNode {
                    return &e.node;
                  });
diff --git a/nnvm/include/nnvm/node.h b/nnvm/include/nnvm/node.h
index 51e7a6049001..dd24c3bd6fa3 100644
--- a/nnvm/include/nnvm/node.h
+++ b/nnvm/include/nnvm/node.h
@@ -72,6 +72,8 @@ class Node {
   inline bool is_variable() const;
   /*! \return number of outputs from this node */
   inline uint32_t num_outputs() const;
+  /*! \return number of inputs from this node */
+  inline uint32_t num_inputs() const;
   /*!
    * \brief create a new empty shared_ptr of Node.
    * \return a created empty node.
@@ -86,10 +88,19 @@ inline bool Node::is_variable() const {
 
 inline uint32_t Node::num_outputs() const {
   if (is_variable()) return 1;
-  if (this->op->num_outputs >= 0) {
-    return static_cast<uint32_t>(this->op->num_outputs);
+  if (this->op->get_num_outputs == nullptr) {
+    return this->op->num_outputs;
   } else {
-    return this->op->get_num_outputs(*this);
+    return this->op->get_num_outputs(this->attrs);
+  }
+}
+
+inline uint32_t Node::num_inputs() const {
+  if (is_variable()) return 1;
+  if (this->op->get_num_inputs == nullptr) {
+    return this->op->num_inputs;
+  } else {
+    return this->op->get_num_inputs(this->attrs);
   }
 }
 
diff --git a/nnvm/include/nnvm/op.h b/nnvm/include/nnvm/op.h
index a3467c5a6bcf..bcbd4d5cb939 100644
--- a/nnvm/include/nnvm/op.h
+++ b/nnvm/include/nnvm/op.h
@@ -10,6 +10,7 @@
 #include <vector>
 #include <utility>
 #include <typeinfo>
+#include <limits>
 #include <functional>
 #include "./base.h"
 
@@ -22,8 +23,8 @@ template<typename ValueType>
 class OpMap;
 class OpRegistryEntry;
 
-/*! \brief constant to indicate variable length inout and output */
-static const int kVarg = -1;
+/*! \brief constant to indicate it take any length of positional inputs */
+static const uint32_t kVarg = std::numeric_limits<uint32_t>::max();
 
 /*!
  * \brief Operator structure.
@@ -79,23 +80,31 @@ class Op {
   /*!
    * \brief number of inputs to the operator,
    * -1 means it is variable length
+   * When get_num_inputs is presented,
+   * the number will be decided by get_num_inputs instead.
+   * \sa get_num_inputs
    */
-  int num_inputs = 0;
+  uint32_t num_inputs = 1;
   /*!
    * \brief number of outputs of the operator
-   *  -1 means it is variable length
+   *  When get_num_outputs is presented.
    *  The number of outputs will be decided by
    *  get_num_outputs function
    * \sa get_num_outputs
    */
-  int num_outputs = 1;
+  uint32_t num_outputs = 1;
   /*!
    * \brief get number of outputs given information about the node.
-   *  This is only valid when num_outputs == -1.
-   * \param node The constructed node.
+   * \param attrs The attribute of the node
    * \return number of outputs.
    */
-  int (*get_num_outputs)(const Node& node) = nullptr;
+  uint32_t (*get_num_outputs)(const NodeAttrs& attrs) = nullptr;
+  /*!
+   * \brief get number of inputs given information about the node.
+   * \param attrs The attribute of the node
+   * \return number of inputs
+   */
+  uint32_t (*get_num_inputs)(const NodeAttrs& attrs) = nullptr;
   /*!
    * \brief Attribute parser to parse the NodeAttrs information.
    *
@@ -143,19 +152,25 @@ class Op {
    * \param n The number of inputs to be set.
    * \return reference to self.
    */
-  inline Op& set_num_inputs(int n);  // NOLINT(*)
+  inline Op& set_num_inputs(uint32_t n);  // NOLINT(*)
+  /*!
+   * \brief Set the get_num_outputs function.
+   * \param fn The function to be set.
+   * \return reference to self.
+   */
+  inline Op& set_num_inputs(uint32_t (*fn)(const NodeAttrs& attr));  // NOLINT(*)
   /*!
    * \brief Set the num_outputs
    * \param n The number of outputs to be set.
    * \return reference to self.
    */
-  inline Op& set_num_outputs(int n);  // NOLINT(*)
+  inline Op& set_num_outputs(uint32_t n);  // NOLINT(*)
   /*!
    * \brief Set the get_num_outputs function.
    * \param fn The function to be set.
    * \return reference to self.
    */
-  inline Op& set_num_outputs(int (*fn)(const Node& node));  // NOLINT(*)
+  inline Op& set_num_outputs(uint32_t (*fn)(const NodeAttrs& attr));  // NOLINT(*)
   /*!
    * \brief Set the attr_parser function.
    * \param fn The number of outputs to be set.
@@ -180,6 +195,7 @@ class Op {
   static const Op* Get(const std::string& op_name);
   /*!
    * \brief Get additional registered attribute about operators.
+   *  If nothing has been registered, an empty OpMap will be returned.
    * \param attr_name The name of the attribute.
    * \return An OpMap of specified attr_name.
    * \tparam ValueType The type of the attribute.
@@ -197,7 +213,7 @@ class Op {
   // internal constructor
   Op();
   // get const reference to certain attribute
-  static const any& GetAttrMap(const std::string& key);
+  static const any* GetAttrMap(const std::string& key);
   // update the attribute OpMap
   static void UpdateAttrMap(const std::string& key,
                             std::function<void(any*)> updater);
@@ -217,6 +233,13 @@ class OpMap {
    * \return the const reference to the content value.
    */
   inline const ValueType& operator[](const Op* op) const;
+  /*!
+   * \brief get the corresponding value element at op with default value.
+   * \param op The key to the map
+   * \param def_value The default value when the key does not exist.
+   * \return the const reference to the content value.
+   */
+  inline const ValueType& get(const Op* op, const ValueType& def_value) const;
   /*!
    * \brief Check if the map has op as key.
    * \param op The key to the map
@@ -262,8 +285,18 @@ class OpMap {
 // member function of Op
 template<typename ValueType>
 inline const OpMap<ValueType>& Op::GetAttr(const std::string& key) {
-  const any& ref = GetAttrMap(key);
-  return nnvm::get<OpMap<ValueType> >(ref);
+  const any* ref = GetAttrMap(key);
+  if (ref == nullptr) {
+    UpdateAttrMap(key, [key](any* pmap) {
+        if (pmap->empty()) {
+          OpMap<ValueType> pm;
+          pm.attr_name_ = key;
+          *pmap = std::move(pm);
+        }
+      });
+    ref = GetAttrMap(key);
+  }
+  return nnvm::get<OpMap<ValueType> >(*ref);
 }
 
 template<typename ValueType>
@@ -273,7 +306,7 @@ inline Op& Op::attr(  // NOLINT(*)
       if (pmap->empty()) {
         OpMap<ValueType> pm;
         pm.attr_name_ = attr_name;
-        *pmap = pm;
+        *pmap = std::move(pm);
       }
       CHECK_EQ(pmap->type(), typeid(OpMap<ValueType>))
           << "Attribute " << attr_name
@@ -301,18 +334,22 @@ inline Op& Op::describe(const std::string& descr) {  // NOLINT(*)
   return *this;
 }
 
-inline Op& Op::set_num_inputs(int n) {  // NOLINT(*)
+inline Op& Op::set_num_inputs(uint32_t n) {  // NOLINT(*)
   this->num_inputs = n;
   return *this;
 }
 
-inline Op& Op::set_num_outputs(int n) {  // NOLINT(*)
+inline Op& Op::set_num_inputs(uint32_t (*fn)(const NodeAttrs&)) {  // NOLINT(*)
+  this->get_num_inputs = fn;
+  return *this;
+}
+
+inline Op& Op::set_num_outputs(uint32_t n) {  // NOLINT(*)
   this->num_outputs = n;
   return *this;
 }
 
-inline Op& Op::set_num_outputs(int (*fn)(const Node& node)) {  // NOLINT(*)
-  this->num_outputs = kVarg;
+inline Op& Op::set_num_outputs(uint32_t (*fn)(const NodeAttrs&)) {  // NOLINT(*)
   this->get_num_outputs = fn;
   return *this;
 }
@@ -338,6 +375,16 @@ inline const ValueType& OpMap<ValueType>::operator[](const Op* op) const {
   return data_[idx].first;
 }
 
+template<typename ValueType>
+inline const ValueType& OpMap<ValueType>::get(const Op* op, const ValueType& def_value) const {
+  const uint32_t idx = op->index_;
+  if (idx < data_.size() && data_[idx].second) {
+    return data_[idx].first;
+  } else {
+    return def_value;
+  }
+}
+
 }  // namespace nnvm
 
 #endif  // NNVM_OP_H_
diff --git a/nnvm/include/nnvm/op_attr_types.h b/nnvm/include/nnvm/op_attr_types.h
new file mode 100644
index 000000000000..bfcf5f6b8eaa
--- /dev/null
+++ b/nnvm/include/nnvm/op_attr_types.h
@@ -0,0 +1,42 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file op_attr_types.h
+ * \brief Data structures that can appear in operator attributes.
+ */
+#ifndef NNVM_OP_ATTR_TYPES_H_
+#define NNVM_OP_ATTR_TYPES_H_
+
+#include <vector>
+#include <string>
+#include <functional>
+
+namespace nnvm {
+
+// These types are optional attributes in each op
+// Some of them are needed for certain pass.
+
+/*!
+ * \brief Return list of input arguments names of each operator.
+ *
+ * \param attrs The attributes of the node.
+ * \return list of inputs
+ * \note Register under "FListInputNames", default return {"data"}.
+ *
+ *  FListInputNames enables automatic variable creation for missing arguments.
+ */
+using FListInputNames = std::function<std::vector<std::string> (const NodeAttrs& attrs)>;
+
+/*!
+ * \brief Return list of output arguments names of each operator.
+ *
+ * \param attrs The attributes of the node.
+ * \return list of inputs
+ * \note Register under "FListOutputNames", default return {"outputs"}.
+ *
+ *  FListOutputNames customized naming for operator outputs.
+ */
+using FListOutputNames = std::function<std::vector<std::string> (const NodeAttrs& attrs)>;
+
+}  // namespace nnvm
+
+#endif  // NNVM_OP_ATTR_TYPES_H_
diff --git a/nnvm/include/nnvm/symbolic.h b/nnvm/include/nnvm/symbolic.h
new file mode 100644
index 000000000000..cba8c3bf8473
--- /dev/null
+++ b/nnvm/include/nnvm/symbolic.h
@@ -0,0 +1,148 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file symbolic.h
+ * \brief Symbolic graph construction API
+ *
+ *  This API is optional, but useful to allow user
+ *  to construct NNVM Graph easily, and quickly create
+ *  front-end host languages.
+ */
+#ifndef NNVM_SYMBOLIC_H_
+#define NNVM_SYMBOLIC_H_
+
+#include <string>
+#include <vector>
+#include <utility>
+
+#include "./base.h"
+#include "./node.h"
+
+namespace nnvm {
+/*!
+ * \brief Symbol is used to represent the
+ */
+class Symbol {
+ public:
+  /*! \brief option passed to ListAttr */
+  enum ListAttrOption {
+    /*! \brief recursively list all attributes */
+    kRecursive,
+    /*! \brief only list attributes in current node */
+    kShallow
+  };
+
+  /*! \brief output entries contained in the symbol */
+  std::vector<NodeEntry> outputs;
+
+  /*!
+   * \brief copy the symbol
+   * \return a deep copy of the symbolic graph.
+   */
+  Symbol Copy() const;
+  /*!
+   * \brief print the symbol info to output stream.
+   * \param os the output stream we like to print to
+   */
+  void Print(std::ostream &os) const; // NOLINT(*)
+  /*!
+   * \brief get the index th element from the returned tuple.
+   * \param index index of multi output
+   * \return the symbol corresponds to the indexed element.
+   */
+  Symbol operator[] (size_t index) const;
+  /*!
+   * \brief List the arguments names.
+   *
+   * The position of the returned list also corresponds to calling position in operator()
+   * \return the arguments list of this symbol, they can be either named or unnamed (empty string).
+   */
+  std::vector<std::string> ListArguments() const;
+  /*!
+   * \brief List the names of outputs for this symbol.
+   *  For normal operators, it is usually symbol node name + "_output"
+   * \return get the descriptions of outputs for this symbol.
+   */
+  std::vector<std::string> ListOutputs() const;
+  /*!
+   * \brief Compose the symbol with arguments, this changes the current symbol.
+   * The kwargs passed in can be in-complete,
+   *
+   * The rest of the symbols will remain the same name.
+   *
+   * \param positional arguments
+   * \param kwargs keyword arguments for the symbol
+   * \param name name of returned symbol.
+   */
+  void Compose(const std::vector<Symbol>& args,
+               const std::unordered_map<std::string, Symbol>& kwargs,
+               const std::string& name);
+  /*!
+   * \brief Apply the symbol as a function, compose with arguments
+   * This is equivalent to Copy then Compose.
+   * \param args positional arguments for the symbol
+   * \param kwargs keyword arguments for the symbol
+   * \param name name of returned symbol.
+   * \return a new Symbol which is the composition of current symbol with its arguments
+   */
+  Symbol operator () (const std::vector<Symbol>& args,
+                      const std::unordered_map<std::string, Symbol>& kwargs,
+                      const std::string& name) const;
+  /*!
+   * \brief Add control flow depenencies to operators involved in symbols.
+   *  For grouped sybmbol, an error will be raised.
+   *  This mutate current symbolic Node.
+   *
+   * \param src The symbols to depend on.
+   */
+  void AddControlDeps(const Symbol& src);
+  /*
+   * \brief Get all the internal nodes of the symbol.
+   * \return symbol A new symbol whose output contains all the outputs of the symbols
+   *  Including input variables and intermediate outputs.
+   */
+  Symbol GetInternals() const;
+  /*!
+   * \brief set additional attributes to current node.
+   *  This only works for symbol with outputs from single operators.
+   *  For grouped sybmbol, an error will be raised.
+   *
+   *  This function mutate the node's symbol and is not recommended.
+   *
+   * \param key the key of the attribute
+   * \param value the value of the attribute.
+   */
+  void SetAttrs(const std::vector<std::pair<std::string, std::string> >& attrs);
+  /*!
+   * \brief Get attribute dictionary from the symbol.
+   *  For grouped sybmbol, an error will be raised.
+   * \param option If recursive is set, the attributes of all children are retrieved,
+   *   The name of symbol will be pre-pended to each key.
+   * \return The created attribute.
+   */
+  std::unordered_map<std::string, std::string> ListAttr(ListAttrOption option) const;
+  /*!
+   * \brief create symbolic functor(AtomicSymbol) by given operator and attributes.
+   * \param op_name The name of the operator.
+   * \param attrs The additional attributes.
+   *
+   * \return Symbol that can be used to call compose further.
+   */
+  static Symbol CreateFunctor(const std::string& op_name,
+                              const std::unordered_map<std::string, std::string>& attrs);
+  /*!
+   * \brief create variable symbol node
+   * \param name name of the variable
+   * \return the new variable
+   */
+  static Symbol CreateVariable(const std::string& name);
+  /*!
+   * \brief create equivalence of symbol by grouping the symbols together
+   * \param symbols list of symbols
+   * \return the grouped symbol
+   */
+  static Symbol CreateGroup(const std::vector<Symbol>& symbols);
+};
+
+}  // namespace nnvm
+
+#endif  // NNVM_SYMBOLIC_H_
diff --git a/nnvm/src/core/graph_attr_types.cc b/nnvm/src/core/graph_attr_types.cc
index 43351f544835..aaefa0626131 100644
--- a/nnvm/src/core/graph_attr_types.cc
+++ b/nnvm/src/core/graph_attr_types.cc
@@ -13,7 +13,7 @@ IndexedGraph::IndexedGraph(const Graph &g) {
   entry_rptr_.push_back(0);
   std::vector<size_t> inputs_rptr{0}, control_rptr{0};
 
-  g.DFSVisit([this, &inputs_rptr, &control_rptr]
+  DFSVisit(g.outputs, [this, &inputs_rptr, &control_rptr]
              (const std::shared_ptr<nnvm::Node>& n) {
       CHECK_LT(nodes_.size(), std::numeric_limits<uint32_t>::max());
       uint32_t nid = static_cast<uint32_t>(nodes_.size());
diff --git a/nnvm/src/core/op.cc b/nnvm/src/core/op.cc
index 57514aa7fbcb..a5518ace6be1 100644
--- a/nnvm/src/core/op.cc
+++ b/nnvm/src/core/op.cc
@@ -6,6 +6,7 @@
 #include <nnvm/base.h>
 #include <nnvm/op.h>
 
+#include <memory>
 #include <atomic>
 #include <mutex>
 
@@ -23,7 +24,7 @@ struct OpManager {
   // global operator counter
   std::atomic<int> op_counter{0};
   // storage of additional attribute table.
-  std::unordered_map<std::string, any> attr;
+  std::unordered_map<std::string, std::unique_ptr<any> > attr;
   // get singleton of the
   static OpManager* Global() {
     static OpManager inst;
@@ -46,24 +47,24 @@ const Op* Op::Get(const std::string& name) {
 }
 
 // Get attribute map by key
-const any& Op::GetAttrMap(const std::string& key) {
-  // assume no operator registration during
-  // the execution phase.
-  const auto& dict = OpManager::Global()->attr;
+const any* Op::GetAttrMap(const std::string& key) {
+  auto& dict =  OpManager::Global()->attr;
   auto it = dict.find(key);
-  CHECK(it != dict.end() && it->first == key)
-      << "Cannot find Operator attribute " << key
-      << " for any operator";
-  return it->second;
+  if (it != dict.end()) {
+    return it->second.get();
+  } else {
+    return nullptr;
+  }
 }
 
-// update attribute map by updater function.
+// update attribute map
 void Op::UpdateAttrMap(const std::string& key,
                        std::function<void(any*)> updater) {
   OpManager* mgr = OpManager::Global();
   std::lock_guard<std::mutex>(mgr->mutex);
-  any& value = mgr->attr[key];
-  updater(&value);
+  std::unique_ptr<any>& value = mgr->attr[key];
+  if (value.get() == nullptr) value.reset(new any());
+  if (updater != nullptr) updater(value.get());
 }
 
 }  // namespace nnvm
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
new file mode 100644
index 000000000000..03a6f065b008
--- /dev/null
+++ b/nnvm/src/core/symbolic.cc
@@ -0,0 +1,369 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file symbolic.cc
+ * \brief Symbolic graph composition API.
+ */
+#include <nnvm/graph.h>
+#include <nnvm/symbolic.h>
+#include <nnvm/op_attr_types.h>
+
+namespace nnvm {
+
+namespace symbol_constants {
+const char *kNamespaceSeparator = "_";
+}  // namespace symbol_constants
+
+
+inline std::string DefaultVarName(const std::string &op_name,
+                                  const std::string &arg_name) {
+  if (op_name.length() == 0) {
+    return arg_name;
+  } else {
+    return op_name + '_' + arg_name;
+  }
+}
+
+inline void KeywordArgumentMismatch(const char *source,
+                                    const std::vector<std::string>& user_args,
+                                    const array_view<std::string>& args) {
+  std::unordered_set<std::string> keys(args.begin(), args.end());
+  std::ostringstream head, msg;
+  msg << "\nCandidate arguments:\n";
+  for (size_t i = 0; i < args.size(); ++i) {
+    msg << "\t[" << i << ']' << args[i] << '\n';
+  }
+
+  for (const auto& key : user_args) {
+    if (keys.count(key) == 0) {
+      LOG(FATAL) << source
+                 << "Keyword argument name " << key << " not found."
+                 << msg.str();
+    }
+  }
+}
+
+template<typename T>
+inline std::vector<std::string> GetKeys(
+    const std::unordered_map<std::string, T>& kwargs) {
+  std::vector<std::string> keys(kwargs.size());
+  std::transform(kwargs.begin(), kwargs.end(), keys.begin(),
+                 [](decltype(*kwargs.begin())& kv) { return kv.first; });
+  return keys;
+}
+
+// whether the symbol is atomic functor
+inline bool IsAtomic(const std::vector<NodeEntry>& outputs) {
+  return outputs.size() == 1 && outputs[0].node->inputs.size() == 0;
+}
+
+// public functions
+Symbol Symbol::Copy() const {
+  std::unordered_map<Node*, std::shared_ptr<Node> > old_new;
+  // use DFSVisit to copy all the nodes
+  DFSVisit(this->outputs, [&old_new](const std::shared_ptr<Node>& node) {
+      old_new[node.get()] =  std::make_shared<Node>(*node);
+    });
+  // connect nodes of new graph
+  for (const auto &kv : old_new) {
+    for (const NodeEntry& e : kv.first->inputs) {
+      Node *ptr = e.node.get();
+      kv.second->inputs.emplace_back(NodeEntry{old_new[ptr], e.index});
+    }
+  }
+  // set the head
+  Symbol ret;
+  for (const NodeEntry &e : outputs) {
+    ret.outputs.emplace_back(NodeEntry{old_new[e.node.get()], e.index});
+  }
+  return ret;
+}
+
+void Symbol::Print(std::ostream &os) const {
+  if (outputs.size() == 1 && outputs[0].node->inputs.size() == 0) {
+    os << "AtomicFunctor "<< " Op:" << outputs[0].node->op->name << '\n';
+  } else {
+    // use DFSVisit to copy all the nodes
+    os << "Outputs:\n";
+    for (size_t i = 0; i < outputs.size(); ++i) {
+      os << "\toutput[" << i << "]=" << outputs[i].node->attrs.name
+         << '(' << outputs[i].index << ")\n";
+    }
+    DFSVisit(this->outputs, [&os](const std::shared_ptr<Node>& node) {
+        if (node->is_variable()) {
+          os << "Variable:" << node->attrs.name << '\n';
+        } else {
+          os << "Name: " << node->attrs.name << " Op:" << node->op->name << '\n'
+             << "Inputs:\n";
+          for (size_t i = 0; i < node->inputs.size(); ++i) {
+            os << "\targ[" << i << "]=" << node->inputs[i].node->attrs.name
+               << '(' << node->inputs[i].index << ")\n";
+          }
+          os << "Attrs:\n";
+          for (auto &kv : node->attrs.dict) {
+            os << '\t' << kv.first << '=' << kv.second << '\n';
+          }
+        }
+      });
+  }
+}
+
+Symbol Symbol::operator[] (size_t index) const {
+  size_t nreturn = outputs.size();
+  CHECK_LT(index, nreturn) << "Symbol only accept nonnegative index";
+  if (nreturn == 1) {
+    return *this;
+  } else {
+    Symbol s;
+    s.outputs.push_back(outputs[index]);
+    return s;
+  }
+}
+
+std::vector<std::string> Symbol::ListArguments() const {
+  std::vector<std::string> ret;
+  DFSVisit(this->outputs, [&ret](const std::shared_ptr<Node> &node) {
+      if (node->is_variable()) {
+        ret.push_back(node->attrs.name);
+      }
+    });
+  return ret;
+}
+
+std::vector<std::string> Symbol::ListOutputs() const {
+  static auto& flist_ouputs = Op::GetAttr<FListOutputNames>("FListOutputNames");
+  std::vector<std::string> ret;
+  for (auto &head : outputs) {
+    if (head.node->is_variable()) {
+      ret.push_back(head.node->attrs.name);
+    } else {
+      const std::string& hname = head.node->attrs.name;
+      std::string rname;
+      FListOutputNames fn = flist_ouputs.get(head.node->op, nullptr);
+      if (fn != nullptr) {
+        rname = fn(head.node->attrs)[head.index];
+      } else {
+        rname = "output";
+        if (head.node->num_outputs() != 1) {
+          std::ostringstream os;
+          os << rname << head.index;
+          rname = os.str();
+        }
+      }
+      if (hname.length() == 0) {
+        ret.push_back(std::move(rname));
+      } else {
+        ret.push_back(hname + '_' + rname);
+      }
+    }
+  }
+  return ret;
+}
+
+// compositional logic
+void Symbol::Compose(const std::vector<Symbol>& args,
+                     const std::unordered_map<std::string, Symbol>& kwargs,
+                     const std::string& name) {
+  CHECK_EQ(outputs.size(), 1)
+      << "Only composition of value function is supported currently";
+  CHECK(!outputs[0].node->is_variable()) << "Variable cannot be composed";
+  // parameter check.
+  for (size_t i = 0; i < args.size(); ++i) {
+    CHECK_EQ(args[i].outputs.size(), 1)
+        << "Argument " << i << " is a tuple, single value is required";
+  }
+  for (const auto& kv : kwargs) {
+    CHECK_EQ(kv.second.outputs.size(), 1)
+        << "Keyword Argument " << kv.first << " is a tuple, single value is required";
+  }
+  // assign new name
+  outputs[0].node->attrs.name = name;
+
+  // Atomic functor composition.
+  if (IsAtomic(outputs)) {
+    Node* n = outputs[0].node.get();
+    uint32_t n_req = n->num_inputs();
+
+    if (n_req != kVarg) {
+      n->inputs.resize(n_req);
+      CHECK_LE(args.size(), n_req)
+          << "Incorrect number of arguments, requires " << n_req
+          << ", provided " << args.size();
+      for (size_t i = 0; i < args.size(); ++i) {
+        n->inputs[i] = args[i].outputs[0];
+      }
+      // switch to keyword argument matching
+      if (args.size() != n_req) {
+        static auto& flist_inputs = Op::GetAttr<FListInputNames>("FListInputNames");
+        FListInputNames fn = flist_inputs.get(n->op, nullptr);
+        auto arg_names = (fn == nullptr) ? std::vector<std::string>{"data"} : fn(n->attrs);
+        CHECK_EQ(arg_names.size(), n_req);
+
+        size_t nmatched = 0;
+        for (size_t i = args.size(); i < n_req; ++i) {
+          auto it = kwargs.find(arg_names[i]);
+          if (it != kwargs.end() && it->first == arg_names[i]) {
+            n->inputs[i] = it->second.outputs[0];
+            ++nmatched;
+          } else {
+            n->inputs[i] = NodeEntry{Node::Create(), 0};
+            n->inputs[i].node->attrs.name = DefaultVarName(name, arg_names[i]);
+          }
+        }
+
+        if (nmatched != kwargs.size()) {
+          n->inputs.clear();
+          std::vector<std::string> keys = GetKeys(kwargs);
+          array_view<std::string> view(dmlc::BeginPtr(arg_names) + args.size(),
+                                       dmlc::BeginPtr(arg_names) + arg_names.size());
+          KeywordArgumentMismatch("Symbol.Compose", keys, view);
+        }
+      }
+    } else {
+      CHECK_EQ(kwargs.size(), 0) << "Variable length function do not accept kwargs";
+      n->inputs.reserve(args.size());
+      for (const Symbol& s : args) {
+        n->inputs.push_back(s.outputs[0]);
+      }
+    }
+  } else {
+    // general composition
+    CHECK_EQ(args.size(), 0)
+        << "General composition only support kwargs for now";
+    size_t nmatched = 0;
+    size_t arg_counter = 0;
+    std::unordered_map<Node *, const NodeEntry*> replace_map;
+    // replace map stores the existing replacement plan for arguments node
+    auto find_replace_map = [&nmatched, &arg_counter, &args, &kwargs, &replace_map]
+        (const std::shared_ptr<Node> &node) {
+      if (node->is_variable()) {
+        if (arg_counter < args.size()) {
+          replace_map[node.get()] = &(args[arg_counter].outputs[0]);
+          ++arg_counter;
+        } else {
+            // match kwargs
+          auto kit = kwargs.find(node->attrs.name);
+          if (kit != kwargs.end()) {
+            replace_map[node.get()] = &(kit->second.outputs[0]);
+            ++nmatched;
+          }
+        }
+      }
+    };
+    DFSVisit(this->outputs, find_replace_map);
+
+    if (nmatched == kwargs.size() && arg_counter < args.size()) {
+      std::vector<std::pair<NodeEntry*, const NodeEntry*> > replace_plan;
+      auto find_replace_plan = [&replace_map, &replace_plan]
+          (const std::shared_ptr<Node> &node) {
+        // visit all the childs, find possible replacement
+        for (size_t i = 0; i < node->inputs.size(); ++i) {
+          NodeEntry *e = &(node->inputs[i]);
+          if (e->node->is_variable()) {
+            auto iter = replace_map.find(e->node.get());
+            if (iter != replace_map.end()) {
+              replace_plan.push_back(std::make_pair(e, iter->second));
+            }
+          }
+        }
+      };
+      DFSVisit(this->outputs, find_replace_plan);
+
+      for (const auto& kv : replace_plan) {
+        *(kv.first) = *(kv.second);
+      }
+    } else {
+      std::vector<std::string> keys = GetKeys(kwargs);
+      std::vector<std::string> arg_names = ListArguments();
+      array_view<std::string> view(dmlc::BeginPtr(arg_names) + arg_counter,
+                                   dmlc::BeginPtr(arg_names) + arg_names.size());
+      KeywordArgumentMismatch("Symbol.Compose", keys, ListArguments());
+    }
+  }
+}
+
+Symbol Symbol::operator () (const std::vector<Symbol>& args,
+                            const std::unordered_map<std::string, Symbol>& kwargs,
+                            const std::string& name) const {
+  Symbol s = this->Copy();
+  s.Compose(args, kwargs, name);
+  return s;
+}
+
+void Symbol::AddControlDeps(const Symbol& src) {
+  CHECK_EQ(outputs.size(), 1)
+      << "AddControlDeps only works for nongrouped symbol";
+  Node* n = outputs[0].node.get();
+  for (const NodeEntry& sp : src.outputs) {
+    n->control_deps.push_back(sp.node);
+  }
+}
+
+Symbol Symbol::GetInternals() const {
+  Symbol ret;
+  DFSVisit(this->outputs, [&ret](const std::shared_ptr<Node>& node) {
+      Node* n = node.get();
+      uint32_t nout = n->num_outputs();
+      for (uint32_t i = 0; i < nout; ++i) {
+        ret.outputs.emplace_back(NodeEntry{node, i});
+      }
+    });
+  return ret;
+}
+
+void Symbol::SetAttrs(const std::vector<std::pair<std::string, std::string> >& attrs) {
+  CHECK_EQ(outputs.size(), 1)
+      << "SetAttrs only works for nongrouped symbol";
+  Node* n = outputs[0].node.get();
+  for (const auto& kv : attrs) {
+    n->attrs.dict[kv.first] = kv.second;
+  }
+  if (n->op->attr_parser != nullptr) {
+    (*n->op->attr_parser)(&(n->attrs));
+  }
+}
+
+std::unordered_map<std::string, std::string> Symbol::ListAttr(ListAttrOption option) const {
+  if (option == kRecursive) {
+    std::unordered_map<std::string, std::string> ret;
+    DFSVisit(this->outputs, [&ret](const std::shared_ptr<Node>& n) {
+        for (const auto& it : n->attrs.dict) {
+          ret[n->attrs.name + symbol_constants::kNamespaceSeparator + it.first] = it.second;
+        }
+      });
+    return ret;
+  } else {
+    return outputs[0].node->attrs.dict;
+  }
+}
+
+Symbol Symbol::CreateFunctor(const std::string& op_name,
+                             const std::unordered_map<std::string, std::string>& attrs) {
+  Symbol s;
+  std::shared_ptr<Node> n = Node::Create();
+  n->op = Op::Get(op_name);
+  n->attrs.dict = attrs;
+  if (n->op->attr_parser != nullptr) {
+    (*n->op->attr_parser)(&(n->attrs));
+  }
+  s.outputs.emplace_back(NodeEntry{std::move(n), 0});
+  return s;
+}
+
+Symbol Symbol::CreateGroup(const std::vector<Symbol> &symbols) {
+  Symbol ret;
+  for (const auto &s : symbols) {
+    ret.outputs.insert(ret.outputs.end(), s.outputs.begin(), s.outputs.end());
+  }
+  return ret;
+}
+
+Symbol Symbol::CreateVariable(const std::string& name) {
+  Symbol s;
+  std::shared_ptr<Node> n = Node::Create();
+  n->op = nullptr;
+  n->attrs.name = name;
+  s.outputs.emplace_back(NodeEntry{std::move(n), 0});
+  return s;
+}
+
+}  // namespace nnvm
diff --git a/nnvm/src/pass/saveload_json.cc b/nnvm/src/pass/saveload_json.cc
index 9e82f1bd59cc..f05e4873e2bf 100644
--- a/nnvm/src/pass/saveload_json.cc
+++ b/nnvm/src/pass/saveload_json.cc
@@ -158,7 +158,7 @@ Graph LoadJSON(const Graph& src) {
 Graph SaveJSON(const Graph& src) {
   JSONGraph jgraph;
   std::unordered_map<Node*, uint32_t> node2index;
-  src.DFSVisit([&node2index, &jgraph](const std::shared_ptr<Node>& n) {
+  DFSVisit(src.outputs, [&node2index, &jgraph](const std::shared_ptr<Node>& n) {
       uint32_t nid = static_cast<uint32_t>(jgraph.nodes.size());
       node2index[n.get()] = nid;
       if (n->is_variable()) {
diff --git a/nnvm/src/test_main.cc b/nnvm/src/test_main.cc
index 6a6946171b38..51d4ec1513cf 100644
--- a/nnvm/src/test_main.cc
+++ b/nnvm/src/test_main.cc
@@ -8,7 +8,7 @@
 void test_op() {
   using namespace nnvm;
   auto add = Op::Get("add");
-  auto nick = Op::GetAttr<std::string>("nick_name");
+  static auto& nick = Op::GetAttr<std::string>("nick_name");
   LOG(INFO) << "nick=" << nick[add];
 }
 
@@ -35,9 +35,7 @@ void test_tuple() {
 
 
 void test_graph() {
-  nnvm::Graph g;
-  g.DFSVisit([](const std::shared_ptr<const nnvm::Node>& n){
-    });
+  nnvm::Symbol s;
 }
 int main() {
   test_tuple();
diff --git a/nnvm/tests/cpp/op_test.cc b/nnvm/tests/cpp/op_test.cc
index c0a8913f4aef..816305c609ca 100644
--- a/nnvm/tests/cpp/op_test.cc
+++ b/nnvm/tests/cpp/op_test.cc
@@ -1,19 +1,19 @@
 #include <dmlc/logging.h>
 #include <gtest/gtest.h>
-#include <nngraph/op.h>
+#include <nnvm/op.h>
 #include <utility>
 
-NNGRAPH_REGISTER_OP(add)
+NNVM_REGISTER_OP(add)
 .describe("add two data together")
 .set_num_inputs(2)
 .attr("inplace_pair", std::make_pair(0, 0));
 
-NNGRAPH_REGISTER_OP(add)
+NNVM_REGISTER_OP(add)
 .attr<std::string>("nick_name", "plus");
 
 
 TEST(Op, GetAttr) {
-  using namespace nngraph;
+  using namespace nnvm;
   auto add = Op::Get("add");
   auto nick = Op::GetAttr<std::string>("nick_name");
 
diff --git a/nnvm/tests/cpp/tuple_test.cc b/nnvm/tests/cpp/tuple_test.cc
index 4750e01d2541..496e62ebe9a5 100644
--- a/nnvm/tests/cpp/tuple_test.cc
+++ b/nnvm/tests/cpp/tuple_test.cc
@@ -1,10 +1,10 @@
 #include <dmlc/logging.h>
 #include <gtest/gtest.h>
-#include <nngraph/tuple.h>
+#include <nnvm/tuple.h>
 
 TEST(Tuple, Basic) {
-  using nngraph::Tuple;
-  using nngraph::TShape;
+  using nnvm::Tuple;
+  using nnvm::TShape;
   Tuple<int> x{1, 2, 3};
   Tuple<int> y{1, 2, 3, 5, 6};
   x = std::move(y);
@@ -17,7 +17,7 @@ TEST(Tuple, Basic) {
   std::istringstream is(os.str());
   is >> y;
   CHECK_EQ(x, y);
-  Tuple<nngraph::index_t> ss{1, 2, 3};
+  Tuple<nnvm::index_t> ss{1, 2, 3};
   TShape s = ss;
   s = std::move(ss);
   CHECK((s == TShape{1, 2, 3}));

From 1800e560805f0c1b6e898194e997cae0849c0711 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 10 Jul 2016 10:55:50 -0700
Subject: [PATCH 332/949] [C API] add in C API for symbolic (#3)

---
 nnvm/Makefile                    |   8 +-
 nnvm/include/nnvm/c_api.h        | 219 ++++++++++++++++++++++++++++++
 nnvm/include/nnvm/op.h           |   9 +-
 nnvm/include/nnvm/symbolic.h     |  18 ++-
 nnvm/src/c_api/c_api_common.h    |  59 ++++++++
 nnvm/src/c_api/c_api_error.cc    |  21 +++
 nnvm/src/c_api/c_api_symbolic.cc | 222 +++++++++++++++++++++++++++++++
 nnvm/src/core/symbolic.cc        |  10 +-
 8 files changed, 544 insertions(+), 22 deletions(-)
 create mode 100644 nnvm/include/nnvm/c_api.h
 create mode 100644 nnvm/src/c_api/c_api_common.h
 create mode 100644 nnvm/src/c_api/c_api_error.cc
 create mode 100644 nnvm/src/c_api/c_api_symbolic.cc

diff --git a/nnvm/Makefile b/nnvm/Makefile
index 6c98ee342e15..78a56b8158e2 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -5,7 +5,7 @@ export CFLAGS =  -std=c++11 -Wall -O3 -msse2  -Wno-unknown-pragmas -funroll-loop
 # specify tensor path
 .PHONY: clean all test lint doc
 
-all: lib/libnngraph.so lib/libnngraph.a cli_test
+all: lib/libnnvm.so lib/libnnvm.a cli_test
 
 SRC = $(wildcard src/*.cc src/*/*.cc example/*.cc)
 ALL_OBJ = $(patsubst src/%.cc, build/%.o, $(SRC))
@@ -20,11 +20,11 @@ build/%.o: src/%.cc
 	$(CXX) $(CFLAGS) -MM -MT build/$*.o $< >build/$*.d
 	$(CXX) -c $(CFLAGS) -c $< -o $@
 
-lib/libnngraph.so: $(ALL_DEP)
+lib/libnnvm.so: $(ALL_DEP)
 	@mkdir -p $(@D)
 	$(CXX) $(CFLAGS) -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
 
-lib/libnngraph.a: $(ALL_DEP)
+lib/libnnvm.a: $(ALL_DEP)
 	@mkdir -p $(@D)
 	ar crv $@ $(filter %.o, $?)
 
@@ -32,7 +32,7 @@ cli_test: $(ALL_DEP) build/test_main.o
 	$(CXX) $(CFLAGS)  -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
 
 lint:
-	python2 dmlc-core/scripts/lint.py nngraph cpp include src
+	python2 dmlc-core/scripts/lint.py nnvm cpp include src
 
 doc:
 	doxygen docs/Doxyfile
diff --git a/nnvm/include/nnvm/c_api.h b/nnvm/include/nnvm/c_api.h
new file mode 100644
index 000000000000..bade0100c59e
--- /dev/null
+++ b/nnvm/include/nnvm/c_api.h
@@ -0,0 +1,219 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file c_api.h
+ * \brief C API of NNVM symbolic construction and pass.
+ *  Enables construction and transformation of Graph
+ *  in any other host languages.
+ */
+#ifndef NNVM_C_API_H_
+#define NNVM_C_API_H_
+
+#ifdef __cplusplus
+#define NNVM_EXTERN_C extern "C"
+#endif
+
+/*! \brief NNVM_DLL prefix for windows */
+#ifdef _WIN32
+#ifdef NNVM_EXPORTS
+#define NNVM_DLL NNVM_EXTERN_C __declspec(dllexport)
+#else
+#define NNVM_DLL NNVM_EXTERN_C __declspec(dllimport)
+#endif
+#else
+#define NNVM_DLL NNVM_EXTERN_C
+#endif
+
+/*! \brief manually define unsigned int */
+typedef unsigned int nn_uint;
+
+/*! \brief handle to a function that takes param and creates symbol */
+typedef void *AtomicSymbolCreator;
+/*! \brief handle to a symbol that can be bind as operator */
+typedef void *SymbolHandle;
+/*! \brief handle to a AtomicSymbol */
+typedef void *AtomicSymbolHandle;
+
+/*!
+ * \brief return str message of the last error
+ *  all function in this file will return 0 when success
+ *  and -1 when an error occured,
+ *  NNGetLastError can be called to retrieve the error
+ *
+ *  this function is threadsafe and can be called by different thread
+ *  \return error info
+ */
+NNVM_DLL const char *NNGetLastError();
+
+/*!
+ * \brief list all the available AtomicSymbolEntry
+ * \param out_size the size of returned array
+ * \param out_array the output AtomicSymbolCreator array
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNSymbolListAtomicSymbolCreators(nn_uint *out_size,
+                                              AtomicSymbolCreator **out_array);
+/*!
+ * \brief Get the detailed information about atomic symbol.
+ * \param creator the AtomicSymbolCreator.
+ * \param name The returned name of the creator.
+ * \param description The returned description of the symbol.
+ * \param num_doc_args Number of arguments that contain documents.
+ * \param arg_names Name of the arguments of doc args
+ * \param arg_type_infos Type informations about the arguments.
+ * \param arg_descriptions Description information about the arguments.
+ * \param return_type Return type of the function, if any.
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNSymbolGetAtomicSymbolInfo(AtomicSymbolCreator creator,
+                                         const char **name,
+                                         const char **description,
+                                         nn_uint *num_doc_args,
+                                         const char ***arg_names,
+                                         const char ***arg_type_infos,
+                                         const char ***arg_descriptions,
+                                         const char **return_type = NULL);
+/*!
+ * \brief Create an AtomicSymbol functor.
+ * \param creator the AtomicSymbolCreator
+ * \param num_param the number of parameters
+ * \param keys the keys to the params
+ * \param vals the vals of the params
+ * \param out pointer to the created symbol handle
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNSymbolCreateAtomicSymbol(AtomicSymbolCreator creator,
+                                        nn_uint num_param,
+                                        const char **keys,
+                                        const char **vals,
+                                        SymbolHandle *out);
+/*!
+ * \brief Create a Variable Symbol.
+ * \param name name of the variable
+ * \param out pointer to the created symbol handle
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNSymbolCreateVariable(const char *name, SymbolHandle *out);
+/*!
+ * \brief Create a Symbol by grouping list of symbols together
+ * \param num_symbols number of symbols to be grouped
+ * \param symbols array of symbol handles
+ * \param out pointer to the created symbol handle
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNSymbolCreateGroup(nn_uint num_symbols,
+                                 SymbolHandle *symbols,
+                                 SymbolHandle *out);
+/*!
+ * \brief Free the symbol handle.
+ * \param symbol the symbol
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNSymbolFree(SymbolHandle symbol);
+/*!
+ * \brief Copy the symbol to another handle
+ * \param symbol the source symbol
+ * \param out used to hold the result of copy
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNSymbolCopy(SymbolHandle symbol, SymbolHandle *out);
+/*!
+ * \brief Print the content of symbol, used for debug.
+ * \param symbol the symbol
+ * \param out_str pointer to hold the output string of the printing.
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNSymbolPrint(SymbolHandle symbol, const char **out_str);
+
+/*!
+ * \brief Set string attribute from symbol.
+ *  NOTE: Setting attribute to a symbol can affect the semantics(mutable/immutable) of symbolic graph.
+ *
+ *  Safe recommendaton: use  immutable graph
+ *  - Only allow set attributes during creation of new symbol as optional parameter
+ *
+ *  Mutable graph (be careful about the semantics):
+ *  - Allow set attr at any point.
+ *  - Mutating an attribute of some common node of two graphs can cause confusion from user.
+ *
+ * \param symbol the source symbol
+ * \param num_param Number of parameters to set.
+ * \param keys The keys of the attribute
+ * \param values The value to be set
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNSymbolSetAttrs(SymbolHandle symbol,
+                              nn_uint num_param,
+                              const char** keys,
+                              const char** values);
+/*!
+ * \brief Get all attributes from symbol, including all descendents.
+ * \param symbol the source symbol
+ * \param recursive_option 0 for recursive, 1 for shallow.
+ * \param out_size The number of output attributes
+ * \param out 2*out_size strings representing key value pairs.
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNSymbolListAttrs(SymbolHandle symbol,
+                               int recursive_option,
+                               nn_uint *out_size,
+                               const char*** out);
+/*!
+ * \brief List arguments in the symbol.
+ * \param symbol the symbol
+ * \param out_size output size
+ * \param out_str_array pointer to hold the output string array
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNSymbolListArguments(SymbolHandle symbol,
+                                   nn_uint *out_size,
+                                   const char ***out_str_array);
+/*!
+ * \brief List returns in the symbol.
+ * \param symbol the symbol
+ * \param out_size output size
+ * \param out_str_array pointer to hold the output string array
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNSymbolListOutputs(SymbolHandle symbol,
+                                 nn_uint *out_size,
+                                 const char ***out_str_array);
+/*!
+ * \brief Get a symbol that contains all the internals.
+ * \param symbol The symbol
+ * \param out The output symbol whose outputs are all the internals.
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNSymbolGetInternals(SymbolHandle symbol,
+                                  SymbolHandle *out);
+/*!
+ * \brief Get index-th outputs of the symbol.
+ * \param symbol The symbol
+ * \param index the Index of the output.
+ * \param out The output symbol whose outputs are the index-th symbol.
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNSymbolGetOutput(SymbolHandle symbol,
+                               nn_uint index,
+                               SymbolHandle *out);
+
+/*!
+ * \brief Compose the symbol on other symbols.
+ *
+ *  This function will change the sym hanlde.
+ *  To achieve function apply behavior, copy the symbol first
+ *  before apply.
+ *
+ * \param sym the symbol to apply
+ * \param name the name of symbol
+ * \param num_args number of arguments
+ * \param keys the key of keyword args (optional)
+ * \param args arguments to sym
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNSymbolCompose(SymbolHandle sym,
+                             const char* name,
+                             nn_uint num_args,
+                             const char** keys,
+                             SymbolHandle* args);
+
+#endif  // NNVM_C_API_H_
diff --git a/nnvm/include/nnvm/op.h b/nnvm/include/nnvm/op.h
index bcbd4d5cb939..516361f208d8 100644
--- a/nnvm/include/nnvm/op.h
+++ b/nnvm/include/nnvm/op.h
@@ -75,7 +75,10 @@ class Op {
  public:
   /*! \brief name of the operator */
   std::string name;
-  /*! \brief detailed description of the operator */
+  /*!
+   * \brief detailed description of the operator
+   *  This can be used to generate docstring automatically for the operator.
+   */
   std::string description;
   /*!
    * \brief number of inputs to the operator,
@@ -339,7 +342,7 @@ inline Op& Op::set_num_inputs(uint32_t n) {  // NOLINT(*)
   return *this;
 }
 
-inline Op& Op::set_num_inputs(uint32_t (*fn)(const NodeAttrs&)) {  // NOLINT(*)
+inline Op& Op::set_num_inputs(uint32_t (*fn)(const NodeAttrs& attr)) {  // NOLINT(*)
   this->get_num_inputs = fn;
   return *this;
 }
@@ -349,7 +352,7 @@ inline Op& Op::set_num_outputs(uint32_t n) {  // NOLINT(*)
   return *this;
 }
 
-inline Op& Op::set_num_outputs(uint32_t (*fn)(const NodeAttrs&)) {  // NOLINT(*)
+inline Op& Op::set_num_outputs(uint32_t (*fn)(const NodeAttrs& attr)) {  // NOLINT(*)
   this->get_num_outputs = fn;
   return *this;
 }
diff --git a/nnvm/include/nnvm/symbolic.h b/nnvm/include/nnvm/symbolic.h
index cba8c3bf8473..1c82b86096ca 100644
--- a/nnvm/include/nnvm/symbolic.h
+++ b/nnvm/include/nnvm/symbolic.h
@@ -26,9 +26,9 @@ class Symbol {
   /*! \brief option passed to ListAttr */
   enum ListAttrOption {
     /*! \brief recursively list all attributes */
-    kRecursive,
+    kRecursive = 0,
     /*! \brief only list attributes in current node */
-    kShallow
+    kShallow = 1
   };
 
   /*! \brief output entries contained in the symbol */
@@ -69,7 +69,7 @@ class Symbol {
    *
    * The rest of the symbols will remain the same name.
    *
-   * \param positional arguments
+   * \param args positional arguments
    * \param kwargs keyword arguments for the symbol
    * \param name name of returned symbol.
    */
@@ -108,8 +108,7 @@ class Symbol {
    *
    *  This function mutate the node's symbol and is not recommended.
    *
-   * \param key the key of the attribute
-   * \param value the value of the attribute.
+   * \param attrs The attributes to set.
    */
   void SetAttrs(const std::vector<std::pair<std::string, std::string> >& attrs);
   /*!
@@ -119,16 +118,15 @@ class Symbol {
    *   The name of symbol will be pre-pended to each key.
    * \return The created attribute.
    */
-  std::unordered_map<std::string, std::string> ListAttr(ListAttrOption option) const;
+  std::unordered_map<std::string, std::string> ListAttrs(ListAttrOption option) const;
   /*!
    * \brief create symbolic functor(AtomicSymbol) by given operator and attributes.
-   * \param op_name The name of the operator.
+   * \param op The operator.
    * \param attrs The additional attributes.
-   *
    * \return Symbol that can be used to call compose further.
    */
-  static Symbol CreateFunctor(const std::string& op_name,
-                              const std::unordered_map<std::string, std::string>& attrs);
+  static Symbol CreateFunctor(const Op* op,
+                              std::unordered_map<std::string, std::string>&& attrs);
   /*!
    * \brief create variable symbol node
    * \param name name of the variable
diff --git a/nnvm/src/c_api/c_api_common.h b/nnvm/src/c_api/c_api_common.h
new file mode 100644
index 000000000000..170ceb2e58d3
--- /dev/null
+++ b/nnvm/src/c_api/c_api_common.h
@@ -0,0 +1,59 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file c_api_error.h
+ * \brief Common fields of all C APIs
+ */
+#ifndef NNVM_C_API_C_API_COMMON_H_
+#define NNVM_C_API_C_API_COMMON_H_
+
+#include <dmlc/base.h>
+#include <dmlc/logging.h>
+#include <dmlc/thread_local.h>
+#include <nnvm/c_api.h>
+#include <vector>
+#include <string>
+
+/*! \brief  macro to guard beginning and end section of all functions */
+#define API_BEGIN() try {
+/*! \brief every function starts with API_BEGIN();
+     and finishes with API_END() or API_END_HANDLE_ERROR */
+#define API_END() } catch(dmlc::Error &_except_) { return NNAPIHandleException(_except_); } return 0;  // NOLINT(*)
+/*!
+ * \brief every function starts with API_BEGIN();
+ *   and finishes with API_END() or API_END_HANDLE_ERROR
+ *   The finally clause contains procedure to cleanup states when an error happens.
+ */
+#define API_END_HANDLE_ERROR(Finalize) } catch(dmlc::Error &_except_) { Finalize; return NNAPIHandleException(_except_); } return 0; // NOLINT(*)
+
+
+/*! \brief entry to to easily hold returning information */
+struct NNAPIThreadLocalEntry {
+  /*! \brief result holder for returning string */
+  std::string ret_str;
+  /*! \brief result holder for returning strings */
+  std::vector<std::string> ret_vec_str;
+  /*! \brief result holder for returning string pointers */
+  std::vector<const char *> ret_vec_charp;
+  /*! \brief result holder for returning handles */
+  std::vector<void *> ret_handles;
+};
+
+/*! \brief Thread local store that can be used to hold return values. */
+typedef dmlc::ThreadLocalStore<NNAPIThreadLocalEntry> NNAPIThreadLocalStore;
+
+/*!
+ * \brief Set the last error message needed by C API
+ * \param msg The error message to set.
+ */
+void NNAPISetLastError(const char* msg);
+/*!
+ * \brief handle exception throwed out
+ * \param e the exception
+ * \return the return value of API after exception is handled
+ */
+inline int NNAPIHandleException(const dmlc::Error &e) {
+  NNAPISetLastError(e.what());
+  return -1;
+}
+
+#endif  // NNVM_C_API_C_API_COMMON_H_
diff --git a/nnvm/src/c_api/c_api_error.cc b/nnvm/src/c_api/c_api_error.cc
new file mode 100644
index 000000000000..399268667ddd
--- /dev/null
+++ b/nnvm/src/c_api/c_api_error.cc
@@ -0,0 +1,21 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file c_api_error.cc
+ * \brief C error handling
+ */
+#include <dmlc/thread_local.h>
+#include "./c_api_common.h"
+
+struct ErrorEntry {
+  std::string last_error;
+};
+
+typedef dmlc::ThreadLocalStore<ErrorEntry> NNAPIErrorStore;
+
+const char *NNGetLastError() {
+  return NNAPIErrorStore::Get()->last_error.c_str();
+}
+
+void NNAPISetLastError(const char* msg) {
+  NNAPIErrorStore::Get()->last_error = msg;
+}
diff --git a/nnvm/src/c_api/c_api_symbolic.cc b/nnvm/src/c_api/c_api_symbolic.cc
new file mode 100644
index 000000000000..c023ee4a8186
--- /dev/null
+++ b/nnvm/src/c_api/c_api_symbolic.cc
@@ -0,0 +1,222 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file c_api_symbolic.cc
+ * \brief C API related to symbolic graph compsition.
+ */
+#include <dmlc/logging.h>
+#include <nnvm/c_api.h>
+#include <nnvm/op.h>
+#include <nnvm/symbolic.h>
+#include "./c_api_common.h"
+
+using namespace nnvm;
+
+int NNSymbolListAtomicSymbolCreators(nn_uint *out_size,
+                                     AtomicSymbolCreator **out_array) {
+  API_BEGIN();
+  auto &vec = dmlc::Registry<Op>::List();
+  *out_size = static_cast<nn_uint>(vec.size());
+  *out_array = (AtomicSymbolCreator*)(dmlc::BeginPtr(vec));  //  NOLINT(*)
+  API_END();
+}
+
+
+int NNSymbolGetAtomicSymbolInfo(AtomicSymbolCreator creator,
+                                const char **name,
+                                const char **description,
+                                nn_uint *num_doc_args,
+                                const char ***arg_names,
+                                const char ***arg_type_infos,
+                                const char ***arg_descriptions,
+                                const char **return_type) {
+  const Op *op = static_cast<const Op *>(creator);
+
+  API_BEGIN();
+  *name = op->name.c_str();
+  *description = op->description.c_str();
+  *num_doc_args = 0;
+  API_END();
+}
+
+
+int NNSymbolCreateAtomicSymbol(AtomicSymbolCreator creator,
+                               nn_uint num_param,
+                               const char **keys,
+                               const char **vals,
+                               SymbolHandle *out) {
+  Symbol *s = new Symbol();
+  API_BEGIN();
+  const Op* op = static_cast<const Op*>(creator);
+  std::unordered_map<std::string, std::string> kwargs;
+  for (nn_uint i = 0; i < num_param; ++i) {
+    kwargs.insert({std::string(keys[i]), std::string(vals[i])});
+  }
+  *s = Symbol::CreateFunctor(op, std::move(kwargs));
+  *out = s;
+  API_END_HANDLE_ERROR(delete s;);
+}
+
+int NNSymbolCreateVariable(const char *name, SymbolHandle *out) {
+  Symbol *s = new Symbol();
+  API_BEGIN();
+  *s = Symbol::CreateVariable(name);
+  *out = s;
+  API_END_HANDLE_ERROR(delete s);
+}
+
+int NNSymbolCreateGroup(nn_uint num_symbols,
+                        SymbolHandle *symbols,
+                        SymbolHandle *out) {
+  Symbol *s = new Symbol();
+  Symbol **sym_arr = (Symbol**)symbols; // NOLINT(*)
+  API_BEGIN();
+  std::vector<Symbol> syms;
+  for (nn_uint i = 0; i < num_symbols; ++i) {
+    syms.push_back(*sym_arr[i]);
+  }
+  *s = Symbol::CreateGroup(syms);
+  *out = s;
+  API_END_HANDLE_ERROR(delete s);
+}
+
+int NNSymbolGetOutput(SymbolHandle symbol,
+                      nn_uint index,
+                      SymbolHandle *out) {
+  Symbol *s = new Symbol();
+  API_BEGIN();
+  *s = (*static_cast<Symbol*>(symbol))[index];
+  *out = s;
+  API_END_HANDLE_ERROR(delete s);
+}
+
+int NNSymbolGetInternals(SymbolHandle symbol,
+                         SymbolHandle *out) {
+  Symbol *s = new Symbol();
+  API_BEGIN();
+  *s = static_cast<Symbol*>(symbol)->GetInternals();
+  *out = s;
+  API_END_HANDLE_ERROR(delete s);
+}
+
+int NNSymbolFree(SymbolHandle symbol) {
+  API_BEGIN();
+  delete static_cast<Symbol*>(symbol);
+  API_END();
+}
+
+int NNSymbolCopy(SymbolHandle symbol, SymbolHandle *out) {
+  Symbol *s = new Symbol();
+  API_BEGIN();
+  *s = static_cast<const Symbol*>(symbol)->Copy();
+  *out = s;
+  API_END_HANDLE_ERROR(delete s);
+}
+
+int NNSymbolPrint(SymbolHandle symbol, const char **out_str) {
+  Symbol *s = static_cast<Symbol*>(symbol);
+  NNAPIThreadLocalEntry *ret = NNAPIThreadLocalStore::Get();
+  API_BEGIN();
+  std::ostringstream os;
+  s->Print(os);
+  ret->ret_str = os.str();
+  *out_str = (ret->ret_str).c_str();
+  API_END();
+}
+
+int MXSymbolSetAttrs(SymbolHandle symbol,
+                     nn_uint num_param,
+                     const char** keys,
+                     const char** vals) {
+  Symbol *s = static_cast<Symbol*>(symbol);
+  API_BEGIN();
+  std::vector<std::pair<std::string, std::string> > kwargs;
+  for (nn_uint i = 0; i < num_param; ++i) {
+    kwargs.emplace_back(
+        std::make_pair(std::string(keys[i]), std::string(vals[i])));
+  }
+  s->SetAttrs(kwargs);
+  API_END();
+}
+
+int NNSymbolListAttrs(SymbolHandle symbol,
+                      int option,
+                      nn_uint *out_size,
+                      const char*** out) {
+  Symbol *s = static_cast<Symbol*>(symbol);
+  NNAPIThreadLocalEntry *ret = NNAPIThreadLocalStore::Get();
+  API_BEGIN();
+  std::unordered_map<std::string, std::string> attr =
+      std::move(s->ListAttrs(static_cast<Symbol::ListAttrOption>(option)));  // NOLINT(*)
+
+  std::vector<std::string>& attr_list = ret->ret_vec_str;
+  attr_list.clear();
+  for (const auto& kv : attr) {
+    attr_list.push_back(kv.first);
+    attr_list.push_back(kv.second);
+  }
+  *out_size = attr.size();
+  ret->ret_vec_charp.clear();
+  for (size_t i = 0; i < ret->ret_vec_str.size(); ++i) {
+    ret->ret_vec_charp.push_back(ret->ret_vec_str[i].c_str());
+  }
+  *out = dmlc::BeginPtr(ret->ret_vec_charp);
+  API_END();
+}
+
+int NNSymbolListArguments(SymbolHandle symbol,
+                          nn_uint *out_size,
+                          const char ***out_str_array) {
+  Symbol *s = static_cast<Symbol*>(symbol);
+  NNAPIThreadLocalEntry *ret = NNAPIThreadLocalStore::Get();
+  API_BEGIN();
+  ret->ret_vec_str = std::move(s->ListArguments());
+  ret->ret_vec_charp.clear();
+  for (size_t i = 0; i < ret->ret_vec_str.size(); ++i) {
+    ret->ret_vec_charp.push_back(ret->ret_vec_str[i].c_str());
+  }
+  *out_size = static_cast<nn_uint>(ret->ret_vec_charp.size());
+  *out_str_array = dmlc::BeginPtr(ret->ret_vec_charp);
+  API_END();
+}
+
+int NNSymbolListOutputs(SymbolHandle symbol,
+                        nn_uint *out_size,
+                        const char ***out_str_array) {
+  Symbol *s = static_cast<Symbol*>(symbol);
+  NNAPIThreadLocalEntry *ret = NNAPIThreadLocalStore::Get();
+  API_BEGIN();
+  ret->ret_vec_str = std::move(s->ListOutputs());
+  ret->ret_vec_charp.clear();
+  for (size_t i = 0; i < ret->ret_vec_str.size(); ++i) {
+    ret->ret_vec_charp.push_back(ret->ret_vec_str[i].c_str());
+  }
+  *out_size = static_cast<nn_uint>(ret->ret_vec_charp.size());
+  *out_str_array = dmlc::BeginPtr(ret->ret_vec_charp);
+  API_END();
+}
+
+int NNSymbolCompose(SymbolHandle sym,
+                    const char *name,
+                    nn_uint num_args,
+                    const char** keys,
+                    SymbolHandle* args) {
+  API_BEGIN();
+  std::string s_name;
+  if (name != nullptr) s_name = name;
+
+  Symbol* s = static_cast<Symbol*>(sym);
+  if (keys == nullptr && num_args != 0) {
+    std::vector<Symbol> pos_args;
+    for (nn_uint i = 0; i < num_args; ++i) {
+      pos_args.push_back(*((Symbol*)args[i]));  //  NOLINT(*)
+    }
+    s->Compose(pos_args, {}, s_name);
+  } else {
+    std::unordered_map<std::string, Symbol> kwargs;
+    for (nn_uint i = 0; i < num_args; ++i) {
+      kwargs[keys[i]] = *((Symbol*)args[i]);  //  NOLINT(*)
+    }
+    s->Compose({}, kwargs, s_name);
+  }
+  API_END();
+}
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index 03a6f065b008..a6e70b29da52 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -322,7 +322,7 @@ void Symbol::SetAttrs(const std::vector<std::pair<std::string, std::string> >& a
   }
 }
 
-std::unordered_map<std::string, std::string> Symbol::ListAttr(ListAttrOption option) const {
+std::unordered_map<std::string, std::string> Symbol::ListAttrs(ListAttrOption option) const {
   if (option == kRecursive) {
     std::unordered_map<std::string, std::string> ret;
     DFSVisit(this->outputs, [&ret](const std::shared_ptr<Node>& n) {
@@ -336,12 +336,12 @@ std::unordered_map<std::string, std::string> Symbol::ListAttr(ListAttrOption opt
   }
 }
 
-Symbol Symbol::CreateFunctor(const std::string& op_name,
-                             const std::unordered_map<std::string, std::string>& attrs) {
+Symbol Symbol::CreateFunctor(const Op* op,
+                             std::unordered_map<std::string, std::string>&& attrs) {
   Symbol s;
   std::shared_ptr<Node> n = Node::Create();
-  n->op = Op::Get(op_name);
-  n->attrs.dict = attrs;
+  n->op = op;
+  n->attrs.dict = std::move(attrs);
   if (n->op->attr_parser != nullptr) {
     (*n->op->attr_parser)(&(n->attrs));
   }

From 877d0c268c7722ec09997d7ace28886996422ac8 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 10 Jul 2016 21:06:48 -0700
Subject: [PATCH 333/949] =?UTF-8?q?[PYTHON]=20Check=20in=20a=20symbolic=20?=
 =?UTF-8?q?construction=20interface=20in=20python,=20start=20=E2=80=A6=20(?=
 =?UTF-8?q?#4)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* [PYTHON] Check in a symbolic construction interface in python, start add graph API

* Graph API
---
 nnvm/include/nnvm/c_api.h        |  74 +++++-
 nnvm/include/nnvm/op.h           |   4 +-
 nnvm/include/nnvm/symbolic.h     |   9 +
 nnvm/python/nnvm/__init__.py     |  10 +
 nnvm/python/nnvm/attribute.py    |  62 +++++
 nnvm/python/nnvm/base.py         | 189 +++++++++++++++
 nnvm/python/nnvm/graph.py        | 106 +++++++++
 nnvm/python/nnvm/libinfo.py      |  41 ++++
 nnvm/python/nnvm/name.py         |  78 +++++++
 nnvm/python/nnvm/symbol.py       | 385 +++++++++++++++++++++++++++++++
 nnvm/src/c_api/c_api_graph.cc    |  77 +++++++
 nnvm/src/c_api/c_api_symbolic.cc |  20 +-
 nnvm/src/core/symbolic.cc        |  39 +++-
 nnvm/src/example/operator.cc     |  17 ++
 nnvm/src/pass/saveload_json.cc   |   5 +
 nnvm/tests/python/test_graph.py  |  14 ++
 nnvm/tests/python/test_symbol.py |  29 +++
 17 files changed, 1143 insertions(+), 16 deletions(-)
 create mode 100644 nnvm/python/nnvm/__init__.py
 create mode 100644 nnvm/python/nnvm/attribute.py
 create mode 100644 nnvm/python/nnvm/base.py
 create mode 100644 nnvm/python/nnvm/graph.py
 create mode 100644 nnvm/python/nnvm/libinfo.py
 create mode 100644 nnvm/python/nnvm/name.py
 create mode 100644 nnvm/python/nnvm/symbol.py
 create mode 100644 nnvm/src/c_api/c_api_graph.cc
 create mode 100644 nnvm/tests/python/test_graph.py
 create mode 100644 nnvm/tests/python/test_symbol.py

diff --git a/nnvm/include/nnvm/c_api.h b/nnvm/include/nnvm/c_api.h
index bade0100c59e..d15ba59b46b2 100644
--- a/nnvm/include/nnvm/c_api.h
+++ b/nnvm/include/nnvm/c_api.h
@@ -30,8 +30,8 @@ typedef unsigned int nn_uint;
 typedef void *AtomicSymbolCreator;
 /*! \brief handle to a symbol that can be bind as operator */
 typedef void *SymbolHandle;
-/*! \brief handle to a AtomicSymbol */
-typedef void *AtomicSymbolHandle;
+/*! \brief handle to Graph */
+typedef void *GraphHandle;
 
 /*!
  * \brief return str message of the last error
@@ -71,7 +71,7 @@ NNVM_DLL int NNSymbolGetAtomicSymbolInfo(AtomicSymbolCreator creator,
                                          const char ***arg_names,
                                          const char ***arg_type_infos,
                                          const char ***arg_descriptions,
-                                         const char **return_type = NULL);
+                                         const char **return_type);
 /*!
  * \brief Create an AtomicSymbol functor.
  * \param creator the AtomicSymbolCreator
@@ -123,7 +123,18 @@ NNVM_DLL int NNSymbolCopy(SymbolHandle symbol, SymbolHandle *out);
  * \return 0 when success, -1 when failure happens
  */
 NNVM_DLL int NNSymbolPrint(SymbolHandle symbol, const char **out_str);
-
+/*!
+ * \brief Get string attribute from symbol
+ * \param symbol the source symbol
+ * \param key The key of the symbol.
+ * \param out The result attribute, can be NULL if the attribute do not exist.
+ * \param success Whether the result is contained in out.
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNSymbolGetAttr(SymbolHandle symbol,
+                             const char* key,
+                             const char** out,
+                             int *success);
 /*!
  * \brief Set string attribute from symbol.
  *  NOTE: Setting attribute to a symbol can affect the semantics(mutable/immutable) of symbolic graph.
@@ -216,4 +227,59 @@ NNVM_DLL int NNSymbolCompose(SymbolHandle sym,
                              const char** keys,
                              SymbolHandle* args);
 
+// Graph IR API
+/*!
+ * \brief create a graph handle from symbol
+ * \param symbol The symbol representing the graph.
+ * \param graph The graph handle created.
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNGraphCreate(SymbolHandle symbol, GraphHandle *graph);
+/*!
+ * \brief free the graph handle
+ * \param handle The handle to be freed.
+ */
+NNVM_DLL int NNGraphFree(GraphHandle handle);
+/*!
+ * \brief Get a new symbol from the graph.
+ * \param graph The graph handle.
+ * \param symbol The corresponding symbol
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNGraphGetSymbol(GraphHandle graph, SymbolHandle *symbol);
+/*!
+ * \brief Get Set a std::string typed attribute to graph.
+ * \param handle The graph handle.
+ * \param key The key to the attribute.
+ * \param value The value to be exposed.
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNGraphSetStrAttr(GraphHandle handle,
+                               const char* key,
+                               const char* value);
+/*!
+ * \brief Get Set a std::string typed attribute from graph attribute.
+ * \param handle The graph handle.
+ * \param key The key to the attribute.
+ * \param out The result attribute, can be NULL if the attribute do not exist.
+ * \param success Whether the result is contained in out.
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNGraphGetStrAttr(SymbolHandle handle,
+                               const char* key,
+                               const char** out,
+                               int *success);
+/*!
+ * \brief Apply pass on the src graph.
+ * \param src The source graph handle.
+ * \param num_pass The number of pass to be applied.
+ * \param pass_names The names of the pass.
+ * \param dst The result graph.
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNGraphApplyPass(GraphHandle src,
+                              nn_uint num_pass,
+                              const char** pass_names,
+                              GraphHandle *dst);
+
 #endif  // NNVM_C_API_H_
diff --git a/nnvm/include/nnvm/op.h b/nnvm/include/nnvm/op.h
index 516361f208d8..a7d2fd5e1e4d 100644
--- a/nnvm/include/nnvm/op.h
+++ b/nnvm/include/nnvm/op.h
@@ -323,10 +323,10 @@ inline Op& Op::attr(  // NOLINT(*)
       vec.resize(index_ + 1,
                  std::make_pair(ValueType(), 0));
       std::pair<ValueType, int>& p = vec[index_];
-      CHECK(p.second == 0 || p.first == value)
+      CHECK(p.second == 0)
           << "Attribute " << attr_name
           << " of operator " << this->name
-          << " is already registered to a different value";
+          << " is already registered.";
           vec[index_] = std::make_pair(value, 1);
     });
   return *this;
diff --git a/nnvm/include/nnvm/symbolic.h b/nnvm/include/nnvm/symbolic.h
index 1c82b86096ca..5f2deaf948a6 100644
--- a/nnvm/include/nnvm/symbolic.h
+++ b/nnvm/include/nnvm/symbolic.h
@@ -111,6 +111,15 @@ class Symbol {
    * \param attrs The attributes to set.
    */
   void SetAttrs(const std::vector<std::pair<std::string, std::string> >& attrs);
+  /*!
+   * \brief Get attributes from the symbol.
+   *  This only works for symbol with outputs from single operators.
+   *  For grouped sybmbol, an error will be raised.
+   * \param key Key of the attribute. When key == "name", it returns the name attirbute.
+   * \param out the output value of the attribute.
+   * \return true if the attribute exists, false if the attribute do not exist.
+   */
+  bool GetAttr(const std::string& key, std::string* out) const;
   /*!
    * \brief Get attribute dictionary from the symbol.
    *  For grouped sybmbol, an error will be raised.
diff --git a/nnvm/python/nnvm/__init__.py b/nnvm/python/nnvm/__init__.py
new file mode 100644
index 000000000000..38f943b9fd6c
--- /dev/null
+++ b/nnvm/python/nnvm/__init__.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# coding: utf-8
+"""NNVM python API for ease of use and help new framework establish python API. """
+from __future__ import absolute_import
+
+from . import base
+from . import symbol as sym
+from . import symbol
+
+__version__ = base.__version__
diff --git a/nnvm/python/nnvm/attribute.py b/nnvm/python/nnvm/attribute.py
new file mode 100644
index 000000000000..b60ea0150492
--- /dev/null
+++ b/nnvm/python/nnvm/attribute.py
@@ -0,0 +1,62 @@
+# coding: utf-8
+"""Attribute scoping support for symbolic API."""
+from __future__ import absolute_import
+
+from .base import string_types
+
+class AttrScope(object):
+    """Attribute manager for scoping.
+
+    User can also inherit this object to change naming behavior.
+
+    Parameters
+    ----------
+    kwargs
+        The attributes to set for all symbol creations in the scope.
+    """
+    current = None
+
+    def __init__(self, **kwargs):
+        self._old_scope = None
+        for value in kwargs.values():
+            if not isinstance(value, string_types):
+                raise ValueError("Attributes need to be string")
+        self._attr = kwargs
+
+    def get(self, attr):
+        """
+        Get the attribute dict given the attribute set by the symbol.
+
+        Parameters
+        ----------
+        attr : dict of string to string
+            The attribute passed in by user during symbol creation.
+
+        Returns
+        -------
+        attr : dict of string to string
+            Updated attributes to add other scope related attributes.
+        """
+        if self._attr:
+            ret = self._attr.copy()
+            if attr:
+                ret.update(attr)
+            return ret
+        else:
+            return attr
+
+    def __enter__(self):
+        # pylint: disable=protected-access
+        self._old_scope = AttrScope.current
+        attr = AttrScope.current._attr.copy()
+        attr.update(self._attr)
+        self._attr = attr
+        AttrScope.current = self
+        return self
+
+    def __exit__(self, ptype, value, trace):
+        assert self._old_scope
+        AttrScope.current = self._old_scope
+
+AttrScope.current = AttrScope()
+
diff --git a/nnvm/python/nnvm/base.py b/nnvm/python/nnvm/base.py
new file mode 100644
index 000000000000..bc3d3b7b6ecf
--- /dev/null
+++ b/nnvm/python/nnvm/base.py
@@ -0,0 +1,189 @@
+# coding: utf-8
+# pylint: disable=invalid-name
+""" ctypes library of nnvm and helper functions """
+from __future__ import absolute_import
+
+import sys
+import ctypes
+import numpy as np
+from . import libinfo
+
+__all__ = ['NNNetError']
+#----------------------------
+# library loading
+#----------------------------
+if sys.version_info[0] == 3:
+    string_types = str,
+    numeric_types = (float, int, np.float32, np.int32)
+    # this function is needed for python3
+    # to convert ctypes.char_p .value back to python str
+    py_str = lambda x: x.decode('utf-8')
+else:
+    string_types = basestring,
+    numeric_types = (float, int, long, np.float32, np.int32)
+    py_str = lambda x: x
+
+
+class NNVMError(Exception):
+    """Error that will be throwed by all nnvm functions"""
+    pass
+
+def _load_lib():
+    """Load libary by searching possible path."""
+    lib_path = libinfo.find_lib_path()
+    lib = ctypes.cdll.LoadLibrary(lib_path[0])
+    # DMatrix functions
+    lib.NNGetLastError.restype = ctypes.c_char_p
+    return lib
+
+# version number
+__version__ = libinfo.__version__
+# library instance of nnvm
+_LIB = _load_lib()
+
+# type definitions
+nn_uint = ctypes.c_uint
+SymbolCreatorHandle = ctypes.c_void_p
+SymbolHandle = ctypes.c_void_p
+GraphHandle = ctypes.c_void_p
+
+#----------------------------
+# helper function definition
+#----------------------------
+def check_call(ret):
+    """Check the return value of C API call
+
+    This function will raise exception when error occurs.
+    Wrap every API call with this function
+
+    Parameters
+    ----------
+    ret : int
+        return value from API calls
+    """
+    if ret != 0:
+        raise NNVMError(py_str(_LIB.NNGetLastError()))
+
+def c_str(string):
+    """Create ctypes char * from a python string
+    Parameters
+    ----------
+    string : string type
+        python string
+
+    Returns
+    -------
+    str : c_char_p
+        A char pointer that can be passed to C API
+    """
+    return ctypes.c_char_p(string.encode('utf-8'))
+
+
+def c_array(ctype, values):
+    """Create ctypes array from a python array
+
+    Parameters
+    ----------
+    ctype : ctypes data type
+        data type of the array we want to convert to
+
+    values : tuple or list
+        data content
+
+    Returns
+    -------
+    out : ctypes array
+        Created ctypes array
+    """
+    return (ctype * len(values))(*values)
+
+def ctypes2buffer(cptr, length):
+    """Convert ctypes pointer to buffer type.
+
+    Parameters
+    ----------
+    cptr : ctypes.POINTER(ctypes.c_char)
+        pointer to the raw memory region
+    length : int
+        the length of the buffer
+
+    Returns
+    -------
+    buffer : bytearray
+        The raw byte memory buffer
+    """
+    if not isinstance(cptr, ctypes.POINTER(ctypes.c_char)):
+        raise TypeError('expected char pointer')
+    res = bytearray(length)
+    rptr = (ctypes.c_char * length).from_buffer(res)
+    if not ctypes.memmove(rptr, cptr, length):
+        raise RuntimeError('memmove failed')
+    return res
+
+def ctypes2numpy_shared(cptr, shape):
+    """Convert a ctypes pointer to a numpy array
+
+    The result numpy array shares the memory with the pointer
+
+    Parameters
+    ----------
+    cptr : ctypes.POINTER(mx_float)
+        pointer to the memory region
+
+    shape : tuple
+        shape of target ndarray
+
+    Returns
+    -------
+    out : numpy_array
+        A numpy array : numpy array
+    """
+    if not isinstance(cptr, ctypes.POINTER(mx_float)):
+        raise RuntimeError('expected float pointer')
+    size = 1
+    for s in shape:
+        size *= s
+    dbuffer = (mx_float * size).from_address(ctypes.addressof(cptr.contents))
+    return np.frombuffer(dbuffer, dtype=np.float32).reshape(shape)
+
+
+def ctypes2docstring(num_args, arg_names, arg_types, arg_descs, remove_dup=True):
+    """Convert ctypes returned doc string information into parameters docstring.
+
+    num_args : nn_uint
+        Number of arguments.
+
+    arg_names : ctypes.POINTER(ctypes.c_char_p)
+        Argument names.
+
+    arg_types : ctypes.POINTER(ctypes.c_char_p)
+        Argument type information.
+
+    arg_descs : ctypes.POINTER(ctypes.c_char_p)
+        Argument description information.
+
+    remove_dup : boolean, optional
+        Whether remove duplication or not.
+
+    Returns
+    -------
+    docstr : str
+        Python docstring of parameter sections.
+    """
+    param_keys = set()
+    param_str = []
+    for i in range(num_args.value):
+        key = py_str(arg_names[i])
+        if key in param_keys and remove_dup:
+            continue
+        param_keys.add(key)
+        type_info = py_str(arg_types[i])
+        ret = '%s : %s' % (key, type_info)
+        if len(arg_descs[i]) != 0:
+            ret += '\n    ' + py_str(arg_descs[i])
+        param_str.append(ret)
+    doc_str = ('Parameters\n' +
+               '----------\n' +
+               '%s\n')
+    doc_str = doc_str % ('\n'.join(param_str))
+    return doc_str
diff --git a/nnvm/python/nnvm/graph.py b/nnvm/python/nnvm/graph.py
new file mode 100644
index 000000000000..c09f98173ea4
--- /dev/null
+++ b/nnvm/python/nnvm/graph.py
@@ -0,0 +1,106 @@
+# coding: utf-8
+# pylint: disable=invalid-name, protected-access, too-many-arguments, too-many-lines
+"""Symbolic configuration API."""
+from __future__ import absolute_import as _abs
+
+import ctypes
+import sys
+from .base import _LIB
+from .base import c_array, c_str, nn_uint, py_str, string_types
+from .base import GraphHandle, SymbolHandle
+from .base import check_call
+from .symbol import Symbol
+
+class Graph(object):
+    """Graph is the graph object that can be used to apply optimization pass.
+    It contains additional graphwise attribute besides the internal symbol.
+
+    """
+
+    # pylint: disable=no-member
+    def __init__(self, handle):
+        """Initialize the function with handle
+
+        Parameters
+        ----------
+        handle : GraphHandle
+            the handle to the underlying C++ Graph
+        """
+        self.handle = handle
+
+    def __del__(self):
+        check_call(_LIB.NNGraphFree(self.handle))
+
+    def attr(self, key):
+        """Get attribute string from the graph.
+
+        Parameters
+        ----------
+        key : str
+            The key to get attribute from.
+
+        Returns
+        -------
+        value : str
+            The attribute value of the key, returns None if attribute do not exist.
+        """
+        ret = ctypes.c_char_p()
+        success = ctypes.c_int()
+        check_call(_LIB.NNGraphGetStrAttr(
+            self.handle, c_str(key), ctypes.byref(ret), ctypes.byref(success)))
+        if success.value != 0:
+            return py_str(ret.value)
+        else:
+            return None
+
+    def _set_attr(self, **kwargs):
+        """Set the attribute of the symbol.
+
+        Parameters
+        ----------
+        **kwargs
+            The attributes to set
+        """
+        for k, v in kwargs.items():
+            check_call(_LIB.NNGraphSetStrAttr(
+                self.handle, c_str(k), c_str(v)))
+
+    @property
+    def symbol(self):
+        shandle = SymbolHandle()
+        check_call(_LIB.NNGraphGetSymbol(self.handle, ctypes.byref(shandle)))
+        return Symbol(shandle)
+
+    def apply(self, passes):
+        """Apply passes to the graph
+
+        Parameters
+        ----------
+
+        """
+        if isinstance(passes, string_types):
+            passes = [passes]
+        cpass = c_array(ctypes.c_char_p, [c_str(key) for key in passes])
+        ghandle = GraphHandle()
+        npass = nn_uint(len(passes))
+        check_call(_LIB.NNGraphApplyPass(self.handle, npass, cpass, ctypes.byref(ghandle)))
+        return Graph(ghandle)
+
+
+def create(symbol):
+    """Create a new graph from symbol.
+
+    Parameters
+    ----------
+    symbol : Symbol
+        The symbolic graph used to create Graph object.
+
+    Returns
+    -------
+    graph : Graph
+        A generated new graph object.
+    """
+    ghandle = GraphHandle()
+    check_call(_LIB.NNGraphCreate(
+        symbol.handle, ctypes.byref(ghandle)))
+    return Graph(ghandle)
diff --git a/nnvm/python/nnvm/libinfo.py b/nnvm/python/nnvm/libinfo.py
new file mode 100644
index 000000000000..664847052f7b
--- /dev/null
+++ b/nnvm/python/nnvm/libinfo.py
@@ -0,0 +1,41 @@
+# coding: utf-8
+"""Information about nnvm."""
+from __future__ import absolute_import
+import os
+import platform
+
+def find_lib_path():
+    """Find NNNet dynamic library files.
+
+    Returns
+    -------
+    lib_path : list(string)
+        List of all found path to the libraries
+    """
+    curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
+    api_path = os.path.join(curr_path, '../../lib/')
+    cmake_build_path = os.path.join(curr_path, '../../build/Release/')
+    dll_path = [curr_path, api_path, cmake_build_path]
+    if os.name == 'nt':
+        vs_configuration = 'Release'
+        if platform.architecture()[0] == '64bit':
+            dll_path.append(os.path.join(curr_path, '../../build', vs_configuration))
+            dll_path.append(os.path.join(curr_path, '../../windows/x64', vs_configuration))
+        else:
+            dll_path.append(os.path.join(curr_path, '../../build', vs_configuration))
+            dll_path.append(os.path.join(curr_path, '../../windows', vs_configuration))
+    elif os.name == "posix" and os.environ.get('LD_LIBRARY_PATH', None):
+        dll_path.extend([p.strip() for p in os.environ['LD_LIBRARY_PATH'].split(":")])
+    if os.name == 'nt':
+        dll_path = [os.path.join(p, 'libnnvm.dll') for p in dll_path]
+    else:
+        dll_path = [os.path.join(p, 'libnnvm.so') for p in dll_path]
+    lib_path = [p for p in dll_path if os.path.exists(p) and os.path.isfile(p)]
+    if len(lib_path) == 0:
+        raise RuntimeError('Cannot find the files.\n' +
+                           'List of candidates:\n' + str('\n'.join(dll_path)))
+    return lib_path
+
+
+# current version
+__version__ = "0.7.0"
diff --git a/nnvm/python/nnvm/name.py b/nnvm/python/nnvm/name.py
new file mode 100644
index 000000000000..7e50e9e5070d
--- /dev/null
+++ b/nnvm/python/nnvm/name.py
@@ -0,0 +1,78 @@
+# coding: utf-8
+"""Automatic naming support for symbolic API."""
+from __future__ import absolute_import
+
+class NameManager(object):
+    """NameManager to do automatic naming.
+
+    User can also inherit this object to change naming behavior.
+    """
+    current = None
+
+    def __init__(self):
+        self._counter = {}
+        self._old_manager = None
+
+    def get(self, name, hint):
+        """Get the canonical name for a symbol.
+
+        This is default implementation.
+        When user specified a name,
+        the user specified name will be used.
+
+        When user did not, we will automatically generate a
+        name based on hint string.
+
+        Parameters
+        ----------
+        name : str or None
+            The name user specified.
+
+        hint : str
+            A hint string, which can be used to generate name.
+
+        Returns
+        -------
+        full_name : str
+            A canonical name for the user.
+        """
+        if name:
+            return name
+        if hint not in self._counter:
+            self._counter[hint] = 0
+        name = '%s%d' % (hint, self._counter[hint])
+        self._counter[hint] += 1
+        return name
+
+    def __enter__(self):
+        self._old_manager = NameManager.current
+        NameManager.current = self
+        return self
+
+    def __exit__(self, ptype, value, trace):
+        assert self._old_manager
+        NameManager.current = self._old_manager
+
+
+class Prefix(NameManager):
+    """A name manager that always attach a prefix to all names.
+
+    Examples
+    --------
+    >>> import nnvm as nn
+    >>> data = nn.symbol.Variable('data')
+    >>> with nn.name.Prefix('mynet_'):
+            net = nn.symbol.FullyConnected(data, num_hidden=10, name='fc1')
+    >>> net.list_arguments()
+    ['data', 'mynet_fc1_weight', 'mynet_fc1_bias']
+    """
+    def __init__(self, prefix):
+        super(Prefix, self).__init__()
+        self._prefix = prefix
+
+    def get(self, name, hint):
+        name = super(Prefix, self).get(name, hint)
+        return self._prefix + name
+
+# initialize the default name manager
+NameManager.current = NameManager()
diff --git a/nnvm/python/nnvm/symbol.py b/nnvm/python/nnvm/symbol.py
new file mode 100644
index 000000000000..bf9b57ab4205
--- /dev/null
+++ b/nnvm/python/nnvm/symbol.py
@@ -0,0 +1,385 @@
+# coding: utf-8
+# pylint: disable=invalid-name, protected-access, too-many-arguments, too-many-lines
+"""Symbolic configuration API."""
+from __future__ import absolute_import as _abs
+
+import copy
+import ctypes
+import sys
+from .base import _LIB
+from .base import c_array, c_str, nn_uint, py_str, string_types
+from .base import SymbolHandle
+from .base import check_call, ctypes2docstring
+from .name import NameManager
+from .attribute import AttrScope
+
+__all__ = ["Symbol", "Variable"]
+
+class Symbol(object):
+    """Symbol is symbolic graph."""
+
+    # pylint: disable=no-member
+    def __init__(self, handle):
+        """Initialize the function with handle
+
+        Parameters
+        ----------
+        handle : SymbolHandle
+            the handle to the underlying C++ Symbol
+        """
+        self.handle = handle
+
+    def __del__(self):
+        check_call(_LIB.NNSymbolFree(self.handle))
+
+    def __copy__(self):
+        return copy.deepcopy(self)
+
+    def __deepcopy__(self, _):
+        handle = SymbolHandle()
+        check_call(_LIB.NNSymbolCopy(self.handle,
+                                     ctypes.byref(handle)))
+        return Symbol(handle)
+
+    def __call__(self, *args, **kwargs):
+        """Invoke symbol as function on inputs.
+
+        Parameters
+        ----------
+        args:
+            provide positional arguments
+
+        kwargs:
+            provide keyword arguments
+        Returns
+        -------
+        the resulting symbol
+        """
+        s = copy.deepcopy(self)
+        s._compose(*args, **kwargs)
+        return s
+
+    def _compose(self, *args, **kwargs):
+        """Compose symbol on inputs.
+
+        This call mutates the current symbol.
+
+        Parameters
+        ----------
+        args:
+            provide positional arguments
+
+        kwargs:
+            provide keyword arguments
+
+        Returns
+        -------
+        the resulting symbol
+        """
+        name = kwargs.pop('name', None)
+
+        if name:
+            name = c_str(name)
+        if len(args) != 0 and len(kwargs) != 0:
+            raise TypeError('compose only accept input Symbols \
+                either as positional or keyword arguments, not both')
+
+        for arg in args:
+            if not isinstance(arg, Symbol):
+                raise TypeError('Compose expect `Symbol` as arguments')
+        for val in kwargs.values():
+            if not isinstance(val, Symbol):
+                raise TypeError('Compose expect `Symbol` as arguments')
+
+        num_args = len(args) + len(kwargs)
+        if len(kwargs) != 0:
+            keys = c_array(ctypes.c_char_p, [c_str(key) for key in kwargs.keys()])
+            args = c_array(SymbolHandle, [s.handle for s in kwargs.values()])
+        else:
+            keys = None
+            args = c_array(SymbolHandle, [s.handle for s in args])
+        check_call(_LIB.NNSymbolCompose(
+            self.handle, name, num_args, keys, args))
+
+    def __getitem__(self, index):
+        if isinstance(index, string_types):
+            idx = None
+            for i, name in enumerate(self.list_outputs()):
+                if name == index:
+                    if idx is not None:
+                        raise ValueError('There are multiple outputs with name \"%s\"' % index)
+                    idx = i
+            if idx is None:
+                raise ValueError('Cannot find output that matches name \"%s\"' % index)
+            index = idx
+        if not isinstance(index, int):
+            raise TypeError('Symbol only support integer index to fetch i-th output')
+        handle = SymbolHandle()
+        check_call(_LIB.NNSymbolGetOutput(
+            self.handle, nn_uint(index), ctypes.byref(handle)))
+        return Symbol(handle=handle)
+
+    def attr(self, key):
+        """Get attribute string from the symbol, this function only works for non-grouped symbol.
+
+        Parameters
+        ----------
+        key : str
+            The key to get attribute from.
+
+        Returns
+        -------
+        value : str
+            The attribute value of the key, returns None if attribute do not exist.
+        """
+        ret = ctypes.c_char_p()
+        success = ctypes.c_int()
+        check_call(_LIB.NNSymbolGetAttr(
+            self.handle, c_str(key), ctypes.byref(ret), ctypes.byref(success)))
+        if success.value != 0:
+            return py_str(ret.value)
+        else:
+            return None
+
+    def list_attr(self, recursive=False):
+        """Get all attributes from the symbol.
+
+        Parameters
+        ----------
+        recursive : bool
+            Default `False`. When `recursive` is `True`, list recursively all the
+            attributes in the descendents. The attribute names are pre-pended with
+            the symbol names to avoid conflicts. If `False`, then only attributes
+            that belongs to this symbol is returned, and the attribute names will
+            **not** be pre-pended with the symbol name.
+        """
+        size = nn_uint()
+        pairs = ctypes.POINTER(ctypes.c_char_p)()
+        option = ctypes.c_int(0) if recursive else ctypes.c_int(1)
+        check_call(_LIB.NNSymbolListAttrs(
+            self.handle, option, ctypes.byref(size), ctypes.byref(pairs)))
+        return {py_str(pairs[i*2]): py_str(pairs[i*2+1]) for i in range(size.value)}
+
+    def _set_attr(self, **kwargs):
+        """Set the attribute of the symbol.
+
+        Parameters
+        ----------
+        **kwargs
+            The attributes to set
+        """
+        keys = c_array(ctypes.c_char_p, [c_str(key) for key in kwargs.keys()])
+        vals = c_array(ctypes.c_char_p, [c_str(str(val)) for val in kwargs.values()])
+        num_args = nn_uint(len(kwargs))
+        check_call(_LIB.NNSymbolSetAttrs(
+            self.handle, num_args, keys, vals))
+
+    def get_internals(self):
+        """Get a new grouped symbol whose output contains all the internal outputs of this symbol.
+
+        Returns
+        -------
+        sgroup : Symbol
+            The internal of the symbol.
+        """
+        handle = SymbolHandle()
+        check_call(_LIB.NNSymbolGetInternals(
+            self.handle, ctypes.byref(handle)))
+        return Symbol(handle=handle)
+
+    def list_arguments(self):
+        """List all the arguments in the symbol.
+
+        Returns
+        -------
+        args : list of string
+            List of all the arguments.
+        """
+        size = ctypes.c_uint()
+        sarr = ctypes.POINTER(ctypes.c_char_p)()
+        check_call(_LIB.NNSymbolListArguments(
+            self.handle, ctypes.byref(size), ctypes.byref(sarr)))
+        return [py_str(sarr[i]) for i in range(size.value)]
+
+    def list_outputs(self):
+        """List all outputs in the symbol.
+
+        Returns
+        -------
+        returns : list of string
+            List of all the outputs.
+        """
+        size = ctypes.c_uint()
+        sarr = ctypes.POINTER(ctypes.c_char_p)()
+        check_call(_LIB.NNSymbolListOutputs(
+            self.handle, ctypes.byref(size), ctypes.byref(sarr)))
+        return [py_str(sarr[i]) for i in range(size.value)]
+
+    def debug_str(self):
+        """Get a debug string.
+
+        Returns
+        -------
+        debug_str : string
+            Debug string of the symbol.
+        """
+        debug_str = ctypes.c_char_p()
+        check_call(_LIB.NNSymbolPrint(
+            self.handle, ctypes.byref(debug_str)))
+        return py_str(debug_str.value)
+
+
+def Variable(name, **kwargs):
+    """Create a symbolic variable with specified name.
+
+    Parameters
+    ----------
+    name : str
+        Name of the variable.
+    kwargs : dict of string -> string
+        Additional attributes to set on the variable.
+
+    Returns
+    -------
+    variable : Symbol
+        The created variable symbol.
+    """
+    if not isinstance(name, string_types):
+        raise TypeError('Expect a string for variable `name`')
+    handle = SymbolHandle()
+    check_call(_LIB.NNSymbolCreateVariable(c_str(name), ctypes.byref(handle)))
+    ret = Symbol(handle)
+    attr = AttrScope.current.get(kwargs)
+    if attr:
+        ret._set_attr(**attr)
+    return ret
+
+
+def Group(symbols):
+    """Create a symbol that groups symbols together.
+
+    Parameters
+    ----------
+    symbols : list
+        List of symbols to be grouped.
+
+    Returns
+    -------
+    sym : Symbol
+        The created group symbol.
+     """
+    ihandles = []
+    for sym in symbols:
+        if not isinstance(sym, Symbol):
+            raise TypeError('Expect Symbols in the list input')
+        ihandles.append(sym.handle)
+    handle = SymbolHandle()
+    check_call(_LIB.NNSymbolCreateGroup(
+        nn_uint(len(ihandles)),
+        c_array(SymbolHandle, ihandles), ctypes.byref(handle)))
+    return Symbol(handle)
+
+
+def _make_atomic_symbol_function(handle):
+    """Create an atomic symbol function by handle and funciton name."""
+    name = ctypes.c_char_p()
+    desc = ctypes.c_char_p()
+    num_args = nn_uint()
+    arg_names = ctypes.POINTER(ctypes.c_char_p)()
+    arg_types = ctypes.POINTER(ctypes.c_char_p)()
+    arg_descs = ctypes.POINTER(ctypes.c_char_p)()
+    ret_type = ctypes.c_char_p()
+
+    check_call(_LIB.NNSymbolGetAtomicSymbolInfo(
+        handle, ctypes.byref(name), ctypes.byref(desc),
+        ctypes.byref(num_args),
+        ctypes.byref(arg_names),
+        ctypes.byref(arg_types),
+        ctypes.byref(arg_descs),
+        ctypes.byref(ret_type)))
+    param_str = ctypes2docstring(num_args, arg_names, arg_types, arg_descs)
+    func_name = py_str(name.value)
+    desc = py_str(desc.value)
+
+    doc_str = ('%s\n\n' +
+               '%s\n' +
+               'name : string, optional.\n' +
+               '    Name of the resulting symbol.\n\n' +
+               'Returns\n' +
+               '-------\n' +
+               'symbol: Symbol\n' +
+               '    The result symbol.')
+    doc_str = doc_str % (desc, param_str)
+
+    def creator(*args, **kwargs):
+        """Activation Operator of Neural Net.
+        The parameters listed below can be passed in as keyword arguments.
+
+        Parameters
+        ----------
+        name : string, required.
+            Name of the resulting symbol.
+
+        Returns
+        -------
+        symbol: Symbol
+            the resulting symbol
+        """
+        param_keys = []
+        param_vals = []
+        symbol_kwargs = {}
+        name = kwargs.pop('name', None)
+        attr = kwargs.pop('attr', None)
+
+        for k, v in kwargs.items():
+            if isinstance(v, Symbol):
+                symbol_kwargs[k] = v
+            else:
+                param_keys.append(c_str(k))
+                param_vals.append(c_str(str(v)))
+        # create atomic symbol
+        param_keys = c_array(ctypes.c_char_p, param_keys)
+        param_vals = c_array(ctypes.c_char_p, param_vals)
+        sym_handle = SymbolHandle()
+        check_call(_LIB.NNSymbolCreateAtomicSymbol(
+            handle,
+            nn_uint(len(param_keys)),
+            param_keys, param_vals,
+            ctypes.byref(sym_handle)))
+
+        if len(args) != 0 and len(symbol_kwargs) != 0:
+            raise TypeError(
+                '%s can only accept input'
+                'Symbols either as positional or keyword arguments, not both' % func_name)
+        s = Symbol(sym_handle)
+        attr = AttrScope.current.get(attr)
+        if attr:
+            s._set_attr(**attr)
+        hint = func_name.lower()
+        name = NameManager.current.get(name, hint)
+        s._compose(*args, name=name, **symbol_kwargs)
+        return s
+
+    creator.__name__ = func_name
+    creator.__doc__ = doc_str
+    return creator
+
+
+def _init_symbol_module():
+    """List and add all the atomic symbol functions to current module."""
+    plist = ctypes.POINTER(ctypes.c_void_p)()
+    size = ctypes.c_uint()
+
+    check_call(_LIB.NNSymbolListAtomicSymbolCreators(ctypes.byref(size),
+                                                     ctypes.byref(plist)))
+    module_obj = sys.modules[__name__]
+    for i in range(size.value):
+        hdl = SymbolHandle(plist[i])
+        function = _make_atomic_symbol_function(hdl)
+        if function.__name__.startswith('_'):
+            setattr(Symbol, function.__name__, staticmethod(function))
+        else:
+            setattr(module_obj, function.__name__, function)
+
+# Initialize the atomic symbol in startups
+_init_symbol_module()
diff --git a/nnvm/src/c_api/c_api_graph.cc b/nnvm/src/c_api/c_api_graph.cc
new file mode 100644
index 000000000000..3e8f573c2d84
--- /dev/null
+++ b/nnvm/src/c_api/c_api_graph.cc
@@ -0,0 +1,77 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file c_api_graph.cc
+ * \brief C API related to Graph IR.
+ */
+#include <nnvm/c_api.h>
+#include <nnvm/op.h>
+#include <nnvm/symbolic.h>
+#include <nnvm/graph.h>
+#include <nnvm/pass.h>
+#include "./c_api_common.h"
+
+using namespace nnvm;
+
+int NNGraphCreate(SymbolHandle symbol, GraphHandle *graph) {
+  Graph* g = new Graph();
+  API_BEGIN();
+  g->outputs = static_cast<Symbol*>(symbol)->outputs;
+  *graph = g;
+  API_END_HANDLE_ERROR(delete g);
+}
+
+int NNGraphFree(GraphHandle handle) {
+  API_BEGIN();
+  delete static_cast<Graph*>(handle);
+  API_END();
+}
+
+int NNGraphGetSymbol(GraphHandle graph, SymbolHandle *symbol) {
+  Symbol* s = new Symbol();
+  API_BEGIN();
+  s->outputs = static_cast<Graph*>(graph)->outputs;
+  *symbol = s;
+  API_END_HANDLE_ERROR(delete s);
+}
+
+int NNGraphSetStrAttr(GraphHandle handle,
+                      const char* key,
+                      const char* value) {
+  API_BEGIN();
+  Graph* g = static_cast<Graph*>(handle);
+  g->attrs[std::string(key)] = std::make_shared<any>(std::string(value));
+  API_END();
+}
+
+int NNGraphGetStrAttr(GraphHandle handle,
+                      const char* key,
+                      const char** out,
+                      int *success) {
+  API_BEGIN();
+  Graph* g = static_cast<Graph*>(handle);
+  std::string skey(key);
+  auto it = g->attrs.find(skey);
+  if (it != g->attrs.end()) {
+    const std::string& str = nnvm::get<std::string>(*it->second.get());
+    *out = str.c_str();
+    *success = 1;
+  } else {
+    *success = 0;
+  }
+  API_END();
+}
+
+int NNGraphApplyPass(GraphHandle src,
+                     nn_uint num_pass,
+                     const char** pass_names,
+                     GraphHandle *dst) {
+  Graph* g = new Graph();
+  API_BEGIN();
+  std::vector<std::string> vpass;
+  for (nn_uint i = 0; i < num_pass; ++i) {
+    vpass.emplace_back(std::string(pass_names[i]));
+  }
+  *g = ApplyPass(*static_cast<Graph*>(src), vpass);
+  *dst = g;
+  API_END_HANDLE_ERROR(delete g);
+}
diff --git a/nnvm/src/c_api/c_api_symbolic.cc b/nnvm/src/c_api/c_api_symbolic.cc
index c023ee4a8186..335d163c7dae 100644
--- a/nnvm/src/c_api/c_api_symbolic.cc
+++ b/nnvm/src/c_api/c_api_symbolic.cc
@@ -3,7 +3,6 @@
  * \file c_api_symbolic.cc
  * \brief C API related to symbolic graph compsition.
  */
-#include <dmlc/logging.h>
 #include <nnvm/c_api.h>
 #include <nnvm/op.h>
 #include <nnvm/symbolic.h>
@@ -123,7 +122,24 @@ int NNSymbolPrint(SymbolHandle symbol, const char **out_str) {
   API_END();
 }
 
-int MXSymbolSetAttrs(SymbolHandle symbol,
+int NNSymbolGetAttr(SymbolHandle symbol,
+                    const char* key,
+                    const char** out,
+                    int* success) {
+  Symbol *s = static_cast<Symbol*>(symbol);
+  NNAPIThreadLocalEntry *ret = NNAPIThreadLocalStore::Get();
+  API_BEGIN();
+  if (s->GetAttr(key, &(ret->ret_str))) {
+    *out = (ret->ret_str).c_str();
+    *success = 1;
+  } else {
+    *out = nullptr;
+    *success = 0;
+  }
+  API_END();
+}
+
+int NNSymbolSetAttrs(SymbolHandle symbol,
                      nn_uint num_param,
                      const char** keys,
                      const char** vals) {
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index a6e70b29da52..a1a1fdcbbe1b 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -196,8 +196,9 @@ void Symbol::Compose(const std::vector<Symbol>& args,
         static auto& flist_inputs = Op::GetAttr<FListInputNames>("FListInputNames");
         FListInputNames fn = flist_inputs.get(n->op, nullptr);
         auto arg_names = (fn == nullptr) ? std::vector<std::string>{"data"} : fn(n->attrs);
-        CHECK_EQ(arg_names.size(), n_req);
-
+        if (arg_names.size() != n_req) {
+          LOG(FATAL) << "Not enough argument to call operator " << outputs[0].node->op->name;
+        }
         size_t nmatched = 0;
         for (size_t i = args.size(); i < n_req; ++i) {
           auto it = kwargs.find(arg_names[i]);
@@ -311,15 +312,37 @@ Symbol Symbol::GetInternals() const {
 }
 
 void Symbol::SetAttrs(const std::vector<std::pair<std::string, std::string> >& attrs) {
-  CHECK_EQ(outputs.size(), 1)
-      << "SetAttrs only works for nongrouped symbol";
-  Node* n = outputs[0].node.get();
+  Node* node = outputs[0].node.get();
+  for (const NodeEntry& e : outputs) {
+    CHECK(node == e.node.get())
+        << "Symbol.SetAttrs only works for non-grouped symbol";
+  }
   for (const auto& kv : attrs) {
-    n->attrs.dict[kv.first] = kv.second;
+    if (kv.first == "name") {
+      node->attrs.name = kv.second;
+    } else {
+      node->attrs.dict[kv.first] = kv.second;
+    }
   }
-  if (n->op->attr_parser != nullptr) {
-    (*n->op->attr_parser)(&(n->attrs));
+  if (node->op != nullptr && node->op->attr_parser != nullptr) {
+    (*node->op->attr_parser)(&(node->attrs));
+  }
+}
+
+bool Symbol::GetAttr(const std::string& key, std::string* out) const {
+  Node* node = outputs[0].node.get();
+  for (const NodeEntry& e : outputs) {
+    CHECK(node == e.node.get())
+        << "Symbol.SetAttrs only works for non-grouped symbol";
+  }
+  if (key == "name") {
+    *out = node->attrs.name;
+    return true;
   }
+  auto it = node->attrs.dict.find(key);
+  if (it == node->attrs.dict.end()) return false;
+  *out = it->second;
+  return true;
 }
 
 std::unordered_map<std::string, std::string> Symbol::ListAttrs(ListAttrOption option) const {
diff --git a/nnvm/src/example/operator.cc b/nnvm/src/example/operator.cc
index 8152b117c2cb..f1332d1af622 100644
--- a/nnvm/src/example/operator.cc
+++ b/nnvm/src/example/operator.cc
@@ -2,13 +2,30 @@
 // This is an example on how we can register operator information to NNVM
 
 #include <nnvm/op.h>
+#include <nnvm/op_attr_types.h>
 #include <utility>
 
+using nnvm::FListInputNames;
+using nnvm::NodeAttrs;
+
 NNVM_REGISTER_OP(add)
 .describe("add two data together")
 .set_num_inputs(2)
 .attr("inplace_pair", std::make_pair(0, 0));
 
 
+NNVM_REGISTER_OP(exp)
+.describe("take exponmential")
+.set_num_inputs(1)
+.attr("inplace_pair", std::make_pair(0, 0));
+
+
+NNVM_REGISTER_OP(conv2d)
+.describe("take conv of input")
+.set_num_inputs(2)
+.attr<FListInputNames>("FListInputNames", [](const NodeAttrs& attrs) {
+    return std::vector<std::string>{"data", "weight"};
+  });
+
 NNVM_REGISTER_OP(add)
 .attr<std::string>("nick_name", "plus");
diff --git a/nnvm/src/pass/saveload_json.cc b/nnvm/src/pass/saveload_json.cc
index f05e4873e2bf..309bb9336eb3 100644
--- a/nnvm/src/pass/saveload_json.cc
+++ b/nnvm/src/pass/saveload_json.cc
@@ -143,6 +143,7 @@ Graph LoadJSON(const Graph& src) {
   for (uint32_t nid : jgraph.arg_nodes) {
     CHECK(jgraph.nodes[nid].node->is_variable());
   }
+
   // return the graph
   Graph ret;
   ret.attrs = std::move(jgraph.attrs);
@@ -177,6 +178,10 @@ Graph SaveJSON(const Graph& src) {
       jgraph.nodes.emplace_back(std::move(jnode));
     });
 
+  for (const NodeEntry& e : src.outputs) {
+    jgraph.heads.push_back(std::make_pair(node2index.at(e.node.get()), e.index));
+  }
+
   std::ostringstream os;
   dmlc::JSONWriter writer(&os);
   jgraph.Save(&writer);
diff --git a/nnvm/tests/python/test_graph.py b/nnvm/tests/python/test_graph.py
new file mode 100644
index 000000000000..160098ab52c9
--- /dev/null
+++ b/nnvm/tests/python/test_graph.py
@@ -0,0 +1,14 @@
+import nnvm.symbol as sym
+import nnvm.graph as graph
+
+def test_json_pass():
+    x = sym.Variable('x')
+    y = sym.conv2d(data=x, name='conv', stride=(2,2))
+    g = graph.create(y)
+    ret = g.apply('SaveJSON')
+    g2 = ret.apply('LoadJSON')
+    assert g2.apply('SaveJSON').attr('json') == ret.attr('json')
+
+
+if __name__ == "__main__":
+    test_json_pass()
diff --git a/nnvm/tests/python/test_symbol.py b/nnvm/tests/python/test_symbol.py
new file mode 100644
index 000000000000..861b5372d5da
--- /dev/null
+++ b/nnvm/tests/python/test_symbol.py
@@ -0,0 +1,29 @@
+import nnvm.symbol as sym
+from nnvm.base import NNVMError
+
+def test_compose():
+    x = sym.Variable('x')
+    z = sym.Variable('z')
+    y = sym.exp(sym.add(x, x, name='add', gpu=2),
+                name='exp', gpu=1, attr={"kk": "1"})
+
+    assert y.list_arguments() == ['x']
+    assert y.list_outputs() == ["exp_output"]
+    assert y.list_attr()['gpu'] == '1'
+    z = y.get_internals()
+    assert z['add_output'].list_outputs() == ['add_output']
+    assert y.list_attr(recursive=True)['add_gpu'] == '2'
+
+def test_default_input():
+    x = sym.Variable('x')
+    y = sym.conv2d(data=x, name='conv')
+    assert y.list_arguments() == ['x', 'conv_weight']
+    try:
+        z = sym.add(x)
+        assert False
+    except NNVMError:
+        pass
+
+if __name__ == "__main__":
+    test_default_input()
+    test_compose()

From fd893864fa49e086c097a20db08bbdb631708b29 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 11 Jul 2016 22:14:04 -0700
Subject: [PATCH 334/949] Enable use json for graph attr exchange (#5)

---
 nnvm/include/nnvm/c_api.h       | 29 ++++++++++++++++++-----------
 nnvm/python/nnvm/base.py        |  1 +
 nnvm/python/nnvm/graph.py       | 29 ++++++++++++++++++++---------
 nnvm/src/c_api/c_api_graph.cc   | 26 ++++++++++++++++++--------
 nnvm/src/pass/saveload_json.cc  |  4 ++++
 nnvm/tests/python/test_graph.py | 11 ++++++++++-
 6 files changed, 71 insertions(+), 29 deletions(-)

diff --git a/nnvm/include/nnvm/c_api.h b/nnvm/include/nnvm/c_api.h
index d15ba59b46b2..46ecf2d510dc 100644
--- a/nnvm/include/nnvm/c_api.h
+++ b/nnvm/include/nnvm/c_api.h
@@ -248,27 +248,34 @@ NNVM_DLL int NNGraphFree(GraphHandle handle);
  */
 NNVM_DLL int NNGraphGetSymbol(GraphHandle graph, SymbolHandle *symbol);
 /*!
- * \brief Get Set a std::string typed attribute to graph.
+ * \brief Get Set a attribute in json format.
+ * This feature allows pass graph attributes back and forth in reasonable speed.
+ *
  * \param handle The graph handle.
  * \param key The key to the attribute.
- * \param value The value to be exposed.
+ * \param json_value The value need to be in format [type_name, value],
+ *  Where type_name is a registered type string in C++ side via DMLC_JSON_ENABLE_ANY.
  * \return 0 when success, -1 when failure happens
  */
-NNVM_DLL int NNGraphSetStrAttr(GraphHandle handle,
-                               const char* key,
-                               const char* value);
+NNVM_DLL int NNGraphSetJSONAttr(GraphHandle handle,
+                                const char* key,
+                                const char* json_value);
 /*!
- * \brief Get Set a std::string typed attribute from graph attribute.
+ * \brief Get a serialized attrirbute from graph.
+ * This feature allows pass graph attributes back and forth in reasonable speed.
+ *
  * \param handle The graph handle.
  * \param key The key to the attribute.
- * \param out The result attribute, can be NULL if the attribute do not exist.
+ * \param json_out The result attribute, can be NULL if the attribute do not exist.
+ *  The json_out is an array of [type_name, value].
+ *  Where the type_name is a registered type string in C++ side via DMLC_JSON_ENABLE_ANY.
  * \param success Whether the result is contained in out.
  * \return 0 when success, -1 when failure happens
  */
-NNVM_DLL int NNGraphGetStrAttr(SymbolHandle handle,
-                               const char* key,
-                               const char** out,
-                               int *success);
+NNVM_DLL int NNGraphGetJSONAttr(SymbolHandle handle,
+                                const char* key,
+                                const char** json_out,
+                                int *success);
 /*!
  * \brief Apply pass on the src graph.
  * \param src The source graph handle.
diff --git a/nnvm/python/nnvm/base.py b/nnvm/python/nnvm/base.py
index bc3d3b7b6ecf..cf5ead2b4ab9 100644
--- a/nnvm/python/nnvm/base.py
+++ b/nnvm/python/nnvm/base.py
@@ -47,6 +47,7 @@ def _load_lib():
 SymbolHandle = ctypes.c_void_p
 GraphHandle = ctypes.c_void_p
 
+
 #----------------------------
 # helper function definition
 #----------------------------
diff --git a/nnvm/python/nnvm/graph.py b/nnvm/python/nnvm/graph.py
index c09f98173ea4..6661d9ca7140 100644
--- a/nnvm/python/nnvm/graph.py
+++ b/nnvm/python/nnvm/graph.py
@@ -5,12 +5,14 @@
 
 import ctypes
 import sys
+import json
 from .base import _LIB
 from .base import c_array, c_str, nn_uint, py_str, string_types
 from .base import GraphHandle, SymbolHandle
 from .base import check_call
 from .symbol import Symbol
 
+
 class Graph(object):
     """Graph is the graph object that can be used to apply optimization pass.
     It contains additional graphwise attribute besides the internal symbol.
@@ -31,7 +33,7 @@ def __init__(self, handle):
     def __del__(self):
         check_call(_LIB.NNGraphFree(self.handle))
 
-    def attr(self, key):
+    def json_attr(self, key):
         """Get attribute string from the graph.
 
         Parameters
@@ -46,24 +48,33 @@ def attr(self, key):
         """
         ret = ctypes.c_char_p()
         success = ctypes.c_int()
-        check_call(_LIB.NNGraphGetStrAttr(
+        check_call(_LIB.NNGraphGetJSONAttr(
             self.handle, c_str(key), ctypes.byref(ret), ctypes.byref(success)))
         if success.value != 0:
-            return py_str(ret.value)
+            json_str = py_str(ret.value)
+            return json.loads(json_str)[1]
         else:
             return None
 
-    def _set_attr(self, **kwargs):
+    def _set_json_attr(self, key, value, type_name=None):
         """Set the attribute of the symbol.
 
         Parameters
         ----------
-        **kwargs
-            The attributes to set
+        key : string
+            The key of the attribute
+        value : value
+            The any type that can be dumped to json
+        type_name : string
+            The typename registered on c++ side.
         """
-        for k, v in kwargs.items():
-            check_call(_LIB.NNGraphSetStrAttr(
-                self.handle, c_str(k), c_str(v)))
+        if isinstance(value, string_types):
+            type_name = 'str'
+        elif type_name is None:
+            raise ValueError("Need to specify type_name")
+        json_value = json.dumps([type_name, value])
+        check_call(_LIB.NNGraphSetJSONAttr(
+            self.handle, c_str(key), c_str(json_value)))
 
     @property
     def symbol(self):
diff --git a/nnvm/src/c_api/c_api_graph.cc b/nnvm/src/c_api/c_api_graph.cc
index 3e8f573c2d84..c3de70618b1f 100644
--- a/nnvm/src/c_api/c_api_graph.cc
+++ b/nnvm/src/c_api/c_api_graph.cc
@@ -8,6 +8,7 @@
 #include <nnvm/symbolic.h>
 #include <nnvm/graph.h>
 #include <nnvm/pass.h>
+#include <dmlc/json.h>
 #include "./c_api_common.h"
 
 using namespace nnvm;
@@ -34,26 +35,35 @@ int NNGraphGetSymbol(GraphHandle graph, SymbolHandle *symbol) {
   API_END_HANDLE_ERROR(delete s);
 }
 
-int NNGraphSetStrAttr(GraphHandle handle,
-                      const char* key,
-                      const char* value) {
+int NNGraphSetJSONAttr(GraphHandle handle,
+                       const char* key,
+                       const char* json_value) {
   API_BEGIN();
   Graph* g = static_cast<Graph*>(handle);
-  g->attrs[std::string(key)] = std::make_shared<any>(std::string(value));
+  std::string temp(json_value);
+  std::istringstream is(temp);
+  dmlc::JSONReader reader(&is);
+  nnvm::any value;
+  reader.Read(&value);
+  g->attrs[std::string(key)] = std::make_shared<any>(std::move(value));
   API_END();
 }
 
-int NNGraphGetStrAttr(GraphHandle handle,
+int NNGraphGetJSONAttr(GraphHandle handle,
                       const char* key,
-                      const char** out,
+                      const char** json_out,
                       int *success) {
+  NNAPIThreadLocalEntry *ret = NNAPIThreadLocalStore::Get();
   API_BEGIN();
   Graph* g = static_cast<Graph*>(handle);
   std::string skey(key);
   auto it = g->attrs.find(skey);
   if (it != g->attrs.end()) {
-    const std::string& str = nnvm::get<std::string>(*it->second.get());
-    *out = str.c_str();
+    std::ostringstream os;
+    dmlc::JSONWriter writer(&os);
+    writer.Write(*it->second.get());
+    ret->ret_str = os.str();
+    *json_out = (ret->ret_str).c_str();
     *success = 1;
   } else {
     *success = 0;
diff --git a/nnvm/src/pass/saveload_json.cc b/nnvm/src/pass/saveload_json.cc
index 309bb9336eb3..fee94fbf6eb9 100644
--- a/nnvm/src/pass/saveload_json.cc
+++ b/nnvm/src/pass/saveload_json.cc
@@ -203,5 +203,9 @@ NNVM_REGISTER_PASS(SaveJSON)
 .set_change_graph(true)
 .provide_graph_attr("json");
 
+
+DMLC_JSON_ENABLE_ANY(std::string, str);
+DMLC_JSON_ENABLE_ANY(std::vector<int>, list_int);
+
 }  // namespace pass
 }  // namespace nnvm
diff --git a/nnvm/tests/python/test_graph.py b/nnvm/tests/python/test_graph.py
index 160098ab52c9..8fa392db3425 100644
--- a/nnvm/tests/python/test_graph.py
+++ b/nnvm/tests/python/test_graph.py
@@ -6,9 +6,18 @@ def test_json_pass():
     y = sym.conv2d(data=x, name='conv', stride=(2,2))
     g = graph.create(y)
     ret = g.apply('SaveJSON')
+    ret._set_json_attr('json', ret.json_attr('json'))
     g2 = ret.apply('LoadJSON')
-    assert g2.apply('SaveJSON').attr('json') == ret.attr('json')
+    assert g2.apply('SaveJSON').json_attr('json') == ret.json_attr('json')
+
+def test_graph_json_attr():
+    x = sym.Variable('x')
+    y = sym.conv2d(data=x, name='conv', stride=(2,2))
+    g = graph.create(y)
+    g._set_json_attr('ilist', [1,2,3], 'list_int')
+    assert g.json_attr('ilist') == [1,2,3]
 
 
 if __name__ == "__main__":
+    test_graph_json_attr()
     test_json_pass()

From d99d55098288b7bfaa43258f3b3dd60f7e6f249a Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 12 Jul 2016 22:03:43 -0700
Subject: [PATCH 335/949] Change op function pointer to std::function, enable
 mutation (#6)

---
 nnvm/include/nnvm/node.h          |  7 +++
 nnvm/include/nnvm/op.h            | 19 +++----
 nnvm/include/nnvm/op_attr_types.h | 14 ++++-
 nnvm/src/core/symbolic.cc         | 91 +++++++++++++++++++++++++------
 nnvm/src/example/operator.cc      |  8 +++
 nnvm/tests/python/test_symbol.py  | 14 +++++
 6 files changed, 123 insertions(+), 30 deletions(-)

diff --git a/nnvm/include/nnvm/node.h b/nnvm/include/nnvm/node.h
index dd24c3bd6fa3..7ca18760d173 100644
--- a/nnvm/include/nnvm/node.h
+++ b/nnvm/include/nnvm/node.h
@@ -24,6 +24,13 @@ struct NodeEntry {
   std::shared_ptr<Node> node;
   /*! \brief index of output from the source. */
   uint32_t index;
+  /*!
+   * \brief version of input Variable.
+   *  This field can only be nonzero when this->node is a Variable node.
+   *  version is increased by one each time a Variable get composed to a mutation Op.
+   *  This information can be helpful to decide order of operations when sequence of mutation happens.
+   */
+  uint32_t version;
 };
 
 /*!
diff --git a/nnvm/include/nnvm/op.h b/nnvm/include/nnvm/op.h
index a7d2fd5e1e4d..5f499b7377e1 100644
--- a/nnvm/include/nnvm/op.h
+++ b/nnvm/include/nnvm/op.h
@@ -101,13 +101,13 @@ class Op {
    * \param attrs The attribute of the node
    * \return number of outputs.
    */
-  uint32_t (*get_num_outputs)(const NodeAttrs& attrs) = nullptr;
+  std::function<uint32_t(const NodeAttrs& attrs)> get_num_outputs = nullptr;
   /*!
    * \brief get number of inputs given information about the node.
    * \param attrs The attribute of the node
    * \return number of inputs
    */
-  uint32_t (*get_num_inputs)(const NodeAttrs& attrs) = nullptr;
+  std::function<uint32_t(const NodeAttrs& attrs)> get_num_inputs = nullptr;
   /*!
    * \brief Attribute parser to parse the NodeAttrs information.
    *
@@ -140,8 +140,7 @@ class Op {
    *  }
    * \endcode
    */
-  void (*attr_parser)(NodeAttrs* attrs) = nullptr;
-
+  std::function<void(NodeAttrs* attrs)> attr_parser = nullptr;
   // function fields.
   /*!
    * \brief setter function during registration
@@ -161,7 +160,7 @@ class Op {
    * \param fn The function to be set.
    * \return reference to self.
    */
-  inline Op& set_num_inputs(uint32_t (*fn)(const NodeAttrs& attr));  // NOLINT(*)
+  inline Op& set_num_inputs(std::function<uint32_t (const NodeAttrs& attr)> fn);  // NOLINT(*)
   /*!
    * \brief Set the num_outputs
    * \param n The number of outputs to be set.
@@ -173,13 +172,13 @@ class Op {
    * \param fn The function to be set.
    * \return reference to self.
    */
-  inline Op& set_num_outputs(uint32_t (*fn)(const NodeAttrs& attr));  // NOLINT(*)
+  inline Op& set_num_outputs(std::function<uint32_t (const NodeAttrs& attr)> fn);  // NOLINT(*)
   /*!
    * \brief Set the attr_parser function.
    * \param fn The number of outputs to be set.
    * \return reference to self.
    */
-  inline Op& set_attr_parser(void (*fn)(NodeAttrs* attrs));  // NOLINT(*)
+  inline Op& set_attr_parser(std::function<void (NodeAttrs* attrs)> fn);  // NOLINT(*)
   /*!
    * \brief Register additional attributes to operator.
    * \param attr_name The name of the attribute.
@@ -342,7 +341,7 @@ inline Op& Op::set_num_inputs(uint32_t n) {  // NOLINT(*)
   return *this;
 }
 
-inline Op& Op::set_num_inputs(uint32_t (*fn)(const NodeAttrs& attr)) {  // NOLINT(*)
+inline Op& Op::set_num_inputs(std::function<uint32_t (const NodeAttrs& attr)> fn) {  // NOLINT(*)
   this->get_num_inputs = fn;
   return *this;
 }
@@ -352,12 +351,12 @@ inline Op& Op::set_num_outputs(uint32_t n) {  // NOLINT(*)
   return *this;
 }
 
-inline Op& Op::set_num_outputs(uint32_t (*fn)(const NodeAttrs& attr)) {  // NOLINT(*)
+inline Op& Op::set_num_outputs(std::function<uint32_t (const NodeAttrs& attr)> fn) {  // NOLINT(*)
   this->get_num_outputs = fn;
   return *this;
 }
 
-inline Op& Op::set_attr_parser(void (*fn)(NodeAttrs* attrs)) {  // NOLINT(*)
+inline Op& Op::set_attr_parser(std::function<void (NodeAttrs* attrs)> fn) {  // NOLINT(*)
   this->attr_parser = fn;
   return *this;
 }
diff --git a/nnvm/include/nnvm/op_attr_types.h b/nnvm/include/nnvm/op_attr_types.h
index bfcf5f6b8eaa..615fca24e07c 100644
--- a/nnvm/include/nnvm/op_attr_types.h
+++ b/nnvm/include/nnvm/op_attr_types.h
@@ -12,8 +12,8 @@
 
 namespace nnvm {
 
-// These types are optional attributes in each op
-// Some of them are needed for certain pass.
+// These types are optional attributes in each operator.
+// Each attribute can be required by some passes.
 
 /*!
  * \brief Return list of input arguments names of each operator.
@@ -37,6 +37,16 @@ using FListInputNames = std::function<std::vector<std::string> (const NodeAttrs&
  */
 using FListOutputNames = std::function<std::vector<std::string> (const NodeAttrs& attrs)>;
 
+/*!
+ * \brief Check whether operator will mutate k-th input.
+ * \param index The input index
+ * \return Whether this operator will mutate index-th input.
+ *
+ * \note Register under "FMutateInput", default return false
+ * FMutateInputs enables mutation order handling correctly.
+ */
+using FMutateInput = std::function<bool (const NodeAttrs& attrs, uint32_t index)>;
+
 }  // namespace nnvm
 
 #endif  // NNVM_OP_ATTR_TYPES_H_
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index a1a1fdcbbe1b..0d6f2a6c786e 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -13,6 +13,43 @@ namespace symbol_constants {
 const char *kNamespaceSeparator = "_";
 }  // namespace symbol_constants
 
+// auxililary version attribute in variable.
+struct VariableParam {
+  uint32_t version{0};
+};
+
+std::shared_ptr<Node> CreateVariableNode(const std::string& name) {
+  std::shared_ptr<Node> n = Node::Create();
+  n->op = nullptr;
+  n->attrs.name = name;
+  n->attrs.parsed = VariableParam();
+  return n;
+}
+
+// scan over a node's input, update the version to latest
+// If the node's op mutates a certain input variable,
+// The version of that varaible will increase
+// version is used to implicitly order the mutation sequences
+inline void UpdateNodeVersion(Node *n) {
+  static auto& fmutate_inputs = Op::GetAttr<FMutateInput>("FMutateInput");
+  for (NodeEntry& e : n->inputs) {
+    if (e.node->is_variable()) {
+      e.version = nnvm::get<VariableParam>(e.node->attrs.parsed).version;
+    }
+  }
+  if (fmutate_inputs.count(n->op) != 0) {
+    FMutateInput fmutate = fmutate_inputs[n->op];
+    for (uint32_t i = 0; i < n->inputs.size(); ++i) {
+      if (fmutate(n->attrs, i)) {
+        NodeEntry& e = n->inputs[i];
+        CHECK(e.node->is_variable())
+            << "Mutation target can only be Variable";
+        // increase the version of the variable.
+        ++nnvm::get<VariableParam>(e.node->attrs.parsed).version;
+      }
+    }
+  }
+}
 
 inline std::string DefaultVarName(const std::string &op_name,
                                   const std::string &arg_name) {
@@ -67,13 +104,13 @@ Symbol Symbol::Copy() const {
   for (const auto &kv : old_new) {
     for (const NodeEntry& e : kv.first->inputs) {
       Node *ptr = e.node.get();
-      kv.second->inputs.emplace_back(NodeEntry{old_new[ptr], e.index});
+      kv.second->inputs.emplace_back(NodeEntry{old_new[ptr], e.index, e.version});
     }
   }
   // set the head
   Symbol ret;
   for (const NodeEntry &e : outputs) {
-    ret.outputs.emplace_back(NodeEntry{old_new[e.node.get()], e.index});
+    ret.outputs.emplace_back(NodeEntry{old_new[e.node.get()], e.index, e.version});
   }
   return ret;
 }
@@ -95,8 +132,14 @@ void Symbol::Print(std::ostream &os) const {
           os << "Name: " << node->attrs.name << " Op:" << node->op->name << '\n'
              << "Inputs:\n";
           for (size_t i = 0; i < node->inputs.size(); ++i) {
-            os << "\targ[" << i << "]=" << node->inputs[i].node->attrs.name
-               << '(' << node->inputs[i].index << ")\n";
+            const NodeEntry& e = node->inputs[i];
+            os << "\targ[" << i << "]=" << e.node->attrs.name
+               << '(' << e.index << ")";
+            if (e.node->is_variable()) {
+              os << " version=" << e.version << '\n';
+            } else {
+              os << '\n';
+            }
           }
           os << "Attrs:\n";
           for (auto &kv : node->attrs.dict) {
@@ -163,6 +206,8 @@ std::vector<std::string> Symbol::ListOutputs() const {
 void Symbol::Compose(const std::vector<Symbol>& args,
                      const std::unordered_map<std::string, Symbol>& kwargs,
                      const std::string& name) {
+  static auto& flist_inputs = Op::GetAttr<FListInputNames>("FListInputNames");
+
   CHECK_EQ(outputs.size(), 1)
       << "Only composition of value function is supported currently";
   CHECK(!outputs[0].node->is_variable()) << "Variable cannot be composed";
@@ -193,7 +238,6 @@ void Symbol::Compose(const std::vector<Symbol>& args,
       }
       // switch to keyword argument matching
       if (args.size() != n_req) {
-        static auto& flist_inputs = Op::GetAttr<FListInputNames>("FListInputNames");
         FListInputNames fn = flist_inputs.get(n->op, nullptr);
         auto arg_names = (fn == nullptr) ? std::vector<std::string>{"data"} : fn(n->attrs);
         if (arg_names.size() != n_req) {
@@ -206,8 +250,8 @@ void Symbol::Compose(const std::vector<Symbol>& args,
             n->inputs[i] = it->second.outputs[0];
             ++nmatched;
           } else {
-            n->inputs[i] = NodeEntry{Node::Create(), 0};
-            n->inputs[i].node->attrs.name = DefaultVarName(name, arg_names[i]);
+            n->inputs[i] = NodeEntry{
+              CreateVariableNode(DefaultVarName(name, arg_names[i])), 0, 0};
           }
         }
 
@@ -226,6 +270,7 @@ void Symbol::Compose(const std::vector<Symbol>& args,
         n->inputs.push_back(s.outputs[0]);
       }
     }
+    UpdateNodeVersion(n);
   } else {
     // general composition
     CHECK_EQ(args.size(), 0)
@@ -253,25 +298,32 @@ void Symbol::Compose(const std::vector<Symbol>& args,
     DFSVisit(this->outputs, find_replace_map);
 
     if (nmatched == kwargs.size() && arg_counter < args.size()) {
+      std::vector<Node*> update_nodes;
       std::vector<std::pair<NodeEntry*, const NodeEntry*> > replace_plan;
-      auto find_replace_plan = [&replace_map, &replace_plan]
+      auto find_replace_plan = [&replace_map, &replace_plan, &update_nodes]
           (const std::shared_ptr<Node> &node) {
         // visit all the childs, find possible replacement
+        bool repl = false;
         for (size_t i = 0; i < node->inputs.size(); ++i) {
           NodeEntry *e = &(node->inputs[i]);
           if (e->node->is_variable()) {
             auto iter = replace_map.find(e->node.get());
             if (iter != replace_map.end()) {
               replace_plan.push_back(std::make_pair(e, iter->second));
+              repl = true;
             }
           }
         }
+        if (repl) update_nodes.push_back(node.get());
       };
       DFSVisit(this->outputs, find_replace_plan);
 
       for (const auto& kv : replace_plan) {
         *(kv.first) = *(kv.second);
       }
+      for (Node* n : update_nodes) {
+        UpdateNodeVersion(n);
+      }
     } else {
       std::vector<std::string> keys = GetKeys(kwargs);
       std::vector<std::string> arg_names = ListArguments();
@@ -303,9 +355,15 @@ Symbol Symbol::GetInternals() const {
   Symbol ret;
   DFSVisit(this->outputs, [&ret](const std::shared_ptr<Node>& node) {
       Node* n = node.get();
-      uint32_t nout = n->num_outputs();
-      for (uint32_t i = 0; i < nout; ++i) {
-        ret.outputs.emplace_back(NodeEntry{node, i});
+      if (n->is_variable()) {
+        // grab version from variable.
+        VariableParam& param = nnvm::get<VariableParam>(n->attrs.parsed);
+        ret.outputs.emplace_back(NodeEntry{node, 0, param.version});
+      } else {
+        uint32_t nout = n->num_outputs();
+        for (uint32_t i = 0; i < nout; ++i) {
+          ret.outputs.emplace_back(NodeEntry{node, i, 0});
+        }
       }
     });
   return ret;
@@ -325,7 +383,7 @@ void Symbol::SetAttrs(const std::vector<std::pair<std::string, std::string> >& a
     }
   }
   if (node->op != nullptr && node->op->attr_parser != nullptr) {
-    (*node->op->attr_parser)(&(node->attrs));
+    node->op->attr_parser(&(node->attrs));
   }
 }
 
@@ -366,9 +424,9 @@ Symbol Symbol::CreateFunctor(const Op* op,
   n->op = op;
   n->attrs.dict = std::move(attrs);
   if (n->op->attr_parser != nullptr) {
-    (*n->op->attr_parser)(&(n->attrs));
+    n->op->attr_parser(&(n->attrs));
   }
-  s.outputs.emplace_back(NodeEntry{std::move(n), 0});
+  s.outputs.emplace_back(NodeEntry{std::move(n), 0, 0});
   return s;
 }
 
@@ -382,10 +440,7 @@ Symbol Symbol::CreateGroup(const std::vector<Symbol> &symbols) {
 
 Symbol Symbol::CreateVariable(const std::string& name) {
   Symbol s;
-  std::shared_ptr<Node> n = Node::Create();
-  n->op = nullptr;
-  n->attrs.name = name;
-  s.outputs.emplace_back(NodeEntry{std::move(n), 0});
+  s.outputs.emplace_back(NodeEntry{CreateVariableNode(name), 0, 0});
   return s;
 }
 
diff --git a/nnvm/src/example/operator.cc b/nnvm/src/example/operator.cc
index f1332d1af622..2bd4a22ed2dd 100644
--- a/nnvm/src/example/operator.cc
+++ b/nnvm/src/example/operator.cc
@@ -6,6 +6,7 @@
 #include <utility>
 
 using nnvm::FListInputNames;
+using nnvm::FMutateInput;
 using nnvm::NodeAttrs;
 
 NNVM_REGISTER_OP(add)
@@ -29,3 +30,10 @@ NNVM_REGISTER_OP(conv2d)
 
 NNVM_REGISTER_OP(add)
 .attr<std::string>("nick_name", "plus");
+
+NNVM_REGISTER_OP(assign)
+.set_num_inputs(2)
+.set_num_outputs(1)
+.attr<FMutateInput>("FMutateInput", [](const NodeAttrs& attrs, uint32_t index) {
+    return index == 0;
+  });
diff --git a/nnvm/tests/python/test_symbol.py b/nnvm/tests/python/test_symbol.py
index 861b5372d5da..08d24536084d 100644
--- a/nnvm/tests/python/test_symbol.py
+++ b/nnvm/tests/python/test_symbol.py
@@ -24,6 +24,20 @@ def test_default_input():
     except NNVMError:
         pass
 
+def test_mutate_input():
+    x = sym.Variable('x')
+    y = sym.conv2d(data=x, name='conv')
+    z = sym.assign(x, y)
+    t = sym.add(z, x)
+
+    try:
+        z = sym.assign(z, z)
+        assert False
+    except NNVMError:
+        pass
+
+
 if __name__ == "__main__":
     test_default_input()
     test_compose()
+    test_mutate_input()

From 9a956a8ca939de6ee9b659e8a4559498323b8d8f Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 13 Jul 2016 17:31:08 -0700
Subject: [PATCH 336/949] [PASS] Add order mutation (#7)

* [PASS] Add order mutation

* A few benchmarks on compose speed
---
 nnvm/include/nnvm/symbolic.h     |   8 +-
 nnvm/src/c_api/c_api_common.h    |   3 +
 nnvm/src/c_api/c_api_symbolic.cc |  26 +++---
 nnvm/src/core/symbolic.cc        |  53 +++++++-----
 nnvm/src/pass/order_mutation.cc  | 142 +++++++++++++++++++++++++++++++
 nnvm/src/pass/saveload_json.cc   |   2 +-
 nnvm/src/test_main.cc            |  99 +++++++++++++++------
 nnvm/tests/python/test_graph.py  |  18 ++++
 nnvm/tests/python/test_symbol.py |   8 ++
 9 files changed, 296 insertions(+), 63 deletions(-)
 create mode 100644 nnvm/src/pass/order_mutation.cc

diff --git a/nnvm/include/nnvm/symbolic.h b/nnvm/include/nnvm/symbolic.h
index 5f2deaf948a6..2d5867a11892 100644
--- a/nnvm/include/nnvm/symbolic.h
+++ b/nnvm/include/nnvm/symbolic.h
@@ -73,8 +73,8 @@ class Symbol {
    * \param kwargs keyword arguments for the symbol
    * \param name name of returned symbol.
    */
-  void Compose(const std::vector<Symbol>& args,
-               const std::unordered_map<std::string, Symbol>& kwargs,
+  void Compose(const array_view<const Symbol*>& args,
+               const std::unordered_map<std::string, const Symbol*>& kwargs,
                const std::string& name);
   /*!
    * \brief Apply the symbol as a function, compose with arguments
@@ -84,8 +84,8 @@ class Symbol {
    * \param name name of returned symbol.
    * \return a new Symbol which is the composition of current symbol with its arguments
    */
-  Symbol operator () (const std::vector<Symbol>& args,
-                      const std::unordered_map<std::string, Symbol>& kwargs,
+  Symbol operator () (const array_view<const Symbol*>& args,
+                      const std::unordered_map<std::string, const Symbol*>& kwargs,
                       const std::string& name) const;
   /*!
    * \brief Add control flow depenencies to operators involved in symbols.
diff --git a/nnvm/src/c_api/c_api_common.h b/nnvm/src/c_api/c_api_common.h
index 170ceb2e58d3..40c91d8ffc11 100644
--- a/nnvm/src/c_api/c_api_common.h
+++ b/nnvm/src/c_api/c_api_common.h
@@ -10,6 +10,7 @@
 #include <dmlc/logging.h>
 #include <dmlc/thread_local.h>
 #include <nnvm/c_api.h>
+#include <nnvm/symbolic.h>
 #include <vector>
 #include <string>
 
@@ -36,6 +37,8 @@ struct NNAPIThreadLocalEntry {
   std::vector<const char *> ret_vec_charp;
   /*! \brief result holder for returning handles */
   std::vector<void *> ret_handles;
+  /*! \brief argument holder to hold symbol */
+  std::unordered_map<std::string, const nnvm::Symbol*> kwarg_symbol;
 };
 
 /*! \brief Thread local store that can be used to hold return values. */
diff --git a/nnvm/src/c_api/c_api_symbolic.cc b/nnvm/src/c_api/c_api_symbolic.cc
index 335d163c7dae..bc6eed5c742a 100644
--- a/nnvm/src/c_api/c_api_symbolic.cc
+++ b/nnvm/src/c_api/c_api_symbolic.cc
@@ -217,22 +217,26 @@ int NNSymbolCompose(SymbolHandle sym,
                     const char** keys,
                     SymbolHandle* args) {
   API_BEGIN();
-  std::string s_name;
-  if (name != nullptr) s_name = name;
-
+  NNAPIThreadLocalEntry *ret = NNAPIThreadLocalStore::Get();
+  std::string& s_name = ret->ret_str;
+  std::unordered_map<std::string, const Symbol*>& kwargs
+      = ret->kwarg_symbol;
+  if (name != nullptr) {
+    s_name = name;
+  } else {
+    s_name.clear();
+  }
   Symbol* s = static_cast<Symbol*>(sym);
   if (keys == nullptr && num_args != 0) {
-    std::vector<Symbol> pos_args;
-    for (nn_uint i = 0; i < num_args; ++i) {
-      pos_args.push_back(*((Symbol*)args[i]));  //  NOLINT(*)
-    }
-    s->Compose(pos_args, {}, s_name);
+    kwargs.clear();
+    array_view<const Symbol*> parg(
+        (Symbol**)args, (Symbol**)args + num_args); // NOLINT(*)
+    s->Compose(parg, kwargs, s_name);
   } else {
-    std::unordered_map<std::string, Symbol> kwargs;
     for (nn_uint i = 0; i < num_args; ++i) {
-      kwargs[keys[i]] = *((Symbol*)args[i]);  //  NOLINT(*)
+      kwargs[keys[i]] = (Symbol*)args[i];  //  NOLINT(*)
     }
-    s->Compose({}, kwargs, s_name);
+    s->Compose(array_view<const Symbol*>(), kwargs, s_name);
   }
   API_END();
 }
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index 0d6f2a6c786e..3cd1d982e433 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -45,7 +45,7 @@ inline void UpdateNodeVersion(Node *n) {
         CHECK(e.node->is_variable())
             << "Mutation target can only be Variable";
         // increase the version of the variable.
-        ++nnvm::get<VariableParam>(e.node->attrs.parsed).version;
+        e.version = ++nnvm::get<VariableParam>(e.node->attrs.parsed).version;
       }
     }
   }
@@ -98,7 +98,10 @@ Symbol Symbol::Copy() const {
   std::unordered_map<Node*, std::shared_ptr<Node> > old_new;
   // use DFSVisit to copy all the nodes
   DFSVisit(this->outputs, [&old_new](const std::shared_ptr<Node>& node) {
-      old_new[node.get()] =  std::make_shared<Node>(*node);
+      std::shared_ptr<Node> np = Node::Create();
+      np->op = node->op;
+      np->attrs = node->attrs;
+      old_new[node.get()] = std::move(np);
     });
   // connect nodes of new graph
   for (const auto &kv : old_new) {
@@ -106,6 +109,9 @@ Symbol Symbol::Copy() const {
       Node *ptr = e.node.get();
       kv.second->inputs.emplace_back(NodeEntry{old_new[ptr], e.index, e.version});
     }
+    for (const std::shared_ptr<Node>& p : kv.first->control_deps) {
+      kv.second->control_deps.emplace_back(old_new[p.get()]);
+    }
   }
   // set the head
   Symbol ret;
@@ -120,7 +126,7 @@ void Symbol::Print(std::ostream &os) const {
     os << "AtomicFunctor "<< " Op:" << outputs[0].node->op->name << '\n';
   } else {
     // use DFSVisit to copy all the nodes
-    os << "Outputs:\n";
+    os << "Symbol Outputs:\n";
     for (size_t i = 0; i < outputs.size(); ++i) {
       os << "\toutput[" << i << "]=" << outputs[i].node->attrs.name
          << '(' << outputs[i].index << ")\n";
@@ -129,7 +135,8 @@ void Symbol::Print(std::ostream &os) const {
         if (node->is_variable()) {
           os << "Variable:" << node->attrs.name << '\n';
         } else {
-          os << "Name: " << node->attrs.name << " Op:" << node->op->name << '\n'
+          os << "--------------------\n";
+          os << "Op:" << node->op->name << ", Name=" << node->attrs.name << '\n'
              << "Inputs:\n";
           for (size_t i = 0; i < node->inputs.size(); ++i) {
             const NodeEntry& e = node->inputs[i];
@@ -141,9 +148,17 @@ void Symbol::Print(std::ostream &os) const {
               os << '\n';
             }
           }
-          os << "Attrs:\n";
-          for (auto &kv : node->attrs.dict) {
-            os << '\t' << kv.first << '=' << kv.second << '\n';
+          if (!node->attrs.dict.empty()) {
+            os << "Attrs:\n";
+            for (auto &kv : node->attrs.dict) {
+              os << '\t' << kv.first << '=' << kv.second << '\n';
+            }
+          }
+          if (node->control_deps.size() != 0) {
+            os << "Control deps:\n";
+            for (size_t i = 0; i < node->control_deps.size(); ++i) {
+              os << "\tcdep[" << i << "]=" << node->control_deps[i]->attrs.name << '\n';
+            }
           }
         }
       });
@@ -203,8 +218,8 @@ std::vector<std::string> Symbol::ListOutputs() const {
 }
 
 // compositional logic
-void Symbol::Compose(const std::vector<Symbol>& args,
-                     const std::unordered_map<std::string, Symbol>& kwargs,
+void Symbol::Compose(const array_view<const Symbol*>& args,
+                     const std::unordered_map<std::string, const Symbol*>& kwargs,
                      const std::string& name) {
   static auto& flist_inputs = Op::GetAttr<FListInputNames>("FListInputNames");
 
@@ -213,11 +228,11 @@ void Symbol::Compose(const std::vector<Symbol>& args,
   CHECK(!outputs[0].node->is_variable()) << "Variable cannot be composed";
   // parameter check.
   for (size_t i = 0; i < args.size(); ++i) {
-    CHECK_EQ(args[i].outputs.size(), 1)
+    CHECK_EQ(args[i]->outputs.size(), 1)
         << "Argument " << i << " is a tuple, single value is required";
   }
   for (const auto& kv : kwargs) {
-    CHECK_EQ(kv.second.outputs.size(), 1)
+    CHECK_EQ(kv.second->outputs.size(), 1)
         << "Keyword Argument " << kv.first << " is a tuple, single value is required";
   }
   // assign new name
@@ -234,7 +249,7 @@ void Symbol::Compose(const std::vector<Symbol>& args,
           << "Incorrect number of arguments, requires " << n_req
           << ", provided " << args.size();
       for (size_t i = 0; i < args.size(); ++i) {
-        n->inputs[i] = args[i].outputs[0];
+        n->inputs[i] = args[i]->outputs[0];
       }
       // switch to keyword argument matching
       if (args.size() != n_req) {
@@ -247,7 +262,7 @@ void Symbol::Compose(const std::vector<Symbol>& args,
         for (size_t i = args.size(); i < n_req; ++i) {
           auto it = kwargs.find(arg_names[i]);
           if (it != kwargs.end() && it->first == arg_names[i]) {
-            n->inputs[i] = it->second.outputs[0];
+            n->inputs[i] = it->second->outputs[0];
             ++nmatched;
           } else {
             n->inputs[i] = NodeEntry{
@@ -266,8 +281,8 @@ void Symbol::Compose(const std::vector<Symbol>& args,
     } else {
       CHECK_EQ(kwargs.size(), 0) << "Variable length function do not accept kwargs";
       n->inputs.reserve(args.size());
-      for (const Symbol& s : args) {
-        n->inputs.push_back(s.outputs[0]);
+      for (const Symbol* s : args) {
+        n->inputs.push_back(s->outputs[0]);
       }
     }
     UpdateNodeVersion(n);
@@ -283,13 +298,13 @@ void Symbol::Compose(const std::vector<Symbol>& args,
         (const std::shared_ptr<Node> &node) {
       if (node->is_variable()) {
         if (arg_counter < args.size()) {
-          replace_map[node.get()] = &(args[arg_counter].outputs[0]);
+          replace_map[node.get()] = &(args[arg_counter]->outputs[0]);
           ++arg_counter;
         } else {
             // match kwargs
           auto kit = kwargs.find(node->attrs.name);
           if (kit != kwargs.end()) {
-            replace_map[node.get()] = &(kit->second.outputs[0]);
+            replace_map[node.get()] = &(kit->second->outputs[0]);
             ++nmatched;
           }
         }
@@ -334,8 +349,8 @@ void Symbol::Compose(const std::vector<Symbol>& args,
   }
 }
 
-Symbol Symbol::operator () (const std::vector<Symbol>& args,
-                            const std::unordered_map<std::string, Symbol>& kwargs,
+Symbol Symbol::operator () (const array_view<const Symbol*>& args,
+                            const std::unordered_map<std::string, const Symbol*>& kwargs,
                             const std::string& name) const {
   Symbol s = this->Copy();
   s.Compose(args, kwargs, name);
diff --git a/nnvm/src/pass/order_mutation.cc b/nnvm/src/pass/order_mutation.cc
new file mode 100644
index 000000000000..d0d527472389
--- /dev/null
+++ b/nnvm/src/pass/order_mutation.cc
@@ -0,0 +1,142 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file saveload_json.cc
+ * \brief Add control flow dependencies between nodes
+ *  To correctly order mutation and read to resolve
+ *  write after read problem and read after write problems.
+ */
+#include <nnvm/pass.h>
+#include <nnvm/op_attr_types.h>
+
+namespace nnvm {
+
+template<typename T>
+inline T get_with_default(const std::unordered_map<Node*, T> &map,
+                          Node* key,
+                          const T& def) {
+  auto it = map.find(key);
+  if (it != map.end()) return it->second;
+  return def;
+}
+
+Graph OrderMutation(const Graph& src) {
+  std::unordered_map<Node*, std::vector<NodeEntry> > version_hist;
+  DFSVisit(src.outputs, [&version_hist](const std::shared_ptr<Node>& n) {
+      for (const NodeEntry& e : n->inputs) {
+        if (e.node->is_variable()) {
+          if (e.version != 0 && version_hist.count(e.node.get()) == 0) {
+            version_hist[e.node.get()] = std::vector<NodeEntry>{};
+          }
+        }
+      }
+    });
+  // no mutation happens, everything if fine.
+  if (version_hist.size() == 0) return src;
+  // start preparing for remapping the nodes.
+  std::unordered_map<Node*, std::shared_ptr<Node> > old_new;
+  auto prepare = [&version_hist, &old_new] (const std::shared_ptr<Node>& n) {
+    static auto& fmutate_inputs = Op::GetAttr<FMutateInput>("FMutateInput");
+    bool need_repl = false;
+    for (size_t i = 0; i < n->inputs.size(); ++i) {
+      const NodeEntry& e = n->inputs[i];
+      if (e.node->is_variable()) {
+        if (e.version != 0) need_repl = true;
+        auto it = version_hist.find(e.node.get());
+        if (it != version_hist.end()) {
+          std::vector<NodeEntry>& vec = it->second;
+          uint32_t is_mutate =
+              fmutate_inputs.count(n->op) ? fmutate_inputs[n->op](n->attrs, i) : 0;
+          vec.emplace_back(NodeEntry{n, is_mutate, e.version});
+        }
+      } else {
+        if (old_new.count(e.node.get()) != 0) need_repl = true;
+      }
+    }
+    for (const std::shared_ptr<Node>& p : n->control_deps) {
+      if (old_new.count(p.get()) != 0) need_repl = true;
+    }
+    if (need_repl) {
+      std::shared_ptr<Node> np = Node::Create();
+      np->op = n->op;
+      np->attrs = n->attrs;
+      old_new[n.get()] = std::move(np);
+    }
+  };
+  DFSVisit(src.outputs, prepare);
+  // comparator of history entry
+  auto comparator = [](const NodeEntry& a, const NodeEntry &b) {
+    if (a.version < b.version) return true;
+    if (a.version > b.version) return false;
+    return a.index > b.index;
+  };
+
+  for (auto &kv : version_hist) {
+    std::sort(kv.second.begin(), kv.second.end(), comparator);
+  }
+  // copy the nodes, as well as add control deps
+  for (auto &kv : old_new) {
+    // copy the nodes
+    for (const NodeEntry& e : kv.first->inputs) {
+      auto it = old_new.find(e.node.get());
+      if (it != old_new.end()) {
+        kv.second->inputs.emplace_back(NodeEntry{it->second, e.index, e.version});
+      } else {
+        kv.second->inputs.push_back(e);
+      }
+    }
+    for (const std::shared_ptr<Node>& p : kv.first->control_deps) {
+      kv.second->control_deps.emplace_back(
+          get_with_default(old_new, p.get(), p));
+    }
+    // add control deps
+    static auto& fmutate_inputs = Op::GetAttr<FMutateInput>("FMutateInput");
+    for (size_t i = 0; i < kv.first->inputs.size(); ++i) {
+      const NodeEntry& e = kv.first->inputs[i];
+      if (e.node->is_variable() && version_hist.count(e.node.get()) != 0) {
+        FMutateInput fmutate = fmutate_inputs.get(kv.first->op, nullptr);
+        uint32_t is_mutate = (fmutate == nullptr) ? 0 : fmutate(kv.first->attrs, i);
+        std::vector<NodeEntry>& vec = version_hist.at(e.node.get());
+
+        auto it = std::lower_bound(vec.begin(), vec.end(),
+                                   NodeEntry{nullptr, 1, e.version},
+                                   comparator);
+        if (is_mutate != 0) {
+          int read_dep = 0;
+          while (it != vec.begin()) {
+            --it;
+            if (it->index != 0) break;
+            ++read_dep;
+            // depend on previous read
+            kv.second->control_deps.push_back(
+                get_with_default(old_new, it->node.get(), it->node));
+          }
+          if (read_dep == 0 && it->index != 0) {
+            // depend on last write
+            kv.second->control_deps.push_back(
+                get_with_default(old_new, it->node.get(), it->node));
+          }
+        } else {
+          // depend on last write
+          if (it->index != 0) {
+            kv.second->control_deps.push_back(
+                get_with_default(old_new, it->node.get(), it->node));
+          }
+        }
+      }
+    }
+  }
+  Graph ret;
+  for (const NodeEntry &e : src.outputs) {
+    ret.outputs.emplace_back(NodeEntry{
+        get_with_default(old_new, e.node.get(), e.node), e.index, e.version});
+  }
+  return ret;
+}
+
+NNVM_REGISTER_PASS(OrderMutation)
+.describe("Return a new graph that adds control dependencies, "\
+          "to order the mutation and reads if mutation exists.")
+.set_body(OrderMutation)
+.set_change_graph(true);
+
+}  // namespace nnvm
diff --git a/nnvm/src/pass/saveload_json.cc b/nnvm/src/pass/saveload_json.cc
index fee94fbf6eb9..4aa979e9cee9 100644
--- a/nnvm/src/pass/saveload_json.cc
+++ b/nnvm/src/pass/saveload_json.cc
@@ -1,7 +1,7 @@
 /*!
  *  Copyright (c) 2016 by Contributors
  * \file saveload_json.cc
- * \brief Passes that defines save and load graph to/from JSON file.
+ * \brief Save and load graph to/from JSON file.
  */
 #include <nnvm/pass.h>
 #include <dmlc/json.h>
diff --git a/nnvm/src/test_main.cc b/nnvm/src/test_main.cc
index 51d4ec1513cf..b79573c05513 100644
--- a/nnvm/src/test_main.cc
+++ b/nnvm/src/test_main.cc
@@ -2,42 +2,85 @@
 #include <nnvm/op.h>
 #include <nnvm/graph.h>
 #include <nnvm/tuple.h>
+#include <nnvm/c_api.h>
 #include <nnvm/graph_attr_types.h>
+#include <dmlc/timer.h>
 #include <string>
 
-void test_op() {
-  using namespace nnvm;
-  auto add = Op::Get("add");
-  static auto& nick = Op::GetAttr<std::string>("nick_name");
-  LOG(INFO) << "nick=" << nick[add];
+void test_speed() {
+  auto add = nnvm::Op::Get("add");
+  double tstart = dmlc::GetTime();
+  size_t rep = 1000;
+  size_t n = 1000;
+  std::unordered_map<std::string, const nnvm::Symbol*> tmp;
+  std::vector<const nnvm::Symbol*> vec{2};
+  std::string name = "xx";
+  for (size_t t = 0; t < rep; ++t) {
+    nnvm::Symbol s = nnvm::Symbol::CreateVariable("x");
+    for (size_t i = 0; i < n; ++i) {
+      nnvm::Symbol nw = nnvm::Symbol::CreateFunctor(add, {});
+      vec[0] = &s;
+      vec[1] =&s;
+      tmp.clear();
+      nw.Compose(vec, tmp, name);
+      s = nw;
+    }
+  }
+  double tend = dmlc::GetTime();
+  LOG(INFO) << "compose speed = " << n * rep / (tend - tstart) << " ops/sec";
 }
 
-void test_tuple() {
-  using nnvm::Tuple;
-  using nnvm::TShape;
-  Tuple<int> x{1, 2, 3};
-  Tuple<int> y{1, 2, 3, 5, 6};
-  x = std::move(y);
-
-  CHECK_EQ(x.ndim(), 5);
-  Tuple<int> z{1, 2, 3, 5, 6};
-  std::ostringstream os;
-  os << z;
-  CHECK_EQ(os.str(), "(1,2,3,5,6)");
-  std::istringstream is(os.str());
-  is >> y;
-  CHECK_EQ(x, y);
-  Tuple<nnvm::index_t> ss{1, 2, 3};
-  TShape s = ss;
-  s = std::move(ss);
-  CHECK((s == TShape{1, 2, 3}));
+void test_node_speed() {
+  using namespace nnvm;
+  auto add = nnvm::Op::Get("add");
+  double tstart = dmlc::GetTime();
+  size_t rep = 1000;
+  size_t n = 100;
+  for (size_t t = 0; t < rep; ++t) {
+    nnvm::Symbol s = nnvm::Symbol::CreateVariable("x");
+    for (size_t i = 0; i < n; ++i) {
+      auto xx = NodeEntry{Node::Create(), 0, 0};
+      NodeEntry x = s.outputs[0];
+      xx.node->op = add;
+      xx.node->inputs.emplace_back(x);
+      xx.node->inputs.emplace_back(x);
+      Symbol ss;
+      ss.outputs.push_back(xx);
+      s = ss;
+    }
+  }
+  double tend = dmlc::GetTime();
+  LOG(INFO) << "test_node_speed speed = " << n * rep / (tend - tstart) << " ops/sec";
 }
 
-
-void test_graph() {
-  nnvm::Symbol s;
+void test_api_speed() {
+  auto add = (void*)nnvm::Op::Get("add");  // NOLINT(*)
+  double tstart = dmlc::GetTime();
+  size_t rep = 1000;
+  size_t n = 1000;
+  std::unordered_map<std::string, const nnvm::Symbol*> tmp;
+  std::vector<const nnvm::Symbol*> vec{2};
+  std::string name = "xx";
+  for (size_t t = 0; t < rep; ++t) {
+    SymbolHandle s;
+    NNSymbolCreateVariable("xx", &s);
+    for (size_t i = 0; i < n; ++i) {
+      SymbolHandle arg[2];
+      SymbolHandle ss;
+      NNSymbolCreateAtomicSymbol(add, 0, nullptr, nullptr, &ss);
+      arg[0] = s;
+      arg[1] = s;
+      NNSymbolCompose(ss, "nn", 2, nullptr, arg);
+      s = ss;
+    }
+  }
+  double tend = dmlc::GetTime();
+  LOG(INFO) << "API compose speed = " << n * rep / (tend - tstart) << " ops/sec";
 }
+
 int main() {
-  test_tuple();
+  test_speed();
+  test_node_speed();
+  test_api_speed();
   return 0;
 }
diff --git a/nnvm/tests/python/test_graph.py b/nnvm/tests/python/test_graph.py
index 8fa392db3425..b6082364458e 100644
--- a/nnvm/tests/python/test_graph.py
+++ b/nnvm/tests/python/test_graph.py
@@ -1,3 +1,4 @@
+import json
 import nnvm.symbol as sym
 import nnvm.graph as graph
 
@@ -17,7 +18,24 @@ def test_graph_json_attr():
     g._set_json_attr('ilist', [1,2,3], 'list_int')
     assert g.json_attr('ilist') == [1,2,3]
 
+def test_order_mutation_pass():
+    x = sym.Variable('x')
+    y = sym.conv2d(data=x, name='conv', dev='gpu')
+    y = sym.add(y, x, name='add1')
+    # write after read
+    z = sym.assign(x, y, name='assign')
+    # read after write
+    t = sym.add(y, x, name='add2')
+    g = graph.create(sym.Group([t, z]))
+    jgraph = json.loads(g.apply(['OrderMutation', 'SaveJSON']).json_attr('json'))
+    jnodes = jgraph['nodes']
+    nindex = {n['name']: i for i, n in enumerate(jnodes)}
+    assert nindex['assign'] in jnodes[nindex['add2']]['control_deps']
+    assert nindex['conv'] in jnodes[nindex['assign']]['control_deps']
+    assert nindex['add1'] in jnodes[nindex['assign']]['control_deps']
+
 
 if __name__ == "__main__":
+    test_order_mutation_pass()
     test_graph_json_attr()
     test_json_pass()
diff --git a/nnvm/tests/python/test_symbol.py b/nnvm/tests/python/test_symbol.py
index 08d24536084d..8259862152ae 100644
--- a/nnvm/tests/python/test_symbol.py
+++ b/nnvm/tests/python/test_symbol.py
@@ -36,8 +36,16 @@ def test_mutate_input():
     except NNVMError:
         pass
 
+def test_copy():
+    x = sym.Variable('x')
+    z = sym.Variable('z')
+    y = sym.exp(sym.add(x, x, name='add', gpu=2),
+                name='exp', gpu=1, attr={"kk": "1"})
+
+    assert y.__copy__().debug_str() == y.debug_str()
 
 if __name__ == "__main__":
+    test_copy()
     test_default_input()
     test_compose()
     test_mutate_input()

From 254d177f3e9e5165ed7dd9af10c07cd27fb707b7 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 14 Jul 2016 17:14:58 -0700
Subject: [PATCH 337/949] Rename shared_ptr<Node> to NodePtr (#8)

---
 nnvm/include/nnvm/node.h        | 15 ++++++++++++---
 nnvm/src/core/node.cc           |  4 ++--
 nnvm/src/core/symbolic.cc       | 26 +++++++++++++-------------
 nnvm/src/pass/order_mutation.cc | 12 ++++++------
 nnvm/src/pass/saveload_json.cc  |  6 +++---
 5 files changed, 36 insertions(+), 27 deletions(-)

diff --git a/nnvm/include/nnvm/node.h b/nnvm/include/nnvm/node.h
index 7ca18760d173..b34a82791d8b 100644
--- a/nnvm/include/nnvm/node.h
+++ b/nnvm/include/nnvm/node.h
@@ -18,10 +18,19 @@ namespace nnvm {
 // Forward declare node.
 class Node;
 
+/*!
+ * \brief we always used NodePtr for a reference pointer
+ *  to the node, so this alias can be changed in case we need
+ *  even faster graph composition than 3M ops/sec.
+ *
+ *  By default, NodePtr is a std::shared_ptr of node
+ */
+using NodePtr = std::shared_ptr<Node>;
+
 /*! \brief an entry that represents output data from a node */
 struct NodeEntry {
   /*! \brief the source node of this data */
-  std::shared_ptr<Node> node;
+  NodePtr node;
   /*! \brief index of output from the source. */
   uint32_t index;
   /*!
@@ -66,7 +75,7 @@ class Node {
    * \brief Optional control flow dependencies
    *  Gives operation must be performed before this operation.
    */
-  std::vector<std::shared_ptr<Node> > control_deps;
+  std::vector<NodePtr> control_deps;
   /*! \brief The attributes in the node. */
   NodeAttrs attrs;
   /*! \brief destructor of node */
@@ -85,7 +94,7 @@ class Node {
    * \brief create a new empty shared_ptr of Node.
    * \return a created empty node.
    */
-  static std::shared_ptr<Node> Create();
+  static NodePtr Create();
 };
 
 // implementation of functions.
diff --git a/nnvm/src/core/node.cc b/nnvm/src/core/node.cc
index 72cea0479ddf..a3b2d9c9c68f 100644
--- a/nnvm/src/core/node.cc
+++ b/nnvm/src/core/node.cc
@@ -24,7 +24,7 @@ Node::~Node() {
           e.node.reset();
         }
       }
-      for (std::shared_ptr<Node>& sp : n->control_deps) {
+      for (NodePtr& sp : n->control_deps) {
         if (sp.unique()) {
           stack.push_back(sp.get());
         } else {
@@ -36,7 +36,7 @@ Node::~Node() {
   }
 }
 
-std::shared_ptr<Node> Node::Create() {
+NodePtr Node::Create() {
   // NOTE: possible change to thread local memory pool
   // via std::allocate_shared instead for faster allocation.
   return std::make_shared<Node>();
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index 3cd1d982e433..ccd13859cf9a 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -18,8 +18,8 @@ struct VariableParam {
   uint32_t version{0};
 };
 
-std::shared_ptr<Node> CreateVariableNode(const std::string& name) {
-  std::shared_ptr<Node> n = Node::Create();
+NodePtr CreateVariableNode(const std::string& name) {
+  NodePtr n = Node::Create();
   n->op = nullptr;
   n->attrs.name = name;
   n->attrs.parsed = VariableParam();
@@ -95,10 +95,10 @@ inline bool IsAtomic(const std::vector<NodeEntry>& outputs) {
 
 // public functions
 Symbol Symbol::Copy() const {
-  std::unordered_map<Node*, std::shared_ptr<Node> > old_new;
+  std::unordered_map<Node*, NodePtr> old_new;
   // use DFSVisit to copy all the nodes
-  DFSVisit(this->outputs, [&old_new](const std::shared_ptr<Node>& node) {
-      std::shared_ptr<Node> np = Node::Create();
+  DFSVisit(this->outputs, [&old_new](const NodePtr& node) {
+      NodePtr np = Node::Create();
       np->op = node->op;
       np->attrs = node->attrs;
       old_new[node.get()] = std::move(np);
@@ -109,7 +109,7 @@ Symbol Symbol::Copy() const {
       Node *ptr = e.node.get();
       kv.second->inputs.emplace_back(NodeEntry{old_new[ptr], e.index, e.version});
     }
-    for (const std::shared_ptr<Node>& p : kv.first->control_deps) {
+    for (const NodePtr& p : kv.first->control_deps) {
       kv.second->control_deps.emplace_back(old_new[p.get()]);
     }
   }
@@ -131,7 +131,7 @@ void Symbol::Print(std::ostream &os) const {
       os << "\toutput[" << i << "]=" << outputs[i].node->attrs.name
          << '(' << outputs[i].index << ")\n";
     }
-    DFSVisit(this->outputs, [&os](const std::shared_ptr<Node>& node) {
+    DFSVisit(this->outputs, [&os](const NodePtr& node) {
         if (node->is_variable()) {
           os << "Variable:" << node->attrs.name << '\n';
         } else {
@@ -179,7 +179,7 @@ Symbol Symbol::operator[] (size_t index) const {
 
 std::vector<std::string> Symbol::ListArguments() const {
   std::vector<std::string> ret;
-  DFSVisit(this->outputs, [&ret](const std::shared_ptr<Node> &node) {
+  DFSVisit(this->outputs, [&ret](const NodePtr &node) {
       if (node->is_variable()) {
         ret.push_back(node->attrs.name);
       }
@@ -295,7 +295,7 @@ void Symbol::Compose(const array_view<const Symbol*>& args,
     std::unordered_map<Node *, const NodeEntry*> replace_map;
     // replace map stores the existing replacement plan for arguments node
     auto find_replace_map = [&nmatched, &arg_counter, &args, &kwargs, &replace_map]
-        (const std::shared_ptr<Node> &node) {
+        (const NodePtr &node) {
       if (node->is_variable()) {
         if (arg_counter < args.size()) {
           replace_map[node.get()] = &(args[arg_counter]->outputs[0]);
@@ -316,7 +316,7 @@ void Symbol::Compose(const array_view<const Symbol*>& args,
       std::vector<Node*> update_nodes;
       std::vector<std::pair<NodeEntry*, const NodeEntry*> > replace_plan;
       auto find_replace_plan = [&replace_map, &replace_plan, &update_nodes]
-          (const std::shared_ptr<Node> &node) {
+          (const NodePtr &node) {
         // visit all the childs, find possible replacement
         bool repl = false;
         for (size_t i = 0; i < node->inputs.size(); ++i) {
@@ -368,7 +368,7 @@ void Symbol::AddControlDeps(const Symbol& src) {
 
 Symbol Symbol::GetInternals() const {
   Symbol ret;
-  DFSVisit(this->outputs, [&ret](const std::shared_ptr<Node>& node) {
+  DFSVisit(this->outputs, [&ret](const NodePtr& node) {
       Node* n = node.get();
       if (n->is_variable()) {
         // grab version from variable.
@@ -421,7 +421,7 @@ bool Symbol::GetAttr(const std::string& key, std::string* out) const {
 std::unordered_map<std::string, std::string> Symbol::ListAttrs(ListAttrOption option) const {
   if (option == kRecursive) {
     std::unordered_map<std::string, std::string> ret;
-    DFSVisit(this->outputs, [&ret](const std::shared_ptr<Node>& n) {
+    DFSVisit(this->outputs, [&ret](const NodePtr& n) {
         for (const auto& it : n->attrs.dict) {
           ret[n->attrs.name + symbol_constants::kNamespaceSeparator + it.first] = it.second;
         }
@@ -435,7 +435,7 @@ std::unordered_map<std::string, std::string> Symbol::ListAttrs(ListAttrOption op
 Symbol Symbol::CreateFunctor(const Op* op,
                              std::unordered_map<std::string, std::string>&& attrs) {
   Symbol s;
-  std::shared_ptr<Node> n = Node::Create();
+  NodePtr n = Node::Create();
   n->op = op;
   n->attrs.dict = std::move(attrs);
   if (n->op->attr_parser != nullptr) {
diff --git a/nnvm/src/pass/order_mutation.cc b/nnvm/src/pass/order_mutation.cc
index d0d527472389..ecaeadeaadb4 100644
--- a/nnvm/src/pass/order_mutation.cc
+++ b/nnvm/src/pass/order_mutation.cc
@@ -21,7 +21,7 @@ inline T get_with_default(const std::unordered_map<Node*, T> &map,
 
 Graph OrderMutation(const Graph& src) {
   std::unordered_map<Node*, std::vector<NodeEntry> > version_hist;
-  DFSVisit(src.outputs, [&version_hist](const std::shared_ptr<Node>& n) {
+  DFSVisit(src.outputs, [&version_hist](const NodePtr& n) {
       for (const NodeEntry& e : n->inputs) {
         if (e.node->is_variable()) {
           if (e.version != 0 && version_hist.count(e.node.get()) == 0) {
@@ -33,8 +33,8 @@ Graph OrderMutation(const Graph& src) {
   // no mutation happens, everything if fine.
   if (version_hist.size() == 0) return src;
   // start preparing for remapping the nodes.
-  std::unordered_map<Node*, std::shared_ptr<Node> > old_new;
-  auto prepare = [&version_hist, &old_new] (const std::shared_ptr<Node>& n) {
+  std::unordered_map<Node*, NodePtr> old_new;
+  auto prepare = [&version_hist, &old_new] (const NodePtr& n) {
     static auto& fmutate_inputs = Op::GetAttr<FMutateInput>("FMutateInput");
     bool need_repl = false;
     for (size_t i = 0; i < n->inputs.size(); ++i) {
@@ -52,11 +52,11 @@ Graph OrderMutation(const Graph& src) {
         if (old_new.count(e.node.get()) != 0) need_repl = true;
       }
     }
-    for (const std::shared_ptr<Node>& p : n->control_deps) {
+    for (const NodePtr& p : n->control_deps) {
       if (old_new.count(p.get()) != 0) need_repl = true;
     }
     if (need_repl) {
-      std::shared_ptr<Node> np = Node::Create();
+      NodePtr np = Node::Create();
       np->op = n->op;
       np->attrs = n->attrs;
       old_new[n.get()] = std::move(np);
@@ -84,7 +84,7 @@ Graph OrderMutation(const Graph& src) {
         kv.second->inputs.push_back(e);
       }
     }
-    for (const std::shared_ptr<Node>& p : kv.first->control_deps) {
+    for (const NodePtr& p : kv.first->control_deps) {
       kv.second->control_deps.emplace_back(
           get_with_default(old_new, p.get(), p));
     }
diff --git a/nnvm/src/pass/saveload_json.cc b/nnvm/src/pass/saveload_json.cc
index 4aa979e9cee9..b454d4f7a736 100644
--- a/nnvm/src/pass/saveload_json.cc
+++ b/nnvm/src/pass/saveload_json.cc
@@ -32,7 +32,7 @@ struct JSONNode {
   // the node entry structure in serialized format
   typedef std::pair<uint32_t, uint32_t> Entry;
   // pointer to the graph node
-  std::shared_ptr<Node> node;
+  NodePtr node;
   // inputs
   std::vector<Entry> inputs;
   // control flow dependencies
@@ -159,7 +159,7 @@ Graph LoadJSON(const Graph& src) {
 Graph SaveJSON(const Graph& src) {
   JSONGraph jgraph;
   std::unordered_map<Node*, uint32_t> node2index;
-  DFSVisit(src.outputs, [&node2index, &jgraph](const std::shared_ptr<Node>& n) {
+  DFSVisit(src.outputs, [&node2index, &jgraph](const NodePtr& n) {
       uint32_t nid = static_cast<uint32_t>(jgraph.nodes.size());
       node2index[n.get()] = nid;
       if (n->is_variable()) {
@@ -172,7 +172,7 @@ Graph SaveJSON(const Graph& src) {
         jnode.inputs.emplace_back(
             std::make_pair(node2index.at(e.node.get()), e.index));
       }
-      for (const std::shared_ptr<Node>& c : n->control_deps) {
+      for (const NodePtr& c : n->control_deps) {
         jnode.control_deps.push_back(node2index.at(c.get()));
       }
       jgraph.nodes.emplace_back(std::move(jnode));

From d07cb6756431319b8d13edda4e452a3f358aed90 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 14 Jul 2016 20:06:51 -0700
Subject: [PATCH 338/949] Introduce NodePtr (#9)

---
 nnvm/include/nnvm/base.h          | 1 +
 nnvm/include/nnvm/graph.h         | 2 +-
 nnvm/include/nnvm/node.h          | 3 +--
 nnvm/src/core/graph_attr_types.cc | 2 +-
 nnvm/src/core/node.cc             | 4 +---
 nnvm/src/test_main.cc             | 2 +-
 6 files changed, 6 insertions(+), 8 deletions(-)

diff --git a/nnvm/include/nnvm/base.h b/nnvm/include/nnvm/base.h
index 92175cf17a8f..31e53dc2fa2f 100644
--- a/nnvm/include/nnvm/base.h
+++ b/nnvm/include/nnvm/base.h
@@ -8,6 +8,7 @@
 
 #include <dmlc/base.h>
 #include <dmlc/any.h>
+#include <dmlc/memory.h>
 #include <dmlc/logging.h>
 #include <dmlc/registry.h>
 #include <dmlc/array_view.h>
diff --git a/nnvm/include/nnvm/graph.h b/nnvm/include/nnvm/graph.h
index 5269f8c33e19..da1c952ac9b3 100644
--- a/nnvm/include/nnvm/graph.h
+++ b/nnvm/include/nnvm/graph.h
@@ -81,7 +81,7 @@ void PostOrderDFSVisit(const std::vector<GNode>& heads,
 template<typename FVisit>
 inline void DFSVisit(const std::vector<NodeEntry>& heads,
                      FVisit fvisit) {
-  typedef const std::shared_ptr<Node>* GNode;
+  typedef const NodePtr* GNode;
   std::vector<GNode> head_nodes(heads.size());
   std::transform(heads.begin(), heads.end(), head_nodes.begin(),
                  [](const NodeEntry& e)->GNode {
diff --git a/nnvm/include/nnvm/node.h b/nnvm/include/nnvm/node.h
index b34a82791d8b..ef9a30d01818 100644
--- a/nnvm/include/nnvm/node.h
+++ b/nnvm/include/nnvm/node.h
@@ -20,8 +20,7 @@ class Node;
 
 /*!
  * \brief we always used NodePtr for a reference pointer
- *  to the node, so this alias can be changed in case we need
- *  even faster graph composition than 3M ops/sec.
+ *  to the node, so this alias can be changed in case.
  *
  *  By default, NodePtr is a std::shared_ptr of node
  */
diff --git a/nnvm/src/core/graph_attr_types.cc b/nnvm/src/core/graph_attr_types.cc
index aaefa0626131..745a909bdf90 100644
--- a/nnvm/src/core/graph_attr_types.cc
+++ b/nnvm/src/core/graph_attr_types.cc
@@ -14,7 +14,7 @@ IndexedGraph::IndexedGraph(const Graph &g) {
   std::vector<size_t> inputs_rptr{0}, control_rptr{0};
 
   DFSVisit(g.outputs, [this, &inputs_rptr, &control_rptr]
-             (const std::shared_ptr<nnvm::Node>& n) {
+             (const NodePtr& n) {
       CHECK_LT(nodes_.size(), std::numeric_limits<uint32_t>::max());
       uint32_t nid = static_cast<uint32_t>(nodes_.size());
       // nodes_
diff --git a/nnvm/src/core/node.cc b/nnvm/src/core/node.cc
index a3b2d9c9c68f..a6f50f694989 100644
--- a/nnvm/src/core/node.cc
+++ b/nnvm/src/core/node.cc
@@ -12,7 +12,7 @@ Node::~Node() {
     // explicit deletion via DFS
     // this is used to avoid stackoverflow caused by chain of deletions
     std::vector<Node*> stack{this};
-    std::vector<std::shared_ptr<Node> > to_delete;
+    std::vector<NodePtr> to_delete;
     while (!stack.empty()) {
       Node* n = stack.back();
       stack.pop_back();
@@ -37,8 +37,6 @@ Node::~Node() {
 }
 
 NodePtr Node::Create() {
-  // NOTE: possible change to thread local memory pool
-  // via std::allocate_shared instead for faster allocation.
   return std::make_shared<Node>();
 }
 
diff --git a/nnvm/src/test_main.cc b/nnvm/src/test_main.cc
index b79573c05513..4d14abced5ab 100644
--- a/nnvm/src/test_main.cc
+++ b/nnvm/src/test_main.cc
@@ -35,7 +35,7 @@ void test_node_speed() {
   auto add = nnvm::Op::Get("add");
   double tstart = dmlc::GetTime();
   size_t rep = 1000;
-  size_t n = 100;
+  size_t n = 1000;
   for (size_t t = 0; t < rep; ++t) {
     nnvm::Symbol s = nnvm::Symbol::CreateVariable("x");
     for (size_t i = 0; i < n; ++i) {

From 9de8254ca6a8abb588080ec7f6aceead14180417 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 15 Jul 2016 12:42:48 -0700
Subject: [PATCH 339/949] Check in a experimental cython API (#10)

---
 nnvm/Makefile                |   7 +-
 nnvm/include/nnvm/c_api.h    |   4 +-
 nnvm/python/.gitignore       |   2 +
 nnvm/python/nnvm/symbolx.pyd |   6 +
 nnvm/python/nnvm/symbolx.pyx | 220 +++++++++++++++++++++++++++++++++++
 nnvm/python/setup.py         |  13 +++
 nnvm/src/core/symbolic.cc    |   6 +-
 7 files changed, 254 insertions(+), 4 deletions(-)
 create mode 100644 nnvm/python/.gitignore
 create mode 100644 nnvm/python/nnvm/symbolx.pyd
 create mode 100644 nnvm/python/nnvm/symbolx.pyx
 create mode 100644 nnvm/python/setup.py

diff --git a/nnvm/Makefile b/nnvm/Makefile
index 78a56b8158e2..84040be98292 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -1,9 +1,9 @@
 export LDFLAGS = -pthread -lm
 export CFLAGS =  -std=c++11 -Wall -O3 -msse2  -Wno-unknown-pragmas -funroll-loops\
-	 -Iinclude -Idmlc-core/include -fPIC
+	 -Iinclude -Idmlc-core/include -I../include -fPIC -L../lib
 
 # specify tensor path
-.PHONY: clean all test lint doc
+.PHONY: clean all test lint doc python
 
 all: lib/libnnvm.so lib/libnnvm.a cli_test
 
@@ -31,6 +31,9 @@ lib/libnnvm.a: $(ALL_DEP)
 cli_test: $(ALL_DEP) build/test_main.o
 	$(CXX) $(CFLAGS)  -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
 
+python:
+	cd python; python setup.py build_ext --inplace
+
 lint:
 	python2 dmlc-core/scripts/lint.py nnvm cpp include src
 
diff --git a/nnvm/include/nnvm/c_api.h b/nnvm/include/nnvm/c_api.h
index 46ecf2d510dc..3b352e3d8df6 100644
--- a/nnvm/include/nnvm/c_api.h
+++ b/nnvm/include/nnvm/c_api.h
@@ -10,6 +10,8 @@
 
 #ifdef __cplusplus
 #define NNVM_EXTERN_C extern "C"
+#else
+#define NNVM_EXTERN_C
 #endif
 
 /*! \brief NNVM_DLL prefix for windows */
@@ -42,7 +44,7 @@ typedef void *GraphHandle;
  *  this function is threadsafe and can be called by different thread
  *  \return error info
  */
-NNVM_DLL const char *NNGetLastError();
+NNVM_DLL const char *NNGetLastError(void);
 
 /*!
  * \brief list all the available AtomicSymbolEntry
diff --git a/nnvm/python/.gitignore b/nnvm/python/.gitignore
new file mode 100644
index 000000000000..40d7cb4cc13a
--- /dev/null
+++ b/nnvm/python/.gitignore
@@ -0,0 +1,2 @@
+*.c
+*.cpp
diff --git a/nnvm/python/nnvm/symbolx.pyd b/nnvm/python/nnvm/symbolx.pyd
new file mode 100644
index 000000000000..a5df49d25f54
--- /dev/null
+++ b/nnvm/python/nnvm/symbolx.pyd
@@ -0,0 +1,6 @@
+ctypedef void* SymbolHandle
+
+
+cdef class Symbol:
+    # handle for symbolic operator.
+    cdef SymbolHandle handle
diff --git a/nnvm/python/nnvm/symbolx.pyx b/nnvm/python/nnvm/symbolx.pyx
new file mode 100644
index 000000000000..5264a391fcf5
--- /dev/null
+++ b/nnvm/python/nnvm/symbolx.pyx
@@ -0,0 +1,220 @@
+import sys
+from libcpp.vector cimport vector
+
+ctypedef void* SymbolHandle
+ctypedef void* AtomicSymbolCreator
+ctypedef unsigned nn_uint
+
+cdef extern from "nnvm/c_api.h":
+    int NNSymbolFree(SymbolHandle symbol)
+    int NNSymbolCreateVariable(const char *name, SymbolHandle *out)
+    const char* NNGetLastError()
+    int NNSymbolPrint(SymbolHandle symbol, const char **out_str)
+    int NNSymbolListAtomicSymbolCreators(nn_uint *out_size,
+                                         AtomicSymbolCreator **out_array);
+    int NNSymbolCreateAtomicSymbol(AtomicSymbolCreator creator,
+                                   nn_uint num_param,
+                                   const char **keys,
+                                   const char **vals,
+                                   SymbolHandle *out);
+    int NNSymbolGetAtomicSymbolInfo(AtomicSymbolCreator creator,
+                                    const char **name,
+                                    const char **description,
+                                    nn_uint *num_doc_args,
+                                    const char ***arg_names,
+                                    const char ***arg_type_infos,
+                                    const char ***arg_descriptions,
+                                    const char **return_type);
+    int NNSymbolCompose(SymbolHandle sym,
+                        const char* name,
+                        nn_uint num_args,
+                        const char** keys,
+                        SymbolHandle* args);
+
+cdef CALL(int ret):
+    if ret != 0:
+        raise RuntimeError(NNGetLastError())
+
+cdef const char** CBeginPtr(vector[const char*]& vec):
+    if (vec.size() != 0):
+        return &vec[0]
+    else:
+        return NULL
+
+cdef ctypes2docstring(nn_uint num_args,
+                      const char** arg_names,
+                      const char** arg_types,
+                      const char** arg_descs,
+                      remove_dup=True):
+    """Convert ctypes returned doc string information into parameters docstring.
+
+    num_args : nn_uint
+        Number of arguments.
+
+    arg_names : ctypes.POINTER(ctypes.c_char_p)
+        Argument names.
+
+    arg_types : ctypes.POINTER(ctypes.c_char_p)
+        Argument type information.
+
+    arg_descs : ctypes.POINTER(ctypes.c_char_p)
+        Argument description information.
+
+    remove_dup : boolean, optional
+        Whether remove duplication or not.
+
+    Returns
+    -------
+    docstr : str
+        Python docstring of parameter sections.
+    """
+    param_keys = set()
+    param_str = []
+    for i in range(num_args):
+        key = arg_names[i]
+        if key in param_keys and remove_dup:
+            continue
+        param_keys.add(key)
+        type_info = arg_types[i]
+        ret = '%s : %s' % (key, type_info)
+        if len(arg_descs[i]) != 0:
+            ret += '\n    ' + arg_descs[i]
+        param_str.append(ret)
+    doc_str = ('Parameters\n' +
+               '----------\n' +
+               '%s\n')
+    doc_str = doc_str % ('\n'.join(param_str))
+    return doc_str
+
+
+cdef class Symbol:
+    # handle for symbolic operator.
+    cdef SymbolHandle handle
+
+    def __dealloc__(self):
+        CALL(NNSymbolFree(self.handle))
+
+    def debug_str(self):
+        cdef const char* out_str
+        CALL(NNSymbolPrint(self.handle, &out_str))
+        return str(out_str)
+
+cdef NewSymbol(SymbolHandle handle):
+    """Create a new symbol given handle"""
+    sym = Symbol()
+    sym.handle = handle
+    return sym
+
+
+def Variable(const char* name, **kwargs):
+    """Create a symbolic variable with specified name.
+
+    Parameters
+    ----------
+    name : str
+        Name of the variable.
+    kwargs : dict of string -> string
+        Additional attributes to set on the variable.
+
+    Returns
+    -------
+    variable : Symbol
+        The created variable symbol.
+    """
+    cdef SymbolHandle handle
+    CALL(NNSymbolCreateVariable(name, &handle))
+    return NewSymbol(handle)
+
+
+cdef _make_atomic_symbol_function(AtomicSymbolCreator handle):
+    """Create an atomic symbol function by handle and funciton name."""
+    cdef const char *name
+    cdef const char *desc
+    cdef nn_uint num_args
+    cdef const char** arg_names
+    cdef const char** arg_types
+    cdef const char** arg_descs
+    cdef const char* return_type
+
+    CALL(NNSymbolGetAtomicSymbolInfo(
+        handle, &name, &desc,
+        &num_args, &arg_names,
+        &arg_types, &arg_descs,
+        &return_type))
+
+    param_str = ctypes2docstring(num_args, arg_names, arg_types, arg_descs)
+    func_name = name
+    doc_str = ('%s\n\n' +
+               '%s\n' +
+               'name : string, optional.\n' +
+               '    Name of the resulting symbol.\n\n' +
+               'Returns\n' +
+               '-------\n' +
+               'symbol: Symbol\n' +
+               '    The result symbol.')
+    doc_str = doc_str % (desc, param_str)
+
+    def creator(*args, **kwargs):
+        cdef vector[const char*] param_keys
+        cdef vector[const char*] param_vals
+        cdef vector[SymbolHandle] symbol_args
+        cdef vector[const char*] symbol_keys
+        cdef SymbolHandle ret_handle
+        cdef const char* c_name = NULL
+
+        name = kwargs.pop('name', None)
+        attr = kwargs.pop('attr', None)
+        if name:
+            c_name = name
+
+        if len(kwargs) != 0:
+            for k, v in kwargs.items():
+                if isinstance(v, Symbol):
+                    symbol_keys.push_back(k)
+                    symbol_args.push_back((<Symbol>v).handle)
+                else:
+                    param_keys.push_back(k)
+                    param_vals.push_back(str(v))
+
+        if len(args) != 0:
+            if symbol_args.size() != 0:
+                raise TypeError("compose only accept input Symbols\
+                    either as positional or keyword arguments, not both")
+            for v in args:
+                if not isinstance(v, Symbol):
+                    raise TypeError('Compose expect `Symbol` as arguments')
+                symbol_args.push_back((<Symbol>v).handle)
+
+        CALL(NNSymbolCreateAtomicSymbol(
+            handle,
+            <nn_uint>param_keys.size(),
+            CBeginPtr(param_keys),
+            CBeginPtr(param_vals),
+            &ret_handle))
+        num_args = <nn_uint>(symbol_args.size())
+        CALL(NNSymbolCompose(
+            ret_handle, c_name, num_args,
+            &symbol_keys[0] if symbol_keys.size() != 0 else NULL,
+            &symbol_args[0] if symbol_args.size() != 0 else NULL))
+        return NewSymbol(ret_handle)
+
+    creator.__name__ = func_name
+    creator.__doc__ = doc_str
+    return creator
+
+
+def _init_symbol_module():
+    """List and add all the atomic symbol functions to current module."""
+    cdef AtomicSymbolCreator* plist
+    cdef nn_uint size
+    CALL(NNSymbolListAtomicSymbolCreators(&size, &plist))
+    module_obj = sys.modules[__name__]
+    for i in range(size):
+        function = _make_atomic_symbol_function(plist[i])
+        if function.__name__.startswith('_'):
+            setattr(Symbol, function.__name__, staticmethod(function))
+        else:
+            setattr(module_obj, function.__name__, function)
+
+# Initialize the atomic symbol in startups
+_init_symbol_module()
diff --git a/nnvm/python/setup.py b/nnvm/python/setup.py
new file mode 100644
index 000000000000..e50d1aaccccb
--- /dev/null
+++ b/nnvm/python/setup.py
@@ -0,0 +1,13 @@
+from distutils.core import setup
+from Cython.Build import cythonize
+from distutils.extension import Extension
+
+setup(
+    name='nnvm',
+    ext_modules = cythonize([
+        Extension("nnvm/symbolx",
+                  ["nnvm/symbolx.pyx"],
+                  libraries=["nnvm"],
+                  language="c++")
+    ])
+)
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index ccd13859cf9a..dcfcc401e4b8 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -123,7 +123,11 @@ Symbol Symbol::Copy() const {
 
 void Symbol::Print(std::ostream &os) const {
   if (outputs.size() == 1 && outputs[0].node->inputs.size() == 0) {
-    os << "AtomicFunctor "<< " Op:" << outputs[0].node->op->name << '\n';
+    if (outputs[0].node->is_variable()) {
+      os << "Variable:" << outputs[0].node->attrs.name << '\n';
+    } else {
+      os << "AtomicFunctor "<< " Op:" << outputs[0].node->op->name << '\n';
+    }
   } else {
     // use DFSVisit to copy all the nodes
     os << "Symbol Outputs:\n";

From b639a637413f70df50c779df15392cae139a7dad Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 15 Jul 2016 16:57:51 -0700
Subject: [PATCH 340/949] Create a ctypes cython optional compatible package
 (#11)

---
 nnvm/Makefile                                 |  10 +-
 nnvm/python/nnvm/__init__.py                  |   7 +-
 nnvm/python/nnvm/{base.py => _base.py}        |   5 +-
 nnvm/python/nnvm/_cy2/README                  |   1 +
 nnvm/python/nnvm/_cy2/__init__.py             |   1 +
 nnvm/python/nnvm/_cy3/README                  |   1 +
 nnvm/python/nnvm/_cy3/__init__.py             |   1 +
 nnvm/python/nnvm/attribute.py                 |   3 +-
 nnvm/python/nnvm/ctypes/README                |   1 +
 nnvm/python/nnvm/ctypes/__init__.py           |   1 +
 nnvm/python/nnvm/ctypes/symbol.py             | 385 +++++++++++++++++
 nnvm/python/nnvm/cython/README                |   1 +
 nnvm/python/nnvm/cython/base.pyi              |  67 +++
 .../nnvm/{symbolx.pyd => cython/symbol.pyd}   |   0
 nnvm/python/nnvm/cython/symbol.pyx            | 376 +++++++++++++++++
 nnvm/python/nnvm/graph.py                     |   8 +-
 nnvm/python/nnvm/name.py                      |   2 +-
 nnvm/python/nnvm/symbol.py                    | 395 +-----------------
 nnvm/python/nnvm/symbolx.pyx                  | 220 ----------
 nnvm/python/setup.py                          |  28 +-
 nnvm/tests/python/test_symbol.py              |   2 +-
 21 files changed, 890 insertions(+), 625 deletions(-)
 rename nnvm/python/nnvm/{base.py => _base.py} (98%)
 create mode 100644 nnvm/python/nnvm/_cy2/README
 create mode 100644 nnvm/python/nnvm/_cy2/__init__.py
 create mode 100644 nnvm/python/nnvm/_cy3/README
 create mode 100644 nnvm/python/nnvm/_cy3/__init__.py
 create mode 100644 nnvm/python/nnvm/ctypes/README
 create mode 100644 nnvm/python/nnvm/ctypes/__init__.py
 create mode 100644 nnvm/python/nnvm/ctypes/symbol.py
 create mode 100644 nnvm/python/nnvm/cython/README
 create mode 100644 nnvm/python/nnvm/cython/base.pyi
 rename nnvm/python/nnvm/{symbolx.pyd => cython/symbol.pyd} (100%)
 create mode 100644 nnvm/python/nnvm/cython/symbol.pyx
 delete mode 100644 nnvm/python/nnvm/symbolx.pyx

diff --git a/nnvm/Makefile b/nnvm/Makefile
index 84040be98292..feba8d278183 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -1,9 +1,9 @@
 export LDFLAGS = -pthread -lm
 export CFLAGS =  -std=c++11 -Wall -O3 -msse2  -Wno-unknown-pragmas -funroll-loops\
-	 -Iinclude -Idmlc-core/include -I../include -fPIC -L../lib
+	 -Iinclude -Idmlc-core/include -fPIC
 
 # specify tensor path
-.PHONY: clean all test lint doc python
+.PHONY: clean all test lint doc cython cython3
 
 all: lib/libnnvm.so lib/libnnvm.a cli_test
 
@@ -31,9 +31,13 @@ lib/libnnvm.a: $(ALL_DEP)
 cli_test: $(ALL_DEP) build/test_main.o
 	$(CXX) $(CFLAGS)  -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
 
-python:
+cython:
 	cd python; python setup.py build_ext --inplace
 
+cython3:
+	cd python; python3 setup.py build_ext --inplace
+
+
 lint:
 	python2 dmlc-core/scripts/lint.py nnvm cpp include src
 
diff --git a/nnvm/python/nnvm/__init__.py b/nnvm/python/nnvm/__init__.py
index 38f943b9fd6c..d30b1c152e2b 100644
--- a/nnvm/python/nnvm/__init__.py
+++ b/nnvm/python/nnvm/__init__.py
@@ -1,10 +1,11 @@
 #!/usr/bin/env python
 # coding: utf-8
 """NNVM python API for ease of use and help new framework establish python API. """
-from __future__ import absolute_import
+from __future__ import absolute_import as _abs
 
-from . import base
+from . import _base
 from . import symbol as sym
 from . import symbol
+from ._base import NNVMError
 
-__version__ = base.__version__
+__version__ = _base.__version__
diff --git a/nnvm/python/nnvm/base.py b/nnvm/python/nnvm/_base.py
similarity index 98%
rename from nnvm/python/nnvm/base.py
rename to nnvm/python/nnvm/_base.py
index cf5ead2b4ab9..825a3d380f38 100644
--- a/nnvm/python/nnvm/base.py
+++ b/nnvm/python/nnvm/_base.py
@@ -4,6 +4,7 @@
 from __future__ import absolute_import
 
 import sys
+import os
 import ctypes
 import numpy as np
 from . import libinfo
@@ -31,7 +32,7 @@ class NNVMError(Exception):
 def _load_lib():
     """Load libary by searching possible path."""
     lib_path = libinfo.find_lib_path()
-    lib = ctypes.cdll.LoadLibrary(lib_path[0])
+    lib = ctypes.CDLL(lib_path[0], ctypes.RTLD_GLOBAL)
     # DMatrix functions
     lib.NNGetLastError.restype = ctypes.c_char_p
     return lib
@@ -41,13 +42,13 @@ def _load_lib():
 # library instance of nnvm
 _LIB = _load_lib()
 
+
 # type definitions
 nn_uint = ctypes.c_uint
 SymbolCreatorHandle = ctypes.c_void_p
 SymbolHandle = ctypes.c_void_p
 GraphHandle = ctypes.c_void_p
 
-
 #----------------------------
 # helper function definition
 #----------------------------
diff --git a/nnvm/python/nnvm/_cy2/README b/nnvm/python/nnvm/_cy2/README
new file mode 100644
index 000000000000..ed4639b674a0
--- /dev/null
+++ b/nnvm/python/nnvm/_cy2/README
@@ -0,0 +1 @@
+This folder is by default empty and will hold DLLs generated by cython.
diff --git a/nnvm/python/nnvm/_cy2/__init__.py b/nnvm/python/nnvm/_cy2/__init__.py
new file mode 100644
index 000000000000..910cbe2e586b
--- /dev/null
+++ b/nnvm/python/nnvm/_cy2/__init__.py
@@ -0,0 +1 @@
+"""Namespace for cython generated modules for python2"""
diff --git a/nnvm/python/nnvm/_cy3/README b/nnvm/python/nnvm/_cy3/README
new file mode 100644
index 000000000000..dc3a57603782
--- /dev/null
+++ b/nnvm/python/nnvm/_cy3/README
@@ -0,0 +1 @@
+This folder is by default empty and will hold DLLs generated by cython.
\ No newline at end of file
diff --git a/nnvm/python/nnvm/_cy3/__init__.py b/nnvm/python/nnvm/_cy3/__init__.py
new file mode 100644
index 000000000000..c3eb41421b3e
--- /dev/null
+++ b/nnvm/python/nnvm/_cy3/__init__.py
@@ -0,0 +1 @@
+"""Cython generated modules"""
diff --git a/nnvm/python/nnvm/attribute.py b/nnvm/python/nnvm/attribute.py
index b60ea0150492..a023b9cd88df 100644
--- a/nnvm/python/nnvm/attribute.py
+++ b/nnvm/python/nnvm/attribute.py
@@ -2,7 +2,7 @@
 """Attribute scoping support for symbolic API."""
 from __future__ import absolute_import
 
-from .base import string_types
+from ._base import string_types
 
 class AttrScope(object):
     """Attribute manager for scoping.
@@ -59,4 +59,3 @@ def __exit__(self, ptype, value, trace):
         AttrScope.current = self._old_scope
 
 AttrScope.current = AttrScope()
-
diff --git a/nnvm/python/nnvm/ctypes/README b/nnvm/python/nnvm/ctypes/README
new file mode 100644
index 000000000000..6e82cb962f99
--- /dev/null
+++ b/nnvm/python/nnvm/ctypes/README
@@ -0,0 +1 @@
+Ctypes specific implementation of certain modules
\ No newline at end of file
diff --git a/nnvm/python/nnvm/ctypes/__init__.py b/nnvm/python/nnvm/ctypes/__init__.py
new file mode 100644
index 000000000000..fc76dabf682b
--- /dev/null
+++ b/nnvm/python/nnvm/ctypes/__init__.py
@@ -0,0 +1 @@
+""""ctypes implementation of the Symbol"""
diff --git a/nnvm/python/nnvm/ctypes/symbol.py b/nnvm/python/nnvm/ctypes/symbol.py
new file mode 100644
index 000000000000..503fc09bde74
--- /dev/null
+++ b/nnvm/python/nnvm/ctypes/symbol.py
@@ -0,0 +1,385 @@
+# coding: utf-8
+# pylint: disable=invalid-name, protected-access, too-many-arguments, too-many-lines
+"""Symbolic configuration API."""
+from __future__ import absolute_import as _abs
+
+import copy
+import ctypes
+import sys
+from .._base import _LIB
+from .._base import c_array, c_str, nn_uint, py_str, string_types
+from .._base import SymbolHandle
+from .._base import check_call, ctypes2docstring
+from ..name import NameManager
+from ..attribute import AttrScope
+
+__all__ = ["Symbol", "Variable"]
+
+class Symbol(object):
+    """Symbol is symbolic graph."""
+
+    # pylint: disable=no-member
+    def __init__(self, handle):
+        """Initialize the function with handle
+
+        Parameters
+        ----------
+        handle : SymbolHandle
+            the handle to the underlying C++ Symbol
+        """
+        self.handle = handle
+
+    def __del__(self):
+        check_call(_LIB.NNSymbolFree(self.handle))
+
+    def __copy__(self):
+        return copy.deepcopy(self)
+
+    def __deepcopy__(self, _):
+        handle = SymbolHandle()
+        check_call(_LIB.NNSymbolCopy(self.handle,
+                                     ctypes.byref(handle)))
+        return Symbol(handle)
+
+    def __call__(self, *args, **kwargs):
+        """Invoke symbol as function on inputs.
+
+        Parameters
+        ----------
+        args:
+            provide positional arguments
+
+        kwargs:
+            provide keyword arguments
+        Returns
+        -------
+        the resulting symbol
+        """
+        s = copy.deepcopy(self)
+        s._compose(*args, **kwargs)
+        return s
+
+    def _compose(self, *args, **kwargs):
+        """Compose symbol on inputs.
+
+        This call mutates the current symbol.
+
+        Parameters
+        ----------
+        args:
+            provide positional arguments
+
+        kwargs:
+            provide keyword arguments
+
+        Returns
+        -------
+        the resulting symbol
+        """
+        name = kwargs.pop('name', None)
+
+        if name:
+            name = c_str(name)
+        if len(args) != 0 and len(kwargs) != 0:
+            raise TypeError('compose only accept input Symbols \
+                either as positional or keyword arguments, not both')
+
+        for arg in args:
+            if not isinstance(arg, Symbol):
+                raise TypeError('Compose expect `Symbol` as arguments')
+        for val in kwargs.values():
+            if not isinstance(val, Symbol):
+                raise TypeError('Compose expect `Symbol` as arguments')
+
+        num_args = len(args) + len(kwargs)
+        if len(kwargs) != 0:
+            keys = c_array(ctypes.c_char_p, [c_str(key) for key in kwargs.keys()])
+            args = c_array(SymbolHandle, [s.handle for s in kwargs.values()])
+        else:
+            keys = None
+            args = c_array(SymbolHandle, [s.handle for s in args])
+        check_call(_LIB.NNSymbolCompose(
+            self.handle, name, num_args, keys, args))
+
+    def __getitem__(self, index):
+        if isinstance(index, string_types):
+            idx = None
+            for i, name in enumerate(self.list_outputs()):
+                if name == index:
+                    if idx is not None:
+                        raise ValueError('There are multiple outputs with name \"%s\"' % index)
+                    idx = i
+            if idx is None:
+                raise ValueError('Cannot find output that matches name \"%s\"' % index)
+            index = idx
+        if not isinstance(index, int):
+            raise TypeError('Symbol only support integer index to fetch i-th output')
+        handle = SymbolHandle()
+        check_call(_LIB.NNSymbolGetOutput(
+            self.handle, nn_uint(index), ctypes.byref(handle)))
+        return Symbol(handle=handle)
+
+    def attr(self, key):
+        """Get attribute string from the symbol, this function only works for non-grouped symbol.
+
+        Parameters
+        ----------
+        key : str
+            The key to get attribute from.
+
+        Returns
+        -------
+        value : str
+            The attribute value of the key, returns None if attribute do not exist.
+        """
+        ret = ctypes.c_char_p()
+        success = ctypes.c_int()
+        check_call(_LIB.NNSymbolGetAttr(
+            self.handle, c_str(key), ctypes.byref(ret), ctypes.byref(success)))
+        if success.value != 0:
+            return py_str(ret.value)
+        else:
+            return None
+
+    def list_attr(self, recursive=False):
+        """Get all attributes from the symbol.
+
+        Parameters
+        ----------
+        recursive : bool
+            Default `False`. When `recursive` is `True`, list recursively all the
+            attributes in the descendents. The attribute names are pre-pended with
+            the symbol names to avoid conflicts. If `False`, then only attributes
+            that belongs to this symbol is returned, and the attribute names will
+            **not** be pre-pended with the symbol name.
+        """
+        size = nn_uint()
+        pairs = ctypes.POINTER(ctypes.c_char_p)()
+        option = ctypes.c_int(0) if recursive else ctypes.c_int(1)
+        check_call(_LIB.NNSymbolListAttrs(
+            self.handle, option, ctypes.byref(size), ctypes.byref(pairs)))
+        return {py_str(pairs[i*2]): py_str(pairs[i*2+1]) for i in range(size.value)}
+
+    def _set_attr(self, **kwargs):
+        """Set the attribute of the symbol.
+
+        Parameters
+        ----------
+        **kwargs
+            The attributes to set
+        """
+        keys = c_array(ctypes.c_char_p, [c_str(key) for key in kwargs.keys()])
+        vals = c_array(ctypes.c_char_p, [c_str(str(val)) for val in kwargs.values()])
+        num_args = nn_uint(len(kwargs))
+        check_call(_LIB.NNSymbolSetAttrs(
+            self.handle, num_args, keys, vals))
+
+    def get_internals(self):
+        """Get a new grouped symbol whose output contains all the internal outputs of this symbol.
+
+        Returns
+        -------
+        sgroup : Symbol
+            The internal of the symbol.
+        """
+        handle = SymbolHandle()
+        check_call(_LIB.NNSymbolGetInternals(
+            self.handle, ctypes.byref(handle)))
+        return Symbol(handle=handle)
+
+    def list_arguments(self):
+        """List all the arguments in the symbol.
+
+        Returns
+        -------
+        args : list of string
+            List of all the arguments.
+        """
+        size = ctypes.c_uint()
+        sarr = ctypes.POINTER(ctypes.c_char_p)()
+        check_call(_LIB.NNSymbolListArguments(
+            self.handle, ctypes.byref(size), ctypes.byref(sarr)))
+        return [py_str(sarr[i]) for i in range(size.value)]
+
+    def list_outputs(self):
+        """List all outputs in the symbol.
+
+        Returns
+        -------
+        returns : list of string
+            List of all the outputs.
+        """
+        size = ctypes.c_uint()
+        sarr = ctypes.POINTER(ctypes.c_char_p)()
+        check_call(_LIB.NNSymbolListOutputs(
+            self.handle, ctypes.byref(size), ctypes.byref(sarr)))
+        return [py_str(sarr[i]) for i in range(size.value)]
+
+    def debug_str(self):
+        """Get a debug string.
+
+        Returns
+        -------
+        debug_str : string
+            Debug string of the symbol.
+        """
+        debug_str = ctypes.c_char_p()
+        check_call(_LIB.NNSymbolPrint(
+            self.handle, ctypes.byref(debug_str)))
+        return py_str(debug_str.value)
+
+
+def Variable(name, **kwargs):
+    """Create a symbolic variable with specified name.
+
+    Parameters
+    ----------
+    name : str
+        Name of the variable.
+    kwargs : dict of string -> string
+        Additional attributes to set on the variable.
+
+    Returns
+    -------
+    variable : Symbol
+        The created variable symbol.
+    """
+    if not isinstance(name, string_types):
+        raise TypeError('Expect a string for variable `name`')
+    handle = SymbolHandle()
+    check_call(_LIB.NNSymbolCreateVariable(c_str(name), ctypes.byref(handle)))
+    ret = Symbol(handle)
+    attr = AttrScope.current.get(kwargs)
+    if attr:
+        ret._set_attr(**attr)
+    return ret
+
+
+def Group(symbols):
+    """Create a symbol that groups symbols together.
+
+    Parameters
+    ----------
+    symbols : list
+        List of symbols to be grouped.
+
+    Returns
+    -------
+    sym : Symbol
+        The created group symbol.
+     """
+    ihandles = []
+    for sym in symbols:
+        if not isinstance(sym, Symbol):
+            raise TypeError('Expect Symbols in the list input')
+        ihandles.append(sym.handle)
+    handle = SymbolHandle()
+    check_call(_LIB.NNSymbolCreateGroup(
+        nn_uint(len(ihandles)),
+        c_array(SymbolHandle, ihandles), ctypes.byref(handle)))
+    return Symbol(handle)
+
+
+def _make_atomic_symbol_function(handle):
+    """Create an atomic symbol function by handle and funciton name."""
+    name = ctypes.c_char_p()
+    desc = ctypes.c_char_p()
+    num_args = nn_uint()
+    arg_names = ctypes.POINTER(ctypes.c_char_p)()
+    arg_types = ctypes.POINTER(ctypes.c_char_p)()
+    arg_descs = ctypes.POINTER(ctypes.c_char_p)()
+    ret_type = ctypes.c_char_p()
+
+    check_call(_LIB.NNSymbolGetAtomicSymbolInfo(
+        handle, ctypes.byref(name), ctypes.byref(desc),
+        ctypes.byref(num_args),
+        ctypes.byref(arg_names),
+        ctypes.byref(arg_types),
+        ctypes.byref(arg_descs),
+        ctypes.byref(ret_type)))
+    param_str = ctypes2docstring(num_args, arg_names, arg_types, arg_descs)
+    func_name = py_str(name.value)
+    desc = py_str(desc.value)
+
+    doc_str = ('%s\n\n' +
+               '%s\n' +
+               'name : string, optional.\n' +
+               '    Name of the resulting symbol.\n\n' +
+               'Returns\n' +
+               '-------\n' +
+               'symbol: Symbol\n' +
+               '    The result symbol.')
+    doc_str = doc_str % (desc, param_str)
+
+    def creator(*args, **kwargs):
+        """Activation Operator of Neural Net.
+        The parameters listed below can be passed in as keyword arguments.
+
+        Parameters
+        ----------
+        name : string, required.
+            Name of the resulting symbol.
+
+        Returns
+        -------
+        symbol: Symbol
+            the resulting symbol
+        """
+        param_keys = []
+        param_vals = []
+        symbol_kwargs = {}
+        name = kwargs.pop('name', None)
+        attr = kwargs.pop('attr', None)
+
+        for k, v in kwargs.items():
+            if isinstance(v, Symbol):
+                symbol_kwargs[k] = v
+            else:
+                param_keys.append(c_str(k))
+                param_vals.append(c_str(str(v)))
+        # create atomic symbol
+        param_keys = c_array(ctypes.c_char_p, param_keys)
+        param_vals = c_array(ctypes.c_char_p, param_vals)
+        sym_handle = SymbolHandle()
+        check_call(_LIB.NNSymbolCreateAtomicSymbol(
+            handle,
+            nn_uint(len(param_keys)),
+            param_keys, param_vals,
+            ctypes.byref(sym_handle)))
+
+        if len(args) != 0 and len(symbol_kwargs) != 0:
+            raise TypeError(
+                '%s can only accept input'
+                'Symbols either as positional or keyword arguments, not both' % func_name)
+        s = Symbol(sym_handle)
+        attr = AttrScope.current.get(attr)
+        if attr:
+            s._set_attr(**attr)
+        hint = func_name.lower()
+        name = NameManager.current.get(name, hint)
+        s._compose(*args, name=name, **symbol_kwargs)
+        return s
+
+    creator.__name__ = func_name
+    creator.__doc__ = doc_str
+    return creator
+
+
+def _init_symbol_module():
+    """List and add all the atomic symbol functions to current module."""
+    plist = ctypes.POINTER(ctypes.c_void_p)()
+    size = ctypes.c_uint()
+
+    check_call(_LIB.NNSymbolListAtomicSymbolCreators(ctypes.byref(size),
+                                                     ctypes.byref(plist)))
+    module_obj = sys.modules["nnvm.symbol"]
+    for i in range(size.value):
+        hdl = SymbolHandle(plist[i])
+        function = _make_atomic_symbol_function(hdl)
+        if function.__name__.startswith('_'):
+            setattr(Symbol, function.__name__, staticmethod(function))
+        else:
+            setattr(module_obj, function.__name__, function)
+
+# Initialize the atomic symbol in startups
+_init_symbol_module()
diff --git a/nnvm/python/nnvm/cython/README b/nnvm/python/nnvm/cython/README
new file mode 100644
index 000000000000..d9deab1abca9
--- /dev/null
+++ b/nnvm/python/nnvm/cython/README
@@ -0,0 +1 @@
+Cython specific implementation of certain modules
\ No newline at end of file
diff --git a/nnvm/python/nnvm/cython/base.pyi b/nnvm/python/nnvm/cython/base.pyi
new file mode 100644
index 000000000000..f9175651dea9
--- /dev/null
+++ b/nnvm/python/nnvm/cython/base.pyi
@@ -0,0 +1,67 @@
+ctypedef void* SymbolHandle
+ctypedef void* AtomicSymbolCreator
+ctypedef unsigned nn_uint
+
+cdef py_str(const char* x):
+    if PY_MAJOR_VERSION < 3:
+        return x
+    else:
+        return x.decode("utf-8")
+
+
+cdef CALL(int ret):
+    if ret != 0:
+        raise NNVMError(NNGetLastError())
+
+
+cdef const char** CBeginPtr(vector[const char*]& vec):
+    if (vec.size() != 0):
+        return &vec[0]
+    else:
+        return NULL
+
+
+cdef BuildDoc(nn_uint num_args,
+              const char** arg_names,
+              const char** arg_types,
+              const char** arg_descs,
+              remove_dup=True):
+    """Convert ctypes returned doc string information into parameters docstring.
+
+    num_args : nn_uint
+        Number of arguments.
+
+    arg_names : ctypes.POINTER(ctypes.c_char_p)
+        Argument names.
+
+    arg_types : ctypes.POINTER(ctypes.c_char_p)
+        Argument type information.
+
+    arg_descs : ctypes.POINTER(ctypes.c_char_p)
+        Argument description information.
+
+    remove_dup : boolean, optional
+        Whether remove duplication or not.
+
+    Returns
+    -------
+    docstr : str
+        Python docstring of parameter sections.
+    """
+    param_keys = set()
+    param_str = []
+    for i in range(num_args):
+        key = arg_names[i]
+        if key in param_keys and remove_dup:
+            continue
+        param_keys.add(key)
+        type_info = arg_types[i]
+        ret = '%s : %s' % (key, type_info)
+        if len(arg_descs[i]) != 0:
+            ret += '\n    ' + arg_descs[i]
+        param_str.append(ret)
+    doc_str = ('Parameters\n' +
+               '----------\n' +
+               '%s\n')
+    doc_str = doc_str % ('\n'.join(param_str))
+    return doc_str
diff --git a/nnvm/python/nnvm/symbolx.pyd b/nnvm/python/nnvm/cython/symbol.pyd
similarity index 100%
rename from nnvm/python/nnvm/symbolx.pyd
rename to nnvm/python/nnvm/cython/symbol.pyd
diff --git a/nnvm/python/nnvm/cython/symbol.pyx b/nnvm/python/nnvm/cython/symbol.pyx
new file mode 100644
index 000000000000..5554520cdf5b
--- /dev/null
+++ b/nnvm/python/nnvm/cython/symbol.pyx
@@ -0,0 +1,376 @@
+from __future__ import absolute_import as _abs
+
+import sys as _sys
+import ctypes as _ctypes
+from .._base import NNVMError
+from ..name import NameManager
+from ..attribute import AttrScope
+from libcpp.vector cimport vector
+from cpython.version cimport PY_MAJOR_VERSION
+
+include "./base.pyi"
+
+cdef extern from "nnvm/c_api.h":
+    const char* NNGetLastError();
+    int NNSymbolCreateVariable(const char *name, SymbolHandle *out);
+    int NNSymbolCreateGroup(nn_uint num_symbols,
+                            SymbolHandle *symbols,
+                            SymbolHandle *out);
+    int NNSymbolListAtomicSymbolCreators(nn_uint *out_size,
+                                         AtomicSymbolCreator **out_array);
+    int NNSymbolCreateAtomicSymbol(AtomicSymbolCreator creator,
+                                   nn_uint num_param,
+                                   const char **keys,
+                                   const char **vals,
+                                   SymbolHandle *out);
+    int NNSymbolGetAtomicSymbolInfo(AtomicSymbolCreator creator,
+                                    const char **name,
+                                    const char **description,
+                                    nn_uint *num_doc_args,
+                                    const char ***arg_names,
+                                    const char ***arg_type_infos,
+                                    const char ***arg_descriptions,
+                                    const char **return_type);
+    int NNSymbolFree(SymbolHandle symbol);
+    int NNSymbolPrint(SymbolHandle symbol, const char **out_str);
+    int NNSymbolCopy(SymbolHandle symbol, SymbolHandle *out);
+    int NNSymbolGetAttr(SymbolHandle symbol,
+                        const char* key,
+                        const char** out,
+                        int *success);
+    int NNSymbolSetAttrs(SymbolHandle symbol,
+                         nn_uint num_param,
+                         const char** keys,
+                         const char** values);
+    int NNSymbolListAttrs(SymbolHandle symbol,
+                          int recursive_option,
+                          nn_uint *out_size,
+                          const char*** out);
+    int NNSymbolListArguments(SymbolHandle symbol,
+                              nn_uint *out_size,
+                              const char ***out_str_array);
+    int NNSymbolListOutputs(SymbolHandle symbol,
+                            nn_uint *out_size,
+                            const char ***out_str_array);
+    int NNSymbolGetInternals(SymbolHandle symbol,
+                             SymbolHandle *out);
+    int NNSymbolGetOutput(SymbolHandle symbol,
+                          nn_uint index,
+                          SymbolHandle *out);
+    int NNSymbolCompose(SymbolHandle sym,
+                        const char* name,
+                        nn_uint num_args,
+                        const char** keys,
+                        SymbolHandle* args);
+
+
+cdef class Symbol:
+    """Symbol is symbolic graph."""
+    # handle for symbolic operator.
+    cdef SymbolHandle handle
+
+    def __init__(self, handle):
+        cdef unsigned long ptr
+        if handle is None:
+            self.handle = NULL
+        else:
+            ptr = handle.value
+            self.handle = <SymbolHandle>(ptr)
+
+    def __dealloc__(self):
+        CALL(NNSymbolFree(self.handle))
+
+    @property
+    def handle(self):
+        return _ctypes.cast(<unsigned long>self.handle, _ctypes.c_void_p)
+
+    def __copy__(self):
+        return self.__deepcopy__()
+
+    def __deepcopy__(self, _ = None):
+        cdef SymbolHandle handle
+        CALL(NNSymbolCopy(self.handle, &handle))
+        return NewSymbol(handle)
+
+    def __getitem__(self, index):
+        if isinstance(index, str):
+            idx = None
+            for i, name in enumerate(self.list_outputs()):
+                if name == index:
+                    if idx is not None:
+                        raise ValueError('There are multiple outputs with name \"%s\"' % index)
+                    idx = i
+            if idx is None:
+                raise ValueError('Cannot find output that matches name \"%s\"' % index)
+            index = idx
+        if not isinstance(index, int):
+            raise TypeError('Symbol only support integer index to fetch i-th output')
+        cdef SymbolHandle handle
+        cdef nn_uint c_index = index
+        CALL(NNSymbolGetOutput(self.handle, c_index, &handle))
+        return NewSymbol(handle)
+
+    def attr(self, const char* key):
+        """Get attribute string from the symbol, this function only works for non-grouped symbol.
+
+        Parameters
+        ----------
+        key : str
+            The key to get attribute from.
+
+        Returns
+        -------
+        value : str
+            The attribute value of the key, returns None if attribute do not exist.
+        """
+        cdef const char* ret
+        cdef int success
+        CALL(NNSymbolGetAttr(
+            self.handle, key, &ret, &success))
+        if success != 0:
+            return py_str(ret.value)
+        else:
+            return None
+
+    def list_attr(self, recursive=False):
+        """Get all attributes from the symbol.
+
+        Parameters
+        ----------
+        recursive : bool
+            Default `False`. When `recursive` is `True`, list recursively all the
+            attributes in the descendents. The attribute names are pre-pended with
+            the symbol names to avoid conflicts. If `False`, then only attributes
+            that belongs to this symbol is returned, and the attribute names will
+            **not** be pre-pended with the symbol name.
+        """
+        cdef nn_uint size
+        cdef const char** pairs
+        cdef int option
+        option = 0 if recursive else 1
+        CALL(NNSymbolListAttrs(
+            self.handle, option, &size, &pairs))
+        return {py_str(pairs[i*2]): py_str(pairs[i*2+1]) for i in range(size)}
+
+    def _set_attr(self, **kwargs):
+        """Set the attribute of the symbol.
+
+        Parameters
+        ----------
+        **kwargs
+            The attributes to set
+        """
+        SymbolSetAttr(self.handle, kwargs)
+
+    def get_internals(self):
+        """Get a new grouped symbol whose output contains all the internal outputs of this symbol.
+
+        Returns
+        -------
+        sgroup : Symbol
+            The internal of the symbol.
+        """
+        cdef SymbolHandle handle
+        CALL(NNSymbolGetInternals(self.handle, &handle))
+        return NewSymbol(handle)
+
+    def list_arguments(self):
+        """List all the arguments in the symbol.
+
+        Returns
+        -------
+        args : list of string
+            List of all the arguments.
+        """
+        cdef nn_uint size
+        cdef const char ** sarr
+        CALL(NNSymbolListArguments(self.handle, &size, &sarr))
+        return [py_str(sarr[i]) for i in range(size)]
+
+    def list_outputs(self):
+        """List all outputs in the symbol.
+
+        Returns
+        -------
+        returns : list of string
+            List of all the outputs.
+        """
+        cdef nn_uint size
+        cdef const char ** sarr
+        CALL(NNSymbolListOutputs(self.handle, &size, &sarr))
+        return [py_str(sarr[i]) for i in range(size)]
+
+    def debug_str(self):
+        cdef const char* out_str
+        CALL(NNSymbolPrint(self.handle, &out_str))
+        return str(out_str)
+
+
+cdef SymbolSetAttr(SymbolHandle handle, dict kwargs):
+    cdef vector[const char*] param_keys
+    cdef vector[const char*] param_vals
+    cdef nn_uint num_args
+    for k, v in kwargs.items():
+        param_keys.push_back(k)
+        param_vals.push_back(str(v))
+    num_args = param_keys.size()
+    CALL(NNSymbolSetAttrs(
+        handle, num_args, CBeginPtr(param_keys), CBeginPtr(param_vals)))
+
+
+cdef NewSymbol(SymbolHandle handle):
+    """Create a new symbol given handle"""
+    sym = Symbol(None)
+    sym.handle = handle
+    return sym
+
+
+def Variable(const char* name, **kwargs):
+    """Create a symbolic variable with specified name.
+
+    Parameters
+    ----------
+    name : str
+        Name of the variable.
+    kwargs : dict of string -> string
+        Additional attributes to set on the variable.
+
+    Returns
+    -------
+    variable : Symbol
+        The created variable symbol.
+    """
+    cdef SymbolHandle handle
+    CALL(NNSymbolCreateVariable(name, &handle))
+    return NewSymbol(handle)
+
+
+cdef _make_atomic_symbol_function(AtomicSymbolCreator handle):
+    """Create an atomic symbol function by handle and funciton name."""
+    cdef const char *name
+    cdef const char *desc
+    cdef nn_uint num_args
+    cdef const char** arg_names
+    cdef const char** arg_types
+    cdef const char** arg_descs
+    cdef const char* return_type
+
+    CALL(NNSymbolGetAtomicSymbolInfo(
+        handle, &name, &desc,
+        &num_args, &arg_names,
+        &arg_types, &arg_descs,
+        &return_type))
+    param_str = BuildDoc(num_args, arg_names, arg_types, arg_descs)
+    func_name = py_str(name)
+    doc_str = ('%s\n\n' +
+               '%s\n' +
+               'name : string, optional.\n' +
+               '    Name of the resulting symbol.\n\n' +
+               'Returns\n' +
+               '-------\n' +
+               'symbol: Symbol\n' +
+               '    The result symbol.')
+    doc_str = doc_str % (desc, param_str)
+    func_hint = func_name.lower()
+
+    def creator(*args, **kwargs):
+        cdef vector[const char*] param_keys
+        cdef vector[const char*] param_vals
+        cdef vector[SymbolHandle] symbol_args
+        cdef vector[const char*] symbol_keys
+        cdef SymbolHandle ret_handle
+
+        name = kwargs.pop("name", None)
+        attr = kwargs.pop("attr", None)
+
+        if len(kwargs) != 0:
+            for k, v in kwargs.items():
+                if isinstance(v, Symbol):
+                    symbol_keys.push_back(k)
+                    symbol_args.push_back((<Symbol>v).handle)
+                else:
+                    param_keys.push_back(k)
+                    param_vals.push_back(str(v))
+
+        if len(args) != 0:
+            if symbol_args.size() != 0:
+                raise TypeError("compose only accept input Symbols\
+                    either as positional or keyword arguments, not both")
+            for v in args:
+                if not isinstance(v, Symbol):
+                    raise TypeError('Compose expect `Symbol` as arguments')
+                symbol_args.push_back((<Symbol>v).handle)
+
+        CALL(NNSymbolCreateAtomicSymbol(
+            handle,
+            <nn_uint>param_keys.size(),
+            CBeginPtr(param_keys),
+            CBeginPtr(param_vals),
+            &ret_handle))
+        num_args = <nn_uint>(symbol_args.size())
+
+        attr = AttrScope.current.get(attr)
+        if attr:
+            SymbolSetAttr(ret_handle, attr)
+        name = NameManager.current.get(name, func_hint)
+
+        cdef const char* c_name = NULL
+        if name:
+            c_name = name
+
+        CALL(NNSymbolCompose(
+            ret_handle,
+            c_name,
+            num_args,
+            &symbol_keys[0] if symbol_keys.size() != 0 else NULL,
+            &symbol_args[0] if symbol_args.size() != 0 else NULL))
+        return NewSymbol(ret_handle)
+
+    creator.__name__ = func_name
+    creator.__doc__ = doc_str
+    return creator
+
+
+def Group(symbols):
+    """Create a symbol that groups symbols together.
+
+    Parameters
+    ----------
+    symbols : list
+        List of symbols to be grouped.
+
+    Returns
+    -------
+    sym : Symbol
+        The created group symbol.
+     """
+    cdef vector[SymbolHandle] ihandles
+    cdef SymbolHandle handle
+
+    for sym in symbols:
+        if not isinstance(sym, Symbol):
+            raise TypeError("Expect Symbols in the list input")
+        ihandles.push_back((<Symbol>sym).handle)
+    if ihandles.size() == 0:
+        raise ValueError("expect at least one element in the input")
+    CALL(NNSymbolCreateGroup(<nn_uint>ihandles.size(),
+                             &ihandles[0], &handle))
+    return NewSymbol(handle)
+
+
+def _init_symbol_module():
+    """List and add all the atomic symbol functions to current module."""
+    cdef AtomicSymbolCreator* plist
+    cdef nn_uint size
+    CALL(NNSymbolListAtomicSymbolCreators(&size, &plist))
+    module_obj = _sys.modules["nnvm.symbol"]
+    for i in range(size):
+        function = _make_atomic_symbol_function(plist[i])
+        if function.__name__.startswith('_'):
+            setattr(Symbol, function.__name__, staticmethod(function))
+        else:
+            setattr(module_obj, function.__name__, function)
+
+
+# Initialize the atomic symbol in startups
+_init_symbol_module()
diff --git a/nnvm/python/nnvm/graph.py b/nnvm/python/nnvm/graph.py
index 6661d9ca7140..3f184928a9ec 100644
--- a/nnvm/python/nnvm/graph.py
+++ b/nnvm/python/nnvm/graph.py
@@ -6,10 +6,10 @@
 import ctypes
 import sys
 import json
-from .base import _LIB
-from .base import c_array, c_str, nn_uint, py_str, string_types
-from .base import GraphHandle, SymbolHandle
-from .base import check_call
+from ._base import _LIB
+from ._base import c_array, c_str, nn_uint, py_str, string_types
+from ._base import GraphHandle, SymbolHandle
+from ._base import check_call
 from .symbol import Symbol
 
 
diff --git a/nnvm/python/nnvm/name.py b/nnvm/python/nnvm/name.py
index 7e50e9e5070d..081d2bae7242 100644
--- a/nnvm/python/nnvm/name.py
+++ b/nnvm/python/nnvm/name.py
@@ -1,6 +1,6 @@
 # coding: utf-8
 """Automatic naming support for symbolic API."""
-from __future__ import absolute_import
+from __future__ import absolute_import as _abs
 
 class NameManager(object):
     """NameManager to do automatic naming.
diff --git a/nnvm/python/nnvm/symbol.py b/nnvm/python/nnvm/symbol.py
index bf9b57ab4205..ac1dee39a08b 100644
--- a/nnvm/python/nnvm/symbol.py
+++ b/nnvm/python/nnvm/symbol.py
@@ -1,385 +1,14 @@
-# coding: utf-8
-# pylint: disable=invalid-name, protected-access, too-many-arguments, too-many-lines
 """Symbolic configuration API."""
 from __future__ import absolute_import as _abs
-
-import copy
-import ctypes
-import sys
-from .base import _LIB
-from .base import c_array, c_str, nn_uint, py_str, string_types
-from .base import SymbolHandle
-from .base import check_call, ctypes2docstring
-from .name import NameManager
-from .attribute import AttrScope
-
-__all__ = ["Symbol", "Variable"]
-
-class Symbol(object):
-    """Symbol is symbolic graph."""
-
-    # pylint: disable=no-member
-    def __init__(self, handle):
-        """Initialize the function with handle
-
-        Parameters
-        ----------
-        handle : SymbolHandle
-            the handle to the underlying C++ Symbol
-        """
-        self.handle = handle
-
-    def __del__(self):
-        check_call(_LIB.NNSymbolFree(self.handle))
-
-    def __copy__(self):
-        return copy.deepcopy(self)
-
-    def __deepcopy__(self, _):
-        handle = SymbolHandle()
-        check_call(_LIB.NNSymbolCopy(self.handle,
-                                     ctypes.byref(handle)))
-        return Symbol(handle)
-
-    def __call__(self, *args, **kwargs):
-        """Invoke symbol as function on inputs.
-
-        Parameters
-        ----------
-        args:
-            provide positional arguments
-
-        kwargs:
-            provide keyword arguments
-        Returns
-        -------
-        the resulting symbol
-        """
-        s = copy.deepcopy(self)
-        s._compose(*args, **kwargs)
-        return s
-
-    def _compose(self, *args, **kwargs):
-        """Compose symbol on inputs.
-
-        This call mutates the current symbol.
-
-        Parameters
-        ----------
-        args:
-            provide positional arguments
-
-        kwargs:
-            provide keyword arguments
-
-        Returns
-        -------
-        the resulting symbol
-        """
-        name = kwargs.pop('name', None)
-
-        if name:
-            name = c_str(name)
-        if len(args) != 0 and len(kwargs) != 0:
-            raise TypeError('compose only accept input Symbols \
-                either as positional or keyword arguments, not both')
-
-        for arg in args:
-            if not isinstance(arg, Symbol):
-                raise TypeError('Compose expect `Symbol` as arguments')
-        for val in kwargs.values():
-            if not isinstance(val, Symbol):
-                raise TypeError('Compose expect `Symbol` as arguments')
-
-        num_args = len(args) + len(kwargs)
-        if len(kwargs) != 0:
-            keys = c_array(ctypes.c_char_p, [c_str(key) for key in kwargs.keys()])
-            args = c_array(SymbolHandle, [s.handle for s in kwargs.values()])
-        else:
-            keys = None
-            args = c_array(SymbolHandle, [s.handle for s in args])
-        check_call(_LIB.NNSymbolCompose(
-            self.handle, name, num_args, keys, args))
-
-    def __getitem__(self, index):
-        if isinstance(index, string_types):
-            idx = None
-            for i, name in enumerate(self.list_outputs()):
-                if name == index:
-                    if idx is not None:
-                        raise ValueError('There are multiple outputs with name \"%s\"' % index)
-                    idx = i
-            if idx is None:
-                raise ValueError('Cannot find output that matches name \"%s\"' % index)
-            index = idx
-        if not isinstance(index, int):
-            raise TypeError('Symbol only support integer index to fetch i-th output')
-        handle = SymbolHandle()
-        check_call(_LIB.NNSymbolGetOutput(
-            self.handle, nn_uint(index), ctypes.byref(handle)))
-        return Symbol(handle=handle)
-
-    def attr(self, key):
-        """Get attribute string from the symbol, this function only works for non-grouped symbol.
-
-        Parameters
-        ----------
-        key : str
-            The key to get attribute from.
-
-        Returns
-        -------
-        value : str
-            The attribute value of the key, returns None if attribute do not exist.
-        """
-        ret = ctypes.c_char_p()
-        success = ctypes.c_int()
-        check_call(_LIB.NNSymbolGetAttr(
-            self.handle, c_str(key), ctypes.byref(ret), ctypes.byref(success)))
-        if success.value != 0:
-            return py_str(ret.value)
-        else:
-            return None
-
-    def list_attr(self, recursive=False):
-        """Get all attributes from the symbol.
-
-        Parameters
-        ----------
-        recursive : bool
-            Default `False`. When `recursive` is `True`, list recursively all the
-            attributes in the descendents. The attribute names are pre-pended with
-            the symbol names to avoid conflicts. If `False`, then only attributes
-            that belongs to this symbol is returned, and the attribute names will
-            **not** be pre-pended with the symbol name.
-        """
-        size = nn_uint()
-        pairs = ctypes.POINTER(ctypes.c_char_p)()
-        option = ctypes.c_int(0) if recursive else ctypes.c_int(1)
-        check_call(_LIB.NNSymbolListAttrs(
-            self.handle, option, ctypes.byref(size), ctypes.byref(pairs)))
-        return {py_str(pairs[i*2]): py_str(pairs[i*2+1]) for i in range(size.value)}
-
-    def _set_attr(self, **kwargs):
-        """Set the attribute of the symbol.
-
-        Parameters
-        ----------
-        **kwargs
-            The attributes to set
-        """
-        keys = c_array(ctypes.c_char_p, [c_str(key) for key in kwargs.keys()])
-        vals = c_array(ctypes.c_char_p, [c_str(str(val)) for val in kwargs.values()])
-        num_args = nn_uint(len(kwargs))
-        check_call(_LIB.NNSymbolSetAttrs(
-            self.handle, num_args, keys, vals))
-
-    def get_internals(self):
-        """Get a new grouped symbol whose output contains all the internal outputs of this symbol.
-
-        Returns
-        -------
-        sgroup : Symbol
-            The internal of the symbol.
-        """
-        handle = SymbolHandle()
-        check_call(_LIB.NNSymbolGetInternals(
-            self.handle, ctypes.byref(handle)))
-        return Symbol(handle=handle)
-
-    def list_arguments(self):
-        """List all the arguments in the symbol.
-
-        Returns
-        -------
-        args : list of string
-            List of all the arguments.
-        """
-        size = ctypes.c_uint()
-        sarr = ctypes.POINTER(ctypes.c_char_p)()
-        check_call(_LIB.NNSymbolListArguments(
-            self.handle, ctypes.byref(size), ctypes.byref(sarr)))
-        return [py_str(sarr[i]) for i in range(size.value)]
-
-    def list_outputs(self):
-        """List all outputs in the symbol.
-
-        Returns
-        -------
-        returns : list of string
-            List of all the outputs.
-        """
-        size = ctypes.c_uint()
-        sarr = ctypes.POINTER(ctypes.c_char_p)()
-        check_call(_LIB.NNSymbolListOutputs(
-            self.handle, ctypes.byref(size), ctypes.byref(sarr)))
-        return [py_str(sarr[i]) for i in range(size.value)]
-
-    def debug_str(self):
-        """Get a debug string.
-
-        Returns
-        -------
-        debug_str : string
-            Debug string of the symbol.
-        """
-        debug_str = ctypes.c_char_p()
-        check_call(_LIB.NNSymbolPrint(
-            self.handle, ctypes.byref(debug_str)))
-        return py_str(debug_str.value)
-
-
-def Variable(name, **kwargs):
-    """Create a symbolic variable with specified name.
-
-    Parameters
-    ----------
-    name : str
-        Name of the variable.
-    kwargs : dict of string -> string
-        Additional attributes to set on the variable.
-
-    Returns
-    -------
-    variable : Symbol
-        The created variable symbol.
-    """
-    if not isinstance(name, string_types):
-        raise TypeError('Expect a string for variable `name`')
-    handle = SymbolHandle()
-    check_call(_LIB.NNSymbolCreateVariable(c_str(name), ctypes.byref(handle)))
-    ret = Symbol(handle)
-    attr = AttrScope.current.get(kwargs)
-    if attr:
-        ret._set_attr(**attr)
-    return ret
-
-
-def Group(symbols):
-    """Create a symbol that groups symbols together.
-
-    Parameters
-    ----------
-    symbols : list
-        List of symbols to be grouped.
-
-    Returns
-    -------
-    sym : Symbol
-        The created group symbol.
-     """
-    ihandles = []
-    for sym in symbols:
-        if not isinstance(sym, Symbol):
-            raise TypeError('Expect Symbols in the list input')
-        ihandles.append(sym.handle)
-    handle = SymbolHandle()
-    check_call(_LIB.NNSymbolCreateGroup(
-        nn_uint(len(ihandles)),
-        c_array(SymbolHandle, ihandles), ctypes.byref(handle)))
-    return Symbol(handle)
-
-
-def _make_atomic_symbol_function(handle):
-    """Create an atomic symbol function by handle and funciton name."""
-    name = ctypes.c_char_p()
-    desc = ctypes.c_char_p()
-    num_args = nn_uint()
-    arg_names = ctypes.POINTER(ctypes.c_char_p)()
-    arg_types = ctypes.POINTER(ctypes.c_char_p)()
-    arg_descs = ctypes.POINTER(ctypes.c_char_p)()
-    ret_type = ctypes.c_char_p()
-
-    check_call(_LIB.NNSymbolGetAtomicSymbolInfo(
-        handle, ctypes.byref(name), ctypes.byref(desc),
-        ctypes.byref(num_args),
-        ctypes.byref(arg_names),
-        ctypes.byref(arg_types),
-        ctypes.byref(arg_descs),
-        ctypes.byref(ret_type)))
-    param_str = ctypes2docstring(num_args, arg_names, arg_types, arg_descs)
-    func_name = py_str(name.value)
-    desc = py_str(desc.value)
-
-    doc_str = ('%s\n\n' +
-               '%s\n' +
-               'name : string, optional.\n' +
-               '    Name of the resulting symbol.\n\n' +
-               'Returns\n' +
-               '-------\n' +
-               'symbol: Symbol\n' +
-               '    The result symbol.')
-    doc_str = doc_str % (desc, param_str)
-
-    def creator(*args, **kwargs):
-        """Activation Operator of Neural Net.
-        The parameters listed below can be passed in as keyword arguments.
-
-        Parameters
-        ----------
-        name : string, required.
-            Name of the resulting symbol.
-
-        Returns
-        -------
-        symbol: Symbol
-            the resulting symbol
-        """
-        param_keys = []
-        param_vals = []
-        symbol_kwargs = {}
-        name = kwargs.pop('name', None)
-        attr = kwargs.pop('attr', None)
-
-        for k, v in kwargs.items():
-            if isinstance(v, Symbol):
-                symbol_kwargs[k] = v
-            else:
-                param_keys.append(c_str(k))
-                param_vals.append(c_str(str(v)))
-        # create atomic symbol
-        param_keys = c_array(ctypes.c_char_p, param_keys)
-        param_vals = c_array(ctypes.c_char_p, param_vals)
-        sym_handle = SymbolHandle()
-        check_call(_LIB.NNSymbolCreateAtomicSymbol(
-            handle,
-            nn_uint(len(param_keys)),
-            param_keys, param_vals,
-            ctypes.byref(sym_handle)))
-
-        if len(args) != 0 and len(symbol_kwargs) != 0:
-            raise TypeError(
-                '%s can only accept input'
-                'Symbols either as positional or keyword arguments, not both' % func_name)
-        s = Symbol(sym_handle)
-        attr = AttrScope.current.get(attr)
-        if attr:
-            s._set_attr(**attr)
-        hint = func_name.lower()
-        name = NameManager.current.get(name, hint)
-        s._compose(*args, name=name, **symbol_kwargs)
-        return s
-
-    creator.__name__ = func_name
-    creator.__doc__ = doc_str
-    return creator
-
-
-def _init_symbol_module():
-    """List and add all the atomic symbol functions to current module."""
-    plist = ctypes.POINTER(ctypes.c_void_p)()
-    size = ctypes.c_uint()
-
-    check_call(_LIB.NNSymbolListAtomicSymbolCreators(ctypes.byref(size),
-                                                     ctypes.byref(plist)))
-    module_obj = sys.modules[__name__]
-    for i in range(size.value):
-        hdl = SymbolHandle(plist[i])
-        function = _make_atomic_symbol_function(hdl)
-        if function.__name__.startswith('_'):
-            setattr(Symbol, function.__name__, staticmethod(function))
-        else:
-            setattr(module_obj, function.__name__, function)
-
-# Initialize the atomic symbol in startups
-_init_symbol_module()
+import sys as _sys
+import os as _os
+
+try:
+    if int(_os.environ.get("NNVM_ENABLE_CYTHON", True)) == 0:
+        from .ctypes.symbol import Symbol, Variable
+    elif _sys.version_info >= (3, 0):
+        from ._cy3.symbol import Symbol, Variable, Group
+    else:
+        from ._cy2.symbol import Symbol, Variable, Group
+except:
+    from .ctypes.symbol import Symbol, Variable, Group
diff --git a/nnvm/python/nnvm/symbolx.pyx b/nnvm/python/nnvm/symbolx.pyx
deleted file mode 100644
index 5264a391fcf5..000000000000
--- a/nnvm/python/nnvm/symbolx.pyx
+++ /dev/null
@@ -1,220 +0,0 @@
-import sys
-from libcpp.vector cimport vector
-
-ctypedef void* SymbolHandle
-ctypedef void* AtomicSymbolCreator
-ctypedef unsigned nn_uint
-
-cdef extern from "nnvm/c_api.h":
-    int NNSymbolFree(SymbolHandle symbol)
-    int NNSymbolCreateVariable(const char *name, SymbolHandle *out)
-    const char* NNGetLastError()
-    int NNSymbolPrint(SymbolHandle symbol, const char **out_str)
-    int NNSymbolListAtomicSymbolCreators(nn_uint *out_size,
-                                         AtomicSymbolCreator **out_array);
-    int NNSymbolCreateAtomicSymbol(AtomicSymbolCreator creator,
-                                   nn_uint num_param,
-                                   const char **keys,
-                                   const char **vals,
-                                   SymbolHandle *out);
-    int NNSymbolGetAtomicSymbolInfo(AtomicSymbolCreator creator,
-                                    const char **name,
-                                    const char **description,
-                                    nn_uint *num_doc_args,
-                                    const char ***arg_names,
-                                    const char ***arg_type_infos,
-                                    const char ***arg_descriptions,
-                                    const char **return_type);
-    int NNSymbolCompose(SymbolHandle sym,
-                        const char* name,
-                        nn_uint num_args,
-                        const char** keys,
-                        SymbolHandle* args);
-
-cdef CALL(int ret):
-    if ret != 0:
-        raise RuntimeError(NNGetLastError())
-
-cdef const char** CBeginPtr(vector[const char*]& vec):
-    if (vec.size() != 0):
-        return &vec[0]
-    else:
-        return NULL
-
-cdef ctypes2docstring(nn_uint num_args,
-                      const char** arg_names,
-                      const char** arg_types,
-                      const char** arg_descs,
-                      remove_dup=True):
-    """Convert ctypes returned doc string information into parameters docstring.
-
-    num_args : nn_uint
-        Number of arguments.
-
-    arg_names : ctypes.POINTER(ctypes.c_char_p)
-        Argument names.
-
-    arg_types : ctypes.POINTER(ctypes.c_char_p)
-        Argument type information.
-
-    arg_descs : ctypes.POINTER(ctypes.c_char_p)
-        Argument description information.
-
-    remove_dup : boolean, optional
-        Whether remove duplication or not.
-
-    Returns
-    -------
-    docstr : str
-        Python docstring of parameter sections.
-    """
-    param_keys = set()
-    param_str = []
-    for i in range(num_args):
-        key = arg_names[i]
-        if key in param_keys and remove_dup:
-            continue
-        param_keys.add(key)
-        type_info = arg_types[i]
-        ret = '%s : %s' % (key, type_info)
-        if len(arg_descs[i]) != 0:
-            ret += '\n    ' + arg_descs[i]
-        param_str.append(ret)
-    doc_str = ('Parameters\n' +
-               '----------\n' +
-               '%s\n')
-    doc_str = doc_str % ('\n'.join(param_str))
-    return doc_str
-
-
-cdef class Symbol:
-    # handle for symbolic operator.
-    cdef SymbolHandle handle
-
-    def __dealloc__(self):
-        CALL(NNSymbolFree(self.handle))
-
-    def debug_str(self):
-        cdef const char* out_str
-        CALL(NNSymbolPrint(self.handle, &out_str))
-        return str(out_str)
-
-cdef NewSymbol(SymbolHandle handle):
-    """Create a new symbol given handle"""
-    sym = Symbol()
-    sym.handle = handle
-    return sym
-
-
-def Variable(const char* name, **kwargs):
-    """Create a symbolic variable with specified name.
-
-    Parameters
-    ----------
-    name : str
-        Name of the variable.
-    kwargs : dict of string -> string
-        Additional attributes to set on the variable.
-
-    Returns
-    -------
-    variable : Symbol
-        The created variable symbol.
-    """
-    cdef SymbolHandle handle
-    CALL(NNSymbolCreateVariable(name, &handle))
-    return NewSymbol(handle)
-
-
-cdef _make_atomic_symbol_function(AtomicSymbolCreator handle):
-    """Create an atomic symbol function by handle and funciton name."""
-    cdef const char *name
-    cdef const char *desc
-    cdef nn_uint num_args
-    cdef const char** arg_names
-    cdef const char** arg_types
-    cdef const char** arg_descs
-    cdef const char* return_type
-
-    CALL(NNSymbolGetAtomicSymbolInfo(
-        handle, &name, &desc,
-        &num_args, &arg_names,
-        &arg_types, &arg_descs,
-        &return_type))
-
-    param_str = ctypes2docstring(num_args, arg_names, arg_types, arg_descs)
-    func_name = name
-    doc_str = ('%s\n\n' +
-               '%s\n' +
-               'name : string, optional.\n' +
-               '    Name of the resulting symbol.\n\n' +
-               'Returns\n' +
-               '-------\n' +
-               'symbol: Symbol\n' +
-               '    The result symbol.')
-    doc_str = doc_str % (desc, param_str)
-
-    def creator(*args, **kwargs):
-        cdef vector[const char*] param_keys
-        cdef vector[const char*] param_vals
-        cdef vector[SymbolHandle] symbol_args
-        cdef vector[const char*] symbol_keys
-        cdef SymbolHandle ret_handle
-        cdef const char* c_name = NULL
-
-        name = kwargs.pop('name', None)
-        attr = kwargs.pop('attr', None)
-        if name:
-            c_name = name
-
-        if len(kwargs) != 0:
-            for k, v in kwargs.items():
-                if isinstance(v, Symbol):
-                    symbol_keys.push_back(k)
-                    symbol_args.push_back((<Symbol>v).handle)
-                else:
-                    param_keys.push_back(k)
-                    param_vals.push_back(str(v))
-
-        if len(args) != 0:
-            if symbol_args.size() != 0:
-                raise TypeError("compose only accept input Symbols\
-                    either as positional or keyword arguments, not both")
-            for v in args:
-                if not isinstance(v, Symbol):
-                    raise TypeError('Compose expect `Symbol` as arguments')
-                symbol_args.push_back((<Symbol>v).handle)
-
-        CALL(NNSymbolCreateAtomicSymbol(
-            handle,
-            <nn_uint>param_keys.size(),
-            CBeginPtr(param_keys),
-            CBeginPtr(param_vals),
-            &ret_handle))
-        num_args = <nn_uint>(symbol_args.size())
-        CALL(NNSymbolCompose(
-            ret_handle, c_name, num_args,
-            &symbol_keys[0] if symbol_keys.size() != 0 else NULL,
-            &symbol_args[0] if symbol_args.size() != 0 else NULL))
-        return NewSymbol(ret_handle)
-
-    creator.__name__ = func_name
-    creator.__doc__ = doc_str
-    return creator
-
-
-def _init_symbol_module():
-    """List and add all the atomic symbol functions to current module."""
-    cdef AtomicSymbolCreator* plist
-    cdef nn_uint size
-    CALL(NNSymbolListAtomicSymbolCreators(&size, &plist))
-    module_obj = sys.modules[__name__]
-    for i in range(size):
-        function = _make_atomic_symbol_function(plist[i])
-        if function.__name__.startswith('_'):
-            setattr(Symbol, function.__name__, staticmethod(function))
-        else:
-            setattr(module_obj, function.__name__, function)
-
-# Initialize the atomic symbol in startups
-_init_symbol_module()
diff --git a/nnvm/python/setup.py b/nnvm/python/setup.py
index e50d1aaccccb..58b6e7efe088 100644
--- a/nnvm/python/setup.py
+++ b/nnvm/python/setup.py
@@ -1,13 +1,29 @@
+import os
+import sys
 from distutils.core import setup
 from Cython.Build import cythonize
 from distutils.extension import Extension
 
+
+def config():
+    if sys.version_info >= (3, 0):
+        subdir = "_cy3"
+    else:
+        subdir = "_cy2"
+    ret = []
+    path = "nnvm/cython"
+
+    for fn in os.listdir(path):
+        if not fn.endswith(".pyx"):
+            continue
+        ret.append(Extension(
+            "nnvm/%s/%s" % (subdir, fn[:-4]),
+            ["nnvm/cython/%s" % fn],
+            include_dirs=["../include/"],
+            language="c++"))
+    return ret
+
 setup(
     name='nnvm',
-    ext_modules = cythonize([
-        Extension("nnvm/symbolx",
-                  ["nnvm/symbolx.pyx"],
-                  libraries=["nnvm"],
-                  language="c++")
-    ])
+    ext_modules = cythonize(config())
 )
diff --git a/nnvm/tests/python/test_symbol.py b/nnvm/tests/python/test_symbol.py
index 8259862152ae..adc9099adc13 100644
--- a/nnvm/tests/python/test_symbol.py
+++ b/nnvm/tests/python/test_symbol.py
@@ -1,5 +1,5 @@
 import nnvm.symbol as sym
-from nnvm.base import NNVMError
+from nnvm import NNVMError
 
 def test_compose():
     x = sym.Variable('x')

From ad0ab0a4177091edf75b2820e40c886ef3a644c4 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 15 Jul 2016 17:38:15 -0700
Subject: [PATCH 341/949] Make cython compatible with python3 (#12)

---
 nnvm/Makefile                      |  4 ++-
 nnvm/python/nnvm/cython/base.pyi   | 22 +++++++++++++++++
 nnvm/python/nnvm/cython/symbol.pyx | 39 ++++++++++++++++++++----------
 3 files changed, 51 insertions(+), 14 deletions(-)

diff --git a/nnvm/Makefile b/nnvm/Makefile
index feba8d278183..d24e4a8e06e2 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -3,7 +3,7 @@ export CFLAGS =  -std=c++11 -Wall -O3 -msse2  -Wno-unknown-pragmas -funroll-loop
 	 -Iinclude -Idmlc-core/include -fPIC
 
 # specify tensor path
-.PHONY: clean all test lint doc cython cython3
+.PHONY: clean all test lint doc cython cython3 cyclean
 
 all: lib/libnnvm.so lib/libnnvm.a cli_test
 
@@ -37,6 +37,8 @@ cython:
 cython3:
 	cd python; python3 setup.py build_ext --inplace
 
+cyclean:
+	rm -rf python/nnvm/*/*.so python/nnvm/*/*.cpp
 
 lint:
 	python2 dmlc-core/scripts/lint.py nnvm cpp include src
diff --git a/nnvm/python/nnvm/cython/base.pyi b/nnvm/python/nnvm/cython/base.pyi
index f9175651dea9..1163f64b07fd 100644
--- a/nnvm/python/nnvm/cython/base.pyi
+++ b/nnvm/python/nnvm/cython/base.pyi
@@ -9,6 +9,21 @@ cdef py_str(const char* x):
         return x.decode("utf-8")
 
 
+cdef c_str(pystr):
+    """Create ctypes char * from a python string
+    Parameters
+    ----------
+    string : string type
+        python string
+
+    Returns
+    -------
+    str : c_char_p
+        A char pointer that can be passed to C API
+    """
+    return pystr.encode("utf-8")
+
+
 cdef CALL(int ret):
     if ret != 0:
         raise NNVMError(NNGetLastError())
@@ -20,6 +35,13 @@ cdef const char** CBeginPtr(vector[const char*]& vec):
     else:
         return NULL
 
+cdef vector[const char*] SVec2Ptr(vector[string]& vec):
+    cdef vector[const char*] svec
+    svec.resize(vec.size())
+    for i in range(vec.size()):
+        svec[i] = vec[i].c_str()
+    return svec
+
 
 cdef BuildDoc(nn_uint num_args,
               const char** arg_names,
diff --git a/nnvm/python/nnvm/cython/symbol.pyx b/nnvm/python/nnvm/cython/symbol.pyx
index 5554520cdf5b..eeec2c430e89 100644
--- a/nnvm/python/nnvm/cython/symbol.pyx
+++ b/nnvm/python/nnvm/cython/symbol.pyx
@@ -6,6 +6,7 @@ from .._base import NNVMError
 from ..name import NameManager
 from ..attribute import AttrScope
 from libcpp.vector cimport vector
+from libcpp.string cimport string
 from cpython.version cimport PY_MAJOR_VERSION
 
 include "./base.pyi"
@@ -110,7 +111,7 @@ cdef class Symbol:
         CALL(NNSymbolGetOutput(self.handle, c_index, &handle))
         return NewSymbol(handle)
 
-    def attr(self, const char* key):
+    def attr(self, key):
         """Get attribute string from the symbol, this function only works for non-grouped symbol.
 
         Parameters
@@ -125,6 +126,8 @@ cdef class Symbol:
         """
         cdef const char* ret
         cdef int success
+        key = c_str(key)
+
         CALL(NNSymbolGetAttr(
             self.handle, key, &ret, &success))
         if success != 0:
@@ -203,16 +206,19 @@ cdef class Symbol:
     def debug_str(self):
         cdef const char* out_str
         CALL(NNSymbolPrint(self.handle, &out_str))
-        return str(out_str)
+        return py_str(out_str)
 
 
 cdef SymbolSetAttr(SymbolHandle handle, dict kwargs):
-    cdef vector[const char*] param_keys
-    cdef vector[const char*] param_vals
+    cdef vector[string] sparam_keys
+    cdef vector[string] sparam_vals
     cdef nn_uint num_args
     for k, v in kwargs.items():
-        param_keys.push_back(k)
-        param_vals.push_back(str(v))
+        sparam_keys.push_back(c_str(k))
+        sparam_vals.push_back(c_str(str(v)))
+    # keep strings in vector
+    cdef vector[const char*] param_keys = SVec2Ptr(sparam_keys)
+    cdef vector[const char*] param_vals = SVec2Ptr(sparam_vals)
     num_args = param_keys.size()
     CALL(NNSymbolSetAttrs(
         handle, num_args, CBeginPtr(param_keys), CBeginPtr(param_vals)))
@@ -225,7 +231,7 @@ cdef NewSymbol(SymbolHandle handle):
     return sym
 
 
-def Variable(const char* name, **kwargs):
+def Variable(name, **kwargs):
     """Create a symbolic variable with specified name.
 
     Parameters
@@ -241,6 +247,7 @@ def Variable(const char* name, **kwargs):
         The created variable symbol.
     """
     cdef SymbolHandle handle
+    name = c_str(name)
     CALL(NNSymbolCreateVariable(name, &handle))
     return NewSymbol(handle)
 
@@ -274,10 +281,10 @@ cdef _make_atomic_symbol_function(AtomicSymbolCreator handle):
     func_hint = func_name.lower()
 
     def creator(*args, **kwargs):
-        cdef vector[const char*] param_keys
-        cdef vector[const char*] param_vals
+        cdef vector[string] sparam_keys
+        cdef vector[string] sparam_vals
         cdef vector[SymbolHandle] symbol_args
-        cdef vector[const char*] symbol_keys
+        cdef vector[string] ssymbol_keys
         cdef SymbolHandle ret_handle
 
         name = kwargs.pop("name", None)
@@ -286,11 +293,11 @@ cdef _make_atomic_symbol_function(AtomicSymbolCreator handle):
         if len(kwargs) != 0:
             for k, v in kwargs.items():
                 if isinstance(v, Symbol):
-                    symbol_keys.push_back(k)
+                    ssymbol_keys.push_back(c_str(k))
                     symbol_args.push_back((<Symbol>v).handle)
                 else:
-                    param_keys.push_back(k)
-                    param_vals.push_back(str(v))
+                    sparam_keys.push_back(c_str(k))
+                    sparam_vals.push_back(c_str(str(v)))
 
         if len(args) != 0:
             if symbol_args.size() != 0:
@@ -301,6 +308,10 @@ cdef _make_atomic_symbol_function(AtomicSymbolCreator handle):
                     raise TypeError('Compose expect `Symbol` as arguments')
                 symbol_args.push_back((<Symbol>v).handle)
 
+        cdef vector[const char*] param_keys = SVec2Ptr(sparam_keys)
+        cdef vector[const char*] param_vals = SVec2Ptr(sparam_vals)
+        cdef vector[const char*] symbol_keys = SVec2Ptr(ssymbol_keys)
+
         CALL(NNSymbolCreateAtomicSymbol(
             handle,
             <nn_uint>param_keys.size(),
@@ -315,7 +326,9 @@ cdef _make_atomic_symbol_function(AtomicSymbolCreator handle):
         name = NameManager.current.get(name, func_hint)
 
         cdef const char* c_name = NULL
+
         if name:
+            name = c_str(name)
             c_name = name
 
         CALL(NNSymbolCompose(

From 20ac35124a99bdaf4530c8709eae6cc608ba95cf Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 17 Jul 2016 11:12:38 -0700
Subject: [PATCH 342/949] [CYTHON] Make speedup component minimum (#13)

---
 nnvm/python/nnvm/_symbol_internal.py |   1 +
 nnvm/python/nnvm/ctypes/symbol.py    | 201 +++-----------------------
 nnvm/python/nnvm/cython/symbol.pyx   | 189 +++---------------------
 nnvm/python/nnvm/symbol.py           | 209 ++++++++++++++++++++++++++-
 4 files changed, 241 insertions(+), 359 deletions(-)
 create mode 100644 nnvm/python/nnvm/_symbol_internal.py

diff --git a/nnvm/python/nnvm/_symbol_internal.py b/nnvm/python/nnvm/_symbol_internal.py
new file mode 100644
index 000000000000..6fadaf89c9d9
--- /dev/null
+++ b/nnvm/python/nnvm/_symbol_internal.py
@@ -0,0 +1 @@
+"""Module space to register internal functions. Leave empty"""
diff --git a/nnvm/python/nnvm/ctypes/symbol.py b/nnvm/python/nnvm/ctypes/symbol.py
index 503fc09bde74..3bd5e65d4e25 100644
--- a/nnvm/python/nnvm/ctypes/symbol.py
+++ b/nnvm/python/nnvm/ctypes/symbol.py
@@ -13,11 +13,9 @@
 from ..name import NameManager
 from ..attribute import AttrScope
 
-__all__ = ["Symbol", "Variable"]
-
-class Symbol(object):
+class SymbolBase(object):
     """Symbol is symbolic graph."""
-
+    __slots__ = ["handle"]
     # pylint: disable=no-member
     def __init__(self, handle):
         """Initialize the function with handle
@@ -32,15 +30,6 @@ def __init__(self, handle):
     def __del__(self):
         check_call(_LIB.NNSymbolFree(self.handle))
 
-    def __copy__(self):
-        return copy.deepcopy(self)
-
-    def __deepcopy__(self, _):
-        handle = SymbolHandle()
-        check_call(_LIB.NNSymbolCopy(self.handle,
-                                     ctypes.byref(handle)))
-        return Symbol(handle)
-
     def __call__(self, *args, **kwargs):
         """Invoke symbol as function on inputs.
 
@@ -85,10 +74,10 @@ def _compose(self, *args, **kwargs):
                 either as positional or keyword arguments, not both')
 
         for arg in args:
-            if not isinstance(arg, Symbol):
+            if not isinstance(arg, SymbolBase):
                 raise TypeError('Compose expect `Symbol` as arguments')
         for val in kwargs.values():
-            if not isinstance(val, Symbol):
+            if not isinstance(val, SymbolBase):
                 raise TypeError('Compose expect `Symbol` as arguments')
 
         num_args = len(args) + len(kwargs)
@@ -101,65 +90,6 @@ def _compose(self, *args, **kwargs):
         check_call(_LIB.NNSymbolCompose(
             self.handle, name, num_args, keys, args))
 
-    def __getitem__(self, index):
-        if isinstance(index, string_types):
-            idx = None
-            for i, name in enumerate(self.list_outputs()):
-                if name == index:
-                    if idx is not None:
-                        raise ValueError('There are multiple outputs with name \"%s\"' % index)
-                    idx = i
-            if idx is None:
-                raise ValueError('Cannot find output that matches name \"%s\"' % index)
-            index = idx
-        if not isinstance(index, int):
-            raise TypeError('Symbol only support integer index to fetch i-th output')
-        handle = SymbolHandle()
-        check_call(_LIB.NNSymbolGetOutput(
-            self.handle, nn_uint(index), ctypes.byref(handle)))
-        return Symbol(handle=handle)
-
-    def attr(self, key):
-        """Get attribute string from the symbol, this function only works for non-grouped symbol.
-
-        Parameters
-        ----------
-        key : str
-            The key to get attribute from.
-
-        Returns
-        -------
-        value : str
-            The attribute value of the key, returns None if attribute do not exist.
-        """
-        ret = ctypes.c_char_p()
-        success = ctypes.c_int()
-        check_call(_LIB.NNSymbolGetAttr(
-            self.handle, c_str(key), ctypes.byref(ret), ctypes.byref(success)))
-        if success.value != 0:
-            return py_str(ret.value)
-        else:
-            return None
-
-    def list_attr(self, recursive=False):
-        """Get all attributes from the symbol.
-
-        Parameters
-        ----------
-        recursive : bool
-            Default `False`. When `recursive` is `True`, list recursively all the
-            attributes in the descendents. The attribute names are pre-pended with
-            the symbol names to avoid conflicts. If `False`, then only attributes
-            that belongs to this symbol is returned, and the attribute names will
-            **not** be pre-pended with the symbol name.
-        """
-        size = nn_uint()
-        pairs = ctypes.POINTER(ctypes.c_char_p)()
-        option = ctypes.c_int(0) if recursive else ctypes.c_int(1)
-        check_call(_LIB.NNSymbolListAttrs(
-            self.handle, option, ctypes.byref(size), ctypes.byref(pairs)))
-        return {py_str(pairs[i*2]): py_str(pairs[i*2+1]) for i in range(size.value)}
-
     def _set_attr(self, **kwargs):
         """Set the attribute of the symbol.
 
@@ -168,116 +98,20 @@ def _set_attr(self, **kwargs):
         **kwargs
             The attributes to set
         """
-        keys = c_array(ctypes.c_char_p, [c_str(key) for key in kwargs.keys()])
-        vals = c_array(ctypes.c_char_p, [c_str(str(val)) for val in kwargs.values()])
-        num_args = nn_uint(len(kwargs))
-        check_call(_LIB.NNSymbolSetAttrs(
+        keys = _base.c_array(_ctypes.c_char_p,
+                             [_base.c_str(key) for key in kwargs.keys()])
+        vals = _base.c_array(_ctypes.c_char_p,
+                             [_base.c_str(str(val)) for val in kwargs.values()])
+        num_args = _base.nn_uint(len(kwargs))
+        _check_call(_LIB.NNSymbolSetAttrs(
             self.handle, num_args, keys, vals))
 
-    def get_internals(self):
-        """Get a new grouped symbol whose output contains all the internal outputs of this symbol.
 
-        Returns
-        -------
-        sgroup : Symbol
-            The internal of the symbol.
-        """
-        handle = SymbolHandle()
-        check_call(_LIB.NNSymbolGetInternals(
-            self.handle, ctypes.byref(handle)))
-        return Symbol(handle=handle)
-
-    def list_arguments(self):
-        """List all the arguments in the symbol.
-
-        Returns
-        -------
-        args : list of string
-            List of all the arguments.
-        """
-        size = ctypes.c_uint()
-        sarr = ctypes.POINTER(ctypes.c_char_p)()
-        check_call(_LIB.NNSymbolListArguments(
-            self.handle, ctypes.byref(size), ctypes.byref(sarr)))
-        return [py_str(sarr[i]) for i in range(size.value)]
-
-    def list_outputs(self):
-        """List all outputs in the symbol.
+_symbol_cls = SymbolBase
 
-        Returns
-        -------
-        returns : list of string
-            List of all the outputs.
-        """
-        size = ctypes.c_uint()
-        sarr = ctypes.POINTER(ctypes.c_char_p)()
-        check_call(_LIB.NNSymbolListOutputs(
-            self.handle, ctypes.byref(size), ctypes.byref(sarr)))
-        return [py_str(sarr[i]) for i in range(size.value)]
-
-    def debug_str(self):
-        """Get a debug string.
-
-        Returns
-        -------
-        debug_str : string
-            Debug string of the symbol.
-        """
-        debug_str = ctypes.c_char_p()
-        check_call(_LIB.NNSymbolPrint(
-            self.handle, ctypes.byref(debug_str)))
-        return py_str(debug_str.value)
-
-
-def Variable(name, **kwargs):
-    """Create a symbolic variable with specified name.
-
-    Parameters
-    ----------
-    name : str
-        Name of the variable.
-    kwargs : dict of string -> string
-        Additional attributes to set on the variable.
-
-    Returns
-    -------
-    variable : Symbol
-        The created variable symbol.
-    """
-    if not isinstance(name, string_types):
-        raise TypeError('Expect a string for variable `name`')
-    handle = SymbolHandle()
-    check_call(_LIB.NNSymbolCreateVariable(c_str(name), ctypes.byref(handle)))
-    ret = Symbol(handle)
-    attr = AttrScope.current.get(kwargs)
-    if attr:
-        ret._set_attr(**attr)
-    return ret
-
-
-def Group(symbols):
-    """Create a symbol that groups symbols together.
-
-    Parameters
-    ----------
-    symbols : list
-        List of symbols to be grouped.
-
-    Returns
-    -------
-    sym : Symbol
-        The created group symbol.
-     """
-    ihandles = []
-    for sym in symbols:
-        if not isinstance(sym, Symbol):
-            raise TypeError('Expect Symbols in the list input')
-        ihandles.append(sym.handle)
-    handle = SymbolHandle()
-    check_call(_LIB.NNSymbolCreateGroup(
-        nn_uint(len(ihandles)),
-        c_array(SymbolHandle, ihandles), ctypes.byref(handle)))
-    return Symbol(handle)
+def _set_symbol_class(cls):
+    global _symbol_cls
+    _symbol_cls = cls
 
 
 def _make_atomic_symbol_function(handle):
@@ -332,7 +166,7 @@ def creator(*args, **kwargs):
         attr = kwargs.pop('attr', None)
 
         for k, v in kwargs.items():
-            if isinstance(v, Symbol):
+            if isinstance(v, SymbolBase):
                 symbol_kwargs[k] = v
             else:
                 param_keys.append(c_str(k))
@@ -351,7 +185,7 @@ def creator(*args, **kwargs):
             raise TypeError(
                 '%s can only accept input'
                 'Symbols either as positional or keyword arguments, not both' % func_name)
-        s = Symbol(sym_handle)
+        s = _symbol_cls(sym_handle)
         attr = AttrScope.current.get(attr)
         if attr:
             s._set_attr(**attr)
@@ -373,11 +207,12 @@ def _init_symbol_module():
     check_call(_LIB.NNSymbolListAtomicSymbolCreators(ctypes.byref(size),
                                                      ctypes.byref(plist)))
     module_obj = sys.modules["nnvm.symbol"]
+    module_internal = sys.modules["nnvm._symbol_internal"]
     for i in range(size.value):
         hdl = SymbolHandle(plist[i])
         function = _make_atomic_symbol_function(hdl)
         if function.__name__.startswith('_'):
-            setattr(Symbol, function.__name__, staticmethod(function))
+            setattr(module_internal, function.__name__, function)
         else:
             setattr(module_obj, function.__name__, function)
 
diff --git a/nnvm/python/nnvm/cython/symbol.pyx b/nnvm/python/nnvm/cython/symbol.pyx
index eeec2c430e89..7b1435381e99 100644
--- a/nnvm/python/nnvm/cython/symbol.pyx
+++ b/nnvm/python/nnvm/cython/symbol.pyx
@@ -2,6 +2,7 @@ from __future__ import absolute_import as _abs
 
 import sys as _sys
 import ctypes as _ctypes
+from numbers import Number as _Number
 from .._base import NNVMError
 from ..name import NameManager
 from ..attribute import AttrScope
@@ -64,8 +65,7 @@ cdef extern from "nnvm/c_api.h":
                         const char** keys,
                         SymbolHandle* args);
 
-
-cdef class Symbol:
+cdef class SymbolBase:
     """Symbol is symbolic graph."""
     # handle for symbolic operator.
     cdef SymbolHandle handle
@@ -85,76 +85,6 @@ cdef class Symbol:
     def handle(self):
         return _ctypes.cast(<unsigned long>self.handle, _ctypes.c_void_p)
 
-    def __copy__(self):
-        return self.__deepcopy__()
-
-    def __deepcopy__(self, _ = None):
-        cdef SymbolHandle handle
-        CALL(NNSymbolCopy(self.handle, &handle))
-        return NewSymbol(handle)
-
-    def __getitem__(self, index):
-        if isinstance(index, str):
-            idx = None
-            for i, name in enumerate(self.list_outputs()):
-                if name == index:
-                    if idx is not None:
-                        raise ValueError('There are multiple outputs with name \"%s\"' % index)
-                    idx = i
-            if idx is None:
-                raise ValueError('Cannot find output that matches name \"%s\"' % index)
-            index = idx
-        if not isinstance(index, int):
-            raise TypeError('Symbol only support integer index to fetch i-th output')
-        cdef SymbolHandle handle
-        cdef nn_uint c_index = index
-        CALL(NNSymbolGetOutput(self.handle, c_index, &handle))
-        return NewSymbol(handle)
-
-    def attr(self, key):
-        """Get attribute string from the symbol, this function only works for non-grouped symbol.
-
-        Parameters
-        ----------
-        key : str
-            The key to get attribute from.
-
-        Returns
-        -------
-        value : str
-            The attribute value of the key, returns None if attribute do not exist.
-        """
-        cdef const char* ret
-        cdef int success
-        key = c_str(key)
-
-        CALL(NNSymbolGetAttr(
-            self.handle, key, &ret, &success))
-        if success != 0:
-            return py_str(ret.value)
-        else:
-            return None
-
-    def list_attr(self, recursive=False):
-        """Get all attributes from the symbol.
-
-        Parameters
-        ----------
-        recursive : bool
-            Default `False`. When `recursive` is `True`, list recursively all the
-            attributes in the descendents. The attribute names are pre-pended with
-            the symbol names to avoid conflicts. If `False`, then only attributes
-            that belongs to this symbol is returned, and the attribute names will
-            **not** be pre-pended with the symbol name.
-        """
-        cdef nn_uint size
-        cdef const char** pairs
-        cdef int option
-        option = 0 if recursive else 1
-        CALL(NNSymbolListAttrs(
-            self.handle, option, &size, &pairs))
-        return {py_str(pairs[i*2]): py_str(pairs[i*2+1]) for i in range(size)}
-
     def _set_attr(self, **kwargs):
         """Set the attribute of the symbol.
 
@@ -165,49 +95,6 @@ cdef class Symbol:
         """
         SymbolSetAttr(self.handle, kwargs)
 
-    def get_internals(self):
-        """Get a new grouped symbol whose output contains all the internal outputs of this symbol.
-
-        Returns
-        -------
-        sgroup : Symbol
-            The internal of the symbol.
-        """
-        cdef SymbolHandle handle
-        CALL(NNSymbolGetInternals(self.handle, &handle))
-        return NewSymbol(handle)
-
-    def list_arguments(self):
-        """List all the arguments in the symbol.
-
-        Returns
-        -------
-        args : list of string
-            List of all the arguments.
-        """
-        cdef nn_uint size
-        cdef const char ** sarr
-        CALL(NNSymbolListArguments(self.handle, &size, &sarr))
-        return [py_str(sarr[i]) for i in range(size)]
-
-    def list_outputs(self):
-        """List all outputs in the symbol.
-
-        Returns
-        -------
-        returns : list of string
-            List of all the outputs.
-        """
-        cdef nn_uint size
-        cdef const char ** sarr
-        CALL(NNSymbolListOutputs(self.handle, &size, &sarr))
-        return [py_str(sarr[i]) for i in range(size)]
-
-    def debug_str(self):
-        cdef const char* out_str
-        CALL(NNSymbolPrint(self.handle, &out_str))
-        return py_str(out_str)
-
 
 cdef SymbolSetAttr(SymbolHandle handle, dict kwargs):
     cdef vector[string] sparam_keys
@@ -224,34 +111,18 @@ cdef SymbolSetAttr(SymbolHandle handle, dict kwargs):
         handle, num_args, CBeginPtr(param_keys), CBeginPtr(param_vals)))
 
 
+_symbol_cls = SymbolBase
+
+def _set_symbol_class(cls):
+    global _symbol_cls
+    _symbol_cls = cls
+
 cdef NewSymbol(SymbolHandle handle):
     """Create a new symbol given handle"""
-    sym = Symbol(None)
-    sym.handle = handle
+    sym = _symbol_cls(None)
+    (<SymbolBase>sym).handle = handle
     return sym
 
-
-def Variable(name, **kwargs):
-    """Create a symbolic variable with specified name.
-
-    Parameters
-    ----------
-    name : str
-        Name of the variable.
-    kwargs : dict of string -> string
-        Additional attributes to set on the variable.
-
-    Returns
-    -------
-    variable : Symbol
-        The created variable symbol.
-    """
-    cdef SymbolHandle handle
-    name = c_str(name)
-    CALL(NNSymbolCreateVariable(name, &handle))
-    return NewSymbol(handle)
-
-
 cdef _make_atomic_symbol_function(AtomicSymbolCreator handle):
     """Create an atomic symbol function by handle and funciton name."""
     cdef const char *name
@@ -292,9 +163,9 @@ cdef _make_atomic_symbol_function(AtomicSymbolCreator handle):
 
         if len(kwargs) != 0:
             for k, v in kwargs.items():
-                if isinstance(v, Symbol):
+                if isinstance(v, SymbolBase):
                     ssymbol_keys.push_back(c_str(k))
-                    symbol_args.push_back((<Symbol>v).handle)
+                    symbol_args.push_back((<SymbolBase>v).handle)
                 else:
                     sparam_keys.push_back(c_str(k))
                     sparam_vals.push_back(c_str(str(v)))
@@ -304,9 +175,9 @@ cdef _make_atomic_symbol_function(AtomicSymbolCreator handle):
                 raise TypeError("compose only accept input Symbols\
                     either as positional or keyword arguments, not both")
             for v in args:
-                if not isinstance(v, Symbol):
+                if not isinstance(v, SymbolBase):
                     raise TypeError('Compose expect `Symbol` as arguments')
-                symbol_args.push_back((<Symbol>v).handle)
+                symbol_args.push_back((<SymbolBase>v).handle)
 
         cdef vector[const char*] param_keys = SVec2Ptr(sparam_keys)
         cdef vector[const char*] param_vals = SVec2Ptr(sparam_vals)
@@ -344,46 +215,20 @@ cdef _make_atomic_symbol_function(AtomicSymbolCreator handle):
     return creator
 
 
-def Group(symbols):
-    """Create a symbol that groups symbols together.
-
-    Parameters
-    ----------
-    symbols : list
-        List of symbols to be grouped.
-
-    Returns
-    -------
-    sym : Symbol
-        The created group symbol.
-     """
-    cdef vector[SymbolHandle] ihandles
-    cdef SymbolHandle handle
-
-    for sym in symbols:
-        if not isinstance(sym, Symbol):
-            raise TypeError("Expect Symbols in the list input")
-        ihandles.push_back((<Symbol>sym).handle)
-    if ihandles.size() == 0:
-        raise ValueError("expect at least one element in the input")
-    CALL(NNSymbolCreateGroup(<nn_uint>ihandles.size(),
-                             &ihandles[0], &handle))
-    return NewSymbol(handle)
-
-
 def _init_symbol_module():
     """List and add all the atomic symbol functions to current module."""
     cdef AtomicSymbolCreator* plist
     cdef nn_uint size
     CALL(NNSymbolListAtomicSymbolCreators(&size, &plist))
     module_obj = _sys.modules["nnvm.symbol"]
+    module_internal = _sys.modules["nnvm._symbol_internal"]
     for i in range(size):
         function = _make_atomic_symbol_function(plist[i])
+
         if function.__name__.startswith('_'):
-            setattr(Symbol, function.__name__, staticmethod(function))
+            setattr(module_internal, function.__name__, function)
         else:
             setattr(module_obj, function.__name__, function)
 
-
 # Initialize the atomic symbol in startups
 _init_symbol_module()
diff --git a/nnvm/python/nnvm/symbol.py b/nnvm/python/nnvm/symbol.py
index ac1dee39a08b..7eab9f6045f7 100644
--- a/nnvm/python/nnvm/symbol.py
+++ b/nnvm/python/nnvm/symbol.py
@@ -2,13 +2,214 @@
 from __future__ import absolute_import as _abs
 import sys as _sys
 import os as _os
+import ctypes as _ctypes
 
+from numbers import Number as _Number
+from . import _base
+from ._base import _LIB, check_call as _check_call
+from . import _symbol_internal as _internal
+from .attribute import AttrScope
+
+# Use different verison of SymbolBase
+# When possible, use cython to speedup part of computation.
 try:
     if int(_os.environ.get("NNVM_ENABLE_CYTHON", True)) == 0:
-        from .ctypes.symbol import Symbol, Variable
+        from .ctypes.symbol import SymbolBase, _set_symbol_class
     elif _sys.version_info >= (3, 0):
-        from ._cy3.symbol import Symbol, Variable, Group
+        from ._cy3.symbol import SymbolBase, _set_symbol_class
     else:
-        from ._cy2.symbol import Symbol, Variable, Group
+        from ._cy2.symbol import SymbolBase, _set_symbol_class
 except:
-    from .ctypes.symbol import Symbol, Variable, Group
+    from .ctypes.symbol import SymbolBase, _set_symbol_class
+
+
+class Symbol(SymbolBase):
+    """Symbol is basic operation unit for symbolic graph compostion."""
+    # disable dictionary storage, also do not have parent type.
+    __slots__ = []
+
+    def __add__(self, other):
+        if isinstance(other, Symbol):
+            return _internal.__add__symbol__(self, other)
+        elif isinstance(other, _Number):
+            return _internal.__add__scalar__(self, scalar=other)
+        else:
+            raise TypeError("type %s not supported" % str(type(other)))
+
+    def __copy__(self):
+        return self.__deepcopy__()
+
+    def __deepcopy__(self, _=None):
+        handle = _base.SymbolHandle()
+        _base.check_call(_LIB.NNSymbolCopy(self.handle,
+                                           _ctypes.byref(handle)))
+        return Symbol(handle)
+
+    def __getitem__(self, index):
+        if isinstance(index, _base.string_types):
+            idx = None
+            for i, name in enumerate(self.list_outputs()):
+                if name == index:
+                    if idx is not None:
+                        raise ValueError('There are multiple outputs with name \"%s\"' % index)
+                    idx = i
+            if idx is None:
+                raise ValueError('Cannot find output that matches name \"%s\"' % index)
+            index = idx
+        if not isinstance(index, int):
+            raise TypeError('Symbol only support integer index to fetch i-th output')
+        handle = _base.SymbolHandle()
+        _check_call(_LIB.NNSymbolGetOutput(
+            self.handle, _base.nn_uint(index), _ctypes.byref(handle)))
+        return Symbol(handle=handle)
+
+    def attr(self, key):
+        """Get attribute string from the symbol, this function only works for non-grouped symbol.
+
+        Parameters
+        ----------
+        key : str
+            The key to get attribute from.
+
+        Returns
+        -------
+        value : str
+            The attribute value of the key, returns None if attribute do not exist.
+        """
+        ret = _ctypes.c_char_p()
+        success = _ctypes.c_int()
+        _check_call(_LIB.NNSymbolGetAttr(
+            self.handle, c_str(key), _ctypes.byref(ret), _ctypes.byref(success)))
+        if success.value != 0:
+            return _base.py_str(ret.value)
+        else:
+            return None
+
+    def list_attr(self, recursive=False):
+        """Get all attributes from the symbol.
+
+        Parameters
+        ----------
+        recursive : bool
+            Default `False`. When `recursive` is `True`, list recursively all the
+            attributes in the descendents. The attribute names are pre-pended with
+            the symbol names to avoid conflicts. If `False`, then only attributes
+            that belongs to this symbol is returned, and the attribute names will
+            **not** be pre-pended with the symbol name.
+        """
+        size = _base.nn_uint()
+        pairs = _ctypes.POINTER(_ctypes.c_char_p)()
+        option = _ctypes.c_int(0) if recursive else _ctypes.c_int(1)
+        _check_call(_LIB.NNSymbolListAttrs(
+            self.handle, option, _ctypes.byref(size), _ctypes.byref(pairs)))
+        return {_base.py_str(pairs[i*2]): _base.py_str(pairs[i*2+1]) for i in range(size.value)}
+
+    def get_internals(self):
+        """Get a new grouped symbol whose output contains all the internal outputs of this symbol.
+
+        Returns
+        -------
+        sgroup : Symbol
+            The internal of the symbol.
+        """
+        handle = _base.SymbolHandle()
+        _check_call(_LIB.NNSymbolGetInternals(
+            self.handle, _ctypes.byref(handle)))
+        return Symbol(handle=handle)
+
+    def list_arguments(self):
+        """List all the arguments in the symbol.
+
+        Returns
+        -------
+        args : list of string
+            List of all the arguments.
+        """
+        size = _ctypes.c_uint()
+        sarr = _ctypes.POINTER(_ctypes.c_char_p)()
+        _check_call(_LIB.NNSymbolListArguments(
+            self.handle, _ctypes.byref(size), _ctypes.byref(sarr)))
+        return [_base.py_str(sarr[i]) for i in range(size.value)]
+
+    def list_outputs(self):
+        """List all outputs in the symbol.
+
+        Returns
+        -------
+        returns : list of string
+            List of all the outputs.
+        """
+        size = _ctypes.c_uint()
+        sarr = _ctypes.POINTER(_ctypes.c_char_p)()
+        _check_call(_LIB.NNSymbolListOutputs(
+            self.handle, _ctypes.byref(size), _ctypes.byref(sarr)))
+        return [_base.py_str(sarr[i]) for i in range(size.value)]
+
+    def debug_str(self):
+        """Get a debug string.
+
+        Returns
+        -------
+        debug_str : string
+            Debug string of the symbol.
+        """
+        debug_str = _ctypes.c_char_p()
+        _check_call(_LIB.NNSymbolPrint(
+            self.handle, _ctypes.byref(debug_str)))
+        return _base.py_str(debug_str.value)
+
+
+def Variable(name, **kwargs):
+    """Create a symbolic variable with specified name.
+
+    Parameters
+    ----------
+    name : str
+        Name of the variable.
+    kwargs : dict of string -> string
+        Additional attributes to set on the variable.
+
+    Returns
+    -------
+    variable : Symbol
+        The created variable symbol.
+    """
+    if not isinstance(name, _base.string_types):
+        raise TypeError('Expect a string for variable `name`')
+    handle = _base.SymbolHandle()
+    _base.check_call(_LIB.NNSymbolCreateVariable(
+        _base.c_str(name), _ctypes.byref(handle)))
+    ret = Symbol(handle)
+    attr = AttrScope.current.get(kwargs)
+    if attr:
+        ret._set_attr(**attr)
+    return ret
+
+
+def Group(symbols):
+    """Create a symbol that groups symbols together.
+
+    Parameters
+    ----------
+    symbols : list
+        List of symbols to be grouped.
+
+    Returns
+    -------
+    sym : Symbol
+        The created group symbol.
+     """
+    ihandles = []
+    for sym in symbols:
+        if not isinstance(sym, Symbol):
+            raise TypeError('Expect Symbols in the list input')
+        ihandles.append(sym.handle)
+    handle = _base.SymbolHandle()
+    _check_call(_LIB.NNSymbolCreateGroup(
+        _base.nn_uint(len(ihandles)),
+        _base.c_array(_base.SymbolHandle, ihandles),
+        _ctypes.byref(handle)))
+    return Symbol(handle)
+
+# Set the real symbol class to Symbol
+_set_symbol_class(Symbol)

From 562933026c7ce317f9915c638dbdbaafa964bf33 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 18 Jul 2016 11:51:06 -0700
Subject: [PATCH 343/949] Updates (#14)

* Remove outstanding cython functions

* Add in operator overload

* Enable JSON to save version
---
 nnvm/python/nnvm/cython/symbol.pyx | 25 ------------
 nnvm/python/nnvm/symbol.py         | 63 +++++++++++++++++++++++++++++-
 nnvm/src/example/operator.cc       |  6 ++-
 nnvm/src/pass/saveload_json.cc     | 40 ++++++++++++++++---
 nnvm/tests/python/test_graph.py    |  2 +-
 5 files changed, 101 insertions(+), 35 deletions(-)

diff --git a/nnvm/python/nnvm/cython/symbol.pyx b/nnvm/python/nnvm/cython/symbol.pyx
index 7b1435381e99..ee3e2d0bf258 100644
--- a/nnvm/python/nnvm/cython/symbol.pyx
+++ b/nnvm/python/nnvm/cython/symbol.pyx
@@ -14,10 +14,6 @@ include "./base.pyi"
 
 cdef extern from "nnvm/c_api.h":
     const char* NNGetLastError();
-    int NNSymbolCreateVariable(const char *name, SymbolHandle *out);
-    int NNSymbolCreateGroup(nn_uint num_symbols,
-                            SymbolHandle *symbols,
-                            SymbolHandle *out);
     int NNSymbolListAtomicSymbolCreators(nn_uint *out_size,
                                          AtomicSymbolCreator **out_array);
     int NNSymbolCreateAtomicSymbol(AtomicSymbolCreator creator,
@@ -34,31 +30,10 @@ cdef extern from "nnvm/c_api.h":
                                     const char ***arg_descriptions,
                                     const char **return_type);
     int NNSymbolFree(SymbolHandle symbol);
-    int NNSymbolPrint(SymbolHandle symbol, const char **out_str);
-    int NNSymbolCopy(SymbolHandle symbol, SymbolHandle *out);
-    int NNSymbolGetAttr(SymbolHandle symbol,
-                        const char* key,
-                        const char** out,
-                        int *success);
     int NNSymbolSetAttrs(SymbolHandle symbol,
                          nn_uint num_param,
                          const char** keys,
                          const char** values);
-    int NNSymbolListAttrs(SymbolHandle symbol,
-                          int recursive_option,
-                          nn_uint *out_size,
-                          const char*** out);
-    int NNSymbolListArguments(SymbolHandle symbol,
-                              nn_uint *out_size,
-                              const char ***out_str_array);
-    int NNSymbolListOutputs(SymbolHandle symbol,
-                            nn_uint *out_size,
-                            const char ***out_str_array);
-    int NNSymbolGetInternals(SymbolHandle symbol,
-                             SymbolHandle *out);
-    int NNSymbolGetOutput(SymbolHandle symbol,
-                          nn_uint index,
-                          SymbolHandle *out);
     int NNSymbolCompose(SymbolHandle sym,
                         const char* name,
                         nn_uint num_args,
diff --git a/nnvm/python/nnvm/symbol.py b/nnvm/python/nnvm/symbol.py
index 7eab9f6045f7..31f1660fc899 100644
--- a/nnvm/python/nnvm/symbol.py
+++ b/nnvm/python/nnvm/symbol.py
@@ -30,12 +30,71 @@ class Symbol(SymbolBase):
 
     def __add__(self, other):
         if isinstance(other, Symbol):
-            return _internal.__add__symbol__(self, other)
+            return _internal.__add_symbol__(self, other)
         elif isinstance(other, _Number):
-            return _internal.__add__scalar__(self, scalar=other)
+            return _internal.__add_scalar__(self, scalar=other)
         else:
             raise TypeError("type %s not supported" % str(type(other)))
 
+    def __radd__(self, other):
+        return self.__add__(other)
+
+    def __sub__(self, other):
+        if isinstance(other, Symbol):
+            return _internal.__sub_symbol__(self, other)
+        if isinstance(other, Number):
+            return _internal.__sub_scalar__(self, scalar=other)
+        else:
+            raise TypeError('type %s not supported' % str(type(other)))
+
+    def __rsub__(self, other):
+        if isinstance(other, Number):
+            return _internal.__rsub_scalar__(self, scalar=other)
+        else:
+            raise TypeError('type %s not supported' % str(type(other)))
+
+    def __mul__(self, other):
+        if isinstance(other, Symbol):
+            return _internal.__mul_symbol__(self, other)
+        if isinstance(other, Number):
+            return _internal.__mul_scalar__(self, scalar=other)
+        else:
+            raise TypeError('type %s not supported' % str(type(other)))
+
+    def __rmul__(self, other):
+        return self.__mul__(other)
+
+    def __div__(self, other):
+        if isinstance(other, Symbol):
+            return _internal.__div_symbol__(self, other)
+        if isinstance(other, Number):
+            return _internal.__div_scalar__(self, scalar=other)
+        else:
+            raise TypeError('type %s not supported' % str(type(other)))
+
+    def __rdiv__(self, other):
+        if isinstance(other, Number):
+            return _internal.__rdiv_scalar__(self, scalar=other)
+        else:
+            raise TypeError('type %s not supported' % str(type(other)))
+
+    def __truediv__(self, other):
+        return self.__div__(other)
+
+    def __rtruediv__(self, other):
+        return self.__rdiv__(other)
+
+    def __pow__(self, other):
+        if isinstance(other, Symbol):
+            return _internal.__pow_symbol__(self, other)
+        if isinstance(other, Number):
+            return _internal.__pow_scalar__(self, scalar=other)
+        else:
+            raise TypeError('type %s not supported' % str(type(other)))
+
+    def __neg__(self):
+        return self.__mul__(-1.0)
+
     def __copy__(self):
         return self.__deepcopy__()
 
diff --git a/nnvm/src/example/operator.cc b/nnvm/src/example/operator.cc
index 2bd4a22ed2dd..9078c314b119 100644
--- a/nnvm/src/example/operator.cc
+++ b/nnvm/src/example/operator.cc
@@ -11,9 +11,11 @@ using nnvm::NodeAttrs;
 
 NNVM_REGISTER_OP(add)
 .describe("add two data together")
-.set_num_inputs(2)
-.attr("inplace_pair", std::make_pair(0, 0));
+.set_num_inputs(2);
 
+NNVM_REGISTER_OP(__add_symbol__)
+.describe("Alias of add")
+.set_num_inputs(2);
 
 NNVM_REGISTER_OP(exp)
 .describe("take exponmential")
diff --git a/nnvm/src/pass/saveload_json.cc b/nnvm/src/pass/saveload_json.cc
index b454d4f7a736..6ba7ac23f50f 100644
--- a/nnvm/src/pass/saveload_json.cc
+++ b/nnvm/src/pass/saveload_json.cc
@@ -30,7 +30,32 @@ namespace pass {
 // auxiliary node structure for serialization.
 struct JSONNode {
   // the node entry structure in serialized format
-  typedef std::pair<uint32_t, uint32_t> Entry;
+  struct Entry {
+    uint32_t node_id;
+    uint32_t index;
+    uint32_t version;
+    void Save(dmlc::JSONWriter *writer) const {
+      writer->BeginArray();
+      writer->WriteArrayItem(node_id);
+      writer->WriteArrayItem(index);
+      writer->WriteArrayItem(version);
+      writer->EndArray();
+    }
+    void Load(dmlc::JSONReader *reader) {
+      reader->BeginArray();
+      CHECK(reader->NextArrayItem()) << "invalid json format";
+      reader->Read(&node_id);
+      CHECK(reader->NextArrayItem()) << "invalid json format";
+      reader->Read(&index);
+      if (reader->NextArrayItem()) {
+        reader->Read(&version);
+        CHECK(!reader->NextArrayItem()) << "invalid json format";
+      } else {
+        version = 0;
+      }
+    }
+  };
+
   // pointer to the graph node
   NodePtr node;
   // inputs
@@ -75,6 +100,10 @@ struct JSONNode {
     if (op_type_str != "null") {
       try {
         node->op = Op::Get(op_type_str);
+        // rebuild attribute parser
+        if (node->op->attr_parser != nullptr) {
+          node->op->attr_parser(&(node->attrs));
+        }
       } catch (const dmlc::Error &err) {
         std::ostringstream os;
         os << "Failed loading Op " << node->attrs.name
@@ -132,7 +161,7 @@ Graph LoadJSON(const Graph& src) {
     n.node->inputs.reserve(n.inputs.size());
     for (const JSONNode::Entry &e : n.inputs) {
       n.node->inputs.emplace_back(
-          NodeEntry{jgraph.nodes[e.first].node, e.second});
+          NodeEntry{jgraph.nodes[e.node_id].node, e.index, e.version});
     }
     n.node->control_deps.reserve(n.control_deps.size());
     for (uint32_t nid : n.control_deps) {
@@ -150,7 +179,7 @@ Graph LoadJSON(const Graph& src) {
   ret.outputs.reserve(jgraph.heads.size());
   for (const JSONNode::Entry &e : jgraph.heads) {
     ret.outputs.emplace_back(
-        NodeEntry{jgraph.nodes[e.first].node, e.second});
+        NodeEntry{jgraph.nodes[e.node_id].node, e.index, e.version});
   }
   return ret;
 }
@@ -170,7 +199,7 @@ Graph SaveJSON(const Graph& src) {
       jnode.inputs.reserve(n->inputs.size());
       for (const NodeEntry& e : n->inputs) {
         jnode.inputs.emplace_back(
-            std::make_pair(node2index.at(e.node.get()), e.index));
+            JSONNode::Entry{node2index.at(e.node.get()), e.index, e.version});
       }
       for (const NodePtr& c : n->control_deps) {
         jnode.control_deps.push_back(node2index.at(c.get()));
@@ -179,7 +208,8 @@ Graph SaveJSON(const Graph& src) {
     });
 
   for (const NodeEntry& e : src.outputs) {
-    jgraph.heads.push_back(std::make_pair(node2index.at(e.node.get()), e.index));
+    jgraph.heads.push_back(
+        JSONNode::Entry{node2index.at(e.node.get()), e.index, e.version});
   }
 
   std::ostringstream os;
diff --git a/nnvm/tests/python/test_graph.py b/nnvm/tests/python/test_graph.py
index b6082364458e..0af641932a5d 100644
--- a/nnvm/tests/python/test_graph.py
+++ b/nnvm/tests/python/test_graph.py
@@ -33,7 +33,7 @@ def test_order_mutation_pass():
     assert nindex['assign'] in jnodes[nindex['add2']]['control_deps']
     assert nindex['conv'] in jnodes[nindex['assign']]['control_deps']
     assert nindex['add1'] in jnodes[nindex['assign']]['control_deps']
-
+    assert jnodes[nindex['assign']]['inputs'][0][2] == 1
 
 if __name__ == "__main__":
     test_order_mutation_pass()

From 9135bc03d4a9f531ebe2e7507b88c2d70874fc73 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 18 Jul 2016 21:33:02 -0700
Subject: [PATCH 344/949] [Pass] Check in infershape, move indexedgraph to
 graph.h (#15)

---
 nnvm/include/nnvm/graph.h                     | 149 ++++++++++++++++++
 nnvm/include/nnvm/graph_attr_types.h          | 133 +++-------------
 nnvm/include/nnvm/op_attr_types.h             |  23 +++
 nnvm/include/nnvm/tuple.h                     |  21 ++-
 .../core/{graph_attr_types.cc => graph.cc}    |   9 +-
 nnvm/src/example/operator.cc                  |  29 +++-
 nnvm/src/pass/infer_shape.cc                  |  47 ++++++
 nnvm/src/pass/order_mutation.cc               |   2 +
 nnvm/src/pass/saveload_json.cc                |   6 +
 9 files changed, 307 insertions(+), 112 deletions(-)
 rename nnvm/src/core/{graph_attr_types.cc => graph.cc} (91%)
 create mode 100644 nnvm/src/pass/infer_shape.cc

diff --git a/nnvm/include/nnvm/graph.h b/nnvm/include/nnvm/graph.h
index da1c952ac9b3..b09d9c61ab2e 100644
--- a/nnvm/include/nnvm/graph.h
+++ b/nnvm/include/nnvm/graph.h
@@ -18,6 +18,8 @@
 
 namespace nnvm {
 
+class IndexedGraph;
+
 /*!
  * \brief Symbolic computation graph.
  *  This is the intermediate representation for optimization pass.
@@ -32,6 +34,145 @@ class Graph {
    *  and can be shared across multiple Instance of graph
    */
   std::unordered_map<std::string, std::shared_ptr<const any> > attrs;
+  /*!
+   * \brief Get the attribute from attrs.
+   * \param attr_name the name of the attribute
+   * \return the reference to corresponding attribute
+   * \tparam T the type of the attribute.
+   */
+  template<typename T>
+  inline const T& GetAttr(const std::string& attr_name);
+  /*!
+   * \brief get a indexed graph of current graph, if not exist, create it on demand
+   * \return The indexed graph.
+   * \sa IndexedGraph
+   */
+  const IndexedGraph& indexed_graph();
+
+ private:
+  // internal structure of indexed graph
+  std::shared_ptr<const IndexedGraph> indexed_graph_;
+};
+
+/*!
+ * \brief Auxililary data structure to index a graph.
+ *  It maps Nodes in the graph to consecutive integers node_id.
+ *  It also maps IndexedGraph::NodeEntry to consecutive integer entry_id.
+ *  This allows storing properties of Node and NodeEntry into
+ *  compact vector and quickly access them without resorting to hashmap.
+ *
+ *  The node_id and entry_rptr are the same as the JSON graph produced by SaveJSON Pass.
+ */
+class IndexedGraph {
+ public:
+  /*! \brief represents a data in the graph */
+  struct NodeEntry {
+    /*! \brief the source node id in the computation graph */
+    uint32_t node_id;
+    /*! \brief index of output from the source. */
+    uint32_t index;
+    /*!
+     * \brief compare equality
+     * \param other the other entry to compare
+     * \return whether two entries equals to each other
+     */
+    inline bool operator==(const NodeEntry& other) const {
+      return node_id == other.node_id && index == other.index;
+    }
+  };
+  /*! \brief Node data structure in IndexedGraph */
+  struct Node {
+    /*! \brief pointer to the source node */
+    const nnvm::Node* source;
+    /*! \brief inputs to the node */
+    array_view<NodeEntry> inputs;
+    /*! \brief control flow dependencies to the node */
+    array_view<uint32_t> control_deps;
+  };
+  /*! \return number of nodes in the graph */
+  inline size_t num_nodes() const {
+    return nodes_.size();
+  }
+  /*! \return total number of NodeEntry in the graph */
+  inline size_t num_node_entries() const {
+    return entry_rptr_.back();
+  }
+  /*!
+   * \brief Get a unique entry id between 0 to num_node_entries()
+   *  for a given IndexedGraph::NodeEntry
+   * \param node_id The node index
+   * \param index the output index
+   * \return the unique index.
+   */
+  inline uint32_t entry_id(uint32_t node_id, uint32_t index) const {
+    return entry_rptr_[node_id] + index;
+  }
+  /*!
+   * \brief Get a unique entry id between 0 to num_node_entries()
+   *  for a given IndexedGraph::NodeEntry
+   * \param e The entry to query for index.
+   * \return the unique index.
+   */
+  inline uint32_t entry_id(const NodeEntry& e) const {
+    return entry_rptr_[e.node_id] + e.index;
+  }
+  /*!
+   * \brief Get a unique entry id between 0 to num_node_entries()
+   *  for a given NodeEntry.
+   * \param e The entry to query for index.
+   * \return the unique index.
+   */
+  inline uint32_t entry_id(const nnvm::NodeEntry& e) const {
+    return entry_rptr_[node_id(e.node.get())] + e.index;
+  }
+  /*!
+   * \brief Get the corresponding node id for a given Node in the IndexedGraph.
+   * \param node The Node to query for index.
+   * \return the node index.
+   */
+  inline uint32_t node_id(const nnvm::Node* node) const {
+    return node2index_.at(node);
+  }
+  /*!
+   * \brief Get the corresponding Node structure for a given node_id.
+   * \param node_id The node id
+   * \return const reference to the corresponding IndexedGraph::Node
+   */
+  inline const Node& operator[](uint32_t node_id) const {
+    return nodes_[node_id];
+  }
+  /*!
+   * \brief Get the corresponding Node structure
+   * \param node The pointer to the Node structure
+   * \return const reference to the corresponding IndexedGraph::Node
+   */
+  inline const Node& operator[](const nnvm::Node* node) const {
+    return nodes_[node_id(node)];
+  }
+  /*! \return list of argument nodes */
+  inline const std::vector<uint32_t>& arg_nodes() const {
+    return arg_nodes_;
+  }
+
+ private:
+  friend class Graph;
+  /*!
+   * \brief Constructor an IndexedGraph from normal Graph
+   * \param other The source graph.
+   */
+  explicit IndexedGraph(const Graph& other);
+  // node pointers in CSR structure.
+  std::vector<Node> nodes_;
+  // index to argument nodes
+  std::vector<uint32_t> arg_nodes_;
+  // mapping from node to index.
+  std::unordered_map<const nnvm::Node*, uint32_t> node2index_;
+  // CSR pointer of node entries
+  std::vector<size_t> entry_rptr_;
+  // space to store input entries of each
+  std::vector<NodeEntry> input_entries_;
+  // control flow dependencies
+  std::vector<uint32_t> control_deps_;
 };
 
 /*!
@@ -45,6 +186,14 @@ template<typename FVisit>
 inline void DFSVisit(const std::vector<NodeEntry>& heads, FVisit fvisit);
 
 // inline function implementations
+template<typename T>
+inline const T& Graph::GetAttr(const std::string& attr_name) {
+  auto it = attrs.find(attr_name);
+  CHECK(it != attrs.end())
+      << "Cannot find attribute " << attr_name << " in the graph";
+  return nnvm::get<T>(*it->second);
+}
+
 template <typename GNode, typename HashType,
            typename FVisit, typename HashFunc,
           typename InDegree, typename GetInput>
diff --git a/nnvm/include/nnvm/graph_attr_types.h b/nnvm/include/nnvm/graph_attr_types.h
index ab14185724f1..4af9fabaf62d 100644
--- a/nnvm/include/nnvm/graph_attr_types.h
+++ b/nnvm/include/nnvm/graph_attr_types.h
@@ -7,120 +7,37 @@
 #define NNVM_GRAPH_ATTR_TYPES_H_
 
 #include <vector>
-#include <unordered_map>
-#include "./graph.h"
+#include <string>
+#include "./tuple.h"
 
 namespace nnvm {
 
 /*!
- * \brief Auxililary data structure to index a graph.
- *  It maps Nodes in the graph to consecutive integers node_id.
- *  It also maps IndexedGraph::NodeEntry to consecutive integer entry_id.
- *  This allows storing properties of Node and NodeEntry into
- *  compact vector and quickly access them without resorting to hashmap.
+ * \brief The result holder of JSON serializer
+ *
+ * \note Stored under ret.attrs["json"], provided by Pass "SaveJSON"
+
+ * \code
+ *  Graph ret = ApplyPass(src_graph, {"SaveJSON"});
+ *  const JSONString& json = ret.GetAttr<JSONString>("shape");
+ * \endcode
  */
-struct IndexedGraph {
- public:
-  /*! \brief represents a data in the graph */
-  struct NodeEntry {
-    /*! \brief the source node id in the computation graph */
-    uint32_t node_id;
-    /*! \brief index of output from the source. */
-    uint32_t index;
-    /*!
-     * \brief compare equality
-     * \param other the other entry to compare
-     * \return whether two entries equals to each other
-     */
-    inline bool operator==(const NodeEntry& other) const {
-      return node_id == other.node_id && index == other.index;
-    }
-  };
-  /*! \brief Node data structure in IndexedGraph */
-  struct Node {
-    /*! \brief pointer to the source node */
-    const nnvm::Node* source;
-    /*! \brief inputs to the node */
-    array_view<NodeEntry> inputs;
-    /*! \brief control flow dependencies to the node */
-    array_view<uint32_t> control_deps;
-  };
-  /*! \return number of nodes in the graph */
-  inline size_t num_nodes() const {
-    return nodes_.size();
-  }
-  /*! \return total number of NodeEntry in the graph */
-  inline size_t num_node_entries() const {
-    return entry_rptr_.back();
-  }
-  /*!
-   * \brief Get a unique entry id between 0 to num_node_entries()
-   *  for a given IndexedGraph::NodeEntry
-   * \param e The entry to query for index.
-   * \return the unique index.
-   */
-  inline uint32_t entry_id(const NodeEntry& e) const {
-    return entry_rptr_[e.node_id] + e.index;
-  }
-  /*!
-   * \brief Get a unique entry id between 0 to num_node_entries()
-   *  for a given NodeEntry.
-   * \param e The entry to query for index.
-   * \return the unique index.
-   */
-  inline uint32_t entry_id(const nnvm::NodeEntry& e) const {
-    return entry_rptr_[node_id(e.node.get())] + e.index;
-  }
-  /*!
-   * \brief Get the corresponding node id for a given Node in the IndexedGraph.
-   * \param node The Node to query for index.
-   * \return the node index.
-   */
-  inline uint32_t node_id(const nnvm::Node* node) const {
-    return node2index_.at(node);
-  }
-  /*!
-   * \brief Get the corresponding Node structure for a given node_id.
-   * \param node_id The node id
-   * \return const reference to the corresponding IndexedGraph::Node
-   */
-  inline const Node& operator[](uint32_t node_id) const {
-    return nodes_[node_id];
-  }
-  /*!
-   * \brief Get the corresponding Node structure
-   * \param node The pointer to the Node structure
-   * \return const reference to the corresponding IndexedGraph::Node
-   */
-  inline const Node& operator[](const nnvm::Node* node) const {
-    return nodes_[node_id(node)];
-  }
-  /*! \return list of argument nodes */
-  inline const std::vector<uint32_t>& arg_nodes() const {
-    return arg_nodes_;
-  }
-  /*!
-   * \brief Constructor an IndexedGraph from normal Graph
-   * \param other The source graph.
-   */
-  explicit IndexedGraph(const Graph& other);
-  // disallow copy assign
-  IndexedGraph(const IndexedGraph& other) = delete;
+using JSONString = std::string;
 
- private:
-  // node pointers in CSR structure.
-  std::vector<Node> nodes_;
-  // index to argument nodes
-  std::vector<uint32_t> arg_nodes_;
-  // mapping from node to index.
-  std::unordered_map<const nnvm::Node*, uint32_t> node2index_;
-  // CSR pointer of node entries
-  std::vector<size_t> entry_rptr_;
-  // space to store input entries of each
-  std::vector<NodeEntry> input_entries_;
-  // control flow dependencies
-  std::vector<uint32_t> control_deps_;
-};
+/*!
+ * \brief The result holder of shape of each NodeEntry in the graph.
+ * \note Stored under graph.attrs["shape"], provided by Pass "InferShape"
+ *
+ * \code
+ *  Graph g = ApplyPass(src_graph, {"InferShape"});
+ *  const ShapeVector& shapes = g.GetAttr<ShapeVector>("shape");
+ *  // get shape by entry id
+ *  TShape entry_shape = shapes[g.indexed_graph().entry_id(my_entry)];
+ * \endcode
+ *
+ * \sa FInferShape
+ */
+using ShapeVector = std::vector<TShape>;
 
 }  // namespace nnvm
 
diff --git a/nnvm/include/nnvm/op_attr_types.h b/nnvm/include/nnvm/op_attr_types.h
index 615fca24e07c..9049f39632e7 100644
--- a/nnvm/include/nnvm/op_attr_types.h
+++ b/nnvm/include/nnvm/op_attr_types.h
@@ -9,6 +9,8 @@
 #include <vector>
 #include <string>
 #include <functional>
+#include "./base.h"
+#include "./tuple.h"
 
 namespace nnvm {
 
@@ -39,6 +41,7 @@ using FListOutputNames = std::function<std::vector<std::string> (const NodeAttrs
 
 /*!
  * \brief Check whether operator will mutate k-th input.
+ * \param attrs The attributes of the node.
  * \param index The input index
  * \return Whether this operator will mutate index-th input.
  *
@@ -47,6 +50,26 @@ using FListOutputNames = std::function<std::vector<std::string> (const NodeAttrs
  */
 using FMutateInput = std::function<bool (const NodeAttrs& attrs, uint32_t index)>;
 
+/*!
+ * \brief Shape inference function.
+ *  Update the shapes given the input shape information.
+ *  TShape.ndim() == 0 means the shape is still unknown.
+ *
+ * \param attrs The attributes of the node.
+ * \param in_shapes Array of shapes from the inputs.
+ * \param out_shapes Array of shapes from the outputs.
+ *
+ * \return Whether all the shapes are known.
+ *
+ * \note Register under "FInferShape",
+ *  by default do not update any shapes.
+ *
+ *  FInferShape is needed by shape inference
+ */
+using FInferShape = std::function<bool (const NodeAttrs& attrs,
+                                        array_view<TShape*> in_shapes,
+                                        array_view<TShape*> out_shapes)>;
+
 }  // namespace nnvm
 
 #endif  // NNVM_OP_ATTR_TYPES_H_
diff --git a/nnvm/include/nnvm/tuple.h b/nnvm/include/nnvm/tuple.h
index 8898fc054ca7..755f2720fa78 100644
--- a/nnvm/include/nnvm/tuple.h
+++ b/nnvm/include/nnvm/tuple.h
@@ -10,6 +10,7 @@
 #include <type_traits>
 #include <algorithm>
 #include <iostream>
+#include "./base.h"
 
 namespace nnvm {
 
@@ -179,7 +180,23 @@ class Tuple {
   inline const ValueType& operator[](index_t i) const {
     return begin()[i];
   }
-
+  /*!
+   * \brief Save Tuple to JSON.
+   * \param writer JSONWriter
+   */
+  inline void Save(dmlc::JSONWriter* writer) const {
+    std::vector<ValueType> tmp(begin(), end());
+    writer->Write(tmp);
+  }
+  /*!
+   * \brief Load Tuple from JSON.
+   * \param reader JSONReader
+   */
+  inline void Load(dmlc::JSONReader* reader) {
+    std::vector<ValueType> tmp;
+    reader->Read(&tmp);
+    this->assign(tmp.begin(), tmp.end());
+  }
   /*!
    * \brief allow output string of tuple to ostream
    * \param os the output stream
@@ -287,6 +304,8 @@ class TShape : public Tuple<index_t> {
  public:
   // inheritate other constructors from Tuple
   using Tuple<index_t>::Tuple;
+  /*! \brief default constructor */
+  TShape() = default;
   /*!
    * \brief copy constructor of TShape
    * \param s source shape.
diff --git a/nnvm/src/core/graph_attr_types.cc b/nnvm/src/core/graph.cc
similarity index 91%
rename from nnvm/src/core/graph_attr_types.cc
rename to nnvm/src/core/graph.cc
index 745a909bdf90..601f84d0a576 100644
--- a/nnvm/src/core/graph_attr_types.cc
+++ b/nnvm/src/core/graph.cc
@@ -3,11 +3,18 @@
  * \file graph_attr_types.cc
  * \brief Graph node data structure.
  */
-#include <nnvm/graph_attr_types.h>
+#include <nnvm/graph.h>
 #include <limits>
 
 namespace nnvm {
 
+const IndexedGraph& Graph::indexed_graph() {
+  if (indexed_graph_ == nullptr) {
+    indexed_graph_.reset(new IndexedGraph(*this));
+  }
+  return *indexed_graph_;
+}
+
 // implement constructor from graph
 IndexedGraph::IndexedGraph(const Graph &g) {
   entry_rptr_.push_back(0);
diff --git a/nnvm/src/example/operator.cc b/nnvm/src/example/operator.cc
index 9078c314b119..1b2fb1e10510 100644
--- a/nnvm/src/example/operator.cc
+++ b/nnvm/src/example/operator.cc
@@ -1,17 +1,39 @@
 // Copyright (c) 2016 by Contributors
 // This is an example on how we can register operator information to NNVM
 
+#include <nnvm/base.h>
 #include <nnvm/op.h>
 #include <nnvm/op_attr_types.h>
+#include <nnvm/graph_attr_types.h>
 #include <utility>
 
+namespace myproject {
+
 using nnvm::FListInputNames;
 using nnvm::FMutateInput;
+using nnvm::FInferShape;
 using nnvm::NodeAttrs;
+using nnvm::TShape;
+using nnvm::array_view;
+
+// simply return the shape as same
+inline bool SameShape(const NodeAttrs& attrs,
+                      array_view<TShape*> ishape,
+                      array_view<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;
+}
 
 NNVM_REGISTER_OP(add)
 .describe("add two data together")
-.set_num_inputs(2);
+.set_num_inputs(2)
+.attr<FInferShape>("FInferShape", SameShape);
 
 NNVM_REGISTER_OP(__add_symbol__)
 .describe("Alias of add")
@@ -20,7 +42,8 @@ NNVM_REGISTER_OP(__add_symbol__)
 NNVM_REGISTER_OP(exp)
 .describe("take exponmential")
 .set_num_inputs(1)
-.attr("inplace_pair", std::make_pair(0, 0));
+.attr("inplace_pair", std::make_pair(0, 0))
+.attr<FInferShape>("FInferShape", SameShape);
 
 
 NNVM_REGISTER_OP(conv2d)
@@ -39,3 +62,5 @@ NNVM_REGISTER_OP(assign)
 .attr<FMutateInput>("FMutateInput", [](const NodeAttrs& attrs, uint32_t index) {
     return index == 0;
   });
+
+}  // namespace myproject
diff --git a/nnvm/src/pass/infer_shape.cc b/nnvm/src/pass/infer_shape.cc
new file mode 100644
index 000000000000..5788cebfa89d
--- /dev/null
+++ b/nnvm/src/pass/infer_shape.cc
@@ -0,0 +1,47 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file infer_shape.cc
+ * \brief Inference the shapes given
+ */
+#include <nnvm/pass.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/graph_attr_types.h>
+
+namespace nnvm {
+namespace pass {
+
+Graph InferShape(const Graph& src) {
+  Graph ret = src;
+  const IndexedGraph& idx = ret.indexed_graph();
+  static auto& finfer_shape = Op::GetAttr<FInferShape>("FInferShape");
+  // reshape shape vector
+  ShapeVector rshape(idx.num_node_entries());
+  // temp space for shape inference.
+  std::vector<TShape*> ishape, oshape;
+  // number of completed nodes
+  size_t num_known = 0;
+  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+    const auto& inode = idx[nid];
+    if (inode.source->is_variable()) continue;
+    ishape.resize(inode.inputs.size());
+    for (uint32_t i = 0; i < ishape.size(); ++i) {
+      ishape[i] = &rshape[idx.entry_id(inode.inputs[i])];
+    }
+    oshape.resize(inode.source->num_outputs());
+    for (uint32_t i = 0; i < oshape.size(); ++i) {
+      oshape[i] = &rshape[idx.entry_id(nid, i)];
+    }
+    if (finfer_shape.count(inode.source->op)) {
+      num_known +=
+          finfer_shape[inode.source->op](inode.source->attrs, ishape, oshape);
+    }
+  }
+  // set the shapes
+  ret.attrs["shape"] = std::make_shared<any>(std::move(rshape));
+  // number of nodes who knows the shape.
+  ret.attrs["shape_num_known_nodes"] = std::make_shared<any>(num_known);
+  return ret;
+}
+
+}  // namespace pass
+}  // namespace nnvm
diff --git a/nnvm/src/pass/order_mutation.cc b/nnvm/src/pass/order_mutation.cc
index ecaeadeaadb4..a498660cfd1f 100644
--- a/nnvm/src/pass/order_mutation.cc
+++ b/nnvm/src/pass/order_mutation.cc
@@ -9,6 +9,7 @@
 #include <nnvm/op_attr_types.h>
 
 namespace nnvm {
+namespace pass {
 
 template<typename T>
 inline T get_with_default(const std::unordered_map<Node*, T> &map,
@@ -139,4 +140,5 @@ NNVM_REGISTER_PASS(OrderMutation)
 .set_body(OrderMutation)
 .set_change_graph(true);
 
+}  // namespace pass
 }  // namespace nnvm
diff --git a/nnvm/src/pass/saveload_json.cc b/nnvm/src/pass/saveload_json.cc
index 6ba7ac23f50f..0fe1d1896db6 100644
--- a/nnvm/src/pass/saveload_json.cc
+++ b/nnvm/src/pass/saveload_json.cc
@@ -120,6 +120,7 @@ struct JSONNode {
 struct JSONGraph {
   std::vector<JSONNode> nodes;
   std::vector<uint32_t> arg_nodes;
+  std::vector<uint32_t> node_row_ptr;
   std::vector<JSONNode::Entry> heads;
   std::unordered_map<std::string, std::shared_ptr<const any> > attrs;
 
@@ -127,6 +128,7 @@ struct JSONGraph {
     writer->BeginObject();
     writer->WriteObjectKeyValue("nodes", nodes);
     writer->WriteObjectKeyValue("arg_nodes", arg_nodes);
+    writer->WriteObjectKeyValue("node_row_ptr", node_row_ptr);
     writer->WriteObjectKeyValue("heads", heads);
     if (attrs.size() != 0) {
       writer->WriteObjectKeyValue("attrs", attrs);
@@ -140,6 +142,7 @@ struct JSONGraph {
     helper.DeclareField("nodes", &nodes);
     helper.DeclareField("arg_nodes", &arg_nodes);
     helper.DeclareField("heads", &heads);
+    helper.DeclareOptionalField("node_row_ptr", &node_row_ptr);
     helper.DeclareOptionalField("attrs", &attrs);
     helper.ReadAllFields(reader);
   }
@@ -188,6 +191,7 @@ Graph LoadJSON(const Graph& src) {
 Graph SaveJSON(const Graph& src) {
   JSONGraph jgraph;
   std::unordered_map<Node*, uint32_t> node2index;
+  jgraph.node_row_ptr.push_back(0);
   DFSVisit(src.outputs, [&node2index, &jgraph](const NodePtr& n) {
       uint32_t nid = static_cast<uint32_t>(jgraph.nodes.size());
       node2index[n.get()] = nid;
@@ -204,6 +208,8 @@ Graph SaveJSON(const Graph& src) {
       for (const NodePtr& c : n->control_deps) {
         jnode.control_deps.push_back(node2index.at(c.get()));
       }
+      jgraph.node_row_ptr.push_back(
+          jgraph.node_row_ptr.back() + n->num_outputs());
       jgraph.nodes.emplace_back(std::move(jnode));
     });
 

From 83c0745649bbb9e0dc3519b35259a6e53979775a Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 19 Jul 2016 19:58:43 -0700
Subject: [PATCH 345/949] [Pass] Finish infershape testcase (#16)

---
 nnvm/include/nnvm/pass.h           |  4 +-
 nnvm/include/nnvm/pass_functions.h | 76 ++++++++++++++++++++++++++++++
 nnvm/src/core/pass.cc              |  9 ++--
 nnvm/src/example/operator.cc       | 26 ++++++++++
 nnvm/src/pass/infer_shape.cc       | 37 +++++++++++++--
 nnvm/src/pass/order_mutation.cc    |  2 +-
 nnvm/src/pass/saveload_json.cc     |  4 +-
 nnvm/src/test_main.cc              |  1 +
 nnvm/tests/python/test_graph.py    | 16 +++++++
 9 files changed, 161 insertions(+), 14 deletions(-)
 create mode 100644 nnvm/include/nnvm/pass_functions.h

diff --git a/nnvm/include/nnvm/pass.h b/nnvm/include/nnvm/pass.h
index c1936941b331..438226f5c93f 100644
--- a/nnvm/include/nnvm/pass.h
+++ b/nnvm/include/nnvm/pass.h
@@ -23,7 +23,7 @@ namespace nnvm {
  * \param src The graph to be transformed.
  * \return The generated graph.
  */
-typedef std::function<Graph (const Graph& src)> PassFunction;
+typedef std::function<Graph (Graph src)> PassFunction;
 
 /*!
  * \brief Apply a series of pass transformations on g.
@@ -31,7 +31,7 @@ typedef std::function<Graph (const Graph& src)> PassFunction;
  * \param pass The name of pass to be applied.
  * \return The transformed graph
  */
-Graph ApplyPass(const Graph& src,
+Graph ApplyPass(Graph src,
                 const std::vector<std::string>& pass);
 
 /*!
diff --git a/nnvm/include/nnvm/pass_functions.h b/nnvm/include/nnvm/pass_functions.h
new file mode 100644
index 000000000000..e87a74fff27e
--- /dev/null
+++ b/nnvm/include/nnvm/pass_functions.h
@@ -0,0 +1,76 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file pass_functions.h
+ * \brief Pass functions that simply redirect the calls to ApplyPass
+ *
+ *  This file serves as documentation on how to use functions implemented in "src/pass".
+ *  It is totally optional to add these functions when you add a new pass, since
+ *  ApplyPass can be directly called.
+ */
+#ifndef NNVM_PASS_FUNCTIONS_H_
+#define NNVM_PASS_FUNCTIONS_H_
+
+#include <string>
+#include <memory>
+#include "./base.h"
+#include "./pass.h"
+#include "./graph_attr_types.h"
+
+namespace nnvm {
+namespace pass {
+
+/*!
+ * \brief Load a graph from JSON string, redirects to "LoadJSON" pass.
+ * \param json_str The json string.
+ * \return Loaded graph.
+ */
+inline Graph LoadJSON(const std::string& json_str) {
+  Graph ret;
+  ret.attrs["json"] = std::make_shared<any>(json_str);
+  return ApplyPass(ret, {"LoadJSON"});
+}
+
+/*!
+ * \brief Save a graph to json, redirects to "SaveJSON" pass.
+ * \param graph The to be saved.
+ * \return The json string.
+ */
+inline std::string SaveJSON(Graph graph) {
+  Graph ret = ApplyPass(std::move(graph), {"SaveJSON"});
+  return ret.GetAttr<std::string>("json");
+}
+
+/*!
+ * \brief Add control flow dependencies between nodes
+ *  To correctly order mutation and read to resolve
+ *  write after read problem and read after write problems.
+ * \param src source graph
+ * \return A graph that added control flow dependencies.
+ */
+inline Graph OrderMutation(Graph src) {
+  return ApplyPass(std::move(src), {"OrderMutation"});
+}
+
+/*!
+ * \brief Infer shapes in the graph given the information.
+ * \param graph source graph
+ * \param shape_args The shapes of aruguments to the graph.
+ * \param shape_attr_key The key to the node attribute that can indicate shape.
+ * \return A graph with new attribute "shape" containing inferred shape of each NodeEntry.
+ *  The index of ShapeVector is given by graph.indexed_graph().entry_id
+ */
+inline Graph InferShape(Graph graph,
+                        ShapeVector shape_args = {},
+                        std::string shape_attr_key = "") {
+  if (shape_args.size() != 0) {
+    graph.attrs["shape_args"] = std::make_shared<any>(std::move(shape_args));
+  }
+  if (shape_attr_key.length() != 0) {
+    graph.attrs["shape_attr_key"] = std::make_shared<any>(std::move(shape_attr_key));
+  }
+  return ApplyPass(std::move(graph), {"InferShape"});
+}
+
+}  // namespace pass
+}  // namespace nnvm
+#endif  // NNVM_PASS_FUNCTIONS_H_
diff --git a/nnvm/src/core/pass.cc b/nnvm/src/core/pass.cc
index f94acf50c8d3..f58c8039989b 100644
--- a/nnvm/src/core/pass.cc
+++ b/nnvm/src/core/pass.cc
@@ -22,7 +22,7 @@ const PassFunctionReg* FindPassDep(const std::string&attr_name) {
   return nullptr;
 }
 
-Graph ApplyPass(const Graph& src,
+Graph ApplyPass(Graph g,
                 const std::vector<std::string>& pass) {
   std::vector<const PassFunctionReg*> fpass;
   for (auto& name : pass) {
@@ -32,11 +32,9 @@ Graph ApplyPass(const Graph& src,
     fpass.push_back(reg);
   }
 
-  Graph g;
-  const Graph* s = &src;
   for (auto r : fpass) {
     for (auto& dep : r->graph_attr_dependency) {
-      if (s->attrs.count(dep) == 0) {
+      if (g.attrs.count(dep) == 0) {
         auto* pass_dep = FindPassDep(dep);
         std::string msg;
         if (pass_dep != nullptr) {
@@ -48,8 +46,7 @@ Graph ApplyPass(const Graph& src,
                    << msg;
       }
     }
-    g = r->body(*s);
-    s = &g;
+    g = r->body(std::move(g));
   }
   return g;
 }
diff --git a/nnvm/src/example/operator.cc b/nnvm/src/example/operator.cc
index 1b2fb1e10510..f286716f51c4 100644
--- a/nnvm/src/example/operator.cc
+++ b/nnvm/src/example/operator.cc
@@ -4,6 +4,7 @@
 #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>
 
@@ -30,6 +31,31 @@ inline bool SameShape(const NodeAttrs& attrs,
   return true;
 }
 
+// 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);
+    })
+.attr<FInferShape>(
+    "FInferShape", [] (const NodeAttrs& attrs,
+                       array_view<TShape*> ishape,
+                       array_view<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;
+    });
+
 NNVM_REGISTER_OP(add)
 .describe("add two data together")
 .set_num_inputs(2)
diff --git a/nnvm/src/pass/infer_shape.cc b/nnvm/src/pass/infer_shape.cc
index 5788cebfa89d..a2d9abf38291 100644
--- a/nnvm/src/pass/infer_shape.cc
+++ b/nnvm/src/pass/infer_shape.cc
@@ -10,19 +10,42 @@
 namespace nnvm {
 namespace pass {
 
-Graph InferShape(const Graph& src) {
-  Graph ret = src;
+Graph InferShape(Graph ret) {
   const IndexedGraph& idx = ret.indexed_graph();
   static auto& finfer_shape = Op::GetAttr<FInferShape>("FInferShape");
   // reshape shape vector
   ShapeVector rshape(idx.num_node_entries());
+
+  if (ret.attrs.count("shape_args") != 0) {
+    const ShapeVector& shape_args = ret.GetAttr<ShapeVector>("shape_args");
+    CHECK_LE(shape_args.size(), idx.arg_nodes().size())
+        << "shape args is more than number of arguments";
+    for (size_t i = 0; i < shape_args.size(); ++i) {
+      rshape[idx.entry_id(idx.arg_nodes()[i], 0)] = shape_args[i];
+    }
+  }
+  std::string shape_attr_key;
+  if (ret.attrs.count("shape_attr_key") != 0) {
+    shape_attr_key = ret.GetAttr<std::string>("shape_attr_key");
+  }
+
   // temp space for shape inference.
   std::vector<TShape*> ishape, oshape;
   // number of completed nodes
   size_t num_known = 0;
   for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
     const auto& inode = idx[nid];
-    if (inode.source->is_variable()) continue;
+    if (inode.source->is_variable()) {
+      if (shape_attr_key.length() != 0) {
+        auto it = inode.source->attrs.dict.find(shape_attr_key);
+        if (it != inode.source->attrs.dict.end()) {
+          CHECK_EQ(inode.source->num_outputs(), 1);
+          std::istringstream is(it->second);
+          CHECK(is >> rshape[idx.entry_id(nid, 0)]) << "Invalid shape attribute";
+        }
+      }
+      continue;
+    }
     ishape.resize(inode.inputs.size());
     for (uint32_t i = 0; i < ishape.size(); ++i) {
       ishape[i] = &rshape[idx.entry_id(inode.inputs[i])];
@@ -43,5 +66,13 @@ Graph InferShape(const Graph& src) {
   return ret;
 }
 
+NNVM_REGISTER_PASS(InferShape)
+.describe("Infer the shape of each node entries.")
+.set_body(InferShape)
+.set_change_graph(false)
+.provide_graph_attr("shape");
+
+DMLC_JSON_ENABLE_ANY(ShapeVector, list_shape);
+
 }  // namespace pass
 }  // namespace nnvm
diff --git a/nnvm/src/pass/order_mutation.cc b/nnvm/src/pass/order_mutation.cc
index a498660cfd1f..ce615fccaad4 100644
--- a/nnvm/src/pass/order_mutation.cc
+++ b/nnvm/src/pass/order_mutation.cc
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file saveload_json.cc
+ * \file order_mutation.cc
  * \brief Add control flow dependencies between nodes
  *  To correctly order mutation and read to resolve
  *  write after read problem and read after write problems.
diff --git a/nnvm/src/pass/saveload_json.cc b/nnvm/src/pass/saveload_json.cc
index 0fe1d1896db6..c2a22a46b611 100644
--- a/nnvm/src/pass/saveload_json.cc
+++ b/nnvm/src/pass/saveload_json.cc
@@ -149,7 +149,7 @@ struct JSONGraph {
 };
 
 // Load a graph from JSON file.
-Graph LoadJSON(const Graph& src) {
+Graph LoadJSON(Graph src) {
   CHECK_NE(src.attrs.count("json"), 0)
       << "Load JSON require json to be presented.";
   const std::string &json_str =
@@ -188,7 +188,7 @@ Graph LoadJSON(const Graph& src) {
 }
 
 // save a graph to json
-Graph SaveJSON(const Graph& src) {
+Graph SaveJSON(Graph src) {
   JSONGraph jgraph;
   std::unordered_map<Node*, uint32_t> node2index;
   jgraph.node_row_ptr.push_back(0);
diff --git a/nnvm/src/test_main.cc b/nnvm/src/test_main.cc
index 4d14abced5ab..139fde8f4f0d 100644
--- a/nnvm/src/test_main.cc
+++ b/nnvm/src/test_main.cc
@@ -4,6 +4,7 @@
 #include <nnvm/tuple.h>
 #include <nnvm/c_api.h>
 #include <nnvm/graph_attr_types.h>
+#include <nnvm/pass_functions.h>
 #include <dmlc/timer.h>
 #include <string>
 
diff --git a/nnvm/tests/python/test_graph.py b/nnvm/tests/python/test_graph.py
index 0af641932a5d..88859250956e 100644
--- a/nnvm/tests/python/test_graph.py
+++ b/nnvm/tests/python/test_graph.py
@@ -35,7 +35,23 @@ def test_order_mutation_pass():
     assert nindex['add1'] in jnodes[nindex['assign']]['control_deps']
     assert jnodes[nindex['assign']]['inputs'][0][2] == 1
 
+def test_infer_shape():
+    x = sym.Variable('x', shape=(4, 2))
+    y = sym.add(x, x, name='add1')
+    y = sym.reshape(y, target=(2, 4), name="reshape1")
+    g = graph.create(y)
+    g._set_json_attr("shape_attr_key", "shape")
+    g = g.apply('InferShape')
+    jgraph = json.loads(g.apply('SaveJSON').json_attr('json'))
+    jnodes = jgraph['nodes']
+    jnode_row_ptr = jgraph['node_row_ptr']
+    nindex = {n['name']: i for i, n in enumerate(jnodes)}
+    assert g.json_attr('shape')[jnode_row_ptr[nindex["reshape1"]]] == [2, 4]
+    assert g.json_attr('shape')[jnode_row_ptr[nindex["add1"]]] == [4, 2]
+
+
 if __name__ == "__main__":
     test_order_mutation_pass()
     test_graph_json_attr()
     test_json_pass()
+    test_infer_shape()

From e008ea83ad83c767636b9405740108e644c7a995 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 19 Jul 2016 21:44:25 -0700
Subject: [PATCH 346/949] [Pass] enable infer type (#17)

---
 nnvm/include/nnvm/graph_attr_types.h          | 15 +++++
 nnvm/include/nnvm/op_attr_types.h             | 28 ++++++---
 nnvm/include/nnvm/pass_functions.h            | 20 +++++++
 nnvm/src/example/operator.cc                  | 23 +++++++
 .../{infer_shape.cc => infer_shape_type.cc}   | 60 +++++++++++++++----
 nnvm/tests/python/test_graph.py               | 28 +++++++++
 6 files changed, 152 insertions(+), 22 deletions(-)
 rename nnvm/src/pass/{infer_shape.cc => infer_shape_type.cc} (54%)

diff --git a/nnvm/include/nnvm/graph_attr_types.h b/nnvm/include/nnvm/graph_attr_types.h
index 4af9fabaf62d..65d2638a79bb 100644
--- a/nnvm/include/nnvm/graph_attr_types.h
+++ b/nnvm/include/nnvm/graph_attr_types.h
@@ -39,6 +39,21 @@ using JSONString = std::string;
  */
 using ShapeVector = std::vector<TShape>;
 
+/*!
+ * \brief The result holder of type of each NodeEntry in the graph.
+ * \note Stored under graph.attrs["dtype"], provided by Pass "InferType"
+ *
+ * \code
+ *  Graph g = ApplyPass(src_graph, {"InferType"});
+ *  const DTypeVector& types = g.GetAttr<ShapeVector>("dtype");
+ *  // get shape by entry id
+ *  int entry_type = dtypes[g.indexed_graph().entry_id(my_entry)];
+ * \endcode
+ *
+ * \sa FInferType
+ */
+using DTypeVector = std::vector<int>;
+
 }  // namespace nnvm
 
 #endif  // NNVM_GRAPH_ATTR_TYPES_H_
diff --git a/nnvm/include/nnvm/op_attr_types.h b/nnvm/include/nnvm/op_attr_types.h
index 9049f39632e7..790767caa012 100644
--- a/nnvm/include/nnvm/op_attr_types.h
+++ b/nnvm/include/nnvm/op_attr_types.h
@@ -50,25 +50,35 @@ using FListOutputNames = std::function<std::vector<std::string> (const NodeAttrs
  */
 using FMutateInput = std::function<bool (const NodeAttrs& attrs, uint32_t index)>;
 
+/*!
+ * \brief Inference function of certain type.
+ * \tparam AttrType The type of the attribute to be infered.
+ * \return whether all attributes are inferred.
+ */
+template<typename AttrType>
+using FInferNodeEntryAttr = std::function<bool (const NodeAttrs& attrs,
+                                                array_view<AttrType*> in_attrs,
+                                                array_view<AttrType*> out_attrs)>;
 /*!
  * \brief Shape inference function.
  *  Update the shapes given the input shape information.
  *  TShape.ndim() == 0 means the shape is still unknown.
  *
- * \param attrs The attributes of the node.
- * \param in_shapes Array of shapes from the inputs.
- * \param out_shapes Array of shapes from the outputs.
- *
- * \return Whether all the shapes are known.
- *
  * \note Register under "FInferShape",
  *  by default do not update any shapes.
  *
  *  FInferShape is needed by shape inference
  */
-using FInferShape = std::function<bool (const NodeAttrs& attrs,
-                                        array_view<TShape*> in_shapes,
-                                        array_view<TShape*> out_shapes)>;
+using FInferShape = FInferNodeEntryAttr<TShape>;
+
+/*!
+ * \brief Type inference function.
+ *  Update the type given the known type information.
+ *
+ * \note Register under "FInferType",
+ *  by default set all the output types to 0.
+ */
+using FInferType = FInferNodeEntryAttr<int>;
 
 }  // namespace nnvm
 
diff --git a/nnvm/include/nnvm/pass_functions.h b/nnvm/include/nnvm/pass_functions.h
index e87a74fff27e..1cb25631e653 100644
--- a/nnvm/include/nnvm/pass_functions.h
+++ b/nnvm/include/nnvm/pass_functions.h
@@ -71,6 +71,26 @@ inline Graph InferShape(Graph graph,
   return ApplyPass(std::move(graph), {"InferShape"});
 }
 
+/*!
+ * \brief Infer types in the graph given the information.
+ * \param graph source graph
+ * \param shape_args The shapes of aruguments to the graph.
+ * \param shape_attr_key The key to the node attribute that can indicate shape.
+ * \return A graph with new attribute "shape" containing inferred shape of each NodeEntry.
+ *  The index of ShapeVector is given by graph.indexed_graph().entry_id
+ */
+inline Graph InferType(Graph graph,
+                       DTypeVector type_args = {},
+                       std::string type_attr_key = "") {
+  if (type_args.size() != 0) {
+    graph.attrs["type_args"] = std::make_shared<any>(std::move(type_args));
+  }
+  if (type_attr_key.length() != 0) {
+    graph.attrs["type_attr_key"] = std::make_shared<any>(std::move(type_attr_key));
+  }
+  return ApplyPass(std::move(graph), {"InferType"});
+}
+
 }  // namespace pass
 }  // namespace nnvm
 #endif  // NNVM_PASS_FUNCTIONS_H_
diff --git a/nnvm/src/example/operator.cc b/nnvm/src/example/operator.cc
index f286716f51c4..6ecb8da68d64 100644
--- a/nnvm/src/example/operator.cc
+++ b/nnvm/src/example/operator.cc
@@ -13,6 +13,7 @@ namespace myproject {
 using nnvm::FListInputNames;
 using nnvm::FMutateInput;
 using nnvm::FInferShape;
+using nnvm::FInferType;
 using nnvm::NodeAttrs;
 using nnvm::TShape;
 using nnvm::array_view;
@@ -56,6 +57,28 @@ NNVM_REGISTER_OP(reshape)
       return true;
     });
 
+
+NNVM_REGISTER_OP(cast)
+.describe("cast source type to target")
+.set_num_inputs(1)
+.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);
+    })
+.attr<FInferShape>("FInferShape", SameShape)
+.attr<FInferType>(
+    "FInferType", [](const NodeAttrs& attrs,
+                     array_view<int*> itype,
+                     array_view<int*> otype) {
+      *otype[0] = nnvm::get<int>(attrs.parsed);
+      return true;
+    });
+
+
 NNVM_REGISTER_OP(add)
 .describe("add two data together")
 .set_num_inputs(2)
diff --git a/nnvm/src/pass/infer_shape.cc b/nnvm/src/pass/infer_shape_type.cc
similarity index 54%
rename from nnvm/src/pass/infer_shape.cc
rename to nnvm/src/pass/infer_shape_type.cc
index a2d9abf38291..e0af82e628bb 100644
--- a/nnvm/src/pass/infer_shape.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -1,7 +1,7 @@
 /*!
  *  Copyright (c) 2016 by Contributors
  * \file infer_shape.cc
- * \brief Inference the shapes given
+ * \brief Inference the shapes given existin information.
  */
 #include <nnvm/pass.h>
 #include <nnvm/op_attr_types.h>
@@ -10,14 +10,23 @@
 namespace nnvm {
 namespace pass {
 
-Graph InferShape(Graph ret) {
+template<typename AttrType>
+Graph InferAttr(Graph &&ret,
+                const AttrType def_value,
+                const char* infer_name,
+                const char* arg_name,
+                const char* attr_key_name,
+                const char* attr_name,
+                const char* known_name) {
+  using AttrVector = std::vector<AttrType>;
   const IndexedGraph& idx = ret.indexed_graph();
-  static auto& finfer_shape = Op::GetAttr<FInferShape>("FInferShape");
+  static auto& finfer_shape =
+      Op::GetAttr<FInferNodeEntryAttr<AttrType> >(infer_name);
   // reshape shape vector
-  ShapeVector rshape(idx.num_node_entries());
+  AttrVector rshape(idx.num_node_entries(), def_value);
 
-  if (ret.attrs.count("shape_args") != 0) {
-    const ShapeVector& shape_args = ret.GetAttr<ShapeVector>("shape_args");
+  if (ret.attrs.count(arg_name) != 0) {
+    const AttrVector& shape_args = ret.GetAttr<AttrVector>(arg_name);
     CHECK_LE(shape_args.size(), idx.arg_nodes().size())
         << "shape args is more than number of arguments";
     for (size_t i = 0; i < shape_args.size(); ++i) {
@@ -25,12 +34,12 @@ Graph InferShape(Graph ret) {
     }
   }
   std::string shape_attr_key;
-  if (ret.attrs.count("shape_attr_key") != 0) {
-    shape_attr_key = ret.GetAttr<std::string>("shape_attr_key");
+  if (ret.attrs.count(attr_key_name) != 0) {
+    shape_attr_key = ret.GetAttr<std::string>(attr_key_name);
   }
 
   // temp space for shape inference.
-  std::vector<TShape*> ishape, oshape;
+  std::vector<AttrType*> ishape, oshape;
   // number of completed nodes
   size_t num_known = 0;
   for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
@@ -41,7 +50,7 @@ Graph InferShape(Graph ret) {
         if (it != inode.source->attrs.dict.end()) {
           CHECK_EQ(inode.source->num_outputs(), 1);
           std::istringstream is(it->second);
-          CHECK(is >> rshape[idx.entry_id(nid, 0)]) << "Invalid shape attribute";
+          CHECK(is >> rshape[idx.entry_id(nid, 0)]) << "Invalid attribute";
         }
       }
       continue;
@@ -60,19 +69,44 @@ Graph InferShape(Graph ret) {
     }
   }
   // set the shapes
-  ret.attrs["shape"] = std::make_shared<any>(std::move(rshape));
+  ret.attrs[attr_name] = std::make_shared<any>(std::move(rshape));
   // number of nodes who knows the shape.
-  ret.attrs["shape_num_known_nodes"] = std::make_shared<any>(num_known);
+  ret.attrs[known_name] = std::make_shared<any>(num_known);
   return ret;
 }
 
 NNVM_REGISTER_PASS(InferShape)
 .describe("Infer the shape of each node entries.")
-.set_body(InferShape)
+.set_body([](Graph ret) {
+    return InferAttr<TShape>(
+        std::move(ret),
+        TShape(),
+        "FInferShape",
+        "shape_args",
+        "shape_attr_key",
+        "shape",
+        "shape_num_known_nodes");
+  })
 .set_change_graph(false)
 .provide_graph_attr("shape");
 
+NNVM_REGISTER_PASS(InferType)
+.describe("Infer the dtype of each node entries.")
+.set_body([](Graph ret) {
+    return InferAttr<int>(
+        std::move(ret),
+        0,
+        "FInferType",
+        "dtype_args",
+        "dtype_attr_key",
+        "dtype",
+        "dtype_num_known_nodes");
+  })
+.set_change_graph(false)
+.provide_graph_attr("dtype");
+
 DMLC_JSON_ENABLE_ANY(ShapeVector, list_shape);
+DMLC_JSON_ENABLE_ANY(DTypeVector, list_int);
 
 }  // namespace pass
 }  // namespace nnvm
diff --git a/nnvm/tests/python/test_graph.py b/nnvm/tests/python/test_graph.py
index 88859250956e..b01e34280877 100644
--- a/nnvm/tests/python/test_graph.py
+++ b/nnvm/tests/python/test_graph.py
@@ -49,9 +49,37 @@ def test_infer_shape():
     assert g.json_attr('shape')[jnode_row_ptr[nindex["reshape1"]]] == [2, 4]
     assert g.json_attr('shape')[jnode_row_ptr[nindex["add1"]]] == [4, 2]
 
+def test_infer_shape():
+    x = sym.Variable('x', shape=(4, 2))
+    y = sym.add(x, x, name='add1')
+    y = sym.reshape(y, target=(2, 4), name="reshape1")
+    g = graph.create(y)
+    g._set_json_attr("shape_attr_key", "shape")
+    g = g.apply('InferShape')
+    jgraph = json.loads(g.apply('SaveJSON').json_attr('json'))
+    jnodes = jgraph['nodes']
+    jnode_row_ptr = jgraph['node_row_ptr']
+    nindex = {n['name']: i for i, n in enumerate(jnodes)}
+    assert g.json_attr('shape')[jnode_row_ptr[nindex["reshape1"]]] == [2, 4]
+    assert g.json_attr('shape')[jnode_row_ptr[nindex["add1"]]] == [4, 2]
+
+def test_infer_type():
+    x = sym.Variable('x')
+    y = sym.add(x, x, name='add1')
+    y = sym.cast(y, dtype=1, name="cast1")
+    g = graph.create(y)
+    g = g.apply('InferType')
+    jgraph = json.loads(g.apply('SaveJSON').json_attr('json'))
+    jnodes = jgraph['nodes']
+    jnode_row_ptr = jgraph['node_row_ptr']
+    nindex = {n['name']: i for i, n in enumerate(jnodes)}
+    assert g.json_attr('dtype')[jnode_row_ptr[nindex["cast1"]]] == 1
+    assert g.json_attr('dtype')[jnode_row_ptr[nindex["add1"]]] == 0
+
 
 if __name__ == "__main__":
     test_order_mutation_pass()
     test_graph_json_attr()
     test_json_pass()
     test_infer_shape()
+    test_infer_type()

From 8a396821a51b256cad64619e245670a2bed60861 Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Wed, 20 Jul 2016 11:10:07 -0700
Subject: [PATCH 347/949] [Pass] Enable BackwardOp

---
 nnvm/README.md                    |  6 ++---
 nnvm/include/nnvm/op_attr_types.h | 13 ++++++++++
 nnvm/include/nnvm/symbolic.h      |  2 +-
 nnvm/src/core/symbolic.cc         |  2 +-
 nnvm/src/pass/infer_shape_type.cc | 42 +++++++++++++++++++------------
 nnvm/src/test_main.cc             |  3 ++-
 6 files changed, 46 insertions(+), 22 deletions(-)

diff --git a/nnvm/README.md b/nnvm/README.md
index a20f40d95fb0..888e398e5f51 100644
--- a/nnvm/README.md
+++ b/nnvm/README.md
@@ -1,6 +1,6 @@
 # NNVM: Build deep learning system by parts
 
-NNVM is not a deep learning library. It is a modular, lightweight library to
+NNVM is not a deep learning library. It is a modular, decentralized and lightweight library to
 help build deep learning libraries efficiently.
 
 ## What is it
@@ -8,7 +8,7 @@ help build deep learning libraries efficiently.
 While most deep learning systems offer end to end solutions,
 it is interesting to ask if we can actually assemble a deep learning system by parts.
 The goal is to enable hackers can customize optimizations, target platforms and set of operators they care about.
-We believe that the modular system is an interesting direction.
+We believe that the decentralized modular system is an interesting direction.
 The hope is that effective parts can be assembled together just like you assemble your own desktops.
 So the customized deep learning solution can be minimax, minimum in terms of dependencies,
 while maxiziming the users' need.
@@ -18,7 +18,7 @@ computation graph optimization such as memory reduction, device allocation,
 operator fusion while being agnostic to the operator
 interface defintion and how operators are executed.
 NNVM is inspired by LLVM, aiming to be an intermediate representation library
-for neural nets and computation graphs in general.
+for neural nets and computation graphs generation and optimizations.
 
 ## Deep learning system by parts
 
diff --git a/nnvm/include/nnvm/op_attr_types.h b/nnvm/include/nnvm/op_attr_types.h
index 790767caa012..d920117f065a 100644
--- a/nnvm/include/nnvm/op_attr_types.h
+++ b/nnvm/include/nnvm/op_attr_types.h
@@ -80,6 +80,19 @@ using FInferShape = FInferNodeEntryAttr<TShape>;
  */
 using FInferType = FInferNodeEntryAttr<int>;
 
+/*!
+ * \brief Whether this op is an explicit backward operator
+ *
+ *  If TIsBackwardOp is set to be true:
+ *   - The first control_deps of the node points to the corresponding forward operator.
+ *   - The outputs operator corresponds to exactly inputs of forward op one by one.
+ *
+ * \note Register under "TIsBackwardOp", default to false.
+ *
+ * This enables easier shape/type inference for backward operators for slice and reduction.
+ */
+using TIsBackwardOp = bool;
+
 }  // namespace nnvm
 
 #endif  // NNVM_OP_ATTR_TYPES_H_
diff --git a/nnvm/include/nnvm/symbolic.h b/nnvm/include/nnvm/symbolic.h
index 2d5867a11892..c14534f3c524 100644
--- a/nnvm/include/nnvm/symbolic.h
+++ b/nnvm/include/nnvm/symbolic.h
@@ -135,7 +135,7 @@ class Symbol {
    * \return Symbol that can be used to call compose further.
    */
   static Symbol CreateFunctor(const Op* op,
-                              std::unordered_map<std::string, std::string>&& attrs);
+                              std::unordered_map<std::string, std::string> attrs);
   /*!
    * \brief create variable symbol node
    * \param name name of the variable
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index dcfcc401e4b8..8c90ccfd6bf7 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -437,7 +437,7 @@ std::unordered_map<std::string, std::string> Symbol::ListAttrs(ListAttrOption op
 }
 
 Symbol Symbol::CreateFunctor(const Op* op,
-                             std::unordered_map<std::string, std::string>&& attrs) {
+                             std::unordered_map<std::string, std::string> attrs) {
   Symbol s;
   NodePtr n = Node::Create();
   n->op = op;
diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index e0af82e628bb..2e0ee7a1cd9e 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -10,18 +10,21 @@
 namespace nnvm {
 namespace pass {
 
-template<typename AttrType>
+template<typename AttrType, typename IsNone>
 Graph InferAttr(Graph &&ret,
                 const AttrType def_value,
                 const char* infer_name,
                 const char* arg_name,
                 const char* attr_key_name,
                 const char* attr_name,
-                const char* known_name) {
+                const char* known_name,
+                IsNone fis_none) {
   using AttrVector = std::vector<AttrType>;
   const IndexedGraph& idx = ret.indexed_graph();
   static auto& finfer_shape =
       Op::GetAttr<FInferNodeEntryAttr<AttrType> >(infer_name);
+  static auto& is_backward =
+      Op::GetAttr<TIsBackwardOp>("TIsBackwardOp");
   // reshape shape vector
   AttrVector rshape(idx.num_node_entries(), def_value);
 
@@ -66,6 +69,19 @@ Graph InferAttr(Graph &&ret,
     if (finfer_shape.count(inode.source->op)) {
       num_known +=
           finfer_shape[inode.source->op](inode.source->attrs, ishape, oshape);
+    } else if (is_backward.get(inode.source->op, false)) {
+      // backward operator inference.
+      CHECK_GE(inode.control_deps.size(), 1)
+          << "BackwardOp need to have control_deps to its forward op";
+      const auto& fnode = idx[inode.control_deps[0]];
+      CHECK_EQ(fnode.inputs.size(), inode.source->num_outputs())
+          << "BackwardOp need to correspond to the forward node";
+      bool known = true;
+      for (size_t i = 0; i < fnode.inputs.size(); ++i) {
+        *oshape[i] = rshape[idx.entry_id(fnode.inputs[i])];
+        if (fis_none(*oshape[i])) known = false;
+      }
+      num_known += known;
     }
   }
   // set the shapes
@@ -79,13 +95,10 @@ NNVM_REGISTER_PASS(InferShape)
 .describe("Infer the shape of each node entries.")
 .set_body([](Graph ret) {
     return InferAttr<TShape>(
-        std::move(ret),
-        TShape(),
-        "FInferShape",
-        "shape_args",
-        "shape_attr_key",
-        "shape",
-        "shape_num_known_nodes");
+        std::move(ret), TShape(),
+        "FInferShape", "shape_args", "shape_attr_key",
+        "shape", "shape_num_known_nodes",
+        [](const TShape& s) { return s.ndim() == 0; });
   })
 .set_change_graph(false)
 .provide_graph_attr("shape");
@@ -94,13 +107,10 @@ NNVM_REGISTER_PASS(InferType)
 .describe("Infer the dtype of each node entries.")
 .set_body([](Graph ret) {
     return InferAttr<int>(
-        std::move(ret),
-        0,
-        "FInferType",
-        "dtype_args",
-        "dtype_attr_key",
-        "dtype",
-        "dtype_num_known_nodes");
+        std::move(ret), 0,
+        "FInferType", "dtype_args", "dtype_attr_key",
+        "dtype", "dtype_num_known_nodes",
+        [](const int t) { return t == -1; });
   })
 .set_change_graph(false)
 .provide_graph_attr("dtype");
diff --git a/nnvm/src/test_main.cc b/nnvm/src/test_main.cc
index 139fde8f4f0d..2d82cfbe9c84 100644
--- a/nnvm/src/test_main.cc
+++ b/nnvm/src/test_main.cc
@@ -14,12 +14,13 @@ void test_speed() {
   size_t rep = 1000;
   size_t n = 1000;
   std::unordered_map<std::string, const nnvm::Symbol*> tmp;
+  std::unordered_map<std::string, std::string> kwargs;
   std::vector<const nnvm::Symbol*> vec{2};
   std::string name = "xx";
   for (size_t t = 0; t < rep; ++t) {
     nnvm::Symbol s = nnvm::Symbol::CreateVariable("x");
     for (size_t i = 0; i < n; ++i) {
-      nnvm::Symbol nw = nnvm::Symbol::CreateFunctor(add, {});
+      nnvm::Symbol nw = nnvm::Symbol::CreateFunctor(add, kwargs);
       vec[0] = &s;
       vec[1] =&s;
       tmp.clear();

From 0413c40c606612f432cba21131c1923b886cfa84 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 21 Jul 2016 18:47:04 -0700
Subject: [PATCH 348/949] [PASS] Add place device (#18)

---
 nnvm/Makefile                        |   2 +-
 nnvm/README.md                       |   7 ++
 nnvm/include/nnvm/graph.h            |  10 +-
 nnvm/include/nnvm/graph_attr_types.h |  24 +++-
 nnvm/include/nnvm/pass_functions.h   |  18 +++
 nnvm/src/core/graph.cc               |   2 +-
 nnvm/src/example/operator.cc         |   5 +
 nnvm/src/pass/infer_shape_type.cc    |   4 +
 nnvm/src/pass/place_device.cc        | 180 +++++++++++++++++++++++++++
 nnvm/src/pass/saveload_json.cc       |   8 +-
 nnvm/tests/python/test_graph.py      |  20 +++
 11 files changed, 267 insertions(+), 13 deletions(-)
 create mode 100644 nnvm/src/pass/place_device.cc

diff --git a/nnvm/Makefile b/nnvm/Makefile
index d24e4a8e06e2..86edfaea378e 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -1,5 +1,5 @@
 export LDFLAGS = -pthread -lm
-export CFLAGS =  -std=c++11 -Wall -O3 -msse2  -Wno-unknown-pragmas -funroll-loops\
+export CFLAGS =  -std=c++11 -Wall -O2 -msse2  -Wno-unknown-pragmas -funroll-loops\
 	 -Iinclude -Idmlc-core/include -fPIC
 
 # specify tensor path
diff --git a/nnvm/README.md b/nnvm/README.md
index 888e398e5f51..7acbf88093a8 100644
--- a/nnvm/README.md
+++ b/nnvm/README.md
@@ -20,6 +20,13 @@ interface defintion and how operators are executed.
 NNVM is inspired by LLVM, aiming to be an intermediate representation library
 for neural nets and computation graphs generation and optimizations.
 
+## Why build deep learning system by parts
+
+- Essential parts can be assembled in minimum way for embedding systems.
+- Hackers can hack the parts they need and compose with other well defined parts.
+- Decentralized modules enable new extensions creators to own their project
+  without creating a monothilic version.
+
 ## Deep learning system by parts
 
 This is one way to divide the deep learning system into common parts.
diff --git a/nnvm/include/nnvm/graph.h b/nnvm/include/nnvm/graph.h
index b09d9c61ab2e..9f12a1b35086 100644
--- a/nnvm/include/nnvm/graph.h
+++ b/nnvm/include/nnvm/graph.h
@@ -71,14 +71,8 @@ class IndexedGraph {
     uint32_t node_id;
     /*! \brief index of output from the source. */
     uint32_t index;
-    /*!
-     * \brief compare equality
-     * \param other the other entry to compare
-     * \return whether two entries equals to each other
-     */
-    inline bool operator==(const NodeEntry& other) const {
-      return node_id == other.node_id && index == other.index;
-    }
+    /*! \brief version of the node */
+    uint32_t version;
   };
   /*! \brief Node data structure in IndexedGraph */
   struct Node {
diff --git a/nnvm/include/nnvm/graph_attr_types.h b/nnvm/include/nnvm/graph_attr_types.h
index 65d2638a79bb..777a8054252a 100644
--- a/nnvm/include/nnvm/graph_attr_types.h
+++ b/nnvm/include/nnvm/graph_attr_types.h
@@ -45,7 +45,7 @@ using ShapeVector = std::vector<TShape>;
  *
  * \code
  *  Graph g = ApplyPass(src_graph, {"InferType"});
- *  const DTypeVector& types = g.GetAttr<ShapeVector>("dtype");
+ *  const DTypeVector& types = g.GetAttr<DTypeVector>("dtype");
  *  // get shape by entry id
  *  int entry_type = dtypes[g.indexed_graph().entry_id(my_entry)];
  * \endcode
@@ -54,6 +54,28 @@ using ShapeVector = std::vector<TShape>;
  */
 using DTypeVector = std::vector<int>;
 
+/*!
+ * \brief The result holder of device of each operator in the graph.
+ * \note Stored under graph.attrs["device"], provided by Pass "PlaceDevice"
+ *
+ * \code
+ *  Graph g = ApplyPass(src_graph, {"PlaceDevice"});
+ *  const &device = g.GetAttr<DeviceVector>("dtype");
+ *  // get device by node_id
+ *  int device_type = device[g.indexed_graph().node_id(my_node)];
+ * \endcode
+ */
+using DeviceVector = std::vector<int>;
+
+/*!
+ * \brief The result holder of device of each operator in the graph.
+ *
+ * \note Stored under graph.attrs["device_assign_map"], needed by Pass "PlaceDevice"
+ * -1 means unknown device
+ */
+using DeviceAssignMap = std::unordered_map<std::string, int>;
+
+
 }  // namespace nnvm
 
 #endif  // NNVM_GRAPH_ATTR_TYPES_H_
diff --git a/nnvm/include/nnvm/pass_functions.h b/nnvm/include/nnvm/pass_functions.h
index 1cb25631e653..d622ce47dd55 100644
--- a/nnvm/include/nnvm/pass_functions.h
+++ b/nnvm/include/nnvm/pass_functions.h
@@ -91,6 +91,24 @@ inline Graph InferType(Graph graph,
   return ApplyPass(std::move(graph), {"InferType"});
 }
 
+/*!
+ * \brief Place the devices
+ * \param graph source graph
+ * \param device_group_attr_key The attribute name for hinting the device group.
+ * \param device_assign_map The assignment map of device
+ * \param device_copy_op The name of copy op to be inserted when cross device copy happened.
+ * \return A graph with new attribute "device", cotaining device information of each node.
+ */
+inline Graph PlaceDevice(Graph graph,
+                         std::string device_group_attr_key,
+                         DeviceAssignMap device_assign_map,
+                         std::string device_copy_op) {
+  graph.attrs["device_group_attr_key"] = std::make_shared<any>(std::move(device_group_attr_key));
+  graph.attrs["device_assign_map"] = std::make_shared<any>(std::move(device_assign_map));
+  graph.attrs["device_copy_op"] = std::make_shared<any>(std::move(device_copy_op));
+  return ApplyPass(std::move(graph), {"PlaceDevice"});
+}
+
 }  // namespace pass
 }  // namespace nnvm
 #endif  // NNVM_PASS_FUNCTIONS_H_
diff --git a/nnvm/src/core/graph.cc b/nnvm/src/core/graph.cc
index 601f84d0a576..c8377671ec34 100644
--- a/nnvm/src/core/graph.cc
+++ b/nnvm/src/core/graph.cc
@@ -40,7 +40,7 @@ IndexedGraph::IndexedGraph(const Graph &g) {
       for (const auto& e : n->inputs) {
         auto it = node2index_.find(e.node.get());
         CHECK(it != node2index_.end() && it->first == e.node.get());
-        input_entries_.emplace_back(NodeEntry{it->second, e.index});
+        input_entries_.emplace_back(NodeEntry{it->second, e.index, e.version});
       }
       inputs_rptr.push_back(input_entries_.size());
       // control deps
diff --git a/nnvm/src/example/operator.cc b/nnvm/src/example/operator.cc
index 6ecb8da68d64..2c15bd2ba701 100644
--- a/nnvm/src/example/operator.cc
+++ b/nnvm/src/example/operator.cc
@@ -94,6 +94,11 @@ NNVM_REGISTER_OP(exp)
 .attr("inplace_pair", std::make_pair(0, 0))
 .attr<FInferShape>("FInferShape", SameShape);
 
+NNVM_REGISTER_OP(cross_device_copy)
+.describe("Copy data across device.")
+.set_num_inputs(1)
+.attr<FInferShape>("FInferShape", SameShape);
+
 
 NNVM_REGISTER_OP(conv2d)
 .describe("take conv of input")
diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index 2e0ee7a1cd9e..662757d403e1 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -35,10 +35,14 @@ Graph InferAttr(Graph &&ret,
     for (size_t i = 0; i < shape_args.size(); ++i) {
       rshape[idx.entry_id(idx.arg_nodes()[i], 0)] = shape_args[i];
     }
+    // erase the provided arguments
+    ret.attrs.erase(arg_name);
   }
   std::string shape_attr_key;
   if (ret.attrs.count(attr_key_name) != 0) {
     shape_attr_key = ret.GetAttr<std::string>(attr_key_name);
+    // erase the provided arguments
+    ret.attrs.erase(attr_key_name);
   }
 
   // temp space for shape inference.
diff --git a/nnvm/src/pass/place_device.cc b/nnvm/src/pass/place_device.cc
new file mode 100644
index 000000000000..787f47b80ca7
--- /dev/null
+++ b/nnvm/src/pass/place_device.cc
@@ -0,0 +1,180 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file place_device.cc
+ * \brief Inference the device of each operator given known information.
+ *  Insert a copy node automatically when there is a cross device.
+ */
+#include <nnvm/pass.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/graph_attr_types.h>
+
+namespace nnvm {
+namespace pass {
+
+// simply logic to place device according to device_group hint
+// insert copy node when there is
+Graph PlaceDevice(Graph src) {
+  CHECK_NE(src.attrs.count("device_group_attr_key"), 0)
+      << "Need graph attribute \"device_group_attr_key\" in PlaceDevice";
+  CHECK_NE(src.attrs.count("device_assign_map"), 0)
+      << "Need graph attribute \"device_assign_map\" in PlaceDevice";
+  CHECK_NE(src.attrs.count("device_copy_op"), 0)
+      << "Need graph attribute \"device_copy_op\" in PlaceDevice";
+
+  std::string device_group_attr_key = src.GetAttr<std::string>("device_group_attr_key");
+  const Op* copy_op = Op::Get(src.GetAttr<std::string>("device_copy_op"));
+  auto& device_assign_map = src.GetAttr<DeviceAssignMap>("device_assign_map");
+  const IndexedGraph& idx = src.indexed_graph();
+
+  DeviceVector device(idx.num_nodes(), -1);
+  // forward pass
+  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+    const auto& inode = idx[nid];
+    auto it = inode.source->attrs.dict.find(device_group_attr_key);
+    if (it != inode.source->attrs.dict.end()) {
+      const std::string& device_group = it->second;
+      auto dit = device_assign_map.find(device_group);
+      CHECK_NE(dit, device_assign_map.end())
+          << "The device assignment not found for group " << device_group;
+      device[nid] = dit->second;
+    } else {
+      for (const IndexedGraph::NodeEntry& e : inode.inputs) {
+        if (device[e.node_id] != -1) {
+          device[nid] = device[e.node_id]; break;
+        }
+      }
+    }
+  }
+  // backward pass
+  for (uint32_t i = idx.num_nodes(); i != 0; --i) {
+    uint32_t nid = i - 1;
+    const auto& inode = idx[nid];
+    if (device[nid] == -1) continue;
+    for (const IndexedGraph::NodeEntry& e : inode.inputs) {
+      if (device[e.node_id] == -1) device[e.node_id] = device[nid];
+    }
+  }
+
+  int num_dev = 1, other_dev_id = -1;
+  for (int& dev : device) {
+    if (dev == -1) dev = 0;
+    if (dev != other_dev_id) {
+      if (other_dev_id != -1) ++num_dev;
+      other_dev_id = dev;
+    }
+  }
+
+  if (num_dev == 1) {
+    src.attrs.erase("device_group_attr_key");
+    src.attrs.erase("device_assign_map");
+    src.attrs.erase("device_copy_op");
+    src.attrs["device"] = std::make_shared<any>(std::move(device));
+    return src;
+  }
+
+  std::map<std::tuple<uint32_t, uint32_t, int>, NodePtr> copy_map;
+  std::vector<NodePtr> new_node_map(idx.num_nodes(), nullptr);
+  std::unordered_map<const Node*, int> new_device_map;
+
+  // insert copy node
+  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+    int dev_id = device[nid];
+    const auto& inode = idx[nid];
+    // check if mutation is needed
+    bool need_mutate = false;
+    for (const IndexedGraph::NodeEntry& e : inode.inputs) {
+      if (new_node_map[e.node_id] != nullptr || dev_id != device[e.node_id]) {
+        need_mutate = true; break;
+      }
+    }
+    if (!need_mutate) {
+      for (const uint32_t cid : inode.control_deps) {
+        if (new_node_map[cid] != nullptr)  {
+          need_mutate = true; break;
+        }
+      }
+    }
+    if (need_mutate) {
+      NodePtr new_node = Node::Create();
+      new_node->attrs = inode.source->attrs;
+      new_node->inputs.reserve(inode.inputs.size());
+      for (size_t i = 0; i < inode.inputs.size(); ++i) {
+        const IndexedGraph::NodeEntry& e = inode.inputs[i];
+        if (dev_id != device[e.node_id]) {
+          auto copy_key = std::make_tuple(e.node_id, e.index, dev_id);
+          auto it = copy_map.find(copy_key);
+          if (it != copy_map.end() && it->first == copy_key) {
+            new_node->inputs.emplace_back(
+                NodeEntry{it->second, 0, 0});
+          } else {
+            NodePtr copy_node = Node::Create();
+            copy_node->op = copy_op;
+            std::ostringstream os;
+            os << inode.source->inputs[i].node->attrs.name << "_" << e.index <<"_copy";
+            copy_node->attrs.name = os.str();
+            copy_node->inputs.push_back(inode.source->inputs[i]);
+            copy_map[copy_key] = copy_node;
+            new_device_map[copy_node.get()] = dev_id;
+            new_node->inputs.emplace_back(
+                NodeEntry{std::move(copy_node), 0, 0});
+          }
+        } else {
+          if (new_node_map[e.node_id] != nullptr) {
+            new_node->inputs.emplace_back(
+                NodeEntry{new_node_map[e.node_id], e.index, 0});
+        } else {
+            new_node->inputs.push_back(inode.source->inputs[i]);
+          }
+        }
+      }
+      new_node->control_deps.reserve(inode.control_deps.size());
+      for (size_t i = 0; i < inode.control_deps.size(); ++i) {
+        uint32_t cid = inode.control_deps[i];
+        if (new_node_map[cid] != nullptr) {
+          new_node->control_deps.push_back(new_node_map[cid]);
+        } else {
+          new_node->control_deps.push_back(inode.source->control_deps[i]);
+        }
+      }
+      new_device_map[new_node.get()] = dev_id;
+      new_node_map[nid] = std::move(new_node);
+    } else {
+      new_device_map[inode.source] = dev_id;
+    }
+  }
+
+  // make the new graph
+  Graph ret;
+  for (const NodeEntry& e : src.outputs) {
+    if (new_node_map[idx.node_id(e.node.get())] != nullptr) {
+      ret.outputs.emplace_back(
+          NodeEntry{new_node_map[idx.node_id(e.node.get())], e.index, e.version});
+    } else {
+      ret.outputs.emplace_back(e);
+    }
+  }
+  DeviceVector new_device_vec(ret.indexed_graph().num_nodes());
+  for (uint32_t nid = 0; nid < ret.indexed_graph().num_nodes(); ++nid) {
+    if (new_device_map.count(ret.indexed_graph()[nid].source) == 0) {
+      LOG(INFO) << "canot find " << ret.indexed_graph()[nid].source->attrs.name;
+    }
+    new_device_vec[nid] = new_device_map.at(ret.indexed_graph()[nid].source);
+  }
+  ret.attrs["device"] = std::make_shared<any>(std::move(new_device_vec));
+  return ret;
+}
+
+NNVM_REGISTER_PASS(PlaceDevice)
+.describe("Infer the device type of each operator."\
+          "Insert a copy node when there is cross device copy")
+.set_body(PlaceDevice)
+.set_change_graph(true)
+.provide_graph_attr("device")
+.depend_graph_attr("device_group_attr_key")
+.depend_graph_attr("device_assign_map")
+.depend_graph_attr("device_copy_op");
+
+DMLC_JSON_ENABLE_ANY(DeviceAssignMap, dict_str_int);
+
+}  // namespace pass
+}  // namespace nnvm
diff --git a/nnvm/src/pass/saveload_json.cc b/nnvm/src/pass/saveload_json.cc
index c2a22a46b611..4a3c97e25b77 100644
--- a/nnvm/src/pass/saveload_json.cc
+++ b/nnvm/src/pass/saveload_json.cc
@@ -68,14 +68,18 @@ struct JSONNode {
     writer->BeginObject();
     if (node->op != nullptr) {
       writer->WriteObjectKeyValue("op", node->op->name);
-      writer->WriteObjectKeyValue("attr", node->attrs.dict);
     } else {
       std::string json_null = "null";
       writer->WriteObjectKeyValue("op", json_null);
     }
     writer->WriteObjectKeyValue("name", node->attrs.name);
+    if (node->attrs.dict.size() != 0) {
+      writer->WriteObjectKeyValue("attr", node->attrs.dict);
+    }
     writer->WriteObjectKeyValue("inputs", inputs);
-    writer->WriteObjectKeyValue("control_deps", control_deps);
+    if (control_deps.size() != 0) {
+      writer->WriteObjectKeyValue("control_deps", control_deps);
+    }
     writer->EndObject();
   }
 
diff --git a/nnvm/tests/python/test_graph.py b/nnvm/tests/python/test_graph.py
index b01e34280877..6209ca9386d6 100644
--- a/nnvm/tests/python/test_graph.py
+++ b/nnvm/tests/python/test_graph.py
@@ -76,6 +76,25 @@ def test_infer_type():
     assert g.json_attr('dtype')[jnode_row_ptr[nindex["cast1"]]] == 1
     assert g.json_attr('dtype')[jnode_row_ptr[nindex["add1"]]] == 0
 
+def test_place_device():
+    x = sym.Variable('x', device_group="stage1")
+    y = sym.add(x, x, name='add1')
+    y = sym.cast(y, dtype=1, name="cast1")
+    z = sym.add(y, y, device_group="stage2", name="add2")
+    z = sym.add(z, sym.exp(y, device_group="stage2"),  name="add3")
+    g = graph.create(z)
+    g._set_json_attr("device_group_attr_key", "device_group")
+    g._set_json_attr("device_assign_map", {"stage1": 0, "stage2" : 1}, "dict_str_int")
+    g._set_json_attr("device_copy_op", "cross_device_copy")
+    g = g.apply("PlaceDevice")
+    jgraph = json.loads(g.apply('SaveJSON').json_attr('json'))
+    jnodes = jgraph['nodes']
+    jnode_row_ptr = jgraph['node_row_ptr']
+    nindex = {n['name']: i for i, n in enumerate(jnodes)}
+    assert g.json_attr('device')[jnode_row_ptr[nindex["add2"]]] == 1
+    assert g.json_attr('device')[jnode_row_ptr[nindex["add3"]]] == 1
+    assert g.json_attr('device')[jnode_row_ptr[nindex["cast1"]]] == 0
+
 
 if __name__ == "__main__":
     test_order_mutation_pass()
@@ -83,3 +102,4 @@ def test_infer_type():
     test_json_pass()
     test_infer_shape()
     test_infer_type()
+    test_place_device()

From d93fbbf759f2d27192d1b5bf7afa38f31c1a957a Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 29 Jul 2016 21:31:31 -0700
Subject: [PATCH 349/949] [PASS] add plan memory (#19)

---
 nnvm/include/nnvm/graph.h            |   6 +
 nnvm/include/nnvm/graph_attr_types.h |  17 +-
 nnvm/include/nnvm/op_attr_types.h    |  15 ++
 nnvm/src/core/graph.cc               |   5 +
 nnvm/src/example/operator.cc         |  11 +-
 nnvm/src/pass/graph_algorithm.h      | 112 +++++++++++++
 nnvm/src/pass/infer_shape_type.cc    |   1 +
 nnvm/src/pass/plan_memory.cc         | 227 +++++++++++++++++++++++++++
 nnvm/tests/python/test_graph.py      |  20 +++
 9 files changed, 411 insertions(+), 3 deletions(-)
 create mode 100644 nnvm/src/pass/graph_algorithm.h
 create mode 100644 nnvm/src/pass/plan_memory.cc

diff --git a/nnvm/include/nnvm/graph.h b/nnvm/include/nnvm/graph.h
index 9f12a1b35086..f3ca0ae7eae9 100644
--- a/nnvm/include/nnvm/graph.h
+++ b/nnvm/include/nnvm/graph.h
@@ -147,6 +147,10 @@ class IndexedGraph {
   inline const std::vector<uint32_t>& arg_nodes() const {
     return arg_nodes_;
   }
+  /*! \return list of output entries */
+  inline const std::vector<NodeEntry>& outputs() const {
+    return outputs_;
+  }
 
  private:
   friend class Graph;
@@ -159,6 +163,8 @@ class IndexedGraph {
   std::vector<Node> nodes_;
   // index to argument nodes
   std::vector<uint32_t> arg_nodes_;
+  // space to store the outputs entries
+  std::vector<NodeEntry> outputs_;
   // mapping from node to index.
   std::unordered_map<const nnvm::Node*, uint32_t> node2index_;
   // CSR pointer of node entries
diff --git a/nnvm/include/nnvm/graph_attr_types.h b/nnvm/include/nnvm/graph_attr_types.h
index 777a8054252a..6ac12e72d0fa 100644
--- a/nnvm/include/nnvm/graph_attr_types.h
+++ b/nnvm/include/nnvm/graph_attr_types.h
@@ -60,7 +60,7 @@ using DTypeVector = std::vector<int>;
  *
  * \code
  *  Graph g = ApplyPass(src_graph, {"PlaceDevice"});
- *  const &device = g.GetAttr<DeviceVector>("dtype");
+ *  const &device = g.GetAttr<DeviceVector>("device");
  *  // get device by node_id
  *  int device_type = device[g.indexed_graph().node_id(my_node)];
  * \endcode
@@ -75,6 +75,21 @@ using DeviceVector = std::vector<int>;
  */
 using DeviceAssignMap = std::unordered_map<std::string, int>;
 
+/*!
+ * \brief The result holder of storage id of each NodeEntry in the graph.
+ *
+ * \note Stored under graph.attrs["storage"], provided by Pass "PlanMemory"
+ *  Storage id is a continuous integer.
+ *  If the storage id is -1 then the storage is not assigned.
+ *
+ * \code
+ *  Graph g = ApplyPass(src_graph, {"PlanMemory"});
+ *  const &storage = g.GetAttr<StorageVector>("storage");
+ *  // get storage id by entry
+ *  int storage_id = storage[g.indexed_graph().entry_id(my_entry)];
+ * \endcode
+ */
+using StorageVector = std::vector<int>;
 
 }  // namespace nnvm
 
diff --git a/nnvm/include/nnvm/op_attr_types.h b/nnvm/include/nnvm/op_attr_types.h
index d920117f065a..08b951a838d4 100644
--- a/nnvm/include/nnvm/op_attr_types.h
+++ b/nnvm/include/nnvm/op_attr_types.h
@@ -8,6 +8,7 @@
 
 #include <vector>
 #include <string>
+#include <utility>
 #include <functional>
 #include "./base.h"
 #include "./tuple.h"
@@ -93,6 +94,20 @@ using FInferType = FInferNodeEntryAttr<int>;
  */
 using TIsBackwardOp = bool;
 
+/*!
+ * \brief Get possible inplace options.
+ *  This function enables optimization to reuse memory of inputs in output.
+ * \param attrs The attributes of the node
+ * \param in_data The input data.
+ * \param out_data The output data.
+ * \return list of pair of that maps input->output,
+ *   indicating possible in place operations.
+ *
+ * \note Register under "FInplaceOption", by default no inplace can happen.
+ */
+using FInplaceOption = std::function<
+  std::vector<std::pair<int, int> > (const NodeAttrs& attrs)>;
+
 }  // namespace nnvm
 
 #endif  // NNVM_OP_ATTR_TYPES_H_
diff --git a/nnvm/src/core/graph.cc b/nnvm/src/core/graph.cc
index c8377671ec34..0f5cd392de05 100644
--- a/nnvm/src/core/graph.cc
+++ b/nnvm/src/core/graph.cc
@@ -52,6 +52,11 @@ IndexedGraph::IndexedGraph(const Graph &g) {
       control_rptr.push_back(control_deps_.size());
   });
 
+  for (const auto& e : g.outputs) {
+    outputs_.emplace_back(NodeEntry{
+        node2index_.at(e.node.get()), e.index, e.version});
+  }
+
   // setup array view
   // input_entries_ and control_rptr must not change after this step.
   const NodeEntry* iptr = dmlc::BeginPtr(input_entries_);
diff --git a/nnvm/src/example/operator.cc b/nnvm/src/example/operator.cc
index 2c15bd2ba701..3a80fefc88be 100644
--- a/nnvm/src/example/operator.cc
+++ b/nnvm/src/example/operator.cc
@@ -14,6 +14,7 @@ using nnvm::FListInputNames;
 using nnvm::FMutateInput;
 using nnvm::FInferShape;
 using nnvm::FInferType;
+using nnvm::FInplaceOption;
 using nnvm::NodeAttrs;
 using nnvm::TShape;
 using nnvm::array_view;
@@ -32,6 +33,10 @@ inline bool SameShape(const NodeAttrs& attrs,
   return true;
 }
 
+inline std::vector<std::pair<int, int> > InplaceIn0Out0(const NodeAttrs& attrs) {
+  return {{0, 0}};
+}
+
 // simple demonstration of reshape.
 NNVM_REGISTER_OP(reshape)
 .describe("reshape source to target shape")
@@ -55,7 +60,8 @@ NNVM_REGISTER_OP(reshape)
       CHECK_EQ(ishape[0]->Size(), target.Size())
           << "Reshape op: source target shape mismatch";
       return true;
-    });
+    })
+.attr<FInplaceOption>("FInplaceOption", InplaceIn0Out0);
 
 
 NNVM_REGISTER_OP(cast)
@@ -82,7 +88,8 @@ NNVM_REGISTER_OP(cast)
 NNVM_REGISTER_OP(add)
 .describe("add two data together")
 .set_num_inputs(2)
-.attr<FInferShape>("FInferShape", SameShape);
+.attr<FInferShape>("FInferShape", SameShape)
+.attr<FInplaceOption>("FInplaceOption", InplaceIn0Out0);
 
 NNVM_REGISTER_OP(__add_symbol__)
 .describe("Alias of add")
diff --git a/nnvm/src/pass/graph_algorithm.h b/nnvm/src/pass/graph_algorithm.h
new file mode 100644
index 000000000000..331b7ad6fe53
--- /dev/null
+++ b/nnvm/src/pass/graph_algorithm.h
@@ -0,0 +1,112 @@
+/*!
+ * Copyright (c) 2016 by Contributors
+ * \file graph_algorithm.h
+ * \brief This header contains graph algorithms on StaticGraph.
+ *  It is used  compute informations such as whether two
+ *  operations can run in parallel, and helps allocation.
+*/
+#ifndef NNVM_PASS_GRAPH_ALGORITHM_H_
+#define NNVM_PASS_GRAPH_ALGORITHM_H_
+
+#include <nnvm/graph.h>
+#include <vector>
+
+namespace nnvm {
+namespace pass {
+
+/*!
+ * \brief Find best path in the DAG, with reward defined
+ *  by sum of reward of each node along the path.
+ * \param graph the original static graph.
+ * \param topo_order topo order of the nodes in the graph.
+ * \param node_reward the reward of each node.
+ * \param path the output path of nodes.
+ * \return the total reward of best path.
+ */
+inline uint32_t FindBestPath(
+    const IndexedGraph& graph,
+    const std::vector<uint32_t>& node_reward,
+    std::vector<uint32_t>* path) {
+  const uint32_t num_nodes = static_cast<uint32_t>(graph.num_nodes());
+  CHECK_EQ(num_nodes, node_reward.size());
+
+  std::vector<uint32_t> best_reward(node_reward.size(), 0);
+  std::vector<uint32_t> next_node(node_reward.size(), num_nodes);
+  uint32_t best_solution = 0, best_start_node = 0;
+
+  // traverse in reverse topo order
+  for (uint32_t i = static_cast<uint32_t>(graph.num_nodes()); i != 0; --i) {
+    const uint32_t nid = i - 1;
+    best_reward[nid] += node_reward[nid];
+    if (best_reward[nid] > best_solution) {
+      best_solution = best_reward[nid];
+      best_start_node = nid;
+    }
+    for (const auto& e : graph[nid].inputs) {
+      const uint32_t prev = e.node_id;
+      if (best_reward[nid] > best_reward[prev]) {
+        best_reward[prev] = best_reward[nid];
+        next_node[prev] = nid;
+      }
+    }
+  }
+  path->clear();
+  uint32_t reward = 0;
+  for (uint32_t nid = best_start_node; nid < num_nodes; nid = next_node[nid]) {
+    path->push_back(nid); reward += node_reward[nid];
+  }
+  CHECK_EQ(reward, best_solution);
+  return best_solution;
+}
+
+/*!
+ * \brief Color the nodes in the graph into index.
+ *  The coloring algorithm tries to assign node group
+ *  such that node in the same group cannot run in parallel.
+ *
+ * \param graph the original indexed graph.
+ * \param node_importance The importance of the node
+ * \param max_ncolor maximum number of colors allowed.
+ * \param color the color index of each of the node.
+ * \return the total number of colors.
+ */
+inline uint32_t ColorNodeGroup(
+    const IndexedGraph &graph,
+    std::vector<uint32_t> node_importance,
+    uint32_t max_ncolor,
+    std::vector<uint32_t> *color) {
+  CHECK_NE(max_ncolor, 0);
+  CHECK_EQ(graph.num_nodes(), node_importance.size());
+
+  color->clear();
+  color->resize(graph.num_nodes(), max_ncolor);
+  uint32_t cindex;
+  // greedy algorithm, every time
+  // find a path with best reward and assign a new color
+  // All the nodes in the path cannot run in parallel.
+  for (cindex = 0; cindex < max_ncolor - 1; ++cindex) {
+    std::vector<uint32_t> path;
+    uint32_t reward = FindBestPath(graph, node_importance, &path);
+    if (reward == 0) break;
+    for (uint32_t nid : path) {
+      if (node_importance[nid] != 0) {
+        CHECK_EQ(color->at(nid), max_ncolor);
+        color->at(nid) = cindex;
+        // make the importance 0 after color is decided.
+        node_importance[nid] = 0;
+      }
+    }
+  }
+  // assign i for rest of the node
+  for (uint32_t i = 0; i < graph.num_nodes(); ++i) {
+    if (color->at(i) == max_ncolor) {
+      color->at(i) = cindex;
+    }
+  }
+  return cindex + 1;
+}
+
+}  // namespace pass
+}  // namespace nnvm
+
+#endif  // NNVM_PASS_GRAPH_ALGORITHM_H_
diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index 662757d403e1..9eb6c9bca704 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -121,6 +121,7 @@ NNVM_REGISTER_PASS(InferType)
 
 DMLC_JSON_ENABLE_ANY(ShapeVector, list_shape);
 DMLC_JSON_ENABLE_ANY(DTypeVector, list_int);
+DMLC_JSON_ENABLE_ANY(size_t, size_t);
 
 }  // namespace pass
 }  // namespace nnvm
diff --git a/nnvm/src/pass/plan_memory.cc b/nnvm/src/pass/plan_memory.cc
new file mode 100644
index 000000000000..3ba4aee16821
--- /dev/null
+++ b/nnvm/src/pass/plan_memory.cc
@@ -0,0 +1,227 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file plan_memory.cc
+ * \brief Assign memory tag to each of the data entries.
+ */
+#include <nnvm/graph.h>
+#include <nnvm/pass.h>
+#include <nnvm/graph_attr_types.h>
+#include <nnvm/op_attr_types.h>
+#include <memory>
+#include "./graph_algorithm.h"
+
+namespace nnvm {
+namespace pass {
+
+// simple graph based allocator.
+class GraphAllocator {
+ public:
+  // storage id equals integer.
+  using StorageID = int;
+  // bad storage id
+  static const StorageID kBadStorageID = -1;
+  // request a free storage
+  StorageID Request(int dev_id, int dtype, TShape shape, uint32_t node_id) {
+    if (shape.ndim() == 0) return kBadStorageID;
+    // search memory block in [size / match_range_, size * match_range_)
+    // TODO(tqchen) add size of the dtype, assume 4 bytes for now
+    size_t size = shape.Size() * 4;
+    if (match_range_ == 0) return this->Alloc(dev_id, size);
+    auto begin = free_.lower_bound(size / match_range_);
+    auto mid = free_.lower_bound(size);
+    auto end = free_.upper_bound(size * match_range_);
+    // search for memory blocks larger than requested
+    for (auto it = mid; it != end; ++it) {
+      StorageEntry *e = it->second;
+      if (e->device_id != dev_id) continue;
+      if (node_color_.size() != 0 &&
+          node_color_[e->released_by_node] != node_color_[node_id]) continue;
+      // Use exect matching strategy
+      e->max_bytes = std::max(size, e->max_bytes);
+      // find a exact match, erase from map and return
+      free_.erase(it);
+      return e->id;
+    }
+    // then search for memory blocks smaller than requested space
+    for (auto it = mid; it != begin;) {
+      --it;
+      StorageEntry *e = it->second;
+      if (e->device_id != dev_id) continue;
+      if (node_color_.size() != 0 &&
+          node_color_[e->released_by_node] != node_color_[node_id]) continue;
+      // Use exect matching strategy
+      e->max_bytes = std::max(size, e->max_bytes);
+      // find a exact match, erase from map and return
+      free_.erase(it);
+      return e->id;
+    }
+    // cannot find anything return a new one.
+    return this->Alloc(dev_id, size);
+  }
+  // release a memory space.
+  void Release(StorageID id, uint32_t node_id) {
+    CHECK_NE(id, kBadStorageID);
+    StorageEntry *e = data_[id].get();
+    e->released_by_node = node_id;
+    free_.insert({e->max_bytes, e});
+  }
+  // totoal number of bytes allocated
+  size_t TotalAllocBytes() const {
+    size_t total = 0;
+    for (auto &p : data_) {
+      total += p->max_bytes;
+    }
+    return total;
+  }
+
+  // constructor
+  explicit GraphAllocator(const IndexedGraph* idx) : idx_(idx) {
+    this->Init(dmlc::GetEnv("NNVM_EXEC_MATCH_RANGE", 16),
+               dmlc::GetEnv("NNVM_EXEC_NUM_TEMP", 1));
+  }
+
+ private:
+  // initialize the graph allocator
+  void Init(size_t match_range, uint32_t num_match_color) {
+    match_range_ = match_range;
+    num_match_color_ = num_match_color;
+    if (num_match_color_ > 1) {
+      std::vector<uint32_t> importance(idx_->num_nodes(), 0);
+      for (uint32_t nid = 0; nid < idx_->num_nodes(); ++nid) {
+        if ((*idx_)[nid].source->is_variable()) continue;
+        importance[nid] = 1;
+      }
+      num_match_color_ = ColorNodeGroup(
+          *idx_, importance, num_match_color_, &node_color_);
+    }
+  }
+
+  StorageID Alloc(int dev_id, size_t size) {
+    StorageID id = static_cast<StorageID>(data_.size());
+    std::unique_ptr<StorageEntry> ptr(new StorageEntry());
+    ptr->id = id;
+    ptr->device_id = dev_id;
+    ptr->max_bytes = size;
+    data_.emplace_back(std::move(ptr));
+    return id;
+  }
+  // internal storage entry
+  struct StorageEntry {
+    // the id of the entry.
+    StorageID id;
+    // the device id of the storage.
+    int device_id;
+    // maximum size of storage requested.
+    size_t max_bytes{0};
+    // node index that released it last time
+    uint32_t released_by_node{0};
+  };
+  // scale used for rough match
+  size_t match_range_;
+  // whether use color based match algorithm
+  uint32_t num_match_color_{1};
+  // the size of each dtype
+  std::vector<size_t> dtype_size_dict_;
+  // free list of storage entry
+  std::multimap<size_t, StorageEntry*> free_;
+  // all the storage resources available
+  std::vector<std::unique_ptr<StorageEntry> > data_;
+  // color of nodes in the graph, used for auxiliary policy making.
+  std::vector<uint32_t> node_color_;
+  // internal indexed graph
+  const IndexedGraph* idx_;
+};
+
+// function to plan memory
+Graph PlanMemory(Graph ret) {
+  // setup ref counter
+  const IndexedGraph& idx = ret.indexed_graph();
+  // reference counter of each node
+  std::vector<uint32_t> ref_count(idx.num_node_entries(), 0);
+  // step 1: initialize reference count
+  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+    for (const auto& e : idx[nid].inputs) {
+      ++ref_count[e.node_id];
+    }
+  }
+  for (const auto& e : idx.outputs()) {
+    ++ref_count[e.node_id];
+  }
+  // step 2: allocate memory.
+  StorageVector storage(idx.num_node_entries(), -1);
+  const ShapeVector& shape_vec = ret.GetAttr<ShapeVector>("shape");
+  const DTypeVector& dtype_vec = ret.GetAttr<DTypeVector>("dtype");
+  const DeviceVector* device_vec = nullptr;
+  static auto& finplace_option = Op::GetAttr<FInplaceOption>("FInplaceOption");
+
+  if (ret.attrs.count("device") != 0) {
+    device_vec = &(ret.GetAttr<DeviceVector>("device"));
+  }
+  // the allocator.
+  GraphAllocator allocator(&idx);
+  // number of entries that are not statically allocated.
+  size_t num_not_allocated = 0;
+
+  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+    const auto& inode = idx[nid];
+    if (inode.source->is_variable()) continue;
+    // check inplace option
+    if (finplace_option.count(inode.source->op) != 0) {
+      auto inplace_pairs = finplace_option[inode.source->op](inode.source->attrs);
+      for (auto& kv : inplace_pairs) {
+        uint32_t eid_out = idx.entry_id(nid, kv.second);
+        uint32_t eid_in = idx.entry_id(inode.inputs[kv.first]);
+        if (ref_count[eid_in] == 1 && storage[eid_in] != GraphAllocator::kBadStorageID) {
+          storage[eid_out] = storage[eid_in];
+          ref_count[eid_in] = 0;
+        }
+      }
+    }
+    // normal allocation
+    const int dev_id = (device_vec != nullptr) ? device_vec->at(nid) : 0;
+    // allocate output
+    for (uint32_t index = 0; index < inode.source->num_outputs(); ++index) {
+      uint32_t eid = idx.entry_id(nid, index);
+      if (storage[eid] == GraphAllocator::kBadStorageID) {
+        storage[eid] = allocator.Request(dev_id, dtype_vec[eid], shape_vec[eid], nid);
+      }
+    }
+    // then free inputs
+    for (const auto& e : inode.inputs) {
+      uint32_t eid = idx.entry_id(e);
+      // temp_ref_count == 0 means it is taken by inplace op
+      if (ref_count[eid] == 0) continue;
+      // if we decrease it to zero, means we are ready to relase
+      --ref_count[eid];
+      if (ref_count[eid] == 0 && storage[eid] != GraphAllocator::kBadStorageID) {
+        allocator.Release(storage[eid], nid);
+      }
+    }
+    // check if there are outputs that can be freeded immediately
+    for (uint32_t index = 0; index < inode.source->num_outputs(); ++index) {
+      uint32_t eid = idx.entry_id(nid, index);
+      if (ref_count[eid] == 0 && storage[eid] != GraphAllocator::kBadStorageID) {
+        allocator.Release(storage[eid], nid);
+      }
+      if (storage[eid] == GraphAllocator::kBadStorageID) {
+        ++num_not_allocated;
+      }
+    }
+  }
+
+  ret.attrs["storage_id"] = std::make_shared<any>(std::move(storage));
+  ret.attrs["storage_allocated_bytes"] = std::make_shared<any>(allocator.TotalAllocBytes());
+  ret.attrs["storage_num_not_allocated"] = std::make_shared<any>(num_not_allocated);
+  return ret;
+}
+
+NNVM_REGISTER_PASS(PlanMemory)
+.describe("Plan the memory allocation of each node entries.")
+.set_body(PlanMemory)
+.set_change_graph(false)
+.depend_graph_attr("dtype")
+.depend_graph_attr("shape")
+.provide_graph_attr("storage_id");
+
+}  // namespace pass
+}  // namespace nnvm
diff --git a/nnvm/tests/python/test_graph.py b/nnvm/tests/python/test_graph.py
index 6209ca9386d6..787e0ae78e14 100644
--- a/nnvm/tests/python/test_graph.py
+++ b/nnvm/tests/python/test_graph.py
@@ -95,6 +95,25 @@ def test_place_device():
     assert g.json_attr('device')[jnode_row_ptr[nindex["add3"]]] == 1
     assert g.json_attr('device')[jnode_row_ptr[nindex["cast1"]]] == 0
 
+def test_plan_memory():
+    x = sym.Variable('x', shape=(4, 2))
+    x2 = sym.add(x, x, name='addk')
+    y = sym.reshape(x2, target=(2, 4), name="reshapek")
+    y = sym.add(y, x2, name="add2")
+    y = sym.add(y, y)
+    g = graph.create(y)
+    g._set_json_attr("shape_attr_key", "shape")
+    g = g.apply(["InferShape", "InferType", "PlanMemory"])
+    jgraph = json.loads(g.apply('SaveJSON').json_attr('json'))
+    jnodes = jgraph['nodes']
+    jnode_row_ptr = jgraph['node_row_ptr']
+    storage_id = g.json_attr('storage_id')
+    nindex = {n['name']: i for i, n in enumerate(jnodes)}
+    assert (storage_id[jnode_row_ptr[nindex["addk"]]] !=
+            storage_id[jnode_row_ptr[nindex["reshapek"]]])
+    assert (storage_id[jnode_row_ptr[nindex["add2"]]] ==
+            storage_id[jnode_row_ptr[nindex["reshapek"]]])
+
 
 if __name__ == "__main__":
     test_order_mutation_pass()
@@ -103,3 +122,4 @@ def test_place_device():
     test_infer_shape()
     test_infer_type()
     test_place_device()
+    test_plan_memory()

From cb1c06e27c94269f446e7919f36bde3f09bc141f Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 2 Aug 2016 11:09:01 -0700
Subject: [PATCH 350/949] [REFACTOR] copy DMLC headers, move operator to
 example (#20)

---
 nnvm/Makefile                                 |  16 +-
 nnvm/{src/example => example/src}/operator.cc |   2 +-
 nnvm/include/dmlc/README                      |   2 +
 nnvm/include/dmlc/any.h                       | 345 +++++++
 nnvm/include/dmlc/array_view.h                | 116 +++
 nnvm/include/dmlc/base.h                      | 228 +++++
 nnvm/include/dmlc/json.h                      | 868 ++++++++++++++++++
 nnvm/include/dmlc/logging.h                   | 262 ++++++
 nnvm/include/dmlc/memory.h                    | 261 ++++++
 nnvm/include/dmlc/parameter.h                 | 831 +++++++++++++++++
 nnvm/include/dmlc/registry.h                  | 277 ++++++
 nnvm/include/dmlc/thread_local.h              |  77 ++
 nnvm/include/dmlc/timer.h                     |  49 +
 nnvm/include/dmlc/type_traits.h               | 171 ++++
 nnvm/include/nnvm/tuple.h                     |   1 +
 nnvm/python/nnvm/ctypes/symbol.py             |  12 +-
 nnvm/python/nnvm/libinfo.py                   |   4 +-
 nnvm/python/setup.py                          |  43 +-
 nnvm/src/test_main.cc                         |  88 --
 19 files changed, 3527 insertions(+), 126 deletions(-)
 rename nnvm/{src/example => example/src}/operator.cc (99%)
 create mode 100644 nnvm/include/dmlc/README
 create mode 100644 nnvm/include/dmlc/any.h
 create mode 100644 nnvm/include/dmlc/array_view.h
 create mode 100644 nnvm/include/dmlc/base.h
 create mode 100644 nnvm/include/dmlc/json.h
 create mode 100644 nnvm/include/dmlc/logging.h
 create mode 100644 nnvm/include/dmlc/memory.h
 create mode 100644 nnvm/include/dmlc/parameter.h
 create mode 100644 nnvm/include/dmlc/registry.h
 create mode 100644 nnvm/include/dmlc/thread_local.h
 create mode 100644 nnvm/include/dmlc/timer.h
 create mode 100644 nnvm/include/dmlc/type_traits.h
 delete mode 100644 nnvm/src/test_main.cc

diff --git a/nnvm/Makefile b/nnvm/Makefile
index 86edfaea378e..07f46ee2456e 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -1,15 +1,15 @@
 export LDFLAGS = -pthread -lm
 export CFLAGS =  -std=c++11 -Wall -O2 -msse2  -Wno-unknown-pragmas -funroll-loops\
-	 -Iinclude -Idmlc-core/include -fPIC
+	 -Iinclude -fPIC
 
 # specify tensor path
 .PHONY: clean all test lint doc cython cython3 cyclean
 
-all: lib/libnnvm.so lib/libnnvm.a cli_test
+all: lib/libnnvm.a lib/libnnvm_example.so
 
-SRC = $(wildcard src/*.cc src/*/*.cc example/*.cc)
+SRC = $(wildcard src/*.cc src/*/*.cc)
 ALL_OBJ = $(patsubst src/%.cc, build/%.o, $(SRC))
-ALL_DEP = $(filter-out build/test_main.o, $(ALL_OBJ))
+ALL_DEP = $(ALL_OBJ)
 
 include tests/cpp/unittest.mk
 
@@ -20,16 +20,14 @@ build/%.o: src/%.cc
 	$(CXX) $(CFLAGS) -MM -MT build/$*.o $< >build/$*.d
 	$(CXX) -c $(CFLAGS) -c $< -o $@
 
-lib/libnnvm.so: $(ALL_DEP)
-	@mkdir -p $(@D)
-	$(CXX) $(CFLAGS) -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
 
 lib/libnnvm.a: $(ALL_DEP)
 	@mkdir -p $(@D)
 	ar crv $@ $(filter %.o, $?)
 
-cli_test: $(ALL_DEP) build/test_main.o
-	$(CXX) $(CFLAGS)  -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
+lib/libnnvm_example.so: example/src/operator.cc lib/libnnvm.a
+	@mkdir -p $(@D)
+	$(CXX) $(CFLAGS) -shared -o $@ $(filter %.cc, $^) $(LDFLAGS) -Wl,--whole-archive lib/libnnvm.a -Wl,--no-whole-archive
 
 cython:
 	cd python; python setup.py build_ext --inplace
diff --git a/nnvm/src/example/operator.cc b/nnvm/example/src/operator.cc
similarity index 99%
rename from nnvm/src/example/operator.cc
rename to nnvm/example/src/operator.cc
index 3a80fefc88be..70063e4c4000 100644
--- a/nnvm/src/example/operator.cc
+++ b/nnvm/example/src/operator.cc
@@ -96,7 +96,7 @@ NNVM_REGISTER_OP(__add_symbol__)
 .set_num_inputs(2);
 
 NNVM_REGISTER_OP(exp)
-.describe("take exponmential")
+.describe("take exponential")
 .set_num_inputs(1)
 .attr("inplace_pair", std::make_pair(0, 0))
 .attr<FInferShape>("FInferShape", SameShape);
diff --git a/nnvm/include/dmlc/README b/nnvm/include/dmlc/README
new file mode 100644
index 000000000000..bfda4925cf1b
--- /dev/null
+++ b/nnvm/include/dmlc/README
@@ -0,0 +1,2 @@
+This folder is synced from dmlc-core/include/dmlc
+Contains useful utility headers for the project.
diff --git a/nnvm/include/dmlc/any.h b/nnvm/include/dmlc/any.h
new file mode 100644
index 000000000000..5707a363db99
--- /dev/null
+++ b/nnvm/include/dmlc/any.h
@@ -0,0 +1,345 @@
+/*!
+ * Copyright (c) 2016 by Contributors
+ * \file any.h
+ * \brief Container to hold any data type.
+ */
+#ifndef DMLC_ANY_H_
+#define DMLC_ANY_H_
+
+// This code need c++11 to compile
+#include <typeinfo>
+#include <type_traits>
+#include <utility>
+#include <algorithm>
+
+#include "./base.h"
+#include "./logging.h"
+
+namespace dmlc {
+// forward declare any;
+class any;
+
+/*!
+ * Get a  reference to content stored in the any as type T.
+ * This will cause an error if
+ * T does not match the type stored.
+ * This function is not part of std::any standard.
+ *
+ * \param src The source source any container.
+ * \return The reference of content
+ * \tparam T The type of the value to be fetched.
+ */
+template<typename T>
+inline T& get(any& src);  // NOLINT(*)
+
+/*!
+ * Get the const reference content stored in the any as type T.
+ * This will cause an error if
+ * T does not match the type stored.
+ * This function is not part of std::any standard.
+ *
+ * \param src The source source any container.
+ * \return The reference of content
+ * \tparam T The type of the value to be fetched.
+ */
+template<typename T>
+inline const T& get(const any& src);
+
+/*!
+ * \brief An any class that is compatible to std::any in c++17.
+ *
+ * \code
+ *   dmlc::any a = std::string("mydear"), b = 1;
+ *   // get reference out and add it
+ *   dmlc::get<int>(b) += 1;
+ *   // a is now string
+ *   LOG(INFO) << dmlc::get<std::string>(a);
+ *   // a is now 2, the string stored will be properly destructed
+ *   a = std::move(b);
+ *   LOG(INFO) << dmlc::get<int>(a);
+ * \endcode
+ * \sa get
+ */
+class any {
+ public:
+  /*! \brief default constructor */
+  inline any() = default;
+  /*!
+   * \brief move constructor from another any
+   * \param other The other any to be moved
+   */
+  inline any(any&& other);  // NOLINT(*)
+  /*!
+   * \brief copy constructor
+   * \param other The other any to be copied
+   */
+  inline any(const any& other);  // NOLINT(*)
+  /*!
+   * \brief constructor from any types
+   * \param other The other types to be constructed into any.
+   * \tparam T The value type of other.
+   */
+  template<typename T>
+  inline any(T&& other);  // NOLINT(*)
+  /*! \brief destructor */
+  inline ~any();
+  /*!
+   * \brief assign operator from other
+   * \param other The other any to be copy or moved.
+   * \return self
+   */
+  inline any& operator=(any&& other);
+  /*!
+   * \brief assign operator from other
+   * \param other The other any to be copy or moved.
+   * \return self
+   */
+  inline any& operator=(const any& other);
+  /*!
+   * \brief assign operator from any type.
+   * \param other The other any to be copy or moved.
+   * \tparam T The value type of other.
+   * \return self
+   */
+  template<typename T>
+  inline any& operator=(T&& other);
+  /*!
+   * \return whether the container is empty.
+   */
+  inline bool empty() const;
+  /*!
+   * \return clear the content of container
+   */
+  inline void clear();
+  /*!
+   * swap current content with other
+   * \param other The other data to be swapped.
+   */
+  inline void swap(any& other); // NOLINT(*)
+  /*!
+   * \return The type_info about the stored type.
+   */
+  inline const std::type_info& type() const;
+
+ private:
+  //! \cond Doxygen_Suppress
+  // declare of helper class
+  template<typename T>
+  class TypeOnHeap;
+  template<typename T>
+  class TypeOnStack;
+  template<typename T>
+  class TypeInfo;
+  // size of stack space, it takes 32 bytes for one any type.
+  static const size_t kStack = sizeof(void*) * 3;
+  static const size_t kAlign = sizeof(void*);
+  // container use dynamic storage only when space runs lager
+  union Data {
+    // stack space
+    std::aligned_storage<kStack, kAlign>::type stack;
+    // pointer to heap space
+    void* pheap;
+  };
+  // type specific information
+  struct Type {
+    // destructor function
+    void (*destroy)(Data* data);
+    // copy constructor
+    void (*create_from_data)(Data* dst, const Data& src);
+    // the type info function
+    const std::type_info* ptype_info;
+  };
+  // constant to check if data can be stored on heap.
+  template<typename T>
+  struct data_on_stack {
+    static const bool value = alignof(T) <= kAlign && sizeof(T) <= kStack;
+  };
+  // declare friend with
+  template<typename T>
+  friend T& get(any& src);  // NOLINT(*)
+  template<typename T>
+  friend const T& get(const any& src);
+  // internal construct function
+  inline void construct(any&& other);
+  // internal construct function
+  inline void construct(const any& other);
+  // internal function to check if type is correct.
+  template<typename T>
+  inline void check_type() const;
+  // internal type specific information
+  const Type* type_{nullptr};
+  // internal data
+  Data data_;
+};
+
+template<typename T>
+inline any::any(T&& other) {
+  typedef typename std::decay<T>::type DT;
+  if (std::is_same<DT, any>::value) {
+    this->construct(std::forward<T>(other));
+  } else {
+    static_assert(std::is_copy_constructible<DT>::value,
+                  "Any can only hold value that is copy constructable");
+    type_ = TypeInfo<DT>::get_type();
+    if (data_on_stack<DT>::value) {
+      new (&(data_.stack)) DT(std::forward<T>(other));
+    } else {
+      data_.pheap = new DT(std::forward<T>(other));
+    }
+  }
+}
+
+inline any::any(any&& other) {
+  this->construct(std::move(other));
+}
+
+inline any::any(const any& other) {
+  this->construct(other);
+}
+
+inline void any::construct(any&& other) {
+  type_ = other.type_;
+  data_ = other.data_;
+  other.type_ = nullptr;
+}
+
+inline void any::construct(const any& other) {
+  type_ = other.type_;
+  if (type_ != nullptr) {
+    type_->create_from_data(&data_, other.data_);
+  }
+}
+
+inline any::~any() {
+  this->clear();
+}
+
+inline any& any::operator=(any&& other) {
+  any(std::move(other)).swap(*this);
+  return *this;
+}
+
+inline any& any::operator=(const any& other) {
+  any(other).swap(*this);
+  return *this;
+}
+
+template<typename T>
+inline any& any::operator=(T&& other) {
+  any(std::forward<T>(other)).swap(*this);
+  return *this;
+}
+
+inline void any::swap(any& other) { // NOLINT(*)
+  std::swap(type_, other.type_);
+  std::swap(data_, other.data_);
+}
+
+inline void any::clear() {
+  if (type_ != nullptr) {
+    if (type_->destroy != nullptr) {
+      type_->destroy(&data_);
+    }
+    type_ = nullptr;
+  }
+}
+
+inline bool any::empty() const {
+  return type_ == nullptr;
+}
+
+inline const std::type_info& any::type() const {
+  if (type_ != nullptr) {
+    return *(type_->ptype_info);
+  } else {
+    return typeid(void);
+  }
+}
+
+template<typename T>
+inline void any::check_type() const {
+  CHECK(type_ != nullptr)
+      << "The any container is empty";
+  CHECK(type_->ptype_info == &typeid(T))
+      << "The stored type mismatch"
+      << " stored=" << type_->ptype_info->name()
+      << " requested=" << typeid(T).name();
+}
+
+template<typename T>
+inline const T& get(const any& src) {
+  src.check_type<T>();
+  return *any::TypeInfo<T>::get_ptr(&(src.data_));
+}
+
+template<typename T>
+inline T& get(any& src) { // NOLINT(*)
+  src.check_type<T>();
+  return *any::TypeInfo<T>::get_ptr(&(src.data_));
+}
+
+template<typename T>
+class any::TypeOnHeap {
+ public:
+  inline static T* get_ptr(any::Data* data) {
+    return static_cast<T*>(data->pheap);
+  }
+  inline static const T* get_ptr(const any::Data* data) {
+    return static_cast<const T*>(data->pheap);
+  }
+  inline static void create_from_data(any::Data* dst, const any::Data& data) {
+    dst->pheap = new T(*get_ptr(&data));
+  }
+  inline static void destroy(Data* data) {
+    delete static_cast<T*>(data->pheap);
+  }
+};
+
+template<typename T>
+class any::TypeOnStack {
+ public:
+  inline static T* get_ptr(any::Data* data) {
+    return reinterpret_cast<T*>(&(data->stack));
+  }
+  inline static const T* get_ptr(const any::Data* data) {
+    return reinterpret_cast<const T*>(&(data->stack));
+  }
+  inline static void create_from_data(any::Data* dst, const any::Data& data) {
+    new (&(dst->stack)) T(*get_ptr(&data));
+  }
+  inline static void destroy(Data* data) {
+    T* dptr = reinterpret_cast<T*>(&(data->stack));
+    dptr->~T();
+  }
+};
+
+template<typename T>
+class any::TypeInfo
+    : public std::conditional<any::data_on_stack<T>::value,
+                              any::TypeOnStack<T>,
+                              any::TypeOnHeap<T> >::type {
+ public:
+  inline static const Type* get_type() {
+    static TypeInfo<T> tp;
+    return &(tp.type_);
+  }
+
+ private:
+  // local type
+  Type type_;
+  // constructor
+  TypeInfo() {
+    if (std::is_pod<T>::value) {
+      type_.destroy = nullptr;
+    } else {
+      type_.destroy = TypeInfo<T>::destroy;
+    }
+    type_.create_from_data = TypeInfo<T>::create_from_data;
+    type_.ptype_info = &typeid(T);
+  }
+};
+//! \endcond
+
+}  // namespace dmlc
+
+#endif  // DMLC_ANY_H_
diff --git a/nnvm/include/dmlc/array_view.h b/nnvm/include/dmlc/array_view.h
new file mode 100644
index 000000000000..c9f19eff3720
--- /dev/null
+++ b/nnvm/include/dmlc/array_view.h
@@ -0,0 +1,116 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file array_view.h
+ * \brief Read only data structure to reference array
+ */
+#ifndef DMLC_ARRAY_VIEW_H_
+#define DMLC_ARRAY_VIEW_H_
+
+#include <vector>
+#include <array>
+
+namespace dmlc {
+
+/*!
+ * \brief Read only data structure to reference continuous memory region of array.
+ * Provide unified view for vector, array and C style array.
+ * This data structure do not guarantee aliveness of referenced array.
+ *
+ * Make sure do not use array_view to record data in async function closures.
+ * Also do not use array_view to create reference to temporary data structure.
+ *
+ * \tparam ValueType The value
+ *
+ * \code
+ *  std::vector<int> myvec{1,2,3};
+ *  dmlc::array_view<int> view(myvec);
+ *  // indexed visit to the view.
+ *  LOG(INFO) << view[0];
+ *
+ *  for (int v : view) {
+ *     // visit each element in the view
+ *  }
+ * \endcode
+ */
+template<typename ValueType>
+class array_view {
+ public:
+  /*! \brief default constructor */
+  array_view() = default;
+  /*!
+   * \brief default copy constructor
+   * \param other another array view.
+   */
+  array_view(const array_view<ValueType> &other) = default;  // NOLINT(*)
+  /*!
+   * \brief default move constructor
+   * \param other another array view.
+   */
+  array_view(array_view<ValueType>&& other) = default; // NOLINT(*)
+  /*!
+   * \brief default assign constructor
+   * \param other another array view.
+   * \return self.
+   */
+  array_view<ValueType>& operator=(const array_view<ValueType>& other) = default; // NOLINT(*)
+  /*!
+   * \brief construct array view std::vector
+   * \param other vector container
+   */
+  array_view(const std::vector<ValueType>& other) {  // NOLINT(*)
+    if (other.size() != 0) {
+      begin_ = &other[0]; size_ = other.size();
+    }
+  }
+  /*!
+   * \brief construct array std::array
+   * \param other another array view.
+   */
+  template<std::size_t size>
+  array_view(const std::array<ValueType, size>& other) {  // NOLINT(*)
+    if (size != 0) {
+      begin_ = &other[0]; size_ = size;
+    }
+  }
+  /*!
+   * \brief construct array view from continuous segment
+   * \param begin beginning pointre
+   * \param end end pointer
+   */
+  array_view(const ValueType* begin, const ValueType* end) {
+    if (begin < end) {
+      begin_ = begin;
+      size_ = end - begin;
+    }
+  }
+  /*! \return size of the array */
+  inline size_t size() const {
+    return size_;
+  }
+  /*! \return begin of the array */
+  inline const ValueType* begin() const {
+    return begin_;
+  }
+  /*! \return end point of the array */
+  inline const ValueType* end() const {
+    return begin_ + size_;
+  }
+  /*!
+   * \brief get i-th element from the view
+   * \param i The index.
+   * \return const reference to i-th element.
+   */
+  inline const ValueType& operator[](size_t i) const {
+    return begin_[i];
+  }
+
+ private:
+  /*! \brief the begin of the view */
+  const ValueType* begin_{nullptr};
+  /*! \brief The size of the view */
+  size_t size_{0};
+};
+
+}  // namespace dmlc
+
+#endif  // DMLC_ARRAY_VIEW_H_
diff --git a/nnvm/include/dmlc/base.h b/nnvm/include/dmlc/base.h
new file mode 100644
index 000000000000..5b34fd6b4e34
--- /dev/null
+++ b/nnvm/include/dmlc/base.h
@@ -0,0 +1,228 @@
+/*!
+ *  Copyright (c) 2015 by Contributors
+ * \file base.h
+ * \brief defines configuration macros
+ */
+#ifndef DMLC_BASE_H_
+#define DMLC_BASE_H_
+
+/*! \brief whether use glog for logging */
+#ifndef DMLC_USE_GLOG
+#define DMLC_USE_GLOG 0
+#endif
+
+/*!
+ * \brief whether throw dmlc::Error instead of
+ *  directly calling abort when FATAL error occured
+ *  NOTE: this may still not be perfect.
+ *  do not use FATAL and CHECK in destructors
+ */
+#ifndef DMLC_LOG_FATAL_THROW
+#define DMLC_LOG_FATAL_THROW 1
+#endif
+
+/*!
+ * \brief whether always log a message before throw
+ * This can help identify the error that cannot be catched.
+ */
+#ifndef DMLC_LOG_BEFORE_THROW
+#define DMLC_LOG_BEFORE_THROW 1
+#endif
+
+/*!
+ * \brief Whether to use customized logger,
+ * whose output can be decided by other libraries.
+ */
+#ifndef DMLC_LOG_CUSTOMIZE
+#define DMLC_LOG_CUSTOMIZE 0
+#endif
+
+/*! \brief whether compile with hdfs support */
+#ifndef DMLC_USE_HDFS
+#define DMLC_USE_HDFS 0
+#endif
+
+/*! \brief whether compile with s3 support */
+#ifndef DMLC_USE_S3
+#define DMLC_USE_S3 0
+#endif
+
+/*! \brief whether or not use parameter server */
+#ifndef DMLC_USE_PS
+#define DMLC_USE_PS 0
+#endif
+
+/*! \brief whether or not use c++11 support */
+#ifndef DMLC_USE_CXX11
+#define DMLC_USE_CXX11 (defined(__GXX_EXPERIMENTAL_CXX0X__) ||\
+                        __cplusplus >= 201103L || defined(_MSC_VER))
+#endif
+
+/// check if g++ is before 4.6
+#if DMLC_USE_CXX11 && defined(__GNUC__) && !defined(__clang_version__)
+#if __GNUC__ == 4 && __GNUC_MINOR__ < 6
+#pragma message("Will need g++-4.6 or higher to compile all"           \
+                "the features in dmlc-core, "                           \
+                "compile without c++0x, some features may be disabled")
+#undef DMLC_USE_CXX11
+#define DMLC_USE_CXX11 0
+#endif
+#endif
+
+/*!
+ * \brief Enable std::thread related modules,
+ *  Used to disable some module in mingw compile.
+ */
+#ifndef DMLC_ENABLE_STD_THREAD
+#define DMLC_ENABLE_STD_THREAD DMLC_USE_CXX11
+#endif
+
+/*! \brief whether enable regex support, actually need g++-4.9 or higher*/
+#ifndef DMLC_USE_REGEX
+#define DMLC_USE_REGEX (__cplusplus >= 201103L || defined(_MSC_VER))
+#endif
+
+/*! \brief helper macro to generate string concat */
+#define DMLC_STR_CONCAT_(__x, __y) __x##__y
+#define DMLC_STR_CONCAT(__x, __y) DMLC_STR_CONCAT_(__x, __y)
+
+/*!
+ * \brief Disable copy constructor and assignment operator.
+ *
+ * If C++11 is supported, both copy and move constructors and
+ * assignment operators are deleted explicitly. Otherwise, they are
+ * only declared but not implemented. Place this macro in private
+ * section if C++11 is not available.
+ */
+#ifndef DISALLOW_COPY_AND_ASSIGN
+#  if DMLC_USE_CXX11
+#    define DISALLOW_COPY_AND_ASSIGN(T) \
+       T(T const&) = delete; \
+       T(T&&) = delete; \
+       T& operator=(T const&) = delete; \
+       T& operator=(T&&) = delete
+#  else
+#    define DISALLOW_COPY_AND_ASSIGN(T) \
+       T(T const&); \
+       T& operator=(T const&)
+#  endif
+#endif
+
+///
+/// code block to handle optionally loading
+///
+#if !defined(__GNUC__)
+#define fopen64 std::fopen
+#endif
+#if (defined __MINGW32__) && !(defined __MINGW64__)
+#define fopen64 std::fopen
+#endif
+#ifdef _MSC_VER
+#if _MSC_VER < 1900
+// NOTE: sprintf_s is not equivalent to snprintf,
+// they are equivalent when success, which is sufficient for our case
+#define snprintf sprintf_s
+#define vsnprintf vsprintf_s
+#endif
+#else
+#ifdef _FILE_OFFSET_BITS
+#if _FILE_OFFSET_BITS == 32
+#pragma message("Warning: FILE OFFSET BITS defined to be 32 bit")
+#endif
+#endif
+
+#ifdef __APPLE__
+#define off64_t off_t
+#define fopen64 std::fopen
+#endif
+
+extern "C" {
+#include <sys/types.h>
+}
+#endif
+
+#ifdef _MSC_VER
+//! \cond Doxygen_Suppress
+typedef signed char int8_t;
+typedef __int16 int16_t;
+typedef __int32 int32_t;
+typedef __int64 int64_t;
+typedef unsigned char uint8_t;
+typedef unsigned __int16 uint16_t;
+typedef unsigned __int32 uint32_t;
+typedef unsigned __int64 uint64_t;
+//! \endcond
+#else
+#include <inttypes.h>
+#endif
+#include <string>
+#include <vector>
+
+#if defined(_MSC_VER) && _MSC_VER < 1900
+#define noexcept_true throw ()
+#define noexcept_false
+#define noexcept(a) noexcept_##a
+#endif
+
+#if DMLC_USE_CXX11
+#define DMLC_THROW_EXCEPTION noexcept(false)
+#define DMLC_NO_EXCEPTION  noexcept(true)
+#else
+#define DMLC_THROW_EXCEPTION
+#define DMLC_NO_EXCEPTION
+#endif
+
+/*! \brief namespace for dmlc */
+namespace dmlc {
+/*!
+ * \brief safely get the beginning address of a vector
+ * \param vec input vector
+ * \return beginning address of a vector
+ */
+template<typename T>
+inline T *BeginPtr(std::vector<T> &vec) {  // NOLINT(*)
+  if (vec.size() == 0) {
+    return NULL;
+  } else {
+    return &vec[0];
+  }
+}
+/*!
+ * \brief get the beginning address of a vector
+ * \param vec input vector
+ * \return beginning address of a vector
+ */
+template<typename T>
+inline const T *BeginPtr(const std::vector<T> &vec) {
+  if (vec.size() == 0) {
+    return NULL;
+  } else {
+    return &vec[0];
+  }
+}
+/*!
+ * \brief get the beginning address of a vector
+ * \param str input string
+ * \return beginning address of a string
+ */
+inline char* BeginPtr(std::string &str) {  // NOLINT(*)
+  if (str.length() == 0) return NULL;
+  return &str[0];
+}
+/*!
+ * \brief get the beginning address of a vector
+ * \param str input string
+ * \return beginning address of a string
+ */
+inline const char* BeginPtr(const std::string &str) {
+  if (str.length() == 0) return NULL;
+  return &str[0];
+}
+}  // namespace dmlc
+
+#if defined(_MSC_VER) && _MSC_VER < 1900
+#define constexpr const
+#define alignof __alignof
+#endif
+
+#endif  // DMLC_BASE_H_
diff --git a/nnvm/include/dmlc/json.h b/nnvm/include/dmlc/json.h
new file mode 100644
index 000000000000..2daa0aaa017f
--- /dev/null
+++ b/nnvm/include/dmlc/json.h
@@ -0,0 +1,868 @@
+/*!
+ * Copyright (c) 2015 by Contributors
+ * \file json.h
+ * \brief Lightweight JSON Reader/Writer that read save into C++ data structs.
+ *  This includes STL composites and structures.
+ */
+#ifndef DMLC_JSON_H_
+#define DMLC_JSON_H_
+
+// This code requires C++11 to compile
+#include <vector>
+#include <iostream>
+#include <cctype>
+#include <string>
+#include <algorithm>
+#include <map>
+#include <list>
+#include <utility>
+
+#include "./base.h"
+#include "./logging.h"
+#include "./type_traits.h"
+
+#if DMLC_USE_CXX11
+#include <typeindex>
+#include <typeinfo>
+#include <unordered_map>
+#include "./any.h"
+#endif  // DMLC_USE_CXX11
+
+namespace dmlc {
+/*!
+ * \brief Lightweight JSON Reader to read any STL compositions and structs.
+ *  The user need to know the schema of the
+ *
+ */
+class JSONReader {
+ public:
+  /*!
+   * \brief Constructor.
+   * \param is the input stream.
+   */
+  explicit JSONReader(std::istream *is)
+      : is_(is),
+        line_count_r_(0),
+        line_count_n_(0) {}
+  /*!
+   * \brief Parse next JSON string.
+   * \param out_str the output string.
+   * \throw dmlc::Error when next token is not string
+   */
+  inline void ReadString(std::string *out_str);
+  /*!
+   * \brief Read Number.
+   * \param out_value output value;
+   * \throw dmlc::Error when next token is not number of ValueType.
+   * \tparam ValueType type of the number
+   */
+  template<typename ValueType>
+  inline void ReadNumber(ValueType *out_value);
+  /*!
+   * \brief Begin parsing an object.
+   * \code
+   *  std::string key;
+   *  // value can be any type that is json serializable.
+   *  std::string value;
+   *  reader->BeginObject();
+   *  while (reader->NextObjectItem(&key)) {
+   *    // do somthing to key value
+   *    reader->Read(&value);
+   *  }
+   * \endcode
+   */
+  inline void BeginObject();
+  /*!
+   * \brief Begin parsing an array.
+   * \code
+   *  // value can be any type that is json serializable.
+   *  std::string value;
+   *  reader->BeginArray();
+   *  while (reader->NextObjectArrayItem(&value)) {
+   *    // do somthing to value
+   *  }
+   * \endcode
+   */
+  inline void BeginArray();
+  /*!
+   * \brief Try to move to next object item.
+   *  If this call is successful, user can proceed to call
+   *  reader->Read to read in the value.
+   * \param out_key the key to the next object.
+   * \return true if the read is successful, false if we are at end of the object.
+   */
+  inline bool NextObjectItem(std::string *out_key);
+  /*!
+   * \brief Try to read the next element in the array.
+   *  If this call is successful, user can proceed to call
+   *  reader->Read to read in the value.
+   * \return true if the read is successful, false if we are at end of the array.
+   */
+  inline bool NextArrayItem();
+  /*!
+   * \brief Read next ValueType.
+   * \param out_value any STL or json readable type to be read
+   * \throw dmlc::Error when the read of ValueType is not successful.
+   * \tparam ValueType the data type to be read.
+   */
+  template<typename ValueType>
+  inline void Read(ValueType *out_value);
+
+  /*! \return current line count */
+  inline std::string line_info() const {
+    char temp[64];
+    std::ostringstream os;
+    os << " Line " << std::max(line_count_r_, line_count_n_);
+    is_->getline(temp, 64);
+    os << ", around ^`" << temp << "`";
+    return os.str();
+  }
+
+ private:
+  /*! \brief internal reader stream */
+  std::istream *is_;
+  /*! \brief "\\r" counter */
+  size_t line_count_r_;
+  /*! \brief "\\n" counter */
+  size_t line_count_n_;
+  /*!
+   * \brief record how many element processed in
+   *  current array/object scope.
+   */
+  std::vector<size_t> scope_counter_;
+  /*!
+   * \brief Read next nonspace character.
+   * \return the next nonspace character.
+   */
+  inline int NextNonSpace();
+  /*!
+   * \brief Read just before next nonspace but not read that.
+   * \return the next nonspace character.
+   */
+  inline int PeekNextNonSpace();
+};
+
+/*!
+ * \brief Lightweight json to write any STL compositions.
+ */
+class JSONWriter {
+ public:
+  /*!
+   * \brief Constructor.
+   * \param os the output stream.
+   */
+  explicit JSONWriter(std::ostream *os)
+      : os_(os) {}
+  /*!
+   * \brief Write a string that do not contain escape characters.
+   * \param s the string to be written.
+   */
+  inline void WriteNoEscape(const std::string &s);
+  /*!
+   * \brief Write a string that can contain escape characters.
+   * \param s the string to be written.
+   */
+  inline void WriteString(const std::string &s);
+  /*!
+   * \brief Write a string that can contain escape characters.
+   * \param v the value to be written.
+   * \tparam ValueType The value type to be written.
+   */
+  template<typename ValueType>
+  inline void WriteNumber(const ValueType &v);
+  /*!
+   * \brief Start beginning of array.
+   * \param multi_line whether to start an multi_line array.
+   * \code
+   *  writer->BeginArray();
+   *  for (auto& v : vdata) {
+   *    writer->WriteArrayItem(v);
+   *  }
+   *  writer->EndArray();
+   * \endcode
+   */
+  inline void BeginArray(bool multi_line = true);
+  /*! \brief Finish writing an array. */
+  inline void EndArray();
+  /*!
+   * \brief Start beginning of array.
+   * \param multi_line whether to start an multi_line array.
+   * \code
+   *  writer->BeginObject();
+   *  for (auto& kv : vmap) {
+   *    writer->WriteObjectKeyValue(kv.first, kv.second);
+   *  }
+   *  writer->EndObject();
+   * \endcode
+   */
+  inline void BeginObject(bool multi_line = true);
+  /*! \brief Finish writing object. */
+  inline void EndObject();
+  /*!
+   * \brief Write key value pair in the object.
+   * \param key the key of the object.
+   * \param value the value of to be written.
+   * \tparam ValueType The value type to be written.
+   */
+  template<typename ValueType>
+  inline void WriteObjectKeyValue(const std::string &key,
+                                  const ValueType &value);
+  /*!
+   * \brief Write seperator of array, before writing next element.
+   * User can proceed to call writer->Write to write next item
+   */
+  inline void WriteArraySeperator();
+  /*!
+   * \brief Write value into array.
+   * \param value The value of to be written.
+   * \tparam ValueType The value type to be written.
+   */
+  template<typename ValueType>
+  inline void WriteArrayItem(const ValueType &value);
+  /*!
+   * \brief Write value to json.
+   * \param value any STL or json readable that can be written.
+   * \tparam ValueType the data type to be write.
+   */
+  template<typename ValueType>
+  inline void Write(const ValueType &value);
+
+ private:
+  /*! \brief Output stream */
+  std::ostream *os_;
+  /*!
+   * \brief record how many element processed in
+   *  current array/object scope.
+   */
+  std::vector<size_t> scope_counter_;
+  /*! \brief Record whether current is a multiline scope */
+  std::vector<bool> scope_multi_line_;
+  /*!
+   * \brief Write seperating space and newlines
+   */
+  inline void WriteSeperator();
+};
+
+/*!
+ * \brief Helper class to read JSON into a class or struct object.
+ * \code
+ *  struct Param {
+ *    std::string name;
+ *    int value;
+ *    // define load function from JSON
+ *    inline void Load(dmlc::JSONReader *reader) {
+ *      dmlc::JSONStructReadHelper helper;
+ *      helper.DeclareField("name", &name);
+ *      helper.DeclareField("value", &value);
+ *      helper.ReadAllFields(reader);
+ *    }
+ *  };
+ * \endcode
+ */
+class JSONObjectReadHelper {
+ public:
+  /*!
+   * \brief Declare field of type T
+   * \param key the key of the of field.
+   * \param addr address of the data type.
+   * \tparam T the data type to be read, must be STL composition of JSON serializable.
+   */
+  template<typename T>
+  inline void DeclareField(const std::string &key, T *addr) {
+    DeclareFieldInternal(key, addr, false);
+  }
+  /*!
+   * \brief Declare optional field of type T
+   * \param key the key of the of field.
+   * \param addr address of the data type.
+   * \tparam T the data type to be read, must be STL composition of JSON serializable.
+   */
+  template<typename T>
+  inline void DeclareOptionalField(const std::string &key, T *addr) {
+    DeclareFieldInternal(key, addr, true);
+  }
+  /*!
+   * \brief Read in all the declared fields.
+   * \param reader the JSONReader to read the json.
+   */
+  inline void ReadAllFields(JSONReader *reader);
+
+ private:
+  /*!
+   * \brief Internal function to declare field.
+   * \param key the key of the of field.
+   * \param addr address of the data type.
+   * \param optional if set to true, no error will be reported if the key is not presented.
+   * \tparam T the data type to be read, must be STL composition of JSON serializable.
+   */
+  template<typename T>
+  inline void DeclareFieldInternal(const std::string &key, T *addr, bool optional);
+  /*!
+   * \brief The internal reader function.
+   * \param reader The reader to read.
+   * \param addr The memory address to read.
+   */
+  template<typename T>
+  inline static void ReaderFunction(JSONReader *reader, void *addr);
+  /*! \brief callback type to reader function */
+  typedef void (*ReadFunction)(JSONReader *reader, void *addr);
+  /*! \brief internal data entry */
+  struct Entry {
+    /*! \brief the reader function */
+    ReadFunction func;
+    /*! \brief the address to read */
+    void *addr;
+    /*! \brief whether it is optional */
+    bool optional;
+  };
+  /*! \brief the internal map of reader callbacks */
+  std::map<std::string, Entry> map_;
+};
+
+#define DMLC_JSON_ENABLE_ANY_VAR_DEF(KeyName)                  \
+  static ::dmlc::json::AnyJSONManager&  __make_AnyJSONType ## _ ## KeyName ## __
+
+/*!
+ * \def DMLC_JSON_ENABLE_ANY
+ * \brief Macro to enable save/load JSON of dmlc:: whose actual type is Type.
+ * Any type will be saved as json array [KeyName, content]
+ *
+ * \param Type The type to be registered.
+ * \param KeyName The Type key assigned to the type, must be same during load.
+ */
+#define DMLC_JSON_ENABLE_ANY(Type, KeyName)                             \
+  DMLC_STR_CONCAT(DMLC_JSON_ENABLE_ANY_VAR_DEF(KeyName), __COUNTER__) = \
+    ::dmlc::json::AnyJSONManager::Global()->EnableType<Type>(#KeyName) \
+
+//! \cond Doxygen_Suppress
+namespace json {
+
+/*!
+ * \brief generic serialization handler
+ * \tparam T the type to be serialized
+ */
+template<typename T>
+struct Handler;
+
+template<typename ValueType>
+struct NumericHandler {
+  inline static void Write(JSONWriter *writer, const ValueType &value) {
+    writer->WriteNumber<ValueType>(value);
+  }
+  inline static void Read(JSONReader *reader, ValueType *value) {
+    reader->ReadNumber<ValueType>(value);
+  }
+};
+
+template<typename ContainerType>
+struct ArrayHandler {
+  inline static void Write(JSONWriter *writer, const ContainerType &array) {
+    typedef typename ContainerType::value_type ElemType;
+    writer->BeginArray(array.size() > 10 || !dmlc::is_pod<ElemType>::value);
+    for (typename ContainerType::const_iterator it = array.begin();
+         it != array.end(); ++it) {
+      writer->WriteArrayItem(*it);
+    }
+    writer->EndArray();
+  }
+  inline static void Read(JSONReader *reader, ContainerType *array) {
+    typedef typename ContainerType::value_type ElemType;
+    array->clear();
+    reader->BeginArray();
+    while (reader->NextArrayItem()) {
+      ElemType value;
+      Handler<ElemType>::Read(reader, &value);
+      array->insert(array->end(), value);
+    }
+  }
+};
+
+template<typename ContainerType>
+struct MapHandler{
+  inline static void Write(JSONWriter *writer, const ContainerType &map) {
+    writer->BeginObject(map.size() > 1);
+    for (typename ContainerType::const_iterator it = map.begin(); it != map.end(); ++it) {
+      writer->WriteObjectKeyValue(it->first, it->second);
+    }
+    writer->EndObject();
+  }
+  inline static void Read(JSONReader *reader, ContainerType *map) {
+    typedef typename ContainerType::mapped_type ElemType;
+    map->clear();
+    reader->BeginObject();
+    std::string key;
+    while (reader->NextObjectItem(&key)) {
+      ElemType value;
+      reader->Read(&value);
+      (*map)[key] = value;
+    }
+  }
+};
+
+template<typename T>
+struct CommonJSONSerializer {
+  inline static void Write(JSONWriter *writer, const T &value) {
+    value.Save(writer);
+  }
+  inline static void Read(JSONReader *reader, T *value) {
+    value->Load(reader);
+  }
+};
+
+template<>
+struct Handler<std::string> {
+  inline static void Write(JSONWriter *writer, const std::string &value) {
+    writer->WriteString(value);
+  }
+  inline static void Read(JSONReader *reader, std::string *str) {
+    reader->ReadString(str);
+  }
+};
+
+template<typename T>
+struct Handler<std::vector<T> > : public ArrayHandler<std::vector<T> > {
+};
+
+template<typename K, typename V>
+struct Handler<std::pair<K, V> > {
+  inline static void Write(JSONWriter *writer, const std::pair<K, V> &kv) {
+    writer->BeginArray();
+    writer->WriteArrayItem(kv.first);
+    writer->WriteArrayItem(kv.second);
+    writer->EndArray();
+  }
+  inline static void Read(JSONReader *reader, std::pair<K, V> *kv) {
+    reader->BeginArray();
+    CHECK(reader->NextArrayItem())
+        << "Expect array of length 2";
+    Handler<K>::Read(reader, &(kv->first));
+    CHECK(reader->NextArrayItem())
+        << "Expect array of length 2";
+    Handler<V>::Read(reader, &(kv->second));
+    CHECK(!reader->NextArrayItem())
+        << "Expect array of length 2";
+  }
+};
+
+template<typename T>
+struct Handler<std::list<T> > : public ArrayHandler<std::list<T> > {
+};
+
+template<typename V>
+struct Handler<std::map<std::string, V> > : public MapHandler<std::map<std::string, V> > {
+};
+
+#if DMLC_USE_CXX11
+template<typename V>
+struct Handler<std::unordered_map<std::string, V> >
+    : public MapHandler<std::unordered_map<std::string, V> > {
+};
+#endif  // DMLC_USE_CXX11
+
+template<typename T>
+struct Handler {
+  inline static void Write(JSONWriter *writer, const T &data) {
+    typedef typename dmlc::IfThenElseType<dmlc::is_arithmetic<T>::value,
+                                          NumericHandler<T>,
+                                          CommonJSONSerializer<T> >::Type THandler;
+    THandler::Write(writer, data);
+  }
+  inline static void Read(JSONReader *reader, T *data) {
+    typedef typename dmlc::IfThenElseType<dmlc::is_arithmetic<T>::value,
+                                          NumericHandler<T>,
+                                          CommonJSONSerializer<T> >::Type THandler;
+    THandler::Read(reader, data);
+  }
+};
+
+#if DMLC_USE_CXX11
+// Manager to store json serialization strategy.
+class AnyJSONManager {
+ public:
+  template<typename T>
+  inline AnyJSONManager& EnableType(const std::string& type_name) {  // NOLINT(*)
+    std::type_index tp = std::type_index(typeid(T));
+    if (type_name_.count(tp) != 0) {
+      CHECK(type_name_.at(tp) == type_name)
+          << "Type has already been registered as another typename " << type_name_.at(tp);
+      return *this;
+    }
+    CHECK(type_map_.count(type_name) == 0)
+        << "Type name " << type_name << " already registered in registry";
+    Entry e;
+    e.read = ReadAny<T>;
+    e.write = WriteAny<T>;
+    type_name_[tp] = type_name;
+    type_map_[type_name] = e;
+    return *this;
+  }
+  // return global singleton
+  inline static AnyJSONManager* Global() {
+    static AnyJSONManager inst;
+    return &inst;
+  }
+
+ private:
+  AnyJSONManager() {}
+
+  template<typename T>
+  inline static void WriteAny(JSONWriter *writer, const any &data) {
+    writer->Write(dmlc::get<T>(data));
+  }
+  template<typename T>
+  inline static void ReadAny(JSONReader *reader, any* data) {
+    T temp;
+    reader->Read(&temp);
+    *data = std::move(temp);
+  }
+  // data entry to store vtable for any type
+  struct Entry {
+    void (*read)(JSONReader* reader, any *data);
+    void (*write)(JSONWriter* reader, const any& data);
+  };
+
+  template<typename T>
+  friend struct Handler;
+
+  std::unordered_map<std::type_index, std::string> type_name_;
+  std::unordered_map<std::string, Entry> type_map_;
+};
+
+template<>
+struct Handler<any> {
+  inline static void Write(JSONWriter *writer, const any &data) {
+    std::unordered_map<std::type_index, std::string>&
+        nmap = AnyJSONManager::Global()->type_name_;
+    std::type_index id = std::type_index(data.type());
+    auto it = nmap.find(id);
+    CHECK(it != nmap.end() && it->first == id)
+        << "Type " << id.name() << " has not been registered via DMLC_JSON_ENABLE_ANY";
+    std::string type_name = it->second;
+    AnyJSONManager::Entry e = AnyJSONManager::Global()->type_map_.at(type_name);
+    writer->BeginArray(false);
+    writer->WriteArrayItem(type_name);
+    writer->WriteArraySeperator();
+    e.write(writer, data);
+    writer->EndArray();
+  }
+  inline static void Read(JSONReader *reader, any *data) {
+    std::string type_name;
+    reader->BeginArray();
+    CHECK(reader->NextArrayItem()) << "invalid any json format";
+    Handler<std::string>::Read(reader, &type_name);
+    std::unordered_map<std::string, AnyJSONManager::Entry>&
+        tmap = AnyJSONManager::Global()->type_map_;
+    auto it = tmap.find(type_name);
+    CHECK(it != tmap.end() && it->first == type_name)
+        << "Typename " << type_name << " has not been registered via DMLC_JSON_ENABLE_ANY";
+    AnyJSONManager::Entry e = it->second;
+    CHECK(reader->NextArrayItem()) << "invalid any json format";
+    e.read(reader, data);
+    CHECK(!reader->NextArrayItem()) << "invalid any json format";
+  }
+};
+#endif  // DMLC_USE_CXX11
+
+}  // namespace json
+
+// implementations of JSONReader/Writer
+inline int JSONReader::NextNonSpace() {
+  int ch;
+  do {
+    ch = is_->get();
+    if (ch == '\n') ++line_count_n_;
+    if (ch == '\r') ++line_count_r_;
+  } while (isspace(ch));
+  return ch;
+}
+
+inline int JSONReader::PeekNextNonSpace() {
+  int ch;
+  while (true) {
+    ch = is_->peek();
+    if (ch == '\n') ++line_count_n_;
+    if (ch == '\r') ++line_count_r_;
+    if (!isspace(ch)) break;
+    is_->get();
+  }
+  return ch;
+}
+
+inline void JSONReader::ReadString(std::string *out_str) {
+  int ch = NextNonSpace();
+  CHECK_EQ(ch, '\"')
+      << "Error at" << line_info()
+      << ", Expect \'\"\' but get \'" << static_cast<char>(ch) << '\'';
+  std::ostringstream os;
+  while (true) {
+    ch = is_->get();
+    if (ch == '\\') {
+      char sch = static_cast<char>(is_->get());
+      switch (sch) {
+        case 'r': os << "\r"; break;
+        case 'n': os << "\n"; break;
+        case '\\': os << "\\"; break;
+        case '\t': os << "\t"; break;
+        case '\"': os << "\""; break;
+        default: LOG(FATAL) << "unknown string escape \\" << sch;
+      }
+    } else {
+      if (ch == '\"') break;
+      os << static_cast<char>(ch);
+    }
+    if (ch == EOF || ch == '\r' || ch == '\n') {
+      LOG(FATAL)
+          << "Error at" << line_info()
+          << ", Expect \'\"\' but reach end of line ";
+    }
+  }
+  *out_str = os.str();
+}
+
+template<typename ValueType>
+inline void JSONReader::ReadNumber(ValueType *out_value) {
+  *is_ >> *out_value;
+  CHECK(!is_->fail())
+      << "Error at" << line_info()
+      << ", Expect number";
+}
+
+inline void JSONReader::BeginObject() {
+  int ch = NextNonSpace();
+  CHECK_EQ(ch, '{')
+      << "Error at" << line_info()
+      << ", Expect \'{\' but get \'" << static_cast<char>(ch) << '\'';
+  scope_counter_.push_back(0);
+}
+
+inline void JSONReader::BeginArray() {
+  int ch = NextNonSpace();
+  CHECK_EQ(ch, '[')
+      << "Error at" << line_info()
+      << ", Expect \'{\' but get \'" << static_cast<char>(ch) << '\'';
+  scope_counter_.push_back(0);
+}
+
+inline bool JSONReader::NextObjectItem(std::string *out_key) {
+  bool next = true;
+  if (scope_counter_.back() != 0) {
+    int ch = NextNonSpace();
+    if (ch == EOF) {
+      next = false;
+    } else if (ch == '}') {
+      next = false;
+    } else {
+      CHECK_EQ(ch, ',')
+          << "Error at" << line_info()
+          << ", JSON object expect \'}\' or \',\' \'" << static_cast<char>(ch) << '\'';
+    }
+  } else {
+    int ch = PeekNextNonSpace();
+    if (ch == '}') {
+      is_->get();
+      next = false;
+    }
+  }
+  if (!next) {
+    scope_counter_.pop_back();
+    return false;
+  } else {
+    scope_counter_.back() += 1;
+    ReadString(out_key);
+    int ch = NextNonSpace();
+    CHECK_EQ(ch, ':')
+        << "Error at" << line_info()
+        << ", Expect \':\' but get \'" << static_cast<char>(ch) << '\'';
+    return true;
+  }
+}
+
+inline bool JSONReader::NextArrayItem() {
+  bool next = true;
+  if (scope_counter_.back() != 0) {
+    int ch = NextNonSpace();
+    if (ch == EOF) {
+      next = false;
+    } else if (ch == ']') {
+      next = false;
+    } else {
+      CHECK_EQ(ch, ',')
+          << "Error at" << line_info()
+          << ", JSON array expect \']\' or \',\'. Get \'" << static_cast<char>(ch) << "\' instead";
+    }
+  } else {
+    int ch = PeekNextNonSpace();
+    if (ch == ']') {
+      is_->get();
+      next = false;
+    }
+  }
+  if (!next) {
+    scope_counter_.pop_back();
+    return false;
+  } else {
+    scope_counter_.back() += 1;
+    return true;
+  }
+}
+
+template<typename ValueType>
+inline void JSONReader::Read(ValueType *out_value) {
+  json::Handler<ValueType>::Read(this, out_value);
+}
+
+inline void JSONWriter::WriteNoEscape(const std::string &s) {
+  *os_ << '\"' << s << '\"';
+}
+
+inline void JSONWriter::WriteString(const std::string &s) {
+  std::ostream &os = *os_;
+  os << '\"';
+  for (size_t i = 0; i < s.length(); ++i) {
+    char ch = s[i];
+    switch (ch) {
+      case '\r': os << "\\r"; break;
+      case '\n': os << "\\n"; break;
+      case '\\': os << "\\\\"; break;
+      case '\t': os << "\\t"; break;
+      case '\"': os << "\\\""; break;
+      default: os << ch;
+    }
+  }
+  os << '\"';
+}
+
+template<typename ValueType>
+inline void JSONWriter::WriteNumber(const ValueType &v) {
+  *os_ << v;
+}
+
+inline void JSONWriter::BeginArray(bool multi_line) {
+  *os_ << '[';
+  scope_multi_line_.push_back(multi_line);
+  scope_counter_.push_back(0);
+}
+
+inline void JSONWriter::EndArray() {
+  CHECK_NE(scope_multi_line_.size(), 0);
+  CHECK_NE(scope_counter_.size(), 0);
+  bool newline = scope_multi_line_.back();
+  size_t nelem = scope_counter_.back();
+  scope_multi_line_.pop_back();
+  scope_counter_.pop_back();
+  if (newline && nelem != 0) WriteSeperator();
+  *os_ << ']';
+}
+
+inline void JSONWriter::BeginObject(bool multi_line) {
+  *os_ << "{";
+  scope_multi_line_.push_back(multi_line);
+  scope_counter_.push_back(0);
+}
+
+inline void JSONWriter::EndObject() {
+  CHECK_NE(scope_multi_line_.size(), 0);
+  CHECK_NE(scope_counter_.size(), 0);
+  bool newline = scope_multi_line_.back();
+  size_t nelem = scope_counter_.back();
+  scope_multi_line_.pop_back();
+  scope_counter_.pop_back();
+  if (newline && nelem != 0) WriteSeperator();
+  *os_ << '}';
+}
+
+template<typename ValueType>
+inline void JSONWriter::WriteObjectKeyValue(const std::string &key,
+                                            const ValueType &value) {
+  std::ostream &os = *os_;
+  if (scope_counter_.back() == 0) {
+    WriteSeperator();
+    os << '\"' << key << "\": ";
+  } else {
+    os << ", ";
+    WriteSeperator();
+    os << '\"' << key << "\": ";
+  }
+  scope_counter_.back() += 1;
+  json::Handler<ValueType>::Write(this, value);
+}
+
+inline void JSONWriter::WriteArraySeperator() {
+  std::ostream &os = *os_;
+  if (scope_counter_.back() != 0) {
+    os << ", ";
+  }
+  scope_counter_.back() += 1;
+  WriteSeperator();
+}
+
+template<typename ValueType>
+inline void JSONWriter::WriteArrayItem(const ValueType &value) {
+  this->WriteArraySeperator();
+  json::Handler<ValueType>::Write(this, value);
+}
+
+template<typename ValueType>
+inline void JSONWriter::Write(const ValueType &value) {
+  size_t nscope = scope_multi_line_.size();
+  json::Handler<ValueType>::Write(this, value);
+  CHECK_EQ(nscope, scope_multi_line_.size())
+      << "Uneven scope, did you call EndArray/EndObject after each BeginObject/Array?";
+}
+
+inline void JSONWriter::WriteSeperator() {
+  if (scope_multi_line_.size() == 0 || scope_multi_line_.back()) {
+    *os_ << '\n' << std::string(scope_multi_line_.size() * 2, ' ');
+  }
+}
+
+inline void JSONObjectReadHelper::ReadAllFields(JSONReader *reader) {
+  reader->BeginObject();
+  std::map<std::string, int> visited;
+  std::string key;
+  while (reader->NextObjectItem(&key)) {
+    if (map_.count(key) != 0) {
+      Entry e = map_[key];
+      (*e.func)(reader, e.addr);
+      visited[key] = 0;
+    } else {
+      std::ostringstream os;
+      os << "JSONReader: Unknown field " << key << ", candidates are: \n";
+      for (std::map<std::string, Entry>::iterator
+               it = map_.begin(); it != map_.end(); ++it) {
+        os << '\"' <<it->first << "\"\n";
+      }
+      LOG(FATAL) << os.str();
+    }
+  }
+  if (visited.size() != map_.size()) {
+    for (std::map<std::string, Entry>::iterator
+             it = map_.begin(); it != map_.end(); ++it) {
+      if (it->second.optional) continue;
+      CHECK_NE(visited.count(it->first), 0)
+          << "JSONReader: Missing field \"" << it->first << "\"\n At "
+          << reader->line_info();
+    }
+  }
+}
+
+template<typename T>
+inline void JSONObjectReadHelper::ReaderFunction(JSONReader *reader, void *addr) {
+  json::Handler<T>::Read(reader, static_cast<T*>(addr));
+}
+
+template<typename T>
+inline void JSONObjectReadHelper::
+DeclareFieldInternal(const std::string &key, T *addr, bool optional) {
+  CHECK_EQ(map_.count(key), 0)
+      << "Adding duplicate field " << key;
+  Entry e;
+  e.func = ReaderFunction<T>;
+  e.addr = static_cast<void*>(addr);
+  e.optional = optional;
+  map_[key] = e;
+}
+
+//! \endcond
+}  // namespace dmlc
+#endif  // DMLC_JSON_H_
diff --git a/nnvm/include/dmlc/logging.h b/nnvm/include/dmlc/logging.h
new file mode 100644
index 000000000000..afdc63955634
--- /dev/null
+++ b/nnvm/include/dmlc/logging.h
@@ -0,0 +1,262 @@
+/*!
+ *  Copyright (c) 2015 by Contributors
+ * \file logging.h
+ * \brief defines logging macros of dmlc
+ *  allows use of GLOG, fall back to internal
+ *  implementation when disabled
+ */
+#ifndef DMLC_LOGGING_H_
+#define DMLC_LOGGING_H_
+#include <cstdio>
+#include <cstdlib>
+#include <string>
+#include <vector>
+#include <stdexcept>
+#include "./base.h"
+
+namespace dmlc {
+/*!
+ * \brief exception class that will be thrown by
+ *  default logger if DMLC_LOG_FATAL_THROW == 1
+ */
+struct Error : public std::runtime_error {
+  /*!
+   * \brief constructor
+   * \param s the error message
+   */
+  explicit Error(const std::string &s) : std::runtime_error(s) {}
+};
+}  // namespace dmlc
+
+#if DMLC_USE_GLOG
+#include <glog/logging.h>
+
+namespace dmlc {
+/*!
+ * \brief optionally redirect to google's init log
+ * \param argv0 The arguments.
+ */
+inline void InitLogging(const char* argv0) {
+  google::InitGoogleLogging(argv0);
+}
+}  // namespace dmlc
+
+#else
+// use a light version of glog
+#include <assert.h>
+#include <iostream>
+#include <sstream>
+#include <ctime>
+
+#if defined(_MSC_VER)
+#pragma warning(disable : 4722)
+#endif
+
+namespace dmlc {
+inline void InitLogging(const char* argv0) {
+  // DO NOTHING
+}
+
+// Always-on checking
+#define CHECK(x)                                           \
+  if (!(x))                                                \
+    dmlc::LogMessageFatal(__FILE__, __LINE__).stream() << "Check "  \
+      "failed: " #x << ' '
+#define CHECK_LT(x, y) CHECK((x) < (y))
+#define CHECK_GT(x, y) CHECK((x) > (y))
+#define CHECK_LE(x, y) CHECK((x) <= (y))
+#define CHECK_GE(x, y) CHECK((x) >= (y))
+#define CHECK_EQ(x, y) CHECK((x) == (y))
+#define CHECK_NE(x, y) CHECK((x) != (y))
+#define CHECK_NOTNULL(x) \
+  ((x) == NULL ? dmlc::LogMessageFatal(__FILE__, __LINE__).stream() << "Check  notnull: "  #x << ' ', (x) : (x)) // NOLINT(*)
+// Debug-only checking.
+#ifdef NDEBUG
+#define DCHECK(x) \
+  while (false) CHECK(x)
+#define DCHECK_LT(x, y) \
+  while (false) CHECK((x) < (y))
+#define DCHECK_GT(x, y) \
+  while (false) CHECK((x) > (y))
+#define DCHECK_LE(x, y) \
+  while (false) CHECK((x) <= (y))
+#define DCHECK_GE(x, y) \
+  while (false) CHECK((x) >= (y))
+#define DCHECK_EQ(x, y) \
+  while (false) CHECK((x) == (y))
+#define DCHECK_NE(x, y) \
+  while (false) CHECK((x) != (y))
+#else
+#define DCHECK(x) CHECK(x)
+#define DCHECK_LT(x, y) CHECK((x) < (y))
+#define DCHECK_GT(x, y) CHECK((x) > (y))
+#define DCHECK_LE(x, y) CHECK((x) <= (y))
+#define DCHECK_GE(x, y) CHECK((x) >= (y))
+#define DCHECK_EQ(x, y) CHECK((x) == (y))
+#define DCHECK_NE(x, y) CHECK((x) != (y))
+#endif  // NDEBUG
+
+#if DMLC_LOG_CUSTOMIZE
+#define LOG_INFO dmlc::CustomLogMessage(__FILE__, __LINE__)
+#else
+#define LOG_INFO dmlc::LogMessage(__FILE__, __LINE__)
+#endif
+#define LOG_ERROR LOG_INFO
+#define LOG_WARNING LOG_INFO
+#define LOG_FATAL dmlc::LogMessageFatal(__FILE__, __LINE__)
+#define LOG_QFATAL LOG_FATAL
+
+// Poor man version of VLOG
+#define VLOG(x) LOG_INFO.stream()
+
+#define LOG(severity) LOG_##severity.stream()
+#define LG LOG_INFO.stream()
+#define LOG_IF(severity, condition) \
+  !(condition) ? (void)0 : dmlc::LogMessageVoidify() & LOG(severity)
+
+#ifdef NDEBUG
+#define LOG_DFATAL LOG_ERROR
+#define DFATAL ERROR
+#define DLOG(severity) true ? (void)0 : dmlc::LogMessageVoidify() & LOG(severity)
+#define DLOG_IF(severity, condition) \
+  (true || !(condition)) ? (void)0 : dmlc::LogMessageVoidify() & LOG(severity)
+#else
+#define LOG_DFATAL LOG_FATAL
+#define DFATAL FATAL
+#define DLOG(severity) LOG(severity)
+#define DLOG_IF(severity, condition) LOG_IF(severity, condition)
+#endif
+
+// Poor man version of LOG_EVERY_N
+#define LOG_EVERY_N(severity, n) LOG(severity)
+
+class DateLogger {
+ public:
+  DateLogger() {
+#if defined(_MSC_VER)
+    _tzset();
+#endif
+  }
+  const char* HumanDate() {
+#if defined(_MSC_VER)
+    _strtime_s(buffer_, sizeof(buffer_));
+#else
+    time_t time_value = time(NULL);
+    struct tm *pnow;
+#if !defined(_WIN32)
+    struct tm now;
+    pnow = localtime_r(&time_value, &now);
+#else
+    pnow = localtime(&time_value);  // NOLINT(*)
+#endif
+    snprintf(buffer_, sizeof(buffer_), "%02d:%02d:%02d",
+             pnow->tm_hour, pnow->tm_min, pnow->tm_sec);
+#endif
+    return buffer_;
+  }
+
+ private:
+  char buffer_[9];
+};
+
+class LogMessage {
+ public:
+  LogMessage(const char* file, int line)
+      :
+#ifdef __ANDROID__
+        log_stream_(std::cout)
+#else
+        log_stream_(std::cerr)
+#endif
+  {
+    log_stream_ << "[" << pretty_date_.HumanDate() << "] " << file << ":"
+                << line << ": ";
+  }
+  ~LogMessage() { log_stream_ << '\n'; }
+  std::ostream& stream() { return log_stream_; }
+
+ protected:
+  std::ostream& log_stream_;
+
+ private:
+  DateLogger pretty_date_;
+  LogMessage(const LogMessage&);
+  void operator=(const LogMessage&);
+};
+
+// customized logger that can allow user to define where to log the message.
+class CustomLogMessage {
+ public:
+  CustomLogMessage(const char* file, int line) {
+    log_stream_ << "[" << DateLogger().HumanDate() << "] " << file << ":"
+                << line << ": ";
+  }
+  ~CustomLogMessage() {
+    Log(log_stream_.str());
+  }
+  std::ostream& stream() { return log_stream_; }
+  /*!
+   * \brief customized logging of the message.
+   * This function won't be implemented by libdmlc
+   * \param msg The message to be logged.
+   */
+  static void Log(const std::string& msg);
+
+ private:
+  std::ostringstream log_stream_;
+};
+
+#if DMLC_LOG_FATAL_THROW == 0
+class LogMessageFatal : public LogMessage {
+ public:
+  LogMessageFatal(const char* file, int line) : LogMessage(file, line) {}
+  ~LogMessageFatal() {
+    log_stream_ << "\n";
+    abort();
+  }
+
+ private:
+  LogMessageFatal(const LogMessageFatal&);
+  void operator=(const LogMessageFatal&);
+};
+#else
+class LogMessageFatal {
+ public:
+  LogMessageFatal(const char* file, int line) {
+    log_stream_ << "[" << pretty_date_.HumanDate() << "] " << file << ":"
+                << line << ": ";
+  }
+  std::ostringstream &stream() { return log_stream_; }
+  ~LogMessageFatal() DMLC_THROW_EXCEPTION {
+    // throwing out of destructor is evil
+    // hopefully we can do it here
+    // also log the message before throw
+#if DMLC_LOG_BEFORE_THROW
+    LOG(ERROR) << log_stream_.str();
+#endif
+    throw Error(log_stream_.str());
+  }
+
+ private:
+  std::ostringstream log_stream_;
+  DateLogger pretty_date_;
+  LogMessageFatal(const LogMessageFatal&);
+  void operator=(const LogMessageFatal&);
+};
+#endif
+
+// This class is used to explicitly ignore values in the conditional
+// logging macros.  This avoids compiler warnings like "value computed
+// is not used" and "statement has no effect".
+class LogMessageVoidify {
+ public:
+  LogMessageVoidify() {}
+  // This has to be an operator with a precedence lower than << but
+  // higher than "?:". See its usage.
+  void operator&(std::ostream&) {}
+};
+
+}  // namespace dmlc
+
+#endif
+#endif  // DMLC_LOGGING_H_
diff --git a/nnvm/include/dmlc/memory.h b/nnvm/include/dmlc/memory.h
new file mode 100644
index 000000000000..3a2b9b07988f
--- /dev/null
+++ b/nnvm/include/dmlc/memory.h
@@ -0,0 +1,261 @@
+/*!
+ *  Copyright (c) 2015 by Contributors
+ * \file memory.h
+ * \brief Additional memory hanlding utilities.
+ */
+#ifndef DMLC_MEMORY_H_
+#define DMLC_MEMORY_H_
+
+#include <vector>
+#include "./base.h"
+#include "./logging.h"
+#include "./thread_local.h"
+
+namespace dmlc {
+
+/*!
+ * \brief A memory pool that allocate memory of fixed size and alignment.
+ * \tparam size The size of each piece.
+ * \tparam align The alignment requirement of the memory.
+ */
+template<size_t size, size_t align>
+class MemoryPool {
+ public:
+  /*! \brief constructor */
+  MemoryPool() {
+    static_assert(align % alignof(LinkedList) == 0,
+                  "alignment requirement failed.");
+    curr_page_.reset(new Page());
+  }
+  /*! \brief allocate a new memory of size */
+  inline void* allocate() {
+    if (head_ != nullptr) {
+      LinkedList* ret = head_;
+      head_ = head_->next;
+      return ret;
+    } else {
+      if (page_ptr_ < kPageSize) {
+        return &(curr_page_->data[page_ptr_++]);
+      } else {
+        allocated_.push_back(std::move(curr_page_));
+        curr_page_.reset(new Page());
+        page_ptr_ = 1;
+        return &(curr_page_->data[0]);
+      }
+    }
+  }
+  /*!
+   * \brief deallocate a piece of memory
+   * \param p The pointer to the memory to be de-allocated.
+   */
+  inline void deallocate(void* p) {
+    LinkedList* ptr = static_cast<LinkedList*>(p);
+    ptr->next = head_;
+    head_ = ptr;
+  }
+
+ private:
+  // page size of each member
+  static const int kPageSize = ((1 << 22) / size);
+  // page to be requested.
+  struct Page {
+    typename std::aligned_storage<size, align>::type data[kPageSize];
+  };
+  // internal linked list structure.
+  struct LinkedList {
+    LinkedList* next{nullptr};
+  };
+  // head of free list
+  LinkedList* head_{nullptr};
+  // current free page
+  std::unique_ptr<Page> curr_page_;
+  // pointer to the current free page position.
+  size_t page_ptr_{0};
+  // allocated pages.
+  std::vector<std::unique_ptr<Page> > allocated_;
+};
+
+
+/*!
+ * \brief A thread local allocator that get memory from a threadlocal memory pool.
+ * This is suitable to allocate objects that do not cross thread.
+ * \tparam T the type of the data to be allocated.
+ */
+template<typename T>
+class ThreadlocalAllocator {
+ public:
+  /*! \brief pointer type */
+  typedef T* pointer;
+  /*! \brief const pointer type */
+  typedef const T* const_ptr;
+  /*! \brief value type */
+  typedef T value_type;
+  /*! \brief default constructor */
+  ThreadlocalAllocator() {}
+  /*!
+   * \brief constructor from another allocator
+   * \param other another allocator
+   * \tparam U another type
+   */
+  template<typename U>
+  ThreadlocalAllocator(const ThreadlocalAllocator<U>& other) {}
+  /*!
+   * \brief allocate memory
+   * \param n number of blocks
+   * \return an uninitialized memory of type T.
+   */
+  inline T* allocate(size_t n) {
+    CHECK_EQ(n, 1);
+    typedef ThreadLocalStore<MemoryPool<sizeof(T), alignof(T)> > Store;
+    return static_cast<T*>(Store::Get()->allocate());
+  }
+  /*!
+   * \brief deallocate memory
+   * \param p a memory to be returned.
+   * \param n number of blocks
+   */
+  inline void deallocate(T* p, size_t n) {
+    CHECK_EQ(n, 1);
+    typedef ThreadLocalStore<MemoryPool<sizeof(T), alignof(T)> > Store;
+    Store::Get()->deallocate(p);
+  }
+};
+
+
+/*!
+ * \brief a shared pointer like type that allocate object
+ *   from a threadlocal object pool. This object is not thread-safe
+ *   but can be faster than shared_ptr in certain usecases.
+ * \tparam T the data type.
+ */
+template<typename T>
+struct ThreadlocalSharedPtr {
+ public:
+  /*! \brief default constructor */
+  ThreadlocalSharedPtr() : block_(nullptr) {}
+  /*!
+   * \brief constructor from nullptr
+   * \param other the nullptr type
+   */
+  ThreadlocalSharedPtr(std::nullptr_t other) : block_(nullptr) {}  // NOLINT(*)
+  /*!
+   * \brief copy constructor
+   * \param other another pointer.
+   */
+  ThreadlocalSharedPtr(const ThreadlocalSharedPtr<T>& other)
+      : block_(other.block_) {
+    IncRef(block_);
+  }
+  /*!
+   * \brief move constructor
+   * \param other another pointer.
+   */
+  ThreadlocalSharedPtr(ThreadlocalSharedPtr<T>&& other)
+      : block_(other.block_) {
+    other.block_ = nullptr;
+  }
+  /*!
+   * \brief destructor
+   */
+  ~ThreadlocalSharedPtr() {
+    DecRef(block_);
+  }
+  /*!
+   * \brief move assignment
+   * \param other another object to be assigned.
+   * \return self.
+   */
+  inline ThreadlocalSharedPtr<T>& operator=(ThreadlocalSharedPtr<T>&& other) {
+    DecRef(block_);
+    block_ = other.block_;
+    other.block_ = nullptr;
+    return *this;
+  }
+  /*!
+   * \brief copy assignment
+   * \param other another object to be assigned.
+   * \return self.
+   */
+  inline ThreadlocalSharedPtr<T> &operator=(const ThreadlocalSharedPtr<T>& other) {
+    DecRef(block_);
+    block_ = other.block_;
+    IncRef(block_);
+    return *this;
+  }
+  /*! \brief check if nullptr */
+  inline bool operator==(std::nullptr_t other) const {
+    return block_ == nullptr;
+  }
+  /*!
+   * \return get the pointer content.
+   */
+  inline T* get() const {
+    if (block_ == nullptr) return nullptr;
+    return reinterpret_cast<T*>(&(block_->data));
+  }
+  /*!
+   * \brief reset the pointer to nullptr.
+   */
+  inline void reset() {
+    DecRef(block_);
+    block_ = nullptr;
+  }
+  /*! \return if use_count == 1*/
+  inline bool unique() const {
+    if (block_ == nullptr) return false;
+    return block_->use_count_ == 1;
+  }
+  /*! \return dereference pointer */
+  inline T* operator*() const {
+    return reinterpret_cast<T*>(&(block_->data));
+  }
+  /*! \return dereference pointer */
+  inline T* operator->() const {
+    return reinterpret_cast<T*>(&(block_->data));
+  }
+  /*!
+   * \brief create a new space from threadlocal storage and return it.
+   * \tparam Args the arguments.
+   * \param args The input argument
+   * \return the allocated pointer.
+   */
+  template <typename... Args>
+  inline static ThreadlocalSharedPtr<T> Create(Args&&... args) {
+    ThreadlocalAllocator<RefBlock> arena;
+    ThreadlocalSharedPtr<T> p;
+    p.block_ = arena.allocate(1);
+    p.block_->use_count_ = 1;
+    new (&(p.block_->data)) T(std::forward<Args>(args)...);
+    return p;
+  }
+
+ private:
+  // internal reference block
+  struct RefBlock {
+    typename std::aligned_storage<sizeof(T), alignof(T)>::type data;
+    unsigned use_count_;
+  };
+  // decrease ref counter
+  inline static void DecRef(RefBlock* block) {
+    if (block != nullptr) {
+      if (--block->use_count_ == 0) {
+        ThreadlocalAllocator<RefBlock> arena;
+        T* dptr = reinterpret_cast<T*>(&(block->data));
+        dptr->~T();
+        arena.deallocate(block, 1);
+      }
+    }
+  }
+  // increase ref counter
+  inline static void IncRef(RefBlock* block) {
+    if (block != nullptr) {
+      ++block->use_count_;
+    }
+  }
+  // internal block
+  RefBlock *block_;
+};
+
+}  // namespace dmlc
+
+#endif  // DMLC_MEMORY_H_
diff --git a/nnvm/include/dmlc/parameter.h b/nnvm/include/dmlc/parameter.h
new file mode 100644
index 000000000000..4ff99f860cc3
--- /dev/null
+++ b/nnvm/include/dmlc/parameter.h
@@ -0,0 +1,831 @@
+/*!
+ * Copyright (c) 2015 by Contributors
+ * \file parameter.h
+ * \brief Provide lightweight util to do parameter setup and checking.
+ */
+#ifndef DMLC_PARAMETER_H_
+#define DMLC_PARAMETER_H_
+
+#include <cstddef>
+#include <cstdlib>
+#include <sstream>
+#include <limits>
+#include <map>
+#include <set>
+#include <typeinfo>
+#include <string>
+#include <vector>
+#include <algorithm>
+#include <utility>
+#include <iostream>
+#include "./base.h"
+#include "./json.h"
+#include "./logging.h"
+#include "./type_traits.h"
+
+namespace dmlc {
+// this file is backward compatible with non-c++11
+/*! \brief Error throwed by parameter checking */
+struct ParamError : public dmlc::Error {
+  /*!
+   * \brief constructor
+   * \param msg error message
+   */
+  explicit ParamError(const std::string &msg)
+      : dmlc::Error(msg) {}
+};
+
+/*!
+ * \brief Get environment variable with default.
+ * \param key the name of environment variable.
+ * \param default_value the default value of environment vriable.
+ * \return The value received
+ */
+template<typename ValueType>
+inline ValueType GetEnv(const char *key,
+                        ValueType default_value);
+
+/*! \brief internal namespace for parameter manangement */
+namespace parameter {
+// forward declare ParamManager
+class ParamManager;
+// forward declare FieldAccessEntry
+class FieldAccessEntry;
+// forward declare FieldEntry
+template<typename DType>
+class FieldEntry;
+// forward declare ParamManagerSingleton
+template<typename PType>
+struct ParamManagerSingleton;
+}  // namespace parameter
+/*!
+ * \brief Information about a parameter field in string representations.
+ */
+struct ParamFieldInfo {
+  /*! \brief name of the field */
+  std::string name;
+  /*! \brief type of the field in string format */
+  std::string type;
+  /*!
+   * \brief detailed type information string
+   *  This include the default value, enum constran and typename.
+   */
+  std::string type_info_str;
+  /*! \brief detailed description of the type */
+  std::string description;
+};
+
+/*!
+ * \brief Parameter is the base type every parameter struct should inheritate from
+ * The following code is a complete example to setup parameters.
+ * \code
+ *   struct Param : public dmlc::Parameter<Param> {
+ *     float learning_rate;
+ *     int num_hidden;
+ *     std::string name;
+ *     // declare parameters in header file
+ *     DMLC_DECLARE_PARAMETER(Param) {
+ *       DMLC_DECLARE_FIELD(num_hidden).set_range(0, 1000);
+ *       DMLC_DECLARE_FIELD(learning_rate).set_default(0.01f);
+ *       DMLC_DECLARE_FIELD(name).set_default("hello");
+ *     }
+ *   };
+ *   // register it in cc file
+ *   DMLC_REGISTER_PARAMETER(Param);
+ * \endcode
+ *
+ *  After that, the Param struct will get all the functions defined in Parameter.
+ * \tparam PType the type of parameter struct
+ *
+ * \sa DMLC_DECLARE_FIELD, DMLC_REGISTER_PARAMETER, DMLC_DECLARE_PARAMETER
+ */
+template<typename PType>
+struct Parameter {
+ public:
+  /*!
+   * \brief initialize the parameter by keyword arguments.
+   *  This function will initialize the parameter struct, check consistency
+   *  and throw error if something wrong happens.
+   *
+   * \param kwargs map of keyword arguments, or vector of pairs
+   * \tparam Container container type
+   * \throw ParamError when something go wrong.
+   */
+  template<typename Container>
+  inline void Init(const Container &kwargs) {
+    PType::__MANAGER__()->RunInit(static_cast<PType*>(this),
+                                  kwargs.begin(), kwargs.end(), NULL);
+  }
+  /*!
+   * \brief initialize the parameter by keyword arguments.
+   *  This is same as Init, but allow unknown arguments.
+   *
+   * \param kwargs map of keyword arguments, or vector of pairs
+   * \tparam Container container type
+   * \throw ParamError when something go wrong.
+   * \return vector of pairs of unknown arguments.
+   */
+  template<typename Container>
+  inline std::vector<std::pair<std::string, std::string> >
+  InitAllowUnknown(const Container &kwargs) {
+    std::vector<std::pair<std::string, std::string> > unknown;
+    PType::__MANAGER__()->RunInit(static_cast<PType*>(this),
+                                  kwargs.begin(), kwargs.end(), &unknown);
+    return unknown;
+  }
+  /*!
+   * \brief Return a dictionary representation of the parameters
+   * \return A dictionary that maps key -> value
+   */
+  inline std::map<std::string, std::string> __DICT__() const {
+    std::vector<std::pair<std::string, std::string> > vec
+        = PType::__MANAGER__()->GetDict(this->head());
+    return std::map<std::string, std::string>(vec.begin(), vec.end());
+  }
+  /*!
+   * \brief Write the parameters in JSON format.
+   * \param writer JSONWriter used for writing.
+   */
+  inline void Save(dmlc::JSONWriter *writer) const {
+    writer->Write(this->__DICT__());
+  }
+  /*!
+   * \brief Load the parameters from JSON.
+   * \param reader JSONReader used for loading.
+   * \throw ParamError when something go wrong.
+   */
+  inline void Load(dmlc::JSONReader *reader) {
+    std::map<std::string, std::string> kwargs;
+    reader->Read(&kwargs);
+    this->Init(kwargs);
+  }
+  /*!
+   * \brief Get the fields of the parameters.
+   * \return List of ParamFieldInfo of each field.
+   */
+  inline static std::vector<ParamFieldInfo> __FIELDS__() {
+    return PType::__MANAGER__()->GetFieldInfo();
+  }
+  /*!
+   * \brief Print docstring of the parameter
+   * \return the printed docstring
+   */
+  inline static std::string __DOC__() {
+    std::ostringstream os;
+    PType::__MANAGER__()->PrintDocString(os);
+    return os.str();
+  }
+
+ protected:
+  /*!
+   * \brief internal function to allow declare of a parameter memember
+   * \param manager the parameter manager
+   * \param key the key name of the parameter
+   * \param ref the reference to the parameter in the struct.
+   */
+  template<typename DType>
+  inline parameter::FieldEntry<DType>& DECLARE(
+      parameter::ParamManagerSingleton<PType> *manager,
+      const std::string &key, DType &ref) { // NOLINT(*)
+    parameter::FieldEntry<DType> *e =
+        new parameter::FieldEntry<DType>();
+    e->Init(key, this->head(), ref);
+    manager->manager.AddEntry(key, e);
+    return *e;
+  }
+
+ private:
+  /*! \return Get head pointer of child structure */
+  inline PType *head() const {
+    return static_cast<PType*>(const_cast<Parameter<PType>*>(this));
+  }
+};
+
+//! \cond Doxygen_Suppress
+/*!
+ * \brief macro used to declare parameter
+ *
+ * Example:
+ * \code
+ *   struct Param : public dmlc::Parameter<Param> {
+ *     // declare parameters in header file
+ *     DMLC_DECLARE_PARAMETER(Param) {
+ *        // details of declarations
+ *     }
+ *   };
+ * \endcode
+ *
+ * This macro need to be put in a source file so that registeration only happens once.
+ * Refer to example code in Parameter for details
+ *
+ * \param PType the name of parameter struct.
+ * \sa Parameter
+ */
+#define DMLC_DECLARE_PARAMETER(PType)                                   \
+  static ::dmlc::parameter::ParamManager *__MANAGER__();                \
+  inline void __DECLARE__(::dmlc::parameter::ParamManagerSingleton<PType> *manager) \
+
+/*!
+ * \brief macro to declare fields
+ * \param FieldName the name of the field.
+ */
+#define DMLC_DECLARE_FIELD(FieldName)  this->DECLARE(manager, #FieldName, FieldName)
+
+/*!
+ * \brief macro to declare alias of a fields
+ * \param FieldName the name of the field.
+ * \param AliasName the name of the alias, must be declared after the field is declared.
+ */
+#define DMLC_DECLARE_ALIAS(FieldName, AliasName)  manager->manager.AddAlias(#FieldName, #AliasName)
+
+/*!
+ * \brief Macro used to register parameter.
+ *
+ * This macro need to be put in a source file so that registeration only happens once.
+ * Refer to example code in Parameter for details
+ * \param PType the type of parameter struct.
+ * \sa Parameter
+ */
+#define DMLC_REGISTER_PARAMETER(PType)                                  \
+  ::dmlc::parameter::ParamManager *PType::__MANAGER__() {               \
+    static ::dmlc::parameter::ParamManagerSingleton<PType> inst(#PType); \
+    return &inst.manager;                                               \
+  }                                                                     \
+  static ::dmlc::parameter::ParamManager &__make__ ## PType ## ParamManager__ = \
+      (*PType::__MANAGER__())                                           \
+
+//! \endcond
+/*!
+ * \brief internal namespace for parameter manangement
+ * There is no need to use it directly in normal case
+ */
+namespace parameter {
+/*!
+ * \brief FieldAccessEntry interface to help manage the parameters
+ *  Each entry can be used to access one parameter in the Parameter struct.
+ *
+ *  This is an internal interface used that is used to manage parameters
+ */
+class FieldAccessEntry {
+ public:
+  FieldAccessEntry()
+      : has_default_(false) {}
+  /*! \brief destructor */
+  virtual ~FieldAccessEntry() {}
+  /*!
+   * \brief set the default value.
+   * \param head the pointer to the head of the struct
+   * \throw error if no default is presented
+   */
+  virtual void SetDefault(void *head) const = 0;
+  /*!
+   * \brief set the parameter by string value
+   * \param head the pointer to the head of the struct
+   * \param value the value to be set
+   */
+  virtual void Set(void *head, const std::string &value) const = 0;
+  // check if value is OK
+  virtual void Check(void *head) const {}
+  /*!
+   * \brief get the string representation of value.
+   * \param head the pointer to the head of the struct
+   */
+  virtual std::string GetStringValue(void *head) const = 0;
+  /*!
+   * \brief Get field information
+   * \return the corresponding field information
+   */
+  virtual ParamFieldInfo GetFieldInfo() const = 0;
+
+ protected:
+  /*! \brief whether this parameter have default value */
+  bool has_default_;
+  /*! \brief positional index of parameter in struct */
+  size_t index_;
+  /*! \brief parameter key name */
+  std::string key_;
+  /*! \brief parameter type */
+  std::string type_;
+  /*! \brief description of the parameter */
+  std::string description_;
+  /*!
+   * \brief print string representation of default value
+   * \parma os the stream to print the docstring to.
+   */
+  virtual void PrintDefaultValueString(std::ostream &os) const = 0;  // NOLINT(*)
+  // allow ParamManager to modify self
+  friend class ParamManager;
+};
+
+/*!
+ * \brief manager class to handle parameter setting for each type
+ *  An manager will be created for each parameter types.
+ */
+class ParamManager {
+ public:
+  /*! \brief destructor */
+  ~ParamManager() {
+    for (size_t i = 0; i < entry_.size(); ++i) {
+      delete entry_[i];
+    }
+  }
+  /*!
+   * \brief find the access entry by parameter key
+   * \param key the key of the parameter.
+   * \return pointer to FieldAccessEntry, NULL if nothing is found.
+   */
+  inline FieldAccessEntry *Find(const std::string &key) const {
+    std::map<std::string, FieldAccessEntry*>::const_iterator it =
+        entry_map_.find(key);
+    if (it == entry_map_.end()) return NULL;
+    return it->second;
+  }
+  /*!
+   * \brief set parameter by keyword arguments.
+   * \param head head to the parameter field.
+   * \param begin begin iterator of original kwargs
+   * \param end end iterator of original kwargs
+   * \param unknown_args optional, used to hold unknown arguments
+   *          When it is specified, unknown arguments will be stored into here, instead of raise an error
+   * \tparam RandomAccessIterator iterator type
+   * \throw ParamError when there is unknown argument and unknown_args == NULL, or required argument is missing.
+   */
+  template<typename RandomAccessIterator>
+  inline void RunInit(void *head,
+                      RandomAccessIterator begin,
+                      RandomAccessIterator end,
+                      std::vector<std::pair<std::string, std::string> > *unknown_args) const {
+    std::set<FieldAccessEntry*> selected_args;
+    for (RandomAccessIterator it = begin; it != end; ++it) {
+      FieldAccessEntry *e = Find(it->first);
+      if (e != NULL) {
+        e->Set(head, it->second);
+        e->Check(head);
+        selected_args.insert(e);
+      } else {
+        if (unknown_args != NULL) {
+          unknown_args->push_back(*it);
+        } else {
+          std::ostringstream os;
+          os << "Cannot find argument \'" << it->first << "\', Possible Arguments:\n";
+          os << "----------------\n";
+          PrintDocString(os);
+          throw dmlc::ParamError(os.str());
+        }
+      }
+    }
+
+    for (std::map<std::string, FieldAccessEntry*>::const_iterator it = entry_map_.begin();
+         it != entry_map_.end(); ++it) {
+      if (selected_args.count(it->second) == 0) {
+        it->second->SetDefault(head);
+      }
+    }
+  }
+  /*!
+   * \brief internal function to add entry to manager,
+   *  The manager will take ownership of the entry.
+   * \param key the key to the parameters
+   * \param e the pointer to the new entry.
+   */
+  inline void AddEntry(const std::string &key, FieldAccessEntry *e) {
+    e->index_ = entry_.size();
+    // TODO(bing) better error message
+    if (entry_map_.count(key) != 0) {
+      LOG(FATAL) << "key " << key << " has already been registered in " << name_;
+    }
+    entry_.push_back(e);
+    entry_map_[key] = e;
+  }
+  /*!
+   * \brief internal function to add entry to manager,
+   *  The manager will take ownership of the entry.
+   * \param key the key to the parameters
+   * \param e the pointer to the new entry.
+   */
+  inline void AddAlias(const std::string& field, const std::string& alias) {
+    if (entry_map_.count(field) == 0) {
+      LOG(FATAL) << "key " << field << " has not been registered in " << name_;
+    }
+    if (entry_map_.count(alias) != 0) {
+      LOG(FATAL) << "Alias " << alias << " has already been registered in " << name_;
+    }
+    entry_map_[alias] = entry_map_[field];
+  }
+  /*!
+   * \brief set the name of parameter manager
+   * \param name the name to set
+   */
+  inline void set_name(const std::string &name) {
+    name_ = name;
+  }
+  /*!
+   * \brief get field information of each field.
+   * \return field information
+   */
+  inline std::vector<ParamFieldInfo> GetFieldInfo() const {
+    std::vector<ParamFieldInfo> ret(entry_.size());
+    for (size_t i = 0; i < entry_.size(); ++i) {
+      ret[i] = entry_[i]->GetFieldInfo();
+    }
+    return ret;
+  }
+  /*!
+   * \brief Print readible docstring to ostream, add newline.
+   * \parma os the stream to print the docstring to.
+   */
+  inline void PrintDocString(std::ostream &os) const {  // NOLINT(*)
+    for (size_t i = 0; i < entry_.size(); ++i) {
+      ParamFieldInfo info = entry_[i]->GetFieldInfo();
+      os << info.name << " : " << info.type_info_str << '\n';
+      if (info.description.length() != 0) {
+        os << "    " << info.description << '\n';
+      }
+    }
+  }
+  /*!
+   * \brief Get internal parameters in vector of pairs.
+   * \param head the head of the struct.
+   * \param skip_default skip the values that equals default value.
+   * \return the parameter dictionary.
+   */
+  inline std::vector<std::pair<std::string, std::string> > GetDict(void * head) const {
+    std::vector<std::pair<std::string, std::string> > ret;
+    for (std::map<std::string, FieldAccessEntry*>::const_iterator
+            it = entry_map_.begin(); it != entry_map_.end(); ++it) {
+      ret.push_back(std::make_pair(it->first, it->second->GetStringValue(head)));
+    }
+    return ret;
+  }
+
+ private:
+  /*! \brief parameter struct name */
+  std::string name_;
+  /*! \brief positional list of entries */
+  std::vector<FieldAccessEntry*> entry_;
+  /*! \brief map of key to entry */
+  std::map<std::string, FieldAccessEntry*> entry_map_;
+};
+
+//! \cond Doxygen_Suppress
+
+// The following piece of code will be template heavy and less documented
+// singleton parameter manager for certain type, used for initialization
+template<typename PType>
+struct ParamManagerSingleton {
+  ParamManager manager;
+  explicit ParamManagerSingleton(const std::string &param_name) {
+    PType param;
+    param.__DECLARE__(this);
+    manager.set_name(param_name);
+  }
+};
+
+// Base class of FieldEntry
+// implement set_default
+template<typename TEntry, typename DType>
+class FieldEntryBase : public FieldAccessEntry {
+ public:
+  // entry type
+  typedef TEntry EntryType;
+  // implement set value
+  virtual void Set(void *head, const std::string &value) const {
+    std::istringstream is(value);
+    is >> this->Get(head);
+    if (!is.fail()) {
+      while (!is.eof()) {
+        int ch = is.get();
+        if (ch == EOF) {
+          is.clear(); break;
+        }
+        if (!isspace(ch)) {
+          is.setstate(std::ios::failbit); break;
+        }
+      }
+    }
+
+    if (is.fail()) {
+      std::ostringstream os;
+      os << "Invalid Parameter format for " << key_
+         << " expect " << type_ << " but value=\'" << value<< '\'';
+      throw dmlc::ParamError(os.str());
+    }
+  }
+  virtual std::string GetStringValue(void *head) const {
+    std::ostringstream os;
+    PrintValue(os, this->Get(head));
+    return os.str();
+  }
+  virtual ParamFieldInfo GetFieldInfo() const {
+    ParamFieldInfo info;
+    std::ostringstream os;
+    info.name = key_;
+    info.type = type_;
+    os << type_;
+    if (has_default_) {
+      os << ',' << " optional, default=";
+      PrintDefaultValueString(os);
+    } else {
+      os << ", required";
+    }
+    info.type_info_str = os.str();
+    info.description = description_;
+    return info;
+  }
+  // implement set head to default value
+  virtual void SetDefault(void *head) const {
+    if (!has_default_) {
+      std::ostringstream os;
+      os << "Required parameter " << key_
+         << " of " << type_ << " is not presented";
+      throw dmlc::ParamError(os.str());
+    } else {
+      this->Get(head) = default_value_;
+    }
+  }
+  // return reference of self as derived type
+  inline TEntry &self() {
+    return *(static_cast<TEntry*>(this));
+  }
+  // implement set_default
+  inline TEntry &set_default(const DType &default_value) {
+    default_value_ = default_value;
+    has_default_ = true;
+    // return self to allow chaining
+    return this->self();
+  }
+  // implement describe
+  inline TEntry &describe(const std::string &description) {
+    description_ = description;
+    // return self to allow chaining
+    return this->self();
+  }
+  // initialization function
+  inline void Init(const std::string &key,
+                   void *head, DType &ref) { // NOLINT(*)
+    this->key_ = key;
+    if (this->type_.length() == 0) {
+      this->type_ = dmlc::type_name<DType>();
+    }
+    this->offset_ = ((char*)&ref) - ((char*)head);  // NOLINT(*)
+  }
+
+ protected:
+  // print the value
+  virtual void PrintValue(std::ostream &os, DType value) const { // NOLINT(*)
+    os << value;
+  }
+  virtual void PrintDefaultValueString(std::ostream &os) const {  // NOLINT(*)
+    PrintValue(os, default_value_);
+  }
+  // get the internal representation of parameter
+  // for example if this entry corresponds field param.learning_rate
+  // then Get(&param) will return reference to param.learning_rate
+  inline DType &Get(void *head) const {
+    return *(DType*)((char*)(head) + offset_);  // NOLINT(*)
+  }
+  // internal offset of the field
+  ptrdiff_t offset_;
+  // default value of field
+  DType default_value_;
+};
+
+// parameter base for numeric types that have range
+template<typename TEntry, typename DType>
+class FieldEntryNumeric
+    : public FieldEntryBase<TEntry, DType> {
+ public:
+  FieldEntryNumeric()
+      : has_begin_(false), has_end_(false) {}
+  // implement set_range
+  virtual TEntry &set_range(DType begin, DType end) {
+    begin_ = begin; end_ = end;
+    has_begin_ = true; has_end_ = true;
+    return this->self();
+  }
+  // implement set_range
+  virtual TEntry &set_lower_bound(DType begin) {
+    begin_ = begin; has_begin_ = true;
+    return this->self();
+  }
+  // consistency check for numeric ranges
+  virtual void Check(void *head) const {
+    FieldEntryBase<TEntry, DType>::Check(head);
+    DType v = this->Get(head);
+    if (has_begin_ && has_end_) {
+      if (v < begin_ || v > end_) {
+        std::ostringstream os;
+        os << "value " << v << "for Parameter " << this->key_
+           << " exceed bound [" << begin_ << ',' << end_ <<']';
+        throw dmlc::ParamError(os.str());
+      }
+    } else if (has_begin_ && v < begin_) {
+        std::ostringstream os;
+        os << "value " << v << "for Parameter " << this->key_
+           << " should be greater equal to " << begin_;
+        throw dmlc::ParamError(os.str());
+    } else if (has_end_ && v > end_) {
+        std::ostringstream os;
+        os << "value " << v << "for Parameter " << this->key_
+           << " should be smaller equal to " << end_;
+        throw dmlc::ParamError(os.str());
+    }
+  }
+
+ protected:
+  // whether it have begin and end range
+  bool has_begin_, has_end_;
+  // data bound
+  DType begin_, end_;
+};
+
+/*!
+ * \brief FieldEntry defines parsing and checking behavior of DType.
+ * This class can be specialized to implement specific behavior of more settings.
+ * \tparam DType the data type of the entry.
+ */
+template<typename DType>
+class FieldEntry :
+      public IfThenElseType<dmlc::is_arithmetic<DType>::value,
+                            FieldEntryNumeric<FieldEntry<DType>, DType>,
+                            FieldEntryBase<FieldEntry<DType>, DType> >::Type {
+};
+
+// specialize define for int(enum)
+template<>
+class FieldEntry<int>
+    : public FieldEntryNumeric<FieldEntry<int>, int> {
+ public:
+  // construct
+  FieldEntry<int>() : is_enum_(false) {}
+  // parent
+  typedef FieldEntryNumeric<FieldEntry<int>, int> Parent;
+  // override set
+  virtual void Set(void *head, const std::string &value) const {
+    if (is_enum_) {
+      std::map<std::string, int>::const_iterator it = enum_map_.find(value);
+      std::ostringstream os;
+      if (it == enum_map_.end()) {
+        os << "Invalid Input: \'" << value;
+        os << "\', valid values are: ";
+        PrintEnums(os);
+        throw dmlc::ParamError(os.str());
+      } else {
+        os << it->second;
+        Parent::Set(head, os.str());
+      }
+    } else {
+      Parent::Set(head, value);
+    }
+  }
+  virtual ParamFieldInfo GetFieldInfo() const {
+    if (is_enum_) {
+      ParamFieldInfo info;
+      std::ostringstream os;
+      info.name = key_;
+      info.type = type_;
+      PrintEnums(os);
+      if (has_default_) {
+        os << ',' << "optional, default=";
+        PrintDefaultValueString(os);
+      } else {
+        os << ", required";
+      }
+      info.type_info_str = os.str();
+      info.description = description_;
+      return info;
+    } else {
+      return Parent::GetFieldInfo();
+    }
+  }
+  // add enum
+  inline FieldEntry<int> &add_enum(const std::string &key, int value) {
+    if ((enum_map_.size() != 0 && enum_map_.count(key) != 0) || \
+        enum_back_map_.count(value) != 0) {
+      std::ostringstream os;
+      os << "Enum " << "(" << key << ": " << value << " exisit!" << ")\n";
+      os << "Enums: ";
+      for (std::map<std::string, int>::const_iterator it = enum_map_.begin();
+           it != enum_map_.end(); ++it) {
+        os << "(" << it->first << ": " << it->second << "), ";
+      }
+      throw dmlc::ParamError(os.str());
+    }
+    enum_map_[key] = value;
+    enum_back_map_[value] = key;
+    is_enum_ = true;
+    return this->self();
+  }
+
+ protected:
+  // enum flag
+  bool is_enum_;
+  // enum map
+  std::map<std::string, int> enum_map_;
+  // enum map
+  std::map<int, std::string> enum_back_map_;
+  // override print behavior
+  virtual void PrintDefaultValueString(std::ostream &os) const { // NOLINT(*)
+    os << '\'';
+    PrintValue(os, default_value_);
+    os << '\'';
+  }
+  // override print default
+  virtual void PrintValue(std::ostream &os, int value) const {  // NOLINT(*)
+    if (is_enum_) {
+      CHECK_NE(enum_back_map_.count(value), 0)
+          << "Value not found in enum declared";
+      os << enum_back_map_.at(value);
+    } else {
+      os << value;
+    }
+  }
+
+
+ private:
+  inline void PrintEnums(std::ostream &os) const {  // NOLINT(*)
+    os << '{';
+    for (std::map<std::string, int>::const_iterator
+             it = enum_map_.begin(); it != enum_map_.end(); ++it) {
+      if (it != enum_map_.begin()) {
+        os << ", ";
+      }
+      os << "\'" << it->first << '\'';
+    }
+    os << '}';
+  }
+};
+
+// specialize define for string
+template<>
+class FieldEntry<std::string>
+    : public FieldEntryBase<FieldEntry<std::string>, std::string> {
+ public:
+  // parent class
+  typedef FieldEntryBase<FieldEntry<std::string>, std::string> Parent;
+  // override set
+  virtual void Set(void *head, const std::string &value) const {
+    this->Get(head) = value;
+  }
+  // override print default
+  virtual void PrintDefaultValueString(std::ostream &os) const {  // NOLINT(*)
+    os << '\'' << default_value_ << '\'';
+  }
+};
+
+// specialize define for bool
+template<>
+class FieldEntry<bool>
+    : public FieldEntryBase<FieldEntry<bool>, bool> {
+ public:
+  // parent class
+  typedef FieldEntryBase<FieldEntry<bool>, bool> Parent;
+  // override set
+  virtual void Set(void *head, const std::string &value) const {
+    std::string lower_case; lower_case.resize(value.length());
+    std::transform(value.begin(), value.end(), lower_case.begin(), ::tolower);
+    bool &ref = this->Get(head);
+    if (lower_case == "true") {
+      ref = true;
+    } else if (lower_case == "false") {
+      ref = false;
+    } else if (lower_case == "1") {
+      ref = true;
+    } else if (lower_case == "0") {
+      ref = false;
+    } else {
+      std::ostringstream os;
+      os << "Invalid Parameter format for " << key_
+         << " expect " << type_ << " but value=\'" << value<< '\'';
+      throw dmlc::ParamError(os.str());
+    }
+  }
+
+ protected:
+  // print default string
+  virtual void PrintValue(std::ostream &os, bool value) const {  // NOLINT(*)
+    if (value) {
+      os << "True";
+    } else {
+      os << "False";
+    }
+  }
+};
+
+}  // namespace parameter
+//! \endcond
+
+// implement GetEnv
+template<typename ValueType>
+inline ValueType GetEnv(const char *key,
+                        ValueType default_value) {
+  const char *val = getenv(key);
+  if (val == NULL) return default_value;
+  ValueType ret;
+  parameter::FieldEntry<ValueType> e;
+  e.Init(key, &ret, ret);
+  e.Set(&ret, val);
+  return ret;
+}
+}  // namespace dmlc
+#endif  // DMLC_PARAMETER_H_
diff --git a/nnvm/include/dmlc/registry.h b/nnvm/include/dmlc/registry.h
new file mode 100644
index 000000000000..67fbc43ded68
--- /dev/null
+++ b/nnvm/include/dmlc/registry.h
@@ -0,0 +1,277 @@
+/*!
+ *  Copyright (c) 2015 by Contributors
+ * \file registry.h
+ * \brief Registry utility that helps to build registry singletons.
+ */
+#ifndef DMLC_REGISTRY_H_
+#define DMLC_REGISTRY_H_
+
+#include <map>
+#include <string>
+#include <vector>
+#include "./base.h"
+#include "./logging.h"
+#include "./parameter.h"
+#include "./type_traits.h"
+
+namespace dmlc {
+/*!
+ * \brief Registry class.
+ *  Registry can be used to register global singletons.
+ *  The most commonly use case are factory functions.
+ *
+ * \tparam EntryType Type of Registry entries,
+ *     EntryType need to name a name field.
+ */
+template<typename EntryType>
+class Registry {
+ public:
+  /*! \return list of functions in the registry */
+  inline static const std::vector<const EntryType*> &List() {
+    return Get()->entry_list_;
+  }
+  /*!
+   * \brief Find the entry with corresponding name.
+   * \param name name of the function
+   * \return the corresponding function, can be NULL
+   */
+  inline static const EntryType *Find(const std::string &name) {
+    const std::map<std::string, EntryType*> &fmap = Get()->fmap_;
+    typename std::map<std::string, EntryType*>::const_iterator p = fmap.find(name);
+    if (p != fmap.end()) {
+      return p->second;
+    } else {
+      return NULL;
+    }
+  }
+  /*!
+   * \brief Internal function to register a name function under name.
+   * \param name name of the function
+   * \return ref to the registered entry, used to set properties
+   */
+  inline EntryType &__REGISTER__(const std::string& name) {
+    CHECK_EQ(fmap_.count(name), 0)
+        << name << " already registered";
+    EntryType *e = new EntryType();
+    e->name = name;
+    fmap_[name] = e;
+    entry_list_.push_back(e);
+    return *e;
+  }
+  /*!
+   * \brief Internal function to either register or get registered entry
+   * \param name name of the function
+   * \return ref to the registered entry, used to set properties
+   */
+  inline EntryType &__REGISTER_OR_GET__(const std::string& name) {
+    if (fmap_.count(name) == 0) {
+      return __REGISTER__(name);
+    } else {
+      return *fmap_.at(name);
+    }
+  }
+  /*!
+   * \brief get a singleton of the Registry.
+   *  This function can be defined by DMLC_ENABLE_REGISTRY.
+   * \return get a singleton
+   */
+  static Registry *Get();
+
+ private:
+  /*! \brief list of entry types */
+  std::vector<const EntryType*> entry_list_;
+  /*! \brief map of name->function */
+  std::map<std::string, EntryType*> fmap_;
+  /*! \brief constructor */
+  Registry() {}
+  /*! \brief destructor */
+  ~Registry() {
+    for (typename std::map<std::string, EntryType*>::iterator p = fmap_.begin();
+         p != fmap_.end(); ++p) {
+      delete p->second;
+    }
+  }
+};
+
+/*!
+ * \brief Common base class for function registry.
+ *
+ * \code
+ *  // This example demonstrates how to use Registry to create a factory of trees.
+ *  struct TreeFactory :
+ *      public FunctionRegEntryBase<TreeFactory, std::function<Tree*()> > {
+ *  };
+ *
+ *  // in a independent cc file
+ *  namespace dmlc {
+ *  DMLC_REGISTRY_ENABLE(TreeFactory);
+ *  }
+ *  // register binary tree constructor into the registry.
+ *  DMLC_REGISTRY_REGISTER(TreeFactory, TreeFactory, BinaryTree)
+ *      .describe("Constructor of BinaryTree")
+ *      .set_body([]() { return new BinaryTree(); });
+ * \endcode
+ *
+ * \tparam EntryType The type of subclass that inheritate the base.
+ * \tparam FunctionType The function type this registry is registerd.
+ */
+template<typename EntryType, typename FunctionType>
+class FunctionRegEntryBase {
+ public:
+  /*! \brief name of the entry */
+  std::string name;
+  /*! \brief description of the entry */
+  std::string description;
+  /*! \brief additional arguments to the factory function */
+  std::vector<ParamFieldInfo> arguments;
+  /*! \brief Function body to create ProductType */
+  FunctionType body;
+  /*! \brief Return type of the function */
+  std::string return_type;
+
+  /*!
+   * \brief Set the function body.
+   * \param body Function body to set.
+   * \return reference to self.
+   */
+  inline EntryType &set_body(FunctionType body) {
+    this->body = body;
+    return this->self();
+  }
+  /*!
+   * \brief Describe the function.
+   * \param description The description of the factory function.
+   * \return reference to self.
+   */
+  inline EntryType &describe(const std::string &description) {
+    this->description = description;
+    return this->self();
+  }
+  /*!
+   * \brief Add argument information to the function.
+   * \param name Name of the argument.
+   * \param type Type of the argument.
+   * \param description Description of the argument.
+   * \return reference to self.
+   */
+  inline EntryType &add_argument(const std::string &name,
+                                 const std::string &type,
+                                 const std::string &description) {
+    ParamFieldInfo info;
+    info.name = name;
+    info.type = type;
+    info.type_info_str = info.type;
+    info.description = description;
+    arguments.push_back(info);
+    return this->self();
+  }
+  /*!
+   * \brief Append list if arguments to the end.
+   * \param args Additional list of arguments.
+   * \return reference to self.
+   */
+  inline EntryType &add_arguments(const std::vector<ParamFieldInfo> &args) {
+    arguments.insert(arguments.end(), args.begin(), args.end());
+    return this->self();
+  }
+  /*!
+  * \brief Set the return type.
+  * \param type Return type of the function, could be Symbol or Symbol[]
+  * \return reference to self.
+  */
+  inline EntryType &set_return_type(const std::string &type) {
+    return_type = type;
+    return this->self();
+  }
+
+ protected:
+  /*!
+   * \return reference of self as derived type
+   */
+  inline EntryType &self() {
+    return *(static_cast<EntryType*>(this));
+  }
+};
+
+/*!
+ * \def DMLC_REGISTRY_ENABLE
+ * \brief Macro to enable the registry of EntryType.
+ * This macro must be used under namespace dmlc, and only used once in cc file.
+ * \param EntryType Type of registry entry
+ */
+#define DMLC_REGISTRY_ENABLE(EntryType)                                 \
+  template<>                                                            \
+  Registry<EntryType > *Registry<EntryType >::Get() {                   \
+    static Registry<EntryType > inst;                                   \
+    return &inst;                                                       \
+  }                                                                     \
+
+/*!
+ * \brief Generic macro to register an EntryType
+ *  There is a complete example in FactoryRegistryEntryBase.
+ *
+ * \param EntryType The type of registry entry.
+ * \param EntryTypeName The typename of EntryType, must do not contain namespace :: .
+ * \param Name The name to be registered.
+ * \sa FactoryRegistryEntryBase
+ */
+#define DMLC_REGISTRY_REGISTER(EntryType, EntryTypeName, Name)          \
+  static EntryType & __make_ ## EntryTypeName ## _ ## Name ## __ =      \
+      ::dmlc::Registry<EntryType>::Get()->__REGISTER__(#Name)           \
+
+/*!
+ * \brief (Optional) Declare a file tag to current file that contains object registrations.
+ *
+ *  This will declare a dummy function that will be called by register file to
+ *  incur a link dependency.
+ *
+ * \param UniqueTag The unique tag used to represent.
+ * \sa DMLC_REGISTRY_LINK_TAG
+ */
+#define DMLC_REGISTRY_FILE_TAG(UniqueTag)                                \
+  int __dmlc_registry_file_tag_ ## UniqueTag ## __() { return 0; }
+
+/*!
+ * \brief (Optional) Force link to all the objects registered in file tag.
+ *
+ *  This macro must be used in the same file as DMLC_REGISTRY_ENABLE and
+ *  in the same namespace as DMLC_REGISTRY_FILE_TAG
+ *
+ *  DMLC_REGISTRY_FILE_TAG and DMLC_REGISTRY_LINK_TAG are optional macros for registration.
+ *  They are used to encforce link of certain file into during static linking.
+ *
+ *  This is mainly used to solve problem during statically link a library which contains backward registration.
+ *  Specifically, this avoids the objects in these file tags to be ignored by compiler.
+ *
+ *  For dynamic linking, this problem won't occur as everything is loaded by default.
+ *
+ *  Use of this is optional as it will create an error when a file tag do not exist.
+ *  An alternative solution is always ask user to enable --whole-archieve during static link.
+ *
+ * \begincode
+ * // in file objective_registry.cc
+ * DMLC_REGISTRY_ENABLE(MyObjective);
+ * DMLC_REGISTRY_LINK_TAG(regression_op);
+ * DMLC_REGISTRY_LINK_TAG(rank_op);
+ *
+ * // in file regression_op.cc
+ * // declare tag of this file.
+ * DMLC_REGISTRY_FILE_TAG(regression_op);
+ * DMLC_REGISTRY_REGISTER(MyObjective, logistic_reg, logistic_reg);
+ * // ...
+ *
+ * // in file rank_op.cc
+ * // declare tag of this file.
+ * DMLC_REGISTRY_FILE_TAG(rank_op);
+ * DMLC_REGISTRY_REGISTER(MyObjective, pairwiserank, pairwiserank);
+ *
+ * \endcode
+ *
+ * \param UniqueTag The unique tag used to represent.
+ * \sa DMLC_REGISTRY_ENABLE, DMLC_REGISTRY_FILE_TAG
+ */
+#define DMLC_REGISTRY_LINK_TAG(UniqueTag)                                \
+  int __dmlc_registry_file_tag_ ## UniqueTag ## __();                   \
+  static int __reg_file_tag_ ## UniqueTag ## __ = __dmlc_registry_file_tag_ ## UniqueTag ## __();
+}  // namespace dmlc
+#endif  // DMLC_REGISTRY_H_
diff --git a/nnvm/include/dmlc/thread_local.h b/nnvm/include/dmlc/thread_local.h
new file mode 100644
index 000000000000..d6596d6e642e
--- /dev/null
+++ b/nnvm/include/dmlc/thread_local.h
@@ -0,0 +1,77 @@
+/*!
+ *  Copyright (c) 2015 by Contributors
+ * \file thread_local.h
+ * \brief Portable thread local storage.
+ */
+#ifndef DMLC_THREAD_LOCAL_H_
+#define DMLC_THREAD_LOCAL_H_
+
+#include <mutex>
+#include <memory>
+#include <vector>
+
+namespace dmlc {
+
+// macro hanlding for threadlocal variables
+#ifdef __GNUC__
+  #define MX_TREAD_LOCAL __thread
+#elif __STDC_VERSION__ >= 201112L
+  #define  MX_TREAD_LOCAL _Thread_local
+#elif defined(_MSC_VER)
+  #define MX_TREAD_LOCAL __declspec(thread)
+#endif
+
+#ifndef MX_TREAD_LOCAL
+#message("Warning: Threadlocal is not enabled");
+#endif
+
+/*!
+ * \brief A threadlocal store to store threadlocal variables.
+ *  Will return a thread local singleton of type T
+ * \tparam T the type we like to store
+ */
+template<typename T>
+class ThreadLocalStore {
+ public:
+  /*! \return get a thread local singleton */
+  static T* Get() {
+    static MX_TREAD_LOCAL T* ptr = nullptr;
+    if (ptr == nullptr) {
+      ptr = new T();
+      Singleton()->RegisterDelete(ptr);
+    }
+    return ptr;
+  }
+
+ private:
+  /*! \brief constructor */
+  ThreadLocalStore() {}
+  /*! \brief destructor */
+  ~ThreadLocalStore() {
+    for (size_t i = 0; i < data_.size(); ++i) {
+      delete data_[i];
+    }
+  }
+  /*! \return singleton of the store */
+  static ThreadLocalStore<T> *Singleton() {
+    static ThreadLocalStore<T> inst;
+    return &inst;
+  }
+  /*!
+   * \brief register str for internal deletion
+   * \param str the string pointer
+   */
+  void RegisterDelete(T *str) {
+    std::unique_lock<std::mutex> lock(mutex_);
+    data_.push_back(str);
+    lock.unlock();
+  }
+  /*! \brief internal mutex */
+  std::mutex mutex_;
+  /*!\brief internal data */
+  std::vector<T*> data_;
+};
+
+}  // namespace dmlc
+
+#endif  // DMLC_THREAD_LOCAL_H_
diff --git a/nnvm/include/dmlc/timer.h b/nnvm/include/dmlc/timer.h
new file mode 100644
index 000000000000..c97059f97812
--- /dev/null
+++ b/nnvm/include/dmlc/timer.h
@@ -0,0 +1,49 @@
+/*!
+ *  Copyright (c) 2015 by Contributors
+ * \file timer.h
+ * \brief cross platform timer for timing
+ * \author Tianqi Chen
+ */
+#ifndef DMLC_TIMER_H_
+#define DMLC_TIMER_H_
+
+#include "base.h"
+
+#if DMLC_USE_CXX11
+#include <chrono>
+#endif
+
+#include <time.h>
+#ifdef __MACH__
+#include <mach/clock.h>
+#include <mach/mach.h>
+#endif
+#include "./logging.h"
+
+namespace dmlc {
+/*!
+ * \brief return time in seconds
+ */
+inline double GetTime(void) {
+  #if DMLC_USE_CXX11
+  return std::chrono::duration<double>(
+      std::chrono::high_resolution_clock::now().time_since_epoch()).count();
+  #elif defined __MACH__
+  clock_serv_t cclock;
+  mach_timespec_t mts;
+  host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
+  CHECK(clock_get_time(cclock, &mts) == 0) << "failed to get time";
+  mach_port_deallocate(mach_task_self(), cclock);
+  return static_cast<double>(mts.tv_sec) + static_cast<double>(mts.tv_nsec) * 1e-9;
+  #else
+  #if defined(__unix__) || defined(__linux__)
+  timespec ts;
+  CHECK(clock_gettime(CLOCK_REALTIME, &ts) == 0) << "failed to get time";
+  return static_cast<double>(ts.tv_sec) + static_cast<double>(ts.tv_nsec) * 1e-9;
+  #else
+  return static_cast<double>(time(NULL));
+  #endif
+  #endif
+}
+}  // namespace dmlc
+#endif  // DMLC_TIMER_H_
diff --git a/nnvm/include/dmlc/type_traits.h b/nnvm/include/dmlc/type_traits.h
new file mode 100644
index 000000000000..73abfba80aca
--- /dev/null
+++ b/nnvm/include/dmlc/type_traits.h
@@ -0,0 +1,171 @@
+/*!
+ *  Copyright (c) 2015 by Contributors
+ * \file type_traits.h
+ * \brief type traits information header
+ */
+#ifndef DMLC_TYPE_TRAITS_H_
+#define DMLC_TYPE_TRAITS_H_
+
+#include "./base.h"
+#if DMLC_USE_CXX11
+#include <type_traits>
+#endif
+#include <string>
+
+namespace dmlc {
+/*!
+ * \brief whether a type is pod type
+ * \tparam T the type to query
+ */
+template<typename T>
+struct is_pod {
+#if DMLC_USE_CXX11
+  /*! \brief the value of the traits */
+  static const bool value = std::is_pod<T>::value;
+#else
+  /*! \brief the value of the traits */
+  static const bool value = false;
+#endif
+};
+
+
+/*!
+ * \brief whether a type is integer type
+ * \tparam T the type to query
+ */
+template<typename T>
+struct is_integral {
+#if DMLC_USE_CXX11
+  /*! \brief the value of the traits */
+  static const bool value = std::is_integral<T>::value;
+#else
+  /*! \brief the value of the traits */
+  static const bool value = false;
+#endif
+};
+
+/*!
+ * \brief whether a type is floating point type
+ * \tparam T the type to query
+ */
+template<typename T>
+struct is_floating_point {
+#if DMLC_USE_CXX11
+  /*! \brief the value of the traits */
+  static const bool value = std::is_floating_point<T>::value;
+#else
+  /*! \brief the value of the traits */
+  static const bool value = false;
+#endif
+};
+
+/*!
+ * \brief whether a type is arithemetic type
+ * \tparam T the type to query
+ */
+template<typename T>
+struct is_arithmetic {
+#if DMLC_USE_CXX11
+  /*! \brief the value of the traits */
+  static const bool value = std::is_arithmetic<T>::value;
+#else
+  /*! \brief the value of the traits */
+  static const bool value = (dmlc::is_integral<T>::value ||
+                             dmlc::is_floating_point<T>::value);
+#endif
+};
+
+/*!
+ * \brief the string representation of type name
+ * \tparam T the type to query
+ * \return a const string of typename.
+ */
+template<typename T>
+inline const char* type_name() {
+  return "";
+}
+
+/*!
+ * \brief whether a type have save/load function
+ * \tparam T the type to query
+ */
+template<typename T>
+struct has_saveload {
+  /*! \brief the value of the traits */
+  static const bool value = false;
+};
+
+/*!
+ * \brief template to select type based on condition
+ * For example, IfThenElseType<true, int, float>::Type will give int
+ * \tparam cond the condition
+ * \tparam Then the typename to be returned if cond is true
+ * \tparam The typename to be returned if cond is false
+*/
+template<bool cond, typename Then, typename Else>
+struct IfThenElseType;
+
+/*! \brief macro to quickly declare traits information */
+#define DMLC_DECLARE_TRAITS(Trait, Type, Value)       \
+  template<>                                          \
+  struct Trait<Type> {                                \
+    static const bool value = Value;                  \
+  }
+
+/*! \brief macro to quickly declare traits information */
+#define DMLC_DECLARE_TYPE_NAME(Type, Name)            \
+  template<>                                          \
+  inline const char* type_name<Type>() {              \
+    return Name;                                      \
+  }
+
+//! \cond Doxygen_Suppress
+// declare special traits when C++11 is not available
+#if DMLC_USE_CXX11 == 0
+DMLC_DECLARE_TRAITS(is_pod, char, true);
+DMLC_DECLARE_TRAITS(is_pod, int8_t, true);
+DMLC_DECLARE_TRAITS(is_pod, int16_t, true);
+DMLC_DECLARE_TRAITS(is_pod, int32_t, true);
+DMLC_DECLARE_TRAITS(is_pod, int64_t, true);
+DMLC_DECLARE_TRAITS(is_pod, uint8_t, true);
+DMLC_DECLARE_TRAITS(is_pod, uint16_t, true);
+DMLC_DECLARE_TRAITS(is_pod, uint32_t, true);
+DMLC_DECLARE_TRAITS(is_pod, uint64_t, true);
+DMLC_DECLARE_TRAITS(is_pod, float, true);
+DMLC_DECLARE_TRAITS(is_pod, double, true);
+
+DMLC_DECLARE_TRAITS(is_integral, char, true);
+DMLC_DECLARE_TRAITS(is_integral, int8_t, true);
+DMLC_DECLARE_TRAITS(is_integral, int16_t, true);
+DMLC_DECLARE_TRAITS(is_integral, int32_t, true);
+DMLC_DECLARE_TRAITS(is_integral, int64_t, true);
+DMLC_DECLARE_TRAITS(is_integral, uint8_t, true);
+DMLC_DECLARE_TRAITS(is_integral, uint16_t, true);
+DMLC_DECLARE_TRAITS(is_integral, uint32_t, true);
+DMLC_DECLARE_TRAITS(is_integral, uint64_t, true);
+
+DMLC_DECLARE_TRAITS(is_floating_point, float, true);
+DMLC_DECLARE_TRAITS(is_floating_point, double, true);
+
+#endif
+
+DMLC_DECLARE_TYPE_NAME(float, "float");
+DMLC_DECLARE_TYPE_NAME(double, "double");
+DMLC_DECLARE_TYPE_NAME(int, "int");
+DMLC_DECLARE_TYPE_NAME(uint32_t, "int (non-negative)");
+DMLC_DECLARE_TYPE_NAME(uint64_t, "long (non-negative)");
+DMLC_DECLARE_TYPE_NAME(std::string, "string");
+DMLC_DECLARE_TYPE_NAME(bool, "boolean");
+
+template<typename Then, typename Else>
+struct IfThenElseType<true, Then, Else> {
+  typedef Then Type;
+};
+
+template<typename Then, typename Else>
+struct IfThenElseType<false, Then, Else> {
+  typedef Else Type;
+};
+//! \endcond
+}  // namespace dmlc
+#endif  // DMLC_TYPE_TRAITS_H_
diff --git a/nnvm/include/nnvm/tuple.h b/nnvm/include/nnvm/tuple.h
index 755f2720fa78..dbae458773ac 100644
--- a/nnvm/include/nnvm/tuple.h
+++ b/nnvm/include/nnvm/tuple.h
@@ -9,6 +9,7 @@
 #include <vector>
 #include <type_traits>
 #include <algorithm>
+#include <utility>
 #include <iostream>
 #include "./base.h"
 
diff --git a/nnvm/python/nnvm/ctypes/symbol.py b/nnvm/python/nnvm/ctypes/symbol.py
index 3bd5e65d4e25..3f5cb4e2abd7 100644
--- a/nnvm/python/nnvm/ctypes/symbol.py
+++ b/nnvm/python/nnvm/ctypes/symbol.py
@@ -98,12 +98,12 @@ def _set_attr(self, **kwargs):
         **kwargs
             The attributes to set
         """
-        keys = _base.c_array(_ctypes.c_char_p,
-                             [_base.c_str(key) for key in kwargs.keys()])
-        vals = _base.c_array(_ctypes.c_char_p,
-                             [_base.c_str(str(val)) for val in kwargs.values()])
-        num_args = _base.nn_uint(len(kwargs))
-        _check_call(_LIB.NNSymbolSetAttrs(
+        keys = c_array(ctypes.c_char_p,
+                       [c_str(key) for key in kwargs.keys()])
+        vals = c_array(ctypes.c_char_p,
+                       [c_str(str(val)) for val in kwargs.values()])
+        num_args = nn_uint(len(kwargs))
+        check_call(_LIB.NNSymbolSetAttrs(
             self.handle, num_args, keys, vals))
 
 
diff --git a/nnvm/python/nnvm/libinfo.py b/nnvm/python/nnvm/libinfo.py
index 664847052f7b..e15d4d8ae1bf 100644
--- a/nnvm/python/nnvm/libinfo.py
+++ b/nnvm/python/nnvm/libinfo.py
@@ -27,9 +27,9 @@ def find_lib_path():
     elif os.name == "posix" and os.environ.get('LD_LIBRARY_PATH', None):
         dll_path.extend([p.strip() for p in os.environ['LD_LIBRARY_PATH'].split(":")])
     if os.name == 'nt':
-        dll_path = [os.path.join(p, 'libnnvm.dll') for p in dll_path]
+        dll_path = [os.path.join(p, 'libnnvm_example.dll') for p in dll_path]
     else:
-        dll_path = [os.path.join(p, 'libnnvm.so') for p in dll_path]
+        dll_path = [os.path.join(p, 'libnnvm_example.so') for p in dll_path]
     lib_path = [p for p in dll_path if os.path.exists(p) and os.path.isfile(p)]
     if len(lib_path) == 0:
         raise RuntimeError('Cannot find the files.\n' +
diff --git a/nnvm/python/setup.py b/nnvm/python/setup.py
index 58b6e7efe088..74ada0c6b83a 100644
--- a/nnvm/python/setup.py
+++ b/nnvm/python/setup.py
@@ -1,29 +1,32 @@
 import os
 import sys
 from distutils.core import setup
-from Cython.Build import cythonize
-from distutils.extension import Extension
 
+def config_cython():
+    try:
+        from Cython.Build import cythonize
+        from distutils.extension import Extension
+        if sys.version_info >= (3, 0):
+            subdir = "_cy3"
+        else:
+            subdir = "_cy2"
+        ret = []
+        path = "nnvm/cython"
 
-def config():
-    if sys.version_info >= (3, 0):
-        subdir = "_cy3"
-    else:
-        subdir = "_cy2"
-    ret = []
-    path = "nnvm/cython"
-
-    for fn in os.listdir(path):
-        if not fn.endswith(".pyx"):
-            continue
-        ret.append(Extension(
-            "nnvm/%s/%s" % (subdir, fn[:-4]),
-            ["nnvm/cython/%s" % fn],
-            include_dirs=["../include/"],
-            language="c++"))
-    return ret
+        for fn in os.listdir(path):
+            if not fn.endswith(".pyx"):
+                continue
+            ret.append(Extension(
+                "nnvm/%s/%s" % (subdir, fn[:-4]),
+                ["nnvm/cython/%s" % fn],
+                include_dirs=["../include/"],
+                language="c++"))
+        return cythonize(ret)
+    except:
+        print("Cython is not installed, will compile without cython module")
+        return []
 
 setup(
     name='nnvm',
-    ext_modules = cythonize(config())
+    ext_modules = config_cython()
 )
diff --git a/nnvm/src/test_main.cc b/nnvm/src/test_main.cc
deleted file mode 100644
index 2d82cfbe9c84..000000000000
--- a/nnvm/src/test_main.cc
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright (c) 2016 by Contributors
-#include <nnvm/op.h>
-#include <nnvm/graph.h>
-#include <nnvm/tuple.h>
-#include <nnvm/c_api.h>
-#include <nnvm/graph_attr_types.h>
-#include <nnvm/pass_functions.h>
-#include <dmlc/timer.h>
-#include <string>
-
-void test_speed() {
-  auto add = nnvm::Op::Get("add");
-  double tstart = dmlc::GetTime();
-  size_t rep = 1000;
-  size_t n = 1000;
-  std::unordered_map<std::string, const nnvm::Symbol*> tmp;
-  std::unordered_map<std::string, std::string> kwargs;
-  std::vector<const nnvm::Symbol*> vec{2};
-  std::string name = "xx";
-  for (size_t t = 0; t < rep; ++t) {
-    nnvm::Symbol s = nnvm::Symbol::CreateVariable("x");
-    for (size_t i = 0; i < n; ++i) {
-      nnvm::Symbol nw = nnvm::Symbol::CreateFunctor(add, kwargs);
-      vec[0] = &s;
-      vec[1] =&s;
-      tmp.clear();
-      nw.Compose(vec, tmp, name);
-      s = nw;
-    }
-  }
-  double tend = dmlc::GetTime();
-  LOG(INFO) << "compose speed = " << n * rep / (tend - tstart) << " ops/sec";
-}
-
-void test_node_speed() {
-  using namespace nnvm;
-  auto add = nnvm::Op::Get("add");
-  double tstart = dmlc::GetTime();
-  size_t rep = 1000;
-  size_t n = 1000;
-  for (size_t t = 0; t < rep; ++t) {
-    nnvm::Symbol s = nnvm::Symbol::CreateVariable("x");
-    for (size_t i = 0; i < n; ++i) {
-      auto xx = NodeEntry{Node::Create(), 0, 0};
-      NodeEntry x = s.outputs[0];
-      xx.node->op = add;
-      xx.node->inputs.emplace_back(x);
-      xx.node->inputs.emplace_back(x);
-      Symbol ss;
-      ss.outputs.push_back(xx);
-      s = ss;
-    }
-  }
-  double tend = dmlc::GetTime();
-  LOG(INFO) << "test_node_speed speed = " << n * rep / (tend - tstart) << " ops/sec";
-}
-
-void test_api_speed() {
-  auto add = (void*)nnvm::Op::Get("add");  // NOLINT(*)
-  double tstart = dmlc::GetTime();
-  size_t rep = 1000;
-  size_t n = 1000;
-  std::unordered_map<std::string, const nnvm::Symbol*> tmp;
-  std::vector<const nnvm::Symbol*> vec{2};
-  std::string name = "xx";
-  for (size_t t = 0; t < rep; ++t) {
-    SymbolHandle s;
-    NNSymbolCreateVariable("xx", &s);
-    for (size_t i = 0; i < n; ++i) {
-      SymbolHandle arg[2];
-      SymbolHandle ss;
-      NNSymbolCreateAtomicSymbol(add, 0, nullptr, nullptr, &ss);
-      arg[0] = s;
-      arg[1] = s;
-      NNSymbolCompose(ss, "nn", 2, nullptr, arg);
-      s = ss;
-    }
-  }
-  double tend = dmlc::GetTime();
-  LOG(INFO) << "API compose speed = " << n * rep / (tend - tstart) << " ops/sec";
-}
-
-int main() {
-  test_speed();
-  test_node_speed();
-  test_api_speed();
-  return 0;
-}

From 5a835235595c3aa1f25babec31d9450377efed7e Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 2 Aug 2016 14:34:17 -0700
Subject: [PATCH 351/949] cleanup (#21)

---
 nnvm/example/src/operator.cc    |  1 -
 nnvm/tests/python/test_graph.py | 14 --------------
 2 files changed, 15 deletions(-)

diff --git a/nnvm/example/src/operator.cc b/nnvm/example/src/operator.cc
index 70063e4c4000..b27fe0b15635 100644
--- a/nnvm/example/src/operator.cc
+++ b/nnvm/example/src/operator.cc
@@ -98,7 +98,6 @@ NNVM_REGISTER_OP(__add_symbol__)
 NNVM_REGISTER_OP(exp)
 .describe("take exponential")
 .set_num_inputs(1)
-.attr("inplace_pair", std::make_pair(0, 0))
 .attr<FInferShape>("FInferShape", SameShape);
 
 NNVM_REGISTER_OP(cross_device_copy)
diff --git a/nnvm/tests/python/test_graph.py b/nnvm/tests/python/test_graph.py
index 787e0ae78e14..53714b1ae018 100644
--- a/nnvm/tests/python/test_graph.py
+++ b/nnvm/tests/python/test_graph.py
@@ -49,20 +49,6 @@ def test_infer_shape():
     assert g.json_attr('shape')[jnode_row_ptr[nindex["reshape1"]]] == [2, 4]
     assert g.json_attr('shape')[jnode_row_ptr[nindex["add1"]]] == [4, 2]
 
-def test_infer_shape():
-    x = sym.Variable('x', shape=(4, 2))
-    y = sym.add(x, x, name='add1')
-    y = sym.reshape(y, target=(2, 4), name="reshape1")
-    g = graph.create(y)
-    g._set_json_attr("shape_attr_key", "shape")
-    g = g.apply('InferShape')
-    jgraph = json.loads(g.apply('SaveJSON').json_attr('json'))
-    jnodes = jgraph['nodes']
-    jnode_row_ptr = jgraph['node_row_ptr']
-    nindex = {n['name']: i for i, n in enumerate(jnodes)}
-    assert g.json_attr('shape')[jnode_row_ptr[nindex["reshape1"]]] == [2, 4]
-    assert g.json_attr('shape')[jnode_row_ptr[nindex["add1"]]] == [4, 2]
-
 def test_infer_type():
     x = sym.Variable('x')
     y = sym.add(x, x, name='add1')

From bdbbdf2590783d5e6e54b6c13cb5a725e0b5c0db Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 8 Aug 2016 16:44:10 -0700
Subject: [PATCH 352/949] Update Symbol and C API (#22)

* Update tuple to be compatible with mshadow

* Move set error message to C API

* simplify with using

* updates to shape inference

* Add unnamed namespace to the implementations

* [SYMBOL] Enable inference of Auxiliary data, rename list_arguments to list_inputs
---
 nnvm/README.md                     |  12 +-
 nnvm/include/nnvm/base.h           |  34 +----
 nnvm/include/nnvm/c_api.h          |  27 ++--
 nnvm/include/nnvm/op.h             |   4 +
 nnvm/include/nnvm/pass_functions.h |   4 +-
 nnvm/include/nnvm/symbolic.h       |  20 ++-
 nnvm/include/nnvm/tuple.h          | 207 ++++++++++++++++++++++++++---
 nnvm/python/nnvm/symbol.py         |  25 +++-
 nnvm/src/c_api/c_api_common.h      |   5 -
 nnvm/src/c_api/c_api_symbolic.cc   |  21 +--
 nnvm/src/core/pass.cc              |   1 +
 nnvm/src/core/symbolic.cc          |  38 +++++-
 nnvm/src/pass/infer_shape_type.cc  |  20 +--
 nnvm/src/pass/order_mutation.cc    |   2 +
 nnvm/src/pass/place_device.cc      |   2 +
 nnvm/src/pass/plan_memory.cc       |   4 +-
 nnvm/src/pass/saveload_json.cc     |  10 +-
 nnvm/tests/python/test_graph.py    |  11 ++
 nnvm/tests/python/test_symbol.py   |   4 +-
 19 files changed, 346 insertions(+), 105 deletions(-)

diff --git a/nnvm/README.md b/nnvm/README.md
index 7acbf88093a8..1a9757b40153 100644
--- a/nnvm/README.md
+++ b/nnvm/README.md
@@ -1,7 +1,7 @@
 # NNVM: Build deep learning system by parts
 
-NNVM is not a deep learning library. It is a modular, decentralized and lightweight library to
-help build deep learning libraries efficiently.
+NNVM is not a deep learning library. It is a modular, decentralized and lightweight part to
+help build deep learning libraries.
 
 ## What is it
 
@@ -9,14 +9,14 @@ While most deep learning systems offer end to end solutions,
 it is interesting to ask if we can actually assemble a deep learning system by parts.
 The goal is to enable hackers can customize optimizations, target platforms and set of operators they care about.
 We believe that the decentralized modular system is an interesting direction.
+
 The hope is that effective parts can be assembled together just like you assemble your own desktops.
 So the customized deep learning solution can be minimax, minimum in terms of dependencies,
 while maxiziming the users' need.
 
-NNVM offers one such part, it provides a generic to do generic
-computation graph optimization such as memory reduction, device allocation,
-operator fusion while being agnostic to the operator
-interface defintion and how operators are executed.
+NNVM offers one such part, it provides a generic way to do
+computation graph optimization such as memory reduction, device allocation and more
+while being agnostic to the operator interface defintion and how operators are executed.
 NNVM is inspired by LLVM, aiming to be an intermediate representation library
 for neural nets and computation graphs generation and optimizations.
 
diff --git a/nnvm/include/nnvm/base.h b/nnvm/include/nnvm/base.h
index 31e53dc2fa2f..94fa35ce3048 100644
--- a/nnvm/include/nnvm/base.h
+++ b/nnvm/include/nnvm/base.h
@@ -16,37 +16,13 @@
 namespace nnvm {
 
 /*! \brief any type */
-using any = dmlc::any;
+using dmlc::any;
 
-/*!
- * \brief array_veiw type
- * \tparam ValueType The value content of array view.
- */
-template<typename ValueType>
-using array_view = dmlc::array_view<ValueType>;
-
-/*!
- * \brief get reference of type T stored in src.
- * \param src The source container
- * \return the reference to the type.
- * \tparam T The type to be fetched.
- */
-template<typename T>
-inline T& get(any& src) {  // NOLINT(*)
-  return dmlc::get<T>(src);
-}
-
-/*!
- * \brief get const reference of type T stored in src.
- * \param src The source container
- * \return the reference to the type.
- * \tparam T The type to be fetched.
- */
+/*! \brief array_veiw type  */
+using dmlc::array_view;
 
-template<typename T>
-inline const T& get(const any& src) {
-  return dmlc::get<T>(src);
-}
+/*!\brief getter function of any type */
+using dmlc::get;
 
 }  // namespace nnvm
 
diff --git a/nnvm/include/nnvm/c_api.h b/nnvm/include/nnvm/c_api.h
index 3b352e3d8df6..1c6943f9c681 100644
--- a/nnvm/include/nnvm/c_api.h
+++ b/nnvm/include/nnvm/c_api.h
@@ -35,6 +35,12 @@ typedef void *SymbolHandle;
 /*! \brief handle to Graph */
 typedef void *GraphHandle;
 
+/*!
+ * \brief Set the last error message needed by C API
+ * \param msg The error message to set.
+ */
+NNVM_DLL void NNAPISetLastError(const char* msg);
+
 /*!
  * \brief return str message of the last error
  *  all function in this file will return 0 when success
@@ -171,25 +177,30 @@ NNVM_DLL int NNSymbolListAttrs(SymbolHandle symbol,
                                nn_uint *out_size,
                                const char*** out);
 /*!
- * \brief List arguments in the symbol.
+ * \brief List inputs in the symbol.
  * \param symbol the symbol
+ * \param option The option to list the inputs
+ *   option=0 means list all arguments.
+ *   option=1 means list arguments that are readed only by the graph.
+ *   option=2 means list arguments that are mutated by the graph.
  * \param out_size output size
  * \param out_str_array pointer to hold the output string array
  * \return 0 when success, -1 when failure happens
  */
-NNVM_DLL int NNSymbolListArguments(SymbolHandle symbol,
-                                   nn_uint *out_size,
-                                   const char ***out_str_array);
+NNVM_DLL int NNSymbolListInputNames(SymbolHandle symbol,
+                                    int option,
+                                    nn_uint *out_size,
+                                    const char ***out_str_array);
 /*!
- * \brief List returns in the symbol.
+ * \brief List returns names in the symbol.
  * \param symbol the symbol
  * \param out_size output size
  * \param out_str_array pointer to hold the output string array
  * \return 0 when success, -1 when failure happens
  */
-NNVM_DLL int NNSymbolListOutputs(SymbolHandle symbol,
-                                 nn_uint *out_size,
-                                 const char ***out_str_array);
+NNVM_DLL int NNSymbolListOutputNames(SymbolHandle symbol,
+                                     nn_uint *out_size,
+                                     const char ***out_str_array);
 /*!
  * \brief Get a symbol that contains all the internals.
  * \param symbol The symbol
diff --git a/nnvm/include/nnvm/op.h b/nnvm/include/nnvm/op.h
index 5f499b7377e1..e32d56642a73 100644
--- a/nnvm/include/nnvm/op.h
+++ b/nnvm/include/nnvm/op.h
@@ -289,7 +289,9 @@ template<typename ValueType>
 inline const OpMap<ValueType>& Op::GetAttr(const std::string& key) {
   const any* ref = GetAttrMap(key);
   if (ref == nullptr) {
+    // update the attribute map of the key by creating new empty OpMap
     UpdateAttrMap(key, [key](any* pmap) {
+        // use callback so it is in lockscope
         if (pmap->empty()) {
           OpMap<ValueType> pm;
           pm.attr_name_ = key;
@@ -304,7 +306,9 @@ inline const OpMap<ValueType>& Op::GetAttr(const std::string& key) {
 template<typename ValueType>
 inline Op& Op::attr(  // NOLINT(*)
     const std::string& attr_name, const ValueType& value) {
+  // update the attribute map of the key by creating new empty if needed.
   UpdateAttrMap(attr_name, [this, attr_name, value](any* pmap) {
+      // the callback is in lockscope so is threadsafe.
       if (pmap->empty()) {
         OpMap<ValueType> pm;
         pm.attr_name_ = attr_name;
diff --git a/nnvm/include/nnvm/pass_functions.h b/nnvm/include/nnvm/pass_functions.h
index d622ce47dd55..742c95d1a1cf 100644
--- a/nnvm/include/nnvm/pass_functions.h
+++ b/nnvm/include/nnvm/pass_functions.h
@@ -83,10 +83,10 @@ inline Graph InferType(Graph graph,
                        DTypeVector type_args = {},
                        std::string type_attr_key = "") {
   if (type_args.size() != 0) {
-    graph.attrs["type_args"] = std::make_shared<any>(std::move(type_args));
+    graph.attrs["dtype_args"] = std::make_shared<any>(std::move(type_args));
   }
   if (type_attr_key.length() != 0) {
-    graph.attrs["type_attr_key"] = std::make_shared<any>(std::move(type_attr_key));
+    graph.attrs["dtype_attr_key"] = std::make_shared<any>(std::move(type_attr_key));
   }
   return ApplyPass(std::move(graph), {"InferType"});
 }
diff --git a/nnvm/include/nnvm/symbolic.h b/nnvm/include/nnvm/symbolic.h
index c14534f3c524..8bca4cb3103c 100644
--- a/nnvm/include/nnvm/symbolic.h
+++ b/nnvm/include/nnvm/symbolic.h
@@ -30,6 +30,18 @@ class Symbol {
     /*! \brief only list attributes in current node */
     kShallow = 1
   };
+  /*! \brief option passed to ListInputNames */
+  enum ListInputOption {
+    /*! \brief list all the arguments */
+    kAll = 0,
+    /*! \brief list only read only arguments */
+    kReadOnlyArgs = 1,
+    /*!
+     * \brief List auxiliary states that can be mutated by the graph.
+     *  This excludes the ReadOnly arguments
+     */
+    kAuxiliaryStates = 2
+  };
 
   /*! \brief output entries contained in the symbol */
   std::vector<NodeEntry> outputs;
@@ -51,18 +63,20 @@ class Symbol {
    */
   Symbol operator[] (size_t index) const;
   /*!
-   * \brief List the arguments names.
+   * \brief List the input names.
+   * \param option The options to list the arguments.
    *
    * The position of the returned list also corresponds to calling position in operator()
    * \return the arguments list of this symbol, they can be either named or unnamed (empty string).
+   * \sa ListInputOption
    */
-  std::vector<std::string> ListArguments() const;
+  std::vector<std::string> ListInputNames(ListInputOption option) const;
   /*!
    * \brief List the names of outputs for this symbol.
    *  For normal operators, it is usually symbol node name + "_output"
    * \return get the descriptions of outputs for this symbol.
    */
-  std::vector<std::string> ListOutputs() const;
+  std::vector<std::string> ListOutputNames() const;
   /*!
    * \brief Compose the symbol with arguments, this changes the current symbol.
    * The kwargs passed in can be in-complete,
diff --git a/nnvm/include/nnvm/tuple.h b/nnvm/include/nnvm/tuple.h
index dbae458773ac..fefb7ce5739d 100644
--- a/nnvm/include/nnvm/tuple.h
+++ b/nnvm/include/nnvm/tuple.h
@@ -58,17 +58,9 @@ class Tuple {
    * \brief move constructor from Tuple
    * \param src the source shape
    */
-  inline Tuple(Tuple<ValueType>&& src) {
-    this->swap(src);
-  }
-  /*!
 
-   * \param ndim the number of dimension of the Tuple
-   * \param v The value to fill.
-   */
-  inline Tuple(index_t ndim, ValueType v) {
-    this->SetDim(ndim);
-    std::fill_n(begin(), ndim, v);
+  inline Tuple(Tuple<ValueType>&& src) { // NOLINT(*)
+    this->swap(src);
   }
   /*!
    * \brief construct the Tuple from content of iterator
@@ -97,7 +89,7 @@ class Tuple {
    * \brief Swap current object with other
    * \param other another object to be swapped.
    */
-  inline void swap(Tuple<ValueType>& other) noexcept {  // NOLINT(*)
+  inline void swap(Tuple<ValueType>& other) {  // NOLINT(*)
     std::swap(ndim_, other.ndim_);
     std::swap(num_heap_allocated_, other.num_heap_allocated_);
     std::swap(data_stack_, other.data_stack_);
@@ -275,7 +267,7 @@ class Tuple {
     return is;
   }
 
- private:
+ protected:
   // stack cache size
   static const uint32_t kStackCache = 4;
   /*! \brief number of dimension of the tuple */
@@ -303,16 +295,30 @@ class Tuple {
  */
 class TShape : public Tuple<index_t> {
  public:
-  // inheritate other constructors from Tuple
-  using Tuple<index_t>::Tuple;
   /*! \brief default constructor */
   TShape() = default;
+  /*!
+   * constructor to construct a shape with all 1.
+   * \param ndim the number of dimension
+   */
+  inline TShape(index_t ndim) {  // NOLINT(*)
+    this->SetDim(ndim);
+    std::fill_n(begin(), ndim, 1);
+  }
   /*!
    * \brief copy constructor of TShape
    * \param s source shape.
    */
-  inline TShape(const Tuple<index_t>& s)  // NOLINT(*)
-      : Tuple<index_t>(s) {}
+  inline TShape(const Tuple<index_t>& s) { // NOLINT(*)
+    this->assign(s.begin(), s.end());
+  }
+  /*!
+   * \brief constructor from initializer list
+   * \param init the initializer_list
+   */
+  inline TShape(std::initializer_list<index_t> init) {
+    this->assign(init.begin(), init.end());
+  }
   /*!
    * \brief move constructor.
    * \param s source shape.
@@ -320,6 +326,17 @@ class TShape : public Tuple<index_t> {
   inline TShape(Tuple<index_t>&& s) {  // NOLINT(*)
     this->swap(s);
   }
+  /*!
+   * \brief construct the Tuple from content of iterator
+   * \param begin the beginning of iterator
+   * \param end end the end of the iterator
+   * \tparam RandomAccessIterator iterator type
+   */
+  template<typename RandomAccessIterator>
+  inline TShape(RandomAccessIterator begin,
+                RandomAccessIterator end) {
+    this->assign(begin, end);
+  }
   /*!
    * \brief assignment function from tshape
    * \param src source shape.
@@ -347,6 +364,164 @@ class TShape : public Tuple<index_t> {
     }
     return size;
   }
+  /*!
+   * \return product shape in [dimstart,dimend)
+   * \param dimstart start dimension
+   * \param dimend end dimension
+   */
+  inline index_t ProdShape(int dimstart, int dimend) const {
+    index_t num = 1;
+    const index_t *d = this->data();
+    for (int i = dimstart; i < dimend; ++i) {
+      num *= d[i];
+    }
+    return num;
+  }
+  /*! \return the begin data pointer to content of the tuple */
+  inline const index_t *data() const {
+    return begin();
+  }
+  /*! \return the begin data pointer to content of the tuple */
+  inline index_t *data() {
+    return begin();
+  }
+#ifdef MSHADOW_XINLINE
+  template<int dim>
+  inline TShape(mshadow::Shape<dim> &&s) {// NOLINT(*)
+    this->assign(s.shape_, s.shape_ + dim);
+  }
+  /*!
+   * \brief assignment from shape
+   * \param shape source shape
+   * \tparam dim shape dimension
+   * \return reference of self
+   */
+  template<int dim>
+  inline TShape &operator=(const mshadow::Shape<dim> &shape) {
+    this->assign(shape.shape_, shape.shape_ + dim);
+    return *this;
+  }
+  /*!
+   * \brief get the shape of tensor specifying dim
+   * \return the shape requested
+   * \tparam dim dimension of the tensor
+   */
+  template<int dim>
+  inline mshadow::Shape<dim> get() const {
+    CHECK_EQ(dim, ndim())
+        << "dimension do not match target dimension " << dim << " vs " << ndim();
+    const index_t *d = this->data();
+    mshadow::Shape<dim> s;
+    for (int i = 0; i < dim; ++i) {
+      s[i] = d[i];
+    }
+    return s;
+  }
+  /*!
+   * flatten the higher dimension to second dimension, return a 2D shape
+   * \return the flat 2d shape
+   */
+  inline mshadow::Shape<2> FlatTo2D(void) const {
+    mshadow::Shape<2> s;
+    if (ndim() == 0) return mshadow::Shape2(0, 0);
+    const index_t *d = this->data();
+    s.shape_[1] = d[ndim() - 1];
+    index_t ymax = 1;
+    for (index_t i = 1; i < ndim(); ++i) {
+      ymax *= d[i - 1];
+    }
+    s.shape_[0] = ymax;
+    return s;
+  }
+  /*!
+   * flatten the shape into three parts: [0, axis_begin), [axis_begin, axis_end], (axis_end, ndim)
+   * \param axis_begin The beginning axis specified.
+   * \param axis_end The ending axis specified.
+   * \return the flat 3d shape
+   */
+  inline mshadow::Shape<3> FlatTo3D(index_t axis_begin, index_t axis_end) const {
+    CHECK(axis_end >= axis_begin);
+    mshadow::Shape<3> s;
+    if (ndim() == 0) return mshadow::Shape3(0, 0, 0);
+    const index_t *d = this->data();
+    s.shape_[0] = 1;
+    s.shape_[1] = 1;
+    s.shape_[2] = 1;
+
+    for (index_t i = 0; i < axis_begin; ++i) {
+      s.shape_[0] *= d[i];
+    }
+    for (index_t i = axis_begin; i <= axis_end; ++i) {
+      s.shape_[1] *= d[i];
+    }
+    for (index_t i = axis_end + 1; i < ndim(); ++i) {
+      s.shape_[2] *= d[i];
+    }
+    return s;
+  }
+  /*!
+   * flatten the axis before and after the specified axis, so it becomes 3D tensor
+   * \param axis The axis specified.
+   * \return the flat 3d shape
+   */
+  inline mshadow::Shape<3> FlatTo3D(index_t axis) const {
+    return FlatTo3D(axis, axis);
+  }
+  inline bool operator==(const TShape &s) const {
+    if (ndim() != s.ndim()) return false;
+    return std::equal(begin(), end(), s.begin());
+  }
+  inline bool operator!=(const TShape &s) const {
+    return !(*this == s);
+  }
+  /*!
+   * \return whether two shape equals
+   * \param s the shape to compare against
+   * \tparam dim dimension of the shape
+   */
+  template<int dim>
+  inline bool operator==(const mshadow::Shape<dim> &s) const {
+    if (ndim_ != dim) return false;
+    const index_t *d = dim <= kStackCache ? data_stack_ : data_heap_;
+    for (index_t i = 0; i < dim; ++i) {
+      if (d[i] != s.shape_[i]) return false;
+    }
+    return true;
+  }
+  /*!
+   * \return whether two shape not equals
+   * \param s the shape to compare against
+   * \tparam dim dimension of the shape
+   */
+  template<int dim>
+  inline bool operator!=(const mshadow::Shape<dim> &s) const {
+    return !(*this == s);
+  }
+  /*!
+   * \brief save the content into binary stream
+   * \param strm the output stream
+   * \tparam TStream any stream type that have write
+   */
+  template<typename TStream>
+  inline void Save(TStream *strm) const {
+    strm->Write(&ndim_, sizeof(ndim_));
+    strm->Write(data(), sizeof(index_t) * ndim_);
+  }
+  /*!
+   * \brief load the content from binary stream
+   * \param strm the output stream
+   * \tparam TStream any stream type that have write
+   * \return whether the load is successful
+   */
+  template<typename TStream>
+  inline bool Load(TStream *strm) {
+    if (strm->Read(&ndim_, sizeof(ndim_)) != sizeof(ndim_)) return false;
+    this->SetDim(ndim_);
+    size_t nread = sizeof(index_t) * ndim_;
+    if (strm->Read(data(), nread) != nread) return false;
+    return true;
+  }
+#endif
 };
 
 }  // namespace nnvm
diff --git a/nnvm/python/nnvm/symbol.py b/nnvm/python/nnvm/symbol.py
index 31f1660fc899..e753299c7433 100644
--- a/nnvm/python/nnvm/symbol.py
+++ b/nnvm/python/nnvm/symbol.py
@@ -176,9 +176,16 @@ def get_internals(self):
             self.handle, _ctypes.byref(handle)))
         return Symbol(handle=handle)
 
-    def list_arguments(self):
-        """List all the arguments in the symbol.
+    def list_inputs(self, option='all'):
+        """List all the inputs in the symbol.
 
+        Parameters
+        ----------
+        option : {'all', 'read_only', 'aux_state'}, optional
+           The listing option
+           - 'all' will list all the arguments.
+           - 'read_only' lists arguments that are readed by the graph.
+           - 'aux_state' lists arguments that are mutated by the graph as state.
         Returns
         -------
         args : list of string
@@ -186,8 +193,16 @@ def list_arguments(self):
         """
         size = _ctypes.c_uint()
         sarr = _ctypes.POINTER(_ctypes.c_char_p)()
-        _check_call(_LIB.NNSymbolListArguments(
-            self.handle, _ctypes.byref(size), _ctypes.byref(sarr)))
+        if option == 'all':
+            copt = _ctypes.c_int(0)
+        elif option == 'read_only':
+            copt = _ctypes.c_int(1)
+        elif option == 'aux_state':
+            copt = _ctypes.c_int(2)
+        else:
+            raise ValueError("option need to be in {'all', 'read_only, 'aux_state'}")
+        _check_call(_LIB.NNSymbolListInputNames(
+            self.handle, copt, _ctypes.byref(size), _ctypes.byref(sarr)))
         return [_base.py_str(sarr[i]) for i in range(size.value)]
 
     def list_outputs(self):
@@ -200,7 +215,7 @@ def list_outputs(self):
         """
         size = _ctypes.c_uint()
         sarr = _ctypes.POINTER(_ctypes.c_char_p)()
-        _check_call(_LIB.NNSymbolListOutputs(
+        _check_call(_LIB.NNSymbolListOutputNames(
             self.handle, _ctypes.byref(size), _ctypes.byref(sarr)))
         return [_base.py_str(sarr[i]) for i in range(size.value)]
 
diff --git a/nnvm/src/c_api/c_api_common.h b/nnvm/src/c_api/c_api_common.h
index 40c91d8ffc11..d1d6c4316f79 100644
--- a/nnvm/src/c_api/c_api_common.h
+++ b/nnvm/src/c_api/c_api_common.h
@@ -44,11 +44,6 @@ struct NNAPIThreadLocalEntry {
 /*! \brief Thread local store that can be used to hold return values. */
 typedef dmlc::ThreadLocalStore<NNAPIThreadLocalEntry> NNAPIThreadLocalStore;
 
-/*!
- * \brief Set the last error message needed by C API
- * \param msg The error message to set.
- */
-void NNAPISetLastError(const char* msg);
 /*!
  * \brief handle exception throwed out
  * \param e the exception
diff --git a/nnvm/src/c_api/c_api_symbolic.cc b/nnvm/src/c_api/c_api_symbolic.cc
index bc6eed5c742a..aabfca4795d2 100644
--- a/nnvm/src/c_api/c_api_symbolic.cc
+++ b/nnvm/src/c_api/c_api_symbolic.cc
@@ -19,7 +19,6 @@ int NNSymbolListAtomicSymbolCreators(nn_uint *out_size,
   API_END();
 }
 
-
 int NNSymbolGetAtomicSymbolInfo(AtomicSymbolCreator creator,
                                 const char **name,
                                 const char **description,
@@ -37,7 +36,6 @@ int NNSymbolGetAtomicSymbolInfo(AtomicSymbolCreator creator,
   API_END();
 }
 
-
 int NNSymbolCreateAtomicSymbol(AtomicSymbolCreator creator,
                                nn_uint num_param,
                                const char **keys,
@@ -179,13 +177,15 @@ int NNSymbolListAttrs(SymbolHandle symbol,
   API_END();
 }
 
-int NNSymbolListArguments(SymbolHandle symbol,
-                          nn_uint *out_size,
-                          const char ***out_str_array) {
+int NNSymbolListInputNames(SymbolHandle symbol,
+                           int option,
+                           nn_uint *out_size,
+                           const char ***out_str_array) {
   Symbol *s = static_cast<Symbol*>(symbol);
   NNAPIThreadLocalEntry *ret = NNAPIThreadLocalStore::Get();
   API_BEGIN();
-  ret->ret_vec_str = std::move(s->ListArguments());
+  ret->ret_vec_str = std::move(
+      s->ListInputNames(Symbol::ListInputOption(option)));
   ret->ret_vec_charp.clear();
   for (size_t i = 0; i < ret->ret_vec_str.size(); ++i) {
     ret->ret_vec_charp.push_back(ret->ret_vec_str[i].c_str());
@@ -195,13 +195,13 @@ int NNSymbolListArguments(SymbolHandle symbol,
   API_END();
 }
 
-int NNSymbolListOutputs(SymbolHandle symbol,
-                        nn_uint *out_size,
-                        const char ***out_str_array) {
+int NNSymbolListOutputNames(SymbolHandle symbol,
+                            nn_uint *out_size,
+                            const char ***out_str_array) {
   Symbol *s = static_cast<Symbol*>(symbol);
   NNAPIThreadLocalEntry *ret = NNAPIThreadLocalStore::Get();
   API_BEGIN();
-  ret->ret_vec_str = std::move(s->ListOutputs());
+  ret->ret_vec_str = std::move(s->ListOutputNames());
   ret->ret_vec_charp.clear();
   for (size_t i = 0; i < ret->ret_vec_str.size(); ++i) {
     ret->ret_vec_charp.push_back(ret->ret_vec_str[i].c_str());
@@ -221,6 +221,7 @@ int NNSymbolCompose(SymbolHandle sym,
   std::string& s_name = ret->ret_str;
   std::unordered_map<std::string, const Symbol*>& kwargs
       = ret->kwarg_symbol;
+  kwargs.clear();
   if (name != nullptr) {
     s_name = name;
   } else {
diff --git a/nnvm/src/core/pass.cc b/nnvm/src/core/pass.cc
index f58c8039989b..5c4aeb2e0232 100644
--- a/nnvm/src/core/pass.cc
+++ b/nnvm/src/core/pass.cc
@@ -48,6 +48,7 @@ Graph ApplyPass(Graph g,
     }
     g = r->body(std::move(g));
   }
+
   return g;
 }
 
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index 8c90ccfd6bf7..1a04bf40b05a 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -181,17 +181,41 @@ Symbol Symbol::operator[] (size_t index) const {
   }
 }
 
-std::vector<std::string> Symbol::ListArguments() const {
+std::vector<std::string> Symbol::ListInputNames(ListInputOption option) const {
   std::vector<std::string> ret;
-  DFSVisit(this->outputs, [&ret](const NodePtr &node) {
-      if (node->is_variable()) {
+  if (option == kAll) {
+    DFSVisit(this->outputs, [&ret](const NodePtr &node) {
+        if (node->is_variable()) {
+          ret.push_back(node->attrs.name);
+        }
+      });
+  } else {
+    std::unordered_set<Node*> mutable_set;
+    std::vector<Node*> vlist;
+    static auto& fmutate_inputs = Op::GetAttr<FMutateInput>("FMutateInput");
+    DFSVisit(this->outputs, [&ret, &mutable_set, &vlist](const NodePtr &node) {
+        if (node->is_variable()) {
+          vlist.push_back(node.get());
+        } else if (fmutate_inputs.count(node->op)) {
+          FMutateInput fmutate = fmutate_inputs[node->op];
+          for (uint32_t i = 0; i < node->inputs.size(); ++i) {
+            if (fmutate(node->attrs, i)) {
+              mutable_set.insert(node->inputs[i].node.get());
+            }
+          }
+        }
+      });
+    for (Node* node : vlist) {
+      if ((option == kReadOnlyArgs && mutable_set.count(node) == 0) ||
+          (option == kAuxiliaryStates && mutable_set.count(node) != 0)) {
         ret.push_back(node->attrs.name);
       }
-    });
+    }
+  }
   return ret;
 }
 
-std::vector<std::string> Symbol::ListOutputs() const {
+std::vector<std::string> Symbol::ListOutputNames() const {
   static auto& flist_ouputs = Op::GetAttr<FListOutputNames>("FListOutputNames");
   std::vector<std::string> ret;
   for (auto &head : outputs) {
@@ -345,10 +369,10 @@ void Symbol::Compose(const array_view<const Symbol*>& args,
       }
     } else {
       std::vector<std::string> keys = GetKeys(kwargs);
-      std::vector<std::string> arg_names = ListArguments();
+      std::vector<std::string> arg_names = ListInputNames(kAll);
       array_view<std::string> view(dmlc::BeginPtr(arg_names) + arg_counter,
                                    dmlc::BeginPtr(arg_names) + arg_names.size());
-      KeywordArgumentMismatch("Symbol.Compose", keys, ListArguments());
+      KeywordArgumentMismatch("Symbol.Compose", keys, arg_names);
     }
   }
 }
diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index 9eb6c9bca704..25e62d563a3f 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -9,6 +9,7 @@
 
 namespace nnvm {
 namespace pass {
+namespace {
 
 template<typename AttrType, typename IsNone>
 Graph InferAttr(Graph &&ret,
@@ -17,7 +18,7 @@ Graph InferAttr(Graph &&ret,
                 const char* arg_name,
                 const char* attr_key_name,
                 const char* attr_name,
-                const char* known_name,
+                const char* unknown_name,
                 IsNone fis_none) {
   using AttrVector = std::vector<AttrType>;
   const IndexedGraph& idx = ret.indexed_graph();
@@ -48,11 +49,11 @@ Graph InferAttr(Graph &&ret,
   // temp space for shape inference.
   std::vector<AttrType*> ishape, oshape;
   // number of completed nodes
-  size_t num_known = 0;
+  size_t num_unknown = 0;
   for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
     const auto& inode = idx[nid];
     if (inode.source->is_variable()) {
-      if (shape_attr_key.length() != 0) {
+      if (shape_attr_key.length() != 0 && fis_none(rshape[idx.entry_id(nid, 0)])) {
         auto it = inode.source->attrs.dict.find(shape_attr_key);
         if (it != inode.source->attrs.dict.end()) {
           CHECK_EQ(inode.source->num_outputs(), 1);
@@ -71,8 +72,8 @@ Graph InferAttr(Graph &&ret,
       oshape[i] = &rshape[idx.entry_id(nid, i)];
     }
     if (finfer_shape.count(inode.source->op)) {
-      num_known +=
-          finfer_shape[inode.source->op](inode.source->attrs, ishape, oshape);
+      num_unknown +=
+          !(finfer_shape[inode.source->op](inode.source->attrs, ishape, oshape));
     } else if (is_backward.get(inode.source->op, false)) {
       // backward operator inference.
       CHECK_GE(inode.control_deps.size(), 1)
@@ -85,13 +86,13 @@ Graph InferAttr(Graph &&ret,
         *oshape[i] = rshape[idx.entry_id(fnode.inputs[i])];
         if (fis_none(*oshape[i])) known = false;
       }
-      num_known += known;
+      num_unknown += !known;
     }
   }
   // set the shapes
   ret.attrs[attr_name] = std::make_shared<any>(std::move(rshape));
   // number of nodes who knows the shape.
-  ret.attrs[known_name] = std::make_shared<any>(num_known);
+  ret.attrs[unknown_name] = std::make_shared<any>(num_unknown);
   return ret;
 }
 
@@ -101,7 +102,7 @@ NNVM_REGISTER_PASS(InferShape)
     return InferAttr<TShape>(
         std::move(ret), TShape(),
         "FInferShape", "shape_args", "shape_attr_key",
-        "shape", "shape_num_known_nodes",
+        "shape", "shape_num_unknown_nodes",
         [](const TShape& s) { return s.ndim() == 0; });
   })
 .set_change_graph(false)
@@ -113,7 +114,7 @@ NNVM_REGISTER_PASS(InferType)
     return InferAttr<int>(
         std::move(ret), 0,
         "FInferType", "dtype_args", "dtype_attr_key",
-        "dtype", "dtype_num_known_nodes",
+        "dtype", "dtype_num_unknown_nodes",
         [](const int t) { return t == -1; });
   })
 .set_change_graph(false)
@@ -123,5 +124,6 @@ DMLC_JSON_ENABLE_ANY(ShapeVector, list_shape);
 DMLC_JSON_ENABLE_ANY(DTypeVector, list_int);
 DMLC_JSON_ENABLE_ANY(size_t, size_t);
 
+}  // namespace
 }  // namespace pass
 }  // namespace nnvm
diff --git a/nnvm/src/pass/order_mutation.cc b/nnvm/src/pass/order_mutation.cc
index ce615fccaad4..775621982794 100644
--- a/nnvm/src/pass/order_mutation.cc
+++ b/nnvm/src/pass/order_mutation.cc
@@ -10,6 +10,7 @@
 
 namespace nnvm {
 namespace pass {
+namespace {
 
 template<typename T>
 inline T get_with_default(const std::unordered_map<Node*, T> &map,
@@ -140,5 +141,6 @@ NNVM_REGISTER_PASS(OrderMutation)
 .set_body(OrderMutation)
 .set_change_graph(true);
 
+}  // namespace
 }  // namespace pass
 }  // namespace nnvm
diff --git a/nnvm/src/pass/place_device.cc b/nnvm/src/pass/place_device.cc
index 787f47b80ca7..6a6e877a9f87 100644
--- a/nnvm/src/pass/place_device.cc
+++ b/nnvm/src/pass/place_device.cc
@@ -10,6 +10,7 @@
 
 namespace nnvm {
 namespace pass {
+namespace {
 
 // simply logic to place device according to device_group hint
 // insert copy node when there is
@@ -176,5 +177,6 @@ NNVM_REGISTER_PASS(PlaceDevice)
 
 DMLC_JSON_ENABLE_ANY(DeviceAssignMap, dict_str_int);
 
+}  // namespace
 }  // namespace pass
 }  // namespace nnvm
diff --git a/nnvm/src/pass/plan_memory.cc b/nnvm/src/pass/plan_memory.cc
index 3ba4aee16821..2d57b5c78f6b 100644
--- a/nnvm/src/pass/plan_memory.cc
+++ b/nnvm/src/pass/plan_memory.cc
@@ -12,6 +12,7 @@
 
 namespace nnvm {
 namespace pass {
+namespace {
 
 // simple graph based allocator.
 class GraphAllocator {
@@ -91,7 +92,7 @@ class GraphAllocator {
         if ((*idx_)[nid].source->is_variable()) continue;
         importance[nid] = 1;
       }
-      num_match_color_ = ColorNodeGroup(
+      num_match_color_ = pass::ColorNodeGroup(
           *idx_, importance, num_match_color_, &node_color_);
     }
   }
@@ -223,5 +224,6 @@ NNVM_REGISTER_PASS(PlanMemory)
 .depend_graph_attr("shape")
 .provide_graph_attr("storage_id");
 
+}  // namespace
 }  // namespace pass
 }  // namespace nnvm
diff --git a/nnvm/src/pass/saveload_json.cc b/nnvm/src/pass/saveload_json.cc
index 4a3c97e25b77..bd22d807e1fe 100644
--- a/nnvm/src/pass/saveload_json.cc
+++ b/nnvm/src/pass/saveload_json.cc
@@ -4,6 +4,7 @@
  * \brief Save and load graph to/from JSON file.
  */
 #include <nnvm/pass.h>
+#include <nnvm/pass_functions.h>
 #include <dmlc/json.h>
 #include <algorithm>
 
@@ -26,6 +27,7 @@ struct Handler<std::shared_ptr<const any> > {
 
 namespace nnvm {
 namespace pass {
+namespace {
 
 // auxiliary node structure for serialization.
 struct JSONNode {
@@ -35,7 +37,7 @@ struct JSONNode {
     uint32_t index;
     uint32_t version;
     void Save(dmlc::JSONWriter *writer) const {
-      writer->BeginArray();
+      writer->BeginArray(false);
       writer->WriteArrayItem(node_id);
       writer->WriteArrayItem(index);
       writer->WriteArrayItem(version);
@@ -74,7 +76,10 @@ struct JSONNode {
     }
     writer->WriteObjectKeyValue("name", node->attrs.name);
     if (node->attrs.dict.size() != 0) {
-      writer->WriteObjectKeyValue("attr", node->attrs.dict);
+      // write attributes in order;
+      std::map<std::string, std::string> dict(
+          node->attrs.dict.begin(), node->attrs.dict.end());
+      writer->WriteObjectKeyValue("attr", dict);
     }
     writer->WriteObjectKeyValue("inputs", inputs);
     if (control_deps.size() != 0) {
@@ -247,5 +252,6 @@ NNVM_REGISTER_PASS(SaveJSON)
 DMLC_JSON_ENABLE_ANY(std::string, str);
 DMLC_JSON_ENABLE_ANY(std::vector<int>, list_int);
 
+}  // namespace
 }  // namespace pass
 }  // namespace nnvm
diff --git a/nnvm/tests/python/test_graph.py b/nnvm/tests/python/test_graph.py
index 53714b1ae018..b958cee10be0 100644
--- a/nnvm/tests/python/test_graph.py
+++ b/nnvm/tests/python/test_graph.py
@@ -35,6 +35,16 @@ def test_order_mutation_pass():
     assert nindex['add1'] in jnodes[nindex['assign']]['control_deps']
     assert jnodes[nindex['assign']]['inputs'][0][2] == 1
 
+def test_list_args():
+    x = sym.Variable('x')
+    z = sym.Variable('z')
+    y = sym.conv2d(data=x, name='conv', dev='gpu')
+    y = sym.add(y, z, name='add1')
+    # write after read
+    z = sym.assign(x, y, name='assign')
+    assert z.list_inputs('read_only') == ['conv_weight', 'z']
+    assert z.list_inputs('aux_state') == ['x']
+
 def test_infer_shape():
     x = sym.Variable('x', shape=(4, 2))
     y = sym.add(x, x, name='add1')
@@ -109,3 +119,4 @@ def test_plan_memory():
     test_infer_type()
     test_place_device()
     test_plan_memory()
+    test_list_args()
diff --git a/nnvm/tests/python/test_symbol.py b/nnvm/tests/python/test_symbol.py
index adc9099adc13..a754f0f60fde 100644
--- a/nnvm/tests/python/test_symbol.py
+++ b/nnvm/tests/python/test_symbol.py
@@ -7,7 +7,7 @@ def test_compose():
     y = sym.exp(sym.add(x, x, name='add', gpu=2),
                 name='exp', gpu=1, attr={"kk": "1"})
 
-    assert y.list_arguments() == ['x']
+    assert y.list_inputs() == ['x']
     assert y.list_outputs() == ["exp_output"]
     assert y.list_attr()['gpu'] == '1'
     z = y.get_internals()
@@ -17,7 +17,7 @@ def test_compose():
 def test_default_input():
     x = sym.Variable('x')
     y = sym.conv2d(data=x, name='conv')
-    assert y.list_arguments() == ['x', 'conv_weight']
+    assert y.list_inputs() == ['x', 'conv_weight']
     try:
         z = sym.add(x)
         assert False

From 5262862501d9a5e2f86d836a3290866855824bc7 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 9 Aug 2016 11:20:44 -0700
Subject: [PATCH 353/949] Update mutate function (#23)

---
 nnvm/docs/Doxyfile                 |  4 ++--
 nnvm/example/src/operator.cc       |  6 +++---
 nnvm/include/nnvm/graph.h          |  8 ++++----
 nnvm/include/nnvm/op_attr_types.h  |  7 +++----
 nnvm/include/nnvm/pass_functions.h | 24 +++++++++++-----------
 nnvm/src/core/graph.cc             |  2 +-
 nnvm/src/core/symbolic.cc          | 26 ++++++++++--------------
 nnvm/src/pass/infer_shape_type.cc  | 14 ++++++-------
 nnvm/src/pass/order_mutation.cc    | 32 +++++++++++++++++++++---------
 9 files changed, 65 insertions(+), 58 deletions(-)

diff --git a/nnvm/docs/Doxyfile b/nnvm/docs/Doxyfile
index c1f5ffcb040c..4c6c2493aa62 100644
--- a/nnvm/docs/Doxyfile
+++ b/nnvm/docs/Doxyfile
@@ -32,7 +32,7 @@ DOXYFILE_ENCODING      = UTF-8
 # title of most generated pages and in a few other places.
 # The default value is: My Project.
 
-PROJECT_NAME           = "mxnngraph"
+PROJECT_NAME           = "nnvm"
 
 # The PROJECT_NUMBER tag can be used to enter a project or revision number. This
 # could be handy for archiving the generated documentation or if some version
@@ -753,7 +753,7 @@ WARN_LOGFILE           =
 # spaces.
 # Note: If this tag is empty the current directory is searched.
 
-INPUT                  = include
+INPUT                  = include/nnvm
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
diff --git a/nnvm/example/src/operator.cc b/nnvm/example/src/operator.cc
index b27fe0b15635..4fac82d24342 100644
--- a/nnvm/example/src/operator.cc
+++ b/nnvm/example/src/operator.cc
@@ -11,7 +11,7 @@
 namespace myproject {
 
 using nnvm::FListInputNames;
-using nnvm::FMutateInput;
+using nnvm::FMutateInputs;
 using nnvm::FInferShape;
 using nnvm::FInferType;
 using nnvm::FInplaceOption;
@@ -119,8 +119,8 @@ NNVM_REGISTER_OP(add)
 NNVM_REGISTER_OP(assign)
 .set_num_inputs(2)
 .set_num_outputs(1)
-.attr<FMutateInput>("FMutateInput", [](const NodeAttrs& attrs, uint32_t index) {
-    return index == 0;
+.attr<FMutateInputs>("FMutateInputs", [](const NodeAttrs& attrs) {
+    return std::vector<uint32_t>{0};
   });
 
 }  // namespace myproject
diff --git a/nnvm/include/nnvm/graph.h b/nnvm/include/nnvm/graph.h
index f3ca0ae7eae9..c9c14c223cce 100644
--- a/nnvm/include/nnvm/graph.h
+++ b/nnvm/include/nnvm/graph.h
@@ -144,8 +144,8 @@ class IndexedGraph {
     return nodes_[node_id(node)];
   }
   /*! \return list of argument nodes */
-  inline const std::vector<uint32_t>& arg_nodes() const {
-    return arg_nodes_;
+  inline const std::vector<uint32_t>& input_nodes() const {
+    return input_nodes_;
   }
   /*! \return list of output entries */
   inline const std::vector<NodeEntry>& outputs() const {
@@ -161,8 +161,8 @@ class IndexedGraph {
   explicit IndexedGraph(const Graph& other);
   // node pointers in CSR structure.
   std::vector<Node> nodes_;
-  // index to argument nodes
-  std::vector<uint32_t> arg_nodes_;
+  // index to input nodes
+  std::vector<uint32_t> input_nodes_;
   // space to store the outputs entries
   std::vector<NodeEntry> outputs_;
   // mapping from node to index.
diff --git a/nnvm/include/nnvm/op_attr_types.h b/nnvm/include/nnvm/op_attr_types.h
index 08b951a838d4..2cb59ea9495d 100644
--- a/nnvm/include/nnvm/op_attr_types.h
+++ b/nnvm/include/nnvm/op_attr_types.h
@@ -43,13 +43,12 @@ using FListOutputNames = std::function<std::vector<std::string> (const NodeAttrs
 /*!
  * \brief Check whether operator will mutate k-th input.
  * \param attrs The attributes of the node.
- * \param index The input index
- * \return Whether this operator will mutate index-th input.
+ * \return list of input indices it mutates.
  *
- * \note Register under "FMutateInput", default return false
+ * \note Register under "FMutateInputs", default return false
  * FMutateInputs enables mutation order handling correctly.
  */
-using FMutateInput = std::function<bool (const NodeAttrs& attrs, uint32_t index)>;
+using FMutateInputs = std::function<std::vector<uint32_t> (const NodeAttrs& attrs)>;
 
 /*!
  * \brief Inference function of certain type.
diff --git a/nnvm/include/nnvm/pass_functions.h b/nnvm/include/nnvm/pass_functions.h
index 742c95d1a1cf..8cca33e97cfd 100644
--- a/nnvm/include/nnvm/pass_functions.h
+++ b/nnvm/include/nnvm/pass_functions.h
@@ -54,16 +54,16 @@ inline Graph OrderMutation(Graph src) {
 /*!
  * \brief Infer shapes in the graph given the information.
  * \param graph source graph
- * \param shape_args The shapes of aruguments to the graph.
+ * \param shape_inputs The shapes of aruguments to the graph.
  * \param shape_attr_key The key to the node attribute that can indicate shape.
  * \return A graph with new attribute "shape" containing inferred shape of each NodeEntry.
  *  The index of ShapeVector is given by graph.indexed_graph().entry_id
  */
 inline Graph InferShape(Graph graph,
-                        ShapeVector shape_args = {},
+                        ShapeVector shape_inputs = {},
                         std::string shape_attr_key = "") {
-  if (shape_args.size() != 0) {
-    graph.attrs["shape_args"] = std::make_shared<any>(std::move(shape_args));
+  if (shape_inputs.size() != 0) {
+    graph.attrs["shape_inputs"] = std::make_shared<any>(std::move(shape_inputs));
   }
   if (shape_attr_key.length() != 0) {
     graph.attrs["shape_attr_key"] = std::make_shared<any>(std::move(shape_attr_key));
@@ -74,19 +74,19 @@ inline Graph InferShape(Graph graph,
 /*!
  * \brief Infer types in the graph given the information.
  * \param graph source graph
- * \param shape_args The shapes of aruguments to the graph.
- * \param shape_attr_key The key to the node attribute that can indicate shape.
+ * \param dtype_inputs The shapes of inputs to the graph.
+ * \param dtype_attr_key The key to the node attribute that can indicate shape.
  * \return A graph with new attribute "shape" containing inferred shape of each NodeEntry.
  *  The index of ShapeVector is given by graph.indexed_graph().entry_id
  */
 inline Graph InferType(Graph graph,
-                       DTypeVector type_args = {},
-                       std::string type_attr_key = "") {
-  if (type_args.size() != 0) {
-    graph.attrs["dtype_args"] = std::make_shared<any>(std::move(type_args));
+                       DTypeVector dtype_inputs = {},
+                       std::string dtype_attr_key = "") {
+  if (dtype_inputs.size() != 0) {
+    graph.attrs["dtype_inputs"] = std::make_shared<any>(std::move(dtype_inputs));
   }
-  if (type_attr_key.length() != 0) {
-    graph.attrs["dtype_attr_key"] = std::make_shared<any>(std::move(type_attr_key));
+  if (dtype_attr_key.length() != 0) {
+    graph.attrs["dtype_attr_key"] = std::make_shared<any>(std::move(dtype_attr_key));
   }
   return ApplyPass(std::move(graph), {"InferType"});
 }
diff --git a/nnvm/src/core/graph.cc b/nnvm/src/core/graph.cc
index 0f5cd392de05..9ddf77538946 100644
--- a/nnvm/src/core/graph.cc
+++ b/nnvm/src/core/graph.cc
@@ -30,7 +30,7 @@ IndexedGraph::IndexedGraph(const Graph &g) {
       nodes_.emplace_back(std::move(new_node));
       // arg_nodes_
       if (n->is_variable()) {
-        arg_nodes_.push_back(nid);
+        input_nodes_.push_back(nid);
       }
       // node2index_
       node2index_[n.get()] = nid;
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index 1a04bf40b05a..09e6c6313433 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -31,22 +31,19 @@ NodePtr CreateVariableNode(const std::string& name) {
 // The version of that varaible will increase
 // version is used to implicitly order the mutation sequences
 inline void UpdateNodeVersion(Node *n) {
-  static auto& fmutate_inputs = Op::GetAttr<FMutateInput>("FMutateInput");
+  static auto& fmutate_inputs = Op::GetAttr<FMutateInputs>("FMutateInputs");
   for (NodeEntry& e : n->inputs) {
     if (e.node->is_variable()) {
       e.version = nnvm::get<VariableParam>(e.node->attrs.parsed).version;
     }
   }
   if (fmutate_inputs.count(n->op) != 0) {
-    FMutateInput fmutate = fmutate_inputs[n->op];
-    for (uint32_t i = 0; i < n->inputs.size(); ++i) {
-      if (fmutate(n->attrs, i)) {
-        NodeEntry& e = n->inputs[i];
-        CHECK(e.node->is_variable())
-            << "Mutation target can only be Variable";
-        // increase the version of the variable.
-        e.version = ++nnvm::get<VariableParam>(e.node->attrs.parsed).version;
-      }
+    for (uint32_t i : fmutate_inputs[n->op](n->attrs)) {
+      NodeEntry& e = n->inputs[i];
+      CHECK(e.node->is_variable())
+          << "Mutation target can only be Variable";
+      // increase the version of the variable.
+      e.version = ++nnvm::get<VariableParam>(e.node->attrs.parsed).version;
     }
   }
 }
@@ -192,16 +189,13 @@ std::vector<std::string> Symbol::ListInputNames(ListInputOption option) const {
   } else {
     std::unordered_set<Node*> mutable_set;
     std::vector<Node*> vlist;
-    static auto& fmutate_inputs = Op::GetAttr<FMutateInput>("FMutateInput");
+    static auto& fmutate_inputs = Op::GetAttr<FMutateInputs>("FMutateInputs");
     DFSVisit(this->outputs, [&ret, &mutable_set, &vlist](const NodePtr &node) {
         if (node->is_variable()) {
           vlist.push_back(node.get());
         } else if (fmutate_inputs.count(node->op)) {
-          FMutateInput fmutate = fmutate_inputs[node->op];
-          for (uint32_t i = 0; i < node->inputs.size(); ++i) {
-            if (fmutate(node->attrs, i)) {
-              mutable_set.insert(node->inputs[i].node.get());
-            }
+          for (uint32_t i : fmutate_inputs[node->op](node->attrs)){
+            mutable_set.insert(node->inputs[i].node.get());
           }
         }
       });
diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index 25e62d563a3f..03944f318c4d 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -15,7 +15,7 @@ template<typename AttrType, typename IsNone>
 Graph InferAttr(Graph &&ret,
                 const AttrType def_value,
                 const char* infer_name,
-                const char* arg_name,
+                const char* input_name,
                 const char* attr_key_name,
                 const char* attr_name,
                 const char* unknown_name,
@@ -29,15 +29,15 @@ Graph InferAttr(Graph &&ret,
   // reshape shape vector
   AttrVector rshape(idx.num_node_entries(), def_value);
 
-  if (ret.attrs.count(arg_name) != 0) {
-    const AttrVector& shape_args = ret.GetAttr<AttrVector>(arg_name);
-    CHECK_LE(shape_args.size(), idx.arg_nodes().size())
+  if (ret.attrs.count(input_name) != 0) {
+    const AttrVector& shape_args = ret.GetAttr<AttrVector>(input_name);
+    CHECK_LE(shape_args.size(), idx.input_nodes().size())
         << "shape args is more than number of arguments";
     for (size_t i = 0; i < shape_args.size(); ++i) {
-      rshape[idx.entry_id(idx.arg_nodes()[i], 0)] = shape_args[i];
+      rshape[idx.entry_id(idx.input_nodes()[i], 0)] = shape_args[i];
     }
     // erase the provided arguments
-    ret.attrs.erase(arg_name);
+    ret.attrs.erase(input_name);
   }
   std::string shape_attr_key;
   if (ret.attrs.count(attr_key_name) != 0) {
@@ -113,7 +113,7 @@ NNVM_REGISTER_PASS(InferType)
 .set_body([](Graph ret) {
     return InferAttr<int>(
         std::move(ret), 0,
-        "FInferType", "dtype_args", "dtype_attr_key",
+        "FInferType", "dtype_inputs", "dtype_attr_key",
         "dtype", "dtype_num_unknown_nodes",
         [](const int t) { return t == -1; });
   })
diff --git a/nnvm/src/pass/order_mutation.cc b/nnvm/src/pass/order_mutation.cc
index 775621982794..c554946c6245 100644
--- a/nnvm/src/pass/order_mutation.cc
+++ b/nnvm/src/pass/order_mutation.cc
@@ -21,6 +21,13 @@ inline T get_with_default(const std::unordered_map<Node*, T> &map,
   return def;
 }
 
+inline bool IsMutate(const std::vector<uint32_t>& mutate_inputs, uint32_t i) {
+  if (mutate_inputs.size() == 0) return false;
+  auto it = std::lower_bound(
+      mutate_inputs.begin(), mutate_inputs.end(), i);
+  return (it != mutate_inputs.end()) && (*it == i);
+}
+
 Graph OrderMutation(const Graph& src) {
   std::unordered_map<Node*, std::vector<NodeEntry> > version_hist;
   DFSVisit(src.outputs, [&version_hist](const NodePtr& n) {
@@ -37,7 +44,13 @@ Graph OrderMutation(const Graph& src) {
   // start preparing for remapping the nodes.
   std::unordered_map<Node*, NodePtr> old_new;
   auto prepare = [&version_hist, &old_new] (const NodePtr& n) {
-    static auto& fmutate_inputs = Op::GetAttr<FMutateInput>("FMutateInput");
+    static auto& fmutate_inputs = Op::GetAttr<FMutateInputs>("FMutateInputs");
+    std::vector<uint32_t> mutate_inputs;
+    if (!n->is_variable() && fmutate_inputs.count(n->op)) {
+      mutate_inputs = fmutate_inputs[n->op](n->attrs);
+    }
+    std::sort(mutate_inputs.begin(), mutate_inputs.end());
+
     bool need_repl = false;
     for (size_t i = 0; i < n->inputs.size(); ++i) {
       const NodeEntry& e = n->inputs[i];
@@ -46,9 +59,7 @@ Graph OrderMutation(const Graph& src) {
         auto it = version_hist.find(e.node.get());
         if (it != version_hist.end()) {
           std::vector<NodeEntry>& vec = it->second;
-          uint32_t is_mutate =
-              fmutate_inputs.count(n->op) ? fmutate_inputs[n->op](n->attrs, i) : 0;
-          vec.emplace_back(NodeEntry{n, is_mutate, e.version});
+          vec.emplace_back(NodeEntry{n, IsMutate(mutate_inputs, i), e.version});
         }
       } else {
         if (old_new.count(e.node.get()) != 0) need_repl = true;
@@ -91,18 +102,21 @@ Graph OrderMutation(const Graph& src) {
           get_with_default(old_new, p.get(), p));
     }
     // add control deps
-    static auto& fmutate_inputs = Op::GetAttr<FMutateInput>("FMutateInput");
+    static auto& fmutate_inputs = Op::GetAttr<FMutateInputs>("FMutateInputs");
+    std::vector<uint32_t> mutate_inputs;
+    if (fmutate_inputs.count(kv.first->op)) {
+      mutate_inputs = fmutate_inputs[kv.first->op](kv.first->attrs);
+    }
+    std::sort(mutate_inputs.begin(), mutate_inputs.end());
+
     for (size_t i = 0; i < kv.first->inputs.size(); ++i) {
       const NodeEntry& e = kv.first->inputs[i];
       if (e.node->is_variable() && version_hist.count(e.node.get()) != 0) {
-        FMutateInput fmutate = fmutate_inputs.get(kv.first->op, nullptr);
-        uint32_t is_mutate = (fmutate == nullptr) ? 0 : fmutate(kv.first->attrs, i);
         std::vector<NodeEntry>& vec = version_hist.at(e.node.get());
-
         auto it = std::lower_bound(vec.begin(), vec.end(),
                                    NodeEntry{nullptr, 1, e.version},
                                    comparator);
-        if (is_mutate != 0) {
+        if (IsMutate(mutate_inputs, i)) {
           int read_dep = 0;
           while (it != vec.begin()) {
             --it;

From 47762e8dd8f734d78e6bc7ce48b4ddfbc1581363 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 9 Aug 2016 12:24:49 -0700
Subject: [PATCH 354/949] Enable aux data (#24)

---
 nnvm/include/nnvm/graph.h         | 8 +++++++-
 nnvm/include/nnvm/op.h            | 3 +++
 nnvm/src/core/graph.cc            | 9 +++++++++
 nnvm/src/pass/infer_shape_type.cc | 2 +-
 4 files changed, 20 insertions(+), 2 deletions(-)

diff --git a/nnvm/include/nnvm/graph.h b/nnvm/include/nnvm/graph.h
index c9c14c223cce..47e95f3b3925 100644
--- a/nnvm/include/nnvm/graph.h
+++ b/nnvm/include/nnvm/graph.h
@@ -147,6 +147,10 @@ class IndexedGraph {
   inline const std::vector<uint32_t>& input_nodes() const {
     return input_nodes_;
   }
+  /*! \return list of mutable nodes */
+  inline const std::unordered_set<uint32_t>& mutable_input_nodes() const {
+    return mutable_input_nodes_;
+  }
   /*! \return list of output entries */
   inline const std::vector<NodeEntry>& outputs() const {
     return outputs_;
@@ -161,8 +165,10 @@ class IndexedGraph {
   explicit IndexedGraph(const Graph& other);
   // node pointers in CSR structure.
   std::vector<Node> nodes_;
-  // index to input nodes
+  // index all to input nodes
   std::vector<uint32_t> input_nodes_;
+  // index to mutable input nodes
+  std::unordered_set<uint32_t> mutable_input_nodes_;
   // space to store the outputs entries
   std::vector<NodeEntry> outputs_;
   // mapping from node to index.
diff --git a/nnvm/include/nnvm/op.h b/nnvm/include/nnvm/op.h
index e32d56642a73..d072b9f7b420 100644
--- a/nnvm/include/nnvm/op.h
+++ b/nnvm/include/nnvm/op.h
@@ -368,12 +368,14 @@ inline Op& Op::set_attr_parser(std::function<void (NodeAttrs* attrs)> fn) {  //
 // member functions of OpMap
 template<typename ValueType>
 inline int OpMap<ValueType>::count(const Op* op) const {
+  if (op == nullptr) return 0;
   const uint32_t idx = op->index_;
   return idx < data_.size() ? data_[idx].second : 0;
 }
 
 template<typename ValueType>
 inline const ValueType& OpMap<ValueType>::operator[](const Op* op) const {
+  CHECK(op != nullptr);
   const uint32_t idx = op->index_;
   CHECK(idx < data_.size() && data_[idx].second)
         << "Attribute " << attr_name_
@@ -383,6 +385,7 @@ inline const ValueType& OpMap<ValueType>::operator[](const Op* op) const {
 
 template<typename ValueType>
 inline const ValueType& OpMap<ValueType>::get(const Op* op, const ValueType& def_value) const {
+  if (op == nullptr) return def_value;
   const uint32_t idx = op->index_;
   if (idx < data_.size() && data_[idx].second) {
     return data_[idx].first;
diff --git a/nnvm/src/core/graph.cc b/nnvm/src/core/graph.cc
index 9ddf77538946..2e0160072612 100644
--- a/nnvm/src/core/graph.cc
+++ b/nnvm/src/core/graph.cc
@@ -4,6 +4,7 @@
  * \brief Graph node data structure.
  */
 #include <nnvm/graph.h>
+#include <nnvm/op_attr_types.h>
 #include <limits>
 
 namespace nnvm {
@@ -57,12 +58,20 @@ IndexedGraph::IndexedGraph(const Graph &g) {
         node2index_.at(e.node.get()), e.index, e.version});
   }
 
+  static auto& fmutate_inputs = Op::GetAttr<FMutateInputs>("FMutateInputs");
+  std::unordered_set<uint32_t> mutable_inputs;
   // setup array view
   // input_entries_ and control_rptr must not change after this step.
   const NodeEntry* iptr = dmlc::BeginPtr(input_entries_);
   for (size_t nid = 0; nid < nodes_.size(); ++nid) {
     nodes_[nid].inputs = array_view<NodeEntry>(
         iptr + inputs_rptr[nid], iptr + inputs_rptr[nid + 1]);
+    if (nodes_[nid].source->op != nullptr &&
+        fmutate_inputs.count(nodes_[nid].source->op)) {
+      for (uint32_t i : fmutate_inputs[nodes_[nid].source->op](nodes_[nid].source->attrs)) {
+        mutable_input_nodes_.insert(nodes_[nid].inputs[i].node_id);
+      }
+    }
   }
   const uint32_t* cptr = dmlc::BeginPtr(control_deps_);
   for (size_t nid = 0; nid < nodes_.size(); ++nid) {
diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index 03944f318c4d..265e576f9eb2 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -101,7 +101,7 @@ NNVM_REGISTER_PASS(InferShape)
 .set_body([](Graph ret) {
     return InferAttr<TShape>(
         std::move(ret), TShape(),
-        "FInferShape", "shape_args", "shape_attr_key",
+        "FInferShape", "shape_inputs", "shape_attr_key",
         "shape", "shape_num_unknown_nodes",
         [](const TShape& s) { return s.ndim() == 0; });
   })

From e4b57396ec0cf4559b8abbfb25a48cf12ea6de01 Mon Sep 17 00:00:00 2001
From: Junyuan Xie <eric.jy.xie@gmail.com>
Date: Sat, 13 Aug 2016 01:23:00 -0700
Subject: [PATCH 355/949] add scalars

---
 nnvm/include/nnvm/node.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/nnvm/include/nnvm/node.h b/nnvm/include/nnvm/node.h
index ef9a30d01818..470d4d576381 100644
--- a/nnvm/include/nnvm/node.h
+++ b/nnvm/include/nnvm/node.h
@@ -48,6 +48,8 @@ struct NodeEntry {
 struct NodeAttrs {
   /*! \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;
   /*!

From b551c47f2a05b7224922d14769fe221062aef751 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 24 Aug 2016 18:40:06 -0700
Subject: [PATCH 356/949] updates (#25)

* [FIX] Remove extra move

* [MEMORY] Add inplace index
---
 nnvm/src/c_api/c_api_symbolic.cc | 8 ++++----
 nnvm/src/pass/order_mutation.cc  | 5 +----
 nnvm/src/pass/plan_memory.cc     | 8 ++++++--
 nnvm/src/pass/saveload_json.cc   | 2 +-
 4 files changed, 12 insertions(+), 11 deletions(-)

diff --git a/nnvm/src/c_api/c_api_symbolic.cc b/nnvm/src/c_api/c_api_symbolic.cc
index aabfca4795d2..3dbb816d1729 100644
--- a/nnvm/src/c_api/c_api_symbolic.cc
+++ b/nnvm/src/c_api/c_api_symbolic.cc
@@ -160,7 +160,7 @@ int NNSymbolListAttrs(SymbolHandle symbol,
   NNAPIThreadLocalEntry *ret = NNAPIThreadLocalStore::Get();
   API_BEGIN();
   std::unordered_map<std::string, std::string> attr =
-      std::move(s->ListAttrs(static_cast<Symbol::ListAttrOption>(option)));  // NOLINT(*)
+      s->ListAttrs(static_cast<Symbol::ListAttrOption>(option));  // NOLINT(*)
 
   std::vector<std::string>& attr_list = ret->ret_vec_str;
   attr_list.clear();
@@ -184,8 +184,8 @@ int NNSymbolListInputNames(SymbolHandle symbol,
   Symbol *s = static_cast<Symbol*>(symbol);
   NNAPIThreadLocalEntry *ret = NNAPIThreadLocalStore::Get();
   API_BEGIN();
-  ret->ret_vec_str = std::move(
-      s->ListInputNames(Symbol::ListInputOption(option)));
+  ret->ret_vec_str =
+      s->ListInputNames(Symbol::ListInputOption(option));
   ret->ret_vec_charp.clear();
   for (size_t i = 0; i < ret->ret_vec_str.size(); ++i) {
     ret->ret_vec_charp.push_back(ret->ret_vec_str[i].c_str());
@@ -201,7 +201,7 @@ int NNSymbolListOutputNames(SymbolHandle symbol,
   Symbol *s = static_cast<Symbol*>(symbol);
   NNAPIThreadLocalEntry *ret = NNAPIThreadLocalStore::Get();
   API_BEGIN();
-  ret->ret_vec_str = std::move(s->ListOutputNames());
+  ret->ret_vec_str = s->ListOutputNames();
   ret->ret_vec_charp.clear();
   for (size_t i = 0; i < ret->ret_vec_str.size(); ++i) {
     ret->ret_vec_charp.push_back(ret->ret_vec_str[i].c_str());
diff --git a/nnvm/src/pass/order_mutation.cc b/nnvm/src/pass/order_mutation.cc
index c554946c6245..3bcfd9922d53 100644
--- a/nnvm/src/pass/order_mutation.cc
+++ b/nnvm/src/pass/order_mutation.cc
@@ -22,10 +22,7 @@ inline T get_with_default(const std::unordered_map<Node*, T> &map,
 }
 
 inline bool IsMutate(const std::vector<uint32_t>& mutate_inputs, uint32_t i) {
-  if (mutate_inputs.size() == 0) return false;
-  auto it = std::lower_bound(
-      mutate_inputs.begin(), mutate_inputs.end(), i);
-  return (it != mutate_inputs.end()) && (*it == i);
+  return std::binary_search(mutate_inputs.begin(), mutate_inputs.end(), i);
 }
 
 Graph OrderMutation(const Graph& src) {
diff --git a/nnvm/src/pass/plan_memory.cc b/nnvm/src/pass/plan_memory.cc
index 2d57b5c78f6b..14a88d217de8 100644
--- a/nnvm/src/pass/plan_memory.cc
+++ b/nnvm/src/pass/plan_memory.cc
@@ -150,6 +150,7 @@ Graph PlanMemory(Graph ret) {
   }
   // step 2: allocate memory.
   StorageVector storage(idx.num_node_entries(), -1);
+  std::vector<int> storage_inplace_index(idx.num_node_entries(), -1);
   const ShapeVector& shape_vec = ret.GetAttr<ShapeVector>("shape");
   const DTypeVector& dtype_vec = ret.GetAttr<DTypeVector>("dtype");
   const DeviceVector* device_vec = nullptr;
@@ -173,8 +174,10 @@ Graph PlanMemory(Graph ret) {
         uint32_t eid_out = idx.entry_id(nid, kv.second);
         uint32_t eid_in = idx.entry_id(inode.inputs[kv.first]);
         if (ref_count[eid_in] == 1 && storage[eid_in] != GraphAllocator::kBadStorageID) {
+          // inplace optimization
           storage[eid_out] = storage[eid_in];
           ref_count[eid_in] = 0;
+          storage_inplace_index[eid_out] = kv.first;
         }
       }
     }
@@ -209,8 +212,8 @@ Graph PlanMemory(Graph ret) {
       }
     }
   }
-
   ret.attrs["storage_id"] = std::make_shared<any>(std::move(storage));
+  ret.attrs["storage_inplace_index"] = std::make_shared<any>(std::move(storage_inplace_index));
   ret.attrs["storage_allocated_bytes"] = std::make_shared<any>(allocator.TotalAllocBytes());
   ret.attrs["storage_num_not_allocated"] = std::make_shared<any>(num_not_allocated);
   return ret;
@@ -222,7 +225,8 @@ NNVM_REGISTER_PASS(PlanMemory)
 .set_change_graph(false)
 .depend_graph_attr("dtype")
 .depend_graph_attr("shape")
-.provide_graph_attr("storage_id");
+.provide_graph_attr("storage_id")
+.provide_graph_attr("storage_inplace_index");
 
 }  // namespace
 }  // namespace pass
diff --git a/nnvm/src/pass/saveload_json.cc b/nnvm/src/pass/saveload_json.cc
index bd22d807e1fe..984a2c9905c4 100644
--- a/nnvm/src/pass/saveload_json.cc
+++ b/nnvm/src/pass/saveload_json.cc
@@ -89,7 +89,7 @@ struct JSONNode {
   }
 
   void Load(dmlc::JSONReader *reader) {
-    node = std::move(Node::Create());
+    node = Node::Create();
     control_deps.clear();
     dmlc::JSONObjectReadHelper helper;
     std::string op_type_str;

From c362a28210b460d139175937a36d7915c44cc7f9 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 24 Aug 2016 23:26:45 -0700
Subject: [PATCH 357/949] update (#26)

* updates (#1)

* add scalars

* change format

* change inferattr interface

* remove scalar

* remove warning
---
 nnvm/example/src/operator.cc      | 30 +++++++++++++-------------
 nnvm/include/nnvm/op.h            | 32 +++++++++++++++++++++++++++
 nnvm/include/nnvm/op_attr_types.h |  4 ++--
 nnvm/src/c_api/c_api_symbolic.cc  | 17 ++++++++++++++-
 nnvm/src/core/symbolic.cc         |  5 ++++-
 nnvm/src/pass/infer_shape_type.cc | 36 +++++++++++++++++++------------
 nnvm/tests/python/test_symbol.py  |  1 -
 7 files changed, 91 insertions(+), 34 deletions(-)

diff --git a/nnvm/example/src/operator.cc b/nnvm/example/src/operator.cc
index 4fac82d24342..c0c729cdb4dd 100644
--- a/nnvm/example/src/operator.cc
+++ b/nnvm/example/src/operator.cc
@@ -21,14 +21,14 @@ using nnvm::array_view;
 
 // simply return the shape as same
 inline bool SameShape(const NodeAttrs& attrs,
-                      array_view<TShape*> ishape,
-                      array_view<TShape*> oshape) {
-  if (ishape.size() == 0 || ishape[0]->ndim() == 0) return false;
-  for (TShape* pshape : oshape) {
-    *pshape = *ishape[0];
+                      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];
+  for (TShape& pshape : *ishape) {
+    pshape = (*ishape)[0];
   }
   return true;
 }
@@ -51,13 +51,13 @@ NNVM_REGISTER_OP(reshape)
     })
 .attr<FInferShape>(
     "FInferShape", [] (const NodeAttrs& attrs,
-                       array_view<TShape*> ishape,
-                       array_view<TShape*> oshape) {
+                       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())
+      (*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;
     })
@@ -78,9 +78,9 @@ NNVM_REGISTER_OP(cast)
 .attr<FInferShape>("FInferShape", SameShape)
 .attr<FInferType>(
     "FInferType", [](const NodeAttrs& attrs,
-                     array_view<int*> itype,
-                     array_view<int*> otype) {
-      *otype[0] = nnvm::get<int>(attrs.parsed);
+                     std::vector<int> *itype,
+                     std::vector<int> *otype) {
+      (*otype)[0] = nnvm::get<int>(attrs.parsed);
       return true;
     });
 
diff --git a/nnvm/include/nnvm/op.h b/nnvm/include/nnvm/op.h
index d072b9f7b420..721e8e736e09 100644
--- a/nnvm/include/nnvm/op.h
+++ b/nnvm/include/nnvm/op.h
@@ -6,6 +6,7 @@
 #ifndef NNVM_OP_H_
 #define NNVM_OP_H_
 
+#include <dmlc/parameter.h>
 #include <string>
 #include <vector>
 #include <utility>
@@ -22,6 +23,7 @@ struct NodeAttrs;
 template<typename ValueType>
 class OpMap;
 class OpRegistryEntry;
+using dmlc::ParamFieldInfo;
 
 /*! \brief constant to indicate it take any length of positional inputs */
 static const uint32_t kVarg = std::numeric_limits<uint32_t>::max();
@@ -80,6 +82,8 @@ class Op {
    *  This can be used to generate docstring automatically for the operator.
    */
   std::string description;
+  /* \brief description of inputs and keyword arguments*/
+  std::vector<ParamFieldInfo> arguments;
   /*!
    * \brief number of inputs to the operator,
    * -1 means it is variable length
@@ -149,6 +153,22 @@ class Op {
    * \return reference to self.
    */
   inline Op& describe(const std::string& descr);  // NOLINT(*)
+  /*!
+   * \brief Add argument information to the function.
+   * \param name Name of the argument.
+   * \param type Type of the argument.
+   * \param description Description of the argument.
+   * \return reference to self.
+   */
+  inline Op& add_argument(const std::string &name,
+                          const std::string &type,
+                          const std::string &description);
+  /*!
+   * \brief Append list if arguments to the end.
+   * \param args Additional list of arguments.
+   * \return reference to self.
+   */
+  inline Op& add_arguments(const std::vector<ParamFieldInfo> &args);
   /*!
    * \brief Set the num_inputs
    * \param n The number of inputs to be set.
@@ -340,6 +360,18 @@ inline Op& Op::describe(const std::string& descr) {  // NOLINT(*)
   return *this;
 }
 
+inline Op& Op::add_argument(const std::string &name,
+                            const std::string &type,
+                            const std::string &description) {
+  arguments.push_back({name, type, type, description});
+  return *this;
+}
+
+inline Op& Op::add_arguments(const std::vector<ParamFieldInfo> &args) {
+  this->arguments.insert(arguments.end(), args.begin(), args.end());
+  return *this;
+}
+
 inline Op& Op::set_num_inputs(uint32_t n) {  // NOLINT(*)
   this->num_inputs = n;
   return *this;
diff --git a/nnvm/include/nnvm/op_attr_types.h b/nnvm/include/nnvm/op_attr_types.h
index 2cb59ea9495d..675b93a6c9d2 100644
--- a/nnvm/include/nnvm/op_attr_types.h
+++ b/nnvm/include/nnvm/op_attr_types.h
@@ -57,8 +57,8 @@ using FMutateInputs = std::function<std::vector<uint32_t> (const NodeAttrs& attr
  */
 template<typename AttrType>
 using FInferNodeEntryAttr = std::function<bool (const NodeAttrs& attrs,
-                                                array_view<AttrType*> in_attrs,
-                                                array_view<AttrType*> out_attrs)>;
+                                                std::vector<AttrType> *in_attrs,
+                                                std::vector<AttrType> *out_attrs)>;
 /*!
  * \brief Shape inference function.
  *  Update the shapes given the input shape information.
diff --git a/nnvm/src/c_api/c_api_symbolic.cc b/nnvm/src/c_api/c_api_symbolic.cc
index 3dbb816d1729..dcdce820c7e6 100644
--- a/nnvm/src/c_api/c_api_symbolic.cc
+++ b/nnvm/src/c_api/c_api_symbolic.cc
@@ -28,11 +28,26 @@ int NNSymbolGetAtomicSymbolInfo(AtomicSymbolCreator creator,
                                 const char ***arg_descriptions,
                                 const char **return_type) {
   const Op *op = static_cast<const Op *>(creator);
+  NNAPIThreadLocalEntry *ret = NNAPIThreadLocalStore::Get();
 
   API_BEGIN();
   *name = op->name.c_str();
   *description = op->description.c_str();
-  *num_doc_args = 0;
+  *num_doc_args = static_cast<nn_uint>(op->arguments.size());
+  if (return_type) *return_type = nullptr;
+  ret->ret_vec_charp.clear();
+  for (size_t i = 0; i < op->arguments.size(); ++i) {
+    ret->ret_vec_charp.push_back(op->arguments[i].name.c_str());
+  }
+  for (size_t i = 0; i < op->arguments.size(); ++i) {
+    ret->ret_vec_charp.push_back(op->arguments[i].type_info_str.c_str());
+  }
+  for (size_t i = 0; i < op->arguments.size(); ++i) {
+    ret->ret_vec_charp.push_back(op->arguments[i].description.c_str());
+  }
+  *arg_names = dmlc::BeginPtr(ret->ret_vec_charp);
+  *arg_type_infos = dmlc::BeginPtr(ret->ret_vec_charp) + op->arguments.size();
+  *arg_descriptions = dmlc::BeginPtr(ret->ret_vec_charp) + (op->arguments.size() * 2);
   API_END();
 }
 
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index 09e6c6313433..d595880aed1c 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -151,7 +151,10 @@ void Symbol::Print(std::ostream &os) const {
           }
           if (!node->attrs.dict.empty()) {
             os << "Attrs:\n";
-            for (auto &kv : node->attrs.dict) {
+            // make an ordered copy because unordered_map doesn't guarantee order.
+            std::map<std::string, std::string> sorted_dict(
+              node->attrs.dict.begin(), node->attrs.dict.end());
+            for (auto &kv : sorted_dict) {
               os << '\t' << kv.first << '=' << kv.second << '\n';
             }
           }
diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index 265e576f9eb2..5978ecdb79f2 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -47,44 +47,52 @@ Graph InferAttr(Graph &&ret,
   }
 
   // temp space for shape inference.
-  std::vector<AttrType*> ishape, oshape;
+  std::vector<AttrType> ishape, oshape;
   // number of completed nodes
   size_t num_unknown = 0;
   for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
     const auto& inode = idx[nid];
+    uint32_t num_inputs = inode.inputs.size();
+    uint32_t num_outputs = inode.source->num_outputs();
     if (inode.source->is_variable()) {
       if (shape_attr_key.length() != 0 && fis_none(rshape[idx.entry_id(nid, 0)])) {
         auto it = inode.source->attrs.dict.find(shape_attr_key);
         if (it != inode.source->attrs.dict.end()) {
-          CHECK_EQ(inode.source->num_outputs(), 1);
+          CHECK_EQ(num_outputs, 1);
           std::istringstream is(it->second);
           CHECK(is >> rshape[idx.entry_id(nid, 0)]) << "Invalid attribute";
         }
       }
       continue;
     }
-    ishape.resize(inode.inputs.size());
-    for (uint32_t i = 0; i < ishape.size(); ++i) {
-      ishape[i] = &rshape[idx.entry_id(inode.inputs[i])];
-    }
-    oshape.resize(inode.source->num_outputs());
-    for (uint32_t i = 0; i < oshape.size(); ++i) {
-      oshape[i] = &rshape[idx.entry_id(nid, i)];
-    }
     if (finfer_shape.count(inode.source->op)) {
+      ishape.resize(num_inputs, def_value);
+      for (uint32_t i = 0; i < ishape.size(); ++i) {
+        ishape[i] = rshape[idx.entry_id(inode.inputs[i])];
+      }
+      oshape.resize(num_outputs, def_value);
+      for (uint32_t i = 0; i < oshape.size(); ++i) {
+        oshape[i] = rshape[idx.entry_id(nid, i)];
+      }
       num_unknown +=
-          !(finfer_shape[inode.source->op](inode.source->attrs, ishape, oshape));
+          !(finfer_shape[inode.source->op](inode.source->attrs, &ishape, &oshape));
+      for (uint32_t i = 0; i < num_inputs; ++i) {
+        rshape[idx.entry_id(inode.inputs[i])] = ishape[i];
+      }
+      for (uint32_t i = 0; i < num_outputs; ++i) {
+        rshape[idx.entry_id(nid, i)] = oshape[i];
+      }
     } else if (is_backward.get(inode.source->op, false)) {
       // backward operator inference.
       CHECK_GE(inode.control_deps.size(), 1)
           << "BackwardOp need to have control_deps to its forward op";
       const auto& fnode = idx[inode.control_deps[0]];
-      CHECK_EQ(fnode.inputs.size(), inode.source->num_outputs())
+      CHECK_EQ(fnode.inputs.size(), num_outputs)
           << "BackwardOp need to correspond to the forward node";
       bool known = true;
       for (size_t i = 0; i < fnode.inputs.size(); ++i) {
-        *oshape[i] = rshape[idx.entry_id(fnode.inputs[i])];
-        if (fis_none(*oshape[i])) known = false;
+        rshape[idx.entry_id(nid, i)] = rshape[idx.entry_id(fnode.inputs[i])];
+        if (fis_none(rshape[idx.entry_id(nid, i)])) known = false;
       }
       num_unknown += !known;
     }
diff --git a/nnvm/tests/python/test_symbol.py b/nnvm/tests/python/test_symbol.py
index a754f0f60fde..994f0d14a605 100644
--- a/nnvm/tests/python/test_symbol.py
+++ b/nnvm/tests/python/test_symbol.py
@@ -41,7 +41,6 @@ def test_copy():
     z = sym.Variable('z')
     y = sym.exp(sym.add(x, x, name='add', gpu=2),
                 name='exp', gpu=1, attr={"kk": "1"})
-
     assert y.__copy__().debug_str() == y.debug_str()
 
 if __name__ == "__main__":

From 6464d902ca9aff9c123c8930f89a10200e5a08f4 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 26 Aug 2016 16:53:10 -0700
Subject: [PATCH 358/949] Change function def to Node ref for more flexiblity
 (#27)

* Remove warning in g++5

* Change function def to Node ref for more flexiblity
---
 nnvm/example/src/operator.cc       | 17 +++++++-------
 nnvm/include/dmlc/base.h           | 13 +++++++++++
 nnvm/include/dmlc/json.h           |  9 +++++---
 nnvm/include/dmlc/parameter.h      |  3 ++-
 nnvm/include/dmlc/registry.h       |  5 +++--
 nnvm/include/nnvm/node.h           |  7 ++----
 nnvm/include/nnvm/op.h             | 26 ++++++++++-----------
 nnvm/include/nnvm/op_attr_types.h  | 36 +++++++++++++++++++++---------
 nnvm/include/nnvm/pass.h           |  2 +-
 nnvm/include/nnvm/pass_functions.h | 29 ++++++++++++++++++++++++
 nnvm/src/core/graph.cc             |  2 +-
 nnvm/src/core/symbolic.cc          |  8 +++----
 nnvm/src/pass/infer_shape_type.cc  |  2 +-
 nnvm/src/pass/order_mutation.cc    |  4 ++--
 nnvm/src/pass/plan_memory.cc       |  2 +-
 15 files changed, 111 insertions(+), 54 deletions(-)

diff --git a/nnvm/example/src/operator.cc b/nnvm/example/src/operator.cc
index c0c729cdb4dd..045e52a183c3 100644
--- a/nnvm/example/src/operator.cc
+++ b/nnvm/example/src/operator.cc
@@ -15,12 +15,13 @@ using nnvm::FMutateInputs;
 using nnvm::FInferShape;
 using nnvm::FInferType;
 using nnvm::FInplaceOption;
+using nnvm::Node;
 using nnvm::NodeAttrs;
 using nnvm::TShape;
 using nnvm::array_view;
 
 // simply return the shape as same
-inline bool SameShape(const NodeAttrs& attrs,
+inline bool SameShape(const Node& n,
                       std::vector<TShape> *ishape,
                       std::vector<TShape> *oshape) {
   if (ishape->size() == 0 || (*ishape)[0].ndim() == 0) return false;
@@ -33,7 +34,7 @@ inline bool SameShape(const NodeAttrs& attrs,
   return true;
 }
 
-inline std::vector<std::pair<int, int> > InplaceIn0Out0(const NodeAttrs& attrs) {
+inline std::vector<std::pair<int, int> > InplaceIn0Out0(const Node& n) {
   return {{0, 0}};
 }
 
@@ -50,11 +51,11 @@ NNVM_REGISTER_OP(reshape)
       attrs->parsed = std::move(target);
     })
 .attr<FInferShape>(
-    "FInferShape", [] (const NodeAttrs& attrs,
+    "FInferShape", [] (const Node& n,
                        std::vector<TShape> *ishape,
                        std::vector<TShape> *oshape) {
       // get parsed attribute
-      const TShape& target = nnvm::get<TShape>(attrs.parsed);
+      const TShape& target = nnvm::get<TShape>(n.attrs.parsed);
       (*oshape)[0] = target;
       if ((*ishape)[0].ndim() == 0) return false;
       CHECK_EQ((*ishape)[0].Size(), target.Size())
@@ -77,10 +78,10 @@ NNVM_REGISTER_OP(cast)
     })
 .attr<FInferShape>("FInferShape", SameShape)
 .attr<FInferType>(
-    "FInferType", [](const NodeAttrs& attrs,
+    "FInferType", [](const Node& n,
                      std::vector<int> *itype,
                      std::vector<int> *otype) {
-      (*otype)[0] = nnvm::get<int>(attrs.parsed);
+      (*otype)[0] = nnvm::get<int>(n.attrs.parsed);
       return true;
     });
 
@@ -109,7 +110,7 @@ NNVM_REGISTER_OP(cross_device_copy)
 NNVM_REGISTER_OP(conv2d)
 .describe("take conv of input")
 .set_num_inputs(2)
-.attr<FListInputNames>("FListInputNames", [](const NodeAttrs& attrs) {
+.attr<FListInputNames>("FListInputNames", [](const Node& n) {
     return std::vector<std::string>{"data", "weight"};
   });
 
@@ -119,7 +120,7 @@ NNVM_REGISTER_OP(add)
 NNVM_REGISTER_OP(assign)
 .set_num_inputs(2)
 .set_num_outputs(1)
-.attr<FMutateInputs>("FMutateInputs", [](const NodeAttrs& attrs) {
+.attr<FMutateInputs>("FMutateInputs", [](const Node& n) {
     return std::vector<uint32_t>{0};
   });
 
diff --git a/nnvm/include/dmlc/base.h b/nnvm/include/dmlc/base.h
index 5b34fd6b4e34..9eca4135f119 100644
--- a/nnvm/include/dmlc/base.h
+++ b/nnvm/include/dmlc/base.h
@@ -58,6 +58,11 @@
                         __cplusplus >= 201103L || defined(_MSC_VER))
 #endif
 
+/*! \brief strict CXX11 support */
+#ifndef DMLC_STRICT_CXX11
+#define DMLC_STRICT_CXX11 (__cplusplus >= 201103L || defined(_MSC_VER))
+#endif
+
 /// check if g++ is before 4.6
 #if DMLC_USE_CXX11 && defined(__GNUC__) && !defined(__clang_version__)
 #if __GNUC__ == 4 && __GNUC_MINOR__ < 6
@@ -69,6 +74,7 @@
 #endif
 #endif
 
+
 /*!
  * \brief Enable std::thread related modules,
  *  Used to disable some module in mingw compile.
@@ -82,6 +88,13 @@
 #define DMLC_USE_REGEX (__cplusplus >= 201103L || defined(_MSC_VER))
 #endif
 
+/*! \brief helper macro to supress unused warning */
+#if defined(__GNUC__)
+#define DMLC_ATTRIBUTE_UNUSED __attribute__((unused))
+#else
+#define DMLC_ATTRIBUTE_UNUSED
+#endif
+
 /*! \brief helper macro to generate string concat */
 #define DMLC_STR_CONCAT_(__x, __y) __x##__y
 #define DMLC_STR_CONCAT(__x, __y) DMLC_STR_CONCAT_(__x, __y)
diff --git a/nnvm/include/dmlc/json.h b/nnvm/include/dmlc/json.h
index 2daa0aaa017f..1934aee6a2ce 100644
--- a/nnvm/include/dmlc/json.h
+++ b/nnvm/include/dmlc/json.h
@@ -25,7 +25,9 @@
 #include <typeindex>
 #include <typeinfo>
 #include <unordered_map>
+#if DMLC_STRICT_CXX11
 #include "./any.h"
+#endif  // DMLC_STRICT_CXX11
 #endif  // DMLC_USE_CXX11
 
 namespace dmlc {
@@ -320,7 +322,8 @@ class JSONObjectReadHelper {
 };
 
 #define DMLC_JSON_ENABLE_ANY_VAR_DEF(KeyName)                  \
-  static ::dmlc::json::AnyJSONManager&  __make_AnyJSONType ## _ ## KeyName ## __
+  static DMLC_ATTRIBUTE_UNUSED ::dmlc::json::AnyJSONManager&   \
+  __make_AnyJSONType ## _ ## KeyName ## __
 
 /*!
  * \def DMLC_JSON_ENABLE_ANY
@@ -475,7 +478,7 @@ struct Handler {
   }
 };
 
-#if DMLC_USE_CXX11
+#if DMLC_STRICT_CXX11
 // Manager to store json serialization strategy.
 class AnyJSONManager {
  public:
@@ -561,7 +564,7 @@ struct Handler<any> {
     CHECK(!reader->NextArrayItem()) << "invalid any json format";
   }
 };
-#endif  // DMLC_USE_CXX11
+#endif  // DMLC_STRICT_CXX11
 
 }  // namespace json
 
diff --git a/nnvm/include/dmlc/parameter.h b/nnvm/include/dmlc/parameter.h
index 4ff99f860cc3..2fbab2a44e32 100644
--- a/nnvm/include/dmlc/parameter.h
+++ b/nnvm/include/dmlc/parameter.h
@@ -251,7 +251,8 @@ struct Parameter {
     static ::dmlc::parameter::ParamManagerSingleton<PType> inst(#PType); \
     return &inst.manager;                                               \
   }                                                                     \
-  static ::dmlc::parameter::ParamManager &__make__ ## PType ## ParamManager__ = \
+  static DMLC_ATTRIBUTE_UNUSED ::dmlc::parameter::ParamManager&         \
+  __make__ ## PType ## ParamManager__ =                                 \
       (*PType::__MANAGER__())                                           \
 
 //! \endcond
diff --git a/nnvm/include/dmlc/registry.h b/nnvm/include/dmlc/registry.h
index 67fbc43ded68..380b31cd3d61 100644
--- a/nnvm/include/dmlc/registry.h
+++ b/nnvm/include/dmlc/registry.h
@@ -216,7 +216,7 @@ class FunctionRegEntryBase {
  * \sa FactoryRegistryEntryBase
  */
 #define DMLC_REGISTRY_REGISTER(EntryType, EntryTypeName, Name)          \
-  static EntryType & __make_ ## EntryTypeName ## _ ## Name ## __ =      \
+  static DMLC_ATTRIBUTE_UNUSED EntryType & __make_ ## EntryTypeName ## _ ## Name ## __ = \
       ::dmlc::Registry<EntryType>::Get()->__REGISTER__(#Name)           \
 
 /*!
@@ -272,6 +272,7 @@ class FunctionRegEntryBase {
  */
 #define DMLC_REGISTRY_LINK_TAG(UniqueTag)                                \
   int __dmlc_registry_file_tag_ ## UniqueTag ## __();                   \
-  static int __reg_file_tag_ ## UniqueTag ## __ = __dmlc_registry_file_tag_ ## UniqueTag ## __();
+  static int DMLC_ATTRIBUTE_UNUSED __reg_file_tag_ ## UniqueTag ## __ = \
+      __dmlc_registry_file_tag_ ## UniqueTag ## __();
 }  // namespace dmlc
 #endif  // DMLC_REGISTRY_H_
diff --git a/nnvm/include/nnvm/node.h b/nnvm/include/nnvm/node.h
index 470d4d576381..f42e272305cb 100644
--- a/nnvm/include/nnvm/node.h
+++ b/nnvm/include/nnvm/node.h
@@ -17,7 +17,6 @@ namespace nnvm {
 
 // Forward declare node.
 class Node;
-
 /*!
  * \brief we always used NodePtr for a reference pointer
  *  to the node, so this alias can be changed in case.
@@ -48,8 +47,6 @@ struct NodeEntry {
 struct NodeAttrs {
   /*! \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;
   /*!
@@ -108,7 +105,7 @@ inline uint32_t Node::num_outputs() const {
   if (this->op->get_num_outputs == nullptr) {
     return this->op->num_outputs;
   } else {
-    return this->op->get_num_outputs(this->attrs);
+    return this->op->get_num_outputs(*this);
   }
 }
 
@@ -117,7 +114,7 @@ inline uint32_t Node::num_inputs() const {
   if (this->op->get_num_inputs == nullptr) {
     return this->op->num_inputs;
   } else {
-    return this->op->get_num_inputs(this->attrs);
+    return this->op->get_num_inputs(*this);
   }
 }
 
diff --git a/nnvm/include/nnvm/op.h b/nnvm/include/nnvm/op.h
index 721e8e736e09..150f36471651 100644
--- a/nnvm/include/nnvm/op.h
+++ b/nnvm/include/nnvm/op.h
@@ -102,16 +102,16 @@ class Op {
   uint32_t num_outputs = 1;
   /*!
    * \brief get number of outputs given information about the node.
-   * \param attrs The attribute of the node
+   * \param n The node
    * \return number of outputs.
    */
-  std::function<uint32_t(const NodeAttrs& attrs)> get_num_outputs = nullptr;
+  std::function<uint32_t(const Node& n)> get_num_outputs = nullptr;
   /*!
    * \brief get number of inputs given information about the node.
-   * \param attrs The attribute of the node
+   * \param n The node
    * \return number of inputs
    */
-  std::function<uint32_t(const NodeAttrs& attrs)> get_num_inputs = nullptr;
+  std::function<uint32_t(const Node& n)> get_num_inputs = nullptr;
   /*!
    * \brief Attribute parser to parse the NodeAttrs information.
    *
@@ -136,11 +136,11 @@ class Op {
    *     attrs->parsed = std::move(param);
    *  }
    *  // The other function that can utilize the parsed result.
-   *  TShape SumInferShape(const NodeAttrs& attrs,
+   *  TShape SumInferShape(const NodePtr& ptr,
    *                       const std::vector<TShape>& ishapes) {
    *     // we can use the parsed version of param
    *     // without repeatively parsing the parameter
-   *     const SumParam& param = nnvm::get<SumParam>(attrs.parsed);
+   *     const SumParam& param = nnvm::get<SumParam>(ptr->attrs.parsed);
    *  }
    * \endcode
    */
@@ -180,7 +180,7 @@ class Op {
    * \param fn The function to be set.
    * \return reference to self.
    */
-  inline Op& set_num_inputs(std::function<uint32_t (const NodeAttrs& attr)> fn);  // NOLINT(*)
+  inline Op& set_num_inputs(std::function<uint32_t (const Node& n)> fn);  // NOLINT(*)
   /*!
    * \brief Set the num_outputs
    * \param n The number of outputs to be set.
@@ -192,7 +192,7 @@ class Op {
    * \param fn The function to be set.
    * \return reference to self.
    */
-  inline Op& set_num_outputs(std::function<uint32_t (const NodeAttrs& attr)> fn);  // NOLINT(*)
+  inline Op& set_num_outputs(std::function<uint32_t (const Node& n)> fn);  // NOLINT(*)
   /*!
    * \brief Set the attr_parser function.
    * \param fn The number of outputs to be set.
@@ -279,10 +279,8 @@ class OpMap {
 };
 
 // internal macros to make
-#define NNVM_STR_CONCAT_(__x, __y) __x##__y
-#define NNVM_STR_CONCAT(__x, __y) NNVM_STR_CONCAT_(__x, __y)
 #define NNVM_REGISTER_VAR_DEF(OpName)                              \
-  static ::nnvm::Op & __make_ ## NnvmOp ## _ ## OpName
+  static DMLC_ATTRIBUTE_UNUSED ::nnvm::Op & __make_ ## NnvmOp ## _ ## OpName
 
 /*!
  * \def NNVM_REGISTER_OP
@@ -300,7 +298,7 @@ class OpMap {
  * \endcode
  */
 #define NNVM_REGISTER_OP(OpName)                                     \
-  NNVM_STR_CONCAT(NNVM_REGISTER_VAR_DEF(OpName), __COUNTER__) =   \
+  DMLC_STR_CONCAT(NNVM_REGISTER_VAR_DEF(OpName), __COUNTER__) =   \
       ::dmlc::Registry<::nnvm::Op>::Get()->__REGISTER_OR_GET__(#OpName)
 
 // implementations of template functions after this.
@@ -377,7 +375,7 @@ inline Op& Op::set_num_inputs(uint32_t n) {  // NOLINT(*)
   return *this;
 }
 
-inline Op& Op::set_num_inputs(std::function<uint32_t (const NodeAttrs& attr)> fn) {  // NOLINT(*)
+inline Op& Op::set_num_inputs(std::function<uint32_t (const Node& n)> fn) {  // NOLINT(*)
   this->get_num_inputs = fn;
   return *this;
 }
@@ -387,7 +385,7 @@ inline Op& Op::set_num_outputs(uint32_t n) {  // NOLINT(*)
   return *this;
 }
 
-inline Op& Op::set_num_outputs(std::function<uint32_t (const NodeAttrs& attr)> fn) {  // NOLINT(*)
+inline Op& Op::set_num_outputs(std::function<uint32_t (const Node& n)> fn) {  // NOLINT(*)
   this->get_num_outputs = fn;
   return *this;
 }
diff --git a/nnvm/include/nnvm/op_attr_types.h b/nnvm/include/nnvm/op_attr_types.h
index 675b93a6c9d2..b1cb44d64df6 100644
--- a/nnvm/include/nnvm/op_attr_types.h
+++ b/nnvm/include/nnvm/op_attr_types.h
@@ -12,6 +12,7 @@
 #include <functional>
 #include "./base.h"
 #include "./tuple.h"
+#include "./node.h"
 
 namespace nnvm {
 
@@ -21,34 +22,34 @@ namespace nnvm {
 /*!
  * \brief Return list of input arguments names of each operator.
  *
- * \param attrs The attributes of the node.
+ * \param n The node.
  * \return list of inputs
  * \note Register under "FListInputNames", default return {"data"}.
  *
  *  FListInputNames enables automatic variable creation for missing arguments.
  */
-using FListInputNames = std::function<std::vector<std::string> (const NodeAttrs& attrs)>;
+using FListInputNames = std::function<std::vector<std::string> (const Node& n)>;
 
 /*!
  * \brief Return list of output arguments names of each operator.
  *
- * \param attrs The attributes of the node.
+ * \param n The node.
  * \return list of inputs
  * \note Register under "FListOutputNames", default return {"outputs"}.
  *
  *  FListOutputNames customized naming for operator outputs.
  */
-using FListOutputNames = std::function<std::vector<std::string> (const NodeAttrs& attrs)>;
+using FListOutputNames = std::function<std::vector<std::string> (const Node& n)>;
 
 /*!
  * \brief Check whether operator will mutate k-th input.
- * \param attrs The attributes of the node.
+ * \param n The node.
  * \return list of input indices it mutates.
  *
  * \note Register under "FMutateInputs", default return false
  * FMutateInputs enables mutation order handling correctly.
  */
-using FMutateInputs = std::function<std::vector<uint32_t> (const NodeAttrs& attrs)>;
+using FMutateInputs = std::function<std::vector<uint32_t> (const Node& n)>;
 
 /*!
  * \brief Inference function of certain type.
@@ -56,9 +57,9 @@ using FMutateInputs = std::function<std::vector<uint32_t> (const NodeAttrs& attr
  * \return whether all attributes are inferred.
  */
 template<typename AttrType>
-using FInferNodeEntryAttr = std::function<bool (const NodeAttrs& attrs,
-                                                std::vector<AttrType> *in_attrs,
-                                                std::vector<AttrType> *out_attrs)>;
+using FInferNodeEntryAttr = std::function<bool (const Node& n,
+                                                std::vector<AttrType> *in_ptr,
+                                                std::vector<AttrType> *out_ptr)>;
 /*!
  * \brief Shape inference function.
  *  Update the shapes given the input shape information.
@@ -96,7 +97,7 @@ using TIsBackwardOp = bool;
 /*!
  * \brief Get possible inplace options.
  *  This function enables optimization to reuse memory of inputs in output.
- * \param attrs The attributes of the node
+ * \param n The node
  * \param in_data The input data.
  * \param out_data The output data.
  * \return list of pair of that maps input->output,
@@ -105,7 +106,20 @@ using TIsBackwardOp = bool;
  * \note Register under "FInplaceOption", by default no inplace can happen.
  */
 using FInplaceOption = std::function<
-  std::vector<std::pair<int, int> > (const NodeAttrs& attrs)>;
+  std::vector<std::pair<int, int> > (const Node& n)>;
+
+/*!
+ * \brief Get the gradient node of the op node
+ *  This function generates the backward graph of the node
+ * \param nodeptr The node to take gradient
+ * \param out_grads Gradient of current node's outputs
+ * \return gradients of the inputs
+ *
+ * \note Register under "FGradient"
+ */
+using FGradient = std::function<std::vector<NodeEntry>(
+    const NodePtr& nodeptr,
+    const std::vector<NodeEntry>& out_grads)>;
 
 }  // namespace nnvm
 
diff --git a/nnvm/include/nnvm/pass.h b/nnvm/include/nnvm/pass.h
index 438226f5c93f..fb97cea5ae0b 100644
--- a/nnvm/include/nnvm/pass.h
+++ b/nnvm/include/nnvm/pass.h
@@ -23,7 +23,7 @@ namespace nnvm {
  * \param src The graph to be transformed.
  * \return The generated graph.
  */
-typedef std::function<Graph (Graph src)> PassFunction;
+using PassFunction = std::function<Graph (Graph src)>;
 
 /*!
  * \brief Apply a series of pass transformations on g.
diff --git a/nnvm/include/nnvm/pass_functions.h b/nnvm/include/nnvm/pass_functions.h
index 8cca33e97cfd..25f5b26957f8 100644
--- a/nnvm/include/nnvm/pass_functions.h
+++ b/nnvm/include/nnvm/pass_functions.h
@@ -11,9 +11,11 @@
 #define NNVM_PASS_FUNCTIONS_H_
 
 #include <string>
+#include <vector>
 #include <memory>
 #include "./base.h"
 #include "./pass.h"
+#include "./node.h"
 #include "./graph_attr_types.h"
 
 namespace nnvm {
@@ -109,6 +111,33 @@ inline Graph PlaceDevice(Graph graph,
   return ApplyPass(std::move(graph), {"PlaceDevice"});
 }
 
+/*!
+ * \brief Get the gradient graph whose outputs are gradients of xs wrt to ys.
+ * \param graph source graph
+ * \param ys The entries we want to take gradient from.
+ * \param xs The input we want to
+ * \param aggregate_fun aggregation function applied to aggregate the inputs
+ * \param mirror_fun Optional mirror function to do mirror optimization and save memory.
+ * \return A new graph, whose outputs corresponds to inputs of xs.
+ */
+inline Graph Gradient(
+    Graph graph,
+    std::vector<NodeEntry> ys,
+    std::vector<NodeEntry> xs,
+    std::function<NodeEntry(std::vector<NodeEntry>&& inputs)> aggregate_fun = nullptr,
+    std::function<int(const Node& node)> mirror_fun = nullptr) {
+  graph.attrs["grad_ys"] = std::make_shared<any>(std::move(ys));
+  graph.attrs["grad_xs"] = std::make_shared<any>(std::move(xs));
+  if (aggregate_fun != nullptr) {
+    graph.attrs["grad_aggregate_fun"] = std::make_shared<any>(aggregate_fun);
+  }
+  if (mirror_fun != nullptr) {
+    graph.attrs["grad_mirror_fun"] = std::make_shared<any>(mirror_fun);
+  }
+
+  return ApplyPass(std::move(graph), {"Gradient"});
+}
+
 }  // namespace pass
 }  // namespace nnvm
 #endif  // NNVM_PASS_FUNCTIONS_H_
diff --git a/nnvm/src/core/graph.cc b/nnvm/src/core/graph.cc
index 2e0160072612..f6f1faa5e0bb 100644
--- a/nnvm/src/core/graph.cc
+++ b/nnvm/src/core/graph.cc
@@ -68,7 +68,7 @@ IndexedGraph::IndexedGraph(const Graph &g) {
         iptr + inputs_rptr[nid], iptr + inputs_rptr[nid + 1]);
     if (nodes_[nid].source->op != nullptr &&
         fmutate_inputs.count(nodes_[nid].source->op)) {
-      for (uint32_t i : fmutate_inputs[nodes_[nid].source->op](nodes_[nid].source->attrs)) {
+      for (uint32_t i : fmutate_inputs[nodes_[nid].source->op](*(nodes_[nid].source))) {
         mutable_input_nodes_.insert(nodes_[nid].inputs[i].node_id);
       }
     }
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index d595880aed1c..d839f2b9bf5b 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -38,7 +38,7 @@ inline void UpdateNodeVersion(Node *n) {
     }
   }
   if (fmutate_inputs.count(n->op) != 0) {
-    for (uint32_t i : fmutate_inputs[n->op](n->attrs)) {
+    for (uint32_t i : fmutate_inputs[n->op](*n)) {
       NodeEntry& e = n->inputs[i];
       CHECK(e.node->is_variable())
           << "Mutation target can only be Variable";
@@ -197,7 +197,7 @@ std::vector<std::string> Symbol::ListInputNames(ListInputOption option) const {
         if (node->is_variable()) {
           vlist.push_back(node.get());
         } else if (fmutate_inputs.count(node->op)) {
-          for (uint32_t i : fmutate_inputs[node->op](node->attrs)){
+          for (uint32_t i : fmutate_inputs[node->op](*node)){
             mutable_set.insert(node->inputs[i].node.get());
           }
         }
@@ -223,7 +223,7 @@ std::vector<std::string> Symbol::ListOutputNames() const {
       std::string rname;
       FListOutputNames fn = flist_ouputs.get(head.node->op, nullptr);
       if (fn != nullptr) {
-        rname = fn(head.node->attrs)[head.index];
+        rname = fn(*head.node)[head.index];
       } else {
         rname = "output";
         if (head.node->num_outputs() != 1) {
@@ -279,7 +279,7 @@ void Symbol::Compose(const array_view<const Symbol*>& args,
       // switch to keyword argument matching
       if (args.size() != n_req) {
         FListInputNames fn = flist_inputs.get(n->op, nullptr);
-        auto arg_names = (fn == nullptr) ? std::vector<std::string>{"data"} : fn(n->attrs);
+        auto arg_names = (fn == nullptr) ? std::vector<std::string>{"data"} : fn(*n);
         if (arg_names.size() != n_req) {
           LOG(FATAL) << "Not enough argument to call operator " << outputs[0].node->op->name;
         }
diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index 5978ecdb79f2..13e225744416 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -75,7 +75,7 @@ Graph InferAttr(Graph &&ret,
         oshape[i] = rshape[idx.entry_id(nid, i)];
       }
       num_unknown +=
-          !(finfer_shape[inode.source->op](inode.source->attrs, &ishape, &oshape));
+          !(finfer_shape[inode.source->op](*inode.source, &ishape, &oshape));
       for (uint32_t i = 0; i < num_inputs; ++i) {
         rshape[idx.entry_id(inode.inputs[i])] = ishape[i];
       }
diff --git a/nnvm/src/pass/order_mutation.cc b/nnvm/src/pass/order_mutation.cc
index 3bcfd9922d53..eddd5727b31b 100644
--- a/nnvm/src/pass/order_mutation.cc
+++ b/nnvm/src/pass/order_mutation.cc
@@ -44,7 +44,7 @@ Graph OrderMutation(const Graph& src) {
     static auto& fmutate_inputs = Op::GetAttr<FMutateInputs>("FMutateInputs");
     std::vector<uint32_t> mutate_inputs;
     if (!n->is_variable() && fmutate_inputs.count(n->op)) {
-      mutate_inputs = fmutate_inputs[n->op](n->attrs);
+      mutate_inputs = fmutate_inputs[n->op](*n);
     }
     std::sort(mutate_inputs.begin(), mutate_inputs.end());
 
@@ -102,7 +102,7 @@ Graph OrderMutation(const Graph& src) {
     static auto& fmutate_inputs = Op::GetAttr<FMutateInputs>("FMutateInputs");
     std::vector<uint32_t> mutate_inputs;
     if (fmutate_inputs.count(kv.first->op)) {
-      mutate_inputs = fmutate_inputs[kv.first->op](kv.first->attrs);
+      mutate_inputs = fmutate_inputs[kv.first->op](*kv.first);
     }
     std::sort(mutate_inputs.begin(), mutate_inputs.end());
 
diff --git a/nnvm/src/pass/plan_memory.cc b/nnvm/src/pass/plan_memory.cc
index 14a88d217de8..4541e7de5937 100644
--- a/nnvm/src/pass/plan_memory.cc
+++ b/nnvm/src/pass/plan_memory.cc
@@ -169,7 +169,7 @@ Graph PlanMemory(Graph ret) {
     if (inode.source->is_variable()) continue;
     // check inplace option
     if (finplace_option.count(inode.source->op) != 0) {
-      auto inplace_pairs = finplace_option[inode.source->op](inode.source->attrs);
+      auto inplace_pairs = finplace_option[inode.source->op](*inode.source);
       for (auto& kv : inplace_pairs) {
         uint32_t eid_out = idx.entry_id(nid, kv.second);
         uint32_t eid_in = idx.entry_id(inode.inputs[kv.first]);

From 112b0784a50704a159d78c2598950e78bd437a15 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 26 Aug 2016 19:30:24 -0700
Subject: [PATCH 359/949] Revert "Change function def to Node ref for more
 flexiblity" (#29)

---
 nnvm/example/src/operator.cc       | 17 +++++++-------
 nnvm/include/dmlc/base.h           | 13 -----------
 nnvm/include/dmlc/json.h           |  9 +++-----
 nnvm/include/dmlc/parameter.h      |  3 +--
 nnvm/include/dmlc/registry.h       |  5 ++---
 nnvm/include/nnvm/node.h           |  7 ++++--
 nnvm/include/nnvm/op.h             | 26 +++++++++++----------
 nnvm/include/nnvm/op_attr_types.h  | 36 +++++++++---------------------
 nnvm/include/nnvm/pass.h           |  2 +-
 nnvm/include/nnvm/pass_functions.h | 29 ------------------------
 nnvm/src/core/graph.cc             |  2 +-
 nnvm/src/core/symbolic.cc          |  8 +++----
 nnvm/src/pass/infer_shape_type.cc  |  2 +-
 nnvm/src/pass/order_mutation.cc    |  4 ++--
 nnvm/src/pass/plan_memory.cc       |  2 +-
 15 files changed, 54 insertions(+), 111 deletions(-)

diff --git a/nnvm/example/src/operator.cc b/nnvm/example/src/operator.cc
index 045e52a183c3..c0c729cdb4dd 100644
--- a/nnvm/example/src/operator.cc
+++ b/nnvm/example/src/operator.cc
@@ -15,13 +15,12 @@ using nnvm::FMutateInputs;
 using nnvm::FInferShape;
 using nnvm::FInferType;
 using nnvm::FInplaceOption;
-using nnvm::Node;
 using nnvm::NodeAttrs;
 using nnvm::TShape;
 using nnvm::array_view;
 
 // simply return the shape as same
-inline bool SameShape(const Node& n,
+inline bool SameShape(const NodeAttrs& attrs,
                       std::vector<TShape> *ishape,
                       std::vector<TShape> *oshape) {
   if (ishape->size() == 0 || (*ishape)[0].ndim() == 0) return false;
@@ -34,7 +33,7 @@ inline bool SameShape(const Node& n,
   return true;
 }
 
-inline std::vector<std::pair<int, int> > InplaceIn0Out0(const Node& n) {
+inline std::vector<std::pair<int, int> > InplaceIn0Out0(const NodeAttrs& attrs) {
   return {{0, 0}};
 }
 
@@ -51,11 +50,11 @@ NNVM_REGISTER_OP(reshape)
       attrs->parsed = std::move(target);
     })
 .attr<FInferShape>(
-    "FInferShape", [] (const Node& n,
+    "FInferShape", [] (const NodeAttrs& attrs,
                        std::vector<TShape> *ishape,
                        std::vector<TShape> *oshape) {
       // get parsed attribute
-      const TShape& target = nnvm::get<TShape>(n.attrs.parsed);
+      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())
@@ -78,10 +77,10 @@ NNVM_REGISTER_OP(cast)
     })
 .attr<FInferShape>("FInferShape", SameShape)
 .attr<FInferType>(
-    "FInferType", [](const Node& n,
+    "FInferType", [](const NodeAttrs& attrs,
                      std::vector<int> *itype,
                      std::vector<int> *otype) {
-      (*otype)[0] = nnvm::get<int>(n.attrs.parsed);
+      (*otype)[0] = nnvm::get<int>(attrs.parsed);
       return true;
     });
 
@@ -110,7 +109,7 @@ NNVM_REGISTER_OP(cross_device_copy)
 NNVM_REGISTER_OP(conv2d)
 .describe("take conv of input")
 .set_num_inputs(2)
-.attr<FListInputNames>("FListInputNames", [](const Node& n) {
+.attr<FListInputNames>("FListInputNames", [](const NodeAttrs& attrs) {
     return std::vector<std::string>{"data", "weight"};
   });
 
@@ -120,7 +119,7 @@ NNVM_REGISTER_OP(add)
 NNVM_REGISTER_OP(assign)
 .set_num_inputs(2)
 .set_num_outputs(1)
-.attr<FMutateInputs>("FMutateInputs", [](const Node& n) {
+.attr<FMutateInputs>("FMutateInputs", [](const NodeAttrs& attrs) {
     return std::vector<uint32_t>{0};
   });
 
diff --git a/nnvm/include/dmlc/base.h b/nnvm/include/dmlc/base.h
index 9eca4135f119..5b34fd6b4e34 100644
--- a/nnvm/include/dmlc/base.h
+++ b/nnvm/include/dmlc/base.h
@@ -58,11 +58,6 @@
                         __cplusplus >= 201103L || defined(_MSC_VER))
 #endif
 
-/*! \brief strict CXX11 support */
-#ifndef DMLC_STRICT_CXX11
-#define DMLC_STRICT_CXX11 (__cplusplus >= 201103L || defined(_MSC_VER))
-#endif
-
 /// check if g++ is before 4.6
 #if DMLC_USE_CXX11 && defined(__GNUC__) && !defined(__clang_version__)
 #if __GNUC__ == 4 && __GNUC_MINOR__ < 6
@@ -74,7 +69,6 @@
 #endif
 #endif
 
-
 /*!
  * \brief Enable std::thread related modules,
  *  Used to disable some module in mingw compile.
@@ -88,13 +82,6 @@
 #define DMLC_USE_REGEX (__cplusplus >= 201103L || defined(_MSC_VER))
 #endif
 
-/*! \brief helper macro to supress unused warning */
-#if defined(__GNUC__)
-#define DMLC_ATTRIBUTE_UNUSED __attribute__((unused))
-#else
-#define DMLC_ATTRIBUTE_UNUSED
-#endif
-
 /*! \brief helper macro to generate string concat */
 #define DMLC_STR_CONCAT_(__x, __y) __x##__y
 #define DMLC_STR_CONCAT(__x, __y) DMLC_STR_CONCAT_(__x, __y)
diff --git a/nnvm/include/dmlc/json.h b/nnvm/include/dmlc/json.h
index 1934aee6a2ce..2daa0aaa017f 100644
--- a/nnvm/include/dmlc/json.h
+++ b/nnvm/include/dmlc/json.h
@@ -25,9 +25,7 @@
 #include <typeindex>
 #include <typeinfo>
 #include <unordered_map>
-#if DMLC_STRICT_CXX11
 #include "./any.h"
-#endif  // DMLC_STRICT_CXX11
 #endif  // DMLC_USE_CXX11
 
 namespace dmlc {
@@ -322,8 +320,7 @@ class JSONObjectReadHelper {
 };
 
 #define DMLC_JSON_ENABLE_ANY_VAR_DEF(KeyName)                  \
-  static DMLC_ATTRIBUTE_UNUSED ::dmlc::json::AnyJSONManager&   \
-  __make_AnyJSONType ## _ ## KeyName ## __
+  static ::dmlc::json::AnyJSONManager&  __make_AnyJSONType ## _ ## KeyName ## __
 
 /*!
  * \def DMLC_JSON_ENABLE_ANY
@@ -478,7 +475,7 @@ struct Handler {
   }
 };
 
-#if DMLC_STRICT_CXX11
+#if DMLC_USE_CXX11
 // Manager to store json serialization strategy.
 class AnyJSONManager {
  public:
@@ -564,7 +561,7 @@ struct Handler<any> {
     CHECK(!reader->NextArrayItem()) << "invalid any json format";
   }
 };
-#endif  // DMLC_STRICT_CXX11
+#endif  // DMLC_USE_CXX11
 
 }  // namespace json
 
diff --git a/nnvm/include/dmlc/parameter.h b/nnvm/include/dmlc/parameter.h
index 2fbab2a44e32..4ff99f860cc3 100644
--- a/nnvm/include/dmlc/parameter.h
+++ b/nnvm/include/dmlc/parameter.h
@@ -251,8 +251,7 @@ struct Parameter {
     static ::dmlc::parameter::ParamManagerSingleton<PType> inst(#PType); \
     return &inst.manager;                                               \
   }                                                                     \
-  static DMLC_ATTRIBUTE_UNUSED ::dmlc::parameter::ParamManager&         \
-  __make__ ## PType ## ParamManager__ =                                 \
+  static ::dmlc::parameter::ParamManager &__make__ ## PType ## ParamManager__ = \
       (*PType::__MANAGER__())                                           \
 
 //! \endcond
diff --git a/nnvm/include/dmlc/registry.h b/nnvm/include/dmlc/registry.h
index 380b31cd3d61..67fbc43ded68 100644
--- a/nnvm/include/dmlc/registry.h
+++ b/nnvm/include/dmlc/registry.h
@@ -216,7 +216,7 @@ class FunctionRegEntryBase {
  * \sa FactoryRegistryEntryBase
  */
 #define DMLC_REGISTRY_REGISTER(EntryType, EntryTypeName, Name)          \
-  static DMLC_ATTRIBUTE_UNUSED EntryType & __make_ ## EntryTypeName ## _ ## Name ## __ = \
+  static EntryType & __make_ ## EntryTypeName ## _ ## Name ## __ =      \
       ::dmlc::Registry<EntryType>::Get()->__REGISTER__(#Name)           \
 
 /*!
@@ -272,7 +272,6 @@ class FunctionRegEntryBase {
  */
 #define DMLC_REGISTRY_LINK_TAG(UniqueTag)                                \
   int __dmlc_registry_file_tag_ ## UniqueTag ## __();                   \
-  static int DMLC_ATTRIBUTE_UNUSED __reg_file_tag_ ## UniqueTag ## __ = \
-      __dmlc_registry_file_tag_ ## UniqueTag ## __();
+  static int __reg_file_tag_ ## UniqueTag ## __ = __dmlc_registry_file_tag_ ## UniqueTag ## __();
 }  // namespace dmlc
 #endif  // DMLC_REGISTRY_H_
diff --git a/nnvm/include/nnvm/node.h b/nnvm/include/nnvm/node.h
index f42e272305cb..470d4d576381 100644
--- a/nnvm/include/nnvm/node.h
+++ b/nnvm/include/nnvm/node.h
@@ -17,6 +17,7 @@ namespace nnvm {
 
 // Forward declare node.
 class Node;
+
 /*!
  * \brief we always used NodePtr for a reference pointer
  *  to the node, so this alias can be changed in case.
@@ -47,6 +48,8 @@ struct NodeEntry {
 struct NodeAttrs {
   /*! \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;
   /*!
@@ -105,7 +108,7 @@ inline uint32_t Node::num_outputs() const {
   if (this->op->get_num_outputs == nullptr) {
     return this->op->num_outputs;
   } else {
-    return this->op->get_num_outputs(*this);
+    return this->op->get_num_outputs(this->attrs);
   }
 }
 
@@ -114,7 +117,7 @@ inline uint32_t Node::num_inputs() const {
   if (this->op->get_num_inputs == nullptr) {
     return this->op->num_inputs;
   } else {
-    return this->op->get_num_inputs(*this);
+    return this->op->get_num_inputs(this->attrs);
   }
 }
 
diff --git a/nnvm/include/nnvm/op.h b/nnvm/include/nnvm/op.h
index 150f36471651..721e8e736e09 100644
--- a/nnvm/include/nnvm/op.h
+++ b/nnvm/include/nnvm/op.h
@@ -102,16 +102,16 @@ class Op {
   uint32_t num_outputs = 1;
   /*!
    * \brief get number of outputs given information about the node.
-   * \param n The node
+   * \param attrs The attribute of the node
    * \return number of outputs.
    */
-  std::function<uint32_t(const Node& n)> get_num_outputs = nullptr;
+  std::function<uint32_t(const NodeAttrs& attrs)> get_num_outputs = nullptr;
   /*!
    * \brief get number of inputs given information about the node.
-   * \param n The node
+   * \param attrs The attribute of the node
    * \return number of inputs
    */
-  std::function<uint32_t(const Node& n)> get_num_inputs = nullptr;
+  std::function<uint32_t(const NodeAttrs& attrs)> get_num_inputs = nullptr;
   /*!
    * \brief Attribute parser to parse the NodeAttrs information.
    *
@@ -136,11 +136,11 @@ class Op {
    *     attrs->parsed = std::move(param);
    *  }
    *  // The other function that can utilize the parsed result.
-   *  TShape SumInferShape(const NodePtr& ptr,
+   *  TShape SumInferShape(const NodeAttrs& attrs,
    *                       const std::vector<TShape>& ishapes) {
    *     // we can use the parsed version of param
    *     // without repeatively parsing the parameter
-   *     const SumParam& param = nnvm::get<SumParam>(ptr->attrs.parsed);
+   *     const SumParam& param = nnvm::get<SumParam>(attrs.parsed);
    *  }
    * \endcode
    */
@@ -180,7 +180,7 @@ class Op {
    * \param fn The function to be set.
    * \return reference to self.
    */
-  inline Op& set_num_inputs(std::function<uint32_t (const Node& n)> fn);  // NOLINT(*)
+  inline Op& set_num_inputs(std::function<uint32_t (const NodeAttrs& attr)> fn);  // NOLINT(*)
   /*!
    * \brief Set the num_outputs
    * \param n The number of outputs to be set.
@@ -192,7 +192,7 @@ class Op {
    * \param fn The function to be set.
    * \return reference to self.
    */
-  inline Op& set_num_outputs(std::function<uint32_t (const Node& n)> fn);  // NOLINT(*)
+  inline Op& set_num_outputs(std::function<uint32_t (const NodeAttrs& attr)> fn);  // NOLINT(*)
   /*!
    * \brief Set the attr_parser function.
    * \param fn The number of outputs to be set.
@@ -279,8 +279,10 @@ class OpMap {
 };
 
 // internal macros to make
+#define NNVM_STR_CONCAT_(__x, __y) __x##__y
+#define NNVM_STR_CONCAT(__x, __y) NNVM_STR_CONCAT_(__x, __y)
 #define NNVM_REGISTER_VAR_DEF(OpName)                              \
-  static DMLC_ATTRIBUTE_UNUSED ::nnvm::Op & __make_ ## NnvmOp ## _ ## OpName
+  static ::nnvm::Op & __make_ ## NnvmOp ## _ ## OpName
 
 /*!
  * \def NNVM_REGISTER_OP
@@ -298,7 +300,7 @@ class OpMap {
  * \endcode
  */
 #define NNVM_REGISTER_OP(OpName)                                     \
-  DMLC_STR_CONCAT(NNVM_REGISTER_VAR_DEF(OpName), __COUNTER__) =   \
+  NNVM_STR_CONCAT(NNVM_REGISTER_VAR_DEF(OpName), __COUNTER__) =   \
       ::dmlc::Registry<::nnvm::Op>::Get()->__REGISTER_OR_GET__(#OpName)
 
 // implementations of template functions after this.
@@ -375,7 +377,7 @@ inline Op& Op::set_num_inputs(uint32_t n) {  // NOLINT(*)
   return *this;
 }
 
-inline Op& Op::set_num_inputs(std::function<uint32_t (const Node& n)> fn) {  // NOLINT(*)
+inline Op& Op::set_num_inputs(std::function<uint32_t (const NodeAttrs& attr)> fn) {  // NOLINT(*)
   this->get_num_inputs = fn;
   return *this;
 }
@@ -385,7 +387,7 @@ inline Op& Op::set_num_outputs(uint32_t n) {  // NOLINT(*)
   return *this;
 }
 
-inline Op& Op::set_num_outputs(std::function<uint32_t (const Node& n)> fn) {  // NOLINT(*)
+inline Op& Op::set_num_outputs(std::function<uint32_t (const NodeAttrs& attr)> fn) {  // NOLINT(*)
   this->get_num_outputs = fn;
   return *this;
 }
diff --git a/nnvm/include/nnvm/op_attr_types.h b/nnvm/include/nnvm/op_attr_types.h
index b1cb44d64df6..675b93a6c9d2 100644
--- a/nnvm/include/nnvm/op_attr_types.h
+++ b/nnvm/include/nnvm/op_attr_types.h
@@ -12,7 +12,6 @@
 #include <functional>
 #include "./base.h"
 #include "./tuple.h"
-#include "./node.h"
 
 namespace nnvm {
 
@@ -22,34 +21,34 @@ namespace nnvm {
 /*!
  * \brief Return list of input arguments names of each operator.
  *
- * \param n The node.
+ * \param attrs The attributes of the node.
  * \return list of inputs
  * \note Register under "FListInputNames", default return {"data"}.
  *
  *  FListInputNames enables automatic variable creation for missing arguments.
  */
-using FListInputNames = std::function<std::vector<std::string> (const Node& n)>;
+using FListInputNames = std::function<std::vector<std::string> (const NodeAttrs& attrs)>;
 
 /*!
  * \brief Return list of output arguments names of each operator.
  *
- * \param n The node.
+ * \param attrs The attributes of the node.
  * \return list of inputs
  * \note Register under "FListOutputNames", default return {"outputs"}.
  *
  *  FListOutputNames customized naming for operator outputs.
  */
-using FListOutputNames = std::function<std::vector<std::string> (const Node& n)>;
+using FListOutputNames = std::function<std::vector<std::string> (const NodeAttrs& attrs)>;
 
 /*!
  * \brief Check whether operator will mutate k-th input.
- * \param n The node.
+ * \param attrs The attributes of the node.
  * \return list of input indices it mutates.
  *
  * \note Register under "FMutateInputs", default return false
  * FMutateInputs enables mutation order handling correctly.
  */
-using FMutateInputs = std::function<std::vector<uint32_t> (const Node& n)>;
+using FMutateInputs = std::function<std::vector<uint32_t> (const NodeAttrs& attrs)>;
 
 /*!
  * \brief Inference function of certain type.
@@ -57,9 +56,9 @@ using FMutateInputs = std::function<std::vector<uint32_t> (const Node& n)>;
  * \return whether all attributes are inferred.
  */
 template<typename AttrType>
-using FInferNodeEntryAttr = std::function<bool (const Node& n,
-                                                std::vector<AttrType> *in_ptr,
-                                                std::vector<AttrType> *out_ptr)>;
+using FInferNodeEntryAttr = std::function<bool (const NodeAttrs& attrs,
+                                                std::vector<AttrType> *in_attrs,
+                                                std::vector<AttrType> *out_attrs)>;
 /*!
  * \brief Shape inference function.
  *  Update the shapes given the input shape information.
@@ -97,7 +96,7 @@ using TIsBackwardOp = bool;
 /*!
  * \brief Get possible inplace options.
  *  This function enables optimization to reuse memory of inputs in output.
- * \param n The node
+ * \param attrs The attributes of the node
  * \param in_data The input data.
  * \param out_data The output data.
  * \return list of pair of that maps input->output,
@@ -106,20 +105,7 @@ using TIsBackwardOp = bool;
  * \note Register under "FInplaceOption", by default no inplace can happen.
  */
 using FInplaceOption = std::function<
-  std::vector<std::pair<int, int> > (const Node& n)>;
-
-/*!
- * \brief Get the gradient node of the op node
- *  This function generates the backward graph of the node
- * \param nodeptr The node to take gradient
- * \param out_grads Gradient of current node's outputs
- * \return gradients of the inputs
- *
- * \note Register under "FGradient"
- */
-using FGradient = std::function<std::vector<NodeEntry>(
-    const NodePtr& nodeptr,
-    const std::vector<NodeEntry>& out_grads)>;
+  std::vector<std::pair<int, int> > (const NodeAttrs& attrs)>;
 
 }  // namespace nnvm
 
diff --git a/nnvm/include/nnvm/pass.h b/nnvm/include/nnvm/pass.h
index fb97cea5ae0b..438226f5c93f 100644
--- a/nnvm/include/nnvm/pass.h
+++ b/nnvm/include/nnvm/pass.h
@@ -23,7 +23,7 @@ namespace nnvm {
  * \param src The graph to be transformed.
  * \return The generated graph.
  */
-using PassFunction = std::function<Graph (Graph src)>;
+typedef std::function<Graph (Graph src)> PassFunction;
 
 /*!
  * \brief Apply a series of pass transformations on g.
diff --git a/nnvm/include/nnvm/pass_functions.h b/nnvm/include/nnvm/pass_functions.h
index 25f5b26957f8..8cca33e97cfd 100644
--- a/nnvm/include/nnvm/pass_functions.h
+++ b/nnvm/include/nnvm/pass_functions.h
@@ -11,11 +11,9 @@
 #define NNVM_PASS_FUNCTIONS_H_
 
 #include <string>
-#include <vector>
 #include <memory>
 #include "./base.h"
 #include "./pass.h"
-#include "./node.h"
 #include "./graph_attr_types.h"
 
 namespace nnvm {
@@ -111,33 +109,6 @@ inline Graph PlaceDevice(Graph graph,
   return ApplyPass(std::move(graph), {"PlaceDevice"});
 }
 
-/*!
- * \brief Get the gradient graph whose outputs are gradients of xs wrt to ys.
- * \param graph source graph
- * \param ys The entries we want to take gradient from.
- * \param xs The input we want to
- * \param aggregate_fun aggregation function applied to aggregate the inputs
- * \param mirror_fun Optional mirror function to do mirror optimization and save memory.
- * \return A new graph, whose outputs corresponds to inputs of xs.
- */
-inline Graph Gradient(
-    Graph graph,
-    std::vector<NodeEntry> ys,
-    std::vector<NodeEntry> xs,
-    std::function<NodeEntry(std::vector<NodeEntry>&& inputs)> aggregate_fun = nullptr,
-    std::function<int(const Node& node)> mirror_fun = nullptr) {
-  graph.attrs["grad_ys"] = std::make_shared<any>(std::move(ys));
-  graph.attrs["grad_xs"] = std::make_shared<any>(std::move(xs));
-  if (aggregate_fun != nullptr) {
-    graph.attrs["grad_aggregate_fun"] = std::make_shared<any>(aggregate_fun);
-  }
-  if (mirror_fun != nullptr) {
-    graph.attrs["grad_mirror_fun"] = std::make_shared<any>(mirror_fun);
-  }
-
-  return ApplyPass(std::move(graph), {"Gradient"});
-}
-
 }  // namespace pass
 }  // namespace nnvm
 #endif  // NNVM_PASS_FUNCTIONS_H_
diff --git a/nnvm/src/core/graph.cc b/nnvm/src/core/graph.cc
index f6f1faa5e0bb..2e0160072612 100644
--- a/nnvm/src/core/graph.cc
+++ b/nnvm/src/core/graph.cc
@@ -68,7 +68,7 @@ IndexedGraph::IndexedGraph(const Graph &g) {
         iptr + inputs_rptr[nid], iptr + inputs_rptr[nid + 1]);
     if (nodes_[nid].source->op != nullptr &&
         fmutate_inputs.count(nodes_[nid].source->op)) {
-      for (uint32_t i : fmutate_inputs[nodes_[nid].source->op](*(nodes_[nid].source))) {
+      for (uint32_t i : fmutate_inputs[nodes_[nid].source->op](nodes_[nid].source->attrs)) {
         mutable_input_nodes_.insert(nodes_[nid].inputs[i].node_id);
       }
     }
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index d839f2b9bf5b..d595880aed1c 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -38,7 +38,7 @@ inline void UpdateNodeVersion(Node *n) {
     }
   }
   if (fmutate_inputs.count(n->op) != 0) {
-    for (uint32_t i : fmutate_inputs[n->op](*n)) {
+    for (uint32_t i : fmutate_inputs[n->op](n->attrs)) {
       NodeEntry& e = n->inputs[i];
       CHECK(e.node->is_variable())
           << "Mutation target can only be Variable";
@@ -197,7 +197,7 @@ std::vector<std::string> Symbol::ListInputNames(ListInputOption option) const {
         if (node->is_variable()) {
           vlist.push_back(node.get());
         } else if (fmutate_inputs.count(node->op)) {
-          for (uint32_t i : fmutate_inputs[node->op](*node)){
+          for (uint32_t i : fmutate_inputs[node->op](node->attrs)){
             mutable_set.insert(node->inputs[i].node.get());
           }
         }
@@ -223,7 +223,7 @@ std::vector<std::string> Symbol::ListOutputNames() const {
       std::string rname;
       FListOutputNames fn = flist_ouputs.get(head.node->op, nullptr);
       if (fn != nullptr) {
-        rname = fn(*head.node)[head.index];
+        rname = fn(head.node->attrs)[head.index];
       } else {
         rname = "output";
         if (head.node->num_outputs() != 1) {
@@ -279,7 +279,7 @@ void Symbol::Compose(const array_view<const Symbol*>& args,
       // switch to keyword argument matching
       if (args.size() != n_req) {
         FListInputNames fn = flist_inputs.get(n->op, nullptr);
-        auto arg_names = (fn == nullptr) ? std::vector<std::string>{"data"} : fn(*n);
+        auto arg_names = (fn == nullptr) ? std::vector<std::string>{"data"} : fn(n->attrs);
         if (arg_names.size() != n_req) {
           LOG(FATAL) << "Not enough argument to call operator " << outputs[0].node->op->name;
         }
diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index 13e225744416..5978ecdb79f2 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -75,7 +75,7 @@ Graph InferAttr(Graph &&ret,
         oshape[i] = rshape[idx.entry_id(nid, i)];
       }
       num_unknown +=
-          !(finfer_shape[inode.source->op](*inode.source, &ishape, &oshape));
+          !(finfer_shape[inode.source->op](inode.source->attrs, &ishape, &oshape));
       for (uint32_t i = 0; i < num_inputs; ++i) {
         rshape[idx.entry_id(inode.inputs[i])] = ishape[i];
       }
diff --git a/nnvm/src/pass/order_mutation.cc b/nnvm/src/pass/order_mutation.cc
index eddd5727b31b..3bcfd9922d53 100644
--- a/nnvm/src/pass/order_mutation.cc
+++ b/nnvm/src/pass/order_mutation.cc
@@ -44,7 +44,7 @@ Graph OrderMutation(const Graph& src) {
     static auto& fmutate_inputs = Op::GetAttr<FMutateInputs>("FMutateInputs");
     std::vector<uint32_t> mutate_inputs;
     if (!n->is_variable() && fmutate_inputs.count(n->op)) {
-      mutate_inputs = fmutate_inputs[n->op](*n);
+      mutate_inputs = fmutate_inputs[n->op](n->attrs);
     }
     std::sort(mutate_inputs.begin(), mutate_inputs.end());
 
@@ -102,7 +102,7 @@ Graph OrderMutation(const Graph& src) {
     static auto& fmutate_inputs = Op::GetAttr<FMutateInputs>("FMutateInputs");
     std::vector<uint32_t> mutate_inputs;
     if (fmutate_inputs.count(kv.first->op)) {
-      mutate_inputs = fmutate_inputs[kv.first->op](*kv.first);
+      mutate_inputs = fmutate_inputs[kv.first->op](kv.first->attrs);
     }
     std::sort(mutate_inputs.begin(), mutate_inputs.end());
 
diff --git a/nnvm/src/pass/plan_memory.cc b/nnvm/src/pass/plan_memory.cc
index 4541e7de5937..14a88d217de8 100644
--- a/nnvm/src/pass/plan_memory.cc
+++ b/nnvm/src/pass/plan_memory.cc
@@ -169,7 +169,7 @@ Graph PlanMemory(Graph ret) {
     if (inode.source->is_variable()) continue;
     // check inplace option
     if (finplace_option.count(inode.source->op) != 0) {
-      auto inplace_pairs = finplace_option[inode.source->op](*inode.source);
+      auto inplace_pairs = finplace_option[inode.source->op](inode.source->attrs);
       for (auto& kv : inplace_pairs) {
         uint32_t eid_out = idx.entry_id(nid, kv.second);
         uint32_t eid_in = idx.entry_id(inode.inputs[kv.first]);

From d26e7c0ac592667da271d6ebc17c1a707b66fdec Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 27 Aug 2016 09:45:16 -0700
Subject: [PATCH 360/949] [PASS] Add gradient pass (#28)

---
 nnvm/example/src/operator.cc       |  77 +++++++++++++--
 nnvm/include/dmlc/base.h           |  13 +++
 nnvm/include/dmlc/json.h           |   9 +-
 nnvm/include/dmlc/parameter.h      |   3 +-
 nnvm/include/dmlc/registry.h       |   5 +-
 nnvm/include/nnvm/c_api.h          |  17 ++++
 nnvm/include/nnvm/op.h             |   6 +-
 nnvm/include/nnvm/op_attr_types.h  |  14 +++
 nnvm/include/nnvm/pass_functions.h |  31 +++++++
 nnvm/python/nnvm/graph.py          |  23 ++++-
 nnvm/src/c_api/c_api_graph.cc      |  11 +++
 nnvm/src/pass/gradient.cc          | 144 +++++++++++++++++++++++++++++
 nnvm/tests/python/test_gradient.py |  24 +++++
 13 files changed, 357 insertions(+), 20 deletions(-)
 create mode 100644 nnvm/src/pass/gradient.cc
 create mode 100644 nnvm/tests/python/test_gradient.py

diff --git a/nnvm/example/src/operator.cc b/nnvm/example/src/operator.cc
index c0c729cdb4dd..674893a55549 100644
--- a/nnvm/example/src/operator.cc
+++ b/nnvm/example/src/operator.cc
@@ -15,6 +15,10 @@ 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;
@@ -37,6 +41,17 @@ 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->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")
@@ -84,21 +99,67 @@ NNVM_REGISTER_OP(cast)
       return true;
     });
 
+NNVM_REGISTER_OP(exp)
+.describe("take exponential")
+.set_num_inputs(1)
+.attr<FInferShape>("FInferShape", SameShape)
+.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}})
+      };
+    });
+
+NNVM_REGISTER_OP(identity)
+.describe("identity function")
+.set_num_inputs(1)
+.attr<FInferShape>("FInferShape", SameShape)
+.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)
 .attr<FInferShape>("FInferShape", SameShape)
-.attr<FInplaceOption>("FInplaceOption", InplaceIn0Out0);
+.attr<FInplaceOption>("FInplaceOption", InplaceIn0Out0)
+.attr<FGradient>(
+    "FGradient", [](const NodePtr& n,
+                    const std::vector<NodeEntry>& ograds){
+      return std::vector<NodeEntry>{ograds[0], ograds[0]};
+    });
 
-NNVM_REGISTER_OP(__add_symbol__)
-.describe("Alias of add")
-.set_num_inputs(2);
+NNVM_REGISTER_OP(mul)
+.describe("multiply two data together")
+.set_num_inputs(2)
+.attr<FInferShape>("FInferShape", SameShape)
+.attr<FInplaceOption>("FInplaceOption", InplaceIn0Out0)
+.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(exp)
-.describe("take exponential")
-.set_num_inputs(1)
-.attr<FInferShape>("FInferShape", SameShape);
+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.")
diff --git a/nnvm/include/dmlc/base.h b/nnvm/include/dmlc/base.h
index 5b34fd6b4e34..9eca4135f119 100644
--- a/nnvm/include/dmlc/base.h
+++ b/nnvm/include/dmlc/base.h
@@ -58,6 +58,11 @@
                         __cplusplus >= 201103L || defined(_MSC_VER))
 #endif
 
+/*! \brief strict CXX11 support */
+#ifndef DMLC_STRICT_CXX11
+#define DMLC_STRICT_CXX11 (__cplusplus >= 201103L || defined(_MSC_VER))
+#endif
+
 /// check if g++ is before 4.6
 #if DMLC_USE_CXX11 && defined(__GNUC__) && !defined(__clang_version__)
 #if __GNUC__ == 4 && __GNUC_MINOR__ < 6
@@ -69,6 +74,7 @@
 #endif
 #endif
 
+
 /*!
  * \brief Enable std::thread related modules,
  *  Used to disable some module in mingw compile.
@@ -82,6 +88,13 @@
 #define DMLC_USE_REGEX (__cplusplus >= 201103L || defined(_MSC_VER))
 #endif
 
+/*! \brief helper macro to supress unused warning */
+#if defined(__GNUC__)
+#define DMLC_ATTRIBUTE_UNUSED __attribute__((unused))
+#else
+#define DMLC_ATTRIBUTE_UNUSED
+#endif
+
 /*! \brief helper macro to generate string concat */
 #define DMLC_STR_CONCAT_(__x, __y) __x##__y
 #define DMLC_STR_CONCAT(__x, __y) DMLC_STR_CONCAT_(__x, __y)
diff --git a/nnvm/include/dmlc/json.h b/nnvm/include/dmlc/json.h
index 2daa0aaa017f..1934aee6a2ce 100644
--- a/nnvm/include/dmlc/json.h
+++ b/nnvm/include/dmlc/json.h
@@ -25,7 +25,9 @@
 #include <typeindex>
 #include <typeinfo>
 #include <unordered_map>
+#if DMLC_STRICT_CXX11
 #include "./any.h"
+#endif  // DMLC_STRICT_CXX11
 #endif  // DMLC_USE_CXX11
 
 namespace dmlc {
@@ -320,7 +322,8 @@ class JSONObjectReadHelper {
 };
 
 #define DMLC_JSON_ENABLE_ANY_VAR_DEF(KeyName)                  \
-  static ::dmlc::json::AnyJSONManager&  __make_AnyJSONType ## _ ## KeyName ## __
+  static DMLC_ATTRIBUTE_UNUSED ::dmlc::json::AnyJSONManager&   \
+  __make_AnyJSONType ## _ ## KeyName ## __
 
 /*!
  * \def DMLC_JSON_ENABLE_ANY
@@ -475,7 +478,7 @@ struct Handler {
   }
 };
 
-#if DMLC_USE_CXX11
+#if DMLC_STRICT_CXX11
 // Manager to store json serialization strategy.
 class AnyJSONManager {
  public:
@@ -561,7 +564,7 @@ struct Handler<any> {
     CHECK(!reader->NextArrayItem()) << "invalid any json format";
   }
 };
-#endif  // DMLC_USE_CXX11
+#endif  // DMLC_STRICT_CXX11
 
 }  // namespace json
 
diff --git a/nnvm/include/dmlc/parameter.h b/nnvm/include/dmlc/parameter.h
index 4ff99f860cc3..2fbab2a44e32 100644
--- a/nnvm/include/dmlc/parameter.h
+++ b/nnvm/include/dmlc/parameter.h
@@ -251,7 +251,8 @@ struct Parameter {
     static ::dmlc::parameter::ParamManagerSingleton<PType> inst(#PType); \
     return &inst.manager;                                               \
   }                                                                     \
-  static ::dmlc::parameter::ParamManager &__make__ ## PType ## ParamManager__ = \
+  static DMLC_ATTRIBUTE_UNUSED ::dmlc::parameter::ParamManager&         \
+  __make__ ## PType ## ParamManager__ =                                 \
       (*PType::__MANAGER__())                                           \
 
 //! \endcond
diff --git a/nnvm/include/dmlc/registry.h b/nnvm/include/dmlc/registry.h
index 67fbc43ded68..380b31cd3d61 100644
--- a/nnvm/include/dmlc/registry.h
+++ b/nnvm/include/dmlc/registry.h
@@ -216,7 +216,7 @@ class FunctionRegEntryBase {
  * \sa FactoryRegistryEntryBase
  */
 #define DMLC_REGISTRY_REGISTER(EntryType, EntryTypeName, Name)          \
-  static EntryType & __make_ ## EntryTypeName ## _ ## Name ## __ =      \
+  static DMLC_ATTRIBUTE_UNUSED EntryType & __make_ ## EntryTypeName ## _ ## Name ## __ = \
       ::dmlc::Registry<EntryType>::Get()->__REGISTER__(#Name)           \
 
 /*!
@@ -272,6 +272,7 @@ class FunctionRegEntryBase {
  */
 #define DMLC_REGISTRY_LINK_TAG(UniqueTag)                                \
   int __dmlc_registry_file_tag_ ## UniqueTag ## __();                   \
-  static int __reg_file_tag_ ## UniqueTag ## __ = __dmlc_registry_file_tag_ ## UniqueTag ## __();
+  static int DMLC_ATTRIBUTE_UNUSED __reg_file_tag_ ## UniqueTag ## __ = \
+      __dmlc_registry_file_tag_ ## UniqueTag ## __();
 }  // namespace dmlc
 #endif  // DMLC_REGISTRY_H_
diff --git a/nnvm/include/nnvm/c_api.h b/nnvm/include/nnvm/c_api.h
index 1c6943f9c681..f92470111594 100644
--- a/nnvm/include/nnvm/c_api.h
+++ b/nnvm/include/nnvm/c_api.h
@@ -260,6 +260,7 @@ NNVM_DLL int NNGraphFree(GraphHandle handle);
  * \return 0 when success, -1 when failure happens
  */
 NNVM_DLL int NNGraphGetSymbol(GraphHandle graph, SymbolHandle *symbol);
+
 /*!
  * \brief Get Set a attribute in json format.
  * This feature allows pass graph attributes back and forth in reasonable speed.
@@ -273,6 +274,7 @@ NNVM_DLL int NNGraphGetSymbol(GraphHandle graph, SymbolHandle *symbol);
 NNVM_DLL int NNGraphSetJSONAttr(GraphHandle handle,
                                 const char* key,
                                 const char* json_value);
+
 /*!
  * \brief Get a serialized attrirbute from graph.
  * This feature allows pass graph attributes back and forth in reasonable speed.
@@ -289,6 +291,21 @@ NNVM_DLL int NNGraphGetJSONAttr(SymbolHandle handle,
                                 const char* key,
                                 const char** json_out,
                                 int *success);
+
+/*!
+ * \brief Set a attribute whose type is std::vector<NodeEntry> in c++
+ * This feature allows pass List of symbolic variables for gradient request.
+ *
+ * \note This is beta feature only used for test purpos
+ *
+ * \param handle The graph handle.
+ * \param key The key to the attribute.
+ * \param list The symbol whose outputs represents the list of NodeEntry to be passed.
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNGraphSetNodeEntryListAttr_(GraphHandle handle,
+                                          const char* key,
+                                          SymbolHandle list);
 /*!
  * \brief Apply pass on the src graph.
  * \param src The source graph handle.
diff --git a/nnvm/include/nnvm/op.h b/nnvm/include/nnvm/op.h
index 721e8e736e09..e49bc9ae6643 100644
--- a/nnvm/include/nnvm/op.h
+++ b/nnvm/include/nnvm/op.h
@@ -279,10 +279,8 @@ class OpMap {
 };
 
 // internal macros to make
-#define NNVM_STR_CONCAT_(__x, __y) __x##__y
-#define NNVM_STR_CONCAT(__x, __y) NNVM_STR_CONCAT_(__x, __y)
 #define NNVM_REGISTER_VAR_DEF(OpName)                              \
-  static ::nnvm::Op & __make_ ## NnvmOp ## _ ## OpName
+  static DMLC_ATTRIBUTE_UNUSED ::nnvm::Op & __make_ ## NnvmOp ## _ ## OpName
 
 /*!
  * \def NNVM_REGISTER_OP
@@ -300,7 +298,7 @@ class OpMap {
  * \endcode
  */
 #define NNVM_REGISTER_OP(OpName)                                     \
-  NNVM_STR_CONCAT(NNVM_REGISTER_VAR_DEF(OpName), __COUNTER__) =   \
+  DMLC_STR_CONCAT(NNVM_REGISTER_VAR_DEF(OpName), __COUNTER__) =         \
       ::dmlc::Registry<::nnvm::Op>::Get()->__REGISTER_OR_GET__(#OpName)
 
 // implementations of template functions after this.
diff --git a/nnvm/include/nnvm/op_attr_types.h b/nnvm/include/nnvm/op_attr_types.h
index 675b93a6c9d2..d3129f978cc2 100644
--- a/nnvm/include/nnvm/op_attr_types.h
+++ b/nnvm/include/nnvm/op_attr_types.h
@@ -11,6 +11,7 @@
 #include <utility>
 #include <functional>
 #include "./base.h"
+#include "./node.h"
 #include "./tuple.h"
 
 namespace nnvm {
@@ -107,6 +108,19 @@ using TIsBackwardOp = bool;
 using FInplaceOption = std::function<
   std::vector<std::pair<int, int> > (const NodeAttrs& attrs)>;
 
+/*!
+ * \brief Get the gradient node of the op node
+ *  This function generates the backward graph of the node
+ * \param nodeptr The node to take gradient
+ * \param out_grads Gradient of current node's outputs
+ * \return gradients of the inputs
+ *
+ * \note Register under "FGradient"
+ */
+using FGradient = std::function<std::vector<NodeEntry>(
+    const NodePtr& nodeptr,
+    const std::vector<NodeEntry>& out_grads)>;
+
 }  // namespace nnvm
 
 #endif  // NNVM_OP_ATTR_TYPES_H_
diff --git a/nnvm/include/nnvm/pass_functions.h b/nnvm/include/nnvm/pass_functions.h
index 8cca33e97cfd..a2cca949ff5b 100644
--- a/nnvm/include/nnvm/pass_functions.h
+++ b/nnvm/include/nnvm/pass_functions.h
@@ -109,6 +109,37 @@ inline Graph PlaceDevice(Graph graph,
   return ApplyPass(std::move(graph), {"PlaceDevice"});
 }
 
+/*!
+ * \brief Get the gradient graph whose outputs are gradients of xs wrt to ys.
+ * \param graph source graph
+ * \param ys The entries we want to take gradient from.
+ * \param xs The input to take gradient with respect to.
+ * \param ys_out_grad The symbol for additional gradient to be propagate back to y.
+ * \param aggregate_fun aggregation function applied to aggregate the inputs
+ * \param mirror_fun Optional mirror function to do mirror optimization and save memory.
+ * \return A new graph, whose outputs corresponds to inputs of xs.
+ */
+inline Graph Gradient(
+    Graph graph,
+    std::vector<NodeEntry> ys,
+    std::vector<NodeEntry> xs,
+    std::vector<NodeEntry> ys_out_grad,
+    std::function<NodeEntry(std::vector<NodeEntry>&& inputs)> aggregate_fun = nullptr,
+    std::function<int(const Node& node)> mirror_fun = nullptr) {
+  graph.attrs["grad_ys"] = std::make_shared<any>(std::move(ys));
+
+  graph.attrs["grad_xs"] = std::make_shared<any>(std::move(xs));
+  graph.attrs["grad_ys_out_grad"] = std::make_shared<any>(std::move(ys_out_grad));
+  if (aggregate_fun != nullptr) {
+    graph.attrs["grad_aggregate_fun"] = std::make_shared<any>(aggregate_fun);
+  }
+  if (mirror_fun != nullptr) {
+    graph.attrs["grad_mirror_fun"] = std::make_shared<any>(mirror_fun);
+  }
+
+  return ApplyPass(std::move(graph), {"Gradient"});
+}
+
 }  // namespace pass
 }  // namespace nnvm
 #endif  // NNVM_PASS_FUNCTIONS_H_
diff --git a/nnvm/python/nnvm/graph.py b/nnvm/python/nnvm/graph.py
index 3f184928a9ec..e3e857eecbf3 100644
--- a/nnvm/python/nnvm/graph.py
+++ b/nnvm/python/nnvm/graph.py
@@ -10,7 +10,7 @@
 from ._base import c_array, c_str, nn_uint, py_str, string_types
 from ._base import GraphHandle, SymbolHandle
 from ._base import check_call
-from .symbol import Symbol
+from .symbol import Symbol, Group as _Group
 
 
 class Graph(object):
@@ -56,8 +56,27 @@ def json_attr(self, key):
         else:
             return None
 
+    def _set_symbol_list_attr(self, key, value):
+        """Set the attribute of the graph.
+
+        Parameters
+        ----------
+        key : string
+            The key of the attribute
+        value : value
+            The any type that can be dumped to json
+        type_name : string
+            The typename registered on c++ side.
+        """
+        if isinstance(value, list):
+            value = _Group(value)
+        if not isinstance(value, Symbol):
+            raise ValueError("value need to be grouped symbol")
+        check_call(_LIB.NNGraphSetNodeEntryListAttr_(
+            self.handle, c_str(key), value.handle))
+
     def _set_json_attr(self, key, value, type_name=None):
-        """Set the attribute of the symbol.
+        """Set the attribute of the graph.
 
         Parameters
         ----------
diff --git a/nnvm/src/c_api/c_api_graph.cc b/nnvm/src/c_api/c_api_graph.cc
index c3de70618b1f..d3dd1d3e49aa 100644
--- a/nnvm/src/c_api/c_api_graph.cc
+++ b/nnvm/src/c_api/c_api_graph.cc
@@ -35,6 +35,17 @@ int NNGraphGetSymbol(GraphHandle graph, SymbolHandle *symbol) {
   API_END_HANDLE_ERROR(delete s);
 }
 
+int NNGraphSetNodeEntryListAttr_(GraphHandle handle,
+                                 const char* key,
+                                 SymbolHandle list) {
+  API_BEGIN();
+  Symbol* s = static_cast<Symbol*>(list);
+  Graph* g = static_cast<Graph*>(handle);
+  g->attrs[std::string(key)]
+      = std::make_shared<any>(s->outputs);
+  API_END();
+}
+
 int NNGraphSetJSONAttr(GraphHandle handle,
                        const char* key,
                        const char* json_value) {
diff --git a/nnvm/src/pass/gradient.cc b/nnvm/src/pass/gradient.cc
new file mode 100644
index 000000000000..530dd29f41c6
--- /dev/null
+++ b/nnvm/src/pass/gradient.cc
@@ -0,0 +1,144 @@
+/*!
+ *  Copyright (c) 2016 by Contributors
+ * \file gradients.cc
+ * \brief Passes that takes gradient of the graph
+ * This code code was modified based on mxnet codebase by Min Lin
+ */
+#include <nnvm/pass.h>
+#include <nnvm/op_attr_types.h>
+#include <algorithm>
+#include <functional>
+
+namespace nnvm {
+namespace pass {
+namespace {
+
+// default aggregate gradient function
+// require operator __zero__ and __ewise_sum__ to be presented.
+NodeEntry DefaultAggregateGradient(std::vector<NodeEntry>&& v) {
+  if (v.size() == 1) {
+    return std::move(v[0]);
+  } else if (v.size() == 0) {
+    NodePtr zero_node = Node::Create();
+    zero_node->op = Op::Get("__zero__");
+    return NodeEntry{zero_node, 0, 0};
+  } else {
+    NodePtr sum_node = Node::Create();
+    sum_node->op = Op::Get("__ewise_sum__");
+    sum_node->inputs = std::move(v);
+    return NodeEntry{sum_node, 0, 0};
+  }
+}
+
+// helper entry
+struct GradEntry {
+  NodeEntry sum{nullptr, 0, 0};
+  std::vector<NodeEntry> grads;
+};
+
+Graph Gradient(Graph src) {
+  using nnvm::FGradient;
+  using MirrorFun = std::function<int (const Node& node)>;
+
+  CHECK_NE(src.attrs.count("grad_ys"), 0)
+      << "Gradient require grad_ys to be presented.";
+  CHECK_NE(src.attrs.count("grad_ys_out_grad"), 0)
+      << "Gradient require grad_ys_out_grad to be presented.";
+  CHECK_NE(src.attrs.count("grad_xs"), 0)
+      << "Gradient require grad_xs to be presented.";
+  const std::vector<NodeEntry>& ys =
+      src.GetAttr<std::vector<NodeEntry> >("grad_ys");
+  const std::vector<NodeEntry>& ys_out_grad =
+      src.GetAttr<std::vector<NodeEntry> >("grad_ys_out_grad");
+  const std::vector<NodeEntry>& xs =
+      src.GetAttr<std::vector<NodeEntry> >("grad_xs");
+  using AggFun = std::function<NodeEntry (std::vector<NodeEntry>&& inputs)>;
+  AggFun agg_fun = DefaultAggregateGradient;
+  if (src.attrs.count("grad_aggregate_fun") != 0) {
+    agg_fun = src.GetAttr<AggFun>("grad_aggregate_fun");
+  }
+  MirrorFun mirror_fun = nullptr;
+  if (src.attrs.count("grad_mirror_fun") != 0) {
+    mirror_fun = src.GetAttr<MirrorFun>("grad_mirror_fun");
+  }
+
+  // topo sort
+  std::vector<NodePtr> topo_order;
+  std::unordered_map<Node*, std::vector<GradEntry> > output_grads;
+  DFSVisit(ys, [&](const NodePtr& node) {
+      if (output_grads.count(node.get()) == 0) {
+        output_grads[node.get()].resize(node->num_outputs());
+      }
+      topo_order.push_back(node);
+    });
+
+  CHECK_EQ(ys.size(), ys_out_grad.size());
+  for (size_t i = 0; i < ys.size(); ++i) {
+    output_grads[ys[i].node.get()][ys[i].index].grads = { ys_out_grad[i] };
+  }
+
+  // construct mirror reduece memory strategy if needed
+  std::unordered_map<Node*, NodePtr> mirror_map;
+  if (mirror_fun != nullptr) {
+    for (const NodePtr& n : topo_order) {
+      if (mirror_fun(*n)) {
+        NodePtr new_node = Node::Create();
+        *new_node = *n;
+        new_node->attrs.name += "_mirror";
+        for (auto& e : new_node->inputs) {
+          e.node = mirror_map.at(e.node.get());
+        }
+        for (auto& n : new_node->control_deps) {
+          n = mirror_map.at(n.get());
+        }
+        mirror_map[n.get()] = std::move(new_node);
+      } else {
+        mirror_map[n.get()] = n;
+      }
+    }
+  }
+
+  // traverse backward
+  static auto& grad_fun_map = Op::GetAttr<FGradient>("FGradient");
+  std::vector<NodeEntry> out_agg_grads;
+  for (auto rit = topo_order.rbegin(); rit != topo_order.rend(); ++rit) {
+    const NodePtr& ptr = *rit;
+    if (ptr->is_variable()) continue;
+    out_agg_grads.clear();
+    for (GradEntry& e : output_grads.at(ptr.get())) {
+      e.sum = agg_fun(std::move(e.grads));
+      out_agg_grads.push_back(e.sum);
+    }
+    std::vector<NodeEntry> input_grads = grad_fun_map[ptr->op]
+        (mirror_map.size() == 0 ? ptr : mirror_map.at(ptr.get()), out_agg_grads);
+    auto git = input_grads.begin();
+    for (auto it = (*rit)->inputs.begin(); it != (*rit)->inputs.end(); ++it, ++git) {
+      output_grads[it->node.get()][it->index].grads.emplace_back(std::move(*git));
+    }
+  }
+  // take out the xs' grads
+  Graph ret;
+  ret.outputs.reserve(xs.size());
+  for (const NodeEntry& e : xs) {
+    GradEntry& entry = output_grads[e.node.get()][e.index];
+    // aggregate sum if there haven't been
+    if (entry.sum.node.get() == nullptr) {
+      entry.sum = agg_fun(std::move(entry.grads));
+    }
+    ret.outputs.emplace_back(std::move(entry.sum));
+  }
+  return ret;
+}
+
+// register pass
+NNVM_REGISTER_PASS(Gradient)
+.describe("Return a gradient graph of src.attrs[\"ys\"] wrt src.attrs[\"xs\"]")
+.set_body(Gradient)
+.set_change_graph(true)
+.depend_graph_attr("grad_ys")
+.depend_graph_attr("grad_xs")
+.depend_graph_attr("grad_ys_out_grad");
+
+}  // namespace
+}  // namespace pass
+}  // namespace nnvm
diff --git a/nnvm/tests/python/test_gradient.py b/nnvm/tests/python/test_gradient.py
new file mode 100644
index 000000000000..5acdb1207f20
--- /dev/null
+++ b/nnvm/tests/python/test_gradient.py
@@ -0,0 +1,24 @@
+import json
+import nnvm.symbol as sym
+import nnvm.graph as graph
+
+def grad(ys, xs, ys_grads):
+    g = graph.create(ys)
+    g._set_symbol_list_attr('grad_ys', ys)
+    g._set_symbol_list_attr('grad_xs', xs)
+    g._set_symbol_list_attr('grad_ys_out_grad', ys_grads)
+    return g.apply('Gradient')
+
+def test_graph_gradient():
+    x0 = sym.Variable('x0')
+    x1 = sym.Variable('x1')
+    yg = sym.Variable('yg')
+    y = sym.exp(sym.mul(x0, x1))
+    grad_graph = grad(y, [x0], yg)
+    print("Original graph")
+    print(y.debug_str())
+    print("Gradient  graph")
+    print grad_graph.symbol.debug_str()
+
+if __name__ == "__main__":
+    test_graph_gradient()

From 96813d871e1fc9a3e7d1f22b1e55a3e83eed4b74 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 29 Aug 2016 10:03:26 -0700
Subject: [PATCH 361/949] [NODE] Move op inside node attribute (#30)

---
 nnvm/example/src/operator.cc       |  2 +-
 nnvm/include/nnvm/node.h           | 33 +++++++++++++++++-------------
 nnvm/include/nnvm/pass_functions.h |  1 +
 nnvm/src/core/graph.cc             |  6 +++---
 nnvm/src/core/symbolic.cc          | 31 ++++++++++++++--------------
 nnvm/src/pass/gradient.cc          |  6 +++---
 nnvm/src/pass/infer_shape_type.cc  |  6 +++---
 nnvm/src/pass/order_mutation.cc    |  9 ++++----
 nnvm/src/pass/place_device.cc      |  2 +-
 nnvm/src/pass/plan_memory.cc       |  4 ++--
 nnvm/src/pass/saveload_json.cc     | 12 +++++------
 11 files changed, 58 insertions(+), 54 deletions(-)

diff --git a/nnvm/example/src/operator.cc b/nnvm/example/src/operator.cc
index 674893a55549..93a1b3db29c1 100644
--- a/nnvm/example/src/operator.cc
+++ b/nnvm/example/src/operator.cc
@@ -46,7 +46,7 @@ inline NodeEntry MakeNode(const char* op_name,
                           std::string node_name,
                           std::vector<NodeEntry> inputs) {
   NodePtr p = Node::Create();
-  p->op = nnvm::Op::Get(op_name);
+  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};
diff --git a/nnvm/include/nnvm/node.h b/nnvm/include/nnvm/node.h
index 470d4d576381..a7daa0a6e887 100644
--- a/nnvm/include/nnvm/node.h
+++ b/nnvm/include/nnvm/node.h
@@ -46,6 +46,11 @@ struct NodeEntry {
  *  Usually are additional parameters like axis,
  */
 struct NodeAttrs {
+  /*!
+   * \brief The operator this node uses.
+   *  For place holder variable, op == nullptr.
+   */
+  const Op *op{nullptr};
   /*! \brief name of the node */
   std::string name;
   /*! \brief Vector representation of positional attributes */
@@ -65,11 +70,8 @@ struct NodeAttrs {
  */
 class Node {
  public:
-  /*!
-   * \brief The operator this node uses.
-   *  For place holder variable, op == nullptr.
-   */
-  const Op *op{nullptr};
+  /*! \brief The attributes in the node. */
+  NodeAttrs attrs;
   /*! \brief inputs to this node */
   std::vector<NodeEntry> inputs;
   /*!
@@ -77,10 +79,10 @@ class Node {
    *  Gives operation must be performed before this operation.
    */
   std::vector<NodePtr> control_deps;
-  /*! \brief The attributes in the node. */
-  NodeAttrs attrs;
   /*! \brief destructor of node */
   ~Node();
+  /*! \return operator in this node */
+  inline const Op* op() const;
   /*!
    * \brief return whether node is placeholder variable.
    *  This is equivalent to op == nullptr
@@ -99,25 +101,28 @@ class Node {
 };
 
 // implementation of functions.
+inline const Op* Node::op() const {
+  return this->attrs.op;
+}
 inline bool Node::is_variable() const {
-  return this->op == nullptr;
+  return this->op() == nullptr;
 }
 
 inline uint32_t Node::num_outputs() const {
   if (is_variable()) return 1;
-  if (this->op->get_num_outputs == nullptr) {
-    return this->op->num_outputs;
+  if (this->op()->get_num_outputs == nullptr) {
+    return this->op()->num_outputs;
   } else {
-    return this->op->get_num_outputs(this->attrs);
+    return this->op()->get_num_outputs(this->attrs);
   }
 }
 
 inline uint32_t Node::num_inputs() const {
   if (is_variable()) return 1;
-  if (this->op->get_num_inputs == nullptr) {
-    return this->op->num_inputs;
+  if (this->op()->get_num_inputs == nullptr) {
+    return this->op()->num_inputs;
   } else {
-    return this->op->get_num_inputs(this->attrs);
+    return this->op()->get_num_inputs(this->attrs);
   }
 }
 
diff --git a/nnvm/include/nnvm/pass_functions.h b/nnvm/include/nnvm/pass_functions.h
index a2cca949ff5b..b7068822d9ee 100644
--- a/nnvm/include/nnvm/pass_functions.h
+++ b/nnvm/include/nnvm/pass_functions.h
@@ -12,6 +12,7 @@
 
 #include <string>
 #include <memory>
+#include <vector>
 #include "./base.h"
 #include "./pass.h"
 #include "./graph_attr_types.h"
diff --git a/nnvm/src/core/graph.cc b/nnvm/src/core/graph.cc
index 2e0160072612..c500995eab70 100644
--- a/nnvm/src/core/graph.cc
+++ b/nnvm/src/core/graph.cc
@@ -66,9 +66,9 @@ IndexedGraph::IndexedGraph(const Graph &g) {
   for (size_t nid = 0; nid < nodes_.size(); ++nid) {
     nodes_[nid].inputs = array_view<NodeEntry>(
         iptr + inputs_rptr[nid], iptr + inputs_rptr[nid + 1]);
-    if (nodes_[nid].source->op != nullptr &&
-        fmutate_inputs.count(nodes_[nid].source->op)) {
-      for (uint32_t i : fmutate_inputs[nodes_[nid].source->op](nodes_[nid].source->attrs)) {
+    if (nodes_[nid].source->op() != nullptr &&
+        fmutate_inputs.count(nodes_[nid].source->op())) {
+      for (uint32_t i : fmutate_inputs[nodes_[nid].source->op()](nodes_[nid].source->attrs)) {
         mutable_input_nodes_.insert(nodes_[nid].inputs[i].node_id);
       }
     }
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index d595880aed1c..97b35648ee7d 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -20,7 +20,7 @@ struct VariableParam {
 
 NodePtr CreateVariableNode(const std::string& name) {
   NodePtr n = Node::Create();
-  n->op = nullptr;
+  n->attrs.op = nullptr;
   n->attrs.name = name;
   n->attrs.parsed = VariableParam();
   return n;
@@ -37,8 +37,8 @@ inline void UpdateNodeVersion(Node *n) {
       e.version = nnvm::get<VariableParam>(e.node->attrs.parsed).version;
     }
   }
-  if (fmutate_inputs.count(n->op) != 0) {
-    for (uint32_t i : fmutate_inputs[n->op](n->attrs)) {
+  if (fmutate_inputs.count(n->op()) != 0) {
+    for (uint32_t i : fmutate_inputs[n->op()](n->attrs)) {
       NodeEntry& e = n->inputs[i];
       CHECK(e.node->is_variable())
           << "Mutation target can only be Variable";
@@ -96,7 +96,6 @@ Symbol Symbol::Copy() const {
   // use DFSVisit to copy all the nodes
   DFSVisit(this->outputs, [&old_new](const NodePtr& node) {
       NodePtr np = Node::Create();
-      np->op = node->op;
       np->attrs = node->attrs;
       old_new[node.get()] = std::move(np);
     });
@@ -123,7 +122,7 @@ void Symbol::Print(std::ostream &os) const {
     if (outputs[0].node->is_variable()) {
       os << "Variable:" << outputs[0].node->attrs.name << '\n';
     } else {
-      os << "AtomicFunctor "<< " Op:" << outputs[0].node->op->name << '\n';
+      os << "AtomicFunctor "<< " Op:" << outputs[0].node->op()->name << '\n';
     }
   } else {
     // use DFSVisit to copy all the nodes
@@ -137,7 +136,7 @@ void Symbol::Print(std::ostream &os) const {
           os << "Variable:" << node->attrs.name << '\n';
         } else {
           os << "--------------------\n";
-          os << "Op:" << node->op->name << ", Name=" << node->attrs.name << '\n'
+          os << "Op:" << node->op()->name << ", Name=" << node->attrs.name << '\n'
              << "Inputs:\n";
           for (size_t i = 0; i < node->inputs.size(); ++i) {
             const NodeEntry& e = node->inputs[i];
@@ -196,8 +195,8 @@ std::vector<std::string> Symbol::ListInputNames(ListInputOption option) const {
     DFSVisit(this->outputs, [&ret, &mutable_set, &vlist](const NodePtr &node) {
         if (node->is_variable()) {
           vlist.push_back(node.get());
-        } else if (fmutate_inputs.count(node->op)) {
-          for (uint32_t i : fmutate_inputs[node->op](node->attrs)){
+        } else if (fmutate_inputs.count(node->op())) {
+          for (uint32_t i : fmutate_inputs[node->op()](node->attrs)){
             mutable_set.insert(node->inputs[i].node.get());
           }
         }
@@ -221,7 +220,7 @@ std::vector<std::string> Symbol::ListOutputNames() const {
     } else {
       const std::string& hname = head.node->attrs.name;
       std::string rname;
-      FListOutputNames fn = flist_ouputs.get(head.node->op, nullptr);
+      FListOutputNames fn = flist_ouputs.get(head.node->op(), nullptr);
       if (fn != nullptr) {
         rname = fn(head.node->attrs)[head.index];
       } else {
@@ -278,10 +277,10 @@ void Symbol::Compose(const array_view<const Symbol*>& args,
       }
       // switch to keyword argument matching
       if (args.size() != n_req) {
-        FListInputNames fn = flist_inputs.get(n->op, nullptr);
+        FListInputNames fn = flist_inputs.get(n->op(), nullptr);
         auto arg_names = (fn == nullptr) ? std::vector<std::string>{"data"} : fn(n->attrs);
         if (arg_names.size() != n_req) {
-          LOG(FATAL) << "Not enough argument to call operator " << outputs[0].node->op->name;
+          LOG(FATAL) << "Not enough argument to call operator " << outputs[0].node->op()->name;
         }
         size_t nmatched = 0;
         for (size_t i = args.size(); i < n_req; ++i) {
@@ -422,8 +421,8 @@ void Symbol::SetAttrs(const std::vector<std::pair<std::string, std::string> >& a
       node->attrs.dict[kv.first] = kv.second;
     }
   }
-  if (node->op != nullptr && node->op->attr_parser != nullptr) {
-    node->op->attr_parser(&(node->attrs));
+  if (node->op() != nullptr && node->op()->attr_parser != nullptr) {
+    node->op()->attr_parser(&(node->attrs));
   }
 }
 
@@ -461,10 +460,10 @@ Symbol Symbol::CreateFunctor(const Op* op,
                              std::unordered_map<std::string, std::string> attrs) {
   Symbol s;
   NodePtr n = Node::Create();
-  n->op = op;
+  n->attrs.op = op;
   n->attrs.dict = std::move(attrs);
-  if (n->op->attr_parser != nullptr) {
-    n->op->attr_parser(&(n->attrs));
+  if (n->op()->attr_parser != nullptr) {
+    n->op()->attr_parser(&(n->attrs));
   }
   s.outputs.emplace_back(NodeEntry{std::move(n), 0, 0});
   return s;
diff --git a/nnvm/src/pass/gradient.cc b/nnvm/src/pass/gradient.cc
index 530dd29f41c6..a64cf0c11303 100644
--- a/nnvm/src/pass/gradient.cc
+++ b/nnvm/src/pass/gradient.cc
@@ -20,11 +20,11 @@ NodeEntry DefaultAggregateGradient(std::vector<NodeEntry>&& v) {
     return std::move(v[0]);
   } else if (v.size() == 0) {
     NodePtr zero_node = Node::Create();
-    zero_node->op = Op::Get("__zero__");
+    zero_node->attrs.op = Op::Get("__zero__");
     return NodeEntry{zero_node, 0, 0};
   } else {
     NodePtr sum_node = Node::Create();
-    sum_node->op = Op::Get("__ewise_sum__");
+    sum_node->attrs.op = Op::Get("__ewise_sum__");
     sum_node->inputs = std::move(v);
     return NodeEntry{sum_node, 0, 0};
   }
@@ -109,7 +109,7 @@ Graph Gradient(Graph src) {
       e.sum = agg_fun(std::move(e.grads));
       out_agg_grads.push_back(e.sum);
     }
-    std::vector<NodeEntry> input_grads = grad_fun_map[ptr->op]
+    std::vector<NodeEntry> input_grads = grad_fun_map[ptr->op()]
         (mirror_map.size() == 0 ? ptr : mirror_map.at(ptr.get()), out_agg_grads);
     auto git = input_grads.begin();
     for (auto it = (*rit)->inputs.begin(); it != (*rit)->inputs.end(); ++it, ++git) {
diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index 5978ecdb79f2..a5cc8c13751f 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -65,7 +65,7 @@ Graph InferAttr(Graph &&ret,
       }
       continue;
     }
-    if (finfer_shape.count(inode.source->op)) {
+    if (finfer_shape.count(inode.source->op())) {
       ishape.resize(num_inputs, def_value);
       for (uint32_t i = 0; i < ishape.size(); ++i) {
         ishape[i] = rshape[idx.entry_id(inode.inputs[i])];
@@ -75,14 +75,14 @@ Graph InferAttr(Graph &&ret,
         oshape[i] = rshape[idx.entry_id(nid, i)];
       }
       num_unknown +=
-          !(finfer_shape[inode.source->op](inode.source->attrs, &ishape, &oshape));
+          !(finfer_shape[inode.source->op()](inode.source->attrs, &ishape, &oshape));
       for (uint32_t i = 0; i < num_inputs; ++i) {
         rshape[idx.entry_id(inode.inputs[i])] = ishape[i];
       }
       for (uint32_t i = 0; i < num_outputs; ++i) {
         rshape[idx.entry_id(nid, i)] = oshape[i];
       }
-    } else if (is_backward.get(inode.source->op, false)) {
+    } else if (is_backward.get(inode.source->op(), false)) {
       // backward operator inference.
       CHECK_GE(inode.control_deps.size(), 1)
           << "BackwardOp need to have control_deps to its forward op";
diff --git a/nnvm/src/pass/order_mutation.cc b/nnvm/src/pass/order_mutation.cc
index 3bcfd9922d53..e91d114ea101 100644
--- a/nnvm/src/pass/order_mutation.cc
+++ b/nnvm/src/pass/order_mutation.cc
@@ -43,8 +43,8 @@ Graph OrderMutation(const Graph& src) {
   auto prepare = [&version_hist, &old_new] (const NodePtr& n) {
     static auto& fmutate_inputs = Op::GetAttr<FMutateInputs>("FMutateInputs");
     std::vector<uint32_t> mutate_inputs;
-    if (!n->is_variable() && fmutate_inputs.count(n->op)) {
-      mutate_inputs = fmutate_inputs[n->op](n->attrs);
+    if (!n->is_variable() && fmutate_inputs.count(n->op())) {
+      mutate_inputs = fmutate_inputs[n->op()](n->attrs);
     }
     std::sort(mutate_inputs.begin(), mutate_inputs.end());
 
@@ -67,7 +67,6 @@ Graph OrderMutation(const Graph& src) {
     }
     if (need_repl) {
       NodePtr np = Node::Create();
-      np->op = n->op;
       np->attrs = n->attrs;
       old_new[n.get()] = std::move(np);
     }
@@ -101,8 +100,8 @@ Graph OrderMutation(const Graph& src) {
     // add control deps
     static auto& fmutate_inputs = Op::GetAttr<FMutateInputs>("FMutateInputs");
     std::vector<uint32_t> mutate_inputs;
-    if (fmutate_inputs.count(kv.first->op)) {
-      mutate_inputs = fmutate_inputs[kv.first->op](kv.first->attrs);
+    if (fmutate_inputs.count(kv.first->op())) {
+      mutate_inputs = fmutate_inputs[kv.first->op()](kv.first->attrs);
     }
     std::sort(mutate_inputs.begin(), mutate_inputs.end());
 
diff --git a/nnvm/src/pass/place_device.cc b/nnvm/src/pass/place_device.cc
index 6a6e877a9f87..402f2cff784c 100644
--- a/nnvm/src/pass/place_device.cc
+++ b/nnvm/src/pass/place_device.cc
@@ -109,9 +109,9 @@ Graph PlaceDevice(Graph src) {
                 NodeEntry{it->second, 0, 0});
           } else {
             NodePtr copy_node = Node::Create();
-            copy_node->op = copy_op;
             std::ostringstream os;
             os << inode.source->inputs[i].node->attrs.name << "_" << e.index <<"_copy";
+            copy_node->attrs.op = copy_op;
             copy_node->attrs.name = os.str();
             copy_node->inputs.push_back(inode.source->inputs[i]);
             copy_map[copy_key] = copy_node;
diff --git a/nnvm/src/pass/plan_memory.cc b/nnvm/src/pass/plan_memory.cc
index 14a88d217de8..34b05d5d6c94 100644
--- a/nnvm/src/pass/plan_memory.cc
+++ b/nnvm/src/pass/plan_memory.cc
@@ -168,8 +168,8 @@ Graph PlanMemory(Graph ret) {
     const auto& inode = idx[nid];
     if (inode.source->is_variable()) continue;
     // check inplace option
-    if (finplace_option.count(inode.source->op) != 0) {
-      auto inplace_pairs = finplace_option[inode.source->op](inode.source->attrs);
+    if (finplace_option.count(inode.source->op()) != 0) {
+      auto inplace_pairs = finplace_option[inode.source->op()](inode.source->attrs);
       for (auto& kv : inplace_pairs) {
         uint32_t eid_out = idx.entry_id(nid, kv.second);
         uint32_t eid_in = idx.entry_id(inode.inputs[kv.first]);
diff --git a/nnvm/src/pass/saveload_json.cc b/nnvm/src/pass/saveload_json.cc
index 984a2c9905c4..681daed7a1dd 100644
--- a/nnvm/src/pass/saveload_json.cc
+++ b/nnvm/src/pass/saveload_json.cc
@@ -68,8 +68,8 @@ struct JSONNode {
   // function to save JSON node.
   void Save(dmlc::JSONWriter *writer) const {
     writer->BeginObject();
-    if (node->op != nullptr) {
-      writer->WriteObjectKeyValue("op", node->op->name);
+    if (node->op() != nullptr) {
+      writer->WriteObjectKeyValue("op", node->op()->name);
     } else {
       std::string json_null = "null";
       writer->WriteObjectKeyValue("op", json_null);
@@ -108,10 +108,10 @@ struct JSONNode {
 
     if (op_type_str != "null") {
       try {
-        node->op = Op::Get(op_type_str);
+        node->attrs.op = Op::Get(op_type_str);
         // rebuild attribute parser
-        if (node->op->attr_parser != nullptr) {
-          node->op->attr_parser(&(node->attrs));
+        if (node->op()->attr_parser != nullptr) {
+          node->op()->attr_parser(&(node->attrs));
         }
       } catch (const dmlc::Error &err) {
         std::ostringstream os;
@@ -120,7 +120,7 @@ struct JSONNode {
         throw dmlc::Error(os.str());
       }
     } else {
-      node->op = nullptr;
+      node->attrs.op = nullptr;
     }
   }
 };

From f560372d8c71b07fa4221b62508596c6a66fc146 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 30 Aug 2016 16:41:31 -0700
Subject: [PATCH 362/949] Enable copy on write in graph attrs (#31)

* [INFER] Enhance backward op policy

* [SYMBOL] add list inputs

* relax graph attr to enable copy-on-write
---
 nnvm/include/nnvm/graph.h         | 38 +++++++++++++++++++++++++++----
 nnvm/include/nnvm/op_attr_types.h | 13 ++++++-----
 nnvm/include/nnvm/symbolic.h      |  9 ++++++++
 nnvm/include/nnvm/tuple.h         |  2 +-
 nnvm/src/core/symbolic.cc         | 27 ++++++++++++++--------
 nnvm/src/pass/infer_shape_type.cc | 17 ++++++++------
 nnvm/src/pass/place_device.cc     | 12 ++++++++--
 nnvm/src/pass/saveload_json.cc    |  8 +++----
 8 files changed, 93 insertions(+), 33 deletions(-)

diff --git a/nnvm/include/nnvm/graph.h b/nnvm/include/nnvm/graph.h
index 47e95f3b3925..5147b22e4aec 100644
--- a/nnvm/include/nnvm/graph.h
+++ b/nnvm/include/nnvm/graph.h
@@ -30,18 +30,34 @@ class Graph {
   std::vector<NodeEntry> outputs;
   /*!
    * \brief attributes of a graph
-   *  Each attribute is immutable,
-   *  and can be shared across multiple Instance of graph
+   *  Note that attribute is shared pointer and can be shared across graphs.
+   *
+   *  It is highly recommended to keep each attribute immutable.
+   *  It is also safe to implement an copy-on-write semnatics.
+   *
+   *  Copy when shared_ptr.unique is not true, while reuse original space
+   *  when shared_ptr.unique is true.
    */
-  std::unordered_map<std::string, std::shared_ptr<const any> > attrs;
+  std::unordered_map<std::string, std::shared_ptr<any> > attrs;
   /*!
-   * \brief Get the attribute from attrs.
+   * \brief Get the immutable attribute from attrs.
    * \param attr_name the name of the attribute
    * \return the reference to corresponding attribute
    * \tparam T the type of the attribute.
    */
   template<typename T>
   inline const T& GetAttr(const std::string& attr_name);
+  /*!
+   * \brief Get a move copy of the attribute, implement copy on write semantics.
+   *  The content is moved if the reference counter of shared_ptr is 1.
+   *  The attribute is erased from attrs after the call.
+   *
+   * \param attr_name the name of the attribute
+   * \return a new copy of the corresponding attribute.
+   * \tparam T the type of the attribute.
+   */
+  template<typename T>
+  inline T MoveCopyAttr(const std::string& attr_name);
   /*!
    * \brief get a indexed graph of current graph, if not exist, create it on demand
    * \return The indexed graph.
@@ -200,6 +216,20 @@ inline const T& Graph::GetAttr(const std::string& attr_name) {
   return nnvm::get<T>(*it->second);
 }
 
+template<typename T>
+inline T Graph::MoveCopyAttr(const std::string& attr_name) {
+  auto it = attrs.find(attr_name);
+  CHECK(it != attrs.end())
+      << "Cannot find attribute " << attr_name << " in the graph";
+  std::shared_ptr<any> sptr = it->second;
+  attrs.erase(it);
+  if (sptr.unique()) {
+    return std::move(nnvm::get<T>(*sptr));
+  } else {
+    return nnvm::get<T>(*sptr);
+  }
+}
+
 template <typename GNode, typename HashType,
            typename FVisit, typename HashFunc,
           typename InDegree, typename GetInput>
diff --git a/nnvm/include/nnvm/op_attr_types.h b/nnvm/include/nnvm/op_attr_types.h
index d3129f978cc2..4d10c304a5d6 100644
--- a/nnvm/include/nnvm/op_attr_types.h
+++ b/nnvm/include/nnvm/op_attr_types.h
@@ -82,17 +82,18 @@ using FInferShape = FInferNodeEntryAttr<TShape>;
 using FInferType = FInferNodeEntryAttr<int>;
 
 /*!
- * \brief Whether this op is an explicit backward operator
+ * \brief Whether this op is an explicit backward operator,
+ *  and the correspondence of each output to input.
  *
- *  If TIsBackwardOp is set to be true:
+ *  If FBackwardOutToInIndex exists:
  *   - The first control_deps of the node points to the corresponding forward operator.
- *   - The outputs operator corresponds to exactly inputs of forward op one by one.
- *
- * \note Register under "TIsBackwardOp", default to false.
+ *   - The k-th outputs corresponds to the FBackwardOutputToInputIndex()[k]-th input of forward op.
  *
+ * \note Register under "FBackwardOutToInIndex"
  * This enables easier shape/type inference for backward operators for slice and reduction.
  */
-using TIsBackwardOp = bool;
+using FBackwardOutToInIndex = std::function<
+  std::vector<uint32_t> (const NodeAttrs& attrs)>;
 
 /*!
  * \brief Get possible inplace options.
diff --git a/nnvm/include/nnvm/symbolic.h b/nnvm/include/nnvm/symbolic.h
index 8bca4cb3103c..e153945e65a7 100644
--- a/nnvm/include/nnvm/symbolic.h
+++ b/nnvm/include/nnvm/symbolic.h
@@ -62,6 +62,15 @@ class Symbol {
    * \return the symbol corresponds to the indexed element.
    */
   Symbol operator[] (size_t index) const;
+  /*!
+   * \brief List the input variable nodes
+   * \param option The options to list the arguments.
+   *
+   * The position of the returned list also corresponds to calling position in operator()
+   * \return the arguments list of this symbol, they can be either named or unnamed (empty string).
+   * \sa ListInputOption
+   */
+  std::vector<NodePtr> ListInputs(ListInputOption option) const;
   /*!
    * \brief List the input names.
    * \param option The options to list the arguments.
diff --git a/nnvm/include/nnvm/tuple.h b/nnvm/include/nnvm/tuple.h
index fefb7ce5739d..326a5d9c6da6 100644
--- a/nnvm/include/nnvm/tuple.h
+++ b/nnvm/include/nnvm/tuple.h
@@ -233,7 +233,7 @@ class Tuple {
         return is;
     }
     }
-    index_t idx;
+    ValueType idx;
     std::vector<ValueType> tmp;
     while (is >> idx) {
       tmp.push_back(idx);
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index 97b35648ee7d..8da33faa6f54 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -180,37 +180,46 @@ Symbol Symbol::operator[] (size_t index) const {
   }
 }
 
-std::vector<std::string> Symbol::ListInputNames(ListInputOption option) const {
-  std::vector<std::string> ret;
+std::vector<NodePtr> Symbol::ListInputs(ListInputOption option) const {
+  std::vector<NodePtr> ret;
   if (option == kAll) {
     DFSVisit(this->outputs, [&ret](const NodePtr &node) {
         if (node->is_variable()) {
-          ret.push_back(node->attrs.name);
+          ret.push_back(node);
         }
       });
   } else {
     std::unordered_set<Node*> mutable_set;
-    std::vector<Node*> vlist;
+    std::vector<NodePtr> vlist;
     static auto& fmutate_inputs = Op::GetAttr<FMutateInputs>("FMutateInputs");
     DFSVisit(this->outputs, [&ret, &mutable_set, &vlist](const NodePtr &node) {
         if (node->is_variable()) {
-          vlist.push_back(node.get());
+          vlist.push_back(node);
         } else if (fmutate_inputs.count(node->op())) {
           for (uint32_t i : fmutate_inputs[node->op()](node->attrs)){
             mutable_set.insert(node->inputs[i].node.get());
           }
         }
       });
-    for (Node* node : vlist) {
-      if ((option == kReadOnlyArgs && mutable_set.count(node) == 0) ||
-          (option == kAuxiliaryStates && mutable_set.count(node) != 0)) {
-        ret.push_back(node->attrs.name);
+    for (const NodePtr& node : vlist) {
+      if ((option == kReadOnlyArgs && mutable_set.count(node.get()) == 0) ||
+          (option == kAuxiliaryStates && mutable_set.count(node.get()) != 0)) {
+        ret.emplace_back(node);
       }
     }
   }
   return ret;
 }
 
+std::vector<std::string> Symbol::ListInputNames(ListInputOption option) const {
+  std::vector<NodePtr> inputs = ListInputs(option);
+  std::vector<std::string> ret(inputs.size());
+  for (size_t i = 0; i < inputs.size(); ++i) {
+    ret[i] = inputs[i]->attrs.name;
+  }
+  return ret;
+}
+
 std::vector<std::string> Symbol::ListOutputNames() const {
   static auto& flist_ouputs = Op::GetAttr<FListOutputNames>("FListOutputNames");
   std::vector<std::string> ret;
diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index a5cc8c13751f..bb50e98b5ede 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -24,8 +24,8 @@ Graph InferAttr(Graph &&ret,
   const IndexedGraph& idx = ret.indexed_graph();
   static auto& finfer_shape =
       Op::GetAttr<FInferNodeEntryAttr<AttrType> >(infer_name);
-  static auto& is_backward =
-      Op::GetAttr<TIsBackwardOp>("TIsBackwardOp");
+  static auto& backward_map =
+      Op::GetAttr<FBackwardOutToInIndex>("FBackwardOutToInIndex");
   // reshape shape vector
   AttrVector rshape(idx.num_node_entries(), def_value);
 
@@ -82,16 +82,19 @@ Graph InferAttr(Graph &&ret,
       for (uint32_t i = 0; i < num_outputs; ++i) {
         rshape[idx.entry_id(nid, i)] = oshape[i];
       }
-    } else if (is_backward.get(inode.source->op(), false)) {
+    } else if (backward_map.count(inode.source->op())) {
       // backward operator inference.
       CHECK_GE(inode.control_deps.size(), 1)
           << "BackwardOp need to have control_deps to its forward op";
       const auto& fnode = idx[inode.control_deps[0]];
-      CHECK_EQ(fnode.inputs.size(), num_outputs)
-          << "BackwardOp need to correspond to the forward node";
+      std::vector<uint32_t> out_map =
+          backward_map[inode.source->op()](inode.source->attrs);
       bool known = true;
-      for (size_t i = 0; i < fnode.inputs.size(); ++i) {
-        rshape[idx.entry_id(nid, i)] = rshape[idx.entry_id(fnode.inputs[i])];
+      for (size_t i = 0; i < out_map.size(); ++i) {
+        uint32_t in_id = out_map[i];
+        CHECK_LT(in_id, fnode.inputs.size());
+        rshape[idx.entry_id(nid, i)] =
+            rshape[idx.entry_id(fnode.inputs[in_id])];
         if (fis_none(rshape[idx.entry_id(nid, i)])) known = false;
       }
       num_unknown += !known;
diff --git a/nnvm/src/pass/place_device.cc b/nnvm/src/pass/place_device.cc
index 402f2cff784c..607c51a7f319 100644
--- a/nnvm/src/pass/place_device.cc
+++ b/nnvm/src/pass/place_device.cc
@@ -12,6 +12,7 @@ namespace nnvm {
 namespace pass {
 namespace {
 
+
 // simply logic to place device according to device_group hint
 // insert copy node when there is
 Graph PlaceDevice(Graph src) {
@@ -21,13 +22,20 @@ Graph PlaceDevice(Graph src) {
       << "Need graph attribute \"device_assign_map\" in PlaceDevice";
   CHECK_NE(src.attrs.count("device_copy_op"), 0)
       << "Need graph attribute \"device_copy_op\" in PlaceDevice";
-
   std::string device_group_attr_key = src.GetAttr<std::string>("device_group_attr_key");
   const Op* copy_op = Op::Get(src.GetAttr<std::string>("device_copy_op"));
   auto& device_assign_map = src.GetAttr<DeviceAssignMap>("device_assign_map");
   const IndexedGraph& idx = src.indexed_graph();
 
-  DeviceVector device(idx.num_nodes(), -1);
+  DeviceVector device;
+  // copy on write semanatics
+  if (src.attrs.count("device") != 0) {
+    device = src.MoveCopyAttr<DeviceVector>("device");
+    CHECK_EQ(device.size(), idx.num_nodes());
+  } else {
+    device.resize(idx.num_nodes(), -1);
+  }
+
   // forward pass
   for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
     const auto& inode = idx[nid];
diff --git a/nnvm/src/pass/saveload_json.cc b/nnvm/src/pass/saveload_json.cc
index 681daed7a1dd..3b3f8561a33f 100644
--- a/nnvm/src/pass/saveload_json.cc
+++ b/nnvm/src/pass/saveload_json.cc
@@ -12,11 +12,11 @@ namespace dmlc {
 namespace json {
 // overload handler for shared ptr
 template<>
-struct Handler<std::shared_ptr<const any> > {
-  inline static void Write(JSONWriter *writer, const std::shared_ptr<const any> &data) {
+struct Handler<std::shared_ptr<any> > {
+  inline static void Write(JSONWriter *writer, const std::shared_ptr<any> &data) {
     writer->Write(*data);
   }
-  inline static void Read(JSONReader *reader, std::shared_ptr<const any> *data) {
+  inline static void Read(JSONReader *reader, std::shared_ptr<any> *data) {
     any v;
     reader->Read(&v);
     *data = std::make_shared<any>(std::move(v));
@@ -131,7 +131,7 @@ struct JSONGraph {
   std::vector<uint32_t> arg_nodes;
   std::vector<uint32_t> node_row_ptr;
   std::vector<JSONNode::Entry> heads;
-  std::unordered_map<std::string, std::shared_ptr<const any> > attrs;
+  std::unordered_map<std::string, std::shared_ptr<any> > attrs;
 
   void Save(dmlc::JSONWriter *writer) const {
     writer->BeginObject();

From faba562c723a4bee58a36cfc2c04bc7b036d2f0c Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 30 Aug 2016 22:17:29 -0700
Subject: [PATCH 363/949] Bugfix plan memory, fully support mxnet executor
 (#32)

* [PASS] include knullop info in plan memory

* Bugfix plan memory, fully support mxnet
---
 nnvm/include/nnvm/op_attr_types.h | 12 ++++++++++++
 nnvm/src/core/symbolic.cc         | 19 +++++++++++++++----
 nnvm/src/pass/place_device.cc     |  1 -
 nnvm/src/pass/plan_memory.cc      |  7 +++++--
 4 files changed, 32 insertions(+), 7 deletions(-)

diff --git a/nnvm/include/nnvm/op_attr_types.h b/nnvm/include/nnvm/op_attr_types.h
index 4d10c304a5d6..b29ade88a034 100644
--- a/nnvm/include/nnvm/op_attr_types.h
+++ b/nnvm/include/nnvm/op_attr_types.h
@@ -30,6 +30,18 @@ namespace nnvm {
  */
 using FListInputNames = std::function<std::vector<std::string> (const NodeAttrs& attrs)>;
 
+/*!
+ * \brief Return number of visible outputs by the user.
+ *
+ * \param attrs The attributes of the node.
+ *
+ * \note Register under "FNumVisibleOutputs", default not registered.
+ *  This can be used to hide certain output from the user,
+ *  but the additional outputs can be used to pass information from
+ *  forward to gradient pass.
+ */
+using FNumVisibleOutputs = std::function<uint32_t (const NodeAttrs& attrs)>;
+
 /*!
  * \brief Return list of output arguments names of each operator.
  *
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index 8da33faa6f54..535dfa4f2127 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -87,7 +87,7 @@ inline std::vector<std::string> GetKeys(
 
 // whether the symbol is atomic functor
 inline bool IsAtomic(const std::vector<NodeEntry>& outputs) {
-  return outputs.size() == 1 && outputs[0].node->inputs.size() == 0;
+  return outputs[0].node->inputs.size() == 0;
 }
 
 // public functions
@@ -222,6 +222,7 @@ std::vector<std::string> Symbol::ListInputNames(ListInputOption option) const {
 
 std::vector<std::string> Symbol::ListOutputNames() const {
   static auto& flist_ouputs = Op::GetAttr<FListOutputNames>("FListOutputNames");
+
   std::vector<std::string> ret;
   for (auto &head : outputs) {
     if (head.node->is_variable()) {
@@ -256,8 +257,6 @@ void Symbol::Compose(const array_view<const Symbol*>& args,
                      const std::string& name) {
   static auto& flist_inputs = Op::GetAttr<FListInputNames>("FListInputNames");
 
-  CHECK_EQ(outputs.size(), 1)
-      << "Only composition of value function is supported currently";
   CHECK(!outputs[0].node->is_variable()) << "Variable cannot be composed";
   // parameter check.
   for (size_t i = 0; i < args.size(); ++i) {
@@ -400,6 +399,7 @@ void Symbol::AddControlDeps(const Symbol& src) {
 }
 
 Symbol Symbol::GetInternals() const {
+  static auto& fnum_vis_output = Op::GetAttr<FNumVisibleOutputs>("FNumVisibleOutputs");
   Symbol ret;
   DFSVisit(this->outputs, [&ret](const NodePtr& node) {
       Node* n = node.get();
@@ -409,6 +409,9 @@ Symbol Symbol::GetInternals() const {
         ret.outputs.emplace_back(NodeEntry{node, 0, param.version});
       } else {
         uint32_t nout = n->num_outputs();
+        if (fnum_vis_output.count(n->op())) {
+          nout = fnum_vis_output[n->op()](n->attrs);
+        }
         for (uint32_t i = 0; i < nout; ++i) {
           ret.outputs.emplace_back(NodeEntry{node, i, 0});
         }
@@ -467,6 +470,7 @@ std::unordered_map<std::string, std::string> Symbol::ListAttrs(ListAttrOption op
 
 Symbol Symbol::CreateFunctor(const Op* op,
                              std::unordered_map<std::string, std::string> attrs) {
+  static auto& fnum_vis_output = Op::GetAttr<FNumVisibleOutputs>("FNumVisibleOutputs");
   Symbol s;
   NodePtr n = Node::Create();
   n->attrs.op = op;
@@ -474,7 +478,14 @@ Symbol Symbol::CreateFunctor(const Op* op,
   if (n->op()->attr_parser != nullptr) {
     n->op()->attr_parser(&(n->attrs));
   }
-  s.outputs.emplace_back(NodeEntry{std::move(n), 0, 0});
+
+  uint32_t nout = n->num_outputs();
+  if (fnum_vis_output.count(n->op())) {
+    nout = fnum_vis_output[n->op()](n->attrs);
+  }
+  for (uint32_t i = 0; i < nout; ++i) {
+    s.outputs.emplace_back(NodeEntry{n, i, 0});
+  }
   return s;
 }
 
diff --git a/nnvm/src/pass/place_device.cc b/nnvm/src/pass/place_device.cc
index 607c51a7f319..00d216512558 100644
--- a/nnvm/src/pass/place_device.cc
+++ b/nnvm/src/pass/place_device.cc
@@ -12,7 +12,6 @@ namespace nnvm {
 namespace pass {
 namespace {
 
-
 // simply logic to place device according to device_group hint
 // insert copy node when there is
 Graph PlaceDevice(Graph src) {
diff --git a/nnvm/src/pass/plan_memory.cc b/nnvm/src/pass/plan_memory.cc
index 34b05d5d6c94..0f0b8e79b9c2 100644
--- a/nnvm/src/pass/plan_memory.cc
+++ b/nnvm/src/pass/plan_memory.cc
@@ -142,11 +142,11 @@ Graph PlanMemory(Graph ret) {
   // step 1: initialize reference count
   for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
     for (const auto& e : idx[nid].inputs) {
-      ++ref_count[e.node_id];
+      ++ref_count[idx.entry_id(e)];
     }
   }
   for (const auto& e : idx.outputs()) {
-    ++ref_count[e.node_id];
+    ++ref_count[idx.entry_id(e)];
   }
   // step 2: allocate memory.
   StorageVector storage(idx.num_node_entries(), -1);
@@ -202,10 +202,13 @@ Graph PlanMemory(Graph ret) {
       }
     }
     // check if there are outputs that can be freeded immediately
+    // these output are not referenced by any operator.
     for (uint32_t index = 0; index < inode.source->num_outputs(); ++index) {
       uint32_t eid = idx.entry_id(nid, index);
       if (ref_count[eid] == 0 && storage[eid] != GraphAllocator::kBadStorageID) {
         allocator.Release(storage[eid], nid);
+        // use -2 to indicate that the node was never touched.
+        storage_inplace_index[eid] = -2;
       }
       if (storage[eid] == GraphAllocator::kBadStorageID) {
         ++num_not_allocated;

From b44568c59df915de45deba80a89da8a6d575f2d9 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 31 Aug 2016 11:48:35 -0700
Subject: [PATCH 364/949] Place device now compatible and tested (#33)

---
 nnvm/include/dmlc/any.h       |  3 ++-
 nnvm/include/dmlc/parameter.h | 34 ++++++++++++++++++++++++---------
 nnvm/src/pass/place_device.cc | 36 +++++++++++++++++++++++++++--------
 3 files changed, 55 insertions(+), 18 deletions(-)

diff --git a/nnvm/include/dmlc/any.h b/nnvm/include/dmlc/any.h
index 5707a363db99..d32e44e48c4d 100644
--- a/nnvm/include/dmlc/any.h
+++ b/nnvm/include/dmlc/any.h
@@ -259,7 +259,8 @@ inline const std::type_info& any::type() const {
 template<typename T>
 inline void any::check_type() const {
   CHECK(type_ != nullptr)
-      << "The any container is empty";
+      << "The any container is empty"
+      << " requested=" << typeid(T).name();
   CHECK(type_->ptype_info == &typeid(T))
       << "The stored type mismatch"
       << " stored=" << type_->ptype_info->name()
diff --git a/nnvm/include/dmlc/parameter.h b/nnvm/include/dmlc/parameter.h
index 2fbab2a44e32..0222df60664d 100644
--- a/nnvm/include/dmlc/parameter.h
+++ b/nnvm/include/dmlc/parameter.h
@@ -57,6 +57,14 @@ class FieldEntry;
 // forward declare ParamManagerSingleton
 template<typename PType>
 struct ParamManagerSingleton;
+
+/*! \brief option in parameter initialization */
+enum ParamInitOption {
+  /*! \brief allow unknown parameters */
+  kAllowUnknown,
+  /*! \brief need to match exact parameters */
+  kAllMatch
+};
 }  // namespace parameter
 /*!
  * \brief Information about a parameter field in string representations.
@@ -108,13 +116,17 @@ struct Parameter {
    *  and throw error if something wrong happens.
    *
    * \param kwargs map of keyword arguments, or vector of pairs
+   * \parma option The option on initialization.
    * \tparam Container container type
    * \throw ParamError when something go wrong.
    */
   template<typename Container>
-  inline void Init(const Container &kwargs) {
+  inline void Init(const Container &kwargs,
+                   parameter::ParamInitOption option = parameter::kAllowUnknown) {
     PType::__MANAGER__()->RunInit(static_cast<PType*>(this),
-                                  kwargs.begin(), kwargs.end(), NULL);
+                                  kwargs.begin(), kwargs.end(),
+                                  NULL,
+                                  option == parameter::kAllowUnknown);
   }
   /*!
    * \brief initialize the parameter by keyword arguments.
@@ -130,7 +142,8 @@ struct Parameter {
   InitAllowUnknown(const Container &kwargs) {
     std::vector<std::pair<std::string, std::string> > unknown;
     PType::__MANAGER__()->RunInit(static_cast<PType*>(this),
-                                  kwargs.begin(), kwargs.end(), &unknown);
+                                  kwargs.begin(), kwargs.end(),
+                                  &unknown, true);
     return unknown;
   }
   /*!
@@ -355,7 +368,8 @@ class ParamManager {
   inline void RunInit(void *head,
                       RandomAccessIterator begin,
                       RandomAccessIterator end,
-                      std::vector<std::pair<std::string, std::string> > *unknown_args) const {
+                      std::vector<std::pair<std::string, std::string> > *unknown_args,
+                      bool allow_unknown) const {
     std::set<FieldAccessEntry*> selected_args;
     for (RandomAccessIterator it = begin; it != end; ++it) {
       FieldAccessEntry *e = Find(it->first);
@@ -367,11 +381,13 @@ class ParamManager {
         if (unknown_args != NULL) {
           unknown_args->push_back(*it);
         } else {
-          std::ostringstream os;
-          os << "Cannot find argument \'" << it->first << "\', Possible Arguments:\n";
-          os << "----------------\n";
-          PrintDocString(os);
-          throw dmlc::ParamError(os.str());
+          if (!allow_unknown) {
+            std::ostringstream os;
+            os << "Cannot find argument \'" << it->first << "\', Possible Arguments:\n";
+            os << "----------------\n";
+            PrintDocString(os);
+            throw dmlc::ParamError(os.str());
+          }
         }
       }
     }
diff --git a/nnvm/src/pass/place_device.cc b/nnvm/src/pass/place_device.cc
index 00d216512558..3fce4fa06d63 100644
--- a/nnvm/src/pass/place_device.cc
+++ b/nnvm/src/pass/place_device.cc
@@ -25,7 +25,6 @@ Graph PlaceDevice(Graph src) {
   const Op* copy_op = Op::Get(src.GetAttr<std::string>("device_copy_op"));
   auto& device_assign_map = src.GetAttr<DeviceAssignMap>("device_assign_map");
   const IndexedGraph& idx = src.indexed_graph();
-
   DeviceVector device;
   // copy on write semanatics
   if (src.attrs.count("device") != 0) {
@@ -79,10 +78,10 @@ Graph PlaceDevice(Graph src) {
     src.attrs["device"] = std::make_shared<any>(std::move(device));
     return src;
   }
-
   std::map<std::tuple<uint32_t, uint32_t, int>, NodePtr> copy_map;
   std::vector<NodePtr> new_node_map(idx.num_nodes(), nullptr);
   std::unordered_map<const Node*, int> new_device_map;
+  static auto& fmutate_inputs = Op::GetAttr<FMutateInputs>("FMutateInputs");
 
   // insert copy node
   for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
@@ -90,6 +89,16 @@ Graph PlaceDevice(Graph src) {
     const auto& inode = idx[nid];
     // check if mutation is needed
     bool need_mutate = false;
+    if (!inode.source->is_variable() && fmutate_inputs.count(inode.source->op())) {
+      for (uint32_t index : fmutate_inputs[inode.source->op()](inode.source->attrs)) {
+        auto e = inode.inputs[index];
+        if (new_node_map[e.node_id] != nullptr || dev_id != device[e.node_id]) {
+          LOG(FATAL) << " mutable state cannot go across device"
+                     << " op=" << inode.source->op()->name
+                     << " input_state_index=" << index;
+        }
+      }
+    }
     for (const IndexedGraph::NodeEntry& e : inode.inputs) {
       if (new_node_map[e.node_id] != nullptr || dev_id != device[e.node_id]) {
         need_mutate = true; break;
@@ -102,6 +111,9 @@ Graph PlaceDevice(Graph src) {
         }
       }
     }
+    if (inode.source->is_variable()) {
+      CHECK(!need_mutate) << "consistency check";
+    }
     if (need_mutate) {
       NodePtr new_node = Node::Create();
       new_node->attrs = inode.source->attrs;
@@ -120,7 +132,15 @@ Graph PlaceDevice(Graph src) {
             os << inode.source->inputs[i].node->attrs.name << "_" << e.index <<"_copy";
             copy_node->attrs.op = copy_op;
             copy_node->attrs.name = os.str();
-            copy_node->inputs.push_back(inode.source->inputs[i]);
+            if (new_node_map[e.node_id] != nullptr) {
+              copy_node->inputs.emplace_back(
+                NodeEntry{new_node_map[e.node_id], e.index, 0});
+            } else {
+              copy_node->inputs.push_back(inode.source->inputs[i]);
+            }
+            if (copy_node->attrs.op->attr_parser != nullptr) {
+              copy_node->attrs.op->attr_parser(&(copy_node->attrs));
+            }
             copy_map[copy_key] = copy_node;
             new_device_map[copy_node.get()] = dev_id;
             new_node->inputs.emplace_back(
@@ -130,7 +150,7 @@ Graph PlaceDevice(Graph src) {
           if (new_node_map[e.node_id] != nullptr) {
             new_node->inputs.emplace_back(
                 NodeEntry{new_node_map[e.node_id], e.index, 0});
-        } else {
+          } else {
             new_node->inputs.push_back(inode.source->inputs[i]);
           }
         }
@@ -150,7 +170,6 @@ Graph PlaceDevice(Graph src) {
       new_device_map[inode.source] = dev_id;
     }
   }
-
   // make the new graph
   Graph ret;
   for (const NodeEntry& e : src.outputs) {
@@ -163,10 +182,11 @@ Graph PlaceDevice(Graph src) {
   }
   DeviceVector new_device_vec(ret.indexed_graph().num_nodes());
   for (uint32_t nid = 0; nid < ret.indexed_graph().num_nodes(); ++nid) {
-    if (new_device_map.count(ret.indexed_graph()[nid].source) == 0) {
-      LOG(INFO) << "canot find " << ret.indexed_graph()[nid].source->attrs.name;
+    auto source = ret.indexed_graph()[nid].source;
+    if (new_device_map.count(source) == 0) {
+      LOG(FATAL) << "canot find " << source;
     }
-    new_device_vec[nid] = new_device_map.at(ret.indexed_graph()[nid].source);
+    new_device_vec[nid] = new_device_map.at(source);
   }
   ret.attrs["device"] = std::make_shared<any>(std::move(new_device_vec));
   return ret;

From 200973ad6d085b94288f1ca4f60881173433ec69 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 31 Aug 2016 13:51:24 -0700
Subject: [PATCH 365/949] Enable copy over behavior of attributes of automatic
 created vars (#34)

---
 nnvm/src/core/symbolic.cc | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index 535dfa4f2127..d3a601d41424 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -299,6 +299,8 @@ void Symbol::Compose(const array_view<const Symbol*>& args,
           } else {
             n->inputs[i] = NodeEntry{
               CreateVariableNode(DefaultVarName(name, arg_names[i])), 0, 0};
+            // copy attribute of parent over automatically created variables
+            n->inputs[i].node->attrs.dict = n->attrs.dict;
           }
         }
 

From ca99c108afd17c95bd7d7547b2b5ca9b74ca78db Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 31 Aug 2016 16:20:24 -0700
Subject: [PATCH 366/949] Add travis test scripts (#35)

---
 nnvm/include/nnvm/graph.h                 |  4 +-
 nnvm/tests/cpp/op_test.cc                 |  6 +++
 nnvm/tests/cpp/tuple_test.cc              |  6 +++
 nnvm/tests/cpp/unittest.mk                |  4 +-
 nnvm/tests/python/test_gradient.py        |  2 +-
 nnvm/tests/travis/run_test.sh             | 54 +++++++++++++++++++++++
 nnvm/tests/travis/setup.sh                | 19 ++++++++
 nnvm/tests/travis/travis_after_failure.sh |  1 +
 8 files changed, 91 insertions(+), 5 deletions(-)
 create mode 100644 nnvm/tests/travis/run_test.sh
 create mode 100644 nnvm/tests/travis/setup.sh
 create mode 100644 nnvm/tests/travis/travis_after_failure.sh

diff --git a/nnvm/include/nnvm/graph.h b/nnvm/include/nnvm/graph.h
index 5147b22e4aec..9c08467218e9 100644
--- a/nnvm/include/nnvm/graph.h
+++ b/nnvm/include/nnvm/graph.h
@@ -46,7 +46,7 @@ class Graph {
    * \tparam T the type of the attribute.
    */
   template<typename T>
-  inline const T& GetAttr(const std::string& attr_name);
+  inline const T& GetAttr(const std::string& attr_name) const;
   /*!
    * \brief Get a move copy of the attribute, implement copy on write semantics.
    *  The content is moved if the reference counter of shared_ptr is 1.
@@ -209,7 +209,7 @@ inline void DFSVisit(const std::vector<NodeEntry>& heads, FVisit fvisit);
 
 // inline function implementations
 template<typename T>
-inline const T& Graph::GetAttr(const std::string& attr_name) {
+inline const T& Graph::GetAttr(const std::string& attr_name) const {
   auto it = attrs.find(attr_name);
   CHECK(it != attrs.end())
       << "Cannot find attribute " << attr_name << " in the graph";
diff --git a/nnvm/tests/cpp/op_test.cc b/nnvm/tests/cpp/op_test.cc
index 816305c609ca..164cc469ad34 100644
--- a/nnvm/tests/cpp/op_test.cc
+++ b/nnvm/tests/cpp/op_test.cc
@@ -19,3 +19,9 @@ TEST(Op, GetAttr) {
 
   CHECK_EQ(nick[add], "plus");
 }
+
+int main(int argc, char ** argv) {
+  testing::InitGoogleTest(&argc, argv);
+  testing::FLAGS_gtest_death_test_style = "threadsafe";
+  return RUN_ALL_TESTS();
+}
diff --git a/nnvm/tests/cpp/tuple_test.cc b/nnvm/tests/cpp/tuple_test.cc
index 496e62ebe9a5..85cbc08f1f6f 100644
--- a/nnvm/tests/cpp/tuple_test.cc
+++ b/nnvm/tests/cpp/tuple_test.cc
@@ -22,3 +22,9 @@ TEST(Tuple, Basic) {
   s = std::move(ss);
   CHECK((s == TShape{1, 2, 3}));
 }
+
+int main(int argc, char ** argv) {
+  testing::InitGoogleTest(&argc, argv);
+  testing::FLAGS_gtest_death_test_style = "threadsafe";
+  return RUN_ALL_TESTS();
+}
diff --git a/nnvm/tests/cpp/unittest.mk b/nnvm/tests/cpp/unittest.mk
index ade9f93947bd..1328eb7a314a 100644
--- a/nnvm/tests/cpp/unittest.mk
+++ b/nnvm/tests/cpp/unittest.mk
@@ -4,9 +4,9 @@ GTEST_INC=$(GTEST_PATH)/include/
 TEST_SRC = $(wildcard tests/cpp/*_test.cc)
 TEST = $(patsubst tests/cpp/%_test.cc, tests/cpp/%_test, $(TEST_SRC))
 
-tests/cpp/%_test: tests/cpp/%_test.cc lib/libnngraph.a
+tests/cpp/%_test: tests/cpp/%_test.cc lib/libnnvm.a
 	$(CXX) -std=c++11 $(CFLAGS) -MM -MT tests/cpp/$* $< >tests/cpp/$*.d
 	$(CXX) -std=c++11 $(CFLAGS) -I$(GTEST_INC) -o $@ $(filter %.cc %.a, $^)  \
-		-L$(GTEST_LIB)  $(LDFLAGS) -lgtest -lgtest_main
+		-L$(GTEST_LIB)  $(LDFLAGS) -lgtest
 
 -include tests/cpp/*.d
diff --git a/nnvm/tests/python/test_gradient.py b/nnvm/tests/python/test_gradient.py
index 5acdb1207f20..00fc5c75e620 100644
--- a/nnvm/tests/python/test_gradient.py
+++ b/nnvm/tests/python/test_gradient.py
@@ -18,7 +18,7 @@ def test_graph_gradient():
     print("Original graph")
     print(y.debug_str())
     print("Gradient  graph")
-    print grad_graph.symbol.debug_str()
+    print(grad_graph.symbol.debug_str())
 
 if __name__ == "__main__":
     test_graph_gradient()
diff --git a/nnvm/tests/travis/run_test.sh b/nnvm/tests/travis/run_test.sh
new file mode 100644
index 000000000000..2c4fe4a2f036
--- /dev/null
+++ b/nnvm/tests/travis/run_test.sh
@@ -0,0 +1,54 @@
+#!/bin/bash
+
+
+if [ ${TASK} == "lint" ]; then
+    make lint || exit -1
+    echo "Check documentations of c++ code..."
+    make doc 2>log.txt
+    (cat log.txt| grep -v ENABLE_PREPROCESSING |grep -v "unsupported tag") > logclean.txt
+    echo "---------Error Log----------"
+    cat logclean.txt
+    echo "----------------------------"
+    (cat logclean.txt|grep warning) && exit -1
+    (cat logclean.txt|grep error) && exit -1
+    exit 0
+fi
+
+
+if [ ! ${TRAVIS_OS_NAME} == "osx" ]; then
+    # use g++-4.8 for linux
+    if [ ${CXX} == "g++" ]; then
+        export CXX=g++-4.8
+    fi
+fi
+
+if [ ${TASK} == "cpp_test" ]; then
+    make -f dmlc-core/scripts/packages.mk gtest
+    echo "GTEST_PATH="${CACHE_PREFIX} >> config.mk
+    make test || exit -1
+    for test in tests/cpp/*_test; do
+        ./$test || exit -1
+    done
+    exit 0
+fi
+
+# run two test one for cython, one for ctypes
+if [ ${TASK} == "python_test" ]; then
+    make clean
+    make -j all || exit -1
+    if [ ${TRAVIS_OS_NAME} == "osx" ]; then
+        python -m nose tests/python/ || exit -1
+        python3 -m nose tests/python/ || exit -1
+    else
+        nosetests tests/python/ || exit -1
+        nosetests3 tests/python/ || exit -1
+    fi
+
+    python -m nose tests/python/ || exit -1
+    python3 -m nose tests/python/ || exit -1
+    make cython || exit -1
+    make cython3 || exit -1
+    python -m nose tests/python/ || exit -1
+    python3 -m nose tests/python/ || exit -1
+    exit 0
+fi
diff --git a/nnvm/tests/travis/setup.sh b/nnvm/tests/travis/setup.sh
new file mode 100644
index 000000000000..2621a7f03f25
--- /dev/null
+++ b/nnvm/tests/travis/setup.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+
+if [ ${TRAVIS_OS_NAME} == "osx" ]; then
+    brew update
+    brew install python3
+    if [ ${TASK} == "python_test" ]; then
+        python -m pip install nose numpy cython --user `whoami`
+        python3 -m pip install nose numpy cython --user `whoami`
+    fi
+fi
+
+if [ ${TASK} == "lint" ]; then
+    pip install cpplint 'pylint==1.4.4' 'astroid==1.3.6' --user `whoami`
+fi
+
+if [ ! -d dmlc-core ]; then
+    git clone https://github.com/dmlc/dmlc-core
+fi
diff --git a/nnvm/tests/travis/travis_after_failure.sh b/nnvm/tests/travis/travis_after_failure.sh
new file mode 100644
index 000000000000..a9bf588e2f88
--- /dev/null
+++ b/nnvm/tests/travis/travis_after_failure.sh
@@ -0,0 +1 @@
+#!/bin/bash

From e66c4e05cfd1058c734830481bd0fdb1743b6e18 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 31 Aug 2016 20:16:08 -0700
Subject: [PATCH 367/949] overview (#36)

* overview

* fix
---
 nnvm/README.md               |  26 ++---
 nnvm/docs/overview.md        | 196 +++++++++++++++++++++++++++++++++++
 nnvm/example/src/operator.cc |   2 +
 3 files changed, 212 insertions(+), 12 deletions(-)
 create mode 100644 nnvm/docs/overview.md

diff --git a/nnvm/README.md b/nnvm/README.md
index 1a9757b40153..2fa01f87c58a 100644
--- a/nnvm/README.md
+++ b/nnvm/README.md
@@ -1,13 +1,13 @@
 # NNVM: Build deep learning system by parts
 
-NNVM is not a deep learning library. It is a modular, decentralized and lightweight part to
-help build deep learning libraries.
+NNVM is not a deep learning library. It is a modular,
+decentralized and lightweight part to help build deep learning libraries.
 
 ## What is it
 
 While most deep learning systems offer end to end solutions,
-it is interesting to ask if we can actually assemble a deep learning system by parts.
-The goal is to enable hackers can customize optimizations, target platforms and set of operators they care about.
+it is interesting to  assemble a deep learning system by parts.
+The goal is to enable user to customize optimizations, target platforms and set of operators they care about.
 We believe that the decentralized modular system is an interesting direction.
 
 The hope is that effective parts can be assembled together just like you assemble your own desktops.
@@ -17,20 +17,22 @@ while maxiziming the users' need.
 NNVM offers one such part, it provides a generic way to do
 computation graph optimization such as memory reduction, device allocation and more
 while being agnostic to the operator interface defintion and how operators are executed.
-NNVM is inspired by LLVM, aiming to be an intermediate representation library
+NNVM is inspired by LLVM, aiming to be a high level intermediate representation library
 for neural nets and computation graphs generation and optimizations.
 
-## Why build deep learning system by parts
+See [Overview](docs/overview.md) for an introduction on what it provides.
+
+## Why build learning system by parts
+
+This is essentially ***Unix philosophy*** applied to machine learning system.
 
 - Essential parts can be assembled in minimum way for embedding systems.
-- Hackers can hack the parts they need and compose with other well defined parts.
+- Developers can hack the parts they need and compose with other well defined parts.
 - Decentralized modules enable new extensions creators to own their project
   without creating a monothilic version.
 
-## Deep learning system by parts
-
-This is one way to divide the deep learning system into common parts.
-Each can be isolated to a modular part.
+Deep learning system itself is not necessary one part, for example
+here are some relative independent parts that can
 
 - Computation graph definition, manipulation.
 - Computation graph intermediate optimization.
@@ -43,5 +45,5 @@ so system building can be fun and rewarding.
 
 ## Links
 
-[MXNet](https://github.com/dmlc/mxnet) will be using NNVM as its intermediate
+[MXNet](https://github.com/dmlc/mxnet) is moving to NNVM as its intermediate
 representation layer for symbolic graphs.
diff --git a/nnvm/docs/overview.md b/nnvm/docs/overview.md
new file mode 100644
index 000000000000..064519057661
--- /dev/null
+++ b/nnvm/docs/overview.md
@@ -0,0 +1,196 @@
+# NNVM Overview
+
+NNVM is a C++ library to help developers to build deep learning system.
+It provides ways to construct, represent and transform computation graphs
+invariant of how it is executed.
+
+To begin with, let us start with a few stories to tell the design goals.
+
+## Stories and Design Goals
+
+X has built a new deep learning framework for image classification for fun,
+with the modular tools like CuDNN and CUDA, it is not hard to assemble a C++ API.
+However, most users like to use python/R/scala or other languages.
+By registering the operators to NNVM, X can now get the graph composition
+language front-end on these languages quickly without coding it up for
+each type of langugage.
+
+Y want to build a deep learning serving system on embedded devices.
+To do that, we need to cut things off, as opposed to add new parts,
+because codes such as gradient calculation multi-GPU scheduling is NOT relevant.
+It is hard to build things from scratch as well, because we want to
+reuse components such as memory optimization and kernel execution.
+It is hard to do so in current frameworks because all these information
+are tied to the operator interface. We want to be able to keep
+certain part of the system we need and throw away other parts
+to get the minimum system we need.
+
+Z want to extend an existing deep learning system by adding a new feature,
+say FPGA execution of some operators. To do so Z need to add a interface like ```FPGAKernel```
+to the operators. E want to do another new feature that generate code for
+certain subset of operations. Then interface like ```GenLLVMCode``` need to be added
+to the operator. Eventually the system end up with a fat operator interface
+in order to support everything (while everyone only want some part of it).
+
+We can think more stories, as the deep learning landscape shifts to more devices
+applications and scenarios. It is desirable to have different specialized
+learning system to solve some problem well,
+
+Here is a list of things we want:
+- Minimum dependency
+- Being able to assemble some part together while discarding some other parts
+- No centralized operator interface but still allow user to provide various information about operators.
+
+## Minimum Registration for a Symbolic Front-End
+To use NNVM to build language front end, developer only need to register
+minimum information about each operators.
+
+```c++
+NNVM_REGISTER_OP(add)
+.describe("add two data together")
+.set_num_inputs(2);
+
+NNVM_REGISTER_OP(conv2d)
+.describe("take 2d convolution of input")
+.set_num_inputs(2);
+
+NNVM_REGISTER_OP(assign)
+.describe("assign second input argument to the first one")
+.set_num_inputs(2);
+```
+
+Compiling the code with nnvm library. User can use the following interface
+to compose the computation graph in python, like the following code.
+
+```python
+import nnvm.symbol as nn
+
+# symbolic variable
+x = nn.Variable('x')
+y = nn.Variable('y')
+w = nn.Variable('w')
+
+z = nn.conv2d(nn.add(x, y), w, filter_size=(2,2), name='conv1')
+```
+
+The graph structure can be accessed in the backend. Currently python interface is supported.
+But as NNVM follows the same C bridge API design as [MXNet](https://github.com/dmlc/mxnet),
+which support many languages such as R/Julia/Scala/C++, more language support can be easily
+moved in in the future.
+
+## Operator Attribute for More Extensions
+
+While the minimum information provided by the operator is enough to get a front-end.
+In order to do transformations and executing the graph. We need more information from each operator.
+A typical difference between neural nets' computation graph and traditional LLVM IR is that
+there are a lot more high level operators. We cannot fix the set of operators in the graph.
+
+Instead developers are allowed to register attributes of operator. The attributes can include shape
+inference function, whether the operator can be carried in-place etc.
+
+This design to having an operator attribute registry is not uncommon in deep learning systems.
+For example, MXNet has a ```OpProperty``` class, Tensorflow has a ```OpDef``` and Caffe2 have a ```OperatorSchema``` class.
+However, the operator attribute interface listed in these frameworks only support a number of defined attributes of interest to the system.
+For example, MXNet support inplace optimization decision, shape and type inference function.
+If we want to extend the framework to add new type of attributes in each operator, we need to change the operator registry.
+Eventually the operator interface become big and have to evolve in the centralized repo.
+
+In NNVM, we decided to change the design and support arbitrary type of operator attributes,
+without need to change the operator registry. This also echos the need of minimum interface
+so that the code can be easier to share accross multiple projects
+
+User can register new attribute, such as inplace property checking function as follows.
+```c++
+using FInplaceOption = std::function<
+  std::vector<std::pair<int, int> > (const NodeAttrs& attrs)>;
+
+// attributes can be registered from multiple places.
+NNVM_REGISTER_OP(add)
+.set_num_inputs(1);
+
+// register to tell first input can be calculate inplace with first output
+NNVM_REGISTER_OP(add)
+.attr<FInplaceOption>("FInplaceOption", [](const NodeAttrs& attrs) {
+  return std::vector<std::pair<int, int> >{{0, 0}};
+ });
+
+NNVM_REGISTER_OP(exp)
+.set_num_inputs(1)
+.attr<FInplaceOption>("FInplaceOption", [](const NodeAttrs& attrs) {
+  return std::vector<std::pair<int, int> >{{0, 0}};
+ });
+```
+
+These attributes can be queried at arbitrary parts of the code, like the following parts.
+Under the hood, each attributes are stored in a any type columar store,
+that can easily be retrieved and cast back to typed table and do quick lookups.
+
+```c++
+void MyFunction() {
+  const Op* add = Op::Get("add");
+  // if we need quick query, we can use static variable
+  // attribute map contains attributes of all operators.
+  static auto& finplace_option_map = Op::GetAttr<FInplaceOption>("FInplaceOption");
+
+  // quick look up attribute of add, O(1) time, vector index lookup internally.
+  auto add_inplace = finplace_option_tbl[add];
+}
+```
+Besides making the code minimum, this attribute store enables decentralization of projects.
+Before, all the attributes of operator have to sit on a centralized interface class.
+Now, everyone can register their own attribute, take some other attribute they need from another project
+without need to change the operator interface.
+
+See [example code](../example/src/operator.cc) on how operators can be registered.
+
+## Graph and Pass
+
+When we get more information about the operators.
+We can use them to do optimizations and get more information about the graph.
+Graph is the unit we manipulate in these steps. A Graph in NNVM contains
+two parts:
+- The computation graph structure
+- A attribute map from string to any type ```map<string, shared_ptr<any> >```
+
+The second attribute map is quite important, as we may need different kinds
+of information about the graph during the transformation process. Let it be
+shapes of each tensor, types of each tensor or the storage allocation plans.
+
+A ```Pass``` can take a graph with existing attribute information,
+and transform it to the same graph with more attributes, or another graph.
+
+We have bunch of pass implemented in NNVM, including symbolic differentiation,
+memory planning, shape/type inference and we can support more.
+
+## Executing the Graph
+
+Currently the library defined nothing on how the graph can be executed.
+Execution is intentionally excluded from this module because we believe
+that can be another module, and there can be many ways to execute one graph.
+We can target different runtime platforms, or even write your own ones.
+
+More importantly, the information such as memory allocation plan,
+shape and type of each tensor can be used during execution phase
+to enhance.
+
+We can also register more runtime related information to the operator registry,
+and define pass function to do runtime related optimization of the graph.
+
+## Relation to LLVM
+
+NNVM is inspired by LLVM. It is at a more high level, in a sense that there are a lot of optimization
+chance we can have by knowing the high level information about the operator.
+On the other hand, we do believe that code generation to LLVM can be a natural extension and can benefit some of the usecases.
+
+## Unix Philosophy in Learning Systems
+
+There are a few existing computation graph based deep learning frameworks (e.g. Theano, Tensorflow, Caffe2, MXNet etc.).
+NNVM do not intend to become another one. Instead, NNVM summarizes a module that contains
+
+- The graph representation is minimum, with no code dependency
+- Operator attribute allow arbitrary information registered in unified way
+- Invariant of execution layer to be re-targetable to multiple frontend and backend.
+
+We believe this is the correct way for learning system.
+By having more such modules, we can pick one ones we need, and remove the ones we do not want in our use cases.
+Hopefully these effort can make deep learning system research and building easy, fun and rewarding.
diff --git a/nnvm/example/src/operator.cc b/nnvm/example/src/operator.cc
index 93a1b3db29c1..e67ad8e024f3 100644
--- a/nnvm/example/src/operator.cc
+++ b/nnvm/example/src/operator.cc
@@ -1,5 +1,7 @@
 // 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>

From e6f27a280172ad4bbe8bd308803d2a3ddfe5d4f3 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 1 Sep 2016 11:40:08 -0700
Subject: [PATCH 368/949] Update README.md

---
 nnvm/README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/README.md b/nnvm/README.md
index 2fa01f87c58a..d58cad954e50 100644
--- a/nnvm/README.md
+++ b/nnvm/README.md
@@ -32,7 +32,7 @@ This is essentially ***Unix philosophy*** applied to machine learning system.
   without creating a monothilic version.
 
 Deep learning system itself is not necessary one part, for example
-here are some relative independent parts that can
+here are some relative independent parts that can be isolated
 
 - Computation graph definition, manipulation.
 - Computation graph intermediate optimization.

From cc468fd3533aa15e2874c23c70a192345c4faa3f Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 1 Sep 2016 15:27:04 -0700
Subject: [PATCH 369/949] trigger travis (#37)

* trigger travis

* fix

* chomo

* make

* squash
---
 nnvm/Makefile                             | 13 ++++++++++++-
 nnvm/README.md                            |  3 +++
 nnvm/include/nnvm/pass_functions.h        |  4 ++--
 nnvm/tests/travis/run_test.sh             | 12 ++++++++----
 nnvm/tests/travis/setup.sh                |  4 ----
 nnvm/tests/travis/travis_after_failure.sh |  0
 6 files changed, 25 insertions(+), 11 deletions(-)
 mode change 100644 => 100755 nnvm/tests/travis/run_test.sh
 mode change 100644 => 100755 nnvm/tests/travis/setup.sh
 mode change 100644 => 100755 nnvm/tests/travis/travis_after_failure.sh

diff --git a/nnvm/Makefile b/nnvm/Makefile
index 07f46ee2456e..5990502367c3 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -11,6 +11,17 @@ SRC = $(wildcard src/*.cc src/*/*.cc)
 ALL_OBJ = $(patsubst src/%.cc, build/%.o, $(SRC))
 ALL_DEP = $(ALL_OBJ)
 
+UNAME_S := $(shell uname -s)
+
+ifeq ($(UNAME_S), Darwin)
+	WHOLE_ARCH= -force_load
+	NO_WHOLE_ARCH= -noforce_load
+else
+	WHOLE_ARCH= --whole-archive
+	NO_WHOLE_ARCH= --no-whole-archive
+endif
+
+
 include tests/cpp/unittest.mk
 
 test: $(TEST)
@@ -27,7 +38,7 @@ lib/libnnvm.a: $(ALL_DEP)
 
 lib/libnnvm_example.so: example/src/operator.cc lib/libnnvm.a
 	@mkdir -p $(@D)
-	$(CXX) $(CFLAGS) -shared -o $@ $(filter %.cc, $^) $(LDFLAGS) -Wl,--whole-archive lib/libnnvm.a -Wl,--no-whole-archive
+	$(CXX) $(CFLAGS) -shared -o $@ $(filter %.cc, $^) $(LDFLAGS) -Wl,${WHOLE_ARCH} lib/libnnvm.a -Wl,${NO_WHOLE_ARCH}
 
 cython:
 	cd python; python setup.py build_ext --inplace
diff --git a/nnvm/README.md b/nnvm/README.md
index d58cad954e50..e25ee9acac70 100644
--- a/nnvm/README.md
+++ b/nnvm/README.md
@@ -1,5 +1,8 @@
 # NNVM: Build deep learning system by parts
 
+[![Build Status](https://travis-ci.org/dmlc/nnvm.svg?branch=master)](https://travis-ci.org/dmlc/nnvm)
+[![GitHub license](http://dmlc.github.io/img/apache2.svg)](./LICENSE)
+
 NNVM is not a deep learning library. It is a modular,
 decentralized and lightweight part to help build deep learning libraries.
 
diff --git a/nnvm/include/nnvm/pass_functions.h b/nnvm/include/nnvm/pass_functions.h
index b7068822d9ee..20019726f15a 100644
--- a/nnvm/include/nnvm/pass_functions.h
+++ b/nnvm/include/nnvm/pass_functions.h
@@ -61,7 +61,7 @@ inline Graph OrderMutation(Graph src) {
  *  The index of ShapeVector is given by graph.indexed_graph().entry_id
  */
 inline Graph InferShape(Graph graph,
-                        ShapeVector shape_inputs = {},
+                        ShapeVector shape_inputs,
                         std::string shape_attr_key = "") {
   if (shape_inputs.size() != 0) {
     graph.attrs["shape_inputs"] = std::make_shared<any>(std::move(shape_inputs));
@@ -81,7 +81,7 @@ inline Graph InferShape(Graph graph,
  *  The index of ShapeVector is given by graph.indexed_graph().entry_id
  */
 inline Graph InferType(Graph graph,
-                       DTypeVector dtype_inputs = {},
+                       DTypeVector dtype_inputs,
                        std::string dtype_attr_key = "") {
   if (dtype_inputs.size() != 0) {
     graph.attrs["dtype_inputs"] = std::make_shared<any>(std::move(dtype_inputs));
diff --git a/nnvm/tests/travis/run_test.sh b/nnvm/tests/travis/run_test.sh
old mode 100644
new mode 100755
index 2c4fe4a2f036..a5ea03a1dc3a
--- a/nnvm/tests/travis/run_test.sh
+++ b/nnvm/tests/travis/run_test.sh
@@ -44,11 +44,15 @@ if [ ${TASK} == "python_test" ]; then
         nosetests3 tests/python/ || exit -1
     fi
 
-    python -m nose tests/python/ || exit -1
-    python3 -m nose tests/python/ || exit -1
     make cython || exit -1
     make cython3 || exit -1
-    python -m nose tests/python/ || exit -1
-    python3 -m nose tests/python/ || exit -1
+
+    if [ ${TRAVIS_OS_NAME} == "osx" ]; then
+        python -m nose tests/python/ || exit -1
+        python3 -m nose tests/python/ || exit -1
+    else
+        nosetests tests/python/ || exit -1
+        nosetests3 tests/python/ || exit -1
+    fi
     exit 0
 fi
diff --git a/nnvm/tests/travis/setup.sh b/nnvm/tests/travis/setup.sh
old mode 100644
new mode 100755
index 2621a7f03f25..42dfc53bc66a
--- a/nnvm/tests/travis/setup.sh
+++ b/nnvm/tests/travis/setup.sh
@@ -13,7 +13,3 @@ fi
 if [ ${TASK} == "lint" ]; then
     pip install cpplint 'pylint==1.4.4' 'astroid==1.3.6' --user `whoami`
 fi
-
-if [ ! -d dmlc-core ]; then
-    git clone https://github.com/dmlc/dmlc-core
-fi
diff --git a/nnvm/tests/travis/travis_after_failure.sh b/nnvm/tests/travis/travis_after_failure.sh
old mode 100644
new mode 100755

From 3ed03299a8a0ba429ec214a4d2e26455902e0e5c Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 1 Sep 2016 18:33:14 -0700
Subject: [PATCH 370/949] travis mac (#38)

* chomo

* try mac options
---
 nnvm/Makefile | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/nnvm/Makefile b/nnvm/Makefile
index 5990502367c3..e07704f499f0 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -14,8 +14,8 @@ ALL_DEP = $(ALL_OBJ)
 UNAME_S := $(shell uname -s)
 
 ifeq ($(UNAME_S), Darwin)
-	WHOLE_ARCH= -force_load
-	NO_WHOLE_ARCH= -noforce_load
+	WHOLE_ARCH= -all_load
+	NO_WHOLE_ARCH= -noall_load
 else
 	WHOLE_ARCH= --whole-archive
 	NO_WHOLE_ARCH= --no-whole-archive

From ac1240c07f057bd36fa66844dfeb196921fea713 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 1 Sep 2016 19:34:31 -0700
Subject: [PATCH 371/949] Update symbolic.cc

---
 nnvm/src/core/symbolic.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index d3a601d41424..ba6633043518 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -346,7 +346,7 @@ void Symbol::Compose(const array_view<const Symbol*>& args,
     };
     DFSVisit(this->outputs, find_replace_map);
 
-    if (nmatched == kwargs.size() && arg_counter < args.size()) {
+    if (nmatched == kwargs.size() && arg_counter <= args.size()) {
       std::vector<Node*> update_nodes;
       std::vector<std::pair<NodeEntry*, const NodeEntry*> > replace_plan;
       auto find_replace_plan = [&replace_map, &replace_plan, &update_nodes]

From a58feb205a9ff094e63b78cf9d041a96e0331817 Mon Sep 17 00:00:00 2001
From: Hu Shiwen <yajiedesign@gmail.com>
Date: Fri, 9 Sep 2016 01:56:42 +0800
Subject: [PATCH 372/949] add cmake with windows (#40)

---
 nnvm/CMakeLists.txt            |  65 ++++++
 nnvm/cmake/Utils.cmake         | 402 +++++++++++++++++++++++++++++++++
 nnvm/include/dmlc/array_view.h |  12 +
 nnvm/src/pass/gradient.cc      |   4 +
 4 files changed, 483 insertions(+)
 create mode 100644 nnvm/CMakeLists.txt
 create mode 100644 nnvm/cmake/Utils.cmake

diff --git a/nnvm/CMakeLists.txt b/nnvm/CMakeLists.txt
new file mode 100644
index 000000000000..22e93665c0b5
--- /dev/null
+++ b/nnvm/CMakeLists.txt
@@ -0,0 +1,65 @@
+cmake_minimum_required(VERSION 2.8.7)
+
+project(nnvm C CXX)
+
+list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
+
+include(cmake/Utils.cmake)
+
+# include path
+include_directories(BEFORE "include")
+
+set(nnvm_LINKER_LIBS "")
+
+add_definitions(-DNNVM_EXPORTS)
+
+# compile
+if(MSVC)
+  add_definitions(-DDMLC_USE_CXX11)
+  add_definitions(-DDMLC_STRICT_CXX11)
+  foreach(flag_var
+        CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
+        CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
+    if(${flag_var} MATCHES "/MD")
+      string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
+    endif(${flag_var} MATCHES "/MD")
+  endforeach(flag_var)
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc")
+else(MSVC)
+  include(CheckCXXCompilerFlag)
+  check_cxx_compiler_flag("-std=c++0x"    SUPPORT_CXX0X)
+  check_cxx_compiler_flag("-msse2"        SUPPORT_MSSE2)
+  check_cxx_compiler_flag("-openmp"       SUPPORT_OPENMP)
+  set(CMAKE_C_FLAGS "-O3 -Wall -msse2 -Wno-unknown-pragmas -std=c++0x -fPIC")
+  if(SUPPORT_OPENMP)
+    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fopenmp")
+  endif()
+  set(CMAKE_CXX_FLAGS ${CMAKE_C_FLAGS})
+endif(MSVC)
+
+mxnet_source_group("Include\\c_api"   GLOB "src/c_api/*.h")
+mxnet_source_group("Include\\core"   GLOB "src/core/*.h")
+mxnet_source_group("Include\\pass"   GLOB "src/pass/*.h")
+mxnet_source_group("Include\\nnvm"   GLOB "include/nnvm/*.h")
+mxnet_source_group("Include\\dmlc"   GLOB "include/dmlc/*.h")
+
+mxnet_source_group("Source"   GLOB "src/*.cc")
+mxnet_source_group("Source\\c_api"   GLOB "src/c_api/*.cc")
+mxnet_source_group("Source\\core"   GLOB "src/core/*.cc")
+mxnet_source_group("Source\\pass"   GLOB "src/pass/*.cc")
+
+
+FILE(GLOB_RECURSE SOURCE "src/*.cc" "src/*.h" "include/*.h")
+
+add_library(nnvm ${SOURCE})
+target_link_libraries(nnvm ${nnvm_LINKER_LIBS})
+
+# ---[ Linter target
+if(MSVC)
+  find_package(PythonInterp 2)
+  set(PYTHON2_EXECUTABLE ${PYTHON_EXECUTABLE} CACHE FILEPATH "Path to the python 2.x executable")
+  find_package(PythonInterp 3)
+  set(PYTHON3_EXECUTABLE ${PYTHON_EXECUTABLE} CACHE FILEPATH "Path to the python 3.x executable")
+endif()
+set(LINT_DIRS include src scripts)
+add_custom_target(nnvm_lint COMMAND ${CMAKE_COMMAND} -DMSVC=${MSVC} -DPYTHON2_EXECUTABLE=${PYTHON2_EXECUTABLE} -DPYTHON3_EXECUTABLE=${PYTHON3_EXECUTABLE} -DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR} -DLINT_DIRS=${LINT_DIRS} -DPROJECT_NAME=dmlc -P ${PROJECT_SOURCE_DIR}/cmake/lint.cmake)
diff --git a/nnvm/cmake/Utils.cmake b/nnvm/cmake/Utils.cmake
new file mode 100644
index 000000000000..0308645df6b8
--- /dev/null
+++ b/nnvm/cmake/Utils.cmake
@@ -0,0 +1,402 @@
+# For cmake_parse_arguments
+include(CMakeParseArguments)
+
+################################################################################################
+# Command alias for debugging messages
+# Usage:
+#   dmsg(<message>)
+function(dmsg)
+  message(STATUS ${ARGN})
+endfunction()
+
+################################################################################################
+# Removes duplicates from list(s)
+# Usage:
+#   mxnet_list_unique(<list_variable> [<list_variable>] [...])
+macro(mxnet_list_unique)
+  foreach(__lst ${ARGN})
+    if(${__lst})
+      list(REMOVE_DUPLICATES ${__lst})
+    endif()
+  endforeach()
+endmacro()
+
+################################################################################################
+# Clears variables from list
+# Usage:
+#   mxnet_clear_vars(<variables_list>)
+macro(mxnet_clear_vars)
+  foreach(_var ${ARGN})
+    unset(${_var})
+  endforeach()
+endmacro()
+
+################################################################################################
+# Removes duplicates from string
+# Usage:
+#   mxnet_string_unique(<string_variable>)
+function(mxnet_string_unique __string)
+  if(${__string})
+    set(__list ${${__string}})
+    separate_arguments(__list)
+    list(REMOVE_DUPLICATES __list)
+    foreach(__e ${__list})
+      set(__str "${__str} ${__e}")
+    endforeach()
+    set(${__string} ${__str} PARENT_SCOPE)
+  endif()
+endfunction()
+
+################################################################################################
+# Prints list element per line
+# Usage:
+#   mxnet_print_list(<list>)
+function(mxnet_print_list)
+  foreach(e ${ARGN})
+    message(STATUS ${e})
+  endforeach()
+endfunction()
+
+################################################################################################
+# Function merging lists of compiler flags to single string.
+# Usage:
+#   mxnet_merge_flag_lists(out_variable <list1> [<list2>] [<list3>] ...)
+function(mxnet_merge_flag_lists out_var)
+  set(__result "")
+  foreach(__list ${ARGN})
+    foreach(__flag ${${__list}})
+      string(STRIP ${__flag} __flag)
+      set(__result "${__result} ${__flag}")
+    endforeach()
+  endforeach()
+  string(STRIP ${__result} __result)
+  set(${out_var} ${__result} PARENT_SCOPE)
+endfunction()
+
+################################################################################################
+# Converts all paths in list to absolute
+# Usage:
+#   mxnet_convert_absolute_paths(<list_variable>)
+function(mxnet_convert_absolute_paths variable)
+  set(__dlist "")
+  foreach(__s ${${variable}})
+    get_filename_component(__abspath ${__s} ABSOLUTE)
+    list(APPEND __list ${__abspath})
+  endforeach()
+  set(${variable} ${__list} PARENT_SCOPE)
+endfunction()
+
+################################################################################################
+# Reads set of version defines from the header file
+# Usage:
+#   mxnet_parse_header(<file> <define1> <define2> <define3> ..)
+macro(mxnet_parse_header FILENAME FILE_VAR)
+  set(vars_regex "")
+  set(__parnet_scope OFF)
+  set(__add_cache OFF)
+  foreach(name ${ARGN})
+    if("${name}" STREQUAL "PARENT_SCOPE")
+      set(__parnet_scope ON)
+    elseif("${name}" STREQUAL "CACHE")
+      set(__add_cache ON)
+    elseif(vars_regex)
+      set(vars_regex "${vars_regex}|${name}")
+    else()
+      set(vars_regex "${name}")
+    endif()
+  endforeach()
+  if(EXISTS "${FILENAME}")
+    file(STRINGS "${FILENAME}" ${FILE_VAR} REGEX "#define[ \t]+(${vars_regex})[ \t]+[0-9]+" )
+  else()
+    unset(${FILE_VAR})
+  endif()
+  foreach(name ${ARGN})
+    if(NOT "${name}" STREQUAL "PARENT_SCOPE" AND NOT "${name}" STREQUAL "CACHE")
+      if(${FILE_VAR})
+        if(${FILE_VAR} MATCHES ".+[ \t]${name}[ \t]+([0-9]+).*")
+          string(REGEX REPLACE ".+[ \t]${name}[ \t]+([0-9]+).*" "\\1" ${name} "${${FILE_VAR}}")
+        else()
+          set(${name} "")
+        endif()
+        if(__add_cache)
+          set(${name} ${${name}} CACHE INTERNAL "${name} parsed from ${FILENAME}" FORCE)
+        elseif(__parnet_scope)
+          set(${name} "${${name}}" PARENT_SCOPE)
+        endif()
+      else()
+        unset(${name} CACHE)
+      endif()
+    endif()
+  endforeach()
+endmacro()
+
+################################################################################################
+# Reads single version define from the header file and parses it
+# Usage:
+#   mxnet_parse_header_single_define(<library_name> <file> <define_name>)
+function(mxnet_parse_header_single_define LIBNAME HDR_PATH VARNAME)
+  set(${LIBNAME}_H "")
+  if(EXISTS "${HDR_PATH}")
+    file(STRINGS "${HDR_PATH}" ${LIBNAME}_H REGEX "^#define[ \t]+${VARNAME}[ \t]+\"[^\"]*\".*$" LIMIT_COUNT 1)
+  endif()
+
+  if(${LIBNAME}_H)
+    string(REGEX REPLACE "^.*[ \t]${VARNAME}[ \t]+\"([0-9]+).*$" "\\1" ${LIBNAME}_VERSION_MAJOR "${${LIBNAME}_H}")
+    string(REGEX REPLACE "^.*[ \t]${VARNAME}[ \t]+\"[0-9]+\\.([0-9]+).*$" "\\1" ${LIBNAME}_VERSION_MINOR  "${${LIBNAME}_H}")
+    string(REGEX REPLACE "^.*[ \t]${VARNAME}[ \t]+\"[0-9]+\\.[0-9]+\\.([0-9]+).*$" "\\1" ${LIBNAME}_VERSION_PATCH "${${LIBNAME}_H}")
+    set(${LIBNAME}_VERSION_MAJOR ${${LIBNAME}_VERSION_MAJOR} ${ARGN} PARENT_SCOPE)
+    set(${LIBNAME}_VERSION_MINOR ${${LIBNAME}_VERSION_MINOR} ${ARGN} PARENT_SCOPE)
+    set(${LIBNAME}_VERSION_PATCH ${${LIBNAME}_VERSION_PATCH} ${ARGN} PARENT_SCOPE)
+    set(${LIBNAME}_VERSION_STRING "${${LIBNAME}_VERSION_MAJOR}.${${LIBNAME}_VERSION_MINOR}.${${LIBNAME}_VERSION_PATCH}" PARENT_SCOPE)
+
+    # append a TWEAK version if it exists:
+    set(${LIBNAME}_VERSION_TWEAK "")
+    if("${${LIBNAME}_H}" MATCHES "^.*[ \t]${VARNAME}[ \t]+\"[0-9]+\\.[0-9]+\\.[0-9]+\\.([0-9]+).*$")
+      set(${LIBNAME}_VERSION_TWEAK "${CMAKE_MATCH_1}" ${ARGN} PARENT_SCOPE)
+    endif()
+    if(${LIBNAME}_VERSION_TWEAK)
+      set(${LIBNAME}_VERSION_STRING "${${LIBNAME}_VERSION_STRING}.${${LIBNAME}_VERSION_TWEAK}" ${ARGN} PARENT_SCOPE)
+    else()
+      set(${LIBNAME}_VERSION_STRING "${${LIBNAME}_VERSION_STRING}" ${ARGN} PARENT_SCOPE)
+    endif()
+  endif()
+endfunction()
+
+########################################################################################################
+# An option that the user can select. Can accept condition to control when option is available for user.
+# Usage:
+#   mxnet_option(<option_variable> "doc string" <initial value or boolean expression> [IF <condition>])
+function(mxnet_option variable description value)
+  set(__value ${value})
+  set(__condition "")
+  set(__varname "__value")
+  foreach(arg ${ARGN})
+    if(arg STREQUAL "IF" OR arg STREQUAL "if")
+      set(__varname "__condition")
+    else()
+      list(APPEND ${__varname} ${arg})
+    endif()
+  endforeach()
+  unset(__varname)
+  if("${__condition}" STREQUAL "")
+    set(__condition 2 GREATER 1)
+  endif()
+
+  if(${__condition})
+    if("${__value}" MATCHES ";")
+      if(${__value})
+        option(${variable} "${description}" ON)
+      else()
+        option(${variable} "${description}" OFF)
+      endif()
+    elseif(DEFINED ${__value})
+      if(${__value})
+        option(${variable} "${description}" ON)
+      else()
+        option(${variable} "${description}" OFF)
+      endif()
+    else()
+      option(${variable} "${description}" ${__value})
+    endif()
+  else()
+    unset(${variable} CACHE)
+  endif()
+endfunction()
+
+################################################################################################
+# Utility macro for comparing two lists. Used for CMake debugging purposes
+# Usage:
+#   mxnet_compare_lists(<list_variable> <list2_variable> [description])
+function(mxnet_compare_lists list1 list2 desc)
+  set(__list1 ${${list1}})
+  set(__list2 ${${list2}})
+  list(SORT __list1)
+  list(SORT __list2)
+  list(LENGTH __list1 __len1)
+  list(LENGTH __list2 __len2)
+
+  if(NOT ${__len1} EQUAL ${__len2})
+    message(FATAL_ERROR "Lists are not equal. ${__len1} != ${__len2}. ${desc}")
+  endif()
+
+  foreach(__i RANGE 1 ${__len1})
+    math(EXPR __index "${__i}- 1")
+    list(GET __list1 ${__index} __item1)
+    list(GET __list2 ${__index} __item2)
+    if(NOT ${__item1} STREQUAL ${__item2})
+      message(FATAL_ERROR "Lists are not equal. Differ at element ${__index}. ${desc}")
+    endif()
+  endforeach()
+endfunction()
+
+################################################################################################
+# Command for disabling warnings for different platforms (see below for gcc and VisualStudio)
+# Usage:
+#   mxnet_warnings_disable(<CMAKE_[C|CXX]_FLAGS[_CONFIGURATION]> -Wshadow /wd4996 ..,)
+macro(mxnet_warnings_disable)
+  set(_flag_vars "")
+  set(_msvc_warnings "")
+  set(_gxx_warnings "")
+
+  foreach(arg ${ARGN})
+    if(arg MATCHES "^CMAKE_")
+      list(APPEND _flag_vars ${arg})
+    elseif(arg MATCHES "^/wd")
+      list(APPEND _msvc_warnings ${arg})
+    elseif(arg MATCHES "^-W")
+      list(APPEND _gxx_warnings ${arg})
+    endif()
+  endforeach()
+
+  if(NOT _flag_vars)
+    set(_flag_vars CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
+  endif()
+
+  if(MSVC AND _msvc_warnings)
+    foreach(var ${_flag_vars})
+      foreach(warning ${_msvc_warnings})
+        set(${var} "${${var}} ${warning}")
+      endforeach()
+    endforeach()
+  elseif((CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX) AND _gxx_warnings)
+    foreach(var ${_flag_vars})
+      foreach(warning ${_gxx_warnings})
+        if(NOT warning MATCHES "^-Wno-")
+          string(REPLACE "${warning}" "" ${var} "${${var}}")
+          string(REPLACE "-W" "-Wno-" warning "${warning}")
+        endif()
+        set(${var} "${${var}} ${warning}")
+      endforeach()
+    endforeach()
+  endif()
+  mxnet_clear_vars(_flag_vars _msvc_warnings _gxx_warnings)
+endmacro()
+
+################################################################################################
+# Helper function get current definitions
+# Usage:
+#   mxnet_get_current_definitions(<definitions_variable>)
+function(mxnet_get_current_definitions definitions_var)
+  get_property(current_definitions DIRECTORY PROPERTY COMPILE_DEFINITIONS)
+  set(result "")
+
+  foreach(d ${current_definitions})
+    list(APPEND result -D${d})
+  endforeach()
+
+  mxnet_list_unique(result)
+  set(${definitions_var} ${result} PARENT_SCOPE)
+endfunction()
+
+################################################################################################
+# Helper function get current includes/definitions
+# Usage:
+#   mxnet_get_current_cflags(<cflagslist_variable>)
+function(mxnet_get_current_cflags cflags_var)
+  get_property(current_includes DIRECTORY PROPERTY INCLUDE_DIRECTORIES)
+  mxnet_convert_absolute_paths(current_includes)
+  mxnet_get_current_definitions(cflags)
+
+  foreach(i ${current_includes})
+    list(APPEND cflags "-I${i}")
+  endforeach()
+
+  mxnet_list_unique(cflags)
+  set(${cflags_var} ${cflags} PARENT_SCOPE)
+endfunction()
+
+################################################################################################
+# Helper function to parse current linker libs into link directories, libflags and osx frameworks
+# Usage:
+#   mxnet_parse_linker_libs(<mxnet_LINKER_LIBS_var> <directories_var> <libflags_var> <frameworks_var>)
+function(mxnet_parse_linker_libs mxnet_LINKER_LIBS_variable folders_var flags_var frameworks_var)
+
+  set(__unspec "")
+  set(__debug "")
+  set(__optimized "")
+  set(__framework "")
+  set(__varname "__unspec")
+
+  # split libs into debug, optimized, unspecified and frameworks
+  foreach(list_elem ${${mxnet_LINKER_LIBS_variable}})
+    if(list_elem STREQUAL "debug")
+      set(__varname "__debug")
+    elseif(list_elem STREQUAL "optimized")
+      set(__varname "__optimized")
+    elseif(list_elem MATCHES "^-framework[ \t]+([^ \t].*)")
+      list(APPEND __framework -framework ${CMAKE_MATCH_1})
+    else()
+      list(APPEND ${__varname} ${list_elem})
+      set(__varname "__unspec")
+    endif()
+  endforeach()
+
+  # attach debug or optimized libs to unspecified according to current configuration
+  if(CMAKE_BUILD_TYPE MATCHES "Debug")
+    set(__libs ${__unspec} ${__debug})
+  else()
+    set(__libs ${__unspec} ${__optimized})
+  endif()
+
+  set(libflags "")
+  set(folders "")
+
+  # convert linker libraries list to link flags
+  foreach(lib ${__libs})
+    if(TARGET ${lib})
+      list(APPEND folders $<TARGET_LINKER_FILE_DIR:${lib}>)
+      list(APPEND libflags -l${lib})
+    elseif(lib MATCHES "^-l.*")
+      list(APPEND libflags ${lib})
+    elseif(IS_ABSOLUTE ${lib})
+      get_filename_component(name_we ${lib} NAME_WE)
+      get_filename_component(folder  ${lib} PATH)
+
+      string(REGEX MATCH "^lib(.*)" __match ${name_we})
+      list(APPEND libflags -l${CMAKE_MATCH_1})
+      list(APPEND folders    ${folder})
+    else()
+      message(FATAL_ERROR "Logic error. Need to update cmake script")
+    endif()
+  endforeach()
+
+  mxnet_list_unique(libflags folders)
+
+  set(${folders_var} ${folders} PARENT_SCOPE)
+  set(${flags_var} ${libflags} PARENT_SCOPE)
+  set(${frameworks_var} ${__framework} PARENT_SCOPE)
+endfunction()
+
+################################################################################################
+# Helper function to detect Darwin version, i.e. 10.8, 10.9, 10.10, ....
+# Usage:
+#   mxnet_detect_darwin_version(<version_variable>)
+function(mxnet_detect_darwin_version output_var)
+  if(APPLE)
+    execute_process(COMMAND /usr/bin/sw_vers -productVersion
+                    RESULT_VARIABLE __sw_vers OUTPUT_VARIABLE __sw_vers_out
+                    ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+    set(${output_var} ${__sw_vers_out} PARENT_SCOPE)
+  else()
+    set(${output_var} "" PARENT_SCOPE)
+  endif()
+endfunction()
+
+################################################################################################
+# Convenient command to setup source group for IDEs that support this feature (VS, XCode)
+# Usage:
+#   caffe_source_group(<group> GLOB[_RECURSE] <globbing_expression>)
+function(mxnet_source_group group)
+  cmake_parse_arguments(CAFFE_SOURCE_GROUP "" "" "GLOB;GLOB_RECURSE" ${ARGN})
+  if(CAFFE_SOURCE_GROUP_GLOB)
+    file(GLOB srcs1 ${CAFFE_SOURCE_GROUP_GLOB})
+    source_group(${group} FILES ${srcs1})
+  endif()
+
+  if(CAFFE_SOURCE_GROUP_GLOB_RECURSE)
+    file(GLOB_RECURSE srcs2 ${CAFFE_SOURCE_GROUP_GLOB_RECURSE})
+    source_group(${group} FILES ${srcs2})
+  endif()
+endfunction()
+
diff --git a/nnvm/include/dmlc/array_view.h b/nnvm/include/dmlc/array_view.h
index c9f19eff3720..5e01a78cc53d 100644
--- a/nnvm/include/dmlc/array_view.h
+++ b/nnvm/include/dmlc/array_view.h
@@ -42,11 +42,23 @@ class array_view {
    * \param other another array view.
    */
   array_view(const array_view<ValueType> &other) = default;  // NOLINT(*)
+#ifndef _MSC_VER
   /*!
    * \brief default move constructor
    * \param other another array view.
    */
   array_view(array_view<ValueType>&& other) = default; // NOLINT(*)
+#else
+  /*!
+  * \brief default move constructor
+  * \param other another array view.
+  */
+  array_view(array_view<ValueType>&& other) { // NOLINT(*)
+    begin_ = other.begin_;
+    size_ = other.size_;
+    other.begin_ = nullptr;
+  }
+#endif
   /*!
    * \brief default assign constructor
    * \param other another array view.
diff --git a/nnvm/src/pass/gradient.cc b/nnvm/src/pass/gradient.cc
index a64cf0c11303..2a6bd00e0e8e 100644
--- a/nnvm/src/pass/gradient.cc
+++ b/nnvm/src/pass/gradient.cc
@@ -32,7 +32,11 @@ NodeEntry DefaultAggregateGradient(std::vector<NodeEntry>&& v) {
 
 // helper entry
 struct GradEntry {
+#ifdef _MSC_VER
+  NodeEntry sum = NodeEntry{nullptr, 0, 0};
+#else
   NodeEntry sum{nullptr, 0, 0};
+#endif
   std::vector<NodeEntry> grads;
 };
 

From 038945feae472fd04523a7a4ec62d1034922129c Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 8 Sep 2016 16:28:47 -0700
Subject: [PATCH 373/949] Enable Alias, refactor C API to reflect Op Semantics
 (#41)

* Enable Alias, refactor C API to reflect Op Semantics

* add alias example
---
 nnvm/example/src/operator.cc                  |  1 +
 nnvm/include/dmlc/registry.h                  | 41 +++++++++---
 nnvm/include/nnvm/c_api.h                     | 54 +++++++++++-----
 nnvm/include/nnvm/op.h                        |  7 +++
 nnvm/python/nnvm/_base.py                     |  2 +-
 nnvm/python/nnvm/{ctypes => _ctypes}/README   |  0
 .../nnvm/{ctypes => _ctypes}/__init__.py      |  0
 .../python/nnvm/{ctypes => _ctypes}/symbol.py | 37 ++++++-----
 nnvm/python/nnvm/cython/base.pyi              |  2 +-
 nnvm/python/nnvm/cython/symbol.pyd            |  6 --
 nnvm/python/nnvm/cython/symbol.pyx            | 62 +++++++++++--------
 nnvm/python/nnvm/symbol.py                    | 15 ++---
 nnvm/src/c_api/c_api_symbolic.cc              | 47 ++++++++++----
 nnvm/src/core/op.cc                           |  5 ++
 14 files changed, 184 insertions(+), 95 deletions(-)
 rename nnvm/python/nnvm/{ctypes => _ctypes}/README (100%)
 rename nnvm/python/nnvm/{ctypes => _ctypes}/__init__.py (100%)
 rename nnvm/python/nnvm/{ctypes => _ctypes}/symbol.py (87%)
 delete mode 100644 nnvm/python/nnvm/cython/symbol.pyd

diff --git a/nnvm/example/src/operator.cc b/nnvm/example/src/operator.cc
index e67ad8e024f3..7ec898961c9b 100644
--- a/nnvm/example/src/operator.cc
+++ b/nnvm/example/src/operator.cc
@@ -127,6 +127,7 @@ NNVM_REGISTER_OP(identity)
 NNVM_REGISTER_OP(add)
 .describe("add two data together")
 .set_num_inputs(2)
+.add_alias("__add_symbol__")
 .attr<FInferShape>("FInferShape", SameShape)
 .attr<FInplaceOption>("FInplaceOption", InplaceIn0Out0)
 .attr<FGradient>(
diff --git a/nnvm/include/dmlc/registry.h b/nnvm/include/dmlc/registry.h
index 380b31cd3d61..4f9947831c28 100644
--- a/nnvm/include/dmlc/registry.h
+++ b/nnvm/include/dmlc/registry.h
@@ -26,9 +26,19 @@ namespace dmlc {
 template<typename EntryType>
 class Registry {
  public:
-  /*! \return list of functions in the registry */
-  inline static const std::vector<const EntryType*> &List() {
-    return Get()->entry_list_;
+  /*! \return list of entries in the registry(excluding alias) */
+  inline static const std::vector<const EntryType*>& List() {
+    return Get()->const_list_;
+  }
+  /*! \return list all names registered in the registry, including alias */
+  inline static std::vector<std::string> ListAllNames() {
+    const std::map<std::string, EntryType*> &fmap = Get()->fmap_;
+    typename std::map<std::string, EntryType*>::const_iterator p;
+    std::vector<std::string> names;
+    for (p = fmap.begin(); p !=fmap.end(); ++p) {
+      names.push_back(p->first);
+    }
+    return names;
   }
   /*!
    * \brief Find the entry with corresponding name.
@@ -44,6 +54,21 @@ class Registry {
       return NULL;
     }
   }
+  /*!
+   * \brief Add alias to the key_name
+   * \param key_name The original entry key
+   * \param alias The alias key.
+   */
+  inline void AddAlias(const std::string& key_name,
+                       const std::string& alias) {
+    EntryType* e = fmap_.at(key_name);
+    if (fmap_.count(alias)) {
+      CHECK_EQ(e, fmap_.at(alias))
+          << "Entry " << e->name << " already registered under different entry";
+    } else {
+      fmap_[alias] = e;
+    }
+  }
   /*!
    * \brief Internal function to register a name function under name.
    * \param name name of the function
@@ -55,6 +80,7 @@ class Registry {
     EntryType *e = new EntryType();
     e->name = name;
     fmap_[name] = e;
+    const_list_.push_back(e);
     entry_list_.push_back(e);
     return *e;
   }
@@ -79,16 +105,17 @@ class Registry {
 
  private:
   /*! \brief list of entry types */
-  std::vector<const EntryType*> entry_list_;
+  std::vector<EntryType*> entry_list_;
+  /*! \brief list of entry types */
+  std::vector<const EntryType*> const_list_;
   /*! \brief map of name->function */
   std::map<std::string, EntryType*> fmap_;
   /*! \brief constructor */
   Registry() {}
   /*! \brief destructor */
   ~Registry() {
-    for (typename std::map<std::string, EntryType*>::iterator p = fmap_.begin();
-         p != fmap_.end(); ++p) {
-      delete p->second;
+    for (size_t i = 0; i < entry_list_.size(); ++i) {
+      delete entry_list_[i];
     }
   }
 };
diff --git a/nnvm/include/nnvm/c_api.h b/nnvm/include/nnvm/c_api.h
index f92470111594..3122e26b7038 100644
--- a/nnvm/include/nnvm/c_api.h
+++ b/nnvm/include/nnvm/c_api.h
@@ -29,7 +29,7 @@
 typedef unsigned int nn_uint;
 
 /*! \brief handle to a function that takes param and creates symbol */
-typedef void *AtomicSymbolCreator;
+typedef void *OpHandle;
 /*! \brief handle to a symbol that can be bind as operator */
 typedef void *SymbolHandle;
 /*! \brief handle to Graph */
@@ -53,17 +53,39 @@ NNVM_DLL void NNAPISetLastError(const char* msg);
 NNVM_DLL const char *NNGetLastError(void);
 
 /*!
- * \brief list all the available AtomicSymbolEntry
+ * \brief list all the available operator names, include entries.
+ * \param out_size the size of returned array
+ * \param out_array the output operator name array.
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNListAllOpNames(nn_uint *out_size,
+                              const char*** out_array);
+
+/*!
+ * \brief Get operator handle given name.
+ * \param op_name The name of the operator.
+ * \param op_out The returnning op handle.
+ */
+NNVM_DLL int NNGetOpHandle(const char* op_name,
+                           OpHandle* op_out);
+
+/*!
+ * \brief list all the available operators.
+ *  This won't include the alias, use ListAllNames
+ *  instead to get all alias names.
+ *
  * \param out_size the size of returned array
  * \param out_array the output AtomicSymbolCreator array
  * \return 0 when success, -1 when failure happens
  */
-NNVM_DLL int NNSymbolListAtomicSymbolCreators(nn_uint *out_size,
-                                              AtomicSymbolCreator **out_array);
+NNVM_DLL int NNListUniqueOps(nn_uint *out_size,
+                             OpHandle **out_array);
+
 /*!
  * \brief Get the detailed information about atomic symbol.
- * \param creator the AtomicSymbolCreator.
- * \param name The returned name of the creator.
+ * \param op The operator handle.
+ * \param real_name The returned name of the creator.
+ *   This name is not the alias name of the atomic symbol.
  * \param description The returned description of the symbol.
  * \param num_doc_args Number of arguments that contain documents.
  * \param arg_names Name of the arguments of doc args
@@ -72,24 +94,24 @@ NNVM_DLL int NNSymbolListAtomicSymbolCreators(nn_uint *out_size,
  * \param return_type Return type of the function, if any.
  * \return 0 when success, -1 when failure happens
  */
-NNVM_DLL int NNSymbolGetAtomicSymbolInfo(AtomicSymbolCreator creator,
-                                         const char **name,
-                                         const char **description,
-                                         nn_uint *num_doc_args,
-                                         const char ***arg_names,
-                                         const char ***arg_type_infos,
-                                         const char ***arg_descriptions,
-                                         const char **return_type);
+NNVM_DLL int NNGetOpInfo(OpHandle op,
+                         const char **real_name,
+                         const char **description,
+                         nn_uint *num_doc_args,
+                         const char ***arg_names,
+                         const char ***arg_type_infos,
+                         const char ***arg_descriptions,
+                         const char **return_type);
 /*!
  * \brief Create an AtomicSymbol functor.
- * \param creator the AtomicSymbolCreator
+ * \param op The operator handle
  * \param num_param the number of parameters
  * \param keys the keys to the params
  * \param vals the vals of the params
  * \param out pointer to the created symbol handle
  * \return 0 when success, -1 when failure happens
  */
-NNVM_DLL int NNSymbolCreateAtomicSymbol(AtomicSymbolCreator creator,
+NNVM_DLL int NNSymbolCreateAtomicSymbol(OpHandle op,
                                         nn_uint num_param,
                                         const char **keys,
                                         const char **vals,
diff --git a/nnvm/include/nnvm/op.h b/nnvm/include/nnvm/op.h
index e49bc9ae6643..e754d5988013 100644
--- a/nnvm/include/nnvm/op.h
+++ b/nnvm/include/nnvm/op.h
@@ -199,6 +199,13 @@ class Op {
    * \return reference to self.
    */
   inline Op& set_attr_parser(std::function<void (NodeAttrs* attrs)> fn);  // NOLINT(*)
+  /*!
+   * \brief Add another alias to this operator.
+   *   The same Op can be queried with Op::Get(alias)
+   * \param alias The alias of the operator.
+   * \return reference to self.
+   */
+  Op& add_alias(const std::string& alias);  // NOLINT(*)
   /*!
    * \brief Register additional attributes to operator.
    * \param attr_name The name of the attribute.
diff --git a/nnvm/python/nnvm/_base.py b/nnvm/python/nnvm/_base.py
index 825a3d380f38..ccc4a3bdb501 100644
--- a/nnvm/python/nnvm/_base.py
+++ b/nnvm/python/nnvm/_base.py
@@ -45,7 +45,7 @@ def _load_lib():
 
 # type definitions
 nn_uint = ctypes.c_uint
-SymbolCreatorHandle = ctypes.c_void_p
+OpHandle = ctypes.c_void_p
 SymbolHandle = ctypes.c_void_p
 GraphHandle = ctypes.c_void_p
 
diff --git a/nnvm/python/nnvm/ctypes/README b/nnvm/python/nnvm/_ctypes/README
similarity index 100%
rename from nnvm/python/nnvm/ctypes/README
rename to nnvm/python/nnvm/_ctypes/README
diff --git a/nnvm/python/nnvm/ctypes/__init__.py b/nnvm/python/nnvm/_ctypes/__init__.py
similarity index 100%
rename from nnvm/python/nnvm/ctypes/__init__.py
rename to nnvm/python/nnvm/_ctypes/__init__.py
diff --git a/nnvm/python/nnvm/ctypes/symbol.py b/nnvm/python/nnvm/_ctypes/symbol.py
similarity index 87%
rename from nnvm/python/nnvm/ctypes/symbol.py
rename to nnvm/python/nnvm/_ctypes/symbol.py
index 3f5cb4e2abd7..0ae6d7a08495 100644
--- a/nnvm/python/nnvm/ctypes/symbol.py
+++ b/nnvm/python/nnvm/_ctypes/symbol.py
@@ -8,7 +8,7 @@
 import sys
 from .._base import _LIB
 from .._base import c_array, c_str, nn_uint, py_str, string_types
-from .._base import SymbolHandle
+from .._base import SymbolHandle, OpHandle
 from .._base import check_call, ctypes2docstring
 from ..name import NameManager
 from ..attribute import AttrScope
@@ -114,9 +114,9 @@ def _set_symbol_class(cls):
     _symbol_cls = cls
 
 
-def _make_atomic_symbol_function(handle):
+def _make_atomic_symbol_function(handle, name):
     """Create an atomic symbol function by handle and funciton name."""
-    name = ctypes.c_char_p()
+    real_name = ctypes.c_char_p()
     desc = ctypes.c_char_p()
     num_args = nn_uint()
     arg_names = ctypes.POINTER(ctypes.c_char_p)()
@@ -124,15 +124,15 @@ def _make_atomic_symbol_function(handle):
     arg_descs = ctypes.POINTER(ctypes.c_char_p)()
     ret_type = ctypes.c_char_p()
 
-    check_call(_LIB.NNSymbolGetAtomicSymbolInfo(
-        handle, ctypes.byref(name), ctypes.byref(desc),
+    check_call(_LIB.NNGetOpInfo(
+        handle, ctypes.byref(real_name), ctypes.byref(desc),
         ctypes.byref(num_args),
         ctypes.byref(arg_names),
         ctypes.byref(arg_types),
         ctypes.byref(arg_descs),
         ctypes.byref(ret_type)))
     param_str = ctypes2docstring(num_args, arg_names, arg_types, arg_descs)
-    func_name = py_str(name.value)
+    func_name = name
     desc = py_str(desc.value)
 
     doc_str = ('%s\n\n' +
@@ -199,22 +199,25 @@ def creator(*args, **kwargs):
     return creator
 
 
-def _init_symbol_module():
+def _init_symbol_module(symbol_class, root_namespace):
     """List and add all the atomic symbol functions to current module."""
-    plist = ctypes.POINTER(ctypes.c_void_p)()
+    _set_symbol_class(symbol_class)
+    plist = ctypes.POINTER(ctypes.c_char_p)()
     size = ctypes.c_uint()
 
-    check_call(_LIB.NNSymbolListAtomicSymbolCreators(ctypes.byref(size),
-                                                     ctypes.byref(plist)))
-    module_obj = sys.modules["nnvm.symbol"]
-    module_internal = sys.modules["nnvm._symbol_internal"]
+    check_call(_LIB.NNListAllOpNames(ctypes.byref(size),
+                                     ctypes.byref(plist)))
+    op_names = []
     for i in range(size.value):
-        hdl = SymbolHandle(plist[i])
-        function = _make_atomic_symbol_function(hdl)
+        op_names.append(py_str(plist[i]))
+
+    module_obj = sys.modules["%s.symbol" % root_namespace]
+    module_internal = sys.modules["%s._symbol_internal" % root_namespace]
+    for name in op_names:
+        hdl = OpHandle()
+        check_call(_LIB.NNGetOpHandle(c_str(name), ctypes.byref(hdl)))
+        function = _make_atomic_symbol_function(hdl, name)
         if function.__name__.startswith('_'):
             setattr(module_internal, function.__name__, function)
         else:
             setattr(module_obj, function.__name__, function)
-
-# Initialize the atomic symbol in startups
-_init_symbol_module()
diff --git a/nnvm/python/nnvm/cython/base.pyi b/nnvm/python/nnvm/cython/base.pyi
index 1163f64b07fd..186d65a46b68 100644
--- a/nnvm/python/nnvm/cython/base.pyi
+++ b/nnvm/python/nnvm/cython/base.pyi
@@ -1,5 +1,5 @@
 ctypedef void* SymbolHandle
-ctypedef void* AtomicSymbolCreator
+ctypedef void* OpHandle
 ctypedef unsigned nn_uint
 
 cdef py_str(const char* x):
diff --git a/nnvm/python/nnvm/cython/symbol.pyd b/nnvm/python/nnvm/cython/symbol.pyd
deleted file mode 100644
index a5df49d25f54..000000000000
--- a/nnvm/python/nnvm/cython/symbol.pyd
+++ /dev/null
@@ -1,6 +0,0 @@
-ctypedef void* SymbolHandle
-
-
-cdef class Symbol:
-    # handle for symbolic operator.
-    cdef SymbolHandle handle
diff --git a/nnvm/python/nnvm/cython/symbol.pyx b/nnvm/python/nnvm/cython/symbol.pyx
index ee3e2d0bf258..fd7c3755786c 100644
--- a/nnvm/python/nnvm/cython/symbol.pyx
+++ b/nnvm/python/nnvm/cython/symbol.pyx
@@ -14,21 +14,25 @@ include "./base.pyi"
 
 cdef extern from "nnvm/c_api.h":
     const char* NNGetLastError();
-    int NNSymbolListAtomicSymbolCreators(nn_uint *out_size,
-                                         AtomicSymbolCreator **out_array);
-    int NNSymbolCreateAtomicSymbol(AtomicSymbolCreator creator,
+    int NNListAllOpNames(nn_uint *out_size,
+                      const char ***out_array);
+    int NNGetOpHandle(const char *op_name,
+                      OpHandle *handle);
+    int NNGetOpInfo(OpHandle op,
+                    const char **name,
+                    const char **description,
+                    nn_uint *num_doc_args,
+                    const char ***arg_names,
+                    const char ***arg_type_infos,
+                    const char ***arg_descriptions,
+                    const char **return_type);
+    int NNListOpNames(nn_uint *out_size,
+                      const char ***out_array);
+    int NNSymbolCreateAtomicSymbol(OpHandle op,
                                    nn_uint num_param,
                                    const char **keys,
                                    const char **vals,
                                    SymbolHandle *out);
-    int NNSymbolGetAtomicSymbolInfo(AtomicSymbolCreator creator,
-                                    const char **name,
-                                    const char **description,
-                                    nn_uint *num_doc_args,
-                                    const char ***arg_names,
-                                    const char ***arg_type_infos,
-                                    const char ***arg_descriptions,
-                                    const char **return_type);
     int NNSymbolFree(SymbolHandle symbol);
     int NNSymbolSetAttrs(SymbolHandle symbol,
                          nn_uint num_param,
@@ -88,7 +92,7 @@ cdef SymbolSetAttr(SymbolHandle handle, dict kwargs):
 
 _symbol_cls = SymbolBase
 
-def _set_symbol_class(cls):
+cdef _set_symbol_class(cls):
     global _symbol_cls
     _symbol_cls = cls
 
@@ -98,9 +102,9 @@ cdef NewSymbol(SymbolHandle handle):
     (<SymbolBase>sym).handle = handle
     return sym
 
-cdef _make_atomic_symbol_function(AtomicSymbolCreator handle):
+cdef _make_atomic_symbol_function(OpHandle handle, string name):
     """Create an atomic symbol function by handle and funciton name."""
-    cdef const char *name
+    cdef const char *real_name
     cdef const char *desc
     cdef nn_uint num_args
     cdef const char** arg_names
@@ -108,13 +112,14 @@ cdef _make_atomic_symbol_function(AtomicSymbolCreator handle):
     cdef const char** arg_descs
     cdef const char* return_type
 
-    CALL(NNSymbolGetAtomicSymbolInfo(
-        handle, &name, &desc,
+    CALL(NNGetOpInfo(
+        handle, &real_name, &desc,
         &num_args, &arg_names,
         &arg_types, &arg_descs,
         &return_type))
+
     param_str = BuildDoc(num_args, arg_names, arg_types, arg_descs)
-    func_name = py_str(name)
+    func_name = py_str(name.c_str())
     doc_str = ('%s\n\n' +
                '%s\n' +
                'name : string, optional.\n' +
@@ -190,20 +195,23 @@ cdef _make_atomic_symbol_function(AtomicSymbolCreator handle):
     return creator
 
 
-def _init_symbol_module():
+def _init_symbol_module(symbol_class, root_namespace):
     """List and add all the atomic symbol functions to current module."""
-    cdef AtomicSymbolCreator* plist
+    cdef const char** op_name_ptrs
     cdef nn_uint size
-    CALL(NNSymbolListAtomicSymbolCreators(&size, &plist))
-    module_obj = _sys.modules["nnvm.symbol"]
-    module_internal = _sys.modules["nnvm._symbol_internal"]
-    for i in range(size):
-        function = _make_atomic_symbol_function(plist[i])
+    cdef vector[string] op_names
+    cdef OpHandle handle
 
+    _set_symbol_class(symbol_class)
+    CALL(NNListAllOpNames(&size, &op_name_ptrs))
+    for i in range(size):
+        op_names.push_back(string(op_name_ptrs[i]));
+    module_obj = _sys.modules["%s.symbol" % root_namespace]
+    module_internal = _sys.modules["%s._symbol_internal" % root_namespace]
+    for i in range(op_names.size()):
+        CALL(NNGetOpHandle(op_names[i].c_str(), &handle))
+        function = _make_atomic_symbol_function(handle, op_names[i])
         if function.__name__.startswith('_'):
             setattr(module_internal, function.__name__, function)
         else:
             setattr(module_obj, function.__name__, function)
-
-# Initialize the atomic symbol in startups
-_init_symbol_module()
diff --git a/nnvm/python/nnvm/symbol.py b/nnvm/python/nnvm/symbol.py
index e753299c7433..df5bad28266a 100644
--- a/nnvm/python/nnvm/symbol.py
+++ b/nnvm/python/nnvm/symbol.py
@@ -12,15 +12,16 @@
 
 # Use different verison of SymbolBase
 # When possible, use cython to speedup part of computation.
+
 try:
-    if int(_os.environ.get("NNVM_ENABLE_CYTHON", True)) == 0:
-        from .ctypes.symbol import SymbolBase, _set_symbol_class
+    if int(_os.environ.get("MXNET_ENABLE_CYTHON", True)) == 0:
+        from ._ctypes.symbol import SymbolBase,  _init_symbol_module
     elif _sys.version_info >= (3, 0):
-        from ._cy3.symbol import SymbolBase, _set_symbol_class
+        from ._cy3.symbol import SymbolBase,  _init_symbol_module
     else:
-        from ._cy2.symbol import SymbolBase, _set_symbol_class
-except:
-    from .ctypes.symbol import SymbolBase, _set_symbol_class
+        from ._cy2.symbol import SymbolBase,  _init_symbol_module
+except ImportError:
+    from ._ctypes.symbol import SymbolBase,  _init_symbol_module
 
 
 class Symbol(SymbolBase):
@@ -286,4 +287,4 @@ def Group(symbols):
     return Symbol(handle)
 
 # Set the real symbol class to Symbol
-_set_symbol_class(Symbol)
+_init_symbol_module(Symbol, "nnvm")
diff --git a/nnvm/src/c_api/c_api_symbolic.cc b/nnvm/src/c_api/c_api_symbolic.cc
index dcdce820c7e6..ebcc252ed455 100644
--- a/nnvm/src/c_api/c_api_symbolic.cc
+++ b/nnvm/src/c_api/c_api_symbolic.cc
@@ -10,24 +10,45 @@
 
 using namespace nnvm;
 
-int NNSymbolListAtomicSymbolCreators(nn_uint *out_size,
-                                     AtomicSymbolCreator **out_array) {
+int NNListAllOpNames(nn_uint *out_size,
+                     const char*** out_array) {
+  API_BEGIN();
+  NNAPIThreadLocalEntry *ret = NNAPIThreadLocalStore::Get();
+  ret->ret_vec_str = dmlc::Registry<Op>::ListAllNames();
+  ret->ret_vec_charp.clear();
+  for (size_t i = 0; i < ret->ret_vec_str.size(); ++i) {
+    ret->ret_vec_charp.push_back(ret->ret_vec_str[i].c_str());
+  }
+  *out_array = dmlc::BeginPtr(ret->ret_vec_charp);
+  *out_size = static_cast<nn_uint>(ret->ret_vec_str.size());
+  API_END();
+}
+
+int NNGetOpHandle(const char* op_name,
+                  OpHandle* op_out) {
+  API_BEGIN();
+  *op_out = (OpHandle)Op::Get(op_name);  // NOLINT(*)
+  API_END();
+}
+
+int NNListUniqueOps(nn_uint *out_size,
+                          OpHandle **out_array) {
   API_BEGIN();
   auto &vec = dmlc::Registry<Op>::List();
   *out_size = static_cast<nn_uint>(vec.size());
-  *out_array = (AtomicSymbolCreator*)(dmlc::BeginPtr(vec));  //  NOLINT(*)
+  *out_array = (OpHandle*)(dmlc::BeginPtr(vec));  //  NOLINT(*)
   API_END();
 }
 
-int NNSymbolGetAtomicSymbolInfo(AtomicSymbolCreator creator,
-                                const char **name,
-                                const char **description,
-                                nn_uint *num_doc_args,
-                                const char ***arg_names,
-                                const char ***arg_type_infos,
-                                const char ***arg_descriptions,
-                                const char **return_type) {
-  const Op *op = static_cast<const Op *>(creator);
+int NNGetOpInfo(OpHandle handle,
+                const char **name,
+                const char **description,
+                nn_uint *num_doc_args,
+                const char ***arg_names,
+                const char ***arg_type_infos,
+                const char ***arg_descriptions,
+                const char **return_type) {
+  const Op *op = static_cast<const Op *>(handle);
   NNAPIThreadLocalEntry *ret = NNAPIThreadLocalStore::Get();
 
   API_BEGIN();
@@ -51,7 +72,7 @@ int NNSymbolGetAtomicSymbolInfo(AtomicSymbolCreator creator,
   API_END();
 }
 
-int NNSymbolCreateAtomicSymbol(AtomicSymbolCreator creator,
+int NNSymbolCreateAtomicSymbol(OpHandle creator,
                                nn_uint num_param,
                                const char **keys,
                                const char **vals,
diff --git a/nnvm/src/core/op.cc b/nnvm/src/core/op.cc
index a5518ace6be1..a8e54ab92cab 100644
--- a/nnvm/src/core/op.cc
+++ b/nnvm/src/core/op.cc
@@ -38,6 +38,11 @@ Op::Op() {
   index_ = mgr->op_counter++;
 }
 
+Op& Op::add_alias(const std::string& alias) {  // NOLINT(*)
+  dmlc::Registry<Op>::Get()->AddAlias(this->name, alias);
+  return *this;
+}
+
 // find operator by name
 const Op* Op::Get(const std::string& name) {
   const Op* op = dmlc::Registry<Op>::Find(name);

From 4ec056b9030db8cbacce7da2e54c3b74bab52718 Mon Sep 17 00:00:00 2001
From: Minjie Wang <minjie.wang@nyu.edu>
Date: Fri, 9 Sep 2016 18:01:17 -0400
Subject: [PATCH 374/949] Fix comments while reading the codes. (#42)

---
 nnvm/include/nnvm/pass.h           |  36 +++++-----
 nnvm/include/nnvm/pass_functions.h |  55 +++++++++------
 nnvm/include/nnvm/symbolic.h       | 105 +++++++++++++++++------------
 nnvm/include/nnvm/tuple.h          |   8 +--
 4 files changed, 118 insertions(+), 86 deletions(-)

diff --git a/nnvm/include/nnvm/pass.h b/nnvm/include/nnvm/pass.h
index 438226f5c93f..b743d2d5eba0 100644
--- a/nnvm/include/nnvm/pass.h
+++ b/nnvm/include/nnvm/pass.h
@@ -14,11 +14,12 @@
 namespace nnvm {
 
 /*!
- * \brief A PassFunction is a basic "Operator on Graph"
- *  It takes a source graph
+ * \brief A PassFunction is an "Operator on Graph".
+ *  It takes a source graph and return a graph that may or may
+ *  not be the same as the input one.
  *
- *  A pass function can either change the graph structure of g,
- *  generating a new Graph, or add new attributes to the graph.
+ *  A pass function can either change the graph structure (thus,
+ *  generating a new Graph), or add new attributes to the graph.
  *
  * \param src The graph to be transformed.
  * \return The generated graph.
@@ -26,10 +27,10 @@ namespace nnvm {
 typedef std::function<Graph (Graph src)> PassFunction;
 
 /*!
- * \brief Apply a series of pass transformations on g.
+ * \brief Apply a series of pass transformations on the input graph.
  * \param src The graph to be transformed.
  * \param pass The name of pass to be applied.
- * \return The transformed graph
+ * \return The transformed graph.
  */
 Graph ApplyPass(Graph src,
                 const std::vector<std::string>& pass);
@@ -52,36 +53,39 @@ struct PassFunctionReg
   /*! \brief generated targets of graph attributes */
   std::vector<std::string> graph_attr_targets;
   /*!
-   * \brief set whether this pass will change graph structure.
-   * \param v the value to set
-   * \return reference to self.
+   * \brief Set whether this pass will change graph structure.
+   * \param v If true, the pass will change graph structure.
+   * \return Reference to self.
    */
   PassFunctionReg& set_change_graph(bool v) {  // NOLINT(*)
     change_graph = v;
     return *this;
   }
   /*!
-   * \brief Declare this pass require operator attribute attr_name to be available.
-   * \param attr_name Name of the attribute.
-   * \return reference to self.
+   * \brief Declare that this pass will generate the given graph attribute name
+   *        once it is applied on the graph.
+   * \param attr_name Name of the graph attribute.
+   * \return Reference to self.
    */
   PassFunctionReg& provide_graph_attr(const std::string& attr_name) {  // NOLINT(*)
     graph_attr_targets.push_back(attr_name);
     return *this;
   }
   /*!
-   * \brief declare this pass require operator attribute attr_name to be available.
+   * \brief Declare this pass requires the given operator attribute to be 
+   *        available before being applied on the graph.
    * \param attr_name Name of the attribute.
-   * \return reference to self.
+   * \return Reference to self.
    */
   PassFunctionReg& depend_op_attr(const std::string& attr_name) {  // NOLINT(*)
     op_attr_dependency.push_back(attr_name);
     return *this;
   }
   /*!
-   * \brief declare this pass require graph attribute attr_name to be available.
+   * \brief Declare this pass requires the given graph attribute to be
+   *        available before being applied on the graph.
    * \param attr_name Name of the attribute.
-   * \return reference to self.
+   * \return Reference to self.
    */
   PassFunctionReg& depend_graph_attr(const std::string& attr_name) {  // NOLINT(*)
     graph_attr_dependency.push_back(attr_name);
diff --git a/nnvm/include/nnvm/pass_functions.h b/nnvm/include/nnvm/pass_functions.h
index 20019726f15a..b94528496e51 100644
--- a/nnvm/include/nnvm/pass_functions.h
+++ b/nnvm/include/nnvm/pass_functions.h
@@ -33,7 +33,7 @@ inline Graph LoadJSON(const std::string& json_str) {
 
 /*!
  * \brief Save a graph to json, redirects to "SaveJSON" pass.
- * \param graph The to be saved.
+ * \param graph The graph to be saved as json format.
  * \return The json string.
  */
 inline std::string SaveJSON(Graph graph) {
@@ -42,11 +42,14 @@ inline std::string SaveJSON(Graph graph) {
 }
 
 /*!
- * \brief Add control flow dependencies between nodes
- *  To correctly order mutation and read to resolve
- *  write after read problem and read after write problems.
- * \param src source graph
- * \return A graph that added control flow dependencies.
+ * \brief Add control flow dependencies between nodes.
+ *
+ *  This function will enforce the correct order between
+ *  write (mutable operators) and read (immutable operators)
+ *  to sovle write-after-read and read-after-write problems.
+ *
+ * \param src The input graph.
+ * \return A graph with proper control flow dependencies added.
  */
 inline Graph OrderMutation(Graph src) {
   return ApplyPass(std::move(src), {"OrderMutation"});
@@ -54,11 +57,12 @@ inline Graph OrderMutation(Graph src) {
 
 /*!
  * \brief Infer shapes in the graph given the information.
- * \param graph source graph
- * \param shape_inputs The shapes of aruguments to the graph.
- * \param shape_attr_key The key to the node attribute that can indicate shape.
+ * \param graph The input graph.
+ * \param shape_inputs The shapes of input symbols to the graph.
+ * \param shape_attr_key The key to the node attribute that can indicate shape. This is
+ *                       the place where manual hint for shapes could be injected.
  * \return A graph with new attribute "shape" containing inferred shape of each NodeEntry.
- *  The index of ShapeVector is given by graph.indexed_graph().entry_id
+ *         The index of ShapeVector is given by graph.indexed_graph().entry_id.
  */
 inline Graph InferShape(Graph graph,
                         ShapeVector shape_inputs,
@@ -74,11 +78,12 @@ inline Graph InferShape(Graph graph,
 
 /*!
  * \brief Infer types in the graph given the information.
- * \param graph source graph
- * \param dtype_inputs The shapes of inputs to the graph.
- * \param dtype_attr_key The key to the node attribute that can indicate shape.
- * \return A graph with new attribute "shape" containing inferred shape of each NodeEntry.
- *  The index of ShapeVector is given by graph.indexed_graph().entry_id
+ * \param graph The input graph.
+ * \param dtype_inputs The types of input symbols to the graph.
+ * \param dtype_attr_key The key to the node attribute that can indicate types. This is
+ *                       the place where manual hint for types could be injected.
+ * \return A graph with new attribute "dtype" containing inferred type of each NodeEntry.
+ *         The index of ShapeVector is given by graph.indexed_graph().entry_id.
  */
 inline Graph InferType(Graph graph,
                        DTypeVector dtype_inputs,
@@ -93,10 +98,16 @@ inline Graph InferType(Graph graph,
 }
 
 /*!
- * \brief Place the devices
- * \param graph source graph
- * \param device_group_attr_key The attribute name for hinting the device group.
- * \param device_assign_map The assignment map of device
+ * \brief Place the devices for each operator in the graph.
+ *
+ *  Current device placement is quite simple. Each operator is assigned to a "group" (stored
+ *  in `device_group_attr_key` attribute). Each group is assigned to a device (stored in
+ *  `device_assign_map` attribute). Operators will be placed to the device assigned to its
+ *  group. Copy operators will be injected if cross device reference happens.
+ *
+ * \param graph The input graph.
+ * \param device_group_attr_key The attribute name for hints of device group.
+ * \param device_assign_map The assignment map of device.
  * \param device_copy_op The name of copy op to be inserted when cross device copy happened.
  * \return A graph with new attribute "device", cotaining device information of each node.
  */
@@ -112,13 +123,13 @@ inline Graph PlaceDevice(Graph graph,
 
 /*!
  * \brief Get the gradient graph whose outputs are gradients of xs wrt to ys.
- * \param graph source graph
+ * \param graph The input graph.
  * \param ys The entries we want to take gradient from.
  * \param xs The input to take gradient with respect to.
  * \param ys_out_grad The symbol for additional gradient to be propagate back to y.
- * \param aggregate_fun aggregation function applied to aggregate the inputs
+ * \param aggregate_fun Aggregation function applied to aggregate the inputs.
  * \param mirror_fun Optional mirror function to do mirror optimization and save memory.
- * \return A new graph, whose outputs corresponds to inputs of xs.
+ * \return A new graph, whose outputs correspond to inputs of xs.
  */
 inline Graph Gradient(
     Graph graph,
diff --git a/nnvm/include/nnvm/symbolic.h b/nnvm/include/nnvm/symbolic.h
index e153945e65a7..3d7f94ce7b0e 100644
--- a/nnvm/include/nnvm/symbolic.h
+++ b/nnvm/include/nnvm/symbolic.h
@@ -19,7 +19,13 @@
 
 namespace nnvm {
 /*!
- * \brief Symbol is used to represent the
+ * \brief Symbol is help class used to represent the operator node in Graph.
+ * 
+ *  Symbol acts as an interface for building graphs from different components
+ *  like Variable, Functor and Group. Symbol is also exported to python front-end
+ *  (while Graph is not) to enable quick test and deployment. Conceptually,
+ *  symbol is the final operation of a graph and thus including all the information
+ *  required (the graph) to evaluate its output value.
  */
 class Symbol {
  public:
@@ -47,42 +53,46 @@ class Symbol {
   std::vector<NodeEntry> outputs;
 
   /*!
-   * \brief copy the symbol
-   * \return a deep copy of the symbolic graph.
+   * \brief Copy the symbol.
+   * \return A deep copy of this symbol.
    */
   Symbol Copy() const;
   /*!
-   * \brief print the symbol info to output stream.
-   * \param os the output stream we like to print to
+   * \brief Print the symbol info to output stream.
+   * \param os The output stream to print to.
    */
   void Print(std::ostream &os) const; // NOLINT(*)
   /*!
-   * \brief get the index th element from the returned tuple.
-   * \param index index of multi output
-   * \return the symbol corresponds to the indexed element.
+   * \brief Get the index-th element from the returned tuple.
+   * \param index Index of multi output.
+   * \return The symbol corresponds to the indexed element.
    */
   Symbol operator[] (size_t index) const;
   /*!
-   * \brief List the input variable nodes
-   * \param option The options to list the arguments.
+   * \brief List the input variable nodes.
+   *
+   *  The order of the returned list is the same as the order of the input list to `operator()`.
    *
-   * The position of the returned list also corresponds to calling position in operator()
-   * \return the arguments list of this symbol, they can be either named or unnamed (empty string).
+   * \param option The options to list the arguments.
+   * \return The arguments list of this symbol, they can be either named or unnamed (empty string).
    * \sa ListInputOption
    */
   std::vector<NodePtr> ListInputs(ListInputOption option) const;
   /*!
    * \brief List the input names.
-   * \param option The options to list the arguments.
    *
-   * The position of the returned list also corresponds to calling position in operator()
-   * \return the arguments list of this symbol, they can be either named or unnamed (empty string).
+   *  The order of the returned list is the same as the order of the input list to `operator()`.
+   *
+   * \param option The options to list the arguments.
+   * \return The arguments list of this symbol, they can be either named or unnamed (empty string).
    * \sa ListInputOption
    */
   std::vector<std::string> ListInputNames(ListInputOption option) const;
   /*!
    * \brief List the names of outputs for this symbol.
-   *  For normal operators, it is usually symbol node name + "_output"
+   *
+   *  For normal operators, it is usually symbol node name + "_output".
+   *
    * \return get the descriptions of outputs for this symbol.
    */
   std::vector<std::string> ListOutputNames() const;
@@ -92,28 +102,30 @@ class Symbol {
    *
    * The rest of the symbols will remain the same name.
    *
-   * \param args positional arguments
-   * \param kwargs keyword arguments for the symbol
-   * \param name name of returned symbol.
+   * \param args Positional arguments.
+   * \param kwargs Keyword arguments for the symbol.
+   * \param name Name of returned symbol.
    */
   void Compose(const array_view<const Symbol*>& args,
                const std::unordered_map<std::string, const Symbol*>& kwargs,
                const std::string& name);
   /*!
    * \brief Apply the symbol as a function, compose with arguments
-   * This is equivalent to Copy then Compose.
-   * \param args positional arguments for the symbol
-   * \param kwargs keyword arguments for the symbol
-   * \param name name of returned symbol.
-   * \return a new Symbol which is the composition of current symbol with its arguments
+   *
+   *  This is equivalent to Copy then Compose.
+   *
+   * \param args Positional arguments for the symbol.
+   * \param kwargs Keyword arguments for the symbol.
+   * \param name Name of returned symbol.
+   * \return A new Symbol which is the composition of current symbol with its arguments.
    */
   Symbol operator () (const array_view<const Symbol*>& args,
                       const std::unordered_map<std::string, const Symbol*>& kwargs,
                       const std::string& name) const;
   /*!
-   * \brief Add control flow depenencies to operators involved in symbols.
-   *  For grouped sybmbol, an error will be raised.
-   *  This mutate current symbolic Node.
+   * \brief Add control flow depenencies to the operators in symbols.
+   *
+   *  For grouped symbol, an error will be raised. This mutates current symbolic Node.
    *
    * \param src The symbols to depend on.
    */
@@ -121,38 +133,43 @@ class Symbol {
   /*
    * \brief Get all the internal nodes of the symbol.
    * \return symbol A new symbol whose output contains all the outputs of the symbols
-   *  Including input variables and intermediate outputs.
+   *                including input variables and intermediate outputs.
    */
   Symbol GetInternals() const;
   /*!
-   * \brief set additional attributes to current node.
+   * \brief Set additional attributes to current node.
+   *
    *  This only works for symbol with outputs from single operators.
-   *  For grouped sybmbol, an error will be raised.
+   *  For grouped symbol, an error will be raised.
    *
-   *  This function mutate the node's symbol and is not recommended.
+   *  This function mutates the node's symbol and is not recommended.
    *
    * \param attrs The attributes to set.
    */
   void SetAttrs(const std::vector<std::pair<std::string, std::string> >& attrs);
   /*!
    * \brief Get attributes from the symbol.
+   *
    *  This only works for symbol with outputs from single operators.
-   *  For grouped sybmbol, an error will be raised.
+   *  For grouped symbol, an error will be raised.
+   *
    * \param key Key of the attribute. When key == "name", it returns the name attirbute.
-   * \param out the output value of the attribute.
-   * \return true if the attribute exists, false if the attribute do not exist.
+   * \param out The output value of the attribute.
+   * \return true If the attribute exists, false if the attribute does not exist.
    */
   bool GetAttr(const std::string& key, std::string* out) const;
   /*!
    * \brief Get attribute dictionary from the symbol.
-   *  For grouped sybmbol, an error will be raised.
-   * \param option If recursive is set, the attributes of all children are retrieved,
-   *   The name of symbol will be pre-pended to each key.
+   *
+   *  For grouped symbol, an error will be raised.
+   *
+   * \param option If recursive flag is set, the attributes of all children are retrieved.
+   *               The name of symbol will be pre-pended to each key.
    * \return The created attribute.
    */
   std::unordered_map<std::string, std::string> ListAttrs(ListAttrOption option) const;
   /*!
-   * \brief create symbolic functor(AtomicSymbol) by given operator and attributes.
+   * \brief Create symbolic functor(AtomicSymbol) by given operator and attributes.
    * \param op The operator.
    * \param attrs The additional attributes.
    * \return Symbol that can be used to call compose further.
@@ -160,15 +177,15 @@ class Symbol {
   static Symbol CreateFunctor(const Op* op,
                               std::unordered_map<std::string, std::string> attrs);
   /*!
-   * \brief create variable symbol node
-   * \param name name of the variable
-   * \return the new variable
+   * \brief Create symbol node representing variable.
+   * \param name Name of the variable.
+   * \return The symbol.
    */
   static Symbol CreateVariable(const std::string& name);
   /*!
-   * \brief create equivalence of symbol by grouping the symbols together
-   * \param symbols list of symbols
-   * \return the grouped symbol
+   * \brief Create equivalence of symbol by grouping the symbols together.
+   * \param symbols A list of symbols to be grouped.
+   * \return The grouped symbol.
    */
   static Symbol CreateGroup(const std::vector<Symbol>& symbols);
 };
diff --git a/nnvm/include/nnvm/tuple.h b/nnvm/include/nnvm/tuple.h
index 326a5d9c6da6..ed5f30e516b0 100644
--- a/nnvm/include/nnvm/tuple.h
+++ b/nnvm/include/nnvm/tuple.h
@@ -19,11 +19,11 @@ namespace nnvm {
 typedef uint32_t index_t;
 
 /*!
- * \brief A dynamic sized array data strcuture
- *  that is optimized for storing small number of elements with same type.
- *  Data will be stored in stack when number of elements is small.
+ * \brief A dynamic sized array data strcuture that is optimized for storing
+ *        small number of elements with same type.
  *
- *  It is suitable to hold Shape of Tensor.
+ *  Data will be stored in stack when number of elements is small.
+ *  It is suitable to hold shape of Tensor.
  *
  * \tparam ValueType The type of data stored inside tuple.
  * \sa TShape

From 8b2d68f688c9ca956e27760c3f99303b0111ed6f Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 11 Sep 2016 10:12:10 -0700
Subject: [PATCH 375/949] Strict gradient boundary check (#44)

---
 nnvm/src/pass/gradient.cc | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/nnvm/src/pass/gradient.cc b/nnvm/src/pass/gradient.cc
index 2a6bd00e0e8e..0f3f57fd7cf4 100644
--- a/nnvm/src/pass/gradient.cc
+++ b/nnvm/src/pass/gradient.cc
@@ -115,6 +115,8 @@ Graph Gradient(Graph src) {
     }
     std::vector<NodeEntry> input_grads = grad_fun_map[ptr->op()]
         (mirror_map.size() == 0 ? ptr : mirror_map.at(ptr.get()), out_agg_grads);
+    CHECK_EQ((*rit)->inputs.size(), input_grads.size())
+        << "Gradient function not returning enough gradient";
     auto git = input_grads.begin();
     for (auto it = (*rit)->inputs.begin(); it != (*rit)->inputs.end(); ++it, ++git) {
       output_grads[it->node.get()][it->index].grads.emplace_back(std::move(*git));

From 2e938a1768282221fdd443c856d68c6a66beeec1 Mon Sep 17 00:00:00 2001
From: Minjie Wang <minjie.wang@nyu.edu>
Date: Sun, 11 Sep 2016 14:29:39 -0400
Subject: [PATCH 376/949] ApplyPass -> ApplyPasses; Refactored infer pass;
 (#43)

* ApplyPass -> ApplyPasses; Refactored infer pass;

* lint fix
---
 nnvm/include/nnvm/c_api.h            | 10 +++----
 nnvm/include/nnvm/graph.h            |  6 ++--
 nnvm/include/nnvm/graph_attr_types.h | 10 +++----
 nnvm/include/nnvm/pass.h             | 15 ++++++++--
 nnvm/include/nnvm/pass_functions.h   | 14 ++++-----
 nnvm/python/nnvm/graph.py            |  2 +-
 nnvm/src/c_api/c_api_graph.cc        | 10 +++----
 nnvm/src/core/pass.cc                |  4 +--
 nnvm/src/pass/infer_shape_type.cc    | 45 ++++++++++++++++------------
 9 files changed, 67 insertions(+), 49 deletions(-)

diff --git a/nnvm/include/nnvm/c_api.h b/nnvm/include/nnvm/c_api.h
index 3122e26b7038..7f12561e9cdd 100644
--- a/nnvm/include/nnvm/c_api.h
+++ b/nnvm/include/nnvm/c_api.h
@@ -329,16 +329,16 @@ NNVM_DLL int NNGraphSetNodeEntryListAttr_(GraphHandle handle,
                                           const char* key,
                                           SymbolHandle list);
 /*!
- * \brief Apply pass on the src graph.
+ * \brief Apply passes on the src graph.
  * \param src The source graph handle.
  * \param num_pass The number of pass to be applied.
  * \param pass_names The names of the pass.
  * \param dst The result graph.
  * \return 0 when success, -1 when failure happens
  */
-NNVM_DLL int NNGraphApplyPass(GraphHandle src,
-                              nn_uint num_pass,
-                              const char** pass_names,
-                              GraphHandle *dst);
+NNVM_DLL int NNGraphApplyPasses(GraphHandle src,
+                                nn_uint num_pass,
+                                const char** pass_names,
+                                GraphHandle *dst);
 
 #endif  // NNVM_C_API_H_
diff --git a/nnvm/include/nnvm/graph.h b/nnvm/include/nnvm/graph.h
index 9c08467218e9..19abfa406c92 100644
--- a/nnvm/include/nnvm/graph.h
+++ b/nnvm/include/nnvm/graph.h
@@ -179,11 +179,11 @@ class IndexedGraph {
    * \param other The source graph.
    */
   explicit IndexedGraph(const Graph& other);
-  // node pointers in CSR structure.
+  // Node pointers in CSR structure.
   std::vector<Node> nodes_;
-  // index all to input nodes
+  // Index to all input nodes.
   std::vector<uint32_t> input_nodes_;
-  // index to mutable input nodes
+  // Index to all mutable input nodes.
   std::unordered_set<uint32_t> mutable_input_nodes_;
   // space to store the outputs entries
   std::vector<NodeEntry> outputs_;
diff --git a/nnvm/include/nnvm/graph_attr_types.h b/nnvm/include/nnvm/graph_attr_types.h
index 6ac12e72d0fa..64894ec58de7 100644
--- a/nnvm/include/nnvm/graph_attr_types.h
+++ b/nnvm/include/nnvm/graph_attr_types.h
@@ -18,7 +18,7 @@ namespace nnvm {
  * \note Stored under ret.attrs["json"], provided by Pass "SaveJSON"
 
  * \code
- *  Graph ret = ApplyPass(src_graph, {"SaveJSON"});
+ *  Graph ret = ApplyPass(src_graph, "SaveJSON");
  *  const JSONString& json = ret.GetAttr<JSONString>("shape");
  * \endcode
  */
@@ -29,7 +29,7 @@ using JSONString = std::string;
  * \note Stored under graph.attrs["shape"], provided by Pass "InferShape"
  *
  * \code
- *  Graph g = ApplyPass(src_graph, {"InferShape"});
+ *  Graph g = ApplyPass(src_graph, "InferShape");
  *  const ShapeVector& shapes = g.GetAttr<ShapeVector>("shape");
  *  // get shape by entry id
  *  TShape entry_shape = shapes[g.indexed_graph().entry_id(my_entry)];
@@ -44,7 +44,7 @@ using ShapeVector = std::vector<TShape>;
  * \note Stored under graph.attrs["dtype"], provided by Pass "InferType"
  *
  * \code
- *  Graph g = ApplyPass(src_graph, {"InferType"});
+ *  Graph g = ApplyPass(src_graph, "InferType");
  *  const DTypeVector& types = g.GetAttr<DTypeVector>("dtype");
  *  // get shape by entry id
  *  int entry_type = dtypes[g.indexed_graph().entry_id(my_entry)];
@@ -59,7 +59,7 @@ using DTypeVector = std::vector<int>;
  * \note Stored under graph.attrs["device"], provided by Pass "PlaceDevice"
  *
  * \code
- *  Graph g = ApplyPass(src_graph, {"PlaceDevice"});
+ *  Graph g = ApplyPass(src_graph, "PlaceDevice");
  *  const &device = g.GetAttr<DeviceVector>("device");
  *  // get device by node_id
  *  int device_type = device[g.indexed_graph().node_id(my_node)];
@@ -83,7 +83,7 @@ using DeviceAssignMap = std::unordered_map<std::string, int>;
  *  If the storage id is -1 then the storage is not assigned.
  *
  * \code
- *  Graph g = ApplyPass(src_graph, {"PlanMemory"});
+ *  Graph g = ApplyPass(src_graph, "PlanMemory");
  *  const &storage = g.GetAttr<StorageVector>("storage");
  *  // get storage id by entry
  *  int storage_id = storage[g.indexed_graph().entry_id(my_entry)];
diff --git a/nnvm/include/nnvm/pass.h b/nnvm/include/nnvm/pass.h
index b743d2d5eba0..8b731bbd0f9f 100644
--- a/nnvm/include/nnvm/pass.h
+++ b/nnvm/include/nnvm/pass.h
@@ -29,11 +29,22 @@ typedef std::function<Graph (Graph src)> PassFunction;
 /*!
  * \brief Apply a series of pass transformations on the input graph.
  * \param src The graph to be transformed.
+ * \param passes A list of pass names to be applied.
+ * \return The transformed graph
+ */
+Graph ApplyPasses(Graph src,
+                  const std::vector<std::string>& passes);
+
+/*!
+ * \brief Apply one pass to the graph.
+ * \param src The graph to be transformed.
  * \param pass The name of pass to be applied.
  * \return The transformed graph.
  */
-Graph ApplyPass(Graph src,
-                const std::vector<std::string>& pass);
+inline Graph ApplyPass(Graph src, const std::string& pass) {
+  return ApplyPasses(src, {pass});
+}
+
 
 /*!
  * \brief Registry entry for DataIterator factory functions.
diff --git a/nnvm/include/nnvm/pass_functions.h b/nnvm/include/nnvm/pass_functions.h
index b94528496e51..68125ebf24bd 100644
--- a/nnvm/include/nnvm/pass_functions.h
+++ b/nnvm/include/nnvm/pass_functions.h
@@ -28,7 +28,7 @@ namespace pass {
 inline Graph LoadJSON(const std::string& json_str) {
   Graph ret;
   ret.attrs["json"] = std::make_shared<any>(json_str);
-  return ApplyPass(ret, {"LoadJSON"});
+  return ApplyPass(ret, "LoadJSON");
 }
 
 /*!
@@ -37,7 +37,7 @@ inline Graph LoadJSON(const std::string& json_str) {
  * \return The json string.
  */
 inline std::string SaveJSON(Graph graph) {
-  Graph ret = ApplyPass(std::move(graph), {"SaveJSON"});
+  Graph ret = ApplyPass(std::move(graph), "SaveJSON");
   return ret.GetAttr<std::string>("json");
 }
 
@@ -52,7 +52,7 @@ inline std::string SaveJSON(Graph graph) {
  * \return A graph with proper control flow dependencies added.
  */
 inline Graph OrderMutation(Graph src) {
-  return ApplyPass(std::move(src), {"OrderMutation"});
+  return ApplyPass(std::move(src), "OrderMutation");
 }
 
 /*!
@@ -73,7 +73,7 @@ inline Graph InferShape(Graph graph,
   if (shape_attr_key.length() != 0) {
     graph.attrs["shape_attr_key"] = std::make_shared<any>(std::move(shape_attr_key));
   }
-  return ApplyPass(std::move(graph), {"InferShape"});
+  return ApplyPass(std::move(graph), "InferShape");
 }
 
 /*!
@@ -94,7 +94,7 @@ inline Graph InferType(Graph graph,
   if (dtype_attr_key.length() != 0) {
     graph.attrs["dtype_attr_key"] = std::make_shared<any>(std::move(dtype_attr_key));
   }
-  return ApplyPass(std::move(graph), {"InferType"});
+  return ApplyPass(std::move(graph), "InferType");
 }
 
 /*!
@@ -118,7 +118,7 @@ inline Graph PlaceDevice(Graph graph,
   graph.attrs["device_group_attr_key"] = std::make_shared<any>(std::move(device_group_attr_key));
   graph.attrs["device_assign_map"] = std::make_shared<any>(std::move(device_assign_map));
   graph.attrs["device_copy_op"] = std::make_shared<any>(std::move(device_copy_op));
-  return ApplyPass(std::move(graph), {"PlaceDevice"});
+  return ApplyPass(std::move(graph), "PlaceDevice");
 }
 
 /*!
@@ -149,7 +149,7 @@ inline Graph Gradient(
     graph.attrs["grad_mirror_fun"] = std::make_shared<any>(mirror_fun);
   }
 
-  return ApplyPass(std::move(graph), {"Gradient"});
+  return ApplyPass(std::move(graph), "Gradient");
 }
 
 }  // namespace pass
diff --git a/nnvm/python/nnvm/graph.py b/nnvm/python/nnvm/graph.py
index e3e857eecbf3..0f2997d4bf3f 100644
--- a/nnvm/python/nnvm/graph.py
+++ b/nnvm/python/nnvm/graph.py
@@ -113,7 +113,7 @@ def apply(self, passes):
         cpass = c_array(ctypes.c_char_p, [c_str(key) for key in passes])
         ghandle = GraphHandle()
         npass = nn_uint(len(passes))
-        check_call(_LIB.NNGraphApplyPass(self.handle, npass, cpass, ctypes.byref(ghandle)))
+        check_call(_LIB.NNGraphApplyPasses(self.handle, npass, cpass, ctypes.byref(ghandle)))
         return Graph(ghandle)
 
 
diff --git a/nnvm/src/c_api/c_api_graph.cc b/nnvm/src/c_api/c_api_graph.cc
index d3dd1d3e49aa..831aaec33e8c 100644
--- a/nnvm/src/c_api/c_api_graph.cc
+++ b/nnvm/src/c_api/c_api_graph.cc
@@ -82,17 +82,17 @@ int NNGraphGetJSONAttr(GraphHandle handle,
   API_END();
 }
 
-int NNGraphApplyPass(GraphHandle src,
-                     nn_uint num_pass,
-                     const char** pass_names,
-                     GraphHandle *dst) {
+int NNGraphApplyPasses(GraphHandle src,
+                       nn_uint num_pass,
+                       const char** pass_names,
+                       GraphHandle *dst) {
   Graph* g = new Graph();
   API_BEGIN();
   std::vector<std::string> vpass;
   for (nn_uint i = 0; i < num_pass; ++i) {
     vpass.emplace_back(std::string(pass_names[i]));
   }
-  *g = ApplyPass(*static_cast<Graph*>(src), vpass);
+  *g = ApplyPasses(*static_cast<Graph*>(src), vpass);
   *dst = g;
   API_END_HANDLE_ERROR(delete g);
 }
diff --git a/nnvm/src/core/pass.cc b/nnvm/src/core/pass.cc
index 5c4aeb2e0232..d72d4af00e65 100644
--- a/nnvm/src/core/pass.cc
+++ b/nnvm/src/core/pass.cc
@@ -22,8 +22,8 @@ const PassFunctionReg* FindPassDep(const std::string&attr_name) {
   return nullptr;
 }
 
-Graph ApplyPass(Graph g,
-                const std::vector<std::string>& pass) {
+Graph ApplyPasses(Graph g,
+                  const std::vector<std::string>& pass) {
   std::vector<const PassFunctionReg*> fpass;
   for (auto& name : pass) {
     auto* reg = dmlc::Registry<PassFunctionReg>::Find(name);
diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index bb50e98b5ede..3f9eca0a8c4b 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -13,7 +13,7 @@ namespace {
 
 template<typename AttrType, typename IsNone>
 Graph InferAttr(Graph &&ret,
-                const AttrType def_value,
+                const AttrType default_val,
                 const char* infer_name,
                 const char* input_name,
                 const char* attr_key_name,
@@ -23,16 +23,16 @@ Graph InferAttr(Graph &&ret,
   using AttrVector = std::vector<AttrType>;
   const IndexedGraph& idx = ret.indexed_graph();
   static auto& finfer_shape =
-      Op::GetAttr<FInferNodeEntryAttr<AttrType> >(infer_name);
+      Op::GetAttr<FInferNodeEntryAttr<AttrType>>(infer_name);
   static auto& backward_map =
       Op::GetAttr<FBackwardOutToInIndex>("FBackwardOutToInIndex");
   // reshape shape vector
-  AttrVector rshape(idx.num_node_entries(), def_value);
+  AttrVector rshape(idx.num_node_entries(), default_val);
 
   if (ret.attrs.count(input_name) != 0) {
     const AttrVector& shape_args = ret.GetAttr<AttrVector>(input_name);
     CHECK_LE(shape_args.size(), idx.input_nodes().size())
-        << "shape args is more than number of arguments";
+        << "More provided shapes than number of arguments.";
     for (size_t i = 0; i < shape_args.size(); ++i) {
       rshape[idx.entry_id(idx.input_nodes()[i], 0)] = shape_args[i];
     }
@@ -46,36 +46,41 @@ Graph InferAttr(Graph &&ret,
     ret.attrs.erase(attr_key_name);
   }
 
-  // temp space for shape inference.
+  // Temp space for shape inference.
   std::vector<AttrType> ishape, oshape;
   // number of completed nodes
   size_t num_unknown = 0;
   for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
     const auto& inode = idx[nid];
-    uint32_t num_inputs = inode.inputs.size();
-    uint32_t num_outputs = inode.source->num_outputs();
+    const uint32_t num_inputs = inode.inputs.size();
+    const uint32_t num_outputs = inode.source->num_outputs();
     if (inode.source->is_variable()) {
-      if (shape_attr_key.length() != 0 && fis_none(rshape[idx.entry_id(nid, 0)])) {
+      // Variable node. No operator. Only one output entry.
+      CHECK(inode.source->op() == nullptr);
+      CHECK_EQ(num_outputs, 1);
+      const uint32_t out_ent_id = idx.entry_id(nid, 0);
+      if (shape_attr_key.length() != 0 && fis_none(rshape[out_ent_id])) {
         auto it = inode.source->attrs.dict.find(shape_attr_key);
         if (it != inode.source->attrs.dict.end()) {
-          CHECK_EQ(num_outputs, 1);
           std::istringstream is(it->second);
-          CHECK(is >> rshape[idx.entry_id(nid, 0)]) << "Invalid attribute";
+          CHECK(is >> rshape[out_ent_id]) << "Invalid attribute";
         }
       }
-      continue;
-    }
-    if (finfer_shape.count(inode.source->op())) {
-      ishape.resize(num_inputs, def_value);
+    } else if (finfer_shape.count(inode.source->op())) {
+      // Forward operator inference.
+      ishape.resize(num_inputs, default_val);
       for (uint32_t i = 0; i < ishape.size(); ++i) {
         ishape[i] = rshape[idx.entry_id(inode.inputs[i])];
       }
-      oshape.resize(num_outputs, def_value);
+      oshape.resize(num_outputs, default_val);
       for (uint32_t i = 0; i < oshape.size(); ++i) {
         oshape[i] = rshape[idx.entry_id(nid, i)];
       }
-      num_unknown +=
-          !(finfer_shape[inode.source->op()](inode.source->attrs, &ishape, &oshape));
+      // Call inference function of the operator.
+      bool forward_known = finfer_shape[inode.source->op()](
+          inode.source->attrs, &ishape, &oshape);
+      num_unknown += !forward_known;
+      // Save to the result map.
       for (uint32_t i = 0; i < num_inputs; ++i) {
         rshape[idx.entry_id(inode.inputs[i])] = ishape[i];
       }
@@ -83,10 +88,12 @@ Graph InferAttr(Graph &&ret,
         rshape[idx.entry_id(nid, i)] = oshape[i];
       }
     } else if (backward_map.count(inode.source->op())) {
-      // backward operator inference.
+      // Backward operator inference.
       CHECK_GE(inode.control_deps.size(), 1)
           << "BackwardOp need to have control_deps to its forward op";
-      const auto& fnode = idx[inode.control_deps[0]];
+      const IndexedGraph::Node& fnode = idx[inode.control_deps[0]];
+      // Inference the outputs of backward operator (equal to the inputs
+      // of its corresponding forward operator).
       std::vector<uint32_t> out_map =
           backward_map[inode.source->op()](inode.source->attrs);
       bool known = true;

From 3b82e71b91d6856cad362c40d7bb06cb18dfa4f8 Mon Sep 17 00:00:00 2001
From: Eric Junyuan Xie <piiswrong@users.noreply.github.com>
Date: Mon, 12 Sep 2016 15:46:42 -0700
Subject: [PATCH 377/949] Fix inplace. Only permit shared when shape and dtype
 is the same (#45)

---
 nnvm/src/pass/plan_memory.cc | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/nnvm/src/pass/plan_memory.cc b/nnvm/src/pass/plan_memory.cc
index 0f0b8e79b9c2..f77e70085fbc 100644
--- a/nnvm/src/pass/plan_memory.cc
+++ b/nnvm/src/pass/plan_memory.cc
@@ -173,7 +173,10 @@ Graph PlanMemory(Graph ret) {
       for (auto& kv : inplace_pairs) {
         uint32_t eid_out = idx.entry_id(nid, kv.second);
         uint32_t eid_in = idx.entry_id(inode.inputs[kv.first]);
-        if (ref_count[eid_in] == 1 && storage[eid_in] != GraphAllocator::kBadStorageID) {
+        if (ref_count[eid_in] == 1 &&
+            storage[eid_in] != GraphAllocator::kBadStorageID &&
+            shape_vec[eid_out].Size() == shape_vec[eid_in].Size() &&
+            dtype_vec[eid_out] == dtype_vec[eid_in]) {
           // inplace optimization
           storage[eid_out] = storage[eid_in];
           ref_count[eid_in] = 0;

From 689de5bf4d6ff5cf0c51550693c87767abadf453 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 14 Sep 2016 11:32:45 -0700
Subject: [PATCH 378/949] [API] Change attr to explicit name set_attr (#46)

---
 nnvm/example/src/operator.cc | 36 ++++++++++++++++++------------------
 nnvm/include/nnvm/op.h       | 12 ++++++------
 2 files changed, 24 insertions(+), 24 deletions(-)

diff --git a/nnvm/example/src/operator.cc b/nnvm/example/src/operator.cc
index 7ec898961c9b..1533b6a4b250 100644
--- a/nnvm/example/src/operator.cc
+++ b/nnvm/example/src/operator.cc
@@ -66,7 +66,7 @@ NNVM_REGISTER_OP(reshape)
       CHECK(is >> target);
       attrs->parsed = std::move(target);
     })
-.attr<FInferShape>(
+.set_attr<FInferShape>(
     "FInferShape", [] (const NodeAttrs& attrs,
                        std::vector<TShape> *ishape,
                        std::vector<TShape> *oshape) {
@@ -78,7 +78,7 @@ NNVM_REGISTER_OP(reshape)
           << "Reshape op: source target shape mismatch";
       return true;
     })
-.attr<FInplaceOption>("FInplaceOption", InplaceIn0Out0);
+.set_attr<FInplaceOption>("FInplaceOption", InplaceIn0Out0);
 
 
 NNVM_REGISTER_OP(cast)
@@ -92,8 +92,8 @@ NNVM_REGISTER_OP(cast)
       CHECK(is >> dtype);
       attrs->parsed = std::move(dtype);
     })
-.attr<FInferShape>("FInferShape", SameShape)
-.attr<FInferType>(
+.set_attr<FInferShape>("FInferShape", SameShape)
+.set_attr<FInferType>(
     "FInferType", [](const NodeAttrs& attrs,
                      std::vector<int> *itype,
                      std::vector<int> *otype) {
@@ -104,8 +104,8 @@ NNVM_REGISTER_OP(cast)
 NNVM_REGISTER_OP(exp)
 .describe("take exponential")
 .set_num_inputs(1)
-.attr<FInferShape>("FInferShape", SameShape)
-.attr<FGradient>(
+.set_attr<FInferShape>("FInferShape", SameShape)
+.set_attr<FGradient>(
     "FGradient", [](const NodePtr& n,
                     const std::vector<NodeEntry>& ograds) {
       return std::vector<NodeEntry>{
@@ -117,8 +117,8 @@ NNVM_REGISTER_OP(exp)
 NNVM_REGISTER_OP(identity)
 .describe("identity function")
 .set_num_inputs(1)
-.attr<FInferShape>("FInferShape", SameShape)
-.attr<FGradient>(
+.set_attr<FInferShape>("FInferShape", SameShape)
+.set_attr<FGradient>(
     "FGradient", [](const NodePtr& n,
                     const std::vector<NodeEntry>& ograds) {
       return std::vector<NodeEntry>{ograds[0]};
@@ -128,9 +128,9 @@ NNVM_REGISTER_OP(add)
 .describe("add two data together")
 .set_num_inputs(2)
 .add_alias("__add_symbol__")
-.attr<FInferShape>("FInferShape", SameShape)
-.attr<FInplaceOption>("FInplaceOption", InplaceIn0Out0)
-.attr<FGradient>(
+.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>{ograds[0], ograds[0]};
@@ -139,9 +139,9 @@ NNVM_REGISTER_OP(add)
 NNVM_REGISTER_OP(mul)
 .describe("multiply two data together")
 .set_num_inputs(2)
-.attr<FInferShape>("FInferShape", SameShape)
-.attr<FInplaceOption>("FInplaceOption", InplaceIn0Out0)
-.attr<FGradient>(
+.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>{
@@ -167,23 +167,23 @@ NNVM_REGISTER_OP(__one__)
 NNVM_REGISTER_OP(cross_device_copy)
 .describe("Copy data across device.")
 .set_num_inputs(1)
-.attr<FInferShape>("FInferShape", SameShape);
+.set_attr<FInferShape>("FInferShape", SameShape);
 
 
 NNVM_REGISTER_OP(conv2d)
 .describe("take conv of input")
 .set_num_inputs(2)
-.attr<FListInputNames>("FListInputNames", [](const NodeAttrs& attrs) {
+.set_attr<FListInputNames>("FListInputNames", [](const NodeAttrs& attrs) {
     return std::vector<std::string>{"data", "weight"};
   });
 
 NNVM_REGISTER_OP(add)
-.attr<std::string>("nick_name", "plus");
+.set_attr<std::string>("nick_name", "plus");
 
 NNVM_REGISTER_OP(assign)
 .set_num_inputs(2)
 .set_num_outputs(1)
-.attr<FMutateInputs>("FMutateInputs", [](const NodeAttrs& attrs) {
+.set_attr<FMutateInputs>("FMutateInputs", [](const NodeAttrs& attrs) {
     return std::vector<uint32_t>{0};
   });
 
diff --git a/nnvm/include/nnvm/op.h b/nnvm/include/nnvm/op.h
index e754d5988013..9f6736ef4e25 100644
--- a/nnvm/include/nnvm/op.h
+++ b/nnvm/include/nnvm/op.h
@@ -44,7 +44,7 @@ static const uint32_t kVarg = std::numeric_limits<uint32_t>::max();
  *  NNVM_REGISTER_OP(add)
  *  .describe("add two inputs together")
  *  .set_num_inputs(2)
- *  .attr<OpKernel>("gpu_kernel", AddKernel);
+ *  .set_attr<OpKernel>("gpu_kernel", AddKernel);
  *
  *  NNVM_REGISTER_OP(sub)
  *  .describe("substract one tensor from another")
@@ -53,7 +53,7 @@ static const uint32_t kVarg = std::numeric_limits<uint32_t>::max();
  *  // Can call regster multiple times in different files
  *  // to register different part of information
  *  NNVM_REGISTER_OP(sub)
- *  .attr<OpKernel>("gpu_kernel", SubKernel);
+ *  .set_attr<OpKernel>("gpu_kernel", SubKernel);
  *
  *  // get operators from registry.
  *  void my_function() {
@@ -213,8 +213,8 @@ class Op {
    * \tparam ValueType The type of the value to be set.
    */
   template<typename ValueType>
-  inline Op& attr(const std::string& attr_name,  // NOLINT(*)
-                  const ValueType& value);
+  inline Op& set_attr(const std::string& attr_name,  // NOLINT(*)
+                      const ValueType& value);
   /*!
    * \brief Get an Op for a given operator name.
    *  Will raise an error if the op has not been registered.
@@ -300,7 +300,7 @@ class OpMap {
  *  NNVM_REGISTER_OP(add)
  *  .describe("add two inputs together")
  *  .set_num_inputs(2)
- *  .attr<OpKernel>("gpu_kernel", AddKernel);
+ *  .set_attr<OpKernel>("gpu_kernel", AddKernel);
  *
  * \endcode
  */
@@ -329,7 +329,7 @@ inline const OpMap<ValueType>& Op::GetAttr(const std::string& key) {
 }
 
 template<typename ValueType>
-inline Op& Op::attr(  // NOLINT(*)
+inline Op& Op::set_attr(  // NOLINT(*)
     const std::string& attr_name, const ValueType& value) {
   // update the attribute map of the key by creating new empty if needed.
   UpdateAttrMap(attr_name, [this, attr_name, value](any* pmap) {

From 2c14498945fed1f5b9b0513ffee6b0738a98b682 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 14 Sep 2016 12:07:04 -0700
Subject: [PATCH 379/949] update doc (#47)

---
 nnvm/docs/overview.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/nnvm/docs/overview.md b/nnvm/docs/overview.md
index 064519057661..e4c9fe4b1641 100644
--- a/nnvm/docs/overview.md
+++ b/nnvm/docs/overview.md
@@ -110,13 +110,13 @@ NNVM_REGISTER_OP(add)
 
 // register to tell first input can be calculate inplace with first output
 NNVM_REGISTER_OP(add)
-.attr<FInplaceOption>("FInplaceOption", [](const NodeAttrs& attrs) {
+.set_attr<FInplaceOption>("FInplaceOption", [](const NodeAttrs& attrs) {
   return std::vector<std::pair<int, int> >{{0, 0}};
  });
 
 NNVM_REGISTER_OP(exp)
 .set_num_inputs(1)
-.attr<FInplaceOption>("FInplaceOption", [](const NodeAttrs& attrs) {
+.set_attr<FInplaceOption>("FInplaceOption", [](const NodeAttrs& attrs) {
   return std::vector<std::pair<int, int> >{{0, 0}};
  });
 ```

From 35f54ecca65563923d37d03a17cc97c20ae9781c Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 14 Sep 2016 17:29:42 -0700
Subject: [PATCH 380/949] update test (#48)

---
 nnvm/tests/cpp/op_test.cc | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/nnvm/tests/cpp/op_test.cc b/nnvm/tests/cpp/op_test.cc
index 164cc469ad34..c97952dda1b9 100644
--- a/nnvm/tests/cpp/op_test.cc
+++ b/nnvm/tests/cpp/op_test.cc
@@ -6,10 +6,10 @@
 NNVM_REGISTER_OP(add)
 .describe("add two data together")
 .set_num_inputs(2)
-.attr("inplace_pair", std::make_pair(0, 0));
+.set_attr("inplace_pair", std::make_pair(0, 0));
 
 NNVM_REGISTER_OP(add)
-.attr<std::string>("nick_name", "plus");
+.set_attr<std::string>("nick_name", "plus");
 
 
 TEST(Op, GetAttr) {

From 90954151a08a1d07e923409cc86a5b62ec3a552f Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 21 Sep 2016 10:06:34 -0700
Subject: [PATCH 381/949] [Compatiblity] backward compatible to mxnet json
 (#49)

---
 nnvm/src/pass/saveload_json.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/src/pass/saveload_json.cc b/nnvm/src/pass/saveload_json.cc
index 3b3f8561a33f..2200a4b6b21f 100644
--- a/nnvm/src/pass/saveload_json.cc
+++ b/nnvm/src/pass/saveload_json.cc
@@ -103,8 +103,8 @@ struct JSONNode {
     std::unordered_map<std::string, std::string> param;
     helper.DeclareOptionalField("param", &param);
     helper.DeclareOptionalField("backward_source_id", &backward_source_id);
-    node->attrs.dict.insert(param.begin(), param.end());
     helper.ReadAllFields(reader);
+    node->attrs.dict.insert(param.begin(), param.end());
 
     if (op_type_str != "null") {
       try {

From c7c5e267d7783d6bcb826e1899721f9d32a25db9 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 21 Sep 2016 16:50:20 -0700
Subject: [PATCH 382/949] Allow libaray path to be configurable (#50)

* Allow libaray path to be configurable

* Enable partial shape inference result to be passed via shape

* fix python3

* disallow copy assign in index
---
 nnvm/include/nnvm/graph.h         |  2 ++
 nnvm/python/nnvm/libinfo.py       | 27 +++++++++++++++++++++------
 nnvm/python/nnvm/symbol.py        |  2 +-
 nnvm/src/pass/infer_shape_type.cc | 10 ++++++++--
 nnvm/tests/python/test_graph.py   | 17 +++++++++++++++++
 5 files changed, 49 insertions(+), 9 deletions(-)

diff --git a/nnvm/include/nnvm/graph.h b/nnvm/include/nnvm/graph.h
index 19abfa406c92..60028113cc1e 100644
--- a/nnvm/include/nnvm/graph.h
+++ b/nnvm/include/nnvm/graph.h
@@ -171,6 +171,8 @@ class IndexedGraph {
   inline const std::vector<NodeEntry>& outputs() const {
     return outputs_;
   }
+  // disalllow copy assign
+  IndexedGraph(const IndexedGraph&) = delete;
 
  private:
   friend class Graph;
diff --git a/nnvm/python/nnvm/libinfo.py b/nnvm/python/nnvm/libinfo.py
index e15d4d8ae1bf..241960f129f1 100644
--- a/nnvm/python/nnvm/libinfo.py
+++ b/nnvm/python/nnvm/libinfo.py
@@ -1,9 +1,15 @@
 # coding: utf-8
 """Information about nnvm."""
 from __future__ import absolute_import
+import sys
 import os
 import platform
 
+if sys.version_info[0] == 3:
+    import builtins as __builtin__
+else:
+    import __builtin__
+
 def find_lib_path():
     """Find NNNet dynamic library files.
 
@@ -12,10 +18,19 @@ def find_lib_path():
     lib_path : list(string)
         List of all found path to the libraries
     """
-    curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
-    api_path = os.path.join(curr_path, '../../lib/')
-    cmake_build_path = os.path.join(curr_path, '../../build/Release/')
-    dll_path = [curr_path, api_path, cmake_build_path]
+    if hasattr(__builtin__, "NNVM_BASE_PATH"):
+        base_path = __builtin__.NNVM_BASE_PATH
+    else:
+        base_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
+
+    if hasattr(__builtin__, "NNVM_LIBRARY_NAME"):
+        lib_name = __builtin__.NNVM_LIBRARY_NAME
+    else:
+        lib_name = "libnnvm_example"
+
+    api_path = os.path.join(base_path, '../../lib/')
+    cmake_build_path = os.path.join(base_path, '../../build/Release/')
+    dll_path = [base_path, api_path, cmake_build_path]
     if os.name == 'nt':
         vs_configuration = 'Release'
         if platform.architecture()[0] == '64bit':
@@ -27,9 +42,9 @@ def find_lib_path():
     elif os.name == "posix" and os.environ.get('LD_LIBRARY_PATH', None):
         dll_path.extend([p.strip() for p in os.environ['LD_LIBRARY_PATH'].split(":")])
     if os.name == 'nt':
-        dll_path = [os.path.join(p, 'libnnvm_example.dll') for p in dll_path]
+        dll_path = [os.path.join(p, '%s.dll' % lib_name) for p in dll_path]
     else:
-        dll_path = [os.path.join(p, 'libnnvm_example.so') for p in dll_path]
+        dll_path = [os.path.join(p, '%s.so' % lib_name) for p in dll_path]
     lib_path = [p for p in dll_path if os.path.exists(p) and os.path.isfile(p)]
     if len(lib_path) == 0:
         raise RuntimeError('Cannot find the files.\n' +
diff --git a/nnvm/python/nnvm/symbol.py b/nnvm/python/nnvm/symbol.py
index df5bad28266a..3b11ad9711fd 100644
--- a/nnvm/python/nnvm/symbol.py
+++ b/nnvm/python/nnvm/symbol.py
@@ -57,7 +57,7 @@ def __rsub__(self, other):
     def __mul__(self, other):
         if isinstance(other, Symbol):
             return _internal.__mul_symbol__(self, other)
-        if isinstance(other, Number):
+        if isinstance(other, _Number):
             return _internal.__mul_scalar__(self, scalar=other)
         else:
             raise TypeError('type %s not supported' % str(type(other)))
diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index 3f9eca0a8c4b..a18528b5b465 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -23,11 +23,16 @@ Graph InferAttr(Graph &&ret,
   using AttrVector = std::vector<AttrType>;
   const IndexedGraph& idx = ret.indexed_graph();
   static auto& finfer_shape =
-      Op::GetAttr<FInferNodeEntryAttr<AttrType>>(infer_name);
+      Op::GetAttr<FInferNodeEntryAttr<AttrType> >(infer_name);
   static auto& backward_map =
       Op::GetAttr<FBackwardOutToInIndex>("FBackwardOutToInIndex");
   // reshape shape vector
-  AttrVector rshape(idx.num_node_entries(), default_val);
+  AttrVector rshape;
+  if (ret.attrs.count(attr_name) != 0) {
+    rshape = ret.MoveCopyAttr<AttrVector>(attr_name);
+  } else {
+    rshape.resize(idx.num_node_entries(), default_val);
+  }
 
   if (ret.attrs.count(input_name) != 0) {
     const AttrVector& shape_args = ret.GetAttr<AttrVector>(input_name);
@@ -39,6 +44,7 @@ Graph InferAttr(Graph &&ret,
     // erase the provided arguments
     ret.attrs.erase(input_name);
   }
+
   std::string shape_attr_key;
   if (ret.attrs.count(attr_key_name) != 0) {
     shape_attr_key = ret.GetAttr<std::string>(attr_key_name);
diff --git a/nnvm/tests/python/test_graph.py b/nnvm/tests/python/test_graph.py
index b958cee10be0..f30acbf5c075 100644
--- a/nnvm/tests/python/test_graph.py
+++ b/nnvm/tests/python/test_graph.py
@@ -59,6 +59,22 @@ def test_infer_shape():
     assert g.json_attr('shape')[jnode_row_ptr[nindex["reshape1"]]] == [2, 4]
     assert g.json_attr('shape')[jnode_row_ptr[nindex["add1"]]] == [4, 2]
 
+def test_infer_shape_known_partial():
+    x = sym.Variable('x', shape=(4, 2))
+    y = sym.add(x, x, name='add1')
+    y = sym.reshape(y, target=(2, 4), name="reshape1")
+    g = graph.create(y)
+    jgraph = json.loads(g.apply('SaveJSON').json_attr('json'))
+    shape = [[4, 2], [] , []]
+    g._set_json_attr("shape", shape, 'list_shape')
+    g = g.apply("InferShape")
+    jnodes = jgraph['nodes']
+    jnode_row_ptr = jgraph['node_row_ptr']
+    nindex = {n['name']: i for i, n in enumerate(jnodes)}
+    assert g.json_attr('shape')[jnode_row_ptr[nindex["reshape1"]]] == [2, 4]
+    assert g.json_attr('shape')[jnode_row_ptr[nindex["add1"]]] == [4, 2]
+
+
 def test_infer_type():
     x = sym.Variable('x')
     y = sym.add(x, x, name='add1')
@@ -116,6 +132,7 @@ def test_plan_memory():
     test_graph_json_attr()
     test_json_pass()
     test_infer_shape()
+    test_infer_shape_known_partial()
     test_infer_type()
     test_place_device()
     test_plan_memory()

From 5575338a865bf434732e05bf805898686ecb77b7 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 22 Sep 2016 16:32:36 -0700
Subject: [PATCH 383/949] [Infer] More robust inference, support backward
 inference (#54)

---
 nnvm/src/pass/infer_shape_type.cc | 114 +++++++++++++++++++++---------
 1 file changed, 82 insertions(+), 32 deletions(-)

diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index a18528b5b465..445787f7e13f 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -11,15 +11,16 @@ namespace nnvm {
 namespace pass {
 namespace {
 
-template<typename AttrType, typename IsNone>
+template<typename AttrType, typename IsNone, typename FDefault>
 Graph InferAttr(Graph &&ret,
-                const AttrType default_val,
+                const AttrType empty_val,
                 const char* infer_name,
                 const char* input_name,
                 const char* attr_key_name,
                 const char* attr_name,
                 const char* unknown_name,
-                IsNone fis_none) {
+                IsNone fis_none,
+                FDefault fdefault) {
   using AttrVector = std::vector<AttrType>;
   const IndexedGraph& idx = ret.indexed_graph();
   static auto& finfer_shape =
@@ -31,7 +32,7 @@ Graph InferAttr(Graph &&ret,
   if (ret.attrs.count(attr_name) != 0) {
     rshape = ret.MoveCopyAttr<AttrVector>(attr_name);
   } else {
-    rshape.resize(idx.num_node_entries(), default_val);
+    rshape.resize(idx.num_node_entries(), empty_val);
   }
 
   if (ret.attrs.count(input_name) != 0) {
@@ -51,12 +52,12 @@ Graph InferAttr(Graph &&ret,
     // erase the provided arguments
     ret.attrs.erase(attr_key_name);
   }
-
   // Temp space for shape inference.
   std::vector<AttrType> ishape, oshape;
-  // number of completed nodes
-  size_t num_unknown = 0;
-  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+  size_t num_unknown;
+
+  // inference step function for nid
+  auto infer_step = [&](uint32_t nid) {
     const auto& inode = idx[nid];
     const uint32_t num_inputs = inode.inputs.size();
     const uint32_t num_outputs = inode.source->num_outputs();
@@ -72,27 +73,6 @@ Graph InferAttr(Graph &&ret,
           CHECK(is >> rshape[out_ent_id]) << "Invalid attribute";
         }
       }
-    } else if (finfer_shape.count(inode.source->op())) {
-      // Forward operator inference.
-      ishape.resize(num_inputs, default_val);
-      for (uint32_t i = 0; i < ishape.size(); ++i) {
-        ishape[i] = rshape[idx.entry_id(inode.inputs[i])];
-      }
-      oshape.resize(num_outputs, default_val);
-      for (uint32_t i = 0; i < oshape.size(); ++i) {
-        oshape[i] = rshape[idx.entry_id(nid, i)];
-      }
-      // Call inference function of the operator.
-      bool forward_known = finfer_shape[inode.source->op()](
-          inode.source->attrs, &ishape, &oshape);
-      num_unknown += !forward_known;
-      // Save to the result map.
-      for (uint32_t i = 0; i < num_inputs; ++i) {
-        rshape[idx.entry_id(inode.inputs[i])] = ishape[i];
-      }
-      for (uint32_t i = 0; i < num_outputs; ++i) {
-        rshape[idx.entry_id(nid, i)] = oshape[i];
-      }
     } else if (backward_map.count(inode.source->op())) {
       // Backward operator inference.
       CHECK_GE(inode.control_deps.size(), 1)
@@ -111,6 +91,47 @@ Graph InferAttr(Graph &&ret,
         if (fis_none(rshape[idx.entry_id(nid, i)])) known = false;
       }
       num_unknown += !known;
+    } else {
+      bool forward_known = true;
+      // Forward operator inference.
+      ishape.resize(num_inputs, empty_val);
+      for (uint32_t i = 0; i < ishape.size(); ++i) {
+        ishape[i] = rshape[idx.entry_id(inode.inputs[i])];
+        if (fis_none(ishape[i])) forward_known = false;
+      }
+      oshape.resize(num_outputs, empty_val);
+      for (uint32_t i = 0; i < oshape.size(); ++i) {
+        oshape[i] = rshape[idx.entry_id(nid, i)];
+        if (fis_none(oshape[i])) forward_known = false;
+      }
+      if (!forward_known) {
+        auto finfer = finfer_shape.get(inode.source->op(), fdefault);
+        CHECK(finfer != nullptr)
+          << "Attribute " << infer_name
+          << " is not registed by op " << inode.source->op()->name;
+        // Call inference function of the operator.
+        forward_known = finfer(inode.source->attrs, &ishape, &oshape);
+      }
+      num_unknown += !forward_known;
+      // Save to the result map.
+      for (uint32_t i = 0; i < num_inputs; ++i) {
+        rshape[idx.entry_id(inode.inputs[i])] = ishape[i];
+      }
+      for (uint32_t i = 0; i < num_outputs; ++i) {
+        rshape[idx.entry_id(nid, i)] = oshape[i];
+      }
+    }
+  };
+
+  num_unknown = 0;
+  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+    infer_step(nid);
+  }
+  if (num_unknown != 0) {
+    num_unknown = 0;
+    // backward inference
+    for (uint32_t i = idx.num_nodes(); i != 0; --i) {
+      infer_step(i - 1);
     }
   }
   // set the shapes
@@ -127,19 +148,48 @@ NNVM_REGISTER_PASS(InferShape)
         std::move(ret), TShape(),
         "FInferShape", "shape_inputs", "shape_attr_key",
         "shape", "shape_num_unknown_nodes",
-        [](const TShape& s) { return s.ndim() == 0; });
+        [](const TShape& s) { return s.ndim() == 0; },
+        nullptr);
   })
 .set_change_graph(false)
 .provide_graph_attr("shape");
 
+// inference fucntion for same type
+inline bool SameType(const NodeAttrs& attrs,
+                     std::vector<int> *iattr,
+                     std::vector<int> *oattr) {
+  int def_v = -1;
+  for (int v : *oattr) {
+    if (v != -1) {
+      def_v = v; break;
+    }
+  }
+  if (def_v == -1) {
+    for (int v : *iattr) {
+      if (v != -1) {
+        def_v = v; break;
+      }
+    }
+  }
+  if (def_v == -1) return false;
+  for (int& v : *oattr) {
+    v = def_v;
+  }
+  for (int& v : *iattr) {
+    v = def_v;
+  }
+  return true;
+}
+
 NNVM_REGISTER_PASS(InferType)
 .describe("Infer the dtype of each node entries.")
 .set_body([](Graph ret) {
     return InferAttr<int>(
-        std::move(ret), 0,
+        std::move(ret), -1,
         "FInferType", "dtype_inputs", "dtype_attr_key",
         "dtype", "dtype_num_unknown_nodes",
-        [](const int t) { return t == -1; });
+        [](const int t) { return t == -1; },
+        SameType);
   })
 .set_change_graph(false)
 .provide_graph_attr("dtype");

From 3a469d5fb0ee134f231049e73c1cda1f28e662d7 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 23 Sep 2016 09:54:18 -0700
Subject: [PATCH 384/949] Enable control deps in API (#55)

* [SYMBOL] support control deps in API

* enable more generic tuple list

* fix

* fix
---
 nnvm/include/nnvm/c_api.h        |  7 +++++++
 nnvm/include/nnvm/op.h           |  6 ++++--
 nnvm/include/nnvm/tuple.h        |  8 ++++----
 nnvm/python/nnvm/symbol.py       | 16 ++++++++++++++++
 nnvm/src/c_api/c_api_symbolic.cc | 10 +++++++++-
 nnvm/tests/python/test_graph.py  |  3 ++-
 nnvm/tests/python/test_symbol.py |  8 ++++++++
 7 files changed, 50 insertions(+), 8 deletions(-)

diff --git a/nnvm/include/nnvm/c_api.h b/nnvm/include/nnvm/c_api.h
index 7f12561e9cdd..a7bf2616ddce 100644
--- a/nnvm/include/nnvm/c_api.h
+++ b/nnvm/include/nnvm/c_api.h
@@ -133,6 +133,13 @@ NNVM_DLL int NNSymbolCreateVariable(const char *name, SymbolHandle *out);
 NNVM_DLL int NNSymbolCreateGroup(nn_uint num_symbols,
                                  SymbolHandle *symbols,
                                  SymbolHandle *out);
+/*!
+ * \brief Add src_dep to the handle as control dep.
+ * \param handle The symbol to add dependency edges on.
+ * \param src_dep the source handles.
+ */
+NNVM_DLL int NNAddControlDeps(SymbolHandle handle,
+                              SymbolHandle src_dep);
 /*!
  * \brief Free the symbol handle.
  * \param symbol the symbol
diff --git a/nnvm/include/nnvm/op.h b/nnvm/include/nnvm/op.h
index 9f6736ef4e25..b79f170ac878 100644
--- a/nnvm/include/nnvm/op.h
+++ b/nnvm/include/nnvm/op.h
@@ -348,8 +348,10 @@ inline Op& Op::set_attr(  // NOLINT(*)
       std::vector<std::pair<ValueType, int> >& vec =
           nnvm::get<OpMap<ValueType> >(*pmap).data_;
       // resize the value type.
-      vec.resize(index_ + 1,
-                 std::make_pair(ValueType(), 0));
+      if (vec.size() <= index_) {
+        vec.resize(index_ + 1,
+                   std::make_pair(ValueType(), 0));
+      }
       std::pair<ValueType, int>& p = vec[index_];
       CHECK(p.second == 0)
           << "Attribute " << attr_name
diff --git a/nnvm/include/nnvm/tuple.h b/nnvm/include/nnvm/tuple.h
index ed5f30e516b0..c925e941f7b6 100644
--- a/nnvm/include/nnvm/tuple.h
+++ b/nnvm/include/nnvm/tuple.h
@@ -227,7 +227,7 @@ class Tuple {
         return is;
       }
       is.get();
-      if (ch == '(') break;
+      if (ch == '(' || ch == '[') break;
       if (!isspace(ch)) {
         is.setstate(std::ios::failbit);
         return is;
@@ -250,13 +250,13 @@ class Tuple {
           if (isspace(ch)) {
             is.get(); continue;
           }
-          if (ch == ')') {
+          if (ch == ')' || ch == ']') {
             is.get(); break;
           }
           break;
         }
-        if (ch == ')') break;
-      } else if (ch == ')') {
+        if (ch == ')' || ch == ']') break;
+      } else if (ch == ')' || ch == ']') {
         break;
       } else {
         is.setstate(std::ios::failbit);
diff --git a/nnvm/python/nnvm/symbol.py b/nnvm/python/nnvm/symbol.py
index 3b11ad9711fd..bcd34cfb6a6a 100644
--- a/nnvm/python/nnvm/symbol.py
+++ b/nnvm/python/nnvm/symbol.py
@@ -233,6 +233,22 @@ def debug_str(self):
             self.handle, _ctypes.byref(debug_str)))
         return _base.py_str(debug_str.value)
 
+    def _add_control_deps(self, deps):
+        """Add control flow dependencies.
+        This makes current op depend on the deps.
+        Only use when necessary,
+        this function mutate the current symbol node.
+
+        Returns
+        -------
+        deps : Symbol for list of symbol
+            The dependencies
+        """
+        if isinstance(deps, list):
+            deps = Group(deps)
+        _check_call(_LIB.NNAddControlDeps(
+            self.handle, deps.handle))
+
 
 def Variable(name, **kwargs):
     """Create a symbolic variable with specified name.
diff --git a/nnvm/src/c_api/c_api_symbolic.cc b/nnvm/src/c_api/c_api_symbolic.cc
index ebcc252ed455..266e2e79606b 100644
--- a/nnvm/src/c_api/c_api_symbolic.cc
+++ b/nnvm/src/c_api/c_api_symbolic.cc
@@ -32,7 +32,7 @@ int NNGetOpHandle(const char* op_name,
 }
 
 int NNListUniqueOps(nn_uint *out_size,
-                          OpHandle **out_array) {
+                    OpHandle **out_array) {
   API_BEGIN();
   auto &vec = dmlc::Registry<Op>::List();
   *out_size = static_cast<nn_uint>(vec.size());
@@ -40,6 +40,14 @@ int NNListUniqueOps(nn_uint *out_size,
   API_END();
 }
 
+int NNAddControlDeps(SymbolHandle handle,
+                     SymbolHandle src_dep) {
+  API_BEGIN();
+  static_cast<Symbol*>(handle)->AddControlDeps(
+      *static_cast<Symbol*>(src_dep));
+  API_END();
+}
+
 int NNGetOpInfo(OpHandle handle,
                 const char **name,
                 const char **description,
diff --git a/nnvm/tests/python/test_graph.py b/nnvm/tests/python/test_graph.py
index f30acbf5c075..9424a2ec66cc 100644
--- a/nnvm/tests/python/test_graph.py
+++ b/nnvm/tests/python/test_graph.py
@@ -76,10 +76,11 @@ def test_infer_shape_known_partial():
 
 
 def test_infer_type():
-    x = sym.Variable('x')
+    x = sym.Variable('x', dtype=0)
     y = sym.add(x, x, name='add1')
     y = sym.cast(y, dtype=1, name="cast1")
     g = graph.create(y)
+    g._set_json_attr("dtype_attr_key", "dtype")
     g = g.apply('InferType')
     jgraph = json.loads(g.apply('SaveJSON').json_attr('json'))
     jnodes = jgraph['nodes']
diff --git a/nnvm/tests/python/test_symbol.py b/nnvm/tests/python/test_symbol.py
index 994f0d14a605..989cd99e730f 100644
--- a/nnvm/tests/python/test_symbol.py
+++ b/nnvm/tests/python/test_symbol.py
@@ -43,8 +43,16 @@ def test_copy():
                 name='exp', gpu=1, attr={"kk": "1"})
     assert y.__copy__().debug_str() == y.debug_str()
 
+def test_control_dep():
+    x = sym.Variable('x')
+    y = sym.conv2d(data=x, name='conv')
+    z = sym.assign(x, y)
+    t = sym.add(x, x)
+    t._add_control_deps([z, y])
+
 if __name__ == "__main__":
     test_copy()
     test_default_input()
     test_compose()
     test_mutate_input()
+    test_control_dep()

From 8928f4e613bb34c86239386a5953a53cdc39c24d Mon Sep 17 00:00:00 2001
From: Wei Wu <tornadomeet@users.noreply.github.com>
Date: Sat, 24 Sep 2016 00:54:24 +0800
Subject: [PATCH 385/949] fix typo (#56)

---
 nnvm/README.md        | 6 +++---
 nnvm/docs/overview.md | 6 +++---
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/nnvm/README.md b/nnvm/README.md
index e25ee9acac70..06d072181f9b 100644
--- a/nnvm/README.md
+++ b/nnvm/README.md
@@ -15,11 +15,11 @@ We believe that the decentralized modular system is an interesting direction.
 
 The hope is that effective parts can be assembled together just like you assemble your own desktops.
 So the customized deep learning solution can be minimax, minimum in terms of dependencies,
-while maxiziming the users' need.
+while maximizing the users' need.
 
 NNVM offers one such part, it provides a generic way to do
 computation graph optimization such as memory reduction, device allocation and more
-while being agnostic to the operator interface defintion and how operators are executed.
+while being agnostic to the operator interface definition and how operators are executed.
 NNVM is inspired by LLVM, aiming to be a high level intermediate representation library
 for neural nets and computation graphs generation and optimizations.
 
@@ -32,7 +32,7 @@ This is essentially ***Unix philosophy*** applied to machine learning system.
 - Essential parts can be assembled in minimum way for embedding systems.
 - Developers can hack the parts they need and compose with other well defined parts.
 - Decentralized modules enable new extensions creators to own their project
-  without creating a monothilic version.
+  without creating a monolithic version.
 
 Deep learning system itself is not necessary one part, for example
 here are some relative independent parts that can be isolated
diff --git a/nnvm/docs/overview.md b/nnvm/docs/overview.md
index e4c9fe4b1641..cd51768ef942 100644
--- a/nnvm/docs/overview.md
+++ b/nnvm/docs/overview.md
@@ -13,7 +13,7 @@ with the modular tools like CuDNN and CUDA, it is not hard to assemble a C++ API
 However, most users like to use python/R/scala or other languages.
 By registering the operators to NNVM, X can now get the graph composition
 language front-end on these languages quickly without coding it up for
-each type of langugage.
+each type of language.
 
 Y want to build a deep learning serving system on embedded devices.
 To do that, we need to cut things off, as opposed to add new parts,
@@ -97,7 +97,7 @@ Eventually the operator interface become big and have to evolve in the centraliz
 
 In NNVM, we decided to change the design and support arbitrary type of operator attributes,
 without need to change the operator registry. This also echos the need of minimum interface
-so that the code can be easier to share accross multiple projects
+so that the code can be easier to share across multiple projects
 
 User can register new attribute, such as inplace property checking function as follows.
 ```c++
@@ -122,7 +122,7 @@ NNVM_REGISTER_OP(exp)
 ```
 
 These attributes can be queried at arbitrary parts of the code, like the following parts.
-Under the hood, each attributes are stored in a any type columar store,
+Under the hood, each attributes are stored in a any type columnar store,
 that can easily be retrieved and cast back to typed table and do quick lookups.
 
 ```c++

From 7c4ed36a1a9d71e449f54189b60077e699e21e09 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 23 Sep 2016 19:27:36 -0700
Subject: [PATCH 386/949] [OP] Enable register via match tag (#57)

* [OP] Enable register via match tag

* more docs on usage
---
 nnvm/example/src/operator.cc |  38 +++++----
 nnvm/include/nnvm/op.h       | 144 ++++++++++++++++++++++++++++++-----
 nnvm/src/core/op.cc          |  42 +++++++++-
 3 files changed, 187 insertions(+), 37 deletions(-)

diff --git a/nnvm/example/src/operator.cc b/nnvm/example/src/operator.cc
index 1533b6a4b250..34e4529ecb0b 100644
--- a/nnvm/example/src/operator.cc
+++ b/nnvm/example/src/operator.cc
@@ -84,6 +84,7 @@ NNVM_REGISTER_OP(reshape)
 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
@@ -92,7 +93,6 @@ NNVM_REGISTER_OP(cast)
       CHECK(is >> dtype);
       attrs->parsed = std::move(dtype);
     })
-.set_attr<FInferShape>("FInferShape", SameShape)
 .set_attr<FInferType>(
     "FInferType", [](const NodeAttrs& attrs,
                      std::vector<int> *itype,
@@ -101,23 +101,10 @@ NNVM_REGISTER_OP(cast)
       return true;
     });
 
-NNVM_REGISTER_OP(exp)
-.describe("take exponential")
-.set_num_inputs(1)
-.set_attr<FInferShape>("FInferShape", SameShape)
-.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}})
-      };
-    });
-
 NNVM_REGISTER_OP(identity)
 .describe("identity function")
 .set_num_inputs(1)
-.set_attr<FInferShape>("FInferShape", SameShape)
+.include("ElementwiseOpAttr")
 .set_attr<FGradient>(
     "FGradient", [](const NodePtr& n,
                     const std::vector<NodeEntry>& ograds) {
@@ -128,7 +115,7 @@ NNVM_REGISTER_OP(add)
 .describe("add two data together")
 .set_num_inputs(2)
 .add_alias("__add_symbol__")
-.set_attr<FInferShape>("FInferShape", SameShape)
+.include("ElementwiseOpAttr")
 .set_attr<FInplaceOption>("FInplaceOption", InplaceIn0Out0)
 .set_attr<FGradient>(
     "FGradient", [](const NodePtr& n,
@@ -139,6 +126,7 @@ NNVM_REGISTER_OP(add)
 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>(
@@ -187,4 +175,22 @@ NNVM_REGISTER_OP(assign)
     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
diff --git a/nnvm/include/nnvm/op.h b/nnvm/include/nnvm/op.h
index b79f170ac878..23648980e1f9 100644
--- a/nnvm/include/nnvm/op.h
+++ b/nnvm/include/nnvm/op.h
@@ -22,6 +22,7 @@ class Node;
 struct NodeAttrs;
 template<typename ValueType>
 class OpMap;
+class OpGroup;
 class OpRegistryEntry;
 using dmlc::ParamFieldInfo;
 
@@ -44,7 +45,13 @@ static const uint32_t kVarg = std::numeric_limits<uint32_t>::max();
  *  NNVM_REGISTER_OP(add)
  *  .describe("add two inputs together")
  *  .set_num_inputs(2)
- *  .set_attr<OpKernel>("gpu_kernel", AddKernel);
+ *  .set_attr<OpKernel>("OpKernel<gpu>", AddKernel)
+ *  .include("ElementwiseOpAttr");
+ *
+ *  // can register attribute by group
+ *  // all the ops that include the group get the attribute.
+ *  NNVM_REGISTER_OP_GROUP(ElementwiseOpAttr)
+ *  .set_attr<FInferShape>("FInferShape", ElementwiseInferShape);
  *
  *  NNVM_REGISTER_OP(sub)
  *  .describe("substract one tensor from another")
@@ -53,7 +60,8 @@ static const uint32_t kVarg = std::numeric_limits<uint32_t>::max();
  *  // Can call regster multiple times in different files
  *  // to register different part of information
  *  NNVM_REGISTER_OP(sub)
- *  .set_attr<OpKernel>("gpu_kernel", SubKernel);
+ *  .set_attr<OpKernel>("OpKernel<gpu>", SubKernel);
+ *  .include("ElementwiseOpAttr");
  *
  *  // get operators from registry.
  *  void my_function() {
@@ -65,7 +73,7 @@ static const uint32_t kVarg = std::numeric_limits<uint32_t>::max();
  *
  *    // get additional registered information,
  *    // Assume user registered a OpKernel type attribute as gpu_kernel on each operator.
- *    const OpMap<OpKernel>& kernel = Op::GetAttr<OpKernel>("gpu_kernel");
+ *    const OpMap<OpKernel>& kernel = Op::GetAttr<OpKernel>("OpKernel<gpu>");
  *    // we can get the kernel functions by using operator as key.
  *    auto add_kernel = kernel[add];
  *    auto sub_kernel = kernel[sub];
@@ -199,6 +207,23 @@ class Op {
    * \return reference to self.
    */
   inline Op& set_attr_parser(std::function<void (NodeAttrs* attrs)> fn);  // NOLINT(*)
+  /*!
+   * \brief Register additional attributes to operator.
+   * \param attr_name The name of the attribute.
+   * \param value The value to be set.
+   * \param plevel The priority level of this set,
+   *  an higher priority level attribute
+   *  will replace lower priority level attribute.
+   *  Must be bigger than 0.
+   *
+   *  Cannot set with same plevel twice in the code.
+   *
+   * \tparam ValueType The type of the value to be set.
+   */
+  template<typename ValueType>
+  inline Op& set_attr(const std::string& attr_name,  // NOLINT(*)
+                      const ValueType& value,
+                      int plevel = 10);
   /*!
    * \brief Add another alias to this operator.
    *   The same Op can be queried with Op::Get(alias)
@@ -207,14 +232,13 @@ class Op {
    */
   Op& add_alias(const std::string& alias);  // NOLINT(*)
   /*!
-   * \brief Register additional attributes to operator.
-   * \param attr_name The name of the attribute.
-   * \param value The value to be set.
-   * \tparam ValueType The type of the value to be set.
+   * \brief Include all the attributes from an registered op group.
+   * \param group_name The name of the group.
+   * \return reference to self.
+   *
+   * \sa NNVM_REGISTER_OP_GROUP
    */
-  template<typename ValueType>
-  inline Op& set_attr(const std::string& attr_name,  // NOLINT(*)
-                      const ValueType& value);
+  Op& include(const std::string& group_name);
   /*!
    * \brief Get an Op for a given operator name.
    *  Will raise an error if the op has not been registered.
@@ -235,6 +259,7 @@ class Op {
  private:
   template<typename ValueType>
   friend class OpMap;
+  friend class OpGroup;
   friend class dmlc::Registry<Op>;
   // Program internal unique index of operator.
   // Used to help index the program.
@@ -246,6 +271,13 @@ class Op {
   // update the attribute OpMap
   static void UpdateAttrMap(const std::string& key,
                             std::function<void(any*)> updater);
+  // add a trigger based on tag matching on certain tag attribute
+  // This will apply trigger on all the op such that
+  // include the corresponding group.
+  // The trigger will also be applied to all future registrations
+  // that calls include
+  static void AddGroupTrigger(const std::string& group_name,
+                              std::function<void(Op*)> trigger);
 };
 
 /*!
@@ -285,14 +317,44 @@ class OpMap {
   OpMap() = default;
 };
 
+/*!
+ * \brief auxiliary data structure used to
+ *  set attributes to a group of operators
+ */
+class OpGroup {
+ public:
+  /*! \brief the tag key to be matched */
+  std::string group_name;
+  /*!
+   * \brief Register additional attributes to operator group.
+   * \param attr_name The name of the attribute.
+   * \param value The value to be set.
+   * \param plevel The priority level of this set,
+   *  an higher priority level attribute
+   *  will replace lower priority level attribute.
+   *  Must be bigger than 0.
+   *
+   *  Cannot set with same plevel twice in the code.
+   *
+   * \tparam ValueType The type of the value to be set.
+   */
+  template<typename ValueType>
+  inline OpGroup& set_attr(const std::string& attr_name,  // NOLINT(*)
+                           const ValueType& value,
+                           int plevel = 1);
+};
+
 // internal macros to make
-#define NNVM_REGISTER_VAR_DEF(OpName)                              \
+#define NNVM_REGISTER_VAR_DEF(OpName)                                   \
   static DMLC_ATTRIBUTE_UNUSED ::nnvm::Op & __make_ ## NnvmOp ## _ ## OpName
 
+#define NNVM_REGISTER_GVAR_DEF(TagName)                                     \
+  static DMLC_ATTRIBUTE_UNUSED ::nnvm::OpGroup __make_ ## NnvmOpGroup ## _ ## TagName
+
 /*!
  * \def NNVM_REGISTER_OP
- * \brief Register
- * This macro must be used under namespace dmlc, and only used once in cc file.
+ * \brief Register a new operator, or set attribute of the corresponding op.
+ *
  * \param OpName The name of registry
  *
  * \code
@@ -308,6 +370,31 @@ class OpMap {
   DMLC_STR_CONCAT(NNVM_REGISTER_VAR_DEF(OpName), __COUNTER__) =         \
       ::dmlc::Registry<::nnvm::Op>::Get()->__REGISTER_OR_GET__(#OpName)
 
+/*!
+ * \def NNVM_REGISTER_OP_GROUP
+ * \brief Register attribute to a group of operators.
+ * These attributes will be registered to Op that include the group.
+ *
+ * \param GroupName The name of the group.
+ *
+ * \code
+ *
+ *  NNVM_REGISTER_OP(add)
+ *  .include("ElementwiseOpAttr");
+ *
+ *  // register same attributes to all the ops that include the group
+ *  NNVM_REGISTER_OP_GROUP(ElementwiseOpAttr)
+ *  .set_attr<FInferShape>("FInferShape", ElementwiseInferShape);
+ *
+ *  NNVM_REGISTER_OP(mul)
+ *  .include("ElementwiseOpAttr");
+ *
+ * \endcode
+ */
+#define NNVM_REGISTER_OP_GROUP(GroupName)                               \
+  DMLC_STR_CONCAT(NNVM_REGISTER_GVAR_DEF(GroupName), __COUNTER__) =     \
+      ::nnvm::OpGroup {#GroupName}
+
 // implementations of template functions after this.
 // member function of Op
 template<typename ValueType>
@@ -330,9 +417,14 @@ inline const OpMap<ValueType>& Op::GetAttr(const std::string& key) {
 
 template<typename ValueType>
 inline Op& Op::set_attr(  // NOLINT(*)
-    const std::string& attr_name, const ValueType& value) {
+    const std::string& attr_name,
+    const ValueType& value,
+    int plevel) {
+  CHECK_GT(plevel, 0)
+      << "plevel in set_attr must be greater than 0";
   // update the attribute map of the key by creating new empty if needed.
-  UpdateAttrMap(attr_name, [this, attr_name, value](any* pmap) {
+  UpdateAttrMap(attr_name,
+                [this, attr_name, value, plevel](any* pmap) {
       // the callback is in lockscope so is threadsafe.
       if (pmap->empty()) {
         OpMap<ValueType> pm;
@@ -353,15 +445,18 @@ inline Op& Op::set_attr(  // NOLINT(*)
                    std::make_pair(ValueType(), 0));
       }
       std::pair<ValueType, int>& p = vec[index_];
-      CHECK(p.second == 0)
+      CHECK(p.second != plevel)
           << "Attribute " << attr_name
           << " of operator " << this->name
-          << " is already registered.";
-          vec[index_] = std::make_pair(value, 1);
+          << " is already registered with same plevel=" << plevel;
+      if (p.second < plevel) {
+        vec[index_] = std::make_pair(value, plevel);
+      }
     });
   return *this;
 }
 
+
 inline Op& Op::describe(const std::string& descr) {  // NOLINT(*)
   this->description = descr;
   return *this;
@@ -409,7 +504,7 @@ template<typename ValueType>
 inline int OpMap<ValueType>::count(const Op* op) const {
   if (op == nullptr) return 0;
   const uint32_t idx = op->index_;
-  return idx < data_.size() ? data_[idx].second : 0;
+  return idx < data_.size() ? (data_[idx].second != 0) : 0;
 }
 
 template<typename ValueType>
@@ -433,6 +528,17 @@ inline const ValueType& OpMap<ValueType>::get(const Op* op, const ValueType& def
   }
 }
 
+template<typename ValueType>
+inline OpGroup& OpGroup::set_attr(const std::string& attr_name,
+                                  const ValueType& value,
+                                  int plevel) {
+  auto trigger = [attr_name, value, plevel](Op* op) {
+    op->set_attr<ValueType>(attr_name, value, plevel);
+  };
+  Op::AddGroupTrigger(group_name, trigger);
+  return *this;
+}
+
 }  // namespace nnvm
 
 #endif  // NNVM_OP_H_
diff --git a/nnvm/src/core/op.cc b/nnvm/src/core/op.cc
index a8e54ab92cab..e554d36b4e8c 100644
--- a/nnvm/src/core/op.cc
+++ b/nnvm/src/core/op.cc
@@ -9,6 +9,7 @@
 #include <memory>
 #include <atomic>
 #include <mutex>
+#include <unordered_set>
 
 namespace dmlc {
 // enable registry
@@ -20,11 +21,16 @@ namespace nnvm {
 // single manager of operator information.
 struct OpManager {
   // mutex to avoid registration from multiple threads.
-  std::mutex mutex;
+  // recursive is needed for trigger(which calls UpdateAttrMap)
+  std::recursive_mutex mutex;
   // global operator counter
   std::atomic<int> op_counter{0};
   // storage of additional attribute table.
   std::unordered_map<std::string, std::unique_ptr<any> > attr;
+  // storage of existing triggers
+  std::unordered_map<std::string, std::vector<std::function<void(Op*)>  > > tmap;
+  // group of each operator.
+  std::vector<std::unordered_set<std::string> > op_group;
   // get singleton of the
   static OpManager* Global() {
     static OpManager inst;
@@ -66,10 +72,42 @@ const any* Op::GetAttrMap(const std::string& key) {
 void Op::UpdateAttrMap(const std::string& key,
                        std::function<void(any*)> updater) {
   OpManager* mgr = OpManager::Global();
-  std::lock_guard<std::mutex>(mgr->mutex);
+  std::lock_guard<std::recursive_mutex>(mgr->mutex);
   std::unique_ptr<any>& value = mgr->attr[key];
   if (value.get() == nullptr) value.reset(new any());
   if (updater != nullptr) updater(value.get());
 }
 
+void Op::AddGroupTrigger(const std::string& group_name,
+                         std::function<void(Op*)> trigger) {
+  OpManager* mgr = OpManager::Global();
+  std::lock_guard<std::recursive_mutex>(mgr->mutex);
+  auto& tvec = mgr->tmap[group_name];
+  tvec.push_back(trigger);
+  auto& op_group = mgr->op_group;
+  for (const Op* op : dmlc::Registry<Op>::List()) {
+    if (op->index_ < op_group.size() &&
+        op_group[op->index_].count(group_name) != 0) {
+      trigger((Op*)op);  // NOLINT(*)
+    }
+  }
+}
+
+Op& Op::include(const std::string& group_name) {
+  OpManager* mgr = OpManager::Global();
+  std::lock_guard<std::recursive_mutex>(mgr->mutex);
+  auto it = mgr->tmap.find(group_name);
+  if (it != mgr->tmap.end()) {
+    for (auto& trigger : it->second) {
+      trigger(this);
+    }
+  }
+  auto& op_group = mgr->op_group;
+  if (index_ >= op_group.size()) {
+    op_group.resize(index_ + 1);
+  }
+  op_group[index_].insert(group_name);
+  return *this;
+}
+
 }  // namespace nnvm

From 37b183fd651a395b8fa94085ac3e77e30f0db290 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 24 Sep 2016 09:00:02 -0700
Subject: [PATCH 387/949] Add shape backward inference (#58)

---
 nnvm/include/nnvm/op_attr_types.h | 15 +++++++++++
 nnvm/src/pass/infer_shape_type.cc | 44 +++++++++++++++++++++----------
 2 files changed, 45 insertions(+), 14 deletions(-)

diff --git a/nnvm/include/nnvm/op_attr_types.h b/nnvm/include/nnvm/op_attr_types.h
index b29ade88a034..220c1c11943a 100644
--- a/nnvm/include/nnvm/op_attr_types.h
+++ b/nnvm/include/nnvm/op_attr_types.h
@@ -107,6 +107,21 @@ using FInferType = FInferNodeEntryAttr<int>;
 using FBackwardOutToInIndex = std::function<
   std::vector<uint32_t> (const NodeAttrs& attrs)>;
 
+/*!
+ * \brief Whether this op is an explicit backward operator,
+ *  Returns list of input index that corresponds to the outputs of the forward operator.
+ *
+ *  If FBackwardInGradIndex exists:
+ *   - The first control_deps of the node points to the corresponding forward operator.
+ *   - The FBackwardInGradIndex[i]-th input of backward op corresponds to the i-th
+ *      output of forward operator.
+ *
+ * \note Register under "FBackwardInGradIndex"
+ * This enables easier shape/type inference for backward operators.
+ */
+using FBackwardInGradIndex = std::function<
+  std::vector<uint32_t> (const NodeAttrs& attrs)>;
+
 /*!
  * \brief Get possible inplace options.
  *  This function enables optimization to reuse memory of inputs in output.
diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index 445787f7e13f..d9f1a0ebe34f 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -27,6 +27,8 @@ Graph InferAttr(Graph &&ret,
       Op::GetAttr<FInferNodeEntryAttr<AttrType> >(infer_name);
   static auto& backward_map =
       Op::GetAttr<FBackwardOutToInIndex>("FBackwardOutToInIndex");
+  static auto& backward_in_grad =
+      Op::GetAttr<FBackwardInGradIndex>("FBackwardInGradIndex");
   // reshape shape vector
   AttrVector rshape;
   if (ret.attrs.count(attr_name) != 0) {
@@ -54,7 +56,6 @@ Graph InferAttr(Graph &&ret,
   }
   // Temp space for shape inference.
   std::vector<AttrType> ishape, oshape;
-  size_t num_unknown;
 
   // inference step function for nid
   auto infer_step = [&](uint32_t nid) {
@@ -76,21 +77,29 @@ Graph InferAttr(Graph &&ret,
     } else if (backward_map.count(inode.source->op())) {
       // Backward operator inference.
       CHECK_GE(inode.control_deps.size(), 1)
-          << "BackwardOp need to have control_deps to its forward op";
+        << "BackwardOp need to have control_deps to its forward op";
       const IndexedGraph::Node& fnode = idx[inode.control_deps[0]];
       // Inference the outputs of backward operator (equal to the inputs
       // of its corresponding forward operator).
       std::vector<uint32_t> out_map =
           backward_map[inode.source->op()](inode.source->attrs);
-      bool known = true;
       for (size_t i = 0; i < out_map.size(); ++i) {
         uint32_t in_id = out_map[i];
         CHECK_LT(in_id, fnode.inputs.size());
         rshape[idx.entry_id(nid, i)] =
             rshape[idx.entry_id(fnode.inputs[in_id])];
-        if (fis_none(rshape[idx.entry_id(nid, i)])) known = false;
       }
-      num_unknown += !known;
+      if (backward_in_grad.count(inode.source->op())) {
+        std::vector<uint32_t> in_grad =
+          backward_in_grad[inode.source->op()](inode.source->attrs);
+        CHECK_LE(in_grad.size(), fnode.source->num_outputs());
+        for (size_t i = 0; i < in_grad.size(); ++i) {
+          uint32_t eid = idx.entry_id(inode.inputs[in_grad[i]]);
+          if (fis_none(rshape[eid])) {
+            rshape[eid] = rshape[idx.entry_id(inode.control_deps[0], i)];
+          }
+        }
+      }
     } else {
       bool forward_known = true;
       // Forward operator inference.
@@ -112,7 +121,6 @@ Graph InferAttr(Graph &&ret,
         // Call inference function of the operator.
         forward_known = finfer(inode.source->attrs, &ishape, &oshape);
       }
-      num_unknown += !forward_known;
       // Save to the result map.
       for (uint32_t i = 0; i < num_inputs; ++i) {
         rshape[idx.entry_id(inode.inputs[i])] = ishape[i];
@@ -123,16 +131,24 @@ Graph InferAttr(Graph &&ret,
     }
   };
 
-  num_unknown = 0;
-  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
-    infer_step(nid);
-  }
-  if (num_unknown != 0) {
+  size_t num_unknown = 0;
+  const int kMaxStep = 3;
+  for (int i = 0; i < kMaxStep; ++i) {
+    if (i % 2 == 0) {
+      for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+        infer_step(nid);
+      }
+    } else {
+      // backward inference
+      for (uint32_t i = idx.num_nodes(); i != 0; --i) {
+        infer_step(i - 1);
+      }
+    }
     num_unknown = 0;
-    // backward inference
-    for (uint32_t i = idx.num_nodes(); i != 0; --i) {
-      infer_step(i - 1);
+    for (size_t i = 0; i < idx.num_node_entries(); ++i) {
+      if (fis_none(rshape[i])) ++num_unknown;
     }
+    if (num_unknown == 0) break;
   }
   // set the shapes
   ret.attrs[attr_name] = std::make_shared<any>(std::move(rshape));

From cbdf2dbe77d43c0c5d6d23c6a96e5a5748a3aef7 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 26 Sep 2016 13:35:26 -0700
Subject: [PATCH 388/949] [SYMBOL] Change list_input->list_input_names, add
 list_input_variables (#59)

* [SYMBOL] Change list_input->list_input_names, add list_input_variables

* fix
---
 nnvm/include/nnvm/c_api.h        | 19 +++++++++++-
 nnvm/python/nnvm/symbol.py       | 50 ++++++++++++++++++++++++--------
 nnvm/src/c_api/c_api_symbolic.cc | 19 ++++++++++++
 nnvm/src/core/symbolic.cc        |  7 +++--
 nnvm/src/pass/gradient.cc        | 17 ++++++-----
 nnvm/tests/python/test_graph.py  |  4 +--
 nnvm/tests/python/test_symbol.py | 10 ++++---
 7 files changed, 98 insertions(+), 28 deletions(-)

diff --git a/nnvm/include/nnvm/c_api.h b/nnvm/include/nnvm/c_api.h
index a7bf2616ddce..e4e7ac59c5f6 100644
--- a/nnvm/include/nnvm/c_api.h
+++ b/nnvm/include/nnvm/c_api.h
@@ -205,8 +205,25 @@ NNVM_DLL int NNSymbolListAttrs(SymbolHandle symbol,
                                int recursive_option,
                                nn_uint *out_size,
                                const char*** out);
+
+/*!
+ * \brief List inputs variables in the symbol.
+ * \param symbol the symbol
+ * \param option The option to list the inputs
+ *   option=0 means list all arguments.
+ *   option=1 means list arguments that are readed only by the graph.
+ *   option=2 means list arguments that are mutated by the graph.
+ * \param out_size output size
+ * \param out_sym_array the output array.
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNSymbolListInputVariables(SymbolHandle symbol,
+                                        int option,
+                                        nn_uint *out_size,
+                                        SymbolHandle** out_sym_array);
+
 /*!
- * \brief List inputs in the symbol.
+ * \brief List input names in the symbol.
  * \param symbol the symbol
  * \param option The option to list the inputs
  *   option=0 means list all arguments.
diff --git a/nnvm/python/nnvm/symbol.py b/nnvm/python/nnvm/symbol.py
index bcd34cfb6a6a..79c4b7ec4626 100644
--- a/nnvm/python/nnvm/symbol.py
+++ b/nnvm/python/nnvm/symbol.py
@@ -108,7 +108,7 @@ def __deepcopy__(self, _=None):
     def __getitem__(self, index):
         if isinstance(index, _base.string_types):
             idx = None
-            for i, name in enumerate(self.list_outputs()):
+            for i, name in enumerate(self.list_output_names()):
                 if name == index:
                     if idx is not None:
                         raise ValueError('There are multiple outputs with name \"%s\"' % index)
@@ -177,7 +177,40 @@ def get_internals(self):
             self.handle, _ctypes.byref(handle)))
         return Symbol(handle=handle)
 
-    def list_inputs(self, option='all'):
+    def _get_list_copt(self, option):
+        """internal function to get list option"""
+        if option == 'all':
+            return _ctypes.c_int(0)
+        elif option == 'read_only':
+            return _ctypes.c_int(1)
+        elif option == 'aux_state':
+            return _ctypes.c_int(2)
+        else:
+            raise ValueError("option need to be in {'all', 'read_only, 'aux_state'}")
+
+    def list_input_variables(self, option='all'):
+        """List all the input variables in the symbol.
+
+        Parameters
+        ----------
+        option : {'all', 'read_only', 'aux_state'}, optional
+           The listing option
+           - 'all' will list all the arguments.
+           - 'read_only' lists arguments that are readed by the graph.
+           - 'aux_state' lists arguments that are mutated by the graph as state.
+        Returns
+        -------
+        vars : list of symbol
+            List of all the variables
+        """
+        size = _ctypes.c_uint()
+        sarr = _ctypes.POINTER(_base.SymbolHandle)()
+        _check_call(_LIB.NNSymbolListInputVariables(
+            self.handle, self._get_list_copt(option),
+            _ctypes.byref(size), _ctypes.byref(sarr)))
+        return [Symbol(_base.SymbolHandle(sarr[i])) for i in range(size.value)]
+
+    def list_input_names(self, option='all'):
         """List all the inputs in the symbol.
 
         Parameters
@@ -194,19 +227,12 @@ def list_inputs(self, option='all'):
         """
         size = _ctypes.c_uint()
         sarr = _ctypes.POINTER(_ctypes.c_char_p)()
-        if option == 'all':
-            copt = _ctypes.c_int(0)
-        elif option == 'read_only':
-            copt = _ctypes.c_int(1)
-        elif option == 'aux_state':
-            copt = _ctypes.c_int(2)
-        else:
-            raise ValueError("option need to be in {'all', 'read_only, 'aux_state'}")
         _check_call(_LIB.NNSymbolListInputNames(
-            self.handle, copt, _ctypes.byref(size), _ctypes.byref(sarr)))
+            self.handle, self._get_list_copt(option),
+            _ctypes.byref(size), _ctypes.byref(sarr)))
         return [_base.py_str(sarr[i]) for i in range(size.value)]
 
-    def list_outputs(self):
+    def list_output_names(self):
         """List all outputs in the symbol.
 
         Returns
diff --git a/nnvm/src/c_api/c_api_symbolic.cc b/nnvm/src/c_api/c_api_symbolic.cc
index 266e2e79606b..fae58636deae 100644
--- a/nnvm/src/c_api/c_api_symbolic.cc
+++ b/nnvm/src/c_api/c_api_symbolic.cc
@@ -221,6 +221,25 @@ int NNSymbolListAttrs(SymbolHandle symbol,
   API_END();
 }
 
+int NNSymbolListInputVariables(SymbolHandle symbol,
+                               int option,
+                               nn_uint *out_size,
+                               SymbolHandle** out_sym_array) {
+  Symbol *s = static_cast<Symbol*>(symbol);
+  NNAPIThreadLocalEntry *ret = NNAPIThreadLocalStore::Get();
+  API_BEGIN();
+  std::vector<NodePtr> vs = s->ListInputs(Symbol::ListInputOption(option));
+  ret->ret_handles.clear();
+  for (size_t i = 0; i < vs.size(); ++i) {
+    nnvm::Symbol* rs = new nnvm::Symbol();
+    rs->outputs.push_back(NodeEntry{vs[i], 0, 0});
+    ret->ret_handles.push_back(rs);
+  }
+  *out_size = static_cast<nn_uint>(vs.size());
+  *out_sym_array = dmlc::BeginPtr(ret->ret_handles);
+  API_END();
+}
+
 int NNSymbolListInputNames(SymbolHandle symbol,
                            int option,
                            nn_uint *out_size,
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index ba6633043518..efb4a7a451f3 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -87,7 +87,8 @@ inline std::vector<std::string> GetKeys(
 
 // whether the symbol is atomic functor
 inline bool IsAtomic(const std::vector<NodeEntry>& outputs) {
-  return outputs[0].node->inputs.size() == 0;
+  return outputs[0].node->inputs.size() == 0 &&
+      outputs[0].node->control_deps.size() == 0;
 }
 
 // public functions
@@ -118,7 +119,9 @@ Symbol Symbol::Copy() const {
 }
 
 void Symbol::Print(std::ostream &os) const {
-  if (outputs.size() == 1 && outputs[0].node->inputs.size() == 0) {
+  if (outputs.size() == 1 &&
+      outputs[0].node->inputs.size() == 0 &&
+      outputs[0].node->control_deps.size() == 0) {
     if (outputs[0].node->is_variable()) {
       os << "Variable:" << outputs[0].node->attrs.name << '\n';
     } else {
diff --git a/nnvm/src/pass/gradient.cc b/nnvm/src/pass/gradient.cc
index 0f3f57fd7cf4..c6460cab3ad0 100644
--- a/nnvm/src/pass/gradient.cc
+++ b/nnvm/src/pass/gradient.cc
@@ -69,6 +69,7 @@ Graph Gradient(Graph src) {
   // topo sort
   std::vector<NodePtr> topo_order;
   std::unordered_map<Node*, std::vector<GradEntry> > output_grads;
+
   DFSVisit(ys, [&](const NodePtr& node) {
       if (output_grads.count(node.get()) == 0) {
         output_grads[node.get()].resize(node->num_outputs());
@@ -113,13 +114,15 @@ Graph Gradient(Graph src) {
       e.sum = agg_fun(std::move(e.grads));
       out_agg_grads.push_back(e.sum);
     }
-    std::vector<NodeEntry> input_grads = grad_fun_map[ptr->op()]
-        (mirror_map.size() == 0 ? ptr : mirror_map.at(ptr.get()), out_agg_grads);
-    CHECK_EQ((*rit)->inputs.size(), input_grads.size())
-        << "Gradient function not returning enough gradient";
-    auto git = input_grads.begin();
-    for (auto it = (*rit)->inputs.begin(); it != (*rit)->inputs.end(); ++it, ++git) {
-      output_grads[it->node.get()][it->index].grads.emplace_back(std::move(*git));
+    if ((*rit)->inputs.size() != 0) {
+      std::vector<NodeEntry> input_grads = grad_fun_map[ptr->op()]
+          (mirror_map.size() == 0 ? ptr : mirror_map.at(ptr.get()), out_agg_grads);
+      CHECK_EQ((*rit)->inputs.size(), input_grads.size())
+          << "Gradient function not returning enough gradient";
+      auto git = input_grads.begin();
+      for (auto it = (*rit)->inputs.begin(); it != (*rit)->inputs.end(); ++it, ++git) {
+        output_grads[it->node.get()][it->index].grads.emplace_back(std::move(*git));
+      }
     }
   }
   // take out the xs' grads
diff --git a/nnvm/tests/python/test_graph.py b/nnvm/tests/python/test_graph.py
index 9424a2ec66cc..bdf4a6aaca52 100644
--- a/nnvm/tests/python/test_graph.py
+++ b/nnvm/tests/python/test_graph.py
@@ -42,8 +42,8 @@ def test_list_args():
     y = sym.add(y, z, name='add1')
     # write after read
     z = sym.assign(x, y, name='assign')
-    assert z.list_inputs('read_only') == ['conv_weight', 'z']
-    assert z.list_inputs('aux_state') == ['x']
+    assert z.list_input_names('read_only') == ['conv_weight', 'z']
+    assert z.list_input_names('aux_state') == ['x']
 
 def test_infer_shape():
     x = sym.Variable('x', shape=(4, 2))
diff --git a/nnvm/tests/python/test_symbol.py b/nnvm/tests/python/test_symbol.py
index 989cd99e730f..915ece3da69d 100644
--- a/nnvm/tests/python/test_symbol.py
+++ b/nnvm/tests/python/test_symbol.py
@@ -7,17 +7,19 @@ def test_compose():
     y = sym.exp(sym.add(x, x, name='add', gpu=2),
                 name='exp', gpu=1, attr={"kk": "1"})
 
-    assert y.list_inputs() == ['x']
-    assert y.list_outputs() == ["exp_output"]
+    assert y.list_input_names() == ['x']
+    assert y.list_output_names() == ["exp_output"]
     assert y.list_attr()['gpu'] == '1'
     z = y.get_internals()
-    assert z['add_output'].list_outputs() == ['add_output']
+    assert z['add_output'].list_output_names() == ['add_output']
     assert y.list_attr(recursive=True)['add_gpu'] == '2'
 
 def test_default_input():
     x = sym.Variable('x')
     y = sym.conv2d(data=x, name='conv')
-    assert y.list_inputs() == ['x', 'conv_weight']
+    assert y.list_input_names() == ['x', 'conv_weight']
+    tname = [z.list_output_names()[0] for z in y.list_input_variables()]
+    assert tname == y.list_input_names()
     try:
         z = sym.add(x)
         assert False

From 0e0a281b0ffd1932608a918f9e0f412b0e804a50 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 28 Sep 2016 20:25:51 -0700
Subject: [PATCH 389/949] Fix attr (#62)

---
 nnvm/python/nnvm/symbol.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/python/nnvm/symbol.py b/nnvm/python/nnvm/symbol.py
index 79c4b7ec4626..a39d9803ab01 100644
--- a/nnvm/python/nnvm/symbol.py
+++ b/nnvm/python/nnvm/symbol.py
@@ -139,7 +139,7 @@ def attr(self, key):
         ret = _ctypes.c_char_p()
         success = _ctypes.c_int()
         _check_call(_LIB.NNSymbolGetAttr(
-            self.handle, c_str(key), _ctypes.byref(ret), _ctypes.byref(success)))
+            self.handle, _base.c_str(key), _ctypes.byref(ret), _ctypes.byref(success)))
         if success.value != 0:
             return _base.py_str(ret.value)
         else:

From de867959a76d9d1ab681d6f6b90ba992f1a6a4a6 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 29 Sep 2016 21:24:27 -0700
Subject: [PATCH 390/949] Update README.md

---
 nnvm/README.md | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/nnvm/README.md b/nnvm/README.md
index 06d072181f9b..9433cae3b09b 100644
--- a/nnvm/README.md
+++ b/nnvm/README.md
@@ -25,6 +25,9 @@ for neural nets and computation graphs generation and optimizations.
 
 See [Overview](docs/overview.md) for an introduction on what it provides.
 
+## Example
+See [TinyFlow](https://github.com/tqchen/tinyflow) on how you can build a TensorFlow API with NNVM and Torch.
+ 
 ## Why build learning system by parts
 
 This is essentially ***Unix philosophy*** applied to machine learning system.

From e1478c68ef9c4f84f94a20eea4aecd2296a287f9 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 6 Oct 2016 12:07:14 -0700
Subject: [PATCH 391/949] Fix Plan memory when multiple inplace option is
 available (#67)

---
 nnvm/src/pass/plan_memory.cc | 1 +
 1 file changed, 1 insertion(+)

diff --git a/nnvm/src/pass/plan_memory.cc b/nnvm/src/pass/plan_memory.cc
index f77e70085fbc..317fef0275bf 100644
--- a/nnvm/src/pass/plan_memory.cc
+++ b/nnvm/src/pass/plan_memory.cc
@@ -174,6 +174,7 @@ Graph PlanMemory(Graph ret) {
         uint32_t eid_out = idx.entry_id(nid, kv.second);
         uint32_t eid_in = idx.entry_id(inode.inputs[kv.first]);
         if (ref_count[eid_in] == 1 &&
+            storage[eid_out] == GraphAllocator::kBadStorageID &&
             storage[eid_in] != GraphAllocator::kBadStorageID &&
             shape_vec[eid_out].Size() == shape_vec[eid_in].Size() &&
             dtype_vec[eid_out] == dtype_vec[eid_in]) {

From 169e91419f904869176636d98eff22d03596d11b Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 8 Oct 2016 10:41:03 -0700
Subject: [PATCH 392/949] [PlanMemory]Avoid inplace for kNullop requests (#68)

---
 nnvm/src/pass/plan_memory.cc | 1 +
 1 file changed, 1 insertion(+)

diff --git a/nnvm/src/pass/plan_memory.cc b/nnvm/src/pass/plan_memory.cc
index 317fef0275bf..9d2a01e37d4f 100644
--- a/nnvm/src/pass/plan_memory.cc
+++ b/nnvm/src/pass/plan_memory.cc
@@ -174,6 +174,7 @@ Graph PlanMemory(Graph ret) {
         uint32_t eid_out = idx.entry_id(nid, kv.second);
         uint32_t eid_in = idx.entry_id(inode.inputs[kv.first]);
         if (ref_count[eid_in] == 1 &&
+            ref_count[eid_out] != 0 &&
             storage[eid_out] == GraphAllocator::kBadStorageID &&
             storage[eid_in] != GraphAllocator::kBadStorageID &&
             shape_vec[eid_out].Size() == shape_vec[eid_in].Size() &&

From 1f2382641069ab27c8220dcd77714ef024b2abee Mon Sep 17 00:00:00 2001
From: Hu Shiwen <yajiedesign@gmail.com>
Date: Tue, 11 Oct 2016 12:49:35 +0800
Subject: [PATCH 393/949] error info (#69)

---
 nnvm/src/pass/infer_shape_type.cc | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index d9f1a0ebe34f..fd79190ea042 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -119,7 +119,11 @@ Graph InferAttr(Graph &&ret,
           << "Attribute " << infer_name
           << " is not registed by op " << inode.source->op()->name;
         // Call inference function of the operator.
-        forward_known = finfer(inode.source->attrs, &ishape, &oshape);
+        try {
+          forward_known = finfer(inode.source->attrs, &ishape, &oshape);
+        } catch (const std::exception& e) {
+          throw dmlc::Error(e.what() + std::string(" with ") + inode.source->attrs.name);
+        }
       }
       // Save to the result map.
       for (uint32_t i = 0; i < num_inputs; ++i) {

From 2ce6ac38cd2f33eb67fb84affe75e07e51c772ae Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 12 Oct 2016 15:23:52 -0700
Subject: [PATCH 394/949] Change license file to be detectable by github (#72)

---
 nnvm/LICENSE | 208 ++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 198 insertions(+), 10 deletions(-)

diff --git a/nnvm/LICENSE b/nnvm/LICENSE
index 3908a9e0dbed..3163d9248efc 100644
--- a/nnvm/LICENSE
+++ b/nnvm/LICENSE
@@ -1,13 +1,201 @@
-Copyright (c) 2016 by Contributors
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
 
-Licensed 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
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
 
-   http://www.apache.org/licenses/LICENSE-2.0
+   1. Definitions.
 
-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.
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2016 by contributors.
+
+   Licensed 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.

From 89d5bcd871fb4820d5e18b4387cc5173413c9283 Mon Sep 17 00:00:00 2001
From: Ziheng Jiang <jzhtomas@gmail.com>
Date: Sat, 29 Oct 2016 01:00:46 +0800
Subject: [PATCH 395/949] add rpow & fix 'Number' to 'Number_' (#76)

---
 nnvm/python/nnvm/symbol.py | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/nnvm/python/nnvm/symbol.py b/nnvm/python/nnvm/symbol.py
index a39d9803ab01..f22d32e0b920 100644
--- a/nnvm/python/nnvm/symbol.py
+++ b/nnvm/python/nnvm/symbol.py
@@ -43,13 +43,13 @@ def __radd__(self, other):
     def __sub__(self, other):
         if isinstance(other, Symbol):
             return _internal.__sub_symbol__(self, other)
-        if isinstance(other, Number):
+        if isinstance(other, _Number):
             return _internal.__sub_scalar__(self, scalar=other)
         else:
             raise TypeError('type %s not supported' % str(type(other)))
 
     def __rsub__(self, other):
-        if isinstance(other, Number):
+        if isinstance(other, _Number):
             return _internal.__rsub_scalar__(self, scalar=other)
         else:
             raise TypeError('type %s not supported' % str(type(other)))
@@ -68,13 +68,13 @@ def __rmul__(self, other):
     def __div__(self, other):
         if isinstance(other, Symbol):
             return _internal.__div_symbol__(self, other)
-        if isinstance(other, Number):
+        if isinstance(other, _Number):
             return _internal.__div_scalar__(self, scalar=other)
         else:
             raise TypeError('type %s not supported' % str(type(other)))
 
     def __rdiv__(self, other):
-        if isinstance(other, Number):
+        if isinstance(other, _Number):
             return _internal.__rdiv_scalar__(self, scalar=other)
         else:
             raise TypeError('type %s not supported' % str(type(other)))
@@ -88,11 +88,17 @@ def __rtruediv__(self, other):
     def __pow__(self, other):
         if isinstance(other, Symbol):
             return _internal.__pow_symbol__(self, other)
-        if isinstance(other, Number):
+        if isinstance(other, _Number):
             return _internal.__pow_scalar__(self, scalar=other)
         else:
             raise TypeError('type %s not supported' % str(type(other)))
 
+    def __rpow__(self, other):
+        if isinstance(other, _Number):
+            return _internal.__rpow_scalar__(self, scalar=other)
+        else:
+            raise TypeError('type %s not supported' % str(type(other)))
+
     def __neg__(self):
         return self.__mul__(-1.0)
 

From bf9a3572b8fdac0fbbb5b0c28ff2bf09b419c789 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 18 Nov 2016 15:36:15 -0800
Subject: [PATCH 396/949] also save graph attributes (#78)

---
 nnvm/src/pass/saveload_json.cc  |  1 +
 nnvm/tests/python/test_graph.py | 15 +++++++++++++++
 2 files changed, 16 insertions(+)

diff --git a/nnvm/src/pass/saveload_json.cc b/nnvm/src/pass/saveload_json.cc
index 2200a4b6b21f..3d0b56843aa8 100644
--- a/nnvm/src/pass/saveload_json.cc
+++ b/nnvm/src/pass/saveload_json.cc
@@ -199,6 +199,7 @@ Graph LoadJSON(Graph src) {
 // save a graph to json
 Graph SaveJSON(Graph src) {
   JSONGraph jgraph;
+  jgraph.attrs = src.attrs;
   std::unordered_map<Node*, uint32_t> node2index;
   jgraph.node_row_ptr.push_back(0);
   DFSVisit(src.outputs, [&node2index, &jgraph](const NodePtr& n) {
diff --git a/nnvm/tests/python/test_graph.py b/nnvm/tests/python/test_graph.py
index bdf4a6aaca52..702f37bd38e0 100644
--- a/nnvm/tests/python/test_graph.py
+++ b/nnvm/tests/python/test_graph.py
@@ -11,6 +11,20 @@ def test_json_pass():
     g2 = ret.apply('LoadJSON')
     assert g2.apply('SaveJSON').json_attr('json') == ret.json_attr('json')
 
+
+def test_json_pass_with_attr():
+    x = sym.Variable('x')
+    y = sym.conv2d(data=x, name='conv', stride=(2,2))
+    g = graph.create(y)
+    g._set_json_attr('version', '0.1.0')
+    ret = g.apply('SaveJSON')
+    json_str = ret.json_attr('json')
+    print(json_str)
+    ret._set_json_attr('json', json_str)
+    g2 = ret.apply('LoadJSON')
+    assert g2.json_attr('version') == '0.1.0'
+
+
 def test_graph_json_attr():
     x = sym.Variable('x')
     y = sym.conv2d(data=x, name='conv', stride=(2,2))
@@ -129,6 +143,7 @@ def test_plan_memory():
 
 
 if __name__ == "__main__":
+    test_json_pass_with_attr()
     test_order_mutation_pass()
     test_graph_json_attr()
     test_json_pass()

From 5e9f9881a659bd19071846d3d4fee27008c71d22 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 20 Nov 2016 22:02:36 -0800
Subject: [PATCH 397/949] Enable optional dependency memory and attr hint (#79)

* Enable optional dependency memory and attr hint

* fix travis
---
 nnvm/include/nnvm/op_attr_types.h  | 12 ++++++++++++
 nnvm/include/nnvm/pass_functions.h | 10 +++++++++-
 nnvm/src/pass/gradient.cc          | 26 +++++++++++++++++++++++---
 nnvm/src/pass/plan_memory.cc       | 26 ++++++++++++++++++++++++--
 nnvm/tests/travis/setup.sh         |  6 +++---
 5 files changed, 71 insertions(+), 9 deletions(-)

diff --git a/nnvm/include/nnvm/op_attr_types.h b/nnvm/include/nnvm/op_attr_types.h
index 220c1c11943a..38ecd2b38d3d 100644
--- a/nnvm/include/nnvm/op_attr_types.h
+++ b/nnvm/include/nnvm/op_attr_types.h
@@ -136,6 +136,18 @@ using FBackwardInGradIndex = std::function<
 using FInplaceOption = std::function<
   std::vector<std::pair<int, int> > (const NodeAttrs& attrs)>;
 
+/*!
+ * \brief Get list of inputs in the op whose content are actually not used by the operator
+ *  These are dummy input that can be used for example in zeros_like, ones_like.
+ *
+ * \param attrs The attributes of the node
+ * \return list input index that are not used by the operator.
+ *
+ * \note Register under "FIgnoreInputs".
+ */
+using FIgnoreInputs = std::function<
+  std::vector<uint32_t> (const NodeAttrs& attrs)>;
+
 /*!
  * \brief Get the gradient node of the op node
  *  This function generates the backward graph of the node
diff --git a/nnvm/include/nnvm/pass_functions.h b/nnvm/include/nnvm/pass_functions.h
index 68125ebf24bd..1498f071e9d1 100644
--- a/nnvm/include/nnvm/pass_functions.h
+++ b/nnvm/include/nnvm/pass_functions.h
@@ -129,6 +129,7 @@ inline Graph PlaceDevice(Graph graph,
  * \param ys_out_grad The symbol for additional gradient to be propagate back to y.
  * \param aggregate_fun Aggregation function applied to aggregate the inputs.
  * \param mirror_fun Optional mirror function to do mirror optimization and save memory.
+ * \param attr_hint_fun Optional, hint function to output a node that like src, but its attr is same as like.
  * \return A new graph, whose outputs correspond to inputs of xs.
  */
 inline Graph Gradient(
@@ -137,7 +138,9 @@ inline Graph Gradient(
     std::vector<NodeEntry> xs,
     std::vector<NodeEntry> ys_out_grad,
     std::function<NodeEntry(std::vector<NodeEntry>&& inputs)> aggregate_fun = nullptr,
-    std::function<int(const Node& node)> mirror_fun = nullptr) {
+    std::function<int(const Node& node)> mirror_fun = nullptr,
+    std::function<NodeEntry(const NodeEntry& src, const NodeEntry &like)>
+    attr_hint_fun = nullptr) {
   graph.attrs["grad_ys"] = std::make_shared<any>(std::move(ys));
 
   graph.attrs["grad_xs"] = std::make_shared<any>(std::move(xs));
@@ -145,10 +148,15 @@ inline Graph Gradient(
   if (aggregate_fun != nullptr) {
     graph.attrs["grad_aggregate_fun"] = std::make_shared<any>(aggregate_fun);
   }
+
   if (mirror_fun != nullptr) {
     graph.attrs["grad_mirror_fun"] = std::make_shared<any>(mirror_fun);
   }
 
+  if (attr_hint_fun != nullptr) {
+    graph.attrs["attr_hint_fun"] = std::make_shared<any>(attr_hint_fun);
+  }
+
   return ApplyPass(std::move(graph), "Gradient");
 }
 
diff --git a/nnvm/src/pass/gradient.cc b/nnvm/src/pass/gradient.cc
index c6460cab3ad0..2318763da362 100644
--- a/nnvm/src/pass/gradient.cc
+++ b/nnvm/src/pass/gradient.cc
@@ -43,6 +43,7 @@ struct GradEntry {
 Graph Gradient(Graph src) {
   using nnvm::FGradient;
   using MirrorFun = std::function<int (const Node& node)>;
+  using AttrHintFun = std::function<NodeEntry (const NodeEntry& src, const NodeEntry &like)>;
 
   CHECK_NE(src.attrs.count("grad_ys"), 0)
       << "Gradient require grad_ys to be presented.";
@@ -65,6 +66,10 @@ Graph Gradient(Graph src) {
   if (src.attrs.count("grad_mirror_fun") != 0) {
     mirror_fun = src.GetAttr<MirrorFun>("grad_mirror_fun");
   }
+  AttrHintFun attr_hint_fun = nullptr;
+  if (src.attrs.count("attr_hint_fun") != 0) {
+    attr_hint_fun = src.GetAttr<AttrHintFun>("attr_hint_fun");
+  }
 
   // topo sort
   std::vector<NodePtr> topo_order;
@@ -79,7 +84,11 @@ Graph Gradient(Graph src) {
 
   CHECK_EQ(ys.size(), ys_out_grad.size());
   for (size_t i = 0; i < ys.size(); ++i) {
-    output_grads[ys[i].node.get()][ys[i].index].grads = { ys_out_grad[i] };
+    NodeEntry ograd = ys_out_grad[i];
+    if (attr_hint_fun != nullptr) {
+      ograd = attr_hint_fun(ograd, ys[i]);
+    }
+    output_grads[ys[i].node.get()][ys[i].index].grads = { ograd };
   }
 
   // construct mirror reduece memory strategy if needed
@@ -105,6 +114,8 @@ Graph Gradient(Graph src) {
 
   // traverse backward
   static auto& grad_fun_map = Op::GetAttr<FGradient>("FGradient");
+  static auto& finfer_shape = Op::GetAttr<FInferShape>("FInferShape");
+
   std::vector<NodeEntry> out_agg_grads;
   for (auto rit = topo_order.rbegin(); rit != topo_order.rend(); ++rit) {
     const NodePtr& ptr = *rit;
@@ -115,8 +126,17 @@ Graph Gradient(Graph src) {
       out_agg_grads.push_back(e.sum);
     }
     if ((*rit)->inputs.size() != 0) {
-      std::vector<NodeEntry> input_grads = grad_fun_map[ptr->op()]
-          (mirror_map.size() == 0 ? ptr : mirror_map.at(ptr.get()), out_agg_grads);
+      NodePtr fwd_node = (mirror_map.size() == 0 ? ptr : mirror_map.at(ptr.get()));
+      std::vector<NodeEntry> input_grads = grad_fun_map[ptr->op()](
+          fwd_node, out_agg_grads);
+
+      if (attr_hint_fun != nullptr) {
+        // only insert hint when shape inference function is not available.
+        for (size_t i = 0; i < input_grads.size(); ++i) {
+          if (finfer_shape.count(input_grads[i].node->op())) continue;
+          input_grads[i] = attr_hint_fun(input_grads[i], fwd_node->inputs[i]);
+        }
+      }
       CHECK_EQ((*rit)->inputs.size(), input_grads.size())
           << "Gradient function not returning enough gradient";
       auto git = input_grads.begin();
diff --git a/nnvm/src/pass/plan_memory.cc b/nnvm/src/pass/plan_memory.cc
index 9d2a01e37d4f..d961df905f05 100644
--- a/nnvm/src/pass/plan_memory.cc
+++ b/nnvm/src/pass/plan_memory.cc
@@ -137,13 +137,25 @@ class GraphAllocator {
 Graph PlanMemory(Graph ret) {
   // setup ref counter
   const IndexedGraph& idx = ret.indexed_graph();
+
+  static auto& fignore_inputs = Op::GetAttr<FIgnoreInputs>("FIgnoreInputs");
   // reference counter of each node
   std::vector<uint32_t> ref_count(idx.num_node_entries(), 0);
   // step 1: initialize reference count
   for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
-    for (const auto& e : idx[nid].inputs) {
+    const auto& inode = idx[nid];
+    if (inode.source->is_variable()) continue;
+    for (const auto& e : inode.inputs) {
       ++ref_count[idx.entry_id(e)];
     }
+    // no dataflow dependency is needed for those are ignored.
+    // revoke the dependency counter.
+    if (fignore_inputs.count(inode.source->op()) != 0) {
+      auto ignore_inputs = fignore_inputs[inode.source->op()](inode.source->attrs);
+      for (uint32_t i : ignore_inputs) {
+        --ref_count[idx.entry_id(inode.inputs[i])];
+      }
+    }
   }
   for (const auto& e : idx.outputs()) {
     ++ref_count[idx.entry_id(e)];
@@ -195,8 +207,18 @@ Graph PlanMemory(Graph ret) {
         storage[eid] = allocator.Request(dev_id, dtype_vec[eid], shape_vec[eid], nid);
       }
     }
+
+    // check if certain inputs is ignored.
+    std::vector<uint32_t> ignore_inputs;
+    if (fignore_inputs.count(inode.source->op()) != 0) {
+      ignore_inputs = fignore_inputs[inode.source->op()](inode.source->attrs);
+      std::sort(ignore_inputs.begin(), ignore_inputs.end());
+    }
     // then free inputs
-    for (const auto& e : inode.inputs) {
+    for (size_t i = 0; i < inode.inputs.size(); ++i) {
+      // ref counter of ignored input is already decreased.
+      if (std::binary_search(ignore_inputs.begin(), ignore_inputs.end(), i)) continue;
+      const auto& e = inode.inputs[i];
       uint32_t eid = idx.entry_id(e);
       // temp_ref_count == 0 means it is taken by inplace op
       if (ref_count[eid] == 0) continue;
diff --git a/nnvm/tests/travis/setup.sh b/nnvm/tests/travis/setup.sh
index 42dfc53bc66a..88010a0b3d7e 100755
--- a/nnvm/tests/travis/setup.sh
+++ b/nnvm/tests/travis/setup.sh
@@ -5,11 +5,11 @@ if [ ${TRAVIS_OS_NAME} == "osx" ]; then
     brew update
     brew install python3
     if [ ${TASK} == "python_test" ]; then
-        python -m pip install nose numpy cython --user `whoami`
-        python3 -m pip install nose numpy cython --user `whoami`
+        python -m pip install --user nose numpy cython
+        python3 -m pip install --user nose numpy cython
     fi
 fi
 
 if [ ${TASK} == "lint" ]; then
-    pip install cpplint 'pylint==1.4.4' 'astroid==1.3.6' --user `whoami`
+    pip install --user cpplint 'pylint==1.4.4' 'astroid==1.3.6'
 fi

From 272dd98eb138e1502bc8cabc71bfdc468802f340 Mon Sep 17 00:00:00 2001
From: Ziheng Jiang <jzhtomas@gmail.com>
Date: Mon, 21 Nov 2016 14:03:48 +0800
Subject: [PATCH 398/949] add make/config.mk; add nnvm-fusion into plugin as a
 submodule (#80)

---
 nnvm/Makefile       | 35 +++++++++++++++++++++++++++++++----
 nnvm/make/config.mk | 42 ++++++++++++++++++++++++++++++++++++++++++
 plugin/nnvm-fusion  |  1 +
 3 files changed, 74 insertions(+), 4 deletions(-)
 create mode 100644 nnvm/make/config.mk
 create mode 160000 plugin/nnvm-fusion

diff --git a/nnvm/Makefile b/nnvm/Makefile
index e07704f499f0..48e03e0f4d17 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -1,15 +1,38 @@
+ROOTDIR = $(CURDIR)
+
+ifndef config
+ifneq ("$(wildcard ./config.mk)", "")
+	config = config.mk
+else
+	config = make/config.mk
+endif
+endif
+include $(config)
+
 export LDFLAGS = -pthread -lm
 export CFLAGS =  -std=c++11 -Wall -O2 -msse2  -Wno-unknown-pragmas -funroll-loops\
 	 -Iinclude -fPIC
 
+ifneq ($(ADD_CFLAGS), NONE)
+	CFLAGS += $(ADD_CFLAGS)
+endif
+
+ifneq ($(ADD_LDFLAGS), NONE)
+	LFFLAGS += $(ADD_LDFLAGS)
+endif
+
+# plugin
+PLUGIN_OBJ =
+include $(NNVM_PLUGINS)
+
 # specify tensor path
 .PHONY: clean all test lint doc cython cython3 cyclean
 
 all: lib/libnnvm.a lib/libnnvm_example.so
 
 SRC = $(wildcard src/*.cc src/*/*.cc)
-ALL_OBJ = $(patsubst src/%.cc, build/%.o, $(SRC))
-ALL_DEP = $(ALL_OBJ)
+ALL_OBJ = $(patsubst %.cc, build/%.o, $(SRC))
+ALL_DEP = $(ALL_OBJ) $(PLUGIN_OBJ)
 
 UNAME_S := $(shell uname -s)
 
@@ -26,11 +49,15 @@ include tests/cpp/unittest.mk
 
 test: $(TEST)
 
-build/%.o: src/%.cc
+build/src/%.o: src/%.cc
 	@mkdir -p $(@D)
-	$(CXX) $(CFLAGS) -MM -MT build/$*.o $< >build/$*.d
+	$(CXX) $(CFLAGS) -MM -MT build/src/$*.o $< >build/src/$*.d
 	$(CXX) -c $(CFLAGS) -c $< -o $@
 
+build/plugin/%.o: plugin/%.cc
+	@mkdir -p $(@D)
+	$(CXX) $(CFLAGS) -MM -MT build/plugin/$*.o $< >build/plugin/$*.d
+	$(CXX) -c $(CFLAGS) -c $< -o $@
 
 lib/libnnvm.a: $(ALL_DEP)
 	@mkdir -p $(@D)
diff --git a/nnvm/make/config.mk b/nnvm/make/config.mk
new file mode 100644
index 000000000000..92dc67c86d5a
--- /dev/null
+++ b/nnvm/make/config.mk
@@ -0,0 +1,42 @@
+#-------------------------------------------------------------------------------
+#  Template configuration for compiling nnvm
+#
+#  If you want to change the configuration, please use the following
+#  steps. Assume you are on the root directory of nnvm. First copy the this
+#  file so that any local changes will be ignored by git
+#
+#  $ cp make/config.mk .
+#
+#  Next modify the according entries, and then compile by
+#
+#  $ make
+#
+#  or build in parallel with 8 threads
+#
+#  $ make -j8
+#-------------------------------------------------------------------------------
+
+#---------------------
+# choice of compiler
+#--------------------
+
+export CC = gcc
+export CXX = g++
+export NVCC = nvcc
+
+# the additional link flags you want to add
+ADD_LDFLAGS =
+
+# the additional compile flags you want to add
+ADD_CFLAGS =
+
+#----------------------------
+# plugins
+#----------------------------
+
+# whether to use fusion integration. This requires installing cuda.
+# ifndef CUDA_PATH
+# 	CUDA_PATH = /usr/local/cuda
+# endif
+# NNVM_FUSION_PATH = plugin/nnvm-fusion
+# NNVM_PLUGINS += $(NNVM_FUSION_PATH)/nnvm-fusion.mk
diff --git a/plugin/nnvm-fusion b/plugin/nnvm-fusion
new file mode 160000
index 000000000000..14593fb85075
--- /dev/null
+++ b/plugin/nnvm-fusion
@@ -0,0 +1 @@
+Subproject commit 14593fb85075ac8c049ca6faf1fee70fda28851a

From 706e8c08b954fd1ba7ba5d95c88e012c70851e88 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 20 Nov 2016 22:54:58 -0800
Subject: [PATCH 399/949] do hint insertion after aggregation (#81)

---
 nnvm/Makefile             |  2 +-
 nnvm/make/config.mk       |  6 ++----
 nnvm/src/pass/gradient.cc | 29 ++++++++++++++++-------------
 3 files changed, 19 insertions(+), 18 deletions(-)

diff --git a/nnvm/Makefile b/nnvm/Makefile
index 48e03e0f4d17..e73cba047db0 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -10,7 +10,7 @@ endif
 include $(config)
 
 export LDFLAGS = -pthread -lm
-export CFLAGS =  -std=c++11 -Wall -O2 -msse2  -Wno-unknown-pragmas -funroll-loops\
+export CFLAGS = -std=c++11 -Wall -O2 -msse2  -Wno-unknown-pragmas -funroll-loops\
 	 -Iinclude -fPIC
 
 ifneq ($(ADD_CFLAGS), NONE)
diff --git a/nnvm/make/config.mk b/nnvm/make/config.mk
index 92dc67c86d5a..b0989b425e50 100644
--- a/nnvm/make/config.mk
+++ b/nnvm/make/config.mk
@@ -20,15 +20,13 @@
 # choice of compiler
 #--------------------
 
-export CC = gcc
-export CXX = g++
 export NVCC = nvcc
 
 # the additional link flags you want to add
-ADD_LDFLAGS =
+ADD_LDFLAGS=
 
 # the additional compile flags you want to add
-ADD_CFLAGS =
+ADD_CFLAGS=
 
 #----------------------------
 # plugins
diff --git a/nnvm/src/pass/gradient.cc b/nnvm/src/pass/gradient.cc
index 2318763da362..80df90389d6c 100644
--- a/nnvm/src/pass/gradient.cc
+++ b/nnvm/src/pass/gradient.cc
@@ -38,6 +38,7 @@ struct GradEntry {
   NodeEntry sum{nullptr, 0, 0};
 #endif
   std::vector<NodeEntry> grads;
+  bool need_attr_hint{true};
 };
 
 Graph Gradient(Graph src) {
@@ -85,9 +86,6 @@ Graph Gradient(Graph src) {
   CHECK_EQ(ys.size(), ys_out_grad.size());
   for (size_t i = 0; i < ys.size(); ++i) {
     NodeEntry ograd = ys_out_grad[i];
-    if (attr_hint_fun != nullptr) {
-      ograd = attr_hint_fun(ograd, ys[i]);
-    }
     output_grads[ys[i].node.get()][ys[i].index].grads = { ograd };
   }
 
@@ -121,27 +119,29 @@ Graph Gradient(Graph src) {
     const NodePtr& ptr = *rit;
     if (ptr->is_variable()) continue;
     out_agg_grads.clear();
-    for (GradEntry& e : output_grads.at(ptr.get())) {
+    auto& out_grad_vec = output_grads.at(ptr.get());
+    for (uint32_t i = 0; i < out_grad_vec.size(); ++i) {
+      GradEntry& e = out_grad_vec[i];
       e.sum = agg_fun(std::move(e.grads));
+      if (e.need_attr_hint && attr_hint_fun != nullptr) {
+        e.sum = attr_hint_fun(e.sum, NodeEntry{ptr, 0, i});
+      }
       out_agg_grads.push_back(e.sum);
     }
     if ((*rit)->inputs.size() != 0) {
       NodePtr fwd_node = (mirror_map.size() == 0 ? ptr : mirror_map.at(ptr.get()));
       std::vector<NodeEntry> input_grads = grad_fun_map[ptr->op()](
           fwd_node, out_agg_grads);
-
-      if (attr_hint_fun != nullptr) {
-        // only insert hint when shape inference function is not available.
-        for (size_t i = 0; i < input_grads.size(); ++i) {
-          if (finfer_shape.count(input_grads[i].node->op())) continue;
-          input_grads[i] = attr_hint_fun(input_grads[i], fwd_node->inputs[i]);
-        }
-      }
       CHECK_EQ((*rit)->inputs.size(), input_grads.size())
           << "Gradient function not returning enough gradient";
       auto git = input_grads.begin();
       for (auto it = (*rit)->inputs.begin(); it != (*rit)->inputs.end(); ++it, ++git) {
-        output_grads[it->node.get()][it->index].grads.emplace_back(std::move(*git));
+        auto& ge = output_grads[it->node.get()][it->index];
+        // if any of the backward op can do shape inference, the hint is not necessary.
+        if (finfer_shape.count(git->node->op())) {
+          ge.need_attr_hint = false;
+        }
+        ge.grads.emplace_back(std::move(*git));
       }
     }
   }
@@ -153,6 +153,9 @@ Graph Gradient(Graph src) {
     // aggregate sum if there haven't been
     if (entry.sum.node.get() == nullptr) {
       entry.sum = agg_fun(std::move(entry.grads));
+      if (entry.need_attr_hint && attr_hint_fun != nullptr) {
+        entry.sum = attr_hint_fun(entry.sum, e);
+      }
     }
     ret.outputs.emplace_back(std::move(entry.sum));
   }

From 31baac83d71ce8512610cef290abecdd7946bef4 Mon Sep 17 00:00:00 2001
From: Ziheng Jiang <jzhtomas@gmail.com>
Date: Tue, 22 Nov 2016 01:09:32 +0800
Subject: [PATCH 400/949] update nnvm-fusion (#82)

---
 plugin/nnvm-fusion | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/plugin/nnvm-fusion b/plugin/nnvm-fusion
index 14593fb85075..86853a73e93f 160000
--- a/plugin/nnvm-fusion
+++ b/plugin/nnvm-fusion
@@ -1 +1 @@
-Subproject commit 14593fb85075ac8c049ca6faf1fee70fda28851a
+Subproject commit 86853a73e93fdbcdaec6fd3eda39e8f11d554c92

From 714e71430d5d9b7053437b9a0323dd5caa3d7d5b Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 21 Nov 2016 20:30:00 -0800
Subject: [PATCH 401/949] enable shape inference with hint func (#84)

---
 nnvm/src/pass/infer_shape_type.cc | 28 ++++++++++++++++------------
 1 file changed, 16 insertions(+), 12 deletions(-)

diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index fd79190ea042..644adb066603 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -58,7 +58,7 @@ Graph InferAttr(Graph &&ret,
   std::vector<AttrType> ishape, oshape;
 
   // inference step function for nid
-  auto infer_step = [&](uint32_t nid) {
+  auto infer_step = [&](uint32_t nid, bool last_iter) {
     const auto& inode = idx[nid];
     const uint32_t num_inputs = inode.inputs.size();
     const uint32_t num_outputs = inode.source->num_outputs();
@@ -113,16 +113,20 @@ Graph InferAttr(Graph &&ret,
         oshape[i] = rshape[idx.entry_id(nid, i)];
         if (fis_none(oshape[i])) forward_known = false;
       }
+      auto finfer = finfer_shape.get(inode.source->op(), fdefault);
       if (!forward_known) {
-        auto finfer = finfer_shape.get(inode.source->op(), fdefault);
-        CHECK(finfer != nullptr)
-          << "Attribute " << infer_name
-          << " is not registed by op " << inode.source->op()->name;
-        // Call inference function of the operator.
-        try {
-          forward_known = finfer(inode.source->attrs, &ishape, &oshape);
-        } catch (const std::exception& e) {
-          throw dmlc::Error(e.what() + std::string(" with ") + inode.source->attrs.name);
+        if (finfer != nullptr) {
+          // Call inference function of the operator.
+          try {
+            forward_known = finfer(inode.source->attrs, &ishape, &oshape);
+          } catch (const std::exception& e) {
+            throw dmlc::Error(e.what() + std::string(" with ") + inode.source->attrs.name);
+          }
+        } else {
+          CHECK(!last_iter)
+              << "Attribute " << infer_name
+              << " is not registed by op " << inode.source->op()->name
+              << " we are not able to complete the inference because of this";
         }
       }
       // Save to the result map.
@@ -140,12 +144,12 @@ Graph InferAttr(Graph &&ret,
   for (int i = 0; i < kMaxStep; ++i) {
     if (i % 2 == 0) {
       for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
-        infer_step(nid);
+        infer_step(nid, i + 1 == kMaxStep);
       }
     } else {
       // backward inference
       for (uint32_t i = idx.num_nodes(); i != 0; --i) {
-        infer_step(i - 1);
+        infer_step(i - 1, i + 1 == kMaxStep);
       }
     }
     num_unknown = 0;

From 38cbdcd88a07ad8b9c10413f93c567fa099e270c Mon Sep 17 00:00:00 2001
From: Eric Junyuan Xie <piiswrong@users.noreply.github.com>
Date: Mon, 21 Nov 2016 20:53:36 -0800
Subject: [PATCH 402/949] Fix json parsing behavior (#83)

---
 nnvm/include/dmlc/parameter.h    | 20 ++++++++++++++------
 nnvm/src/core/symbolic.cc        |  2 +-
 nnvm/src/pass/saveload_json.cc   | 13 +++++++++----
 nnvm/tests/python/test_symbol.py |  2 +-
 4 files changed, 25 insertions(+), 12 deletions(-)

diff --git a/nnvm/include/dmlc/parameter.h b/nnvm/include/dmlc/parameter.h
index 0222df60664d..537976b447ed 100644
--- a/nnvm/include/dmlc/parameter.h
+++ b/nnvm/include/dmlc/parameter.h
@@ -63,7 +63,9 @@ enum ParamInitOption {
   /*! \brief allow unknown parameters */
   kAllowUnknown,
   /*! \brief need to match exact parameters */
-  kAllMatch
+  kAllMatch,
+  /*! \brief allow unmatched hidden field with format __*__ */
+  kAllowHidden
 };
 }  // namespace parameter
 /*!
@@ -122,11 +124,11 @@ struct Parameter {
    */
   template<typename Container>
   inline void Init(const Container &kwargs,
-                   parameter::ParamInitOption option = parameter::kAllowUnknown) {
+                   parameter::ParamInitOption option = parameter::kAllowHidden) {
     PType::__MANAGER__()->RunInit(static_cast<PType*>(this),
                                   kwargs.begin(), kwargs.end(),
                                   NULL,
-                                  option == parameter::kAllowUnknown);
+                                  option);
   }
   /*!
    * \brief initialize the parameter by keyword arguments.
@@ -143,7 +145,7 @@ struct Parameter {
     std::vector<std::pair<std::string, std::string> > unknown;
     PType::__MANAGER__()->RunInit(static_cast<PType*>(this),
                                   kwargs.begin(), kwargs.end(),
-                                  &unknown, true);
+                                  &unknown, parameter::kAllowUnknown);
     return unknown;
   }
   /*!
@@ -369,7 +371,7 @@ class ParamManager {
                       RandomAccessIterator begin,
                       RandomAccessIterator end,
                       std::vector<std::pair<std::string, std::string> > *unknown_args,
-                      bool allow_unknown) const {
+                      parameter::ParamInitOption option) const {
     std::set<FieldAccessEntry*> selected_args;
     for (RandomAccessIterator it = begin; it != end; ++it) {
       FieldAccessEntry *e = Find(it->first);
@@ -381,7 +383,13 @@ class ParamManager {
         if (unknown_args != NULL) {
           unknown_args->push_back(*it);
         } else {
-          if (!allow_unknown) {
+          if (option != parameter::kAllowUnknown) {
+            if (option == parameter::kAllowHidden &&
+                it->first.length() > 4 &&
+                it->first.find("__") == 0 &&
+                it->first.rfind("__") == it->first.length()-2) {
+              continue;
+            }
             std::ostringstream os;
             os << "Cannot find argument \'" << it->first << "\', Possible Arguments:\n";
             os << "----------------\n";
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index efb4a7a451f3..1fcec7674e42 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -10,7 +10,7 @@
 namespace nnvm {
 
 namespace symbol_constants {
-const char *kNamespaceSeparator = "_";
+const char *kNamespaceSeparator = "$";
 }  // namespace symbol_constants
 
 // auxililary version attribute in variable.
diff --git a/nnvm/src/pass/saveload_json.cc b/nnvm/src/pass/saveload_json.cc
index 3d0b56843aa8..21ea709b9019 100644
--- a/nnvm/src/pass/saveload_json.cc
+++ b/nnvm/src/pass/saveload_json.cc
@@ -109,10 +109,6 @@ struct JSONNode {
     if (op_type_str != "null") {
       try {
         node->attrs.op = Op::Get(op_type_str);
-        // rebuild attribute parser
-        if (node->op()->attr_parser != nullptr) {
-          node->op()->attr_parser(&(node->attrs));
-        }
       } catch (const dmlc::Error &err) {
         std::ostringstream os;
         os << "Failed loading Op " << node->attrs.name
@@ -163,6 +159,10 @@ Graph LoadJSON(Graph src) {
       << "Load JSON require json to be presented.";
   const std::string &json_str =
       nnvm::get<std::string>(*src.attrs.at("json"));
+  bool no_parse = false;
+  if (src.attrs.count("load_json_no_parse")) {
+    no_parse = nnvm::get<bool>(*src.attrs.at("load_json_no_parse"));
+  }
   std::istringstream is(json_str);
   dmlc::JSONReader reader(&is);
   JSONGraph jgraph;
@@ -179,6 +179,11 @@ Graph LoadJSON(Graph src) {
     for (uint32_t nid : n.control_deps) {
       n.node->control_deps.push_back(jgraph.nodes[nid].node);
     }
+    // rebuild attribute parser
+    if (!no_parse && n.node->op() != nullptr &&
+        n.node->op()->attr_parser != nullptr) {
+      n.node->op()->attr_parser(&(n.node->attrs));
+    }
   }
   // consistent check
   for (uint32_t nid : jgraph.arg_nodes) {
diff --git a/nnvm/tests/python/test_symbol.py b/nnvm/tests/python/test_symbol.py
index 915ece3da69d..ec5821051c86 100644
--- a/nnvm/tests/python/test_symbol.py
+++ b/nnvm/tests/python/test_symbol.py
@@ -12,7 +12,7 @@ def test_compose():
     assert y.list_attr()['gpu'] == '1'
     z = y.get_internals()
     assert z['add_output'].list_output_names() == ['add_output']
-    assert y.list_attr(recursive=True)['add_gpu'] == '2'
+    assert y.list_attr(recursive=True)['add$gpu'] == '2'
 
 def test_default_input():
     x = sym.Variable('x')

From 804ffbf2547a461e07437600c15a5133130e7089 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 24 Nov 2016 17:41:43 -0800
Subject: [PATCH 403/949] Remove backward index, use gradient guessing instead
 (#85)

* Remove backward index, use gradient guessing instead

* minor fix

* bugfix
---
 nnvm/include/nnvm/op_attr_types.h | 25 ++----------
 nnvm/src/pass/infer_shape_type.cc | 66 ++++++++++++++++++++-----------
 2 files changed, 45 insertions(+), 46 deletions(-)

diff --git a/nnvm/include/nnvm/op_attr_types.h b/nnvm/include/nnvm/op_attr_types.h
index 38ecd2b38d3d..7d3af45e8736 100644
--- a/nnvm/include/nnvm/op_attr_types.h
+++ b/nnvm/include/nnvm/op_attr_types.h
@@ -95,32 +95,13 @@ using FInferType = FInferNodeEntryAttr<int>;
 
 /*!
  * \brief Whether this op is an explicit backward operator,
- *  and the correspondence of each output to input.
- *
- *  If FBackwardOutToInIndex exists:
- *   - The first control_deps of the node points to the corresponding forward operator.
- *   - The k-th outputs corresponds to the FBackwardOutputToInputIndex()[k]-th input of forward op.
- *
- * \note Register under "FBackwardOutToInIndex"
- * This enables easier shape/type inference for backward operators for slice and reduction.
- */
-using FBackwardOutToInIndex = std::function<
-  std::vector<uint32_t> (const NodeAttrs& attrs)>;
-
-/*!
- * \brief Whether this op is an explicit backward operator,
- *  Returns list of input index that corresponds to the outputs of the forward operator.
- *
- *  If FBackwardInGradIndex exists:
+ * If TIsBackward is true:
  *   - The first control_deps of the node points to the corresponding forward operator.
- *   - The FBackwardInGradIndex[i]-th input of backward op corresponds to the i-th
- *      output of forward operator.
  *
- * \note Register under "FBackwardInGradIndex"
+ * \note Register under "TIsBackward"
  * This enables easier shape/type inference for backward operators.
  */
-using FBackwardInGradIndex = std::function<
-  std::vector<uint32_t> (const NodeAttrs& attrs)>;
+using TIsBackward = bool;
 
 /*!
  * \brief Get possible inplace options.
diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index 644adb066603..78d1ff1a494f 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -25,10 +25,11 @@ Graph InferAttr(Graph &&ret,
   const IndexedGraph& idx = ret.indexed_graph();
   static auto& finfer_shape =
       Op::GetAttr<FInferNodeEntryAttr<AttrType> >(infer_name);
-  static auto& backward_map =
-      Op::GetAttr<FBackwardOutToInIndex>("FBackwardOutToInIndex");
-  static auto& backward_in_grad =
-      Op::GetAttr<FBackwardInGradIndex>("FBackwardInGradIndex");
+  static auto& is_backward =
+      Op::GetAttr<TIsBackward>("TIsBackward");
+  // gradient function, used to get node correspondence.
+  static auto& fgrad =
+      Op::GetAttr<FGradient>("FGradient");
   // reshape shape vector
   AttrVector rshape;
   if (ret.attrs.count(attr_name) != 0) {
@@ -74,29 +75,44 @@ Graph InferAttr(Graph &&ret,
           CHECK(is >> rshape[out_ent_id]) << "Invalid attribute";
         }
       }
-    } else if (backward_map.count(inode.source->op())) {
-      // Backward operator inference.
+    } else if (is_backward.get(inode.source->op(), false)) {
       CHECK_GE(inode.control_deps.size(), 1)
         << "BackwardOp need to have control_deps to its forward op";
       const IndexedGraph::Node& fnode = idx[inode.control_deps[0]];
-      // Inference the outputs of backward operator (equal to the inputs
-      // of its corresponding forward operator).
-      std::vector<uint32_t> out_map =
-          backward_map[inode.source->op()](inode.source->attrs);
-      for (size_t i = 0; i < out_map.size(); ++i) {
-        uint32_t in_id = out_map[i];
-        CHECK_LT(in_id, fnode.inputs.size());
-        rshape[idx.entry_id(nid, i)] =
-            rshape[idx.entry_id(fnode.inputs[in_id])];
+      NodePtr fwd_ptr = inode.source->control_deps[0];
+      // use gradient function to find out the correspondence.
+      std::vector<NodeEntry> ograd(fwd_ptr->num_outputs());
+      for (size_t i = 0; i < ograd.size(); ++i) {
+        ograd[i].index = static_cast<uint32_t>(i);
       }
-      if (backward_in_grad.count(inode.source->op())) {
-        std::vector<uint32_t> in_grad =
-          backward_in_grad[inode.source->op()](inode.source->attrs);
-        CHECK_LE(in_grad.size(), fnode.source->num_outputs());
-        for (size_t i = 0; i < in_grad.size(); ++i) {
-          uint32_t eid = idx.entry_id(inode.inputs[in_grad[i]]);
+      // input gradient list
+      auto igrad = fgrad[fwd_ptr->op()](fwd_ptr, ograd);
+      const Op* backward_op = inode.source->op();
+      const Node* igrad_node = nullptr;
+      // Input gradient assignement
+      for (size_t i = 0; i < igrad.size(); ++i) {
+        if (igrad[i].node->op() == backward_op) {
+          uint32_t eid = idx.entry_id(nid, igrad[i].index);
           if (fis_none(rshape[eid])) {
-            rshape[eid] = rshape[idx.entry_id(inode.control_deps[0], i)];
+            rshape[eid] = rshape[idx.entry_id(fnode.inputs[i])];
+          } else {
+            CHECK_EQ(rshape[eid], rshape[idx.entry_id(fnode.inputs[i])])
+                << "Backward shape inconsistent with the forward shape";
+          }
+          if (igrad_node == nullptr) {
+            igrad_node = igrad[i].node.get();
+          } else {
+            CHECK(igrad_node == igrad[i].node.get());
+          }
+        }
+      }
+      // out grad entries
+      for (size_t i = 0; i < igrad_node->inputs.size(); ++i) {
+        const NodeEntry& e = igrad_node->inputs[i];
+        if (e.node == nullptr) {
+          uint32_t eid = idx.entry_id(inode.inputs[i]);
+          if (fis_none(rshape[eid])) {
+            rshape[eid] = rshape[idx.entry_id(inode.control_deps[0], e.index)];
           }
         }
       }
@@ -153,8 +169,10 @@ Graph InferAttr(Graph &&ret,
       }
     }
     num_unknown = 0;
-    for (size_t i = 0; i < idx.num_node_entries(); ++i) {
-      if (fis_none(rshape[i])) ++num_unknown;
+    for (size_t j = 0; j < idx.num_node_entries(); ++j) {
+      if (fis_none(rshape[j])) {
+        ++num_unknown;
+      }
     }
     if (num_unknown == 0) break;
   }

From d05c5e3848f247a9c3de4089902ff1107431a85f Mon Sep 17 00:00:00 2001
From: Eric Junyuan Xie <piiswrong@users.noreply.github.com>
Date: Sun, 27 Nov 2016 09:55:03 -0800
Subject: [PATCH 404/949] 	sync logging improvement from dmlc (#86)

* fix glog

* sync logging improvement from dmlc
---
 nnvm/include/dmlc/base.h      | 14 ++++--
 nnvm/include/dmlc/logging.h   | 81 +++++++++++++++++++++++++++++++----
 nnvm/include/nnvm/op.h        |  2 +-
 nnvm/src/pass/place_device.cc |  2 +-
 4 files changed, 86 insertions(+), 13 deletions(-)

diff --git a/nnvm/include/dmlc/base.h b/nnvm/include/dmlc/base.h
index 9eca4135f119..ed994ef727db 100644
--- a/nnvm/include/dmlc/base.h
+++ b/nnvm/include/dmlc/base.h
@@ -37,6 +37,14 @@
 #define DMLC_LOG_CUSTOMIZE 0
 #endif
 
+/*!
+ * \brief Wheter to print stack trace for fatal error,
+ * enabled on linux when using gcc.
+ */
+#if (!defined(DMLC_LOG_STACK_TRACE) && defined(__GNUC__))
+#define DMLC_LOG_STACK_TRACE 1
+#endif
+
 /*! \brief whether compile with hdfs support */
 #ifndef DMLC_USE_HDFS
 #define DMLC_USE_HDFS 0
@@ -201,7 +209,7 @@ inline T *BeginPtr(std::vector<T> &vec) {  // NOLINT(*)
   }
 }
 /*!
- * \brief get the beginning address of a vector
+ * \brief get the beginning address of a const vector
  * \param vec input vector
  * \return beginning address of a vector
  */
@@ -214,7 +222,7 @@ inline const T *BeginPtr(const std::vector<T> &vec) {
   }
 }
 /*!
- * \brief get the beginning address of a vector
+ * \brief get the beginning address of a string
  * \param str input string
  * \return beginning address of a string
  */
@@ -223,7 +231,7 @@ inline char* BeginPtr(std::string &str) {  // NOLINT(*)
   return &str[0];
 }
 /*!
- * \brief get the beginning address of a vector
+ * \brief get the beginning address of a const string
  * \param str input string
  * \return beginning address of a string
  */
diff --git a/nnvm/include/dmlc/logging.h b/nnvm/include/dmlc/logging.h
index afdc63955634..104e5055f147 100644
--- a/nnvm/include/dmlc/logging.h
+++ b/nnvm/include/dmlc/logging.h
@@ -14,6 +14,10 @@
 #include <stdexcept>
 #include "./base.h"
 
+#if DMLC_LOG_STACK_TRACE
+#include <execinfo.h>
+#endif
+
 namespace dmlc {
 /*!
  * \brief exception class that will be thrown by
@@ -57,17 +61,50 @@ inline void InitLogging(const char* argv0) {
   // DO NOTHING
 }
 
+class LogCheckError {
+ public:
+  LogCheckError() : str(nullptr) {}
+  explicit LogCheckError(const std::string& str_) : str(new std::string(str_)) {}
+  ~LogCheckError() { if (str != nullptr) delete str; }
+  operator bool() {return str != nullptr; }
+  std::string* str;
+};
+
+#define DEFINE_CHECK_FUNC(name, op)                               \
+  template <typename X, typename Y>                               \
+  inline LogCheckError LogCheck##name(const X& x, const Y& y) {   \
+    if (x op y) return LogCheckError();                           \
+    std::ostringstream os;                                        \
+    os << " (" << x << " vs. " << y << ") ";  /* CHECK_XX(x, y) requires x and y can be serialized to string. Use CHECK(x OP y) otherwise. NOLINT(*) */ \
+    return LogCheckError(os.str());                               \
+  }                                                               \
+  inline LogCheckError LogCheck##name(int x, int y) {             \
+    return LogCheck##name<int, int>(x, y);                        \
+  }
+
+#define CHECK_BINARY_OP(name, op, x, y)                               \
+  if (dmlc::LogCheckError _check_err = dmlc::LogCheck##name(x, y))    \
+    dmlc::LogMessageFatal(__FILE__, __LINE__).stream()                \
+      << "Check failed: " << #x " " #op " " #y << *(_check_err.str)
+
+DEFINE_CHECK_FUNC(_LT, <)
+DEFINE_CHECK_FUNC(_GT, >)
+DEFINE_CHECK_FUNC(_LE, <=)
+DEFINE_CHECK_FUNC(_GE, >=)
+DEFINE_CHECK_FUNC(_EQ, ==)
+DEFINE_CHECK_FUNC(_NE, !=)
+
 // Always-on checking
 #define CHECK(x)                                           \
   if (!(x))                                                \
-    dmlc::LogMessageFatal(__FILE__, __LINE__).stream() << "Check "  \
-      "failed: " #x << ' '
-#define CHECK_LT(x, y) CHECK((x) < (y))
-#define CHECK_GT(x, y) CHECK((x) > (y))
-#define CHECK_LE(x, y) CHECK((x) <= (y))
-#define CHECK_GE(x, y) CHECK((x) >= (y))
-#define CHECK_EQ(x, y) CHECK((x) == (y))
-#define CHECK_NE(x, y) CHECK((x) != (y))
+    dmlc::LogMessageFatal(__FILE__, __LINE__).stream()     \
+      << "Check failed: " #x << ' '
+#define CHECK_LT(x, y) CHECK_BINARY_OP(_LT, <, x, y)
+#define CHECK_GT(x, y) CHECK_BINARY_OP(_GT, >, x, y)
+#define CHECK_LE(x, y) CHECK_BINARY_OP(_LE, <=, x, y)
+#define CHECK_GE(x, y) CHECK_BINARY_OP(_GE, >=, x, y)
+#define CHECK_EQ(x, y) CHECK_BINARY_OP(_EQ, ==, x, y)
+#define CHECK_NE(x, y) CHECK_BINARY_OP(_NE, !=, x, y)
 #define CHECK_NOTNULL(x) \
   ((x) == NULL ? dmlc::LogMessageFatal(__FILE__, __LINE__).stream() << "Check  notnull: "  #x << ' ', (x) : (x)) // NOLINT(*)
 // Debug-only checking.
@@ -211,6 +248,20 @@ class LogMessageFatal : public LogMessage {
  public:
   LogMessageFatal(const char* file, int line) : LogMessage(file, line) {}
   ~LogMessageFatal() {
+#if DMLC_LOG_STACK_TRACE
+    const int MAX_STACK_SIZE = 256;
+    void *stack[MAX_STACK_SIZE];
+
+    int nframes = backtrace(stack, MAX_STACK_SIZE);
+    log_stream_ << "\n\n" << "Stack trace returned " << nframes << " entries:\n";
+    char **msgs = backtrace_symbols(stack, nframes);
+    if (msgs != nullptr) {
+      for (int i = 0; i < nframes; ++i) {
+        log_stream_ << "[bt] (" << i << ") " << msgs[i] << "\n";
+      }
+    }
+#endif
+
     log_stream_ << "\n";
     abort();
   }
@@ -228,6 +279,20 @@ class LogMessageFatal {
   }
   std::ostringstream &stream() { return log_stream_; }
   ~LogMessageFatal() DMLC_THROW_EXCEPTION {
+#if DMLC_LOG_STACK_TRACE
+    const int MAX_STACK_SIZE = 256;
+    void *stack[MAX_STACK_SIZE];
+
+    int nframes = backtrace(stack, MAX_STACK_SIZE);
+    log_stream_ << "\n\n" << "Stack trace returned " << nframes << " entries:\n";
+    char **msgs = backtrace_symbols(stack, nframes);
+    if (msgs != nullptr) {
+      for (int i = 0; i < nframes; ++i) {
+        log_stream_ << "[bt] (" << i << ") " << msgs[i] << "\n";
+      }
+    }
+#endif
+
     // throwing out of destructor is evil
     // hopefully we can do it here
     // also log the message before throw
diff --git a/nnvm/include/nnvm/op.h b/nnvm/include/nnvm/op.h
index 23648980e1f9..300d73ab0d93 100644
--- a/nnvm/include/nnvm/op.h
+++ b/nnvm/include/nnvm/op.h
@@ -431,7 +431,7 @@ inline Op& Op::set_attr(  // NOLINT(*)
         pm.attr_name_ = attr_name;
         *pmap = std::move(pm);
       }
-      CHECK_EQ(pmap->type(), typeid(OpMap<ValueType>))
+      CHECK(pmap->type() == typeid(OpMap<ValueType>))
           << "Attribute " << attr_name
           << " of operator " << this->name
           << " is registered as inconsistent types"
diff --git a/nnvm/src/pass/place_device.cc b/nnvm/src/pass/place_device.cc
index 3fce4fa06d63..474565c65cf2 100644
--- a/nnvm/src/pass/place_device.cc
+++ b/nnvm/src/pass/place_device.cc
@@ -41,7 +41,7 @@ Graph PlaceDevice(Graph src) {
     if (it != inode.source->attrs.dict.end()) {
       const std::string& device_group = it->second;
       auto dit = device_assign_map.find(device_group);
-      CHECK_NE(dit, device_assign_map.end())
+      CHECK(dit != device_assign_map.end())
           << "The device assignment not found for group " << device_group;
       device[nid] = dit->second;
     } else {

From 3791c217bc371263351cfb0b1e129acfa79777f0 Mon Sep 17 00:00:00 2001
From: Eric Junyuan Xie <piiswrong@users.noreply.github.com>
Date: Wed, 7 Dec 2016 22:42:58 -0800
Subject: [PATCH 405/949] fix (#87)

---
 nnvm/src/core/symbolic.cc | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index 1fcec7674e42..a35218839296 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -446,8 +446,7 @@ void Symbol::SetAttrs(const std::vector<std::pair<std::string, std::string> >& a
 bool Symbol::GetAttr(const std::string& key, std::string* out) const {
   Node* node = outputs[0].node.get();
   for (const NodeEntry& e : outputs) {
-    CHECK(node == e.node.get())
-        << "Symbol.SetAttrs only works for non-grouped symbol";
+    if (node != e.node.get()) return false;
   }
   if (key == "name") {
     *out = node->attrs.name;

From 242b17e6c95c1ee309addbcc8cbc395ab7494008 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 24 Dec 2016 05:09:14 +0800
Subject: [PATCH 406/949] [PASS] Make placedevice compatible with backward op
 (#88)

---
 nnvm/src/pass/place_device.cc | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/nnvm/src/pass/place_device.cc b/nnvm/src/pass/place_device.cc
index 474565c65cf2..b9e380adb4f0 100644
--- a/nnvm/src/pass/place_device.cc
+++ b/nnvm/src/pass/place_device.cc
@@ -25,6 +25,8 @@ Graph PlaceDevice(Graph src) {
   const Op* copy_op = Op::Get(src.GetAttr<std::string>("device_copy_op"));
   auto& device_assign_map = src.GetAttr<DeviceAssignMap>("device_assign_map");
   const IndexedGraph& idx = src.indexed_graph();
+  static auto& is_backward =
+      Op::GetAttr<TIsBackward>("TIsBackward");
   DeviceVector device;
   // copy on write semanatics
   if (src.attrs.count("device") != 0) {
@@ -45,9 +47,16 @@ Graph PlaceDevice(Graph src) {
           << "The device assignment not found for group " << device_group;
       device[nid] = dit->second;
     } else {
-      for (const IndexedGraph::NodeEntry& e : inode.inputs) {
-        if (device[e.node_id] != -1) {
-          device[nid] = device[e.node_id]; break;
+      if (!inode.source->is_variable() &&
+          is_backward.get(inode.source->op(), false)) {
+        if (device[inode.control_deps[0]] != -1) {
+          device[nid] = device[inode.control_deps[0]];
+        }
+      } else {
+        for (const IndexedGraph::NodeEntry& e : inode.inputs) {
+          if (device[e.node_id] != -1) {
+            device[nid] = device[e.node_id]; break;
+          }
         }
       }
     }

From 5a0c6c093630401e4201a4ae9e1e48c529d55299 Mon Sep 17 00:00:00 2001
From: Eric Junyuan Xie <piiswrong@users.noreply.github.com>
Date: Wed, 28 Dec 2016 17:52:43 -0800
Subject: [PATCH 407/949] Add list attr recursive (#89)

* Add list attr recursive

* fix
---
 nnvm/include/nnvm/symbolic.h | 10 ++++++++++
 nnvm/src/core/symbolic.cc    | 11 +++++++++++
 2 files changed, 21 insertions(+)

diff --git a/nnvm/include/nnvm/symbolic.h b/nnvm/include/nnvm/symbolic.h
index 3d7f94ce7b0e..4d26947ca646 100644
--- a/nnvm/include/nnvm/symbolic.h
+++ b/nnvm/include/nnvm/symbolic.h
@@ -12,6 +12,7 @@
 
 #include <string>
 #include <vector>
+#include <tuple>
 #include <utility>
 
 #include "./base.h"
@@ -168,6 +169,15 @@ class Symbol {
    * \return The created attribute.
    */
   std::unordered_map<std::string, std::string> ListAttrs(ListAttrOption option) const;
+  /*!
+   * \brief Get attribute dictionary from the symbol and all children.
+   *
+   *  For grouped symbol, an error will be raised.
+   *
+   * \return The created attribute in format <operator_name, key, value>.
+   */
+  std::vector<std::tuple<std::string, std::string, std::string> >
+      ListAttrsRecursive() const;
   /*!
    * \brief Create symbolic functor(AtomicSymbol) by given operator and attributes.
    * \param op The operator.
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index a35218839296..2b89bf2a446b 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -472,6 +472,17 @@ std::unordered_map<std::string, std::string> Symbol::ListAttrs(ListAttrOption op
   }
 }
 
+std::vector<std::tuple<std::string, std::string, std::string> >
+    Symbol::ListAttrsRecursive() const {
+  std::vector<std::tuple<std::string, std::string, std::string> > ret;
+  DFSVisit(this->outputs, [&ret](const NodePtr& n) {
+      for (const auto& it : n->attrs.dict) {
+        ret.emplace_back(std::make_tuple(n->attrs.name, it.first, it.second));
+      }
+    });
+  return ret;
+}
+
 Symbol Symbol::CreateFunctor(const Op* op,
                              std::unordered_map<std::string, std::string> attrs) {
   static auto& fnum_vis_output = Op::GetAttr<FNumVisibleOutputs>("FNumVisibleOutputs");

From 33d84af0e85ed6e9a69ac6061b854c4cff735aaf Mon Sep 17 00:00:00 2001
From: Eric Junyuan Xie <piiswrong@users.noreply.github.com>
Date: Wed, 28 Dec 2016 18:56:10 -0800
Subject: [PATCH 408/949] fix error message (#90)

* fix error message

* fix
---
 nnvm/src/pass/infer_shape_type.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index 78d1ff1a494f..7c2e3f7eb21b 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -136,7 +136,7 @@ Graph InferAttr(Graph &&ret,
           try {
             forward_known = finfer(inode.source->attrs, &ishape, &oshape);
           } catch (const std::exception& e) {
-            throw dmlc::Error(e.what() + std::string(" with ") + inode.source->attrs.name);
+            throw dmlc::Error("Error in operator " + inode.source->attrs.name + ": " + e.what());
           }
         } else {
           CHECK(!last_iter)

From 5ae4990a8aab1d75703b10ae047b62b981e4621a Mon Sep 17 00:00:00 2001
From: Bing Xu <antinucleon@gmail.com>
Date: Wed, 28 Dec 2016 23:01:33 -0800
Subject: [PATCH 409/949] add amalgamation support (#91)

* add amalgamation

* update target name
---
 nnvm/amalgamation/Makefile        |  27 ++++++++
 nnvm/amalgamation/amalgamation.py | 100 ++++++++++++++++++++++++++++++
 nnvm/amalgamation/generate.py     |  18 ++++++
 3 files changed, 145 insertions(+)
 create mode 100644 nnvm/amalgamation/Makefile
 create mode 100644 nnvm/amalgamation/amalgamation.py
 create mode 100644 nnvm/amalgamation/generate.py

diff --git a/nnvm/amalgamation/Makefile b/nnvm/amalgamation/Makefile
new file mode 100644
index 000000000000..0dd9dd8e6fc8
--- /dev/null
+++ b/nnvm/amalgamation/Makefile
@@ -0,0 +1,27 @@
+export NNVM_ROOT=`pwd`/..
+export CFLAGS = -std=c++11 -Wall -O3 -msse2 -Wno-unknown-pragmas -funroll-loops -Iinclude -fPIC
+
+.PHONY: all clean
+
+all: libnnvm.a
+
+nnvm.cc:
+	python generate.py $@
+
+nnvm.d: nnvm.cc
+	${CXX} ${CFLAGS} -MD -MF $@ \
+		-I ${NNVM_ROOT}/ -I ${NNVM_ROOT}/include \
+		-D__MIN__=$(MIN) -c $+
+	rm nnvm.o
+
+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
diff --git a/nnvm/amalgamation/amalgamation.py b/nnvm/amalgamation/amalgamation.py
new file mode 100644
index 000000000000..913bab7364bd
--- /dev/null
+++ b/nnvm/amalgamation/amalgamation.py
@@ -0,0 +1,100 @@
+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'
+    ]
+
+
+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
+
diff --git a/nnvm/amalgamation/generate.py b/nnvm/amalgamation/generate.py
new file mode 100644
index 000000000000..84a5fc06fb03
--- /dev/null
+++ b/nnvm/amalgamation/generate.py
@@ -0,0 +1,18 @@
+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()

From 7667cf53125d2254d04a1f2327c629eed58f0a31 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 5 Jan 2017 19:24:44 -0800
Subject: [PATCH 410/949] Fix warnings from Logging, enable Plan memory to take
 external memory (#93)

* Fix warnings from Logging, enable Plan memory to take external memory

* fix external memory id

* fix graph
---
 nnvm/include/dmlc/json.h          |  12 +-
 nnvm/include/dmlc/optional.h      | 181 ++++++++++++++++++++++++++++++
 nnvm/include/dmlc/parameter.h     | 112 +++++++++++++++++-
 nnvm/include/dmlc/registry.h      |   2 +-
 nnvm/src/core/symbolic.cc         |  10 +-
 nnvm/src/pass/gradient.cc         |   6 +-
 nnvm/src/pass/graph_algorithm.h   |   2 +-
 nnvm/src/pass/infer_shape_type.cc |   4 +-
 nnvm/src/pass/place_device.cc     |   6 +-
 nnvm/src/pass/plan_memory.cc      |  12 +-
 nnvm/src/pass/saveload_json.cc    |   2 +-
 11 files changed, 325 insertions(+), 24 deletions(-)
 create mode 100644 nnvm/include/dmlc/optional.h

diff --git a/nnvm/include/dmlc/json.h b/nnvm/include/dmlc/json.h
index 1934aee6a2ce..edf2fae54c8c 100644
--- a/nnvm/include/dmlc/json.h
+++ b/nnvm/include/dmlc/json.h
@@ -747,8 +747,8 @@ inline void JSONWriter::BeginArray(bool multi_line) {
 }
 
 inline void JSONWriter::EndArray() {
-  CHECK_NE(scope_multi_line_.size(), 0);
-  CHECK_NE(scope_counter_.size(), 0);
+  CHECK_NE(scope_multi_line_.size(), 0U);
+  CHECK_NE(scope_counter_.size(), 0U);
   bool newline = scope_multi_line_.back();
   size_t nelem = scope_counter_.back();
   scope_multi_line_.pop_back();
@@ -764,8 +764,8 @@ inline void JSONWriter::BeginObject(bool multi_line) {
 }
 
 inline void JSONWriter::EndObject() {
-  CHECK_NE(scope_multi_line_.size(), 0);
-  CHECK_NE(scope_counter_.size(), 0);
+  CHECK_NE(scope_multi_line_.size(), 0U);
+  CHECK_NE(scope_counter_.size(), 0U);
   bool newline = scope_multi_line_.back();
   size_t nelem = scope_counter_.back();
   scope_multi_line_.pop_back();
@@ -842,7 +842,7 @@ inline void JSONObjectReadHelper::ReadAllFields(JSONReader *reader) {
     for (std::map<std::string, Entry>::iterator
              it = map_.begin(); it != map_.end(); ++it) {
       if (it->second.optional) continue;
-      CHECK_NE(visited.count(it->first), 0)
+      CHECK_NE(visited.count(it->first), 0U)
           << "JSONReader: Missing field \"" << it->first << "\"\n At "
           << reader->line_info();
     }
@@ -857,7 +857,7 @@ inline void JSONObjectReadHelper::ReaderFunction(JSONReader *reader, void *addr)
 template<typename T>
 inline void JSONObjectReadHelper::
 DeclareFieldInternal(const std::string &key, T *addr, bool optional) {
-  CHECK_EQ(map_.count(key), 0)
+  CHECK_EQ(map_.count(key), 0U)
       << "Adding duplicate field " << key;
   Entry e;
   e.func = ReaderFunction<T>;
diff --git a/nnvm/include/dmlc/optional.h b/nnvm/include/dmlc/optional.h
new file mode 100644
index 000000000000..6b72dc419e13
--- /dev/null
+++ b/nnvm/include/dmlc/optional.h
@@ -0,0 +1,181 @@
+/*!
+ * Copyright (c) 2016 by Contributors
+ * \file optional.h
+ * \brief Container to hold optional data.
+ */
+#ifndef DMLC_OPTIONAL_H_
+#define DMLC_OPTIONAL_H_
+
+#include <iostream>
+#include <utility>
+#include <algorithm>
+
+#include "./base.h"
+#include "./logging.h"
+#include "./type_traits.h"
+
+namespace dmlc {
+
+/*! \brief dummy type for assign null to optional */
+struct nullopt_t {
+#if defined(_MSC_VER) && _MSC_VER < 1900
+  /*! \brief dummy constructor */
+  explicit nullopt_t(int) {}
+#else
+  /*! \brief dummy constructor */
+  constexpr nullopt_t(int) {}
+#endif
+};
+
+/*! Assign null to optional: optional<T> x = nullopt; */
+constexpr const nullopt_t nullopt = nullopt_t(0);
+
+/*!
+ * \brief c++17 compatible optional class.
+ *
+ * At any time an optional<T> instance either
+ * hold no value (string representation "None")
+ * or hold a value of type T.
+ */
+template<typename T>
+class optional {
+ public:
+  /*! \brief construct an optional object that contains no value */
+  optional() : is_none(true) {}
+  /*! \brief construct an optional object with value */
+  explicit optional(const T& value) {
+    is_none = false;
+    new (&val) T(value);
+  }
+  /*! \brief construct an optional object with another optional object */
+  optional(const optional<T>& other) {
+    is_none = other.is_none;
+    if (!is_none) {
+      new (&val) T(other.value());
+    }
+  }
+  /*! \brief deconstructor */
+  ~optional() {
+    if (!is_none) {
+      reinterpret_cast<T*>(&val)->~T();
+    }
+  }
+  /*! \brief swap two optional */
+  void swap(optional<T>& other) {
+    std::swap(val, other.val);
+    std::swap(is_none, other.is_none);
+  }
+  /*! \brief set this object to hold value
+   *  \param value the value to hold
+   *  \return return self to support chain assignment
+   */
+  optional<T>& operator=(const T& value) {
+    (optional<T>(value)).swap(*this);
+    return *this;
+  }
+  /*! \brief set this object to hold the same value with other
+   *  \param other the other object
+   *  \return return self to support chain assignment
+   */
+  optional<T>& operator=(const optional<T> &other) {
+    (optional<T>(other)).swap(*this);
+    return *this;
+  }
+  /*! \brief clear the value this object is holding.
+   *         optional<T> x = nullopt;
+   */
+  optional<T>& operator=(nullopt_t) {
+    (optional<T>()).swap(*this);
+    return *this;
+  }
+  /*! \brief non-const dereference operator */
+  T& operator*() {  // NOLINT(*)
+    return *reinterpret_cast<T*>(&val);
+  }
+  /*! \brief const dereference operator */
+  const T& operator*() const {
+    return *reinterpret_cast<const T*>(&val);
+  }
+  /*! \brief return the holded value.
+   *         throws std::logic_error if holding no value
+   */
+  const T& value() const {
+    if (is_none) {
+      throw std::logic_error("bad optional access");
+    }
+    return *reinterpret_cast<const T*>(&val);
+  }
+  /*! \brief whether this object is holding a value */
+  explicit operator bool() const { return !is_none; }
+
+ private:
+  // whether this is none
+  bool is_none;
+  // on stack storage of value
+  typename std::aligned_storage<sizeof(T), alignof(T)>::type val;
+};
+
+/*! \brief serialize an optional object to string.
+ *
+ *  \code
+ *    dmlc::optional<int> x;
+ *    std::cout << x;  // None
+ *    x = 0;
+ *    std::cout << x;  // 0
+ *  \endcode
+ *
+ *  \param os output stream
+ *  \param t source optional<T> object
+ *  \return output stream
+ */
+template<typename T>
+std::ostream &operator<<(std::ostream &os, const optional<T> &t) {
+  if (t) {
+    os << *t;
+  } else {
+    os << "None";
+  }
+  return os;
+}
+
+/*! \brief parse a string object into optional<T>
+ *
+ *  \code
+ *    dmlc::optional<int> x;
+ *    std::string s1 = "1";
+ *    std::istringstream is1(s1);
+ *    s1 >> x;  // x == optional<int>(1)
+ *
+ *    std::string s2 = "None";
+ *    std::istringstream is2(s2);
+ *    s2 >> x;  // x == optional<int>()
+ *  \endcode
+ *
+ *  \param is input stream
+ *  \param t target optional<T> object
+ *  \return input stream
+ */
+template<typename T>
+std::istream &operator>>(std::istream &is, optional<T> &t) {
+  char buf[4];
+  std::streampos origin = is.tellg();
+  is.read(buf, 4);
+  if (is.fail() || buf[0] != 'N' || buf[1] != 'o' ||
+      buf[2] != 'n' || buf[3] != 'e') {
+    is.clear();
+    is.seekg(origin);
+    T x;
+    is >> x;
+    t = x;
+  } else {
+    t = nullopt;
+  }
+  return is;
+}
+
+/*! \brief description for optional int */
+DMLC_DECLARE_TYPE_NAME(optional<int>, "int or None");
+
+}  // namespace dmlc
+
+#endif  // DMLC_OPTIONAL_H_
diff --git a/nnvm/include/dmlc/parameter.h b/nnvm/include/dmlc/parameter.h
index 537976b447ed..717684c79aab 100644
--- a/nnvm/include/dmlc/parameter.h
+++ b/nnvm/include/dmlc/parameter.h
@@ -22,6 +22,7 @@
 #include "./json.h"
 #include "./logging.h"
 #include "./type_traits.h"
+#include "./optional.h"
 
 namespace dmlc {
 // this file is backward compatible with non-c++11
@@ -758,7 +759,7 @@ class FieldEntry<int>
   // override print default
   virtual void PrintValue(std::ostream &os, int value) const {  // NOLINT(*)
     if (is_enum_) {
-      CHECK_NE(enum_back_map_.count(value), 0)
+      CHECK_NE(enum_back_map_.count(value), 0U)
           << "Value not found in enum declared";
       os << enum_back_map_.at(value);
     } else {
@@ -781,6 +782,115 @@ class FieldEntry<int>
   }
 };
 
+
+// specialize define for optional<int>(enum)
+template<>
+class FieldEntry<optional<int> >
+    : public FieldEntryBase<FieldEntry<optional<int> >, optional<int> > {
+ public:
+  // construct
+  FieldEntry<optional<int> >() : is_enum_(false) {}
+  // parent
+  typedef FieldEntryBase<FieldEntry<optional<int> >, optional<int> > Parent;
+  // override set
+  virtual void Set(void *head, const std::string &value) const {
+    if (is_enum_ && value != "None") {
+      std::map<std::string, int>::const_iterator it = enum_map_.find(value);
+      std::ostringstream os;
+      if (it == enum_map_.end()) {
+        os << "Invalid Input: \'" << value;
+        os << "\', valid values are: ";
+        PrintEnums(os);
+        throw dmlc::ParamError(os.str());
+      } else {
+        os << it->second;
+        Parent::Set(head, os.str());
+      }
+    } else {
+      Parent::Set(head, value);
+    }
+  }
+  virtual ParamFieldInfo GetFieldInfo() const {
+    if (is_enum_) {
+      ParamFieldInfo info;
+      std::ostringstream os;
+      info.name = key_;
+      info.type = type_;
+      PrintEnums(os);
+      if (has_default_) {
+        os << ',' << "optional, default=";
+        PrintDefaultValueString(os);
+      } else {
+        os << ", required";
+      }
+      info.type_info_str = os.str();
+      info.description = description_;
+      return info;
+    } else {
+      return Parent::GetFieldInfo();
+    }
+  }
+  // add enum
+  inline FieldEntry<optional<int> > &add_enum(const std::string &key, int value) {
+    CHECK_NE(key, "None") << "None is reserved for empty optional<int>";
+    if ((enum_map_.size() != 0 && enum_map_.count(key) != 0) || \
+        enum_back_map_.count(value) != 0) {
+      std::ostringstream os;
+      os << "Enum " << "(" << key << ": " << value << " exisit!" << ")\n";
+      os << "Enums: ";
+      for (std::map<std::string, int>::const_iterator it = enum_map_.begin();
+           it != enum_map_.end(); ++it) {
+        os << "(" << it->first << ": " << it->second << "), ";
+      }
+      throw dmlc::ParamError(os.str());
+    }
+    enum_map_[key] = value;
+    enum_back_map_[value] = key;
+    is_enum_ = true;
+    return this->self();
+  }
+
+ protected:
+  // enum flag
+  bool is_enum_;
+  // enum map
+  std::map<std::string, int> enum_map_;
+  // enum map
+  std::map<int, std::string> enum_back_map_;
+  // override print behavior
+  virtual void PrintDefaultValueString(std::ostream &os) const { // NOLINT(*)
+    os << '\'';
+    PrintValue(os, default_value_);
+    os << '\'';
+  }
+  // override print default
+  virtual void PrintValue(std::ostream &os, optional<int> value) const {  // NOLINT(*)
+    if (is_enum_) {
+      if (!value) {
+        os << "None";
+      } else {
+        CHECK_NE(enum_back_map_.count(value.value()), 0U)
+            << "Value not found in enum declared";
+        os << enum_back_map_.at(value.value());
+      }
+    } else {
+      os << value;
+    }
+  }
+
+
+ private:
+  inline void PrintEnums(std::ostream &os) const {  // NOLINT(*)
+    os << "{None";
+    for (std::map<std::string, int>::const_iterator
+             it = enum_map_.begin(); it != enum_map_.end(); ++it) {
+      os << ", ";
+      os << "\'" << it->first << '\'';
+    }
+    os << '}';
+  }
+};
+
 // specialize define for string
 template<>
 class FieldEntry<std::string>
diff --git a/nnvm/include/dmlc/registry.h b/nnvm/include/dmlc/registry.h
index 4f9947831c28..a205a0f9dd59 100644
--- a/nnvm/include/dmlc/registry.h
+++ b/nnvm/include/dmlc/registry.h
@@ -75,7 +75,7 @@ class Registry {
    * \return ref to the registered entry, used to set properties
    */
   inline EntryType &__REGISTER__(const std::string& name) {
-    CHECK_EQ(fmap_.count(name), 0)
+    CHECK_EQ(fmap_.count(name), 0U)
         << name << " already registered";
     EntryType *e = new EntryType();
     e->name = name;
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index 2b89bf2a446b..b88c00d8a89f 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -263,11 +263,11 @@ void Symbol::Compose(const array_view<const Symbol*>& args,
   CHECK(!outputs[0].node->is_variable()) << "Variable cannot be composed";
   // parameter check.
   for (size_t i = 0; i < args.size(); ++i) {
-    CHECK_EQ(args[i]->outputs.size(), 1)
+    CHECK_EQ(args[i]->outputs.size(), 1U)
         << "Argument " << i << " is a tuple, single value is required";
   }
   for (const auto& kv : kwargs) {
-    CHECK_EQ(kv.second->outputs.size(), 1)
+    CHECK_EQ(kv.second->outputs.size(), 1U)
         << "Keyword Argument " << kv.first << " is a tuple, single value is required";
   }
   // assign new name
@@ -316,7 +316,7 @@ void Symbol::Compose(const array_view<const Symbol*>& args,
         }
       }
     } else {
-      CHECK_EQ(kwargs.size(), 0) << "Variable length function do not accept kwargs";
+      CHECK_EQ(kwargs.size(), 0U) << "Variable length function do not accept kwargs";
       n->inputs.reserve(args.size());
       for (const Symbol* s : args) {
         n->inputs.push_back(s->outputs[0]);
@@ -325,7 +325,7 @@ void Symbol::Compose(const array_view<const Symbol*>& args,
     UpdateNodeVersion(n);
   } else {
     // general composition
-    CHECK_EQ(args.size(), 0)
+    CHECK_EQ(args.size(), 0U)
         << "General composition only support kwargs for now";
     size_t nmatched = 0;
     size_t arg_counter = 0;
@@ -395,7 +395,7 @@ Symbol Symbol::operator () (const array_view<const Symbol*>& args,
 }
 
 void Symbol::AddControlDeps(const Symbol& src) {
-  CHECK_EQ(outputs.size(), 1)
+  CHECK_EQ(outputs.size(), 1U)
       << "AddControlDeps only works for nongrouped symbol";
   Node* n = outputs[0].node.get();
   for (const NodeEntry& sp : src.outputs) {
diff --git a/nnvm/src/pass/gradient.cc b/nnvm/src/pass/gradient.cc
index 80df90389d6c..580eda8f8750 100644
--- a/nnvm/src/pass/gradient.cc
+++ b/nnvm/src/pass/gradient.cc
@@ -46,11 +46,11 @@ Graph Gradient(Graph src) {
   using MirrorFun = std::function<int (const Node& node)>;
   using AttrHintFun = std::function<NodeEntry (const NodeEntry& src, const NodeEntry &like)>;
 
-  CHECK_NE(src.attrs.count("grad_ys"), 0)
+  CHECK_NE(src.attrs.count("grad_ys"), 0U)
       << "Gradient require grad_ys to be presented.";
-  CHECK_NE(src.attrs.count("grad_ys_out_grad"), 0)
+  CHECK_NE(src.attrs.count("grad_ys_out_grad"), 0U)
       << "Gradient require grad_ys_out_grad to be presented.";
-  CHECK_NE(src.attrs.count("grad_xs"), 0)
+  CHECK_NE(src.attrs.count("grad_xs"), 0U)
       << "Gradient require grad_xs to be presented.";
   const std::vector<NodeEntry>& ys =
       src.GetAttr<std::vector<NodeEntry> >("grad_ys");
diff --git a/nnvm/src/pass/graph_algorithm.h b/nnvm/src/pass/graph_algorithm.h
index 331b7ad6fe53..e933ff6238bb 100644
--- a/nnvm/src/pass/graph_algorithm.h
+++ b/nnvm/src/pass/graph_algorithm.h
@@ -75,7 +75,7 @@ inline uint32_t ColorNodeGroup(
     std::vector<uint32_t> node_importance,
     uint32_t max_ncolor,
     std::vector<uint32_t> *color) {
-  CHECK_NE(max_ncolor, 0);
+  CHECK_NE(max_ncolor, 0U);
   CHECK_EQ(graph.num_nodes(), node_importance.size());
 
   color->clear();
diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index 7c2e3f7eb21b..e16564cb66ca 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -66,7 +66,7 @@ Graph InferAttr(Graph &&ret,
     if (inode.source->is_variable()) {
       // Variable node. No operator. Only one output entry.
       CHECK(inode.source->op() == nullptr);
-      CHECK_EQ(num_outputs, 1);
+      CHECK_EQ(num_outputs, 1U);
       const uint32_t out_ent_id = idx.entry_id(nid, 0);
       if (shape_attr_key.length() != 0 && fis_none(rshape[out_ent_id])) {
         auto it = inode.source->attrs.dict.find(shape_attr_key);
@@ -76,7 +76,7 @@ Graph InferAttr(Graph &&ret,
         }
       }
     } else if (is_backward.get(inode.source->op(), false)) {
-      CHECK_GE(inode.control_deps.size(), 1)
+      CHECK_GE(inode.control_deps.size(), 1U)
         << "BackwardOp need to have control_deps to its forward op";
       const IndexedGraph::Node& fnode = idx[inode.control_deps[0]];
       NodePtr fwd_ptr = inode.source->control_deps[0];
diff --git a/nnvm/src/pass/place_device.cc b/nnvm/src/pass/place_device.cc
index b9e380adb4f0..0c2307fb1a33 100644
--- a/nnvm/src/pass/place_device.cc
+++ b/nnvm/src/pass/place_device.cc
@@ -15,11 +15,11 @@ namespace {
 // simply logic to place device according to device_group hint
 // insert copy node when there is
 Graph PlaceDevice(Graph src) {
-  CHECK_NE(src.attrs.count("device_group_attr_key"), 0)
+  CHECK(src.attrs.count("device_group_attr_key"))
       << "Need graph attribute \"device_group_attr_key\" in PlaceDevice";
-  CHECK_NE(src.attrs.count("device_assign_map"), 0)
+  CHECK(src.attrs.count("device_assign_map"))
       << "Need graph attribute \"device_assign_map\" in PlaceDevice";
-  CHECK_NE(src.attrs.count("device_copy_op"), 0)
+  CHECK(src.attrs.count("device_copy_op"))
       << "Need graph attribute \"device_copy_op\" in PlaceDevice";
   std::string device_group_attr_key = src.GetAttr<std::string>("device_group_attr_key");
   const Op* copy_op = Op::Get(src.GetAttr<std::string>("device_copy_op"));
diff --git a/nnvm/src/pass/plan_memory.cc b/nnvm/src/pass/plan_memory.cc
index d961df905f05..1666da4bb693 100644
--- a/nnvm/src/pass/plan_memory.cc
+++ b/nnvm/src/pass/plan_memory.cc
@@ -21,6 +21,8 @@ class GraphAllocator {
   using StorageID = int;
   // bad storage id
   static const StorageID kBadStorageID = -1;
+  // external storage id
+  static const StorageID kExternalStorageID = -2;
   // request a free storage
   StorageID Request(int dev_id, int dtype, TShape shape, uint32_t node_id) {
     if (shape.ndim() == 0) return kBadStorageID;
@@ -62,6 +64,7 @@ class GraphAllocator {
   // release a memory space.
   void Release(StorageID id, uint32_t node_id) {
     CHECK_NE(id, kBadStorageID);
+    if (id == kExternalStorageID) return;
     StorageEntry *e = data_[id].get();
     e->released_by_node = node_id;
     free_.insert({e->max_bytes, e});
@@ -161,7 +164,14 @@ Graph PlanMemory(Graph ret) {
     ++ref_count[idx.entry_id(e)];
   }
   // step 2: allocate memory.
-  StorageVector storage(idx.num_node_entries(), -1);
+  StorageVector storage;
+
+  if (ret.attrs.count("storage") != 0) {
+    storage = ret.MoveCopyAttr<StorageVector>("storage");
+  } else {
+    storage.resize(idx.num_node_entries(), -1);
+  }
+
   std::vector<int> storage_inplace_index(idx.num_node_entries(), -1);
   const ShapeVector& shape_vec = ret.GetAttr<ShapeVector>("shape");
   const DTypeVector& dtype_vec = ret.GetAttr<DTypeVector>("dtype");
diff --git a/nnvm/src/pass/saveload_json.cc b/nnvm/src/pass/saveload_json.cc
index 21ea709b9019..6c2e93be1800 100644
--- a/nnvm/src/pass/saveload_json.cc
+++ b/nnvm/src/pass/saveload_json.cc
@@ -155,7 +155,7 @@ struct JSONGraph {
 
 // Load a graph from JSON file.
 Graph LoadJSON(Graph src) {
-  CHECK_NE(src.attrs.count("json"), 0)
+  CHECK_NE(src.attrs.count("json"), 0U)
       << "Load JSON require json to be presented.";
   const std::string &json_str =
       nnvm::get<std::string>(*src.attrs.at("json"));

From 5b39340def677d8fd82ed24deaf082027d6d8352 Mon Sep 17 00:00:00 2001
From: Eric Junyuan Xie <piiswrong@users.noreply.github.com>
Date: Sat, 7 Jan 2017 00:35:03 -0800
Subject: [PATCH 411/949] add FSetInputVariableAttrs (#92)

* add FSetInputVariableAttrs

* rename
---
 nnvm/include/nnvm/op_attr_types.h | 12 ++++++++++++
 nnvm/src/core/symbolic.cc         | 10 ++++++++++
 2 files changed, 22 insertions(+)

diff --git a/nnvm/include/nnvm/op_attr_types.h b/nnvm/include/nnvm/op_attr_types.h
index 7d3af45e8736..8e15c394be82 100644
--- a/nnvm/include/nnvm/op_attr_types.h
+++ b/nnvm/include/nnvm/op_attr_types.h
@@ -142,6 +142,18 @@ using FGradient = std::function<std::vector<NodeEntry>(
     const NodePtr& nodeptr,
     const std::vector<NodeEntry>& out_grads)>;
 
+/*!
+ * \brief Set the attributes of input variable.
+ *  Usually used for setting initialization or weight decay.
+ *  \param attrs The attributes of this node.
+ *  \param var the input variable
+ *  \param index index of var in all inputs
+ */
+using FSetInputVarAttrOnCompose = std::function<void(
+    const NodeAttrs& attrs,
+    NodePtr var,
+    const int index)>;
+
 }  // namespace nnvm
 
 #endif  // NNVM_OP_ATTR_TYPES_H_
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index b88c00d8a89f..a101deed7919 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -259,6 +259,7 @@ void Symbol::Compose(const array_view<const Symbol*>& args,
                      const std::unordered_map<std::string, const Symbol*>& kwargs,
                      const std::string& name) {
   static auto& flist_inputs = Op::GetAttr<FListInputNames>("FListInputNames");
+  static auto& fset_attrs = Op::GetAttr<FSetInputVarAttrOnCompose>("FSetInputVarAttrOnCompose");
 
   CHECK(!outputs[0].node->is_variable()) << "Variable cannot be composed";
   // parameter check.
@@ -323,6 +324,15 @@ void Symbol::Compose(const array_view<const Symbol*>& args,
       }
     }
     UpdateNodeVersion(n);
+
+    FSetInputVarAttrOnCompose fn = fset_attrs.get(n->op(), nullptr);
+    if (fn != nullptr) {
+      for (size_t i = 0; i < n->inputs.size(); ++i) {
+        if (n->inputs[i].node->is_variable()) {
+          fn(n->attrs, n->inputs[i].node, i);
+        }
+      }
+    }
   } else {
     // general composition
     CHECK_EQ(args.size(), 0U)

From acc4aebb4a679a38efb2ebd1d7950eba43634477 Mon Sep 17 00:00:00 2001
From: Eric Junyuan Xie <piiswrong@users.noreply.github.com>
Date: Sat, 7 Jan 2017 12:53:29 -0800
Subject: [PATCH 412/949] enhance shape inference. allow in complete shape
 (#94)

---
 nnvm/include/nnvm/tuple.h         |  5 +++++
 nnvm/src/pass/infer_shape_type.cc | 18 ++++++++++--------
 2 files changed, 15 insertions(+), 8 deletions(-)

diff --git a/nnvm/include/nnvm/tuple.h b/nnvm/include/nnvm/tuple.h
index c925e941f7b6..41e74c8f8fe4 100644
--- a/nnvm/include/nnvm/tuple.h
+++ b/nnvm/include/nnvm/tuple.h
@@ -386,6 +386,11 @@ class TShape : public Tuple<index_t> {
     return begin();
   }
 #ifdef MSHADOW_XINLINE
+  template<int dim>
+  inline TShape(const mshadow::Shape<dim> &s) {// NOLINT(*)
+    this->assign(s.shape_, s.shape_ + dim);
+  }
+
   template<int dim>
   inline TShape(mshadow::Shape<dim> &&s) {// NOLINT(*)
     this->assign(s.shape_, s.shape_ + dim);
diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index e16564cb66ca..dd36d3da31cb 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -155,27 +155,29 @@ Graph InferAttr(Graph &&ret,
     }
   };
 
-  size_t num_unknown = 0;
-  const int kMaxStep = 3;
-  for (int i = 0; i < kMaxStep; ++i) {
+  size_t last_num_unknown;
+  size_t num_unknown = rshape.size();
+  int i = 0;
+  do {
     if (i % 2 == 0) {
       for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
-        infer_step(nid, i + 1 == kMaxStep);
+        infer_step(nid, false);
       }
     } else {
       // backward inference
       for (uint32_t i = idx.num_nodes(); i != 0; --i) {
-        infer_step(i - 1, i + 1 == kMaxStep);
+        infer_step(i - 1, false);
       }
     }
+    last_num_unknown = num_unknown;
     num_unknown = 0;
     for (size_t j = 0; j < idx.num_node_entries(); ++j) {
       if (fis_none(rshape[j])) {
         ++num_unknown;
       }
     }
-    if (num_unknown == 0) break;
-  }
+    ++i;
+  } while (num_unknown > 0 && last_num_unknown > num_unknown);
   // set the shapes
   ret.attrs[attr_name] = std::make_shared<any>(std::move(rshape));
   // number of nodes who knows the shape.
@@ -190,7 +192,7 @@ NNVM_REGISTER_PASS(InferShape)
         std::move(ret), TShape(),
         "FInferShape", "shape_inputs", "shape_attr_key",
         "shape", "shape_num_unknown_nodes",
-        [](const TShape& s) { return s.ndim() == 0; },
+        [](const TShape& s) { return s.ndim() == 0 || s.Size() == 0; },
         nullptr);
   })
 .set_change_graph(false)

From 62d5ef8c1b946c218bfbea6637b4d86ba1822588 Mon Sep 17 00:00:00 2001
From: Xin Li <yakumolx@gmail.com>
Date: Wed, 11 Jan 2017 05:00:34 +0800
Subject: [PATCH 413/949] Handle empty tuple (#95)

---
 nnvm/include/nnvm/tuple.h | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/nnvm/include/nnvm/tuple.h b/nnvm/include/nnvm/tuple.h
index 41e74c8f8fe4..3de9a910ee6f 100644
--- a/nnvm/include/nnvm/tuple.h
+++ b/nnvm/include/nnvm/tuple.h
@@ -233,6 +233,15 @@ class Tuple {
         return is;
     }
     }
+    // Handle empty tuple
+    while (isspace(is.peek())) {
+      is.get();
+    }
+    if (is.peek() == ')') {
+      is.get();
+      return is;
+    }
+    // Handle non-empty tuple
     ValueType idx;
     std::vector<ValueType> tmp;
     while (is >> idx) {

From 25f5582a570a9c7c1ced16c31d5939aba795b090 Mon Sep 17 00:00:00 2001
From: Bing Xu <antinucleon@gmail.com>
Date: Thu, 12 Jan 2017 09:24:24 -0800
Subject: [PATCH 414/949] fix mxnet amalgamation (#96)

---
 nnvm/include/nnvm/tuple.h | 48 +++++++++++++++++++--------------------
 1 file changed, 24 insertions(+), 24 deletions(-)

diff --git a/nnvm/include/nnvm/tuple.h b/nnvm/include/nnvm/tuple.h
index 3de9a910ee6f..132e23e42302 100644
--- a/nnvm/include/nnvm/tuple.h
+++ b/nnvm/include/nnvm/tuple.h
@@ -275,6 +275,30 @@ class Tuple {
     t.assign(tmp.begin(), tmp.end());
     return is;
   }
+  /*!
+   * \brief save the content into binary stream
+   * \param strm the output stream
+   * \tparam TStream any stream type that have write
+   */
+  template<typename TStream>
+  inline void Save(TStream *strm) const {
+    strm->Write(&ndim_, sizeof(ndim_));
+    strm->Write(begin(), sizeof(ValueType) * ndim_);
+  }
+  /*!
+   * \brief load the content from binary stream
+   * \param strm the output stream
+   * \tparam TStream any stream type that have write
+   * \return whether the load is successful
+   */
+  template<typename TStream>
+  inline bool Load(TStream *strm) {
+    if (strm->Read(&ndim_, sizeof(ndim_)) != sizeof(ndim_)) return false;
+    this->SetDim(ndim_);
+    size_t nread = sizeof(ValueType) * ndim_;
+    if (strm->Read(begin(), nread) != nread) return false;
+    return true;
+  }
 
  protected:
   // stack cache size
@@ -511,30 +535,6 @@ class TShape : public Tuple<index_t> {
   inline bool operator!=(const mshadow::Shape<dim> &s) const {
     return !(*this == s);
   }
-  /*!
-   * \brief save the content into binary stream
-   * \param strm the output stream
-   * \tparam TStream any stream type that have write
-   */
-  template<typename TStream>
-  inline void Save(TStream *strm) const {
-    strm->Write(&ndim_, sizeof(ndim_));
-    strm->Write(data(), sizeof(index_t) * ndim_);
-  }
-  /*!
-   * \brief load the content from binary stream
-   * \param strm the output stream
-   * \tparam TStream any stream type that have write
-   * \return whether the load is successful
-   */
-  template<typename TStream>
-  inline bool Load(TStream *strm) {
-    if (strm->Read(&ndim_, sizeof(ndim_)) != sizeof(ndim_)) return false;
-    this->SetDim(ndim_);
-    size_t nread = sizeof(index_t) * ndim_;
-    if (strm->Read(data(), nread) != nread) return false;
-    return true;
-  }
 #endif
 };
 

From be0050346656f51e8aae147a89140a83abf21a04 Mon Sep 17 00:00:00 2001
From: Howard Su <howard0su@gmail.com>
Date: Sun, 22 Jan 2017 12:56:33 +0800
Subject: [PATCH 415/949] Fix the build to generate .d file (#99)

---
 nnvm/amalgamation/Makefile | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/nnvm/amalgamation/Makefile b/nnvm/amalgamation/Makefile
index 0dd9dd8e6fc8..e457b770d74f 100644
--- a/nnvm/amalgamation/Makefile
+++ b/nnvm/amalgamation/Makefile
@@ -9,10 +9,9 @@ nnvm.cc:
 	python generate.py $@
 
 nnvm.d: nnvm.cc
-	${CXX} ${CFLAGS} -MD -MF $@ \
+	${CXX} ${CFLAGS} -M -MT nnvm.o \
 		-I ${NNVM_ROOT}/ -I ${NNVM_ROOT}/include \
-		-D__MIN__=$(MIN) -c $+
-	rm nnvm.o
+		-D__MIN__=$(MIN) $+ > nnvm.d
 
 nnvm-all.cc: nnvm.d nnvm.cc
 	python ./amalgamation.py $+ $@

From e9258612a8233861e05f0a712ae1fbf13dc2dd9b Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 26 Jan 2017 14:22:17 -0800
Subject: [PATCH 416/949] simplify makefile

---
 nnvm/Makefile | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/nnvm/Makefile b/nnvm/Makefile
index e73cba047db0..7318d1c48fd6 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -10,8 +10,7 @@ endif
 include $(config)
 
 export LDFLAGS = -pthread -lm
-export CFLAGS = -std=c++11 -Wall -O2 -msse2  -Wno-unknown-pragmas -funroll-loops\
-	 -Iinclude -fPIC
+export CFLAGS = -std=c++11 -Wall -O2 -Iinclude -fPIC
 
 ifneq ($(ADD_CFLAGS), NONE)
 	CFLAGS += $(ADD_CFLAGS)

From 1cd82d7946d9dda9e79d0db946aa8e8d88de14a5 Mon Sep 17 00:00:00 2001
From: Chris Olivier <cjolivier01@gmail.com>
Date: Mon, 30 Jan 2017 19:09:37 -0800
Subject: [PATCH 417/949] Remove dmlc headers, add optional DMLC_CORE_PATH
 variable for build path (#102)

* Remove dmlc headers, add optional DMLC_CORE_PATH variable for build path

* travis.yml doesn't need to clone dmlc-core anymore
---
 nnvm/CMakeLists.txt              |   6 +
 nnvm/Makefile                    |   6 +
 nnvm/include/dmlc/README         |   2 -
 nnvm/include/dmlc/any.h          | 346 -----------
 nnvm/include/dmlc/array_view.h   | 128 ----
 nnvm/include/dmlc/base.h         | 249 --------
 nnvm/include/dmlc/json.h         | 871 ----------------------------
 nnvm/include/dmlc/logging.h      | 327 -----------
 nnvm/include/dmlc/memory.h       | 261 ---------
 nnvm/include/dmlc/optional.h     | 181 ------
 nnvm/include/dmlc/parameter.h    | 966 -------------------------------
 nnvm/include/dmlc/registry.h     | 305 ----------
 nnvm/include/dmlc/thread_local.h |  77 ---
 nnvm/include/dmlc/timer.h        |  49 --
 nnvm/include/dmlc/type_traits.h  | 171 ------
 nnvm/make/config.mk              |   3 +
 16 files changed, 15 insertions(+), 3933 deletions(-)
 delete mode 100644 nnvm/include/dmlc/README
 delete mode 100644 nnvm/include/dmlc/any.h
 delete mode 100644 nnvm/include/dmlc/array_view.h
 delete mode 100644 nnvm/include/dmlc/base.h
 delete mode 100644 nnvm/include/dmlc/json.h
 delete mode 100644 nnvm/include/dmlc/logging.h
 delete mode 100644 nnvm/include/dmlc/memory.h
 delete mode 100644 nnvm/include/dmlc/optional.h
 delete mode 100644 nnvm/include/dmlc/parameter.h
 delete mode 100644 nnvm/include/dmlc/registry.h
 delete mode 100644 nnvm/include/dmlc/thread_local.h
 delete mode 100644 nnvm/include/dmlc/timer.h
 delete mode 100644 nnvm/include/dmlc/type_traits.h

diff --git a/nnvm/CMakeLists.txt b/nnvm/CMakeLists.txt
index 22e93665c0b5..56c1c25baf53 100644
--- a/nnvm/CMakeLists.txt
+++ b/nnvm/CMakeLists.txt
@@ -51,6 +51,12 @@ mxnet_source_group("Source\\pass"   GLOB "src/pass/*.cc")
 
 FILE(GLOB_RECURSE SOURCE "src/*.cc" "src/*.h" "include/*.h")
 
+if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/dmlc-core/CMakeLists.txt)
+  include_directories(${CMAKE_CURRENT_SOURCE_DIR}/dmlc-core/include)
+elseif(DMLC_CORE_PATH)
+  include_directories(${DMLC_CORE_PATH}/include)
+endif()
+
 add_library(nnvm ${SOURCE})
 target_link_libraries(nnvm ${nnvm_LINKER_LIBS})
 
diff --git a/nnvm/Makefile b/nnvm/Makefile
index 7318d1c48fd6..f1506746bd86 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -12,6 +12,12 @@ include $(config)
 export LDFLAGS = -pthread -lm
 export CFLAGS = -std=c++11 -Wall -O2 -Iinclude -fPIC
 
+ifdef DMLC_CORE_PATH
+  CFLAGS += -I$(DMLC_CORE_PATH)/include
+else
+  CFLAGS += -I$(ROOTDIR)/dmlc-core/include
+endif
+
 ifneq ($(ADD_CFLAGS), NONE)
 	CFLAGS += $(ADD_CFLAGS)
 endif
diff --git a/nnvm/include/dmlc/README b/nnvm/include/dmlc/README
deleted file mode 100644
index bfda4925cf1b..000000000000
--- a/nnvm/include/dmlc/README
+++ /dev/null
@@ -1,2 +0,0 @@
-This folder is synced from dmlc-core/include/dmlc
-Contains useful utility headers for the project.
diff --git a/nnvm/include/dmlc/any.h b/nnvm/include/dmlc/any.h
deleted file mode 100644
index d32e44e48c4d..000000000000
--- a/nnvm/include/dmlc/any.h
+++ /dev/null
@@ -1,346 +0,0 @@
-/*!
- * Copyright (c) 2016 by Contributors
- * \file any.h
- * \brief Container to hold any data type.
- */
-#ifndef DMLC_ANY_H_
-#define DMLC_ANY_H_
-
-// This code need c++11 to compile
-#include <typeinfo>
-#include <type_traits>
-#include <utility>
-#include <algorithm>
-
-#include "./base.h"
-#include "./logging.h"
-
-namespace dmlc {
-// forward declare any;
-class any;
-
-/*!
- * Get a  reference to content stored in the any as type T.
- * This will cause an error if
- * T does not match the type stored.
- * This function is not part of std::any standard.
- *
- * \param src The source source any container.
- * \return The reference of content
- * \tparam T The type of the value to be fetched.
- */
-template<typename T>
-inline T& get(any& src);  // NOLINT(*)
-
-/*!
- * Get the const reference content stored in the any as type T.
- * This will cause an error if
- * T does not match the type stored.
- * This function is not part of std::any standard.
- *
- * \param src The source source any container.
- * \return The reference of content
- * \tparam T The type of the value to be fetched.
- */
-template<typename T>
-inline const T& get(const any& src);
-
-/*!
- * \brief An any class that is compatible to std::any in c++17.
- *
- * \code
- *   dmlc::any a = std::string("mydear"), b = 1;
- *   // get reference out and add it
- *   dmlc::get<int>(b) += 1;
- *   // a is now string
- *   LOG(INFO) << dmlc::get<std::string>(a);
- *   // a is now 2, the string stored will be properly destructed
- *   a = std::move(b);
- *   LOG(INFO) << dmlc::get<int>(a);
- * \endcode
- * \sa get
- */
-class any {
- public:
-  /*! \brief default constructor */
-  inline any() = default;
-  /*!
-   * \brief move constructor from another any
-   * \param other The other any to be moved
-   */
-  inline any(any&& other);  // NOLINT(*)
-  /*!
-   * \brief copy constructor
-   * \param other The other any to be copied
-   */
-  inline any(const any& other);  // NOLINT(*)
-  /*!
-   * \brief constructor from any types
-   * \param other The other types to be constructed into any.
-   * \tparam T The value type of other.
-   */
-  template<typename T>
-  inline any(T&& other);  // NOLINT(*)
-  /*! \brief destructor */
-  inline ~any();
-  /*!
-   * \brief assign operator from other
-   * \param other The other any to be copy or moved.
-   * \return self
-   */
-  inline any& operator=(any&& other);
-  /*!
-   * \brief assign operator from other
-   * \param other The other any to be copy or moved.
-   * \return self
-   */
-  inline any& operator=(const any& other);
-  /*!
-   * \brief assign operator from any type.
-   * \param other The other any to be copy or moved.
-   * \tparam T The value type of other.
-   * \return self
-   */
-  template<typename T>
-  inline any& operator=(T&& other);
-  /*!
-   * \return whether the container is empty.
-   */
-  inline bool empty() const;
-  /*!
-   * \return clear the content of container
-   */
-  inline void clear();
-  /*!
-   * swap current content with other
-   * \param other The other data to be swapped.
-   */
-  inline void swap(any& other); // NOLINT(*)
-  /*!
-   * \return The type_info about the stored type.
-   */
-  inline const std::type_info& type() const;
-
- private:
-  //! \cond Doxygen_Suppress
-  // declare of helper class
-  template<typename T>
-  class TypeOnHeap;
-  template<typename T>
-  class TypeOnStack;
-  template<typename T>
-  class TypeInfo;
-  // size of stack space, it takes 32 bytes for one any type.
-  static const size_t kStack = sizeof(void*) * 3;
-  static const size_t kAlign = sizeof(void*);
-  // container use dynamic storage only when space runs lager
-  union Data {
-    // stack space
-    std::aligned_storage<kStack, kAlign>::type stack;
-    // pointer to heap space
-    void* pheap;
-  };
-  // type specific information
-  struct Type {
-    // destructor function
-    void (*destroy)(Data* data);
-    // copy constructor
-    void (*create_from_data)(Data* dst, const Data& src);
-    // the type info function
-    const std::type_info* ptype_info;
-  };
-  // constant to check if data can be stored on heap.
-  template<typename T>
-  struct data_on_stack {
-    static const bool value = alignof(T) <= kAlign && sizeof(T) <= kStack;
-  };
-  // declare friend with
-  template<typename T>
-  friend T& get(any& src);  // NOLINT(*)
-  template<typename T>
-  friend const T& get(const any& src);
-  // internal construct function
-  inline void construct(any&& other);
-  // internal construct function
-  inline void construct(const any& other);
-  // internal function to check if type is correct.
-  template<typename T>
-  inline void check_type() const;
-  // internal type specific information
-  const Type* type_{nullptr};
-  // internal data
-  Data data_;
-};
-
-template<typename T>
-inline any::any(T&& other) {
-  typedef typename std::decay<T>::type DT;
-  if (std::is_same<DT, any>::value) {
-    this->construct(std::forward<T>(other));
-  } else {
-    static_assert(std::is_copy_constructible<DT>::value,
-                  "Any can only hold value that is copy constructable");
-    type_ = TypeInfo<DT>::get_type();
-    if (data_on_stack<DT>::value) {
-      new (&(data_.stack)) DT(std::forward<T>(other));
-    } else {
-      data_.pheap = new DT(std::forward<T>(other));
-    }
-  }
-}
-
-inline any::any(any&& other) {
-  this->construct(std::move(other));
-}
-
-inline any::any(const any& other) {
-  this->construct(other);
-}
-
-inline void any::construct(any&& other) {
-  type_ = other.type_;
-  data_ = other.data_;
-  other.type_ = nullptr;
-}
-
-inline void any::construct(const any& other) {
-  type_ = other.type_;
-  if (type_ != nullptr) {
-    type_->create_from_data(&data_, other.data_);
-  }
-}
-
-inline any::~any() {
-  this->clear();
-}
-
-inline any& any::operator=(any&& other) {
-  any(std::move(other)).swap(*this);
-  return *this;
-}
-
-inline any& any::operator=(const any& other) {
-  any(other).swap(*this);
-  return *this;
-}
-
-template<typename T>
-inline any& any::operator=(T&& other) {
-  any(std::forward<T>(other)).swap(*this);
-  return *this;
-}
-
-inline void any::swap(any& other) { // NOLINT(*)
-  std::swap(type_, other.type_);
-  std::swap(data_, other.data_);
-}
-
-inline void any::clear() {
-  if (type_ != nullptr) {
-    if (type_->destroy != nullptr) {
-      type_->destroy(&data_);
-    }
-    type_ = nullptr;
-  }
-}
-
-inline bool any::empty() const {
-  return type_ == nullptr;
-}
-
-inline const std::type_info& any::type() const {
-  if (type_ != nullptr) {
-    return *(type_->ptype_info);
-  } else {
-    return typeid(void);
-  }
-}
-
-template<typename T>
-inline void any::check_type() const {
-  CHECK(type_ != nullptr)
-      << "The any container is empty"
-      << " requested=" << typeid(T).name();
-  CHECK(type_->ptype_info == &typeid(T))
-      << "The stored type mismatch"
-      << " stored=" << type_->ptype_info->name()
-      << " requested=" << typeid(T).name();
-}
-
-template<typename T>
-inline const T& get(const any& src) {
-  src.check_type<T>();
-  return *any::TypeInfo<T>::get_ptr(&(src.data_));
-}
-
-template<typename T>
-inline T& get(any& src) { // NOLINT(*)
-  src.check_type<T>();
-  return *any::TypeInfo<T>::get_ptr(&(src.data_));
-}
-
-template<typename T>
-class any::TypeOnHeap {
- public:
-  inline static T* get_ptr(any::Data* data) {
-    return static_cast<T*>(data->pheap);
-  }
-  inline static const T* get_ptr(const any::Data* data) {
-    return static_cast<const T*>(data->pheap);
-  }
-  inline static void create_from_data(any::Data* dst, const any::Data& data) {
-    dst->pheap = new T(*get_ptr(&data));
-  }
-  inline static void destroy(Data* data) {
-    delete static_cast<T*>(data->pheap);
-  }
-};
-
-template<typename T>
-class any::TypeOnStack {
- public:
-  inline static T* get_ptr(any::Data* data) {
-    return reinterpret_cast<T*>(&(data->stack));
-  }
-  inline static const T* get_ptr(const any::Data* data) {
-    return reinterpret_cast<const T*>(&(data->stack));
-  }
-  inline static void create_from_data(any::Data* dst, const any::Data& data) {
-    new (&(dst->stack)) T(*get_ptr(&data));
-  }
-  inline static void destroy(Data* data) {
-    T* dptr = reinterpret_cast<T*>(&(data->stack));
-    dptr->~T();
-  }
-};
-
-template<typename T>
-class any::TypeInfo
-    : public std::conditional<any::data_on_stack<T>::value,
-                              any::TypeOnStack<T>,
-                              any::TypeOnHeap<T> >::type {
- public:
-  inline static const Type* get_type() {
-    static TypeInfo<T> tp;
-    return &(tp.type_);
-  }
-
- private:
-  // local type
-  Type type_;
-  // constructor
-  TypeInfo() {
-    if (std::is_pod<T>::value) {
-      type_.destroy = nullptr;
-    } else {
-      type_.destroy = TypeInfo<T>::destroy;
-    }
-    type_.create_from_data = TypeInfo<T>::create_from_data;
-    type_.ptype_info = &typeid(T);
-  }
-};
-//! \endcond
-
-}  // namespace dmlc
-
-#endif  // DMLC_ANY_H_
diff --git a/nnvm/include/dmlc/array_view.h b/nnvm/include/dmlc/array_view.h
deleted file mode 100644
index 5e01a78cc53d..000000000000
--- a/nnvm/include/dmlc/array_view.h
+++ /dev/null
@@ -1,128 +0,0 @@
-/*!
- *  Copyright (c) 2016 by Contributors
- * \file array_view.h
- * \brief Read only data structure to reference array
- */
-#ifndef DMLC_ARRAY_VIEW_H_
-#define DMLC_ARRAY_VIEW_H_
-
-#include <vector>
-#include <array>
-
-namespace dmlc {
-
-/*!
- * \brief Read only data structure to reference continuous memory region of array.
- * Provide unified view for vector, array and C style array.
- * This data structure do not guarantee aliveness of referenced array.
- *
- * Make sure do not use array_view to record data in async function closures.
- * Also do not use array_view to create reference to temporary data structure.
- *
- * \tparam ValueType The value
- *
- * \code
- *  std::vector<int> myvec{1,2,3};
- *  dmlc::array_view<int> view(myvec);
- *  // indexed visit to the view.
- *  LOG(INFO) << view[0];
- *
- *  for (int v : view) {
- *     // visit each element in the view
- *  }
- * \endcode
- */
-template<typename ValueType>
-class array_view {
- public:
-  /*! \brief default constructor */
-  array_view() = default;
-  /*!
-   * \brief default copy constructor
-   * \param other another array view.
-   */
-  array_view(const array_view<ValueType> &other) = default;  // NOLINT(*)
-#ifndef _MSC_VER
-  /*!
-   * \brief default move constructor
-   * \param other another array view.
-   */
-  array_view(array_view<ValueType>&& other) = default; // NOLINT(*)
-#else
-  /*!
-  * \brief default move constructor
-  * \param other another array view.
-  */
-  array_view(array_view<ValueType>&& other) { // NOLINT(*)
-    begin_ = other.begin_;
-    size_ = other.size_;
-    other.begin_ = nullptr;
-  }
-#endif
-  /*!
-   * \brief default assign constructor
-   * \param other another array view.
-   * \return self.
-   */
-  array_view<ValueType>& operator=(const array_view<ValueType>& other) = default; // NOLINT(*)
-  /*!
-   * \brief construct array view std::vector
-   * \param other vector container
-   */
-  array_view(const std::vector<ValueType>& other) {  // NOLINT(*)
-    if (other.size() != 0) {
-      begin_ = &other[0]; size_ = other.size();
-    }
-  }
-  /*!
-   * \brief construct array std::array
-   * \param other another array view.
-   */
-  template<std::size_t size>
-  array_view(const std::array<ValueType, size>& other) {  // NOLINT(*)
-    if (size != 0) {
-      begin_ = &other[0]; size_ = size;
-    }
-  }
-  /*!
-   * \brief construct array view from continuous segment
-   * \param begin beginning pointre
-   * \param end end pointer
-   */
-  array_view(const ValueType* begin, const ValueType* end) {
-    if (begin < end) {
-      begin_ = begin;
-      size_ = end - begin;
-    }
-  }
-  /*! \return size of the array */
-  inline size_t size() const {
-    return size_;
-  }
-  /*! \return begin of the array */
-  inline const ValueType* begin() const {
-    return begin_;
-  }
-  /*! \return end point of the array */
-  inline const ValueType* end() const {
-    return begin_ + size_;
-  }
-  /*!
-   * \brief get i-th element from the view
-   * \param i The index.
-   * \return const reference to i-th element.
-   */
-  inline const ValueType& operator[](size_t i) const {
-    return begin_[i];
-  }
-
- private:
-  /*! \brief the begin of the view */
-  const ValueType* begin_{nullptr};
-  /*! \brief The size of the view */
-  size_t size_{0};
-};
-
-}  // namespace dmlc
-
-#endif  // DMLC_ARRAY_VIEW_H_
diff --git a/nnvm/include/dmlc/base.h b/nnvm/include/dmlc/base.h
deleted file mode 100644
index ed994ef727db..000000000000
--- a/nnvm/include/dmlc/base.h
+++ /dev/null
@@ -1,249 +0,0 @@
-/*!
- *  Copyright (c) 2015 by Contributors
- * \file base.h
- * \brief defines configuration macros
- */
-#ifndef DMLC_BASE_H_
-#define DMLC_BASE_H_
-
-/*! \brief whether use glog for logging */
-#ifndef DMLC_USE_GLOG
-#define DMLC_USE_GLOG 0
-#endif
-
-/*!
- * \brief whether throw dmlc::Error instead of
- *  directly calling abort when FATAL error occured
- *  NOTE: this may still not be perfect.
- *  do not use FATAL and CHECK in destructors
- */
-#ifndef DMLC_LOG_FATAL_THROW
-#define DMLC_LOG_FATAL_THROW 1
-#endif
-
-/*!
- * \brief whether always log a message before throw
- * This can help identify the error that cannot be catched.
- */
-#ifndef DMLC_LOG_BEFORE_THROW
-#define DMLC_LOG_BEFORE_THROW 1
-#endif
-
-/*!
- * \brief Whether to use customized logger,
- * whose output can be decided by other libraries.
- */
-#ifndef DMLC_LOG_CUSTOMIZE
-#define DMLC_LOG_CUSTOMIZE 0
-#endif
-
-/*!
- * \brief Wheter to print stack trace for fatal error,
- * enabled on linux when using gcc.
- */
-#if (!defined(DMLC_LOG_STACK_TRACE) && defined(__GNUC__))
-#define DMLC_LOG_STACK_TRACE 1
-#endif
-
-/*! \brief whether compile with hdfs support */
-#ifndef DMLC_USE_HDFS
-#define DMLC_USE_HDFS 0
-#endif
-
-/*! \brief whether compile with s3 support */
-#ifndef DMLC_USE_S3
-#define DMLC_USE_S3 0
-#endif
-
-/*! \brief whether or not use parameter server */
-#ifndef DMLC_USE_PS
-#define DMLC_USE_PS 0
-#endif
-
-/*! \brief whether or not use c++11 support */
-#ifndef DMLC_USE_CXX11
-#define DMLC_USE_CXX11 (defined(__GXX_EXPERIMENTAL_CXX0X__) ||\
-                        __cplusplus >= 201103L || defined(_MSC_VER))
-#endif
-
-/*! \brief strict CXX11 support */
-#ifndef DMLC_STRICT_CXX11
-#define DMLC_STRICT_CXX11 (__cplusplus >= 201103L || defined(_MSC_VER))
-#endif
-
-/// check if g++ is before 4.6
-#if DMLC_USE_CXX11 && defined(__GNUC__) && !defined(__clang_version__)
-#if __GNUC__ == 4 && __GNUC_MINOR__ < 6
-#pragma message("Will need g++-4.6 or higher to compile all"           \
-                "the features in dmlc-core, "                           \
-                "compile without c++0x, some features may be disabled")
-#undef DMLC_USE_CXX11
-#define DMLC_USE_CXX11 0
-#endif
-#endif
-
-
-/*!
- * \brief Enable std::thread related modules,
- *  Used to disable some module in mingw compile.
- */
-#ifndef DMLC_ENABLE_STD_THREAD
-#define DMLC_ENABLE_STD_THREAD DMLC_USE_CXX11
-#endif
-
-/*! \brief whether enable regex support, actually need g++-4.9 or higher*/
-#ifndef DMLC_USE_REGEX
-#define DMLC_USE_REGEX (__cplusplus >= 201103L || defined(_MSC_VER))
-#endif
-
-/*! \brief helper macro to supress unused warning */
-#if defined(__GNUC__)
-#define DMLC_ATTRIBUTE_UNUSED __attribute__((unused))
-#else
-#define DMLC_ATTRIBUTE_UNUSED
-#endif
-
-/*! \brief helper macro to generate string concat */
-#define DMLC_STR_CONCAT_(__x, __y) __x##__y
-#define DMLC_STR_CONCAT(__x, __y) DMLC_STR_CONCAT_(__x, __y)
-
-/*!
- * \brief Disable copy constructor and assignment operator.
- *
- * If C++11 is supported, both copy and move constructors and
- * assignment operators are deleted explicitly. Otherwise, they are
- * only declared but not implemented. Place this macro in private
- * section if C++11 is not available.
- */
-#ifndef DISALLOW_COPY_AND_ASSIGN
-#  if DMLC_USE_CXX11
-#    define DISALLOW_COPY_AND_ASSIGN(T) \
-       T(T const&) = delete; \
-       T(T&&) = delete; \
-       T& operator=(T const&) = delete; \
-       T& operator=(T&&) = delete
-#  else
-#    define DISALLOW_COPY_AND_ASSIGN(T) \
-       T(T const&); \
-       T& operator=(T const&)
-#  endif
-#endif
-
-///
-/// code block to handle optionally loading
-///
-#if !defined(__GNUC__)
-#define fopen64 std::fopen
-#endif
-#if (defined __MINGW32__) && !(defined __MINGW64__)
-#define fopen64 std::fopen
-#endif
-#ifdef _MSC_VER
-#if _MSC_VER < 1900
-// NOTE: sprintf_s is not equivalent to snprintf,
-// they are equivalent when success, which is sufficient for our case
-#define snprintf sprintf_s
-#define vsnprintf vsprintf_s
-#endif
-#else
-#ifdef _FILE_OFFSET_BITS
-#if _FILE_OFFSET_BITS == 32
-#pragma message("Warning: FILE OFFSET BITS defined to be 32 bit")
-#endif
-#endif
-
-#ifdef __APPLE__
-#define off64_t off_t
-#define fopen64 std::fopen
-#endif
-
-extern "C" {
-#include <sys/types.h>
-}
-#endif
-
-#ifdef _MSC_VER
-//! \cond Doxygen_Suppress
-typedef signed char int8_t;
-typedef __int16 int16_t;
-typedef __int32 int32_t;
-typedef __int64 int64_t;
-typedef unsigned char uint8_t;
-typedef unsigned __int16 uint16_t;
-typedef unsigned __int32 uint32_t;
-typedef unsigned __int64 uint64_t;
-//! \endcond
-#else
-#include <inttypes.h>
-#endif
-#include <string>
-#include <vector>
-
-#if defined(_MSC_VER) && _MSC_VER < 1900
-#define noexcept_true throw ()
-#define noexcept_false
-#define noexcept(a) noexcept_##a
-#endif
-
-#if DMLC_USE_CXX11
-#define DMLC_THROW_EXCEPTION noexcept(false)
-#define DMLC_NO_EXCEPTION  noexcept(true)
-#else
-#define DMLC_THROW_EXCEPTION
-#define DMLC_NO_EXCEPTION
-#endif
-
-/*! \brief namespace for dmlc */
-namespace dmlc {
-/*!
- * \brief safely get the beginning address of a vector
- * \param vec input vector
- * \return beginning address of a vector
- */
-template<typename T>
-inline T *BeginPtr(std::vector<T> &vec) {  // NOLINT(*)
-  if (vec.size() == 0) {
-    return NULL;
-  } else {
-    return &vec[0];
-  }
-}
-/*!
- * \brief get the beginning address of a const vector
- * \param vec input vector
- * \return beginning address of a vector
- */
-template<typename T>
-inline const T *BeginPtr(const std::vector<T> &vec) {
-  if (vec.size() == 0) {
-    return NULL;
-  } else {
-    return &vec[0];
-  }
-}
-/*!
- * \brief get the beginning address of a string
- * \param str input string
- * \return beginning address of a string
- */
-inline char* BeginPtr(std::string &str) {  // NOLINT(*)
-  if (str.length() == 0) return NULL;
-  return &str[0];
-}
-/*!
- * \brief get the beginning address of a const string
- * \param str input string
- * \return beginning address of a string
- */
-inline const char* BeginPtr(const std::string &str) {
-  if (str.length() == 0) return NULL;
-  return &str[0];
-}
-}  // namespace dmlc
-
-#if defined(_MSC_VER) && _MSC_VER < 1900
-#define constexpr const
-#define alignof __alignof
-#endif
-
-#endif  // DMLC_BASE_H_
diff --git a/nnvm/include/dmlc/json.h b/nnvm/include/dmlc/json.h
deleted file mode 100644
index edf2fae54c8c..000000000000
--- a/nnvm/include/dmlc/json.h
+++ /dev/null
@@ -1,871 +0,0 @@
-/*!
- * Copyright (c) 2015 by Contributors
- * \file json.h
- * \brief Lightweight JSON Reader/Writer that read save into C++ data structs.
- *  This includes STL composites and structures.
- */
-#ifndef DMLC_JSON_H_
-#define DMLC_JSON_H_
-
-// This code requires C++11 to compile
-#include <vector>
-#include <iostream>
-#include <cctype>
-#include <string>
-#include <algorithm>
-#include <map>
-#include <list>
-#include <utility>
-
-#include "./base.h"
-#include "./logging.h"
-#include "./type_traits.h"
-
-#if DMLC_USE_CXX11
-#include <typeindex>
-#include <typeinfo>
-#include <unordered_map>
-#if DMLC_STRICT_CXX11
-#include "./any.h"
-#endif  // DMLC_STRICT_CXX11
-#endif  // DMLC_USE_CXX11
-
-namespace dmlc {
-/*!
- * \brief Lightweight JSON Reader to read any STL compositions and structs.
- *  The user need to know the schema of the
- *
- */
-class JSONReader {
- public:
-  /*!
-   * \brief Constructor.
-   * \param is the input stream.
-   */
-  explicit JSONReader(std::istream *is)
-      : is_(is),
-        line_count_r_(0),
-        line_count_n_(0) {}
-  /*!
-   * \brief Parse next JSON string.
-   * \param out_str the output string.
-   * \throw dmlc::Error when next token is not string
-   */
-  inline void ReadString(std::string *out_str);
-  /*!
-   * \brief Read Number.
-   * \param out_value output value;
-   * \throw dmlc::Error when next token is not number of ValueType.
-   * \tparam ValueType type of the number
-   */
-  template<typename ValueType>
-  inline void ReadNumber(ValueType *out_value);
-  /*!
-   * \brief Begin parsing an object.
-   * \code
-   *  std::string key;
-   *  // value can be any type that is json serializable.
-   *  std::string value;
-   *  reader->BeginObject();
-   *  while (reader->NextObjectItem(&key)) {
-   *    // do somthing to key value
-   *    reader->Read(&value);
-   *  }
-   * \endcode
-   */
-  inline void BeginObject();
-  /*!
-   * \brief Begin parsing an array.
-   * \code
-   *  // value can be any type that is json serializable.
-   *  std::string value;
-   *  reader->BeginArray();
-   *  while (reader->NextObjectArrayItem(&value)) {
-   *    // do somthing to value
-   *  }
-   * \endcode
-   */
-  inline void BeginArray();
-  /*!
-   * \brief Try to move to next object item.
-   *  If this call is successful, user can proceed to call
-   *  reader->Read to read in the value.
-   * \param out_key the key to the next object.
-   * \return true if the read is successful, false if we are at end of the object.
-   */
-  inline bool NextObjectItem(std::string *out_key);
-  /*!
-   * \brief Try to read the next element in the array.
-   *  If this call is successful, user can proceed to call
-   *  reader->Read to read in the value.
-   * \return true if the read is successful, false if we are at end of the array.
-   */
-  inline bool NextArrayItem();
-  /*!
-   * \brief Read next ValueType.
-   * \param out_value any STL or json readable type to be read
-   * \throw dmlc::Error when the read of ValueType is not successful.
-   * \tparam ValueType the data type to be read.
-   */
-  template<typename ValueType>
-  inline void Read(ValueType *out_value);
-
-  /*! \return current line count */
-  inline std::string line_info() const {
-    char temp[64];
-    std::ostringstream os;
-    os << " Line " << std::max(line_count_r_, line_count_n_);
-    is_->getline(temp, 64);
-    os << ", around ^`" << temp << "`";
-    return os.str();
-  }
-
- private:
-  /*! \brief internal reader stream */
-  std::istream *is_;
-  /*! \brief "\\r" counter */
-  size_t line_count_r_;
-  /*! \brief "\\n" counter */
-  size_t line_count_n_;
-  /*!
-   * \brief record how many element processed in
-   *  current array/object scope.
-   */
-  std::vector<size_t> scope_counter_;
-  /*!
-   * \brief Read next nonspace character.
-   * \return the next nonspace character.
-   */
-  inline int NextNonSpace();
-  /*!
-   * \brief Read just before next nonspace but not read that.
-   * \return the next nonspace character.
-   */
-  inline int PeekNextNonSpace();
-};
-
-/*!
- * \brief Lightweight json to write any STL compositions.
- */
-class JSONWriter {
- public:
-  /*!
-   * \brief Constructor.
-   * \param os the output stream.
-   */
-  explicit JSONWriter(std::ostream *os)
-      : os_(os) {}
-  /*!
-   * \brief Write a string that do not contain escape characters.
-   * \param s the string to be written.
-   */
-  inline void WriteNoEscape(const std::string &s);
-  /*!
-   * \brief Write a string that can contain escape characters.
-   * \param s the string to be written.
-   */
-  inline void WriteString(const std::string &s);
-  /*!
-   * \brief Write a string that can contain escape characters.
-   * \param v the value to be written.
-   * \tparam ValueType The value type to be written.
-   */
-  template<typename ValueType>
-  inline void WriteNumber(const ValueType &v);
-  /*!
-   * \brief Start beginning of array.
-   * \param multi_line whether to start an multi_line array.
-   * \code
-   *  writer->BeginArray();
-   *  for (auto& v : vdata) {
-   *    writer->WriteArrayItem(v);
-   *  }
-   *  writer->EndArray();
-   * \endcode
-   */
-  inline void BeginArray(bool multi_line = true);
-  /*! \brief Finish writing an array. */
-  inline void EndArray();
-  /*!
-   * \brief Start beginning of array.
-   * \param multi_line whether to start an multi_line array.
-   * \code
-   *  writer->BeginObject();
-   *  for (auto& kv : vmap) {
-   *    writer->WriteObjectKeyValue(kv.first, kv.second);
-   *  }
-   *  writer->EndObject();
-   * \endcode
-   */
-  inline void BeginObject(bool multi_line = true);
-  /*! \brief Finish writing object. */
-  inline void EndObject();
-  /*!
-   * \brief Write key value pair in the object.
-   * \param key the key of the object.
-   * \param value the value of to be written.
-   * \tparam ValueType The value type to be written.
-   */
-  template<typename ValueType>
-  inline void WriteObjectKeyValue(const std::string &key,
-                                  const ValueType &value);
-  /*!
-   * \brief Write seperator of array, before writing next element.
-   * User can proceed to call writer->Write to write next item
-   */
-  inline void WriteArraySeperator();
-  /*!
-   * \brief Write value into array.
-   * \param value The value of to be written.
-   * \tparam ValueType The value type to be written.
-   */
-  template<typename ValueType>
-  inline void WriteArrayItem(const ValueType &value);
-  /*!
-   * \brief Write value to json.
-   * \param value any STL or json readable that can be written.
-   * \tparam ValueType the data type to be write.
-   */
-  template<typename ValueType>
-  inline void Write(const ValueType &value);
-
- private:
-  /*! \brief Output stream */
-  std::ostream *os_;
-  /*!
-   * \brief record how many element processed in
-   *  current array/object scope.
-   */
-  std::vector<size_t> scope_counter_;
-  /*! \brief Record whether current is a multiline scope */
-  std::vector<bool> scope_multi_line_;
-  /*!
-   * \brief Write seperating space and newlines
-   */
-  inline void WriteSeperator();
-};
-
-/*!
- * \brief Helper class to read JSON into a class or struct object.
- * \code
- *  struct Param {
- *    std::string name;
- *    int value;
- *    // define load function from JSON
- *    inline void Load(dmlc::JSONReader *reader) {
- *      dmlc::JSONStructReadHelper helper;
- *      helper.DeclareField("name", &name);
- *      helper.DeclareField("value", &value);
- *      helper.ReadAllFields(reader);
- *    }
- *  };
- * \endcode
- */
-class JSONObjectReadHelper {
- public:
-  /*!
-   * \brief Declare field of type T
-   * \param key the key of the of field.
-   * \param addr address of the data type.
-   * \tparam T the data type to be read, must be STL composition of JSON serializable.
-   */
-  template<typename T>
-  inline void DeclareField(const std::string &key, T *addr) {
-    DeclareFieldInternal(key, addr, false);
-  }
-  /*!
-   * \brief Declare optional field of type T
-   * \param key the key of the of field.
-   * \param addr address of the data type.
-   * \tparam T the data type to be read, must be STL composition of JSON serializable.
-   */
-  template<typename T>
-  inline void DeclareOptionalField(const std::string &key, T *addr) {
-    DeclareFieldInternal(key, addr, true);
-  }
-  /*!
-   * \brief Read in all the declared fields.
-   * \param reader the JSONReader to read the json.
-   */
-  inline void ReadAllFields(JSONReader *reader);
-
- private:
-  /*!
-   * \brief Internal function to declare field.
-   * \param key the key of the of field.
-   * \param addr address of the data type.
-   * \param optional if set to true, no error will be reported if the key is not presented.
-   * \tparam T the data type to be read, must be STL composition of JSON serializable.
-   */
-  template<typename T>
-  inline void DeclareFieldInternal(const std::string &key, T *addr, bool optional);
-  /*!
-   * \brief The internal reader function.
-   * \param reader The reader to read.
-   * \param addr The memory address to read.
-   */
-  template<typename T>
-  inline static void ReaderFunction(JSONReader *reader, void *addr);
-  /*! \brief callback type to reader function */
-  typedef void (*ReadFunction)(JSONReader *reader, void *addr);
-  /*! \brief internal data entry */
-  struct Entry {
-    /*! \brief the reader function */
-    ReadFunction func;
-    /*! \brief the address to read */
-    void *addr;
-    /*! \brief whether it is optional */
-    bool optional;
-  };
-  /*! \brief the internal map of reader callbacks */
-  std::map<std::string, Entry> map_;
-};
-
-#define DMLC_JSON_ENABLE_ANY_VAR_DEF(KeyName)                  \
-  static DMLC_ATTRIBUTE_UNUSED ::dmlc::json::AnyJSONManager&   \
-  __make_AnyJSONType ## _ ## KeyName ## __
-
-/*!
- * \def DMLC_JSON_ENABLE_ANY
- * \brief Macro to enable save/load JSON of dmlc:: whose actual type is Type.
- * Any type will be saved as json array [KeyName, content]
- *
- * \param Type The type to be registered.
- * \param KeyName The Type key assigned to the type, must be same during load.
- */
-#define DMLC_JSON_ENABLE_ANY(Type, KeyName)                             \
-  DMLC_STR_CONCAT(DMLC_JSON_ENABLE_ANY_VAR_DEF(KeyName), __COUNTER__) = \
-    ::dmlc::json::AnyJSONManager::Global()->EnableType<Type>(#KeyName) \
-
-//! \cond Doxygen_Suppress
-namespace json {
-
-/*!
- * \brief generic serialization handler
- * \tparam T the type to be serialized
- */
-template<typename T>
-struct Handler;
-
-template<typename ValueType>
-struct NumericHandler {
-  inline static void Write(JSONWriter *writer, const ValueType &value) {
-    writer->WriteNumber<ValueType>(value);
-  }
-  inline static void Read(JSONReader *reader, ValueType *value) {
-    reader->ReadNumber<ValueType>(value);
-  }
-};
-
-template<typename ContainerType>
-struct ArrayHandler {
-  inline static void Write(JSONWriter *writer, const ContainerType &array) {
-    typedef typename ContainerType::value_type ElemType;
-    writer->BeginArray(array.size() > 10 || !dmlc::is_pod<ElemType>::value);
-    for (typename ContainerType::const_iterator it = array.begin();
-         it != array.end(); ++it) {
-      writer->WriteArrayItem(*it);
-    }
-    writer->EndArray();
-  }
-  inline static void Read(JSONReader *reader, ContainerType *array) {
-    typedef typename ContainerType::value_type ElemType;
-    array->clear();
-    reader->BeginArray();
-    while (reader->NextArrayItem()) {
-      ElemType value;
-      Handler<ElemType>::Read(reader, &value);
-      array->insert(array->end(), value);
-    }
-  }
-};
-
-template<typename ContainerType>
-struct MapHandler{
-  inline static void Write(JSONWriter *writer, const ContainerType &map) {
-    writer->BeginObject(map.size() > 1);
-    for (typename ContainerType::const_iterator it = map.begin(); it != map.end(); ++it) {
-      writer->WriteObjectKeyValue(it->first, it->second);
-    }
-    writer->EndObject();
-  }
-  inline static void Read(JSONReader *reader, ContainerType *map) {
-    typedef typename ContainerType::mapped_type ElemType;
-    map->clear();
-    reader->BeginObject();
-    std::string key;
-    while (reader->NextObjectItem(&key)) {
-      ElemType value;
-      reader->Read(&value);
-      (*map)[key] = value;
-    }
-  }
-};
-
-template<typename T>
-struct CommonJSONSerializer {
-  inline static void Write(JSONWriter *writer, const T &value) {
-    value.Save(writer);
-  }
-  inline static void Read(JSONReader *reader, T *value) {
-    value->Load(reader);
-  }
-};
-
-template<>
-struct Handler<std::string> {
-  inline static void Write(JSONWriter *writer, const std::string &value) {
-    writer->WriteString(value);
-  }
-  inline static void Read(JSONReader *reader, std::string *str) {
-    reader->ReadString(str);
-  }
-};
-
-template<typename T>
-struct Handler<std::vector<T> > : public ArrayHandler<std::vector<T> > {
-};
-
-template<typename K, typename V>
-struct Handler<std::pair<K, V> > {
-  inline static void Write(JSONWriter *writer, const std::pair<K, V> &kv) {
-    writer->BeginArray();
-    writer->WriteArrayItem(kv.first);
-    writer->WriteArrayItem(kv.second);
-    writer->EndArray();
-  }
-  inline static void Read(JSONReader *reader, std::pair<K, V> *kv) {
-    reader->BeginArray();
-    CHECK(reader->NextArrayItem())
-        << "Expect array of length 2";
-    Handler<K>::Read(reader, &(kv->first));
-    CHECK(reader->NextArrayItem())
-        << "Expect array of length 2";
-    Handler<V>::Read(reader, &(kv->second));
-    CHECK(!reader->NextArrayItem())
-        << "Expect array of length 2";
-  }
-};
-
-template<typename T>
-struct Handler<std::list<T> > : public ArrayHandler<std::list<T> > {
-};
-
-template<typename V>
-struct Handler<std::map<std::string, V> > : public MapHandler<std::map<std::string, V> > {
-};
-
-#if DMLC_USE_CXX11
-template<typename V>
-struct Handler<std::unordered_map<std::string, V> >
-    : public MapHandler<std::unordered_map<std::string, V> > {
-};
-#endif  // DMLC_USE_CXX11
-
-template<typename T>
-struct Handler {
-  inline static void Write(JSONWriter *writer, const T &data) {
-    typedef typename dmlc::IfThenElseType<dmlc::is_arithmetic<T>::value,
-                                          NumericHandler<T>,
-                                          CommonJSONSerializer<T> >::Type THandler;
-    THandler::Write(writer, data);
-  }
-  inline static void Read(JSONReader *reader, T *data) {
-    typedef typename dmlc::IfThenElseType<dmlc::is_arithmetic<T>::value,
-                                          NumericHandler<T>,
-                                          CommonJSONSerializer<T> >::Type THandler;
-    THandler::Read(reader, data);
-  }
-};
-
-#if DMLC_STRICT_CXX11
-// Manager to store json serialization strategy.
-class AnyJSONManager {
- public:
-  template<typename T>
-  inline AnyJSONManager& EnableType(const std::string& type_name) {  // NOLINT(*)
-    std::type_index tp = std::type_index(typeid(T));
-    if (type_name_.count(tp) != 0) {
-      CHECK(type_name_.at(tp) == type_name)
-          << "Type has already been registered as another typename " << type_name_.at(tp);
-      return *this;
-    }
-    CHECK(type_map_.count(type_name) == 0)
-        << "Type name " << type_name << " already registered in registry";
-    Entry e;
-    e.read = ReadAny<T>;
-    e.write = WriteAny<T>;
-    type_name_[tp] = type_name;
-    type_map_[type_name] = e;
-    return *this;
-  }
-  // return global singleton
-  inline static AnyJSONManager* Global() {
-    static AnyJSONManager inst;
-    return &inst;
-  }
-
- private:
-  AnyJSONManager() {}
-
-  template<typename T>
-  inline static void WriteAny(JSONWriter *writer, const any &data) {
-    writer->Write(dmlc::get<T>(data));
-  }
-  template<typename T>
-  inline static void ReadAny(JSONReader *reader, any* data) {
-    T temp;
-    reader->Read(&temp);
-    *data = std::move(temp);
-  }
-  // data entry to store vtable for any type
-  struct Entry {
-    void (*read)(JSONReader* reader, any *data);
-    void (*write)(JSONWriter* reader, const any& data);
-  };
-
-  template<typename T>
-  friend struct Handler;
-
-  std::unordered_map<std::type_index, std::string> type_name_;
-  std::unordered_map<std::string, Entry> type_map_;
-};
-
-template<>
-struct Handler<any> {
-  inline static void Write(JSONWriter *writer, const any &data) {
-    std::unordered_map<std::type_index, std::string>&
-        nmap = AnyJSONManager::Global()->type_name_;
-    std::type_index id = std::type_index(data.type());
-    auto it = nmap.find(id);
-    CHECK(it != nmap.end() && it->first == id)
-        << "Type " << id.name() << " has not been registered via DMLC_JSON_ENABLE_ANY";
-    std::string type_name = it->second;
-    AnyJSONManager::Entry e = AnyJSONManager::Global()->type_map_.at(type_name);
-    writer->BeginArray(false);
-    writer->WriteArrayItem(type_name);
-    writer->WriteArraySeperator();
-    e.write(writer, data);
-    writer->EndArray();
-  }
-  inline static void Read(JSONReader *reader, any *data) {
-    std::string type_name;
-    reader->BeginArray();
-    CHECK(reader->NextArrayItem()) << "invalid any json format";
-    Handler<std::string>::Read(reader, &type_name);
-    std::unordered_map<std::string, AnyJSONManager::Entry>&
-        tmap = AnyJSONManager::Global()->type_map_;
-    auto it = tmap.find(type_name);
-    CHECK(it != tmap.end() && it->first == type_name)
-        << "Typename " << type_name << " has not been registered via DMLC_JSON_ENABLE_ANY";
-    AnyJSONManager::Entry e = it->second;
-    CHECK(reader->NextArrayItem()) << "invalid any json format";
-    e.read(reader, data);
-    CHECK(!reader->NextArrayItem()) << "invalid any json format";
-  }
-};
-#endif  // DMLC_STRICT_CXX11
-
-}  // namespace json
-
-// implementations of JSONReader/Writer
-inline int JSONReader::NextNonSpace() {
-  int ch;
-  do {
-    ch = is_->get();
-    if (ch == '\n') ++line_count_n_;
-    if (ch == '\r') ++line_count_r_;
-  } while (isspace(ch));
-  return ch;
-}
-
-inline int JSONReader::PeekNextNonSpace() {
-  int ch;
-  while (true) {
-    ch = is_->peek();
-    if (ch == '\n') ++line_count_n_;
-    if (ch == '\r') ++line_count_r_;
-    if (!isspace(ch)) break;
-    is_->get();
-  }
-  return ch;
-}
-
-inline void JSONReader::ReadString(std::string *out_str) {
-  int ch = NextNonSpace();
-  CHECK_EQ(ch, '\"')
-      << "Error at" << line_info()
-      << ", Expect \'\"\' but get \'" << static_cast<char>(ch) << '\'';
-  std::ostringstream os;
-  while (true) {
-    ch = is_->get();
-    if (ch == '\\') {
-      char sch = static_cast<char>(is_->get());
-      switch (sch) {
-        case 'r': os << "\r"; break;
-        case 'n': os << "\n"; break;
-        case '\\': os << "\\"; break;
-        case '\t': os << "\t"; break;
-        case '\"': os << "\""; break;
-        default: LOG(FATAL) << "unknown string escape \\" << sch;
-      }
-    } else {
-      if (ch == '\"') break;
-      os << static_cast<char>(ch);
-    }
-    if (ch == EOF || ch == '\r' || ch == '\n') {
-      LOG(FATAL)
-          << "Error at" << line_info()
-          << ", Expect \'\"\' but reach end of line ";
-    }
-  }
-  *out_str = os.str();
-}
-
-template<typename ValueType>
-inline void JSONReader::ReadNumber(ValueType *out_value) {
-  *is_ >> *out_value;
-  CHECK(!is_->fail())
-      << "Error at" << line_info()
-      << ", Expect number";
-}
-
-inline void JSONReader::BeginObject() {
-  int ch = NextNonSpace();
-  CHECK_EQ(ch, '{')
-      << "Error at" << line_info()
-      << ", Expect \'{\' but get \'" << static_cast<char>(ch) << '\'';
-  scope_counter_.push_back(0);
-}
-
-inline void JSONReader::BeginArray() {
-  int ch = NextNonSpace();
-  CHECK_EQ(ch, '[')
-      << "Error at" << line_info()
-      << ", Expect \'{\' but get \'" << static_cast<char>(ch) << '\'';
-  scope_counter_.push_back(0);
-}
-
-inline bool JSONReader::NextObjectItem(std::string *out_key) {
-  bool next = true;
-  if (scope_counter_.back() != 0) {
-    int ch = NextNonSpace();
-    if (ch == EOF) {
-      next = false;
-    } else if (ch == '}') {
-      next = false;
-    } else {
-      CHECK_EQ(ch, ',')
-          << "Error at" << line_info()
-          << ", JSON object expect \'}\' or \',\' \'" << static_cast<char>(ch) << '\'';
-    }
-  } else {
-    int ch = PeekNextNonSpace();
-    if (ch == '}') {
-      is_->get();
-      next = false;
-    }
-  }
-  if (!next) {
-    scope_counter_.pop_back();
-    return false;
-  } else {
-    scope_counter_.back() += 1;
-    ReadString(out_key);
-    int ch = NextNonSpace();
-    CHECK_EQ(ch, ':')
-        << "Error at" << line_info()
-        << ", Expect \':\' but get \'" << static_cast<char>(ch) << '\'';
-    return true;
-  }
-}
-
-inline bool JSONReader::NextArrayItem() {
-  bool next = true;
-  if (scope_counter_.back() != 0) {
-    int ch = NextNonSpace();
-    if (ch == EOF) {
-      next = false;
-    } else if (ch == ']') {
-      next = false;
-    } else {
-      CHECK_EQ(ch, ',')
-          << "Error at" << line_info()
-          << ", JSON array expect \']\' or \',\'. Get \'" << static_cast<char>(ch) << "\' instead";
-    }
-  } else {
-    int ch = PeekNextNonSpace();
-    if (ch == ']') {
-      is_->get();
-      next = false;
-    }
-  }
-  if (!next) {
-    scope_counter_.pop_back();
-    return false;
-  } else {
-    scope_counter_.back() += 1;
-    return true;
-  }
-}
-
-template<typename ValueType>
-inline void JSONReader::Read(ValueType *out_value) {
-  json::Handler<ValueType>::Read(this, out_value);
-}
-
-inline void JSONWriter::WriteNoEscape(const std::string &s) {
-  *os_ << '\"' << s << '\"';
-}
-
-inline void JSONWriter::WriteString(const std::string &s) {
-  std::ostream &os = *os_;
-  os << '\"';
-  for (size_t i = 0; i < s.length(); ++i) {
-    char ch = s[i];
-    switch (ch) {
-      case '\r': os << "\\r"; break;
-      case '\n': os << "\\n"; break;
-      case '\\': os << "\\\\"; break;
-      case '\t': os << "\\t"; break;
-      case '\"': os << "\\\""; break;
-      default: os << ch;
-    }
-  }
-  os << '\"';
-}
-
-template<typename ValueType>
-inline void JSONWriter::WriteNumber(const ValueType &v) {
-  *os_ << v;
-}
-
-inline void JSONWriter::BeginArray(bool multi_line) {
-  *os_ << '[';
-  scope_multi_line_.push_back(multi_line);
-  scope_counter_.push_back(0);
-}
-
-inline void JSONWriter::EndArray() {
-  CHECK_NE(scope_multi_line_.size(), 0U);
-  CHECK_NE(scope_counter_.size(), 0U);
-  bool newline = scope_multi_line_.back();
-  size_t nelem = scope_counter_.back();
-  scope_multi_line_.pop_back();
-  scope_counter_.pop_back();
-  if (newline && nelem != 0) WriteSeperator();
-  *os_ << ']';
-}
-
-inline void JSONWriter::BeginObject(bool multi_line) {
-  *os_ << "{";
-  scope_multi_line_.push_back(multi_line);
-  scope_counter_.push_back(0);
-}
-
-inline void JSONWriter::EndObject() {
-  CHECK_NE(scope_multi_line_.size(), 0U);
-  CHECK_NE(scope_counter_.size(), 0U);
-  bool newline = scope_multi_line_.back();
-  size_t nelem = scope_counter_.back();
-  scope_multi_line_.pop_back();
-  scope_counter_.pop_back();
-  if (newline && nelem != 0) WriteSeperator();
-  *os_ << '}';
-}
-
-template<typename ValueType>
-inline void JSONWriter::WriteObjectKeyValue(const std::string &key,
-                                            const ValueType &value) {
-  std::ostream &os = *os_;
-  if (scope_counter_.back() == 0) {
-    WriteSeperator();
-    os << '\"' << key << "\": ";
-  } else {
-    os << ", ";
-    WriteSeperator();
-    os << '\"' << key << "\": ";
-  }
-  scope_counter_.back() += 1;
-  json::Handler<ValueType>::Write(this, value);
-}
-
-inline void JSONWriter::WriteArraySeperator() {
-  std::ostream &os = *os_;
-  if (scope_counter_.back() != 0) {
-    os << ", ";
-  }
-  scope_counter_.back() += 1;
-  WriteSeperator();
-}
-
-template<typename ValueType>
-inline void JSONWriter::WriteArrayItem(const ValueType &value) {
-  this->WriteArraySeperator();
-  json::Handler<ValueType>::Write(this, value);
-}
-
-template<typename ValueType>
-inline void JSONWriter::Write(const ValueType &value) {
-  size_t nscope = scope_multi_line_.size();
-  json::Handler<ValueType>::Write(this, value);
-  CHECK_EQ(nscope, scope_multi_line_.size())
-      << "Uneven scope, did you call EndArray/EndObject after each BeginObject/Array?";
-}
-
-inline void JSONWriter::WriteSeperator() {
-  if (scope_multi_line_.size() == 0 || scope_multi_line_.back()) {
-    *os_ << '\n' << std::string(scope_multi_line_.size() * 2, ' ');
-  }
-}
-
-inline void JSONObjectReadHelper::ReadAllFields(JSONReader *reader) {
-  reader->BeginObject();
-  std::map<std::string, int> visited;
-  std::string key;
-  while (reader->NextObjectItem(&key)) {
-    if (map_.count(key) != 0) {
-      Entry e = map_[key];
-      (*e.func)(reader, e.addr);
-      visited[key] = 0;
-    } else {
-      std::ostringstream os;
-      os << "JSONReader: Unknown field " << key << ", candidates are: \n";
-      for (std::map<std::string, Entry>::iterator
-               it = map_.begin(); it != map_.end(); ++it) {
-        os << '\"' <<it->first << "\"\n";
-      }
-      LOG(FATAL) << os.str();
-    }
-  }
-  if (visited.size() != map_.size()) {
-    for (std::map<std::string, Entry>::iterator
-             it = map_.begin(); it != map_.end(); ++it) {
-      if (it->second.optional) continue;
-      CHECK_NE(visited.count(it->first), 0U)
-          << "JSONReader: Missing field \"" << it->first << "\"\n At "
-          << reader->line_info();
-    }
-  }
-}
-
-template<typename T>
-inline void JSONObjectReadHelper::ReaderFunction(JSONReader *reader, void *addr) {
-  json::Handler<T>::Read(reader, static_cast<T*>(addr));
-}
-
-template<typename T>
-inline void JSONObjectReadHelper::
-DeclareFieldInternal(const std::string &key, T *addr, bool optional) {
-  CHECK_EQ(map_.count(key), 0U)
-      << "Adding duplicate field " << key;
-  Entry e;
-  e.func = ReaderFunction<T>;
-  e.addr = static_cast<void*>(addr);
-  e.optional = optional;
-  map_[key] = e;
-}
-
-//! \endcond
-}  // namespace dmlc
-#endif  // DMLC_JSON_H_
diff --git a/nnvm/include/dmlc/logging.h b/nnvm/include/dmlc/logging.h
deleted file mode 100644
index 104e5055f147..000000000000
--- a/nnvm/include/dmlc/logging.h
+++ /dev/null
@@ -1,327 +0,0 @@
-/*!
- *  Copyright (c) 2015 by Contributors
- * \file logging.h
- * \brief defines logging macros of dmlc
- *  allows use of GLOG, fall back to internal
- *  implementation when disabled
- */
-#ifndef DMLC_LOGGING_H_
-#define DMLC_LOGGING_H_
-#include <cstdio>
-#include <cstdlib>
-#include <string>
-#include <vector>
-#include <stdexcept>
-#include "./base.h"
-
-#if DMLC_LOG_STACK_TRACE
-#include <execinfo.h>
-#endif
-
-namespace dmlc {
-/*!
- * \brief exception class that will be thrown by
- *  default logger if DMLC_LOG_FATAL_THROW == 1
- */
-struct Error : public std::runtime_error {
-  /*!
-   * \brief constructor
-   * \param s the error message
-   */
-  explicit Error(const std::string &s) : std::runtime_error(s) {}
-};
-}  // namespace dmlc
-
-#if DMLC_USE_GLOG
-#include <glog/logging.h>
-
-namespace dmlc {
-/*!
- * \brief optionally redirect to google's init log
- * \param argv0 The arguments.
- */
-inline void InitLogging(const char* argv0) {
-  google::InitGoogleLogging(argv0);
-}
-}  // namespace dmlc
-
-#else
-// use a light version of glog
-#include <assert.h>
-#include <iostream>
-#include <sstream>
-#include <ctime>
-
-#if defined(_MSC_VER)
-#pragma warning(disable : 4722)
-#endif
-
-namespace dmlc {
-inline void InitLogging(const char* argv0) {
-  // DO NOTHING
-}
-
-class LogCheckError {
- public:
-  LogCheckError() : str(nullptr) {}
-  explicit LogCheckError(const std::string& str_) : str(new std::string(str_)) {}
-  ~LogCheckError() { if (str != nullptr) delete str; }
-  operator bool() {return str != nullptr; }
-  std::string* str;
-};
-
-#define DEFINE_CHECK_FUNC(name, op)                               \
-  template <typename X, typename Y>                               \
-  inline LogCheckError LogCheck##name(const X& x, const Y& y) {   \
-    if (x op y) return LogCheckError();                           \
-    std::ostringstream os;                                        \
-    os << " (" << x << " vs. " << y << ") ";  /* CHECK_XX(x, y) requires x and y can be serialized to string. Use CHECK(x OP y) otherwise. NOLINT(*) */ \
-    return LogCheckError(os.str());                               \
-  }                                                               \
-  inline LogCheckError LogCheck##name(int x, int y) {             \
-    return LogCheck##name<int, int>(x, y);                        \
-  }
-
-#define CHECK_BINARY_OP(name, op, x, y)                               \
-  if (dmlc::LogCheckError _check_err = dmlc::LogCheck##name(x, y))    \
-    dmlc::LogMessageFatal(__FILE__, __LINE__).stream()                \
-      << "Check failed: " << #x " " #op " " #y << *(_check_err.str)
-
-DEFINE_CHECK_FUNC(_LT, <)
-DEFINE_CHECK_FUNC(_GT, >)
-DEFINE_CHECK_FUNC(_LE, <=)
-DEFINE_CHECK_FUNC(_GE, >=)
-DEFINE_CHECK_FUNC(_EQ, ==)
-DEFINE_CHECK_FUNC(_NE, !=)
-
-// Always-on checking
-#define CHECK(x)                                           \
-  if (!(x))                                                \
-    dmlc::LogMessageFatal(__FILE__, __LINE__).stream()     \
-      << "Check failed: " #x << ' '
-#define CHECK_LT(x, y) CHECK_BINARY_OP(_LT, <, x, y)
-#define CHECK_GT(x, y) CHECK_BINARY_OP(_GT, >, x, y)
-#define CHECK_LE(x, y) CHECK_BINARY_OP(_LE, <=, x, y)
-#define CHECK_GE(x, y) CHECK_BINARY_OP(_GE, >=, x, y)
-#define CHECK_EQ(x, y) CHECK_BINARY_OP(_EQ, ==, x, y)
-#define CHECK_NE(x, y) CHECK_BINARY_OP(_NE, !=, x, y)
-#define CHECK_NOTNULL(x) \
-  ((x) == NULL ? dmlc::LogMessageFatal(__FILE__, __LINE__).stream() << "Check  notnull: "  #x << ' ', (x) : (x)) // NOLINT(*)
-// Debug-only checking.
-#ifdef NDEBUG
-#define DCHECK(x) \
-  while (false) CHECK(x)
-#define DCHECK_LT(x, y) \
-  while (false) CHECK((x) < (y))
-#define DCHECK_GT(x, y) \
-  while (false) CHECK((x) > (y))
-#define DCHECK_LE(x, y) \
-  while (false) CHECK((x) <= (y))
-#define DCHECK_GE(x, y) \
-  while (false) CHECK((x) >= (y))
-#define DCHECK_EQ(x, y) \
-  while (false) CHECK((x) == (y))
-#define DCHECK_NE(x, y) \
-  while (false) CHECK((x) != (y))
-#else
-#define DCHECK(x) CHECK(x)
-#define DCHECK_LT(x, y) CHECK((x) < (y))
-#define DCHECK_GT(x, y) CHECK((x) > (y))
-#define DCHECK_LE(x, y) CHECK((x) <= (y))
-#define DCHECK_GE(x, y) CHECK((x) >= (y))
-#define DCHECK_EQ(x, y) CHECK((x) == (y))
-#define DCHECK_NE(x, y) CHECK((x) != (y))
-#endif  // NDEBUG
-
-#if DMLC_LOG_CUSTOMIZE
-#define LOG_INFO dmlc::CustomLogMessage(__FILE__, __LINE__)
-#else
-#define LOG_INFO dmlc::LogMessage(__FILE__, __LINE__)
-#endif
-#define LOG_ERROR LOG_INFO
-#define LOG_WARNING LOG_INFO
-#define LOG_FATAL dmlc::LogMessageFatal(__FILE__, __LINE__)
-#define LOG_QFATAL LOG_FATAL
-
-// Poor man version of VLOG
-#define VLOG(x) LOG_INFO.stream()
-
-#define LOG(severity) LOG_##severity.stream()
-#define LG LOG_INFO.stream()
-#define LOG_IF(severity, condition) \
-  !(condition) ? (void)0 : dmlc::LogMessageVoidify() & LOG(severity)
-
-#ifdef NDEBUG
-#define LOG_DFATAL LOG_ERROR
-#define DFATAL ERROR
-#define DLOG(severity) true ? (void)0 : dmlc::LogMessageVoidify() & LOG(severity)
-#define DLOG_IF(severity, condition) \
-  (true || !(condition)) ? (void)0 : dmlc::LogMessageVoidify() & LOG(severity)
-#else
-#define LOG_DFATAL LOG_FATAL
-#define DFATAL FATAL
-#define DLOG(severity) LOG(severity)
-#define DLOG_IF(severity, condition) LOG_IF(severity, condition)
-#endif
-
-// Poor man version of LOG_EVERY_N
-#define LOG_EVERY_N(severity, n) LOG(severity)
-
-class DateLogger {
- public:
-  DateLogger() {
-#if defined(_MSC_VER)
-    _tzset();
-#endif
-  }
-  const char* HumanDate() {
-#if defined(_MSC_VER)
-    _strtime_s(buffer_, sizeof(buffer_));
-#else
-    time_t time_value = time(NULL);
-    struct tm *pnow;
-#if !defined(_WIN32)
-    struct tm now;
-    pnow = localtime_r(&time_value, &now);
-#else
-    pnow = localtime(&time_value);  // NOLINT(*)
-#endif
-    snprintf(buffer_, sizeof(buffer_), "%02d:%02d:%02d",
-             pnow->tm_hour, pnow->tm_min, pnow->tm_sec);
-#endif
-    return buffer_;
-  }
-
- private:
-  char buffer_[9];
-};
-
-class LogMessage {
- public:
-  LogMessage(const char* file, int line)
-      :
-#ifdef __ANDROID__
-        log_stream_(std::cout)
-#else
-        log_stream_(std::cerr)
-#endif
-  {
-    log_stream_ << "[" << pretty_date_.HumanDate() << "] " << file << ":"
-                << line << ": ";
-  }
-  ~LogMessage() { log_stream_ << '\n'; }
-  std::ostream& stream() { return log_stream_; }
-
- protected:
-  std::ostream& log_stream_;
-
- private:
-  DateLogger pretty_date_;
-  LogMessage(const LogMessage&);
-  void operator=(const LogMessage&);
-};
-
-// customized logger that can allow user to define where to log the message.
-class CustomLogMessage {
- public:
-  CustomLogMessage(const char* file, int line) {
-    log_stream_ << "[" << DateLogger().HumanDate() << "] " << file << ":"
-                << line << ": ";
-  }
-  ~CustomLogMessage() {
-    Log(log_stream_.str());
-  }
-  std::ostream& stream() { return log_stream_; }
-  /*!
-   * \brief customized logging of the message.
-   * This function won't be implemented by libdmlc
-   * \param msg The message to be logged.
-   */
-  static void Log(const std::string& msg);
-
- private:
-  std::ostringstream log_stream_;
-};
-
-#if DMLC_LOG_FATAL_THROW == 0
-class LogMessageFatal : public LogMessage {
- public:
-  LogMessageFatal(const char* file, int line) : LogMessage(file, line) {}
-  ~LogMessageFatal() {
-#if DMLC_LOG_STACK_TRACE
-    const int MAX_STACK_SIZE = 256;
-    void *stack[MAX_STACK_SIZE];
-
-    int nframes = backtrace(stack, MAX_STACK_SIZE);
-    log_stream_ << "\n\n" << "Stack trace returned " << nframes << " entries:\n";
-    char **msgs = backtrace_symbols(stack, nframes);
-    if (msgs != nullptr) {
-      for (int i = 0; i < nframes; ++i) {
-        log_stream_ << "[bt] (" << i << ") " << msgs[i] << "\n";
-      }
-    }
-#endif
-
-    log_stream_ << "\n";
-    abort();
-  }
-
- private:
-  LogMessageFatal(const LogMessageFatal&);
-  void operator=(const LogMessageFatal&);
-};
-#else
-class LogMessageFatal {
- public:
-  LogMessageFatal(const char* file, int line) {
-    log_stream_ << "[" << pretty_date_.HumanDate() << "] " << file << ":"
-                << line << ": ";
-  }
-  std::ostringstream &stream() { return log_stream_; }
-  ~LogMessageFatal() DMLC_THROW_EXCEPTION {
-#if DMLC_LOG_STACK_TRACE
-    const int MAX_STACK_SIZE = 256;
-    void *stack[MAX_STACK_SIZE];
-
-    int nframes = backtrace(stack, MAX_STACK_SIZE);
-    log_stream_ << "\n\n" << "Stack trace returned " << nframes << " entries:\n";
-    char **msgs = backtrace_symbols(stack, nframes);
-    if (msgs != nullptr) {
-      for (int i = 0; i < nframes; ++i) {
-        log_stream_ << "[bt] (" << i << ") " << msgs[i] << "\n";
-      }
-    }
-#endif
-
-    // throwing out of destructor is evil
-    // hopefully we can do it here
-    // also log the message before throw
-#if DMLC_LOG_BEFORE_THROW
-    LOG(ERROR) << log_stream_.str();
-#endif
-    throw Error(log_stream_.str());
-  }
-
- private:
-  std::ostringstream log_stream_;
-  DateLogger pretty_date_;
-  LogMessageFatal(const LogMessageFatal&);
-  void operator=(const LogMessageFatal&);
-};
-#endif
-
-// This class is used to explicitly ignore values in the conditional
-// logging macros.  This avoids compiler warnings like "value computed
-// is not used" and "statement has no effect".
-class LogMessageVoidify {
- public:
-  LogMessageVoidify() {}
-  // This has to be an operator with a precedence lower than << but
-  // higher than "?:". See its usage.
-  void operator&(std::ostream&) {}
-};
-
-}  // namespace dmlc
-
-#endif
-#endif  // DMLC_LOGGING_H_
diff --git a/nnvm/include/dmlc/memory.h b/nnvm/include/dmlc/memory.h
deleted file mode 100644
index 3a2b9b07988f..000000000000
--- a/nnvm/include/dmlc/memory.h
+++ /dev/null
@@ -1,261 +0,0 @@
-/*!
- *  Copyright (c) 2015 by Contributors
- * \file memory.h
- * \brief Additional memory hanlding utilities.
- */
-#ifndef DMLC_MEMORY_H_
-#define DMLC_MEMORY_H_
-
-#include <vector>
-#include "./base.h"
-#include "./logging.h"
-#include "./thread_local.h"
-
-namespace dmlc {
-
-/*!
- * \brief A memory pool that allocate memory of fixed size and alignment.
- * \tparam size The size of each piece.
- * \tparam align The alignment requirement of the memory.
- */
-template<size_t size, size_t align>
-class MemoryPool {
- public:
-  /*! \brief constructor */
-  MemoryPool() {
-    static_assert(align % alignof(LinkedList) == 0,
-                  "alignment requirement failed.");
-    curr_page_.reset(new Page());
-  }
-  /*! \brief allocate a new memory of size */
-  inline void* allocate() {
-    if (head_ != nullptr) {
-      LinkedList* ret = head_;
-      head_ = head_->next;
-      return ret;
-    } else {
-      if (page_ptr_ < kPageSize) {
-        return &(curr_page_->data[page_ptr_++]);
-      } else {
-        allocated_.push_back(std::move(curr_page_));
-        curr_page_.reset(new Page());
-        page_ptr_ = 1;
-        return &(curr_page_->data[0]);
-      }
-    }
-  }
-  /*!
-   * \brief deallocate a piece of memory
-   * \param p The pointer to the memory to be de-allocated.
-   */
-  inline void deallocate(void* p) {
-    LinkedList* ptr = static_cast<LinkedList*>(p);
-    ptr->next = head_;
-    head_ = ptr;
-  }
-
- private:
-  // page size of each member
-  static const int kPageSize = ((1 << 22) / size);
-  // page to be requested.
-  struct Page {
-    typename std::aligned_storage<size, align>::type data[kPageSize];
-  };
-  // internal linked list structure.
-  struct LinkedList {
-    LinkedList* next{nullptr};
-  };
-  // head of free list
-  LinkedList* head_{nullptr};
-  // current free page
-  std::unique_ptr<Page> curr_page_;
-  // pointer to the current free page position.
-  size_t page_ptr_{0};
-  // allocated pages.
-  std::vector<std::unique_ptr<Page> > allocated_;
-};
-
-
-/*!
- * \brief A thread local allocator that get memory from a threadlocal memory pool.
- * This is suitable to allocate objects that do not cross thread.
- * \tparam T the type of the data to be allocated.
- */
-template<typename T>
-class ThreadlocalAllocator {
- public:
-  /*! \brief pointer type */
-  typedef T* pointer;
-  /*! \brief const pointer type */
-  typedef const T* const_ptr;
-  /*! \brief value type */
-  typedef T value_type;
-  /*! \brief default constructor */
-  ThreadlocalAllocator() {}
-  /*!
-   * \brief constructor from another allocator
-   * \param other another allocator
-   * \tparam U another type
-   */
-  template<typename U>
-  ThreadlocalAllocator(const ThreadlocalAllocator<U>& other) {}
-  /*!
-   * \brief allocate memory
-   * \param n number of blocks
-   * \return an uninitialized memory of type T.
-   */
-  inline T* allocate(size_t n) {
-    CHECK_EQ(n, 1);
-    typedef ThreadLocalStore<MemoryPool<sizeof(T), alignof(T)> > Store;
-    return static_cast<T*>(Store::Get()->allocate());
-  }
-  /*!
-   * \brief deallocate memory
-   * \param p a memory to be returned.
-   * \param n number of blocks
-   */
-  inline void deallocate(T* p, size_t n) {
-    CHECK_EQ(n, 1);
-    typedef ThreadLocalStore<MemoryPool<sizeof(T), alignof(T)> > Store;
-    Store::Get()->deallocate(p);
-  }
-};
-
-
-/*!
- * \brief a shared pointer like type that allocate object
- *   from a threadlocal object pool. This object is not thread-safe
- *   but can be faster than shared_ptr in certain usecases.
- * \tparam T the data type.
- */
-template<typename T>
-struct ThreadlocalSharedPtr {
- public:
-  /*! \brief default constructor */
-  ThreadlocalSharedPtr() : block_(nullptr) {}
-  /*!
-   * \brief constructor from nullptr
-   * \param other the nullptr type
-   */
-  ThreadlocalSharedPtr(std::nullptr_t other) : block_(nullptr) {}  // NOLINT(*)
-  /*!
-   * \brief copy constructor
-   * \param other another pointer.
-   */
-  ThreadlocalSharedPtr(const ThreadlocalSharedPtr<T>& other)
-      : block_(other.block_) {
-    IncRef(block_);
-  }
-  /*!
-   * \brief move constructor
-   * \param other another pointer.
-   */
-  ThreadlocalSharedPtr(ThreadlocalSharedPtr<T>&& other)
-      : block_(other.block_) {
-    other.block_ = nullptr;
-  }
-  /*!
-   * \brief destructor
-   */
-  ~ThreadlocalSharedPtr() {
-    DecRef(block_);
-  }
-  /*!
-   * \brief move assignment
-   * \param other another object to be assigned.
-   * \return self.
-   */
-  inline ThreadlocalSharedPtr<T>& operator=(ThreadlocalSharedPtr<T>&& other) {
-    DecRef(block_);
-    block_ = other.block_;
-    other.block_ = nullptr;
-    return *this;
-  }
-  /*!
-   * \brief copy assignment
-   * \param other another object to be assigned.
-   * \return self.
-   */
-  inline ThreadlocalSharedPtr<T> &operator=(const ThreadlocalSharedPtr<T>& other) {
-    DecRef(block_);
-    block_ = other.block_;
-    IncRef(block_);
-    return *this;
-  }
-  /*! \brief check if nullptr */
-  inline bool operator==(std::nullptr_t other) const {
-    return block_ == nullptr;
-  }
-  /*!
-   * \return get the pointer content.
-   */
-  inline T* get() const {
-    if (block_ == nullptr) return nullptr;
-    return reinterpret_cast<T*>(&(block_->data));
-  }
-  /*!
-   * \brief reset the pointer to nullptr.
-   */
-  inline void reset() {
-    DecRef(block_);
-    block_ = nullptr;
-  }
-  /*! \return if use_count == 1*/
-  inline bool unique() const {
-    if (block_ == nullptr) return false;
-    return block_->use_count_ == 1;
-  }
-  /*! \return dereference pointer */
-  inline T* operator*() const {
-    return reinterpret_cast<T*>(&(block_->data));
-  }
-  /*! \return dereference pointer */
-  inline T* operator->() const {
-    return reinterpret_cast<T*>(&(block_->data));
-  }
-  /*!
-   * \brief create a new space from threadlocal storage and return it.
-   * \tparam Args the arguments.
-   * \param args The input argument
-   * \return the allocated pointer.
-   */
-  template <typename... Args>
-  inline static ThreadlocalSharedPtr<T> Create(Args&&... args) {
-    ThreadlocalAllocator<RefBlock> arena;
-    ThreadlocalSharedPtr<T> p;
-    p.block_ = arena.allocate(1);
-    p.block_->use_count_ = 1;
-    new (&(p.block_->data)) T(std::forward<Args>(args)...);
-    return p;
-  }
-
- private:
-  // internal reference block
-  struct RefBlock {
-    typename std::aligned_storage<sizeof(T), alignof(T)>::type data;
-    unsigned use_count_;
-  };
-  // decrease ref counter
-  inline static void DecRef(RefBlock* block) {
-    if (block != nullptr) {
-      if (--block->use_count_ == 0) {
-        ThreadlocalAllocator<RefBlock> arena;
-        T* dptr = reinterpret_cast<T*>(&(block->data));
-        dptr->~T();
-        arena.deallocate(block, 1);
-      }
-    }
-  }
-  // increase ref counter
-  inline static void IncRef(RefBlock* block) {
-    if (block != nullptr) {
-      ++block->use_count_;
-    }
-  }
-  // internal block
-  RefBlock *block_;
-};
-
-}  // namespace dmlc
-
-#endif  // DMLC_MEMORY_H_
diff --git a/nnvm/include/dmlc/optional.h b/nnvm/include/dmlc/optional.h
deleted file mode 100644
index 6b72dc419e13..000000000000
--- a/nnvm/include/dmlc/optional.h
+++ /dev/null
@@ -1,181 +0,0 @@
-/*!
- * Copyright (c) 2016 by Contributors
- * \file optional.h
- * \brief Container to hold optional data.
- */
-#ifndef DMLC_OPTIONAL_H_
-#define DMLC_OPTIONAL_H_
-
-#include <iostream>
-#include <utility>
-#include <algorithm>
-
-#include "./base.h"
-#include "./logging.h"
-#include "./type_traits.h"
-
-namespace dmlc {
-
-/*! \brief dummy type for assign null to optional */
-struct nullopt_t {
-#if defined(_MSC_VER) && _MSC_VER < 1900
-  /*! \brief dummy constructor */
-  explicit nullopt_t(int) {}
-#else
-  /*! \brief dummy constructor */
-  constexpr nullopt_t(int) {}
-#endif
-};
-
-/*! Assign null to optional: optional<T> x = nullopt; */
-constexpr const nullopt_t nullopt = nullopt_t(0);
-
-/*!
- * \brief c++17 compatible optional class.
- *
- * At any time an optional<T> instance either
- * hold no value (string representation "None")
- * or hold a value of type T.
- */
-template<typename T>
-class optional {
- public:
-  /*! \brief construct an optional object that contains no value */
-  optional() : is_none(true) {}
-  /*! \brief construct an optional object with value */
-  explicit optional(const T& value) {
-    is_none = false;
-    new (&val) T(value);
-  }
-  /*! \brief construct an optional object with another optional object */
-  optional(const optional<T>& other) {
-    is_none = other.is_none;
-    if (!is_none) {
-      new (&val) T(other.value());
-    }
-  }
-  /*! \brief deconstructor */
-  ~optional() {
-    if (!is_none) {
-      reinterpret_cast<T*>(&val)->~T();
-    }
-  }
-  /*! \brief swap two optional */
-  void swap(optional<T>& other) {
-    std::swap(val, other.val);
-    std::swap(is_none, other.is_none);
-  }
-  /*! \brief set this object to hold value
-   *  \param value the value to hold
-   *  \return return self to support chain assignment
-   */
-  optional<T>& operator=(const T& value) {
-    (optional<T>(value)).swap(*this);
-    return *this;
-  }
-  /*! \brief set this object to hold the same value with other
-   *  \param other the other object
-   *  \return return self to support chain assignment
-   */
-  optional<T>& operator=(const optional<T> &other) {
-    (optional<T>(other)).swap(*this);
-    return *this;
-  }
-  /*! \brief clear the value this object is holding.
-   *         optional<T> x = nullopt;
-   */
-  optional<T>& operator=(nullopt_t) {
-    (optional<T>()).swap(*this);
-    return *this;
-  }
-  /*! \brief non-const dereference operator */
-  T& operator*() {  // NOLINT(*)
-    return *reinterpret_cast<T*>(&val);
-  }
-  /*! \brief const dereference operator */
-  const T& operator*() const {
-    return *reinterpret_cast<const T*>(&val);
-  }
-  /*! \brief return the holded value.
-   *         throws std::logic_error if holding no value
-   */
-  const T& value() const {
-    if (is_none) {
-      throw std::logic_error("bad optional access");
-    }
-    return *reinterpret_cast<const T*>(&val);
-  }
-  /*! \brief whether this object is holding a value */
-  explicit operator bool() const { return !is_none; }
-
- private:
-  // whether this is none
-  bool is_none;
-  // on stack storage of value
-  typename std::aligned_storage<sizeof(T), alignof(T)>::type val;
-};
-
-/*! \brief serialize an optional object to string.
- *
- *  \code
- *    dmlc::optional<int> x;
- *    std::cout << x;  // None
- *    x = 0;
- *    std::cout << x;  // 0
- *  \endcode
- *
- *  \param os output stream
- *  \param t source optional<T> object
- *  \return output stream
- */
-template<typename T>
-std::ostream &operator<<(std::ostream &os, const optional<T> &t) {
-  if (t) {
-    os << *t;
-  } else {
-    os << "None";
-  }
-  return os;
-}
-
-/*! \brief parse a string object into optional<T>
- *
- *  \code
- *    dmlc::optional<int> x;
- *    std::string s1 = "1";
- *    std::istringstream is1(s1);
- *    s1 >> x;  // x == optional<int>(1)
- *
- *    std::string s2 = "None";
- *    std::istringstream is2(s2);
- *    s2 >> x;  // x == optional<int>()
- *  \endcode
- *
- *  \param is input stream
- *  \param t target optional<T> object
- *  \return input stream
- */
-template<typename T>
-std::istream &operator>>(std::istream &is, optional<T> &t) {
-  char buf[4];
-  std::streampos origin = is.tellg();
-  is.read(buf, 4);
-  if (is.fail() || buf[0] != 'N' || buf[1] != 'o' ||
-      buf[2] != 'n' || buf[3] != 'e') {
-    is.clear();
-    is.seekg(origin);
-    T x;
-    is >> x;
-    t = x;
-  } else {
-    t = nullopt;
-  }
-  return is;
-}
-
-/*! \brief description for optional int */
-DMLC_DECLARE_TYPE_NAME(optional<int>, "int or None");
-
-}  // namespace dmlc
-
-#endif  // DMLC_OPTIONAL_H_
diff --git a/nnvm/include/dmlc/parameter.h b/nnvm/include/dmlc/parameter.h
deleted file mode 100644
index 717684c79aab..000000000000
--- a/nnvm/include/dmlc/parameter.h
+++ /dev/null
@@ -1,966 +0,0 @@
-/*!
- * Copyright (c) 2015 by Contributors
- * \file parameter.h
- * \brief Provide lightweight util to do parameter setup and checking.
- */
-#ifndef DMLC_PARAMETER_H_
-#define DMLC_PARAMETER_H_
-
-#include <cstddef>
-#include <cstdlib>
-#include <sstream>
-#include <limits>
-#include <map>
-#include <set>
-#include <typeinfo>
-#include <string>
-#include <vector>
-#include <algorithm>
-#include <utility>
-#include <iostream>
-#include "./base.h"
-#include "./json.h"
-#include "./logging.h"
-#include "./type_traits.h"
-#include "./optional.h"
-
-namespace dmlc {
-// this file is backward compatible with non-c++11
-/*! \brief Error throwed by parameter checking */
-struct ParamError : public dmlc::Error {
-  /*!
-   * \brief constructor
-   * \param msg error message
-   */
-  explicit ParamError(const std::string &msg)
-      : dmlc::Error(msg) {}
-};
-
-/*!
- * \brief Get environment variable with default.
- * \param key the name of environment variable.
- * \param default_value the default value of environment vriable.
- * \return The value received
- */
-template<typename ValueType>
-inline ValueType GetEnv(const char *key,
-                        ValueType default_value);
-
-/*! \brief internal namespace for parameter manangement */
-namespace parameter {
-// forward declare ParamManager
-class ParamManager;
-// forward declare FieldAccessEntry
-class FieldAccessEntry;
-// forward declare FieldEntry
-template<typename DType>
-class FieldEntry;
-// forward declare ParamManagerSingleton
-template<typename PType>
-struct ParamManagerSingleton;
-
-/*! \brief option in parameter initialization */
-enum ParamInitOption {
-  /*! \brief allow unknown parameters */
-  kAllowUnknown,
-  /*! \brief need to match exact parameters */
-  kAllMatch,
-  /*! \brief allow unmatched hidden field with format __*__ */
-  kAllowHidden
-};
-}  // namespace parameter
-/*!
- * \brief Information about a parameter field in string representations.
- */
-struct ParamFieldInfo {
-  /*! \brief name of the field */
-  std::string name;
-  /*! \brief type of the field in string format */
-  std::string type;
-  /*!
-   * \brief detailed type information string
-   *  This include the default value, enum constran and typename.
-   */
-  std::string type_info_str;
-  /*! \brief detailed description of the type */
-  std::string description;
-};
-
-/*!
- * \brief Parameter is the base type every parameter struct should inheritate from
- * The following code is a complete example to setup parameters.
- * \code
- *   struct Param : public dmlc::Parameter<Param> {
- *     float learning_rate;
- *     int num_hidden;
- *     std::string name;
- *     // declare parameters in header file
- *     DMLC_DECLARE_PARAMETER(Param) {
- *       DMLC_DECLARE_FIELD(num_hidden).set_range(0, 1000);
- *       DMLC_DECLARE_FIELD(learning_rate).set_default(0.01f);
- *       DMLC_DECLARE_FIELD(name).set_default("hello");
- *     }
- *   };
- *   // register it in cc file
- *   DMLC_REGISTER_PARAMETER(Param);
- * \endcode
- *
- *  After that, the Param struct will get all the functions defined in Parameter.
- * \tparam PType the type of parameter struct
- *
- * \sa DMLC_DECLARE_FIELD, DMLC_REGISTER_PARAMETER, DMLC_DECLARE_PARAMETER
- */
-template<typename PType>
-struct Parameter {
- public:
-  /*!
-   * \brief initialize the parameter by keyword arguments.
-   *  This function will initialize the parameter struct, check consistency
-   *  and throw error if something wrong happens.
-   *
-   * \param kwargs map of keyword arguments, or vector of pairs
-   * \parma option The option on initialization.
-   * \tparam Container container type
-   * \throw ParamError when something go wrong.
-   */
-  template<typename Container>
-  inline void Init(const Container &kwargs,
-                   parameter::ParamInitOption option = parameter::kAllowHidden) {
-    PType::__MANAGER__()->RunInit(static_cast<PType*>(this),
-                                  kwargs.begin(), kwargs.end(),
-                                  NULL,
-                                  option);
-  }
-  /*!
-   * \brief initialize the parameter by keyword arguments.
-   *  This is same as Init, but allow unknown arguments.
-   *
-   * \param kwargs map of keyword arguments, or vector of pairs
-   * \tparam Container container type
-   * \throw ParamError when something go wrong.
-   * \return vector of pairs of unknown arguments.
-   */
-  template<typename Container>
-  inline std::vector<std::pair<std::string, std::string> >
-  InitAllowUnknown(const Container &kwargs) {
-    std::vector<std::pair<std::string, std::string> > unknown;
-    PType::__MANAGER__()->RunInit(static_cast<PType*>(this),
-                                  kwargs.begin(), kwargs.end(),
-                                  &unknown, parameter::kAllowUnknown);
-    return unknown;
-  }
-  /*!
-   * \brief Return a dictionary representation of the parameters
-   * \return A dictionary that maps key -> value
-   */
-  inline std::map<std::string, std::string> __DICT__() const {
-    std::vector<std::pair<std::string, std::string> > vec
-        = PType::__MANAGER__()->GetDict(this->head());
-    return std::map<std::string, std::string>(vec.begin(), vec.end());
-  }
-  /*!
-   * \brief Write the parameters in JSON format.
-   * \param writer JSONWriter used for writing.
-   */
-  inline void Save(dmlc::JSONWriter *writer) const {
-    writer->Write(this->__DICT__());
-  }
-  /*!
-   * \brief Load the parameters from JSON.
-   * \param reader JSONReader used for loading.
-   * \throw ParamError when something go wrong.
-   */
-  inline void Load(dmlc::JSONReader *reader) {
-    std::map<std::string, std::string> kwargs;
-    reader->Read(&kwargs);
-    this->Init(kwargs);
-  }
-  /*!
-   * \brief Get the fields of the parameters.
-   * \return List of ParamFieldInfo of each field.
-   */
-  inline static std::vector<ParamFieldInfo> __FIELDS__() {
-    return PType::__MANAGER__()->GetFieldInfo();
-  }
-  /*!
-   * \brief Print docstring of the parameter
-   * \return the printed docstring
-   */
-  inline static std::string __DOC__() {
-    std::ostringstream os;
-    PType::__MANAGER__()->PrintDocString(os);
-    return os.str();
-  }
-
- protected:
-  /*!
-   * \brief internal function to allow declare of a parameter memember
-   * \param manager the parameter manager
-   * \param key the key name of the parameter
-   * \param ref the reference to the parameter in the struct.
-   */
-  template<typename DType>
-  inline parameter::FieldEntry<DType>& DECLARE(
-      parameter::ParamManagerSingleton<PType> *manager,
-      const std::string &key, DType &ref) { // NOLINT(*)
-    parameter::FieldEntry<DType> *e =
-        new parameter::FieldEntry<DType>();
-    e->Init(key, this->head(), ref);
-    manager->manager.AddEntry(key, e);
-    return *e;
-  }
-
- private:
-  /*! \return Get head pointer of child structure */
-  inline PType *head() const {
-    return static_cast<PType*>(const_cast<Parameter<PType>*>(this));
-  }
-};
-
-//! \cond Doxygen_Suppress
-/*!
- * \brief macro used to declare parameter
- *
- * Example:
- * \code
- *   struct Param : public dmlc::Parameter<Param> {
- *     // declare parameters in header file
- *     DMLC_DECLARE_PARAMETER(Param) {
- *        // details of declarations
- *     }
- *   };
- * \endcode
- *
- * This macro need to be put in a source file so that registeration only happens once.
- * Refer to example code in Parameter for details
- *
- * \param PType the name of parameter struct.
- * \sa Parameter
- */
-#define DMLC_DECLARE_PARAMETER(PType)                                   \
-  static ::dmlc::parameter::ParamManager *__MANAGER__();                \
-  inline void __DECLARE__(::dmlc::parameter::ParamManagerSingleton<PType> *manager) \
-
-/*!
- * \brief macro to declare fields
- * \param FieldName the name of the field.
- */
-#define DMLC_DECLARE_FIELD(FieldName)  this->DECLARE(manager, #FieldName, FieldName)
-
-/*!
- * \brief macro to declare alias of a fields
- * \param FieldName the name of the field.
- * \param AliasName the name of the alias, must be declared after the field is declared.
- */
-#define DMLC_DECLARE_ALIAS(FieldName, AliasName)  manager->manager.AddAlias(#FieldName, #AliasName)
-
-/*!
- * \brief Macro used to register parameter.
- *
- * This macro need to be put in a source file so that registeration only happens once.
- * Refer to example code in Parameter for details
- * \param PType the type of parameter struct.
- * \sa Parameter
- */
-#define DMLC_REGISTER_PARAMETER(PType)                                  \
-  ::dmlc::parameter::ParamManager *PType::__MANAGER__() {               \
-    static ::dmlc::parameter::ParamManagerSingleton<PType> inst(#PType); \
-    return &inst.manager;                                               \
-  }                                                                     \
-  static DMLC_ATTRIBUTE_UNUSED ::dmlc::parameter::ParamManager&         \
-  __make__ ## PType ## ParamManager__ =                                 \
-      (*PType::__MANAGER__())                                           \
-
-//! \endcond
-/*!
- * \brief internal namespace for parameter manangement
- * There is no need to use it directly in normal case
- */
-namespace parameter {
-/*!
- * \brief FieldAccessEntry interface to help manage the parameters
- *  Each entry can be used to access one parameter in the Parameter struct.
- *
- *  This is an internal interface used that is used to manage parameters
- */
-class FieldAccessEntry {
- public:
-  FieldAccessEntry()
-      : has_default_(false) {}
-  /*! \brief destructor */
-  virtual ~FieldAccessEntry() {}
-  /*!
-   * \brief set the default value.
-   * \param head the pointer to the head of the struct
-   * \throw error if no default is presented
-   */
-  virtual void SetDefault(void *head) const = 0;
-  /*!
-   * \brief set the parameter by string value
-   * \param head the pointer to the head of the struct
-   * \param value the value to be set
-   */
-  virtual void Set(void *head, const std::string &value) const = 0;
-  // check if value is OK
-  virtual void Check(void *head) const {}
-  /*!
-   * \brief get the string representation of value.
-   * \param head the pointer to the head of the struct
-   */
-  virtual std::string GetStringValue(void *head) const = 0;
-  /*!
-   * \brief Get field information
-   * \return the corresponding field information
-   */
-  virtual ParamFieldInfo GetFieldInfo() const = 0;
-
- protected:
-  /*! \brief whether this parameter have default value */
-  bool has_default_;
-  /*! \brief positional index of parameter in struct */
-  size_t index_;
-  /*! \brief parameter key name */
-  std::string key_;
-  /*! \brief parameter type */
-  std::string type_;
-  /*! \brief description of the parameter */
-  std::string description_;
-  /*!
-   * \brief print string representation of default value
-   * \parma os the stream to print the docstring to.
-   */
-  virtual void PrintDefaultValueString(std::ostream &os) const = 0;  // NOLINT(*)
-  // allow ParamManager to modify self
-  friend class ParamManager;
-};
-
-/*!
- * \brief manager class to handle parameter setting for each type
- *  An manager will be created for each parameter types.
- */
-class ParamManager {
- public:
-  /*! \brief destructor */
-  ~ParamManager() {
-    for (size_t i = 0; i < entry_.size(); ++i) {
-      delete entry_[i];
-    }
-  }
-  /*!
-   * \brief find the access entry by parameter key
-   * \param key the key of the parameter.
-   * \return pointer to FieldAccessEntry, NULL if nothing is found.
-   */
-  inline FieldAccessEntry *Find(const std::string &key) const {
-    std::map<std::string, FieldAccessEntry*>::const_iterator it =
-        entry_map_.find(key);
-    if (it == entry_map_.end()) return NULL;
-    return it->second;
-  }
-  /*!
-   * \brief set parameter by keyword arguments.
-   * \param head head to the parameter field.
-   * \param begin begin iterator of original kwargs
-   * \param end end iterator of original kwargs
-   * \param unknown_args optional, used to hold unknown arguments
-   *          When it is specified, unknown arguments will be stored into here, instead of raise an error
-   * \tparam RandomAccessIterator iterator type
-   * \throw ParamError when there is unknown argument and unknown_args == NULL, or required argument is missing.
-   */
-  template<typename RandomAccessIterator>
-  inline void RunInit(void *head,
-                      RandomAccessIterator begin,
-                      RandomAccessIterator end,
-                      std::vector<std::pair<std::string, std::string> > *unknown_args,
-                      parameter::ParamInitOption option) const {
-    std::set<FieldAccessEntry*> selected_args;
-    for (RandomAccessIterator it = begin; it != end; ++it) {
-      FieldAccessEntry *e = Find(it->first);
-      if (e != NULL) {
-        e->Set(head, it->second);
-        e->Check(head);
-        selected_args.insert(e);
-      } else {
-        if (unknown_args != NULL) {
-          unknown_args->push_back(*it);
-        } else {
-          if (option != parameter::kAllowUnknown) {
-            if (option == parameter::kAllowHidden &&
-                it->first.length() > 4 &&
-                it->first.find("__") == 0 &&
-                it->first.rfind("__") == it->first.length()-2) {
-              continue;
-            }
-            std::ostringstream os;
-            os << "Cannot find argument \'" << it->first << "\', Possible Arguments:\n";
-            os << "----------------\n";
-            PrintDocString(os);
-            throw dmlc::ParamError(os.str());
-          }
-        }
-      }
-    }
-
-    for (std::map<std::string, FieldAccessEntry*>::const_iterator it = entry_map_.begin();
-         it != entry_map_.end(); ++it) {
-      if (selected_args.count(it->second) == 0) {
-        it->second->SetDefault(head);
-      }
-    }
-  }
-  /*!
-   * \brief internal function to add entry to manager,
-   *  The manager will take ownership of the entry.
-   * \param key the key to the parameters
-   * \param e the pointer to the new entry.
-   */
-  inline void AddEntry(const std::string &key, FieldAccessEntry *e) {
-    e->index_ = entry_.size();
-    // TODO(bing) better error message
-    if (entry_map_.count(key) != 0) {
-      LOG(FATAL) << "key " << key << " has already been registered in " << name_;
-    }
-    entry_.push_back(e);
-    entry_map_[key] = e;
-  }
-  /*!
-   * \brief internal function to add entry to manager,
-   *  The manager will take ownership of the entry.
-   * \param key the key to the parameters
-   * \param e the pointer to the new entry.
-   */
-  inline void AddAlias(const std::string& field, const std::string& alias) {
-    if (entry_map_.count(field) == 0) {
-      LOG(FATAL) << "key " << field << " has not been registered in " << name_;
-    }
-    if (entry_map_.count(alias) != 0) {
-      LOG(FATAL) << "Alias " << alias << " has already been registered in " << name_;
-    }
-    entry_map_[alias] = entry_map_[field];
-  }
-  /*!
-   * \brief set the name of parameter manager
-   * \param name the name to set
-   */
-  inline void set_name(const std::string &name) {
-    name_ = name;
-  }
-  /*!
-   * \brief get field information of each field.
-   * \return field information
-   */
-  inline std::vector<ParamFieldInfo> GetFieldInfo() const {
-    std::vector<ParamFieldInfo> ret(entry_.size());
-    for (size_t i = 0; i < entry_.size(); ++i) {
-      ret[i] = entry_[i]->GetFieldInfo();
-    }
-    return ret;
-  }
-  /*!
-   * \brief Print readible docstring to ostream, add newline.
-   * \parma os the stream to print the docstring to.
-   */
-  inline void PrintDocString(std::ostream &os) const {  // NOLINT(*)
-    for (size_t i = 0; i < entry_.size(); ++i) {
-      ParamFieldInfo info = entry_[i]->GetFieldInfo();
-      os << info.name << " : " << info.type_info_str << '\n';
-      if (info.description.length() != 0) {
-        os << "    " << info.description << '\n';
-      }
-    }
-  }
-  /*!
-   * \brief Get internal parameters in vector of pairs.
-   * \param head the head of the struct.
-   * \param skip_default skip the values that equals default value.
-   * \return the parameter dictionary.
-   */
-  inline std::vector<std::pair<std::string, std::string> > GetDict(void * head) const {
-    std::vector<std::pair<std::string, std::string> > ret;
-    for (std::map<std::string, FieldAccessEntry*>::const_iterator
-            it = entry_map_.begin(); it != entry_map_.end(); ++it) {
-      ret.push_back(std::make_pair(it->first, it->second->GetStringValue(head)));
-    }
-    return ret;
-  }
-
- private:
-  /*! \brief parameter struct name */
-  std::string name_;
-  /*! \brief positional list of entries */
-  std::vector<FieldAccessEntry*> entry_;
-  /*! \brief map of key to entry */
-  std::map<std::string, FieldAccessEntry*> entry_map_;
-};
-
-//! \cond Doxygen_Suppress
-
-// The following piece of code will be template heavy and less documented
-// singleton parameter manager for certain type, used for initialization
-template<typename PType>
-struct ParamManagerSingleton {
-  ParamManager manager;
-  explicit ParamManagerSingleton(const std::string &param_name) {
-    PType param;
-    param.__DECLARE__(this);
-    manager.set_name(param_name);
-  }
-};
-
-// Base class of FieldEntry
-// implement set_default
-template<typename TEntry, typename DType>
-class FieldEntryBase : public FieldAccessEntry {
- public:
-  // entry type
-  typedef TEntry EntryType;
-  // implement set value
-  virtual void Set(void *head, const std::string &value) const {
-    std::istringstream is(value);
-    is >> this->Get(head);
-    if (!is.fail()) {
-      while (!is.eof()) {
-        int ch = is.get();
-        if (ch == EOF) {
-          is.clear(); break;
-        }
-        if (!isspace(ch)) {
-          is.setstate(std::ios::failbit); break;
-        }
-      }
-    }
-
-    if (is.fail()) {
-      std::ostringstream os;
-      os << "Invalid Parameter format for " << key_
-         << " expect " << type_ << " but value=\'" << value<< '\'';
-      throw dmlc::ParamError(os.str());
-    }
-  }
-  virtual std::string GetStringValue(void *head) const {
-    std::ostringstream os;
-    PrintValue(os, this->Get(head));
-    return os.str();
-  }
-  virtual ParamFieldInfo GetFieldInfo() const {
-    ParamFieldInfo info;
-    std::ostringstream os;
-    info.name = key_;
-    info.type = type_;
-    os << type_;
-    if (has_default_) {
-      os << ',' << " optional, default=";
-      PrintDefaultValueString(os);
-    } else {
-      os << ", required";
-    }
-    info.type_info_str = os.str();
-    info.description = description_;
-    return info;
-  }
-  // implement set head to default value
-  virtual void SetDefault(void *head) const {
-    if (!has_default_) {
-      std::ostringstream os;
-      os << "Required parameter " << key_
-         << " of " << type_ << " is not presented";
-      throw dmlc::ParamError(os.str());
-    } else {
-      this->Get(head) = default_value_;
-    }
-  }
-  // return reference of self as derived type
-  inline TEntry &self() {
-    return *(static_cast<TEntry*>(this));
-  }
-  // implement set_default
-  inline TEntry &set_default(const DType &default_value) {
-    default_value_ = default_value;
-    has_default_ = true;
-    // return self to allow chaining
-    return this->self();
-  }
-  // implement describe
-  inline TEntry &describe(const std::string &description) {
-    description_ = description;
-    // return self to allow chaining
-    return this->self();
-  }
-  // initialization function
-  inline void Init(const std::string &key,
-                   void *head, DType &ref) { // NOLINT(*)
-    this->key_ = key;
-    if (this->type_.length() == 0) {
-      this->type_ = dmlc::type_name<DType>();
-    }
-    this->offset_ = ((char*)&ref) - ((char*)head);  // NOLINT(*)
-  }
-
- protected:
-  // print the value
-  virtual void PrintValue(std::ostream &os, DType value) const { // NOLINT(*)
-    os << value;
-  }
-  virtual void PrintDefaultValueString(std::ostream &os) const {  // NOLINT(*)
-    PrintValue(os, default_value_);
-  }
-  // get the internal representation of parameter
-  // for example if this entry corresponds field param.learning_rate
-  // then Get(&param) will return reference to param.learning_rate
-  inline DType &Get(void *head) const {
-    return *(DType*)((char*)(head) + offset_);  // NOLINT(*)
-  }
-  // internal offset of the field
-  ptrdiff_t offset_;
-  // default value of field
-  DType default_value_;
-};
-
-// parameter base for numeric types that have range
-template<typename TEntry, typename DType>
-class FieldEntryNumeric
-    : public FieldEntryBase<TEntry, DType> {
- public:
-  FieldEntryNumeric()
-      : has_begin_(false), has_end_(false) {}
-  // implement set_range
-  virtual TEntry &set_range(DType begin, DType end) {
-    begin_ = begin; end_ = end;
-    has_begin_ = true; has_end_ = true;
-    return this->self();
-  }
-  // implement set_range
-  virtual TEntry &set_lower_bound(DType begin) {
-    begin_ = begin; has_begin_ = true;
-    return this->self();
-  }
-  // consistency check for numeric ranges
-  virtual void Check(void *head) const {
-    FieldEntryBase<TEntry, DType>::Check(head);
-    DType v = this->Get(head);
-    if (has_begin_ && has_end_) {
-      if (v < begin_ || v > end_) {
-        std::ostringstream os;
-        os << "value " << v << "for Parameter " << this->key_
-           << " exceed bound [" << begin_ << ',' << end_ <<']';
-        throw dmlc::ParamError(os.str());
-      }
-    } else if (has_begin_ && v < begin_) {
-        std::ostringstream os;
-        os << "value " << v << "for Parameter " << this->key_
-           << " should be greater equal to " << begin_;
-        throw dmlc::ParamError(os.str());
-    } else if (has_end_ && v > end_) {
-        std::ostringstream os;
-        os << "value " << v << "for Parameter " << this->key_
-           << " should be smaller equal to " << end_;
-        throw dmlc::ParamError(os.str());
-    }
-  }
-
- protected:
-  // whether it have begin and end range
-  bool has_begin_, has_end_;
-  // data bound
-  DType begin_, end_;
-};
-
-/*!
- * \brief FieldEntry defines parsing and checking behavior of DType.
- * This class can be specialized to implement specific behavior of more settings.
- * \tparam DType the data type of the entry.
- */
-template<typename DType>
-class FieldEntry :
-      public IfThenElseType<dmlc::is_arithmetic<DType>::value,
-                            FieldEntryNumeric<FieldEntry<DType>, DType>,
-                            FieldEntryBase<FieldEntry<DType>, DType> >::Type {
-};
-
-// specialize define for int(enum)
-template<>
-class FieldEntry<int>
-    : public FieldEntryNumeric<FieldEntry<int>, int> {
- public:
-  // construct
-  FieldEntry<int>() : is_enum_(false) {}
-  // parent
-  typedef FieldEntryNumeric<FieldEntry<int>, int> Parent;
-  // override set
-  virtual void Set(void *head, const std::string &value) const {
-    if (is_enum_) {
-      std::map<std::string, int>::const_iterator it = enum_map_.find(value);
-      std::ostringstream os;
-      if (it == enum_map_.end()) {
-        os << "Invalid Input: \'" << value;
-        os << "\', valid values are: ";
-        PrintEnums(os);
-        throw dmlc::ParamError(os.str());
-      } else {
-        os << it->second;
-        Parent::Set(head, os.str());
-      }
-    } else {
-      Parent::Set(head, value);
-    }
-  }
-  virtual ParamFieldInfo GetFieldInfo() const {
-    if (is_enum_) {
-      ParamFieldInfo info;
-      std::ostringstream os;
-      info.name = key_;
-      info.type = type_;
-      PrintEnums(os);
-      if (has_default_) {
-        os << ',' << "optional, default=";
-        PrintDefaultValueString(os);
-      } else {
-        os << ", required";
-      }
-      info.type_info_str = os.str();
-      info.description = description_;
-      return info;
-    } else {
-      return Parent::GetFieldInfo();
-    }
-  }
-  // add enum
-  inline FieldEntry<int> &add_enum(const std::string &key, int value) {
-    if ((enum_map_.size() != 0 && enum_map_.count(key) != 0) || \
-        enum_back_map_.count(value) != 0) {
-      std::ostringstream os;
-      os << "Enum " << "(" << key << ": " << value << " exisit!" << ")\n";
-      os << "Enums: ";
-      for (std::map<std::string, int>::const_iterator it = enum_map_.begin();
-           it != enum_map_.end(); ++it) {
-        os << "(" << it->first << ": " << it->second << "), ";
-      }
-      throw dmlc::ParamError(os.str());
-    }
-    enum_map_[key] = value;
-    enum_back_map_[value] = key;
-    is_enum_ = true;
-    return this->self();
-  }
-
- protected:
-  // enum flag
-  bool is_enum_;
-  // enum map
-  std::map<std::string, int> enum_map_;
-  // enum map
-  std::map<int, std::string> enum_back_map_;
-  // override print behavior
-  virtual void PrintDefaultValueString(std::ostream &os) const { // NOLINT(*)
-    os << '\'';
-    PrintValue(os, default_value_);
-    os << '\'';
-  }
-  // override print default
-  virtual void PrintValue(std::ostream &os, int value) const {  // NOLINT(*)
-    if (is_enum_) {
-      CHECK_NE(enum_back_map_.count(value), 0U)
-          << "Value not found in enum declared";
-      os << enum_back_map_.at(value);
-    } else {
-      os << value;
-    }
-  }
-
-
- private:
-  inline void PrintEnums(std::ostream &os) const {  // NOLINT(*)
-    os << '{';
-    for (std::map<std::string, int>::const_iterator
-             it = enum_map_.begin(); it != enum_map_.end(); ++it) {
-      if (it != enum_map_.begin()) {
-        os << ", ";
-      }
-      os << "\'" << it->first << '\'';
-    }
-    os << '}';
-  }
-};
-
-
-// specialize define for optional<int>(enum)
-template<>
-class FieldEntry<optional<int> >
-    : public FieldEntryBase<FieldEntry<optional<int> >, optional<int> > {
- public:
-  // construct
-  FieldEntry<optional<int> >() : is_enum_(false) {}
-  // parent
-  typedef FieldEntryBase<FieldEntry<optional<int> >, optional<int> > Parent;
-  // override set
-  virtual void Set(void *head, const std::string &value) const {
-    if (is_enum_ && value != "None") {
-      std::map<std::string, int>::const_iterator it = enum_map_.find(value);
-      std::ostringstream os;
-      if (it == enum_map_.end()) {
-        os << "Invalid Input: \'" << value;
-        os << "\', valid values are: ";
-        PrintEnums(os);
-        throw dmlc::ParamError(os.str());
-      } else {
-        os << it->second;
-        Parent::Set(head, os.str());
-      }
-    } else {
-      Parent::Set(head, value);
-    }
-  }
-  virtual ParamFieldInfo GetFieldInfo() const {
-    if (is_enum_) {
-      ParamFieldInfo info;
-      std::ostringstream os;
-      info.name = key_;
-      info.type = type_;
-      PrintEnums(os);
-      if (has_default_) {
-        os << ',' << "optional, default=";
-        PrintDefaultValueString(os);
-      } else {
-        os << ", required";
-      }
-      info.type_info_str = os.str();
-      info.description = description_;
-      return info;
-    } else {
-      return Parent::GetFieldInfo();
-    }
-  }
-  // add enum
-  inline FieldEntry<optional<int> > &add_enum(const std::string &key, int value) {
-    CHECK_NE(key, "None") << "None is reserved for empty optional<int>";
-    if ((enum_map_.size() != 0 && enum_map_.count(key) != 0) || \
-        enum_back_map_.count(value) != 0) {
-      std::ostringstream os;
-      os << "Enum " << "(" << key << ": " << value << " exisit!" << ")\n";
-      os << "Enums: ";
-      for (std::map<std::string, int>::const_iterator it = enum_map_.begin();
-           it != enum_map_.end(); ++it) {
-        os << "(" << it->first << ": " << it->second << "), ";
-      }
-      throw dmlc::ParamError(os.str());
-    }
-    enum_map_[key] = value;
-    enum_back_map_[value] = key;
-    is_enum_ = true;
-    return this->self();
-  }
-
- protected:
-  // enum flag
-  bool is_enum_;
-  // enum map
-  std::map<std::string, int> enum_map_;
-  // enum map
-  std::map<int, std::string> enum_back_map_;
-  // override print behavior
-  virtual void PrintDefaultValueString(std::ostream &os) const { // NOLINT(*)
-    os << '\'';
-    PrintValue(os, default_value_);
-    os << '\'';
-  }
-  // override print default
-  virtual void PrintValue(std::ostream &os, optional<int> value) const {  // NOLINT(*)
-    if (is_enum_) {
-      if (!value) {
-        os << "None";
-      } else {
-        CHECK_NE(enum_back_map_.count(value.value()), 0U)
-            << "Value not found in enum declared";
-        os << enum_back_map_.at(value.value());
-      }
-    } else {
-      os << value;
-    }
-  }
-
-
- private:
-  inline void PrintEnums(std::ostream &os) const {  // NOLINT(*)
-    os << "{None";
-    for (std::map<std::string, int>::const_iterator
-             it = enum_map_.begin(); it != enum_map_.end(); ++it) {
-      os << ", ";
-      os << "\'" << it->first << '\'';
-    }
-    os << '}';
-  }
-};
-
-// specialize define for string
-template<>
-class FieldEntry<std::string>
-    : public FieldEntryBase<FieldEntry<std::string>, std::string> {
- public:
-  // parent class
-  typedef FieldEntryBase<FieldEntry<std::string>, std::string> Parent;
-  // override set
-  virtual void Set(void *head, const std::string &value) const {
-    this->Get(head) = value;
-  }
-  // override print default
-  virtual void PrintDefaultValueString(std::ostream &os) const {  // NOLINT(*)
-    os << '\'' << default_value_ << '\'';
-  }
-};
-
-// specialize define for bool
-template<>
-class FieldEntry<bool>
-    : public FieldEntryBase<FieldEntry<bool>, bool> {
- public:
-  // parent class
-  typedef FieldEntryBase<FieldEntry<bool>, bool> Parent;
-  // override set
-  virtual void Set(void *head, const std::string &value) const {
-    std::string lower_case; lower_case.resize(value.length());
-    std::transform(value.begin(), value.end(), lower_case.begin(), ::tolower);
-    bool &ref = this->Get(head);
-    if (lower_case == "true") {
-      ref = true;
-    } else if (lower_case == "false") {
-      ref = false;
-    } else if (lower_case == "1") {
-      ref = true;
-    } else if (lower_case == "0") {
-      ref = false;
-    } else {
-      std::ostringstream os;
-      os << "Invalid Parameter format for " << key_
-         << " expect " << type_ << " but value=\'" << value<< '\'';
-      throw dmlc::ParamError(os.str());
-    }
-  }
-
- protected:
-  // print default string
-  virtual void PrintValue(std::ostream &os, bool value) const {  // NOLINT(*)
-    if (value) {
-      os << "True";
-    } else {
-      os << "False";
-    }
-  }
-};
-
-}  // namespace parameter
-//! \endcond
-
-// implement GetEnv
-template<typename ValueType>
-inline ValueType GetEnv(const char *key,
-                        ValueType default_value) {
-  const char *val = getenv(key);
-  if (val == NULL) return default_value;
-  ValueType ret;
-  parameter::FieldEntry<ValueType> e;
-  e.Init(key, &ret, ret);
-  e.Set(&ret, val);
-  return ret;
-}
-}  // namespace dmlc
-#endif  // DMLC_PARAMETER_H_
diff --git a/nnvm/include/dmlc/registry.h b/nnvm/include/dmlc/registry.h
deleted file mode 100644
index a205a0f9dd59..000000000000
--- a/nnvm/include/dmlc/registry.h
+++ /dev/null
@@ -1,305 +0,0 @@
-/*!
- *  Copyright (c) 2015 by Contributors
- * \file registry.h
- * \brief Registry utility that helps to build registry singletons.
- */
-#ifndef DMLC_REGISTRY_H_
-#define DMLC_REGISTRY_H_
-
-#include <map>
-#include <string>
-#include <vector>
-#include "./base.h"
-#include "./logging.h"
-#include "./parameter.h"
-#include "./type_traits.h"
-
-namespace dmlc {
-/*!
- * \brief Registry class.
- *  Registry can be used to register global singletons.
- *  The most commonly use case are factory functions.
- *
- * \tparam EntryType Type of Registry entries,
- *     EntryType need to name a name field.
- */
-template<typename EntryType>
-class Registry {
- public:
-  /*! \return list of entries in the registry(excluding alias) */
-  inline static const std::vector<const EntryType*>& List() {
-    return Get()->const_list_;
-  }
-  /*! \return list all names registered in the registry, including alias */
-  inline static std::vector<std::string> ListAllNames() {
-    const std::map<std::string, EntryType*> &fmap = Get()->fmap_;
-    typename std::map<std::string, EntryType*>::const_iterator p;
-    std::vector<std::string> names;
-    for (p = fmap.begin(); p !=fmap.end(); ++p) {
-      names.push_back(p->first);
-    }
-    return names;
-  }
-  /*!
-   * \brief Find the entry with corresponding name.
-   * \param name name of the function
-   * \return the corresponding function, can be NULL
-   */
-  inline static const EntryType *Find(const std::string &name) {
-    const std::map<std::string, EntryType*> &fmap = Get()->fmap_;
-    typename std::map<std::string, EntryType*>::const_iterator p = fmap.find(name);
-    if (p != fmap.end()) {
-      return p->second;
-    } else {
-      return NULL;
-    }
-  }
-  /*!
-   * \brief Add alias to the key_name
-   * \param key_name The original entry key
-   * \param alias The alias key.
-   */
-  inline void AddAlias(const std::string& key_name,
-                       const std::string& alias) {
-    EntryType* e = fmap_.at(key_name);
-    if (fmap_.count(alias)) {
-      CHECK_EQ(e, fmap_.at(alias))
-          << "Entry " << e->name << " already registered under different entry";
-    } else {
-      fmap_[alias] = e;
-    }
-  }
-  /*!
-   * \brief Internal function to register a name function under name.
-   * \param name name of the function
-   * \return ref to the registered entry, used to set properties
-   */
-  inline EntryType &__REGISTER__(const std::string& name) {
-    CHECK_EQ(fmap_.count(name), 0U)
-        << name << " already registered";
-    EntryType *e = new EntryType();
-    e->name = name;
-    fmap_[name] = e;
-    const_list_.push_back(e);
-    entry_list_.push_back(e);
-    return *e;
-  }
-  /*!
-   * \brief Internal function to either register or get registered entry
-   * \param name name of the function
-   * \return ref to the registered entry, used to set properties
-   */
-  inline EntryType &__REGISTER_OR_GET__(const std::string& name) {
-    if (fmap_.count(name) == 0) {
-      return __REGISTER__(name);
-    } else {
-      return *fmap_.at(name);
-    }
-  }
-  /*!
-   * \brief get a singleton of the Registry.
-   *  This function can be defined by DMLC_ENABLE_REGISTRY.
-   * \return get a singleton
-   */
-  static Registry *Get();
-
- private:
-  /*! \brief list of entry types */
-  std::vector<EntryType*> entry_list_;
-  /*! \brief list of entry types */
-  std::vector<const EntryType*> const_list_;
-  /*! \brief map of name->function */
-  std::map<std::string, EntryType*> fmap_;
-  /*! \brief constructor */
-  Registry() {}
-  /*! \brief destructor */
-  ~Registry() {
-    for (size_t i = 0; i < entry_list_.size(); ++i) {
-      delete entry_list_[i];
-    }
-  }
-};
-
-/*!
- * \brief Common base class for function registry.
- *
- * \code
- *  // This example demonstrates how to use Registry to create a factory of trees.
- *  struct TreeFactory :
- *      public FunctionRegEntryBase<TreeFactory, std::function<Tree*()> > {
- *  };
- *
- *  // in a independent cc file
- *  namespace dmlc {
- *  DMLC_REGISTRY_ENABLE(TreeFactory);
- *  }
- *  // register binary tree constructor into the registry.
- *  DMLC_REGISTRY_REGISTER(TreeFactory, TreeFactory, BinaryTree)
- *      .describe("Constructor of BinaryTree")
- *      .set_body([]() { return new BinaryTree(); });
- * \endcode
- *
- * \tparam EntryType The type of subclass that inheritate the base.
- * \tparam FunctionType The function type this registry is registerd.
- */
-template<typename EntryType, typename FunctionType>
-class FunctionRegEntryBase {
- public:
-  /*! \brief name of the entry */
-  std::string name;
-  /*! \brief description of the entry */
-  std::string description;
-  /*! \brief additional arguments to the factory function */
-  std::vector<ParamFieldInfo> arguments;
-  /*! \brief Function body to create ProductType */
-  FunctionType body;
-  /*! \brief Return type of the function */
-  std::string return_type;
-
-  /*!
-   * \brief Set the function body.
-   * \param body Function body to set.
-   * \return reference to self.
-   */
-  inline EntryType &set_body(FunctionType body) {
-    this->body = body;
-    return this->self();
-  }
-  /*!
-   * \brief Describe the function.
-   * \param description The description of the factory function.
-   * \return reference to self.
-   */
-  inline EntryType &describe(const std::string &description) {
-    this->description = description;
-    return this->self();
-  }
-  /*!
-   * \brief Add argument information to the function.
-   * \param name Name of the argument.
-   * \param type Type of the argument.
-   * \param description Description of the argument.
-   * \return reference to self.
-   */
-  inline EntryType &add_argument(const std::string &name,
-                                 const std::string &type,
-                                 const std::string &description) {
-    ParamFieldInfo info;
-    info.name = name;
-    info.type = type;
-    info.type_info_str = info.type;
-    info.description = description;
-    arguments.push_back(info);
-    return this->self();
-  }
-  /*!
-   * \brief Append list if arguments to the end.
-   * \param args Additional list of arguments.
-   * \return reference to self.
-   */
-  inline EntryType &add_arguments(const std::vector<ParamFieldInfo> &args) {
-    arguments.insert(arguments.end(), args.begin(), args.end());
-    return this->self();
-  }
-  /*!
-  * \brief Set the return type.
-  * \param type Return type of the function, could be Symbol or Symbol[]
-  * \return reference to self.
-  */
-  inline EntryType &set_return_type(const std::string &type) {
-    return_type = type;
-    return this->self();
-  }
-
- protected:
-  /*!
-   * \return reference of self as derived type
-   */
-  inline EntryType &self() {
-    return *(static_cast<EntryType*>(this));
-  }
-};
-
-/*!
- * \def DMLC_REGISTRY_ENABLE
- * \brief Macro to enable the registry of EntryType.
- * This macro must be used under namespace dmlc, and only used once in cc file.
- * \param EntryType Type of registry entry
- */
-#define DMLC_REGISTRY_ENABLE(EntryType)                                 \
-  template<>                                                            \
-  Registry<EntryType > *Registry<EntryType >::Get() {                   \
-    static Registry<EntryType > inst;                                   \
-    return &inst;                                                       \
-  }                                                                     \
-
-/*!
- * \brief Generic macro to register an EntryType
- *  There is a complete example in FactoryRegistryEntryBase.
- *
- * \param EntryType The type of registry entry.
- * \param EntryTypeName The typename of EntryType, must do not contain namespace :: .
- * \param Name The name to be registered.
- * \sa FactoryRegistryEntryBase
- */
-#define DMLC_REGISTRY_REGISTER(EntryType, EntryTypeName, Name)          \
-  static DMLC_ATTRIBUTE_UNUSED EntryType & __make_ ## EntryTypeName ## _ ## Name ## __ = \
-      ::dmlc::Registry<EntryType>::Get()->__REGISTER__(#Name)           \
-
-/*!
- * \brief (Optional) Declare a file tag to current file that contains object registrations.
- *
- *  This will declare a dummy function that will be called by register file to
- *  incur a link dependency.
- *
- * \param UniqueTag The unique tag used to represent.
- * \sa DMLC_REGISTRY_LINK_TAG
- */
-#define DMLC_REGISTRY_FILE_TAG(UniqueTag)                                \
-  int __dmlc_registry_file_tag_ ## UniqueTag ## __() { return 0; }
-
-/*!
- * \brief (Optional) Force link to all the objects registered in file tag.
- *
- *  This macro must be used in the same file as DMLC_REGISTRY_ENABLE and
- *  in the same namespace as DMLC_REGISTRY_FILE_TAG
- *
- *  DMLC_REGISTRY_FILE_TAG and DMLC_REGISTRY_LINK_TAG are optional macros for registration.
- *  They are used to encforce link of certain file into during static linking.
- *
- *  This is mainly used to solve problem during statically link a library which contains backward registration.
- *  Specifically, this avoids the objects in these file tags to be ignored by compiler.
- *
- *  For dynamic linking, this problem won't occur as everything is loaded by default.
- *
- *  Use of this is optional as it will create an error when a file tag do not exist.
- *  An alternative solution is always ask user to enable --whole-archieve during static link.
- *
- * \begincode
- * // in file objective_registry.cc
- * DMLC_REGISTRY_ENABLE(MyObjective);
- * DMLC_REGISTRY_LINK_TAG(regression_op);
- * DMLC_REGISTRY_LINK_TAG(rank_op);
- *
- * // in file regression_op.cc
- * // declare tag of this file.
- * DMLC_REGISTRY_FILE_TAG(regression_op);
- * DMLC_REGISTRY_REGISTER(MyObjective, logistic_reg, logistic_reg);
- * // ...
- *
- * // in file rank_op.cc
- * // declare tag of this file.
- * DMLC_REGISTRY_FILE_TAG(rank_op);
- * DMLC_REGISTRY_REGISTER(MyObjective, pairwiserank, pairwiserank);
- *
- * \endcode
- *
- * \param UniqueTag The unique tag used to represent.
- * \sa DMLC_REGISTRY_ENABLE, DMLC_REGISTRY_FILE_TAG
- */
-#define DMLC_REGISTRY_LINK_TAG(UniqueTag)                                \
-  int __dmlc_registry_file_tag_ ## UniqueTag ## __();                   \
-  static int DMLC_ATTRIBUTE_UNUSED __reg_file_tag_ ## UniqueTag ## __ = \
-      __dmlc_registry_file_tag_ ## UniqueTag ## __();
-}  // namespace dmlc
-#endif  // DMLC_REGISTRY_H_
diff --git a/nnvm/include/dmlc/thread_local.h b/nnvm/include/dmlc/thread_local.h
deleted file mode 100644
index d6596d6e642e..000000000000
--- a/nnvm/include/dmlc/thread_local.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/*!
- *  Copyright (c) 2015 by Contributors
- * \file thread_local.h
- * \brief Portable thread local storage.
- */
-#ifndef DMLC_THREAD_LOCAL_H_
-#define DMLC_THREAD_LOCAL_H_
-
-#include <mutex>
-#include <memory>
-#include <vector>
-
-namespace dmlc {
-
-// macro hanlding for threadlocal variables
-#ifdef __GNUC__
-  #define MX_TREAD_LOCAL __thread
-#elif __STDC_VERSION__ >= 201112L
-  #define  MX_TREAD_LOCAL _Thread_local
-#elif defined(_MSC_VER)
-  #define MX_TREAD_LOCAL __declspec(thread)
-#endif
-
-#ifndef MX_TREAD_LOCAL
-#message("Warning: Threadlocal is not enabled");
-#endif
-
-/*!
- * \brief A threadlocal store to store threadlocal variables.
- *  Will return a thread local singleton of type T
- * \tparam T the type we like to store
- */
-template<typename T>
-class ThreadLocalStore {
- public:
-  /*! \return get a thread local singleton */
-  static T* Get() {
-    static MX_TREAD_LOCAL T* ptr = nullptr;
-    if (ptr == nullptr) {
-      ptr = new T();
-      Singleton()->RegisterDelete(ptr);
-    }
-    return ptr;
-  }
-
- private:
-  /*! \brief constructor */
-  ThreadLocalStore() {}
-  /*! \brief destructor */
-  ~ThreadLocalStore() {
-    for (size_t i = 0; i < data_.size(); ++i) {
-      delete data_[i];
-    }
-  }
-  /*! \return singleton of the store */
-  static ThreadLocalStore<T> *Singleton() {
-    static ThreadLocalStore<T> inst;
-    return &inst;
-  }
-  /*!
-   * \brief register str for internal deletion
-   * \param str the string pointer
-   */
-  void RegisterDelete(T *str) {
-    std::unique_lock<std::mutex> lock(mutex_);
-    data_.push_back(str);
-    lock.unlock();
-  }
-  /*! \brief internal mutex */
-  std::mutex mutex_;
-  /*!\brief internal data */
-  std::vector<T*> data_;
-};
-
-}  // namespace dmlc
-
-#endif  // DMLC_THREAD_LOCAL_H_
diff --git a/nnvm/include/dmlc/timer.h b/nnvm/include/dmlc/timer.h
deleted file mode 100644
index c97059f97812..000000000000
--- a/nnvm/include/dmlc/timer.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*!
- *  Copyright (c) 2015 by Contributors
- * \file timer.h
- * \brief cross platform timer for timing
- * \author Tianqi Chen
- */
-#ifndef DMLC_TIMER_H_
-#define DMLC_TIMER_H_
-
-#include "base.h"
-
-#if DMLC_USE_CXX11
-#include <chrono>
-#endif
-
-#include <time.h>
-#ifdef __MACH__
-#include <mach/clock.h>
-#include <mach/mach.h>
-#endif
-#include "./logging.h"
-
-namespace dmlc {
-/*!
- * \brief return time in seconds
- */
-inline double GetTime(void) {
-  #if DMLC_USE_CXX11
-  return std::chrono::duration<double>(
-      std::chrono::high_resolution_clock::now().time_since_epoch()).count();
-  #elif defined __MACH__
-  clock_serv_t cclock;
-  mach_timespec_t mts;
-  host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
-  CHECK(clock_get_time(cclock, &mts) == 0) << "failed to get time";
-  mach_port_deallocate(mach_task_self(), cclock);
-  return static_cast<double>(mts.tv_sec) + static_cast<double>(mts.tv_nsec) * 1e-9;
-  #else
-  #if defined(__unix__) || defined(__linux__)
-  timespec ts;
-  CHECK(clock_gettime(CLOCK_REALTIME, &ts) == 0) << "failed to get time";
-  return static_cast<double>(ts.tv_sec) + static_cast<double>(ts.tv_nsec) * 1e-9;
-  #else
-  return static_cast<double>(time(NULL));
-  #endif
-  #endif
-}
-}  // namespace dmlc
-#endif  // DMLC_TIMER_H_
diff --git a/nnvm/include/dmlc/type_traits.h b/nnvm/include/dmlc/type_traits.h
deleted file mode 100644
index 73abfba80aca..000000000000
--- a/nnvm/include/dmlc/type_traits.h
+++ /dev/null
@@ -1,171 +0,0 @@
-/*!
- *  Copyright (c) 2015 by Contributors
- * \file type_traits.h
- * \brief type traits information header
- */
-#ifndef DMLC_TYPE_TRAITS_H_
-#define DMLC_TYPE_TRAITS_H_
-
-#include "./base.h"
-#if DMLC_USE_CXX11
-#include <type_traits>
-#endif
-#include <string>
-
-namespace dmlc {
-/*!
- * \brief whether a type is pod type
- * \tparam T the type to query
- */
-template<typename T>
-struct is_pod {
-#if DMLC_USE_CXX11
-  /*! \brief the value of the traits */
-  static const bool value = std::is_pod<T>::value;
-#else
-  /*! \brief the value of the traits */
-  static const bool value = false;
-#endif
-};
-
-
-/*!
- * \brief whether a type is integer type
- * \tparam T the type to query
- */
-template<typename T>
-struct is_integral {
-#if DMLC_USE_CXX11
-  /*! \brief the value of the traits */
-  static const bool value = std::is_integral<T>::value;
-#else
-  /*! \brief the value of the traits */
-  static const bool value = false;
-#endif
-};
-
-/*!
- * \brief whether a type is floating point type
- * \tparam T the type to query
- */
-template<typename T>
-struct is_floating_point {
-#if DMLC_USE_CXX11
-  /*! \brief the value of the traits */
-  static const bool value = std::is_floating_point<T>::value;
-#else
-  /*! \brief the value of the traits */
-  static const bool value = false;
-#endif
-};
-
-/*!
- * \brief whether a type is arithemetic type
- * \tparam T the type to query
- */
-template<typename T>
-struct is_arithmetic {
-#if DMLC_USE_CXX11
-  /*! \brief the value of the traits */
-  static const bool value = std::is_arithmetic<T>::value;
-#else
-  /*! \brief the value of the traits */
-  static const bool value = (dmlc::is_integral<T>::value ||
-                             dmlc::is_floating_point<T>::value);
-#endif
-};
-
-/*!
- * \brief the string representation of type name
- * \tparam T the type to query
- * \return a const string of typename.
- */
-template<typename T>
-inline const char* type_name() {
-  return "";
-}
-
-/*!
- * \brief whether a type have save/load function
- * \tparam T the type to query
- */
-template<typename T>
-struct has_saveload {
-  /*! \brief the value of the traits */
-  static const bool value = false;
-};
-
-/*!
- * \brief template to select type based on condition
- * For example, IfThenElseType<true, int, float>::Type will give int
- * \tparam cond the condition
- * \tparam Then the typename to be returned if cond is true
- * \tparam The typename to be returned if cond is false
-*/
-template<bool cond, typename Then, typename Else>
-struct IfThenElseType;
-
-/*! \brief macro to quickly declare traits information */
-#define DMLC_DECLARE_TRAITS(Trait, Type, Value)       \
-  template<>                                          \
-  struct Trait<Type> {                                \
-    static const bool value = Value;                  \
-  }
-
-/*! \brief macro to quickly declare traits information */
-#define DMLC_DECLARE_TYPE_NAME(Type, Name)            \
-  template<>                                          \
-  inline const char* type_name<Type>() {              \
-    return Name;                                      \
-  }
-
-//! \cond Doxygen_Suppress
-// declare special traits when C++11 is not available
-#if DMLC_USE_CXX11 == 0
-DMLC_DECLARE_TRAITS(is_pod, char, true);
-DMLC_DECLARE_TRAITS(is_pod, int8_t, true);
-DMLC_DECLARE_TRAITS(is_pod, int16_t, true);
-DMLC_DECLARE_TRAITS(is_pod, int32_t, true);
-DMLC_DECLARE_TRAITS(is_pod, int64_t, true);
-DMLC_DECLARE_TRAITS(is_pod, uint8_t, true);
-DMLC_DECLARE_TRAITS(is_pod, uint16_t, true);
-DMLC_DECLARE_TRAITS(is_pod, uint32_t, true);
-DMLC_DECLARE_TRAITS(is_pod, uint64_t, true);
-DMLC_DECLARE_TRAITS(is_pod, float, true);
-DMLC_DECLARE_TRAITS(is_pod, double, true);
-
-DMLC_DECLARE_TRAITS(is_integral, char, true);
-DMLC_DECLARE_TRAITS(is_integral, int8_t, true);
-DMLC_DECLARE_TRAITS(is_integral, int16_t, true);
-DMLC_DECLARE_TRAITS(is_integral, int32_t, true);
-DMLC_DECLARE_TRAITS(is_integral, int64_t, true);
-DMLC_DECLARE_TRAITS(is_integral, uint8_t, true);
-DMLC_DECLARE_TRAITS(is_integral, uint16_t, true);
-DMLC_DECLARE_TRAITS(is_integral, uint32_t, true);
-DMLC_DECLARE_TRAITS(is_integral, uint64_t, true);
-
-DMLC_DECLARE_TRAITS(is_floating_point, float, true);
-DMLC_DECLARE_TRAITS(is_floating_point, double, true);
-
-#endif
-
-DMLC_DECLARE_TYPE_NAME(float, "float");
-DMLC_DECLARE_TYPE_NAME(double, "double");
-DMLC_DECLARE_TYPE_NAME(int, "int");
-DMLC_DECLARE_TYPE_NAME(uint32_t, "int (non-negative)");
-DMLC_DECLARE_TYPE_NAME(uint64_t, "long (non-negative)");
-DMLC_DECLARE_TYPE_NAME(std::string, "string");
-DMLC_DECLARE_TYPE_NAME(bool, "boolean");
-
-template<typename Then, typename Else>
-struct IfThenElseType<true, Then, Else> {
-  typedef Then Type;
-};
-
-template<typename Then, typename Else>
-struct IfThenElseType<false, Then, Else> {
-  typedef Else Type;
-};
-//! \endcond
-}  // namespace dmlc
-#endif  // DMLC_TYPE_TRAITS_H_
diff --git a/nnvm/make/config.mk b/nnvm/make/config.mk
index b0989b425e50..d9eb39f4de04 100644
--- a/nnvm/make/config.mk
+++ b/nnvm/make/config.mk
@@ -28,6 +28,9 @@ ADD_LDFLAGS=
 # the additional compile flags you want to add
 ADD_CFLAGS=
 
+# path to dmlc-core module 
+#DMLC_CORE_PATH=
+
 #----------------------------
 # plugins
 #----------------------------

From 73d3921d11b40190bf434fb89770a0ffdd188b8b Mon Sep 17 00:00:00 2001
From: Khomutov Nikita <inikitah@gmail.com>
Date: Tue, 31 Jan 2017 19:59:20 +0200
Subject: [PATCH 418/949] Update overview.md (#103)

misstype fix
---
 nnvm/docs/overview.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/docs/overview.md b/nnvm/docs/overview.md
index cd51768ef942..8d128a3df370 100644
--- a/nnvm/docs/overview.md
+++ b/nnvm/docs/overview.md
@@ -76,7 +76,7 @@ z = nn.conv2d(nn.add(x, y), w, filter_size=(2,2), name='conv1')
 The graph structure can be accessed in the backend. Currently python interface is supported.
 But as NNVM follows the same C bridge API design as [MXNet](https://github.com/dmlc/mxnet),
 which support many languages such as R/Julia/Scala/C++, more language support can be easily
-moved in in the future.
+moved in the future.
 
 ## Operator Attribute for More Extensions
 

From bfa03904f7071fdaff20c87a89bc92cbc4f4bb2c Mon Sep 17 00:00:00 2001
From: Eric Junyuan Xie <piiswrong@users.noreply.github.com>
Date: Mon, 6 Feb 2017 16:16:36 -0800
Subject: [PATCH 419/949] Update Makefile

---
 nnvm/amalgamation/Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/amalgamation/Makefile b/nnvm/amalgamation/Makefile
index e457b770d74f..9c0c1edd89ad 100644
--- a/nnvm/amalgamation/Makefile
+++ b/nnvm/amalgamation/Makefile
@@ -1,5 +1,5 @@
 export NNVM_ROOT=`pwd`/..
-export CFLAGS = -std=c++11 -Wall -O3 -msse2 -Wno-unknown-pragmas -funroll-loops -Iinclude -fPIC
+export CFLAGS = -std=c++11 -Wall -O2 -Iinclude -fPIC
 
 .PHONY: all clean
 

From a648269c0d8e8798fc9cffa36dc91da11e8fb99b Mon Sep 17 00:00:00 2001
From: Eric Junyuan Xie <piiswrong@users.noreply.github.com>
Date: Mon, 6 Feb 2017 16:21:38 -0800
Subject: [PATCH 420/949] Update amalgamation.py

---
 nnvm/amalgamation/amalgamation.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/amalgamation/amalgamation.py b/nnvm/amalgamation/amalgamation.py
index 913bab7364bd..310daa9d68e0 100644
--- a/nnvm/amalgamation/amalgamation.py
+++ b/nnvm/amalgamation/amalgamation.py
@@ -7,7 +7,7 @@
     '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'
+    'omp.h', 'execinfo.h', 'packet/sse-inl.h'
     ]
 
 

From 70bf5af55710f79f38d1db2eec9583c9d715e712 Mon Sep 17 00:00:00 2001
From: piiswrong <eric.jy.xie@gmail.com>
Date: Tue, 7 Feb 2017 01:47:33 +0000
Subject: [PATCH 421/949] fix

---
 nnvm/amalgamation/Makefile | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/nnvm/amalgamation/Makefile b/nnvm/amalgamation/Makefile
index 9c0c1edd89ad..1f286f055237 100644
--- a/nnvm/amalgamation/Makefile
+++ b/nnvm/amalgamation/Makefile
@@ -1,6 +1,12 @@
 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

From c4e3e3d4f5b03c3abef5d4a4b36d70b6f13776d8 Mon Sep 17 00:00:00 2001
From: piiswrong <eric.jy.xie@gmail.com>
Date: Wed, 8 Feb 2017 07:38:08 +0000
Subject: [PATCH 422/949] remove limit on simple type

---
 nnvm/include/nnvm/tuple.h | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/nnvm/include/nnvm/tuple.h b/nnvm/include/nnvm/tuple.h
index 132e23e42302..1acd37b57363 100644
--- a/nnvm/include/nnvm/tuple.h
+++ b/nnvm/include/nnvm/tuple.h
@@ -31,9 +31,6 @@ typedef uint32_t index_t;
 template<typename ValueType>
 class Tuple {
  public:
-  // Tuple requires the content to be simple data type.
-  static_assert(std::is_pod<ValueType>::value,
-                "Tuple only support simple data type like int");
   /*! \brief default constructor */
   Tuple() = default;
   /*! \brief destructor */

From 1bf0df80f40caf47667cc008c91db1cb68ceb25d Mon Sep 17 00:00:00 2001
From: Eric Junyuan Xie <piiswrong@users.noreply.github.com>
Date: Fri, 24 Feb 2017 16:04:55 -0800
Subject: [PATCH 423/949] add symbol::GetChildren (#104)

---
 nnvm/amalgamation/.gitignore |  2 ++
 nnvm/include/nnvm/symbolic.h |  6 ++++++
 nnvm/src/core/symbolic.cc    | 13 +++++++++++++
 3 files changed, 21 insertions(+)
 create mode 100644 nnvm/amalgamation/.gitignore

diff --git a/nnvm/amalgamation/.gitignore b/nnvm/amalgamation/.gitignore
new file mode 100644
index 000000000000..e808ea2764c3
--- /dev/null
+++ b/nnvm/amalgamation/.gitignore
@@ -0,0 +1,2 @@
+nnvm.d
+nnvm.cc
diff --git a/nnvm/include/nnvm/symbolic.h b/nnvm/include/nnvm/symbolic.h
index 4d26947ca646..ab836ad5eab1 100644
--- a/nnvm/include/nnvm/symbolic.h
+++ b/nnvm/include/nnvm/symbolic.h
@@ -137,6 +137,12 @@ class Symbol {
    *                including input variables and intermediate outputs.
    */
   Symbol GetInternals() const;
+  /*
+   * \brief Get the direct inputs of the head node(s) of this symbol.
+   * \return symbol A new symbol whose output contains all the inputs of the head
+   *                node(s).
+   */
+  Symbol GetChildren() const;
   /*!
    * \brief Set additional attributes to current node.
    *
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index a101deed7919..51cc1fa9e9a0 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -435,6 +435,19 @@ Symbol Symbol::GetInternals() const {
   return ret;
 }
 
+Symbol Symbol::GetChildren() const {
+  static auto& fnum_vis_output = Op::GetAttr<FNumVisibleOutputs>("FNumVisibleOutputs");
+  Symbol ret;
+  std::unordered_set<Node*> visited;
+  for (const auto& p : this->outputs) {
+    Node* node = p.node.get();
+    if (visited.count(node)) continue;
+    visited.insert(node);
+    ret.outputs.insert(ret.outputs.end(), node->inputs.begin(), node->inputs.end());
+  }
+  return ret;
+}
+
 void Symbol::SetAttrs(const std::vector<std::pair<std::string, std::string> >& attrs) {
   Node* node = outputs[0].node.get();
   for (const NodeEntry& e : outputs) {

From 20a6c8bad9f6b194d2538e5935c7ffe353ffadcb Mon Sep 17 00:00:00 2001
From: Haibin Lin <linhaibin.eric@gmail.com>
Date: Sat, 25 Feb 2017 11:48:36 -0800
Subject: [PATCH 424/949] LSTM Memory Allocator Fix #5035 (#105)

* Imbalance version of shared pool during plan memory

* Bug fix for no shared_pool case

* Auto search and updated shared mem pool

* Cleanup unused code

* Cleanup logging code

* Add unit test for shared storage

* Remove shared pool in PlanMemory. Fix lint warnings

* Fix lint warnings

* Use reference instead of ptrs
---
 nnvm/src/pass/plan_memory.cc    | 146 ++++++++++++++++++++------------
 nnvm/tests/python/test_graph.py |   1 -
 2 files changed, 94 insertions(+), 53 deletions(-)

diff --git a/nnvm/src/pass/plan_memory.cc b/nnvm/src/pass/plan_memory.cc
index 1666da4bb693..e9029c2268ba 100644
--- a/nnvm/src/pass/plan_memory.cc
+++ b/nnvm/src/pass/plan_memory.cc
@@ -19,10 +19,12 @@ class GraphAllocator {
  public:
   // storage id equals integer.
   using StorageID = int;
+
   // bad storage id
   static const StorageID kBadStorageID = -1;
   // external storage id
   static const StorageID kExternalStorageID = -2;
+
   // request a free storage
   StorageID Request(int dev_id, int dtype, TShape shape, uint32_t node_id) {
     if (shape.ndim() == 0) return kBadStorageID;
@@ -54,7 +56,7 @@ class GraphAllocator {
           node_color_[e->released_by_node] != node_color_[node_id]) continue;
       // Use exect matching strategy
       e->max_bytes = std::max(size, e->max_bytes);
-      // find a exact match, erase from map and return
+      // erase from map and return
       free_.erase(it);
       return e->id;
     }
@@ -69,6 +71,7 @@ class GraphAllocator {
     e->released_by_node = node_id;
     free_.insert({e->max_bytes, e});
   }
+
   // totoal number of bytes allocated
   size_t TotalAllocBytes() const {
     size_t total = 0;
@@ -79,14 +82,13 @@ class GraphAllocator {
   }
 
   // constructor
-  explicit GraphAllocator(const IndexedGraph* idx) : idx_(idx) {
-    this->Init(dmlc::GetEnv("NNVM_EXEC_MATCH_RANGE", 16),
-               dmlc::GetEnv("NNVM_EXEC_NUM_TEMP", 1));
+  explicit GraphAllocator(const IndexedGraph* idx, const size_t match_range) : idx_(idx) {
+    this->Init(match_range, dmlc::GetEnv("NNVM_EXEC_NUM_TEMP", 1));
   }
 
  private:
   // initialize the graph allocator
-  void Init(size_t match_range, uint32_t num_match_color) {
+  void Init(const size_t match_range, const uint32_t num_match_color) {
     match_range_ = match_range;
     num_match_color_ = num_match_color;
     if (num_match_color_ > 1) {
@@ -136,43 +138,17 @@ class GraphAllocator {
   const IndexedGraph* idx_;
 };
 
-// function to plan memory
-Graph PlanMemory(Graph ret) {
-  // setup ref counter
-  const IndexedGraph& idx = ret.indexed_graph();
+/*
+ * Internal method to perform the memory allocation for a graph
+ * */
+size_t AllocMemory(const Graph& ret, const IndexedGraph& idx, StorageVector* storage_ptr,
+                   std::vector<int>* storage_inplace_index_ptr, std::vector<uint32_t> ref_count,
+                   GraphAllocator* allocator) {
+  // Get reference
+  auto &storage = *storage_ptr;
+  auto &storage_inplace_index = *storage_inplace_index_ptr;
 
-  static auto& fignore_inputs = Op::GetAttr<FIgnoreInputs>("FIgnoreInputs");
-  // reference counter of each node
-  std::vector<uint32_t> ref_count(idx.num_node_entries(), 0);
-  // step 1: initialize reference count
-  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
-    const auto& inode = idx[nid];
-    if (inode.source->is_variable()) continue;
-    for (const auto& e : inode.inputs) {
-      ++ref_count[idx.entry_id(e)];
-    }
-    // no dataflow dependency is needed for those are ignored.
-    // revoke the dependency counter.
-    if (fignore_inputs.count(inode.source->op()) != 0) {
-      auto ignore_inputs = fignore_inputs[inode.source->op()](inode.source->attrs);
-      for (uint32_t i : ignore_inputs) {
-        --ref_count[idx.entry_id(inode.inputs[i])];
-      }
-    }
-  }
-  for (const auto& e : idx.outputs()) {
-    ++ref_count[idx.entry_id(e)];
-  }
-  // step 2: allocate memory.
-  StorageVector storage;
-
-  if (ret.attrs.count("storage") != 0) {
-    storage = ret.MoveCopyAttr<StorageVector>("storage");
-  } else {
-    storage.resize(idx.num_node_entries(), -1);
-  }
-
-  std::vector<int> storage_inplace_index(idx.num_node_entries(), -1);
+  // Get attributes from the graph
   const ShapeVector& shape_vec = ret.GetAttr<ShapeVector>("shape");
   const DTypeVector& dtype_vec = ret.GetAttr<DTypeVector>("dtype");
   const DeviceVector* device_vec = nullptr;
@@ -181,9 +157,6 @@ Graph PlanMemory(Graph ret) {
   if (ret.attrs.count("device") != 0) {
     device_vec = &(ret.GetAttr<DeviceVector>("device"));
   }
-  // the allocator.
-  GraphAllocator allocator(&idx);
-  // number of entries that are not statically allocated.
   size_t num_not_allocated = 0;
 
   for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
@@ -210,15 +183,24 @@ Graph PlanMemory(Graph ret) {
     }
     // normal allocation
     const int dev_id = (device_vec != nullptr) ? device_vec->at(nid) : 0;
-    // allocate output
+    // sort output nodes based on size before allocating output
+    std::multimap<size_t, uint32_t> eids;
     for (uint32_t index = 0; index < inode.source->num_outputs(); ++index) {
       uint32_t eid = idx.entry_id(nid, index);
       if (storage[eid] == GraphAllocator::kBadStorageID) {
-        storage[eid] = allocator.Request(dev_id, dtype_vec[eid], shape_vec[eid], nid);
+        auto &eshape = shape_vec[eid];
+        size_t esize = 0;
+        if (eshape.ndim() != 0) esize = eshape.Size();
+        eids.insert(std::make_pair(esize, eid));
       }
     }
+    for (auto rit = eids.rbegin(); rit != eids.rend(); ++rit) {
+        uint32_t eid = rit->second;
+        storage[eid] = allocator->Request(dev_id, dtype_vec[eid], shape_vec[eid], nid);
+    }
 
     // check if certain inputs is ignored.
+    static auto& fignore_inputs = Op::GetAttr<FIgnoreInputs>("FIgnoreInputs");
     std::vector<uint32_t> ignore_inputs;
     if (fignore_inputs.count(inode.source->op()) != 0) {
       ignore_inputs = fignore_inputs[inode.source->op()](inode.source->attrs);
@@ -235,7 +217,7 @@ Graph PlanMemory(Graph ret) {
       // if we decrease it to zero, means we are ready to relase
       --ref_count[eid];
       if (ref_count[eid] == 0 && storage[eid] != GraphAllocator::kBadStorageID) {
-        allocator.Release(storage[eid], nid);
+        allocator->Release(storage[eid], nid);
       }
     }
     // check if there are outputs that can be freeded immediately
@@ -243,7 +225,7 @@ Graph PlanMemory(Graph ret) {
     for (uint32_t index = 0; index < inode.source->num_outputs(); ++index) {
       uint32_t eid = idx.entry_id(nid, index);
       if (ref_count[eid] == 0 && storage[eid] != GraphAllocator::kBadStorageID) {
-        allocator.Release(storage[eid], nid);
+        allocator->Release(storage[eid], nid);
         // use -2 to indicate that the node was never touched.
         storage_inplace_index[eid] = -2;
       }
@@ -252,10 +234,70 @@ Graph PlanMemory(Graph ret) {
       }
     }
   }
-  ret.attrs["storage_id"] = std::make_shared<any>(std::move(storage));
-  ret.attrs["storage_inplace_index"] = std::make_shared<any>(std::move(storage_inplace_index));
-  ret.attrs["storage_allocated_bytes"] = std::make_shared<any>(allocator.TotalAllocBytes());
-  ret.attrs["storage_num_not_allocated"] = std::make_shared<any>(num_not_allocated);
+  return num_not_allocated;
+}
+
+
+// function to plan memory
+Graph PlanMemory(Graph ret) {
+  // setup ref counter
+  const IndexedGraph& idx = ret.indexed_graph();
+  static auto& fignore_inputs = Op::GetAttr<FIgnoreInputs>("FIgnoreInputs");
+  // reference counter of each node
+  std::vector<uint32_t> ref_count(idx.num_node_entries(), 0);
+  // step 1: initialize reference count
+  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+    const auto& inode = idx[nid];
+    if (inode.source->is_variable()) continue;
+    for (const auto& e : inode.inputs) {
+      ++ref_count[idx.entry_id(e)];
+    }
+    // no dataflow dependency is needed for those are ignored.
+    // revoke the dependency counter.
+    if (fignore_inputs.count(inode.source->op()) != 0) {
+      auto ignore_inputs = fignore_inputs[inode.source->op()](inode.source->attrs);
+      for (uint32_t i : ignore_inputs) {
+        --ref_count[idx.entry_id(inode.inputs[i])];
+      }
+    }
+  }
+  for (const auto& e : idx.outputs()) {
+    ++ref_count[idx.entry_id(e)];
+  }
+  // step 2: allocate memory.
+  StorageVector storage;
+  if (ret.attrs.count("storage") != 0) {
+    storage = ret.MoveCopyAttr<StorageVector>("storage");
+  } else {
+    storage.resize(idx.num_node_entries(), -1);
+  }
+
+  // Search the best NNVM_EXEC_MATCH_RANGE parameter. This is turned off by default
+  size_t min_allocated_bytes = -1;
+  size_t max_match_range = dmlc::GetEnv("NNVM_EXEC_MATCH_RANGE", 16);
+  size_t min_match_range =
+         dmlc::GetEnv("NNVM_AUTO_SEARCH_MATCH_RANGE", false) ? 1 : max_match_range;
+  for (size_t match_range = min_match_range; match_range <= max_match_range; match_range *= 2) {
+    // Make a copy of related fields
+    StorageVector storage_vec(storage);
+    std::vector<int> storage_inplace_index(idx.num_node_entries(), -1);
+
+    // the allocator
+    GraphAllocator allocator(&idx, match_range);
+
+    // number of entries that are not statically allocated.
+    size_t storage_num_not_allocated =
+      AllocMemory(ret, idx, &storage_vec, &storage_inplace_index, ref_count, &allocator);
+    size_t storage_allocated_bytes = allocator.TotalAllocBytes();
+    // Choose the plan which leads to minimal memory usage
+    if (min_allocated_bytes > storage_allocated_bytes) {
+      ret.attrs["storage_id"] = std::make_shared<any>(std::move(storage_vec));
+      ret.attrs["storage_inplace_index"] = std::make_shared<any>(std::move(storage_inplace_index));
+      ret.attrs["storage_allocated_bytes"] = std::make_shared<any>(storage_allocated_bytes);
+      ret.attrs["storage_num_not_allocated"] = std::make_shared<any>(storage_num_not_allocated);
+      min_allocated_bytes = storage_allocated_bytes;
+    }
+  }
   return ret;
 }
 
diff --git a/nnvm/tests/python/test_graph.py b/nnvm/tests/python/test_graph.py
index 702f37bd38e0..86595aec3219 100644
--- a/nnvm/tests/python/test_graph.py
+++ b/nnvm/tests/python/test_graph.py
@@ -88,7 +88,6 @@ def test_infer_shape_known_partial():
     assert g.json_attr('shape')[jnode_row_ptr[nindex["reshape1"]]] == [2, 4]
     assert g.json_attr('shape')[jnode_row_ptr[nindex["add1"]]] == [4, 2]
 
-
 def test_infer_type():
     x = sym.Variable('x', dtype=0)
     y = sym.add(x, x, name='add1')

From 8b02e0ed83ec1509f02c4722f24e34a6dd52093b Mon Sep 17 00:00:00 2001
From: Ziheng Jiang <jzhtomas@gmail.com>
Date: Fri, 10 Mar 2017 10:08:58 -0800
Subject: [PATCH 425/949] Enable shape hints during infer_shape pass (#107)

* enable shape hints during infer_shape pass

* fix comment
---
 nnvm/include/nnvm/graph.h         |  6 ++++++
 nnvm/include/nnvm/node.h          | 28 ++++++++++++++++++++++++++++
 nnvm/src/pass/infer_shape_type.cc | 15 ++++++++++++++-
 3 files changed, 48 insertions(+), 1 deletion(-)

diff --git a/nnvm/include/nnvm/graph.h b/nnvm/include/nnvm/graph.h
index 60028113cc1e..af649b4f65cf 100644
--- a/nnvm/include/nnvm/graph.h
+++ b/nnvm/include/nnvm/graph.h
@@ -171,6 +171,12 @@ class IndexedGraph {
   inline const std::vector<NodeEntry>& outputs() const {
     return outputs_;
   }
+
+  /*! \return whether a node is existed in the indexed graph */
+  inline bool exist(const nnvm::Node* node) const {
+    return node2index_.count(node);
+  }
+
   // disalllow copy assign
   IndexedGraph(const IndexedGraph&) = delete;
 
diff --git a/nnvm/include/nnvm/node.h b/nnvm/include/nnvm/node.h
index a7daa0a6e887..ace937f68444 100644
--- a/nnvm/include/nnvm/node.h
+++ b/nnvm/include/nnvm/node.h
@@ -41,6 +41,34 @@ struct NodeEntry {
   uint32_t version;
 };
 
+/*!
+ * \brief This lets you use a NodeEntry as a key in a unordered_map of the form
+ * unordered_map<NodeEntry, ValueType, NodeEntryHash, NodeEntryEqual>
+ */
+struct NodeEntryHash {
+  size_t operator()(const NodeEntry& e) const {
+    return std::hash<Node*>()(e.node.get()) ^
+          (std::hash<size_t>()(e.index) << 1 >> 1) ^
+          (std::hash<size_t>()(e.version) << 1);
+  }
+};
+
+/*!
+ * \brief This lets you use a NodeEntry as a key in a unordered_map of the form
+ * unordered_map<NodeEntry, ValueType, NodeEntryHash, NodeEntryEqual>
+ */
+struct NodeEntryEqual {
+  size_t operator()(const NodeEntry& a, const NodeEntry& b) const {
+    return (a.node.get() == b.node.get()) &&
+           (a.index == b.index) &&
+           (a.version == b.version);
+  }
+};
+
+/*! use NodeEntry as key in unordered_map */
+template<typename ValueType>
+using NodeEntryMap = std::unordered_map<NodeEntry, ValueType, NodeEntryHash, NodeEntryEqual>;
+
 /*!
  * \brief The attributes of the current operation node.
  *  Usually are additional parameters like axis,
diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index dd36d3da31cb..f677909ce181 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -49,6 +49,19 @@ Graph InferAttr(Graph &&ret,
     ret.attrs.erase(input_name);
   }
 
+  // get the shape hints
+  std::string shape_hints_key = std::string(attr_name) + "_hints";
+  if (ret.attrs.count(shape_hints_key)) {
+    NodeEntryMap<AttrType> shape_hints =
+      ret.GetAttr<NodeEntryMap<AttrType>>(shape_hints_key);
+    for (const auto& kv : shape_hints) {
+      NodeEntry e = kv.first;
+      if (idx.exist(e.node.get())) {
+        rshape[idx.entry_id(kv.first)] = kv.second;
+      }
+    }
+  }
+
   std::string shape_attr_key;
   if (ret.attrs.count(attr_key_name) != 0) {
     shape_attr_key = ret.GetAttr<std::string>(attr_key_name);
@@ -75,7 +88,7 @@ Graph InferAttr(Graph &&ret,
           CHECK(is >> rshape[out_ent_id]) << "Invalid attribute";
         }
       }
-    } else if (is_backward.get(inode.source->op(), false)) {
+    } else if (is_backward.get(inode.source->op(), false) && inode.control_deps.size()) {
       CHECK_GE(inode.control_deps.size(), 1U)
         << "BackwardOp need to have control_deps to its forward op";
       const IndexedGraph::Node& fnode = idx[inode.control_deps[0]];

From 855e481fdba732d82bc9962ec2125a5c11f69583 Mon Sep 17 00:00:00 2001
From: Eric Junyuan Xie <piiswrong@users.noreply.github.com>
Date: Fri, 31 Mar 2017 11:06:27 -0700
Subject: [PATCH 426/949] change to match by both op and name (#109)

* change to match by both op and name

* add zero prop for non differentiable ops

* Update infer_shape_type.cc
---
 nnvm/include/nnvm/pass_functions.h |  9 +++++-
 nnvm/src/pass/gradient.cc          | 51 +++++++++++++++++++++++++++---
 nnvm/src/pass/infer_shape_type.cc  |  6 ++--
 3 files changed, 59 insertions(+), 7 deletions(-)

diff --git a/nnvm/include/nnvm/pass_functions.h b/nnvm/include/nnvm/pass_functions.h
index 1498f071e9d1..c40154d4873f 100644
--- a/nnvm/include/nnvm/pass_functions.h
+++ b/nnvm/include/nnvm/pass_functions.h
@@ -130,6 +130,8 @@ inline Graph PlaceDevice(Graph graph,
  * \param aggregate_fun Aggregation function applied to aggregate the inputs.
  * \param mirror_fun Optional mirror function to do mirror optimization and save memory.
  * \param attr_hint_fun Optional, hint function to output a node that like src, but its attr is same as like.
+ * \param zero_ops Optional, list of operators that outputs a single zero array. The first one
+ *  must be zeros_like.
  * \return A new graph, whose outputs correspond to inputs of xs.
  */
 inline Graph Gradient(
@@ -140,7 +142,8 @@ inline Graph Gradient(
     std::function<NodeEntry(std::vector<NodeEntry>&& inputs)> aggregate_fun = nullptr,
     std::function<int(const Node& node)> mirror_fun = nullptr,
     std::function<NodeEntry(const NodeEntry& src, const NodeEntry &like)>
-    attr_hint_fun = nullptr) {
+    attr_hint_fun = nullptr,
+    std::vector<const Op*> zero_ops = std::vector<const Op*>()) {
   graph.attrs["grad_ys"] = std::make_shared<any>(std::move(ys));
 
   graph.attrs["grad_xs"] = std::make_shared<any>(std::move(xs));
@@ -157,6 +160,10 @@ inline Graph Gradient(
     graph.attrs["attr_hint_fun"] = std::make_shared<any>(attr_hint_fun);
   }
 
+  if (zero_ops.size()) {
+    graph.attrs["zero_ops"] = std::make_shared<any>(std::move(zero_ops));
+  }
+
   return ApplyPass(std::move(graph), "Gradient");
 }
 
diff --git a/nnvm/src/pass/gradient.cc b/nnvm/src/pass/gradient.cc
index 580eda8f8750..90e56da4d781 100644
--- a/nnvm/src/pass/gradient.cc
+++ b/nnvm/src/pass/gradient.cc
@@ -30,6 +30,22 @@ NodeEntry DefaultAggregateGradient(std::vector<NodeEntry>&& v) {
   }
 }
 
+bool CheckGradAllZero(const std::vector<NodeEntry>& grads,
+                      const std::vector<const Op*>& zero_ops) {
+  if (!grads.size() || !zero_ops.size()) return false;
+  for (const auto& g : grads) {
+    bool found = false;
+    for (const auto& op : zero_ops) {
+      if (g.node->op() == op) {
+        found = true;
+        break;
+      }
+    }
+    if (!found) return false;
+  }
+  return true;
+}
+
 // helper entry
 struct GradEntry {
 #ifdef _MSC_VER
@@ -71,6 +87,10 @@ Graph Gradient(Graph src) {
   if (src.attrs.count("attr_hint_fun") != 0) {
     attr_hint_fun = src.GetAttr<AttrHintFun>("attr_hint_fun");
   }
+  std::vector<const Op*> zero_ops;
+  if (src.attrs.count("zero_ops") != 0) {
+    zero_ops = src.GetAttr<std::vector<const Op*> >("zero_ops");
+  }
 
   // topo sort
   std::vector<NodePtr> topo_order;
@@ -130,10 +150,33 @@ Graph Gradient(Graph src) {
     }
     if ((*rit)->inputs.size() != 0) {
       NodePtr fwd_node = (mirror_map.size() == 0 ? ptr : mirror_map.at(ptr.get()));
-      std::vector<NodeEntry> input_grads = grad_fun_map[ptr->op()](
-          fwd_node, out_agg_grads);
-      CHECK_EQ((*rit)->inputs.size(), input_grads.size())
-          << "Gradient function not returning enough gradient";
+      std::vector<NodeEntry> input_grads;
+      if (grad_fun_map.count(ptr->op())) {
+        input_grads = grad_fun_map[ptr->op()](fwd_node, out_agg_grads);
+        CHECK_EQ((*rit)->inputs.size(), input_grads.size())
+            << "Gradient function not returning enough gradient";
+      } else if (CheckGradAllZero(out_agg_grads, zero_ops)) {
+        for (index_t i = 0; i < fwd_node->num_inputs(); ++i) {
+          std::ostringstream os;
+          if (1 == fwd_node->num_inputs()) {
+            os << fwd_node->attrs.name << "_backward";
+          } else {
+            os << fwd_node->attrs.name << "_in" << i << "_backward";
+          }
+          auto p = Node::Create();
+          p->attrs.op = zero_ops[0];
+          p->attrs.name = os.str();
+          p->inputs.push_back(fwd_node->inputs[i]);
+          p->control_deps.emplace_back(fwd_node);
+          if (p->op()->attr_parser != nullptr) {
+            p->op()->attr_parser(&(p->attrs));
+          }
+          input_grads.emplace_back(nnvm::NodeEntry{p, 0, 0});
+        }
+      } else {
+        LOG(FATAL) << "Operator " << fwd_node->op()->name << " is non-differentiable "
+                   << "because it didn't register FGradient attribute.";
+      }
       auto git = input_grads.begin();
       for (auto it = (*rit)->inputs.begin(); it != (*rit)->inputs.end(); ++it, ++git) {
         auto& ge = output_grads[it->node.get()][it->index];
diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index f677909ce181..a6903ba8b2d5 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -93,6 +93,7 @@ Graph InferAttr(Graph &&ret,
         << "BackwardOp need to have control_deps to its forward op";
       const IndexedGraph::Node& fnode = idx[inode.control_deps[0]];
       NodePtr fwd_ptr = inode.source->control_deps[0];
+      CHECK(fwd_ptr->op() != nullptr) << "Forward op cannot be a variable";
       // use gradient function to find out the correspondence.
       std::vector<NodeEntry> ograd(fwd_ptr->num_outputs());
       for (size_t i = 0; i < ograd.size(); ++i) {
@@ -100,11 +101,10 @@ Graph InferAttr(Graph &&ret,
       }
       // input gradient list
       auto igrad = fgrad[fwd_ptr->op()](fwd_ptr, ograd);
-      const Op* backward_op = inode.source->op();
       const Node* igrad_node = nullptr;
       // Input gradient assignement
       for (size_t i = 0; i < igrad.size(); ++i) {
-        if (igrad[i].node->op() == backward_op) {
+        if (igrad[i].node->op() == inode.source->op()) {
           uint32_t eid = idx.entry_id(nid, igrad[i].index);
           if (fis_none(rshape[eid])) {
             rshape[eid] = rshape[idx.entry_id(fnode.inputs[i])];
@@ -120,6 +120,8 @@ Graph InferAttr(Graph &&ret,
         }
       }
       // out grad entries
+      CHECK(igrad_node != nullptr)
+        << "Cannot find matching backward op for " << inode.source->attrs.name;
       for (size_t i = 0; i < igrad_node->inputs.size(); ++i) {
         const NodeEntry& e = igrad_node->inputs[i];
         if (e.node == nullptr) {

From b45be7e45d4aedd0fc0f26ce0c961e8711f4b51f Mon Sep 17 00:00:00 2001
From: Chris Olivier <cjolivier01@gmail.com>
Date: Mon, 3 Apr 2017 17:12:44 -0700
Subject: [PATCH 427/949] CMake/make adjustments and warning fix (#106)

* CMake/make adjustments and warning fix

* Fix warnings
---
 nnvm/CMakeLists.txt       | 13 ++++++++++++-
 nnvm/Makefile             |  6 ++++++
 nnvm/include/nnvm/tuple.h |  2 +-
 3 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/nnvm/CMakeLists.txt b/nnvm/CMakeLists.txt
index 56c1c25baf53..d40deb5f94f4 100644
--- a/nnvm/CMakeLists.txt
+++ b/nnvm/CMakeLists.txt
@@ -4,6 +4,10 @@ project(nnvm C CXX)
 
 list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
 
+if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/build/private/local_config.cmake)
+  include(${CMAKE_CURRENT_SOURCE_DIR}/build/private/local_config.cmake)
+endif()
+
 include(cmake/Utils.cmake)
 
 # include path
@@ -57,9 +61,16 @@ elseif(DMLC_CORE_PATH)
   include_directories(${DMLC_CORE_PATH}/include)
 endif()
 
-add_library(nnvm ${SOURCE})
+add_library(nnvm SHARED ${SOURCE})
 target_link_libraries(nnvm ${nnvm_LINKER_LIBS})
 
+if(INSTALL_INCLUDE_DIR)
+  add_custom_command(TARGET nnvm POST_BUILD
+    COMMAND COMMAND ${CMAKE_COMMAND} -E copy_directory
+    ${CMAKE_SOURCE_DIR}/include ${INSTALL_INCLUDE_DIR}/
+    )
+endif()
+
 # ---[ Linter target
 if(MSVC)
   find_package(PythonInterp 2)
diff --git a/nnvm/Makefile b/nnvm/Makefile
index f1506746bd86..915b991d6882 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -18,6 +18,12 @@ else
   CFLAGS += -I$(ROOTDIR)/dmlc-core/include
 endif
 
+ifdef DMLC_CORE_PATH
+  CFLAGS += -I$(DMLC_CORE_PATH)/include
+else
+  CFLAGS += -I$(ROOTDIR)/dmlc-core/include
+endif
+
 ifneq ($(ADD_CFLAGS), NONE)
 	CFLAGS += $(ADD_CFLAGS)
 endif
diff --git a/nnvm/include/nnvm/tuple.h b/nnvm/include/nnvm/tuple.h
index 1acd37b57363..a174172297d1 100644
--- a/nnvm/include/nnvm/tuple.h
+++ b/nnvm/include/nnvm/tuple.h
@@ -443,7 +443,7 @@ class TShape : public Tuple<index_t> {
    */
   template<int dim>
   inline mshadow::Shape<dim> get() const {
-    CHECK_EQ(dim, ndim())
+    CHECK_EQ(dim, static_cast<int>(ndim()))
         << "dimension do not match target dimension " << dim << " vs " << ndim();
     const index_t *d = this->data();
     mshadow::Shape<dim> s;

From 327bad3fd9ef33154e938d3827ca428d03575aa2 Mon Sep 17 00:00:00 2001
From: Eric Junyuan Xie <piiswrong@users.noreply.github.com>
Date: Tue, 18 Apr 2017 10:51:45 -0700
Subject: [PATCH 428/949] fix (#111)

---
 nnvm/src/pass/plan_memory.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/src/pass/plan_memory.cc b/nnvm/src/pass/plan_memory.cc
index e9029c2268ba..696786a12a9f 100644
--- a/nnvm/src/pass/plan_memory.cc
+++ b/nnvm/src/pass/plan_memory.cc
@@ -171,7 +171,7 @@ size_t AllocMemory(const Graph& ret, const IndexedGraph& idx, StorageVector* sto
         if (ref_count[eid_in] == 1 &&
             ref_count[eid_out] != 0 &&
             storage[eid_out] == GraphAllocator::kBadStorageID &&
-            storage[eid_in] != GraphAllocator::kBadStorageID &&
+            storage[eid_in] >= 0 &&
             shape_vec[eid_out].Size() == shape_vec[eid_in].Size() &&
             dtype_vec[eid_out] == dtype_vec[eid_in]) {
           // inplace optimization

From ef7982e827a8b64e5ad810ef1497a42a18dadbd6 Mon Sep 17 00:00:00 2001
From: ziheng <ziheng@apache.org>
Date: Fri, 19 May 2017 10:24:44 -0700
Subject: [PATCH 429/949] Rename TShape::index_t to dim_t and change to int64_t
 (#114)

* Change TShape::index_t to int64_t

* Add comment

* Make Tuple::Save&Load dtype generic

* trigger update

* Fix lint

* Fix comment

* Change index_t to dim_t

* Remove legacy index_t
---
 nnvm/include/nnvm/tuple.h    | 150 ++++++++++++++++++++++-------------
 nnvm/src/pass/gradient.cc    |   2 +-
 nnvm/tests/cpp/tuple_test.cc |   2 +-
 3 files changed, 99 insertions(+), 55 deletions(-)

diff --git a/nnvm/include/nnvm/tuple.h b/nnvm/include/nnvm/tuple.h
index a174172297d1..90a932e8df8c 100644
--- a/nnvm/include/nnvm/tuple.h
+++ b/nnvm/include/nnvm/tuple.h
@@ -15,8 +15,8 @@
 
 namespace nnvm {
 
-/*! \brief data type to store array index */
-typedef uint32_t index_t;
+/*! \brief data type to store dim size */
+typedef int64_t dim_t;
 
 /*!
  * \brief A dynamic sized array data strcuture that is optimized for storing
@@ -151,7 +151,7 @@ class Tuple {
     return ndim_ <= kStackCache ? (data_stack_ + ndim_): (data_heap_ + ndim_);
   }
   /*! \return number of dimension of the tuple */
-  inline index_t ndim() const {
+  inline uint32_t ndim() const {
     return ndim_;
   }
   /*!
@@ -159,7 +159,7 @@ class Tuple {
    * \param i dimension index
    * \return the corresponding dimension size
    */
-  inline ValueType& operator[](index_t i) {
+  inline ValueType& operator[](size_t i) {
     return begin()[i];
   }
   /*!
@@ -167,7 +167,7 @@ class Tuple {
    * \param i dimension index
    * \return the corresponding dimension size
    */
-  inline const ValueType& operator[](index_t i) const {
+  inline const ValueType& operator[](size_t i) const {
     return begin()[i];
   }
   /*!
@@ -275,55 +275,48 @@ class Tuple {
   /*!
    * \brief save the content into binary stream
    * \param strm the output stream
+   * \tparam DType data type that save to
    * \tparam TStream any stream type that have write
    */
-  template<typename TStream>
-  inline void Save(TStream *strm) const {
-    strm->Write(&ndim_, sizeof(ndim_));
-    strm->Write(begin(), sizeof(ValueType) * ndim_);
-  }
+  template<typename DType = ValueType, typename TStream>
+  inline void Save(TStream *strm) const;
   /*!
    * \brief load the content from binary stream
    * \param strm the output stream
+   * \tparam DType data type that load from
    * \tparam TStream any stream type that have write
    * \return whether the load is successful
    */
-  template<typename TStream>
-  inline bool Load(TStream *strm) {
-    if (strm->Read(&ndim_, sizeof(ndim_)) != sizeof(ndim_)) return false;
-    this->SetDim(ndim_);
-    size_t nread = sizeof(ValueType) * ndim_;
-    if (strm->Read(begin(), nread) != nread) return false;
-    return true;
-  }
+  template<typename DType = ValueType, typename TStream>
+  inline bool Load(TStream *strm);
 
  protected:
   // stack cache size
   static const uint32_t kStackCache = 4;
   /*! \brief number of dimension of the tuple */
-  index_t ndim_{0};
+  uint32_t ndim_{0};
   /*! \brief number of cells allocated in data_heap_ */
-  index_t num_heap_allocated_{0};
+  uint32_t num_heap_allocated_{0};
   /*! \brief in stack space used to store shape when it is small */
   ValueType data_stack_[kStackCache];
   /*! \brief space to store shape when dimension is big*/
   ValueType* data_heap_{nullptr};
   // internal function to change the dimension
-  inline void SetDim(index_t dim) {
-    if (dim > kStackCache &&
-        dim > num_heap_allocated_) {
+  inline void SetDim(uint32_t ndim) {
+    if (ndim > kStackCache &&
+        ndim > num_heap_allocated_) {
       delete [] data_heap_;
-      data_heap_ = new ValueType[dim];
-      num_heap_allocated_ = dim;
+      data_heap_ = new ValueType[ndim];
+      num_heap_allocated_ = ndim;
     }
-    ndim_ = dim;
+    ndim_ = ndim;
   }
 };
 
 /*!
  * \brief A Shape class that is used to represent shape of each tensor.
  */
-class TShape : public Tuple<index_t> {
+class TShape : public Tuple<dim_t> {
  public:
   /*! \brief default constructor */
   TShape() = default;
@@ -331,7 +324,7 @@ class TShape : public Tuple<index_t> {
    * constructor to construct a shape with all 1.
    * \param ndim the number of dimension
    */
-  inline TShape(index_t ndim) {  // NOLINT(*)
+  inline TShape(uint32_t ndim) {  // NOLINT(*)
     this->SetDim(ndim);
     std::fill_n(begin(), ndim, 1);
   }
@@ -339,21 +332,21 @@ class TShape : public Tuple<index_t> {
    * \brief copy constructor of TShape
    * \param s source shape.
    */
-  inline TShape(const Tuple<index_t>& s) { // NOLINT(*)
+  inline TShape(const Tuple<dim_t>& s) { // NOLINT(*)
     this->assign(s.begin(), s.end());
   }
   /*!
    * \brief constructor from initializer list
    * \param init the initializer_list
    */
-  inline TShape(std::initializer_list<index_t> init) {
+  inline TShape(std::initializer_list<dim_t> init) {
     this->assign(init.begin(), init.end());
   }
   /*!
    * \brief move constructor.
    * \param s source shape.
    */
-  inline TShape(Tuple<index_t>&& s) {  // NOLINT(*)
+  inline TShape(Tuple<dim_t>&& s) {  // NOLINT(*)
     this->swap(s);
   }
   /*!
@@ -372,7 +365,7 @@ class TShape : public Tuple<index_t> {
    * \param src source shape.
    * \return self.
    */
-  inline TShape& operator=(const Tuple<index_t>& src) {
+  inline TShape& operator=(const Tuple<dim_t>& src) {
     this->assign(src.begin(), src.end());
     return *this;
   }
@@ -381,15 +374,15 @@ class TShape : public Tuple<index_t> {
    * \param src source shape.
    * \return self.
    */
-  inline TShape& operator=(Tuple<index_t>&& src) {  // NOLINT(*)
+  inline TShape& operator=(Tuple<dim_t>&& src) {  // NOLINT(*)
     TShape(std::move(src)).swap(*this);  // NOLINT(*)
     return *this;
   }
   /*! \return total number of elements in the shape */
   inline size_t Size() const {
-    size_t size = 1;
-    const index_t* start = begin(), *fin = end();
-    for (const index_t* it = start; it != fin; ++it) {
+    dim_t size = 1;
+    const dim_t* start = begin(), *fin = end();
+    for (const dim_t* it = start; it != fin; ++it) {
       size *= *it;
     }
     return size;
@@ -399,20 +392,20 @@ class TShape : public Tuple<index_t> {
    * \param dimstart start dimension
    * \param dimend end dimension
    */
-  inline index_t ProdShape(int dimstart, int dimend) const {
-    index_t num = 1;
-    const index_t *d = this->data();
+  inline size_t ProdShape(int dimstart, int dimend) const {
+    dim_t num = 1;
+    const dim_t *d = this->data();
     for (int i = dimstart; i < dimend; ++i) {
       num *= d[i];
     }
     return num;
   }
   /*! \return the begin data pointer to content of the tuple */
-  inline const index_t *data() const {
+  inline const dim_t *data() const {
     return begin();
   }
   /*! \return the begin data pointer to content of the tuple */
-  inline index_t *data() {
+  inline dim_t *data() {
     return begin();
   }
 #ifdef MSHADOW_XINLINE
@@ -445,7 +438,7 @@ class TShape : public Tuple<index_t> {
   inline mshadow::Shape<dim> get() const {
     CHECK_EQ(dim, static_cast<int>(ndim()))
         << "dimension do not match target dimension " << dim << " vs " << ndim();
-    const index_t *d = this->data();
+    const dim_t *d = this->data();
     mshadow::Shape<dim> s;
     for (int i = 0; i < dim; ++i) {
       s[i] = d[i];
@@ -459,10 +452,10 @@ class TShape : public Tuple<index_t> {
   inline mshadow::Shape<2> FlatTo2D(void) const {
     mshadow::Shape<2> s;
     if (ndim() == 0) return mshadow::Shape2(0, 0);
-    const index_t *d = this->data();
+    const dim_t *d = this->data();
     s.shape_[1] = d[ndim() - 1];
-    index_t ymax = 1;
-    for (index_t i = 1; i < ndim(); ++i) {
+    dim_t ymax = 1;
+    for (size_t i = 1; i < ndim(); ++i) {
       ymax *= d[i - 1];
     }
     s.shape_[0] = ymax;
@@ -474,22 +467,22 @@ class TShape : public Tuple<index_t> {
    * \param axis_end The ending axis specified.
    * \return the flat 3d shape
    */
-  inline mshadow::Shape<3> FlatTo3D(index_t axis_begin, index_t axis_end) const {
+  inline mshadow::Shape<3> FlatTo3D(size_t axis_begin, size_t axis_end) const {
     CHECK(axis_end >= axis_begin);
     mshadow::Shape<3> s;
     if (ndim() == 0) return mshadow::Shape3(0, 0, 0);
-    const index_t *d = this->data();
+    const dim_t *d = this->data();
     s.shape_[0] = 1;
     s.shape_[1] = 1;
     s.shape_[2] = 1;
 
-    for (index_t i = 0; i < axis_begin; ++i) {
+    for (size_t i = 0; i < axis_begin; ++i) {
       s.shape_[0] *= d[i];
     }
-    for (index_t i = axis_begin; i <= axis_end; ++i) {
+    for (size_t i = axis_begin; i <= axis_end; ++i) {
       s.shape_[1] *= d[i];
     }
-    for (index_t i = axis_end + 1; i < ndim(); ++i) {
+    for (size_t i = axis_end + 1; i < ndim(); ++i) {
       s.shape_[2] *= d[i];
     }
     return s;
@@ -499,7 +492,7 @@ class TShape : public Tuple<index_t> {
    * \param axis The axis specified.
    * \return the flat 3d shape
    */
-  inline mshadow::Shape<3> FlatTo3D(index_t axis) const {
+  inline mshadow::Shape<3> FlatTo3D(size_t axis) const {
     return FlatTo3D(axis, axis);
   }
   inline bool operator==(const TShape &s) const {
@@ -517,8 +510,8 @@ class TShape : public Tuple<index_t> {
   template<int dim>
   inline bool operator==(const mshadow::Shape<dim> &s) const {
     if (ndim_ != dim) return false;
-    const index_t *d = dim <= kStackCache ? data_stack_ : data_heap_;
-    for (index_t i = 0; i < dim; ++i) {
+    const dim_t *d = dim <= kStackCache ? data_stack_ : data_heap_;
+    for (size_t i = 0; i < dim; ++i) {
       if (d[i] != s.shape_[i]) return false;
     }
     return true;
@@ -535,6 +528,57 @@ class TShape : public Tuple<index_t> {
 #endif
 };
 
+/*! \brief helper function to cast type of container elements */
+template<typename SrcIter, typename DstIter>
+inline DstIter ShapeTypeCast(const SrcIter begin,
+                             const SrcIter end,
+                             DstIter dst_begin) {
+  typedef typename std::iterator_traits<SrcIter>::value_type SrcDType;
+  typedef typename std::iterator_traits<DstIter>::value_type DstDType;
+  auto cast = [](const SrcDType& dim) { return static_cast<DstDType>(dim); };
+  return std::transform(begin, end, dst_begin, cast);
+}
+
+/*! \brief helper function to transform a container to TShape with type cast */
+template<typename SrcIter>
+inline TShape ShapeTypeCast(const SrcIter begin, const SrcIter end) {
+  size_t ndim = std::distance(begin, end);
+  TShape res(ndim);
+  ShapeTypeCast(begin, end, res.begin());
+  return res;
+}
+
+/*! \tparam ValueType The type of data stored inside tuple. */
+template<typename ValueType>
+template<typename DType, typename TStream>
+inline void Tuple<ValueType>::Save(TStream *strm) const {
+  strm->Write(&ndim_, sizeof(ndim_));
+  if (typeid(DType) == typeid(ValueType)) {
+    strm->Write(begin(), sizeof(ValueType) * ndim_);
+  } else {
+    std::vector<DType> buffer(ndim_);
+    ShapeTypeCast(begin(), end(), buffer.data());
+    strm->Write(buffer.data(), sizeof(DType) * ndim_);
+  }
+}
+
+/*! \tparam ValueType The type of data stored inside tuple. */
+template<typename ValueType>
+template<typename DType, typename TStream>
+inline bool Tuple<ValueType>::Load(TStream *strm) {
+  if (strm->Read(&ndim_, sizeof(ndim_)) != sizeof(ndim_)) return false;
+  this->SetDim(ndim_);
+  size_t nread = sizeof(DType) * ndim_;
+  if (typeid(DType) == typeid(ValueType)) {
+    if (strm->Read(begin(), nread) != nread) return false;
+  } else {
+    std::vector<DType> buffer(ndim_);
+    if (strm->Read(buffer.data(), nread) != nread) return false;
+    ShapeTypeCast(buffer.begin(), buffer.end(), begin());
+  }
+  return true;
+}
+
 }  // namespace nnvm
 
 #endif  // NNVM_TUPLE_H_
diff --git a/nnvm/src/pass/gradient.cc b/nnvm/src/pass/gradient.cc
index 90e56da4d781..d0814791ae12 100644
--- a/nnvm/src/pass/gradient.cc
+++ b/nnvm/src/pass/gradient.cc
@@ -156,7 +156,7 @@ Graph Gradient(Graph src) {
         CHECK_EQ((*rit)->inputs.size(), input_grads.size())
             << "Gradient function not returning enough gradient";
       } else if (CheckGradAllZero(out_agg_grads, zero_ops)) {
-        for (index_t i = 0; i < fwd_node->num_inputs(); ++i) {
+        for (size_t i = 0; i < fwd_node->num_inputs(); ++i) {
           std::ostringstream os;
           if (1 == fwd_node->num_inputs()) {
             os << fwd_node->attrs.name << "_backward";
diff --git a/nnvm/tests/cpp/tuple_test.cc b/nnvm/tests/cpp/tuple_test.cc
index 85cbc08f1f6f..36d194927515 100644
--- a/nnvm/tests/cpp/tuple_test.cc
+++ b/nnvm/tests/cpp/tuple_test.cc
@@ -17,7 +17,7 @@ TEST(Tuple, Basic) {
   std::istringstream is(os.str());
   is >> y;
   CHECK_EQ(x, y);
-  Tuple<nnvm::index_t> ss{1, 2, 3};
+  Tuple<nnvm::dim_t> ss{1, 2, 3};
   TShape s = ss;
   s = std::move(ss);
   CHECK((s == TShape{1, 2, 3}));

From 01eb7a7663c63cf9ddac8e0fac2b8768789b8f68 Mon Sep 17 00:00:00 2001
From: ziheng <ziheng@apache.org>
Date: Thu, 25 May 2017 14:36:10 -0700
Subject: [PATCH 430/949] Fix for build (#117)

---
 nnvm/Makefile | 1 +
 1 file changed, 1 insertion(+)

diff --git a/nnvm/Makefile b/nnvm/Makefile
index 915b991d6882..d2f26280909b 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -98,3 +98,4 @@ clean:
 
 -include build/*.d
 -include build/*/*.d
+-include build/*/*/*.d

From 6f36839b46c11c9c24158b71ee381c5bcfeed683 Mon Sep 17 00:00:00 2001
From: Eric Junyuan Xie <piiswrong@users.noreply.github.com>
Date: Fri, 26 May 2017 23:33:11 -0700
Subject: [PATCH 431/949] create symbol from nodeattr (#116)

---
 nnvm/include/nnvm/symbolic.h |  8 +++++++-
 nnvm/src/core/symbolic.cc    | 16 ++++++++++++++++
 2 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/nnvm/include/nnvm/symbolic.h b/nnvm/include/nnvm/symbolic.h
index ab836ad5eab1..22256ae29097 100644
--- a/nnvm/include/nnvm/symbolic.h
+++ b/nnvm/include/nnvm/symbolic.h
@@ -21,7 +21,7 @@
 namespace nnvm {
 /*!
  * \brief Symbol is help class used to represent the operator node in Graph.
- * 
+ *
  *  Symbol acts as an interface for building graphs from different components
  *  like Variable, Functor and Group. Symbol is also exported to python front-end
  *  (while Graph is not) to enable quick test and deployment. Conceptually,
@@ -192,6 +192,12 @@ class Symbol {
    */
   static Symbol CreateFunctor(const Op* op,
                               std::unordered_map<std::string, std::string> attrs);
+  /*!
+   * \brief Create symbolic functor(AtomicSymbol) by given node attributes.
+   * \param attrs pre-initialized Node attributes.
+   * \return Symbol that can be used to call compose further.
+   */
+  static Symbol CreateFunctor(const NodeAttrs& attrs);
   /*!
    * \brief Create symbol node representing variable.
    * \param name Name of the variable.
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index 51cc1fa9e9a0..cae22eab4427 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -527,6 +527,22 @@ Symbol Symbol::CreateFunctor(const Op* op,
   return s;
 }
 
+Symbol Symbol::CreateFunctor(const NodeAttrs& attrs) {
+  static auto& fnum_vis_output = Op::GetAttr<FNumVisibleOutputs>("FNumVisibleOutputs");
+  Symbol s;
+  NodePtr n = Node::Create();
+  n->attrs = attrs;
+
+  uint32_t nout = n->num_outputs();
+  if (fnum_vis_output.count(n->op())) {
+    nout = fnum_vis_output[n->op()](n->attrs);
+  }
+  for (uint32_t i = 0; i < nout; ++i) {
+    s.outputs.emplace_back(NodeEntry{n, i, 0});
+  }
+  return s;
+}
+
 Symbol Symbol::CreateGroup(const std::vector<Symbol> &symbols) {
   Symbol ret;
   for (const auto &s : symbols) {

From b967600a84b307195fd701da94dfcf4a84844a00 Mon Sep 17 00:00:00 2001
From: Przemyslaw Tredak <ptrendx@gmail.com>
Date: Fri, 23 Jun 2017 13:47:29 -0700
Subject: [PATCH 432/949] Handling duplicate NodeEntries on the edge of the
 gradient graph (#122)

* Handling duplicate NodeEntries on the edge of the graph

* Fix docs and segfault

* Suggestions from review

* Added attr_parser check
---
 nnvm/include/nnvm/pass_functions.h |  9 +++++++-
 nnvm/src/pass/gradient.cc          | 34 ++++++++++++++++++++++++++++--
 2 files changed, 40 insertions(+), 3 deletions(-)

diff --git a/nnvm/include/nnvm/pass_functions.h b/nnvm/include/nnvm/pass_functions.h
index c40154d4873f..56ac11bab8e6 100644
--- a/nnvm/include/nnvm/pass_functions.h
+++ b/nnvm/include/nnvm/pass_functions.h
@@ -132,6 +132,8 @@ inline Graph PlaceDevice(Graph graph,
  * \param attr_hint_fun Optional, hint function to output a node that like src, but its attr is same as like.
  * \param zero_ops Optional, list of operators that outputs a single zero array. The first one
  *  must be zeros_like.
+ * \param copy_op_str Optional, name of the copy operation required to handle duplicates
+ *  on the edge of the graph
  * \return A new graph, whose outputs correspond to inputs of xs.
  */
 inline Graph Gradient(
@@ -143,7 +145,8 @@ inline Graph Gradient(
     std::function<int(const Node& node)> mirror_fun = nullptr,
     std::function<NodeEntry(const NodeEntry& src, const NodeEntry &like)>
     attr_hint_fun = nullptr,
-    std::vector<const Op*> zero_ops = std::vector<const Op*>()) {
+    std::vector<const Op*> zero_ops = std::vector<const Op*>(),
+    std::string copy_op_str = std::string()) {
   graph.attrs["grad_ys"] = std::make_shared<any>(std::move(ys));
 
   graph.attrs["grad_xs"] = std::make_shared<any>(std::move(xs));
@@ -164,6 +167,10 @@ inline Graph Gradient(
     graph.attrs["zero_ops"] = std::make_shared<any>(std::move(zero_ops));
   }
 
+  if (copy_op_str != std::string()) {
+      graph.attrs["copy_op"] = std::make_shared<any>(std::move(copy_op_str));
+  }
+
   return ApplyPass(std::move(graph), "Gradient");
 }
 
diff --git a/nnvm/src/pass/gradient.cc b/nnvm/src/pass/gradient.cc
index d0814791ae12..51f2b86c9121 100644
--- a/nnvm/src/pass/gradient.cc
+++ b/nnvm/src/pass/gradient.cc
@@ -91,6 +91,9 @@ Graph Gradient(Graph src) {
   if (src.attrs.count("zero_ops") != 0) {
     zero_ops = src.GetAttr<std::vector<const Op*> >("zero_ops");
   }
+  const Op* copy_op = (src.attrs.count("copy_op") != 0) ?
+      Op::Get(src.GetAttr<std::string>("copy_op")) :
+      nullptr;
 
   // topo sort
   std::vector<NodePtr> topo_order;
@@ -190,7 +193,9 @@ Graph Gradient(Graph src) {
   }
   // take out the xs' grads
   Graph ret;
-  ret.outputs.reserve(xs.size());
+  ret.outputs.resize(xs.size());
+  NodeEntryMap<std::pair<size_t, size_t> > unique_grads;
+  size_t counter = 0;
   for (const NodeEntry& e : xs) {
     GradEntry& entry = output_grads[e.node.get()][e.index];
     // aggregate sum if there haven't been
@@ -200,7 +205,32 @@ Graph Gradient(Graph src) {
         entry.sum = attr_hint_fun(entry.sum, e);
       }
     }
-    ret.outputs.emplace_back(std::move(entry.sum));
+    if (copy_op != nullptr) {
+      auto kv = unique_grads.find(entry.sum);
+      if (kv == unique_grads.end()) {
+        unique_grads.emplace(std::move(entry.sum), std::make_pair(1, counter));
+      } else {
+        NodePtr copy_node = Node::Create();
+        std::ostringstream os;
+        os << entry.sum.node->attrs.name << "_" << kv->second.first << "_copy";
+        kv->second.first++;
+        copy_node->attrs.op = copy_op;
+        copy_node->attrs.name = os.str();
+        copy_node->inputs.emplace_back(entry.sum);
+        if (copy_node->attrs.op->attr_parser != nullptr) {
+            copy_node->attrs.op->attr_parser(&(copy_node->attrs));
+        }
+        unique_grads.emplace(NodeEntry{std::move(copy_node), 0, 0}, std::make_pair(1, counter));
+      }
+    } else {
+        ret.outputs[counter] = entry.sum;
+    }
+    ++counter;
+  }
+  if (copy_op != nullptr) {
+    for (const auto& kv : unique_grads) {
+      ret.outputs[kv.second.second] = kv.first;
+    }
   }
   return ret;
 }

From 8c2a9b0616dc8e5145ca59b805132cb4638a6577 Mon Sep 17 00:00:00 2001
From: formath <jinpengliu@163.com>
Date: Fri, 30 Jun 2017 07:06:02 +0800
Subject: [PATCH 433/949] delete dependency node (#121)

---
 nnvm/src/core/node.cc | 1 +
 1 file changed, 1 insertion(+)

diff --git a/nnvm/src/core/node.cc b/nnvm/src/core/node.cc
index a6f50f694989..b5b5ec22fd3f 100644
--- a/nnvm/src/core/node.cc
+++ b/nnvm/src/core/node.cc
@@ -27,6 +27,7 @@ Node::~Node() {
       for (NodePtr& sp : n->control_deps) {
         if (sp.unique()) {
           stack.push_back(sp.get());
+          to_delete.emplace_back(std::move(sp));
         } else {
           sp.reset();
         }

From 8b5bf2a00e00dab92f3a19e9de6955ccced9c096 Mon Sep 17 00:00:00 2001
From: Eric Junyuan Xie <piiswrong@users.noreply.github.com>
Date: Fri, 30 Jun 2017 11:09:11 -0700
Subject: [PATCH 434/949] fix plan memory add inplace_identity option (#124)

* fix plan memory add inplace_identity option

* comment
---
 nnvm/include/nnvm/op_attr_types.h | 14 ++++++--
 nnvm/src/pass/plan_memory.cc      | 60 ++++++++++++++++++++++---------
 2 files changed, 55 insertions(+), 19 deletions(-)

diff --git a/nnvm/include/nnvm/op_attr_types.h b/nnvm/include/nnvm/op_attr_types.h
index 8e15c394be82..e0873979a14b 100644
--- a/nnvm/include/nnvm/op_attr_types.h
+++ b/nnvm/include/nnvm/op_attr_types.h
@@ -107,8 +107,6 @@ using TIsBackward = bool;
  * \brief Get possible inplace options.
  *  This function enables optimization to reuse memory of inputs in output.
  * \param attrs The attributes of the node
- * \param in_data The input data.
- * \param out_data The output data.
  * \return list of pair of that maps input->output,
  *   indicating possible in place operations.
  *
@@ -117,6 +115,18 @@ using TIsBackward = bool;
 using FInplaceOption = std::function<
   std::vector<std::pair<int, int> > (const NodeAttrs& attrs)>;
 
+/*!
+ * \brief Get if the inplace option is an identity
+ *  This function enables inplace optimization even when input reference count
+ *  is greater than one.
+ * \param attrs The attributes of the node
+ * \return list of bool indicating whether corresponding pair from FInplaceOption
+ *         is an identity
+ *
+ * \note Register under "FInplaceIdentity", by default no identities.
+ */
+using FInplaceIdentity = std::function<std::vector<bool> (const NodeAttrs& attrs)>;
+
 /*!
  * \brief Get list of inputs in the op whose content are actually not used by the operator
  *  These are dummy input that can be used for example in zeros_like, ones_like.
diff --git a/nnvm/src/pass/plan_memory.cc b/nnvm/src/pass/plan_memory.cc
index 696786a12a9f..005aeebfb379 100644
--- a/nnvm/src/pass/plan_memory.cc
+++ b/nnvm/src/pass/plan_memory.cc
@@ -142,8 +142,12 @@ class GraphAllocator {
  * Internal method to perform the memory allocation for a graph
  * */
 size_t AllocMemory(const Graph& ret, const IndexedGraph& idx, StorageVector* storage_ptr,
-                   std::vector<int>* storage_inplace_index_ptr, std::vector<uint32_t> ref_count,
+                   std::vector<int>* storage_inplace_index_ptr,
+                   const std::vector<uint32_t>& entry_ref_count,
                    GraphAllocator* allocator) {
+  static auto& finplace_option = Op::GetAttr<FInplaceOption>("FInplaceOption");
+  static auto& finplace_identity = Op::GetAttr<FInplaceIdentity>("FInplaceIdentity");
+
   // Get reference
   auto &storage = *storage_ptr;
   auto &storage_inplace_index = *storage_inplace_index_ptr;
@@ -152,12 +156,12 @@ size_t AllocMemory(const Graph& ret, const IndexedGraph& idx, StorageVector* sto
   const ShapeVector& shape_vec = ret.GetAttr<ShapeVector>("shape");
   const DTypeVector& dtype_vec = ret.GetAttr<DTypeVector>("dtype");
   const DeviceVector* device_vec = nullptr;
-  static auto& finplace_option = Op::GetAttr<FInplaceOption>("FInplaceOption");
 
   if (ret.attrs.count("device") != 0) {
     device_vec = &(ret.GetAttr<DeviceVector>("device"));
   }
   size_t num_not_allocated = 0;
+  std::vector<GraphAllocator::StorageID> storage_ref_count(idx.num_node_entries(), 0);
 
   for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
     const auto& inode = idx[nid];
@@ -165,18 +169,36 @@ size_t AllocMemory(const Graph& ret, const IndexedGraph& idx, StorageVector* sto
     // check inplace option
     if (finplace_option.count(inode.source->op()) != 0) {
       auto inplace_pairs = finplace_option[inode.source->op()](inode.source->attrs);
-      for (auto& kv : inplace_pairs) {
+      std::vector<bool> identity;
+      if (finplace_identity.count(inode.source->op()) != 0) {
+        identity = finplace_identity[inode.source->op()](inode.source->attrs);
+        CHECK_EQ(identity.size(), inplace_pairs.size())
+            << "FInplaceOption and FInplaceIdentity returned vectors of different "
+            << "size for operator " << inode.source->op()->name;
+      } else {
+        identity = std::vector<bool>(inplace_pairs.size(), false);
+      }
+      std::vector<bool> taken(inode.inputs.size(), false);
+      for (size_t ipair = 0; ipair < inplace_pairs.size(); ++ipair) {
+        const auto& kv = inplace_pairs[ipair];
         uint32_t eid_out = idx.entry_id(nid, kv.second);
         uint32_t eid_in = idx.entry_id(inode.inputs[kv.first]);
-        if (ref_count[eid_in] == 1 &&
-            ref_count[eid_out] != 0 &&
-            storage[eid_out] == GraphAllocator::kBadStorageID &&
-            storage[eid_in] >= 0 &&
+        auto sid_out = storage[eid_out];
+        auto sid_in = storage[eid_in];
+        if (taken[kv.first] == false &&
+            sid_out == GraphAllocator::kBadStorageID &&
+            sid_in >= 0 &&
+            (storage_ref_count[sid_in] == 1 || identity[ipair]) &&
+            entry_ref_count[eid_out] > 0 &&
             shape_vec[eid_out].Size() == shape_vec[eid_in].Size() &&
             dtype_vec[eid_out] == dtype_vec[eid_in]) {
           // inplace optimization
-          storage[eid_out] = storage[eid_in];
-          ref_count[eid_in] = 0;
+          taken[kv.first] = true;
+          storage[eid_out] = sid_in;
+          // Reuse storage for output and add ref count of output
+          // to storage. This will get substracted later in free
+          // input section.
+          storage_ref_count[sid_in] += entry_ref_count[eid_out];
           storage_inplace_index[eid_out] = kv.first;
         }
       }
@@ -196,7 +218,9 @@ size_t AllocMemory(const Graph& ret, const IndexedGraph& idx, StorageVector* sto
     }
     for (auto rit = eids.rbegin(); rit != eids.rend(); ++rit) {
         uint32_t eid = rit->second;
-        storage[eid] = allocator->Request(dev_id, dtype_vec[eid], shape_vec[eid], nid);
+        auto sid = allocator->Request(dev_id, dtype_vec[eid], shape_vec[eid], nid);
+        storage_ref_count[sid] = entry_ref_count[eid];
+        storage[eid] = sid;
     }
 
     // check if certain inputs is ignored.
@@ -212,20 +236,22 @@ size_t AllocMemory(const Graph& ret, const IndexedGraph& idx, StorageVector* sto
       if (std::binary_search(ignore_inputs.begin(), ignore_inputs.end(), i)) continue;
       const auto& e = inode.inputs[i];
       uint32_t eid = idx.entry_id(e);
-      // temp_ref_count == 0 means it is taken by inplace op
-      if (ref_count[eid] == 0) continue;
+      auto sid = storage[eid];
+      // storage_ref_count == 0 means it is taken by inplace op
+      if (sid < 0) continue;
       // if we decrease it to zero, means we are ready to relase
-      --ref_count[eid];
-      if (ref_count[eid] == 0 && storage[eid] != GraphAllocator::kBadStorageID) {
-        allocator->Release(storage[eid], nid);
+      --storage_ref_count[sid];
+      if (storage_ref_count[sid] == 0) {
+        allocator->Release(sid, nid);
       }
     }
     // check if there are outputs that can be freeded immediately
     // these output are not referenced by any operator.
     for (uint32_t index = 0; index < inode.source->num_outputs(); ++index) {
       uint32_t eid = idx.entry_id(nid, index);
-      if (ref_count[eid] == 0 && storage[eid] != GraphAllocator::kBadStorageID) {
-        allocator->Release(storage[eid], nid);
+      auto sid = storage[eid];
+      if (sid >= 0 && storage_ref_count[sid] == 0) {
+        allocator->Release(sid, nid);
         // use -2 to indicate that the node was never touched.
         storage_inplace_index[eid] = -2;
       }

From 176a8370f4f33418139e01bb6543d0f0b243cd88 Mon Sep 17 00:00:00 2001
From: Sheng Zha <szha@users.noreply.github.com>
Date: Thu, 6 Jul 2017 20:53:17 -0700
Subject: [PATCH 435/949] enable negative values in TShape (#125)

---
 nnvm/include/nnvm/tuple.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/include/nnvm/tuple.h b/nnvm/include/nnvm/tuple.h
index 90a932e8df8c..1a5a9e63d42f 100644
--- a/nnvm/include/nnvm/tuple.h
+++ b/nnvm/include/nnvm/tuple.h
@@ -216,7 +216,7 @@ class Tuple {
     // get (
     while (true) {
       char ch = is.peek();
-      if (isdigit(ch)) {
+      if (isdigit(ch) || ch == '-') {
         ValueType idx;
         if (is >> idx) {
           t.assign(&idx, &idx + 1);

From abf1b8a21ffde294ef5a99e17c2a95732eb31872 Mon Sep 17 00:00:00 2001
From: zhuhyc <zzhniy.163.niy@163.com>
Date: Wed, 12 Jul 2017 23:39:55 +0800
Subject: [PATCH 436/949] Fixed make no_optimization runs indefinitely when run
 the memcost example provided  (#126)

* fixed make no_optimization runs indefinitely when run the memcost example provided

* format plan_memory.cc
---
 nnvm/src/pass/plan_memory.cc | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/nnvm/src/pass/plan_memory.cc b/nnvm/src/pass/plan_memory.cc
index 005aeebfb379..ccfe4cf9fded 100644
--- a/nnvm/src/pass/plan_memory.cc
+++ b/nnvm/src/pass/plan_memory.cc
@@ -323,6 +323,10 @@ Graph PlanMemory(Graph ret) {
       ret.attrs["storage_num_not_allocated"] = std::make_shared<any>(storage_num_not_allocated);
       min_allocated_bytes = storage_allocated_bytes;
     }
+
+    if (max_match_range == 0) {
+      break;
+    }
   }
   return ret;
 }

From 094f014287362d07440d4e865c4e5796a30b1121 Mon Sep 17 00:00:00 2001
From: Terence Wu <2326428753@qq.com>
Date: Fri, 14 Jul 2017 11:12:55 +0800
Subject: [PATCH 437/949] Fix a spelling mistake (#128)

---
 nnvm/include/nnvm/base.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/include/nnvm/base.h b/nnvm/include/nnvm/base.h
index 94fa35ce3048..792c61daa938 100644
--- a/nnvm/include/nnvm/base.h
+++ b/nnvm/include/nnvm/base.h
@@ -1,7 +1,7 @@
 /*!
  *  Copyright (c) 2016 by Contributors
  * \file base.h
- * \brief Configuation of nnvm as well as basic data structure.
+ * \brief Configuration of nnvm as well as basic data structure.
  */
 #ifndef NNVM_BASE_H_
 #define NNVM_BASE_H_

From 7a9a02d24e435d57c1f260cfdc429b6ea55c2ada Mon Sep 17 00:00:00 2001
From: Terence Wu <2326428753@qq.com>
Date: Fri, 14 Jul 2017 11:14:31 +0800
Subject: [PATCH 438/949] Fix 2 spelling mistakes (#129)

---
 nnvm/include/nnvm/tuple.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/nnvm/include/nnvm/tuple.h b/nnvm/include/nnvm/tuple.h
index 1a5a9e63d42f..d6978f7b55c5 100644
--- a/nnvm/include/nnvm/tuple.h
+++ b/nnvm/include/nnvm/tuple.h
@@ -19,7 +19,7 @@ namespace nnvm {
 typedef int64_t dim_t;
 
 /*!
- * \brief A dynamic sized array data strcuture that is optimized for storing
+ * \brief A dynamic sized array data structure that is optimized for storing
  *        small number of elements with same type.
  *
  *  Data will be stored in stack when number of elements is small.
@@ -72,7 +72,7 @@ class Tuple {
   }
   /*!
    * \brief Assign content to tuple from iterator.
-   * \param begin the beginning of iteratro
+   * \param begin the beginning of iterator
    * \param end end the end of the iterator
    * \tparam RandomAccessIterator iterator type
    */

From 36ade4746d7eed106417855bf13735c729644c82 Mon Sep 17 00:00:00 2001
From: Haibin Lin <linhaibin.eric@gmail.com>
Date: Fri, 14 Jul 2017 16:23:06 -0700
Subject: [PATCH 439/949] add kDynamicStorageID to plan mem (#127)

---
 nnvm/src/pass/plan_memory.cc | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/nnvm/src/pass/plan_memory.cc b/nnvm/src/pass/plan_memory.cc
index ccfe4cf9fded..1f543f8a82b9 100644
--- a/nnvm/src/pass/plan_memory.cc
+++ b/nnvm/src/pass/plan_memory.cc
@@ -24,6 +24,8 @@ class GraphAllocator {
   static const StorageID kBadStorageID = -1;
   // external storage id
   static const StorageID kExternalStorageID = -2;
+  // dynamic storage id
+  static const StorageID kDynamicStorageID = -3;
 
   // request a free storage
   StorageID Request(int dev_id, int dtype, TShape shape, uint32_t node_id) {
@@ -66,7 +68,7 @@ class GraphAllocator {
   // release a memory space.
   void Release(StorageID id, uint32_t node_id) {
     CHECK_NE(id, kBadStorageID);
-    if (id == kExternalStorageID) return;
+    if (id == kExternalStorageID || id == kDynamicStorageID) return;
     StorageEntry *e = data_[id].get();
     e->released_by_node = node_id;
     free_.insert({e->max_bytes, e});
@@ -209,6 +211,7 @@ size_t AllocMemory(const Graph& ret, const IndexedGraph& idx, StorageVector* sto
     std::multimap<size_t, uint32_t> eids;
     for (uint32_t index = 0; index < inode.source->num_outputs(); ++index) {
       uint32_t eid = idx.entry_id(nid, index);
+      // only request memory for kBadStorageID
       if (storage[eid] == GraphAllocator::kBadStorageID) {
         auto &eshape = shape_vec[eid];
         size_t esize = 0;

From fe49f06848b1ecba5e5e9d05e51770e560cedd61 Mon Sep 17 00:00:00 2001
From: Ye Zhou <zhouye01@gmail.com>
Date: Fri, 21 Jul 2017 23:50:43 +0800
Subject: [PATCH 440/949] Fix error during loading library in python3 (#130)

---
 nnvm/python/nnvm/cython/base.pyi | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/python/nnvm/cython/base.pyi b/nnvm/python/nnvm/cython/base.pyi
index 186d65a46b68..b1f67876598d 100644
--- a/nnvm/python/nnvm/cython/base.pyi
+++ b/nnvm/python/nnvm/cython/base.pyi
@@ -80,7 +80,7 @@ cdef BuildDoc(nn_uint num_args,
         type_info = arg_types[i]
         ret = '%s : %s' % (key, type_info)
         if len(arg_descs[i]) != 0:
-            ret += '\n    ' + arg_descs[i]
+            ret += '\n    ' + py_str(arg_descs[i])
         param_str.append(ret)
     doc_str = ('Parameters\n' +
                '----------\n' +

From c87ef1ae92d8bdf548d15d85346994bdbba59ec1 Mon Sep 17 00:00:00 2001
From: Rahul <rahulhuilgol@gmail.com>
Date: Mon, 24 Jul 2017 15:07:51 -0700
Subject: [PATCH 441/949] remove unused variable (#131)

---
 nnvm/src/core/symbolic.cc | 1 -
 1 file changed, 1 deletion(-)

diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index cae22eab4427..38a95c7637bc 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -436,7 +436,6 @@ Symbol Symbol::GetInternals() const {
 }
 
 Symbol Symbol::GetChildren() const {
-  static auto& fnum_vis_output = Op::GetAttr<FNumVisibleOutputs>("FNumVisibleOutputs");
   Symbol ret;
   std::unordered_set<Node*> visited;
   for (const auto& p : this->outputs) {

From dec98483ec466f9d13841a6c41e9507e6b249398 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 27 Jul 2017 21:37:42 -0700
Subject: [PATCH 442/949] [ATTR/SYMBOL] Expose op_name attr to python (#132)

* [ATTR/SYMBOL] Expose op_name attr to python

* fix xcode
---
 nnvm/src/core/symbolic.cc        |  3 +++
 nnvm/tests/python/test_symbol.py | 10 ++++++++++
 2 files changed, 13 insertions(+)

diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index 38a95c7637bc..38f2738ae220 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -473,6 +473,9 @@ bool Symbol::GetAttr(const std::string& key, std::string* out) const {
   if (key == "name") {
     *out = node->attrs.name;
     return true;
+  } else if (key == "op_name") {
+    *out = node->attrs.op->name;
+    return true;
   }
   auto it = node->attrs.dict.find(key);
   if (it == node->attrs.dict.end()) return false;
diff --git a/nnvm/tests/python/test_symbol.py b/nnvm/tests/python/test_symbol.py
index ec5821051c86..05b47c86300a 100644
--- a/nnvm/tests/python/test_symbol.py
+++ b/nnvm/tests/python/test_symbol.py
@@ -45,6 +45,15 @@ def test_copy():
                 name='exp', gpu=1, attr={"kk": "1"})
     assert y.__copy__().debug_str() == y.debug_str()
 
+
+def test_op_name():
+    x = sym.Variable('x')
+    y = sym.exp(x)
+    op_name = y.attr("op_name")
+    op_func = sym.__dict__[op_name]
+    z = op_func(x)
+
+
 def test_control_dep():
     x = sym.Variable('x')
     y = sym.conv2d(data=x, name='conv')
@@ -53,6 +62,7 @@ def test_control_dep():
     t._add_control_deps([z, y])
 
 if __name__ == "__main__":
+    test_op_name()
     test_copy()
     test_default_input()
     test_compose()

From b6d9b43c47cde8c5181d518dd44f1f52ed54b109 Mon Sep 17 00:00:00 2001
From: Eric Junyuan Xie <piiswrong@users.noreply.github.com>
Date: Fri, 28 Jul 2017 16:16:35 -0700
Subject: [PATCH 443/949] allow variable composition (#133)

---
 nnvm/src/core/symbolic.cc | 20 +++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)

diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index 38f2738ae220..c86cfba94fca 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -87,8 +87,11 @@ inline std::vector<std::string> GetKeys(
 
 // whether the symbol is atomic functor
 inline bool IsAtomic(const std::vector<NodeEntry>& outputs) {
-  return outputs[0].node->inputs.size() == 0 &&
-      outputs[0].node->control_deps.size() == 0;
+  Node* node = outputs[0].node.get();
+  for (const NodeEntry& e : outputs) {
+    if (node != e.node.get()) return false;
+  }
+  return node->inputs.size() == 0 && node->control_deps.size() == 0;
 }
 
 // public functions
@@ -261,7 +264,14 @@ void Symbol::Compose(const array_view<const Symbol*>& args,
   static auto& flist_inputs = Op::GetAttr<FListInputNames>("FListInputNames");
   static auto& fset_attrs = Op::GetAttr<FSetInputVarAttrOnCompose>("FSetInputVarAttrOnCompose");
 
-  CHECK(!outputs[0].node->is_variable()) << "Variable cannot be composed";
+  for (size_t i = 0; i < outputs.size(); ++i) {
+    if (outputs[i].node->is_variable()) {
+      CHECK_EQ(args.size(), 0) << "Variable composition only supports keyword arguments";
+      const auto it = kwargs.find(outputs[i].node->attrs.name);
+      if (it != kwargs.end()) outputs[i] = it->second->outputs[0];
+    }
+  }
+
   // parameter check.
   for (size_t i = 0; i < args.size(); ++i) {
     CHECK_EQ(args[i]->outputs.size(), 1U)
@@ -271,13 +281,13 @@ void Symbol::Compose(const array_view<const Symbol*>& args,
     CHECK_EQ(kv.second->outputs.size(), 1U)
         << "Keyword Argument " << kv.first << " is a tuple, single value is required";
   }
-  // assign new name
-  outputs[0].node->attrs.name = name;
 
   // Atomic functor composition.
   if (IsAtomic(outputs)) {
     Node* n = outputs[0].node.get();
     uint32_t n_req = n->num_inputs();
+    // assign new name
+    if (!name.empty()) n->attrs.name = name;
 
     if (n_req != kVarg) {
       n->inputs.resize(n_req);

From 5e26478acbe911460470f0c628be6eba509e87d8 Mon Sep 17 00:00:00 2001
From: Eric Junyuan Xie <piiswrong@users.noreply.github.com>
Date: Sun, 30 Jul 2017 16:45:59 -0700
Subject: [PATCH 444/949] fix (#134)

---
 nnvm/src/core/symbolic.cc | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index c86cfba94fca..5099e1f4920b 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -281,13 +281,13 @@ void Symbol::Compose(const array_view<const Symbol*>& args,
     CHECK_EQ(kv.second->outputs.size(), 1U)
         << "Keyword Argument " << kv.first << " is a tuple, single value is required";
   }
+  // assign new name
+  if (!name.empty()) outputs[0].node->attrs.name = name;
 
   // Atomic functor composition.
   if (IsAtomic(outputs)) {
     Node* n = outputs[0].node.get();
     uint32_t n_req = n->num_inputs();
-    // assign new name
-    if (!name.empty()) n->attrs.name = name;
 
     if (n_req != kVarg) {
       n->inputs.resize(n_req);

From 82abedb28d1e2acbd7909659c74b9e0d8362656b Mon Sep 17 00:00:00 2001
From: Terence Wu <2326428753@qq.com>
Date: Tue, 1 Aug 2017 00:06:43 +0800
Subject: [PATCH 445/949] Fix a spelling mistake (#135)

---
 nnvm/include/nnvm/graph.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/include/nnvm/graph.h b/nnvm/include/nnvm/graph.h
index af649b4f65cf..2c3ec009731a 100644
--- a/nnvm/include/nnvm/graph.h
+++ b/nnvm/include/nnvm/graph.h
@@ -71,7 +71,7 @@ class Graph {
 };
 
 /*!
- * \brief Auxililary data structure to index a graph.
+ * \brief Auxiliary data structure to index a graph.
  *  It maps Nodes in the graph to consecutive integers node_id.
  *  It also maps IndexedGraph::NodeEntry to consecutive integer entry_id.
  *  This allows storing properties of Node and NodeEntry into

From 6f7c86e4ca4c15c5911800466f7e8412af413392 Mon Sep 17 00:00:00 2001
From: Eric Junyuan Xie <piiswrong@users.noreply.github.com>
Date: Fri, 11 Aug 2017 13:29:45 -0700
Subject: [PATCH 446/949] fix (#138)

---
 nnvm/include/nnvm/graph.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/nnvm/include/nnvm/graph.h b/nnvm/include/nnvm/graph.h
index 2c3ec009731a..514705f65acd 100644
--- a/nnvm/include/nnvm/graph.h
+++ b/nnvm/include/nnvm/graph.h
@@ -285,6 +285,7 @@ inline void DFSVisit(const std::vector<NodeEntry>& heads,
       [fvisit](GNode n) { fvisit(*n); },  // FVisit
       [](GNode n)->Node* { return n->get(); },  // HashFunc
       [](GNode n)->uint32_t {  // InDegree
+        if (!(*n)) return 0;
         return (*n)->inputs.size() + (*n)->control_deps.size();
       },
       [](GNode n, uint32_t index)->GNode {  // GetInput

From 49439578b30dc29e313e4ecaaee12871a706fa82 Mon Sep 17 00:00:00 2001
From: Eric Junyuan Xie <piiswrong@users.noreply.github.com>
Date: Tue, 22 Aug 2017 11:40:28 -0700
Subject: [PATCH 447/949] add hash function for tuple (#141)

---
 nnvm/include/nnvm/base.h  |  1 +
 nnvm/include/nnvm/tuple.h | 30 ++++++++++++++++++++++++++++++
 2 files changed, 31 insertions(+)

diff --git a/nnvm/include/nnvm/base.h b/nnvm/include/nnvm/base.h
index 792c61daa938..2f5eb1969217 100644
--- a/nnvm/include/nnvm/base.h
+++ b/nnvm/include/nnvm/base.h
@@ -7,6 +7,7 @@
 #define NNVM_BASE_H_
 
 #include <dmlc/base.h>
+#include <dmlc/common.h>
 #include <dmlc/any.h>
 #include <dmlc/memory.h>
 #include <dmlc/logging.h>
diff --git a/nnvm/include/nnvm/tuple.h b/nnvm/include/nnvm/tuple.h
index d6978f7b55c5..f61385854b5c 100644
--- a/nnvm/include/nnvm/tuple.h
+++ b/nnvm/include/nnvm/tuple.h
@@ -581,4 +581,34 @@ inline bool Tuple<ValueType>::Load(TStream *strm) {
 
 }  // namespace nnvm
 
+namespace std {
+/*! \brief hash function for Tuple. */
+template<typename T>
+struct hash<nnvm::Tuple<T> > {
+  /*! \brief hash a Tuple into unsigned int */
+  size_t operator()(const nnvm::Tuple<T>& val) const {
+    std::hash<uint32_t> hash_uint;
+    size_t res = hash_uint(val.ndim());
+    for (uint32_t i = 0; i < val.ndim(); ++i) {
+      res = dmlc::HashCombine(res, val[i]);
+    }
+    return res;
+  }
+};
+
+/*! \brief hash function for TShape. */
+template<>
+struct hash<nnvm::TShape> {
+  /*! \brief hash a TShape into unsigned int */
+  size_t operator()(const nnvm::TShape& val) const {
+    std::hash<uint32_t> hash_uint;
+    size_t res = hash_uint(val.ndim());
+    for (uint32_t i = 0; i < val.ndim(); ++i) {
+      res = dmlc::HashCombine(res, val[i]);
+    }
+    return res;
+  }
+};
+}  // namespace std
+
 #endif  // NNVM_TUPLE_H_

From 0910061cb9282f90b7b99f5539c0e7e1397fbb51 Mon Sep 17 00:00:00 2001
From: formath <jinpengliu@163.com>
Date: Fri, 25 Aug 2017 00:22:06 +0800
Subject: [PATCH 448/949] rm unused variable (#143)

---
 nnvm/src/core/graph.cc | 1 -
 1 file changed, 1 deletion(-)

diff --git a/nnvm/src/core/graph.cc b/nnvm/src/core/graph.cc
index c500995eab70..474f3104f145 100644
--- a/nnvm/src/core/graph.cc
+++ b/nnvm/src/core/graph.cc
@@ -59,7 +59,6 @@ IndexedGraph::IndexedGraph(const Graph &g) {
   }
 
   static auto& fmutate_inputs = Op::GetAttr<FMutateInputs>("FMutateInputs");
-  std::unordered_set<uint32_t> mutable_inputs;
   // setup array view
   // input_entries_ and control_rptr must not change after this step.
   const NodeEntry* iptr = dmlc::BeginPtr(input_entries_);

From bc428197238dd510965c5dee2f83215d3064c584 Mon Sep 17 00:00:00 2001
From: ziheng <ziheng@apache.org>
Date: Fri, 25 Aug 2017 09:00:48 -0700
Subject: [PATCH 449/949] [LIBINFO] Enable load .dylib on darwin (#142)

* [LIBINFO] Enable load .dylib on darwin

* Fix makefile
---
 nnvm/Makefile               | 17 +++++++++--------
 nnvm/python/nnvm/libinfo.py | 14 ++++++++++----
 2 files changed, 19 insertions(+), 12 deletions(-)

diff --git a/nnvm/Makefile b/nnvm/Makefile
index d2f26280909b..be42af12c46d 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -39,22 +39,23 @@ include $(NNVM_PLUGINS)
 # specify tensor path
 .PHONY: clean all test lint doc cython cython3 cyclean
 
-all: lib/libnnvm.a lib/libnnvm_example.so
-
-SRC = $(wildcard src/*.cc src/*/*.cc)
-ALL_OBJ = $(patsubst %.cc, build/%.o, $(SRC))
-ALL_DEP = $(ALL_OBJ) $(PLUGIN_OBJ)
-
 UNAME_S := $(shell uname -s)
 
 ifeq ($(UNAME_S), Darwin)
+	SHARED_LIBRARY_SUFFIX := dylib
 	WHOLE_ARCH= -all_load
 	NO_WHOLE_ARCH= -noall_load
 else
+	SHARED_LIBRARY_SUFFIX := so
 	WHOLE_ARCH= --whole-archive
 	NO_WHOLE_ARCH= --no-whole-archive
 endif
 
+all: lib/libnnvm.a lib/libnnvm_example.$(SHARED_LIBRARY_SUFFIX)
+
+SRC = $(wildcard src/*.cc src/*/*.cc)
+ALL_OBJ = $(patsubst %.cc, build/%.o, $(SRC))
+ALL_DEP = $(ALL_OBJ) $(PLUGIN_OBJ)
 
 include tests/cpp/unittest.mk
 
@@ -74,7 +75,7 @@ lib/libnnvm.a: $(ALL_DEP)
 	@mkdir -p $(@D)
 	ar crv $@ $(filter %.o, $?)
 
-lib/libnnvm_example.so: example/src/operator.cc lib/libnnvm.a
+lib/libnnvm_example.$(SHARED_LIBRARY_SUFFIX): example/src/operator.cc lib/libnnvm.a
 	@mkdir -p $(@D)
 	$(CXX) $(CFLAGS) -shared -o $@ $(filter %.cc, $^) $(LDFLAGS) -Wl,${WHOLE_ARCH} lib/libnnvm.a -Wl,${NO_WHOLE_ARCH}
 
@@ -85,7 +86,7 @@ cython3:
 	cd python; python3 setup.py build_ext --inplace
 
 cyclean:
-	rm -rf python/nnvm/*/*.so python/nnvm/*/*.cpp
+	rm -rf python/nnvm/*/*.so python/nnvm/*/*.dylib python/nnvm/*/*.cpp
 
 lint:
 	python2 dmlc-core/scripts/lint.py nnvm cpp include src
diff --git a/nnvm/python/nnvm/libinfo.py b/nnvm/python/nnvm/libinfo.py
index 241960f129f1..237c07fb4d41 100644
--- a/nnvm/python/nnvm/libinfo.py
+++ b/nnvm/python/nnvm/libinfo.py
@@ -31,7 +31,13 @@ def find_lib_path():
     api_path = os.path.join(base_path, '../../lib/')
     cmake_build_path = os.path.join(base_path, '../../build/Release/')
     dll_path = [base_path, api_path, cmake_build_path]
-    if os.name == 'nt':
+
+    if sys.platform.startswith('linux') and os.environ.get('LD_LIBRARY_PATH', None):
+        dll_path.extend([p.strip() for p in os.environ['LD_LIBRARY_PATH'].split(":")])
+    elif sys.platform.startswith('darwin') and os.environ.get('DYLD_LIBRARY_PATH', None):
+        dll_path.extend([p.strip() for p in os.environ['DYLD_LIBRARY_PATH'].split(":")])
+
+    if sys.platform.startswith('win32'):
         vs_configuration = 'Release'
         if platform.architecture()[0] == '64bit':
             dll_path.append(os.path.join(curr_path, '../../build', vs_configuration))
@@ -39,12 +45,12 @@ def find_lib_path():
         else:
             dll_path.append(os.path.join(curr_path, '../../build', vs_configuration))
             dll_path.append(os.path.join(curr_path, '../../windows', vs_configuration))
-    elif os.name == "posix" and os.environ.get('LD_LIBRARY_PATH', None):
-        dll_path.extend([p.strip() for p in os.environ['LD_LIBRARY_PATH'].split(":")])
-    if os.name == 'nt':
         dll_path = [os.path.join(p, '%s.dll' % lib_name) for p in dll_path]
+    elif sys.platform.startswith('darwin'):
+        dll_path = [os.path.join(p, '%s.dylib' % lib_name) for p in dll_path]
     else:
         dll_path = [os.path.join(p, '%s.so' % lib_name) for p in dll_path]
+
     lib_path = [p for p in dll_path if os.path.exists(p) and os.path.isfile(p)]
     if len(lib_path) == 0:
         raise RuntimeError('Cannot find the files.\n' +

From c304efef6d564e11102bf51776e77aee24c16381 Mon Sep 17 00:00:00 2001
From: Terence Wu <2326428753@qq.com>
Date: Wed, 30 Aug 2017 23:47:26 +0800
Subject: [PATCH 450/949] Fix a spelling mistake (#136)

---
 nnvm/include/nnvm/symbolic.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/include/nnvm/symbolic.h b/nnvm/include/nnvm/symbolic.h
index 22256ae29097..02b1dd9475f2 100644
--- a/nnvm/include/nnvm/symbolic.h
+++ b/nnvm/include/nnvm/symbolic.h
@@ -124,7 +124,7 @@ class Symbol {
                       const std::unordered_map<std::string, const Symbol*>& kwargs,
                       const std::string& name) const;
   /*!
-   * \brief Add control flow depenencies to the operators in symbols.
+   * \brief Add control flow dependencies to the operators in symbols.
    *
    *  For grouped symbol, an error will be raised. This mutates current symbolic Node.
    *

From a2875d68e93a58bb4d7008ac6fb9b4419ec415d8 Mon Sep 17 00:00:00 2001
From: Eric Junyuan Xie <piiswrong@users.noreply.github.com>
Date: Sat, 2 Sep 2017 15:50:47 -0700
Subject: [PATCH 451/949] add additional field into node for autograd (#145)

* add additional field into node for autograd

* check inputs reachable

* fix
---
 nnvm/include/nnvm/node.h  | 2 ++
 nnvm/src/pass/gradient.cc | 7 +++++++
 2 files changed, 9 insertions(+)

diff --git a/nnvm/include/nnvm/node.h b/nnvm/include/nnvm/node.h
index ace937f68444..54f31e745c03 100644
--- a/nnvm/include/nnvm/node.h
+++ b/nnvm/include/nnvm/node.h
@@ -107,6 +107,8 @@ class Node {
    *  Gives operation must be performed before this operation.
    */
   std::vector<NodePtr> control_deps;
+  /*! \brief additional fields for this node */
+  any info;
   /*! \brief destructor of node */
   ~Node();
   /*! \return operator in this node */
diff --git a/nnvm/src/pass/gradient.cc b/nnvm/src/pass/gradient.cc
index 51f2b86c9121..561ec01323d6 100644
--- a/nnvm/src/pass/gradient.cc
+++ b/nnvm/src/pass/gradient.cc
@@ -112,6 +112,13 @@ Graph Gradient(Graph src) {
     output_grads[ys[i].node.get()][ys[i].index].grads = { ograd };
   }
 
+  // Check that all xs are reachable from ys
+  for (size_t i = 0; i < xs.size(); ++i) {
+    CHECK(output_grads.find(xs[i].node.get()) != output_grads.end())
+        << "Cannot differentiate with respect to the " << i+1 << "-th variable "
+        << "because it is unreachable from the outputs.";
+  }
+
   // construct mirror reduece memory strategy if needed
   std::unordered_map<Node*, NodePtr> mirror_map;
   if (mirror_fun != nullptr) {

From 5d3f488b5d0b757fe73a02eee540197b891d93e1 Mon Sep 17 00:00:00 2001
From: Eric Junyuan Xie <piiswrong@users.noreply.github.com>
Date: Wed, 13 Sep 2017 17:47:32 -0700
Subject: [PATCH 452/949] add range to plan memory (#147)

---
 nnvm/src/pass/plan_memory.cc | 50 ++++++++++++++++++++++--------------
 1 file changed, 31 insertions(+), 19 deletions(-)

diff --git a/nnvm/src/pass/plan_memory.cc b/nnvm/src/pass/plan_memory.cc
index 1f543f8a82b9..f652352f64e8 100644
--- a/nnvm/src/pass/plan_memory.cc
+++ b/nnvm/src/pass/plan_memory.cc
@@ -143,7 +143,9 @@ class GraphAllocator {
 /*
  * Internal method to perform the memory allocation for a graph
  * */
-size_t AllocMemory(const Graph& ret, const IndexedGraph& idx, StorageVector* storage_ptr,
+size_t AllocMemory(const Graph& ret, const IndexedGraph& idx,
+                   const std::pair<uint32_t, uint32_t>& node_range,
+                   StorageVector* storage_ptr,
                    std::vector<int>* storage_inplace_index_ptr,
                    const std::vector<uint32_t>& entry_ref_count,
                    GraphAllocator* allocator) {
@@ -165,7 +167,7 @@ size_t AllocMemory(const Graph& ret, const IndexedGraph& idx, StorageVector* sto
   size_t num_not_allocated = 0;
   std::vector<GraphAllocator::StorageID> storage_ref_count(idx.num_node_entries(), 0);
 
-  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+  for (uint32_t nid = node_range.first; nid < node_range.second; ++nid) {
     const auto& inode = idx[nid];
     if (inode.source->is_variable()) continue;
     // check inplace option
@@ -272,26 +274,35 @@ Graph PlanMemory(Graph ret) {
   // setup ref counter
   const IndexedGraph& idx = ret.indexed_graph();
   static auto& fignore_inputs = Op::GetAttr<FIgnoreInputs>("FIgnoreInputs");
+  std::pair<uint32_t, uint32_t> node_range = {0, idx.num_nodes()};
+  if (ret.attrs.count("node_range")) {
+    node_range = ret.MoveCopyAttr<std::pair<uint32_t, uint32_t> >("node_range");
+  }
   // reference counter of each node
-  std::vector<uint32_t> ref_count(idx.num_node_entries(), 0);
+  std::vector<uint32_t> ref_count;
   // step 1: initialize reference count
-  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
-    const auto& inode = idx[nid];
-    if (inode.source->is_variable()) continue;
-    for (const auto& e : inode.inputs) {
-      ++ref_count[idx.entry_id(e)];
-    }
-    // no dataflow dependency is needed for those are ignored.
-    // revoke the dependency counter.
-    if (fignore_inputs.count(inode.source->op()) != 0) {
-      auto ignore_inputs = fignore_inputs[inode.source->op()](inode.source->attrs);
-      for (uint32_t i : ignore_inputs) {
-        --ref_count[idx.entry_id(inode.inputs[i])];
+  if (ret.attrs.count("ref_count") != 0) {
+    ref_count = ret.MoveCopyAttr<std::vector<uint32_t> >("ref_count");
+  } else {
+    ref_count.resize(idx.num_node_entries(), 0);
+    for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+      const auto& inode = idx[nid];
+      if (inode.source->is_variable()) continue;
+      for (const auto& e : inode.inputs) {
+        ++ref_count[idx.entry_id(e)];
+      }
+      // no dataflow dependency is needed for those are ignored.
+      // revoke the dependency counter.
+      if (fignore_inputs.count(inode.source->op()) != 0) {
+        auto ignore_inputs = fignore_inputs[inode.source->op()](inode.source->attrs);
+        for (uint32_t i : ignore_inputs) {
+          --ref_count[idx.entry_id(inode.inputs[i])];
+        }
       }
     }
-  }
-  for (const auto& e : idx.outputs()) {
-    ++ref_count[idx.entry_id(e)];
+    for (const auto& e : idx.outputs()) {
+      ++ref_count[idx.entry_id(e)];
+    }
   }
   // step 2: allocate memory.
   StorageVector storage;
@@ -316,7 +327,8 @@ Graph PlanMemory(Graph ret) {
 
     // number of entries that are not statically allocated.
     size_t storage_num_not_allocated =
-      AllocMemory(ret, idx, &storage_vec, &storage_inplace_index, ref_count, &allocator);
+      AllocMemory(ret, idx, node_range, &storage_vec, &storage_inplace_index,
+                  ref_count, &allocator);
     size_t storage_allocated_bytes = allocator.TotalAllocBytes();
     // Choose the plan which leads to minimal memory usage
     if (min_allocated_bytes > storage_allocated_bytes) {

From 247a024d32f08e71bc0c3b1178989678d7ec0179 Mon Sep 17 00:00:00 2001
From: "Joshua Z. Zhang" <cheungchih@gmail.com>
Date: Wed, 20 Sep 2017 19:53:37 -0700
Subject: [PATCH 453/949] fix symbol.attr('op_name') (#149)

---
 nnvm/src/core/symbolic.cc | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index 5099e1f4920b..2b6500502f74 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -484,7 +484,11 @@ bool Symbol::GetAttr(const std::string& key, std::string* out) const {
     *out = node->attrs.name;
     return true;
   } else if (key == "op_name") {
-    *out = node->attrs.op->name;
+    if (node->attrs.op != nullptr) {
+      *out = node->attrs.op->name;
+    } else {
+      *out = "null";  // use null in consistant with json
+    }
     return true;
   }
   auto it = node->attrs.dict.find(key);

From 8a77ca28076b2f572c98f84d3fe232b02aad6061 Mon Sep 17 00:00:00 2001
From: reminisce <wujun.nju@gmail.com>
Date: Wed, 20 Sep 2017 19:53:46 -0700
Subject: [PATCH 454/949] Fix infer shape bug (#148)

---
 nnvm/src/pass/infer_shape_type.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index a6903ba8b2d5..0332532717e6 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -108,7 +108,7 @@ Graph InferAttr(Graph &&ret,
           uint32_t eid = idx.entry_id(nid, igrad[i].index);
           if (fis_none(rshape[eid])) {
             rshape[eid] = rshape[idx.entry_id(fnode.inputs[i])];
-          } else {
+          } else if (!fis_none(rshape[idx.entry_id(fnode.inputs[i])])) {
             CHECK_EQ(rshape[eid], rshape[idx.entry_id(fnode.inputs[i])])
                 << "Backward shape inconsistent with the forward shape";
           }

From 8ffcfc6e4b84ffc9438cc3dc50870e5f47ebb833 Mon Sep 17 00:00:00 2001
From: Jiasen Wu <wujiasen@yahoo.com>
Date: Sun, 24 Sep 2017 19:04:08 +0200
Subject: [PATCH 455/949] declare a type name for each tuple type (#151)

* declare a type name for each tuple type

* generic way to declare type names for each tuple type

* fix lint error

* update submodule dmlc-core
---
 nnvm/include/nnvm/tuple.h | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/nnvm/include/nnvm/tuple.h b/nnvm/include/nnvm/tuple.h
index f61385854b5c..b2d049535de9 100644
--- a/nnvm/include/nnvm/tuple.h
+++ b/nnvm/include/nnvm/tuple.h
@@ -11,6 +11,7 @@
 #include <algorithm>
 #include <utility>
 #include <iostream>
+#include <string>
 #include "./base.h"
 
 namespace nnvm {
@@ -611,4 +612,13 @@ struct hash<nnvm::TShape> {
 };
 }  // namespace std
 
+namespace dmlc {
+template<typename T>
+struct type_name_helper<nnvm::Tuple<T> > {
+  static inline std::string value() {
+    return "tuple of <" + type_name<T>() + ">";
+  }
+};
+}
+
 #endif  // NNVM_TUPLE_H_

From 596c1e85580fbbd7c5889fa08bb0069b551f9c6e Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 28 Sep 2017 18:15:27 -0700
Subject: [PATCH 456/949] [JSON] attr->attrs in node attribute, keep backward
 read compatible (#152)

---
 nnvm/src/pass/saveload_json.cc | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/nnvm/src/pass/saveload_json.cc b/nnvm/src/pass/saveload_json.cc
index 6c2e93be1800..3170d245ca7a 100644
--- a/nnvm/src/pass/saveload_json.cc
+++ b/nnvm/src/pass/saveload_json.cc
@@ -79,7 +79,7 @@ struct JSONNode {
       // write attributes in order;
       std::map<std::string, std::string> dict(
           node->attrs.dict.begin(), node->attrs.dict.end());
-      writer->WriteObjectKeyValue("attr", dict);
+      writer->WriteObjectKeyValue("attrs", dict);
     }
     writer->WriteObjectKeyValue("inputs", inputs);
     if (control_deps.size() != 0) {
@@ -96,6 +96,7 @@ struct JSONNode {
     helper.DeclareField("op", &op_type_str);
     helper.DeclareField("name", &(node->attrs.name));
     helper.DeclareField("inputs", &inputs);
+    helper.DeclareOptionalField("attrs", &(node->attrs.dict));
     helper.DeclareOptionalField("attr", &(node->attrs.dict));
     helper.DeclareOptionalField("control_deps", &control_deps);
     // backward compatible code with mxnet graph.
@@ -257,6 +258,7 @@ NNVM_REGISTER_PASS(SaveJSON)
 
 DMLC_JSON_ENABLE_ANY(std::string, str);
 DMLC_JSON_ENABLE_ANY(std::vector<int>, list_int);
+DMLC_JSON_ENABLE_ANY(std::vector<std::string>, list_str);
 
 }  // namespace
 }  // namespace pass

From 31c92b422015f95a0a310d09408ff2124dab2bba Mon Sep 17 00:00:00 2001
From: Chris Olivier <cjolivier01@gmail.com>
Date: Thu, 28 Sep 2017 18:15:49 -0700
Subject: [PATCH 457/949] Address execution hot spots (#154)

* Address execution hot spots

* extend the reserve logic a bit

* FIx more hot spots

* Performance: use resize(0) instead of clear() in order to avoid a free/malloc
---
 nnvm/src/c_api/c_api_symbolic.cc | 19 +++++++++++++------
 nnvm/src/core/symbolic.cc        |  4 ++++
 2 files changed, 17 insertions(+), 6 deletions(-)

diff --git a/nnvm/src/c_api/c_api_symbolic.cc b/nnvm/src/c_api/c_api_symbolic.cc
index fae58636deae..cc3e934c533c 100644
--- a/nnvm/src/c_api/c_api_symbolic.cc
+++ b/nnvm/src/c_api/c_api_symbolic.cc
@@ -15,7 +15,8 @@ int NNListAllOpNames(nn_uint *out_size,
   API_BEGIN();
   NNAPIThreadLocalEntry *ret = NNAPIThreadLocalStore::Get();
   ret->ret_vec_str = dmlc::Registry<Op>::ListAllNames();
-  ret->ret_vec_charp.clear();
+  ret->ret_vec_charp.resize(0);
+  ret->ret_vec_charp.reserve(ret->ret_vec_str.size());
   for (size_t i = 0; i < ret->ret_vec_str.size(); ++i) {
     ret->ret_vec_charp.push_back(ret->ret_vec_str[i].c_str());
   }
@@ -64,7 +65,8 @@ int NNGetOpInfo(OpHandle handle,
   *description = op->description.c_str();
   *num_doc_args = static_cast<nn_uint>(op->arguments.size());
   if (return_type) *return_type = nullptr;
-  ret->ret_vec_charp.clear();
+  ret->ret_vec_charp.resize(0);
+  ret->ret_vec_charp.reserve(op->arguments.size() * 3);
   for (size_t i = 0; i < op->arguments.size(); ++i) {
     ret->ret_vec_charp.push_back(op->arguments[i].name.c_str());
   }
@@ -207,13 +209,15 @@ int NNSymbolListAttrs(SymbolHandle symbol,
       s->ListAttrs(static_cast<Symbol::ListAttrOption>(option));  // NOLINT(*)
 
   std::vector<std::string>& attr_list = ret->ret_vec_str;
-  attr_list.clear();
+  attr_list.resize(0);
+  attr_list.reserve(attr.size());
   for (const auto& kv : attr) {
     attr_list.push_back(kv.first);
     attr_list.push_back(kv.second);
   }
   *out_size = attr.size();
   ret->ret_vec_charp.clear();
+  ret->ret_vec_charp.reserve(ret->ret_vec_str.size());
   for (size_t i = 0; i < ret->ret_vec_str.size(); ++i) {
     ret->ret_vec_charp.push_back(ret->ret_vec_str[i].c_str());
   }
@@ -229,7 +233,8 @@ int NNSymbolListInputVariables(SymbolHandle symbol,
   NNAPIThreadLocalEntry *ret = NNAPIThreadLocalStore::Get();
   API_BEGIN();
   std::vector<NodePtr> vs = s->ListInputs(Symbol::ListInputOption(option));
-  ret->ret_handles.clear();
+  ret->ret_handles.resize(0);
+  ret->ret_handles.reserve(vs.size());
   for (size_t i = 0; i < vs.size(); ++i) {
     nnvm::Symbol* rs = new nnvm::Symbol();
     rs->outputs.push_back(NodeEntry{vs[i], 0, 0});
@@ -249,7 +254,8 @@ int NNSymbolListInputNames(SymbolHandle symbol,
   API_BEGIN();
   ret->ret_vec_str =
       s->ListInputNames(Symbol::ListInputOption(option));
-  ret->ret_vec_charp.clear();
+  ret->ret_vec_charp.resize(0);
+  ret->ret_vec_charp.reserve(ret->ret_vec_str.size());
   for (size_t i = 0; i < ret->ret_vec_str.size(); ++i) {
     ret->ret_vec_charp.push_back(ret->ret_vec_str[i].c_str());
   }
@@ -265,7 +271,8 @@ int NNSymbolListOutputNames(SymbolHandle symbol,
   NNAPIThreadLocalEntry *ret = NNAPIThreadLocalStore::Get();
   API_BEGIN();
   ret->ret_vec_str = s->ListOutputNames();
-  ret->ret_vec_charp.clear();
+  ret->ret_vec_charp.resize(0);
+  ret->ret_vec_charp.reserve(ret->ret_vec_str.size());
   for (size_t i = 0; i < ret->ret_vec_str.size(); ++i) {
     ret->ret_vec_charp.push_back(ret->ret_vec_str[i].c_str());
   }
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index 2b6500502f74..170ca1c7c072 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -189,6 +189,7 @@ Symbol Symbol::operator[] (size_t index) const {
 std::vector<NodePtr> Symbol::ListInputs(ListInputOption option) const {
   std::vector<NodePtr> ret;
   if (option == kAll) {
+    ret.reserve(this->outputs.size());
     DFSVisit(this->outputs, [&ret](const NodePtr &node) {
         if (node->is_variable()) {
           ret.push_back(node);
@@ -197,6 +198,7 @@ std::vector<NodePtr> Symbol::ListInputs(ListInputOption option) const {
   } else {
     std::unordered_set<Node*> mutable_set;
     std::vector<NodePtr> vlist;
+    vlist.reserve(this->outputs.size());
     static auto& fmutate_inputs = Op::GetAttr<FMutateInputs>("FMutateInputs");
     DFSVisit(this->outputs, [&ret, &mutable_set, &vlist](const NodePtr &node) {
         if (node->is_variable()) {
@@ -207,6 +209,7 @@ std::vector<NodePtr> Symbol::ListInputs(ListInputOption option) const {
           }
         }
       });
+    ret.reserve(vlist.size());
     for (const NodePtr& node : vlist) {
       if ((option == kReadOnlyArgs && mutable_set.count(node.get()) == 0) ||
           (option == kAuxiliaryStates && mutable_set.count(node.get()) != 0)) {
@@ -230,6 +233,7 @@ std::vector<std::string> Symbol::ListOutputNames() const {
   static auto& flist_ouputs = Op::GetAttr<FListOutputNames>("FListOutputNames");
 
   std::vector<std::string> ret;
+  ret.reserve(outputs.size());
   for (auto &head : outputs) {
     if (head.node->is_variable()) {
       ret.push_back(head.node->attrs.name);

From cb82d87e03c8f03c3385967ca5d37a226a5c47ab Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Thu, 7 Sep 2017 20:19:33 -0700
Subject: [PATCH 458/949] [OP] Initial checkin of nnvm core op folders

---
 nnvm/docs/operators.md          | 2 ++
 nnvm/include/nnvm/top/README    | 1 +
 nnvm/include/nnvm/top/README.md | 0
 nnvm/src/top/README.md          | 2 ++
 plugin/nnvm-fusion              | 1 -
 5 files changed, 5 insertions(+), 1 deletion(-)
 create mode 100644 nnvm/docs/operators.md
 create mode 100644 nnvm/include/nnvm/top/README
 create mode 100644 nnvm/include/nnvm/top/README.md
 create mode 100644 nnvm/src/top/README.md
 delete mode 160000 plugin/nnvm-fusion

diff --git a/nnvm/docs/operators.md b/nnvm/docs/operators.md
new file mode 100644
index 000000000000..067003612fba
--- /dev/null
+++ b/nnvm/docs/operators.md
@@ -0,0 +1,2 @@
+NNVM Core Operator Specification
+================================
diff --git a/nnvm/include/nnvm/top/README b/nnvm/include/nnvm/top/README
new file mode 100644
index 000000000000..696c05a4d532
--- /dev/null
+++ b/nnvm/include/nnvm/top/README
@@ -0,0 +1 @@
+NNVM Core Operator Specs
diff --git a/nnvm/include/nnvm/top/README.md b/nnvm/include/nnvm/top/README.md
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/nnvm/src/top/README.md b/nnvm/src/top/README.md
new file mode 100644
index 000000000000..4da78195e267
--- /dev/null
+++ b/nnvm/src/top/README.md
@@ -0,0 +1,2 @@
+Core Operator List
+==================
diff --git a/plugin/nnvm-fusion b/plugin/nnvm-fusion
deleted file mode 160000
index 86853a73e93f..000000000000
--- a/plugin/nnvm-fusion
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 86853a73e93fdbcdaec6fd3eda39e8f11d554c92

From c064a9e964c54408f0897622c4a77ef6e70718f5 Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Fri, 8 Sep 2017 12:36:16 -0700
Subject: [PATCH 459/949] [TOP] Init dense

---
 nnvm/Makefile                     |  17 ++-
 nnvm/docs/operators.md            |  24 ++++
 nnvm/include/nnvm/base.h          |   5 +
 nnvm/include/nnvm/op.h            |  17 +++
 nnvm/include/nnvm/top/README.md   |   0
 nnvm/include/nnvm/top/nn.h        |  33 ++++++
 nnvm/include/nnvm/top/tensor.h    |  15 +++
 nnvm/src/top/elemwise_op_common.h |  88 ++++++++++++++
 nnvm/src/top/nn.cc                |  85 ++++++++++++++
 nnvm/src/top/op_common.h          | 184 ++++++++++++++++++++++++++++++
 nnvm/src/top/tensor.cc            |  13 +++
 nnvm/tests/python/test_symbol.py  |   9 ++
 nnvm/tests/python/test_top_nn.py  |  10 ++
 13 files changed, 490 insertions(+), 10 deletions(-)
 delete mode 100644 nnvm/include/nnvm/top/README.md
 create mode 100644 nnvm/include/nnvm/top/nn.h
 create mode 100644 nnvm/include/nnvm/top/tensor.h
 create mode 100644 nnvm/src/top/elemwise_op_common.h
 create mode 100644 nnvm/src/top/nn.cc
 create mode 100644 nnvm/src/top/op_common.h
 create mode 100644 nnvm/src/top/tensor.cc
 create mode 100644 nnvm/tests/python/test_top_nn.py

diff --git a/nnvm/Makefile b/nnvm/Makefile
index be42af12c46d..7ddeb97c459d 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -53,9 +53,11 @@ endif
 
 all: lib/libnnvm.a lib/libnnvm_example.$(SHARED_LIBRARY_SUFFIX)
 
-SRC = $(wildcard src/*.cc src/*/*.cc)
+SRC = $(wildcard src/*.cc src/c_api/*.cc src/core/*.cc src/pass/*.cc)
+SRC_TOP = $(wildcard src/top/*.cc)
 ALL_OBJ = $(patsubst %.cc, build/%.o, $(SRC))
-ALL_DEP = $(ALL_OBJ) $(PLUGIN_OBJ)
+TOP_OBJ = $(patsubst %.cc, build/%.o, $(SRC_TOP))
+ALL_DEP = $(ALL_OBJ)
 
 include tests/cpp/unittest.mk
 
@@ -66,18 +68,13 @@ build/src/%.o: src/%.cc
 	$(CXX) $(CFLAGS) -MM -MT build/src/$*.o $< >build/src/$*.d
 	$(CXX) -c $(CFLAGS) -c $< -o $@
 
-build/plugin/%.o: plugin/%.cc
-	@mkdir -p $(@D)
-	$(CXX) $(CFLAGS) -MM -MT build/plugin/$*.o $< >build/plugin/$*.d
-	$(CXX) -c $(CFLAGS) -c $< -o $@
-
 lib/libnnvm.a: $(ALL_DEP)
 	@mkdir -p $(@D)
 	ar crv $@ $(filter %.o, $?)
 
-lib/libnnvm_example.$(SHARED_LIBRARY_SUFFIX): example/src/operator.cc lib/libnnvm.a
+lib/libnnvm_example.$(SHARED_LIBRARY_SUFFIX): lib/libnnvm.a ${TOP_OBJ}
 	@mkdir -p $(@D)
-	$(CXX) $(CFLAGS) -shared -o $@ $(filter %.cc, $^) $(LDFLAGS) -Wl,${WHOLE_ARCH} lib/libnnvm.a -Wl,${NO_WHOLE_ARCH}
+	$(CXX) $(CFLAGS) -shared -o $@ $(filter %.o, $^) $(LDFLAGS) -Wl,${WHOLE_ARCH} lib/libnnvm.a -Wl,${NO_WHOLE_ARCH}
 
 cython:
 	cd python; python setup.py build_ext --inplace
@@ -89,7 +86,7 @@ cyclean:
 	rm -rf python/nnvm/*/*.so python/nnvm/*/*.dylib python/nnvm/*/*.cpp
 
 lint:
-	python2 dmlc-core/scripts/lint.py nnvm cpp include src
+	python dmlc-core/scripts/lint.py nnvm cpp include src
 
 doc:
 	doxygen docs/Doxyfile
diff --git a/nnvm/docs/operators.md b/nnvm/docs/operators.md
index 067003612fba..4efa4086b399 100644
--- a/nnvm/docs/operators.md
+++ b/nnvm/docs/operators.md
@@ -1,2 +1,26 @@
 NNVM Core Operator Specification
 ================================
+
+Each operator attributes are stored in json format.
+tuples are stored as json array.
+
+## Tier 1: Basic Operators
+
+***Enables fully connected nets***
+
+- **dense**
+  - attributes
+     - units: int  Number of hidden units in the data.
+     - use_bias: bool Whether use bias
+  - inputs
+     - data, 2D Tensor
+     - weight, 2D Tensor
+     - bias, optional, 1D Tensor
+  - outputs
+     - output, 2D Tensor
+
+- **relu**
+  - inputs
+     - data, nD Tensor
+  - outputs
+     - output, nD Tensor
diff --git a/nnvm/include/nnvm/base.h b/nnvm/include/nnvm/base.h
index 2f5eb1969217..310fc5b6ab79 100644
--- a/nnvm/include/nnvm/base.h
+++ b/nnvm/include/nnvm/base.h
@@ -27,4 +27,9 @@ using dmlc::get;
 
 }  // namespace nnvm
 
+// describe op registration point
+#define NNVM_STRINGIZE_DETAIL(x) #x
+#define NNVM_STRINGIZE(x) NNVM_STRINGIZE_DETAIL(x)
+#define NNVM_DESCRIBE(...) describe(__VA_ARGS__ "\n\nFrom:" __FILE__ ":" NNVM_STRINGIZE(__LINE__))
+#define NNVM_ADD_FILELINE "\n\nDefined in " __FILE__ ":L" NNVM_STRINGIZE(__LINE__)
 #endif  // NNVM_BASE_H_
diff --git a/nnvm/include/nnvm/op.h b/nnvm/include/nnvm/op.h
index 300d73ab0d93..3d82c44ab338 100644
--- a/nnvm/include/nnvm/op.h
+++ b/nnvm/include/nnvm/op.h
@@ -108,6 +108,12 @@ class Op {
    * \sa get_num_outputs
    */
   uint32_t num_outputs = 1;
+  /*!
+   * \brief support level of the operator,
+   *  The lower the more priority it contains.
+   *  This is in analogies to BLAS levels.
+   */
+  uint32_t support_level = 10;
   /*!
    * \brief get number of outputs given information about the node.
    * \param attrs The attribute of the node
@@ -183,6 +189,12 @@ class Op {
    * \return reference to self.
    */
   inline Op& set_num_inputs(uint32_t n);  // NOLINT(*)
+  /*!
+   * \brief Set the support level of op.
+   * \param level The support level.
+   * \return reference to self.
+   */
+  inline Op& set_support_level(uint32_t level);  // NOLINT(*)
   /*!
    * \brief Set the get_num_outputs function.
    * \param fn The function to be set.
@@ -479,6 +491,11 @@ inline Op& Op::set_num_inputs(uint32_t n) {  // NOLINT(*)
   return *this;
 }
 
+inline Op& Op::set_support_level(uint32_t n) {  // NOLINT(*)
+  this->support_level = n;
+  return *this;
+}
+
 inline Op& Op::set_num_inputs(std::function<uint32_t (const NodeAttrs& attr)> fn) {  // NOLINT(*)
   this->get_num_inputs = fn;
   return *this;
diff --git a/nnvm/include/nnvm/top/README.md b/nnvm/include/nnvm/top/README.md
deleted file mode 100644
index e69de29bb2d1..000000000000
diff --git a/nnvm/include/nnvm/top/nn.h b/nnvm/include/nnvm/top/nn.h
new file mode 100644
index 000000000000..dcb573942488
--- /dev/null
+++ b/nnvm/include/nnvm/top/nn.h
@@ -0,0 +1,33 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file nn.h
+ * \brief Auxiliary param for tensor primitive.
+ */
+#ifndef NNVM_TOP_NN_H_
+#define NNVM_TOP_NN_H_
+
+#include <dmlc/base.h>
+#include <dmlc/parameter.h>
+
+namespace nnvm {
+namespace top {
+
+struct DenseParam : public dmlc::Parameter<DenseParam> {
+  int units;
+  bool use_bias;
+  DMLC_DECLARE_PARAMETER(DenseParam) {
+    DMLC_DECLARE_FIELD(units).set_lower_bound(1)
+    .describe("Number of hidden units of the dense transformation.");
+    DMLC_DECLARE_FIELD(use_bias).set_default(true)
+    .describe("Whether to use bias parameter");
+  }
+  // constants
+  static const constexpr int kData = 0;
+  static const constexpr int kWeight = 1;
+  static const constexpr int kBias = 2;
+};
+
+}  // namespace top
+}  // namespace nnvm
+
+#endif  // NNVM_TOP_NN_H_
diff --git a/nnvm/include/nnvm/top/tensor.h b/nnvm/include/nnvm/top/tensor.h
new file mode 100644
index 000000000000..4d1886871899
--- /dev/null
+++ b/nnvm/include/nnvm/top/tensor.h
@@ -0,0 +1,15 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file tensor.h
+ * \brief Auxiliary param for tensor primitive.
+ */
+#ifndef NNVM_TOP_TENSOR_H_
+#define NNVM_TOP_TENSOR_H_
+
+namespace nnvm {
+namespace top {
+
+}  // namespace top
+}  // namespace nnvm
+
+#endif  // NNVM_TOP_TENSOR_H_
diff --git a/nnvm/src/top/elemwise_op_common.h b/nnvm/src/top/elemwise_op_common.h
new file mode 100644
index 000000000000..28b091333bec
--- /dev/null
+++ b/nnvm/src/top/elemwise_op_common.h
@@ -0,0 +1,88 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file elemwise_op_common.h
+ * \brief Common operator utilities
+ */
+#ifndef NNVM_TOP_ELEMWISE_OP_COMMON_H_
+#define NNVM_TOP_ELEMWISE_OP_COMMON_H_
+
+#include <string>
+#include <vector>
+#include "./op_common.h"
+
+namespace nnvm {
+namespace top {
+
+template<typename AttrType, bool (*is_none)(const AttrType&),
+         bool (*assign)(AttrType*, const AttrType&), bool reverse_infer,
+         std::string (*attr_string)(const AttrType&),
+         int n_in = -1, int n_out = -1>
+inline bool ElemwiseAttr(const nnvm::NodeAttrs& attrs,
+                         std::vector<AttrType> *in_attrs,
+                         std::vector<AttrType> *out_attrs,
+                         const AttrType& none) {
+  AttrType dattr = none;
+  size_t in_size = in_attrs->size();
+  size_t out_size = out_attrs->size();
+  if (n_in != -1)
+    in_size = static_cast<size_t>(n_in);
+  if (n_out != -1)
+    out_size = static_cast<size_t>(n_out);
+
+  auto deduce = [&](std::vector<AttrType> *vec, size_t size, const char *name) {
+      for (size_t i = 0; i < size; ++i) {
+        CHECK(assign(&dattr, (*vec)[i]))
+          << "Incompatible attr in node " << attrs.name << " at " << i << "-th "
+          << name << ": " << "expected " << attr_string(dattr)
+          << ", got " << attr_string((*vec)[i]);
+      }
+    };
+  deduce(in_attrs, in_size, "input");
+  if (reverse_infer) deduce(out_attrs, out_size, "output");
+
+  auto write = [&](std::vector<AttrType> *vec, size_t size, const char *name) {
+      for (size_t i = 0; i < size; ++i) {
+        CHECK(assign(&(*vec)[i], dattr))
+          << "Incompatible attr in node " << attrs.name << " at " << i << "-th "
+          << name << ": " << "expected " << attr_string(dattr)
+          << ", got " << attr_string((*vec)[i]);
+      }
+    };
+  write(in_attrs, in_size, "input");
+  write(out_attrs, out_size, "output");
+
+  if (is_none(dattr)) return false;
+  return true;
+}
+
+template<int n_in, int n_out>
+inline bool ElemwiseShape(const nnvm::NodeAttrs& attrs,
+                          std::vector<TShape> *in_attrs,
+                          std::vector<TShape> *out_attrs) {
+  if (n_in != -1) {
+    CHECK_EQ(in_attrs->size(), static_cast<size_t>(n_in)) << " in operator " << attrs.name;
+  }
+  if (n_out != -1) {
+    CHECK_EQ(out_attrs->size(), static_cast<size_t>(n_out)) << " in operator " << attrs.name;
+  }
+  return ElemwiseAttr<TShape, shape_is_none, shape_assign, true, shape_string>(
+    attrs, in_attrs, out_attrs, TShape());
+}
+
+template<int n_in, int n_out>
+inline bool ElemwiseType(const nnvm::NodeAttrs& attrs,
+                         std::vector<int> *in_attrs,
+                         std::vector<int> *out_attrs) {
+  if (n_in != -1) {
+    CHECK_EQ(in_attrs->size(), static_cast<size_t>(n_in)) << " in operator " << attrs.name;
+  }
+  if (n_out != -1) {
+    CHECK_EQ(out_attrs->size(), static_cast<size_t>(n_out)) << " in operator " << attrs.name;
+  }
+  return ElemwiseAttr<int, type_is_none, type_assign, true, type_string>(
+    attrs, in_attrs, out_attrs, -1);
+}
+
+}  // namespace top
+}  // namespace nnvm
+#endif  // NNVM_TOP_ELEMWISE_OP_COMMON_H_
diff --git a/nnvm/src/top/nn.cc b/nnvm/src/top/nn.cc
new file mode 100644
index 000000000000..7c01e6ee2d4c
--- /dev/null
+++ b/nnvm/src/top/nn.cc
@@ -0,0 +1,85 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file nn.cc
+ * \brief Property def of nn operators.
+ */
+#include <nnvm/op.h>
+#include <nnvm/node.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/top/nn.h>
+#include "./op_common.h"
+#include "./elemwise_op_common.h"
+
+namespace nnvm {
+namespace top {
+
+// dense
+DMLC_REGISTER_PARAMETER(DenseParam);
+
+inline std::vector<std::string> DenseListInputNames(const NodeAttrs& attrs) {
+  const DenseParam& param = nnvm::get<DenseParam>(attrs.parsed);
+  if (param.use_bias) {
+    return {"data", "weight", "bias"};
+  } else {
+    return {"data", "weight"};
+  }
+}
+
+inline bool DenseInferShape(const nnvm::NodeAttrs& attrs,
+                            std::vector<TShape> *in_shape,
+                            std::vector<TShape> *out_shape) {
+  const DenseParam& param = nnvm::get<DenseParam>(attrs.parsed);
+  if (param.use_bias) {
+    CHECK_EQ(in_shape->size(), 3U) << "Input:[data, weight, bias]";
+  } else {
+    CHECK_EQ(in_shape->size(), 2U) << "Input:[data, weight]";
+  }
+  CHECK_EQ(out_shape->size(), 1U);
+  TShape dshape = (*in_shape)[DenseParam::kData];
+  TShape oshape = (*out_shape)[0];
+  // require data to be known
+  if (dshape.ndim() ==  0) return false;
+  dim_t num_input;
+  num_input = dshape.ProdShape(1, dshape.ndim());
+  SHAPE_ASSIGN_CHECK(*in_shape, DenseParam::kWeight, TShape({param.units, num_input}));
+  if (param.use_bias) {
+    SHAPE_ASSIGN_CHECK(*in_shape, DenseParam::kBias, TShape({param.units}));
+  }
+  SHAPE_ASSIGN_CHECK(*out_shape, 0, TShape({dshape[0], param.units}));
+  if (oshape.ndim() != 0) {
+    dshape[0] = oshape[0];
+    SHAPE_ASSIGN_CHECK(*in_shape, DenseParam::kData, dshape);
+  }
+  return true;
+}
+
+NNVM_REGISTER_OP(dense)
+.NNVM_DESCRIBE(R"code(Applies a linear transformation: :math:`Y = XW^T + b`.
+
+- **data**: `(x1, x2, ..., xn, input_dim)`
+- **weight**: `(units, input_dim)`
+- **bias**: `(units,)`
+- **out**: `(x1, x2, ..., xn, num_hidden)`
+
+The learnable parameters include both ``weight`` and ``bias``.
+
+If ``use_bias`` is set to be false, then the ``bias`` term is ignored.
+
+)code" NNVM_ADD_FILELINE)
+.set_support_level(1)
+.add_argument("data", "nD Tensor", "Input data.")
+.add_argument("weight", "2D Tensor", "Weight matrix.")
+.add_argument("bias", "1D Tensor", "Bias parameter.")
+.add_arguments(DenseParam::__FIELDS__())
+.set_attr_parser(ParamParser<DenseParam>)
+.set_num_outputs(1)
+.set_num_inputs([](const NodeAttrs& attrs) {
+    const DenseParam& param = nnvm::get<DenseParam>(attrs.parsed);
+    return param.use_bias ? 3 : 2;
+  })
+.set_attr<FListInputNames>("FListInputNames", DenseListInputNames)
+.set_attr<FInferShape>("FInferShape", DenseInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>);
+
+}  // namespace top
+}  // namespace nnvm
diff --git a/nnvm/src/top/op_common.h b/nnvm/src/top/op_common.h
new file mode 100644
index 000000000000..b3af62401da6
--- /dev/null
+++ b/nnvm/src/top/op_common.h
@@ -0,0 +1,184 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file op_common.h
+ * \brief Common operator utilities
+ */
+#ifndef NNVM_TOP_OP_COMMON_H_
+#define NNVM_TOP_OP_COMMON_H_
+
+#include <dmlc/logging.h>
+#include <dmlc/parameter.h>
+#include <string>
+#include <vector>
+
+namespace nnvm {
+namespace top {
+
+/*! \brief exception throwed by InferShape error */
+struct InferShapeError : public dmlc::Error {
+  /*! \brief analyze message */
+  std::string msg;
+  /*! \brief corresponding input index */
+  int index;
+  // constructor
+  InferShapeError(const std::string& msg_, int index)
+    : dmlc::Error(msg_), msg(msg_), index(index) {}
+};
+
+/*! \brief exception throwed by InferShape error */
+struct InferTypeError : public dmlc::Error {
+  /*! \brief analyze message */
+  std::string msg;
+  /*! \brief corresponding input index */
+  int index;
+  // constructor
+  InferTypeError(const std::string& msg_, int index)
+    : dmlc::Error(msg_), msg(msg_), index(index) {}
+};
+
+/*!
+ * \brief Parse keyword arguments as PType arguments and save to parsed
+ * \tparam PType the arameter type.
+ * \param attrs The attributes.
+ */
+template<typename PType>
+inline void ParamParser(nnvm::NodeAttrs* attrs) {
+  PType param;
+  try {
+    param.Init(attrs->dict);
+  } catch (const dmlc::ParamError& e) {
+    std::ostringstream os;
+    os << e.what();
+    os << ", in operator " << attrs->op->name << "("
+       << "name=\"" << attrs->name << "\"";
+    for (const auto& k : attrs->dict) {
+      os << ", " << k.first << "=\"" << k.second << "\"";
+    }
+    os << ")";
+    throw dmlc::ParamError(os.str());
+  }
+  attrs->parsed = std::move(param);
+}
+
+/*! \brief check if shape is empty or contains unkown (0) dim. */
+inline bool shape_is_none(const TShape& x) {
+  return x.ndim() == 0 || x.Size() == 0;
+}
+
+/*! \brief check if type is none (-1) */
+inline bool type_is_none(const int& x) {
+  return x == -1;
+}
+
+/*! \brief check if shape is scalar({1}). */
+inline bool shape_is_scalar(const TShape& x) {
+  return x.ndim() == 1 && x.Size() == 1;
+}
+
+/*! \brief get string representation of shape */
+inline std::string shape_string(const TShape& x) {
+  std::ostringstream os;
+  os << x;
+  return os.str();
+}
+
+/*! \brief get string representation of shape */
+inline std::string type_string(const int& x) {
+  return std::to_string(x);
+}
+
+/*!
+ * \brief Assign x to y. Checks for compatiblity when y is not empty.
+ *  Allow missing dim in both x and y (as 0).
+ * \param y target shape.
+ * \param x source shape.
+ * \return whether x and y are compatible.
+ */
+inline bool shape_assign(TShape *y, const TShape& x) {
+  if (y->ndim() == 0) {
+    *y = x;
+    return true;
+  } else if (y->ndim() != x.ndim()) {
+    return x.ndim() == 0;
+  } else {
+    for (size_t i = 0; i < y->ndim(); ++i) {
+      if ((*y)[i] == 0) {
+        (*y)[i] = x[i];
+      } else if ((*y)[i] != x[i] && x[i] != 0) {
+        return false;
+      }
+    }
+    return true;
+  }
+}
+
+/*!
+ * \brief Assign x to y. Checks for compatiblity when y is not -1.
+ * \param y target type.
+ * \param x source type.
+ * \return whether x and y are compatible.
+ */
+inline bool type_assign(int *y, const int& x) {
+  if (*y == -1) {
+    *y = x;
+    return true;
+  } else if (*y != x && x != -1) {
+    return false;
+  }
+  return true;
+}
+
+/*!
+ * \brief macro assign shape to out if out is unknown otherwise check consistency
+ *  Use macro so we can see the error file more clearly
+ * \param shape_array the shape array to store the result
+ * \param index the index of in the array
+ * \param shape the inferred shape
+ */
+#define SHAPE_ASSIGN_CHECK(shape_array, index, shape)                   \
+  {                                                                     \
+    if (!shape_assign(&(shape_array)[index], TShape(shape))) {          \
+      std::ostringstream os;                                            \
+      os << "Shape inconsistent, Provided=" << (shape_array)[index] << ',' \
+         << " inferred shape=" << shape;                                \
+      throw InferShapeError(os.str(), index);                           \
+    }                                                                   \
+  }
+
+/*!
+ * \brief macro assign type to out if out is unknown (-1) otherwise check consistency
+ *  Use macro so we can see the error file more clearly
+ * \param type_array the type array to store the result
+ * \param index the index of in the array
+ * \param type the inferred type
+ */
+#define TYPE_ASSIGN_CHECK(type_array, index, type)                      \
+  {                                                                     \
+    if (!type_assign(&(type_array)[index], type)) {                     \
+      std::ostringstream os;                                            \
+      os << "Type inconsistent, Provided="                              \
+         << type_string((type_array)[index]) << ','                     \
+         << " inferred type=" << type_string(type);                     \
+      throw InferTypeError(os.str(), index);                            \
+    }                                                                   \
+  }
+
+
+// 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;
+}
+
+}  // namespace top
+}  // namespace nnvm
+
+#endif  // NNVM_TOP_OP_COMMON_H_
diff --git a/nnvm/src/top/tensor.cc b/nnvm/src/top/tensor.cc
new file mode 100644
index 000000000000..071651762f65
--- /dev/null
+++ b/nnvm/src/top/tensor.cc
@@ -0,0 +1,13 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file tensor.cc
+ * \brief Property def of tensor operators.
+ */
+#include <nnvm/base.h>
+#include <nnvm/top/tensor.h>
+
+namespace nnvm {
+namespace top {
+
+}  // namespace top
+}  // namespace nnvm
diff --git a/nnvm/tests/python/test_symbol.py b/nnvm/tests/python/test_symbol.py
index 05b47c86300a..b671c70199c0 100644
--- a/nnvm/tests/python/test_symbol.py
+++ b/nnvm/tests/python/test_symbol.py
@@ -1,6 +1,15 @@
 import nnvm.symbol as sym
 from nnvm import NNVMError
 
+def test_dense():
+    x = sym.Variable('x')
+    y = sym.dense(x)
+    assert y.list_input_names() == ['x']
+
+
+
+
+
 def test_compose():
     x = sym.Variable('x')
     z = sym.Variable('z')
diff --git a/nnvm/tests/python/test_top_nn.py b/nnvm/tests/python/test_top_nn.py
new file mode 100644
index 000000000000..e381c6ec82a7
--- /dev/null
+++ b/nnvm/tests/python/test_top_nn.py
@@ -0,0 +1,10 @@
+import nnvm.symbol as sym
+from nnvm import NNVMError
+
+def test_dense():
+    x = sym.Variable('x')
+    y = sym.dense(x, units=3, name="dense")
+    assert y.list_input_names() == ['x', 'dense_weight', 'dense_bias']
+
+if __name__ == "__main__":
+    test_dense()

From e23030eb6846e0d88f422f06a8e6eede99b1ed51 Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Fri, 8 Sep 2017 15:24:06 -0700
Subject: [PATCH 460/949] [TOP] concat, sigmoid

---
 nnvm/include/nnvm/top/tensor.h                |   8 +
 nnvm/src/top/elemwise_op_common.h             |  12 ++
 nnvm/src/top/nn.cc                            |  13 +-
 nnvm/src/top/tensor.cc                        | 171 +++++++++++++++++-
 .../{test_top_nn.py => test_top_level1.py}    |   8 +
 5 files changed, 209 insertions(+), 3 deletions(-)
 rename nnvm/tests/python/{test_top_nn.py => test_top_level1.py} (61%)

diff --git a/nnvm/include/nnvm/top/tensor.h b/nnvm/include/nnvm/top/tensor.h
index 4d1886871899..cf4d52c59395 100644
--- a/nnvm/include/nnvm/top/tensor.h
+++ b/nnvm/include/nnvm/top/tensor.h
@@ -9,6 +9,14 @@
 namespace nnvm {
 namespace top {
 
+struct ConcatParam : public dmlc::Parameter<ConcatParam> {
+  int dim;
+  DMLC_DECLARE_PARAMETER(ConcatParam) {
+    DMLC_DECLARE_FIELD(dim).set_range(0,  4).set_default(1)
+    .describe("the axis to be concated.");
+  }
+};
+
 }  // namespace top
 }  // namespace nnvm
 
diff --git a/nnvm/src/top/elemwise_op_common.h b/nnvm/src/top/elemwise_op_common.h
index 28b091333bec..0a358ab8f54f 100644
--- a/nnvm/src/top/elemwise_op_common.h
+++ b/nnvm/src/top/elemwise_op_common.h
@@ -8,6 +8,7 @@
 
 #include <string>
 #include <vector>
+#include <utility>
 #include "./op_common.h"
 
 namespace nnvm {
@@ -83,6 +84,17 @@ inline bool ElemwiseType(const nnvm::NodeAttrs& attrs,
     attrs, in_attrs, out_attrs, -1);
 }
 
+#define NNVM_REGISTER_ELEMWISE_UNARY_OP(name)                       \
+  NNVM_REGISTER_OP(name)                                            \
+  .set_num_inputs(1)                                                \
+  .set_num_outputs(1)                                               \
+  .set_attr<nnvm::FInferShape>("FInferShape", ElemwiseShape<1, 1>)  \
+  .set_attr<nnvm::FInferType>("FInferType", ElemwiseType<1, 1>)     \
+  .set_attr<nnvm::FInplaceOption>("FInplaceOption",                 \
+    [](const NodeAttrs& attrs){                                     \
+      return std::vector<std::pair<int, int> >{{0, 0}};             \
+    })                                                              \
+  .add_argument("data", "Tensor", "The input tensor.")
 }  // namespace top
 }  // namespace nnvm
 #endif  // NNVM_TOP_ELEMWISE_OP_COMMON_H_
diff --git a/nnvm/src/top/nn.cc b/nnvm/src/top/nn.cc
index 7c01e6ee2d4c..4fd83adcd723 100644
--- a/nnvm/src/top/nn.cc
+++ b/nnvm/src/top/nn.cc
@@ -66,7 +66,6 @@ The learnable parameters include both ``weight`` and ``bias``.
 If ``use_bias`` is set to be false, then the ``bias`` term is ignored.
 
 )code" NNVM_ADD_FILELINE)
-.set_support_level(1)
 .add_argument("data", "nD Tensor", "Input data.")
 .add_argument("weight", "2D Tensor", "Weight matrix.")
 .add_argument("bias", "1D Tensor", "Bias parameter.")
@@ -79,7 +78,17 @@ If ``use_bias`` is set to be false, then the ``bias`` term is ignored.
   })
 .set_attr<FListInputNames>("FListInputNames", DenseListInputNames)
 .set_attr<FInferShape>("FInferShape", DenseInferShape)
-.set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>);
+.set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
+.set_support_level(1);
 
+// relu
+NNVM_REGISTER_ELEMWISE_UNARY_OP(relu)
+.describe(R"code(Computes rectified linear.
+
+.. math::
+   max(input, 0)
+
+)code" NNVM_ADD_FILELINE)
+.set_support_level(1);
 }  // namespace top
 }  // namespace nnvm
diff --git a/nnvm/src/top/tensor.cc b/nnvm/src/top/tensor.cc
index 071651762f65..5b9c2d3ad1f4 100644
--- a/nnvm/src/top/tensor.cc
+++ b/nnvm/src/top/tensor.cc
@@ -3,11 +3,180 @@
  * \file tensor.cc
  * \brief Property def of tensor operators.
  */
-#include <nnvm/base.h>
+#include <nnvm/op.h>
+#include <nnvm/node.h>
+#include <nnvm/op_attr_types.h>
 #include <nnvm/top/tensor.h>
+#include "./op_common.h"
+#include "./elemwise_op_common.h"
 
 namespace nnvm {
 namespace top {
+// sigmoid
+NNVM_REGISTER_ELEMWISE_UNARY_OP(sigmoid)
+.describe(R"code(Computes sigmoid.
+
+.. math::
+   y = 1 / (1 + exp(-x))
+
+)code" NNVM_ADD_FILELINE)
+.set_support_level(1);
+
+// tanh
+NNVM_REGISTER_ELEMWISE_UNARY_OP(tanh)
+.describe(R"code(Returns the hyperbolic tangent of the input array, computed element-wise.
+
+.. math::
+   tanh(x) = sinh(x) / cosh(x)
+
+)code" NNVM_ADD_FILELINE)
+.set_support_level(1);
+
+// exp
+NNVM_REGISTER_ELEMWISE_UNARY_OP(exp)
+.describe(R"code(Returns the exp input array, computed element-wise.
+
+.. math::
+   exp(x)
+
+)code" NNVM_ADD_FILELINE)
+.set_support_level(1);
+
+// log
+NNVM_REGISTER_ELEMWISE_UNARY_OP(log)
+.describe(R"code(Returns the log input array, computed element-wise.
+
+.. math::
+   log(x)
+
+)code" NNVM_ADD_FILELINE)
+.set_support_level(1);
+
+// flatten
+inline bool FlattenInferShape(const nnvm::NodeAttrs& attrs,
+                              std::vector<TShape> *in_attrs,
+                              std::vector<TShape> *out_attrs) {
+  CHECK_EQ(in_attrs->size(), 1U) << "Input: [data]";
+  CHECK_EQ(out_attrs->size(), 1U);
+  const TShape &dshape = (*in_attrs)[0];
+  if (dshape.ndim() == 0) return false;
+  uint32_t target_dim = 1;
+  for (uint32_t i = 1; i < dshape.ndim(); ++i) {
+    target_dim *= dshape[i];
+  }
+  SHAPE_ASSIGN_CHECK(*out_attrs, 0, TShape({dshape[0], target_dim}));
+  return true;
+}
+
+NNVM_REGISTER_OP(flatten)
+.describe(R"code(Flattens the input array into a 2-D array by collapsing the higher dimensions.
+
+For an input array with shape ``(d1, d2, ..., dk)``, `flatten` operation reshapes
+the input array into an output array of shape ``(d1, d2*...*dk)``.
+
+Example::
+
+    x = [[
+        [1,2,3],
+        [4,5,6],
+        [7,8,9]
+    ],
+    [    [1,2,3],
+        [4,5,6],
+        [7,8,9]
+    ]],
+
+    flatten(x) = [[ 1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9.],
+       [ 1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9.]]
+
+)code" NNVM_ADD_FILELINE)
+.set_num_inputs(1)
+.set_num_outputs(1)
+.set_attr<FInferShape>("FInferShape", FlattenInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.add_argument("data", "Tensor", "Input data.")
+.set_support_level(1);
+
+// concat TODO(eric): change name(concat->concatenate) and argument(dim->axis)
+DMLC_REGISTER_PARAMETER(ConcatParam);
+
+inline bool ConcatInferShape(const nnvm::NodeAttrs& attrs,
+                             std::vector<TShape> *in_shape,
+                             std::vector<TShape> *out_shape) {
+  const ConcatParam& param = nnvm::get<ConcatParam>(attrs.parsed);
+  TShape dshape;
+  dim_t size = 0;
+  bool has_zero = false;
+  for (size_t i = 0; i < in_shape->size(); ++i) {
+    TShape tmp = (*in_shape)[i];
+    if (tmp.ndim()) {
+      CHECK_LT(static_cast<dim_t>(param.dim), tmp.ndim())
+          << "concat dim " << param.dim << " out of range of input shape " << tmp;
+      has_zero = tmp[param.dim] == 0 || has_zero;
+      size += tmp[param.dim];
+      tmp[param.dim] = 0;
+      shape_assign(&dshape, tmp);
+    }
+  }
+
+  TShape tmp = (*out_shape)[0];
+  if (tmp.ndim()) {
+    CHECK_LT(static_cast<dim_t>(param.dim), tmp.ndim())
+        << "concat dim " << param.dim << " out of range of input shape " << tmp;
+    tmp[param.dim] = 0;
+    shape_assign(&dshape, tmp);
+  }
+
+  if (dshape.ndim() == 0) return false;
+
+  for (size_t i = 0; i < in_shape->size(); ++i) {
+    SHAPE_ASSIGN_CHECK(*in_shape, i, dshape);
+  }
+
+  if (!has_zero) dshape[param.dim] = size;
+  SHAPE_ASSIGN_CHECK(*out_shape, 0, dshape);
+  return dshape.Size() != 0;
+}
+
+NNVM_REGISTER_OP(concat)
+.describe(R"code(Joins input arrays along a given axis.
+
+The dimensions of the input arrays should be the same except the axis along
+which they will be concatenated.
+The dimension of the output array along the concatenated axis will be equal
+to the sum of the corresponding dimensions of the input arrays.
+
+Example::
+
+   x = [[1,1],[2,2]]
+   y = [[3,3],[4,4],[5,5]]
+   z = [[6,6], [7,7],[8,8]]
+
+   concat(x,y,z,dim=0) = [[ 1.,  1.],
+                          [ 2.,  2.],
+                          [ 3.,  3.],
+                          [ 4.,  4.],
+                          [ 5.,  5.],
+                          [ 6.,  6.],
+                          [ 7.,  7.],
+                          [ 8.,  8.]]
+
+   Note that you cannot concat x,y,z along dimension 1 since dimension
+   0 is not the same for all the input arrays.
+
+   concat(y,z,dim=1) = [[ 3.,  3.,  6.,  6.],
+                         [ 4.,  4.,  7.,  7.],
+                         [ 5.,  5.,  8.,  8.]]
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "Tensor-or-Tensor[]", "List of arrays to concatenate")
+.set_attr<FInferShape>("FInferShape", ConcatInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
+.add_arguments(ConcatParam::__FIELDS__())
+.set_num_inputs(nnvm::kVarg)
+.set_support_level(1);
+
+
 
 }  // namespace top
 }  // namespace nnvm
diff --git a/nnvm/tests/python/test_top_nn.py b/nnvm/tests/python/test_top_level1.py
similarity index 61%
rename from nnvm/tests/python/test_top_nn.py
rename to nnvm/tests/python/test_top_level1.py
index e381c6ec82a7..45e26d5cdc9d 100644
--- a/nnvm/tests/python/test_top_nn.py
+++ b/nnvm/tests/python/test_top_level1.py
@@ -6,5 +6,13 @@ def test_dense():
     y = sym.dense(x, units=3, name="dense")
     assert y.list_input_names() == ['x', 'dense_weight', 'dense_bias']
 
+def test_concat():
+    x = sym.Variable('x')
+    y = sym.Variable('y')
+    y = sym.concat(x, y)
+    assert y.list_input_names() == ['x', 'y']
+
+
 if __name__ == "__main__":
+    test_concat()
     test_dense()

From 25bc9ca9888a5c2b096da1ccfeaec4151d6a2e74 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 9 Sep 2017 15:06:19 -0700
Subject: [PATCH 461/949] [TOP] Level1 complete (#3)

---
 nnvm/include/nnvm/top/nn.h           |  58 +++++++++++++
 nnvm/include/nnvm/top/tensor.h       |  31 ++++++-
 nnvm/src/top/elemwise_op_common.h    |  25 ++++--
 nnvm/src/top/nn.cc                   | 118 ++++++++++++++++++++++++++-
 nnvm/src/top/tensor.cc               | 109 ++++++++++++++++++-------
 nnvm/tests/python/test_gradient.py   |  24 ------
 nnvm/tests/python/test_graph.py      |  87 +++++---------------
 nnvm/tests/python/test_symbol.py     |  40 ++-------
 nnvm/tests/python/test_top_level1.py |  33 ++++++--
 9 files changed, 355 insertions(+), 170 deletions(-)
 delete mode 100644 nnvm/tests/python/test_gradient.py

diff --git a/nnvm/include/nnvm/top/nn.h b/nnvm/include/nnvm/top/nn.h
index dcb573942488..1c4e70696911 100644
--- a/nnvm/include/nnvm/top/nn.h
+++ b/nnvm/include/nnvm/top/nn.h
@@ -15,6 +15,7 @@ namespace top {
 struct DenseParam : public dmlc::Parameter<DenseParam> {
   int units;
   bool use_bias;
+
   DMLC_DECLARE_PARAMETER(DenseParam) {
     DMLC_DECLARE_FIELD(units).set_lower_bound(1)
     .describe("Number of hidden units of the dense transformation.");
@@ -27,6 +28,63 @@ struct DenseParam : public dmlc::Parameter<DenseParam> {
   static const constexpr int kBias = 2;
 };
 
+struct DropoutParam : public dmlc::Parameter<DropoutParam> {
+  float rate;
+
+  DMLC_DECLARE_PARAMETER(DropoutParam) {
+    DMLC_DECLARE_FIELD(rate).set_default(0.5)
+        .set_range(0, 1)
+        .describe("Fraction of the input that gets dropped out during training time.");
+  }
+};
+
+struct BatchNormParam : public dmlc::Parameter<BatchNormParam> {
+  int axis;
+  float epsilon;
+  float momentum;
+  bool center;
+  bool scale;
+
+  DMLC_DECLARE_PARAMETER(BatchNormParam) {
+    DMLC_DECLARE_FIELD(axis).set_default(1)
+      .describe("Specify which shape axis the channel is specified.");
+    DMLC_DECLARE_FIELD(epsilon).set_default(1e-5f)
+        .describe("Small float added to variance to avoid dividing by zero.");
+    DMLC_DECLARE_FIELD(center).set_default(true)
+        .describe("If True, add offset of `beta` to normalized tensor."
+                  "If False, `beta` is ignored.");
+    DMLC_DECLARE_FIELD(scale).set_default(true)
+        .describe("If True, multiply by `gamma`. If False, `gamma` is not used."
+                  "When the next layer is piecewise linear (also e.g. `nn.relu`),"
+                  "this can be disabled since the scaling"
+                  "will be done by the next layer.");
+  }
+  // constants
+  static const constexpr int kData = 0;
+  static const constexpr int kGamma = 1;
+  static const constexpr int kBeta = 2;
+  static const constexpr int kMovingMean = 3;
+  static const constexpr int kMovingVariance = 4;
+};
+
+struct SoftmaxParam : public dmlc::Parameter<SoftmaxParam> {
+  int axis;
+
+  DMLC_DECLARE_PARAMETER(SoftmaxParam) {
+    DMLC_DECLARE_FIELD(axis).set_default(-1)
+      .describe("The axis to sum over when computing softmax.");
+  }
+};
+
+struct LogSoftmaxParam : public dmlc::Parameter<LogSoftmaxParam> {
+  int axis;
+
+  DMLC_DECLARE_PARAMETER(LogSoftmaxParam) {
+    DMLC_DECLARE_FIELD(axis).set_default(-1)
+      .describe("The axis to sum over when computing softmax.");
+  }
+};
+
 }  // namespace top
 }  // namespace nnvm
 
diff --git a/nnvm/include/nnvm/top/tensor.h b/nnvm/include/nnvm/top/tensor.h
index cf4d52c59395..347c7211d724 100644
--- a/nnvm/include/nnvm/top/tensor.h
+++ b/nnvm/include/nnvm/top/tensor.h
@@ -9,14 +9,37 @@
 namespace nnvm {
 namespace top {
 
-struct ConcatParam : public dmlc::Parameter<ConcatParam> {
-  int dim;
-  DMLC_DECLARE_PARAMETER(ConcatParam) {
-    DMLC_DECLARE_FIELD(dim).set_range(0,  4).set_default(1)
+struct ConcatenateParam : public dmlc::Parameter<ConcatenateParam> {
+  int axis;
+  DMLC_DECLARE_PARAMETER(ConcatenateParam) {
+    DMLC_DECLARE_FIELD(axis).set_lower_bound(0).set_default(1)
     .describe("the axis to be concated.");
   }
 };
 
+enum TypeFlag {
+  kFloat32 = 0,
+  kFloat64 = 1,
+  kFloat16 = 2,
+  kUint8 = 3,
+  kInt32 = 4,
+  kInt8  = 5,
+  kInt64 = 6,
+};
+
+struct CastParam : public dmlc::Parameter<CastParam> {
+  int dtype;
+  DMLC_DECLARE_PARAMETER(CastParam) {
+    DMLC_DECLARE_FIELD(dtype)
+    .add_enum("float32", kFloat32)
+    .add_enum("float64", kFloat64)
+    .add_enum("float16", kFloat16)
+    .add_enum("uint8", kUint8)
+    .add_enum("int32", kInt32)
+    .describe("Output data type.");
+  }
+};
+
 }  // namespace top
 }  // namespace nnvm
 
diff --git a/nnvm/src/top/elemwise_op_common.h b/nnvm/src/top/elemwise_op_common.h
index 0a358ab8f54f..a74482d5281d 100644
--- a/nnvm/src/top/elemwise_op_common.h
+++ b/nnvm/src/top/elemwise_op_common.h
@@ -57,7 +57,7 @@ inline bool ElemwiseAttr(const nnvm::NodeAttrs& attrs,
 }
 
 template<int n_in, int n_out>
-inline bool ElemwiseShape(const nnvm::NodeAttrs& attrs,
+inline bool ElemwiseShape(const NodeAttrs& attrs,
                           std::vector<TShape> *in_attrs,
                           std::vector<TShape> *out_attrs) {
   if (n_in != -1) {
@@ -71,7 +71,7 @@ inline bool ElemwiseShape(const nnvm::NodeAttrs& attrs,
 }
 
 template<int n_in, int n_out>
-inline bool ElemwiseType(const nnvm::NodeAttrs& attrs,
+inline bool ElemwiseType(const NodeAttrs& attrs,
                          std::vector<int> *in_attrs,
                          std::vector<int> *out_attrs) {
   if (n_in != -1) {
@@ -88,13 +88,28 @@ inline bool ElemwiseType(const nnvm::NodeAttrs& attrs,
   NNVM_REGISTER_OP(name)                                            \
   .set_num_inputs(1)                                                \
   .set_num_outputs(1)                                               \
-  .set_attr<nnvm::FInferShape>("FInferShape", ElemwiseShape<1, 1>)  \
-  .set_attr<nnvm::FInferType>("FInferType", ElemwiseType<1, 1>)     \
-  .set_attr<nnvm::FInplaceOption>("FInplaceOption",                 \
+  .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)        \
+  .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)           \
+  .set_attr<FInplaceOption>("FInplaceOption",                       \
     [](const NodeAttrs& attrs){                                     \
       return std::vector<std::pair<int, int> >{{0, 0}};             \
     })                                                              \
   .add_argument("data", "Tensor", "The input tensor.")
+
+
+#define NNVM_REGISTER_ELEMWISE_BINARY_OP(name)                      \
+  NNVM_REGISTER_OP(name)                                            \
+  .set_num_inputs(2)                                                \
+  .set_num_outputs(1)                                               \
+  .set_attr<FInferShape>("FInferShape", ElemwiseShape<2, 1>)        \
+  .set_attr<FInferType>("FInferType", ElemwiseType<2, 1>)           \
+  .set_attr<FInplaceOption>("FInplaceOption",                       \
+    [](const NodeAttrs& attrs) {                                    \
+      return std::vector<std::pair<int, int> >{{0, 0}, {1, 0}};     \
+    })                                                              \
+  .add_argument("lhs", "NDArray-or-Symbol", "first input")          \
+  .add_argument("rhs", "NDArray-or-Symbol", "second input")
+
 }  // namespace top
 }  // namespace nnvm
 #endif  // NNVM_TOP_ELEMWISE_OP_COMMON_H_
diff --git a/nnvm/src/top/nn.cc b/nnvm/src/top/nn.cc
index 4fd83adcd723..4d99bbc1389c 100644
--- a/nnvm/src/top/nn.cc
+++ b/nnvm/src/top/nn.cc
@@ -73,7 +73,7 @@ If ``use_bias`` is set to be false, then the ``bias`` term is ignored.
 .set_attr_parser(ParamParser<DenseParam>)
 .set_num_outputs(1)
 .set_num_inputs([](const NodeAttrs& attrs) {
-    const DenseParam& param = nnvm::get<DenseParam>(attrs.parsed);
+    const DenseParam& param = get<DenseParam>(attrs.parsed);
     return param.use_bias ? 3 : 2;
   })
 .set_attr<FListInputNames>("FListInputNames", DenseListInputNames)
@@ -90,5 +90,121 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(relu)
 
 )code" NNVM_ADD_FILELINE)
 .set_support_level(1);
+
+// dropout
+DMLC_REGISTER_PARAMETER(DropoutParam);
+
+NNVM_REGISTER_OP(dropout)
+.describe(R"(Applies dropout operation to input array.
+
+- During training, each element of the input is set to zero with probability p.
+  The whole array is rescaled by :math:`1/(1-p)` to keep the expected
+  sum of the input unchanged.
+
+)" NNVM_ADD_FILELINE)
+.add_argument("data", "Tensor", "Input to which dropout will be applied")
+.set_num_inputs(1)
+.set_num_outputs(2)
+.set_attr_parser(ParamParser<DropoutParam>)
+.set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 2>)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 2>)
+.set_attr<FNumVisibleOutputs>("FNumVisibleOutputs", [](const NodeAttrs& attrs) {
+    return 1;
+  })
+.set_attr<FListOutputNames>("FListOutputNames", [](const NodeAttrs& attrs) {
+    return std::vector<std::string>{"output", "mask"};
+  })
+.set_support_level(1);
+
+// batchnorm
+DMLC_REGISTER_PARAMETER(BatchNormParam);
+
+NNVM_REGISTER_OP(batch_norm)
+.describe(R"(Batch normalization layer (Ioffe and Szegedy, 2014).
+Normalizes the input at each batch, i.e. applies a transformation
+that maintains the mean activation close to 0 and the activation
+standard deviation close to 1.
+
+.. math::
+
+  data\_mean[i] = mean(data[:,i,:,...]) \\
+  data\_var[i] = var(data[:,i,:,...])
+
+Then compute the normalized output, which has the same shape as input, as following:
+
+.. math::
+
+  out[:,i,:,...] = \frac{data[:,i,:,...] - data\_mean[i]}{\sqrt{data\_var[i]+\epsilon}} * gamma[i] + beta[i]
+
+Both *mean* and *var* returns a scalar by treating the input as a vector.
+
+Assume the input has size *k* on axis 1, then both ``gamma`` and ``beta`` have shape *(k,)*.
+
+Besides the inputs and the outputs, this operator accepts two auxiliary
+states, ``moving_mean`` and ``moving_var``, which are *k*-length
+vectors. They are global statistics for the whole dataset, which are updated
+by::
+
+  moving_mean = moving_mean * momentum + data_mean * (1 - momentum)
+  moving_var = moving_var * momentum + data_var * (1 - momentum)
+
+The parameter ``axis`` specifies which axis of the input shape denotes
+the 'channel' (separately normalized groups).  The default is 1.  Specifying -1 sets the channel
+axis to be the last item in the input shape.
+)" NNVM_ADD_FILELINE)
+.add_argument("data", "Tensor", "Input to which dropout will be applied")
+.add_argument("gamma", "Tensor", "The gamma scale factor")
+.add_argument("beta", "Tensor", "The beta offset factor")
+.add_argument("moving_mean", "Tensor", "running mean of input")
+.add_argument("moving_var", "Tensor", "running variance of input")
+.set_num_inputs(5)
+.set_num_outputs(3)
+.set_attr_parser(ParamParser<BatchNormParam>)
+.set_attr<FListInputNames>("FListInputNames", [](const NodeAttrs& attrs) {
+    return std::vector<std::string>{"data", "gamma", "beta", "moving_mean", "moving_var"};
+  })
+.set_attr<FListOutputNames>("FListOutputNames", [](const NodeAttrs& attrs) {
+    return std::vector<std::string>{"output", "mean", "var"};
+  })
+.set_attr<FNumVisibleOutputs>("FNumVisibleOutputs", [](const NodeAttrs& attrs) {
+    return 1;
+  })
+.set_attr<FMutateInputs>("FListMutateInputs", [](const NodeAttrs& attrs) {
+    return std::vector<uint32_t>{3, 4};
+  })
+.set_support_level(1);
+
+// softmax
+DMLC_REGISTER_PARAMETER(SoftmaxParam);
+
+NNVM_REGISTER_OP(softmax)
+.describe(R"code(Computes softmax.
+
+.. math:: \text{softmax}(x)_i = \frac{exp(x_i)}{\sum_j exp(x_j)}
+
+)code" NNVM_ADD_FILELINE)
+.set_num_inputs(1)
+.set_num_outputs(1)
+.set_attr_parser(ParamParser<SoftmaxParam>)
+.set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_support_level(1);
+
+// log_softmax
+DMLC_REGISTER_PARAMETER(LogSoftmaxParam);
+
+NNVM_REGISTER_OP(log_softmax)
+.describe(R"code(Computes softmax.
+
+.. math:: \text{log_softmax}(x)_i = \log \frac{exp(x_i)}{\sum_j exp(x_j)}
+
+)code" NNVM_ADD_FILELINE)
+.set_num_inputs(1)
+.set_num_outputs(1)
+.set_attr_parser(ParamParser<LogSoftmaxParam>)
+.set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_support_level(1);
+
 }  // namespace top
 }  // namespace nnvm
diff --git a/nnvm/src/top/tensor.cc b/nnvm/src/top/tensor.cc
index 5b9c2d3ad1f4..c006ff4b5d57 100644
--- a/nnvm/src/top/tensor.cc
+++ b/nnvm/src/top/tensor.cc
@@ -97,33 +97,33 @@ Example::
 .add_argument("data", "Tensor", "Input data.")
 .set_support_level(1);
 
-// concat TODO(eric): change name(concat->concatenate) and argument(dim->axis)
-DMLC_REGISTER_PARAMETER(ConcatParam);
+// concatenate
+DMLC_REGISTER_PARAMETER(ConcatenateParam);
 
-inline bool ConcatInferShape(const nnvm::NodeAttrs& attrs,
-                             std::vector<TShape> *in_shape,
-                             std::vector<TShape> *out_shape) {
-  const ConcatParam& param = nnvm::get<ConcatParam>(attrs.parsed);
+inline bool ConcatenateInferShape(const nnvm::NodeAttrs& attrs,
+                                  std::vector<TShape> *in_shape,
+                                  std::vector<TShape> *out_shape) {
+  const ConcatenateParam& param = nnvm::get<ConcatenateParam>(attrs.parsed);
   TShape dshape;
   dim_t size = 0;
   bool has_zero = false;
   for (size_t i = 0; i < in_shape->size(); ++i) {
     TShape tmp = (*in_shape)[i];
     if (tmp.ndim()) {
-      CHECK_LT(static_cast<dim_t>(param.dim), tmp.ndim())
-          << "concat dim " << param.dim << " out of range of input shape " << tmp;
-      has_zero = tmp[param.dim] == 0 || has_zero;
-      size += tmp[param.dim];
-      tmp[param.dim] = 0;
+      CHECK_LT(static_cast<dim_t>(param.axis), tmp.ndim())
+          << "concat dim " << param.axis << " out of range of input shape " << tmp;
+      has_zero = tmp[param.axis] == 0 || has_zero;
+      size += tmp[param.axis];
+      tmp[param.axis] = 0;
       shape_assign(&dshape, tmp);
     }
   }
 
   TShape tmp = (*out_shape)[0];
   if (tmp.ndim()) {
-    CHECK_LT(static_cast<dim_t>(param.dim), tmp.ndim())
-        << "concat dim " << param.dim << " out of range of input shape " << tmp;
-    tmp[param.dim] = 0;
+    CHECK_LT(static_cast<dim_t>(param.axis), tmp.ndim())
+        << "concat dim " << param.axis << " out of range of input shape " << tmp;
+    tmp[param.axis] = 0;
     shape_assign(&dshape, tmp);
   }
 
@@ -133,12 +133,12 @@ inline bool ConcatInferShape(const nnvm::NodeAttrs& attrs,
     SHAPE_ASSIGN_CHECK(*in_shape, i, dshape);
   }
 
-  if (!has_zero) dshape[param.dim] = size;
+  if (!has_zero) dshape[param.axis] = size;
   SHAPE_ASSIGN_CHECK(*out_shape, 0, dshape);
   return dshape.Size() != 0;
 }
 
-NNVM_REGISTER_OP(concat)
+NNVM_REGISTER_OP(concatenate)
 .describe(R"code(Joins input arrays along a given axis.
 
 The dimensions of the input arrays should be the same except the axis along
@@ -152,31 +152,80 @@ Example::
    y = [[3,3],[4,4],[5,5]]
    z = [[6,6], [7,7],[8,8]]
 
-   concat(x,y,z,dim=0) = [[ 1.,  1.],
-                          [ 2.,  2.],
-                          [ 3.,  3.],
-                          [ 4.,  4.],
-                          [ 5.,  5.],
-                          [ 6.,  6.],
-                          [ 7.,  7.],
-                          [ 8.,  8.]]
+   concatenate(x,y,z,dim=0) = [[ 1.,  1.],
+                               [ 2.,  2.],
+                               [ 3.,  3.],
+                               [ 4.,  4.],
+                               [ 5.,  5.],
+                               [ 6.,  6.],
+                               [ 7.,  7.],
+                               [ 8.,  8.]]
 
    Note that you cannot concat x,y,z along dimension 1 since dimension
    0 is not the same for all the input arrays.
 
-   concat(y,z,dim=1) = [[ 3.,  3.,  6.,  6.],
-                         [ 4.,  4.,  7.,  7.],
-                         [ 5.,  5.,  8.,  8.]]
+   concatenate(y,z,dim=1) = [[ 3.,  3.,  6.,  6.],
+                             [ 4.,  4.,  7.,  7.],
+                             [ 5.,  5.,  8.,  8.]]
 
 )code" NNVM_ADD_FILELINE)
+.set_num_outputs(1)
+.set_num_inputs(nnvm::kVarg)
+.set_attr_parser(ParamParser<ConcatenateParam>)
 .add_argument("data", "Tensor-or-Tensor[]", "List of arrays to concatenate")
-.set_attr<FInferShape>("FInferShape", ConcatInferShape)
+.set_attr<FInferShape>("FInferShape", ConcatenateInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
-.add_arguments(ConcatParam::__FIELDS__())
-.set_num_inputs(nnvm::kVarg)
+.add_arguments(ConcatenateParam::__FIELDS__())
+.set_support_level(1);
+
+NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_add)
+.describe(R"code(Element-wise add
+
+)code")
+.set_support_level(1);
+
+NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_sub)
+.describe(R"code(Element-wise substraction
+
+)code"  NNVM_ADD_FILELINE)
+.set_support_level(1);
+
+NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_mul)
+.describe(R"code(Element-wise multiplication
+
+)code"  NNVM_ADD_FILELINE)
 .set_support_level(1);
 
+NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_div)
+.describe(R"code(Element-wise multiplication
 
+)code"  NNVM_ADD_FILELINE)
+.set_support_level(1);
+
+// cast
+DMLC_REGISTER_PARAMETER(CastParam);
+
+inline bool CastInferType(const nnvm::NodeAttrs& attrs,
+                          std::vector<int> *in_attrs,
+                          std::vector<int> *out_attrs) {
+  const CastParam& param = nnvm::get<CastParam>(attrs.parsed);
+  CHECK_EQ(out_attrs->size(), 1U);
+  TYPE_ASSIGN_CHECK(*out_attrs, 0, param.dtype);
+  return true;
+}
+
+NNVM_REGISTER_OP(cast)
+.describe(R"code(Cast the content of input to dtype.
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "Tensor", "Input data array")
+.set_attr_parser(ParamParser<CastParam>)
+.set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
+.set_attr<FInferType>("FInferType", CastInferType)
+.add_arguments(CastParam::__FIELDS__())
+.set_num_inputs(1)
+.set_num_outputs(1)
+.set_support_level(1);
 
 }  // namespace top
 }  // namespace nnvm
diff --git a/nnvm/tests/python/test_gradient.py b/nnvm/tests/python/test_gradient.py
deleted file mode 100644
index 00fc5c75e620..000000000000
--- a/nnvm/tests/python/test_gradient.py
+++ /dev/null
@@ -1,24 +0,0 @@
-import json
-import nnvm.symbol as sym
-import nnvm.graph as graph
-
-def grad(ys, xs, ys_grads):
-    g = graph.create(ys)
-    g._set_symbol_list_attr('grad_ys', ys)
-    g._set_symbol_list_attr('grad_xs', xs)
-    g._set_symbol_list_attr('grad_ys_out_grad', ys_grads)
-    return g.apply('Gradient')
-
-def test_graph_gradient():
-    x0 = sym.Variable('x0')
-    x1 = sym.Variable('x1')
-    yg = sym.Variable('yg')
-    y = sym.exp(sym.mul(x0, x1))
-    grad_graph = grad(y, [x0], yg)
-    print("Original graph")
-    print(y.debug_str())
-    print("Gradient  graph")
-    print(grad_graph.symbol.debug_str())
-
-if __name__ == "__main__":
-    test_graph_gradient()
diff --git a/nnvm/tests/python/test_graph.py b/nnvm/tests/python/test_graph.py
index 86595aec3219..e706ae1b428c 100644
--- a/nnvm/tests/python/test_graph.py
+++ b/nnvm/tests/python/test_graph.py
@@ -4,7 +4,7 @@
 
 def test_json_pass():
     x = sym.Variable('x')
-    y = sym.conv2d(data=x, name='conv', stride=(2,2))
+    y = sym.dense(data=x, name='conv', units=30)
     g = graph.create(y)
     ret = g.apply('SaveJSON')
     ret._set_json_attr('json', ret.json_attr('json'))
@@ -14,12 +14,11 @@ def test_json_pass():
 
 def test_json_pass_with_attr():
     x = sym.Variable('x')
-    y = sym.conv2d(data=x, name='conv', stride=(2,2))
+    y = sym.dense(data=x, name='fc', units=30)
     g = graph.create(y)
     g._set_json_attr('version', '0.1.0')
     ret = g.apply('SaveJSON')
     json_str = ret.json_attr('json')
-    print(json_str)
     ret._set_json_attr('json', json_str)
     g2 = ret.apply('LoadJSON')
     assert g2.json_attr('version') == '0.1.0'
@@ -27,42 +26,21 @@ def test_json_pass_with_attr():
 
 def test_graph_json_attr():
     x = sym.Variable('x')
-    y = sym.conv2d(data=x, name='conv', stride=(2,2))
+    y = sym.dense(data=x, name='fc', units=30)
     g = graph.create(y)
     g._set_json_attr('ilist', [1,2,3], 'list_int')
     assert g.json_attr('ilist') == [1,2,3]
 
-def test_order_mutation_pass():
-    x = sym.Variable('x')
-    y = sym.conv2d(data=x, name='conv', dev='gpu')
-    y = sym.add(y, x, name='add1')
-    # write after read
-    z = sym.assign(x, y, name='assign')
-    # read after write
-    t = sym.add(y, x, name='add2')
-    g = graph.create(sym.Group([t, z]))
-    jgraph = json.loads(g.apply(['OrderMutation', 'SaveJSON']).json_attr('json'))
-    jnodes = jgraph['nodes']
-    nindex = {n['name']: i for i, n in enumerate(jnodes)}
-    assert nindex['assign'] in jnodes[nindex['add2']]['control_deps']
-    assert nindex['conv'] in jnodes[nindex['assign']]['control_deps']
-    assert nindex['add1'] in jnodes[nindex['assign']]['control_deps']
-    assert jnodes[nindex['assign']]['inputs'][0][2] == 1
-
 def test_list_args():
     x = sym.Variable('x')
     z = sym.Variable('z')
-    y = sym.conv2d(data=x, name='conv', dev='gpu')
-    y = sym.add(y, z, name='add1')
-    # write after read
-    z = sym.assign(x, y, name='assign')
-    assert z.list_input_names('read_only') == ['conv_weight', 'z']
-    assert z.list_input_names('aux_state') == ['x']
+    y = sym.dense(data=x, name='fc', units=30)
+    y = sym.elemwise_add(y, z, name='add1')
 
 def test_infer_shape():
-    x = sym.Variable('x', shape=(4, 2))
-    y = sym.add(x, x, name='add1')
-    y = sym.reshape(y, target=(2, 4), name="reshape1")
+    x = sym.Variable('x', shape=(2, 4, 2))
+    y = sym.elemwise_add(x, x, name='add1')
+    y = sym.flatten(y, name="flatten")
     g = graph.create(y)
     g._set_json_attr("shape_attr_key", "shape")
     g = g.apply('InferShape')
@@ -70,28 +48,28 @@ def test_infer_shape():
     jnodes = jgraph['nodes']
     jnode_row_ptr = jgraph['node_row_ptr']
     nindex = {n['name']: i for i, n in enumerate(jnodes)}
-    assert g.json_attr('shape')[jnode_row_ptr[nindex["reshape1"]]] == [2, 4]
-    assert g.json_attr('shape')[jnode_row_ptr[nindex["add1"]]] == [4, 2]
+    assert g.json_attr('shape')[jnode_row_ptr[nindex["flatten"]]] == [2, 8]
+    assert g.json_attr('shape')[jnode_row_ptr[nindex["add1"]]] == [2, 4, 2]
 
 def test_infer_shape_known_partial():
-    x = sym.Variable('x', shape=(4, 2))
-    y = sym.add(x, x, name='add1')
-    y = sym.reshape(y, target=(2, 4), name="reshape1")
+    x = sym.Variable('x')
+    y = sym.elemwise_add(x, x, name='add1')
+    y = sym.flatten(y, name="flatten1")
     g = graph.create(y)
     jgraph = json.loads(g.apply('SaveJSON').json_attr('json'))
-    shape = [[4, 2], [] , []]
+    shape = [[2, 4, 2], [] , []]
     g._set_json_attr("shape", shape, 'list_shape')
     g = g.apply("InferShape")
     jnodes = jgraph['nodes']
     jnode_row_ptr = jgraph['node_row_ptr']
     nindex = {n['name']: i for i, n in enumerate(jnodes)}
-    assert g.json_attr('shape')[jnode_row_ptr[nindex["reshape1"]]] == [2, 4]
-    assert g.json_attr('shape')[jnode_row_ptr[nindex["add1"]]] == [4, 2]
+    assert g.json_attr('shape')[jnode_row_ptr[nindex["flatten1"]]] == [2, 8]
+    assert g.json_attr('shape')[jnode_row_ptr[nindex["add1"]]] == [2, 4, 2]
 
 def test_infer_type():
     x = sym.Variable('x', dtype=0)
-    y = sym.add(x, x, name='add1')
-    y = sym.cast(y, dtype=1, name="cast1")
+    y = sym.elemwise_add(x, x, name='add1')
+    y = sym.cast(y, dtype="float64", name="cast1")
     g = graph.create(y)
     g._set_json_attr("dtype_attr_key", "dtype")
     g = g.apply('InferType')
@@ -102,31 +80,12 @@ def test_infer_type():
     assert g.json_attr('dtype')[jnode_row_ptr[nindex["cast1"]]] == 1
     assert g.json_attr('dtype')[jnode_row_ptr[nindex["add1"]]] == 0
 
-def test_place_device():
-    x = sym.Variable('x', device_group="stage1")
-    y = sym.add(x, x, name='add1')
-    y = sym.cast(y, dtype=1, name="cast1")
-    z = sym.add(y, y, device_group="stage2", name="add2")
-    z = sym.add(z, sym.exp(y, device_group="stage2"),  name="add3")
-    g = graph.create(z)
-    g._set_json_attr("device_group_attr_key", "device_group")
-    g._set_json_attr("device_assign_map", {"stage1": 0, "stage2" : 1}, "dict_str_int")
-    g._set_json_attr("device_copy_op", "cross_device_copy")
-    g = g.apply("PlaceDevice")
-    jgraph = json.loads(g.apply('SaveJSON').json_attr('json'))
-    jnodes = jgraph['nodes']
-    jnode_row_ptr = jgraph['node_row_ptr']
-    nindex = {n['name']: i for i, n in enumerate(jnodes)}
-    assert g.json_attr('device')[jnode_row_ptr[nindex["add2"]]] == 1
-    assert g.json_attr('device')[jnode_row_ptr[nindex["add3"]]] == 1
-    assert g.json_attr('device')[jnode_row_ptr[nindex["cast1"]]] == 0
-
 def test_plan_memory():
     x = sym.Variable('x', shape=(4, 2))
-    x2 = sym.add(x, x, name='addk')
-    y = sym.reshape(x2, target=(2, 4), name="reshapek")
-    y = sym.add(y, x2, name="add2")
-    y = sym.add(y, y)
+    x2 = sym.elemwise_add(x, x, name='addk')
+    y = sym.flatten(x2, name="reshapek")
+    y = sym.elemwise_add(y, x2, name="add2")
+    y = sym.elemwise_add(y, y)
     g = graph.create(y)
     g._set_json_attr("shape_attr_key", "shape")
     g = g.apply(["InferShape", "InferType", "PlanMemory"])
@@ -143,12 +102,10 @@ def test_plan_memory():
 
 if __name__ == "__main__":
     test_json_pass_with_attr()
-    test_order_mutation_pass()
     test_graph_json_attr()
     test_json_pass()
     test_infer_shape()
     test_infer_shape_known_partial()
     test_infer_type()
-    test_place_device()
     test_plan_memory()
     test_list_args()
diff --git a/nnvm/tests/python/test_symbol.py b/nnvm/tests/python/test_symbol.py
index b671c70199c0..4a8ae58c9cf6 100644
--- a/nnvm/tests/python/test_symbol.py
+++ b/nnvm/tests/python/test_symbol.py
@@ -3,17 +3,13 @@
 
 def test_dense():
     x = sym.Variable('x')
-    y = sym.dense(x)
-    assert y.list_input_names() == ['x']
-
-
-
-
+    y = sym.dense(x, units=30, name="fc")
+    assert y.list_input_names() == ["x", "fc_weight", "fc_bias"]
 
 def test_compose():
     x = sym.Variable('x')
     z = sym.Variable('z')
-    y = sym.exp(sym.add(x, x, name='add', gpu=2),
+    y = sym.exp(sym.elemwise_add(x, x, name='add', gpu=2),
                 name='exp', gpu=1, attr={"kk": "1"})
 
     assert y.list_input_names() == ['x']
@@ -25,24 +21,12 @@ def test_compose():
 
 def test_default_input():
     x = sym.Variable('x')
-    y = sym.conv2d(data=x, name='conv')
-    assert y.list_input_names() == ['x', 'conv_weight']
+    y = sym.dense(data=x, units=30, name='fc', use_bias=False)
+    assert y.list_input_names() == ['x', 'fc_weight']
     tname = [z.list_output_names()[0] for z in y.list_input_variables()]
     assert tname == y.list_input_names()
     try:
-        z = sym.add(x)
-        assert False
-    except NNVMError:
-        pass
-
-def test_mutate_input():
-    x = sym.Variable('x')
-    y = sym.conv2d(data=x, name='conv')
-    z = sym.assign(x, y)
-    t = sym.add(z, x)
-
-    try:
-        z = sym.assign(z, z)
+        z = sym.elemwise_add(x)
         assert False
     except NNVMError:
         pass
@@ -50,7 +34,7 @@ def test_mutate_input():
 def test_copy():
     x = sym.Variable('x')
     z = sym.Variable('z')
-    y = sym.exp(sym.add(x, x, name='add', gpu=2),
+    y = sym.exp(sym.elemwise_add(x, x, name='add', gpu=2),
                 name='exp', gpu=1, attr={"kk": "1"})
     assert y.__copy__().debug_str() == y.debug_str()
 
@@ -62,18 +46,8 @@ def test_op_name():
     op_func = sym.__dict__[op_name]
     z = op_func(x)
 
-
-def test_control_dep():
-    x = sym.Variable('x')
-    y = sym.conv2d(data=x, name='conv')
-    z = sym.assign(x, y)
-    t = sym.add(x, x)
-    t._add_control_deps([z, y])
-
 if __name__ == "__main__":
     test_op_name()
     test_copy()
     test_default_input()
     test_compose()
-    test_mutate_input()
-    test_control_dep()
diff --git a/nnvm/tests/python/test_top_level1.py b/nnvm/tests/python/test_top_level1.py
index 45e26d5cdc9d..47e022469a5c 100644
--- a/nnvm/tests/python/test_top_level1.py
+++ b/nnvm/tests/python/test_top_level1.py
@@ -1,18 +1,35 @@
 import nnvm.symbol as sym
-from nnvm import NNVMError
 
-def test_dense():
+def test_fullc():
     x = sym.Variable('x')
-    y = sym.dense(x, units=3, name="dense")
-    assert y.list_input_names() == ['x', 'dense_weight', 'dense_bias']
+    x1 = sym.dense(x, units=3, name="dense")
+    x2 = sym.flatten(x1)
+    x3 = sym.softmax(x2)
+    assert x2.list_input_names() == ['x', 'dense_weight', 'dense_bias']
 
-def test_concat():
+def test_concatenate():
     x = sym.Variable('x')
     y = sym.Variable('y')
-    y = sym.concat(x, y)
+    y = sym.concatenate(x, y)
     assert y.list_input_names() == ['x', 'y']
 
+def test_unary():
+    x = sym.Variable('x')
+    x = sym.exp(x)
+    x = sym.log(x)
+    x = sym.sigmoid(x)
+    x = sym.tanh(x)
+    assert x.list_input_names() == ['x']
+
+def test_batchnorm():
+    x = sym.Variable('x')
+    x = sym.batch_norm(x, name="bn")
+    assert x.list_input_names() == [
+        "x", "bn_gamma", "bn_beta", "bn_moving_mean", "bn_moving_var"]
+
 
 if __name__ == "__main__":
-    test_concat()
-    test_dense()
+    test_concatenate()
+    test_fullc()
+    test_unary()
+    test_batchnorm()

From afa5164ed6ef15ec7d92fd20768a58fcb0c94b73 Mon Sep 17 00:00:00 2001
From: Eric Junyuan Xie <piiswrong@users.noreply.github.com>
Date: Sun, 10 Sep 2017 22:15:59 -0700
Subject: [PATCH 462/949] improve infer shape/type error message (#4)

* improve infer shape/type error message

* fix dense infer shape
---
 nnvm/src/top/nn.cc       |  29 ++++++-----
 nnvm/src/top/op_common.h | 108 ++++++++++++++++++++++++---------------
 nnvm/src/top/tensor.cc   |   8 +--
 3 files changed, 86 insertions(+), 59 deletions(-)

diff --git a/nnvm/src/top/nn.cc b/nnvm/src/top/nn.cc
index 4d99bbc1389c..ff00fcda41e3 100644
--- a/nnvm/src/top/nn.cc
+++ b/nnvm/src/top/nn.cc
@@ -35,20 +35,23 @@ inline bool DenseInferShape(const nnvm::NodeAttrs& attrs,
     CHECK_EQ(in_shape->size(), 2U) << "Input:[data, weight]";
   }
   CHECK_EQ(out_shape->size(), 1U);
-  TShape dshape = (*in_shape)[DenseParam::kData];
-  TShape oshape = (*out_shape)[0];
-  // require data to be known
-  if (dshape.ndim() ==  0) return false;
-  dim_t num_input;
-  num_input = dshape.ProdShape(1, dshape.ndim());
-  SHAPE_ASSIGN_CHECK(*in_shape, DenseParam::kWeight, TShape({param.units, num_input}));
-  if (param.use_bias) {
-    SHAPE_ASSIGN_CHECK(*in_shape, DenseParam::kBias, TShape({param.units}));
+  if ((*out_shape)[0].ndim() != 0) {
+    // reverse infer
+    TShape dshape = (*out_shape)[0];
+    dshape[dshape.ndim() - 1] = 0;
+    NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, DenseParam::kData, dshape);
+  }
+  dim_t num_inputs = 0;
+  if ((*in_shape)[DenseParam::kData].ndim() != 0) {
+    TShape oshape = (*in_shape)[DenseParam::kData];
+    num_inputs = oshape[oshape.ndim() - 1];
+    oshape[oshape.ndim() - 1] = param.units;
+    NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, oshape);
   }
-  SHAPE_ASSIGN_CHECK(*out_shape, 0, TShape({dshape[0], param.units}));
-  if (oshape.ndim() != 0) {
-    dshape[0] = oshape[0];
-    SHAPE_ASSIGN_CHECK(*in_shape, DenseParam::kData, dshape);
+  NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, DenseParam::kWeight,
+                          TShape({param.units, num_inputs}));
+  if (param.use_bias) {
+    NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, DenseParam::kBias, TShape({param.units}));
   }
   return true;
 }
diff --git a/nnvm/src/top/op_common.h b/nnvm/src/top/op_common.h
index b3af62401da6..14ec45bf7457 100644
--- a/nnvm/src/top/op_common.h
+++ b/nnvm/src/top/op_common.h
@@ -13,29 +13,6 @@
 
 namespace nnvm {
 namespace top {
-
-/*! \brief exception throwed by InferShape error */
-struct InferShapeError : public dmlc::Error {
-  /*! \brief analyze message */
-  std::string msg;
-  /*! \brief corresponding input index */
-  int index;
-  // constructor
-  InferShapeError(const std::string& msg_, int index)
-    : dmlc::Error(msg_), msg(msg_), index(index) {}
-};
-
-/*! \brief exception throwed by InferShape error */
-struct InferTypeError : public dmlc::Error {
-  /*! \brief analyze message */
-  std::string msg;
-  /*! \brief corresponding input index */
-  int index;
-  // constructor
-  InferTypeError(const std::string& msg_, int index)
-    : dmlc::Error(msg_), msg(msg_), index(index) {}
-};
-
 /*!
  * \brief Parse keyword arguments as PType arguments and save to parsed
  * \tparam PType the arameter type.
@@ -128,41 +105,88 @@ inline bool type_assign(int *y, const int& x) {
   return true;
 }
 
+template<typename AttrType>
+inline std::string attr_assign_error_msg(const NodeAttrs& attrs,
+                                         int index, bool is_input,
+                                         const AttrType& expected,
+                                         const AttrType& actual,
+                                         const char* attr_name) {
+  static const auto& flist_inputs = Op::GetAttr<FListInputNames>("FListInputNames");
+  static const auto& flist_outputs = Op::GetAttr<FListOutputNames>("FListOutputNames");
+  const auto& flist = is_input ? flist_inputs : flist_outputs;
+  std::string name;
+  if (flist.count(attrs.op)) {
+    name = flist[attrs.op](attrs)[index];
+  } else {
+    name = (is_input ? "data" : "output") + std::to_string(index);
+  }
+  std::ostringstream msg;
+  msg << "Operator " << attrs.op->name << "(";
+  for (const auto& kv : attrs.dict) msg << kv.first << "=" << kv.second << ", ";
+  msg << "name=" << attrs.name << ") expects " << name << "\'s " << attr_name
+      << " to be " << expected << ", but got " << actual << ".";
+  return msg.str();
+}
+
 /*!
  * \brief macro assign shape to out if out is unknown otherwise check consistency
  *  Use macro so we can see the error file more clearly
- * \param shape_array the shape array to store the result
+ * \param inputs the shape array to store the result
  * \param index the index of in the array
  * \param shape the inferred shape
  */
-#define SHAPE_ASSIGN_CHECK(shape_array, index, shape)                   \
-  {                                                                     \
-    if (!shape_assign(&(shape_array)[index], TShape(shape))) {          \
-      std::ostringstream os;                                            \
-      os << "Shape inconsistent, Provided=" << (shape_array)[index] << ',' \
-         << " inferred shape=" << shape;                                \
-      throw InferShapeError(os.str(), index);                           \
-    }                                                                   \
+#define NNVM_ASSIGN_INPUT_SHAPE(attrs, inputs, index, shape)             \
+  {                                                                      \
+    if (!shape_assign(&(inputs)[index], TShape(shape))) {                \
+      LOG(FATAL) << attr_assign_error_msg(attrs, index, true, shape,     \
+                                          (inputs)[index], "shape");     \
+    }                                                                    \
+  }
+
+/*!
+ * \brief macro assign shape to out if out is unknown otherwise check consistency
+ *  Use macro so we can see the error file more clearly
+ * \param inputs the shape array to store the result
+ * \param index the index of in the array
+ * \param shape the inferred shape
+ */
+#define NNVM_ASSIGN_OUTPUT_SHAPE(attrs, outputs, index, shape)           \
+  {                                                                      \
+    if (!shape_assign(&(outputs)[index], TShape(shape))) {               \
+      LOG(FATAL) << attr_assign_error_msg(attrs, index, false, shape,    \
+                                          (outputs)[index], "shape");    \
+    }                                                                    \
   }
 
 /*!
  * \brief macro assign type to out if out is unknown (-1) otherwise check consistency
  *  Use macro so we can see the error file more clearly
- * \param type_array the type array to store the result
+ * \param inputs the type array to store the result
  * \param index the index of in the array
  * \param type the inferred type
  */
-#define TYPE_ASSIGN_CHECK(type_array, index, type)                      \
-  {                                                                     \
-    if (!type_assign(&(type_array)[index], type)) {                     \
-      std::ostringstream os;                                            \
-      os << "Type inconsistent, Provided="                              \
-         << type_string((type_array)[index]) << ','                     \
-         << " inferred type=" << type_string(type);                     \
-      throw InferTypeError(os.str(), index);                            \
-    }                                                                   \
+#define NNVM_ASSIGN_INPUT_TYPE(attrs, inputs, index, type)               \
+  {                                                                      \
+    if (!type_assign(&(inputs)[index], type)) {                          \
+      LOG(FATAL) << attr_assign_error_msg(attrs, index, true, type,      \
+                                          (inputs)[index], "type");      \
+    }                                                                    \
   }
 
+/*!
+ * \brief macro assign type to out if out is unknown (-1) otherwise check consistency
+ *  Use macro so we can see the error file more clearly
+ * \param inputs the type array to store the result
+ * \param index the index of in the array
+ * \param type the inferred type
+ */
+#define NNVM_ASSIGN_OUTPUT_TYPE(attrs, outputs, index, type)             \
+  {                                                                      \
+    if (!type_assign(&(outputs)[index], type)) {                         \
+      LOG(FATAL) << attr_assign_error_msg(attrs, index, false, type,     \
+                                          (outputs)[index], "type");     \
+    }                                                                    \
+  }
 
 // simply return the shape as same
 inline bool SameShape(const NodeAttrs& attrs,
diff --git a/nnvm/src/top/tensor.cc b/nnvm/src/top/tensor.cc
index c006ff4b5d57..5561cefcefbd 100644
--- a/nnvm/src/top/tensor.cc
+++ b/nnvm/src/top/tensor.cc
@@ -64,7 +64,7 @@ inline bool FlattenInferShape(const nnvm::NodeAttrs& attrs,
   for (uint32_t i = 1; i < dshape.ndim(); ++i) {
     target_dim *= dshape[i];
   }
-  SHAPE_ASSIGN_CHECK(*out_attrs, 0, TShape({dshape[0], target_dim}));
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, 0, TShape({dshape[0], target_dim}));
   return true;
 }
 
@@ -130,11 +130,11 @@ inline bool ConcatenateInferShape(const nnvm::NodeAttrs& attrs,
   if (dshape.ndim() == 0) return false;
 
   for (size_t i = 0; i < in_shape->size(); ++i) {
-    SHAPE_ASSIGN_CHECK(*in_shape, i, dshape);
+    NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, i, dshape);
   }
 
   if (!has_zero) dshape[param.axis] = size;
-  SHAPE_ASSIGN_CHECK(*out_shape, 0, dshape);
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, dshape);
   return dshape.Size() != 0;
 }
 
@@ -210,7 +210,7 @@ inline bool CastInferType(const nnvm::NodeAttrs& attrs,
                           std::vector<int> *out_attrs) {
   const CastParam& param = nnvm::get<CastParam>(attrs.parsed);
   CHECK_EQ(out_attrs->size(), 1U);
-  TYPE_ASSIGN_CHECK(*out_attrs, 0, param.dtype);
+  NNVM_ASSIGN_OUTPUT_TYPE(attrs, *out_attrs, 0, param.dtype);
   return true;
 }
 

From 1f26eded381dcbaf8e5e6439af11e243cb116f75 Mon Sep 17 00:00:00 2001
From: Eric Junyuan Xie <piiswrong@users.noreply.github.com>
Date: Mon, 11 Sep 2017 21:05:16 -0700
Subject: [PATCH 463/949] add level2 ops (#6)

---
 nnvm/include/nnvm/top/nn.h | 153 +++++++++++++++++++++++++
 nnvm/src/top/nn.cc         | 225 +++++++++++++++++++++++++++++++++++--
 2 files changed, 369 insertions(+), 9 deletions(-)

diff --git a/nnvm/include/nnvm/top/nn.h b/nnvm/include/nnvm/top/nn.h
index 1c4e70696911..c2ab7b01e057 100644
--- a/nnvm/include/nnvm/top/nn.h
+++ b/nnvm/include/nnvm/top/nn.h
@@ -12,6 +12,20 @@
 namespace nnvm {
 namespace top {
 
+enum LayoutFlag {
+  kNCHW = 0,
+  kNHWC,
+  kCHWN,
+
+  kNCW = 1 << 3,
+  kNWC,
+  kCWN,
+
+  kNCDHW = 1 << 5,
+  kNDHWC,
+  kCDHWN
+};
+
 struct DenseParam : public dmlc::Parameter<DenseParam> {
   int units;
   bool use_bias;
@@ -85,6 +99,145 @@ struct LogSoftmaxParam : public dmlc::Parameter<LogSoftmaxParam> {
   }
 };
 
+struct Conv2DParam : public dmlc::Parameter<Conv2DParam> {
+  int channels;
+  TShape kernel_size;
+  TShape strides;
+  TShape padding;
+  TShape dilation;
+  int groups;
+  int layout;
+  bool use_bias;
+
+  DMLC_DECLARE_PARAMETER(Conv2DParam) {
+    DMLC_DECLARE_FIELD(channels)
+      .describe("The dimensionality of the output space"
+                "i.e. the number of output channels in the convolution.");
+    DMLC_DECLARE_FIELD(kernel_size)
+      .describe("Specifies the dimensions of the convolution window.");
+    DMLC_DECLARE_FIELD(strides).set_default(TShape({1, 1}))
+      .describe("Specifies the strides of the convolution.");
+    DMLC_DECLARE_FIELD(padding).set_default(TShape({0, 0}))
+      .describe("If padding is non-zero, then the input is implicitly zero-padded"
+                "on both sides for padding number of points");
+    DMLC_DECLARE_FIELD(dilation).set_default(TShape({1, 1}))
+      .describe("Specifies the dilation rate to use for dilated convolution.");
+    DMLC_DECLARE_FIELD(groups).set_default(1)
+      .describe("Controls the connections between inputs and outputs."
+                "At groups=1, all inputs are convolved to all outputs."
+                "At groups=2, the operation becomes equivalent to having two convolution"
+                "layers side by side, each seeing half the input channels, and producing"
+                "half the output channels, and both subsequently concatenated.");
+    DMLC_DECLARE_FIELD(layout)
+      .add_enum("NCHW", kNCHW)
+      .add_enum("NHWC", kNHWC)
+      .set_default(kNCHW)
+      .describe("Dimension ordering of data and weight. Can be 'NCHW', 'NHWC', etc."
+                "'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
+                "dimensions respectively. Convolution is applied on the 'H' and"
+                "'W' dimensions.");
+    DMLC_DECLARE_FIELD(use_bias).set_default(true)
+      .describe("Whether the layer uses a bias vector.");
+  }
+};
+
+
+struct Conv2DTransposeParam : public dmlc::Parameter<Conv2DTransposeParam> {
+  int channels;
+  TShape kernel_size;
+  TShape strides;
+  TShape padding;
+  TShape output_padding;
+  TShape dilation;
+  int groups;
+  int layout;
+  bool use_bias;
+
+  DMLC_DECLARE_PARAMETER(Conv2DTransposeParam) {
+    DMLC_DECLARE_FIELD(channels)
+      .describe("The dimensionality of the output space"
+                "i.e. the number of output channels in the convolution.");
+    DMLC_DECLARE_FIELD(kernel_size)
+      .describe("Specifies the dimensions of the convolution window.");
+    DMLC_DECLARE_FIELD(strides).set_default(TShape({1, 1}))
+      .describe("Specifies the strides of the convolution.");
+    DMLC_DECLARE_FIELD(output_padding).set_default(TShape({0, 0}))
+      .describe("Zero-padding added to one side of the output.");
+    DMLC_DECLARE_FIELD(padding).set_default(TShape({0, 0}))
+      .describe("If padding is non-zero, then the input is implicitly zero-padded"
+                "on both sides for padding number of points");
+    DMLC_DECLARE_FIELD(dilation).set_default(TShape({1, 1}))
+      .describe("Specifies the dilation rate to use for dilated convolution.");
+    DMLC_DECLARE_FIELD(groups).set_default(1)
+      .describe("Controls the connections between inputs and outputs."
+                "At groups=1, all inputs are convolved to all outputs."
+                "At groups=2, the operation becomes equivalent to having two convolution"
+                "layers side by side, each seeing half the input channels, and producing"
+                "half the output channels, and both subsequently concatenated.");
+    DMLC_DECLARE_FIELD(layout)
+      .add_enum("NCHW", kNCHW)
+      .add_enum("NHWC", kNHWC)
+      .set_default(kNCHW)
+      .describe("Dimension ordering of data and weight. Can be 'NCHW', 'NHWC', etc."
+                "'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
+                "dimensions respectively. Convolution is applied on the 'H' and"
+                "'W' dimensions.");
+    DMLC_DECLARE_FIELD(use_bias).set_default(true)
+      .describe("Whether the layer uses a bias vector.");
+  }
+};
+
+struct Pool2DParam : public dmlc::Parameter<Pool2DParam> {
+  TShape pool_size;
+  TShape strides;
+  TShape padding;
+  int groups;
+  int layout;
+  bool ceil_mode;
+
+  DMLC_DECLARE_PARAMETER(Pool2DParam) {
+    DMLC_DECLARE_FIELD(pool_size)
+      .describe("Size of the pooling windows..");
+    DMLC_DECLARE_FIELD(strides).set_default(TShape({1, 1}))
+      .describe("Specifies the strides of the convolution.");
+    DMLC_DECLARE_FIELD(padding).set_default(TShape({0, 0}))
+      .describe("If padding is non-zero, then the input is implicitly zero-padded"
+                "on both sides for padding number of points");
+    DMLC_DECLARE_FIELD(groups).set_default(1)
+      .describe("Controls the connections between inputs and outputs."
+                "At groups=1, all inputs are convolved to all outputs."
+                "At groups=2, the operation becomes equivalent to having two convolution"
+                "layers side by side, each seeing half the input channels, and producing"
+                "half the output channels, and both subsequently concatenated.");
+    DMLC_DECLARE_FIELD(layout)
+      .add_enum("NCHW", kNCHW)
+      .add_enum("NHWC", kNHWC)
+      .set_default(kNCHW)
+      .describe("Dimension ordering of data and weight. Can be 'NCHW', 'NHWC', etc."
+                "'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
+                "dimensions respectively. Convolution is applied on the 'H' and"
+                "'W' dimensions.");
+    DMLC_DECLARE_FIELD(ceil_mode).set_default(false)
+      .describe("When true, will use ceil instead of floor to compute the output shape.");
+  }
+};
+
+
+struct GlobalPool2DParam : public dmlc::Parameter<GlobalPool2DParam> {
+  int layout;
+
+  DMLC_DECLARE_PARAMETER(GlobalPool2DParam) {
+    DMLC_DECLARE_FIELD(layout)
+      .add_enum("NCHW", kNCHW)
+      .add_enum("NHWC", kNHWC)
+      .set_default(kNCHW)
+      .describe("Dimension ordering of data and weight. Can be 'NCHW', 'NHWC', etc."
+                "'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
+                "dimensions respectively. Convolution is applied on the 'H' and"
+                "'W' dimensions.");
+  }
+};
+
 }  // namespace top
 }  // namespace nnvm
 
diff --git a/nnvm/src/top/nn.cc b/nnvm/src/top/nn.cc
index ff00fcda41e3..6a26271f37ec 100644
--- a/nnvm/src/top/nn.cc
+++ b/nnvm/src/top/nn.cc
@@ -16,8 +16,9 @@ namespace top {
 // dense
 DMLC_REGISTER_PARAMETER(DenseParam);
 
-inline std::vector<std::string> DenseListInputNames(const NodeAttrs& attrs) {
-  const DenseParam& param = nnvm::get<DenseParam>(attrs.parsed);
+template<typename ParamType>
+inline std::vector<std::string> UseBiasListInputNames(const NodeAttrs& attrs) {
+  const ParamType& param = nnvm::get<ParamType>(attrs.parsed);
   if (param.use_bias) {
     return {"data", "weight", "bias"};
   } else {
@@ -25,6 +26,12 @@ inline std::vector<std::string> DenseListInputNames(const NodeAttrs& attrs) {
   }
 }
 
+template<typename ParamType>
+inline uint32_t UseBiasNumInputs(const NodeAttrs& attrs) {
+  const ParamType& param = get<ParamType>(attrs.parsed);
+  return param.use_bias ? 3 : 2;
+}
+
 inline bool DenseInferShape(const nnvm::NodeAttrs& attrs,
                             std::vector<TShape> *in_shape,
                             std::vector<TShape> *out_shape) {
@@ -35,8 +42,8 @@ inline bool DenseInferShape(const nnvm::NodeAttrs& attrs,
     CHECK_EQ(in_shape->size(), 2U) << "Input:[data, weight]";
   }
   CHECK_EQ(out_shape->size(), 1U);
+  // reverse infer
   if ((*out_shape)[0].ndim() != 0) {
-    // reverse infer
     TShape dshape = (*out_shape)[0];
     dshape[dshape.ndim() - 1] = 0;
     NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, DenseParam::kData, dshape);
@@ -57,7 +64,7 @@ inline bool DenseInferShape(const nnvm::NodeAttrs& attrs,
 }
 
 NNVM_REGISTER_OP(dense)
-.NNVM_DESCRIBE(R"code(Applies a linear transformation: :math:`Y = XW^T + b`.
+.describe(R"code(Applies a linear transformation: :math:`Y = XW^T + b`.
 
 - **data**: `(x1, x2, ..., xn, input_dim)`
 - **weight**: `(units, input_dim)`
@@ -75,11 +82,8 @@ If ``use_bias`` is set to be false, then the ``bias`` term is ignored.
 .add_arguments(DenseParam::__FIELDS__())
 .set_attr_parser(ParamParser<DenseParam>)
 .set_num_outputs(1)
-.set_num_inputs([](const NodeAttrs& attrs) {
-    const DenseParam& param = get<DenseParam>(attrs.parsed);
-    return param.use_bias ? 3 : 2;
-  })
-.set_attr<FListInputNames>("FListInputNames", DenseListInputNames)
+.set_num_inputs(UseBiasNumInputs<DenseParam>)
+.set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<DenseParam>)
 .set_attr<FInferShape>("FInferShape", DenseInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
 .set_support_level(1);
@@ -209,5 +213,208 @@ NNVM_REGISTER_OP(log_softmax)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
 .set_support_level(1);
 
+
+DMLC_REGISTER_PARAMETER(Conv2DParam);
+
+inline bool Conv2DInferShape(const nnvm::NodeAttrs& attrs,
+                             std::vector<TShape> *in_shape,
+                             std::vector<TShape> *out_shape) {
+  const Conv2DParam& param = nnvm::get<Conv2DParam>(attrs.parsed);
+  if (param.use_bias) {
+    CHECK_EQ(in_shape->size(), 3U) << "Input:[data, weight, bias]";
+  } else {
+    CHECK_EQ(in_shape->size(), 2U) << "Input:[data, weight]";
+  }
+  CHECK_EQ(out_shape->size(), 1U);
+  return true;
+}
+
+NNVM_REGISTER_OP(conv2d)
+.describe(R"code(2D convolution layer (e.g. spatial convolution over images).
+
+This layer creates a convolution kernel that is convolved
+with the layer input to produce a tensor of
+outputs. If `use_bias` is True,
+a bias vector is created and added to the outputs.
+
+- **data**: This depends on the `layout` parameter. Input is 4D array of shape
+            (batch_size, in_channels, height, width) if `layout` is `NCHW`.
+- **weight**: (channels, in_channels, kernel_size[0], kernel_size[1])
+- **bias**: (channels,)
+- **out**:  This depends on the `layout` parameter. Output is 4D array of shape
+            (batch_size, channels, out_height, out_width) if `layout` is `NCHW`.
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "4D Tensor", "Input data.")
+.add_argument("weight", "4D Tensor", "Weight matrix.")
+.add_argument("bias", "1D Tensor", "Bias parameter.")
+.add_arguments(Conv2DParam::__FIELDS__())
+.set_attr_parser(ParamParser<Conv2DParam>)
+.set_num_outputs(1)
+.set_num_inputs(UseBiasNumInputs<Conv2DParam>)
+.set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<Conv2DParam>)
+.set_attr<FInferShape>("FInferShape", Conv2DInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
+.set_support_level(2);
+
+
+DMLC_REGISTER_PARAMETER(Conv2DTransposeParam);
+
+inline bool Conv2DTransposeInferShape(const nnvm::NodeAttrs& attrs,
+                             std::vector<TShape> *in_shape,
+                             std::vector<TShape> *out_shape) {
+  const Conv2DTransposeParam& param = nnvm::get<Conv2DTransposeParam>(attrs.parsed);
+  if (param.use_bias) {
+    CHECK_EQ(in_shape->size(), 3U) << "Input:[data, weight, bias]";
+  } else {
+    CHECK_EQ(in_shape->size(), 2U) << "Input:[data, weight]";
+  }
+  CHECK_EQ(out_shape->size(), 1U);
+  return true;
+}
+
+NNVM_REGISTER_OP(conv2d_transpose)
+.describe(R"code(Transposed 2D convolution layer (sometimes called Deconvolution).
+
+The need for transposed convolutions generally arises
+from the desire to use a transformation going in the opposite direction
+of a normal convolution, i.e., from something that has the shape of the
+output of some convolution to something that has the shape of its input
+while maintaining a connectivity pattern that is compatible with
+said convolution.
+
+- **data**: This depends on the `layout` parameter. Input is 4D array of shape
+            (batch_size, in_channels, height, width) if `layout` is `NCHW`.
+- **weight**: (channels, in_channels, kernel_size[0], kernel_size[1])
+- **bias**: (channels,)
+- **out**:  This depends on the `layout` parameter. Output is 4D array of shape
+            (batch_size, channels, out_height, out_width) if `layout` is `NCHW`.
+
+            out_height and out_width are calculated as::
+                out_height = (height-1)*strides[0]-2*padding[0]+kernel_size[0]+output_padding[0]
+                out_width = (width-1)*strides[1]-2*padding[1]+kernel_size[1]+output_padding[1]
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "4D Tensor", "Input data.")
+.add_argument("weight", "4D Tensor", "Weight matrix.")
+.add_argument("bias", "1D Tensor", "Bias parameter.")
+.add_arguments(Conv2DTransposeParam::__FIELDS__())
+.set_attr_parser(ParamParser<Conv2DTransposeParam>)
+.set_num_outputs(1)
+.set_num_inputs(UseBiasNumInputs<Conv2DTransposeParam>)
+.set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<Conv2DTransposeParam>)
+.set_attr<FInferShape>("FInferShape", Conv2DTransposeInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
+.set_support_level(2);
+
+
+DMLC_REGISTER_PARAMETER(Pool2DParam);
+
+inline bool Pool2DInferShape(const nnvm::NodeAttrs& attrs,
+                           std::vector<TShape> *in_shape,
+                           std::vector<TShape> *out_shape) {
+  const Pool2DParam& param = nnvm::get<Pool2DParam>(attrs.parsed);
+  CHECK_EQ(in_shape->size(), 1U);
+  CHECK_EQ(out_shape->size(), 1U);
+  return true;
+}
+
+NNVM_REGISTER_OP(max_pool2d)
+.describe(R"code(Max pooling operation for one dimensional data.
+
+- **data**: This depends on the `layout` parameter. Input is 4D array of shape
+            (batch_size, channels, height, width) if `layout` is `NCHW`.
+- **out**: This depends on the `layout` parameter. Output is 4D array of shape
+           (batch_size, channels, out_height, out_width)  if `layout` is `NCHW`.
+           out_height and out_width are calculated as::
+               out_height = floor((height+2*padding[0]-pool_size[0])/strides[0])+1
+               out_width = floor((width+2*padding[1]-pool_size[1])/strides[1])+1
+           When `ceil_mode` is `True`, ceil will be used instead of floor in this
+           equation.
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "4D Tensor", "Input data.")
+.add_arguments(Pool2DParam::__FIELDS__())
+.set_attr_parser(ParamParser<Pool2DParam>)
+.set_num_outputs(1)
+.set_num_inputs(1)
+.set_attr<FInferShape>("FInferShape", Pool2DInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_support_level(2);
+
+
+NNVM_REGISTER_OP(avg_pool2d)
+.describe(R"code(Average pooling operation for one dimensional data.
+
+- **data**: This depends on the `layout` parameter. Input is 4D array of shape
+            (batch_size, channels, height, width) if `layout` is `NCHW`.
+- **out**: This depends on the `layout` parameter. Output is 4D array of shape
+           (batch_size, channels, out_height, out_width)  if `layout` is `NCHW`.
+           out_height and out_width are calculated as::
+               out_height = floor((height+2*padding[0]-pool_size[0])/strides[0])+1
+               out_width = floor((width+2*padding[1]-pool_size[1])/strides[1])+1
+           When `ceil_mode` is `True`, ceil will be used instead of floor in this
+           equation.
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "4D Tensor", "Input data.")
+.add_arguments(Pool2DParam::__FIELDS__())
+.set_attr_parser(ParamParser<Pool2DParam>)
+.set_num_outputs(1)
+.set_num_inputs(1)
+.set_attr<FInferShape>("FInferShape", Pool2DInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_support_level(2);
+
+
+DMLC_REGISTER_PARAMETER(GlobalPool2DParam);
+
+inline bool GlobalPool2DInferShape(const nnvm::NodeAttrs& attrs,
+                                   std::vector<TShape> *in_shape,
+                                   std::vector<TShape> *out_shape) {
+  const GlobalPool2DParam& param = nnvm::get<GlobalPool2DParam>(attrs.parsed);
+  CHECK_EQ(in_shape->size(), 1U);
+  CHECK_EQ(out_shape->size(), 1U);
+  return true;
+}
+
+NNVM_REGISTER_OP(global_max_pool2d)
+.describe(R"code(Global max pooling operation for 2D data.
+
+- **data**: This depends on the `layout` parameter. Input is 4D array of shape
+            (batch_size, channels, height, width) if `layout` is `NCHW`.
+- **out**: This depends on the `layout` parameter. Output is 4D array of shape
+           (batch_size, channels, 1, 1)  if `layout` is `NCHW`.
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "4D Tensor", "Input data.")
+.add_arguments(GlobalPool2DParam::__FIELDS__())
+.set_attr_parser(ParamParser<GlobalPool2DParam>)
+.set_num_outputs(1)
+.set_num_inputs(1)
+.set_attr<FInferShape>("FInferShape", GlobalPool2DInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_support_level(2);
+
+
+NNVM_REGISTER_OP(global_avg_pool2d)
+.describe(R"code(Global average pooling operation for 2D data.
+
+- **data**: This depends on the `layout` parameter. Input is 4D array of shape
+            (batch_size, channels, height, width) if `layout` is `NCHW`.
+- **out**: This depends on the `layout` parameter. Output is 4D array of shape
+           (batch_size, channels, 1, 1)  if `layout` is `NCHW`.
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "4D Tensor", "Input data.")
+.add_arguments(GlobalPool2DParam::__FIELDS__())
+.set_attr_parser(ParamParser<GlobalPool2DParam>)
+.set_num_outputs(1)
+.set_num_inputs(1)
+.set_attr<FInferShape>("FInferShape", GlobalPool2DInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_support_level(2);
+
+
 }  // namespace top
 }  // namespace nnvm

From da251d0cb589b63b4f5d8a8a2979a9b2c845df29 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 12 Sep 2017 11:35:54 -0700
Subject: [PATCH 464/949] [TOP] Level 3 complete (#7)

---
 nnvm/CMakeLists.txt                           |   5 +-
 nnvm/Makefile                                 |   6 +-
 nnvm/include/nnvm/top/nn.h                    |  20 +-
 nnvm/include/nnvm/top/tensor.h                |  18 +
 nnvm/python/nnvm/libinfo.py                   |   2 +-
 nnvm/src/top/{ => nn}/nn.cc                   |  45 ++-
 nnvm/src/top/tensor.cc                        | 231 -------------
 nnvm/src/top/tensor/elemwise.cc               | 156 +++++++++
 nnvm/src/top/tensor/transform.cc              | 319 ++++++++++++++++++
 .../tests/python/{ => unittest}/test_graph.py |   0
 .../tests/python/unittest/test_infer_shape.py |  82 +++++
 .../python/{ => unittest}/test_symbol.py      |   0
 .../python/{ => unittest}/test_top_level1.py  |   0
 nnvm/tests/python/unittest/test_top_level3.py |  23 ++
 nnvm/tests/travis/run_test.sh                 |  16 +-
 15 files changed, 665 insertions(+), 258 deletions(-)
 rename nnvm/src/top/{ => nn}/nn.cc (91%)
 delete mode 100644 nnvm/src/top/tensor.cc
 create mode 100644 nnvm/src/top/tensor/elemwise.cc
 create mode 100644 nnvm/src/top/tensor/transform.cc
 rename nnvm/tests/python/{ => unittest}/test_graph.py (100%)
 create mode 100644 nnvm/tests/python/unittest/test_infer_shape.py
 rename nnvm/tests/python/{ => unittest}/test_symbol.py (100%)
 rename nnvm/tests/python/{ => unittest}/test_top_level1.py (100%)
 create mode 100644 nnvm/tests/python/unittest/test_top_level3.py

diff --git a/nnvm/CMakeLists.txt b/nnvm/CMakeLists.txt
index d40deb5f94f4..c6bbb8f5fa14 100644
--- a/nnvm/CMakeLists.txt
+++ b/nnvm/CMakeLists.txt
@@ -53,7 +53,10 @@ mxnet_source_group("Source\\core"   GLOB "src/core/*.cc")
 mxnet_source_group("Source\\pass"   GLOB "src/pass/*.cc")
 
 
-FILE(GLOB_RECURSE SOURCE "src/*.cc" "src/*.h" "include/*.h")
+FILE(GLOB_RECURSE SOURCE
+   src/c_api/*.cc
+   src/core/*.cc
+   src/pass/*.cc)
 
 if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/dmlc-core/CMakeLists.txt)
   include_directories(${CMAKE_CURRENT_SOURCE_DIR}/dmlc-core/include)
diff --git a/nnvm/Makefile b/nnvm/Makefile
index 7ddeb97c459d..009e3f9eeec8 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -51,10 +51,10 @@ else
 	NO_WHOLE_ARCH= --no-whole-archive
 endif
 
-all: lib/libnnvm.a lib/libnnvm_example.$(SHARED_LIBRARY_SUFFIX)
+all: lib/libnnvm.a lib/libnnvm_top.$(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 = $(wildcard src/top/*.cc, src/top/*/*.cc)
 ALL_OBJ = $(patsubst %.cc, build/%.o, $(SRC))
 TOP_OBJ = $(patsubst %.cc, build/%.o, $(SRC_TOP))
 ALL_DEP = $(ALL_OBJ)
@@ -72,7 +72,7 @@ lib/libnnvm.a: $(ALL_DEP)
 	@mkdir -p $(@D)
 	ar crv $@ $(filter %.o, $?)
 
-lib/libnnvm_example.$(SHARED_LIBRARY_SUFFIX): lib/libnnvm.a ${TOP_OBJ}
+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}
 
diff --git a/nnvm/include/nnvm/top/nn.h b/nnvm/include/nnvm/top/nn.h
index c2ab7b01e057..61d50eb14cb7 100644
--- a/nnvm/include/nnvm/top/nn.h
+++ b/nnvm/include/nnvm/top/nn.h
@@ -54,15 +54,15 @@ struct DropoutParam : public dmlc::Parameter<DropoutParam> {
 
 struct BatchNormParam : public dmlc::Parameter<BatchNormParam> {
   int axis;
-  float epsilon;
-  float momentum;
+  double epsilon;
+  double momentum;
   bool center;
   bool scale;
 
   DMLC_DECLARE_PARAMETER(BatchNormParam) {
     DMLC_DECLARE_FIELD(axis).set_default(1)
       .describe("Specify which shape axis the channel is specified.");
-    DMLC_DECLARE_FIELD(epsilon).set_default(1e-5f)
+    DMLC_DECLARE_FIELD(epsilon).set_default(1e-5)
         .describe("Small float added to variance to avoid dividing by zero.");
     DMLC_DECLARE_FIELD(center).set_default(true)
         .describe("If True, add offset of `beta` to normalized tensor."
@@ -81,21 +81,23 @@ struct BatchNormParam : public dmlc::Parameter<BatchNormParam> {
   static const constexpr int kMovingVariance = 4;
 };
 
+
+// Shared by softmax and log_softmax
 struct SoftmaxParam : public dmlc::Parameter<SoftmaxParam> {
   int axis;
 
   DMLC_DECLARE_PARAMETER(SoftmaxParam) {
     DMLC_DECLARE_FIELD(axis).set_default(-1)
-      .describe("The axis to sum over when computing softmax.");
+        .describe("The axis to sum over when computing softmax.");
   }
 };
 
-struct LogSoftmaxParam : public dmlc::Parameter<LogSoftmaxParam> {
-  int axis;
+struct LeakyReLUParam : public dmlc::Parameter<LeakyReLUParam> {
+  double alpha;
 
-  DMLC_DECLARE_PARAMETER(LogSoftmaxParam) {
-    DMLC_DECLARE_FIELD(axis).set_default(-1)
-      .describe("The axis to sum over when computing softmax.");
+  DMLC_DECLARE_PARAMETER(LeakyReLUParam) {
+    DMLC_DECLARE_FIELD(alpha).set_lower_bound(0.0).set_default(0.25)
+        .describe("slope coefficient for the negative half axis.");
   }
 };
 
diff --git a/nnvm/include/nnvm/top/tensor.h b/nnvm/include/nnvm/top/tensor.h
index 347c7211d724..056a694f90e1 100644
--- a/nnvm/include/nnvm/top/tensor.h
+++ b/nnvm/include/nnvm/top/tensor.h
@@ -40,6 +40,24 @@ struct CastParam : public dmlc::Parameter<CastParam> {
   }
 };
 
+struct ReshapeParam : public dmlc::Parameter<ReshapeParam> {
+  Tuple<int64_t> shape;
+
+  DMLC_DECLARE_PARAMETER(ReshapeParam) {
+    DMLC_DECLARE_FIELD(shape);
+  }
+};
+
+struct ScalarParam : public dmlc::Parameter<ScalarParam> {
+  double scalar;
+
+  DMLC_DECLARE_PARAMETER(ScalarParam) {
+    DMLC_DECLARE_FIELD(scalar);
+  }
+};
+
+
+
 }  // namespace top
 }  // namespace nnvm
 
diff --git a/nnvm/python/nnvm/libinfo.py b/nnvm/python/nnvm/libinfo.py
index 237c07fb4d41..a996bc163a7d 100644
--- a/nnvm/python/nnvm/libinfo.py
+++ b/nnvm/python/nnvm/libinfo.py
@@ -26,7 +26,7 @@ def find_lib_path():
     if hasattr(__builtin__, "NNVM_LIBRARY_NAME"):
         lib_name = __builtin__.NNVM_LIBRARY_NAME
     else:
-        lib_name = "libnnvm_example"
+        lib_name = "libnnvm_top"
 
     api_path = os.path.join(base_path, '../../lib/')
     cmake_build_path = os.path.join(base_path, '../../build/Release/')
diff --git a/nnvm/src/top/nn.cc b/nnvm/src/top/nn/nn.cc
similarity index 91%
rename from nnvm/src/top/nn.cc
rename to nnvm/src/top/nn/nn.cc
index 6a26271f37ec..b9d2d8ada85d 100644
--- a/nnvm/src/top/nn.cc
+++ b/nnvm/src/top/nn/nn.cc
@@ -7,8 +7,8 @@
 #include <nnvm/node.h>
 #include <nnvm/op_attr_types.h>
 #include <nnvm/top/nn.h>
-#include "./op_common.h"
-#include "./elemwise_op_common.h"
+#include "../op_common.h"
+#include "../elemwise_op_common.h"
 
 namespace nnvm {
 namespace top {
@@ -126,6 +126,25 @@ NNVM_REGISTER_OP(dropout)
 // batchnorm
 DMLC_REGISTER_PARAMETER(BatchNormParam);
 
+inline bool BatchNormInferShape(const nnvm::NodeAttrs& attrs,
+                                std::vector<TShape> *in_shape,
+                                std::vector<TShape> *out_shape) {
+  CHECK_EQ(in_shape->size(), 5U)
+      << "Input:[data, gamma, beta, moving_mean, moving_var]";
+  CHECK_EQ(out_shape->size(), 3U);
+  const TShape &dshape = in_shape->at(0);
+  if (dshape.ndim() == 0) return false;
+  TShape bshape({dshape[1]});
+  NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, 1, bshape);
+  NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, 2, bshape);
+  NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, 3, bshape);
+  NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, 4, bshape);
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, dshape);
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 1, bshape);
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 2, bshape);
+  return true;
+}
+
 NNVM_REGISTER_OP(batch_norm)
 .describe(R"(Batch normalization layer (Ioffe and Szegedy, 2014).
 Normalizes the input at each batch, i.e. applies a transformation
@@ -167,6 +186,8 @@ axis to be the last item in the input shape.
 .set_num_inputs(5)
 .set_num_outputs(3)
 .set_attr_parser(ParamParser<BatchNormParam>)
+.set_attr<FInferShape>("FInferShape", BatchNormInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<5, 3>)
 .set_attr<FListInputNames>("FListInputNames", [](const NodeAttrs& attrs) {
     return std::vector<std::string>{"data", "gamma", "beta", "moving_mean", "moving_var"};
   })
@@ -198,8 +219,6 @@ NNVM_REGISTER_OP(softmax)
 .set_support_level(1);
 
 // log_softmax
-DMLC_REGISTER_PARAMETER(LogSoftmaxParam);
-
 NNVM_REGISTER_OP(log_softmax)
 .describe(R"code(Computes softmax.
 
@@ -208,7 +227,23 @@ NNVM_REGISTER_OP(log_softmax)
 )code" NNVM_ADD_FILELINE)
 .set_num_inputs(1)
 .set_num_outputs(1)
-.set_attr_parser(ParamParser<LogSoftmaxParam>)
+.set_attr_parser(ParamParser<SoftmaxParam>)
+.set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_support_level(1);
+
+// leaky_rlu
+DMLC_REGISTER_PARAMETER(LeakyReLUParam);
+
+NNVM_REGISTER_OP(leaky_relu)
+.describe(R"code(Leaky version of a Rectified Linear Unit.
+
+`y = x > 0 ? x : alpha * x`
+
+)code" NNVM_ADD_FILELINE)
+.set_num_inputs(1)
+.set_num_outputs(1)
+.set_attr_parser(ParamParser<LeakyReLUParam>)
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
 .set_support_level(1);
diff --git a/nnvm/src/top/tensor.cc b/nnvm/src/top/tensor.cc
deleted file mode 100644
index 5561cefcefbd..000000000000
--- a/nnvm/src/top/tensor.cc
+++ /dev/null
@@ -1,231 +0,0 @@
-/*!
- *  Copyright (c) 2017 by Contributors
- * \file tensor.cc
- * \brief Property def of tensor operators.
- */
-#include <nnvm/op.h>
-#include <nnvm/node.h>
-#include <nnvm/op_attr_types.h>
-#include <nnvm/top/tensor.h>
-#include "./op_common.h"
-#include "./elemwise_op_common.h"
-
-namespace nnvm {
-namespace top {
-// sigmoid
-NNVM_REGISTER_ELEMWISE_UNARY_OP(sigmoid)
-.describe(R"code(Computes sigmoid.
-
-.. math::
-   y = 1 / (1 + exp(-x))
-
-)code" NNVM_ADD_FILELINE)
-.set_support_level(1);
-
-// tanh
-NNVM_REGISTER_ELEMWISE_UNARY_OP(tanh)
-.describe(R"code(Returns the hyperbolic tangent of the input array, computed element-wise.
-
-.. math::
-   tanh(x) = sinh(x) / cosh(x)
-
-)code" NNVM_ADD_FILELINE)
-.set_support_level(1);
-
-// exp
-NNVM_REGISTER_ELEMWISE_UNARY_OP(exp)
-.describe(R"code(Returns the exp input array, computed element-wise.
-
-.. math::
-   exp(x)
-
-)code" NNVM_ADD_FILELINE)
-.set_support_level(1);
-
-// log
-NNVM_REGISTER_ELEMWISE_UNARY_OP(log)
-.describe(R"code(Returns the log input array, computed element-wise.
-
-.. math::
-   log(x)
-
-)code" NNVM_ADD_FILELINE)
-.set_support_level(1);
-
-// flatten
-inline bool FlattenInferShape(const nnvm::NodeAttrs& attrs,
-                              std::vector<TShape> *in_attrs,
-                              std::vector<TShape> *out_attrs) {
-  CHECK_EQ(in_attrs->size(), 1U) << "Input: [data]";
-  CHECK_EQ(out_attrs->size(), 1U);
-  const TShape &dshape = (*in_attrs)[0];
-  if (dshape.ndim() == 0) return false;
-  uint32_t target_dim = 1;
-  for (uint32_t i = 1; i < dshape.ndim(); ++i) {
-    target_dim *= dshape[i];
-  }
-  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, 0, TShape({dshape[0], target_dim}));
-  return true;
-}
-
-NNVM_REGISTER_OP(flatten)
-.describe(R"code(Flattens the input array into a 2-D array by collapsing the higher dimensions.
-
-For an input array with shape ``(d1, d2, ..., dk)``, `flatten` operation reshapes
-the input array into an output array of shape ``(d1, d2*...*dk)``.
-
-Example::
-
-    x = [[
-        [1,2,3],
-        [4,5,6],
-        [7,8,9]
-    ],
-    [    [1,2,3],
-        [4,5,6],
-        [7,8,9]
-    ]],
-
-    flatten(x) = [[ 1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9.],
-       [ 1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9.]]
-
-)code" NNVM_ADD_FILELINE)
-.set_num_inputs(1)
-.set_num_outputs(1)
-.set_attr<FInferShape>("FInferShape", FlattenInferShape)
-.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.add_argument("data", "Tensor", "Input data.")
-.set_support_level(1);
-
-// concatenate
-DMLC_REGISTER_PARAMETER(ConcatenateParam);
-
-inline bool ConcatenateInferShape(const nnvm::NodeAttrs& attrs,
-                                  std::vector<TShape> *in_shape,
-                                  std::vector<TShape> *out_shape) {
-  const ConcatenateParam& param = nnvm::get<ConcatenateParam>(attrs.parsed);
-  TShape dshape;
-  dim_t size = 0;
-  bool has_zero = false;
-  for (size_t i = 0; i < in_shape->size(); ++i) {
-    TShape tmp = (*in_shape)[i];
-    if (tmp.ndim()) {
-      CHECK_LT(static_cast<dim_t>(param.axis), tmp.ndim())
-          << "concat dim " << param.axis << " out of range of input shape " << tmp;
-      has_zero = tmp[param.axis] == 0 || has_zero;
-      size += tmp[param.axis];
-      tmp[param.axis] = 0;
-      shape_assign(&dshape, tmp);
-    }
-  }
-
-  TShape tmp = (*out_shape)[0];
-  if (tmp.ndim()) {
-    CHECK_LT(static_cast<dim_t>(param.axis), tmp.ndim())
-        << "concat dim " << param.axis << " out of range of input shape " << tmp;
-    tmp[param.axis] = 0;
-    shape_assign(&dshape, tmp);
-  }
-
-  if (dshape.ndim() == 0) return false;
-
-  for (size_t i = 0; i < in_shape->size(); ++i) {
-    NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, i, dshape);
-  }
-
-  if (!has_zero) dshape[param.axis] = size;
-  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, dshape);
-  return dshape.Size() != 0;
-}
-
-NNVM_REGISTER_OP(concatenate)
-.describe(R"code(Joins input arrays along a given axis.
-
-The dimensions of the input arrays should be the same except the axis along
-which they will be concatenated.
-The dimension of the output array along the concatenated axis will be equal
-to the sum of the corresponding dimensions of the input arrays.
-
-Example::
-
-   x = [[1,1],[2,2]]
-   y = [[3,3],[4,4],[5,5]]
-   z = [[6,6], [7,7],[8,8]]
-
-   concatenate(x,y,z,dim=0) = [[ 1.,  1.],
-                               [ 2.,  2.],
-                               [ 3.,  3.],
-                               [ 4.,  4.],
-                               [ 5.,  5.],
-                               [ 6.,  6.],
-                               [ 7.,  7.],
-                               [ 8.,  8.]]
-
-   Note that you cannot concat x,y,z along dimension 1 since dimension
-   0 is not the same for all the input arrays.
-
-   concatenate(y,z,dim=1) = [[ 3.,  3.,  6.,  6.],
-                             [ 4.,  4.,  7.,  7.],
-                             [ 5.,  5.,  8.,  8.]]
-
-)code" NNVM_ADD_FILELINE)
-.set_num_outputs(1)
-.set_num_inputs(nnvm::kVarg)
-.set_attr_parser(ParamParser<ConcatenateParam>)
-.add_argument("data", "Tensor-or-Tensor[]", "List of arrays to concatenate")
-.set_attr<FInferShape>("FInferShape", ConcatenateInferShape)
-.set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
-.add_arguments(ConcatenateParam::__FIELDS__())
-.set_support_level(1);
-
-NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_add)
-.describe(R"code(Element-wise add
-
-)code")
-.set_support_level(1);
-
-NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_sub)
-.describe(R"code(Element-wise substraction
-
-)code"  NNVM_ADD_FILELINE)
-.set_support_level(1);
-
-NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_mul)
-.describe(R"code(Element-wise multiplication
-
-)code"  NNVM_ADD_FILELINE)
-.set_support_level(1);
-
-NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_div)
-.describe(R"code(Element-wise multiplication
-
-)code"  NNVM_ADD_FILELINE)
-.set_support_level(1);
-
-// cast
-DMLC_REGISTER_PARAMETER(CastParam);
-
-inline bool CastInferType(const nnvm::NodeAttrs& attrs,
-                          std::vector<int> *in_attrs,
-                          std::vector<int> *out_attrs) {
-  const CastParam& param = nnvm::get<CastParam>(attrs.parsed);
-  CHECK_EQ(out_attrs->size(), 1U);
-  NNVM_ASSIGN_OUTPUT_TYPE(attrs, *out_attrs, 0, param.dtype);
-  return true;
-}
-
-NNVM_REGISTER_OP(cast)
-.describe(R"code(Cast the content of input to dtype.
-
-)code" NNVM_ADD_FILELINE)
-.add_argument("data", "Tensor", "Input data array")
-.set_attr_parser(ParamParser<CastParam>)
-.set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
-.set_attr<FInferType>("FInferType", CastInferType)
-.add_arguments(CastParam::__FIELDS__())
-.set_num_inputs(1)
-.set_num_outputs(1)
-.set_support_level(1);
-
-}  // namespace top
-}  // namespace nnvm
diff --git a/nnvm/src/top/tensor/elemwise.cc b/nnvm/src/top/tensor/elemwise.cc
new file mode 100644
index 000000000000..af19f1c56835
--- /dev/null
+++ b/nnvm/src/top/tensor/elemwise.cc
@@ -0,0 +1,156 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file elemwise.cc
+ * \brief Elemenwise operators
+ */
+#include <nnvm/op.h>
+#include <nnvm/node.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/top/tensor.h>
+#include "../op_common.h"
+#include "../elemwise_op_common.h"
+
+namespace nnvm {
+namespace top {
+// sigmoid
+NNVM_REGISTER_ELEMWISE_UNARY_OP(sigmoid)
+.describe(R"code(Computes sigmoid.
+
+.. math::
+   y = 1 / (1 + exp(-x))
+
+)code" NNVM_ADD_FILELINE)
+.set_support_level(1);
+
+// tanh
+NNVM_REGISTER_ELEMWISE_UNARY_OP(tanh)
+.describe(R"code(Returns the hyperbolic tangent of the input array, computed element-wise.
+
+.. math::
+   tanh(x) = sinh(x) / cosh(x)
+
+)code" NNVM_ADD_FILELINE)
+.set_support_level(1);
+
+// exp
+NNVM_REGISTER_ELEMWISE_UNARY_OP(exp)
+.describe(R"code(Returns the exp input array, computed element-wise.
+
+.. math::
+   exp(x)
+
+)code" NNVM_ADD_FILELINE)
+.set_support_level(1);
+
+// log
+NNVM_REGISTER_ELEMWISE_UNARY_OP(log)
+.describe(R"code(Returns the log input array, computed element-wise.
+
+.. math::
+   log(x)
+
+)code" NNVM_ADD_FILELINE)
+.set_support_level(1);
+
+// binary ops
+
+NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_add)
+.describe(R"code(Element-wise add
+
+)code")
+.set_support_level(1);
+
+NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_sub)
+.describe(R"code(Element-wise substraction
+
+)code"  NNVM_ADD_FILELINE)
+.set_support_level(1);
+
+NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_mul)
+.describe(R"code(Element-wise multiplication
+
+)code"  NNVM_ADD_FILELINE)
+.set_support_level(1);
+
+NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_div)
+.describe(R"code(Element-wise multiplication
+
+)code"  NNVM_ADD_FILELINE)
+.set_support_level(1);
+
+// negative
+NNVM_REGISTER_ELEMWISE_UNARY_OP(negative)
+.describe(R"code(Elemenwise numeric negative
+
+)code"  NNVM_ADD_FILELINE)
+.set_support_level(3);
+
+// copy
+NNVM_REGISTER_ELEMWISE_UNARY_OP(copy)
+.describe(R"code(Copy tensor to another one.
+
+)code"  NNVM_ADD_FILELINE)
+.set_support_level(3);
+
+// unary scalar op
+DMLC_REGISTER_PARAMETER(ScalarParam);
+
+NNVM_REGISTER_ELEMWISE_UNARY_OP(__add_scalar__)
+.describe(R"code(Tensor add scalar
+
+)code"  NNVM_ADD_FILELINE)
+.set_attr_parser(ParamParser<ScalarParam>)
+.set_support_level(3);
+
+NNVM_REGISTER_ELEMWISE_UNARY_OP(__sub_scalar__)
+.describe(R"code(Tensor substract scalar
+
+)code"  NNVM_ADD_FILELINE)
+.set_attr_parser(ParamParser<ScalarParam>)
+.set_support_level(3);
+
+NNVM_REGISTER_ELEMWISE_UNARY_OP(__rsub_scalar__)
+.describe(R"code(scalar substract Tensor
+
+)code"  NNVM_ADD_FILELINE)
+.set_attr_parser(ParamParser<ScalarParam>)
+.set_support_level(3);
+
+NNVM_REGISTER_ELEMWISE_UNARY_OP(__mul_scalar__)
+.describe(R"code(Tensor multiplies scalar
+
+)code"  NNVM_ADD_FILELINE)
+.set_attr_parser(ParamParser<ScalarParam>)
+.set_support_level(3);
+
+NNVM_REGISTER_ELEMWISE_UNARY_OP(__div_scalar__)
+.describe(R"code(Tensor divides scalar
+
+)code"  NNVM_ADD_FILELINE)
+.set_attr_parser(ParamParser<ScalarParam>)
+.set_support_level(3);
+
+NNVM_REGISTER_ELEMWISE_UNARY_OP(__rdiv_scalar__)
+.describe(R"code(scalar divides Tensor
+
+)code"  NNVM_ADD_FILELINE)
+.set_attr_parser(ParamParser<ScalarParam>)
+.set_support_level(3);
+
+NNVM_REGISTER_ELEMWISE_UNARY_OP(__pow_scalar__)
+.describe(R"code(Tensor power scalar
+
+)code"  NNVM_ADD_FILELINE)
+.set_attr_parser(ParamParser<ScalarParam>)
+.set_support_level(3);
+
+NNVM_REGISTER_ELEMWISE_UNARY_OP(__rpow_scalar__)
+.describe(R"code(scalar power Tensor
+
+)code"  NNVM_ADD_FILELINE)
+.set_attr_parser(ParamParser<ScalarParam>)
+.set_support_level(3);
+
+
+}  // namespace top
+}  // namespace nnvm
diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
new file mode 100644
index 000000000000..d8e4ee4ec7d4
--- /dev/null
+++ b/nnvm/src/top/tensor/transform.cc
@@ -0,0 +1,319 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file transform.cc
+ * \brief Injective transformation of shape or type.
+ */
+#include <nnvm/op.h>
+#include <nnvm/node.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/top/tensor.h>
+#include "../op_common.h"
+#include "../elemwise_op_common.h"
+
+namespace nnvm {
+namespace top {
+
+// flatten
+inline bool FlattenInferShape(const NodeAttrs& attrs,
+                              std::vector<TShape> *in_attrs,
+                              std::vector<TShape> *out_attrs) {
+  CHECK_EQ(in_attrs->size(), 1U) << "Input: [data]";
+  CHECK_EQ(out_attrs->size(), 1U);
+  const TShape &dshape = (*in_attrs)[0];
+  if (dshape.ndim() == 0) return false;
+  uint32_t target_dim = 1;
+  for (uint32_t i = 1; i < dshape.ndim(); ++i) {
+    target_dim *= dshape[i];
+  }
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, 0, TShape({dshape[0], target_dim}));
+  return true;
+}
+
+NNVM_REGISTER_OP(flatten)
+.describe(R"code(Flattens the input array into a 2-D array by collapsing the higher dimensions.
+
+For an input array with shape ``(d1, d2, ..., dk)``, `flatten` operation reshapes
+the input array into an output array of shape ``(d1, d2*...*dk)``.
+
+Example::
+
+    x = [[
+        [1,2,3],
+        [4,5,6],
+        [7,8,9]
+    ],
+    [    [1,2,3],
+        [4,5,6],
+        [7,8,9]
+    ]],
+
+    flatten(x) = [[ 1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9.],
+       [ 1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9.]]
+
+)code" NNVM_ADD_FILELINE)
+.set_num_inputs(1)
+.set_num_outputs(1)
+.set_attr<FInferShape>("FInferShape", FlattenInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.add_argument("data", "Tensor", "Input data.")
+.set_support_level(1);
+
+// concatenate
+DMLC_REGISTER_PARAMETER(ConcatenateParam);
+
+inline bool ConcatenateInferShape(const NodeAttrs& attrs,
+                                  std::vector<TShape> *in_shape,
+                                  std::vector<TShape> *out_shape) {
+  const ConcatenateParam& param = nnvm::get<ConcatenateParam>(attrs.parsed);
+  TShape dshape;
+  dim_t size = 0;
+  bool has_zero = false;
+  for (size_t i = 0; i < in_shape->size(); ++i) {
+    TShape tmp = (*in_shape)[i];
+    if (tmp.ndim()) {
+      CHECK_LT(static_cast<dim_t>(param.axis), tmp.ndim())
+          << "concat dim " << param.axis << " out of range of input shape " << tmp;
+      has_zero = tmp[param.axis] == 0 || has_zero;
+      size += tmp[param.axis];
+      tmp[param.axis] = 0;
+      shape_assign(&dshape, tmp);
+    }
+  }
+
+  TShape tmp = (*out_shape)[0];
+  if (tmp.ndim()) {
+    CHECK_LT(static_cast<dim_t>(param.axis), tmp.ndim())
+        << "concat dim " << param.axis << " out of range of input shape " << tmp;
+    tmp[param.axis] = 0;
+    shape_assign(&dshape, tmp);
+  }
+
+  if (dshape.ndim() == 0) return false;
+
+  for (size_t i = 0; i < in_shape->size(); ++i) {
+    NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, i, dshape);
+  }
+
+  if (!has_zero) dshape[param.axis] = size;
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, dshape);
+  return dshape.Size() != 0;
+}
+
+NNVM_REGISTER_OP(concatenate)
+.describe(R"code(Joins input arrays along a given axis.
+
+The dimensions of the input arrays should be the same except the axis along
+which they will be concatenated.
+The dimension of the output array along the concatenated axis will be equal
+to the sum of the corresponding dimensions of the input arrays.
+
+Example::
+
+   x = [[1,1],[2,2]]
+   y = [[3,3],[4,4],[5,5]]
+   z = [[6,6], [7,7],[8,8]]
+
+   concatenate(x,y,z,dim=0) = [[ 1.,  1.],
+                               [ 2.,  2.],
+                               [ 3.,  3.],
+                               [ 4.,  4.],
+                               [ 5.,  5.],
+                               [ 6.,  6.],
+                               [ 7.,  7.],
+                               [ 8.,  8.]]
+
+   Note that you cannot concat x,y,z along dimension 1 since dimension
+   0 is not the same for all the input arrays.
+
+   concatenate(y,z,dim=1) = [[ 3.,  3.,  6.,  6.],
+                             [ 4.,  4.,  7.,  7.],
+                             [ 5.,  5.,  8.,  8.]]
+
+)code" NNVM_ADD_FILELINE)
+.set_num_outputs(1)
+.set_num_inputs(kVarg)
+.set_attr_parser(ParamParser<ConcatenateParam>)
+.add_argument("data", "Tensor-or-Tensor[]", "List of arrays to concatenate")
+.set_attr<FInferShape>("FInferShape", ConcatenateInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
+.add_arguments(ConcatenateParam::__FIELDS__())
+.set_support_level(1);
+
+
+// cast
+DMLC_REGISTER_PARAMETER(CastParam);
+
+inline bool CastInferType(const NodeAttrs& attrs,
+                          std::vector<int> *in_attrs,
+                          std::vector<int> *out_attrs) {
+  const CastParam& param = nnvm::get<CastParam>(attrs.parsed);
+  CHECK_EQ(out_attrs->size(), 1U);
+  NNVM_ASSIGN_OUTPUT_TYPE(attrs, *out_attrs, 0, param.dtype);
+  return true;
+}
+
+NNVM_REGISTER_OP(cast)
+.describe(R"code(Cast the content of input to dtype.
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "Tensor", "Input data array")
+.set_attr_parser(ParamParser<CastParam>)
+.set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
+.set_attr<FInferType>("FInferType", CastInferType)
+.add_arguments(CastParam::__FIELDS__())
+.set_num_inputs(1)
+.set_num_outputs(1)
+.set_support_level(1);
+
+
+// reshape
+DMLC_REGISTER_PARAMETER(ReshapeParam);
+
+inline bool ReshapeInferShape(const NodeAttrs& attrs,
+                              std::vector<TShape> *in_attrs,
+                              std::vector<TShape> *out_attrs) {
+  const ReshapeParam& param = nnvm::get<ReshapeParam>(attrs.parsed);
+  CHECK_GT(param.shape.ndim(), 0);
+  CHECK_EQ(in_attrs->size(), 1U) << "Input: [data]";
+  CHECK_EQ(out_attrs->size(), 1U);
+
+  const TShape &dshape = (*in_attrs)[0];
+  if (dshape.ndim() == 0) return false;
+
+  const Tuple<int64_t>& target_shape = param.shape;
+  std::vector<int64_t> oshape;
+  dim_t src_idx = 0;
+  int infer_idx = -1;
+
+  for (dim_t i = 0; i < target_shape.ndim(); ++i) {
+    int svalue = target_shape[i];
+    // special flag handling for shape inference.
+    if (svalue > 0) {
+      oshape.push_back(svalue);
+      ++src_idx;
+    } else if (svalue == 0) {
+      // keep same
+      CHECK_LT(src_idx, dshape.ndim());
+      oshape.push_back(dshape[src_idx++]);
+    } else if (svalue == -1) {
+      // inference based on rest
+      CHECK_LT(infer_idx, 0)
+          << "One and only one dim can be inferred";
+      infer_idx = i;
+      oshape.push_back(1);
+      ++src_idx;
+    } else if (svalue == -2) {
+      // copy all remaining dims from source
+      while (src_idx < dshape.ndim()) {
+        oshape.push_back(dshape[src_idx++]);
+      }
+    } else if (svalue == -3) {
+      // merge two dims from source
+      CHECK_LT(src_idx + 1, dshape.ndim());
+      dim_t d1 = dshape[src_idx++];
+      dim_t d2 = dshape[src_idx++];
+      oshape.push_back(d1 * d2);
+    } else if (svalue == -4) {
+      // split the source dim s into two dims
+      // read the left dim and then the right dim (either can be -1)
+      CHECK_LT(i + 2, target_shape.ndim());
+      CHECK_LT(src_idx, dshape.ndim());
+      dim_t d0 = dshape[src_idx++];
+      int d1 = target_shape[++i];
+      int d2 = target_shape[++i];
+      CHECK(d1 != -1 || d2 != -1) << "Split dims cannot both be -1.";
+      if (d1 == -1) d1 = d0 / d2;
+      if (d2 == -1) d2 = d0 / d1;
+      CHECK_EQ(d1 * d2, static_cast<int>(d0)) <<
+          "Split dims " << d1 << ", " << d2 << " do not divide original dim " << d0;
+      oshape.push_back(d1);
+      oshape.push_back(d2);
+    }
+  }
+
+  if (infer_idx >= 0) {
+    if (dshape.Size() > 0) {
+      int new_size = 1;
+      for (int x : oshape) {
+        new_size *= x;
+      }
+      oshape[infer_idx] = dshape.Size() / new_size;
+    } else {
+      oshape[infer_idx] = 0;
+    }
+  }
+  TShape out_shape(oshape.begin(), oshape.end());
+  CHECK_EQ(out_shape.Size(), dshape.Size())
+      << "Target shape size is different to source. "
+      << "Target: " << out_shape
+      << "\nSource: " << dshape;
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, 0, out_shape);
+  return true;
+}
+
+NNVM_REGISTER_OP(reshape)
+.describe(R"code(Reshapes the input array.
+
+Given an array and a shape, this function returns a copy of the array in the new shape.
+The shape is a tuple of integers such as (2,3,4).The size of the new shape should be same as the size of the input array.
+
+Example::
+
+  reshape([1,2,3,4], shape=(2,2)) = [[1,2], [3,4]]
+
+To give user more convenience in without doing manual shape inference,
+some dimensions of the shape can take special values from the set {0, -1, -2, -3, -4}.
+The significance of each is explained below:
+
+- ``0``  copy this dimension from the input to the output shape.
+
+  Example::
+
+  - input shape = (2,3,4), shape = (4,0,2), output shape = (4,3,2)
+  - input shape = (2,3,4), shape = (2,0,0), output shape = (2,3,4)
+
+- ``-1`` infers the dimension of the output shape by using the remainder of the input dimensions
+  keeping the size of the new array same as that of the input array.
+  At most one dimension of shape can be -1.
+
+  Example::
+
+  - input shape = (2,3,4), shape = (6,1,-1), output shape = (6,1,4)
+  - input shape = (2,3,4), shape = (3,-1,8), output shape = (3,1,8)
+  - input shape = (2,3,4), shape=(-1,), output shape = (24,)
+
+- ``-2`` copy all/remainder of the input dimensions to the output shape.
+
+  Example::
+
+  - input shape = (2,3,4), shape = (-2,), output shape = (2,3,4)
+  - input shape = (2,3,4), shape = (2,-2), output shape = (2,3,4)
+  - input shape = (2,3,4), shape = (-2,1,1), output shape = (2,3,4,1,1)
+
+- ``-3`` use the product of two consecutive dimensions of the input shape as the output dimension.
+
+  Example::
+
+  - input shape = (2,3,4), shape = (-3,4), output shape = (6,4)
+  - input shape = (2,3,4,5), shape = (-3,-3), output shape = (6,20)
+  - input shape = (2,3,4), shape = (0,-3), output shape = (2,12)
+  - input shape = (2,3,4), shape = (-3,-2), output shape = (6,4)
+
+- ``-4`` split one dimension of the input into two dimensions passed subsequent to -4 in shape (can contain -1).
+
+  Example::
+
+  - input shape = (2,3,4), shape = (-4,1,2,-2), output shape =(1,2,3,4)
+  - input shape = (2,3,4), shape = (2,-4,-1,3,-2), output shape = (2,1,3,4)
+
+)code" NNVM_ADD_FILELINE)
+.set_num_inputs(1)
+.set_num_outputs(1)
+.set_attr_parser(ParamParser<ReshapeParam>)
+.set_attr<FInferShape>("FInferShape", ReshapeInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.add_argument("data", "Tensor", "Input data.")
+.set_support_level(3);
+
+}  // namespace top
+}  // namespace nnvm
diff --git a/nnvm/tests/python/test_graph.py b/nnvm/tests/python/unittest/test_graph.py
similarity index 100%
rename from nnvm/tests/python/test_graph.py
rename to nnvm/tests/python/unittest/test_graph.py
diff --git a/nnvm/tests/python/unittest/test_infer_shape.py b/nnvm/tests/python/unittest/test_infer_shape.py
new file mode 100644
index 000000000000..1b5bd27cada3
--- /dev/null
+++ b/nnvm/tests/python/unittest/test_infer_shape.py
@@ -0,0 +1,82 @@
+import json
+import nnvm.symbol as sym
+import nnvm.graph as graph
+
+def infer_shape(sym):
+    g = graph.create(sym)
+    g._set_json_attr("shape_attr_key", "shape")
+    g = g.apply("InferShape")
+    jgraph = json.loads(g.apply("SaveJSON").json_attr("json"))
+    jnodes = jgraph["nodes"]
+    jnode_row_ptr = jgraph["node_row_ptr"]
+    sdict = {}
+    vshape = g.json_attr("shape")
+    for i, n in enumerate(jnodes):
+        begin, end = jnode_row_ptr[i], jnode_row_ptr[i + 1]
+        sdict[n["name"]] = vshape[begin:end]
+    return sdict
+
+# Level 1
+def test_dense():
+    x = sym.Variable("x", shape=(10, 20))
+    y = sym.dense(x, units=30, name="fc")
+    sdict = infer_shape(y)
+    assert(sdict["fc"][0] == [10, 30])
+    assert(sdict["fc_bias"][0] == [30])
+
+
+def test_concatenate():
+    x1 = sym.Variable("x", shape=(10, 20))
+    x2 = sym.Variable("y", shape=(10, 30))
+    z = sym.concatenate(x1, x2, name="concat")
+    sdict = infer_shape(z)
+    assert(sdict["concat"][0] == [10, 50])
+    z = sym.concatenate(x1, x1, axis=0, name="concat")
+    sdict = infer_shape(z)
+    assert(sdict["concat"][0] == [20, 20])
+
+
+def test_batchnorm():
+    x = sym.Variable("x", shape=(10, 20))
+    y = sym.batch_norm(1 / x, name="bn")
+    sdict = infer_shape(y)
+    assert(sdict["bn_gamma"][0] == [20])
+
+
+def test_flatten():
+    x = sym.Variable("x", shape=(10, 20, 10))
+    y = sym.flatten(x) * 2
+    y = sym.exp(y, name="y")
+    sdict = infer_shape(y)
+    assert(sdict["y"][0] == [10, 200])
+
+# Level 3
+def test_reshape():
+    def check(in_shape, tshape, out_shape):
+        x = sym.Variable("x", shape=in_shape)
+        y = sym.reshape(x, shape=tshape, name="y")
+        sdict = infer_shape(y)
+        assert(tuple(sdict["y"][0]) == tuple(out_shape))
+
+    check((4,), (2, 2), (2, 2))
+    check((2, 3, 4), (4, 0, 2), (4, 3, 2))
+    check((2, 3, 4), (2, 0, 0), (2, 3, 4))
+    check((2, 3, 4), (6, 1, -1), (6, 1, 4))
+    check((2, 3, 4), (3, -1, 8), (3, 1, 8))
+    check((2, 3, 4), (-1,), (24,))
+    check((2, 3, 4), (-2,), (2, 3, 4))
+    check((2, 3, 4), (2, -2), (2, 3, 4))
+    check((2, 3, 4), (-2, 1, 1), (2, 3, 4, 1, 1))
+    check((2, 3, 4), (-3, 4), (6, 4))
+    check((2, 3, 4, 5), (-3, -3), (6, 20))
+    check((2, 3, 4), (0, -3), (2, 12))
+    check((2, 3, 4), (-3, -2), (6, 4))
+    check((2, 3, 4), (-4, 1, 2, -2), (1, 2, 3, 4))
+    check((2, 3, 4), (2, -4, -1, 3, -2), (2, 1, 3, 4))
+
+if __name__ == "__main__":
+    test_dense()
+    test_concatenate()
+    test_batchnorm()
+    test_flatten()
+    test_reshape()
diff --git a/nnvm/tests/python/test_symbol.py b/nnvm/tests/python/unittest/test_symbol.py
similarity index 100%
rename from nnvm/tests/python/test_symbol.py
rename to nnvm/tests/python/unittest/test_symbol.py
diff --git a/nnvm/tests/python/test_top_level1.py b/nnvm/tests/python/unittest/test_top_level1.py
similarity index 100%
rename from nnvm/tests/python/test_top_level1.py
rename to nnvm/tests/python/unittest/test_top_level1.py
diff --git a/nnvm/tests/python/unittest/test_top_level3.py b/nnvm/tests/python/unittest/test_top_level3.py
new file mode 100644
index 000000000000..e37863b2b8ac
--- /dev/null
+++ b/nnvm/tests/python/unittest/test_top_level3.py
@@ -0,0 +1,23 @@
+import nnvm.symbol as sym
+
+def test_reshape():
+    x = sym.Variable("x")
+    y = sym.reshape(x, shape=(10, 20), name="y")
+    assert(y.list_input_names() == ["x"])
+
+
+def test_scalar_op():
+    x = sym.Variable("x")
+    y = (1 / (x * 2) - 1) ** 2
+    assert(y.list_input_names() == ["x"])
+
+def test_leaky_relu():
+    x = sym.Variable("x")
+    y = sym.leaky_relu(x, alpha=0.1)
+    assert(y.list_input_names() == ["x"])
+
+
+if __name__ == "__main__":
+    test_scalar_op()
+    test_reshape()
+    test_leaky_relu()
diff --git a/nnvm/tests/travis/run_test.sh b/nnvm/tests/travis/run_test.sh
index a5ea03a1dc3a..ecce1f4d5a27 100755
--- a/nnvm/tests/travis/run_test.sh
+++ b/nnvm/tests/travis/run_test.sh
@@ -37,22 +37,22 @@ if [ ${TASK} == "python_test" ]; then
     make clean
     make -j all || exit -1
     if [ ${TRAVIS_OS_NAME} == "osx" ]; then
-        python -m nose tests/python/ || exit -1
-        python3 -m nose tests/python/ || exit -1
+        python -m nose tests/python/unittest/ || exit -1
+        python3 -m nose tests/python/unittest/ || exit -1
     else
-        nosetests tests/python/ || exit -1
-        nosetests3 tests/python/ || exit -1
+        nosetests tests/python/unittest/ || exit -1
+        nosetests3 tests/python/unittest/ || exit -1
     fi
 
     make cython || exit -1
     make cython3 || exit -1
 
     if [ ${TRAVIS_OS_NAME} == "osx" ]; then
-        python -m nose tests/python/ || exit -1
-        python3 -m nose tests/python/ || exit -1
+        python -m nose tests/python/unittest/ || exit -1
+        python3 -m nose tests/python/unittest/ || exit -1
     else
-        nosetests tests/python/ || exit -1
-        nosetests3 tests/python/ || exit -1
+        nosetests tests/python/unittest/ || exit -1
+        nosetests3 tests/python/unittest/ || exit -1
     fi
     exit 0
 fi

From 404057bdf80b919204db9b0869437d9c362659a1 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 12 Sep 2017 20:44:13 -0700
Subject: [PATCH 465/949] [TOP] complete level2 (#8)

* [TOP] complete level2

* [TOP] add split
---
 nnvm/include/nnvm/top/nn.h                    |  35 ++-
 nnvm/include/nnvm/top/tensor.h                |  19 +-
 nnvm/src/top/nn/convolution.cc                | 214 ++++++++++++++++
 nnvm/src/top/nn/nn.cc                         | 228 +-----------------
 nnvm/src/top/nn/nn_common.h                   | 129 ++++++++++
 nnvm/src/top/nn/pooling.cc                    | 160 ++++++++++++
 nnvm/src/top/tensor/transform.cc              |  85 ++++++-
 .../tests/python/unittest/test_infer_shape.py | 126 ++++++++++
 nnvm/tests/python/unittest/test_top_level1.py |   9 +-
 9 files changed, 756 insertions(+), 249 deletions(-)
 create mode 100644 nnvm/src/top/nn/convolution.cc
 create mode 100644 nnvm/src/top/nn/nn_common.h
 create mode 100644 nnvm/src/top/nn/pooling.cc

diff --git a/nnvm/include/nnvm/top/nn.h b/nnvm/include/nnvm/top/nn.h
index 61d50eb14cb7..5a935ace65ce 100644
--- a/nnvm/include/nnvm/top/nn.h
+++ b/nnvm/include/nnvm/top/nn.h
@@ -8,20 +8,20 @@
 
 #include <dmlc/base.h>
 #include <dmlc/parameter.h>
+#include <nnvm/tuple.h>
 
 namespace nnvm {
 namespace top {
 
+// Layout flag in spatial conv and pooling.
 enum LayoutFlag {
-  kNCHW = 0,
+  kNCHW,
   kNHWC,
   kCHWN,
-
-  kNCW = 1 << 3,
+  kNCW,
   kNWC,
   kCWN,
-
-  kNCDHW = 1 << 5,
+  kNCDHW,
   kNDHWC,
   kCDHWN
 };
@@ -101,7 +101,7 @@ struct LeakyReLUParam : public dmlc::Parameter<LeakyReLUParam> {
   }
 };
 
-struct Conv2DParam : public dmlc::Parameter<Conv2DParam> {
+struct ConvParam : public dmlc::Parameter<ConvParam> {
   int channels;
   TShape kernel_size;
   TShape strides;
@@ -111,7 +111,7 @@ struct Conv2DParam : public dmlc::Parameter<Conv2DParam> {
   int layout;
   bool use_bias;
 
-  DMLC_DECLARE_PARAMETER(Conv2DParam) {
+  DMLC_DECLARE_PARAMETER(ConvParam) {
     DMLC_DECLARE_FIELD(channels)
       .describe("The dimensionality of the output space"
                 "i.e. the number of output channels in the convolution.");
@@ -141,10 +141,14 @@ struct Conv2DParam : public dmlc::Parameter<Conv2DParam> {
     DMLC_DECLARE_FIELD(use_bias).set_default(true)
       .describe("Whether the layer uses a bias vector.");
   }
+  // constants
+  static const constexpr int kData = 0;
+  static const constexpr int kWeight = 1;
+  static const constexpr int kBias = 2;
 };
 
 
-struct Conv2DTransposeParam : public dmlc::Parameter<Conv2DTransposeParam> {
+struct ConvTransposeParam : public dmlc::Parameter<ConvTransposeParam> {
   int channels;
   TShape kernel_size;
   TShape strides;
@@ -155,7 +159,7 @@ struct Conv2DTransposeParam : public dmlc::Parameter<Conv2DTransposeParam> {
   int layout;
   bool use_bias;
 
-  DMLC_DECLARE_PARAMETER(Conv2DTransposeParam) {
+  DMLC_DECLARE_PARAMETER(ConvTransposeParam) {
     DMLC_DECLARE_FIELD(channels)
       .describe("The dimensionality of the output space"
                 "i.e. the number of output channels in the convolution.");
@@ -187,9 +191,14 @@ struct Conv2DTransposeParam : public dmlc::Parameter<Conv2DTransposeParam> {
     DMLC_DECLARE_FIELD(use_bias).set_default(true)
       .describe("Whether the layer uses a bias vector.");
   }
+  // constants
+  static const constexpr int kData = 0;
+  static const constexpr int kWeight = 1;
+  static const constexpr int kBias = 2;
 };
 
-struct Pool2DParam : public dmlc::Parameter<Pool2DParam> {
+
+struct PoolParam : public dmlc::Parameter<PoolParam> {
   TShape pool_size;
   TShape strides;
   TShape padding;
@@ -197,7 +206,7 @@ struct Pool2DParam : public dmlc::Parameter<Pool2DParam> {
   int layout;
   bool ceil_mode;
 
-  DMLC_DECLARE_PARAMETER(Pool2DParam) {
+  DMLC_DECLARE_PARAMETER(PoolParam) {
     DMLC_DECLARE_FIELD(pool_size)
       .describe("Size of the pooling windows..");
     DMLC_DECLARE_FIELD(strides).set_default(TShape({1, 1}))
@@ -225,10 +234,10 @@ struct Pool2DParam : public dmlc::Parameter<Pool2DParam> {
 };
 
 
-struct GlobalPool2DParam : public dmlc::Parameter<GlobalPool2DParam> {
+struct GlobalPoolParam : public dmlc::Parameter<GlobalPoolParam> {
   int layout;
 
-  DMLC_DECLARE_PARAMETER(GlobalPool2DParam) {
+  DMLC_DECLARE_PARAMETER(GlobalPoolParam) {
     DMLC_DECLARE_FIELD(layout)
       .add_enum("NCHW", kNCHW)
       .add_enum("NHWC", kNHWC)
diff --git a/nnvm/include/nnvm/top/tensor.h b/nnvm/include/nnvm/top/tensor.h
index 056a694f90e1..347134235ad5 100644
--- a/nnvm/include/nnvm/top/tensor.h
+++ b/nnvm/include/nnvm/top/tensor.h
@@ -6,6 +6,10 @@
 #ifndef NNVM_TOP_TENSOR_H_
 #define NNVM_TOP_TENSOR_H_
 
+#include <dmlc/base.h>
+#include <dmlc/parameter.h>
+#include <nnvm/tuple.h>
+
 namespace nnvm {
 namespace top {
 
@@ -17,6 +21,19 @@ struct ConcatenateParam : public dmlc::Parameter<ConcatenateParam> {
   }
 };
 
+struct SplitParam : public dmlc::Parameter<SplitParam> {
+  // numpy convention, only support indices, not support list.
+  Tuple<int> indices_or_sections;
+  int axis;
+
+  DMLC_DECLARE_PARAMETER(SplitParam) {
+    DMLC_DECLARE_FIELD(indices_or_sections)
+        .describe("Number of outputs to be splitted");
+    DMLC_DECLARE_FIELD(axis).set_lower_bound(0).set_default(1)
+        .describe("the axis to be splitted.");
+  }
+};
+
 enum TypeFlag {
   kFloat32 = 0,
   kFloat64 = 1,
@@ -56,8 +73,6 @@ struct ScalarParam : public dmlc::Parameter<ScalarParam> {
   }
 };
 
-
-
 }  // namespace top
 }  // namespace nnvm
 
diff --git a/nnvm/src/top/nn/convolution.cc b/nnvm/src/top/nn/convolution.cc
new file mode 100644
index 000000000000..e5f697ac631f
--- /dev/null
+++ b/nnvm/src/top/nn/convolution.cc
@@ -0,0 +1,214 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file convolution.cc
+ * \brief Convolution operators
+ */
+#include <nnvm/op.h>
+#include <nnvm/node.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/top/nn.h>
+#include "./nn_common.h"
+#include "../op_common.h"
+#include "../elemwise_op_common.h"
+
+namespace nnvm {
+namespace top {
+
+// conv2d
+DMLC_REGISTER_PARAMETER(ConvParam);
+
+inline bool Conv2DInferShape(const nnvm::NodeAttrs& attrs,
+                             std::vector<TShape>* in_shape,
+                             std::vector<TShape>* out_shape) {
+  const ConvParam& param = nnvm::get<ConvParam>(attrs.parsed);
+  if (param.use_bias) {
+    CHECK_EQ(in_shape->size(), 3U) << "Input:[data, weight, bias]";
+  } else {
+    CHECK_EQ(in_shape->size(), 2U) << "Input:[data, weight]";
+  }
+  CHECK_EQ(out_shape->size(), 1U);
+
+  TShape dshape = in_shape->at(0);
+  if (dshape.ndim() == 0) return false;
+  dshape = ConvertLayout(dshape, param.layout, kNCHW);
+
+  CHECK_EQ(dshape.ndim(), 4U) << "Input data should be 4D";
+  CHECK_EQ(param.kernel_size.ndim(), 2U);
+  CHECK_EQ(param.strides.ndim(), 2U)
+      << "incorrect stride size: " << param.strides;
+  CHECK_EQ(param.dilation.ndim(), 2U)
+      << "incorrect dilate size: " << param.dilation;
+  CHECK_EQ(dshape[1] % param.groups, 0U)
+      << "input channels must divide group size";
+  CHECK_EQ(param.channels % param.groups, 0U)
+      << "output channels must divide group size";
+
+  TShape wshape({param.channels / param.groups,
+                 dshape[1] / param.groups,
+                 param.kernel_size[0],
+                 param.kernel_size[1]});
+
+  wshape = ConvertLayout(wshape, kNCHW, param.layout);
+  wshape[0] *= param.groups;
+
+  NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, ConvParam::kWeight, wshape);
+  if (param.use_bias) {
+    NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape,
+                            ConvParam::kBias, TShape({param.channels}));
+  }
+  // dilation
+  dim_t dilated_ksize_y = 1 + (param.kernel_size[0] - 1) * param.dilation[0];
+  dim_t dilated_ksize_x = 1 + (param.kernel_size[1] - 1) * param.dilation[1];
+  TShape oshape({dshape[0], param.channels, 0, 0});
+  if (dshape[2] != 0) {
+    oshape[2] = (dshape[2] + param.padding[0] * 2 - dilated_ksize_y) / param.strides[0] + 1;
+  }
+  if (dshape[3] != 0) {
+    oshape[3] = (dshape[3] + param.padding[1] * 2 - dilated_ksize_x) / param.strides[1] + 1;
+  }
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0,
+                           ConvertLayout(oshape, kNCHW, param.layout));
+  // Perform incomplete shape inference. Fill in the missing values in data shape.
+  // 1) We can always fill in the batch_size.
+  // 2) We can back-calculate the input height/width if the corresponding stride is 1.
+  oshape = ConvertLayout((*out_shape)[0], param.layout, kNCHW);
+  dshape[0] = oshape[0];
+  if (oshape[2] && param.strides[0] == 1) {
+    dshape[2] = oshape[2] + dilated_ksize_y - 1 - 2 * param.padding[0];
+  }
+  if (oshape[3] && param.strides[1] == 1) {
+    dshape[3] = oshape[3] + dilated_ksize_x - 1 - 2 * param.padding[1];
+  }
+  NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, ConvParam::kData,
+                          ConvertLayout(dshape, kNCHW, param.layout));
+  // Check whether the kernel sizes are valid
+  if (dshape[2] != 0) {
+    CHECK_LE(dilated_ksize_y, dshape[2] + 2 * param.padding[0])
+      << "kernel size exceed input";
+  }
+  if (dshape[3] != 0) {
+    CHECK_LE(dilated_ksize_x, dshape[3] + 2 * param.padding[1])
+        << "kernel size exceed input";
+  }
+  return true;
+}
+
+NNVM_REGISTER_OP(conv2d)
+.describe(R"code(2D convolution layer (e.g. spatial convolution over images).
+
+This layer creates a convolution kernel that is convolved
+with the layer input to produce a tensor of
+outputs. If `use_bias` is True,
+a bias vector is created and added to the outputs.
+
+- **data**: This depends on the `layout` parameter. Input is 4D array of shape
+            (batch_size, in_channels, height, width) if `layout` is `NCHW`.
+- **weight**: (channels, in_channels, kernel_size[0], kernel_size[1])
+- **bias**: (channels,)
+- **out**:  This depends on the `layout` parameter. Output is 4D array of shape
+            (batch_size, channels, out_height, out_width) if `layout` is `NCHW`.
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "4D Tensor", "Input data.")
+.add_argument("weight", "4D Tensor", "Weight matrix.")
+.add_argument("bias", "1D Tensor", "Bias parameter.")
+.add_arguments(ConvParam::__FIELDS__())
+.set_attr_parser(ParamParser<ConvParam>)
+.set_num_outputs(1)
+.set_num_inputs(UseBiasNumInputs<ConvParam>)
+.set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<ConvParam>)
+.set_attr<FInferShape>("FInferShape", Conv2DInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
+.set_support_level(2);
+
+
+DMLC_REGISTER_PARAMETER(ConvTransposeParam);
+
+inline bool ConvTransposeInferShape(const nnvm::NodeAttrs& attrs,
+                                    std::vector<TShape>* in_shape,
+                                    std::vector<TShape>* out_shape) {
+  const ConvTransposeParam& param = nnvm::get<ConvTransposeParam>(attrs.parsed);
+  if (param.use_bias) {
+    CHECK_EQ(in_shape->size(), 3U) << "Input:[data, weight, bias]";
+  } else {
+    CHECK_EQ(in_shape->size(), 2U) << "Input:[data, weight]";
+  }
+  CHECK_EQ(out_shape->size(), 1U);
+  const TShape& dshape = (*in_shape)[ConvTransposeParam::kData];
+  if (dshape.ndim() ==  0) return false;
+  TShape dshape_nchw = ConvertLayout(dshape, param.layout, kNCHW);
+
+  CHECK_EQ(dshape_nchw[1] % param.groups, 0U)
+      << "input num_filter must divide group size";
+  CHECK_EQ(param.channels % param.groups, 0U)
+      << "output num_filter must divide group size";
+  CHECK_EQ(param.kernel_size.ndim(), 2U)
+      << "incorrect kernel size: " << param.kernel_size;
+  CHECK_EQ(param.strides.ndim(), 2U)
+      << "incorrect stride size: " << param.strides;
+  CHECK_EQ(param.dilation.ndim(), 2U)
+      << "incorrect dilate size: " << param.dilation;
+
+  TShape wshape({dshape_nchw[1],
+                 param.channels / param.groups,
+                 param.kernel_size[0], param.kernel_size[1]});
+  wshape = ConvertLayout(wshape, kNCHW, param.layout);
+
+  NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, ConvTransposeParam::kWeight, wshape);
+
+  if (param.use_bias) {
+    NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape,
+                            ConvTransposeParam::kBias,
+                            TShape({param.channels}));
+  }
+  // dilation
+  dim_t dilated_ksize_y = 1 + (param.kernel_size[0] - 1) * param.dilation[0];
+  dim_t dilated_ksize_x = 1 + (param.kernel_size[1] - 1) * param.dilation[1];
+  // output shape.
+  TShape oshape({dshape_nchw[0], param.channels, 0, 0});
+  oshape[2] = (param.strides[0] * (dshape_nchw[2] - 1) + dilated_ksize_y -
+               2 * param.padding[0] + param.output_padding[0]);
+
+  oshape[3] = (param.strides[1] * (dshape_nchw[3] - 1) + dilated_ksize_x -
+               2 * param.padding[1] + param.output_padding[1]);
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0,
+                           ConvertLayout(oshape, kNCHW, param.layout));
+  return true;
+}
+
+NNVM_REGISTER_OP(conv2d_transpose)
+.describe(R"code(Transposed 2D convolution layer (sometimes called Deconvolution).
+
+The need for transposed convolutions generally arises
+from the desire to use a transformation going in the opposite direction
+of a normal convolution, i.e., from something that has the shape of the
+output of some convolution to something that has the shape of its input
+while maintaining a connectivity pattern that is compatible with
+said convolution.
+
+- **data**: This depends on the `layout` parameter. Input is 4D array of shape
+            (batch_size, in_channels, height, width) if `layout` is `NCHW`.
+- **weight**: (channels, in_channels, kernel_size[0], kernel_size[1])
+- **bias**: (channels,)
+- **out**:  This depends on the `layout` parameter. Output is 4D array of shape
+            (batch_size, channels, out_height, out_width) if `layout` is `NCHW`.
+
+            out_height and out_width are calculated as::
+                out_height = (height-1)*strides[0]-2*padding[0]+kernel_size[0]+output_padding[0]
+                out_width = (width-1)*strides[1]-2*padding[1]+kernel_size[1]+output_padding[1]
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "4D Tensor", "Input data.")
+.add_argument("weight", "4D Tensor", "Weight matrix.")
+.add_argument("bias", "1D Tensor", "Bias parameter.")
+.add_arguments(ConvTransposeParam::__FIELDS__())
+.set_attr_parser(ParamParser<ConvTransposeParam>)
+.set_num_outputs(1)
+.set_num_inputs(UseBiasNumInputs<ConvTransposeParam>)
+.set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<ConvTransposeParam>)
+.set_attr<FInferShape>("FInferShape", ConvTransposeInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
+.set_support_level(2);
+
+}  // namespace top
+}  // namespace nnvm
diff --git a/nnvm/src/top/nn/nn.cc b/nnvm/src/top/nn/nn.cc
index b9d2d8ada85d..e712d2dabe18 100644
--- a/nnvm/src/top/nn/nn.cc
+++ b/nnvm/src/top/nn/nn.cc
@@ -7,6 +7,7 @@
 #include <nnvm/node.h>
 #include <nnvm/op_attr_types.h>
 #include <nnvm/top/nn.h>
+#include "./nn_common.h"
 #include "../op_common.h"
 #include "../elemwise_op_common.h"
 
@@ -16,25 +17,9 @@ namespace top {
 // dense
 DMLC_REGISTER_PARAMETER(DenseParam);
 
-template<typename ParamType>
-inline std::vector<std::string> UseBiasListInputNames(const NodeAttrs& attrs) {
-  const ParamType& param = nnvm::get<ParamType>(attrs.parsed);
-  if (param.use_bias) {
-    return {"data", "weight", "bias"};
-  } else {
-    return {"data", "weight"};
-  }
-}
-
-template<typename ParamType>
-inline uint32_t UseBiasNumInputs(const NodeAttrs& attrs) {
-  const ParamType& param = get<ParamType>(attrs.parsed);
-  return param.use_bias ? 3 : 2;
-}
-
 inline bool DenseInferShape(const nnvm::NodeAttrs& attrs,
-                            std::vector<TShape> *in_shape,
-                            std::vector<TShape> *out_shape) {
+                            std::vector<TShape>* in_shape,
+                            std::vector<TShape>* out_shape) {
   const DenseParam& param = nnvm::get<DenseParam>(attrs.parsed);
   if (param.use_bias) {
     CHECK_EQ(in_shape->size(), 3U) << "Input:[data, weight, bias]";
@@ -127,8 +112,8 @@ NNVM_REGISTER_OP(dropout)
 DMLC_REGISTER_PARAMETER(BatchNormParam);
 
 inline bool BatchNormInferShape(const nnvm::NodeAttrs& attrs,
-                                std::vector<TShape> *in_shape,
-                                std::vector<TShape> *out_shape) {
+                                std::vector<TShape>* in_shape,
+                                std::vector<TShape>* out_shape) {
   CHECK_EQ(in_shape->size(), 5U)
       << "Input:[data, gamma, beta, moving_mean, moving_var]";
   CHECK_EQ(out_shape->size(), 3U);
@@ -248,208 +233,5 @@ NNVM_REGISTER_OP(leaky_relu)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
 .set_support_level(1);
 
-
-DMLC_REGISTER_PARAMETER(Conv2DParam);
-
-inline bool Conv2DInferShape(const nnvm::NodeAttrs& attrs,
-                             std::vector<TShape> *in_shape,
-                             std::vector<TShape> *out_shape) {
-  const Conv2DParam& param = nnvm::get<Conv2DParam>(attrs.parsed);
-  if (param.use_bias) {
-    CHECK_EQ(in_shape->size(), 3U) << "Input:[data, weight, bias]";
-  } else {
-    CHECK_EQ(in_shape->size(), 2U) << "Input:[data, weight]";
-  }
-  CHECK_EQ(out_shape->size(), 1U);
-  return true;
-}
-
-NNVM_REGISTER_OP(conv2d)
-.describe(R"code(2D convolution layer (e.g. spatial convolution over images).
-
-This layer creates a convolution kernel that is convolved
-with the layer input to produce a tensor of
-outputs. If `use_bias` is True,
-a bias vector is created and added to the outputs.
-
-- **data**: This depends on the `layout` parameter. Input is 4D array of shape
-            (batch_size, in_channels, height, width) if `layout` is `NCHW`.
-- **weight**: (channels, in_channels, kernel_size[0], kernel_size[1])
-- **bias**: (channels,)
-- **out**:  This depends on the `layout` parameter. Output is 4D array of shape
-            (batch_size, channels, out_height, out_width) if `layout` is `NCHW`.
-
-)code" NNVM_ADD_FILELINE)
-.add_argument("data", "4D Tensor", "Input data.")
-.add_argument("weight", "4D Tensor", "Weight matrix.")
-.add_argument("bias", "1D Tensor", "Bias parameter.")
-.add_arguments(Conv2DParam::__FIELDS__())
-.set_attr_parser(ParamParser<Conv2DParam>)
-.set_num_outputs(1)
-.set_num_inputs(UseBiasNumInputs<Conv2DParam>)
-.set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<Conv2DParam>)
-.set_attr<FInferShape>("FInferShape", Conv2DInferShape)
-.set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
-.set_support_level(2);
-
-
-DMLC_REGISTER_PARAMETER(Conv2DTransposeParam);
-
-inline bool Conv2DTransposeInferShape(const nnvm::NodeAttrs& attrs,
-                             std::vector<TShape> *in_shape,
-                             std::vector<TShape> *out_shape) {
-  const Conv2DTransposeParam& param = nnvm::get<Conv2DTransposeParam>(attrs.parsed);
-  if (param.use_bias) {
-    CHECK_EQ(in_shape->size(), 3U) << "Input:[data, weight, bias]";
-  } else {
-    CHECK_EQ(in_shape->size(), 2U) << "Input:[data, weight]";
-  }
-  CHECK_EQ(out_shape->size(), 1U);
-  return true;
-}
-
-NNVM_REGISTER_OP(conv2d_transpose)
-.describe(R"code(Transposed 2D convolution layer (sometimes called Deconvolution).
-
-The need for transposed convolutions generally arises
-from the desire to use a transformation going in the opposite direction
-of a normal convolution, i.e., from something that has the shape of the
-output of some convolution to something that has the shape of its input
-while maintaining a connectivity pattern that is compatible with
-said convolution.
-
-- **data**: This depends on the `layout` parameter. Input is 4D array of shape
-            (batch_size, in_channels, height, width) if `layout` is `NCHW`.
-- **weight**: (channels, in_channels, kernel_size[0], kernel_size[1])
-- **bias**: (channels,)
-- **out**:  This depends on the `layout` parameter. Output is 4D array of shape
-            (batch_size, channels, out_height, out_width) if `layout` is `NCHW`.
-
-            out_height and out_width are calculated as::
-                out_height = (height-1)*strides[0]-2*padding[0]+kernel_size[0]+output_padding[0]
-                out_width = (width-1)*strides[1]-2*padding[1]+kernel_size[1]+output_padding[1]
-
-)code" NNVM_ADD_FILELINE)
-.add_argument("data", "4D Tensor", "Input data.")
-.add_argument("weight", "4D Tensor", "Weight matrix.")
-.add_argument("bias", "1D Tensor", "Bias parameter.")
-.add_arguments(Conv2DTransposeParam::__FIELDS__())
-.set_attr_parser(ParamParser<Conv2DTransposeParam>)
-.set_num_outputs(1)
-.set_num_inputs(UseBiasNumInputs<Conv2DTransposeParam>)
-.set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<Conv2DTransposeParam>)
-.set_attr<FInferShape>("FInferShape", Conv2DTransposeInferShape)
-.set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
-.set_support_level(2);
-
-
-DMLC_REGISTER_PARAMETER(Pool2DParam);
-
-inline bool Pool2DInferShape(const nnvm::NodeAttrs& attrs,
-                           std::vector<TShape> *in_shape,
-                           std::vector<TShape> *out_shape) {
-  const Pool2DParam& param = nnvm::get<Pool2DParam>(attrs.parsed);
-  CHECK_EQ(in_shape->size(), 1U);
-  CHECK_EQ(out_shape->size(), 1U);
-  return true;
-}
-
-NNVM_REGISTER_OP(max_pool2d)
-.describe(R"code(Max pooling operation for one dimensional data.
-
-- **data**: This depends on the `layout` parameter. Input is 4D array of shape
-            (batch_size, channels, height, width) if `layout` is `NCHW`.
-- **out**: This depends on the `layout` parameter. Output is 4D array of shape
-           (batch_size, channels, out_height, out_width)  if `layout` is `NCHW`.
-           out_height and out_width are calculated as::
-               out_height = floor((height+2*padding[0]-pool_size[0])/strides[0])+1
-               out_width = floor((width+2*padding[1]-pool_size[1])/strides[1])+1
-           When `ceil_mode` is `True`, ceil will be used instead of floor in this
-           equation.
-
-)code" NNVM_ADD_FILELINE)
-.add_argument("data", "4D Tensor", "Input data.")
-.add_arguments(Pool2DParam::__FIELDS__())
-.set_attr_parser(ParamParser<Pool2DParam>)
-.set_num_outputs(1)
-.set_num_inputs(1)
-.set_attr<FInferShape>("FInferShape", Pool2DInferShape)
-.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_support_level(2);
-
-
-NNVM_REGISTER_OP(avg_pool2d)
-.describe(R"code(Average pooling operation for one dimensional data.
-
-- **data**: This depends on the `layout` parameter. Input is 4D array of shape
-            (batch_size, channels, height, width) if `layout` is `NCHW`.
-- **out**: This depends on the `layout` parameter. Output is 4D array of shape
-           (batch_size, channels, out_height, out_width)  if `layout` is `NCHW`.
-           out_height and out_width are calculated as::
-               out_height = floor((height+2*padding[0]-pool_size[0])/strides[0])+1
-               out_width = floor((width+2*padding[1]-pool_size[1])/strides[1])+1
-           When `ceil_mode` is `True`, ceil will be used instead of floor in this
-           equation.
-
-)code" NNVM_ADD_FILELINE)
-.add_argument("data", "4D Tensor", "Input data.")
-.add_arguments(Pool2DParam::__FIELDS__())
-.set_attr_parser(ParamParser<Pool2DParam>)
-.set_num_outputs(1)
-.set_num_inputs(1)
-.set_attr<FInferShape>("FInferShape", Pool2DInferShape)
-.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_support_level(2);
-
-
-DMLC_REGISTER_PARAMETER(GlobalPool2DParam);
-
-inline bool GlobalPool2DInferShape(const nnvm::NodeAttrs& attrs,
-                                   std::vector<TShape> *in_shape,
-                                   std::vector<TShape> *out_shape) {
-  const GlobalPool2DParam& param = nnvm::get<GlobalPool2DParam>(attrs.parsed);
-  CHECK_EQ(in_shape->size(), 1U);
-  CHECK_EQ(out_shape->size(), 1U);
-  return true;
-}
-
-NNVM_REGISTER_OP(global_max_pool2d)
-.describe(R"code(Global max pooling operation for 2D data.
-
-- **data**: This depends on the `layout` parameter. Input is 4D array of shape
-            (batch_size, channels, height, width) if `layout` is `NCHW`.
-- **out**: This depends on the `layout` parameter. Output is 4D array of shape
-           (batch_size, channels, 1, 1)  if `layout` is `NCHW`.
-
-)code" NNVM_ADD_FILELINE)
-.add_argument("data", "4D Tensor", "Input data.")
-.add_arguments(GlobalPool2DParam::__FIELDS__())
-.set_attr_parser(ParamParser<GlobalPool2DParam>)
-.set_num_outputs(1)
-.set_num_inputs(1)
-.set_attr<FInferShape>("FInferShape", GlobalPool2DInferShape)
-.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_support_level(2);
-
-
-NNVM_REGISTER_OP(global_avg_pool2d)
-.describe(R"code(Global average pooling operation for 2D data.
-
-- **data**: This depends on the `layout` parameter. Input is 4D array of shape
-            (batch_size, channels, height, width) if `layout` is `NCHW`.
-- **out**: This depends on the `layout` parameter. Output is 4D array of shape
-           (batch_size, channels, 1, 1)  if `layout` is `NCHW`.
-
-)code" NNVM_ADD_FILELINE)
-.add_argument("data", "4D Tensor", "Input data.")
-.add_arguments(GlobalPool2DParam::__FIELDS__())
-.set_attr_parser(ParamParser<GlobalPool2DParam>)
-.set_num_outputs(1)
-.set_num_inputs(1)
-.set_attr<FInferShape>("FInferShape", GlobalPool2DInferShape)
-.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_support_level(2);
-
-
 }  // namespace top
 }  // namespace nnvm
diff --git a/nnvm/src/top/nn/nn_common.h b/nnvm/src/top/nn/nn_common.h
new file mode 100644
index 000000000000..a4ce6b1b78d0
--- /dev/null
+++ b/nnvm/src/top/nn/nn_common.h
@@ -0,0 +1,129 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file nn_common.h
+ * \brief Common utilities for nn ops.
+ */
+#ifndef NNVM_TOP_NN_NN_COMMON_H_
+#define NNVM_TOP_NN_NN_COMMON_H_
+
+#include <dmlc/logging.h>
+#include <dmlc/parameter.h>
+#include <nnvm/top/nn.h>
+#include <string>
+#include <vector>
+#include <algorithm>
+
+namespace nnvm {
+namespace top {
+
+template<typename ParamType>
+inline uint32_t UseBiasNumInputs(const NodeAttrs& attrs) {
+  const ParamType& param = get<ParamType>(attrs.parsed);
+  return param.use_bias ? 3 : 2;
+}
+
+template<typename ParamType>
+inline std::vector<std::string> UseBiasListInputNames(const NodeAttrs& attrs) {
+  const ParamType& param = nnvm::get<ParamType>(attrs.parsed);
+  if (param.use_bias) {
+    return {"data", "weight", "bias"};
+  } else {
+    return {"data", "weight"};
+  }
+}
+
+/*!
+ * \brief Convert shape in src_layout to shape in dst_layout
+ * \param src original shape
+ * \param src_layout layout of original shape
+ * \param dst_layout target layout
+ * \return shape in target layout
+ */
+inline TShape ConvertLayout(TShape src, int src_layout, int dst_layout) {
+  if (src_layout == dst_layout) return src;
+  TShape dst = src;
+  if (src.ndim() == 3) {
+    switch (src_layout) {
+      case kNCW: break;
+      case kNWC: {
+        std::swap(dst[1], dst[2]);
+        break;
+      }
+      default: {
+        LOG(FATAL) << "inavlid layout for 3d shape" << src_layout;
+      }
+    }
+    switch (dst_layout) {
+      case kNCW: break;
+      case kNWC: {
+        std::swap(dst[1], dst[2]);
+        break;
+      }
+      default: {
+        LOG(FATAL) << "inavlid layout for 3d shape" << dst_layout;
+      }
+    }
+  } else if (src.ndim() == 4) {
+    switch (src_layout) {
+      case kNCHW: break;
+      case kNHWC: {
+        dst[2] = src[1];
+        dst[3] = src[2];
+        dst[1] = src[3];
+        break;
+      }
+      default: {
+        LOG(FATAL) << "inavlid layout for 4d shape" << src_layout;
+      }
+    }
+    src = dst;
+    switch (dst_layout) {
+      case kNCHW: break;
+      case kNHWC: {
+        dst[1] = src[2];
+        dst[2] = src[3];
+        dst[3] = src[1];
+        break;
+      }
+      default: {
+        LOG(FATAL) << "inavlid layout for 4d shape" << dst_layout;
+      }
+    }
+  } else if (src.ndim() == 5) {
+    switch (src_layout) {
+      case kNCDHW: break;
+      case kNDHWC: {
+        dst[2] = src[1];
+        dst[3] = src[2];
+        dst[4] = src[3];
+        dst[1] = src[4];
+        break;
+      }
+      default: {
+        LOG(FATAL) << "inavlid layout for 5d shape" << src_layout;
+      }
+    }
+    src = dst;
+    switch (dst_layout) {
+      case kNCDHW: break;
+      case kNDHWC: {
+        dst[1] = src[2];
+        dst[2] = src[3];
+        dst[3] = src[4];
+        dst[4] = src[1];
+        break;
+      }
+      default: {
+        LOG(FATAL) << "inavlid layout for 5d shape" << dst_layout;
+      }
+    }
+  } else {
+    LOG(FATAL) << "no layout option for " << dst.ndim() << " dimensions";
+  }
+  return dst;
+}
+
+}  // namespace top
+}  // namespace nnvm
+
+#endif  // NNVM_TOP_NN_NN_COMMON_H_
diff --git a/nnvm/src/top/nn/pooling.cc b/nnvm/src/top/nn/pooling.cc
new file mode 100644
index 000000000000..43568eee7efa
--- /dev/null
+++ b/nnvm/src/top/nn/pooling.cc
@@ -0,0 +1,160 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file pooling.cc
+ * \brief Property def of pooling operators.
+ */
+#include <nnvm/op.h>
+#include <nnvm/node.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/top/nn.h>
+#include "./nn_common.h"
+#include "../op_common.h"
+#include "../elemwise_op_common.h"
+
+namespace nnvm {
+namespace top {
+
+DMLC_REGISTER_PARAMETER(PoolParam);
+
+inline bool Pool2DInferShape(const nnvm::NodeAttrs& attrs,
+                             std::vector<TShape>* in_shape,
+                             std::vector<TShape>* out_shape) {
+  const PoolParam& param = nnvm::get<PoolParam>(attrs.parsed);
+  CHECK_EQ(in_shape->size(), 1U);
+  CHECK_EQ(out_shape->size(), 1U);
+
+  TShape dshape = (*in_shape)[0];
+  if (dshape.ndim() ==  0) return false;
+  dshape = ConvertLayout(dshape, param.layout, kNCHW);
+
+  TShape oshape = dshape;
+  CHECK_EQ(dshape.ndim(), 4U)
+      << "Pooling: Input data should be 4D";
+  CHECK(param.pool_size[0] <= dshape[2] + 2 * param.padding[0])
+      << "pool size (" << param.pool_size[0] << ") exceeds input (" << dshape[2]
+      << " padded to " << (dshape[2] + 2*param.padding[0]) << ")";
+  CHECK(param.pool_size[1] <= dshape[3] + 2 * param.padding[1])
+      << "pool size (" << param.pool_size[1] << ") exceeds input (" << dshape[3]
+      << " padded to " << (dshape[3] + 2*param.padding[1]) << ")";
+
+  if (!param.ceil_mode) {
+    oshape[2] = ((dshape[2] + 2 * param.padding[0] - param.pool_size[0]) /
+                 param.strides[0]) + 1;
+    oshape[3] = ((dshape[3] + 2 * param.padding[1] - param.pool_size[1]) /
+                 param.strides[1]) + 1;
+  } else {
+    oshape[2] = ((dshape[2] + 2 * param.padding[0] - param.pool_size[0] +
+                  param.strides[0] - 1) / param.strides[0]) + 1;
+    oshape[3] = ((dshape[3] + 2 * param.padding[1] - param.pool_size[1] +
+                  param.strides[1] - 1) / param.strides[1]) + 1;
+  }
+  oshape = ConvertLayout(oshape, kNCHW, param.layout);
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, oshape);
+  return true;
+}
+
+NNVM_REGISTER_OP(max_pool2d)
+.describe(R"code(Max pooling operation for one dimensional data.
+
+- **data**: This depends on the `layout` parameter. Input is 4D array of shape
+            (batch_size, channels, height, width) if `layout` is `NCHW`.
+- **out**: This depends on the `layout` parameter. Output is 4D array of shape
+           (batch_size, channels, out_height, out_width)  if `layout` is `NCHW`.
+           out_height and out_width are calculated as::
+               out_height = floor((height+2*padding[0]-pool_size[0])/strides[0])+1
+               out_width = floor((width+2*padding[1]-pool_size[1])/strides[1])+1
+           When `ceil_mode` is `True`, ceil will be used instead of floor in this
+           equation.
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "4D Tensor", "Input data.")
+.add_arguments(PoolParam::__FIELDS__())
+.set_attr_parser(ParamParser<PoolParam>)
+.set_num_outputs(1)
+.set_num_inputs(1)
+.set_attr<FInferShape>("FInferShape", Pool2DInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_support_level(2);
+
+
+NNVM_REGISTER_OP(avg_pool2d)
+.describe(R"code(Average pooling operation for one dimensional data.
+
+- **data**: This depends on the `layout` parameter. Input is 4D array of shape
+            (batch_size, channels, height, width) if `layout` is `NCHW`.
+- **out**: This depends on the `layout` parameter. Output is 4D array of shape
+           (batch_size, channels, out_height, out_width)  if `layout` is `NCHW`.
+           out_height and out_width are calculated as::
+               out_height = floor((height+2*padding[0]-pool_size[0])/strides[0])+1
+               out_width = floor((width+2*padding[1]-pool_size[1])/strides[1])+1
+           When `ceil_mode` is `True`, ceil will be used instead of floor in this
+           equation.
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "4D Tensor", "Input data.")
+.add_arguments(PoolParam::__FIELDS__())
+.set_attr_parser(ParamParser<PoolParam>)
+.set_num_outputs(1)
+.set_num_inputs(1)
+.set_attr<FInferShape>("FInferShape", Pool2DInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_support_level(2);
+
+
+DMLC_REGISTER_PARAMETER(GlobalPoolParam);
+
+inline bool GlobalPool2DInferShape(const nnvm::NodeAttrs& attrs,
+                                   std::vector<TShape>* in_shape,
+                                   std::vector<TShape>* out_shape) {
+  const GlobalPoolParam& param = nnvm::get<GlobalPoolParam>(attrs.parsed);
+  CHECK_EQ(in_shape->size(), 1U);
+  CHECK_EQ(out_shape->size(), 1U);
+  TShape dshape = (*in_shape)[0];
+  if (dshape.ndim() ==  0) return false;
+  dshape = ConvertLayout(dshape, param.layout, kNCHW);
+  TShape oshape = dshape;
+  oshape[2] = oshape[3] = 1;
+  oshape = ConvertLayout(oshape, kNCHW, param.layout);
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, oshape);
+  return true;
+}
+
+NNVM_REGISTER_OP(global_max_pool2d)
+.describe(R"code(Global max pooling operation for 2D data.
+
+- **data**: This depends on the `layout` parameter. Input is 4D array of shape
+            (batch_size, channels, height, width) if `layout` is `NCHW`.
+- **out**: This depends on the `layout` parameter. Output is 4D array of shape
+           (batch_size, channels, 1, 1)  if `layout` is `NCHW`.
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "4D Tensor", "Input data.")
+.add_arguments(GlobalPoolParam::__FIELDS__())
+.set_attr_parser(ParamParser<GlobalPoolParam>)
+.set_num_outputs(1)
+.set_num_inputs(1)
+.set_attr<FInferShape>("FInferShape", GlobalPool2DInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_support_level(2);
+
+
+NNVM_REGISTER_OP(global_avg_pool2d)
+.describe(R"code(Global average pooling operation for 2D data.
+
+- **data**: This depends on the `layout` parameter. Input is 4D array of shape
+            (batch_size, channels, height, width) if `layout` is `NCHW`.
+- **out**: This depends on the `layout` parameter. Output is 4D array of shape
+           (batch_size, channels, 1, 1)  if `layout` is `NCHW`.
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "4D Tensor", "Input data.")
+.add_arguments(GlobalPoolParam::__FIELDS__())
+.set_attr_parser(ParamParser<GlobalPoolParam>)
+.set_num_outputs(1)
+.set_num_inputs(1)
+.set_attr<FInferShape>("FInferShape", GlobalPool2DInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_support_level(2);
+
+}  // namespace top
+}  // namespace nnvm
diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
index d8e4ee4ec7d4..2e065a3f9f51 100644
--- a/nnvm/src/top/tensor/transform.cc
+++ b/nnvm/src/top/tensor/transform.cc
@@ -15,8 +15,8 @@ namespace top {
 
 // flatten
 inline bool FlattenInferShape(const NodeAttrs& attrs,
-                              std::vector<TShape> *in_attrs,
-                              std::vector<TShape> *out_attrs) {
+                              std::vector<TShape>* in_attrs,
+                              std::vector<TShape>* out_attrs) {
   CHECK_EQ(in_attrs->size(), 1U) << "Input: [data]";
   CHECK_EQ(out_attrs->size(), 1U);
   const TShape &dshape = (*in_attrs)[0];
@@ -25,7 +25,8 @@ inline bool FlattenInferShape(const NodeAttrs& attrs,
   for (uint32_t i = 1; i < dshape.ndim(); ++i) {
     target_dim *= dshape[i];
   }
-  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, 0, TShape({dshape[0], target_dim}));
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, 0,
+                           TShape({dshape[0], target_dim}));
   return true;
 }
 
@@ -62,8 +63,8 @@ Example::
 DMLC_REGISTER_PARAMETER(ConcatenateParam);
 
 inline bool ConcatenateInferShape(const NodeAttrs& attrs,
-                                  std::vector<TShape> *in_shape,
-                                  std::vector<TShape> *out_shape) {
+                                  std::vector<TShape>* in_shape,
+                                  std::vector<TShape>* out_shape) {
   const ConcatenateParam& param = nnvm::get<ConcatenateParam>(attrs.parsed);
   TShape dshape;
   dim_t size = 0;
@@ -140,12 +141,78 @@ Example::
 .set_support_level(1);
 
 
+// concatenate
+DMLC_REGISTER_PARAMETER(SplitParam);
+
+inline bool SplitInferShape(const NodeAttrs& attrs,
+                            std::vector<TShape>* in_shape,
+                            std::vector<TShape>* out_shape) {
+  const SplitParam& param = nnvm::get<SplitParam>(attrs.parsed);
+  const TShape& dshape = (*in_shape)[0];
+  if (dshape.ndim() == 0) return false;
+
+  if (param.indices_or_sections.ndim() == 1) {
+    int num_outputs = param.indices_or_sections[0];
+    CHECK_EQ(out_shape->size(), static_cast<size_t>(num_outputs));
+    CHECK_LT(param.axis, dshape.ndim());
+    TShape oshape = dshape;
+    CHECK_EQ(oshape[param.axis] % num_outputs, 0)
+        << "indices_or_sections need to be able to divide input.shape[axis]";
+    oshape[param.axis] /= num_outputs;
+
+    for (size_t i = 0; i < out_shape->size(); ++i) {
+      NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, i, oshape);
+    }
+  } else {
+    dim_t num_outputs = param.indices_or_sections.ndim();
+    CHECK_EQ(out_shape->size(), static_cast<size_t>(num_outputs));
+    CHECK_LT(param.axis, dshape.ndim());
+    TShape oshape = dshape;
+    CHECK_EQ(oshape[param.axis] % num_outputs, 0)
+        << "indices_or_sections need to be able to divide input.shape[axis]";
+    dim_t total = 0;
+    for (size_t i = 0; i < out_shape->size(); ++i) {
+      oshape[param.axis] = param.indices_or_sections[i];
+      total += oshape[param.axis];
+      NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, i, oshape);
+    }
+    CHECK_EQ(total, dshape[param.axis])
+        << "The sum of sections must match the input.shape[axis]";
+  }
+  return true;
+}
+
+inline uint32_t SplitNumOutputs(const NodeAttrs& attrs) {
+  const SplitParam& param = nnvm::get<SplitParam>(attrs.parsed);
+  if (param.indices_or_sections.ndim() == 1) {
+    return static_cast<uint32_t>(param.indices_or_sections[0]);
+  } else {
+    return static_cast<uint32_t>(param.indices_or_sections.ndim());
+  }
+}
+
+NNVM_REGISTER_OP(split)
+.describe(R"code(Splits an array along a particular axis into multiple sub-arrays.
+
+**Note** that `indices_or_sections` should evenly divide the length of the axis
+along which to split the array.
+
+)code" NNVM_ADD_FILELINE)
+.set_num_inputs(1)
+.set_attr_parser(ParamParser<SplitParam>)
+.set_num_outputs(SplitNumOutputs)
+.add_argument("data", "Tensor", "List of arrays to concatenate")
+.set_attr<FInferShape>("FInferShape", SplitInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
+.add_arguments(SplitParam::__FIELDS__())
+.set_support_level(1);
+
 // cast
 DMLC_REGISTER_PARAMETER(CastParam);
 
 inline bool CastInferType(const NodeAttrs& attrs,
-                          std::vector<int> *in_attrs,
-                          std::vector<int> *out_attrs) {
+                          std::vector<int>* in_attrs,
+                          std::vector<int>* out_attrs) {
   const CastParam& param = nnvm::get<CastParam>(attrs.parsed);
   CHECK_EQ(out_attrs->size(), 1U);
   NNVM_ASSIGN_OUTPUT_TYPE(attrs, *out_attrs, 0, param.dtype);
@@ -170,8 +237,8 @@ NNVM_REGISTER_OP(cast)
 DMLC_REGISTER_PARAMETER(ReshapeParam);
 
 inline bool ReshapeInferShape(const NodeAttrs& attrs,
-                              std::vector<TShape> *in_attrs,
-                              std::vector<TShape> *out_attrs) {
+                              std::vector<TShape>* in_attrs,
+                              std::vector<TShape>* out_attrs) {
   const ReshapeParam& param = nnvm::get<ReshapeParam>(attrs.parsed);
   CHECK_GT(param.shape.ndim(), 0);
   CHECK_EQ(in_attrs->size(), 1U) << "Input: [data]";
diff --git a/nnvm/tests/python/unittest/test_infer_shape.py b/nnvm/tests/python/unittest/test_infer_shape.py
index 1b5bd27cada3..2488ec273d47 100644
--- a/nnvm/tests/python/unittest/test_infer_shape.py
+++ b/nnvm/tests/python/unittest/test_infer_shape.py
@@ -36,6 +36,18 @@ def test_concatenate():
     assert(sdict["concat"][0] == [20, 20])
 
 
+def test_split():
+    x1 = sym.Variable("x", shape=(10, 20))
+    z = sym.split(x1, indices_or_sections=[11, 9], name="y")
+    sdict = infer_shape(z)
+    assert(sdict["y"][0] == [10, 11])
+    assert(sdict["y"][1] == [10, 9])
+    z = sym.split(x1, indices_or_sections=2, name="y")
+    sdict = infer_shape(z)
+    assert(sdict["y"][0] == [10, 10])
+    assert(sdict["y"][1] == [10, 10])
+
+
 def test_batchnorm():
     x = sym.Variable("x", shape=(10, 20))
     y = sym.batch_norm(1 / x, name="bn")
@@ -50,6 +62,115 @@ def test_flatten():
     sdict = infer_shape(y)
     assert(sdict["y"][0] == [10, 200])
 
+
+# Level 2
+def test_conv2d():
+    def check(in_shape, out_shape, **kwargs):
+        x = sym.Variable("x", shape=in_shape)
+        y = sym.conv2d(x, name="y", **kwargs)
+        sdict = infer_shape(y)
+        assert(tuple(sdict["y"][0]) == tuple(out_shape))
+
+    check((4, 10, 10, 12),
+          (4, 12, 10, 12),
+          channels=12,
+          kernel_size=(3,3),
+          padding=(1,1))
+    check((4, 10, 12, 4),
+          (4, 8, 8, 5),
+          channels=5,
+          kernel_size=(3, 5),
+          layout="NHWC")
+    check((4, 10, 12, 4),
+          (4, 6, 8, 5),
+          channels=5,
+          dilation=(2, 2),
+          kernel_size=(3, 3),
+          layout="NHWC")
+    check((4, 10, 12, 4),
+          (4, 5, 6, 5),
+          channels=5,
+          strides=(2, 2),
+          kernel_size=(3, 3),
+          padding=(1, 1),
+          layout="NHWC")
+
+
+def test_conv2d_transpose():
+    def check(in_shape, out_shape, **kwargs):
+        x = sym.Variable("x", shape=in_shape)
+        y = sym.conv2d_transpose(x, name="y", **kwargs)
+        sdict = infer_shape(y)
+        assert(tuple(sdict["y"][0]) == tuple(out_shape))
+
+    check((4, 10, 10, 12),
+          (4, 15, 10, 12),
+          channels=15,
+          kernel_size=(3,3),
+          padding=(1,1))
+    check((4, 10, 10, 12),
+          (4, 15, 10, 14),
+          channels=15,
+          kernel_size=(3, 5),
+          padding=(1, 1))
+    check((4, 10, 10, 12),
+          (4, 15, 11, 15),
+          channels=15,
+          kernel_size=(3, 5),
+          padding=(1, 1),
+          output_padding=(1, 1))
+    check((4, 10, 10, 12),
+          (4, 15, 15, 11),
+          channels=11,
+          kernel_size=(5, 5),
+          output_padding=(1, 1),
+          layout="NHWC")
+
+
+def test_max_pool2d():
+    def check(in_shape, out_shape, **kwargs):
+        x = sym.Variable("x", shape=in_shape)
+        y = sym.max_pool2d(x, name="y", **kwargs)
+        sdict = infer_shape(y)
+        assert(tuple(sdict["y"][0]) == tuple(out_shape))
+
+    check((4, 10, 12, 12),
+          (4, 10, 12, 12),
+          pool_size=(3,3),
+          padding=(1,1))
+    check((4, 10, 12, 12),
+          (4, 10, 6, 6),
+          pool_size=(3, 3),
+          padding=(1, 1),
+          strides=(2, 2))
+    check((4, 10, 12, 12),
+          (4, 10, 7, 7),
+          pool_size=(3, 3),
+          padding=(1, 1),
+          strides=(2, 2),
+          ceil_mode=True)
+    check((4, 12, 14, 10),
+          (4, 6, 7, 10),
+          pool_size=(3, 3),
+          padding=(1, 1),
+          strides=(2, 2),
+          layout="NHWC")
+
+
+def test_global_pool2d():
+    def check(in_shape, out_shape, **kwargs):
+        x = sym.Variable("x", shape=in_shape)
+        y = sym.global_max_pool2d(x, name="y", **kwargs)
+        sdict = infer_shape(y)
+        assert(tuple(sdict["y"][0]) == tuple(out_shape))
+
+    check((4, 10, 12, 12),
+          (4, 10, 1, 1))
+    check((4, 10, 12, 12),
+          (4, 1, 1, 12),
+          layout="NHWC")
+
+
 # Level 3
 def test_reshape():
     def check(in_shape, tshape, out_shape):
@@ -77,6 +198,11 @@ def check(in_shape, tshape, out_shape):
 if __name__ == "__main__":
     test_dense()
     test_concatenate()
+    test_split()
     test_batchnorm()
     test_flatten()
+    test_conv2d()
+    test_conv2d_transpose()
+    test_max_pool2d()
+    test_global_pool2d()
     test_reshape()
diff --git a/nnvm/tests/python/unittest/test_top_level1.py b/nnvm/tests/python/unittest/test_top_level1.py
index 47e022469a5c..13eaf0d6fab3 100644
--- a/nnvm/tests/python/unittest/test_top_level1.py
+++ b/nnvm/tests/python/unittest/test_top_level1.py
@@ -7,11 +7,16 @@ def test_fullc():
     x3 = sym.softmax(x2)
     assert x2.list_input_names() == ['x', 'dense_weight', 'dense_bias']
 
-def test_concatenate():
+def test_concatenate_split():
     x = sym.Variable('x')
     y = sym.Variable('y')
     y = sym.concatenate(x, y)
     assert y.list_input_names() == ['x', 'y']
+    z = sym.split(y, indices_or_sections=10)
+    assert len(z.list_output_names()) == 10
+    z = sym.split(y, indices_or_sections=[10, 20])
+    assert len(z.list_output_names()) == 2
+
 
 def test_unary():
     x = sym.Variable('x')
@@ -29,7 +34,7 @@ def test_batchnorm():
 
 
 if __name__ == "__main__":
-    test_concatenate()
+    test_concatenate_split()
     test_fullc()
     test_unary()
     test_batchnorm()

From 68e45a0a03a3d5c535d3b3cfc070ff910bf60dbc Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Tue, 12 Sep 2017 21:18:58 -0700
Subject: [PATCH 466/949] [TOP] Rename conv pool parameter back to 2d

---
 nnvm/include/nnvm/top/nn.h     | 16 ++++++-------
 nnvm/src/top/nn/convolution.cc | 44 +++++++++++++++++-----------------
 nnvm/src/top/nn/pooling.cc     | 24 +++++++++----------
 3 files changed, 42 insertions(+), 42 deletions(-)

diff --git a/nnvm/include/nnvm/top/nn.h b/nnvm/include/nnvm/top/nn.h
index 5a935ace65ce..a48a1287fa71 100644
--- a/nnvm/include/nnvm/top/nn.h
+++ b/nnvm/include/nnvm/top/nn.h
@@ -101,7 +101,7 @@ struct LeakyReLUParam : public dmlc::Parameter<LeakyReLUParam> {
   }
 };
 
-struct ConvParam : public dmlc::Parameter<ConvParam> {
+struct Conv2DParam : public dmlc::Parameter<Conv2DParam> {
   int channels;
   TShape kernel_size;
   TShape strides;
@@ -111,7 +111,7 @@ struct ConvParam : public dmlc::Parameter<ConvParam> {
   int layout;
   bool use_bias;
 
-  DMLC_DECLARE_PARAMETER(ConvParam) {
+  DMLC_DECLARE_PARAMETER(Conv2DParam) {
     DMLC_DECLARE_FIELD(channels)
       .describe("The dimensionality of the output space"
                 "i.e. the number of output channels in the convolution.");
@@ -148,7 +148,7 @@ struct ConvParam : public dmlc::Parameter<ConvParam> {
 };
 
 
-struct ConvTransposeParam : public dmlc::Parameter<ConvTransposeParam> {
+struct Conv2DTransposeParam : public dmlc::Parameter<Conv2DTransposeParam> {
   int channels;
   TShape kernel_size;
   TShape strides;
@@ -159,7 +159,7 @@ struct ConvTransposeParam : public dmlc::Parameter<ConvTransposeParam> {
   int layout;
   bool use_bias;
 
-  DMLC_DECLARE_PARAMETER(ConvTransposeParam) {
+  DMLC_DECLARE_PARAMETER(Conv2DTransposeParam) {
     DMLC_DECLARE_FIELD(channels)
       .describe("The dimensionality of the output space"
                 "i.e. the number of output channels in the convolution.");
@@ -198,7 +198,7 @@ struct ConvTransposeParam : public dmlc::Parameter<ConvTransposeParam> {
 };
 
 
-struct PoolParam : public dmlc::Parameter<PoolParam> {
+struct Pool2DParam : public dmlc::Parameter<Pool2DParam> {
   TShape pool_size;
   TShape strides;
   TShape padding;
@@ -206,7 +206,7 @@ struct PoolParam : public dmlc::Parameter<PoolParam> {
   int layout;
   bool ceil_mode;
 
-  DMLC_DECLARE_PARAMETER(PoolParam) {
+  DMLC_DECLARE_PARAMETER(Pool2DParam) {
     DMLC_DECLARE_FIELD(pool_size)
       .describe("Size of the pooling windows..");
     DMLC_DECLARE_FIELD(strides).set_default(TShape({1, 1}))
@@ -234,10 +234,10 @@ struct PoolParam : public dmlc::Parameter<PoolParam> {
 };
 
 
-struct GlobalPoolParam : public dmlc::Parameter<GlobalPoolParam> {
+struct GlobalPool2DParam : public dmlc::Parameter<GlobalPool2DParam> {
   int layout;
 
-  DMLC_DECLARE_PARAMETER(GlobalPoolParam) {
+  DMLC_DECLARE_PARAMETER(GlobalPool2DParam) {
     DMLC_DECLARE_FIELD(layout)
       .add_enum("NCHW", kNCHW)
       .add_enum("NHWC", kNHWC)
diff --git a/nnvm/src/top/nn/convolution.cc b/nnvm/src/top/nn/convolution.cc
index e5f697ac631f..1de8efb22698 100644
--- a/nnvm/src/top/nn/convolution.cc
+++ b/nnvm/src/top/nn/convolution.cc
@@ -15,12 +15,12 @@ namespace nnvm {
 namespace top {
 
 // conv2d
-DMLC_REGISTER_PARAMETER(ConvParam);
+DMLC_REGISTER_PARAMETER(Conv2DParam);
 
 inline bool Conv2DInferShape(const nnvm::NodeAttrs& attrs,
                              std::vector<TShape>* in_shape,
                              std::vector<TShape>* out_shape) {
-  const ConvParam& param = nnvm::get<ConvParam>(attrs.parsed);
+  const Conv2DParam& param = nnvm::get<Conv2DParam>(attrs.parsed);
   if (param.use_bias) {
     CHECK_EQ(in_shape->size(), 3U) << "Input:[data, weight, bias]";
   } else {
@@ -51,10 +51,10 @@ inline bool Conv2DInferShape(const nnvm::NodeAttrs& attrs,
   wshape = ConvertLayout(wshape, kNCHW, param.layout);
   wshape[0] *= param.groups;
 
-  NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, ConvParam::kWeight, wshape);
+  NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, Conv2DParam::kWeight, wshape);
   if (param.use_bias) {
     NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape,
-                            ConvParam::kBias, TShape({param.channels}));
+                            Conv2DParam::kBias, TShape({param.channels}));
   }
   // dilation
   dim_t dilated_ksize_y = 1 + (param.kernel_size[0] - 1) * param.dilation[0];
@@ -79,7 +79,7 @@ inline bool Conv2DInferShape(const nnvm::NodeAttrs& attrs,
   if (oshape[3] && param.strides[1] == 1) {
     dshape[3] = oshape[3] + dilated_ksize_x - 1 - 2 * param.padding[1];
   }
-  NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, ConvParam::kData,
+  NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, Conv2DParam::kData,
                           ConvertLayout(dshape, kNCHW, param.layout));
   // Check whether the kernel sizes are valid
   if (dshape[2] != 0) {
@@ -112,29 +112,29 @@ a bias vector is created and added to the outputs.
 .add_argument("data", "4D Tensor", "Input data.")
 .add_argument("weight", "4D Tensor", "Weight matrix.")
 .add_argument("bias", "1D Tensor", "Bias parameter.")
-.add_arguments(ConvParam::__FIELDS__())
-.set_attr_parser(ParamParser<ConvParam>)
+.add_arguments(Conv2DParam::__FIELDS__())
+.set_attr_parser(ParamParser<Conv2DParam>)
 .set_num_outputs(1)
-.set_num_inputs(UseBiasNumInputs<ConvParam>)
-.set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<ConvParam>)
+.set_num_inputs(UseBiasNumInputs<Conv2DParam>)
+.set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<Conv2DParam>)
 .set_attr<FInferShape>("FInferShape", Conv2DInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
 .set_support_level(2);
 
 
-DMLC_REGISTER_PARAMETER(ConvTransposeParam);
+DMLC_REGISTER_PARAMETER(Conv2DTransposeParam);
 
-inline bool ConvTransposeInferShape(const nnvm::NodeAttrs& attrs,
-                                    std::vector<TShape>* in_shape,
-                                    std::vector<TShape>* out_shape) {
-  const ConvTransposeParam& param = nnvm::get<ConvTransposeParam>(attrs.parsed);
+inline bool Conv2DTransposeInferShape(const nnvm::NodeAttrs& attrs,
+                                      std::vector<TShape>* in_shape,
+                                      std::vector<TShape>* out_shape) {
+  const Conv2DTransposeParam& param = nnvm::get<Conv2DTransposeParam>(attrs.parsed);
   if (param.use_bias) {
     CHECK_EQ(in_shape->size(), 3U) << "Input:[data, weight, bias]";
   } else {
     CHECK_EQ(in_shape->size(), 2U) << "Input:[data, weight]";
   }
   CHECK_EQ(out_shape->size(), 1U);
-  const TShape& dshape = (*in_shape)[ConvTransposeParam::kData];
+  const TShape& dshape = (*in_shape)[Conv2DTransposeParam::kData];
   if (dshape.ndim() ==  0) return false;
   TShape dshape_nchw = ConvertLayout(dshape, param.layout, kNCHW);
 
@@ -154,11 +154,11 @@ inline bool ConvTransposeInferShape(const nnvm::NodeAttrs& attrs,
                  param.kernel_size[0], param.kernel_size[1]});
   wshape = ConvertLayout(wshape, kNCHW, param.layout);
 
-  NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, ConvTransposeParam::kWeight, wshape);
+  NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, Conv2DTransposeParam::kWeight, wshape);
 
   if (param.use_bias) {
     NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape,
-                            ConvTransposeParam::kBias,
+                            Conv2DTransposeParam::kBias,
                             TShape({param.channels}));
   }
   // dilation
@@ -201,12 +201,12 @@ said convolution.
 .add_argument("data", "4D Tensor", "Input data.")
 .add_argument("weight", "4D Tensor", "Weight matrix.")
 .add_argument("bias", "1D Tensor", "Bias parameter.")
-.add_arguments(ConvTransposeParam::__FIELDS__())
-.set_attr_parser(ParamParser<ConvTransposeParam>)
+.add_arguments(Conv2DTransposeParam::__FIELDS__())
+.set_attr_parser(ParamParser<Conv2DTransposeParam>)
 .set_num_outputs(1)
-.set_num_inputs(UseBiasNumInputs<ConvTransposeParam>)
-.set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<ConvTransposeParam>)
-.set_attr<FInferShape>("FInferShape", ConvTransposeInferShape)
+.set_num_inputs(UseBiasNumInputs<Conv2DTransposeParam>)
+.set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<Conv2DTransposeParam>)
+.set_attr<FInferShape>("FInferShape", Conv2DTransposeInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
 .set_support_level(2);
 
diff --git a/nnvm/src/top/nn/pooling.cc b/nnvm/src/top/nn/pooling.cc
index 43568eee7efa..b1ec76a5c3e1 100644
--- a/nnvm/src/top/nn/pooling.cc
+++ b/nnvm/src/top/nn/pooling.cc
@@ -14,12 +14,12 @@
 namespace nnvm {
 namespace top {
 
-DMLC_REGISTER_PARAMETER(PoolParam);
+DMLC_REGISTER_PARAMETER(Pool2DParam);
 
 inline bool Pool2DInferShape(const nnvm::NodeAttrs& attrs,
                              std::vector<TShape>* in_shape,
                              std::vector<TShape>* out_shape) {
-  const PoolParam& param = nnvm::get<PoolParam>(attrs.parsed);
+  const Pool2DParam& param = nnvm::get<Pool2DParam>(attrs.parsed);
   CHECK_EQ(in_shape->size(), 1U);
   CHECK_EQ(out_shape->size(), 1U);
 
@@ -68,8 +68,8 @@ NNVM_REGISTER_OP(max_pool2d)
 
 )code" NNVM_ADD_FILELINE)
 .add_argument("data", "4D Tensor", "Input data.")
-.add_arguments(PoolParam::__FIELDS__())
-.set_attr_parser(ParamParser<PoolParam>)
+.add_arguments(Pool2DParam::__FIELDS__())
+.set_attr_parser(ParamParser<Pool2DParam>)
 .set_num_outputs(1)
 .set_num_inputs(1)
 .set_attr<FInferShape>("FInferShape", Pool2DInferShape)
@@ -92,8 +92,8 @@ NNVM_REGISTER_OP(avg_pool2d)
 
 )code" NNVM_ADD_FILELINE)
 .add_argument("data", "4D Tensor", "Input data.")
-.add_arguments(PoolParam::__FIELDS__())
-.set_attr_parser(ParamParser<PoolParam>)
+.add_arguments(Pool2DParam::__FIELDS__())
+.set_attr_parser(ParamParser<Pool2DParam>)
 .set_num_outputs(1)
 .set_num_inputs(1)
 .set_attr<FInferShape>("FInferShape", Pool2DInferShape)
@@ -101,12 +101,12 @@ NNVM_REGISTER_OP(avg_pool2d)
 .set_support_level(2);
 
 
-DMLC_REGISTER_PARAMETER(GlobalPoolParam);
+DMLC_REGISTER_PARAMETER(GlobalPool2DParam);
 
 inline bool GlobalPool2DInferShape(const nnvm::NodeAttrs& attrs,
                                    std::vector<TShape>* in_shape,
                                    std::vector<TShape>* out_shape) {
-  const GlobalPoolParam& param = nnvm::get<GlobalPoolParam>(attrs.parsed);
+  const GlobalPool2DParam& param = nnvm::get<GlobalPool2DParam>(attrs.parsed);
   CHECK_EQ(in_shape->size(), 1U);
   CHECK_EQ(out_shape->size(), 1U);
   TShape dshape = (*in_shape)[0];
@@ -129,8 +129,8 @@ NNVM_REGISTER_OP(global_max_pool2d)
 
 )code" NNVM_ADD_FILELINE)
 .add_argument("data", "4D Tensor", "Input data.")
-.add_arguments(GlobalPoolParam::__FIELDS__())
-.set_attr_parser(ParamParser<GlobalPoolParam>)
+.add_arguments(GlobalPool2DParam::__FIELDS__())
+.set_attr_parser(ParamParser<GlobalPool2DParam>)
 .set_num_outputs(1)
 .set_num_inputs(1)
 .set_attr<FInferShape>("FInferShape", GlobalPool2DInferShape)
@@ -148,8 +148,8 @@ NNVM_REGISTER_OP(global_avg_pool2d)
 
 )code" NNVM_ADD_FILELINE)
 .add_argument("data", "4D Tensor", "Input data.")
-.add_arguments(GlobalPoolParam::__FIELDS__())
-.set_attr_parser(ParamParser<GlobalPoolParam>)
+.add_arguments(GlobalPool2DParam::__FIELDS__())
+.set_attr_parser(ParamParser<GlobalPool2DParam>)
 .set_num_outputs(1)
 .set_num_inputs(1)
 .set_attr<FInferShape>("FInferShape", GlobalPool2DInferShape)

From 8e44d0cabc728fd278d710a755f5a45a4860a6c3 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 13 Sep 2017 12:15:33 -0700
Subject: [PATCH 467/949] [TOP] level4 except argmax/min, correct split (#9)

* [TOP] level4 except argmax/min, correct split

* [DOCS] Add doc generator for top
---
 nnvm/docs/.gitignore                          |   1 +
 nnvm/docs/Makefile                            | 193 +++++++++++++++++
 nnvm/docs/conf.py                             | 182 ++++++++++++++++
 nnvm/docs/index.rst                           |  12 ++
 nnvm/docs/operators.md                        |  26 ---
 nnvm/docs/top.rst                             | 122 +++++++++++
 nnvm/include/nnvm/top/tensor.h                |  51 +++++
 nnvm/python/nnvm/_ctypes/symbol.py            |   1 +
 nnvm/python/nnvm/cython/symbol.pyx            |   1 +
 nnvm/python/nnvm/symbol.py                    |  26 +--
 nnvm/src/top/elemwise_op_common.h             |   4 +-
 nnvm/src/top/nn/nn.cc                         |   5 +
 nnvm/src/top/nn/pooling.cc                    |   4 +
 nnvm/src/top/tensor/broadcast.cc              | 199 ++++++++++++++++++
 nnvm/src/top/tensor/elemwise.cc               |  14 +-
 nnvm/src/top/tensor/reduce.cc                 | 135 ++++++++++++
 nnvm/src/top/tensor/transform.cc              | 110 ++++++++--
 .../tests/python/unittest/test_infer_shape.py |  59 +++++-
 nnvm/tests/python/unittest/test_top_level1.py |   8 +-
 nnvm/tests/python/unittest/test_top_level2.py |  19 ++
 nnvm/tests/python/unittest/test_top_level4.py |  20 ++
 21 files changed, 1129 insertions(+), 63 deletions(-)
 create mode 100644 nnvm/docs/Makefile
 create mode 100644 nnvm/docs/conf.py
 create mode 100644 nnvm/docs/index.rst
 delete mode 100644 nnvm/docs/operators.md
 create mode 100644 nnvm/docs/top.rst
 create mode 100644 nnvm/src/top/tensor/broadcast.cc
 create mode 100644 nnvm/src/top/tensor/reduce.cc
 create mode 100644 nnvm/tests/python/unittest/test_top_level2.py
 create mode 100644 nnvm/tests/python/unittest/test_top_level4.py

diff --git a/nnvm/docs/.gitignore b/nnvm/docs/.gitignore
index 8e786536c590..024fbfbe7bd0 100644
--- a/nnvm/docs/.gitignore
+++ b/nnvm/docs/.gitignore
@@ -1 +1,2 @@
 doxygen
+_build
diff --git a/nnvm/docs/Makefile b/nnvm/docs/Makefile
new file mode 100644
index 000000000000..1e45fb5e3787
--- /dev/null
+++ b/nnvm/docs/Makefile
@@ -0,0 +1,193 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# User-friendly check for sphinx-build
+ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
+$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
+endif
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html       to make standalone HTML files"
+	@echo "  dirhtml    to make HTML files named index.html in directories"
+	@echo "  singlehtml to make a single large HTML file"
+	@echo "  pickle     to make pickle files"
+	@echo "  json       to make JSON files"
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
+	@echo "  qthelp     to make HTML files and a qthelp project"
+	@echo "  applehelp  to make an Apple Help Book"
+	@echo "  devhelp    to make HTML files and a Devhelp project"
+	@echo "  epub       to make an epub"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  texinfo    to make Texinfo files"
+	@echo "  info       to make Texinfo files and run them through makeinfo"
+	@echo "  gettext    to make PO message catalogs"
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
+	@echo "  xml        to make Docutils-native XML files"
+	@echo "  pseudoxml  to make pseudoxml-XML files for display purposes"
+	@echo "  linkcheck  to check all external links for integrity"
+	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+	@echo "  coverage   to run coverage check of the documentation (if enabled)"
+
+clean:
+	rm -rf $(BUILDDIR)/*
+	rm -rf gen_modules
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/rabit.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/rabit.qhc"
+
+applehelp:
+	$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
+	@echo
+	@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
+	@echo "N.B. You won't be able to view it unless you put it in" \
+	      "~/Library/Documentation/Help or install it in your application" \
+	      "bundle."
+
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/rabit"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/rabit"
+	@echo "# devhelp"
+
+epub:
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+	@echo
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+latexpdfja:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through platex and dvipdfmx..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+	@echo
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+	@echo
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo
+	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+	@echo "Run \`make' in that directory to run these through makeinfo" \
+	      "(use \`make info' here to do that automatically)."
+
+info:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo "Running Texinfo files through makeinfo..."
+	make -C $(BUILDDIR)/texinfo info
+	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+	@echo
+	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."
+
+coverage:
+	$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
+	@echo "Testing of coverage in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/coverage/python.txt."
+
+xml:
+	$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
+	@echo
+	@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
+
+pseudoxml:
+	$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
+	@echo
+	@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
diff --git a/nnvm/docs/conf.py b/nnvm/docs/conf.py
new file mode 100644
index 000000000000..af089466e00a
--- /dev/null
+++ b/nnvm/docs/conf.py
@@ -0,0 +1,182 @@
+# -*- coding: utf-8 -*-
+#
+# documentation build configuration file, created by
+# sphinx-quickstart on Thu Jul 23 19:40:08 2015.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+import sys
+import os, subprocess
+import shlex
+import recommonmark
+from recommonmark.parser import CommonMarkParser
+from recommonmark.transform import AutoStructify
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
+sys.path.insert(0, os.path.join(curr_path, '../python/'))
+
+# -- General configuration ------------------------------------------------
+
+# General information about the project.
+project = u'nnvm'
+author = u'%s developers' % project
+copyright = u'2017, %s' % author
+github_doc_root = 'https://github.com/dmlc/nnvm/tree/master/docs/'
+
+# add markdown parser
+CommonMarkParser.github_doc_root = github_doc_root
+source_parsers = {
+    '.md': CommonMarkParser
+}
+os.environ['NNVM_BUILD_DOC'] = '1'
+# Version information.
+import nnvm
+version = nnvm.__version__
+release = nnvm.__version__
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones
+extensions = [
+    'sphinx.ext.autodoc',
+    'sphinx.ext.autosummary',
+    'sphinx.ext.intersphinx',
+    'sphinx.ext.napoleon',
+    'sphinx.ext.mathjax'
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+# source_suffix = ['.rst', '.md']
+source_suffix = ['.rst', '.md']
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# generate autosummary even if no references
+autosummary_generate = True
+
+# The master toctree document.
+master_doc = 'index'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all
+# documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+# If true, keep warnings as "system message" paragraphs in the built documents.
+#keep_warnings = False
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+todo_include_todos = False
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme is set by the make target
+html_theme = os.environ.get('NNVM_THEME', 'rtd')
+
+on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
+# only import rtd theme and set it if want to build docs locally
+if not on_rtd and html_theme == 'rtd':
+    import sphinx_rtd_theme
+    html_theme = 'sphinx_rtd_theme'
+    html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = project + 'doc'
+
+# -- Options for LaTeX output ---------------------------------------------
+latex_elements = {
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+#  author, documentclass [howto, manual, or own class]).
+latex_documents = [
+  (master_doc, '%s.tex' % project, project,
+   author, 'manual'),
+]
+
+# hook for doxygen
+def run_doxygen(folder):
+    """Run the doxygen make command in the designated folder."""
+    try:
+        #retcode = subprocess.call("cd %s; make doc" % folder, shell=True)
+        retcode = subprocess.call("rm -rf _build/html/doxygen", shell=True)
+        retcode = subprocess.call("mkdir -p _build/html", shell=True)
+        retcode = subprocess.call("cp -rf doxygen/html _build/html/doxygen", shell=True)
+        if retcode < 0:
+            sys.stderr.write("doxygen terminated by signal %s" % (-retcode))
+    except OSError as e:
+        sys.stderr.write("doxygen execution failed: %s" % e)
+
+intersphinx_mapping = {
+    'python': ('https://docs.python.org/{.major}'.format(sys.version_info), None),
+    'numpy': ('http://docs.scipy.org/doc/numpy/', None),
+    'scipy': ('http://docs.scipy.org/doc/scipy/reference', None),
+    'matplotlib': ('http://matplotlib.org/', None),
+}
+
+
+def generate_doxygen_xml(app):
+    """Run the doxygen make commands if we're on the ReadTheDocs server"""
+    run_doxygen('..')
+
+def setup(app):
+    # Add hook for building doxygen xml when needed
+    # no c++ API for now
+    app.connect("builder-inited", generate_doxygen_xml)
+    app.add_config_value('recommonmark_config', {
+        'url_resolver': lambda url: github_doc_root + url,
+        'auto_doc_ref': True
+            }, True)
+    app.add_transform(AutoStructify)
diff --git a/nnvm/docs/index.rst b/nnvm/docs/index.rst
new file mode 100644
index 000000000000..26f873dd4d3f
--- /dev/null
+++ b/nnvm/docs/index.rst
@@ -0,0 +1,12 @@
+NNVM Documentation
+==================
+Welcome to NNVM documentation.
+
+
+Contents
+--------
+
+.. toctree::
+   :maxdepth: 1
+
+   top
diff --git a/nnvm/docs/operators.md b/nnvm/docs/operators.md
deleted file mode 100644
index 4efa4086b399..000000000000
--- a/nnvm/docs/operators.md
+++ /dev/null
@@ -1,26 +0,0 @@
-NNVM Core Operator Specification
-================================
-
-Each operator attributes are stored in json format.
-tuples are stored as json array.
-
-## Tier 1: Basic Operators
-
-***Enables fully connected nets***
-
-- **dense**
-  - attributes
-     - units: int  Number of hidden units in the data.
-     - use_bias: bool Whether use bias
-  - inputs
-     - data, 2D Tensor
-     - weight, 2D Tensor
-     - bias, optional, 1D Tensor
-  - outputs
-     - output, 2D Tensor
-
-- **relu**
-  - inputs
-     - data, nD Tensor
-  - outputs
-     - output, nD Tensor
diff --git a/nnvm/docs/top.rst b/nnvm/docs/top.rst
new file mode 100644
index 000000000000..28b4a0710933
--- /dev/null
+++ b/nnvm/docs/top.rst
@@ -0,0 +1,122 @@
+NNVM Core Primitives
+====================
+
+**Level 1: Basic Ops**
+
+.. autosummary::
+   :nosignatures:
+
+   nnvm.symbol.dense
+   nnvm.symbol.relu
+   nnvm.symbol.tanh
+   nnvm.symbol.sigmoid
+   nnvm.symbol.exp
+   nnvm.symbol.log
+   nnvm.symbol.elemwise_add
+   nnvm.symbol.elemwise_sub
+   nnvm.symbol.elemwise_mul
+   nnvm.symbol.elemwise_div
+   nnvm.symbol.flatten
+   nnvm.symbol.concatenate
+   nnvm.symbol.split
+   nnvm.symbol.dropout
+   nnvm.symbol.batch_norm
+   nnvm.symbol.softmax
+   nnvm.symbol.log_softmax
+
+
+**Level 2: Convolutions**
+
+.. autosummary::
+   :nosignatures:
+
+   nnvm.symbol.conv2d
+   nnvm.symbol.conv2d_transpose
+   nnvm.symbol.max_pool2d
+   nnvm.symbol.avg_pool2d
+   nnvm.symbol.global_max_pool2d
+   nnvm.symbol.global_avg_pool2d
+
+
+**Level 3: Additional Tensor Ops**
+
+.. autosummary::
+   :nosignatures:
+
+   nnvm.symbol.reshape
+   nnvm.symbol.copy
+   nnvm.symbol.negative
+   nnvm.symbol.leaky_relu
+   nnvm.symbol.__add_scalar__
+   nnvm.symbol.__sub_scalar__
+   nnvm.symbol.__rsub_scalar__
+   nnvm.symbol.__mul_scalar__
+   nnvm.symbol.__div_scalar__
+   nnvm.symbol.__rdiv_scalar__
+   nnvm.symbol.__pow_scalar__
+   nnvm.symbol.__rpow_scalar__
+
+**Level 4: Broadcast and Reductions**
+
+.. autosummary::
+   :nosignatures:
+
+   nnvm.symbol.transpose
+   nnvm.symbol.broadcast_to
+   nnvm.symbol.sum
+   nnvm.symbol.min
+   nnvm.symbol.max
+   nnvm.symbol.broadcast_add
+   nnvm.symbol.broadcast_sub
+   nnvm.symbol.broadcast_mul
+   nnvm.symbol.broadcast_div
+
+
+.. autofunction:: nnvm.symbol.dense
+.. autofunction:: nnvm.symbol.relu
+.. autofunction:: nnvm.symbol.tanh
+.. autofunction:: nnvm.symbol.sigmoid
+.. autofunction:: nnvm.symbol.exp
+.. autofunction:: nnvm.symbol.log
+.. autofunction:: nnvm.symbol.elemwise_add
+.. autofunction:: nnvm.symbol.elemwise_sub
+.. autofunction:: nnvm.symbol.elemwise_mul
+.. autofunction:: nnvm.symbol.elemwise_div
+.. autofunction:: nnvm.symbol.flatten
+.. autofunction:: nnvm.symbol.concatenate
+.. autofunction:: nnvm.symbol.split
+.. autofunction:: nnvm.symbol.dropout
+.. autofunction:: nnvm.symbol.batch_norm
+.. autofunction:: nnvm.symbol.softmax
+.. autofunction:: nnvm.symbol.log_softmax
+
+
+.. autofunction:: nnvm.symbol.conv2d
+.. autofunction:: nnvm.symbol.conv2d_transpose
+.. autofunction:: nnvm.symbol.max_pool2d
+.. autofunction:: nnvm.symbol.avg_pool2d
+.. autofunction:: nnvm.symbol.global_max_pool2d
+.. autofunction:: nnvm.symbol.global_avg_pool2d
+
+.. autofunction:: nnvm.symbol.reshape
+.. autofunction:: nnvm.symbol.copy
+.. autofunction:: nnvm.symbol.negative
+.. autofunction:: nnvm.symbol.leaky_relu
+.. autofunction:: nnvm.symbol.__add_scalar__
+.. autofunction:: nnvm.symbol.__sub_scalar__
+.. autofunction:: nnvm.symbol.__rsub_scalar__
+.. autofunction:: nnvm.symbol.__mul_scalar__
+.. autofunction:: nnvm.symbol.__div_scalar__
+.. autofunction:: nnvm.symbol.__rdiv_scalar__
+.. autofunction:: nnvm.symbol.__pow_scalar__
+.. autofunction:: nnvm.symbol.__rpow_scalar__
+
+.. autofunction:: nnvm.symbol.transpose
+.. autofunction:: nnvm.symbol.broadcast_to
+.. autofunction:: nnvm.symbol.sum
+.. autofunction:: nnvm.symbol.min
+.. autofunction:: nnvm.symbol.max
+.. autofunction:: nnvm.symbol.broadcast_add
+.. autofunction:: nnvm.symbol.broadcast_sub
+.. autofunction:: nnvm.symbol.broadcast_mul
+.. autofunction:: nnvm.symbol.broadcast_div
diff --git a/nnvm/include/nnvm/top/tensor.h b/nnvm/include/nnvm/top/tensor.h
index 347134235ad5..45541a151d57 100644
--- a/nnvm/include/nnvm/top/tensor.h
+++ b/nnvm/include/nnvm/top/tensor.h
@@ -25,6 +25,9 @@ struct SplitParam : public dmlc::Parameter<SplitParam> {
   // numpy convention, only support indices, not support list.
   Tuple<int> indices_or_sections;
   int axis;
+  // additional hint whether it is equal_split mode
+  // deduced from indices_or_sections
+  bool equal_split;
 
   DMLC_DECLARE_PARAMETER(SplitParam) {
     DMLC_DECLARE_FIELD(indices_or_sections)
@@ -73,6 +76,54 @@ struct ScalarParam : public dmlc::Parameter<ScalarParam> {
   }
 };
 
+struct TransposeParam : public dmlc::Parameter<TransposeParam> {
+  TShape axes;
+
+  DMLC_DECLARE_PARAMETER(TransposeParam) {
+    DMLC_DECLARE_FIELD(axes).set_default(TShape())
+    .describe("Target axis order. By default the axes will be inverted.");
+  }
+};
+
+struct BroadcastToParam : public dmlc::Parameter<BroadcastToParam> {
+  TShape shape;
+
+  DMLC_DECLARE_PARAMETER(BroadcastToParam) {
+    DMLC_DECLARE_FIELD(shape).set_default(TShape())
+      .describe("The shape of the desired array."
+                " We can set the dim to zero if it's same as the original."
+                " E.g `A = broadcast_to(B, shape=(10, 0, 0))` ");
+  }
+};
+
+struct ReduceParam : public dmlc::Parameter<ReduceParam> {
+  TShape axis;
+  bool keepdims;
+  bool exclude;
+
+  DMLC_DECLARE_PARAMETER(ReduceParam) {
+    DMLC_DECLARE_FIELD(axis).set_default(TShape())
+        .describe(R"code(The axis or axes along which to perform the reduction.
+
+      The default, `axis=()`, will compute over all elements into a
+      scalar array with shape `(1,)`.
+
+      If `axis` is int, a reduction is performed on a particular axis.
+
+      If `axis` is a tuple of ints, a reduction is performed on all the axes
+      specified in the tuple.
+
+      If `exclude` is true, reduction will be performed on the axes that are
+      NOT in axis instead.)code");
+
+    DMLC_DECLARE_FIELD(keepdims).set_default(false)
+      .describe("If this is set to `True`, the reduced axes are left "
+                "in the result as dimension with size one.");
+    DMLC_DECLARE_FIELD(exclude).set_default(false)
+      .describe("Whether to perform reduction on axis that are NOT in axis instead.");
+  }
+};
+
 }  // namespace top
 }  // namespace nnvm
 
diff --git a/nnvm/python/nnvm/_ctypes/symbol.py b/nnvm/python/nnvm/_ctypes/symbol.py
index 0ae6d7a08495..e00f536d318f 100644
--- a/nnvm/python/nnvm/_ctypes/symbol.py
+++ b/nnvm/python/nnvm/_ctypes/symbol.py
@@ -219,5 +219,6 @@ def _init_symbol_module(symbol_class, root_namespace):
         function = _make_atomic_symbol_function(hdl, name)
         if function.__name__.startswith('_'):
             setattr(module_internal, function.__name__, function)
+            setattr(module_obj, function.__name__, function)
         else:
             setattr(module_obj, function.__name__, function)
diff --git a/nnvm/python/nnvm/cython/symbol.pyx b/nnvm/python/nnvm/cython/symbol.pyx
index fd7c3755786c..e2008389179f 100644
--- a/nnvm/python/nnvm/cython/symbol.pyx
+++ b/nnvm/python/nnvm/cython/symbol.pyx
@@ -213,5 +213,6 @@ def _init_symbol_module(symbol_class, root_namespace):
         function = _make_atomic_symbol_function(handle, op_names[i])
         if function.__name__.startswith('_'):
             setattr(module_internal, function.__name__, function)
+            setattr(module_obj, function.__name__, function)
         else:
             setattr(module_obj, function.__name__, function)
diff --git a/nnvm/python/nnvm/symbol.py b/nnvm/python/nnvm/symbol.py
index f22d32e0b920..5a6c9524c2ca 100644
--- a/nnvm/python/nnvm/symbol.py
+++ b/nnvm/python/nnvm/symbol.py
@@ -31,9 +31,9 @@ class Symbol(SymbolBase):
 
     def __add__(self, other):
         if isinstance(other, Symbol):
-            return _internal.__add_symbol__(self, other)
+            return __add_symbol__(self, other)
         elif isinstance(other, _Number):
-            return _internal.__add_scalar__(self, scalar=other)
+            return __add_scalar__(self, scalar=other)
         else:
             raise TypeError("type %s not supported" % str(type(other)))
 
@@ -42,23 +42,23 @@ def __radd__(self, other):
 
     def __sub__(self, other):
         if isinstance(other, Symbol):
-            return _internal.__sub_symbol__(self, other)
+            return __sub_symbol__(self, other)
         if isinstance(other, _Number):
-            return _internal.__sub_scalar__(self, scalar=other)
+            return __sub_scalar__(self, scalar=other)
         else:
             raise TypeError('type %s not supported' % str(type(other)))
 
     def __rsub__(self, other):
         if isinstance(other, _Number):
-            return _internal.__rsub_scalar__(self, scalar=other)
+            return __rsub_scalar__(self, scalar=other)
         else:
             raise TypeError('type %s not supported' % str(type(other)))
 
     def __mul__(self, other):
         if isinstance(other, Symbol):
-            return _internal.__mul_symbol__(self, other)
+            return __mul_symbol__(self, other)
         if isinstance(other, _Number):
-            return _internal.__mul_scalar__(self, scalar=other)
+            return __mul_scalar__(self, scalar=other)
         else:
             raise TypeError('type %s not supported' % str(type(other)))
 
@@ -67,15 +67,15 @@ def __rmul__(self, other):
 
     def __div__(self, other):
         if isinstance(other, Symbol):
-            return _internal.__div_symbol__(self, other)
+            return __div_symbol__(self, other)
         if isinstance(other, _Number):
-            return _internal.__div_scalar__(self, scalar=other)
+            return __div_scalar__(self, scalar=other)
         else:
             raise TypeError('type %s not supported' % str(type(other)))
 
     def __rdiv__(self, other):
         if isinstance(other, _Number):
-            return _internal.__rdiv_scalar__(self, scalar=other)
+            return __rdiv_scalar__(self, scalar=other)
         else:
             raise TypeError('type %s not supported' % str(type(other)))
 
@@ -87,15 +87,15 @@ def __rtruediv__(self, other):
 
     def __pow__(self, other):
         if isinstance(other, Symbol):
-            return _internal.__pow_symbol__(self, other)
+            return __pow_symbol__(self, other)
         if isinstance(other, _Number):
-            return _internal.__pow_scalar__(self, scalar=other)
+            return __pow_scalar__(self, scalar=other)
         else:
             raise TypeError('type %s not supported' % str(type(other)))
 
     def __rpow__(self, other):
         if isinstance(other, _Number):
-            return _internal.__rpow_scalar__(self, scalar=other)
+            return __rpow_scalar__(self, scalar=other)
         else:
             raise TypeError('type %s not supported' % str(type(other)))
 
diff --git a/nnvm/src/top/elemwise_op_common.h b/nnvm/src/top/elemwise_op_common.h
index a74482d5281d..a4d83e98bbd5 100644
--- a/nnvm/src/top/elemwise_op_common.h
+++ b/nnvm/src/top/elemwise_op_common.h
@@ -107,8 +107,8 @@ inline bool ElemwiseType(const NodeAttrs& attrs,
     [](const NodeAttrs& attrs) {                                    \
       return std::vector<std::pair<int, int> >{{0, 0}, {1, 0}};     \
     })                                                              \
-  .add_argument("lhs", "NDArray-or-Symbol", "first input")          \
-  .add_argument("rhs", "NDArray-or-Symbol", "second input")
+  .add_argument("lhs", "Tensor", "first input")                     \
+  .add_argument("rhs", "Tensor", "second input")
 
 }  // namespace top
 }  // namespace nnvm
diff --git a/nnvm/src/top/nn/nn.cc b/nnvm/src/top/nn/nn.cc
index e712d2dabe18..3dc439103b1b 100644
--- a/nnvm/src/top/nn/nn.cc
+++ b/nnvm/src/top/nn/nn.cc
@@ -98,6 +98,7 @@ NNVM_REGISTER_OP(dropout)
 .set_num_inputs(1)
 .set_num_outputs(2)
 .set_attr_parser(ParamParser<DropoutParam>)
+.add_arguments(DropoutParam::__FIELDS__())
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 2>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 2>)
 .set_attr<FNumVisibleOutputs>("FNumVisibleOutputs", [](const NodeAttrs& attrs) {
@@ -171,6 +172,7 @@ axis to be the last item in the input shape.
 .set_num_inputs(5)
 .set_num_outputs(3)
 .set_attr_parser(ParamParser<BatchNormParam>)
+.add_arguments(BatchNormParam::__FIELDS__())
 .set_attr<FInferShape>("FInferShape", BatchNormInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<5, 3>)
 .set_attr<FListInputNames>("FListInputNames", [](const NodeAttrs& attrs) {
@@ -199,6 +201,7 @@ NNVM_REGISTER_OP(softmax)
 .set_num_inputs(1)
 .set_num_outputs(1)
 .set_attr_parser(ParamParser<SoftmaxParam>)
+.add_arguments(SoftmaxParam::__FIELDS__())
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
 .set_support_level(1);
@@ -213,6 +216,7 @@ NNVM_REGISTER_OP(log_softmax)
 .set_num_inputs(1)
 .set_num_outputs(1)
 .set_attr_parser(ParamParser<SoftmaxParam>)
+.add_arguments(SoftmaxParam::__FIELDS__())
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
 .set_support_level(1);
@@ -229,6 +233,7 @@ NNVM_REGISTER_OP(leaky_relu)
 .set_num_inputs(1)
 .set_num_outputs(1)
 .set_attr_parser(ParamParser<LeakyReLUParam>)
+.add_arguments(LeakyReLUParam::__FIELDS__())
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
 .set_support_level(1);
diff --git a/nnvm/src/top/nn/pooling.cc b/nnvm/src/top/nn/pooling.cc
index b1ec76a5c3e1..299fdc66d57a 100644
--- a/nnvm/src/top/nn/pooling.cc
+++ b/nnvm/src/top/nn/pooling.cc
@@ -61,8 +61,10 @@ NNVM_REGISTER_OP(max_pool2d)
 - **out**: This depends on the `layout` parameter. Output is 4D array of shape
            (batch_size, channels, out_height, out_width)  if `layout` is `NCHW`.
            out_height and out_width are calculated as::
+
                out_height = floor((height+2*padding[0]-pool_size[0])/strides[0])+1
                out_width = floor((width+2*padding[1]-pool_size[1])/strides[1])+1
+
            When `ceil_mode` is `True`, ceil will be used instead of floor in this
            equation.
 
@@ -85,8 +87,10 @@ NNVM_REGISTER_OP(avg_pool2d)
 - **out**: This depends on the `layout` parameter. Output is 4D array of shape
            (batch_size, channels, out_height, out_width)  if `layout` is `NCHW`.
            out_height and out_width are calculated as::
+
                out_height = floor((height+2*padding[0]-pool_size[0])/strides[0])+1
                out_width = floor((width+2*padding[1]-pool_size[1])/strides[1])+1
+
            When `ceil_mode` is `True`, ceil will be used instead of floor in this
            equation.
 
diff --git a/nnvm/src/top/tensor/broadcast.cc b/nnvm/src/top/tensor/broadcast.cc
new file mode 100644
index 000000000000..d7ca979abaeb
--- /dev/null
+++ b/nnvm/src/top/tensor/broadcast.cc
@@ -0,0 +1,199 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file broadcast.cc
+ * \brief broadcast operator.
+ */
+#include <nnvm/op.h>
+#include <nnvm/node.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/top/tensor.h>
+#include "../op_common.h"
+#include "../elemwise_op_common.h"
+
+namespace nnvm {
+namespace top {
+
+// broadcast_to
+DMLC_REGISTER_PARAMETER(BroadcastToParam);
+
+inline bool BroadcastToInferShape(const NodeAttrs& attrs,
+                                  std::vector<TShape>* in_attrs,
+                                  std::vector<TShape>* out_attrs) {
+  CHECK_EQ(in_attrs->size(), 1U);
+  CHECK_EQ(out_attrs->size(), 1U);
+  const TShape& ishape = (*in_attrs)[0];
+  if (ishape.ndim() == 0) return false;
+
+  const BroadcastToParam& param = nnvm::get<BroadcastToParam>(attrs.parsed);
+  CHECK_EQ(ishape.ndim(), param.shape.ndim())
+      << "Operand of shape " << ishape
+      << " cannot be broadcasted to " << param.shape;
+  TShape oshape = param.shape;
+  for (dim_t i = 0; i < ishape.ndim(); ++i) {
+    if (oshape[i] != 0) {
+      CHECK(ishape[i] == oshape[i] || ishape[i] == 1)
+        << "Array cannot be broadcasted from " <<
+          ishape << " to " << param.shape;
+    } else {
+      oshape[i] = ishape[i];
+    }
+  }
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, 0, oshape);
+  return true;
+}
+
+NNVM_REGISTER_OP(broadcast_to)
+.describe(R"code(Broadcasts the input array to a new shape.
+
+Broadcasting is a mechanism that allows NDArrays to perform arithmetic operations
+with arrays of different shapes efficiently without creating multiple copies of arrays.
+Also see, `Broadcasting <https://docs.scipy.org/doc/numpy/user/basics.broadcasting.html>`_ for more explanation.
+
+Broadcasting is allowed on axes with size 1, such as from `(2,1,3,1)` to
+`(2,8,3,9)`. Elements will be duplicated on the broadcasted axes.
+
+For example::
+
+   broadcast_to([[1,2,3]], shape=(2,3)) = [[ 1.,  2.,  3.],
+                                           [ 1.,  2.,  3.]])
+
+The dimension which you do not want to change can also be kept as `0` which means copy the original value.
+So with `shape=(2,0)`, we will obtain the same result as in the above example.
+
+)code" NNVM_ADD_FILELINE)
+.set_attr_parser(ParamParser<BroadcastToParam>)
+.add_arguments(BroadcastToParam::__FIELDS__())
+.set_num_inputs(1)
+.set_num_outputs(1)
+.set_attr<FInferShape>("FInferShape", BroadcastToInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.add_argument("data", "Tensor", "Input data.")
+.set_support_level(4);
+
+// binary broadcast op
+inline bool BinaryBroadcastShape(const nnvm::NodeAttrs& attrs,
+                                 std::vector<TShape>* in_attrs,
+                                 std::vector<TShape>* out_attrs) {
+  CHECK_EQ(in_attrs->size(), 2U);
+  CHECK_EQ(out_attrs->size(), 1U);
+  const TShape& lhs = (*in_attrs)[0];
+  const TShape& rhs = (*in_attrs)[1];
+
+  // avoid pre-mature shape inference.
+  if (lhs.ndim() == 0 || rhs.ndim() == 0) return false;
+
+  if (lhs == rhs) {
+    NNVM_ASSIGN_INPUT_SHAPE(attrs, *out_attrs, 0, lhs);
+    return true;
+  }
+  TShape out(std::max(lhs.ndim(), rhs.ndim()));
+  dim_t bl = out.ndim() - lhs.ndim();
+  dim_t br = out.ndim() - rhs.ndim();
+  for (dim_t i = 0; i < out.ndim(); ++i) {
+    dim_t l = 1, r = 1;
+    if (i >= bl) l = lhs[i - bl];
+    if (i >= br) r = rhs[i - br];
+    if (l != r) {
+      if (l == 0 || r == 0) {
+        out[i] = 0;
+      } else {
+        CHECK(l == 1 || r == 1)
+          << "operands could not be broadcast together with shapes "
+          << lhs << " " << rhs;
+        out[i] = std::max(l, r);
+      }
+    } else {
+      out[i] = l;
+    }
+  }
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, 0, out);
+  return true;
+}
+
+
+#define NNVM_REGISTER_BINARY_BROADCAST_OP(name)                     \
+  NNVM_REGISTER_OP(name)                                            \
+  .set_num_inputs(2)                                                \
+  .set_num_outputs(1)                                               \
+  .set_attr<FInferShape>("FInferShape", BinaryBroadcastShape)       \
+  .set_attr<FInferType>("FInferType", ElemwiseType<2, 1>)           \
+  .set_attr<FInplaceOption>("FInplaceOption",                       \
+    [](const NodeAttrs& attrs) {                                    \
+      return std::vector<std::pair<int, int> >{{0, 0}, {1, 0}};     \
+    })                                                              \
+  .add_argument("lhs", "Tensor", "first input")                     \
+  .add_argument("rhs", "Tensor", "second input")
+
+
+NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_add)
+.add_alias("__add_symbol__")
+.describe(R"code(Returns element-wise sum of the input arrays with broadcasting.
+
+Example::
+
+   x = [[ 1.,  1.,  1.],
+        [ 1.,  1.,  1.]]
+
+   y = [[ 0.],
+        [ 1.]]
+
+   broadcast_add(x, y) = [[ 1.,  1.,  1.],
+                          [ 2.,  2.,  2.]]
+
+)code" NNVM_ADD_FILELINE);
+
+
+NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_sub)
+.add_alias("__sub_symbol__")
+.describe(R"code(Returns element-wise difference of the input arrays with broadcasting.
+
+Example::
+
+   x = [[ 1.,  1.,  1.],
+        [ 1.,  1.,  1.]]
+
+   y = [[ 0.],
+        [ 1.]]
+
+   broadcast_sub(x, y) = [[ 1.,  1.,  1.],
+                          [ 0.,  0.,  0.]]
+
+)code" NNVM_ADD_FILELINE);
+
+
+NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_mul)
+.add_alias("__mul_symbol__")
+.describe(R"code(Returns element-wise product of the input arrays with broadcasting.
+
+Example::
+
+   x = [[ 1.,  1.,  1.],
+        [ 1.,  1.,  1.]]
+
+   y = [[ 0.],
+        [ 1.]]
+
+   broadcast_mul(x, y) = [[ 0.,  0.,  0.],
+                          [ 1.,  1.,  1.]]
+)code" NNVM_ADD_FILELINE);
+
+
+NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_div)
+.add_alias("__div_symbol__")
+.describe(R"code(Returns element-wise division of the input arrays with broadcasting.
+
+Example::
+
+   x = [[ 6.,  6.,  6.],
+        [ 6.,  6.,  6.]]
+
+   y = [[ 2.],
+        [ 3.]]
+
+   broadcast_div(x, y) = [[ 3.,  3.,  3.],
+                          [ 2.,  2.,  2.]]
+
+)code" NNVM_ADD_FILELINE);
+
+}  // namespace top
+}  // namespace nnvm
diff --git a/nnvm/src/top/tensor/elemwise.cc b/nnvm/src/top/tensor/elemwise.cc
index af19f1c56835..dccd80abaaab 100644
--- a/nnvm/src/top/tensor/elemwise.cc
+++ b/nnvm/src/top/tensor/elemwise.cc
@@ -17,17 +17,17 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(sigmoid)
 .describe(R"code(Computes sigmoid.
 
 .. math::
-   y = 1 / (1 + exp(-x))
+  Y = 1 / (1 + exp(-X))
 
 )code" NNVM_ADD_FILELINE)
 .set_support_level(1);
 
 // tanh
 NNVM_REGISTER_ELEMWISE_UNARY_OP(tanh)
-.describe(R"code(Returns the hyperbolic tangent of the input array, computed element-wise.
+.describe(R"code(Computes hyperbolic tangent.
 
 .. math::
-   tanh(x) = sinh(x) / cosh(x)
+   Y = sinh(X) / cosh(X)
 
 )code" NNVM_ADD_FILELINE)
 .set_support_level(1);
@@ -100,6 +100,7 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(__add_scalar__)
 
 )code"  NNVM_ADD_FILELINE)
 .set_attr_parser(ParamParser<ScalarParam>)
+.add_arguments(ScalarParam::__FIELDS__())
 .set_support_level(3);
 
 NNVM_REGISTER_ELEMWISE_UNARY_OP(__sub_scalar__)
@@ -107,6 +108,7 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(__sub_scalar__)
 
 )code"  NNVM_ADD_FILELINE)
 .set_attr_parser(ParamParser<ScalarParam>)
+.add_arguments(ScalarParam::__FIELDS__())
 .set_support_level(3);
 
 NNVM_REGISTER_ELEMWISE_UNARY_OP(__rsub_scalar__)
@@ -114,6 +116,7 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(__rsub_scalar__)
 
 )code"  NNVM_ADD_FILELINE)
 .set_attr_parser(ParamParser<ScalarParam>)
+.add_arguments(ScalarParam::__FIELDS__())
 .set_support_level(3);
 
 NNVM_REGISTER_ELEMWISE_UNARY_OP(__mul_scalar__)
@@ -121,6 +124,7 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(__mul_scalar__)
 
 )code"  NNVM_ADD_FILELINE)
 .set_attr_parser(ParamParser<ScalarParam>)
+.add_arguments(ScalarParam::__FIELDS__())
 .set_support_level(3);
 
 NNVM_REGISTER_ELEMWISE_UNARY_OP(__div_scalar__)
@@ -128,6 +132,7 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(__div_scalar__)
 
 )code"  NNVM_ADD_FILELINE)
 .set_attr_parser(ParamParser<ScalarParam>)
+.add_arguments(ScalarParam::__FIELDS__())
 .set_support_level(3);
 
 NNVM_REGISTER_ELEMWISE_UNARY_OP(__rdiv_scalar__)
@@ -135,6 +140,7 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(__rdiv_scalar__)
 
 )code"  NNVM_ADD_FILELINE)
 .set_attr_parser(ParamParser<ScalarParam>)
+.add_arguments(ScalarParam::__FIELDS__())
 .set_support_level(3);
 
 NNVM_REGISTER_ELEMWISE_UNARY_OP(__pow_scalar__)
@@ -142,6 +148,7 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(__pow_scalar__)
 
 )code"  NNVM_ADD_FILELINE)
 .set_attr_parser(ParamParser<ScalarParam>)
+.add_arguments(ScalarParam::__FIELDS__())
 .set_support_level(3);
 
 NNVM_REGISTER_ELEMWISE_UNARY_OP(__rpow_scalar__)
@@ -149,6 +156,7 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(__rpow_scalar__)
 
 )code"  NNVM_ADD_FILELINE)
 .set_attr_parser(ParamParser<ScalarParam>)
+.add_arguments(ScalarParam::__FIELDS__())
 .set_support_level(3);
 
 
diff --git a/nnvm/src/top/tensor/reduce.cc b/nnvm/src/top/tensor/reduce.cc
new file mode 100644
index 000000000000..58307095bcbb
--- /dev/null
+++ b/nnvm/src/top/tensor/reduce.cc
@@ -0,0 +1,135 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file reduce.cc
+ * \brief reduce operator.
+ */
+#include <nnvm/op.h>
+#include <nnvm/node.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/top/tensor.h>
+#include "../op_common.h"
+#include "../elemwise_op_common.h"
+
+namespace nnvm {
+namespace top {
+
+// reduce
+DMLC_REGISTER_PARAMETER(ReduceParam);
+
+inline TShape ReduceShapeImpl(const TShape& ishape,
+                              const TShape& axis,
+                              bool keepdims,
+                              bool exclude) {
+  if (axis.ndim() == 0) {
+    if (keepdims) {
+      return TShape(ishape.ndim());
+    } else {
+      return TShape(1);
+    }
+  }
+  CHECK_LT(axis[axis.ndim() - 1], ishape.ndim())
+    << "Reduction axis " << axis[axis.ndim() - 1]
+    << " Exceeds input dimensions " << ishape;
+
+  if (keepdims) {
+    TShape oshape(ishape);
+    if (exclude) {
+      for (dim_t i = 0, j = 0; i < ishape.ndim(); ++i) {
+        if (j < axis.ndim() && i == axis[j]) {
+          ++j;
+          continue;
+        }
+        oshape[i] = 1;
+      }
+      return oshape;
+    }
+
+    for (dim_t i = 0; i < axis.ndim(); ++i) {
+      oshape[axis[i]] = 1;
+    }
+    return oshape;
+  }
+
+  if (exclude) {
+    TShape oshape = TShape(axis.ndim());
+    for (dim_t i = 0; i < axis.ndim(); ++i) {
+      oshape[i] = ishape[axis[i]];
+    }
+    return oshape;
+  }
+  TShape oshape = TShape(std::max<dim_t>(1, ishape.ndim() - axis.ndim()));
+  for (dim_t i = 0, j = 0, k = 0; i < ishape.ndim(); ++i) {
+    if (j < axis.ndim() && i == axis[j]) {
+      ++j;
+      continue;
+    }
+    oshape[k++] = ishape[i];
+  }
+  return oshape;
+}
+
+inline bool ReduceShape(const nnvm::NodeAttrs& attrs,
+                        std::vector<TShape>* in_attrs,
+                        std::vector<TShape>* out_attrs) {
+  CHECK_EQ(in_attrs->size(), 1U);
+  CHECK_EQ(out_attrs->size(), 1U);
+  if ((*in_attrs)[0].ndim() == 0) return false;
+  const ReduceParam& param = nnvm::get<ReduceParam>(attrs.parsed);
+  NNVM_ASSIGN_INPUT_SHAPE(
+      attrs, *out_attrs, 0,
+      ReduceShapeImpl((*in_attrs)[0], param.axis,
+                      param.keepdims, param.exclude));
+  return true;
+}
+
+template<typename PType>
+inline void AxesParamParser(nnvm::NodeAttrs* attrs) {
+  PType param;
+  param.Init(attrs->dict);
+  std::sort(&param.axis[0], &param.axis[param.axis.ndim()]);
+  attrs->parsed = std::move(param);
+}
+
+#define NNVM_REGISTER_REDUCE_OP(op)                             \
+  NNVM_REGISTER_OP(op)                                          \
+  .set_num_inputs(1)                                            \
+  .set_num_outputs(1)                                           \
+  .set_attr_parser(AxesParamParser<ReduceParam>)                \
+  .set_attr<FInferShape>("FInferShape", ReduceShape)            \
+  .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)       \
+  .add_argument("data", "Tensor", "The input")                  \
+  .add_arguments(ReduceParam::__FIELDS__())
+
+
+NNVM_REGISTER_REDUCE_OP(sum)
+.describe(R"code(Computes the sum of array elements over given axes.
+
+Example::
+
+  data = [[[1,2],[2,3],[1,3]],
+          [[1,4],[4,3],[5,2]],
+          [[7,1],[7,2],[7,3]]]
+
+  sum(data, axis=1)
+  [[  4.   8.]
+   [ 10.   9.]
+   [ 21.   6.]]
+
+  sum(data, axis=[1,2])
+  [ 12.  19.  27.]
+
+)code" NNVM_ADD_FILELINE);
+
+NNVM_REGISTER_REDUCE_OP(max)
+.describe(R"code(Computes the max of array elements over given axes.
+
+)code" NNVM_ADD_FILELINE);
+
+NNVM_REGISTER_REDUCE_OP(min)
+.describe(R"code(Computes the min of array elements over given axes.
+
+)code" NNVM_ADD_FILELINE);
+
+
+}  // namespace top
+}  // namespace nnvm
diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
index 2e065a3f9f51..18ecfd6f0b1b 100644
--- a/nnvm/src/top/tensor/transform.cc
+++ b/nnvm/src/top/tensor/transform.cc
@@ -7,6 +7,7 @@
 #include <nnvm/node.h>
 #include <nnvm/op_attr_types.h>
 #include <nnvm/top/tensor.h>
+#include <cctype>
 #include "../op_common.h"
 #include "../elemwise_op_common.h"
 
@@ -31,7 +32,7 @@ inline bool FlattenInferShape(const NodeAttrs& attrs,
 }
 
 NNVM_REGISTER_OP(flatten)
-.describe(R"code(Flattens the input array into a 2-D array by collapsing the higher dimensions.
+.describe(R"code(Flattens the input into a 2-D array.
 
 For an input array with shape ``(d1, d2, ..., dk)``, `flatten` operation reshapes
 the input array into an output array of shape ``(d1, d2*...*dk)``.
@@ -134,16 +135,28 @@ Example::
 .set_num_outputs(1)
 .set_num_inputs(kVarg)
 .set_attr_parser(ParamParser<ConcatenateParam>)
+.add_arguments(ConcatenateParam::__FIELDS__())
 .add_argument("data", "Tensor-or-Tensor[]", "List of arrays to concatenate")
 .set_attr<FInferShape>("FInferShape", ConcatenateInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
-.add_arguments(ConcatenateParam::__FIELDS__())
 .set_support_level(1);
 
 
 // concatenate
 DMLC_REGISTER_PARAMETER(SplitParam);
 
+inline void SplitParamParser(nnvm::NodeAttrs* attrs) {
+  SplitParam param;
+  param.Init(attrs->dict);
+  if (!std::isdigit(attrs->dict.at("indices_or_sections")[0])) {
+    param.equal_split = false;
+  } else {
+    CHECK_EQ(param.indices_or_sections.ndim(), 1);
+    param.equal_split = true;
+  }
+  attrs->parsed = std::move(param);
+}
+
 inline bool SplitInferShape(const NodeAttrs& attrs,
                             std::vector<TShape>* in_shape,
                             std::vector<TShape>* out_shape) {
@@ -151,7 +164,7 @@ inline bool SplitInferShape(const NodeAttrs& attrs,
   const TShape& dshape = (*in_shape)[0];
   if (dshape.ndim() == 0) return false;
 
-  if (param.indices_or_sections.ndim() == 1) {
+  if (param.equal_split) {
     int num_outputs = param.indices_or_sections[0];
     CHECK_EQ(out_shape->size(), static_cast<size_t>(num_outputs));
     CHECK_LT(param.axis, dshape.ndim());
@@ -164,30 +177,30 @@ inline bool SplitInferShape(const NodeAttrs& attrs,
       NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, i, oshape);
     }
   } else {
-    dim_t num_outputs = param.indices_or_sections.ndim();
+    dim_t num_outputs = param.indices_or_sections.ndim() + 1;
     CHECK_EQ(out_shape->size(), static_cast<size_t>(num_outputs));
     CHECK_LT(param.axis, dshape.ndim());
     TShape oshape = dshape;
-    CHECK_EQ(oshape[param.axis] % num_outputs, 0)
-        << "indices_or_sections need to be able to divide input.shape[axis]";
     dim_t total = 0;
-    for (size_t i = 0; i < out_shape->size(); ++i) {
-      oshape[param.axis] = param.indices_or_sections[i];
+    for (size_t i = 1; i < num_outputs; ++i) {
+      oshape[param.axis] = param.indices_or_sections[i - 1];
       total += oshape[param.axis];
-      NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, i, oshape);
+      NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, i - 1, oshape);
     }
-    CHECK_EQ(total, dshape[param.axis])
+    CHECK_LT(total, dshape[param.axis])
         << "The sum of sections must match the input.shape[axis]";
+    oshape[param.axis] = dshape[param.axis] - total;
+    NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, num_outputs - 1, oshape);
   }
   return true;
 }
 
 inline uint32_t SplitNumOutputs(const NodeAttrs& attrs) {
   const SplitParam& param = nnvm::get<SplitParam>(attrs.parsed);
-  if (param.indices_or_sections.ndim() == 1) {
+  if (param.equal_split) {
     return static_cast<uint32_t>(param.indices_or_sections[0]);
   } else {
-    return static_cast<uint32_t>(param.indices_or_sections.ndim());
+    return static_cast<uint32_t>(param.indices_or_sections.ndim()) + 1;
   }
 }
 
@@ -199,12 +212,12 @@ along which to split the array.
 
 )code" NNVM_ADD_FILELINE)
 .set_num_inputs(1)
-.set_attr_parser(ParamParser<SplitParam>)
+.set_attr_parser(SplitParamParser)
 .set_num_outputs(SplitNumOutputs)
+.add_arguments(SplitParam::__FIELDS__())
 .add_argument("data", "Tensor", "List of arrays to concatenate")
 .set_attr<FInferShape>("FInferShape", SplitInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
-.add_arguments(SplitParam::__FIELDS__())
 .set_support_level(1);
 
 // cast
@@ -225,9 +238,9 @@ NNVM_REGISTER_OP(cast)
 )code" NNVM_ADD_FILELINE)
 .add_argument("data", "Tensor", "Input data array")
 .set_attr_parser(ParamParser<CastParam>)
+.add_arguments(CastParam::__FIELDS__())
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", CastInferType)
-.add_arguments(CastParam::__FIELDS__())
 .set_num_inputs(1)
 .set_num_outputs(1)
 .set_support_level(1);
@@ -377,10 +390,77 @@ The significance of each is explained below:
 .set_num_inputs(1)
 .set_num_outputs(1)
 .set_attr_parser(ParamParser<ReshapeParam>)
+.add_arguments(ReshapeParam::__FIELDS__())
 .set_attr<FInferShape>("FInferShape", ReshapeInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
 .add_argument("data", "Tensor", "Input data.")
 .set_support_level(3);
 
+// tranpose
+DMLC_REGISTER_PARAMETER(TransposeParam);
+
+inline bool TransposeShape(const nnvm::NodeAttrs& attrs,
+                           std::vector<TShape>* in_attrs,
+                           std::vector<TShape>* out_attrs) {
+  const TransposeParam& param = nnvm::get<TransposeParam>(attrs.parsed);
+  CHECK_EQ(in_attrs->size(), 1U);
+  CHECK_EQ(out_attrs->size(), 1U);
+  const TShape& shp = (*in_attrs)[0];
+  if (shp.ndim() == 0) return false;
+
+  TShape ret(shp.ndim());
+  if (param.axes.ndim() == 0) {
+    for (dim_t i = 0; i < shp.ndim(); ++i) {
+      ret[i] = shp[shp.ndim() - 1 - i];
+    }
+  } else {
+    CHECK_EQ(shp.ndim(), param.axes.ndim());
+    for (size_t i = 0; i < shp.ndim(); ++i) {
+      CHECK(param.axes[i] < shp.ndim());
+      ret[i] = shp[param.axes[i]];
+    }
+  }
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, 0, ret);
+  return true;
+}
+
+NNVM_REGISTER_OP(transpose)
+.describe(R"code(Permutes the dimensions of an array.
+
+Examples::
+
+  x = [[ 1, 2],
+       [ 3, 4]]
+
+  transpose(x) = [[ 1.,  3.],
+                  [ 2.,  4.]]
+
+  x = [[[ 1.,  2.],
+        [ 3.,  4.]],
+
+       [[ 5.,  6.],
+        [ 7.,  8.]]]
+
+  transpose(x) = [[[ 1.,  5.],
+                   [ 3.,  7.]],
+
+                  [[ 2.,  6.],
+                   [ 4.,  8.]]]
+
+  transpose(x, axes=(1,0,2)) = [[[ 1.,  2.],
+                                 [ 5.,  6.]],
+
+                                [[ 3.,  4.],
+                                 [ 7.,  8.]]]
+)code" NNVM_ADD_FILELINE)
+.set_num_inputs(1)
+.set_num_outputs(1)
+.set_attr_parser(ParamParser<TransposeParam>)
+.add_arguments(TransposeParam::__FIELDS__())
+.set_attr<nnvm::FInferShape>("FInferShape", TransposeShape)
+.set_attr<nnvm::FInferType>("FInferType", ElemwiseType<1, 1>)
+.add_argument("data", "Tensor", "Source input")
+.set_support_level(4);
+
 }  // namespace top
 }  // namespace nnvm
diff --git a/nnvm/tests/python/unittest/test_infer_shape.py b/nnvm/tests/python/unittest/test_infer_shape.py
index 2488ec273d47..c7d79f3bcdf6 100644
--- a/nnvm/tests/python/unittest/test_infer_shape.py
+++ b/nnvm/tests/python/unittest/test_infer_shape.py
@@ -38,7 +38,7 @@ def test_concatenate():
 
 def test_split():
     x1 = sym.Variable("x", shape=(10, 20))
-    z = sym.split(x1, indices_or_sections=[11, 9], name="y")
+    z = sym.split(x1, indices_or_sections=[11], name="y")
     sdict = infer_shape(z)
     assert(sdict["y"][0] == [10, 11])
     assert(sdict["y"][1] == [10, 9])
@@ -195,6 +195,59 @@ def check(in_shape, tshape, out_shape):
     check((2, 3, 4), (-4, 1, 2, -2), (1, 2, 3, 4))
     check((2, 3, 4), (2, -4, -1, 3, -2), (2, 1, 3, 4))
 
+
+# Level 4
+def test_transpose():
+    def check(in_shape, out_shape, **kwargs):
+        x = sym.Variable("x", shape=in_shape)
+        y = sym.transpose(x, name="y", **kwargs)
+        sdict = infer_shape(y)
+        assert(tuple(sdict["y"][0]) == tuple(out_shape))
+
+    check((4, 1), (1, 4))
+    check((0, 1, 2, 3), (1, 2, 3, 0), axes=(1, 2, 3, 0))
+
+
+def test_broadcast_to():
+    def check(in_shape, tshape, out_shape):
+        x = sym.Variable("x", shape=in_shape)
+        y = sym.broadcast_to(x, shape=tshape, name="y")
+        sdict = infer_shape(y)
+        assert(tuple(sdict["y"][0]) == tuple(out_shape))
+
+    check((4, 1), (0, 4), (4, 4))
+    check((4, 1, 5), (0, 4, 5), (4, 4, 5))
+
+
+def test_broadcast_binary():
+    def check(lhs_shape, rhs_shape, out_shape):
+        x = sym.Variable("x", shape=lhs_shape)
+        y = sym.Variable("y", shape=rhs_shape)
+        z = sym.broadcast_add(x, y, name="y")
+        sdict = infer_shape(z)
+        assert(tuple(sdict["y"][0]) == tuple(out_shape))
+
+    check((4, 1), (4), (4, 4))
+    check((5, 1, 1), (1, 4, 4), (5, 4, 4))
+    check((6, 1, 4), (5, 4), (6, 5, 4))
+
+
+def test_reduce():
+    def check(in_shape, out_shape, **kwargs):
+        x = sym.Variable("x", shape=in_shape)
+        y = sym.sum(x, name="y", **kwargs)
+        sdict = infer_shape(y)
+        assert(tuple(sdict["y"][0]) == tuple(out_shape))
+
+    check((4, 5), (4,), axis=1)
+    check((4, 5), (4, 1), axis=1, keepdims=True)
+    check((4, 5), (1, 5), axis=0, keepdims=True)
+    check((4, 5), (1, 1), axis=(), keepdims=True)
+    check((4, 5), (1,), axis=())
+    check((4, 5, 10), (5,), axis=(0, 2))
+    check((4, 5, 10), (1, 5, 1), axis=(0, 2), keepdims=True)
+
+
 if __name__ == "__main__":
     test_dense()
     test_concatenate()
@@ -206,3 +259,7 @@ def check(in_shape, tshape, out_shape):
     test_max_pool2d()
     test_global_pool2d()
     test_reshape()
+    test_broadcast_to()
+    test_broadcast_binary()
+    test_reduce()
+    test_transpose()
diff --git a/nnvm/tests/python/unittest/test_top_level1.py b/nnvm/tests/python/unittest/test_top_level1.py
index 13eaf0d6fab3..a53b664acb16 100644
--- a/nnvm/tests/python/unittest/test_top_level1.py
+++ b/nnvm/tests/python/unittest/test_top_level1.py
@@ -1,12 +1,13 @@
 import nnvm.symbol as sym
 
-def test_fullc():
+def test_dense():
     x = sym.Variable('x')
     x1 = sym.dense(x, units=3, name="dense")
     x2 = sym.flatten(x1)
     x3 = sym.softmax(x2)
     assert x2.list_input_names() == ['x', 'dense_weight', 'dense_bias']
 
+
 def test_concatenate_split():
     x = sym.Variable('x')
     y = sym.Variable('y')
@@ -15,7 +16,7 @@ def test_concatenate_split():
     z = sym.split(y, indices_or_sections=10)
     assert len(z.list_output_names()) == 10
     z = sym.split(y, indices_or_sections=[10, 20])
-    assert len(z.list_output_names()) == 2
+    assert len(z.list_output_names()) == 3
 
 
 def test_unary():
@@ -26,6 +27,7 @@ def test_unary():
     x = sym.tanh(x)
     assert x.list_input_names() == ['x']
 
+
 def test_batchnorm():
     x = sym.Variable('x')
     x = sym.batch_norm(x, name="bn")
@@ -35,6 +37,6 @@ def test_batchnorm():
 
 if __name__ == "__main__":
     test_concatenate_split()
-    test_fullc()
+    test_dense()
     test_unary()
     test_batchnorm()
diff --git a/nnvm/tests/python/unittest/test_top_level2.py b/nnvm/tests/python/unittest/test_top_level2.py
new file mode 100644
index 000000000000..df34549fbe46
--- /dev/null
+++ b/nnvm/tests/python/unittest/test_top_level2.py
@@ -0,0 +1,19 @@
+import nnvm.symbol as sym
+
+def test_conv2d():
+    x = sym.Variable('x')
+    y = sym.conv2d(x, channels=3, kernel_size=(3, 3),
+                   name="y", use_bias=False)
+    assert y.list_input_names() == ["x", "y_weight"]
+
+
+def test_max_pool2d():
+    x = sym.Variable('x')
+    y = sym.max_pool2d(x, pool_size=(3, 3), name="y")
+    y = sym.global_max_pool2d(y)
+    assert y.list_input_names() == ["x"]
+
+
+if __name__ == "__main__":
+    test_conv2d()
+    test_max_pool2d()
diff --git a/nnvm/tests/python/unittest/test_top_level4.py b/nnvm/tests/python/unittest/test_top_level4.py
new file mode 100644
index 000000000000..fe90b2b1cc5c
--- /dev/null
+++ b/nnvm/tests/python/unittest/test_top_level4.py
@@ -0,0 +1,20 @@
+import nnvm.symbol as sym
+
+def test_binary_broadcast():
+    x = sym.Variable('x')
+    y = sym.Variable('y')
+    z = x + y
+    z = x * y
+    z = x - y
+    z = x / y
+
+
+def test_broadcast_to():
+    x = sym.Variable('x')
+    y = sym.broadcast_to(x, shape=(3, 3))
+    assert y.list_input_names() == ["x"]
+
+
+if __name__ == "__main__":
+    test_binary_broadcast()
+    test_broadcast_to()

From f0962a1f949976c8e3b6982af2b4f4d376547825 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 13 Sep 2017 12:47:25 -0700
Subject: [PATCH 468/949] [TOP] Add comments about optimizable ops (#10)

---
 nnvm/src/top/nn/nn.cc | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/nnvm/src/top/nn/nn.cc b/nnvm/src/top/nn/nn.cc
index 3dc439103b1b..ee05b6be57a5 100644
--- a/nnvm/src/top/nn/nn.cc
+++ b/nnvm/src/top/nn/nn.cc
@@ -163,6 +163,9 @@ by::
 The parameter ``axis`` specifies which axis of the input shape denotes
 the 'channel' (separately normalized groups).  The default is 1.  Specifying -1 sets the channel
 axis to be the last item in the input shape.
+
+.. note::
+    This operator can be optimized away for inference.
 )" NNVM_ADD_FILELINE)
 .add_argument("data", "Tensor", "Input to which dropout will be applied")
 .add_argument("gamma", "Tensor", "The gamma scale factor")
@@ -197,6 +200,8 @@ NNVM_REGISTER_OP(softmax)
 
 .. math:: \text{softmax}(x)_i = \frac{exp(x_i)}{\sum_j exp(x_j)}
 
+.. note::
+    This operator can be optimized away for inference.
 )code" NNVM_ADD_FILELINE)
 .set_num_inputs(1)
 .set_num_outputs(1)
@@ -208,10 +213,12 @@ NNVM_REGISTER_OP(softmax)
 
 // log_softmax
 NNVM_REGISTER_OP(log_softmax)
-.describe(R"code(Computes softmax.
+.describe(R"code(Computes log softmax.
 
 .. math:: \text{log_softmax}(x)_i = \log \frac{exp(x_i)}{\sum_j exp(x_j)}
 
+.. note::
+    This operator can be optimized away for inference.
 )code" NNVM_ADD_FILELINE)
 .set_num_inputs(1)
 .set_num_outputs(1)

From 7be605fe0a8b7295664309ee5019c5e7e30b4771 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 16 Sep 2017 19:28:00 -0700
Subject: [PATCH 469/949] [TOP] GraphExecutor (#11)

---
 nnvm/Makefile                      |   9 +-
 nnvm/amalgamation/.gitignore       |   2 -
 nnvm/amalgamation/Makefile         |  32 ---
 nnvm/amalgamation/amalgamation.py  | 100 ---------
 nnvm/amalgamation/generate.py      |  18 --
 nnvm/deploy/REAMD.md               |   4 +
 nnvm/deploy/nnvm_runtime.cc        |  11 +
 nnvm/example/src/operator.cc       | 196 -----------------
 nnvm/include/nnvm/node.h           |   2 -
 nnvm/include/nnvm/tuple.h          |   6 +-
 nnvm/src/README.md                 |  14 ++
 nnvm/src/runtime/graph_executor.cc | 334 +++++++++++++++++++++++++++++
 nnvm/src/runtime/graph_executor.h  | 150 +++++++++++++
 nnvm/src/top/README.md             |   2 -
 14 files changed, 523 insertions(+), 357 deletions(-)
 delete mode 100644 nnvm/amalgamation/.gitignore
 delete mode 100644 nnvm/amalgamation/Makefile
 delete mode 100644 nnvm/amalgamation/amalgamation.py
 delete mode 100644 nnvm/amalgamation/generate.py
 create mode 100644 nnvm/deploy/REAMD.md
 create mode 100644 nnvm/deploy/nnvm_runtime.cc
 delete mode 100644 nnvm/example/src/operator.cc
 create mode 100644 nnvm/src/README.md
 create mode 100644 nnvm/src/runtime/graph_executor.cc
 create mode 100644 nnvm/src/runtime/graph_executor.h
 delete mode 100644 nnvm/src/top/README.md

diff --git a/nnvm/Makefile b/nnvm/Makefile
index 009e3f9eeec8..868ab48eea38 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -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
 
diff --git a/nnvm/amalgamation/.gitignore b/nnvm/amalgamation/.gitignore
deleted file mode 100644
index e808ea2764c3..000000000000
--- a/nnvm/amalgamation/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-nnvm.d
-nnvm.cc
diff --git a/nnvm/amalgamation/Makefile b/nnvm/amalgamation/Makefile
deleted file mode 100644
index 1f286f055237..000000000000
--- a/nnvm/amalgamation/Makefile
+++ /dev/null
@@ -1,32 +0,0 @@
-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
diff --git a/nnvm/amalgamation/amalgamation.py b/nnvm/amalgamation/amalgamation.py
deleted file mode 100644
index 310daa9d68e0..000000000000
--- a/nnvm/amalgamation/amalgamation.py
+++ /dev/null
@@ -1,100 +0,0 @@
-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
-
diff --git a/nnvm/amalgamation/generate.py b/nnvm/amalgamation/generate.py
deleted file mode 100644
index 84a5fc06fb03..000000000000
--- a/nnvm/amalgamation/generate.py
+++ /dev/null
@@ -1,18 +0,0 @@
-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()
diff --git a/nnvm/deploy/REAMD.md b/nnvm/deploy/REAMD.md
new file mode 100644
index 000000000000..96ab18d7514b
--- /dev/null
+++ b/nnvm/deploy/REAMD.md
@@ -0,0 +1,4 @@
+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
diff --git a/nnvm/deploy/nnvm_runtime.cc b/nnvm/deploy/nnvm_runtime.cc
new file mode 100644
index 000000000000..15c46012f6ec
--- /dev/null
+++ b/nnvm/deploy/nnvm_runtime.cc
@@ -0,0 +1,11 @@
+/*!
+ *  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"
diff --git a/nnvm/example/src/operator.cc b/nnvm/example/src/operator.cc
deleted file mode 100644
index 34e4529ecb0b..000000000000
--- a/nnvm/example/src/operator.cc
+++ /dev/null
@@ -1,196 +0,0 @@
-// 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
diff --git a/nnvm/include/nnvm/node.h b/nnvm/include/nnvm/node.h
index 54f31e745c03..0e46e23f25e9 100644
--- a/nnvm/include/nnvm/node.h
+++ b/nnvm/include/nnvm/node.h
@@ -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;
   /*!
diff --git a/nnvm/include/nnvm/tuple.h b/nnvm/include/nnvm/tuple.h
index b2d049535de9..b3193ca29eb8 100644
--- a/nnvm/include/nnvm/tuple.h
+++ b/nnvm/include/nnvm/tuple.h
@@ -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;
     }
diff --git a/nnvm/src/README.md b/nnvm/src/README.md
new file mode 100644
index 000000000000..da3584a73cb1
--- /dev/null
+++ b/nnvm/src/README.md
@@ -0,0 +1,14 @@
+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
diff --git a/nnvm/src/runtime/graph_executor.cc b/nnvm/src/runtime/graph_executor.cc
new file mode 100644
index 000000000000..5bdf1979dfa8
--- /dev/null
+++ b/nnvm/src/runtime/graph_executor.cc
@@ -0,0 +1,334 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file graph_executor.cc
+ */
+#include <dmlc/memory_io.h>
+#include <tvm/runtime/registry.h>
+#include <numeric>
+#include "./graph_executor.h"
+
+namespace nnvm {
+namespace runtime {
+
+/*! \brief macro to do C API call */
+#define TVM_CCALL(func)                                            \
+  {                                                                \
+    int ret = (func);                                              \
+    CHECK_EQ(ret, 0)                                               \
+        << TVMGetLastError();                                      \
+  }
+
+using ::tvm::runtime::PackedFunc;
+using ::tvm::runtime::TVMArgs;
+using ::tvm::runtime::TVMRetValue;
+
+PackedFunc GraphExecutor::GetFunction(
+    const std::string& name,
+    const std::shared_ptr<ModuleNode>& sptr_to_self) {
+  // return member functions during query.
+  if (name == "set_input") {
+    return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
+        if (args[0].type_code() == kStr) {
+          this->SetInput(this->GetInputIndex(args[0]), args[1]);
+        } else {
+          this->SetInput(args[0], args[1]);
+        }
+      });
+  } else if (name == "get_output") {
+    return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
+        this->GetOutput(args[0], args[1]);
+      });
+  } else if (name == "run") {
+    return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
+        this->Run();
+      });
+  } else if (name == "load_params") {
+    return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
+        this->LoadParams(args[0].operator std::string());
+      });
+  } else {
+    return PackedFunc();
+  }
+}
+
+GraphExecutor::~GraphExecutor() {
+  for (DLTensor* t : storage_pool_) {
+    TVM_CCALL(TVMArrayFree(t));
+  }
+}
+
+void GraphExecutor::Run() {
+  // setup the array and requirements.
+  for (size_t i = 0; i < op_execs_.size(); ++i) {
+    if (op_execs_[i]) op_execs_[i]();
+  }
+}
+
+void GraphExecutor::Init(Graph graph,
+                         tvm::runtime::Module module,
+                         TVMContext ctx) {
+  graph_ = std::move(graph);
+  module_ = std::move(module);
+  ctx_ = ctx;
+  this->SetupStorage();
+  this->SetupOpExecs();
+}
+
+int GraphExecutor::GetInputIndex(const std::string& name) {
+  const auto& idx = graph_.indexed_graph();
+  for (size_t i = 0; i< idx.input_nodes().size(); ++i) {
+    if (idx[idx.input_nodes()[i]].source->attrs.name == name) {
+      return static_cast<int>(i);
+    }
+  }
+  LOG(FATAL) << "cannot find " << name << " among input";
+  return -1;
+}
+
+void GraphExecutor::SetInput(int index, DLTensor* data_in) {
+  const auto& idx = graph_.indexed_graph();
+  CHECK_LT(static_cast<size_t>(index), idx.input_nodes().size());
+  uint32_t eid = idx.entry_id(idx.input_nodes()[index], 0);
+  TVM_CCALL(TVMArrayCopyFromTo(data_in, &data_entry_[eid], nullptr));
+}
+
+void GraphExecutor::GetOutput(int index, DLTensor* data_out) {
+  const auto& idx = graph_.indexed_graph();
+  CHECK_LT(static_cast<size_t>(index), idx.outputs().size());
+  uint32_t eid = idx.entry_id(idx.outputs()[index]);
+  TVM_CCALL(TVMArrayCopyFromTo(&data_entry_[eid], data_out, nullptr));
+}
+
+bool LoadDLTensor(dmlc::Stream* strm, DLTensor* tensor) {
+  uint64_t header, reserved;
+  CHECK(strm->Read(&header, sizeof(header)))
+      << "Invalid DLTensor file format";
+  CHECK(strm->Read(&reserved, sizeof(reserved)))
+      << "Invalid DLTensor file format";
+  CHECK(header == kTVMNDArrayMagic)
+      << "Invalid DLTensor file format";
+
+  CHECK(strm->Read(&tensor->ctx, sizeof(tensor->ctx)))
+      << "Invalid DLTensor file format";
+  CHECK(strm->Read(&tensor->ndim, sizeof(tensor->ndim)))
+      << "Invalid DLTensor file format";
+  CHECK(strm->Read(&tensor->dtype, sizeof(tensor->dtype)))
+      << "Invalid DLTensor file format";
+
+  int ndim = tensor->ndim;
+  CHECK(strm->Read(tensor->shape, sizeof(int64_t) * ndim))
+      << "Invalid DLTensor file format";
+
+  int64_t size = 1;
+  int type_size = tensor->dtype.bits / 8;
+  for (int i = 0; i < ndim; ++i) {
+    size *= tensor->shape[i];
+  }
+  int64_t data_byte_size;
+  CHECK(strm->Read(&data_byte_size, sizeof(data_byte_size)))
+      << "Invalid DLTensor file format";
+  CHECK(data_byte_size == type_size * size)
+      << "Invalid DLTensor file format";
+  CHECK(strm->Read(tensor->data, type_size * size))
+      << "Invalid DLTensor file format";
+  return true;
+}
+
+void GraphExecutor::LoadParams(dmlc::Stream* strm) {
+  uint64_t header, reserved;
+  CHECK(strm->Read(&header))
+      << "Invalid parameters file format";
+  CHECK(header == kTVMNDArrayListMagic)
+      << "Invalid parameters file format";
+  CHECK(strm->Read(&reserved))
+      << "Invalid parameters file format";
+
+  std::vector<std::string> names;
+  CHECK(strm->Read(&names))
+      << "Invalid parameters file format";
+
+  std::unordered_map<std::string, size_t> name_eid;
+  const auto& idx = graph_.indexed_graph();
+  for (int nid : idx.input_nodes()) {
+    name_eid.emplace(idx[nid].source->attrs.name, idx.entry_id(nid, 0));
+  }
+
+  uint64_t sz;
+  strm->Read(&sz, sizeof(sz));
+  size_t size = static_cast<size_t>(sz);
+  CHECK(size == names.size())
+      << "Invalid parameters file format";
+  for (size_t i = 0; i < size; ++i) {
+    auto iter = name_eid.find(names[i]);
+    CHECK(iter != name_eid.end());
+    CHECK(LoadDLTensor(strm, &data_entry_[iter->second]))
+        << "Invalid parameters file format";
+  }
+}
+
+void GraphExecutor::LoadParams(const std::string& param_blob) {
+  dmlc::MemoryStringStream strm(const_cast<std::string*>(&param_blob));
+  this->LoadParams(&strm);
+}
+
+void GraphExecutor::SetupStorage() {
+  const auto& idx = graph_.indexed_graph();
+  // Grab saved optimization plan from graph.
+  auto vstorage = graph_.MoveCopyAttr<StorageVector>("storage_id");
+  std::vector<TVMType> vtype;
+  for (const std::string& s_type :
+           graph_.GetAttr<std::vector<std::string> >("dltype")) {
+    vtype.push_back(tvm::runtime::String2TVMType(s_type));
+  }
+  data_shape_ = graph_.GetAttr<ShapeVector>("shape");
+  data_entry_.resize(idx.num_node_entries());
+  // Find the maximum space size.
+  int max_id = 0;
+  for (size_t i = 0; i < data_shape_.size(); ++i) {
+    max_id = std::max(vstorage[i] + 1, max_id);
+  }
+  for (const auto& e : idx.input_nodes()) {
+    vstorage[idx.entry_id(e, 0)] = max_id++;
+  }
+  // size of each storage pool entry
+  std::vector<size_t> pool_entry_bytes;
+  // Find the maximum space size.
+  for (size_t i = 0; i < data_shape_.size(); ++i) {
+    int storage_id = vstorage[i];
+    size_t size = data_shape_[i].Size();
+    CHECK_GE(storage_id, 0) << "Do not support runtime shape op";
+    DLDataType t = vtype[i];
+    size_t bits = t.bits * t.lanes;
+    CHECK_EQ(bits % 8U, 0U);
+    size_t bytes = (bits / 8U) * size;
+
+    size_t sid = static_cast<size_t>(storage_id);
+    if (sid >= pool_entry_bytes.size()) {
+      pool_entry_bytes.resize(sid + 1, 0);
+    }
+    pool_entry_bytes[sid] = std::max(pool_entry_bytes[sid], bytes);
+  }
+  // Allocate the space.
+  for (size_t i = 0; i < pool_entry_bytes.size(); ++i) {
+    TShape shape{static_cast<int64_t>(pool_entry_bytes[i] + 3) / 4};
+    DLTensor* tensor;
+    TVM_CCALL(TVMArrayAlloc(
+        shape.data(), 1, kFloat, 32, 1, ctx_.device_type, ctx_.device_id, &tensor));
+    storage_pool_.push_back(tensor);
+  }
+  // Assign the pooled entries.
+  for (size_t i = 0; i < data_entry_.size(); ++i) {
+    int storage_id = vstorage[i];
+    data_entry_[i] = *storage_pool_[storage_id];
+    data_entry_[i].shape = const_cast<int64_t*>(data_shape_[i].data());
+    data_entry_[i].ndim = data_shape_[i].ndim();
+    data_entry_[i].dtype = vtype[i];
+  }
+}
+
+void GraphExecutor::SetupOpExecs() {
+  static const nnvm::Op* tvm_op = nnvm::Op::Get("tvm_op");
+  const auto& idx = graph_.indexed_graph();
+  op_execs_.resize(idx.num_nodes());
+  // setup the array and requirements.
+  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+    const auto& inode = idx[nid];
+    if (inode.source->is_variable()) continue;
+    std::vector<DLTensor> args;
+    for (const auto& e : inode.inputs) {
+      args.push_back(data_entry_[idx.entry_id(e)]);
+    }
+    for (uint32_t index = 0; index < inode.source->num_outputs(); ++index) {
+      uint32_t eid = idx.entry_id(nid, index);
+      args.push_back(data_entry_[eid]);
+    }
+    CHECK_EQ(inode.source->op(), tvm_op)
+        << "transform the graph to tvm op";
+    op_execs_[nid] = CreateTVMOp(
+        inode.source->attrs, args, inode.inputs.size());
+  }
+}
+
+std::function<void()> GraphExecutor::CreateTVMOp(
+    const nnvm::NodeAttrs& attrs,
+    const std::vector<DLTensor>& args,
+    size_t num_inputs) {
+  struct OpArgs {
+    std::vector<DLTensor> args;
+    std::vector<TVMValue> arg_values;
+    std::vector<int> arg_tcodes;
+    std::vector<int64_t> shape_data;
+  };
+  const TVMOpParam& param = nnvm::get<TVMOpParam>(attrs.parsed);
+  std::shared_ptr<OpArgs> arg_ptr = std::make_shared<OpArgs>();
+  // setup address.
+  arg_ptr->args = std::move(args);
+  if (param.flatten_data) {
+    arg_ptr->shape_data.resize(arg_ptr->args.size());
+  }
+  for (size_t i = 0; i < arg_ptr->args.size(); ++i) {
+    TVMValue v;
+    DLTensor* t = &(arg_ptr->args[i]);
+    v.v_handle = t;
+    arg_ptr->arg_values.push_back(v);
+    arg_ptr->arg_tcodes.push_back(kArrayHandle);
+    if (param.flatten_data) {
+      arg_ptr->shape_data[i] = std::accumulate(
+          t->shape, t->shape + t->ndim, 1, std::multiplies<int64_t>());
+      t->ndim = 1;
+      t->shape = &(arg_ptr->shape_data[i]);
+    }
+  }
+  // get compiled function from module.
+  tvm::runtime::PackedFunc pf = module_.GetFunction(param.func_name, false);
+  CHECK(pf != nullptr) << "no such function in module: " << param.func_name;
+  auto fexec = [arg_ptr, pf] () {
+    TVMRetValue rv;
+    TVMArgs targs(arg_ptr->arg_values.data(),
+                  arg_ptr->arg_tcodes.data(),
+                  static_cast<int>(arg_ptr->arg_values.size()));
+    pf.CallPacked(targs, &rv);
+  };
+  return fexec;
+}
+
+// parser
+inline void TVMOpParamParser(nnvm::NodeAttrs* attrs) {
+  TVMOpParam param;
+  param.Init(attrs->dict);
+  attrs->parsed = std::move(param);
+}
+
+DMLC_REGISTER_PARAMETER(TVMOpParam);
+
+NNVM_REGISTER_OP(tvm_op)
+.set_attr_parser(TVMOpParamParser)
+.set_num_inputs([](const NodeAttrs& attrs) {
+    const TVMOpParam& param = nnvm::get<TVMOpParam>(attrs.parsed);
+    return param.num_inputs;
+  })
+.set_num_outputs([](const NodeAttrs& attrs) {
+    const TVMOpParam& param = nnvm::get<TVMOpParam>(attrs.parsed);
+    return param.num_outputs;
+  });
+
+TVM_REGISTER_GLOBAL("nnvm.tvm.create_executor")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+    std::string sym_json = args[0];
+    std::string param_blob = args[1];
+    tvm::runtime::Module m = args[2];
+    TVMContext ctx;
+    ctx.device_type = static_cast<DLDeviceType>(args[3].operator int());
+    ctx.device_id   = args[4];
+    // load graph from json string
+    nnvm::Graph g;
+    g.attrs["json"] = std::make_shared<nnvm::any>(sym_json);
+    g = nnvm::ApplyPass(std::move(g), "LoadJSON");
+    std::shared_ptr<GraphExecutor> exec = std::make_shared<GraphExecutor>();
+    exec->Init(g, m, ctx);
+    // load params form stream of string
+    exec->LoadParams(std::move(param_blob));
+    *rv = tvm::runtime::Module(exec);
+  });
+}  // namespace runtime
+}  // namespace nnvm
diff --git a/nnvm/src/runtime/graph_executor.h b/nnvm/src/runtime/graph_executor.h
new file mode 100644
index 000000000000..243c71646ba7
--- /dev/null
+++ b/nnvm/src/runtime/graph_executor.h
@@ -0,0 +1,150 @@
+/*!
+ *  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_
diff --git a/nnvm/src/top/README.md b/nnvm/src/top/README.md
deleted file mode 100644
index 4da78195e267..000000000000
--- a/nnvm/src/top/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-Core Operator List
-==================

From f773edc4b9bb93722092c422a1fe7cade3435d63 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 17 Sep 2017 22:12:59 -0700
Subject: [PATCH 470/949] [COMPILER] Initial compiler infra (#12)

---
 nnvm/Makefile                                 |  11 +-
 nnvm/include/nnvm/compiler/contrib_op_param.h |  28 ++
 nnvm/include/nnvm/compiler/op_attr_types.h    |  89 ++++
 nnvm/include/nnvm/compiler/packed_func_ext.h  |  57 +++
 nnvm/include/nnvm/op_attr_types.h             |  12 +
 nnvm/include/nnvm/top/README                  |   2 +-
 nnvm/python/nnvm/_base.py                     |  11 +-
 nnvm/python/nnvm/_ctypes/symbol.py            |   5 +-
 nnvm/python/nnvm/compiler/__init__.py         |  18 +
 nnvm/python/nnvm/compiler/build_module.py     |  79 ++++
 nnvm/python/nnvm/compiler/graph_attr.py       |  56 +++
 nnvm/python/nnvm/compiler/graph_pass.py       |   7 +
 nnvm/python/nnvm/compiler/registry.py         |  81 ++++
 nnvm/python/nnvm/graph.py                     |  84 +++-
 nnvm/python/nnvm/libinfo.py                   |   2 +-
 nnvm/python/nnvm/runtime.py                   |  36 ++
 nnvm/python/nnvm/symbol.py                    |  20 +-
 nnvm/python/nnvm/top/__init__.py              |   4 +
 nnvm/python/nnvm/top/attr_dict.py             |  90 ++++
 nnvm/python/nnvm/top/nn.py                    |  35 ++
 nnvm/python/nnvm/top/tensor.py                |  50 +++
 nnvm/src/compiler/packed_func_ext.cc          | 106 +++++
 nnvm/src/compiler/pass/graph_fuse.cc          | 393 +++++++++++++++++
 nnvm/src/compiler/pass/layout_transform.cc    | 159 +++++++
 nnvm/src/compiler/pass/prune_graph.cc         |  76 ++++
 nnvm/src/runtime/graph_executor.cc            |  43 +-
 nnvm/src/top/nn/convolution.cc                |  10 +-
 nnvm/src/top/nn/nn.cc                         |  29 +-
 nnvm/src/top/nn/pooling.cc                    |  16 +-
 nnvm/src/top/op_common.h                      |  13 +
 nnvm/src/top/tensor/broadcast.cc              |   9 +-
 nnvm/src/top/tensor/elemwise.cc               |  40 +-
 nnvm/src/top/tensor/reduce.cc                 |  20 +-
 nnvm/src/top/tensor/transform.cc              |  39 +-
 nnvm/tests/lint/pylintrc                      | 406 ++++++++++++++++++
 nnvm/tests/python/compiler/test_build.py      |  34 ++
 nnvm/tests/python/compiler/test_rpc_exec.py   |  50 +++
 nnvm/tests/python/compiler/test_top_level2.py |  39 ++
 .../tests/python/unittest/test_infer_shape.py |   8 +-
 39 files changed, 2148 insertions(+), 119 deletions(-)
 create mode 100644 nnvm/include/nnvm/compiler/contrib_op_param.h
 create mode 100644 nnvm/include/nnvm/compiler/op_attr_types.h
 create mode 100644 nnvm/include/nnvm/compiler/packed_func_ext.h
 create mode 100644 nnvm/python/nnvm/compiler/__init__.py
 create mode 100644 nnvm/python/nnvm/compiler/build_module.py
 create mode 100644 nnvm/python/nnvm/compiler/graph_attr.py
 create mode 100644 nnvm/python/nnvm/compiler/graph_pass.py
 create mode 100644 nnvm/python/nnvm/compiler/registry.py
 create mode 100644 nnvm/python/nnvm/runtime.py
 create mode 100644 nnvm/python/nnvm/top/__init__.py
 create mode 100644 nnvm/python/nnvm/top/attr_dict.py
 create mode 100644 nnvm/python/nnvm/top/nn.py
 create mode 100644 nnvm/python/nnvm/top/tensor.py
 create mode 100644 nnvm/src/compiler/packed_func_ext.cc
 create mode 100644 nnvm/src/compiler/pass/graph_fuse.cc
 create mode 100644 nnvm/src/compiler/pass/layout_transform.cc
 create mode 100644 nnvm/src/compiler/pass/prune_graph.cc
 create mode 100644 nnvm/tests/lint/pylintrc
 create mode 100644 nnvm/tests/python/compiler/test_build.py
 create mode 100644 nnvm/tests/python/compiler/test_rpc_exec.py
 create mode 100644 nnvm/tests/python/compiler/test_top_level2.py

diff --git a/nnvm/Makefile b/nnvm/Makefile
index 868ab48eea38..b7322c181feb 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -11,7 +11,7 @@ include $(config)
 
 export LDFLAGS = -pthread -lm
 export CFLAGS = -std=c++11 -Wall -O2 -Iinclude -fPIC
-CFLAGS += -Itvm/include -Itvm/dlpack/include
+CFLAGS += -Itvm/include -Itvm/dlpack/include -Itvm/HalideIR/src
 
 ifdef DMLC_CORE_PATH
   CFLAGS += -I$(DMLC_CORE_PATH)/include
@@ -38,7 +38,7 @@ PLUGIN_OBJ =
 include $(NNVM_PLUGINS)
 
 # specify tensor path
-.PHONY: clean all test lint doc cython cython3 cyclean
+.PHONY: clean all test lint pylint doc cython cython3 cyclean
 
 UNAME_S := $(shell uname -s)
 
@@ -55,7 +55,7 @@ endif
 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/runtime/*.cc)
+SRC_TOP = $(wildcard src/top/*/*.cc src/runtime/*.cc src/compiler/*.cc src/compiler/*/*.cc)
 ALL_OBJ = $(patsubst %.cc, build/%.o, $(SRC))
 TOP_OBJ = $(patsubst %.cc, build/%.o, $(SRC_TOP))
 ALL_DEP = $(ALL_OBJ)
@@ -90,9 +90,12 @@ cython3:
 cyclean:
 	rm -rf python/nnvm/*/*.so python/nnvm/*/*.dylib python/nnvm/*/*.cpp
 
-lint:
+lint: pylint
 	python dmlc-core/scripts/lint.py nnvm cpp include src
 
+pylint:
+	pylint python/nnvm --rcfile=$(ROOTDIR)/tests/lint/pylintrc
+
 doc:
 	doxygen docs/Doxyfile
 
diff --git a/nnvm/include/nnvm/compiler/contrib_op_param.h b/nnvm/include/nnvm/compiler/contrib_op_param.h
new file mode 100644
index 000000000000..4eb91f56cafe
--- /dev/null
+++ b/nnvm/include/nnvm/compiler/contrib_op_param.h
@@ -0,0 +1,28 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file contrib_op_param.h
+ * \brief Additional parameters for compiler optimized operators.
+ */
+#ifndef NNVM_COMPILER_CONTRIB_OP_PARAM_H_
+#define NNVM_COMPILER_CONTRIB_OP_PARAM_H_
+
+#include <dmlc/parameter.h>
+#include <string>
+
+namespace nnvm {
+namespace compiler {
+
+/*! \brief Parameters of layout transform operator */
+struct LayoutTransformParam : public dmlc::Parameter<LayoutTransformParam> {
+  std::string src_layout;
+  std::string dst_layout;
+
+  DMLC_DECLARE_PARAMETER(LayoutTransformParam) {
+    DMLC_DECLARE_FIELD(src_layout);
+    DMLC_DECLARE_FIELD(dst_layout);
+  }
+};
+}  // namespace compiler
+}  // namespace nnvm
+
+#endif  // NNVM_COMPILER_CONTRIB_OP_PARAM_H_
diff --git a/nnvm/include/nnvm/compiler/op_attr_types.h b/nnvm/include/nnvm/compiler/op_attr_types.h
new file mode 100644
index 000000000000..c77720da1669
--- /dev/null
+++ b/nnvm/include/nnvm/compiler/op_attr_types.h
@@ -0,0 +1,89 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file op_attr_types.h
+ * \brief The Expr and related elements in DataFlow construction.
+ */
+#ifndef NNVM_COMPILER_OP_ATTR_TYPES_H_
+#define NNVM_COMPILER_OP_ATTR_TYPES_H_
+
+#include <tvm/expr.h>
+#include <tvm/tensor.h>
+#include <tvm/schedule.h>
+#include <tvm/packed_func_ext.h>
+#include <tvm/runtime/registry.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/graph_attr_types.h>
+#include <nnvm/graph.h>
+#include <vector>
+#include <string>
+
+namespace nnvm {
+namespace compiler {
+
+using ::tvm::Array;
+using ::tvm::Tensor;
+using ::tvm::Schedule;
+
+/*! \brief operator pattern used in graph fusion */
+enum OpPatternKind : int {
+  // Elementwise operation
+  kElemWise = 0,
+  // Broadcast operation
+  kBroadcast = 1,
+  // Complex operation, can fuse bcast in input/outputs
+  // but cannot chain another complex op
+  kComplex = 2,
+  // Extern operation, cannot fuse anything.
+  kExtern = 3
+};
+
+/*! \brief the operator pattern */
+using TOpPattern = int;
+
+/*!
+ * \brief Computation description interface
+ * \param attrs The attribute of the node.
+ * \param inputs The input tensors(placeholders)
+ * \return The output description of the tensor.
+ */
+using FTVMCompute = std::function<
+  Array<Tensor>
+  (const NodeAttrs& attrs, const Array<Tensor>& inputs)>;
+
+/*!
+ * \brief Build the computation schedule for
+ *  op whose root is at current op.
+ * \param attrs The attribute of the node.
+ * \param outs The output tensors.
+ * \param target The build target.
+ * \return schedule The computation schedule.
+ */
+using FTVMSchedule = std::function<
+  Schedule(const NodeAttrs& attrs,
+           const Array<Tensor>& outs,
+           const std::string& target)>;
+
+/*! \brief Layout Information about an entry */
+using TLayoutInfo = std::string;
+
+/*!
+ * \brief The producer consumer function of node layout
+ * \param attrs The attribute of the node.
+ * \param ilayouts The input layouts that the node request.
+ * \param olayouts The output layouts that the node produce.
+ * \return bool The success flag.
+ */
+using FTVMLayoutRequest = std::function<bool (const NodeAttrs& attrs,
+                                              std::vector<TLayoutInfo> *ilayouts,
+                                              std::vector<TLayoutInfo> *olayouts)>;
+
+/*!
+ * \brief Transform from normal operator to vectorized operator
+ * \param node The source node.
+ * \return Transformed vectorized op.
+ */
+using FTVMVectorizedOp = std::function<nnvm::NodePtr (const nnvm::Node* node)>;
+
+}  // namespace compiler
+}  // namespace nnvm
+#endif  // NNVM_COMPILER_OP_ATTR_TYPES_H_
diff --git a/nnvm/include/nnvm/compiler/packed_func_ext.h b/nnvm/include/nnvm/compiler/packed_func_ext.h
new file mode 100644
index 000000000000..241febbe9c69
--- /dev/null
+++ b/nnvm/include/nnvm/compiler/packed_func_ext.h
@@ -0,0 +1,57 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file packed_func_ext.h
+ * \brief Extension to enable packed functionn for nnvm types
+ */
+#ifndef NNVM_COMPILER_PACKED_FUNC_EXT_H_
+#define NNVM_COMPILER_PACKED_FUNC_EXT_H_
+
+#include <tvm/runtime/packed_func.h>
+#include <tvm/runtime/registry.h>
+#include <nnvm/graph.h>
+#include <nnvm/symbolic.h>
+#include <string>
+#include <unordered_map>
+
+namespace nnvm {
+namespace compiler {
+
+using tvm::runtime::PackedFunc;
+
+using AttrDict = std::unordered_map<std::string, std::string>;
+
+/*!
+ * \brief Get PackedFunction from global registry and
+ *  report error if it does not exist
+ * \param name The name of the function.
+ * \return The created PackedFunc.
+ */
+inline const PackedFunc& GetPackedFunc(const std::string& name) {
+  const PackedFunc* pf = tvm::runtime::Registry::Get(name);
+  CHECK(pf != nullptr) << "Cannot find function " << name << " in registry";
+  return *pf;
+}
+}  // namespace compiler
+}  // namespace nnvm
+
+// Enable the graph and symbol object exchange.
+namespace tvm {
+namespace runtime {
+
+template<>
+struct extension_class_info<nnvm::Symbol> {
+  static const int code = 16;
+};
+
+template<>
+struct extension_class_info<nnvm::Graph> {
+  static const int code = 17;
+};
+
+template<>
+struct extension_class_info<nnvm::compiler::AttrDict> {
+  static const int code = 18;
+};
+}  // namespace runtime
+}  // namespace tvm
+#endif  // NNVM_COMPILER_PACKED_FUNC_EXT_H_
diff --git a/nnvm/include/nnvm/op_attr_types.h b/nnvm/include/nnvm/op_attr_types.h
index e0873979a14b..25c8c6395bab 100644
--- a/nnvm/include/nnvm/op_attr_types.h
+++ b/nnvm/include/nnvm/op_attr_types.h
@@ -72,6 +72,18 @@ template<typename AttrType>
 using FInferNodeEntryAttr = std::function<bool (const NodeAttrs& attrs,
                                                 std::vector<AttrType> *in_attrs,
                                                 std::vector<AttrType> *out_attrs)>;
+
+/*!
+ * \brief Get attribute dictionary from node.
+ *
+ * \param attrs The attributes of the node.
+ * \return The attribute dict.
+ * \note Register under "FUpdateAttrDict"
+ */
+using FGetAttrDict = std::function<
+  std::unordered_map<std::string, std::string>
+  (const NodeAttrs& attrs)>;
+
 /*!
  * \brief Shape inference function.
  *  Update the shapes given the input shape information.
diff --git a/nnvm/include/nnvm/top/README b/nnvm/include/nnvm/top/README
index 696c05a4d532..09a4d6fc387f 100644
--- a/nnvm/include/nnvm/top/README
+++ b/nnvm/include/nnvm/top/README
@@ -1 +1 @@
-NNVM Core Operator Specs
+NNVM Core Operator and Compiler
diff --git a/nnvm/python/nnvm/_base.py b/nnvm/python/nnvm/_base.py
index ccc4a3bdb501..80bbe6bd293e 100644
--- a/nnvm/python/nnvm/_base.py
+++ b/nnvm/python/nnvm/_base.py
@@ -1,15 +1,18 @@
 # coding: utf-8
-# pylint: disable=invalid-name
+# pylint: disable=invalid-name, unused-import
 """ ctypes library of nnvm and helper functions """
 from __future__ import absolute_import
 
 import sys
-import os
 import ctypes
 import numpy as np
 from . import libinfo
 
-__all__ = ['NNNetError']
+try:
+    import tvm
+except ImportError:
+    pass
+
 #----------------------------
 # library loading
 #----------------------------
@@ -181,7 +184,7 @@ def ctypes2docstring(num_args, arg_names, arg_types, arg_descs, remove_dup=True)
         param_keys.add(key)
         type_info = py_str(arg_types[i])
         ret = '%s : %s' % (key, type_info)
-        if len(arg_descs[i]) != 0:
+        if arg_descs[i]:
             ret += '\n    ' + py_str(arg_descs[i])
         param_str.append(ret)
     doc_str = ('Parameters\n' +
diff --git a/nnvm/python/nnvm/_ctypes/symbol.py b/nnvm/python/nnvm/_ctypes/symbol.py
index e00f536d318f..8da572b7769e 100644
--- a/nnvm/python/nnvm/_ctypes/symbol.py
+++ b/nnvm/python/nnvm/_ctypes/symbol.py
@@ -1,5 +1,6 @@
 # coding: utf-8
-# pylint: disable=invalid-name, protected-access, too-many-arguments, too-many-lines
+# pylint: disable=invalid-name, protected-access, too-many-arguments, too-many-lines,
+# pylint: disable=len-as-condition, consider-iterating-dictionary
 """Symbolic configuration API."""
 from __future__ import absolute_import as _abs
 
@@ -7,7 +8,7 @@
 import ctypes
 import sys
 from .._base import _LIB
-from .._base import c_array, c_str, nn_uint, py_str, string_types
+from .._base import c_array, c_str, nn_uint, py_str
 from .._base import SymbolHandle, OpHandle
 from .._base import check_call, ctypes2docstring
 from ..name import NameManager
diff --git a/nnvm/python/nnvm/compiler/__init__.py b/nnvm/python/nnvm/compiler/__init__.py
new file mode 100644
index 000000000000..093058f1f112
--- /dev/null
+++ b/nnvm/python/nnvm/compiler/__init__.py
@@ -0,0 +1,18 @@
+"""Namespace for NNVM-TVM compiler toolchain"""
+from __future__ import absolute_import
+
+import tvm
+
+from . import build_module
+from . build_module import build
+
+from .. import symbol as _symbol
+from .. import graph as _graph
+
+from .registry import OpPattern
+from .registry import register_compute, register_schedule, register_pattern
+
+from .. import top as _top
+
+tvm.register_extension(_symbol.Symbol, _symbol.Symbol)
+tvm.register_extension(_graph.Graph, _graph.Graph)
diff --git a/nnvm/python/nnvm/compiler/build_module.py b/nnvm/python/nnvm/compiler/build_module.py
new file mode 100644
index 000000000000..e7755a4d495d
--- /dev/null
+++ b/nnvm/python/nnvm/compiler/build_module.py
@@ -0,0 +1,79 @@
+# pylint: disable=invalid-name
+"""Namespace for building operators."""
+from __future__ import absolute_import as _abs
+
+import tvm
+from . import graph_attr
+from .. import graph as _graph
+
+@tvm.register_func("nnvm.compiler.lower")
+def _lower(sch, inputs, func_name):
+    f = tvm.lower(sch, inputs, name=func_name)
+    return f if isinstance(
+        f, (tvm.container.Array, tuple, list)) else [f]
+
+
+@tvm.register_func("nnvm.compiler.build_target")
+def _build(funcs, target):
+    return tvm.build(funcs, target=target)
+
+
+_move_module = tvm.get_global_func("nnvm.compiler._move_module")
+
+
+def optimize(graph):
+    """Perform graph optimization
+
+    Parameters
+    ----------
+    graph : Graph
+        The graph to be used in lowering.
+
+    Returns
+    -------
+    graph : Graph
+        The optimized execution graph.
+    """
+    return graph
+
+
+def build(graph, target, shape, dtype="float32"):
+    """Build graph into runtime library.
+
+    This is the final step of graph compilation.
+
+    Parameters
+    ----------
+    graph : Graph
+        The graph to be used in lowering
+
+    target : str
+        The build target
+
+    shape : dict of str to tuple
+        The input shape to the graph
+
+    dtype : str or dict of str to str
+        The input types to the graph
+
+    Returns
+    -------
+    graph : Graph
+        The final execution graph.
+
+    libmod : tvm.Module
+        The modue that comes with the execution graph
+    """
+    if not isinstance(target, str):
+        raise TypeError("require target to be str")
+    if not isinstance(shape, dict):
+        raise TypeError("require shape to be dict")
+
+    graph = graph if isinstance(graph, _graph.Graph) else _graph.create(graph)
+    graph = graph_attr.set_shape(graph, shape)
+    graph = graph_attr.set_dtype(graph, dtype)
+    graph._set_json_attr("target", target, "str")
+    graph = graph.apply("InferShape").apply("InferType")
+    graph = graph.apply("GraphFusePartition").apply("GraphFuse")
+    libmod = _move_module(graph)
+    return graph, libmod
diff --git a/nnvm/python/nnvm/compiler/graph_attr.py b/nnvm/python/nnvm/compiler/graph_attr.py
new file mode 100644
index 000000000000..c30fd0356929
--- /dev/null
+++ b/nnvm/python/nnvm/compiler/graph_attr.py
@@ -0,0 +1,56 @@
+"""Utilities to access graph attributes"""
+from __future__ import absolute_import as _abs
+
+def set_shape(g, shape):
+    """Set the shape of graph nodes in the graph attribute.
+
+    Parameters
+    ----------
+    g : Graph
+        The input graph
+
+    shape : dict of str to tuple
+        The input shape
+
+    Returns
+    -------
+    g : Graph
+        The updated graph with updated shape.
+    """
+    index = g.index
+    list_shape = [[]] * index.num_node_entries
+    for k, v in shape.items():
+        list_shape[index.entry_id(k)] = v
+    g._set_json_attr("shape", list_shape, 'list_shape')
+    return g
+
+
+DTYPE_DICT = {
+    "float32": 0
+}
+
+def set_dtype(g, dtype):
+    """Set the dtype of graph nodes
+
+    Parameters
+    ----------
+    g : Graph
+        The input graph
+
+    dtype : dict of str to str or str
+        The input dtype
+
+    Returns
+    -------
+    g : Graph
+        The updated graph with updated dtype.
+    """
+    index = g.index
+    if isinstance(dtype, dict):
+        list_dtype = [-1] * index.num_node_entries
+        for k, v in dtype.items():
+            list_dtype[index.entry_id(k)] = DTYPE_DICT[v]
+    else:
+        list_dtype = [DTYPE_DICT[dtype]] * index.num_node_entries
+    g._set_json_attr("dtype", list_dtype, "list_int")
+    return g
diff --git a/nnvm/python/nnvm/compiler/graph_pass.py b/nnvm/python/nnvm/compiler/graph_pass.py
new file mode 100644
index 000000000000..1122eb0170a0
--- /dev/null
+++ b/nnvm/python/nnvm/compiler/graph_pass.py
@@ -0,0 +1,7 @@
+"""Namespace of graph pass.
+
+Principle:
+- Graph in, graph out: always takes in graph as first argument and returns a graph
+- Composable API: break graph transformation pass as segments of small transformations.
+"""
+from __future__ import absolute_import as _abs
diff --git a/nnvm/python/nnvm/compiler/registry.py b/nnvm/python/nnvm/compiler/registry.py
new file mode 100644
index 000000000000..d54b22d12681
--- /dev/null
+++ b/nnvm/python/nnvm/compiler/registry.py
@@ -0,0 +1,81 @@
+# pylint: disable=invalid-name
+"""Information registry to register operator information for compiler"""
+import tvm
+
+class OpPattern(object):
+    ELEM_WISE = 0
+    BROADCAST = 1
+    COMPLEX = 2
+    EXTERN = 2
+
+_register_compute = tvm.get_global_func("nnvm._register_compute")
+_register_schedule = tvm.get_global_func("nnvm._register_schedule")
+_register_pattern = tvm.get_global_func("nnvm._register_pattern")
+
+def register_compute(op_name, f=None, level=10):
+    """Register compute function for operator
+
+    Parameters
+    ----------
+    op_name : str
+        The name of operator
+
+    f : function
+        The schedule function
+
+    level : int
+        The priority level
+
+    Returns
+    -------
+    fregister : function
+        Register function if f is not specified.
+    """
+    def register(myf):
+        """internal register function"""
+        _register_compute(op_name, myf, level)
+        return myf
+    return register(f) if f else register
+
+
+def register_schedule(op_name, f=None, level=10):
+    """Register schedule function for operator
+
+    Parameters
+    ----------
+    op_name : str
+        The name of operator
+
+    f : function
+        The schedule function
+
+    level : int
+        The priority level
+
+    Returns
+    -------
+    fregister : function
+        Register function if f is not specified.
+    """
+    def register(myf):
+        """internal register function"""
+        _register_schedule(op_name, myf, level)
+        return myf
+    return register(f) if f else register
+
+
+def register_pattern(op_name, pattern, level=10):
+    """Register pattern code for operator
+
+    Parameters
+    ----------
+    op_name : str
+        The name of operator
+
+    pattern : int
+        The pattern code.
+
+    level : int
+        The priority level
+    """
+    _register_pattern(op_name, pattern, level)
diff --git a/nnvm/python/nnvm/graph.py b/nnvm/python/nnvm/graph.py
index 0f2997d4bf3f..e7652af6cb33 100644
--- a/nnvm/python/nnvm/graph.py
+++ b/nnvm/python/nnvm/graph.py
@@ -4,7 +4,6 @@
 from __future__ import absolute_import as _abs
 
 import ctypes
-import sys
 import json
 from ._base import _LIB
 from ._base import c_array, c_str, nn_uint, py_str, string_types
@@ -12,12 +11,73 @@
 from ._base import check_call
 from .symbol import Symbol, Group as _Group
 
+class GraphIndex(object):
+    """Index for quickly accessing graph attributes.
+
+    Parameters
+    ----------
+    graph : Graph
+        The graph to create index.
+    """
+    def __init__(self, graph):
+        jgraph = json.loads(create(graph).apply("SaveJSON").json_attr("json"))
+        self.nodes = jgraph["nodes"]
+        self.entry_ptr = jgraph["node_row_ptr"]
+        self._name2nodeid = {n["name"]: i for i, n in enumerate(self.nodes)}
+
+    @property
+    def num_nodes(self):
+        """Number of nodes in graph."""
+        return len(self.entry_ptr) - 1
+
+    @property
+    def num_node_entries(self):
+        """Number of nodes in graph."""
+        return self.entry_ptr[-1]
+
+    def node_id(self, key):
+        """Get the node index for a given key.
+
+        Parameters
+        ----------
+        key : str or int
+            The node key or index
+
+        Returns
+        -------
+        index : int
+            The entry index
+        """
+        return self._name2nodeid[key]
+
+    def entry_id(self, key, value_index=0):
+        """Get the entry id of a node entry.
+
+        Parameters
+        ----------
+        key : str or int
+            The node key or index
+
+        value_index : int
+            The value index of output
+
+        Returns
+        -------
+        index : int
+            The entry index
+        """
+        idx = self.node_id(key) if isinstance(key, str) else key
+        assert value_index < self.entry_ptr[idx + 1]
+        return self.entry_ptr[idx] + value_index
+
+
 
 class Graph(object):
     """Graph is the graph object that can be used to apply optimization pass.
-    It contains additional graphwise attribute besides the internal symbol.
 
+    It contains additional graphwise attribute besides the internal symbol.
     """
+    _tvm_tcode = 17
 
     # pylint: disable=no-member
     def __init__(self, handle):
@@ -29,6 +89,7 @@ def __init__(self, handle):
             the handle to the underlying C++ Graph
         """
         self.handle = handle
+        self._index = None
 
     def __del__(self):
         check_call(_LIB.NNGraphFree(self.handle))
@@ -53,8 +114,7 @@ def json_attr(self, key):
         if success.value != 0:
             json_str = py_str(ret.value)
             return json.loads(json_str)[1]
-        else:
-            return None
+        return None
 
     def _set_symbol_list_attr(self, key, value):
         """Set the attribute of the graph.
@@ -95,18 +155,34 @@ def _set_json_attr(self, key, value, type_name=None):
         check_call(_LIB.NNGraphSetJSONAttr(
             self.handle, c_str(key), c_str(json_value)))
 
+    @property
+    def _tvm_handle(self):
+        return self.handle.value
+
     @property
     def symbol(self):
         shandle = SymbolHandle()
         check_call(_LIB.NNGraphGetSymbol(self.handle, ctypes.byref(shandle)))
         return Symbol(shandle)
 
+    @property
+    def index(self):
+        if not self._index:
+            self._index = GraphIndex(self)
+        return self._index
+
     def apply(self, passes):
         """Apply passes to the graph
 
         Parameters
         ----------
+        passes : str or list of str
+            The passes to be applied
 
+        Returns
+        -------
+        g : Graph
+            The transformed graph.
         """
         if isinstance(passes, string_types):
             passes = [passes]
diff --git a/nnvm/python/nnvm/libinfo.py b/nnvm/python/nnvm/libinfo.py
index a996bc163a7d..a369eb40312e 100644
--- a/nnvm/python/nnvm/libinfo.py
+++ b/nnvm/python/nnvm/libinfo.py
@@ -52,7 +52,7 @@ def find_lib_path():
         dll_path = [os.path.join(p, '%s.so' % lib_name) for p in dll_path]
 
     lib_path = [p for p in dll_path if os.path.exists(p) and os.path.isfile(p)]
-    if len(lib_path) == 0:
+    if not lib_path:
         raise RuntimeError('Cannot find the files.\n' +
                            'List of candidates:\n' + str('\n'.join(dll_path)))
     return lib_path
diff --git a/nnvm/python/nnvm/runtime.py b/nnvm/python/nnvm/runtime.py
new file mode 100644
index 000000000000..733514db71af
--- /dev/null
+++ b/nnvm/python/nnvm/runtime.py
@@ -0,0 +1,36 @@
+"""Runtime environment for nnvm relies on TVM."""
+import tvm
+from tvm.contrib import rpc
+
+def create(graph, libmod, ctx):
+    """Create a runtime executor module given the graph and module.
+
+    Parameters
+    ----------
+    graph : The graph to be deployed
+        The graph to be loaded.
+
+    libmod : tvm.Module
+        The module of the corresponding function
+
+    ctx : TVMContext
+        The context to deploy the module, can be local or remote.
+
+    Returns
+    -------
+    graph_module : tvm.Module
+        Runtime graph module to execute the graph.
+    """
+    json_str = graph if isinstance(graph, str) else graph.apply("SaveJSON").json_attr("json")
+    device_type = ctx.device_type
+    device_id = ctx.device_id
+    if device_type >= rpc.RPC_SESS_MASK:
+        assert libmod.type_key == "rpc"
+        assert rpc._SessTableIndex(libmod) == ctx._rpc_sess._tbl_index
+        hmod = rpc._ModuleHandle(libmod)
+        fcreate = ctx._rpc_sess.get_function("nnvm.runtime.remote_create")
+        device_type = device_type % rpc.RPC_SESS_MASK
+        return fcreate(json_str, hmod, device_type, device_id)
+
+    fcreate = tvm.get_global_func("nnvm.runtime.create")
+    return fcreate(json_str, libmod, device_type, device_id)
diff --git a/nnvm/python/nnvm/symbol.py b/nnvm/python/nnvm/symbol.py
index 5a6c9524c2ca..0891fb42cf3e 100644
--- a/nnvm/python/nnvm/symbol.py
+++ b/nnvm/python/nnvm/symbol.py
@@ -1,3 +1,4 @@
+# pylint: disable=invalid-name, unused-import
 """Symbolic configuration API."""
 from __future__ import absolute_import as _abs
 import sys as _sys
@@ -7,21 +8,21 @@
 from numbers import Number as _Number
 from . import _base
 from ._base import _LIB, check_call as _check_call
-from . import _symbol_internal as _internal
 from .attribute import AttrScope
+from . import _symbol_internal as _internal
 
 # Use different verison of SymbolBase
 # When possible, use cython to speedup part of computation.
 
 try:
     if int(_os.environ.get("MXNET_ENABLE_CYTHON", True)) == 0:
-        from ._ctypes.symbol import SymbolBase,  _init_symbol_module
+        from ._ctypes.symbol import SymbolBase, _init_symbol_module
     elif _sys.version_info >= (3, 0):
-        from ._cy3.symbol import SymbolBase,  _init_symbol_module
+        from ._cy3.symbol import SymbolBase, _init_symbol_module
     else:
-        from ._cy2.symbol import SymbolBase,  _init_symbol_module
+        from ._cy2.symbol import SymbolBase, _init_symbol_module
 except ImportError:
-    from ._ctypes.symbol import SymbolBase,  _init_symbol_module
+    from ._ctypes.symbol import SymbolBase, _init_symbol_module
 
 
 class Symbol(SymbolBase):
@@ -29,6 +30,12 @@ class Symbol(SymbolBase):
     # disable dictionary storage, also do not have parent type.
     __slots__ = []
 
+    _tvm_tcode = 16
+
+    @property
+    def _tvm_handle(self):
+        return self.handle.value
+
     def __add__(self, other):
         if isinstance(other, Symbol):
             return __add_symbol__(self, other)
@@ -148,8 +155,7 @@ def attr(self, key):
             self.handle, _base.c_str(key), _ctypes.byref(ret), _ctypes.byref(success)))
         if success.value != 0:
             return _base.py_str(ret.value)
-        else:
-            return None
+        return None
 
     def list_attr(self, recursive=False):
         """Get all attributes from the symbol.
diff --git a/nnvm/python/nnvm/top/__init__.py b/nnvm/python/nnvm/top/__init__.py
new file mode 100644
index 000000000000..21bf9ba33f43
--- /dev/null
+++ b/nnvm/python/nnvm/top/__init__.py
@@ -0,0 +1,4 @@
+"""Declaration about Tensor operators"""
+from .attr_dict import AttrDict
+from . import tensor
+from . import nn
diff --git a/nnvm/python/nnvm/top/attr_dict.py b/nnvm/python/nnvm/top/attr_dict.py
new file mode 100644
index 000000000000..fdf14b30a90b
--- /dev/null
+++ b/nnvm/python/nnvm/top/attr_dict.py
@@ -0,0 +1,90 @@
+# pylint: disable=invalid-name
+"""Attr dictionary object used by schedule functions"""
+
+import json
+import tvm
+
+_dict_get = tvm.get_global_func("nnvm.compiler._dict_get")
+_dict_size = tvm.get_global_func("nnvm.compiler._dict_size")
+_dict_keys = tvm.get_global_func("nnvm.compiler._dict_keys")
+
+class AttrDict(object):
+    """Attribute dictionary in nnvm.
+
+    Used by python registration of compute and schedule function.
+    """
+    _tvm_tcode = 18
+
+    def __init__(self, handle):
+        self.handle = handle
+
+    def __del__(self):
+        tvm.nd.free_extension_handle(self.handle, 18)
+
+    @property
+    def _tvm_handle(self):
+        return self.handle.value
+
+    def __getitem__(self, key):
+        return _dict_get(self, key)
+
+    def keys(self):
+        """Get list of keys in the dict.
+
+        Returns
+        -------
+        keys : list of str
+            List of keys
+        """
+        return [x.value for x in _dict_keys(self)]
+
+    def get_int_tuple(self, key):
+        """Get tuple of integer from attr dict
+
+        Parameters
+        ----------
+        key : str
+            The attr key
+
+        Returns
+        -------
+        tuple : tuple of int
+            The result tuple
+        """
+        return tuple(json.loads(self[key]))
+
+    def get_int(self, key):
+        """Get integer from attr dict
+
+        Parameters
+        ----------
+        key : str
+            The attr key
+
+        Returns
+        -------
+        value : int
+            The result value
+        """
+        return int(self[key])
+
+    def get_bool(self, key):
+        """Get bool from attr dict
+
+        Parameters
+        ----------
+        key : str
+            The attr key
+
+        Returns
+        -------
+        value : bool
+            The result value
+        """
+        return self[key] != "False"
+
+    def __repr__(self):
+        return str({k : self[k] for k in self.keys()})
+
+
+tvm.register_extension(AttrDict, AttrDict)
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
new file mode 100644
index 000000000000..cca466a04818
--- /dev/null
+++ b/nnvm/python/nnvm/top/nn.py
@@ -0,0 +1,35 @@
+"""Definition of nn ops"""
+from __future__ import absolute_import
+
+import tvm
+import topi
+from ..compiler import registry as reg
+from ..compiler import OpPattern
+
+# conv
+@reg.register_compute("conv2d")
+def compute_conv2d(attrs, inputs):
+    """Compute definition of conv2d"""
+    padding = attrs.get_int_tuple("padding")
+    strides = attrs.get_int_tuple("strides")
+    dilation = attrs.get_int_tuple("dilation")
+    layout = attrs["layout"]
+    assert layout == "NCHW", "only support nchw for now"
+    assert dilation == (1, 1), "not support dilate now"
+    out = topi.nn.conv2d_nchw(inputs[0], inputs[1], strides, padding)
+    if attrs.get_bool("use_bias"):
+        bias = inputs[2]
+        bias = topi.broadcast_to(bias, (1, bias.shape[0], 1, 1))
+        out = topi.broadcast_add(out, bias)
+    return out
+
+
+@reg.register_schedule("conv2d")
+def schedule_conv2d(_, outs, target):
+    """Schedule definition of conv2d"""
+    if target == "cuda":
+        return topi.cuda.schedule_conv2d_nchw(outs)
+    # naive schedule
+    return tvm.create_schedule([x.op for x in outs])
+
+reg.register_pattern("conv2d", OpPattern.COMPLEX)
diff --git a/nnvm/python/nnvm/top/tensor.py b/nnvm/python/nnvm/top/tensor.py
new file mode 100644
index 000000000000..7b3883fa4edd
--- /dev/null
+++ b/nnvm/python/nnvm/top/tensor.py
@@ -0,0 +1,50 @@
+# pylint: disable=invalid-name
+"""Tensor ops"""
+from __future__ import absolute_import
+
+import tvm
+import topi
+import topi.cuda
+from ..compiler import registry as reg
+from ..compiler import OpPattern
+
+def _schedule_broadcast(_, outs, target):
+    """Generic schedule for binary bcast"""
+    if target == "cuda":
+        return topi.cuda.schedule_elemwise(outs)
+    assert target.startswith("llvm")
+    s = tvm.create_schedule([x.op for x in outs])
+    tvm.schedule.AutoInlineInjective(s)
+    return s
+
+_fschedule_broadcast = tvm.convert(_schedule_broadcast)
+
+# exp
+reg.register_compute("exp",
+                     lambda _, x: topi.exp(x[0]))
+reg.register_pattern("exp", OpPattern.ELEM_WISE)
+reg.register_schedule("exp", _fschedule_broadcast)
+
+# broadcast_add
+reg.register_compute("broadcast_add",
+                     lambda _, x: topi.broadcast_add(x[0], x[1]))
+reg.register_pattern("broadcast_add", OpPattern.BROADCAST)
+reg.register_schedule("broadcast_add", _fschedule_broadcast)
+
+# broadcast_sub
+reg.register_compute("broadcast_sub",
+                     lambda _, x: topi.broadcast_sub(x[0], x[1]))
+reg.register_pattern("broadcast_sub", OpPattern.BROADCAST)
+reg.register_schedule("broadcast_sub", _fschedule_broadcast)
+
+# broadcast_mul
+reg.register_compute("broadcast_mul",
+                     lambda _, x: topi.broadcast_mul(x[0], x[1]))
+reg.register_pattern("broadcast_mul", OpPattern.BROADCAST)
+reg.register_schedule("broadcast_mul", _fschedule_broadcast)
+
+# broadcast_div
+reg.register_compute("broadcast_div",
+                     lambda _, x: topi.broadcast_div(x[0], x[1]))
+reg.register_pattern("broadcast_div", OpPattern.BROADCAST)
+reg.register_schedule("broadcast_div", _fschedule_broadcast)
diff --git a/nnvm/src/compiler/packed_func_ext.cc b/nnvm/src/compiler/packed_func_ext.cc
new file mode 100644
index 000000000000..a66d21807913
--- /dev/null
+++ b/nnvm/src/compiler/packed_func_ext.cc
@@ -0,0 +1,106 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file packed_func_ext.cc
+ * \brief Registeration of extension type.
+ */
+#include <tvm/expr.h>
+#include <tvm/packed_func_ext.h>
+#include <nnvm/op.h>
+#include <nnvm/compiler/packed_func_ext.h>
+#include <nnvm/compiler/op_attr_types.h>
+
+namespace tvm {
+namespace runtime {
+
+TVM_REGISTER_EXT_TYPE(nnvm::Graph);
+TVM_REGISTER_EXT_TYPE(nnvm::Symbol);
+TVM_REGISTER_EXT_TYPE(nnvm::compiler::AttrDict);
+
+}  // namespace runtime
+}  // namespace tvm
+
+
+namespace nnvm {
+namespace compiler {
+
+using tvm::Tensor;
+using tvm::Array;
+using tvm::Node;
+using tvm::runtime::TVMArgs;
+using tvm::runtime::TVMRetValue;
+
+TVM_REGISTER_GLOBAL("nnvm.compiler._dict_get")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+    const AttrDict& dict = args[0].AsExtension<AttrDict>();
+    std::string key = args[1];
+    auto it = dict.find(key);
+    if (it != dict.end()) {
+      *rv = it->second;
+    } else {
+      *rv = nullptr;
+    }
+  });
+
+TVM_REGISTER_GLOBAL("nnvm.compiler._dict_size")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+    const AttrDict& dict = args[0].AsExtension<AttrDict>();
+    *rv = static_cast<int64_t>(dict.size());
+  });
+
+TVM_REGISTER_GLOBAL("nnvm.compiler._dict_keys")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+    const AttrDict& dict = args[0].AsExtension<AttrDict>();
+    tvm::Array<tvm::Expr> keys;
+    for (const auto& kv : dict) {
+      keys.push_back(kv.first);
+    }
+    *rv = keys;
+  });
+
+// custom version of TVM compute
+inline std::unordered_map<std::string, std::string>
+GetAttrDict(const NodeAttrs& attrs) {
+  static auto& fgetdict = nnvm::Op::GetAttr<FGetAttrDict>("FGetAttrDict");
+  if (fgetdict.count(attrs.op)) {
+    return fgetdict[attrs.op](attrs);
+  } else {
+    return attrs.dict;
+  }
+}
+
+
+TVM_REGISTER_GLOBAL("nnvm._register_compute")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+    PackedFunc f = args[1];
+    Op& op = ::dmlc::Registry<nnvm::Op>::Get()->__REGISTER_OR_GET__(args[0]);
+    auto fcompute = [f](const NodeAttrs& attrs, const Array<Tensor>& inputs)
+        -> Array<Tensor> {
+      TVMRetValue ret = f(GetAttrDict(attrs), inputs);
+      if ((*ret.ptr<std::shared_ptr<tvm::Node> >())->derived_from<tvm::TensorNode>()) {
+        return {ret.operator Tensor()};
+      } else {
+        return ret;
+      }
+    };
+    op.set_attr<FTVMCompute>("FTVMCompute", fcompute, args[2]);
+  });
+
+TVM_REGISTER_GLOBAL("nnvm._register_schedule")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+    PackedFunc f = args[1];
+    Op& op = ::dmlc::Registry<nnvm::Op>::Get()->__REGISTER_OR_GET__(args[0]);
+    auto fschedule = [f](const NodeAttrs& attrs,
+                         const Array<Tensor>& outs,
+                         const std::string& target) {
+      return f(GetAttrDict(attrs), outs, target).operator Schedule();
+    };
+    op.set_attr<FTVMSchedule>("FTVMSchedule", fschedule, args[2]);
+  });
+
+TVM_REGISTER_GLOBAL("nnvm._register_pattern")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+    Op& op = ::dmlc::Registry<nnvm::Op>::Get()->__REGISTER_OR_GET__(args[0]);
+    op.set_attr<TOpPattern>("TOpPattern", args[1].operator int(), args[2]);
+  });
+}  // namespace compiler
+}  // namespace nnvm
diff --git a/nnvm/src/compiler/pass/graph_fuse.cc b/nnvm/src/compiler/pass/graph_fuse.cc
new file mode 100644
index 000000000000..23cd6343d236
--- /dev/null
+++ b/nnvm/src/compiler/pass/graph_fuse.cc
@@ -0,0 +1,393 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file graph_fuse.cc
+ * \brief Fuse the operators together.
+ */
+#include <nnvm/graph.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/graph_attr_types.h>
+#include <nnvm/tuple.h>
+#include <nnvm/pass.h>
+#include <nnvm/compiler/op_attr_types.h>
+#include <nnvm/compiler/packed_func_ext.h>
+#include <tvm/runtime/packed_func.h>
+#include <tvm/operation.h>
+#include <tvm/lowered_func.h>
+#include "../../runtime/graph_executor.h"
+
+namespace nnvm {
+namespace compiler {
+
+using namespace tvm;
+
+using DLTypeVector = std::vector<DLDataType>;
+
+// The single fuse rule.
+enum class FuseRule {
+  kUknown,
+  kFuseToMaster,
+  kRealize
+};
+
+DLDataType GetDLType(int type_flag) {
+  if (type_flag == 0) return Type2TVMType(Float(32));
+  LOG(FATAL) << "unknown type_flag=" << type_flag;
+  return Type2TVMType(Float(32));
+}
+
+// Partition the graph into segments
+// Each segment will be compiled into one operator.
+// Need also mark the property of the segment.
+nnvm::Graph GraphFusePartition(nnvm::Graph g) {
+  // setup ref counter
+  const IndexedGraph& idx = g.indexed_graph();
+  // Get attributes from the graph
+  const ShapeVector& shape_vec = g.GetAttr<ShapeVector>("shape");
+  const DTypeVector& dtype_vec = g.GetAttr<DTypeVector>("dtype");
+  // Transform to dltype
+  // In future, directly fo type inference in dltype.
+  DLTypeVector dltype_vec = DLTypeVector(dtype_vec.size());
+  for (size_t i = 0; i < dtype_vec.size(); ++i) {
+    dltype_vec[i] = GetDLType(dtype_vec[i]);
+  }
+
+  // Reference counter of each op node
+  // For now, always store result when an op is referred more than once.
+  std::vector<uint32_t> ref_count(idx.num_nodes(), 0);
+  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+    const auto& inode = idx[nid];
+    if (inode.source->is_variable()) continue;
+    for (const auto& e : inode.inputs) {
+      ++ref_count[e.node_id];
+    }
+  }
+  for (const auto& e : idx.outputs()) {
+    // this line will realize all the outputs
+    ref_count[e.node_id] += 2;
+  }
+  // Pattern fo the subgraph
+  std::vector<TOpPattern> pattern_vec(idx.num_nodes(),  kExtern);
+  // Whether node can be fused to parent.
+  std::vector<FuseRule> fuse_vec(idx.num_nodes(), FuseRule::kUknown);
+  // Master node id of fusion segment.
+  std::vector<int> master_vec(idx.num_nodes(), -1);
+  // Operator pattern
+  static auto& op_pattern = nnvm::Op::GetAttr<TOpPattern>("TOpPattern");
+
+  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+    const auto& inode = idx[nid];
+    if (inode.source->is_variable()) {
+      fuse_vec[nid] = FuseRule::kRealize; continue;
+    }
+    TOpPattern pt = op_pattern.get(inode.source->op(), kExtern);
+
+    if (pt <= kBroadcast) {
+      int chosen_master = -1;
+      bool ewise = inode.source->num_outputs() == 1;
+      for (const auto& e : inode.inputs) {
+        if (fuse_vec[e.node_id] == FuseRule::kUknown) {
+          TOpPattern ipt = pattern_vec[e.node_id];
+          if (ipt != kElemWise) ewise = false;
+          if (ipt <= kBroadcast) {
+            fuse_vec[e.node_id] = FuseRule::kFuseToMaster;
+          } else if (ipt == kComplex && chosen_master == -1 &&
+            shape_vec[idx.entry_id(nid, 0)] == shape_vec[idx.entry_id(e)]) {
+            chosen_master = master_vec[e.node_id];
+            fuse_vec[e.node_id] = FuseRule::kFuseToMaster;
+          } else {
+            fuse_vec[e.node_id] = FuseRule::kRealize;
+          }
+        }
+        if (ewise) {
+          if (shape_vec[idx.entry_id(nid, 0)] != shape_vec[idx.entry_id(e)]) {
+            ewise = false;
+          }
+        }
+      }
+      master_vec[nid] = chosen_master;
+      if (chosen_master != -1) {
+        pt = kComplex;
+      } else {
+        pt = ewise ? kElemWise : kBroadcast;
+      }
+    } else {
+      master_vec[nid] = nid;
+      for (const auto& e : inode.inputs) {
+        if (fuse_vec[e.node_id] == FuseRule::kUknown) {
+          fuse_vec[e.node_id] = FuseRule::kRealize;
+          if (master_vec[e.node_id] == -1) {
+            master_vec[e.node_id] = e.node_id;
+          }
+        }
+      }
+    }
+
+    pattern_vec[nid] = pt;
+    if (ref_count[nid] > 1) {
+      fuse_vec[nid] = FuseRule::kRealize;
+      if (master_vec[nid] == -1) {
+        master_vec[nid] = nid;
+      }
+    }
+  }
+
+
+  // point to the group root id of each node
+  std::vector<int> group_vec(idx.num_nodes(), -1);
+  for (uint32_t i = idx.num_nodes(); i != 0; --i) {
+    uint32_t nid = i - 1;
+    const auto& inode = idx[nid];
+    if (group_vec[nid] == -1) {
+      group_vec[nid] = nid;
+    }
+    // propagate the group id.
+    for (const auto& e : inode.inputs) {
+      if (fuse_vec[e.node_id] == FuseRule::kFuseToMaster) {
+        CHECK(group_vec[e.node_id] == -1||
+              group_vec[e.node_id] == group_vec[nid]);
+        group_vec[e.node_id] = group_vec[nid];
+      }
+    }
+  }
+  g.attrs["group_root"] = std::make_shared<any>(std::move(group_vec));
+  g.attrs["group_master"] = std::make_shared<any>(std::move(master_vec));
+  g.attrs["pattern"] = std::make_shared<any>(std::move(pattern_vec));
+  g.attrs["dltype"] = std::make_shared<any>(std::move(dltype_vec));
+  return g;
+}
+
+
+NNVM_REGISTER_PASS(GraphFusePartition)
+.set_body(GraphFusePartition)
+.depend_graph_attr("shape")
+.depend_graph_attr("dtype")
+.provide_graph_attr("dltype");
+
+struct NodeEntryHash {
+  size_t operator()(const IndexedGraph::NodeEntry& e) const {
+    return e.node_id;
+  }
+};
+
+struct NodeEntryEqual {
+  size_t operator()(const IndexedGraph::NodeEntry& a,
+                    const IndexedGraph::NodeEntry& b) const {
+    return a.node_id == b.node_id && a.index == b.index;
+  }
+};
+
+// Auxiliary data structure for representing fused op.
+struct FuseEntry {
+  // The inputs
+  std::vector<IndexedGraph::NodeEntry> inputs;
+  // The input map
+  std::unordered_map<IndexedGraph::NodeEntry, Tensor,
+                     NodeEntryHash, NodeEntryEqual> imap;
+  // Output tensors
+  Array<Tensor> outputs;
+  // Placeholder for inputs
+  Array<Tensor> placeholder;
+  // Computing schedule
+  Schedule schedule;
+  // Function name
+  std::string func_name;
+};
+
+// Fuse the partitioned graph into segments.
+// Create a new graph with fused noded.
+// Also inheritate attribute shape, dltype from previous graph.
+nnvm::Graph GraphFuse(nnvm::Graph g) {
+  // setup ref counter
+  const IndexedGraph& idx = g.indexed_graph();
+  // Get attributes from the graph
+  const ShapeVector& shape_vec = g.GetAttr<ShapeVector>("shape");
+  const DLTypeVector& dltype_vec = g.GetAttr<DLTypeVector>("dltype");
+  const DTypeVector& dtype_vec = g.GetAttr<DTypeVector>("dtype");
+  const std::vector<int>& group_vec = g.GetAttr<std::vector<int> >("group_root");
+  const std::vector<int>& master_vec = g.GetAttr<std::vector<int> >("group_master");
+  const std::vector<TOpPattern>& pattern_vec =
+      g.GetAttr<std::vector<TOpPattern> >("pattern");
+  std::string target = g.GetAttr<std::string>("target");
+  std::vector<FuseEntry> fuse_vec(idx.num_nodes());
+  // setup inputs and placeholder.
+  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+    const auto& inode = idx[nid];
+    if (inode.source->is_variable()) continue;
+    CHECK_GE(group_vec[nid], 0);
+    int root_id = group_vec[nid];
+    FuseEntry& fe = fuse_vec[root_id];
+    TOpPattern pt = pattern_vec[root_id];
+    for (const auto& e : inode.inputs) {
+      if (group_vec[e.node_id] != root_id && fe.imap.count(e) == 0) {
+        Array<Expr> shape;
+        if (pt == kElemWise) {
+          // elementwise support flatten
+          int64_t prod = 1;
+          for (int64_t x : shape_vec[idx.entry_id(e)]) {
+            prod *= x;
+          }
+          CHECK_LE(prod, static_cast<int64_t>(std::numeric_limits<int>::max()));
+          shape.push_back(make_const(Int(32), prod));
+        } else {
+          for (int64_t x : shape_vec[idx.entry_id(e)]) {
+            CHECK_LE(x, static_cast<int64_t>(std::numeric_limits<int>::max()));
+            shape.push_back(make_const(Int(32), x));
+          }
+        }
+        std::ostringstream os_name;
+        os_name << "input" << fe.inputs.size();
+        Tensor data = placeholder(
+            shape, TVMType2Type(dltype_vec[idx.entry_id(e)]),
+            os_name.str());
+        fe.imap[e] = data;
+        fe.inputs.push_back(e);
+        fe.placeholder.push_back(data);
+      }
+    }
+  }
+  // Setup the Tensor
+  std::vector<Tensor> tensor_vec(idx.num_node_entries());
+  static auto& fcompute =
+      nnvm::Op::GetAttr<FTVMCompute>("FTVMCompute");
+  static auto& fschedule =
+      nnvm::Op::GetAttr<FTVMSchedule>("FTVMSchedule");
+  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+    const auto& inode = idx[nid];
+    if (inode.source->is_variable()) continue;
+    int root_id = group_vec[nid];
+    FuseEntry& fe = fuse_vec[root_id];
+    Array<Tensor> inputs;
+    // input loading
+    for (const auto& e : inode.inputs) {
+      if (group_vec[e.node_id] != root_id) {
+        auto it = fe.imap.find(e);
+        CHECK(it != fe.imap.end());
+        inputs.push_back(it->second);
+      } else {
+        Tensor t = tensor_vec[idx.entry_id(e)];
+        CHECK(t.defined());
+        inputs.push_back(t);
+      }
+    }
+    // get default
+    Array<Tensor> out = fcompute[inode.source->op()](
+        inode.source->attrs, inputs);
+    CHECK_EQ(out.size(), inode.source->num_outputs());
+
+    // schedule on root node, and use master's schedule
+    if (nid != root_id) {
+      for (uint32_t index = 0; index < inode.source->num_outputs(); ++index) {
+        uint32_t eid = idx.entry_id(nid, index);
+        tensor_vec[eid] = out[index];
+      }
+    } else {
+      fe.outputs = out;
+      int master = master_vec[root_id];
+      CHECK_GE(master, 0);
+      fe.schedule = fschedule[idx[master].source->op()](
+          idx[master].source->attrs, fe.outputs, target);
+      std::ostringstream os;
+      os << idx[master].source->attrs.name + "_id" << nid;
+      fe.func_name = os.str();
+    }
+  }
+  static const PackedFunc& flower = GetPackedFunc("nnvm.compiler.lower");
+  static const PackedFunc& fbuild = GetPackedFunc("nnvm.compiler.build_target");
+
+  Array<tvm::LoweredFunc> funcs;
+  for (const FuseEntry& fe : fuse_vec) {
+    if (fe.schedule.defined()) {
+      Array<tvm::Tensor> args = fe.placeholder;
+      for (tvm::Tensor x : fe.outputs) {
+        args.push_back(x);
+      }
+      Array<tvm::LoweredFunc> ret = flower(fe.schedule, args, fe.func_name);
+      for (LoweredFunc x : ret) {
+        funcs.push_back(x);
+      }
+    }
+  }
+  tvm::runtime::Module module = fbuild(funcs, target);
+  // Final step: Remap the node, with given attribute
+  const nnvm::Op* tvm_op = nnvm::Op::Get("tvm_op");
+
+  std::unordered_map<uint32_t, nnvm::NodePtr> old_new;
+  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+    const auto& inode = idx[nid];
+    if (inode.source->is_variable()) {
+      nnvm::NodePtr np = nnvm::Node::Create();
+      np->attrs = inode.source->attrs;
+      old_new[nid] = np;
+    } else {
+      int root_id = group_vec[nid];
+      if (nid != root_id) continue;
+      FuseEntry& fe = fuse_vec[root_id];
+      nnvm::NodePtr np = nnvm::Node::Create();
+      np->attrs.op = tvm_op;
+      np->attrs.name = inode.source->attrs.name;
+      runtime::TVMOpParam param;
+      param.func_name = fuse_vec[nid].func_name;
+      param.num_inputs = static_cast<uint32_t>(fe.inputs.size());
+      param.num_outputs = static_cast<uint32_t>(fe.outputs.size());
+      param.flatten_data = pattern_vec[nid] == kElemWise;
+      param.UpdateDict(&(np->attrs.dict));
+      np->attrs.parsed = std::move(param);
+      for (const auto& e : fe.inputs) {
+        auto it = old_new.find(e.node_id);
+        CHECK(it != old_new.end())
+            << "cannot find node_id=" << e.node_id;
+        np->inputs.emplace_back(
+            nnvm::NodeEntry{it->second, e.index, e.version});
+      }
+      for (const uint32_t node_id : inode.control_deps) {
+        auto it = old_new.find(node_id);
+        CHECK(it != old_new.end());
+        np->control_deps.emplace_back(it->second);
+      }
+      old_new[nid] = np;
+    }
+  }
+
+  nnvm::Graph ret;
+  for (const auto& e : idx.outputs()) {
+    auto it = old_new.find(group_vec[e.node_id]);
+    CHECK(it != old_new.end())
+        << "cannot find node_id=" << e.node_id;
+    ret.outputs.emplace_back(
+        nnvm::NodeEntry{it->second, e.index, e.version});
+  }
+  const IndexedGraph& new_idx = ret.indexed_graph();
+  ShapeVector new_shape_vec = ShapeVector(new_idx.num_node_entries(), TShape());
+  DTypeVector new_dtype_vec = DTypeVector(new_idx.num_node_entries());
+  std::vector<std::string> new_dltype_vec(new_idx.num_node_entries());
+  for (const auto& kv : old_new) {
+    uint32_t nid = kv.first;
+    const auto& inode = idx[nid];
+    for (uint32_t i = 0; i < inode.source->num_outputs(); ++i) {
+      uint32_t new_eid = new_idx.entry_id(new_idx.node_id(kv.second.get()), i);
+      uint32_t old_eid = idx.entry_id(nid, i);
+      new_shape_vec[new_eid] = shape_vec[old_eid];
+      new_dtype_vec[new_eid] = dtype_vec[old_eid];
+      new_dltype_vec[new_eid] = tvm::runtime::TVMType2String(dltype_vec[old_eid]);
+    }
+  }
+  ret.attrs["shape"] = std::make_shared<any>(std::move(new_shape_vec));
+  ret.attrs["dtype"] = std::make_shared<any>(std::move(new_dtype_vec));
+  ret.attrs["dltype"] = std::make_shared<any>(std::move(new_dltype_vec));
+  ret.attrs["module"] = std::make_shared<any>(std::move(module));
+  ret = nnvm::ApplyPass(ret, "PlanMemory");
+  return ret;
+}
+
+NNVM_REGISTER_PASS(GraphFuse)
+.set_body(GraphFuse);
+
+
+TVM_REGISTER_GLOBAL("nnvm.compiler._move_module")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+    const nnvm::Graph& g = args[0].AsExtension<Graph>();
+    *rv = const_cast<nnvm::Graph*>(&g)->
+        MoveCopyAttr<tvm::runtime::Module>("module");
+  });
+}  // namespace compiler
+}  // namespace nnvm
diff --git a/nnvm/src/compiler/pass/layout_transform.cc b/nnvm/src/compiler/pass/layout_transform.cc
new file mode 100644
index 000000000000..2834654fd017
--- /dev/null
+++ b/nnvm/src/compiler/pass/layout_transform.cc
@@ -0,0 +1,159 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file layout_transform.cc
+ * \brief Transforms layout.
+ */
+#include <nnvm/graph.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/graph_attr_types.h>
+#include <nnvm/pass.h>
+#include <nnvm/compiler/op_attr_types.h>
+#include <nnvm/compiler/contrib_op_param.h>
+
+namespace nnvm {
+namespace compiler {
+
+const TLayoutInfo& GetDefaultLayout() {
+  static TLayoutInfo default_layout = "default";
+  return default_layout;
+}
+
+nnvm::NodePtr CreateLayoutTransformNode(const std::string& src,
+                                        const std::string& dst) {
+  static const nnvm::Op* trans_op = nnvm::Op::Get("layout_transform");
+  static int count = 0;
+  nnvm::NodePtr n = nnvm::Node::Create();
+  n->attrs.op = trans_op;
+  n->attrs.name = src + "_to_" + dst + std::to_string(count++);
+  n->attrs.dict["src_layout"] = src;
+  n->attrs.dict["dst_layout"] = dst;
+  n->op()->attr_parser(&(n->attrs));
+  return n;
+}
+
+/*!
+ * \brief A simple layout transform pass that will
+ *  insert layout transform nodes automatically.
+ */
+nnvm::Graph LayoutTransform(nnvm::Graph src) {
+  static auto& op_layout_request =
+    nnvm::Op::GetAttr<FTVMLayoutRequest>("FTVMLayoutRequest");
+  static auto& op_vecop =
+    nnvm::Op::GetAttr<FTVMVectorizedOp>("FTVMVectorizedOp");
+  static auto& op_pattern = nnvm::Op::GetAttr<TOpPattern>("TOpPattern");
+
+  const ShapeVector& shape_vec = src.GetAttr<ShapeVector>("shape");
+  const std::vector<TLayoutInfo>& input_layouts =
+    src.GetAttr<std::vector<TLayoutInfo> >("layout");
+
+  const IndexedGraph& idx = src.indexed_graph();
+  std::vector<TLayoutInfo> produce_vec(idx.num_node_entries(), GetDefaultLayout());
+  std::vector<nnvm::NodePtr> mirror_vec(idx.num_nodes(), nullptr);
+
+  // use op pattern to decide whether an op is map
+  auto is_map_op = [&](size_t nid) {
+    TOpPattern pt = op_pattern.get(idx[nid].source->op(), kExtern);
+    bool is_map = (pt <= kBroadcast);
+    if (pt == kBroadcast) {
+      for (const auto& e : idx[nid].inputs) {
+        if (shape_vec[idx.entry_id(nid, 0)] != shape_vec[idx.entry_id(e)]) {
+          is_map = false;
+          break;
+        }
+      }
+    }
+    return is_map;
+  };
+
+  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+    const auto& inode = idx[nid];
+    nnvm::NodePtr new_node = nnvm::Node::Create();
+    *new_node = *(inode.source);
+    if (new_node->is_variable()) {
+      auto input_iter = std::find(
+        idx.input_nodes().cbegin(), idx.input_nodes().cend(), nid);
+      CHECK(input_iter != idx.input_nodes().cend());
+      size_t input_id = std::distance(idx.input_nodes().cbegin(), input_iter);
+      produce_vec[idx.entry_id(nid, 0)] = input_layouts[input_id];
+      mirror_vec[nid] = new_node;
+      continue;
+    }
+
+    if (op_vecop.count(inode.source->op())) {
+      new_node = op_vecop[inode.source->op()](inode.source);
+      new_node->inputs.resize(new_node->num_inputs());
+    }
+
+    // set up output and input layouts
+    std::vector<TLayoutInfo> request_ilayouts(new_node->num_inputs(), GetDefaultLayout());
+    if (op_layout_request.count(new_node->op())) {
+      std::vector<TLayoutInfo> produce_olayouts(new_node->num_outputs(), GetDefaultLayout());
+      CHECK(op_layout_request[new_node->op()](
+          new_node->attrs, &request_ilayouts, &produce_olayouts))
+          << "Layout request fail";
+
+      CHECK_EQ(request_ilayouts.size(), new_node->num_inputs());
+      CHECK_EQ(produce_olayouts.size(), new_node->num_outputs());
+      for (size_t i = 0; i < new_node->num_outputs(); ++i) {
+        produce_vec[idx.entry_id(nid, i)] = produce_olayouts[i];
+      }
+    }
+
+    bool map_layout = is_map_op(nid);
+    if (map_layout) {
+      const TLayoutInfo& layout = produce_vec[idx.entry_id(inode.inputs[0])];
+      for (const auto& e : inode.inputs) {
+        if (produce_vec[idx.entry_id(e)] != layout) {
+          map_layout = false;
+          break;
+        }
+      }
+      if (map_layout) {
+        for (size_t i = 0; i < inode.source->num_outputs(); ++i) {
+          produce_vec[idx.entry_id(nid, i)] = layout;
+        }
+      }
+    }
+
+    for (size_t i = 0; i < inode.inputs.size(); ++i) {
+      const auto& e = inode.inputs[i];
+      const nnvm::NodePtr& in = mirror_vec[e.node_id];
+      new_node->inputs[i] =
+        nnvm::NodeEntry{in, e.index, e.version};
+
+      TLayoutInfo produce = produce_vec[idx.entry_id(e)];
+      TLayoutInfo request = request_ilayouts[i];
+      if (!map_layout && (produce != request)) {
+        nnvm::NodePtr tnode = CreateLayoutTransformNode(produce, request);
+        tnode->attrs.name =
+          idx[e.node_id].source->attrs.name + "_" + request;
+        tnode->inputs.emplace_back(new_node->inputs[i]);
+        new_node->inputs[i] = nnvm::NodeEntry{tnode, 0, 0};
+      }
+    }
+    mirror_vec[nid] = new_node;
+  }
+
+  std::vector<nnvm::NodeEntry> outputs;
+  for (const auto& e : idx.outputs()) {
+    TLayoutInfo produce = produce_vec[idx.entry_id(e)];
+    if (produce != GetDefaultLayout()) {
+      nnvm::NodePtr tnode = CreateLayoutTransformNode(produce, GetDefaultLayout());
+      tnode->attrs.name =
+        idx[e.node_id].source->attrs.name + "_default";
+      tnode->inputs.emplace_back(
+        nnvm::NodeEntry{mirror_vec[e.node_id], e.index, e.version});
+      outputs.emplace_back(nnvm::NodeEntry{tnode, 0, 0});
+    } else {
+      outputs.emplace_back(
+        nnvm::NodeEntry{mirror_vec[e.node_id], e.index, e.version});
+    }
+  }
+
+  nnvm::Graph ret;
+  ret.outputs = std::move(outputs);
+  return ret;
+}
+
+}  // namespace compiler
+}  // namespace nnvm
diff --git a/nnvm/src/compiler/pass/prune_graph.cc b/nnvm/src/compiler/pass/prune_graph.cc
new file mode 100644
index 000000000000..209c1ed36dee
--- /dev/null
+++ b/nnvm/src/compiler/pass/prune_graph.cc
@@ -0,0 +1,76 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file prune_graph.cc
+ * \brief Prune the graph to do constant folding.
+ *
+ *  This pass breaks the graph into pre-compute graph
+ *  and the execution graph.
+ */
+#include <nnvm/graph.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/graph_attr_types.h>
+#include <nnvm/pass.h>
+#include <nnvm/compiler/op_attr_types.h>
+#include <unordered_set>
+
+namespace nnvm {
+namespace compiler {
+
+nnvm::Graph PruneGraph(nnvm::Graph src) {
+  const auto& params = src.GetAttr<std::unordered_set<std::string> >("params");
+
+  std::unordered_set<nnvm::Node*> pruned;
+  nnvm::NodeEntryMap<nnvm::NodePtr> entry_var;
+  DFSVisit(src.outputs, [&](const nnvm::NodePtr& n) {
+    bool can_be_pruned = true;
+    if (n->is_variable()) {
+      if (params.count(n->attrs.name)) {
+        pruned.emplace(n.get());
+      }
+      can_be_pruned = false;
+    }
+
+    for (const auto& e : n->inputs) {
+      if (!pruned.count(e.node.get())) {
+        can_be_pruned = false;
+      }
+    }
+    if (can_be_pruned) {
+      pruned.emplace(n.get());
+    } else {
+      // scan again to find edge nodes, skip variables
+      for (auto& e : n->inputs) {
+        if (!e.node->is_variable() && pruned.count(e.node.get())) {
+          if (!entry_var.count(e)) {
+            nnvm::NodePtr var = nnvm::Node::Create();
+            var->attrs.name = e.node->attrs.name + "_output" + std::to_string(e.index);
+            entry_var.emplace(e, var);
+          }
+          e = nnvm::NodeEntry{entry_var.at(e), 0, 0};
+        }
+      }
+    }
+  });
+
+  nnvm::Graph pre_graph;
+  pre_graph.outputs.reserve(entry_var.size());
+  std::vector<std::string> output_names;
+  output_names.reserve(entry_var.size());
+  for (auto kv : entry_var) {
+    if (kv.first.node->is_variable()) continue;
+    pre_graph.outputs.emplace_back(kv.first);
+    output_names.emplace_back(kv.second->attrs.name);
+  }
+
+  pre_graph.attrs["pruned_params"] =
+    std::make_shared<dmlc::any>(std::move(output_names));
+  src.attrs["pre_graph"] =
+    std::make_shared<dmlc::any>(std::move(pre_graph));
+  return src;
+}
+
+NNVM_REGISTER_PASS(PruneGraph)
+.set_body(PruneGraph);
+
+}  // namespace compiler
+}  // namespace nnvm
diff --git a/nnvm/src/runtime/graph_executor.cc b/nnvm/src/runtime/graph_executor.cc
index 5bdf1979dfa8..63e42a6a8844 100644
--- a/nnvm/src/runtime/graph_executor.cc
+++ b/nnvm/src/runtime/graph_executor.cc
@@ -312,23 +312,34 @@ NNVM_REGISTER_OP(tvm_op)
     return param.num_outputs;
   });
 
-TVM_REGISTER_GLOBAL("nnvm.tvm.create_executor")
+tvm::runtime::Module RuntimeCreate(std::string sym_json,
+                                   tvm::runtime::Module m,
+                                   int device_type,
+                                   int device_id) {
+  TVMContext ctx;
+  ctx.device_type = static_cast<DLDeviceType>(device_type);
+  ctx.device_id   = device_id;
+  // load graph from json string
+  nnvm::Graph g;
+  g.attrs["json"] = std::make_shared<nnvm::any>(sym_json);
+  g = nnvm::ApplyPass(std::move(g), "LoadJSON");
+  std::shared_ptr<GraphExecutor> exec = std::make_shared<GraphExecutor>();
+  exec->Init(g, m, ctx);
+  return tvm::runtime::Module(exec);
+}
+
+TVM_REGISTER_GLOBAL("nnvm.runtime.create")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+    *rv = RuntimeCreate(args[0], args[1], args[2], args[3]);
+  });
+
+TVM_REGISTER_GLOBAL("nnvm.runtime.remote_create")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
-    std::string sym_json = args[0];
-    std::string param_blob = args[1];
-    tvm::runtime::Module m = args[2];
-    TVMContext ctx;
-    ctx.device_type = static_cast<DLDeviceType>(args[3].operator int());
-    ctx.device_id   = args[4];
-    // load graph from json string
-    nnvm::Graph g;
-    g.attrs["json"] = std::make_shared<nnvm::any>(sym_json);
-    g = nnvm::ApplyPass(std::move(g), "LoadJSON");
-    std::shared_ptr<GraphExecutor> exec = std::make_shared<GraphExecutor>();
-    exec->Init(g, m, ctx);
-    // load params form stream of string
-    exec->LoadParams(std::move(param_blob));
-    *rv = tvm::runtime::Module(exec);
+    void* mhandle = args[1];
+    *rv = RuntimeCreate(args[0],
+                        *static_cast<tvm::runtime::Module*>(mhandle),
+                        args[2], args[3]);
   });
+
 }  // namespace runtime
 }  // namespace nnvm
diff --git a/nnvm/src/top/nn/convolution.cc b/nnvm/src/top/nn/convolution.cc
index 1de8efb22698..77d9ad67f711 100644
--- a/nnvm/src/top/nn/convolution.cc
+++ b/nnvm/src/top/nn/convolution.cc
@@ -114,11 +114,12 @@ a bias vector is created and added to the outputs.
 .add_argument("bias", "1D Tensor", "Bias parameter.")
 .add_arguments(Conv2DParam::__FIELDS__())
 .set_attr_parser(ParamParser<Conv2DParam>)
-.set_num_outputs(1)
-.set_num_inputs(UseBiasNumInputs<Conv2DParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<Conv2DParam>)
 .set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<Conv2DParam>)
 .set_attr<FInferShape>("FInferShape", Conv2DInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
+.set_num_outputs(1)
+.set_num_inputs(UseBiasNumInputs<Conv2DParam>)
 .set_support_level(2);
 
 
@@ -203,11 +204,12 @@ said convolution.
 .add_argument("bias", "1D Tensor", "Bias parameter.")
 .add_arguments(Conv2DTransposeParam::__FIELDS__())
 .set_attr_parser(ParamParser<Conv2DTransposeParam>)
-.set_num_outputs(1)
-.set_num_inputs(UseBiasNumInputs<Conv2DTransposeParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<Conv2DTransposeParam>)
 .set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<Conv2DTransposeParam>)
 .set_attr<FInferShape>("FInferShape", Conv2DTransposeInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
+.set_num_outputs(1)
+.set_num_inputs(UseBiasNumInputs<Conv2DTransposeParam>)
 .set_support_level(2);
 
 }  // namespace top
diff --git a/nnvm/src/top/nn/nn.cc b/nnvm/src/top/nn/nn.cc
index ee05b6be57a5..cc0f5d1ea381 100644
--- a/nnvm/src/top/nn/nn.cc
+++ b/nnvm/src/top/nn/nn.cc
@@ -66,6 +66,7 @@ If ``use_bias`` is set to be false, then the ``bias`` term is ignored.
 .add_argument("bias", "1D Tensor", "Bias parameter.")
 .add_arguments(DenseParam::__FIELDS__())
 .set_attr_parser(ParamParser<DenseParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<DenseParam>)
 .set_num_outputs(1)
 .set_num_inputs(UseBiasNumInputs<DenseParam>)
 .set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<DenseParam>)
@@ -95,10 +96,11 @@ NNVM_REGISTER_OP(dropout)
 
 )" NNVM_ADD_FILELINE)
 .add_argument("data", "Tensor", "Input to which dropout will be applied")
+.add_arguments(DropoutParam::__FIELDS__())
+.set_attr_parser(ParamParser<DropoutParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<DropoutParam>)
 .set_num_inputs(1)
 .set_num_outputs(2)
-.set_attr_parser(ParamParser<DropoutParam>)
-.add_arguments(DropoutParam::__FIELDS__())
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 2>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 2>)
 .set_attr<FNumVisibleOutputs>("FNumVisibleOutputs", [](const NodeAttrs& attrs) {
@@ -172,10 +174,11 @@ axis to be the last item in the input shape.
 .add_argument("beta", "Tensor", "The beta offset factor")
 .add_argument("moving_mean", "Tensor", "running mean of input")
 .add_argument("moving_var", "Tensor", "running variance of input")
+.add_arguments(BatchNormParam::__FIELDS__())
+.set_attr_parser(ParamParser<BatchNormParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<BatchNormParam>)
 .set_num_inputs(5)
 .set_num_outputs(3)
-.set_attr_parser(ParamParser<BatchNormParam>)
-.add_arguments(BatchNormParam::__FIELDS__())
 .set_attr<FInferShape>("FInferShape", BatchNormInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<5, 3>)
 .set_attr<FListInputNames>("FListInputNames", [](const NodeAttrs& attrs) {
@@ -203,10 +206,12 @@ NNVM_REGISTER_OP(softmax)
 .. note::
     This operator can be optimized away for inference.
 )code" NNVM_ADD_FILELINE)
+.add_argument("data", "Tensor", "Input data.")
+.add_arguments(SoftmaxParam::__FIELDS__())
+.set_attr_parser(ParamParser<SoftmaxParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<SoftmaxParam>)
 .set_num_inputs(1)
 .set_num_outputs(1)
-.set_attr_parser(ParamParser<SoftmaxParam>)
-.add_arguments(SoftmaxParam::__FIELDS__())
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
 .set_support_level(1);
@@ -220,10 +225,12 @@ NNVM_REGISTER_OP(log_softmax)
 .. note::
     This operator can be optimized away for inference.
 )code" NNVM_ADD_FILELINE)
+.add_argument("data", "Tensor", "Input data.")
+.add_arguments(SoftmaxParam::__FIELDS__())
+.set_attr_parser(ParamParser<SoftmaxParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<SoftmaxParam>)
 .set_num_inputs(1)
 .set_num_outputs(1)
-.set_attr_parser(ParamParser<SoftmaxParam>)
-.add_arguments(SoftmaxParam::__FIELDS__())
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
 .set_support_level(1);
@@ -237,10 +244,12 @@ NNVM_REGISTER_OP(leaky_relu)
 `y = x > 0 ? x : alpha * x`
 
 )code" NNVM_ADD_FILELINE)
+.add_argument("data", "Tensor", "Input data.")
+.add_arguments(LeakyReLUParam::__FIELDS__())
+.set_attr_parser(ParamParser<LeakyReLUParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<LeakyReLUParam>)
 .set_num_inputs(1)
 .set_num_outputs(1)
-.set_attr_parser(ParamParser<LeakyReLUParam>)
-.add_arguments(LeakyReLUParam::__FIELDS__())
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
 .set_support_level(1);
diff --git a/nnvm/src/top/nn/pooling.cc b/nnvm/src/top/nn/pooling.cc
index 299fdc66d57a..c895437cea15 100644
--- a/nnvm/src/top/nn/pooling.cc
+++ b/nnvm/src/top/nn/pooling.cc
@@ -72,6 +72,7 @@ NNVM_REGISTER_OP(max_pool2d)
 .add_argument("data", "4D Tensor", "Input data.")
 .add_arguments(Pool2DParam::__FIELDS__())
 .set_attr_parser(ParamParser<Pool2DParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<Pool2DParam>)
 .set_num_outputs(1)
 .set_num_inputs(1)
 .set_attr<FInferShape>("FInferShape", Pool2DInferShape)
@@ -98,10 +99,11 @@ NNVM_REGISTER_OP(avg_pool2d)
 .add_argument("data", "4D Tensor", "Input data.")
 .add_arguments(Pool2DParam::__FIELDS__())
 .set_attr_parser(ParamParser<Pool2DParam>)
-.set_num_outputs(1)
-.set_num_inputs(1)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<Pool2DParam>)
 .set_attr<FInferShape>("FInferShape", Pool2DInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_num_outputs(1)
+.set_num_inputs(1)
 .set_support_level(2);
 
 
@@ -135,10 +137,11 @@ NNVM_REGISTER_OP(global_max_pool2d)
 .add_argument("data", "4D Tensor", "Input data.")
 .add_arguments(GlobalPool2DParam::__FIELDS__())
 .set_attr_parser(ParamParser<GlobalPool2DParam>)
-.set_num_outputs(1)
-.set_num_inputs(1)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<GlobalPool2DParam>)
 .set_attr<FInferShape>("FInferShape", GlobalPool2DInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_num_outputs(1)
+.set_num_inputs(1)
 .set_support_level(2);
 
 
@@ -154,10 +157,11 @@ NNVM_REGISTER_OP(global_avg_pool2d)
 .add_argument("data", "4D Tensor", "Input data.")
 .add_arguments(GlobalPool2DParam::__FIELDS__())
 .set_attr_parser(ParamParser<GlobalPool2DParam>)
-.set_num_outputs(1)
-.set_num_inputs(1)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<GlobalPool2DParam>)
 .set_attr<FInferShape>("FInferShape", GlobalPool2DInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_num_outputs(1)
+.set_num_inputs(1)
 .set_support_level(2);
 
 }  // namespace top
diff --git a/nnvm/src/top/op_common.h b/nnvm/src/top/op_common.h
index 14ec45bf7457..110e08a5841e 100644
--- a/nnvm/src/top/op_common.h
+++ b/nnvm/src/top/op_common.h
@@ -37,6 +37,19 @@ inline void ParamParser(nnvm::NodeAttrs* attrs) {
   attrs->parsed = std::move(param);
 }
 
+/*!
+ * \brief Parse keyword arguments as PType arguments and save to parsed
+ * \tparam PType the arameter type.
+ * \param attrs The attributes.
+ */
+template<typename PType>
+inline std::unordered_map<std::string, std::string>
+ParamGetAttrDict(const nnvm::NodeAttrs& attrs) {
+  std::unordered_map<std::string, std::string> dict = attrs.dict;
+  nnvm::get<PType>(attrs.parsed).UpdateDict(&dict);
+  return dict;
+}
+
 /*! \brief check if shape is empty or contains unkown (0) dim. */
 inline bool shape_is_none(const TShape& x) {
   return x.ndim() == 0 || x.Size() == 0;
diff --git a/nnvm/src/top/tensor/broadcast.cc b/nnvm/src/top/tensor/broadcast.cc
index d7ca979abaeb..945195fd0d32 100644
--- a/nnvm/src/top/tensor/broadcast.cc
+++ b/nnvm/src/top/tensor/broadcast.cc
@@ -61,13 +61,14 @@ The dimension which you do not want to change can also be kept as `0` which mean
 So with `shape=(2,0)`, we will obtain the same result as in the above example.
 
 )code" NNVM_ADD_FILELINE)
-.set_attr_parser(ParamParser<BroadcastToParam>)
+.add_argument("data", "Tensor", "Input data.")
 .add_arguments(BroadcastToParam::__FIELDS__())
-.set_num_inputs(1)
-.set_num_outputs(1)
+.set_attr_parser(ParamParser<BroadcastToParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<BroadcastToParam>)
 .set_attr<FInferShape>("FInferShape", BroadcastToInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.add_argument("data", "Tensor", "Input data.")
+.set_num_inputs(1)
+.set_num_outputs(1)
 .set_support_level(4);
 
 // binary broadcast op
diff --git a/nnvm/src/top/tensor/elemwise.cc b/nnvm/src/top/tensor/elemwise.cc
index dccd80abaaab..825b01b018e2 100644
--- a/nnvm/src/top/tensor/elemwise.cc
+++ b/nnvm/src/top/tensor/elemwise.cc
@@ -95,70 +95,60 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(copy)
 // unary scalar op
 DMLC_REGISTER_PARAMETER(ScalarParam);
 
-NNVM_REGISTER_ELEMWISE_UNARY_OP(__add_scalar__)
+#define NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(op)                        \
+  NNVM_REGISTER_ELEMWISE_UNARY_OP(op)                                   \
+  .add_arguments(ScalarParam::__FIELDS__())                             \
+  .set_attr_parser(ParamParser<ScalarParam>)                            \
+  .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<ScalarParam>)
+
+
+NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__add_scalar__)
 .describe(R"code(Tensor add scalar
 
 )code"  NNVM_ADD_FILELINE)
-.set_attr_parser(ParamParser<ScalarParam>)
-.add_arguments(ScalarParam::__FIELDS__())
 .set_support_level(3);
 
-NNVM_REGISTER_ELEMWISE_UNARY_OP(__sub_scalar__)
+NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__sub_scalar__)
 .describe(R"code(Tensor substract scalar
 
 )code"  NNVM_ADD_FILELINE)
-.set_attr_parser(ParamParser<ScalarParam>)
-.add_arguments(ScalarParam::__FIELDS__())
 .set_support_level(3);
 
-NNVM_REGISTER_ELEMWISE_UNARY_OP(__rsub_scalar__)
+NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__rsub_scalar__)
 .describe(R"code(scalar substract Tensor
 
 )code"  NNVM_ADD_FILELINE)
-.set_attr_parser(ParamParser<ScalarParam>)
-.add_arguments(ScalarParam::__FIELDS__())
 .set_support_level(3);
 
-NNVM_REGISTER_ELEMWISE_UNARY_OP(__mul_scalar__)
+NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__mul_scalar__)
 .describe(R"code(Tensor multiplies scalar
 
 )code"  NNVM_ADD_FILELINE)
-.set_attr_parser(ParamParser<ScalarParam>)
-.add_arguments(ScalarParam::__FIELDS__())
 .set_support_level(3);
 
-NNVM_REGISTER_ELEMWISE_UNARY_OP(__div_scalar__)
+NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__div_scalar__)
 .describe(R"code(Tensor divides scalar
 
 )code"  NNVM_ADD_FILELINE)
-.set_attr_parser(ParamParser<ScalarParam>)
-.add_arguments(ScalarParam::__FIELDS__())
 .set_support_level(3);
 
-NNVM_REGISTER_ELEMWISE_UNARY_OP(__rdiv_scalar__)
+NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__rdiv_scalar__)
 .describe(R"code(scalar divides Tensor
 
 )code"  NNVM_ADD_FILELINE)
-.set_attr_parser(ParamParser<ScalarParam>)
-.add_arguments(ScalarParam::__FIELDS__())
 .set_support_level(3);
 
-NNVM_REGISTER_ELEMWISE_UNARY_OP(__pow_scalar__)
+NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__pow_scalar__)
 .describe(R"code(Tensor power scalar
 
 )code"  NNVM_ADD_FILELINE)
-.set_attr_parser(ParamParser<ScalarParam>)
-.add_arguments(ScalarParam::__FIELDS__())
 .set_support_level(3);
 
-NNVM_REGISTER_ELEMWISE_UNARY_OP(__rpow_scalar__)
+NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__rpow_scalar__)
 .describe(R"code(scalar power Tensor
 
 )code"  NNVM_ADD_FILELINE)
-.set_attr_parser(ParamParser<ScalarParam>)
-.add_arguments(ScalarParam::__FIELDS__())
 .set_support_level(3);
 
-
 }  // namespace top
 }  // namespace nnvm
diff --git a/nnvm/src/top/tensor/reduce.cc b/nnvm/src/top/tensor/reduce.cc
index 58307095bcbb..4599394176a5 100644
--- a/nnvm/src/top/tensor/reduce.cc
+++ b/nnvm/src/top/tensor/reduce.cc
@@ -90,15 +90,17 @@ inline void AxesParamParser(nnvm::NodeAttrs* attrs) {
   attrs->parsed = std::move(param);
 }
 
-#define NNVM_REGISTER_REDUCE_OP(op)                             \
-  NNVM_REGISTER_OP(op)                                          \
-  .set_num_inputs(1)                                            \
-  .set_num_outputs(1)                                           \
-  .set_attr_parser(AxesParamParser<ReduceParam>)                \
-  .set_attr<FInferShape>("FInferShape", ReduceShape)            \
-  .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)       \
-  .add_argument("data", "Tensor", "The input")                  \
-  .add_arguments(ReduceParam::__FIELDS__())
+#define NNVM_REGISTER_REDUCE_OP(op)                                     \
+  NNVM_REGISTER_OP(op)                                                  \
+  .add_argument("data", "Tensor", "The input")                          \
+  .add_arguments(ReduceParam::__FIELDS__())                             \
+  .set_attr_parser(AxesParamParser<ReduceParam>)                        \
+  .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<ReduceParam>) \
+  .set_attr<FInferShape>("FInferShape", ReduceShape)                    \
+  .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)               \
+  .set_num_inputs(1)                                                    \
+  .set_num_outputs(1)                                                   \
+
 
 
 NNVM_REGISTER_REDUCE_OP(sum)
diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
index 18ecfd6f0b1b..c9eaf0c0ee6e 100644
--- a/nnvm/src/top/tensor/transform.cc
+++ b/nnvm/src/top/tensor/transform.cc
@@ -132,13 +132,14 @@ Example::
                              [ 5.,  5.,  8.,  8.]]
 
 )code" NNVM_ADD_FILELINE)
-.set_num_outputs(1)
-.set_num_inputs(kVarg)
-.set_attr_parser(ParamParser<ConcatenateParam>)
-.add_arguments(ConcatenateParam::__FIELDS__())
 .add_argument("data", "Tensor-or-Tensor[]", "List of arrays to concatenate")
+.add_arguments(ConcatenateParam::__FIELDS__())
+.set_attr_parser(ParamParser<ConcatenateParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<ConcatenateParam>)
 .set_attr<FInferShape>("FInferShape", ConcatenateInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
+.set_num_outputs(1)
+.set_num_inputs(kVarg)
 .set_support_level(1);
 
 
@@ -204,6 +205,7 @@ inline uint32_t SplitNumOutputs(const NodeAttrs& attrs) {
   }
 }
 
+// Intentionally not add ParamGetAttrDict for indices_or_sections.
 NNVM_REGISTER_OP(split)
 .describe(R"code(Splits an array along a particular axis into multiple sub-arrays.
 
@@ -211,13 +213,13 @@ NNVM_REGISTER_OP(split)
 along which to split the array.
 
 )code" NNVM_ADD_FILELINE)
-.set_num_inputs(1)
-.set_attr_parser(SplitParamParser)
-.set_num_outputs(SplitNumOutputs)
-.add_arguments(SplitParam::__FIELDS__())
 .add_argument("data", "Tensor", "List of arrays to concatenate")
+.add_arguments(SplitParam::__FIELDS__())
+.set_attr_parser(SplitParamParser)
 .set_attr<FInferShape>("FInferShape", SplitInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
+.set_num_inputs(1)
+.set_num_outputs(SplitNumOutputs)
 .set_support_level(1);
 
 // cast
@@ -237,8 +239,9 @@ NNVM_REGISTER_OP(cast)
 
 )code" NNVM_ADD_FILELINE)
 .add_argument("data", "Tensor", "Input data array")
-.set_attr_parser(ParamParser<CastParam>)
 .add_arguments(CastParam::__FIELDS__())
+.set_attr_parser(ParamParser<CastParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<CastParam>)
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", CastInferType)
 .set_num_inputs(1)
@@ -387,13 +390,14 @@ The significance of each is explained below:
   - input shape = (2,3,4), shape = (2,-4,-1,3,-2), output shape = (2,1,3,4)
 
 )code" NNVM_ADD_FILELINE)
-.set_num_inputs(1)
-.set_num_outputs(1)
-.set_attr_parser(ParamParser<ReshapeParam>)
+.add_argument("data", "Tensor", "Input data.")
 .add_arguments(ReshapeParam::__FIELDS__())
+.set_attr_parser(ParamParser<ReshapeParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<ReshapeParam>)
 .set_attr<FInferShape>("FInferShape", ReshapeInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.add_argument("data", "Tensor", "Input data.")
+.set_num_inputs(1)
+.set_num_outputs(1)
 .set_support_level(3);
 
 // tranpose
@@ -453,13 +457,14 @@ Examples::
                                 [[ 3.,  4.],
                                  [ 7.,  8.]]]
 )code" NNVM_ADD_FILELINE)
-.set_num_inputs(1)
-.set_num_outputs(1)
-.set_attr_parser(ParamParser<TransposeParam>)
+.add_argument("data", "Tensor", "Source input")
 .add_arguments(TransposeParam::__FIELDS__())
+.set_attr_parser(ParamParser<TransposeParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<TransposeParam>)
 .set_attr<nnvm::FInferShape>("FInferShape", TransposeShape)
 .set_attr<nnvm::FInferType>("FInferType", ElemwiseType<1, 1>)
-.add_argument("data", "Tensor", "Source input")
+.set_num_inputs(1)
+.set_num_outputs(1)
 .set_support_level(4);
 
 }  // namespace top
diff --git a/nnvm/tests/lint/pylintrc b/nnvm/tests/lint/pylintrc
new file mode 100644
index 000000000000..ef5a780f3d42
--- /dev/null
+++ b/nnvm/tests/lint/pylintrc
@@ -0,0 +1,406 @@
+[MASTER]
+
+# Specify a configuration file.
+#rcfile=
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Add files or directories to the blacklist. They should be base names, not
+# paths.
+ignore=CVS, _cy2, _cy3
+
+# Add files or directories matching the regex patterns to the blacklist. The
+# regex matches against base names, not paths.
+ignore-patterns=
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+# Use multiple processes to speed up Pylint.
+jobs=8
+
+# Allow loading of arbitrary C extensions. Extensions are imported into the
+# active Python interpreter and may run arbitrary code.
+unsafe-load-any-extension=no
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code
+extension-pkg-whitelist=numpy,opencv
+
+# Allow optimization of some AST trees. This will activate a peephole AST
+# optimizer, which will apply various small optimizations. For instance, it can
+# be used to obtain the result of joining multiple strings with the addition
+# operator. Joining a lot of strings can lead to a maximum recursion error in
+# Pylint and this flag can prevent that. It has one side effect, the resulting
+# AST will be different than the one from reality. This option is deprecated
+# and it will be removed in Pylint 2.0.
+optimize-ast=no
+
+
+[MESSAGES CONTROL]
+
+# Only show warnings with the listed confidence levels. Leave empty to show
+# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
+confidence=
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time (only on the command line, not in the configuration file where
+# it should appear only once). See also the "--disable" option for examples.
+enable=indexing-exception,old-raise-syntax
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifiers separated by comma (,) or put this
+# option multiple times (only on the command line, not in the configuration
+# file where it should appear only once).You can also use "--disable=all" to
+# disable everything first and then reenable specific checks. For example, if
+# you want to run only the similarities checker, you can use "--disable=all
+# --enable=similarities". If you want to run only the classes checker, but have
+# no Warning level messages displayed, use"--disable=all --enable=classes
+# --disable=W"
+disable=design,similarities,no-self-use,attribute-defined-outside-init,locally-disabled,star-args,pointless-except,bad-option-value,global-statement,fixme,suppressed-message,useless-suppression,locally-enabled,no-member,no-name-in-module,import-error,unsubscriptable-object,unbalanced-tuple-unpacking,undefined-variable,protected-access
+
+[REPORTS]
+
+# Set the output format. Available formats are text, parseable, colorized, msvs
+# (visual studio) and html. You can also give a reporter class, eg
+# mypackage.mymodule.MyReporterClass.
+output-format=text
+
+# Put messages in a separate file for each module / package specified on the
+# command line instead of printing them on stdout. Reports (if any) will be
+# written in a file name "pylint_global.[txt|html]". This option is deprecated
+# and it will be removed in Pylint 2.0.
+files-output=no
+
+# Tells whether to display a full report or only the messages
+reports=no
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note). You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Template used to display messages. This is a python new-style format string
+# used to format the message information. See doc for all details
+#msg-template=
+
+
+[FORMAT]
+
+# Maximum number of characters on a single line.
+max-line-length=100
+
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=^\s*(# )?<?https?://\S+>?$
+
+# Allow the body of an if to be on the same line as the test if there is no
+# else.
+single-line-if-stmt=no
+
+# List of optional constructs for which whitespace checking is disabled. `dict-
+# separator` is used to allow tabulation in dicts, etc.: {1  : 1,\n222: 2}.
+# `trailing-comma` allows a space between comma and closing bracket: (a, ).
+# `empty-line` allows space-only lines.
+no-space-check=trailing-comma,dict-separator
+
+# Maximum number of lines in a module
+max-module-lines=1000
+
+# String used as indentation unit. This is usually "    " (4 spaces) or "\t" (1
+# tab).
+indent-string='    '
+
+# Number of spaces of indent required inside a hanging  or continued line.
+indent-after-paren=4
+
+# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
+expected-line-ending-format=
+
+
+[SPELLING]
+
+# Spelling dictionary name. Available dictionaries: none. To make it working
+# install python-enchant package.
+spelling-dict=
+
+# List of comma separated words that should not be checked.
+spelling-ignore-words=
+
+# A path to a file that contains private dictionary; one word per line.
+spelling-private-dict-file=
+
+# Tells whether to store unknown words to indicated private dictionary in
+# --spelling-private-dict-file option instead of raising a message.
+spelling-store-unknown-words=no
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,XXX,TODO
+
+
+[TYPECHECK]
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# List of module names for which member attributes should not be checked
+# (useful for modules/projects where namespaces are manipulated during runtime
+# and thus existing member attributes cannot be deduced by static analysis. It
+# supports qualified module names, as well as Unix pattern matching.
+ignored-modules=
+
+# List of class names for which member attributes should not be checked (useful
+# for classes with dynamically set attributes). This supports the use of
+# qualified names.
+ignored-classes=optparse.Values,thread._local,_thread._local
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E1101 when accessed. Python regular
+# expressions are accepted.
+generated-members=
+
+# List of decorators that produce context managers, such as
+# contextlib.contextmanager. Add to this list to register other decorators that
+# produce valid context managers.
+contextmanager-decorators=contextlib.contextmanager
+
+
+[LOGGING]
+
+# Logging modules to check that the string format arguments are in logging
+# function parameter format
+logging-modules=logging
+
+
+[SIMILARITIES]
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+# Ignore imports when computing similarities.
+ignore-imports=no
+
+
+[VARIABLES]
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# A regular expression matching the name of dummy variables (i.e. expectedly
+# not used).
+dummy-variables-rgx=(_+[a-zA-Z0-9]*?$)|dummy
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+# List of strings which can identify a callback function by name. A callback
+# name must start or end with one of those strings.
+callbacks=cb_,_cb
+
+# List of qualified module names which can have objects that can redefine
+# builtins.
+redefining-builtins-modules=six.moves,future.builtins
+
+
+[BASIC]
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=i,j,_,a,b,op,x,y,wd,lr,kv,k,v,s,p,h,c,m,n,X,t,g,f
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=
+
+# Colon-delimited sets of names that determine each other's naming style when
+# the name regexes allow several styles.
+name-group=
+
+# Include a hint for the correct naming format with invalid-name
+include-naming-hint=no
+
+# List of decorators that produce properties, such as abc.abstractproperty. Add
+# to this list to register other decorators that produce valid properties.
+property-classes=abc.abstractproperty
+
+# Regular expression matching correct module names
+module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Naming hint for module names
+module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Regular expression matching correct constant names
+const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Naming hint for constant names
+const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Regular expression matching correct inline iteration names
+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+
+# Naming hint for inline iteration names
+inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
+
+# Regular expression matching correct method names
+method-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for method names
+method-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct class attribute names
+class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
+
+# Naming hint for class attribute names
+class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
+
+# Regular expression matching correct argument names
+argument-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for argument names
+argument-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct attribute names
+attr-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for attribute names
+attr-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct variable names
+variable-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for variable names
+variable-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct function names
+function-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for function names
+function-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct class names
+class-rgx=[A-Z_][a-zA-Z0-9]+$
+
+# Naming hint for class names
+class-name-hint=[A-Z_][a-zA-Z0-9]+$
+
+# Regular expression which should only match function or class names that do
+# not require a docstring.
+no-docstring-rgx=^_
+
+# Minimum line length for functions/classes that require docstrings, shorter
+# ones are exempt.
+docstring-min-length=10
+
+
+[ELIF]
+
+# Maximum number of nested blocks for function / method body
+max-nested-blocks=5
+
+
+[CLASSES]
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,__new__,setUp
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+# List of valid names for the first argument in a metaclass class method.
+valid-metaclass-classmethod-first-arg=mcs
+
+# List of member names, which should be excluded from the protected access
+# warning.
+exclude-protected=_asdict,_fields,_replace,_source,_make
+
+
+[IMPORTS]
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=optparse
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled)
+import-graph=
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled)
+int-import-graph=
+
+# Force import order to recognize a module as part of the standard
+# compatibility libraries.
+known-standard-library=
+
+# Force import order to recognize a module as part of a third party library.
+known-third-party=enchant
+
+# Analyse import fallback blocks. This can be used to support both Python 2 and
+# 3 compatible code, which means that the block might have code that exists
+# only in one or another interpreter, leading to false positives when analysed.
+analyse-fallback-blocks=no
+
+
+[DESIGN]
+
+# Maximum number of arguments for function / method
+max-args=5
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore
+ignored-argument-names=_.*
+
+# Maximum number of locals for function / method body
+max-locals=15
+
+# Maximum number of return / yield for function / method body
+max-returns=6
+
+# Maximum number of branch for function / method body
+max-branches=12
+
+# Maximum number of statements in function / method body
+max-statements=50
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=0
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+# Maximum number of boolean expressions in a if statement
+max-bool-expr=5
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "Exception"
+overgeneral-exceptions=Exception
diff --git a/nnvm/tests/python/compiler/test_build.py b/nnvm/tests/python/compiler/test_build.py
new file mode 100644
index 000000000000..877d1602d6af
--- /dev/null
+++ b/nnvm/tests/python/compiler/test_build.py
@@ -0,0 +1,34 @@
+import numpy as np
+
+import tvm
+import nnvm.symbol as sym
+import nnvm.compiler
+import nnvm.runtime
+
+def test_compile():
+    x = sym.Variable("x")
+    y = sym.Variable("y")
+    z = sym.exp(y + x)
+    shape = (10, 128)
+    dtype = tvm.float32
+    shape_dict = {"x": shape, "y": shape}
+
+    graph, lib = nnvm.compiler.build(z, "llvm", shape_dict)
+    m = nnvm.runtime.create(graph, lib, tvm.cpu(0))
+    # get member functions
+    set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
+    na = tvm.nd.array(np.ones(shape).astype(dtype))
+    nb = tvm.nd.array(np.ones(shape).astype(dtype))
+    # set inputs
+    set_input("x", na)
+    set_input("y", nb)
+    # execute
+    run()
+    # get outputs
+    out = tvm.nd.empty(shape, dtype)
+    get_output(0, out)
+    np.testing.assert_allclose(
+        out.asnumpy(), np.exp(na.asnumpy() + nb.asnumpy()))
+
+if __name__ == "__main__":
+    test_compile()
diff --git a/nnvm/tests/python/compiler/test_rpc_exec.py b/nnvm/tests/python/compiler/test_rpc_exec.py
new file mode 100644
index 000000000000..89f67d5945e6
--- /dev/null
+++ b/nnvm/tests/python/compiler/test_rpc_exec.py
@@ -0,0 +1,50 @@
+from tvm.contrib import util, rpc
+import tvm
+import nnvm.symbol as sym
+import nnvm.compiler
+import nnvm.runtime
+import numpy as np
+
+def test_rpc_executor():
+    host = "localhost"
+    port = 9091
+    server = rpc.Server(host, port)
+
+    x = sym.Variable("x")
+    y = sym.Variable("y")
+    z = sym.exp(y + x)
+    shape = (10, 128)
+    dtype = tvm.float32
+    shape_dict = {"x": shape, "y": shape}
+    tmp = util.tempdir()
+    lib_name  = tmp.relpath("net.o")
+
+    graph, lib = nnvm.compiler.build(z, "llvm", shape_dict)
+    # save module
+    lib.save(lib_name)
+    remote = rpc.connect(host, port)
+    remote.upload(lib_name)
+    ctx = remote.cpu(0)
+    # load remote
+    rlib = remote.load_module("net.o")
+
+    # Create remotemodule
+    m = nnvm.runtime.create(graph, rlib, remote.cpu(0))
+    # get member functions
+    set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
+    na = tvm.nd.array(np.ones(shape).astype(dtype), ctx)
+    nb = tvm.nd.array(np.ones(shape).astype(dtype), ctx)
+    # set inputs
+    set_input("x", na)
+    set_input("y", nb)
+    # execute
+    run()
+    # get outputs
+    out = tvm.nd.empty(shape, dtype, ctx)
+    get_output(0, out)
+    np.testing.assert_allclose(
+        out.asnumpy(), np.exp(na.asnumpy() + nb.asnumpy()))
+    server.terminate()
+
+if __name__ == "__main__":
+    test_rpc_executor()
diff --git a/nnvm/tests/python/compiler/test_top_level2.py b/nnvm/tests/python/compiler/test_top_level2.py
new file mode 100644
index 000000000000..9f3e80cb3701
--- /dev/null
+++ b/nnvm/tests/python/compiler/test_top_level2.py
@@ -0,0 +1,39 @@
+import numpy as np
+
+import tvm
+import topi
+import nnvm.symbol as sym
+import nnvm.compiler
+import nnvm.runtime
+
+def test_conv2d():
+    x = sym.Variable("x")
+    y = sym.conv2d(x, channels=10, kernel_size=(3, 3),
+                   name="y", use_bias=False, padding=(1,1))
+    dtype = "float32"
+    dshape = (1, 3, 18, 18)
+    kshape = (10, 3, 3, 3)
+    oshape = (1, 10, 18, 18)
+    shape_dict = {"x": dshape}
+    graph, lib = nnvm.compiler.build(y, "llvm", shape_dict)
+    m = nnvm.runtime.create(graph, lib, tvm.cpu(0))
+    # get member functions
+    set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
+    # execute
+    run()
+
+    data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+    kernel = tvm.nd.array(np.random.uniform(size=kshape).astype(dtype))
+    set_input("x", data)
+    set_input("y_weight", kernel)
+    # execute
+    run()
+    # get outputs
+    out = tvm.nd.empty(oshape, dtype)
+    get_output(0, out)
+    c_np = topi.testing.conv2d_nchw_python(
+        data.asnumpy(), kernel.asnumpy(), 1, 1)
+    np.testing.assert_allclose(out.asnumpy(), c_np, rtol=1e-5)
+
+if __name__ == "__main__":
+    test_conv2d()
diff --git a/nnvm/tests/python/unittest/test_infer_shape.py b/nnvm/tests/python/unittest/test_infer_shape.py
index c7d79f3bcdf6..77f14573b74e 100644
--- a/nnvm/tests/python/unittest/test_infer_shape.py
+++ b/nnvm/tests/python/unittest/test_infer_shape.py
@@ -6,13 +6,11 @@ def infer_shape(sym):
     g = graph.create(sym)
     g._set_json_attr("shape_attr_key", "shape")
     g = g.apply("InferShape")
-    jgraph = json.loads(g.apply("SaveJSON").json_attr("json"))
-    jnodes = jgraph["nodes"]
-    jnode_row_ptr = jgraph["node_row_ptr"]
     sdict = {}
     vshape = g.json_attr("shape")
-    for i, n in enumerate(jnodes):
-        begin, end = jnode_row_ptr[i], jnode_row_ptr[i + 1]
+    entry_ptr = g.index.entry_ptr
+    for i, n in enumerate(g.index.nodes):
+        begin, end = entry_ptr[i], entry_ptr[i + 1]
         sdict[n["name"]] = vshape[begin:end]
     return sdict
 

From 31c2b24d57f986f4cd5aeb0a0009e31740cc016b Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Sun, 17 Sep 2017 23:11:53 -0700
Subject: [PATCH 471/949] [DLL] Use local dll, not de-allocate function in
 shutdown

---
 nnvm/python/nnvm/_base.py            |  2 +-
 nnvm/src/compiler/packed_func_ext.cc | 10 ++++++----
 2 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/nnvm/python/nnvm/_base.py b/nnvm/python/nnvm/_base.py
index 80bbe6bd293e..225b0844c4f9 100644
--- a/nnvm/python/nnvm/_base.py
+++ b/nnvm/python/nnvm/_base.py
@@ -35,7 +35,7 @@ class NNVMError(Exception):
 def _load_lib():
     """Load libary by searching possible path."""
     lib_path = libinfo.find_lib_path()
-    lib = ctypes.CDLL(lib_path[0], ctypes.RTLD_GLOBAL)
+    lib = ctypes.CDLL(lib_path[0], ctypes.RTLD_LOCAL)
     # DMatrix functions
     lib.NNGetLastError.restype = ctypes.c_char_p
     return lib
diff --git a/nnvm/src/compiler/packed_func_ext.cc b/nnvm/src/compiler/packed_func_ext.cc
index a66d21807913..6f191dccfc4a 100644
--- a/nnvm/src/compiler/packed_func_ext.cc
+++ b/nnvm/src/compiler/packed_func_ext.cc
@@ -71,11 +71,12 @@ GetAttrDict(const NodeAttrs& attrs) {
 
 TVM_REGISTER_GLOBAL("nnvm._register_compute")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
-    PackedFunc f = args[1];
+    // Intentionally copy and not de-allocate it, to avoid free pyobject during shutdown
+    PackedFunc* f = new PackedFunc(args[1].operator PackedFunc());
     Op& op = ::dmlc::Registry<nnvm::Op>::Get()->__REGISTER_OR_GET__(args[0]);
     auto fcompute = [f](const NodeAttrs& attrs, const Array<Tensor>& inputs)
         -> Array<Tensor> {
-      TVMRetValue ret = f(GetAttrDict(attrs), inputs);
+      TVMRetValue ret = (*f)(GetAttrDict(attrs), inputs);
       if ((*ret.ptr<std::shared_ptr<tvm::Node> >())->derived_from<tvm::TensorNode>()) {
         return {ret.operator Tensor()};
       } else {
@@ -87,12 +88,13 @@ TVM_REGISTER_GLOBAL("nnvm._register_compute")
 
 TVM_REGISTER_GLOBAL("nnvm._register_schedule")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
-    PackedFunc f = args[1];
+        // Intentionally copy and not de-allocate it, to avoid free pyobject during shutdown
+    PackedFunc* f = new PackedFunc(args[1].operator PackedFunc());
     Op& op = ::dmlc::Registry<nnvm::Op>::Get()->__REGISTER_OR_GET__(args[0]);
     auto fschedule = [f](const NodeAttrs& attrs,
                          const Array<Tensor>& outs,
                          const std::string& target) {
-      return f(GetAttrDict(attrs), outs, target).operator Schedule();
+      return (*f)(GetAttrDict(attrs), outs, target).operator Schedule();
     };
     op.set_attr<FTVMSchedule>("FTVMSchedule", fschedule, args[2]);
   });

From 3e428a8b2f37b65085daae2a2f9965774dc3cfc3 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 19 Sep 2017 09:30:35 -0700
Subject: [PATCH 472/949] [PASS] PrecomputePrune, add testcase (#14)

* [PASS] PrecomputePrune, add testcase

* update comment
---
 nnvm/Makefile                                 |  3 +-
 nnvm/python/nnvm/compiler/__init__.py         |  2 +-
 nnvm/python/nnvm/compiler/build_module.py     | 85 +++++++++++++++++--
 nnvm/python/nnvm/compiler/graph_attr.py       | 64 ++++++++++----
 nnvm/python/nnvm/compiler/graph_pass.py       | 55 ++++++++++++
 nnvm/python/nnvm/graph.py                     |  6 ++
 nnvm/python/nnvm/top/attr_dict.py             | 15 ++++
 nnvm/python/nnvm/top/tensor.py                | 17 ++++
 nnvm/src/compiler/packed_func_ext.cc          | 14 +++
 nnvm/src/compiler/pass/graph_fuse.cc          |  8 --
 nnvm/src/compiler/pass/layout_transform.cc    |  2 +-
 .../{prune_graph.cc => precompute_prune.cc}   | 37 +++++---
 nnvm/tests/python/compiler/test_build.py      | 36 +++++++-
 nnvm/tests/python/compiler/test_graph_pass.py | 18 ++++
 14 files changed, 312 insertions(+), 50 deletions(-)
 rename nnvm/src/compiler/pass/{prune_graph.cc => precompute_prune.cc} (61%)
 create mode 100644 nnvm/tests/python/compiler/test_graph_pass.py

diff --git a/nnvm/Makefile b/nnvm/Makefile
index b7322c181feb..9b7e78953d1c 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -30,7 +30,7 @@ ifneq ($(ADD_CFLAGS), NONE)
 endif
 
 ifneq ($(ADD_LDFLAGS), NONE)
-	LFFLAGS += $(ADD_LDFLAGS)
+	LDFLAGS += $(ADD_LDFLAGS)
 endif
 
 # plugin
@@ -46,6 +46,7 @@ ifeq ($(UNAME_S), Darwin)
 	SHARED_LIBRARY_SUFFIX := dylib
 	WHOLE_ARCH= -all_load
 	NO_WHOLE_ARCH= -noall_load
+	LDFLAGS += -undefined dynamic_lookup
 else
 	SHARED_LIBRARY_SUFFIX := so
 	WHOLE_ARCH= --whole-archive
diff --git a/nnvm/python/nnvm/compiler/__init__.py b/nnvm/python/nnvm/compiler/__init__.py
index 093058f1f112..993d0ed7c8a2 100644
--- a/nnvm/python/nnvm/compiler/__init__.py
+++ b/nnvm/python/nnvm/compiler/__init__.py
@@ -4,7 +4,7 @@
 import tvm
 
 from . import build_module
-from . build_module import build
+from . build_module import build, precompute_prune, _run_graph
 
 from .. import symbol as _symbol
 from .. import graph as _graph
diff --git a/nnvm/python/nnvm/compiler/build_module.py b/nnvm/python/nnvm/compiler/build_module.py
index e7755a4d495d..3b053e2fd806 100644
--- a/nnvm/python/nnvm/compiler/build_module.py
+++ b/nnvm/python/nnvm/compiler/build_module.py
@@ -3,8 +3,9 @@
 from __future__ import absolute_import as _abs
 
 import tvm
-from . import graph_attr
+from . import graph_attr, graph_pass
 from .. import graph as _graph
+from .. import runtime
 
 @tvm.register_func("nnvm.compiler.lower")
 def _lower(sch, inputs, func_name):
@@ -18,9 +19,6 @@ def _build(funcs, target):
     return tvm.build(funcs, target=target)
 
 
-_move_module = tvm.get_global_func("nnvm.compiler._move_module")
-
-
 def optimize(graph):
     """Perform graph optimization
 
@@ -70,10 +68,83 @@ def build(graph, target, shape, dtype="float32"):
         raise TypeError("require shape to be dict")
 
     graph = graph if isinstance(graph, _graph.Graph) else _graph.create(graph)
-    graph = graph_attr.set_shape(graph, shape)
-    graph = graph_attr.set_dtype(graph, dtype)
+    graph = graph_attr.set_shape_inputs(graph, shape)
+    graph = graph_attr.set_dtype_inputs(graph, dtype)
     graph._set_json_attr("target", target, "str")
     graph = graph.apply("InferShape").apply("InferType")
     graph = graph.apply("GraphFusePartition").apply("GraphFuse")
-    libmod = _move_module(graph)
+    libmod = graph_attr._move_out_module(graph, "module")
     return graph, libmod
+
+
+def _run_graph(graph, params):
+    """Helper utility to build and run and get outputs, only use cpu mode.
+
+    Parameters
+    ----------
+    graph : Graph
+        The graph to be executed.
+
+    params: dict of str to ndarray
+        The parameter dictionary.
+
+    Returns
+    -------
+    out_dict: dict of str to tvm.NDArray
+        The output dictionaries.
+    """
+    graph = graph if isinstance(graph, _graph.Graph) else _graph.create(graph)
+    shape = {k : v.shape for k, v in params.items()}
+    dtype = {k : v.dtype for k, v in params.items()}
+    target = "llvm"
+    ctx = tvm.cpu(0)
+    _, oshape = graph_pass.infer_shape(graph, **shape)
+    _, odtype = graph_pass.infer_dtype(graph, **dtype)
+    graph, libmod = build(graph, target, shape, dtype)
+    m = runtime.create(graph, libmod, ctx)
+    set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
+    for k, v in params.items():
+        set_input(k, tvm.nd.array(v))
+    run()
+    out_data = []
+    for i, kv in enumerate(zip(oshape, odtype)):
+        shape, dtype = kv
+        arr = tvm.nd.empty(shape, dtype, ctx)
+        get_output(i, arr)
+        out_data.append(arr)
+    return out_data
+
+
+def precompute_prune(graph, params):
+    """Precompute the part of graph that can be pre-computed.
+
+    This will create a new graph that only contains the ops
+    that need to be computed depending on input as well as
+    updated version of param dict that pre-computes some of
+    intermediate results.
+
+    Parameters
+    ----------
+    graph : Graph
+        The input graph
+
+    params : dict of str -> tvm.NDArray
+        The parameter dictionary of the graph
+
+    Returns
+    -------
+    pruned_graph : Graph
+        The pruned graph
+
+    new_params : dict of str-> tvm.NDArray
+        The updated dictionary of parameters.
+    """
+    graph = graph if isinstance(graph, _graph.Graph) else _graph.create(graph)
+    graph._set_json_attr("param_name_list", list(params.keys()), "list_str")
+    graph = graph.apply("PrecomputePrune")
+    pre_graph = graph_attr._move_out_graph(graph, "precompute_graph")
+    if not pre_graph.symbol.list_output_names():
+        return graph, params
+    out_names = pre_graph.json_attr("output_names")
+    out_arrs = _run_graph(pre_graph, params)
+    return graph, dict(zip(out_names, out_arrs))
diff --git a/nnvm/python/nnvm/compiler/graph_attr.py b/nnvm/python/nnvm/compiler/graph_attr.py
index c30fd0356929..0b5d19898221 100644
--- a/nnvm/python/nnvm/compiler/graph_attr.py
+++ b/nnvm/python/nnvm/compiler/graph_attr.py
@@ -1,8 +1,11 @@
+# pylint: disable=invalid-name
 """Utilities to access graph attributes"""
 from __future__ import absolute_import as _abs
 
-def set_shape(g, shape):
-    """Set the shape of graph nodes in the graph attribute.
+import tvm
+
+def set_shape_inputs(g, shape):
+    """Set the shape of input graph nodes in the graph attribute.
 
     Parameters
     ----------
@@ -17,20 +20,24 @@ def set_shape(g, shape):
     g : Graph
         The updated graph with updated shape.
     """
-    index = g.index
-    list_shape = [[]] * index.num_node_entries
-    for k, v in shape.items():
-        list_shape[index.entry_id(k)] = v
-    g._set_json_attr("shape", list_shape, 'list_shape')
+    list_shape = [
+        shape.get(name, ()) for name in g.index.input_names]
+    g._set_json_attr("shape_inputs", list_shape, 'list_shape')
     return g
 
 
-DTYPE_DICT = {
+DTYPE_TO_TCODE = {
+    "default": -1,
     "float32": 0
 }
 
-def set_dtype(g, dtype):
-    """Set the dtype of graph nodes
+TCODE_TO_DTYPE = {
+    -1: None,
+    0: "float32"
+}
+
+def set_dtype_inputs(g, dtype):
+    """Set the dtype inputs of graph nodes
 
     Parameters
     ----------
@@ -45,12 +52,37 @@ def set_dtype(g, dtype):
     g : Graph
         The updated graph with updated dtype.
     """
-    index = g.index
     if isinstance(dtype, dict):
-        list_dtype = [-1] * index.num_node_entries
-        for k, v in dtype.items():
-            list_dtype[index.entry_id(k)] = DTYPE_DICT[v]
+        list_dtype = [
+            DTYPE_TO_TCODE[dtype.get(name, "default")]
+            for name in g.index.input_names]
     else:
-        list_dtype = [DTYPE_DICT[dtype]] * index.num_node_entries
-    g._set_json_attr("dtype", list_dtype, "list_int")
+        list_dtype = [DTYPE_TO_TCODE[dtype]] * len(g.index.input_names)
+    g._set_json_attr("dtype_inputs", list_dtype, "list_int")
+    return g
+
+
+def set_layout_inputs(g, layout):
+    """Set the layout inputs of graph nodes
+
+    Parameters
+    ----------
+    g : Graph
+        The input graph
+
+    layout : dict of str to str or str
+        The input layout
+
+    Returns
+    -------
+    g : Graph
+        The updated graph with updated dtype.
+    """
+    list_shape = [
+        layout.get(name, "default") for name in g.index.input_names]
+    g._set_json_attr("layout_inputs", list_shape, 'list_str')
     return g
+
+
+_move_out_module = tvm.get_global_func("nnvm.graph_attr._move_module")
+_move_out_graph = tvm.get_global_func("nnvm.graph_attr._move_graph")
diff --git a/nnvm/python/nnvm/compiler/graph_pass.py b/nnvm/python/nnvm/compiler/graph_pass.py
index 1122eb0170a0..7d25a055777d 100644
--- a/nnvm/python/nnvm/compiler/graph_pass.py
+++ b/nnvm/python/nnvm/compiler/graph_pass.py
@@ -1,3 +1,4 @@
+# pylint: disable=invalid-name
 """Namespace of graph pass.
 
 Principle:
@@ -5,3 +6,57 @@
 - Composable API: break graph transformation pass as segments of small transformations.
 """
 from __future__ import absolute_import as _abs
+
+from . import graph_attr
+
+
+def infer_shape(graph, **shape):
+    """Infer the shape given the shape of inputs.
+
+    Parameters
+    ----------
+    graph : Graph
+        The graph to perform shape inference from
+
+    Returns
+    -------
+    in_shape : list of tuple
+         Shape of inputs
+
+    out_shape: list of tuple
+         Shape of outputs
+    """
+    graph = graph_attr.set_shape_inputs(graph, shape)
+    graph = graph.apply("InferShape")
+    shape = graph.json_attr("shape")
+    index = graph.index
+    input_shape = [shape[index.entry_id(x)] for x in index.input_names]
+    output_shape = [shape[index.entry_id(x)] for x in index.output_entries]
+    return input_shape, output_shape
+
+
+def infer_dtype(graph, **dtype):
+    """Infer the type given the typeS of inputs.
+
+    Parameters
+    ----------
+    graph : Graph
+        The graph to perform type inference from
+
+    Returns
+    -------
+    in_dtype : list of tuple
+         Dtype of inputs
+
+    out_dtype: list of tuple
+         Dtype of outputs
+    """
+    graph = graph_attr.set_dtype_inputs(graph, dtype)
+    graph = graph.apply("InferType")
+    dtype = graph.json_attr("dtype")
+    index = graph.index
+    input_dtype = [graph_attr.TCODE_TO_DTYPE[dtype[index.entry_id(x)]]
+                   for x in index.input_names]
+    output_dtype = [graph_attr.TCODE_TO_DTYPE[dtype[index.entry_id(x)]]
+                    for x in index.output_entries]
+    return input_dtype, output_dtype
diff --git a/nnvm/python/nnvm/graph.py b/nnvm/python/nnvm/graph.py
index e7652af6cb33..309603d06264 100644
--- a/nnvm/python/nnvm/graph.py
+++ b/nnvm/python/nnvm/graph.py
@@ -24,6 +24,8 @@ def __init__(self, graph):
         self.nodes = jgraph["nodes"]
         self.entry_ptr = jgraph["node_row_ptr"]
         self._name2nodeid = {n["name"]: i for i, n in enumerate(self.nodes)}
+        self.input_names = graph.symbol.list_input_names()
+        self.output_entries = jgraph["heads"]
 
     @property
     def num_nodes(self):
@@ -66,6 +68,10 @@ def entry_id(self, key, value_index=0):
         index : int
             The entry index
         """
+        if isinstance(key, (list, tuple)):
+            if len(key) != 3:
+                raise ValueError("Expect entry index to be tuple of 3 elems")
+            key, value_index, _ = key
         idx = self.node_id(key) if isinstance(key, str) else key
         assert value_index < self.entry_ptr[idx + 1]
         return self.entry_ptr[idx] + value_index
diff --git a/nnvm/python/nnvm/top/attr_dict.py b/nnvm/python/nnvm/top/attr_dict.py
index fdf14b30a90b..1265d4cf7441 100644
--- a/nnvm/python/nnvm/top/attr_dict.py
+++ b/nnvm/python/nnvm/top/attr_dict.py
@@ -68,6 +68,21 @@ def get_int(self, key):
         """
         return int(self[key])
 
+    def get_float(self, key):
+        """Get float from attr dict
+
+        Parameters
+        ----------
+        key : str
+            The attr key
+
+        Returns
+        -------
+        value : float
+            The result value
+        """
+        return float(self[key])
+
     def get_bool(self, key):
         """Get bool from attr dict
 
diff --git a/nnvm/python/nnvm/top/tensor.py b/nnvm/python/nnvm/top/tensor.py
index 7b3883fa4edd..a316bf11102f 100644
--- a/nnvm/python/nnvm/top/tensor.py
+++ b/nnvm/python/nnvm/top/tensor.py
@@ -17,6 +17,17 @@ def _schedule_broadcast(_, outs, target):
     tvm.schedule.AutoInlineInjective(s)
     return s
 
+def _compute_binary_scalar(f):
+    """auxiliary function"""
+    @tvm.tag_scope("ewise")
+    def _compute(attrs, x):
+        x = x[0]
+        scalar = attrs.get_float("scalar")
+        scalar = tvm.const(scalar, x.dtype)
+        return tvm.compute(x.shape, lambda *i: f(x(*i), scalar))
+    return _compute
+
+
 _fschedule_broadcast = tvm.convert(_schedule_broadcast)
 
 # exp
@@ -25,6 +36,12 @@ def _schedule_broadcast(_, outs, target):
 reg.register_pattern("exp", OpPattern.ELEM_WISE)
 reg.register_schedule("exp", _fschedule_broadcast)
 
+# add scalar
+reg.register_compute("__add_scalar__",
+                     _compute_binary_scalar(lambda x, y: x + y))
+reg.register_pattern("__add_scalar__", OpPattern.ELEM_WISE)
+reg.register_schedule("__add_scalar__", _fschedule_broadcast)
+
 # broadcast_add
 reg.register_compute("broadcast_add",
                      lambda _, x: topi.broadcast_add(x[0], x[1]))
diff --git a/nnvm/src/compiler/packed_func_ext.cc b/nnvm/src/compiler/packed_func_ext.cc
index 6f191dccfc4a..54d0f627c7a5 100644
--- a/nnvm/src/compiler/packed_func_ext.cc
+++ b/nnvm/src/compiler/packed_func_ext.cc
@@ -104,5 +104,19 @@ TVM_REGISTER_GLOBAL("nnvm._register_pattern")
     Op& op = ::dmlc::Registry<nnvm::Op>::Get()->__REGISTER_OR_GET__(args[0]);
     op.set_attr<TOpPattern>("TOpPattern", args[1].operator int(), args[2]);
   });
+
+TVM_REGISTER_GLOBAL("nnvm.graph_attr._move_module")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+    const nnvm::Graph& g = args[0].AsExtension<Graph>();
+    *rv = const_cast<nnvm::Graph*>(&g)->
+        MoveCopyAttr<tvm::runtime::Module>(args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("nnvm.graph_attr._move_graph")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+    const nnvm::Graph& g = args[0].AsExtension<Graph>();
+    *rv = const_cast<nnvm::Graph*>(&g)->
+        MoveCopyAttr<nnvm::Graph>(args[1]);
+  });
 }  // namespace compiler
 }  // namespace nnvm
diff --git a/nnvm/src/compiler/pass/graph_fuse.cc b/nnvm/src/compiler/pass/graph_fuse.cc
index 23cd6343d236..0880a90b9dad 100644
--- a/nnvm/src/compiler/pass/graph_fuse.cc
+++ b/nnvm/src/compiler/pass/graph_fuse.cc
@@ -381,13 +381,5 @@ nnvm::Graph GraphFuse(nnvm::Graph g) {
 
 NNVM_REGISTER_PASS(GraphFuse)
 .set_body(GraphFuse);
-
-
-TVM_REGISTER_GLOBAL("nnvm.compiler._move_module")
-.set_body([](TVMArgs args, TVMRetValue *rv) {
-    const nnvm::Graph& g = args[0].AsExtension<Graph>();
-    *rv = const_cast<nnvm::Graph*>(&g)->
-        MoveCopyAttr<tvm::runtime::Module>("module");
-  });
 }  // namespace compiler
 }  // namespace nnvm
diff --git a/nnvm/src/compiler/pass/layout_transform.cc b/nnvm/src/compiler/pass/layout_transform.cc
index 2834654fd017..2bce0fad54c9 100644
--- a/nnvm/src/compiler/pass/layout_transform.cc
+++ b/nnvm/src/compiler/pass/layout_transform.cc
@@ -44,7 +44,7 @@ nnvm::Graph LayoutTransform(nnvm::Graph src) {
 
   const ShapeVector& shape_vec = src.GetAttr<ShapeVector>("shape");
   const std::vector<TLayoutInfo>& input_layouts =
-    src.GetAttr<std::vector<TLayoutInfo> >("layout");
+      src.GetAttr<std::vector<TLayoutInfo> >("layout_inputs");
 
   const IndexedGraph& idx = src.indexed_graph();
   std::vector<TLayoutInfo> produce_vec(idx.num_node_entries(), GetDefaultLayout());
diff --git a/nnvm/src/compiler/pass/prune_graph.cc b/nnvm/src/compiler/pass/precompute_prune.cc
similarity index 61%
rename from nnvm/src/compiler/pass/prune_graph.cc
rename to nnvm/src/compiler/pass/precompute_prune.cc
index 209c1ed36dee..a0159757c398 100644
--- a/nnvm/src/compiler/pass/prune_graph.cc
+++ b/nnvm/src/compiler/pass/precompute_prune.cc
@@ -1,10 +1,10 @@
 /*!
  *  Copyright (c) 2017 by Contributors
- * \file prune_graph.cc
- * \brief Prune the graph to do constant folding.
+ * \file precompute_prune.cc
+ * \brief Split the graph into a pre-compute graph and a execution graph.
  *
- *  This pass breaks the graph into pre-compute graph
- *  and the execution graph.
+ *  The pre-compute graph outputs parameters that can be taken
+ *  by execution graph during execution phase.
  */
 #include <nnvm/graph.h>
 #include <nnvm/op_attr_types.h>
@@ -16,11 +16,15 @@
 namespace nnvm {
 namespace compiler {
 
-nnvm::Graph PruneGraph(nnvm::Graph src) {
-  const auto& params = src.GetAttr<std::unordered_set<std::string> >("params");
+nnvm::Graph PrecomputePrune(nnvm::Graph src) {
+  const auto& plist
+      = src.GetAttr<std::vector<std::string> >("param_name_list");
+  std::unordered_set<std::string> params(plist.begin(), plist.end());
 
   std::unordered_set<nnvm::Node*> pruned;
   nnvm::NodeEntryMap<nnvm::NodePtr> entry_var;
+  std::unordered_set<std::string> unique_name;
+
   DFSVisit(src.outputs, [&](const nnvm::NodePtr& n) {
     bool can_be_pruned = true;
     if (n->is_variable()) {
@@ -45,7 +49,12 @@ nnvm::Graph PruneGraph(nnvm::Graph src) {
             nnvm::NodePtr var = nnvm::Node::Create();
             var->attrs.name = e.node->attrs.name + "_output" + std::to_string(e.index);
             entry_var.emplace(e, var);
+            CHECK(!unique_name.count(var->attrs.name));
+            unique_name.insert(var->attrs.name);
           }
+          // TODO(ziheng): this pass now mutates the original graph structure
+          // This might not be a good thing, change to copy the structure instead
+          //
           e = nnvm::NodeEntry{entry_var.at(e), 0, 0};
         }
       }
@@ -56,21 +65,21 @@ nnvm::Graph PruneGraph(nnvm::Graph src) {
   pre_graph.outputs.reserve(entry_var.size());
   std::vector<std::string> output_names;
   output_names.reserve(entry_var.size());
+
   for (auto kv : entry_var) {
     if (kv.first.node->is_variable()) continue;
     pre_graph.outputs.emplace_back(kv.first);
     output_names.emplace_back(kv.second->attrs.name);
   }
-
-  pre_graph.attrs["pruned_params"] =
-    std::make_shared<dmlc::any>(std::move(output_names));
-  src.attrs["pre_graph"] =
-    std::make_shared<dmlc::any>(std::move(pre_graph));
+  // new parameter list
+  pre_graph.attrs["output_names"] =
+      std::make_shared<dmlc::any>(std::move(output_names));
+  src.attrs["precompute_graph"] =
+      std::make_shared<dmlc::any>(std::move(pre_graph));
   return src;
 }
 
-NNVM_REGISTER_PASS(PruneGraph)
-.set_body(PruneGraph);
-
+NNVM_REGISTER_PASS(PrecomputePrune)
+.set_body(PrecomputePrune);
 }  // namespace compiler
 }  // namespace nnvm
diff --git a/nnvm/tests/python/compiler/test_build.py b/nnvm/tests/python/compiler/test_build.py
index 877d1602d6af..3abe94913a8f 100644
--- a/nnvm/tests/python/compiler/test_build.py
+++ b/nnvm/tests/python/compiler/test_build.py
@@ -17,8 +17,8 @@ def test_compile():
     m = nnvm.runtime.create(graph, lib, tvm.cpu(0))
     # get member functions
     set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
-    na = tvm.nd.array(np.ones(shape).astype(dtype))
-    nb = tvm.nd.array(np.ones(shape).astype(dtype))
+    na = tvm.nd.array(np.random.uniform(size=shape).astype(dtype))
+    nb = tvm.nd.array(np.random.uniform(size=shape).astype(dtype))
     # set inputs
     set_input("x", na)
     set_input("y", nb)
@@ -30,5 +30,37 @@ def test_compile():
     np.testing.assert_allclose(
         out.asnumpy(), np.exp(na.asnumpy() + nb.asnumpy()))
 
+
+def test_run():
+    x = sym.Variable("x")
+    y = sym.Variable("y")
+    z = sym.exp(y + x)
+    shape = (10, 10)
+    dtype = tvm.float32
+    nx = tvm.nd.array(np.random.uniform(size=shape).astype(dtype))
+    ny = tvm.nd.array(np.random.uniform(size=shape).astype(dtype))
+    res = nnvm.compiler._run_graph(z, {"x": nx, "y": ny})
+    np.testing.assert_allclose(
+        res[0].asnumpy(), np.exp(nx.asnumpy() + ny.asnumpy()))
+
+
+def test_precompute_prune():
+    x = sym.Variable("x") + 1
+    y = sym.Variable("y")
+    z = y + x
+    shape = (10, 10)
+    dtype = tvm.float32
+    nx = tvm.nd.array(np.random.uniform(size=shape).astype(dtype))
+    ny = tvm.nd.array(np.random.uniform(size=shape).astype(dtype))
+    params = {"x": nx}
+    graph, pdict = nnvm.compiler.precompute_prune(z, params)
+    pdict["y"] = ny
+    res = nnvm.compiler._run_graph(z, pdict)
+    np.testing.assert_allclose(
+        res[0].asnumpy(), nx.asnumpy() + 1 + ny.asnumpy())
+
+
 if __name__ == "__main__":
     test_compile()
+    test_run()
+    test_precompute_prune()
diff --git a/nnvm/tests/python/compiler/test_graph_pass.py b/nnvm/tests/python/compiler/test_graph_pass.py
new file mode 100644
index 000000000000..110184c8f07c
--- /dev/null
+++ b/nnvm/tests/python/compiler/test_graph_pass.py
@@ -0,0 +1,18 @@
+"""Unittest cases for graph pass"""
+import nnvm
+import nnvm.compiler
+from nnvm.compiler import graph_pass
+
+def test_infer_attr():
+    x = nnvm.symbol.Variable("x")
+    y = x * 2
+    g = nnvm.graph.create(y)
+    ishape, oshape = graph_pass.infer_shape(g, x=(10,20))
+    assert tuple(oshape[0]) == (10, 20)
+
+    itype, otype = graph_pass.infer_dtype(g, x="float32")
+    assert otype[0] == "float32"
+
+
+if __name__ == "__main__":
+    test_infer_attr()

From 7cbfb605c688c720264eeb1f46496ebf585ecb8e Mon Sep 17 00:00:00 2001
From: "Joshua Z. Zhang" <cheungchih@gmail.com>
Date: Tue, 19 Sep 2017 20:36:01 -0700
Subject: [PATCH 473/949] fix segfault when op is unset (#15)

---
 nnvm/src/core/symbolic.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index 170ca1c7c072..38050367582c 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -491,7 +491,7 @@ bool Symbol::GetAttr(const std::string& key, std::string* out) const {
     if (node->attrs.op != nullptr) {
       *out = node->attrs.op->name;
     } else {
-      *out = "null";  // use null in consistant with json
+      *out = "null";  // use null with json
     }
     return true;
   }

From 1650593b1360fb86a8b0e542131f742e2c8dfc71 Mon Sep 17 00:00:00 2001
From: Yuwei HU <huyuwei1995@gmail.com>
Date: Wed, 20 Sep 2017 13:07:30 +0800
Subject: [PATCH 474/949] register softmax (#16)

---
 nnvm/python/nnvm/top/nn.py                    | 20 +++++++-
 nnvm/tests/python/compiler/test_top_level1.py | 46 +++++++++++++++++++
 nnvm/tests/python/unittest/test_top_level1.py |  2 +-
 3 files changed, 66 insertions(+), 2 deletions(-)
 create mode 100644 nnvm/tests/python/compiler/test_top_level1.py

diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index cca466a04818..abe82aed65e7 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -23,7 +23,6 @@ def compute_conv2d(attrs, inputs):
         out = topi.broadcast_add(out, bias)
     return out
 
-
 @reg.register_schedule("conv2d")
 def schedule_conv2d(_, outs, target):
     """Schedule definition of conv2d"""
@@ -33,3 +32,22 @@ def schedule_conv2d(_, outs, target):
     return tvm.create_schedule([x.op for x in outs])
 
 reg.register_pattern("conv2d", OpPattern.COMPLEX)
+
+
+# softmax
+@reg.register_compute("softmax")
+def compute_softmax(attrs, inputs):
+    """Compute definition of softmax"""
+    axis = attrs.get_int("axis")
+    assert axis == -1, "only support axis == -1 for now"
+    return topi.nn.softmax(inputs[0])
+
+@reg.register_schedule("softmax")
+def schedule_softmax(_, outs, target):
+    """Schedule definition of softmax"""
+    if target == "cuda":
+        return topi.cuda.schedule_softmax(outs)
+    # naive schedule
+    return tvm.create_schedule([x.op for x in outs])
+
+reg.register_pattern("softmax", OpPattern.COMPLEX)
diff --git a/nnvm/tests/python/compiler/test_top_level1.py b/nnvm/tests/python/compiler/test_top_level1.py
new file mode 100644
index 000000000000..bb920c5a773b
--- /dev/null
+++ b/nnvm/tests/python/compiler/test_top_level1.py
@@ -0,0 +1,46 @@
+import numpy as np
+
+import tvm
+import topi
+import nnvm.symbol as sym
+import nnvm.compiler
+import nnvm.runtime
+
+USE_GPU=True
+
+def default_target():
+    if USE_GPU:
+        return 'cuda'
+    else:
+        return 'llvm'
+
+def default_ctx():
+    if USE_GPU:
+        return tvm.gpu(0)
+    else:
+        return tvm.cpu(0)
+
+def test_softmax():
+    x = sym.Variable("x")
+    y = sym.softmax(x)
+    dtype = "float32"
+    dshape = (10, 1000)
+    oshape = dshape
+    graph, lib = nnvm.compiler.build(y, default_target(), {"x": dshape})
+    m = nnvm.runtime.create(graph, lib, default_ctx())
+    # get member functions
+    set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
+    # set input
+    data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+    set_input("x", data)
+    # execute
+    run()
+    # get outputs
+    out = tvm.nd.empty(oshape, dtype)
+    get_output(0, out)
+    y_np = topi.testing.softmax_python(data.asnumpy())
+    np.testing.assert_allclose(out.asnumpy(), y_np, rtol=1e-5)
+
+
+if __name__ == "__main__":
+    test_softmax()
diff --git a/nnvm/tests/python/unittest/test_top_level1.py b/nnvm/tests/python/unittest/test_top_level1.py
index a53b664acb16..135468a60b6b 100644
--- a/nnvm/tests/python/unittest/test_top_level1.py
+++ b/nnvm/tests/python/unittest/test_top_level1.py
@@ -5,7 +5,7 @@ def test_dense():
     x1 = sym.dense(x, units=3, name="dense")
     x2 = sym.flatten(x1)
     x3 = sym.softmax(x2)
-    assert x2.list_input_names() == ['x', 'dense_weight', 'dense_bias']
+    assert x3.list_input_names() == ['x', 'dense_weight', 'dense_bias']
 
 
 def test_concatenate_split():

From 74855caa55bcc9f49ae682c524e05208f5bbca51 Mon Sep 17 00:00:00 2001
From: Yuwei HU <huyuwei1995@gmail.com>
Date: Thu, 21 Sep 2017 08:59:13 +0800
Subject: [PATCH 475/949] register depthconv, elemwise (#17)

* register depthconv, elemwise

* use global elemwise schedule for relu
---
 nnvm/python/nnvm/top/nn.py                    |  71 +++++++----
 nnvm/python/nnvm/top/tensor.py                |  27 ++++
 nnvm/tests/python/compiler/test_top_level1.py | 119 +++++++++++++++++-
 nnvm/tests/python/compiler/test_top_level2.py |  52 +++++++-
 nnvm/tests/python/unittest/test_top_level1.py |   1 +
 5 files changed, 241 insertions(+), 29 deletions(-)

diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index abe82aed65e7..69ca49e6603e 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -3,9 +3,44 @@
 
 import tvm
 import topi
+from topi.util import get_const_int
+from .tensor import schedule_elemwise
 from ..compiler import registry as reg
 from ..compiler import OpPattern
 
+# relu
+@reg.register_compute("relu")
+def compute_relu(attrs, inputs):
+    """Compute definition of relu"""
+    return topi.nn.relu(inputs[0])
+
+@reg.register_schedule("relu")
+def schedule_relu(_, outs, target):
+    """Schedule definition of relu"""
+    return schedule_elemwise(_, outs, target)
+
+reg.register_pattern("relu", OpPattern.ELEM_WISE)
+
+
+# softmax
+@reg.register_compute("softmax")
+def compute_softmax(attrs, inputs):
+    """Compute definition of softmax"""
+    axis = attrs.get_int("axis")
+    assert axis == -1, "only support axis == -1 for now"
+    return topi.nn.softmax(inputs[0])
+
+@reg.register_schedule("softmax")
+def schedule_softmax(_, outs, target):
+    """Schedule definition of softmax"""
+    if target == "cuda":
+        return topi.cuda.schedule_softmax(outs)
+    # naive schedule
+    return tvm.create_schedule([x.op for x in outs])
+
+reg.register_pattern("softmax", OpPattern.COMPLEX)
+
+
 # conv
 @reg.register_compute("conv2d")
 def compute_conv2d(attrs, inputs):
@@ -13,10 +48,17 @@ def compute_conv2d(attrs, inputs):
     padding = attrs.get_int_tuple("padding")
     strides = attrs.get_int_tuple("strides")
     dilation = attrs.get_int_tuple("dilation")
+    groups = attrs.get_int("groups")
+    channels = attrs.get_int("channels")
     layout = attrs["layout"]
     assert layout == "NCHW", "only support nchw for now"
     assert dilation == (1, 1), "not support dilate now"
-    out = topi.nn.conv2d_nchw(inputs[0], inputs[1], strides, padding)
+    if groups == 1:
+        out = topi.nn.conv2d_nchw(inputs[0], inputs[1], strides, padding)
+    elif groups == get_const_int(inputs[0].shape[1]) and groups == channels:
+        out = topi.nn.depthwise_conv2d_nchw(inputs[0], inputs[1], strides, padding)
+    else:
+        raise ValueError("not support arbitrary group number for now")
     if attrs.get_bool("use_bias"):
         bias = inputs[2]
         bias = topi.broadcast_to(bias, (1, bias.shape[0], 1, 1))
@@ -24,30 +66,15 @@ def compute_conv2d(attrs, inputs):
     return out
 
 @reg.register_schedule("conv2d")
-def schedule_conv2d(_, outs, target):
+def schedule_conv2d(attrs, outs, target):
     """Schedule definition of conv2d"""
+    groups = attrs.get_int("groups")
     if target == "cuda":
-        return topi.cuda.schedule_conv2d_nchw(outs)
+        if groups == 1:
+            return topi.cuda.schedule_conv2d_nchw(outs)
+        else:
+            return topi.cuda.schedule_depthwise_conv2d_nchw(outs)
     # naive schedule
     return tvm.create_schedule([x.op for x in outs])
 
 reg.register_pattern("conv2d", OpPattern.COMPLEX)
-
-
-# softmax
-@reg.register_compute("softmax")
-def compute_softmax(attrs, inputs):
-    """Compute definition of softmax"""
-    axis = attrs.get_int("axis")
-    assert axis == -1, "only support axis == -1 for now"
-    return topi.nn.softmax(inputs[0])
-
-@reg.register_schedule("softmax")
-def schedule_softmax(_, outs, target):
-    """Schedule definition of softmax"""
-    if target == "cuda":
-        return topi.cuda.schedule_softmax(outs)
-    # naive schedule
-    return tvm.create_schedule([x.op for x in outs])
-
-reg.register_pattern("softmax", OpPattern.COMPLEX)
diff --git a/nnvm/python/nnvm/top/tensor.py b/nnvm/python/nnvm/top/tensor.py
index a316bf11102f..c49aeae0d19f 100644
--- a/nnvm/python/nnvm/top/tensor.py
+++ b/nnvm/python/nnvm/top/tensor.py
@@ -8,6 +8,15 @@
 from ..compiler import registry as reg
 from ..compiler import OpPattern
 
+def schedule_elemwise(_, outs, target):
+    """Generic schedule for elemwise operation"""
+    if target == "cuda":
+        return topi.cuda.schedule_elemwise(outs)
+    assert target.startswith("llvm")
+    s = tvm.create_schedule([x.op for x in outs])
+    tvm.schedule.AutoInlineInjective(s)
+    return s
+
 def _schedule_broadcast(_, outs, target):
     """Generic schedule for binary bcast"""
     if target == "cuda":
@@ -36,6 +45,24 @@ def _compute(attrs, x):
 reg.register_pattern("exp", OpPattern.ELEM_WISE)
 reg.register_schedule("exp", _fschedule_broadcast)
 
+# log
+reg.register_compute("log",
+                     lambda _, x: topi.log(x[0]))
+reg.register_pattern("log", OpPattern.ELEM_WISE)
+reg.register_schedule("log", _fschedule_broadcast)
+
+# tanh
+reg.register_compute("tanh",
+                     lambda _, x: topi.tanh(x[0]))
+reg.register_pattern("tanh", OpPattern.ELEM_WISE)
+reg.register_schedule("tanh", _fschedule_broadcast)
+
+# sigmoid
+reg.register_compute("sigmoid",
+                     lambda _, x: topi.sigmoid(x[0]))
+reg.register_pattern("sigmoid", OpPattern.ELEM_WISE)
+reg.register_schedule("sigmoid", _fschedule_broadcast)
+
 # add scalar
 reg.register_compute("__add_scalar__",
                      _compute_binary_scalar(lambda x, y: x + y))
diff --git a/nnvm/tests/python/compiler/test_top_level1.py b/nnvm/tests/python/compiler/test_top_level1.py
index bb920c5a773b..88aeb3459886 100644
--- a/nnvm/tests/python/compiler/test_top_level1.py
+++ b/nnvm/tests/python/compiler/test_top_level1.py
@@ -20,6 +20,116 @@ def default_ctx():
     else:
         return tvm.cpu(0)
 
+def test_relu():
+    x = sym.Variable("x")
+    y = sym.relu(x)
+    dtype = "float32"
+    dshape = (1, 3, 32, 32)
+    oshape = dshape
+    graph, lib = nnvm.compiler.build(y, default_target(), {"x": dshape})
+    m = nnvm.runtime.create(graph, lib, default_ctx())
+    # get member functions
+    set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
+    # set input
+    data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+    set_input("x", data)
+    # execute
+    run()
+    # get output
+    out = tvm.nd.empty(oshape, dtype)
+    get_output(0, out)
+    y_np = np.maximum(data.asnumpy(), 0.0)
+    np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+
+
+def test_exp():
+    x = sym.Variable("x")
+    y = sym.exp(x)
+    dtype = "float32"
+    dshape = (1, 3, 32, 32)
+    oshape = dshape
+    graph, lib = nnvm.compiler.build(y, default_target(), {"x": dshape})
+    m = nnvm.runtime.create(graph, lib, default_ctx())
+    # get member functions
+    set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
+    # set input
+    data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+    set_input("x", data)
+    # execute
+    run()
+    # get output
+    out = tvm.nd.empty(oshape, dtype)
+    get_output(0, out)
+    y_np = np.exp(data.asnumpy())
+    np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+
+
+def test_log():
+    x = sym.Variable("x")
+    y = sym.log(x)
+    dtype = "float32"
+    dshape = (1, 3, 32, 32)
+    oshape = dshape
+    graph, lib = nnvm.compiler.build(y, default_target(), {"x": dshape})
+    m = nnvm.runtime.create(graph, lib, default_ctx())
+    # get member functions
+    set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
+    # set input
+    data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+    set_input("x", data)
+    # execute
+    run()
+    # get output
+    out = tvm.nd.empty(oshape, dtype)
+    get_output(0, out)
+    y_np = np.log(data.asnumpy())
+    np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+
+
+def test_tanh():
+    x = sym.Variable("x")
+    y = sym.tanh(x)
+    dtype = "float32"
+    dshape = (1, 3, 32, 32)
+    oshape = dshape
+    graph, lib = nnvm.compiler.build(y, default_target(), {"x": dshape})
+    m = nnvm.runtime.create(graph, lib, default_ctx())
+    # get member functions
+    set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
+    # set input
+    data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+    set_input("x", data)
+    # execute
+    run()
+    # get output
+    out = tvm.nd.empty(oshape, dtype)
+    get_output(0, out)
+    y_np = np.sinh(data.asnumpy()) / np.cosh(data.asnumpy())
+    np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+
+
+def test_sigmoid():
+    x = sym.Variable("x")
+    y = sym.sigmoid(x)
+    dtype = "float32"
+    dshape = (1, 3, 32, 32)
+    oshape = dshape
+    graph, lib = nnvm.compiler.build(y, default_target(), {"x": dshape})
+    m = nnvm.runtime.create(graph, lib, default_ctx())
+    # get member functions
+    set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
+    # set input
+    data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+    set_input("x", data)
+    # execute
+    run()
+    # get output
+    out = tvm.nd.empty(oshape, dtype)
+    get_output(0, out)
+    y_np = 1.0 / (1.0 + np.exp(-data.asnumpy()))
+    np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+
+
 def test_softmax():
     x = sym.Variable("x")
     y = sym.softmax(x)
@@ -35,12 +145,17 @@ def test_softmax():
     set_input("x", data)
     # execute
     run()
-    # get outputs
+    # get output
     out = tvm.nd.empty(oshape, dtype)
     get_output(0, out)
     y_np = topi.testing.softmax_python(data.asnumpy())
-    np.testing.assert_allclose(out.asnumpy(), y_np, rtol=1e-5)
+    np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
 
 
 if __name__ == "__main__":
+    test_relu()
+    test_exp()
+    test_log()
+    test_tanh()
+    test_sigmoid()
     test_softmax()
diff --git a/nnvm/tests/python/compiler/test_top_level2.py b/nnvm/tests/python/compiler/test_top_level2.py
index 9f3e80cb3701..133b22fdcb65 100644
--- a/nnvm/tests/python/compiler/test_top_level2.py
+++ b/nnvm/tests/python/compiler/test_top_level2.py
@@ -6,6 +6,20 @@
 import nnvm.compiler
 import nnvm.runtime
 
+USE_GPU=True
+
+def default_target():
+    if USE_GPU:
+        return 'cuda'
+    else:
+        return 'llvm'
+
+def default_ctx():
+    if USE_GPU:
+        return tvm.gpu(0)
+    else:
+        return tvm.cpu(0)
+
 def test_conv2d():
     x = sym.Variable("x")
     y = sym.conv2d(x, channels=10, kernel_size=(3, 3),
@@ -15,25 +29,53 @@ def test_conv2d():
     kshape = (10, 3, 3, 3)
     oshape = (1, 10, 18, 18)
     shape_dict = {"x": dshape}
-    graph, lib = nnvm.compiler.build(y, "llvm", shape_dict)
-    m = nnvm.runtime.create(graph, lib, tvm.cpu(0))
+    graph, lib = nnvm.compiler.build(y, default_target(), shape_dict)
+    m = nnvm.runtime.create(graph, lib, default_ctx())
     # get member functions
     set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
+    # set input
+    data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+    kernel = tvm.nd.array(np.random.uniform(size=kshape).astype(dtype))
+    set_input("x", data)
+    set_input("y_weight", kernel)
     # execute
     run()
+    # get output
+    out = tvm.nd.empty(oshape, dtype)
+    get_output(0, out)
+    c_np = topi.testing.conv2d_nchw_python(
+        data.asnumpy(), kernel.asnumpy(), 1, 1)
+    np.testing.assert_allclose(out.asnumpy(), c_np, rtol=1e-5)
+
 
+def test_grouped_conv2d():
+    x = sym.Variable("x")
+    y = sym.conv2d(x, channels=32, kernel_size=(3, 3), groups=32,
+                   name="y", use_bias=False, padding=(1,1))
+    dtype = "float32"
+    dshape = (1, 32, 18, 18)
+    kshape = (32, 1, 3, 3)
+    oshape = (1, 32, 18, 18)
+    shape_dict = {"x": dshape}
+    graph, lib = nnvm.compiler.build(y, default_target(), shape_dict)
+    m = nnvm.runtime.create(graph, lib, default_ctx())
+    # get member functions
+    set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
+    # set input
     data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
     kernel = tvm.nd.array(np.random.uniform(size=kshape).astype(dtype))
     set_input("x", data)
     set_input("y_weight", kernel)
     # execute
     run()
-    # get outputs
+    # get output
     out = tvm.nd.empty(oshape, dtype)
     get_output(0, out)
-    c_np = topi.testing.conv2d_nchw_python(
-        data.asnumpy(), kernel.asnumpy(), 1, 1)
+    c_np = topi.testing.depthwise_conv2d_python_nchw(
+        data.asnumpy(), kernel.asnumpy(), (1,1), 'SAME')
     np.testing.assert_allclose(out.asnumpy(), c_np, rtol=1e-5)
 
+
 if __name__ == "__main__":
     test_conv2d()
+    test_grouped_conv2d()
diff --git a/nnvm/tests/python/unittest/test_top_level1.py b/nnvm/tests/python/unittest/test_top_level1.py
index 135468a60b6b..4c5c28005f88 100644
--- a/nnvm/tests/python/unittest/test_top_level1.py
+++ b/nnvm/tests/python/unittest/test_top_level1.py
@@ -25,6 +25,7 @@ def test_unary():
     x = sym.log(x)
     x = sym.sigmoid(x)
     x = sym.tanh(x)
+    x = sym.relu(x)
     assert x.list_input_names() == ['x']
 
 

From 9c529cbeffcc8efbc8715e6b7d52013ba0de98d4 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 21 Sep 2017 12:12:38 -0700
Subject: [PATCH 476/949] [PASS] PrintGraphIR, SimplifyBatchNormInference (#19)

---
 nnvm/include/nnvm/node.h                      |  27 ++++
 nnvm/python/nnvm/compiler/graph_attr.py       |   5 +-
 nnvm/python/nnvm/compiler/graph_pass.py       |  24 ++++
 nnvm/python/nnvm/graph.py                     |   4 +
 nnvm/python/nnvm/top/nn.py                    |   5 +-
 nnvm/src/compiler/graph_deep_compare.cc       | 113 ++++++++++++++++
 nnvm/src/compiler/{pass => }/graph_fuse.cc    |   2 +-
 nnvm/src/compiler/graph_transform.h           | 125 +++++++++++++++++
 .../compiler/{pass => }/layout_transform.cc   |   0
 nnvm/src/compiler/node_attr.h                 |  34 +++++
 nnvm/src/compiler/packed_func_ext.cc          |  17 +--
 .../compiler/{pass => }/precompute_prune.cc   |   0
 nnvm/src/compiler/simplify_batch_norm.cc      | 114 ++++++++++++++++
 nnvm/src/pass/print_graph_ir.cc               | 127 ++++++++++++++++++
 nnvm/src/top/nn/nn_common.h                   |   1 +
 nnvm/src/top/tensor/elemwise.cc               |  20 +++
 nnvm/tests/python/compiler/test_graph_pass.py |   6 +-
 .../compiler/test_simplify_batchnorm.py       |  49 +++++++
 nnvm/tests/python/unittest/test_top_level1.py |   1 +
 19 files changed, 650 insertions(+), 24 deletions(-)
 create mode 100644 nnvm/src/compiler/graph_deep_compare.cc
 rename nnvm/src/compiler/{pass => }/graph_fuse.cc (99%)
 create mode 100644 nnvm/src/compiler/graph_transform.h
 rename nnvm/src/compiler/{pass => }/layout_transform.cc (100%)
 create mode 100644 nnvm/src/compiler/node_attr.h
 rename nnvm/src/compiler/{pass => }/precompute_prune.cc (100%)
 create mode 100644 nnvm/src/compiler/simplify_batch_norm.cc
 create mode 100644 nnvm/src/pass/print_graph_ir.cc
 create mode 100644 nnvm/tests/python/compiler/test_simplify_batchnorm.py

diff --git a/nnvm/include/nnvm/node.h b/nnvm/include/nnvm/node.h
index 0e46e23f25e9..603ff3d05fb2 100644
--- a/nnvm/include/nnvm/node.h
+++ b/nnvm/include/nnvm/node.h
@@ -128,6 +128,33 @@ class Node {
   static NodePtr Create();
 };
 
+/*!
+ * \brief Quick utilities make node.
+ * \param op_name The name of operator
+ * \param node_name The name of the node
+ * \param inputs The input entries
+ * \param attrs The attributes
+ * \return The created node entry.
+ */
+inline NodeEntry MakeNode(
+    const char* op_name,
+    std::string node_name,
+    std::vector<NodeEntry> inputs,
+    std::unordered_map<std::string, std::string> attrs =
+    std::unordered_map<std::string, std::string>()) {
+  NodePtr p = Node::Create();
+  p->attrs.op = nnvm::Op::Get(op_name);
+  p->attrs.name = std::move(node_name);
+  if (attrs.size() != 0) {
+    p->attrs.dict = attrs;
+    if (p->attrs.op->attr_parser) {
+      p->attrs.op->attr_parser(&(p->attrs));
+    }
+  }
+  p->inputs = std::move(inputs);
+  return NodeEntry{p, 0, 0};
+}
+
 // implementation of functions.
 inline const Op* Node::op() const {
   return this->attrs.op;
diff --git a/nnvm/python/nnvm/compiler/graph_attr.py b/nnvm/python/nnvm/compiler/graph_attr.py
index 0b5d19898221..3787eca68707 100644
--- a/nnvm/python/nnvm/compiler/graph_attr.py
+++ b/nnvm/python/nnvm/compiler/graph_attr.py
@@ -83,6 +83,5 @@ def set_layout_inputs(g, layout):
     g._set_json_attr("layout_inputs", list_shape, 'list_str')
     return g
 
-
-_move_out_module = tvm.get_global_func("nnvm.graph_attr._move_module")
-_move_out_graph = tvm.get_global_func("nnvm.graph_attr._move_graph")
+_move_out_module = tvm.get_global_func("nnvm.graph._move_module")
+_move_out_graph = tvm.get_global_func("nnvm.graph._move_graph")
diff --git a/nnvm/python/nnvm/compiler/graph_pass.py b/nnvm/python/nnvm/compiler/graph_pass.py
index 7d25a055777d..3e98615d8ff2 100644
--- a/nnvm/python/nnvm/compiler/graph_pass.py
+++ b/nnvm/python/nnvm/compiler/graph_pass.py
@@ -7,6 +7,7 @@
 """
 from __future__ import absolute_import as _abs
 
+import tvm
 from . import graph_attr
 
 
@@ -60,3 +61,26 @@ def infer_dtype(graph, **dtype):
     output_dtype = [graph_attr.TCODE_TO_DTYPE[dtype[index.entry_id(x)]]
                     for x in index.output_entries]
     return input_dtype, output_dtype
+
+
+_deep_compare = tvm.get_global_func("nnvm.graph.DeepCompare")
+
+def check_graph_equal(grapha, graphb):
+    """Check if two graphs have equal structure.
+
+    Parameters
+    ----------
+    grapha : Graph
+        The first graph
+
+    graphb : Graph
+        The second graph
+
+    Raises
+    ------
+    ValueError
+        ValueError is raised with error message when graph not equal
+    """
+    err = _deep_compare(grapha, graphb)
+    if err:
+        raise ValueError("Graph compare error: " + err)
diff --git a/nnvm/python/nnvm/graph.py b/nnvm/python/nnvm/graph.py
index 309603d06264..b8cb655c9b65 100644
--- a/nnvm/python/nnvm/graph.py
+++ b/nnvm/python/nnvm/graph.py
@@ -177,6 +177,10 @@ def index(self):
             self._index = GraphIndex(self)
         return self._index
 
+    def graphir(self):
+        """Get text form of graph ir."""
+        return self.apply("PrintGraphIR").json_attr("graphir")
+
     def apply(self, passes):
         """Apply passes to the graph
 
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index 69ca49e6603e..b4c0c44f0edc 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -10,7 +10,7 @@
 
 # relu
 @reg.register_compute("relu")
-def compute_relu(attrs, inputs):
+def compute_relu(_, inputs):
     """Compute definition of relu"""
     return topi.nn.relu(inputs[0])
 
@@ -72,8 +72,7 @@ def schedule_conv2d(attrs, outs, target):
     if target == "cuda":
         if groups == 1:
             return topi.cuda.schedule_conv2d_nchw(outs)
-        else:
-            return topi.cuda.schedule_depthwise_conv2d_nchw(outs)
+        return topi.cuda.schedule_depthwise_conv2d_nchw(outs)
     # naive schedule
     return tvm.create_schedule([x.op for x in outs])
 
diff --git a/nnvm/src/compiler/graph_deep_compare.cc b/nnvm/src/compiler/graph_deep_compare.cc
new file mode 100644
index 000000000000..dd64f0e3b062
--- /dev/null
+++ b/nnvm/src/compiler/graph_deep_compare.cc
@@ -0,0 +1,113 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file graph_deep_compare.cc
+ * \brief Deep compare two graph structure
+ */
+#include <nnvm/graph.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/compiler/packed_func_ext.h>
+#include <tvm/runtime/packed_func.h>
+#include "./node_attr.h"
+
+namespace nnvm {
+namespace compiler {
+
+// deep compare the graph structure
+// not considering the graph attributes
+// return non-empty error message if the graph mismatch.
+// the comparator won't match name of intermediate node.
+std::string DeepCompare(Graph a, Graph b) {
+  const IndexedGraph& idxa = a.indexed_graph();
+  const IndexedGraph& idxb = b.indexed_graph();
+  std::ostringstream err;
+  if (idxa.num_nodes() != idxb.num_nodes()) {
+    err << "Number of nodes mismatch";
+    return err.str();
+  }
+  if (idxa.num_node_entries() != idxb.num_node_entries()) {
+    err << "Number of node entry mismatch";
+    return err.str();
+  }
+  if (idxa.outputs().size() != idxb.outputs().size()) {
+    err << "Number of outputs mismatch";
+    return err.str();
+  }
+  for (size_t i = 0; i < idxa.outputs().size(); ++i) {
+    if (idxa.outputs()[i].node_id != idxb.outputs()[i].node_id ||
+        idxa.outputs()[i].index != idxb.outputs()[i].index) {
+      err << "Output entry mismatch";
+      return err.str();
+    }
+  }
+  if (idxa.input_nodes().size() != idxb.input_nodes().size()) {
+    err << "Number of inputs mismatch";
+    return err.str();
+  }
+
+  for (uint32_t nid = 0; nid < idxa.num_nodes(); ++nid) {
+    const IndexedGraph::Node& anode = idxa[nid];
+    const IndexedGraph::Node& bnode = idxb[nid];
+    if (anode.source->op() != bnode.source->op()) {
+      err << "Node mismatch ";
+      return err.str();
+    }
+    AttrDict adict = GetAttrDict(anode.source->attrs);
+    AttrDict bdict = GetAttrDict(bnode.source->attrs);
+
+    auto fmatch = [&err, &anode](const AttrDict& adict, const AttrDict& bdict) {
+      for (const auto& kv : adict) {
+        auto it = bdict.find(kv.first);
+        if (it != bdict.end()) {
+          if (it->second != kv.second) {
+            err << "Node attr mismatch, op=" << anode.source->attrs.name
+                << " attr_key=" << kv.first << " " << it->second
+                << " v.s. " << kv.second;
+            return false;
+          }
+        } else {
+          err << "One attr_key=" << kv.first << " is missing in another "
+               << "op=" << anode.source->attrs.name;
+          return false;
+        }
+      }
+      return true;
+    };
+    if (!fmatch(adict, bdict)) return err.str();
+    if (adict.size() != bdict.size()) {
+      CHECK(!fmatch(bdict, adict));
+      return err.str();
+    }
+    if (anode.inputs.size() != bnode.inputs.size()) {
+      err << "Node input mismatch, op=" << anode.source->attrs.name;
+      return err.str();
+    }
+    if (anode.control_deps.size() != bnode.control_deps.size()) {
+      err << "Node control_deps mistach, op=" << anode.source->attrs.name;
+      return err.str();
+    }
+    for (size_t i = 0; i < anode.inputs.size(); ++i) {
+      const IndexedGraph::NodeEntry& ae = anode.inputs[i];
+      const IndexedGraph::NodeEntry& be = bnode.inputs[i];
+      if (ae.node_id != be.node_id ||
+          ae.index != be.index ||
+          ae.version != be.version) {
+        err << "Node input mismatch on, op=" << anode.source->attrs.name;
+        return err.str();
+      }
+    }
+    for (size_t i = 0; i < anode.control_deps.size(); ++i) {
+      if (anode.control_deps[i] != bnode.control_deps[i]) {
+        err << "Node control_dep mismatch on, op=" << anode.source->attrs.name;
+        return err.str();
+      }
+    }
+  }
+  return "";
+}
+
+TVM_REGISTER_GLOBAL("nnvm.graph.DeepCompare")
+.set_body([](tvm::runtime::TVMArgs args, tvm::runtime::TVMRetValue *rv) {
+    *rv = DeepCompare(args[0], args[1]);
+  });
+}  // namespace compiler
+}  // namespace nnvm
diff --git a/nnvm/src/compiler/pass/graph_fuse.cc b/nnvm/src/compiler/graph_fuse.cc
similarity index 99%
rename from nnvm/src/compiler/pass/graph_fuse.cc
rename to nnvm/src/compiler/graph_fuse.cc
index 0880a90b9dad..9496e110ba64 100644
--- a/nnvm/src/compiler/pass/graph_fuse.cc
+++ b/nnvm/src/compiler/graph_fuse.cc
@@ -13,7 +13,7 @@
 #include <tvm/runtime/packed_func.h>
 #include <tvm/operation.h>
 #include <tvm/lowered_func.h>
-#include "../../runtime/graph_executor.h"
+#include "../runtime/graph_executor.h"
 
 namespace nnvm {
 namespace compiler {
diff --git a/nnvm/src/compiler/graph_transform.h b/nnvm/src/compiler/graph_transform.h
new file mode 100644
index 000000000000..2099809115aa
--- /dev/null
+++ b/nnvm/src/compiler/graph_transform.h
@@ -0,0 +1,125 @@
+/*!
+ * Copyright (c) 2017 by Contributors
+ * \file graph_transform.h
+ * \brief A mutator class that does local pattern matching and mutates a node.
+*/
+#ifndef NNVM_COMPILER_GRAPH_TRANSFORM_H_
+#define NNVM_COMPILER_GRAPH_TRANSFORM_H_
+
+#include <nnvm/graph.h>
+#include <vector>
+
+namespace nnvm {
+namespace compiler {
+
+/*!
+ * \brief Transform the graph to build a new Graph, in post DFS order.
+ *
+ *  Automatically copies node when some of its children or control_deps changed.
+ *  This function won't be called in Variable.
+ *
+ * \param graph The original graph
+ *
+ * \param ftransform Function of (int nid, const Node* node, std::vector<NodeEntry>* out) -> bool
+ *
+ *      If empty vector is returned, it means original entries should be kept.
+ *
+ * \tparam FTransform The transformation function.
+ */
+template<typename FTransform>
+Graph GraphTransform(Graph graph, FTransform ftransform) {
+  const IndexedGraph& idx = graph.indexed_graph();
+  // new nodes
+  std::vector<NodeEntry> new_entry_map(idx.num_node_entries());
+  std::vector<bool> updated(idx.num_node_entries(), false);
+
+  // setup inputs and placeholder.
+  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+    const auto& inode = idx[nid];
+    if (inode.source->is_variable()) continue;
+    bool need_copy = false;
+    for (const IndexedGraph::NodeEntry& e : inode.inputs) {
+      if (updated[idx.entry_id(e)]) {
+        need_copy = true; break;
+      }
+    }
+    if (!need_copy) {
+      for (const uint32_t cid : inode.control_deps) {
+        const auto& cnode = idx[cid];
+        for (uint32_t i = 0 ; i < cnode.source->num_outputs(); ++i) {
+          if (updated[idx.entry_id(cid, i)]) {
+            need_copy = true;
+          }
+        }
+        if (need_copy) break;
+      }
+    }
+
+    if (!need_copy) {
+      std::vector<NodeEntry> ret;
+      if (ftransform(nid, inode.source, &ret)) {
+        CHECK_EQ(ret.size(), static_cast<size_t>(inode.source->num_outputs()));
+        for (uint32_t i = 0 ; i < inode.source->num_outputs(); ++i) {
+          updated[idx.entry_id(nid, i)] = true;
+          new_entry_map[idx.entry_id(nid, i)] = ret[i];
+        }
+      }
+    } else {
+      NodePtr node = Node::Create();
+      node->attrs = inode.source->attrs;
+      for (size_t i = 0; i < inode.inputs.size(); ++i) {
+        const IndexedGraph::NodeEntry& e = inode.inputs[i];
+        if (updated[idx.entry_id(e)]) {
+          node->inputs.push_back(new_entry_map[idx.entry_id(e)]);
+        } else {
+          node->inputs.push_back(inode.source->inputs[i]);
+        }
+      }
+      for (size_t i = 0; i < inode.control_deps.size(); ++i) {
+        const uint32_t cid = inode.control_deps[i];
+        const auto& cnode = idx[cid];
+        CHECK_NE(cnode.source->num_outputs(), 0U);
+        NodePtr selected_ptr;
+        for (uint32_t j = 0 ; j < cnode.source->num_outputs(); ++j) {
+          NodePtr cptr = updated[idx.entry_id(cid, j)] ?
+              new_entry_map[idx.entry_id(cid, j)].node : inode.source->control_deps[i];
+          if (selected_ptr == nullptr) {
+            selected_ptr = std::move(cptr);
+          } else {
+            CHECK(selected_ptr.get() == cptr.get())
+                << "Control dependency node changed to more than one node";
+          }
+        }
+        node->control_deps.push_back(selected_ptr);
+      }
+      std::vector<NodeEntry> ret;
+      if (ftransform(nid, node.get(), &ret)) {
+        CHECK_EQ(ret.size(), static_cast<size_t>(inode.source->num_outputs()));
+        for (uint32_t i = 0 ; i < inode.source->num_outputs(); ++i) {
+          updated[idx.entry_id(nid, i)] = true;
+          new_entry_map[idx.entry_id(nid, i)] = ret[i];
+        }
+      } else {
+        for (uint32_t i = 0 ; i < inode.source->num_outputs(); ++i) {
+          updated[idx.entry_id(nid, i)] = true;
+          new_entry_map[idx.entry_id(nid, i)] = NodeEntry{node, i, 0};
+        }
+      }
+    }
+  }
+  Graph ret;
+  for (size_t i = 0; i < idx.outputs().size(); ++i) {
+    const IndexedGraph::NodeEntry& e = idx.outputs()[i];
+    if (updated[idx.entry_id(e)]) {
+      ret.outputs.push_back(new_entry_map[idx.entry_id(e)]);
+    } else {
+      ret.outputs.push_back(graph.outputs[i]);
+    }
+  }
+  return ret;
+}
+
+}  // namespace compiler
+}  // namespace nnvm
+
+#endif  // NNVM_COMPILER_GRAPH_TRANSFORM_H_
diff --git a/nnvm/src/compiler/pass/layout_transform.cc b/nnvm/src/compiler/layout_transform.cc
similarity index 100%
rename from nnvm/src/compiler/pass/layout_transform.cc
rename to nnvm/src/compiler/layout_transform.cc
diff --git a/nnvm/src/compiler/node_attr.h b/nnvm/src/compiler/node_attr.h
new file mode 100644
index 000000000000..c4395ad98b69
--- /dev/null
+++ b/nnvm/src/compiler/node_attr.h
@@ -0,0 +1,34 @@
+/*!
+ * Copyright (c) 2017 by Contributors
+ * \file node_attr.h
+ * \brief utility to access node attributes
+*/
+#ifndef NNVM_COMPILER_NODE_ATTR_H_
+#define NNVM_COMPILER_NODE_ATTR_H_
+
+#include <nnvm/op.h>
+#include <nnvm/compiler/op_attr_types.h>
+#include <unordered_map>
+#include <string>
+
+namespace nnvm {
+namespace compiler {
+
+using AttrDict = std::unordered_map<std::string, std::string>;
+/*!
+ * \brief Get canonicalized attr dict from node
+ * \param attrs The node attrs
+ * \return The attribute dict
+ */
+inline AttrDict GetAttrDict(const NodeAttrs& attrs) {
+  static auto& fgetdict = nnvm::Op::GetAttr<FGetAttrDict>("FGetAttrDict");
+  if (fgetdict.count(attrs.op)) {
+    return fgetdict[attrs.op](attrs);
+  } else {
+    return attrs.dict;
+  }
+}
+
+}  // namespace compiler
+}  // namespace nnvm
+#endif  // NNVM_COMPILER_NODE_ATTR_H_
diff --git a/nnvm/src/compiler/packed_func_ext.cc b/nnvm/src/compiler/packed_func_ext.cc
index 54d0f627c7a5..5444ece9c110 100644
--- a/nnvm/src/compiler/packed_func_ext.cc
+++ b/nnvm/src/compiler/packed_func_ext.cc
@@ -8,6 +8,7 @@
 #include <nnvm/op.h>
 #include <nnvm/compiler/packed_func_ext.h>
 #include <nnvm/compiler/op_attr_types.h>
+#include "./node_attr.h"
 
 namespace tvm {
 namespace runtime {
@@ -19,7 +20,6 @@ TVM_REGISTER_EXT_TYPE(nnvm::compiler::AttrDict);
 }  // namespace runtime
 }  // namespace tvm
 
-
 namespace nnvm {
 namespace compiler {
 
@@ -58,17 +58,6 @@ TVM_REGISTER_GLOBAL("nnvm.compiler._dict_keys")
   });
 
 // custom version of TVM compute
-inline std::unordered_map<std::string, std::string>
-GetAttrDict(const NodeAttrs& attrs) {
-  static auto& fgetdict = nnvm::Op::GetAttr<FGetAttrDict>("FGetAttrDict");
-  if (fgetdict.count(attrs.op)) {
-    return fgetdict[attrs.op](attrs);
-  } else {
-    return attrs.dict;
-  }
-}
-
-
 TVM_REGISTER_GLOBAL("nnvm._register_compute")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
     // Intentionally copy and not de-allocate it, to avoid free pyobject during shutdown
@@ -105,14 +94,14 @@ TVM_REGISTER_GLOBAL("nnvm._register_pattern")
     op.set_attr<TOpPattern>("TOpPattern", args[1].operator int(), args[2]);
   });
 
-TVM_REGISTER_GLOBAL("nnvm.graph_attr._move_module")
+TVM_REGISTER_GLOBAL("nnvm.graph._move_module")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
     const nnvm::Graph& g = args[0].AsExtension<Graph>();
     *rv = const_cast<nnvm::Graph*>(&g)->
         MoveCopyAttr<tvm::runtime::Module>(args[1]);
   });
 
-TVM_REGISTER_GLOBAL("nnvm.graph_attr._move_graph")
+TVM_REGISTER_GLOBAL("nnvm.graph._move_graph")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
     const nnvm::Graph& g = args[0].AsExtension<Graph>();
     *rv = const_cast<nnvm::Graph*>(&g)->
diff --git a/nnvm/src/compiler/pass/precompute_prune.cc b/nnvm/src/compiler/precompute_prune.cc
similarity index 100%
rename from nnvm/src/compiler/pass/precompute_prune.cc
rename to nnvm/src/compiler/precompute_prune.cc
diff --git a/nnvm/src/compiler/simplify_batch_norm.cc b/nnvm/src/compiler/simplify_batch_norm.cc
new file mode 100644
index 000000000000..16d7557f29a5
--- /dev/null
+++ b/nnvm/src/compiler/simplify_batch_norm.cc
@@ -0,0 +1,114 @@
+/*!
+ * Copyright (c) 2017 by Contributors
+ * \file simplify_batch_norm.cc
+ * \author Ziheng Jiang
+*/
+#include <nnvm/graph.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/graph_attr_types.h>
+#include <nnvm/pass.h>
+#include <nnvm/compiler/op_attr_types.h>
+#include <nnvm/top/nn.h>
+#include "./graph_transform.h"
+
+namespace nnvm {
+namespace compiler {
+
+std::vector<NodeEntry>
+BatchNormToInferUnpack(const nnvm::NodeAttrs& attrs,
+                       nnvm::NodeEntry data,
+                       nnvm::NodeEntry gamma,
+                       nnvm::NodeEntry beta,
+                       nnvm::NodeEntry moving_mean,
+                       nnvm::NodeEntry moving_var,
+                       int data_dim) {
+  CHECK(attrs.op);
+  static const  Op* bn_op = Op::Get("batch_norm");
+  CHECK(attrs.op == bn_op);
+  const auto& param = nnvm::get<top::BatchNormParam>(attrs.parsed);
+  std::string bn_name = attrs.name;
+
+  // transform batch_norm(data) to scale * data + shift
+  NodeEntry var_add_eps = MakeNode(
+      "__add_scalar__", bn_name + "_add_eps",
+      {moving_var}, {{"scalar", std::to_string(param.epsilon)}});
+
+  NodeEntry sqrt = MakeNode(
+      "sqrt", bn_name + "_sqrt", {var_add_eps});
+
+  NodeEntry scale = MakeNode(
+      "__rdiv_scalar__", bn_name + "_div",
+      {sqrt}, {{"scalar", "1"}});
+
+  if (param.scale) {
+    scale = MakeNode(
+        "elemwise_mul", bn_name + "_gamma_mul_div",
+        {scale, gamma});
+  }
+
+  NodeEntry neg_mean = MakeNode(
+      "negative", bn_name + "_neg_mean", {moving_mean});
+
+  NodeEntry shift = MakeNode(
+      "elemwise_mul", bn_name + "_neg_mean_mul_a",
+      {neg_mean, scale});
+
+  if (param.center) {
+    shift = MakeNode(
+        "elemwise_add", bn_name + "_add_beta", {shift, beta});
+  }
+  // reshape to nhwc
+  std::ostringstream oshape;
+  oshape << "(";
+  for (int i = 0; i < data_dim; ++i) {
+    if (i != 0) oshape << ", ";
+    if (i == param.axis) {
+      oshape << "-1";
+    } else {
+      oshape << "1";
+    }
+  }
+  oshape << ")";
+
+  scale = MakeNode("reshape", bn_name + "_sc_reshape",
+                   {scale}, {{"shape", oshape.str()}});
+  shift = MakeNode("reshape", bn_name + "_sh_reshape",
+                   {shift}, {{"shape", oshape.str()}});
+  NodeEntry out = MakeNode("broadcast_mul", bn_name + "_a_mul_data",
+                           {data, scale});
+  out = MakeNode("broadcast_add", bn_name + "_out",
+                 {out, shift});
+  // It is invalid to ref the other values of BN after infernece transform.
+  NodeEntry undef = MakeNode("__undef__", "undef", {});
+  return {out, undef, undef};
+}
+
+Graph SimplifyBatchNormInference(nnvm::Graph src) {
+  // Get attributes from the graph
+  const IndexedGraph& idx = src.indexed_graph();
+  const ShapeVector& shape_vec = src.GetAttr<ShapeVector>("shape");
+  auto transform = [&](uint32_t nid, const Node* n, std::vector<NodeEntry>* ret) {
+    if (n->is_variable()) return false;
+    static const Op* bn_op = Op::Get("batch_norm");
+    if (n->op() == bn_op) {
+      *ret = BatchNormToInferUnpack(
+          n->attrs,
+          n->inputs[0],
+          n->inputs[1],
+          n->inputs[2],
+          n->inputs[3],
+          n->inputs[4],
+          shape_vec[idx.entry_id(nid, 0)].ndim());
+      return true;
+    } else {
+      return false;
+    }
+  };
+  return GraphTransform(src, transform);
+}
+
+NNVM_REGISTER_PASS(SimplifyBatchNormInference)
+.set_body(SimplifyBatchNormInference);
+
+}  // namespace compiler
+}  // namespace nnvm
diff --git a/nnvm/src/pass/print_graph_ir.cc b/nnvm/src/pass/print_graph_ir.cc
new file mode 100644
index 000000000000..a29ee922b644
--- /dev/null
+++ b/nnvm/src/pass/print_graph_ir.cc
@@ -0,0 +1,127 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file print_graph_ir.cc
+ * \brief Print the graph IR in LLVM style human readable format.
+ */
+#include <nnvm/graph.h>
+#include <nnvm/pass.h>
+#include <iostream>
+
+namespace nnvm {
+namespace pass {
+
+// print the graph ir in readable format
+void PrintGraphIR_(Graph src, std::ostream& os) { // NOLINT(*)
+  const IndexedGraph& idx = src.indexed_graph();
+  os << "Graph(";
+  if (idx.input_nodes().size() < 4) {
+    for (size_t i = 0; i < idx.input_nodes().size(); ++i) {
+      uint32_t nid = idx.input_nodes()[i];
+      if (i != 0)  {
+        os << ", ";
+      }
+      os << '%' << idx[nid].source->attrs.name;
+    }
+  } else {
+    for (size_t i = 0; i < idx.input_nodes().size(); ++i) {
+      uint32_t nid = idx.input_nodes()[i];
+      if (i != 0)  {
+        os << ",\n      ";
+      }
+      os << '%' << idx[nid].source->attrs.name;
+    }
+  }
+  os << ") {\n";
+
+  auto print_entry = [&](const IndexedGraph::NodeEntry& e) {
+    if (idx[e.node_id].source->is_variable()) {
+      os << '%' << idx[e.node_id].source->attrs.name;
+    } else if (idx[e.node_id].source->num_outputs() == 1) {
+      os << '%' << e.node_id;
+    } else {
+      os << '%' << e.node_id << "." << e.index;
+    }
+  };
+
+  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+    const auto& inode = idx[nid];
+    if (inode.source->is_variable()) continue;
+    os << "  " << "%" << nid << " = "
+       << inode.source->op()->name << "(";
+    bool first = true;
+    for (const IndexedGraph::NodeEntry& e : inode.inputs) {
+      if (first) {
+        first = false;
+      } else {
+        os << ", ";
+      }
+      print_entry(e);
+    }
+    for (const auto& kv : inode.source->attrs.dict) {
+      if (first) {
+        first = false;
+      } else {
+        os << ", ";
+      }
+      os << kv.first << "=\'" << kv.second << "\'";
+    }
+    os << ")";
+    if (inode.control_deps.size() != 0) {
+      os << ", control_deps=[";
+      for (size_t i = 0; i < inode.control_deps.size(); ++i) {
+        if (i != 0) os << ", ";
+        uint32_t cid = inode.control_deps[i];
+        if (idx[cid].source->is_variable()) {
+          os << '%' << idx[cid].source->attrs.name;
+        } else {
+          os << '%' << cid;
+        }
+      }
+      os << "]";
+    }
+    os << "\n";
+  }
+  os << "  ret ";
+  {
+    bool first = true;
+    for (const IndexedGraph::NodeEntry& e : idx.outputs()) {
+      if (first) {
+        first = false;
+      } else {
+        os << ", ";
+      }
+      print_entry(e);
+    }
+  }
+  os << "\n}";
+  if (src.attrs.size() != 0) {
+    os << "\ngraph_attr_keys = [";
+    bool first = true;
+    for (const auto& kv : src.attrs) {
+      if (first) {
+        first = false;
+      } else {
+        os << ", ";
+      }
+      os << kv.first;
+    }
+    os << "]\n";
+  }
+}
+
+// save a graph to json
+Graph PrintGraphIR(Graph src) {
+  std::ostringstream os;
+  PrintGraphIR_(src, os);
+  Graph ret;
+  ret.attrs["graphir"] = std::make_shared<any>(os.str());
+  return ret;
+}
+
+// register pass
+NNVM_REGISTER_PASS(PrintGraphIR)
+.describe("Return a empty Graph, save ir to ret.attrs[\"graphir\"]")
+.set_body(PrintGraphIR);
+
+}  // namespace pass
+}  // namespace nnvm
diff --git a/nnvm/src/top/nn/nn_common.h b/nnvm/src/top/nn/nn_common.h
index a4ce6b1b78d0..a75077b87ca4 100644
--- a/nnvm/src/top/nn/nn_common.h
+++ b/nnvm/src/top/nn/nn_common.h
@@ -11,6 +11,7 @@
 #include <nnvm/top/nn.h>
 #include <string>
 #include <vector>
+#include <utility>
 #include <algorithm>
 
 namespace nnvm {
diff --git a/nnvm/src/top/tensor/elemwise.cc b/nnvm/src/top/tensor/elemwise.cc
index 825b01b018e2..17e457e56b3c 100644
--- a/nnvm/src/top/tensor/elemwise.cc
+++ b/nnvm/src/top/tensor/elemwise.cc
@@ -12,6 +12,16 @@
 
 namespace nnvm {
 namespace top {
+// undefined op
+NNVM_REGISTER_ELEMWISE_UNARY_OP(__undef__)
+.describe(R"code(undefined op.
+
+Used to produce invalide node during optimization.
+
+)code" NNVM_ADD_FILELINE)
+.set_num_outputs(1)
+.set_num_inputs(0);
+
 // sigmoid
 NNVM_REGISTER_ELEMWISE_UNARY_OP(sigmoid)
 .describe(R"code(Computes sigmoid.
@@ -52,6 +62,16 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(log)
 )code" NNVM_ADD_FILELINE)
 .set_support_level(1);
 
+// sqrt
+NNVM_REGISTER_ELEMWISE_UNARY_OP(sqrt)
+.describe(R"code(Returns the sqrt input array, computed element-wise.
+
+.. math::
+   \sqrt(x)
+
+)code" NNVM_ADD_FILELINE)
+.set_support_level(1);
+
 // binary ops
 
 NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_add)
diff --git a/nnvm/tests/python/compiler/test_graph_pass.py b/nnvm/tests/python/compiler/test_graph_pass.py
index 110184c8f07c..9f2c36a1d3f9 100644
--- a/nnvm/tests/python/compiler/test_graph_pass.py
+++ b/nnvm/tests/python/compiler/test_graph_pass.py
@@ -1,10 +1,11 @@
 """Unittest cases for graph pass"""
 import nnvm
 import nnvm.compiler
-from nnvm.compiler import graph_pass
+from nnvm import symbol as sym
+from nnvm.compiler import graph_pass, graph_attr
 
 def test_infer_attr():
-    x = nnvm.symbol.Variable("x")
+    x = sym.Variable("x")
     y = x * 2
     g = nnvm.graph.create(y)
     ishape, oshape = graph_pass.infer_shape(g, x=(10,20))
@@ -13,6 +14,5 @@ def test_infer_attr():
     itype, otype = graph_pass.infer_dtype(g, x="float32")
     assert otype[0] == "float32"
 
-
 if __name__ == "__main__":
     test_infer_attr()
diff --git a/nnvm/tests/python/compiler/test_simplify_batchnorm.py b/nnvm/tests/python/compiler/test_simplify_batchnorm.py
new file mode 100644
index 000000000000..54cb8bc0bc57
--- /dev/null
+++ b/nnvm/tests/python/compiler/test_simplify_batchnorm.py
@@ -0,0 +1,49 @@
+"""Unittest cases for simplify batch_norm"""
+import nnvm
+from nnvm import symbol as sym
+from nnvm.compiler import graph_pass, graph_attr
+
+def test_simplify_batchnorm():
+    def simple_bn(x, gamma, beta, moving_mean, moving_var,
+                  axis=1, epsilon=1e-5, dim=2):
+        # expect = (x - moving_mean) / sym.sqrt(moving_var + eps) * gamma + beta
+        scale = sym.elemwise_mul(1 / sym.sqrt(moving_var + epsilon), gamma)
+        shift = sym.elemwise_add(
+            sym.elemwise_mul(sym.negative(moving_mean), scale), beta)
+        # for 2D
+        shape = tuple(1 if i != axis else -1 for i in range(dim))
+        scale = sym.reshape(scale, shape=shape)
+        shift = sym.reshape(shift, shape=shape)
+        return x * scale + shift
+
+
+    # Before simplify
+    def check(dim, axis, nstep):
+        eps = 0.01
+        x = sym.Variable("x") + 1
+        beta = sym.Variable("beta")
+        gamma = sym.Variable("gamma")
+        moving_var = sym.Variable("moving_var")
+        moving_mean = sym.Variable("moving_mean")
+        y1, y2 = x, x
+
+        for i in range(nstep):
+            y1 = sym.batch_norm(
+                y1 + 1, gamma, beta, moving_mean, moving_var, epsilon=eps, axis=axis)
+            y2 = simple_bn(y2 + 1, gamma, beta, moving_mean, moving_var,
+                           epsilon=eps, axis=axis, dim=dim)
+        g = nnvm.graph.create(y1)
+        g2 = nnvm.graph.create(y2)
+        ishape = {"x": tuple(10 for i in range(dim))}
+        graph_attr.set_shape_inputs(g, ishape)
+        g1 = g.apply("InferShape").apply("SimplifyBatchNormInference")
+        # Some prints for debug
+        # print(g1.graphir())
+        # assert graph equals as expected
+        graph_pass.check_graph_equal(g1, g2)
+
+    check(2, 1, 1)
+    check(4, 0, 3)
+
+if __name__ == "__main__":
+    test_simplify_batchnorm()
diff --git a/nnvm/tests/python/unittest/test_top_level1.py b/nnvm/tests/python/unittest/test_top_level1.py
index 4c5c28005f88..6b75cf1d83f1 100644
--- a/nnvm/tests/python/unittest/test_top_level1.py
+++ b/nnvm/tests/python/unittest/test_top_level1.py
@@ -1,4 +1,5 @@
 import nnvm.symbol as sym
+import nnvm.graph as graph
 
 def test_dense():
     x = sym.Variable('x')

From ddd23a8720fca9dd7acd82395b765a65febb0396 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 21 Sep 2017 14:41:12 -0700
Subject: [PATCH 477/949] [PASS] PrintGraphIR Join attributes when print ir
 (#20)

---
 nnvm/python/nnvm/graph.py                     | 19 ++++-
 nnvm/src/pass/infer_shape_type.cc             |  2 +
 nnvm/src/pass/print_graph_ir.cc               | 83 ++++++++++++++++++-
 .../compiler/test_simplify_batchnorm.py       |  2 +-
 nnvm/tests/python/unittest/test_graph.py      | 11 +++
 5 files changed, 112 insertions(+), 5 deletions(-)

diff --git a/nnvm/python/nnvm/graph.py b/nnvm/python/nnvm/graph.py
index b8cb655c9b65..5a7b36342a36 100644
--- a/nnvm/python/nnvm/graph.py
+++ b/nnvm/python/nnvm/graph.py
@@ -177,8 +177,23 @@ def index(self):
             self._index = GraphIndex(self)
         return self._index
 
-    def graphir(self):
-        """Get text form of graph ir."""
+    def ir(self, join_entry_attrs=None, join_node_attrs=None):
+        """Get text form of graph ir.
+
+        Parameters
+        ----------
+        join_entry_attrs : list of str
+            List of graph NodeEntry attribute to be
+            printed along each operator.
+
+        join_node_attrs : list of str
+            List of graph node attribute to be
+            printed along each operator.
+        """
+        if join_entry_attrs:
+            self._set_json_attr("join_entry_attrs", join_entry_attrs, "list_str")
+        if join_node_attrs:
+            self._set_json_attr("join_node_attrs", join_node_attrs, "list_str")
         return self.apply("PrintGraphIR").json_attr("graphir")
 
     def apply(self, passes):
diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index 0332532717e6..fd9b77c42c3f 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -67,6 +67,8 @@ Graph InferAttr(Graph &&ret,
     shape_attr_key = ret.GetAttr<std::string>(attr_key_name);
     // erase the provided arguments
     ret.attrs.erase(attr_key_name);
+  } else {
+    shape_attr_key = attr_name;
   }
   // Temp space for shape inference.
   std::vector<AttrType> ishape, oshape;
diff --git a/nnvm/src/pass/print_graph_ir.cc b/nnvm/src/pass/print_graph_ir.cc
index a29ee922b644..6a42aabce616 100644
--- a/nnvm/src/pass/print_graph_ir.cc
+++ b/nnvm/src/pass/print_graph_ir.cc
@@ -5,14 +5,80 @@
  */
 #include <nnvm/graph.h>
 #include <nnvm/pass.h>
+#include <nnvm/tuple.h>
 #include <iostream>
 
 namespace nnvm {
 namespace pass {
 
+using AttrPrinter = std::function<void(uint32_t index, std::ostream& os)>;  // NOLINT(*)
+
+template<typename T>
+AttrPrinter GetVectorPrinter_(const T& vec) {
+  return [&vec](uint32_t index, std::ostream& os) {  // NOLINT(*)
+    os << vec[index];
+  };
+}
+
+AttrPrinter GetVectorPrinter(const Graph& graph,
+                             const std::string& key) {
+  auto it = graph.attrs.find(key);
+  CHECK(it != graph.attrs.end())
+      << "Cannot find " << key << " in graph attr";
+  const any& value = *(it->second);
+  if (value.type() == typeid(std::vector<TShape>)) {
+    return GetVectorPrinter_(
+        nnvm::get<std::vector<TShape> >(value));
+  } else if (value.type() == typeid(std::vector<int>)) {
+    return GetVectorPrinter_(
+        nnvm::get<std::vector<int> >(value));
+  } else if (value.type() == typeid(std::vector<std::string>)) {
+    return GetVectorPrinter_(
+        nnvm::get<std::vector<std::string> >(value));
+  } else {
+    LOG(FATAL) << "Cannot handle type " << value.type().name();
+    return nullptr;
+  }
+}
+
+
 // print the graph ir in readable format
-void PrintGraphIR_(Graph src, std::ostream& os) { // NOLINT(*)
+void PrintGraphIR_(Graph src,
+                   const std::vector<std::string>& join_entry_attrs,
+                   const std::vector<std::string>& join_node_attrs,
+                   std::ostream& os) { // NOLINT(*)
   const IndexedGraph& idx = src.indexed_graph();
+  std::vector<std::function<void(uint32_t, std::ostream&)> > trigger;  // NOLINT(*)
+
+  for (const std::string& key : join_entry_attrs) {
+    AttrPrinter fp = GetVectorPrinter(src, key);
+    auto fprint = [&idx, key, fp](
+        uint32_t nid, std::ostream& os) {  // NOLINT(*)
+      const IndexedGraph::Node& inode = idx[nid];
+      os << ", " << key << "=";
+      if (inode.source->num_outputs() != 1) {
+        os << '[';
+        for (uint32_t i = 0; i < inode.source->num_outputs(); ++i) {
+          if (i != 0) os << ", ";
+          fp(idx.entry_id(nid, i), os);
+        }
+        os << ']';
+      } else {
+        fp(idx.entry_id(nid, 0), os);
+      }
+    };
+    trigger.push_back(fprint);
+  }
+  for (const std::string& key : join_node_attrs) {
+    AttrPrinter fp = GetVectorPrinter(src, key);
+    auto fprint = [&idx, key, fp](
+        uint32_t nid, std::ostream& os) {  // NOLINT(*)
+      os << key << "=";
+      fp(idx.entry_id(nid, 0), os);
+    };
+    trigger.push_back(fprint);
+  }
+
   os << "Graph(";
   if (idx.input_nodes().size() < 4) {
     for (size_t i = 0; i < idx.input_nodes().size(); ++i) {
@@ -79,6 +145,10 @@ void PrintGraphIR_(Graph src, std::ostream& os) { // NOLINT(*)
       }
       os << "]";
     }
+    // additional attribute trigger
+    for (const auto& fp : trigger) {
+      fp(nid, os);
+    }
     os << "\n";
   }
   os << "  ret ";
@@ -112,7 +182,16 @@ void PrintGraphIR_(Graph src, std::ostream& os) { // NOLINT(*)
 // save a graph to json
 Graph PrintGraphIR(Graph src) {
   std::ostringstream os;
-  PrintGraphIR_(src, os);
+  std::vector<std::string> join_entry_attrs, join_node_attrs;
+  if (src.attrs.count("join_entry_attrs") != 0) {
+    join_entry_attrs = src.MoveCopyAttr<std::vector<std::string> >(
+        "join_entry_attrs");
+  }
+  if (src.attrs.count("join_node_attrs") != 0) {
+    join_node_attrs = src.MoveCopyAttr<std::vector<std::string> >(
+        "join_node_attrs");
+  }
+  PrintGraphIR_(src, join_entry_attrs, join_node_attrs, os);
   Graph ret;
   ret.attrs["graphir"] = std::make_shared<any>(os.str());
   return ret;
diff --git a/nnvm/tests/python/compiler/test_simplify_batchnorm.py b/nnvm/tests/python/compiler/test_simplify_batchnorm.py
index 54cb8bc0bc57..67d582769ef2 100644
--- a/nnvm/tests/python/compiler/test_simplify_batchnorm.py
+++ b/nnvm/tests/python/compiler/test_simplify_batchnorm.py
@@ -38,7 +38,7 @@ def check(dim, axis, nstep):
         graph_attr.set_shape_inputs(g, ishape)
         g1 = g.apply("InferShape").apply("SimplifyBatchNormInference")
         # Some prints for debug
-        # print(g1.graphir())
+        # print(g1.ir())
         # assert graph equals as expected
         graph_pass.check_graph_equal(g1, g2)
 
diff --git a/nnvm/tests/python/unittest/test_graph.py b/nnvm/tests/python/unittest/test_graph.py
index e706ae1b428c..f41d62538817 100644
--- a/nnvm/tests/python/unittest/test_graph.py
+++ b/nnvm/tests/python/unittest/test_graph.py
@@ -99,8 +99,19 @@ def test_plan_memory():
     assert (storage_id[jnode_row_ptr[nindex["add2"]]] ==
             storage_id[jnode_row_ptr[nindex["reshapek"]]])
 
+def test_print_graph_ir():
+    x = sym.Variable("x", shape=(1, 1, 10, 20))
+    y = sym.conv2d(x + 1, name="y", channels=10, kernel_size=(3,3))
+    g = graph.create(y)
+    g = g.apply("InferShape")
+    ir1 = g.ir()
+    ir2 = g.ir(join_entry_attrs=["shape"])
+    assert("y_bias" in ir1)
+    assert("shape=" in ir2)
+
 
 if __name__ == "__main__":
+    test_print_graph_ir()
     test_json_pass_with_attr()
     test_graph_json_attr()
     test_json_pass()

From b37e5c20bea85c777dbaea480c7f9ea692d35f03 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 21 Sep 2017 18:46:29 -0700
Subject: [PATCH 478/949] [RUNTIME][COMPILER] Formal compiler pipeline, runtime
 wrapper module (#21)

* [RUNTIME][COMPILER] Formal compiler pipeline, runtime wrapper module

* more detailed comments
---
 nnvm/python/nnvm/compiler/__init__.py         |   2 +-
 nnvm/python/nnvm/compiler/build_module.py     | 126 ++++++++++++++++--
 nnvm/python/nnvm/compiler/graph_pass.py       |  78 -----------
 nnvm/python/nnvm/compiler/graph_util.py       |  80 +++++++++++
 nnvm/python/nnvm/runtime.py                   |  81 ++++++++++-
 nnvm/src/compiler/graph_fuse.cc               |   9 +-
 nnvm/src/compiler/packed_func_ext.cc          |   4 +
 nnvm/tests/python/compiler/test_build.py      |  59 +++++---
 nnvm/tests/python/compiler/test_graph_pass.py |   6 +-
 nnvm/tests/python/compiler/test_rpc_exec.py   |   2 +-
 .../compiler/test_simplify_batchnorm.py       |   4 +-
 nnvm/tests/python/compiler/test_top_level1.py |  15 ++-
 nnvm/tests/python/compiler/test_top_level2.py |   4 +-
 13 files changed, 340 insertions(+), 130 deletions(-)
 create mode 100644 nnvm/python/nnvm/compiler/graph_util.py

diff --git a/nnvm/python/nnvm/compiler/__init__.py b/nnvm/python/nnvm/compiler/__init__.py
index 993d0ed7c8a2..1d8b9219b4f7 100644
--- a/nnvm/python/nnvm/compiler/__init__.py
+++ b/nnvm/python/nnvm/compiler/__init__.py
@@ -4,7 +4,7 @@
 import tvm
 
 from . import build_module
-from . build_module import build, precompute_prune, _run_graph
+from . build_module import build, optimize, build_config
 
 from .. import symbol as _symbol
 from .. import graph as _graph
diff --git a/nnvm/python/nnvm/compiler/build_module.py b/nnvm/python/nnvm/compiler/build_module.py
index 3b053e2fd806..891a0b65729d 100644
--- a/nnvm/python/nnvm/compiler/build_module.py
+++ b/nnvm/python/nnvm/compiler/build_module.py
@@ -3,10 +3,74 @@
 from __future__ import absolute_import as _abs
 
 import tvm
-from . import graph_attr, graph_pass
+from . import graph_attr, graph_util
 from .. import graph as _graph
 from .. import runtime
 
+OPT_PASS_LEVEL = {
+    "SimplifyBatchNormInference": 2,
+    "PrecomputePrune": 2,
+    "OpFusion": 1
+}
+
+# List of optimization pass and level when switch on
+class BuildConfig(object):
+    """Configuration scope to set a build config option.
+
+    Parameters
+    ----------
+    kwargs
+        Keyword arguments of configurations to set.
+    """
+    current = None
+    defaults = {
+        "opt_level": 2,
+    }
+    def __init__(self, **kwargs):
+        self._old_scope = None
+        for k, _ in kwargs.items():
+            if k not in BuildConfig.defaults:
+                raise ValueError(
+                    "invalid argument %s, candidates are %s" % (k, BuildConfig.defaults.keys()))
+        self._attr = kwargs
+
+    def __getattr__(self, name):
+        if name not in self._attr:
+            return BuildConfig.defaults[name]
+        return self._attr[name]
+
+    def __enter__(self):
+        # pylint: disable=protected-access
+        self._old_scope = BuildConfig.current
+        attr = BuildConfig.current._attr.copy()
+        attr.update(self._attr)
+        self._attr = attr
+        BuildConfig.current = self
+        return self
+
+    def __exit__(self, ptype, value, trace):
+        assert self._old_scope
+        BuildConfig.current = self._old_scope
+
+
+BuildConfig.current = BuildConfig()
+
+def build_config(**kwargs):
+    """Configure the build behavior by setting config variables.
+
+    Parameters
+    ----------
+    opt_level: int, default=2
+        Optimization level. See OPT_PASS_LEVEL for level of each pass.
+
+    Returns
+    -------
+    config: BuildConfig
+        The build configuration
+    """
+    return BuildConfig(**kwargs)
+
+
 @tvm.register_func("nnvm.compiler.lower")
 def _lower(sch, inputs, func_name):
     f = tvm.lower(sch, inputs, name=func_name)
@@ -19,23 +83,45 @@ def _build(funcs, target):
     return tvm.build(funcs, target=target)
 
 
-def optimize(graph):
-    """Perform graph optimization
+def _update_shape_dtype(shape, dtype, params):
+    """Update shape dtype given params information"""
+    if not params:
+        return shape, dtype
+    shape = shape.copy()
+    shape.update({k : v.shape for k, v in params.items()})
+    if isinstance(dtype, str):
+        for k, v in params.items():
+            if v.dtype != dtype:
+                raise ValueError(
+                    "%s: dtype not expected %s vs %s" % (k, dtype, v.dtype))
+    else:
+        dtype = dtype.copy()
+        dtype.update({k : str(v.dtype) for k, v in params.items()})
+    return shape, dtype
+
+
+def optimize(graph, shape, dtype="float32"):
+    """Perform target and parameter invariant graph optimization.
 
     Parameters
     ----------
     graph : Graph
-        The graph to be used in lowering.
+        The graph to be used in optimized.
 
     Returns
     -------
     graph : Graph
-        The optimized execution graph.
+        The optimized graph.
     """
+    # pylint: disable=unused-argument
+    cfg = BuildConfig.current
+    if cfg.opt_level >= OPT_PASS_LEVEL["SimplifyBatchNormInference"]:
+        graph = graph_attr.set_shape_inputs(graph, shape)
+        graph = graph.apply(["InferShape", "SimplifyBatchNormInference"])
     return graph
 
 
-def build(graph, target, shape, dtype="float32"):
+def build(graph, target, shape, dtype="float32", params=None):
     """Build graph into runtime library.
 
     This is the final step of graph compilation.
@@ -54,6 +140,11 @@ def build(graph, target, shape, dtype="float32"):
     dtype : str or dict of str to str
         The input types to the graph
 
+    params : dict of str to NDArray
+        Input parameetrs to the graph that do not change
+        during inference time. Used for pre-compute
+        folding optimization.
+
     Returns
     -------
     graph : Graph
@@ -61,20 +152,33 @@ def build(graph, target, shape, dtype="float32"):
 
     libmod : tvm.Module
         The modue that comes with the execution graph
+
+    params : dict of str to NDArray
+        The updated parameters of graph if params is passed.
+        This can be different from the params passed in.
     """
     if not isinstance(target, str):
         raise TypeError("require target to be str")
     if not isinstance(shape, dict):
         raise TypeError("require shape to be dict")
-
+    cfg = BuildConfig.current
     graph = graph if isinstance(graph, _graph.Graph) else _graph.create(graph)
+    shape, dtype = _update_shape_dtype(shape, dtype, params)
+    # Apply optimization
+    graph = optimize(graph, shape, dtype)
+    # Precompute prune
+    if params and cfg.opt_level >= OPT_PASS_LEVEL["PrecomputePrune"]:
+        graph, params = precompute_prune(graph, params)
+        shape, dtype = _update_shape_dtype(shape, dtype, params)
+    # Operator Fusion and generatiom
     graph = graph_attr.set_shape_inputs(graph, shape)
     graph = graph_attr.set_dtype_inputs(graph, dtype)
     graph._set_json_attr("target", target, "str")
+    graph._set_json_attr("opt_level", cfg.opt_level, "int")
     graph = graph.apply("InferShape").apply("InferType")
     graph = graph.apply("GraphFusePartition").apply("GraphFuse")
     libmod = graph_attr._move_out_module(graph, "module")
-    return graph, libmod
+    return graph, libmod, params
 
 
 def _run_graph(graph, params):
@@ -98,9 +202,9 @@ def _run_graph(graph, params):
     dtype = {k : v.dtype for k, v in params.items()}
     target = "llvm"
     ctx = tvm.cpu(0)
-    _, oshape = graph_pass.infer_shape(graph, **shape)
-    _, odtype = graph_pass.infer_dtype(graph, **dtype)
-    graph, libmod = build(graph, target, shape, dtype)
+    _, oshape = graph_util.infer_shape(graph, **shape)
+    _, odtype = graph_util.infer_dtype(graph, **dtype)
+    graph, libmod, _ = build(graph, target, shape, dtype)
     m = runtime.create(graph, libmod, ctx)
     set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
     for k, v in params.items():
diff --git a/nnvm/python/nnvm/compiler/graph_pass.py b/nnvm/python/nnvm/compiler/graph_pass.py
index 3e98615d8ff2..a37e83a2c5c0 100644
--- a/nnvm/python/nnvm/compiler/graph_pass.py
+++ b/nnvm/python/nnvm/compiler/graph_pass.py
@@ -6,81 +6,3 @@
 - Composable API: break graph transformation pass as segments of small transformations.
 """
 from __future__ import absolute_import as _abs
-
-import tvm
-from . import graph_attr
-
-
-def infer_shape(graph, **shape):
-    """Infer the shape given the shape of inputs.
-
-    Parameters
-    ----------
-    graph : Graph
-        The graph to perform shape inference from
-
-    Returns
-    -------
-    in_shape : list of tuple
-         Shape of inputs
-
-    out_shape: list of tuple
-         Shape of outputs
-    """
-    graph = graph_attr.set_shape_inputs(graph, shape)
-    graph = graph.apply("InferShape")
-    shape = graph.json_attr("shape")
-    index = graph.index
-    input_shape = [shape[index.entry_id(x)] for x in index.input_names]
-    output_shape = [shape[index.entry_id(x)] for x in index.output_entries]
-    return input_shape, output_shape
-
-
-def infer_dtype(graph, **dtype):
-    """Infer the type given the typeS of inputs.
-
-    Parameters
-    ----------
-    graph : Graph
-        The graph to perform type inference from
-
-    Returns
-    -------
-    in_dtype : list of tuple
-         Dtype of inputs
-
-    out_dtype: list of tuple
-         Dtype of outputs
-    """
-    graph = graph_attr.set_dtype_inputs(graph, dtype)
-    graph = graph.apply("InferType")
-    dtype = graph.json_attr("dtype")
-    index = graph.index
-    input_dtype = [graph_attr.TCODE_TO_DTYPE[dtype[index.entry_id(x)]]
-                   for x in index.input_names]
-    output_dtype = [graph_attr.TCODE_TO_DTYPE[dtype[index.entry_id(x)]]
-                    for x in index.output_entries]
-    return input_dtype, output_dtype
-
-
-_deep_compare = tvm.get_global_func("nnvm.graph.DeepCompare")
-
-def check_graph_equal(grapha, graphb):
-    """Check if two graphs have equal structure.
-
-    Parameters
-    ----------
-    grapha : Graph
-        The first graph
-
-    graphb : Graph
-        The second graph
-
-    Raises
-    ------
-    ValueError
-        ValueError is raised with error message when graph not equal
-    """
-    err = _deep_compare(grapha, graphb)
-    if err:
-        raise ValueError("Graph compare error: " + err)
diff --git a/nnvm/python/nnvm/compiler/graph_util.py b/nnvm/python/nnvm/compiler/graph_util.py
new file mode 100644
index 000000000000..fcca00b0abe0
--- /dev/null
+++ b/nnvm/python/nnvm/compiler/graph_util.py
@@ -0,0 +1,80 @@
+# pylint: disable=invalid-name
+"""Utility function to get information from graph."""
+from __future__ import absolute_import as _abs
+
+import tvm
+from . import graph_attr
+
+def infer_shape(graph, **shape):
+    """Infer the shape given the shape of inputs.
+
+    Parameters
+    ----------
+    graph : Graph
+        The graph to perform shape inference from
+
+    Returns
+    -------
+    in_shape : list of tuple
+         Shape of inputs
+
+    out_shape: list of tuple
+         Shape of outputs
+    """
+    graph = graph_attr.set_shape_inputs(graph, shape)
+    graph = graph.apply("InferShape")
+    shape = graph.json_attr("shape")
+    index = graph.index
+    input_shape = [shape[index.entry_id(x)] for x in index.input_names]
+    output_shape = [shape[index.entry_id(x)] for x in index.output_entries]
+    return input_shape, output_shape
+
+
+def infer_dtype(graph, **dtype):
+    """Infer the type given the typeS of inputs.
+
+    Parameters
+    ----------
+    graph : Graph
+        The graph to perform type inference from
+
+    Returns
+    -------
+    in_dtype : list of tuple
+         Dtype of inputs
+
+    out_dtype: list of tuple
+         Dtype of outputs
+    """
+    graph = graph_attr.set_dtype_inputs(graph, dtype)
+    graph = graph.apply("InferType")
+    dtype = graph.json_attr("dtype")
+    index = graph.index
+    input_dtype = [graph_attr.TCODE_TO_DTYPE[dtype[index.entry_id(x)]]
+                   for x in index.input_names]
+    output_dtype = [graph_attr.TCODE_TO_DTYPE[dtype[index.entry_id(x)]]
+                    for x in index.output_entries]
+    return input_dtype, output_dtype
+
+
+_deep_compare = tvm.get_global_func("nnvm.graph.DeepCompare")
+
+def check_graph_equal(grapha, graphb):
+    """Check if two graphs have equal structure.
+
+    Parameters
+    ----------
+    grapha : Graph
+        The first graph
+
+    graphb : Graph
+        The second graph
+
+    Raises
+    ------
+    ValueError
+        ValueError is raised with error message when graph not equal
+    """
+    err = _deep_compare(grapha, graphb)
+    if err:
+        raise ValueError("Graph compare error: " + err)
diff --git a/nnvm/python/nnvm/runtime.py b/nnvm/python/nnvm/runtime.py
index 733514db71af..dd9866cb4b3a 100644
--- a/nnvm/python/nnvm/runtime.py
+++ b/nnvm/python/nnvm/runtime.py
@@ -2,6 +2,82 @@
 import tvm
 from tvm.contrib import rpc
 
+class Module(object):
+    """Wrapper runtime module.
+
+    This is a thin wrapper of the underlying TVM module.
+    you can also directly call set_input, run, and get_output
+    of underlying module functions
+
+    Parameters
+    ----------
+    tvm_module : tvm.Module
+        The interal tvm module
+    """
+    def __init__(self, tvm_module):
+        self.tvm_module = tvm_module
+        self._set_input = tvm_module["set_input"]
+        self._run = tvm_module["run"]
+        self._get_output = tvm_module["get_output"]
+
+    def set_input(self, key=None, value=None, **params):
+        """Set inputs to the module via kwargs
+
+        Parameters
+        ----------
+        key : int or str
+           The input key
+
+        value : the input value.
+           The input key
+
+        params : dict of str to NDArray
+           Additonal arguments
+        """
+        if key:
+            self._set_input(key, tvm.nd.array(value))
+        for k, v in params.items():
+            self._set_input(k, tvm.nd.array(v))
+        return self
+
+    def run(self, **input_dict):
+        """Run forward execution of the graph
+
+        Parameters
+        ----------
+        input_dict: dict of str to NDArray
+            List of input values to be feed to
+        """
+        if input_dict:
+            self.set_input(**input_dict)
+        self._run()
+
+    def get_output(self, index, out):
+        """Get index-th output to out
+
+        Parameters
+        ----------
+        index : int
+            The input index
+
+        out : tvm.NDArray
+            The output array container
+        """
+        self._get_output(index, out)
+        return out
+
+    def __getitem__(self, key):
+        """Get internal module function
+
+        Parameters
+        ----------
+        key : str
+            The key to the module.
+        """
+        return self.tvm_module[key]
+
+
+
 def create(graph, libmod, ctx):
     """Create a runtime executor module given the graph and module.
 
@@ -30,7 +106,6 @@ def create(graph, libmod, ctx):
         hmod = rpc._ModuleHandle(libmod)
         fcreate = ctx._rpc_sess.get_function("nnvm.runtime.remote_create")
         device_type = device_type % rpc.RPC_SESS_MASK
-        return fcreate(json_str, hmod, device_type, device_id)
-
+        return Module(fcreate(json_str, hmod, device_type, device_id))
     fcreate = tvm.get_global_func("nnvm.runtime.create")
-    return fcreate(json_str, libmod, device_type, device_id)
+    return Module(fcreate(json_str, libmod, device_type, device_id))
diff --git a/nnvm/src/compiler/graph_fuse.cc b/nnvm/src/compiler/graph_fuse.cc
index 9496e110ba64..acf0f5677187 100644
--- a/nnvm/src/compiler/graph_fuse.cc
+++ b/nnvm/src/compiler/graph_fuse.cc
@@ -41,6 +41,11 @@ DLDataType GetDLType(int type_flag) {
 nnvm::Graph GraphFusePartition(nnvm::Graph g) {
   // setup ref counter
   const IndexedGraph& idx = g.indexed_graph();
+  int opt_level = 2;
+  if (g.attrs.count("opt_level") != 0) {
+    opt_level = g.MoveCopyAttr<int>("opt_level");
+  }
+
   // Get attributes from the graph
   const ShapeVector& shape_vec = g.GetAttr<ShapeVector>("shape");
   const DTypeVector& dtype_vec = g.GetAttr<DTypeVector>("dtype");
@@ -65,7 +70,7 @@ nnvm::Graph GraphFusePartition(nnvm::Graph g) {
     // this line will realize all the outputs
     ref_count[e.node_id] += 2;
   }
-  // Pattern fo the subgraph
+  // Pattern for the subgraph
   std::vector<TOpPattern> pattern_vec(idx.num_nodes(),  kExtern);
   // Whether node can be fused to parent.
   std::vector<FuseRule> fuse_vec(idx.num_nodes(), FuseRule::kUknown);
@@ -123,7 +128,7 @@ nnvm::Graph GraphFusePartition(nnvm::Graph g) {
     }
 
     pattern_vec[nid] = pt;
-    if (ref_count[nid] > 1) {
+    if (ref_count[nid] > 1 || opt_level < 1) {
       fuse_vec[nid] = FuseRule::kRealize;
       if (master_vec[nid] == -1) {
         master_vec[nid] = nid;
diff --git a/nnvm/src/compiler/packed_func_ext.cc b/nnvm/src/compiler/packed_func_ext.cc
index 5444ece9c110..e67f74312605 100644
--- a/nnvm/src/compiler/packed_func_ext.cc
+++ b/nnvm/src/compiler/packed_func_ext.cc
@@ -20,6 +20,10 @@ TVM_REGISTER_EXT_TYPE(nnvm::compiler::AttrDict);
 }  // namespace runtime
 }  // namespace tvm
 
+namespace nnvm {
+DMLC_JSON_ENABLE_ANY(int, int);
+}  // namespace nnvm
+
 namespace nnvm {
 namespace compiler {
 
diff --git a/nnvm/tests/python/compiler/test_build.py b/nnvm/tests/python/compiler/test_build.py
index 3abe94913a8f..bb10e8400bb1 100644
--- a/nnvm/tests/python/compiler/test_build.py
+++ b/nnvm/tests/python/compiler/test_build.py
@@ -4,6 +4,7 @@
 import nnvm.symbol as sym
 import nnvm.compiler
 import nnvm.runtime
+from nnvm.compiler.build_module import _run_graph, precompute_prune
 
 def test_compile():
     x = sym.Variable("x")
@@ -12,23 +13,34 @@ def test_compile():
     shape = (10, 128)
     dtype = tvm.float32
     shape_dict = {"x": shape, "y": shape}
+    def verify(graph, lib):
+        m = nnvm.runtime.create(graph, lib, tvm.cpu(0))
+        # get member functions
+        set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
+        na = tvm.nd.array(np.random.uniform(size=shape).astype(dtype))
+        nb = tvm.nd.array(np.random.uniform(size=shape).astype(dtype))
+        # set inputs
+        set_input("x", na)
+        set_input("y", nb)
+        # execute
+        run()
+        # get outputs
+        out = tvm.nd.empty(shape, dtype)
+        get_output(0, out)
+        np.testing.assert_allclose(
+            out.asnumpy(), np.exp(na.asnumpy() + nb.asnumpy()))
+
+    graph, lib, _ = nnvm.compiler.build(z, "llvm", shape_dict)
+    assert graph.index.num_nodes == 3
+    verify(graph, lib)
+
+    with nnvm.compiler.build_config(opt_level=0):
+        graph, lib, _ = nnvm.compiler.build(z, "llvm", shape_dict)
+        # print(graph.ir())
+        assert graph.index.num_nodes == 4
+        verify(graph, lib)
+
 
-    graph, lib = nnvm.compiler.build(z, "llvm", shape_dict)
-    m = nnvm.runtime.create(graph, lib, tvm.cpu(0))
-    # get member functions
-    set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
-    na = tvm.nd.array(np.random.uniform(size=shape).astype(dtype))
-    nb = tvm.nd.array(np.random.uniform(size=shape).astype(dtype))
-    # set inputs
-    set_input("x", na)
-    set_input("y", nb)
-    # execute
-    run()
-    # get outputs
-    out = tvm.nd.empty(shape, dtype)
-    get_output(0, out)
-    np.testing.assert_allclose(
-        out.asnumpy(), np.exp(na.asnumpy() + nb.asnumpy()))
 
 
 def test_run():
@@ -39,7 +51,7 @@ def test_run():
     dtype = tvm.float32
     nx = tvm.nd.array(np.random.uniform(size=shape).astype(dtype))
     ny = tvm.nd.array(np.random.uniform(size=shape).astype(dtype))
-    res = nnvm.compiler._run_graph(z, {"x": nx, "y": ny})
+    res = _run_graph(z, {"x": nx, "y": ny})
     np.testing.assert_allclose(
         res[0].asnumpy(), np.exp(nx.asnumpy() + ny.asnumpy()))
 
@@ -53,11 +65,16 @@ def test_precompute_prune():
     nx = tvm.nd.array(np.random.uniform(size=shape).astype(dtype))
     ny = tvm.nd.array(np.random.uniform(size=shape).astype(dtype))
     params = {"x": nx}
-    graph, pdict = nnvm.compiler.precompute_prune(z, params)
-    pdict["y"] = ny
-    res = nnvm.compiler._run_graph(z, pdict)
+    graph, lib, params = nnvm.compiler.build(
+        z, "llvm", shape={"y": ny.shape}, params=params)
+    assert graph.index.num_nodes == 3
+    m = nnvm.runtime.create(graph, lib, tvm.cpu(0))
+    params["y"] = ny
+    res = tvm.nd.empty(shape)
+    m.run(**params)
+    out = m.get_output(0, out=res)
     np.testing.assert_allclose(
-        res[0].asnumpy(), nx.asnumpy() + 1 + ny.asnumpy())
+        res.asnumpy(), nx.asnumpy() + 1 + ny.asnumpy())
 
 
 if __name__ == "__main__":
diff --git a/nnvm/tests/python/compiler/test_graph_pass.py b/nnvm/tests/python/compiler/test_graph_pass.py
index 9f2c36a1d3f9..ec5ab0479389 100644
--- a/nnvm/tests/python/compiler/test_graph_pass.py
+++ b/nnvm/tests/python/compiler/test_graph_pass.py
@@ -2,16 +2,16 @@
 import nnvm
 import nnvm.compiler
 from nnvm import symbol as sym
-from nnvm.compiler import graph_pass, graph_attr
+from nnvm.compiler import graph_util, graph_attr
 
 def test_infer_attr():
     x = sym.Variable("x")
     y = x * 2
     g = nnvm.graph.create(y)
-    ishape, oshape = graph_pass.infer_shape(g, x=(10,20))
+    ishape, oshape = graph_util.infer_shape(g, x=(10,20))
     assert tuple(oshape[0]) == (10, 20)
 
-    itype, otype = graph_pass.infer_dtype(g, x="float32")
+    itype, otype = graph_util.infer_dtype(g, x="float32")
     assert otype[0] == "float32"
 
 if __name__ == "__main__":
diff --git a/nnvm/tests/python/compiler/test_rpc_exec.py b/nnvm/tests/python/compiler/test_rpc_exec.py
index 89f67d5945e6..4a94a1d7686e 100644
--- a/nnvm/tests/python/compiler/test_rpc_exec.py
+++ b/nnvm/tests/python/compiler/test_rpc_exec.py
@@ -19,7 +19,7 @@ def test_rpc_executor():
     tmp = util.tempdir()
     lib_name  = tmp.relpath("net.o")
 
-    graph, lib = nnvm.compiler.build(z, "llvm", shape_dict)
+    graph, lib, _ = nnvm.compiler.build(z, "llvm", shape_dict)
     # save module
     lib.save(lib_name)
     remote = rpc.connect(host, port)
diff --git a/nnvm/tests/python/compiler/test_simplify_batchnorm.py b/nnvm/tests/python/compiler/test_simplify_batchnorm.py
index 67d582769ef2..ec6dfb86ac47 100644
--- a/nnvm/tests/python/compiler/test_simplify_batchnorm.py
+++ b/nnvm/tests/python/compiler/test_simplify_batchnorm.py
@@ -1,7 +1,7 @@
 """Unittest cases for simplify batch_norm"""
 import nnvm
 from nnvm import symbol as sym
-from nnvm.compiler import graph_pass, graph_attr
+from nnvm.compiler import graph_util, graph_attr
 
 def test_simplify_batchnorm():
     def simple_bn(x, gamma, beta, moving_mean, moving_var,
@@ -40,7 +40,7 @@ def check(dim, axis, nstep):
         # Some prints for debug
         # print(g1.ir())
         # assert graph equals as expected
-        graph_pass.check_graph_equal(g1, g2)
+        graph_util.check_graph_equal(g1, g2)
 
     check(2, 1, 1)
     check(4, 0, 3)
diff --git a/nnvm/tests/python/compiler/test_top_level1.py b/nnvm/tests/python/compiler/test_top_level1.py
index 88aeb3459886..14108d18db0e 100644
--- a/nnvm/tests/python/compiler/test_top_level1.py
+++ b/nnvm/tests/python/compiler/test_top_level1.py
@@ -26,7 +26,7 @@ def test_relu():
     dtype = "float32"
     dshape = (1, 3, 32, 32)
     oshape = dshape
-    graph, lib = nnvm.compiler.build(y, default_target(), {"x": dshape})
+    graph, lib, _ = nnvm.compiler.build(y, default_target(), {"x": dshape})
     m = nnvm.runtime.create(graph, lib, default_ctx())
     # get member functions
     set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
@@ -48,7 +48,7 @@ def test_exp():
     dtype = "float32"
     dshape = (1, 3, 32, 32)
     oshape = dshape
-    graph, lib = nnvm.compiler.build(y, default_target(), {"x": dshape})
+    graph, lib, _ = nnvm.compiler.build(y, default_target(), {"x": dshape})
     m = nnvm.runtime.create(graph, lib, default_ctx())
     # get member functions
     set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
@@ -70,7 +70,8 @@ def test_log():
     dtype = "float32"
     dshape = (1, 3, 32, 32)
     oshape = dshape
-    graph, lib = nnvm.compiler.build(y, default_target(), {"x": dshape})
+    with nnvm.compiler.build_config(opt_level=1):
+        graph, lib, _ = nnvm.compiler.build(y, default_target(), {"x": dshape})
     m = nnvm.runtime.create(graph, lib, default_ctx())
     # get member functions
     set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
@@ -92,7 +93,8 @@ def test_tanh():
     dtype = "float32"
     dshape = (1, 3, 32, 32)
     oshape = dshape
-    graph, lib = nnvm.compiler.build(y, default_target(), {"x": dshape})
+    with nnvm.compiler.build_config(opt_level=1):
+        graph, lib, _ = nnvm.compiler.build(y, default_target(), {"x": dshape})
     m = nnvm.runtime.create(graph, lib, default_ctx())
     # get member functions
     set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
@@ -114,7 +116,7 @@ def test_sigmoid():
     dtype = "float32"
     dshape = (1, 3, 32, 32)
     oshape = dshape
-    graph, lib = nnvm.compiler.build(y, default_target(), {"x": dshape})
+    graph, lib, _ = nnvm.compiler.build(y, default_target(), {"x": dshape})
     m = nnvm.runtime.create(graph, lib, default_ctx())
     # get member functions
     set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
@@ -136,7 +138,8 @@ def test_softmax():
     dtype = "float32"
     dshape = (10, 1000)
     oshape = dshape
-    graph, lib = nnvm.compiler.build(y, default_target(), {"x": dshape})
+    with nnvm.compiler.build_config(opt_level=1):
+        graph, lib, _ = nnvm.compiler.build(y, default_target(), {"x": dshape})
     m = nnvm.runtime.create(graph, lib, default_ctx())
     # get member functions
     set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
diff --git a/nnvm/tests/python/compiler/test_top_level2.py b/nnvm/tests/python/compiler/test_top_level2.py
index 133b22fdcb65..32d84158b336 100644
--- a/nnvm/tests/python/compiler/test_top_level2.py
+++ b/nnvm/tests/python/compiler/test_top_level2.py
@@ -29,7 +29,7 @@ def test_conv2d():
     kshape = (10, 3, 3, 3)
     oshape = (1, 10, 18, 18)
     shape_dict = {"x": dshape}
-    graph, lib = nnvm.compiler.build(y, default_target(), shape_dict)
+    graph, lib, _ = nnvm.compiler.build(y, default_target(), shape_dict)
     m = nnvm.runtime.create(graph, lib, default_ctx())
     # get member functions
     set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
@@ -57,7 +57,7 @@ def test_grouped_conv2d():
     kshape = (32, 1, 3, 3)
     oshape = (1, 32, 18, 18)
     shape_dict = {"x": dshape}
-    graph, lib = nnvm.compiler.build(y, default_target(), shape_dict)
+    graph, lib, _ = nnvm.compiler.build(y, default_target(), shape_dict)
     m = nnvm.runtime.create(graph, lib, default_ctx())
     # get member functions
     set_input, run, get_output = m["set_input"], m["run"], m["get_output"]

From 02a60d02d5d6a6d505554efa9871369dcb88a212 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 21 Sep 2017 21:52:08 -0700
Subject: [PATCH 479/949] [TOP] Add dense, batchnorm (#22)

* [TOP] Add dense, batchnorm

* update tvm
---
 nnvm/include/nnvm/compiler/op_attr_types.h    |   7 +-
 nnvm/python/nnvm/compiler/build_module.py     |  13 +-
 nnvm/python/nnvm/compiler/registry.py         |   4 +-
 nnvm/python/nnvm/top/__init__.py              |   1 +
 nnvm/python/nnvm/top/nn.py                    |  49 +++-
 nnvm/python/nnvm/top/tensor.py                | 121 +++++++--
 nnvm/python/nnvm/top/transform.py             |  31 +++
 nnvm/src/compiler/graph_fuse.cc               |  17 +-
 nnvm/src/compiler/packed_func_ext.cc          |   6 +-
 nnvm/src/compiler/simplify_batch_norm.cc      |  19 +-
 nnvm/src/pass/print_graph_ir.cc               |   2 +-
 .../compiler/test_simplify_batchnorm.py       |   9 +-
 nnvm/tests/python/compiler/test_top_level1.py | 249 +++++++++++-------
 nnvm/tests/python/compiler/test_top_level2.py |  86 +++---
 14 files changed, 401 insertions(+), 213 deletions(-)
 create mode 100644 nnvm/python/nnvm/top/transform.py

diff --git a/nnvm/include/nnvm/compiler/op_attr_types.h b/nnvm/include/nnvm/compiler/op_attr_types.h
index c77720da1669..8381733c33a1 100644
--- a/nnvm/include/nnvm/compiler/op_attr_types.h
+++ b/nnvm/include/nnvm/compiler/op_attr_types.h
@@ -44,11 +44,14 @@ using TOpPattern = int;
  * \brief Computation description interface
  * \param attrs The attribute of the node.
  * \param inputs The input tensors(placeholders)
+ * \param out_info Tensors holding shape/type information about output,
+ &                 these are always placeholders.
  * \return The output description of the tensor.
  */
 using FTVMCompute = std::function<
-  Array<Tensor>
-  (const NodeAttrs& attrs, const Array<Tensor>& inputs)>;
+  Array<Tensor>(const NodeAttrs& attrs,
+                const Array<Tensor>& inputs,
+                const Array<Tensor>& out_info)>;
 
 /*!
  * \brief Build the computation schedule for
diff --git a/nnvm/python/nnvm/compiler/build_module.py b/nnvm/python/nnvm/compiler/build_module.py
index 891a0b65729d..04b1f0a2e96f 100644
--- a/nnvm/python/nnvm/compiler/build_module.py
+++ b/nnvm/python/nnvm/compiler/build_module.py
@@ -115,9 +115,12 @@ def optimize(graph, shape, dtype="float32"):
     """
     # pylint: disable=unused-argument
     cfg = BuildConfig.current
+    graph = graph_attr.set_shape_inputs(graph, shape)
+    graph = graph.apply("InferShape")
+    if graph.json_attr("shape_num_unknown_nodes"):
+        raise ValueError("InferShape fails..")
     if cfg.opt_level >= OPT_PASS_LEVEL["SimplifyBatchNormInference"]:
-        graph = graph_attr.set_shape_inputs(graph, shape)
-        graph = graph.apply(["InferShape", "SimplifyBatchNormInference"])
+        graph = graph.apply("SimplifyBatchNormInference")
     return graph
 
 
@@ -164,6 +167,12 @@ def build(graph, target, shape, dtype="float32", params=None):
     cfg = BuildConfig.current
     graph = graph if isinstance(graph, _graph.Graph) else _graph.create(graph)
     shape, dtype = _update_shape_dtype(shape, dtype, params)
+    # Initial pass do shape type inference
+    ishape, _ = graph_util.infer_shape(graph, **shape)
+    shape.update(zip(graph.index.input_names, ishape))
+    if not isinstance(dtype, str):
+        idtype, _ = graph_util.infer_dtype(graph, **dtype)
+        dtype.update(zip(graph.index.input_names, idtype))
     # Apply optimization
     graph = optimize(graph, shape, dtype)
     # Precompute prune
diff --git a/nnvm/python/nnvm/compiler/registry.py b/nnvm/python/nnvm/compiler/registry.py
index d54b22d12681..c8094b1d345f 100644
--- a/nnvm/python/nnvm/compiler/registry.py
+++ b/nnvm/python/nnvm/compiler/registry.py
@@ -5,8 +5,10 @@
 class OpPattern(object):
     ELEM_WISE = 0
     BROADCAST = 1
+    # Complex means we can fuse elemwise to it
     COMPLEX = 2
-    EXTERN = 2
+    # Extern means the op is not fusable
+    EXTERN = 3
 
 _register_compute = tvm.get_global_func("nnvm._register_compute")
 _register_schedule = tvm.get_global_func("nnvm._register_schedule")
diff --git a/nnvm/python/nnvm/top/__init__.py b/nnvm/python/nnvm/top/__init__.py
index 21bf9ba33f43..11e776f1b0dd 100644
--- a/nnvm/python/nnvm/top/__init__.py
+++ b/nnvm/python/nnvm/top/__init__.py
@@ -2,3 +2,4 @@
 from .attr_dict import AttrDict
 from . import tensor
 from . import nn
+from . import transform
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index b4c0c44f0edc..5b0dfe2fe145 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -1,30 +1,37 @@
+# pylint: disable=invalid-name, unused-argument
 """Definition of nn ops"""
 from __future__ import absolute_import
 
 import tvm
 import topi
 from topi.util import get_const_int
-from .tensor import schedule_elemwise
+from .tensor import _fschedule_broadcast
 from ..compiler import registry as reg
 from ..compiler import OpPattern
 
 # relu
 @reg.register_compute("relu")
-def compute_relu(_, inputs):
+def compute_relu(attrs, inputs, _):
     """Compute definition of relu"""
     return topi.nn.relu(inputs[0])
 
-@reg.register_schedule("relu")
-def schedule_relu(_, outs, target):
-    """Schedule definition of relu"""
-    return schedule_elemwise(_, outs, target)
-
+reg.register_schedule("relu", _fschedule_broadcast)
 reg.register_pattern("relu", OpPattern.ELEM_WISE)
 
 
+# flatten
+@reg.register_compute("flatten")
+def compute_flatten(attrs, inputs, _):
+    """Compute definition of flatten"""
+    return topi.nn.flatten(inputs[0])
+
+reg.register_schedule("flatten", _fschedule_broadcast)
+reg.register_pattern("flatten", OpPattern.COMPLEX)
+
+
 # softmax
 @reg.register_compute("softmax")
-def compute_softmax(attrs, inputs):
+def compute_softmax(attrs, inputs, _):
     """Compute definition of softmax"""
     axis = attrs.get_int("axis")
     assert axis == -1, "only support axis == -1 for now"
@@ -38,12 +45,34 @@ def schedule_softmax(_, outs, target):
     # naive schedule
     return tvm.create_schedule([x.op for x in outs])
 
-reg.register_pattern("softmax", OpPattern.COMPLEX)
+# Mark softmax as extern as we do not fuse it in call cases
+reg.register_pattern("softmax", OpPattern.EXTERN)
+
+
+# dense
+@reg.register_compute("dense")
+def compute_dense(attrs, inputs, _):
+    """Compute definition of dense"""
+    if attrs.get_bool("use_bias"):
+        return topi.nn.fully_connected_with_bias(
+            inputs[0], inputs[1], inputs[2])
+    return topi.nn.fully_connected(inputs[0], inputs[1])
+
+@reg.register_schedule("dense")
+def schedule_dense(_, outs, target):
+    """Schedule definition of dense"""
+    if target == "cuda":
+        raise ValueError("fully_connected not yet implemented")
+    # naive schedule
+    return tvm.create_schedule([x.op for x in outs])
+
+# register extern for now, change me when fusion is enabled.
+reg.register_pattern("dense", OpPattern.EXTERN)
 
 
 # conv
 @reg.register_compute("conv2d")
-def compute_conv2d(attrs, inputs):
+def compute_conv2d(attrs, inputs, _):
     """Compute definition of conv2d"""
     padding = attrs.get_int_tuple("padding")
     strides = attrs.get_int_tuple("strides")
diff --git a/nnvm/python/nnvm/top/tensor.py b/nnvm/python/nnvm/top/tensor.py
index c49aeae0d19f..0259e99b6aee 100644
--- a/nnvm/python/nnvm/top/tensor.py
+++ b/nnvm/python/nnvm/top/tensor.py
@@ -1,4 +1,4 @@
-# pylint: disable=invalid-name
+# pylint: disable=invalid-name, unused-argument
 """Tensor ops"""
 from __future__ import absolute_import
 
@@ -8,15 +8,6 @@
 from ..compiler import registry as reg
 from ..compiler import OpPattern
 
-def schedule_elemwise(_, outs, target):
-    """Generic schedule for elemwise operation"""
-    if target == "cuda":
-        return topi.cuda.schedule_elemwise(outs)
-    assert target.startswith("llvm")
-    s = tvm.create_schedule([x.op for x in outs])
-    tvm.schedule.AutoInlineInjective(s)
-    return s
-
 def _schedule_broadcast(_, outs, target):
     """Generic schedule for binary bcast"""
     if target == "cuda":
@@ -29,7 +20,7 @@ def _schedule_broadcast(_, outs, target):
 def _compute_binary_scalar(f):
     """auxiliary function"""
     @tvm.tag_scope("ewise")
-    def _compute(attrs, x):
+    def _compute(attrs, x, _):
         x = x[0]
         scalar = attrs.get_float("scalar")
         scalar = tvm.const(scalar, x.dtype)
@@ -37,58 +28,132 @@ def _compute(attrs, x):
     return _compute
 
 
+def _compute_unary(f):
+    """auxiliary function"""
+    def _compute(attrs, x, _):
+        return f(x[0])
+    return _compute
+
+
+def _compute_binary(f):
+    """auxiliary function"""
+    def _compute(attrs, x, _):
+        return f(x[0], x[1])
+    return _compute
+
+
 _fschedule_broadcast = tvm.convert(_schedule_broadcast)
 
 # exp
-reg.register_compute("exp",
-                     lambda _, x: topi.exp(x[0]))
+reg.register_compute("exp", _compute_unary(topi.exp))
 reg.register_pattern("exp", OpPattern.ELEM_WISE)
 reg.register_schedule("exp", _fschedule_broadcast)
 
+# sqrt
+reg.register_compute("sqrt", _compute_unary(topi.sqrt))
+reg.register_pattern("sqrt", OpPattern.ELEM_WISE)
+reg.register_schedule("sqrt", _fschedule_broadcast)
+
 # log
-reg.register_compute("log",
-                     lambda _, x: topi.log(x[0]))
+reg.register_compute("log", _compute_unary(topi.log))
 reg.register_pattern("log", OpPattern.ELEM_WISE)
 reg.register_schedule("log", _fschedule_broadcast)
 
 # tanh
-reg.register_compute("tanh",
-                     lambda _, x: topi.tanh(x[0]))
+reg.register_compute("tanh", _compute_unary(topi.tanh))
 reg.register_pattern("tanh", OpPattern.ELEM_WISE)
 reg.register_schedule("tanh", _fschedule_broadcast)
 
+# negative
+reg.register_compute("negative", _compute_unary(topi.negative))
+reg.register_pattern("negative", OpPattern.ELEM_WISE)
+reg.register_schedule("negative", _fschedule_broadcast)
+
 # sigmoid
-reg.register_compute("sigmoid",
-                     lambda _, x: topi.sigmoid(x[0]))
+reg.register_compute("sigmoid", _compute_unary(topi.sigmoid))
 reg.register_pattern("sigmoid", OpPattern.ELEM_WISE)
 reg.register_schedule("sigmoid", _fschedule_broadcast)
 
-# add scalar
+# add_scalar
 reg.register_compute("__add_scalar__",
                      _compute_binary_scalar(lambda x, y: x + y))
 reg.register_pattern("__add_scalar__", OpPattern.ELEM_WISE)
 reg.register_schedule("__add_scalar__", _fschedule_broadcast)
 
+# sub_calar
+reg.register_compute("__sub_scalar__",
+                     _compute_binary_scalar(lambda x, y: x - y))
+reg.register_pattern("__sub_scalar__", OpPattern.ELEM_WISE)
+reg.register_schedule("__sub_scalar__", _fschedule_broadcast)
+
+# rsub_scalar
+reg.register_compute("__rsub_scalar__",
+                     _compute_binary_scalar(lambda x, y: y - x))
+reg.register_pattern("__rsub_scalar__", OpPattern.ELEM_WISE)
+reg.register_schedule("__rsub_scalar__", _fschedule_broadcast)
+
+# mul_scalar
+reg.register_compute("__mul_scalar__",
+                     _compute_binary_scalar(lambda x, y: x * y))
+reg.register_pattern("__mul_scalar__", OpPattern.ELEM_WISE)
+reg.register_schedule("__mul_scalar__", _fschedule_broadcast)
+
+# div_scalar
+reg.register_compute("__div_scalar__",
+                     _compute_binary_scalar(lambda x, y: x / y))
+reg.register_pattern("__div_scalar__", OpPattern.ELEM_WISE)
+reg.register_schedule("__div_scalar__", _fschedule_broadcast)
+
+# rdiv_scalar
+reg.register_compute("__rdiv_scalar__",
+                     _compute_binary_scalar(lambda x, y: y / x))
+reg.register_pattern("__rdiv_scalar__", OpPattern.ELEM_WISE)
+reg.register_schedule("__rdiv_scalar__", _fschedule_broadcast)
+
+# elemwise_add
+reg.register_compute("elemwise_add", _compute_binary(topi.broadcast_add))
+reg.register_pattern("elemwise_add", OpPattern.BROADCAST)
+reg.register_schedule("elemwise_add", _fschedule_broadcast)
+
+# elemwise_sub
+reg.register_compute("elemwise_sub", _compute_binary(topi.broadcast_sub))
+reg.register_pattern("elemwise_sub", OpPattern.BROADCAST)
+reg.register_schedule("elemwise_sub", _fschedule_broadcast)
+
+# elemwise_mul
+reg.register_compute("elemwise_mul", _compute_binary(topi.broadcast_mul))
+reg.register_pattern("elemwise_mul", OpPattern.BROADCAST)
+reg.register_schedule("elemwise_mul", _fschedule_broadcast)
+
+# elemwise_div
+reg.register_compute("elemwise_div", _compute_binary(topi.broadcast_div))
+reg.register_pattern("elemwise_div", OpPattern.BROADCAST)
+reg.register_schedule("elemwise_div", _fschedule_broadcast)
+
 # broadcast_add
-reg.register_compute("broadcast_add",
-                     lambda _, x: topi.broadcast_add(x[0], x[1]))
+reg.register_compute("broadcast_add", _compute_binary(topi.broadcast_add))
 reg.register_pattern("broadcast_add", OpPattern.BROADCAST)
 reg.register_schedule("broadcast_add", _fschedule_broadcast)
 
 # broadcast_sub
-reg.register_compute("broadcast_sub",
-                     lambda _, x: topi.broadcast_sub(x[0], x[1]))
+reg.register_compute("broadcast_sub", _compute_binary(topi.broadcast_sub))
 reg.register_pattern("broadcast_sub", OpPattern.BROADCAST)
 reg.register_schedule("broadcast_sub", _fschedule_broadcast)
 
 # broadcast_mul
-reg.register_compute("broadcast_mul",
-                     lambda _, x: topi.broadcast_mul(x[0], x[1]))
+reg.register_compute("broadcast_mul", _compute_binary(topi.broadcast_mul))
 reg.register_pattern("broadcast_mul", OpPattern.BROADCAST)
 reg.register_schedule("broadcast_mul", _fschedule_broadcast)
 
 # broadcast_div
-reg.register_compute("broadcast_div",
-                     lambda _, x: topi.broadcast_div(x[0], x[1]))
+reg.register_compute("broadcast_div", _compute_binary(topi.broadcast_div))
 reg.register_pattern("broadcast_div", OpPattern.BROADCAST)
 reg.register_schedule("broadcast_div", _fschedule_broadcast)
+
+# broadcast_to
+@reg.register_compute("broadcast_to")
+def compute_softmax(attrs, inputs, out_info):
+    """Compute definition of softmax"""
+    return topi.broadcast_to(inputs[0], shape=out_info[0].shape)
+reg.register_pattern("broadcast_to", OpPattern.BROADCAST)
+reg.register_schedule("broadcast_to", _fschedule_broadcast)
diff --git a/nnvm/python/nnvm/top/transform.py b/nnvm/python/nnvm/top/transform.py
new file mode 100644
index 000000000000..89e3c64a05ce
--- /dev/null
+++ b/nnvm/python/nnvm/top/transform.py
@@ -0,0 +1,31 @@
+# pylint: disable=invalid-name, unused-argument
+"""Tensor transformation ops"""
+from __future__ import absolute_import
+
+import tvm
+from .tensor import _fschedule_broadcast
+from ..compiler import registry as reg
+from ..compiler import OpPattern
+
+# Need add reshape, transpose
+
+def _flatten_index(indices, shape):
+    """flatten the index to 1D"""
+    idx = 0
+    for i, value in enumerate(shape):
+        if i != 0:
+            idx *= value
+        idx = idx + indices[i]
+    return idx
+
+# reshape
+@reg.register_compute("reshape")
+def compute_reshape(attrs, inputs, out_info):
+    """Compute definition of softmax"""
+    # TODO(sxj) add support for general reshape
+    assert len(inputs[0].shape) == 1, "Only support 1d input for now"
+    oshape = out_info[0].shape
+    x = inputs[0]
+    return tvm.compute(oshape, lambda *i: x(_flatten_index(i, oshape)))
+reg.register_pattern("reshape", OpPattern.COMPLEX)
+reg.register_schedule("reshape", _fschedule_broadcast)
diff --git a/nnvm/src/compiler/graph_fuse.cc b/nnvm/src/compiler/graph_fuse.cc
index acf0f5677187..1daa5fd11394 100644
--- a/nnvm/src/compiler/graph_fuse.cc
+++ b/nnvm/src/compiler/graph_fuse.cc
@@ -261,7 +261,7 @@ nnvm::Graph GraphFuse(nnvm::Graph g) {
     if (inode.source->is_variable()) continue;
     int root_id = group_vec[nid];
     FuseEntry& fe = fuse_vec[root_id];
-    Array<Tensor> inputs;
+    Array<Tensor> inputs, out_info;
     // input loading
     for (const auto& e : inode.inputs) {
       if (group_vec[e.node_id] != root_id) {
@@ -274,11 +274,21 @@ nnvm::Graph GraphFuse(nnvm::Graph g) {
         inputs.push_back(t);
       }
     }
+    // output hint
+    for (uint32_t i = 0; i < inode.source->num_outputs(); ++i) {
+      Array<Expr> shape;
+      for (int64_t x : shape_vec[idx.entry_id(nid, i)]) {
+        CHECK_LE(x, static_cast<int64_t>(std::numeric_limits<int>::max()));
+        shape.push_back(make_const(Int(32), x));
+      }
+      out_info.push_back(
+          placeholder(shape,
+                      TVMType2Type(dltype_vec[idx.entry_id(nid, i)])));
+    }
     // get default
     Array<Tensor> out = fcompute[inode.source->op()](
-        inode.source->attrs, inputs);
+        inode.source->attrs, inputs, out_info);
     CHECK_EQ(out.size(), inode.source->num_outputs());
-
     // schedule on root node, and use master's schedule
     if (nid != root_id) {
       for (uint32_t index = 0; index < inode.source->num_outputs(); ++index) {
@@ -312,6 +322,7 @@ nnvm::Graph GraphFuse(nnvm::Graph g) {
       }
     }
   }
+
   tvm::runtime::Module module = fbuild(funcs, target);
   // Final step: Remap the node, with given attribute
   const nnvm::Op* tvm_op = nnvm::Op::Get("tvm_op");
diff --git a/nnvm/src/compiler/packed_func_ext.cc b/nnvm/src/compiler/packed_func_ext.cc
index e67f74312605..fed31be4033e 100644
--- a/nnvm/src/compiler/packed_func_ext.cc
+++ b/nnvm/src/compiler/packed_func_ext.cc
@@ -67,9 +67,11 @@ TVM_REGISTER_GLOBAL("nnvm._register_compute")
     // Intentionally copy and not de-allocate it, to avoid free pyobject during shutdown
     PackedFunc* f = new PackedFunc(args[1].operator PackedFunc());
     Op& op = ::dmlc::Registry<nnvm::Op>::Get()->__REGISTER_OR_GET__(args[0]);
-    auto fcompute = [f](const NodeAttrs& attrs, const Array<Tensor>& inputs)
+    auto fcompute = [f](const NodeAttrs& attrs,
+                        const Array<Tensor>& inputs,
+                        const Array<Tensor>& out_info)
         -> Array<Tensor> {
-      TVMRetValue ret = (*f)(GetAttrDict(attrs), inputs);
+      TVMRetValue ret = (*f)(GetAttrDict(attrs), inputs, out_info);
       if ((*ret.ptr<std::shared_ptr<tvm::Node> >())->derived_from<tvm::TensorNode>()) {
         return {ret.operator Tensor()};
       } else {
diff --git a/nnvm/src/compiler/simplify_batch_norm.cc b/nnvm/src/compiler/simplify_batch_norm.cc
index 16d7557f29a5..fdee4dca2352 100644
--- a/nnvm/src/compiler/simplify_batch_norm.cc
+++ b/nnvm/src/compiler/simplify_batch_norm.cc
@@ -21,7 +21,7 @@ BatchNormToInferUnpack(const nnvm::NodeAttrs& attrs,
                        nnvm::NodeEntry beta,
                        nnvm::NodeEntry moving_mean,
                        nnvm::NodeEntry moving_var,
-                       int data_dim) {
+                       TShape dshape) {
   CHECK(attrs.op);
   static const  Op* bn_op = Op::Get("batch_norm");
   CHECK(attrs.op == bn_op);
@@ -57,19 +57,12 @@ BatchNormToInferUnpack(const nnvm::NodeAttrs& attrs,
     shift = MakeNode(
         "elemwise_add", bn_name + "_add_beta", {shift, beta});
   }
-  // reshape to nhwc
+  // use broaodcast to reshape
   std::ostringstream oshape;
-  oshape << "(";
-  for (int i = 0; i < data_dim; ++i) {
-    if (i != 0) oshape << ", ";
-    if (i == param.axis) {
-      oshape << "-1";
-    } else {
-      oshape << "1";
-    }
+  for (dim_t i = 0; i < dshape.ndim(); ++i) {
+    dshape[i] = (i != param.axis) ? 1 : -1;
   }
-  oshape << ")";
-
+  oshape << dshape;
   scale = MakeNode("reshape", bn_name + "_sc_reshape",
                    {scale}, {{"shape", oshape.str()}});
   shift = MakeNode("reshape", bn_name + "_sh_reshape",
@@ -98,7 +91,7 @@ Graph SimplifyBatchNormInference(nnvm::Graph src) {
           n->inputs[2],
           n->inputs[3],
           n->inputs[4],
-          shape_vec[idx.entry_id(nid, 0)].ndim());
+          shape_vec[idx.entry_id(nid, 0)]);
       return true;
     } else {
       return false;
diff --git a/nnvm/src/pass/print_graph_ir.cc b/nnvm/src/pass/print_graph_ir.cc
index 6a42aabce616..52298c0a77a5 100644
--- a/nnvm/src/pass/print_graph_ir.cc
+++ b/nnvm/src/pass/print_graph_ir.cc
@@ -73,7 +73,7 @@ void PrintGraphIR_(Graph src,
     AttrPrinter fp = GetVectorPrinter(src, key);
     auto fprint = [&idx, key, fp](
         uint32_t nid, std::ostream& os) {  // NOLINT(*)
-      os << key << "=";
+      os << ", " << key << "=";
       fp(idx.entry_id(nid, 0), os);
     };
     trigger.push_back(fprint);
diff --git a/nnvm/tests/python/compiler/test_simplify_batchnorm.py b/nnvm/tests/python/compiler/test_simplify_batchnorm.py
index ec6dfb86ac47..307c1348fbba 100644
--- a/nnvm/tests/python/compiler/test_simplify_batchnorm.py
+++ b/nnvm/tests/python/compiler/test_simplify_batchnorm.py
@@ -5,13 +5,13 @@
 
 def test_simplify_batchnorm():
     def simple_bn(x, gamma, beta, moving_mean, moving_var,
-                  axis=1, epsilon=1e-5, dim=2):
+                  axis=1, epsilon=1e-5, shape=None):
         # expect = (x - moving_mean) / sym.sqrt(moving_var + eps) * gamma + beta
         scale = sym.elemwise_mul(1 / sym.sqrt(moving_var + epsilon), gamma)
         shift = sym.elemwise_add(
             sym.elemwise_mul(sym.negative(moving_mean), scale), beta)
+        shape = [-1 if i == axis else 1 for i in range(len(shape))]
         # for 2D
-        shape = tuple(1 if i != axis else -1 for i in range(dim))
         scale = sym.reshape(scale, shape=shape)
         shift = sym.reshape(shift, shape=shape)
         return x * scale + shift
@@ -26,15 +26,14 @@ def check(dim, axis, nstep):
         moving_var = sym.Variable("moving_var")
         moving_mean = sym.Variable("moving_mean")
         y1, y2 = x, x
-
+        ishape = {"x": tuple(10 for i in range(dim))}
         for i in range(nstep):
             y1 = sym.batch_norm(
                 y1 + 1, gamma, beta, moving_mean, moving_var, epsilon=eps, axis=axis)
             y2 = simple_bn(y2 + 1, gamma, beta, moving_mean, moving_var,
-                           epsilon=eps, axis=axis, dim=dim)
+                           epsilon=eps, axis=axis, shape=ishape["x"])
         g = nnvm.graph.create(y1)
         g2 = nnvm.graph.create(y2)
-        ishape = {"x": tuple(10 for i in range(dim))}
         graph_attr.set_shape_inputs(g, ishape)
         g1 = g.apply("InferShape").apply("SimplifyBatchNormInference")
         # Some prints for debug
diff --git a/nnvm/tests/python/compiler/test_top_level1.py b/nnvm/tests/python/compiler/test_top_level1.py
index 14108d18db0e..5822e58f995b 100644
--- a/nnvm/tests/python/compiler/test_top_level1.py
+++ b/nnvm/tests/python/compiler/test_top_level1.py
@@ -6,19 +6,10 @@
 import nnvm.compiler
 import nnvm.runtime
 
-USE_GPU=True
+def ctx_list():
+    res = [("llvm", tvm.cpu(0)), ("cuda", tvm.gpu(0))]
+    return [x for x in res if x[1].exist]
 
-def default_target():
-    if USE_GPU:
-        return 'cuda'
-    else:
-        return 'llvm'
-
-def default_ctx():
-    if USE_GPU:
-        return tvm.gpu(0)
-    else:
-        return tvm.cpu(0)
 
 def test_relu():
     x = sym.Variable("x")
@@ -26,20 +17,21 @@ def test_relu():
     dtype = "float32"
     dshape = (1, 3, 32, 32)
     oshape = dshape
-    graph, lib, _ = nnvm.compiler.build(y, default_target(), {"x": dshape})
-    m = nnvm.runtime.create(graph, lib, default_ctx())
-    # get member functions
-    set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
-    # set input
-    data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
-    set_input("x", data)
-    # execute
-    run()
-    # get output
-    out = tvm.nd.empty(oshape, dtype)
-    get_output(0, out)
-    y_np = np.maximum(data.asnumpy(), 0.0)
-    np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
+        m = nnvm.runtime.create(graph, lib, ctx)
+        # get member functions
+        set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
+        # set input
+        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+        set_input("x", data)
+        # execute
+        run()
+        # get output
+        out = tvm.nd.empty(oshape, dtype)
+        get_output(0, out)
+        y_np = np.maximum(data.asnumpy(), 0.0)
+        np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
 
 
 def test_exp():
@@ -48,20 +40,21 @@ def test_exp():
     dtype = "float32"
     dshape = (1, 3, 32, 32)
     oshape = dshape
-    graph, lib, _ = nnvm.compiler.build(y, default_target(), {"x": dshape})
-    m = nnvm.runtime.create(graph, lib, default_ctx())
-    # get member functions
-    set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
-    # set input
-    data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
-    set_input("x", data)
-    # execute
-    run()
-    # get output
-    out = tvm.nd.empty(oshape, dtype)
-    get_output(0, out)
-    y_np = np.exp(data.asnumpy())
-    np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
+        m = nnvm.runtime.create(graph, lib, ctx)
+        # get member functions
+        set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
+        # set input
+        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+        set_input("x", data)
+        # execute
+        run()
+        # get output
+        out = tvm.nd.empty(oshape, dtype)
+        get_output(0, out)
+        y_np = np.exp(data.asnumpy())
+        np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
 
 
 def test_log():
@@ -70,21 +63,22 @@ def test_log():
     dtype = "float32"
     dshape = (1, 3, 32, 32)
     oshape = dshape
-    with nnvm.compiler.build_config(opt_level=1):
-        graph, lib, _ = nnvm.compiler.build(y, default_target(), {"x": dshape})
-    m = nnvm.runtime.create(graph, lib, default_ctx())
-    # get member functions
-    set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
-    # set input
-    data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
-    set_input("x", data)
-    # execute
-    run()
-    # get output
-    out = tvm.nd.empty(oshape, dtype)
-    get_output(0, out)
-    y_np = np.log(data.asnumpy())
-    np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+    for target, ctx in ctx_list():
+        with nnvm.compiler.build_config(opt_level=1):
+            graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
+        m = nnvm.runtime.create(graph, lib, ctx)
+        # get member functions
+        set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
+        # set input
+        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+        set_input("x", data)
+        # execute
+        run()
+        # get output
+        out = tvm.nd.empty(oshape, dtype)
+        get_output(0, out)
+        y_np = np.log(data.asnumpy())
+        np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
 
 
 def test_tanh():
@@ -93,21 +87,22 @@ def test_tanh():
     dtype = "float32"
     dshape = (1, 3, 32, 32)
     oshape = dshape
-    with nnvm.compiler.build_config(opt_level=1):
-        graph, lib, _ = nnvm.compiler.build(y, default_target(), {"x": dshape})
-    m = nnvm.runtime.create(graph, lib, default_ctx())
-    # get member functions
-    set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
-    # set input
-    data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
-    set_input("x", data)
-    # execute
-    run()
-    # get output
-    out = tvm.nd.empty(oshape, dtype)
-    get_output(0, out)
-    y_np = np.sinh(data.asnumpy()) / np.cosh(data.asnumpy())
-    np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+    for target, ctx in ctx_list():
+        with nnvm.compiler.build_config(opt_level=1):
+            graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
+        m = nnvm.runtime.create(graph, lib, ctx)
+        # get member functions
+        set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
+        # set input
+        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+        set_input("x", data)
+        # execute
+        run()
+        # get output
+        out = tvm.nd.empty(oshape, dtype)
+        get_output(0, out)
+        y_np = np.sinh(data.asnumpy()) / np.cosh(data.asnumpy())
+        np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
 
 
 def test_sigmoid():
@@ -116,20 +111,21 @@ def test_sigmoid():
     dtype = "float32"
     dshape = (1, 3, 32, 32)
     oshape = dshape
-    graph, lib, _ = nnvm.compiler.build(y, default_target(), {"x": dshape})
-    m = nnvm.runtime.create(graph, lib, default_ctx())
-    # get member functions
-    set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
-    # set input
-    data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
-    set_input("x", data)
-    # execute
-    run()
-    # get output
-    out = tvm.nd.empty(oshape, dtype)
-    get_output(0, out)
-    y_np = 1.0 / (1.0 + np.exp(-data.asnumpy()))
-    np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
+        m = nnvm.runtime.create(graph, lib, ctx)
+        # get member functions
+        set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
+        # set input
+        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+        set_input("x", data)
+        # execute
+        run()
+        # get output
+        out = tvm.nd.empty(oshape, dtype)
+        get_output(0, out)
+        y_np = 1.0 / (1.0 + np.exp(-data.asnumpy()))
+        np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
 
 
 def test_softmax():
@@ -138,24 +134,79 @@ def test_softmax():
     dtype = "float32"
     dshape = (10, 1000)
     oshape = dshape
-    with nnvm.compiler.build_config(opt_level=1):
-        graph, lib, _ = nnvm.compiler.build(y, default_target(), {"x": dshape})
-    m = nnvm.runtime.create(graph, lib, default_ctx())
-    # get member functions
-    set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
-    # set input
-    data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
-    set_input("x", data)
-    # execute
-    run()
-    # get output
-    out = tvm.nd.empty(oshape, dtype)
-    get_output(0, out)
-    y_np = topi.testing.softmax_python(data.asnumpy())
-    np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+    for target, ctx in ctx_list():
+        with nnvm.compiler.build_config(opt_level=1):
+            graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
+        m = nnvm.runtime.create(graph, lib, ctx)
+        # get member functions
+        set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
+        # set input
+        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+        set_input("x", data)
+        # execute
+        run()
+        # get output
+        out = tvm.nd.empty(oshape, dtype)
+        get_output(0, out)
+        y_np = topi.testing.softmax_python(data.asnumpy())
+        np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+
+
+def test_dense():
+    x = sym.Variable("x")
+    y = sym.dense(x, units=3, name="dense")
+    y = sym.flatten(y)
+    dtype = "float32"
+    shape = {
+        "x" : (10, 100),
+        "dense_weight" : (3, 100),
+        "dense_bias" : (3,),
+    }
+    graph, lib, _ = nnvm.compiler.build(y, "llvm", shape)
+    m = nnvm.runtime.create(graph, lib, tvm.cpu(0))
+    x_np = np.random.uniform(size=shape["x"]).astype(dtype)
+    w_np = np.random.uniform(size=shape["dense_weight"]).astype(dtype)
+    b_np = np.random.uniform(size=shape["dense_bias"]).astype(dtype)
+    res = tvm.nd.empty((10, 3))
+    m.run(x=x_np, dense_weight=w_np, dense_bias=b_np)
+    m.get_output(0, res)
+    res_np = np.dot(x_np, w_np.T) + b_np
+    np.testing.assert_allclose(
+        res.asnumpy(), res_np, atol=1e-5, rtol=1e-5)
+
+
+def test_batchnorm():
+    x = sym.Variable("x")
+    beta = sym.Variable("beta")
+    gamma = sym.Variable("gamma")
+    moving_var = sym.Variable("moving_var")
+    moving_mean = sym.Variable("moving_mean")
+    shape = (10, 20)
+    eps = 1e-5
+    dtype = "float32"
+    y = sym.batch_norm(
+        x, gamma, beta, moving_mean, moving_var, epsilon=eps)
+
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, "llvm", {"x": shape})
+        m = nnvm.runtime.create(graph, lib, tvm.cpu(0))
+        x_np = np.random.uniform(size=shape).astype(dtype)
+        mean_np = np.random.uniform(size=shape[1]).astype(dtype)
+        var_np = np.random.uniform(size=shape[1]).astype(dtype)
+        gamma_np = np.random.uniform(size=shape[1]).astype(dtype)
+        beta_np = np.random.uniform(size=shape[1]).astype(dtype)
+        res = tvm.nd.empty(shape)
+        m.run(x=x_np, moving_mean=mean_np, moving_var=var_np,
+              gamma=gamma_np, beta=beta_np)
+        m.get_output(0, res)
+        res_np = (x_np - mean_np) / np.sqrt(var_np + eps) * gamma_np + beta_np
+        np.testing.assert_allclose(
+            res.asnumpy(), res_np, atol=1e-5, rtol=1e-5)
 
 
 if __name__ == "__main__":
+    test_batchnorm()
+    test_dense()
     test_relu()
     test_exp()
     test_log()
diff --git a/nnvm/tests/python/compiler/test_top_level2.py b/nnvm/tests/python/compiler/test_top_level2.py
index 32d84158b336..7e39bff4017f 100644
--- a/nnvm/tests/python/compiler/test_top_level2.py
+++ b/nnvm/tests/python/compiler/test_top_level2.py
@@ -6,19 +6,9 @@
 import nnvm.compiler
 import nnvm.runtime
 
-USE_GPU=True
-
-def default_target():
-    if USE_GPU:
-        return 'cuda'
-    else:
-        return 'llvm'
-
-def default_ctx():
-    if USE_GPU:
-        return tvm.gpu(0)
-    else:
-        return tvm.cpu(0)
+def ctx_list():
+    res = [("llvm", tvm.cpu(0)), ("cuda", tvm.gpu(0))]
+    return [x for x in res if x[1].exist]
 
 def test_conv2d():
     x = sym.Variable("x")
@@ -29,23 +19,24 @@ def test_conv2d():
     kshape = (10, 3, 3, 3)
     oshape = (1, 10, 18, 18)
     shape_dict = {"x": dshape}
-    graph, lib, _ = nnvm.compiler.build(y, default_target(), shape_dict)
-    m = nnvm.runtime.create(graph, lib, default_ctx())
-    # get member functions
-    set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
-    # set input
-    data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
-    kernel = tvm.nd.array(np.random.uniform(size=kshape).astype(dtype))
-    set_input("x", data)
-    set_input("y_weight", kernel)
-    # execute
-    run()
-    # get output
-    out = tvm.nd.empty(oshape, dtype)
-    get_output(0, out)
-    c_np = topi.testing.conv2d_nchw_python(
-        data.asnumpy(), kernel.asnumpy(), 1, 1)
-    np.testing.assert_allclose(out.asnumpy(), c_np, rtol=1e-5)
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
+        m = nnvm.runtime.create(graph, lib, ctx)
+        # get member functions
+        set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
+        # set input
+        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+        kernel = tvm.nd.array(np.random.uniform(size=kshape).astype(dtype))
+        set_input("x", data)
+        set_input("y_weight", kernel)
+        # execute
+        run()
+        # get output
+        out = tvm.nd.empty(oshape, dtype)
+        get_output(0, out)
+        c_np = topi.testing.conv2d_nchw_python(
+            data.asnumpy(), kernel.asnumpy(), 1, 1)
+        np.testing.assert_allclose(out.asnumpy(), c_np, rtol=1e-5)
 
 
 def test_grouped_conv2d():
@@ -57,23 +48,24 @@ def test_grouped_conv2d():
     kshape = (32, 1, 3, 3)
     oshape = (1, 32, 18, 18)
     shape_dict = {"x": dshape}
-    graph, lib, _ = nnvm.compiler.build(y, default_target(), shape_dict)
-    m = nnvm.runtime.create(graph, lib, default_ctx())
-    # get member functions
-    set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
-    # set input
-    data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
-    kernel = tvm.nd.array(np.random.uniform(size=kshape).astype(dtype))
-    set_input("x", data)
-    set_input("y_weight", kernel)
-    # execute
-    run()
-    # get output
-    out = tvm.nd.empty(oshape, dtype)
-    get_output(0, out)
-    c_np = topi.testing.depthwise_conv2d_python_nchw(
-        data.asnumpy(), kernel.asnumpy(), (1,1), 'SAME')
-    np.testing.assert_allclose(out.asnumpy(), c_np, rtol=1e-5)
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
+        m = nnvm.runtime.create(graph, lib, ctx)
+        # get member functions
+        set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
+        # set input
+        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+        kernel = tvm.nd.array(np.random.uniform(size=kshape).astype(dtype))
+        set_input("x", data)
+        set_input("y_weight", kernel)
+        # execute
+        run()
+        # get output
+        out = tvm.nd.empty(oshape, dtype)
+        get_output(0, out)
+        c_np = topi.testing.depthwise_conv2d_python_nchw(
+            data.asnumpy(), kernel.asnumpy(), (1,1), 'SAME')
+        np.testing.assert_allclose(out.asnumpy(), c_np, rtol=1e-5)
 
 
 if __name__ == "__main__":

From 3116ef75097849ff1e9bca1d88560ef21e580885 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 21 Sep 2017 22:57:43 -0700
Subject: [PATCH 480/949] [PASS] SimplifyBatchNorm->SimplifyInference, remove
 dropout (#24)

---
 nnvm/python/nnvm/compiler/build_module.py            | 11 ++++-------
 nnvm/python/nnvm/top/tensor.py                       |  5 +++++
 ...{simplify_batch_norm.cc => simplify_inference.cc} | 12 +++++++++---
 ...plify_batchnorm.py => test_simplify_inference.py} |  3 ++-
 4 files changed, 20 insertions(+), 11 deletions(-)
 rename nnvm/src/compiler/{simplify_batch_norm.cc => simplify_inference.cc} (90%)
 rename nnvm/tests/python/compiler/{test_simplify_batchnorm.py => test_simplify_inference.py} (94%)

diff --git a/nnvm/python/nnvm/compiler/build_module.py b/nnvm/python/nnvm/compiler/build_module.py
index 04b1f0a2e96f..24d7a85dea99 100644
--- a/nnvm/python/nnvm/compiler/build_module.py
+++ b/nnvm/python/nnvm/compiler/build_module.py
@@ -8,7 +8,7 @@
 from .. import runtime
 
 OPT_PASS_LEVEL = {
-    "SimplifyBatchNormInference": 2,
+    "SimplifyInference": 2,
     "PrecomputePrune": 2,
     "OpFusion": 1
 }
@@ -115,12 +115,9 @@ def optimize(graph, shape, dtype="float32"):
     """
     # pylint: disable=unused-argument
     cfg = BuildConfig.current
-    graph = graph_attr.set_shape_inputs(graph, shape)
-    graph = graph.apply("InferShape")
-    if graph.json_attr("shape_num_unknown_nodes"):
-        raise ValueError("InferShape fails..")
-    if cfg.opt_level >= OPT_PASS_LEVEL["SimplifyBatchNormInference"]:
-        graph = graph.apply("SimplifyBatchNormInference")
+    if cfg.opt_level >= OPT_PASS_LEVEL["SimplifyInference"]:
+        graph = graph_attr.set_shape_inputs(graph, shape)
+        graph = graph.apply(["InferShape", "SimplifyInference"])
     return graph
 
 
diff --git a/nnvm/python/nnvm/top/tensor.py b/nnvm/python/nnvm/top/tensor.py
index 0259e99b6aee..05396d65abeb 100644
--- a/nnvm/python/nnvm/top/tensor.py
+++ b/nnvm/python/nnvm/top/tensor.py
@@ -44,6 +44,11 @@ def _compute(attrs, x, _):
 
 _fschedule_broadcast = tvm.convert(_schedule_broadcast)
 
+# copy
+reg.register_compute("copy", _compute_unary(topi.identity))
+reg.register_pattern("copy", OpPattern.ELEM_WISE)
+reg.register_schedule("copy", _fschedule_broadcast)
+
 # exp
 reg.register_compute("exp", _compute_unary(topi.exp))
 reg.register_pattern("exp", OpPattern.ELEM_WISE)
diff --git a/nnvm/src/compiler/simplify_batch_norm.cc b/nnvm/src/compiler/simplify_inference.cc
similarity index 90%
rename from nnvm/src/compiler/simplify_batch_norm.cc
rename to nnvm/src/compiler/simplify_inference.cc
index fdee4dca2352..ad27d885f68e 100644
--- a/nnvm/src/compiler/simplify_batch_norm.cc
+++ b/nnvm/src/compiler/simplify_inference.cc
@@ -22,6 +22,7 @@ BatchNormToInferUnpack(const nnvm::NodeAttrs& attrs,
                        nnvm::NodeEntry moving_mean,
                        nnvm::NodeEntry moving_var,
                        TShape dshape) {
+  CHECK_NE(dshape.ndim(), 0);
   CHECK(attrs.op);
   static const  Op* bn_op = Op::Get("batch_norm");
   CHECK(attrs.op == bn_op);
@@ -76,13 +77,14 @@ BatchNormToInferUnpack(const nnvm::NodeAttrs& attrs,
   return {out, undef, undef};
 }
 
-Graph SimplifyBatchNormInference(nnvm::Graph src) {
+Graph SimplifyInference(nnvm::Graph src) {
   // Get attributes from the graph
   const IndexedGraph& idx = src.indexed_graph();
   const ShapeVector& shape_vec = src.GetAttr<ShapeVector>("shape");
   auto transform = [&](uint32_t nid, const Node* n, std::vector<NodeEntry>* ret) {
     if (n->is_variable()) return false;
     static const Op* bn_op = Op::Get("batch_norm");
+    static const Op* dropout_op = Op::Get("dropout");
     if (n->op() == bn_op) {
       *ret = BatchNormToInferUnpack(
           n->attrs,
@@ -93,6 +95,10 @@ Graph SimplifyBatchNormInference(nnvm::Graph src) {
           n->inputs[4],
           shape_vec[idx.entry_id(nid, 0)]);
       return true;
+    } else if (n->op() == dropout_op) {
+      NodeEntry undef = MakeNode("__undef__", "undef", {});
+      *ret = {n->inputs[0], undef};
+      return true;
     } else {
       return false;
     }
@@ -100,8 +106,8 @@ Graph SimplifyBatchNormInference(nnvm::Graph src) {
   return GraphTransform(src, transform);
 }
 
-NNVM_REGISTER_PASS(SimplifyBatchNormInference)
-.set_body(SimplifyBatchNormInference);
+NNVM_REGISTER_PASS(SimplifyInference)
+.set_body(SimplifyInference);
 
 }  // namespace compiler
 }  // namespace nnvm
diff --git a/nnvm/tests/python/compiler/test_simplify_batchnorm.py b/nnvm/tests/python/compiler/test_simplify_inference.py
similarity index 94%
rename from nnvm/tests/python/compiler/test_simplify_batchnorm.py
rename to nnvm/tests/python/compiler/test_simplify_inference.py
index 307c1348fbba..eaff669a904b 100644
--- a/nnvm/tests/python/compiler/test_simplify_batchnorm.py
+++ b/nnvm/tests/python/compiler/test_simplify_inference.py
@@ -30,12 +30,13 @@ def check(dim, axis, nstep):
         for i in range(nstep):
             y1 = sym.batch_norm(
                 y1 + 1, gamma, beta, moving_mean, moving_var, epsilon=eps, axis=axis)
+            y1 = sym.dropout(y1)
             y2 = simple_bn(y2 + 1, gamma, beta, moving_mean, moving_var,
                            epsilon=eps, axis=axis, shape=ishape["x"])
         g = nnvm.graph.create(y1)
         g2 = nnvm.graph.create(y2)
         graph_attr.set_shape_inputs(g, ishape)
-        g1 = g.apply("InferShape").apply("SimplifyBatchNormInference")
+        g1 = g.apply("InferShape").apply("SimplifyInference")
         # Some prints for debug
         # print(g1.ir())
         # assert graph equals as expected

From 0757d8b6c98278d727381a15307a740687f18630 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 22 Sep 2017 15:46:56 -0700
Subject: [PATCH 481/949] [TOP][COMPILER] Add expand_dims, change graph_compare
 to not compare input optionally (#25)

---
 nnvm/include/nnvm/top/tensor.h                | 11 +++++
 nnvm/python/nnvm/compiler/build_module.py     |  2 +
 nnvm/python/nnvm/compiler/graph_util.py       |  9 +++-
 nnvm/python/nnvm/testing/__init__.py          |  1 +
 nnvm/python/nnvm/testing/config.py            | 12 +++++
 nnvm/python/nnvm/top/nn.py                    |  2 +-
 nnvm/python/nnvm/top/tensor.py                | 12 +++++
 nnvm/python/nnvm/top/transform.py             | 10 +++++
 nnvm/src/compiler/graph_deep_compare.cc       | 10 ++++-
 nnvm/src/compiler/simplify_inference.cc       | 17 ++++---
 nnvm/src/top/tensor/transform.cc              | 45 ++++++++++++++++++-
 nnvm/tests/python/compiler/test_build.py      |  3 --
 .../compiler/test_simplify_inference.py       |  9 ++--
 nnvm/tests/python/compiler/test_top_level1.py | 21 ++++-----
 nnvm/tests/python/compiler/test_top_level2.py | 22 ++++-----
 .../tests/python/unittest/test_infer_shape.py | 11 +++++
 nnvm/tests/python/unittest/test_top_level1.py |  6 +++
 17 files changed, 155 insertions(+), 48 deletions(-)
 create mode 100644 nnvm/python/nnvm/testing/__init__.py
 create mode 100644 nnvm/python/nnvm/testing/config.py

diff --git a/nnvm/include/nnvm/top/tensor.h b/nnvm/include/nnvm/top/tensor.h
index 45541a151d57..23fe9a1e9644 100644
--- a/nnvm/include/nnvm/top/tensor.h
+++ b/nnvm/include/nnvm/top/tensor.h
@@ -21,6 +21,17 @@ struct ConcatenateParam : public dmlc::Parameter<ConcatenateParam> {
   }
 };
 
+struct ExpandDimsParam : public dmlc::Parameter<ExpandDimsParam> {
+  int axis;
+  int num_newaxis;
+  DMLC_DECLARE_PARAMETER(ExpandDimsParam) {
+    DMLC_DECLARE_FIELD(axis)
+    .describe("the axis to be expanded.");
+    DMLC_DECLARE_FIELD(num_newaxis).set_lower_bound(1).set_default(1)
+    .describe("Number of new axis to be inserted.");
+  }
+};
+
 struct SplitParam : public dmlc::Parameter<SplitParam> {
   // numpy convention, only support indices, not support list.
   Tuple<int> indices_or_sections;
diff --git a/nnvm/python/nnvm/compiler/build_module.py b/nnvm/python/nnvm/compiler/build_module.py
index 24d7a85dea99..27d86e9a6967 100644
--- a/nnvm/python/nnvm/compiler/build_module.py
+++ b/nnvm/python/nnvm/compiler/build_module.py
@@ -2,6 +2,7 @@
 """Namespace for building operators."""
 from __future__ import absolute_import as _abs
 
+import logging
 import tvm
 from . import graph_attr, graph_util
 from .. import graph as _graph
@@ -74,6 +75,7 @@ def build_config(**kwargs):
 @tvm.register_func("nnvm.compiler.lower")
 def _lower(sch, inputs, func_name):
     f = tvm.lower(sch, inputs, name=func_name)
+    logging.debug("lower function %s", func_name)
     return f if isinstance(
         f, (tvm.container.Array, tuple, list)) else [f]
 
diff --git a/nnvm/python/nnvm/compiler/graph_util.py b/nnvm/python/nnvm/compiler/graph_util.py
index fcca00b0abe0..68ce857264b4 100644
--- a/nnvm/python/nnvm/compiler/graph_util.py
+++ b/nnvm/python/nnvm/compiler/graph_util.py
@@ -59,7 +59,7 @@ def infer_dtype(graph, **dtype):
 
 _deep_compare = tvm.get_global_func("nnvm.graph.DeepCompare")
 
-def check_graph_equal(grapha, graphb):
+def check_graph_equal(grapha, graphb, compare_variable_attrs=False):
     """Check if two graphs have equal structure.
 
     Parameters
@@ -70,11 +70,16 @@ def check_graph_equal(grapha, graphb):
     graphb : Graph
         The second graph
 
+    compare_variable_attrs : bool, optional
+        Whether we want to compare attributes(names) on variables.
+        Usually it is safe to skip it unless we want input name
+        to exactly match
+
     Raises
     ------
     ValueError
         ValueError is raised with error message when graph not equal
     """
-    err = _deep_compare(grapha, graphb)
+    err = _deep_compare(grapha, graphb, compare_variable_attrs)
     if err:
         raise ValueError("Graph compare error: " + err)
diff --git a/nnvm/python/nnvm/testing/__init__.py b/nnvm/python/nnvm/testing/__init__.py
new file mode 100644
index 000000000000..1241e403b1a0
--- /dev/null
+++ b/nnvm/python/nnvm/testing/__init__.py
@@ -0,0 +1 @@
+"""Utilities for testcase"""
diff --git a/nnvm/python/nnvm/testing/config.py b/nnvm/python/nnvm/testing/config.py
new file mode 100644
index 000000000000..26d1d41014cf
--- /dev/null
+++ b/nnvm/python/nnvm/testing/config.py
@@ -0,0 +1,12 @@
+"""Configuration about tests"""
+import os
+import tvm
+
+def test_ctx_list():
+    """Get context list for testcases"""
+    device_list = os.environ.get("NNVM_TEST_TARGETS", "")
+    device_list = (device_list.split(",") if device_list
+                   else ["llvm", "cuda"])
+    device_list = set(device_list)
+    res = [("llvm", tvm.cpu(0)), ("cuda", tvm.gpu(0))]
+    return [x for x in res if x[1].exist and x[0] in device_list]
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index 5b0dfe2fe145..71246bc0823e 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -90,7 +90,7 @@ def compute_conv2d(attrs, inputs, _):
         raise ValueError("not support arbitrary group number for now")
     if attrs.get_bool("use_bias"):
         bias = inputs[2]
-        bias = topi.broadcast_to(bias, (1, bias.shape[0], 1, 1))
+        bias = topi.expand_dims(bias, axis=1, num_newaxis=2)
         out = topi.broadcast_add(out, bias)
     return out
 
diff --git a/nnvm/python/nnvm/top/tensor.py b/nnvm/python/nnvm/top/tensor.py
index 05396d65abeb..c427f49ab8d2 100644
--- a/nnvm/python/nnvm/top/tensor.py
+++ b/nnvm/python/nnvm/top/tensor.py
@@ -115,6 +115,18 @@ def _compute(attrs, x, _):
 reg.register_pattern("__rdiv_scalar__", OpPattern.ELEM_WISE)
 reg.register_schedule("__rdiv_scalar__", _fschedule_broadcast)
 
+# pow_scalar
+reg.register_compute("__pow_scalar__",
+                     _compute_binary_scalar(tvm.power))
+reg.register_pattern("__pow_scalar__", OpPattern.ELEM_WISE)
+reg.register_schedule("__pow_scalar__", _fschedule_broadcast)
+
+# rpow_scalar
+reg.register_compute("__rpow_scalar__",
+                     _compute_binary_scalar(lambda x, y: tvm.power(y, x)))
+reg.register_pattern("__rpow_scalar__", OpPattern.ELEM_WISE)
+reg.register_schedule("__rpow_scalar__", _fschedule_broadcast)
+
 # elemwise_add
 reg.register_compute("elemwise_add", _compute_binary(topi.broadcast_add))
 reg.register_pattern("elemwise_add", OpPattern.BROADCAST)
diff --git a/nnvm/python/nnvm/top/transform.py b/nnvm/python/nnvm/top/transform.py
index 89e3c64a05ce..e7419c030df4 100644
--- a/nnvm/python/nnvm/top/transform.py
+++ b/nnvm/python/nnvm/top/transform.py
@@ -3,11 +3,21 @@
 from __future__ import absolute_import
 
 import tvm
+import topi
 from .tensor import _fschedule_broadcast
 from ..compiler import registry as reg
 from ..compiler import OpPattern
 
 # Need add reshape, transpose
+@reg.register_compute("expand_dims")
+def compute_expand_dims(attrs, inputs, out_info):
+    """Compute definition of expand_dims"""
+    return topi.expand_dims(
+        inputs[0], attrs.get_int("axis"),
+        num_newaxis=attrs.get_int("num_newaxis"))
+reg.register_pattern("expand_dims", OpPattern.BROADCAST)
+reg.register_schedule("expand_dims", _fschedule_broadcast)
+
 
 def _flatten_index(indices, shape):
     """flatten the index to 1D"""
diff --git a/nnvm/src/compiler/graph_deep_compare.cc b/nnvm/src/compiler/graph_deep_compare.cc
index dd64f0e3b062..df578165ed6b 100644
--- a/nnvm/src/compiler/graph_deep_compare.cc
+++ b/nnvm/src/compiler/graph_deep_compare.cc
@@ -16,7 +16,9 @@ namespace compiler {
 // not considering the graph attributes
 // return non-empty error message if the graph mismatch.
 // the comparator won't match name of intermediate node.
-std::string DeepCompare(Graph a, Graph b) {
+// compare_var_attr
+std::string DeepCompare(Graph a, Graph b,
+                        bool compare_variable_attr) {
   const IndexedGraph& idxa = a.indexed_graph();
   const IndexedGraph& idxb = b.indexed_graph();
   std::ostringstream err;
@@ -51,6 +53,10 @@ std::string DeepCompare(Graph a, Graph b) {
       err << "Node mismatch ";
       return err.str();
     }
+    if (anode.source->is_variable()) {
+      CHECK(bnode.source->is_variable());
+      if (!compare_variable_attr) continue;
+    }
     AttrDict adict = GetAttrDict(anode.source->attrs);
     AttrDict bdict = GetAttrDict(bnode.source->attrs);
 
@@ -107,7 +113,7 @@ std::string DeepCompare(Graph a, Graph b) {
 
 TVM_REGISTER_GLOBAL("nnvm.graph.DeepCompare")
 .set_body([](tvm::runtime::TVMArgs args, tvm::runtime::TVMRetValue *rv) {
-    *rv = DeepCompare(args[0], args[1]);
+    *rv = DeepCompare(args[0], args[1], args[2]);
   });
 }  // namespace compiler
 }  // namespace nnvm
diff --git a/nnvm/src/compiler/simplify_inference.cc b/nnvm/src/compiler/simplify_inference.cc
index ad27d885f68e..7dd9ade0ac96 100644
--- a/nnvm/src/compiler/simplify_inference.cc
+++ b/nnvm/src/compiler/simplify_inference.cc
@@ -58,16 +58,15 @@ BatchNormToInferUnpack(const nnvm::NodeAttrs& attrs,
     shift = MakeNode(
         "elemwise_add", bn_name + "_add_beta", {shift, beta});
   }
-  // use broaodcast to reshape
-  std::ostringstream oshape;
-  for (dim_t i = 0; i < dshape.ndim(); ++i) {
-    dshape[i] = (i != param.axis) ? 1 : -1;
+  // use expand dims to pad lower dims to 1
+  int num_pad_axis = static_cast<int>(dshape.ndim() - param.axis) - 1;
+  if (num_pad_axis != 0) {
+    std::unordered_map<std::string, std::string> kwargs{
+      {"axis", std::to_string(param.axis)},
+      {"num_newaxis", std::to_string(num_pad_axis)}};
+    scale = MakeNode("expand_dims", bn_name + "_sc_expand", {scale}, kwargs);
+    shift = MakeNode("expand_dims", bn_name + "_sh_expand", {shift}, kwargs);
   }
-  oshape << dshape;
-  scale = MakeNode("reshape", bn_name + "_sc_reshape",
-                   {scale}, {{"shape", oshape.str()}});
-  shift = MakeNode("reshape", bn_name + "_sh_reshape",
-                   {shift}, {{"shape", oshape.str()}});
   NodeEntry out = MakeNode("broadcast_mul", bn_name + "_a_mul_data",
                            {data, scale});
   out = MakeNode("broadcast_add", bn_name + "_out",
diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
index c9eaf0c0ee6e..da93fe4cf26f 100644
--- a/nnvm/src/top/tensor/transform.cc
+++ b/nnvm/src/top/tensor/transform.cc
@@ -142,8 +142,51 @@ Example::
 .set_num_inputs(kVarg)
 .set_support_level(1);
 
+// expand_dims
+DMLC_REGISTER_PARAMETER(ExpandDimsParam);
+
+inline bool ExpandDimsInferShape(const NodeAttrs& attrs,
+                                 std::vector<TShape>* in_shape,
+                                 std::vector<TShape>* out_shape) {
+  const ExpandDimsParam& param = nnvm::get<ExpandDimsParam>(attrs.parsed);
+  CHECK_EQ(in_shape->size(), 1U);
+  const TShape& dshape = in_shape->at(0);
+  int ndim = static_cast<int>(dshape.ndim());
+  CHECK(param.axis >= -ndim - 1 && param.axis <= ndim);
+  int axis = param.axis < 0 ? ndim + param.axis + 1 : param.axis;
+  std::vector<dim_t> oshape;
+  for (int i = 0; i < axis; ++i) {
+    oshape.push_back(dshape[i]);
+  }
+  for (int i = 0; i < param.num_newaxis; ++i) {
+    oshape.push_back(1);
+  }
+  for (int i = axis; i < ndim; ++i) {
+    oshape.push_back(dshape[i]);
+  }
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0,
+                           TShape(oshape.begin(), oshape.end()));
+  return true;
+}
 
-// concatenate
+NNVM_REGISTER_OP(expand_dims)
+.describe(R"code(Inserts a new axis of size 1 into the array shape
+
+For example, given ``x`` with shape ``(2,3,4)``, then ``expand_dims(x, axis=1)``
+will return a new array with shape ``(2,1,3,4)``.
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "Tensor", "Input tensor")
+.add_arguments(ExpandDimsParam::__FIELDS__())
+.set_attr_parser(ParamParser<ExpandDimsParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<ExpandDimsParam>)
+.set_attr<FInferShape>("FInferShape", ExpandDimsInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_num_inputs(1)
+.set_num_outputs(1)
+.set_support_level(1);
+
+// split
 DMLC_REGISTER_PARAMETER(SplitParam);
 
 inline void SplitParamParser(nnvm::NodeAttrs* attrs) {
diff --git a/nnvm/tests/python/compiler/test_build.py b/nnvm/tests/python/compiler/test_build.py
index bb10e8400bb1..379975d2d6a4 100644
--- a/nnvm/tests/python/compiler/test_build.py
+++ b/nnvm/tests/python/compiler/test_build.py
@@ -40,9 +40,6 @@ def verify(graph, lib):
         assert graph.index.num_nodes == 4
         verify(graph, lib)
 
-
-
-
 def test_run():
     x = sym.Variable("x")
     y = sym.Variable("y")
diff --git a/nnvm/tests/python/compiler/test_simplify_inference.py b/nnvm/tests/python/compiler/test_simplify_inference.py
index eaff669a904b..5da46608081a 100644
--- a/nnvm/tests/python/compiler/test_simplify_inference.py
+++ b/nnvm/tests/python/compiler/test_simplify_inference.py
@@ -12,8 +12,10 @@ def simple_bn(x, gamma, beta, moving_mean, moving_var,
             sym.elemwise_mul(sym.negative(moving_mean), scale), beta)
         shape = [-1 if i == axis else 1 for i in range(len(shape))]
         # for 2D
-        scale = sym.reshape(scale, shape=shape)
-        shift = sym.reshape(shift, shape=shape)
+        num_newaxis=len(shape) - axis - 1
+        if num_newaxis:
+            scale = sym.expand_dims(scale, axis=axis, num_newaxis=num_newaxis)
+            shift = sym.expand_dims(shift, axis=axis, num_newaxis=num_newaxis)
         return x * scale + shift
 
 
@@ -25,7 +27,7 @@ def check(dim, axis, nstep):
         gamma = sym.Variable("gamma")
         moving_var = sym.Variable("moving_var")
         moving_mean = sym.Variable("moving_mean")
-        y1, y2 = x, x
+        y1, y2 = x, sym.Variable("xx") + 1
         ishape = {"x": tuple(10 for i in range(dim))}
         for i in range(nstep):
             y1 = sym.batch_norm(
@@ -44,6 +46,7 @@ def check(dim, axis, nstep):
 
     check(2, 1, 1)
     check(4, 0, 3)
+    check(4, 1, 2)
 
 if __name__ == "__main__":
     test_simplify_batchnorm()
diff --git a/nnvm/tests/python/compiler/test_top_level1.py b/nnvm/tests/python/compiler/test_top_level1.py
index 5822e58f995b..dc3423e67322 100644
--- a/nnvm/tests/python/compiler/test_top_level1.py
+++ b/nnvm/tests/python/compiler/test_top_level1.py
@@ -1,15 +1,10 @@
 import numpy as np
-
 import tvm
 import topi
 import nnvm.symbol as sym
 import nnvm.compiler
 import nnvm.runtime
-
-def ctx_list():
-    res = [("llvm", tvm.cpu(0)), ("cuda", tvm.gpu(0))]
-    return [x for x in res if x[1].exist]
-
+from nnvm.testing.config import test_ctx_list
 
 def test_relu():
     x = sym.Variable("x")
@@ -17,7 +12,7 @@ def test_relu():
     dtype = "float32"
     dshape = (1, 3, 32, 32)
     oshape = dshape
-    for target, ctx in ctx_list():
+    for target, ctx in test_ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
         m = nnvm.runtime.create(graph, lib, ctx)
         # get member functions
@@ -40,7 +35,7 @@ def test_exp():
     dtype = "float32"
     dshape = (1, 3, 32, 32)
     oshape = dshape
-    for target, ctx in ctx_list():
+    for target, ctx in test_ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
         m = nnvm.runtime.create(graph, lib, ctx)
         # get member functions
@@ -63,7 +58,7 @@ def test_log():
     dtype = "float32"
     dshape = (1, 3, 32, 32)
     oshape = dshape
-    for target, ctx in ctx_list():
+    for target, ctx in test_ctx_list():
         with nnvm.compiler.build_config(opt_level=1):
             graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
         m = nnvm.runtime.create(graph, lib, ctx)
@@ -87,7 +82,7 @@ def test_tanh():
     dtype = "float32"
     dshape = (1, 3, 32, 32)
     oshape = dshape
-    for target, ctx in ctx_list():
+    for target, ctx in test_ctx_list():
         with nnvm.compiler.build_config(opt_level=1):
             graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
         m = nnvm.runtime.create(graph, lib, ctx)
@@ -111,7 +106,7 @@ def test_sigmoid():
     dtype = "float32"
     dshape = (1, 3, 32, 32)
     oshape = dshape
-    for target, ctx in ctx_list():
+    for target, ctx in test_ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
         m = nnvm.runtime.create(graph, lib, ctx)
         # get member functions
@@ -134,7 +129,7 @@ def test_softmax():
     dtype = "float32"
     dshape = (10, 1000)
     oshape = dshape
-    for target, ctx in ctx_list():
+    for target, ctx in test_ctx_list():
         with nnvm.compiler.build_config(opt_level=1):
             graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
         m = nnvm.runtime.create(graph, lib, ctx)
@@ -187,7 +182,7 @@ def test_batchnorm():
     y = sym.batch_norm(
         x, gamma, beta, moving_mean, moving_var, epsilon=eps)
 
-    for target, ctx in ctx_list():
+    for target, ctx in test_ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, "llvm", {"x": shape})
         m = nnvm.runtime.create(graph, lib, tvm.cpu(0))
         x_np = np.random.uniform(size=shape).astype(dtype)
diff --git a/nnvm/tests/python/compiler/test_top_level2.py b/nnvm/tests/python/compiler/test_top_level2.py
index 7e39bff4017f..793d6d3e955f 100644
--- a/nnvm/tests/python/compiler/test_top_level2.py
+++ b/nnvm/tests/python/compiler/test_top_level2.py
@@ -5,10 +5,8 @@
 import nnvm.symbol as sym
 import nnvm.compiler
 import nnvm.runtime
+from nnvm.testing.config import test_ctx_list
 
-def ctx_list():
-    res = [("llvm", tvm.cpu(0)), ("cuda", tvm.gpu(0))]
-    return [x for x in res if x[1].exist]
 
 def test_conv2d():
     x = sym.Variable("x")
@@ -19,7 +17,7 @@ def test_conv2d():
     kshape = (10, 3, 3, 3)
     oshape = (1, 10, 18, 18)
     shape_dict = {"x": dshape}
-    for target, ctx in ctx_list():
+    for target, ctx in test_ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
         m = nnvm.runtime.create(graph, lib, ctx)
         # get member functions
@@ -42,29 +40,25 @@ def test_conv2d():
 def test_grouped_conv2d():
     x = sym.Variable("x")
     y = sym.conv2d(x, channels=32, kernel_size=(3, 3), groups=32,
-                   name="y", use_bias=False, padding=(1,1))
+                   name="y", padding=(1,1))
     dtype = "float32"
     dshape = (1, 32, 18, 18)
     kshape = (32, 1, 3, 3)
     oshape = (1, 32, 18, 18)
     shape_dict = {"x": dshape}
-    for target, ctx in ctx_list():
+    for target, ctx in test_ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
         m = nnvm.runtime.create(graph, lib, ctx)
-        # get member functions
-        set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
         # set input
         data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
         kernel = tvm.nd.array(np.random.uniform(size=kshape).astype(dtype))
-        set_input("x", data)
-        set_input("y_weight", kernel)
-        # execute
-        run()
+        bias = tvm.nd.array(np.random.uniform(size=kshape[0]).astype(dtype))
+        m.run(x=data, y_weight=kernel, y_bias=bias)
         # get output
-        out = tvm.nd.empty(oshape, dtype)
-        get_output(0, out)
+        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
         c_np = topi.testing.depthwise_conv2d_python_nchw(
             data.asnumpy(), kernel.asnumpy(), (1,1), 'SAME')
+        c_np = c_np + bias.asnumpy().reshape(kshape[0], 1, 1)
         np.testing.assert_allclose(out.asnumpy(), c_np, rtol=1e-5)
 
 
diff --git a/nnvm/tests/python/unittest/test_infer_shape.py b/nnvm/tests/python/unittest/test_infer_shape.py
index 77f14573b74e..251ea36f6a53 100644
--- a/nnvm/tests/python/unittest/test_infer_shape.py
+++ b/nnvm/tests/python/unittest/test_infer_shape.py
@@ -34,6 +34,16 @@ def test_concatenate():
     assert(sdict["concat"][0] == [20, 20])
 
 
+def test_expand_dims():
+    x = sym.Variable("x", shape=(10, 20))
+    y = sym.expand_dims(x, axis=1, name="y")
+    sdict = infer_shape(y)
+    assert(sdict["y"][0] == [10, 1, 20])
+    y = sym.expand_dims(x, axis=-1, name="y", num_newaxis=2)
+    sdict = infer_shape(y)
+    assert(sdict["y"][0] == [10, 20, 1, 1])
+
+
 def test_split():
     x1 = sym.Variable("x", shape=(10, 20))
     z = sym.split(x1, indices_or_sections=[11], name="y")
@@ -247,6 +257,7 @@ def check(in_shape, out_shape, **kwargs):
 
 
 if __name__ == "__main__":
+    test_expand_dims()
     test_dense()
     test_concatenate()
     test_split()
diff --git a/nnvm/tests/python/unittest/test_top_level1.py b/nnvm/tests/python/unittest/test_top_level1.py
index 6b75cf1d83f1..cf0baf4c3696 100644
--- a/nnvm/tests/python/unittest/test_top_level1.py
+++ b/nnvm/tests/python/unittest/test_top_level1.py
@@ -19,6 +19,11 @@ def test_concatenate_split():
     z = sym.split(y, indices_or_sections=[10, 20])
     assert len(z.list_output_names()) == 3
 
+def test_expand_dims():
+    x = sym.Variable('x')
+    y = sym.expand_dims(x, axis=1, num_newaxis=2)
+    assert y.list_input_names() == ['x']
+
 
 def test_unary():
     x = sym.Variable('x')
@@ -39,6 +44,7 @@ def test_batchnorm():
 
 if __name__ == "__main__":
     test_concatenate_split()
+    test_expand_dims()
     test_dense()
     test_unary()
     test_batchnorm()

From 4eec68472adfffd877968ab0637ec34c510777f0 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 22 Sep 2017 20:48:28 -0700
Subject: [PATCH 482/949] [PASS] Improve GraphFuse to include five patterns
 (#26)

---
 nnvm/docs/top.rst                            | 13 ++++-
 nnvm/include/nnvm/compiler/op_attr_types.h   | 19 ++++--
 nnvm/python/nnvm/compiler/registry.py        | 22 +++++--
 nnvm/python/nnvm/top/nn.py                   | 18 ++++--
 nnvm/python/nnvm/top/tensor.py               | 40 +++++++------
 nnvm/python/nnvm/top/transform.py            |  2 +-
 nnvm/src/compiler/graph_fuse.cc              | 31 +++++++---
 nnvm/src/compiler/layout_transform.cc        |  2 +-
 nnvm/tests/python/compiler/test_op_fusion.py | 61 ++++++++++++++++++++
 9 files changed, 162 insertions(+), 46 deletions(-)
 create mode 100644 nnvm/tests/python/compiler/test_op_fusion.py

diff --git a/nnvm/docs/top.rst b/nnvm/docs/top.rst
index 28b4a0710933..945cef00ad44 100644
--- a/nnvm/docs/top.rst
+++ b/nnvm/docs/top.rst
@@ -1,7 +1,8 @@
-NNVM Core Primitives
-====================
+NNVM Core Tensor Operators
+==========================
 
-**Level 1: Basic Ops**
+**Level 1: Basic Operators**
+This level enables fully connected multi-layer perceptron.
 
 .. autosummary::
    :nosignatures:
@@ -12,12 +13,14 @@ NNVM Core Primitives
    nnvm.symbol.sigmoid
    nnvm.symbol.exp
    nnvm.symbol.log
+   nnvm.symbol.sqrt
    nnvm.symbol.elemwise_add
    nnvm.symbol.elemwise_sub
    nnvm.symbol.elemwise_mul
    nnvm.symbol.elemwise_div
    nnvm.symbol.flatten
    nnvm.symbol.concatenate
+   nnvm.symbol.expand_dims
    nnvm.symbol.split
    nnvm.symbol.dropout
    nnvm.symbol.batch_norm
@@ -27,6 +30,8 @@ NNVM Core Primitives
 
 **Level 2: Convolutions**
 
+This level enables typical convnet models.
+
 .. autosummary::
    :nosignatures:
 
@@ -78,12 +83,14 @@ NNVM Core Primitives
 .. autofunction:: nnvm.symbol.sigmoid
 .. autofunction:: nnvm.symbol.exp
 .. autofunction:: nnvm.symbol.log
+.. autofunction:: nnvm.symbol.sqrt
 .. autofunction:: nnvm.symbol.elemwise_add
 .. autofunction:: nnvm.symbol.elemwise_sub
 .. autofunction:: nnvm.symbol.elemwise_mul
 .. autofunction:: nnvm.symbol.elemwise_div
 .. autofunction:: nnvm.symbol.flatten
 .. autofunction:: nnvm.symbol.concatenate
+.. autofunction:: nnvm.symbol.expand_dims
 .. autofunction:: nnvm.symbol.split
 .. autofunction:: nnvm.symbol.dropout
 .. autofunction:: nnvm.symbol.batch_norm
diff --git a/nnvm/include/nnvm/compiler/op_attr_types.h b/nnvm/include/nnvm/compiler/op_attr_types.h
index 8381733c33a1..9b9fbed4455f 100644
--- a/nnvm/include/nnvm/compiler/op_attr_types.h
+++ b/nnvm/include/nnvm/compiler/op_attr_types.h
@@ -25,16 +25,23 @@ using ::tvm::Tensor;
 using ::tvm::Schedule;
 
 /*! \brief operator pattern used in graph fusion */
-enum OpPatternKind : int {
+enum OpPatternKind {
   // Elementwise operation
   kElemWise = 0,
-  // Broadcast operation
+  // Broadcasting operator, can always map output axis to the input in order.
+  // for example :code:`out[i, ax1, j, ax2] = input[i, j]`.
+  // Note that the axis need to be in order so transpose is not a bcast operator.
   kBroadcast = 1,
-  // Complex operation, can fuse bcast in input/outputs
+  // Injective operator, can always injectively map output axis to a single input axis.
+  // All injective operator can still be safely fused to injective and reduction.
+  kInjective = 2,
+  // Communicative reduction operator.
+  kCommReduce = 3,
+  // Complex operation, can still fuse elemwise operations into its output.
   // but cannot chain another complex op
-  kComplex = 2,
-  // Extern operation, cannot fuse anything.
-  kExtern = 3
+  kOutEWiseFusable = 4,
+  // Opaque operation, cannot fuse anything.
+  kOpaque = 8
 };
 
 /*! \brief the operator pattern */
diff --git a/nnvm/python/nnvm/compiler/registry.py b/nnvm/python/nnvm/compiler/registry.py
index c8094b1d345f..2861877e8db3 100644
--- a/nnvm/python/nnvm/compiler/registry.py
+++ b/nnvm/python/nnvm/compiler/registry.py
@@ -3,12 +3,24 @@
 import tvm
 
 class OpPattern(object):
-    ELEM_WISE = 0
+    """Operator generic patterns
+
+    See Also
+    --------
+    top.tag : Contains explaination of the tag type.
+    """
+    # Elementwise operator
+    ELEMWISE = 0
+    # Broadcast operator
     BROADCAST = 1
-    # Complex means we can fuse elemwise to it
-    COMPLEX = 2
-    # Extern means the op is not fusable
-    EXTERN = 3
+    # Injective mapping
+    INJECTIVE = 2
+    # Comunication
+    COMM_REDUCE = 3
+    # Complex op, can still fuse ewise into it
+    OUT_ELEMWISE_FUSABLE = 4
+    # Not fusable opaque op
+    OPAQUE = 8
 
 _register_compute = tvm.get_global_func("nnvm._register_compute")
 _register_schedule = tvm.get_global_func("nnvm._register_schedule")
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index 71246bc0823e..3c3f737f4daf 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -16,8 +16,16 @@ def compute_relu(attrs, inputs, _):
     return topi.nn.relu(inputs[0])
 
 reg.register_schedule("relu", _fschedule_broadcast)
-reg.register_pattern("relu", OpPattern.ELEM_WISE)
+reg.register_pattern("relu", OpPattern.ELEMWISE)
 
+# leaky_relu
+@reg.register_compute("leaky_relu")
+def compute_relu(attrs, inputs, _):
+    """Compute definition of relu"""
+    return topi.nn.leaky_relu(inputs[0])
+
+reg.register_schedule("leaky_relu", _fschedule_broadcast)
+reg.register_pattern("leaky_relu", OpPattern.ELEMWISE)
 
 # flatten
 @reg.register_compute("flatten")
@@ -26,7 +34,7 @@ def compute_flatten(attrs, inputs, _):
     return topi.nn.flatten(inputs[0])
 
 reg.register_schedule("flatten", _fschedule_broadcast)
-reg.register_pattern("flatten", OpPattern.COMPLEX)
+reg.register_pattern("flatten", OpPattern.INJECTIVE)
 
 
 # softmax
@@ -46,7 +54,7 @@ def schedule_softmax(_, outs, target):
     return tvm.create_schedule([x.op for x in outs])
 
 # Mark softmax as extern as we do not fuse it in call cases
-reg.register_pattern("softmax", OpPattern.EXTERN)
+reg.register_pattern("softmax", OpPattern.OPAQUE)
 
 
 # dense
@@ -67,7 +75,7 @@ def schedule_dense(_, outs, target):
     return tvm.create_schedule([x.op for x in outs])
 
 # register extern for now, change me when fusion is enabled.
-reg.register_pattern("dense", OpPattern.EXTERN)
+reg.register_pattern("dense", OpPattern.OPAQUE)
 
 
 # conv
@@ -105,4 +113,4 @@ def schedule_conv2d(attrs, outs, target):
     # naive schedule
     return tvm.create_schedule([x.op for x in outs])
 
-reg.register_pattern("conv2d", OpPattern.COMPLEX)
+reg.register_pattern("conv2d", OpPattern.OUT_ELEMWISE_FUSABLE)
diff --git a/nnvm/python/nnvm/top/tensor.py b/nnvm/python/nnvm/top/tensor.py
index c427f49ab8d2..cc35e6e8ba4a 100644
--- a/nnvm/python/nnvm/top/tensor.py
+++ b/nnvm/python/nnvm/top/tensor.py
@@ -8,13 +8,15 @@
 from ..compiler import registry as reg
 from ..compiler import OpPattern
 
-def _schedule_broadcast(_, outs, target):
+def _schedule_injective(_, outs, target):
     """Generic schedule for binary bcast"""
     if target == "cuda":
-        return topi.cuda.schedule_elemwise(outs)
+        return topi.cuda.schedule_injective(outs)
     assert target.startswith("llvm")
     s = tvm.create_schedule([x.op for x in outs])
+    x = outs[0]
     tvm.schedule.AutoInlineInjective(s)
+    s[x].fuse(s[x].op.axis)
     return s
 
 def _compute_binary_scalar(f):
@@ -42,89 +44,91 @@ def _compute(attrs, x, _):
     return _compute
 
 
-_fschedule_broadcast = tvm.convert(_schedule_broadcast)
+_fschedule_injective = tvm.convert(_schedule_injective)
+_fschedule_broadcast = _fschedule_injective
+_fschedule_elemwise = _fschedule_injective
 
 # copy
 reg.register_compute("copy", _compute_unary(topi.identity))
-reg.register_pattern("copy", OpPattern.ELEM_WISE)
+reg.register_pattern("copy", OpPattern.ELEMWISE)
 reg.register_schedule("copy", _fschedule_broadcast)
 
 # exp
 reg.register_compute("exp", _compute_unary(topi.exp))
-reg.register_pattern("exp", OpPattern.ELEM_WISE)
+reg.register_pattern("exp", OpPattern.ELEMWISE)
 reg.register_schedule("exp", _fschedule_broadcast)
 
 # sqrt
 reg.register_compute("sqrt", _compute_unary(topi.sqrt))
-reg.register_pattern("sqrt", OpPattern.ELEM_WISE)
+reg.register_pattern("sqrt", OpPattern.ELEMWISE)
 reg.register_schedule("sqrt", _fschedule_broadcast)
 
 # log
 reg.register_compute("log", _compute_unary(topi.log))
-reg.register_pattern("log", OpPattern.ELEM_WISE)
+reg.register_pattern("log", OpPattern.ELEMWISE)
 reg.register_schedule("log", _fschedule_broadcast)
 
 # tanh
 reg.register_compute("tanh", _compute_unary(topi.tanh))
-reg.register_pattern("tanh", OpPattern.ELEM_WISE)
+reg.register_pattern("tanh", OpPattern.ELEMWISE)
 reg.register_schedule("tanh", _fschedule_broadcast)
 
 # negative
 reg.register_compute("negative", _compute_unary(topi.negative))
-reg.register_pattern("negative", OpPattern.ELEM_WISE)
+reg.register_pattern("negative", OpPattern.ELEMWISE)
 reg.register_schedule("negative", _fschedule_broadcast)
 
 # sigmoid
 reg.register_compute("sigmoid", _compute_unary(topi.sigmoid))
-reg.register_pattern("sigmoid", OpPattern.ELEM_WISE)
+reg.register_pattern("sigmoid", OpPattern.ELEMWISE)
 reg.register_schedule("sigmoid", _fschedule_broadcast)
 
 # add_scalar
 reg.register_compute("__add_scalar__",
                      _compute_binary_scalar(lambda x, y: x + y))
-reg.register_pattern("__add_scalar__", OpPattern.ELEM_WISE)
+reg.register_pattern("__add_scalar__", OpPattern.ELEMWISE)
 reg.register_schedule("__add_scalar__", _fschedule_broadcast)
 
 # sub_calar
 reg.register_compute("__sub_scalar__",
                      _compute_binary_scalar(lambda x, y: x - y))
-reg.register_pattern("__sub_scalar__", OpPattern.ELEM_WISE)
+reg.register_pattern("__sub_scalar__", OpPattern.ELEMWISE)
 reg.register_schedule("__sub_scalar__", _fschedule_broadcast)
 
 # rsub_scalar
 reg.register_compute("__rsub_scalar__",
                      _compute_binary_scalar(lambda x, y: y - x))
-reg.register_pattern("__rsub_scalar__", OpPattern.ELEM_WISE)
+reg.register_pattern("__rsub_scalar__", OpPattern.ELEMWISE)
 reg.register_schedule("__rsub_scalar__", _fschedule_broadcast)
 
 # mul_scalar
 reg.register_compute("__mul_scalar__",
                      _compute_binary_scalar(lambda x, y: x * y))
-reg.register_pattern("__mul_scalar__", OpPattern.ELEM_WISE)
+reg.register_pattern("__mul_scalar__", OpPattern.ELEMWISE)
 reg.register_schedule("__mul_scalar__", _fschedule_broadcast)
 
 # div_scalar
 reg.register_compute("__div_scalar__",
                      _compute_binary_scalar(lambda x, y: x / y))
-reg.register_pattern("__div_scalar__", OpPattern.ELEM_WISE)
+reg.register_pattern("__div_scalar__", OpPattern.ELEMWISE)
 reg.register_schedule("__div_scalar__", _fschedule_broadcast)
 
 # rdiv_scalar
 reg.register_compute("__rdiv_scalar__",
                      _compute_binary_scalar(lambda x, y: y / x))
-reg.register_pattern("__rdiv_scalar__", OpPattern.ELEM_WISE)
+reg.register_pattern("__rdiv_scalar__", OpPattern.ELEMWISE)
 reg.register_schedule("__rdiv_scalar__", _fschedule_broadcast)
 
 # pow_scalar
 reg.register_compute("__pow_scalar__",
                      _compute_binary_scalar(tvm.power))
-reg.register_pattern("__pow_scalar__", OpPattern.ELEM_WISE)
+reg.register_pattern("__pow_scalar__", OpPattern.ELEMWISE)
 reg.register_schedule("__pow_scalar__", _fschedule_broadcast)
 
 # rpow_scalar
 reg.register_compute("__rpow_scalar__",
                      _compute_binary_scalar(lambda x, y: tvm.power(y, x)))
-reg.register_pattern("__rpow_scalar__", OpPattern.ELEM_WISE)
+reg.register_pattern("__rpow_scalar__", OpPattern.ELEMWISE)
 reg.register_schedule("__rpow_scalar__", _fschedule_broadcast)
 
 # elemwise_add
diff --git a/nnvm/python/nnvm/top/transform.py b/nnvm/python/nnvm/top/transform.py
index e7419c030df4..a69caf39bcd0 100644
--- a/nnvm/python/nnvm/top/transform.py
+++ b/nnvm/python/nnvm/top/transform.py
@@ -37,5 +37,5 @@ def compute_reshape(attrs, inputs, out_info):
     oshape = out_info[0].shape
     x = inputs[0]
     return tvm.compute(oshape, lambda *i: x(_flatten_index(i, oshape)))
-reg.register_pattern("reshape", OpPattern.COMPLEX)
+reg.register_pattern("reshape", OpPattern.INJECTIVE)
 reg.register_schedule("reshape", _fschedule_broadcast)
diff --git a/nnvm/src/compiler/graph_fuse.cc b/nnvm/src/compiler/graph_fuse.cc
index 1daa5fd11394..e3935ed95ff1 100644
--- a/nnvm/src/compiler/graph_fuse.cc
+++ b/nnvm/src/compiler/graph_fuse.cc
@@ -71,7 +71,7 @@ nnvm::Graph GraphFusePartition(nnvm::Graph g) {
     ref_count[e.node_id] += 2;
   }
   // Pattern for the subgraph
-  std::vector<TOpPattern> pattern_vec(idx.num_nodes(),  kExtern);
+  std::vector<TOpPattern> pattern_vec(idx.num_nodes(),  kOpaque);
   // Whether node can be fused to parent.
   std::vector<FuseRule> fuse_vec(idx.num_nodes(), FuseRule::kUknown);
   // Master node id of fusion segment.
@@ -84,19 +84,21 @@ nnvm::Graph GraphFusePartition(nnvm::Graph g) {
     if (inode.source->is_variable()) {
       fuse_vec[nid] = FuseRule::kRealize; continue;
     }
-    TOpPattern pt = op_pattern.get(inode.source->op(), kExtern);
+    TOpPattern pt = op_pattern.get(inode.source->op(), kOpaque);
 
     if (pt <= kBroadcast) {
+      // Try to check if we can fuse to the master.
       int chosen_master = -1;
       bool ewise = inode.source->num_outputs() == 1;
       for (const auto& e : inode.inputs) {
         if (fuse_vec[e.node_id] == FuseRule::kUknown) {
           TOpPattern ipt = pattern_vec[e.node_id];
           if (ipt != kElemWise) ewise = false;
-          if (ipt <= kBroadcast) {
+          if (ipt <= kInjective) {
             fuse_vec[e.node_id] = FuseRule::kFuseToMaster;
-          } else if (ipt == kComplex && chosen_master == -1 &&
-            shape_vec[idx.entry_id(nid, 0)] == shape_vec[idx.entry_id(e)]) {
+          } else if (ipt == kOutEWiseFusable &&
+                     chosen_master == -1 &&
+                     shape_vec[idx.entry_id(nid, 0)] == shape_vec[idx.entry_id(e)]) {
             chosen_master = master_vec[e.node_id];
             fuse_vec[e.node_id] = FuseRule::kFuseToMaster;
           } else {
@@ -111,11 +113,27 @@ nnvm::Graph GraphFusePartition(nnvm::Graph g) {
       }
       master_vec[nid] = chosen_master;
       if (chosen_master != -1) {
-        pt = kComplex;
+        pt = kOutEWiseFusable;
       } else {
         pt = ewise ? kElemWise : kBroadcast;
       }
+    } else if (pt == kInjective || pt == kCommReduce) {
+      // fuse to the comm reduce or injective
+      for (const auto& e : inode.inputs) {
+        if (fuse_vec[e.node_id] == FuseRule::kUknown) {
+          TOpPattern ipt = pattern_vec[e.node_id];
+          if (ipt <= kInjective) {
+            fuse_vec[e.node_id] = FuseRule::kFuseToMaster;
+          } else {
+            fuse_vec[e.node_id] = FuseRule::kRealize;
+          }
+        }
+      }
+      if (pt == kCommReduce) {
+        master_vec[nid] = nid;
+      }
     } else {
+      // realize
       master_vec[nid] = nid;
       for (const auto& e : inode.inputs) {
         if (fuse_vec[e.node_id] == FuseRule::kUknown) {
@@ -136,7 +154,6 @@ nnvm::Graph GraphFusePartition(nnvm::Graph g) {
     }
   }
 
-
   // point to the group root id of each node
   std::vector<int> group_vec(idx.num_nodes(), -1);
   for (uint32_t i = idx.num_nodes(); i != 0; --i) {
diff --git a/nnvm/src/compiler/layout_transform.cc b/nnvm/src/compiler/layout_transform.cc
index 2bce0fad54c9..5651838ffb85 100644
--- a/nnvm/src/compiler/layout_transform.cc
+++ b/nnvm/src/compiler/layout_transform.cc
@@ -52,7 +52,7 @@ nnvm::Graph LayoutTransform(nnvm::Graph src) {
 
   // use op pattern to decide whether an op is map
   auto is_map_op = [&](size_t nid) {
-    TOpPattern pt = op_pattern.get(idx[nid].source->op(), kExtern);
+    TOpPattern pt = op_pattern.get(idx[nid].source->op(), kOpaque);
     bool is_map = (pt <= kBroadcast);
     if (pt == kBroadcast) {
       for (const auto& e : idx[nid].inputs) {
diff --git a/nnvm/tests/python/compiler/test_op_fusion.py b/nnvm/tests/python/compiler/test_op_fusion.py
new file mode 100644
index 000000000000..45031bf1802b
--- /dev/null
+++ b/nnvm/tests/python/compiler/test_op_fusion.py
@@ -0,0 +1,61 @@
+import nnvm
+import numpy as np
+import tvm
+import topi
+from nnvm import symbol as sym
+from nnvm.compiler import graph_util, graph_attr
+from nnvm.testing.config import test_ctx_list
+
+def test_ewise_injective():
+    x = sym.Variable("x")
+    y = x * 2
+    y = sym.flatten(y) + 1
+    dshape = (10, 2, 3)
+    shape_dict = {"x": dshape}
+    dtype = "float32"
+    target = "llvm"
+    for target, ctx in test_ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
+        assert graph.index.num_nodes == 2
+        m = nnvm.runtime.create(graph, lib, ctx)
+        x_np = np.random.uniform(size=dshape).astype(dtype)
+        m.run(x=x_np)
+        out = m.get_output(0, tvm.nd.empty((10, 6)))
+        np.testing.assert_allclose(
+            out.asnumpy(),  x_np.reshape(out.shape) * 2 + 1,
+            atol=1e-5, rtol=1e-5)
+
+
+def test_conv_ewise_injective():
+    x = sym.Variable("x")
+    y = sym.conv2d(x, channels=32, kernel_size=(3, 3), groups=32,
+                   name="y", padding=(1,1))
+    y = sym.flatten(y + 1) + 1
+    dtype = "float32"
+    dshape = (1, 32, 18, 18)
+    kshape = (32, 1, 3, 3)
+    oshape = (1, 32* 18 * 18)
+    shape_dict = {"x": dshape}
+
+    for target, ctx in test_ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
+        m = nnvm.runtime.create(graph, lib, ctx)
+        # print(graph.ir(join_entry_attrs=["shape"]))
+        assert graph.index.num_nodes == 5
+        # set input
+        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+        kernel = tvm.nd.array(np.random.uniform(size=kshape).astype(dtype))
+        bias = tvm.nd.array(np.random.uniform(size=kshape[0]).astype(dtype))
+        m.run(x=data, y_weight=kernel, y_bias=bias)
+        # get output
+        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
+        c_np = topi.testing.depthwise_conv2d_python_nchw(
+            data.asnumpy(), kernel.asnumpy(), (1,1), 'SAME')
+        c_np = c_np + bias.asnumpy().reshape(kshape[0], 1, 1) + 1
+        c_np = c_np.reshape(c_np.shape[0], np.prod(c_np.shape[1:])) + 1
+        np.testing.assert_allclose(out.asnumpy(), c_np, rtol=1e-5)
+
+
+if __name__ == "__main__":
+    test_ewise_injective()
+    test_conv_ewise_injective()

From e7069283aa6b0768468111f2743993c6fbf073fb Mon Sep 17 00:00:00 2001
From: "Joshua Z. Zhang" <cheungchih@gmail.com>
Date: Fri, 22 Sep 2017 22:50:07 -0700
Subject: [PATCH 483/949] init mxnet converter (#27)

graph

backup

update

finish mxnet converter

fix

fix various

add tests

fix

add multi networks

uses model_zoo

fix tests

minor fix

fix graph

fix
---
 nnvm/python/nnvm/__init__.py                  |   1 +
 nnvm/python/nnvm/frontend/__init__.py         |   3 +
 nnvm/python/nnvm/frontend/mxnet.py            | 301 ++++++++++++++++
 .../frontend/mxnet/model_zoo/__init__.py      |  22 ++
 .../python/frontend/mxnet/model_zoo/mlp.py    |  44 +++
 .../python/frontend/mxnet/model_zoo/resnet.py | 322 ++++++++++++++++++
 .../python/frontend/mxnet/model_zoo/vgg.py    | 128 +++++++
 .../python/frontend/mxnet/test_forward.py     |  88 +++++
 .../tests/python/frontend/mxnet/test_graph.py |  38 +++
 9 files changed, 947 insertions(+)
 create mode 100644 nnvm/python/nnvm/frontend/__init__.py
 create mode 100644 nnvm/python/nnvm/frontend/mxnet.py
 create mode 100644 nnvm/tests/python/frontend/mxnet/model_zoo/__init__.py
 create mode 100644 nnvm/tests/python/frontend/mxnet/model_zoo/mlp.py
 create mode 100644 nnvm/tests/python/frontend/mxnet/model_zoo/resnet.py
 create mode 100644 nnvm/tests/python/frontend/mxnet/model_zoo/vgg.py
 create mode 100644 nnvm/tests/python/frontend/mxnet/test_forward.py
 create mode 100644 nnvm/tests/python/frontend/mxnet/test_graph.py

diff --git a/nnvm/python/nnvm/__init__.py b/nnvm/python/nnvm/__init__.py
index d30b1c152e2b..31b88587764d 100644
--- a/nnvm/python/nnvm/__init__.py
+++ b/nnvm/python/nnvm/__init__.py
@@ -7,5 +7,6 @@
 from . import symbol as sym
 from . import symbol
 from ._base import NNVMError
+from . import frontend
 
 __version__ = _base.__version__
diff --git a/nnvm/python/nnvm/frontend/__init__.py b/nnvm/python/nnvm/frontend/__init__.py
new file mode 100644
index 000000000000..de6c9ee3f7a1
--- /dev/null
+++ b/nnvm/python/nnvm/frontend/__init__.py
@@ -0,0 +1,3 @@
+"""Frontend package."""
+from __future__ import absolute_import
+from .mxnet import from_mxnet
diff --git a/nnvm/python/nnvm/frontend/mxnet.py b/nnvm/python/nnvm/frontend/mxnet.py
new file mode 100644
index 000000000000..5e1ea0457d60
--- /dev/null
+++ b/nnvm/python/nnvm/frontend/mxnet.py
@@ -0,0 +1,301 @@
+"""MXNet symbol frontend."""
+from __future__ import absolute_import as _abs
+import json
+from .. import symbol as _sym
+
+__all__ = ['from_mxnet']
+
+def _required_attr(attr, key):
+    assert isinstance(attr, dict)
+    if key not in attr:
+        raise AttributeError("Required attribute {} not found.".format(key))
+    return attr[key]
+
+def _raise_not_supported(attr, op='nnvm'):
+    err = "{} is not supported in {}.".format(attr, op)
+    raise NotImplementedError(err)
+
+def _warn_not_used(attr, op='nnvm'):
+    import warnings
+    err = "{} is ignored in {}.".format(attr, op)
+    warnings.warn(err)
+
+def _parse_tshape(tshape):
+    """Parse tshape in string."""
+    return [int(x.strip()) for x in tshape.strip('()').split(',')]
+
+def _parse_bool_str(attr, key, default='False'):
+    """Parse bool string to boolean."""
+    return attr.get(key, default).strip().lower() in ['true', '1', 't', 'y', 'yes']
+
+def _rename(new_name):
+    def impl(attr):
+        return new_name, attr
+    return impl
+
+def _variable(attrs):
+    return "Variable", attrs
+
+def _pooling(attrs):
+    kernel = _parse_tshape(_required_attr(attrs, 'kernel'))
+    if len(kernel) != 2:
+        _raise_not_supported('non-2d kernel', 'pool_2d')
+    global_pool = 'global' if _parse_bool_str(attrs, 'global_pool') else ''
+    pool_type = _required_attr(attrs, 'pool_type')
+    if pool_type not in ['avg', 'max']:
+        _raise_not_supported('non-avg/max', 'pool2d')
+    op_name, new_attrs = '_'.join([global_pool, pool_type, 'pool2d']).strip('_'), {}
+    # new_attrs['layout'] = 'NCHW'
+    if not global_pool:
+        new_attrs['pool_size'] = kernel
+        new_attrs['strides'] = attrs.get('stride', (1, 1))
+        new_attrs['padding'] = attrs.get('pad', (0, 0))
+        new_attrs['ceil_mode'] = (attrs.get('pooling_convention', 'valid') == 'full')
+    return op_name, new_attrs
+
+def _batch_norm(attrs):
+    if _parse_bool_str(attrs, 'output_mean_var'):
+        _raise_not_supported('output_mean_var', 'batch_norm')
+    if _parse_bool_str(attrs, 'fix_gamma'):
+        _warn_not_used('fix_gamma', 'batch_norm')
+    if _parse_bool_str(attrs, 'use_global_stats'):
+        _warn_not_used('use_global_stats', 'batch_norm')
+    if _parse_bool_str(attrs, 'momentum'):
+        _warn_not_used('momentum', 'batch_norm')
+    op_name, new_attrs = 'batch_norm', {}
+    new_attrs['axis'] = attrs.get('axis', 1)
+    new_attrs['epsilon'] = attrs.get('eps', 0.001)
+    new_attrs['center'] = True
+    new_attrs['scale'] = True
+    return op_name, new_attrs
+
+def _concat(attrs):
+    op_name = 'concatenate'
+    new_attrs = {'axis': attrs.get('dim', 1)}
+    return op_name, new_attrs
+
+def _conv2d(attrs):
+    kernel = _parse_tshape(_required_attr(attrs, 'kernel'))
+    if len(kernel) != 2:
+        _raise_not_supported('non 2d kernel', 'conv2d')
+    layout = attrs.get('layout', 'NCHW')
+    if layout not in ['NCHW', 'NHWC']:
+        _raise_not_supported('layout: ' + layout, 'conv2d')
+    op_name, new_attrs = 'conv2d', {}
+    new_attrs['channels'] = _required_attr(attrs, 'num_filter')
+    new_attrs['kernel_size'] = kernel
+    new_attrs['strides'] = attrs.get('stride', (1, 1))
+    new_attrs['padding'] = attrs.get('pad', (0, 0))
+    new_attrs['dilation'] = attrs.get('dilate', (1, 1))
+    new_attrs['groups'] = attrs.get('num_group', 1)
+    new_attrs['layout'] = layout
+    new_attrs['use_bias'] = attrs.get('no_bias', 'False').strip() == 'False'
+    return op_name, new_attrs
+
+def _conv2d_transpose(attrs):
+    if 'target_shape' in attrs:
+        _raise_not_supported('target_shape', 'conv2d_transpose')
+    kernel = _parse_tshape(_required_attr(attrs, 'kernel'))
+    if len(kernel) != 2:
+        _raise_not_supported('non-2d kernel', 'conv2d_transpose')
+    layout = attrs.get('layout', 'NCHW')
+    if layout not in ['NCHW', 'NHWC']:
+        _raise_not_supported('layout: ' + layout, 'conv2d_transpose')
+    op_name, new_attrs = 'conv2d_transpose', {}
+    new_attrs['channels'] = _required_attr(attrs, 'num_filter')
+    new_attrs['kernel_size'] = kernel
+    new_attrs['strides'] = attrs.get('stride', (1, 1))
+    new_attrs['output_padding'] = attrs.get('adj', (0, 0))
+    new_attrs['padding'] = attrs.get('pad', (0, 0))
+    new_attrs['dilation'] = attrs.get('dilate', (1, 1))
+    new_attrs['groups'] = attrs.get('num_group', 1)
+    new_attrs['layout'] = layout
+    new_attrs['use_bias'] = not _parse_bool_str(attrs, 'no_bias')
+    return op_name, new_attrs
+
+def _dense(attrs):
+    op_name, new_attrs = 'dense', {}
+    new_attrs['units'] = _required_attr(attrs, 'num_hidden')
+    new_attrs['use_bias'] = not _parse_bool_str(attrs, 'no_bias')
+    return op_name, new_attrs
+
+def _dropout(attrs):
+    op_name, new_attrs = 'dropout', {}
+    new_attrs['rate'] = attrs.get('p', 0.5)
+    return op_name, new_attrs
+
+def _leaky_relu(attrs):
+    act_type = _required_attr(attrs, 'act_type')
+    if act_type not in ['leaky']:
+        _raise_not_supported('act_type: ' + act_type)
+    op_name, new_attrs = 'leaky_relu', {}
+    new_attrs['alpha'] = attrs.get('slope', 0.25)
+    return op_name, new_attrs
+
+def _activations(attrs):
+    act_type = _required_attr(attrs, 'act_type')
+    if act_type not in ['relu', 'sigmoid', 'tanh']:
+        _raise_not_supported('act_type: ' + act_type)
+    op_name, new_attrs = act_type, {}
+    return op_name, new_attrs
+
+def _reshape(attrs):
+    if _parse_bool_str(attrs, 'reverse'):
+        _raise_not_supported('reverse', 'reshape')
+    op_name, new_attrs = 'reshape', {}
+    new_attrs['shape'] = _required_attr(attrs, 'shape')
+    return op_name, new_attrs
+
+def _split(attrs):
+    if _parse_bool_str(attrs, 'squeeze_axis'):
+        _raise_not_supported('squeeze_axis', 'split')
+    op_name, new_attrs = 'split', {}
+    new_attrs['indices_or_sections'] = _required_attr(attrs, 'num_outputs')
+    new_attrs['axis'] = attrs.get('axis', 1)
+    return op_name, new_attrs
+
+_identity_list = ['__add_scalar__', '__add_symbol__', '__div_scalar__',
+                '__div_symbol__', '__mul_scalar__', '__mul_symbol__',
+                '__pow_scalar__', '__rdiv_scalar__', '__rpow_scalar__',
+                '__rsub_scalar__', '__sub_scalar__', '__sub_symbol__',
+                'broadcast_add', 'broadcast_div', 'broadcast_mul',
+                'broadcast_sub', 'broadcast_to', 'cast', 'elemwise_add',
+                'elemwise_div', 'elemwise_mul', 'elemwise_sub', 'exp',
+                'flatten', 'log', 'log_softmax', 'max', 'min', 'negative',
+                'relu', 'sigmoid', 'softmax', 'sum', 'tanh', 'transpose']
+
+_convert_map = {
+    'null'          : _variable,
+    'Activation'    : _activations,
+    'BatchNorm'     : _batch_norm,
+    'BatchNorm_v1'  : _batch_norm,
+    'Cast'          : _rename('cast'),
+    'Concat'        : _concat,
+    'Convolution'   : _conv2d,
+    'Convolution_v1': _conv2d,
+    'Deconvolution' : _conv2d_transpose,
+    'Dropout'       : _dropout,
+    'Flatten'       : _rename('flatten'),
+    'FullyConnected': _dense,
+    'LeakyReLU'     : _leaky_relu,
+    'Pooling'       : _pooling,
+    'Pooling_v1'    : _pooling,
+    'Reshape'       : _reshape,
+    'Softmax'       : _rename('softmax'),
+    'concat'        : _concat,
+    'max_axis'      : _rename('max'),
+    'min_axis'      : _rename('min'),
+    'reshape'       : _reshape,
+    'sum_axis'      : _rename('sum'),
+}
+
+def _convert_symbol(op_name, attrs,
+                    identity_list=_identity_list,
+                    convert_map=_convert_map):
+    """Convert from mxnet op to nnvm op.
+    The converter must specify some conversions explicitly to
+    support gluon format ops such as conv2d...
+
+    Parameters
+    ----------
+    op_name : str
+        Operator name, such as Convolution, FullyConnected
+    attrs : dict
+        Dict of operator attributes
+    identity_list : list
+        List of operators that don't require conversion
+    convert_map : dict
+        Dict of name : callable, where name is the op's name that
+        require conversion to nnvm, callable are functions which
+        take attrs and return (new_op_name, new_attrs)
+
+    Returns
+    -------
+    (op_name, attrs)
+        Converted (op_name, attrs) for nnvm.
+    """
+    if op_name in identity_list:
+        pass
+    elif op_name in convert_map:
+        op_name, attrs = convert_map[op_name](attrs)
+    else:
+        _raise_not_supported('Operator: ' + op_name)
+    op = getattr(_sym, op_name, None)
+    if not op:
+        raise RuntimeError("Unable to map op_name {} to nnvm.sym".format(op_name))
+    return op, attrs
+
+def _is_mxnet_group_symbol(symbol):
+    """Internal check for mxnet group symbol."""
+    return len(symbol.list_outputs()) > 1
+
+def _as_list(arr):
+    """Force being a list, ignore if already is."""
+    if isinstance(arr, list):
+        return arr
+    return [arr]
+
+def _from_mxnet_impl(symbol, graph):
+    """Convert mxnet symbol to nnvm implementation.
+    Reconstruct a nnvm symbol by traversing the mxnet symbol.
+
+    Parameters
+    ----------
+    symbol : mxnet.sym.Symbol
+        Incompatible symbol from mxnet, sharing similar graph structure.
+        The op_name and attrs inside are not always compatible.
+    graph : dict
+        Reusable nodes are stored in graph.
+
+    Returns:
+    -------
+    nnvm.sym.Symbol
+        Converted symbol
+    """
+    try:
+        from mxnet import sym as mx_sym
+    except ImportError as e:
+        raise ImportError('{}. MXNet is required to parse symbols.'.format(e))
+
+    if not isinstance(symbol, mx_sym.Symbol):
+        raise ValueError("Provided {}, while MXNet symbol is expected", type(symbol))
+
+    if _is_mxnet_group_symbol(symbol):
+        return [_from_mxnet_impl(s, graph) for s in symbol]
+
+    name = symbol.attr('name')
+    node = graph.get(name, None)
+    if node:
+        return node
+    # op_name = symbol.attr('op_name')
+    if symbol.get_children():
+        op_name = symbol.attr('op_name')
+    else:
+        op_name = json.loads(symbol.tojson())['nodes'][0]['op']
+    attr = symbol.list_attr()
+    new_op, new_attr = _convert_symbol(op_name, attr)
+    if new_op == _sym.Variable:
+        node = new_op(name=name, **new_attr)
+    else:
+        childs = symbol.get_children()
+        childs = [_from_mxnet_impl(c, graph) for c in _as_list(childs)]
+        childs = [x for y in childs for x in _as_list(y)]  # expand group symbol
+        node = new_op(name=name, *childs, **new_attr)
+    graph[name] = node
+    return node
+
+
+def from_mxnet(symbol):
+    """Convert from mxnet.Symbol to compatible nnvm.Symbol
+
+    Parameters
+    ----------
+    symbol : mxnet.Symbol
+        MXNet symbol
+
+    Returns
+    -------
+    nnvm.Symbol
+        Compatible nnvm symbol
+    """
+    return _from_mxnet_impl(symbol, {})
diff --git a/nnvm/tests/python/frontend/mxnet/model_zoo/__init__.py b/nnvm/tests/python/frontend/mxnet/model_zoo/__init__.py
new file mode 100644
index 000000000000..07be9bba02c0
--- /dev/null
+++ b/nnvm/tests/python/frontend/mxnet/model_zoo/__init__.py
@@ -0,0 +1,22 @@
+from __future__ import absolute_import
+from . import mlp, resnet, vgg
+
+_num_class = 1000
+
+# mlp fc
+mx_mlp = mlp.get_symbol(_num_class)
+nnvm_mlp = mlp.get_symbol_nnvm(_num_class)
+
+# resnet fc
+mx_resnet = {}
+nnvm_resnet = {}
+for num_layer in [18, 34, 50, 101, 152, 200, 269]:
+    mx_resnet[num_layer] = resnet.get_symbol(_num_class, num_layer, '3,224,224')
+    nnvm_resnet[num_layer] = resnet.get_symbol(_num_class, num_layer, '3, 224, 224', lib='nnvm')
+
+# vgg fc
+mx_vgg = {}
+nnvm_vgg = {}
+for num_layer in [11, 13, 16, 19]:
+    mx_vgg[num_layer] = vgg.get_symbol(_num_class, num_layer)
+    nnvm_vgg[num_layer] = vgg.get_symbol_nnvm(_num_class, num_layer)
diff --git a/nnvm/tests/python/frontend/mxnet/model_zoo/mlp.py b/nnvm/tests/python/frontend/mxnet/model_zoo/mlp.py
new file mode 100644
index 000000000000..f69d4a2d1d52
--- /dev/null
+++ b/nnvm/tests/python/frontend/mxnet/model_zoo/mlp.py
@@ -0,0 +1,44 @@
+# 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.
+
+"""
+a simple multilayer perceptron
+"""
+import mxnet as mx
+import nnvm
+
+def get_symbol(num_classes=10, **kwargs):
+    data = mx.symbol.Variable('data')
+    data = mx.sym.Flatten(data=data)
+    fc1  = mx.symbol.FullyConnected(data = data, name='fc1', num_hidden=128)
+    act1 = mx.symbol.Activation(data = fc1, name='relu1', act_type="relu")
+    fc2  = mx.symbol.FullyConnected(data = act1, name = 'fc2', num_hidden = 64)
+    act2 = mx.symbol.Activation(data = fc2, name='relu2', act_type="relu")
+    fc3  = mx.symbol.FullyConnected(data = act2, name='fc3', num_hidden=num_classes)
+    mlp  = mx.symbol.softmax(data = fc3, name = 'softmax')
+    return mlp
+
+def get_symbol_nnvm(num_classes=10, **kwargs):
+    data = nnvm.symbol.Variable('data')
+    data = nnvm.sym.flatten(data=data)
+    fc1  = nnvm.symbol.dense(data = data, name='fc1', units=128)
+    act1 = nnvm.symbol.relu(data = fc1, name='relu1')
+    fc2  = nnvm.symbol.dense(data = act1, name = 'fc2', units = 64)
+    act2 = nnvm.symbol.relu(data = fc2, name='relu2')
+    fc3  = nnvm.symbol.dense(data = act2, name='fc3', units=num_classes)
+    mlp  = nnvm.symbol.softmax(data = fc3, name = 'softmax')
+    return mlp
diff --git a/nnvm/tests/python/frontend/mxnet/model_zoo/resnet.py b/nnvm/tests/python/frontend/mxnet/model_zoo/resnet.py
new file mode 100644
index 000000000000..2d4a5c9179d1
--- /dev/null
+++ b/nnvm/tests/python/frontend/mxnet/model_zoo/resnet.py
@@ -0,0 +1,322 @@
+# 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.
+
+'''
+Adapted from https://github.com/tornadomeet/ResNet/blob/master/symbol_resnet.py
+Original author Wei Wu
+
+Implemented the following paper:
+
+Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun. "Identity Mappings in Deep Residual Networks"
+'''
+import mxnet as mx
+import numpy as np
+import nnvm
+
+def residual_unit(data, num_filter, stride, dim_match, name, bottle_neck=True, bn_mom=0.9, workspace=256, memonger=False):
+    """Return ResNet Unit symbol for building ResNet
+    Parameters
+    ----------
+    data : str
+        Input data
+    num_filter : int
+        Number of output channels
+    bnf : int
+        Bottle neck channels factor with regard to num_filter
+    stride : tuple
+        Stride used in convolution
+    dim_match : Boolean
+        True means channel number between input and output is the same, otherwise means differ
+    name : str
+        Base name of the operators
+    workspace : int
+        Workspace used in convolution operator
+    """
+    if bottle_neck:
+        # the same as https://github.com/facebook/fb.resnet.torch#notes, a bit difference with origin paper
+        bn1 = mx.sym.BatchNorm(data=data, fix_gamma=False, eps=2e-5, momentum=bn_mom, name=name + '_bn1')
+        act1 = mx.sym.Activation(data=bn1, act_type='relu', name=name + '_relu1')
+        conv1 = mx.sym.Convolution(data=act1, num_filter=int(num_filter*0.25), kernel=(1,1), stride=(1,1), pad=(0,0),
+                                   no_bias=True, workspace=workspace, name=name + '_conv1')
+        bn2 = mx.sym.BatchNorm(data=conv1, fix_gamma=False, eps=2e-5, momentum=bn_mom, name=name + '_bn2')
+        act2 = mx.sym.Activation(data=bn2, act_type='relu', name=name + '_relu2')
+        conv2 = mx.sym.Convolution(data=act2, num_filter=int(num_filter*0.25), kernel=(3,3), stride=stride, pad=(1,1),
+                                   no_bias=True, workspace=workspace, name=name + '_conv2')
+        bn3 = mx.sym.BatchNorm(data=conv2, fix_gamma=False, eps=2e-5, momentum=bn_mom, name=name + '_bn3')
+        act3 = mx.sym.Activation(data=bn3, act_type='relu', name=name + '_relu3')
+        conv3 = mx.sym.Convolution(data=act3, num_filter=num_filter, kernel=(1,1), stride=(1,1), pad=(0,0), no_bias=True,
+                                   workspace=workspace, name=name + '_conv3')
+        if dim_match:
+            shortcut = data
+        else:
+            shortcut = mx.sym.Convolution(data=act1, num_filter=num_filter, kernel=(1,1), stride=stride, no_bias=True,
+                                            workspace=workspace, name=name+'_sc')
+        if memonger:
+            shortcut._set_attr(mirror_stage='True')
+        return conv3 + shortcut
+    else:
+        bn1 = mx.sym.BatchNorm(data=data, fix_gamma=False, momentum=bn_mom, eps=2e-5, name=name + '_bn1')
+        act1 = mx.sym.Activation(data=bn1, act_type='relu', name=name + '_relu1')
+        conv1 = mx.sym.Convolution(data=act1, num_filter=num_filter, kernel=(3,3), stride=stride, pad=(1,1),
+                                      no_bias=True, workspace=workspace, name=name + '_conv1')
+        bn2 = mx.sym.BatchNorm(data=conv1, fix_gamma=False, momentum=bn_mom, eps=2e-5, name=name + '_bn2')
+        act2 = mx.sym.Activation(data=bn2, act_type='relu', name=name + '_relu2')
+        conv2 = mx.sym.Convolution(data=act2, num_filter=num_filter, kernel=(3,3), stride=(1,1), pad=(1,1),
+                                      no_bias=True, workspace=workspace, name=name + '_conv2')
+        if dim_match:
+            shortcut = data
+        else:
+            shortcut = mx.sym.Convolution(data=act1, num_filter=num_filter, kernel=(1,1), stride=stride, no_bias=True,
+                                            workspace=workspace, name=name+'_sc')
+        if memonger:
+            shortcut._set_attr(mirror_stage='True')
+        return conv2 + shortcut
+
+def residual_unit_nnvm(data, num_filter, stride, dim_match, name, bottle_neck=True, bn_mom=0.9, workspace=256, memonger=False):
+    """Return ResNet Unit symbol for building ResNet
+    Parameters
+    ----------
+    data : str
+        Input data
+    num_filter : int
+        Number of output channels
+    bnf : int
+        Bottle neck channels factor with regard to num_filter
+    stride : tuple
+        Stride used in convolution
+    dim_match : Boolean
+        True means channel number between input and output is the same, otherwise means differ
+    name : str
+        Base name of the operators
+    workspace : int
+        Workspace used in convolution operator
+    """
+    if bottle_neck:
+        # the same as https://github.com/facebook/fb.resnet.torch#notes, a bit difference with origin paper
+        bn1 = nnvm.sym.batch_norm(data=data, epsilon=2e-5, name=name + '_bn1')
+        act1 = nnvm.sym.relu(data=bn1, name=name + '_relu1')
+        conv1 = nnvm.sym.conv2d(data=act1, channels=int(num_filter*0.25), kernel_size=(1,1), strides=(1,1), padding=(0,0),
+                                   use_bias=False, name=name + '_conv1')
+        bn2 = nnvm.sym.batch_norm(data=conv1, epsilon=2e-5, name=name + '_bn2')
+        act2 = nnvm.sym.relu(data=bn2, name=name + '_relu2')
+        conv2 = nnvm.sym.conv2d(data=act2, channels=int(num_filter*0.25), kernel_size=(3,3), strides=stride, padding=(1,1),
+                                   use_bias=False, name=name + '_conv2')
+        bn3 = nnvm.sym.batch_norm(data=conv2, epsilon=2e-5, name=name + '_bn3')
+        act3 = nnvm.sym.relu(data=bn3, name=name + '_relu3')
+        conv3 = nnvm.sym.conv2d(data=act3, channels=num_filter, kernel_size=(1,1), strides=(1,1), padding=(0,0), use_bias=False,
+                                   name=name + '_conv3')
+        if dim_match:
+            shortcut = data
+        else:
+            shortcut = nnvm.sym.conv2d(data=act1, channels=num_filter, kernel_size=(1,1), strides=stride, use_bias=False,
+                                            name=name+'_sc')
+        if memonger:
+            shortcut._set_attr(mirror_stage='True')
+        return nnvm.sym.elemwise_add(conv3, shortcut)
+    else:
+        bn1 = nnvm.sym.batch_norm(data=data, epsilon=2e-5, name=name + '_bn1')
+        act1 = nnvm.sym.relu(data=bn1, name=name + '_relu1')
+        conv1 = nnvm.sym.conv2d(data=act1, channels=num_filter, kernel_size=(3,3), strides=stride, padding=(1,1),
+                                      use_bias=False, name=name + '_conv1')
+        bn2 = nnvm.sym.batch_norm(data=conv1, epsilon=2e-5, name=name + '_bn2')
+        act2 = nnvm.sym.relu(data=bn2, name=name + '_relu2')
+        conv2 = nnvm.sym.conv2d(data=act2, channels=num_filter, kernel_size=(3,3), strides=(1,1), padding=(1,1),
+                                      use_bias=False, name=name + '_conv2')
+        if dim_match:
+            shortcut = data
+        else:
+            shortcut = nnvm.sym.conv2d(data=act1, channels=num_filter, kernel_size=(1,1), strides=stride, use_bias=False,
+                                            name=name+'_sc')
+        if memonger:
+            shortcut._set_attr(mirror_stage='True')
+        return nnvm.sym.elemwise_add(conv2, shortcut)
+
+def resnet(units, num_stages, filter_list, num_classes, image_shape, bottle_neck=True, bn_mom=0.9, workspace=256, dtype='float32', memonger=False):
+    """Return ResNet symbol of
+    Parameters
+    ----------
+    units : list
+        Number of units in each stage
+    num_stages : int
+        Number of stage
+    filter_list : list
+        Channel size of each stage
+    num_classes : int
+        Ouput size of symbol
+    dataset : str
+        Dataset type, only cifar10 and imagenet supports
+    workspace : int
+        Workspace used in convolution operator
+    dtype : str
+        Precision (float32 or float16)
+    """
+    num_unit = len(units)
+    assert(num_unit == num_stages)
+    data = mx.sym.Variable(name='data')
+    if dtype == 'float32':
+        # data = mx.sym.identity(data=data, name='id')
+        data = data
+    else:
+        if dtype == 'float16':
+            data = mx.sym.Cast(data=data, dtype=np.float16)
+    data = mx.sym.BatchNorm(data=data, fix_gamma=True, eps=2e-5, momentum=bn_mom, name='bn_data')
+    (nchannel, height, width) = image_shape
+    if height <= 32:            # such as cifar10
+        body = mx.sym.Convolution(data=data, num_filter=filter_list[0], kernel=(3, 3), stride=(1,1), pad=(1, 1),
+                                  no_bias=True, name="conv0", workspace=workspace)
+    else:                       # often expected to be 224 such as imagenet
+        body = mx.sym.Convolution(data=data, num_filter=filter_list[0], kernel=(7, 7), stride=(2,2), pad=(3, 3),
+                                  no_bias=True, name="conv0", workspace=workspace)
+        body = mx.sym.BatchNorm(data=body, fix_gamma=False, eps=2e-5, momentum=bn_mom, name='bn0')
+        body = mx.sym.Activation(data=body, act_type='relu', name='relu0')
+        body = mx.sym.Pooling(data=body, kernel=(3, 3), stride=(2,2), pad=(1,1), pool_type='max')
+
+    for i in range(num_stages):
+        body = residual_unit(body, filter_list[i+1], (1 if i==0 else 2, 1 if i==0 else 2), False,
+                             name='stage%d_unit%d' % (i + 1, 1), bottle_neck=bottle_neck, workspace=workspace,
+                             memonger=memonger)
+        for j in range(units[i]-1):
+            body = residual_unit(body, filter_list[i+1], (1,1), True, name='stage%d_unit%d' % (i + 1, j + 2),
+                                 bottle_neck=bottle_neck, workspace=workspace, memonger=memonger)
+    bn1 = mx.sym.BatchNorm(data=body, fix_gamma=False, eps=2e-5, momentum=bn_mom, name='bn1')
+    relu1 = mx.sym.Activation(data=bn1, act_type='relu', name='relu1')
+    # Although kernel is not used here when global_pool=True, we should put one
+    pool1 = mx.sym.Pooling(data=relu1, global_pool=True, kernel=(7, 7), pool_type='avg', name='pool1')
+    flat = mx.sym.Flatten(data=pool1)
+    fc1 = mx.sym.FullyConnected(data=flat, num_hidden=num_classes, name='fc1')
+    if dtype == 'float16':
+        fc1 = mx.sym.Cast(data=fc1, dtype=np.float32)
+    return mx.sym.softmax(data=fc1, name='softmax')
+
+def resnet_nnvm(units, num_stages, filter_list, num_classes, image_shape, bottle_neck=True, bn_mom=0.9, workspace=256, dtype='float32', memonger=False):
+    """Return ResNet symbol of
+    Parameters
+    ----------
+    units : list
+        Number of units in each stage
+    num_stages : int
+        Number of stage
+    filter_list : list
+        Channel size of each stage
+    num_classes : int
+        Ouput size of symbol
+    dataset : str
+        Dataset type, only cifar10 and imagenet supports
+    workspace : int
+        Workspace used in convolution operator
+    dtype : str
+        Precision (float32 or float16)
+    """
+    num_unit = len(units)
+    assert(num_unit == num_stages)
+    data = nnvm.sym.Variable(name='data')
+    if dtype == 'float32':
+        # data = nnvm.sym.identity(data=data, name='id')
+        data = data
+    else:
+        if dtype == 'float16':
+            data = nnvm.sym.cast(data=data, dtype=np.float16)
+    data = nnvm.sym.batch_norm(data=data, epsilon=2e-5, name='bn_data')
+    (nchannel, height, width) = image_shape
+    if height <= 32:            # such as cifar10
+        body = nnvm.sym.conv2d(data=data, channels=filter_list[0], kernel_size=(3, 3), strides=(1,1), padding=(1, 1),
+                                  use_bias=False, name="conv0")
+    else:                       # often expected to be 224 such as imagenet
+        body = nnvm.sym.conv2d(data=data, channels=filter_list[0], kernel_size=(7, 7), strides=(2,2), padding=(3, 3),
+                                  use_bias=False, name="conv0")
+        body = nnvm.sym.batch_norm(data=body, epsilon=2e-5, name='bn0')
+        body = nnvm.sym.relu(data=body, name='relu0')
+        body = nnvm.sym.max_pool2d(data=body, pool_size=(3, 3), strides=(2,2), padding=(1,1))
+
+    for i in range(num_stages):
+        body = residual_unit_nnvm(body, filter_list[i+1], (1 if i==0 else 2, 1 if i==0 else 2), False,
+                             name='stage%d_unit%d' % (i + 1, 1), bottle_neck=bottle_neck,
+                             memonger=memonger)
+        for j in range(units[i]-1):
+            body = residual_unit_nnvm(body, filter_list[i+1], (1,1), True, name='stage%d_unit%d' % (i + 1, j + 2),
+                                 bottle_neck=bottle_neck, memonger=memonger)
+    bn1 = nnvm.sym.batch_norm(data=body, epsilon=2e-5, name='bn1')
+    relu1 = nnvm.sym.relu(data=bn1, name='relu1')
+    # Although kernel is not used here when global_pool=True, we should put one
+    pool1 = nnvm.sym.global_avg_pool2d(data=relu1, name='pool1')
+    flat = nnvm.sym.flatten(data=pool1)
+    fc1 = nnvm.sym.dense(data=flat, units=num_classes, name='fc1')
+    if dtype == 'float16':
+        fc1 = nnvm.sym.cast(data=fc1, dtype=np.float32)
+    return nnvm.sym.softmax(data=fc1, name='softmax')
+
+def get_symbol(num_classes, num_layers, image_shape, conv_workspace=256, dtype='float32', lib='mxnet', **kwargs):
+    """
+    Adapted from https://github.com/tornadomeet/ResNet/blob/master/train_resnet.py
+    Original author Wei Wu
+    """
+    image_shape = [int(l) for l in image_shape.split(',')]
+    (nchannel, height, width) = image_shape
+    if height <= 28:
+        num_stages = 3
+        if (num_layers-2) % 9 == 0 and num_layers >= 164:
+            per_unit = [(num_layers-2)//9]
+            filter_list = [16, 64, 128, 256]
+            bottle_neck = True
+        elif (num_layers-2) % 6 == 0 and num_layers < 164:
+            per_unit = [(num_layers-2)//6]
+            filter_list = [16, 16, 32, 64]
+            bottle_neck = False
+        else:
+            raise ValueError("no experiments done on num_layers {}, you can do it yourself".format(num_layers))
+        units = per_unit * num_stages
+    else:
+        if num_layers >= 50:
+            filter_list = [64, 256, 512, 1024, 2048]
+            bottle_neck = True
+        else:
+            filter_list = [64, 64, 128, 256, 512]
+            bottle_neck = False
+        num_stages = 4
+        if num_layers == 18:
+            units = [2, 2, 2, 2]
+        elif num_layers == 34:
+            units = [3, 4, 6, 3]
+        elif num_layers == 50:
+            units = [3, 4, 6, 3]
+        elif num_layers == 101:
+            units = [3, 4, 23, 3]
+        elif num_layers == 152:
+            units = [3, 8, 36, 3]
+        elif num_layers == 200:
+            units = [3, 24, 36, 3]
+        elif num_layers == 269:
+            units = [3, 30, 48, 8]
+        else:
+            raise ValueError("no experiments done on num_layers {}, you can do it yourself".format(num_layers))
+
+    return resnet(units       = units,
+                  num_stages  = num_stages,
+                  filter_list = filter_list,
+                  num_classes = num_classes,
+                  image_shape = image_shape,
+                  bottle_neck = bottle_neck,
+                  workspace   = conv_workspace,
+                  dtype       = dtype) if lib == 'mxnet' else \
+        resnet_nnvm(units       = units,
+                      num_stages  = num_stages,
+                      filter_list = filter_list,
+                      num_classes = num_classes,
+                      image_shape = image_shape,
+                      bottle_neck = bottle_neck,
+                      workspace   = conv_workspace,
+                      dtype       = dtype)
diff --git a/nnvm/tests/python/frontend/mxnet/model_zoo/vgg.py b/nnvm/tests/python/frontend/mxnet/model_zoo/vgg.py
new file mode 100644
index 000000000000..1992aa0ccc1c
--- /dev/null
+++ b/nnvm/tests/python/frontend/mxnet/model_zoo/vgg.py
@@ -0,0 +1,128 @@
+# 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.
+
+"""References:
+
+Simonyan, Karen, and Andrew Zisserman. "Very deep convolutional networks for
+large-scale image recognition." arXiv preprint arXiv:1409.1556 (2014).
+"""
+
+import mxnet as mx
+import nnvm
+import numpy as np
+
+def get_feature(internel_layer, layers, filters, batch_norm = False, **kwargs):
+    for i, num in enumerate(layers):
+        for j in range(num):
+            internel_layer = mx.sym.Convolution(data = internel_layer, kernel=(3, 3), pad=(1, 1), num_filter=filters[i], name="conv%s_%s" %(i + 1, j + 1))
+            if batch_norm:
+                internel_layer = mx.symbol.BatchNorm(data=internel_layer, name="bn%s_%s" %(i + 1, j + 1))
+            internel_layer = mx.sym.Activation(data=internel_layer, act_type="relu", name="relu%s_%s" %(i + 1, j + 1))
+        internel_layer = mx.sym.Pooling(data=internel_layer, pool_type="max", kernel=(2, 2), stride=(2,2), name="pool%s" %(i + 1))
+    return internel_layer
+
+def get_feature_nnvm(internel_layer, layers, filters, batch_norm = False, **kwargs):
+    for i, num in enumerate(layers):
+        for j in range(num):
+            internel_layer = nnvm.sym.conv2d(data = internel_layer, kernel_size=(3, 3), padding=(1, 1), channels=filters[i], name="conv%s_%s" %(i + 1, j + 1))
+            if batch_norm:
+                internel_layer = nnvm.symbol.batch_norm(data=internel_layer, name="bn%s_%s" %(i + 1, j + 1))
+            internel_layer = nnvm.sym.relu(data=internel_layer, name="relu%s_%s" %(i + 1, j + 1))
+        internel_layer = nnvm.sym.max_pool2d(data=internel_layer, pool_size=(2, 2), strides=(2,2), name="pool%s" %(i + 1))
+    return internel_layer
+
+def get_classifier(input_data, num_classes, **kwargs):
+    flatten = mx.sym.Flatten(data=input_data, name="flatten")
+    fc6 = mx.sym.FullyConnected(data=flatten, num_hidden=4096, name="fc6")
+    relu6 = mx.sym.Activation(data=fc6, act_type="relu", name="relu6")
+    drop6 = mx.sym.Dropout(data=relu6, p=0.5, name="drop6")
+    fc7 = mx.sym.FullyConnected(data=drop6, num_hidden=4096, name="fc7")
+    relu7 = mx.sym.Activation(data=fc7, act_type="relu", name="relu7")
+    drop7 = mx.sym.Dropout(data=relu7, p=0.5, name="drop7")
+    fc8 = mx.sym.FullyConnected(data=drop7, num_hidden=num_classes, name="fc8")
+    return fc8
+
+def get_classifier_nnvm(input_data, num_classes, **kwargs):
+    flatten = nnvm.sym.flatten(data=input_data, name="flatten")
+    fc6 = nnvm.sym.dense(data=flatten, units=4096, name="fc6")
+    relu6 = nnvm.sym.relu(data=fc6, name="relu6")
+    drop6 = nnvm.sym.dropout(data=relu6, rate=0.5, name="drop6")
+    fc7 = nnvm.sym.dense(data=drop6, units=4096, name="fc7")
+    relu7 = nnvm.sym.relu(data=fc7, name="relu7")
+    drop7 = nnvm.sym.dropout(data=relu7, rate=0.5, name="drop7")
+    fc8 = nnvm.sym.dense(data=drop7, units=num_classes, name="fc8")
+    return fc8
+
+def get_symbol(num_classes, num_layers=11, batch_norm=False, dtype='float32', **kwargs):
+    """
+    Parameters
+    ----------
+    num_classes : int, default 1000
+        Number of classification classes.
+    num_layers : int
+        Number of layers for the variant of densenet. Options are 11, 13, 16, 19.
+    batch_norm : bool, default False
+        Use batch normalization.
+    dtype: str, float32 or float16
+        Data precision.
+    """
+    vgg_spec = {11: ([1, 1, 2, 2, 2], [64, 128, 256, 512, 512]),
+                13: ([2, 2, 2, 2, 2], [64, 128, 256, 512, 512]),
+                16: ([2, 2, 3, 3, 3], [64, 128, 256, 512, 512]),
+                19: ([2, 2, 4, 4, 4], [64, 128, 256, 512, 512])}
+    if not vgg_spec.has_key(num_layers):
+        raise ValueError("Invalide num_layers {}. Possible choices are 11,13,16,19.".format(num_layers))
+    layers, filters = vgg_spec[num_layers]
+    data = mx.sym.Variable(name="data")
+    if dtype == 'float16':
+        data = mx.sym.Cast(data=data, dtype=np.float16)
+    feature = get_feature(data, layers, filters, batch_norm)
+    classifier = get_classifier(feature, num_classes)
+    if dtype == 'float16':
+        classifier = mx.sym.Cast(data=classifier, dtype=np.float32)
+    symbol = mx.sym.softmax(data=classifier, name='softmax')
+    return symbol
+
+def get_symbol_nnvm(num_classes, num_layers=11, batch_norm=False, dtype='float32', **kwargs):
+    """
+    Parameters
+    ----------
+    num_classes : int, default 1000
+        Number of classification classes.
+    num_layers : int
+        Number of layers for the variant of densenet. Options are 11, 13, 16, 19.
+    batch_norm : bool, default False
+        Use batch normalization.
+    dtype: str, float32 or float16
+        Data precision.
+    """
+    vgg_spec = {11: ([1, 1, 2, 2, 2], [64, 128, 256, 512, 512]),
+                13: ([2, 2, 2, 2, 2], [64, 128, 256, 512, 512]),
+                16: ([2, 2, 3, 3, 3], [64, 128, 256, 512, 512]),
+                19: ([2, 2, 4, 4, 4], [64, 128, 256, 512, 512])}
+    if not vgg_spec.has_key(num_layers):
+        raise ValueError("Invalide num_layers {}. Possible choices are 11,13,16,19.".format(num_layers))
+    layers, filters = vgg_spec[num_layers]
+    data = nnvm.sym.Variable(name="data")
+    if dtype == 'float16':
+        data = nnvm.sym.cast(data=data, dtype=np.float16)
+    feature = get_feature_nnvm(data, layers, filters, batch_norm)
+    classifier = get_classifier_nnvm(feature, num_classes)
+    if dtype == 'float16':
+        classifier = nnvm.sym.cast(data=classifier, dtype=np.float32)
+    symbol = nnvm.sym.softmax(data=classifier, name='softmax')
+    return symbol
diff --git a/nnvm/tests/python/frontend/mxnet/test_forward.py b/nnvm/tests/python/frontend/mxnet/test_forward.py
new file mode 100644
index 000000000000..8fcc106e4db2
--- /dev/null
+++ b/nnvm/tests/python/frontend/mxnet/test_forward.py
@@ -0,0 +1,88 @@
+import numpy as np
+
+import topi
+import tvm
+import nnvm.symbol as sym
+import nnvm.compiler
+import nnvm.runtime
+from nnvm import frontend
+import mxnet as mx
+import model_zoo
+
+USE_GPU=True
+
+def default_target():
+    if USE_GPU:
+        return 'cuda'
+    else:
+        return 'llvm'
+
+def default_ctx():
+    if USE_GPU:
+        return tvm.gpu(0)
+    else:
+        return tvm.cpu(0)
+
+def test_mxnet_frontend_impl(mx_symbol, data_shape=(2, 3, 224, 224), out_shape=(2, 1000)):
+    def get_mxnet_output(symbol, x, dtype='float32'):
+        from collections import namedtuple
+        Batch = namedtuple('Batch', ['data'])
+        mod = mx.mod.Module(symbol, label_names=None)
+        mod.bind(data_shapes=[('data', x.shape)], for_training=False)
+        mod.init_params()
+        mod.forward(Batch([mx.nd.array(x.astype(dtype))]))
+        out = mod.get_outputs()[0].asnumpy()
+        args, auxs = mod.get_params()
+        return out, args, auxs
+
+    def get_tvm_output(symbol, x, args, auxs, dtype='float32'):
+        dshape = x.shape
+        shape_dict = {'data': dshape}
+        for k, v in args.items():
+            shape_dict[k] = v.shape
+        for k, v in auxs.items():
+            shape_dict[k] = v.shape
+        graph, lib, _ = nnvm.compiler.build(symbol, default_target(), shape_dict)
+        m = nnvm.runtime.create(graph, lib, default_ctx())
+        # get member functions
+        set_input, run, get_output = m['set_input'], m['run'], m['get_output']
+        # set inputs
+        set_input('data', tvm.nd.array(x.astype(dtype)))
+        for k, v in args.items():
+            set_input(k, tvm.nd.array(v.asnumpy().astype(dtype)))
+        for k, v in auxs.items():
+            set_input(k, tvm.nd.array(v.asnumpy().astype(dtype)))
+        # execute
+        run()
+        # get outputs
+        out = tvm.nd.empty(out_shape, dtype)
+        get_output(0, out)
+        return out.asnumpy()
+
+    # random input
+    dtype = 'float32'
+    x = np.random.uniform(size=data_shape)
+    mx_out, args, auxs = get_mxnet_output(mx_symbol, x, dtype)
+    new_sym = frontend.from_mxnet(mx_symbol)
+    tvm_out = get_tvm_output(new_sym, x, args, auxs, dtype)
+    np.testing.assert_allclose(mx_out, tvm_out, rtol=1e-5)
+
+def test_forward_mlp():
+    mlp = model_zoo.mx_mlp
+    test_mxnet_frontend_impl(mlp)
+
+def test_forward_vgg():
+    for n in [11]:
+        mx_sym = model_zoo.mx_vgg[n]
+        test_mxnet_frontend_impl(mx_sym)
+
+def test_forward_resnet():
+    for n in [18]:
+        mx_sym = model_zoo.mx_resnet[n]
+        test_mxnet_frontend_impl(mx_sym)
+
+if __name__ == '__main__':
+    test_forward_mlp()
+    # waiting for max_pool2d
+    # test_forward_vgg()
+    # test_forward_resnet()
diff --git a/nnvm/tests/python/frontend/mxnet/test_graph.py b/nnvm/tests/python/frontend/mxnet/test_graph.py
new file mode 100644
index 000000000000..7701aa86ae58
--- /dev/null
+++ b/nnvm/tests/python/frontend/mxnet/test_graph.py
@@ -0,0 +1,38 @@
+import mxnet as mx
+import nnvm
+from nnvm.compiler import graph_util, graph_attr
+import model_zoo
+
+def compare_graph(sym1, sym2, ishape=(2, 3, 224, 224)):
+    g1 = nnvm.graph.create(sym1)
+    g2 = nnvm.graph.create(sym2)
+    graph_attr.set_shape_inputs(g1, {'data':ishape})
+    graph_attr.set_shape_inputs(g2, {'data':ishape})
+    g1 = g1.apply("InferShape").apply("SimplifyInference")
+    g2 = g2.apply("InferShape").apply("SimplifyInference")
+    graph_util.check_graph_equal(g1, g2)
+
+def test_mlp():
+    mx_sym = model_zoo.mx_mlp
+    from_mx_sym = nnvm.frontend.from_mxnet(mx_sym)
+    nnvm_sym = model_zoo.nnvm_mlp
+    compare_graph(from_mx_sym, nnvm_sym)
+
+def test_vgg():
+    for n in [11, 13, 16, 19]:
+        mx_sym = model_zoo.mx_vgg[n]
+        from_mx_sym = nnvm.frontend.from_mxnet(mx_sym)
+        nnvm_sym = model_zoo.nnvm_vgg[n]
+        compare_graph(from_mx_sym, nnvm_sym)
+
+def test_resnet():
+    for n in [18, 34, 50, 101]:
+        mx_sym = model_zoo.mx_resnet[n]
+        from_mx_sym = nnvm.frontend.from_mxnet(mx_sym)
+        nnvm_sym = model_zoo.nnvm_resnet[n]
+        compare_graph(from_mx_sym, nnvm_sym)
+
+if __name__ == '__main__':
+    test_mlp()
+    test_vgg()
+    test_resnet()

From f84b8aa44858b013d3a67d3c5aa6c706f367c85c Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 23 Sep 2017 00:01:55 -0700
Subject: [PATCH 484/949] [TOP][COMPILER] sum, min, max, transpose, fix dense
 (#28)

---
 nnvm/python/nnvm/frontend/mxnet.py            | 23 ++++----
 nnvm/python/nnvm/top/__init__.py              |  1 +
 nnvm/python/nnvm/top/attr_dict.py             |  4 +-
 nnvm/python/nnvm/top/nn.py                    | 13 ++---
 nnvm/python/nnvm/top/reduction.py             | 47 ++++++++++++++++
 nnvm/python/nnvm/top/tensor.py                |  3 +-
 nnvm/python/nnvm/top/transform.py             | 16 +++++-
 nnvm/tests/python/compiler/test_op_fusion.py  | 21 +++++++
 nnvm/tests/python/compiler/test_top_level1.py | 43 +++++++-------
 nnvm/tests/python/compiler/test_top_level4.py | 56 +++++++++++++++++++
 10 files changed, 180 insertions(+), 47 deletions(-)
 create mode 100644 nnvm/python/nnvm/top/reduction.py
 create mode 100644 nnvm/tests/python/compiler/test_top_level4.py

diff --git a/nnvm/python/nnvm/frontend/mxnet.py b/nnvm/python/nnvm/frontend/mxnet.py
index 5e1ea0457d60..629d686597db 100644
--- a/nnvm/python/nnvm/frontend/mxnet.py
+++ b/nnvm/python/nnvm/frontend/mxnet.py
@@ -1,3 +1,4 @@
+# pylint: disable=invalid-name
 """MXNet symbol frontend."""
 from __future__ import absolute_import as _abs
 import json
@@ -155,14 +156,14 @@ def _split(attrs):
     return op_name, new_attrs
 
 _identity_list = ['__add_scalar__', '__add_symbol__', '__div_scalar__',
-                '__div_symbol__', '__mul_scalar__', '__mul_symbol__',
-                '__pow_scalar__', '__rdiv_scalar__', '__rpow_scalar__',
-                '__rsub_scalar__', '__sub_scalar__', '__sub_symbol__',
-                'broadcast_add', 'broadcast_div', 'broadcast_mul',
-                'broadcast_sub', 'broadcast_to', 'cast', 'elemwise_add',
-                'elemwise_div', 'elemwise_mul', 'elemwise_sub', 'exp',
-                'flatten', 'log', 'log_softmax', 'max', 'min', 'negative',
-                'relu', 'sigmoid', 'softmax', 'sum', 'tanh', 'transpose']
+                  '__div_symbol__', '__mul_scalar__', '__mul_symbol__',
+                  '__pow_scalar__', '__rdiv_scalar__', '__rpow_scalar__',
+                  '__rsub_scalar__', '__sub_scalar__', '__sub_symbol__',
+                  'broadcast_add', 'broadcast_div', 'broadcast_mul',
+                  'broadcast_sub', 'broadcast_to', 'cast', 'elemwise_add',
+                  'elemwise_div', 'elemwise_mul', 'elemwise_sub', 'exp',
+                  'flatten', 'log', 'log_softmax', 'max', 'min', 'negative',
+                  'relu', 'sigmoid', 'softmax', 'sum', 'tanh', 'transpose']
 
 _convert_map = {
     'null'          : _variable,
@@ -190,8 +191,8 @@ def _split(attrs):
 }
 
 def _convert_symbol(op_name, attrs,
-                    identity_list=_identity_list,
-                    convert_map=_convert_map):
+                    identity_list=None,
+                    convert_map=None):
     """Convert from mxnet op to nnvm op.
     The converter must specify some conversions explicitly to
     support gluon format ops such as conv2d...
@@ -214,6 +215,8 @@ def _convert_symbol(op_name, attrs,
     (op_name, attrs)
         Converted (op_name, attrs) for nnvm.
     """
+    identity_list = identity_list if identity_list else _identity_list
+    convert_map = convert_map if convert_map else _convert_map
     if op_name in identity_list:
         pass
     elif op_name in convert_map:
diff --git a/nnvm/python/nnvm/top/__init__.py b/nnvm/python/nnvm/top/__init__.py
index 11e776f1b0dd..3535c463f9ea 100644
--- a/nnvm/python/nnvm/top/__init__.py
+++ b/nnvm/python/nnvm/top/__init__.py
@@ -3,3 +3,4 @@
 from . import tensor
 from . import nn
 from . import transform
+from . import reduction
diff --git a/nnvm/python/nnvm/top/attr_dict.py b/nnvm/python/nnvm/top/attr_dict.py
index 1265d4cf7441..b5baf3decbde 100644
--- a/nnvm/python/nnvm/top/attr_dict.py
+++ b/nnvm/python/nnvm/top/attr_dict.py
@@ -1,7 +1,5 @@
 # pylint: disable=invalid-name
 """Attr dictionary object used by schedule functions"""
-
-import json
 import tvm
 
 _dict_get = tvm.get_global_func("nnvm.compiler._dict_get")
@@ -51,7 +49,7 @@ def get_int_tuple(self, key):
         tuple : tuple of int
             The result tuple
         """
-        return tuple(json.loads(self[key]))
+        return tuple(int(x) for x in self[key][1:-1].split(",") if x)
 
     def get_int(self, key):
         """Get integer from attr dict
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index 3c3f737f4daf..c10830b79556 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -20,9 +20,9 @@ def compute_relu(attrs, inputs, _):
 
 # leaky_relu
 @reg.register_compute("leaky_relu")
-def compute_relu(attrs, inputs, _):
+def compute_leaky_relu(attrs, inputs, _):
     """Compute definition of relu"""
-    return topi.nn.leaky_relu(inputs[0])
+    return topi.nn.leaky_relu(inputs[0], attrs.get_float("alpha"))
 
 reg.register_schedule("leaky_relu", _fschedule_broadcast)
 reg.register_pattern("leaky_relu", OpPattern.ELEMWISE)
@@ -62,20 +62,19 @@ def schedule_softmax(_, outs, target):
 def compute_dense(attrs, inputs, _):
     """Compute definition of dense"""
     if attrs.get_bool("use_bias"):
-        return topi.nn.fully_connected_with_bias(
-            inputs[0], inputs[1], inputs[2])
-    return topi.nn.fully_connected(inputs[0], inputs[1])
+        return topi.nn.dense(inputs[0], inputs[1], bias=inputs[2])
+    return topi.nn.dense(inputs[0], inputs[1])
 
 @reg.register_schedule("dense")
 def schedule_dense(_, outs, target):
     """Schedule definition of dense"""
     if target == "cuda":
-        raise ValueError("fully_connected not yet implemented")
+        return topi.cuda.schedule_dense(outs)
     # naive schedule
     return tvm.create_schedule([x.op for x in outs])
 
 # register extern for now, change me when fusion is enabled.
-reg.register_pattern("dense", OpPattern.OPAQUE)
+reg.register_pattern("dense", OpPattern.OUT_ELEMWISE_FUSABLE)
 
 
 # conv
diff --git a/nnvm/python/nnvm/top/reduction.py b/nnvm/python/nnvm/top/reduction.py
new file mode 100644
index 000000000000..345aca74b68b
--- /dev/null
+++ b/nnvm/python/nnvm/top/reduction.py
@@ -0,0 +1,47 @@
+# pylint: disable=invalid-name, unused-argument
+"""Reduction ops"""
+from __future__ import absolute_import
+
+import tvm
+import topi
+import topi.cuda
+from ..compiler import registry as reg
+from ..compiler import OpPattern
+
+def _schedule_reduce(_, outs, target):
+    """Generic schedule for reduce"""
+    if target == "cuda":
+        return topi.cuda.schedule_reduce(outs)
+    assert target.startswith("llvm")
+    s = tvm.create_schedule([x.op for x in outs])
+    x = outs[0]
+    tvm.schedule.AutoInlineInjective(s)
+    s[x].fuse(s[x].op.axis)
+    return s
+
+_fschedule_reduce = tvm.convert(_schedule_reduce)
+
+def _compute_reduce(f):
+    """auxiliary function"""
+    def _compute(attrs, inputs, out_info):
+        axis = attrs.get_int_tuple("axis")
+        keepdims = attrs.get_bool("keepdims")
+        if axis:
+            return f(inputs[0], axis=axis, keepdims=keepdims)
+        return f(inputs[0], keepdims=keepdims)
+    return _compute
+
+# sum
+reg.register_compute("sum", _compute_reduce(topi.sum))
+reg.register_pattern("sum", OpPattern.COMM_REDUCE)
+reg.register_schedule("sum", _fschedule_reduce)
+
+# max
+reg.register_compute("max", _compute_reduce(topi.max))
+reg.register_pattern("max", OpPattern.COMM_REDUCE)
+reg.register_schedule("max", _fschedule_reduce)
+
+# min
+reg.register_compute("min", _compute_reduce(topi.min))
+reg.register_pattern("min", OpPattern.COMM_REDUCE)
+reg.register_schedule("min", _fschedule_reduce)
diff --git a/nnvm/python/nnvm/top/tensor.py b/nnvm/python/nnvm/top/tensor.py
index cc35e6e8ba4a..0ee3f3d12f6e 100644
--- a/nnvm/python/nnvm/top/tensor.py
+++ b/nnvm/python/nnvm/top/tensor.py
@@ -19,9 +19,10 @@ def _schedule_injective(_, outs, target):
     s[x].fuse(s[x].op.axis)
     return s
 
+
 def _compute_binary_scalar(f):
     """auxiliary function"""
-    @tvm.tag_scope("ewise")
+    @tvm.tag_scope(topi.tag.ELEMWISE)
     def _compute(attrs, x, _):
         x = x[0]
         scalar = attrs.get_float("scalar")
diff --git a/nnvm/python/nnvm/top/transform.py b/nnvm/python/nnvm/top/transform.py
index a69caf39bcd0..8fc6130db90f 100644
--- a/nnvm/python/nnvm/top/transform.py
+++ b/nnvm/python/nnvm/top/transform.py
@@ -4,11 +4,11 @@
 
 import tvm
 import topi
-from .tensor import _fschedule_broadcast
+from .tensor import _fschedule_broadcast, _fschedule_injective
 from ..compiler import registry as reg
 from ..compiler import OpPattern
 
-# Need add reshape, transpose
+# Need add reshape
 @reg.register_compute("expand_dims")
 def compute_expand_dims(attrs, inputs, out_info):
     """Compute definition of expand_dims"""
@@ -19,6 +19,16 @@ def compute_expand_dims(attrs, inputs, out_info):
 reg.register_schedule("expand_dims", _fschedule_broadcast)
 
 
+@reg.register_compute("transpose")
+def compute_transpose(attrs, inputs, out_info):
+    """Compute definition of expand_dims"""
+    axes = attrs.get_int_tuple("axes")
+    axes = tuple(axes) if axes else None
+    return topi.transpose(inputs[0], axes)
+reg.register_pattern("transpose", OpPattern.INJECTIVE)
+reg.register_schedule("transpose", _fschedule_injective)
+
+
 def _flatten_index(indices, shape):
     """flatten the index to 1D"""
     idx = 0
@@ -38,4 +48,4 @@ def compute_reshape(attrs, inputs, out_info):
     x = inputs[0]
     return tvm.compute(oshape, lambda *i: x(_flatten_index(i, oshape)))
 reg.register_pattern("reshape", OpPattern.INJECTIVE)
-reg.register_schedule("reshape", _fschedule_broadcast)
+reg.register_schedule("reshape", _fschedule_injective)
diff --git a/nnvm/tests/python/compiler/test_op_fusion.py b/nnvm/tests/python/compiler/test_op_fusion.py
index 45031bf1802b..ca82da2023c0 100644
--- a/nnvm/tests/python/compiler/test_op_fusion.py
+++ b/nnvm/tests/python/compiler/test_op_fusion.py
@@ -56,6 +56,27 @@ def test_conv_ewise_injective():
         np.testing.assert_allclose(out.asnumpy(), c_np, rtol=1e-5)
 
 
+def test_injective_reduce_injective():
+    x = sym.Variable("x")
+    x = sym.flatten(x) + 1
+    y = sym.sum(x, axis=1)
+    dtype = "float32"
+    dshape = (32, 1, 18, 18)
+    shape_dict = {"x": dshape}
+
+    for target, ctx in test_ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
+        m = nnvm.runtime.create(graph, lib, ctx)
+        assert graph.index.num_nodes == 2
+        data = np.random.uniform(size=dshape).astype(dtype)
+        m.run(x=data)
+        c_np = np.sum(data.reshape(32, 18 * 18) + 1, axis=1)
+        # get output
+        out = m.get_output(0, tvm.nd.empty(c_np.shape, dtype))
+        np.testing.assert_allclose(out.asnumpy(), c_np, rtol=1e-5)
+
+
 if __name__ == "__main__":
+    test_injective_reduce_injective()
     test_ewise_injective()
     test_conv_ewise_injective()
diff --git a/nnvm/tests/python/compiler/test_top_level1.py b/nnvm/tests/python/compiler/test_top_level1.py
index dc3423e67322..d03782bb4cb5 100644
--- a/nnvm/tests/python/compiler/test_top_level1.py
+++ b/nnvm/tests/python/compiler/test_top_level1.py
@@ -8,7 +8,8 @@
 
 def test_relu():
     x = sym.Variable("x")
-    y = sym.relu(x)
+    y = sym.leaky_relu(x, alpha=0.3) - 0.2
+    y = sym.relu(y)
     dtype = "float32"
     dshape = (1, 3, 32, 32)
     oshape = dshape
@@ -16,17 +17,12 @@ def test_relu():
         graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
         m = nnvm.runtime.create(graph, lib, ctx)
         # get member functions
-        set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
-        # set input
-        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
-        set_input("x", data)
-        # execute
-        run()
-        # get output
-        out = tvm.nd.empty(oshape, dtype)
-        get_output(0, out)
-        y_np = np.maximum(data.asnumpy(), 0.0)
-        np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+        data = np.random.uniform(size=dshape).astype(dtype)
+        m.run(x=data)
+        data = (data < 0) * data * 0.3 + (data>0) * data - 0.2
+        data = (data > 0) * data
+        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
+        np.testing.assert_allclose(out.asnumpy(), data, atol=1e-5, rtol=1e-5)
 
 
 def test_exp():
@@ -157,17 +153,18 @@ def test_dense():
         "dense_weight" : (3, 100),
         "dense_bias" : (3,),
     }
-    graph, lib, _ = nnvm.compiler.build(y, "llvm", shape)
-    m = nnvm.runtime.create(graph, lib, tvm.cpu(0))
-    x_np = np.random.uniform(size=shape["x"]).astype(dtype)
-    w_np = np.random.uniform(size=shape["dense_weight"]).astype(dtype)
-    b_np = np.random.uniform(size=shape["dense_bias"]).astype(dtype)
-    res = tvm.nd.empty((10, 3))
-    m.run(x=x_np, dense_weight=w_np, dense_bias=b_np)
-    m.get_output(0, res)
-    res_np = np.dot(x_np, w_np.T) + b_np
-    np.testing.assert_allclose(
-        res.asnumpy(), res_np, atol=1e-5, rtol=1e-5)
+    for target, ctx in test_ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, shape)
+        m = nnvm.runtime.create(graph, lib, ctx)
+        x_np = np.random.uniform(size=shape["x"]).astype(dtype)
+        w_np = np.random.uniform(size=shape["dense_weight"]).astype(dtype)
+        b_np = np.random.uniform(size=shape["dense_bias"]).astype(dtype)
+        res = tvm.nd.empty((10, 3))
+        m.run(x=x_np, dense_weight=w_np, dense_bias=b_np)
+        m.get_output(0, res)
+        res_np = np.dot(x_np, w_np.T) + b_np
+        np.testing.assert_allclose(
+            res.asnumpy(), res_np, atol=1e-5, rtol=1e-5)
 
 
 def test_batchnorm():
diff --git a/nnvm/tests/python/compiler/test_top_level4.py b/nnvm/tests/python/compiler/test_top_level4.py
new file mode 100644
index 000000000000..851fc8b7d46c
--- /dev/null
+++ b/nnvm/tests/python/compiler/test_top_level4.py
@@ -0,0 +1,56 @@
+import numpy as np
+import tvm
+import topi
+import nnvm.symbol as sym
+import nnvm.compiler
+import nnvm.runtime
+from nnvm.testing.config import test_ctx_list
+
+def verify_transpose(dshape, axes):
+    x = sym.Variable("x")
+    if axes:
+        y = sym.transpose(x, axes=axes)
+    else:
+        y = sym.transpose(x)
+    y = y + 1
+    dtype = "float32"
+    for target, ctx in test_ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
+        m = nnvm.runtime.create(graph, lib, ctx)
+        # set input
+        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+        m.run(x=data)
+        out_np = np.transpose(data.asnumpy(), axes=axes) + 1
+        out = m.get_output(0, tvm.nd.empty(out_np.shape))
+        np.testing.assert_allclose(out.asnumpy(), out_np, atol=1e-5, rtol=1e-5)
+
+
+def verify_reduce(dshape, fnp, fsym, **kwargs):
+    x = sym.Variable("x")
+    y = fsym(x + 1, **kwargs)
+    dtype = "float32"
+    for target, ctx in test_ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
+        m = nnvm.runtime.create(graph, lib, ctx)
+        # set input
+        data = np.random.uniform(size=dshape).astype(dtype)
+        out_np = fnp(data + 1, **kwargs)
+        m.run(x=data)
+        out = m.get_output(0, tvm.nd.empty(out_np.shape))
+        np.testing.assert_allclose(out.asnumpy(), out_np, atol=1e-5, rtol=1e-5)
+
+
+def test_tranpose():
+    verify_transpose((2, 3, 4), (0, 2, 1))
+    verify_transpose((2, 3, 4), None)
+
+
+def test_reduce():
+    verify_reduce((2, 3, 4), np.max, sym.max, axis=1, keepdims=True)
+    verify_reduce((4, 4, 3), np.min, sym.min, keepdims=True)
+    verify_reduce((4, 4, 3), np.sum, sym.sum, axis=(0, 2))
+
+
+if __name__ == "__main__":
+    test_reduce()
+    test_tranpose()

From 5b351dc451f447ae8b2d8779353c9be9801eb901 Mon Sep 17 00:00:00 2001
From: "Joshua Z. Zhang" <cheungchih@gmail.com>
Date: Sat, 23 Sep 2017 09:28:55 -0700
Subject: [PATCH 485/949] fix lint (#29)

---
 nnvm/python/nnvm/frontend/mxnet.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/python/nnvm/frontend/mxnet.py b/nnvm/python/nnvm/frontend/mxnet.py
index 629d686597db..4509a31e540c 100644
--- a/nnvm/python/nnvm/frontend/mxnet.py
+++ b/nnvm/python/nnvm/frontend/mxnet.py
@@ -256,7 +256,7 @@ def _from_mxnet_impl(symbol, graph):
         Converted symbol
     """
     try:
-        from mxnet import sym as mx_sym
+        from mxnet import sym as mx_sym  # pylint: disable=import-self
     except ImportError as e:
         raise ImportError('{}. MXNet is required to parse symbols.'.format(e))
 

From e6ef73ae675d3621a78d72be965f9b7872a500dc Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 23 Sep 2017 20:52:04 -0700
Subject: [PATCH 486/949] [COMPILER] GraphHash based cache system, allow dump
 and query duplicated functions. (#30)

---
 nnvm/include/nnvm/graph.h                     |   4 +-
 nnvm/include/nnvm/pass_functions.h            |  11 +
 nnvm/python/nnvm/compiler/__init__.py         |   2 +
 nnvm/python/nnvm/compiler/build_module.py     |   2 +-
 nnvm/python/nnvm/compiler/compile_engine.py   |  99 +++++++
 nnvm/python/nnvm/testing/__init__.py          |   2 +
 nnvm/python/nnvm/testing/config.py            |   2 +-
 nnvm/src/compiler/compile_engine.cc           | 272 ++++++++++++++++++
 nnvm/src/compiler/compile_engine.h            |  96 +++++++
 nnvm/src/compiler/graph_fuse.cc               | 238 ++++++++-------
 .../{graph_deep_compare.cc => graph_hash.cc}  | 108 ++++++-
 nnvm/src/compiler/graph_hash.h                |  82 ++++++
 nnvm/src/core/graph.cc                        |   2 +-
 nnvm/src/pass/print_graph_ir.cc               |   4 +-
 .../python/compiler/test_compiler_cache.py    |  42 +++
 nnvm/tests/python/compiler/test_op_fusion.py  |   8 +-
 nnvm/tests/python/compiler/test_top_level1.py |  18 +-
 nnvm/tests/python/compiler/test_top_level2.py |   6 +-
 nnvm/tests/python/compiler/test_top_level4.py |   7 +-
 19 files changed, 856 insertions(+), 149 deletions(-)
 create mode 100644 nnvm/python/nnvm/compiler/compile_engine.py
 create mode 100644 nnvm/src/compiler/compile_engine.cc
 create mode 100644 nnvm/src/compiler/compile_engine.h
 rename nnvm/src/compiler/{graph_deep_compare.cc => graph_hash.cc} (52%)
 create mode 100644 nnvm/src/compiler/graph_hash.h
 create mode 100644 nnvm/tests/python/compiler/test_compiler_cache.py

diff --git a/nnvm/include/nnvm/graph.h b/nnvm/include/nnvm/graph.h
index 514705f65acd..a555ecd68a65 100644
--- a/nnvm/include/nnvm/graph.h
+++ b/nnvm/include/nnvm/graph.h
@@ -63,11 +63,11 @@ class Graph {
    * \return The indexed graph.
    * \sa IndexedGraph
    */
-  const IndexedGraph& indexed_graph();
+  const IndexedGraph& indexed_graph() const;
 
  private:
   // internal structure of indexed graph
-  std::shared_ptr<const IndexedGraph> indexed_graph_;
+  mutable std::shared_ptr<const IndexedGraph> indexed_graph_;
 };
 
 /*!
diff --git a/nnvm/include/nnvm/pass_functions.h b/nnvm/include/nnvm/pass_functions.h
index 56ac11bab8e6..e4884a196e79 100644
--- a/nnvm/include/nnvm/pass_functions.h
+++ b/nnvm/include/nnvm/pass_functions.h
@@ -41,6 +41,17 @@ inline std::string SaveJSON(Graph graph) {
   return ret.GetAttr<std::string>("json");
 }
 
+
+/*!
+ * \brief Print graph ir
+ * \param graph The graph to be printed
+ * \return The graph ir string.
+ */
+inline std::string PrintGraphIR(Graph graph) {
+  Graph ret = ApplyPass(std::move(graph), "PrintGraphIR");
+  return ret.GetAttr<std::string>("graphir");
+}
+
 /*!
  * \brief Add control flow dependencies between nodes.
  *
diff --git a/nnvm/python/nnvm/compiler/__init__.py b/nnvm/python/nnvm/compiler/__init__.py
index 1d8b9219b4f7..08cdb1850a39 100644
--- a/nnvm/python/nnvm/compiler/__init__.py
+++ b/nnvm/python/nnvm/compiler/__init__.py
@@ -5,6 +5,7 @@
 
 from . import build_module
 from . build_module import build, optimize, build_config
+from . compile_engine import engine, graph_key
 
 from .. import symbol as _symbol
 from .. import graph as _graph
@@ -14,5 +15,6 @@
 
 from .. import top as _top
 
+
 tvm.register_extension(_symbol.Symbol, _symbol.Symbol)
 tvm.register_extension(_graph.Graph, _graph.Graph)
diff --git a/nnvm/python/nnvm/compiler/build_module.py b/nnvm/python/nnvm/compiler/build_module.py
index 27d86e9a6967..18b2a163c692 100644
--- a/nnvm/python/nnvm/compiler/build_module.py
+++ b/nnvm/python/nnvm/compiler/build_module.py
@@ -184,7 +184,7 @@ def build(graph, target, shape, dtype="float32", params=None):
     graph._set_json_attr("target", target, "str")
     graph._set_json_attr("opt_level", cfg.opt_level, "int")
     graph = graph.apply("InferShape").apply("InferType")
-    graph = graph.apply("GraphFusePartition").apply("GraphFuse")
+    graph = graph.apply("GraphFusePartition").apply("GraphFuseCompile")
     libmod = graph_attr._move_out_module(graph, "module")
     return graph, libmod, params
 
diff --git a/nnvm/python/nnvm/compiler/compile_engine.py b/nnvm/python/nnvm/compiler/compile_engine.py
new file mode 100644
index 000000000000..426c56d2d9c3
--- /dev/null
+++ b/nnvm/python/nnvm/compiler/compile_engine.py
@@ -0,0 +1,99 @@
+# pylint: disable=invalid-name
+"""Compiler engine interface to internal engine"""
+import tvm
+
+_list_cache_items = tvm.get_global_func("nnvm.compiler.ListCacheItems")
+_clear_cache = tvm.get_global_func("nnvm.compiler.ClearCache")
+_get_cache_item = tvm.get_global_func("nnvm.compiler.GetCacheItem")
+_set_cache_item = tvm.get_global_func("nnvm.compiler.SetCacheItem")
+_graph_key_get_graph = tvm.get_global_func("nnvm.compiler.GraphKeyGetGraph")
+_make_graph_key = tvm.get_global_func("nnvm.compiler.MakeGraphKey")
+
+@tvm.register_node
+class GraphKey(tvm.node.NodeBase):
+    """Key of a graph compilation context"""
+    @property
+    def graph(self):
+        return _graph_key_get_graph(self)
+
+
+@tvm.register_node
+class GraphCacheEntry(tvm.node.NodeBase):
+    """CacheEntry of compilation into a TVM Function"""
+    pass
+
+
+@tvm.register_node
+class GraphFunc(tvm.node.NodeBase):
+    """Compiled result of a graph into a TVM Function"""
+    pass
+
+
+class Engine(object):
+    """Global singleton compilation engine."""
+    def items(self):
+        """List the available cache key value pairs.
+
+        Returns
+        -------
+        item_list : list of (GraphKey, GraphCacheEntry)
+            The existing cache items
+        """
+        res = _list_cache_items()
+        assert len(res) % 2 == 0
+        return [(res[2*i], res[2*i+1]) for i in range(len(res)/2)]
+
+    def clear_cache(self):
+        """Clear the existing cached functions."""
+        _clear_cache()
+
+    def __setitem__(self, key, value):
+        """Clear the existing cached functions."""
+        if isinstance(value, GraphCacheEntry):
+            _set_cache_item(key, value.graph_func)
+        else:
+            _set_cache_item(key, value)
+
+    def __getitem__(self, key):
+        """Clear the existing cached functions."""
+        return _get_cache_item(key)
+
+    def dump(self):
+        """Return a string representation of engine dump
+
+        Returns
+        -------
+        dump : str
+            The dumped string representation
+        """
+        items = self.items()
+        res = "====================================\n"
+        res += "CompilerEngine dump, %d items cached\n" % len(items)
+        for key, value in items:
+            res += "------------------------------------\n"
+            res += "target={}\n".format(key.target)
+            res += "inputs={}\n".format(key.inputs)
+            res += "use_count={}\n".format(value.use_count)
+            res += "func_name={}\n".format(value.graph_func.func_name)
+            res += key.graph.ir() + "\n"
+        res += "===================================\n"
+        return res
+
+engine = Engine()
+
+
+def graph_key(graph, inputs, target):
+    """Construct a new graph key.
+
+    Parameters
+    ----------
+    graph : Graph
+        The computation graph structure
+
+    inputs : list of Tensor(placeholder)
+        The input requirement to the graph.
+
+    target : str
+        The target of compilation.
+    """
+    return _make_graph_key(graph, inputs, target)
diff --git a/nnvm/python/nnvm/testing/__init__.py b/nnvm/python/nnvm/testing/__init__.py
index 1241e403b1a0..6dd015d872ea 100644
--- a/nnvm/python/nnvm/testing/__init__.py
+++ b/nnvm/python/nnvm/testing/__init__.py
@@ -1 +1,3 @@
 """Utilities for testcase"""
+
+from .config import ctx_list
diff --git a/nnvm/python/nnvm/testing/config.py b/nnvm/python/nnvm/testing/config.py
index 26d1d41014cf..a96e4b4ea8e1 100644
--- a/nnvm/python/nnvm/testing/config.py
+++ b/nnvm/python/nnvm/testing/config.py
@@ -2,7 +2,7 @@
 import os
 import tvm
 
-def test_ctx_list():
+def ctx_list():
     """Get context list for testcases"""
     device_list = os.environ.get("NNVM_TEST_TARGETS", "")
     device_list = (device_list.split(",") if device_list
diff --git a/nnvm/src/compiler/compile_engine.cc b/nnvm/src/compiler/compile_engine.cc
new file mode 100644
index 000000000000..d31612f5a826
--- /dev/null
+++ b/nnvm/src/compiler/compile_engine.cc
@@ -0,0 +1,272 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file compile_engine.cc
+ * \brief The compile engine.
+ */
+#include <dmlc/common.h>
+#include <tvm/ir.h>
+#include <tvm/operation.h>
+#include <nnvm/graph.h>
+#include <nnvm/node.h>
+#include <nnvm/pass_functions.h>
+#include <nnvm/compiler/op_attr_types.h>
+#include <mutex>
+#include "./graph_hash.h"
+#include "./compile_engine.h"
+
+namespace nnvm {
+namespace compiler {
+
+using namespace tvm;
+
+/*!
+ * \brief Get type flag from TVM Type
+ *
+ * \param type the tvm type.
+ * \return corresponding DLDataType
+ */
+int GetTypeFlag(tvm::Type type) {
+  if (type == tvm::Float(32)) return 0;
+  LOG(FATAL) << "cannot convert " << type;
+  return 0;
+}
+// convert from type flag to tvm type.
+Type GetTVMType(int type_flag) {
+  if (type_flag == 0) return tvm::Float(32);
+  LOG(FATAL) << "unknown type_flag=" << type_flag;
+  return Float(32);
+}
+
+// internal compile engine
+class CompileEngine {
+ public:
+  static CompileEngine* Global() {
+    static CompileEngine inst;
+    return &inst;
+  }
+  // lower graph possible get back an cached op.
+  GraphFunc Lower(Graph graph,
+                  const Array<tvm::Tensor>& inputs,
+                  const std::string& target,
+                  const Op* schedule_op_key,
+                  const NodeAttrs& schedule_op_attr) {
+    GraphKey key = GraphKeyNode::make(graph, inputs, target);
+    std::lock_guard<std::mutex> lock(mutex_);
+    auto it = cache_.find(key);
+    if (it != cache_.end()) {
+      ++(it->second->use_count);
+      return it->second->graph_func;
+    }
+    GraphFunc f = DoLower(key->graph, key->inputs, key->target,
+                           schedule_op_key, schedule_op_attr);
+    std::shared_ptr<GraphCacheEntryNode> n = std::make_shared<GraphCacheEntryNode>();
+    n->graph_func = f;
+    n->use_count = 1;
+    cache_[key] = GraphCacheEntry(n);
+    return f;
+  }
+  // List all items in the cache.
+  Array<NodeRef> ListCacheItems() {
+    std::lock_guard<std::mutex> lock(mutex_);
+    Array<NodeRef> items;
+    for (auto& kv : cache_) {
+      items.push_back(kv.first);
+      std::shared_ptr<GraphCacheEntryNode> n =
+          std::make_shared<GraphCacheEntryNode>(*(kv.second.operator->()));
+      items.push_back(GraphCacheEntry(n));
+    }
+    return items;
+  }
+  // Find the function given graph key.
+  GraphCacheEntry Find(const GraphKey& key) {
+    std::lock_guard<std::mutex> lock(mutex_);
+    auto it = cache_.find(key);
+    if (it != cache_.end()) {
+      return it->second;
+    } else {
+      return GraphCacheEntry();
+    }
+  }
+  // Find the function given graph key.
+  void Set(const GraphKey& key, GraphFunc func) {
+    std::lock_guard<std::mutex> lock(mutex_);
+    std::shared_ptr<GraphCacheEntryNode> n = std::make_shared<GraphCacheEntryNode>();
+    n->graph_func = func;
+    n->use_count = 1;
+    cache_[key] = GraphCacheEntry(n);
+  }
+    // Find the function given graph key.
+  void Clear() {
+    std::lock_guard<std::mutex> lock(mutex_);
+    cache_.clear();
+  }
+  // run the actual lowering process
+  GraphFunc DoLower(Graph graph,
+                    const Array<tvm::Tensor>& inputs,
+                    const std::string& target,
+                    const Op* schedule_op_key,
+                    const NodeAttrs& schedule_op_attr) {
+    // shape, type
+    static auto& fcompute =
+        nnvm::Op::GetAttr<FTVMCompute>("FTVMCompute");
+    static auto& fschedule =
+        nnvm::Op::GetAttr<FTVMSchedule>("FTVMSchedule");
+
+    std::vector<TShape> ishape;
+    std::vector<int> idtype;
+
+    for (const tvm::Tensor t : inputs) {
+      std::vector<dim_t> shape;
+      for (Expr v : t->shape) {
+        CHECK(v.as<tvm::ir::IntImm>());
+        shape.push_back(v.as<tvm::ir::IntImm>()->value);
+      }
+      ishape.emplace_back(TShape(shape.begin(), shape.end()));
+      idtype.emplace_back(GetTypeFlag(t->dtype));
+    }
+    graph = pass::InferShape(graph, ishape);
+    graph = pass::InferType(graph, idtype);
+
+    const ShapeVector& shape_vec = graph.GetAttr<ShapeVector>("shape");
+    const DTypeVector& dtype_vec = graph.GetAttr<DTypeVector>("dtype");
+    const IndexedGraph& idx = graph.indexed_graph();
+    CHECK_EQ(inputs.size(), idx.input_nodes().size());
+
+    std::vector<tvm::Tensor> tensor_vec(idx.num_node_entries());
+    for (size_t i = 0; i < idx.input_nodes().size(); ++i) {
+      uint32_t nid = idx.input_nodes()[i];
+      tensor_vec[idx.entry_id(nid, 0)] = inputs[i];
+    }
+
+    std::ostringstream readable_name;
+    readable_name << "fuse";
+    for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+      const auto& inode = idx[nid];
+      if (inode.source->is_variable()) continue;
+      Array<Tensor> inputs, out_info;
+      readable_name << "_" << inode.source->op()->name;
+      // input array
+      for (const IndexedGraph::NodeEntry& e : inode.inputs) {
+        const tvm::Tensor& t = tensor_vec[idx.entry_id(e)];
+        CHECK(t.defined());
+        inputs.push_back(t);
+      }
+      // output hint
+      for (uint32_t i = 0; i < inode.source->num_outputs(); ++i) {
+        Array<Expr> shape;
+        for (int64_t x : shape_vec[idx.entry_id(nid, i)]) {
+          CHECK_LE(x, static_cast<int64_t>(std::numeric_limits<int>::max()));
+          shape.push_back(make_const(Int(32), x));
+        }
+        out_info.push_back(
+            placeholder(shape,
+                        GetTVMType(dtype_vec[idx.entry_id(nid, i)])));
+      }
+      // get default
+      Array<Tensor> out = fcompute[inode.source->op()](
+          inode.source->attrs, inputs, out_info);
+      CHECK_EQ(out.size(), inode.source->num_outputs());
+      // schedule on root node, and use master's schedule
+      for (uint32_t index = 0; index < inode.source->num_outputs(); ++index) {
+        uint32_t eid = idx.entry_id(nid, index);
+        tensor_vec[eid] = out[index];
+      }
+    }
+    // Schedule on final output.
+    Array<Tensor> outputs;
+    Array<Tensor> all_args = inputs;
+    for (const IndexedGraph::NodeEntry& e : idx.outputs()) {
+      const tvm::Tensor& t = tensor_vec[idx.entry_id(e)];
+      CHECK(t.defined());
+      outputs.push_back(t);
+      all_args.push_back(t);
+    }
+    Schedule sch = fschedule[schedule_op_key](
+        schedule_op_attr, outputs, target);
+    std::shared_ptr<GraphFuncNode> gf = std::make_shared<GraphFuncNode>();
+    gf->target = target;
+    gf->func_name = GetUniqeName(readable_name.str());
+    gf->inputs = inputs;
+    gf->outputs = outputs;
+    static const PackedFunc& flower = GetPackedFunc("nnvm.compiler.lower");
+    gf->funcs = flower(sch, all_args, gf->func_name);
+    return GraphFunc(gf);
+  }
+
+ private:
+  // Get unique name
+  std::string GetUniqeName(std::string name) {
+    while (true) {
+      auto it = name_map_.find(name);
+      if (it == name_map_.end()) {
+        name_map_[name] = 1;
+        return name;
+      } else {
+        std::ostringstream os;
+        os << name << "_" << it->second;
+        ++(it->second);
+        name = os.str();
+      }
+    }
+    return name;
+  }
+
+  // global mutex
+  std::mutex mutex_;
+  // the name map
+  std::unordered_map<std::string, int> name_map_;
+  // the compiler cache
+  std::unordered_map<GraphKey, GraphCacheEntry,
+                     GraphKeyHash, GraphKeyEqual> cache_;
+};
+
+GraphFunc GraphLower(Graph graph,
+                     const Array<tvm::Tensor>& inputs,
+                     const std::string& target,
+                     const Op* schedule_op_key,
+                     const NodeAttrs& schedule_op_attr) {
+  return CompileEngine::Global()->Lower(
+      graph, inputs, target, schedule_op_key, schedule_op_attr);
+}
+
+// Expose cache to front end
+TVM_REGISTER_GLOBAL("nnvm.compiler.ListCacheItems")
+.set_body([](tvm::runtime::TVMArgs args, tvm::runtime::TVMRetValue *rv) {
+    *rv = CompileEngine::Global()->ListCacheItems();
+  });
+
+TVM_REGISTER_GLOBAL("nnvm.compiler.ClearCache")
+.set_body([](tvm::runtime::TVMArgs args, tvm::runtime::TVMRetValue *rv) {
+    CompileEngine::Global()->Clear();
+  });
+
+// NOTE: this involves graph lookup and can be slow
+TVM_REGISTER_GLOBAL("nnvm.compiler.GetCacheItem")
+.set_body([](tvm::runtime::TVMArgs args, tvm::runtime::TVMRetValue *rv) {
+    *rv = CompileEngine::Global()->Find(args[0]);
+  });
+
+TVM_REGISTER_GLOBAL("nnvm.compiler.SetCacheItem")
+.set_body([](tvm::runtime::TVMArgs args, tvm::runtime::TVMRetValue *rv) {
+    CompileEngine::Global()->Set(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("nnvm.compiler.GraphKeyGetGraph")
+.set_body([](tvm::runtime::TVMArgs args, tvm::runtime::TVMRetValue *rv) {
+    *rv = args[0].operator GraphKey()->graph;
+  });
+
+TVM_REGISTER_GLOBAL("nnvm.compiler.MakeGraphKey")
+.set_body([](tvm::runtime::TVMArgs args, tvm::runtime::TVMRetValue *rv) {
+    *rv = GraphKeyNode::make(args[0], args[1], args[2]);
+  });
+
+
+TVM_STATIC_IR_FUNCTOR(IRPrinter, vtable)
+.set_dispatch<GraphFuncNode>([](const GraphFuncNode *op, IRPrinter *p) {
+    p->stream << "GraphFunc(name=" << op->func_name
+              << ", addr=" << op << ")";
+});
+
+}  // namespace compiler
+}  // namespace nnvm
diff --git a/nnvm/src/compiler/compile_engine.h b/nnvm/src/compiler/compile_engine.h
new file mode 100644
index 000000000000..91ab2588a593
--- /dev/null
+++ b/nnvm/src/compiler/compile_engine.h
@@ -0,0 +1,96 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file compile_engine.h
+ * \brief Internal engine to compile a subgraph fragment and cache compilation.
+ */
+#ifndef NNVM_COMPILER_COMPILE_ENGINE_H_
+#define NNVM_COMPILER_COMPILE_ENGINE_H_
+
+#include <nnvm/graph.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/graph_attr_types.h>
+#include <nnvm/tuple.h>
+#include <nnvm/pass.h>
+#include <nnvm/compiler/op_attr_types.h>
+#include <nnvm/compiler/packed_func_ext.h>
+#include <tvm/runtime/packed_func.h>
+#include <tvm/operation.h>
+#include <tvm/lowered_func.h>
+#include <string>
+#include "./graph_hash.h"
+
+namespace nnvm {
+namespace compiler {
+
+/*! \brief A TVM Node to represent compiled graph function */
+struct GraphFuncNode : public tvm::Node {
+  /* \brief compiled target */
+  std::string target;
+  /*! \brief Function name */
+  std::string func_name;
+  /* \brief The inputs to the function */
+  tvm::Array<Tensor> inputs;
+  /* \brief The outputs to the function */
+  tvm::Array<Tensor> outputs;
+  /*! \brief The lowered functions */
+  tvm::Array<tvm::LoweredFunc> funcs;
+
+  void VisitAttrs(tvm::AttrVisitor* v) final {
+    v->Visit("target", &target);
+    v->Visit("func_name", &func_name);
+    v->Visit("inputs", &inputs);
+    v->Visit("outputs", &outputs);
+    v->Visit("funcs", &funcs);
+  }
+
+  static constexpr const char* _type_key = "GraphFunc";
+  TVM_DECLARE_NODE_TYPE_INFO(GraphFuncNode, tvm::Node);
+};
+
+TVM_DEFINE_NODE_REF(GraphFunc, GraphFuncNode);
+
+/*! \brief Cache Entry in the graph */
+struct GraphCacheEntryNode : public tvm::Node {
+  /*! \brief The graph function */
+  GraphFunc graph_func;
+  /*! \brief Usage statistics */
+  int use_count{0};
+
+  void VisitAttrs(tvm::AttrVisitor* v) final {
+    v->Visit("graph_func", &graph_func);
+    v->Visit("use_count", &use_count);
+  }
+  static constexpr const char* _type_key = "GraphCacheEntry";
+  TVM_DECLARE_NODE_TYPE_INFO(GraphCacheEntryNode, tvm::Node);
+};
+
+class GraphCacheEntry : public ::tvm::NodeRef {
+ public:
+  GraphCacheEntry() {}
+  explicit GraphCacheEntry(std::shared_ptr<::tvm::Node> n) : NodeRef(n) {}
+  GraphCacheEntryNode* operator->() {
+    return static_cast<GraphCacheEntryNode*>(node_.get());
+  }
+  using ContainerType = GraphCacheEntryNode;
+};
+
+/*!
+ * \brief Call compile engine to lower a graph with given inputs.
+ *
+ * \param graph The graph to be compiled
+ * \param inputs The input specification.
+ * \param schedule_op_key The hint key for the schedule.
+ * \param schedule_op_attr  The hint attribute for the schedule.
+ *
+ * \return func A lowered tvm function.
+ */
+GraphFunc GraphLower(Graph graph,
+                     const Array<tvm::Tensor>& inputs,
+                     const std::string& target,
+                     const Op* schedule_op_key,
+                     const NodeAttrs& schedule_op_attr);
+
+}  // namespace compiler
+}  // namespace nnvm
+
+#endif  // NNVM_COMPILER_COMPILE_ENGINE_H_
diff --git a/nnvm/src/compiler/graph_fuse.cc b/nnvm/src/compiler/graph_fuse.cc
index e3935ed95ff1..bd7cdbae4e98 100644
--- a/nnvm/src/compiler/graph_fuse.cc
+++ b/nnvm/src/compiler/graph_fuse.cc
@@ -4,15 +4,16 @@
  * \brief Fuse the operators together.
  */
 #include <nnvm/graph.h>
+#include <nnvm/node.h>
 #include <nnvm/op_attr_types.h>
 #include <nnvm/graph_attr_types.h>
 #include <nnvm/tuple.h>
 #include <nnvm/pass.h>
-#include <nnvm/compiler/op_attr_types.h>
+#include <nnvm/pass_functions.h>
 #include <nnvm/compiler/packed_func_ext.h>
 #include <tvm/runtime/packed_func.h>
-#include <tvm/operation.h>
 #include <tvm/lowered_func.h>
+#include "./compile_engine.h"
 #include "../runtime/graph_executor.h"
 
 namespace nnvm {
@@ -20,8 +21,6 @@ namespace compiler {
 
 using namespace tvm;
 
-using DLTypeVector = std::vector<DLDataType>;
-
 // The single fuse rule.
 enum class FuseRule {
   kUknown,
@@ -29,8 +28,14 @@ enum class FuseRule {
   kRealize
 };
 
+/*!
+ * \brief Get DLDataType from dtype flag.
+ *
+ * \param type_flag The data type flag
+ * \return corresponding DLDataType
+ */
 DLDataType GetDLType(int type_flag) {
-  if (type_flag == 0) return Type2TVMType(Float(32));
+  if (type_flag == 0) return tvm::Type2TVMType(tvm::Float(32));
   LOG(FATAL) << "unknown type_flag=" << type_flag;
   return Type2TVMType(Float(32));
 }
@@ -48,13 +53,6 @@ nnvm::Graph GraphFusePartition(nnvm::Graph g) {
 
   // Get attributes from the graph
   const ShapeVector& shape_vec = g.GetAttr<ShapeVector>("shape");
-  const DTypeVector& dtype_vec = g.GetAttr<DTypeVector>("dtype");
-  // Transform to dltype
-  // In future, directly fo type inference in dltype.
-  DLTypeVector dltype_vec = DLTypeVector(dtype_vec.size());
-  for (size_t i = 0; i < dtype_vec.size(); ++i) {
-    dltype_vec[i] = GetDLType(dtype_vec[i]);
-  }
 
   // Reference counter of each op node
   // For now, always store result when an op is referred more than once.
@@ -174,7 +172,6 @@ nnvm::Graph GraphFusePartition(nnvm::Graph g) {
   g.attrs["group_root"] = std::make_shared<any>(std::move(group_vec));
   g.attrs["group_master"] = std::make_shared<any>(std::move(master_vec));
   g.attrs["pattern"] = std::make_shared<any>(std::move(pattern_vec));
-  g.attrs["dltype"] = std::make_shared<any>(std::move(dltype_vec));
   return g;
 }
 
@@ -182,16 +179,15 @@ nnvm::Graph GraphFusePartition(nnvm::Graph g) {
 NNVM_REGISTER_PASS(GraphFusePartition)
 .set_body(GraphFusePartition)
 .depend_graph_attr("shape")
-.depend_graph_attr("dtype")
-.provide_graph_attr("dltype");
+.depend_graph_attr("dtype");
 
-struct NodeEntryHash {
+struct INodeEntryHash {
   size_t operator()(const IndexedGraph::NodeEntry& e) const {
     return e.node_id;
   }
 };
 
-struct NodeEntryEqual {
+struct INodeEntryEqual {
   size_t operator()(const IndexedGraph::NodeEntry& a,
                     const IndexedGraph::NodeEntry& b) const {
     return a.node_id == b.node_id && a.index == b.index;
@@ -200,30 +196,29 @@ struct NodeEntryEqual {
 
 // Auxiliary data structure for representing fused op.
 struct FuseEntry {
-  // The inputs
-  std::vector<IndexedGraph::NodeEntry> inputs;
+  // subgraph of the fragement
+  Graph subgraph;
   // The input map
-  std::unordered_map<IndexedGraph::NodeEntry, Tensor,
-                     NodeEntryHash, NodeEntryEqual> imap;
-  // Output tensors
-  Array<Tensor> outputs;
-  // Placeholder for inputs
-  Array<Tensor> placeholder;
-  // Computing schedule
-  Schedule schedule;
-  // Function name
-  std::string func_name;
+  std::unordered_map<IndexedGraph::NodeEntry, nnvm::NodeEntry,
+                     INodeEntryHash, INodeEntryEqual> imap;
+  // reverse map to the old input entry
+  std::unordered_map<const Node*, IndexedGraph::NodeEntry> reverse_imap;
+  // TVM Placeholder for inputs
+  std::unordered_map<const Node*, Tensor> input_info;
+  // Whether we can flatten data
+  bool flatten_data;
+  // The corresponding function.
+  GraphFunc compiled_func;
 };
 
 // Fuse the partitioned graph into segments.
 // Create a new graph with fused noded.
 // Also inheritate attribute shape, dltype from previous graph.
-nnvm::Graph GraphFuse(nnvm::Graph g) {
+nnvm::Graph GraphFuseCompile(nnvm::Graph g) {
   // setup ref counter
   const IndexedGraph& idx = g.indexed_graph();
   // Get attributes from the graph
   const ShapeVector& shape_vec = g.GetAttr<ShapeVector>("shape");
-  const DLTypeVector& dltype_vec = g.GetAttr<DLTypeVector>("dltype");
   const DTypeVector& dtype_vec = g.GetAttr<DTypeVector>("dtype");
   const std::vector<int>& group_vec = g.GetAttr<std::vector<int> >("group_root");
   const std::vector<int>& master_vec = g.GetAttr<std::vector<int> >("group_master");
@@ -238,11 +233,11 @@ nnvm::Graph GraphFuse(nnvm::Graph g) {
     CHECK_GE(group_vec[nid], 0);
     int root_id = group_vec[nid];
     FuseEntry& fe = fuse_vec[root_id];
-    TOpPattern pt = pattern_vec[root_id];
+    fe.flatten_data = (pattern_vec[root_id] == kElemWise);
     for (const auto& e : inode.inputs) {
       if (group_vec[e.node_id] != root_id && fe.imap.count(e) == 0) {
         Array<Expr> shape;
-        if (pt == kElemWise) {
+        if (fe.flatten_data) {
           // elementwise support flatten
           int64_t prod = 1;
           for (int64_t x : shape_vec[idx.entry_id(e)]) {
@@ -257,93 +252,85 @@ nnvm::Graph GraphFuse(nnvm::Graph g) {
           }
         }
         std::ostringstream os_name;
-        os_name << "input" << fe.inputs.size();
+        os_name << "input" << fe.imap.size();
         Tensor data = placeholder(
-            shape, TVMType2Type(dltype_vec[idx.entry_id(e)]),
+            shape, TVMType2Type(GetDLType(dtype_vec[idx.entry_id(e)])),
             os_name.str());
-        fe.imap[e] = data;
-        fe.inputs.push_back(e);
-        fe.placeholder.push_back(data);
+        NodeEntry garg = Symbol::CreateVariable(os_name.str()).outputs[0];
+        fe.imap[e] = garg;
+        fe.reverse_imap[garg.node.get()] = e;
+        fe.input_info[garg.node.get()] = std::move(data);
       }
     }
   }
-  // Setup the Tensor
-  std::vector<Tensor> tensor_vec(idx.num_node_entries());
-  static auto& fcompute =
-      nnvm::Op::GetAttr<FTVMCompute>("FTVMCompute");
-  static auto& fschedule =
-      nnvm::Op::GetAttr<FTVMSchedule>("FTVMSchedule");
+  // Setup the Subgraph
+  std::vector<NodeEntry> subgraph_vec(idx.num_node_entries());
   for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
     const auto& inode = idx[nid];
     if (inode.source->is_variable()) continue;
     int root_id = group_vec[nid];
     FuseEntry& fe = fuse_vec[root_id];
-    Array<Tensor> inputs, out_info;
+    // copy and create subgraph node.
+    NodePtr gnode = Node::Create();
+    gnode->attrs = inode.source->attrs;
     // input loading
     for (const auto& e : inode.inputs) {
       if (group_vec[e.node_id] != root_id) {
         auto it = fe.imap.find(e);
         CHECK(it != fe.imap.end());
-        inputs.push_back(it->second);
+        gnode->inputs.push_back(it->second);
       } else {
-        Tensor t = tensor_vec[idx.entry_id(e)];
-        CHECK(t.defined());
-        inputs.push_back(t);
+        const NodeEntry& ne = subgraph_vec[idx.entry_id(e)];
+        CHECK(!idx[e.node_id].source->is_variable());
+        CHECK(ne.node != nullptr);
+        gnode->inputs.push_back(ne);
       }
     }
-    // output hint
-    for (uint32_t i = 0; i < inode.source->num_outputs(); ++i) {
-      Array<Expr> shape;
-      for (int64_t x : shape_vec[idx.entry_id(nid, i)]) {
-        CHECK_LE(x, static_cast<int64_t>(std::numeric_limits<int>::max()));
-        shape.push_back(make_const(Int(32), x));
-      }
-      out_info.push_back(
-          placeholder(shape,
-                      TVMType2Type(dltype_vec[idx.entry_id(nid, i)])));
-    }
-    // get default
-    Array<Tensor> out = fcompute[inode.source->op()](
-        inode.source->attrs, inputs, out_info);
-    CHECK_EQ(out.size(), inode.source->num_outputs());
     // schedule on root node, and use master's schedule
     if (nid != root_id) {
       for (uint32_t index = 0; index < inode.source->num_outputs(); ++index) {
         uint32_t eid = idx.entry_id(nid, index);
-        tensor_vec[eid] = out[index];
+        subgraph_vec[eid] = NodeEntry{gnode, index, 0};
       }
     } else {
-      fe.outputs = out;
-      int master = master_vec[root_id];
-      CHECK_GE(master, 0);
-      fe.schedule = fschedule[idx[master].source->op()](
-          idx[master].source->attrs, fe.outputs, target);
-      std::ostringstream os;
-      os << idx[master].source->attrs.name + "_id" << nid;
-      fe.func_name = os.str();
+      for (uint32_t index = 0; index < inode.source->num_outputs(); ++index) {
+        fe.subgraph.outputs.push_back(NodeEntry{gnode, index, 0});
+      }
     }
   }
-  static const PackedFunc& flower = GetPackedFunc("nnvm.compiler.lower");
-  static const PackedFunc& fbuild = GetPackedFunc("nnvm.compiler.build_target");
+  // Start lowering
+  Array<tvm::LoweredFunc> func_list;
+  std::unordered_set<const tvm::Node*> func_set;
 
-  Array<tvm::LoweredFunc> funcs;
-  for (const FuseEntry& fe : fuse_vec) {
-    if (fe.schedule.defined()) {
-      Array<tvm::Tensor> args = fe.placeholder;
-      for (tvm::Tensor x : fe.outputs) {
-        args.push_back(x);
-      }
-      Array<tvm::LoweredFunc> ret = flower(fe.schedule, args, fe.func_name);
-      for (LoweredFunc x : ret) {
-        funcs.push_back(x);
+  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+    const auto& inode = idx[nid];
+    if (inode.source->is_variable()) continue;
+    int root_id = group_vec[nid];
+    if (nid != root_id) continue;
+    int master = master_vec[root_id];
+    FuseEntry& fe = fuse_vec[root_id];
+
+    const IndexedGraph& subidx = fe.subgraph.indexed_graph();
+    CHECK_EQ(subidx.input_nodes().size(), fe.imap.size());
+    CHECK_EQ(subidx.input_nodes().size(), fe.input_info.size());
+
+    Array<Tensor> inputs;
+    for (uint32_t sub_input_id : subidx.input_nodes()) {
+      auto it = fe.input_info.find(subidx[sub_input_id].source);
+      inputs.push_back(it->second);
+    }
+    fe.compiled_func = GraphLower(fe.subgraph, inputs, target,
+                                  idx[master].source->op(),
+                                  idx[master].source->attrs);
+    for (LoweredFunc f : fe.compiled_func->funcs) {
+      if (!func_set.count(f.get())) {
+        func_set.insert(f.get());
+        func_list.push_back(f);
       }
     }
   }
-
-  tvm::runtime::Module module = fbuild(funcs, target);
-  // Final step: Remap the node, with given attribute
+  // Rebuild the fused graph
   const nnvm::Op* tvm_op = nnvm::Op::Get("tvm_op");
-
   std::unordered_map<uint32_t, nnvm::NodePtr> old_new;
   for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
     const auto& inode = idx[nid];
@@ -351,36 +338,41 @@ nnvm::Graph GraphFuse(nnvm::Graph g) {
       nnvm::NodePtr np = nnvm::Node::Create();
       np->attrs = inode.source->attrs;
       old_new[nid] = np;
-    } else {
-      int root_id = group_vec[nid];
-      if (nid != root_id) continue;
-      FuseEntry& fe = fuse_vec[root_id];
-      nnvm::NodePtr np = nnvm::Node::Create();
-      np->attrs.op = tvm_op;
-      np->attrs.name = inode.source->attrs.name;
-      runtime::TVMOpParam param;
-      param.func_name = fuse_vec[nid].func_name;
-      param.num_inputs = static_cast<uint32_t>(fe.inputs.size());
-      param.num_outputs = static_cast<uint32_t>(fe.outputs.size());
-      param.flatten_data = pattern_vec[nid] == kElemWise;
-      param.UpdateDict(&(np->attrs.dict));
-      np->attrs.parsed = std::move(param);
-      for (const auto& e : fe.inputs) {
-        auto it = old_new.find(e.node_id);
-        CHECK(it != old_new.end())
-            << "cannot find node_id=" << e.node_id;
-        np->inputs.emplace_back(
-            nnvm::NodeEntry{it->second, e.index, e.version});
-      }
-      for (const uint32_t node_id : inode.control_deps) {
-        auto it = old_new.find(node_id);
-        CHECK(it != old_new.end());
-        np->control_deps.emplace_back(it->second);
-      }
-      old_new[nid] = np;
+      continue;
     }
-  }
+    int root_id = group_vec[nid];
+    if (nid != root_id) continue;
+    FuseEntry& fe = fuse_vec[root_id];
+    const IndexedGraph& subidx = fe.subgraph.indexed_graph();
+    nnvm::NodePtr np = nnvm::Node::Create();
+    np->attrs.op = tvm_op;
+    np->attrs.name = inode.source->attrs.name;
+    runtime::TVMOpParam param;
+    param.func_name = fe.compiled_func->func_name;
+    param.num_inputs = static_cast<uint32_t>(fe.imap.size());
+    param.num_outputs = static_cast<uint32_t>(fe.subgraph.outputs.size());
+    param.flatten_data = fe.flatten_data;
+    param.UpdateDict(&(np->attrs.dict));
+    np->attrs.parsed = std::move(param);
 
+    for (uint32_t sub_input_id : subidx.input_nodes()) {
+      // Need to make sure subgraph input order meets order of the graph input
+      auto rit = fe.reverse_imap.find(subidx[sub_input_id].source);
+      CHECK(rit != fe.reverse_imap.end());
+      const IndexedGraph::NodeEntry& e = rit->second;
+      auto it = old_new.find(e.node_id);
+      CHECK(it != old_new.end())
+          << "cannot find node_id=" << e.node_id;
+      np->inputs.emplace_back(
+          nnvm::NodeEntry{it->second, e.index, e.version});
+    }
+    for (const uint32_t node_id : inode.control_deps) {
+      auto it = old_new.find(node_id);
+      CHECK(it != old_new.end());
+      np->control_deps.emplace_back(it->second);
+    }
+    old_new[nid] = np;
+  }
   nnvm::Graph ret;
   for (const auto& e : idx.outputs()) {
     auto it = old_new.find(group_vec[e.node_id]);
@@ -389,6 +381,7 @@ nnvm::Graph GraphFuse(nnvm::Graph g) {
     ret.outputs.emplace_back(
         nnvm::NodeEntry{it->second, e.index, e.version});
   }
+
   const IndexedGraph& new_idx = ret.indexed_graph();
   ShapeVector new_shape_vec = ShapeVector(new_idx.num_node_entries(), TShape());
   DTypeVector new_dtype_vec = DTypeVector(new_idx.num_node_entries());
@@ -401,18 +394,23 @@ nnvm::Graph GraphFuse(nnvm::Graph g) {
       uint32_t old_eid = idx.entry_id(nid, i);
       new_shape_vec[new_eid] = shape_vec[old_eid];
       new_dtype_vec[new_eid] = dtype_vec[old_eid];
-      new_dltype_vec[new_eid] = tvm::runtime::TVMType2String(dltype_vec[old_eid]);
+      new_dltype_vec[new_eid] = tvm::runtime::TVMType2String(
+          GetDLType(dtype_vec[old_eid]));
     }
   }
   ret.attrs["shape"] = std::make_shared<any>(std::move(new_shape_vec));
   ret.attrs["dtype"] = std::make_shared<any>(std::move(new_dtype_vec));
   ret.attrs["dltype"] = std::make_shared<any>(std::move(new_dltype_vec));
+  // Setup module
+  static const PackedFunc& fbuild = GetPackedFunc("nnvm.compiler.build_target");
+  tvm::runtime::Module module = fbuild(func_list, target);
   ret.attrs["module"] = std::make_shared<any>(std::move(module));
   ret = nnvm::ApplyPass(ret, "PlanMemory");
   return ret;
 }
 
-NNVM_REGISTER_PASS(GraphFuse)
-.set_body(GraphFuse);
+NNVM_REGISTER_PASS(GraphFuseCompile)
+.set_body(GraphFuseCompile);
+
 }  // namespace compiler
 }  // namespace nnvm
diff --git a/nnvm/src/compiler/graph_deep_compare.cc b/nnvm/src/compiler/graph_hash.cc
similarity index 52%
rename from nnvm/src/compiler/graph_deep_compare.cc
rename to nnvm/src/compiler/graph_hash.cc
index df578165ed6b..a38b6e135d43 100644
--- a/nnvm/src/compiler/graph_deep_compare.cc
+++ b/nnvm/src/compiler/graph_hash.cc
@@ -3,22 +3,124 @@
  * \file graph_deep_compare.cc
  * \brief Deep compare two graph structure
  */
+#include <dmlc/common.h>
 #include <nnvm/graph.h>
 #include <nnvm/op_attr_types.h>
 #include <nnvm/compiler/packed_func_ext.h>
+#include <tvm/ir.h>
 #include <tvm/runtime/packed_func.h>
+#include <functional>
 #include "./node_attr.h"
+#include "./graph_hash.h"
 
 namespace nnvm {
 namespace compiler {
 
+using namespace tvm;
+using tvm::ir::IntImm;
+
+size_t HashPlaceHolder(const Tensor& t) {
+  size_t key = t->shape.size();
+  key = dmlc::HashCombine(key, (t->dtype.code() << 8) | t->dtype.bits());
+  for (Expr s : t->shape) {
+    if (const IntImm* op = s.as<IntImm>()) {
+      key = dmlc::HashCombine(key, op->value);
+    }
+  }
+  return key;
+}
+
+bool PlaceHolderEqual(const Tensor& a, const Tensor& b) {
+  if (a->shape.size() != b->shape.size()) return false;
+  if (a->dtype != b->dtype) return false;
+  for (size_t i = 0; i < a->shape.size(); ++i) {
+    const IntImm* a_value = a->shape[i].as<IntImm>();
+    const IntImm* b_value = b->shape[i].as<IntImm>();
+    if (a_value && b_value == nullptr) return false;
+    if (b_value && a_value == nullptr) return false;
+    if (a_value == nullptr && b_value == nullptr) {
+      continue;
+    }
+    if (a_value->value != b_value->value) return false;
+  }
+  return true;
+}
+
+size_t GraphKeyHash::Hash(const GraphKey& gkey)  {
+  if (gkey->cache_hash_key_ != 0) return gkey->cache_hash_key_;
+  size_t key = dmlc::HashCombine(GraphHash(gkey->graph), gkey->target);
+  key = dmlc::HashCombine(key, gkey->inputs.size());
+  for (size_t i = 0; i < gkey->inputs.size(); ++i) {
+    key = dmlc::HashCombine(key, HashPlaceHolder(gkey->inputs[i]));
+  }
+  if (key == 0) key = 1;
+  gkey->cache_hash_key_ = key;
+  return key;
+}
+
+bool GraphKeyEqual::Equal(const GraphKey& a,
+                          const GraphKey& b) {
+  if (a->target != b->target) return false;
+  if (a->inputs.size() != b->inputs.size()) return false;
+  for (size_t i = 0; i < a->inputs.size(); ++i) {
+    if (!PlaceHolderEqual(a->inputs[i], b->inputs[i])) return false;
+  }
+  if (GraphDeepCompare(a->graph, b->graph, false).length() != 0) return false;
+  return true;
+}
+
+GraphKey GraphKeyNode::make(Graph graph,
+                            tvm::Array<Tensor> inputs,
+                            std::string target) {
+  std::shared_ptr<GraphKeyNode> n
+      = std::make_shared<GraphKeyNode>();
+  n->graph = std::move(graph);
+  n->inputs = inputs;
+  n->target = std::move(target);
+  return GraphKey(n);
+}
+
+TVM_STATIC_IR_FUNCTOR(IRPrinter, vtable)
+.set_dispatch<GraphKeyNode>([](const GraphKeyNode *op, IRPrinter *p) {
+    p->stream << "GraphKeyNode("<< op << ")";
+});
+
+
+// Run graph hash
+size_t GraphHash(const Graph& graph) {
+  const IndexedGraph& idx = graph.indexed_graph();
+  size_t key = 0;
+  // Combine a linearized sequence of ops in subgraph
+  key = dmlc::HashCombine(key, idx.num_nodes());
+  std::hash<std::string> str_hash;
+  std::vector<size_t> hash_temp;
+  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+    const IndexedGraph::Node& inode = idx[nid];
+    // Use name instad op address so it is deterministic across runs
+    key = dmlc::HashCombine(key, inode.source->op()p);
+    if (inode.source->is_variable()) continue;
+    hash_temp.clear();
+    for (const auto& kv : GetAttrDict(inode.source->attrs)) {
+      hash_temp.push_back(dmlc::HashCombine(str_hash(kv.first), kv.second));
+    }
+    // to make sure it is deterministic
+    // since unordered_map is not deterministic
+    std::sort(hash_temp.begin(), hash_temp.end());
+    for (size_t value : hash_temp) {
+      key = dmlc::HashCombine(key, value);
+    }
+  }
+  return key;
+}
+
 // deep compare the graph structure
 // not considering the graph attributes
 // return non-empty error message if the graph mismatch.
 // the comparator won't match name of intermediate node.
 // compare_var_attr
-std::string DeepCompare(Graph a, Graph b,
-                        bool compare_variable_attr) {
+std::string GraphDeepCompare(const Graph& a,
+                             const Graph& b,
+                             bool compare_variable_attr) {
   const IndexedGraph& idxa = a.indexed_graph();
   const IndexedGraph& idxb = b.indexed_graph();
   std::ostringstream err;
@@ -113,7 +215,7 @@ std::string DeepCompare(Graph a, Graph b,
 
 TVM_REGISTER_GLOBAL("nnvm.graph.DeepCompare")
 .set_body([](tvm::runtime::TVMArgs args, tvm::runtime::TVMRetValue *rv) {
-    *rv = DeepCompare(args[0], args[1], args[2]);
+    *rv = GraphDeepCompare(args[0], args[1], args[2]);
   });
 }  // namespace compiler
 }  // namespace nnvm
diff --git a/nnvm/src/compiler/graph_hash.h b/nnvm/src/compiler/graph_hash.h
new file mode 100644
index 000000000000..f6f93a9d7e95
--- /dev/null
+++ b/nnvm/src/compiler/graph_hash.h
@@ -0,0 +1,82 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file graph_hash.h
+ * \brief The graph hashing function.
+ */
+#ifndef NNVM_COMPILER_GRAPH_HASH_H_
+#define NNVM_COMPILER_GRAPH_HASH_H_
+
+#include <dmlc/common.h>
+#include <nnvm/graph.h>
+#include <tvm/operation.h>
+#include <string>
+
+namespace nnvm {
+namespace compiler {
+
+class GraphKey;
+
+/*! \brief Key to a graph compiler cache */
+struct GraphKeyNode : public tvm::Node {
+  /*! \brief The graph structure */
+  Graph graph;
+  /* \brief The inputs to the function */
+  tvm::Array<Tensor> inputs;
+  /*! \brief The target */
+  std::string target;
+  // Cached internal hash key, invisible to the user.
+  // The graph hash key is ensured always not to be 0
+  mutable size_t cache_hash_key_{0};
+
+  void VisitAttrs(tvm::AttrVisitor* v) final {
+    v->Visit("inputs", &inputs);
+    v->Visit("target", &target);
+  }
+
+  static GraphKey make(Graph graph,
+                       tvm::Array<Tensor> inputs,
+                       std::string target);
+  static constexpr const char* _type_key = "GraphKey";
+  TVM_DECLARE_NODE_TYPE_INFO(GraphKeyNode, tvm::Node);
+};
+
+TVM_DEFINE_NODE_REF(GraphKey, GraphKeyNode);
+
+/*! \brief Hashing function for graph key */
+struct GraphKeyHash {
+  size_t operator()(const GraphKey& gkey) const {
+    return Hash(gkey);
+  }
+  static size_t Hash(const GraphKey& gkey);
+};
+
+/*! \brief function for graph key */
+struct GraphKeyEqual {
+  bool operator()(const GraphKey& a,
+                  const GraphKey& b) const {
+    return Equal(a, b);
+  }
+  static bool Equal(const GraphKey& a, const GraphKey& b);
+};
+
+/*!
+ * \brief Create a hash code for a given graph.
+ * \return The hash code of the graph.
+ */
+size_t GraphHash(const Graph& graph);
+
+/*!
+ * \brief Compare two graphs
+ *  return empty string if they are equal
+ *  otherwise return error message
+ * \param a The first graph.
+ * \param b The second graph.
+ * \return empty string if they are equal, otherwise return error message.
+ */
+std::string GraphDeepCompare(const Graph& a,
+                             const Graph& b,
+                             bool compare_variable_attr);
+}  // namespace compiler
+}  // namespace nnvm
+
+#endif  // NNVM_COMPILER_GRAPH_HASH_H_
diff --git a/nnvm/src/core/graph.cc b/nnvm/src/core/graph.cc
index 474f3104f145..d1b6efb66dbb 100644
--- a/nnvm/src/core/graph.cc
+++ b/nnvm/src/core/graph.cc
@@ -9,7 +9,7 @@
 
 namespace nnvm {
 
-const IndexedGraph& Graph::indexed_graph() {
+const IndexedGraph& Graph::indexed_graph() const {
   if (indexed_graph_ == nullptr) {
     indexed_graph_.reset(new IndexedGraph(*this));
   }
diff --git a/nnvm/src/pass/print_graph_ir.cc b/nnvm/src/pass/print_graph_ir.cc
index 52298c0a77a5..80b6df2006ba 100644
--- a/nnvm/src/pass/print_graph_ir.cc
+++ b/nnvm/src/pass/print_graph_ir.cc
@@ -180,7 +180,7 @@ void PrintGraphIR_(Graph src,
 }
 
 // save a graph to json
-Graph PrintGraphIR(Graph src) {
+Graph PrintGraphIRPass(Graph src) {
   std::ostringstream os;
   std::vector<std::string> join_entry_attrs, join_node_attrs;
   if (src.attrs.count("join_entry_attrs") != 0) {
@@ -200,7 +200,7 @@ Graph PrintGraphIR(Graph src) {
 // register pass
 NNVM_REGISTER_PASS(PrintGraphIR)
 .describe("Return a empty Graph, save ir to ret.attrs[\"graphir\"]")
-.set_body(PrintGraphIR);
+.set_body(PrintGraphIRPass);
 
 }  // namespace pass
 }  // namespace nnvm
diff --git a/nnvm/tests/python/compiler/test_compiler_cache.py b/nnvm/tests/python/compiler/test_compiler_cache.py
new file mode 100644
index 000000000000..f7666b39f005
--- /dev/null
+++ b/nnvm/tests/python/compiler/test_compiler_cache.py
@@ -0,0 +1,42 @@
+import numpy as np
+import tvm
+import nnvm.symbol as sym
+import nnvm.compiler
+import nnvm.runtime
+
+def test_compile_cache():
+    x = sym.Variable("x")
+    y = sym.Variable("y")
+    z = sym.exp(y + x)
+    shape = (10, 1)
+    dtype = tvm.float32
+    shape_dict = {"x": shape, "y": shape}
+    def verify(graph, lib):
+        m = nnvm.runtime.create(graph, lib, tvm.cpu(0))
+        # get member functions
+        na = tvm.nd.array(np.random.uniform(size=shape).astype(dtype))
+        nb = tvm.nd.array(np.random.uniform(size=shape).astype(dtype))
+        m.run(x=na, y=nb)
+        # get outputs
+        out = m.get_output(0, tvm.nd.empty(shape, dtype))
+        np.testing.assert_allclose(
+            out.asnumpy(), np.exp(na.asnumpy() + nb.asnumpy()))
+
+    engine = nnvm.compiler.engine
+    graph, lib, _ = nnvm.compiler.build(z, "llvm", shape_dict)
+    inputs = [tvm.placeholder((10,)), tvm.placeholder((10,))]
+
+    gkey = nnvm.compiler.graph_key(nnvm.graph.create(z), inputs, "llvm")
+    gkey2 = nnvm.compiler.graph_key(nnvm.graph.create(z), inputs + inputs, "llvm")
+    gf = engine[gkey]
+    assert gf is not None
+    assert engine[gkey2] is None
+    graph, lib, _ = nnvm.compiler.build(z, "llvm", shape_dict)
+    assert graph.index.num_nodes == 3
+    verify(graph, lib)
+    # Test various set external cache
+    engine.clear_cache()
+    engine[gkey] = gf
+
+if __name__ == "__main__":
+    test_compile_cache()
diff --git a/nnvm/tests/python/compiler/test_op_fusion.py b/nnvm/tests/python/compiler/test_op_fusion.py
index ca82da2023c0..a7c6ca4b288d 100644
--- a/nnvm/tests/python/compiler/test_op_fusion.py
+++ b/nnvm/tests/python/compiler/test_op_fusion.py
@@ -4,7 +4,7 @@
 import topi
 from nnvm import symbol as sym
 from nnvm.compiler import graph_util, graph_attr
-from nnvm.testing.config import test_ctx_list
+from nnvm.testing import ctx_list
 
 def test_ewise_injective():
     x = sym.Variable("x")
@@ -14,7 +14,7 @@ def test_ewise_injective():
     shape_dict = {"x": dshape}
     dtype = "float32"
     target = "llvm"
-    for target, ctx in test_ctx_list():
+    for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
         assert graph.index.num_nodes == 2
         m = nnvm.runtime.create(graph, lib, ctx)
@@ -37,7 +37,7 @@ def test_conv_ewise_injective():
     oshape = (1, 32* 18 * 18)
     shape_dict = {"x": dshape}
 
-    for target, ctx in test_ctx_list():
+    for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
         m = nnvm.runtime.create(graph, lib, ctx)
         # print(graph.ir(join_entry_attrs=["shape"]))
@@ -64,7 +64,7 @@ def test_injective_reduce_injective():
     dshape = (32, 1, 18, 18)
     shape_dict = {"x": dshape}
 
-    for target, ctx in test_ctx_list():
+    for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
         m = nnvm.runtime.create(graph, lib, ctx)
         assert graph.index.num_nodes == 2
diff --git a/nnvm/tests/python/compiler/test_top_level1.py b/nnvm/tests/python/compiler/test_top_level1.py
index d03782bb4cb5..baeaaf86040a 100644
--- a/nnvm/tests/python/compiler/test_top_level1.py
+++ b/nnvm/tests/python/compiler/test_top_level1.py
@@ -4,7 +4,7 @@
 import nnvm.symbol as sym
 import nnvm.compiler
 import nnvm.runtime
-from nnvm.testing.config import test_ctx_list
+from nnvm.testing.config import ctx_list
 
 def test_relu():
     x = sym.Variable("x")
@@ -13,7 +13,7 @@ def test_relu():
     dtype = "float32"
     dshape = (1, 3, 32, 32)
     oshape = dshape
-    for target, ctx in test_ctx_list():
+    for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
         m = nnvm.runtime.create(graph, lib, ctx)
         # get member functions
@@ -31,7 +31,7 @@ def test_exp():
     dtype = "float32"
     dshape = (1, 3, 32, 32)
     oshape = dshape
-    for target, ctx in test_ctx_list():
+    for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
         m = nnvm.runtime.create(graph, lib, ctx)
         # get member functions
@@ -54,7 +54,7 @@ def test_log():
     dtype = "float32"
     dshape = (1, 3, 32, 32)
     oshape = dshape
-    for target, ctx in test_ctx_list():
+    for target, ctx in ctx_list():
         with nnvm.compiler.build_config(opt_level=1):
             graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
         m = nnvm.runtime.create(graph, lib, ctx)
@@ -78,7 +78,7 @@ def test_tanh():
     dtype = "float32"
     dshape = (1, 3, 32, 32)
     oshape = dshape
-    for target, ctx in test_ctx_list():
+    for target, ctx in ctx_list():
         with nnvm.compiler.build_config(opt_level=1):
             graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
         m = nnvm.runtime.create(graph, lib, ctx)
@@ -102,7 +102,7 @@ def test_sigmoid():
     dtype = "float32"
     dshape = (1, 3, 32, 32)
     oshape = dshape
-    for target, ctx in test_ctx_list():
+    for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
         m = nnvm.runtime.create(graph, lib, ctx)
         # get member functions
@@ -125,7 +125,7 @@ def test_softmax():
     dtype = "float32"
     dshape = (10, 1000)
     oshape = dshape
-    for target, ctx in test_ctx_list():
+    for target, ctx in ctx_list():
         with nnvm.compiler.build_config(opt_level=1):
             graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
         m = nnvm.runtime.create(graph, lib, ctx)
@@ -153,7 +153,7 @@ def test_dense():
         "dense_weight" : (3, 100),
         "dense_bias" : (3,),
     }
-    for target, ctx in test_ctx_list():
+    for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, shape)
         m = nnvm.runtime.create(graph, lib, ctx)
         x_np = np.random.uniform(size=shape["x"]).astype(dtype)
@@ -179,7 +179,7 @@ def test_batchnorm():
     y = sym.batch_norm(
         x, gamma, beta, moving_mean, moving_var, epsilon=eps)
 
-    for target, ctx in test_ctx_list():
+    for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, "llvm", {"x": shape})
         m = nnvm.runtime.create(graph, lib, tvm.cpu(0))
         x_np = np.random.uniform(size=shape).astype(dtype)
diff --git a/nnvm/tests/python/compiler/test_top_level2.py b/nnvm/tests/python/compiler/test_top_level2.py
index 793d6d3e955f..79f29f40ce5c 100644
--- a/nnvm/tests/python/compiler/test_top_level2.py
+++ b/nnvm/tests/python/compiler/test_top_level2.py
@@ -5,7 +5,7 @@
 import nnvm.symbol as sym
 import nnvm.compiler
 import nnvm.runtime
-from nnvm.testing.config import test_ctx_list
+from nnvm.testing.config import ctx_list
 
 
 def test_conv2d():
@@ -17,7 +17,7 @@ def test_conv2d():
     kshape = (10, 3, 3, 3)
     oshape = (1, 10, 18, 18)
     shape_dict = {"x": dshape}
-    for target, ctx in test_ctx_list():
+    for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
         m = nnvm.runtime.create(graph, lib, ctx)
         # get member functions
@@ -46,7 +46,7 @@ def test_grouped_conv2d():
     kshape = (32, 1, 3, 3)
     oshape = (1, 32, 18, 18)
     shape_dict = {"x": dshape}
-    for target, ctx in test_ctx_list():
+    for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
         m = nnvm.runtime.create(graph, lib, ctx)
         # set input
diff --git a/nnvm/tests/python/compiler/test_top_level4.py b/nnvm/tests/python/compiler/test_top_level4.py
index 851fc8b7d46c..eac3178e3e45 100644
--- a/nnvm/tests/python/compiler/test_top_level4.py
+++ b/nnvm/tests/python/compiler/test_top_level4.py
@@ -4,7 +4,7 @@
 import nnvm.symbol as sym
 import nnvm.compiler
 import nnvm.runtime
-from nnvm.testing.config import test_ctx_list
+from nnvm.testing.config import ctx_list
 
 def verify_transpose(dshape, axes):
     x = sym.Variable("x")
@@ -14,7 +14,7 @@ def verify_transpose(dshape, axes):
         y = sym.transpose(x)
     y = y + 1
     dtype = "float32"
-    for target, ctx in test_ctx_list():
+    for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
         m = nnvm.runtime.create(graph, lib, ctx)
         # set input
@@ -29,7 +29,7 @@ def verify_reduce(dshape, fnp, fsym, **kwargs):
     x = sym.Variable("x")
     y = fsym(x + 1, **kwargs)
     dtype = "float32"
-    for target, ctx in test_ctx_list():
+    for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
         m = nnvm.runtime.create(graph, lib, ctx)
         # set input
@@ -54,3 +54,4 @@ def test_reduce():
 if __name__ == "__main__":
     test_reduce()
     test_tranpose()
+    print(nnvm.compiler.engine.dump())

From 2d4ae5e6582e705fe885979b07d513fd62d197d6 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 23 Sep 2017 23:19:20 -0700
Subject: [PATCH 487/949] [RUNTIME] Minimum runtime module (#31)

---
 nnvm/deploy/nnvm_runtime.cc        |   4 +-
 nnvm/src/compiler/graph_fuse.cc    |   3 +-
 nnvm/src/compiler/graph_hash.cc    |   2 +-
 nnvm/src/runtime/graph_executor.cc |   5 +-
 nnvm/src/runtime/graph_executor.h  |  24 +-
 nnvm/src/runtime/graph_runtime.cc  | 563 +++++++++++++++++++++++++++++
 nnvm/src/runtime/graph_runtime.h   |  40 ++
 7 files changed, 612 insertions(+), 29 deletions(-)
 create mode 100644 nnvm/src/runtime/graph_runtime.cc
 create mode 100644 nnvm/src/runtime/graph_runtime.h

diff --git a/nnvm/deploy/nnvm_runtime.cc b/nnvm/deploy/nnvm_runtime.cc
index 15c46012f6ec..df72af46cb28 100644
--- a/nnvm/deploy/nnvm_runtime.cc
+++ b/nnvm/deploy/nnvm_runtime.cc
@@ -3,9 +3,11 @@
  *  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"
+#include "../src/runtime/graph_executor.cc"*/
+#include "../src/runtime/graph_runtime.cc"
diff --git a/nnvm/src/compiler/graph_fuse.cc b/nnvm/src/compiler/graph_fuse.cc
index bd7cdbae4e98..a6395760f19f 100644
--- a/nnvm/src/compiler/graph_fuse.cc
+++ b/nnvm/src/compiler/graph_fuse.cc
@@ -335,8 +335,9 @@ nnvm::Graph GraphFuseCompile(nnvm::Graph g) {
   for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
     const auto& inode = idx[nid];
     if (inode.source->is_variable()) {
+      // only copy over name since that is sufficient.
       nnvm::NodePtr np = nnvm::Node::Create();
-      np->attrs = inode.source->attrs;
+      np->attrs.name = inode.source->attrs.name;
       old_new[nid] = np;
       continue;
     }
diff --git a/nnvm/src/compiler/graph_hash.cc b/nnvm/src/compiler/graph_hash.cc
index a38b6e135d43..f493bc0429a6 100644
--- a/nnvm/src/compiler/graph_hash.cc
+++ b/nnvm/src/compiler/graph_hash.cc
@@ -97,8 +97,8 @@ size_t GraphHash(const Graph& graph) {
   for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
     const IndexedGraph::Node& inode = idx[nid];
     // Use name instad op address so it is deterministic across runs
-    key = dmlc::HashCombine(key, inode.source->op()p);
     if (inode.source->is_variable()) continue;
+    key = dmlc::HashCombine(key, inode.source->op()->name);
     hash_temp.clear();
     for (const auto& kv : GetAttrDict(inode.source->attrs)) {
       hash_temp.push_back(dmlc::HashCombine(str_hash(kv.first), kv.second));
diff --git a/nnvm/src/runtime/graph_executor.cc b/nnvm/src/runtime/graph_executor.cc
index 63e42a6a8844..bf72dc56d53e 100644
--- a/nnvm/src/runtime/graph_executor.cc
+++ b/nnvm/src/runtime/graph_executor.cc
@@ -299,7 +299,6 @@ inline void TVMOpParamParser(nnvm::NodeAttrs* attrs) {
   attrs->parsed = std::move(param);
 }
 
-DMLC_REGISTER_PARAMETER(TVMOpParam);
 
 NNVM_REGISTER_OP(tvm_op)
 .set_attr_parser(TVMOpParamParser)
@@ -328,12 +327,12 @@ tvm::runtime::Module RuntimeCreate(std::string sym_json,
   return tvm::runtime::Module(exec);
 }
 
-TVM_REGISTER_GLOBAL("nnvm.runtime.create")
+TVM_REGISTER_GLOBAL("nnvm.runtime.createx")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
     *rv = RuntimeCreate(args[0], args[1], args[2], args[3]);
   });
 
-TVM_REGISTER_GLOBAL("nnvm.runtime.remote_create")
+TVM_REGISTER_GLOBAL("nnvm.runtime.remote_createx")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
     void* mhandle = args[1];
     *rv = RuntimeCreate(args[0],
diff --git a/nnvm/src/runtime/graph_executor.h b/nnvm/src/runtime/graph_executor.h
index 243c71646ba7..215240610657 100644
--- a/nnvm/src/runtime/graph_executor.h
+++ b/nnvm/src/runtime/graph_executor.h
@@ -17,33 +17,11 @@
 #include <nnvm/pass.h>
 #include <vector>
 #include <string>
+#include "./graph_runtime.h"
 
 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
diff --git a/nnvm/src/runtime/graph_runtime.cc b/nnvm/src/runtime/graph_runtime.cc
new file mode 100644
index 000000000000..d9faeb45f783
--- /dev/null
+++ b/nnvm/src/runtime/graph_runtime.cc
@@ -0,0 +1,563 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file graph_executor.cc
+ */
+#include <dmlc/memory_io.h>
+#include <dmlc/json.h>
+#include <tvm/runtime/packed_func.h>
+#include <tvm/runtime/registry.h>
+#include <numeric>
+#include "./graph_runtime.h"
+
+namespace nnvm {
+namespace runtime {
+
+/*! \brief macro to do C API call */
+#define TVM_CCALL(func)                                            \
+  {                                                                \
+    int ret = (func);                                              \
+    CHECK_EQ(ret, 0)                                               \
+        << TVMGetLastError();                                      \
+  }
+
+using ::tvm::runtime::PackedFunc;
+using ::tvm::runtime::TVMArgs;
+using ::tvm::runtime::TVMRetValue;
+
+/*!
+ * \brief Minimum graph structure for deployment
+ *  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 GraphRuntime : public ::tvm::runtime::ModuleNode {
+ public:
+  ~GraphRuntime() {
+    for (DLTensor* t : storage_pool_) {
+      TVM_CCALL(TVMArrayFree(t));
+    }
+  }
+  /*!
+   * \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;
+
+  /*!
+   * \return The type key of the executor.
+   */
+  const char* type_key() const final {
+    return "GraphRuntime";
+  }
+  void Run() {
+    // setup the array and requirements.
+    for (size_t i = 0; i < op_execs_.size(); ++i) {
+      if (op_execs_[i]) op_execs_[i]();
+    }
+  }
+  /*!
+   * \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(const std::string& graph_json,
+            tvm::runtime::Module module,
+            TVMContext ctx) {
+    std::istringstream is(graph_json);
+    dmlc::JSONReader reader(&is);
+    this->Load(&reader);
+    module_ = module;
+    ctx_ = ctx;
+    this->SetupStorage();
+    this->SetupOpExecs();
+  }
+  /*!
+   * \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) {
+    for (size_t i = 0; i< input_nodes_.size(); ++i) {
+      uint32_t nid = input_nodes_[i];
+      if (nodes_[nid].name == name) {
+        return static_cast<int>(i);
+      }
+    }
+    LOG(FATAL) << "cannot find " << name << " among input";
+    return -1;
+  }
+  /*!
+   * \brief set index-th input to the graph.
+   * \param index The input index.
+   * \param data The input data.
+   */
+  void SetInput(int index, DLTensor* data_in) {
+    CHECK_LT(static_cast<size_t>(index), input_nodes_.size());
+    uint32_t eid = this->entry_id(input_nodes_[index], 0);
+    TVM_CCALL(TVMArrayCopyFromTo(data_in, &data_entry_[eid], nullptr));
+  }
+  /*!
+   * \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) {
+    CHECK_LT(static_cast<size_t>(index), outputs_.size());
+    uint32_t eid = this->entry_id(outputs_[index]);
+    TVM_CCALL(TVMArrayCopyFromTo(&data_entry_[eid], data_out, nullptr));
+  }
+
+  /*!
+   * \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) {
+    dmlc::MemoryStringStream strm(const_cast<std::string*>(&param_blob));
+    this->LoadParams(&strm);
+  }
+
+ private:
+  // Node entry
+  struct NodeEntry {
+    uint32_t node_id;
+    uint32_t index;
+    uint32_t version;
+    // JSON Loader
+    void Load(dmlc::JSONReader *reader) {
+      reader->BeginArray();
+      CHECK(reader->NextArrayItem()) << "invalid json format";
+      reader->Read(&node_id);
+      CHECK(reader->NextArrayItem()) << "invalid json format";
+      reader->Read(&index);
+      if (reader->NextArrayItem()) {
+        reader->Read(&version);
+        CHECK(!reader->NextArrayItem()) << "invalid json format";
+      } else {
+        version = 0;
+      }
+    }
+  };
+  // Node
+  struct Node {
+    // operator type in string
+    std::string op_type;
+    // name of the op
+    std::string name;
+    // parameters
+    TVMOpParam param;
+    // inputs
+    std::vector<NodeEntry> inputs;
+    // control deps
+    std::vector<uint32_t> control_deps;
+    // JSON Loader
+    void Load(dmlc::JSONReader *reader) {
+      reader->BeginObject();
+      std::unordered_map<std::string, std::string> dict;
+      int bitmask = 0;
+      std::string key;
+      while (reader->NextObjectItem(&key)) {
+        if (key == "op") {
+          reader->Read(&op_type);
+          bitmask |= 1;
+        } else if (key == "name") {
+          reader->Read(&name);
+          bitmask |= 2;
+        } else if (key == "inputs") {
+          reader->Read(&inputs);
+          bitmask |= 4;
+        } else if (key == "attr") {
+          reader->Read(&dict);
+          param.Init(dict);
+        } else if (key == "control_deps") {
+          reader->Read(&control_deps);
+        } else {
+          LOG(FATAL) << "do not support key" << key;
+        }
+      }
+      CHECK_EQ(bitmask, 1|2|4) << "invalid format";
+    }
+  };
+  struct GraphAttr {
+    size_t storage_num_not_alloctaed{0};
+    std::vector<int> storage_id;
+    std::vector<std::string> dltype;
+    std::vector<std::vector<int64_t> > shape;
+    // The graph attribute fields.
+    void Load(dmlc::JSONReader *reader) {
+      reader->BeginObject();
+      int bitmask = 0;
+      std::string key, type;
+      while (reader->NextObjectItem(&key)) {
+        if (key == "dltype") {
+          reader->BeginArray();
+          CHECK(reader->NextArrayItem());
+          reader->Read(&type);
+          CHECK_EQ(type, "list_str");
+          CHECK(reader->NextArrayItem());
+          reader->Read(&dltype);
+          CHECK(!reader->NextArrayItem());
+          bitmask |= 1;
+        } else if (key == "storage_id") {
+          reader->BeginArray();
+          CHECK(reader->NextArrayItem());
+          reader->Read(&type);
+          CHECK_EQ(type, "list_int");
+          CHECK(reader->NextArrayItem());
+          reader->Read(&storage_id);
+          CHECK(!reader->NextArrayItem());
+          bitmask |= 2;
+        } else if (key == "shape") {
+          reader->BeginArray();
+          CHECK(reader->NextArrayItem());
+          reader->Read(&type);
+          CHECK_EQ(type, "list_shape");
+          CHECK(reader->NextArrayItem());
+          reader->Read(&shape);
+          CHECK(!reader->NextArrayItem());
+          bitmask |= 4;
+        } else {
+          reader->BeginArray();
+          CHECK(reader->NextArrayItem());
+          reader->Read(&type);
+          if (type == "list_int") {
+            CHECK(reader->NextArrayItem());
+            std::vector<int> temp;
+            reader->Read(&temp);
+          } else if (type == "size_t") {
+            CHECK(reader->NextArrayItem());
+            size_t temp;
+            reader->Read(&temp);
+          } else {
+            LOG(FATAL) << "cannot skip graph attr " << key;
+          }
+          CHECK(!reader->NextArrayItem());
+        }
+      }
+      CHECK_EQ(bitmask, 1|2|4) << "invalid format";
+    }
+  };
+  // The graph attribute fields.
+  void Load(dmlc::JSONReader *reader) {
+      reader->BeginObject();
+      int bitmask = 0;
+      std::string key;
+      while (reader->NextObjectItem(&key)) {
+        if (key == "nodes") {
+          reader->Read(&nodes_);
+          bitmask |= 1;
+        } else if (key == "arg_nodes") {
+          reader->Read(&input_nodes_);
+          bitmask |= 2;
+        } else if (key == "node_row_ptr") {
+          reader->Read(&node_row_ptr_);
+          bitmask |= 4;
+        } else if (key == "heads") {
+          reader->Read(&outputs_);
+          bitmask |= 8;
+        } else if (key == "attrs") {
+          reader->Read(&attrs_);
+          bitmask |= 16;
+        }
+      }
+      CHECK_EQ(bitmask, 1|2|4|8|16) << "invalid format";
+  }
+  bool LoadDLTensor(dmlc::Stream* strm, DLTensor* tensor);
+  /*! \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 TVMOpParam& attrs,
+                                    const std::vector<DLTensor>& args,
+                                    size_t num_inputs);
+  // Get node entry index.
+  uint32_t entry_id(uint32_t nid, uint32_t index) const {
+    return node_row_ptr_[nid] + index;
+  }
+  // Get node entry index.
+  uint32_t entry_id(const NodeEntry& e) const {
+    return entry_id(e.node_id, e.index);
+  }
+  // Number of node entries
+  uint32_t num_node_entries() const {
+    return node_row_ptr_.back();
+  }
+  // Number of nodes.
+  uint32_t num_nodes() const {
+    return static_cast<uint32_t>(nodes_.size());
+  }
+  // The graph nodes.
+  std::vector<Node> nodes_;
+  // The argument nodes.
+  std::vector<uint32_t> input_nodes_;
+  // used or quick entry indexing
+  std::vector<uint32_t> node_row_ptr_;
+  // output entries
+  std::vector<NodeEntry> outputs_;
+  // Additional graph attributes
+  GraphAttr attrs_;
+  /*! \brief The code module */
+  tvm::runtime::Module module_;
+  /*! \brief execution context */
+  TVMContext ctx_;
+  /*! \brief common storage pool */
+  std::vector<DLTensor*> storage_pool_;
+  /*! \brief data entry of each node */
+  std::vector<DLTensor> data_entry_;
+  /*! \brief operator on each node */
+  std::vector<std::function<void()> > op_execs_;
+};
+
+DMLC_REGISTER_PARAMETER(TVMOpParam);
+
+bool GraphRuntime::LoadDLTensor(dmlc::Stream* strm, DLTensor* tensor) {
+  uint64_t header, reserved;
+  CHECK(strm->Read(&header, sizeof(header)))
+      << "Invalid DLTensor file format";
+  CHECK(strm->Read(&reserved, sizeof(reserved)))
+      << "Invalid DLTensor file format";
+  CHECK(header == kTVMNDArrayMagic)
+      << "Invalid DLTensor file format";
+
+  CHECK(strm->Read(&tensor->ctx, sizeof(tensor->ctx)))
+      << "Invalid DLTensor file format";
+  CHECK(strm->Read(&tensor->ndim, sizeof(tensor->ndim)))
+      << "Invalid DLTensor file format";
+  CHECK(strm->Read(&tensor->dtype, sizeof(tensor->dtype)))
+      << "Invalid DLTensor file format";
+
+  int ndim = tensor->ndim;
+  CHECK(strm->Read(tensor->shape, sizeof(int64_t) * ndim))
+      << "Invalid DLTensor file format";
+
+  int64_t size = 1;
+  int type_size = tensor->dtype.bits / 8;
+  for (int i = 0; i < ndim; ++i) {
+    size *= tensor->shape[i];
+  }
+  int64_t data_byte_size;
+  CHECK(strm->Read(&data_byte_size, sizeof(data_byte_size)))
+      << "Invalid DLTensor file format";
+  CHECK(data_byte_size == type_size * size)
+      << "Invalid DLTensor file format";
+  CHECK(strm->Read(tensor->data, type_size * size))
+      << "Invalid DLTensor file format";
+  return true;
+}
+
+void GraphRuntime::LoadParams(dmlc::Stream* strm) {
+  uint64_t header, reserved;
+  CHECK(strm->Read(&header))
+      << "Invalid parameters file format";
+  CHECK(header == kTVMNDArrayListMagic)
+      << "Invalid parameters file format";
+  CHECK(strm->Read(&reserved))
+      << "Invalid parameters file format";
+
+  std::vector<std::string> names;
+  CHECK(strm->Read(&names))
+      << "Invalid parameters file format";
+  uint64_t sz;
+  strm->Read(&sz, sizeof(sz));
+  size_t size = static_cast<size_t>(sz);
+
+  CHECK(size == names.size())
+      << "Invalid parameters file format";
+
+  for (size_t i = 0; i < size; ++i) {
+    uint32_t in_idx = GetInputIndex(names[i]);
+    CHECK(LoadDLTensor(strm, &data_entry_[this->entry_id(input_nodes_[in_idx], 0)]))
+        << "Invalid parameters file format";
+  }
+}
+
+void GraphRuntime::SetupStorage() {
+  // Grab saved optimization plan from graph.
+  std::vector<TVMType> vtype;
+  for (const std::string& s_type : attrs_.dltype) {
+    vtype.push_back(tvm::runtime::String2TVMType(s_type));
+  }
+  data_entry_.resize(num_node_entries());
+  // Find the maximum space size.
+  int max_id = 0;
+  for (size_t i = 0; i < attrs_.shape.size(); ++i) {
+    max_id = std::max(attrs_.storage_id[i] + 1, max_id);
+  }
+  for (uint32_t nid : input_nodes_) {
+    attrs_.storage_id[this->entry_id(nid, 0)] = max_id++;
+  }
+  // size of each storage pool entry
+  std::vector<size_t> pool_entry_bytes;
+  // Find the maximum space size.
+  for (size_t i = 0; i < attrs_.shape.size(); ++i) {
+    int storage_id = attrs_.storage_id[i];
+    size_t size = 1;
+    for (int64_t sz : attrs_.shape[i]) {
+      size *= static_cast<size_t>(sz);
+    }
+    CHECK_GE(storage_id, 0) << "Do not support runtime shape op";
+    DLDataType t = vtype[i];
+    size_t bits = t.bits * t.lanes;
+    CHECK_EQ(bits % 8U, 0U);
+    size_t bytes = (bits / 8U) * size;
+
+    size_t sid = static_cast<size_t>(storage_id);
+    if (sid >= pool_entry_bytes.size()) {
+      pool_entry_bytes.resize(sid + 1, 0);
+    }
+    pool_entry_bytes[sid] = std::max(pool_entry_bytes[sid], bytes);
+  }
+  // Allocate the space.
+  for (size_t i = 0; i < pool_entry_bytes.size(); ++i) {
+    int64_t shape[] = {static_cast<int64_t>(pool_entry_bytes[i] + 3) / 4};
+    DLTensor* tensor;
+    TVM_CCALL(TVMArrayAlloc(
+        shape, 1, kFloat, 32, 1, ctx_.device_type, ctx_.device_id, &tensor));
+    storage_pool_.push_back(tensor);
+  }
+  // Assign the pooled entries.
+  for (size_t i = 0; i < data_entry_.size(); ++i) {
+    int storage_id = attrs_.storage_id[i];
+    data_entry_[i] = *storage_pool_[storage_id];
+    data_entry_[i].shape = const_cast<int64_t*>(attrs_.shape[i].data());
+    data_entry_[i].ndim = static_cast<int>(attrs_.shape[i].size());
+    data_entry_[i].dtype = vtype[i];
+  }
+}
+
+/*! \brief Setup the executors */
+void GraphRuntime::SetupOpExecs() {
+  op_execs_.resize(this->num_nodes());
+  // setup the array and requirements.
+  for (uint32_t nid = 0; nid < this->num_nodes(); ++nid) {
+    const auto& inode = nodes_[nid];
+    if (inode.op_type == "null") continue;
+    std::vector<DLTensor> args;
+    for (const auto& e : inode.inputs) {
+      args.push_back(data_entry_[this->entry_id(e)]);
+    }
+    for (uint32_t index = 0; index < inode.param.num_outputs; ++index) {
+      uint32_t eid = this->entry_id(nid, index);
+      args.push_back(data_entry_[eid]);
+    }
+    CHECK_EQ(inode.op_type, "tvm_op")
+        << "transform the graph to tvm op5A";
+    op_execs_[nid] = CreateTVMOp(inode.param, args, inode.inputs.size());
+  }
+}
+
+std::function<void()> GraphRuntime::CreateTVMOp(
+    const TVMOpParam& param,
+    const std::vector<DLTensor>& args,
+    size_t num_inputs) {
+  struct OpArgs {
+    std::vector<DLTensor> args;
+    std::vector<TVMValue> arg_values;
+    std::vector<int> arg_tcodes;
+    std::vector<int64_t> shape_data;
+  };
+  std::shared_ptr<OpArgs> arg_ptr = std::make_shared<OpArgs>();
+  // setup address.
+  arg_ptr->args = std::move(args);
+  if (param.flatten_data) {
+    arg_ptr->shape_data.resize(arg_ptr->args.size());
+  }
+  for (size_t i = 0; i < arg_ptr->args.size(); ++i) {
+    TVMValue v;
+    DLTensor* t = &(arg_ptr->args[i]);
+    v.v_handle = t;
+    arg_ptr->arg_values.push_back(v);
+    arg_ptr->arg_tcodes.push_back(kArrayHandle);
+    if (param.flatten_data) {
+      arg_ptr->shape_data[i] = std::accumulate(
+          t->shape, t->shape + t->ndim, 1, std::multiplies<int64_t>());
+      t->ndim = 1;
+      t->shape = &(arg_ptr->shape_data[i]);
+    }
+  }
+  // get compiled function from module.
+  tvm::runtime::PackedFunc pf = module_.GetFunction(param.func_name, false);
+  CHECK(pf != nullptr) << "no such function in module: " << param.func_name;
+  auto fexec = [arg_ptr, pf] () {
+    TVMRetValue rv;
+    TVMArgs targs(arg_ptr->arg_values.data(),
+                  arg_ptr->arg_tcodes.data(),
+                  static_cast<int>(arg_ptr->arg_values.size()));
+    pf.CallPacked(targs, &rv);
+  };
+  return fexec;
+}
+
+PackedFunc GraphRuntime::GetFunction(
+    const std::string& name,
+    const std::shared_ptr<ModuleNode>& sptr_to_self) {
+  // return member functions during query.
+  if (name == "set_input") {
+    return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
+        if (args[0].type_code() == kStr) {
+          this->SetInput(this->GetInputIndex(args[0]), args[1]);
+        } else {
+          this->SetInput(args[0], args[1]);
+        }
+      });
+  } else if (name == "get_output") {
+    return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
+        this->GetOutput(args[0], args[1]);
+      });
+  } else if (name == "run") {
+    return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
+        this->Run();
+      });
+  } else if (name == "load_params") {
+    return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
+        this->LoadParams(args[0].operator std::string());
+      });
+  } else {
+    return PackedFunc();
+  }
+}
+
+tvm::runtime::Module GraphRuntimeCreate(std::string sym_json,
+                                        tvm::runtime::Module m,
+                                        int device_type,
+                                        int device_id) {
+  TVMContext ctx;
+  ctx.device_type = static_cast<DLDeviceType>(device_type);
+  ctx.device_id   = device_id;
+  std::shared_ptr<GraphRuntime> exec = std::make_shared<GraphRuntime>();
+  exec->Init(sym_json, m, ctx);
+  return tvm::runtime::Module(exec);
+}
+
+TVM_REGISTER_GLOBAL("nnvm.runtime.create")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+    *rv = GraphRuntimeCreate(args[0], args[1], args[2], args[3]);
+  });
+
+TVM_REGISTER_GLOBAL("nnvm.runtime.remote_create")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+    void* mhandle = args[1];
+    *rv = GraphRuntimeCreate(args[0],
+                             *static_cast<tvm::runtime::Module*>(mhandle),
+                             args[2], args[3]);
+  });
+}  // namespace runtime
+}  // namespace nnvm
diff --git a/nnvm/src/runtime/graph_runtime.h b/nnvm/src/runtime/graph_runtime.h
new file mode 100644
index 000000000000..6ed130f1dd5c
--- /dev/null
+++ b/nnvm/src/runtime/graph_runtime.h
@@ -0,0 +1,40 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ *
+ *  Runtime module for graph deployment.
+ *
+ * \file graph_executor.h
+ */
+#ifndef NNVM_RUNTIME_GRAPH_RUNTIME_H_
+#define NNVM_RUNTIME_GRAPH_RUNTIME_H_
+
+#include <dmlc/parameter.h>
+#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 operator attributes about tvm op */
+struct TVMOpParam : public dmlc::Parameter<TVMOpParam> {
+  std::string func_name;
+  uint32_t num_inputs;
+  uint32_t num_outputs;
+  uint32_t 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(0);
+  }
+};
+
+}  // namespace runtime
+}  // namespace nnvm
+
+#endif  // NNVM_RUNTIME_GRAPH_RUNTIME_H_

From 71a7618919ea5f35f2a4de40ab2b1a62d12e1d43 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 24 Sep 2017 10:06:37 -0700
Subject: [PATCH 488/949] [ATTR] More robust attr parsing (#33)

---
 nnvm/include/nnvm/tuple.h         |  2 --
 nnvm/python/nnvm/top/attr_dict.py | 12 +++++++++++-
 2 files changed, 11 insertions(+), 3 deletions(-)

diff --git a/nnvm/include/nnvm/tuple.h b/nnvm/include/nnvm/tuple.h
index b3193ca29eb8..551300b277ca 100644
--- a/nnvm/include/nnvm/tuple.h
+++ b/nnvm/include/nnvm/tuple.h
@@ -202,8 +202,6 @@ class Tuple {
       if (it != begin) os << ',';
       os << *it;
     }
-    // python style tuple
-    if (t.ndim() == 1) os << ',';
     os << ']';
     return os;
   }
diff --git a/nnvm/python/nnvm/top/attr_dict.py b/nnvm/python/nnvm/top/attr_dict.py
index b5baf3decbde..2c37a0e40d6a 100644
--- a/nnvm/python/nnvm/top/attr_dict.py
+++ b/nnvm/python/nnvm/top/attr_dict.py
@@ -94,7 +94,17 @@ def get_bool(self, key):
         value : bool
             The result value
         """
-        return self[key] != "False"
+        lowercase = self[key].lower()
+        if lowercase == "1":
+            return True
+        elif lowercase == "0":
+            return False
+        elif lowercase == "true":
+            return True
+        elif lowercase == "false":
+            return False
+        else:
+            raise ValueError("Wrong bool format for key %s" % key)
 
     def __repr__(self):
         return str({k : self[k] for k in self.keys()})

From 3dbf9e5b1433367b2bab3a04503b366e3ad7ab58 Mon Sep 17 00:00:00 2001
From: Yuwei HU <huyuwei1995@gmail.com>
Date: Mon, 25 Sep 2017 03:02:02 +0800
Subject: [PATCH 489/949] [TOP][Example] register pool, global_pool; add
 mobilenet example (#32)

* register pool, global_pool; add mobilenet example

* tests of pool and global_pool

* use new API of runtime module

* small fix
---
 nnvm/example/mobilenet_inference_gpu.py       | 117 ++++++++++++++++++
 nnvm/include/nnvm/top/nn.h                    |   7 --
 nnvm/python/nnvm/top/nn.py                    |  91 +++++++++++++-
 nnvm/tests/python/compiler/test_top_level1.py |  76 +++---------
 nnvm/tests/python/compiler/test_top_level2.py |  94 +++++++++++---
 .../python/frontend/mxnet/test_forward.py     |   7 +-
 6 files changed, 308 insertions(+), 84 deletions(-)
 create mode 100644 nnvm/example/mobilenet_inference_gpu.py

diff --git a/nnvm/example/mobilenet_inference_gpu.py b/nnvm/example/mobilenet_inference_gpu.py
new file mode 100644
index 000000000000..4331ec4c0f02
--- /dev/null
+++ b/nnvm/example/mobilenet_inference_gpu.py
@@ -0,0 +1,117 @@
+"""Forward propagation of MobileNet on GPU."""
+import numpy as np
+import time
+import os
+
+import tvm
+import topi
+import nnvm.symbol as sym
+import nnvm.compiler
+import nnvm.runtime
+from tvm.contrib import nvcc
+
+TASK="mobilenet"
+
+target = 'cuda'
+ctx = tvm.gpu(0)
+
+@tvm.register_func
+def tvm_callback_cuda_compile(code):
+    ptx = nvcc.compile_cuda(code, target="ptx", options=["-arch=sm_60"])
+    return ptx
+
+def write_code(code, fname):
+    with open(fname, "w") as f:
+        f.write(code)
+
+@tvm.register_func
+def tvm_callback_cuda_postproc(code):
+    if not os.path.exists("perf"):
+        os.mkdir("perf")
+    write_code(code, "perf/%s_generated.cu" % TASK)
+    return code
+
+dtype = 'float32'
+epsilon = 1e-10 + 1e-5
+
+def conv_block(data, name, channels, kernel_size=(3,3), strides=(1,1), padding=(1,1)):
+    # convolution + bn + relu
+    conv = sym.conv2d(data=data, channels=channels, kernel_size=kernel_size, strides=strides,
+        padding=padding, use_bias=False, layout='NCHW', name=name + '_conv')
+    bn = sym.batch_norm(data=conv, epsilon=epsilon, name=name + '_bn')
+    act = sym.relu(data=bn, name=name + '_relu')
+    return act
+
+def separable_conv_block(data, name, depthwise_channels, pointwise_channels, kernel_size=(3,3), downsample=False, padding=(1,1)):
+    if downsample:
+        strides = (2,2)
+    else:
+        strides = (1,1)
+    # depthwise convolution + bn + relu
+    conv1 = sym.conv2d(data=data, channels=depthwise_channels, groups=depthwise_channels, kernel_size=kernel_size, strides=strides,
+        padding=padding, use_bias=False, layout='NCHW', name=name + '_conv1')
+    bn1 = sym.batch_norm(data=conv1, epsilon=epsilon, name=name + '_bn1')
+    act1 = sym.relu(data=bn1, name=name + '_relu1')
+    # pointwise convolution + bn + relu
+    conv2 = sym.conv2d(data=act1, channels=pointwise_channels, kernel_size=(1,1), strides=(1,1),
+        padding=(0,0), use_bias=False, layout='NCHW', name=name + '_conv2')
+    bn2 = sym.batch_norm(data=conv2, epsilon=epsilon, name=name + '_bn2')
+    act2 = sym.relu(data=bn2, name=name + '_relu2')
+    return act2
+
+def mobile_net(num_classes=1000, alpha=1.0, is_shallow=False):
+    data = sym.Variable("data")
+    body = conv_block(data, 'conv_block_1', int(32*alpha), strides=(2,2))
+    body = separable_conv_block(body, 'separable_conv_block_1', int(32*alpha), int(64*alpha))
+    body = separable_conv_block(body, 'separable_conv_block_2', int(64*alpha), int(128*alpha), downsample=True)
+    body = separable_conv_block(body, 'separable_conv_block_3', int(128*alpha), int(128*alpha))
+    body = separable_conv_block(body, 'separable_conv_block_4', int(128*alpha), int(256*alpha), downsample=True)
+    body = separable_conv_block(body, 'separable_conv_block_5', int(256*alpha), int(256*alpha))
+    body = separable_conv_block(body, 'separable_conv_block_6', int(256*alpha), int(512*alpha), downsample=True)
+    if is_shallow:
+        body = separable_conv_block(body, 'separable_conv_block_7', int(512*alpha), int(1024*alpha), downsample=True)
+        body = separable_conv_block(body, 'separable_conv_block_8', int(1024*alpha), int(1024*alpha))
+    else:
+        for i in range(7, 12):
+            body = separable_conv_block(body, 'separable_conv_block_%d' % i, int(512*alpha), int(512*alpha))
+        body = separable_conv_block(body, 'separable_conv_block_12', int(512*alpha), int(1024*alpha), downsample=True)
+        body = separable_conv_block(body, 'separable_conv_block_13', int(1024*alpha), int(1024*alpha))
+    pool = sym.global_avg_pool2d(data=body, name='pool')
+    flatten = sym.flatten(data=pool, name='flatten')
+    fc = sym.dense(data=flatten, units=num_classes, use_bias=False, name='fc')
+    softmax = sym.softmax(data=fc, name='softmax')
+    return softmax
+
+
+batch_size = 1
+num_classes = 1000
+image_shape = (3,224,224)
+data_shape = (batch_size,) + image_shape
+out_shape = (batch_size, num_classes)
+
+net = mobile_net(num_classes=num_classes, alpha=1.0, is_shallow=False)
+
+# build graph
+with nnvm.compiler.build_config(opt_level=2):
+    graph, lib, _ = nnvm.compiler.build(net, target, {'data': data_shape})
+# prepare params
+params = {}
+names = graph.index.input_names
+shapes = [graph.json_attr("shape")[graph.index.entry_id(x)] for x in names]
+for i in range(len(names)):
+    params[names[i]] = tvm.nd.array(np.random.uniform(-0.1, 0.1, size=shapes[i]).astype(dtype), ctx=ctx)
+# create runtime module
+module = nnvm.runtime.create(graph, lib, ctx)
+# set input
+module.set_input(**params)
+# run
+print("run")
+module.run()
+ctx.sync()
+start = time.time()
+for i in range(1000):
+    module.run()
+    ctx.sync()
+print("average time cost of 1000 runs = %g ms" % ((time.time() - start)))
+# get output
+out = module.get_output(0, tvm.nd.empty(out_shape, dtype))
diff --git a/nnvm/include/nnvm/top/nn.h b/nnvm/include/nnvm/top/nn.h
index a48a1287fa71..672709a68394 100644
--- a/nnvm/include/nnvm/top/nn.h
+++ b/nnvm/include/nnvm/top/nn.h
@@ -202,7 +202,6 @@ struct Pool2DParam : public dmlc::Parameter<Pool2DParam> {
   TShape pool_size;
   TShape strides;
   TShape padding;
-  int groups;
   int layout;
   bool ceil_mode;
 
@@ -214,12 +213,6 @@ struct Pool2DParam : public dmlc::Parameter<Pool2DParam> {
     DMLC_DECLARE_FIELD(padding).set_default(TShape({0, 0}))
       .describe("If padding is non-zero, then the input is implicitly zero-padded"
                 "on both sides for padding number of points");
-    DMLC_DECLARE_FIELD(groups).set_default(1)
-      .describe("Controls the connections between inputs and outputs."
-                "At groups=1, all inputs are convolved to all outputs."
-                "At groups=2, the operation becomes equivalent to having two convolution"
-                "layers side by side, each seeing half the input channels, and producing"
-                "half the output channels, and both subsequently concatenated.");
     DMLC_DECLARE_FIELD(layout)
       .add_enum("NCHW", kNCHW)
       .add_enum("NHWC", kNHWC)
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index c10830b79556..c3b32d01a253 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -18,6 +18,7 @@ def compute_relu(attrs, inputs, _):
 reg.register_schedule("relu", _fschedule_broadcast)
 reg.register_pattern("relu", OpPattern.ELEMWISE)
 
+
 # leaky_relu
 @reg.register_compute("leaky_relu")
 def compute_leaky_relu(attrs, inputs, _):
@@ -27,6 +28,7 @@ def compute_leaky_relu(attrs, inputs, _):
 reg.register_schedule("leaky_relu", _fschedule_broadcast)
 reg.register_pattern("leaky_relu", OpPattern.ELEMWISE)
 
+
 # flatten
 @reg.register_compute("flatten")
 def compute_flatten(attrs, inputs, _):
@@ -73,11 +75,10 @@ def schedule_dense(_, outs, target):
     # naive schedule
     return tvm.create_schedule([x.op for x in outs])
 
-# register extern for now, change me when fusion is enabled.
 reg.register_pattern("dense", OpPattern.OUT_ELEMWISE_FUSABLE)
 
 
-# conv
+# conv2d
 @reg.register_compute("conv2d")
 def compute_conv2d(attrs, inputs, _):
     """Compute definition of conv2d"""
@@ -113,3 +114,89 @@ def schedule_conv2d(attrs, outs, target):
     return tvm.create_schedule([x.op for x in outs])
 
 reg.register_pattern("conv2d", OpPattern.OUT_ELEMWISE_FUSABLE)
+
+
+# max_pool2d
+@reg.register_compute("max_pool2d")
+def compute_max_pool2d(attrs, inputs, _):
+    """Compute definition of max_pool2d"""
+    pool_size = attrs.get_int_tuple("pool_size")
+    strides = attrs.get_int_tuple("strides")
+    padding = attrs.get_int_tuple("padding")
+    layout = attrs["layout"]
+    ceil_mode = attrs["ceil_mode"]
+    assert layout == "NCHW", "only support nchw for now"
+    assert ceil_mode == "False", "not support ceil_mode now"
+    return topi.nn.pool(inputs[0], pool_size, strides, padding, pool_type='max')
+
+@reg.register_schedule("max_pool2d")
+def schedule_max_pool2d(_, outs, target):
+    """Schedule definition of max_pool2d"""
+    if target == "cuda":
+        return topi.cuda.schedule_pool(outs)
+    # naive schedule
+    return tvm.create_schedule([x.op for x in outs])
+
+reg.register_pattern("max_pool2d", OpPattern.OUT_ELEMWISE_FUSABLE)
+
+
+# avg_pool2d
+@reg.register_compute("avg_pool2d")
+def compute_avg_pool2d(attrs, inputs, _):
+    """Compute definition of avg_pool2d"""
+    pool_size = attrs.get_int_tuple("pool_size")
+    strides = attrs.get_int_tuple("strides")
+    padding = attrs.get_int_tuple("padding")
+    layout = attrs["layout"]
+    ceil_mode = attrs["ceil_mode"]
+    assert layout == "NCHW", "only support nchw for now"
+    assert ceil_mode == "False", "not support ceil_mode now"
+    return topi.nn.pool(inputs[0], pool_size, strides, padding, pool_type='avg')
+
+@reg.register_schedule("avg_pool2d")
+def schedule_avg_pool2d(_, outs, target):
+    """Schedule definition of avg_pool2d"""
+    if target == "cuda":
+        return topi.cuda.schedule_pool(outs)
+    # naive schedule
+    return tvm.create_schedule([x.op for x in outs])
+
+reg.register_pattern("avg_pool2d", OpPattern.OUT_ELEMWISE_FUSABLE)
+
+
+# global_max_pool2d
+@reg.register_compute("global_max_pool2d")
+def compute_global_max_pool2d(attrs, inputs, _):
+    """Compute definition of global_max_pool2d"""
+    layout = attrs["layout"]
+    assert layout == "NCHW", "only support nchw for now"
+    return topi.nn.global_pool(inputs[0], pool_type='max')
+
+@reg.register_schedule("global_max_pool2d")
+def schedule_global_max_pool2d(_, outs, target):
+    """Schedule definition of global_max_pool2d"""
+    if target == "cuda":
+        return topi.cuda.schedule_global_pool(outs)
+    # naive schedule
+    return tvm.create_schedule([x.op for x in outs])
+
+reg.register_pattern("global_max_pool2d", OpPattern.OUT_ELEMWISE_FUSABLE)
+
+
+# global_avg_pool2d
+@reg.register_compute("global_avg_pool2d")
+def compute_global_avg_pool2d(attrs, inputs, _):
+    """Compute definition of global_avg_pool2d"""
+    layout = attrs["layout"]
+    assert layout == "NCHW", "only support nchw for now"
+    return topi.nn.global_pool(inputs[0], pool_type='avg')
+
+@reg.register_schedule("global_avg_pool2d")
+def schedule_global_avg_pool2d(_, outs, target):
+    """Schedule definition of global_avg_pool2d"""
+    if target == "cuda":
+        return topi.cuda.schedule_global_pool(outs)
+    # naive schedule
+    return tvm.create_schedule([x.op for x in outs])
+
+reg.register_pattern("global_avg_pool2d", OpPattern.OUT_ELEMWISE_FUSABLE)
diff --git a/nnvm/tests/python/compiler/test_top_level1.py b/nnvm/tests/python/compiler/test_top_level1.py
index baeaaf86040a..7443449e4096 100644
--- a/nnvm/tests/python/compiler/test_top_level1.py
+++ b/nnvm/tests/python/compiler/test_top_level1.py
@@ -16,7 +16,6 @@ def test_relu():
     for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
         m = nnvm.runtime.create(graph, lib, ctx)
-        # get member functions
         data = np.random.uniform(size=dshape).astype(dtype)
         m.run(x=data)
         data = (data < 0) * data * 0.3 + (data>0) * data - 0.2
@@ -34,17 +33,10 @@ def test_exp():
     for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
         m = nnvm.runtime.create(graph, lib, ctx)
-        # get member functions
-        set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
-        # set input
-        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
-        set_input("x", data)
-        # execute
-        run()
-        # get output
-        out = tvm.nd.empty(oshape, dtype)
-        get_output(0, out)
-        y_np = np.exp(data.asnumpy())
+        data = np.random.uniform(size=dshape).astype(dtype)
+        m.run(x=data)
+        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
+        y_np = np.exp(data)
         np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
 
 
@@ -58,17 +50,10 @@ def test_log():
         with nnvm.compiler.build_config(opt_level=1):
             graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
         m = nnvm.runtime.create(graph, lib, ctx)
-        # get member functions
-        set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
-        # set input
-        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
-        set_input("x", data)
-        # execute
-        run()
-        # get output
-        out = tvm.nd.empty(oshape, dtype)
-        get_output(0, out)
-        y_np = np.log(data.asnumpy())
+        data = np.random.uniform(size=dshape).astype(dtype)
+        m.run(x=data)
+        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
+        y_np = np.log(data)
         np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
 
 
@@ -82,17 +67,10 @@ def test_tanh():
         with nnvm.compiler.build_config(opt_level=1):
             graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
         m = nnvm.runtime.create(graph, lib, ctx)
-        # get member functions
-        set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
-        # set input
-        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
-        set_input("x", data)
-        # execute
-        run()
-        # get output
-        out = tvm.nd.empty(oshape, dtype)
-        get_output(0, out)
-        y_np = np.sinh(data.asnumpy()) / np.cosh(data.asnumpy())
+        data = np.random.uniform(size=dshape).astype(dtype)
+        m.run(x=data)
+        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
+        y_np = np.sinh(data) / np.cosh(data)
         np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
 
 
@@ -105,17 +83,10 @@ def test_sigmoid():
     for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
         m = nnvm.runtime.create(graph, lib, ctx)
-        # get member functions
-        set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
-        # set input
-        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
-        set_input("x", data)
-        # execute
-        run()
-        # get output
-        out = tvm.nd.empty(oshape, dtype)
-        get_output(0, out)
-        y_np = 1.0 / (1.0 + np.exp(-data.asnumpy()))
+        data = np.random.uniform(size=dshape).astype(dtype)
+        m.run(x=data)
+        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
+        y_np = 1.0 / (1.0 + np.exp(-data))
         np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
 
 
@@ -129,17 +100,10 @@ def test_softmax():
         with nnvm.compiler.build_config(opt_level=1):
             graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
         m = nnvm.runtime.create(graph, lib, ctx)
-        # get member functions
-        set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
-        # set input
-        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
-        set_input("x", data)
-        # execute
-        run()
-        # get output
-        out = tvm.nd.empty(oshape, dtype)
-        get_output(0, out)
-        y_np = topi.testing.softmax_python(data.asnumpy())
+        data = np.random.uniform(size=dshape).astype(dtype)
+        m.run(x=data)
+        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
+        y_np = topi.testing.softmax_python(data)
         np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
 
 
diff --git a/nnvm/tests/python/compiler/test_top_level2.py b/nnvm/tests/python/compiler/test_top_level2.py
index 79f29f40ce5c..c6f38e06fb78 100644
--- a/nnvm/tests/python/compiler/test_top_level2.py
+++ b/nnvm/tests/python/compiler/test_top_level2.py
@@ -10,8 +10,8 @@
 
 def test_conv2d():
     x = sym.Variable("x")
-    y = sym.conv2d(x, channels=10, kernel_size=(3, 3),
-                   name="y", use_bias=False, padding=(1,1))
+    y = sym.conv2d(x, channels=10, kernel_size=(3,3),
+                   name="y", padding=(1,1))
     dtype = "float32"
     dshape = (1, 3, 18, 18)
     kshape = (10, 3, 3, 3)
@@ -20,26 +20,20 @@ def test_conv2d():
     for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
         m = nnvm.runtime.create(graph, lib, ctx)
-        # get member functions
-        set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
-        # set input
         data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
         kernel = tvm.nd.array(np.random.uniform(size=kshape).astype(dtype))
-        set_input("x", data)
-        set_input("y_weight", kernel)
-        # execute
-        run()
-        # get output
-        out = tvm.nd.empty(oshape, dtype)
-        get_output(0, out)
+        bias = tvm.nd.array(np.random.uniform(size=kshape[0]).astype(dtype))
+        m.run(x=data, y_weight=kernel, y_bias=bias)
+        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
         c_np = topi.testing.conv2d_nchw_python(
             data.asnumpy(), kernel.asnumpy(), 1, 1)
+        c_np = c_np + bias.asnumpy().reshape(kshape[0], 1, 1)
         np.testing.assert_allclose(out.asnumpy(), c_np, rtol=1e-5)
 
 
 def test_grouped_conv2d():
     x = sym.Variable("x")
-    y = sym.conv2d(x, channels=32, kernel_size=(3, 3), groups=32,
+    y = sym.conv2d(x, channels=32, kernel_size=(3,3), groups=32,
                    name="y", padding=(1,1))
     dtype = "float32"
     dshape = (1, 32, 18, 18)
@@ -49,12 +43,10 @@ def test_grouped_conv2d():
     for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
         m = nnvm.runtime.create(graph, lib, ctx)
-        # set input
         data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
         kernel = tvm.nd.array(np.random.uniform(size=kshape).astype(dtype))
         bias = tvm.nd.array(np.random.uniform(size=kshape[0]).astype(dtype))
         m.run(x=data, y_weight=kernel, y_bias=bias)
-        # get output
         out = m.get_output(0, tvm.nd.empty(oshape, dtype))
         c_np = topi.testing.depthwise_conv2d_python_nchw(
             data.asnumpy(), kernel.asnumpy(), (1,1), 'SAME')
@@ -62,6 +54,78 @@ def test_grouped_conv2d():
         np.testing.assert_allclose(out.asnumpy(), c_np, rtol=1e-5)
 
 
+def test_max_pool2d():
+    x = sym.Variable("x")
+    y = sym.max_pool2d(x, pool_size=(2,2), strides=(2,2), padding=(0,0), name="y")
+    dtype = "float32"
+    dshape = (1, 3, 28, 28)
+    oshape = (1, 3, 14, 14)
+    shape_dict = {"x": dshape}
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
+        m = nnvm.runtime.create(graph, lib, ctx)
+        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+        m.run(x=data)
+        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
+        b_np = np.max(data.asnumpy().reshape(1,3,14,2,14,2), axis=(3,5))
+        np.testing.assert_allclose(out.asnumpy(), b_np, rtol=1e-5)
+
+
+def test_avg_pool2d():
+    x = sym.Variable("x")
+    y = sym.avg_pool2d(x, pool_size=(2,2), strides=(2,2), padding=(0,0), name="y")
+    dtype = "float32"
+    dshape = (1, 3, 28, 28)
+    oshape = (1, 3, 14, 14)
+    shape_dict = {"x": dshape}
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
+        m = nnvm.runtime.create(graph, lib, ctx)
+        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+        m.run(x=data)
+        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
+        b_np = np.mean(data.asnumpy().reshape(1,3,14,2,14,2), axis=(3,5))
+        np.testing.assert_allclose(out.asnumpy(), b_np, rtol=1e-5)
+
+
+def test_global_max_pool2d():
+    x = sym.Variable("x")
+    y = sym.global_max_pool2d(x, name="y")
+    dtype = "float32"
+    dshape = (1, 1024, 7, 7)
+    oshape = (1, 1024, 1, 1)
+    shape_dict = {"x": dshape}
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
+        m = nnvm.runtime.create(graph, lib, ctx)
+        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+        m.run(x=data)
+        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
+        b_np = np.max(data.asnumpy(), axis=(2,3), keepdims=True)
+        np.testing.assert_allclose(out.asnumpy(), b_np, rtol=1e-5)
+
+
+def test_global_avg_pool2d():
+    x = sym.Variable("x")
+    y = sym.global_avg_pool2d(x, name="y")
+    dtype = "float32"
+    dshape = (1, 1024, 7, 7)
+    oshape = (1, 1024, 1, 1)
+    shape_dict = {"x": dshape}
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
+        m = nnvm.runtime.create(graph, lib, ctx)
+        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+        m.run(x=data)
+        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
+        b_np = np.mean(data.asnumpy(), axis=(2,3), keepdims=True)
+        np.testing.assert_allclose(out.asnumpy(), b_np, rtol=1e-5)
+
+
 if __name__ == "__main__":
     test_conv2d()
     test_grouped_conv2d()
+    test_max_pool2d()
+    test_avg_pool2d()
+    test_global_max_pool2d()
+    test_global_avg_pool2d()
diff --git a/nnvm/tests/python/frontend/mxnet/test_forward.py b/nnvm/tests/python/frontend/mxnet/test_forward.py
index 8fcc106e4db2..807ab6eab375 100644
--- a/nnvm/tests/python/frontend/mxnet/test_forward.py
+++ b/nnvm/tests/python/frontend/mxnet/test_forward.py
@@ -23,7 +23,7 @@ def default_ctx():
     else:
         return tvm.cpu(0)
 
-def test_mxnet_frontend_impl(mx_symbol, data_shape=(2, 3, 224, 224), out_shape=(2, 1000)):
+def test_mxnet_frontend_impl(mx_symbol, data_shape=(1, 3, 224, 224), out_shape=(1, 1000)):
     def get_mxnet_output(symbol, x, dtype='float32'):
         from collections import namedtuple
         Batch = namedtuple('Batch', ['data'])
@@ -83,6 +83,5 @@ def test_forward_resnet():
 
 if __name__ == '__main__':
     test_forward_mlp()
-    # waiting for max_pool2d
-    # test_forward_vgg()
-    # test_forward_resnet()
+    test_forward_vgg()
+    test_forward_resnet()

From ae0d16c407265b0aeea469e4ccba584ca4e4449c Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 24 Sep 2017 12:03:50 -0700
Subject: [PATCH 490/949] [DOCS] Revamp the docs (#34)

---
 nnvm/README.md                     |  55 ++------
 nnvm/docs/dev/index.rst            |   9 ++
 nnvm/docs/dev/overview.md          | 126 +++++++++++++++++++
 nnvm/docs/index.rst                |   3 +-
 nnvm/docs/overview.md              | 196 -----------------------------
 nnvm/docs/top.rst                  |  23 +++-
 nnvm/python/nnvm/_ctypes/symbol.py |   6 +-
 nnvm/python/nnvm/cython/symbol.pyx |   6 +-
 nnvm/python/nnvm/libinfo.py        |   2 +-
 9 files changed, 173 insertions(+), 253 deletions(-)
 create mode 100644 nnvm/docs/dev/index.rst
 create mode 100644 nnvm/docs/dev/overview.md
 delete mode 100644 nnvm/docs/overview.md

diff --git a/nnvm/README.md b/nnvm/README.md
index 9433cae3b09b..6872e12120d0 100644
--- a/nnvm/README.md
+++ b/nnvm/README.md
@@ -1,55 +1,18 @@
-# NNVM: Build deep learning system by parts
+# NNVM: Graph IR Stack for Deep Learning Systems
 
 [![Build Status](https://travis-ci.org/dmlc/nnvm.svg?branch=master)](https://travis-ci.org/dmlc/nnvm)
 [![GitHub license](http://dmlc.github.io/img/apache2.svg)](./LICENSE)
 
-NNVM is not a deep learning library. It is a modular,
-decentralized and lightweight part to help build deep learning libraries.
+NNVM is a reusable computational graph optimization and compilation stack for deep learning systems.
+NNVM provides modules to:
 
-## What is it
+- Represent deep learning workloads from front-end frameworks via a graph IR.
+- Optimize computation graphs to improve performance.
+- Compile into executable modules and deploy to different hardware backends with minimum dependency.
 
-While most deep learning systems offer end to end solutions,
-it is interesting to  assemble a deep learning system by parts.
-The goal is to enable user to customize optimizations, target platforms and set of operators they care about.
-We believe that the decentralized modular system is an interesting direction.
-
-The hope is that effective parts can be assembled together just like you assemble your own desktops.
-So the customized deep learning solution can be minimax, minimum in terms of dependencies,
-while maximizing the users' need.
-
-NNVM offers one such part, it provides a generic way to do
-computation graph optimization such as memory reduction, device allocation and more
-while being agnostic to the operator interface definition and how operators are executed.
-NNVM is inspired by LLVM, aiming to be a high level intermediate representation library
-for neural nets and computation graphs generation and optimizations.
-
-See [Overview](docs/overview.md) for an introduction on what it provides.
-
-## Example
-See [TinyFlow](https://github.com/tqchen/tinyflow) on how you can build a TensorFlow API with NNVM and Torch.
- 
-## Why build learning system by parts
-
-This is essentially ***Unix philosophy*** applied to machine learning system.
-
-- Essential parts can be assembled in minimum way for embedding systems.
-- Developers can hack the parts they need and compose with other well defined parts.
-- Decentralized modules enable new extensions creators to own their project
-  without creating a monolithic version.
-
-Deep learning system itself is not necessary one part, for example
-here are some relative independent parts that can be isolated
-
-- Computation graph definition, manipulation.
-- Computation graph intermediate optimization.
-- Computation graph execution.
-- Operator kernel libraries.
-- Imperative task scheduling and parallel task coordination.
-
-We hope that there will be more modular parts in the future,
-so system building can be fun and rewarding.
+NNVM is designed to add new frontend, operators and graph optimizations in a decentralized fashion without changing the core interface. NNVM is part of [TVM stack](https://github.com/dmlc/tvm), which provides an end to end IR compilation stack for deploying deep learning workloads into different hardware backends
 
 ## Links
+- [TinyFlow](https://github.com/tqchen/tinyflow) on how you can use  NNVM to build a TensorFlow like API.
+- [Apache MXNet](http://mxnet.io/) uses NNVM as a backend.
 
-[MXNet](https://github.com/dmlc/mxnet) is moving to NNVM as its intermediate
-representation layer for symbolic graphs.
diff --git a/nnvm/docs/dev/index.rst b/nnvm/docs/dev/index.rst
new file mode 100644
index 000000000000..ecee6889d071
--- /dev/null
+++ b/nnvm/docs/dev/index.rst
@@ -0,0 +1,9 @@
+NNVM Design Note
+================
+
+In this part of documentation, we share the rationale for the specific choices made when designing NNVM.
+
+.. toctree::
+   :maxdepth: 2
+
+   overview
diff --git a/nnvm/docs/dev/overview.md b/nnvm/docs/dev/overview.md
new file mode 100644
index 000000000000..35e3e3d72ff5
--- /dev/null
+++ b/nnvm/docs/dev/overview.md
@@ -0,0 +1,126 @@
+
+# NNVM Design Overview
+
+NNVM is a reusable graph IR stack for deep learning systems. It provides useful API to construct, represent and transform computation graphs to get most high-level optimization needed in deep learning.
+As a part of TVM stack for deep learning, NNVM also provides a shared compiler for deep learning frameworks to optimize, compile and deploy into different hardware backends via [TVM](https://github.com/dmlc/tvm)
+
+## Key Requirements and Design Choices
+
+- Have minimum dependency in the deployment module.
+- Being able to add new operators to the IR, in a decentralized fashion.
+- Being able to add new optimization passes to the IR and applies to existing graphs.
+
+The item2 and 3 are particularly interesting if we compare it to a typical compiler IR. Compiler IR usually contains a fixed set of primitives(instructions), and use them as a contract between optimization pass designers. This design enables easy addition of new optimization passes, but not new operator(instruction). Because every time we add a new instruction, we need to modify the passes to accommodate these changes.
+
+Deep learning frameworks usually have a fixed operator interface(schema). These interfaces can contain properties like shape inference function, whether in-place computation can happen.  The operator interface is an again contract that makes it easy to add new an operator. But it is hard to add new passes in decentralized fashion a new optimization pass usually requires additional information, and this results in frequent changes of the centralized operator interface when we are exploring new optimizations. There is also a drawback of modularization. For example, a graph compiler for FPGA devices may not need the GPU device specific attributes.
+
+During our explorations in graph optimization and compilation, we find that it is important to quickly add both operators and passes to the framework without changing the core library.
+
+Here is a list of key elements in NNVM's design
+
+-  Operator registry system to register and add new operators
+-  Operator attribute system provide property of operator in decentralized fashion
+-  A reusable IR data structure for optimization passes.
+
+The above list is more like the generic language part of NNVM, besides of that, we also provide a collection of core operator primitives, and graph optimization passes.   The core tensor operator primitives and optimizations already cover commonly deep learning workloads. This design allows the NNVM compiler to be directly used as optimization and compilation stack for frameworks. The extendible nature of NNVM makes new adjustment easy without constraining the backend providers.
+
+## Minimum Registration for a Symbolic Front-End
+To use NNVM to build language front end, a developer only needs to register minimum information about each operator.
+
+```c++
+NNVM_REGISTER_OP(add)
+.describe("add two data together")
+.set_num_inputs(2);
+
+NNVM_REGISTER_OP(conv2d)
+.describe("take 2d convolution of input")
+.set_num_inputs(2);
+
+NNVM_REGISTER_OP(assign)
+.describe("assign second input argument to the first one")
+.set_num_inputs(2);
+```
+
+Compiling the code with NNVM library. User can use the following interface to compose the computation graph in python, like the following code.
+
+```python
+import nnvm.symbol as nn
+
+# symbolic variable
+x = nn.Variable('x')
+y = nn.Variable('y')
+w = nn.Variable('w')
+
+z = nn.conv2d(nn.elemwise_add(x, y), w, kernel_size=(2,2), name='conv1')
+```
+
+The graph structure is interchangeable between the frontend and the backend.  Python interface is supported currently. More language support can be easily
+moved in the future.
+
+## Operator Attribute for More Extensions
+
+The minimum information provided by the operator is enough to get a front-end. However,   we need more knowledge about each operator to do transformations and executing the graph.
+A typical difference between neural nets' computation graph and traditional compiler IR is that there are a lot more high-level operators. We cannot fix the set of operators in the IR.
+
+NNVM allow developers to register attributes of each operator. The attributes can include shape inference function, whether the operator can perform in-place calculation etc.
+
+This design to having an operator attribute registry is not uncommon in deep learning systems.
+For example, MXNet has a ```OpProperty``` class, Tensorflow has a ```OpDef``` and Caffe2 have a ```OperatorSchema``` class.
+However, the operator attribute interface listed in these frameworks only support a fixed number of defined attributes of interest to the system. If we want to extend the framework to add a new attribute in each operator, we need to change the operator registry.
+Eventually, the operator interface grows into to be very big and have to evolve in the centralized repo.
+
+In NNVM, we decided to change the design and support arbitrary type of operator attributes, without changing the interface registry. The minimum interface also makes it easier to share across multiple projects
+
+User can register new attribute, such as inplace property checking function as follows.
+```c++
+using FInplaceOption = std::function<
+  std::vector<std::pair<int, int> > (const NodeAttrs& attrs)>;
+
+// we can register attributes from multiple places.
+NNVM_REGISTER_OP(elemwise_add)
+.set_num_inputs(2);
+
+// register to tell first input can be calculate inplace with first output
+NNVM_REGISTER_OP(add)
+.set_attr<FInplaceOption>("FInplaceOption", [](const NodeAttrs& attrs) {
+  return std::vector<std::pair<int, int> >{{0, 0}};
+ });
+
+NNVM_REGISTER_OP(exp)
+.set_num_inputs(1)
+.set_attr<FInplaceOption>("FInplaceOption", [](const NodeAttrs& attrs) {
+  return std::vector<std::pair<int, int> >{{0, 0}};
+ });
+```
+
+We can query these attributes at arbitrary parts of the code, like the following parts. Under the hood, each attribute is stored in a columnar store, that can easily be retrieved table and do quick lookups.
+
+```c++
+void MyFunction() {
+  const Op* add = Op::Get("add");
+  // if we need quick query, we can use static variable
+  // attribute map contains attributes of all operators.
+  static auto& finplace_option_map = Op::GetAttr<FInplaceOption>("FInplaceOption");
+
+  // quick look up attribute of add, O(1) time, vector index lookup internally.
+  auto add_inplace = finplace_option_tbl[add];
+}
+```
+Besides making the code minimum, this attribute store enables decentralization of projects.
+Before, all the attributes of operator have to sit on a centralized interface class.
+Now, everyone can register attributes of their own, take some other attributes they need from another project without changing the operator interface and core library
+
+
+## Graph and Pass
+
+We can use the additional information on attribute registry to do optimizations and get more information about the graph. Graph is the unit we manipulate in these steps. A Graph in NNVM contains
+two parts:
+- The computation graph structure
+- A attribute map from string to any type ```map<string, shared_ptr<any> >```
+
+The second attribute map is quite important, as we may need different kinds
+of information about the graph during the transformation process. Let it be
+shapes of each tensor, types of each tensor or the storage allocation plans.
+
+A ```Pass``` can take a graph with existing attribute information,
+and transform it to the same graph structure with more graph attributes or another graph.
diff --git a/nnvm/docs/index.rst b/nnvm/docs/index.rst
index 26f873dd4d3f..9011bacc9e5e 100644
--- a/nnvm/docs/index.rst
+++ b/nnvm/docs/index.rst
@@ -1,6 +1,5 @@
 NNVM Documentation
 ==================
-Welcome to NNVM documentation.
 
 
 Contents
@@ -9,4 +8,6 @@ Contents
 .. toctree::
    :maxdepth: 1
 
+   self
    top
+   dev/index
diff --git a/nnvm/docs/overview.md b/nnvm/docs/overview.md
deleted file mode 100644
index 8d128a3df370..000000000000
--- a/nnvm/docs/overview.md
+++ /dev/null
@@ -1,196 +0,0 @@
-# NNVM Overview
-
-NNVM is a C++ library to help developers to build deep learning system.
-It provides ways to construct, represent and transform computation graphs
-invariant of how it is executed.
-
-To begin with, let us start with a few stories to tell the design goals.
-
-## Stories and Design Goals
-
-X has built a new deep learning framework for image classification for fun,
-with the modular tools like CuDNN and CUDA, it is not hard to assemble a C++ API.
-However, most users like to use python/R/scala or other languages.
-By registering the operators to NNVM, X can now get the graph composition
-language front-end on these languages quickly without coding it up for
-each type of language.
-
-Y want to build a deep learning serving system on embedded devices.
-To do that, we need to cut things off, as opposed to add new parts,
-because codes such as gradient calculation multi-GPU scheduling is NOT relevant.
-It is hard to build things from scratch as well, because we want to
-reuse components such as memory optimization and kernel execution.
-It is hard to do so in current frameworks because all these information
-are tied to the operator interface. We want to be able to keep
-certain part of the system we need and throw away other parts
-to get the minimum system we need.
-
-Z want to extend an existing deep learning system by adding a new feature,
-say FPGA execution of some operators. To do so Z need to add a interface like ```FPGAKernel```
-to the operators. E want to do another new feature that generate code for
-certain subset of operations. Then interface like ```GenLLVMCode``` need to be added
-to the operator. Eventually the system end up with a fat operator interface
-in order to support everything (while everyone only want some part of it).
-
-We can think more stories, as the deep learning landscape shifts to more devices
-applications and scenarios. It is desirable to have different specialized
-learning system to solve some problem well,
-
-Here is a list of things we want:
-- Minimum dependency
-- Being able to assemble some part together while discarding some other parts
-- No centralized operator interface but still allow user to provide various information about operators.
-
-## Minimum Registration for a Symbolic Front-End
-To use NNVM to build language front end, developer only need to register
-minimum information about each operators.
-
-```c++
-NNVM_REGISTER_OP(add)
-.describe("add two data together")
-.set_num_inputs(2);
-
-NNVM_REGISTER_OP(conv2d)
-.describe("take 2d convolution of input")
-.set_num_inputs(2);
-
-NNVM_REGISTER_OP(assign)
-.describe("assign second input argument to the first one")
-.set_num_inputs(2);
-```
-
-Compiling the code with nnvm library. User can use the following interface
-to compose the computation graph in python, like the following code.
-
-```python
-import nnvm.symbol as nn
-
-# symbolic variable
-x = nn.Variable('x')
-y = nn.Variable('y')
-w = nn.Variable('w')
-
-z = nn.conv2d(nn.add(x, y), w, filter_size=(2,2), name='conv1')
-```
-
-The graph structure can be accessed in the backend. Currently python interface is supported.
-But as NNVM follows the same C bridge API design as [MXNet](https://github.com/dmlc/mxnet),
-which support many languages such as R/Julia/Scala/C++, more language support can be easily
-moved in the future.
-
-## Operator Attribute for More Extensions
-
-While the minimum information provided by the operator is enough to get a front-end.
-In order to do transformations and executing the graph. We need more information from each operator.
-A typical difference between neural nets' computation graph and traditional LLVM IR is that
-there are a lot more high level operators. We cannot fix the set of operators in the graph.
-
-Instead developers are allowed to register attributes of operator. The attributes can include shape
-inference function, whether the operator can be carried in-place etc.
-
-This design to having an operator attribute registry is not uncommon in deep learning systems.
-For example, MXNet has a ```OpProperty``` class, Tensorflow has a ```OpDef``` and Caffe2 have a ```OperatorSchema``` class.
-However, the operator attribute interface listed in these frameworks only support a number of defined attributes of interest to the system.
-For example, MXNet support inplace optimization decision, shape and type inference function.
-If we want to extend the framework to add new type of attributes in each operator, we need to change the operator registry.
-Eventually the operator interface become big and have to evolve in the centralized repo.
-
-In NNVM, we decided to change the design and support arbitrary type of operator attributes,
-without need to change the operator registry. This also echos the need of minimum interface
-so that the code can be easier to share across multiple projects
-
-User can register new attribute, such as inplace property checking function as follows.
-```c++
-using FInplaceOption = std::function<
-  std::vector<std::pair<int, int> > (const NodeAttrs& attrs)>;
-
-// attributes can be registered from multiple places.
-NNVM_REGISTER_OP(add)
-.set_num_inputs(1);
-
-// register to tell first input can be calculate inplace with first output
-NNVM_REGISTER_OP(add)
-.set_attr<FInplaceOption>("FInplaceOption", [](const NodeAttrs& attrs) {
-  return std::vector<std::pair<int, int> >{{0, 0}};
- });
-
-NNVM_REGISTER_OP(exp)
-.set_num_inputs(1)
-.set_attr<FInplaceOption>("FInplaceOption", [](const NodeAttrs& attrs) {
-  return std::vector<std::pair<int, int> >{{0, 0}};
- });
-```
-
-These attributes can be queried at arbitrary parts of the code, like the following parts.
-Under the hood, each attributes are stored in a any type columnar store,
-that can easily be retrieved and cast back to typed table and do quick lookups.
-
-```c++
-void MyFunction() {
-  const Op* add = Op::Get("add");
-  // if we need quick query, we can use static variable
-  // attribute map contains attributes of all operators.
-  static auto& finplace_option_map = Op::GetAttr<FInplaceOption>("FInplaceOption");
-
-  // quick look up attribute of add, O(1) time, vector index lookup internally.
-  auto add_inplace = finplace_option_tbl[add];
-}
-```
-Besides making the code minimum, this attribute store enables decentralization of projects.
-Before, all the attributes of operator have to sit on a centralized interface class.
-Now, everyone can register their own attribute, take some other attribute they need from another project
-without need to change the operator interface.
-
-See [example code](../example/src/operator.cc) on how operators can be registered.
-
-## Graph and Pass
-
-When we get more information about the operators.
-We can use them to do optimizations and get more information about the graph.
-Graph is the unit we manipulate in these steps. A Graph in NNVM contains
-two parts:
-- The computation graph structure
-- A attribute map from string to any type ```map<string, shared_ptr<any> >```
-
-The second attribute map is quite important, as we may need different kinds
-of information about the graph during the transformation process. Let it be
-shapes of each tensor, types of each tensor or the storage allocation plans.
-
-A ```Pass``` can take a graph with existing attribute information,
-and transform it to the same graph with more attributes, or another graph.
-
-We have bunch of pass implemented in NNVM, including symbolic differentiation,
-memory planning, shape/type inference and we can support more.
-
-## Executing the Graph
-
-Currently the library defined nothing on how the graph can be executed.
-Execution is intentionally excluded from this module because we believe
-that can be another module, and there can be many ways to execute one graph.
-We can target different runtime platforms, or even write your own ones.
-
-More importantly, the information such as memory allocation plan,
-shape and type of each tensor can be used during execution phase
-to enhance.
-
-We can also register more runtime related information to the operator registry,
-and define pass function to do runtime related optimization of the graph.
-
-## Relation to LLVM
-
-NNVM is inspired by LLVM. It is at a more high level, in a sense that there are a lot of optimization
-chance we can have by knowing the high level information about the operator.
-On the other hand, we do believe that code generation to LLVM can be a natural extension and can benefit some of the usecases.
-
-## Unix Philosophy in Learning Systems
-
-There are a few existing computation graph based deep learning frameworks (e.g. Theano, Tensorflow, Caffe2, MXNet etc.).
-NNVM do not intend to become another one. Instead, NNVM summarizes a module that contains
-
-- The graph representation is minimum, with no code dependency
-- Operator attribute allow arbitrary information registered in unified way
-- Invariant of execution layer to be re-targetable to multiple frontend and backend.
-
-We believe this is the correct way for learning system.
-By having more such modules, we can pick one ones we need, and remove the ones we do not want in our use cases.
-Hopefully these effort can make deep learning system research and building easy, fun and rewarding.
diff --git a/nnvm/docs/top.rst b/nnvm/docs/top.rst
index 945cef00ad44..adbafdc99b6b 100644
--- a/nnvm/docs/top.rst
+++ b/nnvm/docs/top.rst
@@ -1,7 +1,27 @@
 NNVM Core Tensor Operators
 ==========================
 
+This page contains the list of core tensor operator primitives re-defined in NNVM.
+The core tensor operator primitives(``nnvm.top``) covers typical workloads in deep learning.
+They can represent workloads in front-end frameworks, and provide basic building blocks for optimization.
+Since deep learning is a fast evolving field and it is that possible to have operators that are not in here.
+NNVM is designed for this problem and can easily new operators without changing the core library.
+
+.. note::
+
+   Each operator node in the graph IR contains the following two kinds of parameters.
+
+   - inputs: positional list of input tensors
+   - attrs: attributes about operator(e.g. kernel_size in conv2d)
+
+   This document lists both inputs and attributes in the parameter field.  You can distinguish them by the marked type. The inputs are of type Tensor, while the rest parameters are attributes.
+   To construct the graph with NNVM python API, a user can pass in the input Tensors as positional arguments, and attributes as keyword arguments.
+
+
+Overview of Operators
+---------------------
 **Level 1: Basic Operators**
+
 This level enables fully connected multi-layer perceptron.
 
 .. autosummary::
@@ -76,7 +96,8 @@ This level enables typical convnet models.
    nnvm.symbol.broadcast_mul
    nnvm.symbol.broadcast_div
 
-
+Detailed Definitions
+--------------------
 .. autofunction:: nnvm.symbol.dense
 .. autofunction:: nnvm.symbol.relu
 .. autofunction:: nnvm.symbol.tanh
diff --git a/nnvm/python/nnvm/_ctypes/symbol.py b/nnvm/python/nnvm/_ctypes/symbol.py
index 8da572b7769e..7a680f093cd4 100644
--- a/nnvm/python/nnvm/_ctypes/symbol.py
+++ b/nnvm/python/nnvm/_ctypes/symbol.py
@@ -138,12 +138,10 @@ def _make_atomic_symbol_function(handle, name):
 
     doc_str = ('%s\n\n' +
                '%s\n' +
-               'name : string, optional.\n' +
-               '    Name of the resulting symbol.\n\n' +
                'Returns\n' +
                '-------\n' +
-               'symbol: Symbol\n' +
-               '    The result symbol.')
+               'result: Tensor\n' +
+               '    The result Tensor.')
     doc_str = doc_str % (desc, param_str)
 
     def creator(*args, **kwargs):
diff --git a/nnvm/python/nnvm/cython/symbol.pyx b/nnvm/python/nnvm/cython/symbol.pyx
index e2008389179f..6848699b3939 100644
--- a/nnvm/python/nnvm/cython/symbol.pyx
+++ b/nnvm/python/nnvm/cython/symbol.pyx
@@ -122,12 +122,10 @@ cdef _make_atomic_symbol_function(OpHandle handle, string name):
     func_name = py_str(name.c_str())
     doc_str = ('%s\n\n' +
                '%s\n' +
-               'name : string, optional.\n' +
-               '    Name of the resulting symbol.\n\n' +
                'Returns\n' +
                '-------\n' +
-               'symbol: Symbol\n' +
-               '    The result symbol.')
+               'result: Tensor\n' +
+               '    The result Tensor.')
     doc_str = doc_str % (desc, param_str)
     func_hint = func_name.lower()
 
diff --git a/nnvm/python/nnvm/libinfo.py b/nnvm/python/nnvm/libinfo.py
index a369eb40312e..d376c6da0aaf 100644
--- a/nnvm/python/nnvm/libinfo.py
+++ b/nnvm/python/nnvm/libinfo.py
@@ -59,4 +59,4 @@ def find_lib_path():
 
 
 # current version
-__version__ = "0.7.0"
+__version__ = "0.8.0"

From 3bee03266841ba40412a6089129ca0848fecaf9f Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 24 Sep 2017 14:17:06 -0700
Subject: [PATCH 491/949] [TUTORIAL] Move mobilenet to tutorial, fix
 precompute_prune (#35)

* [TUTORIAL] Move mobilenet to tutorial, fix precompute_prune

* Some language improvements
---
 nnvm/docs/.gitignore                      |   2 +
 nnvm/docs/README.txt                      |   4 +
 nnvm/docs/conf.py                         |  30 +++++-
 nnvm/docs/dev/index.rst                   |   4 +-
 nnvm/docs/index.rst                       |   1 +
 nnvm/docs/top.rst                         |   4 +-
 nnvm/example/mobilenet_inference_gpu.py   | 117 --------------------
 nnvm/examples/README.md                   |   5 +
 nnvm/python/nnvm/testing/__init__.py      |   4 +-
 nnvm/python/nnvm/testing/config.py        |   2 +
 nnvm/python/nnvm/testing/mobilenet.py     | 125 ++++++++++++++++++++++
 nnvm/src/compiler/precompute_prune.cc     |  11 +-
 nnvm/tests/python/compiler/test_build.py  |  12 ++-
 nnvm/tutorials/README.txt                 |   3 +
 nnvm/tutorials/mobilenet_inference_gpu.py |  82 ++++++++++++++
 15 files changed, 271 insertions(+), 135 deletions(-)
 create mode 100644 nnvm/docs/README.txt
 delete mode 100644 nnvm/example/mobilenet_inference_gpu.py
 create mode 100644 nnvm/examples/README.md
 create mode 100644 nnvm/python/nnvm/testing/mobilenet.py
 create mode 100644 nnvm/tutorials/README.txt
 create mode 100644 nnvm/tutorials/mobilenet_inference_gpu.py

diff --git a/nnvm/docs/.gitignore b/nnvm/docs/.gitignore
index 024fbfbe7bd0..d5d021127425 100644
--- a/nnvm/docs/.gitignore
+++ b/nnvm/docs/.gitignore
@@ -1,2 +1,4 @@
 doxygen
 _build
+gen_modules
+tutorials
diff --git a/nnvm/docs/README.txt b/nnvm/docs/README.txt
new file mode 100644
index 000000000000..8b8c750822be
--- /dev/null
+++ b/nnvm/docs/README.txt
@@ -0,0 +1,4 @@
+The documentation of nnvm is generated with recommonmark and sphinx.
+
+- pip install sphinx>=1.5.5 sphinx-gallery sphinx_rtd_theme matplotlib Image recommonmark
+- Build tvm first in the root folder.
diff --git a/nnvm/docs/conf.py b/nnvm/docs/conf.py
index af089466e00a..5175167185cd 100644
--- a/nnvm/docs/conf.py
+++ b/nnvm/docs/conf.py
@@ -15,6 +15,7 @@
 import os, subprocess
 import shlex
 import recommonmark
+import sphinx_gallery
 from recommonmark.parser import CommonMarkParser
 from recommonmark.transform import AutoStructify
 
@@ -50,7 +51,8 @@
     'sphinx.ext.autosummary',
     'sphinx.ext.intersphinx',
     'sphinx.ext.napoleon',
-    'sphinx.ext.mathjax'
+    'sphinx.ext.mathjax',
+    'sphinx_gallery.gen_gallery',
 ]
 
 # Add any paths that contain templates here, relative to this directory.
@@ -129,7 +131,7 @@
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+# html_static_path = ['_static']
 
 # Output file base name for HTML help builder.
 htmlhelp_basename = project + 'doc'
@@ -164,9 +166,17 @@ def run_doxygen(folder):
     'numpy': ('http://docs.scipy.org/doc/numpy/', None),
     'scipy': ('http://docs.scipy.org/doc/scipy/reference', None),
     'matplotlib': ('http://matplotlib.org/', None),
+    'tvm': ('http://docs.tvmlang.org/', None),
 }
 
 
+from sphinx_gallery.sorting import ExplicitOrder
+
+examples_dirs = ['../tutorials/']
+gallery_dirs = ['tutorials']
+
+subsection_order = ExplicitOrder([])
+
 def generate_doxygen_xml(app):
     """Run the doxygen make commands if we're on the ReadTheDocs server"""
     run_doxygen('..')
@@ -180,3 +190,19 @@ def setup(app):
         'auto_doc_ref': True
             }, True)
     app.add_transform(AutoStructify)
+
+
+sphinx_gallery_conf = {
+    'backreferences_dir': 'gen_modules/backreferences',
+    'doc_module': ('tvm', 'nnvm', 'numpy'),
+'reference_url': {
+    'nnvm': None,
+    'tvm': 'http://docs.tvmlang.org',
+    'numpy': 'http://docs.scipy.org/doc/numpy-1.9.1'},
+    'examples_dirs': examples_dirs,
+    'gallery_dirs': gallery_dirs,
+    'subsection_order': subsection_order,
+    'find_mayavi_figures': False,
+    'filename_pattern': '.py',
+    'expected_failing_examples': []
+}
diff --git a/nnvm/docs/dev/index.rst b/nnvm/docs/dev/index.rst
index ecee6889d071..0647c9cce586 100644
--- a/nnvm/docs/dev/index.rst
+++ b/nnvm/docs/dev/index.rst
@@ -1,5 +1,5 @@
-NNVM Design Note
-================
+Design Note
+===========
 
 In this part of documentation, we share the rationale for the specific choices made when designing NNVM.
 
diff --git a/nnvm/docs/index.rst b/nnvm/docs/index.rst
index 9011bacc9e5e..14db719029ea 100644
--- a/nnvm/docs/index.rst
+++ b/nnvm/docs/index.rst
@@ -10,4 +10,5 @@ Contents
 
    self
    top
+   tutorials/index
    dev/index
diff --git a/nnvm/docs/top.rst b/nnvm/docs/top.rst
index adbafdc99b6b..89af46509d02 100644
--- a/nnvm/docs/top.rst
+++ b/nnvm/docs/top.rst
@@ -1,5 +1,5 @@
-NNVM Core Tensor Operators
-==========================
+Core Tensor Operators
+=====================
 
 This page contains the list of core tensor operator primitives re-defined in NNVM.
 The core tensor operator primitives(``nnvm.top``) covers typical workloads in deep learning.
diff --git a/nnvm/example/mobilenet_inference_gpu.py b/nnvm/example/mobilenet_inference_gpu.py
deleted file mode 100644
index 4331ec4c0f02..000000000000
--- a/nnvm/example/mobilenet_inference_gpu.py
+++ /dev/null
@@ -1,117 +0,0 @@
-"""Forward propagation of MobileNet on GPU."""
-import numpy as np
-import time
-import os
-
-import tvm
-import topi
-import nnvm.symbol as sym
-import nnvm.compiler
-import nnvm.runtime
-from tvm.contrib import nvcc
-
-TASK="mobilenet"
-
-target = 'cuda'
-ctx = tvm.gpu(0)
-
-@tvm.register_func
-def tvm_callback_cuda_compile(code):
-    ptx = nvcc.compile_cuda(code, target="ptx", options=["-arch=sm_60"])
-    return ptx
-
-def write_code(code, fname):
-    with open(fname, "w") as f:
-        f.write(code)
-
-@tvm.register_func
-def tvm_callback_cuda_postproc(code):
-    if not os.path.exists("perf"):
-        os.mkdir("perf")
-    write_code(code, "perf/%s_generated.cu" % TASK)
-    return code
-
-dtype = 'float32'
-epsilon = 1e-10 + 1e-5
-
-def conv_block(data, name, channels, kernel_size=(3,3), strides=(1,1), padding=(1,1)):
-    # convolution + bn + relu
-    conv = sym.conv2d(data=data, channels=channels, kernel_size=kernel_size, strides=strides,
-        padding=padding, use_bias=False, layout='NCHW', name=name + '_conv')
-    bn = sym.batch_norm(data=conv, epsilon=epsilon, name=name + '_bn')
-    act = sym.relu(data=bn, name=name + '_relu')
-    return act
-
-def separable_conv_block(data, name, depthwise_channels, pointwise_channels, kernel_size=(3,3), downsample=False, padding=(1,1)):
-    if downsample:
-        strides = (2,2)
-    else:
-        strides = (1,1)
-    # depthwise convolution + bn + relu
-    conv1 = sym.conv2d(data=data, channels=depthwise_channels, groups=depthwise_channels, kernel_size=kernel_size, strides=strides,
-        padding=padding, use_bias=False, layout='NCHW', name=name + '_conv1')
-    bn1 = sym.batch_norm(data=conv1, epsilon=epsilon, name=name + '_bn1')
-    act1 = sym.relu(data=bn1, name=name + '_relu1')
-    # pointwise convolution + bn + relu
-    conv2 = sym.conv2d(data=act1, channels=pointwise_channels, kernel_size=(1,1), strides=(1,1),
-        padding=(0,0), use_bias=False, layout='NCHW', name=name + '_conv2')
-    bn2 = sym.batch_norm(data=conv2, epsilon=epsilon, name=name + '_bn2')
-    act2 = sym.relu(data=bn2, name=name + '_relu2')
-    return act2
-
-def mobile_net(num_classes=1000, alpha=1.0, is_shallow=False):
-    data = sym.Variable("data")
-    body = conv_block(data, 'conv_block_1', int(32*alpha), strides=(2,2))
-    body = separable_conv_block(body, 'separable_conv_block_1', int(32*alpha), int(64*alpha))
-    body = separable_conv_block(body, 'separable_conv_block_2', int(64*alpha), int(128*alpha), downsample=True)
-    body = separable_conv_block(body, 'separable_conv_block_3', int(128*alpha), int(128*alpha))
-    body = separable_conv_block(body, 'separable_conv_block_4', int(128*alpha), int(256*alpha), downsample=True)
-    body = separable_conv_block(body, 'separable_conv_block_5', int(256*alpha), int(256*alpha))
-    body = separable_conv_block(body, 'separable_conv_block_6', int(256*alpha), int(512*alpha), downsample=True)
-    if is_shallow:
-        body = separable_conv_block(body, 'separable_conv_block_7', int(512*alpha), int(1024*alpha), downsample=True)
-        body = separable_conv_block(body, 'separable_conv_block_8', int(1024*alpha), int(1024*alpha))
-    else:
-        for i in range(7, 12):
-            body = separable_conv_block(body, 'separable_conv_block_%d' % i, int(512*alpha), int(512*alpha))
-        body = separable_conv_block(body, 'separable_conv_block_12', int(512*alpha), int(1024*alpha), downsample=True)
-        body = separable_conv_block(body, 'separable_conv_block_13', int(1024*alpha), int(1024*alpha))
-    pool = sym.global_avg_pool2d(data=body, name='pool')
-    flatten = sym.flatten(data=pool, name='flatten')
-    fc = sym.dense(data=flatten, units=num_classes, use_bias=False, name='fc')
-    softmax = sym.softmax(data=fc, name='softmax')
-    return softmax
-
-
-batch_size = 1
-num_classes = 1000
-image_shape = (3,224,224)
-data_shape = (batch_size,) + image_shape
-out_shape = (batch_size, num_classes)
-
-net = mobile_net(num_classes=num_classes, alpha=1.0, is_shallow=False)
-
-# build graph
-with nnvm.compiler.build_config(opt_level=2):
-    graph, lib, _ = nnvm.compiler.build(net, target, {'data': data_shape})
-# prepare params
-params = {}
-names = graph.index.input_names
-shapes = [graph.json_attr("shape")[graph.index.entry_id(x)] for x in names]
-for i in range(len(names)):
-    params[names[i]] = tvm.nd.array(np.random.uniform(-0.1, 0.1, size=shapes[i]).astype(dtype), ctx=ctx)
-# create runtime module
-module = nnvm.runtime.create(graph, lib, ctx)
-# set input
-module.set_input(**params)
-# run
-print("run")
-module.run()
-ctx.sync()
-start = time.time()
-for i in range(1000):
-    module.run()
-    ctx.sync()
-print("average time cost of 1000 runs = %g ms" % ((time.time() - start)))
-# get output
-out = module.get_output(0, tvm.nd.empty(out_shape, dtype))
diff --git a/nnvm/examples/README.md b/nnvm/examples/README.md
new file mode 100644
index 000000000000..123007b552ea
--- /dev/null
+++ b/nnvm/examples/README.md
@@ -0,0 +1,5 @@
+NNVM Examples
+=============
+This folder contains example snippets of running NNVM Compilation.
+
+- See also [Tutorials](tutorials) for tutorials with detailed explainations.
diff --git a/nnvm/python/nnvm/testing/__init__.py b/nnvm/python/nnvm/testing/__init__.py
index 6dd015d872ea..27aaad8de09f 100644
--- a/nnvm/python/nnvm/testing/__init__.py
+++ b/nnvm/python/nnvm/testing/__init__.py
@@ -1,3 +1,5 @@
-"""Utilities for testcase"""
+"""Utilities for testing and benchmarks"""
+from __future__ import absolute_import as _abs
 
 from .config import ctx_list
+from . import mobilenet
diff --git a/nnvm/python/nnvm/testing/config.py b/nnvm/python/nnvm/testing/config.py
index a96e4b4ea8e1..0eab3e6b3389 100644
--- a/nnvm/python/nnvm/testing/config.py
+++ b/nnvm/python/nnvm/testing/config.py
@@ -1,4 +1,6 @@
 """Configuration about tests"""
+from __future__ import absolute_import as _abs
+
 import os
 import tvm
 
diff --git a/nnvm/python/nnvm/testing/mobilenet.py b/nnvm/python/nnvm/testing/mobilenet.py
new file mode 100644
index 000000000000..4a08380312d5
--- /dev/null
+++ b/nnvm/python/nnvm/testing/mobilenet.py
@@ -0,0 +1,125 @@
+"""Helper utility to get mobilenet workload for testing."""
+# pylint: disable=invalid-name
+from __future__ import absolute_import as _abs
+
+import numpy as np
+import tvm
+from .. compiler import graph_util
+from .. import graph
+from .. import symbol as sym
+
+def conv_block(data, name, channels,
+               kernel_size=(3, 3), strides=(1, 1), padding=(1, 1),
+               epsilon=1e-5):
+    """Helper function to construct conv-bn-relu"""
+    # convolution + bn + relu
+    conv = sym.conv2d(data=data, channels=channels,
+                      kernel_size=kernel_size, strides=strides,
+                      padding=padding, use_bias=False,
+                      layout="NCHW", name=name + "_conv")
+    bn = sym.batch_norm(data=conv, epsilon=epsilon, name=name + "_bn")
+    act = sym.relu(data=bn, name=name + "_relu")
+    return act
+
+def separable_conv_block(data, name, depthwise_channels,
+                         pointwise_channels, kernel_size=(3, 3),
+                         downsample=False, padding=(1, 1),
+                         epsilon=1e-5):
+    """Helper function to get a separable conv block"""
+    if downsample:
+        strides = (2, 2)
+    else:
+        strides = (1, 1)
+    # depthwise convolution + bn + relu
+    conv1 = sym.conv2d(data=data, channels=depthwise_channels,
+                       groups=depthwise_channels, kernel_size=kernel_size, strides=strides,
+                       padding=padding, use_bias=False, layout="NCHW", name=name + "_conv1")
+    bn1 = sym.batch_norm(data=conv1, epsilon=epsilon, name=name + "_bn1")
+    act1 = sym.relu(data=bn1, name=name + "_relu1")
+    # pointwise convolution + bn + relu
+    conv2 = sym.conv2d(data=act1, channels=pointwise_channels, kernel_size=(1, 1), strides=(1, 1),
+                       padding=(0, 0), use_bias=False, layout="NCHW", name=name + "_conv2")
+    bn2 = sym.batch_norm(data=conv2, epsilon=epsilon, name=name + "_bn2")
+    act2 = sym.relu(data=bn2, name=name + "_relu2")
+    return act2
+
+def mobile_net(num_classes=1000, alpha=1.0, is_shallow=False):
+    """Function to construct a MobileNet"""
+    data = sym.Variable("data")
+    body = conv_block(data, "conv_block_1", int(32*alpha), strides=(2, 2))
+    body = separable_conv_block(body, "separable_conv_block_1",
+                                int(32*alpha), int(64*alpha))
+    body = separable_conv_block(body, "separable_conv_block_2",
+                                int(64*alpha), int(128*alpha), downsample=True)
+    body = separable_conv_block(body, "separable_conv_block_3",
+                                int(128*alpha), int(128*alpha))
+    body = separable_conv_block(body, "separable_conv_block_4",
+                                int(128*alpha), int(256*alpha), downsample=True)
+    body = separable_conv_block(body, "separable_conv_block_5",
+                                int(256*alpha), int(256*alpha))
+    body = separable_conv_block(body, "separable_conv_block_6",
+                                int(256*alpha), int(512*alpha), downsample=True)
+    if is_shallow:
+        body = separable_conv_block(body, "separable_conv_block_7",
+                                    int(512*alpha), int(1024*alpha), downsample=True)
+        body = separable_conv_block(body, "separable_conv_block_8",
+                                    int(1024*alpha), int(1024*alpha))
+    else:
+        for i in range(7, 12):
+            body = separable_conv_block(body, "separable_conv_block_%d" % i,
+                                        int(512*alpha), int(512*alpha))
+        body = separable_conv_block(body, "separable_conv_block_12",
+                                    int(512*alpha), int(1024*alpha), downsample=True)
+        body = separable_conv_block(body, "separable_conv_block_13",
+                                    int(1024*alpha), int(1024*alpha))
+    pool = sym.global_avg_pool2d(data=body, name="pool")
+    flatten = sym.flatten(data=pool, name="flatten")
+    fc = sym.dense(data=flatten, units=num_classes, use_bias=False, name="fc")
+    softmax = sym.softmax(data=fc, name="softmax")
+    return softmax
+
+
+def get_workload(batch_size, num_classes=1000, image_shape=(3, 224, 224), dtype="float32"):
+    """Get benchmark workload for mobilenet
+
+    Parameters
+    ----------
+    batch_size : int
+        The batch size used in the model
+
+    num_classes : int, optional
+        Number of claseses
+
+    image_shape : tuple, optional
+        The input image shape
+
+    dtype : str, optional
+        The data type
+
+    Returns
+    -------
+    net : nnvm.Symbol
+        The computational graph
+
+    params : dict of str to NDArray
+        The parameters.
+    """
+    image_shape = (3, 224, 224)
+    data_shape = (batch_size,) + image_shape
+    net = mobile_net(num_classes=num_classes, alpha=1.0, is_shallow=False)
+    params = {}
+    g = graph.create(net)
+    input_shapes, _ = graph_util.infer_shape(g, data=data_shape)
+    shape_dict = dict(zip(g.index.input_names, input_shapes))
+    for k, v in shape_dict.items():
+        if k == "data":
+            continue
+        # Specially generate non-negative parameters.
+        if k.endswith("gamma"):
+            init = np.random.uniform(0.9, 1, size=v)
+        elif k.endswith("var"):
+            init = np.random.uniform(0.9, 1, size=v)
+        else:
+            init = np.random.uniform(-0.1, 0.1, size=v)
+        params[k] = tvm.nd.array(init.astype(dtype), ctx=tvm.cpu(0))
+    return net, params
diff --git a/nnvm/src/compiler/precompute_prune.cc b/nnvm/src/compiler/precompute_prune.cc
index a0159757c398..a56a0398679c 100644
--- a/nnvm/src/compiler/precompute_prune.cc
+++ b/nnvm/src/compiler/precompute_prune.cc
@@ -44,17 +44,17 @@ nnvm::Graph PrecomputePrune(nnvm::Graph src) {
     } else {
       // scan again to find edge nodes, skip variables
       for (auto& e : n->inputs) {
-        if (!e.node->is_variable() && pruned.count(e.node.get())) {
+        if (pruned.count(e.node.get())) {
           if (!entry_var.count(e)) {
             nnvm::NodePtr var = nnvm::Node::Create();
-            var->attrs.name = e.node->attrs.name + "_output" + std::to_string(e.index);
+            var->attrs.name = e.node->attrs.name;
+            if (e.node->num_outputs() != 1) {
+              var->attrs.name += "_output" + std::to_string(e.index);
+            }
             entry_var.emplace(e, var);
             CHECK(!unique_name.count(var->attrs.name));
             unique_name.insert(var->attrs.name);
           }
-          // TODO(ziheng): this pass now mutates the original graph structure
-          // This might not be a good thing, change to copy the structure instead
-          //
           e = nnvm::NodeEntry{entry_var.at(e), 0, 0};
         }
       }
@@ -67,7 +67,6 @@ nnvm::Graph PrecomputePrune(nnvm::Graph src) {
   output_names.reserve(entry_var.size());
 
   for (auto kv : entry_var) {
-    if (kv.first.node->is_variable()) continue;
     pre_graph.outputs.emplace_back(kv.first);
     output_names.emplace_back(kv.second->attrs.name);
   }
diff --git a/nnvm/tests/python/compiler/test_build.py b/nnvm/tests/python/compiler/test_build.py
index 379975d2d6a4..59220a7ca63e 100644
--- a/nnvm/tests/python/compiler/test_build.py
+++ b/nnvm/tests/python/compiler/test_build.py
@@ -55,26 +55,28 @@ def test_run():
 
 def test_precompute_prune():
     x = sym.Variable("x") + 1
+    a = sym.Variable("a")
     y = sym.Variable("y")
-    z = y + x
+    z = y + x + a
     shape = (10, 10)
     dtype = tvm.float32
     nx = tvm.nd.array(np.random.uniform(size=shape).astype(dtype))
+    na = tvm.nd.array(np.random.uniform(size=shape).astype(dtype))
     ny = tvm.nd.array(np.random.uniform(size=shape).astype(dtype))
-    params = {"x": nx}
+    params = {"x": nx, "a": na}
     graph, lib, params = nnvm.compiler.build(
         z, "llvm", shape={"y": ny.shape}, params=params)
-    assert graph.index.num_nodes == 3
+    assert graph.index.num_nodes == 4
     m = nnvm.runtime.create(graph, lib, tvm.cpu(0))
     params["y"] = ny
     res = tvm.nd.empty(shape)
     m.run(**params)
     out = m.get_output(0, out=res)
     np.testing.assert_allclose(
-        res.asnumpy(), nx.asnumpy() + 1 + ny.asnumpy())
+        res.asnumpy(), nx.asnumpy() + 1 + ny.asnumpy() + na.asnumpy())
 
 
 if __name__ == "__main__":
+    test_precompute_prune()
     test_compile()
     test_run()
-    test_precompute_prune()
diff --git a/nnvm/tutorials/README.txt b/nnvm/tutorials/README.txt
new file mode 100644
index 000000000000..72f772fa6feb
--- /dev/null
+++ b/nnvm/tutorials/README.txt
@@ -0,0 +1,3 @@
+Tutorials
+=========
+This page contains the tutorials about NNVM.
diff --git a/nnvm/tutorials/mobilenet_inference_gpu.py b/nnvm/tutorials/mobilenet_inference_gpu.py
new file mode 100644
index 000000000000..9343316b3896
--- /dev/null
+++ b/nnvm/tutorials/mobilenet_inference_gpu.py
@@ -0,0 +1,82 @@
+"""
+Compile MobileNet Inference on GPU
+==================================
+**Author**: `Yuwei Hu <https://huyuwei.github.io/>`_
+
+This is an example of using NNVM to compile MobileNet model and deploy its inference on GPU.
+
+To begin with, we import nnvm(for compilation) and TVM(for deployment).
+"""
+import tvm
+import nnvm.compiler
+import nnvm.runtime
+import nnvm.testing
+from tvm.contrib import nvcc
+
+######################################################################
+# Register the NVCC Compiler Option
+# ---------------------------------
+# NNVM optimizes the graph and relies on TVM to generate fast
+# GPU code, to get the maximum performance, we need to enable
+# nvcc's compiler hook. This gives better performance than nvrtc mode.
+
+@tvm.register_func
+def tvm_callback_cuda_compile(code):
+    ptx = nvcc.compile_cuda(code, target="ptx", options=["-arch=sm_52"])
+    return ptx
+
+######################################################################
+# Prepare the Benchmark
+# ---------------------
+# We construct a standard imagenet inference benchmark.
+# We use nnvm's testing utility to produce the model description and random parameters that so the example does not
+# depend on a specific front-end framework.
+#
+# .. note::
+#
+#   In a typical workflow, we can get this pair from :any:`nnvm.frontend`
+#
+target = "cuda"
+ctx = tvm.gpu(0)
+batch_size = 1
+num_classes = 1000
+image_shape = (3, 224, 224)
+data_shape = (batch_size,) + image_shape
+out_shape = (batch_size, num_classes)
+net, params = nnvm.testing.mobilenet.get_workload(
+    batch_size=1, image_shape=image_shape)
+
+######################################################################
+# Compile The Graph
+# -----------------
+# NNVM needs two things to compile a deep learning model:
+#
+# - net which is the graph representation of the computation
+# - params a dictionary of str to parameters.
+#
+# To compile the graph, we call the build function with the graph
+# configuration and parameters.
+# When parameters are provided, NNVM will pre-compute certain part of the graph if possible, 
+# the new parameter set returned as the third return value.
+
+graph, lib, params = nnvm.compiler.build(
+    net, target, shape={"data": data_shape}, params=params)
+
+######################################################################
+# Run the Compiled Module
+# -----------------------
+#
+# To deploy the module, we call :any:`nnvm.runtime.create` passing in the graph the lib and context.
+# Thanks to TVM, we can deploy the compiled module to many platforms and languages.
+# The deployment module is designed to contain minimum dependencies.
+# This example runs on the same machine.
+
+module = nnvm.runtime.create(graph, lib, ctx)
+# set input
+module.set_input(**params)
+# run
+module.run()
+# get output
+out = module.get_output(0, tvm.nd.empty(out_shape))
+# Convert to numpy
+out.asnumpy()

From 41d90699b5ff3e0077ffb2f702535b20dff9fc19 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 24 Sep 2017 15:52:35 -0700
Subject: [PATCH 492/949] [DOCS][FRONTEND] Modify from_mxnet to also return
 params, update docs (#36)

---
 nnvm/docs/api/python/compiler.rst             | 19 ++++++++
 nnvm/docs/api/python/frontend.rst             |  7 +++
 nnvm/docs/api/python/graph.rst                |  8 ++++
 nnvm/docs/api/python/index.rst                | 16 +++++++
 nnvm/docs/api/python/runtime.rst              |  8 ++++
 nnvm/docs/api/python/symbol.rst               |  7 +++
 nnvm/docs/api/python/top.rst                  | 13 +++++
 nnvm/docs/index.rst                           |  1 +
 nnvm/examples/README.md                       |  2 +-
 nnvm/python/nnvm/compiler/__init__.py         |  9 ++--
 nnvm/python/nnvm/compiler/build_module.py     | 13 ++++-
 nnvm/python/nnvm/compiler/compile_engine.py   | 10 +++-
 nnvm/python/nnvm/compiler/graph_util.py       |  6 +++
 nnvm/python/nnvm/frontend/__init__.py         |  2 +-
 nnvm/python/nnvm/frontend/mxnet.py            | 26 ++++++++--
 nnvm/python/nnvm/graph.py                     |  5 +-
 nnvm/python/nnvm/symbol.py                    |  6 ++-
 nnvm/python/nnvm/top/__init__.py              |  8 +++-
 nnvm/python/nnvm/top/attr_dict.py             |  1 +
 nnvm/python/nnvm/top/nn.py                    | 23 +++++++--
 nnvm/python/nnvm/top/reduction.py             |  4 +-
 .../python/nnvm/{compiler => top}/registry.py |  0
 nnvm/python/nnvm/top/tensor.py                |  4 +-
 nnvm/python/nnvm/top/transform.py             |  4 +-
 nnvm/src/compiler/packed_func_ext.cc          |  9 +++-
 nnvm/src/compiler/precompute_prune.cc         | 10 ++++
 nnvm/tests/python/compiler/test_top_level1.py | 18 +++++++
 .../python/frontend/mxnet/test_forward.py     | 47 +++++--------------
 28 files changed, 224 insertions(+), 62 deletions(-)
 create mode 100644 nnvm/docs/api/python/compiler.rst
 create mode 100644 nnvm/docs/api/python/frontend.rst
 create mode 100644 nnvm/docs/api/python/graph.rst
 create mode 100644 nnvm/docs/api/python/index.rst
 create mode 100644 nnvm/docs/api/python/runtime.rst
 create mode 100644 nnvm/docs/api/python/symbol.rst
 create mode 100644 nnvm/docs/api/python/top.rst
 rename nnvm/python/nnvm/{compiler => top}/registry.py (100%)

diff --git a/nnvm/docs/api/python/compiler.rst b/nnvm/docs/api/python/compiler.rst
new file mode 100644
index 000000000000..74f3bed07e52
--- /dev/null
+++ b/nnvm/docs/api/python/compiler.rst
@@ -0,0 +1,19 @@
+nnvm.compiler
+-------------
+
+.. automodule:: nnvm.compiler
+
+.. autofunction:: nnvm.compiler.build
+
+.. autofunction:: nnvm.compiler.build_config
+
+.. autofunction:: nnvm.compiler.optimize
+
+.. automodule:: nnvm.compiler.graph_util
+    :members:
+
+.. automodule:: nnvm.compiler.graph_attr
+    :members:
+
+.. automodule:: nnvm.compiler.compile_engine
+    :members:
diff --git a/nnvm/docs/api/python/frontend.rst b/nnvm/docs/api/python/frontend.rst
new file mode 100644
index 000000000000..d1a0ddbd13e5
--- /dev/null
+++ b/nnvm/docs/api/python/frontend.rst
@@ -0,0 +1,7 @@
+nnvm.frontend
+-------------
+
+.. automodule:: nnvm.frontend
+
+
+.. autofunction:: nnvm.frontend.from_mxnet
diff --git a/nnvm/docs/api/python/graph.rst b/nnvm/docs/api/python/graph.rst
new file mode 100644
index 000000000000..5b36ab5194fd
--- /dev/null
+++ b/nnvm/docs/api/python/graph.rst
@@ -0,0 +1,8 @@
+nnvm.graph
+----------
+.. automodule:: nnvm.graph
+
+.. autofunction:: nnvm.graph.create
+
+.. autoclass:: nnvm.graph.Graph
+   :members:
diff --git a/nnvm/docs/api/python/index.rst b/nnvm/docs/api/python/index.rst
new file mode 100644
index 000000000000..71c9f83fca6f
--- /dev/null
+++ b/nnvm/docs/api/python/index.rst
@@ -0,0 +1,16 @@
+Python API
+==========
+
+This document contains the python API to NNVM compiler toolchain.
+For user
+
+
+.. toctree::
+   :maxdepth: 2
+
+   compiler
+   frontend
+   runtime
+   symbol
+   graph
+   top
diff --git a/nnvm/docs/api/python/runtime.rst b/nnvm/docs/api/python/runtime.rst
new file mode 100644
index 000000000000..a187424c68ce
--- /dev/null
+++ b/nnvm/docs/api/python/runtime.rst
@@ -0,0 +1,8 @@
+nnvm.runtime
+------------
+.. automodule:: nnvm.runtime
+
+.. autofunction:: nnvm.runtime.create
+
+.. autoclass:: nnvm.runtime.Module
+    :members:
diff --git a/nnvm/docs/api/python/symbol.rst b/nnvm/docs/api/python/symbol.rst
new file mode 100644
index 000000000000..c3b20d085870
--- /dev/null
+++ b/nnvm/docs/api/python/symbol.rst
@@ -0,0 +1,7 @@
+nnvm.symbol
+-----------
+.. automodule:: nnvm.symbol
+
+.. autoclass:: nnvm.symbol.Symbol
+
+.. autofunction:: nnvm.symbol.Group
diff --git a/nnvm/docs/api/python/top.rst b/nnvm/docs/api/python/top.rst
new file mode 100644
index 000000000000..fd28ff363f0d
--- /dev/null
+++ b/nnvm/docs/api/python/top.rst
@@ -0,0 +1,13 @@
+nnvm.top
+--------
+.. automodule:: nnvm.top
+
+.. autofunction:: register_compute
+
+.. autofunction:: register_schedule
+
+.. autofunction:: register_pattern
+
+
+.. autoclass:: nnvm.top.AttrDict
+   :members:
diff --git a/nnvm/docs/index.rst b/nnvm/docs/index.rst
index 14db719029ea..8bb6c2820e5e 100644
--- a/nnvm/docs/index.rst
+++ b/nnvm/docs/index.rst
@@ -11,4 +11,5 @@ Contents
    self
    top
    tutorials/index
+   api/python/index
    dev/index
diff --git a/nnvm/examples/README.md b/nnvm/examples/README.md
index 123007b552ea..112dc92caaaf 100644
--- a/nnvm/examples/README.md
+++ b/nnvm/examples/README.md
@@ -2,4 +2,4 @@ NNVM Examples
 =============
 This folder contains example snippets of running NNVM Compilation.
 
-- See also [Tutorials](tutorials) for tutorials with detailed explainations.
+- See also [Tutorials](../tutorials) for tutorials with detailed explainations.
diff --git a/nnvm/python/nnvm/compiler/__init__.py b/nnvm/python/nnvm/compiler/__init__.py
index 08cdb1850a39..3954c920a67a 100644
--- a/nnvm/python/nnvm/compiler/__init__.py
+++ b/nnvm/python/nnvm/compiler/__init__.py
@@ -1,4 +1,8 @@
-"""Namespace for NNVM-TVM compiler toolchain"""
+"""NNVM compiler toolchain.
+
+User only need to use :any:`build` and :any:`build_config` to do the compilation.
+The other APIs are for more advanced interaction with the compiler toolchain.
+"""
 from __future__ import absolute_import
 
 import tvm
@@ -10,9 +14,6 @@
 from .. import symbol as _symbol
 from .. import graph as _graph
 
-from .registry import OpPattern
-from .registry import register_compute, register_schedule, register_pattern
-
 from .. import top as _top
 
 
diff --git a/nnvm/python/nnvm/compiler/build_module.py b/nnvm/python/nnvm/compiler/build_module.py
index 18b2a163c692..82a2da4f5caa 100644
--- a/nnvm/python/nnvm/compiler/build_module.py
+++ b/nnvm/python/nnvm/compiler/build_module.py
@@ -105,6 +105,9 @@ def _update_shape_dtype(shape, dtype, params):
 def optimize(graph, shape, dtype="float32"):
     """Perform target and parameter invariant graph optimization.
 
+    This is an advanced function that usually do not need to be called.
+    Call build instead.
+
     Parameters
     ----------
     graph : Graph
@@ -126,7 +129,11 @@ def optimize(graph, shape, dtype="float32"):
 def build(graph, target, shape, dtype="float32", params=None):
     """Build graph into runtime library.
 
-    This is the final step of graph compilation.
+    The build function will optimize the graph and do the compilation.
+
+    When params is provided, the compiler might split the graph to
+    pre-compute certain values, so the final execution graph can
+    be different from the original one.
 
     Parameters
     ----------
@@ -255,8 +262,10 @@ def precompute_prune(graph, params):
     graph._set_json_attr("param_name_list", list(params.keys()), "list_str")
     graph = graph.apply("PrecomputePrune")
     pre_graph = graph_attr._move_out_graph(graph, "precompute_graph")
-    if not pre_graph.symbol.list_output_names():
+    if pre_graph is None:
         return graph, params
     out_names = pre_graph.json_attr("output_names")
+    if not pre_graph.symbol.list_output_names():
+        return graph, params
     out_arrs = _run_graph(pre_graph, params)
     return graph, dict(zip(out_names, out_arrs))
diff --git a/nnvm/python/nnvm/compiler/compile_engine.py b/nnvm/python/nnvm/compiler/compile_engine.py
index 426c56d2d9c3..b215abf6eac2 100644
--- a/nnvm/python/nnvm/compiler/compile_engine.py
+++ b/nnvm/python/nnvm/compiler/compile_engine.py
@@ -1,5 +1,8 @@
 # pylint: disable=invalid-name
-"""Compiler engine interface to internal engine"""
+"""Compiler engine interface to internal engine
+
+You can get the engine singleton at ``nnvm.compiler.engine``
+"""
 import tvm
 
 _list_cache_items = tvm.get_global_func("nnvm.compiler.ListCacheItems")
@@ -30,7 +33,10 @@ class GraphFunc(tvm.node.NodeBase):
 
 
 class Engine(object):
-    """Global singleton compilation engine."""
+    """Global singleton compilation engine.
+
+    You can get the singleton at ``nnvm.compiler.engine``
+    """
     def items(self):
         """List the available cache key value pairs.
 
diff --git a/nnvm/python/nnvm/compiler/graph_util.py b/nnvm/python/nnvm/compiler/graph_util.py
index 68ce857264b4..dcad54a8b778 100644
--- a/nnvm/python/nnvm/compiler/graph_util.py
+++ b/nnvm/python/nnvm/compiler/graph_util.py
@@ -13,6 +13,9 @@ def infer_shape(graph, **shape):
     graph : Graph
         The graph to perform shape inference from
 
+    shape : dict of str to tuple
+        The specific input shape.
+
     Returns
     -------
     in_shape : list of tuple
@@ -38,6 +41,9 @@ def infer_dtype(graph, **dtype):
     graph : Graph
         The graph to perform type inference from
 
+    dtype : dict of str to dtype
+        The specific input data type.
+
     Returns
     -------
     in_dtype : list of tuple
diff --git a/nnvm/python/nnvm/frontend/__init__.py b/nnvm/python/nnvm/frontend/__init__.py
index de6c9ee3f7a1..4db991e29a56 100644
--- a/nnvm/python/nnvm/frontend/__init__.py
+++ b/nnvm/python/nnvm/frontend/__init__.py
@@ -1,3 +1,3 @@
-"""Frontend package."""
+"""NNVM frontends."""
 from __future__ import absolute_import
 from .mxnet import from_mxnet
diff --git a/nnvm/python/nnvm/frontend/mxnet.py b/nnvm/python/nnvm/frontend/mxnet.py
index 4509a31e540c..af64d6b3f316 100644
--- a/nnvm/python/nnvm/frontend/mxnet.py
+++ b/nnvm/python/nnvm/frontend/mxnet.py
@@ -2,6 +2,7 @@
 """MXNet symbol frontend."""
 from __future__ import absolute_import as _abs
 import json
+import tvm
 from .. import symbol as _sym
 
 __all__ = ['from_mxnet']
@@ -288,17 +289,34 @@ def _from_mxnet_impl(symbol, graph):
     return node
 
 
-def from_mxnet(symbol):
-    """Convert from mxnet.Symbol to compatible nnvm.Symbol
+def from_mxnet(symbol, arg_params=None, aux_params=None):
+    """Convert from MXNet's model into compatible NNVM format.
 
     Parameters
     ----------
     symbol : mxnet.Symbol
         MXNet symbol
 
+    arg_params : dict of str to mx.NDArray
+        The argument parameters in mxnet
+
+    aux_params : dict of str to mx.NDArray
+        The auxiliary parameters in mxnet
+
     Returns
     -------
-    nnvm.Symbol
+    net: nnvm.Symbol
         Compatible nnvm symbol
+
+    params : dict of str to tvm.NDArray
+        The parameter dict to be used by nnvm
     """
-    return _from_mxnet_impl(symbol, {})
+    sym = _from_mxnet_impl(symbol, {})
+    params = {}
+    arg_params = arg_params if arg_params else {}
+    aux_params = aux_params if aux_params else {}
+    for k, v in arg_params.items():
+        params[k] = tvm.nd.array(v.asnumpy())
+    for k, v in aux_params.items():
+        params[k] = tvm.nd.array(v.asnumpy())
+    return sym, params
diff --git a/nnvm/python/nnvm/graph.py b/nnvm/python/nnvm/graph.py
index 5a7b36342a36..932d2d131550 100644
--- a/nnvm/python/nnvm/graph.py
+++ b/nnvm/python/nnvm/graph.py
@@ -1,6 +1,9 @@
 # coding: utf-8
 # pylint: disable=invalid-name, protected-access, too-many-arguments, too-many-lines
-"""Symbolic configuration API."""
+"""NNVM Graph IR API.
+
+This is a developer API that is used to manipulate and transform graphs.
+"""
 from __future__ import absolute_import as _abs
 
 import ctypes
diff --git a/nnvm/python/nnvm/symbol.py b/nnvm/python/nnvm/symbol.py
index 0891fb42cf3e..888623403772 100644
--- a/nnvm/python/nnvm/symbol.py
+++ b/nnvm/python/nnvm/symbol.py
@@ -1,5 +1,9 @@
 # pylint: disable=invalid-name, unused-import
-"""Symbolic configuration API."""
+"""Symbolic graph construction API.
+
+This namespace contains most of the registered operators.
+For detailed list of operators, checkout ``Core Tensor Operators``
+"""
 from __future__ import absolute_import as _abs
 import sys as _sys
 import os as _os
diff --git a/nnvm/python/nnvm/top/__init__.py b/nnvm/python/nnvm/top/__init__.py
index 3535c463f9ea..273324d1f88c 100644
--- a/nnvm/python/nnvm/top/__init__.py
+++ b/nnvm/python/nnvm/top/__init__.py
@@ -1,6 +1,12 @@
-"""Declaration about Tensor operators"""
+"""Tensor operator property registry
+
+Provide information to lower and schedule tensor operators.
+"""
 from .attr_dict import AttrDict
 from . import tensor
 from . import nn
 from . import transform
 from . import reduction
+
+from .registry import OpPattern
+from .registry import register_compute, register_schedule, register_pattern
diff --git a/nnvm/python/nnvm/top/attr_dict.py b/nnvm/python/nnvm/top/attr_dict.py
index 2c37a0e40d6a..5dece6b1e980 100644
--- a/nnvm/python/nnvm/top/attr_dict.py
+++ b/nnvm/python/nnvm/top/attr_dict.py
@@ -10,6 +10,7 @@ class AttrDict(object):
     """Attribute dictionary in nnvm.
 
     Used by python registration of compute and schedule function.
+    AttrDict is passed as the first argument to schedule and compute function.
     """
     _tvm_tcode = 18
 
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index c3b32d01a253..84a8f071454f 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -6,8 +6,8 @@
 import topi
 from topi.util import get_const_int
 from .tensor import _fschedule_broadcast
-from ..compiler import registry as reg
-from ..compiler import OpPattern
+from . import registry as reg
+from .registry import OpPattern
 
 # relu
 @reg.register_compute("relu")
@@ -55,9 +55,26 @@ def schedule_softmax(_, outs, target):
     # naive schedule
     return tvm.create_schedule([x.op for x in outs])
 
-# Mark softmax as extern as we do not fuse it in call cases
 reg.register_pattern("softmax", OpPattern.OPAQUE)
 
+# log softmax
+@reg.register_compute("log_softmax")
+def compute_log_softmax(attrs, inputs, _):
+    """Compute definition of softmax"""
+    axis = attrs.get_int("axis")
+    assert axis == -1, "only support axis == -1 for now"
+    return topi.nn.log_softmax(inputs[0])
+
+@reg.register_schedule("log_softmax")
+def schedule_log_softmax(_, outs, target):
+    """Schedule definition of softmax"""
+    if target == "cuda":
+        return topi.cuda.schedule_softmax(outs)
+    # naive schedule
+    return tvm.create_schedule([x.op for x in outs])
+
+# Mark softmax as extern as we do not fuse it in call cases
+reg.register_pattern("log_softmax", OpPattern.OPAQUE)
 
 # dense
 @reg.register_compute("dense")
diff --git a/nnvm/python/nnvm/top/reduction.py b/nnvm/python/nnvm/top/reduction.py
index 345aca74b68b..bee401e42545 100644
--- a/nnvm/python/nnvm/top/reduction.py
+++ b/nnvm/python/nnvm/top/reduction.py
@@ -5,8 +5,8 @@
 import tvm
 import topi
 import topi.cuda
-from ..compiler import registry as reg
-from ..compiler import OpPattern
+from . import registry as reg
+from .registry import OpPattern
 
 def _schedule_reduce(_, outs, target):
     """Generic schedule for reduce"""
diff --git a/nnvm/python/nnvm/compiler/registry.py b/nnvm/python/nnvm/top/registry.py
similarity index 100%
rename from nnvm/python/nnvm/compiler/registry.py
rename to nnvm/python/nnvm/top/registry.py
diff --git a/nnvm/python/nnvm/top/tensor.py b/nnvm/python/nnvm/top/tensor.py
index 0ee3f3d12f6e..15163e28cc90 100644
--- a/nnvm/python/nnvm/top/tensor.py
+++ b/nnvm/python/nnvm/top/tensor.py
@@ -5,8 +5,8 @@
 import tvm
 import topi
 import topi.cuda
-from ..compiler import registry as reg
-from ..compiler import OpPattern
+from . import registry as reg
+from .registry import OpPattern
 
 def _schedule_injective(_, outs, target):
     """Generic schedule for binary bcast"""
diff --git a/nnvm/python/nnvm/top/transform.py b/nnvm/python/nnvm/top/transform.py
index 8fc6130db90f..29cf6016a89e 100644
--- a/nnvm/python/nnvm/top/transform.py
+++ b/nnvm/python/nnvm/top/transform.py
@@ -5,8 +5,8 @@
 import tvm
 import topi
 from .tensor import _fschedule_broadcast, _fschedule_injective
-from ..compiler import registry as reg
-from ..compiler import OpPattern
+from . import registry as reg
+from .registry import OpPattern
 
 # Need add reshape
 @reg.register_compute("expand_dims")
diff --git a/nnvm/src/compiler/packed_func_ext.cc b/nnvm/src/compiler/packed_func_ext.cc
index fed31be4033e..0cb19c576980 100644
--- a/nnvm/src/compiler/packed_func_ext.cc
+++ b/nnvm/src/compiler/packed_func_ext.cc
@@ -110,8 +110,13 @@ TVM_REGISTER_GLOBAL("nnvm.graph._move_module")
 TVM_REGISTER_GLOBAL("nnvm.graph._move_graph")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
     const nnvm::Graph& g = args[0].AsExtension<Graph>();
-    *rv = const_cast<nnvm::Graph*>(&g)->
-        MoveCopyAttr<nnvm::Graph>(args[1]);
+    std::string key = args[1];
+    if (g.attrs.count(key)) {
+      *rv = const_cast<nnvm::Graph*>(&g)->
+          MoveCopyAttr<nnvm::Graph>(key);
+    } else {
+      *rv = nullptr;
+    }
   });
 }  // namespace compiler
 }  // namespace nnvm
diff --git a/nnvm/src/compiler/precompute_prune.cc b/nnvm/src/compiler/precompute_prune.cc
index a56a0398679c..850a961fc63a 100644
--- a/nnvm/src/compiler/precompute_prune.cc
+++ b/nnvm/src/compiler/precompute_prune.cc
@@ -24,6 +24,8 @@ nnvm::Graph PrecomputePrune(nnvm::Graph src) {
   std::unordered_set<nnvm::Node*> pruned;
   nnvm::NodeEntryMap<nnvm::NodePtr> entry_var;
   std::unordered_set<std::string> unique_name;
+  // number of edges that are not variable
+  int non_var_edge = 0;
 
   DFSVisit(src.outputs, [&](const nnvm::NodePtr& n) {
     bool can_be_pruned = true;
@@ -46,6 +48,9 @@ nnvm::Graph PrecomputePrune(nnvm::Graph src) {
       for (auto& e : n->inputs) {
         if (pruned.count(e.node.get())) {
           if (!entry_var.count(e)) {
+            if (!e.node->is_variable()) {
+              ++non_var_edge;
+            }
             nnvm::NodePtr var = nnvm::Node::Create();
             var->attrs.name = e.node->attrs.name;
             if (e.node->num_outputs() != 1) {
@@ -61,6 +66,11 @@ nnvm::Graph PrecomputePrune(nnvm::Graph src) {
     }
   });
 
+  // nothing being pruned.
+  if (non_var_edge == 0) {
+    return src;
+  }
+
   nnvm::Graph pre_graph;
   pre_graph.outputs.reserve(entry_var.size());
   std::vector<std::string> output_names;
diff --git a/nnvm/tests/python/compiler/test_top_level1.py b/nnvm/tests/python/compiler/test_top_level1.py
index 7443449e4096..0686f4412733 100644
--- a/nnvm/tests/python/compiler/test_top_level1.py
+++ b/nnvm/tests/python/compiler/test_top_level1.py
@@ -107,6 +107,23 @@ def test_softmax():
         np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
 
 
+def test_log_softmax():
+    x = sym.Variable("x")
+    y = sym.log_softmax(x)
+    dtype = "float32"
+    dshape = (10, 1000)
+    oshape = dshape
+    for target, ctx in ctx_list():
+        with nnvm.compiler.build_config(opt_level=1):
+            graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
+        m = nnvm.runtime.create(graph, lib, ctx)
+        data = np.random.uniform(size=dshape).astype(dtype)
+        m.run(x=data)
+        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
+        y_np = topi.testing.log_softmax_python(data)
+        np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+
+
 def test_dense():
     x = sym.Variable("x")
     y = sym.dense(x, units=3, name="dense")
@@ -161,6 +178,7 @@ def test_batchnorm():
 
 
 if __name__ == "__main__":
+    test_log_softmax()
     test_batchnorm()
     test_dense()
     test_relu()
diff --git a/nnvm/tests/python/frontend/mxnet/test_forward.py b/nnvm/tests/python/frontend/mxnet/test_forward.py
index 807ab6eab375..380d0bca1d3a 100644
--- a/nnvm/tests/python/frontend/mxnet/test_forward.py
+++ b/nnvm/tests/python/frontend/mxnet/test_forward.py
@@ -5,23 +5,11 @@
 import nnvm.symbol as sym
 import nnvm.compiler
 import nnvm.runtime
+from nnvm.testing.config import ctx_list
 from nnvm import frontend
 import mxnet as mx
 import model_zoo
 
-USE_GPU=True
-
-def default_target():
-    if USE_GPU:
-        return 'cuda'
-    else:
-        return 'llvm'
-
-def default_ctx():
-    if USE_GPU:
-        return tvm.gpu(0)
-    else:
-        return tvm.cpu(0)
 
 def test_mxnet_frontend_impl(mx_symbol, data_shape=(1, 3, 224, 224), out_shape=(1, 1000)):
     def get_mxnet_output(symbol, x, dtype='float32'):
@@ -35,37 +23,28 @@ def get_mxnet_output(symbol, x, dtype='float32'):
         args, auxs = mod.get_params()
         return out, args, auxs
 
-    def get_tvm_output(symbol, x, args, auxs, dtype='float32'):
+    def get_tvm_output(symbol, x, args, auxs, target, ctx, dtype='float32'):
+        new_sym, params = frontend.from_mxnet(symbol, args, auxs)
         dshape = x.shape
         shape_dict = {'data': dshape}
-        for k, v in args.items():
-            shape_dict[k] = v.shape
-        for k, v in auxs.items():
-            shape_dict[k] = v.shape
-        graph, lib, _ = nnvm.compiler.build(symbol, default_target(), shape_dict)
-        m = nnvm.runtime.create(graph, lib, default_ctx())
-        # get member functions
-        set_input, run, get_output = m['set_input'], m['run'], m['get_output']
+        graph, lib, params = nnvm.compiler.build(new_sym, target, shape_dict, params=params)
+        m = nnvm.runtime.create(graph, lib, ctx)
         # set inputs
-        set_input('data', tvm.nd.array(x.astype(dtype)))
-        for k, v in args.items():
-            set_input(k, tvm.nd.array(v.asnumpy().astype(dtype)))
-        for k, v in auxs.items():
-            set_input(k, tvm.nd.array(v.asnumpy().astype(dtype)))
-        # execute
-        run()
+        m.set_input("data", tvm.nd.array(x.astype(dtype)))
+        m.set_input(**params)
+        m.run()
         # get outputs
-        out = tvm.nd.empty(out_shape, dtype)
-        get_output(0, out)
+        out = m.get_output(0, tvm.nd.empty(out_shape, dtype))
         return out.asnumpy()
 
     # random input
     dtype = 'float32'
     x = np.random.uniform(size=data_shape)
     mx_out, args, auxs = get_mxnet_output(mx_symbol, x, dtype)
-    new_sym = frontend.from_mxnet(mx_symbol)
-    tvm_out = get_tvm_output(new_sym, x, args, auxs, dtype)
-    np.testing.assert_allclose(mx_out, tvm_out, rtol=1e-5)
+    assert "data" not in args
+    for target, ctx in ctx_list():
+        tvm_out = get_tvm_output(mx_symbol, x, args, auxs, target, ctx, dtype)
+        np.testing.assert_allclose(mx_out, tvm_out, rtol=1e-5)
 
 def test_forward_mlp():
     mlp = model_zoo.mx_mlp

From 8521944cd634c4af9b1a6ebce02ada52f6242a19 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 24 Sep 2017 19:30:38 -0700
Subject: [PATCH 493/949] [RUNTIME] Remove runtime, rely on tvm only (#38)

---
 nnvm/Makefile                                 |   8 +-
 nnvm/deploy/REAMD.md                          |   4 -
 nnvm/deploy/nnvm_runtime.cc                   |  13 -
 nnvm/python/nnvm/graph.py                     |   4 +
 nnvm/python/nnvm/runtime.py                   | 111 ----
 nnvm/src/compiler/graph_fuse.cc               |  22 +-
 nnvm/src/runtime/graph_executor.cc            | 344 -----------
 nnvm/src/runtime/graph_executor.h             | 128 ----
 nnvm/src/runtime/graph_runtime.cc             | 563 ------------------
 nnvm/src/runtime/graph_runtime.h              |  40 --
 nnvm/tests/python/compiler/test_build.py      |   6 +-
 .../python/compiler/test_compiler_cache.py    |   4 +-
 nnvm/tests/python/compiler/test_op_fusion.py  |   7 +-
 nnvm/tests/python/compiler/test_rpc_exec.py   |   5 +-
 nnvm/tests/python/compiler/test_top_level1.py |  20 +-
 nnvm/tests/python/compiler/test_top_level2.py |  14 +-
 nnvm/tests/python/compiler/test_top_level4.py |   6 +-
 .../python/frontend/mxnet/test_forward.py     |   4 +-
 nnvm/tutorials/mobilenet_inference_gpu.py     |  11 +-
 19 files changed, 66 insertions(+), 1248 deletions(-)
 delete mode 100644 nnvm/deploy/REAMD.md
 delete mode 100644 nnvm/deploy/nnvm_runtime.cc
 delete mode 100644 nnvm/python/nnvm/runtime.py
 delete mode 100644 nnvm/src/runtime/graph_executor.cc
 delete mode 100644 nnvm/src/runtime/graph_executor.h
 delete mode 100644 nnvm/src/runtime/graph_runtime.cc
 delete mode 100644 nnvm/src/runtime/graph_runtime.h

diff --git a/nnvm/Makefile b/nnvm/Makefile
index 9b7e78953d1c..515856c5f078 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -53,10 +53,10 @@ else
 	NO_WHOLE_ARCH= --no-whole-archive
 endif
 
-all: lib/libnnvm.a lib/libnnvm_top.$(SHARED_LIBRARY_SUFFIX) lib/libnnvm_top_runtime.$(SHARED_LIBRARY_SUFFIX)
+all: lib/libnnvm.a lib/libnnvm_top.$(SHARED_LIBRARY_SUFFIX)
 
 SRC = $(wildcard src/*.cc src/c_api/*.cc src/core/*.cc src/pass/*.cc)
-SRC_TOP = $(wildcard src/top/*/*.cc src/runtime/*.cc src/compiler/*.cc src/compiler/*/*.cc)
+SRC_TOP = $(wildcard src/top/*/*.cc src/compiler/*.cc src/compiler/*/*.cc)
 ALL_OBJ = $(patsubst %.cc, build/%.o, $(SRC))
 TOP_OBJ = $(patsubst %.cc, build/%.o, $(SRC_TOP))
 ALL_DEP = $(ALL_OBJ)
@@ -78,10 +78,6 @@ 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
 
diff --git a/nnvm/deploy/REAMD.md b/nnvm/deploy/REAMD.md
deleted file mode 100644
index 96ab18d7514b..000000000000
--- a/nnvm/deploy/REAMD.md
+++ /dev/null
@@ -1,4 +0,0 @@
-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
diff --git a/nnvm/deploy/nnvm_runtime.cc b/nnvm/deploy/nnvm_runtime.cc
deleted file mode 100644
index df72af46cb28..000000000000
--- a/nnvm/deploy/nnvm_runtime.cc
+++ /dev/null
@@ -1,13 +0,0 @@
-/*!
- *  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"*/
-#include "../src/runtime/graph_runtime.cc"
diff --git a/nnvm/python/nnvm/graph.py b/nnvm/python/nnvm/graph.py
index 932d2d131550..bfe5251e2bd8 100644
--- a/nnvm/python/nnvm/graph.py
+++ b/nnvm/python/nnvm/graph.py
@@ -174,6 +174,10 @@ def symbol(self):
         check_call(_LIB.NNGraphGetSymbol(self.handle, ctypes.byref(shandle)))
         return Symbol(shandle)
 
+    def _tvm_graph_json(self):
+        """Get TVM graph json"""
+        return self.apply("SaveJSON").json_attr("json")
+
     @property
     def index(self):
         if not self._index:
diff --git a/nnvm/python/nnvm/runtime.py b/nnvm/python/nnvm/runtime.py
deleted file mode 100644
index dd9866cb4b3a..000000000000
--- a/nnvm/python/nnvm/runtime.py
+++ /dev/null
@@ -1,111 +0,0 @@
-"""Runtime environment for nnvm relies on TVM."""
-import tvm
-from tvm.contrib import rpc
-
-class Module(object):
-    """Wrapper runtime module.
-
-    This is a thin wrapper of the underlying TVM module.
-    you can also directly call set_input, run, and get_output
-    of underlying module functions
-
-    Parameters
-    ----------
-    tvm_module : tvm.Module
-        The interal tvm module
-    """
-    def __init__(self, tvm_module):
-        self.tvm_module = tvm_module
-        self._set_input = tvm_module["set_input"]
-        self._run = tvm_module["run"]
-        self._get_output = tvm_module["get_output"]
-
-    def set_input(self, key=None, value=None, **params):
-        """Set inputs to the module via kwargs
-
-        Parameters
-        ----------
-        key : int or str
-           The input key
-
-        value : the input value.
-           The input key
-
-        params : dict of str to NDArray
-           Additonal arguments
-        """
-        if key:
-            self._set_input(key, tvm.nd.array(value))
-        for k, v in params.items():
-            self._set_input(k, tvm.nd.array(v))
-        return self
-
-    def run(self, **input_dict):
-        """Run forward execution of the graph
-
-        Parameters
-        ----------
-        input_dict: dict of str to NDArray
-            List of input values to be feed to
-        """
-        if input_dict:
-            self.set_input(**input_dict)
-        self._run()
-
-    def get_output(self, index, out):
-        """Get index-th output to out
-
-        Parameters
-        ----------
-        index : int
-            The input index
-
-        out : tvm.NDArray
-            The output array container
-        """
-        self._get_output(index, out)
-        return out
-
-    def __getitem__(self, key):
-        """Get internal module function
-
-        Parameters
-        ----------
-        key : str
-            The key to the module.
-        """
-        return self.tvm_module[key]
-
-
-
-def create(graph, libmod, ctx):
-    """Create a runtime executor module given the graph and module.
-
-    Parameters
-    ----------
-    graph : The graph to be deployed
-        The graph to be loaded.
-
-    libmod : tvm.Module
-        The module of the corresponding function
-
-    ctx : TVMContext
-        The context to deploy the module, can be local or remote.
-
-    Returns
-    -------
-    graph_module : tvm.Module
-        Runtime graph module to execute the graph.
-    """
-    json_str = graph if isinstance(graph, str) else graph.apply("SaveJSON").json_attr("json")
-    device_type = ctx.device_type
-    device_id = ctx.device_id
-    if device_type >= rpc.RPC_SESS_MASK:
-        assert libmod.type_key == "rpc"
-        assert rpc._SessTableIndex(libmod) == ctx._rpc_sess._tbl_index
-        hmod = rpc._ModuleHandle(libmod)
-        fcreate = ctx._rpc_sess.get_function("nnvm.runtime.remote_create")
-        device_type = device_type % rpc.RPC_SESS_MASK
-        return Module(fcreate(json_str, hmod, device_type, device_id))
-    fcreate = tvm.get_global_func("nnvm.runtime.create")
-    return Module(fcreate(json_str, libmod, device_type, device_id))
diff --git a/nnvm/src/compiler/graph_fuse.cc b/nnvm/src/compiler/graph_fuse.cc
index a6395760f19f..bbbe2d834d53 100644
--- a/nnvm/src/compiler/graph_fuse.cc
+++ b/nnvm/src/compiler/graph_fuse.cc
@@ -14,11 +14,31 @@
 #include <tvm/runtime/packed_func.h>
 #include <tvm/lowered_func.h>
 #include "./compile_engine.h"
-#include "../runtime/graph_executor.h"
+#include "../../tvm/src/runtime/graph/graph_runtime.h"
 
 namespace nnvm {
 namespace compiler {
 
+using tvm::runtime::TVMOpParam;
+
+// parser
+inline void TVMOpParamParser(nnvm::NodeAttrs* attrs) {
+  TVMOpParam param;
+  param.Init(attrs->dict);
+  attrs->parsed = std::move(param);
+}
+
+NNVM_REGISTER_OP(tvm_op)
+.set_attr_parser(TVMOpParamParser)
+.set_num_inputs([](const NodeAttrs& attrs) {
+    const TVMOpParam& param = nnvm::get<TVMOpParam>(attrs.parsed);
+    return param.num_inputs;
+  })
+.set_num_outputs([](const NodeAttrs& attrs) {
+    const TVMOpParam& param = nnvm::get<TVMOpParam>(attrs.parsed);
+    return param.num_outputs;
+  });
+
 using namespace tvm;
 
 // The single fuse rule.
diff --git a/nnvm/src/runtime/graph_executor.cc b/nnvm/src/runtime/graph_executor.cc
deleted file mode 100644
index bf72dc56d53e..000000000000
--- a/nnvm/src/runtime/graph_executor.cc
+++ /dev/null
@@ -1,344 +0,0 @@
-/*!
- *  Copyright (c) 2017 by Contributors
- * \file graph_executor.cc
- */
-#include <dmlc/memory_io.h>
-#include <tvm/runtime/registry.h>
-#include <numeric>
-#include "./graph_executor.h"
-
-namespace nnvm {
-namespace runtime {
-
-/*! \brief macro to do C API call */
-#define TVM_CCALL(func)                                            \
-  {                                                                \
-    int ret = (func);                                              \
-    CHECK_EQ(ret, 0)                                               \
-        << TVMGetLastError();                                      \
-  }
-
-using ::tvm::runtime::PackedFunc;
-using ::tvm::runtime::TVMArgs;
-using ::tvm::runtime::TVMRetValue;
-
-PackedFunc GraphExecutor::GetFunction(
-    const std::string& name,
-    const std::shared_ptr<ModuleNode>& sptr_to_self) {
-  // return member functions during query.
-  if (name == "set_input") {
-    return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
-        if (args[0].type_code() == kStr) {
-          this->SetInput(this->GetInputIndex(args[0]), args[1]);
-        } else {
-          this->SetInput(args[0], args[1]);
-        }
-      });
-  } else if (name == "get_output") {
-    return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
-        this->GetOutput(args[0], args[1]);
-      });
-  } else if (name == "run") {
-    return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
-        this->Run();
-      });
-  } else if (name == "load_params") {
-    return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
-        this->LoadParams(args[0].operator std::string());
-      });
-  } else {
-    return PackedFunc();
-  }
-}
-
-GraphExecutor::~GraphExecutor() {
-  for (DLTensor* t : storage_pool_) {
-    TVM_CCALL(TVMArrayFree(t));
-  }
-}
-
-void GraphExecutor::Run() {
-  // setup the array and requirements.
-  for (size_t i = 0; i < op_execs_.size(); ++i) {
-    if (op_execs_[i]) op_execs_[i]();
-  }
-}
-
-void GraphExecutor::Init(Graph graph,
-                         tvm::runtime::Module module,
-                         TVMContext ctx) {
-  graph_ = std::move(graph);
-  module_ = std::move(module);
-  ctx_ = ctx;
-  this->SetupStorage();
-  this->SetupOpExecs();
-}
-
-int GraphExecutor::GetInputIndex(const std::string& name) {
-  const auto& idx = graph_.indexed_graph();
-  for (size_t i = 0; i< idx.input_nodes().size(); ++i) {
-    if (idx[idx.input_nodes()[i]].source->attrs.name == name) {
-      return static_cast<int>(i);
-    }
-  }
-  LOG(FATAL) << "cannot find " << name << " among input";
-  return -1;
-}
-
-void GraphExecutor::SetInput(int index, DLTensor* data_in) {
-  const auto& idx = graph_.indexed_graph();
-  CHECK_LT(static_cast<size_t>(index), idx.input_nodes().size());
-  uint32_t eid = idx.entry_id(idx.input_nodes()[index], 0);
-  TVM_CCALL(TVMArrayCopyFromTo(data_in, &data_entry_[eid], nullptr));
-}
-
-void GraphExecutor::GetOutput(int index, DLTensor* data_out) {
-  const auto& idx = graph_.indexed_graph();
-  CHECK_LT(static_cast<size_t>(index), idx.outputs().size());
-  uint32_t eid = idx.entry_id(idx.outputs()[index]);
-  TVM_CCALL(TVMArrayCopyFromTo(&data_entry_[eid], data_out, nullptr));
-}
-
-bool LoadDLTensor(dmlc::Stream* strm, DLTensor* tensor) {
-  uint64_t header, reserved;
-  CHECK(strm->Read(&header, sizeof(header)))
-      << "Invalid DLTensor file format";
-  CHECK(strm->Read(&reserved, sizeof(reserved)))
-      << "Invalid DLTensor file format";
-  CHECK(header == kTVMNDArrayMagic)
-      << "Invalid DLTensor file format";
-
-  CHECK(strm->Read(&tensor->ctx, sizeof(tensor->ctx)))
-      << "Invalid DLTensor file format";
-  CHECK(strm->Read(&tensor->ndim, sizeof(tensor->ndim)))
-      << "Invalid DLTensor file format";
-  CHECK(strm->Read(&tensor->dtype, sizeof(tensor->dtype)))
-      << "Invalid DLTensor file format";
-
-  int ndim = tensor->ndim;
-  CHECK(strm->Read(tensor->shape, sizeof(int64_t) * ndim))
-      << "Invalid DLTensor file format";
-
-  int64_t size = 1;
-  int type_size = tensor->dtype.bits / 8;
-  for (int i = 0; i < ndim; ++i) {
-    size *= tensor->shape[i];
-  }
-  int64_t data_byte_size;
-  CHECK(strm->Read(&data_byte_size, sizeof(data_byte_size)))
-      << "Invalid DLTensor file format";
-  CHECK(data_byte_size == type_size * size)
-      << "Invalid DLTensor file format";
-  CHECK(strm->Read(tensor->data, type_size * size))
-      << "Invalid DLTensor file format";
-  return true;
-}
-
-void GraphExecutor::LoadParams(dmlc::Stream* strm) {
-  uint64_t header, reserved;
-  CHECK(strm->Read(&header))
-      << "Invalid parameters file format";
-  CHECK(header == kTVMNDArrayListMagic)
-      << "Invalid parameters file format";
-  CHECK(strm->Read(&reserved))
-      << "Invalid parameters file format";
-
-  std::vector<std::string> names;
-  CHECK(strm->Read(&names))
-      << "Invalid parameters file format";
-
-  std::unordered_map<std::string, size_t> name_eid;
-  const auto& idx = graph_.indexed_graph();
-  for (int nid : idx.input_nodes()) {
-    name_eid.emplace(idx[nid].source->attrs.name, idx.entry_id(nid, 0));
-  }
-
-  uint64_t sz;
-  strm->Read(&sz, sizeof(sz));
-  size_t size = static_cast<size_t>(sz);
-  CHECK(size == names.size())
-      << "Invalid parameters file format";
-  for (size_t i = 0; i < size; ++i) {
-    auto iter = name_eid.find(names[i]);
-    CHECK(iter != name_eid.end());
-    CHECK(LoadDLTensor(strm, &data_entry_[iter->second]))
-        << "Invalid parameters file format";
-  }
-}
-
-void GraphExecutor::LoadParams(const std::string& param_blob) {
-  dmlc::MemoryStringStream strm(const_cast<std::string*>(&param_blob));
-  this->LoadParams(&strm);
-}
-
-void GraphExecutor::SetupStorage() {
-  const auto& idx = graph_.indexed_graph();
-  // Grab saved optimization plan from graph.
-  auto vstorage = graph_.MoveCopyAttr<StorageVector>("storage_id");
-  std::vector<TVMType> vtype;
-  for (const std::string& s_type :
-           graph_.GetAttr<std::vector<std::string> >("dltype")) {
-    vtype.push_back(tvm::runtime::String2TVMType(s_type));
-  }
-  data_shape_ = graph_.GetAttr<ShapeVector>("shape");
-  data_entry_.resize(idx.num_node_entries());
-  // Find the maximum space size.
-  int max_id = 0;
-  for (size_t i = 0; i < data_shape_.size(); ++i) {
-    max_id = std::max(vstorage[i] + 1, max_id);
-  }
-  for (const auto& e : idx.input_nodes()) {
-    vstorage[idx.entry_id(e, 0)] = max_id++;
-  }
-  // size of each storage pool entry
-  std::vector<size_t> pool_entry_bytes;
-  // Find the maximum space size.
-  for (size_t i = 0; i < data_shape_.size(); ++i) {
-    int storage_id = vstorage[i];
-    size_t size = data_shape_[i].Size();
-    CHECK_GE(storage_id, 0) << "Do not support runtime shape op";
-    DLDataType t = vtype[i];
-    size_t bits = t.bits * t.lanes;
-    CHECK_EQ(bits % 8U, 0U);
-    size_t bytes = (bits / 8U) * size;
-
-    size_t sid = static_cast<size_t>(storage_id);
-    if (sid >= pool_entry_bytes.size()) {
-      pool_entry_bytes.resize(sid + 1, 0);
-    }
-    pool_entry_bytes[sid] = std::max(pool_entry_bytes[sid], bytes);
-  }
-  // Allocate the space.
-  for (size_t i = 0; i < pool_entry_bytes.size(); ++i) {
-    TShape shape{static_cast<int64_t>(pool_entry_bytes[i] + 3) / 4};
-    DLTensor* tensor;
-    TVM_CCALL(TVMArrayAlloc(
-        shape.data(), 1, kFloat, 32, 1, ctx_.device_type, ctx_.device_id, &tensor));
-    storage_pool_.push_back(tensor);
-  }
-  // Assign the pooled entries.
-  for (size_t i = 0; i < data_entry_.size(); ++i) {
-    int storage_id = vstorage[i];
-    data_entry_[i] = *storage_pool_[storage_id];
-    data_entry_[i].shape = const_cast<int64_t*>(data_shape_[i].data());
-    data_entry_[i].ndim = data_shape_[i].ndim();
-    data_entry_[i].dtype = vtype[i];
-  }
-}
-
-void GraphExecutor::SetupOpExecs() {
-  static const nnvm::Op* tvm_op = nnvm::Op::Get("tvm_op");
-  const auto& idx = graph_.indexed_graph();
-  op_execs_.resize(idx.num_nodes());
-  // setup the array and requirements.
-  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
-    const auto& inode = idx[nid];
-    if (inode.source->is_variable()) continue;
-    std::vector<DLTensor> args;
-    for (const auto& e : inode.inputs) {
-      args.push_back(data_entry_[idx.entry_id(e)]);
-    }
-    for (uint32_t index = 0; index < inode.source->num_outputs(); ++index) {
-      uint32_t eid = idx.entry_id(nid, index);
-      args.push_back(data_entry_[eid]);
-    }
-    CHECK_EQ(inode.source->op(), tvm_op)
-        << "transform the graph to tvm op";
-    op_execs_[nid] = CreateTVMOp(
-        inode.source->attrs, args, inode.inputs.size());
-  }
-}
-
-std::function<void()> GraphExecutor::CreateTVMOp(
-    const nnvm::NodeAttrs& attrs,
-    const std::vector<DLTensor>& args,
-    size_t num_inputs) {
-  struct OpArgs {
-    std::vector<DLTensor> args;
-    std::vector<TVMValue> arg_values;
-    std::vector<int> arg_tcodes;
-    std::vector<int64_t> shape_data;
-  };
-  const TVMOpParam& param = nnvm::get<TVMOpParam>(attrs.parsed);
-  std::shared_ptr<OpArgs> arg_ptr = std::make_shared<OpArgs>();
-  // setup address.
-  arg_ptr->args = std::move(args);
-  if (param.flatten_data) {
-    arg_ptr->shape_data.resize(arg_ptr->args.size());
-  }
-  for (size_t i = 0; i < arg_ptr->args.size(); ++i) {
-    TVMValue v;
-    DLTensor* t = &(arg_ptr->args[i]);
-    v.v_handle = t;
-    arg_ptr->arg_values.push_back(v);
-    arg_ptr->arg_tcodes.push_back(kArrayHandle);
-    if (param.flatten_data) {
-      arg_ptr->shape_data[i] = std::accumulate(
-          t->shape, t->shape + t->ndim, 1, std::multiplies<int64_t>());
-      t->ndim = 1;
-      t->shape = &(arg_ptr->shape_data[i]);
-    }
-  }
-  // get compiled function from module.
-  tvm::runtime::PackedFunc pf = module_.GetFunction(param.func_name, false);
-  CHECK(pf != nullptr) << "no such function in module: " << param.func_name;
-  auto fexec = [arg_ptr, pf] () {
-    TVMRetValue rv;
-    TVMArgs targs(arg_ptr->arg_values.data(),
-                  arg_ptr->arg_tcodes.data(),
-                  static_cast<int>(arg_ptr->arg_values.size()));
-    pf.CallPacked(targs, &rv);
-  };
-  return fexec;
-}
-
-// parser
-inline void TVMOpParamParser(nnvm::NodeAttrs* attrs) {
-  TVMOpParam param;
-  param.Init(attrs->dict);
-  attrs->parsed = std::move(param);
-}
-
-
-NNVM_REGISTER_OP(tvm_op)
-.set_attr_parser(TVMOpParamParser)
-.set_num_inputs([](const NodeAttrs& attrs) {
-    const TVMOpParam& param = nnvm::get<TVMOpParam>(attrs.parsed);
-    return param.num_inputs;
-  })
-.set_num_outputs([](const NodeAttrs& attrs) {
-    const TVMOpParam& param = nnvm::get<TVMOpParam>(attrs.parsed);
-    return param.num_outputs;
-  });
-
-tvm::runtime::Module RuntimeCreate(std::string sym_json,
-                                   tvm::runtime::Module m,
-                                   int device_type,
-                                   int device_id) {
-  TVMContext ctx;
-  ctx.device_type = static_cast<DLDeviceType>(device_type);
-  ctx.device_id   = device_id;
-  // load graph from json string
-  nnvm::Graph g;
-  g.attrs["json"] = std::make_shared<nnvm::any>(sym_json);
-  g = nnvm::ApplyPass(std::move(g), "LoadJSON");
-  std::shared_ptr<GraphExecutor> exec = std::make_shared<GraphExecutor>();
-  exec->Init(g, m, ctx);
-  return tvm::runtime::Module(exec);
-}
-
-TVM_REGISTER_GLOBAL("nnvm.runtime.createx")
-.set_body([](TVMArgs args, TVMRetValue *rv) {
-    *rv = RuntimeCreate(args[0], args[1], args[2], args[3]);
-  });
-
-TVM_REGISTER_GLOBAL("nnvm.runtime.remote_createx")
-.set_body([](TVMArgs args, TVMRetValue *rv) {
-    void* mhandle = args[1];
-    *rv = RuntimeCreate(args[0],
-                        *static_cast<tvm::runtime::Module*>(mhandle),
-                        args[2], args[3]);
-  });
-
-}  // namespace runtime
-}  // namespace nnvm
diff --git a/nnvm/src/runtime/graph_executor.h b/nnvm/src/runtime/graph_executor.h
deleted file mode 100644
index 215240610657..000000000000
--- a/nnvm/src/runtime/graph_executor.h
+++ /dev/null
@@ -1,128 +0,0 @@
-/*!
- *  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>
-#include "./graph_runtime.h"
-
-namespace nnvm {
-namespace runtime {
-
-/*!
- * \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_
diff --git a/nnvm/src/runtime/graph_runtime.cc b/nnvm/src/runtime/graph_runtime.cc
deleted file mode 100644
index d9faeb45f783..000000000000
--- a/nnvm/src/runtime/graph_runtime.cc
+++ /dev/null
@@ -1,563 +0,0 @@
-/*!
- *  Copyright (c) 2017 by Contributors
- * \file graph_executor.cc
- */
-#include <dmlc/memory_io.h>
-#include <dmlc/json.h>
-#include <tvm/runtime/packed_func.h>
-#include <tvm/runtime/registry.h>
-#include <numeric>
-#include "./graph_runtime.h"
-
-namespace nnvm {
-namespace runtime {
-
-/*! \brief macro to do C API call */
-#define TVM_CCALL(func)                                            \
-  {                                                                \
-    int ret = (func);                                              \
-    CHECK_EQ(ret, 0)                                               \
-        << TVMGetLastError();                                      \
-  }
-
-using ::tvm::runtime::PackedFunc;
-using ::tvm::runtime::TVMArgs;
-using ::tvm::runtime::TVMRetValue;
-
-/*!
- * \brief Minimum graph structure for deployment
- *  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 GraphRuntime : public ::tvm::runtime::ModuleNode {
- public:
-  ~GraphRuntime() {
-    for (DLTensor* t : storage_pool_) {
-      TVM_CCALL(TVMArrayFree(t));
-    }
-  }
-  /*!
-   * \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;
-
-  /*!
-   * \return The type key of the executor.
-   */
-  const char* type_key() const final {
-    return "GraphRuntime";
-  }
-  void Run() {
-    // setup the array and requirements.
-    for (size_t i = 0; i < op_execs_.size(); ++i) {
-      if (op_execs_[i]) op_execs_[i]();
-    }
-  }
-  /*!
-   * \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(const std::string& graph_json,
-            tvm::runtime::Module module,
-            TVMContext ctx) {
-    std::istringstream is(graph_json);
-    dmlc::JSONReader reader(&is);
-    this->Load(&reader);
-    module_ = module;
-    ctx_ = ctx;
-    this->SetupStorage();
-    this->SetupOpExecs();
-  }
-  /*!
-   * \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) {
-    for (size_t i = 0; i< input_nodes_.size(); ++i) {
-      uint32_t nid = input_nodes_[i];
-      if (nodes_[nid].name == name) {
-        return static_cast<int>(i);
-      }
-    }
-    LOG(FATAL) << "cannot find " << name << " among input";
-    return -1;
-  }
-  /*!
-   * \brief set index-th input to the graph.
-   * \param index The input index.
-   * \param data The input data.
-   */
-  void SetInput(int index, DLTensor* data_in) {
-    CHECK_LT(static_cast<size_t>(index), input_nodes_.size());
-    uint32_t eid = this->entry_id(input_nodes_[index], 0);
-    TVM_CCALL(TVMArrayCopyFromTo(data_in, &data_entry_[eid], nullptr));
-  }
-  /*!
-   * \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) {
-    CHECK_LT(static_cast<size_t>(index), outputs_.size());
-    uint32_t eid = this->entry_id(outputs_[index]);
-    TVM_CCALL(TVMArrayCopyFromTo(&data_entry_[eid], data_out, nullptr));
-  }
-
-  /*!
-   * \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) {
-    dmlc::MemoryStringStream strm(const_cast<std::string*>(&param_blob));
-    this->LoadParams(&strm);
-  }
-
- private:
-  // Node entry
-  struct NodeEntry {
-    uint32_t node_id;
-    uint32_t index;
-    uint32_t version;
-    // JSON Loader
-    void Load(dmlc::JSONReader *reader) {
-      reader->BeginArray();
-      CHECK(reader->NextArrayItem()) << "invalid json format";
-      reader->Read(&node_id);
-      CHECK(reader->NextArrayItem()) << "invalid json format";
-      reader->Read(&index);
-      if (reader->NextArrayItem()) {
-        reader->Read(&version);
-        CHECK(!reader->NextArrayItem()) << "invalid json format";
-      } else {
-        version = 0;
-      }
-    }
-  };
-  // Node
-  struct Node {
-    // operator type in string
-    std::string op_type;
-    // name of the op
-    std::string name;
-    // parameters
-    TVMOpParam param;
-    // inputs
-    std::vector<NodeEntry> inputs;
-    // control deps
-    std::vector<uint32_t> control_deps;
-    // JSON Loader
-    void Load(dmlc::JSONReader *reader) {
-      reader->BeginObject();
-      std::unordered_map<std::string, std::string> dict;
-      int bitmask = 0;
-      std::string key;
-      while (reader->NextObjectItem(&key)) {
-        if (key == "op") {
-          reader->Read(&op_type);
-          bitmask |= 1;
-        } else if (key == "name") {
-          reader->Read(&name);
-          bitmask |= 2;
-        } else if (key == "inputs") {
-          reader->Read(&inputs);
-          bitmask |= 4;
-        } else if (key == "attr") {
-          reader->Read(&dict);
-          param.Init(dict);
-        } else if (key == "control_deps") {
-          reader->Read(&control_deps);
-        } else {
-          LOG(FATAL) << "do not support key" << key;
-        }
-      }
-      CHECK_EQ(bitmask, 1|2|4) << "invalid format";
-    }
-  };
-  struct GraphAttr {
-    size_t storage_num_not_alloctaed{0};
-    std::vector<int> storage_id;
-    std::vector<std::string> dltype;
-    std::vector<std::vector<int64_t> > shape;
-    // The graph attribute fields.
-    void Load(dmlc::JSONReader *reader) {
-      reader->BeginObject();
-      int bitmask = 0;
-      std::string key, type;
-      while (reader->NextObjectItem(&key)) {
-        if (key == "dltype") {
-          reader->BeginArray();
-          CHECK(reader->NextArrayItem());
-          reader->Read(&type);
-          CHECK_EQ(type, "list_str");
-          CHECK(reader->NextArrayItem());
-          reader->Read(&dltype);
-          CHECK(!reader->NextArrayItem());
-          bitmask |= 1;
-        } else if (key == "storage_id") {
-          reader->BeginArray();
-          CHECK(reader->NextArrayItem());
-          reader->Read(&type);
-          CHECK_EQ(type, "list_int");
-          CHECK(reader->NextArrayItem());
-          reader->Read(&storage_id);
-          CHECK(!reader->NextArrayItem());
-          bitmask |= 2;
-        } else if (key == "shape") {
-          reader->BeginArray();
-          CHECK(reader->NextArrayItem());
-          reader->Read(&type);
-          CHECK_EQ(type, "list_shape");
-          CHECK(reader->NextArrayItem());
-          reader->Read(&shape);
-          CHECK(!reader->NextArrayItem());
-          bitmask |= 4;
-        } else {
-          reader->BeginArray();
-          CHECK(reader->NextArrayItem());
-          reader->Read(&type);
-          if (type == "list_int") {
-            CHECK(reader->NextArrayItem());
-            std::vector<int> temp;
-            reader->Read(&temp);
-          } else if (type == "size_t") {
-            CHECK(reader->NextArrayItem());
-            size_t temp;
-            reader->Read(&temp);
-          } else {
-            LOG(FATAL) << "cannot skip graph attr " << key;
-          }
-          CHECK(!reader->NextArrayItem());
-        }
-      }
-      CHECK_EQ(bitmask, 1|2|4) << "invalid format";
-    }
-  };
-  // The graph attribute fields.
-  void Load(dmlc::JSONReader *reader) {
-      reader->BeginObject();
-      int bitmask = 0;
-      std::string key;
-      while (reader->NextObjectItem(&key)) {
-        if (key == "nodes") {
-          reader->Read(&nodes_);
-          bitmask |= 1;
-        } else if (key == "arg_nodes") {
-          reader->Read(&input_nodes_);
-          bitmask |= 2;
-        } else if (key == "node_row_ptr") {
-          reader->Read(&node_row_ptr_);
-          bitmask |= 4;
-        } else if (key == "heads") {
-          reader->Read(&outputs_);
-          bitmask |= 8;
-        } else if (key == "attrs") {
-          reader->Read(&attrs_);
-          bitmask |= 16;
-        }
-      }
-      CHECK_EQ(bitmask, 1|2|4|8|16) << "invalid format";
-  }
-  bool LoadDLTensor(dmlc::Stream* strm, DLTensor* tensor);
-  /*! \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 TVMOpParam& attrs,
-                                    const std::vector<DLTensor>& args,
-                                    size_t num_inputs);
-  // Get node entry index.
-  uint32_t entry_id(uint32_t nid, uint32_t index) const {
-    return node_row_ptr_[nid] + index;
-  }
-  // Get node entry index.
-  uint32_t entry_id(const NodeEntry& e) const {
-    return entry_id(e.node_id, e.index);
-  }
-  // Number of node entries
-  uint32_t num_node_entries() const {
-    return node_row_ptr_.back();
-  }
-  // Number of nodes.
-  uint32_t num_nodes() const {
-    return static_cast<uint32_t>(nodes_.size());
-  }
-  // The graph nodes.
-  std::vector<Node> nodes_;
-  // The argument nodes.
-  std::vector<uint32_t> input_nodes_;
-  // used or quick entry indexing
-  std::vector<uint32_t> node_row_ptr_;
-  // output entries
-  std::vector<NodeEntry> outputs_;
-  // Additional graph attributes
-  GraphAttr attrs_;
-  /*! \brief The code module */
-  tvm::runtime::Module module_;
-  /*! \brief execution context */
-  TVMContext ctx_;
-  /*! \brief common storage pool */
-  std::vector<DLTensor*> storage_pool_;
-  /*! \brief data entry of each node */
-  std::vector<DLTensor> data_entry_;
-  /*! \brief operator on each node */
-  std::vector<std::function<void()> > op_execs_;
-};
-
-DMLC_REGISTER_PARAMETER(TVMOpParam);
-
-bool GraphRuntime::LoadDLTensor(dmlc::Stream* strm, DLTensor* tensor) {
-  uint64_t header, reserved;
-  CHECK(strm->Read(&header, sizeof(header)))
-      << "Invalid DLTensor file format";
-  CHECK(strm->Read(&reserved, sizeof(reserved)))
-      << "Invalid DLTensor file format";
-  CHECK(header == kTVMNDArrayMagic)
-      << "Invalid DLTensor file format";
-
-  CHECK(strm->Read(&tensor->ctx, sizeof(tensor->ctx)))
-      << "Invalid DLTensor file format";
-  CHECK(strm->Read(&tensor->ndim, sizeof(tensor->ndim)))
-      << "Invalid DLTensor file format";
-  CHECK(strm->Read(&tensor->dtype, sizeof(tensor->dtype)))
-      << "Invalid DLTensor file format";
-
-  int ndim = tensor->ndim;
-  CHECK(strm->Read(tensor->shape, sizeof(int64_t) * ndim))
-      << "Invalid DLTensor file format";
-
-  int64_t size = 1;
-  int type_size = tensor->dtype.bits / 8;
-  for (int i = 0; i < ndim; ++i) {
-    size *= tensor->shape[i];
-  }
-  int64_t data_byte_size;
-  CHECK(strm->Read(&data_byte_size, sizeof(data_byte_size)))
-      << "Invalid DLTensor file format";
-  CHECK(data_byte_size == type_size * size)
-      << "Invalid DLTensor file format";
-  CHECK(strm->Read(tensor->data, type_size * size))
-      << "Invalid DLTensor file format";
-  return true;
-}
-
-void GraphRuntime::LoadParams(dmlc::Stream* strm) {
-  uint64_t header, reserved;
-  CHECK(strm->Read(&header))
-      << "Invalid parameters file format";
-  CHECK(header == kTVMNDArrayListMagic)
-      << "Invalid parameters file format";
-  CHECK(strm->Read(&reserved))
-      << "Invalid parameters file format";
-
-  std::vector<std::string> names;
-  CHECK(strm->Read(&names))
-      << "Invalid parameters file format";
-  uint64_t sz;
-  strm->Read(&sz, sizeof(sz));
-  size_t size = static_cast<size_t>(sz);
-
-  CHECK(size == names.size())
-      << "Invalid parameters file format";
-
-  for (size_t i = 0; i < size; ++i) {
-    uint32_t in_idx = GetInputIndex(names[i]);
-    CHECK(LoadDLTensor(strm, &data_entry_[this->entry_id(input_nodes_[in_idx], 0)]))
-        << "Invalid parameters file format";
-  }
-}
-
-void GraphRuntime::SetupStorage() {
-  // Grab saved optimization plan from graph.
-  std::vector<TVMType> vtype;
-  for (const std::string& s_type : attrs_.dltype) {
-    vtype.push_back(tvm::runtime::String2TVMType(s_type));
-  }
-  data_entry_.resize(num_node_entries());
-  // Find the maximum space size.
-  int max_id = 0;
-  for (size_t i = 0; i < attrs_.shape.size(); ++i) {
-    max_id = std::max(attrs_.storage_id[i] + 1, max_id);
-  }
-  for (uint32_t nid : input_nodes_) {
-    attrs_.storage_id[this->entry_id(nid, 0)] = max_id++;
-  }
-  // size of each storage pool entry
-  std::vector<size_t> pool_entry_bytes;
-  // Find the maximum space size.
-  for (size_t i = 0; i < attrs_.shape.size(); ++i) {
-    int storage_id = attrs_.storage_id[i];
-    size_t size = 1;
-    for (int64_t sz : attrs_.shape[i]) {
-      size *= static_cast<size_t>(sz);
-    }
-    CHECK_GE(storage_id, 0) << "Do not support runtime shape op";
-    DLDataType t = vtype[i];
-    size_t bits = t.bits * t.lanes;
-    CHECK_EQ(bits % 8U, 0U);
-    size_t bytes = (bits / 8U) * size;
-
-    size_t sid = static_cast<size_t>(storage_id);
-    if (sid >= pool_entry_bytes.size()) {
-      pool_entry_bytes.resize(sid + 1, 0);
-    }
-    pool_entry_bytes[sid] = std::max(pool_entry_bytes[sid], bytes);
-  }
-  // Allocate the space.
-  for (size_t i = 0; i < pool_entry_bytes.size(); ++i) {
-    int64_t shape[] = {static_cast<int64_t>(pool_entry_bytes[i] + 3) / 4};
-    DLTensor* tensor;
-    TVM_CCALL(TVMArrayAlloc(
-        shape, 1, kFloat, 32, 1, ctx_.device_type, ctx_.device_id, &tensor));
-    storage_pool_.push_back(tensor);
-  }
-  // Assign the pooled entries.
-  for (size_t i = 0; i < data_entry_.size(); ++i) {
-    int storage_id = attrs_.storage_id[i];
-    data_entry_[i] = *storage_pool_[storage_id];
-    data_entry_[i].shape = const_cast<int64_t*>(attrs_.shape[i].data());
-    data_entry_[i].ndim = static_cast<int>(attrs_.shape[i].size());
-    data_entry_[i].dtype = vtype[i];
-  }
-}
-
-/*! \brief Setup the executors */
-void GraphRuntime::SetupOpExecs() {
-  op_execs_.resize(this->num_nodes());
-  // setup the array and requirements.
-  for (uint32_t nid = 0; nid < this->num_nodes(); ++nid) {
-    const auto& inode = nodes_[nid];
-    if (inode.op_type == "null") continue;
-    std::vector<DLTensor> args;
-    for (const auto& e : inode.inputs) {
-      args.push_back(data_entry_[this->entry_id(e)]);
-    }
-    for (uint32_t index = 0; index < inode.param.num_outputs; ++index) {
-      uint32_t eid = this->entry_id(nid, index);
-      args.push_back(data_entry_[eid]);
-    }
-    CHECK_EQ(inode.op_type, "tvm_op")
-        << "transform the graph to tvm op5A";
-    op_execs_[nid] = CreateTVMOp(inode.param, args, inode.inputs.size());
-  }
-}
-
-std::function<void()> GraphRuntime::CreateTVMOp(
-    const TVMOpParam& param,
-    const std::vector<DLTensor>& args,
-    size_t num_inputs) {
-  struct OpArgs {
-    std::vector<DLTensor> args;
-    std::vector<TVMValue> arg_values;
-    std::vector<int> arg_tcodes;
-    std::vector<int64_t> shape_data;
-  };
-  std::shared_ptr<OpArgs> arg_ptr = std::make_shared<OpArgs>();
-  // setup address.
-  arg_ptr->args = std::move(args);
-  if (param.flatten_data) {
-    arg_ptr->shape_data.resize(arg_ptr->args.size());
-  }
-  for (size_t i = 0; i < arg_ptr->args.size(); ++i) {
-    TVMValue v;
-    DLTensor* t = &(arg_ptr->args[i]);
-    v.v_handle = t;
-    arg_ptr->arg_values.push_back(v);
-    arg_ptr->arg_tcodes.push_back(kArrayHandle);
-    if (param.flatten_data) {
-      arg_ptr->shape_data[i] = std::accumulate(
-          t->shape, t->shape + t->ndim, 1, std::multiplies<int64_t>());
-      t->ndim = 1;
-      t->shape = &(arg_ptr->shape_data[i]);
-    }
-  }
-  // get compiled function from module.
-  tvm::runtime::PackedFunc pf = module_.GetFunction(param.func_name, false);
-  CHECK(pf != nullptr) << "no such function in module: " << param.func_name;
-  auto fexec = [arg_ptr, pf] () {
-    TVMRetValue rv;
-    TVMArgs targs(arg_ptr->arg_values.data(),
-                  arg_ptr->arg_tcodes.data(),
-                  static_cast<int>(arg_ptr->arg_values.size()));
-    pf.CallPacked(targs, &rv);
-  };
-  return fexec;
-}
-
-PackedFunc GraphRuntime::GetFunction(
-    const std::string& name,
-    const std::shared_ptr<ModuleNode>& sptr_to_self) {
-  // return member functions during query.
-  if (name == "set_input") {
-    return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
-        if (args[0].type_code() == kStr) {
-          this->SetInput(this->GetInputIndex(args[0]), args[1]);
-        } else {
-          this->SetInput(args[0], args[1]);
-        }
-      });
-  } else if (name == "get_output") {
-    return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
-        this->GetOutput(args[0], args[1]);
-      });
-  } else if (name == "run") {
-    return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
-        this->Run();
-      });
-  } else if (name == "load_params") {
-    return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
-        this->LoadParams(args[0].operator std::string());
-      });
-  } else {
-    return PackedFunc();
-  }
-}
-
-tvm::runtime::Module GraphRuntimeCreate(std::string sym_json,
-                                        tvm::runtime::Module m,
-                                        int device_type,
-                                        int device_id) {
-  TVMContext ctx;
-  ctx.device_type = static_cast<DLDeviceType>(device_type);
-  ctx.device_id   = device_id;
-  std::shared_ptr<GraphRuntime> exec = std::make_shared<GraphRuntime>();
-  exec->Init(sym_json, m, ctx);
-  return tvm::runtime::Module(exec);
-}
-
-TVM_REGISTER_GLOBAL("nnvm.runtime.create")
-.set_body([](TVMArgs args, TVMRetValue *rv) {
-    *rv = GraphRuntimeCreate(args[0], args[1], args[2], args[3]);
-  });
-
-TVM_REGISTER_GLOBAL("nnvm.runtime.remote_create")
-.set_body([](TVMArgs args, TVMRetValue *rv) {
-    void* mhandle = args[1];
-    *rv = GraphRuntimeCreate(args[0],
-                             *static_cast<tvm::runtime::Module*>(mhandle),
-                             args[2], args[3]);
-  });
-}  // namespace runtime
-}  // namespace nnvm
diff --git a/nnvm/src/runtime/graph_runtime.h b/nnvm/src/runtime/graph_runtime.h
deleted file mode 100644
index 6ed130f1dd5c..000000000000
--- a/nnvm/src/runtime/graph_runtime.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*!
- *  Copyright (c) 2017 by Contributors
- *
- *  Runtime module for graph deployment.
- *
- * \file graph_executor.h
- */
-#ifndef NNVM_RUNTIME_GRAPH_RUNTIME_H_
-#define NNVM_RUNTIME_GRAPH_RUNTIME_H_
-
-#include <dmlc/parameter.h>
-#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 operator attributes about tvm op */
-struct TVMOpParam : public dmlc::Parameter<TVMOpParam> {
-  std::string func_name;
-  uint32_t num_inputs;
-  uint32_t num_outputs;
-  uint32_t 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(0);
-  }
-};
-
-}  // namespace runtime
-}  // namespace nnvm
-
-#endif  // NNVM_RUNTIME_GRAPH_RUNTIME_H_
diff --git a/nnvm/tests/python/compiler/test_build.py b/nnvm/tests/python/compiler/test_build.py
index 59220a7ca63e..936631185032 100644
--- a/nnvm/tests/python/compiler/test_build.py
+++ b/nnvm/tests/python/compiler/test_build.py
@@ -1,9 +1,9 @@
 import numpy as np
 
 import tvm
+from tvm.contrib import graph_runtime
 import nnvm.symbol as sym
 import nnvm.compiler
-import nnvm.runtime
 from nnvm.compiler.build_module import _run_graph, precompute_prune
 
 def test_compile():
@@ -14,7 +14,7 @@ def test_compile():
     dtype = tvm.float32
     shape_dict = {"x": shape, "y": shape}
     def verify(graph, lib):
-        m = nnvm.runtime.create(graph, lib, tvm.cpu(0))
+        m = graph_runtime.create(graph, lib, tvm.cpu(0))
         # get member functions
         set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
         na = tvm.nd.array(np.random.uniform(size=shape).astype(dtype))
@@ -67,7 +67,7 @@ def test_precompute_prune():
     graph, lib, params = nnvm.compiler.build(
         z, "llvm", shape={"y": ny.shape}, params=params)
     assert graph.index.num_nodes == 4
-    m = nnvm.runtime.create(graph, lib, tvm.cpu(0))
+    m = graph_runtime.create(graph, lib, tvm.cpu(0))
     params["y"] = ny
     res = tvm.nd.empty(shape)
     m.run(**params)
diff --git a/nnvm/tests/python/compiler/test_compiler_cache.py b/nnvm/tests/python/compiler/test_compiler_cache.py
index f7666b39f005..970b193a6875 100644
--- a/nnvm/tests/python/compiler/test_compiler_cache.py
+++ b/nnvm/tests/python/compiler/test_compiler_cache.py
@@ -1,8 +1,8 @@
 import numpy as np
 import tvm
+from tvm.contrib import graph_runtime
 import nnvm.symbol as sym
 import nnvm.compiler
-import nnvm.runtime
 
 def test_compile_cache():
     x = sym.Variable("x")
@@ -12,7 +12,7 @@ def test_compile_cache():
     dtype = tvm.float32
     shape_dict = {"x": shape, "y": shape}
     def verify(graph, lib):
-        m = nnvm.runtime.create(graph, lib, tvm.cpu(0))
+        m = graph_runtime.create(graph, lib, tvm.cpu(0))
         # get member functions
         na = tvm.nd.array(np.random.uniform(size=shape).astype(dtype))
         nb = tvm.nd.array(np.random.uniform(size=shape).astype(dtype))
diff --git a/nnvm/tests/python/compiler/test_op_fusion.py b/nnvm/tests/python/compiler/test_op_fusion.py
index a7c6ca4b288d..4c079b41bb81 100644
--- a/nnvm/tests/python/compiler/test_op_fusion.py
+++ b/nnvm/tests/python/compiler/test_op_fusion.py
@@ -2,6 +2,7 @@
 import numpy as np
 import tvm
 import topi
+from tvm.contrib import graph_runtime
 from nnvm import symbol as sym
 from nnvm.compiler import graph_util, graph_attr
 from nnvm.testing import ctx_list
@@ -17,7 +18,7 @@ def test_ewise_injective():
     for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
         assert graph.index.num_nodes == 2
-        m = nnvm.runtime.create(graph, lib, ctx)
+        m = graph_runtime.create(graph, lib, ctx)
         x_np = np.random.uniform(size=dshape).astype(dtype)
         m.run(x=x_np)
         out = m.get_output(0, tvm.nd.empty((10, 6)))
@@ -39,7 +40,7 @@ def test_conv_ewise_injective():
 
     for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
-        m = nnvm.runtime.create(graph, lib, ctx)
+        m = graph_runtime.create(graph, lib, ctx)
         # print(graph.ir(join_entry_attrs=["shape"]))
         assert graph.index.num_nodes == 5
         # set input
@@ -66,7 +67,7 @@ def test_injective_reduce_injective():
 
     for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
-        m = nnvm.runtime.create(graph, lib, ctx)
+        m = graph_runtime.create(graph, lib, ctx)
         assert graph.index.num_nodes == 2
         data = np.random.uniform(size=dshape).astype(dtype)
         m.run(x=data)
diff --git a/nnvm/tests/python/compiler/test_rpc_exec.py b/nnvm/tests/python/compiler/test_rpc_exec.py
index 4a94a1d7686e..4cfd87ad243f 100644
--- a/nnvm/tests/python/compiler/test_rpc_exec.py
+++ b/nnvm/tests/python/compiler/test_rpc_exec.py
@@ -1,8 +1,7 @@
-from tvm.contrib import util, rpc
 import tvm
+from tvm.contrib import util, rpc, graph_runtime
 import nnvm.symbol as sym
 import nnvm.compiler
-import nnvm.runtime
 import numpy as np
 
 def test_rpc_executor():
@@ -29,7 +28,7 @@ def test_rpc_executor():
     rlib = remote.load_module("net.o")
 
     # Create remotemodule
-    m = nnvm.runtime.create(graph, rlib, remote.cpu(0))
+    m = graph_runtime.create(graph, rlib, remote.cpu(0))
     # get member functions
     set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
     na = tvm.nd.array(np.ones(shape).astype(dtype), ctx)
diff --git a/nnvm/tests/python/compiler/test_top_level1.py b/nnvm/tests/python/compiler/test_top_level1.py
index 0686f4412733..83f1c25d25df 100644
--- a/nnvm/tests/python/compiler/test_top_level1.py
+++ b/nnvm/tests/python/compiler/test_top_level1.py
@@ -1,9 +1,9 @@
 import numpy as np
 import tvm
+from tvm.contrib import graph_runtime
 import topi
 import nnvm.symbol as sym
 import nnvm.compiler
-import nnvm.runtime
 from nnvm.testing.config import ctx_list
 
 def test_relu():
@@ -15,7 +15,7 @@ def test_relu():
     oshape = dshape
     for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
-        m = nnvm.runtime.create(graph, lib, ctx)
+        m = graph_runtime.create(graph, lib, ctx)
         data = np.random.uniform(size=dshape).astype(dtype)
         m.run(x=data)
         data = (data < 0) * data * 0.3 + (data>0) * data - 0.2
@@ -32,7 +32,7 @@ def test_exp():
     oshape = dshape
     for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
-        m = nnvm.runtime.create(graph, lib, ctx)
+        m = graph_runtime.create(graph, lib, ctx)
         data = np.random.uniform(size=dshape).astype(dtype)
         m.run(x=data)
         out = m.get_output(0, tvm.nd.empty(oshape, dtype))
@@ -49,7 +49,7 @@ def test_log():
     for target, ctx in ctx_list():
         with nnvm.compiler.build_config(opt_level=1):
             graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
-        m = nnvm.runtime.create(graph, lib, ctx)
+        m = graph_runtime.create(graph, lib, ctx)
         data = np.random.uniform(size=dshape).astype(dtype)
         m.run(x=data)
         out = m.get_output(0, tvm.nd.empty(oshape, dtype))
@@ -66,7 +66,7 @@ def test_tanh():
     for target, ctx in ctx_list():
         with nnvm.compiler.build_config(opt_level=1):
             graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
-        m = nnvm.runtime.create(graph, lib, ctx)
+        m = graph_runtime.create(graph, lib, ctx)
         data = np.random.uniform(size=dshape).astype(dtype)
         m.run(x=data)
         out = m.get_output(0, tvm.nd.empty(oshape, dtype))
@@ -82,7 +82,7 @@ def test_sigmoid():
     oshape = dshape
     for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
-        m = nnvm.runtime.create(graph, lib, ctx)
+        m = graph_runtime.create(graph, lib, ctx)
         data = np.random.uniform(size=dshape).astype(dtype)
         m.run(x=data)
         out = m.get_output(0, tvm.nd.empty(oshape, dtype))
@@ -99,7 +99,7 @@ def test_softmax():
     for target, ctx in ctx_list():
         with nnvm.compiler.build_config(opt_level=1):
             graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
-        m = nnvm.runtime.create(graph, lib, ctx)
+        m = graph_runtime.create(graph, lib, ctx)
         data = np.random.uniform(size=dshape).astype(dtype)
         m.run(x=data)
         out = m.get_output(0, tvm.nd.empty(oshape, dtype))
@@ -116,7 +116,7 @@ def test_log_softmax():
     for target, ctx in ctx_list():
         with nnvm.compiler.build_config(opt_level=1):
             graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
-        m = nnvm.runtime.create(graph, lib, ctx)
+        m = graph_runtime.create(graph, lib, ctx)
         data = np.random.uniform(size=dshape).astype(dtype)
         m.run(x=data)
         out = m.get_output(0, tvm.nd.empty(oshape, dtype))
@@ -136,7 +136,7 @@ def test_dense():
     }
     for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, shape)
-        m = nnvm.runtime.create(graph, lib, ctx)
+        m = graph_runtime.create(graph, lib, ctx)
         x_np = np.random.uniform(size=shape["x"]).astype(dtype)
         w_np = np.random.uniform(size=shape["dense_weight"]).astype(dtype)
         b_np = np.random.uniform(size=shape["dense_bias"]).astype(dtype)
@@ -162,7 +162,7 @@ def test_batchnorm():
 
     for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, "llvm", {"x": shape})
-        m = nnvm.runtime.create(graph, lib, tvm.cpu(0))
+        m = graph_runtime.create(graph, lib, tvm.cpu(0))
         x_np = np.random.uniform(size=shape).astype(dtype)
         mean_np = np.random.uniform(size=shape[1]).astype(dtype)
         var_np = np.random.uniform(size=shape[1]).astype(dtype)
diff --git a/nnvm/tests/python/compiler/test_top_level2.py b/nnvm/tests/python/compiler/test_top_level2.py
index c6f38e06fb78..312094cd31d4 100644
--- a/nnvm/tests/python/compiler/test_top_level2.py
+++ b/nnvm/tests/python/compiler/test_top_level2.py
@@ -1,10 +1,10 @@
 import numpy as np
 
 import tvm
+from tvm.contrib import graph_runtime
 import topi
 import nnvm.symbol as sym
 import nnvm.compiler
-import nnvm.runtime
 from nnvm.testing.config import ctx_list
 
 
@@ -19,7 +19,7 @@ def test_conv2d():
     shape_dict = {"x": dshape}
     for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
-        m = nnvm.runtime.create(graph, lib, ctx)
+        m = graph_runtime.create(graph, lib, ctx)
         data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
         kernel = tvm.nd.array(np.random.uniform(size=kshape).astype(dtype))
         bias = tvm.nd.array(np.random.uniform(size=kshape[0]).astype(dtype))
@@ -42,7 +42,7 @@ def test_grouped_conv2d():
     shape_dict = {"x": dshape}
     for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
-        m = nnvm.runtime.create(graph, lib, ctx)
+        m = graph_runtime.create(graph, lib, ctx)
         data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
         kernel = tvm.nd.array(np.random.uniform(size=kshape).astype(dtype))
         bias = tvm.nd.array(np.random.uniform(size=kshape[0]).astype(dtype))
@@ -63,7 +63,7 @@ def test_max_pool2d():
     shape_dict = {"x": dshape}
     for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
-        m = nnvm.runtime.create(graph, lib, ctx)
+        m = graph_runtime.create(graph, lib, ctx)
         data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
         m.run(x=data)
         out = m.get_output(0, tvm.nd.empty(oshape, dtype))
@@ -80,7 +80,7 @@ def test_avg_pool2d():
     shape_dict = {"x": dshape}
     for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
-        m = nnvm.runtime.create(graph, lib, ctx)
+        m = graph_runtime.create(graph, lib, ctx)
         data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
         m.run(x=data)
         out = m.get_output(0, tvm.nd.empty(oshape, dtype))
@@ -97,7 +97,7 @@ def test_global_max_pool2d():
     shape_dict = {"x": dshape}
     for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
-        m = nnvm.runtime.create(graph, lib, ctx)
+        m = graph_runtime.create(graph, lib, ctx)
         data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
         m.run(x=data)
         out = m.get_output(0, tvm.nd.empty(oshape, dtype))
@@ -114,7 +114,7 @@ def test_global_avg_pool2d():
     shape_dict = {"x": dshape}
     for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
-        m = nnvm.runtime.create(graph, lib, ctx)
+        m = graph_runtime.create(graph, lib, ctx)
         data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
         m.run(x=data)
         out = m.get_output(0, tvm.nd.empty(oshape, dtype))
diff --git a/nnvm/tests/python/compiler/test_top_level4.py b/nnvm/tests/python/compiler/test_top_level4.py
index eac3178e3e45..bc2c5465d461 100644
--- a/nnvm/tests/python/compiler/test_top_level4.py
+++ b/nnvm/tests/python/compiler/test_top_level4.py
@@ -1,9 +1,9 @@
 import numpy as np
 import tvm
+from tvm.contrib import graph_runtime
 import topi
 import nnvm.symbol as sym
 import nnvm.compiler
-import nnvm.runtime
 from nnvm.testing.config import ctx_list
 
 def verify_transpose(dshape, axes):
@@ -16,7 +16,7 @@ def verify_transpose(dshape, axes):
     dtype = "float32"
     for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
-        m = nnvm.runtime.create(graph, lib, ctx)
+        m = graph_runtime.create(graph, lib, ctx)
         # set input
         data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
         m.run(x=data)
@@ -31,7 +31,7 @@ def verify_reduce(dshape, fnp, fsym, **kwargs):
     dtype = "float32"
     for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
-        m = nnvm.runtime.create(graph, lib, ctx)
+        m = graph_runtime.create(graph, lib, ctx)
         # set input
         data = np.random.uniform(size=dshape).astype(dtype)
         out_np = fnp(data + 1, **kwargs)
diff --git a/nnvm/tests/python/frontend/mxnet/test_forward.py b/nnvm/tests/python/frontend/mxnet/test_forward.py
index 380d0bca1d3a..07acb74cfaa7 100644
--- a/nnvm/tests/python/frontend/mxnet/test_forward.py
+++ b/nnvm/tests/python/frontend/mxnet/test_forward.py
@@ -2,9 +2,9 @@
 
 import topi
 import tvm
+from tvm.contrib import graph_runtime
 import nnvm.symbol as sym
 import nnvm.compiler
-import nnvm.runtime
 from nnvm.testing.config import ctx_list
 from nnvm import frontend
 import mxnet as mx
@@ -28,7 +28,7 @@ def get_tvm_output(symbol, x, args, auxs, target, ctx, dtype='float32'):
         dshape = x.shape
         shape_dict = {'data': dshape}
         graph, lib, params = nnvm.compiler.build(new_sym, target, shape_dict, params=params)
-        m = nnvm.runtime.create(graph, lib, ctx)
+        m = graph_runtime.create(graph, lib, ctx)
         # set inputs
         m.set_input("data", tvm.nd.array(x.astype(dtype)))
         m.set_input(**params)
diff --git a/nnvm/tutorials/mobilenet_inference_gpu.py b/nnvm/tutorials/mobilenet_inference_gpu.py
index 9343316b3896..3f68c6ff255f 100644
--- a/nnvm/tutorials/mobilenet_inference_gpu.py
+++ b/nnvm/tutorials/mobilenet_inference_gpu.py
@@ -8,10 +8,9 @@
 To begin with, we import nnvm(for compilation) and TVM(for deployment).
 """
 import tvm
+from tvm.contrib import nvcc, graph_runtime
 import nnvm.compiler
-import nnvm.runtime
 import nnvm.testing
-from tvm.contrib import nvcc
 
 ######################################################################
 # Register the NVCC Compiler Option
@@ -56,7 +55,7 @@ def tvm_callback_cuda_compile(code):
 #
 # To compile the graph, we call the build function with the graph
 # configuration and parameters.
-# When parameters are provided, NNVM will pre-compute certain part of the graph if possible, 
+# When parameters are provided, NNVM will pre-compute certain part of the graph if possible,
 # the new parameter set returned as the third return value.
 
 graph, lib, params = nnvm.compiler.build(
@@ -66,12 +65,14 @@ def tvm_callback_cuda_compile(code):
 # Run the Compiled Module
 # -----------------------
 #
-# To deploy the module, we call :any:`nnvm.runtime.create` passing in the graph the lib and context.
+# To deploy the module, we call :any:`tvm.contrib.graph_runtime.create` passing in the graph the lib and context.
 # Thanks to TVM, we can deploy the compiled module to many platforms and languages.
 # The deployment module is designed to contain minimum dependencies.
 # This example runs on the same machine.
+#
+# Note that the code below no longer depends on NNVM, and only relies TVM's runtime to run(deploy).
 
-module = nnvm.runtime.create(graph, lib, ctx)
+module = graph_runtime.create(graph, lib, ctx)
 # set input
 module.set_input(**params)
 # run

From 79a0603ed4c4f018098201d2eb865e921214fa4d Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Sun, 24 Sep 2017 21:09:35 -0700
Subject: [PATCH 494/949] [FIX] Make master compile

---
 nnvm/python/nnvm/top/nn.py      |  8 ++++----
 nnvm/src/compiler/graph_fuse.cc | 20 ++++++++++++++++++--
 2 files changed, 22 insertions(+), 6 deletions(-)

diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index 84a8f071454f..9fe945d1eb6c 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -141,9 +141,9 @@ def compute_max_pool2d(attrs, inputs, _):
     strides = attrs.get_int_tuple("strides")
     padding = attrs.get_int_tuple("padding")
     layout = attrs["layout"]
-    ceil_mode = attrs["ceil_mode"]
+    ceil_mode = attrs.get_bool("ceil_mode")
     assert layout == "NCHW", "only support nchw for now"
-    assert ceil_mode == "False", "not support ceil_mode now"
+    assert not ceil_mode, "not support ceil_mode now"
     return topi.nn.pool(inputs[0], pool_size, strides, padding, pool_type='max')
 
 @reg.register_schedule("max_pool2d")
@@ -165,9 +165,9 @@ def compute_avg_pool2d(attrs, inputs, _):
     strides = attrs.get_int_tuple("strides")
     padding = attrs.get_int_tuple("padding")
     layout = attrs["layout"]
-    ceil_mode = attrs["ceil_mode"]
+    ceil_mode = attrs.get_bool("ceil_mode")
     assert layout == "NCHW", "only support nchw for now"
-    assert ceil_mode == "False", "not support ceil_mode now"
+    assert not ceil_mode, "not support ceil_mode now"
     return topi.nn.pool(inputs[0], pool_size, strides, padding, pool_type='avg')
 
 @reg.register_schedule("avg_pool2d")
diff --git a/nnvm/src/compiler/graph_fuse.cc b/nnvm/src/compiler/graph_fuse.cc
index bbbe2d834d53..b97bf50b98a2 100644
--- a/nnvm/src/compiler/graph_fuse.cc
+++ b/nnvm/src/compiler/graph_fuse.cc
@@ -13,13 +13,29 @@
 #include <nnvm/compiler/packed_func_ext.h>
 #include <tvm/runtime/packed_func.h>
 #include <tvm/lowered_func.h>
+#include <dmlc/parameter.h>
 #include "./compile_engine.h"
 #include "../../tvm/src/runtime/graph/graph_runtime.h"
 
 namespace nnvm {
 namespace compiler {
 
-using tvm::runtime::TVMOpParam;
+
+struct TVMOpParam : public dmlc::Parameter<TVMOpParam> {
+  std::string func_name;
+  uint32_t num_inputs;
+  uint32_t num_outputs;
+  uint32_t 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(0);
+  }
+};
+
+DMLC_REGISTER_PARAMETER(TVMOpParam);
 
 // parser
 inline void TVMOpParamParser(nnvm::NodeAttrs* attrs) {
@@ -368,7 +384,7 @@ nnvm::Graph GraphFuseCompile(nnvm::Graph g) {
     nnvm::NodePtr np = nnvm::Node::Create();
     np->attrs.op = tvm_op;
     np->attrs.name = inode.source->attrs.name;
-    runtime::TVMOpParam param;
+    TVMOpParam param;
     param.func_name = fe.compiled_func->func_name;
     param.num_inputs = static_cast<uint32_t>(fe.imap.size());
     param.num_outputs = static_cast<uint32_t>(fe.subgraph.outputs.size());

From 02e1099bec4a726963da1642f4c2910ad01d4236 Mon Sep 17 00:00:00 2001
From: Yuwei HU <huyuwei1995@gmail.com>
Date: Tue, 26 Sep 2017 00:49:39 +0800
Subject: [PATCH 495/949] update nnvm.runtime to tvm.contrib.graph_runtime
 (#41)

---
 nnvm/docs/api/python/index.rst            |  1 -
 nnvm/docs/api/python/runtime.rst          |  8 --------
 nnvm/python/nnvm/compiler/build_module.py |  4 ++--
 nnvm/tutorials/mobilenet_inference_gpu.py | 22 +++++++++++-----------
 4 files changed, 13 insertions(+), 22 deletions(-)
 delete mode 100644 nnvm/docs/api/python/runtime.rst

diff --git a/nnvm/docs/api/python/index.rst b/nnvm/docs/api/python/index.rst
index 71c9f83fca6f..2c574ac60994 100644
--- a/nnvm/docs/api/python/index.rst
+++ b/nnvm/docs/api/python/index.rst
@@ -10,7 +10,6 @@ For user
 
    compiler
    frontend
-   runtime
    symbol
    graph
    top
diff --git a/nnvm/docs/api/python/runtime.rst b/nnvm/docs/api/python/runtime.rst
deleted file mode 100644
index a187424c68ce..000000000000
--- a/nnvm/docs/api/python/runtime.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-nnvm.runtime
-------------
-.. automodule:: nnvm.runtime
-
-.. autofunction:: nnvm.runtime.create
-
-.. autoclass:: nnvm.runtime.Module
-    :members:
diff --git a/nnvm/python/nnvm/compiler/build_module.py b/nnvm/python/nnvm/compiler/build_module.py
index 82a2da4f5caa..8c4b1a46f6bd 100644
--- a/nnvm/python/nnvm/compiler/build_module.py
+++ b/nnvm/python/nnvm/compiler/build_module.py
@@ -4,9 +4,9 @@
 
 import logging
 import tvm
+from tvm.contrib import graph_runtime
 from . import graph_attr, graph_util
 from .. import graph as _graph
-from .. import runtime
 
 OPT_PASS_LEVEL = {
     "SimplifyInference": 2,
@@ -220,7 +220,7 @@ def _run_graph(graph, params):
     _, oshape = graph_util.infer_shape(graph, **shape)
     _, odtype = graph_util.infer_dtype(graph, **dtype)
     graph, libmod, _ = build(graph, target, shape, dtype)
-    m = runtime.create(graph, libmod, ctx)
+    m = graph_runtime.create(graph, libmod, ctx)
     set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
     for k, v in params.items():
         set_input(k, tvm.nd.array(v))
diff --git a/nnvm/tutorials/mobilenet_inference_gpu.py b/nnvm/tutorials/mobilenet_inference_gpu.py
index 3f68c6ff255f..28e97244f88a 100644
--- a/nnvm/tutorials/mobilenet_inference_gpu.py
+++ b/nnvm/tutorials/mobilenet_inference_gpu.py
@@ -15,9 +15,9 @@
 ######################################################################
 # Register the NVCC Compiler Option
 # ---------------------------------
-# NNVM optimizes the graph and relies on TVM to generate fast
-# GPU code, to get the maximum performance, we need to enable
-# nvcc's compiler hook. This gives better performance than nvrtc mode.
+# NNVM optimizes the graph and relies on TVM to generate fast GPU code.
+# To get the maximum performance, we need to enable nvcc's compiler hook.
+# This gives better performance than nvrtc mode.
 
 @tvm.register_func
 def tvm_callback_cuda_compile(code):
@@ -28,7 +28,7 @@ def tvm_callback_cuda_compile(code):
 # Prepare the Benchmark
 # ---------------------
 # We construct a standard imagenet inference benchmark.
-# We use nnvm's testing utility to produce the model description and random parameters that so the example does not
+# We use nnvm's testing utility to produce the model description and random parameters so that the example does not
 # depend on a specific front-end framework.
 #
 # .. note::
@@ -46,17 +46,17 @@ def tvm_callback_cuda_compile(code):
     batch_size=1, image_shape=image_shape)
 
 ######################################################################
-# Compile The Graph
+# Compile the Graph
 # -----------------
 # NNVM needs two things to compile a deep learning model:
 #
-# - net which is the graph representation of the computation
-# - params a dictionary of str to parameters.
+# - net: the graph representation of the computation
+# - params: a dictionary of str to parameters
 #
 # To compile the graph, we call the build function with the graph
 # configuration and parameters.
-# When parameters are provided, NNVM will pre-compute certain part of the graph if possible,
-# the new parameter set returned as the third return value.
+# When parameters are provided, NNVM will pre-compute certain part of the graph if possible (e.g. simplify batch normalization to scale shift),
+# and return the updated parameters.
 
 graph, lib, params = nnvm.compiler.build(
     net, target, shape={"data": data_shape}, params=params)
@@ -65,7 +65,7 @@ def tvm_callback_cuda_compile(code):
 # Run the Compiled Module
 # -----------------------
 #
-# To deploy the module, we call :any:`tvm.contrib.graph_runtime.create` passing in the graph the lib and context.
+# To deploy the module, we call :any:`tvm.contrib.graph_runtime.create` passing in the graph, the lib, and context.
 # Thanks to TVM, we can deploy the compiled module to many platforms and languages.
 # The deployment module is designed to contain minimum dependencies.
 # This example runs on the same machine.
@@ -79,5 +79,5 @@ def tvm_callback_cuda_compile(code):
 module.run()
 # get output
 out = module.get_output(0, tvm.nd.empty(out_shape))
-# Convert to numpy
+# convert to numpy
 out.asnumpy()

From d34036fc499eead98ed18ac9043a44acdfe22aa1 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 25 Sep 2017 12:16:48 -0700
Subject: [PATCH 496/949] [DOCS] Add save_param_dict, readme (#42)

---
 nnvm/README.md                                |  46 ++++-
 nnvm/docs/api/python/compiler.rst             |   4 +
 nnvm/python/nnvm/compiler/__init__.py         |   4 +-
 nnvm/python/nnvm/compiler/build_module.py     |  32 ++-
 nnvm/python/nnvm/compiler/param_dict.py       |  66 +++++++
 nnvm/python/nnvm/graph.py                     |  32 ++-
 nnvm/src/compiler/graph_fuse.cc               |  38 +---
 nnvm/src/compiler/graph_runtime.cc            | 182 ++++++++++++++++++
 nnvm/src/compiler/graph_runtime.h             |  32 +++
 nnvm/tests/python/compiler/test_build.py      |   3 +-
 nnvm/tests/python/compiler/test_param_dict.py |  19 ++
 nnvm/tests/python/unittest/test_graph.py      |   3 +
 12 files changed, 412 insertions(+), 49 deletions(-)
 create mode 100644 nnvm/python/nnvm/compiler/param_dict.py
 create mode 100644 nnvm/src/compiler/graph_runtime.cc
 create mode 100644 nnvm/src/compiler/graph_runtime.h
 create mode 100644 nnvm/tests/python/compiler/test_param_dict.py

diff --git a/nnvm/README.md b/nnvm/README.md
index 6872e12120d0..6e4b4568920e 100644
--- a/nnvm/README.md
+++ b/nnvm/README.md
@@ -3,16 +3,54 @@
 [![Build Status](https://travis-ci.org/dmlc/nnvm.svg?branch=master)](https://travis-ci.org/dmlc/nnvm)
 [![GitHub license](http://dmlc.github.io/img/apache2.svg)](./LICENSE)
 
-NNVM is a reusable computational graph optimization and compilation stack for deep learning systems.
-NNVM provides modules to:
+NNVM is a reusable computational graph optimization and compilation stack for deep learning systems. It provides modules to:
 
 - Represent deep learning workloads from front-end frameworks via a graph IR.
 - Optimize computation graphs to improve performance.
 - Compile into executable modules and deploy to different hardware backends with minimum dependency.
 
-NNVM is designed to add new frontend, operators and graph optimizations in a decentralized fashion without changing the core interface. NNVM is part of [TVM stack](https://github.com/dmlc/tvm), which provides an end to end IR compilation stack for deploying deep learning workloads into different hardware backends
+NNVM is designed to add new frontend, operators and graph optimizations in a decentralized fashion without changing the core interface. NNVM is part of [TVM stack](https://github.com/dmlc/tvm). NNVM compiler toolchain can target hardware backends supported by TVM.
+The compiled module can be deployed to server, mobile, embedded devices and browsers with minimum dependency, in languages including c++, python, javascript, java, objective-c.
+
+The following code snippet demonstrates the general workflow of nnvm compiler toolchain.
+
+```python
+import tvm
+from tvm.contrib import graph_runtime, rpc
+import nnvm.frontend
+import nnvm.compiler
+
+# get model from frameworks
+# change xyz to supported framework name.
+graph, params = nnvm.frontend.from_xyz(...)
+
+# optimize and compile the graph to get a deployable module
+# target can be "opencl", "llvm", "metal" or any target supported by tvm
+target = "cuda"
+graph, lib, params = nnvm.compiler.build(
+   graph, target, shape={"data", data_shape}, params=params)
+
+# deploy and run on gpu(0)
+module = graph_runtime.create(graph, lib, tvm.gpu(0))
+module.set_input(**params)
+output = tvm.nd.empty(out_shape, ctx=tvm.gpu(0))
+for data_array in dataset:
+    module.set_input("data", data_array)
+    module.run()
+    module.get_output(0, output)
+
+# deploy to remote mobile/rasp/browser with minimum tvm rpc runtime
+# useful for quick experiments on mobile devices
+remote = rpc.connect(remote_host, remote_port)
+lib.export_library("mylib.so")
+remote.upload("mylib.so")
+rlib = rpc.load_module("mylib.so")
+# run on remote device
+rmodule = graph_runtime.create(graph, rlib, remote.gpu(0))
+rmodule.set_input(**params)
+rmodule.run()
+```
 
 ## Links
 - [TinyFlow](https://github.com/tqchen/tinyflow) on how you can use  NNVM to build a TensorFlow like API.
 - [Apache MXNet](http://mxnet.io/) uses NNVM as a backend.
-
diff --git a/nnvm/docs/api/python/compiler.rst b/nnvm/docs/api/python/compiler.rst
index 74f3bed07e52..4b995b28cd9e 100644
--- a/nnvm/docs/api/python/compiler.rst
+++ b/nnvm/docs/api/python/compiler.rst
@@ -7,6 +7,10 @@ nnvm.compiler
 
 .. autofunction:: nnvm.compiler.build_config
 
+.. autofunction:: nnvm.compiler.save_param_dict
+
+.. autofunction:: nnvm.compiler.load_param_dict
+
 .. autofunction:: nnvm.compiler.optimize
 
 .. automodule:: nnvm.compiler.graph_util
diff --git a/nnvm/python/nnvm/compiler/__init__.py b/nnvm/python/nnvm/compiler/__init__.py
index 3954c920a67a..1625150a6edc 100644
--- a/nnvm/python/nnvm/compiler/__init__.py
+++ b/nnvm/python/nnvm/compiler/__init__.py
@@ -1,6 +1,7 @@
 """NNVM compiler toolchain.
 
-User only need to use :any:`build` and :any:`build_config` to do the compilation.
+User only need to use :any:`build` and :any:`build_config` to do the compilation,
+and :any:`save_param_dict` to save the parameters into bytes.
 The other APIs are for more advanced interaction with the compiler toolchain.
 """
 from __future__ import absolute_import
@@ -10,6 +11,7 @@
 from . import build_module
 from . build_module import build, optimize, build_config
 from . compile_engine import engine, graph_key
+from . param_dict import save_param_dict, load_param_dict
 
 from .. import symbol as _symbol
 from .. import graph as _graph
diff --git a/nnvm/python/nnvm/compiler/build_module.py b/nnvm/python/nnvm/compiler/build_module.py
index 8c4b1a46f6bd..5f1fbcc0df56 100644
--- a/nnvm/python/nnvm/compiler/build_module.py
+++ b/nnvm/python/nnvm/compiler/build_module.py
@@ -9,7 +9,7 @@
 from .. import graph as _graph
 
 OPT_PASS_LEVEL = {
-    "SimplifyInference": 2,
+    "SimplifyInference": 0,
     "PrecomputePrune": 2,
     "OpFusion": 1
 }
@@ -26,6 +26,7 @@ class BuildConfig(object):
     current = None
     defaults = {
         "opt_level": 2,
+        "add_pass": None,
     }
     def __init__(self, **kwargs):
         self._old_scope = None
@@ -53,6 +54,23 @@ def __exit__(self, ptype, value, trace):
         assert self._old_scope
         BuildConfig.current = self._old_scope
 
+    def pass_enabled(self, pass_name):
+        """Get whether pass is enabled.
+
+        Parameters
+        ----------
+        pass_name : str
+            The optimization pass name
+
+        Returns
+        -------
+        enabled : bool
+            Whether pass is enabled.
+        """
+        if self.add_pass and pass_name in self.add_pass:
+            return True
+        return self.opt_level >= OPT_PASS_LEVEL[pass_name]
+
 
 BuildConfig.current = BuildConfig()
 
@@ -64,6 +82,9 @@ def build_config(**kwargs):
     opt_level: int, default=2
         Optimization level. See OPT_PASS_LEVEL for level of each pass.
 
+    add_pass: set of str
+        Optimization pass to be added regardless of optimization level.
+
     Returns
     -------
     config: BuildConfig
@@ -120,7 +141,7 @@ def optimize(graph, shape, dtype="float32"):
     """
     # pylint: disable=unused-argument
     cfg = BuildConfig.current
-    if cfg.opt_level >= OPT_PASS_LEVEL["SimplifyInference"]:
+    if cfg.pass_enabled("SimplifyInference"):
         graph = graph_attr.set_shape_inputs(graph, shape)
         graph = graph.apply(["InferShape", "SimplifyInference"])
     return graph
@@ -182,14 +203,17 @@ def build(graph, target, shape, dtype="float32", params=None):
     # Apply optimization
     graph = optimize(graph, shape, dtype)
     # Precompute prune
-    if params and cfg.opt_level >= OPT_PASS_LEVEL["PrecomputePrune"]:
+    if params and cfg.pass_enabled("PrecomputePrune"):
         graph, params = precompute_prune(graph, params)
         shape, dtype = _update_shape_dtype(shape, dtype, params)
     # Operator Fusion and generatiom
     graph = graph_attr.set_shape_inputs(graph, shape)
     graph = graph_attr.set_dtype_inputs(graph, dtype)
     graph._set_json_attr("target", target, "str")
-    graph._set_json_attr("opt_level", cfg.opt_level, "int")
+    if cfg.pass_enabled("OpFusion"):
+        graph._set_json_attr("opt_level", 1, "int")
+    else:
+        graph._set_json_attr("opt_level", 0, "int")
     graph = graph.apply("InferShape").apply("InferType")
     graph = graph.apply("GraphFusePartition").apply("GraphFuseCompile")
     libmod = graph_attr._move_out_module(graph, "module")
diff --git a/nnvm/python/nnvm/compiler/param_dict.py b/nnvm/python/nnvm/compiler/param_dict.py
new file mode 100644
index 000000000000..97c3158c694f
--- /dev/null
+++ b/nnvm/python/nnvm/compiler/param_dict.py
@@ -0,0 +1,66 @@
+"""Helper utility to save parameter dict"""
+import tvm
+
+_save_param_dict = tvm.get_global_func("nnvm.compiler._save_param_dict")
+_load_param_dict = tvm.get_global_func("nnvm.compiler._load_param_dict")
+
+def save_param_dict(params):
+    """Save parameter dictionary to binary bytes.
+
+    The result binary bytes can be loaded by the
+    GraphModule with API "load_params".
+
+    Parameters
+    ----------
+    params : dict of str to NDArray
+        The parameter dictionary.
+
+    Returns
+    -------
+    param_bytes: bytearray
+        Serialized parameters.
+
+    Examples
+    --------
+    .. code-block:: python
+
+       # compile and save the modules to file.
+       graph, lib, params = nnvm.compiler.build(
+          graph, target, shape={"data", data_shape}, params=params)
+       module = graph_runtime.create(graph, lib, tvm.gpu(0))
+       # save the parameters as byte array
+       param_bytes = nnvm.compiler.save_param_dict(params)
+       # We can serialize the param_bytes and load it back later.
+       # Pass in byte array to module to directly set parameters
+       module["load_params"](param_bytes)
+    """
+    args = []
+    for k, v in params.items():
+        args.append(k)
+        args.append(tvm.nd.array(v))
+    return _save_param_dict(*args)
+
+
+def load_param_dict(param_bytes):
+    """Load parameter dictionary to binary bytes.
+
+    Parameters
+    ----------
+    param_bytes: bytearray
+        Serialized parameters.
+
+    Returns
+    -------
+    params : dict of str to NDArray
+        The parameter dictionary.
+    """
+    if isinstance(param_bytes, (bytes, str)):
+        param_bytes = bytearray(param_bytes)
+    load_mod = _load_param_dict(param_bytes)
+    size = load_mod(0)
+    param_dict = {}
+    for i in range(size):
+        key = load_mod(1, i)
+        dltensor_handle = load_mod(2, i)
+        param_dict[key] = tvm.nd.NDArray(dltensor_handle, False)
+    return param_dict
diff --git a/nnvm/python/nnvm/graph.py b/nnvm/python/nnvm/graph.py
index bfe5251e2bd8..2ea365e67ef4 100644
--- a/nnvm/python/nnvm/graph.py
+++ b/nnvm/python/nnvm/graph.py
@@ -12,7 +12,7 @@
 from ._base import c_array, c_str, nn_uint, py_str, string_types
 from ._base import GraphHandle, SymbolHandle
 from ._base import check_call
-from .symbol import Symbol, Group as _Group
+from .symbol import Variable, Symbol, Group as _Group
 
 class GraphIndex(object):
     """Index for quickly accessing graph attributes.
@@ -174,9 +174,19 @@ def symbol(self):
         check_call(_LIB.NNGraphGetSymbol(self.handle, ctypes.byref(shandle)))
         return Symbol(shandle)
 
+    def json(self):
+        """Get JSON representation of the graph
+
+        Returns
+        -------
+        json : str
+            JSON representation of the graph
+        """
+        return self.apply("SaveJSON").json_attr("json")
+
     def _tvm_graph_json(self):
         """Get TVM graph json"""
-        return self.apply("SaveJSON").json_attr("json")
+        return self.json()
 
     @property
     def index(self):
@@ -225,6 +235,24 @@ def apply(self, passes):
         return Graph(ghandle)
 
 
+def load_json(json_str):
+    """Create a new graph by loading from json
+
+    Parameters
+    ----------
+    json_str : str
+        The json string
+
+    Returns
+    -------
+    graph : Graph
+        The loaded graph
+    """
+    ret = create(Variable("x"))
+    ret._set_json_attr("json", json_str)
+    return ret.apply("LoadJSON")
+
+
 def create(symbol):
     """Create a new graph from symbol.
 
diff --git a/nnvm/src/compiler/graph_fuse.cc b/nnvm/src/compiler/graph_fuse.cc
index b97bf50b98a2..b8c6a1adc02c 100644
--- a/nnvm/src/compiler/graph_fuse.cc
+++ b/nnvm/src/compiler/graph_fuse.cc
@@ -15,46 +15,10 @@
 #include <tvm/lowered_func.h>
 #include <dmlc/parameter.h>
 #include "./compile_engine.h"
-#include "../../tvm/src/runtime/graph/graph_runtime.h"
+#include "./graph_runtime.h"
 
 namespace nnvm {
 namespace compiler {
-
-
-struct TVMOpParam : public dmlc::Parameter<TVMOpParam> {
-  std::string func_name;
-  uint32_t num_inputs;
-  uint32_t num_outputs;
-  uint32_t 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(0);
-  }
-};
-
-DMLC_REGISTER_PARAMETER(TVMOpParam);
-
-// parser
-inline void TVMOpParamParser(nnvm::NodeAttrs* attrs) {
-  TVMOpParam param;
-  param.Init(attrs->dict);
-  attrs->parsed = std::move(param);
-}
-
-NNVM_REGISTER_OP(tvm_op)
-.set_attr_parser(TVMOpParamParser)
-.set_num_inputs([](const NodeAttrs& attrs) {
-    const TVMOpParam& param = nnvm::get<TVMOpParam>(attrs.parsed);
-    return param.num_inputs;
-  })
-.set_num_outputs([](const NodeAttrs& attrs) {
-    const TVMOpParam& param = nnvm::get<TVMOpParam>(attrs.parsed);
-    return param.num_outputs;
-  });
-
 using namespace tvm;
 
 // The single fuse rule.
diff --git a/nnvm/src/compiler/graph_runtime.cc b/nnvm/src/compiler/graph_runtime.cc
new file mode 100644
index 000000000000..902ccad25914
--- /dev/null
+++ b/nnvm/src/compiler/graph_runtime.cc
@@ -0,0 +1,182 @@
+/*!
+ * Copyright (c) 2017 by Contributors
+ * \file graph_runtime.cc
+ * \brief Interface code with TVM graph runtime.
+*/
+#include <dmlc/memory_io.h>
+#include <tvm/runtime/packed_func.h>
+#include <tvm/runtime/c_runtime_api.h>
+#include <tvm/runtime/registry.h>
+#include "./graph_runtime.h"
+
+namespace nnvm {
+namespace compiler {
+
+using tvm::runtime::TVMArgs;
+using tvm::runtime::TVMRetValue;
+using tvm::runtime::PackedFunc;
+using tvm::runtime::kTVMNDArrayMagic;
+using tvm::runtime::kTVMNDArrayListMagic;
+
+DMLC_REGISTER_PARAMETER(TVMOpParam);
+
+// parser
+inline void TVMOpParamParser(nnvm::NodeAttrs* attrs) {
+  TVMOpParam param;
+  param.Init(attrs->dict);
+  attrs->parsed = std::move(param);
+}
+
+NNVM_REGISTER_OP(tvm_op)
+.set_attr_parser(TVMOpParamParser)
+.set_num_inputs([](const NodeAttrs& attrs) {
+    const TVMOpParam& param = nnvm::get<TVMOpParam>(attrs.parsed);
+    return param.num_inputs;
+  })
+.set_num_outputs([](const NodeAttrs& attrs) {
+    const TVMOpParam& param = nnvm::get<TVMOpParam>(attrs.parsed);
+    return param.num_outputs;
+  });
+
+bool SaveDLTensor(dmlc::Stream* strm, DLTensor* tensor) {
+  uint64_t header = kTVMNDArrayMagic, reserved = 0;
+  strm->Write(&header, sizeof(header));
+  strm->Write(&reserved, sizeof(reserved));
+
+  strm->Write(&tensor->ctx, sizeof(tensor->ctx));
+  strm->Write(&tensor->ndim, sizeof(tensor->ndim));
+  strm->Write(&tensor->dtype, sizeof(tensor->dtype));
+
+  int ndim = tensor->ndim;
+  strm->Write(tensor->shape, sizeof(int64_t) * ndim);
+
+  int type_size = tensor->dtype.bits / 8;
+  int64_t size = 1;
+  for (int i = 0; i < ndim; ++i) {
+    size *= tensor->shape[i];
+  }
+  int64_t data_byte_size = type_size * size;
+  strm->Write(&data_byte_size, sizeof(data_byte_size));
+  strm->Write(tensor->data, data_byte_size);
+  return true;
+}
+
+DLTensor* LoadDLTensor(dmlc::Stream* strm) {
+  uint64_t header, reserved;
+  CHECK(strm->Read(&header, sizeof(header)))
+      << "Invalid DLTensor file format";
+  CHECK(strm->Read(&reserved, sizeof(reserved)))
+      << "Invalid DLTensor file format";
+  CHECK(header == kTVMNDArrayMagic)
+      << "Invalid DLTensor file format";
+
+  DLTensor tensor;
+  CHECK(strm->Read(&tensor.ctx, sizeof(tensor.ctx)))
+      << "Invalid DLTensor file format";
+  CHECK(strm->Read(&tensor.ndim, sizeof(tensor.ndim)))
+      << "Invalid DLTensor file format";
+  CHECK(strm->Read(&tensor.dtype, sizeof(tensor.dtype)))
+      << "Invalid DLTensor file format";
+  std::vector<int64_t> shape(tensor.ndim);
+  CHECK(strm->Read(&shape[0], sizeof(int64_t) * tensor.ndim))
+      << "Invalid DLTensor file format";
+  DLTensor* ret;
+  CHECK_EQ(TVMArrayAlloc(shape.data(),
+                         tensor.ndim,
+                         tensor.dtype.code,
+                         tensor.dtype.bits,
+                         tensor.dtype.lanes,
+                         static_cast<int>(tensor.ctx.device_type),
+                         tensor.ctx.device_id,
+                         &ret), 0) << TVMGetLastError();
+  int64_t size = 1;
+  int type_size = ret->dtype.bits / 8;
+  for (int i = 0; i < ret->ndim; ++i) {
+    size *= ret->shape[i];
+  }
+  int64_t data_byte_size;
+  CHECK(strm->Read(&data_byte_size, sizeof(data_byte_size)))
+      << "Invalid DLTensor file format";
+  CHECK(data_byte_size == type_size * size)
+      << "Invalid DLTensor file format";
+  CHECK(strm->Read(ret->data, type_size * size))
+      << "Invalid DLTensor file format";
+  return ret;
+}
+
+TVM_REGISTER_GLOBAL("nnvm.compiler._save_param_dict")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+    CHECK_EQ(args.size() % 2, 0u);
+    size_t num_params = args.size() / 2;
+    std::vector<std::string> names;
+    names.reserve(num_params);
+    std::vector<DLTensor*> arrays;
+    arrays.reserve(num_params);
+    for (size_t i = 0; i < num_params * 2; i += 2) {
+      names.emplace_back(args[i].operator std::string());
+      arrays.emplace_back(args[i + 1].operator DLTensor*());
+    }
+    std::string bytes;
+    dmlc::MemoryStringStream strm(&bytes);
+    dmlc::Stream* fo = &strm;
+    uint64_t header = kTVMNDArrayListMagic, reserved = 0;
+    fo->Write(&header, sizeof(header));
+    fo->Write(&reserved, sizeof(reserved));
+    fo->Write(names);
+    {
+      uint64_t sz = static_cast<uint64_t>(arrays.size());
+      fo->Write(&sz, sizeof(sz));
+      for (size_t i = 0; i < sz; ++i) {
+        SaveDLTensor(fo, arrays[i]);
+      }
+    }
+    TVMByteArray arr;
+    arr.data = bytes.c_str();
+    arr.size = bytes.length();
+    *rv = arr;
+  });
+
+
+TVM_REGISTER_GLOBAL("nnvm.compiler._load_param_dict")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+    std::string bytes = args[0];
+    std::vector<DLTensor*> data;
+    std::vector<std::string> names;
+    dmlc::MemoryStringStream memstrm(&bytes);
+    dmlc::Stream* strm = &memstrm;
+
+    uint64_t header, reserved;
+    CHECK(strm->Read(&header))
+        << "Invalid parameters file format";
+    CHECK(header == kTVMNDArrayListMagic)
+        << "Invalid parameters file format";
+    CHECK(strm->Read(&reserved))
+        << "Invalid parameters file format";
+
+    CHECK(strm->Read(&names))
+        << "Invalid parameters file format";
+    uint64_t sz;
+    strm->Read(&sz, sizeof(sz));
+    size_t size = static_cast<size_t>(sz);
+    CHECK(size == names.size())
+        << "Invalid parameters file format";
+    for (size_t i = 0; i < size; ++i) {
+      data.push_back(LoadDLTensor(strm));
+    }
+    auto packed = [data, names](TVMArgs args, TVMRetValue* rv) {
+      int code = args[0];
+      if (code == 0) {
+        *rv = static_cast<int64_t>(data.size());
+      } else if (code == 1) {
+        int index = args[1];
+        *rv = names[index];
+      } else {
+        CHECK_EQ(code, 2);
+        int index = args[1];
+        *rv = static_cast<void*>(data[index]);
+      }
+    };
+    *rv = PackedFunc(packed);
+  });
+}  // namespace compiler
+}  // namespace nnvm
diff --git a/nnvm/src/compiler/graph_runtime.h b/nnvm/src/compiler/graph_runtime.h
new file mode 100644
index 000000000000..085e5bbf062f
--- /dev/null
+++ b/nnvm/src/compiler/graph_runtime.h
@@ -0,0 +1,32 @@
+/*!
+ * Copyright (c) 2017 by Contributors
+ * \file graph_runtime.h
+ * \brief Interface code with TVM graph runtime.
+*/
+#ifndef NNVM_COMPILER_GRAPH_RUNTIME_H_
+#define NNVM_COMPILER_GRAPH_RUNTIME_H_
+
+#include <nnvm/graph.h>
+#include <vector>
+#include "../../tvm/src/runtime/graph/graph_runtime.h"
+
+namespace nnvm {
+namespace compiler {
+
+struct TVMOpParam : public dmlc::Parameter<TVMOpParam> {
+  std::string func_name;
+  uint32_t num_inputs;
+  uint32_t num_outputs;
+  uint32_t 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(0);
+  }
+};
+
+}  // namespace compiler
+}  // namespace nnvm
+#endif   // NNVM_COMPILER_GRAPH_RUNTIME_H_
diff --git a/nnvm/tests/python/compiler/test_build.py b/nnvm/tests/python/compiler/test_build.py
index 936631185032..f4e57954f53f 100644
--- a/nnvm/tests/python/compiler/test_build.py
+++ b/nnvm/tests/python/compiler/test_build.py
@@ -70,7 +70,8 @@ def test_precompute_prune():
     m = graph_runtime.create(graph, lib, tvm.cpu(0))
     params["y"] = ny
     res = tvm.nd.empty(shape)
-    m.run(**params)
+    m["load_params"](nnvm.compiler.save_param_dict(params))
+    m.run()
     out = m.get_output(0, out=res)
     np.testing.assert_allclose(
         res.asnumpy(), nx.asnumpy() + 1 + ny.asnumpy() + na.asnumpy())
diff --git a/nnvm/tests/python/compiler/test_param_dict.py b/nnvm/tests/python/compiler/test_param_dict.py
new file mode 100644
index 000000000000..667ae4e48cec
--- /dev/null
+++ b/nnvm/tests/python/compiler/test_param_dict.py
@@ -0,0 +1,19 @@
+import numpy as np
+import nnvm.compiler
+
+def test_save_load():
+    x = np.random.uniform(size=(10, 2)).astype("float32")
+    y = np.random.uniform(size=(1, 2, 3)).astype("float32")
+    x[:] = 1
+    y[:] = 1
+    params = {"x": x, "y": y}
+    param_bytes = nnvm.compiler.save_param_dict(params)
+    assert isinstance(param_bytes, bytearray)
+    param2 = nnvm.compiler.load_param_dict(param_bytes)
+    assert len(param2) == 2
+    np.testing.assert_equal(param2["x"].asnumpy(), x)
+    np.testing.assert_equal(param2["y"].asnumpy(), y)
+
+
+if __name__ == "__main__":
+    test_save_load()
diff --git a/nnvm/tests/python/unittest/test_graph.py b/nnvm/tests/python/unittest/test_graph.py
index f41d62538817..60c842f36a6d 100644
--- a/nnvm/tests/python/unittest/test_graph.py
+++ b/nnvm/tests/python/unittest/test_graph.py
@@ -10,6 +10,9 @@ def test_json_pass():
     ret._set_json_attr('json', ret.json_attr('json'))
     g2 = ret.apply('LoadJSON')
     assert g2.apply('SaveJSON').json_attr('json') == ret.json_attr('json')
+    json = g.json()
+    g2 = graph.load_json(json)
+    assert json == g2.json()
 
 
 def test_json_pass_with_attr():

From 81fd125a3e4782e239e4eed999f66756cd1c7330 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 25 Sep 2017 13:49:41 -0700
Subject: [PATCH 497/949] [TOP] split, reshape, concatenate (#43)

---
 nnvm/README.md                                | 19 +++----
 nnvm/python/nnvm/compiler/param_dict.py       |  1 +
 nnvm/python/nnvm/top/transform.py             | 49 ++++++++++++-------
 nnvm/src/compiler/graph_runtime.h             |  1 +
 nnvm/src/top/tensor/transform.cc              | 20 ++++----
 nnvm/tests/python/compiler/test_top_level1.py | 45 +++++++++++++++++
 nnvm/tests/python/compiler/test_top_level4.py | 22 +++++++++
 7 files changed, 118 insertions(+), 39 deletions(-)

diff --git a/nnvm/README.md b/nnvm/README.md
index 6e4b4568920e..66aa7530f709 100644
--- a/nnvm/README.md
+++ b/nnvm/README.md
@@ -3,7 +3,7 @@
 [![Build Status](https://travis-ci.org/dmlc/nnvm.svg?branch=master)](https://travis-ci.org/dmlc/nnvm)
 [![GitHub license](http://dmlc.github.io/img/apache2.svg)](./LICENSE)
 
-NNVM is a reusable computational graph optimization and compilation stack for deep learning systems. It provides modules to:
+NNVM is a reusable computational graph compilation stack for deep learning systems. It provides modules to:
 
 - Represent deep learning workloads from front-end frameworks via a graph IR.
 - Optimize computation graphs to improve performance.
@@ -20,26 +20,23 @@ from tvm.contrib import graph_runtime, rpc
 import nnvm.frontend
 import nnvm.compiler
 
-# get model from frameworks
+# GET model from frameworks
 # change xyz to supported framework name.
 graph, params = nnvm.frontend.from_xyz(...)
 
-# optimize and compile the graph to get a deployable module
+# OPTIMIZE and COMPILE the graph to get a deployable module
 # target can be "opencl", "llvm", "metal" or any target supported by tvm
 target = "cuda"
-graph, lib, params = nnvm.compiler.build(
-   graph, target, shape={"data", data_shape}, params=params)
+graph, lib, params = nnvm.compiler.build(graph, target, {"data", data_shape}, params=params)
 
-# deploy and run on gpu(0)
+# DEPLOY and run on gpu(0)
 module = graph_runtime.create(graph, lib, tvm.gpu(0))
 module.set_input(**params)
+module.run(data=data_array)
 output = tvm.nd.empty(out_shape, ctx=tvm.gpu(0))
-for data_array in dataset:
-    module.set_input("data", data_array)
-    module.run()
-    module.get_output(0, output)
+module.get_output(0, output)
 
-# deploy to remote mobile/rasp/browser with minimum tvm rpc runtime
+# DEPLOY to REMOTE mobile/rasp/browser with minimum tvm rpc runtime
 # useful for quick experiments on mobile devices
 remote = rpc.connect(remote_host, remote_port)
 lib.export_library("mylib.so")
diff --git a/nnvm/python/nnvm/compiler/param_dict.py b/nnvm/python/nnvm/compiler/param_dict.py
index 97c3158c694f..0821a015b968 100644
--- a/nnvm/python/nnvm/compiler/param_dict.py
+++ b/nnvm/python/nnvm/compiler/param_dict.py
@@ -1,3 +1,4 @@
+# pylint: disable=invalid-name
 """Helper utility to save parameter dict"""
 import tvm
 
diff --git a/nnvm/python/nnvm/top/transform.py b/nnvm/python/nnvm/top/transform.py
index 29cf6016a89e..a82511943ebf 100644
--- a/nnvm/python/nnvm/top/transform.py
+++ b/nnvm/python/nnvm/top/transform.py
@@ -2,13 +2,12 @@
 """Tensor transformation ops"""
 from __future__ import absolute_import
 
-import tvm
 import topi
 from .tensor import _fschedule_broadcast, _fschedule_injective
 from . import registry as reg
 from .registry import OpPattern
 
-# Need add reshape
+# expand_dims
 @reg.register_compute("expand_dims")
 def compute_expand_dims(attrs, inputs, out_info):
     """Compute definition of expand_dims"""
@@ -18,34 +17,46 @@ def compute_expand_dims(attrs, inputs, out_info):
 reg.register_pattern("expand_dims", OpPattern.BROADCAST)
 reg.register_schedule("expand_dims", _fschedule_broadcast)
 
-
+# transpose
 @reg.register_compute("transpose")
 def compute_transpose(attrs, inputs, out_info):
-    """Compute definition of expand_dims"""
+    """Compute definition of transpose"""
     axes = attrs.get_int_tuple("axes")
     axes = tuple(axes) if axes else None
     return topi.transpose(inputs[0], axes)
 reg.register_pattern("transpose", OpPattern.INJECTIVE)
 reg.register_schedule("transpose", _fschedule_injective)
 
-
-def _flatten_index(indices, shape):
-    """flatten the index to 1D"""
-    idx = 0
-    for i, value in enumerate(shape):
-        if i != 0:
-            idx *= value
-        idx = idx + indices[i]
-    return idx
-
 # reshape
 @reg.register_compute("reshape")
 def compute_reshape(attrs, inputs, out_info):
-    """Compute definition of softmax"""
-    # TODO(sxj) add support for general reshape
-    assert len(inputs[0].shape) == 1, "Only support 1d input for now"
+    """Compute definition of reshape"""
     oshape = out_info[0].shape
-    x = inputs[0]
-    return tvm.compute(oshape, lambda *i: x(_flatten_index(i, oshape)))
+    return topi.reshape(inputs[0], oshape)
 reg.register_pattern("reshape", OpPattern.INJECTIVE)
 reg.register_schedule("reshape", _fschedule_injective)
+
+# concatenate
+@reg.register_compute("concatenate")
+def compute_concatenate(attrs, inputs, out_info):
+    """Compute definition of concatenate"""
+    axis = attrs.get_int("axis")
+    return topi.concatenate([x for x in inputs], axis=axis)
+
+reg.register_pattern("concatenate", OpPattern.INJECTIVE)
+reg.register_schedule("concatenate", _fschedule_injective)
+
+# split
+@reg.register_compute("split")
+def compute_split(attrs, inputs, out_info):
+    """Compute definition of split"""
+    x = attrs["indices_or_sections"]
+    if x.startswith("(") or x.startswith("["):
+        indices_or_sections = attrs.get_int_tuple("indices_or_sections")
+    else:
+        indices_or_sections = attrs.get_int("indices_or_sections")
+    return topi.split(inputs[0], indices_or_sections, axis=attrs.get_int("axis"))
+
+
+reg.register_pattern("split", OpPattern.INJECTIVE)
+reg.register_schedule("split", _fschedule_injective)
diff --git a/nnvm/src/compiler/graph_runtime.h b/nnvm/src/compiler/graph_runtime.h
index 085e5bbf062f..cbea8f22b420 100644
--- a/nnvm/src/compiler/graph_runtime.h
+++ b/nnvm/src/compiler/graph_runtime.h
@@ -8,6 +8,7 @@
 
 #include <nnvm/graph.h>
 #include <vector>
+#include <string>
 #include "../../tvm/src/runtime/graph/graph_runtime.h"
 
 namespace nnvm {
diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
index da93fe4cf26f..0fe772e28e47 100644
--- a/nnvm/src/top/tensor/transform.cc
+++ b/nnvm/src/top/tensor/transform.cc
@@ -225,15 +225,17 @@ inline bool SplitInferShape(const NodeAttrs& attrs,
     CHECK_EQ(out_shape->size(), static_cast<size_t>(num_outputs));
     CHECK_LT(param.axis, dshape.ndim());
     TShape oshape = dshape;
-    dim_t total = 0;
-    for (size_t i = 1; i < num_outputs; ++i) {
-      oshape[param.axis] = param.indices_or_sections[i - 1];
-      total += oshape[param.axis];
-      NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, i - 1, oshape);
+    dim_t begin = 0;
+    for (size_t i = 0; i < num_outputs - 1; ++i) {
+      CHECK_GT(param.indices_or_sections[i], begin)
+          << "indices_or_sections need to be a sorted ascending list";
+      oshape[param.axis] = param.indices_or_sections[i] - begin;
+      begin = param.indices_or_sections[i];
+      NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, i, oshape);
     }
-    CHECK_LT(total, dshape[param.axis])
+    CHECK_LT(begin, dshape[param.axis])
         << "The sum of sections must match the input.shape[axis]";
-    oshape[param.axis] = dshape[param.axis] - total;
+    oshape[param.axis] = dshape[param.axis] - begin;
     NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, num_outputs - 1, oshape);
   }
   return true;
@@ -256,11 +258,11 @@ NNVM_REGISTER_OP(split)
 along which to split the array.
 
 )code" NNVM_ADD_FILELINE)
-.add_argument("data", "Tensor", "List of arrays to concatenate")
+.add_argument("data", "Tensor", "Array to be splitted")
 .add_arguments(SplitParam::__FIELDS__())
 .set_attr_parser(SplitParamParser)
 .set_attr<FInferShape>("FInferShape", SplitInferShape)
-.set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, -1>)
 .set_num_inputs(1)
 .set_num_outputs(SplitNumOutputs)
 .set_support_level(1);
diff --git a/nnvm/tests/python/compiler/test_top_level1.py b/nnvm/tests/python/compiler/test_top_level1.py
index 83f1c25d25df..068ea14c28b7 100644
--- a/nnvm/tests/python/compiler/test_top_level1.py
+++ b/nnvm/tests/python/compiler/test_top_level1.py
@@ -177,7 +177,52 @@ def test_batchnorm():
             res.asnumpy(), res_np, atol=1e-5, rtol=1e-5)
 
 
+def verify_concatenate(ishape, axis):
+    x = [sym.Variable("x%d" % i) for i in range(len(ishape))]
+    y = sym.concatenate(*x, axis=axis) + 1
+    dtype = "float32"
+    for target, ctx in ctx_list():
+        # set input
+        data = []
+        for i, shape in enumerate(ishape):
+            data.append(np.random.uniform(size=shape).astype(dtype))
+        pdict = {"x%d" % i :  v for i, v in enumerate(data)}
+        shape = {"x%d" % i :  v.shape for i, v in enumerate(data)}
+        graph, lib, _ = nnvm.compiler.build(y, target, shape)
+        m = graph_runtime.create(graph, lib, ctx)
+        m.run(**pdict)
+        out_np = np.concatenate(data, axis=axis) + 1
+        out = m.get_output(0, tvm.nd.empty(out_np.shape))
+        np.testing.assert_allclose(out.asnumpy(), out_np, atol=1e-5, rtol=1e-5)
+
+def test_concatenate():
+    verify_concatenate([(2, 3, 4), (1, 3, 4)], axis=0)
+    verify_concatenate([(2, 4), (2, 7)], axis=1)
+
+
+def verify_split(ishape, indices_or_sections, axis):
+    x = sym.Variable("x")
+    y = sym.split(x, indices_or_sections=indices_or_sections, axis=axis)
+    dtype = "float32"
+    x_np = np.random.uniform(size=ishape).astype(dtype)
+    res = np.split(x_np, indices_or_sections, axis=axis)
+    for target, ctx in ctx_list():
+        # set input
+        graph, lib, _ = nnvm.compiler.build(y, target, {"x": ishape})
+        m = graph_runtime.create(graph, lib, ctx)
+        m.run(x=x_np)
+        for i, arr  in enumerate(res):
+            out = m.get_output(i, tvm.nd.empty(arr.shape))
+            np.testing.assert_allclose(out.asnumpy(), arr, atol=1e-5, rtol=1e-5)
+
+def test_split():
+    verify_split((2, 3), 2, axis=0)
+    verify_split((5, 3), [3], axis=0)
+    verify_split((5, 9, 3), [3, 4], axis=1)
+
 if __name__ == "__main__":
+    test_split()
+    test_concatenate()
     test_log_softmax()
     test_batchnorm()
     test_dense()
diff --git a/nnvm/tests/python/compiler/test_top_level4.py b/nnvm/tests/python/compiler/test_top_level4.py
index bc2c5465d461..1b005e27d9ef 100644
--- a/nnvm/tests/python/compiler/test_top_level4.py
+++ b/nnvm/tests/python/compiler/test_top_level4.py
@@ -51,7 +51,29 @@ def test_reduce():
     verify_reduce((4, 4, 3), np.sum, sym.sum, axis=(0, 2))
 
 
+def verify_reshape(dshape, oshape):
+    x = sym.Variable("x")
+    y = sym.reshape(x, shape=oshape)
+    y = y + 1
+    dtype = "float32"
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
+        m = graph_runtime.create(graph, lib, ctx)
+        # set input
+        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+        m.run(x=data)
+        out_np = data.asnumpy().reshape(oshape) + 1
+        out = m.get_output(0, tvm.nd.empty(out_np.shape))
+        np.testing.assert_allclose(out.asnumpy(), out_np, atol=1e-5, rtol=1e-5)
+
+def test_reshape():
+    verify_reshape((2, 3, 4), (-1, 2, 1))
+    verify_reshape((2, 3, 4), (8, 3))
+    verify_reshape((4, 7), (2, 7, 2))
+
+
 if __name__ == "__main__":
+    test_reshape()
     test_reduce()
     test_tranpose()
     print(nnvm.compiler.engine.dump())

From 0d2b882d4045c3ca2fb790a0acc4b8f9ac9568f1 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 25 Sep 2017 19:58:02 -0700
Subject: [PATCH 498/949] [DOCS] Add intro tutorial (#45)

---
 nnvm/README.md                            |   4 +-
 nnvm/docs/conf.py                         |   1 +
 nnvm/python/nnvm/compiler/graph_attr.py   |   2 +-
 nnvm/tutorials/get_started.py             | 174 ++++++++++++++++++++++
 nnvm/tutorials/mobilenet_inference_gpu.py |  18 +--
 5 files changed, 187 insertions(+), 12 deletions(-)
 create mode 100644 nnvm/tutorials/get_started.py

diff --git a/nnvm/README.md b/nnvm/README.md
index 66aa7530f709..d9bd4aa0d2a4 100644
--- a/nnvm/README.md
+++ b/nnvm/README.md
@@ -9,10 +9,10 @@ NNVM is a reusable computational graph compilation stack for deep learning syste
 - Optimize computation graphs to improve performance.
 - Compile into executable modules and deploy to different hardware backends with minimum dependency.
 
-NNVM is designed to add new frontend, operators and graph optimizations in a decentralized fashion without changing the core interface. NNVM is part of [TVM stack](https://github.com/dmlc/tvm). NNVM compiler toolchain can target hardware backends supported by TVM.
+NNVM is designed to add new frontend, operators and graph optimizations in a decentralized fashion without changing the core interface. It is part of [TVM stack](https://github.com/dmlc/tvm). The compiler toolchain can target hardware backends supported by TVM.
 The compiled module can be deployed to server, mobile, embedded devices and browsers with minimum dependency, in languages including c++, python, javascript, java, objective-c.
 
-The following code snippet demonstrates the general workflow of nnvm compiler toolchain.
+The following code snippet demonstrates the general workflow of nnvm.
 
 ```python
 import tvm
diff --git a/nnvm/docs/conf.py b/nnvm/docs/conf.py
index 5175167185cd..64e40eb748b5 100644
--- a/nnvm/docs/conf.py
+++ b/nnvm/docs/conf.py
@@ -16,6 +16,7 @@
 import shlex
 import recommonmark
 import sphinx_gallery
+from tvm.contrib import rpc, graph_runtime
 from recommonmark.parser import CommonMarkParser
 from recommonmark.transform import AutoStructify
 
diff --git a/nnvm/python/nnvm/compiler/graph_attr.py b/nnvm/python/nnvm/compiler/graph_attr.py
index 3787eca68707..43be85e644bb 100644
--- a/nnvm/python/nnvm/compiler/graph_attr.py
+++ b/nnvm/python/nnvm/compiler/graph_attr.py
@@ -54,7 +54,7 @@ def set_dtype_inputs(g, dtype):
     """
     if isinstance(dtype, dict):
         list_dtype = [
-            DTYPE_TO_TCODE[dtype.get(name, "default")]
+            DTYPE_TO_TCODE[str(dtype.get(name, "default"))]
             for name in g.index.input_names]
     else:
         list_dtype = [DTYPE_TO_TCODE[dtype]] * len(g.index.input_names)
diff --git a/nnvm/tutorials/get_started.py b/nnvm/tutorials/get_started.py
new file mode 100644
index 000000000000..f505853375df
--- /dev/null
+++ b/nnvm/tutorials/get_started.py
@@ -0,0 +1,174 @@
+"""
+Get Started with NNVM
+=====================
+**Author**: `Tianqi Chen <https://tqchen.github.io/>`_
+
+This article is an introductory tutorial to workflow in NNVM.
+"""
+import nnvm.compiler
+import nnvm.symbol as sym
+
+######################################################################
+# Declare Computation
+# -------------------
+# We start by describing our need using computational graph.
+# Most deep learning frameworks use computation graph to describe
+# their computation. In this example, we directly use
+# NNVM's API to construct the computational graph.
+#
+# .. note::
+#
+#   In a typical deep learning compilation workflow,
+#   we can get the models from :any:`nnvm.frontend`
+#
+# The following code snippet describes :math:`z = x + \sqrt{y}`
+# and creates a nnvm graph from the description.
+# We can print out the graph ir to check the graph content.
+
+x = sym.Variable("x")
+y = sym.Variable("y")
+z = sym.elemwise_add(x, sym.sqrt(y))
+compute_graph = nnvm.graph.create(z)
+print("-------compute graph-------")
+print(compute_graph.ir())
+
+######################################################################
+# Compile
+# -------
+# We can call :any:`nnvm.compiler.build` to compile the graph.
+# The build function takes a shape parameter which specifies the
+# input shape requirement. Here we only need to pass in shape of ``x``
+# and the other one will be inferred automatically by NNVM.
+#
+# The function returns three values. ``deploy_graph`` contains
+# the final compiled graph structure. ``lib`` is a :any:`tvm.module.Module`
+# that contains compiled CUDA functions. We do not need the ``params``
+# in this case.
+shape = (4,)
+deploy_graph, lib, params = nnvm.compiler.build(
+    compute_graph, target="cuda", shape={"x": shape}, dtype="float32")
+
+######################################################################
+# We can print out the IR of ``deploy_graph`` to understand what just
+# happened under the hood. We can find that ``deploy_graph`` only
+# contains a single operator ``tvm_op``. This is because NNVM
+# automatically fused the operator together into one operator.
+#
+print("-------deploy graph-------")
+print(deploy_graph.ir())
+
+######################################################################
+# Let us also peek into content of ``lib``.
+# Typically a compiled TVM CUDA module contains a host module(lib)
+# and a device module(``lib.imported_modules[0]``) that contains the CUDA code.
+# We print out the the generated device code here.
+# This is exactly a fused CUDA version of kernel that the graph points to.
+#
+print("-------deploy library-------")
+print(lib.imported_modules[0].get_source())
+
+######################################################################
+# Deploy and Run
+# --------------
+# Now that we have have compiled module, let us run it.
+# We can use :any:`graph_runtime <tvm.contrib.graph_runtime.create>`
+# in tvm to create a deployable :any:`GraphModule <tvm.contrib.graph_runtime.GraphModule>`.
+# We can use the :any:`set_input <tvm.contrib.graph_runtime.GraphModule.set_input>`,
+# :any:`run <tvm.contrib.graph_runtime.GraphModule.run>` and
+# :any:`get_output <tvm.contrib.graph_runtime.GraphModule.get_output>` function
+# to set the input, execute the graph and get the output we need.
+#
+import tvm
+import numpy as np
+from tvm.contrib import graph_runtime, util
+
+module = graph_runtime.create(deploy_graph, lib, tvm.gpu(0))
+x_np = np.array([1, 2, 3, 4]).astype("float32")
+y_np = np.array([4, 4, 4, 4]).astype("float32")
+# set input to the graph module
+module.set_input(x=x_np, y=y_np)
+# run forward computation
+module.run()
+# get the first output
+out = module.get_output(0, out=tvm.nd.empty(shape))
+print(out.asnumpy())
+
+######################################################################
+# Provide Model Parameters
+# ------------------------
+# Most deep learning models contains two types of inputs: parameters
+# that remains fixed during inference and data input that need to
+# change for each inference task. It is helpful to provide these
+# information to NNVM. Let us assume that ``y`` is the parameter
+# in our example. We can provide the model parameter information
+# by the params argument to :any:`nnvm.compiler.build`.
+#
+deploy_graph, lib, params = nnvm.compiler.build(
+    compute_graph, target="cuda", shape={"x": shape}, params={"y": y_np})
+
+######################################################################
+# This time we will need params value returned by :any:`nnvm.compiler.build`.
+# NNVM applys  optimization  to pre-compute the intermediate values in
+# the graph that can be determined by parameters. In this case
+# :math:`\sqrt{y}` can be pre-computed. The pre-computed values
+# are returned as new params. We can print out the new compiled library
+# to confirm that the fused kernel only now contains add.
+#
+print("-----optimized params-----")
+print(params)
+print("-------deploy library-------")
+print(lib.imported_modules[0].get_source())
+
+######################################################################
+# Save the Deployed Module
+# ------------------------
+# We can save the ``deploy_graph``, ``lib`` and ``params`` separately
+# and load them back later. We can use :any:`tvm.module.Module` to export
+# the compiled library. ``deploy_graph`` is saved in json format and ``params``
+# is serialized into a bytearray.
+#
+temp = util.tempdir()
+path_lib = temp.relpath("deploy.so")
+lib.export_library(path_lib)
+with open(temp.relpath("deploy.json"), "w") as fo:
+    fo.write(deploy_graph.json())
+with open(temp.relpath("deploy.params"), "wb") as fo:
+    fo.write(nnvm.compiler.save_param_dict(params))
+print(temp.listdir())
+
+######################################################################
+# We can load the module back.
+loaded_lib = tvm.module.load(path_lib)
+loaded_json = open(temp.relpath("deploy.json")).read()
+loaded_params = bytearray(open(temp.relpath("deploy.params"), "rb").read())
+module = graph_runtime.create(loaded_json, loaded_lib, tvm.gpu(0))
+params = nnvm.compiler.load_param_dict(loaded_params)
+# directly load from byte array
+module.load_params(loaded_params)
+module.run(x=x_np)
+# get the first output
+out = module.get_output(0, out=tvm.nd.empty(shape))
+print(out.asnumpy())
+
+######################################################################
+# Deploy using Another Language
+# -----------------------------
+# We use python in this example for demonstration.
+# We can also deploy the compiled modules with other languages
+# supported by TVM such as  c++, java, javascript.
+# The graph module itself is fully embedded in TVM runtime.
+#
+# The following block demonstrates how we can directly use TVM's
+# runtime API to execute the compiled module.
+# You can find similar runtime API in TVMRuntime of other languages.
+#
+fcreate = tvm.get_global_func("tvm.graph_runtime.create")
+ctx = tvm.gpu(0)
+gmodule = fcreate(loaded_json, loaded_lib, ctx.device_type, ctx.device_id)
+set_input, get_output, run = gmodule["set_input"], gmodule["get_output"], gmodule["run"]
+set_input("x", tvm.nd.array(x_np))
+gmodule["load_params"](loaded_params)
+run()
+out = tvm.nd.empty(shape)
+get_output(0, out)
+print(out.asnumpy())
diff --git a/nnvm/tutorials/mobilenet_inference_gpu.py b/nnvm/tutorials/mobilenet_inference_gpu.py
index 28e97244f88a..e477e758cf91 100644
--- a/nnvm/tutorials/mobilenet_inference_gpu.py
+++ b/nnvm/tutorials/mobilenet_inference_gpu.py
@@ -17,19 +17,24 @@
 # ---------------------------------
 # NNVM optimizes the graph and relies on TVM to generate fast GPU code.
 # To get the maximum performance, we need to enable nvcc's compiler hook.
-# This gives better performance than nvrtc mode.
+# This usually gives better performance than nvrtc mode.
 
 @tvm.register_func
 def tvm_callback_cuda_compile(code):
-    ptx = nvcc.compile_cuda(code, target="ptx", options=["-arch=sm_52"])
+    ptx = nvcc.compile_cuda(code, target="ptx")
     return ptx
 
 ######################################################################
 # Prepare the Benchmark
 # ---------------------
 # We construct a standard imagenet inference benchmark.
-# We use nnvm's testing utility to produce the model description and random parameters so that the example does not
-# depend on a specific front-end framework.
+# NNVM needs two things to compile a deep learning model:
+#
+# - net: the graph representation of the computation
+# - params: a dictionary of str to parameters
+#
+# We use nnvm's testing utility to produce the model description and random parameters
+# so that the example does not depend on a specific front-end framework.
 #
 # .. note::
 #
@@ -48,11 +53,6 @@ def tvm_callback_cuda_compile(code):
 ######################################################################
 # Compile the Graph
 # -----------------
-# NNVM needs two things to compile a deep learning model:
-#
-# - net: the graph representation of the computation
-# - params: a dictionary of str to parameters
-#
 # To compile the graph, we call the build function with the graph
 # configuration and parameters.
 # When parameters are provided, NNVM will pre-compute certain part of the graph if possible (e.g. simplify batch normalization to scale shift),

From 9416240d05a11b05cc1f047ab78dcaf60f75bef7 Mon Sep 17 00:00:00 2001
From: "Joshua Z. Zhang" <cheungchih@gmail.com>
Date: Mon, 25 Sep 2017 22:24:26 -0700
Subject: [PATCH 499/949] [Frontend] Onnx (#40)

* init onnx

finish onnx frontend

add onnx tests

fix various

backup

use transformer

[Frontend] graph passed

add test forward

test forward

fix doc and lint

fix test graph tuple

from_onnx now take 2 args, output (sym, params)

fix rename

fix input names

fix multiple

fix lint

fix lint check

* better doc
---
 nnvm/python/nnvm/frontend/__init__.py         |   1 +
 nnvm/python/nnvm/frontend/common.py           | 130 ++++++
 nnvm/python/nnvm/frontend/mxnet.py            |   8 +-
 nnvm/python/nnvm/frontend/onnx.py             | 377 ++++++++++++++++++
 nnvm/python/nnvm/testing/__init__.py          |   4 +
 nnvm/python/nnvm/testing/mlp.py               |  60 +++
 nnvm/python/nnvm/testing/mobilenet.py         |  24 +-
 nnvm/python/nnvm/testing/resnet.py            | 232 +++++++++++
 nnvm/python/nnvm/testing/utils.py             |  52 +++
 nnvm/python/nnvm/testing/vgg.py               | 114 ++++++
 .../frontend/mxnet/model_zoo/__init__.py      |  12 +-
 .../python/frontend/mxnet/model_zoo/mlp.py    |  12 -
 .../python/frontend/mxnet/model_zoo/resnet.py | 129 +-----
 .../python/frontend/mxnet/model_zoo/vgg.py    |  52 ---
 .../tests/python/frontend/mxnet/test_graph.py |   6 +-
 .../frontend/onnx/model_zoo/__init__.py       |  13 +
 .../onnx/model_zoo/super_resolution.onnx      | Bin 0 -> 239554 bytes
 .../onnx/model_zoo/super_resolution.py        |  36 ++
 .../python/frontend/onnx/test_forward.py      |  42 ++
 nnvm/tests/python/frontend/onnx/test_graph.py |  24 ++
 20 files changed, 1105 insertions(+), 223 deletions(-)
 create mode 100644 nnvm/python/nnvm/frontend/common.py
 create mode 100644 nnvm/python/nnvm/frontend/onnx.py
 create mode 100644 nnvm/python/nnvm/testing/mlp.py
 create mode 100644 nnvm/python/nnvm/testing/resnet.py
 create mode 100644 nnvm/python/nnvm/testing/utils.py
 create mode 100644 nnvm/python/nnvm/testing/vgg.py
 create mode 100644 nnvm/tests/python/frontend/onnx/model_zoo/__init__.py
 create mode 100644 nnvm/tests/python/frontend/onnx/model_zoo/super_resolution.onnx
 create mode 100644 nnvm/tests/python/frontend/onnx/model_zoo/super_resolution.py
 create mode 100644 nnvm/tests/python/frontend/onnx/test_forward.py
 create mode 100644 nnvm/tests/python/frontend/onnx/test_graph.py

diff --git a/nnvm/python/nnvm/frontend/__init__.py b/nnvm/python/nnvm/frontend/__init__.py
index 4db991e29a56..08fa9f46ca93 100644
--- a/nnvm/python/nnvm/frontend/__init__.py
+++ b/nnvm/python/nnvm/frontend/__init__.py
@@ -1,3 +1,4 @@
 """NNVM frontends."""
 from __future__ import absolute_import
 from .mxnet import from_mxnet
+from .onnx import from_onnx
diff --git a/nnvm/python/nnvm/frontend/common.py b/nnvm/python/nnvm/frontend/common.py
new file mode 100644
index 000000000000..663de6a4059d
--- /dev/null
+++ b/nnvm/python/nnvm/frontend/common.py
@@ -0,0 +1,130 @@
+"""Shared functions and classes for frontends."""
+from __future__ import absolute_import as _abs
+import warnings
+from .._base import string_types
+
+class Renamer(object):
+    """A simply renamer for operators.
+
+    Parameters
+    ----------
+    new_name : str
+        The new name for the operator
+    """
+    def __init__(self, new_name):
+        self._new_name = new_name
+
+    def __call__(self, attrs):
+        return self._new_name, attrs
+
+
+class AttrConverter(object):
+    """Common attribute conveter. An AttrConverter instance is a callable:
+    ```
+    attr_converter = AttrConverter(op_name, transforms={'a':'b', 'c':('d', 1)})
+    new_op_name, new_attr = attr_converter(attrs)
+    ```
+
+    Parameters
+    ----------
+    op_name : str or callable
+        If set as str, returned operator name is the str.
+        If set as callable, returned operator is the str returned by calling:
+        `op_name = func(attr)`
+    transforms : dict of `new_name, or (new_name, default_value, transform function)`
+        If only a new_name is provided, it's like renaming the attribute name.
+        If default_value if provded, then the attribute is considered as optional.
+        If transform function is provided, the original attribute value is handled
+        by transform function.
+    excludes : list
+        A list of excluded attributes that should `NOT` appear.
+        Raise NotImplementedError if occured.
+    disables : list
+        A list of attributes that is disabled in nnvm. Raise warnings.
+    ignores : list
+        A list of attributes that is ignored in nnvm. Silent.
+    extras : dict
+        A series of additional attributes should be added anyway to the returned
+        attribute dict.
+    custom_check : callable
+        A custom function takes attribute, and return True/False.
+        Raise RuntimeError if not bool(True) returned.
+    """
+    def __init__(self, op_name, transforms=None,
+                 excludes=None, disables=None, ignores=None,
+                 extras=None, custom_check=None):
+        self._op_name = op_name
+        self._transforms = transforms if transforms else {}
+        self._excludes = excludes if excludes else []
+        self._disables = disables if disables else []
+        self._ignores = ignores if ignores else []
+        self._extras = extras if extras else {}
+        self._custom_check = custom_check
+
+    def __call__(self, attrs):
+        # apply custom check
+        if self._custom_check:
+            func, msg = self._custom_check
+            if not func(attrs):
+                raise RuntimeError("Check failed: {}".format(msg))
+        # get new op_name
+        if isinstance(self._op_name, string_types):
+            op_name = self._op_name
+        else:
+            assert callable(self._op_name), "op_name can either be string or callable"
+            op_name = self._op_name(attrs)
+        # convert attributes
+        new_attrs = {}
+        for k in attrs.keys():
+            if k in self._excludes:
+                raise NotImplementedError("Attribute {} not supported yet.".format(k))
+            elif k in self._disables:
+                warnings.warn("Attribute {} is disabled in nnvm.sym.{}".format(k, op_name))
+            elif k in self._ignores:
+                pass
+            elif k in self._transforms:
+                new_name, defaults, transform = self._parse_default(self._transforms[k])
+                if defaults is None:
+                    new_attr = self._required_attr(attrs, k)
+                else:
+                    new_attr = attrs.get(k, None)
+                if new_attr is None:
+                    new_attrs[new_name] = defaults
+                else:
+                    new_attrs[new_name] = transform(new_attr)
+            else:
+                # copy
+                new_attrs[k] = attrs[k]
+        # add extras
+        new_attrs.update(self._extras)
+        return op_name, new_attrs
+
+    def _parse_default(self, target):
+        """Helper function to parse default values."""
+        if not isinstance(target, (list, tuple)):
+            k, v, t = target, None, lambda x: x
+        elif len(target) == 1:
+            k, v, t = target[0], None, lambda x: x
+        elif len(target) == 2:
+            k, v, t = target[0], target[1], lambda x: x
+        elif len(target) > 2:
+            k, v, t = target[0], target[1], target[2]
+        else:
+            k = None  # should raise
+        if not isinstance(k, string_types):
+            msg = "{} is not a valid target, (name, default) expected.".format(target)
+            raise ValueError(msg)
+        return k, v, t
+
+    def _parse_bool(self, value):
+        """Helper function to parse default boolean values."""
+        if isinstance(value, string_types):
+            return value.strip().lower() in ['true', '1', 't', 'y', 'yes']
+        return bool(value)
+
+    def _required_attr(self, attr, key):
+        """Wrapper for getting required attributes."""
+        assert isinstance(attr, dict)
+        if key not in attr:
+            raise AttributeError("Required attribute {} not found.".format(key))
+        return attr[key]
diff --git a/nnvm/python/nnvm/frontend/mxnet.py b/nnvm/python/nnvm/frontend/mxnet.py
index af64d6b3f316..16419e737568 100644
--- a/nnvm/python/nnvm/frontend/mxnet.py
+++ b/nnvm/python/nnvm/frontend/mxnet.py
@@ -58,12 +58,12 @@ def _pooling(attrs):
 def _batch_norm(attrs):
     if _parse_bool_str(attrs, 'output_mean_var'):
         _raise_not_supported('output_mean_var', 'batch_norm')
-    if _parse_bool_str(attrs, 'fix_gamma'):
-        _warn_not_used('fix_gamma', 'batch_norm')
+    # if _parse_bool_str(attrs, 'fix_gamma'):
+    #     _warn_not_used('fix_gamma', 'batch_norm')
     if _parse_bool_str(attrs, 'use_global_stats'):
         _warn_not_used('use_global_stats', 'batch_norm')
-    if _parse_bool_str(attrs, 'momentum'):
-        _warn_not_used('momentum', 'batch_norm')
+    # if _parse_bool_str(attrs, 'momentum'):
+    #     _warn_not_used('momentum', 'batch_norm')
     op_name, new_attrs = 'batch_norm', {}
     new_attrs['axis'] = attrs.get('axis', 1)
     new_attrs['epsilon'] = attrs.get('eps', 0.001)
diff --git a/nnvm/python/nnvm/frontend/onnx.py b/nnvm/python/nnvm/frontend/onnx.py
new file mode 100644
index 000000000000..a433485520d5
--- /dev/null
+++ b/nnvm/python/nnvm/frontend/onnx.py
@@ -0,0 +1,377 @@
+# pylint: disable=import-self, invalid-name
+"""ONNX: Open Neural Network Exchange frontend."""
+from __future__ import absolute_import as _abs
+import tvm
+from .. import symbol as _sym
+from .common import Renamer, AttrConverter as AttrCvt
+
+__all__ = ['from_onnx']
+
+def _revert_caffe2_pad(attr):
+    """Caffe2 require two times the normal padding."""
+    if len(attr) == 4:
+        attr = attr[:2]
+    elif len(attr) == 2:
+        pass
+    else:
+        raise ValueError("Invalid caffe2 type padding: {}".format(attr))
+    return attr
+
+def _math_name_picker(surfix):
+    def _impl(attr):
+        if attr.get('broadcast', 0):
+            return 'broadcast_' + surfix
+        return 'elemwise_' + surfix
+    return _impl
+
+def _broadcast_constraint():
+    def _broadcast_check(attrs):
+        if attrs.get('axis', None):
+            return False
+        return True
+    return _broadcast_check, "Specifying broadcast axis not allowed."
+
+def _dimension_picker(prefix, surfix=''):
+    def _impl(attr):
+        kernel = attr['kernel_shape']
+        if len(kernel) == 2:
+            return prefix + '2d' + surfix
+        else:
+            raise NotImplementedError("Only 2d kernel supported.")
+    return _impl
+
+def _dimension_constraint():
+    def _dim_check(attrs):
+        if len(attrs['kernel_shape']) == 2:
+            return True
+        return False
+    return _dim_check, "Only 2d kernel supported."
+
+def _elemwise(name):
+    return AttrCvt(
+        op_name=_math_name_picker(name),
+        disables=['axis'],
+        ignores=['broadcast'])
+
+def _pooling(name):
+    return AttrCvt(
+        op_name=_dimension_picker(name),
+        transforms={
+            'kernel_shape': 'pool_size',
+            'pads': ('padding', (0, 0), _revert_caffe2_pad)},
+        # very weird attributes here in onnx, force check
+        excludes=['dilations'],
+        # TODO(zhreshold): make sure ceil_mode in onnx, and layout?
+        extras={'ceil_mode': True},
+        custom_check=_dimension_constraint())
+
+def _conv():
+    return AttrCvt(
+        op_name=_dimension_picker('conv'),
+        transforms={
+            'kernel_shape': 'kernel_size',
+            'dilations': ('dilation', (0, 0)),
+            'pads': ('padding', (0, 0), _revert_caffe2_pad),
+            'group': ('groups', 1)},
+        custom_check=_dimension_constraint())
+
+def _conv_transpose():
+    return AttrCvt(
+        op_name=_dimension_picker('conv', '_transpose'),
+        transforms={
+            'kernel_shape': 'kernel_size',
+            'dilations': ('dilation', (0, 0)),
+            'pads': ('padding', (0, 0), _revert_caffe2_pad)},
+        disables=['output_shape'],
+        custom_check=_dimension_constraint())
+
+def _batch_norm():
+    # TODO(zhreshold): 'spatial' is not properly handled here.
+    return AttrCvt(
+        op_name='batch_norm',
+        disables=['momentum'],
+        ignores=['spatial', 'is_test'])
+
+
+# compatible operators that do NOT require any conversion.
+_identity_list = []
+
+# _convert_map defines maps of name to converter functor(callable)
+_convert_map = {
+    # defs/experimental
+    'FC'            : AttrCvt('dense', ignores=['axis', 'axis_w']),
+
+    # defs/generator
+    # 'Constant'
+    # 'RandomUniform'
+    # 'RandomNormal'
+    # 'RandomUniformLike'
+    # 'RandomNormalLike'
+
+    # defs/logical
+
+    # defs/math
+    'Add'           : _elemwise('add'),
+    'Sub'           : _elemwise('sub'),
+    'Mul'           : _elemwise('mul'),
+    'Div'           : _elemwise('div'),
+    'Neg'           : Renamer('negative'),
+    # 'Abs'
+    # 'Reciprocal'
+    # 'Floor'
+    # 'Ceil'
+    # 'Sqrt'
+    'Relu'          : Renamer('relu'),
+    'LeakyRelu'     : Renamer('leaky_relu'),
+    # 'Selu'
+    # 'Elu'
+    'Exp'           : Renamer('exp'),
+    'Log'           : Renamer('log'),
+    'Tanh'          : Renamer('tanh'),
+    # 'Pow'
+    # 'Dot'
+    # 'PRelu'
+    'Sigmoid'       : Renamer('sigmoid'),
+    # 'Max' : this is the elemwise maximum
+    # 'Min' : this is the elemwise minimum
+    # 'Sum' : elemwise sum
+    # softmax default axis is different in onnx
+    'Softmax'       : AttrCvt('softmax', {'axis': ('axis', 1)}),
+
+    # defs/nn
+    'AveragePool'   : _pooling('avg_pool'),
+    'MaxPool'       : _pooling('max_pool'),
+    'Conv'          : _conv(),
+    'ConvTranspose' : _conv_transpose(),
+    'GlobalAveragePool': Renamer('global_avg_pool2d'),
+    'GlobalMaxPool' : Renamer('global_max_pool2d'),
+    'BatchNormalization': _batch_norm(),
+    'Dropout'       : AttrCvt('dropout', {'ratio': 'rate'}, ignores=['is_test']),
+    'Flatten'       : Renamer('flatten'),
+
+    # defs/reduction
+    'ReduceMax'     : AttrCvt('max', {'axes', 'axis'}),
+    'ReduceMin'     : AttrCvt('min', {'axes', 'axis'}),
+    'ReduceSum'     : AttrCvt('sum', {'axes', 'axis'}),
+    # 'ReduceMean'
+    # 'ReduceProd'
+    # 'ReduceLogSumExp'
+    # 'ArgMax'
+    # 'ArgMin'
+
+    # defs/tensor
+    'Cast'          : AttrCvt('cast', {'to': 'dtype'}),
+    'Reshape'       : Renamer('reshape'),
+    'Concat'        : Renamer('concatenate'),
+    'Split'         : AttrCvt('split', {'split': 'indices_or_sections'}),
+    # 'Slice'
+    'Transpose'     : AttrCvt('transpose', {'perm': 'axes'}),
+    # 'Gather'
+    # 'Squeeze'
+}
+
+def _convert_operator(op_name, attrs, identity_list=None, convert_map=None):
+    """Convert from onnx operator to nnvm operator.
+    The converter must specify conversions explicity for incompatible name, and
+    apply handlers to operator attributes.
+
+    Parameters
+    ----------
+    op_name : str
+        Operator name, such as Convolution, FullyConnected
+    attrs : dict
+        Dict of operator attributes
+    identity_list : list
+        List of operators that don't require conversion
+    convert_map : dict
+        Dict of name : callable, where name is the op's name that
+        require conversion to nnvm, callable are functions which
+        take attrs and return (new_op_name, new_attrs)
+
+    Returns
+    -------
+    (op_name, attrs)
+        Converted (op_name, attrs) for nnvm.
+    """
+    identity_list = identity_list if identity_list else _identity_list
+    convert_map = convert_map if convert_map else _convert_map
+    if op_name in identity_list:
+        pass
+    elif op_name in convert_map:
+        op_name, attrs = convert_map[op_name](attrs)
+    else:
+        _raise_not_supported('Operator: ' + op_name)
+    op = getattr(_sym, op_name, None)
+    if not op:
+        raise RuntimeError("Unable to map op_name {} to nnvm.sym".format(op_name))
+    return op, attrs
+
+
+class GraphProto(object):
+    """A helper class for handling nnvm graph copying from pb2.GraphProto.
+    Definition: https://github.com/onnx/onnx/blob/master/onnx/onnx.proto
+    """
+    def __init__(self):
+        self._nodes = {}
+        self._params = {}
+        self._renames = {}
+        self._num_input = 0
+        self._num_param = 0
+
+    def from_onnx(self, graph):
+        """Construct nnvm nodes from onnx graph.
+        The inputs from onnx graph is vague, only providing "1", "2"...
+        For convenience, we rename the `real` input names to "input_0",
+        "input_1"... And renaming parameters to "param_0", "param_1"...
+
+        Parameters
+        ----------
+        graph : onnx protobuf object
+            The loaded onnx graph
+
+        Returns
+        -------
+        sym : nnvm.sym.Symbol
+            The returned nnvm symbol
+        params : dict
+            A dict of name: tvm.nd.array pairs, used as pretrained weights
+        """
+        # parse network inputs to nnvm, aka parameters
+        for init_tensor in graph.initializer:
+            if not init_tensor.name.strip():
+                raise ValueError("Tensor's name is required.")
+            self._params[init_tensor.name] = self._parse_array(init_tensor)
+        for i in graph.input:
+            if i in self._params:
+                # i is a param instead of input
+                name_param = 'param_{}'.format(self._num_param)
+                self._num_param += 1
+                self._params[name_param] = self._params.pop(i)
+                self._nodes[name_param] = _sym.Variable(name=name_param)
+                self._renames[i] = name_param
+            else:
+                name_input = 'input_{}'.format(self._num_input)
+                self._num_input += 1
+                self._nodes[name_input] = _sym.Variable(name=name_input)
+                self._renames[i] = name_input
+        # construct nodes, nodes are stored as directed acyclic graph
+        for idx, node in enumerate(graph.node):
+            op_name = node.op_type
+            node_name = node.name.strip()
+            node_name = node_name if node_name else None
+            attr = self._parse_attr(node.attribute)
+            new_op, new_attr = _convert_operator(op_name, attr)
+            inputs = [self._nodes[self._renames.get(i, i)] for i in node.input]
+            # some hacks for onnx problem
+            new_attr = self._fix_bias(new_op, new_attr, len(inputs))
+            new_attr = self._fix_channels(new_op, new_attr, list(node.input))
+            self._fix_bias_shape(node.op_type, graph.node[idx-1].op_type, node.input)
+            op = new_op(name=node_name, *inputs, **new_attr)
+            assert len(node.output) == len(op.list_output_names()), (
+                "Number of output mismatch {} vs {}.".format(
+                    len(node.output), len(op.list_output_names())))
+            for k, i in zip(list(node.output), range(len(node.output))):
+                self._nodes[k] = op[i]
+        # now return the outputs
+        out = [self._nodes[i] for i in graph.output]
+        if len(out) > 1:
+            out = _sym.Group(out)
+        else:
+            out = out[0]
+        return out, self._params
+
+    def _parse_array(self, tensor_proto):
+        """Grab data in TensorProto and convert to numpy array."""
+        try:
+            from onnx.numpy_helper import to_array
+        except ImportError as e:
+            raise ImportError("Unable to import onnx which is required {}".format(e))
+        np_array = to_array(tensor_proto).reshape(tuple(tensor_proto.dims))
+        return tvm.nd.array(np_array)
+
+    def _parse_attr(self, attr_proto):
+        """Convert a list of AttributeProto to a dict, with names as keys."""
+        attrs = {}
+        for a in attr_proto:
+            for f in ['f', 'i', 's']:
+                if a.HasField(f):
+                    attrs[a.name] = getattr(a, f)
+            for f in ['floats', 'ints', 'strings']:
+                if list(getattr(a, f)):
+                    assert a.name not in attrs, "Only one type of attr is allowed"
+                    attrs[a.name] = tuple(getattr(a, f))
+            for f in ['t', 'g']:
+                if a.HasField(f):
+                    raise NotImplementedError("Filed {} is not supported in nnvm.".format(f))
+            for f in ['tensors', 'graphs']:
+                if list(getattr(a, f)):
+                    raise NotImplementedError("Filed {} is not supported in nnvm.".format(f))
+            if a.name not in attrs:
+                raise ValueError("Cannot parse attribute: \n{}\n.".format(a))
+        return attrs
+
+    def _fix_bias(self, op, attrs, num_inputs):
+        """A hack for 'use_bias' attribute since onnx don't provide this attribute,
+        we have to check the number of inputs to decide it."""
+        if op not in [_sym.conv2d, _sym.conv2d_transpose, _sym.dense]:
+            return attrs
+        if num_inputs == 3:
+            attrs['use_bias'] = True
+        elif num_inputs == 2:
+            attrs['use_bias'] = False
+        else:
+            raise ValueError("Unexpected number of inputs for: {}".format(op))
+        return attrs
+
+    def _fix_bias_shape(self, op_name, last_op_name, inputs):
+        """A hack to reshape bias term to (1, num_channel)."""
+        if op_name == 'Add' and last_op_name == 'Conv':
+            assert len(list(inputs)) == 2
+            bias_name = self._renames.get(inputs[1], inputs[1])
+            bias = self._params[bias_name]
+            assert len(bias.shape) == 1
+            # reshape to (1, n)
+            bias = tvm.nd.array(bias.asnumpy().reshape((1, -1, 1, 1)))
+            self._params[bias_name] = bias
+
+    def _fix_channels(self, op, attrs, inputs):
+        """A hack for getting 'channles' or 'units' since onnx don't provide
+        these attributes. We check the shape of weights provided to get the number.
+        """
+        if op not in [_sym.conv2d, _sym.conv2d_transpose, _sym.dense]:
+            return attrs
+        weight_name = self._renames[inputs[1]]
+        if not weight_name in self._params:
+            raise ValueError("Unable to get channels/units attr from onnx graph.")
+        else:
+            wshape = self._params[weight_name].shape
+            assert len(wshape) >= 2, "Weights shape is invalid: {}".format(wshape)
+            channels = wshape[0]
+            if op in [_sym.dense]:
+                attrs['units'] = channels
+            else:
+                attrs['channels'] = channels
+        return attrs
+
+def from_onnx(graph):
+    """Load onnx graph which is a python protobuf object in to nnvm graph.
+    The companion parameters will be handled automatically.
+    The inputs from onnx graph is vague, only providing "1", "2"...
+    For convenience, we rename the `real` input names to "input_0",
+    "input_1"... And renaming parameters to "param_0", "param_1"...
+
+    Parameters
+    ----------
+    graph : protobuf object
+        ONNX graph
+
+    Returns
+    -------
+    nnvm.Symbol
+        Compatible nnvm symbol
+    dict of tvm.ndarray
+        Dict of converted parameters stored in tvm.ndarray format
+    """
+    g = GraphProto()
+    sym, params = g.from_onnx(graph)
+    return sym, params
diff --git a/nnvm/python/nnvm/testing/__init__.py b/nnvm/python/nnvm/testing/__init__.py
index 27aaad8de09f..84c51a4df41d 100644
--- a/nnvm/python/nnvm/testing/__init__.py
+++ b/nnvm/python/nnvm/testing/__init__.py
@@ -2,4 +2,8 @@
 from __future__ import absolute_import as _abs
 
 from .config import ctx_list
+from .utils import create_workload
 from . import mobilenet
+from . import mlp
+from . import resnet
+from . import vgg
diff --git a/nnvm/python/nnvm/testing/mlp.py b/nnvm/python/nnvm/testing/mlp.py
new file mode 100644
index 000000000000..1b6975661fe4
--- /dev/null
+++ b/nnvm/python/nnvm/testing/mlp.py
@@ -0,0 +1,60 @@
+# 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.
+"""
+a simple multilayer perceptron
+"""
+from .. import symbol as sym
+from . utils import create_workload
+
+def get_symbol(num_classes=1000):
+    data = sym.Variable('data')
+    data = sym.flatten(data=data)
+    fc1 = sym.dense(data=data, name='fc1', units=128)
+    act1 = sym.relu(data=fc1, name='relu1')
+    fc2 = sym.dense(data=act1, name='fc2', units=64)
+    act2 = sym.relu(data=fc2, name='relu2')
+    fc3 = sym.dense(data=act2, name='fc3', units=num_classes)
+    mlp = sym.softmax(data=fc3, name='softmax')
+    return mlp
+
+def get_workload(batch_size, num_classes=1000, image_shape=(3, 224, 224), dtype="float32"):
+    """Get benchmark workload for a simple multilayer perceptron
+
+    Parameters
+    ----------
+    batch_size : int
+        The batch size used in the model
+
+    num_classes : int, optional
+        Number of claseses
+
+    image_shape : tuple, optional
+        The input image shape
+
+    dtype : str, optional
+        The data type
+
+    Returns
+    -------
+    net : nnvm.symbol
+        The computational graph
+
+    params : dict of str to NDArray
+        The parameters.
+    """
+    net = get_symbol(num_classes=num_classes)
+    return create_workload(net, batch_size, image_shape, dtype)
diff --git a/nnvm/python/nnvm/testing/mobilenet.py b/nnvm/python/nnvm/testing/mobilenet.py
index 4a08380312d5..f5667b649c43 100644
--- a/nnvm/python/nnvm/testing/mobilenet.py
+++ b/nnvm/python/nnvm/testing/mobilenet.py
@@ -2,11 +2,8 @@
 # pylint: disable=invalid-name
 from __future__ import absolute_import as _abs
 
-import numpy as np
-import tvm
-from .. compiler import graph_util
-from .. import graph
 from .. import symbol as sym
+from . utils import create_workload
 
 def conv_block(data, name, channels,
                kernel_size=(3, 3), strides=(1, 1), padding=(1, 1),
@@ -104,22 +101,5 @@ def get_workload(batch_size, num_classes=1000, image_shape=(3, 224, 224), dtype=
     params : dict of str to NDArray
         The parameters.
     """
-    image_shape = (3, 224, 224)
-    data_shape = (batch_size,) + image_shape
     net = mobile_net(num_classes=num_classes, alpha=1.0, is_shallow=False)
-    params = {}
-    g = graph.create(net)
-    input_shapes, _ = graph_util.infer_shape(g, data=data_shape)
-    shape_dict = dict(zip(g.index.input_names, input_shapes))
-    for k, v in shape_dict.items():
-        if k == "data":
-            continue
-        # Specially generate non-negative parameters.
-        if k.endswith("gamma"):
-            init = np.random.uniform(0.9, 1, size=v)
-        elif k.endswith("var"):
-            init = np.random.uniform(0.9, 1, size=v)
-        else:
-            init = np.random.uniform(-0.1, 0.1, size=v)
-        params[k] = tvm.nd.array(init.astype(dtype), ctx=tvm.cpu(0))
-    return net, params
+    return create_workload(net, batch_size, image_shape, dtype)
diff --git a/nnvm/python/nnvm/testing/resnet.py b/nnvm/python/nnvm/testing/resnet.py
new file mode 100644
index 000000000000..67b2c3efc017
--- /dev/null
+++ b/nnvm/python/nnvm/testing/resnet.py
@@ -0,0 +1,232 @@
+# 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.
+
+'''
+Adapted from https://github.com/tornadomeet/ResNet/blob/master/symbol_resnet.py
+Original author Wei Wu
+
+Implemented the following paper:
+
+Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun. "Identity Mappings in Deep Residual Networks"
+'''
+import numpy as np
+from .. import symbol as sym
+from . utils import create_workload
+
+def residual_unit(data, num_filter, stride, dim_match, name, bottle_neck=True):
+    """Return ResNet Unit symbol for building ResNet
+    Parameters
+    ----------
+    data : str
+        Input data
+    num_filter : int
+        Number of output channels
+    bnf : int
+        Bottle neck channels factor with regard to num_filter
+    stride : tuple
+        Stride used in convolution
+    dim_match : Boolean
+        True means channel number between input and output is the same, otherwise means differ
+    name : str
+        Base name of the operators
+    """
+    if bottle_neck:
+        # the same as https://github.com/facebook/fb.resnet.torch#notes,
+        # a bit difference with origin paper
+        bn1 = sym.batch_norm(data=data, epsilon=2e-5, name=name + '_bn1')
+        act1 = sym.relu(data=bn1, name=name + '_relu1')
+        conv1 = sym.conv2d(
+            data=act1, channels=int(num_filter*0.25), kernel_size=(1, 1),
+            strides=(1, 1), padding=(0, 0), use_bias=False, name=name + '_conv1')
+        bn2 = sym.batch_norm(data=conv1, epsilon=2e-5, name=name + '_bn2')
+        act2 = sym.relu(data=bn2, name=name + '_relu2')
+        conv2 = sym.conv2d(
+            data=act2, channels=int(num_filter*0.25), kernel_size=(3, 3),
+            strides=stride, padding=(1, 1), use_bias=False, name=name + '_conv2')
+        bn3 = sym.batch_norm(data=conv2, epsilon=2e-5, name=name + '_bn3')
+        act3 = sym.relu(data=bn3, name=name + '_relu3')
+        conv3 = sym.conv2d(
+            data=act3, channels=num_filter, kernel_size=(1, 1),
+            strides=(1, 1), padding=(0, 0), use_bias=False, name=name + '_conv3')
+        if dim_match:
+            shortcut = data
+        else:
+            shortcut = sym.conv2d(
+                data=act1, channels=num_filter, kernel_size=(1, 1),
+                strides=stride, use_bias=False, name=name+'_sc')
+        return sym.elemwise_add(conv3, shortcut)
+    else:
+        bn1 = sym.batch_norm(data=data, epsilon=2e-5, name=name + '_bn1')
+        act1 = sym.relu(data=bn1, name=name + '_relu1')
+        conv1 = sym.conv2d(
+            data=act1, channels=num_filter, kernel_size=(3, 3),
+            strides=stride, padding=(1, 1), use_bias=False, name=name + '_conv1')
+        bn2 = sym.batch_norm(data=conv1, epsilon=2e-5, name=name + '_bn2')
+        act2 = sym.relu(data=bn2, name=name + '_relu2')
+        conv2 = sym.conv2d(
+            data=act2, channels=num_filter, kernel_size=(3, 3),
+            strides=(1, 1), padding=(1, 1), use_bias=False, name=name + '_conv2')
+        if dim_match:
+            shortcut = data
+        else:
+            shortcut = sym.conv2d(
+                data=act1, channels=num_filter, kernel_size=(1, 1),
+                strides=stride, use_bias=False, name=name+'_sc')
+        return sym.elemwise_add(conv2, shortcut)
+
+def resnet(units, num_stages, filter_list, num_classes, image_shape,
+           bottle_neck=True, dtype='float32'):
+    """Return ResNet symbol of
+    Parameters
+    ----------
+    units : list
+        Number of units in each stage
+    num_stages : int
+        Number of stage
+    filter_list : list
+        Channel size of each stage
+    num_classes : int
+        Ouput size of symbol
+    dataset : str
+        Dataset type, only cifar10 and imagenet supports
+    dtype : str
+        Precision (float32 or float16)
+    """
+    num_unit = len(units)
+    assert num_unit == num_stages
+    data = sym.Variable(name='data')
+    if dtype == 'float32':
+        data = data
+    else:
+        if dtype == 'float16':
+            data = sym.cast(data=data, dtype=np.float16)
+    data = sym.batch_norm(data=data, epsilon=2e-5, name='bn_data')
+    (_, height, _) = image_shape
+    if height <= 32:            # such as cifar10
+        body = sym.conv2d(
+            data=data, channels=filter_list[0], kernel_size=(3, 3),
+            strides=(1, 1), padding=(1, 1), use_bias=False, name="conv0")
+    else:                       # often expected to be 224 such as imagenet
+        body = sym.conv2d(
+            data=data, channels=filter_list[0], kernel_size=(7, 7),
+            strides=(2, 2), padding=(3, 3), use_bias=False, name="conv0")
+        body = sym.batch_norm(data=body, epsilon=2e-5, name='bn0')
+        body = sym.relu(data=body, name='relu0')
+        body = sym.max_pool2d(data=body, pool_size=(3, 3), strides=(2, 2), padding=(1, 1))
+
+    for i in range(num_stages):
+        body = residual_unit(
+            body, filter_list[i+1], (1 if i == 0 else 2, 1 if i == 0 else 2),
+            False, name='stage%d_unit%d' % (i + 1, 1), bottle_neck=bottle_neck)
+        for j in range(units[i]-1):
+            body = residual_unit(
+                body, filter_list[i+1], (1, 1), True,
+                name='stage%d_unit%d' % (i + 1, j + 2), bottle_neck=bottle_neck)
+    bn1 = sym.batch_norm(data=body, epsilon=2e-5, name='bn1')
+    relu1 = sym.relu(data=bn1, name='relu1')
+    # Although kernel is not used here when global_pool=True, we should put one
+    pool1 = sym.global_avg_pool2d(data=relu1, name='pool1')
+    flat = sym.flatten(data=pool1)
+    fc1 = sym.dense(data=flat, units=num_classes, name='fc1')
+    if dtype == 'float16':
+        fc1 = sym.cast(data=fc1, dtype=np.float32)
+    return sym.softmax(data=fc1, name='softmax')
+
+def get_symbol(num_classes, num_layers=50, image_shape=(3, 224, 224), dtype='float32'):
+    """
+    Adapted from https://github.com/tornadomeet/ResNet/blob/master/train_resnet.py
+    Original author Wei Wu
+    """
+    (_, height, _) = image_shape
+    if height <= 28:
+        num_stages = 3
+        if (num_layers-2) % 9 == 0 and num_layers >= 164:
+            per_unit = [(num_layers-2)//9]
+            filter_list = [16, 64, 128, 256]
+            bottle_neck = True
+        elif (num_layers-2) % 6 == 0 and num_layers < 164:
+            per_unit = [(num_layers-2)//6]
+            filter_list = [16, 16, 32, 64]
+            bottle_neck = False
+        else:
+            raise ValueError("no experiments done on num_layers {}".format(num_layers))
+        units = per_unit * num_stages
+    else:
+        if num_layers >= 50:
+            filter_list = [64, 256, 512, 1024, 2048]
+            bottle_neck = True
+        else:
+            filter_list = [64, 64, 128, 256, 512]
+            bottle_neck = False
+        num_stages = 4
+        if num_layers == 18:
+            units = [2, 2, 2, 2]
+        elif num_layers == 34:
+            units = [3, 4, 6, 3]
+        elif num_layers == 50:
+            units = [3, 4, 6, 3]
+        elif num_layers == 101:
+            units = [3, 4, 23, 3]
+        elif num_layers == 152:
+            units = [3, 8, 36, 3]
+        elif num_layers == 200:
+            units = [3, 24, 36, 3]
+        elif num_layers == 269:
+            units = [3, 30, 48, 8]
+        else:
+            raise ValueError("no experiments done on num_layers {}".format(num_layers))
+
+    return resnet(units=units,
+                  num_stages=num_stages,
+                  filter_list=filter_list,
+                  num_classes=num_classes,
+                  image_shape=image_shape,
+                  bottle_neck=bottle_neck,
+                  dtype=dtype)
+
+def get_workload(batch_size, num_classes=1000, image_shape=(3, 224, 224),
+                 dtype="float32", **kwargs):
+    """Get benchmark workload for resnet
+
+    Parameters
+    ----------
+    batch_size : int
+        The batch size used in the model
+
+    num_classes : int, optional
+        Number of claseses
+
+    image_shape : tuple, optional
+        The input image shape
+
+    dtype : str, optional
+        The data type
+
+    kwargs : dict
+        Extra arguments
+
+    Returns
+    -------
+    net : nnvm.Symbol
+        The computational graph
+
+    params : dict of str to NDArray
+        The parameters.
+    """
+    net = get_symbol(num_classes=num_classes, image_shape=image_shape,
+                     dtype=dtype, **kwargs)
+    return create_workload(net, batch_size, image_shape, dtype)
diff --git a/nnvm/python/nnvm/testing/utils.py b/nnvm/python/nnvm/testing/utils.py
new file mode 100644
index 000000000000..5d2146971172
--- /dev/null
+++ b/nnvm/python/nnvm/testing/utils.py
@@ -0,0 +1,52 @@
+"""Helper utility to create common workload for testing."""
+from __future__ import absolute_import as _abs
+
+import numpy as np
+import tvm
+from ..compiler import graph_util
+from ..import graph
+
+
+def create_workload(net, batch_size, image_shape=(3, 224, 224), dtype="float32"):
+    """Helper function to create benchmark workload for input network
+
+    Parameters
+    ----------
+    net : nnvm.Symbol
+        The selected network symbol to use
+
+    batch_size : int
+        The batch size used in the model
+
+    image_shape : tuple, optional
+        The input image shape
+
+    dtype : str, optional
+        The data type
+
+    Returns
+    -------
+    net : nnvm.Symbol
+        The computational graph
+
+    params : dict of str to NDArray
+        The parameters.
+    """
+    image_shape = (3, 224, 224)
+    data_shape = (batch_size,) + image_shape
+    params = {}
+    g = graph.create(net)
+    input_shapes, _ = graph_util.infer_shape(g, data=data_shape)
+    shape_dict = dict(zip(g.index.input_names, input_shapes))
+    for k, v in shape_dict.items():
+        if k == "data":
+            continue
+        # Specially generate non-negative parameters.
+        if k.endswith("gamma"):
+            init = np.random.uniform(0.9, 1, size=v)
+        elif k.endswith("var"):
+            init = np.random.uniform(0.9, 1, size=v)
+        else:
+            init = np.random.uniform(-0.1, 0.1, size=v)
+        params[k] = tvm.nd.array(init.astype(dtype), ctx=tvm.cpu(0))
+    return net, params
diff --git a/nnvm/python/nnvm/testing/vgg.py b/nnvm/python/nnvm/testing/vgg.py
new file mode 100644
index 000000000000..fe240d6eafcf
--- /dev/null
+++ b/nnvm/python/nnvm/testing/vgg.py
@@ -0,0 +1,114 @@
+# 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.
+
+"""References:
+
+Simonyan, Karen, and Andrew Zisserman. "Very deep convolutional networks for
+large-scale image recognition." arXiv preprint arXiv:1409.1556 (2014).
+"""
+import numpy as np
+from .. import symbol as sym
+from . utils import create_workload
+
+def get_feature(internel_layer, layers, filters, batch_norm=False):
+    """Get VGG feature body as stacks of convoltions."""
+    for i, num in enumerate(layers):
+        for j in range(num):
+            internel_layer = sym.conv2d(
+                data=internel_layer, kernel_size=(3, 3), padding=(1, 1),
+                channels=filters[i], name="conv%s_%s"%(i + 1, j + 1))
+            if batch_norm:
+                internel_layer = sym.batch_norm(
+                    data=internel_layer, name="bn%s_%s" %(i + 1, j + 1))
+            internel_layer = sym.relu(data=internel_layer, name="relu%s_%s" %(i + 1, j + 1))
+        internel_layer = sym.max_pool2d(
+            data=internel_layer, pool_size=(2, 2), strides=(2, 2), name="pool%s"%(i + 1))
+    return internel_layer
+
+def get_classifier(input_data, num_classes):
+    """Get VGG classifier layers as fc layers."""
+    flatten = sym.flatten(data=input_data, name="flatten")
+    fc6 = sym.dense(data=flatten, units=4096, name="fc6")
+    relu6 = sym.relu(data=fc6, name="relu6")
+    drop6 = sym.dropout(data=relu6, rate=0.5, name="drop6")
+    fc7 = sym.dense(data=drop6, units=4096, name="fc7")
+    relu7 = sym.relu(data=fc7, name="relu7")
+    drop7 = sym.dropout(data=relu7, rate=0.5, name="drop7")
+    fc8 = sym.dense(data=drop7, units=num_classes, name="fc8")
+    return fc8
+
+def get_symbol(num_classes, num_layers=11, batch_norm=False, dtype='float32'):
+    """
+    Parameters
+    ----------
+    num_classes : int, default 1000
+        Number of classification classes.
+    num_layers : int
+        Number of layers for the variant of densenet. Options are 11, 13, 16, 19.
+    batch_norm : bool, default False
+        Use batch normalization.
+    dtype: str, float32 or float16
+        Data precision.
+    """
+    vgg_spec = {11: ([1, 1, 2, 2, 2], [64, 128, 256, 512, 512]),
+                13: ([2, 2, 2, 2, 2], [64, 128, 256, 512, 512]),
+                16: ([2, 2, 3, 3, 3], [64, 128, 256, 512, 512]),
+                19: ([2, 2, 4, 4, 4], [64, 128, 256, 512, 512])}
+    if not vgg_spec.has_key(num_layers):
+        raise ValueError("Invalide num_layers {}. Choices are 11,13,16,19.".format(num_layers))
+    layers, filters = vgg_spec[num_layers]
+    data = sym.Variable(name="data")
+    if dtype == 'float16':
+        data = sym.cast(data=data, dtype=np.float16)
+    feature = get_feature(data, layers, filters, batch_norm)
+    classifier = get_classifier(feature, num_classes)
+    if dtype == 'float16':
+        classifier = sym.cast(data=classifier, dtype=np.float32)
+    symbol = sym.softmax(data=classifier, name='softmax')
+    return symbol
+
+def get_workload(batch_size, num_classes=1000, image_shape=(3, 224, 224),
+                 dtype="float32", **kwargs):
+    """Get benchmark workload for VGG nets.
+
+    Parameters
+    ----------
+    batch_size : int
+        The batch size used in the model
+
+    num_classes : int, optional
+        Number of claseses
+
+    image_shape : tuple, optional
+        The input image shape
+
+    dtype : str, optional
+        The data type
+
+    kwargs : dict
+        Extra arguments
+
+    Returns
+    -------
+    net : nnvm.Symbol
+        The computational graph
+
+    params : dict of str to NDArray
+        The parameters.
+    """
+    net = get_symbol(num_classes=num_classes, dtype=dtype, **kwargs)
+    return create_workload(net, batch_size, image_shape, dtype)
diff --git a/nnvm/tests/python/frontend/mxnet/model_zoo/__init__.py b/nnvm/tests/python/frontend/mxnet/model_zoo/__init__.py
index 07be9bba02c0..c39e2b21490f 100644
--- a/nnvm/tests/python/frontend/mxnet/model_zoo/__init__.py
+++ b/nnvm/tests/python/frontend/mxnet/model_zoo/__init__.py
@@ -1,22 +1,28 @@
+"""MXNet and NNVM model zoo."""
 from __future__ import absolute_import
 from . import mlp, resnet, vgg
+import nnvm.testing
+
+__all__ = ['mx_mlp', 'nnvm_mlp', 'mx_resnet', 'nnvm_resnet', 'mx_vgg', 'nnvm_vgg']
 
 _num_class = 1000
 
 # mlp fc
 mx_mlp = mlp.get_symbol(_num_class)
-nnvm_mlp = mlp.get_symbol_nnvm(_num_class)
+nnvm_mlp = nnvm.testing.mlp.get_workload(1, _num_class)[0]
 
 # resnet fc
 mx_resnet = {}
 nnvm_resnet = {}
 for num_layer in [18, 34, 50, 101, 152, 200, 269]:
     mx_resnet[num_layer] = resnet.get_symbol(_num_class, num_layer, '3,224,224')
-    nnvm_resnet[num_layer] = resnet.get_symbol(_num_class, num_layer, '3, 224, 224', lib='nnvm')
+    nnvm_resnet[num_layer] = nnvm.testing.resnet.get_workload(
+        1, _num_class, num_layers=num_layer)[0]
 
 # vgg fc
 mx_vgg = {}
 nnvm_vgg = {}
 for num_layer in [11, 13, 16, 19]:
     mx_vgg[num_layer] = vgg.get_symbol(_num_class, num_layer)
-    nnvm_vgg[num_layer] = vgg.get_symbol_nnvm(_num_class, num_layer)
+    nnvm_vgg[num_layer] = nnvm.testing.vgg.get_workload(
+        1, _num_class, num_layers=num_layer)[0]
diff --git a/nnvm/tests/python/frontend/mxnet/model_zoo/mlp.py b/nnvm/tests/python/frontend/mxnet/model_zoo/mlp.py
index f69d4a2d1d52..76db9d132fcb 100644
--- a/nnvm/tests/python/frontend/mxnet/model_zoo/mlp.py
+++ b/nnvm/tests/python/frontend/mxnet/model_zoo/mlp.py
@@ -19,7 +19,6 @@
 a simple multilayer perceptron
 """
 import mxnet as mx
-import nnvm
 
 def get_symbol(num_classes=10, **kwargs):
     data = mx.symbol.Variable('data')
@@ -31,14 +30,3 @@ def get_symbol(num_classes=10, **kwargs):
     fc3  = mx.symbol.FullyConnected(data = act2, name='fc3', num_hidden=num_classes)
     mlp  = mx.symbol.softmax(data = fc3, name = 'softmax')
     return mlp
-
-def get_symbol_nnvm(num_classes=10, **kwargs):
-    data = nnvm.symbol.Variable('data')
-    data = nnvm.sym.flatten(data=data)
-    fc1  = nnvm.symbol.dense(data = data, name='fc1', units=128)
-    act1 = nnvm.symbol.relu(data = fc1, name='relu1')
-    fc2  = nnvm.symbol.dense(data = act1, name = 'fc2', units = 64)
-    act2 = nnvm.symbol.relu(data = fc2, name='relu2')
-    fc3  = nnvm.symbol.dense(data = act2, name='fc3', units=num_classes)
-    mlp  = nnvm.symbol.softmax(data = fc3, name = 'softmax')
-    return mlp
diff --git a/nnvm/tests/python/frontend/mxnet/model_zoo/resnet.py b/nnvm/tests/python/frontend/mxnet/model_zoo/resnet.py
index 2d4a5c9179d1..85c0758bf26a 100644
--- a/nnvm/tests/python/frontend/mxnet/model_zoo/resnet.py
+++ b/nnvm/tests/python/frontend/mxnet/model_zoo/resnet.py
@@ -25,7 +25,6 @@
 '''
 import mxnet as mx
 import numpy as np
-import nnvm
 
 def residual_unit(data, num_filter, stride, dim_match, name, bottle_neck=True, bn_mom=0.9, workspace=256, memonger=False):
     """Return ResNet Unit symbol for building ResNet
@@ -86,65 +85,6 @@ def residual_unit(data, num_filter, stride, dim_match, name, bottle_neck=True, b
             shortcut._set_attr(mirror_stage='True')
         return conv2 + shortcut
 
-def residual_unit_nnvm(data, num_filter, stride, dim_match, name, bottle_neck=True, bn_mom=0.9, workspace=256, memonger=False):
-    """Return ResNet Unit symbol for building ResNet
-    Parameters
-    ----------
-    data : str
-        Input data
-    num_filter : int
-        Number of output channels
-    bnf : int
-        Bottle neck channels factor with regard to num_filter
-    stride : tuple
-        Stride used in convolution
-    dim_match : Boolean
-        True means channel number between input and output is the same, otherwise means differ
-    name : str
-        Base name of the operators
-    workspace : int
-        Workspace used in convolution operator
-    """
-    if bottle_neck:
-        # the same as https://github.com/facebook/fb.resnet.torch#notes, a bit difference with origin paper
-        bn1 = nnvm.sym.batch_norm(data=data, epsilon=2e-5, name=name + '_bn1')
-        act1 = nnvm.sym.relu(data=bn1, name=name + '_relu1')
-        conv1 = nnvm.sym.conv2d(data=act1, channels=int(num_filter*0.25), kernel_size=(1,1), strides=(1,1), padding=(0,0),
-                                   use_bias=False, name=name + '_conv1')
-        bn2 = nnvm.sym.batch_norm(data=conv1, epsilon=2e-5, name=name + '_bn2')
-        act2 = nnvm.sym.relu(data=bn2, name=name + '_relu2')
-        conv2 = nnvm.sym.conv2d(data=act2, channels=int(num_filter*0.25), kernel_size=(3,3), strides=stride, padding=(1,1),
-                                   use_bias=False, name=name + '_conv2')
-        bn3 = nnvm.sym.batch_norm(data=conv2, epsilon=2e-5, name=name + '_bn3')
-        act3 = nnvm.sym.relu(data=bn3, name=name + '_relu3')
-        conv3 = nnvm.sym.conv2d(data=act3, channels=num_filter, kernel_size=(1,1), strides=(1,1), padding=(0,0), use_bias=False,
-                                   name=name + '_conv3')
-        if dim_match:
-            shortcut = data
-        else:
-            shortcut = nnvm.sym.conv2d(data=act1, channels=num_filter, kernel_size=(1,1), strides=stride, use_bias=False,
-                                            name=name+'_sc')
-        if memonger:
-            shortcut._set_attr(mirror_stage='True')
-        return nnvm.sym.elemwise_add(conv3, shortcut)
-    else:
-        bn1 = nnvm.sym.batch_norm(data=data, epsilon=2e-5, name=name + '_bn1')
-        act1 = nnvm.sym.relu(data=bn1, name=name + '_relu1')
-        conv1 = nnvm.sym.conv2d(data=act1, channels=num_filter, kernel_size=(3,3), strides=stride, padding=(1,1),
-                                      use_bias=False, name=name + '_conv1')
-        bn2 = nnvm.sym.batch_norm(data=conv1, epsilon=2e-5, name=name + '_bn2')
-        act2 = nnvm.sym.relu(data=bn2, name=name + '_relu2')
-        conv2 = nnvm.sym.conv2d(data=act2, channels=num_filter, kernel_size=(3,3), strides=(1,1), padding=(1,1),
-                                      use_bias=False, name=name + '_conv2')
-        if dim_match:
-            shortcut = data
-        else:
-            shortcut = nnvm.sym.conv2d(data=act1, channels=num_filter, kernel_size=(1,1), strides=stride, use_bias=False,
-                                            name=name+'_sc')
-        if memonger:
-            shortcut._set_attr(mirror_stage='True')
-        return nnvm.sym.elemwise_add(conv2, shortcut)
-
 def resnet(units, num_stages, filter_list, num_classes, image_shape, bottle_neck=True, bn_mom=0.9, workspace=256, dtype='float32', memonger=False):
     """Return ResNet symbol of
     Parameters
@@ -202,64 +142,7 @@ def resnet(units, num_stages, filter_list, num_classes, image_shape, bottle_neck
         fc1 = mx.sym.Cast(data=fc1, dtype=np.float32)
     return mx.sym.softmax(data=fc1, name='softmax')
 
-def resnet_nnvm(units, num_stages, filter_list, num_classes, image_shape, bottle_neck=True, bn_mom=0.9, workspace=256, dtype='float32', memonger=False):
-    """Return ResNet symbol of
-    Parameters
-    ----------
-    units : list
-        Number of units in each stage
-    num_stages : int
-        Number of stage
-    filter_list : list
-        Channel size of each stage
-    num_classes : int
-        Ouput size of symbol
-    dataset : str
-        Dataset type, only cifar10 and imagenet supports
-    workspace : int
-        Workspace used in convolution operator
-    dtype : str
-        Precision (float32 or float16)
-    """
-    num_unit = len(units)
-    assert(num_unit == num_stages)
-    data = nnvm.sym.Variable(name='data')
-    if dtype == 'float32':
-        # data = nnvm.sym.identity(data=data, name='id')
-        data = data
-    else:
-        if dtype == 'float16':
-            data = nnvm.sym.cast(data=data, dtype=np.float16)
-    data = nnvm.sym.batch_norm(data=data, epsilon=2e-5, name='bn_data')
-    (nchannel, height, width) = image_shape
-    if height <= 32:            # such as cifar10
-        body = nnvm.sym.conv2d(data=data, channels=filter_list[0], kernel_size=(3, 3), strides=(1,1), padding=(1, 1),
-                                  use_bias=False, name="conv0")
-    else:                       # often expected to be 224 such as imagenet
-        body = nnvm.sym.conv2d(data=data, channels=filter_list[0], kernel_size=(7, 7), strides=(2,2), padding=(3, 3),
-                                  use_bias=False, name="conv0")
-        body = nnvm.sym.batch_norm(data=body, epsilon=2e-5, name='bn0')
-        body = nnvm.sym.relu(data=body, name='relu0')
-        body = nnvm.sym.max_pool2d(data=body, pool_size=(3, 3), strides=(2,2), padding=(1,1))
-
-    for i in range(num_stages):
-        body = residual_unit_nnvm(body, filter_list[i+1], (1 if i==0 else 2, 1 if i==0 else 2), False,
-                             name='stage%d_unit%d' % (i + 1, 1), bottle_neck=bottle_neck,
-                             memonger=memonger)
-        for j in range(units[i]-1):
-            body = residual_unit_nnvm(body, filter_list[i+1], (1,1), True, name='stage%d_unit%d' % (i + 1, j + 2),
-                                 bottle_neck=bottle_neck, memonger=memonger)
-    bn1 = nnvm.sym.batch_norm(data=body, epsilon=2e-5, name='bn1')
-    relu1 = nnvm.sym.relu(data=bn1, name='relu1')
-    # Although kernel is not used here when global_pool=True, we should put one
-    pool1 = nnvm.sym.global_avg_pool2d(data=relu1, name='pool1')
-    flat = nnvm.sym.flatten(data=pool1)
-    fc1 = nnvm.sym.dense(data=flat, units=num_classes, name='fc1')
-    if dtype == 'float16':
-        fc1 = nnvm.sym.cast(data=fc1, dtype=np.float32)
-    return nnvm.sym.softmax(data=fc1, name='softmax')
-
-def get_symbol(num_classes, num_layers, image_shape, conv_workspace=256, dtype='float32', lib='mxnet', **kwargs):
+def get_symbol(num_classes, num_layers, image_shape, conv_workspace=256, dtype='float32', **kwargs):
     """
     Adapted from https://github.com/tornadomeet/ResNet/blob/master/train_resnet.py
     Original author Wei Wu
@@ -311,12 +194,4 @@ def get_symbol(num_classes, num_layers, image_shape, conv_workspace=256, dtype='
                   image_shape = image_shape,
                   bottle_neck = bottle_neck,
                   workspace   = conv_workspace,
-                  dtype       = dtype) if lib == 'mxnet' else \
-        resnet_nnvm(units       = units,
-                      num_stages  = num_stages,
-                      filter_list = filter_list,
-                      num_classes = num_classes,
-                      image_shape = image_shape,
-                      bottle_neck = bottle_neck,
-                      workspace   = conv_workspace,
-                      dtype       = dtype)
+                  dtype       = dtype)
diff --git a/nnvm/tests/python/frontend/mxnet/model_zoo/vgg.py b/nnvm/tests/python/frontend/mxnet/model_zoo/vgg.py
index 1992aa0ccc1c..cd99ff72e917 100644
--- a/nnvm/tests/python/frontend/mxnet/model_zoo/vgg.py
+++ b/nnvm/tests/python/frontend/mxnet/model_zoo/vgg.py
@@ -22,7 +22,6 @@
 """
 
 import mxnet as mx
-import nnvm
 import numpy as np
 
 def get_feature(internel_layer, layers, filters, batch_norm = False, **kwargs):
@@ -35,16 +34,6 @@ def get_feature(internel_layer, layers, filters, batch_norm = False, **kwargs):
         internel_layer = mx.sym.Pooling(data=internel_layer, pool_type="max", kernel=(2, 2), stride=(2,2), name="pool%s" %(i + 1))
     return internel_layer
 
-def get_feature_nnvm(internel_layer, layers, filters, batch_norm = False, **kwargs):
-    for i, num in enumerate(layers):
-        for j in range(num):
-            internel_layer = nnvm.sym.conv2d(data = internel_layer, kernel_size=(3, 3), padding=(1, 1), channels=filters[i], name="conv%s_%s" %(i + 1, j + 1))
-            if batch_norm:
-                internel_layer = nnvm.symbol.batch_norm(data=internel_layer, name="bn%s_%s" %(i + 1, j + 1))
-            internel_layer = nnvm.sym.relu(data=internel_layer, name="relu%s_%s" %(i + 1, j + 1))
-        internel_layer = nnvm.sym.max_pool2d(data=internel_layer, pool_size=(2, 2), strides=(2,2), name="pool%s" %(i + 1))
-    return internel_layer
-
 def get_classifier(input_data, num_classes, **kwargs):
     flatten = mx.sym.Flatten(data=input_data, name="flatten")
     fc6 = mx.sym.FullyConnected(data=flatten, num_hidden=4096, name="fc6")
@@ -56,17 +45,6 @@ def get_classifier(input_data, num_classes, **kwargs):
     fc8 = mx.sym.FullyConnected(data=drop7, num_hidden=num_classes, name="fc8")
     return fc8
 
-def get_classifier_nnvm(input_data, num_classes, **kwargs):
-    flatten = nnvm.sym.flatten(data=input_data, name="flatten")
-    fc6 = nnvm.sym.dense(data=flatten, units=4096, name="fc6")
-    relu6 = nnvm.sym.relu(data=fc6, name="relu6")
-    drop6 = nnvm.sym.dropout(data=relu6, rate=0.5, name="drop6")
-    fc7 = nnvm.sym.dense(data=drop6, units=4096, name="fc7")
-    relu7 = nnvm.sym.relu(data=fc7, name="relu7")
-    drop7 = nnvm.sym.dropout(data=relu7, rate=0.5, name="drop7")
-    fc8 = nnvm.sym.dense(data=drop7, units=num_classes, name="fc8")
-    return fc8
-
 def get_symbol(num_classes, num_layers=11, batch_norm=False, dtype='float32', **kwargs):
     """
     Parameters
@@ -96,33 +74,3 @@ def get_symbol(num_classes, num_layers=11, batch_norm=False, dtype='float32', **
         classifier = mx.sym.Cast(data=classifier, dtype=np.float32)
     symbol = mx.sym.softmax(data=classifier, name='softmax')
     return symbol
-
-def get_symbol_nnvm(num_classes, num_layers=11, batch_norm=False, dtype='float32', **kwargs):
-    """
-    Parameters
-    ----------
-    num_classes : int, default 1000
-        Number of classification classes.
-    num_layers : int
-        Number of layers for the variant of densenet. Options are 11, 13, 16, 19.
-    batch_norm : bool, default False
-        Use batch normalization.
-    dtype: str, float32 or float16
-        Data precision.
-    """
-    vgg_spec = {11: ([1, 1, 2, 2, 2], [64, 128, 256, 512, 512]),
-                13: ([2, 2, 2, 2, 2], [64, 128, 256, 512, 512]),
-                16: ([2, 2, 3, 3, 3], [64, 128, 256, 512, 512]),
-                19: ([2, 2, 4, 4, 4], [64, 128, 256, 512, 512])}
-    if not vgg_spec.has_key(num_layers):
-        raise ValueError("Invalide num_layers {}. Possible choices are 11,13,16,19.".format(num_layers))
-    layers, filters = vgg_spec[num_layers]
-    data = nnvm.sym.Variable(name="data")
-    if dtype == 'float16':
-        data = nnvm.sym.cast(data=data, dtype=np.float16)
-    feature = get_feature_nnvm(data, layers, filters, batch_norm)
-    classifier = get_classifier_nnvm(feature, num_classes)
-    if dtype == 'float16':
-        classifier = nnvm.sym.cast(data=classifier, dtype=np.float32)
-    symbol = nnvm.sym.softmax(data=classifier, name='softmax')
-    return symbol
diff --git a/nnvm/tests/python/frontend/mxnet/test_graph.py b/nnvm/tests/python/frontend/mxnet/test_graph.py
index 7701aa86ae58..bd2ce8727c92 100644
--- a/nnvm/tests/python/frontend/mxnet/test_graph.py
+++ b/nnvm/tests/python/frontend/mxnet/test_graph.py
@@ -14,21 +14,21 @@ def compare_graph(sym1, sym2, ishape=(2, 3, 224, 224)):
 
 def test_mlp():
     mx_sym = model_zoo.mx_mlp
-    from_mx_sym = nnvm.frontend.from_mxnet(mx_sym)
+    from_mx_sym, _ = nnvm.frontend.from_mxnet(mx_sym)
     nnvm_sym = model_zoo.nnvm_mlp
     compare_graph(from_mx_sym, nnvm_sym)
 
 def test_vgg():
     for n in [11, 13, 16, 19]:
         mx_sym = model_zoo.mx_vgg[n]
-        from_mx_sym = nnvm.frontend.from_mxnet(mx_sym)
+        from_mx_sym, _ = nnvm.frontend.from_mxnet(mx_sym)
         nnvm_sym = model_zoo.nnvm_vgg[n]
         compare_graph(from_mx_sym, nnvm_sym)
 
 def test_resnet():
     for n in [18, 34, 50, 101]:
         mx_sym = model_zoo.mx_resnet[n]
-        from_mx_sym = nnvm.frontend.from_mxnet(mx_sym)
+        from_mx_sym, _ = nnvm.frontend.from_mxnet(mx_sym)
         nnvm_sym = model_zoo.nnvm_resnet[n]
         compare_graph(from_mx_sym, nnvm_sym)
 
diff --git a/nnvm/tests/python/frontend/onnx/model_zoo/__init__.py b/nnvm/tests/python/frontend/onnx/model_zoo/__init__.py
new file mode 100644
index 000000000000..811dd3b7e69f
--- /dev/null
+++ b/nnvm/tests/python/frontend/onnx/model_zoo/__init__.py
@@ -0,0 +1,13 @@
+"""Store for onnx examples and common models."""
+from __future__ import absolute_import as _abs
+import os
+from .super_resolution import get_super_resolution
+
+__all__ = ['super_resolution']
+
+def _as_abs_path(fname):
+    cur_dir = os.path.abspath(os.path.dirname(__file__))
+    return os.path.join(cur_dir, fname)
+
+# a pair of onnx pb file and corresponding nnvm symbol
+super_resolution = (_as_abs_path('super_resolution.onnx'), get_super_resolution())
diff --git a/nnvm/tests/python/frontend/onnx/model_zoo/super_resolution.onnx b/nnvm/tests/python/frontend/onnx/model_zoo/super_resolution.onnx
new file mode 100644
index 0000000000000000000000000000000000000000..65e138b0585b399bf902889093b4cddedbadad71
GIT binary patch
diff --git a/nnvm/tests/python/frontend/onnx/model_zoo/super_resolution.py b/nnvm/tests/python/frontend/onnx/model_zoo/super_resolution.py
new file mode 100644
index 000000000000..5aa2219fd11c
--- /dev/null
+++ b/nnvm/tests/python/frontend/onnx/model_zoo/super_resolution.py
@@ -0,0 +1,36 @@
+"""NNVM symbol corresponding to super_resolution.onnx example."""
+from nnvm import sym
+
+def get_super_resolution_deprecated():
+    factor = 3
+    size = 224
+    data = sym.Variable(name='9')
+    conv1 = sym.conv2d(data, channels=64, kernel_size=(5, 5), padding=(2, 2))
+    relu1 = sym.relu(conv1)
+    conv2 = sym.conv2d(relu1, channels=64, kernel_size=(3, 3), padding=(1, 1))
+    relu2 = sym.relu(conv2)
+    conv3 = sym.conv2d(relu2, channels=32, kernel_size=(3, 3), padding=(1, 1))
+    relu3 = sym.relu(conv3)
+    conv4 = sym.conv2d(relu3, channels=factor**2, kernel_size=(3, 3), padding=(1, 1))
+    r1 = sym.reshape(conv4, shape=(0, 1, factor, factor, size, size))
+    t1 = sym.transpose(r1, axes=(0, 1, 4, 2, 5, 3))
+    r2 = sym.reshape(t1, shape=(0, 1, size * factor, size * factor))
+    return r2
+
+def get_super_resolution():
+    factor = 3
+    size = 224
+    data = sym.Variable(name='9')
+    conv1 = sym.conv2d(data, channels=64, kernel_size=(5, 5), padding=(2, 2), use_bias=False)
+    relu1 = sym.relu(conv1 + sym.Variable(name='2'))
+    conv2 = sym.conv2d(relu1, channels=64, kernel_size=(3, 3), padding=(1, 1), use_bias=False)
+    relu2 = sym.relu(conv2 + sym.Variable(name='4'))
+    conv3 = sym.conv2d(relu2, channels=32, kernel_size=(3, 3), padding=(1, 1), use_bias=False)
+    relu3 = sym.relu(conv3 + sym.Variable(name='6'))
+    conv4 = sym.conv2d(relu3, channels=factor**2, kernel_size=(3, 3), padding=(1, 1), use_bias=False)
+    conv4 = conv4 + sym.Variable(name='8')
+    # TODO(zhreshold): allow shape inference for batch size > 1
+    r1 = sym.reshape(conv4, shape=(1, 1, factor, factor, size, size))
+    t1 = sym.transpose(r1, axes=(0, 1, 4, 2, 5, 3))
+    r2 = sym.reshape(t1, shape=(1, 1, size * factor, size * factor))
+    return r2
diff --git a/nnvm/tests/python/frontend/onnx/test_forward.py b/nnvm/tests/python/frontend/onnx/test_forward.py
new file mode 100644
index 000000000000..36020103b934
--- /dev/null
+++ b/nnvm/tests/python/frontend/onnx/test_forward.py
@@ -0,0 +1,42 @@
+import numpy as np
+import nnvm
+import tvm
+from tvm.contrib import graph_runtime
+from nnvm.testing.config import ctx_list
+import onnx
+import onnx_caffe2.backend
+from model_zoo import super_resolution
+
+def test_onnx_forward_impl(graph_file, data_shape, out_shape):
+    def get_caffe2_output(graph, x, dtype='float32'):
+        prepared_backend = onnx_caffe2.backend.prepare(graph)
+        W = {graph.input[-1]: x.astype(dtype)}
+        c2_out = prepared_backend.run(W)[0]
+        return c2_out
+
+    def get_tvm_output(graph, x, target, ctx, dtype='float32'):
+        new_sym, params = nnvm.frontend.from_onnx(graph)
+        shape_dict = {'input_0': x.shape}
+        graph, lib, params = nnvm.compiler.build(new_sym, target, shape_dict, params=params)
+        m = graph_runtime.create(graph, lib, ctx)
+        # set inputs
+        m.set_input('input_0', tvm.nd.array(x.astype(dtype)))
+        m.set_input(**params)
+        m.run()
+        # get outputs
+        out = m.get_output(0, tvm.nd.empty(out_shape, dtype))
+        return out.asnumpy()
+
+    dtype = 'float32'
+    x = np.random.uniform(size=data_shape)
+    graph = onnx.load(graph_file)
+    c2_out = get_caffe2_output(graph, x, dtype)
+    for target, ctx in ctx_list():
+        tvm_out = get_tvm_output(graph, x, target, ctx, dtype)
+        np.testing.assert_allclose(c2_out, tvm_out, rtol=1e-5, atol=1e-5)
+
+def test_super_resolution_example():
+    test_onnx_forward_impl(super_resolution[0], (1, 1, 224, 224), (1, 1, 672, 672))
+
+if __name__ == '__main__':
+    test_super_resolution_example()
diff --git a/nnvm/tests/python/frontend/onnx/test_graph.py b/nnvm/tests/python/frontend/onnx/test_graph.py
new file mode 100644
index 000000000000..52eff24fa233
--- /dev/null
+++ b/nnvm/tests/python/frontend/onnx/test_graph.py
@@ -0,0 +1,24 @@
+"""Test graph equality of onnx models."""
+import nnvm
+import onnx
+from nnvm.compiler import graph_util, graph_attr
+from model_zoo import super_resolution
+
+def compare_graph(onnx_file, nnvm_sym, ishape):
+    onnx_graph = onnx.load(onnx_file)
+    onnx_sym, params = nnvm.frontend.from_onnx(onnx_graph)
+    g1 = nnvm.graph.create(onnx_sym)
+    g2 = nnvm.graph.create(nnvm_sym)
+    ishapes = {'input_0': ishape}
+    graph_attr.set_shape_inputs(g1, ishapes)
+    graph_attr.set_shape_inputs(g2, ishapes)
+    g1 = g1.apply("InferShape").apply("SimplifyInference")
+    g2 = g2.apply("InferShape").apply("SimplifyInference")
+    graph_util.check_graph_equal(g1, g2)
+
+def test_super_resolution_example():
+    fname, symbol = super_resolution
+    compare_graph(fname, symbol, ishape=(1, 1, 224, 224))
+
+if __name__ == '__main__':
+    test_super_resolution_example()

From c503e0896ec0cc97b4a304204695b18383317999 Mon Sep 17 00:00:00 2001
From: Leyuan Wang <laurawly@gmail.com>
Date: Tue, 26 Sep 2017 09:11:00 -0700
Subject: [PATCH 500/949] Merge with pull request #44 (#46)

* resnet example merged to imagenet

* merge with master
---
 nnvm/python/nnvm/testing/resnet.py               | 16 ++++++++++------
 ...nference_gpu.py => imagenet_inference_gpu.py} |  9 ++++++---
 2 files changed, 16 insertions(+), 9 deletions(-)
 rename nnvm/tutorials/{mobilenet_inference_gpu.py => imagenet_inference_gpu.py} (88%)

diff --git a/nnvm/python/nnvm/testing/resnet.py b/nnvm/python/nnvm/testing/resnet.py
index 67b2c3efc017..0e9c81232138 100644
--- a/nnvm/python/nnvm/testing/resnet.py
+++ b/nnvm/python/nnvm/testing/resnet.py
@@ -40,7 +40,8 @@ def residual_unit(data, num_filter, stride, dim_match, name, bottle_neck=True):
     stride : tuple
         Stride used in convolution
     dim_match : Boolean
-        True means channel number between input and output is the same, otherwise means differ
+        True means channel number between input and output is the same,
+        otherwise means differ
     name : str
         Base name of the operators
     """
@@ -146,7 +147,7 @@ def resnet(units, num_stages, filter_list, num_classes, image_shape,
         fc1 = sym.cast(data=fc1, dtype=np.float32)
     return sym.softmax(data=fc1, name='softmax')
 
-def get_symbol(num_classes, num_layers=50, image_shape=(3, 224, 224), dtype='float32'):
+def get_symbol(num_classes, num_layers=50, image_shape=(3, 224, 224), dtype='float32', **kwargs):
     """
     Adapted from https://github.com/tornadomeet/ResNet/blob/master/train_resnet.py
     Original author Wei Wu
@@ -198,8 +199,8 @@ def get_symbol(num_classes, num_layers=50, image_shape=(3, 224, 224), dtype='flo
                   bottle_neck=bottle_neck,
                   dtype=dtype)
 
-def get_workload(batch_size, num_classes=1000, image_shape=(3, 224, 224),
-                 dtype="float32", **kwargs):
+def get_workload(batch_size=1, num_classes=1000, num_layers=18,
+                 image_shape=(3, 224, 224), dtype="float32", **kwargs):
     """Get benchmark workload for resnet
 
     Parameters
@@ -210,6 +211,9 @@ def get_workload(batch_size, num_classes=1000, image_shape=(3, 224, 224),
     num_classes : int, optional
         Number of claseses
 
+    num_layers : int, optional
+        Number of layers
+
     image_shape : tuple, optional
         The input image shape
 
@@ -227,6 +231,6 @@ def get_workload(batch_size, num_classes=1000, image_shape=(3, 224, 224),
     params : dict of str to NDArray
         The parameters.
     """
-    net = get_symbol(num_classes=num_classes, image_shape=image_shape,
-                     dtype=dtype, **kwargs)
+    net = get_symbol(num_classes=num_classes, num_layers=num_layers,
+                     image_shape=image_shape, dtype=dtype, **kwargs)
     return create_workload(net, batch_size, image_shape, dtype)
diff --git a/nnvm/tutorials/mobilenet_inference_gpu.py b/nnvm/tutorials/imagenet_inference_gpu.py
similarity index 88%
rename from nnvm/tutorials/mobilenet_inference_gpu.py
rename to nnvm/tutorials/imagenet_inference_gpu.py
index e477e758cf91..98f0e94c6b5f 100644
--- a/nnvm/tutorials/mobilenet_inference_gpu.py
+++ b/nnvm/tutorials/imagenet_inference_gpu.py
@@ -1,9 +1,9 @@
 """
-Compile MobileNet Inference on GPU
+Compile ImageNet Inference on GPU
 ==================================
 **Author**: `Yuwei Hu <https://huyuwei.github.io/>`_
 
-This is an example of using NNVM to compile MobileNet model and deploy its inference on GPU.
+This is an example of using NNVM to compile MobileNet/ResNet model and deploy its inference on GPU.
 
 To begin with, we import nnvm(for compilation) and TVM(for deployment).
 """
@@ -39,7 +39,7 @@ def tvm_callback_cuda_compile(code):
 # .. note::
 #
 #   In a typical workflow, we can get this pair from :any:`nnvm.frontend`
-#
+# Example: /nnvm-top/tests/python/frontend/mxnet/test_forward.py  
 target = "cuda"
 ctx = tvm.gpu(0)
 batch_size = 1
@@ -47,6 +47,9 @@ def tvm_callback_cuda_compile(code):
 image_shape = (3, 224, 224)
 data_shape = (batch_size,) + image_shape
 out_shape = (batch_size, num_classes)
+# To use ResNet to do inference, run the following instead
+#net, params = nnvm.testing.resnet.get_workload(
+#    batch_size=1, image_shape=image_shape)
 net, params = nnvm.testing.mobilenet.get_workload(
     batch_size=1, image_shape=image_shape)
 

From 3b47db7cfc3237df749c7f36edd7d0d2ba0e6823 Mon Sep 17 00:00:00 2001
From: "Joshua Z. Zhang" <cheungchih@gmail.com>
Date: Tue, 26 Sep 2017 21:18:35 -0700
Subject: [PATCH 501/949] [Tutorial] mxnet (#47)

* [Tutorial] mxnet

update

add from_gluon

add to __init__

fix tutorial and from_gluon

fix doc lint

merge from_mxnet

fix

fix

fix tutorial

fix

fix header

* fix tutorial

* fix data

* fix
---
 nnvm/python/nnvm/frontend/mxnet.py |  46 +++++++-----
 nnvm/python/nnvm/testing/resnet.py |   1 +
 nnvm/tutorials/from_mxnet.py       | 114 +++++++++++++++++++++++++++++
 3 files changed, 143 insertions(+), 18 deletions(-)
 create mode 100644 nnvm/tutorials/from_mxnet.py

diff --git a/nnvm/python/nnvm/frontend/mxnet.py b/nnvm/python/nnvm/frontend/mxnet.py
index 16419e737568..168d3c67b499 100644
--- a/nnvm/python/nnvm/frontend/mxnet.py
+++ b/nnvm/python/nnvm/frontend/mxnet.py
@@ -256,14 +256,6 @@ def _from_mxnet_impl(symbol, graph):
     nnvm.sym.Symbol
         Converted symbol
     """
-    try:
-        from mxnet import sym as mx_sym  # pylint: disable=import-self
-    except ImportError as e:
-        raise ImportError('{}. MXNet is required to parse symbols.'.format(e))
-
-    if not isinstance(symbol, mx_sym.Symbol):
-        raise ValueError("Provided {}, while MXNet symbol is expected", type(symbol))
-
     if _is_mxnet_group_symbol(symbol):
         return [_from_mxnet_impl(s, graph) for s in symbol]
 
@@ -294,7 +286,7 @@ def from_mxnet(symbol, arg_params=None, aux_params=None):
 
     Parameters
     ----------
-    symbol : mxnet.Symbol
+    symbol : mxnet.Symbol or mxnet.gluon.HybridBlock
         MXNet symbol
 
     arg_params : dict of str to mx.NDArray
@@ -305,18 +297,36 @@ def from_mxnet(symbol, arg_params=None, aux_params=None):
 
     Returns
     -------
-    net: nnvm.Symbol
+    sym : nnvm.Symbol
         Compatible nnvm symbol
 
     params : dict of str to tvm.NDArray
         The parameter dict to be used by nnvm
     """
-    sym = _from_mxnet_impl(symbol, {})
-    params = {}
-    arg_params = arg_params if arg_params else {}
-    aux_params = aux_params if aux_params else {}
-    for k, v in arg_params.items():
-        params[k] = tvm.nd.array(v.asnumpy())
-    for k, v in aux_params.items():
-        params[k] = tvm.nd.array(v.asnumpy())
+    try:
+        import mxnet as mx  # pylint: disable=import-self
+    except ImportError as e:
+        raise ImportError('{}. MXNet is required to parse symbols.'.format(e))
+
+    if isinstance(symbol, mx.sym.Symbol):
+        sym = _from_mxnet_impl(symbol, {})
+        params = {}
+        arg_params = arg_params if arg_params else {}
+        aux_params = aux_params if aux_params else {}
+        for k, v in arg_params.items():
+            params[k] = tvm.nd.array(v.asnumpy())
+        for k, v in aux_params.items():
+            params[k] = tvm.nd.array(v.asnumpy())
+    elif isinstance(symbol, mx.gluon.HybridBlock):
+        data = mx.sym.Variable('data')
+        sym = symbol(data)
+        sym = _from_mxnet_impl(sym, {})
+        params = {}
+        for k, v in symbol.collect_params().items():
+            params[k] = tvm.nd.array(v.data().asnumpy())
+    elif isinstance(symbol, mx.gluon.Block):
+        raise NotImplementedError("The dynamic Block is not supported yet.")
+    else:
+        msg = "mxnet.Symbol or gluon.HybridBlock expected, got {}".format(type(symbol))
+        raise ValueError(msg)
     return sym, params
diff --git a/nnvm/python/nnvm/testing/resnet.py b/nnvm/python/nnvm/testing/resnet.py
index 0e9c81232138..76b5c1d893b8 100644
--- a/nnvm/python/nnvm/testing/resnet.py
+++ b/nnvm/python/nnvm/testing/resnet.py
@@ -23,6 +23,7 @@
 
 Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun. "Identity Mappings in Deep Residual Networks"
 '''
+# pylint: disable=unused-argument
 import numpy as np
 from .. import symbol as sym
 from . utils import create_workload
diff --git a/nnvm/tutorials/from_mxnet.py b/nnvm/tutorials/from_mxnet.py
new file mode 100644
index 000000000000..0b6b70ef2d09
--- /dev/null
+++ b/nnvm/tutorials/from_mxnet.py
@@ -0,0 +1,114 @@
+"""
+Compiling MXNet Models with NNVM
+================================
+**Author**: `Joshua Z. Zhang <https://zhreshold.github.io/>`_
+
+This article is an introductory tutorial to deploy mxnet models with NNVM.
+
+For us to begin with, mxnet module is required to be installed.
+
+A quick solution is
+```
+pip install mxnet --user
+```
+or please refer to offical installation guide.
+https://mxnet.incubator.apache.org/versions/master/install/index.html
+"""
+# some standard imports
+import mxnet as mx
+import nnvm
+import tvm
+import numpy as np
+
+######################################################################
+# Download Resnet18 model from Gluon Model Zoo
+# ---------------------------------------------
+# In this section, we download a pretrained imagenet model and classify an image.
+from mxnet.gluon.model_zoo.vision import get_model
+from mxnet.gluon.utils import download
+import Image
+from matplotlib import pyplot as plt
+block = get_model('resnet18_v1', pretrained=True)
+img_name = 'cat.jpg'
+synset_url = ''.join(['https://gist.githubusercontent.com/zhreshold/',
+                      '4d0b62f3d01426887599d4f7ede23ee5/raw/',
+                      '596b27d23537e5a1b5751d2b0481ef172f58b539/',
+                      'imagenet1000_clsid_to_human.txt'])
+synset_name = 'synset.txt'
+download('https://github.com/dmlc/mxnet.js/blob/master/data/cat.png?raw=true', img_name)
+download(synset_url, synset_name)
+with open(synset_name) as f:
+    synset = eval(f.read())
+image = Image.open(img_name).resize((224, 224))
+plt.imshow(image)
+plt.show()
+
+def transform_image(image):
+    image = np.array(image) - np.array([123., 117., 104.])
+    image /= np.array([58.395, 57.12, 57.375])
+    image = image.transpose((2, 0, 1))
+    image = image[np.newaxis, :]
+    return image
+
+x = transform_image(image)
+print('x', x.shape)
+
+######################################################################
+# Compile the Graph
+# -----------------
+# Now we would like to port the Gluon model to a portable computational graph.
+# It's as easy as several lines.
+# We support MXNet static graph(symbol) and HybridBlock in mxnet.gluon
+sym, params = nnvm.frontend.from_mxnet(block)
+# we want a probability so add a softmax operator
+sym = nnvm.sym.softmax(sym)
+
+######################################################################
+# now compile the graph
+import nnvm.compiler
+target = 'cuda'
+shape_dict = {'data': x.shape}
+graph, lib, params = nnvm.compiler.build(sym, target, shape_dict, params=params)
+
+######################################################################
+# Execute the portable graph on TVM
+# ---------------------------------
+# Now, we would like to reproduce the same forward computation using TVM.
+from tvm.contrib import graph_runtime
+ctx = tvm.gpu(0)
+dtype = 'float32'
+m = graph_runtime.create(graph, lib, ctx)
+# set inputs
+m.set_input('data', tvm.nd.array(x.astype(dtype)))
+m.set_input(**params)
+# execute
+m.run()
+# get outputs
+tvm_output = m.get_output(0, tvm.nd.empty((1000,), dtype))
+top1 = np.argmax(tvm_output)
+print('TVM prediction top-1:', top1, synset[top1])
+
+######################################################################
+# Use MXNet symbol with pretrained weights
+# ----------------------------------------
+# MXNet often use `arg_prams` and `aux_params` to store network parameters
+# separately, here we show how to use these weights with existing API
+def block2symbol(block):
+    data = mx.sym.Variable('data')
+    sym = block(data)
+    args = {}
+    auxs = {}
+    for k, v in block.collect_params().items():
+        args[k] = mx.nd.array(v.data().asnumpy())
+    return sym, args, auxs
+mx_sym, args, auxs = block2symbol(block)
+# usually we would save/load it as checkpoint
+mx.model.save_checkpoint('resnet18_v1', 0, mx_sym, args, auxs)
+# there are 'resnet18_v1-0000.params' and 'resnet18_v1-symbol.json' on disk
+
+######################################################################
+# for a normal mxnet model, we start from here
+mx_sym, args, auxs = mx.model.load_checkpoint('resnet18_v1', 0)
+# now we use the same API to get NNVM compatible symbol
+nnvm_sym, nnvm_params = nnvm.frontend.from_mxnet(mx_sym, args, auxs)
+# repeat the same steps to run this model using TVM

From e04779b8d508c33375866279e324efbdffc75b25 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 26 Sep 2017 22:24:37 -0700
Subject: [PATCH 502/949] [TEST] Checkin test docker and scripts (#48)

---
 nnvm/Jenkinsfile                              | 126 ++++++++++++++++++
 nnvm/Makefile                                 |   6 +-
 nnvm/include/nnvm/compiler/op_attr_types.h    |   2 +-
 nnvm/include/nnvm/op_attr_types.h             |   2 +-
 nnvm/python/nnvm/_base.py                     |   4 +-
 nnvm/python/nnvm/symbol.py                    |  13 +-
 nnvm/python/setup.py                          |   3 +
 nnvm/tests/ci_build/Dockerfile.gpu            |  44 ++++++
 nnvm/tests/ci_build/Dockerfile.lint           |   6 +
 nnvm/tests/ci_build/README.md                 |  36 +++++
 nnvm/tests/ci_build/ci_build.sh               | 126 ++++++++++++++++++
 .../ci_build/install/ubuntu_install_core.sh   |   6 +
 .../ci_build/install/ubuntu_install_llvm.sh   |  17 +++
 .../ci_build/install/ubuntu_install_mxnet.sh  |   2 +
 .../ci_build/install/ubuntu_install_onnx.sh   |   7 +
 .../ci_build/install/ubuntu_install_opencl.sh |  11 ++
 .../ci_build/install/ubuntu_install_python.sh |   5 +
 .../install/ubuntu_install_python_package.sh  |   3 +
 .../ci_build/install/ubuntu_install_sphinx.sh |   1 +
 nnvm/tests/ci_build/with_the_same_user        |  34 +++++
 .../python/frontend/mxnet/test_forward.py     |   9 +-
 .../python/frontend/onnx/test_forward.py      |  10 +-
 nnvm/tests/scripts/task_build.sh              |  16 +++
 nnvm/tests/scripts/task_clean.sh              |   6 +
 nnvm/tests/scripts/task_frontend_test.sh      |   9 ++
 nnvm/tests/scripts/task_lint.sh               |  15 +++
 nnvm/tests/scripts/task_python_docs.sh        |  12 ++
 nnvm/tests/scripts/task_python_test.sh        |  11 ++
 nnvm/tutorials/imagenet_inference_gpu.py      |   2 +-
 29 files changed, 524 insertions(+), 20 deletions(-)
 create mode 100644 nnvm/Jenkinsfile
 create mode 100644 nnvm/tests/ci_build/Dockerfile.gpu
 create mode 100644 nnvm/tests/ci_build/Dockerfile.lint
 create mode 100644 nnvm/tests/ci_build/README.md
 create mode 100755 nnvm/tests/ci_build/ci_build.sh
 create mode 100644 nnvm/tests/ci_build/install/ubuntu_install_core.sh
 create mode 100644 nnvm/tests/ci_build/install/ubuntu_install_llvm.sh
 create mode 100644 nnvm/tests/ci_build/install/ubuntu_install_mxnet.sh
 create mode 100644 nnvm/tests/ci_build/install/ubuntu_install_onnx.sh
 create mode 100644 nnvm/tests/ci_build/install/ubuntu_install_opencl.sh
 create mode 100644 nnvm/tests/ci_build/install/ubuntu_install_python.sh
 create mode 100644 nnvm/tests/ci_build/install/ubuntu_install_python_package.sh
 create mode 100644 nnvm/tests/ci_build/install/ubuntu_install_sphinx.sh
 create mode 100644 nnvm/tests/ci_build/with_the_same_user
 create mode 100755 nnvm/tests/scripts/task_build.sh
 create mode 100755 nnvm/tests/scripts/task_clean.sh
 create mode 100755 nnvm/tests/scripts/task_frontend_test.sh
 create mode 100755 nnvm/tests/scripts/task_lint.sh
 create mode 100755 nnvm/tests/scripts/task_python_docs.sh
 create mode 100755 nnvm/tests/scripts/task_python_test.sh

diff --git a/nnvm/Jenkinsfile b/nnvm/Jenkinsfile
new file mode 100644
index 000000000000..de3cfd3021af
--- /dev/null
+++ b/nnvm/Jenkinsfile
@@ -0,0 +1,126 @@
+#!groovy
+// -*- mode: groovy -*-
+// Jenkins pipeline
+// See documents at https://jenkins.io/doc/book/pipeline/jenkinsfile/
+
+// nnvm libraries
+nnvm_lib = "tvm/lib/libtvm.so, tvm/lib/libtvm_runtime.so, lib/libnnvm_top.so, config.mk"
+
+// command to start a docker container
+docker_run = 'tests/ci_build/ci_build.sh'
+// timeout in minutes
+max_time = 60
+
+// initialize source codes
+def init_git() {
+  checkout scm
+  retry(5) {
+    timeout(time: 2, unit: 'MINUTES') {
+      sh 'git submodule update --init --recursive'
+    }
+  }
+}
+
+def init_git_win() {
+    checkout scm
+    retry(5) {
+        timeout(time: 2, unit: 'MINUTES') {
+            bat 'git submodule update --init --recursive'
+        }
+    }
+}
+
+stage("Sanity Check") {
+  timeout(time: max_time, unit: 'MINUTES') {
+    node('linux') {
+      ws('workspace/tvm/sanity') {
+        init_git()
+        sh "${docker_run} lint  ./tests/scripts/task_lint.sh"
+      }
+    }
+  }
+}
+
+// Run make. First try to do an incremental make from a previous workspace in hope to
+// accelerate the compilation. If something wrong, clean the workspace and then
+// build from scratch.
+def make(docker_type, make_flag) {
+  timeout(time: max_time, unit: 'MINUTES') {
+    try {
+      sh "${docker_run} ${docker_type} ./tests/script/task_build.sh ${make_flag}"
+    } catch (exc) {
+      echo 'Incremental compilation failed. Fall back to build from scratch'
+      sh "${docker_run} ${docker_type} ./tests/script/task_clean.sh"
+      sh "${docker_run} ${docker_type} ./tests/script/task_build.sh ${make_flag}"
+    }
+  }
+}
+
+// pack libraries for later use
+def pack_lib(name, libs) {
+  sh """
+     echo "Packing ${libs} into ${name}"
+     echo ${libs} | sed -e 's/,/ /g' | xargs md5sum
+     """
+  stash includes: libs, name: name
+}
+
+
+// unpack libraries saved before
+def unpack_lib(name, libs) {
+  unstash name
+  sh """
+     echo "Unpacked ${libs} from ${name}"
+     echo ${libs} | sed -e 's/,/ /g' | xargs md5sum
+     """
+}
+
+stage('Build') {
+  timeout(time: max_time, unit: 'MINUTES') {
+    node('GPU' && 'linux') {
+      ws('workspace/nnvm/build-gpu') {
+        init_git()
+        make('gpu', '-j2')
+        pack_lib('gpu', nnvm_lib)
+      }
+    }
+  }
+}
+
+stage('Tests') {
+  parallel 'python': {
+    node('GPU' && 'linux') {
+      ws('workspace/nnvm/it-python-gpu') {
+        init_git()
+        unpack_lib('gpu', nnvm_lib)
+        timeout(time: max_time, unit: 'MINUTES') {
+          sh "${docker_run} gpu ./tests/scripts/task_python_test.sh"
+          sh "${docker_run} gpu ./tests/scripts/task_frontend_test.sh"
+        }
+      }
+    }
+  },
+  'docs': {
+    node('GPU' && 'linux') {
+      ws('workspace/nnvm/docs-python-gpu') {
+        init_git()
+        unpack_lib('gpu', nnvm_lib)
+        timeout(time: max_time, unit: 'MINUTES') {
+          sh "${docker_run} gpu ./tests/scripts/task_python_docs.sh"
+        }
+        pack_lib('mydocs', 'docs.tgz')
+      }
+    }
+  }
+}
+
+stage('Deploy') {
+    node('docker' && 'doc') {
+      ws('workspace/nnvm/deploy-docs') {
+        if (env.BRANCH_NAME == "master") {
+           unpack_lib('mydocs', 'docs.tgz')
+           sh "tar xf docs.tgz -C /var/docs"
+        }
+      }
+    }
+}
diff --git a/nnvm/Makefile b/nnvm/Makefile
index 515856c5f078..24a23b054349 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -38,7 +38,7 @@ PLUGIN_OBJ =
 include $(NNVM_PLUGINS)
 
 # specify tensor path
-.PHONY: clean all test lint pylint doc cython cython3 cyclean
+.PHONY: clean all test lint cpplint pylint doc cython cython3 cyclean
 
 UNAME_S := $(shell uname -s)
 
@@ -87,7 +87,9 @@ cython3:
 cyclean:
 	rm -rf python/nnvm/*/*.so python/nnvm/*/*.dylib python/nnvm/*/*.cpp
 
-lint: pylint
+lint: pylint cpplint
+
+cpplint:
 	python dmlc-core/scripts/lint.py nnvm cpp include src
 
 pylint:
diff --git a/nnvm/include/nnvm/compiler/op_attr_types.h b/nnvm/include/nnvm/compiler/op_attr_types.h
index 9b9fbed4455f..f8d32320ac00 100644
--- a/nnvm/include/nnvm/compiler/op_attr_types.h
+++ b/nnvm/include/nnvm/compiler/op_attr_types.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2017 by Contributors
- * \file op_attr_types.h
+ * \file nnvm/compiler/op_attr_types.h
  * \brief The Expr and related elements in DataFlow construction.
  */
 #ifndef NNVM_COMPILER_OP_ATTR_TYPES_H_
diff --git a/nnvm/include/nnvm/op_attr_types.h b/nnvm/include/nnvm/op_attr_types.h
index 25c8c6395bab..e7bbb9685f6d 100644
--- a/nnvm/include/nnvm/op_attr_types.h
+++ b/nnvm/include/nnvm/op_attr_types.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file op_attr_types.h
+ * \file nnvm/op_attr_types.h
  * \brief Data structures that can appear in operator attributes.
  */
 #ifndef NNVM_OP_ATTR_TYPES_H_
diff --git a/nnvm/python/nnvm/_base.py b/nnvm/python/nnvm/_base.py
index 225b0844c4f9..1c25f4cfe551 100644
--- a/nnvm/python/nnvm/_base.py
+++ b/nnvm/python/nnvm/_base.py
@@ -3,6 +3,7 @@
 """ ctypes library of nnvm and helper functions """
 from __future__ import absolute_import
 
+import os
 import sys
 import ctypes
 import numpy as np
@@ -44,7 +45,8 @@ def _load_lib():
 __version__ = libinfo.__version__
 # library instance of nnvm
 _LIB = _load_lib()
-
+# The FFI mode of TVM
+_FFI_MODE = os.environ.get("TVM_FFI", "auto")
 
 # type definitions
 nn_uint = ctypes.c_uint
diff --git a/nnvm/python/nnvm/symbol.py b/nnvm/python/nnvm/symbol.py
index 888623403772..bca63a3656f2 100644
--- a/nnvm/python/nnvm/symbol.py
+++ b/nnvm/python/nnvm/symbol.py
@@ -11,21 +11,24 @@
 
 from numbers import Number as _Number
 from . import _base
-from ._base import _LIB, check_call as _check_call
+from ._base import _LIB, check_call as _check_call, _FFI_MODE
 from .attribute import AttrScope
 from . import _symbol_internal as _internal
 
 # Use different verison of SymbolBase
 # When possible, use cython to speedup part of computation.
 
+IMPORT_EXCEPT = RuntimeError if _FFI_MODE == "cython" else ImportError
+
 try:
-    if int(_os.environ.get("MXNET_ENABLE_CYTHON", True)) == 0:
-        from ._ctypes.symbol import SymbolBase, _init_symbol_module
-    elif _sys.version_info >= (3, 0):
+    if _FFI_MODE == "ctypes":
+        raise ImportError()
+    if _sys.version_info >= (3, 0):
         from ._cy3.symbol import SymbolBase, _init_symbol_module
     else:
         from ._cy2.symbol import SymbolBase, _init_symbol_module
-except ImportError:
+except IMPORT_EXCEPT:
+    # pylint: disable=wrong-import-position
     from ._ctypes.symbol import SymbolBase, _init_symbol_module
 
 
diff --git a/nnvm/python/setup.py b/nnvm/python/setup.py
index 74ada0c6b83a..5f5b3cada78d 100644
--- a/nnvm/python/setup.py
+++ b/nnvm/python/setup.py
@@ -3,6 +3,9 @@
 from distutils.core import setup
 
 def config_cython():
+    # temporary disable cython for now
+    # as NNVM uses local DLL build
+    return []
     try:
         from Cython.Build import cythonize
         from distutils.extension import Extension
diff --git a/nnvm/tests/ci_build/Dockerfile.gpu b/nnvm/tests/ci_build/Dockerfile.gpu
new file mode 100644
index 000000000000..b7cda20f169d
--- /dev/null
+++ b/nnvm/tests/ci_build/Dockerfile.gpu
@@ -0,0 +1,44 @@
+FROM nvidia/cuda:8.0-cudnn7-devel
+
+# Base scripts
+RUN apt-get update --fix-missing
+
+COPY install/ubuntu_install_core.sh /install/ubuntu_install_core.sh
+RUN bash /install/ubuntu_install_core.sh
+
+COPY install/ubuntu_install_python.sh /install/ubuntu_install_python.sh
+RUN bash /install/ubuntu_install_python.sh
+
+COPY install/ubuntu_install_llvm.sh /install/ubuntu_install_llvm.sh
+RUN bash /install/ubuntu_install_llvm.sh
+
+COPY install/ubuntu_install_opencl.sh /install/ubuntu_install_opencl.sh
+RUN bash /install/ubuntu_install_opencl.sh
+
+COPY install/ubuntu_install_python_package.sh /install/ubuntu_install_python_package.sh
+RUN bash /install/ubuntu_install_python_package.sh
+
+COPY install/ubuntu_install_sphinx.sh /install/ubuntu_install_sphinx.sh
+RUN bash /install/ubuntu_install_sphinx.sh
+
+# Fix recommonmark to latest version
+RUN git clone https://github.com/rtfd/recommonmark
+RUN cd recommonmark; python setup.py install
+
+# Enable doxygen for c++ doc build
+RUN apt-get update && apt-get install -y doxygen graphviz libprotobuf-dev protobuf-compiler
+
+# DL Frameworks
+COPY install/ubuntu_install_mxnet.sh /install/ubuntu_install_mxnet.sh
+RUN bash /install/ubuntu_install_mxnet.sh
+
+COPY install/ubuntu_install_onnx.sh /install/ubuntu_install_onnx.sh
+RUN bash /install/ubuntu_install_onnx.sh
+
+# Environment variables
+ENV PATH=/usr/local/nvidia/bin:${PATH}
+ENV PATH=/usr/local/cuda/bin:${PATH}
+ENV CPLUS_INCLUDE_PATH=/usr/local/cuda/include:${CPLUS_INCLUDE_PATH}
+ENV C_INCLUDE_PATH=/usr/local/cuda/include:${C_INCLUDE_PATH}
+ENV LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/nvidia/lib64:${LIBRARY_PATH}
+ENV LD_LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/nvidia/lib64:${LD_LIBRARY_PATH}
diff --git a/nnvm/tests/ci_build/Dockerfile.lint b/nnvm/tests/ci_build/Dockerfile.lint
new file mode 100644
index 000000000000..4ba4ca3be294
--- /dev/null
+++ b/nnvm/tests/ci_build/Dockerfile.lint
@@ -0,0 +1,6 @@
+# For lint test
+FROM ubuntu:16.04
+
+RUN apt-get update && apt-get install -y python-pip sudo
+RUN apt-get install -y doxygen graphviz
+RUN pip install cpplint pylint
diff --git a/nnvm/tests/ci_build/README.md b/nnvm/tests/ci_build/README.md
new file mode 100644
index 000000000000..68f0c2d0e96a
--- /dev/null
+++ b/nnvm/tests/ci_build/README.md
@@ -0,0 +1,36 @@
+# CI Build Scripts
+
+This directory contains the files and setup instructions to run all tests.
+
+## Run locally
+
+To run locally, we need to first install
+[docker](https://docs.docker.com/engine/installation/) and
+[nvidia-docker](https://github.com/NVIDIA/nvidia-docker/wiki).
+
+Then we can run the tasks defined in the [Jenkinsfile](../../Jenkinsfile) by
+using (`ci_build.sh`)[./ci_build.sh]. For example
+
+- lint the python codes
+
+  ```bash
+  ./ci_build.sh lint make pylint
+  ```
+
+- build codes with CUDA supports
+
+  ```bash
+  ./ci_build.sh gpu tests/scripts/task_build.sh
+  ```
+
+- do the python unittest
+
+  ```bash
+  ./ci_build.sh gpu tests/scripts/task_python_test.sh
+  ```
+
+- build the documents. The results will be available at `docs/_build/html`
+
+  ```bash
+  tests/ci_build/ci_build.sh gpu tests/scripts/task_python_docs.sh
+  ```
diff --git a/nnvm/tests/ci_build/ci_build.sh b/nnvm/tests/ci_build/ci_build.sh
new file mode 100755
index 000000000000..db4283ca9753
--- /dev/null
+++ b/nnvm/tests/ci_build/ci_build.sh
@@ -0,0 +1,126 @@
+#!/usr/bin/env bash
+#
+# Execute command within a docker container
+#
+# Usage: ci_build.sh <CONTAINER_TYPE> [--dockerfile <DOCKERFILE_PATH>] [-it]
+#                    <COMMAND>
+#
+# CONTAINER_TYPE: Type of the docker container used the run the build: e.g.,
+#                 (cpu | gpu)
+#
+# DOCKERFILE_PATH: (Optional) Path to the Dockerfile used for docker build.  If
+#                  this optional value is not supplied (via the --dockerfile
+#                  flag), will use Dockerfile.CONTAINER_TYPE in default
+#
+# COMMAND: Command to be executed in the docker container
+#
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+# Get the command line arguments.
+CONTAINER_TYPE=$( echo "$1" | tr '[:upper:]' '[:lower:]' )
+shift 1
+
+# Dockerfile to be used in docker build
+DOCKERFILE_PATH="${SCRIPT_DIR}/Dockerfile.${CONTAINER_TYPE}"
+DOCKER_CONTEXT_PATH="${SCRIPT_DIR}"
+
+if [[ "$1" == "--dockerfile" ]]; then
+    DOCKERFILE_PATH="$2"
+    DOCKER_CONTEXT_PATH=$(dirname "${DOCKERFILE_PATH}")
+    echo "Using custom Dockerfile path: ${DOCKERFILE_PATH}"
+    echo "Using custom docker build context path: ${DOCKER_CONTEXT_PATH}"
+    shift 2
+fi
+
+if [[ "$1" == "-it" ]]; then
+    CI_DOCKER_EXTRA_PARAMS+=('-it')
+    shift 1
+fi
+
+if [[ ! -f "${DOCKERFILE_PATH}" ]]; then
+    echo "Invalid Dockerfile path: \"${DOCKERFILE_PATH}\""
+    exit 1
+fi
+
+COMMAND=("$@")
+
+# Validate command line arguments.
+if [ "$#" -lt 1 ] || [ ! -e "${SCRIPT_DIR}/Dockerfile.${CONTAINER_TYPE}" ]; then
+    supported_container_types=$( ls -1 ${SCRIPT_DIR}/Dockerfile.* | \
+        sed -n 's/.*Dockerfile\.\([^\/]*\)/\1/p' | tr '\n' ' ' )
+      echo "Usage: $(basename $0) CONTAINER_TYPE COMMAND"
+      echo "       CONTAINER_TYPE can be one of [${supported_container_types}]"
+      echo "       COMMAND is a command (with arguments) to run inside"
+      echo "               the container."
+      exit 1
+fi
+
+# Use nvidia-docker if the container is GPU.
+if [[ "${CONTAINER_TYPE}" == *"gpu"* ]]; then
+    DOCKER_BINARY="nvidia-docker"
+else
+    DOCKER_BINARY="docker"
+fi
+
+# Helper function to traverse directories up until given file is found.
+function upsearch () {
+    test / == "$PWD" && return || \
+        test -e "$1" && echo "$PWD" && return || \
+        cd .. && upsearch "$1"
+}
+
+# Set up WORKSPACE and BUILD_TAG. Jenkins will set them for you or we pick
+# reasonable defaults if you run it outside of Jenkins.
+WORKSPACE="${WORKSPACE:-${SCRIPT_DIR}/../../}"
+BUILD_TAG="${BUILD_TAG:-nnvm-ci}"
+
+# Determine the docker image name
+DOCKER_IMG_NAME="${BUILD_TAG}.${CONTAINER_TYPE}"
+
+# Under Jenkins matrix build, the build tag may contain characters such as
+# commas (,) and equal signs (=), which are not valid inside docker image names.
+DOCKER_IMG_NAME=$(echo "${DOCKER_IMG_NAME}" | sed -e 's/=/_/g' -e 's/,/-/g')
+
+# Convert to all lower-case, as per requirement of Docker image names
+DOCKER_IMG_NAME=$(echo "${DOCKER_IMG_NAME}" | tr '[:upper:]' '[:lower:]')
+
+# Print arguments.
+echo "WORKSPACE: ${WORKSPACE}"
+echo "CI_DOCKER_EXTRA_PARAMS: ${CI_DOCKER_EXTRA_PARAMS[@]}"
+echo "COMMAND: ${COMMAND[@]}"
+echo "CONTAINER_TYPE: ${CONTAINER_TYPE}"
+echo "BUILD_TAG: ${BUILD_TAG}"
+echo "DOCKER CONTAINER NAME: ${DOCKER_IMG_NAME}"
+echo ""
+
+
+# Build the docker container.
+echo "Building container (${DOCKER_IMG_NAME})..."
+docker build -t ${DOCKER_IMG_NAME} \
+    -f "${DOCKERFILE_PATH}" "${DOCKER_CONTEXT_PATH}"
+
+# Check docker build status
+if [[ $? != "0" ]]; then
+    echo "ERROR: docker build failed."
+    exit 1
+fi
+
+# Run the command inside the container.
+echo "Running '${COMMAND[@]}' inside ${DOCKER_IMG_NAME}..."
+
+# By default we cleanup - remove the container once it finish running (--rm)
+# and share the PID namespace (--pid=host) so the process inside does not have
+# pid 1 and SIGKILL is propagated to the process inside (jenkins can kill it).
+echo ${DOCKER_BINARY}
+${DOCKER_BINARY} run --rm --pid=host \
+    -v ${WORKSPACE}:/workspace \
+    -w /workspace \
+    -e "CI_BUILD_HOME=/workspace" \
+    -e "CI_BUILD_USER=$(id -u -n)" \
+    -e "CI_BUILD_UID=$(id -u)" \
+    -e "CI_BUILD_GROUP=$(id -g -n)" \
+    -e "CI_BUILD_GID=$(id -g)" \
+    ${CI_DOCKER_EXTRA_PARAMS[@]} \
+    ${DOCKER_IMG_NAME} \
+    bash tests/ci_build/with_the_same_user \
+    ${COMMAND[@]}
diff --git a/nnvm/tests/ci_build/install/ubuntu_install_core.sh b/nnvm/tests/ci_build/install/ubuntu_install_core.sh
new file mode 100644
index 000000000000..9823ae0788ac
--- /dev/null
+++ b/nnvm/tests/ci_build/install/ubuntu_install_core.sh
@@ -0,0 +1,6 @@
+# install libraries for building c++ core on ubuntu
+apt-get install -y --no-install-recommends --force-yes \
+        git make libgtest-dev cmake wget unzip libtinfo-dev libz-dev\
+        libcurl4-openssl-dev libopenblas-dev g++ sudo
+
+cd /usr/src/gtest && cmake CMakeLists.txt && make && cp *.a /usr/lib
diff --git a/nnvm/tests/ci_build/install/ubuntu_install_llvm.sh b/nnvm/tests/ci_build/install/ubuntu_install_llvm.sh
new file mode 100644
index 000000000000..e5b28b911f61
--- /dev/null
+++ b/nnvm/tests/ci_build/install/ubuntu_install_llvm.sh
@@ -0,0 +1,17 @@
+echo deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-4.0 main\
+     >> /etc/apt/sources.list.d/llvm.list
+echo deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial-4.0 main\
+     >> /etc/apt/sources.list.d/llvm.list
+
+echo deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-5.0 main\
+     >> /etc/apt/sources.list.d/llvm.list
+echo deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial-5.0 main\
+     >> /etc/apt/sources.list.d/llvm.list
+
+echo deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial main\
+     >> /etc/apt/sources.list.d/llvm.list
+echo deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial main\
+     >> /etc/apt/sources.list.d/llvm.list
+
+wget -O - http://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
+apt-get update && apt-get install -y --force-yes llvm-4.0 llvm-5.0 llvm-6.0
diff --git a/nnvm/tests/ci_build/install/ubuntu_install_mxnet.sh b/nnvm/tests/ci_build/install/ubuntu_install_mxnet.sh
new file mode 100644
index 000000000000..ebf641e2a910
--- /dev/null
+++ b/nnvm/tests/ci_build/install/ubuntu_install_mxnet.sh
@@ -0,0 +1,2 @@
+pip2 install mxnet
+pip3 install mxnet
diff --git a/nnvm/tests/ci_build/install/ubuntu_install_onnx.sh b/nnvm/tests/ci_build/install/ubuntu_install_onnx.sh
new file mode 100644
index 000000000000..ec4cae1be2d0
--- /dev/null
+++ b/nnvm/tests/ci_build/install/ubuntu_install_onnx.sh
@@ -0,0 +1,7 @@
+pip2 install onnx
+pip3 install onnx
+
+pip2 install http://download.pytorch.org/whl/cu75/torch-0.2.0.post3-cp27-cp27mu-manylinux1_x86_64.whl
+pip2 install torchvision
+pip3 install http://download.pytorch.org/whl/cu75/torch-0.2.0.post3-cp35-cp35m-manylinux1_x86_64.whl
+pip3 install torchvision
diff --git a/nnvm/tests/ci_build/install/ubuntu_install_opencl.sh b/nnvm/tests/ci_build/install/ubuntu_install_opencl.sh
new file mode 100644
index 000000000000..636236539a98
--- /dev/null
+++ b/nnvm/tests/ci_build/install/ubuntu_install_opencl.sh
@@ -0,0 +1,11 @@
+# Install OpenCL runtime in nvidia docker.
+apt-get install -y --no-install-recommends --force-yes \
+        ocl-icd-libopencl1 \
+        clinfo && \
+        rm -rf /var/lib/apt/lists/*
+
+mkdir -p /etc/OpenCL/vendors && \
+    echo "libnvidia-opencl.so.1" > /etc/OpenCL/vendors/nvidia.icd
+
+echo "/usr/local/nvidia/lib" >> /etc/ld.so.conf.d/nvidia.conf && \
+    echo "/usr/local/nvidia/lib64" >> /etc/ld.so.conf.d/nvidia.conf
diff --git a/nnvm/tests/ci_build/install/ubuntu_install_python.sh b/nnvm/tests/ci_build/install/ubuntu_install_python.sh
new file mode 100644
index 000000000000..c6f6f75c564e
--- /dev/null
+++ b/nnvm/tests/ci_build/install/ubuntu_install_python.sh
@@ -0,0 +1,5 @@
+# install python and pip, don't modify this, modify install_python_package.sh
+apt-get update && apt-get install -y python-pip python-dev python3-dev
+
+# the version of the pip shipped with ubuntu may be too lower, install a recent version here
+cd /tmp && wget https://bootstrap.pypa.io/get-pip.py && python3 get-pip.py && python2 get-pip.py
diff --git a/nnvm/tests/ci_build/install/ubuntu_install_python_package.sh b/nnvm/tests/ci_build/install/ubuntu_install_python_package.sh
new file mode 100644
index 000000000000..fbed2e1904cd
--- /dev/null
+++ b/nnvm/tests/ci_build/install/ubuntu_install_python_package.sh
@@ -0,0 +1,3 @@
+# install libraries for python package on ubuntu
+pip2 install nose pylint numpy nose-timer cython decorator scipy tornado
+pip3 install nose pylint numpy nose-timer cython decorator scipy tornado
diff --git a/nnvm/tests/ci_build/install/ubuntu_install_sphinx.sh b/nnvm/tests/ci_build/install/ubuntu_install_sphinx.sh
new file mode 100644
index 000000000000..767643f10488
--- /dev/null
+++ b/nnvm/tests/ci_build/install/ubuntu_install_sphinx.sh
@@ -0,0 +1 @@
+pip install sphinx==1.6.2 sphinx-gallery sphinx_rtd_theme matplotlib Image commonmark>=0.7.3 docutils>=0.11
diff --git a/nnvm/tests/ci_build/with_the_same_user b/nnvm/tests/ci_build/with_the_same_user
new file mode 100644
index 000000000000..1e6ab883694b
--- /dev/null
+++ b/nnvm/tests/ci_build/with_the_same_user
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+
+# This script is a wrapper creating the same user inside container as the one
+# running the ci_build.sh outside the container. It also set the home directory
+# for the user inside container to match the same absolute path as the workspace
+# outside of container.  Do not run this manually. It does not make sense. It is
+# intended to be called by ci_build.sh only.
+
+set -e
+
+COMMAND=("$@")
+
+if ! touch /this_is_writable_file_system; then
+  echo "You can't write to your filesystem!"
+  echo "If you are in Docker you should check you do not have too many images" \
+      "with too many files in them. Docker has some issue with it."
+  exit 1
+else
+  rm /this_is_writable_file_system
+fi
+
+getent group "${CI_BUILD_GID}" || addgroup --gid "${CI_BUILD_GID}" "${CI_BUILD_GROUP}"
+getent passwd "${CI_BUILD_UID}" || adduser --gid "${CI_BUILD_GID}" --uid "${CI_BUILD_UID}" \
+    --gecos "${CI_BUILD_USER} (generated by with_the_same_user script)" \
+    --disabled-password --home "${CI_BUILD_HOME}" --quiet "${CI_BUILD_USER}"
+usermod -a -G sudo "${CI_BUILD_USER}"
+echo "${CI_BUILD_USER} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/90-nopasswd-sudo
+
+HOME=${CI_BUILD_HOME}\
+    sudo -u "#${CI_BUILD_UID}" --preserve-env\
+    PATH=${PATH}\
+    LD_LIBRARY_PATH=${LD_LIBRARY_PATH}\
+    HOME=${CI_BUILD_HOME}\
+    ${COMMAND[@]}
diff --git a/nnvm/tests/python/frontend/mxnet/test_forward.py b/nnvm/tests/python/frontend/mxnet/test_forward.py
index 07acb74cfaa7..d19aa5664fdf 100644
--- a/nnvm/tests/python/frontend/mxnet/test_forward.py
+++ b/nnvm/tests/python/frontend/mxnet/test_forward.py
@@ -11,7 +11,8 @@
 import model_zoo
 
 
-def test_mxnet_frontend_impl(mx_symbol, data_shape=(1, 3, 224, 224), out_shape=(1, 1000)):
+def verify_mxnet_frontend_impl(mx_symbol, data_shape=(1, 3, 224, 224), out_shape=(1, 1000)):
+    """Use name different from test to avoid let nose pick it up"""
     def get_mxnet_output(symbol, x, dtype='float32'):
         from collections import namedtuple
         Batch = namedtuple('Batch', ['data'])
@@ -48,17 +49,17 @@ def get_tvm_output(symbol, x, args, auxs, target, ctx, dtype='float32'):
 
 def test_forward_mlp():
     mlp = model_zoo.mx_mlp
-    test_mxnet_frontend_impl(mlp)
+    verify_mxnet_frontend_impl(mlp)
 
 def test_forward_vgg():
     for n in [11]:
         mx_sym = model_zoo.mx_vgg[n]
-        test_mxnet_frontend_impl(mx_sym)
+        verify_mxnet_frontend_impl(mx_sym)
 
 def test_forward_resnet():
     for n in [18]:
         mx_sym = model_zoo.mx_resnet[n]
-        test_mxnet_frontend_impl(mx_sym)
+        verify_mxnet_frontend_impl(mx_sym)
 
 if __name__ == '__main__':
     test_forward_mlp()
diff --git a/nnvm/tests/python/frontend/onnx/test_forward.py b/nnvm/tests/python/frontend/onnx/test_forward.py
index 36020103b934..155b054f47a9 100644
--- a/nnvm/tests/python/frontend/onnx/test_forward.py
+++ b/nnvm/tests/python/frontend/onnx/test_forward.py
@@ -4,10 +4,10 @@
 from tvm.contrib import graph_runtime
 from nnvm.testing.config import ctx_list
 import onnx
-import onnx_caffe2.backend
 from model_zoo import super_resolution
 
-def test_onnx_forward_impl(graph_file, data_shape, out_shape):
+def verify_onnx_forward_impl(graph_file, data_shape, out_shape):
+    import onnx_caffe2.backend
     def get_caffe2_output(graph, x, dtype='float32'):
         prepared_backend = onnx_caffe2.backend.prepare(graph)
         W = {graph.input[-1]: x.astype(dtype)}
@@ -35,8 +35,8 @@ def get_tvm_output(graph, x, target, ctx, dtype='float32'):
         tvm_out = get_tvm_output(graph, x, target, ctx, dtype)
         np.testing.assert_allclose(c2_out, tvm_out, rtol=1e-5, atol=1e-5)
 
-def test_super_resolution_example():
-    test_onnx_forward_impl(super_resolution[0], (1, 1, 224, 224), (1, 1, 672, 672))
+def verify_super_resolution_example():
+    verify_onnx_forward_impl(super_resolution[0], (1, 1, 224, 224), (1, 1, 672, 672))
 
 if __name__ == '__main__':
-    test_super_resolution_example()
+    verify_super_resolution_example()
diff --git a/nnvm/tests/scripts/task_build.sh b/nnvm/tests/scripts/task_build.sh
new file mode 100755
index 000000000000..e8ced824ff94
--- /dev/null
+++ b/nnvm/tests/scripts/task_build.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+echo "Build TVM..."
+cd tvm
+cp make/config.mk .
+echo USE_CUDNN=1 >> config.mk
+echo USE_CUDA=1 >> config.mk
+echo USE_OPENCL=1 >> config.mk
+echo LLVM_CONFIG=llvm-config-4.0 >> config.mk
+echo USE_RPC=1 >> config.mk
+echo USE_BLAS=openblas >> config.mk
+echo USE_GRAPH_RUNTIME=1 >> config.mk
+make "$@"
+cd ..
+
+echo "Build NNVM..."
+make "$@"
diff --git a/nnvm/tests/scripts/task_clean.sh b/nnvm/tests/scripts/task_clean.sh
new file mode 100755
index 000000000000..f4c02ed639c3
--- /dev/null
+++ b/nnvm/tests/scripts/task_clean.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+echo "Cleanup data..."
+cd tvm
+make clean
+cd ..
+make clean
diff --git a/nnvm/tests/scripts/task_frontend_test.sh b/nnvm/tests/scripts/task_frontend_test.sh
new file mode 100755
index 000000000000..9218a5c08e0b
--- /dev/null
+++ b/nnvm/tests/scripts/task_frontend_test.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+export PYTHONPATH=python:tvm/python:tvm/topi/python
+
+echo "Running ONNX frontend test..."
+python -m nose -v tests/python/frontend/onnx || exit -1
+
+echo "Running MXNet frontend test..."
+python -m nose -v tests/python/frontend/mxnet || exit -1
diff --git a/nnvm/tests/scripts/task_lint.sh b/nnvm/tests/scripts/task_lint.sh
new file mode 100755
index 000000000000..a6774a189f35
--- /dev/null
+++ b/nnvm/tests/scripts/task_lint.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+echo "Check codestyle of c++ code..."
+make cpplint || exit -1
+echo "Check codestyle of python code..."
+make pylint || exit -1
+echo "Check documentations of c++ code..."
+make doc 2>log.txt
+(cat log.txt| grep -v ENABLE_PREPROCESSING |grep -v "unsupported tag") > logclean.txt
+echo "---------Error Log----------"
+cat logclean.txt
+echo "----------------------------"
+(cat logclean.txt|grep warning) && exit -1
+(cat logclean.txt|grep error) && exit -1
+rm logclean.txt
+rm log.txt
diff --git a/nnvm/tests/scripts/task_python_docs.sh b/nnvm/tests/scripts/task_python_docs.sh
new file mode 100755
index 000000000000..94fa73e5e2de
--- /dev/null
+++ b/nnvm/tests/scripts/task_python_docs.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+mkdir -p docs/_build/html
+# C++ doc
+make doc
+
+rm -rf python/nnvm/*.pyc python/nnvm/*/*.pyc
+
+cd docs
+PYTHONPATH=../python:../tvm/python:../tvm/topi/python make html || exit -1
+cd _build/html
+tar czf docs.tgz *
+mv docs.tgz ../../../
diff --git a/nnvm/tests/scripts/task_python_test.sh b/nnvm/tests/scripts/task_python_test.sh
new file mode 100755
index 000000000000..c8dc662cc73c
--- /dev/null
+++ b/nnvm/tests/scripts/task_python_test.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+export PYTHONPATH=python:tvm/python:tvm/topi/python
+
+echo "Running unittest..."
+python -m nose -v tests/python/unittest || exit -1
+python3 -m nose -v tests/python/unittest || exit -1
+
+echo "Running compiler test..."
+python -m nose -v tests/python/compiler || exit -1
+python3 -m nose -v tests/python/compiler || exit -1
diff --git a/nnvm/tutorials/imagenet_inference_gpu.py b/nnvm/tutorials/imagenet_inference_gpu.py
index 98f0e94c6b5f..1eca09fe13cf 100644
--- a/nnvm/tutorials/imagenet_inference_gpu.py
+++ b/nnvm/tutorials/imagenet_inference_gpu.py
@@ -39,7 +39,7 @@ def tvm_callback_cuda_compile(code):
 # .. note::
 #
 #   In a typical workflow, we can get this pair from :any:`nnvm.frontend`
-# Example: /nnvm-top/tests/python/frontend/mxnet/test_forward.py  
+#
 target = "cuda"
 ctx = tvm.gpu(0)
 batch_size = 1

From 717c8a1969a1c11e16295f8d3871643765c2de36 Mon Sep 17 00:00:00 2001
From: "Joshua Z. Zhang" <cheungchih@gmail.com>
Date: Wed, 27 Sep 2017 14:47:48 -0700
Subject: [PATCH 503/949] [TUTORIAL] Onnx tutorial (#50)

* add onnx

* fix
---
 nnvm/tutorials/from_onnx.py | 87 +++++++++++++++++++++++++++++++++++++
 1 file changed, 87 insertions(+)
 create mode 100644 nnvm/tutorials/from_onnx.py

diff --git a/nnvm/tutorials/from_onnx.py b/nnvm/tutorials/from_onnx.py
new file mode 100644
index 000000000000..f2dbde3f95fe
--- /dev/null
+++ b/nnvm/tutorials/from_onnx.py
@@ -0,0 +1,87 @@
+"""
+Compiling ONNX Models with NNVM
+================================
+**Author**: `Joshua Z. Zhang <https://zhreshold.github.io/>`_
+
+This article is an introductory tutorial to deploy ONNX models with NNVM.
+
+For us to begin with, onnx module is required to be installed.
+
+A quick solution is to install protobuf compiler, and
+```bash
+pip install onnx --user
+```
+or please refer to offical site.
+https://github.com/onnx/onnx
+"""
+import nnvm
+import tvm
+import onnx
+import numpy as np
+
+######################################################################
+# Load pretrained ONNX model
+# ---------------------------------------------
+# The example super resolution model used here is exactly the same model in onnx tutorial
+# http://pytorch.org/tutorials/advanced/super_resolution_with_caffe2.html
+# we skip the pytorch model construction part, and download the saved onnx model
+import urllib2
+model_url = ''.join(['https://gist.github.com/zhreshold/',
+                     'bcda4716699ac97ea44f791c24310193/raw/',
+                     '41b443bf2b6cf795892d98edd28bacecd8eb0d8d/',
+                     'super_resolution.onnx'])
+with open('super_resolution.onnx', 'w') as f:
+    f.write(urllib2.urlopen(model_url).read())
+# now you have super_resolution.onnx on disk
+onnx_graph = onnx.load('super_resolution.onnx')
+# we can load the graph as NNVM compatible model
+sym, params = nnvm.frontend.from_onnx(onnx_graph)
+
+######################################################################
+# Load a test image
+# ---------------------------------------------
+# A single cat dominates the examples!
+import Image
+img_url = 'https://github.com/dmlc/mxnet.js/blob/master/data/cat.png?raw=true'
+with open('cat.jpg', 'w') as f:
+    f.write(urllib2.urlopen(img_url).read())
+img = Image.open('cat.jpg').convert("L")  # convert to greyscale
+x = np.array(img.resize((224, 224)))[np.newaxis, np.newaxis, :, :]
+
+######################################################################
+# Compile the model on NNVM
+# ---------------------------------------------
+# We should be familiar with the process right now.
+import nnvm.compiler
+target = 'cuda'
+shape_dict = {'input_0': x.shape}
+graph, lib, params = nnvm.compiler.build(sym, target, shape_dict, params=params)
+
+######################################################################
+# Execute on TVM
+# ---------------------------------------------
+# The process is no different from other example
+from tvm.contrib import graph_runtime
+ctx = tvm.gpu(0)
+dtype = 'float32'
+m = graph_runtime.create(graph, lib, ctx)
+# set inputs
+m.set_input('input_0', tvm.nd.array(x.astype(dtype)))
+m.set_input(**params)
+# execute
+m.run()
+# get outputs
+output_shape = (1, 1, 672, 672)
+tvm_output = m.get_output(0, tvm.nd.empty(output_shape, dtype)).asnumpy()
+out_img = tvm_output.reshape((672, 672))
+
+######################################################################
+# Display results
+# ---------------------------------------------
+# We put input and output image neck to neck
+from matplotlib import pyplot as plt
+canvas = np.full((672, 672*2), 255)
+canvas[0:224, 0:224] = x[0, 0, :, :]
+canvas[:, 672:] = out_img
+plt.imshow(canvas, cmap='gray')
+plt.show()

From 7a510473f4b16824001e7087f0514718584638d3 Mon Sep 17 00:00:00 2001
From: "Joshua Z. Zhang" <cheungchih@gmail.com>
Date: Thu, 28 Sep 2017 09:31:12 -0700
Subject: [PATCH 504/949] [TUTORIAL] use resnet v2 (#51)

* use resnet v2

* fix
---
 nnvm/tutorials/from_mxnet.py | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/nnvm/tutorials/from_mxnet.py b/nnvm/tutorials/from_mxnet.py
index 0b6b70ef2d09..7068020f15de 100644
--- a/nnvm/tutorials/from_mxnet.py
+++ b/nnvm/tutorials/from_mxnet.py
@@ -28,7 +28,7 @@
 from mxnet.gluon.utils import download
 import Image
 from matplotlib import pyplot as plt
-block = get_model('resnet18_v1', pretrained=True)
+block = get_model('resnet18_v2', pretrained=True)
 img_name = 'cat.jpg'
 synset_url = ''.join(['https://gist.githubusercontent.com/zhreshold/',
                       '4d0b62f3d01426887599d4f7ede23ee5/raw/',
@@ -85,7 +85,7 @@ def transform_image(image):
 m.run()
 # get outputs
 tvm_output = m.get_output(0, tvm.nd.empty((1000,), dtype))
-top1 = np.argmax(tvm_output)
+top1 = np.argmax(tvm_output.asnumpy())
 print('TVM prediction top-1:', top1, synset[top1])
 
 ######################################################################
@@ -103,12 +103,12 @@ def block2symbol(block):
     return sym, args, auxs
 mx_sym, args, auxs = block2symbol(block)
 # usually we would save/load it as checkpoint
-mx.model.save_checkpoint('resnet18_v1', 0, mx_sym, args, auxs)
-# there are 'resnet18_v1-0000.params' and 'resnet18_v1-symbol.json' on disk
+mx.model.save_checkpoint('resnet18_v2', 0, mx_sym, args, auxs)
+# there are 'resnet18_v2-0000.params' and 'resnet18_v2-symbol.json' on disk
 
 ######################################################################
 # for a normal mxnet model, we start from here
-mx_sym, args, auxs = mx.model.load_checkpoint('resnet18_v1', 0)
+mx_sym, args, auxs = mx.model.load_checkpoint('resnet18_v2', 0)
 # now we use the same API to get NNVM compatible symbol
 nnvm_sym, nnvm_params = nnvm.frontend.from_mxnet(mx_sym, args, auxs)
 # repeat the same steps to run this model using TVM

From f996cf283f78595853f98e99cf96cf450e0afe3b Mon Sep 17 00:00:00 2001
From: "Joshua Z. Zhang" <cheungchih@gmail.com>
Date: Thu, 28 Sep 2017 10:05:22 -0700
Subject: [PATCH 505/949] revert v1 (#53)

---
 nnvm/tutorials/from_mxnet.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/nnvm/tutorials/from_mxnet.py b/nnvm/tutorials/from_mxnet.py
index 7068020f15de..7fb96f166797 100644
--- a/nnvm/tutorials/from_mxnet.py
+++ b/nnvm/tutorials/from_mxnet.py
@@ -28,7 +28,7 @@
 from mxnet.gluon.utils import download
 import Image
 from matplotlib import pyplot as plt
-block = get_model('resnet18_v2', pretrained=True)
+block = get_model('resnet18_v1', pretrained=True)
 img_name = 'cat.jpg'
 synset_url = ''.join(['https://gist.githubusercontent.com/zhreshold/',
                       '4d0b62f3d01426887599d4f7ede23ee5/raw/',
@@ -103,12 +103,12 @@ def block2symbol(block):
     return sym, args, auxs
 mx_sym, args, auxs = block2symbol(block)
 # usually we would save/load it as checkpoint
-mx.model.save_checkpoint('resnet18_v2', 0, mx_sym, args, auxs)
-# there are 'resnet18_v2-0000.params' and 'resnet18_v2-symbol.json' on disk
+mx.model.save_checkpoint('resnet18_v1', 0, mx_sym, args, auxs)
+# there are 'resnet18_v1-0000.params' and 'resnet18_v1-symbol.json' on disk
 
 ######################################################################
 # for a normal mxnet model, we start from here
-mx_sym, args, auxs = mx.model.load_checkpoint('resnet18_v2', 0)
+mx_sym, args, auxs = mx.model.load_checkpoint('resnet18_v1', 0)
 # now we use the same API to get NNVM compatible symbol
 nnvm_sym, nnvm_params = nnvm.frontend.from_mxnet(mx_sym, args, auxs)
 # repeat the same steps to run this model using TVM

From 882c3bcf9b528ec65b40d1b989cd04903b4fdee8 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 28 Sep 2017 11:03:14 -0700
Subject: [PATCH 506/949] [TEST] Xavie initialization for benchmarks (#54)

* [TEST] Xavie initialization for benchmarks

* remove additional line
---
 nnvm/python/nnvm/testing/init.py         | 110 +++++++++++++++++++++++
 nnvm/python/nnvm/testing/mobilenet.py    |   2 +-
 nnvm/python/nnvm/testing/utils.py        |  24 ++---
 nnvm/tutorials/imagenet_inference_gpu.py |   5 +-
 4 files changed, 129 insertions(+), 12 deletions(-)
 create mode 100644 nnvm/python/nnvm/testing/init.py

diff --git a/nnvm/python/nnvm/testing/init.py b/nnvm/python/nnvm/testing/init.py
new file mode 100644
index 000000000000..3ce7e40ef87f
--- /dev/null
+++ b/nnvm/python/nnvm/testing/init.py
@@ -0,0 +1,110 @@
+"""Initializer of parameters."""
+import numpy as np
+
+class Initializer(object):
+    """The base class of an initializer."""
+    def __init__(self, **kwargs):
+        self._kwargs = kwargs
+
+    def __call__(self, desc, arr):
+        """Initialize an array
+
+        Parameters
+        ----------
+        desc : str
+            Initialization pattern descriptor.
+
+        arr : NDArray
+            The array to be initialized.
+        """
+        if desc.endswith('weight'):
+            self._init_weight(desc, arr)
+        elif desc.endswith('bias'):
+            self._init_bias(desc, arr)
+        elif desc.endswith('gamma'):
+            self._init_gamma(desc, arr)
+        elif desc.endswith('beta'):
+            self._init_beta(desc, arr)
+        elif desc.endswith('mean'):
+            self._init_mean(desc, arr)
+        elif desc.endswith('var'):
+            self._init_var(desc, arr)
+        else:
+            self._init_default(desc, arr)
+
+    def _init_bias(self, _, arr):
+        arr[:] = 0.0
+
+    def _init_gamma(self, _, arr):
+        arr[:] = 1.0
+
+    def _init_beta(self, _, arr):
+        arr[:] = 0.0
+
+    def _init_mean(self, _, arr):
+        arr[:] = 0.0
+
+    def _init_var(self, _, arr):
+        arr[:] = 1.0
+
+    def _init_weight(self, name, arr):
+        """Abstract method to Initialize weight."""
+        raise NotImplementedError("Must override it")
+
+    def _init_default(self, name, _):
+        raise ValueError(
+            'Unknown initialization pattern for %s. ' \
+            'Default initialization is now limited to '\
+            '"weight", "bias", "gamma" (1.0), and "beta" (0.0).' \
+            'Please use mx.sym.Variable(init=mx.init.*) to set initialization pattern' % name)
+
+
+class Xavier(Initializer):
+    """ "Xavier" initialization for weights
+
+    Parameters
+    ----------
+    rnd_type: str, optional
+        Random generator type, can be ``'gaussian'`` or ``'uniform'``.
+
+    factor_type: str, optional
+        Can be ``'avg'``, ``'in'``, or ``'out'``.
+
+    magnitude: float, optional
+        Scale of random number.
+    """
+    def __init__(self, rnd_type="uniform", factor_type="avg", magnitude=3):
+        super(Xavier, self).__init__(rnd_type=rnd_type,
+                                     factor_type=factor_type,
+                                     magnitude=magnitude)
+        self.rnd_type = rnd_type
+        self.factor_type = factor_type
+        self.magnitude = float(magnitude)
+
+
+    def _init_weight(self, name, arr):
+        shape = arr.shape
+        hw_scale = 1.
+        if len(shape) < 2:
+            raise ValueError('Xavier initializer cannot be applied to vector {0}. It requires at'
+                             ' least 2D.'.format(name))
+        if len(shape) > 2:
+            hw_scale = np.prod(shape[2:])
+        fan_in, fan_out = shape[1] * hw_scale, shape[0] * hw_scale
+        factor = 1.
+        if self.factor_type == "avg":
+            factor = (fan_in + fan_out) / 2.0
+        elif self.factor_type == "in":
+            factor = fan_in
+        elif self.factor_type == "out":
+            factor = fan_out
+        else:
+            raise ValueError("Incorrect factor type")
+        # Hack for mobilenet, because there is less connectivity
+        if "depthwise" in name:
+            factor = 3 * 3
+        scale = np.sqrt(self.magnitude / factor)
+        if self.rnd_type == "uniform":
+            arr[:] = np.random.uniform(-scale, scale, size=arr.shape)
+        else:
+            raise ValueError("Unknown random type")
diff --git a/nnvm/python/nnvm/testing/mobilenet.py b/nnvm/python/nnvm/testing/mobilenet.py
index f5667b649c43..bebd7ddc6802 100644
--- a/nnvm/python/nnvm/testing/mobilenet.py
+++ b/nnvm/python/nnvm/testing/mobilenet.py
@@ -30,7 +30,7 @@ def separable_conv_block(data, name, depthwise_channels,
     # depthwise convolution + bn + relu
     conv1 = sym.conv2d(data=data, channels=depthwise_channels,
                        groups=depthwise_channels, kernel_size=kernel_size, strides=strides,
-                       padding=padding, use_bias=False, layout="NCHW", name=name + "_conv1")
+                       padding=padding, use_bias=False, layout="NCHW", name=name + "_depthwise_conv1")
     bn1 = sym.batch_norm(data=conv1, epsilon=epsilon, name=name + "_bn1")
     act1 = sym.relu(data=bn1, name=name + "_relu1")
     # pointwise convolution + bn + relu
diff --git a/nnvm/python/nnvm/testing/utils.py b/nnvm/python/nnvm/testing/utils.py
index 5d2146971172..fcc008e61fc3 100644
--- a/nnvm/python/nnvm/testing/utils.py
+++ b/nnvm/python/nnvm/testing/utils.py
@@ -5,9 +5,10 @@
 import tvm
 from ..compiler import graph_util
 from ..import graph
+from . init import Xavier
 
-
-def create_workload(net, batch_size, image_shape=(3, 224, 224), dtype="float32"):
+def create_workload(net, batch_size, image_shape=(3, 224, 224),
+                    dtype="float32", initializer=None, seed=0):
     """Helper function to create benchmark workload for input network
 
     Parameters
@@ -24,6 +25,12 @@ def create_workload(net, batch_size, image_shape=(3, 224, 224), dtype="float32")
     dtype : str, optional
         The data type
 
+    initializer : Initializer
+        The initializer used
+
+    seed : int
+        The seed used in initialization.
+
     Returns
     -------
     net : nnvm.Symbol
@@ -38,15 +45,12 @@ def create_workload(net, batch_size, image_shape=(3, 224, 224), dtype="float32")
     g = graph.create(net)
     input_shapes, _ = graph_util.infer_shape(g, data=data_shape)
     shape_dict = dict(zip(g.index.input_names, input_shapes))
+    np.random.seed(seed)
+    initializer = initializer if initializer else Xavier(magnitude=3)
     for k, v in shape_dict.items():
         if k == "data":
             continue
-        # Specially generate non-negative parameters.
-        if k.endswith("gamma"):
-            init = np.random.uniform(0.9, 1, size=v)
-        elif k.endswith("var"):
-            init = np.random.uniform(0.9, 1, size=v)
-        else:
-            init = np.random.uniform(-0.1, 0.1, size=v)
-        params[k] = tvm.nd.array(init.astype(dtype), ctx=tvm.cpu(0))
+        init_value = np.zeros(v).astype(dtype)
+        initializer(k, init_value)
+        params[k] = tvm.nd.array(init_value, ctx=tvm.cpu(0))
     return net, params
diff --git a/nnvm/tutorials/imagenet_inference_gpu.py b/nnvm/tutorials/imagenet_inference_gpu.py
index 1eca09fe13cf..778ead0b5a36 100644
--- a/nnvm/tutorials/imagenet_inference_gpu.py
+++ b/nnvm/tutorials/imagenet_inference_gpu.py
@@ -8,6 +8,7 @@
 To begin with, we import nnvm(for compilation) and TVM(for deployment).
 """
 import tvm
+import numpy as np
 from tvm.contrib import nvcc, graph_runtime
 import nnvm.compiler
 import nnvm.testing
@@ -64,6 +65,7 @@ def tvm_callback_cuda_compile(code):
 graph, lib, params = nnvm.compiler.build(
     net, target, shape={"data": data_shape}, params=params)
 
+
 ######################################################################
 # Run the Compiled Module
 # -----------------------
@@ -74,10 +76,11 @@ def tvm_callback_cuda_compile(code):
 # This example runs on the same machine.
 #
 # Note that the code below no longer depends on NNVM, and only relies TVM's runtime to run(deploy).
-
+data = np.random.uniform(-1, 1, size=data_shape).astype("float32")
 module = graph_runtime.create(graph, lib, ctx)
 # set input
 module.set_input(**params)
+module.set_input("data", data)
 # run
 module.run()
 # get output

From bca1656e0088feab77c5f42ef4fe5af1d0e37b81 Mon Sep 17 00:00:00 2001
From: Xingjian Shi <xshiab@ust.hk>
Date: Thu, 28 Sep 2017 13:06:26 -0500
Subject: [PATCH 507/949] add squeeze (#52)

* add transform

* fix

* update doc

* Update tvm
---
 nnvm/docs/top.rst                             |  2 +
 nnvm/include/nnvm/top/tensor.h                | 10 +++
 nnvm/python/nnvm/compiler/compile_engine.py   |  2 +-
 nnvm/python/nnvm/top/transform.py             | 10 +++
 nnvm/src/top/op_common.h                      |  1 +
 nnvm/src/top/tensor/transform.cc              | 74 +++++++++++++++++++
 nnvm/tests/python/compiler/test_top_level1.py | 25 +++++++
 nnvm/tests/python/compiler/test_top_level4.py |  1 -
 8 files changed, 123 insertions(+), 2 deletions(-)

diff --git a/nnvm/docs/top.rst b/nnvm/docs/top.rst
index 89af46509d02..65beccde0c5c 100644
--- a/nnvm/docs/top.rst
+++ b/nnvm/docs/top.rst
@@ -41,6 +41,7 @@ This level enables fully connected multi-layer perceptron.
    nnvm.symbol.flatten
    nnvm.symbol.concatenate
    nnvm.symbol.expand_dims
+   nnvm.symbol.squeeze
    nnvm.symbol.split
    nnvm.symbol.dropout
    nnvm.symbol.batch_norm
@@ -112,6 +113,7 @@ Detailed Definitions
 .. autofunction:: nnvm.symbol.flatten
 .. autofunction:: nnvm.symbol.concatenate
 .. autofunction:: nnvm.symbol.expand_dims
+.. autofunction:: nnvm.symbol.squeeze
 .. autofunction:: nnvm.symbol.split
 .. autofunction:: nnvm.symbol.dropout
 .. autofunction:: nnvm.symbol.batch_norm
diff --git a/nnvm/include/nnvm/top/tensor.h b/nnvm/include/nnvm/top/tensor.h
index 23fe9a1e9644..16ed259dac69 100644
--- a/nnvm/include/nnvm/top/tensor.h
+++ b/nnvm/include/nnvm/top/tensor.h
@@ -79,6 +79,16 @@ struct ReshapeParam : public dmlc::Parameter<ReshapeParam> {
   }
 };
 
+struct SqueezeParam : public dmlc::Parameter<SqueezeParam> {
+  TShape axis;
+
+  DMLC_DECLARE_PARAMETER(SqueezeParam) {
+    DMLC_DECLARE_FIELD(axis).set_default(TShape())
+    .describe("The axis to squeeze in the input tensor."
+              " If set to None, all size=1 axes will be squeezed");
+  }
+};
+
 struct ScalarParam : public dmlc::Parameter<ScalarParam> {
   double scalar;
 
diff --git a/nnvm/python/nnvm/compiler/compile_engine.py b/nnvm/python/nnvm/compiler/compile_engine.py
index b215abf6eac2..289f09deb280 100644
--- a/nnvm/python/nnvm/compiler/compile_engine.py
+++ b/nnvm/python/nnvm/compiler/compile_engine.py
@@ -47,7 +47,7 @@ def items(self):
         """
         res = _list_cache_items()
         assert len(res) % 2 == 0
-        return [(res[2*i], res[2*i+1]) for i in range(len(res)/2)]
+        return [(res[2*i], res[2*i+1]) for i in range(len(res) // 2)]
 
     def clear_cache(self):
         """Clear the existing cached functions."""
diff --git a/nnvm/python/nnvm/top/transform.py b/nnvm/python/nnvm/top/transform.py
index a82511943ebf..21c10f5cccf4 100644
--- a/nnvm/python/nnvm/top/transform.py
+++ b/nnvm/python/nnvm/top/transform.py
@@ -36,6 +36,16 @@ def compute_reshape(attrs, inputs, out_info):
 reg.register_pattern("reshape", OpPattern.INJECTIVE)
 reg.register_schedule("reshape", _fschedule_injective)
 
+# reshape
+@reg.register_compute("squeeze")
+def compute_squeeze(attrs, inputs, out_info):
+    """Compute definition of reshape"""
+    axis = attrs.get_int_tuple("axis")
+    axis = tuple(axis) if axis else None
+    return topi.squeeze(inputs[0], axis)
+reg.register_pattern("squeeze", OpPattern.INJECTIVE)
+reg.register_schedule("squeeze", _fschedule_injective)
+
 # concatenate
 @reg.register_compute("concatenate")
 def compute_concatenate(attrs, inputs, out_info):
diff --git a/nnvm/src/top/op_common.h b/nnvm/src/top/op_common.h
index 110e08a5841e..e23059a88c42 100644
--- a/nnvm/src/top/op_common.h
+++ b/nnvm/src/top/op_common.h
@@ -10,6 +10,7 @@
 #include <dmlc/parameter.h>
 #include <string>
 #include <vector>
+#include <unordered_set>
 
 namespace nnvm {
 namespace top {
diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
index 0fe772e28e47..12ae06731145 100644
--- a/nnvm/src/top/tensor/transform.cc
+++ b/nnvm/src/top/tensor/transform.cc
@@ -445,6 +445,80 @@ The significance of each is explained below:
 .set_num_outputs(1)
 .set_support_level(3);
 
+// squeeze
+DMLC_REGISTER_PARAMETER(SqueezeParam);
+
+inline bool SqueezeShape(const nnvm::NodeAttrs& attrs,
+                           std::vector<TShape>* in_attrs,
+                           std::vector<TShape>* out_attrs) {
+  const SqueezeParam& param = nnvm::get<SqueezeParam>(attrs.parsed);
+  CHECK_EQ(in_attrs->size(), 1U);
+  CHECK_EQ(out_attrs->size(), 1U);
+  const TShape& shp = (*in_attrs)[0];
+  if (shp.ndim() == 0) return false;
+
+  std::vector<int64_t> oshape;
+  if (param.axis.ndim() == 0) {
+    for (dim_t i = 0; i < shp.ndim(); ++i) {
+      if(shp[i] != 1) {
+        oshape.emplace_back(shp[i]);
+      }
+    }
+  } else {
+    std::unordered_set<dim_t> axis_checker;
+    for (size_t i = 0; i < param.axis.ndim(); ++i) {
+      if(param.axis[i] < 0) {
+        int real_axis = param.axis[i] + static_cast<int>(shp.ndim());
+        CHECK(real_axis < static_cast<int>(shp.ndim()) && real_axis >= 0);
+        axis_checker.insert(real_axis);
+      }
+    }
+    for (size_t i = 0; i < shp.ndim(); ++i) {
+      if(axis_checker.find(i) == axis_checker.end()) {
+        oshape.emplace_back(shp[i]);
+      } else {
+        CHECK_EQ(shp[i], 1) << "The squeezed axis must have shape 1!"
+                            << "Want to squeeze " << i
+                            << ", which has shape" << shp[i];
+      }
+    }
+  }
+  if(oshape.size() == 0) {
+    // Handles the case where all axes are squeezed.
+    oshape.push_back(1);
+  }
+  TShape out_shape(oshape.begin(), oshape.end());
+  CHECK_EQ(out_shape.Size(), shp.Size())
+      << "Target shape size is different to source. "
+      << "Target: " << out_shape
+      << "\nSource: " << shp;
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, 0, out_shape);
+  return true;
+}
+
+NNVM_REGISTER_OP(squeeze)
+.describe(R"code(Squeeze axises in the array.
+
+Examples::
+
+  x = [[[0], [1], [2]]]
+
+  squeeze(x) = [0, 1, 2]
+
+  squeeze(x, 0) = [[0], [1], [2]]
+
+  squeeze(x, (0, 2)) = [0, 1, 2]
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "Tensor", "Source input")
+.add_arguments(SqueezeParam::__FIELDS__())
+.set_attr_parser(ParamParser<SqueezeParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<SqueezeParam>)
+.set_attr<nnvm::FInferShape>("FInferShape", SqueezeShape)
+.set_attr<nnvm::FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_num_inputs(1)
+.set_num_outputs(1)
+.set_support_level(1);
+
 // tranpose
 DMLC_REGISTER_PARAMETER(TransposeParam);
 
diff --git a/nnvm/tests/python/compiler/test_top_level1.py b/nnvm/tests/python/compiler/test_top_level1.py
index 068ea14c28b7..26ff34eb7ecb 100644
--- a/nnvm/tests/python/compiler/test_top_level1.py
+++ b/nnvm/tests/python/compiler/test_top_level1.py
@@ -220,6 +220,30 @@ def test_split():
     verify_split((5, 3), [3], axis=0)
     verify_split((5, 9, 3), [3, 4], axis=1)
 
+
+def verify_squeeze(dshape, axis):
+    x = sym.Variable("x")
+    if axis:
+        y = sym.squeeze(x, axis=axis)
+    else:
+        y = sym.squeeze(x)
+    y = y + 1
+    dtype = "float32"
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
+        m = graph_runtime.create(graph, lib, ctx)
+        # set input
+        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+        m.run(x=data)
+        out_np = np.squeeze(data.asnumpy(), axis=axis) + 1
+        out = m.get_output(0, tvm.nd.empty(out_np.shape))
+        np.testing.assert_allclose(out.asnumpy(), out_np, atol=1e-5, rtol=1e-5)
+
+def test_squeeze():
+    verify_squeeze((1, 3, 2, 5), None)
+    verify_squeeze((1, 3, 1), axis=0)
+    verify_squeeze((1, 3, 2, 5, 1), axis=-1)
+
 if __name__ == "__main__":
     test_split()
     test_concatenate()
@@ -232,3 +256,4 @@ def test_split():
     test_tanh()
     test_sigmoid()
     test_softmax()
+    test_squeeze()
diff --git a/nnvm/tests/python/compiler/test_top_level4.py b/nnvm/tests/python/compiler/test_top_level4.py
index 1b005e27d9ef..ad09d73ec28d 100644
--- a/nnvm/tests/python/compiler/test_top_level4.py
+++ b/nnvm/tests/python/compiler/test_top_level4.py
@@ -71,7 +71,6 @@ def test_reshape():
     verify_reshape((2, 3, 4), (8, 3))
     verify_reshape((4, 7), (2, 7, 2))
 
-
 if __name__ == "__main__":
     test_reshape()
     test_reduce()

From c9f1057b558df03b49a38cebe1b050f8681b5d5c Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 28 Sep 2017 22:33:19 -0700
Subject: [PATCH 508/949] [PASS] FoldScaleAxis (#55)

* [PASS] FoldScaleAxis

* Move FoldAxis to O3

* Set unroll to 0 when ready
---
 nnvm/include/nnvm/graph.h                     |   2 +
 nnvm/python/nnvm/compiler/build_module.py     |  10 +-
 nnvm/python/nnvm/testing/init.py              |   1 -
 nnvm/python/nnvm/testing/mobilenet.py         |   3 +-
 nnvm/python/nnvm/testing/utils.py             |   2 +-
 nnvm/src/README.md                            |   7 +-
 nnvm/src/compiler/compile_engine.cc           |   2 +-
 nnvm/src/compiler/fold_scale_axis.cc          | 271 ++++++++++++++++++
 nnvm/src/compiler/graph_fuse.cc               |  12 +-
 nnvm/src/compiler/graph_transform.h           |   7 +-
 nnvm/src/compiler/pattern_util.h              |  99 +++++++
 nnvm/src/compiler/simplify_inference.cc       |  17 +-
 nnvm/src/core/graph.cc                        |   1 +
 nnvm/src/top/tensor/transform.cc              |   8 +-
 nnvm/tests/python/compiler/test_fold_axis.py  |  49 ++++
 .../compiler/test_simplify_inference.py       |   6 +-
 .../python/frontend/mxnet/test_forward.py     |   3 +-
 17 files changed, 457 insertions(+), 43 deletions(-)
 create mode 100644 nnvm/src/compiler/fold_scale_axis.cc
 create mode 100644 nnvm/src/compiler/pattern_util.h
 create mode 100644 nnvm/tests/python/compiler/test_fold_axis.py

diff --git a/nnvm/include/nnvm/graph.h b/nnvm/include/nnvm/graph.h
index a555ecd68a65..1cd1ed63db97 100644
--- a/nnvm/include/nnvm/graph.h
+++ b/nnvm/include/nnvm/graph.h
@@ -98,6 +98,8 @@ class IndexedGraph {
     array_view<NodeEntry> inputs;
     /*! \brief control flow dependencies to the node */
     array_view<uint32_t> control_deps;
+    /*! \brief weak reference to node */
+    std::weak_ptr<nnvm::Node> weak_ref;
   };
   /*! \return number of nodes in the graph */
   inline size_t num_nodes() const {
diff --git a/nnvm/python/nnvm/compiler/build_module.py b/nnvm/python/nnvm/compiler/build_module.py
index 5f1fbcc0df56..8570c03d88f0 100644
--- a/nnvm/python/nnvm/compiler/build_module.py
+++ b/nnvm/python/nnvm/compiler/build_module.py
@@ -11,7 +11,8 @@
 OPT_PASS_LEVEL = {
     "SimplifyInference": 0,
     "PrecomputePrune": 2,
-    "OpFusion": 1
+    "OpFusion": 1,
+    "FoldScaleAxis": 3
 }
 
 # List of optimization pass and level when switch on
@@ -144,6 +145,10 @@ def optimize(graph, shape, dtype="float32"):
     if cfg.pass_enabled("SimplifyInference"):
         graph = graph_attr.set_shape_inputs(graph, shape)
         graph = graph.apply(["InferShape", "SimplifyInference"])
+
+    if cfg.pass_enabled("FoldScaleAxis"):
+        graph = graph_attr.set_shape_inputs(graph, shape)
+        graph = graph.apply(["InferShape", "FoldScaleAxis"])
     return graph
 
 
@@ -291,5 +296,6 @@ def precompute_prune(graph, params):
     out_names = pre_graph.json_attr("output_names")
     if not pre_graph.symbol.list_output_names():
         return graph, params
-    out_arrs = _run_graph(pre_graph, params)
+    with tvm.build_config(auto_unroll_max_step=0):
+        out_arrs = _run_graph(pre_graph, params)
     return graph, dict(zip(out_names, out_arrs))
diff --git a/nnvm/python/nnvm/testing/init.py b/nnvm/python/nnvm/testing/init.py
index 3ce7e40ef87f..36ddcc955f7c 100644
--- a/nnvm/python/nnvm/testing/init.py
+++ b/nnvm/python/nnvm/testing/init.py
@@ -81,7 +81,6 @@ def __init__(self, rnd_type="uniform", factor_type="avg", magnitude=3):
         self.factor_type = factor_type
         self.magnitude = float(magnitude)
 
-
     def _init_weight(self, name, arr):
         shape = arr.shape
         hw_scale = 1.
diff --git a/nnvm/python/nnvm/testing/mobilenet.py b/nnvm/python/nnvm/testing/mobilenet.py
index bebd7ddc6802..1d59df64ab4a 100644
--- a/nnvm/python/nnvm/testing/mobilenet.py
+++ b/nnvm/python/nnvm/testing/mobilenet.py
@@ -30,7 +30,8 @@ def separable_conv_block(data, name, depthwise_channels,
     # depthwise convolution + bn + relu
     conv1 = sym.conv2d(data=data, channels=depthwise_channels,
                        groups=depthwise_channels, kernel_size=kernel_size, strides=strides,
-                       padding=padding, use_bias=False, layout="NCHW", name=name + "_depthwise_conv1")
+                       padding=padding, use_bias=False, layout="NCHW",
+                       name=name + "_depthwise_conv1")
     bn1 = sym.batch_norm(data=conv1, epsilon=epsilon, name=name + "_bn1")
     act1 = sym.relu(data=bn1, name=name + "_relu1")
     # pointwise convolution + bn + relu
diff --git a/nnvm/python/nnvm/testing/utils.py b/nnvm/python/nnvm/testing/utils.py
index fcc008e61fc3..d6c03fc1b745 100644
--- a/nnvm/python/nnvm/testing/utils.py
+++ b/nnvm/python/nnvm/testing/utils.py
@@ -46,7 +46,7 @@ def create_workload(net, batch_size, image_shape=(3, 224, 224),
     input_shapes, _ = graph_util.infer_shape(g, data=data_shape)
     shape_dict = dict(zip(g.index.input_names, input_shapes))
     np.random.seed(seed)
-    initializer = initializer if initializer else Xavier(magnitude=3)
+    initializer = initializer if initializer else Xavier()
     for k, v in shape_dict.items():
         if k == "data":
             continue
diff --git a/nnvm/src/README.md b/nnvm/src/README.md
index da3584a73cb1..adae68105650 100644
--- a/nnvm/src/README.md
+++ b/nnvm/src/README.md
@@ -7,8 +7,7 @@ The following components are operator invariant.
 - core: NNVM core data structure
 - pass: NNVM pass
 
-The following components are generic graph compiler for NNVM-TOP
+The following components are generic NNVM compiler and defines tensor operator set
 
-- top: NNVM-TOP core operator defs
-- tvm: NNVM-TOP to TVM compiler toolchain
-- runtime: NNVM-TOP runtime
+- top: NNVM core tensor operators
+- compiler: NNVM compiler toolchain
diff --git a/nnvm/src/compiler/compile_engine.cc b/nnvm/src/compiler/compile_engine.cc
index d31612f5a826..e345497e2512 100644
--- a/nnvm/src/compiler/compile_engine.cc
+++ b/nnvm/src/compiler/compile_engine.cc
@@ -58,7 +58,7 @@ class CompileEngine {
       return it->second->graph_func;
     }
     GraphFunc f = DoLower(key->graph, key->inputs, key->target,
-                           schedule_op_key, schedule_op_attr);
+                          schedule_op_key, schedule_op_attr);
     std::shared_ptr<GraphCacheEntryNode> n = std::make_shared<GraphCacheEntryNode>();
     n->graph_func = f;
     n->use_count = 1;
diff --git a/nnvm/src/compiler/fold_scale_axis.cc b/nnvm/src/compiler/fold_scale_axis.cc
new file mode 100644
index 000000000000..eb0ea4b87292
--- /dev/null
+++ b/nnvm/src/compiler/fold_scale_axis.cc
@@ -0,0 +1,271 @@
+/*!
+ * Copyright (c) 2017 by Contributors
+ * \file fold_scale_axis.cc
+ * \author Fold scaling parameter of axis into weight of conv/dense
+*/
+#include <nnvm/graph.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/graph_attr_types.h>
+#include <nnvm/pass.h>
+#include <nnvm/compiler/op_attr_types.h>
+#include <nnvm/top/nn.h>
+#include "./pattern_util.h"
+#include "./graph_transform.h"
+
+namespace nnvm {
+namespace compiler {
+
+enum FoldScaleKind {
+  // No folding is applied
+  kNone,
+  // The folding decision is pending
+  kPending,
+  // The original operator that contains the scale.
+  kProvider,
+  // Pass through the scale to parent/child to the first axis.
+  kPassTroughFirst,
+  // The final conumer of axis scale using multiply
+  // Likely be a conv or dense operator.
+  kMulConsumer,
+  // The final conumer of axis scale using division
+  kDivConsumer
+};
+
+// Input fold information
+struct FoldScaleInput {
+  uint32_t index;
+  int axis;
+};
+
+// The entry of folding chains on which
+// we should perform folding on
+struct FoldChainEntry {
+  // Entry kind
+  FoldScaleKind kind{kNone};
+  // The output axis to be folded
+  int axis{0};
+  // Source node in the fold chain
+  int source{0};
+  // Following field only used by provider.
+  // The input index
+  int fold_input_index{1};
+  // The scale entry
+  NodeEntry scale_entry;
+};
+
+// Try to pass axis scaling to backward,
+// Given that we we know the status of current fold axis.
+using FScaleAxisBackward = std::function<
+  FoldScaleKind(const NodeAttrs& attrs,
+                int axis,
+                const std::vector<TShape>& in_shape,
+                const std::vector<TShape>& out_shape,
+                std::vector<std::pair<uint32_t, int> >* in_axis)>;
+
+// Detect if there is a scaling axis happening
+bool DetectScaleAxis(const IndexedGraph& idx,
+                     uint32_t nid,
+                     const ShapeVector& shape_vec,
+                     const std::vector<uint32_t>& ref_count,
+                     bool is_forward,
+                     std::vector<FoldChainEntry>* chain) {
+  const IndexedGraph::Node& inode = idx[nid];
+  static const Op* bcast_mul = Op::Get("broadcast_mul");
+  static const Op* expand_dims = Op::Get("expand_dims");
+  if (inode.source->op() != bcast_mul) return false;
+  const TShape& oshape = shape_vec[idx.entry_id(nid, 0)];
+  CHECK_NE(oshape.ndim(), 0);
+  if (oshape.ndim() <= 1) return false;
+  for (int i = 0; i < 2; ++i) {
+    const IndexedGraph::NodeEntry& a = inode.inputs[i];
+    const IndexedGraph::NodeEntry& b = inode.inputs[1 - i];
+    std::pair<int, int> axis =
+        MatchBroadcast1DAxis(oshape, shape_vec[idx.entry_id(a)]);
+    if (axis.first != -1 &&
+        shape_vec[idx.entry_id(b)] == oshape) {
+      if (ref_count[a.node_id] != 1) return false;
+      if (is_forward && ref_count[nid] != 1) return false;
+      if (!is_forward && ref_count[b.node_id] != 1) return false;
+      const IndexedGraph::Node& anode = idx[a.node_id];
+      // mark the current entry.
+      FoldChainEntry& e = (*chain)[nid];
+      if (anode.source->is_variable()) {
+        e.fold_input_index = 1 - i;
+        e.scale_entry = inode.source->inputs[1 - i];
+      } else if (anode.source->op()  == expand_dims &&
+                 shape_vec[idx.entry_id(anode.source->inputs[0])].ndim() == 1) {
+        e.fold_input_index = 1 - i;
+        e.scale_entry = anode.source->inputs[0];
+      } else {
+        return false;
+      }
+      e.axis = axis.first;
+      e.kind = kPending;
+      e.source = nid;
+      if (!is_forward) {
+        // pass message to another input
+        FoldChainEntry& enext = (*chain)[b.node_id];
+        enext.axis = e.axis;
+        enext.kind = kPending;
+        enext.source = nid;
+      }
+      return true;
+    }
+  }
+  return false;
+}
+
+Graph FoldScaleAxis(Graph src) {
+  // Operator pattern
+  static auto& fbackward =
+      nnvm::Op::GetAttr<FScaleAxisBackward>("FScaleAxisBackward");
+  const IndexedGraph& idx = src.indexed_graph();
+  const ShapeVector& shape_vec = src.GetAttr<ShapeVector>("shape");
+  std::vector<uint32_t> ref_count = GetNodeRefCounts(idx);
+  std::vector<FoldChainEntry> bwd_chain(idx.num_nodes());
+  // shape hint for the inference.
+  std::vector<TShape> in_shape, out_shape;
+  // perform backward folding.
+  for (uint32_t i = idx.num_nodes(); i != 0; --i) {
+    uint32_t nid = i - 1;
+    const auto& inode = idx[nid];
+    if (inode.source->is_variable()) continue;
+    if (DetectScaleAxis(idx, nid, shape_vec,
+                        ref_count, false, &bwd_chain)) continue;
+    if (bwd_chain[nid].kind != kPending) continue;
+    if (ref_count[nid] != 1 || !fbackward.count(inode.source->op())) {
+      bwd_chain[nid].kind = kNone; continue;
+    }
+    // get input shape and output shape.
+    in_shape.clear(); out_shape.clear();
+    for (const IndexedGraph::NodeEntry& e : inode.inputs) {
+      in_shape.push_back(shape_vec[idx.entry_id(e)]);
+    }
+    for (uint32_t i = 0; i < inode.source->num_outputs(); ++i) {
+      out_shape.push_back(shape_vec[idx.entry_id(nid, i)]);
+    }
+    std::vector<std::pair<uint32_t, int> > in_axis;
+    FoldScaleKind kind =
+        fbackward[inode.source->op()](
+            inode.source->attrs, bwd_chain[nid].axis,
+            in_shape, out_shape, &in_axis);
+    bwd_chain[nid].kind = kind;
+    if (kind == kNone) continue;
+    CHECK_GE(in_axis.size(), 1U);
+    CHECK(kind == kPassTroughFirst || kMulConsumer);
+    // propagate back.
+    bool can_prop = true;
+    for (size_t i = 0; i < in_axis.size(); ++i) {
+      const IndexedGraph::NodeEntry& e = inode.inputs[in_axis[0].first];
+      if (ref_count[e.node_id] != 1 ||
+          idx[e.node_id].source->num_outputs() != 1) {
+        can_prop = false; break;
+      }
+    }
+    if (!can_prop) continue;
+    for (size_t i = 0; i < in_axis.size(); ++i) {
+      const IndexedGraph::NodeEntry& e = inode.inputs[in_axis[i].first];
+      if (kind == kPassTroughFirst && i == 0) {
+        bwd_chain[e.node_id].kind = kPending;
+      } else {
+        bwd_chain[nid].kind = kNone;
+        bwd_chain[e.node_id].kind = kMulConsumer;
+      }
+      bwd_chain[e.node_id].axis = in_axis[i].second;
+      bwd_chain[e.node_id].source = bwd_chain[nid].source;
+    }
+    if (kind == kMulConsumer) {
+      bwd_chain[bwd_chain[nid].source].kind = kProvider;
+    }
+  }
+  auto transform = [&](uint32_t nid, const NodePtr& n, std::vector<NodeEntry>* ret) {
+    const FoldChainEntry& e = bwd_chain[nid];
+    if (e.kind == kMulConsumer && bwd_chain[e.source].kind == kProvider) {
+      const FoldChainEntry& se = bwd_chain[e.source];
+      CHECK_EQ(n->num_outputs(), 1);
+      NodeEntry scale = ExpandBiasToMatchAxis(
+          se.scale_entry,
+          shape_vec[idx.entry_id(nid, 0)].ndim(),
+          shape_vec[idx.entry_id(se.scale_entry)].ndim(),
+          e.axis);
+      *ret = {MakeNode("broadcast_mul", n->attrs.name + "_sc",
+                       {NodeEntry{n, 0, 0}, scale})};
+      return true;
+    } else if (e.kind == kProvider) {
+      *ret = {n->inputs[e.fold_input_index]};
+      return true;
+    } else {
+      return false;
+    }
+  };
+  return GraphTransform(src, transform);
+}
+
+NNVM_REGISTER_PASS(FoldScaleAxis)
+.set_body(FoldScaleAxis);
+
+// property registration.
+FoldScaleKind ReluScaleAxisBackward(
+    const NodeAttrs& attrs,
+    int axis,
+    const std::vector<TShape>& in_shape,
+    const std::vector<TShape>& out_shape,
+    std::vector<std::pair<uint32_t, int> >* in_axis) {
+  in_axis->emplace_back(0, axis);
+  return kPassTroughFirst;
+}
+
+NNVM_REGISTER_OP(relu)
+.set_attr<FScaleAxisBackward>("FScaleAxisBackward", ReluScaleAxisBackward);
+
+NNVM_REGISTER_OP(leaky_relu)
+.set_attr<FScaleAxisBackward>("FScaleAxisBackward", ReluScaleAxisBackward);
+
+FoldScaleKind BroadcastAddSubScaleAxisBackward(
+    const NodeAttrs& attrs,
+    int axis,
+    const std::vector<TShape>& in_shape,
+    const std::vector<TShape>& out_shape,
+    std::vector<std::pair<uint32_t, int> >* in_axis) {
+  for (int i = 0; i < 2; ++i) {
+    std::pair<int, int> m = MatchBroadcast1DAxis(out_shape[0], in_shape[i]);
+    if (m.second != -1 && in_shape[1 - i] == out_shape[0]) {
+      in_axis->emplace_back(i, axis);
+      in_axis->emplace_back(1 - i, m.second);
+      return kPassTroughFirst;
+    }
+  }
+  return kNone;
+}
+
+NNVM_REGISTER_OP(broadcast_add)
+.set_attr<FScaleAxisBackward>("FScaleAxisBackward", BroadcastAddSubScaleAxisBackward);
+
+NNVM_REGISTER_OP(broadcast_sub)
+.set_attr<FScaleAxisBackward>("FScaleAxisBackward", BroadcastAddSubScaleAxisBackward);
+
+FoldScaleKind Conv2DScaleAxisBackward(
+    const NodeAttrs& attrs,
+    int axis,
+    const std::vector<TShape>& in_shape,
+    const std::vector<TShape>& out_shape,
+    std::vector<std::pair<uint32_t, int> >* in_axis) {
+  using top::Conv2DParam;
+  const Conv2DParam& param = nnvm::get<Conv2DParam>(attrs.parsed);
+  // only optimize for nchw for now
+  if (param.layout == top::kNCHW) {
+    in_axis->emplace_back(1, 0);
+    if (param.use_bias) {
+      in_axis->emplace_back(2, 0);
+    }
+    return kMulConsumer;
+  } else {
+    return kNone;
+  }
+}
+
+NNVM_REGISTER_OP(conv2d)
+.set_attr<FScaleAxisBackward>("FScaleAxisBackward", Conv2DScaleAxisBackward);
+
+}  // namespace compiler
+}  // namespace nnvm
diff --git a/nnvm/src/compiler/graph_fuse.cc b/nnvm/src/compiler/graph_fuse.cc
index b8c6a1adc02c..0f11fe6d46ac 100644
--- a/nnvm/src/compiler/graph_fuse.cc
+++ b/nnvm/src/compiler/graph_fuse.cc
@@ -16,6 +16,7 @@
 #include <dmlc/parameter.h>
 #include "./compile_engine.h"
 #include "./graph_runtime.h"
+#include "./pattern_util.h"
 
 namespace nnvm {
 namespace compiler {
@@ -56,17 +57,10 @@ nnvm::Graph GraphFusePartition(nnvm::Graph g) {
 
   // Reference counter of each op node
   // For now, always store result when an op is referred more than once.
-  std::vector<uint32_t> ref_count(idx.num_nodes(), 0);
-  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
-    const auto& inode = idx[nid];
-    if (inode.source->is_variable()) continue;
-    for (const auto& e : inode.inputs) {
-      ++ref_count[e.node_id];
-    }
-  }
+  std::vector<uint32_t> ref_count = GetNodeRefCounts(idx);
   for (const auto& e : idx.outputs()) {
     // this line will realize all the outputs
-    ref_count[e.node_id] += 2;
+    ref_count[e.node_id] += 1;
   }
   // Pattern for the subgraph
   std::vector<TOpPattern> pattern_vec(idx.num_nodes(),  kOpaque);
diff --git a/nnvm/src/compiler/graph_transform.h b/nnvm/src/compiler/graph_transform.h
index 2099809115aa..dd80accbee3f 100644
--- a/nnvm/src/compiler/graph_transform.h
+++ b/nnvm/src/compiler/graph_transform.h
@@ -20,7 +20,7 @@ namespace compiler {
  *
  * \param graph The original graph
  *
- * \param ftransform Function of (int nid, const Node* node, std::vector<NodeEntry>* out) -> bool
+ * \param ftransform Function of (int nid, const NodePtr& node, std::vector<NodeEntry>* out) -> bool
  *
  *      If empty vector is returned, it means original entries should be kept.
  *
@@ -36,7 +36,6 @@ Graph GraphTransform(Graph graph, FTransform ftransform) {
   // setup inputs and placeholder.
   for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
     const auto& inode = idx[nid];
-    if (inode.source->is_variable()) continue;
     bool need_copy = false;
     for (const IndexedGraph::NodeEntry& e : inode.inputs) {
       if (updated[idx.entry_id(e)]) {
@@ -57,7 +56,7 @@ Graph GraphTransform(Graph graph, FTransform ftransform) {
 
     if (!need_copy) {
       std::vector<NodeEntry> ret;
-      if (ftransform(nid, inode.source, &ret)) {
+      if (ftransform(nid, inode.weak_ref.lock(), &ret)) {
         CHECK_EQ(ret.size(), static_cast<size_t>(inode.source->num_outputs()));
         for (uint32_t i = 0 ; i < inode.source->num_outputs(); ++i) {
           updated[idx.entry_id(nid, i)] = true;
@@ -93,7 +92,7 @@ Graph GraphTransform(Graph graph, FTransform ftransform) {
         node->control_deps.push_back(selected_ptr);
       }
       std::vector<NodeEntry> ret;
-      if (ftransform(nid, node.get(), &ret)) {
+      if (ftransform(nid, node, &ret)) {
         CHECK_EQ(ret.size(), static_cast<size_t>(inode.source->num_outputs()));
         for (uint32_t i = 0 ; i < inode.source->num_outputs(); ++i) {
           updated[idx.entry_id(nid, i)] = true;
diff --git a/nnvm/src/compiler/pattern_util.h b/nnvm/src/compiler/pattern_util.h
new file mode 100644
index 000000000000..10322a7dce85
--- /dev/null
+++ b/nnvm/src/compiler/pattern_util.h
@@ -0,0 +1,99 @@
+/*!
+ * Copyright (c) 2017 by Contributors
+ * \file pattern_util.h
+ * \brief Utilities for doing various pattern matching in graph.
+*/
+#ifndef NNVM_COMPILER_PATTERN_UTIL_H_
+#define NNVM_COMPILER_PATTERN_UTIL_H_
+
+#include <nnvm/graph.h>
+#include <vector>
+#include <utility>
+#include <string>
+
+namespace nnvm {
+namespace compiler {
+
+/*!
+ * \brief find axis in oshape, such that:
+ *  bias_shape = [1,1, ... oshape[axis], 1,1,]
+ *
+ *  This is used to detect bias or scaling factor on channel dimension.
+ * \param oshape The output shape
+ * \param bias_shape The shape of bias or scaling factor.
+ * \return Pair of matched axis in o shape and bias_shape if found.
+ */
+inline std::pair<int, int> MatchBroadcast1DAxis(
+    const TShape& oshape, const TShape& bias_shape) {
+  dim_t axis_dim = bias_shape.ndim();
+  for (dim_t i = bias_shape.ndim(); i != 0; --i, --axis_dim) {
+    if (bias_shape[i - 1] != 1) break;
+  }
+  // everything is 1
+  if (axis_dim == 0) {
+    return {oshape.ndim()  - bias_shape.ndim(), 0};
+  }
+  axis_dim = axis_dim - 1;
+  // The bias shape is not 1D
+  for (dim_t i = 0; i < axis_dim; ++i) {
+    if (bias_shape[i] != 1) return {-1, -1};
+  }
+  int axis = static_cast<int>(
+      oshape.ndim() - bias_shape.ndim() + axis_dim);
+  if (oshape[axis] != bias_shape[axis_dim]) return {-1, -1};
+  return {axis, axis_dim};
+}
+
+/*!
+ * \brief Expand bias dimension to match needed axis.
+ *
+ * \param bias The bias NodeEntry
+ * \param out_dim output dimension.
+ * \param bias_dim The current bias dimension.
+ * \param axis The axis we want to match on.
+ */
+inline NodeEntry
+ExpandBiasToMatchAxis(NodeEntry bias,
+                      int out_dim,
+                      int bias_dim,
+                      int axis) {
+  if (bias_dim != 1) {
+    bias = MakeNode("squeeze", bias.node->attrs.name + "_sqz", {bias});
+  }
+  int num_pad_axis = out_dim - axis - 1;
+  if (num_pad_axis > 0) {
+    std::unordered_map<std::string, std::string> kwargs{
+      {"axis", "1"},
+      {"num_newaxis", std::to_string(num_pad_axis)}};
+    return MakeNode("expand_dims", bias.node->attrs.name + "_expand",
+                    {bias}, kwargs);
+
+  } else {
+    return bias;
+  }
+}
+
+/*!
+ * \brief Get the reference count of each node.
+ * \param idx The IndexedGraph
+ * \return ref_count vector of length number nodes.
+ */
+inline std::vector<uint32_t>
+GetNodeRefCounts(const IndexedGraph& idx) {
+  std::vector<uint32_t> ref_count(idx.num_nodes(), 0);
+  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+    const auto& inode = idx[nid];
+    if (inode.source->is_variable()) continue;
+    for (const auto& e : inode.inputs) {
+      ++ref_count[e.node_id];
+    }
+  }
+  for (const auto& e : idx.outputs()) {
+    // this line will realize all the outputs
+    ref_count[e.node_id] += 1;
+  }
+  return ref_count;
+}
+}  // namespace compiler
+}  // namespace nnvm
+#endif  //  NNVM_COMPILER_PATTERN_UTIL_H_
diff --git a/nnvm/src/compiler/simplify_inference.cc b/nnvm/src/compiler/simplify_inference.cc
index 7dd9ade0ac96..141950b0545d 100644
--- a/nnvm/src/compiler/simplify_inference.cc
+++ b/nnvm/src/compiler/simplify_inference.cc
@@ -1,6 +1,6 @@
 /*!
  * Copyright (c) 2017 by Contributors
- * \file simplify_batch_norm.cc
+ * \file simplify_inference.cc
  * \author Ziheng Jiang
 */
 #include <nnvm/graph.h>
@@ -10,6 +10,7 @@
 #include <nnvm/compiler/op_attr_types.h>
 #include <nnvm/top/nn.h>
 #include "./graph_transform.h"
+#include "./pattern_util.h"
 
 namespace nnvm {
 namespace compiler {
@@ -58,15 +59,9 @@ BatchNormToInferUnpack(const nnvm::NodeAttrs& attrs,
     shift = MakeNode(
         "elemwise_add", bn_name + "_add_beta", {shift, beta});
   }
-  // use expand dims to pad lower dims to 1
-  int num_pad_axis = static_cast<int>(dshape.ndim() - param.axis) - 1;
-  if (num_pad_axis != 0) {
-    std::unordered_map<std::string, std::string> kwargs{
-      {"axis", std::to_string(param.axis)},
-      {"num_newaxis", std::to_string(num_pad_axis)}};
-    scale = MakeNode("expand_dims", bn_name + "_sc_expand", {scale}, kwargs);
-    shift = MakeNode("expand_dims", bn_name + "_sh_expand", {shift}, kwargs);
-  }
+  int axis = param.axis;
+  scale = ExpandBiasToMatchAxis(scale, dshape.ndim(), 1, axis);
+  shift = ExpandBiasToMatchAxis(shift, dshape.ndim(), 1, axis);
   NodeEntry out = MakeNode("broadcast_mul", bn_name + "_a_mul_data",
                            {data, scale});
   out = MakeNode("broadcast_add", bn_name + "_out",
@@ -80,7 +75,7 @@ Graph SimplifyInference(nnvm::Graph src) {
   // Get attributes from the graph
   const IndexedGraph& idx = src.indexed_graph();
   const ShapeVector& shape_vec = src.GetAttr<ShapeVector>("shape");
-  auto transform = [&](uint32_t nid, const Node* n, std::vector<NodeEntry>* ret) {
+  auto transform = [&](uint32_t nid, const NodePtr& n, std::vector<NodeEntry>* ret) {
     if (n->is_variable()) return false;
     static const Op* bn_op = Op::Get("batch_norm");
     static const Op* dropout_op = Op::Get("dropout");
diff --git a/nnvm/src/core/graph.cc b/nnvm/src/core/graph.cc
index d1b6efb66dbb..62c7085c1210 100644
--- a/nnvm/src/core/graph.cc
+++ b/nnvm/src/core/graph.cc
@@ -28,6 +28,7 @@ IndexedGraph::IndexedGraph(const Graph &g) {
       // nodes_
       IndexedGraph::Node new_node;
       new_node.source = n.get();
+      new_node.weak_ref = n;
       nodes_.emplace_back(std::move(new_node));
       // arg_nodes_
       if (n->is_variable()) {
diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
index 12ae06731145..c3ca3681cba9 100644
--- a/nnvm/src/top/tensor/transform.cc
+++ b/nnvm/src/top/tensor/transform.cc
@@ -460,21 +460,21 @@ inline bool SqueezeShape(const nnvm::NodeAttrs& attrs,
   std::vector<int64_t> oshape;
   if (param.axis.ndim() == 0) {
     for (dim_t i = 0; i < shp.ndim(); ++i) {
-      if(shp[i] != 1) {
+      if (shp[i] != 1) {
         oshape.emplace_back(shp[i]);
       }
     }
   } else {
     std::unordered_set<dim_t> axis_checker;
     for (size_t i = 0; i < param.axis.ndim(); ++i) {
-      if(param.axis[i] < 0) {
+      if (param.axis[i] < 0) {
         int real_axis = param.axis[i] + static_cast<int>(shp.ndim());
         CHECK(real_axis < static_cast<int>(shp.ndim()) && real_axis >= 0);
         axis_checker.insert(real_axis);
       }
     }
     for (size_t i = 0; i < shp.ndim(); ++i) {
-      if(axis_checker.find(i) == axis_checker.end()) {
+      if (axis_checker.find(i) == axis_checker.end()) {
         oshape.emplace_back(shp[i]);
       } else {
         CHECK_EQ(shp[i], 1) << "The squeezed axis must have shape 1!"
@@ -483,7 +483,7 @@ inline bool SqueezeShape(const nnvm::NodeAttrs& attrs,
       }
     }
   }
-  if(oshape.size() == 0) {
+  if (oshape.size() == 0) {
     // Handles the case where all axes are squeezed.
     oshape.push_back(1);
   }
diff --git a/nnvm/tests/python/compiler/test_fold_axis.py b/nnvm/tests/python/compiler/test_fold_axis.py
new file mode 100644
index 000000000000..f306b3a222aa
--- /dev/null
+++ b/nnvm/tests/python/compiler/test_fold_axis.py
@@ -0,0 +1,49 @@
+"""Unittest cases for fold_axis"""
+import nnvm
+from nnvm import symbol as sym
+from nnvm.compiler import graph_util, graph_attr
+
+def test_fold_axis_conv():
+    def before(x, conv_weight, conv_bias, scale, channels):
+        y = sym.conv2d(x, conv_weight, conv_bias,
+                       channels=channels,
+                       kernel_size=(3, 3),
+                       padding=(1, 1),
+                       name="conv")
+        y = sym.relu(y)
+        y = y * sym.expand_dims(scale, axis=1, num_newaxis=2)
+        return y
+
+    def expected(x, conv_weight, conv_bias, scale, channels):
+        conv_weight = conv_weight * sym.expand_dims(scale, axis=1, num_newaxis=3)
+        conv_bias = conv_bias * scale
+        y = sym.conv2d(x,
+                       conv_weight,
+                       conv_bias,
+                       channels=channels,
+                       kernel_size=(3, 3),
+                       padding=(1, 1),
+                       name="conv")
+        y = sym.relu(y)
+        return y
+
+    # Before simplify
+    def check(shape, channels):
+        x = sym.Variable("x") + 1
+        weight = sym.Variable("weight")
+        bias = sym.Variable("bias")
+        scale = sym.Variable("scale")
+        y1 = before(x, weight, bias, scale, channels)
+        y2 = expected(x, weight, bias, scale, channels)
+        ishape = {"x": shape, "scale": (channels,)}
+        g1 = nnvm.graph.create(y1)
+        g2 = nnvm.graph.create(y2)
+        graph_attr.set_shape_inputs(g1, ishape)
+        g1 = g1.apply("InferShape").apply("FoldScaleAxis")
+        # assert graph equals as expected
+        graph_util.check_graph_equal(g1, g2)
+
+    check((2, 4, 10, 10), 2)
+
+if __name__ == "__main__":
+    test_fold_axis_conv()
diff --git a/nnvm/tests/python/compiler/test_simplify_inference.py b/nnvm/tests/python/compiler/test_simplify_inference.py
index 5da46608081a..e2826765995e 100644
--- a/nnvm/tests/python/compiler/test_simplify_inference.py
+++ b/nnvm/tests/python/compiler/test_simplify_inference.py
@@ -14,8 +14,8 @@ def simple_bn(x, gamma, beta, moving_mean, moving_var,
         # for 2D
         num_newaxis=len(shape) - axis - 1
         if num_newaxis:
-            scale = sym.expand_dims(scale, axis=axis, num_newaxis=num_newaxis)
-            shift = sym.expand_dims(shift, axis=axis, num_newaxis=num_newaxis)
+            scale = sym.expand_dims(scale, axis=1, num_newaxis=num_newaxis)
+            shift = sym.expand_dims(shift, axis=1, num_newaxis=num_newaxis)
         return x * scale + shift
 
 
@@ -39,8 +39,6 @@ def check(dim, axis, nstep):
         g2 = nnvm.graph.create(y2)
         graph_attr.set_shape_inputs(g, ishape)
         g1 = g.apply("InferShape").apply("SimplifyInference")
-        # Some prints for debug
-        # print(g1.ir())
         # assert graph equals as expected
         graph_util.check_graph_equal(g1, g2)
 
diff --git a/nnvm/tests/python/frontend/mxnet/test_forward.py b/nnvm/tests/python/frontend/mxnet/test_forward.py
index d19aa5664fdf..ca7a0156a8b5 100644
--- a/nnvm/tests/python/frontend/mxnet/test_forward.py
+++ b/nnvm/tests/python/frontend/mxnet/test_forward.py
@@ -28,7 +28,8 @@ def get_tvm_output(symbol, x, args, auxs, target, ctx, dtype='float32'):
         new_sym, params = frontend.from_mxnet(symbol, args, auxs)
         dshape = x.shape
         shape_dict = {'data': dshape}
-        graph, lib, params = nnvm.compiler.build(new_sym, target, shape_dict, params=params)
+        with nnvm.compiler.build_config(opt_level=3):
+            graph, lib, params = nnvm.compiler.build(new_sym, target, shape_dict, params=params)
         m = graph_runtime.create(graph, lib, ctx)
         # set inputs
         m.set_input("data", tvm.nd.array(x.astype(dtype)))

From 508078ab1aa02074a5f03a6a60d2c3d3b6a6859b Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 29 Sep 2017 16:43:53 -0700
Subject: [PATCH 509/949] [DOCS] Add install docs, fix Jenkins (#57)

---
 nnvm/Jenkinsfile                        |  10 +--
 nnvm/Makefile                           |   8 +-
 nnvm/NEWS.md                            |  17 ++++
 nnvm/README.md                          |   9 ++-
 nnvm/docs/how_to/contribute.md          | 102 ++++++++++++++++++++++++
 nnvm/docs/how_to/deploy.md              |  37 +++++++++
 nnvm/docs/how_to/install.md             |  60 ++++++++++++++
 nnvm/docs/index.rst                     |   3 +
 nnvm/python/nnvm/compiler/param_dict.py |   4 +-
 nnvm/python/nnvm/libinfo.py             |   2 +-
 nnvm/src/compiler/graph_fuse.cc         |   6 +-
 nnvm/src/top/tensor/transform.cc        |   2 +-
 nnvm/tests/ci_build/Dockerfile.gpu      |   2 +
 nnvm/tutorials/from_mxnet.py            |   2 +-
 nnvm/tutorials/from_onnx.py             |   2 +-
 15 files changed, 245 insertions(+), 21 deletions(-)
 create mode 100644 nnvm/NEWS.md
 create mode 100644 nnvm/docs/how_to/contribute.md
 create mode 100644 nnvm/docs/how_to/deploy.md
 create mode 100644 nnvm/docs/how_to/install.md

diff --git a/nnvm/Jenkinsfile b/nnvm/Jenkinsfile
index de3cfd3021af..b299cee33f48 100644
--- a/nnvm/Jenkinsfile
+++ b/nnvm/Jenkinsfile
@@ -4,7 +4,7 @@
 // See documents at https://jenkins.io/doc/book/pipeline/jenkinsfile/
 
 // nnvm libraries
-nnvm_lib = "tvm/lib/libtvm.so, tvm/lib/libtvm_runtime.so, lib/libnnvm_top.so, config.mk"
+nnvm_lib = "tvm/lib/libtvm.so, tvm/lib/libtvm_runtime.so, lib/libnnvm_compiler.so"
 
 // command to start a docker container
 docker_run = 'tests/ci_build/ci_build.sh'
@@ -47,11 +47,11 @@ stage("Sanity Check") {
 def make(docker_type, make_flag) {
   timeout(time: max_time, unit: 'MINUTES') {
     try {
-      sh "${docker_run} ${docker_type} ./tests/script/task_build.sh ${make_flag}"
+      sh "${docker_run} ${docker_type} ./tests/scripts/task_build.sh ${make_flag}"
     } catch (exc) {
       echo 'Incremental compilation failed. Fall back to build from scratch'
-      sh "${docker_run} ${docker_type} ./tests/script/task_clean.sh"
-      sh "${docker_run} ${docker_type} ./tests/script/task_build.sh ${make_flag}"
+      sh "${docker_run} ${docker_type} ./tests/scripts/task_clean.sh"
+      sh "${docker_run} ${docker_type} ./tests/scripts/task_build.sh ${make_flag}"
     }
   }
 }
@@ -119,7 +119,7 @@ stage('Deploy') {
       ws('workspace/nnvm/deploy-docs') {
         if (env.BRANCH_NAME == "master") {
            unpack_lib('mydocs', 'docs.tgz')
-           sh "tar xf docs.tgz -C /var/docs"
+           sh "tar xf docs.tgz -C /var/nnvm-docs"
         }
       }
     }
diff --git a/nnvm/Makefile b/nnvm/Makefile
index 24a23b054349..7d9992f05b09 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -53,12 +53,12 @@ else
 	NO_WHOLE_ARCH= --no-whole-archive
 endif
 
-all: lib/libnnvm.a lib/libnnvm_top.$(SHARED_LIBRARY_SUFFIX)
+all: lib/libnnvm.a lib/libnnvm_compiler.$(SHARED_LIBRARY_SUFFIX)
 
 SRC = $(wildcard src/*.cc src/c_api/*.cc src/core/*.cc src/pass/*.cc)
-SRC_TOP = $(wildcard src/top/*/*.cc src/compiler/*.cc src/compiler/*/*.cc)
+SRC_COMPILER = $(wildcard src/top/*/*.cc src/compiler/*.cc src/compiler/*/*.cc)
 ALL_OBJ = $(patsubst %.cc, build/%.o, $(SRC))
-TOP_OBJ = $(patsubst %.cc, build/%.o, $(SRC_TOP))
+TOP_OBJ = $(patsubst %.cc, build/%.o, $(SRC_COMPILER))
 ALL_DEP = $(ALL_OBJ)
 
 include tests/cpp/unittest.mk
@@ -74,7 +74,7 @@ lib/libnnvm.a: $(ALL_DEP)
 	@mkdir -p $(@D)
 	ar crv $@ $(filter %.o, $?)
 
-lib/libnnvm_top.$(SHARED_LIBRARY_SUFFIX): lib/libnnvm.a ${TOP_OBJ}
+lib/libnnvm_compiler.$(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}
 
diff --git a/nnvm/NEWS.md b/nnvm/NEWS.md
new file mode 100644
index 000000000000..8093e9ef375c
--- /dev/null
+++ b/nnvm/NEWS.md
@@ -0,0 +1,17 @@
+NNVM Change Log
+===============
+
+This file records the changes in TVM library in reverse chronological order.
+
+## 0.8rc
+
+- This is major change in NNVM to introduce end to end compiler stack.
+  - The NNVM compiler stack ready
+- Core tensor operators
+- integrates compiler with TVM
+- The libnnvm.a is still independent from compiler modules.
+
+## 0.7
+
+- NNVM graph
+- Basic pass of serialization, gradient, infer_shape, place_deice, plan_memory
diff --git a/nnvm/README.md b/nnvm/README.md
index d9bd4aa0d2a4..1a33568c863d 100644
--- a/nnvm/README.md
+++ b/nnvm/README.md
@@ -1,18 +1,19 @@
-# NNVM: Graph IR Stack for Deep Learning Systems
+# NNVM: Open Compiler for AI Frameworks
 
 [![Build Status](https://travis-ci.org/dmlc/nnvm.svg?branch=master)](https://travis-ci.org/dmlc/nnvm)
 [![GitHub license](http://dmlc.github.io/img/apache2.svg)](./LICENSE)
 
-NNVM is a reusable computational graph compilation stack for deep learning systems. It provides modules to:
+NNVM compiler offers reusable computation graph optimization and compilation for deep learning systems.
+It is backed by the [TVM stack](http://tvmlang.org) and provides modules to:
 
 - Represent deep learning workloads from front-end frameworks via a graph IR.
 - Optimize computation graphs to improve performance.
 - Compile into executable modules and deploy to different hardware backends with minimum dependency.
 
-NNVM is designed to add new frontend, operators and graph optimizations in a decentralized fashion without changing the core interface. It is part of [TVM stack](https://github.com/dmlc/tvm). The compiler toolchain can target hardware backends supported by TVM.
+NNVM is designed to add new frontend, operators and graph optimizations in a decentralized fashion without changing the core interface.
 The compiled module can be deployed to server, mobile, embedded devices and browsers with minimum dependency, in languages including c++, python, javascript, java, objective-c.
 
-The following code snippet demonstrates the general workflow of nnvm.
+The following code snippet demonstrates the general workflow of nnvm compiler.
 
 ```python
 import tvm
diff --git a/nnvm/docs/how_to/contribute.md b/nnvm/docs/how_to/contribute.md
new file mode 100644
index 000000000000..34ff2f19644c
--- /dev/null
+++ b/nnvm/docs/how_to/contribute.md
@@ -0,0 +1,102 @@
+# Contribute to NNVM
+
+NNVM has been developed by community members.
+Everyone is more than welcome to contribute.
+It is a way to make the project better and more accessible to more users.
+NNVM compiler relies on TVM to deploy to different hardware backends.
+You can improve the compiler performance by contributing to [TVM](https://github.com/dmlc/tvm)
+
+- Please update [NEWS.md](https://github.com/dmlc/nnvm/blob/master/NEWS.md) to
+  add note on your changes to the API or added a new document.
+
+## Guidelines
+* [Submit Pull Request](#submit-pull-request)
+* [Git Workflow Howtos](#git-workflow-howtos)
+  - [How to resolve conflict with master](#how-to-resolve-conflict-with-master)
+  - [How to combine multiple commits into one](#how-to-combine-multiple-commits-into-one)
+  - [What is the consequence of force push](#what-is-the-consequence-of-force-push)
+* [Document](#document)
+* [Testcases](#testcases)
+* [Examples](#examples)
+* [Core Library](#core-library)
+* [Python Package](#python-package)
+
+## Submit Pull Request
+* Before submit, please rebase your code on the most recent version of master, you can do it by
+```bash
+git remote add upstream [url to nnvm repo]
+git fetch upstream
+git rebase upstream/master
+```
+* If you have multiple small commits,
+  it might be good to merge them together(use git rebase then squash) into more meaningful groups.
+* Send the pull request!
+  - Fix the problems reported by automatic checks
+  - If you are contributing a new module or new function, add a test.
+
+## Git Workflow Howtos
+### How to resolve conflict with master
+- First rebase to most recent master
+```bash
+# The first two steps can be skipped after you do it once.
+git remote add upstream [url to nnvm repo]
+git fetch upstream
+git rebase upstream/master
+```
+- The git may show some conflicts it cannot merge, say ```conflicted.py```.
+  - Manually modify the file to resolve the conflict.
+  - After you resolved the conflict, mark it as resolved by
+```bash
+git add conflicted.py
+```
+- Then you can continue rebase by
+```bash
+git rebase --continue
+```
+- Finally push to your fork, you may need to force push here.
+```bash
+git push --force
+```
+
+### How to combine multiple commits into one
+Sometimes we want to combine multiple commits, especially when later commits are only fixes to previous ones,
+to create a PR with set of meaningful commits. You can do it by following steps.
+- Before doing so, configure the default editor of git if you haven't done so before.
+```bash
+git config core.editor the-editor-you-like
+```
+- Assume we want to merge last 3 commits, type the following commands
+```bash
+git rebase -i HEAD~3
+```
+- It will pop up an text editor. Set the first commit as ```pick```, and change later ones to ```squash```.
+- After you saved the file, it will pop up another text editor to ask you modify the combined commit message.
+- Push the changes to your fork, you need to force push.
+```bash
+git push --force
+```
+
+### Reset to the most recent master
+You can always use git reset to reset your version to the most recent master.
+Note that all your ***local changes will get lost***.
+So only do it when you do not have local changes or when your pull request just get merged.
+```bash
+git reset --hard [hash tag of master]
+git push --force
+```
+
+### What is the consequence of force push
+The previous two tips requires force push, this is because we altered the path of the commits.
+It is fine to force push to your own fork, as long as the commits changed are only yours.
+
+## Testcases
+- All the testcases are in tests
+
+## Core Library
+- Follow Google C style for C++.
+- We use doxygen to document all the interface code.
+- You can reproduce the linter checks by typing ```make lint```
+
+## Python Package
+- Always add docstring to the new functions in numpydoc format.
+- You can reproduce the linter checks by typing ```make lint```
diff --git a/nnvm/docs/how_to/deploy.md b/nnvm/docs/how_to/deploy.md
new file mode 100644
index 000000000000..d0cd26a0adc2
--- /dev/null
+++ b/nnvm/docs/how_to/deploy.md
@@ -0,0 +1,37 @@
+Deploy Compiled Modules
+=======================
+NNVM compiled modules are fully embedded in TVM runtime as long as ```GRAPH_RUNTIME``` option
+is enabled in tvm runtime. Check out the [TVM documentation](http://docs.tvmlang.org/) for
+how to deploy TVM runtime to your system.
+
+In a nutshell, we will need three items to deploy a compiled module.
+Checkout our tutorials on getting started with NNVM compiler for more details.
+
+- The graph json data which contains the execution graph.
+- The tvm module library of compiled functions.
+- The parameter blobs for stored parameters.
+
+We can then use TVM's runtime API to deploy the compiled module.
+Here is an example in python.
+
+```python
+import tvm
+
+# tvm module for compiled functions.
+loaded_lib = tvm.module.load("deploy.so")
+# json graph
+loaded_json = open(temp.relpath("deploy.json")).read()
+# parameters in binary
+loaded_params = bytearray(open(temp.relpath("deploy.params"), "rb").read())
+
+fcreate = tvm.get_global_func("tvm.graph_runtime.create")
+ctx = tvm.gpu(0)
+gmodule = fcreate(loaded_json, loaded_lib, ctx.device_type, ctx.device_id)
+set_input, get_output, run = gmodule["set_input"], gmodule["get_output"], gmodule["run"]
+set_input("x", tvm.nd.array(x_np))
+gmodule["load_params"](loaded_params)
+run()
+out = tvm.nd.empty(shape)
+get_output(0, out)
+print(out.asnumpy())
+```
diff --git a/nnvm/docs/how_to/install.md b/nnvm/docs/how_to/install.md
new file mode 100644
index 000000000000..000b96fe9da8
--- /dev/null
+++ b/nnvm/docs/how_to/install.md
@@ -0,0 +1,60 @@
+Installation Guide
+==================
+This page gives instructions on how to build and install the nnvm compiler package from
+scratch on various systems. It consists of two steps:
+
+1. First build the shared library from the C++ codes (`libnnvm_compiler.so` for linux/osx and `libnnvm_compiler.dll` for windows).
+2. Setup for the language packages (e.g. Python Package).
+
+To get started, clone nnvm repo from github. It is important to clone the submodules along, with ```--recursive``` option.
+```bash
+git clone --recursive https://github.com/dmlc/nnvm
+```
+For windows users who use github tools, you can open the git shell, and type the following command.
+```bash
+git submodule init
+git submodule update --recursive
+```
+
+NNVM compiler depend on TVM and TOPI, so make sure you install them by following [TVM document](http://docs.tvmlang.org/)
+
+## Contents
+- [Build the Shared Library](#build-the-shared-library)
+- [Python Package Installation](#python-package-installation)
+
+## Build the Shared Library
+
+Our goal is to build the shared library:
+- On Linux/OSX the target library is `libnnvm_compiler.so`
+- On Windows the target library is `libnnvm_compiler.dll`
+
+The minimal building requirement is
+- A recent c++ compiler supporting C++ 11 (g++-4.8 or higher)
+
+You can edit `make/config.mk` to change the compile options, and then build by
+`make`. If everything goes well, we can go to the specific language installation section.
+
+## Python Package Installation
+
+The python package is located at python
+There are several ways to install the package:
+
+1. Set the environment variable `PYTHONPATH` to tell python where to find
+   the library. For example, assume we cloned `nnvm` on the home directory
+   `~`. then we can added the following line in `~/.bashrc`.
+    It is ***recommended for developers*** who may change the codes.
+    The changes will be immediately reflected once you pulled the code and rebuild the project (no need to call ```setup``` again)
+
+    ```bash
+    export PYTHONPATH=/path/to/nnvm/python:${PYTHONPATH}
+    ```
+
+2. Install tvm python bindings by `setup.py`:
+
+    ```bash
+    # install tvm package for the current user
+    # NOTE: if you installed python via homebrew, --user is not needed during installaiton
+    #       it will be automatically installed to your user directory.
+    #       providing --user flag may trigger error during installation in such case.
+    cd python; python setup.py install --user; cd ..
+    ```
diff --git a/nnvm/docs/index.rst b/nnvm/docs/index.rst
index 8bb6c2820e5e..d4a3243aab96 100644
--- a/nnvm/docs/index.rst
+++ b/nnvm/docs/index.rst
@@ -10,6 +10,9 @@ Contents
 
    self
    top
+   how_to/install
    tutorials/index
+   how_to/contribute
+   how_to/deploy
    api/python/index
    dev/index
diff --git a/nnvm/python/nnvm/compiler/param_dict.py b/nnvm/python/nnvm/compiler/param_dict.py
index 0821a015b968..e5db3ce9fc81 100644
--- a/nnvm/python/nnvm/compiler/param_dict.py
+++ b/nnvm/python/nnvm/compiler/param_dict.py
@@ -1,6 +1,8 @@
 # pylint: disable=invalid-name
 """Helper utility to save parameter dict"""
+import ctypes
 import tvm
+from tvm._ffi.runtime_ctypes import TVMArrayHandle
 
 _save_param_dict = tvm.get_global_func("nnvm.compiler._save_param_dict")
 _load_param_dict = tvm.get_global_func("nnvm.compiler._load_param_dict")
@@ -62,6 +64,6 @@ def load_param_dict(param_bytes):
     param_dict = {}
     for i in range(size):
         key = load_mod(1, i)
-        dltensor_handle = load_mod(2, i)
+        dltensor_handle = ctypes.cast(load_mod(2, i), TVMArrayHandle)
         param_dict[key] = tvm.nd.NDArray(dltensor_handle, False)
     return param_dict
diff --git a/nnvm/python/nnvm/libinfo.py b/nnvm/python/nnvm/libinfo.py
index d376c6da0aaf..5dfa6377e14b 100644
--- a/nnvm/python/nnvm/libinfo.py
+++ b/nnvm/python/nnvm/libinfo.py
@@ -26,7 +26,7 @@ def find_lib_path():
     if hasattr(__builtin__, "NNVM_LIBRARY_NAME"):
         lib_name = __builtin__.NNVM_LIBRARY_NAME
     else:
-        lib_name = "libnnvm_top"
+        lib_name = "libnnvm_compiler"
 
     api_path = os.path.join(base_path, '../../lib/')
     cmake_build_path = os.path.join(base_path, '../../build/Release/')
diff --git a/nnvm/src/compiler/graph_fuse.cc b/nnvm/src/compiler/graph_fuse.cc
index 0f11fe6d46ac..60a4e9de433f 100644
--- a/nnvm/src/compiler/graph_fuse.cc
+++ b/nnvm/src/compiler/graph_fuse.cc
@@ -281,7 +281,7 @@ nnvm::Graph GraphFuseCompile(nnvm::Graph g) {
       }
     }
     // schedule on root node, and use master's schedule
-    if (nid != root_id) {
+    if (static_cast<int>(nid) != root_id) {
       for (uint32_t index = 0; index < inode.source->num_outputs(); ++index) {
         uint32_t eid = idx.entry_id(nid, index);
         subgraph_vec[eid] = NodeEntry{gnode, index, 0};
@@ -300,7 +300,7 @@ nnvm::Graph GraphFuseCompile(nnvm::Graph g) {
     const auto& inode = idx[nid];
     if (inode.source->is_variable()) continue;
     int root_id = group_vec[nid];
-    if (nid != root_id) continue;
+    if (static_cast<int>(nid) != root_id) continue;
     int master = master_vec[root_id];
     FuseEntry& fe = fuse_vec[root_id];
 
@@ -336,7 +336,7 @@ nnvm::Graph GraphFuseCompile(nnvm::Graph g) {
       continue;
     }
     int root_id = group_vec[nid];
-    if (nid != root_id) continue;
+    if (static_cast<int>(nid) != root_id) continue;
     FuseEntry& fe = fuse_vec[root_id];
     const IndexedGraph& subidx = fe.subgraph.indexed_graph();
     nnvm::NodePtr np = nnvm::Node::Create();
diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
index c3ca3681cba9..d745256a151e 100644
--- a/nnvm/src/top/tensor/transform.cc
+++ b/nnvm/src/top/tensor/transform.cc
@@ -226,7 +226,7 @@ inline bool SplitInferShape(const NodeAttrs& attrs,
     CHECK_LT(param.axis, dshape.ndim());
     TShape oshape = dshape;
     dim_t begin = 0;
-    for (size_t i = 0; i < num_outputs - 1; ++i) {
+    for (dim_t i = 0; i < num_outputs - 1; ++i) {
       CHECK_GT(param.indices_or_sections[i], begin)
           << "indices_or_sections need to be a sorted ascending list";
       oshape[param.axis] = param.indices_or_sections[i] - begin;
diff --git a/nnvm/tests/ci_build/Dockerfile.gpu b/nnvm/tests/ci_build/Dockerfile.gpu
index b7cda20f169d..ce0f17263b69 100644
--- a/nnvm/tests/ci_build/Dockerfile.gpu
+++ b/nnvm/tests/ci_build/Dockerfile.gpu
@@ -35,6 +35,8 @@ RUN bash /install/ubuntu_install_mxnet.sh
 COPY install/ubuntu_install_onnx.sh /install/ubuntu_install_onnx.sh
 RUN bash /install/ubuntu_install_onnx.sh
 
+RUN pip install Pillow
+
 # Environment variables
 ENV PATH=/usr/local/nvidia/bin:${PATH}
 ENV PATH=/usr/local/cuda/bin:${PATH}
diff --git a/nnvm/tutorials/from_mxnet.py b/nnvm/tutorials/from_mxnet.py
index 7fb96f166797..ea11dddac46d 100644
--- a/nnvm/tutorials/from_mxnet.py
+++ b/nnvm/tutorials/from_mxnet.py
@@ -26,7 +26,7 @@
 # In this section, we download a pretrained imagenet model and classify an image.
 from mxnet.gluon.model_zoo.vision import get_model
 from mxnet.gluon.utils import download
-import Image
+from PIL import Image
 from matplotlib import pyplot as plt
 block = get_model('resnet18_v1', pretrained=True)
 img_name = 'cat.jpg'
diff --git a/nnvm/tutorials/from_onnx.py b/nnvm/tutorials/from_onnx.py
index f2dbde3f95fe..f46215ec3314 100644
--- a/nnvm/tutorials/from_onnx.py
+++ b/nnvm/tutorials/from_onnx.py
@@ -41,7 +41,7 @@
 # Load a test image
 # ---------------------------------------------
 # A single cat dominates the examples!
-import Image
+from PIL import Image
 img_url = 'https://github.com/dmlc/mxnet.js/blob/master/data/cat.png?raw=true'
 with open('cat.jpg', 'w') as f:
     f.write(urllib2.urlopen(img_url).read())

From 694c91751fcc6dba07369bb223770a962bbb5d6d Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 29 Sep 2017 16:54:30 -0700
Subject: [PATCH 510/949] use shorter title (#58)

---
 nnvm/tutorials/from_mxnet.py             | 4 ++--
 nnvm/tutorials/from_onnx.py              | 4 ++--
 nnvm/tutorials/imagenet_inference_gpu.py | 4 ++--
 3 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/nnvm/tutorials/from_mxnet.py b/nnvm/tutorials/from_mxnet.py
index ea11dddac46d..5ea6acbd3a9b 100644
--- a/nnvm/tutorials/from_mxnet.py
+++ b/nnvm/tutorials/from_mxnet.py
@@ -1,6 +1,6 @@
 """
-Compiling MXNet Models with NNVM
-================================
+Compile MXNet Models
+====================
 **Author**: `Joshua Z. Zhang <https://zhreshold.github.io/>`_
 
 This article is an introductory tutorial to deploy mxnet models with NNVM.
diff --git a/nnvm/tutorials/from_onnx.py b/nnvm/tutorials/from_onnx.py
index f46215ec3314..a9fe3824675c 100644
--- a/nnvm/tutorials/from_onnx.py
+++ b/nnvm/tutorials/from_onnx.py
@@ -1,6 +1,6 @@
 """
-Compiling ONNX Models with NNVM
-================================
+Compile ONNX Models
+===================
 **Author**: `Joshua Z. Zhang <https://zhreshold.github.io/>`_
 
 This article is an introductory tutorial to deploy ONNX models with NNVM.
diff --git a/nnvm/tutorials/imagenet_inference_gpu.py b/nnvm/tutorials/imagenet_inference_gpu.py
index 778ead0b5a36..09c90c2c54a5 100644
--- a/nnvm/tutorials/imagenet_inference_gpu.py
+++ b/nnvm/tutorials/imagenet_inference_gpu.py
@@ -1,6 +1,6 @@
 """
-Compile ImageNet Inference on GPU
-==================================
+Compile GPU Inference
+=====================
 **Author**: `Yuwei Hu <https://huyuwei.github.io/>`_
 
 This is an example of using NNVM to compile MobileNet/ResNet model and deploy its inference on GPU.

From b9dcd0e49f14234981f1cc163ce08153a362e929 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 29 Sep 2017 17:04:10 -0700
Subject: [PATCH 511/949] Fix docs issue (#59)

---
 nnvm/docs/how_to/install.md | 4 ++--
 nnvm/docs/top.rst           | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/nnvm/docs/how_to/install.md b/nnvm/docs/how_to/install.md
index 000b96fe9da8..270aae63e8e3 100644
--- a/nnvm/docs/how_to/install.md
+++ b/nnvm/docs/how_to/install.md
@@ -49,10 +49,10 @@ There are several ways to install the package:
     export PYTHONPATH=/path/to/nnvm/python:${PYTHONPATH}
     ```
 
-2. Install tvm python bindings by `setup.py`:
+2. Install nnvm python bindings by `setup.py`:
 
     ```bash
-    # install tvm package for the current user
+    # install nnvm package for the current user
     # NOTE: if you installed python via homebrew, --user is not needed during installaiton
     #       it will be automatically installed to your user directory.
     #       providing --user flag may trigger error during installation in such case.
diff --git a/nnvm/docs/top.rst b/nnvm/docs/top.rst
index 65beccde0c5c..3761ae1b20eb 100644
--- a/nnvm/docs/top.rst
+++ b/nnvm/docs/top.rst
@@ -1,7 +1,7 @@
 Core Tensor Operators
 =====================
 
-This page contains the list of core tensor operator primitives re-defined in NNVM.
+This page contains the list of core tensor operator primitives pre-defined in NNVM.
 The core tensor operator primitives(``nnvm.top``) covers typical workloads in deep learning.
 They can represent workloads in front-end frameworks, and provide basic building blocks for optimization.
 Since deep learning is a fast evolving field and it is that possible to have operators that are not in here.

From 275319d0b243a4d805e3ca8db17f7c9fd7d30aee Mon Sep 17 00:00:00 2001
From: ziheng <ziheng@apache.org>
Date: Fri, 29 Sep 2017 18:20:24 -0700
Subject: [PATCH 512/949] [OP] Conv2d and Depthwise Conv2d for Raspberry Pi
 (#49)

* [TUTORIAL] ImageNet Inference on Raspberry Pi

* Update tvm
---
 nnvm/python/nnvm/testing/utils.py | 3 ++-
 nnvm/python/nnvm/top/nn.py        | 8 +++++++-
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/nnvm/python/nnvm/testing/utils.py b/nnvm/python/nnvm/testing/utils.py
index d6c03fc1b745..9b228d595d6a 100644
--- a/nnvm/python/nnvm/testing/utils.py
+++ b/nnvm/python/nnvm/testing/utils.py
@@ -39,7 +39,8 @@ def create_workload(net, batch_size, image_shape=(3, 224, 224),
     params : dict of str to NDArray
         The parameters.
     """
-    image_shape = (3, 224, 224)
+    if image_shape is None:
+        image_shape = (3, 224, 224)
     data_shape = (batch_size,) + image_shape
     params = {}
     g = graph.create(net)
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index 9fe945d1eb6c..6efde0c573cd 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -108,7 +108,7 @@ def compute_conv2d(attrs, inputs, _):
     assert layout == "NCHW", "only support nchw for now"
     assert dilation == (1, 1), "not support dilate now"
     if groups == 1:
-        out = topi.nn.conv2d_nchw(inputs[0], inputs[1], strides, padding)
+        out = topi.nn.conv2d(inputs[0], inputs[1], strides, padding)
     elif groups == get_const_int(inputs[0].shape[1]) and groups == channels:
         out = topi.nn.depthwise_conv2d_nchw(inputs[0], inputs[1], strides, padding)
     else:
@@ -128,6 +128,12 @@ def schedule_conv2d(attrs, outs, target):
             return topi.cuda.schedule_conv2d_nchw(outs)
         return topi.cuda.schedule_depthwise_conv2d_nchw(outs)
     # naive schedule
+
+    if tvm.target.current_target() == tvm.target.rasp():
+        if groups == 1:
+            return topi.rasp.schedule_conv2d(outs)
+        return topi.rasp.schedule_depthwise_conv2d(outs)
+
     return tvm.create_schedule([x.op for x in outs])
 
 reg.register_pattern("conv2d", OpPattern.OUT_ELEMWISE_FUSABLE)

From 695d6403dc1bdcecd157423e148dab6174b03f81 Mon Sep 17 00:00:00 2001
From: "Joshua Z. Zhang" <cheungchih@gmail.com>
Date: Fri, 29 Sep 2017 19:59:24 -0700
Subject: [PATCH 513/949] [TUTORIAL] Onnx tutorial (#60)

* update

* back to color
---
 nnvm/tutorials/from_onnx.py | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)

diff --git a/nnvm/tutorials/from_onnx.py b/nnvm/tutorials/from_onnx.py
index a9fe3824675c..88e245be2b91 100644
--- a/nnvm/tutorials/from_onnx.py
+++ b/nnvm/tutorials/from_onnx.py
@@ -45,8 +45,10 @@
 img_url = 'https://github.com/dmlc/mxnet.js/blob/master/data/cat.png?raw=true'
 with open('cat.jpg', 'w') as f:
     f.write(urllib2.urlopen(img_url).read())
-img = Image.open('cat.jpg').convert("L")  # convert to greyscale
-x = np.array(img.resize((224, 224)))[np.newaxis, np.newaxis, :, :]
+img = Image.open('cat.jpg').resize((224, 224))
+img_ycbcr = img.convert("YCbCr")  # convert to YCbCr
+img_y, img_cb, img_cr = img_ycbcr.split()
+x = np.array(img_y)[np.newaxis, np.newaxis, :, :]
 
 ######################################################################
 # Compile the model on NNVM
@@ -73,15 +75,18 @@
 # get outputs
 output_shape = (1, 1, 672, 672)
 tvm_output = m.get_output(0, tvm.nd.empty(output_shape, dtype)).asnumpy()
-out_img = tvm_output.reshape((672, 672))
 
 ######################################################################
 # Display results
 # ---------------------------------------------
 # We put input and output image neck to neck
 from matplotlib import pyplot as plt
-canvas = np.full((672, 672*2), 255)
-canvas[0:224, 0:224] = x[0, 0, :, :]
-canvas[:, 672:] = out_img
-plt.imshow(canvas, cmap='gray')
+out_y = Image.fromarray(np.uint8((tvm_output[0, 0]).clip(0, 255)), mode='L')
+out_cb = img_cb.resize(out_y.size, Image.BICUBIC)
+out_cr = img_cr.resize(out_y.size, Image.BICUBIC)
+result = Image.merge('YCbCr', [out_y, out_cb, out_cr]).convert('RGB')
+canvas = np.full((672, 672*2, 3), 255)
+canvas[0:224, 0:224, :] = np.asarray(img)
+canvas[:, 672:, :] = np.asarray(result)
+plt.imshow(canvas.astype(np.uint8))
 plt.show()

From e38aa9b91b2b55f224ab8b0de68859a3c47eaff3 Mon Sep 17 00:00:00 2001
From: "Joshua Z. Zhang" <cheungchih@gmail.com>
Date: Mon, 2 Oct 2017 15:27:41 -0700
Subject: [PATCH 514/949] [DOC] add json spec intro (#62)

* add json spec intro

* fix error

* fix name attrs

* rename and move to top

* address boolean values

* update title
---
 nnvm/docs/index.rst               |   1 +
 nnvm/docs/json_spec.rst           | 212 ++++++++++++++++++++++++++++++
 nnvm/python/nnvm/frontend/onnx.py |   5 +-
 3 files changed, 216 insertions(+), 2 deletions(-)
 create mode 100644 nnvm/docs/json_spec.rst

diff --git a/nnvm/docs/index.rst b/nnvm/docs/index.rst
index d4a3243aab96..df61dca776f8 100644
--- a/nnvm/docs/index.rst
+++ b/nnvm/docs/index.rst
@@ -14,5 +14,6 @@ Contents
    tutorials/index
    how_to/contribute
    how_to/deploy
+   json_spec
    api/python/index
    dev/index
diff --git a/nnvm/docs/json_spec.rst b/nnvm/docs/json_spec.rst
new file mode 100644
index 000000000000..31f2d2dc5c07
--- /dev/null
+++ b/nnvm/docs/json_spec.rst
@@ -0,0 +1,212 @@
+NNVM Graph JSON Specification
+=============================
+
+NNVM uses JSON for graph serialization. This allows NNVM graph to be
+exported to any backend either natively supported or by third-party
+without any dependency such as protobuf.
+
+Getting started
+---------------
+
+A serialized NNVM graph in JSON format can be deserialized by any JSON
+parser.
+
+.. code:: python
+
+    # python
+    import json
+    with open('model.json', 'r') as f:
+      graph = json.loads(f.read())
+    print(graph.keys())
+
+``['nodes', 'arg_nodes', 'heads', 'node_row_ptr']``
+
+Actually, the following keys are valid in JSON graph.
+
++--------------------------------------+------------+-----------------------------------+
+| Keys                                 | Required   | Description                       |
++======================================+============+===================================+
+| `nodes <#nodes>`__                   | Yes        | The nodes in graph.               |
++--------------------------------------+------------+-----------------------------------+
+| `arg\_nodes <#arg_nodes>`__          | Yes        | Indices of input nodes.           |
++--------------------------------------+------------+-----------------------------------+
+| `heads <#heads>`__                   | Yes        | Indices of output nodes.          |
++--------------------------------------+------------+-----------------------------------+
+| `node\_row\_ptr <#node_row_ptr>`__   | Optional   | Depth first search row indices.   |
++--------------------------------------+------------+-----------------------------------+
+| `attr <#attr>`__                     | Optional   | Additional information.           |
++--------------------------------------+------------+-----------------------------------+
+
+nodes
+-----
+
+Explained by the name itself, ``nodes`` are either placeholders or
+computational nodes in NNVM graph. The ``nodes`` are stored in list.
+
+.. code:: python
+
+    nodes = graph['nodes']
+    print(len(nodes))
+    print(nodes[0])
+    print(nodes[3])
+
+::
+
+    53
+    {'inputs': [], 'name': 'data', 'op': 'null'}
+    {'inputs': [[0, 0, 0], [1, 0, 0], [2, 0, 0]], 'attrs': {'channels': '64',
+    'padding': '(1, 1)', 'layout': 'NCHW', 'kernel_size': '[3, 3]', 'groups': '1',
+    'strides': '(1, 1)', 'use_bias': 'True', 'dilation': '(1, 1)'},
+    'name': 'conv1_1', 'op': 'conv2d'}
+
+The following keys are valid in each node:
+
++----------------+------------------+----------+
+| Keys           | Required         | Descript |
+|                |                  | ion      |
++================+==================+==========+
+| op             | Yes              | The      |
+|                |                  | operator |
+|                |                  | type     |
+|                |                  | name,    |
+|                |                  | 'null'   |
+|                |                  | is used  |
+|                |                  | if it's  |
+|                |                  | a        |
+|                |                  | placehol |
+|                |                  | der/vari |
+|                |                  | able/inp |
+|                |                  | ut.      |
++----------------+------------------+----------+
+| name           | Yes              | The      |
+|                |                  | given    |
+|                |                  | name of  |
+|                |                  | the      |
+|                |                  | node,    |
+|                |                  | defined  |
+|                |                  | by user  |
+|                |                  | composin |
+|                |                  | g        |
+|                |                  | the      |
+|                |                  | network. |
++----------------+------------------+----------+
+| inputs         | Yes              | List of  |
+|                |                  | Entry    |
+|                |                  | of the   |
+|                |                  | input    |
+|                |                  | nodes,   |
+|                |                  | can be   |
+|                |                  | empty    |
+|                |                  | list []. |
+|                |                  | Entry is |
+|                |                  | a list   |
+|                |                  | of       |
+|                |                  | [nose\_i |
+|                |                  | d,       |
+|                |                  | index,   |
+|                |                  | version] |
++----------------+------------------+----------+
+| attrs          | Optional         | Extra    |
+|                |                  | attribut |
+|                |                  | es       |
+|                |                  | for the  |
+|                |                  | specific |
+|                |                  | operator |
+|                |                  | .        |
++----------------+------------------+----------+
+| control\_deps  | Optional         | Control  |
+|                |                  | dependen |
+|                |                  | cies,    |
+|                |                  | left     |
+|                |                  | blank    |
+|                |                  | unless   |
+|                |                  | specific |
+|                |                  | ally     |
+|                |                  | used.    |
++----------------+------------------+----------+
+
+``attrs`` for operators is a dictionary. Key-value pair examples:
+
++----------------+------------------+----------+----------+
+| Keys           | Value            | Operator | Descript |
+|                |                  |          | ion      |
++================+==================+==========+==========+
+| 'channels'     | '64'             | conv2d   | Output   |
+|                |                  |          | channels |
+|                |                  |          | for 2d   |
+|                |                  |          | convolut |
+|                |                  |          | ion.     |
++----------------+------------------+----------+----------+
+| 'kernel\_size' | '[3, 3]'         | conv2d   | Convolut |
+|                |                  |          | ion      |
+|                |                  |          | filter   |
+|                |                  |          | kernel   |
+|                |                  |          | size in  |
+|                |                  |          | (h, w),  |
+|                |                  |          | list and |
+|                |                  |          | tuple    |
+|                |                  |          | both     |
+|                |                  |          | works.   |
++----------------+------------------+----------+----------+
+| 'use\_bias'    | '1'              | conv2d   | Whether  |
+|                |                  |          | use bias |
+|                |                  |          | such     |
+|                |                  |          | that     |
+|                |                  |          | `y = w   |
+|                |                  |          | * x + b` |
+|                |                  |          | .        |
++----------------+------------------+----------+----------+
+
+.. note::
+
+    Tips for parsing key-value pair:
+
+    * Both key and value are stored as strings.
+
+    * Boolean values need extra attention, convert to int is recommended since `bool('0') == True` in python.
+
+    * For a full list of operator attributes, please refer to the core operator `documentation <top.html>`__.
+
+arg\_nodes
+----------
+
+``arg_nodes`` is a list of indices of nodes which is
+placeholder/variable/input to the graph.
+
+.. code:: python
+
+    print(graph['arg_nodes'])
+
+::
+
+    [0, 1, 2, 6, 7, 11, 12, 15, 16, 20, 21, 24, 25, 29, 30, 33, 34, 39, 40, 44, 45, 49, 50]
+
+For example, ``nodes[3]`` is not in ``arg_nodes`` because it's an
+internal node.
+
+heads
+-----
+
+``heads`` is a list of entries as the outlet/output of the graph.
+
+.. code:: python
+
+    print(graph['heads'])
+
+::
+
+    [[52, 0, 0]]
+
+This example indicating that there's only one output in the graph, with
+index 52.
+
+node\_row\_ptr
+--------------
+
+``node_row_ptr`` stores the history of forward path, so you can skip
+constructing the entire graph in inference tasks.
+
+attrs
+-----
+
+``attrs`` can contain version numbers or similar helpful informations.
diff --git a/nnvm/python/nnvm/frontend/onnx.py b/nnvm/python/nnvm/frontend/onnx.py
index a433485520d5..8df61bf25065 100644
--- a/nnvm/python/nnvm/frontend/onnx.py
+++ b/nnvm/python/nnvm/frontend/onnx.py
@@ -367,9 +367,10 @@ def from_onnx(graph):
 
     Returns
     -------
-    nnvm.Symbol
+    sym : nnvm.Symbol
         Compatible nnvm symbol
-    dict of tvm.ndarray
+    
+    params : dict of str to tvm.ndarray
         Dict of converted parameters stored in tvm.ndarray format
     """
     g = GraphProto()

From 5aefd3438a9b89aac24e8b61afaba2a310489c86 Mon Sep 17 00:00:00 2001
From: ziheng <ziheng@apache.org>
Date: Mon, 2 Oct 2017 18:24:23 -0700
Subject: [PATCH 515/949] [TUTORIAL] Deploy the Pretrained Model on Raspberry
 Pi (#61)

* [TUTORIAL] Deploy the Pretrained Model on Raspberry Pi

* [TUTORIAL] Improve

* [TUTORIAL] Improve

* [TUTORIAL] Improve

* [TUTORIAL] Improve
---
 nnvm/tutorials/deploy_model_on_rasp.py | 234 +++++++++++++++++++++++++
 1 file changed, 234 insertions(+)
 create mode 100644 nnvm/tutorials/deploy_model_on_rasp.py

diff --git a/nnvm/tutorials/deploy_model_on_rasp.py b/nnvm/tutorials/deploy_model_on_rasp.py
new file mode 100644
index 000000000000..5edf1cf1233c
--- /dev/null
+++ b/nnvm/tutorials/deploy_model_on_rasp.py
@@ -0,0 +1,234 @@
+"""
+Deploy the Pretrained Model on Raspberry Pi
+===========================================
+**Author**: `Ziheng Jiang <https://ziheng.org/>`_
+
+This is an example of using NNVM to compile a ResNet model and deploy
+it on raspberry pi.
+
+To begin with, we import nnvm(for compilation) and TVM(for deployment).
+"""
+import tvm
+import nnvm.compiler
+import nnvm.testing
+from tvm.contrib import util, rpc
+from tvm.contrib import graph_runtime as runtime
+
+
+######################################################################
+# Build TVM Runtime on Device
+# ---------------------------
+#
+# There're some prerequisites: we need build tvm runtime and set up
+# a RPC server on remote device.
+#
+# To get started, clone tvm repo from github. It is important to clone
+# the submodules along, with --recursive option (Assuming you are in
+# your home directory):
+#
+#   .. code-block:: bash
+#
+#     git clone --recursive https://github.com/dmlc/tvm
+#
+# .. note::
+#
+#   Usually device has limited resources and we only need to build
+#   runtime. The idea is we will use TVM compiler on the local server
+#   to compile and upload the compiled program to the device and run
+#   the device function remotely.
+#
+#   .. code-block:: bash
+#
+#     make runtime
+#
+# After success of buildind runtime, we need set environment varibles
+# in :code:`~/.bashrc` file of yourself account or :code:`/etc/profile`
+# of system enviroment variables. Assuming your TVM directory is in
+# :code:`~/tvm` and set environment variables below your account.
+#
+#   .. code-block:: bash
+#
+#    vi ~/.bashrc
+#
+# We need edit :code:`~/.bashrc` using :code:`vi ~/.bashrc` and add
+# lines below (Assuming your TVM directory is in :code:`~/tvm`):
+#
+#   .. code-block:: bash
+#
+#    export TVM_HOME=~/tvm
+#    export PATH=$PATH:$TVM_HOME/lib
+#    export PYTHONPATH=$PYTHONPATH:$TVM_HOME/python
+#
+# To enable updated :code:`~/.bashrc`, execute :code:`source ~/.bashrc`.
+
+######################################################################
+# Set Up RPC Server on Device
+# ---------------------------
+# To set up a TVM RPC server on the Raspberry Pi (our remote device),
+# we have prepared a one-line script so you only need to run this
+# command after following the installation guide to install TVM on
+# your device:
+#
+#   .. code-block:: bash
+#
+#     python -m tvm.exec.rpc_server --host 0.0.0.0 --port=9090
+#
+# After executing command above, if you see these lines below, it's
+# successful to start RPC server on your device.
+#
+#    .. code-block:: bash
+#
+#      Loading runtime library /home/YOURNAME/code/tvm/lib/libtvm_runtime.so... exec only
+#      INFO:root:RPCServer: bind to 0.0.0.0:9090
+#
+######################################################################
+# For demonstration, we simply start an RPC server on the same machine,
+# if :code:`use_rasp` is False. If you have set up the remote
+# environment, please change the three lines below: change the
+# :code:`use_rasp` to True, also change the host and port with your
+# device's host address and port number.
+
+use_rasp = False
+host = 'rasp0'
+port = 9090
+
+if not use_rasp:
+    # run server locally
+    host = 'localhost'
+    port = 9090
+    server = rpc.Server(host=host, port=port)
+
+######################################################################
+# Prepare the Pretrained Model
+# ----------------------------
+# Back to the host machine, firstly, we need to download a MXNet Gluon
+# ResNet model from model zoo, which is pretrained on ImageNet. You
+# can found more details about this part at `Compile MXNet Models`
+
+from mxnet.gluon.model_zoo.vision import get_model
+from mxnet.gluon.utils import download
+from PIL import Image
+import numpy as np
+
+# only one line to get the model
+block = get_model('resnet18_v1', pretrained=True)
+
+######################################################################
+# In order to test our model, here we download a image of cat and
+# transform its format.
+img_name = 'cat.jpg'
+download('https://github.com/dmlc/mxnet.js/blob/master/data/cat.png?raw=true', img_name)
+image = Image.open(img_name).resize((224, 224))
+
+def transform_image(image):
+    image = np.array(image) - np.array([123., 117., 104.])
+    image /= np.array([58.395, 57.12, 57.375])
+    image = image.transpose((2, 0, 1))
+    image = image[np.newaxis, :]
+    return image
+
+x = transform_image(image)
+
+
+######################################################################
+# synset is used to transform the label from number of ImageNet class to
+# the word human can understand.
+synset_url = ''.join(['https://gist.githubusercontent.com/zhreshold/',
+                      '4d0b62f3d01426887599d4f7ede23ee5/raw/',
+                      '596b27d23537e5a1b5751d2b0481ef172f58b539/',
+                      'imagenet1000_clsid_to_human.txt'])
+synset_name = 'synset.txt'
+download(synset_url, synset_name)
+with open(synset_name) as f:
+    synset = eval(f.read())
+
+######################################################################
+# Now we would like to port the Gluon model to a portable computational graph.
+# It's as easy as several lines.
+
+# We support MXNet static graph(symbol) and HybridBlock in mxnet.gluon
+net, params = nnvm.frontend.from_mxnet(block)
+# we want a probability so add a softmax operator
+net = nnvm.sym.softmax(net)
+
+######################################################################
+# Here are some basic data workload configurations.
+batch_size = 1
+num_classes = 1000
+image_shape = (3, 224, 224)
+data_shape = (batch_size,) + image_shape
+out_shape = (batch_size, num_classes)
+
+######################################################################
+# Compile The Graph
+# -----------------
+# To compile the graph, we call the :any:`nnvm.compiler.build` function
+# with the graph configuration and parameters. However, You cannot to
+# deploy a x86 program on a device with ARM instruction set. It means
+# NNVM also needs to know the compilation option of target device,
+# apart from arguments :code:`net` and :code:`params` to specify the
+# deep learning workload. Actually, the option matters, different option
+# will lead to very different performance.
+
+######################################################################
+# If we run the example locally for demonstration, we can simply set
+# it as :code:`llvm`. If to run it on the Raspberry Pi, you need to
+# specify its instruction set. Here is the option I use for my Raspberry
+# Pi, which has been proved as a good compilation configuration.
+
+if use_rasp:
+    target = "llvm -target=armv7l-none-linux-anueabihf -mcpu=cortex-a53 -mattr=+neon"
+else:
+    target = "llvm"
+
+
+# use `with tvm.target.rasp` for some target-specified optimization
+with tvm.target.rasp():
+    graph, lib, params = nnvm.compiler.build(
+        net, target, shape={"data": data_shape}, params=params)
+
+# After `nnvm.compiler.build`, you will get three return values: graph,
+# library and the new parameter, since we do some optimization that will
+# change the parameters but keep the result of model as the same.
+
+
+# Save the library at local temporary directory.
+tmp = util.tempdir()
+lib_fname = tmp.relpath('net.o')
+lib.save(lib_fname)
+
+
+######################################################################
+# Deploy the Model Remotely by RPC
+# --------------------------------
+# With RPC, you can deploy the model remotely from your host machine
+# to the remote device.
+
+# connect the server
+remote = rpc.connect(host, port)
+
+# upload the library to remote device and load it
+remote.upload(lib_fname)
+rlib = remote.load_module('net.o')
+
+ctx = remote.cpu(0)
+# upload the parameter
+rparams = {k: tvm.nd.array(v, ctx) for k, v in params.items()}
+
+# create the remote runtime module
+module = runtime.create(graph, rlib, ctx)
+# set parameter
+module.set_input(**rparams)
+# set input data
+module.set_input('data', tvm.nd.array(x.astype('float32')))
+# run
+module.run()
+# get output
+out = module.get_output(0, tvm.nd.empty(out_shape, ctx=ctx))
+# get top1 result
+top1 = np.argmax(out.asnumpy())
+print('TVM prediction top-1: {}'.format(synset[top1]))
+
+if not use_rasp:
+    # terminate the local server
+    server.terminate()

From a510cbaf56fa94761b610020a1fcaacb26d2e78a Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 5 Oct 2017 12:10:44 -0700
Subject: [PATCH 516/949] [COMPILE] More debug message when compile error (#66)

---
 nnvm/python/nnvm/compiler/build_module.py | 15 ++++++++++++---
 nnvm/python/nnvm/frontend/onnx.py         |  2 +-
 nnvm/src/compiler/compile_engine.cc       |  2 +-
 3 files changed, 14 insertions(+), 5 deletions(-)

diff --git a/nnvm/python/nnvm/compiler/build_module.py b/nnvm/python/nnvm/compiler/build_module.py
index 8570c03d88f0..92f39d712265 100644
--- a/nnvm/python/nnvm/compiler/build_module.py
+++ b/nnvm/python/nnvm/compiler/build_module.py
@@ -95,9 +95,18 @@ def build_config(**kwargs):
 
 
 @tvm.register_func("nnvm.compiler.lower")
-def _lower(sch, inputs, func_name):
-    f = tvm.lower(sch, inputs, name=func_name)
-    logging.debug("lower function %s", func_name)
+def _lower(sch, inputs, func_name, graph):
+    import traceback
+    # pylint: disable=broad-except
+    try:
+        f = tvm.lower(sch, inputs, name=func_name)
+        logging.debug("lower function %s", func_name)
+    except Exception:
+        msg = traceback.format_exc()
+        msg += "Error during compile graph\n"
+        msg += "--------------------------\n"
+        msg += graph.ir(join_entry_attrs=["shape"])
+        raise RuntimeError(msg)
     return f if isinstance(
         f, (tvm.container.Array, tuple, list)) else [f]
 
diff --git a/nnvm/python/nnvm/frontend/onnx.py b/nnvm/python/nnvm/frontend/onnx.py
index 8df61bf25065..667c1787bf7e 100644
--- a/nnvm/python/nnvm/frontend/onnx.py
+++ b/nnvm/python/nnvm/frontend/onnx.py
@@ -369,7 +369,7 @@ def from_onnx(graph):
     -------
     sym : nnvm.Symbol
         Compatible nnvm symbol
-    
+
     params : dict of str to tvm.ndarray
         Dict of converted parameters stored in tvm.ndarray format
     """
diff --git a/nnvm/src/compiler/compile_engine.cc b/nnvm/src/compiler/compile_engine.cc
index e345497e2512..b7f695d9f332 100644
--- a/nnvm/src/compiler/compile_engine.cc
+++ b/nnvm/src/compiler/compile_engine.cc
@@ -189,7 +189,7 @@ class CompileEngine {
     gf->inputs = inputs;
     gf->outputs = outputs;
     static const PackedFunc& flower = GetPackedFunc("nnvm.compiler.lower");
-    gf->funcs = flower(sch, all_args, gf->func_name);
+    gf->funcs = flower(sch, all_args, gf->func_name, graph);
     return GraphFunc(gf);
   }
 

From c53081ae1ca6154e03af0f937bdb6a50f8403f25 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 5 Oct 2017 16:22:00 -0700
Subject: [PATCH 517/949] [DOCS] update docs (#67)

---
 nnvm/README.md      | 14 ++++++++++++--
 nnvm/docs/index.rst |  6 +++---
 2 files changed, 15 insertions(+), 5 deletions(-)

diff --git a/nnvm/README.md b/nnvm/README.md
index 1a33568c863d..9c770d42b9c3 100644
--- a/nnvm/README.md
+++ b/nnvm/README.md
@@ -1,8 +1,12 @@
 # NNVM: Open Compiler for AI Frameworks
-
 [![Build Status](https://travis-ci.org/dmlc/nnvm.svg?branch=master)](https://travis-ci.org/dmlc/nnvm)
 [![GitHub license](http://dmlc.github.io/img/apache2.svg)](./LICENSE)
 
+[Installation](docs/how_to/install.md) |
+[Documentation](http://nnvm.tvmlang.org) |
+[Tutorials](http://nnvm.tvmlang.org/tutorials/index.html) |
+[Release Notes](NEWS.md)
+
 NNVM compiler offers reusable computation graph optimization and compilation for deep learning systems.
 It is backed by the [TVM stack](http://tvmlang.org) and provides modules to:
 
@@ -49,6 +53,12 @@ rmodule.set_input(**params)
 rmodule.run()
 ```
 
-## Links
+License
+-------
+Licensed under an [Apache-2.0](https://github.com/dmlc/tvm/blob/master/LICENSE) license.
+
+
+Links
+-----
 - [TinyFlow](https://github.com/tqchen/tinyflow) on how you can use  NNVM to build a TensorFlow like API.
 - [Apache MXNet](http://mxnet.io/) uses NNVM as a backend.
diff --git a/nnvm/docs/index.rst b/nnvm/docs/index.rst
index df61dca776f8..939c46b45bb4 100644
--- a/nnvm/docs/index.rst
+++ b/nnvm/docs/index.rst
@@ -1,6 +1,6 @@
 NNVM Documentation
 ==================
-
+This is a document about NNVM and NNVM compiler.
 
 Contents
 --------
@@ -9,11 +9,11 @@ Contents
    :maxdepth: 1
 
    self
-   top
    how_to/install
    tutorials/index
+   top
+   json_spec
    how_to/contribute
    how_to/deploy
-   json_spec
    api/python/index
    dev/index

From 9e7584cefd5daf25599330195e0124cfce638094 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 5 Oct 2017 20:14:47 -0700
Subject: [PATCH 518/949] [GRAPHIR] Print attributes of input (#69)

---
 nnvm/src/pass/print_graph_ir.cc | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/nnvm/src/pass/print_graph_ir.cc b/nnvm/src/pass/print_graph_ir.cc
index 80b6df2006ba..e10185b1951f 100644
--- a/nnvm/src/pass/print_graph_ir.cc
+++ b/nnvm/src/pass/print_graph_ir.cc
@@ -109,6 +109,17 @@ void PrintGraphIR_(Graph src,
     }
   };
 
+  if (trigger.size() != 0) {
+    for (size_t i = 0; i < idx.input_nodes().size(); ++i) {
+      uint32_t nid = idx.input_nodes()[i];
+      os << "  %" << idx[nid].source->attrs.name;
+      for (const auto& fp : trigger) {
+        fp(nid, os);
+      }
+      os << '\n';
+    }
+  }
+
   for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
     const auto& inode = idx[nid];
     if (inode.source->is_variable()) continue;

From 8be09c647d68c269d26ce4600861f63bd2faa609 Mon Sep 17 00:00:00 2001
From: ziheng <ziheng@apache.org>
Date: Thu, 5 Oct 2017 20:42:09 -0700
Subject: [PATCH 519/949] [BENCH] Add Benchmark for Rasp (#68)

* [BENCH] Add Benchmark for Rasp

* [BENCH] Add arg opt-level

* [BENCH] Add model choices

* [BENCH] Improve
---
 .../examples/benchmark/rasp_imagenet_bench.py | 78 +++++++++++++++++++
 1 file changed, 78 insertions(+)
 create mode 100644 nnvm/examples/benchmark/rasp_imagenet_bench.py

diff --git a/nnvm/examples/benchmark/rasp_imagenet_bench.py b/nnvm/examples/benchmark/rasp_imagenet_bench.py
new file mode 100644
index 000000000000..bba40c84d6dd
--- /dev/null
+++ b/nnvm/examples/benchmark/rasp_imagenet_bench.py
@@ -0,0 +1,78 @@
+""" Benchmark script for performance on Raspberry Pi. For example, run the file with:
+`python rasp_imagenet_bench.py --model='modbilenet' --host='rasp0' --port=9090`. For
+more details about how to set up the inference environment on Raspberry Pi, Please
+refer to NNVM Tutorial: Deploy the Pretrained Model on Raspberry Pi """
+import time
+import argparse
+import numpy as np
+import tvm
+import nnvm.compiler
+import nnvm.testing
+from tvm.contrib import util, rpc
+from tvm.contrib import graph_runtime as runtime
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--model', type=str, required=True, choices=['resnet', 'mobilenet'],
+        help="The model type.")
+    parser.add_argument('--host', type=str, required=True, help="The host address of your Raspberry Pi.")
+    parser.add_argument('--port', type=int, required=True, help="The port number of your Raspberry Pi.")
+    parser.add_argument('--opt-level', type=int, default=1, help="Level of optimization.")
+    parser.add_argument('--num-iter', type=int, default=50, help="Number of iteration during benchmark.")
+    args = parser.parse_args()
+
+    opt_level = args.opt_level
+    target = "llvm -target=armv7l-none-linux-anueabihf -mcpu=cortex-a53 -mattr=+neon"
+
+    num_iter = args.num_iter
+    batch_size = 1
+    num_classes = 1000
+    image_shape = (3, 224, 224)
+
+    data_shape = (batch_size,) + image_shape
+    out_shape = (batch_size, num_classes)
+    if args.model == 'resnet':
+        net, params = nnvm.testing.resnet.get_workload(
+            batch_size=1, image_shape=image_shape)
+    elif args.model == 'mobilenet':
+        net, params = nnvm.testing.mobilenet.get_workload(
+            batch_size=1, image_shape=image_shape)
+    else:
+        raise ValueError('no benchmark prepared for {}.'.format(args.model))
+
+
+    with nnvm.compiler.build_config(opt_level=opt_level):
+        with tvm.target.rasp():
+            graph, lib, params = nnvm.compiler.build(
+                net, target, shape={"data": data_shape}, params=params)
+
+    tmp = util.tempdir()
+    lib_fname = tmp.relpath('net.o')
+    lib.save(lib_fname)
+
+    remote = rpc.connect(args.host, args.port)
+    remote.upload(lib_fname)
+
+    ctx = remote.cpu(0)
+    rlib = remote.load_module('net.o')
+    rparams = {k: tvm.nd.array(v, ctx) for k, v in params.items()}
+
+    module = runtime.create(graph, rlib, ctx)
+    module.set_input('data', tvm.nd.array(np.random.uniform(size=(data_shape)).astype("float32")))
+    module.set_input(**rparams)
+    module.run()
+    out = module.get_output(0, tvm.nd.empty(out_shape, ctx=ctx))
+    out.asnumpy()
+
+    print('benchmark args: {}'.format(args))
+    ftimer = module.module.time_evaluator("run", ctx, num_iter)
+    for i in range(3):
+        prof_res = ftimer()
+        print(prof_res)
+        # sleep for avoiding cpu overheat
+        time.sleep(45)
+
+
+if __name__ == '__main__':
+    main()

From e5bfc0eaaccf83e972dbb3938d30fe14cc237646 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 5 Oct 2017 22:38:06 -0700
Subject: [PATCH 520/949]  [TEST] include coreml tools in docker (#71)

* [TEST] include coreml tools in docker

* upgrade tvm
---
 nnvm/tests/ci_build/Dockerfile.gpu                   | 3 +++
 nnvm/tests/ci_build/install/ubuntu_install_coreml.sh | 2 ++
 2 files changed, 5 insertions(+)
 create mode 100644 nnvm/tests/ci_build/install/ubuntu_install_coreml.sh

diff --git a/nnvm/tests/ci_build/Dockerfile.gpu b/nnvm/tests/ci_build/Dockerfile.gpu
index ce0f17263b69..257865f155ab 100644
--- a/nnvm/tests/ci_build/Dockerfile.gpu
+++ b/nnvm/tests/ci_build/Dockerfile.gpu
@@ -35,6 +35,9 @@ RUN bash /install/ubuntu_install_mxnet.sh
 COPY install/ubuntu_install_onnx.sh /install/ubuntu_install_onnx.sh
 RUN bash /install/ubuntu_install_onnx.sh
 
+COPY install/ubuntu_install_coreml.sh /install/ubuntu_install_coreml.sh
+RUN bash /install/ubuntu_install_coreml.sh
+
 RUN pip install Pillow
 
 # Environment variables
diff --git a/nnvm/tests/ci_build/install/ubuntu_install_coreml.sh b/nnvm/tests/ci_build/install/ubuntu_install_coreml.sh
new file mode 100644
index 000000000000..f7b392bd20b6
--- /dev/null
+++ b/nnvm/tests/ci_build/install/ubuntu_install_coreml.sh
@@ -0,0 +1,2 @@
+pip2 install coremltools
+pip3 install coremltools

From 06e7826249bb040becd7816f54f7b7b9df08e242 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 5 Oct 2017 22:47:09 -0700
Subject: [PATCH 521/949] fix coreml tools (#72)

---
 nnvm/tests/ci_build/install/ubuntu_install_coreml.sh | 1 -
 1 file changed, 1 deletion(-)

diff --git a/nnvm/tests/ci_build/install/ubuntu_install_coreml.sh b/nnvm/tests/ci_build/install/ubuntu_install_coreml.sh
index f7b392bd20b6..3cfd6c293590 100644
--- a/nnvm/tests/ci_build/install/ubuntu_install_coreml.sh
+++ b/nnvm/tests/ci_build/install/ubuntu_install_coreml.sh
@@ -1,2 +1 @@
 pip2 install coremltools
-pip3 install coremltools

From 2195c1c00e20f1bc68e79e12767da006704cdc73 Mon Sep 17 00:00:00 2001
From: Leyuan Wang <laurawly@gmail.com>
Date: Thu, 5 Oct 2017 23:42:06 -0700
Subject: [PATCH 522/949] Add cuda imagenet inference benchmark script  (#73)

* resnet example merged to imagenet

* resnet example merged to imagenet

* merge with master

* merge with master

* cuda imagenet benchmakr added

* benchmark syntax fixed
---
 .../examples/benchmark/cuda_imagenet_bench.py | 69 +++++++++++++++++++
 1 file changed, 69 insertions(+)
 create mode 100644 nnvm/examples/benchmark/cuda_imagenet_bench.py

diff --git a/nnvm/examples/benchmark/cuda_imagenet_bench.py b/nnvm/examples/benchmark/cuda_imagenet_bench.py
new file mode 100644
index 000000000000..77e91c0c458f
--- /dev/null
+++ b/nnvm/examples/benchmark/cuda_imagenet_bench.py
@@ -0,0 +1,69 @@
+""" Benchmark script for performance on GPUs. For example, run the file with: `python cuda_imagenet_bench.py --model='mobilenet'`. For more details about how to set up the inference environment on GPUs, please refer to NNVM Tutorial: ImageNet Inference on the GPU """
+import time
+import argparse
+import numpy as np
+import tvm
+import nnvm.compiler
+import nnvm.testing
+from tvm.contrib import util, nvcc
+from tvm.contrib import graph_runtime as runtime
+
+@tvm.register_func
+def tvm_callback_cuda_compile(code):
+    ptx = nvcc.compile_cuda(code, target="ptx")
+    return ptx
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--model', type=str, required=True, choices=['resnet', 'mobilenet'],
+        help="The model type.")
+    parser.add_argument('--opt-level', type=int, default=1, help="Level of optimization.")
+    parser.add_argument('--num-iter', type=int, default=1000, help="Number of iteration during benchmark.")
+    args = parser.parse_args()
+    opt_level = args.opt_level
+    num_iter = args.num_iter
+    target = "cuda"
+    ctx = tvm.gpu(0)
+    batch_size = 1
+    num_classes = 1000
+    image_shape = (3, 224, 224)
+
+    data_shape = (batch_size,) + image_shape
+    out_shape = (batch_size, num_classes)
+    if args.model == 'resnet':
+        net, params = nnvm.testing.resnet.get_workload(
+            batch_size=1, image_shape=image_shape)
+    elif args.model == 'mobilenet':
+        net, params = nnvm.testing.mobilenet.get_workload(
+            batch_size=1, image_shape=image_shape)
+    else:
+        raise ValueError('no benchmark prepared for {}.'.format(args.model))
+
+    with nnvm.compiler.build_config(opt_level=opt_level):
+        with tvm.build_config(auto_unroll_max_step=32,
+                              auto_unroll_min_depth=0,
+                              unroll_explicit=False):
+            graph, lib, params = nnvm.compiler.build(
+                net, target, shape={"data": data_shape}, params=params)
+
+    data = np.random.uniform(-1, 1, size=data_shape).astype("float32")
+    module = runtime.create(graph, lib, ctx)
+    module.set_input(**params)
+    module.set_input("data", data)
+    print('benchmark args: {}'.format(args))
+    # warm-up run
+    module.run()
+    ctx.sync()
+
+    tic = time.time()
+    for _ in range(num_iter):
+        module.run()
+    ctx.sync()
+    print("average time cost of %d runs = %g ms" % (num_iter, ((time.time() - tic)*1000/num_iter)))
+    out = module.get_output(0, tvm.nd.empty(out_shape))
+    out.asnumpy()
+
+
+if __name__ == '__main__':
+    main()
+

From 5f7de01dc3d7c475456016ac88c3be7d0b892ddc Mon Sep 17 00:00:00 2001
From: "Joshua Z. Zhang" <cheungchih@gmail.com>
Date: Fri, 6 Oct 2017 00:03:53 -0700
Subject: [PATCH 523/949] [FRONTEND] CoreML (#63)

* add coreml

* fix bool

* fix flatten in fullyconnected

* fix duplicate flatten

* fix syntax

* add tutorial

* fix mxnet flatten, fix tutorials

* fix flatten issue

* fix lint
---
 nnvm/python/nnvm/frontend/__init__.py         |   1 +
 nnvm/python/nnvm/frontend/coreml.py           | 375 ++++++++++++++++++
 nnvm/python/nnvm/frontend/mxnet.py            |  21 +-
 .../frontend/coreml/model_zoo/.gitignore      |   3 +
 .../frontend/coreml/model_zoo/__init__.py     |  33 ++
 .../python/frontend/coreml/test_forward.py    |  45 +++
 nnvm/tutorials/from_coreml.py                 |  93 +++++
 nnvm/tutorials/from_onnx.py                   |  17 +-
 8 files changed, 579 insertions(+), 9 deletions(-)
 create mode 100644 nnvm/python/nnvm/frontend/coreml.py
 create mode 100644 nnvm/tests/python/frontend/coreml/model_zoo/.gitignore
 create mode 100644 nnvm/tests/python/frontend/coreml/model_zoo/__init__.py
 create mode 100644 nnvm/tests/python/frontend/coreml/test_forward.py
 create mode 100644 nnvm/tutorials/from_coreml.py

diff --git a/nnvm/python/nnvm/frontend/__init__.py b/nnvm/python/nnvm/frontend/__init__.py
index 08fa9f46ca93..5a2796aa1c64 100644
--- a/nnvm/python/nnvm/frontend/__init__.py
+++ b/nnvm/python/nnvm/frontend/__init__.py
@@ -2,3 +2,4 @@
 from __future__ import absolute_import
 from .mxnet import from_mxnet
 from .onnx import from_onnx
+from .coreml import from_coreml
diff --git a/nnvm/python/nnvm/frontend/coreml.py b/nnvm/python/nnvm/frontend/coreml.py
new file mode 100644
index 000000000000..d8d6461effaa
--- /dev/null
+++ b/nnvm/python/nnvm/frontend/coreml.py
@@ -0,0 +1,375 @@
+# pylint: disable=invalid-name, unused-argument
+"""CoreML frontend."""
+from __future__ import absolute_import as _abs
+import tvm
+import numpy as np
+from .. import symbol as _sym
+
+__all__ = ['from_coreml']
+
+
+class SymbolTable(object):
+    """Table storing symbols by names."""
+    def __init__(self):
+        self.vars = {}
+        self.params = {}
+        self.const_ctr = 1
+        self.in_padding = False
+        self.paddings = [0, 0]
+
+    def new_const(self, value):
+        name = "_param_%d" % (self.const_ctr)
+        self.const_ctr += 1
+        self.params[name] = value
+        self.vars[name] = _sym.Variable(name=name)
+        return self.vars[name]
+
+    def get_var(self, name, must_contain=True):
+        if must_contain:
+            assert name in self.vars
+        if name not in self.vars:
+            self.vars[name] = _sym.Variable(name=name)
+        return self.vars[name]
+
+    def set_var(self, name, sym):
+        assert isinstance(sym, _sym.Symbol)
+        self.vars[name] = sym
+
+    def set_padding(self, paddings):
+        self.paddings = paddings
+        self.in_padding = True
+
+    def clear_padding(self):
+        self.in_padding = False
+
+def NeuralNetworkImageScaler(op, insym, symtab):
+    # this changes the symbol
+    biases = np.array([op.blueBias, op.greenBias, op.redBias]).reshape([3, 1, 1])
+    bias = symtab.new_const(biases)
+    ret = _sym.__mul_scalar__(insym, scalar=op.channelScale)
+    ret = _sym.broadcast_add(ret, bias)
+    return ret
+
+
+def NeuralNetworkMeanImage(op, insym, symtab):
+    # this changes the symbol
+    ret = _sym.elemwise_sub(insym, scalar=op.meanImage)
+    return ret
+
+
+def ConvolutionLayerParams(op, insym, symtab):
+    """Convolution layer params."""
+    weights = symtab.new_const(np.array(list(op.weights.floatValue)).reshape(
+        tuple([op.outputChannels, op.kernelChannels] + list(op.kernelSize))))
+    if op.hasBias:
+        biases = symtab.new_const(list(op.bias.floatValue))
+    dilation = list(op.dilationFactor)
+    if not dilation:
+        dilation = [1, 1]
+    params = {'channels':op.outputChannels,
+              'kernel_size':list(op.kernelSize),
+              'strides':list(op.stride),
+              'dilation': dilation,
+              'use_bias': op.hasBias,
+              'groups':op.nGroups}
+
+    if op.WhichOneof('ConvolutionPaddingType') == 'valid':
+        valid = op.valid
+        padding = [b.startEdgeSize for b in valid.paddingAmounts.borderAmounts]
+        padding2 = [b.endEdgeSize for b in valid.paddingAmounts.borderAmounts]
+        for i, j in zip(padding, padding2):
+            assert i == j, "Asymmetry padding not supported"
+        if padding:
+            params['padding'] = padding
+    elif op.WhichOneof('ConvolutionPaddingType') == 'same':
+        kernel = params['kernel_size']
+        pad_h = kernel[0] - 1
+        pad_w = kernel[1] - 1
+        pad_t = pad_h // 2
+        pad_l = pad_w // 2
+        pad_b = pad_h - pad_t
+        pad_r = pad_w - pad_l
+        assert pad_t == pad_r and pad_l == pad_b, "Asymmetry padding not supported"
+        params['padding'] = [pad_t, pad_l]
+    else:
+        raise NotImplementedError("Valid/Same convolution padding implemented")
+
+    if op.hasBias:
+        pos = [insym, weights, biases]
+    else:
+        pos = [insym, weights]
+
+    if op.isDeconvolution:
+        ret = _sym.conv2d_transpose(*pos, **params)
+    else:
+        ret = _sym.conv2d(*pos, **params)
+    # consume padding layer
+    if symtab.in_padding:
+        params['padding'] = [sum(x) for x in zip(params.get('padding', [0, 0]), symtab.paddings)]
+        symtab.clear_padding()
+    return ret
+
+def BatchnormLayerParams(op, insym, symtab):
+    # this changes the symbol
+    if op.instanceNormalization:
+        raise NotImplementedError("instance normalization not implemented")
+    else:
+        params = {'gamma':symtab.new_const(list(op.gamma.floatValue)),
+                  'beta':symtab.new_const(list(op.beta.floatValue)),
+                  'moving_mean':symtab.new_const(list(op.mean.floatValue)),
+                  'moving_var': symtab.new_const(list(op.variance.floatValue)),
+                  'epsilon': op.epsilon}
+        return _sym.batch_norm(data=insym, **params)
+
+def ActivationParams(op, insym, symtab):
+    whichActivation = op.WhichOneof('NonlinearityType')
+    par = getattr(op, whichActivation)
+    if whichActivation == 'linear':
+        return _sym.__add_scalar__(_sym.__mul_scalar__(insym, scalar=par.alpha), scalar=par.beta)
+    elif whichActivation == 'ReLU':
+        return _sym.relu(insym)
+    elif whichActivation == 'leakyReLU':
+        return _sym.leaky_relu(insym, alpha=par.alpha)
+    elif whichActivation == 'thresholdedReLU':
+        raise NotImplementedError('thresholdedReLU not implemented')
+    elif whichActivation == 'PReLU':
+        raise NotImplementedError('PReLU not implemented')
+    elif whichActivation == 'tanh':
+        return _sym.tanh(insym)
+    elif whichActivation == 'scaledTanh':
+        return _sym.__mul_scalar__(_sym.tanh(_sym.__mul_scalar__(
+            insym, scalar=par.beta)), scalar=par.alpha)
+    elif whichActivation == 'sigmoid':
+        return _sym.sigmoid(insym)
+    elif whichActivation == 'sigmoidHard':
+        raise NotImplementedError('sigmoidHard not immplemented')
+    elif whichActivation == 'ELU':
+        return _sym.__mul_scalar__(_sym.__add_scalar__(
+            _sym.exp(insym), scalar=-1), scalar=par.alpha)
+    elif whichActivation == 'softsign':
+        raise NotImplementedError('softsign not implemented')
+    elif whichActivation == 'softplus':
+        return _sym.log(_sym.__add_scalar__(_sym.exp(insym), scalar=1))
+    elif whichActivation == 'parametricSoftplus':
+        alpha = list(par.alpha.floatValue)
+        beta = list(par.alpha.floatValue)
+        if len(alpha) == 1:
+            return _sym.__mul_scalar__(_sym.log(_sym.__add_scalar__(
+                _sym.exp(insym), scalar=beta[0])), scalar=alpha[0])
+        alpha = np.array(alpha).reshape((len(alpha), 1, 1))
+        beta = np.array(beta).reshape((len(beta), 1, 1))
+        alphasym = symtab.new_const(alpha)
+        betasym = symtab.new_const(beta)
+        return _sym.broadcast_mul(_sym.log(_sym.broadcast_add(
+            _sym.exp(insym), betasym)), alphasym)
+
+def ScaleLayerParams(op, insym, symtab):
+    """Scale layer params."""
+    scale = symtab.new_const(np.array(list(op.scale.floatValue)).reshape(
+        tuple(list(op.shapeScale) + [1, 1])))
+    # scale = _sym.reshape(scale, shape=tuple(list(op.shapeScale) + [1,1]))
+    ret = _sym.broadcast_mul(insym, scale)
+    if op.hasBias:
+        bias = symtab.new_const(np.array(list(op.bias.floatValue)).reshape(
+            tuple(list(op.shapeBias) + [1, 1])))
+        # bias = _sym.reshape(bias, shape=tuple(list(op.shapeBias) + [1,1]))
+        ret = _sym.broadcast_add(ret, bias)
+    return ret
+
+def PoolingLayerParams(op, insym, symtab):
+    if op.globalPooling:
+        if op.type == 0:
+            return _sym.global_max_pool2d(insym)
+        elif op.type == 1:
+            return _sym.global_avg_pool2d(insym)
+        else:
+            raise NotImplementedError("Only max and average pooling implemented")
+
+    else:
+        params = {'pool_size':list(op.kernelSize),
+                  'strides':list(op.stride)}
+
+        if op.WhichOneof('PoolingPaddingType') == 'valid':
+            valid = op.valid
+            padding = [b.startEdgeSize for b in valid.paddingAmounts.borderAmounts]
+            padding2 = [b.endEdgeSize for b in valid.paddingAmounts.borderAmounts]
+            for i, j in zip(padding, padding2):
+                assert i == j
+            params['padding'] = padding
+        elif op.WhichOneof('PoolingPaddingType') == 'includeLastPixel':
+            # I don't know if this is correct
+            valid = op.includeLastPixel
+            padding = list(valid.paddingAmounts)
+            params['padding'] = padding
+            params['ceil_mode'] = True
+        else:
+            raise NotImplementedError("Other convolution padding not implemented")
+
+        # consume padding layer
+        if symtab.in_padding:
+            params['padding'] = [sum(x) for x in zip(
+                params.get('padding', [0, 0]), symtab.paddings)]
+            symtab.clear_padding()
+
+        if op.type == 0:
+            return _sym.max_pool2d(insym, **params)
+        elif op.type == 1:
+            return _sym.avg_pool2d(insym, **params)
+        else:
+            raise NotImplementedError("Only max and average pooling implemented")
+
+def SoftmaxLayerParams(op, insym, symtab):
+    return _sym.softmax(_sym.flatten(insym))
+
+def InnerProductLayerParams(op, insym, symtab):
+    weights = symtab.new_const(np.array(op.weights.floatValue).reshape(
+        (op.outputChannels, op.inputChannels)))
+    par = {'weight':weights, 'use_bias':False, 'units':op.outputChannels}
+    if op.hasBias:
+        bias = symtab.new_const(np.array(op.bias.floatValue))
+        par['bias'] = bias
+        par['use_bias'] = True
+    return _sym.dense(data=insym, **par)
+
+def AddLayerParams(op, insyms, symtab):
+    if not isinstance(insyms, list):
+        insyms = [insyms]
+    ret = insyms[0]
+    for i in range(1, len(insyms)):
+        ret = _sym.elemwise_add(ret, insyms[i])
+    if op.alpha > 0:
+        ret = _sym.__add_scalar__(ret, scalar=op.alpha)
+    return ret
+
+def ConcatLayerParams(op, insyms, symtab):
+    if not isinstance(insyms, list):
+        insyms = [insyms]
+    if op.sequenceConcat:
+        raise NotImplementedError("Sequence Concat not supported")
+    ret = insyms[0]
+    for i in range(1, len(insyms)):
+        ret = _sym.concat(ret, insyms[i], dim=1)
+    return ret
+
+def FlattenLayerParams(op, insym, symtab):
+    if op.mode == 1:
+        insym = _sym.transpose(_sym.reshape(insym, shape=(0, 0, -1)), axes=(0, 2, 1))
+    return _sym.flatten(insym)
+
+def PaddingLayerParams(op, insym, symtab):
+    """Hacking for padding layer params."""
+    if op.WhichOneof('PaddingType') == 'constant':
+        constant = op.constant
+        if constant.value != 0:
+            raise NotImplementedError("Padding value {} not supported.".format(constant.value))
+        padding = [b.startEdgeSize for b in op.paddingAmounts.borderAmounts]
+        padding2 = [b.endEdgeSize for b in op.paddingAmounts.borderAmounts]
+        for i, j in zip(padding, padding2):
+            assert i == j
+        symtab.set_padding(padding)
+    else:
+        raise NotImplementedError("Only constant padding is supported now.")
+    return insym
+
+_convert_map = {
+    'NeuralNetworkMeanImage': NeuralNetworkMeanImage,
+    'NeuralNetworkImageScaler': NeuralNetworkImageScaler,
+    'ConvolutionLayerParams':ConvolutionLayerParams,
+    'BatchnormLayerParams':BatchnormLayerParams,
+    'ActivationParams':ActivationParams,
+    'ScaleLayerParams':ScaleLayerParams,
+    'PoolingLayerParams':PoolingLayerParams,
+    'SoftmaxLayerParams':SoftmaxLayerParams,
+    'InnerProductLayerParams':InnerProductLayerParams,
+    'AddLayerParams':AddLayerParams,
+    'FlattenLayerParams':FlattenLayerParams,
+    'ConcatLayerParams':ConcatLayerParams,
+    'PaddingLayerParams':PaddingLayerParams,
+}
+
+def coreml_op_to_nnvm(op, inname, outname, symtab):
+    """Convert coreml layer to nnvm layer.
+
+    Parameters
+    ----------
+    coremlop: a coreml protobuf bit
+
+    prevsym: previous nnvm symbol
+
+    Returns:
+    -------
+    nnvm.sym.Symbol
+        Converted symbol
+    """
+    classname = type(op).__name__
+    if classname not in _convert_map:
+        raise NotImplementedError("%s is not supported" % (classname))
+    if isinstance(inname, (str, unicode)):
+        insym = symtab.get_var(inname)
+    else:
+        insym = [symtab.get_var(i) for i in inname]
+    ret = _convert_map[classname](op, insym, symtab)
+    if outname:
+        symtab.set_var(outname, ret)
+    if classname != 'PaddingLayerParams':
+        assert not symtab.in_padding, "Previous padding not consumed by conv/pool"
+
+def from_coreml(model):
+    """Convert from coreml model into NNVM format.
+
+    Parameters
+    ----------
+    model:
+        coremltools.models.MLModel of a NeuralNetworkClassifier
+
+    arg_params : dict of str to mx.NDArray
+        The argument parameters in mxnet
+
+    aux_params : dict of str to mx.NDArray
+        The auxiliary parameters in mxnet
+
+    Returns
+    -------
+    sym : nnvm.Symbol
+        Compatible nnvm symbol
+
+    params : dict of str to tvm.NDArray
+        The parameter dict to be used by nnvm
+    """
+    try:
+        import coremltools as cm
+    except ImportError:
+        raise ImportError('The coremltools package must be installed')
+
+    assert isinstance(model, cm.models.MLModel)
+    spec = model.get_spec()
+    modeltype = spec.WhichOneof('Type')
+    assert modeltype in ['neuralNetworkClassifier', 'neuralNetwork', 'neuralNetworkRegressor']
+    cc = getattr(spec, modeltype)
+
+    symtab = SymbolTable()
+    for i in spec.description.input:
+        symtab.get_var(i.name, must_contain=False)
+
+    for pp in cc.preprocessing:
+        whichpp = pp.WhichOneof('preprocessor')
+        ppmethod = getattr(pp, whichpp)
+        # the NeuralNetworkImageScalar doesn't seem to have a featureName?
+        if whichpp == 'scaler':
+            for i in spec.description.input:
+                coreml_op_to_nnvm(ppmethod, i.name, i.name, symtab)
+        else:
+            coreml_op_to_nnvm(ppmethod, pp.featureName, pp.featureName, symtab)
+
+    for l in cc.layers:
+        layertype = l.WhichOneof('layer')
+        layerop = getattr(l, layertype)
+        assert len(l.output) == 1
+        if len(l.input) == 1:
+            coreml_op_to_nnvm(layerop, l.input[0], l.output[0], symtab)
+        else:
+            coreml_op_to_nnvm(layerop, list(l.input), l.output[0], symtab)
+    returns = [symtab.get_var(i.name, must_contain=False) for i in spec.description.output]
+    tvmparams = {k:tvm.nd.array(np.array(v, dtype=np.float32)) for k, v in symtab.params.items()}
+    # for now return first output
+    return returns[0], tvmparams
diff --git a/nnvm/python/nnvm/frontend/mxnet.py b/nnvm/python/nnvm/frontend/mxnet.py
index 168d3c67b499..0f5efd3b4ad2 100644
--- a/nnvm/python/nnvm/frontend/mxnet.py
+++ b/nnvm/python/nnvm/frontend/mxnet.py
@@ -1,4 +1,4 @@
-# pylint: disable=invalid-name
+# pylint: disable=invalid-name, import-self
 """MXNet symbol frontend."""
 from __future__ import absolute_import as _abs
 import json
@@ -7,6 +7,14 @@
 
 __all__ = ['from_mxnet']
 
+def _get_mxnet_version():
+    try:
+        import mxnet as mx
+        version = mx.__version__
+    except ImportError:
+        version = '0.11.1'
+    return [int(x) for x in version.split('.')]
+
 def _required_attr(attr, key):
     assert isinstance(attr, dict)
     if key not in attr:
@@ -119,6 +127,9 @@ def _dense(attrs):
     op_name, new_attrs = 'dense', {}
     new_attrs['units'] = _required_attr(attrs, 'num_hidden')
     new_attrs['use_bias'] = not _parse_bool_str(attrs, 'no_bias')
+    major, minor, micro = _get_mxnet_version()
+    if major >= 0 and minor >= 11 and micro >= 1:
+        new_attrs['flatten'] = _parse_bool_str(attrs, 'flatten', 'True')
     return op_name, new_attrs
 
 def _dropout(attrs):
@@ -276,6 +287,10 @@ def _from_mxnet_impl(symbol, graph):
         childs = symbol.get_children()
         childs = [_from_mxnet_impl(c, graph) for c in _as_list(childs)]
         childs = [x for y in childs for x in _as_list(y)]  # expand group symbol
+        if new_op == _sym.dense and 'flatten' in new_attr:
+            if new_attr['flatten']:
+                childs[0] = _sym.flatten(childs[0])
+            new_attr.pop('flatten')
         node = new_op(name=name, *childs, **new_attr)
     graph[name] = node
     return node
@@ -304,7 +319,7 @@ def from_mxnet(symbol, arg_params=None, aux_params=None):
         The parameter dict to be used by nnvm
     """
     try:
-        import mxnet as mx  # pylint: disable=import-self
+        import mxnet as mx
     except ImportError as e:
         raise ImportError('{}. MXNet is required to parse symbols.'.format(e))
 
@@ -325,7 +340,7 @@ def from_mxnet(symbol, arg_params=None, aux_params=None):
         for k, v in symbol.collect_params().items():
             params[k] = tvm.nd.array(v.data().asnumpy())
     elif isinstance(symbol, mx.gluon.Block):
-        raise NotImplementedError("The dynamic Block is not supported yet.")
+        raise NotImplementedError("Only Hybrid Blocks are supported now.")
     else:
         msg = "mxnet.Symbol or gluon.HybridBlock expected, got {}".format(type(symbol))
         raise ValueError(msg)
diff --git a/nnvm/tests/python/frontend/coreml/model_zoo/.gitignore b/nnvm/tests/python/frontend/coreml/model_zoo/.gitignore
new file mode 100644
index 000000000000..4242a1b2e2e0
--- /dev/null
+++ b/nnvm/tests/python/frontend/coreml/model_zoo/.gitignore
@@ -0,0 +1,3 @@
+*.mlmodel
+*.jpg
+*.png
diff --git a/nnvm/tests/python/frontend/coreml/model_zoo/__init__.py b/nnvm/tests/python/frontend/coreml/model_zoo/__init__.py
new file mode 100644
index 000000000000..87b9b5668432
--- /dev/null
+++ b/nnvm/tests/python/frontend/coreml/model_zoo/__init__.py
@@ -0,0 +1,33 @@
+import urllib
+import os
+from PIL import Image
+import numpy as np
+
+def download(url, path, overwrite=False):
+    if os.path.exists(path) and not overwrite:
+        return
+    print('Downloading {} to {}.'.format(url, path))
+    urllib.URLopener().retrieve(url, path)
+
+def get_mobilenet():
+    url = 'https://docs-assets.developer.apple.com/coreml/models/MobileNet.mlmodel'
+    dst = 'mobilenet.mlmodel'
+    real_dst = os.path.abspath(os.path.join(os.path.dirname(__file__), dst))
+    download(url, real_dst)
+    return os.path.abspath(real_dst)
+
+def get_resnet50():
+    url = 'https://docs-assets.developer.apple.com/coreml/models/Resnet50.mlmodel'
+    dst = 'resnet50.mlmodel'
+    real_dst = os.path.abspath(os.path.join(os.path.dirname(__file__), dst))
+    download(url, real_dst)
+    return os.path.abspath(real_dst)
+
+def get_cat_image():
+    url = 'https://gist.githubusercontent.com/zhreshold/bcda4716699ac97ea44f791c24310193/raw/fa7ef0e9c9a5daea686d6473a62aacd1a5885849/cat.png'
+    dst = 'cat.jpg'
+    real_dst = os.path.abspath(os.path.join(os.path.dirname(__file__), dst))
+    download(url, real_dst)
+    img = Image.open(real_dst).resize((224, 224))
+    img = np.transpose(img, (2, 0, 1))[np.newaxis, :]
+    return np.asarray(img)
diff --git a/nnvm/tests/python/frontend/coreml/test_forward.py b/nnvm/tests/python/frontend/coreml/test_forward.py
new file mode 100644
index 000000000000..d5c460e56987
--- /dev/null
+++ b/nnvm/tests/python/frontend/coreml/test_forward.py
@@ -0,0 +1,45 @@
+import numpy as np
+
+import topi
+import tvm
+from tvm.contrib import graph_runtime
+import nnvm.symbol as sym
+import nnvm.compiler
+from nnvm.testing.config import ctx_list
+from nnvm import frontend
+import coremltools as cm
+import model_zoo
+
+def get_tvm_output(symbol, x, params, target, ctx,
+                   out_shape=(1000,), input_name='image', dtype='float32'):
+    shape_dict = {input_name : x.shape}
+    with nnvm.compiler.build_config(opt_level=3):
+        graph, lib, params = nnvm.compiler.build(symbol, target, shape_dict, params=params)
+    m = graph_runtime.create(graph, lib, ctx)
+    # set inputs
+    m.set_input(input_name, tvm.nd.array(x.astype(dtype)))
+    m.set_input(**params)
+    m.run()
+    # get outputs
+    out = m.get_output(0, tvm.nd.empty(out_shape, dtype))
+    return out.asnumpy()
+
+def test_model_checkonly(model_file, model_name=''):
+    model = cm.models.MLModel(model_file)
+    sym, params = nnvm.frontend.from_coreml(model)
+    x = model_zoo.get_cat_image()
+    for target, ctx in ctx_list():
+        tvm_output = get_tvm_output(sym, x, params, target, ctx)
+        print(target, ctx, model_name, 'prediction id: ', np.argmax(tvm_output.flat))
+
+def test_mobilenet_checkonly():
+    model_file = model_zoo.get_mobilenet()
+    test_model_checkonly(model_file, 'mobilenet')
+
+def test_resnet50_checkonly():
+    model_file = model_zoo.get_resnet50()
+    test_model_checkonly(model_file, 'resnet50')
+
+if __name__ == '__main__':
+    test_mobilenet_checkonly()
+    test_resnet50_checkonly()
diff --git a/nnvm/tutorials/from_coreml.py b/nnvm/tutorials/from_coreml.py
new file mode 100644
index 000000000000..d2c99ddf6ed3
--- /dev/null
+++ b/nnvm/tutorials/from_coreml.py
@@ -0,0 +1,93 @@
+"""
+Compile CoreML Models
+=====================
+**Author**: `Joshua Z. Zhang <https://zhreshold.github.io/>`_
+
+This article is an introductory tutorial to deploy CoreML models with NNVM.
+
+For us to begin with, coremltools module is required to be installed.
+
+A quick solution is to install via pip
+```bash
+pip install -U coremltools --user
+```
+or please refer to offical site
+https://github.com/apple/coremltools
+"""
+import nnvm
+import tvm
+import coremltools as cm
+import numpy as np
+from PIL import Image
+
+def download(url, path, overwrite=False):
+    import urllib2, os
+    if os.path.exists(path) and not overwrite:
+        return
+    print('Downloading {} to {}.'.format(url, path))
+    with open(path, 'w') as f:
+        f.write(urllib2.urlopen(url).read())
+
+######################################################################
+# Load pretrained CoreML model
+# ----------------------------
+# We will download and load a pretrained mobilenet classification network
+# privided by apple in this example
+model_url = 'https://docs-assets.developer.apple.com/coreml/models/MobileNet.mlmodel'
+model_file = 'mobilenet.mlmodel'
+download(model_url, model_file)
+# now you mobilenet.mlmodel on disk
+mlmodel = cm.models.MLModel(model_file)
+# we can load the graph as NNVM compatible model
+sym, params = nnvm.frontend.from_coreml(mlmodel)
+
+######################################################################
+# Load a test image
+# ------------------
+# A single cat dominates the examples!
+from PIL import Image
+img_url = 'https://github.com/dmlc/mxnet.js/blob/master/data/cat.png?raw=true'
+download(img_url, 'cat.png')
+img = Image.open('cat.png').resize((224, 224))
+x = np.transpose(img, (2, 0, 1))[np.newaxis, :]
+
+######################################################################
+# Compile the model on NNVM
+# ---------------------------
+# We should be familiar with the process right now.
+import nnvm.compiler
+target = 'cuda'
+shape_dict = {'image': x.shape}
+graph, lib, params = nnvm.compiler.build(sym, target, shape_dict, params=params)
+
+######################################################################
+# Execute on TVM
+# -------------------
+# The process is no different from other example
+from tvm.contrib import graph_runtime
+ctx = tvm.gpu(0)
+dtype = 'float32'
+m = graph_runtime.create(graph, lib, ctx)
+# set inputs
+m.set_input('image', tvm.nd.array(x.astype(dtype)))
+m.set_input(**params)
+# execute
+m.run()
+# get outputs
+output_shape = (1000,)
+tvm_output = m.get_output(0, tvm.nd.empty(output_shape, dtype)).asnumpy()
+top1 = np.argmax(tvm_output)
+
+#####################################################################
+# Look up synset name
+# -------------------
+# Look up prdiction top 1 index in 1000 class synset.
+synset_url = ''.join(['https://gist.githubusercontent.com/zhreshold/',
+                      '4d0b62f3d01426887599d4f7ede23ee5/raw/',
+                      '596b27d23537e5a1b5751d2b0481ef172f58b539/',
+                      'imagenet1000_clsid_to_human.txt'])
+synset_name = 'synset.txt'
+download(synset_url, synset_name)
+with open(synset_name) as f:
+    synset = eval(f.read())
+print('Top-1 id', top1, 'class name', synset[top1])
diff --git a/nnvm/tutorials/from_onnx.py b/nnvm/tutorials/from_onnx.py
index 88e245be2b91..93f21cddae1c 100644
--- a/nnvm/tutorials/from_onnx.py
+++ b/nnvm/tutorials/from_onnx.py
@@ -19,19 +19,25 @@
 import onnx
 import numpy as np
 
+def download(url, path, overwrite=False):
+    import urllib2, os
+    if os.path.exists(path) and not overwrite:
+        return
+    print('Downloading {} to {}.'.format(url, path))
+    with open(path, 'w') as f:
+        f.write(urllib2.urlopen(url).read())
+
 ######################################################################
 # Load pretrained ONNX model
 # ---------------------------------------------
 # The example super resolution model used here is exactly the same model in onnx tutorial
 # http://pytorch.org/tutorials/advanced/super_resolution_with_caffe2.html
 # we skip the pytorch model construction part, and download the saved onnx model
-import urllib2
 model_url = ''.join(['https://gist.github.com/zhreshold/',
                      'bcda4716699ac97ea44f791c24310193/raw/',
                      '41b443bf2b6cf795892d98edd28bacecd8eb0d8d/',
                      'super_resolution.onnx'])
-with open('super_resolution.onnx', 'w') as f:
-    f.write(urllib2.urlopen(model_url).read())
+download(model_url, 'super_resolution.onnx')
 # now you have super_resolution.onnx on disk
 onnx_graph = onnx.load('super_resolution.onnx')
 # we can load the graph as NNVM compatible model
@@ -43,9 +49,8 @@
 # A single cat dominates the examples!
 from PIL import Image
 img_url = 'https://github.com/dmlc/mxnet.js/blob/master/data/cat.png?raw=true'
-with open('cat.jpg', 'w') as f:
-    f.write(urllib2.urlopen(img_url).read())
-img = Image.open('cat.jpg').resize((224, 224))
+download(img_url, 'cat.png')
+img = Image.open('cat.png').resize((224, 224))
 img_ycbcr = img.convert("YCbCr")  # convert to YCbCr
 img_y, img_cb, img_cr = img_ycbcr.split()
 x = np.array(img_y)[np.newaxis, np.newaxis, :, :]

From 5e11bb34f4981d1a0a5dde8a98be9063937f6671 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 6 Oct 2017 09:25:56 -0700
Subject: [PATCH 524/949] Update README.md

---
 nnvm/README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/README.md b/nnvm/README.md
index 9c770d42b9c3..e3aa8223a78e 100644
--- a/nnvm/README.md
+++ b/nnvm/README.md
@@ -1,5 +1,5 @@
 # NNVM: Open Compiler for AI Frameworks
-[![Build Status](https://travis-ci.org/dmlc/nnvm.svg?branch=master)](https://travis-ci.org/dmlc/nnvm)
+[![Build Status](http://mode-gpu.cs.washington.edu:8080/buildStatus/icon?job=dmlc/nnvm/master)](http://mode-gpu.cs.washington.edu:8080/job/dmlc/job/nnvm/job/master/)
 [![GitHub license](http://dmlc.github.io/img/apache2.svg)](./LICENSE)
 
 [Installation](docs/how_to/install.md) |

From 6914fd89d18c00c9d3c1a3e36c6cf46e74ccf102 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 6 Oct 2017 09:35:56 -0700
Subject: [PATCH 525/949] Update README.md

---
 nnvm/README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/README.md b/nnvm/README.md
index e3aa8223a78e..0072821e4b97 100644
--- a/nnvm/README.md
+++ b/nnvm/README.md
@@ -15,7 +15,7 @@ It is backed by the [TVM stack](http://tvmlang.org) and provides modules to:
 - Compile into executable modules and deploy to different hardware backends with minimum dependency.
 
 NNVM is designed to add new frontend, operators and graph optimizations in a decentralized fashion without changing the core interface.
-The compiled module can be deployed to server, mobile, embedded devices and browsers with minimum dependency, in languages including c++, python, javascript, java, objective-c.
+The compiled module can be deployed to server, mobile, embedded devices and browsers with minimum dependency, in languages including c++, python, javascript, java, objective-c. Checkout [our release announcement](http://www.tvmlang.org/2017/10/06/nnvm-compiler-announcement.html)
 
 The following code snippet demonstrates the general workflow of nnvm compiler.
 

From 16ec6bfcd591f72858b5f8d39466ac6f73875492 Mon Sep 17 00:00:00 2001
From: Leyuan Wang <laurawly@gmail.com>
Date: Fri, 6 Oct 2017 14:11:20 -0700
Subject: [PATCH 526/949] cuda imagenet inference benchmark timing updated
 (#158)

---
 nnvm/examples/benchmark/cuda_imagenet_bench.py | 17 +++++++----------
 1 file changed, 7 insertions(+), 10 deletions(-)

diff --git a/nnvm/examples/benchmark/cuda_imagenet_bench.py b/nnvm/examples/benchmark/cuda_imagenet_bench.py
index 77e91c0c458f..7f5652e0f111 100644
--- a/nnvm/examples/benchmark/cuda_imagenet_bench.py
+++ b/nnvm/examples/benchmark/cuda_imagenet_bench.py
@@ -50,20 +50,17 @@ def main():
     module = runtime.create(graph, lib, ctx)
     module.set_input(**params)
     module.set_input("data", data)
-    print('benchmark args: {}'.format(args))
-    # warm-up run
     module.run()
-    ctx.sync()
-
-    tic = time.time()
-    for _ in range(num_iter):
-        module.run()
-    ctx.sync()
-    print("average time cost of %d runs = %g ms" % (num_iter, ((time.time() - tic)*1000/num_iter)))
     out = module.get_output(0, tvm.nd.empty(out_shape))
     out.asnumpy()
 
+    print('benchmark args: {}'.format(args))
+    ftimer = module.module.time_evaluator("run", ctx, num_iter)
+    for i in range(3):
+        prof_res = ftimer()
+        print(prof_res)
+        # sleep for avoiding cpu overheat
+        time.sleep(45)
 
 if __name__ == '__main__':
     main()
-

From fca2af13b4ee9582edf2ef309de05c45c12af8e2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=81=A5=E7=BE=8E=E7=8C=AB?= <weijianwen@gmail.com>
Date: Sat, 7 Oct 2017 10:46:42 +0800
Subject: [PATCH 527/949] Install nnvm lib and haders. Offer choice to build
 static or shared lib. (#146)

---
 nnvm/CMakeLists.txt | 25 +++++++++++++++++++++++--
 1 file changed, 23 insertions(+), 2 deletions(-)

diff --git a/nnvm/CMakeLists.txt b/nnvm/CMakeLists.txt
index c6bbb8f5fa14..eef2b890b32e 100644
--- a/nnvm/CMakeLists.txt
+++ b/nnvm/CMakeLists.txt
@@ -17,6 +17,10 @@ set(nnvm_LINKER_LIBS "")
 
 add_definitions(-DNNVM_EXPORTS)
 
+# Build a shared lib (libnnvm.so) by default
+option(BUILD_SHARED_NNVM "Build a shared nnvm lib" ON)            
+option(BUILD_STATIC_NNVM "Build a static nnvm lib" OFF)
+
 # compile
 if(MSVC)
   add_definitions(-DDMLC_USE_CXX11)
@@ -64,8 +68,15 @@ elseif(DMLC_CORE_PATH)
   include_directories(${DMLC_CORE_PATH}/include)
 endif()
 
-add_library(nnvm SHARED ${SOURCE})
-target_link_libraries(nnvm ${nnvm_LINKER_LIBS})
+if(BUILD_SHARED_NNVM)
+  add_library(nnvm SHARED ${SOURCE})
+  target_link_libraries(nnvm ${nnvm_LINKER_LIBS})
+endif()
+if(BUILD_STATIC_NNVM)
+  add_library(nnvm_static STATIC ${SOURCE})
+  target_link_libraries(nnvm_static ${nnvm_LINKER_LIBS})
+  set_target_properties(nnvm_static PROPERTIES OUTPUT_NAME "nnvm")
+endif()
 
 if(INSTALL_INCLUDE_DIR)
   add_custom_command(TARGET nnvm POST_BUILD
@@ -74,6 +85,16 @@ if(INSTALL_INCLUDE_DIR)
     )
 endif()
 
+# ---[ Install lib, header and docs
+if(BUILD_SHARED_NNVM)
+  install(TARGETS nnvm LIBRARY DESTINATION lib) 
+endif()
+if(BUILD_STATIC_NNVM)
+  install(TARGETS nnvm_static ARCHIVE DESTINATION lib) 
+endif()
+install(DIRECTORY include DESTINATION .)
+install(DIRECTORY docs DESTINATION .)
+
 # ---[ Linter target
 if(MSVC)
   find_package(PythonInterp 2)

From d1485722cb5b33081700155440a9c2a35bd04550 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 8 Oct 2017 16:55:47 -0700
Subject: [PATCH 528/949] [CMAKE] Windows build instruction (#161)

---
 nnvm/CMakeLists.txt         | 44 ++++++++++++++++++++++++++-----------
 nnvm/docs/how_to/install.md | 20 +++++++++++++++--
 nnvm/python/nnvm/libinfo.py | 10 ++++-----
 nnvm/python/setup.py        | 30 +++++++++++++++++++++----
 4 files changed, 80 insertions(+), 24 deletions(-)

diff --git a/nnvm/CMakeLists.txt b/nnvm/CMakeLists.txt
index eef2b890b32e..30e2ad3b0397 100644
--- a/nnvm/CMakeLists.txt
+++ b/nnvm/CMakeLists.txt
@@ -9,16 +9,26 @@ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/build/private/local_config.cmake)
 endif()
 
 include(cmake/Utils.cmake)
+if(EXISTS ${CMAKE_CURRENT_BINARY_DIR}/config.cmake)
+  include(${CMAKE_CURRENT_BINARY_DIR}/config.cmake)
+else()
+  if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/config.cmake)
+    include(${CMAKE_CURRENT_SOURCE_DIR}/config.cmake)
+  endif()
+endif()
 
 # include path
 include_directories(BEFORE "include")
+include_directories("tvm/include")
+include_directories("tvm/dlpack/include")
+include_directories("tvm/HalideIR/src")
 
-set(nnvm_LINKER_LIBS "")
-
+set(NNVM_LINKER_LIBS "")
+set(NNVM_COMPILER_LINKER_LIBS "")
 add_definitions(-DNNVM_EXPORTS)
 
 # Build a shared lib (libnnvm.so) by default
-option(BUILD_SHARED_NNVM "Build a shared nnvm lib" ON)            
+option(BUILD_SHARED_NNVM "Build a shared nnvm lib" ON)
 option(BUILD_STATIC_NNVM "Build a static nnvm lib" OFF)
 
 # compile
@@ -33,15 +43,15 @@ if(MSVC)
     endif(${flag_var} MATCHES "/MD")
   endforeach(flag_var)
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc")
+  find_library(TVM_LIB tvm  
+			   HINTS ${CMAKE_CURRENT_SOURCE_DIR}/../tvm/build/Release
+			   HINTS ${CMAKE_CURRENT_SOURCE_DIR}/tvm/build/Release) 
+  message(STATUS "Build with TVM libary: " ${TVM_LIB})			    
+  list(APPEND NNVM_COMPILER_LINKER_LIBS ${TVM_LIB})
 else(MSVC)
   include(CheckCXXCompilerFlag)
-  check_cxx_compiler_flag("-std=c++0x"    SUPPORT_CXX0X)
-  check_cxx_compiler_flag("-msse2"        SUPPORT_MSSE2)
-  check_cxx_compiler_flag("-openmp"       SUPPORT_OPENMP)
-  set(CMAKE_C_FLAGS "-O3 -Wall -msse2 -Wno-unknown-pragmas -std=c++0x -fPIC")
-  if(SUPPORT_OPENMP)
-    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fopenmp")
-  endif()
+  check_cxx_compiler_flag("-std=c++11"    SUPPORT_CXX11)
+  set(CMAKE_C_FLAGS "-O3 -Wall -std=c++11 -fPIC")
   set(CMAKE_CXX_FLAGS ${CMAKE_C_FLAGS})
 endif(MSVC)
 
@@ -57,11 +67,16 @@ mxnet_source_group("Source\\core"   GLOB "src/core/*.cc")
 mxnet_source_group("Source\\pass"   GLOB "src/pass/*.cc")
 
 
-FILE(GLOB_RECURSE SOURCE
+file(GLOB_RECURSE SOURCE
    src/c_api/*.cc
    src/core/*.cc
    src/pass/*.cc)
 
+file(GLOB_RECURSE COMPILER_SRCS
+    src/compiler/*.cc
+    src/top/*.cc
+)
+
 if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/dmlc-core/CMakeLists.txt)
   include_directories(${CMAKE_CURRENT_SOURCE_DIR}/dmlc-core/include)
 elseif(DMLC_CORE_PATH)
@@ -78,6 +93,9 @@ if(BUILD_STATIC_NNVM)
   set_target_properties(nnvm_static PROPERTIES OUTPUT_NAME "nnvm")
 endif()
 
+add_library(nnvm_compiler SHARED ${COMPILER_SRCS} ${SOURCE})
+target_link_libraries(nnvm_compiler ${NNVM_COMPILER_LINKER_LIBS} ${NNVM_LINKER_LIBS})
+
 if(INSTALL_INCLUDE_DIR)
   add_custom_command(TARGET nnvm POST_BUILD
     COMMAND COMMAND ${CMAKE_COMMAND} -E copy_directory
@@ -87,10 +105,10 @@ endif()
 
 # ---[ Install lib, header and docs
 if(BUILD_SHARED_NNVM)
-  install(TARGETS nnvm LIBRARY DESTINATION lib) 
+  install(TARGETS nnvm LIBRARY DESTINATION lib)
 endif()
 if(BUILD_STATIC_NNVM)
-  install(TARGETS nnvm_static ARCHIVE DESTINATION lib) 
+  install(TARGETS nnvm_static ARCHIVE DESTINATION lib)
 endif()
 install(DIRECTORY include DESTINATION .)
 install(DIRECTORY docs DESTINATION .)
diff --git a/nnvm/docs/how_to/install.md b/nnvm/docs/how_to/install.md
index 270aae63e8e3..113fd0967dcf 100644
--- a/nnvm/docs/how_to/install.md
+++ b/nnvm/docs/how_to/install.md
@@ -34,10 +34,26 @@ The minimal building requirement is
 You can edit `make/config.mk` to change the compile options, and then build by
 `make`. If everything goes well, we can go to the specific language installation section.
 
+### Building on Windows
+
+NNVM support build via MSVC using cmake. The minimum required VS version is **Visual Studio Community 2015 Update 3**.
+In order to generate the VS solution file using cmake, make sure you have a recent version of cmake added to your path.
+NNVM compiler depend on tvm, please follow [TVM document](http://docs.tvmlang.org/how_to/install.html#building-on-windows)
+to build the TVM windows library. You can build the TVM in the submodule folder under nnvm.
+
+After tvm is built, we can then start to build nnvm, using the following command.
+
+```bash
+mkdir build
+cd build
+cmake -G "Visual Studio 14 2015 Win64" -DCMAKE_BUILD_TYPE=Release -DCMAKE_CONFIGURATION_TYPES="Release" ..
+```
+This will generate the VS project using the MSVC 14 64 bit generator. Open the .sln file in the build directory and build with Visual Studio.
+
 ## Python Package Installation
 
-The python package is located at python
-There are several ways to install the package:
+The python package is located at python.
+There are several ways to install the package, in all these cases the TVM library must be present in the python env:
 
 1. Set the environment variable `PYTHONPATH` to tell python where to find
    the library. For example, assume we cloned `nnvm` on the home directory
diff --git a/nnvm/python/nnvm/libinfo.py b/nnvm/python/nnvm/libinfo.py
index 5dfa6377e14b..4a326b269aff 100644
--- a/nnvm/python/nnvm/libinfo.py
+++ b/nnvm/python/nnvm/libinfo.py
@@ -26,7 +26,7 @@ def find_lib_path():
     if hasattr(__builtin__, "NNVM_LIBRARY_NAME"):
         lib_name = __builtin__.NNVM_LIBRARY_NAME
     else:
-        lib_name = "libnnvm_compiler"
+        lib_name = "nnvm_compiler" if sys.platform.startswith('win32') else "libnnvm_compiler"
 
     api_path = os.path.join(base_path, '../../lib/')
     cmake_build_path = os.path.join(base_path, '../../build/Release/')
@@ -40,11 +40,11 @@ def find_lib_path():
     if sys.platform.startswith('win32'):
         vs_configuration = 'Release'
         if platform.architecture()[0] == '64bit':
-            dll_path.append(os.path.join(curr_path, '../../build', vs_configuration))
-            dll_path.append(os.path.join(curr_path, '../../windows/x64', vs_configuration))
+            dll_path.append(os.path.join(base_path, '../../build', vs_configuration))
+            dll_path.append(os.path.join(base_path, '../../windows/x64', vs_configuration))
         else:
-            dll_path.append(os.path.join(curr_path, '../../build', vs_configuration))
-            dll_path.append(os.path.join(curr_path, '../../windows', vs_configuration))
+            dll_path.append(os.path.join(base_path, '../../build', vs_configuration))
+            dll_path.append(os.path.join(base_path, '../../windows', vs_configuration))
         dll_path = [os.path.join(p, '%s.dll' % lib_name) for p in dll_path]
     elif sys.platform.startswith('darwin'):
         dll_path = [os.path.join(p, '%s.dylib' % lib_name) for p in dll_path]
diff --git a/nnvm/python/setup.py b/nnvm/python/setup.py
index 5f5b3cada78d..2b2102e319eb 100644
--- a/nnvm/python/setup.py
+++ b/nnvm/python/setup.py
@@ -1,5 +1,6 @@
 import os
 import sys
+from setuptools import find_packages
 from distutils.core import setup
 
 def config_cython():
@@ -29,7 +30,28 @@ def config_cython():
         print("Cython is not installed, will compile without cython module")
         return []
 
-setup(
-    name='nnvm',
-    ext_modules = config_cython()
-)
+# We can not import `libinfo.py` in setup.py directly since __init__.py
+# Will be invoked which introduces dependences
+CURRENT_DIR = os.path.dirname(__file__)
+libinfo_py = os.path.join(CURRENT_DIR, './nnvm/libinfo.py')
+libinfo = {'__file__': libinfo_py}
+exec(compile(open(libinfo_py, "rb").read(), libinfo_py, 'exec'), libinfo, libinfo)
+
+LIB_PATH = libinfo['find_lib_path']()
+_, LIB_NAME = os.path.split(LIB_PATH[0])
+__version__ = libinfo['__version__']
+curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
+rpath = os.path.relpath(LIB_PATH[0], curr_path)
+
+setup(name='nnvm',
+      version=__version__,      
+      description="NNVM: Open Compiler for AI Frameworks",
+      zip_safe=False,
+      install_requires=[
+        'numpy'
+      ],
+      packages=find_packages(),
+      url='https://github.com/dmlc/nnvm',
+      include_package_data=True,
+      data_files=[('nnvm', [rpath])])
+

From 07050d353d1a32a8cb0666c1d7fb213063f7d93a Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 9 Oct 2017 15:21:39 -0700
Subject: [PATCH 529/949] [RUNTIME] Remove header dependency on private header
 (#163)

---
 nnvm/docs/how_to/install.md        | 3 ++-
 nnvm/src/compiler/graph_runtime.cc | 2 --
 nnvm/src/compiler/graph_runtime.h  | 6 +++++-
 3 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/nnvm/docs/how_to/install.md b/nnvm/docs/how_to/install.md
index 113fd0967dcf..ffd1ecf40be2 100644
--- a/nnvm/docs/how_to/install.md
+++ b/nnvm/docs/how_to/install.md
@@ -16,7 +16,8 @@ git submodule init
 git submodule update --recursive
 ```
 
-NNVM compiler depend on TVM and TOPI, so make sure you install them by following [TVM document](http://docs.tvmlang.org/)
+NNVM compiler depend on TVM and TOPI, so make sure you install them by following [TVM document](http://docs.tvmlang.org/).
+Note that it is necessary to build TVM with LLVM support to take full benefit of NNVM compiler.
 
 ## Contents
 - [Build the Shared Library](#build-the-shared-library)
diff --git a/nnvm/src/compiler/graph_runtime.cc b/nnvm/src/compiler/graph_runtime.cc
index 902ccad25914..51a240a46119 100644
--- a/nnvm/src/compiler/graph_runtime.cc
+++ b/nnvm/src/compiler/graph_runtime.cc
@@ -15,8 +15,6 @@ namespace compiler {
 using tvm::runtime::TVMArgs;
 using tvm::runtime::TVMRetValue;
 using tvm::runtime::PackedFunc;
-using tvm::runtime::kTVMNDArrayMagic;
-using tvm::runtime::kTVMNDArrayListMagic;
 
 DMLC_REGISTER_PARAMETER(TVMOpParam);
 
diff --git a/nnvm/src/compiler/graph_runtime.h b/nnvm/src/compiler/graph_runtime.h
index cbea8f22b420..49a2e9bfc975 100644
--- a/nnvm/src/compiler/graph_runtime.h
+++ b/nnvm/src/compiler/graph_runtime.h
@@ -9,11 +9,15 @@
 #include <nnvm/graph.h>
 #include <vector>
 #include <string>
-#include "../../tvm/src/runtime/graph/graph_runtime.h"
 
 namespace nnvm {
 namespace compiler {
 
+/*! \brief Magic number for NDArray file */
+constexpr uint64_t kTVMNDArrayMagic = 0xDD5E40F096B4A13F;
+/*! \brief Magic number for NDArray list file  */
+constexpr uint64_t kTVMNDArrayListMagic = 0xF7E58D4F05049CB7;
+
 struct TVMOpParam : public dmlc::Parameter<TVMOpParam> {
   std::string func_name;
   uint32_t num_inputs;

From 473275d609439c4e2f2333bae1007cfb000cef67 Mon Sep 17 00:00:00 2001
From: ziheng <ziheng@apache.org>
Date: Tue, 10 Oct 2017 10:18:39 -0700
Subject: [PATCH 530/949] [FIX] Fix Typo (#164)

---
 nnvm/examples/benchmark/rasp_imagenet_bench.py | 2 +-
 nnvm/tutorials/deploy_model_on_rasp.py         | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/nnvm/examples/benchmark/rasp_imagenet_bench.py b/nnvm/examples/benchmark/rasp_imagenet_bench.py
index bba40c84d6dd..5c3a9b519e9a 100644
--- a/nnvm/examples/benchmark/rasp_imagenet_bench.py
+++ b/nnvm/examples/benchmark/rasp_imagenet_bench.py
@@ -23,7 +23,7 @@ def main():
     args = parser.parse_args()
 
     opt_level = args.opt_level
-    target = "llvm -target=armv7l-none-linux-anueabihf -mcpu=cortex-a53 -mattr=+neon"
+    target = "llvm -target=armv7l-none-linux-gnueabihf -mcpu=cortex-a53 -mattr=+neon"
 
     num_iter = args.num_iter
     batch_size = 1
diff --git a/nnvm/tutorials/deploy_model_on_rasp.py b/nnvm/tutorials/deploy_model_on_rasp.py
index 5edf1cf1233c..1de7b4c56381 100644
--- a/nnvm/tutorials/deploy_model_on_rasp.py
+++ b/nnvm/tutorials/deploy_model_on_rasp.py
@@ -177,7 +177,7 @@ def transform_image(image):
 # Pi, which has been proved as a good compilation configuration.
 
 if use_rasp:
-    target = "llvm -target=armv7l-none-linux-anueabihf -mcpu=cortex-a53 -mattr=+neon"
+    target = "llvm -target=armv7l-none-linux-gnueabihf -mcpu=cortex-a53 -mattr=+neon"
 else:
     target = "llvm"
 

From 9c71a9586ba83db713f77c24c5e925bd8176dd4e Mon Sep 17 00:00:00 2001
From: PENGUINLIONG <penguinliong@sina.com>
Date: Wed, 11 Oct 2017 01:18:48 +0800
Subject: [PATCH 531/949] Fix Cmake error on Windows (#160)

---
 nnvm/CMakeLists.txt | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/nnvm/CMakeLists.txt b/nnvm/CMakeLists.txt
index 30e2ad3b0397..da89bdb24d9d 100644
--- a/nnvm/CMakeLists.txt
+++ b/nnvm/CMakeLists.txt
@@ -105,7 +105,12 @@ endif()
 
 # ---[ Install lib, header and docs
 if(BUILD_SHARED_NNVM)
-  install(TARGETS nnvm LIBRARY DESTINATION lib)
+  if(WIN32)
+    install(TARGETS nnvm RUNTIME DESTINATION bin)
+    install(TARGETS nnvm ARCHIVE DESTINATION lib)
+  else()
+    install(TARGETS nnvm LIBRARY DESTINATION lib)
+  endif()
 endif()
 if(BUILD_STATIC_NNVM)
   install(TARGETS nnvm_static ARCHIVE DESTINATION lib)

From 9ed11bcae518d05f166127ec622b3f56959e8f1e Mon Sep 17 00:00:00 2001
From: "Joshua Z. Zhang" <cheungchih@gmail.com>
Date: Tue, 10 Oct 2017 16:41:37 -0700
Subject: [PATCH 532/949] [Frontend] Onnx improvement (#165)

* fix recently released layers

* fix fc layers with partial infer_shape
---
 nnvm/python/nnvm/frontend/onnx.py | 54 +++++++++++++++++++++----------
 1 file changed, 37 insertions(+), 17 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/onnx.py b/nnvm/python/nnvm/frontend/onnx.py
index 667c1787bf7e..fcd15db1fe30 100644
--- a/nnvm/python/nnvm/frontend/onnx.py
+++ b/nnvm/python/nnvm/frontend/onnx.py
@@ -3,6 +3,8 @@
 from __future__ import absolute_import as _abs
 import tvm
 from .. import symbol as _sym
+from .. import graph as _graph
+from .. compiler import graph_util
 from .common import Renamer, AttrConverter as AttrCvt
 
 __all__ = ['from_onnx']
@@ -60,9 +62,9 @@ def _pooling(name):
             'kernel_shape': 'pool_size',
             'pads': ('padding', (0, 0), _revert_caffe2_pad)},
         # very weird attributes here in onnx, force check
-        excludes=['dilations'],
+        ignores=['dilations'],
         # TODO(zhreshold): make sure ceil_mode in onnx, and layout?
-        extras={'ceil_mode': True},
+        extras={'ceil_mode': False},
         custom_check=_dimension_constraint())
 
 def _conv():
@@ -90,7 +92,7 @@ def _batch_norm():
     return AttrCvt(
         op_name='batch_norm',
         disables=['momentum'],
-        ignores=['spatial', 'is_test'])
+        ignores=['spatial', 'is_test', 'consumed_inputs'])
 
 
 # compatible operators that do NOT require any conversion.
@@ -100,6 +102,7 @@ def _batch_norm():
 _convert_map = {
     # defs/experimental
     'FC'            : AttrCvt('dense', ignores=['axis', 'axis_w']),
+    'SpatialBN'     : _batch_norm(),
 
     # defs/generator
     # 'Constant'
@@ -200,7 +203,7 @@ def _convert_operator(op_name, attrs, identity_list=None, convert_map=None):
     elif op_name in convert_map:
         op_name, attrs = convert_map[op_name](attrs)
     else:
-        _raise_not_supported('Operator: ' + op_name)
+        raise NotImplementedError("Operator {} not implemented.".format(op_name))
     op = getattr(_sym, op_name, None)
     if not op:
         raise RuntimeError("Unable to map op_name {} to nnvm.sym".format(op_name))
@@ -267,10 +270,11 @@ def from_onnx(self, graph):
             new_attr = self._fix_channels(new_op, new_attr, list(node.input))
             self._fix_bias_shape(node.op_type, graph.node[idx-1].op_type, node.input)
             op = new_op(name=node_name, *inputs, **new_attr)
-            assert len(node.output) == len(op.list_output_names()), (
-                "Number of output mismatch {} vs {}.".format(
-                    len(node.output), len(op.list_output_names())))
-            for k, i in zip(list(node.output), range(len(node.output))):
+            node_output = self._fix_outputs(op_name, node.output)
+            assert len(node_output) == len(op.list_output_names()), (
+                "Number of output mismatch {} vs {} in {}.".format(
+                    len(node_output), len(op.list_output_names()), op_name))
+            for k, i in zip(list(node_output), range(len(node_output))):
                 self._nodes[k] = op[i]
         # now return the outputs
         out = [self._nodes[i] for i in graph.output]
@@ -310,6 +314,15 @@ def _parse_attr(self, attr_proto):
                 raise ValueError("Cannot parse attribute: \n{}\n.".format(a))
         return attrs
 
+    def _fix_outputs(self, op, outputs):
+        """A hack to handle dropout or similar operator that have more than one out
+        in ONNX.
+        """
+        if op == 'Dropout':
+            assert len(outputs) == 2, "ONNX have two outputs for dropout layer."
+            outputs = outputs[:-1]
+        return outputs
+
     def _fix_bias(self, op, attrs, num_inputs):
         """A hack for 'use_bias' attribute since onnx don't provide this attribute,
         we have to check the number of inputs to decide it."""
@@ -340,17 +353,24 @@ def _fix_channels(self, op, attrs, inputs):
         """
         if op not in [_sym.conv2d, _sym.conv2d_transpose, _sym.dense]:
             return attrs
-        weight_name = self._renames[inputs[1]]
-        if not weight_name in self._params:
-            raise ValueError("Unable to get channels/units attr from onnx graph.")
+        if inputs[1] not in self._renames:
+            assert inputs[1] in self._nodes
+            g = _graph.create(self._nodes[inputs[1]])
+            shape_dict = {k: v.shape for k, v in self._params.items()}
+            _, out_shapes = graph_util.infer_shape(g, **shape_dict)
+            channels = out_shapes[0][0]
         else:
-            wshape = self._params[weight_name].shape
-            assert len(wshape) >= 2, "Weights shape is invalid: {}".format(wshape)
-            channels = wshape[0]
-            if op in [_sym.dense]:
-                attrs['units'] = channels
+            weight_name = self._renames[inputs[1]]
+            if not weight_name in self._params:
+                raise ValueError("Unable to get channels/units attr from onnx graph.")
             else:
-                attrs['channels'] = channels
+                wshape = self._params[weight_name].shape
+                assert len(wshape) >= 2, "Weights shape is invalid: {}".format(wshape)
+                channels = wshape[0]
+        if op in [_sym.dense]:
+            attrs['units'] = channels
+        else:
+            attrs['channels'] = channels
         return attrs
 
 def from_onnx(graph):

From ee3f2eb588bf053fb3220ce38fec93d7d48b910c Mon Sep 17 00:00:00 2001
From: Eric Junyuan Xie <piiswrong@users.noreply.github.com>
Date: Wed, 11 Oct 2017 12:27:38 -0700
Subject: [PATCH 533/949] fix symbol output compose (#166)

---
 nnvm/src/core/symbolic.cc | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index 38050367582c..ce56db255d7b 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -268,14 +268,6 @@ void Symbol::Compose(const array_view<const Symbol*>& args,
   static auto& flist_inputs = Op::GetAttr<FListInputNames>("FListInputNames");
   static auto& fset_attrs = Op::GetAttr<FSetInputVarAttrOnCompose>("FSetInputVarAttrOnCompose");
 
-  for (size_t i = 0; i < outputs.size(); ++i) {
-    if (outputs[i].node->is_variable()) {
-      CHECK_EQ(args.size(), 0) << "Variable composition only supports keyword arguments";
-      const auto it = kwargs.find(outputs[i].node->attrs.name);
-      if (it != kwargs.end()) outputs[i] = it->second->outputs[0];
-    }
-  }
-
   // parameter check.
   for (size_t i = 0; i < args.size(); ++i) {
     CHECK_EQ(args[i]->outputs.size(), 1U)
@@ -407,6 +399,15 @@ void Symbol::Compose(const array_view<const Symbol*>& args,
                                    dmlc::BeginPtr(arg_names) + arg_names.size());
       KeywordArgumentMismatch("Symbol.Compose", keys, arg_names);
     }
+
+    // update outputs in case the composed variable is part of outputs.
+    for (size_t i = 0; i < outputs.size(); ++i) {
+      if (outputs[i].node->is_variable()) {
+        CHECK_EQ(args.size(), 0) << "Variable composition only supports keyword arguments";
+        const auto it = kwargs.find(outputs[i].node->attrs.name);
+        if (it != kwargs.end()) outputs[i] = it->second->outputs[0];
+      }
+    }
   }
 }
 

From a9baf4ea792bf35a4a3be2e959bd36f7c90d7a71 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 11 Oct 2017 14:32:17 -0700
Subject: [PATCH 534/949] [BUILD] Make the core library compatible with msvc 13
 (#167)

---
 nnvm/include/nnvm/tuple.h | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/nnvm/include/nnvm/tuple.h b/nnvm/include/nnvm/tuple.h
index 551300b277ca..e571a14d5c38 100644
--- a/nnvm/include/nnvm/tuple.h
+++ b/nnvm/include/nnvm/tuple.h
@@ -611,12 +611,15 @@ struct hash<nnvm::TShape> {
 }  // namespace std
 
 namespace dmlc {
+// avoid low version of MSVC
+#if !defined(_MSC_VER) || _MSC_VER >= 1900
 template<typename T>
 struct type_name_helper<nnvm::Tuple<T> > {
   static inline std::string value() {
     return "tuple of <" + type_name<T>() + ">";
   }
 };
+#endif
 }
 
 #endif  // NNVM_TUPLE_H_

From c6ee7124d6362e828300f5cb89c1006b444a7442 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 12 Oct 2017 18:31:24 -0700
Subject: [PATCH 535/949] Fix lint, temporary add amaga back (#170)

---
 nnvm/amalgamation/.gitignore      |   2 +
 nnvm/amalgamation/Makefile        |  32 ++++++++++
 nnvm/amalgamation/README          |   1 +
 nnvm/amalgamation/amalgamation.py | 100 ++++++++++++++++++++++++++++++
 nnvm/amalgamation/generate.py     |  18 ++++++
 nnvm/include/nnvm/tuple.h         |   5 +-
 6 files changed, 155 insertions(+), 3 deletions(-)
 create mode 100644 nnvm/amalgamation/.gitignore
 create mode 100644 nnvm/amalgamation/Makefile
 create mode 100644 nnvm/amalgamation/README
 create mode 100644 nnvm/amalgamation/amalgamation.py
 create mode 100644 nnvm/amalgamation/generate.py

diff --git a/nnvm/amalgamation/.gitignore b/nnvm/amalgamation/.gitignore
new file mode 100644
index 000000000000..e808ea2764c3
--- /dev/null
+++ b/nnvm/amalgamation/.gitignore
@@ -0,0 +1,2 @@
+nnvm.d
+nnvm.cc
diff --git a/nnvm/amalgamation/Makefile b/nnvm/amalgamation/Makefile
new file mode 100644
index 000000000000..1f286f055237
--- /dev/null
+++ b/nnvm/amalgamation/Makefile
@@ -0,0 +1,32 @@
+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
diff --git a/nnvm/amalgamation/README b/nnvm/amalgamation/README
new file mode 100644
index 000000000000..be16ec5f6c8f
--- /dev/null
+++ b/nnvm/amalgamation/README
@@ -0,0 +1 @@
+This folder is deprecated and will be deleted in the future.
\ No newline at end of file
diff --git a/nnvm/amalgamation/amalgamation.py b/nnvm/amalgamation/amalgamation.py
new file mode 100644
index 000000000000..310daa9d68e0
--- /dev/null
+++ b/nnvm/amalgamation/amalgamation.py
@@ -0,0 +1,100 @@
+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
+
diff --git a/nnvm/amalgamation/generate.py b/nnvm/amalgamation/generate.py
new file mode 100644
index 000000000000..84a5fc06fb03
--- /dev/null
+++ b/nnvm/amalgamation/generate.py
@@ -0,0 +1,18 @@
+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()
diff --git a/nnvm/include/nnvm/tuple.h b/nnvm/include/nnvm/tuple.h
index e571a14d5c38..353b79ab0641 100644
--- a/nnvm/include/nnvm/tuple.h
+++ b/nnvm/include/nnvm/tuple.h
@@ -612,7 +612,7 @@ struct hash<nnvm::TShape> {
 
 namespace dmlc {
 // avoid low version of MSVC
-#if !defined(_MSC_VER) || _MSC_VER >= 1900
+#if !defined(_MSC_VER)
 template<typename T>
 struct type_name_helper<nnvm::Tuple<T> > {
   static inline std::string value() {
@@ -620,6 +620,5 @@ struct type_name_helper<nnvm::Tuple<T> > {
   }
 };
 #endif
-}
-
+}  // namespace dmlc
 #endif  // NNVM_TUPLE_H_

From b5ea79dd2c1033ebf02d7c5d94bde51113e06d17 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 14 Oct 2017 17:23:41 -0700
Subject: [PATCH 536/949] [TVM] upgrade to generic schedule (#173)

---
 nnvm/python/nnvm/compiler/build_module.py | 19 +++++---
 nnvm/python/nnvm/top/nn.py                | 57 +++++++----------------
 nnvm/python/nnvm/top/reduction.py         | 11 ++---
 nnvm/python/nnvm/top/tensor.py            | 13 ++----
 4 files changed, 36 insertions(+), 64 deletions(-)

diff --git a/nnvm/python/nnvm/compiler/build_module.py b/nnvm/python/nnvm/compiler/build_module.py
index 92f39d712265..f2fc77d07287 100644
--- a/nnvm/python/nnvm/compiler/build_module.py
+++ b/nnvm/python/nnvm/compiler/build_module.py
@@ -161,7 +161,7 @@ def optimize(graph, shape, dtype="float32"):
     return graph
 
 
-def build(graph, target, shape, dtype="float32", params=None):
+def build(graph, target=None, shape=None, dtype="float32", params=None):
     """Build graph into runtime library.
 
     The build function will optimize the graph and do the compilation.
@@ -175,10 +175,10 @@ def build(graph, target, shape, dtype="float32", params=None):
     graph : Graph
         The graph to be used in lowering
 
-    target : str
+    target : str or :any:`tvm.target.Target`, optional
         The build target
 
-    shape : dict of str to tuple
+    shape : dict of str to tuple, optional
         The input shape to the graph
 
     dtype : str or dict of str to str
@@ -201,8 +201,12 @@ def build(graph, target, shape, dtype="float32", params=None):
         The updated parameters of graph if params is passed.
         This can be different from the params passed in.
     """
-    if not isinstance(target, str):
-        raise TypeError("require target to be str")
+    target = target if target else tvm.target.current_target()
+    if target is None:
+        raise ValueError("Target is not set in env or passed as argument.")
+    target = tvm.target.create(target)
+
+    shape = shape if shape else {}
     if not isinstance(shape, dict):
         raise TypeError("require shape to be dict")
     cfg = BuildConfig.current
@@ -223,13 +227,14 @@ def build(graph, target, shape, dtype="float32", params=None):
     # Operator Fusion and generatiom
     graph = graph_attr.set_shape_inputs(graph, shape)
     graph = graph_attr.set_dtype_inputs(graph, dtype)
-    graph._set_json_attr("target", target, "str")
+    graph._set_json_attr("target", str(target), "str")
     if cfg.pass_enabled("OpFusion"):
         graph._set_json_attr("opt_level", 1, "int")
     else:
         graph._set_json_attr("opt_level", 0, "int")
     graph = graph.apply("InferShape").apply("InferType")
-    graph = graph.apply("GraphFusePartition").apply("GraphFuseCompile")
+    with target:
+        graph = graph.apply("GraphFusePartition").apply("GraphFuseCompile")
     libmod = graph_attr._move_out_module(graph, "module")
     return graph, libmod, params
 
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index 6efde0c573cd..e54da0511cd6 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -50,10 +50,9 @@ def compute_softmax(attrs, inputs, _):
 @reg.register_schedule("softmax")
 def schedule_softmax(_, outs, target):
     """Schedule definition of softmax"""
-    if target == "cuda":
-        return topi.cuda.schedule_softmax(outs)
-    # naive schedule
-    return tvm.create_schedule([x.op for x in outs])
+    with tvm.target.create(target):
+        return topi.generic.schedule_softmax(outs)
+
 
 reg.register_pattern("softmax", OpPattern.OPAQUE)
 
@@ -68,10 +67,8 @@ def compute_log_softmax(attrs, inputs, _):
 @reg.register_schedule("log_softmax")
 def schedule_log_softmax(_, outs, target):
     """Schedule definition of softmax"""
-    if target == "cuda":
-        return topi.cuda.schedule_softmax(outs)
-    # naive schedule
-    return tvm.create_schedule([x.op for x in outs])
+    with tvm.target.create(target):
+        return topi.generic.schedule_softmax(outs)
 
 # Mark softmax as extern as we do not fuse it in call cases
 reg.register_pattern("log_softmax", OpPattern.OPAQUE)
@@ -87,10 +84,8 @@ def compute_dense(attrs, inputs, _):
 @reg.register_schedule("dense")
 def schedule_dense(_, outs, target):
     """Schedule definition of dense"""
-    if target == "cuda":
-        return topi.cuda.schedule_dense(outs)
-    # naive schedule
-    return tvm.create_schedule([x.op for x in outs])
+    with tvm.target.create(target):
+        return topi.generic.schedule_dense(outs)
 
 reg.register_pattern("dense", OpPattern.OUT_ELEMWISE_FUSABLE)
 
@@ -123,18 +118,10 @@ def compute_conv2d(attrs, inputs, _):
 def schedule_conv2d(attrs, outs, target):
     """Schedule definition of conv2d"""
     groups = attrs.get_int("groups")
-    if target == "cuda":
-        if groups == 1:
-            return topi.cuda.schedule_conv2d_nchw(outs)
-        return topi.cuda.schedule_depthwise_conv2d_nchw(outs)
-    # naive schedule
-
-    if tvm.target.current_target() == tvm.target.rasp():
+    with tvm.target.create(target):
         if groups == 1:
-            return topi.rasp.schedule_conv2d(outs)
-        return topi.rasp.schedule_depthwise_conv2d(outs)
-
-    return tvm.create_schedule([x.op for x in outs])
+            return topi.generic.schedule_conv2d_nchw(outs)
+        return topi.generic.schedule_depthwise_conv2d_nchw(outs)
 
 reg.register_pattern("conv2d", OpPattern.OUT_ELEMWISE_FUSABLE)
 
@@ -155,10 +142,8 @@ def compute_max_pool2d(attrs, inputs, _):
 @reg.register_schedule("max_pool2d")
 def schedule_max_pool2d(_, outs, target):
     """Schedule definition of max_pool2d"""
-    if target == "cuda":
-        return topi.cuda.schedule_pool(outs)
-    # naive schedule
-    return tvm.create_schedule([x.op for x in outs])
+    with tvm.target.create(target):
+        return topi.generic.schedule_pool(outs)
 
 reg.register_pattern("max_pool2d", OpPattern.OUT_ELEMWISE_FUSABLE)
 
@@ -179,10 +164,8 @@ def compute_avg_pool2d(attrs, inputs, _):
 @reg.register_schedule("avg_pool2d")
 def schedule_avg_pool2d(_, outs, target):
     """Schedule definition of avg_pool2d"""
-    if target == "cuda":
-        return topi.cuda.schedule_pool(outs)
-    # naive schedule
-    return tvm.create_schedule([x.op for x in outs])
+    with tvm.target.create(target):
+        return topi.generic.schedule_pool(outs)
 
 reg.register_pattern("avg_pool2d", OpPattern.OUT_ELEMWISE_FUSABLE)
 
@@ -198,10 +181,8 @@ def compute_global_max_pool2d(attrs, inputs, _):
 @reg.register_schedule("global_max_pool2d")
 def schedule_global_max_pool2d(_, outs, target):
     """Schedule definition of global_max_pool2d"""
-    if target == "cuda":
-        return topi.cuda.schedule_global_pool(outs)
-    # naive schedule
-    return tvm.create_schedule([x.op for x in outs])
+    with tvm.target.create(target):
+        return topi.generic.schedule_global_pool(outs)
 
 reg.register_pattern("global_max_pool2d", OpPattern.OUT_ELEMWISE_FUSABLE)
 
@@ -217,9 +198,7 @@ def compute_global_avg_pool2d(attrs, inputs, _):
 @reg.register_schedule("global_avg_pool2d")
 def schedule_global_avg_pool2d(_, outs, target):
     """Schedule definition of global_avg_pool2d"""
-    if target == "cuda":
-        return topi.cuda.schedule_global_pool(outs)
-    # naive schedule
-    return tvm.create_schedule([x.op for x in outs])
+    with tvm.target.create(target):
+        return topi.generic.schedule_global_pool(outs)
 
 reg.register_pattern("global_avg_pool2d", OpPattern.OUT_ELEMWISE_FUSABLE)
diff --git a/nnvm/python/nnvm/top/reduction.py b/nnvm/python/nnvm/top/reduction.py
index bee401e42545..7003d3a52df3 100644
--- a/nnvm/python/nnvm/top/reduction.py
+++ b/nnvm/python/nnvm/top/reduction.py
@@ -10,14 +10,9 @@
 
 def _schedule_reduce(_, outs, target):
     """Generic schedule for reduce"""
-    if target == "cuda":
-        return topi.cuda.schedule_reduce(outs)
-    assert target.startswith("llvm")
-    s = tvm.create_schedule([x.op for x in outs])
-    x = outs[0]
-    tvm.schedule.AutoInlineInjective(s)
-    s[x].fuse(s[x].op.axis)
-    return s
+    with tvm.target.create(target):
+        return topi.generic.schedule_reduce(outs)
+
 
 _fschedule_reduce = tvm.convert(_schedule_reduce)
 
diff --git a/nnvm/python/nnvm/top/tensor.py b/nnvm/python/nnvm/top/tensor.py
index 15163e28cc90..7ca800e5b7dc 100644
--- a/nnvm/python/nnvm/top/tensor.py
+++ b/nnvm/python/nnvm/top/tensor.py
@@ -10,15 +10,8 @@
 
 def _schedule_injective(_, outs, target):
     """Generic schedule for binary bcast"""
-    if target == "cuda":
-        return topi.cuda.schedule_injective(outs)
-    assert target.startswith("llvm")
-    s = tvm.create_schedule([x.op for x in outs])
-    x = outs[0]
-    tvm.schedule.AutoInlineInjective(s)
-    s[x].fuse(s[x].op.axis)
-    return s
-
+    with tvm.target.create(target):
+        return topi.generic.schedule_injective(outs)
 
 def _compute_binary_scalar(f):
     """auxiliary function"""
@@ -174,7 +167,7 @@ def _compute(attrs, x, _):
 
 # broadcast_to
 @reg.register_compute("broadcast_to")
-def compute_softmax(attrs, inputs, out_info):
+def compute_broadcast_to(attrs, inputs, out_info):
     """Compute definition of softmax"""
     return topi.broadcast_to(inputs[0], shape=out_info[0].shape)
 reg.register_pattern("broadcast_to", OpPattern.BROADCAST)

From c9addb3a4415e89aafe6f608a2280157d5b969d6 Mon Sep 17 00:00:00 2001
From: "Joshua Z. Zhang" <cheungchih@gmail.com>
Date: Sun, 15 Oct 2017 22:54:44 -0700
Subject: [PATCH 537/949] [FRONTEND] Composed operators (#175)

* fix for composed symbol

* fix

* clean up

* fix exception type
---
 nnvm/python/nnvm/frontend/mxnet.py            | 133 ++++++++++--------
 .../python/frontend/mxnet/test_forward.py     |  35 ++++-
 2 files changed, 109 insertions(+), 59 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/mxnet.py b/nnvm/python/nnvm/frontend/mxnet.py
index 0f5efd3b4ad2..6cbd497fd470 100644
--- a/nnvm/python/nnvm/frontend/mxnet.py
+++ b/nnvm/python/nnvm/frontend/mxnet.py
@@ -7,6 +7,12 @@
 
 __all__ = ['from_mxnet']
 
+def _get_nnvm_op(op_name):
+    op = getattr(_sym, op_name)
+    if not op:
+        raise RuntimeError("Unable to map op_name {} to nnvm.sym".format(op_name))
+    return op
+
 def _get_mxnet_version():
     try:
         import mxnet as mx
@@ -39,14 +45,11 @@ def _parse_bool_str(attr, key, default='False'):
     return attr.get(key, default).strip().lower() in ['true', '1', 't', 'y', 'yes']
 
 def _rename(new_name):
-    def impl(attr):
-        return new_name, attr
+    def impl(inputs, attrs):
+        return _get_nnvm_op(new_name)(*inputs, **attrs)
     return impl
 
-def _variable(attrs):
-    return "Variable", attrs
-
-def _pooling(attrs):
+def _pooling(inputs, attrs):
     kernel = _parse_tshape(_required_attr(attrs, 'kernel'))
     if len(kernel) != 2:
         _raise_not_supported('non-2d kernel', 'pool_2d')
@@ -61,9 +64,9 @@ def _pooling(attrs):
         new_attrs['strides'] = attrs.get('stride', (1, 1))
         new_attrs['padding'] = attrs.get('pad', (0, 0))
         new_attrs['ceil_mode'] = (attrs.get('pooling_convention', 'valid') == 'full')
-    return op_name, new_attrs
+    return _get_nnvm_op(op_name)(*inputs, **new_attrs)
 
-def _batch_norm(attrs):
+def _batch_norm(inputs, attrs):
     if _parse_bool_str(attrs, 'output_mean_var'):
         _raise_not_supported('output_mean_var', 'batch_norm')
     # if _parse_bool_str(attrs, 'fix_gamma'):
@@ -77,14 +80,14 @@ def _batch_norm(attrs):
     new_attrs['epsilon'] = attrs.get('eps', 0.001)
     new_attrs['center'] = True
     new_attrs['scale'] = True
-    return op_name, new_attrs
+    return _get_nnvm_op(op_name)(*inputs, **new_attrs)
 
-def _concat(attrs):
+def _concat(inputs, attrs):
     op_name = 'concatenate'
     new_attrs = {'axis': attrs.get('dim', 1)}
-    return op_name, new_attrs
+    return _get_nnvm_op(op_name)(*inputs, **new_attrs)
 
-def _conv2d(attrs):
+def _conv2d(inputs, attrs):
     kernel = _parse_tshape(_required_attr(attrs, 'kernel'))
     if len(kernel) != 2:
         _raise_not_supported('non 2d kernel', 'conv2d')
@@ -100,9 +103,9 @@ def _conv2d(attrs):
     new_attrs['groups'] = attrs.get('num_group', 1)
     new_attrs['layout'] = layout
     new_attrs['use_bias'] = attrs.get('no_bias', 'False').strip() == 'False'
-    return op_name, new_attrs
+    return _get_nnvm_op(op_name)(*inputs, **new_attrs)
 
-def _conv2d_transpose(attrs):
+def _conv2d_transpose(inputs, attrs):
     if 'target_shape' in attrs:
         _raise_not_supported('target_shape', 'conv2d_transpose')
     kernel = _parse_tshape(_required_attr(attrs, 'kernel'))
@@ -121,51 +124,68 @@ def _conv2d_transpose(attrs):
     new_attrs['groups'] = attrs.get('num_group', 1)
     new_attrs['layout'] = layout
     new_attrs['use_bias'] = not _parse_bool_str(attrs, 'no_bias')
-    return op_name, new_attrs
+    return _get_nnvm_op(op_name)(*inputs, **new_attrs)
 
-def _dense(attrs):
+def _dense(inputs, attrs):
     op_name, new_attrs = 'dense', {}
     new_attrs['units'] = _required_attr(attrs, 'num_hidden')
     new_attrs['use_bias'] = not _parse_bool_str(attrs, 'no_bias')
     major, minor, micro = _get_mxnet_version()
     if major >= 0 and minor >= 11 and micro >= 1:
-        new_attrs['flatten'] = _parse_bool_str(attrs, 'flatten', 'True')
-    return op_name, new_attrs
+        use_flatten = _parse_bool_str(attrs, 'flatten', 'True')
+        if use_flatten:
+            inputs[0] = _sym.flatten(inputs[0])
+    return _get_nnvm_op(op_name)(*inputs, **new_attrs)
 
-def _dropout(attrs):
+def _dropout(inputs, attrs):
     op_name, new_attrs = 'dropout', {}
     new_attrs['rate'] = attrs.get('p', 0.5)
-    return op_name, new_attrs
+    return _get_nnvm_op(op_name)(*inputs, **new_attrs)
 
-def _leaky_relu(attrs):
+def _leaky_relu(inputs, attrs):
     act_type = _required_attr(attrs, 'act_type')
-    if act_type not in ['leaky']:
+    if act_type in ['leaky']:
+        op_name, new_attrs = 'leaky_relu', {}
+        new_attrs['alpha'] = attrs.get('slope', 0.25)
+        sym = _get_nnvm_op(op_name)(*inputs, **new_attrs)
+    elif act_type == 'elu':
+        slope = attrs.get('slope', 0.25)
+        sym = -slope * _sym.relu(1 - _sym.exp(*inputs)) + _sym.relu(*inputs)
+    elif act_type == 'rrelu':
+        lower_bound = float(_required_attr(attrs, 'lower_bound'))
+        upper_bound = float(_required_attr(attrs, 'upper_bound'))
+        slope = (lower_bound + upper_bound) / 2.0
+        op_name, new_attrs = 'leaky_relu', {'alpha': str(slope)}
+        sym = _get_nnvm_op(op_name)(*inputs, **new_attrs)
+    else:
         _raise_not_supported('act_type: ' + act_type)
-    op_name, new_attrs = 'leaky_relu', {}
-    new_attrs['alpha'] = attrs.get('slope', 0.25)
-    return op_name, new_attrs
+    return sym
 
-def _activations(attrs):
+def _activations(inputs, attrs):
     act_type = _required_attr(attrs, 'act_type')
-    if act_type not in ['relu', 'sigmoid', 'tanh']:
+    if act_type in ['relu', 'sigmoid', 'tanh']:
+        op_name, new_attrs = act_type, {}
+        sym = _get_nnvm_op(op_name)(*inputs, **new_attrs)
+    elif act_type == 'softrelu':
+        sym = _sym.log((1 + _sym.exp(*inputs)))
+    else:
         _raise_not_supported('act_type: ' + act_type)
-    op_name, new_attrs = act_type, {}
-    return op_name, new_attrs
+    return sym
 
-def _reshape(attrs):
+def _reshape(inputs, attrs):
     if _parse_bool_str(attrs, 'reverse'):
         _raise_not_supported('reverse', 'reshape')
     op_name, new_attrs = 'reshape', {}
     new_attrs['shape'] = _required_attr(attrs, 'shape')
-    return op_name, new_attrs
+    return _get_nnvm_op(op_name)(*inputs, **new_attrs)
 
-def _split(attrs):
+def _split(inputs, attrs):
     if _parse_bool_str(attrs, 'squeeze_axis'):
         _raise_not_supported('squeeze_axis', 'split')
     op_name, new_attrs = 'split', {}
     new_attrs['indices_or_sections'] = _required_attr(attrs, 'num_outputs')
     new_attrs['axis'] = attrs.get('axis', 1)
-    return op_name, new_attrs
+    return _get_nnvm_op(op_name)(*inputs, **new_attrs)
 
 _identity_list = ['__add_scalar__', '__add_symbol__', '__div_scalar__',
                   '__div_symbol__', '__mul_scalar__', '__mul_symbol__',
@@ -178,7 +198,12 @@ def _split(attrs):
                   'relu', 'sigmoid', 'softmax', 'sum', 'tanh', 'transpose']
 
 _convert_map = {
-    'null'          : _variable,
+    '_div_scalar'   : _rename('__div_scalar__'),
+    '_minus_scalar' : _rename('__sub_scalar__'),
+    '_mul_scalar'   : _rename('__mul_scalar__'),
+    '_plus_scalar'  : _rename('__add_scalar__'),
+    '_rdiv_scalar'  : _rename('__rdiv_scalar__'),
+    '_rminus_scalar': _rename('__rsub_scalar__'),
     'Activation'    : _activations,
     'BatchNorm'     : _batch_norm,
     'BatchNorm_v1'  : _batch_norm,
@@ -202,7 +227,7 @@ def _split(attrs):
     'sum_axis'      : _rename('sum'),
 }
 
-def _convert_symbol(op_name, attrs,
+def _convert_symbol(op_name, inputs, attrs,
                     identity_list=None,
                     convert_map=None):
     """Convert from mxnet op to nnvm op.
@@ -213,6 +238,8 @@ def _convert_symbol(op_name, attrs,
     ----------
     op_name : str
         Operator name, such as Convolution, FullyConnected
+    inputs : list of nnvm.Symbol
+        List of input symbols.
     attrs : dict
         Dict of operator attributes
     identity_list : list
@@ -224,21 +251,19 @@ def _convert_symbol(op_name, attrs,
 
     Returns
     -------
-    (op_name, attrs)
-        Converted (op_name, attrs) for nnvm.
+    sym : nnvm.Symbol
+        Converted nnvm Symbol
     """
     identity_list = identity_list if identity_list else _identity_list
     convert_map = convert_map if convert_map else _convert_map
     if op_name in identity_list:
-        pass
+        op = _get_nnvm_op(op_name)
+        sym = op(*inputs, **attrs)
     elif op_name in convert_map:
-        op_name, attrs = convert_map[op_name](attrs)
+        sym = convert_map[op_name](inputs, attrs)
     else:
         _raise_not_supported('Operator: ' + op_name)
-    op = getattr(_sym, op_name, None)
-    if not op:
-        raise RuntimeError("Unable to map op_name {} to nnvm.sym".format(op_name))
-    return op, attrs
+    return sym
 
 def _is_mxnet_group_symbol(symbol):
     """Internal check for mxnet group symbol."""
@@ -274,28 +299,20 @@ def _from_mxnet_impl(symbol, graph):
     node = graph.get(name, None)
     if node:
         return node
+    attr = symbol.list_attr()
     # op_name = symbol.attr('op_name')
-    if symbol.get_children():
+    childs = symbol.get_children()
+    if childs:
         op_name = symbol.attr('op_name')
-    else:
-        op_name = json.loads(symbol.tojson())['nodes'][0]['op']
-    attr = symbol.list_attr()
-    new_op, new_attr = _convert_symbol(op_name, attr)
-    if new_op == _sym.Variable:
-        node = new_op(name=name, **new_attr)
-    else:
-        childs = symbol.get_children()
         childs = [_from_mxnet_impl(c, graph) for c in _as_list(childs)]
         childs = [x for y in childs for x in _as_list(y)]  # expand group symbol
-        if new_op == _sym.dense and 'flatten' in new_attr:
-            if new_attr['flatten']:
-                childs[0] = _sym.flatten(childs[0])
-            new_attr.pop('flatten')
-        node = new_op(name=name, *childs, **new_attr)
+        node = _convert_symbol(op_name, childs, attr)
+    else:
+        op_name = json.loads(symbol.tojson())['nodes'][0]['op']
+        node = _sym.Variable(name=name, **attr)
     graph[name] = node
     return node
 
-
 def from_mxnet(symbol, arg_params=None, aux_params=None):
     """Convert from MXNet's model into compatible NNVM format.
 
diff --git a/nnvm/tests/python/frontend/mxnet/test_forward.py b/nnvm/tests/python/frontend/mxnet/test_forward.py
index ca7a0156a8b5..0f9747538ce9 100644
--- a/nnvm/tests/python/frontend/mxnet/test_forward.py
+++ b/nnvm/tests/python/frontend/mxnet/test_forward.py
@@ -46,7 +46,7 @@ def get_tvm_output(symbol, x, args, auxs, target, ctx, dtype='float32'):
     assert "data" not in args
     for target, ctx in ctx_list():
         tvm_out = get_tvm_output(mx_symbol, x, args, auxs, target, ctx, dtype)
-        np.testing.assert_allclose(mx_out, tvm_out, rtol=1e-5)
+        np.testing.assert_allclose(mx_out, tvm_out, rtol=1e-5, atol=1e-5)
 
 def test_forward_mlp():
     mlp = model_zoo.mx_mlp
@@ -62,7 +62,40 @@ def test_forward_resnet():
         mx_sym = model_zoo.mx_resnet[n]
         verify_mxnet_frontend_impl(mx_sym)
 
+def test_forward_elu():
+    data = mx.sym.var('data')
+    data = mx.sym.concat(data, -data, dim=1)  # negative part explicitly
+    mx_sym = mx.sym.LeakyReLU(data, act_type='elu')
+    verify_mxnet_frontend_impl(mx_sym, (1, 3, 100, 100), (1, 6, 100, 100))
+
+def test_forward_rrelu():
+    data = mx.sym.var('data')
+    data = mx.sym.concat(data, -data, dim=1)  # negative part explicitly
+    mx_sym = mx.sym.LeakyReLU(data, act_type='rrelu', lower_bound=0.3, upper_bound=0.7)
+    verify_mxnet_frontend_impl(mx_sym, (1, 3, 100, 100), (1, 6, 100, 100))
+
+def test_forward_softrelu():
+    data = mx.sym.var('data')
+    data = mx.sym.concat(data, -data, dim=1)  # negative part explicitly
+    mx_sym = mx.sym.Activation(data, act_type='softrelu')
+    verify_mxnet_frontend_impl(mx_sym, (1, 3, 100, 100), (1, 6, 100, 100))
+
+def test_forward_fc_flatten():
+    # test flatten=True option in mxnet 0.11.1
+    data = mx.sym.var('data')
+    try:
+        mx_sym = mx.sym.FullyConnected(data, num_hidden=100, flatten=True)
+        verify_mxnet_frontend_impl(mx_sym, (1, 3, 100, 100), (1, 100))
+        mx_sym = mx.sym.FullyConnected(mx.sym.Flatten(data), num_hidden=100, flatten=False)
+        verify_mxnet_frontend_impl(mx_sym, (1, 3, 100, 100), (1, 100))
+    except:
+        pass
+
 if __name__ == '__main__':
     test_forward_mlp()
     test_forward_vgg()
     test_forward_resnet()
+    test_forward_elu()
+    test_forward_rrelu()
+    test_forward_softrelu()
+    test_forward_fc_flatten()

From 6259ee576c218cb00297c0d1f701960391b2106d Mon Sep 17 00:00:00 2001
From: ziheng <ziheng@apache.org>
Date: Mon, 16 Oct 2017 21:43:56 -0700
Subject: [PATCH 538/949] [OPT] Improve PreComputePrune When Output Is Pruned
 (#178)

---
 nnvm/src/compiler/precompute_prune.cc | 40 +++++++++++++++++----------
 1 file changed, 26 insertions(+), 14 deletions(-)

diff --git a/nnvm/src/compiler/precompute_prune.cc b/nnvm/src/compiler/precompute_prune.cc
index 850a961fc63a..c3656b640891 100644
--- a/nnvm/src/compiler/precompute_prune.cc
+++ b/nnvm/src/compiler/precompute_prune.cc
@@ -27,6 +27,25 @@ nnvm::Graph PrecomputePrune(nnvm::Graph src) {
   // number of edges that are not variable
   int non_var_edge = 0;
 
+  auto replace_pruned_entry = [&] (const NodeEntry& e) {
+    if (!entry_var.count(e)) {
+      if (!e.node->is_variable()) {
+        ++non_var_edge;
+      }
+      nnvm::NodePtr var = nnvm::Node::Create();
+      var->attrs.name = e.node->attrs.name;
+      if (e.node->num_outputs() != 1) {
+        var->attrs.name += "_output" + std::to_string(e.index);
+      }
+      entry_var.emplace(e, var);
+      CHECK(!unique_name.count(var->attrs.name));
+      unique_name.insert(var->attrs.name);
+      return nnvm::NodeEntry{var, 0, 0};
+    } else {
+      return nnvm::NodeEntry{entry_var.at(e), 0, 0};
+    }
+  };
+
   DFSVisit(src.outputs, [&](const nnvm::NodePtr& n) {
     bool can_be_pruned = true;
     if (n->is_variable()) {
@@ -47,20 +66,7 @@ nnvm::Graph PrecomputePrune(nnvm::Graph src) {
       // scan again to find edge nodes, skip variables
       for (auto& e : n->inputs) {
         if (pruned.count(e.node.get())) {
-          if (!entry_var.count(e)) {
-            if (!e.node->is_variable()) {
-              ++non_var_edge;
-            }
-            nnvm::NodePtr var = nnvm::Node::Create();
-            var->attrs.name = e.node->attrs.name;
-            if (e.node->num_outputs() != 1) {
-              var->attrs.name += "_output" + std::to_string(e.index);
-            }
-            entry_var.emplace(e, var);
-            CHECK(!unique_name.count(var->attrs.name));
-            unique_name.insert(var->attrs.name);
-          }
-          e = nnvm::NodeEntry{entry_var.at(e), 0, 0};
+          e = replace_pruned_entry(e);
         }
       }
     }
@@ -71,6 +77,12 @@ nnvm::Graph PrecomputePrune(nnvm::Graph src) {
     return src;
   }
 
+  for (auto& e : src.outputs) {
+    if (pruned.count(e.node.get())) {
+      e = replace_pruned_entry(e);
+    }
+  }
+
   nnvm::Graph pre_graph;
   pre_graph.outputs.reserve(entry_var.size());
   std::vector<std::string> output_names;

From 3bec9a84642239315ebc55ec7d1662580834c7ff Mon Sep 17 00:00:00 2001
From: PENGUINLIONG <admin@penguinliong.moe>
Date: Thu, 19 Oct 2017 02:45:40 +0800
Subject: [PATCH 539/949] [FRONTEND] Correct the use of `concatenate` operator
 (#181)

* Correct the use of `concatenate` operator

* Optimize
---
 nnvm/python/nnvm/frontend/coreml.py | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/coreml.py b/nnvm/python/nnvm/frontend/coreml.py
index d8d6461effaa..e89c784dfe70 100644
--- a/nnvm/python/nnvm/frontend/coreml.py
+++ b/nnvm/python/nnvm/frontend/coreml.py
@@ -246,9 +246,7 @@ def ConcatLayerParams(op, insyms, symtab):
         insyms = [insyms]
     if op.sequenceConcat:
         raise NotImplementedError("Sequence Concat not supported")
-    ret = insyms[0]
-    for i in range(1, len(insyms)):
-        ret = _sym.concat(ret, insyms[i], dim=1)
+    ret = _sym.concatenate(*insyms, dim=1)
     return ret
 
 def FlattenLayerParams(op, insym, symtab):

From bd0a8d96118d9c7d74e3781dec85de7b18f32015 Mon Sep 17 00:00:00 2001
From: PENGUINLIONG <admin@penguinliong.moe>
Date: Fri, 20 Oct 2017 01:49:40 +0800
Subject: [PATCH 540/949] Fixed attribute parameter (#184)

---
 nnvm/python/nnvm/frontend/coreml.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/python/nnvm/frontend/coreml.py b/nnvm/python/nnvm/frontend/coreml.py
index e89c784dfe70..06bf80648da6 100644
--- a/nnvm/python/nnvm/frontend/coreml.py
+++ b/nnvm/python/nnvm/frontend/coreml.py
@@ -246,7 +246,7 @@ def ConcatLayerParams(op, insyms, symtab):
         insyms = [insyms]
     if op.sequenceConcat:
         raise NotImplementedError("Sequence Concat not supported")
-    ret = _sym.concatenate(*insyms, dim=1)
+    ret = _sym.concatenate(*insyms, axis=1)
     return ret
 
 def FlattenLayerParams(op, insym, symtab):

From de1eaa01a6b6637b57bdc2d994e0a141ea617799 Mon Sep 17 00:00:00 2001
From: Takeo Imai <imai@leapmind.io>
Date: Sun, 22 Oct 2017 01:50:34 +0900
Subject: [PATCH 541/949] Change the way to import ONNX model. (#186, #187)
 (#189)

* Change the way to import ONNX model.

* Decide ONNX version and change the processing

* Check whether onnx has the attribute '__version__'
---
 nnvm/tests/python/frontend/onnx/test_graph.py | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/nnvm/tests/python/frontend/onnx/test_graph.py b/nnvm/tests/python/frontend/onnx/test_graph.py
index 52eff24fa233..b12496a641bf 100644
--- a/nnvm/tests/python/frontend/onnx/test_graph.py
+++ b/nnvm/tests/python/frontend/onnx/test_graph.py
@@ -5,8 +5,13 @@
 from model_zoo import super_resolution
 
 def compare_graph(onnx_file, nnvm_sym, ishape):
-    onnx_graph = onnx.load(onnx_file)
-    onnx_sym, params = nnvm.frontend.from_onnx(onnx_graph)
+    onnx_vars = [int(n) for n in onnx.__version__.split('.')] if hasattr(onnx, "__version__") else []
+    if len(onnx_vars) >= 2 and (onnx_vars[0] > 0 or onnx_vars[1] >= 2):  # version >= 0.2
+        onnx_model = onnx.load(onnx_file)
+        onnx_sym, params = nnvm.frontend.from_onnx(onnx_model.graph)
+    else:
+        onnx_graph = onnx.load(onnx_file)
+        onnx_sym, params = nnvm.frontend.from_onnx(onnx_graph)
     g1 = nnvm.graph.create(onnx_sym)
     g2 = nnvm.graph.create(nnvm_sym)
     ishapes = {'input_0': ishape}

From b54e49872093ca3b4f6acd411a1b96ddb1b8af46 Mon Sep 17 00:00:00 2001
From: Takeo Imai <imai@leapmind.io>
Date: Mon, 23 Oct 2017 03:07:47 +0900
Subject: [PATCH 542/949] Change as graph.input can be either ValueInfoProto or
 string (#186) (#192)

* graph.input can be either ValueInfoProto or string

* pylint
---
 nnvm/python/nnvm/frontend/onnx.py | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/onnx.py b/nnvm/python/nnvm/frontend/onnx.py
index fcd15db1fe30..da6340607ea3 100644
--- a/nnvm/python/nnvm/frontend/onnx.py
+++ b/nnvm/python/nnvm/frontend/onnx.py
@@ -245,18 +245,24 @@ def from_onnx(self, graph):
                 raise ValueError("Tensor's name is required.")
             self._params[init_tensor.name] = self._parse_array(init_tensor)
         for i in graph.input:
-            if i in self._params:
+            # from onnx v0.2, GraphProto.input has type ValueInfoProto,
+            #  and the name is 'i.name'
+            try:
+                i_name = i.name
+            except AttributeError:
+                i_name = i
+            if i_name in self._params:
                 # i is a param instead of input
                 name_param = 'param_{}'.format(self._num_param)
                 self._num_param += 1
-                self._params[name_param] = self._params.pop(i)
+                self._params[name_param] = self._params.pop(i_name)
                 self._nodes[name_param] = _sym.Variable(name=name_param)
-                self._renames[i] = name_param
+                self._renames[i_name] = name_param
             else:
                 name_input = 'input_{}'.format(self._num_input)
                 self._num_input += 1
                 self._nodes[name_input] = _sym.Variable(name=name_input)
-                self._renames[i] = name_input
+                self._renames[i_name] = name_input
         # construct nodes, nodes are stored as directed acyclic graph
         for idx, node in enumerate(graph.node):
             op_name = node.op_type

From f0eb123fe09c59565dc3e846c2d97fa8081af0ae Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 24 Oct 2017 13:18:07 -0700
Subject: [PATCH 543/949] [TVM] Update tvm and benchmark script (#196)

---
 ...magenet_bench.py => gpu_imagenet_bench.py} | 26 +++++++++++--------
 1 file changed, 15 insertions(+), 11 deletions(-)
 rename nnvm/examples/benchmark/{cuda_imagenet_bench.py => gpu_imagenet_bench.py} (71%)

diff --git a/nnvm/examples/benchmark/cuda_imagenet_bench.py b/nnvm/examples/benchmark/gpu_imagenet_bench.py
similarity index 71%
rename from nnvm/examples/benchmark/cuda_imagenet_bench.py
rename to nnvm/examples/benchmark/gpu_imagenet_bench.py
index 7f5652e0f111..cc1b14c42ea8 100644
--- a/nnvm/examples/benchmark/cuda_imagenet_bench.py
+++ b/nnvm/examples/benchmark/gpu_imagenet_bench.py
@@ -15,15 +15,19 @@ def tvm_callback_cuda_compile(code):
 
 def main():
     parser = argparse.ArgumentParser()
-    parser.add_argument('--model', type=str, required=True, choices=['resnet', 'mobilenet'],
-        help="The model type.")
+    parser.add_argument('--model', type=str, required=True,
+                        choices=['resnet', 'mobilenet'],
+                        help="The model type.")
+    parser.add_argument('--target', type=str, required=True,
+                        choices=['cuda', 'rocm', 'opencl', 'metal'],
+                        help="Compilation target.")
     parser.add_argument('--opt-level', type=int, default=1, help="Level of optimization.")
     parser.add_argument('--num-iter', type=int, default=1000, help="Number of iteration during benchmark.")
+    parser.add_argument('--repeat', type=int, default=1, help="Number of repeative times.")
     args = parser.parse_args()
     opt_level = args.opt_level
     num_iter = args.num_iter
-    target = "cuda"
-    ctx = tvm.gpu(0)
+    ctx = tvm.context(args.target, 0)
     batch_size = 1
     num_classes = 1000
     image_shape = (3, 224, 224)
@@ -40,11 +44,10 @@ def main():
         raise ValueError('no benchmark prepared for {}.'.format(args.model))
 
     with nnvm.compiler.build_config(opt_level=opt_level):
-        with tvm.build_config(auto_unroll_max_step=32,
-                              auto_unroll_min_depth=0,
-                              unroll_explicit=False):
+        with tvm.build_config(auto_unroll_max_step=128,
+                              unroll_explicit=(args.target != "cuda")):
             graph, lib, params = nnvm.compiler.build(
-                net, target, shape={"data": data_shape}, params=params)
+                net, args.target, shape={"data": data_shape}, params=params)
 
     data = np.random.uniform(-1, 1, size=data_shape).astype("float32")
     module = runtime.create(graph, lib, ctx)
@@ -56,11 +59,12 @@ def main():
 
     print('benchmark args: {}'.format(args))
     ftimer = module.module.time_evaluator("run", ctx, num_iter)
-    for i in range(3):
+    for i in range(args.repeat):
         prof_res = ftimer()
         print(prof_res)
-        # sleep for avoiding cpu overheat
-        time.sleep(45)
+        # sleep for avoiding device overheat
+        if i + 1 != args.repeat:
+            time.sleep(45)
 
 if __name__ == '__main__':
     main()

From c438b4a4f67a61c491bd816100b01f6f320b4475 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 24 Oct 2017 13:26:02 -0700
Subject: [PATCH 544/949] More comment change on bench script (#197)

---
 nnvm/examples/benchmark/gpu_imagenet_bench.py | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/nnvm/examples/benchmark/gpu_imagenet_bench.py b/nnvm/examples/benchmark/gpu_imagenet_bench.py
index cc1b14c42ea8..b1f940d14143 100644
--- a/nnvm/examples/benchmark/gpu_imagenet_bench.py
+++ b/nnvm/examples/benchmark/gpu_imagenet_bench.py
@@ -1,4 +1,10 @@
-""" Benchmark script for performance on GPUs. For example, run the file with: `python cuda_imagenet_bench.py --model='mobilenet'`. For more details about how to set up the inference environment on GPUs, please refer to NNVM Tutorial: ImageNet Inference on the GPU """
+""" Benchmark script for performance on GPUs.
+
+For example, run the file with:
+`python gpu_imagenet_bench.py --model=mobilenet --target=cuda`.
+For more details about how to set up the inference environment on GPUs,
+please refer to NNVM Tutorial: ImageNet Inference on the GPU
+"""
 import time
 import argparse
 import numpy as np

From 0e8a015e723cb498dcd9cefbe9183a98731bd281 Mon Sep 17 00:00:00 2001
From: "Joshua Z. Zhang" <cheungchih@gmail.com>
Date: Wed, 25 Oct 2017 22:45:09 -0700
Subject: [PATCH 545/949] fix version comparison (#200)

---
 nnvm/python/nnvm/frontend/mxnet.py | 23 ++++++++++-------------
 1 file changed, 10 insertions(+), 13 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/mxnet.py b/nnvm/python/nnvm/frontend/mxnet.py
index 6cbd497fd470..39a118d24d0f 100644
--- a/nnvm/python/nnvm/frontend/mxnet.py
+++ b/nnvm/python/nnvm/frontend/mxnet.py
@@ -13,14 +13,6 @@ def _get_nnvm_op(op_name):
         raise RuntimeError("Unable to map op_name {} to nnvm.sym".format(op_name))
     return op
 
-def _get_mxnet_version():
-    try:
-        import mxnet as mx
-        version = mx.__version__
-    except ImportError:
-        version = '0.11.1'
-    return [int(x) for x in version.split('.')]
-
 def _required_attr(attr, key):
     assert isinstance(attr, dict)
     if key not in attr:
@@ -127,14 +119,19 @@ def _conv2d_transpose(inputs, attrs):
     return _get_nnvm_op(op_name)(*inputs, **new_attrs)
 
 def _dense(inputs, attrs):
+    import mxnet as mx
     op_name, new_attrs = 'dense', {}
     new_attrs['units'] = _required_attr(attrs, 'num_hidden')
     new_attrs['use_bias'] = not _parse_bool_str(attrs, 'no_bias')
-    major, minor, micro = _get_mxnet_version()
-    if major >= 0 and minor >= 11 and micro >= 1:
-        use_flatten = _parse_bool_str(attrs, 'flatten', 'True')
-        if use_flatten:
-            inputs[0] = _sym.flatten(inputs[0])
+    try:
+        _ = mx.sym.FullyConnected(mx.sym.var('x'), num_hidden=1, flatten=True)
+        has_flatten = True
+    except mx.base.MXNetError:
+        # no flatten attribute in old mxnet
+        has_flatten = False
+    use_flatten = _parse_bool_str(attrs, 'flatten', 'True')
+    if has_flatten and use_flatten:
+        inputs[0] = _sym.flatten(inputs[0])
     return _get_nnvm_op(op_name)(*inputs, **new_attrs)
 
 def _dropout(inputs, attrs):

From d2fd18f260f537a6176ff73381209704974e137c Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Thu, 26 Oct 2017 20:18:07 -0700
Subject: [PATCH 546/949] [TOP] Support ceil_mode

---
 nnvm/python/nnvm/top/nn.py                    | 8 ++++----
 nnvm/tests/python/compiler/test_top_level2.py | 3 ++-
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index e54da0511cd6..5f3485b25363 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -136,8 +136,8 @@ def compute_max_pool2d(attrs, inputs, _):
     layout = attrs["layout"]
     ceil_mode = attrs.get_bool("ceil_mode")
     assert layout == "NCHW", "only support nchw for now"
-    assert not ceil_mode, "not support ceil_mode now"
-    return topi.nn.pool(inputs[0], pool_size, strides, padding, pool_type='max')
+    return topi.nn.pool(inputs[0], pool_size, strides, padding,
+                        pool_type='max', ceil_mode=ceil_mode)
 
 @reg.register_schedule("max_pool2d")
 def schedule_max_pool2d(_, outs, target):
@@ -158,8 +158,8 @@ def compute_avg_pool2d(attrs, inputs, _):
     layout = attrs["layout"]
     ceil_mode = attrs.get_bool("ceil_mode")
     assert layout == "NCHW", "only support nchw for now"
-    assert not ceil_mode, "not support ceil_mode now"
-    return topi.nn.pool(inputs[0], pool_size, strides, padding, pool_type='avg')
+    return topi.nn.pool(inputs[0], pool_size, strides, padding,
+                        pool_type='avg', ceil_mode=ceil_mode)
 
 @reg.register_schedule("avg_pool2d")
 def schedule_avg_pool2d(_, outs, target):
diff --git a/nnvm/tests/python/compiler/test_top_level2.py b/nnvm/tests/python/compiler/test_top_level2.py
index 312094cd31d4..0bfaef2c9f2d 100644
--- a/nnvm/tests/python/compiler/test_top_level2.py
+++ b/nnvm/tests/python/compiler/test_top_level2.py
@@ -56,7 +56,8 @@ def test_grouped_conv2d():
 
 def test_max_pool2d():
     x = sym.Variable("x")
-    y = sym.max_pool2d(x, pool_size=(2,2), strides=(2,2), padding=(0,0), name="y")
+    y = sym.max_pool2d(x, pool_size=(2,2), strides=(2,2),
+                       padding=(0,0), name="y", ceil_mode=True)
     dtype = "float32"
     dshape = (1, 3, 28, 28)
     oshape = (1, 3, 14, 14)

From cca07be37d22b360f0b342053ede18ffee83f614 Mon Sep 17 00:00:00 2001
From: "Joshua Z. Zhang" <cheungchih@gmail.com>
Date: Sat, 28 Oct 2017 00:37:36 -0700
Subject: [PATCH 547/949] [Frontend] ONNX frontend v0.2 support (#202)

* update

* fix

* use generated onnx model

* fix tests

* fix lint

* remove log filter

* add vgg

* fix tests

* update tests

* fix download

* fix ci

* fix tutorial url

* clean cache
---
 nnvm/python/nnvm/frontend/common.py           |  25 +-
 nnvm/python/nnvm/frontend/onnx.py             | 265 +++++++++---------
 .../ci_build/install/ubuntu_install_onnx.sh   |   4 +-
 .../frontend/onnx/model_zoo/__init__.py       |  30 +-
 .../onnx/model_zoo/super_resolution.onnx      | Bin 239554 -> 0 bytes
 .../onnx/model_zoo/super_resolution.py        |  24 +-
 .../python/frontend/onnx/test_forward.py      |  24 +-
 nnvm/tests/python/frontend/onnx/test_graph.py |  13 +-
 nnvm/tutorials/from_coreml.py                 |  15 +-
 nnvm/tutorials/from_onnx.py                   |  21 +-
 10 files changed, 231 insertions(+), 190 deletions(-)
 delete mode 100644 nnvm/tests/python/frontend/onnx/model_zoo/super_resolution.onnx

diff --git a/nnvm/python/nnvm/frontend/common.py b/nnvm/python/nnvm/frontend/common.py
index 663de6a4059d..72e9bffa6456 100644
--- a/nnvm/python/nnvm/frontend/common.py
+++ b/nnvm/python/nnvm/frontend/common.py
@@ -1,8 +1,15 @@
 """Shared functions and classes for frontends."""
 from __future__ import absolute_import as _abs
-import warnings
+import logging
+from nnvm import sym as _sym
 from .._base import string_types
 
+def get_nnvm_op(op_name):
+    op = getattr(_sym, op_name)
+    if not op:
+        raise RuntimeError("Unable to map op_name {} to nnvm.sym".format(op_name))
+    return op
+
 class Renamer(object):
     """A simply renamer for operators.
 
@@ -14,8 +21,8 @@ class Renamer(object):
     def __init__(self, new_name):
         self._new_name = new_name
 
-    def __call__(self, attrs):
-        return self._new_name, attrs
+    def __call__(self, inputs, attrs, *args):
+        return get_nnvm_op(self._new_name)(*inputs, **attrs)
 
 
 class AttrConverter(object):
@@ -40,9 +47,9 @@ class AttrConverter(object):
         A list of excluded attributes that should `NOT` appear.
         Raise NotImplementedError if occured.
     disables : list
-        A list of attributes that is disabled in nnvm. Raise warnings.
+        A list of attributes that is disabled in nnvm. Log warnings.
     ignores : list
-        A list of attributes that is ignored in nnvm. Silent.
+        A list of attributes that is ignored in nnvm. Debug level logging.
     extras : dict
         A series of additional attributes should be added anyway to the returned
         attribute dict.
@@ -61,7 +68,7 @@ def __init__(self, op_name, transforms=None,
         self._extras = extras if extras else {}
         self._custom_check = custom_check
 
-    def __call__(self, attrs):
+    def __call__(self, inputs, attrs, *args):
         # apply custom check
         if self._custom_check:
             func, msg = self._custom_check
@@ -79,9 +86,9 @@ def __call__(self, attrs):
             if k in self._excludes:
                 raise NotImplementedError("Attribute {} not supported yet.".format(k))
             elif k in self._disables:
-                warnings.warn("Attribute {} is disabled in nnvm.sym.{}".format(k, op_name))
+                logging.warning("Attribute %s is disabled in nnvm.sym.%s", k, op_name)
             elif k in self._ignores:
-                pass
+                logging.debug("Attribute %s is ignored in nnvm.sym.%s", k, op_name)
             elif k in self._transforms:
                 new_name, defaults, transform = self._parse_default(self._transforms[k])
                 if defaults is None:
@@ -97,7 +104,7 @@ def __call__(self, attrs):
                 new_attrs[k] = attrs[k]
         # add extras
         new_attrs.update(self._extras)
-        return op_name, new_attrs
+        return get_nnvm_op(op_name)(*inputs, **new_attrs)
 
     def _parse_default(self, target):
         """Helper function to parse default values."""
diff --git a/nnvm/python/nnvm/frontend/onnx.py b/nnvm/python/nnvm/frontend/onnx.py
index da6340607ea3..c7d26393cbbf 100644
--- a/nnvm/python/nnvm/frontend/onnx.py
+++ b/nnvm/python/nnvm/frontend/onnx.py
@@ -1,11 +1,11 @@
-# pylint: disable=import-self, invalid-name
+# pylint: disable=import-self, invalid-name, unused-argument
 """ONNX: Open Neural Network Exchange frontend."""
 from __future__ import absolute_import as _abs
 import tvm
 from .. import symbol as _sym
 from .. import graph as _graph
 from .. compiler import graph_util
-from .common import Renamer, AttrConverter as AttrCvt
+from .common import get_nnvm_op, Renamer, AttrConverter as AttrCvt
 
 __all__ = ['from_onnx']
 
@@ -49,11 +49,26 @@ def _dim_check(attrs):
         return False
     return _dim_check, "Only 2d kernel supported."
 
+def _infer_channels(inputs, params, transpose=False):
+    """A hack for getting 'channles' or 'units' since onnx don't provide
+    these attributes. We check the shape of weights provided to get the number.
+    """
+    g = _graph.create(inputs)
+    shape_dict = {k: v.shape for k, v in params.items()}
+    _, out_shapes = graph_util.infer_shape(g, **shape_dict)
+    channels = out_shapes[0][0] if not transpose else out_shapes[0][1]
+    return channels
+
 def _elemwise(name):
-    return AttrCvt(
-        op_name=_math_name_picker(name),
-        disables=['axis'],
-        ignores=['broadcast'])
+    def _impl(inputs, attr, *args):
+        assert len(inputs) == 2, "Math op take 2 inputs, {} given".format(len(inputs))
+        op_name = _math_name_picker(name)(attr)
+        axis = int(attr.get('axis', 0))
+        if op_name == 'broadcast_add' and inputs[0].attr('op_name') == 'conv2d':
+            # TODO(zhreshold): remove hard coded infershape
+            inputs[1] = _sym.expand_dims(inputs[1], axis=axis, num_newaxis=2)
+        return get_nnvm_op(op_name)(*inputs)
+    return _impl
 
 def _pooling(name):
     return AttrCvt(
@@ -68,24 +83,44 @@ def _pooling(name):
         custom_check=_dimension_constraint())
 
 def _conv():
-    return AttrCvt(
-        op_name=_dimension_picker('conv'),
-        transforms={
-            'kernel_shape': 'kernel_size',
-            'dilations': ('dilation', (0, 0)),
-            'pads': ('padding', (0, 0), _revert_caffe2_pad),
-            'group': ('groups', 1)},
-        custom_check=_dimension_constraint())
+    def _impl(inputs, attr, params):
+        # get number of channels
+        channels = _infer_channels(inputs[1], params)
+        attr['channels'] = channels
+        return AttrCvt(
+            op_name=_dimension_picker('conv'),
+            transforms={
+                'kernel_shape': 'kernel_size',
+                'dilations': ('dilation', (0, 0)),
+                'pads': ('padding', (0, 0), _revert_caffe2_pad),
+                'group': ('groups', 1)},
+            extras={'use_bias': False},
+            custom_check=_dimension_constraint())(inputs, attr)
+    return _impl
 
 def _conv_transpose():
-    return AttrCvt(
-        op_name=_dimension_picker('conv', '_transpose'),
-        transforms={
-            'kernel_shape': 'kernel_size',
-            'dilations': ('dilation', (0, 0)),
-            'pads': ('padding', (0, 0), _revert_caffe2_pad)},
-        disables=['output_shape'],
-        custom_check=_dimension_constraint())
+    def _impl(inputs, attr, params):
+        # get number of channels
+        channels = _infer_channels(inputs[1], params)
+        attr['channels'] = channels
+        return AttrCvt(
+            op_name=_dimension_picker('conv', '_transpose'),
+            transforms={
+                'kernel_shape': 'kernel_size',
+                'dilations': ('dilation', (0, 0)),
+                'pads': ('padding', (0, 0), _revert_caffe2_pad)},
+            disables=['output_shape'],
+            extras={'use_bias': False},
+            custom_check=_dimension_constraint())(inputs, attr)
+    return _impl
+
+def _fully_connected():
+    def _impl(inputs, attr, params):
+        # get number of channels
+        channels = _infer_channels(inputs[1], params)
+        attr['units'] = channels
+        return AttrCvt('dense', ignores=['axis', 'axis_w'])(inputs, attr)
+    return _impl
 
 def _batch_norm():
     # TODO(zhreshold): 'spatial' is not properly handled here.
@@ -95,10 +130,32 @@ def _batch_norm():
         ignores=['spatial', 'is_test', 'consumed_inputs'])
 
 
+def _gemm():
+    def _impl(inputs, attr, params):
+        assert len(inputs) == 3, "Gemm op take 3 inputs, {} given".format(len(inputs))
+        # Y = alpha * A * B + beta * C
+        alpha = float(attr.get('alpha', 1.0))
+        beta = float(attr.get('beta', 1.0))
+        transA = int(attr.get('transA', 0))
+        transB = int(attr.get('transB', 0))
+        # get number of channels
+        channels = _infer_channels(inputs[1], params, not transB)
+        if transA:
+            inputs[0] = _sym.transpose(inputs[0], axes=(1, 0))
+        if not transB:
+            inputs[1] = _sym.transpose(inputs[1], axes=(1, 0))
+        return _sym.dense(alpha * inputs[0], inputs[1], beta * inputs[2], units=channels)
+    return _impl
+
+
 # compatible operators that do NOT require any conversion.
 _identity_list = []
 
 # _convert_map defines maps of name to converter functor(callable)
+# for 1 to 1 mapping, use Renamer if nothing but name is different
+# use AttrCvt if attributes need to be converted
+# for 1 to N mapping(composed), use custom callable functions
+# for N to 1 mapping, currently not supported(?)
 _convert_map = {
     # defs/experimental
     'FC'            : AttrCvt('dense', ignores=['axis', 'axis_w']),
@@ -140,6 +197,7 @@ def _batch_norm():
     # 'Sum' : elemwise sum
     # softmax default axis is different in onnx
     'Softmax'       : AttrCvt('softmax', {'axis': ('axis', 1)}),
+    'Gemm'          : _gemm(),
 
     # defs/nn
     'AveragePool'   : _pooling('avg_pool'),
@@ -151,6 +209,7 @@ def _batch_norm():
     'BatchNormalization': _batch_norm(),
     'Dropout'       : AttrCvt('dropout', {'ratio': 'rate'}, ignores=['is_test']),
     'Flatten'       : Renamer('flatten'),
+    # 'LRN'
 
     # defs/reduction
     'ReduceMax'     : AttrCvt('max', {'axes', 'axis'}),
@@ -173,42 +232,6 @@ def _batch_norm():
     # 'Squeeze'
 }
 
-def _convert_operator(op_name, attrs, identity_list=None, convert_map=None):
-    """Convert from onnx operator to nnvm operator.
-    The converter must specify conversions explicity for incompatible name, and
-    apply handlers to operator attributes.
-
-    Parameters
-    ----------
-    op_name : str
-        Operator name, such as Convolution, FullyConnected
-    attrs : dict
-        Dict of operator attributes
-    identity_list : list
-        List of operators that don't require conversion
-    convert_map : dict
-        Dict of name : callable, where name is the op's name that
-        require conversion to nnvm, callable are functions which
-        take attrs and return (new_op_name, new_attrs)
-
-    Returns
-    -------
-    (op_name, attrs)
-        Converted (op_name, attrs) for nnvm.
-    """
-    identity_list = identity_list if identity_list else _identity_list
-    convert_map = convert_map if convert_map else _convert_map
-    if op_name in identity_list:
-        pass
-    elif op_name in convert_map:
-        op_name, attrs = convert_map[op_name](attrs)
-    else:
-        raise NotImplementedError("Operator {} not implemented.".format(op_name))
-    op = getattr(_sym, op_name, None)
-    if not op:
-        raise RuntimeError("Unable to map op_name {} to nnvm.sym".format(op_name))
-    return op, attrs
-
 
 class GraphProto(object):
     """A helper class for handling nnvm graph copying from pb2.GraphProto.
@@ -247,16 +270,14 @@ def from_onnx(self, graph):
         for i in graph.input:
             # from onnx v0.2, GraphProto.input has type ValueInfoProto,
             #  and the name is 'i.name'
-            try:
-                i_name = i.name
-            except AttributeError:
-                i_name = i
+            i_name = self._parse_value_proto(i)
             if i_name in self._params:
                 # i is a param instead of input
                 name_param = 'param_{}'.format(self._num_param)
                 self._num_param += 1
                 self._params[name_param] = self._params.pop(i_name)
-                self._nodes[name_param] = _sym.Variable(name=name_param)
+                self._nodes[name_param] = _sym.Variable(
+                    name=name_param, shape=self._params[name_param].shape)
                 self._renames[i_name] = name_param
             else:
                 name_input = 'input_{}'.format(self._num_input)
@@ -264,18 +285,11 @@ def from_onnx(self, graph):
                 self._nodes[name_input] = _sym.Variable(name=name_input)
                 self._renames[i_name] = name_input
         # construct nodes, nodes are stored as directed acyclic graph
-        for idx, node in enumerate(graph.node):
+        for node in graph.node:
             op_name = node.op_type
-            node_name = node.name.strip()
-            node_name = node_name if node_name else None
             attr = self._parse_attr(node.attribute)
-            new_op, new_attr = _convert_operator(op_name, attr)
             inputs = [self._nodes[self._renames.get(i, i)] for i in node.input]
-            # some hacks for onnx problem
-            new_attr = self._fix_bias(new_op, new_attr, len(inputs))
-            new_attr = self._fix_channels(new_op, new_attr, list(node.input))
-            self._fix_bias_shape(node.op_type, graph.node[idx-1].op_type, node.input)
-            op = new_op(name=node_name, *inputs, **new_attr)
+            op = self._convert_operator(op_name, inputs, attr)
             node_output = self._fix_outputs(op_name, node.output)
             assert len(node_output) == len(op.list_output_names()), (
                 "Number of output mismatch {} vs {} in {}.".format(
@@ -283,13 +297,21 @@ def from_onnx(self, graph):
             for k, i in zip(list(node_output), range(len(node_output))):
                 self._nodes[k] = op[i]
         # now return the outputs
-        out = [self._nodes[i] for i in graph.output]
+        out = [self._nodes[self._parse_value_proto(i)] for i in graph.output]
         if len(out) > 1:
             out = _sym.Group(out)
         else:
             out = out[0]
         return out, self._params
 
+    def _parse_value_proto(self, value_proto):
+        """Parse ValueProto or raw str."""
+        try:
+            name = value_proto.name
+        except AttributeError:
+            name = value_proto
+        return name
+
     def _parse_array(self, tensor_proto):
         """Grab data in TensorProto and convert to numpy array."""
         try:
@@ -320,64 +342,52 @@ def _parse_attr(self, attr_proto):
                 raise ValueError("Cannot parse attribute: \n{}\n.".format(a))
         return attrs
 
-    def _fix_outputs(self, op, outputs):
+    def _convert_operator(self, op_name, inputs, attrs, identity_list=None, convert_map=None):
+        """Convert from onnx operator to nnvm operator.
+        The converter must specify conversions explicity for incompatible name, and
+        apply handlers to operator attributes.
+
+        Parameters
+        ----------
+        op_name : str
+            Operator name, such as Convolution, FullyConnected
+        inputs : list of nnvm.Symbol
+            List of input symbols.
+        attrs : dict
+            Dict of operator attributes
+        identity_list : list
+            List of operators that don't require conversion
+        convert_map : dict
+            Dict of name : callable, where name is the op's name that
+            require conversion to nnvm, callable are functions which
+            take attrs and return (new_op_name, new_attrs)
+
+        Returns
+        -------
+        sym : nnvm.Symbol
+            Converted nnvm Symbol
+        """
+        identity_list = identity_list if identity_list else _identity_list
+        convert_map = convert_map if convert_map else _convert_map
+        if op_name in identity_list:
+            sym = get_nnvm_op(op_name)(*inputs, **attrs)
+        elif op_name in convert_map:
+            sym = convert_map[op_name](inputs, attrs, self._params)
+        else:
+            raise NotImplementedError("Operator {} not implemented.".format(op_name))
+        return sym
+
+    def _fix_outputs(self, op_name, outputs):
         """A hack to handle dropout or similar operator that have more than one out
         in ONNX.
         """
-        if op == 'Dropout':
-            assert len(outputs) == 2, "ONNX have two outputs for dropout layer."
+        if op_name == 'Dropout':
+            if len(outputs) == 1:
+                return outputs
+            # TODO(zhreshold): support dropout mask?
             outputs = outputs[:-1]
         return outputs
 
-    def _fix_bias(self, op, attrs, num_inputs):
-        """A hack for 'use_bias' attribute since onnx don't provide this attribute,
-        we have to check the number of inputs to decide it."""
-        if op not in [_sym.conv2d, _sym.conv2d_transpose, _sym.dense]:
-            return attrs
-        if num_inputs == 3:
-            attrs['use_bias'] = True
-        elif num_inputs == 2:
-            attrs['use_bias'] = False
-        else:
-            raise ValueError("Unexpected number of inputs for: {}".format(op))
-        return attrs
-
-    def _fix_bias_shape(self, op_name, last_op_name, inputs):
-        """A hack to reshape bias term to (1, num_channel)."""
-        if op_name == 'Add' and last_op_name == 'Conv':
-            assert len(list(inputs)) == 2
-            bias_name = self._renames.get(inputs[1], inputs[1])
-            bias = self._params[bias_name]
-            assert len(bias.shape) == 1
-            # reshape to (1, n)
-            bias = tvm.nd.array(bias.asnumpy().reshape((1, -1, 1, 1)))
-            self._params[bias_name] = bias
-
-    def _fix_channels(self, op, attrs, inputs):
-        """A hack for getting 'channles' or 'units' since onnx don't provide
-        these attributes. We check the shape of weights provided to get the number.
-        """
-        if op not in [_sym.conv2d, _sym.conv2d_transpose, _sym.dense]:
-            return attrs
-        if inputs[1] not in self._renames:
-            assert inputs[1] in self._nodes
-            g = _graph.create(self._nodes[inputs[1]])
-            shape_dict = {k: v.shape for k, v in self._params.items()}
-            _, out_shapes = graph_util.infer_shape(g, **shape_dict)
-            channels = out_shapes[0][0]
-        else:
-            weight_name = self._renames[inputs[1]]
-            if not weight_name in self._params:
-                raise ValueError("Unable to get channels/units attr from onnx graph.")
-            else:
-                wshape = self._params[weight_name].shape
-                assert len(wshape) >= 2, "Weights shape is invalid: {}".format(wshape)
-                channels = wshape[0]
-        if op in [_sym.dense]:
-            attrs['units'] = channels
-        else:
-            attrs['channels'] = channels
-        return attrs
 
 def from_onnx(graph):
     """Load onnx graph which is a python protobuf object in to nnvm graph.
@@ -389,7 +399,7 @@ def from_onnx(graph):
     Parameters
     ----------
     graph : protobuf object
-        ONNX graph
+        ONNX GraphProto, or ONNX ModelProto after ONNX v0.2
 
     Returns
     -------
@@ -400,5 +410,8 @@ def from_onnx(graph):
         Dict of converted parameters stored in tvm.ndarray format
     """
     g = GraphProto()
+    if hasattr(graph, 'graph'):
+        # it's a ModelProto wrapper
+        graph = graph.graph
     sym, params = g.from_onnx(graph)
     return sym, params
diff --git a/nnvm/tests/ci_build/install/ubuntu_install_onnx.sh b/nnvm/tests/ci_build/install/ubuntu_install_onnx.sh
index ec4cae1be2d0..37bd4e66edde 100644
--- a/nnvm/tests/ci_build/install/ubuntu_install_onnx.sh
+++ b/nnvm/tests/ci_build/install/ubuntu_install_onnx.sh
@@ -1,5 +1,5 @@
-pip2 install onnx
-pip3 install onnx
+pip2 install onnx>=0.2.0
+pip3 install onnx>=0.2.0
 
 pip2 install http://download.pytorch.org/whl/cu75/torch-0.2.0.post3-cp27-cp27mu-manylinux1_x86_64.whl
 pip2 install torchvision
diff --git a/nnvm/tests/python/frontend/onnx/model_zoo/__init__.py b/nnvm/tests/python/frontend/onnx/model_zoo/__init__.py
index 811dd3b7e69f..ace19a32ae45 100644
--- a/nnvm/tests/python/frontend/onnx/model_zoo/__init__.py
+++ b/nnvm/tests/python/frontend/onnx/model_zoo/__init__.py
@@ -1,13 +1,37 @@
 """Store for onnx examples and common models."""
 from __future__ import absolute_import as _abs
 import os
+import logging
 from .super_resolution import get_super_resolution
 
-__all__ = ['super_resolution']
+def _download(url, filename, overwrite=False):
+    if os.path.isfile(filename) and not overwrite:
+        logging.debug('File %s existed, skip.', filename)
+        return
+    logging.debug('Downloading from url %s to %s', url, filename)
+    try:
+        import urllib.request
+        urllib.request.urlretrieve(url, filename)
+    except:
+        import urllib
+        urllib.urlretrieve(url, filename)
 
 def _as_abs_path(fname):
     cur_dir = os.path.abspath(os.path.dirname(__file__))
     return os.path.join(cur_dir, fname)
 
-# a pair of onnx pb file and corresponding nnvm symbol
-super_resolution = (_as_abs_path('super_resolution.onnx'), get_super_resolution())
+
+URLS = {
+    'super_resolution.onnx': 'https://gist.github.com/zhreshold/bcda4716699ac97ea44f791c24310193/raw/93672b029103648953c4e5ad3ac3aadf346a4cdc/super_resolution_0.2.onnx',
+    'squeezenet1_1.onnx': 'https://gist.github.com/zhreshold/bcda4716699ac97ea44f791c24310193/raw/93672b029103648953c4e5ad3ac3aadf346a4cdc/squeezenet1_1_0.2.onnx',
+    'lenet.onnx': 'https://gist.github.com/zhreshold/bcda4716699ac97ea44f791c24310193/raw/93672b029103648953c4e5ad3ac3aadf346a4cdc/lenet_0.2.onnx'}
+
+# download and add paths
+for k, v  in URLS.items():
+    name = k.split('.')[0]
+    path = _as_abs_path(k)
+    _download(v, path, False)
+    locals()[name] = path
+
+# symbol for graph comparison
+super_resolution_sym = get_super_resolution()
diff --git a/nnvm/tests/python/frontend/onnx/model_zoo/super_resolution.onnx b/nnvm/tests/python/frontend/onnx/model_zoo/super_resolution.onnx
deleted file mode 100644
index 65e138b0585b399bf902889093b4cddedbadad71..0000000000000000000000000000000000000000
GIT binary patch
diff --git a/nnvm/tests/python/frontend/onnx/model_zoo/super_resolution.py b/nnvm/tests/python/frontend/onnx/model_zoo/super_resolution.py
index 5aa2219fd11c..19662322a66b 100644
--- a/nnvm/tests/python/frontend/onnx/model_zoo/super_resolution.py
+++ b/nnvm/tests/python/frontend/onnx/model_zoo/super_resolution.py
@@ -1,34 +1,18 @@
 """NNVM symbol corresponding to super_resolution.onnx example."""
 from nnvm import sym
 
-def get_super_resolution_deprecated():
-    factor = 3
-    size = 224
-    data = sym.Variable(name='9')
-    conv1 = sym.conv2d(data, channels=64, kernel_size=(5, 5), padding=(2, 2))
-    relu1 = sym.relu(conv1)
-    conv2 = sym.conv2d(relu1, channels=64, kernel_size=(3, 3), padding=(1, 1))
-    relu2 = sym.relu(conv2)
-    conv3 = sym.conv2d(relu2, channels=32, kernel_size=(3, 3), padding=(1, 1))
-    relu3 = sym.relu(conv3)
-    conv4 = sym.conv2d(relu3, channels=factor**2, kernel_size=(3, 3), padding=(1, 1))
-    r1 = sym.reshape(conv4, shape=(0, 1, factor, factor, size, size))
-    t1 = sym.transpose(r1, axes=(0, 1, 4, 2, 5, 3))
-    r2 = sym.reshape(t1, shape=(0, 1, size * factor, size * factor))
-    return r2
-
 def get_super_resolution():
     factor = 3
     size = 224
     data = sym.Variable(name='9')
     conv1 = sym.conv2d(data, channels=64, kernel_size=(5, 5), padding=(2, 2), use_bias=False)
-    relu1 = sym.relu(conv1 + sym.Variable(name='2'))
+    relu1 = sym.relu(conv1 + sym.expand_dims(sym.Variable(name='2', shape=(64)), axis=1, num_newaxis=2))
     conv2 = sym.conv2d(relu1, channels=64, kernel_size=(3, 3), padding=(1, 1), use_bias=False)
-    relu2 = sym.relu(conv2 + sym.Variable(name='4'))
+    relu2 = sym.relu(conv2 + sym.expand_dims(sym.Variable(name='4', shape=(64)), axis=1, num_newaxis=2))
     conv3 = sym.conv2d(relu2, channels=32, kernel_size=(3, 3), padding=(1, 1), use_bias=False)
-    relu3 = sym.relu(conv3 + sym.Variable(name='6'))
+    relu3 = sym.relu(conv3 + sym.expand_dims(sym.Variable(name='6', shape=(32)), axis=1, num_newaxis=2))
     conv4 = sym.conv2d(relu3, channels=factor**2, kernel_size=(3, 3), padding=(1, 1), use_bias=False)
-    conv4 = conv4 + sym.Variable(name='8')
+    conv4 = conv4 + sym.expand_dims(sym.Variable(name='8', shape=(factor**2)), axis=1, num_newaxis=2)
     # TODO(zhreshold): allow shape inference for batch size > 1
     r1 = sym.reshape(conv4, shape=(1, 1, factor, factor, size, size))
     t1 = sym.transpose(r1, axes=(0, 1, 4, 2, 5, 3))
diff --git a/nnvm/tests/python/frontend/onnx/test_forward.py b/nnvm/tests/python/frontend/onnx/test_forward.py
index 155b054f47a9..5831071869c6 100644
--- a/nnvm/tests/python/frontend/onnx/test_forward.py
+++ b/nnvm/tests/python/frontend/onnx/test_forward.py
@@ -4,13 +4,13 @@
 from tvm.contrib import graph_runtime
 from nnvm.testing.config import ctx_list
 import onnx
-from model_zoo import super_resolution
+from model_zoo import super_resolution, squeezenet1_1, lenet
 
 def verify_onnx_forward_impl(graph_file, data_shape, out_shape):
     import onnx_caffe2.backend
-    def get_caffe2_output(graph, x, dtype='float32'):
-        prepared_backend = onnx_caffe2.backend.prepare(graph)
-        W = {graph.input[-1]: x.astype(dtype)}
+    def get_caffe2_output(model, x, dtype='float32'):
+        prepared_backend = onnx_caffe2.backend.prepare(model)
+        W = {model.graph.input[0].name: x.astype(dtype)}
         c2_out = prepared_backend.run(W)[0]
         return c2_out
 
@@ -29,14 +29,22 @@ def get_tvm_output(graph, x, target, ctx, dtype='float32'):
 
     dtype = 'float32'
     x = np.random.uniform(size=data_shape)
-    graph = onnx.load(graph_file)
-    c2_out = get_caffe2_output(graph, x, dtype)
+    model = onnx.load(graph_file)
+    c2_out = get_caffe2_output(model, x, dtype)
     for target, ctx in ctx_list():
-        tvm_out = get_tvm_output(graph, x, target, ctx, dtype)
+        tvm_out = get_tvm_output(model, x, target, ctx, dtype)
         np.testing.assert_allclose(c2_out, tvm_out, rtol=1e-5, atol=1e-5)
 
 def verify_super_resolution_example():
-    verify_onnx_forward_impl(super_resolution[0], (1, 1, 224, 224), (1, 1, 672, 672))
+    verify_onnx_forward_impl(super_resolution, (1, 1, 224, 224), (1, 1, 672, 672))
+
+def verify_squeezenet1_1():
+    verify_onnx_forward_impl(squeezenet1_1, (1, 3, 224, 224), (1, 1000))
+
+def verify_lenet():
+    verify_onnx_forward_impl(lenet, (1, 1, 28, 28), (1, 10))
 
 if __name__ == '__main__':
     verify_super_resolution_example()
+    verify_squeezenet1_1()
+    verify_lenet()
diff --git a/nnvm/tests/python/frontend/onnx/test_graph.py b/nnvm/tests/python/frontend/onnx/test_graph.py
index b12496a641bf..6d64f47469ca 100644
--- a/nnvm/tests/python/frontend/onnx/test_graph.py
+++ b/nnvm/tests/python/frontend/onnx/test_graph.py
@@ -2,16 +2,11 @@
 import nnvm
 import onnx
 from nnvm.compiler import graph_util, graph_attr
-from model_zoo import super_resolution
+from model_zoo import super_resolution, super_resolution_sym
 
 def compare_graph(onnx_file, nnvm_sym, ishape):
-    onnx_vars = [int(n) for n in onnx.__version__.split('.')] if hasattr(onnx, "__version__") else []
-    if len(onnx_vars) >= 2 and (onnx_vars[0] > 0 or onnx_vars[1] >= 2):  # version >= 0.2
-        onnx_model = onnx.load(onnx_file)
-        onnx_sym, params = nnvm.frontend.from_onnx(onnx_model.graph)
-    else:
-        onnx_graph = onnx.load(onnx_file)
-        onnx_sym, params = nnvm.frontend.from_onnx(onnx_graph)
+    onnx_graph = onnx.load(onnx_file)
+    onnx_sym, params = nnvm.frontend.from_onnx(onnx_graph)
     g1 = nnvm.graph.create(onnx_sym)
     g2 = nnvm.graph.create(nnvm_sym)
     ishapes = {'input_0': ishape}
@@ -22,7 +17,7 @@ def compare_graph(onnx_file, nnvm_sym, ishape):
     graph_util.check_graph_equal(g1, g2)
 
 def test_super_resolution_example():
-    fname, symbol = super_resolution
+    fname, symbol = super_resolution, super_resolution_sym
     compare_graph(fname, symbol, ishape=(1, 1, 224, 224))
 
 if __name__ == '__main__':
diff --git a/nnvm/tutorials/from_coreml.py b/nnvm/tutorials/from_coreml.py
index d2c99ddf6ed3..7e5cf7ae1fc4 100644
--- a/nnvm/tutorials/from_coreml.py
+++ b/nnvm/tutorials/from_coreml.py
@@ -21,12 +21,17 @@
 from PIL import Image
 
 def download(url, path, overwrite=False):
-    import urllib2, os
-    if os.path.exists(path) and not overwrite:
+    import os
+    if os.path.isfile(path) and not overwrite:
+        print('File {} existed, skip.'.format(path))
         return
-    print('Downloading {} to {}.'.format(url, path))
-    with open(path, 'w') as f:
-        f.write(urllib2.urlopen(url).read())
+    print('Downloading from url {} to {}'.format(url, path))
+    try:
+        import urllib.request
+        urllib.request.urlretrieve(url, path)
+    except:
+        import urllib
+        urllib.urlretrieve(url, path)
 
 ######################################################################
 # Load pretrained CoreML model
diff --git a/nnvm/tutorials/from_onnx.py b/nnvm/tutorials/from_onnx.py
index 93f21cddae1c..5a01fb60dc53 100644
--- a/nnvm/tutorials/from_onnx.py
+++ b/nnvm/tutorials/from_onnx.py
@@ -20,12 +20,17 @@
 import numpy as np
 
 def download(url, path, overwrite=False):
-    import urllib2, os
-    if os.path.exists(path) and not overwrite:
+    import os
+    if os.path.isfile(path) and not overwrite:
+        print('File {} existed, skip.'.format(path))
         return
-    print('Downloading {} to {}.'.format(url, path))
-    with open(path, 'w') as f:
-        f.write(urllib2.urlopen(url).read())
+    print('Downloading from url {} to {}'.format(url, path))
+    try:
+        import urllib.request
+        urllib.request.urlretrieve(url, path)
+    except:
+        import urllib
+        urllib.urlretrieve(url, path)
 
 ######################################################################
 # Load pretrained ONNX model
@@ -35,9 +40,9 @@ def download(url, path, overwrite=False):
 # we skip the pytorch model construction part, and download the saved onnx model
 model_url = ''.join(['https://gist.github.com/zhreshold/',
                      'bcda4716699ac97ea44f791c24310193/raw/',
-                     '41b443bf2b6cf795892d98edd28bacecd8eb0d8d/',
-                     'super_resolution.onnx'])
-download(model_url, 'super_resolution.onnx')
+                     '93672b029103648953c4e5ad3ac3aadf346a4cdc/',
+                     'super_resolution_0.2.onnx'])
+download(model_url, 'super_resolution.onnx', True)
 # now you have super_resolution.onnx on disk
 onnx_graph = onnx.load('super_resolution.onnx')
 # we can load the graph as NNVM compatible model

From 3db6242ed51330cd8ae158ca8547b2f6730d1f11 Mon Sep 17 00:00:00 2001
From: "Joshua Z. Zhang" <cheungchih@gmail.com>
Date: Mon, 30 Oct 2017 18:00:36 -0700
Subject: [PATCH 548/949] add softmaxoutput (#207)

---
 nnvm/python/nnvm/frontend/mxnet.py | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/nnvm/python/nnvm/frontend/mxnet.py b/nnvm/python/nnvm/frontend/mxnet.py
index 39a118d24d0f..4229a775efa3 100644
--- a/nnvm/python/nnvm/frontend/mxnet.py
+++ b/nnvm/python/nnvm/frontend/mxnet.py
@@ -184,6 +184,12 @@ def _split(inputs, attrs):
     new_attrs['axis'] = attrs.get('axis', 1)
     return _get_nnvm_op(op_name)(*inputs, **new_attrs)
 
+def _softmax_output(inputs, attrs):
+    op_name, new_attrs = 'softmax', {}
+    if _parse_bool_str(attrs, 'multi_output'):
+        new_attrs['axis'] = 1
+    return _get_nnvm_op(op_name)(inputs[0], **new_attrs)
+
 _identity_list = ['__add_scalar__', '__add_symbol__', '__div_scalar__',
                   '__div_symbol__', '__mul_scalar__', '__mul_symbol__',
                   '__pow_scalar__', '__rdiv_scalar__', '__rpow_scalar__',
@@ -217,6 +223,7 @@ def _split(inputs, attrs):
     'Pooling_v1'    : _pooling,
     'Reshape'       : _reshape,
     'Softmax'       : _rename('softmax'),
+    'SoftmaxOutput' : _softmax_output,
     'concat'        : _concat,
     'max_axis'      : _rename('max'),
     'min_axis'      : _rename('min'),

From d753752e7ea40761fa6391bee12b652cd221e79f Mon Sep 17 00:00:00 2001
From: Yuwei Hu <huyuwei1995@gmail.com>
Date: Fri, 3 Nov 2017 13:27:39 +0800
Subject: [PATCH 549/949] [TOP] add conv2d_transpose (#217)

* [TOP] add conv2d_transpose

* update tvm

* fix pylint
---
 nnvm/python/nnvm/top/nn.py                    | 31 +++++++++++++++++++
 nnvm/src/top/nn/convolution.cc                |  9 ++++--
 nnvm/tests/python/compiler/test_top_level2.py | 26 ++++++++++++++++
 3 files changed, 63 insertions(+), 3 deletions(-)

diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index 5f3485b25363..ceb93d213426 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -126,6 +126,37 @@ def schedule_conv2d(attrs, outs, target):
 reg.register_pattern("conv2d", OpPattern.OUT_ELEMWISE_FUSABLE)
 
 
+# conv2d_transpose
+@reg.register_compute("conv2d_transpose")
+def compute_conv2d_transpose(attrs, inputs, _):
+    """Compute definition of conv2d_transpose"""
+    padding = attrs.get_int_tuple("padding")
+    strides = attrs.get_int_tuple("strides")
+    dilation = attrs.get_int_tuple("dilation")
+    groups = attrs.get_int("groups")
+    layout = attrs["layout"]
+    assert layout == "NCHW", "only support nchw for now"
+    assert dilation == (1, 1), "not support dilate now"
+    assert groups == 1, "only support groups == 1 for now"
+    out = topi.nn.conv2d_transpose_nchw(inputs[0], inputs[1], strides, padding)
+    if attrs.get_bool("use_bias"):
+        bias = inputs[2]
+        bias = topi.expand_dims(bias, axis=1, num_newaxis=2)
+        out = topi.broadcast_add(out, bias)
+    output_padding = attrs.get_int_tuple("output_padding")
+    out = topi.nn.pad(out, \
+        [0, 0, 0, 0], [0, 0, output_padding[0], output_padding[1]])
+    return out
+
+@reg.register_schedule("conv2d_transpose")
+def schedule_conv2d_transpose(attrs, outs, target):
+    """Schedule definition of conv2d_transpose"""
+    with tvm.target.create(target):
+        return topi.generic.schedule_conv2d_transpose_nchw(outs)
+
+reg.register_pattern("conv2d_transpose", OpPattern.OUT_ELEMWISE_FUSABLE)
+
+
 # max_pool2d
 @reg.register_compute("max_pool2d")
 def compute_max_pool2d(attrs, inputs, _):
diff --git a/nnvm/src/top/nn/convolution.cc b/nnvm/src/top/nn/convolution.cc
index 77d9ad67f711..166caabc9e6a 100644
--- a/nnvm/src/top/nn/convolution.cc
+++ b/nnvm/src/top/nn/convolution.cc
@@ -150,10 +150,13 @@ inline bool Conv2DTransposeInferShape(const nnvm::NodeAttrs& attrs,
   CHECK_EQ(param.dilation.ndim(), 2U)
       << "incorrect dilate size: " << param.dilation;
 
-  TShape wshape({dshape_nchw[1],
-                 param.channels / param.groups,
-                 param.kernel_size[0], param.kernel_size[1]});
+  TShape wshape({param.channels / param.groups,
+                 dshape_nchw[1] / param.groups,
+                 param.kernel_size[0],
+                 param.kernel_size[1]});
+
   wshape = ConvertLayout(wshape, kNCHW, param.layout);
+  wshape[0] *= param.groups;
 
   NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, Conv2DTransposeParam::kWeight, wshape);
 
diff --git a/nnvm/tests/python/compiler/test_top_level2.py b/nnvm/tests/python/compiler/test_top_level2.py
index 0bfaef2c9f2d..458dc47707b4 100644
--- a/nnvm/tests/python/compiler/test_top_level2.py
+++ b/nnvm/tests/python/compiler/test_top_level2.py
@@ -54,6 +54,31 @@ def test_grouped_conv2d():
         np.testing.assert_allclose(out.asnumpy(), c_np, rtol=1e-5)
 
 
+def test_conv2d_transpose():
+    x = sym.Variable("x")
+    y = sym.conv2d_transpose(x, channels=10, kernel_size=(3,3), strides=(2,2),
+                             name="y", padding=(1,1), output_padding=(2,2))
+    dtype = "float32"
+    dshape = (1, 3, 18, 18)
+    kshape = (10, 3, 3, 3)
+    oshape = (1, 10, 37, 37)
+    shape_dict = {"x": dshape}
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
+        m = graph_runtime.create(graph, lib, ctx)
+        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+        kernel = tvm.nd.array(np.random.uniform(size=kshape).astype(dtype))
+        bias = tvm.nd.array(np.random.uniform(size=kshape[0]).astype(dtype))
+        m.run(x=data, y_weight=kernel, y_bias=bias)
+        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
+        c_np = topi.testing.conv2d_transpose_nchw_python(
+            data.asnumpy(), kernel.asnumpy(), 2, 1)
+        c_np = c_np + bias.asnumpy().reshape(kshape[0], 1, 1)
+        d_np = np.zeros(shape=oshape)
+        d_np[:,:,0:c_np.shape[2],0:c_np.shape[3]] = c_np
+        np.testing.assert_allclose(out.asnumpy(), d_np, rtol=1e-5)
+
+
 def test_max_pool2d():
     x = sym.Variable("x")
     y = sym.max_pool2d(x, pool_size=(2,2), strides=(2,2),
@@ -126,6 +151,7 @@ def test_global_avg_pool2d():
 if __name__ == "__main__":
     test_conv2d()
     test_grouped_conv2d()
+    test_conv2d_transpose()
     test_max_pool2d()
     test_avg_pool2d()
     test_global_max_pool2d()

From 4180465525ee6e4ceef1623130c98a2e33003b7c Mon Sep 17 00:00:00 2001
From: WolffyChen <sunjingrun@yeah.net>
Date: Sat, 4 Nov 2017 17:48:30 +0800
Subject: [PATCH 550/949] Update deploy.md (#219)

* Update deploy.md

Add an example in c++.

* Update deploy.md

Add an example in c++.

* Update deploy.md

Add an example in c++.

* Update deploy.md

Add an example in c++.
---
 nnvm/docs/how_to/deploy.md | 82 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 82 insertions(+)

diff --git a/nnvm/docs/how_to/deploy.md b/nnvm/docs/how_to/deploy.md
index d0cd26a0adc2..f18f70560116 100644
--- a/nnvm/docs/how_to/deploy.md
+++ b/nnvm/docs/how_to/deploy.md
@@ -35,3 +35,85 @@ out = tvm.nd.empty(shape)
 get_output(0, out)
 print(out.asnumpy())
 ```
+
+An example in c++.
+```cpp
+#include <dlpack/dlpack.h>
+#include <tvm/runtime/module.h>
+#include <tvm/runtime/registry.h>
+#include <tvm/runtime/packed_func.h>
+
+#include <fstream>
+#include <iterator>
+#include <algorithm>
+
+int main()
+{
+    // tvm module for compiled functions
+    tvm::runtime::Module mod_dylib = tvm::runtime::Module::LoadFromFile("deploy.so");
+
+    // json graph
+    std::ifstream json_in("deploy.json", std::ios::in);
+    std::string json_data((std::istreambuf_iterator<char>(json_in)), std::istreambuf_iterator<char>());
+    json_in.close();
+
+    // parameters in binary
+    std::ifstream params_in("deploy.params", std::ios::binary);
+    std::string params_data((std::istreambuf_iterator<char>(params_in)), std::istreambuf_iterator<char>());
+    params_in.close();
+
+    // parameters need to be TVMByteArray type to indicate the binary data
+    TVMByteArray params_arr;
+    params_arr.data = params_data.c_str();
+    params_arr.size = params_data.length();
+
+    // get global function module for graph runtime
+    tvm::runtime::Module mod = (*tvm::runtime::Registry::Get("tvm.graph_runtime.create"))(json_data, mod_dylib, 1, 0);
+
+    int dtype_code = kFloat;
+    int dtype_bits = 32;
+    int dtype_lanes = 1;
+    int device_type = kCPU;
+    int device_id = 0;
+
+    DLTensor* x;
+    int in_ndim = 4;
+    int64_t in_shape[4] = {1, 3, 224, 224};
+    TVMArrayAlloc(in_shape, in_ndim, dtype_code, dtype_bits, dtype_lanes, device_type, device_id, &x);
+    // load image data saved in binary
+    std::ifstream data_fin("cat.bin", std::ios::binary);
+    data_fin.read(static_cast<char*>(x->data), 3 * 224 * 224 * 4);
+
+    // get the function from the module(set input data)
+    tvm::runtime::PackedFunc set_input = mod.GetFunction("set_input");
+    set_input("data", x);
+
+    // get the function from the module(load patameters)
+    tvm::runtime::PackedFunc load_params = mod.GetFunction("load_params");
+    load_params(params_arr);
+
+    // get the function from the module(run it)
+    tvm::runtime::PackedFunc run = mod.GetFunction("run");
+    run();
+
+    DLTensor* y;
+    int out_ndim = 1;
+    int64_t out_shape[1] = {1000, };
+    TVMArrayAlloc(out_shape, out_ndim, dtype_code, dtype_bits, dtype_lanes, device_type, device_id, &y);
+
+    // get the function from the module(get output data)
+    tvm::runtime::PackedFunc get_output = mod.GetFunction("get_output");
+    get_output(0, y);
+
+    // get the maximum position in output vector
+    auto y_iter = static_cast<float*>(y->data);
+    auto max_iter = std::max_element(y_iter, y_iter + 1000);
+    auto max_index = std::distance(y_iter, max_iter);
+    std::cout << "The maximum position in output vector is: " << max_index << std::endl;
+
+    TVMArrayFree(x);
+    TVMArrayFree(y);
+
+    return 0;
+}
+```

From c307dade9af8b405864c68f423233c1e30c5dd00 Mon Sep 17 00:00:00 2001
From: Yuwei Hu <huyuwei1995@gmail.com>
Date: Mon, 6 Nov 2017 13:10:41 +0800
Subject: [PATCH 551/949]  [TOP] fix weight layout in conv2d_transpose (#220)

* update tvm

* [TOP] fix weight layout in conv2d_transpose
---
 nnvm/src/top/nn/convolution.cc                | 9 +++------
 nnvm/tests/python/compiler/test_top_level2.py | 6 +++---
 2 files changed, 6 insertions(+), 9 deletions(-)

diff --git a/nnvm/src/top/nn/convolution.cc b/nnvm/src/top/nn/convolution.cc
index 166caabc9e6a..48164d5e7f16 100644
--- a/nnvm/src/top/nn/convolution.cc
+++ b/nnvm/src/top/nn/convolution.cc
@@ -150,14 +150,11 @@ inline bool Conv2DTransposeInferShape(const nnvm::NodeAttrs& attrs,
   CHECK_EQ(param.dilation.ndim(), 2U)
       << "incorrect dilate size: " << param.dilation;
 
-  TShape wshape({param.channels / param.groups,
-                 dshape_nchw[1] / param.groups,
+  TShape wshape({dshape_nchw[1],
+                 param.channels / param.groups,
                  param.kernel_size[0],
                  param.kernel_size[1]});
-
   wshape = ConvertLayout(wshape, kNCHW, param.layout);
-  wshape[0] *= param.groups;
-
   NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, Conv2DTransposeParam::kWeight, wshape);
 
   if (param.use_bias) {
@@ -192,7 +189,7 @@ said convolution.
 
 - **data**: This depends on the `layout` parameter. Input is 4D array of shape
             (batch_size, in_channels, height, width) if `layout` is `NCHW`.
-- **weight**: (channels, in_channels, kernel_size[0], kernel_size[1])
+- **weight**: (in_channels, channels, kernel_size[0], kernel_size[1])
 - **bias**: (channels,)
 - **out**:  This depends on the `layout` parameter. Output is 4D array of shape
             (batch_size, channels, out_height, out_width) if `layout` is `NCHW`.
diff --git a/nnvm/tests/python/compiler/test_top_level2.py b/nnvm/tests/python/compiler/test_top_level2.py
index 458dc47707b4..84fe13c4be4e 100644
--- a/nnvm/tests/python/compiler/test_top_level2.py
+++ b/nnvm/tests/python/compiler/test_top_level2.py
@@ -60,7 +60,7 @@ def test_conv2d_transpose():
                              name="y", padding=(1,1), output_padding=(2,2))
     dtype = "float32"
     dshape = (1, 3, 18, 18)
-    kshape = (10, 3, 3, 3)
+    kshape = (3, 10, 3, 3)
     oshape = (1, 10, 37, 37)
     shape_dict = {"x": dshape}
     for target, ctx in ctx_list():
@@ -68,12 +68,12 @@ def test_conv2d_transpose():
         m = graph_runtime.create(graph, lib, ctx)
         data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
         kernel = tvm.nd.array(np.random.uniform(size=kshape).astype(dtype))
-        bias = tvm.nd.array(np.random.uniform(size=kshape[0]).astype(dtype))
+        bias = tvm.nd.array(np.random.uniform(size=kshape[1]).astype(dtype))
         m.run(x=data, y_weight=kernel, y_bias=bias)
         out = m.get_output(0, tvm.nd.empty(oshape, dtype))
         c_np = topi.testing.conv2d_transpose_nchw_python(
             data.asnumpy(), kernel.asnumpy(), 2, 1)
-        c_np = c_np + bias.asnumpy().reshape(kshape[0], 1, 1)
+        c_np = c_np + bias.asnumpy().reshape(kshape[1], 1, 1)
         d_np = np.zeros(shape=oshape)
         d_np[:,:,0:c_np.shape[2],0:c_np.shape[3]] = c_np
         np.testing.assert_allclose(out.asnumpy(), d_np, rtol=1e-5)

From bcdf0a20271aa826cd3dc31251b583c56a5fff99 Mon Sep 17 00:00:00 2001
From: WolffyChen <sunjingrun@yeah.net>
Date: Tue, 7 Nov 2017 01:57:38 +0800
Subject: [PATCH 552/949] Update deploy.md (#223)

* Update deploy.md

Add an example in c++.

* Update deploy.md

Add an example in c++.

* Update deploy.md

Add an example in c++.

* Update deploy.md

Add an example in c++.

* Update deploy.md

Update dlpack device and type enumerations.
---
 nnvm/docs/how_to/deploy.md | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/nnvm/docs/how_to/deploy.md b/nnvm/docs/how_to/deploy.md
index f18f70560116..fc3c59a68863 100644
--- a/nnvm/docs/how_to/deploy.md
+++ b/nnvm/docs/how_to/deploy.md
@@ -67,15 +67,15 @@ int main()
     params_arr.data = params_data.c_str();
     params_arr.size = params_data.length();
 
-    // get global function module for graph runtime
-    tvm::runtime::Module mod = (*tvm::runtime::Registry::Get("tvm.graph_runtime.create"))(json_data, mod_dylib, 1, 0);
-
-    int dtype_code = kFloat;
+    int dtype_code = kDLFloat;
     int dtype_bits = 32;
     int dtype_lanes = 1;
-    int device_type = kCPU;
+    int device_type = kDLCPU;
     int device_id = 0;
 
+    // get global function module for graph runtime
+    tvm::runtime::Module mod = (*tvm::runtime::Registry::Get("tvm.graph_runtime.create"))(json_data, mod_syslib, device_type, device_id);
+
     DLTensor* x;
     int in_ndim = 4;
     int64_t in_shape[4] = {1, 3, 224, 224};

From 287046baf52e1b1604b6722e3a9e852ff5ef08ef Mon Sep 17 00:00:00 2001
From: ziheng <ziheng@apache.org>
Date: Mon, 6 Nov 2017 12:09:40 -0800
Subject: [PATCH 553/949] [FIX] Update rasp benchmark example (#225)

---
 nnvm/examples/benchmark/rasp_imagenet_bench.py | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/nnvm/examples/benchmark/rasp_imagenet_bench.py b/nnvm/examples/benchmark/rasp_imagenet_bench.py
index 5c3a9b519e9a..098ae721da40 100644
--- a/nnvm/examples/benchmark/rasp_imagenet_bench.py
+++ b/nnvm/examples/benchmark/rasp_imagenet_bench.py
@@ -23,7 +23,6 @@ def main():
     args = parser.parse_args()
 
     opt_level = args.opt_level
-    target = "llvm -target=armv7l-none-linux-gnueabihf -mcpu=cortex-a53 -mattr=+neon"
 
     num_iter = args.num_iter
     batch_size = 1
@@ -43,9 +42,8 @@ def main():
 
 
     with nnvm.compiler.build_config(opt_level=opt_level):
-        with tvm.target.rasp():
-            graph, lib, params = nnvm.compiler.build(
-                net, target, shape={"data": data_shape}, params=params)
+        graph, lib, params = nnvm.compiler.build(
+            net, tvm.target.rasp(), shape={"data": data_shape}, params=params)
 
     tmp = util.tempdir()
     lib_fname = tmp.relpath('net.o')

From 20163eb4a41a0a4af0192414dc83a4b802eab5f1 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 9 Nov 2017 12:07:45 -0800
Subject: [PATCH 554/949] [CMAKE] Update cmake to support OSX/Linux (#228)

---
 nnvm/CMakeLists.txt         | 11 ++++++++---
 nnvm/python/nnvm/libinfo.py |  1 +
 2 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/nnvm/CMakeLists.txt b/nnvm/CMakeLists.txt
index da89bdb24d9d..6a7adb949beb 100644
--- a/nnvm/CMakeLists.txt
+++ b/nnvm/CMakeLists.txt
@@ -43,10 +43,10 @@ if(MSVC)
     endif(${flag_var} MATCHES "/MD")
   endforeach(flag_var)
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc")
-  find_library(TVM_LIB tvm  
+  find_library(TVM_LIB tvm
 			   HINTS ${CMAKE_CURRENT_SOURCE_DIR}/../tvm/build/Release
-			   HINTS ${CMAKE_CURRENT_SOURCE_DIR}/tvm/build/Release) 
-  message(STATUS "Build with TVM libary: " ${TVM_LIB})			    
+			   HINTS ${CMAKE_CURRENT_SOURCE_DIR}/tvm/build/Release)
+  message(STATUS "Build with TVM libary: " ${TVM_LIB})
   list(APPEND NNVM_COMPILER_LINKER_LIBS ${TVM_LIB})
 else(MSVC)
   include(CheckCXXCompilerFlag)
@@ -55,6 +55,11 @@ else(MSVC)
   set(CMAKE_CXX_FLAGS ${CMAKE_C_FLAGS})
 endif(MSVC)
 
+if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
+  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -undefined dynamic_lookup")
+endif()
+
+
 mxnet_source_group("Include\\c_api"   GLOB "src/c_api/*.h")
 mxnet_source_group("Include\\core"   GLOB "src/core/*.h")
 mxnet_source_group("Include\\pass"   GLOB "src/pass/*.h")
diff --git a/nnvm/python/nnvm/libinfo.py b/nnvm/python/nnvm/libinfo.py
index 4a326b269aff..bd802cdbf35c 100644
--- a/nnvm/python/nnvm/libinfo.py
+++ b/nnvm/python/nnvm/libinfo.py
@@ -30,6 +30,7 @@ def find_lib_path():
 
     api_path = os.path.join(base_path, '../../lib/')
     cmake_build_path = os.path.join(base_path, '../../build/Release/')
+    cmake_build_path = os.path.join(base_path, '../../build/')
     dll_path = [base_path, api_path, cmake_build_path]
 
     if sys.platform.startswith('linux') and os.environ.get('LD_LIBRARY_PATH', None):

From 1ac4f9a4552baa297aaf9c7c733dfc5928ae57ad Mon Sep 17 00:00:00 2001
From: abergeron <bergearn@iro.umontreal.ca>
Date: Mon, 13 Nov 2017 15:57:06 -0500
Subject: [PATCH 555/949] Add conda package (#231)

* Make sure to install the nnvm_compiler library.

* Add conda packge.
---
 nnvm/CMakeLists.txt         |  3 +++
 nnvm/python/conda/build.sh  | 28 ++++++++++++++++++++++++++++
 nnvm/python/conda/meta.yaml | 32 ++++++++++++++++++++++++++++++++
 3 files changed, 63 insertions(+)
 create mode 100644 nnvm/python/conda/build.sh
 create mode 100644 nnvm/python/conda/meta.yaml

diff --git a/nnvm/CMakeLists.txt b/nnvm/CMakeLists.txt
index 6a7adb949beb..a37bc5f1fbd6 100644
--- a/nnvm/CMakeLists.txt
+++ b/nnvm/CMakeLists.txt
@@ -113,8 +113,11 @@ if(BUILD_SHARED_NNVM)
   if(WIN32)
     install(TARGETS nnvm RUNTIME DESTINATION bin)
     install(TARGETS nnvm ARCHIVE DESTINATION lib)
+    install(TARGETS nnvm_compiler RUNTIME DESTINATION bin)
+    install(TARGETS nnvm_compiler ARCHIVE DESTINATION lib)
   else()
     install(TARGETS nnvm LIBRARY DESTINATION lib)
+    install(TARGETS nnvm_compiler LIBRARY DESTINATION lib)
   endif()
 endif()
 if(BUILD_STATIC_NNVM)
diff --git a/nnvm/python/conda/build.sh b/nnvm/python/conda/build.sh
new file mode 100644
index 000000000000..484d0626ae03
--- /dev/null
+++ b/nnvm/python/conda/build.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+set -e
+
+if [ -z "$PREFIX" ]; then
+  PREFIX="$CONDA_PREFIX"
+fi
+
+if [ "$(uname)" = 'Darwin' ]
+then
+    # Without this, Apple's default shipped clang will refuse to see any
+    # headers like mutex.
+    export MACOSX_DEPLOYMENT_TARGET=10.9
+fi
+
+rm -rf build || true
+mkdir -p build
+cd build
+# Enable static-libstdc++ to make it easier to link this library with
+# other C++ compilers
+CXXFLAGS=-static-libstdc++ cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} ..
+make -j4 VERBOSE=1
+make install/fast
+cd ..
+
+cd python
+$PYTHON setup.py install
+cd ..
diff --git a/nnvm/python/conda/meta.yaml b/nnvm/python/conda/meta.yaml
new file mode 100644
index 000000000000..56e052000b40
--- /dev/null
+++ b/nnvm/python/conda/meta.yaml
@@ -0,0 +1,32 @@
+{% set version = "0.1.dev" %}
+
+package:
+  name: nnvm
+  version: {{ version }}
+
+source:
+  path: ../..
+
+build:
+  number: 1
+  skip: True  # [win]
+
+requirements:
+  build:
+    - cmake
+    - python >=3
+    - numpy
+    - setuptools
+    - nose
+    - decorator
+  run:
+    - tvm
+    - topi
+    - python >=3
+    - numpy
+    - decorator
+
+about:
+  home: https://github.com/dmlc/nnvm
+  license: Apache2
+  summary: Bring deep learning to bare metal

From 2221d4b09ebae88580b7643eeb8e2c1abe0611f2 Mon Sep 17 00:00:00 2001
From: Leyuan Wang <laurawly@gmail.com>
Date: Mon, 13 Nov 2017 21:06:33 -0800
Subject: [PATCH 556/949] unroll condition changed for cuda (#232)

---
 nnvm/examples/benchmark/gpu_imagenet_bench.py | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/nnvm/examples/benchmark/gpu_imagenet_bench.py b/nnvm/examples/benchmark/gpu_imagenet_bench.py
index b1f940d14143..1ffbccd90f53 100644
--- a/nnvm/examples/benchmark/gpu_imagenet_bench.py
+++ b/nnvm/examples/benchmark/gpu_imagenet_bench.py
@@ -49,8 +49,12 @@ def main():
     else:
         raise ValueError('no benchmark prepared for {}.'.format(args.model))
 
+    if args.target == "cuda":
+        unroll = 1400
+    else:
+        unroll = 128
     with nnvm.compiler.build_config(opt_level=opt_level):
-        with tvm.build_config(auto_unroll_max_step=128,
+        with tvm.build_config(auto_unroll_max_step=unroll,
                               unroll_explicit=(args.target != "cuda")):
             graph, lib, params = nnvm.compiler.build(
                 net, args.target, shape={"data": data_shape}, params=params)

From ab6950d8a69ace5558db68ff6e79b6c941d3819d Mon Sep 17 00:00:00 2001
From: srkreddy1238 <srk.it38@gmail.com>
Date: Sat, 18 Nov 2017 00:13:17 +0530
Subject: [PATCH 557/949] Correction in C++sample code. (#238)

Variable declaration correction.
---
 nnvm/docs/how_to/deploy.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/docs/how_to/deploy.md b/nnvm/docs/how_to/deploy.md
index fc3c59a68863..d6c011bd2584 100644
--- a/nnvm/docs/how_to/deploy.md
+++ b/nnvm/docs/how_to/deploy.md
@@ -50,7 +50,7 @@ An example in c++.
 int main()
 {
     // tvm module for compiled functions
-    tvm::runtime::Module mod_dylib = tvm::runtime::Module::LoadFromFile("deploy.so");
+    tvm::runtime::Module mod_syslib = tvm::runtime::Module::LoadFromFile("deploy.so");
 
     // json graph
     std::ifstream json_in("deploy.json", std::ios::in);

From ebee65036168b6ce4d39559ae50123cc4bd1ada8 Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Sun, 19 Nov 2017 08:00:52 +0800
Subject: [PATCH 558/949] [BUILD] add target_host to compiler.build (#240)

---
 nnvm/include/nnvm/graph.h                 | 11 +++++++++++
 nnvm/python/nnvm/compiler/build_module.py | 19 ++++++++++++++++---
 nnvm/src/compiler/graph_fuse.cc           |  6 +++++-
 3 files changed, 32 insertions(+), 4 deletions(-)

diff --git a/nnvm/include/nnvm/graph.h b/nnvm/include/nnvm/graph.h
index 1cd1ed63db97..3f9ab59bb4c4 100644
--- a/nnvm/include/nnvm/graph.h
+++ b/nnvm/include/nnvm/graph.h
@@ -47,6 +47,12 @@ class Graph {
    */
   template<typename T>
   inline const T& GetAttr(const std::string& attr_name) const;
+  /*!
+   * \brief Check whether has a specific attribute.
+   * \param attr_name the name of the attribute
+   * \return a boolean result
+   */
+  inline bool HasAttr(const std::string& attr_name) const;
   /*!
    * \brief Get a move copy of the attribute, implement copy on write semantics.
    *  The content is moved if the reference counter of shared_ptr is 1.
@@ -226,6 +232,11 @@ inline const T& Graph::GetAttr(const std::string& attr_name) const {
   return nnvm::get<T>(*it->second);
 }
 
+inline bool Graph::HasAttr(const std::string& attr_name) const {
+  auto it = attrs.find(attr_name);
+  return it != attrs.end();
+}
+
 template<typename T>
 inline T Graph::MoveCopyAttr(const std::string& attr_name) {
   auto it = attrs.find(attr_name);
diff --git a/nnvm/python/nnvm/compiler/build_module.py b/nnvm/python/nnvm/compiler/build_module.py
index f2fc77d07287..4d644ef0e5e7 100644
--- a/nnvm/python/nnvm/compiler/build_module.py
+++ b/nnvm/python/nnvm/compiler/build_module.py
@@ -112,8 +112,10 @@ def _lower(sch, inputs, func_name, graph):
 
 
 @tvm.register_func("nnvm.compiler.build_target")
-def _build(funcs, target):
-    return tvm.build(funcs, target=target)
+def _build(funcs, target, target_host):
+    if target_host == "":
+        target_host = None
+    return tvm.build(funcs, target=target, target_host=target_host)
 
 
 def _update_shape_dtype(shape, dtype, params):
@@ -161,7 +163,7 @@ def optimize(graph, shape, dtype="float32"):
     return graph
 
 
-def build(graph, target=None, shape=None, dtype="float32", params=None):
+def build(graph, target=None, shape=None, dtype="float32", params=None, target_host=None):
     """Build graph into runtime library.
 
     The build function will optimize the graph and do the compilation.
@@ -189,6 +191,15 @@ def build(graph, target=None, shape=None, dtype="float32", params=None):
         during inference time. Used for pre-compute
         folding optimization.
 
+    target_host : str or :any:`tvm.target.Target` optional
+        Host compilation target, if target is device.
+        When TVM compiles device specific program such as CUDA,
+        we also need host(CPU) side code to interact with the driver
+        setup the dimensions and parameters correctly.
+        target_host is used to specify the host side codegen target.
+        By default, llvm is used if it is enabled,
+        otherwise a stackvm intepreter is used.
+
     Returns
     -------
     graph : Graph
@@ -228,6 +239,8 @@ def build(graph, target=None, shape=None, dtype="float32", params=None):
     graph = graph_attr.set_shape_inputs(graph, shape)
     graph = graph_attr.set_dtype_inputs(graph, dtype)
     graph._set_json_attr("target", str(target), "str")
+    if target_host is not None:
+        graph._set_json_attr("target_host", str(target_host), "str")
     if cfg.pass_enabled("OpFusion"):
         graph._set_json_attr("opt_level", 1, "int")
     else:
diff --git a/nnvm/src/compiler/graph_fuse.cc b/nnvm/src/compiler/graph_fuse.cc
index 60a4e9de433f..354c95aa4c0f 100644
--- a/nnvm/src/compiler/graph_fuse.cc
+++ b/nnvm/src/compiler/graph_fuse.cc
@@ -219,6 +219,10 @@ nnvm::Graph GraphFuseCompile(nnvm::Graph g) {
   const std::vector<TOpPattern>& pattern_vec =
       g.GetAttr<std::vector<TOpPattern> >("pattern");
   std::string target = g.GetAttr<std::string>("target");
+  std::string target_host;
+  if (g.HasAttr("target_host"))
+    target_host = g.GetAttr<std::string>("target_host");
+
   std::vector<FuseEntry> fuse_vec(idx.num_nodes());
   // setup inputs and placeholder.
   for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
@@ -398,7 +402,7 @@ nnvm::Graph GraphFuseCompile(nnvm::Graph g) {
   ret.attrs["dltype"] = std::make_shared<any>(std::move(new_dltype_vec));
   // Setup module
   static const PackedFunc& fbuild = GetPackedFunc("nnvm.compiler.build_target");
-  tvm::runtime::Module module = fbuild(func_list, target);
+  tvm::runtime::Module module = fbuild(func_list, target, target_host);
   ret.attrs["module"] = std::make_shared<any>(std::move(module));
   ret = nnvm::ApplyPass(ret, "PlanMemory");
   return ret;

From 559e1e600da6d6ae2167133222de2483ccd87a9a Mon Sep 17 00:00:00 2001
From: ziheng <ziheng@apache.org>
Date: Mon, 20 Nov 2017 21:41:27 -0800
Subject: [PATCH 559/949] [FIX] Fix from_mxnet for multiple outputs symbol
 (#247)

---
 nnvm/python/nnvm/frontend/mxnet.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/nnvm/python/nnvm/frontend/mxnet.py b/nnvm/python/nnvm/frontend/mxnet.py
index 4229a775efa3..bffe071eeb64 100644
--- a/nnvm/python/nnvm/frontend/mxnet.py
+++ b/nnvm/python/nnvm/frontend/mxnet.py
@@ -365,4 +365,6 @@ def from_mxnet(symbol, arg_params=None, aux_params=None):
     else:
         msg = "mxnet.Symbol or gluon.HybridBlock expected, got {}".format(type(symbol))
         raise ValueError(msg)
+    if isinstance(sym, list):
+        sym = _sym.Group(sym)
     return sym, params

From dee074614f512744a13c1dce2fdcd18b0c6f4639 Mon Sep 17 00:00:00 2001
From: masahi <masahi129@gmail.com>
Date: Wed, 22 Nov 2017 02:20:55 +0900
Subject: [PATCH 560/949] fix onnx conv2d_transpose loading (#245)

---
 nnvm/python/nnvm/frontend/onnx.py | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/onnx.py b/nnvm/python/nnvm/frontend/onnx.py
index c7d26393cbbf..c4db5599fbd1 100644
--- a/nnvm/python/nnvm/frontend/onnx.py
+++ b/nnvm/python/nnvm/frontend/onnx.py
@@ -64,7 +64,8 @@ def _impl(inputs, attr, *args):
         assert len(inputs) == 2, "Math op take 2 inputs, {} given".format(len(inputs))
         op_name = _math_name_picker(name)(attr)
         axis = int(attr.get('axis', 0))
-        if op_name == 'broadcast_add' and inputs[0].attr('op_name') == 'conv2d':
+        conv_ops = ["conv2d", "conv2d_transpose"]
+        if op_name == 'broadcast_add' and inputs[0].attr('op_name') in conv_ops:
             # TODO(zhreshold): remove hard coded infershape
             inputs[1] = _sym.expand_dims(inputs[1], axis=axis, num_newaxis=2)
         return get_nnvm_op(op_name)(*inputs)
@@ -101,8 +102,10 @@ def _impl(inputs, attr, params):
 def _conv_transpose():
     def _impl(inputs, attr, params):
         # get number of channels
-        channels = _infer_channels(inputs[1], params)
+        channels = _infer_channels(inputs[1], params, True)
         attr['channels'] = channels
+        groups = attr.pop('group')
+        attr['groups'] = groups
         return AttrCvt(
             op_name=_dimension_picker('conv', '_transpose'),
             transforms={

From fd1c086bd31f88b2a48f4063f1b748b517ea60c9 Mon Sep 17 00:00:00 2001
From: ziheng <ziheng@apache.org>
Date: Wed, 22 Nov 2017 12:05:06 -0800
Subject: [PATCH 561/949] [CMPL] Add Support for Other Data Types (#252)

* [CMPL] Add Support for Other Data Types

* [CMPL] Add test

* [CMPL] Fix
---
 nnvm/include/nnvm/top/tensor.h           |  4 +++
 nnvm/python/nnvm/compiler/graph_attr.py  | 18 ++++++++++++--
 nnvm/src/compiler/compile_engine.cc      | 31 +++++++++++++++++++++---
 nnvm/src/compiler/compile_engine.h       | 16 ++++++++++++
 nnvm/src/compiler/graph_fuse.cc          |  4 +--
 nnvm/tests/python/compiler/test_build.py | 19 +++++++++++++++
 6 files changed, 84 insertions(+), 8 deletions(-)

diff --git a/nnvm/include/nnvm/top/tensor.h b/nnvm/include/nnvm/top/tensor.h
index 16ed259dac69..a1faf58a6f35 100644
--- a/nnvm/include/nnvm/top/tensor.h
+++ b/nnvm/include/nnvm/top/tensor.h
@@ -56,6 +56,7 @@ enum TypeFlag {
   kInt32 = 4,
   kInt8  = 5,
   kInt64 = 6,
+  kInt16 = 7,
 };
 
 struct CastParam : public dmlc::Parameter<CastParam> {
@@ -67,6 +68,9 @@ struct CastParam : public dmlc::Parameter<CastParam> {
     .add_enum("float16", kFloat16)
     .add_enum("uint8", kUint8)
     .add_enum("int32", kInt32)
+    .add_enum("int8", kInt8)
+    .add_enum("int64", kInt64)
+    .add_enum("int16", kInt16)
     .describe("Output data type.");
   }
 };
diff --git a/nnvm/python/nnvm/compiler/graph_attr.py b/nnvm/python/nnvm/compiler/graph_attr.py
index 43be85e644bb..85625aca162a 100644
--- a/nnvm/python/nnvm/compiler/graph_attr.py
+++ b/nnvm/python/nnvm/compiler/graph_attr.py
@@ -28,12 +28,26 @@ def set_shape_inputs(g, shape):
 
 DTYPE_TO_TCODE = {
     "default": -1,
-    "float32": 0
+    "float32": 0,
+    "float64": 1,
+    "float16": 2,
+    "uint8": 3,
+    "int32": 4,
+    "int8": 5,
+    "int64": 6,
+    "int16": 7,
 }
 
 TCODE_TO_DTYPE = {
     -1: None,
-    0: "float32"
+    0: "float32",
+    1: "float64",
+    2: "float16",
+    3: "uint8",
+    4: "int32",
+    5: "int8",
+    6: "int64",
+    7: "int16",
 }
 
 def set_dtype_inputs(g, dtype):
diff --git a/nnvm/src/compiler/compile_engine.cc b/nnvm/src/compiler/compile_engine.cc
index b7f695d9f332..f527e83872e5 100644
--- a/nnvm/src/compiler/compile_engine.cc
+++ b/nnvm/src/compiler/compile_engine.cc
@@ -27,14 +27,39 @@ using namespace tvm;
  */
 int GetTypeFlag(tvm::Type type) {
   if (type == tvm::Float(32)) return 0;
+  if (type == tvm::Float(64)) return 1;
+  if (type == tvm::Float(16)) return 2;
+  if (type == tvm::UInt(8)) return 3;
+  if (type == tvm::Int(32)) return 4;
+  if (type == tvm::Int(8)) return 5;
+  if (type == tvm::Int(64)) return 6;
+  if (type == tvm::Int(16)) return 7;
   LOG(FATAL) << "cannot convert " << type;
   return 0;
 }
 // convert from type flag to tvm type.
 Type GetTVMType(int type_flag) {
-  if (type_flag == 0) return tvm::Float(32);
-  LOG(FATAL) << "unknown type_flag=" << type_flag;
-  return Float(32);
+  switch (type_flag) {
+    case 0:
+      return tvm::Float(32);
+    case 1:
+      return tvm::Float(64);
+    case 2:
+      return tvm::Float(16);
+    case 3:
+      return tvm::UInt(8);
+    case 4:
+      return tvm::Int(32);
+    case 5:
+      return tvm::Int(8);
+    case 6:
+      return tvm::Int(64);
+    case 7:
+      return tvm::Int(16);
+    default:
+      LOG(FATAL) << "unknown type_flag=" << type_flag;
+      return Float(32);
+  }
 }
 
 // internal compile engine
diff --git a/nnvm/src/compiler/compile_engine.h b/nnvm/src/compiler/compile_engine.h
index 91ab2588a593..109b8d8df03b 100644
--- a/nnvm/src/compiler/compile_engine.h
+++ b/nnvm/src/compiler/compile_engine.h
@@ -90,6 +90,22 @@ GraphFunc GraphLower(Graph graph,
                      const Op* schedule_op_key,
                      const NodeAttrs& schedule_op_attr);
 
+/*!
+ * \brief Get type flag from TVM Type
+ *
+ * \param type the tvm type
+ * \return corresponding DLDataType
+ */
+int GetTypeFlag(tvm::Type type);
+
+/*!
+ * \brief Get TVM Type from type flag
+ *
+ * \param type_flag the type flag
+ * \return corresponding TVM type
+ */
+tvm::Type GetTVMType(int type_flag);
+
 }  // namespace compiler
 }  // namespace nnvm
 
diff --git a/nnvm/src/compiler/graph_fuse.cc b/nnvm/src/compiler/graph_fuse.cc
index 354c95aa4c0f..d47295838f18 100644
--- a/nnvm/src/compiler/graph_fuse.cc
+++ b/nnvm/src/compiler/graph_fuse.cc
@@ -36,9 +36,7 @@ enum class FuseRule {
  * \return corresponding DLDataType
  */
 DLDataType GetDLType(int type_flag) {
-  if (type_flag == 0) return tvm::Type2TVMType(tvm::Float(32));
-  LOG(FATAL) << "unknown type_flag=" << type_flag;
-  return Type2TVMType(Float(32));
+  return Type2TVMType(GetTVMType(type_flag));
 }
 
 // Partition the graph into segments
diff --git a/nnvm/tests/python/compiler/test_build.py b/nnvm/tests/python/compiler/test_build.py
index f4e57954f53f..5e1f0337c293 100644
--- a/nnvm/tests/python/compiler/test_build.py
+++ b/nnvm/tests/python/compiler/test_build.py
@@ -77,7 +77,26 @@ def test_precompute_prune():
         res.asnumpy(), nx.asnumpy() + 1 + ny.asnumpy() + na.asnumpy())
 
 
+def test_dtypes():
+    x = sym.Variable("x")
+    y = sym.relu(x)
+    dshape = (1, 3, 32, 32)
+    oshape = dshape
+    for dtype in ['float32', 'float64', 'int32', 'int16', 'int8', 'int64']:
+        graph, lib, _ = nnvm.compiler.build(y, 'llvm', {"x": dshape}, dtype=dtype)
+        m = graph_runtime.create(graph, lib, tvm.cpu())
+        if 'float' in dtype:
+          data = np.random.uniform(size=dshape).astype(dtype)
+        elif 'int' in dtype:
+          data = np.random.randint(-127, 127, dshape).astype(dtype)
+        m.run(x=data)
+        data = (data > 0) * data
+        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
+        np.testing.assert_allclose(out.asnumpy(), data, atol=1e-5, rtol=1e-5)
+
+
 if __name__ == "__main__":
     test_precompute_prune()
     test_compile()
     test_run()
+    test_dtypes()

From 153be2ef93e564c20e669a5e9c908140a8f83d44 Mon Sep 17 00:00:00 2001
From: Cyril Lashkevich <notorca@gmail.com>
Date: Tue, 28 Nov 2017 23:01:20 +0100
Subject: [PATCH 562/949] Added support for CoreML Permute layers (#262)

https://apple.github.io/coremltools/generated/coremltools.models.neural_network.html?highlight=permute#coremltools.models.neural_network.NeuralNetworkBuilder.add_permute
---
 nnvm/python/nnvm/frontend/coreml.py | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/nnvm/python/nnvm/frontend/coreml.py b/nnvm/python/nnvm/frontend/coreml.py
index 06bf80648da6..67c2d92f2741 100644
--- a/nnvm/python/nnvm/frontend/coreml.py
+++ b/nnvm/python/nnvm/frontend/coreml.py
@@ -269,6 +269,11 @@ def PaddingLayerParams(op, insym, symtab):
         raise NotImplementedError("Only constant padding is supported now.")
     return insym
 
+def PermuteLayerParams(op, insym, symtab):
+    axes = tuple(op.axis)
+    return _sym.transpose(insym, axes=axes)
+
+
 _convert_map = {
     'NeuralNetworkMeanImage': NeuralNetworkMeanImage,
     'NeuralNetworkImageScaler': NeuralNetworkImageScaler,
@@ -283,6 +288,7 @@ def PaddingLayerParams(op, insym, symtab):
     'FlattenLayerParams':FlattenLayerParams,
     'ConcatLayerParams':ConcatLayerParams,
     'PaddingLayerParams':PaddingLayerParams,
+    'PermuteLayerParams':PermuteLayerParams,
 }
 
 def coreml_op_to_nnvm(op, inname, outname, symtab):

From 88ea6988c73419fd9a12546377d1e6fb160188da Mon Sep 17 00:00:00 2001
From: "Joshua Z. Zhang" <cheungchih@gmail.com>
Date: Sat, 9 Dec 2017 20:48:14 -0600
Subject: [PATCH 563/949] [FRONTEND] Fix mxnet multi outputs (#271)

* fix mxnet multi outputs

* add test case for multi outputs

* fix

* fix

* fix

* fix index

* use json hack

* fix test cases

* fix test cases

* fix test cases

* fix test cases

* fix test cases

* fix test cases
---
 nnvm/python/nnvm/frontend/mxnet.py            | 15 ++++++------
 .../python/frontend/mxnet/model_zoo/mlp.py    | 20 +++++++++++-----
 .../python/frontend/mxnet/model_zoo/resnet.py |  5 +++-
 .../python/frontend/mxnet/model_zoo/vgg.py    | 23 +++++++++++++------
 .../tests/python/frontend/mxnet/test_graph.py | 12 ++++++++++
 5 files changed, 53 insertions(+), 22 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/mxnet.py b/nnvm/python/nnvm/frontend/mxnet.py
index bffe071eeb64..cd7b9fdc7ee9 100644
--- a/nnvm/python/nnvm/frontend/mxnet.py
+++ b/nnvm/python/nnvm/frontend/mxnet.py
@@ -222,6 +222,8 @@ def _softmax_output(inputs, attrs):
     'Pooling'       : _pooling,
     'Pooling_v1'    : _pooling,
     'Reshape'       : _reshape,
+    'SliceChannel'  : _split,
+    'split'         : _split,
     'Softmax'       : _rename('softmax'),
     'SoftmaxOutput' : _softmax_output,
     'concat'        : _concat,
@@ -269,10 +271,6 @@ def _convert_symbol(op_name, inputs, attrs,
         _raise_not_supported('Operator: ' + op_name)
     return sym
 
-def _is_mxnet_group_symbol(symbol):
-    """Internal check for mxnet group symbol."""
-    return len(symbol.list_outputs()) > 1
-
 def _as_list(arr):
     """Force being a list, ignore if already is."""
     if isinstance(arr, list):
@@ -296,26 +294,27 @@ def _from_mxnet_impl(symbol, graph):
     nnvm.sym.Symbol
         Converted symbol
     """
-    if _is_mxnet_group_symbol(symbol):
+    if len(symbol.list_outputs()) > 1:
         return [_from_mxnet_impl(s, graph) for s in symbol]
 
     name = symbol.attr('name')
+    output_index = json.loads(symbol.tojson())['heads'][0][1]
     node = graph.get(name, None)
     if node:
-        return node
+        return node[output_index]
     attr = symbol.list_attr()
     # op_name = symbol.attr('op_name')
     childs = symbol.get_children()
     if childs:
         op_name = symbol.attr('op_name')
-        childs = [_from_mxnet_impl(c, graph) for c in _as_list(childs)]
+        childs = [_from_mxnet_impl(c, graph) for c in childs]
         childs = [x for y in childs for x in _as_list(y)]  # expand group symbol
         node = _convert_symbol(op_name, childs, attr)
     else:
         op_name = json.loads(symbol.tojson())['nodes'][0]['op']
         node = _sym.Variable(name=name, **attr)
     graph[name] = node
-    return node
+    return node[output_index]
 
 def from_mxnet(symbol, arg_params=None, aux_params=None):
     """Convert from MXNet's model into compatible NNVM format.
diff --git a/nnvm/tests/python/frontend/mxnet/model_zoo/mlp.py b/nnvm/tests/python/frontend/mxnet/model_zoo/mlp.py
index 76db9d132fcb..922b208749bf 100644
--- a/nnvm/tests/python/frontend/mxnet/model_zoo/mlp.py
+++ b/nnvm/tests/python/frontend/mxnet/model_zoo/mlp.py
@@ -23,10 +23,18 @@
 def get_symbol(num_classes=10, **kwargs):
     data = mx.symbol.Variable('data')
     data = mx.sym.Flatten(data=data)
-    fc1  = mx.symbol.FullyConnected(data = data, name='fc1', num_hidden=128)
-    act1 = mx.symbol.Activation(data = fc1, name='relu1', act_type="relu")
-    fc2  = mx.symbol.FullyConnected(data = act1, name = 'fc2', num_hidden = 64)
-    act2 = mx.symbol.Activation(data = fc2, name='relu2', act_type="relu")
-    fc3  = mx.symbol.FullyConnected(data = act2, name='fc3', num_hidden=num_classes)
-    mlp  = mx.symbol.softmax(data = fc3, name = 'softmax')
+    try:
+        fc1  = mx.symbol.FullyConnected(data = data, name='fc1', num_hidden=128, flatten=False)
+        act1 = mx.symbol.Activation(data = fc1, name='relu1', act_type="relu")
+        fc2  = mx.symbol.FullyConnected(data = act1, name = 'fc2', num_hidden = 64, flatten=False)
+        act2 = mx.symbol.Activation(data = fc2, name='relu2', act_type="relu")
+        fc3  = mx.symbol.FullyConnected(data = act2, name='fc3', num_hidden=num_classes, flatten=False)
+        mlp  = mx.symbol.softmax(data = fc3, name = 'softmax')
+    except:
+        fc1  = mx.symbol.FullyConnected(data = data, name='fc1', num_hidden=128)
+        act1 = mx.symbol.Activation(data = fc1, name='relu1', act_type="relu")
+        fc2  = mx.symbol.FullyConnected(data = act1, name = 'fc2', num_hidden = 64)
+        act2 = mx.symbol.Activation(data = fc2, name='relu2', act_type="relu")
+        fc3  = mx.symbol.FullyConnected(data = act2, name='fc3', num_hidden=num_classes)
+        mlp  = mx.symbol.softmax(data = fc3, name = 'softmax')
     return mlp
diff --git a/nnvm/tests/python/frontend/mxnet/model_zoo/resnet.py b/nnvm/tests/python/frontend/mxnet/model_zoo/resnet.py
index 85c0758bf26a..42a62af023e7 100644
--- a/nnvm/tests/python/frontend/mxnet/model_zoo/resnet.py
+++ b/nnvm/tests/python/frontend/mxnet/model_zoo/resnet.py
@@ -137,7 +137,10 @@ def resnet(units, num_stages, filter_list, num_classes, image_shape, bottle_neck
     # Although kernel is not used here when global_pool=True, we should put one
     pool1 = mx.sym.Pooling(data=relu1, global_pool=True, kernel=(7, 7), pool_type='avg', name='pool1')
     flat = mx.sym.Flatten(data=pool1)
-    fc1 = mx.sym.FullyConnected(data=flat, num_hidden=num_classes, name='fc1')
+    try:
+        fc1 = mx.sym.FullyConnected(data=flat, num_hidden=num_classes, name='fc1', flatten=False)
+    except:
+        fc1 = mx.sym.FullyConnected(data=flat, num_hidden=num_classes, name='fc1')
     if dtype == 'float16':
         fc1 = mx.sym.Cast(data=fc1, dtype=np.float32)
     return mx.sym.softmax(data=fc1, name='softmax')
diff --git a/nnvm/tests/python/frontend/mxnet/model_zoo/vgg.py b/nnvm/tests/python/frontend/mxnet/model_zoo/vgg.py
index cd99ff72e917..c9243d3610fa 100644
--- a/nnvm/tests/python/frontend/mxnet/model_zoo/vgg.py
+++ b/nnvm/tests/python/frontend/mxnet/model_zoo/vgg.py
@@ -36,13 +36,22 @@ def get_feature(internel_layer, layers, filters, batch_norm = False, **kwargs):
 
 def get_classifier(input_data, num_classes, **kwargs):
     flatten = mx.sym.Flatten(data=input_data, name="flatten")
-    fc6 = mx.sym.FullyConnected(data=flatten, num_hidden=4096, name="fc6")
-    relu6 = mx.sym.Activation(data=fc6, act_type="relu", name="relu6")
-    drop6 = mx.sym.Dropout(data=relu6, p=0.5, name="drop6")
-    fc7 = mx.sym.FullyConnected(data=drop6, num_hidden=4096, name="fc7")
-    relu7 = mx.sym.Activation(data=fc7, act_type="relu", name="relu7")
-    drop7 = mx.sym.Dropout(data=relu7, p=0.5, name="drop7")
-    fc8 = mx.sym.FullyConnected(data=drop7, num_hidden=num_classes, name="fc8")
+    try:
+        fc6 = mx.sym.FullyConnected(data=flatten, num_hidden=4096, name="fc6", flatten=False)
+        relu6 = mx.sym.Activation(data=fc6, act_type="relu", name="relu6")
+        drop6 = mx.sym.Dropout(data=relu6, p=0.5, name="drop6")
+        fc7 = mx.sym.FullyConnected(data=drop6, num_hidden=4096, name="fc7", flatten=False)
+        relu7 = mx.sym.Activation(data=fc7, act_type="relu", name="relu7")
+        drop7 = mx.sym.Dropout(data=relu7, p=0.5, name="drop7")
+        fc8 = mx.sym.FullyConnected(data=drop7, num_hidden=num_classes, name="fc8", flatten=False)
+    except:
+        fc6 = mx.sym.FullyConnected(data=flatten, num_hidden=4096, name="fc6")
+        relu6 = mx.sym.Activation(data=fc6, act_type="relu", name="relu6")
+        drop6 = mx.sym.Dropout(data=relu6, p=0.5, name="drop6")
+        fc7 = mx.sym.FullyConnected(data=drop6, num_hidden=4096, name="fc7")
+        relu7 = mx.sym.Activation(data=fc7, act_type="relu", name="relu7")
+        drop7 = mx.sym.Dropout(data=relu7, p=0.5, name="drop7")
+        fc8 = mx.sym.FullyConnected(data=drop7, num_hidden=num_classes, name="fc8")
     return fc8
 
 def get_symbol(num_classes, num_layers=11, batch_norm=False, dtype='float32', **kwargs):
diff --git a/nnvm/tests/python/frontend/mxnet/test_graph.py b/nnvm/tests/python/frontend/mxnet/test_graph.py
index bd2ce8727c92..bbbc42db1917 100644
--- a/nnvm/tests/python/frontend/mxnet/test_graph.py
+++ b/nnvm/tests/python/frontend/mxnet/test_graph.py
@@ -32,7 +32,19 @@ def test_resnet():
         nnvm_sym = model_zoo.nnvm_resnet[n]
         compare_graph(from_mx_sym, nnvm_sym)
 
+def test_multi_outputs():
+    def compose(F, **kwargs):
+        x = F.sym.Variable('x')
+        y = F.sym.Variable('y')
+        z = F.sym.split(x, **kwargs)
+        return F.sym.broadcast_sub(F.sym.broadcast_add(z[0], z[2]), y)
+    mx_sym = compose(mx, num_outputs=3, axis=1)
+    from_mx_sym, _ = nnvm.frontend.from_mxnet(mx_sym)
+    nnvm_sym = compose(nnvm, indices_or_sections=3, axis=1)
+    compare_graph(from_mx_sym, nnvm_sym)
+
 if __name__ == '__main__':
     test_mlp()
     test_vgg()
     test_resnet()
+    test_multi_outputs()

From bc0f5b6f552791bb6ebd259af45afc8efa4f3cbd Mon Sep 17 00:00:00 2001
From: Chris Olivier <cjolivier01@gmail.com>
Date: Sat, 9 Dec 2017 21:21:19 -0800
Subject: [PATCH 564/949] API call to get symbol output count (#270)

* Symbol __getitem__ using list_outputs() is too expensive, when it only cares about the output count in most cases

* Add cython cmake

* GetNumOutputs() and __len__ changes per PR comments

* set commit for tvm
---
 nnvm/include/nnvm/c_api.h        | 11 +++++++++++
 nnvm/src/c_api/c_api_symbolic.cc |  8 ++++++++
 2 files changed, 19 insertions(+)

diff --git a/nnvm/include/nnvm/c_api.h b/nnvm/include/nnvm/c_api.h
index e4e7ac59c5f6..f3ae626f3afc 100644
--- a/nnvm/include/nnvm/c_api.h
+++ b/nnvm/include/nnvm/c_api.h
@@ -247,6 +247,17 @@ NNVM_DLL int NNSymbolListInputNames(SymbolHandle symbol,
 NNVM_DLL int NNSymbolListOutputNames(SymbolHandle symbol,
                                      nn_uint *out_size,
                                      const char ***out_str_array);
+
+
+/*!
+ * \brief Supply number of outputs of the symbol.
+ * \param symbol the symbol
+ * \param output_count number of outputs
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNSymbolGetNumOutputs(SymbolHandle symbol,
+                                    nn_uint *output_count);
+
 /*!
  * \brief Get a symbol that contains all the internals.
  * \param symbol The symbol
diff --git a/nnvm/src/c_api/c_api_symbolic.cc b/nnvm/src/c_api/c_api_symbolic.cc
index cc3e934c533c..feb12fbc6d65 100644
--- a/nnvm/src/c_api/c_api_symbolic.cc
+++ b/nnvm/src/c_api/c_api_symbolic.cc
@@ -281,6 +281,14 @@ int NNSymbolListOutputNames(SymbolHandle symbol,
   API_END();
 }
 
+int NNSymbolGetNumOutputs(SymbolHandle symbol,
+                           nn_uint *output_count) {
+  Symbol *s = static_cast<Symbol*>(symbol);
+  API_BEGIN();
+    *output_count = static_cast<nn_uint>(s->outputs.size());
+  API_END();
+}
+
 int NNSymbolCompose(SymbolHandle sym,
                     const char *name,
                     nn_uint num_args,

From 8b5477023026ffc87e890a8cd6eaa57d691cd0b7 Mon Sep 17 00:00:00 2001
From: ziheng <ziheng@apache.org>
Date: Sat, 9 Dec 2017 22:00:15 -0800
Subject: [PATCH 565/949] [SYMBOL] Add __iter__ and GetChildren for symbol
 (#268)

* [SYMBOL] Add __iter__ and GetChildren for symbol

* [SYMBOL] Fix lint
---
 nnvm/include/nnvm/c_api.h        |  8 ++++++++
 nnvm/python/nnvm/symbol.py       | 14 ++++++++++++++
 nnvm/src/c_api/c_api_symbolic.cc |  9 +++++++++
 3 files changed, 31 insertions(+)

diff --git a/nnvm/include/nnvm/c_api.h b/nnvm/include/nnvm/c_api.h
index f3ae626f3afc..028c88d98154 100644
--- a/nnvm/include/nnvm/c_api.h
+++ b/nnvm/include/nnvm/c_api.h
@@ -266,6 +266,14 @@ NNVM_DLL int NNSymbolGetNumOutputs(SymbolHandle symbol,
  */
 NNVM_DLL int NNSymbolGetInternals(SymbolHandle symbol,
                                   SymbolHandle *out);
+/*!
+ * \brief Get a symbol that contains only direct children.
+ * \param symbol The symbol
+ * \param out The output symbol whose outputs are the direct children.
+ * \return 0 when success, -1 when failure happens
+ */
+NNVM_DLL int NNSymbolGetChildren(SymbolHandle symbol,
+                                 SymbolHandle *out);
 /*!
  * \brief Get index-th outputs of the symbol.
  * \param symbol The symbol
diff --git a/nnvm/python/nnvm/symbol.py b/nnvm/python/nnvm/symbol.py
index bca63a3656f2..0ac51f23fe94 100644
--- a/nnvm/python/nnvm/symbol.py
+++ b/nnvm/python/nnvm/symbol.py
@@ -143,6 +143,9 @@ def __getitem__(self, index):
             self.handle, _base.nn_uint(index), _ctypes.byref(handle)))
         return Symbol(handle=handle)
 
+    def __iter__(self):
+        return (self[i] for i in self.list_output_names())
+
     def attr(self, key):
         """Get attribute string from the symbol, this function only works for non-grouped symbol.
 
@@ -196,6 +199,17 @@ def get_internals(self):
             self.handle, _ctypes.byref(handle)))
         return Symbol(handle=handle)
 
+    def get_children(self):
+        """Gets a new grouped symbol whose output contains
+           inputs to output nodes of the original symbol."""
+        handle = _base.SymbolHandle()
+        _check_call(_LIB.NNSymbolGetChildren(
+            self.handle, _ctypes.byref(handle)))
+        ret = Symbol(handle=handle)
+        if not ret.list_output_names():
+            return None
+        return ret
+
     def _get_list_copt(self, option):
         """internal function to get list option"""
         if option == 'all':
diff --git a/nnvm/src/c_api/c_api_symbolic.cc b/nnvm/src/c_api/c_api_symbolic.cc
index feb12fbc6d65..0327ca4ae8a7 100644
--- a/nnvm/src/c_api/c_api_symbolic.cc
+++ b/nnvm/src/c_api/c_api_symbolic.cc
@@ -141,6 +141,15 @@ int NNSymbolGetInternals(SymbolHandle symbol,
   API_END_HANDLE_ERROR(delete s);
 }
 
+int NNSymbolGetChildren(SymbolHandle symbol,
+                        SymbolHandle *out) {
+  Symbol *s = new Symbol();
+  API_BEGIN();
+  *s = static_cast<Symbol*>(symbol)->GetChildren();
+  *out = s;
+  API_END_HANDLE_ERROR(delete s);
+}
+
 int NNSymbolFree(SymbolHandle symbol) {
   API_BEGIN();
   delete static_cast<Symbol*>(symbol);

From 2a29e4a11b793ca3596c27ba7f7510048c3af650 Mon Sep 17 00:00:00 2001
From: abergeron <bergearn@iro.umontreal.ca>
Date: Mon, 11 Dec 2017 19:42:13 -0500
Subject: [PATCH 566/949] Add support for missing uint types. (#272)

---
 nnvm/include/nnvm/top/tensor.h          | 14 ++++++++++----
 nnvm/python/nnvm/compiler/graph_attr.py |  6 ++++++
 nnvm/src/compiler/compile_engine.cc     |  9 +++++++++
 3 files changed, 25 insertions(+), 4 deletions(-)

diff --git a/nnvm/include/nnvm/top/tensor.h b/nnvm/include/nnvm/top/tensor.h
index a1faf58a6f35..d37825e63a8f 100644
--- a/nnvm/include/nnvm/top/tensor.h
+++ b/nnvm/include/nnvm/top/tensor.h
@@ -57,20 +57,26 @@ enum TypeFlag {
   kInt8  = 5,
   kInt64 = 6,
   kInt16 = 7,
+  kUint16 = 8,
+  kUint32 = 9,
+  kUint64 = 10,
 };
 
 struct CastParam : public dmlc::Parameter<CastParam> {
   int dtype;
   DMLC_DECLARE_PARAMETER(CastParam) {
     DMLC_DECLARE_FIELD(dtype)
+    .add_enum("float16", kFloat16)
     .add_enum("float32", kFloat32)
     .add_enum("float64", kFloat64)
-    .add_enum("float16", kFloat16)
-    .add_enum("uint8", kUint8)
+    .add_enum("uint8",  kUint8)
+    .add_enum("uint16", kUint16)
+    .add_enum("uint32", kUint32)
+    .add_enum("uint64", kUint64)
+    .add_enum("int8",  kInt8)
+    .add_enum("int16", kInt16)
     .add_enum("int32", kInt32)
-    .add_enum("int8", kInt8)
     .add_enum("int64", kInt64)
-    .add_enum("int16", kInt16)
     .describe("Output data type.");
   }
 };
diff --git a/nnvm/python/nnvm/compiler/graph_attr.py b/nnvm/python/nnvm/compiler/graph_attr.py
index 85625aca162a..94b973353e47 100644
--- a/nnvm/python/nnvm/compiler/graph_attr.py
+++ b/nnvm/python/nnvm/compiler/graph_attr.py
@@ -36,6 +36,9 @@ def set_shape_inputs(g, shape):
     "int8": 5,
     "int64": 6,
     "int16": 7,
+    "uint16": 8,
+    "uint32": 9,
+    "uint64": 10,
 }
 
 TCODE_TO_DTYPE = {
@@ -48,6 +51,9 @@ def set_shape_inputs(g, shape):
     5: "int8",
     6: "int64",
     7: "int16",
+    8: "uint16",
+    9: "uint32",
+    10: "uint64",
 }
 
 def set_dtype_inputs(g, dtype):
diff --git a/nnvm/src/compiler/compile_engine.cc b/nnvm/src/compiler/compile_engine.cc
index f527e83872e5..79d7fcfb8109 100644
--- a/nnvm/src/compiler/compile_engine.cc
+++ b/nnvm/src/compiler/compile_engine.cc
@@ -34,6 +34,9 @@ int GetTypeFlag(tvm::Type type) {
   if (type == tvm::Int(8)) return 5;
   if (type == tvm::Int(64)) return 6;
   if (type == tvm::Int(16)) return 7;
+  if (type == tvm::UInt(16)) return 8;
+  if (type == tvm::UInt(32)) return 9;
+  if (type == tvm::UInt(64)) return 10;
   LOG(FATAL) << "cannot convert " << type;
   return 0;
 }
@@ -56,6 +59,12 @@ Type GetTVMType(int type_flag) {
       return tvm::Int(64);
     case 7:
       return tvm::Int(16);
+    case 8:
+      return tvm::UInt(16);
+    case 9:
+      return tvm::UInt(32);
+    case 10:
+      return tvm::UInt(64);
     default:
       LOG(FATAL) << "unknown type_flag=" << type_flag;
       return Float(32);

From 45518bbbab562d13cb03b488f4a63a68d8abf486 Mon Sep 17 00:00:00 2001
From: Yuwei Hu <huyuwei1995@gmail.com>
Date: Tue, 19 Dec 2017 09:09:50 -0600
Subject: [PATCH 567/949] Keras Frontend (#273)

* vgg16 success

* remove six.PY2, use sys.version_info;
convert_activation() accepts activation type name(str, e.g. 'relu') as input;

* add convert_merge

* fix convert_batchnorm;
improve tests

* fix lint

* add numpy-style pad operator

* deal with asymmetry padding

* resnet50 success

* fix pool_convert; xception passes test

* update tvm

* fix bias error; all tests pass

* use > >, not >>
---
 nnvm/docs/top.rst                             |   3 +-
 nnvm/include/nnvm/top/nn.h                    |  15 +
 nnvm/python/nnvm/frontend/__init__.py         |   1 +
 nnvm/python/nnvm/frontend/common.py           |  35 ++
 nnvm/python/nnvm/frontend/coreml.py           |  35 +-
 nnvm/python/nnvm/frontend/keras.py            | 465 ++++++++++++++++++
 nnvm/python/nnvm/top/attr_dict.py             |  16 +
 nnvm/python/nnvm/top/nn.py                    |  19 +-
 nnvm/src/top/nn/nn.cc                         |  37 ++
 nnvm/tests/python/compiler/test_top_level1.py |  20 +
 .../python/frontend/keras/test_forward.py     | 117 +++++
 11 files changed, 727 insertions(+), 36 deletions(-)
 create mode 100644 nnvm/python/nnvm/frontend/keras.py
 create mode 100644 nnvm/tests/python/frontend/keras/test_forward.py

diff --git a/nnvm/docs/top.rst b/nnvm/docs/top.rst
index 3761ae1b20eb..6c07f6b08f31 100644
--- a/nnvm/docs/top.rst
+++ b/nnvm/docs/top.rst
@@ -47,6 +47,7 @@ This level enables fully connected multi-layer perceptron.
    nnvm.symbol.batch_norm
    nnvm.symbol.softmax
    nnvm.symbol.log_softmax
+   nnvm.symbol.pad
 
 
 **Level 2: Convolutions**
@@ -119,7 +120,7 @@ Detailed Definitions
 .. autofunction:: nnvm.symbol.batch_norm
 .. autofunction:: nnvm.symbol.softmax
 .. autofunction:: nnvm.symbol.log_softmax
-
+.. autofunction:: nnvm.symbol.pad
 
 .. autofunction:: nnvm.symbol.conv2d
 .. autofunction:: nnvm.symbol.conv2d_transpose
diff --git a/nnvm/include/nnvm/top/nn.h b/nnvm/include/nnvm/top/nn.h
index 672709a68394..4210676db80e 100644
--- a/nnvm/include/nnvm/top/nn.h
+++ b/nnvm/include/nnvm/top/nn.h
@@ -101,6 +101,21 @@ struct LeakyReLUParam : public dmlc::Parameter<LeakyReLUParam> {
   }
 };
 
+
+struct PadParam : public dmlc::Parameter<PadParam> {
+  float pad_value;
+  Tuple<Tuple<int> > pad_width;
+
+  DMLC_DECLARE_PARAMETER(PadParam) {
+    DMLC_DECLARE_FIELD(pad_value).set_default(0.0)
+      .describe("The value to be padded.");
+    DMLC_DECLARE_FIELD(pad_width)
+      .describe("Number of values padded to the edges of each axis, "
+                "in the format of ((before_1, after_1), ... (before_N, after_N))");
+  }
+};
+
+
 struct Conv2DParam : public dmlc::Parameter<Conv2DParam> {
   int channels;
   TShape kernel_size;
diff --git a/nnvm/python/nnvm/frontend/__init__.py b/nnvm/python/nnvm/frontend/__init__.py
index 5a2796aa1c64..100d4115bc3a 100644
--- a/nnvm/python/nnvm/frontend/__init__.py
+++ b/nnvm/python/nnvm/frontend/__init__.py
@@ -3,3 +3,4 @@
 from .mxnet import from_mxnet
 from .onnx import from_onnx
 from .coreml import from_coreml
+from .keras import from_keras
diff --git a/nnvm/python/nnvm/frontend/common.py b/nnvm/python/nnvm/frontend/common.py
index 72e9bffa6456..7b8c4621029d 100644
--- a/nnvm/python/nnvm/frontend/common.py
+++ b/nnvm/python/nnvm/frontend/common.py
@@ -135,3 +135,38 @@ def _required_attr(self, attr, key):
         if key not in attr:
             raise AttributeError("Required attribute {} not found.".format(key))
         return attr[key]
+
+
+class SymbolTable(object):
+    """Table storing symbols by names."""
+    def __init__(self):
+        self.vars = {}
+        self.params = {}
+        self.const_ctr = 1
+        self.in_padding = False
+        self.paddings = [0, 0]
+
+    def new_const(self, value):
+        name = "_param_%d" % (self.const_ctr)
+        self.const_ctr += 1
+        self.params[name] = value
+        self.vars[name] = _sym.Variable(name=name)
+        return self.vars[name]
+
+    def get_var(self, name, must_contain=True):
+        if must_contain:
+            assert name in self.vars
+        if name not in self.vars:
+            self.vars[name] = _sym.Variable(name=name)
+        return self.vars[name]
+
+    def set_var(self, name, sym):
+        assert isinstance(sym, _sym.Symbol)
+        self.vars[name] = sym
+
+    def set_padding(self, paddings):
+        self.paddings = paddings
+        self.in_padding = True
+
+    def clear_padding(self):
+        self.in_padding = False
diff --git a/nnvm/python/nnvm/frontend/coreml.py b/nnvm/python/nnvm/frontend/coreml.py
index 67c2d92f2741..8bf949ea5c96 100644
--- a/nnvm/python/nnvm/frontend/coreml.py
+++ b/nnvm/python/nnvm/frontend/coreml.py
@@ -4,44 +4,11 @@
 import tvm
 import numpy as np
 from .. import symbol as _sym
+from .common import SymbolTable
 
 __all__ = ['from_coreml']
 
 
-class SymbolTable(object):
-    """Table storing symbols by names."""
-    def __init__(self):
-        self.vars = {}
-        self.params = {}
-        self.const_ctr = 1
-        self.in_padding = False
-        self.paddings = [0, 0]
-
-    def new_const(self, value):
-        name = "_param_%d" % (self.const_ctr)
-        self.const_ctr += 1
-        self.params[name] = value
-        self.vars[name] = _sym.Variable(name=name)
-        return self.vars[name]
-
-    def get_var(self, name, must_contain=True):
-        if must_contain:
-            assert name in self.vars
-        if name not in self.vars:
-            self.vars[name] = _sym.Variable(name=name)
-        return self.vars[name]
-
-    def set_var(self, name, sym):
-        assert isinstance(sym, _sym.Symbol)
-        self.vars[name] = sym
-
-    def set_padding(self, paddings):
-        self.paddings = paddings
-        self.in_padding = True
-
-    def clear_padding(self):
-        self.in_padding = False
-
 def NeuralNetworkImageScaler(op, insym, symtab):
     # this changes the symbol
     biases = np.array([op.blueBias, op.greenBias, op.redBias]).reshape([3, 1, 1])
diff --git a/nnvm/python/nnvm/frontend/keras.py b/nnvm/python/nnvm/frontend/keras.py
new file mode 100644
index 000000000000..b2498488af08
--- /dev/null
+++ b/nnvm/python/nnvm/frontend/keras.py
@@ -0,0 +1,465 @@
+# pylint: disable=invalid-name, import-self
+"""Keras frontend."""
+from __future__ import absolute_import as _abs
+import sys
+import numpy as np
+import tvm
+from .. import symbol as _sym
+from .common import SymbolTable
+
+__all__ = ['from_keras']
+
+
+def _check_data_format(keras_layer):
+    if hasattr(keras_layer, ('data_format')):
+        if keras_layer.data_format != 'channels_last':
+            raise ValueError("Keras frontend currently supports data_format = channels_last only.")
+
+
+def _convert_activation(insym, keras_layer, _):
+    if isinstance(keras_layer, (str, unicode)):
+        act_type = keras_layer
+    else:
+        if sys.version_info.major < 3:
+            act_type = keras_layer.activation.func_name
+        else:
+            act_type = keras_layer.activation.__name__
+    if act_type == 'linear':
+        if isinstance(keras_layer, (str, unicode)):
+            return insym
+        return _sym.__add_scalar__(_sym.__mul_scalar__(insym, \
+            scalar=keras_layer.alpha), scalar=keras_layer.beta)
+    elif act_type == 'softmax':
+        return _sym.softmax(insym)
+    elif act_type == 'sigmoid':
+        return _sym.sigmoid(insym)
+    elif act_type == 'tanh':
+        return _sym.tanh(insym)
+    elif act_type == 'relu':
+        return _sym.relu(insym)
+    elif act_type == 'softplus':
+        return _sym.log(_sym.__add_scalar__(_sym.exp(insym), scalar=1))
+    elif act_type == 'elu':
+        raise NotImplementedError('elu not implemented')
+    elif act_type == 'relu6':
+        raise NotImplementedError('relu6 not implemented')
+    elif act_type == 'softsign':
+        raise NotImplementedError('softsign not implemented')
+    elif act_type == 'hard_sigmoid':
+        raise NotImplementedError('hard_sigmoid not implemented')
+    else:
+        raise TypeError("Unsupported activation type : {}".format(act_type))
+
+
+def _convert_advanced_activation(insym, keras_layer, _):
+    act_type = type(keras_layer).__name__
+    if act_type == 'LeakyReLU':
+        return _sym.leaky_relu(insym, alpha=keras_layer.alpha)
+    elif act_type == 'ELU':
+        raise NotImplementedError('ELU not implemented')
+    elif act_type == 'PReLU':
+        raise NotImplementedError('PReLU not implemented')
+    elif act_type == 'ThresholdedReLU':
+        raise NotImplementedError('ThresholdedReLU not implemented')
+    else:
+        raise TypeError("Unsupported advanced activation type : {}".format(act_type))
+
+
+def _convert_merge(insym, keras_layer, _):
+    merge_type = type(keras_layer).__name__
+    if merge_type == 'Add':
+        return _sym.elemwise_add(insym[0], insym[1])
+    elif merge_type == 'Subtract':
+        return _sym.elemwise_sub(insym[0], insym[1])
+    elif merge_type == 'Multiply':
+        return _sym.elemwise_mul(insym[0], insym[1])
+    elif merge_type == 'Average':
+        raise NotImplementedError('Average merge not implemented')
+    elif merge_type == 'Maximum':
+        raise NotImplementedError('Maximum merge not implemented')
+    else:
+        raise TypeError("Unsupported merge type : {}".format(merge_type))
+
+
+def _convert_dense(insym, keras_layer, symtab):
+    weightList = keras_layer.get_weights()
+    weight = symtab.new_const(weightList[0].transpose([1, 0]))
+    params = {'weight':weight, 'use_bias':False, 'units':weightList[0].shape[1]}
+    if keras_layer.use_bias:
+        params['use_bias'] = True
+        params['bias'] = symtab.new_const(weightList[1])
+    out = _sym.dense(data=insym, **params)
+    # defuse activation
+    if sys.version_info.major < 3:
+        act_type = keras_layer.activation.func_name
+    else:
+        act_type = keras_layer.activation.__name__
+    if act_type != 'linear':
+        out = _convert_activation(out, act_type, symtab)
+    return out
+
+
+def _convert_convolution(insym, keras_layer, symtab):
+    _check_data_format(keras_layer)
+    is_deconv = type(keras_layer).__name__ == 'Conv2DTranspose'
+    is_depthconv = type(keras_layer).__name__ == 'DepthwiseConv2D'
+    weightList = keras_layer.get_weights()
+    if is_deconv:
+        kernel_h, kernel_w, n_filters, in_channels = weightList[0].shape
+        weight = weightList[0].transpose([3, 2, 0, 1])
+    elif is_depthconv:
+        kernel_h, kernel_w, in_channels, depth_mult = weightList[0].shape
+        weight = weightList[0].transpose([2, 3, 0, 1])
+    else:
+        kernel_h, kernel_w, in_channels, n_filters = weightList[0].shape
+        weight = weightList[0].transpose([3, 2, 0, 1])
+    dilation = [1, 1]
+    if isinstance(keras_layer.dilation_rate, (list, tuple)):
+        dilation = [keras_layer.dilation_rate[0], keras_layer.dilation_rate[1]]
+    else:
+        dilation = [keras_layer.dilation_rate, keras_layer.dilation_rate]
+    stride_h, stride_w = keras_layer.strides
+    params = {'weight': symtab.new_const(weight),
+              'kernel_size': [kernel_h, kernel_w],
+              'strides': [stride_h, stride_w],
+              'dilation': dilation,
+              'padding': [0, 0],
+              'use_bias': False}
+    if is_depthconv:
+        params['channels'] = in_channels * depth_mult
+        params['groups'] = in_channels
+    else:
+        params['channels'] = n_filters
+    if keras_layer.use_bias:
+        params['use_bias'] = True
+        params['bias'] = symtab.new_const(weightList[1])
+    if keras_layer.padding == 'valid':
+        pass
+    # we insert a separate pad operator
+    elif keras_layer.padding == 'same':
+        in_h = keras_layer.input.shape[1].value
+        in_w = keras_layer.input.shape[2].value
+        out_h = (in_h + stride_h - 1) // stride_h
+        out_w = (in_w + stride_w - 1) // stride_w
+        pad_h = np.maximum((out_h - 1) * stride_h + kernel_h - in_h, 0)
+        pad_w = np.maximum((out_w - 1) * stride_w + kernel_w - in_w, 0)
+        pad_t = pad_h // 2
+        pad_l = pad_w // 2
+        pad_b = pad_h - pad_t
+        pad_r = pad_w - pad_l
+        insym = _sym.pad(data=insym, pad_width=((0, 0), (0, 0), (pad_t, pad_b), (pad_l, pad_r)))
+    else:
+        raise TypeError("Unsupported padding type : {}".format(keras_layer.padding))
+    if is_deconv:
+        out = _sym.conv2d_transpose(data=insym, **params)
+    else:
+        out = _sym.conv2d(data=insym, **params)
+    # defuse activation
+    if sys.version_info.major < 3:
+        act_type = keras_layer.activation.func_name
+    else:
+        act_type = keras_layer.activation.__name__
+    if act_type != 'linear':
+        out = _convert_activation(out, act_type, symtab)
+    return out
+
+
+def _convert_separable_convolution(insym, keras_layer, symtab):
+    _check_data_format(keras_layer)
+    weightList = keras_layer.get_weights()
+    # depthwise conv
+    kernel_h, kernel_w, in_channels, depth_mult = weightList[0].shape
+    stride_h, stride_w = keras_layer.strides
+    weight0 = weightList[0].transpose([2, 3, 0, 1])
+    params0 = {'weight': symtab.new_const(weight0),
+               'channels': in_channels * depth_mult,
+               'groups': in_channels,
+               'kernel_size': [kernel_h, kernel_w],
+               'strides': [stride_h, stride_w],
+               'dilation': [1, 1],
+               'padding': [0, 0],
+               'use_bias': False}
+    if keras_layer.padding == 'valid':
+        pass
+    # we insert a separate pad operator
+    elif keras_layer.padding == 'same':
+        in_h = keras_layer.input.shape[1].value
+        in_w = keras_layer.input.shape[2].value
+        out_h = (in_h + stride_h - 1) // stride_h
+        out_w = (in_w + stride_w - 1) // stride_w
+        pad_h = np.maximum((out_h - 1) * stride_h + kernel_h - in_h, 0)
+        pad_w = np.maximum((out_w - 1) * stride_w + kernel_w - in_w, 0)
+        pad_t = pad_h // 2
+        pad_l = pad_w // 2
+        pad_b = pad_h - pad_t
+        pad_r = pad_w - pad_l
+        insym = _sym.pad(data=insym, pad_width=(
+            (0, 0), (0, 0), (pad_t, pad_b), (pad_l, pad_r)))
+    else:
+        raise TypeError("Unsupported padding type : {}".format(keras_layer.padding))
+    depthconv = _sym.conv2d(data=insym, **params0)
+    # pointwise conv
+    weight1 = weightList[1].transpose([3, 2, 0, 1])
+    params1 = {'weight': symtab.new_const(weight1),
+               'channels': weight1.shape[0],
+               'groups': 1,
+               'kernel_size': [1, 1],
+               'strides': [1, 1],
+               'dilation': [1, 1],
+               'use_bias': False}
+    if keras_layer.use_bias:
+        params1['use_bias'] = True
+        params1['bias'] = symtab.new_const(weightList[2])
+    out = _sym.conv2d(data=depthconv, **params1)
+    # defuse activation
+    if sys.version_info.major < 3:
+        act_type = keras_layer.activation.func_name
+    else:
+        act_type = keras_layer.activation.__name__
+    if act_type != 'linear':
+        out = _convert_activation(out, act_type, symtab)
+    return out
+
+
+def _convert_flatten(insym, keras_layer, _):
+    _check_data_format(keras_layer)
+    # NCHW -> NHWC so that dense can be correctly converted
+    insym = _sym.transpose(insym, axes=[0, 2, 3, 1])
+    return _sym.flatten(insym)
+
+
+def _convert_pooling(insym, keras_layer, symtab):
+    _check_data_format(keras_layer)
+    pool_type = type(keras_layer).__name__
+    # global pool in keras = global pool + flatten in nnvm
+    if pool_type == 'GlobalMaxPooling2D':
+        return _convert_flatten(_sym.global_max_pool2d(insym), keras_layer, symtab)
+    elif pool_type == 'GlobalAveragePooling2D':
+        return _convert_flatten(_sym.global_avg_pool2d(insym), keras_layer, symtab)
+    else:
+        pool_h, pool_w = keras_layer.pool_size
+        stride_h, stride_w = keras_layer.strides
+        params = {'pool_size': [pool_h, pool_w],
+                  'strides': [stride_h, stride_w]}
+        if keras_layer.padding == 'valid':
+            params['padding'] = [0, 0]
+        elif keras_layer.padding == 'same':
+            in_h = keras_layer.input.shape[1].value
+            in_w = keras_layer.input.shape[2].value
+            out_h = (in_h + stride_h - 1) // stride_h
+            out_w = (in_w + stride_w - 1) // stride_w
+            pad_h = np.maximum((out_h - 1) * stride_h + pool_h - in_h, 0)
+            pad_w = np.maximum((out_w - 1) * stride_w + pool_w - in_w, 0)
+            pad_t = pad_h // 2
+            pad_l = pad_w // 2
+            pad_b = pad_h - pad_t
+            pad_r = pad_w - pad_l
+            params['padding'] = [pad_t, pad_l]
+            if pad_b > pad_t and pad_r > pad_l:
+                params['ceil_mode'] = True
+        else:
+            raise TypeError("Unsupported padding type : {}".format(keras_layer.padding))
+        if pool_type == 'MaxPooling2D':
+            return _sym.max_pool2d(insym, **params)
+        elif pool_type == 'AveragePooling2D':
+            # TODO: in keras, padded zeros are not calculated
+            return _sym.avg_pool2d(insym, **params)
+        else:
+            raise TypeError("Unsupported pooling type : {}".format(keras_layer))
+
+
+def _convert_batchnorm(insym, keras_layer, symtab):
+    params = {'scale': False,
+              'center': False,
+              'epsilon': keras_layer.epsilon}
+    idx = 0
+    if keras_layer.scale:
+        params['scale'] = True
+        gamma = keras_layer.get_weights()[idx]
+        params['gamma'] = symtab.new_const(gamma)
+        idx += 1
+    if keras_layer.center:
+        params['center'] = True
+        beta = keras_layer.get_weights()[idx]
+        params['beta'] = symtab.new_const(beta)
+        idx += 1
+    moving_mean = keras_layer.get_weights()[idx]
+    moving_var = keras_layer.get_weights()[idx + 1]
+    params['moving_mean'] = symtab.new_const(moving_mean)
+    params['moving_var'] = symtab.new_const(moving_var)
+    return _sym.batch_norm(data=insym, **params)
+
+
+def _convert_padding(insym, keras_layer, _):
+    _check_data_format(keras_layer)
+    padding_type = type(keras_layer).__name__
+    padding = keras_layer.padding
+    top = left = bottom = right = 0
+    if padding_type == 'ZeroPadding2D':
+        if isinstance(padding, int):
+            top = left = bottom = right = padding
+        elif isinstance(padding, tuple):
+            if isinstance(padding[0], int):
+                top, left = padding
+                bottom, right = padding
+            elif isinstance(padding[0], tuple):
+                top, bottom = padding[0]
+                left, right = padding[1]
+            else:
+                raise ValueError("Unrecognized padding option: {}".format(str(padding)))
+        else:
+            raise ValueError("Unrecognized padding option: {}".format(str(padding)))
+    elif padding_type == 'ZeroPadding1D':
+        raise NotImplementedError("ZeroPadding1D not implemented")
+    else:
+        raise ValueError("Unrecognized padding type: {}".format(padding_type))
+    return _sym.pad(data=insym, pad_width=((0, 0), (0, 0), (top, bottom), (left, right)))
+
+
+def _convert_concat(insym, keras_layer, _):
+    _check_data_format(keras_layer)
+    if not isinstance(insym, list):
+        insym = [insym]
+    return _sym.concatenate(*insym, axis=1)
+
+
+def _convert_reshape(insym, keras_layer, _):
+    return _sym.reshape(insym, keras_layer.shape)
+
+
+def _default_skip(insym, keras_layer, _): # pylint: disable=unused-argument
+    """Layers that can be skipped because they are train time only."""
+    return
+
+
+_convert_map = {
+    'Dense'                    : _convert_dense,
+    'Activation'               : _convert_activation,
+    'LeakyReLU'                : _convert_advanced_activation,
+    'PReLU'                    : _convert_advanced_activation,
+    'ELU'                      : _convert_advanced_activation,
+    'ThresholdedReLU'          : _convert_advanced_activation,
+
+    'AveragePooling2D'         : _convert_pooling,
+    'MaxPooling2D'             : _convert_pooling,
+    'GlobalAveragePooling2D'   : _convert_pooling,
+    'GlobalMaxPooling2D'       : _convert_pooling,
+    'Conv2D'                   : _convert_convolution,
+    'Conv2DTranspose'          : _convert_convolution,
+    'DepthwiseConv2D'          : _convert_convolution,
+    'SeparableConv2D'          : _convert_separable_convolution,
+
+    'Flatten'                  : _convert_flatten,
+    'Reshape'                  : _convert_reshape,
+    'Concatenate'              : _convert_concat,
+    'BatchNormalization'       : _convert_batchnorm,
+
+    'Add'                      : _convert_merge,
+    'Subtract'                 : _convert_merge,
+    'Multiply'                 : _convert_merge,
+    'ZeroPadding2D'            : _convert_padding,
+
+    # 'ZeroPadding1D'          : _convert_padding,
+    # 'AveragePooling1D'       : _convert_pooling,
+    # 'MaxPooling1D'           : _convert_pooling,
+    # 'GlobalAveragePooling1D' : _convert_pooling,
+    # 'GlobalMaxPooling1D'     : _convert_pooling,
+    # 'Cropping1D'             : _convert_cropping,
+    # 'Cropping2D'             : _convert_cropping,
+    # 'UpSampling1D'           : _convert_upsample,
+    # 'UpSampling2D'           : _convert_upsample,
+    # 'Conv1D'                 : _convert_convolution1d,
+
+    # 'GRU'                    : _convert_gru,
+    # 'LSTM'                   : _convert_lstm,
+    # 'SimpleRNN'              : _convert_simple_rnn,
+    # 'Bidirectional'          : _convert_bidirectional,
+    # 'TimeDistributed'        : _default_skip,
+
+    # 'Average'                : _convert_merge,
+    # 'Maximum'                : _convert_merge,
+    # 'Dot'                    : _convert_merge,
+    # 'Permute'                : _convert_permute,
+    # 'Embedding'              : _convert_embedding,
+    # 'RepeatVector'           : _convert_repeat_vector,
+
+    'InputLayer'               : _default_skip,
+    'Dropout'                  : _default_skip,
+    'SpatialDropout2D'         : _default_skip,
+    'SpatialDropout1D'         : _default_skip,
+}
+
+
+def _check_unsupported_layers(model):
+    for layer in model.layers:
+        if type(layer).__name__ not in _convert_map:
+            raise ValueError("Keras layer {} not supported.".format(type(layer).__name__))
+
+
+def keras_op_to_nnvm(insym, keras_layer, outname, symtab):
+    """Convert keras layer to nnvm symbol, and update symtab.
+
+    Parameters
+    ----------
+    insym : nnvm.symbol.Symbol or a list of it
+        The input nnvm symbol(s)
+
+    keras_layer : keras.layers
+        The keras layer to be converted
+
+    outname : str
+        Name of the output nnvm symbol
+
+    symtab : nnvm.frontend.common.SymbolTable
+        The global symbol table to be updated
+    """
+    if type(keras_layer).__name__ not in _convert_map:
+        raise NotImplementedError("{} is not supported".format((type(keras_layer).__name__)))
+    ret = _convert_map[type(keras_layer).__name__](insym, keras_layer, symtab)
+    symtab.set_var(outname, ret)
+
+
+def from_keras(model):
+    """Convert keras model to NNVM format.
+
+    Parameters
+    ----------
+    model : keras.engine.training.Model
+        The keras model to be converted
+
+    Returns
+    -------
+    sym : nnvm.Symbol
+        Compatible nnvm symbol
+
+    params : dict of str to tvm.NDArray
+        The parameter dict to be used by nnvm
+    """
+    try:
+        import keras
+    except ImportError:
+        raise ImportError('Keras must be installed')
+    assert isinstance(model, keras.engine.training.Model)
+    if keras.backend.image_data_format() != 'channels_last':
+        raise ValueError("Keras frontend currently supports data_format = channels_last only.")
+    _check_unsupported_layers(model)
+
+    symtab = SymbolTable()
+    for keras_layer in model.layers:
+        if isinstance(keras_layer, keras.engine.topology.InputLayer):
+            keras_layer.name = 'data'
+            symtab.get_var(keras_layer.name, must_contain=False)
+        else:
+            predecessors = []
+            for node in keras_layer.inbound_nodes:
+                for pred in node.inbound_layers:
+                    predecessors.append(pred.name)
+            if len(predecessors) == 1:
+                insym = symtab.get_var(predecessors[0], must_contain=True)
+            else:
+                insym = [symtab.get_var(pred, must_contain=True) for pred in predecessors]
+            keras_op_to_nnvm(insym, keras_layer, keras_layer.name, symtab)
+
+    returns = [symtab.get_var(i.name, must_contain=False) for i in model.output_layers]
+    tvmparams = {k:tvm.nd.array(np.array(v, dtype=np.float32)) for k, v in symtab.params.items()}
+    return returns[0], tvmparams
diff --git a/nnvm/python/nnvm/top/attr_dict.py b/nnvm/python/nnvm/top/attr_dict.py
index 5dece6b1e980..453c2971f73f 100644
--- a/nnvm/python/nnvm/top/attr_dict.py
+++ b/nnvm/python/nnvm/top/attr_dict.py
@@ -52,6 +52,22 @@ def get_int_tuple(self, key):
         """
         return tuple(int(x) for x in self[key][1:-1].split(",") if x)
 
+    def get_int_pair_tuple(self, key):
+        """Get tuple of integer pairs from attr dict
+
+        Parameters
+        ----------
+        key : str
+            The attr key
+
+        Returns
+        -------
+        tuple : tuple of int pairs
+            The result tuple
+        """
+        flat = [int(x.strip(' [] ')) for x in self[key][1:-1].split(",")]
+        return tuple((flat[i], flat[i+1]) for i in range(0, len(flat), 2))
+
     def get_int(self, key):
         """Get integer from attr dict
 
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index ceb93d213426..5e2dae97f7fd 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -39,6 +39,22 @@ def compute_flatten(attrs, inputs, _):
 reg.register_pattern("flatten", OpPattern.INJECTIVE)
 
 
+# pad
+@reg.register_compute("pad")
+def compute_pad(attrs, inputs, _):
+    """Compute definition of pad"""
+    pad_width = attrs.get_int_pair_tuple('pad_width')
+    assert len(pad_width) == len(inputs[0].shape) and \
+        len(pad_width[0]) == 2, "illegal pad_width"
+    pad_before = [x[0] for x in pad_width]
+    pad_after = [x[1] for x in pad_width]
+    pad_value = attrs.get_int('pad_value')
+    return topi.nn.pad(inputs[0], pad_before, pad_after, pad_value)
+
+reg.register_schedule("pad", _fschedule_broadcast)
+reg.register_pattern("pad", OpPattern.INJECTIVE)
+
+
 # softmax
 @reg.register_compute("softmax")
 def compute_softmax(attrs, inputs, _):
@@ -53,9 +69,9 @@ def schedule_softmax(_, outs, target):
     with tvm.target.create(target):
         return topi.generic.schedule_softmax(outs)
 
-
 reg.register_pattern("softmax", OpPattern.OPAQUE)
 
+
 # log softmax
 @reg.register_compute("log_softmax")
 def compute_log_softmax(attrs, inputs, _):
@@ -73,6 +89,7 @@ def schedule_log_softmax(_, outs, target):
 # Mark softmax as extern as we do not fuse it in call cases
 reg.register_pattern("log_softmax", OpPattern.OPAQUE)
 
+
 # dense
 @reg.register_compute("dense")
 def compute_dense(attrs, inputs, _):
diff --git a/nnvm/src/top/nn/nn.cc b/nnvm/src/top/nn/nn.cc
index cc0f5d1ea381..f8c1aa67c89a 100644
--- a/nnvm/src/top/nn/nn.cc
+++ b/nnvm/src/top/nn/nn.cc
@@ -254,5 +254,42 @@ NNVM_REGISTER_OP(leaky_relu)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
 .set_support_level(1);
 
+
+DMLC_REGISTER_PARAMETER(PadParam);
+
+inline bool PadInferShape(const nnvm::NodeAttrs& attrs,
+                          std::vector<TShape>* in_shape,
+                          std::vector<TShape>* out_shape) {
+  const PadParam& param = nnvm::get<PadParam>(attrs.parsed);
+  CHECK_EQ(in_shape->size(), 1U);
+  CHECK_EQ(out_shape->size(), 1U);
+  TShape dshape = (*in_shape)[0];
+  if (dshape.ndim() == 0) return false;
+  CHECK_EQ(param.pad_width.ndim(), dshape.ndim());
+  CHECK_EQ(param.pad_width[0].ndim(), 2U);
+  TShape oshape = dshape;
+  for (uint32_t i = 0; i < dshape.ndim(); i++) {
+    int pad_before = param.pad_width[i][0];
+    int pad_after = param.pad_width[i][1];
+    oshape[i] = dshape[i] + pad_before + pad_after;
+  }
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, oshape);
+  return true;
+}
+
+NNVM_REGISTER_OP(pad)
+.describe(R"code(Pad for n-D tensor.
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "n-D Tensor", "Input data.")
+.add_arguments(PadParam::__FIELDS__())
+.set_attr_parser(ParamParser<PadParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<PadParam>)
+.set_num_outputs(1)
+.set_num_inputs(1)
+.set_attr<FInferShape>("FInferShape", PadInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_support_level(1);
+
 }  // namespace top
 }  // namespace nnvm
diff --git a/nnvm/tests/python/compiler/test_top_level1.py b/nnvm/tests/python/compiler/test_top_level1.py
index 26ff34eb7ecb..33db05cc1de5 100644
--- a/nnvm/tests/python/compiler/test_top_level1.py
+++ b/nnvm/tests/python/compiler/test_top_level1.py
@@ -244,6 +244,25 @@ def test_squeeze():
     verify_squeeze((1, 3, 1), axis=0)
     verify_squeeze((1, 3, 2, 5, 1), axis=-1)
 
+
+def test_pad():
+    x = sym.Variable("x")
+    y = sym.pad(x, pad_width=((0, 0), (0, 0), (0, 1), (2, 3)), pad_value=1.)
+    dtype = "float32"
+    dshape = (1, 3, 28, 28)
+    oshape = (1, 3, 29, 33)
+    shape_dict = {"x": dshape}
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
+        m = graph_runtime.create(graph, lib, ctx)
+        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+        m.run(x=data)
+        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
+        b_np = np.pad(data.asnumpy(), pad_width=((0, 0), (0, 0), (0, 1), (2, 3)),
+            mode='constant', constant_values=1.)
+        np.testing.assert_allclose(out.asnumpy(), b_np, rtol=1e-5)
+
+
 if __name__ == "__main__":
     test_split()
     test_concatenate()
@@ -257,3 +276,4 @@ def test_squeeze():
     test_sigmoid()
     test_softmax()
     test_squeeze()
+    test_pad()
diff --git a/nnvm/tests/python/frontend/keras/test_forward.py b/nnvm/tests/python/frontend/keras/test_forward.py
new file mode 100644
index 000000000000..4c69b6da15df
--- /dev/null
+++ b/nnvm/tests/python/frontend/keras/test_forward.py
@@ -0,0 +1,117 @@
+import numpy as np
+import nnvm
+import tvm
+from tvm.contrib import graph_runtime
+from nnvm.testing.config import ctx_list
+import keras
+
+# prevent keras from using up all gpu memory
+import tensorflow as tf
+from keras.backend.tensorflow_backend import set_session
+config = tf.ConfigProto()
+config.gpu_options.per_process_gpu_memory_fraction = 0.5
+set_session(tf.Session(config=config))
+
+
+def verify_keras_frontend(keras_model):
+    in_shape = [dim.value if dim.value is not None else 1 for dim in keras_model.input_layers[0].input.shape]
+    out_shape = [dim.value if dim.value is not None else 1 for dim in keras_model.output_layers[0].output.shape]
+
+    def get_keras_output(x, dtype='float32'):
+        return keras_model.predict(x)
+
+    def get_tvm_output(x, target, ctx, input_name='data', dtype='float32'):
+        sym, params = nnvm.frontend.from_keras(keras_model)
+        shape_dict = {input_name : x.shape}
+        with nnvm.compiler.build_config(opt_level=2):
+            graph, lib, params = nnvm.compiler.build(sym, target, shape_dict, params=params)
+        m = graph_runtime.create(graph, lib, ctx)
+        m.set_input(input_name, tvm.nd.array(x.astype(dtype)))
+        m.set_input(**params)
+        m.run()
+        out = m.get_output(0, tvm.nd.empty(out_shape, dtype))
+        return out.asnumpy()
+
+    x = np.random.uniform(size=in_shape)
+    keras_out = get_keras_output(x)
+    for target, ctx in ctx_list():
+        tvm_out = get_tvm_output(x.transpose([0,3,1,2]), target, ctx)
+        np.testing.assert_allclose(keras_out, tvm_out, rtol=1e-5, atol=1e-5)
+
+
+def verify_forward_softrelu():
+    data = keras.layers.Input(shape=(32,32,3))
+    x = keras.layers.Activation('softplus')(data)
+    x = keras.layers.Concatenate()([x, x])
+    x = keras.layers.GlobalMaxPooling2D()(x)
+    keras_model = keras.models.Model(data, x)
+    verify_keras_frontend(keras_model)
+
+
+def verify_forward_leaky_relu():
+    data = keras.layers.Input(shape=(32,32,3))
+    x = keras.layers.LeakyReLU(alpha=0.3)(data)
+    x = keras.layers.Add()([x, x])
+    x = keras.layers.GlobalAveragePooling2D()(x)
+    keras_model = keras.models.Model(data, x)
+    verify_keras_frontend(keras_model)
+
+
+def verify_forward_dense():
+    data = keras.layers.Input(shape=(32,32,3))
+    x = keras.layers.MaxPooling2D(pool_size=(2,2))(data)
+    x = keras.layers.Flatten()(x)
+    x = keras.layers.Dense(10, activation='relu', kernel_initializer='uniform')(x)
+    keras_model = keras.models.Model(data, x)
+    verify_keras_frontend(keras_model)
+
+
+def verify_forward_transpose_conv():
+    data = keras.layers.Input(shape=(32,32,3))
+    x = keras.layers.Conv2D(filters=10, kernel_size=(3,3), strides=(2,2), padding='same')(data)
+    x = keras.applications.mobilenet.DepthwiseConv2D(kernel_size=(3,3), padding='same')(x)
+    x = keras.layers.Conv2DTranspose(filters=64, kernel_size=(3,3), padding='valid')(x)
+    x = keras.layers.GlobalMaxPooling2D()(x)
+    keras_model = keras.models.Model(data, x)
+    verify_keras_frontend(keras_model)
+
+
+def verify_forward_separable_conv():
+    data = keras.layers.Input(shape=(32,32,3))
+    x = keras.layers.SeparableConv2D(filters=10, kernel_size=(3,3),
+        padding='same', activation='relu')(data)
+    x = keras.layers.BatchNormalization(scale=True, center=False,
+        beta_initializer='uniform', gamma_initializer='uniform')(x)
+    x = keras.layers.GlobalAveragePooling2D()(x)
+    keras_model = keras.models.Model(data, x)
+    verify_keras_frontend(keras_model)
+
+
+def verify_forward_vgg16():
+    keras_model = keras.applications.vgg16.VGG16(include_top=True, weights='imagenet',
+        input_shape=(224,224,3), classes=1000)
+    verify_keras_frontend(keras_model)
+
+
+def verify_forward_xception():
+    keras_model = keras.applications.xception.Xception(include_top=True, weights='imagenet',
+        input_shape=(299,299,3), classes=1000)
+    verify_keras_frontend(keras_model)
+
+
+def verify_forward_resnet50():
+    keras_model = keras.applications.resnet50.ResNet50(include_top=True, weights='imagenet',
+        input_shape=(224,224,3), classes=1000)
+    verify_keras_frontend(keras_model)
+
+
+if __name__ == '__main__':
+    verify_forward_softrelu()
+    verify_forward_leaky_relu()
+    verify_forward_dense()
+    verify_forward_transpose_conv()
+    verify_forward_separable_conv()
+
+    verify_forward_vgg16()
+    verify_forward_xception()
+    verify_forward_resnet50()

From 655504142ef190d1bf5d93ded14cc5b2d2f413ee Mon Sep 17 00:00:00 2001
From: Yuwei Hu <huyuwei1995@gmail.com>
Date: Thu, 21 Dec 2017 22:49:08 +0800
Subject: [PATCH 568/949] keras frontend tutorial (#278)

* keras frontend tutorial

* fix
---
 nnvm/docs/api/python/frontend.rst             |   7 +-
 nnvm/python/nnvm/frontend/coreml.py           |   6 -
 nnvm/python/nnvm/frontend/onnx.py             |   2 +-
 nnvm/src/top/nn/nn.cc                         |   2 +-
 nnvm/tests/ci_build/Dockerfile.gpu            |   3 +
 .../ci_build/install/ubuntu_install_keras.sh  |   1 +
 nnvm/tutorials/from_keras.py                  | 114 ++++++++++++++++++
 7 files changed, 126 insertions(+), 9 deletions(-)
 create mode 100644 nnvm/tests/ci_build/install/ubuntu_install_keras.sh
 create mode 100644 nnvm/tutorials/from_keras.py

diff --git a/nnvm/docs/api/python/frontend.rst b/nnvm/docs/api/python/frontend.rst
index d1a0ddbd13e5..f872a6b878e2 100644
--- a/nnvm/docs/api/python/frontend.rst
+++ b/nnvm/docs/api/python/frontend.rst
@@ -3,5 +3,10 @@ nnvm.frontend
 
 .. automodule:: nnvm.frontend
 
-
 .. autofunction:: nnvm.frontend.from_mxnet
+
+.. autofunction:: nnvm.frontend.from_onnx
+
+.. autofunction:: nnvm.frontend.from_coreml
+
+.. autofunction:: nnvm.frontend.from_keras
diff --git a/nnvm/python/nnvm/frontend/coreml.py b/nnvm/python/nnvm/frontend/coreml.py
index 8bf949ea5c96..fb942fb18448 100644
--- a/nnvm/python/nnvm/frontend/coreml.py
+++ b/nnvm/python/nnvm/frontend/coreml.py
@@ -293,12 +293,6 @@ def from_coreml(model):
     model:
         coremltools.models.MLModel of a NeuralNetworkClassifier
 
-    arg_params : dict of str to mx.NDArray
-        The argument parameters in mxnet
-
-    aux_params : dict of str to mx.NDArray
-        The auxiliary parameters in mxnet
-
     Returns
     -------
     sym : nnvm.Symbol
diff --git a/nnvm/python/nnvm/frontend/onnx.py b/nnvm/python/nnvm/frontend/onnx.py
index c4db5599fbd1..c3a40ef89650 100644
--- a/nnvm/python/nnvm/frontend/onnx.py
+++ b/nnvm/python/nnvm/frontend/onnx.py
@@ -393,7 +393,7 @@ def _fix_outputs(self, op_name, outputs):
 
 
 def from_onnx(graph):
-    """Load onnx graph which is a python protobuf object in to nnvm graph.
+    """Load onnx graph which is a python protobuf object into nnvm graph.
     The companion parameters will be handled automatically.
     The inputs from onnx graph is vague, only providing "1", "2"...
     For convenience, we rename the `real` input names to "input_0",
diff --git a/nnvm/src/top/nn/nn.cc b/nnvm/src/top/nn/nn.cc
index f8c1aa67c89a..c9dde3ebb4ee 100644
--- a/nnvm/src/top/nn/nn.cc
+++ b/nnvm/src/top/nn/nn.cc
@@ -266,9 +266,9 @@ inline bool PadInferShape(const nnvm::NodeAttrs& attrs,
   TShape dshape = (*in_shape)[0];
   if (dshape.ndim() == 0) return false;
   CHECK_EQ(param.pad_width.ndim(), dshape.ndim());
-  CHECK_EQ(param.pad_width[0].ndim(), 2U);
   TShape oshape = dshape;
   for (uint32_t i = 0; i < dshape.ndim(); i++) {
+    CHECK_EQ(param.pad_width[i].ndim(), 2U);
     int pad_before = param.pad_width[i][0];
     int pad_after = param.pad_width[i][1];
     oshape[i] = dshape[i] + pad_before + pad_after;
diff --git a/nnvm/tests/ci_build/Dockerfile.gpu b/nnvm/tests/ci_build/Dockerfile.gpu
index 257865f155ab..bde32322ceaa 100644
--- a/nnvm/tests/ci_build/Dockerfile.gpu
+++ b/nnvm/tests/ci_build/Dockerfile.gpu
@@ -38,6 +38,9 @@ RUN bash /install/ubuntu_install_onnx.sh
 COPY install/ubuntu_install_coreml.sh /install/ubuntu_install_coreml.sh
 RUN bash /install/ubuntu_install_coreml.sh
 
+COPY install/ubuntu_install_keras.sh /install/ubuntu_install_keras.sh
+RUN bash /install/ubuntu_install_keras.sh
+
 RUN pip install Pillow
 
 # Environment variables
diff --git a/nnvm/tests/ci_build/install/ubuntu_install_keras.sh b/nnvm/tests/ci_build/install/ubuntu_install_keras.sh
new file mode 100644
index 000000000000..9730d83bf469
--- /dev/null
+++ b/nnvm/tests/ci_build/install/ubuntu_install_keras.sh
@@ -0,0 +1 @@
+pip2 install keras tensorflow h5py
diff --git a/nnvm/tutorials/from_keras.py b/nnvm/tutorials/from_keras.py
new file mode 100644
index 000000000000..0466d672c1eb
--- /dev/null
+++ b/nnvm/tutorials/from_keras.py
@@ -0,0 +1,114 @@
+"""
+Compile Keras Models
+=====================
+**Author**: `Yuwei Hu <https://Huyuwei.github.io/>`_
+
+This article is an introductory tutorial to deploy keras models with NNVM.
+
+For us to begin with, keras should be installed.
+Tensorflow is also required since it's used as the default backend of keras.
+
+A quick solution is to install via pip
+```
+pip install -U keras --user
+```
+```
+pip install -U tensorflow --user
+```
+or please refer to official site
+https://keras.io/#installation
+"""
+import nnvm
+import tvm
+import keras
+import numpy as np
+
+def download(url, path, overwrite=False):
+    import os
+    if os.path.isfile(path) and not overwrite:
+        print('File {} exists, skip.'.format(path))
+        return
+    print('Downloading from url {} to {}'.format(url, path))
+    try:
+        import urllib.request
+        urllib.request.urlretrieve(url, path)
+    except:
+        import urllib
+        urllib.urlretrieve(url, path)
+
+######################################################################
+# Load pretrained keras model
+# ----------------------------
+# We load a pretrained resnet-50 classification model provided by keras.
+weights_url = ''.join(['https://github.com/fchollet/deep-learning-models/releases/',
+                       'download/v0.2/resnet50_weights_tf_dim_ordering_tf_kernels.h5'])
+weights_file = 'resnet50_weights.h5'
+download(weights_url, weights_file)
+keras_resnet50 = keras.applications.resnet50.ResNet50(include_top=True, weights=None,
+	input_shape=(224,224,3), classes=1000)
+keras_resnet50.load_weights('resnet50_weights.h5')
+
+######################################################################
+# Load a test image
+# ------------------
+# A single cat dominates the examples!
+from PIL import Image
+from matplotlib import pyplot as plt
+from keras.applications.resnet50 import preprocess_input
+img_url = 'https://github.com/dmlc/mxnet.js/blob/master/data/cat.png?raw=true'
+download(img_url, 'cat.jpg')
+img = Image.open('cat.jpg').resize((224, 224))
+plt.imshow(img)
+plt.show()
+# input preprocess
+data = np.array(img)[np.newaxis, :].astype('float32')
+data = preprocess_input(data).transpose([0, 3, 1, 2])
+print('data', data.shape)
+
+######################################################################
+# Compile the model on NNVM
+# --------------------------
+# We should be familiar with the process now.
+
+# convert the keras model(NHWC layout) to NNVM format(NCHW layout).
+sym, params = nnvm.frontend.from_keras(keras_resnet50)
+# compile the model
+target = 'cuda'
+shape_dict = {'data': data.shape}
+with nnvm.compiler.build_config(opt_level=2):
+	graph, lib, params = nnvm.compiler.build(sym, target, shape_dict, params=params)
+
+######################################################################
+# Execute on TVM
+# ---------------
+# The process is no different from other examples.
+from tvm.contrib import graph_runtime
+ctx = tvm.gpu(0)
+m = graph_runtime.create(graph, lib, ctx)
+# set inputs
+m.set_input('data', tvm.nd.array(data.astype('float32')))
+m.set_input(**params)
+# execute
+m.run()
+# get outputs
+out_shape = (1000,)
+tvm_out = m.get_output(0, tvm.nd.empty(out_shape, 'float32')).asnumpy()
+top1_tvm = np.argmax(tvm_out)
+
+#####################################################################
+# Look up synset name
+# -------------------
+# Look up prdiction top 1 index in 1000 class synset.
+synset_url = ''.join(['https://gist.githubusercontent.com/zhreshold/',
+                      '4d0b62f3d01426887599d4f7ede23ee5/raw/',
+                      '596b27d23537e5a1b5751d2b0481ef172f58b539/',
+                      'imagenet1000_clsid_to_human.txt'])
+synset_name = 'synset.txt'
+download(synset_url, synset_name)
+with open(synset_name) as f:
+    synset = eval(f.read())
+print('NNVM top-1 id: {}, class name: {}'.format(top1_tvm, synset[top1_tvm]))
+# confirm correctness with keras output
+keras_out = keras_resnet50.predict(data.transpose([0, 2, 3, 1]))
+top1_keras = np.argmax(keras_out)
+print('Keras top-1 id: {}, class name: {}'.format(top1_keras, synset[top1_keras]))

From 39bf0867fa75857cb069f5731e0a30ad7b50d1d5 Mon Sep 17 00:00:00 2001
From: Yao Wang <kevinthesunwy@gmail.com>
Date: Thu, 21 Dec 2017 20:55:14 -0800
Subject: [PATCH 569/949] Add compile library tutorial (#277)

* Add compile library tutorial

* Clean output

* Refactor with sphinx gallery

* Refactor

* Change title and other minor fixes
---
 nnvm/tutorials/define_and_compile_model.py | 187 +++++++++++++++++++++
 1 file changed, 187 insertions(+)
 create mode 100644 nnvm/tutorials/define_and_compile_model.py

diff --git a/nnvm/tutorials/define_and_compile_model.py b/nnvm/tutorials/define_and_compile_model.py
new file mode 100644
index 000000000000..1c7b2bcaf89f
--- /dev/null
+++ b/nnvm/tutorials/define_and_compile_model.py
@@ -0,0 +1,187 @@
+"""
+Quick Start - End-to-End Tutorial for NNVM/TVM Pipeline
+=======================================================
+**Author**: `Yao Wang <https://github.com/kevinthesun>`_
+
+This example shows how to build a neural network with NNVM python frontend and
+generate runtime library for Nvidia GPU and Raspberry Pi with TVM. (Thanks to
+Tianqi's `tutorial for cuda <http://nnvm.tvmlang.org/tutorials/get_started.html>`_ and
+Ziheng's `tutorial for Raspberry Pi <http://nnvm.tvmlang.org/tutorials/deploy_model_on_rasp.html>`_)
+To run this notebook, you need to install tvm and nnvm following
+`these instructions <https://github.com/dmlc/nnvm/blob/master/docs/how_to/install.md>`_.
+Notice that you need to build tvm with cuda and llvm.
+"""
+
+######################################################################
+# Overview for Supported Hardware Backend of TVM
+# -----------------------------
+# The image below shows hardware backend currently supported by TVM:
+#
+# .. image:: https://github.com/dmlc/web-data/raw/master/tvm/tutorial/tvm_support_list.png
+#      :align: center
+#      :scale: 100%
+#
+# In this tutorial, we'll choose cuda and llvm as target backends.
+# To begin with, let's import NNVM and TVM.
+import tvm
+import nnvm.compiler
+import nnvm.testing
+
+
+######################################################################
+# Define Neural Network in NNVM
+# -----------------------------
+# First, let's define a neural network with nnvm python frontend.
+# For simplicity, we'll use pre-defined resnet-18 network in NNVM.
+# Parameters are initialized with Xavier initializer.
+# NNVM also supports other model formats such as MXNet, CoreML and ONNX.
+#
+# In this tutorial, we assume we will do inference on our device
+# and the batch size is set to be 1. Input images are RGB color
+# images of size 224 * 224. We can call the :any:`nnvm.symbol.debug_str`
+# to show the network structure.
+
+batch_size = 1
+num_class = 1000
+image_shape = (3, 224, 224)
+data_shape = (batch_size,) + image_shape
+out_shape = (batch_size, num_class)
+
+net, params = nnvm.testing.resnet.get_workload(batch_size=batch_size, image_shape=image_shape)
+print(net.debug_str())
+
+######################################################################
+# Compilation
+# ----------------------------
+# Next step is to compile the model using the NNVM/TVM pipeline.
+# Users can specify the optimization level of the compilation.
+# Currently this value can be 0 to 2, which corresponds to
+# "SimplifyInference", "OpFusion" and "PrecomputePrune" respectively.
+# In this example we set optimization level to be 0
+# and use Raspberry Pi as compile target.
+#
+# :any:`nnvm.compiler.build` returns three components: the execution graph in
+# json format, the TVM module library of compiled functions specifically
+# for this graph on the target hardware, and the parameter blobs of
+# the model. During the compilation, NNVM does the graph-level
+# optimization while TVM does the tensor-level optimization, resulting
+# in an optimized runtime module for model serving.
+#
+# We'll first compile for Nvidia GPU.
+
+opt_level = 0
+target = tvm.target.cuda()
+with nnvm.compiler.build_config(opt_level=opt_level):
+    graph, lib, params = nnvm.compiler.build(
+        net, target, shape={"data": data_shape}, params=params)
+
+######################################################################
+# Save Compiled Module
+# ----------------------------
+# After compilation, we can save the graph, lib and params into separate files
+# and deploy them to Nvidia GPU.
+
+from tvm.contrib import util
+
+temp = util.tempdir()
+path_lib = temp.relpath("deploy_lib.so")
+lib.export_library(path_lib)
+with open(temp.relpath("deploy_graph.json"), "w") as fo:
+    fo.write(graph.json())
+with open(temp.relpath("deploy_param.params"), "wb") as fo:
+    fo.write(nnvm.compiler.save_param_dict(params))
+print(temp.listdir())
+
+######################################################################
+# Deploy locally to Nvidia GPU
+# ------------------------------
+# Now we can load the module back.
+
+import numpy as np
+from tvm.contrib import graph_runtime
+
+loaded_lib = tvm.module.load(path_lib)
+loaded_json = open(temp.relpath("deploy_graph.json")).read()
+loaded_params = bytearray(open(temp.relpath("deploy_param.params"), "rb").read())
+module = graph_runtime.create(loaded_json, loaded_lib, tvm.gpu(0))
+module.load_params(loaded_params)
+
+input_data = tvm.nd.array(np.random.uniform(size=data_shape).astype("float32"))
+module.run(data=input_data)
+out = module.get_output(0, out=tvm.nd.empty(out_shape))
+# Print first 10 elements of output
+print(out.asnumpy()[0][0:10])
+
+######################################################################
+# Compile and Deploy the Model to Raspberry Pi Remotely with RPC
+# ------------------------------
+# Following the steps above, we can also compile the model for Raspberry Pi.
+# TVM provides rpc module to help with remote deploying.
+#
+# For demonstration, we simply start an RPC server on the same machine,
+# if :code:`use_rasp` is False. If you have set up the remote
+# environment, please change the three lines below: change the
+# :code:`use_rasp` to True, also change the host and port with your
+# device's host address and port number.
+
+# If we run the example locally for demonstration, we can simply set the 
+# compilation target as `llvm`. 
+# To run it on the Raspberry Pi, you need to specify its instruction set. 
+# `llvm -target=armv7l-none-linux-gnueabihf -mcpu=cortex-a53 -mattr=+neon`
+# is the recommended compilation configuration, thanks to Ziheng's work.
+
+from tvm.contrib import rpc
+
+use_rasp = False
+host = 'rasp0'
+port = 9090
+
+if not use_rasp:
+    # run server locally
+    host = 'localhost'
+    port = 9090
+    server = rpc.Server(host=host, port=port)
+
+# compile and save model library
+if use_rasp:
+    target = "llvm -target=armv7l-none-linux-gnueabihf -mcpu=cortex-a53 -mattr=+neon"
+else:
+    target = "llvm"
+# use `with tvm.target.rasp` for some target-specified optimization
+with tvm.target.rasp():
+    graph, lib, params = nnvm.compiler.build(
+        net, target, shape={"data": data_shape}, params=params)
+
+temp = util.tempdir()
+path_lib = temp.relpath("deploy_lib_rasp.o")
+lib.save(path_lib)
+
+# connect the server
+remote = rpc.connect(host, port)
+
+# upload the library to remote device and load it
+remote.upload(path_lib)
+rlib = remote.load_module('deploy_lib_rasp.o')
+
+ctx = remote.cpu(0)
+# upload the parameter
+rparams = {k: tvm.nd.array(v, ctx) for k, v in params.items()}
+
+# create the remote runtime module
+module = graph_runtime.create(graph, rlib, ctx)
+# set parameter
+module.set_input(**rparams)
+# set input data
+input_data = np.random.uniform(size=data_shape)
+module.set_input('data', tvm.nd.array(input_data.astype('float32')))
+# run
+module.run()
+
+out = module.get_output(0, out=tvm.nd.empty(out_shape, ctx=ctx))
+# Print first 10 elements of output
+print(out.asnumpy()[0][0:10])
+
+if not use_rasp:
+    # terminate the local server
+    server.terminate()
+

From 84e57598f5f2cf9d02542df914f1a33fc25fc597 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 23 Dec 2017 11:00:08 +0800
Subject: [PATCH 570/949] Update deploy_model_on_rasp.py

---
 nnvm/tutorials/deploy_model_on_rasp.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/tutorials/deploy_model_on_rasp.py b/nnvm/tutorials/deploy_model_on_rasp.py
index 1de7b4c56381..1aa84087167d 100644
--- a/nnvm/tutorials/deploy_model_on_rasp.py
+++ b/nnvm/tutorials/deploy_model_on_rasp.py
@@ -95,7 +95,7 @@
 if not use_rasp:
     # run server locally
     host = 'localhost'
-    port = 9090
+    port = 9091
     server = rpc.Server(host=host, port=port)
 
 ######################################################################

From 8d18241acf0e44b20321ca8a98898c043a2bed28 Mon Sep 17 00:00:00 2001
From: yuruofeifei <yuruofeifei@gmail.com>
Date: Fri, 22 Dec 2017 23:21:08 -0800
Subject: [PATCH 571/949] [GRADIENT] Add backward operator to enable backward
 graph  (#276)

* Update docs

* Add backward operator to enable backward graph

* Fix testing

* Refactor top level1 test code

* Fix format

* Test

* Added zeros ones op

* Register fill_like operator

* Fix unit test
---
 nnvm/docs/top.rst                             |   4 +
 nnvm/include/nnvm/top/tensor.h                |  40 ++-
 nnvm/python/nnvm/top/registry.py              |   2 +-
 nnvm/src/top/elemwise_op_common.h             |  10 +
 nnvm/src/top/op_common.h                      |  47 ++-
 nnvm/src/top/tensor/elemwise.cc               | 276 ++++++++++++++--
 nnvm/tests/python/compiler/test_top_level1.py | 295 ++++++++++--------
 7 files changed, 517 insertions(+), 157 deletions(-)

diff --git a/nnvm/docs/top.rst b/nnvm/docs/top.rst
index 6c07f6b08f31..f86820dd99c2 100644
--- a/nnvm/docs/top.rst
+++ b/nnvm/docs/top.rst
@@ -38,6 +38,8 @@ This level enables fully connected multi-layer perceptron.
    nnvm.symbol.elemwise_sub
    nnvm.symbol.elemwise_mul
    nnvm.symbol.elemwise_div
+   nnvm.symbol.fill
+   nnvm.symbol.fill_like
    nnvm.symbol.flatten
    nnvm.symbol.concatenate
    nnvm.symbol.expand_dims
@@ -111,6 +113,8 @@ Detailed Definitions
 .. autofunction:: nnvm.symbol.elemwise_sub
 .. autofunction:: nnvm.symbol.elemwise_mul
 .. autofunction:: nnvm.symbol.elemwise_div
+.. autofunction:: nnvm.symbol.fill
+.. autofunction:: nnvm.symbol.fill_like
 .. autofunction:: nnvm.symbol.flatten
 .. autofunction:: nnvm.symbol.concatenate
 .. autofunction:: nnvm.symbol.expand_dims
diff --git a/nnvm/include/nnvm/top/tensor.h b/nnvm/include/nnvm/top/tensor.h
index d37825e63a8f..9ebeeb01273f 100644
--- a/nnvm/include/nnvm/top/tensor.h
+++ b/nnvm/include/nnvm/top/tensor.h
@@ -62,21 +62,24 @@ enum TypeFlag {
   kUint64 = 10,
 };
 
+#define DMLC_DECLARE_DTYPE_FIELD(name)                              \
+  DMLC_DECLARE_FIELD(name)                                          \
+  .add_enum("float16", kFloat16)                                    \
+  .add_enum("float32", kFloat32)                                    \
+  .add_enum("float64", kFloat64)                                    \
+  .add_enum("uint8",  kUint8)                                       \
+  .add_enum("uint16", kUint16)                                      \
+  .add_enum("uint32", kUint32)                                      \
+  .add_enum("uint64", kUint64)                                      \
+  .add_enum("int8",  kInt8)                                         \
+  .add_enum("int16", kInt16)                                        \
+  .add_enum("int32", kInt32)                                        \
+  .add_enum("int64", kInt64)
+
 struct CastParam : public dmlc::Parameter<CastParam> {
   int dtype;
   DMLC_DECLARE_PARAMETER(CastParam) {
-    DMLC_DECLARE_FIELD(dtype)
-    .add_enum("float16", kFloat16)
-    .add_enum("float32", kFloat32)
-    .add_enum("float64", kFloat64)
-    .add_enum("uint8",  kUint8)
-    .add_enum("uint16", kUint16)
-    .add_enum("uint32", kUint32)
-    .add_enum("uint64", kUint64)
-    .add_enum("int8",  kInt8)
-    .add_enum("int16", kInt16)
-    .add_enum("int32", kInt32)
-    .add_enum("int64", kInt64)
+    DMLC_DECLARE_DTYPE_FIELD(dtype)
     .describe("Output data type.");
   }
 };
@@ -155,6 +158,19 @@ struct ReduceParam : public dmlc::Parameter<ReduceParam> {
   }
 };
 
+struct InitOpParam : public dmlc::Parameter<InitOpParam> {
+  TShape shape;
+  int dtype;
+  double value;
+
+  DMLC_DECLARE_PARAMETER(InitOpParam) {
+    DMLC_DECLARE_FIELD(shape).set_default(TShape());
+    DMLC_DECLARE_DTYPE_FIELD(dtype).set_default(kFloat32)
+      .describe("Target data type.");
+    DMLC_DECLARE_FIELD(value).describe("Value to fill");
+  }
+};
+
 }  // namespace top
 }  // namespace nnvm
 
diff --git a/nnvm/python/nnvm/top/registry.py b/nnvm/python/nnvm/top/registry.py
index 2861877e8db3..6a720944250b 100644
--- a/nnvm/python/nnvm/top/registry.py
+++ b/nnvm/python/nnvm/top/registry.py
@@ -7,7 +7,7 @@ class OpPattern(object):
 
     See Also
     --------
-    top.tag : Contains explaination of the tag type.
+    top.tag : Contains explanation of the tag type.
     """
     # Elementwise operator
     ELEMWISE = 0
diff --git a/nnvm/src/top/elemwise_op_common.h b/nnvm/src/top/elemwise_op_common.h
index a4d83e98bbd5..902c2fe41269 100644
--- a/nnvm/src/top/elemwise_op_common.h
+++ b/nnvm/src/top/elemwise_op_common.h
@@ -97,6 +97,16 @@ inline bool ElemwiseType(const NodeAttrs& attrs,
   .add_argument("data", "Tensor", "The input tensor.")
 
 
+#define NNVM_REGISTER_INIT_OP(name)                                 \
+  NNVM_REGISTER_OP(name)                                            \
+  .set_num_inputs(0)                                                \
+  .set_num_outputs(1)                                               \
+  .set_attr_parser(ParamParser<InitOpParam>)                        \
+  .add_arguments(InitOpParam::__FIELDS__())                         \
+  .set_attr<FInferShape>("FInferShape", ZeroShape)                  \
+  .set_attr<FInferType>("FInferType", ZeroType)
+
+
 #define NNVM_REGISTER_ELEMWISE_BINARY_OP(name)                      \
   NNVM_REGISTER_OP(name)                                            \
   .set_num_inputs(2)                                                \
diff --git a/nnvm/src/top/op_common.h b/nnvm/src/top/op_common.h
index e23059a88c42..f13d7e782dae 100644
--- a/nnvm/src/top/op_common.h
+++ b/nnvm/src/top/op_common.h
@@ -8,6 +8,7 @@
 
 #include <dmlc/logging.h>
 #include <dmlc/parameter.h>
+#include <nnvm/top/tensor.h>
 #include <string>
 #include <vector>
 #include <unordered_set>
@@ -16,7 +17,7 @@ namespace nnvm {
 namespace top {
 /*!
  * \brief Parse keyword arguments as PType arguments and save to parsed
- * \tparam PType the arameter type.
+ * \tparam PType the parameter type.
  * \param attrs The attributes.
  */
 template<typename PType>
@@ -202,6 +203,28 @@ inline std::string attr_assign_error_msg(const NodeAttrs& attrs,
     }                                                                    \
   }
 
+/*!
+ * \brief macro assign rhs shape to lhs
+ *  Use macro so we can see the error file more clearly
+ * \param lhs lhs shape
+ * \param rhs rhs shape
+ */
+#define SHAPE_ASSIGN(lhs, rhs)                                \
+  if ((lhs).ndim() == 0) (lhs) = (rhs);                       \
+  else                                                        \
+    CHECK_EQ(lhs, rhs) << "shape inference inconsistent";     \
+
+/*!
+ * \brief macro assign rhs type to lhs
+ *  Use macro so we can see the error file more clearly
+ * \param lhs lhs type
+ * \param rhs rhs type
+ */
+#define DTYPE_ASSIGN(lhs, rhs)                                \
+  if ((lhs) == -1) (lhs) = (rhs);                             \
+  else                                                        \
+    CHECK_EQ(lhs, rhs) << "type inference inconsistent";     \
+
 // simply return the shape as same
 inline bool SameShape(const NodeAttrs& attrs,
                       std::vector<TShape> *ishape,
@@ -216,6 +239,28 @@ inline bool SameShape(const NodeAttrs& attrs,
   return true;
 }
 
+// return shape from node attrs
+inline bool ZeroShape(const NodeAttrs& attrs,
+                      std::vector<TShape> *ishape,
+                      std::vector<TShape> *oshape) {
+  const TShape& ts = dmlc::get<InitOpParam>(attrs.parsed).shape;
+  if (ts.ndim() != 0) {
+    SHAPE_ASSIGN(oshape->at(0), ts);
+    return true;
+  } else {
+    return false;
+  }
+}
+
+// return type from node attrs
+inline bool ZeroType(const NodeAttrs& attrs,
+                     std::vector<int> *iattr,
+                     std::vector<int> *oattr) {
+  int dtype = dmlc::get<InitOpParam>(attrs.parsed).dtype;
+  DTYPE_ASSIGN(oattr->at(0), dtype);
+  return true;
+}
+
 }  // namespace top
 }  // namespace nnvm
 
diff --git a/nnvm/src/top/tensor/elemwise.cc b/nnvm/src/top/tensor/elemwise.cc
index 17e457e56b3c..c93afa5dfd0a 100644
--- a/nnvm/src/top/tensor/elemwise.cc
+++ b/nnvm/src/top/tensor/elemwise.cc
@@ -30,7 +30,21 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(sigmoid)
   Y = 1 / (1 + exp(-X))
 
 )code" NNVM_ADD_FILELINE)
-.set_support_level(1);
+.set_support_level(1)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds) {
+    // y = 1 / (1 + exp(-n0))
+    // grad_0 = grad_y * y * (1 - y)
+    NodeEntry sub0 = MakeNode("elemwise_mul", n->attrs.name + "_grad_sub_0",
+                              {ograds[0], NodeEntry{n, 0, 0}});
+    NodeEntry sub1 = MakeNode("__rsub_scalar__", n->attrs.name + "_grad_sub_1",
+                              {NodeEntry{n, 0, 0}}, {{"scalar", "1"}});
+    return std::vector<NodeEntry>{
+      MakeNode("elemwise_mul", n->attrs.name + "_grad_0",
+               {sub0, sub1})
+    };
+});
 
 // tanh
 NNVM_REGISTER_ELEMWISE_UNARY_OP(tanh)
@@ -40,7 +54,21 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(tanh)
    Y = sinh(X) / cosh(X)
 
 )code" NNVM_ADD_FILELINE)
-.set_support_level(1);
+.set_support_level(1)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds) {
+    // y = sinh(n0) / cosh(n0)
+    // grad_0 = grad_y * (1 - y^2)
+    NodeEntry sub0 = MakeNode("elemwise_mul", n->attrs.name + "_grad_sub_0",
+                              {NodeEntry{n, 0, 0}, NodeEntry{n, 0, 0}});
+    NodeEntry sub1 = MakeNode("__rsub_scalar__", n->attrs.name + "_grad_sub_1",
+                              {sub0}, {{"scalar", "1"}});
+    return std::vector<NodeEntry>{
+      MakeNode("elemwise_mul", n->attrs.name + "_grad_0",
+               {ograds[0], sub1})
+    };
+});
 
 // exp
 NNVM_REGISTER_ELEMWISE_UNARY_OP(exp)
@@ -50,7 +78,17 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(exp)
    exp(x)
 
 )code" NNVM_ADD_FILELINE)
-.set_support_level(1);
+.set_support_level(1)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds) {
+    // y = exp(n0)
+    // grad_0 = grad_y * y
+    return std::vector<NodeEntry>{
+      MakeNode("elemwise_mul", n->attrs.name + "_grad_0",
+               {ograds[0], NodeEntry{n, 0, 0}})
+    };
+});
 
 // log
 NNVM_REGISTER_ELEMWISE_UNARY_OP(log)
@@ -60,7 +98,17 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(log)
    log(x)
 
 )code" NNVM_ADD_FILELINE)
-.set_support_level(1);
+.set_support_level(1)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds) {
+    // y = log(n0)
+    // grad_0 = grad_y / n0
+    return std::vector<NodeEntry>{
+      MakeNode("elemwise_div", n->attrs.name + "_grad_0",
+               {ograds[0], n->inputs[0]})
+    };
+});
 
 // sqrt
 NNVM_REGISTER_ELEMWISE_UNARY_OP(sqrt)
@@ -70,7 +118,19 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(sqrt)
    \sqrt(x)
 
 )code" NNVM_ADD_FILELINE)
-.set_support_level(1);
+.set_support_level(1)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds) {
+    // y = sqrt(n0)
+    // grad_0 = grad_y / (2 * y)
+    NodeEntry sub0 = MakeNode("__mul_scalar__", n->attrs.name + "_grad_sub_0",
+                              {NodeEntry{n, 0, 0}}, {{"scalar", "2"}});
+    return std::vector<NodeEntry>{
+      MakeNode("elemwise_div", n->attrs.name + "_grad_0",
+             {ograds[0], sub0})
+    };
+});
 
 // binary ops
 
@@ -78,39 +138,132 @@ NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_add)
 .describe(R"code(Element-wise add
 
 )code")
-.set_support_level(1);
+.set_support_level(1)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    // y = n0 + n1
+    // grad_0 = grad_y
+    // grad_1 = grad_y
+    return std::vector<NodeEntry>{ograds[0], ograds[0]};
+});
 
 NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_sub)
 .describe(R"code(Element-wise substraction
 
 )code"  NNVM_ADD_FILELINE)
-.set_support_level(1);
+.set_support_level(1)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    // y = n0 - n1
+    // grad_0 = grad_y
+    // grad_1 = - grad_y
+    return std::vector<NodeEntry>{
+      ograds[0],
+      MakeNode("negative", n->attrs.name + "_grad_1", {ograds[0]}),
+    };
+});
 
 NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_mul)
 .describe(R"code(Element-wise multiplication
 
 )code"  NNVM_ADD_FILELINE)
-.set_support_level(1);
+.set_support_level(1)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    // y = n0 * n1
+    // grad_0 = grad_y * n1
+    // grad_1 = grad_y * n0
+    return std::vector<NodeEntry>{
+      MakeNode("elemwise_mul", n->attrs.name + "_grad_0",
+               {ograds[0], n->inputs[1]}),
+      MakeNode("elemwise_mul", n->attrs.name + "_grad_1",
+               {ograds[0], n->inputs[0]})
+    };
+});
 
 NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_div)
 .describe(R"code(Element-wise multiplication
 
 )code"  NNVM_ADD_FILELINE)
-.set_support_level(1);
+.set_support_level(1)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    // y = n0 / n1
+    // grad_0 = grad_y / n1
+    // grad_1 = - grad_y * n0 / n1^2
+    NodeEntry sub0 = MakeNode("elemwise_mul", n->attrs.name + "_grad_sub_0",
+                              {ograds[0], n->inputs[0]});
+    NodeEntry sub1 = MakeNode("negative", n->attrs.name + "_grad_sub_1", {sub0});
+    NodeEntry sub2 = MakeNode("elemwise_mul", n->attrs.name + "_grad_sub_2",
+                              {n->inputs[1], n->inputs[1]});
+    return std::vector<NodeEntry>{
+      MakeNode("elemwise_div", n->attrs.name + "_grad_0",
+               {ograds[0], n->inputs[1]}),
+      MakeNode("elemwise_div", n->attrs.name + "_grad_1",
+               {sub1, sub2})
+    };
+});
 
 // negative
 NNVM_REGISTER_ELEMWISE_UNARY_OP(negative)
 .describe(R"code(Elemenwise numeric negative
 
 )code"  NNVM_ADD_FILELINE)
-.set_support_level(3);
+.set_support_level(3)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    // y = - n0
+    // grad_0 = - grad_y
+    return std::vector<NodeEntry>{
+      MakeNode("negative", n->attrs.name + "_grad_0", {ograds[0]}),
+    };
+});
 
 // copy
 NNVM_REGISTER_ELEMWISE_UNARY_OP(copy)
 .describe(R"code(Copy tensor to another one.
 
 )code"  NNVM_ADD_FILELINE)
-.set_support_level(3);
+.set_support_level(3)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    // y = copy(n0)
+    // grad_0 = grad_y
+    return std::vector<NodeEntry>{ograds[0]};
+});
+
+DMLC_REGISTER_PARAMETER(InitOpParam);
+
+// fill
+NNVM_REGISTER_INIT_OP(fill)
+.describe(R"code(Fill array with scalar value
+
+)code"  NNVM_ADD_FILELINE)
+.set_support_level(1);
+
+// fill_like
+NNVM_REGISTER_ELEMWISE_UNARY_OP(fill_like)
+  .describe(R"code(Return an scalar value array with the same shape and type
+as the input array
+
+)code"  NNVM_ADD_FILELINE)
+.set_support_level(1)
+.add_arguments(InitOpParam::__FIELDS__())
+.set_attr_parser(ParamParser<InitOpParam>)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    return std::vector<NodeEntry>{
+      MakeNode("fill_like", n->attrs.name + "_zero",
+               {n->inputs[0]}, {{"value", "0"}})
+    };
+});
 
 // unary scalar op
 DMLC_REGISTER_PARAMETER(ScalarParam);
@@ -126,49 +279,134 @@ NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__add_scalar__)
 .describe(R"code(Tensor add scalar
 
 )code"  NNVM_ADD_FILELINE)
-.set_support_level(3);
+.set_support_level(3)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    return std::vector<NodeEntry>{ograds[0]};
+});
 
 NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__sub_scalar__)
 .describe(R"code(Tensor substract scalar
 
 )code"  NNVM_ADD_FILELINE)
-.set_support_level(3);
+.set_support_level(3)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    return std::vector<NodeEntry>{ograds[0]};
+});
 
 NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__rsub_scalar__)
 .describe(R"code(scalar substract Tensor
 
 )code"  NNVM_ADD_FILELINE)
-.set_support_level(3);
+.set_support_level(3)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    return std::vector<NodeEntry>{
+      MakeNode("negative", n->attrs.name + "_grad_0", {ograds[0]})
+    };
+});
 
 NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__mul_scalar__)
 .describe(R"code(Tensor multiplies scalar
 
 )code"  NNVM_ADD_FILELINE)
-.set_support_level(3);
+.set_support_level(3)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    // y = n0 * scalar
+    // grad_0 = grad_y * scalar
+    return std::vector<NodeEntry>{
+      MakeNode("__mul_scalar__", n->attrs.name + "_grad_0",
+               {ograds[0]}, {{"scalar", n->attrs.dict["scalar"]}})
+    };
+});
 
 NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__div_scalar__)
 .describe(R"code(Tensor divides scalar
 
 )code"  NNVM_ADD_FILELINE)
-.set_support_level(3);
+.set_support_level(3)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    // y = n0 / scalar
+    // grad_0 = grad_y / scalar
+    return std::vector<NodeEntry>{
+      MakeNode("__div_scalar__", n->attrs.name + "_grad_0",
+               {ograds[0]}, {{"scalar", n->attrs.dict["scalar"]}})
+    };
+});
 
 NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__rdiv_scalar__)
 .describe(R"code(scalar divides Tensor
 
 )code"  NNVM_ADD_FILELINE)
-.set_support_level(3);
+.set_support_level(3)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    // y = scalar / n0
+    // grad_0 = - grad_y * scalar / n0^2
+    NodeEntry sub0 = MakeNode("__mul_scalar__", n->attrs.name + "_grad_sub_0",
+                              {ograds[0]}, {{"scalar", n->attrs.dict["scalar"]}});
+    NodeEntry sub1 = MakeNode("negative", n->attrs.name + "_grad_sub_1", {sub0});
+    NodeEntry sub2 = MakeNode("elemwise_mul", n->attrs.name + "_grad_sub_2",
+                              {n->inputs[0], n->inputs[0]});
+    return std::vector<NodeEntry>{
+      MakeNode("elemwise_div", n->attrs.name + "_grad_0",
+               {sub1, sub2})
+    };
+});
 
 NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__pow_scalar__)
 .describe(R"code(Tensor power scalar
 
 )code"  NNVM_ADD_FILELINE)
-.set_support_level(3);
+.set_support_level(3)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    // y = n0^scalar
+    // grad_0 = grad_y * scalar * n0^(scalar - 1)
+    double scalar = std::stod(n->attrs.dict["scalar"]);
+    NodeEntry sub0 = MakeNode("__pow_scalar__", n->attrs.name + "_grad_sub_0",
+                              {n->inputs[0]},
+                              {{"scalar", std::to_string(scalar - 1)}});
+    NodeEntry sub1 = MakeNode("__mul_scalar__", n->attrs.name + "_grad_sub_1",
+                              {ograds[0]},
+                              {{"scalar", std::to_string(scalar)}});
+    return std::vector<NodeEntry>{
+      MakeNode("elemwise_mul", n->attrs.name + "_grad_0",
+               {sub0, sub1})
+    };
+});
 
 NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__rpow_scalar__)
 .describe(R"code(scalar power Tensor
 
 )code"  NNVM_ADD_FILELINE)
-.set_support_level(3);
+.set_support_level(3)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    // y = scalar^n0
+    // grad_0 = grad_y * scalar^n0 * log(scalar)
+    double num = std::stod(n->attrs.dict["scalar"]);
+    NodeEntry sub0 = MakeNode("__mul_scalar__", n->attrs.name + "_grad_sub_0",
+                              {NodeEntry{n, 0, 0}},
+                              {{"scalar", std::to_string(std::log(num))}});
+    return std::vector<NodeEntry>{
+      MakeNode("__mul_symbol__", n->attrs.name + "_grad_0",
+               {ograds[0], sub0})
+    };
+});
+
+
 
 }  // namespace top
 }  // namespace nnvm
diff --git a/nnvm/tests/python/compiler/test_top_level1.py b/nnvm/tests/python/compiler/test_top_level1.py
index 33db05cc1de5..fc2776f2b01c 100644
--- a/nnvm/tests/python/compiler/test_top_level1.py
+++ b/nnvm/tests/python/compiler/test_top_level1.py
@@ -6,146 +6,199 @@
 import nnvm.compiler
 from nnvm.testing.config import ctx_list
 
+
+def helper(symbol, inputs, dtype,
+           np_forward, np_backward=None):
+    ishapes = {}
+    input_syms = []
+    np_inputs = {}
+    for (k, v) in inputs.items():
+        ishapes.update({k: v[0]})
+        np_inputs.update({k: np.random.uniform(size=v[0]).astype(dtype)})
+        if len(v) > 1:
+            input_syms.append(v[1])
+
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(symbol, target, ishapes)
+        m = graph_runtime.create(graph, lib, ctx)
+        m.run(**np_inputs)
+        y_np = np_forward(**np_inputs)
+        out = m.get_output(0, tvm.nd.empty(y_np.shape, dtype))
+        np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+
+        # backward
+        if np_backward:
+            graph._set_symbol_list_attr("grad_ys", symbol)
+            for x in input_syms:
+                graph._set_symbol_list_attr("grad_xs", x)
+            graph._set_symbol_list_attr("grad_ys_out_grad", sym.Variable("head_grads"))
+            graph = graph.apply("Gradient")
+            ishapes.update({"head_grads": y_np.shape})
+            graph, lib, _ = nnvm.compiler.build(graph, target, ishapes)
+            m = graph_runtime.create(graph, lib, ctx)
+            head_grads = np.random.uniform(size=y_np.shape).astype(dtype)
+            y_np = head_grads * np_backward(**np_inputs)
+            m.run(head_grads=head_grads, **np_inputs)
+            out = m.get_output(0, tvm.nd.empty(y_np.shape, dtype))
+
+            np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+
+
 def test_relu():
     x = sym.Variable("x")
-    y = sym.leaky_relu(x, alpha=0.3) - 0.2
-    y = sym.relu(y)
+    y = sym.relu(sym.leaky_relu(x, alpha=0.3) - 0.2)
+
+    def forward(x):
+        x = (x < 0) * x * 0.3 + (x > 0) * x - 0.2
+        return (x > 0) * x
+
+    dtype = "float32"
+    dshape = (1, 3, 32, 32)
+    inputs = {'x': (dshape, x)}
+    helper(y, inputs, dtype, forward)
+
+
+def test_sym_scalar_pow():
+    scalar = 3
+    x = sym.Variable("x")
+    y = x**scalar
+
+    def forward(x):
+        return x**scalar
+
+    def backward(x):
+        return scalar * x**(scalar -  1)
+
     dtype = "float32"
     dshape = (1, 3, 32, 32)
-    oshape = dshape
-    for target, ctx in ctx_list():
-        graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
-        m = graph_runtime.create(graph, lib, ctx)
-        data = np.random.uniform(size=dshape).astype(dtype)
-        m.run(x=data)
-        data = (data < 0) * data * 0.3 + (data>0) * data - 0.2
-        data = (data > 0) * data
-        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
-        np.testing.assert_allclose(out.asnumpy(), data, atol=1e-5, rtol=1e-5)
+    inputs = {'x': (dshape, x)}
+    helper(y, inputs, dtype, forward, backward)
+
+
+def test_scalar_sym_pow():
+    scalar = 3
+    x = sym.Variable("x")
+    y = scalar**x
+
+    def forward(x):
+        return scalar**x
+
+    def backward(x):
+        return np.log(scalar) * scalar**x
+
+    dtype = "float32"
+    dshape = (1, 3, 32, 32)
+    inputs = {'x': (dshape, x)}
+    helper(y, inputs, dtype, forward, backward)
 
 
 def test_exp():
     x = sym.Variable("x")
     y = sym.exp(x)
+
+    def forward(x):
+        return np.exp(x)
+
+    def backward(x):
+        return np.exp(x)
+
     dtype = "float32"
     dshape = (1, 3, 32, 32)
-    oshape = dshape
-    for target, ctx in ctx_list():
-        graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
-        m = graph_runtime.create(graph, lib, ctx)
-        data = np.random.uniform(size=dshape).astype(dtype)
-        m.run(x=data)
-        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
-        y_np = np.exp(data)
-        np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+    inputs = {'x': (dshape, x)}
+    helper(y, inputs, dtype, forward, backward)
 
 
 def test_log():
     x = sym.Variable("x")
     y = sym.log(x)
+
+    def forward(x):
+        return np.log(x)
+
+    def backward(x):
+        return 1. / x
+
     dtype = "float32"
     dshape = (1, 3, 32, 32)
-    oshape = dshape
-    for target, ctx in ctx_list():
-        with nnvm.compiler.build_config(opt_level=1):
-            graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
-        m = graph_runtime.create(graph, lib, ctx)
-        data = np.random.uniform(size=dshape).astype(dtype)
-        m.run(x=data)
-        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
-        y_np = np.log(data)
-        np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+    inputs = {'x': (dshape, x)}
+    helper(y, inputs, dtype, forward, backward)
 
 
 def test_tanh():
     x = sym.Variable("x")
     y = sym.tanh(x)
+
+    def forward(x):
+        return np.sinh(x) / np.cosh(x)
+
+    def backward(x):
+        y_np = forward(x)
+        return (1 - y_np**2)
+
     dtype = "float32"
     dshape = (1, 3, 32, 32)
-    oshape = dshape
-    for target, ctx in ctx_list():
-        with nnvm.compiler.build_config(opt_level=1):
-            graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
-        m = graph_runtime.create(graph, lib, ctx)
-        data = np.random.uniform(size=dshape).astype(dtype)
-        m.run(x=data)
-        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
-        y_np = np.sinh(data) / np.cosh(data)
-        np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+    inputs = {'x': (dshape, x)}
+    helper(y, inputs, dtype, forward, backward)
 
 
 def test_sigmoid():
     x = sym.Variable("x")
     y = sym.sigmoid(x)
+
+    def forward(x):
+        return 1.0 / (1.0 + np.exp(-x))
+
+    def backward(x):
+        y_np = forward(x)
+        return y_np *(1 - y_np)
+
     dtype = "float32"
     dshape = (1, 3, 32, 32)
-    oshape = dshape
-    for target, ctx in ctx_list():
-        graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
-        m = graph_runtime.create(graph, lib, ctx)
-        data = np.random.uniform(size=dshape).astype(dtype)
-        m.run(x=data)
-        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
-        y_np = 1.0 / (1.0 + np.exp(-data))
-        np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+    inputs = {'x': (dshape, x)}
+    helper(y, inputs, dtype, forward, backward)
 
 
 def test_softmax():
     x = sym.Variable("x")
     y = sym.softmax(x)
+
+    def forward(x):
+        return topi.testing.softmax_python(x)
+
     dtype = "float32"
     dshape = (10, 1000)
-    oshape = dshape
-    for target, ctx in ctx_list():
-        with nnvm.compiler.build_config(opt_level=1):
-            graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
-        m = graph_runtime.create(graph, lib, ctx)
-        data = np.random.uniform(size=dshape).astype(dtype)
-        m.run(x=data)
-        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
-        y_np = topi.testing.softmax_python(data)
-        np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+    inputs = {'x': (dshape, x)}
+    helper(y, inputs, dtype, forward)
 
 
 def test_log_softmax():
     x = sym.Variable("x")
     y = sym.log_softmax(x)
+
+    def forward(x):
+        return topi.testing.log_softmax_python(x)
+
     dtype = "float32"
     dshape = (10, 1000)
-    oshape = dshape
-    for target, ctx in ctx_list():
-        with nnvm.compiler.build_config(opt_level=1):
-            graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
-        m = graph_runtime.create(graph, lib, ctx)
-        data = np.random.uniform(size=dshape).astype(dtype)
-        m.run(x=data)
-        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
-        y_np = topi.testing.log_softmax_python(data)
-        np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+    inputs = {'x': (dshape, x)}
+    helper(y, inputs, dtype, forward)
 
 
 def test_dense():
     x = sym.Variable("x")
     y = sym.dense(x, units=3, name="dense")
     y = sym.flatten(y)
+
+    def forward(x, dense_weight, dense_bias):
+        return np.dot(x, dense_weight.T) + dense_bias
+
     dtype = "float32"
-    shape = {
-        "x" : (10, 100),
-        "dense_weight" : (3, 100),
-        "dense_bias" : (3,),
+    inputs = {
+        'x': ((10, 100), x),
+        'dense_weight': ((3, 100),),
+        'dense_bias': ((3,),)
     }
-    for target, ctx in ctx_list():
-        graph, lib, _ = nnvm.compiler.build(y, target, shape)
-        m = graph_runtime.create(graph, lib, ctx)
-        x_np = np.random.uniform(size=shape["x"]).astype(dtype)
-        w_np = np.random.uniform(size=shape["dense_weight"]).astype(dtype)
-        b_np = np.random.uniform(size=shape["dense_bias"]).astype(dtype)
-        res = tvm.nd.empty((10, 3))
-        m.run(x=x_np, dense_weight=w_np, dense_bias=b_np)
-        m.get_output(0, res)
-        res_np = np.dot(x_np, w_np.T) + b_np
-        np.testing.assert_allclose(
-            res.asnumpy(), res_np, atol=1e-5, rtol=1e-5)
+    helper(y, inputs, dtype, forward)
 
 
 def test_batchnorm():
@@ -154,27 +207,23 @@ def test_batchnorm():
     gamma = sym.Variable("gamma")
     moving_var = sym.Variable("moving_var")
     moving_mean = sym.Variable("moving_mean")
-    shape = (10, 20)
     eps = 1e-5
-    dtype = "float32"
     y = sym.batch_norm(
         x, gamma, beta, moving_mean, moving_var, epsilon=eps)
 
-    for target, ctx in ctx_list():
-        graph, lib, _ = nnvm.compiler.build(y, "llvm", {"x": shape})
-        m = graph_runtime.create(graph, lib, tvm.cpu(0))
-        x_np = np.random.uniform(size=shape).astype(dtype)
-        mean_np = np.random.uniform(size=shape[1]).astype(dtype)
-        var_np = np.random.uniform(size=shape[1]).astype(dtype)
-        gamma_np = np.random.uniform(size=shape[1]).astype(dtype)
-        beta_np = np.random.uniform(size=shape[1]).astype(dtype)
-        res = tvm.nd.empty(shape)
-        m.run(x=x_np, moving_mean=mean_np, moving_var=var_np,
-              gamma=gamma_np, beta=beta_np)
-        m.get_output(0, res)
-        res_np = (x_np - mean_np) / np.sqrt(var_np + eps) * gamma_np + beta_np
-        np.testing.assert_allclose(
-            res.asnumpy(), res_np, atol=1e-5, rtol=1e-5)
+    def forward(x, gamma, beta, moving_mean, moving_var):
+        return (x - moving_mean) / np.sqrt(moving_var + eps) * gamma + beta
+
+    dtype = "float32"
+    inputs = {
+        'x': ((10, 20), x),
+        'gamma': ((20,),),
+        'beta': ((20,),),
+        'moving_mean': ((20,),),
+        'moving_var': ((20,),)
+    }
+
+    helper(y, inputs,  dtype, forward)
 
 
 def verify_concatenate(ishape, axis):
@@ -195,6 +244,7 @@ def verify_concatenate(ishape, axis):
         out = m.get_output(0, tvm.nd.empty(out_np.shape))
         np.testing.assert_allclose(out.asnumpy(), out_np, atol=1e-5, rtol=1e-5)
 
+
 def test_concatenate():
     verify_concatenate([(2, 3, 4), (1, 3, 4)], axis=0)
     verify_concatenate([(2, 4), (2, 7)], axis=1)
@@ -215,6 +265,7 @@ def verify_split(ishape, indices_or_sections, axis):
             out = m.get_output(i, tvm.nd.empty(arr.shape))
             np.testing.assert_allclose(out.asnumpy(), arr, atol=1e-5, rtol=1e-5)
 
+
 def test_split():
     verify_split((2, 3), 2, axis=0)
     verify_split((5, 3), [3], axis=0)
@@ -228,16 +279,14 @@ def verify_squeeze(dshape, axis):
     else:
         y = sym.squeeze(x)
     y = y + 1
+
+    def forward(x):
+        return np.squeeze(x, axis=axis) + 1
+
     dtype = "float32"
-    for target, ctx in ctx_list():
-        graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
-        m = graph_runtime.create(graph, lib, ctx)
-        # set input
-        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
-        m.run(x=data)
-        out_np = np.squeeze(data.asnumpy(), axis=axis) + 1
-        out = m.get_output(0, tvm.nd.empty(out_np.shape))
-        np.testing.assert_allclose(out.asnumpy(), out_np, atol=1e-5, rtol=1e-5)
+    inputs = {'x': (dshape, x)}
+    helper(y, inputs, dtype, forward)
+
 
 def test_squeeze():
     verify_squeeze((1, 3, 2, 5), None)
@@ -248,19 +297,15 @@ def test_squeeze():
 def test_pad():
     x = sym.Variable("x")
     y = sym.pad(x, pad_width=((0, 0), (0, 0), (0, 1), (2, 3)), pad_value=1.)
+
+    def forward(x):
+        return np.pad(x,
+                      pad_width=((0, 0), (0, 0), (0, 1), (2, 3)),
+                      mode='constant', constant_values=1.)
+
     dtype = "float32"
-    dshape = (1, 3, 28, 28)
-    oshape = (1, 3, 29, 33)
-    shape_dict = {"x": dshape}
-    for target, ctx in ctx_list():
-        graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
-        m = graph_runtime.create(graph, lib, ctx)
-        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
-        m.run(x=data)
-        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
-        b_np = np.pad(data.asnumpy(), pad_width=((0, 0), (0, 0), (0, 1), (2, 3)),
-            mode='constant', constant_values=1.)
-        np.testing.assert_allclose(out.asnumpy(), b_np, rtol=1e-5)
+    inputs = {'x': ((1, 3, 28, 28), x)}
+    helper(y, inputs, dtype, forward)
 
 
 if __name__ == "__main__":
@@ -270,6 +315,8 @@ def test_pad():
     test_batchnorm()
     test_dense()
     test_relu()
+    test_sym_scalar_pow()
+    test_scalar_sym_pow()
     test_exp()
     test_log()
     test_tanh()

From bbf379b775094b0fc34ea84ed98eabf88b9130b3 Mon Sep 17 00:00:00 2001
From: Yuwei Hu <huyuwei1995@gmail.com>
Date: Wed, 27 Dec 2017 11:27:37 +0800
Subject: [PATCH 572/949] [FIX] make keras frontend py3 compatible (#283)

---
 nnvm/python/nnvm/frontend/keras.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/keras.py b/nnvm/python/nnvm/frontend/keras.py
index b2498488af08..1272d549b5c1 100644
--- a/nnvm/python/nnvm/frontend/keras.py
+++ b/nnvm/python/nnvm/frontend/keras.py
@@ -17,7 +17,7 @@ def _check_data_format(keras_layer):
 
 
 def _convert_activation(insym, keras_layer, _):
-    if isinstance(keras_layer, (str, unicode)):
+    if isinstance(keras_layer, str):
         act_type = keras_layer
     else:
         if sys.version_info.major < 3:
@@ -25,7 +25,7 @@ def _convert_activation(insym, keras_layer, _):
         else:
             act_type = keras_layer.activation.__name__
     if act_type == 'linear':
-        if isinstance(keras_layer, (str, unicode)):
+        if isinstance(keras_layer, str):
             return insym
         return _sym.__add_scalar__(_sym.__mul_scalar__(insym, \
             scalar=keras_layer.alpha), scalar=keras_layer.beta)

From 7b3511d218c28e4eab6de279fff243c0787ae5a8 Mon Sep 17 00:00:00 2001
From: Yao Wang <kevinthesunwy@gmail.com>
Date: Sat, 30 Dec 2017 10:43:39 -0800
Subject: [PATCH 573/949] Add gradient graph (#280)

* Add creating gradient symbol

* Fix lint

* Address comments

* Fix typo

* Address comment
---
 nnvm/docs/top.rst                        |  16 ++-
 nnvm/python/nnvm/graph.py                |  36 +++++++
 nnvm/src/pass/gradient.cc                |   4 +-
 nnvm/src/top/tensor/elemwise.cc          | 120 +++++++++++++++++++++--
 nnvm/tests/python/unittest/test_graph.py |  18 ++++
 5 files changed, 179 insertions(+), 15 deletions(-)

diff --git a/nnvm/docs/top.rst b/nnvm/docs/top.rst
index f86820dd99c2..fc22eec86116 100644
--- a/nnvm/docs/top.rst
+++ b/nnvm/docs/top.rst
@@ -38,8 +38,12 @@ This level enables fully connected multi-layer perceptron.
    nnvm.symbol.elemwise_sub
    nnvm.symbol.elemwise_mul
    nnvm.symbol.elemwise_div
-   nnvm.symbol.fill
-   nnvm.symbol.fill_like
+   nnvm.symbol.full
+   nnvm.symbol.full_like
+   nnvm.symbol.ones
+   nnvm.symbol.ones_like
+   nnvm.symbol.zeros
+   nnvm.symbol.zeros_like
    nnvm.symbol.flatten
    nnvm.symbol.concatenate
    nnvm.symbol.expand_dims
@@ -113,8 +117,12 @@ Detailed Definitions
 .. autofunction:: nnvm.symbol.elemwise_sub
 .. autofunction:: nnvm.symbol.elemwise_mul
 .. autofunction:: nnvm.symbol.elemwise_div
-.. autofunction:: nnvm.symbol.fill
-.. autofunction:: nnvm.symbol.fill_like
+.. autofunction:: nnvm.symbol.full
+.. autofunction:: nnvm.symbol.full_like
+.. autofunction:: nnvm.symbol.ones
+.. autofunction:: nnvm.symbol.ones_like
+.. autofunction:: nnvm.symbol.zeros
+.. autofunction:: nnvm.symbol.zeros_like
 .. autofunction:: nnvm.symbol.flatten
 .. autofunction:: nnvm.symbol.concatenate
 .. autofunction:: nnvm.symbol.expand_dims
diff --git a/nnvm/python/nnvm/graph.py b/nnvm/python/nnvm/graph.py
index 2ea365e67ef4..3940e0b5b864 100644
--- a/nnvm/python/nnvm/graph.py
+++ b/nnvm/python/nnvm/graph.py
@@ -13,6 +13,7 @@
 from ._base import GraphHandle, SymbolHandle
 from ._base import check_call
 from .symbol import Variable, Symbol, Group as _Group
+from .symbol import ones_like
 
 class GraphIndex(object):
     """Index for quickly accessing graph attributes.
@@ -270,3 +271,38 @@ def create(symbol):
     check_call(_LIB.NNGraphCreate(
         symbol.handle, ctypes.byref(ghandle)))
     return Graph(ghandle)
+
+
+def gradients(ys, xs, grad_ys=None):
+    """Create gradient symbol of ys respect to xs.
+
+    Parameters
+    ----------
+    ys : Symbol or list of Symbol
+        Symbols from which the gradient is calculated.
+    xs : Symbol or list of Symbol
+        Symbols the gradient respect to.
+        For group symbol, gradients for all outputs will be calculated.
+    grad_ys : Symbol or list of Symbol
+        Head gradients for ys.
+
+    Returns
+    -------
+    ret : list of Symbol
+        Generated gradient symbol. For each xs,
+        all gradients from ys are merged into a single symbol.
+    """
+    if isinstance(ys, list):
+        ys = _Group(ys)
+    g = create(ys)
+    g._set_symbol_list_attr('grad_ys', ys)
+    g._set_symbol_list_attr('grad_xs', xs)
+    ny = len(ys.list_output_names())
+    if grad_ys is None:
+        grad_ys = [ones_like(ys[i]) for i in range(ny)]
+    g._set_symbol_list_attr('grad_ys_out_grad', grad_ys)
+    sym = g.apply('Gradient').symbol
+    nx = len(_Group(xs).list_output_names()) \
+        if isinstance(xs, list) else len(xs.list_output_names())
+    ret = [sym[i] for i in range(nx)]
+    return ret
diff --git a/nnvm/src/pass/gradient.cc b/nnvm/src/pass/gradient.cc
index 561ec01323d6..ff5f8b9ed731 100644
--- a/nnvm/src/pass/gradient.cc
+++ b/nnvm/src/pass/gradient.cc
@@ -20,11 +20,11 @@ NodeEntry DefaultAggregateGradient(std::vector<NodeEntry>&& v) {
     return std::move(v[0]);
   } else if (v.size() == 0) {
     NodePtr zero_node = Node::Create();
-    zero_node->attrs.op = Op::Get("__zero__");
+    zero_node->attrs.op = Op::Get("_zeros");
     return NodeEntry{zero_node, 0, 0};
   } else {
     NodePtr sum_node = Node::Create();
-    sum_node->attrs.op = Op::Get("__ewise_sum__");
+    sum_node->attrs.op = Op::Get("elemwise_sum");
     sum_node->inputs = std::move(v);
     return NodeEntry{sum_node, 0, 0};
   }
diff --git a/nnvm/src/top/tensor/elemwise.cc b/nnvm/src/top/tensor/elemwise.cc
index c93afa5dfd0a..944ff5fe0576 100644
--- a/nnvm/src/top/tensor/elemwise.cc
+++ b/nnvm/src/top/tensor/elemwise.cc
@@ -197,7 +197,8 @@ NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_div)
     // grad_1 = - grad_y * n0 / n1^2
     NodeEntry sub0 = MakeNode("elemwise_mul", n->attrs.name + "_grad_sub_0",
                               {ograds[0], n->inputs[0]});
-    NodeEntry sub1 = MakeNode("negative", n->attrs.name + "_grad_sub_1", {sub0});
+    NodeEntry sub1 = MakeNode("negative", n->attrs.name + "_grad_sub_1",
+                              {sub0});
     NodeEntry sub2 = MakeNode("elemwise_mul", n->attrs.name + "_grad_sub_2",
                               {n->inputs[1], n->inputs[1]});
     return std::vector<NodeEntry>{
@@ -240,15 +241,27 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(copy)
 
 DMLC_REGISTER_PARAMETER(InitOpParam);
 
-// fill
-NNVM_REGISTER_INIT_OP(fill)
+// full
+NNVM_REGISTER_INIT_OP(full)
 .describe(R"code(Fill array with scalar value
 
 )code"  NNVM_ADD_FILELINE)
 .set_support_level(1);
 
-// fill_like
-NNVM_REGISTER_ELEMWISE_UNARY_OP(fill_like)
+NNVM_REGISTER_INIT_OP(zeros)
+.describe(R"code(Fill target with zeros
+
+)code"  NNVM_ADD_FILELINE)
+.set_support_level(1);
+
+NNVM_REGISTER_INIT_OP(ones)
+.describe(R"code(Fill target with ones
+
+)code"  NNVM_ADD_FILELINE)
+.set_support_level(1);
+
+// full_like
+NNVM_REGISTER_ELEMWISE_UNARY_OP(full_like)
   .describe(R"code(Return an scalar value array with the same shape and type
 as the input array
 
@@ -260,8 +273,38 @@ as the input array
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds){
     return std::vector<NodeEntry>{
-      MakeNode("fill_like", n->attrs.name + "_zero",
-               {n->inputs[0]}, {{"value", "0"}})
+      MakeNode("zeros_like", n->attrs.name + "_grad",
+               {n->inputs[0]})
+    };
+});
+
+NNVM_REGISTER_ELEMWISE_UNARY_OP(zeros_like)
+.describe(R"code(Return an array of zeros with the same shape and type
+as the input array.
+
+)code")
+.add_argument("data", "Symbol", "The input")
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    return std::vector<NodeEntry>{
+      MakeNode("zeros_like", n->attrs.name + "_grad",
+               {n->inputs[0]})
+    };
+});
+
+NNVM_REGISTER_ELEMWISE_UNARY_OP(ones_like)
+.describe(R"code(Return an array of ones with the same shape and type
+as the input array.
+
+)code")
+.add_argument("data", "Symbol", "The input")
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    return std::vector<NodeEntry>{
+      MakeNode("zeros_like", n->attrs.name + "_grad",
+               {n->inputs[0]})
     };
 });
 
@@ -353,8 +396,10 @@ NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__rdiv_scalar__)
     // y = scalar / n0
     // grad_0 = - grad_y * scalar / n0^2
     NodeEntry sub0 = MakeNode("__mul_scalar__", n->attrs.name + "_grad_sub_0",
-                              {ograds[0]}, {{"scalar", n->attrs.dict["scalar"]}});
-    NodeEntry sub1 = MakeNode("negative", n->attrs.name + "_grad_sub_1", {sub0});
+                              {ograds[0]},
+                              {{"scalar", n->attrs.dict["scalar"]}});
+    NodeEntry sub1 = MakeNode("negative", n->attrs.name + "_grad_sub_1",
+                              {sub0});
     NodeEntry sub2 = MakeNode("elemwise_mul", n->attrs.name + "_grad_sub_2",
                               {n->inputs[0], n->inputs[0]});
     return std::vector<NodeEntry>{
@@ -407,6 +452,63 @@ NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__rpow_scalar__)
 });
 
 
+struct ElementWiseSumParam : public dmlc::Parameter<ElementWiseSumParam> {
+  int num_args;
+  DMLC_DECLARE_PARAMETER(ElementWiseSumParam) {
+    DMLC_DECLARE_FIELD(num_args).set_lower_bound(1)
+      .describe("Number of inputs to be summed.");
+  }
+};
+
+DMLC_REGISTER_PARAMETER(ElementWiseSumParam);
+
+bool ElementWiseSumShape(const NodeAttrs& attrs,
+                         std::vector<TShape> *in_attrs,
+                         std::vector<TShape> *out_attrs) {
+  CHECK_EQ(out_attrs->size(), 1);
+  return ElemwiseAttr<TShape, shape_is_none, shape_assign, true, shape_string>(
+    attrs, in_attrs, out_attrs, TShape());
+}
+
+bool ElementWiseSumType(const NodeAttrs& attrs,
+                        std::vector<int> *in_attrs,
+                        std::vector<int> *out_attrs) {
+  CHECK_EQ(out_attrs->size(), 1);
+  return ElemwiseAttr<int, type_is_none, type_assign, true, type_string>(
+    attrs, in_attrs, out_attrs, -1);
+}
+
+std::vector<NodeEntry> ElementWiseSumGrad(
+    const NodePtr& n,
+    const std::vector<NodeEntry>& ograds) {
+  // identity constraints in the beginning for easier shape inference.
+  const Op* copy_op = Op::Get("identity");
+  CHECK_EQ(ograds.size(), 1);
+  std::vector<NodeEntry> ret;
+  NodeEntry n_out{n, 0, 0};
+  for (size_t i = 0; i < n->inputs.size(); i++) {
+    NodePtr id_node = Node::Create();
+    id_node->attrs.op = copy_op;
+    id_node->inputs = {ograds[0]};
+    ret.push_back(NodeEntry{id_node, 0, 0});
+  }
+  return ret;
+}
+
+
+NNVM_REGISTER_OP(elemwise_sum)
+.describe(R"code(Adds all input arguments element-wise.
+
+)code"  NNVM_ADD_FILELINE)
+.set_attr_parser(ParamParser<ElementWiseSumParam>)
+.set_num_inputs([](const NodeAttrs& attrs) {
+  uint32_t ret = dmlc::get<ElementWiseSumParam>(attrs.parsed).num_args;
+  return ret;
+})
+.set_attr<nnvm::FInferShape>("FInferShape", ElementWiseSumShape)
+.set_attr<nnvm::FInferType>("FInferType", ElementWiseSumType)
+.set_attr<nnvm::FGradient>("FGradient", ElementWiseSumGrad)
+.add_argument("args", "Symbol[]", "Positional input arguments");
 
 }  // namespace top
 }  // namespace nnvm
diff --git a/nnvm/tests/python/unittest/test_graph.py b/nnvm/tests/python/unittest/test_graph.py
index 60c842f36a6d..647749a8adc9 100644
--- a/nnvm/tests/python/unittest/test_graph.py
+++ b/nnvm/tests/python/unittest/test_graph.py
@@ -112,6 +112,23 @@ def test_print_graph_ir():
     assert("y_bias" in ir1)
     assert("shape=" in ir2)
 
+def test_gradient():
+    x = sym.Variable("x")
+    y = sym.Variable("y")
+    z1 = sym.elemwise_add(x, sym.sqrt(y))
+    z2 = sym.log(x)
+    gradient = graph.gradients([z1, z2], [x, y])
+    assert len(gradient) == 2
+
+    g1 = sym.Variable("g1")
+    g2 = sym.Variable("g2")
+    grad_ys = [g1, g2]
+    gradient = graph.gradients(sym.Group([z1, z2]),
+                               sym.Group([x, y]), grad_ys=grad_ys)
+    g_graph = graph.create(sym.Group(gradient)).ir()
+    assert len(gradient) == 2
+    assert "g1" in g_graph
+    assert "g2" in g_graph
 
 if __name__ == "__main__":
     test_print_graph_ir()
@@ -123,3 +140,4 @@ def test_print_graph_ir():
     test_infer_type()
     test_plan_memory()
     test_list_args()
+    test_gradient()

From aa9f745be2fbce3d5b07222217b885bea9ff227f Mon Sep 17 00:00:00 2001
From: ziheng <ziheng@apache.org>
Date: Sun, 31 Dec 2017 09:19:09 +0800
Subject: [PATCH 574/949] [SYMBOL] Enable get index from symbol (#286)

---
 nnvm/src/core/symbolic.cc | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index ce56db255d7b..2a2f5be50bc0 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -495,6 +495,15 @@ bool Symbol::GetAttr(const std::string& key, std::string* out) const {
       *out = "null";  // use null with json
     }
     return true;
+  } else if (key == "_value_index") {
+    *out = "";
+    for (size_t i = 0; i < outputs.size(); ++i) {
+      if (i != 0) {
+        *out += ", ";
+      }
+      *out += std::to_string(outputs[i].index);
+    }
+    return true;
   }
   auto it = node->attrs.dict.find(key);
   if (it == node->attrs.dict.end()) return false;

From c368a6965faf0a5a2d8653f9818005c9fbafc899 Mon Sep 17 00:00:00 2001
From: Nehal J Wani <nehaljw.kkd1@gmail.com>
Date: Mon, 1 Jan 2018 06:16:47 -0600
Subject: [PATCH 575/949] Make choice of archiver configurable (#288)

At present, the build system assumes that the Unix archiv utility 'ar'
will always be present in PATH, but this might not be true if a separate
toolchain is being used. This patch makes the build system a bit more
cross-compile capable.
---
 nnvm/Makefile       | 2 +-
 nnvm/make/config.mk | 3 +++
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/nnvm/Makefile b/nnvm/Makefile
index 7d9992f05b09..3f86eac40bf2 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -72,7 +72,7 @@ build/src/%.o: src/%.cc
 
 lib/libnnvm.a: $(ALL_DEP)
 	@mkdir -p $(@D)
-	ar crv $@ $(filter %.o, $?)
+	$(AR) crv $@ $(filter %.o, $?)
 
 lib/libnnvm_compiler.$(SHARED_LIBRARY_SUFFIX): lib/libnnvm.a ${TOP_OBJ}
 	@mkdir -p $(@D)
diff --git a/nnvm/make/config.mk b/nnvm/make/config.mk
index d9eb39f4de04..e363af2ae9b2 100644
--- a/nnvm/make/config.mk
+++ b/nnvm/make/config.mk
@@ -22,6 +22,9 @@
 
 export NVCC = nvcc
 
+# choice of archiver
+export AR = ar
+
 # the additional link flags you want to add
 ADD_LDFLAGS=
 

From f862b38181876e7c232b5a21a9f0e18301b0c507 Mon Sep 17 00:00:00 2001
From: "Daniel M. Weeks" <dan@danweeks.net>
Date: Mon, 1 Jan 2018 07:32:47 -0500
Subject: [PATCH 576/949] Add missing cmath header (#287)

f9ed337552a26cc55c7c0fb22cd54839ee13f19c added a call to std::log
without including cmath
---
 nnvm/src/top/tensor/elemwise.cc | 1 +
 1 file changed, 1 insertion(+)

diff --git a/nnvm/src/top/tensor/elemwise.cc b/nnvm/src/top/tensor/elemwise.cc
index 944ff5fe0576..793d84b1484c 100644
--- a/nnvm/src/top/tensor/elemwise.cc
+++ b/nnvm/src/top/tensor/elemwise.cc
@@ -7,6 +7,7 @@
 #include <nnvm/node.h>
 #include <nnvm/op_attr_types.h>
 #include <nnvm/top/tensor.h>
+#include <cmath>
 #include "../op_common.h"
 #include "../elemwise_op_common.h"
 

From c5bbc169e28005d1ad92531be7f597ace461ca06 Mon Sep 17 00:00:00 2001
From: Iblis Lin <iblis@hs.ntnu.edu.tw>
Date: Tue, 2 Jan 2018 22:55:12 +0800
Subject: [PATCH 577/949] typo in c_api.h (#290)

---
 nnvm/include/nnvm/c_api.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/include/nnvm/c_api.h b/nnvm/include/nnvm/c_api.h
index 028c88d98154..2fd0bc3b2a1f 100644
--- a/nnvm/include/nnvm/c_api.h
+++ b/nnvm/include/nnvm/c_api.h
@@ -352,7 +352,7 @@ NNVM_DLL int NNGraphSetJSONAttr(GraphHandle handle,
  * \param success Whether the result is contained in out.
  * \return 0 when success, -1 when failure happens
  */
-NNVM_DLL int NNGraphGetJSONAttr(SymbolHandle handle,
+NNVM_DLL int NNGraphGetJSONAttr(GraphHandle handle,
                                 const char* key,
                                 const char** json_out,
                                 int *success);

From 98686f823d8b0aae82cd12b27824abbfb1083f89 Mon Sep 17 00:00:00 2001
From: masahi <masahi129@gmail.com>
Date: Sat, 6 Jan 2018 13:58:51 +0900
Subject: [PATCH 578/949] dump lowered IR when debug logging (#292)

* dump lowered ir when debug log

* avoid calling tvm.lower() twice when not debug
---
 nnvm/python/nnvm/compiler/build_module.py | 1 +
 nnvm/python/nnvm/testing/vgg.py           | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/nnvm/python/nnvm/compiler/build_module.py b/nnvm/python/nnvm/compiler/build_module.py
index 4d644ef0e5e7..c21db8297e03 100644
--- a/nnvm/python/nnvm/compiler/build_module.py
+++ b/nnvm/python/nnvm/compiler/build_module.py
@@ -101,6 +101,7 @@ def _lower(sch, inputs, func_name, graph):
     try:
         f = tvm.lower(sch, inputs, name=func_name)
         logging.debug("lower function %s", func_name)
+        logging.debug("%s", tvm.lower(sch, inputs, simple_mode=True))
     except Exception:
         msg = traceback.format_exc()
         msg += "Error during compile graph\n"
diff --git a/nnvm/python/nnvm/testing/vgg.py b/nnvm/python/nnvm/testing/vgg.py
index fe240d6eafcf..86b620684ef7 100644
--- a/nnvm/python/nnvm/testing/vgg.py
+++ b/nnvm/python/nnvm/testing/vgg.py
@@ -68,7 +68,7 @@ def get_symbol(num_classes, num_layers=11, batch_norm=False, dtype='float32'):
                 13: ([2, 2, 2, 2, 2], [64, 128, 256, 512, 512]),
                 16: ([2, 2, 3, 3, 3], [64, 128, 256, 512, 512]),
                 19: ([2, 2, 4, 4, 4], [64, 128, 256, 512, 512])}
-    if not vgg_spec.has_key(num_layers):
+    if num_layers not in vgg_spec:
         raise ValueError("Invalide num_layers {}. Choices are 11,13,16,19.".format(num_layers))
     layers, filters = vgg_spec[num_layers]
     data = sym.Variable(name="data")

From 4ea0352e81ceeed22c77e37085cd84604635b807 Mon Sep 17 00:00:00 2001
From: Hector Li <hecli@microsoft.com>
Date: Tue, 9 Jan 2018 14:37:39 -0800
Subject: [PATCH 579/949] =?UTF-8?q?Add=20=5F=5Fdeclspec(dllexport)=20to=20?=
 =?UTF-8?q?class=20Node=20and=20class=20Op=20if=20build=20on=20Wind?=
 =?UTF-8?q?=E2=80=A6=20(#296)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Add __declspec(dllexport) to class Node and class Op if build on Windows, so that it can be referenced in some test code in MXNet

* correct typo

* reuse the macro in c_api.h

* revert changes on base.h

* one more place to revert

* use enclosing extern c block

* revert a change caused by copy

* remove a space
---
 nnvm/include/nnvm/c_api.h | 19 ++++++++++---------
 nnvm/include/nnvm/node.h  |  3 ++-
 nnvm/include/nnvm/op.h    |  3 ++-
 3 files changed, 14 insertions(+), 11 deletions(-)

diff --git a/nnvm/include/nnvm/c_api.h b/nnvm/include/nnvm/c_api.h
index 2fd0bc3b2a1f..2f6134b08b48 100644
--- a/nnvm/include/nnvm/c_api.h
+++ b/nnvm/include/nnvm/c_api.h
@@ -8,21 +8,15 @@
 #ifndef NNVM_C_API_H_
 #define NNVM_C_API_H_
 
-#ifdef __cplusplus
-#define NNVM_EXTERN_C extern "C"
-#else
-#define NNVM_EXTERN_C
-#endif
-
 /*! \brief NNVM_DLL prefix for windows */
 #ifdef _WIN32
 #ifdef NNVM_EXPORTS
-#define NNVM_DLL NNVM_EXTERN_C __declspec(dllexport)
+#define NNVM_DLL __declspec(dllexport)
 #else
-#define NNVM_DLL NNVM_EXTERN_C __declspec(dllimport)
+#define NNVM_DLL __declspec(dllimport)
 #endif
 #else
-#define NNVM_DLL NNVM_EXTERN_C
+#define NNVM_DLL
 #endif
 
 /*! \brief manually define unsigned int */
@@ -35,6 +29,9 @@ typedef void *SymbolHandle;
 /*! \brief handle to Graph */
 typedef void *GraphHandle;
 
+#ifdef __cplusplus
+extern "C" {
+#endif
 /*!
  * \brief Set the last error message needed by C API
  * \param msg The error message to set.
@@ -384,4 +381,8 @@ NNVM_DLL int NNGraphApplyPasses(GraphHandle src,
                                 const char** pass_names,
                                 GraphHandle *dst);
 
+#ifdef __cplusplus
+} /* end extern "C" */
+#endif
+
 #endif  // NNVM_C_API_H_
diff --git a/nnvm/include/nnvm/node.h b/nnvm/include/nnvm/node.h
index 603ff3d05fb2..c3698ed75ec7 100644
--- a/nnvm/include/nnvm/node.h
+++ b/nnvm/include/nnvm/node.h
@@ -12,6 +12,7 @@
 #include <unordered_map>
 #include "./base.h"
 #include "./op.h"
+#include "./c_api.h"
 
 namespace nnvm {
 
@@ -94,7 +95,7 @@ struct NodeAttrs {
 /*!
  * \brief Node represents an operation in a computation graph.
  */
-class Node {
+class NNVM_DLL Node {
  public:
   /*! \brief The attributes in the node. */
   NodeAttrs attrs;
diff --git a/nnvm/include/nnvm/op.h b/nnvm/include/nnvm/op.h
index 3d82c44ab338..4d74c994277b 100644
--- a/nnvm/include/nnvm/op.h
+++ b/nnvm/include/nnvm/op.h
@@ -14,6 +14,7 @@
 #include <limits>
 #include <functional>
 #include "./base.h"
+#include "./c_api.h"
 
 namespace nnvm {
 
@@ -81,7 +82,7 @@ static const uint32_t kVarg = std::numeric_limits<uint32_t>::max();
  * }
  * \endcode
  */
-class Op {
+class NNVM_DLL Op {
  public:
   /*! \brief name of the operator */
   std::string name;

From b9a92d1313293a1a21dcaa2a974adaeb9083891b Mon Sep 17 00:00:00 2001
From: "Joshua Z. Zhang" <cheungchih@gmail.com>
Date: Tue, 9 Jan 2018 18:03:52 -0600
Subject: [PATCH 580/949] [WIP] Onnx1.0 (#294)

* add more op for onnx 1.0

* fix syntax

* fix lint

* fix

* update 1.0

* fix

* update model
---
 nnvm/python/nnvm/frontend/onnx.py             | 123 +++++++++++++++++-
 .../frontend/onnx/model_zoo/__init__.py       |   3 +-
 .../python/frontend/onnx/test_forward.py      |  12 +-
 3 files changed, 126 insertions(+), 12 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/onnx.py b/nnvm/python/nnvm/frontend/onnx.py
index c3a40ef89650..09f29affedf3 100644
--- a/nnvm/python/nnvm/frontend/onnx.py
+++ b/nnvm/python/nnvm/frontend/onnx.py
@@ -150,6 +150,90 @@ def _impl(inputs, attr, params):
         return _sym.dense(alpha * inputs[0], inputs[1], beta * inputs[2], units=channels)
     return _impl
 
+def _thresholded_relu():
+    def _impl(inputs, attr, params):
+        alpha = float(attr.get('alpha', 0.0))
+        return _sym.relu(inputs[0] - alpha)
+    return _impl
+
+def _scaled_tanh():
+    def _impl(inputs, attr, params):
+        alpha = float(attr.get('alpha', 1.0))
+        beta = float(attr.get('beta', 1.0))
+        return _sym.tanh(beta * inputs[0]) * alpha
+    return _impl
+
+def parametric_soft_plus():
+    def _impl(inputs, attr, params):
+        alpha = float(attr.get('alpha', 1.0))
+        beta = float(attr.get('beta', 1.0))
+        return _sym.log(_sym.exp(beta * inputs[0]) + 1) * alpha
+    return _impl
+
+def _scale():
+    def _impl(inputs, attr, params):
+        scale = float(attr.get('scale', 1.0))
+        return inputs[0] * scale
+    return _impl
+
+def _absolute():
+    """This is a workaround."""
+    def _impl(inputs, attr, params):
+        return _sym.relu(inputs[0]) + _sym.relu(_sym.negative(inputs[0]))
+    return _impl
+
+def _reciprocal():
+    def _impl(inputs, attr, params):
+        return 1.0 / inputs[0]
+    return _impl
+
+def _selu():
+    def _impl(inputs, attr, params):
+        alpha = float(attr.get('alpha', 1.6732))
+        gamma = float(attr.get('gamma', 1.0507))
+        return gamma * (-alpha * _sym.relu(1 - _sym.exp(inputs[0]))
+                        + _sym.relu(inputs[0]))
+    return _impl
+
+def _elu():
+    def _impl(inputs, attr, params):
+        alpha = float(attr.get('alpha', 1.0))
+        return -alpha * _sym.relu(1 - _sym.exp(inputs[0])) + _sym.relu(inputs[0])
+    return _impl
+
+def _prelu():
+    def _impl(inputs, attr, params):
+        assert len(inputs) == 2, "Prelu need 2 inputs, {} given".format(len(inputs))
+        channels = _infer_channels(inputs[1], params, False)
+        if channels == 1:
+            return inputs[0] * inputs[1]
+        return _sym.broadcast_mul(inputs[0], inputs[1])
+    return _impl
+
+def _softsign():
+    def _impl(inputs, attr, params):
+        return inputs[0] / (1 + _absolute()(inputs, attr, params))
+    return _impl
+
+def _softplus():
+    def _impl(inputs, attr, params):
+        return _sym.log(_sym.exp(x) + 1)
+    return _impl
+
+def _pad():
+    def _impl(inputs, attr, params):
+        # get number of channels
+        channels = _infer_channels(inputs[1], params, True)
+        attr['channels'] = channels
+        groups = attr.pop('group')
+        attr['groups'] = groups
+        return AttrCvt(
+            op_name='pad',
+            transforms={
+                'value': 'pad_value',
+                'pads': 'pad_width'},
+            custom_check=lambda attrs: attrs.get('mode') == 'constant')(inputs, attr)
+    return _impl
 
 # compatible operators that do NOT require any conversion.
 _identity_list = []
@@ -161,7 +245,22 @@ def _impl(inputs, attr, params):
 # for N to 1 mapping, currently not supported(?)
 _convert_map = {
     # defs/experimental
+    'Identity'      : Renamer('copy'),
+    # 'Affine'
+    'ThresholdedRelu': _thresholded_relu(),
+    'ScaledTanh'    : _scaled_tanh(),
+    'ParametricSoftplus': parametric_soft_plus(),
+    # 'ConstantFill'
+    # 'GivenTensorFill'
     'FC'            : AttrCvt('dense', ignores=['axis', 'axis_w']),
+    'Scale'         : _scale(),
+    # 'GRUUnit'
+    # 'ATen'
+    # 'ImageScaler'
+    # 'MeanVarianceNormalization'
+    # 'Crop'
+    # 'Embedding'
+    # 'Upsample'
     'SpatialBN'     : _batch_norm(),
 
     # defs/generator
@@ -179,28 +278,35 @@ def _impl(inputs, attr, params):
     'Mul'           : _elemwise('mul'),
     'Div'           : _elemwise('div'),
     'Neg'           : Renamer('negative'),
-    # 'Abs'
-    # 'Reciprocal'
+    'Abs'           : _absolute(),
+    'Reciprocal'    : _reciprocal(),
     # 'Floor'
     # 'Ceil'
-    # 'Sqrt'
+    'Sqrt'          : Renamer('sqrt'),
     'Relu'          : Renamer('relu'),
     'LeakyRelu'     : Renamer('leaky_relu'),
-    # 'Selu'
-    # 'Elu'
+    'Selu'          : _selu(),
+    'Elu'           : _elu(),
     'Exp'           : Renamer('exp'),
     'Log'           : Renamer('log'),
     'Tanh'          : Renamer('tanh'),
     # 'Pow'
-    # 'Dot'
-    # 'PRelu'
+    'PRelu'         : _prelu(),
     'Sigmoid'       : Renamer('sigmoid'),
+    # 'HardSigmoid'
     # 'Max' : this is the elemwise maximum
     # 'Min' : this is the elemwise minimum
     # 'Sum' : elemwise sum
+    # 'Mean'
+    # 'Clip'
     # softmax default axis is different in onnx
     'Softmax'       : AttrCvt('softmax', {'axis': ('axis', 1)}),
+    'LogSoftmax'    : AttrCvt('log_softmax', {'axis': ('axis', 1)}),
+    # 'Hardmax'
+    'Softsign'      : _softsign(),
+    'SoftPlus'      : _softplus(),
     'Gemm'          : _gemm(),
+    # 'MatMul'  batch stacked dot operation
 
     # defs/nn
     'AveragePool'   : _pooling('avg_pool'),
@@ -210,6 +316,8 @@ def _impl(inputs, attr, params):
     'GlobalAveragePool': Renamer('global_avg_pool2d'),
     'GlobalMaxPool' : Renamer('global_max_pool2d'),
     'BatchNormalization': _batch_norm(),
+    # 'InstanceNormalization'
+    # 'LpNormalization'
     'Dropout'       : AttrCvt('dropout', {'ratio': 'rate'}, ignores=['is_test']),
     'Flatten'       : Renamer('flatten'),
     # 'LRN'
@@ -233,6 +341,7 @@ def _impl(inputs, attr, params):
     'Transpose'     : AttrCvt('transpose', {'perm': 'axes'}),
     # 'Gather'
     # 'Squeeze'
+    'Pad'           : _pad(),
 }
 
 
diff --git a/nnvm/tests/python/frontend/onnx/model_zoo/__init__.py b/nnvm/tests/python/frontend/onnx/model_zoo/__init__.py
index ace19a32ae45..ee5093e07a97 100644
--- a/nnvm/tests/python/frontend/onnx/model_zoo/__init__.py
+++ b/nnvm/tests/python/frontend/onnx/model_zoo/__init__.py
@@ -24,7 +24,8 @@ def _as_abs_path(fname):
 URLS = {
     'super_resolution.onnx': 'https://gist.github.com/zhreshold/bcda4716699ac97ea44f791c24310193/raw/93672b029103648953c4e5ad3ac3aadf346a4cdc/super_resolution_0.2.onnx',
     'squeezenet1_1.onnx': 'https://gist.github.com/zhreshold/bcda4716699ac97ea44f791c24310193/raw/93672b029103648953c4e5ad3ac3aadf346a4cdc/squeezenet1_1_0.2.onnx',
-    'lenet.onnx': 'https://gist.github.com/zhreshold/bcda4716699ac97ea44f791c24310193/raw/93672b029103648953c4e5ad3ac3aadf346a4cdc/lenet_0.2.onnx'}
+    'lenet.onnx': 'https://gist.github.com/zhreshold/bcda4716699ac97ea44f791c24310193/raw/93672b029103648953c4e5ad3ac3aadf346a4cdc/lenet_0.2.onnx',
+    'resnet18_1_0.onnx': 'https://gist.github.com/zhreshold/bcda4716699ac97ea44f791c24310193/raw/b385b1b242dc89a35dd808235b885ed8a19aedc1/resnet18_1.0.onnx'}
 
 # download and add paths
 for k, v  in URLS.items():
diff --git a/nnvm/tests/python/frontend/onnx/test_forward.py b/nnvm/tests/python/frontend/onnx/test_forward.py
index 5831071869c6..20fbcefadee3 100644
--- a/nnvm/tests/python/frontend/onnx/test_forward.py
+++ b/nnvm/tests/python/frontend/onnx/test_forward.py
@@ -4,7 +4,7 @@
 from tvm.contrib import graph_runtime
 from nnvm.testing.config import ctx_list
 import onnx
-from model_zoo import super_resolution, squeezenet1_1, lenet
+from model_zoo import super_resolution, squeezenet1_1, lenet, resnet18_1_0
 
 def verify_onnx_forward_impl(graph_file, data_shape, out_shape):
     import onnx_caffe2.backend
@@ -44,7 +44,11 @@ def verify_squeezenet1_1():
 def verify_lenet():
     verify_onnx_forward_impl(lenet, (1, 1, 28, 28), (1, 10))
 
+def verify_resnet18():
+    verify_onnx_forward_impl(resnet18_1_0, (1, 3, 224, 224), (1, 1000))
+
 if __name__ == '__main__':
-    verify_super_resolution_example()
-    verify_squeezenet1_1()
-    verify_lenet()
+    # verify_super_resolution_example()
+    # verify_squeezenet1_1()
+    # verify_lenet()
+    verify_resnet18()

From 5b1183995bff31d4469a83a75294b42116bb43f9 Mon Sep 17 00:00:00 2001
From: masahi <masahi129@gmail.com>
Date: Fri, 12 Jan 2018 08:22:00 +0900
Subject: [PATCH 581/949] Upsampling op support (#298)

* add nnvm upsampling symbol

* add upsampling mxnet frontend

* add doc for upsampling op

* cleanup upsampling test

* minor fix

* use schedule_injective for upsampling

* upgrade tvm
---
 nnvm/include/nnvm/top/nn.h                    |  9 ++++
 nnvm/python/nnvm/frontend/mxnet.py            |  7 +++
 nnvm/python/nnvm/top/nn.py                    | 15 ++++++
 nnvm/src/top/nn/upsampling.cc                 | 52 +++++++++++++++++++
 nnvm/tests/python/compiler/test_top_level2.py | 20 +++++++
 5 files changed, 103 insertions(+)
 create mode 100644 nnvm/src/top/nn/upsampling.cc

diff --git a/nnvm/include/nnvm/top/nn.h b/nnvm/include/nnvm/top/nn.h
index 4210676db80e..3bd0d5f278ef 100644
--- a/nnvm/include/nnvm/top/nn.h
+++ b/nnvm/include/nnvm/top/nn.h
@@ -257,6 +257,15 @@ struct GlobalPool2DParam : public dmlc::Parameter<GlobalPool2DParam> {
   }
 };
 
+struct UpSamplingParam : public dmlc::Parameter<UpSamplingParam> {
+  int scale;
+
+  DMLC_DECLARE_PARAMETER(UpSamplingParam) {
+    DMLC_DECLARE_FIELD(scale)
+      .describe("upsampling scaling factor");
+  }
+};
+
 }  // namespace top
 }  // namespace nnvm
 
diff --git a/nnvm/python/nnvm/frontend/mxnet.py b/nnvm/python/nnvm/frontend/mxnet.py
index cd7b9fdc7ee9..738074f2dd2c 100644
--- a/nnvm/python/nnvm/frontend/mxnet.py
+++ b/nnvm/python/nnvm/frontend/mxnet.py
@@ -190,6 +190,12 @@ def _softmax_output(inputs, attrs):
         new_attrs['axis'] = 1
     return _get_nnvm_op(op_name)(inputs[0], **new_attrs)
 
+def _upsampling(inputs, attrs):
+    scale = attrs.get('scale')
+    new_attrs = {'scale':int(scale)}
+    return _get_nnvm_op('upsampling')(inputs[0], **new_attrs)
+
+
 _identity_list = ['__add_scalar__', '__add_symbol__', '__div_scalar__',
                   '__div_symbol__', '__mul_scalar__', '__mul_symbol__',
                   '__pow_scalar__', '__rdiv_scalar__', '__rpow_scalar__',
@@ -231,6 +237,7 @@ def _softmax_output(inputs, attrs):
     'min_axis'      : _rename('min'),
     'reshape'       : _reshape,
     'sum_axis'      : _rename('sum'),
+    'UpSampling'    : _upsampling
 }
 
 def _convert_symbol(op_name, inputs, attrs,
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index 5e2dae97f7fd..ccfdacb4cfd8 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -250,3 +250,18 @@ def schedule_global_avg_pool2d(_, outs, target):
         return topi.generic.schedule_global_pool(outs)
 
 reg.register_pattern("global_avg_pool2d", OpPattern.OUT_ELEMWISE_FUSABLE)
+
+
+@reg.register_compute("upsampling")
+def compute_upsampling(attrs, inputs, _):
+    """Compute definition of upsampling"""
+    scale = attrs.get_int("scale")
+    return topi.nn.upsampling(inputs[0], scale)
+
+@reg.register_schedule("upsampling")
+def schedule_upsampling(_, outs, target):
+    """Compute definition of upsampling"""
+    with tvm.target.create(target):
+        return topi.generic.schedule_injective(outs)
+
+reg.register_pattern("upsampling", OpPattern.OUT_ELEMWISE_FUSABLE)
diff --git a/nnvm/src/top/nn/upsampling.cc b/nnvm/src/top/nn/upsampling.cc
new file mode 100644
index 000000000000..608314a082c4
--- /dev/null
+++ b/nnvm/src/top/nn/upsampling.cc
@@ -0,0 +1,52 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file pooling.cc
+ * \brief Property def of pooling operators.
+ */
+#include <nnvm/op.h>
+#include <nnvm/node.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/top/nn.h>
+#include "./nn_common.h"
+#include "../op_common.h"
+#include "../elemwise_op_common.h"
+
+namespace nnvm {
+namespace top {
+
+DMLC_REGISTER_PARAMETER(UpSamplingParam);
+
+inline bool UpSamplingInferShape(const nnvm::NodeAttrs& attrs,
+                                   std::vector<TShape>* in_shape,
+                                   std::vector<TShape>* out_shape) {
+  const UpSamplingParam& param = nnvm::get<UpSamplingParam>(attrs.parsed);
+  CHECK_EQ(in_shape->size(), 1U);
+  CHECK_EQ(out_shape->size(), 1U);
+  TShape dshape = (*in_shape)[0];
+  if (dshape.ndim() ==  0) return false;
+  TShape oshape = dshape;
+  oshape[2] = oshape[2] * param.scale;
+  oshape[3] = oshape[3] * param.scale;
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, oshape);
+  return true;
+}
+
+NNVM_REGISTER_OP(upsampling)
+.describe(R"(Perform nearest neighbor upsampling to input array.
+
+- **data**: Input is 4D array of shape (batch_size, channels, in_height, in_width).
+- **out**: Output is 4D array of shape (batch_size, channels, in_height*scale, in_width*scale).
+
+)" NNVM_ADD_FILELINE)
+.add_argument("data", "4D Tensor", "Input data.")
+.add_arguments(UpSamplingParam::__FIELDS__())
+.set_attr_parser(ParamParser<UpSamplingParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<UpSamplingParam>)
+.set_attr<FInferShape>("FInferShape", UpSamplingInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_num_outputs(1)
+.set_num_inputs(1)
+.set_support_level(2);
+
+}  // namespace top
+}  // namespace nnvm
diff --git a/nnvm/tests/python/compiler/test_top_level2.py b/nnvm/tests/python/compiler/test_top_level2.py
index 84fe13c4be4e..d5da4d117967 100644
--- a/nnvm/tests/python/compiler/test_top_level2.py
+++ b/nnvm/tests/python/compiler/test_top_level2.py
@@ -148,6 +148,25 @@ def test_global_avg_pool2d():
         np.testing.assert_allclose(out.asnumpy(), b_np, rtol=1e-5)
 
 
+def test_upsampling():
+    x = sym.Variable("x")
+    scale = 2
+    y = sym.upsampling(x, scale=scale, name="y")
+    dtype = "float32"
+    dshape = (1, 16, 32, 32)
+    oshape = (1, 16, 32*scale, 32*scale)
+    shape_dict = {"x": dshape}
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
+        m = graph_runtime.create(graph, lib, ctx)
+        a_np = np.random.uniform(size=dshape).astype(dtype)
+        data = tvm.nd.array(a_np)
+        m.run(x=data)
+        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
+        b_np = topi.testing.upsampling_python(a_np, scale)
+        np.testing.assert_allclose(out.asnumpy(), b_np, rtol=1e-5)
+
+
 if __name__ == "__main__":
     test_conv2d()
     test_grouped_conv2d()
@@ -156,3 +175,4 @@ def test_global_avg_pool2d():
     test_avg_pool2d()
     test_global_max_pool2d()
     test_global_avg_pool2d()
+    test_upsampling()

From c5d2bfff8155af6329c259e16fa53192996ef825 Mon Sep 17 00:00:00 2001
From: thefiddler <sa@xcv.io>
Date: Sat, 13 Jan 2018 18:53:55 +0100
Subject: [PATCH 582/949] Add support for keras upsampling (#305)

* Add support for keras upsampling

* Fix code formatting

* Fix indentation

* Fix indentation round 2

* Hide unused parameter

* Only enable UpSampling2D since the others are not currently supported by TVM

* Improve error messages and code layout
---
 nnvm/python/nnvm/frontend/keras.py | 28 +++++++++++++++++++++++++++-
 1 file changed, 27 insertions(+), 1 deletion(-)

diff --git a/nnvm/python/nnvm/frontend/keras.py b/nnvm/python/nnvm/frontend/keras.py
index 1272d549b5c1..9b3540ae3c3c 100644
--- a/nnvm/python/nnvm/frontend/keras.py
+++ b/nnvm/python/nnvm/frontend/keras.py
@@ -268,6 +268,31 @@ def _convert_pooling(insym, keras_layer, symtab):
             raise TypeError("Unsupported pooling type : {}".format(keras_layer))
 
 
+def _convert_upsample(insym, keras_layer, _):
+    _check_data_format(keras_layer)
+    upsample_type = type(keras_layer).__name__
+
+    if upsample_type == "UpSampling1D":
+        h = keras_layer.size
+        params = {'scale': h}
+    elif upsample_type == "UpSampling2D":
+        h, w = keras_layer.size
+        if h != w:
+            raise TypeError("Unsupported upsampling type with different axes size : {}"
+                            .format(keras_layer.size))
+        params = {'scale': h}
+    elif upsample_type == "UpSampling3D":
+        h, w, d = keras_layer.size
+        if h != w or w != d:
+            raise TypeError("Unsupported upsampling type with different axes size : {}"
+                            .format(keras_layer.size))
+        params = {'scale': h}
+    else:
+        raise TypeError("Unsupported upsampling type : {}".format(upsample_type))
+
+    return _sym.upsampling(insym, **params)
+
+
 def _convert_batchnorm(insym, keras_layer, symtab):
     params = {'scale': False,
               'center': False,
@@ -358,6 +383,7 @@ def _default_skip(insym, keras_layer, _): # pylint: disable=unused-argument
     'Subtract'                 : _convert_merge,
     'Multiply'                 : _convert_merge,
     'ZeroPadding2D'            : _convert_padding,
+    'UpSampling2D'           : _convert_upsample,
 
     # 'ZeroPadding1D'          : _convert_padding,
     # 'AveragePooling1D'       : _convert_pooling,
@@ -367,7 +393,7 @@ def _default_skip(insym, keras_layer, _): # pylint: disable=unused-argument
     # 'Cropping1D'             : _convert_cropping,
     # 'Cropping2D'             : _convert_cropping,
     # 'UpSampling1D'           : _convert_upsample,
-    # 'UpSampling2D'           : _convert_upsample,
+    # 'UpSampling3D'           : _convert_upsample,
     # 'Conv1D'                 : _convert_convolution1d,
 
     # 'GRU'                    : _convert_gru,

From 27d77f71a8a468be4394c11b88aef515c8815850 Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Tue, 16 Jan 2018 01:44:34 +0800
Subject: [PATCH 583/949] remove dtype in model symbol (#310)

---
 nnvm/python/nnvm/testing/resnet.py | 19 ++++---------------
 nnvm/python/nnvm/testing/vgg.py    | 11 ++---------
 2 files changed, 6 insertions(+), 24 deletions(-)

diff --git a/nnvm/python/nnvm/testing/resnet.py b/nnvm/python/nnvm/testing/resnet.py
index 76b5c1d893b8..64eb63c29b7a 100644
--- a/nnvm/python/nnvm/testing/resnet.py
+++ b/nnvm/python/nnvm/testing/resnet.py
@@ -24,7 +24,6 @@
 Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun. "Identity Mappings in Deep Residual Networks"
 '''
 # pylint: disable=unused-argument
-import numpy as np
 from .. import symbol as sym
 from . utils import create_workload
 
@@ -91,7 +90,7 @@ def residual_unit(data, num_filter, stride, dim_match, name, bottle_neck=True):
         return sym.elemwise_add(conv2, shortcut)
 
 def resnet(units, num_stages, filter_list, num_classes, image_shape,
-           bottle_neck=True, dtype='float32'):
+           bottle_neck=True):
     """Return ResNet symbol of
     Parameters
     ----------
@@ -105,17 +104,10 @@ def resnet(units, num_stages, filter_list, num_classes, image_shape,
         Ouput size of symbol
     dataset : str
         Dataset type, only cifar10 and imagenet supports
-    dtype : str
-        Precision (float32 or float16)
     """
     num_unit = len(units)
     assert num_unit == num_stages
     data = sym.Variable(name='data')
-    if dtype == 'float32':
-        data = data
-    else:
-        if dtype == 'float16':
-            data = sym.cast(data=data, dtype=np.float16)
     data = sym.batch_norm(data=data, epsilon=2e-5, name='bn_data')
     (_, height, _) = image_shape
     if height <= 32:            # such as cifar10
@@ -144,11 +136,9 @@ def resnet(units, num_stages, filter_list, num_classes, image_shape,
     pool1 = sym.global_avg_pool2d(data=relu1, name='pool1')
     flat = sym.flatten(data=pool1)
     fc1 = sym.dense(data=flat, units=num_classes, name='fc1')
-    if dtype == 'float16':
-        fc1 = sym.cast(data=fc1, dtype=np.float32)
     return sym.softmax(data=fc1, name='softmax')
 
-def get_symbol(num_classes, num_layers=50, image_shape=(3, 224, 224), dtype='float32', **kwargs):
+def get_symbol(num_classes, num_layers=50, image_shape=(3, 224, 224), **kwargs):
     """
     Adapted from https://github.com/tornadomeet/ResNet/blob/master/train_resnet.py
     Original author Wei Wu
@@ -197,8 +187,7 @@ def get_symbol(num_classes, num_layers=50, image_shape=(3, 224, 224), dtype='flo
                   filter_list=filter_list,
                   num_classes=num_classes,
                   image_shape=image_shape,
-                  bottle_neck=bottle_neck,
-                  dtype=dtype)
+                  bottle_neck=bottle_neck)
 
 def get_workload(batch_size=1, num_classes=1000, num_layers=18,
                  image_shape=(3, 224, 224), dtype="float32", **kwargs):
@@ -233,5 +222,5 @@ def get_workload(batch_size=1, num_classes=1000, num_layers=18,
         The parameters.
     """
     net = get_symbol(num_classes=num_classes, num_layers=num_layers,
-                     image_shape=image_shape, dtype=dtype, **kwargs)
+                     image_shape=image_shape, **kwargs)
     return create_workload(net, batch_size, image_shape, dtype)
diff --git a/nnvm/python/nnvm/testing/vgg.py b/nnvm/python/nnvm/testing/vgg.py
index 86b620684ef7..2c290bdc3c68 100644
--- a/nnvm/python/nnvm/testing/vgg.py
+++ b/nnvm/python/nnvm/testing/vgg.py
@@ -20,7 +20,6 @@
 Simonyan, Karen, and Andrew Zisserman. "Very deep convolutional networks for
 large-scale image recognition." arXiv preprint arXiv:1409.1556 (2014).
 """
-import numpy as np
 from .. import symbol as sym
 from . utils import create_workload
 
@@ -51,7 +50,7 @@ def get_classifier(input_data, num_classes):
     fc8 = sym.dense(data=drop7, units=num_classes, name="fc8")
     return fc8
 
-def get_symbol(num_classes, num_layers=11, batch_norm=False, dtype='float32'):
+def get_symbol(num_classes, num_layers=11, batch_norm=False):
     """
     Parameters
     ----------
@@ -61,8 +60,6 @@ def get_symbol(num_classes, num_layers=11, batch_norm=False, dtype='float32'):
         Number of layers for the variant of densenet. Options are 11, 13, 16, 19.
     batch_norm : bool, default False
         Use batch normalization.
-    dtype: str, float32 or float16
-        Data precision.
     """
     vgg_spec = {11: ([1, 1, 2, 2, 2], [64, 128, 256, 512, 512]),
                 13: ([2, 2, 2, 2, 2], [64, 128, 256, 512, 512]),
@@ -72,12 +69,8 @@ def get_symbol(num_classes, num_layers=11, batch_norm=False, dtype='float32'):
         raise ValueError("Invalide num_layers {}. Choices are 11,13,16,19.".format(num_layers))
     layers, filters = vgg_spec[num_layers]
     data = sym.Variable(name="data")
-    if dtype == 'float16':
-        data = sym.cast(data=data, dtype=np.float16)
     feature = get_feature(data, layers, filters, batch_norm)
     classifier = get_classifier(feature, num_classes)
-    if dtype == 'float16':
-        classifier = sym.cast(data=classifier, dtype=np.float32)
     symbol = sym.softmax(data=classifier, name='softmax')
     return symbol
 
@@ -110,5 +103,5 @@ def get_workload(batch_size, num_classes=1000, image_shape=(3, 224, 224),
     params : dict of str to NDArray
         The parameters.
     """
-    net = get_symbol(num_classes=num_classes, dtype=dtype, **kwargs)
+    net = get_symbol(num_classes=num_classes, **kwargs)
     return create_workload(net, batch_size, image_shape, dtype)

From 708c83aa65db6d94ae28632cf184f725330d44bc Mon Sep 17 00:00:00 2001
From: thefiddler <sa@xcv.io>
Date: Mon, 15 Jan 2018 18:44:45 +0100
Subject: [PATCH 584/949] Avoid crash when linear activation does not have
 alpha and beta defined (#306)

---
 nnvm/python/nnvm/frontend/keras.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/nnvm/python/nnvm/frontend/keras.py b/nnvm/python/nnvm/frontend/keras.py
index 9b3540ae3c3c..4314a496c159 100644
--- a/nnvm/python/nnvm/frontend/keras.py
+++ b/nnvm/python/nnvm/frontend/keras.py
@@ -27,8 +27,10 @@ def _convert_activation(insym, keras_layer, _):
     if act_type == 'linear':
         if isinstance(keras_layer, str):
             return insym
+        alpha = keras_layer.alpha if hasattr(keras_layer, "alpha") else 1
+        beta = keras_layer.beta if hasattr(keras_layer, "beta") else 0
         return _sym.__add_scalar__(_sym.__mul_scalar__(insym, \
-            scalar=keras_layer.alpha), scalar=keras_layer.beta)
+            scalar=alpha), scalar=beta)
     elif act_type == 'softmax':
         return _sym.softmax(insym)
     elif act_type == 'sigmoid':

From 4061dcf8d2205996857e68290b5fcf5a8a610066 Mon Sep 17 00:00:00 2001
From: guopinglong <35064637+guopinglong@users.noreply.github.com>
Date: Tue, 16 Jan 2018 01:45:08 +0800
Subject: [PATCH 585/949] make shape inference of BatchNorm layout neutral
 (#301)

* make shape inference of BatchNorm layout neutral

* refactor to use the axis variable to do BatchNorm shape inference

* refactor to use the axis variable to do BatchNorm shape inference

* add unittest to the axis param for batch norm shape inference
---
 nnvm/src/top/nn/nn.cc                          |  5 ++++-
 nnvm/tests/python/unittest/test_infer_shape.py | 16 ++++++++++++++++
 2 files changed, 20 insertions(+), 1 deletion(-)

diff --git a/nnvm/src/top/nn/nn.cc b/nnvm/src/top/nn/nn.cc
index c9dde3ebb4ee..0bf1d2921fd2 100644
--- a/nnvm/src/top/nn/nn.cc
+++ b/nnvm/src/top/nn/nn.cc
@@ -117,12 +117,15 @@ DMLC_REGISTER_PARAMETER(BatchNormParam);
 inline bool BatchNormInferShape(const nnvm::NodeAttrs& attrs,
                                 std::vector<TShape>* in_shape,
                                 std::vector<TShape>* out_shape) {
+  const BatchNormParam& param = nnvm::get<BatchNormParam>(attrs.parsed);
   CHECK_EQ(in_shape->size(), 5U)
       << "Input:[data, gamma, beta, moving_mean, moving_var]";
   CHECK_EQ(out_shape->size(), 3U);
   const TShape &dshape = in_shape->at(0);
   if (dshape.ndim() == 0) return false;
-  TShape bshape({dshape[1]});
+  CHECK((size_t)param.axis < dshape.Size());
+
+  TShape bshape({dshape[param.axis]});
   NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, 1, bshape);
   NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, 2, bshape);
   NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, 3, bshape);
diff --git a/nnvm/tests/python/unittest/test_infer_shape.py b/nnvm/tests/python/unittest/test_infer_shape.py
index 251ea36f6a53..7af65c616bce 100644
--- a/nnvm/tests/python/unittest/test_infer_shape.py
+++ b/nnvm/tests/python/unittest/test_infer_shape.py
@@ -62,6 +62,22 @@ def test_batchnorm():
     sdict = infer_shape(y)
     assert(sdict["bn_gamma"][0] == [20])
 
+    x = sym.Variable("x", shape=(10, 20, 30, 40))
+    y = sym.batch_norm(data=x, axis=0, epsilon=2e-5, name='bn')
+    sdict = infer_shape(y)
+    assert(sdict['bn_moving_var'][0] == [10])
+
+    y = sym.batch_norm(data=x, axis=1, epsilon=2e-5, name='bn')
+    sdict = infer_shape(y)
+    assert(sdict['bn_gamma'][0] == [20])
+
+    y = sym.batch_norm(data=x, axis=2, epsilon=2e-5, name='bn')
+    sdict = infer_shape(y)
+    assert(sdict['bn_beta'][0] == [30])
+
+    y = sym.batch_norm(data=x, axis=3, epsilon=2e-5, name='bn')
+    sdict = infer_shape(y)
+    assert(sdict['bn_moving_mean'][0] == [40])
 
 def test_flatten():
     x = sym.Variable("x", shape=(10, 20, 10))

From 0fbc90ad683bf96545be5307a436da096af04a6b Mon Sep 17 00:00:00 2001
From: Yuwei Hu <huyuwei1995@gmail.com>
Date: Tue, 16 Jan 2018 01:45:32 +0800
Subject: [PATCH 586/949] [CI] turn on keras frontend test (#309)

* [CI] turn on keras frontend test

* fix

* using tensorflow cpu version
---
 nnvm/python/nnvm/frontend/keras.py            |  4 +-
 .../python/frontend/keras/test_forward.py     | 49 +++++++++++--------
 nnvm/tests/scripts/task_frontend_test.sh      |  3 ++
 3 files changed, 33 insertions(+), 23 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/keras.py b/nnvm/python/nnvm/frontend/keras.py
index 4314a496c159..f29e24ea712f 100644
--- a/nnvm/python/nnvm/frontend/keras.py
+++ b/nnvm/python/nnvm/frontend/keras.py
@@ -273,7 +273,6 @@ def _convert_pooling(insym, keras_layer, symtab):
 def _convert_upsample(insym, keras_layer, _):
     _check_data_format(keras_layer)
     upsample_type = type(keras_layer).__name__
-
     if upsample_type == "UpSampling1D":
         h = keras_layer.size
         params = {'scale': h}
@@ -291,7 +290,6 @@ def _convert_upsample(insym, keras_layer, _):
         params = {'scale': h}
     else:
         raise TypeError("Unsupported upsampling type : {}".format(upsample_type))
-
     return _sym.upsampling(insym, **params)
 
 
@@ -385,7 +383,7 @@ def _default_skip(insym, keras_layer, _): # pylint: disable=unused-argument
     'Subtract'                 : _convert_merge,
     'Multiply'                 : _convert_merge,
     'ZeroPadding2D'            : _convert_padding,
-    'UpSampling2D'           : _convert_upsample,
+    'UpSampling2D'             : _convert_upsample,
 
     # 'ZeroPadding1D'          : _convert_padding,
     # 'AveragePooling1D'       : _convert_pooling,
diff --git a/nnvm/tests/python/frontend/keras/test_forward.py b/nnvm/tests/python/frontend/keras/test_forward.py
index 4c69b6da15df..b89176217817 100644
--- a/nnvm/tests/python/frontend/keras/test_forward.py
+++ b/nnvm/tests/python/frontend/keras/test_forward.py
@@ -39,7 +39,7 @@ def get_tvm_output(x, target, ctx, input_name='data', dtype='float32'):
         np.testing.assert_allclose(keras_out, tvm_out, rtol=1e-5, atol=1e-5)
 
 
-def verify_forward_softrelu():
+def test_forward_softrelu():
     data = keras.layers.Input(shape=(32,32,3))
     x = keras.layers.Activation('softplus')(data)
     x = keras.layers.Concatenate()([x, x])
@@ -48,7 +48,7 @@ def verify_forward_softrelu():
     verify_keras_frontend(keras_model)
 
 
-def verify_forward_leaky_relu():
+def test_forward_leaky_relu():
     data = keras.layers.Input(shape=(32,32,3))
     x = keras.layers.LeakyReLU(alpha=0.3)(data)
     x = keras.layers.Add()([x, x])
@@ -57,7 +57,7 @@ def verify_forward_leaky_relu():
     verify_keras_frontend(keras_model)
 
 
-def verify_forward_dense():
+def test_forward_dense():
     data = keras.layers.Input(shape=(32,32,3))
     x = keras.layers.MaxPooling2D(pool_size=(2,2))(data)
     x = keras.layers.Flatten()(x)
@@ -66,7 +66,7 @@ def verify_forward_dense():
     verify_keras_frontend(keras_model)
 
 
-def verify_forward_transpose_conv():
+def test_forward_transpose_conv():
     data = keras.layers.Input(shape=(32,32,3))
     x = keras.layers.Conv2D(filters=10, kernel_size=(3,3), strides=(2,2), padding='same')(data)
     x = keras.applications.mobilenet.DepthwiseConv2D(kernel_size=(3,3), padding='same')(x)
@@ -76,7 +76,7 @@ def verify_forward_transpose_conv():
     verify_keras_frontend(keras_model)
 
 
-def verify_forward_separable_conv():
+def test_forward_separable_conv():
     data = keras.layers.Input(shape=(32,32,3))
     x = keras.layers.SeparableConv2D(filters=10, kernel_size=(3,3),
         padding='same', activation='relu')(data)
@@ -87,31 +87,40 @@ def verify_forward_separable_conv():
     verify_keras_frontend(keras_model)
 
 
-def verify_forward_vgg16():
-    keras_model = keras.applications.vgg16.VGG16(include_top=True, weights='imagenet',
+def test_forward_upsample():
+    data = keras.layers.Input(shape=(32,32,3))
+    x = keras.layers.UpSampling2D(size=(3,3))(data)
+    x = keras.layers.GlobalAveragePooling2D()(x)
+    keras_model = keras.models.Model(data, x)
+    verify_keras_frontend(keras_model)
+
+
+def test_forward_vgg16():
+    keras_model = keras.applications.vgg16.VGG16(include_top=True, weights=None,
         input_shape=(224,224,3), classes=1000)
     verify_keras_frontend(keras_model)
 
 
-def verify_forward_xception():
-    keras_model = keras.applications.xception.Xception(include_top=True, weights='imagenet',
+def test_forward_xception():
+    keras_model = keras.applications.xception.Xception(include_top=True, weights=None,
         input_shape=(299,299,3), classes=1000)
     verify_keras_frontend(keras_model)
 
 
-def verify_forward_resnet50():
-    keras_model = keras.applications.resnet50.ResNet50(include_top=True, weights='imagenet',
+def test_forward_resnet50():
+    keras_model = keras.applications.resnet50.ResNet50(include_top=True, weights=None,
         input_shape=(224,224,3), classes=1000)
     verify_keras_frontend(keras_model)
 
 
 if __name__ == '__main__':
-    verify_forward_softrelu()
-    verify_forward_leaky_relu()
-    verify_forward_dense()
-    verify_forward_transpose_conv()
-    verify_forward_separable_conv()
-
-    verify_forward_vgg16()
-    verify_forward_xception()
-    verify_forward_resnet50()
+    test_forward_softrelu()
+    test_forward_leaky_relu()
+    test_forward_dense()
+    test_forward_transpose_conv()
+    test_forward_separable_conv()
+    test_forward_upsample()
+
+    test_forward_vgg16()
+    test_forward_xception()
+    test_forward_resnet50()
diff --git a/nnvm/tests/scripts/task_frontend_test.sh b/nnvm/tests/scripts/task_frontend_test.sh
index 9218a5c08e0b..5881d1846a0f 100755
--- a/nnvm/tests/scripts/task_frontend_test.sh
+++ b/nnvm/tests/scripts/task_frontend_test.sh
@@ -7,3 +7,6 @@ python -m nose -v tests/python/frontend/onnx || exit -1
 
 echo "Running MXNet frontend test..."
 python -m nose -v tests/python/frontend/mxnet || exit -1
+
+echo "Running Keras frontend test..."
+python -m nose -v tests/python/frontend/keras || exit -1

From 49e8c208d4493977b980e06b891566331812c714 Mon Sep 17 00:00:00 2001
From: thefiddler <sa@xcv.io>
Date: Wed, 17 Jan 2018 04:15:35 +0100
Subject: [PATCH 587/949] Update frontend for keras 2.1.3 compatibility (#314)

* Keras keeps renaming properties. Update frontend for keras 2.1.3 compatibility

* Add error message when inbound_nodes is not found
---
 nnvm/python/nnvm/frontend/keras.py | 19 +++++++++++++++++--
 1 file changed, 17 insertions(+), 2 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/keras.py b/nnvm/python/nnvm/frontend/keras.py
index f29e24ea712f..d516d3f33a0e 100644
--- a/nnvm/python/nnvm/frontend/keras.py
+++ b/nnvm/python/nnvm/frontend/keras.py
@@ -349,7 +349,14 @@ def _convert_concat(insym, keras_layer, _):
 
 
 def _convert_reshape(insym, keras_layer, _):
-    return _sym.reshape(insym, keras_layer.shape)
+    shape = keras_layer.shape if hasattr(keras_layer, 'shape') else \
+                keras_layer.target_shape if hasattr(keras_layer, 'target_shape') else\
+                None
+
+    if shape is None:
+        raise TypeError("No shape attribute in reshape layer: {}".format(keras_layer))
+
+    return _sym.reshape(insym, shape=shape)
 
 
 def _default_skip(insym, keras_layer, _): # pylint: disable=unused-argument
@@ -477,7 +484,15 @@ def from_keras(model):
             symtab.get_var(keras_layer.name, must_contain=False)
         else:
             predecessors = []
-            for node in keras_layer.inbound_nodes:
+            inbound_nodes = keras_layer.inbound_nodes if hasattr(keras_layer, 'inbound_nodes') \
+                        else keras_layer._inbound_nodes if hasattr(keras_layer, '_inbound_nodes') \
+                        else None
+
+            if inbound_nodes is None:
+                raise TypeError("Unknown layer type or unsupported Keras version : {}"
+                                .format(keras_layer))
+
+            for node in inbound_nodes:
                 for pred in node.inbound_layers:
                     predecessors.append(pred.name)
             if len(predecessors) == 1:

From fdf54ec51a265a3666f81ec2cf8b4fe101b4c440 Mon Sep 17 00:00:00 2001
From: Yuwei Hu <huyuwei1995@gmail.com>
Date: Thu, 18 Jan 2018 02:13:50 +0800
Subject: [PATCH 588/949] [Keras] fix convert_pooling with same pad (#322)

---
 nnvm/python/nnvm/frontend/keras.py | 65 +++++++++++++-----------------
 1 file changed, 27 insertions(+), 38 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/keras.py b/nnvm/python/nnvm/frontend/keras.py
index d516d3f33a0e..30c0a132ff5f 100644
--- a/nnvm/python/nnvm/frontend/keras.py
+++ b/nnvm/python/nnvm/frontend/keras.py
@@ -16,6 +16,14 @@ def _check_data_format(keras_layer):
             raise ValueError("Keras frontend currently supports data_format = channels_last only.")
 
 
+def _get_pad_pair(input1d, kernel1d, stride1d):
+    out1d = (input1d + stride1d - 1) // stride1d
+    pad = np.maximum((out1d - 1) * stride1d + kernel1d - input1d, 0)
+    pad_before = pad // 2
+    pad_after = pad - pad_before
+    return [pad_before, pad_after]
+
+
 def _convert_activation(insym, keras_layer, _):
     if isinstance(keras_layer, str):
         act_type = keras_layer
@@ -120,6 +128,8 @@ def _convert_convolution(insym, keras_layer, symtab):
         dilation = [keras_layer.dilation_rate[0], keras_layer.dilation_rate[1]]
     else:
         dilation = [keras_layer.dilation_rate, keras_layer.dilation_rate]
+    kernel_h = (kernel_h - 1) * dilation[0] + 1
+    kernel_w = (kernel_w - 1) * dilation[1] + 1
     stride_h, stride_w = keras_layer.strides
     params = {'weight': symtab.new_const(weight),
               'kernel_size': [kernel_h, kernel_w],
@@ -141,14 +151,8 @@ def _convert_convolution(insym, keras_layer, symtab):
     elif keras_layer.padding == 'same':
         in_h = keras_layer.input.shape[1].value
         in_w = keras_layer.input.shape[2].value
-        out_h = (in_h + stride_h - 1) // stride_h
-        out_w = (in_w + stride_w - 1) // stride_w
-        pad_h = np.maximum((out_h - 1) * stride_h + kernel_h - in_h, 0)
-        pad_w = np.maximum((out_w - 1) * stride_w + kernel_w - in_w, 0)
-        pad_t = pad_h // 2
-        pad_l = pad_w // 2
-        pad_b = pad_h - pad_t
-        pad_r = pad_w - pad_l
+        pad_t, pad_b = _get_pad_pair(in_h, kernel_h, stride_h)
+        pad_l, pad_r = _get_pad_pair(in_w, kernel_w, stride_w)
         insym = _sym.pad(data=insym, pad_width=((0, 0), (0, 0), (pad_t, pad_b), (pad_l, pad_r)))
     else:
         raise TypeError("Unsupported padding type : {}".format(keras_layer.padding))
@@ -187,14 +191,8 @@ def _convert_separable_convolution(insym, keras_layer, symtab):
     elif keras_layer.padding == 'same':
         in_h = keras_layer.input.shape[1].value
         in_w = keras_layer.input.shape[2].value
-        out_h = (in_h + stride_h - 1) // stride_h
-        out_w = (in_w + stride_w - 1) // stride_w
-        pad_h = np.maximum((out_h - 1) * stride_h + kernel_h - in_h, 0)
-        pad_w = np.maximum((out_w - 1) * stride_w + kernel_w - in_w, 0)
-        pad_t = pad_h // 2
-        pad_l = pad_w // 2
-        pad_b = pad_h - pad_t
-        pad_r = pad_w - pad_l
+        pad_t, pad_b = _get_pad_pair(in_h, kernel_h, stride_h)
+        pad_l, pad_r = _get_pad_pair(in_w, kernel_w, stride_w)
         insym = _sym.pad(data=insym, pad_width=(
             (0, 0), (0, 0), (pad_t, pad_b), (pad_l, pad_r)))
     else:
@@ -242,23 +240,18 @@ def _convert_pooling(insym, keras_layer, symtab):
         pool_h, pool_w = keras_layer.pool_size
         stride_h, stride_w = keras_layer.strides
         params = {'pool_size': [pool_h, pool_w],
-                  'strides': [stride_h, stride_w]}
+                  'strides': [stride_h, stride_w],
+                  'padding': [0, 0]}
         if keras_layer.padding == 'valid':
-            params['padding'] = [0, 0]
+            pass
+        # we insert a separate pad operator
         elif keras_layer.padding == 'same':
             in_h = keras_layer.input.shape[1].value
             in_w = keras_layer.input.shape[2].value
-            out_h = (in_h + stride_h - 1) // stride_h
-            out_w = (in_w + stride_w - 1) // stride_w
-            pad_h = np.maximum((out_h - 1) * stride_h + pool_h - in_h, 0)
-            pad_w = np.maximum((out_w - 1) * stride_w + pool_w - in_w, 0)
-            pad_t = pad_h // 2
-            pad_l = pad_w // 2
-            pad_b = pad_h - pad_t
-            pad_r = pad_w - pad_l
-            params['padding'] = [pad_t, pad_l]
-            if pad_b > pad_t and pad_r > pad_l:
-                params['ceil_mode'] = True
+            pad_t, pad_b = _get_pad_pair(in_h, pool_h, stride_h)
+            pad_l, pad_r = _get_pad_pair(in_w, pool_w, stride_w)
+            insym = _sym.pad(data=insym, pad_width=(
+                (0, 0), (0, 0), (pad_t, pad_b), (pad_l, pad_r)))
         else:
             raise TypeError("Unsupported padding type : {}".format(keras_layer.padding))
         if pool_type == 'MaxPooling2D':
@@ -349,13 +342,11 @@ def _convert_concat(insym, keras_layer, _):
 
 
 def _convert_reshape(insym, keras_layer, _):
-    shape = keras_layer.shape if hasattr(keras_layer, 'shape') else \
-                keras_layer.target_shape if hasattr(keras_layer, 'target_shape') else\
-                None
-
+    shape = keras_layer.shape if hasattr(keras_layer, 'shape') \
+       else keras_layer.target_shape if hasattr(keras_layer, 'target_shape') \
+       else None
     if shape is None:
         raise TypeError("No shape attribute in reshape layer: {}".format(keras_layer))
-
     return _sym.reshape(insym, shape=shape)
 
 
@@ -485,13 +476,11 @@ def from_keras(model):
         else:
             predecessors = []
             inbound_nodes = keras_layer.inbound_nodes if hasattr(keras_layer, 'inbound_nodes') \
-                        else keras_layer._inbound_nodes if hasattr(keras_layer, '_inbound_nodes') \
-                        else None
-
+                       else keras_layer._inbound_nodes if hasattr(keras_layer, '_inbound_nodes') \
+                       else None
             if inbound_nodes is None:
                 raise TypeError("Unknown layer type or unsupported Keras version : {}"
                                 .format(keras_layer))
-
             for node in inbound_nodes:
                 for pred in node.inbound_layers:
                     predecessors.append(pred.name)

From 5615880fe62d907b429e67ec5c5ed6e07604083b Mon Sep 17 00:00:00 2001
From: yuruofeifei <yuruofeifei@gmail.com>
Date: Wed, 17 Jan 2018 13:21:53 -0800
Subject: [PATCH 589/949] [GRADIENT] Register more gradient operators (#300)

* Add conv2d max_pool backward op

* Added tests

* Fix testing

* Address comments

* Change dot to matmul

* Address comments

* Break down indicator function

* Make greater, less numpy compatible
---
 nnvm/docs/top.rst                             |  12 ++
 nnvm/include/nnvm/top/tensor.h                |  78 +++++++-
 nnvm/python/nnvm/compiler/build_module.py     |   2 +-
 nnvm/python/nnvm/compiler/graph_util.py       |  57 ++++++
 nnvm/python/nnvm/graph.py                     |  36 ----
 nnvm/src/pass/gradient.cc                     |   9 +-
 nnvm/src/top/elemwise_op_common.h             |  63 +++++-
 nnvm/src/top/nn/convolution.cc                |  37 +++-
 nnvm/src/top/nn/nn.cc                         | 125 +++++++++++-
 nnvm/src/top/nn/pooling.cc                    |  22 ++
 nnvm/src/top/op_common.h                      |  55 ++++-
 nnvm/src/top/tensor/elemwise.cc               | 189 ++++++++++--------
 nnvm/src/top/tensor/matrix_op.cc              | 138 +++++++++++++
 nnvm/src/top/tensor/reduce.cc                 |  83 ++++++--
 nnvm/src/top/tensor/transform.cc              | 129 +++++++++++-
 nnvm/tests/cpp/tuple_test.cc                  |   2 +-
 nnvm/tests/python/unittest/test_graph.py      |   5 +-
 .../python/unittest/test_graph_gradient.py    | 136 +++++++++++++
 18 files changed, 1016 insertions(+), 162 deletions(-)
 create mode 100644 nnvm/src/top/tensor/matrix_op.cc
 create mode 100644 nnvm/tests/python/unittest/test_graph_gradient.py

diff --git a/nnvm/docs/top.rst b/nnvm/docs/top.rst
index fc22eec86116..c34ae5b54b07 100644
--- a/nnvm/docs/top.rst
+++ b/nnvm/docs/top.rst
@@ -28,6 +28,7 @@ This level enables fully connected multi-layer perceptron.
    :nosignatures:
 
    nnvm.symbol.dense
+   nnvm.symbol.matmul
    nnvm.symbol.relu
    nnvm.symbol.tanh
    nnvm.symbol.sigmoid
@@ -38,6 +39,7 @@ This level enables fully connected multi-layer perceptron.
    nnvm.symbol.elemwise_sub
    nnvm.symbol.elemwise_mul
    nnvm.symbol.elemwise_div
+   nnvm.symbol.elemwise_sum
    nnvm.symbol.full
    nnvm.symbol.full_like
    nnvm.symbol.ones
@@ -54,6 +56,8 @@ This level enables fully connected multi-layer perceptron.
    nnvm.symbol.softmax
    nnvm.symbol.log_softmax
    nnvm.symbol.pad
+   nnvm.symbol.block_grad
+   nnvm.symbol.indicator
 
 
 **Level 2: Convolutions**
@@ -77,6 +81,8 @@ This level enables typical convnet models.
    :nosignatures:
 
    nnvm.symbol.reshape
+   nnvm.symbol.reshape_like
+   nnvm.symbol.expand_like
    nnvm.symbol.copy
    nnvm.symbol.negative
    nnvm.symbol.leaky_relu
@@ -107,6 +113,7 @@ This level enables typical convnet models.
 Detailed Definitions
 --------------------
 .. autofunction:: nnvm.symbol.dense
+.. autofunction:: nnvm.symbol.matmul
 .. autofunction:: nnvm.symbol.relu
 .. autofunction:: nnvm.symbol.tanh
 .. autofunction:: nnvm.symbol.sigmoid
@@ -117,6 +124,7 @@ Detailed Definitions
 .. autofunction:: nnvm.symbol.elemwise_sub
 .. autofunction:: nnvm.symbol.elemwise_mul
 .. autofunction:: nnvm.symbol.elemwise_div
+.. autofunction:: nnvm.symbol.elemwise_sum
 .. autofunction:: nnvm.symbol.full
 .. autofunction:: nnvm.symbol.full_like
 .. autofunction:: nnvm.symbol.ones
@@ -133,6 +141,8 @@ Detailed Definitions
 .. autofunction:: nnvm.symbol.softmax
 .. autofunction:: nnvm.symbol.log_softmax
 .. autofunction:: nnvm.symbol.pad
+.. autofunction:: nnvm.symbol.block_grad
+.. autofunction:: nnvm.symbol.indicator
 
 .. autofunction:: nnvm.symbol.conv2d
 .. autofunction:: nnvm.symbol.conv2d_transpose
@@ -142,6 +152,8 @@ Detailed Definitions
 .. autofunction:: nnvm.symbol.global_avg_pool2d
 
 .. autofunction:: nnvm.symbol.reshape
+.. autofunction:: nnvm.symbol.reshape_like
+.. autofunction:: nnvm.symbol.expand_like
 .. autofunction:: nnvm.symbol.copy
 .. autofunction:: nnvm.symbol.negative
 .. autofunction:: nnvm.symbol.leaky_relu
diff --git a/nnvm/include/nnvm/top/tensor.h b/nnvm/include/nnvm/top/tensor.h
index 9ebeeb01273f..7f50e49b996c 100644
--- a/nnvm/include/nnvm/top/tensor.h
+++ b/nnvm/include/nnvm/top/tensor.h
@@ -62,6 +62,13 @@ enum TypeFlag {
   kUint64 = 10,
 };
 
+enum IndicatorRuleFlag {
+  kGT0 = 0,
+  kLT0 = 1,
+  kMax = 2,
+  kMin = 3,
+};
+
 #define DMLC_DECLARE_DTYPE_FIELD(name)                              \
   DMLC_DECLARE_FIELD(name)                                          \
   .add_enum("float16", kFloat16)                                    \
@@ -84,6 +91,28 @@ struct CastParam : public dmlc::Parameter<CastParam> {
   }
 };
 
+struct IndicatorParam : public dmlc::Parameter<IndicatorParam> {
+  TShape axis;
+  bool exclude;
+  DMLC_DECLARE_PARAMETER(IndicatorParam) {
+    DMLC_DECLARE_FIELD(axis).set_default(TShape())
+    .describe(R"code(The axis or axes along which to perform the indicator rule.
+
+        The default, `axis=()`, will compute over all elements into a
+        scalar array with shape `(1,)`.
+
+        If `axis` is int, rule is applied on a particular axis.
+
+        If `axis` is a tuple of ints, rule is applied on all the axes
+        specified in the tuple.
+
+        If `exclude` is true, rule will be applied on the axes that are
+        NOT in axis instead.)code");
+    DMLC_DECLARE_FIELD(exclude).set_default(false)
+    .describe("Whether to apply rule on axis that are NOT in axis instead.");
+  }
+};
+
 struct ReshapeParam : public dmlc::Parameter<ReshapeParam> {
   Tuple<int64_t> shape;
 
@@ -97,8 +126,7 @@ struct SqueezeParam : public dmlc::Parameter<SqueezeParam> {
 
   DMLC_DECLARE_PARAMETER(SqueezeParam) {
     DMLC_DECLARE_FIELD(axis).set_default(TShape())
-    .describe("The axis to squeeze in the input tensor."
-              " If set to None, all size=1 axes will be squeezed");
+    .describe("The axis to squeeze in the input tensor.");
   }
 };
 
@@ -110,6 +138,15 @@ struct ScalarParam : public dmlc::Parameter<ScalarParam> {
   }
 };
 
+struct FillValueParam : public dmlc::Parameter<FillValueParam> {
+  double fill_value;
+
+  DMLC_DECLARE_PARAMETER(FillValueParam) {
+    DMLC_DECLARE_FIELD(fill_value)
+    .describe("Scalar value to be filled");
+  }
+};
+
 struct TransposeParam : public dmlc::Parameter<TransposeParam> {
   TShape axes;
 
@@ -158,16 +195,49 @@ struct ReduceParam : public dmlc::Parameter<ReduceParam> {
   }
 };
 
+struct InitOpWithScalarParam : public dmlc::Parameter<InitOpWithScalarParam> {
+  TShape shape;
+  int dtype;
+  double fill_value;
+
+  DMLC_DECLARE_PARAMETER(InitOpWithScalarParam) {
+    DMLC_DECLARE_FIELD(shape).set_default(TShape());
+    DMLC_DECLARE_DTYPE_FIELD(dtype).set_default(kFloat32)
+      .describe("Target data type.");
+    DMLC_DECLARE_FIELD(fill_value).describe("Scalar value to fill");
+  }
+};
+
 struct InitOpParam : public dmlc::Parameter<InitOpParam> {
   TShape shape;
   int dtype;
-  double value;
 
   DMLC_DECLARE_PARAMETER(InitOpParam) {
     DMLC_DECLARE_FIELD(shape).set_default(TShape());
     DMLC_DECLARE_DTYPE_FIELD(dtype).set_default(kFloat32)
       .describe("Target data type.");
-    DMLC_DECLARE_FIELD(value).describe("Value to fill");
+  }
+};
+
+struct ElementWiseReduceParam : public dmlc::Parameter<ElementWiseReduceParam> {
+  int num_args;
+  DMLC_DECLARE_PARAMETER(ElementWiseReduceParam) {
+    DMLC_DECLARE_FIELD(num_args).set_lower_bound(1)
+      .describe("Number of inputs to be reduced.");
+  }
+};
+
+struct MatMulParam : public dmlc::Parameter<MatMulParam> {
+  bool transpose_a;
+  bool transpose_b;
+
+  DMLC_DECLARE_PARAMETER(MatMulParam) {
+    DMLC_DECLARE_FIELD(transpose_a)
+      .describe("If true then transpose the first input before dot.")
+      .set_default(false);
+    DMLC_DECLARE_FIELD(transpose_b)
+      .describe("If true then transpose the second input before dot.")
+      .set_default(false);
   }
 };
 
diff --git a/nnvm/python/nnvm/compiler/build_module.py b/nnvm/python/nnvm/compiler/build_module.py
index c21db8297e03..afd25a2b45a3 100644
--- a/nnvm/python/nnvm/compiler/build_module.py
+++ b/nnvm/python/nnvm/compiler/build_module.py
@@ -188,7 +188,7 @@ def build(graph, target=None, shape=None, dtype="float32", params=None, target_h
         The input types to the graph
 
     params : dict of str to NDArray
-        Input parameetrs to the graph that do not change
+        Input parameters to the graph that do not change
         during inference time. Used for pre-compute
         folding optimization.
 
diff --git a/nnvm/python/nnvm/compiler/graph_util.py b/nnvm/python/nnvm/compiler/graph_util.py
index dcad54a8b778..e831298b27d9 100644
--- a/nnvm/python/nnvm/compiler/graph_util.py
+++ b/nnvm/python/nnvm/compiler/graph_util.py
@@ -5,6 +5,9 @@
 import tvm
 from . import graph_attr
 
+from ..graph import create
+from ..symbol import Group, ones_like
+
 def infer_shape(graph, **shape):
     """Infer the shape given the shape of inputs.
 
@@ -89,3 +92,57 @@ def check_graph_equal(grapha, graphb, compare_variable_attrs=False):
     err = _deep_compare(grapha, graphb, compare_variable_attrs)
     if err:
         raise ValueError("Graph compare error: " + err)
+
+def get_gradient_graph(ys, xs, grad_ys=None):
+    """Create gradient graph of ys with respect to xs.
+
+    Parameters
+    ----------
+    ys : Symbol or list of Symbol
+        Symbols from which the gradient is calculated.
+    xs : Symbol or list of Symbol
+        Symbols the gradient respect to.
+        For group symbol, gradients for all outputs will be calculated.
+    grad_ys : Symbol or list of Symbol
+        Head gradients for ys.
+
+    Returns
+    -------
+    ret : Graph
+        Generated gradient graph.
+    """
+    if isinstance(ys, list):
+        ys = Group(ys)
+    g = create(ys)
+    g._set_symbol_list_attr('grad_ys', ys)
+    g._set_symbol_list_attr('grad_xs', xs)
+    ny = len(ys.list_output_names())
+    if grad_ys is None:
+        grad_ys = [ones_like(ys[i]) for i in range(ny)]
+    g._set_symbol_list_attr('grad_ys_out_grad', grad_ys)
+    return g.apply('Gradient')
+
+def gradients(ys, xs, grad_ys=None):
+    """Create gradient symbol of ys respect to xs.
+
+    Parameters
+    ----------
+    ys : Symbol or list of Symbol
+        Symbols from which the gradient is calculated.
+    xs : Symbol or list of Symbol
+        Symbols the gradient respect to.
+        For group symbol, gradients for all outputs will be calculated.
+    grad_ys : Symbol or list of Symbol
+        Head gradients for ys.
+
+    Returns
+    -------
+    ret : list of Symbol
+        Generated gradient symbol. For each xs,
+        all gradients from ys are merged into a single symbol.
+    """
+    grad_g = get_gradient_graph(ys, xs, grad_ys)
+    nx = len(Group(xs).list_output_names()) \
+        if isinstance(xs, list) else len(xs.list_output_names())
+    ret = [grad_g.symbol[i] for i in range(nx)]
+    return ret
diff --git a/nnvm/python/nnvm/graph.py b/nnvm/python/nnvm/graph.py
index 3940e0b5b864..2ea365e67ef4 100644
--- a/nnvm/python/nnvm/graph.py
+++ b/nnvm/python/nnvm/graph.py
@@ -13,7 +13,6 @@
 from ._base import GraphHandle, SymbolHandle
 from ._base import check_call
 from .symbol import Variable, Symbol, Group as _Group
-from .symbol import ones_like
 
 class GraphIndex(object):
     """Index for quickly accessing graph attributes.
@@ -271,38 +270,3 @@ def create(symbol):
     check_call(_LIB.NNGraphCreate(
         symbol.handle, ctypes.byref(ghandle)))
     return Graph(ghandle)
-
-
-def gradients(ys, xs, grad_ys=None):
-    """Create gradient symbol of ys respect to xs.
-
-    Parameters
-    ----------
-    ys : Symbol or list of Symbol
-        Symbols from which the gradient is calculated.
-    xs : Symbol or list of Symbol
-        Symbols the gradient respect to.
-        For group symbol, gradients for all outputs will be calculated.
-    grad_ys : Symbol or list of Symbol
-        Head gradients for ys.
-
-    Returns
-    -------
-    ret : list of Symbol
-        Generated gradient symbol. For each xs,
-        all gradients from ys are merged into a single symbol.
-    """
-    if isinstance(ys, list):
-        ys = _Group(ys)
-    g = create(ys)
-    g._set_symbol_list_attr('grad_ys', ys)
-    g._set_symbol_list_attr('grad_xs', xs)
-    ny = len(ys.list_output_names())
-    if grad_ys is None:
-        grad_ys = [ones_like(ys[i]) for i in range(ny)]
-    g._set_symbol_list_attr('grad_ys_out_grad', grad_ys)
-    sym = g.apply('Gradient').symbol
-    nx = len(_Group(xs).list_output_names()) \
-        if isinstance(xs, list) else len(xs.list_output_names())
-    ret = [sym[i] for i in range(nx)]
-    return ret
diff --git a/nnvm/src/pass/gradient.cc b/nnvm/src/pass/gradient.cc
index ff5f8b9ed731..6e6d01d5f6a5 100644
--- a/nnvm/src/pass/gradient.cc
+++ b/nnvm/src/pass/gradient.cc
@@ -14,18 +14,23 @@ namespace pass {
 namespace {
 
 // default aggregate gradient function
-// require operator __zero__ and __ewise_sum__ to be presented.
+// require operator zeros and elemwise_sum to be presented.
 NodeEntry DefaultAggregateGradient(std::vector<NodeEntry>&& v) {
   if (v.size() == 1) {
     return std::move(v[0]);
   } else if (v.size() == 0) {
     NodePtr zero_node = Node::Create();
-    zero_node->attrs.op = Op::Get("_zeros");
+    zero_node->attrs.op = Op::Get("zeros");
+    zero_node->attrs.name = "zero_grad";
+    zero_node->attrs.op->attr_parser(&(zero_node->attrs));
     return NodeEntry{zero_node, 0, 0};
   } else {
     NodePtr sum_node = Node::Create();
     sum_node->attrs.op = Op::Get("elemwise_sum");
     sum_node->inputs = std::move(v);
+    sum_node->attrs.name = "grad_sum";
+    sum_node->attrs.dict["num_args"] = std::to_string(sum_node->inputs.size());
+    sum_node->attrs.op->attr_parser(&(sum_node->attrs));
     return NodeEntry{sum_node, 0, 0};
   }
 }
diff --git a/nnvm/src/top/elemwise_op_common.h b/nnvm/src/top/elemwise_op_common.h
index 902c2fe41269..7bcc262d3428 100644
--- a/nnvm/src/top/elemwise_op_common.h
+++ b/nnvm/src/top/elemwise_op_common.h
@@ -84,6 +84,22 @@ inline bool ElemwiseType(const NodeAttrs& attrs,
     attrs, in_attrs, out_attrs, -1);
 }
 
+inline bool ElementWiseReduceShape(const NodeAttrs& attrs,
+                                   std::vector<TShape> *in_attrs,
+                                   std::vector<TShape> *out_attrs) {
+  CHECK_EQ(out_attrs->size(), 1);
+  return ElemwiseAttr<TShape, shape_is_none, shape_assign, true, shape_string>(
+    attrs, in_attrs, out_attrs, TShape());
+}
+
+inline bool ElementWiseReduceType(const NodeAttrs& attrs,
+                                  std::vector<int> *in_attrs,
+                                  std::vector<int> *out_attrs) {
+  CHECK_EQ(out_attrs->size(), 1);
+  return ElemwiseAttr<int, type_is_none, type_assign, true, type_string>(
+    attrs, in_attrs, out_attrs, -1);
+}
+
 #define NNVM_REGISTER_ELEMWISE_UNARY_OP(name)                       \
   NNVM_REGISTER_OP(name)                                            \
   .set_num_inputs(1)                                                \
@@ -100,11 +116,13 @@ inline bool ElemwiseType(const NodeAttrs& attrs,
 #define NNVM_REGISTER_INIT_OP(name)                                 \
   NNVM_REGISTER_OP(name)                                            \
   .set_num_inputs(0)                                                \
-  .set_num_outputs(1)                                               \
-  .set_attr_parser(ParamParser<InitOpParam>)                        \
-  .add_arguments(InitOpParam::__FIELDS__())                         \
-  .set_attr<FInferShape>("FInferShape", ZeroShape)                  \
-  .set_attr<FInferType>("FInferType", ZeroType)
+  .set_num_outputs(1)
+
+
+#define NNVM_REGISTER_INIT_LIKE_OP(name)                            \
+  NNVM_REGISTER_ELEMWISE_UNARY_OP(name)                             \
+  .set_attr<FGradient>("FGradient", MakeZeroGradNodes)              \
+  .add_argument("data", "Symbol", "The input")
 
 
 #define NNVM_REGISTER_ELEMWISE_BINARY_OP(name)                      \
@@ -120,6 +138,41 @@ inline bool ElemwiseType(const NodeAttrs& attrs,
   .add_argument("lhs", "Tensor", "first input")                     \
   .add_argument("rhs", "Tensor", "second input")
 
+
+#define NNVM_REGISTER_ELEMWISE_REDUCE_OP(name)                      \
+  NNVM_REGISTER_OP(name)                                            \
+  .set_num_inputs([](const NodeAttrs& attrs) {                      \
+    return static_cast<uint32_t>(                                   \
+      dmlc::get<ElementWiseReduceParam>(attrs.parsed).num_args);    \
+    })                                                              \
+  .set_attr_parser(ParamParser<ElementWiseReduceParam>)             \
+  .set_attr<FGetAttrDict>("FGetAttrDict",                           \
+    ParamGetAttrDict<ElementWiseReduceParam>)                       \
+  .set_attr<nnvm::FInferShape>("FInferShape",                       \
+    ElementWiseReduceShape)                                         \
+  .set_attr<nnvm::FInferType>("FInferType", ElementWiseReduceType)  \
+  .add_argument("args", "Symbol[]", "Positional input arguments")
+
+
+#define NNVM_REGISTER_INDICATOR_OP(name)                            \
+  NNVM_REGISTER_OP(name)                                            \
+  .set_num_outputs(1)                                               \
+  .set_attr<FInferType>(                                            \
+    "FInferType", [](const NodeAttrs& attrs,                        \
+                     std::vector<int>* in_attrs,                    \
+                     std::vector<int>* out_attrs) {                 \
+      CHECK_EQ(out_attrs->size(), 1U);                              \
+      NNVM_ASSIGN_OUTPUT_TYPE(attrs, *out_attrs, 0,                 \
+        static_cast<int>(kFloat32));                                \
+      return true;                                                  \
+  })                                                                \
+  .set_attr<FGradient>(                                             \
+    "FGradient", [](const NodePtr& n,                               \
+                    const std::vector<NodeEntry>& ograds) {         \
+      return MakeZeroGradNodes(n, ograds);                          \
+  })
+
+
 }  // namespace top
 }  // namespace nnvm
 #endif  // NNVM_TOP_ELEMWISE_OP_COMMON_H_
diff --git a/nnvm/src/top/nn/convolution.cc b/nnvm/src/top/nn/convolution.cc
index 48164d5e7f16..8664fa0a60f2 100644
--- a/nnvm/src/top/nn/convolution.cc
+++ b/nnvm/src/top/nn/convolution.cc
@@ -120,7 +120,42 @@ a bias vector is created and added to the outputs.
 .set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
 .set_num_outputs(1)
 .set_num_inputs(UseBiasNumInputs<Conv2DParam>)
-.set_support_level(2);
+.set_support_level(2)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds) {
+    return MakeGradNode("_conv2d_grad", n,
+                        {ograds[0], n->inputs[Conv2DParam::kData],
+                         n->inputs[Conv2DParam::kWeight]},
+                        n->attrs.dict);
+});
+
+NNVM_REGISTER_OP(_conv2d_grad)
+  .describe(R"code(2D convolution grad.
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("ograd", "4D Tensor", "Output grad.")
+.add_argument("data", "4D Tensor", "Input data of conv2d.")
+.add_argument("weight", "4D Tensor", "Input weight.")
+.set_num_inputs(3)
+.set_num_outputs(UseBiasNumInputs<Conv2DParam>)
+.set_attr<FListOutputNames>("FListOutputNames", UseBiasListInputNames<Conv2DParam>)
+.set_attr_parser(ParamParser<Conv2DParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<Conv2DParam>)
+.set_attr<FInferShape>(
+  "FInferShape", [](const nnvm::NodeAttrs& attrs,
+                    std::vector<TShape>* in_attrs,
+                    std::vector<TShape>* out_attrs) {
+    const Conv2DParam& param = nnvm::get<Conv2DParam>(attrs.parsed);
+    NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, Conv2DParam::kData, in_attrs->at(1));
+    NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, Conv2DParam::kWeight, in_attrs->at(2));
+    if (param.use_bias) {
+      NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, Conv2DParam::kBias, TShape({param.channels}));
+    }
+    return true;
+})
+.set_attr<FInferType>("FInferType", ElemwiseType<3, -1>)
+.set_attr<TIsBackward>("TIsBackward", true);
 
 
 DMLC_REGISTER_PARAMETER(Conv2DTransposeParam);
diff --git a/nnvm/src/top/nn/nn.cc b/nnvm/src/top/nn/nn.cc
index 0bf1d2921fd2..bf169acc78ec 100644
--- a/nnvm/src/top/nn/nn.cc
+++ b/nnvm/src/top/nn/nn.cc
@@ -54,7 +54,7 @@ NNVM_REGISTER_OP(dense)
 - **data**: `(x1, x2, ..., xn, input_dim)`
 - **weight**: `(units, input_dim)`
 - **bias**: `(units,)`
-- **out**: `(x1, x2, ..., xn, num_hidden)`
+- **out**: `(x1, x2, ..., xn, units)`
 
 The learnable parameters include both ``weight`` and ``bias``.
 
@@ -72,6 +72,34 @@ If ``use_bias`` is set to be false, then the ``bias`` term is ignored.
 .set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<DenseParam>)
 .set_attr<FInferShape>("FInferShape", DenseInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds) {
+    const DenseParam& param = nnvm::get<DenseParam>(n->attrs.parsed);
+
+    NodeEntry data_grad = MakeNode("matmul",
+                                   n->attrs.name + "_data_grad",
+                                   {ograds[0], n->inputs[DenseParam::kWeight]});
+    NodeEntry w_grad_sub = MakeNode("matmul",
+                                     n->attrs.name + "_weight_grad_sub0",
+                                     {ograds[0], n->inputs[DenseParam::kData]},
+                                     {{"transpose_a", "true"}});
+    TShape w_reduce_axis = {0, -1};
+    std::ostringstream w_oss; w_oss << w_reduce_axis;
+    NodeEntry w_grad = MakeNode("sum", n->attrs.name + "_weight_grad",
+                                {w_grad_sub},
+                                {{"axis", w_oss.str()}, {"exclude", "true"}});
+    std::vector<NodeEntry> grads = {data_grad, w_grad};
+
+    if (param.use_bias) {
+      TShape axis = {-1};
+      std::ostringstream b_oss; b_oss << axis;
+      grads.push_back(MakeNode("sum", n->attrs.name + "_bias_grad",
+                      {ograds[0]},
+                      {{"axis", b_oss.str()}, {"exclude", "true"}}));
+    }
+    return grads;
+})
 .set_support_level(1);
 
 // relu
@@ -82,6 +110,18 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(relu)
    max(input, 0)
 
 )code" NNVM_ADD_FILELINE)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds) {
+    // y = relu(x)
+    // grad = indicator(x > 0)
+    NodeEntry zero = MakeNode("zeros_like", n->attrs.name + "_grad_zero",
+                              {n->inputs[0]});
+    return std::vector<NodeEntry>{
+      MakeNode("greater", n->attrs.name + "_grad",
+               {n->inputs[0], zero}, {{"exclude", "true"}})
+    };
+})
 .set_support_level(1);
 
 // dropout
@@ -217,7 +257,37 @@ NNVM_REGISTER_OP(softmax)
 .set_num_outputs(1)
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_support_level(1);
+.set_support_level(1)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds) {
+    // grad_x = grad_y dot jacobian of softmax
+    //
+    // jacobian of softmax
+    // [-y1y1 + y1, -y1y2,        ...    ]
+    // [ ...      , -y2y2 + y2,   ...    ]
+    // [ ...                      ...    ]
+    // [ ...                  ,-ynyn + yn]
+    //
+    // grad_x =
+    // [-y1*(ograd1*y1 - 1 + ograd2*y2 + ..., -y2*(ograd1*y1 - 1 + ograd2*y2, ..., ...]]
+
+    // grad_x = ograd elemwise_mul output
+    // grad_x = sum(grad_x, keepdim, axis)
+    // grad_x = grad_x broadcast_mul output
+    // grad_x = neg grad_x
+    // grad_x = grad_x + output
+    const SoftmaxParam& param = nnvm::get<SoftmaxParam>(n->attrs.parsed);
+    NodeEntry output =  NodeEntry{n, 0, 0};
+    NodeEntry sub0 = MakeNode("elemwise_mul", n->attrs.name + "_grad_sub0", {ograds[0], output});
+    NodeEntry sub1 = MakeNode("sum", n->attrs.name + "_grad_sub1", {sub0},
+                              {{"axis", std::to_string(param.axis)}, {"keepdims", "true"}});
+    NodeEntry sub2 = MakeNode("broadcast_mul", n->attrs.name + "_grad_sub2", {sub1, output});
+    NodeEntry sub3 = MakeNode("negative", n->attrs.name + "_grad_sub3", {sub2});
+    return std::vector<NodeEntry> {
+      MakeNode("elemwise_add", n->attrs.name + "_grad", {sub3, output})
+    };
+});
 
 // log_softmax
 NNVM_REGISTER_OP(log_softmax)
@@ -236,6 +306,38 @@ NNVM_REGISTER_OP(log_softmax)
 .set_num_outputs(1)
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds) {
+    // grad_x = grad_y dot jacobian of softmax
+    //
+    // jacobian of softmax
+    // [-y1 + 1, -y2,        ...    ]
+    // [ ...   , -y2 + 1,    ...    ]
+    // [ ...                 ...    ]
+    // [ ...                ,-yn + 1]
+    //
+    // grad_x =
+    // [-(ograd1*y1 - 1 + ograd2*y2 + ..., -(ograd1*y1 - 1 + ograd2*y2, ..., ...]]
+
+    // grad_x = ograd elemwise_mul output
+    // grad_x = sum(grad_x, keepdim, axis)
+    // grad_x = neg grad_x
+    // grad_x = grad_x + ones_like(grad_x)
+    // grad_x = expand_dims(grad_x, axis)
+    const SoftmaxParam& param = nnvm::get<SoftmaxParam>(n->attrs.parsed);
+    NodeEntry output =  NodeEntry{n, 0, 0};
+    NodeEntry sub0 = MakeNode("elemwise_mul", n->attrs.name + "_grad_sub0", {ograds[0], output});
+    NodeEntry sub1 = MakeNode("sum", n->attrs.name + "_grad_sub1", {sub0},
+                              {{"axis", std::to_string(param.axis)}, {"keepdims", "true"}});
+    NodeEntry sub2 = MakeNode("negative", n->attrs.name + "_grad_sub2", {sub1});
+    NodeEntry sub3 = MakeNode("ones_like", n->attrs.name + "_grad_sub3", {sub2});
+    NodeEntry sub4 = MakeNode("elemwise_add", n->attrs.name + "_grad_sub4", {sub2, sub3});
+    return std::vector<NodeEntry> {
+      MakeNode("expand_like", n->attrs.name + "_grad", {sub4, output},
+               {{"axis", std::to_string(param.axis)}})
+    };
+})
 .set_support_level(1);
 
 // leaky_rlu
@@ -255,6 +357,25 @@ NNVM_REGISTER_OP(leaky_relu)
 .set_num_outputs(1)
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds) {
+    // y = leak_relu(x)
+    // grad = indicator(x > 0) + alpha * indicator(x < 0)
+    const LeakyReLUParam& param = nnvm::get<LeakyReLUParam>(n->attrs.parsed);
+    NodeEntry zero = MakeNode("zeros_like", n->attrs.name + "_grad_zero",
+                              {n->inputs[0]});
+    NodeEntry sub0 = MakeNode("greater", n->attrs.name + "_pos_grad",
+                              {n->inputs[0], zero}, {{"exclude", "true"}});
+    NodeEntry sub1 = MakeNode("less", n->attrs.name + "_neg_grad",
+                              {n->inputs[0], zero}, {{"exclude", "true"}});
+    NodeEntry sub2 = MakeNode("__mul_scalar__", n->attrs.name + "_neg_mul_2",
+                              {sub1},
+                              {{"scalar", std::to_string(param.alpha)}});
+    return std::vector<NodeEntry>{
+      MakeNode("elemwise_add", n->attrs.name + "_add_grad", {sub0, sub2})
+    };
+})
 .set_support_level(1);
 
 
diff --git a/nnvm/src/top/nn/pooling.cc b/nnvm/src/top/nn/pooling.cc
index c895437cea15..0f0eb6817929 100644
--- a/nnvm/src/top/nn/pooling.cc
+++ b/nnvm/src/top/nn/pooling.cc
@@ -77,8 +77,30 @@ NNVM_REGISTER_OP(max_pool2d)
 .set_num_inputs(1)
 .set_attr<FInferShape>("FInferShape", Pool2DInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds) {
+    return MakeGradNode("_max_pool2d_grad", n,
+                        {ograds[0], n->inputs[0], NodeEntry{n, 0, 0}},
+                        n->attrs.dict);
+})
 .set_support_level(2);
 
+NNVM_REGISTER_OP(_max_pool2d_grad)
+  .describe(R"code(Max pooling 2D grad.
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("ograd", "4D Tensor", "Output grad.")
+.add_argument("input", "4D Tensor", "Input data of max_pool2d grad.")
+.add_argument("output", "4D Tensor", "Output data of max_pool2d grad.")
+.set_num_inputs(3)
+.set_num_outputs(1)
+.set_attr_parser(ParamParser<Pool2DParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<Pool2DParam>)
+.set_attr<FInferShape>("FInferShape", AssignOutputAttr<TShape, 1, 0>)
+.set_attr<FInferType>("FInferType", ElemwiseType<3, 1>)
+.set_attr<TIsBackward>("TIsBackward", true);
+
 
 NNVM_REGISTER_OP(avg_pool2d)
 .describe(R"code(Average pooling operation for one dimensional data.
diff --git a/nnvm/src/top/op_common.h b/nnvm/src/top/op_common.h
index f13d7e782dae..ee6b7ced3b4e 100644
--- a/nnvm/src/top/op_common.h
+++ b/nnvm/src/top/op_common.h
@@ -144,7 +144,7 @@ inline std::string attr_assign_error_msg(const NodeAttrs& attrs,
 }
 
 /*!
- * \brief macro assign shape to out if out is unknown otherwise check consistency
+ * \brief macro assign shape to input if out is unknown otherwise check consistency
  *  Use macro so we can see the error file more clearly
  * \param inputs the shape array to store the result
  * \param index the index of in the array
@@ -240,10 +240,11 @@ inline bool SameShape(const NodeAttrs& attrs,
 }
 
 // return shape from node attrs
+template<typename PType>
 inline bool ZeroShape(const NodeAttrs& attrs,
                       std::vector<TShape> *ishape,
                       std::vector<TShape> *oshape) {
-  const TShape& ts = dmlc::get<InitOpParam>(attrs.parsed).shape;
+  const TShape& ts = dmlc::get<PType>(attrs.parsed).shape;
   if (ts.ndim() != 0) {
     SHAPE_ASSIGN(oshape->at(0), ts);
     return true;
@@ -252,15 +253,63 @@ inline bool ZeroShape(const NodeAttrs& attrs,
   }
 }
 
+// simply assign output shape or type from input
+template<typename AttrType, int in_index, int out_index>
+inline bool AssignOutputAttr(const NodeAttrs& attrs,
+                              std::vector<AttrType> *in_attrs,
+                              std::vector<AttrType> *out_attrs) {
+  CHECK_LT(in_index, in_attrs->size());
+  CHECK_LT(out_index, out_attrs->size());
+  const TShape &dshape = in_attrs->at(in_index);
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, out_index, dshape);
+  return true;
+}
+
 // return type from node attrs
+template<typename PType>
 inline bool ZeroType(const NodeAttrs& attrs,
                      std::vector<int> *iattr,
                      std::vector<int> *oattr) {
-  int dtype = dmlc::get<InitOpParam>(attrs.parsed).dtype;
+  int dtype = dmlc::get<PType>(attrs.parsed).dtype;
   DTYPE_ASSIGN(oattr->at(0), dtype);
   return true;
 }
 
+// Make zero grad node
+inline std::vector<NodeEntry> MakeZeroGradNodes(
+  const NodePtr& n,
+  const std::vector<NodeEntry>& ograds) {
+  std::vector<NodeEntry> ret;
+  for (uint32_t i = 0; i < n->num_inputs(); ++i) {
+    std::ostringstream os;
+    ret.push_back(MakeNode("zeros_like", n->attrs.name + "_zero_grad",
+                           {n->inputs[i]}));
+  }
+  return ret;
+}
+
+// Helper to make gradient node
+inline std::vector<NodeEntry> MakeGradNode(
+  const char* op_name,
+  const NodePtr& n,
+  std::vector<NodeEntry> inputs,
+  std::unordered_map<std::string, std::string> attr = {}) {
+  NodePtr p = Node::Create();
+  p->attrs.op = nnvm::Op::Get(op_name);
+  p->attrs.name = n->attrs.name + "_grad";
+  p->inputs = std::move(inputs);
+  p->attrs.dict = std::move(attr);
+  if (p->attrs.op->attr_parser) {
+    p->attrs.op->attr_parser(&p->attrs);
+  }
+  std::vector<NodeEntry> ret;
+  for (uint32_t i = 0; i < p->num_outputs(); ++i) {
+    ret.emplace_back(NodeEntry{p, i, 0});
+  }
+  return ret;
+}
+
+
 }  // namespace top
 }  // namespace nnvm
 
diff --git a/nnvm/src/top/tensor/elemwise.cc b/nnvm/src/top/tensor/elemwise.cc
index 793d84b1484c..73a9583220d0 100644
--- a/nnvm/src/top/tensor/elemwise.cc
+++ b/nnvm/src/top/tensor/elemwise.cc
@@ -241,73 +241,70 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(copy)
 });
 
 DMLC_REGISTER_PARAMETER(InitOpParam);
+DMLC_REGISTER_PARAMETER(InitOpWithScalarParam);
+DMLC_REGISTER_PARAMETER(FillValueParam);
 
 // full
 NNVM_REGISTER_INIT_OP(full)
 .describe(R"code(Fill array with scalar value
 
 )code"  NNVM_ADD_FILELINE)
+.set_attr_parser(ParamParser<InitOpWithScalarParam>)
+.set_attr<FGetAttrDict>(
+  "FGetAttrDict", ParamGetAttrDict<InitOpWithScalarParam>)
+.add_arguments(InitOpWithScalarParam::__FIELDS__())
+.set_attr<FInferShape>("FInferShape", ZeroShape<InitOpWithScalarParam>)
+.set_attr<FInferType>("FInferType", ZeroType<InitOpWithScalarParam>)
 .set_support_level(1);
 
 NNVM_REGISTER_INIT_OP(zeros)
 .describe(R"code(Fill target with zeros
 
 )code"  NNVM_ADD_FILELINE)
+.set_attr_parser(ParamParser<InitOpParam>)
+.set_attr<FGetAttrDict>(
+  "FGetAttrDict", ParamGetAttrDict<InitOpParam>)
+.add_arguments(InitOpParam::__FIELDS__())
+.set_attr<FInferShape>("FInferShape", ZeroShape<InitOpParam>)
+.set_attr<FInferType>("FInferType", ZeroType<InitOpParam>)
 .set_support_level(1);
 
 NNVM_REGISTER_INIT_OP(ones)
 .describe(R"code(Fill target with ones
 
 )code"  NNVM_ADD_FILELINE)
+.set_attr_parser(ParamParser<InitOpParam>)
+.set_attr<FGetAttrDict>(
+  "FGetAttrDict", ParamGetAttrDict<InitOpParam>)
+.add_arguments(InitOpParam::__FIELDS__())
+.set_attr<FInferShape>("FInferShape", ZeroShape<InitOpParam>)
+.set_attr<FInferType>("FInferType", ZeroType<InitOpParam>)
 .set_support_level(1);
 
 // full_like
-NNVM_REGISTER_ELEMWISE_UNARY_OP(full_like)
-  .describe(R"code(Return an scalar value array with the same shape and type
+NNVM_REGISTER_INIT_LIKE_OP(full_like)
+.describe(R"code(Return an scalar value array with the same shape and type
 as the input array
 
 )code"  NNVM_ADD_FILELINE)
-.set_support_level(1)
-.add_arguments(InitOpParam::__FIELDS__())
-.set_attr_parser(ParamParser<InitOpParam>)
-.set_attr<FGradient>(
-  "FGradient", [](const NodePtr& n,
-                  const std::vector<NodeEntry>& ograds){
-    return std::vector<NodeEntry>{
-      MakeNode("zeros_like", n->attrs.name + "_grad",
-               {n->inputs[0]})
-    };
-});
+.add_arguments(FillValueParam::__FIELDS__())
+.set_attr_parser(ParamParser<FillValueParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<FillValueParam>)
+.set_support_level(1);
 
-NNVM_REGISTER_ELEMWISE_UNARY_OP(zeros_like)
+NNVM_REGISTER_INIT_LIKE_OP(zeros_like)
 .describe(R"code(Return an array of zeros with the same shape and type
 as the input array.
 
 )code")
-.add_argument("data", "Symbol", "The input")
-.set_attr<FGradient>(
-  "FGradient", [](const NodePtr& n,
-                  const std::vector<NodeEntry>& ograds){
-    return std::vector<NodeEntry>{
-      MakeNode("zeros_like", n->attrs.name + "_grad",
-               {n->inputs[0]})
-    };
-});
+.set_support_level(1);
 
-NNVM_REGISTER_ELEMWISE_UNARY_OP(ones_like)
+NNVM_REGISTER_INIT_LIKE_OP(ones_like)
 .describe(R"code(Return an array of ones with the same shape and type
 as the input array.
 
 )code")
-.add_argument("data", "Symbol", "The input")
-.set_attr<FGradient>(
-  "FGradient", [](const NodePtr& n,
-                  const std::vector<NodeEntry>& ograds){
-    return std::vector<NodeEntry>{
-      MakeNode("zeros_like", n->attrs.name + "_grad",
-               {n->inputs[0]})
-    };
-});
+.set_support_level(1);
 
 // unary scalar op
 DMLC_REGISTER_PARAMETER(ScalarParam);
@@ -452,64 +449,84 @@ NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__rpow_scalar__)
     };
 });
 
+DMLC_REGISTER_PARAMETER(ElementWiseReduceParam);
 
-struct ElementWiseSumParam : public dmlc::Parameter<ElementWiseSumParam> {
-  int num_args;
-  DMLC_DECLARE_PARAMETER(ElementWiseSumParam) {
-    DMLC_DECLARE_FIELD(num_args).set_lower_bound(1)
-      .describe("Number of inputs to be summed.");
-  }
-};
-
-DMLC_REGISTER_PARAMETER(ElementWiseSumParam);
-
-bool ElementWiseSumShape(const NodeAttrs& attrs,
-                         std::vector<TShape> *in_attrs,
-                         std::vector<TShape> *out_attrs) {
-  CHECK_EQ(out_attrs->size(), 1);
-  return ElemwiseAttr<TShape, shape_is_none, shape_assign, true, shape_string>(
-    attrs, in_attrs, out_attrs, TShape());
-}
-
-bool ElementWiseSumType(const NodeAttrs& attrs,
-                        std::vector<int> *in_attrs,
-                        std::vector<int> *out_attrs) {
-  CHECK_EQ(out_attrs->size(), 1);
-  return ElemwiseAttr<int, type_is_none, type_assign, true, type_string>(
-    attrs, in_attrs, out_attrs, -1);
-}
-
-std::vector<NodeEntry> ElementWiseSumGrad(
-    const NodePtr& n,
-    const std::vector<NodeEntry>& ograds) {
-  // identity constraints in the beginning for easier shape inference.
-  const Op* copy_op = Op::Get("identity");
-  CHECK_EQ(ograds.size(), 1);
-  std::vector<NodeEntry> ret;
-  NodeEntry n_out{n, 0, 0};
-  for (size_t i = 0; i < n->inputs.size(); i++) {
-    NodePtr id_node = Node::Create();
-    id_node->attrs.op = copy_op;
-    id_node->inputs = {ograds[0]};
-    ret.push_back(NodeEntry{id_node, 0, 0});
-  }
-  return ret;
-}
-
-
-NNVM_REGISTER_OP(elemwise_sum)
+NNVM_REGISTER_ELEMWISE_REDUCE_OP(elemwise_sum)
 .describe(R"code(Adds all input arguments element-wise.
 
 )code"  NNVM_ADD_FILELINE)
-.set_attr_parser(ParamParser<ElementWiseSumParam>)
-.set_num_inputs([](const NodeAttrs& attrs) {
-  uint32_t ret = dmlc::get<ElementWiseSumParam>(attrs.parsed).num_args;
-  return ret;
+.set_attr<nnvm::FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    CHECK_EQ(ograds.size(), 1);
+    std::vector<NodeEntry> ret;
+    for (size_t i = 0; i < n->inputs.size(); i++) {
+      ret.push_back(ograds[0]);
+    }
+    return ret;
+  });
+
+NNVM_REGISTER_ELEMWISE_UNARY_OP(block_grad)
+.describe(R"code(Blocks gradient computation for input.
+
+)code" NNVM_ADD_FILELINE)
+.set_attr<nnvm::FInplaceIdentity>(
+  "FInplaceIdentity", [](const NodeAttrs& attrs){
+    return std::vector<bool>{true};
 })
-.set_attr<nnvm::FInferShape>("FInferShape", ElementWiseSumShape)
-.set_attr<nnvm::FInferType>("FInferType", ElementWiseSumType)
-.set_attr<nnvm::FGradient>("FGradient", ElementWiseSumGrad)
-.add_argument("args", "Symbol[]", "Positional input arguments");
+.set_attr<nnvm::FGradient>("FGradient", MakeZeroGradNodes);
+
+DMLC_REGISTER_PARAMETER(IndicatorParam);
+
+// indicator function
+NNVM_REGISTER_INDICATOR_OP(greater)
+.describe(R"code(Greater function that returns a mask tensor
+with 1.0 if (left > right), otherwise 0.0 element-wise.
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("lhs", "Tensor", "First input")
+.add_argument("rhs", "Tensor", "Second input")
+.set_num_inputs(2)
+.set_attr<nnvm::FInferShape>("FInferShape", ElemwiseShape<2, 1>)
+.set_support_level(1);
+
+
+NNVM_REGISTER_INDICATOR_OP(less)
+  .describe(R"code(Less function that returns a mask tensor
+with 1.0 if (left < right), otherwise 0.0 element-wise.
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("lhs", "Tensor", "First input")
+.add_argument("rhs", "Tensor", "Second input")
+.set_num_inputs(2)
+.set_attr<nnvm::FInferShape>("FInferShape", ElemwiseShape<2, 1>)
+.set_support_level(1);
+
+NNVM_REGISTER_INDICATOR_OP(_max_mask)
+  .describe(R"code(Function that returns a mask tensor
+with 1.0 if the value is maximum over given axes, otherwise 0.0 element-wise.
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "Tensor", "Input")
+.set_num_inputs(1)
+.add_arguments(IndicatorParam::__FIELDS__())
+.set_attr_parser(ParamParser<IndicatorParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<IndicatorParam>)
+.set_attr<nnvm::FInferShape>("FInferShape", ElemwiseShape<1, 1>)
+.set_support_level(1);
+
+NNVM_REGISTER_INDICATOR_OP(_min_mask)
+  .describe(R"code(Function that returns a mask tensor
+with 1.0 if the value is minimum over given axes, otherwise 0.0 element-wise.
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "Tensor", "Input")
+.set_num_inputs(1)
+.add_arguments(IndicatorParam::__FIELDS__())
+.set_attr_parser(ParamParser<IndicatorParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<IndicatorParam>)
+.set_attr<nnvm::FInferShape>("FInferShape", ElemwiseShape<1, 1>)
+.set_support_level(1);
 
 }  // namespace top
 }  // namespace nnvm
diff --git a/nnvm/src/top/tensor/matrix_op.cc b/nnvm/src/top/tensor/matrix_op.cc
new file mode 100644
index 000000000000..93dc57016f55
--- /dev/null
+++ b/nnvm/src/top/tensor/matrix_op.cc
@@ -0,0 +1,138 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file matrix_op.cc
+ * \brief Matrix operators
+ */
+#include <nnvm/op.h>
+#include <nnvm/node.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/top/tensor.h>
+#include "../op_common.h"
+#include "../elemwise_op_common.h"
+
+namespace nnvm {
+namespace top {
+
+DMLC_REGISTER_PARAMETER(MatMulParam);
+
+inline bool DotShape(const nnvm::NodeAttrs& attrs,
+                     std::vector<TShape> *in_attrs,
+                     std::vector<TShape> *out_attrs) {
+  const MatMulParam& param = nnvm::get<MatMulParam>(attrs.parsed);
+  CHECK_EQ(in_attrs->size(), 2U);
+  CHECK_EQ(out_attrs->size(), 1U);
+  TShape lshape = (*in_attrs)[0];
+  TShape rshape = (*in_attrs)[1];
+
+  if (lshape.ndim() == 1)  lshape = TShape{1, lshape[0]};
+  if (rshape.ndim() == 1) rshape = TShape{1, rshape[0]};
+
+  if (param.transpose_a) std::reverse(lshape.begin(), lshape.end());
+  if (param.transpose_b) std::reverse(rshape.begin(), rshape.end());
+
+  CHECK_EQ(lshape[lshape.ndim() - 1], rshape[0])
+    << "dot shape inconsistent: " << lshape << " X " << rshape;
+
+  TShape oshape(lshape.ndim() + rshape.ndim() - 1);
+  for (int i = 0; i < lshape.ndim() - 1; i++) oshape[i] = lshape[i];
+  for (int i = 1; i < rshape.ndim(); i++) oshape[i + lshape.ndim() - 1] = rshape[i];
+
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, 0, oshape);
+  return true;
+}
+
+NNVM_REGISTER_OP(matmul)
+  .describe(R"doc(Matrix multiplication of two arrays.
+
+``dot``'s behavior depends on the input array dimensions:
+
+- 1-D arrays: inner product of vectors
+- 2-D arrays: matrix multiplication
+- N-D arrays: a sum product over the last axis of the first input and the first
+  axis of the second input
+
+  For example, given 3-D ``x`` with shape `(n,m,k)` and ``y`` with shape `(k,r,s)`, the
+  result array will have shape `(n,m,r,s)`. It is computed by::
+
+    dot(x,y) = sum(x[i,j,:]*y[:,a,b])
+
+)doc" NNVM_ADD_FILELINE)
+.set_support_level(1)
+.set_num_inputs(2)
+.set_num_outputs(1)
+.set_attr_parser(ParamParser<MatMulParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<MatMulParam>)
+.add_arguments(MatMulParam::__FIELDS__())
+.add_argument("lhs", "NDArray-or-Symbol", "The first input")
+.add_argument("rhs", "NDArray-or-Symbol", "The second input")
+.set_attr<FInferShape>("FInferShape", DotShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<2, 1>)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds) {
+    // z = x dot y
+    // xshape (n,m,k), yshape (k,r,s)
+    const MatMulParam& param = nnvm::get<MatMulParam>(n->attrs.parsed);
+    bool Ta = param.transpose_a;
+    bool Tb = param.transpose_b;
+    // Ta = false, Tb = false
+    // grad_x = grad_z dot y.T
+    // grad_y = x.T dot grad_z
+    if (!Ta && !Tb) {
+      return std::vector<NodeEntry>{
+        MakeNode("matmul", n->attrs.name + "_grad_0",
+                 {ograds[0], n->inputs[1]},
+                 {{"transpose_a", "false"},
+                  {"transpose_b", "true"}}),
+        MakeNode("matmul", n->attrs.name + "_grad_1",
+                 {n->inputs[0], ograds[0]},
+                 {{"transpose_a", "true"},
+                  {"transpose_b", "false"}})
+      };
+    } else if (Ta && !Tb) {
+      // Ta = true, Tb = false
+      // grad_x = y dot grad_z.T
+      // grad_y = x dot grad_z
+      return std::vector<NodeEntry>{
+        MakeNode("matmul", n->attrs.name + "_grad_0",
+                 {n->inputs[1], ograds[0]},
+                 {{"transpose_a", "false"},
+                  {"transpose_b", "true"}}),
+        MakeNode("matmul", n->attrs.name + "_grad_1",
+                 {n->inputs[0], ograds[0]},
+                 {{"transpose_a", "false"},
+                  {"transpose_b", "false"}})
+      };
+    } else if (!Ta && Tb) {
+      // Ta = false, Tb = true
+      // grad_x = grad_z dot y
+      // grad_y = grad_z.T dot x
+      return std::vector<NodeEntry>{
+        MakeNode("matmul", n->attrs.name + "_grad_0",
+                 {ograds[0], n->inputs[1]},
+                 {{"transpose_a", "false"},
+                  {"transpose_b", "false"}}),
+        MakeNode("matmul", n->attrs.name + "_grad_1",
+                 {ograds[0], n->inputs[0]},
+                 {{"transpose_a", "true"},
+                  {"transpose_b", "false"}})
+      };
+    } else {
+      // Ta = true, Tb = true
+      // grad_x = y.T dot grad_z.T
+      // grad_y = grad_z.T dot x.T
+      return std::vector<NodeEntry>{
+        MakeNode("matmul", n->attrs.name + "_grad_0",
+                 {n->inputs[1], ograds[0]},
+                 {{"transpose_a", "true"},
+                  {"transpose_b", "true"}}),
+        MakeNode("matmul", n->attrs.name + "_grad_1",
+                 {ograds[0], n->inputs[0]},
+                 {{"transpose_a", "true"},
+                  {"transpose_b", "true"}})
+      };
+    }
+});
+
+}  // namespace top
+}  // namespace nnvm
diff --git a/nnvm/src/top/tensor/reduce.cc b/nnvm/src/top/tensor/reduce.cc
index 4599394176a5..429800fe5ae1 100644
--- a/nnvm/src/top/tensor/reduce.cc
+++ b/nnvm/src/top/tensor/reduce.cc
@@ -31,11 +31,19 @@ inline TShape ReduceShapeImpl(const TShape& ishape,
     << "Reduction axis " << axis[axis.ndim() - 1]
     << " Exceeds input dimensions " << ishape;
 
+  TShape in_axis = axis;
+  for (auto& i : in_axis) {
+    i = i < 0 ? i + ishape.ndim(): i;
+    CHECK_GE(i, 0) << "axis out of bounds in reduce operator";
+    CHECK_LT(i, ishape.ndim()) << "axis out of bounds in reduce operator";
+  }
+  std::sort(in_axis.begin(), in_axis.end());
+
   if (keepdims) {
     TShape oshape(ishape);
     if (exclude) {
       for (dim_t i = 0, j = 0; i < ishape.ndim(); ++i) {
-        if (j < axis.ndim() && i == axis[j]) {
+        if (j < in_axis.ndim() && i == in_axis[j]) {
           ++j;
           continue;
         }
@@ -44,22 +52,22 @@ inline TShape ReduceShapeImpl(const TShape& ishape,
       return oshape;
     }
 
-    for (dim_t i = 0; i < axis.ndim(); ++i) {
-      oshape[axis[i]] = 1;
+    for (dim_t i = 0; i < in_axis.ndim(); ++i) {
+      oshape[in_axis[i]] = 1;
     }
     return oshape;
   }
 
   if (exclude) {
-    TShape oshape = TShape(axis.ndim());
-    for (dim_t i = 0; i < axis.ndim(); ++i) {
-      oshape[i] = ishape[axis[i]];
+    TShape oshape = TShape(in_axis.ndim());
+    for (dim_t i = 0; i < in_axis.ndim(); ++i) {
+      oshape[i] = ishape[in_axis[i]];
     }
     return oshape;
   }
-  TShape oshape = TShape(std::max<dim_t>(1, ishape.ndim() - axis.ndim()));
+  TShape oshape = TShape(std::max<dim_t>(1, ishape.ndim() - in_axis.ndim()));
   for (dim_t i = 0, j = 0, k = 0; i < ishape.ndim(); ++i) {
-    if (j < axis.ndim() && i == axis[j]) {
+    if (j < in_axis.ndim() && i == in_axis[j]) {
       ++j;
       continue;
     }
@@ -99,9 +107,7 @@ inline void AxesParamParser(nnvm::NodeAttrs* attrs) {
   .set_attr<FInferShape>("FInferShape", ReduceShape)                    \
   .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)               \
   .set_num_inputs(1)                                                    \
-  .set_num_outputs(1)                                                   \
-
-
+  .set_num_outputs(1)
 
 NNVM_REGISTER_REDUCE_OP(sum)
 .describe(R"code(Computes the sum of array elements over given axes.
@@ -120,17 +126,66 @@ Example::
   sum(data, axis=[1,2])
   [ 12.  19.  27.]
 
-)code" NNVM_ADD_FILELINE);
+)code" NNVM_ADD_FILELINE)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    const ReduceParam& param = nnvm::get<ReduceParam>(n->attrs.parsed);
+    std::ostringstream axis; axis << param.axis;
+    return std::vector<NodeEntry>{
+      MakeNode("expand_like", n->attrs.name + "_grad",
+               {ograds[0], n->inputs[0]},
+               {{"axis", axis.str()},
+                {"keepdims", std::to_string(param.keepdims)},
+                {"exclude", std::to_string(param.exclude)}})
+  };
+});
 
 NNVM_REGISTER_REDUCE_OP(max)
 .describe(R"code(Computes the max of array elements over given axes.
 
-)code" NNVM_ADD_FILELINE);
+)code" NNVM_ADD_FILELINE)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    const ReduceParam& param = nnvm::get<ReduceParam>(n->attrs.parsed);
+    std::ostringstream axis; axis << param.axis;
+    NodeEntry sub0 = MakeNode("expand_like", n->attrs.name + "_grad_sub0",
+                             {ograds[0], n->inputs[0]},
+                             {{"axis", axis.str()},
+                              {"keepdims", std::to_string(param.keepdims)},
+                              {"exclude", std::to_string(param.exclude)}});
+    NodeEntry sub1 = MakeNode("_max_mask", n->attrs.name + "_grad_sub1",
+                              {ograds[0]},
+                              {{"axis", axis.str()},
+                               {"exclude", std::to_string(param.exclude)}});
+    return std::vector<NodeEntry>{
+      MakeNode("elemwise_mul", n->attrs.name + "_grad", {sub0, sub1})
+    };
+});
 
 NNVM_REGISTER_REDUCE_OP(min)
 .describe(R"code(Computes the min of array elements over given axes.
 
-)code" NNVM_ADD_FILELINE);
+)code" NNVM_ADD_FILELINE)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    const ReduceParam& param = nnvm::get<ReduceParam>(n->attrs.parsed);
+    std::ostringstream axis; axis << param.axis;
+    NodeEntry sub0 = MakeNode("expand_like", n->attrs.name + "_grad_sub0",
+                              {ograds[0], n->inputs[0]},
+                              {{"axis", axis.str()},
+                               {"keepdims", std::to_string(param.keepdims)},
+                               {"exclude", std::to_string(param.exclude)}});
+    NodeEntry sub1 = MakeNode("_min_mask", n->attrs.name + "_grad_sub1",
+                              {ograds[0]},
+                              {{"axis", axis.str()},
+                               {"exclude", std::to_string(param.exclude)}});
+    return std::vector<NodeEntry>{
+      MakeNode("elemwise_mul", n->attrs.name + "_grad", {sub0, sub1})
+    };
+});
 
 
 }  // namespace top
diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
index d745256a151e..90457389ca2e 100644
--- a/nnvm/src/top/tensor/transform.cc
+++ b/nnvm/src/top/tensor/transform.cc
@@ -44,7 +44,7 @@ Example::
         [4,5,6],
         [7,8,9]
     ],
-    [    [1,2,3],
+    [   [1,2,3],
         [4,5,6],
         [7,8,9]
     ]],
@@ -58,6 +58,12 @@ Example::
 .set_attr<FInferShape>("FInferShape", FlattenInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
 .add_argument("data", "Tensor", "Input data.")
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    return MakeGradNode("reshape_like", n,
+                        {ograds[0], n->inputs[0]});
+})
 .set_support_level(1);
 
 // concatenate
@@ -172,8 +178,8 @@ inline bool ExpandDimsInferShape(const NodeAttrs& attrs,
 NNVM_REGISTER_OP(expand_dims)
 .describe(R"code(Inserts a new axis of size 1 into the array shape
 
-For example, given ``x`` with shape ``(2,3,4)``, then ``expand_dims(x, axis=1)``
-will return a new array with shape ``(2,1,3,4)``.
+For example, given ``x`` with shape ``(2,3,4)``, then ``expand_dims(x, axis=1, num_newaxis=5)``
+will return a new array with shape ``(2,5,3,4)``.
 
 )code" NNVM_ADD_FILELINE)
 .add_argument("data", "Tensor", "Input tensor")
@@ -184,6 +190,61 @@ will return a new array with shape ``(2,1,3,4)``.
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
 .set_num_inputs(1)
 .set_num_outputs(1)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    const ExpandDimsParam& param = nnvm::get<ExpandDimsParam>(n->attrs.parsed);
+    return std::vector<NodeEntry> {
+      MakeNode("sum", n->attrs.name + "_grad", {ograds[0]},
+               {{"axis", std::to_string(param.axis)}})
+    };
+})
+.set_support_level(1);
+
+NNVM_REGISTER_OP(expand_like)
+  .describe(R"code(Expand an input array with the shape of second array.
+
+This operation can always be composed of unsqueezing and expanding dims.
+
+Examples::
+  input = [ 12.  19.  27.]
+  input.shape = (3,)
+
+  new_shape_array = [[[1,2],[2,3],[1,3]],
+                     [[1,4],[4,3],[5,2]],
+                     [[7,1],[7,2],[7,3]]]
+  new_shape_array.shape = (3, 3, 2)
+
+  expand_like(input, [1,2], new_shape_array) =
+                    [[[12,12],[12,12],[12,12]],
+                     [[19,19],[19,19],[19,19]],
+                     [[27,27],[27,27],[27,27]]]
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("input", "Tensor", "Source input")
+.add_argument("shape_like", "Tensor", "Input with new shape")
+.add_arguments(ReduceParam::__FIELDS__())
+.set_attr_parser(ParamParser<ReduceParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<ReduceParam>)
+.set_attr<nnvm::FInferShape>("FInferShape", AssignOutputAttr<TShape, 1, 0>)
+.set_attr<nnvm::FInferType>("FInferType", ElemwiseType<2, 1>)
+.set_num_inputs(2)
+.set_num_outputs(1)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds) {
+    const ReduceParam& param = nnvm::get<ReduceParam>(n->attrs.parsed);
+    std::ostringstream axis;
+    axis << param.axis;
+
+    return std::vector<NodeEntry>{
+      MakeNode("sum", n->attrs.name + "_grad",
+               {ograds[0]},
+               {{"axis", axis.str()},
+                {"keepdims", std::to_string(param.keepdims)},
+                {"exclude", std::to_string(param.exclude)}})
+    };
+})
 .set_support_level(1);
 
 // split
@@ -383,7 +444,7 @@ NNVM_REGISTER_OP(reshape)
 .describe(R"code(Reshapes the input array.
 
 Given an array and a shape, this function returns a copy of the array in the new shape.
-The shape is a tuple of integers such as (2,3,4).The size of the new shape should be same as the size of the input array.
+The shape is a tuple of integers such as (2,3,4). The size of the new shape should be same as the size of the input array.
 
 Example::
 
@@ -443,6 +504,46 @@ The significance of each is explained below:
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
 .set_num_inputs(1)
 .set_num_outputs(1)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds) {
+    return std::vector<NodeEntry>{
+      MakeNode("reshape_like", n->attrs.name + "_grad",
+               {ograds[0], n->inputs[0]})
+    };
+})
+.set_support_level(3);
+
+NNVM_REGISTER_OP(reshape_like)
+  .describe(R"code(Reshapes the input array by the size of another array.
+
+For an input array with shape ``(d1, d2, ..., dk)``, `reshape_like` operation reshapes
+the input array into an output array with the same shape as the second input array.
+
+.. note::
+    Sizes for both array should be compatible.
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "Tensor", "Input data.")
+.add_argument("shape_like", "Tensor", "Input data.")
+.set_num_inputs(1)
+.set_num_outputs(1)
+.set_attr<FInferShape>(
+  "FInferShape", [](const NodeAttrs& attrs,
+                    std::vector<TShape>* in_attrs,
+                    std::vector<TShape>* out_attrs) {
+    CHECK_EQ(in_attrs->at(0).Size(), in_attrs->at(1).Size())
+      << "Reshape inputs size should be compatible";
+    NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, 0, in_attrs->at(1));
+    return true;
+})
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds) {
+    return MakeGradNode("reshape_like", n,
+                        {ograds[0], n->inputs[0]});
+})
 .set_support_level(3);
 
 // squeeze
@@ -502,12 +603,14 @@ NNVM_REGISTER_OP(squeeze)
 Examples::
 
   x = [[[0], [1], [2]]]
+  x.shape = (1, 3, 1)
 
   squeeze(x) = [0, 1, 2]
 
   squeeze(x, 0) = [[0], [1], [2]]
 
   squeeze(x, (0, 2)) = [0, 1, 2]
+
 )code" NNVM_ADD_FILELINE)
 .add_argument("data", "Tensor", "Source input")
 .add_arguments(SqueezeParam::__FIELDS__())
@@ -517,6 +620,13 @@ Examples::
 .set_attr<nnvm::FInferType>("FInferType", ElemwiseType<1, 1>)
 .set_num_inputs(1)
 .set_num_outputs(1)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds) {
+    return std::vector<NodeEntry>{
+      MakeNode("reshape_like", n->attrs.name + "_grad", {n->inputs[0]})
+    };
+})
 .set_support_level(1);
 
 // tranpose
@@ -584,7 +694,16 @@ Examples::
 .set_attr<nnvm::FInferType>("FInferType", ElemwiseType<1, 1>)
 .set_num_inputs(1)
 .set_num_outputs(1)
-.set_support_level(4);
+.set_support_level(4)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds) {
+    const TransposeParam& param = nnvm::get<TransposeParam>(n->attrs.parsed);
+    std::ostringstream oss; oss << param.axes;
+    return std::vector<NodeEntry>{
+      MakeNode("transpose", n->attrs.name + "_t", {ograds[0]}, {{"axes", oss.str()}})
+    };
+});
 
 }  // namespace top
 }  // namespace nnvm
diff --git a/nnvm/tests/cpp/tuple_test.cc b/nnvm/tests/cpp/tuple_test.cc
index 36d194927515..806fdc42ac20 100644
--- a/nnvm/tests/cpp/tuple_test.cc
+++ b/nnvm/tests/cpp/tuple_test.cc
@@ -13,7 +13,7 @@ TEST(Tuple, Basic) {
   Tuple<int> z{1, 2, 3, 5, 6};
   std::ostringstream os;
   os << z;
-  CHECK_EQ(os.str(), "(1,2,3,5,6)");
+  CHECK_EQ(os.str(), "[1,2,3,5,6]");
   std::istringstream is(os.str());
   is >> y;
   CHECK_EQ(x, y);
diff --git a/nnvm/tests/python/unittest/test_graph.py b/nnvm/tests/python/unittest/test_graph.py
index 647749a8adc9..09d249700ad9 100644
--- a/nnvm/tests/python/unittest/test_graph.py
+++ b/nnvm/tests/python/unittest/test_graph.py
@@ -1,6 +1,7 @@
 import json
 import nnvm.symbol as sym
 import nnvm.graph as graph
+import nnvm.compiler.graph_util as graph_util
 
 def test_json_pass():
     x = sym.Variable('x')
@@ -117,13 +118,13 @@ def test_gradient():
     y = sym.Variable("y")
     z1 = sym.elemwise_add(x, sym.sqrt(y))
     z2 = sym.log(x)
-    gradient = graph.gradients([z1, z2], [x, y])
+    gradient = graph_util.gradients([z1, z2], [x, y])
     assert len(gradient) == 2
 
     g1 = sym.Variable("g1")
     g2 = sym.Variable("g2")
     grad_ys = [g1, g2]
-    gradient = graph.gradients(sym.Group([z1, z2]),
+    gradient = graph_util.gradients(sym.Group([z1, z2]),
                                sym.Group([x, y]), grad_ys=grad_ys)
     g_graph = graph.create(sym.Group(gradient)).ir()
     assert len(gradient) == 2
diff --git a/nnvm/tests/python/unittest/test_graph_gradient.py b/nnvm/tests/python/unittest/test_graph_gradient.py
new file mode 100644
index 000000000000..d3f25097819b
--- /dev/null
+++ b/nnvm/tests/python/unittest/test_graph_gradient.py
@@ -0,0 +1,136 @@
+import nnvm.symbol as sym
+from nnvm.compiler import graph_util
+
+def test_cnn_gradients():
+    # input data
+    h = 128
+    w = 128
+    data_shape = (1000, 3, h, w)
+    data = sym.Variable('data', shape=data_shape, dtype=0)
+
+    # conv2d
+    num_channels = 64
+    kernel_size = 32
+    conv_w_shape = (num_channels, 3, kernel_size, kernel_size)
+    conv_b_shape = (num_channels,)
+    conv_w = sym.Variable('conv_w', shape=conv_w_shape)
+    conv_b = sym.Variable('conv_b', shape=conv_b_shape)
+    conv1 = sym.conv2d(data=data, weight=conv_w, bias=conv_b,
+                      channels=num_channels, kernel_size=(kernel_size, kernel_size),
+                      name='conv1')
+    # relu1
+    relu1 = sym.relu(data=conv1, name='relu1')
+    # max pooling
+    max_pooling1 = sym.max_pool2d(data=relu1, pool_size=(2, 2), name='max_pooling1')
+    # flatten
+    flatten1 = sym.flatten(data=max_pooling1)
+    # shape after flatten
+    flatten_out_shape = (h - kernel_size) * (w - kernel_size) * num_channels
+    # dense1
+    dense1_hidden_units = 100
+    dense1 = sym.dense(data=flatten1, name='dense1', units=dense1_hidden_units)
+    # relu2
+    relu2 = sym.relu(data=dense1, name='relu2')
+    # dense2
+    dense2_hidden_units = 10
+    dense2 = sym.dense(data=relu2, name='dense2', units=dense2_hidden_units)
+    # softmax
+    mlp = sym.softmax(data=dense2, name='softmax')
+    # fake non-sparse label
+    label = sym.full_like(mlp, fill_value=1)
+    # cross entropy loss
+    ce_loss = sym.sum(
+        sym.elemwise_mul(sym.log_softmax(dense2), label),
+        axis=1,
+        keepdims=True,
+        name="ce_loss")
+
+    # input variables:
+    # print grad_g.symbol.list_input_names()
+    # >> ['data', 'conv_w', 'conv_b',
+    #     'dense1_weight', 'dense1_bias',
+    #     'dense2_weight', 'dense2_bias']
+
+    # output gradient variables:
+    # print grad_g.symbol.list_output_names()
+    # >> ['conv1_grad_data', 'conv1_grad_weight', 'conv1_grad_bias',
+    #     'dense1_grad_weight', 'dense1_grad_bias',
+    #     'dense2_grad_weight', 'dense2_grad_bias']
+    grad_g = graph_util.get_gradient_graph(ce_loss, ce_loss.list_input_variables())
+
+    # infer shape
+    in_shapes, out_shapes = graph_util.infer_shape(grad_g)
+
+    # forward graph shape
+    assert in_shapes == [list(data_shape), list(conv_w_shape), list(conv_b_shape),
+                          [dense1_hidden_units, flatten_out_shape], [dense1_hidden_units],
+                          [dense2_hidden_units, dense1_hidden_units], [dense2_hidden_units]]
+    # input grads shape should be equal with input shape
+    assert in_shapes == out_shapes
+
+    # output grads w.r.t input variables
+    grads = graph_util.gradients(ce_loss, ce_loss.list_input_variables())
+
+    # gradients number should be equal with grad_input number
+    assert len(grads) == len(ce_loss.list_input_variables())
+
+    # infer type
+    in_dtypes, out_dtypes = graph_util.infer_dtype(grad_g)
+    assert out_dtypes == ['float32', 'float32', 'float32', 'float32', 'float32', 'float32', 'float32']
+
+def test_multi_loss_graph_gradients():
+    # input data
+    shape1 = (1000, 100)
+    data1 = sym.Variable('data1', shape=(1000, 100), dtype=0)
+
+    # fake non-sparse label
+    label = sym.full(fill_value=3)
+
+    # square loss
+    sub1 = sym.elemwise_sub(data1, label, name="sub1")
+    square_loss = sym.sum(data=sub1**2, axis=1, name="square_loss")
+
+    # fake loss1
+    shape2 = (1000, )
+    data2 = sym.Variable('data2', shape=shape2, dtype=0)
+    loss1 = sym.sqrt(data2, name="loss1")
+
+    # fake loss2
+    loss2 = sym.relu(data1, name='loss2')
+
+    # block loss1
+    total_loss = sym.elemwise_sum(
+        sym.block_grad(loss1),
+        square_loss,
+        num_args=2,
+        name="total_loss")
+
+    # grad_g.symbol.list_output_names()
+    # >> ['loss1_grad_0_output', 'grad_sum_output']
+    grad_g = graph_util.get_gradient_graph([total_loss, loss2], total_loss.list_input_variables())
+    # infer shape
+    in_shapes, out_shapes = graph_util.infer_shape(grad_g)
+    assert out_shapes == [list(shape2), list(shape1)]
+
+    # grad_data1 is elemwise_sum of grad_loss2, grad_square_loss
+    grad_data1 = grad_g.symbol[1]
+    assert grad_data1.list_attr()['num_args'] == '2'
+
+    # block grad should return zero grad
+    grad_data2 = grad_g.symbol[0]
+    assert 'zeros_like' in grad_g.ir()
+
+    # test reverse infer shape for label
+    assert grad_g.apply('InferShape').json_attr('shape_num_unknown_nodes') == 0
+
+    # infer type
+    in_dtypes, out_dtypes = graph_util.infer_dtype(grad_g)
+    assert out_dtypes == ['float32', 'float32']
+    
+    # test reverse infer type for label
+    assert grad_g.apply('InferType').json_attr('dtype_num_unknown_nodes') == 0
+
+
+if __name__ == "__main__":
+    test_cnn_gradients()
+    test_multi_loss_graph_gradients()

From 452bcd5a548a5ea7a3e034d9984b45170e8c9da1 Mon Sep 17 00:00:00 2001
From: Yao Wang <kevinthesunwy@gmail.com>
Date: Thu, 18 Jan 2018 08:24:50 +0800
Subject: [PATCH 590/949]  Explain how to generate module library in Quick
 Start tutorial (#323)

* Explain how to generate module library

* Small fix
---
 nnvm/tutorials/define_and_compile_model.py | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/nnvm/tutorials/define_and_compile_model.py b/nnvm/tutorials/define_and_compile_model.py
index 1c7b2bcaf89f..639684fb77d7 100644
--- a/nnvm/tutorials/define_and_compile_model.py
+++ b/nnvm/tutorials/define_and_compile_model.py
@@ -68,6 +68,9 @@
 # in an optimized runtime module for model serving.
 #
 # We'll first compile for Nvidia GPU.
+# To generate the module library, TVM will first transfer graph IR into lower
+# intrinsic IR for the specified target backend, which is CUDA in this example.
+# Then target backend will generate module library.
 
 opt_level = 0
 target = tvm.target.cuda()

From e5280215ddf3ad02b8066e364806a511153a6def Mon Sep 17 00:00:00 2001
From: Siva <14215163+srkreddy1238@users.noreply.github.com>
Date: Thu, 18 Jan 2018 11:34:24 +0530
Subject: [PATCH 591/949] [NNVM] : Function header corrections. (#324)

---
 nnvm/src/compiler/compile_engine.cc | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/nnvm/src/compiler/compile_engine.cc b/nnvm/src/compiler/compile_engine.cc
index 79d7fcfb8109..3282189abd2f 100644
--- a/nnvm/src/compiler/compile_engine.cc
+++ b/nnvm/src/compiler/compile_engine.cc
@@ -121,7 +121,7 @@ class CompileEngine {
       return GraphCacheEntry();
     }
   }
-  // Find the function given graph key.
+  // Set the given function on given graph key.
   void Set(const GraphKey& key, GraphFunc func) {
     std::lock_guard<std::mutex> lock(mutex_);
     std::shared_ptr<GraphCacheEntryNode> n = std::make_shared<GraphCacheEntryNode>();
@@ -129,7 +129,7 @@ class CompileEngine {
     n->use_count = 1;
     cache_[key] = GraphCacheEntry(n);
   }
-    // Find the function given graph key.
+    // Clear the function cache.
   void Clear() {
     std::lock_guard<std::mutex> lock(mutex_);
     cache_.clear();

From e23161135a700509933e331785fd2609edfe6170 Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Fri, 19 Jan 2018 00:47:17 +0800
Subject: [PATCH 592/949] [TUTORIAL] add tutorial for mali gpu (#313)

* add tutorial for mali gpu

* update submodule version

* fix doc error

* fix server error

* fix server in test
---
 nnvm/tests/python/compiler/test_rpc_exec.py |   4 +-
 nnvm/tutorials/define_and_compile_model.py  |   2 +-
 nnvm/tutorials/deploy_model_on_mali_gpu.py  | 235 ++++++++++++++++++++
 nnvm/tutorials/deploy_model_on_rasp.py      |   8 +-
 4 files changed, 242 insertions(+), 7 deletions(-)
 create mode 100644 nnvm/tutorials/deploy_model_on_mali_gpu.py

diff --git a/nnvm/tests/python/compiler/test_rpc_exec.py b/nnvm/tests/python/compiler/test_rpc_exec.py
index 4cfd87ad243f..61baf18d262d 100644
--- a/nnvm/tests/python/compiler/test_rpc_exec.py
+++ b/nnvm/tests/python/compiler/test_rpc_exec.py
@@ -6,8 +6,8 @@
 
 def test_rpc_executor():
     host = "localhost"
-    port = 9091
-    server = rpc.Server(host, port)
+    port = 9100
+    server = rpc.Server(host, port, use_popen=True)
 
     x = sym.Variable("x")
     y = sym.Variable("y")
diff --git a/nnvm/tutorials/define_and_compile_model.py b/nnvm/tutorials/define_and_compile_model.py
index 639684fb77d7..35fe7b84c27e 100644
--- a/nnvm/tutorials/define_and_compile_model.py
+++ b/nnvm/tutorials/define_and_compile_model.py
@@ -143,7 +143,7 @@
     # run server locally
     host = 'localhost'
     port = 9090
-    server = rpc.Server(host=host, port=port)
+    server = rpc.Server(host=host, port=port, use_popen=True)
 
 # compile and save model library
 if use_rasp:
diff --git a/nnvm/tutorials/deploy_model_on_mali_gpu.py b/nnvm/tutorials/deploy_model_on_mali_gpu.py
new file mode 100644
index 000000000000..53016607112d
--- /dev/null
+++ b/nnvm/tutorials/deploy_model_on_mali_gpu.py
@@ -0,0 +1,235 @@
+"""
+Deploy the Pretrained Model on ARM Mali GPU
+=======================================================
+**Author**: `Lianmin Zheng <https://lmzheng.net/>`_, `Ziheng Jiang <https://ziheng.org/>`_
+
+This is an example of using NNVM to compile a ResNet model and
+deploy it on Firefly-RK3399 with ARM Mali GPU.  We will use the
+Mali-T860 MP4 GPU on this board to accelerate the inference.
+
+This tutorial is based on the `tutorial <http://nnvm.tvmlang.org/tutorials/deploy_model_on_rasp.html>`_
+for deploying on Raspberry Pi by `Ziheng Jiang <https://ziheng.org/>`_.
+Great thanks to the original author, I only do several lines of modification.
+
+To begin with, we import nnvm (for compilation) and TVM (for deployment).
+"""
+import tvm
+import nnvm.compiler
+import nnvm.testing
+from tvm.contrib import util, rpc
+from tvm.contrib import graph_runtime as runtime
+
+
+######################################################################
+# Build TVM Runtime on Device
+# ---------------------------
+#
+# There're some prerequisites: we need build tvm runtime and set up
+# a RPC server on remote device.
+#
+# To get started, clone tvm repo from github. It is important to clone
+# the submodules along, with --recursive option (Assuming you are in
+# your home directory):
+#
+#   .. code-block:: bash
+#
+#     git clone --recursive https://github.com/dmlc/tvm
+#
+# .. note::
+#
+#   Usually device has limited resources and we only need to build
+#   runtime. The idea is we will use TVM compiler on the local server
+#   to compile and upload the compiled program to the device and run
+#   the device function remotely.
+#
+#   .. code-block:: bash
+#
+#     make runtime
+#
+# After success of buildind runtime, we need set environment varibles
+# in :code:`~/.bashrc` file of yourself account or :code:`/etc/profile`
+# of system enviroment variables. Assuming your TVM directory is in
+# :code:`~/tvm` and set environment variables below your account.
+#
+#   .. code-block:: bash
+#
+#    vi ~/.bashrc
+#
+# We need edit :code:`~/.bashrc` using :code:`vi ~/.bashrc` and add
+# lines below (Assuming your TVM directory is in :code:`~/tvm`):
+#
+#   .. code-block:: bash
+#
+#    export TVM_HOME=~/tvm
+#    export PATH=$PATH:$TVM_HOME/lib
+#    export PYTHONPATH=$PYTHONPATH:$TVM_HOME/python
+#
+# To enable updated :code:`~/.bashrc`, execute :code:`source ~/.bashrc`.
+
+######################################################################
+# Set Up RPC Server on Device
+# ---------------------------
+# To set up a TVM RPC server on the your ARM device (our remote device),
+# we have prepared a one-line script so you only need to run this
+# command after following the installation guide to install TVM on
+# your device:
+#
+#   .. code-block:: bash
+#
+#     python -m tvm.exec.rpc_server --host 0.0.0.0 --port=9090
+#
+# After executing command above, if you see these lines below, it's
+# successful to start RPC server on your device.
+#
+#    .. code-block:: bash
+#
+#      Loading runtime library /home/YOURNAME/code/tvm/lib/libtvm_runtime.so... exec only
+#      INFO:root:RPCServer: bind to 0.0.0.0:9090
+#
+
+######################################################################
+# For demonstration, we simply start an RPC server on the same machine,
+# if :code:`use_mali` is False. If you have set up the remote
+# environment, please change the three lines below: change the
+# :code:`use_mali` to True, also change the :code:`host` and :code:`port`
+# with your device's host address and port number.
+
+use_mali = False
+host = '10.42.0.96'
+port = 9090
+
+if not use_mali:
+    # run server locally
+    host = 'localhost'
+    port = 9092
+    server = rpc.Server(host=host, port=port, use_popen=True)
+
+######################################################################
+# Prepare the Pretrained Model
+# ----------------------------
+# Back to the host machine, firstly, we need to download a MXNet Gluon
+# ResNet model from model zoo, which is pretrained on ImageNet. You
+# can found more details about this part at `Compile MXNet Models`
+
+from mxnet.gluon.model_zoo.vision import get_model
+from mxnet.gluon.utils import download
+from PIL import Image
+import numpy as np
+
+# only one line to get the model
+block = get_model('resnet18_v1', pretrained=True)
+
+######################################################################
+# In order to test our model, here we download an image of cat and
+# transform its format.
+img_name = 'cat.jpg'
+download('https://github.com/dmlc/mxnet.js/blob/master/data/cat.png?raw=true', img_name)
+image = Image.open(img_name).resize((224, 224))
+
+def transform_image(image):
+    image = np.array(image) - np.array([123., 117., 104.])
+    image /= np.array([58.395, 57.12, 57.375])
+    image = image.transpose((2, 0, 1))
+    image = image[np.newaxis, :]
+    return image
+
+x = transform_image(image)
+
+
+######################################################################
+# synset is used to transform the label from number of ImageNet class to
+# the word human can understand.
+synset_url = ''.join(['https://gist.githubusercontent.com/zhreshold/',
+                      '4d0b62f3d01426887599d4f7ede23ee5/raw/',
+                      '596b27d23537e5a1b5751d2b0481ef172f58b539/',
+                      'imagenet1000_clsid_to_human.txt'])
+synset_name = 'synset.txt'
+download(synset_url, synset_name)
+with open(synset_name) as f:
+    synset = eval(f.read())
+
+######################################################################
+# Now we would like to port the Gluon model to a portable computational graph.
+# It's as easy as several lines.
+
+# We support MXNet static graph(symbol) and HybridBlock in mxnet.gluon
+net, params = nnvm.frontend.from_mxnet(block)
+# we want a probability so add a softmax operator
+net = nnvm.sym.softmax(net)
+
+######################################################################
+# Here are some basic data workload configurations.
+batch_size = 1
+num_classes = 1000
+image_shape = (3, 224, 224)
+data_shape = (batch_size,) + image_shape
+out_shape = (batch_size, num_classes)
+
+######################################################################
+# Compile The Graph
+# -----------------
+# To compile the graph, we call the :any:`nnvm.compiler.build` function
+# with the graph configuration and parameters. As we use OpenCL for 
+# GPU computing, the tvm will generate both OpenCL kernel code and ARM
+# CPU host code. The CPU host code is used for calling OpenCL kernels.
+# In order to generate correct CPU code, we need to specify the target
+# triplet for host ARM device by setting the parameter :code:`target_host`.
+
+######################################################################
+# If we run the example locally for demonstration, we can simply set
+# it as :code:`llvm`. If to run it on the ARM device, you need to specify
+# its instruction set. Here is the option I use for my Firefly-RK3399.
+
+if use_mali:
+    target_host = "llvm -target=aarch64-linux-gnu -mattr=+neon"
+else:
+    target_host = "llvm"
+
+# set target as  `tvm.target.mali` instead of 'opencl' to enable
+# target-specified optimization
+graph, lib, params = nnvm.compiler.build(net, target=tvm.target.mali(),
+        shape={"data": data_shape}, params=params, target_host=target_host)
+
+# After `nnvm.compiler.build`, you will get three return values: graph,
+# library and the new parameter, since we do some optimization that will
+# change the parameters but keep the result of model as the same.
+
+# Save the library at local temporary directory.
+tmp = util.tempdir()
+lib_fname = tmp.relpath('net.tar')
+lib.export_library(lib_fname)
+
+######################################################################
+# Deploy the Model Remotely by RPC
+# --------------------------------
+# With RPC, you can deploy the model remotely from your host machine
+# to the remote device.
+
+# connect the server
+remote = rpc.connect(host, port)
+
+# upload the library to remote device and load it
+remote.upload(lib_fname)
+rlib = remote.load_module('net.tar')
+
+ctx = remote.cl(0)
+# upload the parameter
+rparams = {k: tvm.nd.array(v, ctx) for k, v in params.items()}
+
+# create the remote runtime module
+module = runtime.create(graph, rlib, ctx)
+# set parameter
+module.set_input(**rparams)
+# set input data
+module.set_input('data', tvm.nd.array(x.astype('float32')))
+# run
+module.run()
+# get output
+out = module.get_output(0, tvm.nd.empty(out_shape, ctx=ctx))
+# get top1 result
+top1 = np.argmax(out.asnumpy())
+print('TVM prediction top-1: {}'.format(synset[top1]))
+
+if not use_mali:
+    # terminate the local server
+    server.terminate()
diff --git a/nnvm/tutorials/deploy_model_on_rasp.py b/nnvm/tutorials/deploy_model_on_rasp.py
index 1aa84087167d..033a8b885799 100644
--- a/nnvm/tutorials/deploy_model_on_rasp.py
+++ b/nnvm/tutorials/deploy_model_on_rasp.py
@@ -85,8 +85,8 @@
 # For demonstration, we simply start an RPC server on the same machine,
 # if :code:`use_rasp` is False. If you have set up the remote
 # environment, please change the three lines below: change the
-# :code:`use_rasp` to True, also change the host and port with your
-# device's host address and port number.
+# :code:`use_rasp` to True, also change the :code:`host` and :code:`port`
+# with your device's host address and port number.
 
 use_rasp = False
 host = 'rasp0'
@@ -96,7 +96,7 @@
     # run server locally
     host = 'localhost'
     port = 9091
-    server = rpc.Server(host=host, port=port)
+    server = rpc.Server(host=host, port=port, use_popen=True)
 
 ######################################################################
 # Prepare the Pretrained Model
@@ -114,7 +114,7 @@
 block = get_model('resnet18_v1', pretrained=True)
 
 ######################################################################
-# In order to test our model, here we download a image of cat and
+# In order to test our model, here we download an image of cat and
 # transform its format.
 img_name = 'cat.jpg'
 download('https://github.com/dmlc/mxnet.js/blob/master/data/cat.png?raw=true', img_name)

From ab0b65c89e827b1c9cfbc4a2be21df40193ee514 Mon Sep 17 00:00:00 2001
From: thefiddler <sa@xcv.io>
Date: Thu, 18 Jan 2018 19:12:58 +0100
Subject: [PATCH 593/949] Fix keras frontend elementwise-ops for lists with
 len>2  (fixes issue #325) (#326)

* Added elementwise-add test

* Fix typo

* Fixed elem-wise ops for lists with len>2
---
 nnvm/python/nnvm/frontend/keras.py            | 27 ++++++++++---------
 .../python/frontend/keras/test_forward.py     | 22 +++++++++++++++
 2 files changed, 37 insertions(+), 12 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/keras.py b/nnvm/python/nnvm/frontend/keras.py
index 30c0a132ff5f..58ee5faf8771 100644
--- a/nnvm/python/nnvm/frontend/keras.py
+++ b/nnvm/python/nnvm/frontend/keras.py
@@ -77,18 +77,21 @@ def _convert_advanced_activation(insym, keras_layer, _):
 
 def _convert_merge(insym, keras_layer, _):
     merge_type = type(keras_layer).__name__
-    if merge_type == 'Add':
-        return _sym.elemwise_add(insym[0], insym[1])
-    elif merge_type == 'Subtract':
-        return _sym.elemwise_sub(insym[0], insym[1])
-    elif merge_type == 'Multiply':
-        return _sym.elemwise_mul(insym[0], insym[1])
-    elif merge_type == 'Average':
-        raise NotImplementedError('Average merge not implemented')
-    elif merge_type == 'Maximum':
-        raise NotImplementedError('Maximum merge not implemented')
-    else:
-        raise TypeError("Unsupported merge type : {}".format(merge_type))
+    ret = insym[0]
+    for i in range(1, len(insym)):
+        if merge_type == 'Add':
+            ret = _sym.elemwise_add(ret, insym[i])
+        elif merge_type == 'Subtract':
+            ret = _sym.elemwise_sub(ret, insym[i])
+        elif merge_type == 'Multiply':
+            ret = _sym.elemwise_mul(ret, insym[i])
+        elif merge_type == 'Average':
+            raise NotImplementedError('Average merge not implemented')
+        elif merge_type == 'Maximum':
+            raise NotImplementedError('Maximum merge not implemented')
+        else:
+            raise TypeError("Unsupported merge type : {}".format(merge_type))
+    return ret
 
 
 def _convert_dense(insym, keras_layer, symtab):
diff --git a/nnvm/tests/python/frontend/keras/test_forward.py b/nnvm/tests/python/frontend/keras/test_forward.py
index b89176217817..6461d220b301 100644
--- a/nnvm/tests/python/frontend/keras/test_forward.py
+++ b/nnvm/tests/python/frontend/keras/test_forward.py
@@ -39,6 +39,27 @@ def get_tvm_output(x, target, ctx, input_name='data', dtype='float32'):
         np.testing.assert_allclose(keras_out, tvm_out, rtol=1e-5, atol=1e-5)
 
 
+def test_forward_elemwise_add():
+    print("test_forward_elemwise_add")
+    r = []
+    data = keras.layers.Input(shape=(32,32,3))
+    x = keras.layers.Conv2D(8, (3, 3), padding="same")(data)
+    r.append(x)
+    x = keras.layers.Conv2D(8, (3, 3), padding="same")(x)
+    r.append(x)
+    x = keras.layers.Conv2D(8, (3, 3), padding="same")(x)
+
+    y = keras.layers.add([keras.layers.add([x, r[0]]), r[1]])
+    y = keras.layers.GlobalAveragePooling2D()(y)
+    keras_model = keras.models.Model(data, y)
+    verify_keras_frontend(keras_model)
+
+    y = keras.layers.add([x, r[0], r[1]])
+    y = keras.layers.GlobalAveragePooling2D()(y)
+    keras_model = keras.models.Model(data, y)
+    verify_keras_frontend(keras_model)
+
+
 def test_forward_softrelu():
     data = keras.layers.Input(shape=(32,32,3))
     x = keras.layers.Activation('softplus')(data)
@@ -114,6 +135,7 @@ def test_forward_resnet50():
 
 
 if __name__ == '__main__':
+    test_forward_elemwise_add()
     test_forward_softrelu()
     test_forward_leaky_relu()
     test_forward_dense()

From 0b5b535f52adac0568dcf255a5340e174979cc9a Mon Sep 17 00:00:00 2001
From: masahi <masahi129@gmail.com>
Date: Sat, 20 Jan 2018 02:47:23 +0900
Subject: [PATCH 594/949] fix upsampling fuse pattern (#330)

---
 nnvm/python/nnvm/top/nn.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index ccfdacb4cfd8..88aafec625ea 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -264,4 +264,4 @@ def schedule_upsampling(_, outs, target):
     with tvm.target.create(target):
         return topi.generic.schedule_injective(outs)
 
-reg.register_pattern("upsampling", OpPattern.OUT_ELEMWISE_FUSABLE)
+reg.register_pattern("upsampling", OpPattern.INJECTIVE)

From b8706b5fad8efdd37e242abad172124ec4af8cb1 Mon Sep 17 00:00:00 2001
From: thefiddler <sa@xcv.io>
Date: Fri, 19 Jan 2018 21:39:47 +0100
Subject: [PATCH 595/949] Add test for issue #327 (#328)

* Added test for adding down-/up-sampled layers

* Enabled test for adding down-/up-sampled layers

* Normalize whitespace
---
 nnvm/tests/python/frontend/keras/test_forward.py | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/nnvm/tests/python/frontend/keras/test_forward.py b/nnvm/tests/python/frontend/keras/test_forward.py
index 6461d220b301..0f5846dcf149 100644
--- a/nnvm/tests/python/frontend/keras/test_forward.py
+++ b/nnvm/tests/python/frontend/keras/test_forward.py
@@ -38,7 +38,7 @@ def get_tvm_output(x, target, ctx, input_name='data', dtype='float32'):
         tvm_out = get_tvm_output(x.transpose([0,3,1,2]), target, ctx)
         np.testing.assert_allclose(keras_out, tvm_out, rtol=1e-5, atol=1e-5)
 
-
+    
 def test_forward_elemwise_add():
     print("test_forward_elemwise_add")
     r = []
@@ -58,6 +58,16 @@ def test_forward_elemwise_add():
     y = keras.layers.GlobalAveragePooling2D()(y)
     keras_model = keras.models.Model(data, y)
     verify_keras_frontend(keras_model)
+    
+    
+def test_forward_elementwise_add2():
+    data = keras.layers.Input(shape=(32,32,3))
+    r = keras.layers.Conv2D(10, (3, 3), padding="same")(data)
+    x = keras.layers.Conv2D(10, (3, 3), strides=(2, 2), padding="same")(data)
+    x = keras.layers.UpSampling2D()(x)
+    x = keras.layers.add([x, r])
+    x = keras.layers.GlobalAveragePooling2D()(x)
+    keras_model = keras.models.Model(data, x)
 
 
 def test_forward_softrelu():
@@ -136,6 +146,7 @@ def test_forward_resnet50():
 
 if __name__ == '__main__':
     test_forward_elemwise_add()
+    test_forward_elementwise_add2()
     test_forward_softrelu()
     test_forward_leaky_relu()
     test_forward_dense()

From 00f36ee9bb11e8c93c23ef6b3779d1bd752c2ef3 Mon Sep 17 00:00:00 2001
From: masahi <masahi129@gmail.com>
Date: Mon, 22 Jan 2018 08:08:52 +0900
Subject: [PATCH 596/949] [TUTORIAL] add extern lib tutorial (#331)

* add extern lib tutorial

* do fix
---
 nnvm/tutorials/using_external_lib.py | 218 +++++++++++++++++++++++++++
 1 file changed, 218 insertions(+)
 create mode 100644 nnvm/tutorials/using_external_lib.py

diff --git a/nnvm/tutorials/using_external_lib.py b/nnvm/tutorials/using_external_lib.py
new file mode 100644
index 000000000000..6cca37867fc0
--- /dev/null
+++ b/nnvm/tutorials/using_external_lib.py
@@ -0,0 +1,218 @@
+"""
+Using external libaries with NNVM
+=====================
+**Author**: `Masahiro Masuda <https://github.com/masahi>`_
+
+This is a short tutorial on how to use external libraries such as cuDNN, or cuBLAS with NNVM.
+
+NNVM uses TVM internally to generate target specific code. For example, with cuda backend TVM generates cuda kernels for all layers in the user provided network.
+But sometimes it is also helpful to incorporate external libraries developed by various vendors into NNVM.
+Luckily, TVM has a mechanism to transparently call into these libraries.
+For NNVM users, all we need to do is just to set a target string appropriately.
+
+Before we can use external libraries from NNVM, your TVM needs to be built with libraries you want to use.
+For example, to use cuDNN, USE_CUDNN option in tvm/make/config.mk needs to be enabled, and cuDNN include and library directories need to be specified.
+
+To begin with, we import NNVM and TVM.
+"""
+import tvm
+import numpy as np
+from tvm.contrib import graph_runtime as runtime
+import nnvm.symbol as sym
+import nnvm.compiler
+from nnvm.testing import utils
+
+######################################################################
+# Create a simple network
+# ---------------------------------------------
+# Let's create a very simple network for demonstration.
+# It consists of convolution, batch normalization, and ReLU activation.
+
+out_channels = 16
+data = sym.Variable(name="data")
+simple_net = sym.conv2d(data=data, kernel_size=(3,3), channels=out_channels, padding = (1, 1), use_bias=True)
+simple_net = sym.batch_norm(data=simple_net)
+simple_net = sym.relu(data=simple_net)
+
+batch_size = 1
+data_shape = (batch_size, 3, 224, 224)
+net, params = utils.create_workload(simple_net, batch_size, data_shape[1:])
+
+######################################################################
+# Build and run with cuda backend
+# ---------------------------------------------
+# We build and run this network with cuda backend, as usual.
+# By setting the logging level to DEBUG, the result of NNVM graph compilation will be dumped as pseudo code.
+import logging
+logging.basicConfig(level=logging.DEBUG) # to dump TVM IR after fusion
+
+target = "cuda"
+graph, lib, params = nnvm.compiler.build(
+    net, target, shape={"data": data_shape}, params=params)
+
+ctx = tvm.context(target, 0)
+data = np.random.uniform(-1, 1, size=data_shape).astype("float32")
+module = runtime.create(graph, lib, ctx)
+module.set_input(**params)
+module.set_input("data", data)
+module.run()
+out_shape = (batch_size, out_channels, 224, 224)
+out = module.get_output(0, tvm.nd.empty(out_shape))
+out_cuda = out.asnumpy()
+
+######################################################################
+# The generated pseudo code should look something like below.
+# Note how bias add, batch normalization, and ReLU activation are fused into the convolution kernel.
+# TVM generates a single, fused kernel from this representation.
+#
+# .. code-block:: text
+#
+#       produce compute {
+#         // attr [iter_var(blockIdx.x, , blockIdx.x)] thread_extent = 112
+#         // attr [input1.shared] storage_scope = "shared"
+#         allocate input1.shared[float32 * 16 * 3 * 3 * 3]
+#         // attr [compute] storage_scope = "local"
+#         allocate compute[float32 * 16 * 1 * 1 * 1 * 1]
+#         // attr [pad_temp.global.global.shared] storage_scope = "shared"
+#         allocate pad_temp.global.global.shared[float32 * 1 * 1 * 4 * 57 * 4]
+#         // attr [iter_var(threadIdx.x, Range(min=0, extent=448), threadIdx.x)] thread_extent = 448
+#         produce compute {
+#           produce input1.shared {
+#             for (ax0, 0, 16) {
+#               if (likely((threadIdx.x < 27))) {
+#                 input1.shared[(threadIdx.x + (ax0*27))] = input1[((((((blockIdx.x/112)*48) + (threadIdx.x/9))*9) + (threadIdx.x % 9)) + (ax0*27))]
+#               }
+#             }
+#           }
+#           compute[0] = 0.000000f
+#           compute[1] = 0.000000f
+#           compute[2] = 0.000000f
+#           compute[3] = 0.000000f
+#           compute[4] = 0.000000f
+#           compute[5] = 0.000000f
+#           compute[6] = 0.000000f
+#           compute[7] = 0.000000f
+#           compute[8] = 0.000000f
+#           compute[9] = 0.000000f
+#           compute[10] = 0.000000f
+#           compute[11] = 0.000000f
+#           compute[12] = 0.000000f
+#           compute[13] = 0.000000f
+#           compute[14] = 0.000000f
+#           compute[15] = 0.000000f
+#           for (rc, 0, 3) {
+#             produce pad_temp.global.global.shared {
+#               if (likely((threadIdx.x < 228))) {
+#                 if (likely(((blockIdx.x*2) < (226 - (threadIdx.x/57))))) {
+#                   pad_temp.global.global.shared[ramp((threadIdx.x*4), 1, 4)] = pad_temp[ramp(((((((blockIdx.x*2) + (threadIdx.x/57))*57) + (threadIdx.x % 57)) + (rc*12882))*4), 1, 4)]
+#                 }
+#               }
+#             }
+#             for (ry, 0, 3) {
+#               for (rx, 0, 3) {
+#                 compute[0] = (compute[0] + (pad_temp.global.global.shared[(((((threadIdx.x/224)*228) + (threadIdx.x % 224)) + (ry*228)) + rx)]*input1.shared[((((rc*3) + ry)*3) + rx)]))
+#                 compute[1] = (compute[1] + (pad_temp.global.global.shared[(((((threadIdx.x/224)*228) + (threadIdx.x % 224)) + (ry*228)) + rx)]*input1.shared[(((((rc*3) + ry)*3) + rx) + 27)]))
+#                 compute[2] = (compute[2] + (pad_temp.global.global.shared[(((((threadIdx.x/224)*228) + (threadIdx.x % 224)) + (ry*228)) + rx)]*input1.shared[(((((rc*3) + ry)*3) + rx) + 54)]))
+#                 compute[3] = (compute[3] + (pad_temp.global.global.shared[(((((threadIdx.x/224)*228) + (threadIdx.x % 224)) + (ry*228)) + rx)]*input1.shared[(((((rc*3) + ry)*3) + rx) + 81)]))
+#                 compute[4] = (compute[4] + (pad_temp.global.global.shared[(((((threadIdx.x/224)*228) + (threadIdx.x % 224)) + (ry*228)) + rx)]*input1.shared[(((((rc*3) + ry)*3) + rx) + 108)]))
+#                 compute[5] = (compute[5] + (pad_temp.global.global.shared[(((((threadIdx.x/224)*228) + (threadIdx.x % 224)) + (ry*228)) + rx)]*input1.shared[(((((rc*3) + ry)*3) + rx) + 135)]))
+#                 compute[6] = (compute[6] + (pad_temp.global.global.shared[(((((threadIdx.x/224)*228) + (threadIdx.x % 224)) + (ry*228)) + rx)]*input1.shared[(((((rc*3) + ry)*3) + rx) + 162)]))
+#                 compute[7] = (compute[7] + (pad_temp.global.global.shared[(((((threadIdx.x/224)*228) + (threadIdx.x % 224)) + (ry*228)) + rx)]*input1.shared[(((((rc*3) + ry)*3) + rx) + 189)]))
+#                 compute[8] = (compute[8] + (pad_temp.global.global.shared[(((((threadIdx.x/224)*228) + (threadIdx.x % 224)) + (ry*228)) + rx)]*input1.shared[(((((rc*3) + ry)*3) + rx) + 216)]))
+#                 compute[9] = (compute[9] + (pad_temp.global.global.shared[(((((threadIdx.x/224)*228) + (threadIdx.x % 224)) + (ry*228)) + rx)]*input1.shared[(((((rc*3) + ry)*3) + rx) + 243)]))
+#                 compute[10] = (compute[10] + (pad_temp.global.global.shared[(((((threadIdx.x/224)*228) + (threadIdx.x % 224)) + (ry*228)) + rx)]*input1.shared[(((((rc*3) + ry)*3) + rx) + 270)]))
+#                 compute[11] = (compute[11] + (pad_temp.global.global.shared[(((((threadIdx.x/224)*228) + (threadIdx.x % 224)) + (ry*228)) + rx)]*input1.shared[(((((rc*3) + ry)*3) + rx) + 297)]))
+#                 compute[12] = (compute[12] + (pad_temp.global.global.shared[(((((threadIdx.x/224)*228) + (threadIdx.x % 224)) + (ry*228)) + rx)]*input1.shared[(((((rc*3) + ry)*3) + rx) + 324)]))
+#                 compute[13] = (compute[13] + (pad_temp.global.global.shared[(((((threadIdx.x/224)*228) + (threadIdx.x % 224)) + (ry*228)) + rx)]*input1.shared[(((((rc*3) + ry)*3) + rx) + 351)]))
+#                 compute[14] = (compute[14] + (pad_temp.global.global.shared[(((((threadIdx.x/224)*228) + (threadIdx.x % 224)) + (ry*228)) + rx)]*input1.shared[(((((rc*3) + ry)*3) + rx) + 378)]))
+#                 compute[15] = (compute[15] + (pad_temp.global.global.shared[(((((threadIdx.x/224)*228) + (threadIdx.x % 224)) + (ry*228)) + rx)]*input1.shared[(((((rc*3) + ry)*3) + rx) + 405)]))
+#               }
+#             }
+#           }
+#         }
+#         compute[(((((blockIdx.x + ((blockIdx.x/112)*1792))*2) + (threadIdx.x/224))*224) + (threadIdx.x % 224))] = max((((compute[0] + input2[((blockIdx.x/112)*16)])*input3[((blockIdx.x/112)*16)]) + input4[((blockIdx.x/112)*16)]), 0.000000f)
+#         compute[((((((blockIdx.x + ((blockIdx.x/112)*1792))*2) + (threadIdx.x/224))*224) + (threadIdx.x % 224)) + 50176)] = max((((compute[1] + input2[(((blockIdx.x/112)*16) + 1)])*input3[(((blockIdx.x/112)*16) + 1)]) + input4[(((blockIdx.x/112)*16) + 1)]), 0.000000f)
+#         compute[((((((blockIdx.x + ((blockIdx.x/112)*1792))*2) + (threadIdx.x/224))*224) + (threadIdx.x % 224)) + 100352)] = max((((compute[2] + input2[(((blockIdx.x/112)*16) + 2)])*input3[(((blockIdx.x/112)*16) + 2)]) + input4[(((blockIdx.x/112)*16) + 2)]), 0.000000f)
+#         compute[((((((blockIdx.x + ((blockIdx.x/112)*1792))*2) + (threadIdx.x/224))*224) + (threadIdx.x % 224)) + 150528)] = max((((compute[3] + input2[(((blockIdx.x/112)*16) + 3)])*input3[(((blockIdx.x/112)*16) + 3)]) + input4[(((blockIdx.x/112)*16) + 3)]), 0.000000f)
+#         compute[((((((blockIdx.x + ((blockIdx.x/112)*1792))*2) + (threadIdx.x/224))*224) + (threadIdx.x % 224)) + 200704)] = max((((compute[4] + input2[(((blockIdx.x/112)*16) + 4)])*input3[(((blockIdx.x/112)*16) + 4)]) + input4[(((blockIdx.x/112)*16) + 4)]), 0.000000f)
+#         compute[((((((blockIdx.x + ((blockIdx.x/112)*1792))*2) + (threadIdx.x/224))*224) + (threadIdx.x % 224)) + 250880)] = max((((compute[5] + input2[(((blockIdx.x/112)*16) + 5)])*input3[(((blockIdx.x/112)*16) + 5)]) + input4[(((blockIdx.x/112)*16) + 5)]), 0.000000f)
+#         compute[((((((blockIdx.x + ((blockIdx.x/112)*1792))*2) + (threadIdx.x/224))*224) + (threadIdx.x % 224)) + 301056)] = max((((compute[6] + input2[(((blockIdx.x/112)*16) + 6)])*input3[(((blockIdx.x/112)*16) + 6)]) + input4[(((blockIdx.x/112)*16) + 6)]), 0.000000f)
+#         compute[((((((blockIdx.x + ((blockIdx.x/112)*1792))*2) + (threadIdx.x/224))*224) + (threadIdx.x % 224)) + 351232)] = max((((compute[7] + input2[(((blockIdx.x/112)*16) + 7)])*input3[(((blockIdx.x/112)*16) + 7)]) + input4[(((blockIdx.x/112)*16) + 7)]), 0.000000f)
+#         compute[((((((blockIdx.x + ((blockIdx.x/112)*1792))*2) + (threadIdx.x/224))*224) + (threadIdx.x % 224)) + 401408)] = max((((compute[8] + input2[(((blockIdx.x/112)*16) + 8)])*input3[(((blockIdx.x/112)*16) + 8)]) + input4[(((blockIdx.x/112)*16) + 8)]), 0.000000f)
+#         compute[((((((blockIdx.x + ((blockIdx.x/112)*1792))*2) + (threadIdx.x/224))*224) + (threadIdx.x % 224)) + 451584)] = max((((compute[9] + input2[(((blockIdx.x/112)*16) + 9)])*input3[(((blockIdx.x/112)*16) + 9)]) + input4[(((blockIdx.x/112)*16) + 9)]), 0.000000f)
+#         compute[((((((blockIdx.x + ((blockIdx.x/112)*1792))*2) + (threadIdx.x/224))*224) + (threadIdx.x % 224)) + 501760)] = max((((compute[10] + input2[(((blockIdx.x/112)*16) + 10)])*input3[(((blockIdx.x/112)*16) + 10)]) + input4[(((blockIdx.x/112)*16) + 10)]), 0.000000f)
+#         compute[((((((blockIdx.x + ((blockIdx.x/112)*1792))*2) + (threadIdx.x/224))*224) + (threadIdx.x % 224)) + 551936)] = max((((compute[11] + input2[(((blockIdx.x/112)*16) + 11)])*input3[(((blockIdx.x/112)*16) + 11)]) + input4[(((blockIdx.x/112)*16) + 11)]), 0.000000f)
+#         compute[((((((blockIdx.x + ((blockIdx.x/112)*1792))*2) + (threadIdx.x/224))*224) + (threadIdx.x % 224)) + 602112)] = max((((compute[12] + input2[(((blockIdx.x/112)*16) + 12)])*input3[(((blockIdx.x/112)*16) + 12)]) + input4[(((blockIdx.x/112)*16) + 12)]), 0.000000f)
+#         compute[((((((blockIdx.x + ((blockIdx.x/112)*1792))*2) + (threadIdx.x/224))*224) + (threadIdx.x % 224)) + 652288)] = max((((compute[13] + input2[(((blockIdx.x/112)*16) + 13)])*input3[(((blockIdx.x/112)*16) + 13)]) + input4[(((blockIdx.x/112)*16) + 13)]), 0.000000f)
+#         compute[((((((blockIdx.x + ((blockIdx.x/112)*1792))*2) + (threadIdx.x/224))*224) + (threadIdx.x % 224)) + 702464)] = max((((compute[14] + input2[(((blockIdx.x/112)*16) + 14)])*input3[(((blockIdx.x/112)*16) + 14)]) + input4[(((blockIdx.x/112)*16) + 14)]), 0.000000f)
+#         compute[((((((blockIdx.x + ((blockIdx.x/112)*1792))*2) + (threadIdx.x/224))*224) + (threadIdx.x % 224)) + 752640)] = max((((compute[15] + input2[(((blockIdx.x/112)*16) + 15)])*input3[(((blockIdx.x/112)*16) + 15)]) + input4[(((blockIdx.x/112)*16) + 15)]), 0.000000f)
+#       }
+#
+
+######################################################################
+# Use cuDNN for a convolutional layer
+# ---------------------------------------------
+# We can use cuDNN to replace convolution kernels with cuDNN ones.
+# To do that, all we need to do is to append the option " -libs=cudnn" to the target string.
+net, params = utils.create_workload(simple_net, batch_size, data_shape[1:])
+target = "cuda -libs=cudnn" # use cudnn for convolution
+graph, lib, params = nnvm.compiler.build(
+    net, target, shape={"data": data_shape}, params=params)
+
+ctx = tvm.context(target, 0)
+data = np.random.uniform(-1, 1, size=data_shape).astype("float32")
+module = runtime.create(graph, lib, ctx)
+module.set_input(**params)
+module.set_input("data", data)
+module.run()
+out_shape = (batch_size, out_channels, 224, 224)
+out = module.get_output(0, tvm.nd.empty(out_shape))
+out_cudnn = out.asnumpy()
+
+######################################################################
+# Note that if you use cuDNN, NNVM cannot fuse convolution with layers following it.
+# This is because layer fusion happens at the level of TVM internal representation(IR).
+# NNVM treats external libraries as black box, so there is no way to fuse them with TVM IR.
+#
+# The pseudo code below shows that cuDNN convolution + bias add + batch norm + ReLU turned into two stages of computation, one for cuDNN call and the other for the rest of operations.
+#
+# .. code-block:: text
+#
+#       allocate y[float32 * 1 * 16 * 224 * 224]
+#       produce y {
+#          // attr [0] extern_scope = 0
+#          tvm_call_packed("tvm.contrib.cudnn.conv2d.forward", 1, 0, 1, 1, 1, 1, 1, 1, 1, tvm_stack_make_array(input0, tvm_stack_make_shape(1, 3, 224, 224), 0, 4, 0.000000f, 0), tvm_stack_make_array(input1, tvm_stack_make_shape(16, 3, 3, 3), 0, 4, 0.000000f, 0), tvm_stack_make_array(y, tvm_stack_make_shape(1, 16, 224, 224), 0, 4, 0.000000f, 0))
+#        }
+#       produce compute {
+#          // attr [iter_var(blockIdx.x, , blockIdx.x)] thread_extent = 1568
+#          // attr [iter_var(threadIdx.x, , threadIdx.x)] thread_extent = 512
+#          compute[((((((blockIdx.x*512) + threadIdx.x)/50176) + ((((blockIdx.x*512) + threadIdx.x)/802816)*16))*50176) + ((((((blockIdx.x*512) + threadIdx.x)/224) % 224)*224) + (((blockIdx.x*64) + threadIdx.x) % 224)))] = max((((y[((((((blockIdx.x*512) + threadIdx.x)/50176) + ((((blockIdx.x*512) + threadIdx.x)/802816)*16))*50176) + ((((((blockIdx.x*512) + threadIdx.x)/224) % 224)*224) + (((blockIdx.x*64) + threadIdx.x) % 224)))] + input2[(((blockIdx.x*512) + threadIdx.x)/50176)])*input3[(((blockIdx.x*512) + threadIdx.x)/50176)]) + input4[(((blockIdx.x*512) + threadIdx.x)/50176)]), 0.000000f)
+#        }
+#
+
+######################################################################
+# Verify the result
+# ---------------------------------------------
+# We can check that the results of two runs match.
+
+np.testing.assert_allclose(out_cuda, out_cudnn, rtol=1e-5)
+
+#####################################################################
+# Conclusion
+# ---------------------------------------------
+# This tutorial covered the usage of cuDNN with NNVM.
+# We also have support for cuBLAS. If cuBLAS is enabled, it will be used inside a fully connected layer (nnvm.symbol.dense).
+# To use cuBLAS, set a target string as "cuda -libs=cublas".
+# You can use both cuDNN and cuBLAS with "cuda -libs=cudnn,cublas".
+#
+# For ROCm backend, we have support for MIOpen and rocBLAS.
+# They can be enabled with target "rocm -libs=miopen,rocblas".
+#
+# Being able to use external libraries is great, but we need to keep in mind some cautions.
+#
+# First, the use of external libraries may restrict your usage of TVM and NNVM.
+# For example, MIOpen only supports NCHW layout and fp32 data type at the moment, so you cannot use other layouts or data type in TVM.
+#
+# Second, and more importantly, external libraries restrict the possibility of operator fusion during graph compilation, as shown above.
+# TVM and NNVM aim to achieve the best performance on a varity of hardwares, with joint operator level and graph level optimization.
+# To achieve this goal, we should continue developing better optimizations for TVM and NNVM, while using external libraries as a nice way to fall back to existing implementation when necessary.

From 3482093400649f50f016d97e7e3106ba150b4387 Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Sat, 27 Jan 2018 05:03:36 +0800
Subject: [PATCH 597/949] Add CacheItem2Schedule Extension (#338)

* add CacheItem2Schedule extension

* fix lint

* move function position

* make cache item visible to frontend
---
 nnvm/src/compiler/compile_engine.cc  | 96 +++++++++++++++++++++-------
 nnvm/src/compiler/compile_engine.h   | 11 ++--
 nnvm/src/compiler/graph_fuse.cc      | 12 +++-
 nnvm/src/compiler/packed_func_ext.cc |  1 +
 4 files changed, 89 insertions(+), 31 deletions(-)

diff --git a/nnvm/src/compiler/compile_engine.cc b/nnvm/src/compiler/compile_engine.cc
index 3282189abd2f..5902abdc6afb 100644
--- a/nnvm/src/compiler/compile_engine.cc
+++ b/nnvm/src/compiler/compile_engine.cc
@@ -82,8 +82,7 @@ class CompileEngine {
   GraphFunc Lower(Graph graph,
                   const Array<tvm::Tensor>& inputs,
                   const std::string& target,
-                  const Op* schedule_op_key,
-                  const NodeAttrs& schedule_op_attr) {
+                  int master_idx) {
     GraphKey key = GraphKeyNode::make(graph, inputs, target);
     std::lock_guard<std::mutex> lock(mutex_);
     auto it = cache_.find(key);
@@ -91,11 +90,11 @@ class CompileEngine {
       ++(it->second->use_count);
       return it->second->graph_func;
     }
-    GraphFunc f = DoLower(key->graph, key->inputs, key->target,
-                          schedule_op_key, schedule_op_attr);
+    GraphFunc f = DoLower(key->graph, key->inputs, key->target, master_idx);
     std::shared_ptr<GraphCacheEntryNode> n = std::make_shared<GraphCacheEntryNode>();
     n->graph_func = f;
     n->use_count = 1;
+    n->master_idx = master_idx;
     cache_[key] = GraphCacheEntry(n);
     return f;
   }
@@ -134,12 +133,14 @@ class CompileEngine {
     std::lock_guard<std::mutex> lock(mutex_);
     cache_.clear();
   }
-  // run the actual lowering process
-  GraphFunc DoLower(Graph graph,
-                    const Array<tvm::Tensor>& inputs,
-                    const std::string& target,
-                    const Op* schedule_op_key,
-                    const NodeAttrs& schedule_op_attr) {
+
+  // get schedule and its args
+  std::pair<Schedule, Array<tvm::Tensor> > GetScheduleArgs(Graph graph,
+                                                           const Array<tvm::Tensor> &inputs,
+                                                           const std::string &target,
+                                                           int master_idx,
+                                                           std::string *readable_name,
+                                                           Array<tvm::Tensor> *outputs) {
     // shape, type
     static auto& fcompute =
         nnvm::Op::GetAttr<FTVMCompute>("FTVMCompute");
@@ -172,18 +173,18 @@ class CompileEngine {
       tensor_vec[idx.entry_id(nid, 0)] = inputs[i];
     }
 
-    std::ostringstream readable_name;
-    readable_name << "fuse";
+    std::ostringstream readable_name_os;
+    readable_name_os << "fuse";
     for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
       const auto& inode = idx[nid];
       if (inode.source->is_variable()) continue;
-      Array<Tensor> inputs, out_info;
-      readable_name << "_" << inode.source->op()->name;
+      Array<Tensor> op_inputs, out_info;
+      readable_name_os << "_" << inode.source->op()->name;
       // input array
       for (const IndexedGraph::NodeEntry& e : inode.inputs) {
         const tvm::Tensor& t = tensor_vec[idx.entry_id(e)];
         CHECK(t.defined());
-        inputs.push_back(t);
+        op_inputs.push_back(t);
       }
       // output hint
       for (uint32_t i = 0; i < inode.source->num_outputs(); ++i) {
@@ -198,7 +199,7 @@ class CompileEngine {
       }
       // get default
       Array<Tensor> out = fcompute[inode.source->op()](
-          inode.source->attrs, inputs, out_info);
+          inode.source->attrs, op_inputs, out_info);
       CHECK_EQ(out.size(), inode.source->num_outputs());
       // schedule on root node, and use master's schedule
       for (uint32_t index = 0; index < inode.source->num_outputs(); ++index) {
@@ -207,19 +208,43 @@ class CompileEngine {
       }
     }
     // Schedule on final output.
-    Array<Tensor> outputs;
     Array<Tensor> all_args = inputs;
+    Array<Tensor> outs;
     for (const IndexedGraph::NodeEntry& e : idx.outputs()) {
       const tvm::Tensor& t = tensor_vec[idx.entry_id(e)];
       CHECK(t.defined());
-      outputs.push_back(t);
+      outs.push_back(t);
       all_args.push_back(t);
     }
-    Schedule sch = fschedule[schedule_op_key](
-        schedule_op_attr, outputs, target);
+
+    Schedule sch = fschedule[idx[master_idx].source->op()](
+        idx[master_idx].source->attrs, outs, target);
+
+    // store extra return values
+    if (readable_name != nullptr)
+      *readable_name = readable_name_os.str();
+    if (outputs != nullptr)
+      *outputs = outs;
+
+    return std::make_pair(sch, all_args);
+  }
+
+  // run the actual lowering process
+  GraphFunc DoLower(Graph graph,
+                    const Array<tvm::Tensor>& inputs,
+                    const std::string& target,
+                    int master_idx) {
+    std::string readable_name;
+    Array<tvm::Tensor> all_args;
+    Array<tvm::Tensor> outputs;
+    Schedule sch;
+
+    std::tie(sch, all_args) = GetScheduleArgs(graph, inputs, target, master_idx,
+                                              &readable_name, &outputs);
+
     std::shared_ptr<GraphFuncNode> gf = std::make_shared<GraphFuncNode>();
     gf->target = target;
-    gf->func_name = GetUniqeName(readable_name.str());
+    gf->func_name = GetUniqeName(readable_name);
     gf->inputs = inputs;
     gf->outputs = outputs;
     static const PackedFunc& flower = GetPackedFunc("nnvm.compiler.lower");
@@ -257,10 +282,9 @@ class CompileEngine {
 GraphFunc GraphLower(Graph graph,
                      const Array<tvm::Tensor>& inputs,
                      const std::string& target,
-                     const Op* schedule_op_key,
-                     const NodeAttrs& schedule_op_attr) {
+                     int master_idx) {
   return CompileEngine::Global()->Lower(
-      graph, inputs, target, schedule_op_key, schedule_op_attr);
+      graph, inputs, target, master_idx);
 }
 
 // Expose cache to front end
@@ -295,6 +319,30 @@ TVM_REGISTER_GLOBAL("nnvm.compiler.MakeGraphKey")
     *rv = GraphKeyNode::make(args[0], args[1], args[2]);
   });
 
+// This can be used to extract workloads from nnvm compiler
+TVM_REGISTER_GLOBAL("nnvm.compiler.CacheItem2ScheduleArgs")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+    Array<tvm::NodeRef> item = args[0];
+
+    const GraphKeyNode *key = reinterpret_cast<const GraphKeyNode *>(item[0].get());
+    const GraphCacheEntryNode *value = reinterpret_cast<const GraphCacheEntryNode *>(item[1].get());
+
+    // extract arguments from cached item
+    Graph graph = key->graph;
+    const Array<tvm::Tensor> &inputs = key->inputs;
+    std::string target = args[1];
+    int master_idx = value->master_idx;
+
+    Schedule sch;
+    Array<tvm::Tensor> all_args;
+    std::tie(sch, all_args) = CompileEngine::Global()->GetScheduleArgs(
+        graph, inputs, target, master_idx, nullptr, nullptr);
+
+    Array<tvm::NodeRef> ret;
+    ret.push_back(sch);
+    ret.push_back(all_args);
+    *rv = ret;
+  });
 
 TVM_STATIC_IR_FUNCTOR(IRPrinter, vtable)
 .set_dispatch<GraphFuncNode>([](const GraphFuncNode *op, IRPrinter *p) {
diff --git a/nnvm/src/compiler/compile_engine.h b/nnvm/src/compiler/compile_engine.h
index 109b8d8df03b..d84fe2facbd3 100644
--- a/nnvm/src/compiler/compile_engine.h
+++ b/nnvm/src/compiler/compile_engine.h
@@ -17,6 +17,7 @@
 #include <tvm/operation.h>
 #include <tvm/lowered_func.h>
 #include <string>
+#include <utility>
 #include "./graph_hash.h"
 
 namespace nnvm {
@@ -55,10 +56,13 @@ struct GraphCacheEntryNode : public tvm::Node {
   GraphFunc graph_func;
   /*! \brief Usage statistics */
   int use_count{0};
+  /*! \brief Index of the master node for calling schedule*/
+  int master_idx;
 
   void VisitAttrs(tvm::AttrVisitor* v) final {
     v->Visit("graph_func", &graph_func);
     v->Visit("use_count", &use_count);
+    v->Visit("master_idx", &master_idx);
   }
   static constexpr const char* _type_key = "GraphCacheEntry";
   TVM_DECLARE_NODE_TYPE_INFO(GraphCacheEntryNode, tvm::Node);
@@ -79,16 +83,15 @@ class GraphCacheEntry : public ::tvm::NodeRef {
  *
  * \param graph The graph to be compiled
  * \param inputs The input specification.
- * \param schedule_op_key The hint key for the schedule.
- * \param schedule_op_attr  The hint attribute for the schedule.
+ * \param target The build target
+ * \param master_idx The index of master node for calling schedule
  *
  * \return func A lowered tvm function.
  */
 GraphFunc GraphLower(Graph graph,
                      const Array<tvm::Tensor>& inputs,
                      const std::string& target,
-                     const Op* schedule_op_key,
-                     const NodeAttrs& schedule_op_attr);
+                     int master_idx);
 
 /*!
  * \brief Get type flag from TVM Type
diff --git a/nnvm/src/compiler/graph_fuse.cc b/nnvm/src/compiler/graph_fuse.cc
index d47295838f18..8e0578acf9c3 100644
--- a/nnvm/src/compiler/graph_fuse.cc
+++ b/nnvm/src/compiler/graph_fuse.cc
@@ -315,9 +315,15 @@ nnvm::Graph GraphFuseCompile(nnvm::Graph g) {
       auto it = fe.input_info.find(subidx[sub_input_id].source);
       inputs.push_back(it->second);
     }
-    fe.compiled_func = GraphLower(fe.subgraph, inputs, target,
-                                  idx[master].source->op(),
-                                  idx[master].source->attrs);
+    // find master idx in subgraph
+    int sub_master_idx = 0;
+    for (uint32_t i = 0; i < subidx.num_nodes(); i++) {
+      if (subidx[i].source->op() == idx[master].source->op()) {
+        sub_master_idx = i;
+        break;
+      }
+    }
+    fe.compiled_func = GraphLower(fe.subgraph, inputs, target, sub_master_idx);
     for (LoweredFunc f : fe.compiled_func->funcs) {
       if (!func_set.count(f.get())) {
         func_set.insert(f.get());
diff --git a/nnvm/src/compiler/packed_func_ext.cc b/nnvm/src/compiler/packed_func_ext.cc
index 0cb19c576980..4483f5303999 100644
--- a/nnvm/src/compiler/packed_func_ext.cc
+++ b/nnvm/src/compiler/packed_func_ext.cc
@@ -9,6 +9,7 @@
 #include <nnvm/compiler/packed_func_ext.h>
 #include <nnvm/compiler/op_attr_types.h>
 #include "./node_attr.h"
+#include "compile_engine.h"
 
 namespace tvm {
 namespace runtime {

From 271a289a82bfc85c8a6de3fe9f86cfcb54350e22 Mon Sep 17 00:00:00 2001
From: Yida Wang <yidawa@gmail.com>
Date: Fri, 26 Jan 2018 18:51:33 -0800
Subject: [PATCH 598/949] explain the lowering process in nnvm.compiler.build
 (#339)

---
 nnvm/python/nnvm/compiler/build_module.py  |  4 ++--
 nnvm/tutorials/define_and_compile_model.py | 11 +++++++----
 2 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/nnvm/python/nnvm/compiler/build_module.py b/nnvm/python/nnvm/compiler/build_module.py
index afd25a2b45a3..68e8d42449a1 100644
--- a/nnvm/python/nnvm/compiler/build_module.py
+++ b/nnvm/python/nnvm/compiler/build_module.py
@@ -207,7 +207,7 @@ def build(graph, target=None, shape=None, dtype="float32", params=None, target_h
         The final execution graph.
 
     libmod : tvm.Module
-        The modue that comes with the execution graph
+        The module that comes with the execution graph
 
     params : dict of str to NDArray
         The updated parameters of graph if params is passed.
@@ -236,7 +236,7 @@ def build(graph, target=None, shape=None, dtype="float32", params=None, target_h
     if params and cfg.pass_enabled("PrecomputePrune"):
         graph, params = precompute_prune(graph, params)
         shape, dtype = _update_shape_dtype(shape, dtype, params)
-    # Operator Fusion and generatiom
+    # Operator Fusion and generation
     graph = graph_attr.set_shape_inputs(graph, shape)
     graph = graph_attr.set_dtype_inputs(graph, dtype)
     graph._set_json_attr("target", str(target), "str")
diff --git a/nnvm/tutorials/define_and_compile_model.py b/nnvm/tutorials/define_and_compile_model.py
index 35fe7b84c27e..45cefcd1c81d 100644
--- a/nnvm/tutorials/define_and_compile_model.py
+++ b/nnvm/tutorials/define_and_compile_model.py
@@ -67,10 +67,13 @@
 # optimization while TVM does the tensor-level optimization, resulting
 # in an optimized runtime module for model serving.
 #
-# We'll first compile for Nvidia GPU.
-# To generate the module library, TVM will first transfer graph IR into lower
-# intrinsic IR for the specified target backend, which is CUDA in this example.
-# Then target backend will generate module library.
+# We'll first compile for Nvidia GPU. Behind the scene, `nnvm.compiler.build`
+# first does a number of graph-level optimizations, e.g. pruning, fusing, etc.,
+# then registers the operators (i.e. the nodes of the optmized graphs) to 
+# TVM implementations to generate a `tvm.module`.
+# To generate the module library, TVM will first transfer the HLO IR into the lower
+# intrinsic IR of the specified target backend, which is CUDA in this example.
+# Then the machine code will be generated as the module library.
 
 opt_level = 0
 target = tvm.target.cuda()

From 824a044301c02732c1a35f52f92986f639ffe9d0 Mon Sep 17 00:00:00 2001
From: Siva <sivar.b@huawei.com>
Date: Tue, 30 Jan 2018 12:51:36 +0530
Subject: [PATCH 599/949] [Convolution] Error while importing onnx model with
 weights. (#345)

* [Convolution] Error while imported onnx model has weights.

Fix the use_bias based on the input parameters to use bias (Ex: sqeezenet). Ref #336

* Review corrections.
---
 nnvm/python/nnvm/frontend/onnx.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/onnx.py b/nnvm/python/nnvm/frontend/onnx.py
index 09f29affedf3..b98d323ebae1 100644
--- a/nnvm/python/nnvm/frontend/onnx.py
+++ b/nnvm/python/nnvm/frontend/onnx.py
@@ -95,7 +95,7 @@ def _impl(inputs, attr, params):
                 'dilations': ('dilation', (0, 0)),
                 'pads': ('padding', (0, 0), _revert_caffe2_pad),
                 'group': ('groups', 1)},
-            extras={'use_bias': False},
+            extras={'use_bias': len(inputs) == 3},
             custom_check=_dimension_constraint())(inputs, attr)
     return _impl
 
@@ -113,7 +113,7 @@ def _impl(inputs, attr, params):
                 'dilations': ('dilation', (0, 0)),
                 'pads': ('padding', (0, 0), _revert_caffe2_pad)},
             disables=['output_shape'],
-            extras={'use_bias': False},
+            extras={'use_bias': len(inputs) == 3},
             custom_check=_dimension_constraint())(inputs, attr)
     return _impl
 

From e6178ea6dad93a7ad3d43baee7a8de5f1d98884f Mon Sep 17 00:00:00 2001
From: ziheng <ziheng@apache.org>
Date: Tue, 30 Jan 2018 15:29:04 +0800
Subject: [PATCH 600/949] [FIX] Fix target in tutorial (#347)

---
 nnvm/tutorials/deploy_model_on_rasp.py | 11 ++++-------
 1 file changed, 4 insertions(+), 7 deletions(-)

diff --git a/nnvm/tutorials/deploy_model_on_rasp.py b/nnvm/tutorials/deploy_model_on_rasp.py
index 033a8b885799..885d928c9393 100644
--- a/nnvm/tutorials/deploy_model_on_rasp.py
+++ b/nnvm/tutorials/deploy_model_on_rasp.py
@@ -177,15 +177,12 @@ def transform_image(image):
 # Pi, which has been proved as a good compilation configuration.
 
 if use_rasp:
-    target = "llvm -target=armv7l-none-linux-gnueabihf -mcpu=cortex-a53 -mattr=+neon"
+    target = tvm.target.rasp()
 else:
-    target = "llvm"
+    target = tvm.target.create('llvm')
 
-
-# use `with tvm.target.rasp` for some target-specified optimization
-with tvm.target.rasp():
-    graph, lib, params = nnvm.compiler.build(
-        net, target, shape={"data": data_shape}, params=params)
+graph, lib, params = nnvm.compiler.build(
+    net, target, shape={"data": data_shape}, params=params)
 
 # After `nnvm.compiler.build`, you will get three return values: graph,
 # library and the new parameter, since we do some optimization that will

From bb5dea79e575a96bb22e764ad0f8402c014ea390 Mon Sep 17 00:00:00 2001
From: Liangfu Chen <chenclf@gmail.com>
Date: Wed, 31 Jan 2018 00:56:50 +0800
Subject: [PATCH 601/949] improved empty set definition (#346)

fix similar problem like https://github.com/apache/incubator-mxnet/pull/4589/files .
---
 nnvm/src/top/op_common.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/src/top/op_common.h b/nnvm/src/top/op_common.h
index ee6b7ced3b4e..ae4388ade1c0 100644
--- a/nnvm/src/top/op_common.h
+++ b/nnvm/src/top/op_common.h
@@ -293,7 +293,7 @@ inline std::vector<NodeEntry> MakeGradNode(
   const char* op_name,
   const NodePtr& n,
   std::vector<NodeEntry> inputs,
-  std::unordered_map<std::string, std::string> attr = {}) {
+  std::unordered_map<std::string, std::string> attr = {{}}) {
   NodePtr p = Node::Create();
   p->attrs.op = nnvm::Op::Get(op_name);
   p->attrs.name = n->attrs.name + "_grad";

From 1da51b76724de960e7e6421d1fbd855eed322079 Mon Sep 17 00:00:00 2001
From: wl3b10s <wl3b10s@live.cn>
Date: Fri, 2 Feb 2018 01:05:46 +0800
Subject: [PATCH 602/949] image tranform runtime wrong on python (#354)

x = np.transpose(img, (2, 0, 1))[np.newaxis, :]
TypeError: an integer is required
---
 nnvm/tutorials/from_coreml.py | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/nnvm/tutorials/from_coreml.py b/nnvm/tutorials/from_coreml.py
index 7e5cf7ae1fc4..a8f551265cd5 100644
--- a/nnvm/tutorials/from_coreml.py
+++ b/nnvm/tutorials/from_coreml.py
@@ -54,8 +54,10 @@ def download(url, path, overwrite=False):
 img_url = 'https://github.com/dmlc/mxnet.js/blob/master/data/cat.png?raw=true'
 download(img_url, 'cat.png')
 img = Image.open('cat.png').resize((224, 224))
-x = np.transpose(img, (2, 0, 1))[np.newaxis, :]
-
+#x = np.transpose(img, (2, 0, 1))[np.newaxis, :]
+image = np.asarray(img)
+image = image.transpose((2, 0, 1))
+x = image[np.newaxis, :]
 ######################################################################
 # Compile the model on NNVM
 # ---------------------------

From 21ac7edf796e75f3fedd1913a984596c7a76f9d1 Mon Sep 17 00:00:00 2001
From: Siju Samuel <sijusamuel@gmail.com>
Date: Thu, 1 Feb 2018 22:36:14 +0530
Subject: [PATCH 603/949] [PYLINT FIX]pylint issue fixes (#348)

Before Fix
*************
************* Module nnvm._base
R: 21, 0: Disallow trailing comma tuple (trailing-comma-tuple)
R: 27, 0: Disallow trailing comma tuple (trailing-comma-tuple)

-----------------------------------
Your code has been rated at 9.99/

After Fix
*************
siju@siju-DAEMON:~/D/Community/nnvm$ make lint
pylint python/nnvm --rcfile=/media/siju/DATA/Community/nnvm/tests/lint/pylintrc

-------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 9.99/10, +0.01)
---
 nnvm/python/nnvm/_base.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/nnvm/python/nnvm/_base.py b/nnvm/python/nnvm/_base.py
index 1c25f4cfe551..d01bdcaeb5b1 100644
--- a/nnvm/python/nnvm/_base.py
+++ b/nnvm/python/nnvm/_base.py
@@ -18,13 +18,13 @@
 # library loading
 #----------------------------
 if sys.version_info[0] == 3:
-    string_types = str,
+    string_types = str
     numeric_types = (float, int, np.float32, np.int32)
     # this function is needed for python3
     # to convert ctypes.char_p .value back to python str
     py_str = lambda x: x.decode('utf-8')
 else:
-    string_types = basestring,
+    string_types = basestring
     numeric_types = (float, int, long, np.float32, np.int32)
     py_str = lambda x: x
 

From cb96339b581bfedd679d8958af5686d5febec614 Mon Sep 17 00:00:00 2001
From: Siju Samuel <sijusamuel@gmail.com>
Date: Sat, 3 Feb 2018 08:09:41 +0530
Subject: [PATCH 604/949] [Compilation Warning Fix] in matrix_op.cc (#356)

The below compilation warning issue has been fixed.

src/top/tensor/matrix_op.cc:37:21: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
   for (int i = 0; i < lshape.ndim() - 1; i++) oshape[i] = lshape[i];
                     ^
src/top/tensor/matrix_op.cc:38:21: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
   for (int i = 1; i < rshape.ndim(); i++) oshape[i + lshape.ndim() - 1] = rshap
---
 nnvm/src/top/tensor/matrix_op.cc | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/nnvm/src/top/tensor/matrix_op.cc b/nnvm/src/top/tensor/matrix_op.cc
index 93dc57016f55..5c163280186f 100644
--- a/nnvm/src/top/tensor/matrix_op.cc
+++ b/nnvm/src/top/tensor/matrix_op.cc
@@ -34,8 +34,8 @@ inline bool DotShape(const nnvm::NodeAttrs& attrs,
     << "dot shape inconsistent: " << lshape << " X " << rshape;
 
   TShape oshape(lshape.ndim() + rshape.ndim() - 1);
-  for (int i = 0; i < lshape.ndim() - 1; i++) oshape[i] = lshape[i];
-  for (int i = 1; i < rshape.ndim(); i++) oshape[i + lshape.ndim() - 1] = rshape[i];
+  for (size_t i = 0; i < lshape.ndim() - 1; i++) oshape[i] = lshape[i];
+  for (size_t i = 1; i < rshape.ndim(); i++) oshape[i + lshape.ndim() - 1] = rshape[i];
 
   NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, 0, oshape);
   return true;

From bdce3ab3e52e28a028568a818f1181e72091ec25 Mon Sep 17 00:00:00 2001
From: alex-weaver <awsweaver@gmail.com>
Date: Tue, 6 Feb 2018 17:39:59 +0000
Subject: [PATCH 605/949] Change TOPI ops to use C++ implementation where
 applicable (#357)

* Updated TVM version. Implemented fix for nnvm_compiler crash on exit on windows. Changed TOPI ops from using python to using C++ where applicable.

* Fix lint

* Fix lint

* Fix macro

* Fix reshape

* Update TVM to fix test fails
---
 nnvm/CMakeLists.txt                 |   1 +
 nnvm/Makefile                       |   2 +-
 nnvm/include/nnvm/compiler/util.h   |  33 +++++++
 nnvm/include/nnvm/symbolic.h        |   2 +-
 nnvm/python/nnvm/top/nn.py          |  85 -----------------
 nnvm/python/nnvm/top/reduction.py   |   3 -
 nnvm/python/nnvm/top/tensor.py      |  35 -------
 nnvm/python/nnvm/top/transform.py   |  43 +--------
 nnvm/src/compiler/compile_engine.cc |   2 +-
 nnvm/src/compiler/graph_hash.cc     |   2 +-
 nnvm/src/top/nn/nn.cc               |  73 +++++++++++++++
 nnvm/src/top/nn/pooling.cc          |  53 +++++++++++
 nnvm/src/top/tensor/broadcast.cc    |  22 +++++
 nnvm/src/top/tensor/elemwise.cc     | 138 ++++++++++++++++++++++++++++
 nnvm/src/top/tensor/reduce.cc       |  32 +++++++
 nnvm/src/top/tensor/transform.cc    |  64 +++++++++++++
 16 files changed, 421 insertions(+), 169 deletions(-)
 create mode 100644 nnvm/include/nnvm/compiler/util.h

diff --git a/nnvm/CMakeLists.txt b/nnvm/CMakeLists.txt
index a37bc5f1fbd6..3747be06aa56 100644
--- a/nnvm/CMakeLists.txt
+++ b/nnvm/CMakeLists.txt
@@ -22,6 +22,7 @@ include_directories(BEFORE "include")
 include_directories("tvm/include")
 include_directories("tvm/dlpack/include")
 include_directories("tvm/HalideIR/src")
+include_directories("tvm/topi/include")
 
 set(NNVM_LINKER_LIBS "")
 set(NNVM_COMPILER_LINKER_LIBS "")
diff --git a/nnvm/Makefile b/nnvm/Makefile
index 3f86eac40bf2..4779e95b317a 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -11,7 +11,7 @@ include $(config)
 
 export LDFLAGS = -pthread -lm
 export CFLAGS = -std=c++11 -Wall -O2 -Iinclude -fPIC
-CFLAGS += -Itvm/include -Itvm/dlpack/include -Itvm/HalideIR/src
+CFLAGS += -Itvm/include -Itvm/dlpack/include -Itvm/HalideIR/src -Itvm/topi/include
 
 ifdef DMLC_CORE_PATH
   CFLAGS += -I$(DMLC_CORE_PATH)/include
diff --git a/nnvm/include/nnvm/compiler/util.h b/nnvm/include/nnvm/compiler/util.h
new file mode 100644
index 000000000000..04095c7838af
--- /dev/null
+++ b/nnvm/include/nnvm/compiler/util.h
@@ -0,0 +1,33 @@
+/*!
+*  Copyright (c) 2016 by Contributors
+* \file util.h
+* \brief Utility functions for nnvm compiler
+*/
+#ifndef NNVM_COMPILER_UTIL_H_
+#define NNVM_COMPILER_UTIL_H_
+
+#include <tvm/expr.h>
+#include <nnvm/tuple.h>
+
+namespace nnvm {
+namespace compiler {
+
+/*
+ * \brief Helper function to convert TShape to TVM array. Useful for
+ * passing data from NNVM param structures to TOPI ops.
+ *
+ * \param shape The shape to convert
+ *
+ * \return An Array of Expr, where each element is a constant int32
+ */
+inline tvm::Array<tvm::Expr> ShapeToArray(TShape shape) {
+  tvm::Array<tvm::Expr> result;
+  for (auto i : shape) {
+    result.push_back(tvm::make_const(tvm::Int(32), i));
+  }
+  return result;
+}
+
+}  // namespace compiler
+}  // namespace nnvm
+#endif  // NNVM_COMPILER_UTIL_H_
diff --git a/nnvm/include/nnvm/symbolic.h b/nnvm/include/nnvm/symbolic.h
index 02b1dd9475f2..f290a1683792 100644
--- a/nnvm/include/nnvm/symbolic.h
+++ b/nnvm/include/nnvm/symbolic.h
@@ -28,7 +28,7 @@ namespace nnvm {
  *  symbol is the final operation of a graph and thus including all the information
  *  required (the graph) to evaluate its output value.
  */
-class Symbol {
+class NNVM_DLL Symbol {
  public:
   /*! \brief option passed to ListAttr */
   enum ListAttrOption {
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index 88aafec625ea..f91f77dfdc33 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -10,59 +10,26 @@
 from .registry import OpPattern
 
 # relu
-@reg.register_compute("relu")
-def compute_relu(attrs, inputs, _):
-    """Compute definition of relu"""
-    return topi.nn.relu(inputs[0])
-
 reg.register_schedule("relu", _fschedule_broadcast)
 reg.register_pattern("relu", OpPattern.ELEMWISE)
 
 
 # leaky_relu
-@reg.register_compute("leaky_relu")
-def compute_leaky_relu(attrs, inputs, _):
-    """Compute definition of relu"""
-    return topi.nn.leaky_relu(inputs[0], attrs.get_float("alpha"))
-
 reg.register_schedule("leaky_relu", _fschedule_broadcast)
 reg.register_pattern("leaky_relu", OpPattern.ELEMWISE)
 
 
 # flatten
-@reg.register_compute("flatten")
-def compute_flatten(attrs, inputs, _):
-    """Compute definition of flatten"""
-    return topi.nn.flatten(inputs[0])
-
 reg.register_schedule("flatten", _fschedule_broadcast)
 reg.register_pattern("flatten", OpPattern.INJECTIVE)
 
 
 # pad
-@reg.register_compute("pad")
-def compute_pad(attrs, inputs, _):
-    """Compute definition of pad"""
-    pad_width = attrs.get_int_pair_tuple('pad_width')
-    assert len(pad_width) == len(inputs[0].shape) and \
-        len(pad_width[0]) == 2, "illegal pad_width"
-    pad_before = [x[0] for x in pad_width]
-    pad_after = [x[1] for x in pad_width]
-    pad_value = attrs.get_int('pad_value')
-    return topi.nn.pad(inputs[0], pad_before, pad_after, pad_value)
-
 reg.register_schedule("pad", _fschedule_broadcast)
 reg.register_pattern("pad", OpPattern.INJECTIVE)
 
 
 # softmax
-@reg.register_compute("softmax")
-def compute_softmax(attrs, inputs, _):
-    """Compute definition of softmax"""
-    axis = attrs.get_int("axis")
-    assert axis == -1, "only support axis == -1 for now"
-    return topi.nn.softmax(inputs[0])
-
 @reg.register_schedule("softmax")
 def schedule_softmax(_, outs, target):
     """Schedule definition of softmax"""
@@ -73,13 +40,6 @@ def schedule_softmax(_, outs, target):
 
 
 # log softmax
-@reg.register_compute("log_softmax")
-def compute_log_softmax(attrs, inputs, _):
-    """Compute definition of softmax"""
-    axis = attrs.get_int("axis")
-    assert axis == -1, "only support axis == -1 for now"
-    return topi.nn.log_softmax(inputs[0])
-
 @reg.register_schedule("log_softmax")
 def schedule_log_softmax(_, outs, target):
     """Schedule definition of softmax"""
@@ -91,13 +51,6 @@ def schedule_log_softmax(_, outs, target):
 
 
 # dense
-@reg.register_compute("dense")
-def compute_dense(attrs, inputs, _):
-    """Compute definition of dense"""
-    if attrs.get_bool("use_bias"):
-        return topi.nn.dense(inputs[0], inputs[1], bias=inputs[2])
-    return topi.nn.dense(inputs[0], inputs[1])
-
 @reg.register_schedule("dense")
 def schedule_dense(_, outs, target):
     """Schedule definition of dense"""
@@ -175,18 +128,6 @@ def schedule_conv2d_transpose(attrs, outs, target):
 
 
 # max_pool2d
-@reg.register_compute("max_pool2d")
-def compute_max_pool2d(attrs, inputs, _):
-    """Compute definition of max_pool2d"""
-    pool_size = attrs.get_int_tuple("pool_size")
-    strides = attrs.get_int_tuple("strides")
-    padding = attrs.get_int_tuple("padding")
-    layout = attrs["layout"]
-    ceil_mode = attrs.get_bool("ceil_mode")
-    assert layout == "NCHW", "only support nchw for now"
-    return topi.nn.pool(inputs[0], pool_size, strides, padding,
-                        pool_type='max', ceil_mode=ceil_mode)
-
 @reg.register_schedule("max_pool2d")
 def schedule_max_pool2d(_, outs, target):
     """Schedule definition of max_pool2d"""
@@ -197,18 +138,6 @@ def schedule_max_pool2d(_, outs, target):
 
 
 # avg_pool2d
-@reg.register_compute("avg_pool2d")
-def compute_avg_pool2d(attrs, inputs, _):
-    """Compute definition of avg_pool2d"""
-    pool_size = attrs.get_int_tuple("pool_size")
-    strides = attrs.get_int_tuple("strides")
-    padding = attrs.get_int_tuple("padding")
-    layout = attrs["layout"]
-    ceil_mode = attrs.get_bool("ceil_mode")
-    assert layout == "NCHW", "only support nchw for now"
-    return topi.nn.pool(inputs[0], pool_size, strides, padding,
-                        pool_type='avg', ceil_mode=ceil_mode)
-
 @reg.register_schedule("avg_pool2d")
 def schedule_avg_pool2d(_, outs, target):
     """Schedule definition of avg_pool2d"""
@@ -219,13 +148,6 @@ def schedule_avg_pool2d(_, outs, target):
 
 
 # global_max_pool2d
-@reg.register_compute("global_max_pool2d")
-def compute_global_max_pool2d(attrs, inputs, _):
-    """Compute definition of global_max_pool2d"""
-    layout = attrs["layout"]
-    assert layout == "NCHW", "only support nchw for now"
-    return topi.nn.global_pool(inputs[0], pool_type='max')
-
 @reg.register_schedule("global_max_pool2d")
 def schedule_global_max_pool2d(_, outs, target):
     """Schedule definition of global_max_pool2d"""
@@ -236,13 +158,6 @@ def schedule_global_max_pool2d(_, outs, target):
 
 
 # global_avg_pool2d
-@reg.register_compute("global_avg_pool2d")
-def compute_global_avg_pool2d(attrs, inputs, _):
-    """Compute definition of global_avg_pool2d"""
-    layout = attrs["layout"]
-    assert layout == "NCHW", "only support nchw for now"
-    return topi.nn.global_pool(inputs[0], pool_type='avg')
-
 @reg.register_schedule("global_avg_pool2d")
 def schedule_global_avg_pool2d(_, outs, target):
     """Schedule definition of global_avg_pool2d"""
diff --git a/nnvm/python/nnvm/top/reduction.py b/nnvm/python/nnvm/top/reduction.py
index 7003d3a52df3..193ea6038e37 100644
--- a/nnvm/python/nnvm/top/reduction.py
+++ b/nnvm/python/nnvm/top/reduction.py
@@ -27,16 +27,13 @@ def _compute(attrs, inputs, out_info):
     return _compute
 
 # sum
-reg.register_compute("sum", _compute_reduce(topi.sum))
 reg.register_pattern("sum", OpPattern.COMM_REDUCE)
 reg.register_schedule("sum", _fschedule_reduce)
 
 # max
-reg.register_compute("max", _compute_reduce(topi.max))
 reg.register_pattern("max", OpPattern.COMM_REDUCE)
 reg.register_schedule("max", _fschedule_reduce)
 
 # min
-reg.register_compute("min", _compute_reduce(topi.min))
 reg.register_pattern("min", OpPattern.COMM_REDUCE)
 reg.register_schedule("min", _fschedule_reduce)
diff --git a/nnvm/python/nnvm/top/tensor.py b/nnvm/python/nnvm/top/tensor.py
index 7ca800e5b7dc..565733775209 100644
--- a/nnvm/python/nnvm/top/tensor.py
+++ b/nnvm/python/nnvm/top/tensor.py
@@ -43,132 +43,97 @@ def _compute(attrs, x, _):
 _fschedule_elemwise = _fschedule_injective
 
 # copy
-reg.register_compute("copy", _compute_unary(topi.identity))
 reg.register_pattern("copy", OpPattern.ELEMWISE)
 reg.register_schedule("copy", _fschedule_broadcast)
 
 # exp
-reg.register_compute("exp", _compute_unary(topi.exp))
 reg.register_pattern("exp", OpPattern.ELEMWISE)
 reg.register_schedule("exp", _fschedule_broadcast)
 
 # sqrt
-reg.register_compute("sqrt", _compute_unary(topi.sqrt))
 reg.register_pattern("sqrt", OpPattern.ELEMWISE)
 reg.register_schedule("sqrt", _fschedule_broadcast)
 
 # log
-reg.register_compute("log", _compute_unary(topi.log))
 reg.register_pattern("log", OpPattern.ELEMWISE)
 reg.register_schedule("log", _fschedule_broadcast)
 
 # tanh
-reg.register_compute("tanh", _compute_unary(topi.tanh))
 reg.register_pattern("tanh", OpPattern.ELEMWISE)
 reg.register_schedule("tanh", _fschedule_broadcast)
 
 # negative
-reg.register_compute("negative", _compute_unary(topi.negative))
 reg.register_pattern("negative", OpPattern.ELEMWISE)
 reg.register_schedule("negative", _fschedule_broadcast)
 
 # sigmoid
-reg.register_compute("sigmoid", _compute_unary(topi.sigmoid))
 reg.register_pattern("sigmoid", OpPattern.ELEMWISE)
 reg.register_schedule("sigmoid", _fschedule_broadcast)
 
 # add_scalar
-reg.register_compute("__add_scalar__",
-                     _compute_binary_scalar(lambda x, y: x + y))
 reg.register_pattern("__add_scalar__", OpPattern.ELEMWISE)
 reg.register_schedule("__add_scalar__", _fschedule_broadcast)
 
 # sub_calar
-reg.register_compute("__sub_scalar__",
-                     _compute_binary_scalar(lambda x, y: x - y))
 reg.register_pattern("__sub_scalar__", OpPattern.ELEMWISE)
 reg.register_schedule("__sub_scalar__", _fschedule_broadcast)
 
 # rsub_scalar
-reg.register_compute("__rsub_scalar__",
-                     _compute_binary_scalar(lambda x, y: y - x))
 reg.register_pattern("__rsub_scalar__", OpPattern.ELEMWISE)
 reg.register_schedule("__rsub_scalar__", _fschedule_broadcast)
 
 # mul_scalar
-reg.register_compute("__mul_scalar__",
-                     _compute_binary_scalar(lambda x, y: x * y))
 reg.register_pattern("__mul_scalar__", OpPattern.ELEMWISE)
 reg.register_schedule("__mul_scalar__", _fschedule_broadcast)
 
 # div_scalar
-reg.register_compute("__div_scalar__",
-                     _compute_binary_scalar(lambda x, y: x / y))
 reg.register_pattern("__div_scalar__", OpPattern.ELEMWISE)
 reg.register_schedule("__div_scalar__", _fschedule_broadcast)
 
 # rdiv_scalar
-reg.register_compute("__rdiv_scalar__",
-                     _compute_binary_scalar(lambda x, y: y / x))
 reg.register_pattern("__rdiv_scalar__", OpPattern.ELEMWISE)
 reg.register_schedule("__rdiv_scalar__", _fschedule_broadcast)
 
 # pow_scalar
-reg.register_compute("__pow_scalar__",
-                     _compute_binary_scalar(tvm.power))
 reg.register_pattern("__pow_scalar__", OpPattern.ELEMWISE)
 reg.register_schedule("__pow_scalar__", _fschedule_broadcast)
 
 # rpow_scalar
-reg.register_compute("__rpow_scalar__",
-                     _compute_binary_scalar(lambda x, y: tvm.power(y, x)))
 reg.register_pattern("__rpow_scalar__", OpPattern.ELEMWISE)
 reg.register_schedule("__rpow_scalar__", _fschedule_broadcast)
 
 # elemwise_add
-reg.register_compute("elemwise_add", _compute_binary(topi.broadcast_add))
 reg.register_pattern("elemwise_add", OpPattern.BROADCAST)
 reg.register_schedule("elemwise_add", _fschedule_broadcast)
 
 # elemwise_sub
-reg.register_compute("elemwise_sub", _compute_binary(topi.broadcast_sub))
 reg.register_pattern("elemwise_sub", OpPattern.BROADCAST)
 reg.register_schedule("elemwise_sub", _fschedule_broadcast)
 
 # elemwise_mul
-reg.register_compute("elemwise_mul", _compute_binary(topi.broadcast_mul))
 reg.register_pattern("elemwise_mul", OpPattern.BROADCAST)
 reg.register_schedule("elemwise_mul", _fschedule_broadcast)
 
 # elemwise_div
-reg.register_compute("elemwise_div", _compute_binary(topi.broadcast_div))
 reg.register_pattern("elemwise_div", OpPattern.BROADCAST)
 reg.register_schedule("elemwise_div", _fschedule_broadcast)
 
 # broadcast_add
-reg.register_compute("broadcast_add", _compute_binary(topi.broadcast_add))
 reg.register_pattern("broadcast_add", OpPattern.BROADCAST)
 reg.register_schedule("broadcast_add", _fschedule_broadcast)
 
 # broadcast_sub
-reg.register_compute("broadcast_sub", _compute_binary(topi.broadcast_sub))
 reg.register_pattern("broadcast_sub", OpPattern.BROADCAST)
 reg.register_schedule("broadcast_sub", _fschedule_broadcast)
 
 # broadcast_mul
-reg.register_compute("broadcast_mul", _compute_binary(topi.broadcast_mul))
 reg.register_pattern("broadcast_mul", OpPattern.BROADCAST)
 reg.register_schedule("broadcast_mul", _fschedule_broadcast)
 
 # broadcast_div
-reg.register_compute("broadcast_div", _compute_binary(topi.broadcast_div))
 reg.register_pattern("broadcast_div", OpPattern.BROADCAST)
 reg.register_schedule("broadcast_div", _fschedule_broadcast)
 
 # broadcast_to
-@reg.register_compute("broadcast_to")
-def compute_broadcast_to(attrs, inputs, out_info):
-    """Compute definition of softmax"""
-    return topi.broadcast_to(inputs[0], shape=out_info[0].shape)
 reg.register_pattern("broadcast_to", OpPattern.BROADCAST)
 reg.register_schedule("broadcast_to", _fschedule_broadcast)
diff --git a/nnvm/python/nnvm/top/transform.py b/nnvm/python/nnvm/top/transform.py
index 21c10f5cccf4..ec1596e9599b 100644
--- a/nnvm/python/nnvm/top/transform.py
+++ b/nnvm/python/nnvm/top/transform.py
@@ -2,71 +2,30 @@
 """Tensor transformation ops"""
 from __future__ import absolute_import
 
-import topi
 from .tensor import _fschedule_broadcast, _fschedule_injective
 from . import registry as reg
 from .registry import OpPattern
 
 # expand_dims
-@reg.register_compute("expand_dims")
-def compute_expand_dims(attrs, inputs, out_info):
-    """Compute definition of expand_dims"""
-    return topi.expand_dims(
-        inputs[0], attrs.get_int("axis"),
-        num_newaxis=attrs.get_int("num_newaxis"))
 reg.register_pattern("expand_dims", OpPattern.BROADCAST)
 reg.register_schedule("expand_dims", _fschedule_broadcast)
 
 # transpose
-@reg.register_compute("transpose")
-def compute_transpose(attrs, inputs, out_info):
-    """Compute definition of transpose"""
-    axes = attrs.get_int_tuple("axes")
-    axes = tuple(axes) if axes else None
-    return topi.transpose(inputs[0], axes)
 reg.register_pattern("transpose", OpPattern.INJECTIVE)
 reg.register_schedule("transpose", _fschedule_injective)
 
 # reshape
-@reg.register_compute("reshape")
-def compute_reshape(attrs, inputs, out_info):
-    """Compute definition of reshape"""
-    oshape = out_info[0].shape
-    return topi.reshape(inputs[0], oshape)
 reg.register_pattern("reshape", OpPattern.INJECTIVE)
 reg.register_schedule("reshape", _fschedule_injective)
 
-# reshape
-@reg.register_compute("squeeze")
-def compute_squeeze(attrs, inputs, out_info):
-    """Compute definition of reshape"""
-    axis = attrs.get_int_tuple("axis")
-    axis = tuple(axis) if axis else None
-    return topi.squeeze(inputs[0], axis)
+# squeeze
 reg.register_pattern("squeeze", OpPattern.INJECTIVE)
 reg.register_schedule("squeeze", _fschedule_injective)
 
 # concatenate
-@reg.register_compute("concatenate")
-def compute_concatenate(attrs, inputs, out_info):
-    """Compute definition of concatenate"""
-    axis = attrs.get_int("axis")
-    return topi.concatenate([x for x in inputs], axis=axis)
-
 reg.register_pattern("concatenate", OpPattern.INJECTIVE)
 reg.register_schedule("concatenate", _fschedule_injective)
 
 # split
-@reg.register_compute("split")
-def compute_split(attrs, inputs, out_info):
-    """Compute definition of split"""
-    x = attrs["indices_or_sections"]
-    if x.startswith("(") or x.startswith("["):
-        indices_or_sections = attrs.get_int_tuple("indices_or_sections")
-    else:
-        indices_or_sections = attrs.get_int("indices_or_sections")
-    return topi.split(inputs[0], indices_or_sections, axis=attrs.get_int("axis"))
-
-
 reg.register_pattern("split", OpPattern.INJECTIVE)
 reg.register_schedule("split", _fschedule_injective)
diff --git a/nnvm/src/compiler/compile_engine.cc b/nnvm/src/compiler/compile_engine.cc
index 5902abdc6afb..23c4d4c9b6bd 100644
--- a/nnvm/src/compiler/compile_engine.cc
+++ b/nnvm/src/compiler/compile_engine.cc
@@ -344,7 +344,7 @@ TVM_REGISTER_GLOBAL("nnvm.compiler.CacheItem2ScheduleArgs")
     *rv = ret;
   });
 
-TVM_STATIC_IR_FUNCTOR(IRPrinter, vtable)
+TVM_STATIC_IR_FUNCTOR_REGISTER(IRPrinter, vtable)
 .set_dispatch<GraphFuncNode>([](const GraphFuncNode *op, IRPrinter *p) {
     p->stream << "GraphFunc(name=" << op->func_name
               << ", addr=" << op << ")";
diff --git a/nnvm/src/compiler/graph_hash.cc b/nnvm/src/compiler/graph_hash.cc
index f493bc0429a6..d881130f72cc 100644
--- a/nnvm/src/compiler/graph_hash.cc
+++ b/nnvm/src/compiler/graph_hash.cc
@@ -80,7 +80,7 @@ GraphKey GraphKeyNode::make(Graph graph,
   return GraphKey(n);
 }
 
-TVM_STATIC_IR_FUNCTOR(IRPrinter, vtable)
+TVM_STATIC_IR_FUNCTOR_REGISTER(IRPrinter, vtable)
 .set_dispatch<GraphKeyNode>([](const GraphKeyNode *op, IRPrinter *p) {
     p->stream << "GraphKeyNode("<< op << ")";
 });
diff --git a/nnvm/src/top/nn/nn.cc b/nnvm/src/top/nn/nn.cc
index bf169acc78ec..88ed7fd58f83 100644
--- a/nnvm/src/top/nn/nn.cc
+++ b/nnvm/src/top/nn/nn.cc
@@ -3,17 +3,27 @@
  * \file nn.cc
  * \brief Property def of nn operators.
  */
+#include <tvm/expr.h>
+#include <tvm/packed_func_ext.h>
 #include <nnvm/op.h>
 #include <nnvm/node.h>
 #include <nnvm/op_attr_types.h>
+#include <nnvm/compiler/op_attr_types.h>
 #include <nnvm/top/nn.h>
 #include "./nn_common.h"
 #include "../op_common.h"
 #include "../elemwise_op_common.h"
+#include "topi/nn/dense.h"
+#include "topi/nn.h"
+#include "topi/nn/softmax.h"
 
 namespace nnvm {
 namespace top {
 
+using tvm::Tensor;
+using tvm::Array;
+using nnvm::compiler::FTVMCompute;
+
 // dense
 DMLC_REGISTER_PARAMETER(DenseParam);
 
@@ -72,6 +82,21 @@ If ``use_bias`` is set to be false, then the ``bias`` term is ignored.
 .set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<DenseParam>)
 .set_attr<FInferShape>("FInferShape", DenseInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    Tensor bias_val;
+    Tensor* bias;
+    const DenseParam& param = nnvm::get<DenseParam>(attrs.parsed);
+    if (param.use_bias) {
+      bias_val = inputs[2];
+      bias = &bias_val;
+    } else {
+      bias = nullptr;
+    }
+    return Array<Tensor>{ topi::nn::dense(inputs[0], inputs[1], bias) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
@@ -110,6 +135,12 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(relu)
    max(input, 0)
 
 )code" NNVM_ADD_FILELINE)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    return Array<Tensor>{ topi::relu(inputs[0], 0.0f) };
+  })
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
@@ -258,6 +289,14 @@ NNVM_REGISTER_OP(softmax)
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
 .set_support_level(1)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const SoftmaxParam& param = nnvm::get<SoftmaxParam>(attrs.parsed);
+    CHECK_EQ(param.axis, -1) << "Currently only axis=-1 is supported";
+    return Array<Tensor>{ topi::nn::softmax(inputs[0]) };
+  })
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
@@ -306,6 +345,14 @@ NNVM_REGISTER_OP(log_softmax)
 .set_num_outputs(1)
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const SoftmaxParam& param = nnvm::get<SoftmaxParam>(attrs.parsed);
+    CHECK_EQ(param.axis, -1) << "Currently only axis=-1 is supported";
+    return Array<Tensor>{ topi::nn::log_softmax(inputs[0]) };
+  })
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
@@ -357,6 +404,13 @@ NNVM_REGISTER_OP(leaky_relu)
 .set_num_outputs(1)
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const LeakyReLUParam& param = nnvm::get<LeakyReLUParam>(attrs.parsed);
+    return Array<Tensor>{ topi::leaky_relu<float>(inputs[0], 0.0, param.alpha) };
+  })
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
@@ -413,6 +467,25 @@ NNVM_REGISTER_OP(pad)
 .set_num_inputs(1)
 .set_attr<FInferShape>("FInferShape", PadInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const PadParam& param = nnvm::get<PadParam>(attrs.parsed);
+    auto pad_width = param.pad_width;
+    CHECK(pad_width.ndim() == inputs[0]->shape.size() &&
+      pad_width[0].ndim() == 2)
+      << "Illegal pad_width";
+    Array<tvm::Expr> pad_before;
+    for (size_t i = 0; i < pad_width.ndim(); ++i) {
+      pad_before.push_back(tvm::make_const(tvm::Int(32), pad_width[i][0]));
+    }
+    Array<tvm::Expr> pad_after;
+    for (size_t i = 0; i < pad_width.ndim(); ++i) {
+      pad_after.push_back(tvm::make_const(tvm::Int(32), pad_width[i][1]));
+    }
+    return Array<Tensor>{ topi::pad(inputs[0], pad_before, pad_after, param.pad_value) };
+})
 .set_support_level(1);
 
 }  // namespace top
diff --git a/nnvm/src/top/nn/pooling.cc b/nnvm/src/top/nn/pooling.cc
index 0f0eb6817929..ae8d872dec7c 100644
--- a/nnvm/src/top/nn/pooling.cc
+++ b/nnvm/src/top/nn/pooling.cc
@@ -6,13 +6,18 @@
 #include <nnvm/op.h>
 #include <nnvm/node.h>
 #include <nnvm/op_attr_types.h>
+#include <nnvm/compiler/op_attr_types.h>
+#include <nnvm/compiler/util.h>
 #include <nnvm/top/nn.h>
 #include "./nn_common.h"
 #include "../op_common.h"
 #include "../elemwise_op_common.h"
+#include "topi/nn/pooling.h"
 
 namespace nnvm {
 namespace top {
+using namespace tvm;
+using namespace nnvm::compiler;
 
 DMLC_REGISTER_PARAMETER(Pool2DParam);
 
@@ -77,6 +82,20 @@ NNVM_REGISTER_OP(max_pool2d)
 .set_num_inputs(1)
 .set_attr<FInferShape>("FInferShape", Pool2DInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const Pool2DParam& param = nnvm::get<Pool2DParam>(attrs.parsed);
+    auto pool_size = ShapeToArray(param.pool_size);
+    auto strides = ShapeToArray(param.strides);
+    auto padding = ShapeToArray(param.padding);
+    auto ceil_mode = param.ceil_mode;
+    CHECK_EQ(param.layout, kNCHW)
+      << "max_pool2d currently only supports NCHW layout";
+    return Array<Tensor>{
+      topi::nn::pool(inputs[0], pool_size, strides, padding, topi::nn::kMaxPool, ceil_mode) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
@@ -124,6 +143,20 @@ NNVM_REGISTER_OP(avg_pool2d)
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<Pool2DParam>)
 .set_attr<FInferShape>("FInferShape", Pool2DInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const Pool2DParam& param = nnvm::get<Pool2DParam>(attrs.parsed);
+    auto pool_size = ShapeToArray(param.pool_size);
+    auto strides = ShapeToArray(param.strides);
+    auto padding = ShapeToArray(param.padding);
+    auto ceil_mode = param.ceil_mode;
+    CHECK_EQ(param.layout, kNCHW)
+      << "avg_pool2d currently only supports NCHW layout";
+    return Array<Tensor>{
+      topi::nn::pool(inputs[0], pool_size, strides, padding, topi::nn::kAvgPool, ceil_mode) };
+})
 .set_num_outputs(1)
 .set_num_inputs(1)
 .set_support_level(2);
@@ -162,6 +195,16 @@ NNVM_REGISTER_OP(global_max_pool2d)
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<GlobalPool2DParam>)
 .set_attr<FInferShape>("FInferShape", GlobalPool2DInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const GlobalPool2DParam& param = nnvm::get<GlobalPool2DParam>(attrs.parsed);
+    CHECK_EQ(param.layout, kNCHW)
+      << "global_max_pool2d currently only supports NCHW layout";
+    return Array<Tensor>{
+      topi::nn::global_pool(inputs[0], topi::nn::kMaxPool) };
+})
 .set_num_outputs(1)
 .set_num_inputs(1)
 .set_support_level(2);
@@ -182,6 +225,16 @@ NNVM_REGISTER_OP(global_avg_pool2d)
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<GlobalPool2DParam>)
 .set_attr<FInferShape>("FInferShape", GlobalPool2DInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const GlobalPool2DParam& param = nnvm::get<GlobalPool2DParam>(attrs.parsed);
+    CHECK_EQ(param.layout, kNCHW)
+      << "global_avg_pool2d currently only supports NCHW layout";
+    return Array<Tensor>{
+      topi::nn::global_pool(inputs[0], topi::nn::kAvgPool) };
+})
 .set_num_outputs(1)
 .set_num_inputs(1)
 .set_support_level(2);
diff --git a/nnvm/src/top/tensor/broadcast.cc b/nnvm/src/top/tensor/broadcast.cc
index 945195fd0d32..773281450193 100644
--- a/nnvm/src/top/tensor/broadcast.cc
+++ b/nnvm/src/top/tensor/broadcast.cc
@@ -3,15 +3,22 @@
  * \file broadcast.cc
  * \brief broadcast operator.
  */
+#include <tvm/expr.h>
+#include <tvm/packed_func_ext.h>
 #include <nnvm/op.h>
 #include <nnvm/node.h>
 #include <nnvm/op_attr_types.h>
+#include <nnvm/compiler/op_attr_types.h>
+#include <nnvm/compiler/util.h>
 #include <nnvm/top/tensor.h>
 #include "../op_common.h"
 #include "../elemwise_op_common.h"
+#include "topi/broadcast.h"
 
 namespace nnvm {
 namespace top {
+using namespace tvm;
+using namespace nnvm::compiler;
 
 // broadcast_to
 DMLC_REGISTER_PARAMETER(BroadcastToParam);
@@ -67,6 +74,14 @@ So with `shape=(2,0)`, we will obtain the same result as in the above example.
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<BroadcastToParam>)
 .set_attr<FInferShape>("FInferShape", BroadcastToInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+    const Array<Tensor>& inputs,
+    const Array<Tensor>& out_info) {
+      const BroadcastToParam& param = nnvm::get<BroadcastToParam>(attrs.parsed);
+      auto shape = ShapeToArray(param.shape);
+      return Array<Tensor>{ topi::broadcast_to(inputs[0], shape) };
+  })
 .set_num_inputs(1)
 .set_num_outputs(1)
 .set_support_level(4);
@@ -122,6 +137,13 @@ inline bool BinaryBroadcastShape(const nnvm::NodeAttrs& attrs,
     [](const NodeAttrs& attrs) {                                    \
       return std::vector<std::pair<int, int> >{{0, 0}, {1, 0}};     \
     })                                                              \
+  .set_attr<FTVMCompute>(                                           \
+    "FTVMCompute", [](const NodeAttrs& attrs,                       \
+      const Array<Tensor>& inputs,                                  \
+      const Array<Tensor>& out_info) {                              \
+        return Array<Tensor>{                                       \
+          topi::name(inputs[0], inputs[1]) };                       \
+    })                                                              \
   .add_argument("lhs", "Tensor", "first input")                     \
   .add_argument("rhs", "Tensor", "second input")
 
diff --git a/nnvm/src/top/tensor/elemwise.cc b/nnvm/src/top/tensor/elemwise.cc
index 73a9583220d0..ac66e0c3f70e 100644
--- a/nnvm/src/top/tensor/elemwise.cc
+++ b/nnvm/src/top/tensor/elemwise.cc
@@ -6,13 +6,19 @@
 #include <nnvm/op.h>
 #include <nnvm/node.h>
 #include <nnvm/op_attr_types.h>
+#include <nnvm/compiler/op_attr_types.h>
 #include <nnvm/top/tensor.h>
 #include <cmath>
 #include "../op_common.h"
 #include "../elemwise_op_common.h"
+#include "topi/broadcast.h"
+#include "topi/elemwise.h"
+#include "topi/tags.h"
 
 namespace nnvm {
 namespace top {
+using namespace tvm;
+using namespace nnvm::compiler;
 // undefined op
 NNVM_REGISTER_ELEMWISE_UNARY_OP(__undef__)
 .describe(R"code(undefined op.
@@ -32,6 +38,12 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(sigmoid)
 
 )code" NNVM_ADD_FILELINE)
 .set_support_level(1)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+      return Array<Tensor>{ topi::sigmoid(inputs[0]) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
@@ -56,6 +68,12 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(tanh)
 
 )code" NNVM_ADD_FILELINE)
 .set_support_level(1)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+      return Array<Tensor>{ topi::tanh(inputs[0]) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
@@ -80,6 +98,12 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(exp)
 
 )code" NNVM_ADD_FILELINE)
 .set_support_level(1)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+      return Array<Tensor>{ topi::exp(inputs[0]) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
@@ -100,6 +124,12 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(log)
 
 )code" NNVM_ADD_FILELINE)
 .set_support_level(1)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+      return Array<Tensor>{ topi::log(inputs[0]) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
@@ -120,6 +150,12 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(sqrt)
 
 )code" NNVM_ADD_FILELINE)
 .set_support_level(1)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+      return Array<Tensor>{ topi::sqrt(inputs[0]) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
@@ -140,6 +176,12 @@ NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_add)
 
 )code")
 .set_support_level(1)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+      return Array<Tensor>{ topi::broadcast_add(inputs[0], inputs[1]) };
+  })
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds){
@@ -154,6 +196,12 @@ NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_sub)
 
 )code"  NNVM_ADD_FILELINE)
 .set_support_level(1)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+      return Array<Tensor>{ topi::broadcast_sub(inputs[0], inputs[1]) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds){
@@ -171,6 +219,12 @@ NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_mul)
 
 )code"  NNVM_ADD_FILELINE)
 .set_support_level(1)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+      return Array<Tensor>{ topi::broadcast_mul(inputs[0], inputs[1]) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds){
@@ -190,6 +244,12 @@ NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_div)
 
 )code"  NNVM_ADD_FILELINE)
 .set_support_level(1)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+      return Array<Tensor>{ topi::broadcast_div(inputs[0], inputs[1]) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds){
@@ -216,6 +276,12 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(negative)
 
 )code"  NNVM_ADD_FILELINE)
 .set_support_level(3)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+      return Array<Tensor>{ topi::negative(inputs[0]) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds){
@@ -232,6 +298,12 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(copy)
 
 )code"  NNVM_ADD_FILELINE)
 .set_support_level(3)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+      return Array<Tensor>{ topi::identity(inputs[0]) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds){
@@ -315,12 +387,29 @@ DMLC_REGISTER_PARAMETER(ScalarParam);
   .set_attr_parser(ParamParser<ScalarParam>)                            \
   .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<ScalarParam>)
 
+inline Tensor binary_scalar_op(const NodeAttrs& attrs,
+                               const Tensor& x,
+                               std::function<Expr(Expr, Expr)> f) {
+  const ScalarParam& param = nnvm::get<ScalarParam>(attrs.parsed);
+  auto scalar_val = static_cast<float>(param.scalar);
+  return compute(x->shape, [&](const Array<Var>& i) {
+    auto scalar_const = make_const(x->dtype, scalar_val);
+    return f(x(i), scalar_const);
+    }, "tensor", topi::kElementWise);
+}
 
 NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__add_scalar__)
 .describe(R"code(Tensor add scalar
 
 )code"  NNVM_ADD_FILELINE)
 .set_support_level(3)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    return Array<Tensor>{ binary_scalar_op(attrs, inputs[0],
+      [](Expr x, Expr y) { return x + y; }) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds){
@@ -332,6 +421,13 @@ NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__sub_scalar__)
 
 )code"  NNVM_ADD_FILELINE)
 .set_support_level(3)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    return Array<Tensor>{ binary_scalar_op(attrs, inputs[0],
+      [](Expr x, Expr y) { return x - y; }) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds){
@@ -343,6 +439,13 @@ NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__rsub_scalar__)
 
 )code"  NNVM_ADD_FILELINE)
 .set_support_level(3)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    return Array<Tensor>{ binary_scalar_op(attrs, inputs[0],
+      [](Expr x, Expr y) { return y - x; }) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds){
@@ -356,6 +459,13 @@ NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__mul_scalar__)
 
 )code"  NNVM_ADD_FILELINE)
 .set_support_level(3)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    return Array<Tensor>{ binary_scalar_op(attrs, inputs[0],
+      [](Expr x, Expr y) { return x * y; }) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds){
@@ -372,6 +482,13 @@ NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__div_scalar__)
 
 )code"  NNVM_ADD_FILELINE)
 .set_support_level(3)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    return Array<Tensor>{ binary_scalar_op(attrs, inputs[0],
+      [](Expr x, Expr y) { return x / y; }) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds){
@@ -388,6 +505,13 @@ NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__rdiv_scalar__)
 
 )code"  NNVM_ADD_FILELINE)
 .set_support_level(3)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    return Array<Tensor>{ binary_scalar_op(attrs, inputs[0],
+      [](Expr x, Expr y) { return y / x; }) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds){
@@ -411,6 +535,13 @@ NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__pow_scalar__)
 
 )code"  NNVM_ADD_FILELINE)
 .set_support_level(3)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    return Array<Tensor>{ binary_scalar_op(attrs, inputs[0],
+      [](Expr x, Expr y) { return tvm::pow(x, y); }) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds){
@@ -434,6 +565,13 @@ NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__rpow_scalar__)
 
 )code"  NNVM_ADD_FILELINE)
 .set_support_level(3)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    return Array<Tensor>{ binary_scalar_op(attrs, inputs[0],
+      [](Expr x, Expr y) { return tvm::pow(y, x); }) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds){
diff --git a/nnvm/src/top/tensor/reduce.cc b/nnvm/src/top/tensor/reduce.cc
index 429800fe5ae1..8eac2449b271 100644
--- a/nnvm/src/top/tensor/reduce.cc
+++ b/nnvm/src/top/tensor/reduce.cc
@@ -6,12 +6,17 @@
 #include <nnvm/op.h>
 #include <nnvm/node.h>
 #include <nnvm/op_attr_types.h>
+#include <nnvm/compiler/op_attr_types.h>
+#include <nnvm/compiler/util.h>
 #include <nnvm/top/tensor.h>
 #include "../op_common.h"
 #include "../elemwise_op_common.h"
+#include "topi/reduction.h"
 
 namespace nnvm {
 namespace top {
+using namespace tvm;
+using namespace nnvm::compiler;
 
 // reduce
 DMLC_REGISTER_PARAMETER(ReduceParam);
@@ -127,6 +132,15 @@ Example::
   [ 12.  19.  27.]
 
 )code" NNVM_ADD_FILELINE)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const ReduceParam& param = nnvm::get<ReduceParam>(attrs.parsed);
+    auto axis = ShapeToArray(param.axis);
+    return Array<Tensor>{
+      topi::sum(inputs[0], axis, param.keepdims) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds){
@@ -145,6 +159,15 @@ NNVM_REGISTER_REDUCE_OP(max)
 .describe(R"code(Computes the max of array elements over given axes.
 
 )code" NNVM_ADD_FILELINE)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const ReduceParam& param = nnvm::get<ReduceParam>(attrs.parsed);
+    auto axis = ShapeToArray(param.axis);
+    return Array<Tensor>{
+      topi::max(inputs[0], axis, param.keepdims) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds){
@@ -168,6 +191,15 @@ NNVM_REGISTER_REDUCE_OP(min)
 .describe(R"code(Computes the min of array elements over given axes.
 
 )code" NNVM_ADD_FILELINE)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const ReduceParam& param = nnvm::get<ReduceParam>(attrs.parsed);
+    auto axis = ShapeToArray(param.axis);
+    return Array<Tensor>{
+      topi::min(inputs[0], axis, param.keepdims) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds){
diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
index 90457389ca2e..0bf1a91ec830 100644
--- a/nnvm/src/top/tensor/transform.cc
+++ b/nnvm/src/top/tensor/transform.cc
@@ -6,13 +6,19 @@
 #include <nnvm/op.h>
 #include <nnvm/node.h>
 #include <nnvm/op_attr_types.h>
+#include <nnvm/compiler/op_attr_types.h>
+#include <nnvm/compiler/util.h>
 #include <nnvm/top/tensor.h>
 #include <cctype>
 #include "../op_common.h"
 #include "../elemwise_op_common.h"
+#include "topi/nn/flatten.h"
+#include "topi/transform.h"
 
 namespace nnvm {
 namespace top {
+using namespace tvm;
+using namespace nnvm::compiler;
 
 // flatten
 inline bool FlattenInferShape(const NodeAttrs& attrs,
@@ -58,6 +64,12 @@ Example::
 .set_attr<FInferShape>("FInferShape", FlattenInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
 .add_argument("data", "Tensor", "Input data.")
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    return Array<Tensor>{ topi::nn::flatten(inputs[0]) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds){
@@ -144,6 +156,13 @@ Example::
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<ConcatenateParam>)
 .set_attr<FInferShape>("FInferShape", ConcatenateInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const ConcatenateParam& param = nnvm::get<ConcatenateParam>(attrs.parsed);
+    return Array<Tensor>{ topi::concatenate(inputs, param.axis) };
+})
 .set_num_outputs(1)
 .set_num_inputs(kVarg)
 .set_support_level(1);
@@ -190,6 +209,13 @@ will return a new array with shape ``(2,5,3,4)``.
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
 .set_num_inputs(1)
 .set_num_outputs(1)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const ExpandDimsParam& param = nnvm::get<ExpandDimsParam>(attrs.parsed);
+    return Array<Tensor>{ topi::expand_dims(inputs[0], param.axis, param.num_newaxis) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds){
@@ -326,6 +352,22 @@ along which to split the array.
 .set_attr<FInferType>("FInferType", ElemwiseType<1, -1>)
 .set_num_inputs(1)
 .set_num_outputs(SplitNumOutputs)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const SplitParam& param = nnvm::get<SplitParam>(attrs.parsed);
+    if (param.equal_split) {
+      return Array<Tensor>{
+        topi::split_sections(inputs[0], param.indices_or_sections[0], param.axis) };
+    } else {
+      Array<Expr> indices;
+      for (auto i : param.indices_or_sections) {
+        indices.push_back(tvm::make_const(tvm::Int(32), i));
+      }
+      return Array<Tensor>{ topi::split(inputs[0], indices, param.axis) };
+    }
+})
 .set_support_level(1);
 
 // cast
@@ -504,6 +546,12 @@ The significance of each is explained below:
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
 .set_num_inputs(1)
 .set_num_outputs(1)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    return Array<Tensor>{ topi::reshape(inputs[0], out_info[0]->shape) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
@@ -620,6 +668,14 @@ Examples::
 .set_attr<nnvm::FInferType>("FInferType", ElemwiseType<1, 1>)
 .set_num_inputs(1)
 .set_num_outputs(1)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const SqueezeParam& param = nnvm::get<SqueezeParam>(attrs.parsed);
+    auto axis = ShapeToArray(param.axis);
+    return Array<Tensor>{ topi::squeeze(inputs[0], axis) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
@@ -695,6 +751,14 @@ Examples::
 .set_num_inputs(1)
 .set_num_outputs(1)
 .set_support_level(4)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const TransposeParam& param = nnvm::get<TransposeParam>(attrs.parsed);
+    auto axes = ShapeToArray(param.axes);
+    return Array<Tensor>{ topi::transpose(inputs[0], axes) };
+})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {

From 2adbfbfed65ebd80fd9d4f34151f7b9d98ce4466 Mon Sep 17 00:00:00 2001
From: Yao Wang <kevinthesunwy@gmail.com>
Date: Thu, 8 Feb 2018 18:54:14 -0800
Subject: [PATCH 606/949] Move dense compute back to python (#364)

---
 nnvm/python/nnvm/top/nn.py |  7 +++++++
 nnvm/src/top/nn/nn.cc      | 15 ---------------
 2 files changed, 7 insertions(+), 15 deletions(-)

diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index f91f77dfdc33..086e411856d0 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -51,6 +51,13 @@ def schedule_log_softmax(_, outs, target):
 
 
 # dense
+@reg.register_compute("dense")
+def compute_dense(attrs, inputs, _):
+    """Compute definition of dense"""
+    if attrs.get_bool("use_bias"):
+        return topi.nn.dense(inputs[0], inputs[1], bias=inputs[2])
+    return topi.nn.dense(inputs[0], inputs[1])
+
 @reg.register_schedule("dense")
 def schedule_dense(_, outs, target):
     """Schedule definition of dense"""
diff --git a/nnvm/src/top/nn/nn.cc b/nnvm/src/top/nn/nn.cc
index 88ed7fd58f83..4bb5185672d3 100644
--- a/nnvm/src/top/nn/nn.cc
+++ b/nnvm/src/top/nn/nn.cc
@@ -82,21 +82,6 @@ If ``use_bias`` is set to be false, then the ``bias`` term is ignored.
 .set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<DenseParam>)
 .set_attr<FInferShape>("FInferShape", DenseInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
-.set_attr<FTVMCompute>(
-  "FTVMCompute", [](const NodeAttrs& attrs,
-                    const Array<Tensor>& inputs,
-                    const Array<Tensor>& out_info) {
-    Tensor bias_val;
-    Tensor* bias;
-    const DenseParam& param = nnvm::get<DenseParam>(attrs.parsed);
-    if (param.use_bias) {
-      bias_val = inputs[2];
-      bias = &bias_val;
-    } else {
-      bias = nullptr;
-    }
-    return Array<Tensor>{ topi::nn::dense(inputs[0], inputs[1], bias) };
-})
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {

From 3c3387df40a163ba5c2c035472c39e3529cdacc1 Mon Sep 17 00:00:00 2001
From: Siju Samuel <sijusamuel@gmail.com>
Date: Fri, 9 Feb 2018 22:49:58 +0530
Subject: [PATCH 607/949] [Compilation Warning Fix]Enum constant in boolean
 context (#365)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The below compilation warning is fixed.

In file included from /mnt/D_DRIVE/work/nnvm_8_feb/dmlc-core/include/dmlc/any.h:16:0,
                 from include/nnvm/./base.h:11,
                 from include/nnvm/graph.h:15,
                 from src/compiler/fold_scale_axis.cc:6:
src/compiler/fold_scale_axis.cc: In function ‘nnvm::Graph nnvm::compiler::FoldScaleAxis(nnvm::Graph)’:
src/compiler/fold_scale_axis.cc:155:39: warning: enum constant in boolean context [-Wint-in-bool-context]
     CHECK(kind == kPassTroughFirst || kMulConsumer);
                                       ^
/mnt/D_DRIVE/work/nnvm_8_feb/dmlc-core/include/dmlc/./logging.h:110:9: note: in definition of macro ‘CHECK’
   if (!(x))
---
 nnvm/src/compiler/fold_scale_axis.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/src/compiler/fold_scale_axis.cc b/nnvm/src/compiler/fold_scale_axis.cc
index eb0ea4b87292..34383c4e6223 100644
--- a/nnvm/src/compiler/fold_scale_axis.cc
+++ b/nnvm/src/compiler/fold_scale_axis.cc
@@ -152,7 +152,7 @@ Graph FoldScaleAxis(Graph src) {
     bwd_chain[nid].kind = kind;
     if (kind == kNone) continue;
     CHECK_GE(in_axis.size(), 1U);
-    CHECK(kind == kPassTroughFirst || kMulConsumer);
+    CHECK(kind == kPassTroughFirst || kind == kMulConsumer);
     // propagate back.
     bool can_prop = true;
     for (size_t i = 0; i < in_axis.size(); ++i) {

From 833eb956e2d6a778b360fc255283badfebd365ce Mon Sep 17 00:00:00 2001
From: Siva <sivar.b@huawei.com>
Date: Sun, 18 Feb 2018 23:45:25 +0530
Subject: [PATCH 608/949] [NHWC] InferShape Layout conversion fix. (#372)

---
 nnvm/src/top/nn/convolution.cc |  4 ++--
 nnvm/src/top/nn/nn_common.h    | 28 +++++++++++++++++++++-------
 2 files changed, 23 insertions(+), 9 deletions(-)

diff --git a/nnvm/src/top/nn/convolution.cc b/nnvm/src/top/nn/convolution.cc
index 8664fa0a60f2..d517e7e5dbf6 100644
--- a/nnvm/src/top/nn/convolution.cc
+++ b/nnvm/src/top/nn/convolution.cc
@@ -48,7 +48,7 @@ inline bool Conv2DInferShape(const nnvm::NodeAttrs& attrs,
                  param.kernel_size[0],
                  param.kernel_size[1]});
 
-  wshape = ConvertLayout(wshape, kNCHW, param.layout);
+  wshape = ConvertLayout(wshape, kNCHW, param.layout, true);
   wshape[0] *= param.groups;
 
   NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, Conv2DParam::kWeight, wshape);
@@ -189,7 +189,7 @@ inline bool Conv2DTransposeInferShape(const nnvm::NodeAttrs& attrs,
                  param.channels / param.groups,
                  param.kernel_size[0],
                  param.kernel_size[1]});
-  wshape = ConvertLayout(wshape, kNCHW, param.layout);
+  wshape = ConvertLayout(wshape, kNCHW, param.layout, true);
   NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, Conv2DTransposeParam::kWeight, wshape);
 
   if (param.use_bias) {
diff --git a/nnvm/src/top/nn/nn_common.h b/nnvm/src/top/nn/nn_common.h
index a75077b87ca4..e9176d17aa1d 100644
--- a/nnvm/src/top/nn/nn_common.h
+++ b/nnvm/src/top/nn/nn_common.h
@@ -40,7 +40,7 @@ inline std::vector<std::string> UseBiasListInputNames(const NodeAttrs& attrs) {
  * \param dst_layout target layout
  * \return shape in target layout
  */
-inline TShape ConvertLayout(TShape src, int src_layout, int dst_layout) {
+inline TShape ConvertLayout(TShape src, int src_layout, int dst_layout, bool is_weight = false) {
   if (src_layout == dst_layout) return src;
   TShape dst = src;
   if (src.ndim() == 3) {
@@ -68,9 +68,16 @@ inline TShape ConvertLayout(TShape src, int src_layout, int dst_layout) {
     switch (src_layout) {
       case kNCHW: break;
       case kNHWC: {
-        dst[2] = src[1];
-        dst[3] = src[2];
-        dst[1] = src[3];
+        if (is_weight) {
+           dst[2] = src[0];
+           dst[3] = src[1];
+           dst[1] = src[2];
+           dst[0] = src[3];
+        } else {
+           dst[2] = src[1];
+           dst[3] = src[2];
+           dst[1] = src[3];
+        }
         break;
       }
       default: {
@@ -81,9 +88,16 @@ inline TShape ConvertLayout(TShape src, int src_layout, int dst_layout) {
     switch (dst_layout) {
       case kNCHW: break;
       case kNHWC: {
-        dst[1] = src[2];
-        dst[2] = src[3];
-        dst[3] = src[1];
+        if (is_weight) {
+            dst[0] = src[2];
+            dst[1] = src[3];
+            dst[2] = src[1];
+            dst[3] = src[0];
+        } else {
+            dst[1] = src[2];
+            dst[2] = src[3];
+            dst[3] = src[1];
+        }
         break;
       }
       default: {

From 7d0332191b2e7161689c39d4f2679ae1467b5bbd Mon Sep 17 00:00:00 2001
From: masahi <masahi129@gmail.com>
Date: Tue, 20 Feb 2018 07:47:39 +0900
Subject: [PATCH 609/949] Initial NHWC layout support (#376)

* initial NHWC layout support

* remove layout param from softmax

* more nhwc support

* fix typo

* add nhwc layout test

* fix lint

* update tvm

* update for c++ topi

* fix lint

* update tvm
---
 nnvm/include/nnvm/top/nn.h                    |  9 +++
 nnvm/python/nnvm/top/nn.py                    | 17 ++++--
 nnvm/src/top/nn/nn.cc                         |  3 +-
 nnvm/src/top/nn/pooling.cc                    | 14 +++--
 nnvm/src/top/nn/upsampling.cc                 |  2 +
 .../tests/python/compiler/test_nhwc_layout.py | 56 +++++++++++++++++++
 6 files changed, 87 insertions(+), 14 deletions(-)
 create mode 100644 nnvm/tests/python/compiler/test_nhwc_layout.py

diff --git a/nnvm/include/nnvm/top/nn.h b/nnvm/include/nnvm/top/nn.h
index 3bd0d5f278ef..a8d443241529 100644
--- a/nnvm/include/nnvm/top/nn.h
+++ b/nnvm/include/nnvm/top/nn.h
@@ -259,10 +259,19 @@ struct GlobalPool2DParam : public dmlc::Parameter<GlobalPool2DParam> {
 
 struct UpSamplingParam : public dmlc::Parameter<UpSamplingParam> {
   int scale;
+  int layout;
 
   DMLC_DECLARE_PARAMETER(UpSamplingParam) {
     DMLC_DECLARE_FIELD(scale)
       .describe("upsampling scaling factor");
+    DMLC_DECLARE_FIELD(layout)
+      .add_enum("NCHW", kNCHW)
+      .add_enum("NHWC", kNHWC)
+      .set_default(kNCHW)
+      .describe("Dimension ordering of data and weight. Can be 'NCHW', 'NHWC', etc."
+                "'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
+                "dimensions respectively. Convolution is applied on the 'H' and"
+                "'W' dimensions.");
   }
 };
 
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index 086e411856d0..330e5739b30d 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -29,7 +29,6 @@
 reg.register_pattern("pad", OpPattern.INJECTIVE)
 
 
-# softmax
 @reg.register_schedule("softmax")
 def schedule_softmax(_, outs, target):
     """Schedule definition of softmax"""
@@ -77,17 +76,18 @@ def compute_conv2d(attrs, inputs, _):
     groups = attrs.get_int("groups")
     channels = attrs.get_int("channels")
     layout = attrs["layout"]
-    assert layout == "NCHW", "only support nchw for now"
+    assert layout == "NCHW" or layout == "NHWC"
     assert dilation == (1, 1), "not support dilate now"
     if groups == 1:
-        out = topi.nn.conv2d(inputs[0], inputs[1], strides, padding)
+        out = topi.nn.conv2d(inputs[0], inputs[1], strides, padding, layout)
     elif groups == get_const_int(inputs[0].shape[1]) and groups == channels:
         out = topi.nn.depthwise_conv2d_nchw(inputs[0], inputs[1], strides, padding)
     else:
         raise ValueError("not support arbitrary group number for now")
     if attrs.get_bool("use_bias"):
         bias = inputs[2]
-        bias = topi.expand_dims(bias, axis=1, num_newaxis=2)
+        expand_axis = 1 if layout == "NCHW" else 0
+        bias = topi.expand_dims(bias, axis=expand_axis, num_newaxis=2)
         out = topi.broadcast_add(out, bias)
     return out
 
@@ -95,9 +95,12 @@ def compute_conv2d(attrs, inputs, _):
 def schedule_conv2d(attrs, outs, target):
     """Schedule definition of conv2d"""
     groups = attrs.get_int("groups")
+    layout = attrs["layout"]
     with tvm.target.create(target):
-        if groups == 1:
+        if groups == 1 and layout == "NCHW":
             return topi.generic.schedule_conv2d_nchw(outs)
+        elif groups == 1 and layout == "NHWC":
+            return topi.generic.schedule_conv2d_nhwc(outs)
         return topi.generic.schedule_depthwise_conv2d_nchw(outs)
 
 reg.register_pattern("conv2d", OpPattern.OUT_ELEMWISE_FUSABLE)
@@ -178,7 +181,9 @@ def schedule_global_avg_pool2d(_, outs, target):
 def compute_upsampling(attrs, inputs, _):
     """Compute definition of upsampling"""
     scale = attrs.get_int("scale")
-    return topi.nn.upsampling(inputs[0], scale)
+    layout = attrs["layout"]
+    assert layout == "NCHW" or layout == "NHWC"
+    return topi.nn.upsampling(inputs[0], scale, layout)
 
 @reg.register_schedule("upsampling")
 def schedule_upsampling(_, outs, target):
diff --git a/nnvm/src/top/nn/nn.cc b/nnvm/src/top/nn/nn.cc
index 4bb5185672d3..24510d252d18 100644
--- a/nnvm/src/top/nn/nn.cc
+++ b/nnvm/src/top/nn/nn.cc
@@ -279,8 +279,7 @@ NNVM_REGISTER_OP(softmax)
                     const Array<Tensor>& inputs,
                     const Array<Tensor>& out_info) {
     const SoftmaxParam& param = nnvm::get<SoftmaxParam>(attrs.parsed);
-    CHECK_EQ(param.axis, -1) << "Currently only axis=-1 is supported";
-    return Array<Tensor>{ topi::nn::softmax(inputs[0]) };
+    return Array<Tensor>{ topi::nn::softmax(inputs[0], param.axis) };
   })
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
diff --git a/nnvm/src/top/nn/pooling.cc b/nnvm/src/top/nn/pooling.cc
index ae8d872dec7c..10d9e27855cf 100644
--- a/nnvm/src/top/nn/pooling.cc
+++ b/nnvm/src/top/nn/pooling.cc
@@ -91,10 +91,11 @@ NNVM_REGISTER_OP(max_pool2d)
     auto strides = ShapeToArray(param.strides);
     auto padding = ShapeToArray(param.padding);
     auto ceil_mode = param.ceil_mode;
-    CHECK_EQ(param.layout, kNCHW)
-      << "max_pool2d currently only supports NCHW layout";
+    CHECK(param.layout == kNCHW || param.layout == kNHWC) << "Unsupported layout";
+    std::string layout = (param.layout == kNCHW ? "NCHW" : "NHWC");
     return Array<Tensor>{
-      topi::nn::pool(inputs[0], pool_size, strides, padding, topi::nn::kMaxPool, ceil_mode) };
+      topi::nn::pool(inputs[0], pool_size, strides, padding, \
+                     topi::nn::kMaxPool, ceil_mode, layout) };
 })
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
@@ -152,10 +153,11 @@ NNVM_REGISTER_OP(avg_pool2d)
     auto strides = ShapeToArray(param.strides);
     auto padding = ShapeToArray(param.padding);
     auto ceil_mode = param.ceil_mode;
-    CHECK_EQ(param.layout, kNCHW)
-      << "avg_pool2d currently only supports NCHW layout";
+    CHECK(param.layout == kNCHW || param.layout == kNHWC) << "Unsupported layout";
+    std::string layout = (param.layout == kNCHW ? "NCHW" : "NHWC");
     return Array<Tensor>{
-      topi::nn::pool(inputs[0], pool_size, strides, padding, topi::nn::kAvgPool, ceil_mode) };
+      topi::nn::pool(inputs[0], pool_size, strides, padding, \
+                     topi::nn::kAvgPool, ceil_mode, layout) };
 })
 .set_num_outputs(1)
 .set_num_inputs(1)
diff --git a/nnvm/src/top/nn/upsampling.cc b/nnvm/src/top/nn/upsampling.cc
index 608314a082c4..3195338c2223 100644
--- a/nnvm/src/top/nn/upsampling.cc
+++ b/nnvm/src/top/nn/upsampling.cc
@@ -24,9 +24,11 @@ inline bool UpSamplingInferShape(const nnvm::NodeAttrs& attrs,
   CHECK_EQ(out_shape->size(), 1U);
   TShape dshape = (*in_shape)[0];
   if (dshape.ndim() ==  0) return false;
+  dshape = ConvertLayout(dshape, param.layout, kNCHW);
   TShape oshape = dshape;
   oshape[2] = oshape[2] * param.scale;
   oshape[3] = oshape[3] * param.scale;
+  oshape = ConvertLayout(oshape, kNCHW, param.layout);
   NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, oshape);
   return true;
 }
diff --git a/nnvm/tests/python/compiler/test_nhwc_layout.py b/nnvm/tests/python/compiler/test_nhwc_layout.py
new file mode 100644
index 000000000000..57e27db74751
--- /dev/null
+++ b/nnvm/tests/python/compiler/test_nhwc_layout.py
@@ -0,0 +1,56 @@
+import numpy as np
+import tvm
+from tvm.contrib import graph_runtime as runtime
+import nnvm.symbol as sym
+import nnvm.compiler
+from nnvm.testing.config import ctx_list
+
+def get_sym(layout, channels):
+    data = sym.Variable(name="data")
+    data = sym.conv2d(data=data, kernel_size=(3,3), channels=channels, padding=(1, 1), layout=layout, use_bias=True)
+    data = sym.max_pool2d(data=data, pool_size=(2, 2), strides=(2, 2), layout=layout)
+    data = sym.upsampling(data=data, scale=2, layout=layout)
+    softmax_axis = 1
+    if layout == "NHWC":
+        softmax_axis = 3
+    data = sym.softmax(data=data, axis=softmax_axis)
+    return data
+
+
+def build_and_run(sym, params, data, out_shape):
+    ctx = tvm.cpu(0)
+    graph, lib, params = nnvm.compiler.build(sym, "llvm", shape={"data":data.shape}, params=params)
+    module = runtime.create(graph, lib, ctx)
+    module.set_input(**params)
+    module.set_input("data", data)
+    module.run()
+    out =  module.get_output(0, tvm.nd.empty(out_shape))
+    return out.asnumpy()
+
+
+def test_nhwc():
+    data_shape = (1, 3, 224, 224)
+    out_channel = 8
+    nchw_sym = get_sym("NCHW", out_channel)
+    nhwc_sym = get_sym("NHWC", out_channel)
+    conv_weight = np.random.uniform(-1, 1, (out_channel, 3, 3, 3)).astype(np.float32)
+    conv_bias = np.random.uniform(-1, 1, (out_channel)).astype(np.float32)
+    nchw_params = {
+        "conv2d0_weight" : tvm.nd.array(conv_weight, ctx=tvm.cpu(0)),
+        "conv2d0_bias" : tvm.nd.array(conv_bias, ctx=tvm.cpu(0))
+    }
+    nhwc_params = {
+        "conv2d1_weight" : tvm.nd.array(conv_weight.transpose(2, 3, 1, 0), ctx=tvm.cpu(0)),
+        "conv2d1_bias" : tvm.nd.array(conv_bias, ctx=tvm.cpu(0))
+    }
+
+    data = np.random.uniform(-1, 1, data_shape).astype(np.float32)
+    oshape = (1, out_channel, 224, 224)
+    oshape_nhwc = (1, 224, 224, out_channel)
+    nchw_output = build_and_run(nchw_sym, nchw_params, data, oshape)
+    nhwc_output = build_and_run(nhwc_sym, nhwc_params, data.transpose(0, 2, 3, 1), oshape_nhwc)
+    np.testing.assert_allclose(nchw_output, nhwc_output.transpose(0, 3, 1, 2), rtol=1e-5, atol=1e-5)
+
+
+if __name__ == "__main__":
+    test_nhwc()

From 16129e5c34d09e9239ec66bb300c6f8e31ca0c7f Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 20 Feb 2018 16:29:11 -0800
Subject: [PATCH 610/949] Make compiler more robust (#378)

---
 nnvm/python/nnvm/compiler/build_module.py |  4 +++-
 nnvm/src/compiler/compile_engine.cc       | 29 +++++++++++++----------
 2 files changed, 20 insertions(+), 13 deletions(-)

diff --git a/nnvm/python/nnvm/compiler/build_module.py b/nnvm/python/nnvm/compiler/build_module.py
index 68e8d42449a1..bce142d21b63 100644
--- a/nnvm/python/nnvm/compiler/build_module.py
+++ b/nnvm/python/nnvm/compiler/build_module.py
@@ -279,8 +279,10 @@ def _run_graph(graph, params):
     graph, libmod, _ = build(graph, target, shape, dtype)
     m = graph_runtime.create(graph, libmod, ctx)
     set_input, run, get_output = m["set_input"], m["run"], m["get_output"]
+    kset = set(graph.symbol.list_input_names())
     for k, v in params.items():
-        set_input(k, tvm.nd.array(v))
+        if k in kset:
+            set_input(k, tvm.nd.array(v))
     run()
     out_data = []
     for i, kv in enumerate(zip(oshape, odtype)):
diff --git a/nnvm/src/compiler/compile_engine.cc b/nnvm/src/compiler/compile_engine.cc
index 23c4d4c9b6bd..1ef39f851203 100644
--- a/nnvm/src/compiler/compile_engine.cc
+++ b/nnvm/src/compiler/compile_engine.cc
@@ -135,12 +135,13 @@ class CompileEngine {
   }
 
   // get schedule and its args
-  std::pair<Schedule, Array<tvm::Tensor> > GetScheduleArgs(Graph graph,
-                                                           const Array<tvm::Tensor> &inputs,
-                                                           const std::string &target,
-                                                           int master_idx,
-                                                           std::string *readable_name,
-                                                           Array<tvm::Tensor> *outputs) {
+  std::tuple<Schedule, Array<tvm::Tensor>, Graph>
+  GetScheduleArgs(Graph graph,
+                  const Array<tvm::Tensor> &inputs,
+                  const std::string &target,
+                  int master_idx,
+                  std::string *readable_name,
+                  Array<tvm::Tensor> *outputs) {
     // shape, type
     static auto& fcompute =
         nnvm::Op::GetAttr<FTVMCompute>("FTVMCompute");
@@ -221,12 +222,14 @@ class CompileEngine {
         idx[master_idx].source->attrs, outs, target);
 
     // store extra return values
-    if (readable_name != nullptr)
+    if (readable_name != nullptr) {
       *readable_name = readable_name_os.str();
-    if (outputs != nullptr)
+    }
+    if (outputs != nullptr) {
       *outputs = outs;
+    }
 
-    return std::make_pair(sch, all_args);
+    return std::make_tuple(sch, all_args, graph);
   }
 
   // run the actual lowering process
@@ -239,8 +242,9 @@ class CompileEngine {
     Array<tvm::Tensor> outputs;
     Schedule sch;
 
-    std::tie(sch, all_args) = GetScheduleArgs(graph, inputs, target, master_idx,
-                                              &readable_name, &outputs);
+    std::tie(sch, all_args, graph) = GetScheduleArgs(
+        graph, inputs, target, master_idx,
+        &readable_name, &outputs);
 
     std::shared_ptr<GraphFuncNode> gf = std::make_shared<GraphFuncNode>();
     gf->target = target;
@@ -335,7 +339,8 @@ TVM_REGISTER_GLOBAL("nnvm.compiler.CacheItem2ScheduleArgs")
 
     Schedule sch;
     Array<tvm::Tensor> all_args;
-    std::tie(sch, all_args) = CompileEngine::Global()->GetScheduleArgs(
+    std::tie(sch, all_args, graph) =
+        CompileEngine::Global()->GetScheduleArgs(
         graph, inputs, target, master_idx, nullptr, nullptr);
 
     Array<tvm::NodeRef> ret;

From 08da1362da1aa0193f210f6476425491362b83d1 Mon Sep 17 00:00:00 2001
From: "Joshua Z. Zhang" <cheungchih@gmail.com>
Date: Mon, 26 Feb 2018 14:19:59 -0600
Subject: [PATCH 611/949] [FRONTEND] fix same var used in single op (#383)

* fix same var used in single op

* revert to older version
---
 nnvm/python/nnvm/frontend/mxnet.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/python/nnvm/frontend/mxnet.py b/nnvm/python/nnvm/frontend/mxnet.py
index 738074f2dd2c..a96c9948189d 100644
--- a/nnvm/python/nnvm/frontend/mxnet.py
+++ b/nnvm/python/nnvm/frontend/mxnet.py
@@ -314,7 +314,7 @@ def _from_mxnet_impl(symbol, graph):
     childs = symbol.get_children()
     if childs:
         op_name = symbol.attr('op_name')
-        childs = [_from_mxnet_impl(c, graph) for c in childs]
+        childs = [_from_mxnet_impl(childs[i], graph) for i in range(len(childs.list_outputs()))]
         childs = [x for y in childs for x in _as_list(y)]  # expand group symbol
         node = _convert_symbol(op_name, childs, attr)
     else:

From 4523bdb8fd78d40b63f3d45b9905e5770086c623 Mon Sep 17 00:00:00 2001
From: Zhixun Tan <phisiart@gmail.com>
Date: Sat, 3 Mar 2018 09:08:39 -0800
Subject: [PATCH 612/949] WebGL end-to-end example (#369)

---
 nnvm/tutorials/from_mxnet_to_webgl.py | 505 ++++++++++++++++++++++++++
 nnvm/tutorials/web/resnet.html        | 187 ++++++++++
 2 files changed, 692 insertions(+)
 create mode 100644 nnvm/tutorials/from_mxnet_to_webgl.py
 create mode 100644 nnvm/tutorials/web/resnet.html

diff --git a/nnvm/tutorials/from_mxnet_to_webgl.py b/nnvm/tutorials/from_mxnet_to_webgl.py
new file mode 100644
index 000000000000..b74d367be22b
--- /dev/null
+++ b/nnvm/tutorials/from_mxnet_to_webgl.py
@@ -0,0 +1,505 @@
+"""
+Quick Start - End-to-End Tutorial for NNVM/TVM Pipeline for OpenGL and WebGL
+============================================================================
+**Author**: `Zhixun Tan <https://github.com/phisiart>`_
+
+This example shows how to build a neural network with NNVM python frontend and
+generate runtime library for WebGL running in a browser with TVM. (Thanks to
+Tianqi's `tutorial for cuda <http://nnvm.tvmlang.org/tutorials/get_started.html>`_ and
+Ziheng's `tutorial for Raspberry Pi <http://nnvm.tvmlang.org/tutorials/deploy_model_on_rasp.html>`_)
+To run this notebook, you need to install tvm and nnvm following
+`these instructions <https://github.com/dmlc/nnvm/blob/master/docs/how_to/install.md>`_.
+Notice that you need to build tvm with OpenGL.
+"""
+
+######################################################################
+# Overview
+# --------
+# In this tutorial, we will download a pre-trained resnet18 model from Gluon
+# Model Zoo, and run image classification in 3 different ways:
+#
+# - Run locally:
+#   We will compile the model into a TVM library with OpenGL device code and
+#   directly run it locally.
+#
+# - Run in a browser through RPC:
+#   We will compile the model into a JavaScript TVM library with WebGL device
+#   code, and upload it to an RPC server that is hosting JavaScript TVM runtime
+#   to run it.
+#
+# - Export a JavaScript library and run in a browser:
+#   We will compile the model into a JavaScript TVM library with WebGL device
+#   code, combine it with JavaScript TVM runtime, and pack everything together.
+#   Then we will run it directly in a browser.
+#
+from __future__ import print_function
+
+import numpy as np
+import tvm
+import nnvm.compiler
+import nnvm.testing
+
+# This tutorial must be run with OpenGL backend enabled in TVM.
+# The NNVM CI does not enable OpenGL yet. But the user can run this script.
+if not tvm.module.enabled("opengl"):
+    print("OpenGL backend not enabled. This tutorial cannot be run.")
+    exit(0)
+
+# To run the local demo, set this flag to True.
+run_deploy_local = False
+
+# To run the RPC demo, set this flag to True.
+run_deploy_rpc = False
+
+# To run the WebGL deploy demo, set this flag to True.
+run_deploy_web = True
+
+######################################################################
+# Download a Pre-trained Resnet18 Model
+# -------------------------------------
+# Here we define 2 functions:
+# 
+# - A function that downloads a pre-trained resnet18 model from Gluon Model Zoo.
+#   The model that we download is in MXNet format, we then transform it into an
+#   NNVM computation graph.
+#
+# - A function that downloads a file that contains the name of all the image
+#   classes in this model.
+#
+def load_mxnet_resnet():
+    """Load a pretrained resnet model from MXNet and transform that into NNVM
+       format.
+
+    Returns
+    -------
+    net : nnvm.Symbol
+        The loaded resnet computation graph.
+
+    params : dict[str -> NDArray]
+        The pretrained model parameters.
+    
+    data_shape: tuple
+        The shape of the input tensor (an image).
+
+    out_shape: tuple
+        The shape of the output tensor (probability of all classes).
+    """
+
+    print("Loading pretrained resnet model from MXNet...")
+
+    # Download a pre-trained mxnet resnet18_v1 model.
+    from mxnet.gluon.model_zoo.vision import get_model
+    block = get_model('resnet18_v1', pretrained=True)
+
+    # Transform the mxnet model into NNVM.
+    # We want a probability so add a softmax operator.
+    sym, params = nnvm.frontend.from_mxnet(block)
+    sym = nnvm.sym.softmax(sym)
+
+    print("- Model loaded!")
+    return sym, params, (1, 3, 224, 224), (1, 1000)
+
+def download_synset():
+    """Download a dictionary from class index to name.
+    This lets us know what our prediction actually is.
+
+    Returns
+    -------
+    synset : dict[int -> str]
+        The loaded synset.
+    """
+
+    print("Downloading synset...")
+
+    from mxnet import gluon
+
+    url = "https://gist.githubusercontent.com/zhreshold/" + \
+          "4d0b62f3d01426887599d4f7ede23ee5/raw/" + \
+          "596b27d23537e5a1b5751d2b0481ef172f58b539/" + \
+          "imagenet1000_clsid_to_human.txt"
+    file_name = "synset.txt"
+    
+    gluon.utils.download(url, file_name)
+    with open(file_name) as f:
+        synset = eval(f.read())
+    
+    print("- Synset downloaded!")
+    return synset
+
+######################################################################
+# Download Input Image
+# --------------------
+# Here we define 2 functions that prepare an image that we want to perform
+# classification on.
+#
+# - A function that downloads a cat image.
+#
+# - A function that performs preprocessing to an image so that it fits the
+#   format required by the resnet18 model.
+#
+def download_image():
+    """Download a cat image and resize it to 224x224 which fits resnet.
+
+    Returns
+    -------
+    image : PIL.Image.Image
+        The loaded and resized image.
+    """
+
+    print("Downloading cat image...")
+
+    from matplotlib import pyplot as plt
+    from mxnet import gluon
+    from PIL import Image
+
+    url = "https://github.com/dmlc/mxnet.js/blob/master/data/cat.png?raw=true"
+    img_name = "cat.jpg"
+
+    gluon.utils.download(url, img_name)
+    image = Image.open(img_name).resize((224, 224))
+
+    print("- Cat image downloaded!")
+
+    plt.imshow(image)
+    plt.show()
+
+    return image
+
+def transform_image(image):
+    """Perform necessary preprocessing to input image.
+
+    Parameters
+    ----------
+    image : numpy.ndarray
+        The raw image.
+
+    Returns
+    -------
+    image : numpy.ndarray
+        The preprocessed image.
+    """
+
+    image = np.array(image) - np.array([123., 117., 104.])
+    image /= np.array([58.395, 57.12, 57.375])
+    image = image.transpose((2, 0, 1))
+    image = image[np.newaxis, :]
+    return image
+
+######################################################################
+# Compile the Model
+# -----------------
+# Here we define a function that invokes the NNVM compiler.
+#
+def compile_net(net, target_host, target, data_shape, params):
+    """Compiles an NNVM computation graph.
+
+    Parameters
+    ----------
+    net : nnvm.Graph
+        The NNVM computation graph.
+
+    target_host : str
+        The target to compile the host portion of the library.
+
+    target : str
+        The target to compile the device portion of the library.
+
+    data_shape : tuple
+        The shape of the input data (image).
+
+    params : dict[str -> NDArray]
+        Model parameters.
+
+    Returns
+    -------
+    graph : Graph
+        The final execution graph.
+
+    libmod : tvm.Module
+        The module that comes with the execution graph
+
+    params : dict[str -> NDArray]
+        The updated parameters of graph if params is passed.
+        This can be different from the params passed in.
+    """
+
+    print("Compiling the neural network...")
+
+    with nnvm.compiler.build_config(opt_level=0):
+        deploy_graph, lib, deploy_params = nnvm.compiler.build(
+            net,
+            target_host=target_host,
+            target=target,
+            shape={"data": data_shape},
+            params=params)
+
+    print("- Complilation completed!")
+    return deploy_graph, lib, deploy_params
+
+######################################################################
+# Demo 1: Deploy Locally
+# ----------------------
+# In this demo, we will compile the model targetting the local machine.
+#
+# Then we will demonstrate how to save the compiled model as a shared library
+# and load it back.
+#
+# Finally, we will run the model.
+#
+def deploy_local():
+    """Runs the demo that deploys a model locally.
+    """
+
+    # Load resnet model.
+    net, params, data_shape, out_shape = load_mxnet_resnet()
+
+    # Compile the model.
+    # Note that we specify the the host target as "llvm".
+    deploy_graph, lib, deploy_params = compile_net(
+        net,
+        target_host="llvm",
+        target="opengl",
+        data_shape=data_shape,
+        params=params)
+
+    # Save the compiled module.
+    # Note we need to save all three files returned from the NNVM compiler.
+    print("Saving the compiled module...")
+    from tvm.contrib import util
+    temp = util.tempdir()
+
+    path_lib = temp.relpath("deploy_lib.so")
+    path_graph_json = temp.relpath("deploy_graph.json")
+    path_params = temp.relpath("deploy_param.params")
+
+    lib.export_library(path_lib)
+    with open(path_graph_json, "w") as fo:
+        fo.write(deploy_graph.json())
+    with open(path_params, "wb") as fo:
+        fo.write(nnvm.compiler.save_param_dict(deploy_params))
+
+    print("- Saved files:", temp.listdir())
+
+    # Load the module back.
+    print("Loading the module back...")
+    loaded_lib = tvm.module.load(path_lib)
+    with open(path_graph_json) as fi:
+        loaded_graph_json = fi.read()
+    with open(path_params, "rb") as fi:
+        loaded_params = bytearray(fi.read())
+    print("- Module loaded!")
+
+    # Run the model! We will perform prediction on an image.
+    print("Running the graph...")
+    from tvm.contrib import graph_runtime
+
+    module = graph_runtime.create(loaded_graph_json, loaded_lib, tvm.opengl(0))
+    module.load_params(loaded_params)
+
+    image = transform_image(download_image())
+    input_data = tvm.nd.array(image.astype("float32"), ctx=tvm.opengl(0))
+
+    module.set_input("data", input_data)
+    module.run()
+
+    # Retrieve the output.
+    out = module.get_output(0, tvm.nd.empty(out_shape, ctx=tvm.opengl(0)))
+    top1 = np.argmax(out.asnumpy())
+    synset = download_synset()
+    print('TVM prediction top-1:', top1, synset[top1])
+
+if run_deploy_local:
+    deploy_local()
+
+######################################################################
+# Demo 2: Deploy the Model to WebGL Remotely with RPC
+# -------------------------------------------------------
+# Following the steps above, we can also compile the model for WebGL.
+# TVM provides rpc module to help with remote deploying.
+#
+# When we deploy a model locally to OpenGL, the model consists of two parts:
+# the host LLVM part and the device GLSL part. Now that we want to deploy to
+# WebGL, we need to leverage Emscripten to transform LLVM into JavaScript. In
+# order to do that, we will need to specify the host target as
+# 'llvm -target=asmjs-unknown-emscripten -system-lib`. Then call Emscripten to
+# compile the LLVM binary output into a JavaScript file.
+#
+# First, we need to manually start an RPC server. Please follow the instructions
+# in `tvm/web/README.md`. After following the steps, you should have a web page
+# opened in a browser, and a Python script running a proxy.
+#
+def deploy_rpc():
+    """Runs the demo that deploys a model remotely through RPC.
+    """
+    from tvm.contrib import rpc, util, emscripten
+
+    # As usual, load the resnet18 model.
+    net, params, data_shape, out_shape = load_mxnet_resnet()
+
+    # Compile the model.
+    # Note that this time we are changing the target.
+    # This is because we want to translate the host library into JavaScript
+    # through Emscripten.
+    graph, lib, params = compile_net(
+        net,
+        target_host="llvm -target=asmjs-unknown-emscripten -system-lib",
+        target="opengl",
+        data_shape=data_shape,
+        params=params)
+
+    # Now we want to deploy our model through RPC.
+    # First we ned to prepare the module files locally.
+    print("Saving the compiled module...")
+
+    temp = util.tempdir()
+    path_obj = temp.relpath("deploy.bc") # host LLVM part
+    path_dso = temp.relpath("deploy.js") # host JavaScript part
+    path_gl = temp.relpath("deploy.gl") # device GLSL part
+    path_json = temp.relpath("deploy.tvm_meta.json")
+
+    lib.save(path_obj)
+    emscripten.create_js(path_dso, path_obj, side_module=True)
+    lib.imported_modules[0].save(path_gl)
+
+    print("- Saved files:", temp.listdir())
+
+    # Connect to the RPC server.
+    print("Connecting to RPC server...")
+    proxy_host = 'localhost'
+    proxy_port = 9090
+    remote = rpc.connect(proxy_host, proxy_port, key="js")
+    print("- Connected to RPC server!")
+
+    # Upload module to RPC server.
+    print("Uploading module to RPC server...")
+    remote.upload(path_dso, "deploy.dso")
+    remote.upload(path_gl)
+    remote.upload(path_json)
+    print("- Upload completed!")
+
+    # Load remote library.
+    print("Loading remote library...")
+    fdev = remote.load_module("deploy.gl")
+    fhost = remote.load_module("deploy.dso")
+    fhost.import_module(fdev)
+    rlib = fhost
+    print("- Remote library loaded!")
+
+    ctx = remote.opengl(0)
+
+    # Upload the parameters.
+    print("Uploading parameters...")
+    rparams = {k: tvm.nd.array(v, ctx) for k, v in params.items()}
+    print("- Parameters uploaded!")
+
+    # Create the remote runtime module.
+    print("Running remote module...")
+    from tvm.contrib import graph_runtime
+    module = graph_runtime.create(graph, rlib, ctx)
+
+    # Set parameter.
+    module.set_input(**rparams)
+
+    # Set input data.
+    input_data = np.random.uniform(size=data_shape)
+    module.set_input('data', tvm.nd.array(input_data.astype('float32')))
+
+    # Run.
+    module.run()
+    print("- Remote module execution completed!")
+
+    out = module.get_output(0, out=tvm.nd.empty(out_shape, ctx=ctx))
+    # Print first 10 elements of output.
+    print(out.asnumpy()[0][0:10])
+
+if run_deploy_rpc:
+    deploy_rpc()
+
+######################################################################
+# Demo 3: Deploy the Model to WebGL SystemLib
+# -----------------------------------------------
+# This time we are not using RPC. Instead, we will compile the model and link it
+# with the entire tvm runtime into a single giant JavaScript file. Then we will
+# run the model using JavaScript.
+#
+def deploy_web():
+    """Runs the demo that deploys to web.
+    """
+
+    import base64
+    import json
+    import os
+    import shutil
+    import SimpleHTTPServer, SocketServer
+
+    from tvm.contrib import emscripten
+
+    curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
+    working_dir = os.getcwd()
+    output_dir = os.path.join(working_dir, "resnet")
+    if not os.path.exists(output_dir):
+        os.makedirs(output_dir)
+
+    # As usual, load the resnet18 model.
+    net, params, data_shape, out_shape = load_mxnet_resnet()
+
+    # As usual, compile the model.
+    graph, lib, params = compile_net(
+        net,
+        target_host="llvm -target=asmjs-unknown-emscripten -system-lib",
+        target="opengl",
+        data_shape=data_shape,
+        params=params)
+
+    # Now we save the model and link it with the TVM web runtime.
+    path_lib = os.path.join(output_dir, "resnet.js")
+    path_graph = os.path.join(output_dir, "resnet.json")
+    path_params = os.path.join(output_dir, "resnet.params")
+    path_data_shape = os.path.join(output_dir, "data_shape.json")
+    path_out_shape = os.path.join(output_dir, "out_shape.json")
+
+    lib.export_library(path_lib, emscripten.create_js, options=[
+        "-s", "USE_GLFW=3",
+        "-s", "USE_WEBGL2=1",
+        "-lglfw",
+        "-s", "TOTAL_MEMORY=1073741824",
+    ])
+    with open(path_graph, "w") as fo:
+        fo.write(graph.json())
+    with open(path_params, "w") as fo:
+        fo.write(base64.b64encode(nnvm.compiler.save_param_dict(params)))
+
+    shutil.copyfile(os.path.join(curr_path, "../tvm/web/tvm_runtime.js"),
+                    os.path.join(output_dir, "tvm_runtime.js"))
+    shutil.copyfile(os.path.join(curr_path, "web/resnet.html"),
+                    os.path.join(output_dir, "resnet.html"))
+    
+    # Now we want to save some extra files so that we can execute the model from
+    # JavaScript.
+    # - data shape
+    with open(path_data_shape, "w") as fo:
+        json.dump(list(data_shape), fo)
+    # - out shape
+    with open(path_out_shape, "w") as fo:
+        json.dump(list(out_shape), fo)
+    # - input image
+    image = download_image()
+    image.save(os.path.join(output_dir, "data.png"))
+    # - synset
+    synset = download_synset()
+    with open(os.path.join(output_dir, "synset.json"), "w") as fo:
+        json.dump(synset, fo)
+
+    print("Output files are in", output_dir)
+
+    # Finally, we fire up a simple web server to serve all the exported files.
+    print("Now running a simple server to serve the files...")
+    os.chdir(output_dir)
+    port = 8080
+    handler = SimpleHTTPServer.SimpleHTTPRequestHandler
+    httpd = SocketServer.TCPServer(("", port), handler)
+    print("Please open http://localhost:" + str(port) + "/resnet.html")
+    httpd.serve_forever()
+
+if run_deploy_web:
+    deploy_web()
\ No newline at end of file
diff --git a/nnvm/tutorials/web/resnet.html b/nnvm/tutorials/web/resnet.html
new file mode 100644
index 000000000000..b858fbc19770
--- /dev/null
+++ b/nnvm/tutorials/web/resnet.html
@@ -0,0 +1,187 @@
+<html>
+
+<head>
+  <meta charset="UTF-8">
+  <title>NNVM WebGL Test Page</title>
+</head>
+
+<body>
+  <h1>NNVM WebGL Test Page</h1>
+
+  <!-- We will draw the input image here. -->
+  <div>Input Image:</div>
+  <img id="image", src="data.png">
+
+  <!-- We need a canvas to get the image pixel data. Hide this element. -->
+  <canvas hidden id="image_canvas" width="224" height="224"></canvas>
+
+  <!-- We will write te prediction result here. -->
+  <div id="prediction"></div>
+
+  <!-- We will write all log messages here. -->
+  <div id="log">Log:</div>
+
+  <!-- The OpenGL canvas. -->
+  <canvas id="canvas"></canvas>
+
+  <script>
+    var Module = {};
+
+    // resnet.js would recognize Module["canvas"]
+    Module["canvas"] = document.getElementById("canvas");
+  </script>
+
+  <script src="resnet.js"></script>
+  <script src="tvm_runtime.js"></script>
+
+  <script>
+
+    /**
+     * Load a text file synchronously.
+     * @param {string} url The file path.
+     * @return {string} The file content.
+     */
+    function load_file(url) {
+      assert(typeof url == "string", "URL must be string");
+
+      var req = new XMLHttpRequest();
+      var result;
+      req.addEventListener("load", function() {
+        result = this.responseText;
+      });
+      req.open("get", url, false);
+      req.send();
+      return result;
+    }
+
+    /**
+     * The index of the maximum element in an array.
+     * @param {Array} The array.
+     * @return {number} The index.
+     */
+    function argmax(arr) {
+      assert(typeof arr.length == "number", "Input must be array-like");
+
+      var res = 0;
+      for (var i = 0; i < arr.length; i++) {
+        if (arr[i] > arr[res]) {
+          res = i;
+        }
+      }
+      return res;
+    }
+
+    /**
+     * Preprocess an image to fit resnet input format.
+     * @param {ImageData} The input image data. Should be 224x224xRGBA.
+     * @return {Float32Array} The preprocessed input array.
+     */
+    function preprocess_image(image_data) {
+      assert(image_data instanceof ImageData, "Input must be ImageData.");
+      assert(image_data.width == 224, "Width must be 224.");
+      assert(image_data.height == 224, "Height must be 224.");
+
+      var width = image_data.width;
+      var height = image_data.height;
+      var npixels = width * height;
+
+      var rgba_uint8 = image_data.data;
+      assert(rgba_uint8.length == npixels * 4, "Image should be RGBA.");
+
+      // Drop alpha channel. Resnet does not need it.
+      var rgb_uint8 = new Uint8Array(npixels * 3);
+      for (var i = 0; i < npixels; i++) {
+        rgb_uint8[i * 3] = rgba_uint8[i * 4];
+        rgb_uint8[i * 3 + 1] = rgba_uint8[i * 4 + 1];
+        rgb_uint8[i * 3 + 2] = rgba_uint8[i * 4 + 2];
+      }
+
+      // Cast to float and normalize.
+      var rgb_float = new Float32Array(npixels * 3);
+      for (var i = 0; i < npixels; i++) {
+        rgb_float[i * 3] = (rgb_uint8[i * 3] - 123.0) / 58.395;
+        rgb_float[i * 3 + 1] = (rgb_uint8[i * 3 + 1] - 117.0) / 57.12;
+        rgb_float[i * 3 + 2] = (rgb_uint8[i * 3 + 2] - 104.0) / 57.375;
+      }
+
+      // Transpose. Resnet expects 3 greyscale images.
+      var data = new Float32Array(npixels * 3);
+      for (var i = 0; i < npixels; i++) {
+        data[i] = rgb_float[i * 3];
+        data[npixels + i] = rgb_float[i * 3 + 1];
+        data[npixels * 2 + i] = rgb_float[i * 3 + 2];
+      }
+
+      return data;
+    }
+
+    // Set these variables at the global scope so that we can debug more easily.
+    var tvm;
+    var syslib;
+    var graph_json_str;
+    var loaded_module;
+    var data_array;
+    var data;
+    var input;
+    var base64_params;
+    var output;
+    Module["onRuntimeInitialized"] = function () {
+      tvm = tvm_runtime.create(Module);
+
+      tvm.logger = function (message) {
+        console.log(message);
+        var d = document.createElement("div");
+        d.innerHTML = message;
+        document.getElementById("log").appendChild(d);
+      };
+
+      tvm.logger("Loading SystemLib...");
+      syslib = tvm.systemLib();
+      tvm.logger("- SystemLib loaded!");
+
+      tvm.logger("Loading resnet model...");
+      graph_json_str = load_file("resnet.json");
+      ctx = tvm.context("opengl", 0);
+      loaded_module = tvm.createGraphRuntime(graph_json_str, syslib, ctx);
+      tvm.logger("- Model loaded!");
+
+      tvm.logger("Loading model parameters...");
+      base64_params = load_file("resnet.params");
+      loaded_module.load_base64_params(base64_params);
+      tvm.logger("- Model parameters loaded!");
+
+      tvm.logger("Loading input image...");
+      var image = document.getElementById("image");
+      var image_canvas = document.getElementById("image_canvas");
+      var image_canvas_context = image_canvas.getContext("2d");
+      image_canvas_context.drawImage(image, 0, 0);
+      var image_data = image_canvas_context.getImageData(0, 0, 224, 224);
+      data_array = preprocess_image(image_data);
+      tvm.logger("- Input image loaded!");
+
+      tvm.logger("Setting input data...");
+      data_shape = JSON.parse(load_file("data_shape.json"));
+      data = tvm.empty(data_shape, "float32", ctx);
+      data.copyFrom(data_array);
+      loaded_module.set_input("data", data);
+      tvm.logger("- Input data set!");
+
+      tvm.logger("Running model...");
+      loaded_module.run();
+      tvm.logger("- Model execution completed!");
+
+      out_shape = JSON.parse(load_file("out_shape.json"));
+      output = tvm.empty(out_shape, "float32", ctx);
+      loaded_module.get_output(0, output);
+
+      prediction = argmax(output.asArray());
+      
+      synset = JSON.parse(load_file("synset.json"));
+      result_string = "Prediction: " + synset[prediction] + "\n";
+      document.getElementById("prediction").innerHTML = result_string;
+    };
+
+  </script>
+</body>
+
+</html>

From 41e8699c72d5fa8e427ae5bca21b1abf20c8d36b Mon Sep 17 00:00:00 2001
From: Siva <srk.it38@gmail.com>
Date: Wed, 7 Mar 2018 07:42:00 +0530
Subject: [PATCH 613/949]     Sum operator for ONNX. (#386)

---
 nnvm/python/nnvm/frontend/onnx.py | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/nnvm/python/nnvm/frontend/onnx.py b/nnvm/python/nnvm/frontend/onnx.py
index b98d323ebae1..5457f763f23c 100644
--- a/nnvm/python/nnvm/frontend/onnx.py
+++ b/nnvm/python/nnvm/frontend/onnx.py
@@ -235,6 +235,15 @@ def _impl(inputs, attr, params):
             custom_check=lambda attrs: attrs.get('mode') == 'constant')(inputs, attr)
     return _impl
 
+def _sum():
+    def _impl(inputs, attr, params):
+        # Onnx Sum Operator
+        for in_index in range(len(inputs)-1):
+            inputs[in_index+1] = _sym.broadcast_add(inputs[in_index], inputs[in_index+1])
+
+        return inputs[len(inputs)-1]
+    return _impl
+
 # compatible operators that do NOT require any conversion.
 _identity_list = []
 
@@ -296,7 +305,7 @@ def _impl(inputs, attr, params):
     # 'HardSigmoid'
     # 'Max' : this is the elemwise maximum
     # 'Min' : this is the elemwise minimum
-    # 'Sum' : elemwise sum
+    'Sum'           : _sum(),
     # 'Mean'
     # 'Clip'
     # softmax default axis is different in onnx

From f225d8dc9f7140e28b3094c6cf4d6dee21c8d78b Mon Sep 17 00:00:00 2001
From: Atsushi Nukariya <34906252+AtuNuka@users.noreply.github.com>
Date: Wed, 7 Mar 2018 14:52:19 +0900
Subject: [PATCH 614/949] [DOCS] add a solution to installation error because
 of the old submodule (#384)

* [DOCS] add a solution to installation error

* Fix the command for updating submodules
---
 nnvm/docs/how_to/install.md | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/nnvm/docs/how_to/install.md b/nnvm/docs/how_to/install.md
index ffd1ecf40be2..0190658e2d6f 100644
--- a/nnvm/docs/how_to/install.md
+++ b/nnvm/docs/how_to/install.md
@@ -22,6 +22,7 @@ Note that it is necessary to build TVM with LLVM support to take full benefit of
 ## Contents
 - [Build the Shared Library](#build-the-shared-library)
 - [Python Package Installation](#python-package-installation)
+- [Solution to Installation Error](#solution-to-installation-error)
 
 ## Build the Shared Library
 
@@ -75,3 +76,14 @@ There are several ways to install the package, in all these cases the TVM librar
     #       providing --user flag may trigger error during installation in such case.
     cd python; python setup.py install --user; cd ..
     ```
+
+## Solution to Installation Error
+
+If you encounter the problem while installation process, you can solve by updating submodules to the latest commit set.
+To update submodules to the latest commit set, type the following command.
+
+```bash
+git submodule update --init --recursive
+```
+
+*WARNING: The default commit set in submodule is the recommended setting. Using the latest commit set may lead to another compilation error or something else.*

From 58d196762547c0b0070c4a734543b9e1d8bb0e58 Mon Sep 17 00:00:00 2001
From: nhynes <nhynes@berkeley.edu>
Date: Thu, 8 Mar 2018 17:03:01 -0800
Subject: [PATCH 615/949] Add end-to-end SGX ResNet inference example (#388)

---
 nnvm/examples/sgx/.gitignore                  |   2 +
 nnvm/examples/sgx/Makefile                    | 102 +++++++++++++
 nnvm/examples/sgx/README.md                   |  39 +++++
 nnvm/examples/sgx/app.cc                      | 135 ++++++++++++++++++
 nnvm/examples/sgx/build_model.py              |  74 ++++++++++
 nnvm/examples/sgx/enclave.cc                  |  98 +++++++++++++
 nnvm/examples/sgx/enclave_config.xml          |  11 ++
 nnvm/examples/sgx/enclave_private.pem         |  39 +++++
 nnvm/examples/sgx/model.edl                   |   7 +
 nnvm/examples/sgx/run_example.sh              |   7 +
 nnvm/python/nnvm/top/nn.py                    |   6 +-
 nnvm/tests/python/compiler/test_op_fusion.py  |   2 +-
 nnvm/tests/python/compiler/test_top_level1.py |   2 +-
 13 files changed, 520 insertions(+), 4 deletions(-)
 create mode 100644 nnvm/examples/sgx/.gitignore
 create mode 100644 nnvm/examples/sgx/Makefile
 create mode 100644 nnvm/examples/sgx/README.md
 create mode 100644 nnvm/examples/sgx/app.cc
 create mode 100644 nnvm/examples/sgx/build_model.py
 create mode 100644 nnvm/examples/sgx/enclave.cc
 create mode 100644 nnvm/examples/sgx/enclave_config.xml
 create mode 100644 nnvm/examples/sgx/enclave_private.pem
 create mode 100644 nnvm/examples/sgx/model.edl
 create mode 100755 nnvm/examples/sgx/run_example.sh

diff --git a/nnvm/examples/sgx/.gitignore b/nnvm/examples/sgx/.gitignore
new file mode 100644
index 000000000000..25f8f1058cab
--- /dev/null
+++ b/nnvm/examples/sgx/.gitignore
@@ -0,0 +1,2 @@
+lib/
+bin/
diff --git a/nnvm/examples/sgx/Makefile b/nnvm/examples/sgx/Makefile
new file mode 100644
index 000000000000..490de0aa5ba2
--- /dev/null
+++ b/nnvm/examples/sgx/Makefile
@@ -0,0 +1,102 @@
+# Makefile for example to deploy TVM modules in SGX.
+
+PYTHON ?= python
+
+NNVM_ROOT := $(shell cd ../../; pwd)
+TVM_ROOT := $(NNVM_ROOT)/tvm
+DMLC_CORE_ROOT := $(NNVM_ROOT)/dmlc-core
+
+SGX_SDK ?= /opt/sgxsdk
+SGX_MODE ?= SIM
+SGX_ARCH ?= x64
+SGX_DEBUG ?= 1
+
+sgx_edger8r := $(SGX_SDK)/bin/x64/sgx_edger8r
+sgx_enclave_signer := $(SGX_SDK)/bin/x64/sgx_sign
+
+ifneq ($(SGX_MODE), HW)
+	sgx_sim := _sim
+endif
+urts_library_name := sgx_urts$(sgx_sim)
+trts_library_name := sgx_trts$(sgx_sim)
+tservice_library_name := sgx_tservice$(sgx_sim)
+uservice_library_name := sgx_uae_service$(sgx_sim)
+
+pkg_cflags := -std=c++11 -O2 -fPIC\
+	-I$(NNVM_ROOT)/include\
+	-I$(NNVM_ROOT)\
+	-I$(TVM_ROOT)/include\
+	-I$(TVM_ROOT)/dlpack/include\
+	-I$(DMLC_CORE_ROOT)/include\
+	-DDMLC_LOG_STACK_TRACE=0\
+
+pkg_ldflags := -L$(TVM_ROOT)/lib
+
+enclave_include_paths := -I$(SGX_SDK)/include\
+	-I$(SGX_SDK)/include/tlibc\
+	-I$(SGX_SDK)/include/libcxx\
+	-I$(SGX_SDK)/include/stdc++\
+
+enclave_cflags := -static -nostdinc\
+	-fvisibility=hidden -fpie -fstack-protector-strong\
+	-ffunction-sections -fdata-sections\
+	-DDMLC_CXX11_THREAD_LOCAL=0\
+	$(enclave_include_paths)\
+
+enclave_cxxflags := -nostdinc++ $(enclave_cflags)
+
+enclave_ldflags :=\
+	-Wl,--no-undefined -nostdlib -nodefaultlibs -nostartfiles -L$(SGX_SDK)/lib64\
+	-Wl,--whole-archive -l$(trts_library_name) -Wl,--no-whole-archive\
+	-Wl,--start-group\
+	-lsgx_tstdc -lsgx_tstdcxx -lsgx_tcxx -lsgx_tcrypto -lsgx_tkey_exchange -l$(tservice_library_name)\
+	-Wl,--end-group\
+	-Wl,-Bstatic -Wl,-Bsymbolic -Wl,--no-undefined\
+	-Wl,-pie,-eenclave_entry -Wl,--export-dynamic\
+	-Wl,--defsym,__ImageBase=0 -Wl,--gc-sections
+
+app_cflags := -I$(SGX_SDK)/include -Ilib
+
+app_ldflags := -L$(SGX_SDK)/lib64\
+	-l$(urts_library_name) -l$(uservice_library_name) -lpthread\
+
+.PHONY: clean all
+
+all: lib/model.signed.so bin/run_model
+
+# The code library built by TVM
+lib/deploy_%.o: build_model.py
+	@mkdir -p $(@D)
+	$(PYTHON) build_model.py
+
+# EDL files
+lib/model_%.c: model.edl $(sgx_edger8r)
+	@mkdir -p $(@D)
+	$(sgx_edger8r) $< --trusted-dir $(@D) --untrusted-dir $(@D) --search-path $(SGX_SDK)/include
+
+lib/model_%.o: lib/model_%.c
+	$(CC) $(enclave_cflags) -c $< -o $@
+
+# The enclave library
+lib/model.so: enclave.cc $(TVM_ROOT)/sgx/sgx_runtime.cc lib/model_t.o lib/deploy_lib.o
+	$(CXX) $^ -o $@ $(pkg_cflags) $(pkg_ldflags) $(enclave_cxxflags) $(enclave_ldflags)\
+		-Wl,--format=binary -Wl,lib/deploy_graph.json -Wl,lib/deploy_params.bin -Wl,--format=default
+
+# The signed enclave
+lib/model.signed.so: lib/model.so enclave_config.xml
+	$(sgx_enclave_signer) sign -key enclave_private.pem -enclave $< -out $@ -config enclave_config.xml
+
+# An app that runs the enclave
+bin/run_model: app.cc lib/model_u.o
+	@mkdir -p $(@D)
+	$(CXX) $^ -o $@ $(app_cflags) $(app_ldflags)
+
+# Debugging binary that runs TVM without SGX
+bin/run_model_nosgx: enclave.cc $(TVM_ROOT)/sgx/sgx_runtime.cc lib/deploy_lib.o
+	@mkdir -p $(@D)
+	$(CXX) $^ -o $@ $(pkg_cflags) $(pkg_ldflags)\
+		-Wl,--format=binary -Wl,lib/deploy_graph.json -Wl,lib/deploy_params.bin -Wl,--format=default
+
+
+clean:
+	rm -rf lib bin
diff --git a/nnvm/examples/sgx/README.md b/nnvm/examples/sgx/README.md
new file mode 100644
index 000000000000..97ec1fcfca57
--- /dev/null
+++ b/nnvm/examples/sgx/README.md
@@ -0,0 +1,39 @@
+# TVM in Intel SGX Example
+
+This application demonstrates running a ResNet18 using NNVM inside of an
+[Intel SGX](https://software.intel.com/en-us/blogs/2013/09/26/protecting-application-secrets-with-intel-sgx) trusted computing environment.
+
+## Prerequisites
+
+1. A GNU/Linux environment
+2. NNVM, TVM compiled with LLVM, and their corresponding Python modules
+3. The [Linux SGX SDK](https://github.com/intel/linux-sgx) [link to pre-built libraries](https://01.org/intel-software-guard-extensions/downloads)
+4. `pip install --user mxnet pillow`
+
+## Running the example
+
+`SGX_SDK=/path/to/sgxsdk bash run_example.sh`
+
+If everything goes well, you should see a lot of build messages and below them
+the text `It's a tabby!`.
+
+## High-level overview
+
+First of all, it helps to think of an SGX enclave as a library that can be called
+to perform trusted computation.
+In this library, one can use other libraries like TVM.
+
+Building this example performs the following steps:
+
+1. Downloads a pre-trained MXNet ResNet and a
+   [test image](https://github.com/BVLC/caffe/blob/master/examples/images/cat.jpg)
+2. Converts the ResNet to an NNVM graph + library
+3. Links the graph JSON definition, params, and runtime library into into an SGX
+   enclave along with some code that performs inference.
+4. Compiles and runs an executable that loads the enclave and requests that it perform
+   inference on the image.
+   which invokes the TVM module.
+
+For more information on building, please refer to the `Makefile`.  
+For more information on the TVM module, please refer to `../howto_deploy`.  
+For more in formation on SGX enclaves, please refer to the [SGX Enclave Demo](https://github.com/intel/linux-sgx/tree/master/SampleCode/SampleEnclave/)
diff --git a/nnvm/examples/sgx/app.cc b/nnvm/examples/sgx/app.cc
new file mode 100644
index 000000000000..001c0d71d69e
--- /dev/null
+++ b/nnvm/examples/sgx/app.cc
@@ -0,0 +1,135 @@
+#include <cstdio>
+#include <sstream>
+#include <fstream>
+#include <iostream>
+
+#include "sgx_urts.h"
+#include "sgx_eid.h"
+#include "model_u.h"
+
+#define TOKEN_FILENAME   "bin/enclave.token"
+#define ENCLAVE_FILENAME "lib/model.signed.so"
+
+sgx_enclave_id_t global_eid = 0;  // global EID shared by multiple threads
+
+typedef struct _sgx_errlist_t {
+  sgx_status_t err;
+  const char *msg;
+} sgx_errlist_t;
+
+/* Error code returned by sgx_create_enclave */
+static sgx_errlist_t sgx_errlist[] = {
+  { SGX_ERROR_DEVICE_BUSY, "SGX device was busy." },
+  { SGX_ERROR_ENCLAVE_FILE_ACCESS, "Can't open enclave file." },
+  { SGX_ERROR_ENCLAVE_LOST, "Power transition occurred." },
+  { SGX_ERROR_INVALID_ATTRIBUTE, "Enclave was not authorized." },
+  { SGX_ERROR_INVALID_ENCLAVE, "Invalid enclave image." },
+  { SGX_ERROR_INVALID_ENCLAVE_ID, "Invalid enclave identification." },
+  { SGX_ERROR_INVALID_METADATA, "Invalid enclave metadata." },
+  { SGX_ERROR_INVALID_PARAMETER, "Invalid parameter." },
+  { SGX_ERROR_INVALID_SIGNATURE, "Invalid enclave signature." },
+  { SGX_ERROR_INVALID_VERSION, "Enclave version was invalid." },
+  { SGX_ERROR_MEMORY_MAP_CONFLICT, "Memory map conflicted." },
+  { SGX_ERROR_NO_DEVICE, "Invalid SGX device." },
+  { SGX_ERROR_OUT_OF_EPC, "Out of EPC memory." },
+  { SGX_ERROR_OUT_OF_MEMORY, "Out of memory." },
+  { SGX_ERROR_UNEXPECTED, "Unexpected error occurred." },
+};
+
+/* Check error conditions for loading enclave */
+void print_error_message(sgx_status_t status)
+{
+  size_t idx = 0;
+  size_t ttl = sizeof sgx_errlist/sizeof sgx_errlist[0];
+
+  for (idx = 0; idx < ttl; idx++) {
+    if(status == sgx_errlist[idx].err) {
+      printf("Error: %s\n", sgx_errlist[idx].msg);
+      break;
+    }
+  }
+
+  if (idx == ttl)
+    printf("Error code is 0x%X. Please refer to the \"Intel SGX SDK Developer Reference\" for more details.\n", status);
+}
+
+/* Initialize the enclave:
+ *   Step 1: try to retrieve the launch token saved by last transaction
+ *   Step 2: call sgx_create_enclave to initialize an enclave instance
+ *   Step 3: save the launch token if it is updated
+ */
+int initialize_enclave(void)
+{
+  sgx_launch_token_t token = {0};
+  sgx_status_t sgx_status = SGX_ERROR_UNEXPECTED;
+  int updated = 0;
+
+  /* Step 1: try to retrieve the launch token saved by last transaction
+   *     if there is no token, then create a new one.
+   */
+  FILE *fp = fopen(TOKEN_FILENAME, "rb");
+  if (fp == NULL && (fp = fopen(TOKEN_FILENAME, "wb")) == NULL) {
+    printf("Warning: Failed to create/open the launch token file \"%s\".\n", TOKEN_FILENAME);
+    return -1;
+  }
+
+  /* read the token from saved file */
+  size_t read_num = fread(token, 1, sizeof(sgx_launch_token_t), fp);
+  if (read_num != 0 && read_num != sizeof(sgx_launch_token_t)) {
+    /* if token is invalid, clear the buffer */
+    memset(&token, 0x0, sizeof(sgx_launch_token_t));
+    printf("Warning: Invalid launch token read from \"%s\".\n", TOKEN_FILENAME);
+  }
+
+  /* Step 2: call sgx_create_enclave to initialize an enclave instance */
+  /* Debug Support: set 2nd parameter to 1 */
+  sgx_status = sgx_create_enclave(ENCLAVE_FILENAME, SGX_DEBUG_FLAG, &token, &updated, &global_eid, NULL);
+  if (sgx_status != SGX_SUCCESS) {
+    print_error_message(sgx_status);
+    if (fp != NULL) fclose(fp);
+    return -1;
+  }
+
+  /* Step 3: save the launch token if it is updated */
+  if (updated == 0 || fp == NULL) {
+    /* if the token is not updated, or file handler is invalid, do not perform saving */
+    if (fp != NULL) fclose(fp);
+    return 0;
+  }
+
+  /* reopen the file with write capablity */
+  fp = freopen(TOKEN_FILENAME, "wb", fp);
+  if (fp == NULL) return 0;
+  size_t write_num = fwrite(token, 1, sizeof(sgx_launch_token_t), fp);
+  if (write_num != sizeof(sgx_launch_token_t))
+    printf("Warning: Failed to save launch token to \"%s\".\n", TOKEN_FILENAME);
+  fclose(fp);
+  return 0;
+}
+
+int SGX_CDECL main(int argc, char *argv[]) {
+  if(initialize_enclave() < 0){
+    printf("Failed to initialize enclave.\n");
+    return -1;
+  }
+
+  std::ifstream f_img("bin/cat.bin", std::ios::binary);
+  std::string img(static_cast<std::stringstream const&>(
+                  std::stringstream() << f_img.rdbuf()).str());
+
+  unsigned predicted_class;
+  sgx_status_t sgx_status = SGX_ERROR_UNEXPECTED;
+  sgx_status = ecall_infer(global_eid, &predicted_class, img.c_str());
+  if (sgx_status != SGX_SUCCESS) {
+    print_error_message(sgx_status);
+  }
+
+  sgx_destroy_enclave(global_eid);
+  if (predicted_class == 281) {
+    std::cout << "It's a tabby!" << std::endl;
+    return 0;
+  }
+  std::cerr << "Inference failed! Predicted class: " <<
+    predicted_class << std::endl;
+  return 1;
+}
diff --git a/nnvm/examples/sgx/build_model.py b/nnvm/examples/sgx/build_model.py
new file mode 100644
index 000000000000..df3220afa840
--- /dev/null
+++ b/nnvm/examples/sgx/build_model.py
@@ -0,0 +1,74 @@
+"""Creates a neural network graph module, the system library, and params.
+Heavily inspired by tutorials/from_mxnet.py
+"""
+from __future__ import print_function
+import ast
+import os
+from os import path as osp
+import tempfile
+
+import mxnet as mx
+from mxnet.gluon.model_zoo.vision import get_model
+from mxnet.gluon.utils import download
+import nnvm
+import nnvm.compiler
+import numpy as np
+from PIL import Image
+import tvm
+
+
+EXAMPLE_ROOT = osp.abspath(osp.join(osp.dirname(__file__)))
+BIN_DIR = osp.join(EXAMPLE_ROOT, 'bin')
+LIB_DIR = osp.join(EXAMPLE_ROOT, 'lib')
+
+TVM_TARGET = 'llvm --system-lib'
+
+
+def _download_model_and_image(out_dir):
+    mx_model = get_model('resnet18_v1', pretrained=True)
+
+    img_path = osp.join(out_dir, 'cat.png')
+    bin_img_path = osp.join(out_dir, 'cat.bin')
+    download(
+        'https://github.com/dmlc/mxnet.js/blob/master/data/cat.png?raw=true',
+        img_path)
+    img = Image.open(img_path).resize((224, 224))
+    img = _transform_image(img)
+    img.astype('float32').tofile(bin_img_path)
+    shape_dict = {'data': img.shape}
+
+    return mx_model, shape_dict
+
+
+def _transform_image(image):
+    image = np.array(image) - np.array([123., 117., 104.])
+    image /= np.array([58.395, 57.12, 57.375])
+    image = image.transpose((2, 0, 1))
+    image = image[np.newaxis, :]
+    return image
+
+
+def main():
+    # load the model, input image, and imagenet classes
+    mx_model, shape_dict = _download_model_and_image(BIN_DIR)
+
+    # convert the model, add a softmax
+    sym, params = nnvm.frontend.from_mxnet(mx_model)
+    sym = nnvm.sym.softmax(sym)
+
+    # build the graph
+    graph, lib, params = nnvm.compiler.build(
+        sym, TVM_TARGET, shape_dict, params=params)
+
+    # save the built graph
+    if not osp.isdir(LIB_DIR):
+        os.mkdir(LIB_DIR)
+    lib.save(osp.join(LIB_DIR, 'deploy_lib.o'))
+    with open(osp.join(LIB_DIR, 'deploy_graph.json'), 'w') as f_graph_json:
+        f_graph_json.write(graph.json())
+    with open(osp.join(LIB_DIR, 'deploy_params.bin'), 'wb') as f_params:
+        f_params.write(nnvm.compiler.save_param_dict(params))
+
+
+if __name__ == '__main__':
+    main()
diff --git a/nnvm/examples/sgx/enclave.cc b/nnvm/examples/sgx/enclave.cc
new file mode 100644
index 000000000000..f1210c91e046
--- /dev/null
+++ b/nnvm/examples/sgx/enclave.cc
@@ -0,0 +1,98 @@
+#include <string.h>
+#include <dlpack/dlpack.h>
+#include <tvm/runtime/module.h>
+#include <tvm/runtime/c_runtime_api.h>
+#include <tvm/runtime/registry.h>
+#include <tvm/runtime/packed_func.h>
+#include "tvm/src/runtime/graph/graph_runtime.cc"
+#ifndef _LIBCPP_SGX_CONFIG
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include "tvm/src/runtime/file_util.cc"
+#endif
+
+// the statically linked graph json and params
+extern char _binary_lib_deploy_params_bin_start[];
+extern char _binary_lib_deploy_params_bin_end[];
+extern char _binary_lib_deploy_graph_json_start[];
+extern char _binary_lib_deploy_graph_json_end[];
+
+int RunInference(const char* img) {
+  tvm::runtime::Module graph_lib =
+    (*tvm::runtime::Registry::Get("module._GetSystemLib"))();
+
+  size_t graph_json_size = ((size_t)_binary_lib_deploy_graph_json_end -
+                            (size_t)_binary_lib_deploy_graph_json_start);
+  size_t graph_params_size = ((size_t)_binary_lib_deploy_params_bin_end -
+                              (size_t)_binary_lib_deploy_params_bin_start);
+  std::string graph_json(_binary_lib_deploy_graph_json_start, graph_json_size);
+  std::string graph_params(_binary_lib_deploy_params_bin_start, graph_params_size);
+
+  int device_type = kDLCPU;
+  int device_id = 0;
+
+  TVMContext ctx;
+  ctx.device_type = static_cast<DLDeviceType>(device_type);
+  ctx.device_id = device_id;
+  std::shared_ptr<tvm::runtime::GraphRuntime> graph_rt =
+    std::make_shared<tvm::runtime::GraphRuntime>();
+
+  graph_rt->Init(graph_json, graph_lib, ctx);
+  graph_rt->LoadParams(graph_params);
+
+  DLTensor* input;
+  DLTensor* output;
+  int ndim = 2;
+  int dtype_code = kDLFloat;
+  int dtype_bits = 32;
+  int dtype_lanes = 1;
+
+  int batch_size = 1;
+  int64_t input_shape[4] = {batch_size, 3, 224, 224};
+  int64_t output_shape[1] = {1000 /* num_classes */};
+  TVMArrayAlloc(input_shape, 4 /* ndim */, dtype_code, dtype_bits, dtype_lanes,
+                device_type, device_id, &input);
+  TVMArrayAlloc(output_shape, 1, dtype_code, dtype_bits, dtype_lanes,
+                device_type, device_id, &output);
+  memcpy(input->data, img, sizeof(float)*batch_size*3*224*224);
+
+  graph_rt->SetInput(graph_rt->GetInputIndex("data"), input);
+  graph_rt->Run();
+  graph_rt->GetOutput(0, output);
+
+  float max_prob = 0;
+  unsigned max_class = -1;
+  for (int i = 0; i < 1000; ++i) {
+    float p = static_cast<float*>(output->data)[i];
+    if (p > max_prob) {
+      max_prob = p;
+      max_class = i;
+    }
+  }
+
+  return max_class;
+}
+
+
+extern "C" {
+int ecall_infer(const char* img) {
+  return RunInference(img);
+}
+}
+
+#ifndef _LIBCPP_SGX_CONFIG
+int main(void) {
+  std::ifstream f_img("bin/cat.bin", std::ios::binary);
+  std::string img(static_cast<std::stringstream const&>(
+                  std::stringstream() << f_img.rdbuf()).str());
+  unsigned predicted_class = RunInference(img.c_str());
+  if (predicted_class == 281) {
+    std::cout << "It's a tabby!" << std::endl;
+    return 0;
+  }
+  std::cerr << "Inference failed! Predicted class: " <<
+    predicted_class << std::endl;
+  return -1;
+}
+#endif
diff --git a/nnvm/examples/sgx/enclave_config.xml b/nnvm/examples/sgx/enclave_config.xml
new file mode 100644
index 000000000000..923d58ec8bf3
--- /dev/null
+++ b/nnvm/examples/sgx/enclave_config.xml
@@ -0,0 +1,11 @@
+<EnclaveConfiguration>
+  <ProdID>0</ProdID>
+  <ISVSVN>0</ISVSVN>
+  <StackMaxSize>0x2000</StackMaxSize>
+  <HeapMaxSize>0x6794000</HeapMaxSize>
+  <TCSNum>1</TCSNum>
+  <TCSPolicy>1</TCSPolicy>
+  <DisableDebug>0</DisableDebug>
+  <MiscSelect>0</MiscSelect>
+  <MiscMask>0xFFFFFFFF</MiscMask>
+</EnclaveConfiguration>
diff --git a/nnvm/examples/sgx/enclave_private.pem b/nnvm/examples/sgx/enclave_private.pem
new file mode 100644
index 000000000000..529d07be3574
--- /dev/null
+++ b/nnvm/examples/sgx/enclave_private.pem
@@ -0,0 +1,39 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIG4gIBAAKCAYEAroOogvsj/fZDZY8XFdkl6dJmky0lRvnWMmpeH41Bla6U1qLZ
+AmZuyIF+mQC/cgojIsrBMzBxb1kKqzATF4+XwPwgKz7fmiddmHyYz2WDJfAjIveJ
+ZjdMjM4+EytGlkkJ52T8V8ds0/L2qKexJ+NBLxkeQLfV8n1mIk7zX7jguwbCG1Pr
+nEMdJ3Sew20vnje+RsngAzdPChoJpVsWi/K7cettX/tbnre1DL02GXc5qJoQYk7b
+3zkmhz31TgFrd9VVtmUGyFXAysuSAb3EN+5VnHGr0xKkeg8utErea2FNtNIgua8H
+ONfm9Eiyaav1SVKzPHlyqLtcdxH3I8Wg7yqMsaprZ1n5A1v/levxnL8+It02KseD
+5HqV4rf/cImSlCt3lpRg8U5E1pyFQ2IVEC/XTDMiI3c+AR+w2jSRB3Bwn9zJtFlW
+KHG3m1xGI4ck+Lci1JvWWLXQagQSPtZTsubxTQNx1gsgZhgv1JHVZMdbVlAbbRMC
+1nSuJNl7KPAS/VfzAgEDAoIBgHRXxaynbVP5gkO0ug6Qw/E27wzIw4SmjsxG6Wpe
+K7kfDeRskKxESdsA/xCrKkwGwhcx1iIgS5+Qscd1Yg+1D9X9asd/P7waPmWoZd+Z
+AhlKwhdPsO7PiF3e1AzHhGQwsUTt/Y/aSI1MpHBvy2/s1h9mFCslOUxTmWw0oj/Q
+ldIEgWeNR72CE2+jFIJIyml6ftnb6qzPiga8Bm48ubKh0kvySOqnkmnPzgh+JBD6
+JnBmtZbfPT97bwTT+N6rnPqOOApvfHPf15kWI8yDbprG1l4OCUaIUH1AszxLd826
+5IPM+8gINLRDP1MA6azECPjTyHXhtnSIBZCyWSVkc05vYmNXYUNiXWMajcxW9M02
+wKzFELO8NCEAkaTPxwo4SCyIjUxiK1LbQ9h8PSy4c1+gGP4LAMR8xqP4QKg6zdu9
+osUGG/xRe/uufgTBFkcjqBHtK5L5VI0jeNIUAgW/6iNbYXjBMJ0GfauLs+g1VsOm
+WfdgXzsb9DYdMa0OXXHypmV4GwKBwQDUwQj8RKJ6c8cT4vcWCoJvJF00+RFL+P3i
+Gx2DLERxRrDa8AVGfqaCjsR+3vLgG8V/py+z+dxZYSqeB80Qeo6PDITcRKoeAYh9
+xlT3LJOS+k1cJcEmlbbO2IjLkTmzSwa80fWexKu8/Xv6vv15gpqYl1ngYoqJM3pd
+vzmTIOi7MKSZ0WmEQavrZj8zK4endE3v0eAEeQ55j1GImbypSf7Idh7wOXtjZ7WD
+Dg6yWDrri+AP/L3gClMj8wsAxMV4ZR8CgcEA0fzDHkFa6raVOxWnObmRoDhAtE0a
+cjUj976NM5yyfdf2MrKy4/RhdTiPZ6b08/lBC/+xRfV3xKVGzacm6QjqjZrUpgHC
+0LKiZaMtccCJjLtPwQd0jGQEnKfMFaPsnhOc5y8qVkCzVOSthY5qhz0XNotHHFmJ
+gffVgB0iqrMTvSL7IA2yqqpOqNRlhaYhNl8TiFP3gIeMtVa9rZy31JPgT2uJ+kfo
+gV7sdTPEjPWZd7OshGxWpT6QfVDj/T9T7L6tAoHBAI3WBf2DFvxNL2KXT2QHAZ9t
+k3imC4f7U+wSE6zILaDZyzygA4RUbwG0gv8/TJVn2P/Eynf76DuWHGlaiLWnCbSz
+Az2DHBQBBaku409zDQym3j1ugMRjzzSQWzJg0SIyBH3hTmnYcn3+Uqcp/lEBvGW6
+O+rsXFt3pukqJmIV8HzLGGaLm62BHUeZf3dyWm+i3p/hQAL7Xvu04QW70xuGqdr5
+afV7p5eaeQIJXyGQJ0eylV/90+qxjMKiB1XYg6WYvwKBwQCL/ddpgOdHJGN8uRom
+e7Zq0Csi3hGheMKlKbN3vcxT5U7MdyHtTZZOJbTvxKNNUNYH/8uD+PqDGNneb29G
+BfGzvI3EASyLIcGZF3OhKwZd0jUrWk2y7Vhob91jwp2+t73vdMbkKyI4mHOuXvGv
+fg95si9oO7EBT+Oqvhccd2J+F1IVXncccYnF4u5ZGWt5lLewN/pVr7MjjykeaHqN
+t+rfnQam2psA6fL4zS2zTmZPzR2tnY8Y1GBTi0Ko1OKd1HMCgcAb5cB/7/AQlhP9
+yQa04PLH9ygQkKKptZp7dy5WcWRx0K/hAHRoi2aw1wZqfm7VBNu2SLcs90kCCCxp
+6C5sfJi6b8NpNbIPC+sc9wsFr7pGo9SFzQ78UlcWYK2Gu2FxlMjonhka5hvo4zvg
+WxlpXKEkaFt3gLd92m/dMqBrHfafH7VwOJY2zT3WIpjwuk0ZzmRg5p0pG/svVQEH
+NZmwRwlopysbR69B/n1nefJ84UO50fLh5s5Zr3gBRwbWNZyzhXk=
+-----END RSA PRIVATE KEY-----
diff --git a/nnvm/examples/sgx/model.edl b/nnvm/examples/sgx/model.edl
new file mode 100644
index 000000000000..fd45cc318dbf
--- /dev/null
+++ b/nnvm/examples/sgx/model.edl
@@ -0,0 +1,7 @@
+enclave {
+    from "sgx_tstdc.edl" import sgx_thread_wait_untrusted_event_ocall, sgx_thread_set_untrusted_event_ocall, sgx_thread_setwait_untrusted_events_ocall, sgx_thread_set_multiple_untrusted_events_ocall;
+
+    trusted {
+        public unsigned ecall_infer([user_check] const char* img);
+    };
+};
diff --git a/nnvm/examples/sgx/run_example.sh b/nnvm/examples/sgx/run_example.sh
new file mode 100755
index 000000000000..4fd0e3f36a63
--- /dev/null
+++ b/nnvm/examples/sgx/run_example.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+sgx_sdk=${SGX_SDK:=/opt/sgxsdk}
+
+make
+echo "========================="
+LD_LIBRARY_PATH="$sgx_sdk/lib64":${LD_LIBRARY_PATH} bin/run_model
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index 330e5739b30d..f6dd6f562d60 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -182,8 +182,10 @@ def compute_upsampling(attrs, inputs, _):
     """Compute definition of upsampling"""
     scale = attrs.get_int("scale")
     layout = attrs["layout"]
-    assert layout == "NCHW" or layout == "NHWC"
-    return topi.nn.upsampling(inputs[0], scale, layout)
+    if layout:
+        assert layout == "NCHW" or layout == "NHWC"
+        return topi.nn.upsampling(inputs[0], scale, layout)
+    return topi.nn.upsampling(inputs[0], scale)
 
 @reg.register_schedule("upsampling")
 def schedule_upsampling(_, outs, target):
diff --git a/nnvm/tests/python/compiler/test_op_fusion.py b/nnvm/tests/python/compiler/test_op_fusion.py
index 4c079b41bb81..f33e18197840 100644
--- a/nnvm/tests/python/compiler/test_op_fusion.py
+++ b/nnvm/tests/python/compiler/test_op_fusion.py
@@ -1,7 +1,7 @@
 import nnvm
 import numpy as np
 import tvm
-import topi
+import topi.testing
 from tvm.contrib import graph_runtime
 from nnvm import symbol as sym
 from nnvm.compiler import graph_util, graph_attr
diff --git a/nnvm/tests/python/compiler/test_top_level1.py b/nnvm/tests/python/compiler/test_top_level1.py
index fc2776f2b01c..abe30d1ec8d0 100644
--- a/nnvm/tests/python/compiler/test_top_level1.py
+++ b/nnvm/tests/python/compiler/test_top_level1.py
@@ -1,7 +1,7 @@
 import numpy as np
 import tvm
 from tvm.contrib import graph_runtime
-import topi
+import topi.testing
 import nnvm.symbol as sym
 import nnvm.compiler
 from nnvm.testing.config import ctx_list

From b84a882d66116bf6f960332c901b5ab14cedef92 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 10 Mar 2018 20:47:07 -0800
Subject: [PATCH 616/949] [OP] Experimental assign op (#389)

---
 nnvm/python/nnvm/top/tensor.py                |  6 ++
 nnvm/src/compiler/graph_fuse.cc               | 92 ++++++++++++++++++-
 nnvm/src/pass/plan_memory.cc                  |  6 +-
 nnvm/src/top/tensor/elemwise.cc               |  2 +
 nnvm/src/top/tensor/state_op.cc               | 54 +++++++++++
 nnvm/tests/python/compiler/test_top_assign.py | 41 +++++++++
 6 files changed, 196 insertions(+), 5 deletions(-)
 create mode 100644 nnvm/src/top/tensor/state_op.cc
 create mode 100644 nnvm/tests/python/compiler/test_top_assign.py

diff --git a/nnvm/python/nnvm/top/tensor.py b/nnvm/python/nnvm/top/tensor.py
index 565733775209..4f9d3ba9f36f 100644
--- a/nnvm/python/nnvm/top/tensor.py
+++ b/nnvm/python/nnvm/top/tensor.py
@@ -42,6 +42,12 @@ def _compute(attrs, x, _):
 _fschedule_broadcast = _fschedule_injective
 _fschedule_elemwise = _fschedule_injective
 
+# Assign requires special treatment in the compiler
+# The compute and schedule are designed as
+# copy from rhs to output
+reg.register_pattern("_assign", OpPattern.OPAQUE)
+reg.register_schedule("_assign", _fschedule_broadcast)
+
 # copy
 reg.register_pattern("copy", OpPattern.ELEMWISE)
 reg.register_schedule("copy", _fschedule_broadcast)
diff --git a/nnvm/src/compiler/graph_fuse.cc b/nnvm/src/compiler/graph_fuse.cc
index 8e0578acf9c3..d4e668972593 100644
--- a/nnvm/src/compiler/graph_fuse.cc
+++ b/nnvm/src/compiler/graph_fuse.cc
@@ -173,6 +173,48 @@ NNVM_REGISTER_PASS(GraphFusePartition)
 .depend_graph_attr("shape")
 .depend_graph_attr("dtype");
 
+
+// Decorate the result of PlanMemory
+// This function does two things:
+// - Give separate memory to each variable
+// - Tie the memory of output/lhs in assign node properly
+//   so the execution of assign can have side effect.
+nnvm::Graph DecorateMemoryPlan(
+    nnvm::Graph g,
+    const std::vector<int>& assign_flag) {
+  // setup ref counter
+  const IndexedGraph& idx = g.indexed_graph();
+  StorageVector storage_vec = g.MoveCopyAttr<StorageVector>("storage_id");
+  g.attrs.erase("storage_allocated_bytes");
+  g.attrs.erase("storage_inplace_index");
+  size_t num_not_allocated = g.MoveCopyAttr<size_t>(
+      "storage_num_not_allocated");
+  CHECK_EQ(num_not_allocated, 0U)
+      << "Can only build inference graph with all statically allocated memory";
+
+  // reassign variable id so that they are different.
+  int max_id = 0;
+  for (size_t i = 0; i < storage_vec.size(); ++i) {
+    max_id = std::max(storage_vec[i] + 1, max_id);
+  }
+  for (uint32_t nid : idx.input_nodes()) {
+    storage_vec[idx.entry_id(nid, 0)] = max_id++;
+  }
+  // tie up the assign node storage properly
+  for (uint32_t nid = 0 ; nid < idx.num_nodes(); ++nid) {
+    if (assign_flag[nid] == 0) continue;
+    const auto& inode = idx[nid];
+    int var_storage_id = storage_vec[idx.entry_id(inode.inputs[0])];
+    storage_vec[idx.entry_id(nid, 0)] = var_storage_id;
+
+    if (assign_flag[nid] == 2) {
+      storage_vec[idx.entry_id(inode.inputs[1])] = var_storage_id;
+    }
+  }
+  g.attrs["storage_id"] = std::make_shared<any>(std::move(storage_vec));
+  return g;
+}
+
 struct INodeEntryHash {
   size_t operator()(const IndexedGraph::NodeEntry& e) const {
     return e.node_id;
@@ -218,8 +260,12 @@ nnvm::Graph GraphFuseCompile(nnvm::Graph g) {
       g.GetAttr<std::vector<TOpPattern> >("pattern");
   std::string target = g.GetAttr<std::string>("target");
   std::string target_host;
-  if (g.HasAttr("target_host"))
+
+  if (g.HasAttr("target_host")) {
     target_host = g.GetAttr<std::string>("target_host");
+  }
+  // specially handle assign
+  const nnvm::Op* assign_op = nnvm::Op::Get("_assign");
 
   std::vector<FuseEntry> fuse_vec(idx.num_nodes());
   // setup inputs and placeholder.
@@ -229,7 +275,8 @@ nnvm::Graph GraphFuseCompile(nnvm::Graph g) {
     CHECK_GE(group_vec[nid], 0);
     int root_id = group_vec[nid];
     FuseEntry& fe = fuse_vec[root_id];
-    fe.flatten_data = (pattern_vec[root_id] == kElemWise);
+    fe.flatten_data = (pattern_vec[root_id] == kElemWise ||
+                       inode.source->op() == assign_op);
     for (const auto& e : inode.inputs) {
       if (group_vec[e.node_id] != root_id && fe.imap.count(e) == 0) {
         Array<Expr> shape;
@@ -331,8 +378,9 @@ nnvm::Graph GraphFuseCompile(nnvm::Graph g) {
       }
     }
   }
-  // Rebuild the fused graph
+
   const nnvm::Op* tvm_op = nnvm::Op::Get("tvm_op");
+
   std::unordered_map<uint32_t, nnvm::NodePtr> old_new;
   for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
     const auto& inode = idx[nid];
@@ -345,6 +393,8 @@ nnvm::Graph GraphFuseCompile(nnvm::Graph g) {
     }
     int root_id = group_vec[nid];
     if (static_cast<int>(nid) != root_id) continue;
+
+    // Handle normal op
     FuseEntry& fe = fuse_vec[root_id];
     const IndexedGraph& subidx = fe.subgraph.indexed_graph();
     nnvm::NodePtr np = nnvm::Node::Create();
@@ -385,13 +435,48 @@ nnvm::Graph GraphFuseCompile(nnvm::Graph g) {
         nnvm::NodeEntry{it->second, e.index, e.version});
   }
 
+  // Reference counter of each op node
+  // For now, always store result when an op is referred more than once.
+  std::vector<uint32_t> ref_count = GetNodeRefCounts(idx);
+  for (const auto& e : idx.outputs()) {
+    // this line will realize all the outputs
+    ref_count[e.node_id] += 1;
+  }
+
   const IndexedGraph& new_idx = ret.indexed_graph();
+
+  // Handling assign:
+  //
+  //  assign is a special operator that mutates the variable.
+  //  Currently assign is implemented as output = copy(input[1])
+  //  Then we run DecorageMemoryPlan to force
+  //  output.storage = input[0].storage
+  //
+  std::vector<int> assign_flag(new_idx.num_nodes(), 0);
   ShapeVector new_shape_vec = ShapeVector(new_idx.num_node_entries(), TShape());
   DTypeVector new_dtype_vec = DTypeVector(new_idx.num_node_entries());
   std::vector<std::string> new_dltype_vec(new_idx.num_node_entries());
+
   for (const auto& kv : old_new) {
     uint32_t nid = kv.first;
     const auto& inode = idx[nid];
+    uint32_t new_nid = new_idx.node_id(kv.second.get());
+    if (inode.source->op() == assign_op) {
+      // Check if rhs of assign can be comute inplace
+      // If yes, we can simply set that memory to be assign target
+      // and change assign to nop
+      const IndexedGraph::NodeEntry& rhs = inode.inputs[1];
+      if (ref_count[rhs.node_id] <= 1 &&
+          !(idx[rhs.node_id].source->is_variable()) &&
+          pattern_vec[group_vec[rhs.node_id]] <= kBroadcast) {
+        assign_flag[new_nid] = 2;
+        TVMOpParam& param = dmlc::get<TVMOpParam>(kv.second->attrs.parsed);
+        param.func_name = "__nop";
+        param.UpdateDict(&(kv.second->attrs.dict));
+      } else {
+        assign_flag[new_nid] = 1;
+      }
+    }
     for (uint32_t i = 0; i < inode.source->num_outputs(); ++i) {
       uint32_t new_eid = new_idx.entry_id(new_idx.node_id(kv.second.get()), i);
       uint32_t old_eid = idx.entry_id(nid, i);
@@ -409,6 +494,7 @@ nnvm::Graph GraphFuseCompile(nnvm::Graph g) {
   tvm::runtime::Module module = fbuild(func_list, target, target_host);
   ret.attrs["module"] = std::make_shared<any>(std::move(module));
   ret = nnvm::ApplyPass(ret, "PlanMemory");
+  ret = DecorateMemoryPlan(ret, assign_flag);
   return ret;
 }
 
diff --git a/nnvm/src/pass/plan_memory.cc b/nnvm/src/pass/plan_memory.cc
index f652352f64e8..b1f2a37d4820 100644
--- a/nnvm/src/pass/plan_memory.cc
+++ b/nnvm/src/pass/plan_memory.cc
@@ -224,10 +224,11 @@ size_t AllocMemory(const Graph& ret, const IndexedGraph& idx,
     for (auto rit = eids.rbegin(); rit != eids.rend(); ++rit) {
         uint32_t eid = rit->second;
         auto sid = allocator->Request(dev_id, dtype_vec[eid], shape_vec[eid], nid);
-        storage_ref_count[sid] = entry_ref_count[eid];
+        if (sid >= 0) {
+          storage_ref_count[sid] = entry_ref_count[eid];
+        }
         storage[eid] = sid;
     }
-
     // check if certain inputs is ignored.
     static auto& fignore_inputs = Op::GetAttr<FIgnoreInputs>("FIgnoreInputs");
     std::vector<uint32_t> ignore_inputs;
@@ -330,6 +331,7 @@ Graph PlanMemory(Graph ret) {
       AllocMemory(ret, idx, node_range, &storage_vec, &storage_inplace_index,
                   ref_count, &allocator);
     size_t storage_allocated_bytes = allocator.TotalAllocBytes();
+
     // Choose the plan which leads to minimal memory usage
     if (min_allocated_bytes > storage_allocated_bytes) {
       ret.attrs["storage_id"] = std::make_shared<any>(std::move(storage_vec));
diff --git a/nnvm/src/top/tensor/elemwise.cc b/nnvm/src/top/tensor/elemwise.cc
index ac66e0c3f70e..d23d9c4f0a3f 100644
--- a/nnvm/src/top/tensor/elemwise.cc
+++ b/nnvm/src/top/tensor/elemwise.cc
@@ -17,8 +17,10 @@
 
 namespace nnvm {
 namespace top {
+
 using namespace tvm;
 using namespace nnvm::compiler;
+
 // undefined op
 NNVM_REGISTER_ELEMWISE_UNARY_OP(__undef__)
 .describe(R"code(undefined op.
diff --git a/nnvm/src/top/tensor/state_op.cc b/nnvm/src/top/tensor/state_op.cc
new file mode 100644
index 000000000000..f275cf309511
--- /dev/null
+++ b/nnvm/src/top/tensor/state_op.cc
@@ -0,0 +1,54 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file state_op.cc
+ * \brief Experimental operators
+ *   Currently we only support assign
+ */
+#include <nnvm/op.h>
+#include <nnvm/node.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/compiler/op_attr_types.h>
+#include <nnvm/top/tensor.h>
+#include <topi/elemwise.h>
+#include "../op_common.h"
+#include "../elemwise_op_common.h"
+
+namespace nnvm {
+namespace top {
+
+using namespace tvm;
+using namespace nnvm::compiler;
+
+NNVM_REGISTER_OP(_assign)
+.describe(R"doc(Assign rhs to the lhs.
+
+lhs must be a Variable.
+This is an experimental operator.
+
+)doc" NNVM_ADD_FILELINE)
+.set_num_inputs(2)
+.set_num_outputs(1)
+.set_attr<FMutateInputs>(
+  "FMutateInputs", [](const NodeAttrs& attrs) {
+    return std::vector<uint32_t>{0};
+})
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    // This implementation is needed for the special
+    // logic handling assign in the compiler
+    // It simply copies the result of rhs the output
+    // The later decoration in compiler will change
+    // the memory assignment of assign to tie
+    // the lhs to the output.
+    return Array<Tensor>{ topi::identity(inputs[1]) };
+})
+.set_attr<FInferShape>("FInferShape", SameShape)
+.set_attr<FInplaceOption>(
+  "FInplaceOption", [](const NodeAttrs& attrs) {
+    return std::vector<std::pair<int, int> >{{1, 0}};
+});
+
+}  // namespace top
+}  // namespace nnvm
diff --git a/nnvm/tests/python/compiler/test_top_assign.py b/nnvm/tests/python/compiler/test_top_assign.py
new file mode 100644
index 000000000000..e411385712f5
--- /dev/null
+++ b/nnvm/tests/python/compiler/test_top_assign.py
@@ -0,0 +1,41 @@
+import numpy as np
+
+import tvm
+from tvm.contrib import graph_runtime
+
+import nnvm.symbol as sym
+import nnvm.compiler
+from nnvm.testing.config import ctx_list
+
+
+def test_update():
+    w = sym.Variable("w")
+    w2 = sym.Variable("w2")
+    w = sym._assign(w, w + 1)
+    w2 = sym._assign(w2, w + 1)
+
+    dshape = (5, 3, 18, 18)
+    shape_dict = {"w": dshape, "w2":dshape}
+    dtype = "float32"
+
+    def check(target, ctx):
+        graph, lib, _ = nnvm.compiler.build(w2, target, shape_dict)
+
+        m = graph_runtime.create(graph, lib, ctx)
+
+        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+        m.set_input("w", data)
+        m.run()
+        out = m.get_input("w2", tvm.nd.empty(dshape, dtype))
+        np.testing.assert_allclose(out.asnumpy(), data.asnumpy() + 2, rtol=1e-5)
+
+        m.run()
+        out = m.get_input("w2", tvm.nd.empty(dshape, dtype))
+        np.testing.assert_allclose(out.asnumpy(), data.asnumpy() + 3, rtol=1e-5)
+
+    for target, ctx in ctx_list():
+        check(target, ctx)
+
+
+if __name__ == "__main__":
+    test_update()

From ac6d395ea9e128e6adc6245ba5578b9c9cf46fac Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 12 Mar 2018 14:10:51 -0700
Subject: [PATCH 617/949] fix tutorial build (#390)

---
 nnvm/tutorials/from_mxnet_to_webgl.py | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/nnvm/tutorials/from_mxnet_to_webgl.py b/nnvm/tutorials/from_mxnet_to_webgl.py
index b74d367be22b..8190b6a7e550 100644
--- a/nnvm/tutorials/from_mxnet_to_webgl.py
+++ b/nnvm/tutorials/from_mxnet_to_webgl.py
@@ -41,9 +41,7 @@
 
 # This tutorial must be run with OpenGL backend enabled in TVM.
 # The NNVM CI does not enable OpenGL yet. But the user can run this script.
-if not tvm.module.enabled("opengl"):
-    print("OpenGL backend not enabled. This tutorial cannot be run.")
-    exit(0)
+opengl_enabled = tvm.module.enabled("opengl")
 
 # To run the local demo, set this flag to True.
 run_deploy_local = False
@@ -308,7 +306,7 @@ def deploy_local():
     synset = download_synset()
     print('TVM prediction top-1:', top1, synset[top1])
 
-if run_deploy_local:
+if run_deploy_local and opengl_enabled:
     deploy_local()
 
 ######################################################################
@@ -412,7 +410,7 @@ def deploy_rpc():
     # Print first 10 elements of output.
     print(out.asnumpy()[0][0:10])
 
-if run_deploy_rpc:
+if run_deploy_rpc and opengl_enabled:
     deploy_rpc()
 
 ######################################################################
@@ -501,5 +499,5 @@ def deploy_web():
     print("Please open http://localhost:" + str(port) + "/resnet.html")
     httpd.serve_forever()
 
-if run_deploy_web:
-    deploy_web()
\ No newline at end of file
+if run_deploy_web and opengl_enabled:
+    deploy_web()

From 906e3b19848a37d80dad9a12039bfd8eda5c46b9 Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Thu, 15 Mar 2018 08:59:37 +0530
Subject: [PATCH 618/949] Documentation errors updated (#392)

---
 nnvm/tutorials/using_external_lib.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/nnvm/tutorials/using_external_lib.py b/nnvm/tutorials/using_external_lib.py
index 6cca37867fc0..f599438f402b 100644
--- a/nnvm/tutorials/using_external_lib.py
+++ b/nnvm/tutorials/using_external_lib.py
@@ -1,5 +1,5 @@
 """
-Using external libaries with NNVM
+Using external libraries with NNVM
 =====================
 **Author**: `Masahiro Masuda <https://github.com/masahi>`_
 
@@ -214,5 +214,5 @@
 # For example, MIOpen only supports NCHW layout and fp32 data type at the moment, so you cannot use other layouts or data type in TVM.
 #
 # Second, and more importantly, external libraries restrict the possibility of operator fusion during graph compilation, as shown above.
-# TVM and NNVM aim to achieve the best performance on a varity of hardwares, with joint operator level and graph level optimization.
+# TVM and NNVM aim to achieve the best performance on a variety of hardwares, with joint operator level and graph level optimization.
 # To achieve this goal, we should continue developing better optimizations for TVM and NNVM, while using external libraries as a nice way to fall back to existing implementation when necessary.

From 5bd7ceed97d9148b2cce2004366632072ecc2974 Mon Sep 17 00:00:00 2001
From: Yan Huang <FinalTheory@hotmail.com>
Date: Fri, 16 Mar 2018 05:55:50 +0800
Subject: [PATCH 619/949] [FIX] several bugs found when using NNVM (#391)

---
 nnvm/include/nnvm/node.h                      |  8 ++---
 nnvm/src/pass/plan_memory.cc                  |  7 +++--
 nnvm/src/top/nn/nn.cc                         |  9 ++++--
 nnvm/src/top/tensor/matrix_op.cc              |  6 ++--
 nnvm/src/top/tensor/transform.cc              |  4 +--
 .../tests/python/unittest/test_infer_shape.py | 31 +++++++++++++++++++
 nnvm/tests/python/unittest/test_symbol.py     |  8 +++++
 7 files changed, 58 insertions(+), 15 deletions(-)

diff --git a/nnvm/include/nnvm/node.h b/nnvm/include/nnvm/node.h
index c3698ed75ec7..2c7d0ef30680 100644
--- a/nnvm/include/nnvm/node.h
+++ b/nnvm/include/nnvm/node.h
@@ -146,11 +146,9 @@ inline NodeEntry MakeNode(
   NodePtr p = Node::Create();
   p->attrs.op = nnvm::Op::Get(op_name);
   p->attrs.name = std::move(node_name);
-  if (attrs.size() != 0) {
-    p->attrs.dict = attrs;
-    if (p->attrs.op->attr_parser) {
-      p->attrs.op->attr_parser(&(p->attrs));
-    }
+  p->attrs.dict = attrs;
+  if (p->attrs.op->attr_parser) {
+    p->attrs.op->attr_parser(&(p->attrs));
   }
   p->inputs = std::move(inputs);
   return NodeEntry{p, 0, 0};
diff --git a/nnvm/src/pass/plan_memory.cc b/nnvm/src/pass/plan_memory.cc
index b1f2a37d4820..f96f061b564f 100644
--- a/nnvm/src/pass/plan_memory.cc
+++ b/nnvm/src/pass/plan_memory.cc
@@ -151,6 +151,7 @@ size_t AllocMemory(const Graph& ret, const IndexedGraph& idx,
                    GraphAllocator* allocator) {
   static auto& finplace_option = Op::GetAttr<FInplaceOption>("FInplaceOption");
   static auto& finplace_identity = Op::GetAttr<FInplaceIdentity>("FInplaceIdentity");
+  static auto& fignore_inputs = Op::GetAttr<FIgnoreInputs>("FIgnoreInputs");
 
   // Get reference
   auto &storage = *storage_ptr;
@@ -189,10 +190,13 @@ size_t AllocMemory(const Graph& ret, const IndexedGraph& idx,
         uint32_t eid_in = idx.entry_id(inode.inputs[kv.first]);
         auto sid_out = storage[eid_out];
         auto sid_in = storage[eid_in];
+        bool ignore_all_inputs = (fignore_inputs.count(inode.source->op()) != 0 &&
+                                  fignore_inputs[inode.source->op()](
+                                      inode.source->attrs).size() == inode.source->num_inputs());
         if (taken[kv.first] == false &&
             sid_out == GraphAllocator::kBadStorageID &&
             sid_in >= 0 &&
-            (storage_ref_count[sid_in] == 1 || identity[ipair]) &&
+            (storage_ref_count[sid_in] == 1 && !ignore_all_inputs || identity[ipair]) &&
             entry_ref_count[eid_out] > 0 &&
             shape_vec[eid_out].Size() == shape_vec[eid_in].Size() &&
             dtype_vec[eid_out] == dtype_vec[eid_in]) {
@@ -230,7 +234,6 @@ size_t AllocMemory(const Graph& ret, const IndexedGraph& idx,
         storage[eid] = sid;
     }
     // check if certain inputs is ignored.
-    static auto& fignore_inputs = Op::GetAttr<FIgnoreInputs>("FIgnoreInputs");
     std::vector<uint32_t> ignore_inputs;
     if (fignore_inputs.count(inode.source->op()) != 0) {
       ignore_inputs = fignore_inputs[inode.source->op()](inode.source->attrs);
diff --git a/nnvm/src/top/nn/nn.cc b/nnvm/src/top/nn/nn.cc
index 24510d252d18..79b4c8587a64 100644
--- a/nnvm/src/top/nn/nn.cc
+++ b/nnvm/src/top/nn/nn.cc
@@ -134,8 +134,11 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(relu)
     NodeEntry zero = MakeNode("zeros_like", n->attrs.name + "_grad_zero",
                               {n->inputs[0]});
     return std::vector<NodeEntry>{
-      MakeNode("greater", n->attrs.name + "_grad",
-               {n->inputs[0], zero}, {{"exclude", "true"}})
+      MakeNode("elemwise_mul", n->attrs.name + "_grad", {
+        ograds[0],
+        MakeNode("greater", n->attrs.name + "_grad_mask",
+                 {n->inputs[0], zero}, {{"exclude", "true"}})
+      })
     };
 })
 .set_support_level(1);
@@ -249,7 +252,7 @@ axis to be the last item in the input shape.
 .set_attr<FNumVisibleOutputs>("FNumVisibleOutputs", [](const NodeAttrs& attrs) {
     return 1;
   })
-.set_attr<FMutateInputs>("FListMutateInputs", [](const NodeAttrs& attrs) {
+.set_attr<FMutateInputs>("FMutateInputs", [](const NodeAttrs& attrs) {
     return std::vector<uint32_t>{3, 4};
   })
 .set_support_level(1);
diff --git a/nnvm/src/top/tensor/matrix_op.cc b/nnvm/src/top/tensor/matrix_op.cc
index 5c163280186f..149c609ee08d 100644
--- a/nnvm/src/top/tensor/matrix_op.cc
+++ b/nnvm/src/top/tensor/matrix_op.cc
@@ -33,9 +33,9 @@ inline bool DotShape(const nnvm::NodeAttrs& attrs,
   CHECK_EQ(lshape[lshape.ndim() - 1], rshape[0])
     << "dot shape inconsistent: " << lshape << " X " << rshape;
 
-  TShape oshape(lshape.ndim() + rshape.ndim() - 1);
-  for (size_t i = 0; i < lshape.ndim() - 1; i++) oshape[i] = lshape[i];
-  for (size_t i = 1; i < rshape.ndim(); i++) oshape[i + lshape.ndim() - 1] = rshape[i];
+  TShape oshape(lshape.ndim() + rshape.ndim() - 2);
+  for (int i = 0; i < lshape.ndim() - 1; i++) oshape[i] = lshape[i];
+  for (int i = 1; i < rshape.ndim(); i++) oshape[i + lshape.ndim() - 2] = rshape[i];
 
   NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, 0, oshape);
   return true;
diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
index 0bf1a91ec830..dcdedbb867f9 100644
--- a/nnvm/src/top/tensor/transform.cc
+++ b/nnvm/src/top/tensor/transform.cc
@@ -574,7 +574,7 @@ the input array into an output array with the same shape as the second input arr
 )code" NNVM_ADD_FILELINE)
 .add_argument("data", "Tensor", "Input data.")
 .add_argument("shape_like", "Tensor", "Input data.")
-.set_num_inputs(1)
+.set_num_inputs(2)
 .set_num_outputs(1)
 .set_attr<FInferShape>(
   "FInferShape", [](const NodeAttrs& attrs,
@@ -585,7 +585,7 @@ the input array into an output array with the same shape as the second input arr
     NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, 0, in_attrs->at(1));
     return true;
 })
-.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FInferType>("FInferType", ElemwiseType<2, 1>)
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
diff --git a/nnvm/tests/python/unittest/test_infer_shape.py b/nnvm/tests/python/unittest/test_infer_shape.py
index 7af65c616bce..226dedd3d7a1 100644
--- a/nnvm/tests/python/unittest/test_infer_shape.py
+++ b/nnvm/tests/python/unittest/test_infer_shape.py
@@ -23,6 +23,36 @@ def test_dense():
     assert(sdict["fc_bias"][0] == [30])
 
 
+def test_matmul():
+    a = sym.Variable('a', shape=(10, 20))
+    b = sym.Variable('b', shape=(20, 30))
+    c = sym.matmul(a, b, name="matmul")
+    sdict = infer_shape(c)
+    assert(sdict["matmul"][0] == [10, 30])
+    a = sym.Variable('a', shape=(20, 10))
+    c = sym.matmul(a, b, name="matmul", transpose_a=True)
+    sdict = infer_shape(c)
+    assert(sdict["matmul"][0] == [10, 30])
+    b = sym.Variable('b', shape=(30, 20))
+    c = sym.matmul(a, b, name="matmul", transpose_a=True, transpose_b=True)
+    sdict = infer_shape(c)
+    assert(sdict["matmul"][0] == [10, 30])
+    a = sym.Variable('a', shape=(10, 20))
+    c = sym.matmul(a, b, name="matmul", transpose_b=True)
+    sdict = infer_shape(c)
+    assert(sdict["matmul"][0] == [10, 30])
+    a = sym.Variable('a', shape=(10, 20, 30))
+    b = sym.Variable('b', shape=(30, 40, 50))
+    c = sym.matmul(a, b, name="matmul")
+    sdict = infer_shape(c)
+    assert(sdict["matmul"][0] == [10, 20, 40, 50])
+    a = sym.Variable('a', shape=(30, 20, 10))
+    b = sym.Variable('b', shape=(50, 40, 30))
+    c = sym.matmul(a, b, name="matmul", transpose_a=True, transpose_b=True)
+    sdict = infer_shape(c)
+    assert(sdict["matmul"][0] == [10, 20, 40, 50])
+
+
 def test_concatenate():
     x1 = sym.Variable("x", shape=(10, 20))
     x2 = sym.Variable("y", shape=(10, 30))
@@ -275,6 +305,7 @@ def check(in_shape, out_shape, **kwargs):
 if __name__ == "__main__":
     test_expand_dims()
     test_dense()
+    test_matmul()
     test_concatenate()
     test_split()
     test_batchnorm()
diff --git a/nnvm/tests/python/unittest/test_symbol.py b/nnvm/tests/python/unittest/test_symbol.py
index 4a8ae58c9cf6..93d4fea26445 100644
--- a/nnvm/tests/python/unittest/test_symbol.py
+++ b/nnvm/tests/python/unittest/test_symbol.py
@@ -6,6 +6,13 @@ def test_dense():
     y = sym.dense(x, units=30, name="fc")
     assert y.list_input_names() == ["x", "fc_weight", "fc_bias"]
 
+def test_batch_norm():
+    x = sym.Variable('x')
+    y = sym.dense(x, units=30, name="fc")
+    z = sym.batch_norm(x, name='bn')
+    assert z.list_input_names('aux_state') == ['bn_moving_mean', 'bn_moving_var']
+    assert z.list_input_names('read_only') == ['x', 'bn_gamma', 'bn_beta']
+
 def test_compose():
     x = sym.Variable('x')
     z = sym.Variable('z')
@@ -51,3 +58,4 @@ def test_op_name():
     test_copy()
     test_default_input()
     test_compose()
+    test_batch_norm()

From 8f45013bff61a8e703fad47e53f70f4ee101dd71 Mon Sep 17 00:00:00 2001
From: Yuwei Hu <huyuwei1995@gmail.com>
Date: Mon, 19 Mar 2018 18:34:00 -0400
Subject: [PATCH 620/949] [Keras] fix dropout bug (#399)

---
 nnvm/python/nnvm/frontend/keras.py              |  2 +-
 .../tests/python/frontend/keras/test_forward.py | 17 +++--------------
 2 files changed, 4 insertions(+), 15 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/keras.py b/nnvm/python/nnvm/frontend/keras.py
index 58ee5faf8771..e363184d730f 100644
--- a/nnvm/python/nnvm/frontend/keras.py
+++ b/nnvm/python/nnvm/frontend/keras.py
@@ -355,7 +355,7 @@ def _convert_reshape(insym, keras_layer, _):
 
 def _default_skip(insym, keras_layer, _): # pylint: disable=unused-argument
     """Layers that can be skipped because they are train time only."""
-    return
+    return insym
 
 
 _convert_map = {
diff --git a/nnvm/tests/python/frontend/keras/test_forward.py b/nnvm/tests/python/frontend/keras/test_forward.py
index 0f5846dcf149..b51b97604192 100644
--- a/nnvm/tests/python/frontend/keras/test_forward.py
+++ b/nnvm/tests/python/frontend/keras/test_forward.py
@@ -40,7 +40,6 @@ def get_tvm_output(x, target, ctx, input_name='data', dtype='float32'):
 
     
 def test_forward_elemwise_add():
-    print("test_forward_elemwise_add")
     r = []
     data = keras.layers.Input(shape=(32,32,3))
     x = keras.layers.Conv2D(8, (3, 3), padding="same")(data)
@@ -48,26 +47,16 @@ def test_forward_elemwise_add():
     x = keras.layers.Conv2D(8, (3, 3), padding="same")(x)
     r.append(x)
     x = keras.layers.Conv2D(8, (3, 3), padding="same")(x)
-
+    # add two symbols
     y = keras.layers.add([keras.layers.add([x, r[0]]), r[1]])
     y = keras.layers.GlobalAveragePooling2D()(y)
     keras_model = keras.models.Model(data, y)
     verify_keras_frontend(keras_model)
-
+    # add three symbols
     y = keras.layers.add([x, r[0], r[1]])
     y = keras.layers.GlobalAveragePooling2D()(y)
     keras_model = keras.models.Model(data, y)
     verify_keras_frontend(keras_model)
-    
-    
-def test_forward_elementwise_add2():
-    data = keras.layers.Input(shape=(32,32,3))
-    r = keras.layers.Conv2D(10, (3, 3), padding="same")(data)
-    x = keras.layers.Conv2D(10, (3, 3), strides=(2, 2), padding="same")(data)
-    x = keras.layers.UpSampling2D()(x)
-    x = keras.layers.add([x, r])
-    x = keras.layers.GlobalAveragePooling2D()(x)
-    keras_model = keras.models.Model(data, x)
 
 
 def test_forward_softrelu():
@@ -92,6 +81,7 @@ def test_forward_dense():
     data = keras.layers.Input(shape=(32,32,3))
     x = keras.layers.MaxPooling2D(pool_size=(2,2))(data)
     x = keras.layers.Flatten()(x)
+    x = keras.layers.Dropout(0.5)(x)
     x = keras.layers.Dense(10, activation='relu', kernel_initializer='uniform')(x)
     keras_model = keras.models.Model(data, x)
     verify_keras_frontend(keras_model)
@@ -146,7 +136,6 @@ def test_forward_resnet50():
 
 if __name__ == '__main__':
     test_forward_elemwise_add()
-    test_forward_elementwise_add2()
     test_forward_softrelu()
     test_forward_leaky_relu()
     test_forward_dense()

From f5d6ab5df22e5067fca61b7fd51cd5fd4d045f3c Mon Sep 17 00:00:00 2001
From: Wenhao Hu <fumihwh@gmail.com>
Date: Sun, 25 Mar 2018 00:45:18 +0900
Subject: [PATCH 621/949] fix onnx frontend softplus bug (#413)

---
 nnvm/python/nnvm/frontend/onnx.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/python/nnvm/frontend/onnx.py b/nnvm/python/nnvm/frontend/onnx.py
index 5457f763f23c..b34d3bbd8283 100644
--- a/nnvm/python/nnvm/frontend/onnx.py
+++ b/nnvm/python/nnvm/frontend/onnx.py
@@ -217,7 +217,7 @@ def _impl(inputs, attr, params):
 
 def _softplus():
     def _impl(inputs, attr, params):
-        return _sym.log(_sym.exp(x) + 1)
+        return _sym.log(_sym.exp(inputs[0]) + 1)
     return _impl
 
 def _pad():

From 38d028321236deb59d9caf70e7744d1427ab5b63 Mon Sep 17 00:00:00 2001
From: Wenhao Hu <fumihwh@gmail.com>
Date: Wed, 28 Mar 2018 13:26:22 +0900
Subject: [PATCH 622/949] use caffe2.python.onnx.backend instead of onnx-caffe2
 (#418)

---
 nnvm/tests/python/frontend/onnx/test_forward.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/nnvm/tests/python/frontend/onnx/test_forward.py b/nnvm/tests/python/frontend/onnx/test_forward.py
index 20fbcefadee3..d71e171836ae 100644
--- a/nnvm/tests/python/frontend/onnx/test_forward.py
+++ b/nnvm/tests/python/frontend/onnx/test_forward.py
@@ -7,9 +7,9 @@
 from model_zoo import super_resolution, squeezenet1_1, lenet, resnet18_1_0
 
 def verify_onnx_forward_impl(graph_file, data_shape, out_shape):
-    import onnx_caffe2.backend
+    import caffe2.python.onnx.backend
     def get_caffe2_output(model, x, dtype='float32'):
-        prepared_backend = onnx_caffe2.backend.prepare(model)
+        prepared_backend = caffe2.python.onnx.backend.prepare(model)
         W = {model.graph.input[0].name: x.astype(dtype)}
         c2_out = prepared_backend.run(W)[0]
         return c2_out

From 1e7c758ef57df0d4dc18068ea56ca58f58da9730 Mon Sep 17 00:00:00 2001
From: Yao Wang <kevinthesunwy@gmail.com>
Date: Thu, 29 Mar 2018 09:27:37 -0700
Subject: [PATCH 623/949] Add optimizer (#334)

---
 nnvm/docs/top.rst                             |  42 +--
 nnvm/include/nnvm/top/tensor.h                |  10 +
 nnvm/python/nnvm/_base.py                     |   3 +
 nnvm/python/nnvm/compiler/build_module.py     |  57 ++++
 nnvm/python/nnvm/compiler/lr_scheduler.py     |  58 ++++
 nnvm/python/nnvm/compiler/optimizer.py        | 131 +++++++++
 nnvm/python/nnvm/symbol.py                    |  21 +-
 nnvm/python/nnvm/top/attr_dict.py             |  15 +
 nnvm/python/nnvm/top/tensor.py                |  92 ++++++
 nnvm/python/nnvm/top/transform.py             |  27 ++
 nnvm/src/top/nn/nn.cc                         |  32 +--
 nnvm/src/top/tensor/elemwise.cc               | 103 ++++++-
 nnvm/src/top/tensor/reduce.cc                 |  16 +-
 nnvm/src/top/tensor/state_op.cc               |   9 +
 nnvm/src/top/tensor/transform.cc              |  35 +--
 nnvm/tests/python/compiler/test_optimizer.py  | 118 ++++++++
 nnvm/tests/python/compiler/test_top_level1.py | 135 +++++----
 nnvm/tests/python/compiler/test_top_level4.py | 272 ++++++++++++++++++
 18 files changed, 1042 insertions(+), 134 deletions(-)
 create mode 100644 nnvm/python/nnvm/compiler/lr_scheduler.py
 create mode 100644 nnvm/python/nnvm/compiler/optimizer.py
 create mode 100644 nnvm/tests/python/compiler/test_optimizer.py

diff --git a/nnvm/docs/top.rst b/nnvm/docs/top.rst
index c34ae5b54b07..11d0f6093f83 100644
--- a/nnvm/docs/top.rst
+++ b/nnvm/docs/top.rst
@@ -28,7 +28,6 @@ This level enables fully connected multi-layer perceptron.
    :nosignatures:
 
    nnvm.symbol.dense
-   nnvm.symbol.matmul
    nnvm.symbol.relu
    nnvm.symbol.tanh
    nnvm.symbol.sigmoid
@@ -40,12 +39,6 @@ This level enables fully connected multi-layer perceptron.
    nnvm.symbol.elemwise_mul
    nnvm.symbol.elemwise_div
    nnvm.symbol.elemwise_sum
-   nnvm.symbol.full
-   nnvm.symbol.full_like
-   nnvm.symbol.ones
-   nnvm.symbol.ones_like
-   nnvm.symbol.zeros
-   nnvm.symbol.zeros_like
    nnvm.symbol.flatten
    nnvm.symbol.concatenate
    nnvm.symbol.expand_dims
@@ -57,7 +50,6 @@ This level enables fully connected multi-layer perceptron.
    nnvm.symbol.log_softmax
    nnvm.symbol.pad
    nnvm.symbol.block_grad
-   nnvm.symbol.indicator
 
 
 **Level 2: Convolutions**
@@ -81,8 +73,6 @@ This level enables typical convnet models.
    :nosignatures:
 
    nnvm.symbol.reshape
-   nnvm.symbol.reshape_like
-   nnvm.symbol.expand_like
    nnvm.symbol.copy
    nnvm.symbol.negative
    nnvm.symbol.leaky_relu
@@ -109,11 +99,21 @@ This level enables typical convnet models.
    nnvm.symbol.broadcast_sub
    nnvm.symbol.broadcast_mul
    nnvm.symbol.broadcast_div
+   nnvm.symbol.clip
+   nnvm.symbol.greater
+   nnvm.symbol.less
+   nnvm.symbol.expand_like
+   nnvm.symbol.reshape_like
+   nnvm.symbol.full
+   nnvm.symbol.full_like
+   nnvm.symbol.ones
+   nnvm.symbol.ones_like
+   nnvm.symbol.zeros
+   nnvm.symbol.zeros_like
 
 Detailed Definitions
 --------------------
 .. autofunction:: nnvm.symbol.dense
-.. autofunction:: nnvm.symbol.matmul
 .. autofunction:: nnvm.symbol.relu
 .. autofunction:: nnvm.symbol.tanh
 .. autofunction:: nnvm.symbol.sigmoid
@@ -125,12 +125,6 @@ Detailed Definitions
 .. autofunction:: nnvm.symbol.elemwise_mul
 .. autofunction:: nnvm.symbol.elemwise_div
 .. autofunction:: nnvm.symbol.elemwise_sum
-.. autofunction:: nnvm.symbol.full
-.. autofunction:: nnvm.symbol.full_like
-.. autofunction:: nnvm.symbol.ones
-.. autofunction:: nnvm.symbol.ones_like
-.. autofunction:: nnvm.symbol.zeros
-.. autofunction:: nnvm.symbol.zeros_like
 .. autofunction:: nnvm.symbol.flatten
 .. autofunction:: nnvm.symbol.concatenate
 .. autofunction:: nnvm.symbol.expand_dims
@@ -142,7 +136,6 @@ Detailed Definitions
 .. autofunction:: nnvm.symbol.log_softmax
 .. autofunction:: nnvm.symbol.pad
 .. autofunction:: nnvm.symbol.block_grad
-.. autofunction:: nnvm.symbol.indicator
 
 .. autofunction:: nnvm.symbol.conv2d
 .. autofunction:: nnvm.symbol.conv2d_transpose
@@ -152,8 +145,6 @@ Detailed Definitions
 .. autofunction:: nnvm.symbol.global_avg_pool2d
 
 .. autofunction:: nnvm.symbol.reshape
-.. autofunction:: nnvm.symbol.reshape_like
-.. autofunction:: nnvm.symbol.expand_like
 .. autofunction:: nnvm.symbol.copy
 .. autofunction:: nnvm.symbol.negative
 .. autofunction:: nnvm.symbol.leaky_relu
@@ -175,3 +166,14 @@ Detailed Definitions
 .. autofunction:: nnvm.symbol.broadcast_sub
 .. autofunction:: nnvm.symbol.broadcast_mul
 .. autofunction:: nnvm.symbol.broadcast_div
+.. autofunction:: nnvm.symbol.clip
+.. autofunction:: nnvm.symbol.greater
+.. autofunction:: nnvm.symbol.less
+.. autofunction:: nnvm.symbol.expand_like
+.. autofunction:: nnvm.symbol.reshape_like
+.. autofunction:: nnvm.symbol.full
+.. autofunction:: nnvm.symbol.full_like
+.. autofunction:: nnvm.symbol.ones
+.. autofunction:: nnvm.symbol.ones_like
+.. autofunction:: nnvm.symbol.zeros
+.. autofunction:: nnvm.symbol.zeros_like
diff --git a/nnvm/include/nnvm/top/tensor.h b/nnvm/include/nnvm/top/tensor.h
index 7f50e49b996c..00bad8245713 100644
--- a/nnvm/include/nnvm/top/tensor.h
+++ b/nnvm/include/nnvm/top/tensor.h
@@ -241,6 +241,16 @@ struct MatMulParam : public dmlc::Parameter<MatMulParam> {
   }
 };
 
+struct ClipParam : public dmlc::Parameter<ClipParam> {
+  double a_min, a_max;
+  DMLC_DECLARE_PARAMETER(ClipParam) {
+    DMLC_DECLARE_FIELD(a_min)
+      .describe("Minimum value such that value smaller then this will be clipped.");
+    DMLC_DECLARE_FIELD(a_max)
+      .describe("Maximum value such that value larger then this will be clipped.");
+  }
+};
+
 }  // namespace top
 }  // namespace nnvm
 
diff --git a/nnvm/python/nnvm/_base.py b/nnvm/python/nnvm/_base.py
index d01bdcaeb5b1..29390a2201bf 100644
--- a/nnvm/python/nnvm/_base.py
+++ b/nnvm/python/nnvm/_base.py
@@ -54,6 +54,9 @@ def _load_lib():
 SymbolHandle = ctypes.c_void_p
 GraphHandle = ctypes.c_void_p
 
+# Global dict of str to symbol to initialize variables
+_all_var_init = {}
+
 #----------------------------
 # helper function definition
 #----------------------------
diff --git a/nnvm/python/nnvm/compiler/build_module.py b/nnvm/python/nnvm/compiler/build_module.py
index bce142d21b63..d97a8784dcc2 100644
--- a/nnvm/python/nnvm/compiler/build_module.py
+++ b/nnvm/python/nnvm/compiler/build_module.py
@@ -4,9 +4,12 @@
 
 import logging
 import tvm
+
 from tvm.contrib import graph_runtime
 from . import graph_attr, graph_util
 from .. import graph as _graph
+from .. import symbol as sym
+from .._base import _all_var_init
 
 OPT_PASS_LEVEL = {
     "SimplifyInference": 0,
@@ -201,6 +204,9 @@ def build(graph, target=None, shape=None, dtype="float32", params=None, target_h
         By default, llvm is used if it is enabled,
         otherwise a stackvm intepreter is used.
 
+    initialize : bool, optional
+        Whether to initialize variables in global dict _all_var_init.
+
     Returns
     -------
     graph : Graph
@@ -230,6 +236,10 @@ def build(graph, target=None, shape=None, dtype="float32", params=None, target_h
     if not isinstance(dtype, str):
         idtype, _ = graph_util.infer_dtype(graph, **dtype)
         dtype.update(zip(graph.index.input_names, idtype))
+    # Initialize all variables specified in _all_var_init
+    init_var = {}
+    if _all_var_init:
+        init_var = initialize_variables(shape, dtype)
     # Apply optimization
     graph = optimize(graph, shape, dtype)
     # Precompute prune
@@ -250,6 +260,11 @@ def build(graph, target=None, shape=None, dtype="float32", params=None, target_h
     with target:
         graph = graph.apply("GraphFusePartition").apply("GraphFuseCompile")
     libmod = graph_attr._move_out_module(graph, "module")
+    # Write variable initial values into params
+    if init_var:
+        if params is None:
+            params = {}
+        params.update(init_var)
     return graph, libmod, params
 
 
@@ -329,3 +344,45 @@ def precompute_prune(graph, params):
     with tvm.build_config(auto_unroll_max_step=0):
         out_arrs = _run_graph(pre_graph, params)
     return graph, dict(zip(out_names, out_arrs))
+
+
+def initialize_variables(ishape, idtype):
+    """ Initialize variables stored in _all_var_init dictionary.
+
+    Parameters
+    ----------
+    ishape : dict of str to tuple of int
+        The input shape to the graph
+
+    idtype : str or dict of str to str
+        The input types to the graph
+
+    Returns
+    -------
+    init_var : dict of str to tvm.ndarray
+    """
+    symbol_init_dict = {}
+    const_init_dict = {}
+    init_var = {}
+    for key, value in _all_var_init.items():
+        if isinstance(value, sym.Symbol):
+            symbol_init_dict[key] = value
+        else:
+            const_init_dict[key] = tvm.nd.array(value)
+    # Make sure variables are initialized only once.
+    _all_var_init.clear()
+    if symbol_init_dict:
+        # Create dummy params to run initialization graph
+        params = {}
+        for name, shape in ishape.items():
+            dtype = idtype if isinstance(idtype, str) else idtype[name]
+            params[name] = tvm.nd.empty(shape, dtype, ctx=tvm.cpu())
+        init_group_sym = sym.Group(symbol_init_dict.values())
+        graph = _graph.create(init_group_sym)
+        with tvm.build_config(auto_unroll_max_step=0):
+            init_values = _run_graph(graph, params)
+        init_var.update(dict(zip(symbol_init_dict.keys(), init_values)))
+    init_var.update(const_init_dict)
+    for name, data in init_var.items():
+        ishape[name] = data.shape
+    return init_var
diff --git a/nnvm/python/nnvm/compiler/lr_scheduler.py b/nnvm/python/nnvm/compiler/lr_scheduler.py
new file mode 100644
index 000000000000..791925e74960
--- /dev/null
+++ b/nnvm/python/nnvm/compiler/lr_scheduler.py
@@ -0,0 +1,58 @@
+# pylint: disable=too-few-public-methods, no-member
+"""API for scheduling learning rate."""
+from .. import symbol as sym
+
+class LRScheduler(object):
+    """Base class of a learning rate scheduler.
+
+    A scheduler returns a new learning rate based on the number of updates that have
+    been performed.
+
+    Parameters
+    ----------
+    base_lr : float, optional
+        The initial learning rate.
+    """
+    def __init__(self, base_lr=0.01, name='LRScheduler'):
+        self.name = name
+        self.base_lr = base_lr
+
+    def __call__(self, num_update):
+        """Return a new learning rate based on number of updates.
+
+        Parameters
+        ----------
+        num_update: nnvm Symbol
+            the number of updates applied to weight.
+        """
+        raise NotImplementedError("__call__ method must be overridden.")
+
+class FactorScheduler(LRScheduler):
+    """Reduce the learning rate by a factor for every *n* steps.
+
+    It returns a new learning rate by::
+
+        base_lr * pow(factor, num_update/step)
+
+    Parameters
+    ----------
+    step : int
+        Changes the learning rate for every n updates.
+    factor : float, optional
+        The factor to change the learning rate.
+    stop_factor_lr : float, optional
+        Stop updating the learning rate if it is less than this value.
+    """
+    def __init__(self, step, factor=1, stop_factor_lr=1e-8, name='FactorScheduler', **kwargs):
+        super(FactorScheduler, self).__init__(name=name, **kwargs)
+        if step < 1:
+            raise ValueError("Schedule step must be greater or equal than 1 round")
+        if factor > 1.0:
+            raise ValueError("Factor must be no more than 1 to make lr reduce")
+        self.step = step
+        self.factor = factor
+        self.stop_factor_lr = stop_factor_lr
+
+    def __call__(self, num_update):
+        updated_lr = self.base_lr * self.factor ** (num_update / self.step)
+        return sym.clip(updated_lr, a_min=self.stop_factor_lr, a_max=self.base_lr)
diff --git a/nnvm/python/nnvm/compiler/optimizer.py b/nnvm/python/nnvm/compiler/optimizer.py
new file mode 100644
index 000000000000..bcf7498528af
--- /dev/null
+++ b/nnvm/python/nnvm/compiler/optimizer.py
@@ -0,0 +1,131 @@
+# pylint: disable=invalid-name, no-member, too-few-public-methods, too-many-arguments, too-many-locals, protected-access
+"""Optimizer API"""
+from . import graph_util
+from .. import symbol as sym
+
+class Optimizer(object):
+    """Base class inherited by all optimizers.
+
+    Parameters
+    ----------
+    learning_rate : float, optional
+        The initial learning rate.
+
+    lr_scheduler : LRScheduler, optional
+        The learning rate scheduler.
+
+    rescale_grad : float, optional
+        Multiply the gradient with `rescale_grad` before updating. Often
+        choose to be ``1.0/batch_size``.
+
+    clip_gradient : float, optional
+        Clip the gradient by projecting onto the box ``[-clip_gradient, clip_gradient]``.
+
+    wd : float, optional
+        The weight decay (or L2 regularization) coefficient. Modifies objective
+        by adding a penalty for having large weights.
+
+    name : string, optional
+        The name of optimizer.
+    """
+    def __init__(self, learning_rate=0.01, lr_scheduler=None,
+                 rescale_grad=1, clip_gradient=None, wd=0, name="Optimizer"):
+        self.name = name
+        self.lr = learning_rate
+        self.lr_scheduler = lr_scheduler
+        self.rescale_grad = rescale_grad
+        self.clip_gradient = clip_gradient
+        self.wd = wd
+        init_update_t = sym.Variable(name+'_t', init=sym.zeros(shape=(1,), dtype="int32"))
+        self.update_t = sym._assign(init_update_t, init_update_t + 1)
+
+    def minimize(self, obj, var=None):
+        """Minimize given obj symbol respect to var. If var is not set, all input
+        variables of obj will be used.
+
+        Parameters
+        ----------
+        obj : nnvm Symbol or list of nnvm Symbols
+            Symbols to be minimized.
+        var : nnvm Symbol or list of nnvm Symbols, optional
+            Symbols the gradient respect to.
+
+        Returns
+        -------
+        group_sym : nnvm Symbol
+            Group symbol represents update symbols.
+        """
+        raise NotImplementedError()
+
+    def _get_lr(self):
+        """Gets the learning rate with learning rate scheduler.
+
+        Returns
+        -------
+        lr : float
+            Learning rate.
+        """
+        if self.lr_scheduler is not None:
+            lr = self.lr_scheduler(self.update_t)
+        else:
+            lr = self.lr
+        return lr
+
+
+class SGD(Optimizer):
+    """The SGD optimizer
+    """
+    def __init__(self, name='SGD', **kwargs):
+        super(SGD, self).__init__(name=name, **kwargs)
+
+    def minimize(self, obj, var=None):
+        variables = var or obj.list_input_variables()
+        if not isinstance(variables, list):
+            variables = [variables]
+        grads = graph_util.gradients(obj, variables)
+        updates = []
+        lr_t = self._get_lr()
+        for v, g in zip(variables, grads):
+            g = self.rescale_grad * g
+            if self.clip_gradient is not None:
+                g = sym.clip(g, a_min=-1 * self.clip_gradient, a_max=self.clip_gradient)
+            updates.append(sym._assign(v, v - lr_t * (g + self.wd * v)))
+        return sym.Group(updates)
+
+
+class Adam(Optimizer):
+    """The Adam optimizer.
+
+    This class implements the optimizer described in *Adam: A Method for
+    Stochastic Optimization*, available at http://arxiv.org/abs/1412.6980.
+    """
+    def __init__(self, learning_rate=0.001, beta1=0.9, beta2=0.999,
+                 epsilon=1e-8, name='Adam', **kwargs):
+        super(Adam, self).__init__(learning_rate=learning_rate, name=name, **kwargs)
+        self.beta1 = beta1
+        self.beta2 = beta2
+        self.epsilon = epsilon
+        self.m = []
+        self.v = []
+
+    def minimize(self, obj, var=None):
+        variables = var or obj.list_input_variables()
+        if not isinstance(variables, list):
+            variables = [variables]
+        grads = graph_util.gradients(obj, variables)
+        updates = []
+        for i, v in enumerate(variables):
+            self.m.append(sym.Variable(self.name + '_m' + str(i), init=sym.zeros_like(v)))
+            self.v.append(sym.Variable(self.name + '_v' + str(i), init=sym.zeros_like(v)))
+        rate = sym.sqrt(1 - self.beta2 ** self.update_t) / (1 -  self.beta1 ** self.update_t)
+        lr_t = self._get_lr() * rate
+        for variable, g, m, v in zip(variables, grads, self.m, self.v):
+            g = self.rescale_grad * g
+            if self.clip_gradient is not None:
+                g = sym.clip(g, a_min=-1 * self.clip_gradient, a_max=self.clip_gradient)
+            update_m = sym._assign(m, self.beta1 * m + (1 - self.beta1) * g)
+            update_v = sym._assign(v, self.beta2 * v + (1 - self.beta2) * g * g)
+            update_var = sym._assign(variable, variable - lr_t * (update_m / (sym.sqrt(update_v) \
+                         + self.epsilon) + self.wd * variable))
+            updates.append(update_var)
+        return sym.Group(updates)
diff --git a/nnvm/python/nnvm/symbol.py b/nnvm/python/nnvm/symbol.py
index 0ac51f23fe94..8b390e2cb72f 100644
--- a/nnvm/python/nnvm/symbol.py
+++ b/nnvm/python/nnvm/symbol.py
@@ -1,4 +1,4 @@
-# pylint: disable=invalid-name, unused-import
+# pylint: disable=invalid-name, unused-import, protected-access
 """Symbolic graph construction API.
 
 This namespace contains most of the registered operators.
@@ -8,10 +8,12 @@
 import sys as _sys
 import os as _os
 import ctypes as _ctypes
-
 from numbers import Number as _Number
+
+import numpy as np
+
 from . import _base
-from ._base import _LIB, check_call as _check_call, _FFI_MODE
+from ._base import _LIB, check_call as _check_call, _FFI_MODE, _all_var_init
 from .attribute import AttrScope
 from . import _symbol_internal as _internal
 
@@ -309,13 +311,19 @@ def _add_control_deps(self, deps):
             self.handle, deps.handle))
 
 
-def Variable(name, **kwargs):
+def Variable(name, init=None, **kwargs):
     """Create a symbolic variable with specified name.
 
     Parameters
     ----------
     name : str
         Name of the variable.
+    init : Symbol or numpy.ndarray
+        Symbol or numpy ndarray of initial value for the variable.
+        Note that for symbolic initialization value, it must be able
+        to be defined through InferShape, such as sym.zeros_like(v),
+        in which v is an input or parameter. Otherwise, pass a numpy
+        ndarray instead.
     kwargs : dict of string -> string
         Additional attributes to set on the variable.
 
@@ -333,6 +341,11 @@ def Variable(name, **kwargs):
     attr = AttrScope.current.get(kwargs)
     if attr:
         ret._set_attr(**attr)
+    if init is not None:
+        if not isinstance(init, (Symbol, np.ndarray)):
+            raise TypeError('Expect a Symbol or numpy ndarray'
+                            'for variable `init`')
+        _all_var_init[name] = init
     return ret
 
 
diff --git a/nnvm/python/nnvm/top/attr_dict.py b/nnvm/python/nnvm/top/attr_dict.py
index 453c2971f73f..a913a92552b2 100644
--- a/nnvm/python/nnvm/top/attr_dict.py
+++ b/nnvm/python/nnvm/top/attr_dict.py
@@ -123,6 +123,21 @@ def get_bool(self, key):
         else:
             raise ValueError("Wrong bool format for key %s" % key)
 
+    def get_string(self, key):
+        """Get string from attr dict
+
+        Parameters
+        ----------
+        key : str
+            The attr key
+
+        Returns
+        -------
+        value : str
+            The result value
+        """
+        return self[key]
+
     def __repr__(self):
         return str({k : self[k] for k in self.keys()})
 
diff --git a/nnvm/python/nnvm/top/tensor.py b/nnvm/python/nnvm/top/tensor.py
index 4f9d3ba9f36f..1e8688f9f2e6 100644
--- a/nnvm/python/nnvm/top/tensor.py
+++ b/nnvm/python/nnvm/top/tensor.py
@@ -143,3 +143,95 @@ def _compute(attrs, x, _):
 # broadcast_to
 reg.register_pattern("broadcast_to", OpPattern.BROADCAST)
 reg.register_schedule("broadcast_to", _fschedule_broadcast)
+
+# clip
+reg.register_pattern("clip", OpPattern.ELEMWISE)
+reg.register_schedule("clip", _fschedule_elemwise)
+
+# elemwise sum
+@reg.register_compute("elemwise_sum")
+def compute_elemwise_sum(attrs, inputs, _):
+    """Compute definition of elemwise sum"""
+    num_args = attrs.get_int("num_args")
+    assert num_args == len(inputs), "Number of tensors does not match num_args."
+    return topi.tensor.elemwise_sum(inputs, num_args)
+reg.register_pattern("elemwise_sum", OpPattern.ELEMWISE)
+reg.register_schedule("elemwise_sum", _fschedule_elemwise)
+
+# full
+@reg.register_compute("full")
+def compute_full(attrs, inputs, _):
+    """Compute definition of full"""
+    shape = attrs.get_int_tuple("shape")
+    dtype = attrs.get_string("dtype")
+    fill_value = attrs.get_float("fill_value")
+    return topi.tensor.full(shape, dtype, fill_value)
+reg.register_pattern("full", OpPattern.OUT_ELEMWISE_FUSABLE)
+reg.register_schedule("full", _fschedule_elemwise)
+
+# full_like
+@reg.register_compute("full_like")
+def compute_full_like(attrs, inputs, _):
+    """Compute definition of full_like"""
+    fill_value = attrs.get_float("fill_value")
+    return topi.tensor.full_like(inputs[0], fill_value)
+reg.register_pattern("full_like", OpPattern.ELEMWISE)
+reg.register_schedule("full_like", _fschedule_elemwise)
+
+# zeros
+@reg.register_compute("zeros")
+def compute_zeros(attrs, inputs, _):
+    """Compute definition of zeros"""
+    shape = attrs.get_int_tuple("shape")
+    dtype = attrs.get_string("dtype")
+    return topi.tensor.full(shape, dtype, 0)
+reg.register_pattern("zeros", OpPattern.OUT_ELEMWISE_FUSABLE)
+reg.register_schedule("zeros", _fschedule_elemwise)
+
+# zeros_like
+@reg.register_compute("zeros_like")
+def compute_zeros_like(_, inputs, out_info):
+    """Compute definition of zeros_like"""
+    return topi.tensor.full_like(inputs[0], 0)
+reg.register_pattern("zeros_like", OpPattern.ELEMWISE)
+reg.register_schedule("zeros_like", _fschedule_elemwise)
+
+# ones
+@reg.register_compute("ones")
+def compute_ones(attrs, inputs, _):
+    """Compute definition of ones"""
+    shape = attrs.get_int_tuple("shape")
+    dtype = attrs.get_string("dtype")
+    #tvm.tensor.Tensor()
+    return topi.tensor.full(shape, dtype, 1)
+reg.register_pattern("ones", OpPattern.OUT_ELEMWISE_FUSABLE)
+reg.register_schedule("ones", _fschedule_elemwise)
+
+# ones_like
+@reg.register_compute("ones_like")
+def compute_ones_like(_, inputs, out_info):
+    """Compute definition of ones_like"""
+    return topi.tensor.full_like(inputs[0], 1)
+reg.register_pattern("ones_like", OpPattern.ELEMWISE)
+reg.register_schedule("ones_like", _fschedule_elemwise)
+
+# greater
+@reg.register_compute("greater")
+def compute_greater(_, inputs, out_info):
+    """Compute definition of greater"""
+    return topi.tensor.greater(inputs[0], inputs[1], 'float32')
+reg.register_pattern("greater", OpPattern.ELEMWISE)
+reg.register_schedule("greater", _fschedule_elemwise)
+
+# less
+@reg.register_compute("less")
+def compute_less(_, inputs, out_info):
+    """Compute definition of less"""
+    return topi.tensor.less(inputs[0], inputs[1], 'float32')
+reg.register_pattern("less", OpPattern.ELEMWISE)
+reg.register_schedule("less", _fschedule_elemwise)
+
+# block_grad
+reg.register_compute("block_grad", _compute_unary(topi.identity))
+reg.register_pattern("block_grad", OpPattern.ELEMWISE)
+reg.register_schedule("block_grad", _fschedule_elemwise)
diff --git a/nnvm/python/nnvm/top/transform.py b/nnvm/python/nnvm/top/transform.py
index ec1596e9599b..c3ceb68682ee 100644
--- a/nnvm/python/nnvm/top/transform.py
+++ b/nnvm/python/nnvm/top/transform.py
@@ -2,6 +2,7 @@
 """Tensor transformation ops"""
 from __future__ import absolute_import
 
+import topi
 from .tensor import _fschedule_broadcast, _fschedule_injective
 from . import registry as reg
 from .registry import OpPattern
@@ -10,6 +11,32 @@
 reg.register_pattern("expand_dims", OpPattern.BROADCAST)
 reg.register_schedule("expand_dims", _fschedule_broadcast)
 
+# expand_like
+@reg.register_compute("expand_like")
+def compute_expand_like(attrs, inputs, _):
+    """Compute definition of expand_like"""
+    exclude = attrs.get_bool("exclude")
+    axis = attrs.get_int_tuple("axis")
+    if exclude:
+        exclude_axis = (axis,) if isinstance(axis, int) else axis
+        axis = []
+        for item in range(len(inputs[1].shape)):
+            if item not in exclude_axis:
+                axis.append(item)
+        axis = tuple(axis)
+
+    return topi.transform.expand_like(inputs[0], inputs[1], axis)
+reg.register_pattern("expand_like", OpPattern.BROADCAST)
+reg.register_schedule("expand_like", _fschedule_broadcast)
+
+# reshape_like
+@reg.register_compute("reshape_like")
+def compute_reshape_like(attrs, inputs, out_info):
+    """Compute definition of reshape_like"""
+    return topi.reshape(inputs[0], inputs[1].shape)
+reg.register_pattern("reshape_like", OpPattern.INJECTIVE)
+reg.register_schedule("reshape_like", _fschedule_injective)
+
 # transpose
 reg.register_pattern("transpose", OpPattern.INJECTIVE)
 reg.register_schedule("transpose", _fschedule_injective)
diff --git a/nnvm/src/top/nn/nn.cc b/nnvm/src/top/nn/nn.cc
index 79b4c8587a64..5ac4b7662ff5 100644
--- a/nnvm/src/top/nn/nn.cc
+++ b/nnvm/src/top/nn/nn.cc
@@ -130,15 +130,14 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(relu)
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
     // y = relu(x)
-    // grad = indicator(x > 0)
-    NodeEntry zero = MakeNode("zeros_like", n->attrs.name + "_grad_zero",
+    // grad = indicator(x > 0) * ograd
+    NodeEntry sub0 = MakeNode("zeros_like", n->attrs.name + "_sub0",
                               {n->inputs[0]});
+    NodeEntry sub1 = MakeNode("greater", n->attrs.name + "_sub1",
+                              {n->inputs[0], sub0}, {{"exclude", "true"}});
     return std::vector<NodeEntry>{
-      MakeNode("elemwise_mul", n->attrs.name + "_grad", {
-        ograds[0],
-        MakeNode("greater", n->attrs.name + "_grad_mask",
-                 {n->inputs[0], zero}, {{"exclude", "true"}})
-      })
+      MakeNode("elemwise_mul", n->attrs.name + "_grad",
+               {ograds[0], sub1})
     };
 })
 .set_support_level(1);
@@ -358,23 +357,21 @@ NNVM_REGISTER_OP(log_softmax)
     // grad_x = sum(grad_x, keepdim, axis)
     // grad_x = neg grad_x
     // grad_x = grad_x + ones_like(grad_x)
-    // grad_x = expand_dims(grad_x, axis)
     const SoftmaxParam& param = nnvm::get<SoftmaxParam>(n->attrs.parsed);
     NodeEntry output =  NodeEntry{n, 0, 0};
     NodeEntry sub0 = MakeNode("elemwise_mul", n->attrs.name + "_grad_sub0", {ograds[0], output});
     NodeEntry sub1 = MakeNode("sum", n->attrs.name + "_grad_sub1", {sub0},
                               {{"axis", std::to_string(param.axis)}, {"keepdims", "true"}});
-    NodeEntry sub2 = MakeNode("negative", n->attrs.name + "_grad_sub2", {sub1});
-    NodeEntry sub3 = MakeNode("ones_like", n->attrs.name + "_grad_sub3", {sub2});
-    NodeEntry sub4 = MakeNode("elemwise_add", n->attrs.name + "_grad_sub4", {sub2, sub3});
+    NodeEntry sub2 = MakeNode("full_like", n->attrs.name + "_grad_sub2", {n->inputs[0]},
+                              {{"fill_value", "-1"}});
+    NodeEntry sub3 = MakeNode("broadcast_mul", n->attrs.name + "_grad_sub3", {sub1, sub2});
     return std::vector<NodeEntry> {
-      MakeNode("expand_like", n->attrs.name + "_grad", {sub4, output},
-               {{"axis", std::to_string(param.axis)}})
+      MakeNode("elemwise_add", n->attrs.name + "_grad", {sub3, ograds[0]})
     };
 })
 .set_support_level(1);
 
-// leaky_rlu
+// leaky_relu
 DMLC_REGISTER_PARAMETER(LeakyReLUParam);
 
 NNVM_REGISTER_OP(leaky_relu)
@@ -407,14 +404,15 @@ NNVM_REGISTER_OP(leaky_relu)
     NodeEntry zero = MakeNode("zeros_like", n->attrs.name + "_grad_zero",
                               {n->inputs[0]});
     NodeEntry sub0 = MakeNode("greater", n->attrs.name + "_pos_grad",
-                              {n->inputs[0], zero}, {{"exclude", "true"}});
+                              {n->inputs[0], zero});
     NodeEntry sub1 = MakeNode("less", n->attrs.name + "_neg_grad",
-                              {n->inputs[0], zero}, {{"exclude", "true"}});
+                              {n->inputs[0], zero});
     NodeEntry sub2 = MakeNode("__mul_scalar__", n->attrs.name + "_neg_mul_2",
                               {sub1},
                               {{"scalar", std::to_string(param.alpha)}});
+    NodeEntry sub3 = MakeNode("elemwise_add", n->attrs.name + "_sub3", {sub0, sub2});
     return std::vector<NodeEntry>{
-      MakeNode("elemwise_add", n->attrs.name + "_add_grad", {sub0, sub2})
+      MakeNode("elemwise_mul", n->attrs.name + "_grad", {ograds[0], sub3})
     };
 })
 .set_support_level(1);
diff --git a/nnvm/src/top/tensor/elemwise.cc b/nnvm/src/top/tensor/elemwise.cc
index d23d9c4f0a3f..87fbf5823d9b 100644
--- a/nnvm/src/top/tensor/elemwise.cc
+++ b/nnvm/src/top/tensor/elemwise.cc
@@ -190,7 +190,10 @@ NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_add)
     // y = n0 + n1
     // grad_0 = grad_y
     // grad_1 = grad_y
-    return std::vector<NodeEntry>{ograds[0], ograds[0]};
+    return std::vector<NodeEntry>{ MakeNode("copy", n->attrs.name + "_grad_0",
+                                            {ograds[0]}),
+                                   MakeNode("copy", n->attrs.name + "_grad_0",
+                                            {ograds[0]}) };
 });
 
 NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_sub)
@@ -311,7 +314,8 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(copy)
                   const std::vector<NodeEntry>& ograds){
     // y = copy(n0)
     // grad_0 = grad_y
-    return std::vector<NodeEntry>{ograds[0]};
+    return std::vector<NodeEntry>{ MakeNode("copy", n->attrs.name + "_grad_0",
+                                            {ograds[0]}) };
 });
 
 DMLC_REGISTER_PARAMETER(InitOpParam);
@@ -329,7 +333,7 @@ NNVM_REGISTER_INIT_OP(full)
 .add_arguments(InitOpWithScalarParam::__FIELDS__())
 .set_attr<FInferShape>("FInferShape", ZeroShape<InitOpWithScalarParam>)
 .set_attr<FInferType>("FInferType", ZeroType<InitOpWithScalarParam>)
-.set_support_level(1);
+.set_support_level(4);
 
 NNVM_REGISTER_INIT_OP(zeros)
 .describe(R"code(Fill target with zeros
@@ -341,7 +345,7 @@ NNVM_REGISTER_INIT_OP(zeros)
 .add_arguments(InitOpParam::__FIELDS__())
 .set_attr<FInferShape>("FInferShape", ZeroShape<InitOpParam>)
 .set_attr<FInferType>("FInferType", ZeroType<InitOpParam>)
-.set_support_level(1);
+.set_support_level(4);
 
 NNVM_REGISTER_INIT_OP(ones)
 .describe(R"code(Fill target with ones
@@ -353,7 +357,7 @@ NNVM_REGISTER_INIT_OP(ones)
 .add_arguments(InitOpParam::__FIELDS__())
 .set_attr<FInferShape>("FInferShape", ZeroShape<InitOpParam>)
 .set_attr<FInferType>("FInferType", ZeroType<InitOpParam>)
-.set_support_level(1);
+.set_support_level(4);
 
 // full_like
 NNVM_REGISTER_INIT_LIKE_OP(full_like)
@@ -364,21 +368,21 @@ as the input array
 .add_arguments(FillValueParam::__FIELDS__())
 .set_attr_parser(ParamParser<FillValueParam>)
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<FillValueParam>)
-.set_support_level(1);
+.set_support_level(4);
 
 NNVM_REGISTER_INIT_LIKE_OP(zeros_like)
 .describe(R"code(Return an array of zeros with the same shape and type
 as the input array.
 
 )code")
-.set_support_level(1);
+.set_support_level(4);
 
 NNVM_REGISTER_INIT_LIKE_OP(ones_like)
 .describe(R"code(Return an array of ones with the same shape and type
 as the input array.
 
 )code")
-.set_support_level(1);
+.set_support_level(4);
 
 // unary scalar op
 DMLC_REGISTER_PARAMETER(ScalarParam);
@@ -415,7 +419,8 @@ NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__add_scalar__)
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds){
-    return std::vector<NodeEntry>{ograds[0]};
+    return std::vector<NodeEntry>{ MakeNode("copy", n->attrs.name + "_grad_0",
+                                            {ograds[0]}) };
 });
 
 NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__sub_scalar__)
@@ -601,10 +606,11 @@ NNVM_REGISTER_ELEMWISE_REDUCE_OP(elemwise_sum)
     CHECK_EQ(ograds.size(), 1);
     std::vector<NodeEntry> ret;
     for (size_t i = 0; i < n->inputs.size(); i++) {
-      ret.push_back(ograds[0]);
+      ret.push_back(MakeNode("copy", n->attrs.name + "_grad_0", {ograds[0]}));
     }
     return ret;
-  });
+  })
+.set_support_level(4);
 
 NNVM_REGISTER_ELEMWISE_UNARY_OP(block_grad)
 .describe(R"code(Blocks gradient computation for input.
@@ -614,7 +620,8 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(block_grad)
   "FInplaceIdentity", [](const NodeAttrs& attrs){
     return std::vector<bool>{true};
 })
-.set_attr<nnvm::FGradient>("FGradient", MakeZeroGradNodes);
+.set_attr<nnvm::FGradient>("FGradient", MakeZeroGradNodes)
+.set_support_level(4);
 
 DMLC_REGISTER_PARAMETER(IndicatorParam);
 
@@ -628,7 +635,7 @@ with 1.0 if (left > right), otherwise 0.0 element-wise.
 .add_argument("rhs", "Tensor", "Second input")
 .set_num_inputs(2)
 .set_attr<nnvm::FInferShape>("FInferShape", ElemwiseShape<2, 1>)
-.set_support_level(1);
+.set_support_level(4);
 
 
 NNVM_REGISTER_INDICATOR_OP(less)
@@ -640,7 +647,7 @@ with 1.0 if (left < right), otherwise 0.0 element-wise.
 .add_argument("rhs", "Tensor", "Second input")
 .set_num_inputs(2)
 .set_attr<nnvm::FInferShape>("FInferShape", ElemwiseShape<2, 1>)
-.set_support_level(1);
+.set_support_level(4);
 
 NNVM_REGISTER_INDICATOR_OP(_max_mask)
   .describe(R"code(Function that returns a mask tensor
@@ -668,5 +675,73 @@ with 1.0 if the value is minimum over given axes, otherwise 0.0 element-wise.
 .set_attr<nnvm::FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_support_level(1);
 
+
+DMLC_REGISTER_PARAMETER(ClipParam);
+
+NNVM_REGISTER_OP(clip)
+.describe(R"doc(Clips (limits) the values in an array.
+Given an interval, values outside the interval are clipped to the interval edges.
+Clipping ``x`` between `a_min` and `a_x` would be::
+   clip(x, a_min, a_max) = max(min(x, a_max), a_min))
+Example::
+    x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+    clip(x,1,8) = [ 1.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  8.]
+)doc" NNVM_ADD_FILELINE)
+.set_num_inputs(1)
+.set_num_outputs(1)
+.set_attr_parser(ParamParser<ClipParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<ClipParam>)
+.set_attr<nnvm::FInferShape>("FInferShape", ElemwiseShape<1, 1>)
+.set_attr<nnvm::FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const ClipParam params = get<ClipParam>(attrs.parsed);
+    return Array<Tensor>{
+      topi::clip(inputs[0], tvm::make_const(tvm::Float(32), params.a_min),
+                 tvm::make_const(tvm::Float(32), params.a_max)) };
+  })
+.add_argument("data", "NDArray-or-Symbol", "Input array.")
+.add_arguments(ClipParam::__FIELDS__())
+.set_attr<nnvm::FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    // y = clip(x, a_min, a_max)
+    // min_mask = greater_equal(x, a_min*ones_like(x))
+    //          => ones_like(x) - less(x, a_min)
+    // max_mask = less_equal(x, a_max*ones_like(x))
+    //          => ones_like(x) - greater(x, a_max)
+    // grad_x = min_mask * max_mask * grad_y
+    CHECK_EQ(ograds.size(), 1);
+
+    NodeEntry sub0 = MakeNode("ones_like", n->attrs.name + "_grad_sub_0",
+                              {n->inputs[0]});
+    // min_mask
+    NodeEntry sub1 = MakeNode("__mul_scalar__", n->attrs.name + "_grad_sub_1",
+                              {sub0}, {{"scalar", n->attrs.dict["a_min"]}});
+    NodeEntry sub2 = MakeNode("less", n->attrs.name + "_grad_sub_2",
+                              {n->inputs[0], sub1});
+    NodeEntry sub3 = MakeNode("elemwise_sub", n->attrs.name + "_grad_sub_3",
+                              {sub0, sub2});
+
+    // max_mask
+    NodeEntry sub4 = MakeNode("__mul_scalar__", n->attrs.name + "_grad_sub_4",
+                              {sub0}, {{"scalar", n->attrs.dict["a_max"]}});
+    NodeEntry sub5 = MakeNode("greater", n->attrs.name + "_grad_sub_5",
+                              {n->inputs[0], sub4});
+    NodeEntry sub6 = MakeNode("elemwise_sub", n->attrs.name + "_grad_sub_6",
+                              {sub0, sub5});
+
+    // min_mask * max_mask
+    NodeEntry sub7 = MakeNode("elemwise_mul", n->attrs.name + "_grad_sub_7",
+                              {sub3, sub6});
+    return std::vector<NodeEntry>{
+      MakeNode("elemwise_mul", n->attrs.name + "_grad",
+               {sub7, ograds[0]})
+    };
+  })
+.set_support_level(4);
+
 }  // namespace top
 }  // namespace nnvm
diff --git a/nnvm/src/top/tensor/reduce.cc b/nnvm/src/top/tensor/reduce.cc
index 8eac2449b271..84a7dd0f0e12 100644
--- a/nnvm/src/top/tensor/reduce.cc
+++ b/nnvm/src/top/tensor/reduce.cc
@@ -137,7 +137,20 @@ Example::
                     const Array<Tensor>& inputs,
                     const Array<Tensor>& out_info) {
     const ReduceParam& param = nnvm::get<ReduceParam>(attrs.parsed);
-    auto axis = ShapeToArray(param.axis);
+    Array<Expr> axis;
+    if (param.exclude) {
+      std::set<dim_t> exclude_axis;
+      for (dim_t i = 0; i < param.axis.ndim(); ++i) {
+        exclude_axis.insert(param.axis[i]);
+      }
+      for (dim_t i = 0; i < inputs[0].ndim(); ++i) {
+        if (exclude_axis.count(i) == 0) {
+          axis.push_back(make_const(Int(32), i));
+        }
+      }
+    } else {
+      axis = ShapeToArray(param.axis);
+    }
     return Array<Tensor>{
       topi::sum(inputs[0], axis, param.keepdims) };
 })
@@ -150,7 +163,6 @@ Example::
       MakeNode("expand_like", n->attrs.name + "_grad",
                {ograds[0], n->inputs[0]},
                {{"axis", axis.str()},
-                {"keepdims", std::to_string(param.keepdims)},
                 {"exclude", std::to_string(param.exclude)}})
   };
 });
diff --git a/nnvm/src/top/tensor/state_op.cc b/nnvm/src/top/tensor/state_op.cc
index f275cf309511..ebce07696fe4 100644
--- a/nnvm/src/top/tensor/state_op.cc
+++ b/nnvm/src/top/tensor/state_op.cc
@@ -48,6 +48,15 @@ This is an experimental operator.
 .set_attr<FInplaceOption>(
   "FInplaceOption", [](const NodeAttrs& attrs) {
     return std::vector<std::pair<int, int> >{{1, 0}};
+})
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds){
+    return std::vector<NodeEntry>{
+      MakeNode("zeros_like", n->attrs.name + "_zero_grad",
+               {n->inputs[0]}),
+      ograds[0]
+    };
 });
 
 }  // namespace top
diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
index dcdedbb867f9..2457747341c9 100644
--- a/nnvm/src/top/tensor/transform.cc
+++ b/nnvm/src/top/tensor/transform.cc
@@ -229,29 +229,24 @@ will return a new array with shape ``(2,5,3,4)``.
 
 NNVM_REGISTER_OP(expand_like)
   .describe(R"code(Expand an input array with the shape of second array.
-
 This operation can always be composed of unsqueezing and expanding dims.
-
 Examples::
   input = [ 12.  19.  27.]
   input.shape = (3,)
-
   new_shape_array = [[[1,2],[2,3],[1,3]],
                      [[1,4],[4,3],[5,2]],
                      [[7,1],[7,2],[7,3]]]
   new_shape_array.shape = (3, 3, 2)
-
   expand_like(input, [1,2], new_shape_array) =
                     [[[12,12],[12,12],[12,12]],
                      [[19,19],[19,19],[19,19]],
                      [[27,27],[27,27],[27,27]]]
-
 )code" NNVM_ADD_FILELINE)
 .add_argument("input", "Tensor", "Source input")
 .add_argument("shape_like", "Tensor", "Input with new shape")
-.add_arguments(ReduceParam::__FIELDS__())
-.set_attr_parser(ParamParser<ReduceParam>)
-.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<ReduceParam>)
+.add_arguments(IndicatorParam::__FIELDS__())
+.set_attr_parser(ParamParser<IndicatorParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<IndicatorParam>)
 .set_attr<nnvm::FInferShape>("FInferShape", AssignOutputAttr<TShape, 1, 0>)
 .set_attr<nnvm::FInferType>("FInferType", ElemwiseType<2, 1>)
 .set_num_inputs(2)
@@ -259,7 +254,7 @@ Examples::
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
-    const ReduceParam& param = nnvm::get<ReduceParam>(n->attrs.parsed);
+    const IndicatorParam& param = nnvm::get<IndicatorParam>(n->attrs.parsed);
     std::ostringstream axis;
     axis << param.axis;
 
@@ -267,11 +262,11 @@ Examples::
       MakeNode("sum", n->attrs.name + "_grad",
                {ograds[0]},
                {{"axis", axis.str()},
-                {"keepdims", std::to_string(param.keepdims)},
-                {"exclude", std::to_string(param.exclude)}})
+                {"exclude", std::to_string(param.exclude)}}),
+      MakeNode("zeros_like", n->attrs.name + "_zero_grad", {n->inputs[1]})
     };
-})
-.set_support_level(1);
+  })
+  .set_support_level(4);
 
 // split
 DMLC_REGISTER_PARAMETER(SplitParam);
@@ -564,13 +559,10 @@ The significance of each is explained below:
 
 NNVM_REGISTER_OP(reshape_like)
   .describe(R"code(Reshapes the input array by the size of another array.
-
 For an input array with shape ``(d1, d2, ..., dk)``, `reshape_like` operation reshapes
 the input array into an output array with the same shape as the second input array.
-
 .. note::
     Sizes for both array should be compatible.
-
 )code" NNVM_ADD_FILELINE)
 .add_argument("data", "Tensor", "Input data.")
 .add_argument("shape_like", "Tensor", "Input data.")
@@ -589,10 +581,12 @@ the input array into an output array with the same shape as the second input arr
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
-    return MakeGradNode("reshape_like", n,
-                        {ograds[0], n->inputs[0]});
+    return std::vector<NodeEntry>{
+      MakeNode("reshape_like", n->attrs.name + "_grad", {ograds[0], n->inputs[0]}),
+      MakeNode("zeros_like", n->attrs.name + "_zero_grad", { n->inputs[1]})
+    };
 })
-.set_support_level(3);
+.set_support_level(4);
 
 // squeeze
 DMLC_REGISTER_PARAMETER(SqueezeParam);
@@ -680,7 +674,8 @@ Examples::
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
     return std::vector<NodeEntry>{
-      MakeNode("reshape_like", n->attrs.name + "_grad", {n->inputs[0]})
+      MakeNode("reshape_like", n->attrs.name + "_grad",
+               {ograds[0], n->inputs[0]})
     };
 })
 .set_support_level(1);
diff --git a/nnvm/tests/python/compiler/test_optimizer.py b/nnvm/tests/python/compiler/test_optimizer.py
new file mode 100644
index 000000000000..fd620271d861
--- /dev/null
+++ b/nnvm/tests/python/compiler/test_optimizer.py
@@ -0,0 +1,118 @@
+import numpy as np
+import tvm
+import nnvm
+import nnvm.compiler.optimizer as optimizer
+import nnvm.compiler.lr_scheduler as lr_scheduler
+
+from nnvm.testing.config import ctx_list
+from tvm.contrib import graph_runtime
+
+
+def helper(symbol, inputs, params, update_func, run_times, target, ctx, dtype="float32"):
+    ishapes = {}
+    np_inputs = {}
+    params_dict = {}
+    for (name, shape, s) in inputs:
+        ishapes.update({name: shape})
+        np_inputs.update({name: np.random.uniform(size=shape).astype(dtype)})
+    for (name, shape, s) in params:
+        np_inputs.update({name: np.random.uniform(size=shape).astype(dtype)})
+        params_dict.update({name: np_inputs[name]})
+
+    graph, lib, rt_params = nnvm.compiler.build(symbol, target, shape=ishapes)
+    m = graph_runtime.create(graph, lib, ctx)
+    m.set_input(**np_inputs)
+    m.set_input(**rt_params)
+    for _ in range(run_times):
+        m.run()
+    y_np = update_func(**np_inputs)
+    out = m.get_output(0, tvm.nd.empty(y_np.shape, dtype))
+    np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+
+
+def test_sgd():
+    for target, ctx in ctx_list():
+        data = nnvm.sym.Variable("data")
+        weight = nnvm.sym.Variable("weight")
+        out = nnvm.sym.elemwise_mul(data, weight ** 2)
+
+        dshape = (1, 2, 3)
+        wshape = dshape
+
+        base_lr = 0.1
+        lr_factor = 0.5
+        rescale_grad = 0.2
+        wd = 0.1
+        clip_gradient = 0.25
+
+        scheduler = lr_scheduler.FactorScheduler(base_lr=base_lr, step=1, factor=lr_factor)
+        opt = optimizer.SGD(learning_rate=base_lr, lr_scheduler=scheduler,
+                            rescale_grad=rescale_grad, clip_gradient=clip_gradient,
+                            wd=wd)
+        opt_sym = opt.minimize(out, var=weight)
+
+        inputs = [("data", dshape, data)]
+        params = [("weight", wshape, weight)]
+
+        def update_func(data, weight):
+            gradient_0 = data * 2 * weight * rescale_grad
+            gradient_0 = np.clip(gradient_0, -clip_gradient, clip_gradient)
+            weight_0 = weight - base_lr * lr_factor * (gradient_0 + wd * weight)
+            gradient_1 = data * 2 * weight_0 * rescale_grad
+            gradient_1 = np.clip(gradient_1, -clip_gradient, clip_gradient)
+            weight_1 = weight_0 - base_lr * (lr_factor ** 2) * (gradient_1 + wd * weight_0)
+            return weight_1
+
+        helper(opt_sym, inputs, params, update_func, 2, target, ctx)
+
+
+
+def test_adam():
+    for target, ctx in ctx_list():
+        data = nnvm.sym.Variable("data")
+        weight = nnvm.sym.Variable("weight")
+        out = nnvm.sym.elemwise_mul(data, weight ** 2)
+
+        dshape = (1, 2, 3)
+        wshape = dshape
+
+        base_lr = 0.1
+        beta1 = 0.9
+        beta2 = 0.999
+        epsilon = 1e-8
+        lr_factor = 0.5
+        rescale_grad = 0.2
+        wd = 0.1
+        clip_gradient = 0.25
+
+        scheduler = lr_scheduler.FactorScheduler(base_lr=base_lr, step=1, factor=lr_factor)
+        opt = optimizer.Adam(learning_rate=base_lr, beta1=beta1, beta2=beta2, epsilon=epsilon,
+                             lr_scheduler=scheduler, rescale_grad=rescale_grad,
+                             clip_gradient=clip_gradient, wd=wd)
+        opt_sym = opt.minimize(out, var=weight)
+
+        inputs = [("data", dshape, data)]
+        params = [("weight", wshape, weight)]
+
+        def update_func(data, weight):
+            rate_0 = np.sqrt(1 - beta2) / (1 - beta1)
+            lr_0 = base_lr * lr_factor * rate_0
+            gradient_0 = data * 2 * weight * rescale_grad
+            gradient_0 = np.clip(gradient_0, -clip_gradient, clip_gradient)
+            m_0 = (1 - beta1) * gradient_0
+            v_0 = (1 - beta2) * (gradient_0 ** 2)
+            weight_0 = weight - lr_0 * (m_0 / (np.sqrt(v_0) + epsilon) + wd * weight)
+            rate_1 = np.sqrt(1 - beta2 ** 2) / (1 - beta1 ** 2)
+            lr_1 = base_lr * (lr_factor ** 2) * rate_1
+            gradient_1 = data * 2 * weight_0 * rescale_grad
+            gradient_1 = np.clip(gradient_1, -clip_gradient, clip_gradient)
+            m_1 = beta1 * m_0 + (1 - beta1) * gradient_1
+            v_1 = beta2 * v_0 + (1 - beta2) * (gradient_1 ** 2)
+            weight_1 = weight_0 - lr_1 * (m_1 / (np.sqrt(v_1) + epsilon) + wd * weight_0)
+            return weight_1
+
+        helper(opt_sym, inputs, params, update_func, 2, target, ctx)
+
+if __name__ == "__main__":
+    test_sgd()
+    test_adam()
diff --git a/nnvm/tests/python/compiler/test_top_level1.py b/nnvm/tests/python/compiler/test_top_level1.py
index abe30d1ec8d0..480aa271af8f 100644
--- a/nnvm/tests/python/compiler/test_top_level1.py
+++ b/nnvm/tests/python/compiler/test_top_level1.py
@@ -8,15 +8,14 @@
 
 
 def helper(symbol, inputs, dtype,
-           np_forward, np_backward=None):
+           np_forward, np_backward=None, need_input=True, need_head_grads=True):
     ishapes = {}
     input_syms = []
     np_inputs = {}
-    for (k, v) in inputs.items():
-        ishapes.update({k: v[0]})
-        np_inputs.update({k: np.random.uniform(size=v[0]).astype(dtype)})
-        if len(v) > 1:
-            input_syms.append(v[1])
+    for (name, shape, s) in inputs:
+        ishapes.update({name: shape})
+        np_inputs.update({name: np.random.uniform(size=shape).astype(dtype)})
+        input_syms.append(s)
 
     for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(symbol, target, ishapes)
@@ -25,23 +24,26 @@ def helper(symbol, inputs, dtype,
         y_np = np_forward(**np_inputs)
         out = m.get_output(0, tvm.nd.empty(y_np.shape, dtype))
         np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
-
         # backward
         if np_backward:
             graph._set_symbol_list_attr("grad_ys", symbol)
-            for x in input_syms:
-                graph._set_symbol_list_attr("grad_xs", x)
-            graph._set_symbol_list_attr("grad_ys_out_grad", sym.Variable("head_grads"))
+            graph._set_symbol_list_attr("grad_xs", input_syms)
+            graph._set_symbol_list_attr("grad_ys_out_grad", sym.Variable("head_grads", shape=y_np.shape))
             graph = graph.apply("Gradient")
             ishapes.update({"head_grads": y_np.shape})
             graph, lib, _ = nnvm.compiler.build(graph, target, ishapes)
             m = graph_runtime.create(graph, lib, ctx)
             head_grads = np.random.uniform(size=y_np.shape).astype(dtype)
-            y_np = head_grads * np_backward(**np_inputs)
-            m.run(head_grads=head_grads, **np_inputs)
-            out = m.get_output(0, tvm.nd.empty(y_np.shape, dtype))
-
-            np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+            y_np = np_backward(head_grads=head_grads, **np_inputs)
+            b_inputs = {}
+            if need_input:
+                b_inputs.update(np_inputs)
+            if need_head_grads:
+                b_inputs.update({"head_grads":head_grads})
+            m.run(**b_inputs)
+            for i in range(len(y_np)):
+                out = m.get_output(i, tvm.nd.empty(y_np[i].shape, dtype))
+                np.testing.assert_allclose(out.asnumpy(), y_np[i], atol=1e-5, rtol=1e-5)
 
 
 def test_relu():
@@ -52,10 +54,15 @@ def forward(x):
         x = (x < 0) * x * 0.3 + (x > 0) * x - 0.2
         return (x > 0) * x
 
+    def backward(head_grads, x):
+        sub = (x < 0) * x * 0.3 + (x > 0) * x - 0.2
+        return [(sub > 0).astype("float") * \
+                ((x > 0).astype("float") + 0.3 * (x < 0).astype("float")) * head_grads]
+
     dtype = "float32"
     dshape = (1, 3, 32, 32)
-    inputs = {'x': (dshape, x)}
-    helper(y, inputs, dtype, forward)
+    inputs = [('x', dshape, x)]
+    helper(y, inputs, dtype, forward, backward)
 
 
 def test_sym_scalar_pow():
@@ -66,12 +73,12 @@ def test_sym_scalar_pow():
     def forward(x):
         return x**scalar
 
-    def backward(x):
-        return scalar * x**(scalar -  1)
+    def backward(head_grads, x):
+        return [scalar * x**(scalar -  1) * head_grads]
 
     dtype = "float32"
     dshape = (1, 3, 32, 32)
-    inputs = {'x': (dshape, x)}
+    inputs = [('x', dshape, x)]
     helper(y, inputs, dtype, forward, backward)
 
 
@@ -83,12 +90,12 @@ def test_scalar_sym_pow():
     def forward(x):
         return scalar**x
 
-    def backward(x):
-        return np.log(scalar) * scalar**x
+    def backward(head_grads, x):
+        return [np.log(scalar) * scalar**x * head_grads]
 
     dtype = "float32"
     dshape = (1, 3, 32, 32)
-    inputs = {'x': (dshape, x)}
+    inputs = [('x', dshape, x)]
     helper(y, inputs, dtype, forward, backward)
 
 
@@ -99,12 +106,12 @@ def test_exp():
     def forward(x):
         return np.exp(x)
 
-    def backward(x):
-        return np.exp(x)
+    def backward(head_grads, x):
+        return [np.exp(x) * head_grads]
 
     dtype = "float32"
     dshape = (1, 3, 32, 32)
-    inputs = {'x': (dshape, x)}
+    inputs = [('x', dshape, x)]
     helper(y, inputs, dtype, forward, backward)
 
 
@@ -115,12 +122,12 @@ def test_log():
     def forward(x):
         return np.log(x)
 
-    def backward(x):
-        return 1. / x
+    def backward(head_grads, x):
+        return [1. / x * head_grads]
 
     dtype = "float32"
     dshape = (1, 3, 32, 32)
-    inputs = {'x': (dshape, x)}
+    inputs = [('x', dshape, x)]
     helper(y, inputs, dtype, forward, backward)
 
 
@@ -131,13 +138,13 @@ def test_tanh():
     def forward(x):
         return np.sinh(x) / np.cosh(x)
 
-    def backward(x):
+    def backward(head_grads, x):
         y_np = forward(x)
-        return (1 - y_np**2)
+        return [(1 - y_np**2) * head_grads]
 
     dtype = "float32"
     dshape = (1, 3, 32, 32)
-    inputs = {'x': (dshape, x)}
+    inputs = [('x', dshape, x)]
     helper(y, inputs, dtype, forward, backward)
 
 
@@ -148,13 +155,13 @@ def test_sigmoid():
     def forward(x):
         return 1.0 / (1.0 + np.exp(-x))
 
-    def backward(x):
+    def backward(head_grads, x):
         y_np = forward(x)
-        return y_np *(1 - y_np)
+        return [y_np *(1 - y_np) * head_grads]
 
     dtype = "float32"
     dshape = (1, 3, 32, 32)
-    inputs = {'x': (dshape, x)}
+    inputs = [('x', dshape, x)]
     helper(y, inputs, dtype, forward, backward)
 
 
@@ -165,10 +172,15 @@ def test_softmax():
     def forward(x):
         return topi.testing.softmax_python(x)
 
+    def backward(head_grads, x):
+        y = topi.testing.softmax_python(x)
+        grad = y * (head_grads - np.sum(y * head_grads, axis=1, keepdims=True))
+        return [grad]
+
     dtype = "float32"
     dshape = (10, 1000)
-    inputs = {'x': (dshape, x)}
-    helper(y, inputs, dtype, forward)
+    inputs = [('x', dshape, x)]
+    helper(y, inputs, dtype, forward), backward
 
 
 def test_log_softmax():
@@ -178,26 +190,32 @@ def test_log_softmax():
     def forward(x):
         return topi.testing.log_softmax_python(x)
 
+    def backward(head_grads, x):
+        y = topi.testing.log_softmax_python(x)
+        grad = head_grads - np.sum(y * head_grads, axis=1, keepdims=True)
+        return [grad]
+
     dtype = "float32"
     dshape = (10, 1000)
-    inputs = {'x': (dshape, x)}
-    helper(y, inputs, dtype, forward)
+    inputs = [('x', dshape, x)]
+    helper(y, inputs, dtype, forward, backward)
 
 
 def test_dense():
-    x = sym.Variable("x")
-    y = sym.dense(x, units=3, name="dense")
+    x = sym.Variable("x", shape=(10, 100))
+    w = sym.Variable("dense_weight", shape=(3, 100))
+    b = sym.Variable("dense_bias", shape=(3,))
+    y = sym.dense(x, w, b, use_bias=True, units=3, name="dense")
     y = sym.flatten(y)
 
     def forward(x, dense_weight, dense_bias):
         return np.dot(x, dense_weight.T) + dense_bias
-
     dtype = "float32"
-    inputs = {
-        'x': ((10, 100), x),
-        'dense_weight': ((3, 100),),
-        'dense_bias': ((3,),)
-    }
+    inputs = [
+        ('x', (10, 100), x),
+        ('dense_weight', (3, 100), w),
+        ('dense_bias', (3,), b)
+    ]
     helper(y, inputs, dtype, forward)
 
 
@@ -215,13 +233,13 @@ def forward(x, gamma, beta, moving_mean, moving_var):
         return (x - moving_mean) / np.sqrt(moving_var + eps) * gamma + beta
 
     dtype = "float32"
-    inputs = {
-        'x': ((10, 20), x),
-        'gamma': ((20,),),
-        'beta': ((20,),),
-        'moving_mean': ((20,),),
-        'moving_var': ((20,),)
-    }
+    inputs = [
+        ('x', (10, 20), x),
+        ('gamma', (20,), gamma),
+        ('beta', (20,), beta),
+        ('moving_mean', (20,), moving_var),
+        ('moving_var', (20,), moving_mean)
+    ]
 
     helper(y, inputs,  dtype, forward)
 
@@ -283,9 +301,12 @@ def verify_squeeze(dshape, axis):
     def forward(x):
         return np.squeeze(x, axis=axis) + 1
 
+    def backward(head_grads, x):
+        return [np.reshape(head_grads, x.shape)]
+
     dtype = "float32"
-    inputs = {'x': (dshape, x)}
-    helper(y, inputs, dtype, forward)
+    inputs = [('x', dshape, x)]
+    helper(y, inputs, dtype, forward, backward)
 
 
 def test_squeeze():
@@ -304,7 +325,7 @@ def forward(x):
                       mode='constant', constant_values=1.)
 
     dtype = "float32"
-    inputs = {'x': ((1, 3, 28, 28), x)}
+    inputs = [('x', (1, 3, 28, 28), x)]
     helper(y, inputs, dtype, forward)
 
 
diff --git a/nnvm/tests/python/compiler/test_top_level4.py b/nnvm/tests/python/compiler/test_top_level4.py
index ad09d73ec28d..c6e8620fc972 100644
--- a/nnvm/tests/python/compiler/test_top_level4.py
+++ b/nnvm/tests/python/compiler/test_top_level4.py
@@ -6,6 +6,46 @@
 import nnvm.compiler
 from nnvm.testing.config import ctx_list
 
+
+def helper(symbol, inputs, dtype,
+           np_forward, np_backward=None, need_input=True, need_head_grads=True):
+    ishapes = {}
+    input_syms = []
+    np_inputs = {}
+    for (name, shape, s) in inputs:
+        ishapes.update({name: shape})
+        np_inputs.update({name: np.random.uniform(size=shape).astype(dtype)})
+        input_syms.append(s)
+
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(symbol, target, ishapes)
+        m = graph_runtime.create(graph, lib, ctx)
+        m.run(**np_inputs)
+        y_np = np_forward(**np_inputs)
+        out = m.get_output(0, tvm.nd.empty(y_np.shape, dtype))
+        np.testing.assert_allclose(out.asnumpy(), y_np, atol=1e-5, rtol=1e-5)
+        # backward
+        if np_backward:
+            graph._set_symbol_list_attr("grad_ys", symbol)
+            graph._set_symbol_list_attr("grad_xs", input_syms)
+            graph._set_symbol_list_attr("grad_ys_out_grad", sym.Variable("head_grads", shape=y_np.shape))
+            graph = graph.apply("Gradient")
+            ishapes.update({"head_grads": y_np.shape})
+            graph, lib, _ = nnvm.compiler.build(graph, target, ishapes)
+            m = graph_runtime.create(graph, lib, ctx)
+            head_grads = np.random.uniform(size=y_np.shape).astype(dtype)
+            y_np = np_backward(head_grads=head_grads, **np_inputs)
+            b_inputs = {}
+            if need_input:
+                b_inputs.update(np_inputs)
+            if need_head_grads:
+                b_inputs.update({"head_grads":head_grads})
+            m.run(**b_inputs)
+            for i in range(len(y_np)):
+                out = m.get_output(i, tvm.nd.empty(y_np[i].shape, dtype))
+                np.testing.assert_allclose(out.asnumpy(), y_np[i], atol=1e-5, rtol=1e-5)
+
+
 def verify_transpose(dshape, axes):
     x = sym.Variable("x")
     if axes:
@@ -66,13 +106,245 @@ def verify_reshape(dshape, oshape):
         out = m.get_output(0, tvm.nd.empty(out_np.shape))
         np.testing.assert_allclose(out.asnumpy(), out_np, atol=1e-5, rtol=1e-5)
 
+
 def test_reshape():
     verify_reshape((2, 3, 4), (-1, 2, 1))
     verify_reshape((2, 3, 4), (8, 3))
     verify_reshape((4, 7), (2, 7, 2))
 
+
+def test_clip():
+    x = sym.Variable("x")
+    a_min=0.2
+    a_max=0.75
+    y = sym.clip(x, a_min=a_min, a_max=a_max)
+
+    def forward(x):
+        return np.clip(x, a_min=a_min, a_max=a_max)
+
+    def backward(head_grads, x):
+        mask1 = np.greater_equal(x, a_min).astype("float")
+        mask2 = np.less_equal(x, a_max).astype("float")
+        return [head_grads * mask1 * mask2]
+
+
+    dtype = "float32"
+    inputs = [('x', (3, 4, 5), x)]
+    helper(y, inputs, dtype, forward, backward)
+
+
+def test_greater():
+    l = sym.Variable("l")
+    r = sym.Variable("r")
+    y = sym.greater(l, r)
+
+    def forward(l, r):
+        return np.greater(l, r).astype("float32")
+
+    def backward(head_grads, l, r):
+        return [np.zeros_like(l)]
+
+
+    dtype = "float32"
+    inputs = [('l', (3, 4, 5), l),
+              ('r', (3, 4, 5), r)]
+    helper(y, inputs, dtype, forward, backward, need_head_grads=False)
+
+
+def test_less():
+    l = sym.Variable("l")
+    r = sym.Variable("r")
+    y = sym.less(l, r)
+
+    def forward(l, r):
+        return np.less(l, r).astype("float32")
+
+    def backward(head_grads, l, r):
+        return [np.zeros_like(l)]
+
+
+    dtype = "float32"
+    inputs = [('l', (3, 4, 5), l),
+              ('r', (3, 4, 5), r)]
+    helper(y, inputs, dtype, forward, backward, need_head_grads=False)
+
+
+def test_reshape_like():
+    x = sym.Variable("x")
+    y = sym.Variable("y")
+    z = sym.reshape_like(x, y)
+
+    def forward(x, y):
+        return np.reshape(x, y.shape)
+
+    def backward(head_grads, x, y):
+        return [np.reshape(head_grads, x.shape),
+                np.zeros_like(y)]
+
+
+    dtype = "float32"
+    inputs = [('x', (3, 4, 5), x),
+              ('y', (5, 4, 3), y)]
+    helper(z, inputs, dtype, forward, backward)
+
+
+def verify_expand_like(in_shape, out_shape, axis, exclude):
+    x = sym.Variable("x")
+    y = sym.Variable("y")
+    z = sym.expand_like(x, y, axis=axis, exclude=exclude)
+
+    def forward(x, y):
+        odim = len(out_shape)
+        real_axis = [i if i >= 0 else i + odim for i in axis]
+        real_axis = sorted(real_axis)
+        if exclude:
+            real_axis = list(set(range(odim)) - set(real_axis))
+        for i in real_axis:
+            x = np.expand_dims(x, i).astype(x.dtype)
+        for i in real_axis:
+            x = np.concatenate([x]*out_shape[i], axis=i).astype(x.dtype)
+
+        return x
+
+    def backward(head_grads, x, y):
+        odim = len(out_shape)
+        real_axis = [i if i >= 0 else i + odim for i in axis]
+        real_axis = sorted(real_axis)
+        if exclude:
+            real_axis = list(set(range(odim)) - set(real_axis))
+        return [np.sum(head_grads, axis=tuple(real_axis)),
+                np.zeros_like(y)]
+
+
+    dtype = "float32"
+    inputs = [('x', in_shape, x),
+              ('y', out_shape, y)]
+    helper(z, inputs, dtype, forward, backward, need_input=False)
+
+
+def test_expand_like():
+    verify_expand_like((3,), (3, 2), [1], False)
+    verify_expand_like((2,), (2, 3), [1], False)
+    verify_expand_like((3, 4), (3, 5, 4), [1], False)
+    verify_expand_like((5, 7), (5, 6, 7, 8), [0, 2], True)
+
+
+def verify_elemwise_sum(num_args):
+    s = [sym.Variable("input" + str(i)) for i in range(num_args)]
+    y = sym.elemwise_sum(*s, num_args=num_args)
+
+    def forward(**inputs):
+        return np.sum(np.array(list(inputs.values())), axis=0)
+
+    def backward(head_grads, **inputs):
+        return [head_grads] * num_args
+
+    dtype = "float32"
+    inputs = [("input" + str(i), (3, 4, 5), s[i])
+              for i in range(num_args)]
+    helper(y, inputs, dtype, forward, backward, need_input=False)
+
+
+def test_elemwise_sum():
+    verify_elemwise_sum(1)
+    verify_elemwise_sum(5)
+    verify_elemwise_sum(7)
+
+
+def test_block_grad():
+    x = sym.Variable("x")
+    y = sym.block_grad(x)
+
+    def forward(x):
+        return x
+
+    def backward(head_grads, x):
+        return [np.zeros_like(head_grads)]
+
+
+    dtype = "float32"
+    inputs = [('x', (3, 4, 5), x)]
+    helper(y, inputs, dtype, forward, backward, need_head_grads=False)
+
+
+def test_full():
+    shape = (3, 4, 5)
+    value = 7
+    dtype = "float32"
+    for target, ctx in ctx_list():
+        data = sym.Variable("data", dtype=dtype)
+        # full_like
+        s = sym.full_like(data=data, fill_value=value, name="s")
+        graph, lib, _ = nnvm.compiler.build(s, target, {"data": shape})
+        m = graph_runtime.create(graph, lib, ctx)
+        m.run(data=np.random.uniform(size=shape).astype(dtype))
+        out = m.get_output(0, tvm.nd.empty(shape, dtype=dtype))
+        np.testing.assert_allclose(
+            out.asnumpy(),
+            np.full(shape, fill_value=value, dtype=dtype),
+            atol=1e-5, rtol=1e-5)
+        # ones_like
+        s = sym.ones_like(data=data, fill_value=value, name="s")
+        graph, lib, _ = nnvm.compiler.build(s, target, {"data": shape})
+        m = graph_runtime.create(graph, lib, ctx)
+        m.run(data=np.random.uniform(size=shape).astype(dtype))
+        out = m.get_output(0, tvm.nd.empty(shape, dtype=dtype))
+        np.testing.assert_allclose(
+            out.asnumpy(),
+            np.full(shape, fill_value=1, dtype=dtype),
+            atol=1e-5, rtol=1e-5)
+        # zeros_like
+        s = sym.zeros_like(data=data, fill_value=value, name="s")
+        graph, lib, _ = nnvm.compiler.build(s, target, {"data": shape})
+        m = graph_runtime.create(graph, lib, ctx)
+        m.run(data=np.random.uniform(size=shape).astype(dtype))
+        out = m.get_output(0, tvm.nd.empty(shape, dtype=dtype))
+        np.testing.assert_allclose(
+            out.asnumpy(),
+            np.full(shape, fill_value=0, dtype=dtype),
+            atol=1e-5, rtol=1e-5)
+        # full
+        s = sym.full(shape=shape, dtype=dtype, fill_value=value, name="s")
+        graph, lib, _ = nnvm.compiler.build(s, target)
+        m = graph_runtime.create(graph, lib, ctx)
+        m.run()
+        out = m.get_output(0, tvm.nd.empty(shape, dtype=dtype))
+        np.testing.assert_allclose(
+            out.asnumpy(),
+            np.full(shape, fill_value=value, dtype=dtype),
+            atol=1e-5, rtol=1e-5)
+        # ones
+        s = sym.ones(shape=shape, dtype=dtype, name="s")
+        graph, lib, _ = nnvm.compiler.build(s, target)
+        m = graph_runtime.create(graph, lib, ctx)
+        m.run()
+        out = m.get_output(0, tvm.nd.empty(shape, dtype=dtype))
+        np.testing.assert_allclose(
+            out.asnumpy(),
+            np.full(shape, fill_value=1, dtype=dtype),
+            atol=1e-5, rtol=1e-5)
+        # zeros
+        s = sym.zeros(shape=shape, dtype=dtype, name="s")
+        graph, lib, _ = nnvm.compiler.build(s, target)
+        m = graph_runtime.create(graph, lib, ctx)
+        m.run()
+        out = m.get_output(0, tvm.nd.empty(shape, dtype=dtype))
+        np.testing.assert_allclose(
+            out.asnumpy(),
+            np.full(shape, fill_value=0, dtype=dtype),
+            atol=1e-5, rtol=1e-5)
+
+
 if __name__ == "__main__":
     test_reshape()
     test_reduce()
     test_tranpose()
+    test_clip()
+    test_greater()
+    test_less()
+    test_reshape_like()
+    test_expand_like()
+    test_elemwise_sum()
+    test_block_grad()
+    test_full()
     print(nnvm.compiler.engine.dump())

From 6bbc177373c4ce65a4e51d287423550cdbed8007 Mon Sep 17 00:00:00 2001
From: Wenhao Hu <fumihwh@gmail.com>
Date: Fri, 30 Mar 2018 13:56:05 +0900
Subject: [PATCH 624/949] Onnx opset support (#416)

---
 nnvm/python/nnvm/frontend/onnx.py             | 700 +++++++++++-------
 .../ci_build/install/ubuntu_install_onnx.sh   |   4 +-
 .../python/frontend/onnx/test_forward.py      |   4 +-
 nnvm/tests/python/frontend/onnx/test_graph.py |   4 +-
 nnvm/tutorials/from_onnx.py                   |   4 +-
 5 files changed, 437 insertions(+), 279 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/onnx.py b/nnvm/python/nnvm/frontend/onnx.py
index b34d3bbd8283..0ab6f4a331ed 100644
--- a/nnvm/python/nnvm/frontend/onnx.py
+++ b/nnvm/python/nnvm/frontend/onnx.py
@@ -4,87 +4,119 @@
 import tvm
 from .. import symbol as _sym
 from .. import graph as _graph
-from .. compiler import graph_util
+from ..compiler import graph_util
 from .common import get_nnvm_op, Renamer, AttrConverter as AttrCvt
 
 __all__ = ['from_onnx']
 
-def _revert_caffe2_pad(attr):
-    """Caffe2 require two times the normal padding."""
-    if len(attr) == 4:
-        attr = attr[:2]
-    elif len(attr) == 2:
-        pass
-    else:
-        raise ValueError("Invalid caffe2 type padding: {}".format(attr))
-    return attr
 
-def _math_name_picker(surfix):
-    def _impl(attr):
-        if attr.get('broadcast', 0):
-            return 'broadcast_' + surfix
-        return 'elemwise_' + surfix
-    return _impl
+class OnnxOpConverter(object):
+    """ A helper class for holding onnx op converters.
+    """
 
-def _broadcast_constraint():
-    def _broadcast_check(attrs):
-        if attrs.get('axis', None):
-            return False
-        return True
-    return _broadcast_check, "Specifying broadcast axis not allowed."
+    @classmethod
+    def get_converter(cls, opset):
+        """ Get converter matches given opset.
 
-def _dimension_picker(prefix, surfix=''):
-    def _impl(attr):
-        kernel = attr['kernel_shape']
-        if len(kernel) == 2:
-            return prefix + '2d' + surfix
+        :param opset: opset from model.
+        :return: converter, which should be `_impl_vx`. Number x is the biggest
+            number smaller than or equal to opset belongs to all support versions.
+        """
+        versions = [
+            int(d.replace('_impl_v', '')) for d in dir(cls) if '_impl_v' in d
+        ]
+        versions = sorted(versions + [opset])
+        version = versions[
+            max([i for i, v in enumerate(versions) if v == opset]) - 1]
+        if hasattr(cls, '_impl_v{}'.format(version)):
+            return getattr(cls, '_impl_v{}'.format(version))
         else:
-            raise NotImplementedError("Only 2d kernel supported.")
-    return _impl
+            raise NotImplementedError(
+                'opset version {} of {} not implemented'.format(
+                    version, cls.__name__))
 
-def _dimension_constraint():
-    def _dim_check(attrs):
-        if len(attrs['kernel_shape']) == 2:
-            return True
-        return False
-    return _dim_check, "Only 2d kernel supported."
 
-def _infer_channels(inputs, params, transpose=False):
-    """A hack for getting 'channles' or 'units' since onnx don't provide
-    these attributes. We check the shape of weights provided to get the number.
+class Elemwise(OnnxOpConverter):
+    """ A helper class for elemwise op converters.
     """
-    g = _graph.create(inputs)
-    shape_dict = {k: v.shape for k, v in params.items()}
-    _, out_shapes = graph_util.infer_shape(g, **shape_dict)
-    channels = out_shapes[0][0] if not transpose else out_shapes[0][1]
-    return channels
 
-def _elemwise(name):
-    def _impl(inputs, attr, *args):
-        assert len(inputs) == 2, "Math op take 2 inputs, {} given".format(len(inputs))
-        op_name = _math_name_picker(name)(attr)
+    name = ''
+
+    @classmethod
+    def _math_name_picker(cls, suffix):
+
+        def _impl(attr):
+            if attr.get('broadcast', 0):
+                return 'broadcast_' + suffix
+            return 'elemwise_' + suffix
+
+        return _impl
+
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
+        assert len(inputs) == 2, "Math op take 2 inputs, {} given".format(
+            len(inputs))
+        op_name = cls._math_name_picker(cls.name)(attr)
         axis = int(attr.get('axis', 0))
         conv_ops = ["conv2d", "conv2d_transpose"]
         if op_name == 'broadcast_add' and inputs[0].attr('op_name') in conv_ops:
             # TODO(zhreshold): remove hard coded infershape
             inputs[1] = _sym.expand_dims(inputs[1], axis=axis, num_newaxis=2)
         return get_nnvm_op(op_name)(*inputs)
-    return _impl
 
-def _pooling(name):
-    return AttrCvt(
-        op_name=_dimension_picker(name),
-        transforms={
-            'kernel_shape': 'pool_size',
-            'pads': ('padding', (0, 0), _revert_caffe2_pad)},
-        # very weird attributes here in onnx, force check
-        ignores=['dilations'],
-        # TODO(zhreshold): make sure ceil_mode in onnx, and layout?
-        extras={'ceil_mode': False},
-        custom_check=_dimension_constraint())
-
-def _conv():
-    def _impl(inputs, attr, params):
+
+class Pool(OnnxOpConverter):
+    """ A helper class for pool op converters.
+    """
+
+    name = ''
+
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
+        return AttrCvt(
+            op_name=_dimension_picker(cls.name),
+            transforms={
+                'kernel_shape': 'pool_size',
+                'pads': ('padding', (0, 0), _revert_caffe2_pad)
+            },
+            # very weird attributes here in onnx, force check
+            ignores=['dilations'],
+            # TODO(zhreshold): make sure ceil_mode in onnx, and layout?
+            extras={'ceil_mode': False},
+            custom_check=_dimension_constraint())(inputs, attr, params)
+
+
+class Absolute(OnnxOpConverter):
+
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
+        return _sym.relu(inputs[0]) + _sym.relu(_sym.negative(inputs[0]))
+
+
+class Add(Elemwise):
+    name = 'add'
+
+
+class AveragePool(Pool):
+    name = 'avg_pool'
+
+
+class BatchNorm(OnnxOpConverter):
+
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
+        # TODO(zhreshold): 'spatial' is not properly handled here.
+        return AttrCvt(
+            op_name='batch_norm',
+            disables=['momentum'],
+            ignores=['spatial', 'is_test', 'consumed_inputs'])(inputs, attr,
+                                                               params)
+
+
+class Conv(OnnxOpConverter):
+
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
         # get number of channels
         channels = _infer_channels(inputs[1], params)
         attr['channels'] = channels
@@ -94,13 +126,16 @@ def _impl(inputs, attr, params):
                 'kernel_shape': 'kernel_size',
                 'dilations': ('dilation', (0, 0)),
                 'pads': ('padding', (0, 0), _revert_caffe2_pad),
-                'group': ('groups', 1)},
+                'group': ('groups', 1)
+            },
             extras={'use_bias': len(inputs) == 3},
-            custom_check=_dimension_constraint())(inputs, attr)
-    return _impl
+            custom_check=_dimension_constraint())(inputs, attr, params)
 
-def _conv_transpose():
-    def _impl(inputs, attr, params):
+
+class ConvTranspose(OnnxOpConverter):
+
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
         # get number of channels
         channels = _infer_channels(inputs[1], params, True)
         attr['channels'] = channels
@@ -111,31 +146,34 @@ def _impl(inputs, attr, params):
             transforms={
                 'kernel_shape': 'kernel_size',
                 'dilations': ('dilation', (0, 0)),
-                'pads': ('padding', (0, 0), _revert_caffe2_pad)},
+                'pads': ('padding', (0, 0), _revert_caffe2_pad)
+            },
             disables=['output_shape'],
             extras={'use_bias': len(inputs) == 3},
-            custom_check=_dimension_constraint())(inputs, attr)
-    return _impl
+            custom_check=_dimension_constraint())(inputs, attr, params)
 
-def _fully_connected():
-    def _impl(inputs, attr, params):
-        # get number of channels
-        channels = _infer_channels(inputs[1], params)
-        attr['units'] = channels
-        return AttrCvt('dense', ignores=['axis', 'axis_w'])(inputs, attr)
-    return _impl
 
-def _batch_norm():
-    # TODO(zhreshold): 'spatial' is not properly handled here.
-    return AttrCvt(
-        op_name='batch_norm',
-        disables=['momentum'],
-        ignores=['spatial', 'is_test', 'consumed_inputs'])
+class Div(Elemwise):
+    name = 'div'
 
 
-def _gemm():
-    def _impl(inputs, attr, params):
-        assert len(inputs) == 3, "Gemm op take 3 inputs, {} given".format(len(inputs))
+class Elu(OnnxOpConverter):
+
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
+        alpha = float(attr.get('alpha', 1.0))
+        return -alpha * _sym.relu(1 - _sym.exp(inputs[0])) + _sym.relu(
+            inputs[0])
+
+
+class Gemm(OnnxOpConverter):
+    """ Operator converter for Gemm.
+    """
+
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
+        assert len(inputs) == 3, "Gemm op take 3 inputs, {} given".format(
+            len(inputs))
         # Y = alpha * A * B + beta * C
         alpha = float(attr.get('alpha', 1.0))
         beta = float(attr.get('beta', 1.0))
@@ -147,217 +185,325 @@ def _impl(inputs, attr, params):
             inputs[0] = _sym.transpose(inputs[0], axes=(1, 0))
         if not transB:
             inputs[1] = _sym.transpose(inputs[1], axes=(1, 0))
-        return _sym.dense(alpha * inputs[0], inputs[1], beta * inputs[2], units=channels)
-    return _impl
+        return _sym.dense(
+            alpha * inputs[0], inputs[1], beta * inputs[2], units=channels)
 
-def _thresholded_relu():
-    def _impl(inputs, attr, params):
-        alpha = float(attr.get('alpha', 0.0))
-        return _sym.relu(inputs[0] - alpha)
-    return _impl
 
-def _scaled_tanh():
-    def _impl(inputs, attr, params):
-        alpha = float(attr.get('alpha', 1.0))
-        beta = float(attr.get('beta', 1.0))
-        return _sym.tanh(beta * inputs[0]) * alpha
-    return _impl
+class MaxPool(Pool):
+    name = 'max_pool'
 
-def parametric_soft_plus():
-    def _impl(inputs, attr, params):
+
+class Mul(Elemwise):
+    name = 'mul'
+
+
+class Pad(OnnxOpConverter):
+
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
+        # get number of channels
+        channels = _infer_channels(inputs[1], params, True)
+        attr['channels'] = channels
+        groups = attr.pop('group')
+        attr['groups'] = groups
+        return AttrCvt(
+            op_name='pad',
+            transforms={
+                'value': 'pad_value',
+                'pads': 'pad_width'
+            },
+            custom_check=lambda attrs: attrs.get('mode') == 'constant')(
+                inputs, attr, params)
+
+
+class ParametricSoftPlus(OnnxOpConverter):
+
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
         alpha = float(attr.get('alpha', 1.0))
         beta = float(attr.get('beta', 1.0))
         return _sym.log(_sym.exp(beta * inputs[0]) + 1) * alpha
-    return _impl
 
-def _scale():
-    def _impl(inputs, attr, params):
+
+class Prelu(OnnxOpConverter):
+
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
+        assert len(inputs) == 2, "Prelu need 2 inputs, {} given".format(
+            len(inputs))
+        channels = _infer_channels(inputs[1], params, False)
+        if channels == 1:
+            return inputs[0] * inputs[1]
+        return _sym.broadcast_mul(inputs[0], inputs[1])
+
+
+class Reciprocal(OnnxOpConverter):
+
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
+        return 1.0 / inputs[0]
+
+
+class Reshape(OnnxOpConverter):
+
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
+        return _sym.reshape(inputs[0], shape=attr['shape'])
+
+    @classmethod
+    def _impl_v5(cls, inputs, attr, params):
+        return _sym.reshape(
+            inputs[0],
+            shape=tuple(params[inputs[1].list_output_names()[0]].asnumpy()))
+
+
+class Scale(OnnxOpConverter):
+
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
         scale = float(attr.get('scale', 1.0))
         return inputs[0] * scale
-    return _impl
 
-def _absolute():
-    """This is a workaround."""
-    def _impl(inputs, attr, params):
-        return _sym.relu(inputs[0]) + _sym.relu(_sym.negative(inputs[0]))
-    return _impl
 
-def _reciprocal():
-    def _impl(inputs, attr, params):
-        return 1.0 / inputs[0]
-    return _impl
+class Selu(OnnxOpConverter):
 
-def _selu():
-    def _impl(inputs, attr, params):
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
         alpha = float(attr.get('alpha', 1.6732))
         gamma = float(attr.get('gamma', 1.0507))
-        return gamma * (-alpha * _sym.relu(1 - _sym.exp(inputs[0]))
-                        + _sym.relu(inputs[0]))
-    return _impl
+        return gamma * (
+            -alpha * _sym.relu(1 - _sym.exp(inputs[0])) + _sym.relu(inputs[0]))
 
-def _elu():
-    def _impl(inputs, attr, params):
+
+class ScaledTanh(OnnxOpConverter):
+
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
         alpha = float(attr.get('alpha', 1.0))
-        return -alpha * _sym.relu(1 - _sym.exp(inputs[0])) + _sym.relu(inputs[0])
-    return _impl
+        beta = float(attr.get('beta', 1.0))
+        return _sym.tanh(beta * inputs[0]) * alpha
 
-def _prelu():
-    def _impl(inputs, attr, params):
-        assert len(inputs) == 2, "Prelu need 2 inputs, {} given".format(len(inputs))
-        channels = _infer_channels(inputs[1], params, False)
-        if channels == 1:
-            return inputs[0] * inputs[1]
-        return _sym.broadcast_mul(inputs[0], inputs[1])
-    return _impl
 
-def _softsign():
-    def _impl(inputs, attr, params):
-        return inputs[0] / (1 + _absolute()(inputs, attr, params))
-    return _impl
+class SoftPlus(OnnxOpConverter):
 
-def _softplus():
-    def _impl(inputs, attr, params):
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
         return _sym.log(_sym.exp(inputs[0]) + 1)
-    return _impl
 
-def _pad():
-    def _impl(inputs, attr, params):
-        # get number of channels
-        channels = _infer_channels(inputs[1], params, True)
-        attr['channels'] = channels
-        groups = attr.pop('group')
-        attr['groups'] = groups
-        return AttrCvt(
-            op_name='pad',
-            transforms={
-                'value': 'pad_value',
-                'pads': 'pad_width'},
-            custom_check=lambda attrs: attrs.get('mode') == 'constant')(inputs, attr)
+
+class Softsign(OnnxOpConverter):
+
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
+        return inputs[0] / (1 + Absolute.get_converter(1)(inputs, attr, params))
+
+
+class Sub(Elemwise):
+    name = 'sub'
+
+
+class Sum(OnnxOpConverter):
+
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
+        # Onnx Sum Operator
+        for in_index in range(len(inputs) - 1):
+            inputs[in_index + 1] = _sym.broadcast_add(inputs[in_index],
+                                                      inputs[in_index + 1])
+
+        return inputs[len(inputs) - 1]
+
+
+class ThresholdedRelu(OnnxOpConverter):
+
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
+        alpha = float(attr.get('alpha', 0.0))
+        return _sym.relu(inputs[0] - alpha)
+
+
+def _revert_caffe2_pad(attr):
+    """Caffe2 require two times the normal padding."""
+    if len(attr) == 4:
+        attr = attr[:2]
+    elif len(attr) == 2:
+        pass
+    else:
+        raise ValueError("Invalid caffe2 type padding: {}".format(attr))
+    return attr
+
+
+def _broadcast_constraint():
+
+    def _broadcast_check(attrs):
+        if attrs.get('axis', None):
+            return False
+        return True
+
+    return _broadcast_check, "Specifying broadcast axis not allowed."
+
+
+def _dimension_picker(prefix, surfix=''):
+
+    def _impl(attr):
+        kernel = attr['kernel_shape']
+        if len(kernel) == 2:
+            return prefix + '2d' + surfix
+        else:
+            raise NotImplementedError("Only 2d kernel supported.")
+
     return _impl
 
-def _sum():
+
+def _dimension_constraint():
+
+    def _dim_check(attrs):
+        if len(attrs['kernel_shape']) == 2:
+            return True
+        return False
+
+    return _dim_check, "Only 2d kernel supported."
+
+
+def _infer_channels(inputs, params, transpose=False):
+    """A hack for getting 'channles' or 'units' since onnx don't provide
+    these attributes. We check the shape of weights provided to get the number.
+    """
+    g = _graph.create(inputs)
+    shape_dict = {k: v.shape for k, v in params.items()}
+    _, out_shapes = graph_util.infer_shape(g, **shape_dict)
+    channels = out_shapes[0][0] if not transpose else out_shapes[0][1]
+    return channels
+
+
+def _fully_connected(opset):
+
     def _impl(inputs, attr, params):
-        # Onnx Sum Operator
-        for in_index in range(len(inputs)-1):
-            inputs[in_index+1] = _sym.broadcast_add(inputs[in_index], inputs[in_index+1])
+        # get number of channels
+        channels = _infer_channels(inputs[1], params)
+        attr['units'] = channels
+        return AttrCvt('dense', ignores=['axis', 'axis_w'])(inputs, attr)
 
-        return inputs[len(inputs)-1]
     return _impl
 
+
 # compatible operators that do NOT require any conversion.
 _identity_list = []
 
+
 # _convert_map defines maps of name to converter functor(callable)
 # for 1 to 1 mapping, use Renamer if nothing but name is different
 # use AttrCvt if attributes need to be converted
 # for 1 to N mapping(composed), use custom callable functions
 # for N to 1 mapping, currently not supported(?)
-_convert_map = {
-    # defs/experimental
-    'Identity'      : Renamer('copy'),
-    # 'Affine'
-    'ThresholdedRelu': _thresholded_relu(),
-    'ScaledTanh'    : _scaled_tanh(),
-    'ParametricSoftplus': parametric_soft_plus(),
-    # 'ConstantFill'
-    # 'GivenTensorFill'
-    'FC'            : AttrCvt('dense', ignores=['axis', 'axis_w']),
-    'Scale'         : _scale(),
-    # 'GRUUnit'
-    # 'ATen'
-    # 'ImageScaler'
-    # 'MeanVarianceNormalization'
-    # 'Crop'
-    # 'Embedding'
-    # 'Upsample'
-    'SpatialBN'     : _batch_norm(),
-
-    # defs/generator
-    # 'Constant'
-    # 'RandomUniform'
-    # 'RandomNormal'
-    # 'RandomUniformLike'
-    # 'RandomNormalLike'
-
-    # defs/logical
-
-    # defs/math
-    'Add'           : _elemwise('add'),
-    'Sub'           : _elemwise('sub'),
-    'Mul'           : _elemwise('mul'),
-    'Div'           : _elemwise('div'),
-    'Neg'           : Renamer('negative'),
-    'Abs'           : _absolute(),
-    'Reciprocal'    : _reciprocal(),
-    # 'Floor'
-    # 'Ceil'
-    'Sqrt'          : Renamer('sqrt'),
-    'Relu'          : Renamer('relu'),
-    'LeakyRelu'     : Renamer('leaky_relu'),
-    'Selu'          : _selu(),
-    'Elu'           : _elu(),
-    'Exp'           : Renamer('exp'),
-    'Log'           : Renamer('log'),
-    'Tanh'          : Renamer('tanh'),
-    # 'Pow'
-    'PRelu'         : _prelu(),
-    'Sigmoid'       : Renamer('sigmoid'),
-    # 'HardSigmoid'
-    # 'Max' : this is the elemwise maximum
-    # 'Min' : this is the elemwise minimum
-    'Sum'           : _sum(),
-    # 'Mean'
-    # 'Clip'
-    # softmax default axis is different in onnx
-    'Softmax'       : AttrCvt('softmax', {'axis': ('axis', 1)}),
-    'LogSoftmax'    : AttrCvt('log_softmax', {'axis': ('axis', 1)}),
-    # 'Hardmax'
-    'Softsign'      : _softsign(),
-    'SoftPlus'      : _softplus(),
-    'Gemm'          : _gemm(),
-    # 'MatMul'  batch stacked dot operation
-
-    # defs/nn
-    'AveragePool'   : _pooling('avg_pool'),
-    'MaxPool'       : _pooling('max_pool'),
-    'Conv'          : _conv(),
-    'ConvTranspose' : _conv_transpose(),
-    'GlobalAveragePool': Renamer('global_avg_pool2d'),
-    'GlobalMaxPool' : Renamer('global_max_pool2d'),
-    'BatchNormalization': _batch_norm(),
-    # 'InstanceNormalization'
-    # 'LpNormalization'
-    'Dropout'       : AttrCvt('dropout', {'ratio': 'rate'}, ignores=['is_test']),
-    'Flatten'       : Renamer('flatten'),
-    # 'LRN'
-
-    # defs/reduction
-    'ReduceMax'     : AttrCvt('max', {'axes', 'axis'}),
-    'ReduceMin'     : AttrCvt('min', {'axes', 'axis'}),
-    'ReduceSum'     : AttrCvt('sum', {'axes', 'axis'}),
-    # 'ReduceMean'
-    # 'ReduceProd'
-    # 'ReduceLogSumExp'
-    # 'ArgMax'
-    # 'ArgMin'
-
-    # defs/tensor
-    'Cast'          : AttrCvt('cast', {'to': 'dtype'}),
-    'Reshape'       : Renamer('reshape'),
-    'Concat'        : Renamer('concatenate'),
-    'Split'         : AttrCvt('split', {'split': 'indices_or_sections'}),
-    # 'Slice'
-    'Transpose'     : AttrCvt('transpose', {'perm': 'axes'}),
-    # 'Gather'
-    # 'Squeeze'
-    'Pad'           : _pad(),
-}
+def _get_convert_map(opset):
+    return {
+        # defs/experimental
+        'Identity': Renamer('copy'),
+        # 'Affine'
+        'ThresholdedRelu': ThresholdedRelu.get_converter(opset),
+        'ScaledTanh': ScaledTanh.get_converter(opset),
+        'ParametricSoftplus': ParametricSoftPlus.get_converter(opset),
+        # 'ConstantFill'
+        # 'GivenTensorFill'
+        'FC': AttrCvt('dense', ignores=['axis', 'axis_w']),
+        'Scale': Scale.get_converter(opset),
+        # 'GRUUnit'
+        # 'ATen'
+        # 'ImageScaler'
+        # 'MeanVarianceNormalization'
+        # 'Crop'
+        # 'Embedding'
+        # 'Upsample'
+        'SpatialBN': BatchNorm.get_converter(opset),
+
+        # defs/generator
+        # 'Constant'
+        # 'RandomUniform'
+        # 'RandomNormal'
+        # 'RandomUniformLike'
+        # 'RandomNormalLike'
+
+        # defs/logical
+
+        # defs/math
+        'Add': Add.get_converter(opset),
+        'Sub': Sub.get_converter(opset),
+        'Mul': Mul.get_converter(opset),
+        'Div': Div.get_converter(opset),
+        'Neg': Renamer('negative'),
+        'Abs': Absolute.get_converter(opset),
+        'Reciprocal': Reciprocal.get_converter(opset),
+        # 'Floor'
+        # 'Ceil'
+        'Sqrt': Renamer('sqrt'),
+        'Relu': Renamer('relu'),
+        'LeakyRelu': Renamer('leaky_relu'),
+        'Selu': Selu.get_converter(opset),
+        'Elu': Elu.get_converter(opset),
+        'Exp': Renamer('exp'),
+        'Log': Renamer('log'),
+        'Tanh': Renamer('tanh'),
+        # 'Pow'
+        'PRelu': Prelu.get_converter(opset),
+        'Sigmoid': Renamer('sigmoid'),
+        # 'HardSigmoid'
+        # 'Max' : this is the elemwise maximum
+        # 'Min' : this is the elemwise minimum
+        'Sum': Sum.get_converter(opset),
+        # 'Mean'
+        # 'Clip'
+        # softmax default axis is different in onnx
+        'Softmax': AttrCvt('softmax', {'axis': ('axis', 1)}),
+        'LogSoftmax': AttrCvt('log_softmax', {'axis': ('axis', 1)}),
+        # 'Hardmax'
+        'Softsign': Softsign.get_converter(opset),
+        'SoftPlus': SoftPlus.get_converter(opset),
+        'Gemm': Gemm.get_converter(opset),
+        # 'MatMul'  batch stacked dot operation
+
+        # defs/nn
+        'AveragePool': AveragePool.get_converter(opset),
+        'MaxPool': MaxPool.get_converter(opset),
+        'Conv': Conv.get_converter(opset),
+        'ConvTranspose': ConvTranspose.get_converter(opset),
+        'GlobalAveragePool': Renamer('global_avg_pool2d'),
+        'GlobalMaxPool': Renamer('global_max_pool2d'),
+        'BatchNormalization': BatchNorm.get_converter(opset),
+        # 'InstanceNormalization'
+        # 'LpNormalization'
+        'Dropout': AttrCvt('dropout', {'ratio': 'rate'}, ignores=['is_test']),
+        'Flatten': Renamer('flatten'),
+        # 'LRN'
+
+        # defs/reduction
+        'ReduceMax': AttrCvt('max', {'axes', 'axis'}),
+        'ReduceMin': AttrCvt('min', {'axes', 'axis'}),
+        'ReduceSum': AttrCvt('sum', {'axes', 'axis'}),
+        # 'ReduceMean'
+        # 'ReduceProd'
+        # 'ReduceLogSumExp'
+        # 'ArgMax'
+        # 'ArgMin'
+
+        # defs/tensor
+        'Cast': AttrCvt('cast', {'to': 'dtype'}),
+        'Reshape': Reshape.get_converter(opset),
+        'Concat': Renamer('concatenate'),
+        'Split': AttrCvt('split', {'split': 'indices_or_sections'}),
+        # 'Slice'
+        'Transpose': AttrCvt('transpose', {'perm': 'axes'}),
+        # 'Gather'
+        # 'Squeeze'
+        'Pad': Pad.get_converter(opset),
+    }
 
 
 class GraphProto(object):
     """A helper class for handling nnvm graph copying from pb2.GraphProto.
     Definition: https://github.com/onnx/onnx/blob/master/onnx/onnx.proto
     """
+
     def __init__(self):
         self._nodes = {}
         self._params = {}
@@ -365,7 +511,7 @@ def __init__(self):
         self._num_input = 0
         self._num_param = 0
 
-    def from_onnx(self, graph):
+    def from_onnx(self, graph, opset):
         """Construct nnvm nodes from onnx graph.
         The inputs from onnx graph is vague, only providing "1", "2"...
         For convenience, we rename the `real` input names to "input_0",
@@ -375,6 +521,7 @@ def from_onnx(self, graph):
         ----------
         graph : onnx protobuf object
             The loaded onnx graph
+        opset : opset version
 
         Returns
         -------
@@ -410,7 +557,7 @@ def from_onnx(self, graph):
             op_name = node.op_type
             attr = self._parse_attr(node.attribute)
             inputs = [self._nodes[self._renames.get(i, i)] for i in node.input]
-            op = self._convert_operator(op_name, inputs, attr)
+            op = self._convert_operator(op_name, inputs, attr, opset)
             node_output = self._fix_outputs(op_name, node.output)
             assert len(node_output) == len(op.list_output_names()), (
                 "Number of output mismatch {} vs {} in {}.".format(
@@ -438,7 +585,8 @@ def _parse_array(self, tensor_proto):
         try:
             from onnx.numpy_helper import to_array
         except ImportError as e:
-            raise ImportError("Unable to import onnx which is required {}".format(e))
+            raise ImportError(
+                "Unable to import onnx which is required {}".format(e))
         np_array = to_array(tensor_proto).reshape(tuple(tensor_proto.dims))
         return tvm.nd.array(np_array)
 
@@ -455,15 +603,23 @@ def _parse_attr(self, attr_proto):
                     attrs[a.name] = tuple(getattr(a, f))
             for f in ['t', 'g']:
                 if a.HasField(f):
-                    raise NotImplementedError("Filed {} is not supported in nnvm.".format(f))
+                    raise NotImplementedError(
+                        "Filed {} is not supported in nnvm.".format(f))
             for f in ['tensors', 'graphs']:
                 if list(getattr(a, f)):
-                    raise NotImplementedError("Filed {} is not supported in nnvm.".format(f))
+                    raise NotImplementedError(
+                        "Filed {} is not supported in nnvm.".format(f))
             if a.name not in attrs:
                 raise ValueError("Cannot parse attribute: \n{}\n.".format(a))
         return attrs
 
-    def _convert_operator(self, op_name, inputs, attrs, identity_list=None, convert_map=None):
+    def _convert_operator(self,
+                          op_name,
+                          inputs,
+                          attrs,
+                          opset,
+                          identity_list=None,
+                          convert_map=None):
         """Convert from onnx operator to nnvm operator.
         The converter must specify conversions explicity for incompatible name, and
         apply handlers to operator attributes.
@@ -476,6 +632,8 @@ def _convert_operator(self, op_name, inputs, attrs, identity_list=None, convert_
             List of input symbols.
         attrs : dict
             Dict of operator attributes
+        opset : int
+            Opset version
         identity_list : list
             List of operators that don't require conversion
         convert_map : dict
@@ -489,13 +647,14 @@ def _convert_operator(self, op_name, inputs, attrs, identity_list=None, convert_
             Converted nnvm Symbol
         """
         identity_list = identity_list if identity_list else _identity_list
-        convert_map = convert_map if convert_map else _convert_map
+        convert_map = convert_map if convert_map else _get_convert_map(opset)
         if op_name in identity_list:
             sym = get_nnvm_op(op_name)(*inputs, **attrs)
         elif op_name in convert_map:
             sym = convert_map[op_name](inputs, attrs, self._params)
         else:
-            raise NotImplementedError("Operator {} not implemented.".format(op_name))
+            raise NotImplementedError(
+                "Operator {} not implemented.".format(op_name))
         return sym
 
     def _fix_outputs(self, op_name, outputs):
@@ -510,7 +669,7 @@ def _fix_outputs(self, op_name, outputs):
         return outputs
 
 
-def from_onnx(graph):
+def from_onnx(model):
     """Load onnx graph which is a python protobuf object into nnvm graph.
     The companion parameters will be handled automatically.
     The inputs from onnx graph is vague, only providing "1", "2"...
@@ -519,8 +678,8 @@ def from_onnx(graph):
 
     Parameters
     ----------
-    graph : protobuf object
-        ONNX GraphProto, or ONNX ModelProto after ONNX v0.2
+    model : protobuf object
+        ONNX ModelProto after ONNX v1.1.0
 
     Returns
     -------
@@ -531,8 +690,7 @@ def from_onnx(graph):
         Dict of converted parameters stored in tvm.ndarray format
     """
     g = GraphProto()
-    if hasattr(graph, 'graph'):
-        # it's a ModelProto wrapper
-        graph = graph.graph
-    sym, params = g.from_onnx(graph)
+    graph = model.graph
+    opset = model.opset_import[0].version if model.opset_import else 1
+    sym, params = g.from_onnx(graph, opset)
     return sym, params
diff --git a/nnvm/tests/ci_build/install/ubuntu_install_onnx.sh b/nnvm/tests/ci_build/install/ubuntu_install_onnx.sh
index 37bd4e66edde..138e70d71200 100644
--- a/nnvm/tests/ci_build/install/ubuntu_install_onnx.sh
+++ b/nnvm/tests/ci_build/install/ubuntu_install_onnx.sh
@@ -1,5 +1,5 @@
-pip2 install onnx>=0.2.0
-pip3 install onnx>=0.2.0
+pip2 install onnx>=1.1.0
+pip3 install onnx>=1.1.0
 
 pip2 install http://download.pytorch.org/whl/cu75/torch-0.2.0.post3-cp27-cp27mu-manylinux1_x86_64.whl
 pip2 install torchvision
diff --git a/nnvm/tests/python/frontend/onnx/test_forward.py b/nnvm/tests/python/frontend/onnx/test_forward.py
index d71e171836ae..3a7076d17b68 100644
--- a/nnvm/tests/python/frontend/onnx/test_forward.py
+++ b/nnvm/tests/python/frontend/onnx/test_forward.py
@@ -14,8 +14,8 @@ def get_caffe2_output(model, x, dtype='float32'):
         c2_out = prepared_backend.run(W)[0]
         return c2_out
 
-    def get_tvm_output(graph, x, target, ctx, dtype='float32'):
-        new_sym, params = nnvm.frontend.from_onnx(graph)
+    def get_tvm_output(model, x, target, ctx, dtype='float32'):
+        new_sym, params = nnvm.frontend.from_onnx(model)
         shape_dict = {'input_0': x.shape}
         graph, lib, params = nnvm.compiler.build(new_sym, target, shape_dict, params=params)
         m = graph_runtime.create(graph, lib, ctx)
diff --git a/nnvm/tests/python/frontend/onnx/test_graph.py b/nnvm/tests/python/frontend/onnx/test_graph.py
index 6d64f47469ca..89f13b447991 100644
--- a/nnvm/tests/python/frontend/onnx/test_graph.py
+++ b/nnvm/tests/python/frontend/onnx/test_graph.py
@@ -5,8 +5,8 @@
 from model_zoo import super_resolution, super_resolution_sym
 
 def compare_graph(onnx_file, nnvm_sym, ishape):
-    onnx_graph = onnx.load(onnx_file)
-    onnx_sym, params = nnvm.frontend.from_onnx(onnx_graph)
+    onnx_model = onnx.load(onnx_file)
+    onnx_sym, params = nnvm.frontend.from_onnx(onnx_model)
     g1 = nnvm.graph.create(onnx_sym)
     g2 = nnvm.graph.create(nnvm_sym)
     ishapes = {'input_0': ishape}
diff --git a/nnvm/tutorials/from_onnx.py b/nnvm/tutorials/from_onnx.py
index 5a01fb60dc53..c5680f4ae816 100644
--- a/nnvm/tutorials/from_onnx.py
+++ b/nnvm/tutorials/from_onnx.py
@@ -44,9 +44,9 @@ def download(url, path, overwrite=False):
                      'super_resolution_0.2.onnx'])
 download(model_url, 'super_resolution.onnx', True)
 # now you have super_resolution.onnx on disk
-onnx_graph = onnx.load('super_resolution.onnx')
+onnx_model = onnx.load('super_resolution.onnx')
 # we can load the graph as NNVM compatible model
-sym, params = nnvm.frontend.from_onnx(onnx_graph)
+sym, params = nnvm.frontend.from_onnx(onnx_model)
 
 ######################################################################
 # Load a test image

From 0dbfd3314c85cb8fe5b97385886b285925fd46a3 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 31 Mar 2018 21:50:33 -0700
Subject: [PATCH 625/949] Update tvm, fix lint due to pylint update (#423)

---
 nnvm/python/nnvm/frontend/coreml.py           | 8 +++++++-
 nnvm/tests/python/compiler/test_top_level2.py | 1 +
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/nnvm/python/nnvm/frontend/coreml.py b/nnvm/python/nnvm/frontend/coreml.py
index fb942fb18448..006fb9017de1 100644
--- a/nnvm/python/nnvm/frontend/coreml.py
+++ b/nnvm/python/nnvm/frontend/coreml.py
@@ -1,8 +1,9 @@
 # pylint: disable=invalid-name, unused-argument
 """CoreML frontend."""
 from __future__ import absolute_import as _abs
-import tvm
 import numpy as np
+
+import tvm
 from .. import symbol as _sym
 from .common import SymbolTable
 
@@ -77,6 +78,7 @@ def ConvolutionLayerParams(op, insym, symtab):
     return ret
 
 def BatchnormLayerParams(op, insym, symtab):
+    """Get layer of batchnorm parameter"""
     # this changes the symbol
     if op.instanceNormalization:
         raise NotImplementedError("instance normalization not implemented")
@@ -89,6 +91,7 @@ def BatchnormLayerParams(op, insym, symtab):
         return _sym.batch_norm(data=insym, **params)
 
 def ActivationParams(op, insym, symtab):
+    """Get activation parameters"""
     whichActivation = op.WhichOneof('NonlinearityType')
     par = getattr(op, whichActivation)
     if whichActivation == 'linear':
@@ -129,6 +132,8 @@ def ActivationParams(op, insym, symtab):
         betasym = symtab.new_const(beta)
         return _sym.broadcast_mul(_sym.log(_sym.broadcast_add(
             _sym.exp(insym), betasym)), alphasym)
+    else:
+        raise NotImplementedError('%s not implemented' % whichActivation)
 
 def ScaleLayerParams(op, insym, symtab):
     """Scale layer params."""
@@ -144,6 +149,7 @@ def ScaleLayerParams(op, insym, symtab):
     return ret
 
 def PoolingLayerParams(op, insym, symtab):
+    """get pooling parameters"""
     if op.globalPooling:
         if op.type == 0:
             return _sym.global_max_pool2d(insym)
diff --git a/nnvm/tests/python/compiler/test_top_level2.py b/nnvm/tests/python/compiler/test_top_level2.py
index d5da4d117967..89c582fedefa 100644
--- a/nnvm/tests/python/compiler/test_top_level2.py
+++ b/nnvm/tests/python/compiler/test_top_level2.py
@@ -3,6 +3,7 @@
 import tvm
 from tvm.contrib import graph_runtime
 import topi
+import topi.testing
 import nnvm.symbol as sym
 import nnvm.compiler
 from nnvm.testing.config import ctx_list

From 72e3b24c246b37e3d6b7267fd981b5aef60a37de Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 1 Apr 2018 10:43:29 -0700
Subject: [PATCH 626/949] [PASS] Enhance scale fold axis (#424)

---
 nnvm/src/compiler/fold_scale_axis.cc         | 413 +++++++++++++++----
 nnvm/src/pass/plan_memory.cc                 |   2 +-
 nnvm/tests/python/compiler/test_fold_axis.py |  79 +++-
 3 files changed, 398 insertions(+), 96 deletions(-)

diff --git a/nnvm/src/compiler/fold_scale_axis.cc b/nnvm/src/compiler/fold_scale_axis.cc
index 34383c4e6223..7b05153b65f5 100644
--- a/nnvm/src/compiler/fold_scale_axis.cc
+++ b/nnvm/src/compiler/fold_scale_axis.cc
@@ -18,12 +18,10 @@ namespace compiler {
 enum FoldScaleKind {
   // No folding is applied
   kNone,
-  // The folding decision is pending
+  // The folding decision is pending, we can fold on a state.
   kPending,
   // The original operator that contains the scale.
   kProvider,
-  // Pass through the scale to parent/child to the first axis.
-  kPassTroughFirst,
   // The final conumer of axis scale using multiply
   // Likely be a conv or dense operator.
   kMulConsumer,
@@ -31,21 +29,23 @@ enum FoldScaleKind {
   kDivConsumer
 };
 
-// Input fold information
-struct FoldScaleInput {
-  uint32_t index;
-  int axis;
-};
-
-// The entry of folding chains on which
-// we should perform folding on
-struct FoldChainEntry {
+struct FoldChainInfo {
   // Entry kind
   FoldScaleKind kind{kNone};
   // The output axis to be folded
   int axis{0};
   // Source node in the fold chain
   int source{0};
+};
+
+// The entry of folding chains on which
+// we should perform folding on
+struct FoldChainEntry {
+  // Fold information
+  FoldChainInfo info;
+  // Number of outgoing fork count
+  // in forward propagation.
+  int fork_count{0};
   // Following field only used by provider.
   // The input index
   int fold_input_index{1};
@@ -55,12 +55,26 @@ struct FoldChainEntry {
 
 // Try to pass axis scaling to backward,
 // Given that we we know the status of current fold axis.
+// return whether the forward signal is consumed.
 using FScaleAxisBackward = std::function<
-  FoldScaleKind(const NodeAttrs& attrs,
-                int axis,
-                const std::vector<TShape>& in_shape,
-                const std::vector<TShape>& out_shape,
-                std::vector<std::pair<uint32_t, int> >* in_axis)>;
+  bool(const NodeAttrs& attrs,
+       const std::vector<TShape>& in_shape,
+       const std::vector<TShape>& out_shape,
+       const FoldChainInfo& out_info,
+       std::vector<FoldChainInfo>* in_info)>;
+
+
+// Try to pass axis scaling to forward,
+// Given that we we know the status of one of its input to be pending
+// also update other input info
+// return whether the forward signal is consumed.
+using FScaleAxisForward = std::function<
+  bool(const NodeAttrs& attrs,
+       const std::vector<TShape>& in_shape,
+       const std::vector<TShape>& out_shape,
+       std::vector<FoldChainInfo>* in_info,
+       FoldChainInfo* out_info)>;
+
 
 // Detect if there is a scaling axis happening
 bool DetectScaleAxis(const IndexedGraph& idx,
@@ -99,15 +113,19 @@ bool DetectScaleAxis(const IndexedGraph& idx,
       } else {
         return false;
       }
-      e.axis = axis.first;
-      e.kind = kPending;
-      e.source = nid;
+      e.info.axis = axis.first;
+      e.info.kind = kPending;
+      e.info.source = nid;
+      e.fork_count = 1;
+      // In the backward message passing
+      // We need to eagerly pass it to the input
+      // In the forward message passing
+      // we will "pull" the message from input.
       if (!is_forward) {
-        // pass message to another input
         FoldChainEntry& enext = (*chain)[b.node_id];
-        enext.axis = e.axis;
-        enext.kind = kPending;
-        enext.source = nid;
+        enext.info.axis = e.info.axis;
+        enext.info.kind = kPending;
+        enext.info.source = nid;
       }
       return true;
     }
@@ -119,12 +137,16 @@ Graph FoldScaleAxis(Graph src) {
   // Operator pattern
   static auto& fbackward =
       nnvm::Op::GetAttr<FScaleAxisBackward>("FScaleAxisBackward");
+  static auto& fforward =
+      nnvm::Op::GetAttr<FScaleAxisForward>("FScaleAxisForward");
   const IndexedGraph& idx = src.indexed_graph();
   const ShapeVector& shape_vec = src.GetAttr<ShapeVector>("shape");
   std::vector<uint32_t> ref_count = GetNodeRefCounts(idx);
   std::vector<FoldChainEntry> bwd_chain(idx.num_nodes());
+  std::vector<FoldChainEntry> fwd_chain(idx.num_nodes());
   // shape hint for the inference.
   std::vector<TShape> in_shape, out_shape;
+
   // perform backward folding.
   for (uint32_t i = idx.num_nodes(); i != 0; --i) {
     uint32_t nid = i - 1;
@@ -132,9 +154,10 @@ Graph FoldScaleAxis(Graph src) {
     if (inode.source->is_variable()) continue;
     if (DetectScaleAxis(idx, nid, shape_vec,
                         ref_count, false, &bwd_chain)) continue;
-    if (bwd_chain[nid].kind != kPending) continue;
+    if (bwd_chain[nid].info.kind != kPending) continue;
+    // if referred by multiple node, cannot do propagation
     if (ref_count[nid] != 1 || !fbackward.count(inode.source->op())) {
-      bwd_chain[nid].kind = kNone; continue;
+      bwd_chain[nid].info.kind = kNone; continue;
     }
     // get input shape and output shape.
     in_shape.clear(); out_shape.clear();
@@ -144,58 +167,151 @@ Graph FoldScaleAxis(Graph src) {
     for (uint32_t i = 0; i < inode.source->num_outputs(); ++i) {
       out_shape.push_back(shape_vec[idx.entry_id(nid, i)]);
     }
-    std::vector<std::pair<uint32_t, int> > in_axis;
-    FoldScaleKind kind =
-        fbackward[inode.source->op()](
-            inode.source->attrs, bwd_chain[nid].axis,
-            in_shape, out_shape, &in_axis);
-    bwd_chain[nid].kind = kind;
-    if (kind == kNone) continue;
-    CHECK_GE(in_axis.size(), 1U);
-    CHECK(kind == kPassTroughFirst || kind == kMulConsumer);
+    std::vector<FoldChainInfo> in_info(in_shape.size(), FoldChainInfo());
+    bool consumed = fbackward[inode.source->op()](
+        inode.source->attrs,
+        in_shape,
+        out_shape,
+        bwd_chain[nid].info,
+        &in_info);
+    CHECK_EQ(in_info.size(), in_shape.size());
     // propagate back.
     bool can_prop = true;
-    for (size_t i = 0; i < in_axis.size(); ++i) {
-      const IndexedGraph::NodeEntry& e = inode.inputs[in_axis[0].first];
+    for (size_t i = 0; i < in_info.size(); ++i) {
+      const IndexedGraph::NodeEntry& e = inode.inputs[i];
       if (ref_count[e.node_id] != 1 ||
           idx[e.node_id].source->num_outputs() != 1) {
         can_prop = false; break;
       }
     }
     if (!can_prop) continue;
-    for (size_t i = 0; i < in_axis.size(); ++i) {
-      const IndexedGraph::NodeEntry& e = inode.inputs[in_axis[i].first];
-      if (kind == kPassTroughFirst && i == 0) {
-        bwd_chain[e.node_id].kind = kPending;
-      } else {
-        bwd_chain[nid].kind = kNone;
-        bwd_chain[e.node_id].kind = kMulConsumer;
+    for (size_t i = 0; i < in_info.size(); ++i) {
+      const IndexedGraph::NodeEntry& e = inode.inputs[i];
+      bwd_chain[e.node_id].info = in_info[i];
+    }
+    // mark consumed by making the source as provider.
+    if (consumed) {
+      bwd_chain[bwd_chain[nid].info.source].info.kind = kProvider;
+    }
+  }
+
+
+  // perform forward folding.
+  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+    const auto& inode = idx[nid];
+    if (inode.source->is_variable()) continue;
+    // skip scales that are already folded in backward.
+    if (bwd_chain[nid].info.kind == kProvider) continue;
+    if (DetectScaleAxis(idx, nid, shape_vec,
+                        ref_count, true, &fwd_chain)) continue;
+    if (inode.source->num_outputs() != 1) continue;
+    // Do state update
+    // get input shape and output shape.
+    std::vector<FoldChainInfo> in_info;
+    FoldChainInfo out_info;
+    int num_inpending = 0;
+    in_shape.clear(); out_shape.clear();
+    for (const IndexedGraph::NodeEntry& e : inode.inputs) {
+      in_shape.push_back(shape_vec[idx.entry_id(e)]);
+      // input information
+      in_info.push_back(fwd_chain[e.node_id].info);
+      if (fwd_chain[e.node_id].info.kind == kPending) {
+        ++num_inpending;
       }
-      bwd_chain[e.node_id].axis = in_axis[i].second;
-      bwd_chain[e.node_id].source = bwd_chain[nid].source;
     }
-    if (kind == kMulConsumer) {
-      bwd_chain[bwd_chain[nid].source].kind = kProvider;
+    for (uint32_t i = 0; i < inode.source->num_outputs(); ++i) {
+      out_shape.push_back(shape_vec[idx.entry_id(nid, i)]);
+    }
+    if (num_inpending != 1 ||
+        !fforward.count(inode.source->op())) continue;
+    bool consumed = fforward[inode.source->op()](
+        inode.source->attrs,
+        in_shape,
+        out_shape,
+        &in_info,
+        &out_info);
+    // update input info
+    for (size_t i = 0; i < in_info.size(); ++i) {
+      fwd_chain[inode.inputs[i].node_id].info = in_info[i];
+    }
+    if (consumed) {
+      fwd_chain[nid].info = out_info;
+      for (size_t i = 0; i < in_info.size(); ++i) {
+        if (in_info[i].kind == kPending) {
+          if (--fwd_chain[in_info[i].source].fork_count == 0) {
+            fwd_chain[in_info[i].source].info.kind = kProvider;
+          }
+        }
+      }
+    } else {
+      // can propagate condition
+      if (inode.source->num_outputs() == 1) {
+        fwd_chain[nid].info = out_info;
+        if (out_info.kind == kPending) {
+          // When there is multiple reference to input
+          // every path have to be consumed
+          fwd_chain[out_info.source].fork_count += ref_count[nid] - 1;
+        }
+      }
     }
   }
+
   auto transform = [&](uint32_t nid, const NodePtr& n, std::vector<NodeEntry>* ret) {
-    const FoldChainEntry& e = bwd_chain[nid];
-    if (e.kind == kMulConsumer && bwd_chain[e.source].kind == kProvider) {
-      const FoldChainEntry& se = bwd_chain[e.source];
-      CHECK_EQ(n->num_outputs(), 1);
-      NodeEntry scale = ExpandBiasToMatchAxis(
-          se.scale_entry,
-          shape_vec[idx.entry_id(nid, 0)].ndim(),
-          shape_vec[idx.entry_id(se.scale_entry)].ndim(),
-          e.axis);
-      *ret = {MakeNode("broadcast_mul", n->attrs.name + "_sc",
-                       {NodeEntry{n, 0, 0}, scale})};
-      return true;
-    } else if (e.kind == kProvider) {
-      *ret = {n->inputs[e.fold_input_index]};
-      return true;
-    } else {
+    NodeEntry rvalue = NodeEntry{n, 0, 0};
+    {
+      // Backward chain
+      const FoldChainEntry& e = bwd_chain[nid];
+      if (e.info.kind == kMulConsumer &&
+          bwd_chain[e.info.source].info.kind == kProvider) {
+        const FoldChainEntry& se = bwd_chain[e.info.source];
+        CHECK_EQ(n->num_outputs(), 1);
+        NodeEntry scale = ExpandBiasToMatchAxis(
+            se.scale_entry,
+            shape_vec[idx.entry_id(nid, 0)].ndim(),
+            shape_vec[idx.entry_id(se.scale_entry)].ndim(),
+            e.info.axis);
+        rvalue = MakeNode("broadcast_mul", n->attrs.name + "_sc",
+                          {rvalue, scale});
+      } else if (e.info.kind == kProvider) {
+        rvalue = n->inputs[e.fold_input_index];
+      }
+    }
+    // Note that the value might get transformed twice if it
+    // folds value from both fwd and backward chain.
+    {
+      // forward chain
+      const FoldChainEntry& e = fwd_chain[nid];
+      if (e.info.kind == kMulConsumer &&
+          fwd_chain[e.info.source].info.kind == kProvider) {
+        const FoldChainEntry& se = fwd_chain[e.info.source];
+        CHECK_EQ(n->num_outputs(), 1);
+        NodeEntry scale = ExpandBiasToMatchAxis(
+            se.scale_entry,
+            shape_vec[idx.entry_id(nid, 0)].ndim(),
+            shape_vec[idx.entry_id(se.scale_entry)].ndim(),
+            e.info.axis);
+        rvalue = MakeNode("broadcast_mul", n->attrs.name + "_sc",
+                          {rvalue, scale});
+      } else if (e.info.kind == kDivConsumer &&
+                 fwd_chain[e.info.source].info.kind == kProvider) {
+        const FoldChainEntry& se = fwd_chain[e.info.source];
+        CHECK_EQ(n->num_outputs(), 1);
+        NodeEntry scale = ExpandBiasToMatchAxis(
+            se.scale_entry,
+            shape_vec[idx.entry_id(nid, 0)].ndim(),
+            shape_vec[idx.entry_id(se.scale_entry)].ndim(),
+            e.info.axis);
+        rvalue = MakeNode("broadcast_div", n->attrs.name + "_sc",
+                          {rvalue, scale});
+      } else if (e.info.kind == kProvider) {
+        rvalue = n->inputs[e.fold_input_index];
+      }
+    }
+    if (rvalue.node == n) {
       return false;
+    } else {
+      *ret = {rvalue};
+      return true;
     }
   };
   return GraphTransform(src, transform);
@@ -205,14 +321,24 @@ NNVM_REGISTER_PASS(FoldScaleAxis)
 .set_body(FoldScaleAxis);
 
 // property registration.
-FoldScaleKind ReluScaleAxisBackward(
+bool ReluScaleAxisBackward(
     const NodeAttrs& attrs,
-    int axis,
     const std::vector<TShape>& in_shape,
     const std::vector<TShape>& out_shape,
-    std::vector<std::pair<uint32_t, int> >* in_axis) {
-  in_axis->emplace_back(0, axis);
-  return kPassTroughFirst;
+    const FoldChainInfo& out_info,
+    std::vector<FoldChainInfo>* in_axis) {
+  (*in_axis)[0] = out_info;
+  return false;
+}
+
+bool ReluScaleAxisForward(
+    const NodeAttrs& attrs,
+    const std::vector<TShape>& in_shape,
+    const std::vector<TShape>& out_shape,
+    std::vector<FoldChainInfo>* in_info,
+    FoldChainInfo* out_info) {
+  *out_info = (*in_info)[0];
+  return false;
 }
 
 NNVM_REGISTER_OP(relu)
@@ -221,21 +347,102 @@ NNVM_REGISTER_OP(relu)
 NNVM_REGISTER_OP(leaky_relu)
 .set_attr<FScaleAxisBackward>("FScaleAxisBackward", ReluScaleAxisBackward);
 
-FoldScaleKind BroadcastAddSubScaleAxisBackward(
+NNVM_REGISTER_OP(relu)
+.set_attr<FScaleAxisForward>("FScaleAxisForward", ReluScaleAxisForward);
+
+NNVM_REGISTER_OP(leaky_relu)
+.set_attr<FScaleAxisForward>("FScaleAxisForward", ReluScaleAxisForward);
+
+// property registration.
+bool Pool2DBackward(
+    const NodeAttrs& attrs,
+    const std::vector<TShape>& in_shape,
+    const std::vector<TShape>& out_shape,
+    const FoldChainInfo& out_info,
+    std::vector<FoldChainInfo>* in_axis) {
+  using top::Pool2DParam;
+  const Pool2DParam& param = nnvm::get<Pool2DParam>(attrs.parsed);
+  if (out_info.axis == 1 && param.layout == top::kNCHW) {
+    (*in_axis)[0] = out_info;
+  }
+  return false;
+}
+
+bool Pool2DForward(
+    const NodeAttrs& attrs,
+    const std::vector<TShape>& in_shape,
+    const std::vector<TShape>& out_shape,
+    std::vector<FoldChainInfo>* in_info,
+    FoldChainInfo* out_info) {
+  using top::Pool2DParam;
+  const Pool2DParam& param = nnvm::get<Pool2DParam>(attrs.parsed);
+  if ((*in_info)[0].axis == 1 && param.layout == top::kNCHW) {
+    *out_info = (*in_info)[0];
+  }
+  return false;
+}
+
+NNVM_REGISTER_OP(max_pool2d)
+.set_attr<FScaleAxisBackward>("FScaleAxisBackward", Pool2DBackward);
+
+NNVM_REGISTER_OP(avg_pool2d)
+.set_attr<FScaleAxisBackward>("FScaleAxisBackward", Pool2DBackward);
+
+NNVM_REGISTER_OP(max_pool2d)
+.set_attr<FScaleAxisForward>("FScaleAxisForward", Pool2DForward);
+
+NNVM_REGISTER_OP(avg_pool2d)
+.set_attr<FScaleAxisForward>("FScaleAxisForward", Pool2DForward);
+
+
+
+bool BroadcastAddSubScaleAxisBackward(
+    const NodeAttrs& attrs,
+    const std::vector<TShape>& in_shape,
+    const std::vector<TShape>& out_shape,
+    const FoldChainInfo& out_info,
+    std::vector<FoldChainInfo>* in_axis) {
+  if (out_info.kind != kPending) return false;
+  for (int i = 0; i < 2; ++i) {
+    std::pair<int, int> m = MatchBroadcast1DAxis(out_shape[0], in_shape[1 - i]);
+    if (m.second != -1 &&
+        in_shape[i] == out_shape[0] &&
+        m.first == out_info.axis) {
+      (*in_axis)[i].kind = kPending;
+      (*in_axis)[i].axis = out_info.axis;
+      (*in_axis)[i].source = out_info.source;
+      (*in_axis)[1 - i].kind = kMulConsumer;
+      (*in_axis)[1 - i].axis = m.second;
+      (*in_axis)[1 - i].source = out_info.source;
+      return false;
+    }
+  }
+  return false;
+}
+
+bool BroadcastAddSubScaleAxisForward(
     const NodeAttrs& attrs,
-    int axis,
     const std::vector<TShape>& in_shape,
     const std::vector<TShape>& out_shape,
-    std::vector<std::pair<uint32_t, int> >* in_axis) {
+    std::vector<FoldChainInfo>* in_info,
+    FoldChainInfo* out_info) {
   for (int i = 0; i < 2; ++i) {
-    std::pair<int, int> m = MatchBroadcast1DAxis(out_shape[0], in_shape[i]);
-    if (m.second != -1 && in_shape[1 - i] == out_shape[0]) {
-      in_axis->emplace_back(i, axis);
-      in_axis->emplace_back(1 - i, m.second);
-      return kPassTroughFirst;
+    if ((*in_info)[i].kind == kPending) {
+      std::pair<int, int> m = MatchBroadcast1DAxis(out_shape[0], in_shape[1 - i]);
+      if (m.second != -1 &&
+          in_shape[i] == out_shape[0] &&
+          m.first == (*in_info)[i].axis) {
+        out_info->kind = kPending;
+        out_info->axis = m.first;
+        out_info->source = (*in_info)[i].source;
+        (*in_info)[1 - i].kind = kDivConsumer;
+        (*in_info)[1 - i].axis = m.second;
+        (*in_info)[1 - i].source = (*in_info)[i].source;
+        return false;
+      }
     }
   }
-  return kNone;
+  return false;
 }
 
 NNVM_REGISTER_OP(broadcast_add)
@@ -244,28 +451,62 @@ NNVM_REGISTER_OP(broadcast_add)
 NNVM_REGISTER_OP(broadcast_sub)
 .set_attr<FScaleAxisBackward>("FScaleAxisBackward", BroadcastAddSubScaleAxisBackward);
 
-FoldScaleKind Conv2DScaleAxisBackward(
+NNVM_REGISTER_OP(broadcast_add)
+.set_attr<FScaleAxisForward>("FScaleAxisForward", BroadcastAddSubScaleAxisForward);
+
+NNVM_REGISTER_OP(broadcast_sub)
+.set_attr<FScaleAxisForward>("FScaleAxisForward", BroadcastAddSubScaleAxisForward);
+
+bool Conv2DScaleAxisBackward(
     const NodeAttrs& attrs,
-    int axis,
     const std::vector<TShape>& in_shape,
     const std::vector<TShape>& out_shape,
-    std::vector<std::pair<uint32_t, int> >* in_axis) {
+    const FoldChainInfo& out_info,
+    std::vector<FoldChainInfo>* in_axis) {
   using top::Conv2DParam;
   const Conv2DParam& param = nnvm::get<Conv2DParam>(attrs.parsed);
+  if (out_info.kind != kPending) return false;
   // only optimize for nchw for now
-  if (param.layout == top::kNCHW) {
-    in_axis->emplace_back(1, 0);
+  if (param.layout == top::kNCHW && out_info.axis == 1) {
+    (*in_axis)[1].kind = kMulConsumer;
+    (*in_axis)[1].axis = 0;
+    (*in_axis)[1].source = out_info.source;
     if (param.use_bias) {
-      in_axis->emplace_back(2, 0);
+      (*in_axis)[2].kind = kMulConsumer;
+      (*in_axis)[2].axis = 0;
+      (*in_axis)[2].source = out_info.source;
     }
-    return kMulConsumer;
+    return true;
   } else {
-    return kNone;
+    return false;
+  }
+}
+
+bool Conv2DScaleAxisForward(
+    const NodeAttrs& attrs,
+    const std::vector<TShape>& in_shape,
+    const std::vector<TShape>& out_shape,
+    std::vector<FoldChainInfo>* in_info,
+    FoldChainInfo* out_info) {
+  using top::Conv2DParam;
+  const Conv2DParam& param = nnvm::get<Conv2DParam>(attrs.parsed);
+  if ((*in_info)[0].kind != kPending) return false;
+  // only optimize for nchw for now
+  if (param.layout == top::kNCHW && (*in_info)[0].axis == 1) {
+    (*in_info)[1].kind = kMulConsumer;
+    (*in_info)[1].axis = 1;
+    (*in_info)[1].source = (*in_info)[0].source;
+    return true;
+  } else {
+    return false;
   }
 }
 
 NNVM_REGISTER_OP(conv2d)
 .set_attr<FScaleAxisBackward>("FScaleAxisBackward", Conv2DScaleAxisBackward);
 
+NNVM_REGISTER_OP(conv2d)
+.set_attr<FScaleAxisForward>("FScaleAxisForward", Conv2DScaleAxisForward);
+
 }  // namespace compiler
 }  // namespace nnvm
diff --git a/nnvm/src/pass/plan_memory.cc b/nnvm/src/pass/plan_memory.cc
index f96f061b564f..51448bcf1065 100644
--- a/nnvm/src/pass/plan_memory.cc
+++ b/nnvm/src/pass/plan_memory.cc
@@ -196,7 +196,7 @@ size_t AllocMemory(const Graph& ret, const IndexedGraph& idx,
         if (taken[kv.first] == false &&
             sid_out == GraphAllocator::kBadStorageID &&
             sid_in >= 0 &&
-            (storage_ref_count[sid_in] == 1 && !ignore_all_inputs || identity[ipair]) &&
+            ((storage_ref_count[sid_in] == 1 && !ignore_all_inputs) || identity[ipair]) &&
             entry_ref_count[eid_out] > 0 &&
             shape_vec[eid_out].Size() == shape_vec[eid_in].Size() &&
             dtype_vec[eid_out] == dtype_vec[eid_in]) {
diff --git a/nnvm/tests/python/compiler/test_fold_axis.py b/nnvm/tests/python/compiler/test_fold_axis.py
index f306b3a222aa..bbd50193b4b0 100644
--- a/nnvm/tests/python/compiler/test_fold_axis.py
+++ b/nnvm/tests/python/compiler/test_fold_axis.py
@@ -1,22 +1,26 @@
 """Unittest cases for fold_axis"""
 import nnvm
+import nnvm.testing.resnet
+import numpy as np
 from nnvm import symbol as sym
 from nnvm.compiler import graph_util, graph_attr
 
 def test_fold_axis_conv():
-    def before(x, conv_weight, conv_bias, scale, channels):
+    def before(x, conv_weight, conv_bias, in_scale, out_scale, channels):
+        x = x * sym.expand_dims(in_scale, axis=1, num_newaxis=2)
         y = sym.conv2d(x, conv_weight, conv_bias,
                        channels=channels,
                        kernel_size=(3, 3),
                        padding=(1, 1),
                        name="conv")
         y = sym.relu(y)
-        y = y * sym.expand_dims(scale, axis=1, num_newaxis=2)
+        y = y * sym.expand_dims(out_scale, axis=1, num_newaxis=2)
         return y
 
-    def expected(x, conv_weight, conv_bias, scale, channels):
-        conv_weight = conv_weight * sym.expand_dims(scale, axis=1, num_newaxis=3)
-        conv_bias = conv_bias * scale
+    def expected(x, conv_weight, conv_bias, in_scale, out_scale, channels):
+        conv_weight = conv_weight * sym.expand_dims(out_scale, axis=1, num_newaxis=3)
+        conv_weight = conv_weight * sym.expand_dims(in_scale, axis=1, num_newaxis=2)
+        conv_bias = conv_bias * out_scale
         y = sym.conv2d(x,
                        conv_weight,
                        conv_bias,
@@ -32,10 +36,11 @@ def check(shape, channels):
         x = sym.Variable("x") + 1
         weight = sym.Variable("weight")
         bias = sym.Variable("bias")
-        scale = sym.Variable("scale")
-        y1 = before(x, weight, bias, scale, channels)
-        y2 = expected(x, weight, bias, scale, channels)
-        ishape = {"x": shape, "scale": (channels,)}
+        in_scale = sym.Variable("in_scale")
+        out_scale = sym.Variable("out_scale")
+        y1 = before(x, weight, bias, in_scale, out_scale, channels)
+        y2 = expected(x, weight, bias, in_scale, out_scale, channels)
+        ishape = {"x": shape, "out_scale": (channels,), "in_scale": (shape[1],)}
         g1 = nnvm.graph.create(y1)
         g2 = nnvm.graph.create(y2)
         graph_attr.set_shape_inputs(g1, ishape)
@@ -45,5 +50,61 @@ def check(shape, channels):
 
     check((2, 4, 10, 10), 2)
 
+
+def test_fold_fail():
+    def before(x, scale, channels):
+        y = sym.conv2d(x,
+                       channels=channels,
+                       kernel_size=(3, 3),
+                       padding=(1, 1),
+                       name="conv")
+        y = y * sym.expand_dims(scale, axis=1, num_newaxis=1)
+        return y
+
+    # Before simplify
+    def check(shape, channels):
+        x = sym.Variable("x")
+        bias = sym.Variable("bias")
+        scale = sym.Variable("scale")
+        y1 = before(x, scale, channels)
+        ishape = {"x": shape, "scale": (channels,), "bias": (channels,)}
+        g1 = nnvm.graph.create(y1)
+        graph_attr.set_shape_inputs(g1, ishape)
+        g2 = g1.apply("InferShape").apply("FoldScaleAxis")
+        # assert graph equals as expected
+        graph_util.check_graph_equal(g1, g2)
+
+    check((2, 10, 10, 10), 10)
+
+
+def test_fold_resnet():
+    batch_size = 1
+    num_classes = 1000
+    image_shape = (3, 224, 224)
+    data_shape = (batch_size,) +image_shape
+    net, params = nnvm.testing.resnet.get_workload(
+        batch_size=1, image_shape=image_shape)
+    ishape = {"data" : data_shape}
+    graph = nnvm.graph.create(net)
+    data = np.random.uniform(size=data_shape).astype("float32")
+    # Initial pass do shape type inference
+    shape, _ = graph_util.infer_shape(graph, **ishape)
+    ishape.update(zip(graph.index.input_names, shape))
+
+    def run_prune(graph, params, opt_level):
+        # Apply optimization
+        with nnvm.compiler.build_config(opt_level=0):
+            graph = nnvm.compiler.optimize(graph, ishape)
+        graph, params = nnvm.compiler.build_module.precompute_prune(graph, params)
+        params["data"] = data
+        return nnvm.compiler.build_module._run_graph(graph, params)
+
+    x = run_prune(graph, params, 0)
+    y = run_prune(graph, params, 3)
+    np.testing.assert_allclose(y[0].asnumpy(), x[0].asnumpy())
+
+
 if __name__ == "__main__":
+    test_fold_resnet()
     test_fold_axis_conv()
+    test_fold_fail()

From c505193ac170e1f2b9149d17e96433b590b30d12 Mon Sep 17 00:00:00 2001
From: Haibin Lin <linhaibin.eric@gmail.com>
Date: Thu, 5 Apr 2018 13:55:59 -0700
Subject: [PATCH 627/949] declare type name for optional<TShape> (#429)

* declare type for optional tshape

* add doc

* move code to another place
---
 nnvm/include/nnvm/tuple.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/nnvm/include/nnvm/tuple.h b/nnvm/include/nnvm/tuple.h
index 353b79ab0641..c5caa9bd8fe9 100644
--- a/nnvm/include/nnvm/tuple.h
+++ b/nnvm/include/nnvm/tuple.h
@@ -611,6 +611,8 @@ struct hash<nnvm::TShape> {
 }  // namespace std
 
 namespace dmlc {
+/*! \brief description for optional TShape */
+DMLC_DECLARE_TYPE_NAME(optional<nnvm::TShape>, "Shape or None");
 // avoid low version of MSVC
 #if !defined(_MSC_VER)
 template<typename T>

From d68614dfd35766931859ef3fd91dfb3f8b523858 Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Fri, 6 Apr 2018 10:23:29 +0530
Subject: [PATCH 628/949] [FRONTEND] DarkNet Yolo2 Frontend Support  (#377)

---
 nnvm/Makefile                                 |   2 +-
 nnvm/python/nnvm/frontend/__init__.py         |   1 +
 nnvm/python/nnvm/frontend/darknet.py          | 637 ++++++++++++++++++
 nnvm/python/nnvm/testing/__init__.py          |   2 +
 nnvm/python/nnvm/testing/darknet.py           | 494 ++++++++++++++
 nnvm/python/nnvm/testing/yolo2_detection.py   | 246 +++++++
 nnvm/python/nnvm/top/__init__.py              |   1 +
 nnvm/python/nnvm/top/vision.py                |  40 ++
 nnvm/src/top/vision/yolo2/region.cc           |  35 +
 nnvm/src/top/vision/yolo2/region.h            | 101 +++
 nnvm/src/top/vision/yolo2/reorg.cc            |  52 ++
 nnvm/src/top/vision/yolo2/reorg.h             | 110 +++
 nnvm/tests/ci_build/Dockerfile.gpu            |   3 +
 .../install/ubuntu_install_darknet.sh         |   4 +
 .../python/frontend/darknet/test_forward.py   | 257 +++++++
 nnvm/tutorials/from_darknet.py                | 227 +++++++
 16 files changed, 2211 insertions(+), 1 deletion(-)
 create mode 100644 nnvm/python/nnvm/frontend/darknet.py
 create mode 100644 nnvm/python/nnvm/testing/darknet.py
 create mode 100644 nnvm/python/nnvm/testing/yolo2_detection.py
 create mode 100644 nnvm/python/nnvm/top/vision.py
 create mode 100644 nnvm/src/top/vision/yolo2/region.cc
 create mode 100644 nnvm/src/top/vision/yolo2/region.h
 create mode 100644 nnvm/src/top/vision/yolo2/reorg.cc
 create mode 100644 nnvm/src/top/vision/yolo2/reorg.h
 create mode 100644 nnvm/tests/ci_build/install/ubuntu_install_darknet.sh
 create mode 100644 nnvm/tests/python/frontend/darknet/test_forward.py
 create mode 100644 nnvm/tutorials/from_darknet.py

diff --git a/nnvm/Makefile b/nnvm/Makefile
index 4779e95b317a..62a4fadad6f0 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -56,7 +56,7 @@ endif
 all: lib/libnnvm.a lib/libnnvm_compiler.$(SHARED_LIBRARY_SUFFIX)
 
 SRC = $(wildcard src/*.cc src/c_api/*.cc src/core/*.cc src/pass/*.cc)
-SRC_COMPILER = $(wildcard src/top/*/*.cc src/compiler/*.cc src/compiler/*/*.cc)
+SRC_COMPILER = $(wildcard src/top/*/*.cc wildcard src/top/vision/*/*.cc src/compiler/*.cc src/compiler/*/*.cc)
 ALL_OBJ = $(patsubst %.cc, build/%.o, $(SRC))
 TOP_OBJ = $(patsubst %.cc, build/%.o, $(SRC_COMPILER))
 ALL_DEP = $(ALL_OBJ)
diff --git a/nnvm/python/nnvm/frontend/__init__.py b/nnvm/python/nnvm/frontend/__init__.py
index 100d4115bc3a..00ed9e51fbfc 100644
--- a/nnvm/python/nnvm/frontend/__init__.py
+++ b/nnvm/python/nnvm/frontend/__init__.py
@@ -4,3 +4,4 @@
 from .onnx import from_onnx
 from .coreml import from_coreml
 from .keras import from_keras
+from .darknet import from_darknet
diff --git a/nnvm/python/nnvm/frontend/darknet.py b/nnvm/python/nnvm/frontend/darknet.py
new file mode 100644
index 000000000000..413b07d648a4
--- /dev/null
+++ b/nnvm/python/nnvm/frontend/darknet.py
@@ -0,0 +1,637 @@
+"""
+DarkNet symbol frontend.
+"""
+
+from __future__ import absolute_import as _abs
+from enum import IntEnum
+import numpy as np
+import tvm
+from .. import symbol as _sym
+
+class LAYERTYPE(IntEnum):
+    """Darknet LAYERTYPE Class constant."""
+    CONVOLUTIONAL = 0
+    DECONVOLUTIONAL = 1
+    CONNECTED = 2
+    MAXPOOL = 3
+    SOFTMAX = 4
+    DETECTION = 5
+    DROPOUT = 6
+    CROP = 7
+    ROUTE = 8
+    COST = 9
+    NORMALIZATION = 10
+    AVGPOOL = 11
+    LOCAL = 12
+    SHORTCUT = 13
+    ACTIVE = 14
+    RNN = 15
+    GRU = 16
+    LSTM = 17
+    CRNN = 18
+    BATCHNORM = 19
+    NETWORK = 20
+    XNOR = 21
+    REGION = 22
+    REORG = 23
+    BLANK = 24
+
+class ACTIVATION(IntEnum):
+    """Darknet ACTIVATION Class constant."""
+    LOGISTIC = 0
+    RELU = 1
+    RELIE = 2
+    LINEAR = 3
+    RAMP = 4
+    TANH = 5
+    PLSE = 6
+    LEAKY = 7
+    ELU = 8
+    LOGGY = 9
+    STAIR = 10
+    HARDTAN = 11
+    LHTAN = 12
+
+__all__ = ['from_darknet']
+
+def _darknet_get_nnvm_op(op_name):
+    """Get the nnvm operation from opname, raise error if not supported."""
+    op = getattr(_sym, op_name)
+    if not op:
+        raise RuntimeError("Not to map op_name {} to nnvm.sym".format(op_name))
+    return op
+
+def _darknet_required_attr(attr, key):
+    """Check the attribute exists and return if exists, if not return error."""
+    assert isinstance(attr, dict)
+    if key not in attr:
+        raise AttributeError("Required attribute {} not found.".format(key))
+    return attr[key]
+
+def _darknet_raise_not_supported(attr, op='nnvm'):
+    """Raise error if any operation is not supported."""
+    err = "{} is not supported in {}.".format(attr, op)
+    raise NotImplementedError(err)
+
+def _darknet_warn_not_used(attr, op='nnvm'):
+    """Raise warning if any operation not supported."""
+    import warnings
+    err = "{} is ignored in {}.".format(attr, op)
+    warnings.warn(err)
+
+def _darknet_parse_tshape(tshape):
+    """Parse tshape in string."""
+    return [int(x.strip()) for x in tshape.strip('()').split(',')]
+
+def _darknet_parse_bool_str(attr, key, default='False'):
+    """Parse bool string to boolean."""
+    return attr.get(key, default).strip().lower() in \
+                                    ['true', '1', 't', 'y', 'yes']
+
+def _darknet_maxpooling(inputs, attrs):
+    """Process the max pool 2d operation."""
+    kernel = _darknet_parse_tshape(_darknet_required_attr(attrs, 'kernel'))
+    if len(kernel) != 1:
+        _darknet_raise_not_supported('non-2d kernel', 'pool_2d')
+
+    op_name, new_attrs = 'max_pool2d', {}
+    strides = int(attrs.get('stride', (1, 1)))
+    pads = int(attrs.get('pad', (0, 0)))
+    new_attrs['pool_size'] = [kernel[0], kernel[0]]
+    new_attrs['strides'] = str((strides, strides))
+    new_attrs['padding'] = str((pads, pads))
+
+    return _darknet_get_nnvm_op(op_name)(*inputs, **new_attrs), None
+
+def _darknet_avgpooling(inputs, attrs):
+    """Process the average pool 2d operation."""
+    kernel = _darknet_parse_tshape(_darknet_required_attr(attrs, 'kernel'))
+    if len(kernel) != 1:
+        _darknet_raise_not_supported('non-2d kernel', 'pool_2d')
+
+    op_name, new_attrs = 'avg_pool2d', {}
+    strides = int(attrs.get('stride', (1, 1)))
+    pads = int(attrs.get('pad', (0, 0)))
+    new_attrs['pool_size'] = [kernel[0], kernel[0]]
+    new_attrs['strides'] = str((strides, strides))
+    new_attrs['padding'] = str((pads, pads))
+
+    return _darknet_get_nnvm_op(op_name)(*inputs, **new_attrs), None
+
+def _darknet_batch_norm(inputs, attrs):
+    """Process the batchnormalization operation."""
+    op_name, new_attrs = 'darknet_batch_norm', {}
+    new_attrs['axis'] = attrs.get('axis', 1)
+    new_attrs['epsilon'] = attrs.get('eps', 0.000001)
+    new_attrs['center'] = True
+    new_attrs['scale'] = True
+    return _darknet_get_nnvm_op(op_name)(*inputs, **new_attrs), None
+
+def _darknet_conv2d(inputs, attrs):
+    """Process the convolution 2d operation."""
+    kernel = _darknet_parse_tshape(_darknet_required_attr(attrs, 'kernel'))
+    if len(kernel) != 1:
+        _darknet_raise_not_supported('non 2d kernel', 'conv2d')
+    layout = attrs.get('layout', 'NCHW')
+    if layout not in ['NCHW', 'NHWC']:
+        _darknet_raise_not_supported('layout: ' + layout, 'conv2d')
+    strides = int(attrs.get('stride', (1, 1)))
+    pads = int(attrs.get('pad', (0, 0)))
+
+    op_name, new_attrs = 'conv2d', {}
+    new_attrs['channels'] = _darknet_required_attr(attrs, 'num_filter')
+    new_attrs['kernel_size'] = [kernel[0], kernel[0]]
+    new_attrs['strides'] = (strides, strides)
+    new_attrs['padding'] = (pads, pads)
+    new_attrs['dilation'] = attrs.get('dilate', (1, 1))
+    new_attrs['groups'] = attrs.get('num_group', 1)
+    new_attrs['layout'] = layout
+    if attrs.get('use_batchNorm', False) is True:
+        new_attrs['use_bias'] = False
+    else:
+        new_attrs['use_bias'] = True
+    out_name = {}
+    sym = _darknet_get_nnvm_op(op_name)(*inputs, **new_attrs)
+    out_name[0] = sym.list_output_names()[0].replace('_output', '')
+
+    if attrs.get('use_batchNorm', False) is True:
+        op_name, new_attrs = 'batch_norm', {}
+        new_attrs['epsilon'] = 0.000001
+        sym = _darknet_get_nnvm_op(op_name)(*sym, **new_attrs)
+        out_name[1] = sym.list_output_names()[0].replace('_output', '')
+    if 'activation' in attrs:
+        new_attrs = {}
+        new_attrs['activation'] = attrs['activation']
+        new_attrs['slope'] = 0.1
+        sym, _ = _darknet_activations(sym, new_attrs)
+    return sym, out_name
+
+
+def _darknet_conv2d_transpose(inputs, attrs):
+    """Process the convolution 2d transpose operation."""
+    if 'target_shape' in attrs:
+        _darknet_raise_not_supported('target_shape', 'conv2d_transpose')
+    kernel = _darknet_parse_tshape(_darknet_required_attr(attrs, 'kernel'))
+    if len(kernel) != 2:
+        _darknet_raise_not_supported('non-2d kernel', 'conv2d_transpose')
+    layout = attrs.get('layout', 'NCHW')
+    if layout not in ['NCHW', 'NHWC']:
+        _darknet_raise_not_supported('layout: ' + layout, 'conv2d_transpose')
+    op_name, new_attrs = 'conv2d_transpose', {}
+    new_attrs['channels'] = _darknet_required_attr(attrs, 'num_filter')
+    new_attrs['kernel_size'] = kernel
+    new_attrs['strides'] = attrs.get('stride', (1, 1))
+    new_attrs['output_padding'] = attrs.get('adj', (0, 0))
+    new_attrs['padding'] = attrs.get('pad', (0, 0))
+    new_attrs['dilation'] = attrs.get('dilate', (1, 1))
+    new_attrs['groups'] = attrs.get('num_group', 1)
+    new_attrs['layout'] = layout
+    new_attrs['use_bias'] = not _darknet_parse_bool_str(attrs, 'no_bias')
+    return _darknet_get_nnvm_op(op_name)(*inputs, **new_attrs), None
+
+def _darknet_shortcut(inputs, attrs):
+    """Process the shortcut operation."""
+    op_name, new_attrs = 'elemwise_add', {}
+    input_0 = inputs[0]
+    input_1 = inputs[1]
+    input_0_channel = int(attrs['out_channel'])
+    input_1_channel = int(attrs['add_out_channel'])
+    input_0_size = int(attrs['out_size'])
+    input_1_size = int(attrs['add_out_size'])
+
+    if input_0_size > input_1_size:
+        scale = int(input_0_size/input_1_size)
+        input_1 = _sym.upsampling(input_1, scale=scale, name="_upsampling")
+    elif input_0_size < input_1_size:
+        stride = int(input_1_size/input_0_size)
+        input_1 = _sym.avg_pool2d(input_1, pool_size=(1, 1),
+                                  strides=(stride, stride), padding=(0, 0), name="_downsampling")
+
+    if input_0_channel != input_1_channel:
+        pad_channel = input_0_channel - input_1_channel
+        input_1 = _sym.pad(input_1, pad_width=((0, 0), (0, pad_channel), (0, 0), (0, 0)),
+                           pad_value=0.)
+
+    new_inputs = _as_list([input_0, input_1])
+    sym = _darknet_get_nnvm_op(op_name)(*new_inputs, **new_attrs)
+    out_name = sym.list_output_names()[0].replace('_output', '')
+    if 'activation' in attrs:
+        new_attrs['activation'] = attrs['activation']
+        sym, _ = _darknet_activations(sym, new_attrs)
+    return sym, out_name
+
+def _darknet_dense(inputs, attrs):
+    """Process the dense operation."""
+    op_name, new_attrs = 'dense', {}
+    new_attrs['units'] = _darknet_required_attr(attrs, 'num_hidden')
+
+    if attrs.get('use_bias', False) is True:
+        new_attrs['use_bias'] = True
+    if attrs.get('use_flatten', False) is True:
+        inputs[0] = _sym.flatten(inputs[0])
+    sym = _darknet_get_nnvm_op(op_name)(*inputs, **new_attrs)
+    out_name = sym.list_output_names()[0].replace('_output', '')
+    if 'activation' in attrs:
+        new_attrs = {}
+        new_attrs['activation'] = attrs['activation']
+        sym, _ = _darknet_activations(sym, new_attrs)
+    return sym, out_name
+
+def _darknet_dropout(inputs, attrs):
+    """Process the dropout operation, its a blank operation."""
+    op_name, new_attrs = 'dropout', {}
+    new_attrs['rate'] = attrs.get('p', 0.5)
+    return _darknet_get_nnvm_op(op_name)(*inputs, **new_attrs), None
+
+def _darknet_reshape(inputs, attrs):
+    """Process the reshape operation."""
+    if _darknet_parse_bool_str(attrs, 'reverse'):
+        _darknet_raise_not_supported('reverse', 'reshape')
+    op_name, new_attrs = 'reshape', {}
+    new_attrs['shape'] = _darknet_required_attr(attrs, 'shape')
+    return _darknet_get_nnvm_op(op_name)(*inputs, **new_attrs), None
+
+def _darknet_softmax_output(inputs, attrs):
+    """Process the softmax operation."""
+    op_name, new_attrs = 'softmax', {}
+    if _darknet_parse_bool_str(attrs, 'multi_output'):
+        new_attrs['axis'] = 1
+
+    if attrs.get('use_flatten', False) is True:
+        inputs[0] = _sym.flatten(inputs[0])
+    return _darknet_get_nnvm_op(op_name)(*inputs, **new_attrs), None
+
+def _darknet_route(inputs, attrs):
+    """Process the route operation, which is equivalent to concat."""
+    op_name = 'concatenate'
+    new_attrs = {'axis': attrs.get('dim', 1)}
+    return _darknet_get_nnvm_op(op_name)(*inputs, **new_attrs), None
+
+def _darknet_reorg(inputs, attrs):
+    """Process the reorg operation."""
+    op_name, new_attrs = 'yolo2_reorg', {}
+    if 'stride' in attrs:
+        new_attrs = {'stride': attrs.get('stride', 1)}
+    return _darknet_get_nnvm_op(op_name)(*inputs, **new_attrs), None
+
+def _darknet_region(inputs, attrs):
+    """Process the region operation."""
+    op_name, new_attrs = 'yolo2_region', {}
+    if 'n' in attrs:
+        new_attrs['n'] = attrs.get('n', 1)
+    if 'classes' in attrs:
+        new_attrs['classes'] = attrs.get('classes', 1)
+    if 'coords' in attrs:
+        new_attrs['coords'] = attrs.get('coords', 0)
+    if 'background' in attrs:
+        new_attrs['background'] = attrs.get('background', 0)
+    if 'softmax' in attrs:
+        new_attrs['softmax'] = attrs.get('softmax', 0)
+    return _darknet_get_nnvm_op(op_name)(*inputs, **new_attrs), None
+
+def _darknet_activations(inputs, attrs):
+    """Process the activation function."""
+    act = _darknet_required_attr(attrs, 'activation')
+    if ACTIVATION.RELU == act:
+        act_type = 'relu'
+    elif ACTIVATION.TANH == act:
+        act_type = 'tanh'
+    elif ACTIVATION.LINEAR == act:
+        return inputs, None
+    elif ACTIVATION.LEAKY == act:
+        act_type = 'leaky_relu'
+    else:
+        _darknet_raise_not_supported('act: ' + act)
+
+    if act_type in ['relu', 'tanh']:
+        op_name, new_attrs = act_type, {}
+        sym = _darknet_get_nnvm_op(op_name)(*inputs, **new_attrs)
+    elif act_type in ['leaky_relu']:
+        op_name, new_attrs = act_type, {}
+        new_attrs['alpha'] = attrs.get('slope', 0.1)
+        sym = _darknet_get_nnvm_op(op_name)(*inputs, **new_attrs)
+    else:
+        _darknet_raise_not_supported('act_type: ' + act_type)
+    return sym, None
+
+def _darknet_op_not_support(inputs, attrs):
+    """Raise exception if the operation is not supported."""
+    err = "{} is not supported in {}.".format(attrs, inputs)
+    raise NotImplementedError(err)
+
+_DARKNET_CONVERT_MAP = {
+    'CONVOLUTIONAL'   : _darknet_conv2d,
+    'DECONVOLUTIONAL' : _darknet_conv2d_transpose,
+    'CONNECTED'       : _darknet_dense,
+    'MAXPOOL'         : _darknet_maxpooling,
+    'SOFTMAX'         : _darknet_softmax_output,
+    'DROPOUT'         : _darknet_dropout,
+    'AVGPOOL'         : _darknet_avgpooling,
+    'BATCHNORM'       : _darknet_batch_norm,
+    'RESHAPE'         : _darknet_reshape,
+    'ROUTE'           : _darknet_route,
+    'REORG'           : _darknet_reorg,
+    'REGION'          : _darknet_region,
+    'ACTIVATION'      : _darknet_activations,
+    'SHORTCUT'        : _darknet_shortcut,
+    'DETECTION'       : _darknet_op_not_support,
+    'CROP'            : _darknet_op_not_support,
+    'COST'            : _darknet_op_not_support,
+    'NORMALIZATION'   : _darknet_op_not_support,
+    'LOCAL'           : _darknet_op_not_support,
+    'ACTIVE'          : _darknet_op_not_support,
+    'RNN'             : _darknet_op_not_support,
+    'GRU'             : _darknet_op_not_support,
+    'LSTM'            : _darknet_op_not_support,
+    'CRNN'            : _darknet_op_not_support,
+    'NETWORK'         : _darknet_op_not_support,
+    'XNOR'            : _darknet_op_not_support,
+    'BLANK'           : _darknet_op_not_support,
+}
+
+def _darknet_convert_symbol(op_name, inputs, attrs):
+    """Convert from darknet op to nnvm op.
+    The converter must specify some conversions explicitly to
+    support gluon format ops such as conv2d...
+
+    Parameters
+    ----------
+    op_name : str
+        Operator name, such as Convolution, Connected, etc
+    inputs : list of nnvm.Symbol
+        List of input symbols.
+    attrs : dict
+        Dict of operator attributes
+
+    Returns
+    -------
+    out_name : converted out name of operation
+    sym : nnvm.Symbol
+        Converted nnvm Symbol
+    """
+
+    if op_name in _DARKNET_CONVERT_MAP:
+        sym, out_name = _DARKNET_CONVERT_MAP[op_name](inputs, attrs)
+    else:
+        _darknet_raise_not_supported('Operator: ' + op_name)
+    if out_name is  None:
+        out_name = sym.list_output_names()[0].replace('_output', '')
+    return out_name, sym
+
+
+def _as_list(arr):
+    """Force being a list, ignore if already is."""
+    if isinstance(arr, list):
+        return arr
+    return [arr]
+
+def _read_memory_buffer(shape, data, dtype):
+    length = 1
+    for x in shape:
+        length *= x
+    data_np = np.zeros(length, dtype=dtype)
+    for i in range(length):
+        data_np[i] = data[i]
+    return data_np.reshape(shape)
+
+def _get_darknet_layername(layer_type):
+    """Get the layer name from the darknet enums."""
+    return str((LAYERTYPE(layer_type))).replace('LAYERTYPE.', '')
+
+def _get_convolution_weights(layer, opname, params, dtype):
+    """Get the convolution layer weights and biases."""
+    if layer.nweights == 0:
+        return
+
+    if (layer.n * layer.c * layer.size * layer.size) != layer.nweights:
+        raise RuntimeError("layer weights size not matching with n c h w")
+
+    weights = _read_memory_buffer((layer.n, layer.c, layer.size, layer.size), layer.weights, dtype)
+
+    biases = _read_memory_buffer((layer.n, ), layer.biases, dtype)
+
+    k = _get_tvm_params_name(opname[0], 'weight')
+    params[k] = tvm.nd.array(weights)
+
+    if layer.batch_normalize == 1 and layer.dontloadscales != 1:
+        _get_batchnorm_weights(layer, opname[1], params, layer.n, dtype)
+        k = _get_tvm_params_name(opname[1], 'beta')
+        params[k] = tvm.nd.array(biases)
+    else:
+        k = _get_tvm_params_name(opname[0], 'bias')
+        params[k] = tvm.nd.array(biases)
+
+def _get_connected_weights(layer, opname, params, dtype):
+    """Parse the weights and biases for fully connected or dense layer."""
+    size = layer.outputs * layer.inputs
+    if size == 0:
+        return
+
+    weights = _read_memory_buffer((layer.outputs, layer.inputs), layer.weights, dtype)
+    biases = _read_memory_buffer((layer.outputs, ), layer.biases, dtype)
+
+    k = _get_tvm_params_name(opname, 'weight')
+    params[k] = tvm.nd.array(weights)
+    k = _get_tvm_params_name(opname, 'bias')
+    params[k] = tvm.nd.array(biases)
+
+    if layer.batch_normalize == 1 and layer.dontloadscales != 1:
+        _get_batchnorm_weights(layer, opname, params, layer.outputs, dtype)
+
+def _get_batchnorm_weights(layer, opname, params, size, dtype):
+    """Parse the weights for batchnorm, which includes, scales, moving mean
+    and moving variances."""
+    scales = _read_memory_buffer((size, ), layer.scales, dtype)
+    rolling_mean = _read_memory_buffer((size, ), layer.rolling_mean, dtype)
+    rolling_variance = _read_memory_buffer((size, ), layer.rolling_variance, dtype)
+
+    k = _get_tvm_params_name(opname, 'moving_mean')
+    params[k] = tvm.nd.array(rolling_mean)
+    k = _get_tvm_params_name(opname, 'moving_var')
+    params[k] = tvm.nd.array(rolling_variance)
+    k = _get_tvm_params_name(opname, 'gamma')
+    params[k] = tvm.nd.array(scales)
+
+def _get_darknet_attrs(net, layer_num):
+    """Parse attributes of each layer and return."""
+    attr = {}
+    use_flatten = True
+    layer = net.layers[layer_num]
+    op_name = _get_darknet_layername(layer.type)
+
+    if LAYERTYPE.CONVOLUTIONAL == layer.type:
+        attr.update({'layout' : 'NCHW'})
+        attr.update({'pad' : str(layer.pad)})
+        attr.update({'num_group' : str(layer.groups)})
+        attr.update({'num_filter' : str(layer.n)})
+        attr.update({'stride' : str(layer.stride)})
+        attr.update({'kernel' : str(layer.size)})
+        attr.update({'activation' : (layer.activation)})
+
+        if layer.nbiases == 0:
+            attr.update({'use_bias' : False})
+        else:
+            attr.update({'use_bias' : True})
+
+        if layer.batch_normalize == 1 and layer.dontloadscales != 1:
+            attr.update({'use_batchNorm' : True})
+            attr.update({'use_scales' : True})
+
+    #elif LAYERTYPE.BATCHNORM == layer.type:
+    #    attr.update({'flatten' : str('True')})
+
+    elif LAYERTYPE.CONNECTED == layer.type:
+        attr.update({'num_hidden' : str(layer.outputs)})
+        attr.update({'activation' : (layer.activation)})
+        if layer_num != 0:
+            layer_prev = net.layers[layer_num - 1]
+            if (layer_prev.out_h == layer.h and
+                    layer_prev.out_w == layer.w and
+                    layer_prev.out_c == layer.c):
+                use_flatten = False
+        attr.update({'use_flatten' : use_flatten})
+        if layer.nbiases == 0:
+            attr.update({'use_bias' : False})
+        else:
+            attr.update({'use_bias' : True})
+        if layer.batch_normalize == 1 and layer.dontloadscales != 1:
+            attr.update({'use_batchNorm' : True})
+            attr.update({'use_scales' : True})
+
+    elif LAYERTYPE.MAXPOOL == layer.type:
+        attr.update({'pad' : str(layer.pad)})
+        attr.update({'stride' : str(layer.stride)})
+        attr.update({'kernel' : str(layer.size)})
+
+    elif LAYERTYPE.AVGPOOL == layer.type:
+        attr.update({'pad' : str(layer.pad)})
+        if layer.stride == 0:
+            attr.update({'stride' : str(1)})
+        else:
+            attr.update({'stride' : str(layer.stride)})
+        if layer.size == 0 and layer.h == layer.w:
+            attr.update({'kernel' : str(layer.h)})
+        else:
+            attr.update({'kernel' : str(layer.size)})
+
+    elif LAYERTYPE.DROPOUT == layer.type:
+        attr.update({'p' : str(layer.probability)})
+
+    elif LAYERTYPE.SOFTMAX == layer.type:
+        attr.update({'axis' : 1})
+        attr.update({'use_flatten' : True})
+
+    elif LAYERTYPE.SHORTCUT == layer.type:
+        add_layer = net.layers[layer.index]
+        attr.update({'activation' : (layer.activation)})
+        attr.update({'out_channel' : (layer.out_c)})
+        attr.update({'out_size' : (layer.out_h)})
+        attr.update({'add_out_channel' : (add_layer.out_c)})
+        attr.update({'add_out_size' : (add_layer.out_h)})
+
+    elif LAYERTYPE.ROUTE == layer.type:
+        pass
+
+    elif LAYERTYPE.COST == layer.type:
+        pass
+
+    elif LAYERTYPE.REORG == layer.type:
+        attr.update({'stride' : layer.stride})
+
+    elif LAYERTYPE.REGION == layer.type:
+        attr.update({'n' : layer.n})
+        attr.update({'classes' : layer.classes})
+        attr.update({'coords' : layer.coords})
+        attr.update({'background' : layer.background})
+        attr.update({'softmax' : layer.softmax})
+    else:
+        err = "Darknet layer {} is not supported in nnvm.".format(op_name)
+        raise NotImplementedError(err)
+
+    return op_name, attr
+
+def _get_tvm_params_name(opname, arg_name):
+    """Makes the params name for the k,v pair."""
+    return opname + '_'+ arg_name
+
+def _get_darknet_params(layer, opname, tvmparams, dtype='float32'):
+    """To parse and get the darknet params."""
+    if LAYERTYPE.CONVOLUTIONAL == layer.type:
+        _get_convolution_weights(layer, opname, tvmparams, dtype)
+
+    #elif LAYERTYPE.BATCHNORM == layer.type:
+    #   size = layer.outputs
+    #   _get_batchnorm_weights(layer, opname, tvmparams, size, dtype)
+
+    elif LAYERTYPE.CONNECTED == layer.type:
+        _get_connected_weights(layer, opname, tvmparams, dtype)
+
+def _preproc_layer(net, i, sym_array):
+    """To preprocess each darknet layer, some layer doesnt need processing."""
+    layer = net.layers[i]
+    if i == 0:
+        name = 'data'
+        attribute = {}
+        sym = [_sym.Variable(name, **attribute)]
+    else:
+        sym = sym_array[i - 1]
+    skip_layer = False
+
+    if LAYERTYPE.ROUTE == layer.type:
+        sym = []
+        for j in range(layer.n):
+            sym.append(sym_array[layer.input_layers[j]])
+        if layer.n == 1:
+            skip_layer = True
+
+    elif LAYERTYPE.COST == layer.type:
+        skip_layer = True
+
+    elif LAYERTYPE.SHORTCUT == layer.type:
+        sym = [sym, sym_array[layer.index]]
+
+    elif LAYERTYPE.BLANK == layer.type:
+        skip_layer = True
+
+    if skip_layer is True:
+        sym_array[i] = sym
+
+    return skip_layer, sym
+
+def _from_darknet(net, dtype='float32'):
+    """To convert the darknet symbol to nnvm symbols."""
+    sym_array = {}
+    tvmparams = {}
+    for i in range(net.n):
+        need_skip, sym = _preproc_layer(net, i, sym_array)
+        if need_skip is True:
+            continue
+        op_name, attr = _get_darknet_attrs(net, i)
+        layer_name, sym = _darknet_convert_symbol(op_name, _as_list(sym), attr)
+        _get_darknet_params(net.layers[i], layer_name, tvmparams, dtype)
+        sym_array[i] = sym
+
+    return sym, tvmparams
+
+def from_darknet(net, dtype='float32'):
+    """Convert from darknet's model into compatible NNVM format.
+    Reconstruct a nnvm symbol by traversing the darknet input.
+
+    Parameters
+    ----------
+    net : ctype Pointer to network
+        Darknet parsed symbols
+
+    dtype : str
+        Datatype of the input net structure, default is float32
+
+    Returns
+    -------
+    sym : nnvm.Symbol
+        Compatible nnvm symbol
+
+    params : dict of str to tvm.NDArray
+        The parameter dict to be used by nnvm
+    """
+
+    return _from_darknet(net, dtype)
diff --git a/nnvm/python/nnvm/testing/__init__.py b/nnvm/python/nnvm/testing/__init__.py
index 84c51a4df41d..19c37f7ac187 100644
--- a/nnvm/python/nnvm/testing/__init__.py
+++ b/nnvm/python/nnvm/testing/__init__.py
@@ -7,3 +7,5 @@
 from . import mlp
 from . import resnet
 from . import vgg
+from . import darknet
+from . import yolo2_detection
diff --git a/nnvm/python/nnvm/testing/darknet.py b/nnvm/python/nnvm/testing/darknet.py
new file mode 100644
index 000000000000..30b790bb4e6d
--- /dev/null
+++ b/nnvm/python/nnvm/testing/darknet.py
@@ -0,0 +1,494 @@
+# pylint: disable=invalid-name, unused-variable, unused-argument, no-init
+"""
+Compile DarkNet Models
+====================
+DarkNet helper functions for darknet model parsing and image loading.
+This functions will not be loaded by default.
+These are utility functions used for testing and tutorial file.
+"""
+from __future__ import division
+from enum import IntEnum
+import math
+import numpy as np
+import cv2
+from cffi import FFI
+
+def _resize_image(img, w_in, h_in):
+    """Resize the image to the given height and width."""
+    imc, imh, imw = img.shape
+    h_in = int(h_in)
+    w_in = int(w_in)
+    part = np.zeros((imc, imh, w_in))
+    resized = np.zeros((imc, h_in, w_in))
+    w_scale = (imw - 1) / (w_in - 1)
+    h_scale = (imh - 1) / (h_in - 1)
+    for k in range(imc):
+        for j in range(imh):
+            for c in range(w_in):
+                if c == w_in - 1 or imw == 1:
+                    part[k][j][c] = img[k][j][imw - 1]
+                else:
+                    fdx, idx = math.modf(c * w_scale)
+                    part[k][j][c] = (1 - fdx) * img[k][j][int(idx)] + \
+                                            fdx * img[k][j][int(idx) + 1]
+    for k in range(imc):
+        for j in range(h_in):
+            fdy, idy = math.modf(j * h_scale)
+            for c in range(w_in):
+                resized[k][j][c] = (1 - fdy)*part[k][int(idy)][c]
+            if (j == h_in - 1) or (imh == 1):
+                continue
+            for c in range(w_in):
+                resized[k][j][c] += fdy * part[k][int(idy) + 1][c]
+    return resized
+
+def load_image_color(test_image):
+    """To load the image using opencv api and do preprocessing."""
+    imagex = cv2.imread(test_image)
+    imagex = np.array(imagex)
+    imagex = imagex.transpose((2, 0, 1))
+    imagex = np.divide(imagex, 255.0)
+    imagex = np.flip(imagex, 0)
+    return imagex
+
+def _letterbox_image(img, w_in, h_in):
+    """To get the image in boxed format."""
+    imc, imh, imw = img.shape
+    if (w_in / imw) < (h_in / imh):
+        new_w = w_in
+        new_h = imh * w_in / imw
+    else:
+        new_h = h_in
+        new_w = imw * h_in/imh
+    resized = _resize_image(img, new_w, new_h)
+    boxed = np.full((imc, h_in, w_in), 0.5, dtype=float)
+    _, resizedh, resizedw = resized.shape
+    boxed[:, int((h_in - new_h) / 2)
+          :int((h_in - new_h) / 2) + resizedh, int((w_in - new_w) / 2)
+          :int((w_in - new_w) / 2) + resizedw] = resized
+    return boxed
+
+def load_image(image, resize_width, resize_height):
+    """Load the image and convert to the darknet model format.
+    The image processing of darknet is different from normal.
+    Parameters
+    ----------
+    image : string
+        The image file name with path
+
+    resize_width : integer
+        The width to which the image needs to be resized
+
+    resize_height : integer
+        The height to which the image needs to be resized
+
+    Returns
+    -------
+    img : Float array
+        Array of processed image
+    """
+
+    img = load_image_color(image)
+    return _letterbox_image(img, resize_width, resize_height)
+
+class LAYERTYPE(IntEnum):
+    """Darknet LAYERTYPE Class constant."""
+    CONVOLUTIONAL = 0
+    DECONVOLUTIONAL = 1
+    CONNECTED = 2
+    MAXPOOL = 3
+    SOFTMAX = 4
+    DETECTION = 5
+    DROPOUT = 6
+    CROP = 7
+    ROUTE = 8
+    COST = 9
+    NORMALIZATION = 10
+    AVGPOOL = 11
+    LOCAL = 12
+    SHORTCUT = 13
+    ACTIVE = 14
+    RNN = 15
+    GRU = 16
+    LSTM = 17
+    CRNN = 18
+    BATCHNORM = 19
+    NETWORK = 20
+    XNOR = 21
+    REGION = 22
+    REORG = 23
+    BLANK = 24
+
+class ACTIVATION(IntEnum):
+    """Darknet ACTIVATION Class constant."""
+    LOGISTIC = 0
+    RELU = 1
+    RELIE = 2
+    LINEAR = 3
+    RAMP = 4
+    TANH = 5
+    PLSE = 6
+    LEAKY = 7
+    ELU = 8
+    LOGGY = 9
+    STAIR = 10
+    HARDTAN = 11
+    LHTAN = 12
+
+__darknetffi__ = FFI()
+
+__darknetffi__.cdef("""
+typedef struct network network;
+typedef struct layer layer;
+
+typedef struct{
+    int *leaf;
+    int n;
+    int *parent;
+    int *child;
+    int *group;
+    char **name;
+
+    int groups;
+    int *group_size;
+    int *group_offset;
+} tree;
+
+typedef enum{
+    LOGISTIC, RELU, RELIE, LINEAR, RAMP, TANH, PLSE, LEAKY, ELU, LOGGY, STAIR, HARDTAN, LHTAN
+} ACTIVATION;
+
+
+typedef enum {
+    CONVOLUTIONAL,
+    DECONVOLUTIONAL,
+    CONNECTED,
+    MAXPOOL,
+    SOFTMAX,
+    DETECTION,
+    DROPOUT,
+    CROP,
+    ROUTE,
+    COST,
+    NORMALIZATION,
+    AVGPOOL,
+    LOCAL,
+    SHORTCUT,
+    ACTIVE,
+    RNN,
+    GRU,
+    LSTM,
+    CRNN,
+    BATCHNORM,
+    NETWORK,
+    XNOR,
+    REGION,
+    REORG,
+    BLANK
+} LAYERTYPE;
+
+typedef enum{
+    SSE, MASKED, LONE, SEG, SMOOTH
+} COSTTYPE;
+
+
+struct layer{
+    LAYERTYPE type;
+    ACTIVATION activation;
+    COSTTYPE cost_type;
+    void (*forward);
+    void (*backward);
+    void (*update);
+    void (*forward_gpu);
+    void (*backward_gpu);
+    void (*update_gpu);
+    int batch_normalize;
+    int shortcut;
+    int batch;
+    int forced;
+    int flipped;
+    int inputs;
+    int outputs;
+    int nweights;
+    int nbiases;
+    int extra;
+    int truths;
+    int h,w,c;
+    int out_h, out_w, out_c;
+    int n;
+    int max_boxes;
+    int groups;
+    int size;
+    int side;
+    int stride;
+    int reverse;
+    int flatten;
+    int spatial;
+    int pad;
+    int sqrt;
+    int flip;
+    int index;
+    int binary;
+    int xnor;
+    int steps;
+    int hidden;
+    int truth;
+    float smooth;
+    float dot;
+    float angle;
+    float jitter;
+    float saturation;
+    float exposure;
+    float shift;
+    float ratio;
+    float learning_rate_scale;
+    int softmax;
+    int classes;
+    int coords;
+    int background;
+    int rescore;
+    int objectness;
+    int does_cost;
+    int joint;
+    int noadjust;
+    int reorg;
+    int log;
+    int tanh;
+
+    float alpha;
+    float beta;
+    float kappa;
+
+    float coord_scale;
+    float object_scale;
+    float noobject_scale;
+    float mask_scale;
+    float class_scale;
+    int bias_match;
+    int random;
+    float thresh;
+    int classfix;
+    int absolute;
+
+    int onlyforward;
+    int stopbackward;
+    int dontload;
+    int dontloadscales;
+
+    float temperature;
+    float probability;
+    float scale;
+
+    char  * cweights;
+    int   * indexes;
+    int   * input_layers;
+    int   * input_sizes;
+    int   * map;
+    float * rand;
+    float * cost;
+    float * state;
+    float * prev_state;
+    float * forgot_state;
+    float * forgot_delta;
+    float * state_delta;
+    float * combine_cpu;
+    float * combine_delta_cpu;
+
+    float * concat;
+    float * concat_delta;
+
+    float * binary_weights;
+
+    float * biases;
+    float * bias_updates;
+
+    float * scales;
+    float * scale_updates;
+
+    float * weights;
+    float * weight_updates;
+
+    float * delta;
+    float * output;
+    float * squared;
+    float * norms;
+
+    float * spatial_mean;
+    float * mean;
+    float * variance;
+
+    float * mean_delta;
+    float * variance_delta;
+
+    float * rolling_mean;
+    float * rolling_variance;
+
+    float * x;
+    float * x_norm;
+
+    float * m;
+    float * v;
+
+    float * bias_m;
+    float * bias_v;
+    float * scale_m;
+    float * scale_v;
+
+
+    float *z_cpu;
+    float *r_cpu;
+    float *h_cpu;
+    float * prev_state_cpu;
+
+    float *temp_cpu;
+    float *temp2_cpu;
+    float *temp3_cpu;
+
+    float *dh_cpu;
+    float *hh_cpu;
+    float *prev_cell_cpu;
+    float *cell_cpu;
+    float *f_cpu;
+    float *i_cpu;
+    float *g_cpu;
+    float *o_cpu;
+    float *c_cpu;
+    float *dc_cpu;
+
+    float * binary_input;
+
+    struct layer *input_layer;
+    struct layer *self_layer;
+    struct layer *output_layer;
+
+    struct layer *reset_layer;
+    struct layer *update_layer;
+    struct layer *state_layer;
+
+    struct layer *input_gate_layer;
+    struct layer *state_gate_layer;
+    struct layer *input_save_layer;
+    struct layer *state_save_layer;
+    struct layer *input_state_layer;
+    struct layer *state_state_layer;
+
+    struct layer *input_z_layer;
+    struct layer *state_z_layer;
+
+    struct layer *input_r_layer;
+    struct layer *state_r_layer;
+
+    struct layer *input_h_layer;
+    struct layer *state_h_layer;
+
+    struct layer *wz;
+    struct layer *uz;
+    struct layer *wr;
+    struct layer *ur;
+    struct layer *wh;
+    struct layer *uh;
+    struct layer *uo;
+    struct layer *wo;
+    struct layer *uf;
+    struct layer *wf;
+    struct layer *ui;
+    struct layer *wi;
+    struct layer *ug;
+    struct layer *wg;
+
+    tree *softmax_tree;
+
+    size_t workspace_size;
+};
+
+
+typedef enum {
+    CONSTANT, STEP, EXP, POLY, STEPS, SIG, RANDOM
+} LEARNINGRATEPOLICY;
+
+typedef struct network{
+    int n;
+    int batch;
+    size_t *seen;
+    int *t;
+    float epoch;
+    int subdivisions;
+    layer *layers;
+    float *output;
+    LEARNINGRATEPOLICY policy;
+
+    float learning_rate;
+    float momentum;
+    float decay;
+    float gamma;
+    float scale;
+    float power;
+    int time_steps;
+    int step;
+    int max_batches;
+    float *scales;
+    int   *steps;
+    int num_steps;
+    int burn_in;
+
+    int adam;
+    float B1;
+    float B2;
+    float eps;
+
+    int inputs;
+    int outputs;
+    int truths;
+    int notruth;
+    int h, w, c;
+    int max_crop;
+    int min_crop;
+    float max_ratio;
+    float min_ratio;
+    int center;
+    float angle;
+    float aspect;
+    float exposure;
+    float saturation;
+    float hue;
+    int random;
+
+    int gpu_index;
+    tree *hierarchy;
+
+    float *input;
+    float *truth;
+    float *delta;
+    float *workspace;
+    int train;
+    int index;
+    float *cost;
+} network;
+
+
+typedef struct {
+    int w;
+    int h;
+    int c;
+    float *data;
+} image;
+
+network *load_network(char *cfg, char *weights, int clear);
+image letterbox_image(image im, int w, int h);
+int resize_network(network *net, int w, int h);
+void top_predictions(network *net, int n, int *index);
+void free_image(image m);
+image load_image_color(char *filename, int w, int h);
+float *network_predict_image(network *net, image im);
+network *make_network(int n);
+layer make_convolutional_layer(int batch, int h, int w, int c, int n, int groups, int size, int stride, int padding, ACTIVATION activation, int batch_normalize, int binary, int xnor, int adam);
+layer make_connected_layer(int batch, int inputs, int outputs, ACTIVATION activation, int batch_normalize, int adam);
+layer make_maxpool_layer(int batch, int h, int w, int c, int size, int stride, int padding);
+layer make_avgpool_layer(int batch, int w, int h, int c);
+layer make_shortcut_layer(int batch, int index, int w, int h, int c, int w2, int h2, int c2);
+layer make_batchnorm_layer(int batch, int w, int h, int c);
+layer make_reorg_layer(int batch, int w, int h, int c, int stride, int reverse, int flatten, int extra);
+layer make_region_layer(int batch, int w, int h, int n, int classes, int coords);
+void free_network(network *net);
+"""
+                   )
diff --git a/nnvm/python/nnvm/testing/yolo2_detection.py b/nnvm/python/nnvm/testing/yolo2_detection.py
new file mode 100644
index 000000000000..b7744c45cff4
--- /dev/null
+++ b/nnvm/python/nnvm/testing/yolo2_detection.py
@@ -0,0 +1,246 @@
+# pylint: disable=invalid-name, unused-variable, unused-argument, no-init
+"""
+Yolo detection boxes helper functions
+====================
+DarkNet helper functions for yolo and image loading.
+This functions will not be loaded by default.
+These are utility functions used for testing and tutorial file.
+"""
+from __future__ import division
+import math
+from collections import namedtuple
+import numpy as np
+from PIL import Image
+from PIL import ImageDraw
+from PIL import ImageFont
+
+def _entry_index(batch, w, h, outputs, classes, coords, location, entry):
+    n = int(location/(w*h))
+    loc = location%(w*h)
+    return batch*outputs + n*w*h*(coords+classes+1) + entry*w*h + loc
+
+Box = namedtuple('Box', ['x', 'y', 'w', 'h'])
+def _get_region_box(x, biases, n, index, i, j, w, h, stride):
+    b = Box(0, 0, 0, 0)
+    b = b._replace(x=(i + x[index + 0*stride]) / w)
+    b = b._replace(y=(j + x[index + 1*stride]) / h)
+    b = b._replace(w=np.exp(x[index + 2*stride]) * biases[2*n] / w)
+    b = b._replace(h=np.exp(x[index + 3*stride]) * biases[2*n+1] / h)
+    return b
+
+def _correct_region_boxes(boxes, n, w, h, netw, neth, relative):
+    new_w, new_h = (netw, (h*netw)/w) if (netw/w < neth/h) else ((w*neth/h), neth)
+    for i in range(n):
+        b = boxes[i]
+        b = boxes[i]
+        b = b._replace(x=(b.x - (netw - new_w)/2/netw) / (new_w/netw))
+        b = b._replace(y=(b.y - (neth - new_h)/2/neth) / (new_h/neth))
+        b = b._replace(w=b.w * netw/new_w)
+        b = b._replace(h=b.h * neth/new_h)
+        if not relative:
+            b = b._replace(x=b.x * w)
+            b = b._replace(w=b.w * w)
+            b = b._replace(y=b.y * h)
+            b = b._replace(h=b.h * h)
+        boxes[i] = b
+
+def _overlap(x1, w1, x2, w2):
+    l1 = x1 - w1/2
+    l2 = x2 - w2/2
+    left = l1 if l1 > l2 else l2
+    r1 = x1 + w1/2
+    r2 = x2 + w2/2
+    right = r1 if r1 < r2 else r2
+    return right - left
+
+def _box_intersection(a, b):
+    w = _overlap(a.x, a.w, b.x, b.w)
+    h = _overlap(a.y, a.h, b.y, b.h)
+    if w < 0 or h < 0:
+        return 0
+    return w*h
+
+def _box_union(a, b):
+    i = _box_intersection(a, b)
+    u = a.w*a.h + b.w*b.h - i
+    return u
+
+def _box_iou(a, b):
+    return _box_intersection(a, b)/_box_union(a, b)
+
+def get_region_boxes(layer_in, imw, imh, netw, neth, thresh, probs,
+                     boxes, relative, tvm_out):
+    "To get the boxes for the image based on the prediction"
+    lw = layer_in.w
+    lh = layer_in.h
+    probs = [[0 for i in range(layer_in.classes + 1)] for y in range(lw*lh*layer_in.n)]
+    boxes = [Box(0, 0, 0, 0) for i in range(lw*lh*layer_in.n)]
+    for i in range(lw*lh):
+        row = int(i / lw)
+        col = int(i % lw)
+        for n in range(layer_in.n):
+            index = n*lw*lh + i
+            obj_index = _entry_index(0, lw, lh, layer_in.outputs, layer_in.classes,
+                                     layer_in.coords, n*lw*lh + i, layer_in.coords)
+            box_index = _entry_index(0, lw, lh, layer_in.outputs, layer_in.classes,
+                                     layer_in.coords, n*lw*lh + i, 0)
+            mask_index = _entry_index(0, lw, lh, layer_in.outputs, layer_in.classes,
+                                      layer_in.coords, n*lw*lh + i, 4)
+            scale = 1 if layer_in.background  else tvm_out[obj_index]
+            boxes[index] = _get_region_box(tvm_out, layer_in.biases, n, box_index, col,
+                                           row, lw, lh, lw*lh)
+            if not layer_in.softmax_tree:
+                max_element = 0
+                for j in range(layer_in.classes):
+                    class_index = _entry_index(0, lw, lh, layer_in.outputs, layer_in.classes,
+                                               layer_in.coords, n*lw*lh + i, layer_in.coords+1+j)
+                    prob = scale*tvm_out[class_index]
+                    probs[index][j] = prob if prob > thresh else 0
+                    max_element = max(max_element, prob)
+                probs[index][layer_in.classes] = max_element
+
+    _correct_region_boxes(boxes, lw*lh*layer_in.n, imw, imh, netw, neth, relative)
+    return boxes, probs
+
+
+def do_nms_sort(boxes, probs, total, classes, thresh):
+    "Does the sorting based on the threshold values"
+    SortableBbox = namedtuple('SortableBbox', ['index_var', 'class_var', 'probs'])
+
+    s = [SortableBbox(0, 0, []) for i in range(total)]
+    for i in range(total):
+        s[i] = s[i]._replace(index_var=i)
+        s[i] = s[i]._replace(class_var=0)
+        s[i] = s[i]._replace(probs=probs)
+
+    for k in range(classes):
+        for i in range(total):
+            s[i] = s[i]._replace(class_var=k)
+        s = sorted(s, key=lambda x: x.probs[x.index_var][x.class_var], reverse=True)
+        for i in range(total):
+            if probs[s[i].index_var][k] == 0:
+                continue
+            a = boxes[s[i].index_var]
+            for j in range(i+1, total):
+                b = boxes[s[j].index_var]
+                if _box_iou(a, b) > thresh:
+                    probs[s[j].index_var][k] = 0
+    return boxes, probs
+
+def draw_detections(im, num, thresh, boxes, probs, names, classes):
+    "Draw the markings around the detected region"
+    for i in range(num):
+        labelstr = []
+        category = -1
+        for j in range(classes):
+            if probs[i][j] > thresh:
+                if category == -1:
+                    category = j
+                labelstr.append(names[j])
+        if category > -1:
+            imc, imh, imw = im.shape
+            width = int(imh * 0.006)
+            offset = category*123457 % classes
+            red = _get_color(2, offset, classes)
+            green = _get_color(1, offset, classes)
+            blue = _get_color(0, offset, classes)
+            rgb = [red, green, blue]
+            b = boxes[i]
+            left = int((b.x-b.w/2.)*imw)
+            right = int((b.x+b.w/2.)*imw)
+            top = int((b.y-b.h/2.)*imh)
+            bot = int((b.y+b.h/2.)*imh)
+
+            if left < 0:
+                left = 0
+            if right > imw-1:
+                right = imw-1
+            if top < 0:
+                top = 0
+            if bot > imh-1:
+                bot = imh-1
+            _draw_box_width(im, left, top, right, bot, width, red, green, blue)
+            label = _get_label(''.join(labelstr), rgb)
+            _draw_label(im, top + width, left, label, rgb)
+
+def _get_pixel(im, x, y, c):
+    return im[c][y][x]
+
+def _set_pixel(im, x, y, c, val):
+    if x < 0 or y < 0 or c < 0 or x >= im.shape[2] or y >= im.shape[1] or c >= im.shape[0]:
+        return
+    im[c][y][x] = val
+
+def _draw_label(im, r, c, label, rgb):
+    w = label.shape[2]
+    h = label.shape[1]
+    if (r - h) >= 0:
+        r = r - h
+
+    for j in range(h):
+        if j < h and (j + r) < im.shape[1]:
+            for i in range(w):
+                if i < w and (i + c) < im.shape[2]:
+                    for k in range(label.shape[0]):
+                        val = _get_pixel(label, i, j, k)
+                        _set_pixel(im, i+c, j+r, k, val)#rgb[k] * val)
+
+def _get_label(labelstr, rgb):
+    text = labelstr
+    colorText = "black"
+    testDraw = ImageDraw.Draw(Image.new('RGB', (1, 1)))
+    font = ImageFont.truetype("arial.ttf", 25)
+    width, height = testDraw.textsize(labelstr, font=font)
+    img = Image.new('RGB', (width, height), color=(int(rgb[0]*255), int(rgb[1]*255),
+                                                   int(rgb[2]*255)))
+    d = ImageDraw.Draw(img)
+    d.text((0, 0), text, fill=colorText, font=font)
+    opencvImage = np.divide(np.asarray(img), 255)
+    return opencvImage.transpose(2, 0, 1)
+
+def _get_color(c, x, max_value):
+    c = int(c)
+    colors = [[1, 0, 1], [0, 0, 1], [0, 1, 1], [0, 1, 0], [1, 1, 0], [1, 0, 0]]
+    ratio = (float(x)/float(max_value)) * 5
+    i = int(math.floor(ratio))
+    j = int(math.ceil(ratio))
+    ratio -= i
+    r = (1-ratio) * colors[i][c] + ratio*colors[j][c]
+    return r
+
+def _draw_box(im, x1, y1, x2, y2, r, g, b):
+    y1 = int(y1)
+    y2 = int(y2)
+    x1 = int(x1)
+    x2 = int(x2)
+    ac, ah, aw = im.shape
+    if x1 < 0:
+        x1 = 0
+    if x1 >= aw:
+        y1 = 0
+    if y1 >= ah:
+        y1 = ah - 1
+    if y2 < 0:
+        y2 = 0
+    if y2 >= ah:
+        y2 = ah - 1
+
+    for i in range(x1, x2):
+        im[0][y1][i] = r
+        im[0][y2][i] = r
+        im[1][y1][i] = g
+        im[1][y2][i] = g
+        im[2][y1][i] = b
+        im[2][y2][i] = b
+
+    for i in range(y1, y2):
+        im[0][i][x1] = r
+        im[0][i][x2] = r
+        im[1][i][x1] = g
+        im[1][i][x2] = g
+        im[2][i][x1] = b
+        im[2][i][x2] = b
+
+def _draw_box_width(im, x1, y1, x2, y2, w, r, g, b):
+    for i in range(int(w)):
+        _draw_box(im, x1+i, y1+i, x2-i, y2-i, r, g, b)
diff --git a/nnvm/python/nnvm/top/__init__.py b/nnvm/python/nnvm/top/__init__.py
index 273324d1f88c..12294fa0df48 100644
--- a/nnvm/python/nnvm/top/__init__.py
+++ b/nnvm/python/nnvm/top/__init__.py
@@ -7,6 +7,7 @@
 from . import nn
 from . import transform
 from . import reduction
+from . import vision
 
 from .registry import OpPattern
 from .registry import register_compute, register_schedule, register_pattern
diff --git a/nnvm/python/nnvm/top/vision.py b/nnvm/python/nnvm/top/vision.py
new file mode 100644
index 000000000000..89409de6263b
--- /dev/null
+++ b/nnvm/python/nnvm/top/vision.py
@@ -0,0 +1,40 @@
+
+# pylint: disable=invalid-name, unused-argument
+"""Definition of nn ops"""
+from __future__ import absolute_import
+
+import topi
+import tvm
+from . import registry as reg
+from .registry import OpPattern
+
+@reg.register_compute("yolo2_reorg")
+def compute_reorg(attrs, inputs, _):
+    """Compute definition of reorg"""
+    return topi.vision.reorg(inputs[0], attrs.get_int("stride"))
+
+@reg.register_schedule("yolo2_reorg")
+def schedule_reorg(attrs, outs, target):
+    """Schedule definition of reorg"""
+    with tvm.target.create(target):
+        return topi.generic.schedule_injective(outs)
+
+reg.register_pattern("yolo2_reorg", OpPattern.INJECTIVE)
+
+@reg.register_compute("yolo2_region")
+def compute_region(attrs, inputs, _):
+    """Compute definition of region"""
+    n = attrs.get_int("n")
+    classes = attrs.get_int("classes")
+    coords = attrs.get_int("coords")
+    background = attrs.get_int("background")
+    softmax = attrs.get_int("softmax")
+    return topi.vision.yolo2.region(inputs[0], n, classes, coords, background, softmax)
+
+@reg.register_schedule("yolo2_region")
+def schedule_region(attrs, outs, target):
+    """Schedule definition of region"""
+    with tvm.target.create(target):
+        return topi.generic.vision.schedule_region(outs)
+
+reg.register_pattern("yolo2_region", OpPattern.OPAQUE)
diff --git a/nnvm/src/top/vision/yolo2/region.cc b/nnvm/src/top/vision/yolo2/region.cc
new file mode 100644
index 000000000000..87860be3d03a
--- /dev/null
+++ b/nnvm/src/top/vision/yolo2/region.cc
@@ -0,0 +1,35 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file region.cc
+ * \brief Property def of pooling operators.
+ */
+#include <nnvm/op.h>
+#include <nnvm/node.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/top/nn.h>
+#include "../../op_common.h"
+#include "region.h"
+
+namespace nnvm {
+namespace top {
+
+NNVM_REGISTER_OP(yolo2_region)
+.describe(R"code(Region layer
+)code" NNVM_ADD_FILELINE)
+.set_num_inputs(1)
+.set_num_outputs(1)
+.set_support_level(5)
+.add_argument("data", "Tensor", "Input data")
+.set_attr<FInferType>("FInferType", RegionType<1, 1>)
+.set_attr<FInferShape>("FInferShape", RegionShape<1, 1>)
+.set_attr<FInplaceOption>(
+    "FInplaceOption",
+    [](const NodeAttrs &attrs) {
+      return std::vector<std::pair<int, int>>{{0, 0}, {1, 0}};
+    })
+.set_attr<FGradient>("FGradient", [](const NodePtr &n,
+                                     const std::vector<NodeEntry> &ograds) {
+  return std::vector<NodeEntry>{ograds[0], ograds[0]};
+});
+}  // namespace top
+}  // namespace nnvm
diff --git a/nnvm/src/top/vision/yolo2/region.h b/nnvm/src/top/vision/yolo2/region.h
new file mode 100644
index 000000000000..cc816eab6ae1
--- /dev/null
+++ b/nnvm/src/top/vision/yolo2/region.h
@@ -0,0 +1,101 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file region.h
+ */
+#ifndef NNVM_TOP_VISION_YOLO2_REGION_H_
+#define NNVM_TOP_VISION_YOLO2_REGION_H_
+
+#include <string>
+#include <vector>
+#include <utility>
+#include <iostream>
+#include <sstream>
+
+namespace nnvm {
+namespace top {
+
+template <typename AttrType,
+          bool (*is_none)(const AttrType &),
+          bool (*assign)(AttrType *,
+          const AttrType &),
+          bool reverse_infer,
+          std::string (*attr_string)(const AttrType &),
+          int n_in = -1,
+          int n_out = -1>
+inline bool RegionAttr(const nnvm::NodeAttrs &attrs,
+                       std::vector<AttrType> *in_attrs,
+                       std::vector<AttrType> *out_attrs,
+                       const AttrType &none) {
+  AttrType dattr = none;
+  size_t in_size = in_attrs->size();
+  size_t out_size = out_attrs->size();
+  if (n_in != -1) {
+    in_size = static_cast<size_t>(n_in);
+  }
+  if (n_out != -1) {
+    out_size = static_cast<size_t>(n_out);
+  }
+
+  auto deduce = [&](std::vector<AttrType> *vec, size_t size, const char *name) {
+    for (size_t i = 0; i < size; ++i) {
+      if (i == 0)
+        CHECK(assign(&dattr, (*vec)[i]))
+            << "Incompatible attr in node " << attrs.name << " at " << i
+            << "-th " << name << ": "
+            << "expected " << attr_string(dattr) << ", got "
+            << attr_string((*vec)[i]);
+    }
+  };
+  deduce(in_attrs, in_size, "input");
+
+  auto write = [&](std::vector<AttrType> *vec, size_t size, const char *name) {
+    for (size_t i = 0; i < size; ++i) {
+      CHECK(assign(&(*vec)[i], dattr))
+          << "Incompatible attr in node " << attrs.name << " at " << i << "-th "
+          << name << ": "
+          << "expected " << attr_string(dattr) << ", got "
+          << attr_string((*vec)[i]);
+    }
+  };
+  write(out_attrs, out_size, "output");
+
+  if (is_none(dattr)) {
+    return false;
+  }
+  return true;
+}
+
+template <int n_in, int n_out>
+inline bool RegionShape(const NodeAttrs &attrs,
+                        std::vector<TShape> *in_attrs,
+                        std::vector<TShape> *out_attrs) {
+  if (n_in != -1) {
+    CHECK_EQ(in_attrs->size(), static_cast<size_t>(n_in))
+        << " in operator " << attrs.name;
+  }
+  if (n_out != -1) {
+    CHECK_EQ(out_attrs->size(), static_cast<size_t>(n_out))
+        << " in operator " << attrs.name;
+  }
+  return RegionAttr<TShape, shape_is_none, shape_assign, true, shape_string>(
+      attrs, in_attrs, out_attrs, TShape());
+}
+
+template <int n_in, int n_out>
+inline bool RegionType(const NodeAttrs &attrs,
+                       std::vector<int> *in_attrs,
+                       std::vector<int> *out_attrs) {
+  if (n_in != -1) {
+    CHECK_EQ(in_attrs->size(), static_cast<size_t>(n_in))
+        << " in operator " << attrs.name;
+  }
+  if (n_out != -1) {
+    CHECK_EQ(out_attrs->size(), static_cast<size_t>(n_out))
+        << " in operator " << attrs.name;
+  }
+  return RegionAttr<int, type_is_none, type_assign, true, type_string>(
+      attrs, in_attrs, out_attrs, -1);
+}
+}  // namespace top
+}  // namespace nnvm
+#endif  // NNVM_TOP_VISION_YOLO2_REGION_H_
diff --git a/nnvm/src/top/vision/yolo2/reorg.cc b/nnvm/src/top/vision/yolo2/reorg.cc
new file mode 100644
index 000000000000..e58940eb25dd
--- /dev/null
+++ b/nnvm/src/top/vision/yolo2/reorg.cc
@@ -0,0 +1,52 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file reorg.cc
+ */
+#include <nnvm/op.h>
+#include <nnvm/node.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/top/nn.h>
+#include "../../op_common.h"
+#include "../../elemwise_op_common.h"
+#include "reorg.h"
+
+namespace nnvm {
+namespace top {
+
+// reorg
+DMLC_REGISTER_PARAMETER(ReorgParam);
+
+inline bool ReorgInferShape(const nnvm::NodeAttrs &attrs,
+                            std::vector<TShape> *in_shape,
+                            std::vector<TShape> *out_shape) {
+  const ReorgParam &param = nnvm::get<ReorgParam>(attrs.parsed);
+  TShape dshape = in_shape->at(0);
+  if (dshape.ndim() == 0)
+    return false;
+  NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, 0, dshape);
+  CHECK_EQ(dshape.ndim(), 4) << "Input data should be 4D";
+  CHECK_GT(param.stride, 0U) << "Stride value cannot be 0";
+  TShape oshape({dshape[0], 0, 0, 0});
+  oshape[1] = dshape[1] * param.stride * param.stride;
+  oshape[2] = dshape[2] / param.stride;
+  oshape[3] = dshape[3] / param.stride;
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, oshape);
+  return true;
+}
+
+NNVM_REGISTER_OP(yolo2_reorg)
+.describe(R"(Perform reorg operation on input array based on the stride value.
+- **data**: Input is 4D array of shape (batch_size, channels, in_height, in_width).
+- **out**: Output is 4D array of shape (batch_size, channels/(stride*stride), in_height*stride, in_width*stride).
+)" NNVM_ADD_FILELINE)
+.set_num_inputs(1)
+.set_num_outputs(1)
+.set_support_level(5)
+.add_argument("data", "Tensor", "Data input to reorganize")
+.set_attr_parser(ParamParser<ReorgParam>)
+.add_arguments(ReorgParam::__FIELDS__())
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<ReorgParam>)
+.set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
+.set_attr<FInferShape>("FInferShape", ReorgInferShape);
+}  // namespace top
+}  // namespace nnvm
diff --git a/nnvm/src/top/vision/yolo2/reorg.h b/nnvm/src/top/vision/yolo2/reorg.h
new file mode 100644
index 000000000000..87e0510e2781
--- /dev/null
+++ b/nnvm/src/top/vision/yolo2/reorg.h
@@ -0,0 +1,110 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file reorg.h
+ */
+#ifndef NNVM_TOP_VISION_YOLO2_REORG_H_
+#define NNVM_TOP_VISION_YOLO2_REORG_H_
+
+#include <string>
+#include <vector>
+#include <utility>
+#include <iostream>
+#include <sstream>
+
+namespace nnvm {
+namespace top {
+
+template <typename AttrType,
+          bool (*is_none)(const AttrType &),
+          bool (*assign)(AttrType *,
+          const AttrType &),
+          bool reverse_infer,
+          std::string (*attr_string)(const AttrType &),
+          int n_in = -1,
+          int n_out = -1>
+inline bool ReorgAttr(const nnvm::NodeAttrs &attrs,
+                      std::vector<AttrType> *in_attrs,
+                      std::vector<AttrType> *out_attrs,
+                      const AttrType &none) {
+  AttrType dattr = none;
+  size_t in_size = in_attrs->size();
+  size_t out_size = out_attrs->size();
+  if (n_in != -1) {
+    in_size = static_cast<size_t>(n_in);
+  }
+  if (n_out != -1) {
+    out_size = static_cast<size_t>(n_out);
+  }
+
+  auto deduce = [&](std::vector<AttrType> *vec, size_t size, const char *name) {
+    for (size_t i = 0; i < size; ++i) {
+      if (i == 0) {
+        CHECK(assign(&dattr, (*vec)[i]))
+            << "Incompatible attr in node " << attrs.name << " at " << i
+            << "-th " << name << ": "
+            << "expected " << attr_string(dattr) << ", got "
+            << attr_string((*vec)[i]);
+      }
+    }
+  };
+  deduce(in_attrs, in_size, "input");
+
+  auto write = [&](std::vector<AttrType> *vec, size_t size, const char *name) {
+    for (size_t i = 0; i < size; ++i) {
+      CHECK(assign(&(*vec)[i], dattr))
+          << "Incompatible attr in node " << attrs.name << " at " << i << "-th "
+          << name << ": "
+          << "expected " << attr_string(dattr) << ", got "
+          << attr_string((*vec)[i]);
+    }
+  };
+  write(out_attrs, out_size, "output");
+
+  if (is_none(dattr)) {
+    return false;
+  }
+  return true;
+}
+
+template <int n_in, int n_out>
+inline bool ReorgShape(const NodeAttrs &attrs,
+                       std::vector<TShape> *in_attrs,
+                       std::vector<TShape> *out_attrs) {
+  if (n_in != -1) {
+    CHECK_EQ(in_attrs->size(), static_cast<size_t>(n_in))
+        << " in operator " << attrs.name;
+  }
+  if (n_out != -1) {
+    CHECK_EQ(out_attrs->size(), static_cast<size_t>(n_out))
+        << " in operator " << attrs.name;
+  }
+  return ReorgAttr<TShape, shape_is_none, shape_assign, true, shape_string>(
+      attrs, in_attrs, out_attrs, TShape());
+}
+
+template <int n_in, int n_out>
+inline bool ReorgType(const NodeAttrs &attrs,
+                      std::vector<int> *in_attrs,
+                      std::vector<int> *out_attrs) {
+  if (n_in != -1) {
+    CHECK_EQ(in_attrs->size(), static_cast<size_t>(n_in))
+        << " in operator " << attrs.name;
+  }
+  if (n_out != -1) {
+    CHECK_EQ(out_attrs->size(), static_cast<size_t>(n_out))
+        << " in operator " << attrs.name;
+  }
+  return ReorgAttr<int, type_is_none, type_assign, true, type_string>(
+      attrs, in_attrs, out_attrs, -1);
+}
+
+struct ReorgParam : public dmlc::Parameter<ReorgParam> {
+  int stride;
+
+  DMLC_DECLARE_PARAMETER(ReorgParam) {
+    DMLC_DECLARE_FIELD(stride).set_default(1).describe("Stride value");
+  }
+};
+}  // namespace top
+}  // namespace nnvm
+#endif  // NNVM_TOP_VISION_YOLO2_REORG_H_
diff --git a/nnvm/tests/ci_build/Dockerfile.gpu b/nnvm/tests/ci_build/Dockerfile.gpu
index bde32322ceaa..2ee5ed04e91f 100644
--- a/nnvm/tests/ci_build/Dockerfile.gpu
+++ b/nnvm/tests/ci_build/Dockerfile.gpu
@@ -41,6 +41,9 @@ RUN bash /install/ubuntu_install_coreml.sh
 COPY install/ubuntu_install_keras.sh /install/ubuntu_install_keras.sh
 RUN bash /install/ubuntu_install_keras.sh
 
+COPY install/ubuntu_install_darknet.sh /install/ubuntu_install_darknet.sh
+RUN bash /install/ubuntu_install_darknet.sh
+
 RUN pip install Pillow
 
 # Environment variables
diff --git a/nnvm/tests/ci_build/install/ubuntu_install_darknet.sh b/nnvm/tests/ci_build/install/ubuntu_install_darknet.sh
new file mode 100644
index 000000000000..f5e0c2791d80
--- /dev/null
+++ b/nnvm/tests/ci_build/install/ubuntu_install_darknet.sh
@@ -0,0 +1,4 @@
+#install the necessary dependancies, cffi, opencv
+wget 'https://github.com/siju-samuel/darknet/blob/master/lib/libdarknet.so?raw=true' -O libdarknet.so
+pip2 install opencv-python cffi
+pip3 install opencv-python cffi
diff --git a/nnvm/tests/python/frontend/darknet/test_forward.py b/nnvm/tests/python/frontend/darknet/test_forward.py
new file mode 100644
index 000000000000..ad28c49c014d
--- /dev/null
+++ b/nnvm/tests/python/frontend/darknet/test_forward.py
@@ -0,0 +1,257 @@
+"""
+Compile Darknet Models
+=====================
+This article is a test script to test darknet models with NNVM.
+All the required models and libraries will be downloaded from the internet
+by the script.
+"""
+import os
+import requests
+import numpy as np
+from nnvm import frontend
+from nnvm.testing.darknet import __darknetffi__
+import nnvm.compiler
+import tvm
+import sys
+import urllib
+if sys.version_info >= (3,):
+    import urllib.request as urllib2
+else:
+    import urllib2
+
+def _download(url, path, overwrite=False, sizecompare=False):
+    ''' Download from internet'''
+    if os.path.isfile(path) and not overwrite:
+        if sizecompare:
+            file_size = os.path.getsize(path)
+            res_head = requests.head(url)
+            res_get = requests.get(url, stream=True)
+            if 'Content-Length' not in res_head.headers:
+                res_get = urllib2.urlopen(url)
+            urlfile_size = int(res_get.headers['Content-Length'])
+            if urlfile_size != file_size:
+                print("exist file got corrupted, downloading", path, " file freshly")
+                _download(url, path, True, False)
+                return
+        print('File {} exists, skip.'.format(path))
+        return
+    print('Downloading from url {} to {}'.format(url, path))
+    try:
+        urllib.request.urlretrieve(url, path)
+        print('')
+    except:
+        urllib.urlretrieve(url, path)
+
+DARKNET_LIB = 'libdarknet.so'
+DARKNETLIB_URL = 'https://github.com/siju-samuel/darknet/blob/master/lib/' \
+                                    + DARKNET_LIB + '?raw=true'
+_download(DARKNETLIB_URL, DARKNET_LIB)
+LIB = __darknetffi__.dlopen('./' + DARKNET_LIB)
+
+def test_forward(net):
+    '''Test network with given input image on both darknet and tvm'''
+    def get_darknet_output(net, img):
+        return LIB.network_predict_image(net, img)
+
+    def get_tvm_output(net, img):
+        '''Compute TVM output'''
+        dtype = 'float32'
+        batch_size = 1
+        sym, params = frontend.darknet.from_darknet(net, dtype)
+        data = np.empty([batch_size, img.c, img.h, img.w], dtype)
+        i = 0
+        for c in range(img.c):
+            for h in range(img.h):
+                for k in range(img.w):
+                    data[0][c][h][k] = img.data[i]
+                    i = i + 1
+
+        target = 'llvm'
+        shape_dict = {'data': data.shape}
+        #with nnvm.compiler.build_config(opt_level=2):
+        graph, library, params = nnvm.compiler.build(sym, target, shape_dict, dtype, params=params)
+        ######################################################################
+        # Execute on TVM
+        # ---------------
+        # The process is no different from other examples.
+        from tvm.contrib import graph_runtime
+        ctx = tvm.cpu(0)
+        m = graph_runtime.create(graph, library, ctx)
+        # set inputs
+        m.set_input('data', tvm.nd.array(data.astype(dtype)))
+        m.set_input(**params)
+        m.run()
+        # get outputs
+        out_shape = (net.outputs,)
+        tvm_out = m.get_output(0, tvm.nd.empty(out_shape, dtype)).asnumpy()
+        return tvm_out
+
+    test_image = 'dog.jpg'
+    img_url = 'https://github.com/siju-samuel/darknet/blob/master/data/' + test_image   +'?raw=true'
+    _download(img_url, test_image)
+    img = LIB.letterbox_image(LIB.load_image_color(test_image.encode('utf-8'), 0, 0), net.w, net.h)
+    darknet_output = get_darknet_output(net, img)
+    darknet_out = np.zeros(net.outputs, dtype='float32')
+    for i in range(net.outputs):
+        darknet_out[i] = darknet_output[i]
+    tvm_out = get_tvm_output(net, img)
+    np.testing.assert_allclose(darknet_out, tvm_out, rtol=1e-3, atol=1e-3)
+
+def test_forward_extraction():
+    '''test extraction model'''
+    model_name = 'extraction'
+    cfg_name = model_name + '.cfg'
+    weights_name = model_name + '.weights'
+    cfg_url = 'https://github.com/pjreddie/darknet/blob/master/cfg/' + cfg_name + '?raw=true'
+    weights_url = 'http://pjreddie.com/media/files/' + weights_name + '?raw=true'
+    _download(cfg_url, cfg_name)
+    _download(weights_url, weights_name)
+    net = LIB.load_network(cfg_name.encode('utf-8'), weights_name.encode('utf-8'), 0)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_alexnet():
+    '''test alexnet model'''
+    model_name = 'alexnet'
+    cfg_name = model_name + '.cfg'
+    weights_name = model_name + '.weights'
+    cfg_url = 'https://github.com/pjreddie/darknet/blob/master/cfg/' + cfg_name + '?raw=true'
+    weights_url = 'http://pjreddie.com/media/files/' + weights_name + '?raw=true'
+    _download(cfg_url, cfg_name)
+    _download(weights_url, weights_name)
+    net = LIB.load_network(cfg_name.encode('utf-8'), weights_name.encode('utf-8'), 0)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_resnet50():
+    '''test resnet50 model'''
+    model_name = 'resnet50'
+    cfg_name = model_name + '.cfg'
+    weights_name = model_name + '.weights'
+    cfg_url = 'https://github.com/pjreddie/darknet/blob/master/cfg/' + cfg_name + '?raw=true'
+    weights_url = 'http://pjreddie.com/media/files/' + weights_name + '?raw=true'
+    _download(cfg_url, cfg_name)
+    _download(weights_url, weights_name)
+    net = LIB.load_network(cfg_name.encode('utf-8'), weights_name.encode('utf-8'), 0)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_yolo():
+    '''test yolo model'''
+    model_name = 'yolo'
+    cfg_name = model_name + '.cfg'
+    weights_name = model_name + '.weights'
+    cfg_url = 'https://github.com/pjreddie/darknet/blob/master/cfg/' + cfg_name + '?raw=true'
+    weights_url = 'http://pjreddie.com/media/files/' + weights_name + '?raw=true'
+    _download(cfg_url, cfg_name)
+    _download(weights_url, weights_name)
+    net = LIB.load_network(cfg_name.encode('utf-8'), weights_name.encode('utf-8'), 0)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_convolutional():
+    '''test convolutional layer'''
+    net = LIB.make_network(1)
+    layer = LIB.make_convolutional_layer(1, 224, 224, 3, 32, 1, 3, 2, 0, 1, 0, 0, 0, 0)
+    net.layers[0] = layer
+    net.w = net.h = 224
+    LIB.resize_network(net, 224, 224)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_dense():
+    '''test fully connected layer'''
+    net = LIB.make_network(1)
+    layer = LIB.make_connected_layer(1, 75, 20, 1, 0, 0)
+    net.layers[0] = layer
+    net.w = net.h = 5
+    LIB.resize_network(net, 5, 5)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_maxpooling():
+    '''test maxpooling layer'''
+    net = LIB.make_network(1)
+    layer = LIB.make_maxpool_layer(1, 224, 224, 3, 2, 2, 0)
+    net.layers[0] = layer
+    net.w = net.h = 224
+    LIB.resize_network(net, 224, 224)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_avgpooling():
+    '''test avgerage pooling layer'''
+    net = LIB.make_network(1)
+    layer = LIB.make_avgpool_layer(1, 224, 224, 3)
+    net.layers[0] = layer
+    net.w = net.h = 224
+    LIB.resize_network(net, 224, 224)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_batch_norm():
+    '''test batch normalization layer'''
+    net = LIB.make_network(1)
+    layer = LIB.make_convolutional_layer(1, 224, 224, 3, 32, 1, 3, 2, 0, 1, 1, 0, 0, 0)
+    for i in range(32):
+        layer.rolling_mean[i] = np.random.rand(1)
+        layer.rolling_variance[i] = np.random.rand(1)
+    net.layers[0] = layer
+    net.w = net.h = 224
+    LIB.resize_network(net, 224, 224)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_shortcut():
+    '''test shortcut layer'''
+    net = LIB.make_network(3)
+    layer_1 = LIB.make_convolutional_layer(1, 224, 224, 3, 32, 1, 3, 2, 0, 1, 0, 0, 0, 0)
+    layer_2 = LIB.make_convolutional_layer(1, 111, 111, 32, 32, 1, 1, 1, 0, 1, 0, 0, 0, 0)
+    layer_3 = LIB.make_shortcut_layer(1, 0, 111, 111, 32, 111, 111, 32)
+    layer_3.activation = 1
+    net.layers[0] = layer_1
+    net.layers[1] = layer_2
+    net.layers[2] = layer_3
+    net.w = net.h = 224
+    LIB.resize_network(net, 224, 224)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_reorg():
+    '''test reorg layer'''
+    net = LIB.make_network(2)
+    layer_1 = LIB.make_convolutional_layer(1, 222, 222, 3, 32, 1, 3, 2, 0, 1, 0, 0, 0, 0)
+    layer_2 = LIB.make_reorg_layer(1, 110, 110, 32, 2, 0, 0, 0)
+    net.layers[0] = layer_1
+    net.layers[1] = layer_2
+    net.w = net.h = 222
+    LIB.resize_network(net, 222, 222)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_region():
+    '''test region layer'''
+    net = LIB.make_network(2)
+    layer_1 = LIB.make_convolutional_layer(1, 224, 224, 3, 8, 1, 3, 2, 0, 1, 0, 0, 0, 0)
+    layer_2 = LIB.make_region_layer(1, 111, 111, 2, 2, 1)
+    layer_2.softmax = 1
+    net.layers[0] = layer_1
+    net.layers[1] = layer_2
+    net.w = net.h = 224
+    LIB.resize_network(net, 224, 224)
+    test_forward(net)
+    LIB.free_network(net)
+
+if __name__ == '__main__':
+    test_forward_resnet50()
+    test_forward_alexnet()
+    test_forward_extraction()
+    test_forward_yolo()
+    test_forward_convolutional()
+    test_forward_maxpooling()
+    test_forward_avgpooling()
+    test_forward_batch_norm()
+    test_forward_shortcut()
+    test_forward_dense()
+    test_forward_reorg()
+    test_forward_region()
diff --git a/nnvm/tutorials/from_darknet.py b/nnvm/tutorials/from_darknet.py
new file mode 100644
index 000000000000..b10327168b6a
--- /dev/null
+++ b/nnvm/tutorials/from_darknet.py
@@ -0,0 +1,227 @@
+"""
+Tutorial for running Yolo-V2 in Darknet Models
+=====================
+**Author**: `Siju Samuel <https://siju-samuel.github.io/>`_
+
+This article is an introductory tutorial to deploy darknet models with NNVM.
+
+All the required models and libraries will be downloaded from the internet
+
+by the script.
+
+This script runs the YOLO-V2 Model with the bounding boxes
+
+Darknet parsing have dependancy with CFFI and CV2 library
+
+Please install CFFI and CV2 before executing this script
+
+pip install cffi
+
+pip install opencv-python
+"""
+from ctypes import *
+import math
+import random
+import nnvm
+import nnvm.frontend.darknet
+from nnvm.testing.darknet import __darknetffi__
+import matplotlib.pyplot as plt
+import numpy as np
+import tvm
+import os, sys, time, urllib, requests
+if sys.version_info >= (3,):
+    import urllib.request as urllib2
+    import urllib.parse as urlparse
+else:
+    import urllib2
+    import urlparse
+
+######################################################################
+# Set the parameters here.
+# Supported models alexnet, resnet50, resnet152, extraction, yolo
+######################################################################
+model_name = 'yolo'
+test_image = 'dog.jpg'
+target = 'llvm'
+ctx = tvm.cpu(0)
+######################################################################
+
+def dlProgress(count, block_size, total_size):
+    """Show the download progress."""
+    global start_time
+    if count == 0:
+        start_time = time.time()
+        return
+    duration = time.time() - start_time
+    progress_size = int(count * block_size)
+    speed = int(progress_size / (1024 * duration))
+    percent = int(count * block_size * 100 / total_size)
+    sys.stdout.write("\r...%d%%, %d MB, %d KB/s, %d seconds passed" %
+          (percent, progress_size / (1024 * 1024), speed, duration))
+    sys.stdout.flush()
+
+def download(url, path, overwrite=False, sizecompare=False):
+    """Downloads the file from the internet.
+    Set the input options correctly to overwrite or do the size comparison
+
+    Parameters
+    ----------
+    url : str
+        Operator name, such as Convolution, Connected, etc
+    path : str
+        List of input symbols.
+    overwrite : dict
+        Dict of operator attributes
+    sizecompare : dict
+        Dict of operator attributes
+
+    Returns
+    -------
+    out_name : converted out name of operation
+    sym : nnvm.Symbol
+        Converted nnvm Symbol
+    """
+    if os.path.isfile(path) and not overwrite:
+        if (sizecompare):
+            fileSize = os.path.getsize(path)
+            resHead = requests.head(url)
+            resGet = requests.get(url,stream=True)
+            if 'Content-Length' not in resHead.headers :
+                resGet = urllib2.urlopen(url)
+            urlFileSize = int(resGet.headers['Content-Length'])
+            if urlFileSize != fileSize:
+                print ("exist file got corrupted, downloading", path , " file freshly")
+                download(url, path, True, False)
+                return
+        print('File {} exists, skip.'.format(path))
+        return
+    print('Downloading from url {} to {}'.format(url, path))
+    try:
+        urllib.request.urlretrieve(url, path, reporthook=dlProgress)
+        print('')
+    except:
+        urllib.urlretrieve(url, path, reporthook=dlProgress)
+
+######################################################################
+# Prepare cfg and weights file
+# Pretrained model available https://pjreddie.com/darknet/imagenet/
+# --------------------------------------------------------------------
+# Download cfg and weights file first time.
+
+cfg_name = model_name + '.cfg'
+weights_name = model_name + '.weights'
+cfg_url = 'https://github.com/siju-samuel/darknet/blob/master/cfg/' + \
+            cfg_name + '?raw=true'
+weights_url = 'http://pjreddie.com/media/files/' + weights_name + '?raw=true'
+
+download(cfg_url, cfg_name)
+download(weights_url, weights_name)
+
+######################################################################
+# Download and Load darknet library
+# ---------------------------------
+
+darknet_lib = 'libdarknet.so'
+darknetlib_url = 'https://github.com/siju-samuel/darknet/blob/master/lib/' + \
+                        darknet_lib + '?raw=true'
+download(darknetlib_url, darknet_lib)
+
+#if the file doesnt exist, then exit normally.
+if os.path.isfile('./' + darknet_lib) is False:
+    exit(0)
+
+darknet_lib = __darknetffi__.dlopen('./' + darknet_lib)
+cfg = "./" + str(cfg_name)
+weights = "./" + str(weights_name)
+net = darknet_lib.load_network(cfg.encode('utf-8'), weights.encode('utf-8'), 0)
+dtype = 'float32'
+batch_size = 1
+print("Converting darknet to nnvm symbols...")
+sym, params = nnvm.frontend.darknet.from_darknet(net, dtype)
+
+######################################################################
+# Compile the model on NNVM
+# --------------------------------------------------------------------
+# compile the model
+data = np.empty([batch_size, net.c ,net.h, net.w], dtype);
+shape = {'data': data.shape}
+print("Compiling the model...")
+with nnvm.compiler.build_config(opt_level=2):
+    graph, lib, params = nnvm.compiler.build(sym, target, shape, dtype, params)
+
+#####################################################################
+# Save the json
+# --------------------------------------------------------------------
+def save_lib():
+    #Save the graph, params and .so to the current directory
+    print("Saving the compiled output...")
+    path_name = 'nnvm_darknet_' + model_name
+    path_lib = path_name + '_deploy_lib.so'
+    lib.export_library(path_lib)
+    with open(path_name
++ "deploy_graph.json", "w") as fo:
+        fo.write(graph.json())
+    with open(path_name
++ "deploy_param.params", "wb") as fo:
+        fo.write(nnvm.compiler.save_param_dict(params))
+#save_lib()
+
+######################################################################
+# Load a test image
+# --------------------------------------------------------------------
+print("Loading the test image...")
+img_url = 'https://github.com/siju-samuel/darknet/blob/master/data/' + \
+            test_image   +'?raw=true'
+download(img_url, test_image)
+
+data = nnvm.testing.darknet.load_image(test_image, net.w, net.h)
+
+######################################################################
+# Execute on TVM
+# --------------------------------------------------------------------
+# The process is no different from other examples.
+from tvm.contrib import graph_runtime
+
+m = graph_runtime.create(graph, lib, ctx)
+
+# set inputs
+m.set_input('data', tvm.nd.array(data.astype(dtype)))
+m.set_input(**params)
+# execute
+print("Running the test image...")
+
+m.run()
+# get outputs
+out_shape = (net.outputs,)
+tvm_out = m.get_output(0, tvm.nd.empty(out_shape, dtype)).asnumpy()
+
+#do the detection and bring up the bounding boxes
+thresh = 0.24
+hier_thresh = 0.5
+img = nnvm.testing.darknet.load_image_color(test_image)
+_, im_h, im_w = img.shape
+probs= []
+boxes = []
+region_layer = net.layers[net.n - 1]
+boxes, probs = nnvm.testing.yolo2_detection.get_region_boxes(region_layer, im_w, im_h, net.w, net.h,
+                       thresh, probs, boxes, 1, tvm_out)
+
+boxes, probs = nnvm.testing.yolo2_detection.do_nms_sort(boxes, probs,
+                       region_layer.w*region_layer.h*region_layer.n, region_layer.classes, 0.3)
+
+coco_name = 'coco.names'
+coco_url = 'https://github.com/siju-samuel/darknet/blob/master/data/' + coco_name   +'?raw=true'
+font_name = 'arial.ttf'
+font_url = 'https://github.com/siju-samuel/darknet/blob/master/data/' + font_name   +'?raw=true'
+download(coco_url, coco_name)
+download(font_url, font_name)
+
+with open(coco_name) as f:
+    content = f.readlines()
+
+names = [x.strip() for x in content]
+
+nnvm.testing.yolo2_detection.draw_detections(img, region_layer.w*region_layer.h*region_layer.n,
+                 thresh, boxes, probs, names, region_layer.classes)
+plt.imshow(img.transpose(1,2,0))
+plt.show()

From d1b2bcaf2404017b8ff1221e02a69ba404f7206c Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 7 Apr 2018 21:52:17 -0700
Subject: [PATCH 629/949] Update TVM to latest (#432)

* Update TVM to latest

* remove darknet from testing due to cffi
---
 nnvm/python/nnvm/testing/__init__.py | 1 -
 nnvm/tutorials/from_darknet.py       | 1 +
 2 files changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/python/nnvm/testing/__init__.py b/nnvm/python/nnvm/testing/__init__.py
index 19c37f7ac187..56d5a9a48b59 100644
--- a/nnvm/python/nnvm/testing/__init__.py
+++ b/nnvm/python/nnvm/testing/__init__.py
@@ -7,5 +7,4 @@
 from . import mlp
 from . import resnet
 from . import vgg
-from . import darknet
 from . import yolo2_detection
diff --git a/nnvm/tutorials/from_darknet.py b/nnvm/tutorials/from_darknet.py
index b10327168b6a..7a233f56b718 100644
--- a/nnvm/tutorials/from_darknet.py
+++ b/nnvm/tutorials/from_darknet.py
@@ -24,6 +24,7 @@
 import random
 import nnvm
 import nnvm.frontend.darknet
+import nnvm.testing.darknet
 from nnvm.testing.darknet import __darknetffi__
 import matplotlib.pyplot as plt
 import numpy as np

From a7a6096325e2b7d54b26056bf9ea748285c5c732 Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Tue, 10 Apr 2018 10:17:56 +0530
Subject: [PATCH 630/949] Issue fix for tiny yolo (#436)

* Issue fix for tiny yolo

There was an issue in maxpool frontend for tiny-yolo. It is fixed in PR.
https://github.com/dmlc/nnvm/issues/431

* updated review comment, python 3 compatabilty

* Update darknet.py

* Updated review comment
---
 nnvm/python/nnvm/frontend/darknet.py | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/darknet.py b/nnvm/python/nnvm/frontend/darknet.py
index 413b07d648a4..1c5fdfa87406 100644
--- a/nnvm/python/nnvm/frontend/darknet.py
+++ b/nnvm/python/nnvm/frontend/darknet.py
@@ -100,7 +100,10 @@ def _darknet_maxpooling(inputs, attrs):
     new_attrs['pool_size'] = [kernel[0], kernel[0]]
     new_attrs['strides'] = str((strides, strides))
     new_attrs['padding'] = str((pads, pads))
-
+    extra_pad_size = attrs.get('extra_pad_size', 0)
+    if extra_pad_size:
+        pad_width = ((0, 0), (0, 0), (0, extra_pad_size), (0, extra_pad_size))
+        inputs = _sym.pad(*inputs, pad_width=pad_width, pad_value=np.finfo(np.float32).min)
     return _darknet_get_nnvm_op(op_name)(*inputs, **new_attrs), None
 
 def _darknet_avgpooling(inputs, attrs):
@@ -502,7 +505,10 @@ def _get_darknet_attrs(net, layer_num):
         attr.update({'pad' : str(layer.pad)})
         attr.update({'stride' : str(layer.stride)})
         attr.update({'kernel' : str(layer.size)})
-
+        max_output = (layer.w - layer.size + 2 * layer.pad)/float(layer.stride) + 1
+        if max_output < layer.out_w:
+            extra_pad = (layer.out_w - max_output)*layer.stride
+            attr.update({'extra_pad_size' : int(extra_pad)})
     elif LAYERTYPE.AVGPOOL == layer.type:
         attr.update({'pad' : str(layer.pad)})
         if layer.stride == 0:

From 15beb41b3155424b9326492296a6a13fe02616c9 Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Sat, 21 Apr 2018 09:27:04 +0530
Subject: [PATCH 631/949] [OP] PReLU Support (#394)

---
 nnvm/include/nnvm/top/nn.h                    |  7 +++
 nnvm/python/nnvm/top/nn.py                    |  3 ++
 nnvm/src/top/nn/nn.cc                         | 47 +++++++++++++++++++
 nnvm/tests/python/compiler/test_top_level1.py | 39 +++++++++++++++
 .../tests/python/unittest/test_infer_shape.py | 12 +++++
 nnvm/tests/python/unittest/test_top_level3.py |  7 +++
 6 files changed, 115 insertions(+)

diff --git a/nnvm/include/nnvm/top/nn.h b/nnvm/include/nnvm/top/nn.h
index a8d443241529..7eb2e5e11e4a 100644
--- a/nnvm/include/nnvm/top/nn.h
+++ b/nnvm/include/nnvm/top/nn.h
@@ -101,6 +101,13 @@ struct LeakyReLUParam : public dmlc::Parameter<LeakyReLUParam> {
   }
 };
 
+struct PReLUParam : public dmlc::Parameter<PReLUParam> {
+  int axis;
+  DMLC_DECLARE_PARAMETER(PReLUParam) {
+    DMLC_DECLARE_FIELD(axis).set_default(1)
+      .describe("Specify which shape axis the channel is specified.");
+  }
+};
 
 struct PadParam : public dmlc::Parameter<PadParam> {
   float pad_value;
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index f6dd6f562d60..d7e6e5a08f85 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -18,6 +18,9 @@
 reg.register_schedule("leaky_relu", _fschedule_broadcast)
 reg.register_pattern("leaky_relu", OpPattern.ELEMWISE)
 
+# prelu
+reg.register_schedule("prelu", _fschedule_broadcast)
+reg.register_pattern("prelu", OpPattern.BROADCAST)
 
 # flatten
 reg.register_schedule("flatten", _fschedule_broadcast)
diff --git a/nnvm/src/top/nn/nn.cc b/nnvm/src/top/nn/nn.cc
index 5ac4b7662ff5..e3755d9529b2 100644
--- a/nnvm/src/top/nn/nn.cc
+++ b/nnvm/src/top/nn/nn.cc
@@ -417,6 +417,53 @@ NNVM_REGISTER_OP(leaky_relu)
 })
 .set_support_level(1);
 
+// prelu
+DMLC_REGISTER_PARAMETER(PReLUParam);
+
+inline bool PReluInferShape(const nnvm::NodeAttrs &attrs,
+                            std::vector<TShape> *in_shape,
+                            std::vector<TShape> *out_shape) {
+  const PReLUParam &param = nnvm::get<PReLUParam>(attrs.parsed);
+  TShape dshape = in_shape->at(0);
+  NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, 0, dshape);
+
+  // The case of parametric relu
+  CHECK_EQ(dshape.ndim(), 4) << "Input data should be 4D, but got " << dshape.ndim();
+  CHECK(size_t(param.axis) < dshape.Size())
+      << "Wrong axis ("  << param.axis << ")value.";
+
+  NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, 1, TShape({dshape[param.axis]}));
+
+  TShape oshape(dshape);
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, oshape);
+  return true;
+}
+
+NNVM_REGISTER_OP(prelu)
+.describe(R"code(Parametric version of a Rectified Linear Unit.
+It accepts two arguments: an input ``x`` and a channelwise slope ``alpha``
+and computes the output as :math:`PReLU(x) y = x > 0 ? x : alpha * x`,
+where :math:`*` is an channelwise multiplication for each sample in the
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "Tensor", "Input data.")
+.add_argument("alpha", "Tensor", "Input channelwise alpha.")
+.add_arguments(PReLUParam::__FIELDS__())
+.set_attr_parser(ParamParser<PReLUParam>)
+.set_num_inputs(2)
+.set_num_outputs(1)
+.set_attr<FInferShape>("FInferShape", PReluInferShape)
+.set_attr<FListInputNames>("FListInputNames", [](const NodeAttrs& attrs) {
+    return std::vector<std::string>{"data", "alpha"};
+  })
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const PReLUParam& param = nnvm::get<PReLUParam>(attrs.parsed);
+    return Array<Tensor>{ topi::prelu<float>(inputs[0], inputs[1], param.axis)};
+  })
+.set_support_level(4);
 
 DMLC_REGISTER_PARAMETER(PadParam);
 
diff --git a/nnvm/tests/python/compiler/test_top_level1.py b/nnvm/tests/python/compiler/test_top_level1.py
index 480aa271af8f..ebf8c6ce6ae2 100644
--- a/nnvm/tests/python/compiler/test_top_level1.py
+++ b/nnvm/tests/python/compiler/test_top_level1.py
@@ -64,6 +64,43 @@ def backward(head_grads, x):
     inputs = [('x', dshape, x)]
     helper(y, inputs, dtype, forward, backward)
 
+def test_prelu_nchw():
+    x = sym.Variable("x")
+    a = sym.Variable("a")
+    y = sym.prelu(data=x, alpha=a)
+
+    def forward(x, a):
+        return (x < 0) * (x * a.reshape(3, 1, 1)) + (x>=0) * x
+
+    dtype = "float32"
+    dshape_x = (1, 3, 32, 32)
+    dshape_w = (3,)
+
+    inputs = [
+        ('x', dshape_x, x),
+        ('a', dshape_w, a)
+    ]
+    helper(y, inputs, dtype, forward)
+
+def test_prelu_nhwc():
+    x = sym.Variable("x")
+    a = sym.Variable("a")
+    y = sym.prelu(data=x, alpha=a, axis=3)
+
+    def forward(x, a):
+        return (x < 0) * (x * a.reshape(1, 1, 3)) + (x>=0) * x
+
+    dtype = "float32"
+    dshape_x = (1, 32, 32, 3)
+    dshape_w = (3,)
+
+    inputs = [
+        ('x', dshape_x, x),
+        ('a', dshape_w, a)
+    ]
+
+
+    helper(y, inputs, dtype, forward)
 
 def test_sym_scalar_pow():
     scalar = 3
@@ -336,6 +373,8 @@ def forward(x):
     test_batchnorm()
     test_dense()
     test_relu()
+    test_prelu_nchw()
+    test_prelu_nhwc()
     test_sym_scalar_pow()
     test_scalar_sym_pow()
     test_exp()
diff --git a/nnvm/tests/python/unittest/test_infer_shape.py b/nnvm/tests/python/unittest/test_infer_shape.py
index 226dedd3d7a1..8011e96f3aa6 100644
--- a/nnvm/tests/python/unittest/test_infer_shape.py
+++ b/nnvm/tests/python/unittest/test_infer_shape.py
@@ -250,6 +250,17 @@ def check(in_shape, tshape, out_shape):
     check((2, 3, 4), (2, -4, -1, 3, -2), (2, 1, 3, 4))
 
 
+def test_prelu():
+    def check(in_shape, axis,  out_shape):
+        x = sym.Variable("x", shape=in_shape)
+        w = sym.Variable("w")
+        y = sym.prelu(x, w, axis=axis, name="y")
+        sdict = infer_shape(y)
+        assert(tuple(sdict["y"][0]) == tuple(out_shape))
+    check((1, 3, 2, 2), 1, (1, 3, 2, 2))
+    check((1, 2, 2, 3), 3, (1, 2, 2, 3))
+
+
 # Level 4
 def test_transpose():
     def check(in_shape, out_shape, **kwargs):
@@ -319,3 +330,4 @@ def check(in_shape, out_shape, **kwargs):
     test_broadcast_binary()
     test_reduce()
     test_transpose()
+    test_prelu()
diff --git a/nnvm/tests/python/unittest/test_top_level3.py b/nnvm/tests/python/unittest/test_top_level3.py
index e37863b2b8ac..47e7a8bce100 100644
--- a/nnvm/tests/python/unittest/test_top_level3.py
+++ b/nnvm/tests/python/unittest/test_top_level3.py
@@ -16,8 +16,15 @@ def test_leaky_relu():
     y = sym.leaky_relu(x, alpha=0.1)
     assert(y.list_input_names() == ["x"])
 
+def test_prelu():
+    x = sym.Variable("x")
+    w = sym.Variable("w")
+    y = sym.prelu(x, w)
+    assert(y.list_input_names()[0] == 'x')
+    assert(y.list_input_names()[1] == 'w')
 
 if __name__ == "__main__":
     test_scalar_op()
     test_reshape()
     test_leaky_relu()
+    test_prelu()

From 86f41e72d612a4180ffe5ccb6205eea005379cbc Mon Sep 17 00:00:00 2001
From: Yizhi Liu <liuyizhi@apache.org>
Date: Mon, 23 Apr 2018 13:17:45 -0700
Subject: [PATCH 632/949] fix mxnet model import (#449)

---
 nnvm/python/nnvm/frontend/mxnet.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/python/nnvm/frontend/mxnet.py b/nnvm/python/nnvm/frontend/mxnet.py
index a96c9948189d..4296705f063f 100644
--- a/nnvm/python/nnvm/frontend/mxnet.py
+++ b/nnvm/python/nnvm/frontend/mxnet.py
@@ -312,7 +312,7 @@ def _from_mxnet_impl(symbol, graph):
     attr = symbol.list_attr()
     # op_name = symbol.attr('op_name')
     childs = symbol.get_children()
-    if childs:
+    if childs is not None:
         op_name = symbol.attr('op_name')
         childs = [_from_mxnet_impl(childs[i], graph) for i in range(len(childs.list_outputs()))]
         childs = [x for y in childs for x in _as_list(y)]  # expand group symbol

From 97855ae5aaf055fdca01d244561d6e966bee819f Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Tue, 24 Apr 2018 01:48:19 +0530
Subject: [PATCH 633/949] Support imagescalar operator for onnx (#448)

---
 nnvm/python/nnvm/frontend/onnx.py | 17 +++++++++++++++--
 1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/onnx.py b/nnvm/python/nnvm/frontend/onnx.py
index 0ab6f4a331ed..fd4f0bb47e12 100644
--- a/nnvm/python/nnvm/frontend/onnx.py
+++ b/nnvm/python/nnvm/frontend/onnx.py
@@ -1,11 +1,12 @@
 # pylint: disable=import-self, invalid-name, unused-argument
 """ONNX: Open Neural Network Exchange frontend."""
 from __future__ import absolute_import as _abs
+import numpy as np
 import tvm
 from .. import symbol as _sym
 from .. import graph as _graph
 from ..compiler import graph_util
-from .common import get_nnvm_op, Renamer, AttrConverter as AttrCvt
+from .common import get_nnvm_op, Renamer, SymbolTable, AttrConverter as AttrCvt
 
 __all__ = ['from_onnx']
 
@@ -322,6 +323,18 @@ def _impl_v1(cls, inputs, attr, params):
         return _sym.relu(inputs[0] - alpha)
 
 
+class ImageScaler(OnnxOpConverter):
+
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
+        channelScale = attr['scale']
+        bias_attr = attr['bias']
+        bias = SymbolTable().new_const(np.array(bias_attr).reshape([3, 1, 1]))
+        scaledChannel = _sym.__mul_scalar__(inputs[0], scalar=channelScale)
+        ret = _sym.broadcast_add(scaledChannel, bias)
+        return ret
+
+
 def _revert_caffe2_pad(attr):
     """Caffe2 require two times the normal padding."""
     if len(attr) == 4:
@@ -410,7 +423,7 @@ def _get_convert_map(opset):
         'Scale': Scale.get_converter(opset),
         # 'GRUUnit'
         # 'ATen'
-        # 'ImageScaler'
+        'ImageScaler': ImageScaler.get_converter(opset),
         # 'MeanVarianceNormalization'
         # 'Crop'
         # 'Embedding'

From 013ca9222eec724427852278799eff86690995ed Mon Sep 17 00:00:00 2001
From: MORITA Kazutaka <morita.kazutaka@gmail.com>
Date: Thu, 26 Apr 2018 01:02:17 +0900
Subject: [PATCH 634/949] Fix license URL (#451)

---
 nnvm/README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/README.md b/nnvm/README.md
index 0072821e4b97..b733f4f852eb 100644
--- a/nnvm/README.md
+++ b/nnvm/README.md
@@ -55,7 +55,7 @@ rmodule.run()
 
 License
 -------
-Licensed under an [Apache-2.0](https://github.com/dmlc/tvm/blob/master/LICENSE) license.
+Licensed under an [Apache-2.0](https://github.com/dmlc/nnvm/blob/master/LICENSE) license.
 
 
 Links

From 46d7f97f2f6e83a1c80679b727b6bb25330f6a09 Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Wed, 25 Apr 2018 21:32:44 +0530
Subject: [PATCH 635/949] Some minor documentation issues rectified (#450)

---
 nnvm/tutorials/from_coreml.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/nnvm/tutorials/from_coreml.py b/nnvm/tutorials/from_coreml.py
index a8f551265cd5..3cf8babe6418 100644
--- a/nnvm/tutorials/from_coreml.py
+++ b/nnvm/tutorials/from_coreml.py
@@ -11,7 +11,7 @@
 ```bash
 pip install -U coremltools --user
 ```
-or please refer to offical site
+or please refer to official site
 https://github.com/apple/coremltools
 """
 import nnvm
@@ -37,7 +37,7 @@ def download(url, path, overwrite=False):
 # Load pretrained CoreML model
 # ----------------------------
 # We will download and load a pretrained mobilenet classification network
-# privided by apple in this example
+# provided by apple in this example
 model_url = 'https://docs-assets.developer.apple.com/coreml/models/MobileNet.mlmodel'
 model_file = 'mobilenet.mlmodel'
 download(model_url, model_file)

From 8f9c593c696964e1c0d29ab25f7fc1689d0a4051 Mon Sep 17 00:00:00 2001
From: Yizhi Liu <liuyizhi@apache.org>
Date: Wed, 25 Apr 2018 09:07:31 -0700
Subject: [PATCH 636/949] General Layout Support (#447)

---
 nnvm/include/nnvm/compiler/contrib_op_param.h |  28 --
 nnvm/include/nnvm/compiler/op_attr_types.h    |  21 +-
 nnvm/include/nnvm/compiler/packed_func_ext.h  |   2 +
 nnvm/include/nnvm/graph_attr_types.h          |  18 +-
 nnvm/include/nnvm/layout.h                    | 455 ++++++++++++++++++
 nnvm/include/nnvm/op_attr_types.h             |  26 +
 nnvm/include/nnvm/top/nn.h                    |  80 +--
 nnvm/python/nnvm/_ctypes/symbol.py            |   5 +-
 nnvm/python/nnvm/compiler/build_module.py     |  36 +-
 nnvm/python/nnvm/compiler/graph_attr.py       |  19 +-
 nnvm/python/nnvm/contrib.py                   |   1 +
 nnvm/python/nnvm/frontend/mxnet.py            |  12 +-
 nnvm/python/nnvm/symbol.py                    |   1 +
 nnvm/python/nnvm/top/nn.py                    |  43 +-
 nnvm/python/nnvm/top/registry.py              |  27 ++
 nnvm/src/c_api/c_api_symbolic.cc              |   2 +-
 nnvm/src/compiler/alter_op_layout.cc          | 151 ++++++
 nnvm/src/compiler/fold_scale_axis.cc          |   8 +-
 nnvm/src/compiler/layout_transform.cc         | 159 ------
 nnvm/src/compiler/packed_func_ext.cc          |  20 +-
 nnvm/src/compiler/simplify_inference.cc       |  16 +-
 nnvm/src/pass/correct_layout.cc               | 169 +++++++
 nnvm/src/pass/infer_shape_type.cc             |   2 +-
 nnvm/src/top/elemwise_op_common.h             | 173 +++++++
 nnvm/src/top/nn/convolution.cc                | 143 +++++-
 nnvm/src/top/nn/nn.cc                         | 200 +++++++-
 nnvm/src/top/nn/nn_common.h                   | 130 ++---
 nnvm/src/top/nn/pooling.cc                    | 241 +++++++---
 nnvm/src/top/nn/upsampling.cc                 |  15 +
 nnvm/src/top/op_common.h                      |  15 +
 nnvm/src/top/tensor/broadcast.cc              |  77 ++-
 nnvm/src/top/tensor/elemwise.cc               |   4 +
 nnvm/src/top/tensor/matrix_op.cc              |  26 +
 nnvm/src/top/tensor/reduce.cc                 |   2 +
 nnvm/src/top/tensor/state_op.cc               |   9 +
 nnvm/src/top/tensor/transform.cc              |  69 ++-
 .../python/compiler/test_alter_op_layout.py   |  49 ++
 .../tests/python/compiler/test_nhwc_layout.py |   9 +-
 .../python/unittest/test_correct_layout.py    | 338 +++++++++++++
 39 files changed, 2362 insertions(+), 439 deletions(-)
 delete mode 100644 nnvm/include/nnvm/compiler/contrib_op_param.h
 create mode 100644 nnvm/include/nnvm/layout.h
 create mode 100644 nnvm/python/nnvm/contrib.py
 create mode 100644 nnvm/src/compiler/alter_op_layout.cc
 delete mode 100644 nnvm/src/compiler/layout_transform.cc
 create mode 100644 nnvm/src/pass/correct_layout.cc
 create mode 100644 nnvm/tests/python/compiler/test_alter_op_layout.py
 create mode 100644 nnvm/tests/python/unittest/test_correct_layout.py

diff --git a/nnvm/include/nnvm/compiler/contrib_op_param.h b/nnvm/include/nnvm/compiler/contrib_op_param.h
deleted file mode 100644
index 4eb91f56cafe..000000000000
--- a/nnvm/include/nnvm/compiler/contrib_op_param.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/*!
- *  Copyright (c) 2017 by Contributors
- * \file contrib_op_param.h
- * \brief Additional parameters for compiler optimized operators.
- */
-#ifndef NNVM_COMPILER_CONTRIB_OP_PARAM_H_
-#define NNVM_COMPILER_CONTRIB_OP_PARAM_H_
-
-#include <dmlc/parameter.h>
-#include <string>
-
-namespace nnvm {
-namespace compiler {
-
-/*! \brief Parameters of layout transform operator */
-struct LayoutTransformParam : public dmlc::Parameter<LayoutTransformParam> {
-  std::string src_layout;
-  std::string dst_layout;
-
-  DMLC_DECLARE_PARAMETER(LayoutTransformParam) {
-    DMLC_DECLARE_FIELD(src_layout);
-    DMLC_DECLARE_FIELD(dst_layout);
-  }
-};
-}  // namespace compiler
-}  // namespace nnvm
-
-#endif  // NNVM_COMPILER_CONTRIB_OP_PARAM_H_
diff --git a/nnvm/include/nnvm/compiler/op_attr_types.h b/nnvm/include/nnvm/compiler/op_attr_types.h
index f8d32320ac00..231e85093433 100644
--- a/nnvm/include/nnvm/compiler/op_attr_types.h
+++ b/nnvm/include/nnvm/compiler/op_attr_types.h
@@ -16,6 +16,7 @@
 #include <nnvm/graph.h>
 #include <vector>
 #include <string>
+#include "packed_func_ext.h"
 
 namespace nnvm {
 namespace compiler {
@@ -73,19 +74,17 @@ using FTVMSchedule = std::function<
            const Array<Tensor>& outs,
            const std::string& target)>;
 
-/*! \brief Layout Information about an entry */
-using TLayoutInfo = std::string;
-
 /*!
- * \brief The producer consumer function of node layout
- * \param attrs The attribute of the node.
- * \param ilayouts The input layouts that the node request.
- * \param olayouts The output layouts that the node produce.
- * \return bool The success flag.
+ * \brief Modify the op node to alter its input layout.
+ *  it is invoked in AlterOpLayout pass.
+ * \param attrs The attribute of the original node.
+ * \param inputs The input symbols of the original node.
+ * \param tinfos The inferred shape and dtype of the inputs.
  */
-using FTVMLayoutRequest = std::function<bool (const NodeAttrs& attrs,
-                                              std::vector<TLayoutInfo> *ilayouts,
-                                              std::vector<TLayoutInfo> *olayouts)>;
+using FTVMAlterOpLayout = std::function<
+  Symbol(const NodeAttrs& attrs,
+         const Symbol& inputs,
+         const Array<Tensor>& tinfos)>;
 
 /*!
  * \brief Transform from normal operator to vectorized operator
diff --git a/nnvm/include/nnvm/compiler/packed_func_ext.h b/nnvm/include/nnvm/compiler/packed_func_ext.h
index 241febbe9c69..bd768ff90a85 100644
--- a/nnvm/include/nnvm/compiler/packed_func_ext.h
+++ b/nnvm/include/nnvm/compiler/packed_func_ext.h
@@ -11,6 +11,7 @@
 #include <nnvm/graph.h>
 #include <nnvm/symbolic.h>
 #include <string>
+#include <vector>
 #include <unordered_map>
 
 namespace nnvm {
@@ -52,6 +53,7 @@ template<>
 struct extension_class_info<nnvm::compiler::AttrDict> {
   static const int code = 18;
 };
+
 }  // namespace runtime
 }  // namespace tvm
 #endif  // NNVM_COMPILER_PACKED_FUNC_EXT_H_
diff --git a/nnvm/include/nnvm/graph_attr_types.h b/nnvm/include/nnvm/graph_attr_types.h
index 64894ec58de7..c8a9e7aad6b0 100644
--- a/nnvm/include/nnvm/graph_attr_types.h
+++ b/nnvm/include/nnvm/graph_attr_types.h
@@ -9,6 +9,7 @@
 #include <vector>
 #include <string>
 #include "./tuple.h"
+#include "./layout.h"
 
 namespace nnvm {
 
@@ -46,7 +47,7 @@ using ShapeVector = std::vector<TShape>;
  * \code
  *  Graph g = ApplyPass(src_graph, "InferType");
  *  const DTypeVector& types = g.GetAttr<DTypeVector>("dtype");
- *  // get shape by entry id
+ *  // get type by entry id
  *  int entry_type = dtypes[g.indexed_graph().entry_id(my_entry)];
  * \endcode
  *
@@ -54,6 +55,21 @@ using ShapeVector = std::vector<TShape>;
  */
 using DTypeVector = std::vector<int>;
 
+/*!
+ * \brief The result holder of layout of each NodeEntry in the graph.
+ * \note Stored under graph.attrs["layout"], provided by Pass "InferType"
+ *
+ * \code
+ *  Graph g = ApplyPass(src_graph, "LayoutTransform");
+ *  const LayoutVector& layouts = g.GetAttr<LayoutVector>("layout");
+ *  // get layout by entry id
+ *  int entry_layout = layouts[g.indexed_graph().entry_id(my_entry)];
+ * \endcode
+ *
+ * \sa FInferLayout
+ */
+using LayoutVector = std::vector<Layout>;
+
 /*!
  * \brief The result holder of device of each operator in the graph.
  * \note Stored under graph.attrs["device"], provided by Pass "PlaceDevice"
diff --git a/nnvm/include/nnvm/layout.h b/nnvm/include/nnvm/layout.h
new file mode 100644
index 000000000000..494db60c4779
--- /dev/null
+++ b/nnvm/include/nnvm/layout.h
@@ -0,0 +1,455 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file layout.h
+ * \brief Layout expression.
+ *        The layout is composed of upper cases, lower cases and numbers,
+ *        where upper case indicates a (super-)dimension and
+ *        the corresponding lower case with factor size indicates the split (sub-)dimension.
+ *        For example, NCHW16c can describe a 5-D tensor of
+ *        [batch_size, channel, height, width, channel_block].
+ *        Here sub-dimension channel_block=16 is the split of super-dimension C (channel).
+ */
+#ifndef NNVM_LAYOUT_H_
+#define NNVM_LAYOUT_H_
+
+#include <dmlc/parameter.h>
+#include <string>
+#include <sstream>
+#include <vector>
+#include <utility>
+#include <algorithm>
+
+namespace nnvm {
+
+class Layout {
+ public:
+  using LayoutDim = char;
+
+  /*! \brief default constructor */
+  Layout() : name_("__undef__") {} // NOLINT(*)
+
+  /*!
+   * \brief construct from a string.
+   * \param layout input in layout convention:
+   *        upper case indicates a dimension and
+   *        the corresponding lower case with factor size
+   *        indicates the split dimension.
+   *        return undefined layout if "__undef__" is passed.
+   */
+  inline Layout(const std::string& layout) { // NOLINT(*)
+    parse(layout);
+  }
+  /*!
+   * \brief copy constructor from another layout
+   * \param s the source layout
+   */
+  inline Layout(const Layout& s) { // NOLINT(*)
+    this->parse(s.name_);
+  }
+  /*!
+   * \brief move constructor from Layout
+   * \param src the source layout
+   */
+  inline Layout(Layout&& src) { // NOLINT(*)
+    this->swap(src);
+  }
+  /*!
+   * \brief assignment from another layout.
+   * \param src source layout
+   * \return reference of self
+   */
+  inline Layout& operator=(const Layout& src) {
+    this->parse(src.name_);
+    return *this;
+  }
+  /*!
+   * \brief assignment from rvalue of another layout.
+   * \param src source layout
+   * \return reference of self
+   */
+  inline Layout& operator=(Layout&& src) {
+    Layout(std::move(src)).swap(*this); // NOLINT(*)
+    return *this;
+  }
+  /*!
+   * \brief assignment from string.
+   * \param src source layout
+   * \return reference of self
+   */
+  inline Layout& operator=(const std::string& src) {
+    this->parse(src);
+    return *this;
+  }
+  /*!
+   * \return whether two layout equals
+   * \param s the layout to compare against
+   */
+  inline bool operator==(const Layout& s) const {
+    return name_ == s.name_;
+  }
+  /*!
+   * \return whether two layout not equal
+   * \param s the layout to compare against
+   */
+  inline bool operator!=(const Layout& s) const {
+    return !(*this == s);
+  }
+
+  /*!
+   * \brief Append the current layout by another.
+   * @param other the layout to be appended
+   * @return a new layout
+   */
+  inline Layout operator+(const Layout& other) const {
+    if (!this->defined() && !other.defined()) {
+      return Layout::Undef();
+    } else if (!this->defined()) {
+      return other;
+    } else if (!other.defined()) {
+      return *this;
+    }
+    return Layout(this->name_ + other.name_);
+  }
+
+  /*!
+   * \brief Check whether a given dimension is a super-dimension.
+   * \param dim input dimension
+   * \return Whether a given dimension is a super-dimension.
+   */
+  static inline bool is_superdim(LayoutDim dim) {
+    return dim >= 'A' && dim <= 'Z';
+  }
+
+  /*!
+   * \brief Check whether a given dimension is a sub-dimension.
+   * \param dim input dimension
+   * \return Whether a given dimension is a sub-dimension.
+   */
+  static inline bool is_subdim(LayoutDim dim) {
+    return dim >= 'a' && dim <= 'z';
+  }
+
+  /*!
+   * \brief Convert a given dimension to super-dimension.
+   * \param dim input dimension
+   * \return The converted description.
+   */
+  static inline LayoutDim to_superdim(LayoutDim dim) {
+    if (is_subdim(dim)) {
+      return dim - 'a' + 'A';
+    }
+    return dim;
+  }
+
+  /*!
+   * \brief Convert a given dimension to sub-dimension.
+   * \param dim input dimension
+   * \return The converted description.
+   */
+  static inline LayoutDim to_subdim(LayoutDim dim) {
+    if (is_superdim(dim)) {
+      return dim - 'A' + 'a';
+    }
+    return dim;
+  }
+
+  /*!
+   * \brief Return an undefined layout.
+   * \return a (global) undefined layout.
+   */
+  static inline const Layout& Undef() {
+    static Layout undef;
+    return undef;
+  }
+
+  /*!
+   * \brief Swap current object with other
+   * \param other another object to be swapped.
+   */
+  inline void swap(Layout& other) {  // NOLINT(*)
+    std::swap(name_, other.name_);
+    std::swap(superdim_pos_, other.superdim_pos_);
+    std::swap(subdim_pos_, other.subdim_pos_);
+    std::swap(subdim_size_, other.subdim_size_);
+    std::swap(layout_simplified_, other.layout_simplified_);
+  }
+
+  /*!
+   * \brief Two layouts are convertible only if
+   *        they have same set of super-dimensions.
+   *        e.g., NCHW, NCHW16c, NHWC are convertible between each other,
+   *        but NCHW, CHW, OIHW are not.
+   * \param dst the target layout
+   * \return Whether can be converted to dst layout.
+   */
+  inline bool convertible(const Layout &dst) const {
+    if (!this->defined() || !dst.defined()) return false;
+    for (size_t i = 0; i < kUniqueDim; ++i) {
+      if ((superdim_pos_[i] >= 0 && dst.superdim_pos_[i] < 0) ||
+          (superdim_pos_[i] < 0 && dst.superdim_pos_[i] >= 0)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /*!
+   * \brief Returns a sublayout which is the portion of the object
+   *        that starts at dimension \p pos and spans \p len dimensions
+   *        (or until the end of the layout, whichever comes first).
+   * \param pos The start position.
+   * \param len The length of the sub-layout.
+   * \return A newly constructed Layout object.
+   */
+  inline Layout sublayout(size_t pos, size_t len) const {
+    if (pos > ndim()) return Layout::Undef();
+    if (pos + len > ndim()) len = ndim() - pos;
+    if (len == 0) return Layout::Undef();
+    std::ostringstream new_layout;
+    for (size_t i = pos; i < pos + len; ++i) {
+      if (is_subdim(layout_simplified_[i])) {
+        auto block_size = this->subsizeof(layout_simplified_[i]);
+        CHECK_GT(block_size, 0);
+        new_layout << block_size;
+      }
+      new_layout << layout_simplified_[i];
+    }
+    return Layout(new_layout.str());
+  }
+
+  /*! \return A newly constructed reversed Layout object. */
+  inline Layout reverse() const {
+    if (!this->defined()) return Layout::Undef();
+    std::ostringstream new_layout;
+    for (int64_t i = this->ndim() - 1; i >= 0; --i) {
+      if (is_subdim(layout_simplified_[i])) {
+        auto block_size = this->subsizeof(layout_simplified_[i]);
+        CHECK_GT(block_size, 0);
+        new_layout << block_size;
+      }
+      new_layout << layout_simplified_[i];
+    }
+    return Layout(new_layout.str());
+  }
+
+  /*!
+   * \brief Split \p dim by \p size and put the sub-dimension to position \p target_pos.
+   * \param dim The source dimension to be split. It must be a super-dimension.
+   * \param target_pos The target position of the newly split sub-dimension.
+   * \param size size of the sub-dimension.
+   * \return A newly constructed Layout object.
+   */
+  inline Layout split(LayoutDim dim, size_t target_pos, uint32_t size) const {
+    CHECK(target_pos <= this->ndim()) << "Invalid split position "
+                                      << target_pos << " for layout " << name_;
+    CHECK(is_superdim(dim)) << "Cannot split a sub-dimension " << dim;
+    CHECK(this->contains(dim)) << "Axis " << dim << " does not exist in " << name_;
+    CHECK(!this->contains(to_subdim(dim))) << "Dimension " << dim
+                                           << " has already been split in "
+                                           << name_;
+    CHECK(size > 0) << "Invalid split size " << size;
+    std::ostringstream new_layout;
+    for (size_t i = 0; i <= this->ndim(); ++i) {
+      if (i == target_pos) {
+        new_layout << size << Layout::to_subdim(dim);
+      }
+      if (i == this->ndim()) break;
+      new_layout << this->at(i);
+    }
+    Layout x(new_layout.str());
+    return x;
+  }
+
+  using iterator = std::vector<LayoutDim>::const_iterator;
+  using reverse_iterator = std::vector<LayoutDim>::const_reverse_iterator;
+
+  /*! \return begin iterator */
+  inline iterator begin() const {
+    return layout_simplified_.begin();
+  }
+  /*! \return end iterator */
+  inline iterator end() const {
+    return layout_simplified_.end();
+  }
+  /*! \return rbegin iterator */
+  inline reverse_iterator rbegin() const {
+    return layout_simplified_.rbegin();
+  }
+  /*! \return rend iterator */
+  inline reverse_iterator rend() const {
+    return layout_simplified_.rend();
+  }
+
+  /*! \return number of dimensions */
+  inline size_t ndim() const {
+    return layout_simplified_.size();
+  }
+
+  /*!
+   * \brief The description of the \p i-th dimension.
+   *        If it is a sub-dimension, the size will be returned as well,
+   *        e.g., 16c. Otherwise a single character is returned, e.g., C.
+   * \param i The position
+   * \return the description of the dimension.
+   */
+  inline std::string at(size_t i) const {
+    CHECK_LT(i, this->ndim()) << "position " << i
+                              << " exceeds ndim=" << this->ndim();
+    std::ostringstream repr;
+    if (is_subdim(layout_simplified_[i])) {
+      auto factor = subsizeof(layout_simplified_[i]);
+      CHECK_LT(factor, 0);
+      repr << factor;
+    }
+    repr << layout_simplified_[i];
+    return repr.str();
+  }
+
+  /*!
+   * \brief return the index of the input dimension.
+   *        If it is not found in the layout or the layout is undefined,
+   *        return -1.
+   * \param dim the input dimension.
+   * \return the index or -1 if not found.
+   */
+  inline int32_t indexof(LayoutDim dim) const {
+    if (!this->defined()) return -1;
+    else if (is_superdim(dim)) return superdim_pos_[dim - 'A'];
+    else if (is_subdim(dim)) return subdim_pos_[dim - 'a'];
+    return -1;
+  }
+
+  /*!
+   * \param dim the input super-dimension or sub-dimension.
+   * \return the size of the sub-dimension of \p dim (if \p dim is a super-dimension),
+   *         or the size of \p dim itself (if \p dim is a sub-dimension).
+   *         Return -1 if \p dim is not in the layout or the layout is undefined.
+   */
+  inline int64_t subsizeof(LayoutDim dim) const {
+    CHECK(is_superdim(dim) || is_subdim(dim)) << "Invalid dim " << dim;
+    if (!this->defined() || !this->contains(to_subdim(dim))) {
+      return -1;
+    }
+    int idx = to_subdim(dim) - 'a';
+    return subdim_size_[idx];
+  }
+
+  /*!
+   * \brief Whether the layout contains a dimension.
+   * \param dim dimension to be checked.
+   * \return Whether the layout contains the dimension.
+   */
+  inline bool contains(LayoutDim dim) const {
+    if (is_superdim(dim)) {
+      return superdim_pos_[dim-'A'] >= 0;
+    } else if (is_subdim(dim)) {
+      return subdim_pos_[dim-'a'] >= 0;
+    }
+    return false;
+  }
+
+  inline const LayoutDim operator[](size_t i) const {
+    return layout_simplified_[i];
+  }
+
+  /*! \return whether the layout is defined */
+  inline bool defined() const {
+    return name_ != "__undef__";
+  }
+
+  /*! \return the string description of the layout */
+  inline const std::string& name() const {
+    return name_;
+  }
+
+  /*!
+   * \brief Write layout in JSON format.
+   * \param writer JSONWriter
+   */
+  inline void Save(dmlc::JSONWriter* writer) const {
+    writer->Write(name_);
+  }
+
+  /*!
+   * \brief Load layout from JSON.
+   * \param reader JSONReader
+   */
+  inline void Load(dmlc::JSONReader* reader) {
+    std::string tmp;
+    reader->Read(&tmp);
+    this->parse(tmp);
+  }
+
+  /*!
+   * \brief allow output string of layout to ostream
+   * \param os the output stream
+   * \param l the layout
+   * \return the ostream
+   */
+  friend std::ostream& operator<<(std::ostream& os, const Layout& l) {
+    os << l.name_;
+    return os;
+  }
+
+ private:
+  static const uint32_t kUniqueDim = 26;
+
+  std::string name_;
+  int32_t superdim_pos_[kUniqueDim];
+  int32_t subdim_pos_[kUniqueDim];
+  int64_t subdim_size_[kUniqueDim];
+  std::vector<LayoutDim> layout_simplified_;
+
+  void parse(const std::string& layout) {
+    name_ = layout;
+    std::fill_n(superdim_pos_, kUniqueDim, -1);
+    std::fill_n(subdim_pos_, kUniqueDim, -1);
+    std::fill_n(subdim_size_, kUniqueDim, -1);
+    layout_simplified_.clear();
+
+    if (layout == "__undef__") return;
+
+    int32_t factor = 0;
+    uint32_t curr = 0;
+    for (size_t i = 0; i < layout.size(); ++i) {
+      const LayoutDim c = layout.at(i);
+      if (is_superdim(c)) {
+        int pos = c - 'A';
+        CHECK_EQ(factor, 0) << "Invalid layout " << layout
+                            << ": invalid factor size " << factor
+                            << " before dimension " << c;
+        CHECK_EQ(superdim_pos_[pos], -1) << "Invalid layout " << layout
+                                           << ": duplicate dimension " << c;
+        superdim_pos_[pos] = curr++;
+        layout_simplified_.push_back(c);
+      } else if (is_subdim(c)) {
+        int pos = c - 'a';
+        CHECK_GT(factor, 0) << "Invalid layout " << layout << ": invalid factor size "
+                            << factor << " for dimension " << c;
+        CHECK_EQ(subdim_pos_[pos], -1) << "Invalid layout " << layout
+                                           << ": duplicate dimension " << c;
+        CHECK_EQ(subdim_size_[pos], -1) << "Invalid layout " << layout
+                                         << ": duplicate dimension " << c;
+        subdim_pos_[pos] = curr++;
+        subdim_size_[pos] = factor;
+        layout_simplified_.push_back(c);
+        factor = 0;
+      } else if (c >= '0' && c <= '9') {
+        CHECK(factor >= 0) << "Invalid layout " << layout << ": _ is adjacent to a number.";
+        factor = factor * 10 + c - '0';
+      } else {
+        LOG(FATAL) << "Invalid layout " << layout;
+      }
+    }
+    CHECK(!layout_simplified_.empty()) << "Invalid layout " << layout;
+    for (LayoutDim dim : layout_simplified_) {
+      CHECK(is_superdim(dim) || superdim_pos_[dim-'a'] >= 0)
+        << "Invalid layout " << layout << ": missing axis "
+        << static_cast<char>(dim - 'a' + 'A');
+    }
+  }
+};
+
+}  // namespace nnvm
+
+#endif  // NNVM_LAYOUT_H_
diff --git a/nnvm/include/nnvm/op_attr_types.h b/nnvm/include/nnvm/op_attr_types.h
index e7bbb9685f6d..9891b82f91e8 100644
--- a/nnvm/include/nnvm/op_attr_types.h
+++ b/nnvm/include/nnvm/op_attr_types.h
@@ -13,6 +13,7 @@
 #include "./base.h"
 #include "./node.h"
 #include "./tuple.h"
+#include "./layout.h"
 
 namespace nnvm {
 
@@ -176,6 +177,31 @@ using FSetInputVarAttrOnCompose = std::function<void(
     NodePtr var,
     const int index)>;
 
+/*!
+ * \brief Inference function of node layout. See \p Layout for layout convention
+ * \param attrs The attribute of the node.
+ * \param ilayouts Given the input layouts produced by ancestor nodes,
+ *                 it should be filled by layouts that the node requests.
+ *                 If the requested layout is different from what ancestor produces,
+ *                 a __layout_transform__ operator will be inserted automatically.
+ * \param last_ilayouts The input layouts requested by the node
+ *                      at the last infer pass (if any).
+ *                      This can be useful when an operator wants to keep
+ *                      the input layout the same as the original one.
+ *                      For example, after the pass of AlterOpLayout,
+ *                      transpose(input, axis=[1, 2, 3, 0]) may receive an input of NCHW16c layout,
+ *                      with which it cannot calculate with axis=[1, 2, 3, 0].
+ *                      Last input layouts allow it to know what the layout it originally inferred,
+ *                      i.e., the layout in the imported model.
+ * \param olayouts Inferred output layouts.
+ * \return success flag.
+ */
+using FInferLayout = std::function<bool(
+    const NodeAttrs& attrs,
+    std::vector<Layout> *ilayouts,
+    const std::vector<Layout> *last_ilayouts,
+    std::vector<Layout> *olayouts)>;
+
 }  // namespace nnvm
 
 #endif  // NNVM_OP_ATTR_TYPES_H_
diff --git a/nnvm/include/nnvm/top/nn.h b/nnvm/include/nnvm/top/nn.h
index 7eb2e5e11e4a..80d8e02882ef 100644
--- a/nnvm/include/nnvm/top/nn.h
+++ b/nnvm/include/nnvm/top/nn.h
@@ -9,23 +9,12 @@
 #include <dmlc/base.h>
 #include <dmlc/parameter.h>
 #include <nnvm/tuple.h>
+#include <nnvm/layout.h>
+#include <string>
 
 namespace nnvm {
 namespace top {
 
-// Layout flag in spatial conv and pooling.
-enum LayoutFlag {
-  kNCHW,
-  kNHWC,
-  kCHWN,
-  kNCW,
-  kNWC,
-  kCWN,
-  kNCDHW,
-  kNDHWC,
-  kCDHWN
-};
-
 struct DenseParam : public dmlc::Parameter<DenseParam> {
   int units;
   bool use_bias;
@@ -130,7 +119,9 @@ struct Conv2DParam : public dmlc::Parameter<Conv2DParam> {
   TShape padding;
   TShape dilation;
   int groups;
-  int layout;
+  std::string layout;
+  std::string kernel_layout;
+  std::string out_layout;
   bool use_bias;
 
   DMLC_DECLARE_PARAMETER(Conv2DParam) {
@@ -152,14 +143,19 @@ struct Conv2DParam : public dmlc::Parameter<Conv2DParam> {
                 "At groups=2, the operation becomes equivalent to having two convolution"
                 "layers side by side, each seeing half the input channels, and producing"
                 "half the output channels, and both subsequently concatenated.");
-    DMLC_DECLARE_FIELD(layout)
-      .add_enum("NCHW", kNCHW)
-      .add_enum("NHWC", kNHWC)
-      .set_default(kNCHW)
-      .describe("Dimension ordering of data and weight. Can be 'NCHW', 'NHWC', etc."
+    DMLC_DECLARE_FIELD(layout).set_default("NCHW")
+      .describe("Dimension ordering of input data. Can be 'NCHW', 'NHWC', etc."
                 "'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
                 "dimensions respectively. Convolution is applied on the 'H' and"
                 "'W' dimensions.");
+    DMLC_DECLARE_FIELD(out_layout).set_default("__undef__")
+      .describe("Dimension ordering of output. Can be 'NCHW', 'NHWC', etc."
+                "'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
+                "dimensions respectively. Default to be same as input layout.");
+    DMLC_DECLARE_FIELD(kernel_layout).set_default("OIHW")
+      .describe("Dimension ordering of weight. Can be 'OIHW', 'OIHW16o16i', etc."
+                "'O', 'I', 'H', 'W' stands for num_filter, input_channel, height, and width"
+                "dimensions respectively.");
     DMLC_DECLARE_FIELD(use_bias).set_default(true)
       .describe("Whether the layer uses a bias vector.");
   }
@@ -178,7 +174,8 @@ struct Conv2DTransposeParam : public dmlc::Parameter<Conv2DTransposeParam> {
   TShape output_padding;
   TShape dilation;
   int groups;
-  int layout;
+  std::string layout;
+  std::string kernel_layout;
   bool use_bias;
 
   DMLC_DECLARE_PARAMETER(Conv2DTransposeParam) {
@@ -202,14 +199,15 @@ struct Conv2DTransposeParam : public dmlc::Parameter<Conv2DTransposeParam> {
                 "At groups=2, the operation becomes equivalent to having two convolution"
                 "layers side by side, each seeing half the input channels, and producing"
                 "half the output channels, and both subsequently concatenated.");
-    DMLC_DECLARE_FIELD(layout)
-      .add_enum("NCHW", kNCHW)
-      .add_enum("NHWC", kNHWC)
-      .set_default(kNCHW)
-      .describe("Dimension ordering of data and weight. Can be 'NCHW', 'NHWC', etc."
+    DMLC_DECLARE_FIELD(layout).set_default("NCHW")
+      .describe("Dimension ordering of data. Can be 'NCHW', 'NHWC', etc."
                 "'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
                 "dimensions respectively. Convolution is applied on the 'H' and"
                 "'W' dimensions.");
+    DMLC_DECLARE_FIELD(kernel_layout).set_default("OIHW")
+      .describe("Dimension ordering of data and weight. Can be 'OIHW', 'OIHW16o16i', etc."
+                "'O', 'I', 'H', 'W' stands for num_filter, input_channel, height, and width"
+                "dimensions respectively.");
     DMLC_DECLARE_FIELD(use_bias).set_default(true)
       .describe("Whether the layer uses a bias vector.");
   }
@@ -224,7 +222,7 @@ struct Pool2DParam : public dmlc::Parameter<Pool2DParam> {
   TShape pool_size;
   TShape strides;
   TShape padding;
-  int layout;
+  std::string layout;
   bool ceil_mode;
 
   DMLC_DECLARE_PARAMETER(Pool2DParam) {
@@ -235,10 +233,7 @@ struct Pool2DParam : public dmlc::Parameter<Pool2DParam> {
     DMLC_DECLARE_FIELD(padding).set_default(TShape({0, 0}))
       .describe("If padding is non-zero, then the input is implicitly zero-padded"
                 "on both sides for padding number of points");
-    DMLC_DECLARE_FIELD(layout)
-      .add_enum("NCHW", kNCHW)
-      .add_enum("NHWC", kNHWC)
-      .set_default(kNCHW)
+    DMLC_DECLARE_FIELD(layout).set_default("NCHW")
       .describe("Dimension ordering of data and weight. Can be 'NCHW', 'NHWC', etc."
                 "'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
                 "dimensions respectively. Convolution is applied on the 'H' and"
@@ -250,13 +245,10 @@ struct Pool2DParam : public dmlc::Parameter<Pool2DParam> {
 
 
 struct GlobalPool2DParam : public dmlc::Parameter<GlobalPool2DParam> {
-  int layout;
+  std::string layout;
 
   DMLC_DECLARE_PARAMETER(GlobalPool2DParam) {
-    DMLC_DECLARE_FIELD(layout)
-      .add_enum("NCHW", kNCHW)
-      .add_enum("NHWC", kNHWC)
-      .set_default(kNCHW)
+    DMLC_DECLARE_FIELD(layout).set_default("NCHW")
       .describe("Dimension ordering of data and weight. Can be 'NCHW', 'NHWC', etc."
                 "'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
                 "dimensions respectively. Convolution is applied on the 'H' and"
@@ -266,15 +258,13 @@ struct GlobalPool2DParam : public dmlc::Parameter<GlobalPool2DParam> {
 
 struct UpSamplingParam : public dmlc::Parameter<UpSamplingParam> {
   int scale;
-  int layout;
+  std::string layout;
 
   DMLC_DECLARE_PARAMETER(UpSamplingParam) {
     DMLC_DECLARE_FIELD(scale)
       .describe("upsampling scaling factor");
     DMLC_DECLARE_FIELD(layout)
-      .add_enum("NCHW", kNCHW)
-      .add_enum("NHWC", kNHWC)
-      .set_default(kNCHW)
+      .set_default("NCHW")
       .describe("Dimension ordering of data and weight. Can be 'NCHW', 'NHWC', etc."
                 "'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
                 "dimensions respectively. Convolution is applied on the 'H' and"
@@ -282,6 +272,18 @@ struct UpSamplingParam : public dmlc::Parameter<UpSamplingParam> {
   }
 };
 
+struct LayoutTransformParam : public dmlc::Parameter<LayoutTransformParam> {
+  std::string src_layout;
+  std::string dst_layout;
+
+  DMLC_DECLARE_PARAMETER(LayoutTransformParam) {
+    DMLC_DECLARE_FIELD(src_layout).set_default("__undef__")
+    .describe("Dimension ordering of data");
+    DMLC_DECLARE_FIELD(dst_layout).set_default("__undef__")
+    .describe("Dimension ordering of data.");
+  }
+};
+
 }  // namespace top
 }  // namespace nnvm
 
diff --git a/nnvm/python/nnvm/_ctypes/symbol.py b/nnvm/python/nnvm/_ctypes/symbol.py
index 7a680f093cd4..843601c10f4e 100644
--- a/nnvm/python/nnvm/_ctypes/symbol.py
+++ b/nnvm/python/nnvm/_ctypes/symbol.py
@@ -211,12 +211,15 @@ def _init_symbol_module(symbol_class, root_namespace):
         op_names.append(py_str(plist[i]))
 
     module_obj = sys.modules["%s.symbol" % root_namespace]
+    module_obj_contrib = sys.modules["%s.contrib" % root_namespace]
     module_internal = sys.modules["%s._symbol_internal" % root_namespace]
     for name in op_names:
         hdl = OpHandle()
         check_call(_LIB.NNGetOpHandle(c_str(name), ctypes.byref(hdl)))
         function = _make_atomic_symbol_function(hdl, name)
-        if function.__name__.startswith('_'):
+        if function.__name__.startswith('_contrib_'):
+            setattr(module_obj_contrib, function.__name__.split('_contrib_')[1], function)
+        elif function.__name__.startswith('_'):
             setattr(module_internal, function.__name__, function)
             setattr(module_obj, function.__name__, function)
         else:
diff --git a/nnvm/python/nnvm/compiler/build_module.py b/nnvm/python/nnvm/compiler/build_module.py
index d97a8784dcc2..436b06c5e34c 100644
--- a/nnvm/python/nnvm/compiler/build_module.py
+++ b/nnvm/python/nnvm/compiler/build_module.py
@@ -15,7 +15,8 @@
     "SimplifyInference": 0,
     "PrecomputePrune": 2,
     "OpFusion": 1,
-    "FoldScaleAxis": 3
+    "FoldScaleAxis": 3,
+    "AlterOpLayout": 3,
 }
 
 # List of optimization pass and level when switch on
@@ -139,7 +140,7 @@ def _update_shape_dtype(shape, dtype, params):
     return shape, dtype
 
 
-def optimize(graph, shape, dtype="float32"):
+def optimize(graph, shape, dtype="float32", layout=None):
     """Perform target and parameter invariant graph optimization.
 
     This is an advanced function that usually do not need to be called.
@@ -157,6 +158,18 @@ def optimize(graph, shape, dtype="float32"):
     """
     # pylint: disable=unused-argument
     cfg = BuildConfig.current
+
+    if cfg.pass_enabled("AlterOpLayout"):
+        layout = layout if layout else {}
+        graph = graph_attr.set_layout_inputs(graph, layout)
+        graph = graph.apply(["CorrectLayout"])
+
+        graph = graph_attr.set_shape_inputs(graph, shape)
+        graph = graph_attr.set_dtype_inputs(graph, dtype)
+        graph = graph.apply(["InferShape", "InferType", "AlterOpLayout"])
+        graph = graph_attr.set_layout_inputs(graph, layout)
+        graph = graph.apply(["CorrectLayout"])
+
     if cfg.pass_enabled("SimplifyInference"):
         graph = graph_attr.set_shape_inputs(graph, shape)
         graph = graph.apply(["InferShape", "SimplifyInference"])
@@ -167,7 +180,8 @@ def optimize(graph, shape, dtype="float32"):
     return graph
 
 
-def build(graph, target=None, shape=None, dtype="float32", params=None, target_host=None):
+def build(graph, target=None, shape=None, dtype="float32",
+          params=None, target_host=None, layout=None):
     """Build graph into runtime library.
 
     The build function will optimize the graph and do the compilation.
@@ -204,8 +218,8 @@ def build(graph, target=None, shape=None, dtype="float32", params=None, target_h
         By default, llvm is used if it is enabled,
         otherwise a stackvm intepreter is used.
 
-    initialize : bool, optional
-        Whether to initialize variables in global dict _all_var_init.
+    layout : dict of str to str or str optional
+        The input layout
 
     Returns
     -------
@@ -230,6 +244,15 @@ def build(graph, target=None, shape=None, dtype="float32", params=None, target_h
     cfg = BuildConfig.current
     graph = graph if isinstance(graph, _graph.Graph) else _graph.create(graph)
     shape, dtype = _update_shape_dtype(shape, dtype, params)
+
+    # correct layout if necessary
+    layout = layout if layout else {}
+    graph = graph_attr.set_layout_inputs(graph, layout)
+    graph = graph.apply("CorrectLayout")
+    index = graph.index
+    layouts = graph.json_attr("layout")
+    layout = {x : layouts[index.entry_id(x)] for x in index.input_names}
+
     # Initial pass do shape type inference
     ishape, _ = graph_util.infer_shape(graph, **shape)
     shape.update(zip(graph.index.input_names, ishape))
@@ -241,13 +264,14 @@ def build(graph, target=None, shape=None, dtype="float32", params=None, target_h
     if _all_var_init:
         init_var = initialize_variables(shape, dtype)
     # Apply optimization
-    graph = optimize(graph, shape, dtype)
+    graph = optimize(graph, shape, dtype, layout)
     # Precompute prune
     if params and cfg.pass_enabled("PrecomputePrune"):
         graph, params = precompute_prune(graph, params)
         shape, dtype = _update_shape_dtype(shape, dtype, params)
     # Operator Fusion and generation
     graph = graph_attr.set_shape_inputs(graph, shape)
+    graph = graph.apply("InferShape")
     graph = graph_attr.set_dtype_inputs(graph, dtype)
     graph._set_json_attr("target", str(target), "str")
     if target_host is not None:
diff --git a/nnvm/python/nnvm/compiler/graph_attr.py b/nnvm/python/nnvm/compiler/graph_attr.py
index 94b973353e47..3ce6c4b53239 100644
--- a/nnvm/python/nnvm/compiler/graph_attr.py
+++ b/nnvm/python/nnvm/compiler/graph_attr.py
@@ -96,11 +96,22 @@ def set_layout_inputs(g, layout):
     Returns
     -------
     g : Graph
-        The updated graph with updated dtype.
+        The updated graph with updated layout.
     """
-    list_shape = [
-        layout.get(name, "default") for name in g.index.input_names]
-    g._set_json_attr("layout_inputs", list_shape, 'list_str')
+    if isinstance(layout, dict):
+        list_layout = [
+            layout.get(name, "__undef__") for name in g.index.input_names]
+    elif isinstance(layout, str):
+        list_layout = ["__undef__"] * len(g.index.input_names)
+        list_layout[0] = layout
+    else:
+        raise ValueError("Input layout must be str or dict")
+    last_inferred_layouts = g.json_attr("layout")
+    if last_inferred_layouts:
+        input_layout = [last_inferred_layouts[g.index.entry_id(x)] for x in g.index.input_names]
+        for i, layout_stored in enumerate(input_layout):
+            list_layout[i] = list_layout[i] if list_layout[i] != '__undef__' else layout_stored
+    g._set_json_attr("layout_inputs", list_layout, 'list_layout')
     return g
 
 _move_out_module = tvm.get_global_func("nnvm.graph._move_module")
diff --git a/nnvm/python/nnvm/contrib.py b/nnvm/python/nnvm/contrib.py
new file mode 100644
index 000000000000..976eb532b019
--- /dev/null
+++ b/nnvm/python/nnvm/contrib.py
@@ -0,0 +1 @@
+"""Module space to register contrib functions. Leave empty"""
diff --git a/nnvm/python/nnvm/frontend/mxnet.py b/nnvm/python/nnvm/frontend/mxnet.py
index 4296705f063f..e671acbe7d75 100644
--- a/nnvm/python/nnvm/frontend/mxnet.py
+++ b/nnvm/python/nnvm/frontend/mxnet.py
@@ -86,6 +86,10 @@ def _conv2d(inputs, attrs):
     layout = attrs.get('layout', 'NCHW')
     if layout not in ['NCHW', 'NHWC']:
         _raise_not_supported('layout: ' + layout, 'conv2d')
+    if 'kernel_layout' in attrs:
+        kernel_layout = attrs['kernel_layout']
+    else:
+        kernel_layout = 'HWIO' if layout == 'NHWC' else 'OIHW'
     op_name, new_attrs = 'conv2d', {}
     new_attrs['channels'] = _required_attr(attrs, 'num_filter')
     new_attrs['kernel_size'] = kernel
@@ -94,6 +98,7 @@ def _conv2d(inputs, attrs):
     new_attrs['dilation'] = attrs.get('dilate', (1, 1))
     new_attrs['groups'] = attrs.get('num_group', 1)
     new_attrs['layout'] = layout
+    new_attrs['kernel_layout'] = kernel_layout
     new_attrs['use_bias'] = attrs.get('no_bias', 'False').strip() == 'False'
     return _get_nnvm_op(op_name)(*inputs, **new_attrs)
 
@@ -106,6 +111,10 @@ def _conv2d_transpose(inputs, attrs):
     layout = attrs.get('layout', 'NCHW')
     if layout not in ['NCHW', 'NHWC']:
         _raise_not_supported('layout: ' + layout, 'conv2d_transpose')
+    if 'kernel_layout' in attrs:
+        kernel_layout = attrs['kernel_layout']
+    else:
+        kernel_layout = 'HWIO' if layout == 'NHWC' else 'OIHW'
     op_name, new_attrs = 'conv2d_transpose', {}
     new_attrs['channels'] = _required_attr(attrs, 'num_filter')
     new_attrs['kernel_size'] = kernel
@@ -115,6 +124,7 @@ def _conv2d_transpose(inputs, attrs):
     new_attrs['dilation'] = attrs.get('dilate', (1, 1))
     new_attrs['groups'] = attrs.get('num_group', 1)
     new_attrs['layout'] = layout
+    new_attrs['kernel_layout'] = kernel_layout
     new_attrs['use_bias'] = not _parse_bool_str(attrs, 'no_bias')
     return _get_nnvm_op(op_name)(*inputs, **new_attrs)
 
@@ -237,7 +247,7 @@ def _upsampling(inputs, attrs):
     'min_axis'      : _rename('min'),
     'reshape'       : _reshape,
     'sum_axis'      : _rename('sum'),
-    'UpSampling'    : _upsampling
+    'UpSampling'    : _upsampling,
 }
 
 def _convert_symbol(op_name, inputs, attrs,
diff --git a/nnvm/python/nnvm/symbol.py b/nnvm/python/nnvm/symbol.py
index 8b390e2cb72f..9fa0af286af8 100644
--- a/nnvm/python/nnvm/symbol.py
+++ b/nnvm/python/nnvm/symbol.py
@@ -16,6 +16,7 @@
 from ._base import _LIB, check_call as _check_call, _FFI_MODE, _all_var_init
 from .attribute import AttrScope
 from . import _symbol_internal as _internal
+from . import contrib
 
 # Use different verison of SymbolBase
 # When possible, use cython to speedup part of computation.
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index d7e6e5a08f85..7a5144983838 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -5,7 +5,7 @@
 import tvm
 import topi
 from topi.util import get_const_int
-from .tensor import _fschedule_broadcast
+from .tensor import _fschedule_broadcast, _fschedule_injective
 from . import registry as reg
 from .registry import OpPattern
 
@@ -32,6 +32,11 @@
 reg.register_pattern("pad", OpPattern.INJECTIVE)
 
 
+# layout transform
+reg.register_schedule("__layout_transform__", _fschedule_injective)
+reg.register_pattern("__layout_transform__", OpPattern.INJECTIVE)
+
+
 @reg.register_schedule("softmax")
 def schedule_softmax(_, outs, target):
     """Schedule definition of softmax"""
@@ -108,6 +113,42 @@ def schedule_conv2d(attrs, outs, target):
 
 reg.register_pattern("conv2d", OpPattern.OUT_ELEMWISE_FUSABLE)
 
+# convolution NCHWc
+@reg.register_compute("_contrib_conv2d_NCHWc")
+def compute_contrib_conv2d_NCHWc(attrs, inputs, _):
+    """Compute definition of conv2d NCHWc"""
+    padding = attrs.get_int_tuple("padding")
+    strides = attrs.get_int_tuple("strides")
+    dilation = attrs.get_int_tuple("dilation")
+    kh, kw = attrs.get_int_tuple('kernel_size')
+    groups = attrs.get_int("groups")
+    channels = attrs.get_int("channels")
+    assert dilation == (1, 1), "not support dilate now"
+    if groups == 1:
+        out = topi.nn.conv2d_NCHWc(inputs[0], inputs[1], channels, (kh, kw), strides, padding)
+    else:
+        raise ValueError("not support arbitrary group number > 1 for now")
+    if attrs.get_bool("use_bias"):
+        bias = inputs[2]
+        bias = topi.expand_dims(bias, axis=1, num_newaxis=2)
+        out = topi.broadcast_add(out, bias)
+    return out
+
+@reg.register_schedule("_contrib_conv2d_NCHWc")
+def schedule_contrib_conv2d_NCHWc(attrs, outs, target):
+    """Schedule definition of conv2d NCHWc"""
+    groups = attrs.get_int("groups")
+    kh, kw = attrs.get_int_tuple('kernel_size')
+    oc = attrs.get_int("channels")
+    padding = attrs.get_int_tuple("padding")
+    strides = attrs.get_int_tuple("strides")
+    with tvm.target.create(target):
+        if groups == 1:
+            return topi.generic.schedule_conv2d_NCHWc(oc, (kh, kw), strides, padding, outs)
+        else:
+            raise ValueError("not support group number > 1 for now")
+
+reg.register_pattern("_contrib_conv2d_NCHWc", OpPattern.OUT_ELEMWISE_FUSABLE)
 
 # conv2d_transpose
 @reg.register_compute("conv2d_transpose")
diff --git a/nnvm/python/nnvm/top/registry.py b/nnvm/python/nnvm/top/registry.py
index 6a720944250b..68ea80e7e017 100644
--- a/nnvm/python/nnvm/top/registry.py
+++ b/nnvm/python/nnvm/top/registry.py
@@ -25,6 +25,7 @@ class OpPattern(object):
 _register_compute = tvm.get_global_func("nnvm._register_compute")
 _register_schedule = tvm.get_global_func("nnvm._register_schedule")
 _register_pattern = tvm.get_global_func("nnvm._register_pattern")
+_register_alter_op_layout = tvm.get_global_func("nnvm.compiler._register_alter_op_layout")
 
 def register_compute(op_name, f=None, level=10):
     """Register compute function for operator
@@ -93,3 +94,29 @@ def register_pattern(op_name, pattern, level=10):
         The priority level
     """
     _register_pattern(op_name, pattern, level)
+
+
+def register_alter_op_layout(op_name, f=None, level=10):
+    """Register alter layout function for operator
+
+    Parameters
+    ----------
+    op_name : str
+        The name of operator
+
+    f : function
+        The schedule function
+
+    level : int
+        The priority level
+
+    Returns
+    -------
+    fregister : function
+        Register function if f is not specified.
+    """
+    def register(myf):
+        """internal register function"""
+        _register_alter_op_layout(op_name, myf, level)
+        return myf
+    return register(f) if f else register
diff --git a/nnvm/src/c_api/c_api_symbolic.cc b/nnvm/src/c_api/c_api_symbolic.cc
index 0327ca4ae8a7..9f62dbd80b0c 100644
--- a/nnvm/src/c_api/c_api_symbolic.cc
+++ b/nnvm/src/c_api/c_api_symbolic.cc
@@ -294,7 +294,7 @@ int NNSymbolGetNumOutputs(SymbolHandle symbol,
                            nn_uint *output_count) {
   Symbol *s = static_cast<Symbol*>(symbol);
   API_BEGIN();
-    *output_count = static_cast<nn_uint>(s->outputs.size());
+  *output_count = static_cast<nn_uint>(s->outputs.size());
   API_END();
 }
 
diff --git a/nnvm/src/compiler/alter_op_layout.cc b/nnvm/src/compiler/alter_op_layout.cc
new file mode 100644
index 000000000000..893a0d2987ae
--- /dev/null
+++ b/nnvm/src/compiler/alter_op_layout.cc
@@ -0,0 +1,151 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file alter_op_layout.cc
+ * \brief Alter the operator layouts. Keep inferred layouts (if any) from previous stages.
+ *        e.g., convolution may calculates faster with NCHW16c layout.
+ */
+#include <nnvm/pass.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/layout.h>
+#include <nnvm/compiler/op_attr_types.h>
+#include <nnvm/pass_functions.h>
+#include <tvm/tvm.h>
+#include <algorithm>
+#include <functional>
+#include "./compile_engine.h"
+#include "./graph_transform.h"
+
+namespace nnvm {
+namespace compiler {
+namespace {
+
+tvm::Array<tvm::Tensor> GetTensorInfo(const IndexedGraph& idx_graph,
+                                      const uint32_t nid,
+                                      const ShapeVector& shape_vec,
+                                      const DTypeVector& dtype_vec) {
+  tvm::Array<tvm::Tensor> vec;
+  for (uint32_t i = 0; i < idx_graph[nid].source->num_outputs(); ++i) {
+    tvm::Array<tvm::Expr> shape;
+    for (int64_t x : shape_vec[idx_graph.entry_id(nid, i)]) {
+      CHECK_LE(x, static_cast<int64_t>(std::numeric_limits<int>::max()));
+      shape.push_back(tvm::make_const(tvm::Int(32), x));
+    }
+    vec.push_back(tvm::placeholder(
+      shape, GetTVMType(dtype_vec[idx_graph.entry_id(nid, i)])));
+  }
+  return vec;
+}
+
+Graph AlterOpLayout(const Graph& src) {
+  static auto& falter_op_layout =
+    Op::GetAttr<nnvm::compiler::FTVMAlterOpLayout >("FTVMAlterOpLayout");
+
+  const ShapeVector& shape_vec = src.GetAttr<ShapeVector>("shape");
+  const DTypeVector& dtype_vec = src.GetAttr<DTypeVector>("dtype");
+  const IndexedGraph& idx_graph = src.indexed_graph();
+
+  std::vector<std::vector<Layout> > in_layouts_of_node(idx_graph.num_nodes());
+  std::vector<std::vector<Layout> > out_layouts_of_node(idx_graph.num_nodes());
+  std::unordered_map<const Node*, uint32_t> new_nodes;
+
+  if (src.HasAttr("layout")) {
+    // record layouts so that LayoutTransform pass can fix layouts correctly,
+    // e.g., conv2d can be replaced by some contrib implement
+    // whose layout is different from the original one
+    // (which was imported from a model file).
+    const auto& layouts = src.GetAttr<std::vector<Layout> >("layout");
+    for (uint32_t nid = 0; nid < idx_graph.num_nodes(); ++nid) {
+      const auto &inode = idx_graph[nid];
+      if (falter_op_layout.count(inode.source->op())) {
+        // do not record input layouts of nodes that will be replaced.
+        continue;
+      }
+      std::vector<Layout> in_layout;
+      for (const auto& e : inode.inputs) {
+        in_layout.emplace_back(layouts[idx_graph.entry_id(e)]);
+      }
+      in_layouts_of_node[nid] = in_layout;
+
+      std::vector<Layout> out_layout;
+      for (uint i = 0; i < inode.source->num_outputs(); ++i) {
+        out_layout.emplace_back(layouts[idx_graph.entry_id(nid, i)]);
+      }
+      out_layouts_of_node[nid] = out_layout;
+    }
+  }
+
+  auto transform = [&](uint32_t nid,
+                       const NodePtr& n,
+                       std::vector<NodeEntry>* ret) {
+    nnvm::compiler::FTVMAlterOpLayout fn_alter_op_layout =
+      falter_op_layout.get(n->op(), nullptr);
+    if (fn_alter_op_layout == nullptr) {
+      new_nodes[n.get()] = nid;
+      return false;
+    }
+
+    // construct parameters for registered function
+    std::vector<Symbol> op_inputs;
+    tvm::Array<tvm::Tensor> tensor_infos;
+    CHECK_EQ(n->num_inputs(), idx_graph[nid].inputs.size());
+    for (uint32_t i = 0; i < n->num_inputs(); ++i) {
+      const nnvm::NodeEntry& input = n->inputs[i];
+      // input operator
+      Symbol op_input;
+      op_input.outputs.push_back(input);
+      op_inputs.push_back(op_input);
+
+      // input tinfo, extract from the original graph
+      // because it was where infer_shape & infer_type applied.
+      tvm::Array<tvm::Tensor> op_output_tinfos =
+        GetTensorInfo(idx_graph, idx_graph[nid].inputs[i].node_id,
+                      shape_vec, dtype_vec);
+      tensor_infos.push_back(op_output_tinfos[input.index]);
+    }
+    // callback registered function to get a new operator.
+    auto op = fn_alter_op_layout(n->attrs, Symbol::CreateGroup(op_inputs), tensor_infos);
+    *ret = op.outputs;
+    return true;
+  };
+
+  Graph ret = nnvm::compiler::GraphTransform(src, transform);
+
+  if (src.HasAttr("layout")) {
+    // restore the layouts to return graph
+    const auto& ret_idx = ret.indexed_graph();
+    std::vector<Layout> ret_layouts(ret_idx.num_node_entries(), Layout::Undef());
+    for (uint32_t nid = 0; nid < ret_idx.num_nodes(); ++nid) {
+      const auto& inode = ret_idx[nid];
+      if (new_nodes.count(inode.source)) {
+        const std::vector<Layout>& in_layouts =
+          in_layouts_of_node[new_nodes[inode.source]];
+        for (const auto& e : inode.inputs) {
+          ret_layouts[ret_idx.entry_id(e)] = in_layouts[e.index];
+        }
+        const std::vector<Layout>& out_layouts =
+          out_layouts_of_node[new_nodes[inode.source]];
+        for (uint32_t i = 0; i < inode.source->num_outputs(); ++i) {
+          ret_layouts[ret_idx.entry_id(nid, i)] = out_layouts[i];
+        }
+      }
+    }
+
+    // cannot call indexed_graph() before return the origin Graph,
+    // thus create a new one.
+    nnvm::Graph new_ret;
+    new_ret.outputs = ret.outputs;
+    new_ret.attrs["layout"] = std::make_shared<any>(std::move(ret_layouts));
+    return new_ret;
+  }
+
+  return ret;
+}
+
+// register pass
+NNVM_REGISTER_PASS(AlterOpLayout)
+.set_body(AlterOpLayout)
+.set_change_graph(true);
+
+}  // namespace
+}  // namespace compiler
+}  // namespace nnvm
diff --git a/nnvm/src/compiler/fold_scale_axis.cc b/nnvm/src/compiler/fold_scale_axis.cc
index 7b05153b65f5..f9524eb8ed34 100644
--- a/nnvm/src/compiler/fold_scale_axis.cc
+++ b/nnvm/src/compiler/fold_scale_axis.cc
@@ -362,7 +362,7 @@ bool Pool2DBackward(
     std::vector<FoldChainInfo>* in_axis) {
   using top::Pool2DParam;
   const Pool2DParam& param = nnvm::get<Pool2DParam>(attrs.parsed);
-  if (out_info.axis == 1 && param.layout == top::kNCHW) {
+  if (out_info.axis == 1 && param.layout == "NCHW") {
     (*in_axis)[0] = out_info;
   }
   return false;
@@ -376,7 +376,7 @@ bool Pool2DForward(
     FoldChainInfo* out_info) {
   using top::Pool2DParam;
   const Pool2DParam& param = nnvm::get<Pool2DParam>(attrs.parsed);
-  if ((*in_info)[0].axis == 1 && param.layout == top::kNCHW) {
+  if ((*in_info)[0].axis == 1 && param.layout == "NCHW") {
     *out_info = (*in_info)[0];
   }
   return false;
@@ -467,7 +467,7 @@ bool Conv2DScaleAxisBackward(
   const Conv2DParam& param = nnvm::get<Conv2DParam>(attrs.parsed);
   if (out_info.kind != kPending) return false;
   // only optimize for nchw for now
-  if (param.layout == top::kNCHW && out_info.axis == 1) {
+  if (param.layout == "NCHW" && out_info.axis == 1) {
     (*in_axis)[1].kind = kMulConsumer;
     (*in_axis)[1].axis = 0;
     (*in_axis)[1].source = out_info.source;
@@ -492,7 +492,7 @@ bool Conv2DScaleAxisForward(
   const Conv2DParam& param = nnvm::get<Conv2DParam>(attrs.parsed);
   if ((*in_info)[0].kind != kPending) return false;
   // only optimize for nchw for now
-  if (param.layout == top::kNCHW && (*in_info)[0].axis == 1) {
+  if (param.layout == "NCHW" && (*in_info)[0].axis == 1) {
     (*in_info)[1].kind = kMulConsumer;
     (*in_info)[1].axis = 1;
     (*in_info)[1].source = (*in_info)[0].source;
diff --git a/nnvm/src/compiler/layout_transform.cc b/nnvm/src/compiler/layout_transform.cc
deleted file mode 100644
index 5651838ffb85..000000000000
--- a/nnvm/src/compiler/layout_transform.cc
+++ /dev/null
@@ -1,159 +0,0 @@
-/*!
- *  Copyright (c) 2017 by Contributors
- * \file layout_transform.cc
- * \brief Transforms layout.
- */
-#include <nnvm/graph.h>
-#include <nnvm/op_attr_types.h>
-#include <nnvm/graph_attr_types.h>
-#include <nnvm/pass.h>
-#include <nnvm/compiler/op_attr_types.h>
-#include <nnvm/compiler/contrib_op_param.h>
-
-namespace nnvm {
-namespace compiler {
-
-const TLayoutInfo& GetDefaultLayout() {
-  static TLayoutInfo default_layout = "default";
-  return default_layout;
-}
-
-nnvm::NodePtr CreateLayoutTransformNode(const std::string& src,
-                                        const std::string& dst) {
-  static const nnvm::Op* trans_op = nnvm::Op::Get("layout_transform");
-  static int count = 0;
-  nnvm::NodePtr n = nnvm::Node::Create();
-  n->attrs.op = trans_op;
-  n->attrs.name = src + "_to_" + dst + std::to_string(count++);
-  n->attrs.dict["src_layout"] = src;
-  n->attrs.dict["dst_layout"] = dst;
-  n->op()->attr_parser(&(n->attrs));
-  return n;
-}
-
-/*!
- * \brief A simple layout transform pass that will
- *  insert layout transform nodes automatically.
- */
-nnvm::Graph LayoutTransform(nnvm::Graph src) {
-  static auto& op_layout_request =
-    nnvm::Op::GetAttr<FTVMLayoutRequest>("FTVMLayoutRequest");
-  static auto& op_vecop =
-    nnvm::Op::GetAttr<FTVMVectorizedOp>("FTVMVectorizedOp");
-  static auto& op_pattern = nnvm::Op::GetAttr<TOpPattern>("TOpPattern");
-
-  const ShapeVector& shape_vec = src.GetAttr<ShapeVector>("shape");
-  const std::vector<TLayoutInfo>& input_layouts =
-      src.GetAttr<std::vector<TLayoutInfo> >("layout_inputs");
-
-  const IndexedGraph& idx = src.indexed_graph();
-  std::vector<TLayoutInfo> produce_vec(idx.num_node_entries(), GetDefaultLayout());
-  std::vector<nnvm::NodePtr> mirror_vec(idx.num_nodes(), nullptr);
-
-  // use op pattern to decide whether an op is map
-  auto is_map_op = [&](size_t nid) {
-    TOpPattern pt = op_pattern.get(idx[nid].source->op(), kOpaque);
-    bool is_map = (pt <= kBroadcast);
-    if (pt == kBroadcast) {
-      for (const auto& e : idx[nid].inputs) {
-        if (shape_vec[idx.entry_id(nid, 0)] != shape_vec[idx.entry_id(e)]) {
-          is_map = false;
-          break;
-        }
-      }
-    }
-    return is_map;
-  };
-
-  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
-    const auto& inode = idx[nid];
-    nnvm::NodePtr new_node = nnvm::Node::Create();
-    *new_node = *(inode.source);
-    if (new_node->is_variable()) {
-      auto input_iter = std::find(
-        idx.input_nodes().cbegin(), idx.input_nodes().cend(), nid);
-      CHECK(input_iter != idx.input_nodes().cend());
-      size_t input_id = std::distance(idx.input_nodes().cbegin(), input_iter);
-      produce_vec[idx.entry_id(nid, 0)] = input_layouts[input_id];
-      mirror_vec[nid] = new_node;
-      continue;
-    }
-
-    if (op_vecop.count(inode.source->op())) {
-      new_node = op_vecop[inode.source->op()](inode.source);
-      new_node->inputs.resize(new_node->num_inputs());
-    }
-
-    // set up output and input layouts
-    std::vector<TLayoutInfo> request_ilayouts(new_node->num_inputs(), GetDefaultLayout());
-    if (op_layout_request.count(new_node->op())) {
-      std::vector<TLayoutInfo> produce_olayouts(new_node->num_outputs(), GetDefaultLayout());
-      CHECK(op_layout_request[new_node->op()](
-          new_node->attrs, &request_ilayouts, &produce_olayouts))
-          << "Layout request fail";
-
-      CHECK_EQ(request_ilayouts.size(), new_node->num_inputs());
-      CHECK_EQ(produce_olayouts.size(), new_node->num_outputs());
-      for (size_t i = 0; i < new_node->num_outputs(); ++i) {
-        produce_vec[idx.entry_id(nid, i)] = produce_olayouts[i];
-      }
-    }
-
-    bool map_layout = is_map_op(nid);
-    if (map_layout) {
-      const TLayoutInfo& layout = produce_vec[idx.entry_id(inode.inputs[0])];
-      for (const auto& e : inode.inputs) {
-        if (produce_vec[idx.entry_id(e)] != layout) {
-          map_layout = false;
-          break;
-        }
-      }
-      if (map_layout) {
-        for (size_t i = 0; i < inode.source->num_outputs(); ++i) {
-          produce_vec[idx.entry_id(nid, i)] = layout;
-        }
-      }
-    }
-
-    for (size_t i = 0; i < inode.inputs.size(); ++i) {
-      const auto& e = inode.inputs[i];
-      const nnvm::NodePtr& in = mirror_vec[e.node_id];
-      new_node->inputs[i] =
-        nnvm::NodeEntry{in, e.index, e.version};
-
-      TLayoutInfo produce = produce_vec[idx.entry_id(e)];
-      TLayoutInfo request = request_ilayouts[i];
-      if (!map_layout && (produce != request)) {
-        nnvm::NodePtr tnode = CreateLayoutTransformNode(produce, request);
-        tnode->attrs.name =
-          idx[e.node_id].source->attrs.name + "_" + request;
-        tnode->inputs.emplace_back(new_node->inputs[i]);
-        new_node->inputs[i] = nnvm::NodeEntry{tnode, 0, 0};
-      }
-    }
-    mirror_vec[nid] = new_node;
-  }
-
-  std::vector<nnvm::NodeEntry> outputs;
-  for (const auto& e : idx.outputs()) {
-    TLayoutInfo produce = produce_vec[idx.entry_id(e)];
-    if (produce != GetDefaultLayout()) {
-      nnvm::NodePtr tnode = CreateLayoutTransformNode(produce, GetDefaultLayout());
-      tnode->attrs.name =
-        idx[e.node_id].source->attrs.name + "_default";
-      tnode->inputs.emplace_back(
-        nnvm::NodeEntry{mirror_vec[e.node_id], e.index, e.version});
-      outputs.emplace_back(nnvm::NodeEntry{tnode, 0, 0});
-    } else {
-      outputs.emplace_back(
-        nnvm::NodeEntry{mirror_vec[e.node_id], e.index, e.version});
-    }
-  }
-
-  nnvm::Graph ret;
-  ret.outputs = std::move(outputs);
-  return ret;
-}
-
-}  // namespace compiler
-}  // namespace nnvm
diff --git a/nnvm/src/compiler/packed_func_ext.cc b/nnvm/src/compiler/packed_func_ext.cc
index 4483f5303999..2587534d7d07 100644
--- a/nnvm/src/compiler/packed_func_ext.cc
+++ b/nnvm/src/compiler/packed_func_ext.cc
@@ -8,6 +8,7 @@
 #include <nnvm/op.h>
 #include <nnvm/compiler/packed_func_ext.h>
 #include <nnvm/compiler/op_attr_types.h>
+#include <tvm/runtime/c_runtime_api.h>
 #include "./node_attr.h"
 #include "compile_engine.h"
 
@@ -62,6 +63,23 @@ TVM_REGISTER_GLOBAL("nnvm.compiler._dict_keys")
     *rv = keys;
   });
 
+TVM_REGISTER_GLOBAL("nnvm.compiler._register_alter_op_layout")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  // Intentionally copy and not de-allocate it, to avoid free pyobject during shutdown
+  PackedFunc* f = new PackedFunc(args[1].operator PackedFunc());
+  Op& op = ::dmlc::Registry<nnvm::Op>::Get()->__REGISTER_OR_GET__(args[0]);
+  auto fpack = [f](const NodeAttrs& attrs,
+                   const Symbol& inputs,
+                   const Array<Tensor>& tinfos) {
+    TVMRetValue ret = (*f)(GetAttrDict(attrs), inputs, tinfos);
+    CHECK_EQ(ret.type_code(), tvm::runtime::extension_class_info<Symbol>::code)
+      << " expected " << "Symbol (code = " << tvm::runtime::extension_class_info<Symbol>::code
+      << ") but get code = " << ret.type_code();
+    return *(static_cast<Symbol*>(ret.value().v_handle));
+  };
+  op.set_attr<FTVMAlterOpLayout>("FTVMAlterOpLayout", fpack, args[2]);
+});
+
 // custom version of TVM compute
 TVM_REGISTER_GLOBAL("nnvm._register_compute")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
@@ -84,7 +102,7 @@ TVM_REGISTER_GLOBAL("nnvm._register_compute")
 
 TVM_REGISTER_GLOBAL("nnvm._register_schedule")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
-        // Intentionally copy and not de-allocate it, to avoid free pyobject during shutdown
+    // Intentionally copy and not de-allocate it, to avoid free pyobject during shutdown
     PackedFunc* f = new PackedFunc(args[1].operator PackedFunc());
     Op& op = ::dmlc::Registry<nnvm::Op>::Get()->__REGISTER_OR_GET__(args[0]);
     auto fschedule = [f](const NodeAttrs& attrs,
diff --git a/nnvm/src/compiler/simplify_inference.cc b/nnvm/src/compiler/simplify_inference.cc
index 141950b0545d..a0782222aa06 100644
--- a/nnvm/src/compiler/simplify_inference.cc
+++ b/nnvm/src/compiler/simplify_inference.cc
@@ -22,7 +22,8 @@ BatchNormToInferUnpack(const nnvm::NodeAttrs& attrs,
                        nnvm::NodeEntry beta,
                        nnvm::NodeEntry moving_mean,
                        nnvm::NodeEntry moving_var,
-                       TShape dshape) {
+                       TShape dshape,
+                       TShape bshape) {
   CHECK_NE(dshape.ndim(), 0);
   CHECK(attrs.op);
   static const  Op* bn_op = Op::Get("batch_norm");
@@ -60,13 +61,14 @@ BatchNormToInferUnpack(const nnvm::NodeAttrs& attrs,
         "elemwise_add", bn_name + "_add_beta", {shift, beta});
   }
   int axis = param.axis;
-  scale = ExpandBiasToMatchAxis(scale, dshape.ndim(), 1, axis);
-  shift = ExpandBiasToMatchAxis(shift, dshape.ndim(), 1, axis);
+  scale = ExpandBiasToMatchAxis(scale, dshape.ndim()-bshape.ndim()+1, 1, axis);
+  shift = ExpandBiasToMatchAxis(shift, dshape.ndim()-bshape.ndim()+1, 1, axis);
+
   NodeEntry out = MakeNode("broadcast_mul", bn_name + "_a_mul_data",
                            {data, scale});
   out = MakeNode("broadcast_add", bn_name + "_out",
                  {out, shift});
-  // It is invalid to ref the other values of BN after infernece transform.
+  // It is invalid to ref the other values of BN after inference transform.
   NodeEntry undef = MakeNode("__undef__", "undef", {});
   return {out, undef, undef};
 }
@@ -87,7 +89,8 @@ Graph SimplifyInference(nnvm::Graph src) {
           n->inputs[2],
           n->inputs[3],
           n->inputs[4],
-          shape_vec[idx.entry_id(nid, 0)]);
+          shape_vec[idx.entry_id(nid, 0)],
+          shape_vec[idx.entry_id(nid, 1)]);
       return true;
     } else if (n->op() == dropout_op) {
       NodeEntry undef = MakeNode("__undef__", "undef", {});
@@ -101,7 +104,8 @@ Graph SimplifyInference(nnvm::Graph src) {
 }
 
 NNVM_REGISTER_PASS(SimplifyInference)
-.set_body(SimplifyInference);
+.set_body(SimplifyInference)
+.set_change_graph(true);
 
 }  // namespace compiler
 }  // namespace nnvm
diff --git a/nnvm/src/pass/correct_layout.cc b/nnvm/src/pass/correct_layout.cc
new file mode 100644
index 000000000000..0aa7d74784ae
--- /dev/null
+++ b/nnvm/src/pass/correct_layout.cc
@@ -0,0 +1,169 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file correct_layout.cc
+ * \brief Infer and correct layout.
+ */
+#include <nnvm/graph.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/graph_attr_types.h>
+#include <nnvm/pass.h>
+#include <nnvm/layout.h>
+
+namespace nnvm {
+namespace pass {
+
+nnvm::NodePtr CreateLayoutTransformNode(const Layout& src,
+                                        const Layout& dst) {
+  static const nnvm::Op* trans_op = nnvm::Op::Get("__layout_transform__");
+  static int count = 0;
+  nnvm::NodePtr n = nnvm::Node::Create();
+  n->attrs.op = trans_op;
+  n->attrs.name = src.name() + "_to_" + dst.name() + std::to_string(count++);
+  n->attrs.dict["src_layout"] = src.name();
+  n->attrs.dict["dst_layout"] = dst.name();
+  n->op()->attr_parser(&(n->attrs));
+  return n;
+}
+
+using LayoutAttrDict = std::unordered_map<const Node*, std::vector<Layout> >;
+
+/*!
+ * \brief A simple layout infer pass that will
+ *        insert layout transform nodes automatically.
+ */
+nnvm::Graph CorrectLayout(nnvm::Graph src) {
+  static auto& op_infer_layout =
+    nnvm::Op::GetAttr<FInferLayout>("FInferLayout");
+
+  const IndexedGraph& idx = src.indexed_graph();
+  std::vector<nnvm::NodePtr> mirror_vec(idx.num_nodes(), nullptr);
+
+  // (new) NodePtr -> output_layouts
+  LayoutAttrDict new_layouts;
+
+  for (uint32_t nid = 0; nid < idx.num_nodes(); ++nid) {
+    const auto& inode = idx[nid];
+    nnvm::NodePtr new_node = nnvm::Node::Create();
+    *new_node = *(inode.source);
+    if (new_node->is_variable()) {
+      // Variable node. No operator. Only one output entry.
+      auto input_iter = std::find(
+        idx.input_nodes().cbegin(), idx.input_nodes().cend(), nid);
+      CHECK(input_iter != idx.input_nodes().cend());
+      int64_t input_id = std::distance(idx.input_nodes().cbegin(), input_iter);
+      if (src.HasAttr("layout_inputs")) {
+        new_layouts[new_node.get()] =
+          {src.GetAttr<std::vector<Layout> >("layout_inputs")[input_id]};
+      } else {
+        new_layouts[new_node.get()] = {Layout::Undef()};
+      }
+      mirror_vec[nid] = new_node;
+      continue;
+    }
+
+    const uint32_t num_inputs = inode.inputs.size();
+    const uint32_t num_outputs = inode.source->num_outputs();
+    // set up output and input layouts
+    std::vector<Layout> request_ilayouts(num_inputs, Layout::Undef());
+    for (size_t i = 0; i < num_inputs; ++i) {
+      const IndexedGraph::NodeEntry& input_entry = inode.inputs[i];
+      const NodePtr& new_input_node = mirror_vec[input_entry.node_id];
+      CHECK(new_input_node != nullptr);
+
+      // fill inputs by previous node (DFS order) inferred layouts.
+      const auto& layouts_iter = new_layouts.find(new_input_node.get());
+      CHECK(layouts_iter != new_layouts.end());
+      request_ilayouts[i] = layouts_iter->second[input_entry.index];
+    }
+    // layouts produced by previous node.
+    std::vector<Layout> produce_ilayouts(request_ilayouts);
+    // input layouts from last pass of LayoutTransform (if apply)
+    std::vector<Layout> last_request_ilayouts(num_inputs, Layout::Undef());
+    // fill outputs by last pass of LayoutTransform (if apply)
+    std::vector<Layout> produce_olayouts(num_outputs, Layout::Undef());
+    if (src.HasAttr("layout")) {
+      const auto& layouts = src.GetAttr<std::vector<Layout> >("layout");
+      for (uint32_t i = 0; i < num_outputs; ++i) {
+        produce_olayouts[i] = layouts[idx.entry_id(nid, i)];
+      }
+      for (uint32_t i = 0; i < num_inputs; ++i) {
+        last_request_ilayouts[i] = layouts[idx.entry_id(inode.inputs[i])];
+      }
+    }
+
+    const auto& flayout = op_infer_layout[new_node->op()];
+    CHECK(flayout != nullptr) << "Attribute FInferLayout"
+                              << " is not registered by op " << inode.source->op()->name
+                              << " we are not able to complete layout transform.";
+    CHECK(flayout(new_node->attrs, &request_ilayouts, &last_request_ilayouts, &produce_olayouts))
+        << "Layout infer fail";
+    CHECK_EQ(request_ilayouts.size(), num_inputs);
+    CHECK_EQ(produce_olayouts.size(), num_outputs);
+
+    // update new layouts
+    new_layouts[new_node.get()] = std::move(produce_olayouts);
+
+    for (uint32_t i = 0; i < inode.inputs.size(); ++i) {
+      const auto& e = inode.inputs[i];
+      const nnvm::NodePtr& in = mirror_vec[e.node_id];
+      new_node->inputs[i] = nnvm::NodeEntry{in, e.index, e.version};
+
+      // insert layout_transform if necessary
+      const Layout& produce = produce_ilayouts[i];
+      const Layout& request = request_ilayouts[i];
+      if (produce != request && produce.defined()) {
+        nnvm::NodePtr tnode = CreateLayoutTransformNode(produce, request);
+        tnode->attrs.name = idx[e.node_id].source->attrs.name + "_" + request.name();
+        tnode->inputs.emplace_back(new_node->inputs[i]);
+        nnvm::NodeEntry tnode_output{tnode, 0, 0};
+        new_node->inputs[i] = tnode_output;
+        // layout produced by LayoutTransformNode
+        new_layouts[tnode.get()] = {request};
+      } else if (!produce.defined()) {
+        // do reverse infer
+        new_layouts[in.get()][e.index] = request;
+      }
+    }
+    mirror_vec[nid] = new_node;
+  }
+
+  std::vector<nnvm::NodeEntry> outputs;
+  for (const auto& e : idx.outputs()) {
+    outputs.emplace_back(nnvm::NodeEntry{mirror_vec[e.node_id], e.index, e.version});
+  }
+
+  nnvm::Graph ret;
+  ret.outputs = outputs;
+  // restore the layouts to return graph
+  const auto& ret_idx = ret.indexed_graph();
+  std::vector<Layout> ret_layouts(ret_idx.num_node_entries(), Layout::Undef());
+  for (uint32_t nid = 0; nid < ret_idx.num_nodes(); ++nid) {
+    const auto& inode = ret_idx[nid];
+    const auto& layout_iter = new_layouts.find(inode.source);
+    if (layout_iter != new_layouts.end()) {
+      for (uint32_t i = 0; i < inode.source->num_outputs(); ++i) {
+        ret_layouts[ret_idx.entry_id(nid, i)] = std::move(layout_iter->second[i]);
+      }
+    }
+  }
+
+  // cannot call indexed_graph() before return the origin Graph,
+  // thus create a new one
+  nnvm::Graph new_ret;
+  new_ret.outputs = std::move(outputs);
+  new_ret.attrs["layout"] = std::make_shared<any>(std::move(ret_layouts));
+
+  return new_ret;
+}
+
+// register pass
+NNVM_REGISTER_PASS(CorrectLayout)
+.describe("Return a layout-transformed graph of src.")
+.set_body(CorrectLayout)
+.provide_graph_attr("layout")
+.set_change_graph(true);
+
+DMLC_JSON_ENABLE_ANY(LayoutVector, list_layout);
+
+}  // namespace pass
+}  // namespace nnvm
diff --git a/nnvm/src/pass/infer_shape_type.cc b/nnvm/src/pass/infer_shape_type.cc
index fd9b77c42c3f..cc4916ce0b9f 100644
--- a/nnvm/src/pass/infer_shape_type.cc
+++ b/nnvm/src/pass/infer_shape_type.cc
@@ -158,7 +158,7 @@ Graph InferAttr(Graph &&ret,
         } else {
           CHECK(!last_iter)
               << "Attribute " << infer_name
-              << " is not registed by op " << inode.source->op()->name
+              << " is not registered by op " << inode.source->op()->name
               << " we are not able to complete the inference because of this";
         }
       }
diff --git a/nnvm/src/top/elemwise_op_common.h b/nnvm/src/top/elemwise_op_common.h
index 7bcc262d3428..27a7c2f0e219 100644
--- a/nnvm/src/top/elemwise_op_common.h
+++ b/nnvm/src/top/elemwise_op_common.h
@@ -6,9 +6,12 @@
 #ifndef NNVM_TOP_ELEMWISE_OP_COMMON_H_
 #define NNVM_TOP_ELEMWISE_OP_COMMON_H_
 
+#include <nnvm/layout.h>
+#include <nnvm/top/nn.h>
 #include <string>
 #include <vector>
 #include <utility>
+#include <functional>
 #include "./op_common.h"
 
 namespace nnvm {
@@ -100,12 +103,176 @@ inline bool ElementWiseReduceType(const NodeAttrs& attrs,
     attrs, in_attrs, out_attrs, -1);
 }
 
+template<int n_in, int n_out>
+inline bool ElemwiseFixedLayout(const NodeAttrs& attrs,
+                                std::vector<Layout> *in_layouts,
+                                const std::vector<Layout> *last_in_layouts,
+                                std::vector<Layout> *out_layouts,
+                                const std::function<Layout(const Layout& in)>& finfer) {
+  const size_t in_size = (n_in == -1) ? in_layouts->size() : static_cast<size_t>(n_in);
+  const size_t out_size = (n_out == -1) ? out_layouts->size() : static_cast<size_t>(n_out);
+
+  auto deduce = [&](Layout *target, const std::vector<Layout> *vec,
+                    size_t size, const char *name) {
+    for (size_t i = 0; i < size; ++i) {
+      if (vec->at(i).defined()) {
+        if (!target->defined()) {
+          *target = vec->at(i);
+        }
+        CHECK_EQ(*target, vec->at(i))
+          << "Incompatible attr in node " << attrs.name << " at " << i << "-th "
+          << name << ": " << "expected " << *target
+          << ", got " << vec->at(i);
+      }
+    }
+  };
+
+  Layout in, last_in, out;
+  deduce(&in, in_layouts, in_size, "input");
+  deduce(&last_in, last_in_layouts, in_size, "input (last infer pass)");
+  deduce(&out, out_layouts, out_size, "output");
+
+  if (!last_in.defined()) {
+    last_in = in;
+  } else {
+    // else we copy in_layout produced by last infer pass to in_layout,
+    // and let LayoutTransform pass
+    // to insert an layout_transform node to fix the input layout.
+    in = last_in;
+  }
+
+  out = finfer(in);
+
+  auto write = [](std::vector<Layout> *vec, Layout& value, size_t size) {
+    for (size_t i = 0; i < size; ++i) {
+      vec->at(i) = value;
+    }
+  };
+  if (in.defined()) write(in_layouts, in, in_size);
+  if (out.defined()) write(out_layouts, out, out_size);
+
+  return true;
+}
+
+/*! \brief Fix the input layout as the previous inferred (if any) and copy to output */
+template<int n_in, int n_out>
+inline bool ElemwiseFixedLayoutCopyToOut(const NodeAttrs& attrs,
+                                         std::vector<Layout> *in_layouts,
+                                         const std::vector<Layout> *last_in_layouts,
+                                         std::vector<Layout> *out_layouts) {
+  return ElemwiseFixedLayout<n_in, n_out>(
+    attrs, in_layouts, last_in_layouts, out_layouts, [](const Layout& in) {
+    return in;
+  });
+}
+
+/*! \brief Fix the input layout as the previous inferred (if any) and do not define output */
+template<int n_in, int n_out>
+inline bool ElemwiseFixedLayoutUnknownOut(const NodeAttrs& attrs,
+                                          std::vector<Layout> *in_layouts,
+                                          const std::vector<Layout> *last_in_layouts,
+                                          std::vector<Layout> *out_layouts) {
+  return ElemwiseFixedLayout<n_in, n_out>(
+    attrs, in_layouts, last_in_layouts, out_layouts, [](const Layout& in) {
+    return Layout::Undef();
+  });
+}
+
+/*! \brief take arbitrary input layout and copy to output */
+template<int n_in, int n_out>
+inline bool ElemwiseArbitraryLayout(const NodeAttrs& attrs,
+                                    std::vector<Layout> *in_layouts,
+                                    const std::vector<Layout> *last_in_layouts,
+                                    std::vector<Layout> *out_layouts) {
+  const size_t in_size = (n_in == -1) ? in_layouts->size() : static_cast<size_t>(n_in);
+  const size_t out_size = (n_out == -1) ? out_layouts->size() : static_cast<size_t>(n_out);
+
+  Layout in;
+  for (size_t i = 0; i < in_size; ++i) {
+    if (!in.defined()) in = in_layouts->at(i);
+    CHECK_EQ(in, in_layouts->at(i))
+      << "Incompatible attr in node " << attrs.name << " at " << i
+      << "-th input: expected " << in
+      << ", got " << in_layouts->at(i);
+  }
+
+  if (in.defined()) {
+    for (size_t i = 0; i < out_size; ++i) {
+      out_layouts->at(i) = in;
+    }
+  }
+
+  return true;
+}
+
+/*!
+ * \brief try to convert right layout to left layout if they are different.
+ *        if the converting fails, it will use the last inferred layouts.
+ */
+inline bool ElemwiseBinaryKeepLeftLayout(const NodeAttrs& attrs,
+                                         std::vector<Layout> *in_layouts,
+                                         const std::vector<Layout> *last_in_layouts,
+                                         std::vector<Layout> *out_layouts) {
+  CHECK_EQ(in_layouts->size(), 2U);
+  CHECK_EQ(last_in_layouts->size(), 2U);
+  CHECK_EQ(out_layouts->size(), 1U);
+
+  const Layout& lhs_last = (*last_in_layouts)[0];
+  const Layout& rhs_last = (*last_in_layouts)[1];
+  CHECK((lhs_last.defined() && rhs_last.defined()) ||
+        (!lhs_last.defined() && !rhs_last.defined()));
+
+  const Layout& lhs = (*in_layouts)[0];
+  const Layout& rhs = (*in_layouts)[1];
+
+  if (!lhs.defined() && !rhs.defined()) {
+    CHECK(!lhs_last.defined() && !rhs_last.defined())
+      << "Lost input layouts in node " << attrs.name
+      << ": last inferred lhs=" << lhs_last << ", rhs=" << rhs_last;
+    return true;
+  } else if (!lhs.defined()) {
+    CHECK(!lhs_last.defined() && !rhs_last.defined());
+    in_layouts->at(0) = rhs;
+    out_layouts->at(0) = rhs;
+    return true;
+  } else if (!rhs.defined()) {
+    CHECK(!lhs_last.defined() && !rhs_last.defined());
+    in_layouts->at(1) = lhs;
+    out_layouts->at(0) = lhs;
+    return true;
+  }
+
+  if (lhs == rhs) {
+    // for same layout, we can always do binary calculation
+    // and pass the layout to next layer
+    out_layouts->at(0) = lhs;
+    return true;
+  }
+
+  if (rhs.convertible(lhs)) {
+    in_layouts->at(1) = lhs;
+    out_layouts->at(0) = lhs;
+  } else {
+    CHECK(lhs_last.defined() && rhs_last.defined())
+      << "Incompatible input layouts in node " << attrs.name
+      << ". lhs: " << lhs << ", rhs: " << rhs;
+    CHECK(lhs_last == rhs_last);
+    in_layouts->at(0) = lhs_last;
+    in_layouts->at(1) = rhs_last;
+    out_layouts->at(0) = lhs_last;
+  }
+
+  return true;
+}
+
 #define NNVM_REGISTER_ELEMWISE_UNARY_OP(name)                       \
   NNVM_REGISTER_OP(name)                                            \
   .set_num_inputs(1)                                                \
   .set_num_outputs(1)                                               \
   .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)        \
   .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)           \
+  .set_attr<FInferLayout>("FInferLayout",                           \
+    ElemwiseArbitraryLayout<1, 1>)                                  \
   .set_attr<FInplaceOption>("FInplaceOption",                       \
     [](const NodeAttrs& attrs){                                     \
       return std::vector<std::pair<int, int> >{{0, 0}};             \
@@ -131,6 +298,8 @@ inline bool ElementWiseReduceType(const NodeAttrs& attrs,
   .set_num_outputs(1)                                               \
   .set_attr<FInferShape>("FInferShape", ElemwiseShape<2, 1>)        \
   .set_attr<FInferType>("FInferType", ElemwiseType<2, 1>)           \
+  .set_attr<FInferLayout>("FInferLayout",                           \
+    ElemwiseBinaryKeepLeftLayout)                                   \
   .set_attr<FInplaceOption>("FInplaceOption",                       \
     [](const NodeAttrs& attrs) {                                    \
       return std::vector<std::pair<int, int> >{{0, 0}, {1, 0}};     \
@@ -150,6 +319,8 @@ inline bool ElementWiseReduceType(const NodeAttrs& attrs,
     ParamGetAttrDict<ElementWiseReduceParam>)                       \
   .set_attr<nnvm::FInferShape>("FInferShape",                       \
     ElementWiseReduceShape)                                         \
+  .set_attr<FInferLayout>("FInferLayout",                           \
+    ElemwiseFixedLayoutCopyToOut<1, 1>)                             \
   .set_attr<nnvm::FInferType>("FInferType", ElementWiseReduceType)  \
   .add_argument("args", "Symbol[]", "Positional input arguments")
 
@@ -166,6 +337,8 @@ inline bool ElementWiseReduceType(const NodeAttrs& attrs,
         static_cast<int>(kFloat32));                                \
       return true;                                                  \
   })                                                                \
+  .set_attr<FInferLayout>("FInferLayout",                           \
+    ElemwiseFixedLayoutUnknownOut<1, 1>)                            \
   .set_attr<FGradient>(                                             \
     "FGradient", [](const NodePtr& n,                               \
                     const std::vector<NodeEntry>& ograds) {         \
diff --git a/nnvm/src/top/nn/convolution.cc b/nnvm/src/top/nn/convolution.cc
index d517e7e5dbf6..dc7451e517a5 100644
--- a/nnvm/src/top/nn/convolution.cc
+++ b/nnvm/src/top/nn/convolution.cc
@@ -5,11 +5,22 @@
  */
 #include <nnvm/op.h>
 #include <nnvm/node.h>
+#include <nnvm/layout.h>
 #include <nnvm/op_attr_types.h>
 #include <nnvm/top/nn.h>
+#include <tvm/tensor.h>
+#include <tvm/packed_func_ext.h>
+#include <nnvm/compiler/op_attr_types.h>
+#include <tvm/tvm.h>
 #include "./nn_common.h"
 #include "../op_common.h"
 #include "../elemwise_op_common.h"
+#include "topi/nn.h"
+
+
+using tvm::Tensor;
+using tvm::Array;
+using nnvm::compiler::FTVMCompute;
 
 namespace nnvm {
 namespace top {
@@ -20,7 +31,26 @@ DMLC_REGISTER_PARAMETER(Conv2DParam);
 inline bool Conv2DInferShape(const nnvm::NodeAttrs& attrs,
                              std::vector<TShape>* in_shape,
                              std::vector<TShape>* out_shape) {
+  static const Layout kNCHW("NCHW");
+  static const Layout kOIHW("OIHW");
+
   const Conv2DParam& param = nnvm::get<Conv2DParam>(attrs.parsed);
+
+  const Layout in_layout(param.layout);
+  const Layout kernel_layout(param.kernel_layout);
+  CHECK(in_layout.convertible(kNCHW))
+    << "Conv only support input layouts that are convertible from NCHW."
+    << " But got " << in_layout;
+  CHECK(kernel_layout.convertible(kOIHW))
+    << "Conv only support kernel layouts that are convertible from OIHW."
+    << " But got "<< kernel_layout;
+
+  Layout out_layout(param.out_layout);
+  if (!out_layout.defined()) out_layout = in_layout;
+  CHECK(out_layout.convertible(kNCHW))
+    << "Conv only support output layouts that are convertible from NCHW."
+    << " But got " << out_layout;
+
   if (param.use_bias) {
     CHECK_EQ(in_shape->size(), 3U) << "Input:[data, weight, bias]";
   } else {
@@ -30,7 +60,7 @@ inline bool Conv2DInferShape(const nnvm::NodeAttrs& attrs,
 
   TShape dshape = in_shape->at(0);
   if (dshape.ndim() == 0) return false;
-  dshape = ConvertLayout(dshape, param.layout, kNCHW);
+  dshape = ConvertLayout(dshape, in_layout, kNCHW);
 
   CHECK_EQ(dshape.ndim(), 4U) << "Input data should be 4D";
   CHECK_EQ(param.kernel_size.ndim(), 2U);
@@ -48,13 +78,20 @@ inline bool Conv2DInferShape(const nnvm::NodeAttrs& attrs,
                  param.kernel_size[0],
                  param.kernel_size[1]});
 
-  wshape = ConvertLayout(wshape, kNCHW, param.layout, true);
+  wshape = ConvertLayout(wshape, kOIHW, kernel_layout);
   wshape[0] *= param.groups;
 
   NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, Conv2DParam::kWeight, wshape);
   if (param.use_bias) {
-    NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape,
-                            Conv2DParam::kBias, TShape({param.channels}));
+    static const Layout default_bias_layout("C");
+    TShape bias_shape({param.channels});
+    auto oc_block = out_layout.subsizeof('C');
+    if (oc_block > 0) {
+      size_t split_axis = (out_layout.indexof('C') < out_layout.indexof('c')) ? 1 : 0;
+      bias_shape = ConvertLayout(bias_shape, default_bias_layout,
+                                 default_bias_layout.split('C', split_axis, oc_block));
+    }
+    NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, Conv2DParam::kBias, bias_shape);
   }
   // dilation
   dim_t dilated_ksize_y = 1 + (param.kernel_size[0] - 1) * param.dilation[0];
@@ -66,12 +103,11 @@ inline bool Conv2DInferShape(const nnvm::NodeAttrs& attrs,
   if (dshape[3] != 0) {
     oshape[3] = (dshape[3] + param.padding[1] * 2 - dilated_ksize_x) / param.strides[1] + 1;
   }
-  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0,
-                           ConvertLayout(oshape, kNCHW, param.layout));
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, ConvertLayout(oshape, kNCHW, out_layout));
   // Perform incomplete shape inference. Fill in the missing values in data shape.
   // 1) We can always fill in the batch_size.
   // 2) We can back-calculate the input height/width if the corresponding stride is 1.
-  oshape = ConvertLayout((*out_shape)[0], param.layout, kNCHW);
+  oshape = ConvertLayout((*out_shape)[0], out_layout, kNCHW);
   dshape[0] = oshape[0];
   if (oshape[2] && param.strides[0] == 1) {
     dshape[2] = oshape[2] + dilated_ksize_y - 1 - 2 * param.padding[0];
@@ -80,7 +116,7 @@ inline bool Conv2DInferShape(const nnvm::NodeAttrs& attrs,
     dshape[3] = oshape[3] + dilated_ksize_x - 1 - 2 * param.padding[1];
   }
   NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, Conv2DParam::kData,
-                          ConvertLayout(dshape, kNCHW, param.layout));
+                          ConvertLayout(dshape, kNCHW, in_layout));
   // Check whether the kernel sizes are valid
   if (dshape[2] != 0) {
     CHECK_LE(dilated_ksize_y, dshape[2] + 2 * param.padding[0])
@@ -93,6 +129,41 @@ inline bool Conv2DInferShape(const nnvm::NodeAttrs& attrs,
   return true;
 }
 
+inline bool Conv2DInferLayout(const NodeAttrs& attrs,
+                              std::vector<Layout> *ilayouts,
+                              const std::vector<Layout> *last_ilayouts,
+                              std::vector<Layout> *olayouts) {
+  const Conv2DParam& param = nnvm::get<Conv2DParam>(attrs.parsed);
+
+  const Layout in_layout(param.layout);
+  Layout out_layout(param.out_layout);
+  if (!out_layout.defined()) out_layout = in_layout;
+
+  const Layout kernel_layout(param.kernel_layout);
+  if (param.use_bias) {
+    CHECK_EQ(ilayouts->size(), 3U) << "Input:[data, weight, bias]";
+    NNVM_ASSIGN_LAYOUT(*ilayouts, 0, in_layout);
+    NNVM_ASSIGN_LAYOUT(*ilayouts, 1, kernel_layout);
+    // automatically decide bias layout
+    Layout bias_layout("C");
+    auto oc_block = out_layout.subsizeof('C');
+    if (oc_block > 0) {
+      size_t split_axis = (out_layout.indexof('C') < out_layout.indexof('c')) ? 1 : 0;
+      bias_layout = bias_layout.split('C', split_axis, oc_block);
+    }
+    NNVM_ASSIGN_LAYOUT(*ilayouts, 2, bias_layout);
+  } else {
+    CHECK_EQ(ilayouts->size(), 2U) << "Input:[data, weight]";
+    NNVM_ASSIGN_LAYOUT(*ilayouts, 0, in_layout);
+    NNVM_ASSIGN_LAYOUT(*ilayouts, 1, kernel_layout);
+  }
+
+  CHECK_EQ(olayouts->size(), 1U);
+  NNVM_ASSIGN_LAYOUT(*olayouts, 0, out_layout);
+
+  return true;
+}
+
 NNVM_REGISTER_OP(conv2d)
 .describe(R"code(2D convolution layer (e.g. spatial convolution over images).
 
@@ -118,6 +189,7 @@ a bias vector is created and added to the outputs.
 .set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<Conv2DParam>)
 .set_attr<FInferShape>("FInferShape", Conv2DInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
+.set_attr<FInferLayout>("FInferLayout", Conv2DInferLayout)
 .set_num_outputs(1)
 .set_num_inputs(UseBiasNumInputs<Conv2DParam>)
 .set_support_level(2)
@@ -130,6 +202,23 @@ a bias vector is created and added to the outputs.
                         n->attrs.dict);
 });
 
+NNVM_REGISTER_OP(_contrib_conv2d_NCHWc)
+.describe(R"code(2D convolution layer (e.g. spatial convolution over images).
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "5D Tensor", "Packed input data.")
+.add_argument("weight", "6D Tensor", "Packed weight matrix.")
+.add_argument("bias", "1D Tensor", "Bias parameter.")
+.add_arguments(Conv2DParam::__FIELDS__())
+.set_attr_parser(ParamParser<Conv2DParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<Conv2DParam>)
+.set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<Conv2DParam>)
+.set_attr<FInferShape>("FInferShape", Conv2DInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
+.set_attr<FInferLayout>("FInferLayout", Conv2DInferLayout)
+.set_num_outputs(1)
+.set_num_inputs(UseBiasNumInputs<Conv2DParam>)
+.set_support_level(2);
+
 NNVM_REGISTER_OP(_conv2d_grad)
   .describe(R"code(2D convolution grad.
 
@@ -163,16 +252,21 @@ DMLC_REGISTER_PARAMETER(Conv2DTransposeParam);
 inline bool Conv2DTransposeInferShape(const nnvm::NodeAttrs& attrs,
                                       std::vector<TShape>* in_shape,
                                       std::vector<TShape>* out_shape) {
+  static const Layout kNCHW("NCHW");
+  static const Layout kOIHW("OIHW");
   const Conv2DTransposeParam& param = nnvm::get<Conv2DTransposeParam>(attrs.parsed);
+  const Layout layout(param.layout);
+  const Layout kernel_layout(param.kernel_layout);
   if (param.use_bias) {
     CHECK_EQ(in_shape->size(), 3U) << "Input:[data, weight, bias]";
   } else {
     CHECK_EQ(in_shape->size(), 2U) << "Input:[data, weight]";
   }
   CHECK_EQ(out_shape->size(), 1U);
+
   const TShape& dshape = (*in_shape)[Conv2DTransposeParam::kData];
   if (dshape.ndim() ==  0) return false;
-  TShape dshape_nchw = ConvertLayout(dshape, param.layout, kNCHW);
+  TShape dshape_nchw = ConvertLayout(dshape, layout, kNCHW);
 
   CHECK_EQ(dshape_nchw[1] % param.groups, 0U)
       << "input num_filter must divide group size";
@@ -189,7 +283,7 @@ inline bool Conv2DTransposeInferShape(const nnvm::NodeAttrs& attrs,
                  param.channels / param.groups,
                  param.kernel_size[0],
                  param.kernel_size[1]});
-  wshape = ConvertLayout(wshape, kNCHW, param.layout, true);
+  wshape = ConvertLayout(wshape, kOIHW, kernel_layout);
   NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, Conv2DTransposeParam::kWeight, wshape);
 
   if (param.use_bias) {
@@ -208,7 +302,33 @@ inline bool Conv2DTransposeInferShape(const nnvm::NodeAttrs& attrs,
   oshape[3] = (param.strides[1] * (dshape_nchw[3] - 1) + dilated_ksize_x -
                2 * param.padding[1] + param.output_padding[1]);
   NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0,
-                           ConvertLayout(oshape, kNCHW, param.layout));
+                           ConvertLayout(oshape, kNCHW, layout));
+  return true;
+}
+
+inline bool Conv2DTransposeInferLayout(const NodeAttrs& attrs,
+                                       std::vector<Layout> *ilayouts,
+                                       const std::vector<Layout> *last_ilayouts,
+                                       std::vector<Layout> *olayouts) {
+  const Conv2DTransposeParam& param = nnvm::get<Conv2DTransposeParam>(attrs.parsed);
+
+  const Layout in_layout(param.layout);
+
+  const Layout kernel_layout(param.kernel_layout);
+  if (param.use_bias) {
+    CHECK_EQ(ilayouts->size(), 3U) << "Input:[data, weight, bias]";
+    NNVM_ASSIGN_LAYOUT(*ilayouts, 0, in_layout);
+    NNVM_ASSIGN_LAYOUT(*ilayouts, 1, kernel_layout);
+    NNVM_ASSIGN_LAYOUT(*ilayouts, 2, Layout("C"));
+  } else {
+    CHECK_EQ(ilayouts->size(), 2U) << "Input:[data, weight]";
+    NNVM_ASSIGN_LAYOUT(*ilayouts, 0, in_layout);
+    NNVM_ASSIGN_LAYOUT(*ilayouts, 1, kernel_layout);
+  }
+
+  CHECK_EQ(olayouts->size(), 1U);
+  NNVM_ASSIGN_LAYOUT(*olayouts, 0, in_layout);
+
   return true;
 }
 
@@ -243,6 +363,7 @@ said convolution.
 .set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<Conv2DTransposeParam>)
 .set_attr<FInferShape>("FInferShape", Conv2DTransposeInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
+.set_attr<FInferLayout>("FInferLayout", Conv2DTransposeInferLayout)
 .set_num_outputs(1)
 .set_num_inputs(UseBiasNumInputs<Conv2DTransposeParam>)
 .set_support_level(2);
diff --git a/nnvm/src/top/nn/nn.cc b/nnvm/src/top/nn/nn.cc
index e3755d9529b2..219b5f7f9d9f 100644
--- a/nnvm/src/top/nn/nn.cc
+++ b/nnvm/src/top/nn/nn.cc
@@ -3,10 +3,12 @@
  * \file nn.cc
  * \brief Property def of nn operators.
  */
+#include <tvm/tvm.h>
 #include <tvm/expr.h>
 #include <tvm/packed_func_ext.h>
 #include <nnvm/op.h>
 #include <nnvm/node.h>
+#include <nnvm/layout.h>
 #include <nnvm/op_attr_types.h>
 #include <nnvm/compiler/op_attr_types.h>
 #include <nnvm/top/nn.h>
@@ -20,6 +22,8 @@
 namespace nnvm {
 namespace top {
 
+using tvm::Var;
+using tvm::Expr;
 using tvm::Tensor;
 using tvm::Array;
 using nnvm::compiler::FTVMCompute;
@@ -82,6 +86,8 @@ If ``use_bias`` is set to be false, then the ``bias`` term is ignored.
 .set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<DenseParam>)
 .set_attr<FInferShape>("FInferShape", DenseInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
+// leave weight & bias layout undefined
+.set_attr<FInferLayout>("FInferLayout", ElemwiseFixedLayoutCopyToOut<1, 1>)
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
@@ -161,6 +167,7 @@ NNVM_REGISTER_OP(dropout)
 .set_num_outputs(2)
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 2>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 2>)
+.set_attr<FInferLayout>("FInferLayout", ElemwiseArbitraryLayout<1, 1>)
 .set_attr<FNumVisibleOutputs>("FNumVisibleOutputs", [](const NodeAttrs& attrs) {
     return 1;
   })
@@ -184,13 +191,75 @@ inline bool BatchNormInferShape(const nnvm::NodeAttrs& attrs,
   CHECK((size_t)param.axis < dshape.Size());
 
   TShape bshape({dshape[param.axis]});
-  NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, 1, bshape);
-  NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, 2, bshape);
-  NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, 3, bshape);
-  NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, 4, bshape);
+  if (in_shape->at(1).ndim() == 0) in_shape->at(1) = bshape;
+  if (in_shape->at(2).ndim() == 0) in_shape->at(2) = bshape;
+  if (in_shape->at(3).ndim() == 0) in_shape->at(3) = bshape;
+  if (in_shape->at(4).ndim() == 0) in_shape->at(4) = bshape;
   NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, dshape);
-  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 1, bshape);
-  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 2, bshape);
+  out_shape->at(1) = in_shape->at(3);
+  out_shape->at(2) = in_shape->at(4);
+  return true;
+}
+
+inline bool BatchNormInferLayout(const NodeAttrs& attrs,
+                                 std::vector<Layout> *in_layouts,
+                                 const std::vector<Layout> *last_in_layouts,
+                                 std::vector<Layout> *out_layouts) {
+  const BatchNormParam& param = nnvm::get<BatchNormParam>(attrs.parsed);
+  CHECK_EQ(in_layouts->size(), 5U);
+  CHECK_EQ(last_in_layouts->size(), 5U);
+  CHECK_EQ(out_layouts->size(), 3U);
+
+  Layout data_layout = in_layouts->at(0);
+  const Layout& origin_data_layout = last_in_layouts->at(0);
+  Layout param_layout("C");
+  if (data_layout.defined()) {
+    if (data_layout.indexof('C') != param.axis) {
+      CHECK(origin_data_layout.defined())
+        << "Channel in data layout " << data_layout
+        << " is not at index " << param.axis;
+      // convert it to the original one.
+      data_layout = origin_data_layout;
+      NNVM_ASSIGN_LAYOUT(*in_layouts, 0, origin_data_layout);
+    } else if (data_layout.indexof('c') >= 0 &&
+               static_cast<uint32_t>(data_layout.indexof('c')) != (data_layout.ndim()-1)) {
+      CHECK(origin_data_layout.defined())
+        << "sub-channel c in data layout " << data_layout
+        << " does not at the final dimension";
+      // convert it to the original one.
+      data_layout = origin_data_layout;
+      NNVM_ASSIGN_LAYOUT(*in_layouts, 0, origin_data_layout);
+    } else {
+      for (Layout::LayoutDim axis : data_layout) {
+        if (Layout::is_subdim(axis) && axis != 'c') {
+          CHECK(origin_data_layout.defined())
+            << "sub-axis other than c appears in data layout " << data_layout;
+          // convert it to the original one.
+          data_layout = origin_data_layout;
+          NNVM_ASSIGN_LAYOUT(*in_layouts, 0, origin_data_layout);
+          break;
+        }
+      }
+    }
+
+    // decide the param layout
+    if (data_layout.defined()) {
+      auto channel_block = data_layout.subsizeof('C');
+      if (channel_block > 0) {
+        param_layout = param_layout.split('C', 1, channel_block);
+      }
+    }
+  }
+
+  NNVM_ASSIGN_LAYOUT(*in_layouts, 0, data_layout);
+  NNVM_ASSIGN_LAYOUT(*in_layouts, 1, param_layout);
+  NNVM_ASSIGN_LAYOUT(*in_layouts, 2, param_layout);
+  NNVM_ASSIGN_LAYOUT(*in_layouts, 3, param_layout);
+  NNVM_ASSIGN_LAYOUT(*in_layouts, 4, param_layout);
+
+  NNVM_ASSIGN_LAYOUT(*out_layouts, 0, data_layout);
+  NNVM_ASSIGN_LAYOUT(*out_layouts, 1, param_layout);
+  NNVM_ASSIGN_LAYOUT(*out_layouts, 2, param_layout);
   return true;
 }
 
@@ -238,6 +307,7 @@ axis to be the last item in the input shape.
 .add_arguments(BatchNormParam::__FIELDS__())
 .set_attr_parser(ParamParser<BatchNormParam>)
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<BatchNormParam>)
+.set_attr<FInferLayout>("FInferLayout", BatchNormInferLayout)
 .set_num_inputs(5)
 .set_num_outputs(3)
 .set_attr<FInferShape>("FInferShape", BatchNormInferShape)
@@ -275,6 +345,7 @@ NNVM_REGISTER_OP(softmax)
 .set_num_outputs(1)
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FInferLayout>("FInferLayout", ElemwiseArbitraryLayout<1, 1>)
 .set_support_level(1)
 .set_attr<FTVMCompute>(
   "FTVMCompute", [](const NodeAttrs& attrs,
@@ -331,6 +402,7 @@ NNVM_REGISTER_OP(log_softmax)
 .set_num_outputs(1)
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FInferLayout>("FInferLayout", ElemwiseArbitraryLayout<1, 1>)
 .set_attr<FTVMCompute>(
   "FTVMCompute", [](const NodeAttrs& attrs,
                     const Array<Tensor>& inputs,
@@ -388,6 +460,7 @@ NNVM_REGISTER_OP(leaky_relu)
 .set_num_outputs(1)
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FInferLayout>("FInferLayout", ElemwiseArbitraryLayout<1, 1>)
 .set_attr<FTVMCompute>(
   "FTVMCompute", [](const NodeAttrs& attrs,
                     const Array<Tensor>& inputs,
@@ -439,6 +512,30 @@ inline bool PReluInferShape(const nnvm::NodeAttrs &attrs,
   return true;
 }
 
+inline bool PReluInferLayout(const NodeAttrs& attrs,
+                             std::vector<Layout> *in_layouts,
+                             const std::vector<Layout> *last_in_layouts,
+                             std::vector<Layout> *out_layouts) {
+  const PReLUParam& param = nnvm::get<PReLUParam>(attrs.parsed);
+  CHECK_EQ(in_layouts->size(), 2U);
+  CHECK_EQ(last_in_layouts->size(), 2U);
+  CHECK_EQ(out_layouts->size(), 1U);
+
+  const Layout& data_layout = last_in_layouts->at(0).defined() ?
+                              last_in_layouts->at(0) : in_layouts->at(0);
+  if (data_layout.defined()) {
+    CHECK(data_layout.indexof('C') == param.axis && !data_layout.contains('c'))
+      << "Channel in data layout " << data_layout
+      << " is not at index " << param.axis;
+  }
+
+  NNVM_ASSIGN_LAYOUT(*in_layouts, 0, data_layout);
+  NNVM_ASSIGN_LAYOUT(*in_layouts, 1, Layout("C"));
+  NNVM_ASSIGN_LAYOUT(*out_layouts, 0, data_layout);
+
+  return true;
+}
+
 NNVM_REGISTER_OP(prelu)
 .describe(R"code(Parametric version of a Rectified Linear Unit.
 It accepts two arguments: an input ``x`` and a channelwise slope ``alpha``
@@ -453,6 +550,7 @@ where :math:`*` is an channelwise multiplication for each sample in the
 .set_num_inputs(2)
 .set_num_outputs(1)
 .set_attr<FInferShape>("FInferShape", PReluInferShape)
+.set_attr<FInferLayout>("FInferLayout", PReluInferLayout)
 .set_attr<FListInputNames>("FListInputNames", [](const NodeAttrs& attrs) {
     return std::vector<std::string>{"data", "alpha"};
   })
@@ -499,6 +597,7 @@ NNVM_REGISTER_OP(pad)
 .set_num_inputs(1)
 .set_attr<FInferShape>("FInferShape", PadInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FInferLayout>("FInferLayout", ElemwiseFixedLayoutCopyToOut<1, 1>)
 .set_attr<FTVMCompute>(
   "FTVMCompute", [](const NodeAttrs& attrs,
                     const Array<Tensor>& inputs,
@@ -520,5 +619,94 @@ NNVM_REGISTER_OP(pad)
 })
 .set_support_level(1);
 
+// layout transformer
+DMLC_REGISTER_PARAMETER(LayoutTransformParam);
+
+inline bool LayoutTransformInferShape(const NodeAttrs& attrs,
+                                      std::vector<TShape>* in_attrs,
+                                      std::vector<TShape>* out_attrs) {
+  CHECK_EQ(in_attrs->size(), 1U) << "Input: [data]";
+  CHECK_EQ(out_attrs->size(), 1U);
+  const LayoutTransformParam& param = nnvm::get<LayoutTransformParam>(attrs.parsed);
+  const TShape &dshape = (*in_attrs)[0];
+  if (dshape.ndim() == 0) return false;
+  const TShape &oshape = ConvertLayout(dshape,
+                                       Layout(param.src_layout),
+                                       Layout(param.dst_layout));
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, 0, oshape);
+  return true;
+}
+
+NNVM_REGISTER_OP(__layout_transform__)
+.describe(R"code(Transform the input data layout.
+
+For transforming from NCHW to N16cHWC, the `__layout_transform__` operator reshapes
+the input array by output[n, c, h, w, C] = data[n, C*16+c, h, w]
+
+)code" NNVM_ADD_FILELINE)
+.set_num_inputs(1)
+.set_num_outputs(1)
+.add_argument("data", "Tensor", "Input data.")
+.add_arguments(LayoutTransformParam::__FIELDS__())
+.set_attr_parser(ParamParser<LayoutTransformParam>)
+.set_attr<FInferShape>("FInferShape", LayoutTransformInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FInferLayout>(
+  "FInferLayout", [](const NodeAttrs& attrs,
+                     std::vector<Layout> *ilayouts,
+                     const std::vector<Layout> *last_ilayouts,
+                     std::vector<Layout> *olayouts) {
+    const LayoutTransformParam& param = nnvm::get<LayoutTransformParam>(attrs.parsed);
+    CHECK_EQ(ilayouts->size(), 1U);
+    CHECK_EQ(olayouts->size(), 1U);
+    NNVM_ASSIGN_LAYOUT(*ilayouts, 0, Layout(param.src_layout));
+    NNVM_ASSIGN_LAYOUT(*olayouts, 0, Layout(param.dst_layout));
+    return true;
+})
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& outputs) {
+    const LayoutTransformParam& param = nnvm::get<LayoutTransformParam>(attrs.parsed);
+
+    Layout src_layout(param.src_layout);
+    Layout dst_layout(param.dst_layout);
+
+    if (src_layout == dst_layout) {
+      return Array<Tensor>{ inputs[0] };
+    } else if (!src_layout.defined() || !dst_layout.defined()) {
+      LOG(FATAL) << "cannot convert from/to undefined layout";
+    }
+
+    CHECK(src_layout.convertible(dst_layout)) << "cannot convert from " << param.src_layout
+                                                << " to " << param.dst_layout;
+
+    return Array<Tensor> {
+      topi::layout_transform(inputs[0], outputs[0]->shape, [&](const Array<Var>& dst_indices) {
+        std::vector<Expr> dst_to_src_indices;
+        for (Layout::LayoutDim src_axis : src_layout) {
+          int dst_major_pos = dst_layout.indexof(Layout::to_superdim(src_axis));
+          int dst_minor_pos = dst_layout.indexof(Layout::to_subdim(src_axis));
+          int32_t src_factor = static_cast<int32_t>(src_layout.subsizeof(src_axis));
+          int32_t dst_factor = static_cast<int32_t>(dst_layout.subsizeof(src_axis));
+
+          Expr src_index(dst_indices[dst_major_pos]);
+          if (dst_minor_pos >= 0) {
+            CHECK_GT(dst_factor, 0);
+            src_index = src_index * dst_factor + dst_indices[dst_minor_pos];
+          }
+          if (Layout::is_superdim(src_axis) && src_factor > 0) {
+            src_index = src_index / src_factor;
+          } else if (Layout::is_subdim(src_axis) && src_factor > 0) {
+            src_index = src_index % src_factor;
+          }
+          dst_to_src_indices.push_back(src_index);
+        }
+        return Array<Expr>(dst_to_src_indices);
+      })
+    };
+})
+.set_support_level(1);
+
 }  // namespace top
 }  // namespace nnvm
diff --git a/nnvm/src/top/nn/nn_common.h b/nnvm/src/top/nn/nn_common.h
index e9176d17aa1d..49a020348485 100644
--- a/nnvm/src/top/nn/nn_common.h
+++ b/nnvm/src/top/nn/nn_common.h
@@ -8,6 +8,7 @@
 
 #include <dmlc/logging.h>
 #include <dmlc/parameter.h>
+#include <nnvm/layout.h>
 #include <nnvm/top/nn.h>
 #include <string>
 #include <vector>
@@ -40,100 +41,47 @@ inline std::vector<std::string> UseBiasListInputNames(const NodeAttrs& attrs) {
  * \param dst_layout target layout
  * \return shape in target layout
  */
-inline TShape ConvertLayout(TShape src, int src_layout, int dst_layout, bool is_weight = false) {
-  if (src_layout == dst_layout) return src;
-  TShape dst = src;
-  if (src.ndim() == 3) {
-    switch (src_layout) {
-      case kNCW: break;
-      case kNWC: {
-        std::swap(dst[1], dst[2]);
-        break;
-      }
-      default: {
-        LOG(FATAL) << "inavlid layout for 3d shape" << src_layout;
-      }
-    }
-    switch (dst_layout) {
-      case kNCW: break;
-      case kNWC: {
-        std::swap(dst[1], dst[2]);
-        break;
-      }
-      default: {
-        LOG(FATAL) << "inavlid layout for 3d shape" << dst_layout;
-      }
-    }
-  } else if (src.ndim() == 4) {
-    switch (src_layout) {
-      case kNCHW: break;
-      case kNHWC: {
-        if (is_weight) {
-           dst[2] = src[0];
-           dst[3] = src[1];
-           dst[1] = src[2];
-           dst[0] = src[3];
-        } else {
-           dst[2] = src[1];
-           dst[3] = src[2];
-           dst[1] = src[3];
-        }
-        break;
-      }
-      default: {
-        LOG(FATAL) << "inavlid layout for 4d shape" << src_layout;
-      }
-    }
-    src = dst;
-    switch (dst_layout) {
-      case kNCHW: break;
-      case kNHWC: {
-        if (is_weight) {
-            dst[0] = src[2];
-            dst[1] = src[3];
-            dst[2] = src[1];
-            dst[3] = src[0];
-        } else {
-            dst[1] = src[2];
-            dst[2] = src[3];
-            dst[3] = src[1];
-        }
-        break;
-      }
-      default: {
-        LOG(FATAL) << "inavlid layout for 4d shape" << dst_layout;
-      }
-    }
-  } else if (src.ndim() == 5) {
-    switch (src_layout) {
-      case kNCDHW: break;
-      case kNDHWC: {
-        dst[2] = src[1];
-        dst[3] = src[2];
-        dst[4] = src[3];
-        dst[1] = src[4];
-        break;
-      }
-      default: {
-        LOG(FATAL) << "inavlid layout for 5d shape" << src_layout;
-      }
-    }
-    src = dst;
-    switch (dst_layout) {
-      case kNCDHW: break;
-      case kNDHWC: {
-        dst[1] = src[2];
-        dst[2] = src[3];
-        dst[3] = src[4];
-        dst[4] = src[1];
-        break;
+inline TShape ConvertLayout(TShape src, const Layout& src_layout, const Layout& dst_layout) {
+  if (src_layout == dst_layout) {
+    return src;
+  } else if (!src_layout.defined()) {
+    LOG(FATAL) << "cannot convert undefined layout to " << dst_layout;
+  } else if (!dst_layout.defined()) {
+    LOG(FATAL) << "cannot convert " << src_layout << " to undefined layout";
+  }
+
+  CHECK(src_layout.convertible(dst_layout)) << "cannot convert from "
+                                            << src_layout << " to " << dst_layout;
+
+  TShape dst(dst_layout.ndim());
+  for (size_t i = 0; i < src_layout.ndim(); ++i) {
+    Layout::LayoutDim src_dim = src_layout[i];
+    if (Layout::is_superdim(src_dim)) {
+      int dst_major_pos = dst_layout.indexof(Layout::to_superdim(src_dim));
+      int dst_minor_pos = dst_layout.indexof(Layout::to_subdim(src_dim));
+      int src_minor_pos = src_layout.indexof(Layout::to_subdim(src_dim));
+      int src_factor = src_layout.subsizeof(src_dim);
+      int dst_factor = dst_layout.subsizeof(src_dim);
+
+      uint32_t src_dim_size = src[i];
+      if (src_minor_pos >= 0) {
+        CHECK_EQ(src_factor, src[src_minor_pos]) << "src shape " << src
+                                                 << " does not agree with layout " << src_layout;
+        src_dim_size *= src_factor;
       }
-      default: {
-        LOG(FATAL) << "inavlid layout for 5d shape" << dst_layout;
+
+      dst[dst_major_pos] = src_dim_size;
+      if (dst_minor_pos >= 0) {
+        CHECK_GT(dst_factor, 0);
+        CHECK_LE(dst_factor, src_dim_size) << "Converting " << src
+                                           << " from " << src_layout
+                                           << " to " << dst_factor
+                                           << ": cannot split dimension size of "
+                                           << src_dim_size << " by " << dst_factor;
+        dst[dst_major_pos] /= dst_factor;
+        dst[dst_minor_pos] = dst_factor;
       }
     }
-  } else {
-    LOG(FATAL) << "no layout option for " << dst.ndim() << " dimensions";
   }
   return dst;
 }
diff --git a/nnvm/src/top/nn/pooling.cc b/nnvm/src/top/nn/pooling.cc
index 10d9e27855cf..e32569ce3da5 100644
--- a/nnvm/src/top/nn/pooling.cc
+++ b/nnvm/src/top/nn/pooling.cc
@@ -30,34 +30,73 @@ inline bool Pool2DInferShape(const nnvm::NodeAttrs& attrs,
 
   TShape dshape = (*in_shape)[0];
   if (dshape.ndim() ==  0) return false;
-  dshape = ConvertLayout(dshape, param.layout, kNCHW);
+
+  CHECK_GE(dshape.ndim(), 2U)
+    << "Pool2D only support input >= 2-D: input must have height and width";
+
+  Layout layout(param.layout);
+  CHECK(layout.contains('H') && layout.contains('W') &&
+        !layout.contains('h') && !layout.contains('w'))
+    << "Invalid layout " << layout
+    << ". Pool2D layout must have H and W, which cannot be split";
+
+  const auto hidx = layout.indexof('H');
+  const auto widx = layout.indexof('W');
 
   TShape oshape = dshape;
-  CHECK_EQ(dshape.ndim(), 4U)
-      << "Pooling: Input data should be 4D";
-  CHECK(param.pool_size[0] <= dshape[2] + 2 * param.padding[0])
-      << "pool size (" << param.pool_size[0] << ") exceeds input (" << dshape[2]
-      << " padded to " << (dshape[2] + 2*param.padding[0]) << ")";
-  CHECK(param.pool_size[1] <= dshape[3] + 2 * param.padding[1])
-      << "pool size (" << param.pool_size[1] << ") exceeds input (" << dshape[3]
-      << " padded to " << (dshape[3] + 2*param.padding[1]) << ")";
+  CHECK(param.pool_size[0] <= dshape[hidx] + 2 * param.padding[0])
+      << "pool size (" << param.pool_size[0] << ") exceeds input (" << dshape[hidx]
+      << " padded to " << (dshape[hidx] + 2*param.padding[0]) << ")";
+  CHECK(param.pool_size[1] <= dshape[widx] + 2 * param.padding[1])
+      << "pool size (" << param.pool_size[1] << ") exceeds input (" << dshape[widx]
+      << " padded to " << (dshape[widx] + 2*param.padding[1]) << ")";
 
   if (!param.ceil_mode) {
-    oshape[2] = ((dshape[2] + 2 * param.padding[0] - param.pool_size[0]) /
-                 param.strides[0]) + 1;
-    oshape[3] = ((dshape[3] + 2 * param.padding[1] - param.pool_size[1]) /
-                 param.strides[1]) + 1;
+    oshape[hidx] = ((dshape[hidx] + 2 * param.padding[0] - param.pool_size[0]) /
+                    param.strides[0]) + 1;
+    oshape[widx] = ((dshape[widx] + 2 * param.padding[1] - param.pool_size[1]) /
+                    param.strides[1]) + 1;
   } else {
-    oshape[2] = ((dshape[2] + 2 * param.padding[0] - param.pool_size[0] +
-                  param.strides[0] - 1) / param.strides[0]) + 1;
-    oshape[3] = ((dshape[3] + 2 * param.padding[1] - param.pool_size[1] +
-                  param.strides[1] - 1) / param.strides[1]) + 1;
+    oshape[hidx] = ((dshape[hidx] + 2 * param.padding[0] - param.pool_size[0] +
+                    param.strides[0] - 1) / param.strides[0]) + 1;
+    oshape[widx] = ((dshape[3] + 2 * param.padding[1] - param.pool_size[1] +
+                    param.strides[1] - 1) / param.strides[1]) + 1;
   }
-  oshape = ConvertLayout(oshape, kNCHW, param.layout);
   NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, oshape);
   return true;
 }
 
+inline bool Pool2DInferLayout(const NodeAttrs& attrs,
+                              std::vector<Layout> *ilayouts,
+                              const std::vector<Layout> *last_ilayouts,
+                              std::vector<Layout> *olayouts) {
+  const Pool2DParam &param = nnvm::get<Pool2DParam>(attrs.parsed);
+  CHECK_EQ(ilayouts->size(), 1);
+  CHECK_EQ(last_ilayouts->size(), 1);
+  CHECK_EQ(olayouts->size(), 1);
+
+  Layout input = (*ilayouts)[0];
+  const Layout layout(param.layout);
+
+  if (input.defined()) {
+    CHECK(input.convertible(layout)) << "Invalid input layout " << input;
+    if (input.indexof('W') != layout.indexof('W') ||
+        input.indexof('H') != layout.indexof('H') ||
+        input.contains('w') || input.contains('h')) {
+      // as long as the index doesn't change for width and height
+      // pool2d can keep the input layout.
+      input = layout;
+    }
+  } else {
+    input = layout;
+  }
+
+  NNVM_ASSIGN_LAYOUT(*ilayouts, 0, input);
+  NNVM_ASSIGN_LAYOUT(*olayouts, 0, input);
+
+  return true;
+}
+
 NNVM_REGISTER_OP(max_pool2d)
 .describe(R"code(Max pooling operation for one dimensional data.
 
@@ -82,20 +121,29 @@ NNVM_REGISTER_OP(max_pool2d)
 .set_num_inputs(1)
 .set_attr<FInferShape>("FInferShape", Pool2DInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_attr<FTVMCompute>(
-  "FTVMCompute", [](const NodeAttrs& attrs,
-                    const Array<Tensor>& inputs,
-                    const Array<Tensor>& out_info) {
-    const Pool2DParam& param = nnvm::get<Pool2DParam>(attrs.parsed);
-    auto pool_size = ShapeToArray(param.pool_size);
-    auto strides = ShapeToArray(param.strides);
-    auto padding = ShapeToArray(param.padding);
-    auto ceil_mode = param.ceil_mode;
-    CHECK(param.layout == kNCHW || param.layout == kNHWC) << "Unsupported layout";
-    std::string layout = (param.layout == kNCHW ? "NCHW" : "NHWC");
-    return Array<Tensor>{
-      topi::nn::pool(inputs[0], pool_size, strides, padding, \
-                     topi::nn::kMaxPool, ceil_mode, layout) };
+.set_attr<FInferLayout>("FInferLayout", Pool2DInferLayout)
+.set_attr<FTVMCompute>("FTVMCompute", [](const NodeAttrs& attrs,
+                                         const Array<Tensor>& inputs,
+                                         const Array<Tensor>& out_info) {
+  const Pool2DParam& param = nnvm::get<Pool2DParam>(attrs.parsed);
+  auto pool_size = ShapeToArray(param.pool_size);
+  auto strides = ShapeToArray(param.strides);
+  auto padding = ShapeToArray(param.padding);
+  auto ceil_mode = param.ceil_mode;
+
+  Layout layout(param.layout);
+  CHECK(layout.convertible(Layout("NCHW")))
+    << "max_pool2d currently only supports layouts that are convertible from NCHW";
+  CHECK_EQ(layout.indexof('h'), -1) << "max_pool2d does not support input split on height";
+  CHECK_EQ(layout.indexof('w'), -1) << "max_pool2d does not support input split on width";
+
+  CHECK(inputs[0].ndim() == 4U || inputs[0].ndim() == 5U)
+    << "Pool2D only support 4-D input (e.g., NCHW)"
+    << " or 5-D input (last dimension is a split of channel)";
+
+  return Array<Tensor>{
+    topi::nn::pool(inputs[0], pool_size, strides, padding,
+                   topi::nn::kMaxPool, ceil_mode, layout.name())};
 })
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
@@ -144,20 +192,29 @@ NNVM_REGISTER_OP(avg_pool2d)
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<Pool2DParam>)
 .set_attr<FInferShape>("FInferShape", Pool2DInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_attr<FTVMCompute>(
-  "FTVMCompute", [](const NodeAttrs& attrs,
-                    const Array<Tensor>& inputs,
-                    const Array<Tensor>& out_info) {
-    const Pool2DParam& param = nnvm::get<Pool2DParam>(attrs.parsed);
-    auto pool_size = ShapeToArray(param.pool_size);
-    auto strides = ShapeToArray(param.strides);
-    auto padding = ShapeToArray(param.padding);
-    auto ceil_mode = param.ceil_mode;
-    CHECK(param.layout == kNCHW || param.layout == kNHWC) << "Unsupported layout";
-    std::string layout = (param.layout == kNCHW ? "NCHW" : "NHWC");
-    return Array<Tensor>{
-      topi::nn::pool(inputs[0], pool_size, strides, padding, \
-                     topi::nn::kAvgPool, ceil_mode, layout) };
+.set_attr<FInferLayout>("FInferLayout", Pool2DInferLayout)
+.set_attr<FTVMCompute>("FTVMCompute", [](const NodeAttrs& attrs,
+                                         const Array<Tensor>& inputs,
+                                         const Array<Tensor>& out_info) {
+  const Pool2DParam& param = nnvm::get<Pool2DParam>(attrs.parsed);
+  auto pool_size = ShapeToArray(param.pool_size);
+  auto strides = ShapeToArray(param.strides);
+  auto padding = ShapeToArray(param.padding);
+  auto ceil_mode = param.ceil_mode;
+
+  Layout layout(param.layout);
+  CHECK(layout.convertible(Layout("NCHW")))
+    << "avg_pool2d currently only supports layouts that are convertible from NCHW";
+  CHECK_EQ(layout.indexof('h'), -1) << "avg_pool2d does not support input split on height";
+  CHECK_EQ(layout.indexof('w'), -1) << "avg_pool2d does not support input split on width";
+
+  CHECK(inputs[0].ndim() == 4U || inputs[0].ndim() == 5U)
+    << "Pool2D only support 4-D input (e.g., NCHW)"
+    << " or 5-D input (last dimension is a split of channel)";
+
+  return Array<Tensor>{
+    topi::nn::pool(inputs[0], pool_size, strides, padding,
+                   topi::nn::kAvgPool, ceil_mode, layout.name())};
 })
 .set_num_outputs(1)
 .set_num_inputs(1)
@@ -169,19 +226,63 @@ DMLC_REGISTER_PARAMETER(GlobalPool2DParam);
 inline bool GlobalPool2DInferShape(const nnvm::NodeAttrs& attrs,
                                    std::vector<TShape>* in_shape,
                                    std::vector<TShape>* out_shape) {
+  static const Layout kNCHW("NCHW");
   const GlobalPool2DParam& param = nnvm::get<GlobalPool2DParam>(attrs.parsed);
   CHECK_EQ(in_shape->size(), 1U);
   CHECK_EQ(out_shape->size(), 1U);
+
   TShape dshape = (*in_shape)[0];
   if (dshape.ndim() ==  0) return false;
-  dshape = ConvertLayout(dshape, param.layout, kNCHW);
+
+  CHECK_GE(dshape.ndim(), 2U)
+    << "Pool2D only support input >= 2-D: input must have height and width";
+
+  Layout layout(param.layout);
+  CHECK(layout.contains('H') && layout.contains('W') &&
+        !layout.contains('h') && !layout.contains('w'))
+    << "Invalid layout " << layout
+    << ". Pool2D layout must have H and W, which cannot be split";
+
+  const auto hidx = layout.indexof('H');
+  const auto widx = layout.indexof('W');
+
   TShape oshape = dshape;
-  oshape[2] = oshape[3] = 1;
-  oshape = ConvertLayout(oshape, kNCHW, param.layout);
+  oshape[hidx] = oshape[widx] = 1;
   NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, oshape);
   return true;
 }
 
+inline bool GlobalPool2DInferLayout(const NodeAttrs& attrs,
+                                    std::vector<Layout> *ilayouts,
+                                    const std::vector<Layout> *last_ilayouts,
+                                    std::vector<Layout> *olayouts) {
+  const GlobalPool2DParam &param = nnvm::get<GlobalPool2DParam>(attrs.parsed);
+  CHECK_EQ(ilayouts->size(), 1);
+  CHECK_EQ(last_ilayouts->size(), 1);
+  CHECK_EQ(olayouts->size(), 1);
+
+  Layout input = (*ilayouts)[0];
+  const Layout layout(param.layout);
+
+  if (input.defined()) {
+    CHECK(input.convertible(layout)) << "Invalid input layout " << input;
+    if (input.indexof('W') != layout.indexof('W') ||
+        input.indexof('H') != layout.indexof('H') ||
+        input.contains('w') || input.contains('h')) {
+      // as long as the index doesn't change for width and height
+      // pool2d can keep the input layout.
+      input = layout;
+    }
+  } else {
+    input = layout;
+  }
+
+  NNVM_ASSIGN_LAYOUT(*ilayouts, 0, input);
+  NNVM_ASSIGN_LAYOUT(*olayouts, 0, input);
+
+  return true;
+}
+
 NNVM_REGISTER_OP(global_max_pool2d)
 .describe(R"code(Global max pooling operation for 2D data.
 
@@ -197,15 +298,26 @@ NNVM_REGISTER_OP(global_max_pool2d)
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<GlobalPool2DParam>)
 .set_attr<FInferShape>("FInferShape", GlobalPool2DInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FInferLayout>("FInferLayout", GlobalPool2DInferLayout)
 .set_attr<FTVMCompute>(
   "FTVMCompute", [](const NodeAttrs& attrs,
                     const Array<Tensor>& inputs,
                     const Array<Tensor>& out_info) {
-    const GlobalPool2DParam& param = nnvm::get<GlobalPool2DParam>(attrs.parsed);
-    CHECK_EQ(param.layout, kNCHW)
-      << "global_max_pool2d currently only supports NCHW layout";
-    return Array<Tensor>{
-      topi::nn::global_pool(inputs[0], topi::nn::kMaxPool) };
+  const GlobalPool2DParam& param = nnvm::get<GlobalPool2DParam>(attrs.parsed);
+  Layout layout(param.layout);
+  CHECK(layout.convertible(Layout("NCHW")))
+    << "global_max_pool2d currently only supports layouts that are convertible from NCHW";
+  CHECK_EQ(layout.indexof('h'), -1)
+    << "global_max_pool2d does not support input split on height";
+  CHECK_EQ(layout.indexof('w'), -1)
+    << "global_max_pool2d does not support input split on width";
+
+  CHECK(inputs[0].ndim() == 4U || inputs[0].ndim() == 5U)
+    << "Pool2D only support 4-D input (e.g., NCHW)"
+    << " or 5-D input (last dimension is a split of channel)";
+
+  return Array<Tensor>{
+    topi::nn::global_pool(inputs[0], topi::nn::kMaxPool, layout.name()) };
 })
 .set_num_outputs(1)
 .set_num_inputs(1)
@@ -227,15 +339,26 @@ NNVM_REGISTER_OP(global_avg_pool2d)
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<GlobalPool2DParam>)
 .set_attr<FInferShape>("FInferShape", GlobalPool2DInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FInferLayout>("FInferLayout", GlobalPool2DInferLayout)
 .set_attr<FTVMCompute>(
   "FTVMCompute", [](const NodeAttrs& attrs,
                     const Array<Tensor>& inputs,
                     const Array<Tensor>& out_info) {
-    const GlobalPool2DParam& param = nnvm::get<GlobalPool2DParam>(attrs.parsed);
-    CHECK_EQ(param.layout, kNCHW)
-      << "global_avg_pool2d currently only supports NCHW layout";
-    return Array<Tensor>{
-      topi::nn::global_pool(inputs[0], topi::nn::kAvgPool) };
+  const GlobalPool2DParam& param = nnvm::get<GlobalPool2DParam>(attrs.parsed);
+  Layout layout(param.layout);
+  CHECK(layout.convertible(Layout("NCHW")))
+    << "global_avg_pool2d currently only supports layouts that are convertible from NCHW";
+  CHECK_EQ(layout.indexof('h'), -1)
+    << "global_avg_pool2d does not support input split on height";
+  CHECK_EQ(layout.indexof('w'), -1)
+    << "global_avg_pool2d does not support input split on width";
+
+  CHECK(inputs[0].ndim() == 4U || inputs[0].ndim() == 5U)
+    << "Pool2D only support 4-D input (e.g., NCHW)"
+    << " or 5-D input (last dimension is a split of channel)";
+
+  return Array<Tensor>{
+    topi::nn::global_pool(inputs[0], topi::nn::kAvgPool, layout.name()) };
 })
 .set_num_outputs(1)
 .set_num_inputs(1)
diff --git a/nnvm/src/top/nn/upsampling.cc b/nnvm/src/top/nn/upsampling.cc
index 3195338c2223..0e0f3b274103 100644
--- a/nnvm/src/top/nn/upsampling.cc
+++ b/nnvm/src/top/nn/upsampling.cc
@@ -19,6 +19,7 @@ DMLC_REGISTER_PARAMETER(UpSamplingParam);
 inline bool UpSamplingInferShape(const nnvm::NodeAttrs& attrs,
                                    std::vector<TShape>* in_shape,
                                    std::vector<TShape>* out_shape) {
+  static const Layout kNCHW("NCHW");
   const UpSamplingParam& param = nnvm::get<UpSamplingParam>(attrs.parsed);
   CHECK_EQ(in_shape->size(), 1U);
   CHECK_EQ(out_shape->size(), 1U);
@@ -33,6 +34,19 @@ inline bool UpSamplingInferShape(const nnvm::NodeAttrs& attrs,
   return true;
 }
 
+inline bool UpsamplingLayout(const NodeAttrs& attrs,
+                             std::vector<Layout> *in_layouts,
+                             const std::vector<Layout> *last_in_layouts,
+                             std::vector<Layout> *out_layouts) {
+  const UpSamplingParam& param = nnvm::get<UpSamplingParam>(attrs.parsed);
+  CHECK_EQ(in_layouts->size(), 1U);
+  CHECK_EQ(out_layouts->size(), 1U);
+  const Layout layout(param.layout);
+  NNVM_ASSIGN_LAYOUT(*in_layouts, 0, layout);
+  NNVM_ASSIGN_LAYOUT(*out_layouts, 0, layout);
+  return true;
+}
+
 NNVM_REGISTER_OP(upsampling)
 .describe(R"(Perform nearest neighbor upsampling to input array.
 
@@ -46,6 +60,7 @@ NNVM_REGISTER_OP(upsampling)
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<UpSamplingParam>)
 .set_attr<FInferShape>("FInferShape", UpSamplingInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FInferLayout>("FInferLayout", UpsamplingLayout)
 .set_num_outputs(1)
 .set_num_inputs(1)
 .set_support_level(2);
diff --git a/nnvm/src/top/op_common.h b/nnvm/src/top/op_common.h
index ae4388ade1c0..826067ed50d7 100644
--- a/nnvm/src/top/op_common.h
+++ b/nnvm/src/top/op_common.h
@@ -203,6 +203,13 @@ inline std::string attr_assign_error_msg(const NodeAttrs& attrs,
     }                                                                    \
   }
 
+#define NNVM_ASSIGN_LAYOUT(outputs, index, layout)                       \
+  {                                                                      \
+    if (layout.defined()) {                                              \
+      (outputs)[index] = layout;                                         \
+    }                                                                    \
+  }
+
 /*!
  * \brief macro assign rhs shape to lhs
  *  Use macro so we can see the error file more clearly
@@ -253,6 +260,14 @@ inline bool ZeroShape(const NodeAttrs& attrs,
   }
 }
 
+// do not infer layout
+inline bool ZeroLayout(const NodeAttrs& attrs,
+                       std::vector<Layout> *in_layouts,
+                       const std::vector<Layout> *last_in_layouts,
+                       std::vector<Layout> *out_layouts) {
+  return true;
+}
+
 // simply assign output shape or type from input
 template<typename AttrType, int in_index, int out_index>
 inline bool AssignOutputAttr(const NodeAttrs& attrs,
diff --git a/nnvm/src/top/tensor/broadcast.cc b/nnvm/src/top/tensor/broadcast.cc
index 773281450193..3200f1aaf93f 100644
--- a/nnvm/src/top/tensor/broadcast.cc
+++ b/nnvm/src/top/tensor/broadcast.cc
@@ -11,6 +11,7 @@
 #include <nnvm/compiler/op_attr_types.h>
 #include <nnvm/compiler/util.h>
 #include <nnvm/top/tensor.h>
+#include <nnvm/top/nn.h>
 #include "../op_common.h"
 #include "../elemwise_op_common.h"
 #include "topi/broadcast.h"
@@ -74,6 +75,7 @@ So with `shape=(2,0)`, we will obtain the same result as in the above example.
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<BroadcastToParam>)
 .set_attr<FInferShape>("FInferShape", BroadcastToInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FInferLayout>("FInferLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
 .set_attr<FTVMCompute>(
   "FTVMCompute", [](const NodeAttrs& attrs,
     const Array<Tensor>& inputs,
@@ -115,7 +117,7 @@ inline bool BinaryBroadcastShape(const nnvm::NodeAttrs& attrs,
       } else {
         CHECK(l == 1 || r == 1)
           << "operands could not be broadcast together with shapes "
-          << lhs << " " << rhs;
+          << lhs << " " << rhs << ", l=" << l << ", r=" << r;
         out[i] = std::max(l, r);
       }
     } else {
@@ -126,6 +128,77 @@ inline bool BinaryBroadcastShape(const nnvm::NodeAttrs& attrs,
   return true;
 }
 
+inline bool BinaryBroadcastInferLayout(const NodeAttrs& attrs,
+                                       std::vector<Layout> *ilayouts,
+                                       const std::vector<Layout> *last_ilayouts,
+                                       std::vector<Layout> *olayouts) {
+  CHECK_EQ(ilayouts->size(), 2U);
+  CHECK_EQ(olayouts->size(), 1U);
+  Layout lhs = (*ilayouts)[0];
+  Layout rhs = (*ilayouts)[1];
+  Layout out(Layout::Undef());
+
+  if (lhs.defined() && rhs.defined()) {
+    if (lhs == rhs) {
+      NNVM_ASSIGN_LAYOUT(*olayouts, 0, lhs);
+      return true;
+    }
+    // For example, NCHW <-> CHW, N16nCH16cW <-> HCW16c, etc, are broadcast-convertible
+    // because as the definition, CHW can broadcast with NCHW.
+    // For the second case, we can convert HCW16c to CH16cW then it can broadcast with N16nCH16cW.
+    // But CNHW <-> CHW, NCHW16n <-> CHW are not,
+    // because not matter how we adjust the layout of 'CHW',
+    // we can never have an 'N' between 'C' and "HW".
+    size_t l_start = 0, r_start = 0;
+    size_t l = 0, r = 0;
+    bool find_first_match = false;
+    while (l < lhs.ndim() && r < rhs.ndim()) {
+      if (!rhs.contains(Layout::to_superdim(lhs[l]))) {
+        CHECK(!find_first_match) << lhs << " and " << rhs << " are not broadcast-convertible";
+        l_start = ++l;
+      } else if (!lhs.contains(Layout::to_superdim(rhs[r]))) {
+        CHECK(!find_first_match) << lhs << " and " << rhs << " are not broadcast-convertible";
+        r_start = ++r;
+      } else {
+        find_first_match = true;
+        ++l; ++r;
+      }
+    }
+    if (l_start > 0 && r_start > 0) {
+      LOG(FATAL) << lhs << " and " << rhs << " are not broadcast-convertible";
+    } else if (l_start > 0) {
+      rhs = lhs.sublayout(l_start, lhs.ndim()-l_start);
+      out = lhs;
+    } else if (r_start > 0) {
+      lhs = rhs.sublayout(r_start, rhs.ndim()-r_start);
+      out = rhs;
+    } else {
+      // prior to keep left layout
+      rhs = lhs;
+      out = lhs;
+    }
+  } else if (lhs.defined()) {
+    const Layout& last_lhs = last_ilayouts->at(0);
+    if (last_lhs.defined()) {
+      CHECK(lhs.convertible(last_lhs)) << "current lhs layout " << lhs
+                                       << " cannot be converted to the original one " << last_lhs;
+      lhs = last_lhs;
+      // cannot decide output layout
+    }
+  } else if (rhs.defined()) {
+    const Layout& last_rhs = last_ilayouts->at(1);
+    if (last_rhs.defined()) {
+      CHECK(rhs.convertible(last_rhs)) << "current rhs layout " << rhs
+                                       << " cannot be converted to the original one " << last_rhs;
+      rhs = last_rhs;
+      // cannot decide output layout
+    }
+  }
+  NNVM_ASSIGN_LAYOUT(*ilayouts, 0, lhs);
+  NNVM_ASSIGN_LAYOUT(*ilayouts, 1, rhs);
+  NNVM_ASSIGN_LAYOUT(*olayouts, 0, out);
+  return true;
+}
 
 #define NNVM_REGISTER_BINARY_BROADCAST_OP(name)                     \
   NNVM_REGISTER_OP(name)                                            \
@@ -133,6 +206,8 @@ inline bool BinaryBroadcastShape(const nnvm::NodeAttrs& attrs,
   .set_num_outputs(1)                                               \
   .set_attr<FInferShape>("FInferShape", BinaryBroadcastShape)       \
   .set_attr<FInferType>("FInferType", ElemwiseType<2, 1>)           \
+  .set_attr<FInferLayout>("FInferLayout",                           \
+    BinaryBroadcastInferLayout)                                     \
   .set_attr<FInplaceOption>("FInplaceOption",                       \
     [](const NodeAttrs& attrs) {                                    \
       return std::vector<std::pair<int, int> >{{0, 0}, {1, 0}};     \
diff --git a/nnvm/src/top/tensor/elemwise.cc b/nnvm/src/top/tensor/elemwise.cc
index 87fbf5823d9b..51a55e649601 100644
--- a/nnvm/src/top/tensor/elemwise.cc
+++ b/nnvm/src/top/tensor/elemwise.cc
@@ -333,6 +333,7 @@ NNVM_REGISTER_INIT_OP(full)
 .add_arguments(InitOpWithScalarParam::__FIELDS__())
 .set_attr<FInferShape>("FInferShape", ZeroShape<InitOpWithScalarParam>)
 .set_attr<FInferType>("FInferType", ZeroType<InitOpWithScalarParam>)
+.set_attr<FInferLayout>("FInferLayout", ZeroLayout)
 .set_support_level(4);
 
 NNVM_REGISTER_INIT_OP(zeros)
@@ -345,6 +346,7 @@ NNVM_REGISTER_INIT_OP(zeros)
 .add_arguments(InitOpParam::__FIELDS__())
 .set_attr<FInferShape>("FInferShape", ZeroShape<InitOpParam>)
 .set_attr<FInferType>("FInferType", ZeroType<InitOpParam>)
+.set_attr<FInferLayout>("FInferLayout", ZeroLayout)
 .set_support_level(4);
 
 NNVM_REGISTER_INIT_OP(ones)
@@ -357,6 +359,7 @@ NNVM_REGISTER_INIT_OP(ones)
 .add_arguments(InitOpParam::__FIELDS__())
 .set_attr<FInferShape>("FInferShape", ZeroShape<InitOpParam>)
 .set_attr<FInferType>("FInferType", ZeroType<InitOpParam>)
+.set_attr<FInferLayout>("FInferLayout", ZeroLayout)
 .set_support_level(4);
 
 // full_like
@@ -693,6 +696,7 @@ Example::
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<ClipParam>)
 .set_attr<nnvm::FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<nnvm::FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<nnvm::FInferLayout>("FInferLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
 .set_attr<FTVMCompute>(
   "FTVMCompute", [](const NodeAttrs& attrs,
                     const Array<Tensor>& inputs,
diff --git a/nnvm/src/top/tensor/matrix_op.cc b/nnvm/src/top/tensor/matrix_op.cc
index 149c609ee08d..d357267be40b 100644
--- a/nnvm/src/top/tensor/matrix_op.cc
+++ b/nnvm/src/top/tensor/matrix_op.cc
@@ -41,6 +41,31 @@ inline bool DotShape(const nnvm::NodeAttrs& attrs,
   return true;
 }
 
+inline bool DotInferLayout(const NodeAttrs& attrs,
+                           std::vector<Layout> *ilayouts,
+                           const std::vector<Layout> *last_ilayouts,
+                           std::vector<Layout> *olayouts) {
+  const MatMulParam& param = nnvm::get<MatMulParam>(attrs.parsed);
+  CHECK_EQ(ilayouts->size(), 2U);
+  CHECK_EQ(olayouts->size(), 1U);
+  const Layout& lhs = last_ilayouts->at(0).defined() ? last_ilayouts->at(0)
+                                                     : ilayouts->at(0);
+  const Layout& rhs = last_ilayouts->at(1).defined() ? last_ilayouts->at(1)
+                                                     : ilayouts->at(1);
+  NNVM_ASSIGN_LAYOUT(*ilayouts, 0, lhs);
+  NNVM_ASSIGN_LAYOUT(*ilayouts, 1, rhs);
+
+  if (lhs.ndim() > 1 && rhs.ndim() > 1) {
+    // concat lhs and rhs layout
+    const Layout& lhs_out = param.transpose_a ? lhs.reverse() : lhs;
+    const Layout& rhs_out = param.transpose_b ? rhs.reverse() : rhs;
+    Layout out = std::move(lhs_out.sublayout(0, lhs_out.ndim()-1) +
+                           rhs_out.sublayout(1, rhs_out.ndim()-1));
+    NNVM_ASSIGN_LAYOUT(*olayouts, 0, out);
+  }
+  return true;
+}
+
 NNVM_REGISTER_OP(matmul)
   .describe(R"doc(Matrix multiplication of two arrays.
 
@@ -67,6 +92,7 @@ NNVM_REGISTER_OP(matmul)
 .add_argument("rhs", "NDArray-or-Symbol", "The second input")
 .set_attr<FInferShape>("FInferShape", DotShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<2, 1>)
+.set_attr<FInferLayout>("FInferLayout", DotInferLayout)
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
diff --git a/nnvm/src/top/tensor/reduce.cc b/nnvm/src/top/tensor/reduce.cc
index 84a7dd0f0e12..d4049d362fb0 100644
--- a/nnvm/src/top/tensor/reduce.cc
+++ b/nnvm/src/top/tensor/reduce.cc
@@ -111,6 +111,8 @@ inline void AxesParamParser(nnvm::NodeAttrs* attrs) {
   .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<ReduceParam>) \
   .set_attr<FInferShape>("FInferShape", ReduceShape)                    \
   .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)               \
+  .set_attr<FInferLayout>("FInferLayout",                               \
+    ElemwiseFixedLayoutUnknownOut<1, 1>)                                \
   .set_num_inputs(1)                                                    \
   .set_num_outputs(1)
 
diff --git a/nnvm/src/top/tensor/state_op.cc b/nnvm/src/top/tensor/state_op.cc
index ebce07696fe4..9adbf6f36daa 100644
--- a/nnvm/src/top/tensor/state_op.cc
+++ b/nnvm/src/top/tensor/state_op.cc
@@ -45,6 +45,15 @@ This is an experimental operator.
     return Array<Tensor>{ topi::identity(inputs[1]) };
 })
 .set_attr<FInferShape>("FInferShape", SameShape)
+.set_attr<FInferLayout>(
+  "FInferLayout", [](const NodeAttrs& attrs,
+                     std::vector<Layout> *in_layouts,
+                     const std::vector<Layout> *last_in_layouts,
+                     std::vector<Layout> *out_layouts) {
+  NNVM_ASSIGN_LAYOUT(*in_layouts, 1, (*in_layouts)[0]);
+  NNVM_ASSIGN_LAYOUT(*out_layouts, 0, (*in_layouts)[0]);
+  return true;
+})
 .set_attr<FInplaceOption>(
   "FInplaceOption", [](const NodeAttrs& attrs) {
     return std::vector<std::pair<int, int> >{{1, 0}};
diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
index 2457747341c9..034fa957bf61 100644
--- a/nnvm/src/top/tensor/transform.cc
+++ b/nnvm/src/top/tensor/transform.cc
@@ -10,6 +10,7 @@
 #include <nnvm/compiler/util.h>
 #include <nnvm/top/tensor.h>
 #include <cctype>
+#include <sstream>
 #include "../op_common.h"
 #include "../elemwise_op_common.h"
 #include "topi/nn/flatten.h"
@@ -63,6 +64,7 @@ Example::
 .set_num_outputs(1)
 .set_attr<FInferShape>("FInferShape", FlattenInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FInferLayout>("FInferLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
 .add_argument("data", "Tensor", "Input data.")
 .set_attr<FTVMCompute>(
   "FTVMCompute", [](const NodeAttrs& attrs,
@@ -119,6 +121,22 @@ inline bool ConcatenateInferShape(const NodeAttrs& attrs,
   return dshape.Size() != 0;
 }
 
+inline bool ConcatenateInferLayout(const NodeAttrs& attrs,
+                                   std::vector<Layout> *ilayouts,
+                                   const std::vector<Layout> *last_ilayouts,
+                                   std::vector<Layout> *olayouts) {
+  CHECK_EQ(ilayouts->size(), last_ilayouts->size());
+  CHECK_EQ(olayouts->size(), 1U);
+
+  for (size_t i = 0; i < ilayouts->size(); ++i) {
+    const Layout& input = last_ilayouts->at(i).defined() ?
+                          last_ilayouts->at(i) : ilayouts->at(i);
+    NNVM_ASSIGN_LAYOUT(*ilayouts, i, input);
+  }
+
+  return true;
+}
+
 NNVM_REGISTER_OP(concatenate)
 .describe(R"code(Joins input arrays along a given axis.
 
@@ -156,6 +174,7 @@ Example::
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<ConcatenateParam>)
 .set_attr<FInferShape>("FInferShape", ConcatenateInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
+.set_attr<FInferLayout>("FInferLayout", ConcatenateInferLayout)
 .set_attr<FTVMCompute>(
   "FTVMCompute", [](const NodeAttrs& attrs,
                     const Array<Tensor>& inputs,
@@ -177,7 +196,8 @@ inline bool ExpandDimsInferShape(const NodeAttrs& attrs,
   CHECK_EQ(in_shape->size(), 1U);
   const TShape& dshape = in_shape->at(0);
   int ndim = static_cast<int>(dshape.ndim());
-  CHECK(param.axis >= -ndim - 1 && param.axis <= ndim);
+  CHECK(param.axis >= -ndim - 1 && param.axis <= ndim)
+    << "with axis = " << param.axis << " ndim = " << ndim;
   int axis = param.axis < 0 ? ndim + param.axis + 1 : param.axis;
   std::vector<dim_t> oshape;
   for (int i = 0; i < axis; ++i) {
@@ -198,7 +218,7 @@ NNVM_REGISTER_OP(expand_dims)
 .describe(R"code(Inserts a new axis of size 1 into the array shape
 
 For example, given ``x`` with shape ``(2,3,4)``, then ``expand_dims(x, axis=1, num_newaxis=5)``
-will return a new array with shape ``(2,5,3,4)``.
+will return a new array with shape ``(2,1,1,1,1,1,3,4)``.
 
 )code" NNVM_ADD_FILELINE)
 .add_argument("data", "Tensor", "Input tensor")
@@ -207,6 +227,7 @@ will return a new array with shape ``(2,5,3,4)``.
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<ExpandDimsParam>)
 .set_attr<FInferShape>("FInferShape", ExpandDimsInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FInferLayout>("FInferLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
 .set_num_inputs(1)
 .set_num_outputs(1)
 .set_attr<FTVMCompute>(
@@ -249,6 +270,8 @@ Examples::
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<IndicatorParam>)
 .set_attr<nnvm::FInferShape>("FInferShape", AssignOutputAttr<TShape, 1, 0>)
 .set_attr<nnvm::FInferType>("FInferType", ElemwiseType<2, 1>)
+// never transform layout of the second input array.
+.set_attr<FInferLayout>("FInferLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
 .set_num_inputs(2)
 .set_num_outputs(1)
 .set_attr<FGradient>(
@@ -345,6 +368,7 @@ along which to split the array.
 .set_attr_parser(SplitParamParser)
 .set_attr<FInferShape>("FInferShape", SplitInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, -1>)
+.set_attr<FInferLayout>("FInferLayout", ElemwiseFixedLayoutUnknownOut<1, -1>)
 .set_num_inputs(1)
 .set_num_outputs(SplitNumOutputs)
 .set_attr<FTVMCompute>(
@@ -387,6 +411,7 @@ NNVM_REGISTER_OP(cast)
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<CastParam>)
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", CastInferType)
+.set_attr<FInferLayout>("FInferLayout", ElemwiseArbitraryLayout<1, 1>)
 .set_num_inputs(1)
 .set_num_outputs(1)
 .set_support_level(1);
@@ -539,6 +564,7 @@ The significance of each is explained below:
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<ReshapeParam>)
 .set_attr<FInferShape>("FInferShape", ReshapeInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FInferLayout>("FInferLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
 .set_num_inputs(1)
 .set_num_outputs(1)
 .set_attr<FTVMCompute>(
@@ -578,6 +604,8 @@ the input array into an output array with the same shape as the second input arr
     return true;
 })
 .set_attr<FInferType>("FInferType", ElemwiseType<2, 1>)
+// never transform layout of the second input array.
+.set_attr<FInferLayout>("FInferLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
@@ -660,6 +688,7 @@ Examples::
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<SqueezeParam>)
 .set_attr<nnvm::FInferShape>("FInferShape", SqueezeShape)
 .set_attr<nnvm::FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FInferLayout>("FInferLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
 .set_num_inputs(1)
 .set_num_outputs(1)
 .set_attr<FTVMCompute>(
@@ -680,7 +709,7 @@ Examples::
 })
 .set_support_level(1);
 
-// tranpose
+// transpose
 DMLC_REGISTER_PARAMETER(TransposeParam);
 
 inline bool TransposeShape(const nnvm::NodeAttrs& attrs,
@@ -708,6 +737,39 @@ inline bool TransposeShape(const nnvm::NodeAttrs& attrs,
   return true;
 }
 
+inline bool TransposeInferLayout(const NodeAttrs& attrs,
+                                 std::vector<Layout> *ilayouts,
+                                 const std::vector<Layout> *last_ilayouts,
+                                 std::vector<Layout> *olayouts) {
+  const TransposeParam& param = nnvm::get<TransposeParam>(attrs.parsed);
+  CHECK_EQ(ilayouts->size(), 1U);
+  CHECK_EQ(olayouts->size(), 1U);
+
+  const Layout& input = last_ilayouts->at(0).defined()
+                        ? last_ilayouts->at(0)
+                        : ilayouts->at(0);
+
+  NNVM_ASSIGN_LAYOUT(*ilayouts, 0, input);
+
+  if (input.defined()) {
+    std::ostringstream new_layout;
+    if (param.axes.ndim() == 0) {
+      for (size_t i = 0; i < input.ndim(); ++i) {
+        new_layout << input.at(input.ndim() - 1 - i);
+      }
+    } else {
+      CHECK_EQ(input.ndim(), param.axes.ndim());
+      for (size_t i = 0; i < input.ndim(); ++i) {
+        CHECK(param.axes[i] < input.ndim());
+        new_layout << input.at(param.axes[i]);
+      }
+    }
+    NNVM_ASSIGN_LAYOUT(*olayouts, 0, Layout(new_layout.str()));
+  }
+
+  return true;
+}
+
 NNVM_REGISTER_OP(transpose)
 .describe(R"code(Permutes the dimensions of an array.
 
@@ -743,6 +805,7 @@ Examples::
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<TransposeParam>)
 .set_attr<nnvm::FInferShape>("FInferShape", TransposeShape)
 .set_attr<nnvm::FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FInferLayout>("FInferLayout", TransposeInferLayout)
 .set_num_inputs(1)
 .set_num_outputs(1)
 .set_support_level(4)
diff --git a/nnvm/tests/python/compiler/test_alter_op_layout.py b/nnvm/tests/python/compiler/test_alter_op_layout.py
new file mode 100644
index 000000000000..d921d4a646ee
--- /dev/null
+++ b/nnvm/tests/python/compiler/test_alter_op_layout.py
@@ -0,0 +1,49 @@
+"""Unittest cases for AlterOpLayout pass"""
+from nnvm import symbol as sym
+from nnvm.compiler import graph_attr
+from nnvm.top import registry as reg
+import nnvm.graph as graph
+
+def get_layouts(g):
+    ldict = {}
+    vlayout = g.json_attr("layout")
+    entry_ptr = g.index.entry_ptr
+    for i, n in enumerate(g.index.nodes):
+        begin, end = entry_ptr[i], entry_ptr[i + 1]
+        ldict[n["name"]] = vlayout[begin:end]
+    return ldict
+
+
+def test_alter_conv2d_layout():
+    data = sym.Variable("data", shape=(1, 32, 512, 512))
+    conv = sym.conv2d(data, name="conv", channels=16,
+                      kernel_size=(3,3), padding=(1,1),
+                      use_bias=False, layout="NCHW")
+    relu = sym.relu(conv, name="relu")
+    flatten = sym.flatten(relu, name="flatten")
+    softmax = sym.softmax(flatten, name="softmax")
+    g = graph.create(softmax)
+    g = g.apply("CorrectLayout")
+    g = graph_attr.set_dtype_inputs(g, "float32")
+    g = g.apply(["InferShape", "InferType"])
+    layouts_origin = get_layouts(g)
+
+    @reg.register_alter_op_layout("conv2d")
+    def alter_conv2d_layout(attrs, inputs, tinfos):
+        new_attrs = {k : attrs[k] for k in attrs.keys()}
+        new_attrs["layout"] = "NCHW16c"
+        new_attrs["kernel_layout"] = "NCHW16c"
+        new_attrs["name"] = "conv_alter"
+        return sym.conv2d(inputs[0], inputs[1], **new_attrs)
+
+    g = g.apply("AlterOpLayout")
+    layouts = get_layouts(g)
+
+    # check copy layouts
+    for node in ["data", "relu", "flatten", "softmax", "conv_weight"]:
+        assert(layouts[node] == layouts_origin[node])
+    assert(layouts["conv_alter"] == layouts_origin["conv"])
+
+
+if __name__ == "__main__":
+    test_alter_conv2d_layout()
diff --git a/nnvm/tests/python/compiler/test_nhwc_layout.py b/nnvm/tests/python/compiler/test_nhwc_layout.py
index 57e27db74751..96a8135435c3 100644
--- a/nnvm/tests/python/compiler/test_nhwc_layout.py
+++ b/nnvm/tests/python/compiler/test_nhwc_layout.py
@@ -5,9 +5,10 @@
 import nnvm.compiler
 from nnvm.testing.config import ctx_list
 
-def get_sym(layout, channels):
+def get_sym(layout, kernel_layout, channels):
     data = sym.Variable(name="data")
-    data = sym.conv2d(data=data, kernel_size=(3,3), channels=channels, padding=(1, 1), layout=layout, use_bias=True)
+    data = sym.conv2d(data=data, kernel_size=(3,3), channels=channels, padding=(1, 1),
+                      layout=layout, kernel_layout=kernel_layout, use_bias=True)
     data = sym.max_pool2d(data=data, pool_size=(2, 2), strides=(2, 2), layout=layout)
     data = sym.upsampling(data=data, scale=2, layout=layout)
     softmax_axis = 1
@@ -31,8 +32,8 @@ def build_and_run(sym, params, data, out_shape):
 def test_nhwc():
     data_shape = (1, 3, 224, 224)
     out_channel = 8
-    nchw_sym = get_sym("NCHW", out_channel)
-    nhwc_sym = get_sym("NHWC", out_channel)
+    nchw_sym = get_sym("NCHW", "OIHW", out_channel)
+    nhwc_sym = get_sym("NHWC", "HWIO", out_channel)
     conv_weight = np.random.uniform(-1, 1, (out_channel, 3, 3, 3)).astype(np.float32)
     conv_bias = np.random.uniform(-1, 1, (out_channel)).astype(np.float32)
     nchw_params = {
diff --git a/nnvm/tests/python/unittest/test_correct_layout.py b/nnvm/tests/python/unittest/test_correct_layout.py
new file mode 100644
index 000000000000..c428a2f837ac
--- /dev/null
+++ b/nnvm/tests/python/unittest/test_correct_layout.py
@@ -0,0 +1,338 @@
+import nnvm
+import nnvm.symbol as sym
+import nnvm.graph as graph
+from nnvm.compiler import graph_attr
+
+# Level 1
+def correct_layout(g, layout=None):
+    if isinstance(g, nnvm.symbol.Symbol):
+        g = graph.create(g)
+    if layout:
+        graph_attr.set_layout_inputs(g, layout)
+    g = g.apply("CorrectLayout")
+    ldict = {}
+    vlayout = g.json_attr("layout")
+    entry_ptr = g.index.entry_ptr
+    for i, n in enumerate(g.index.nodes):
+        begin, end = entry_ptr[i], entry_ptr[i + 1]
+        ldict[n["name"]] = vlayout[begin:end]
+    return g, ldict
+
+
+def test_dense():
+    x = sym.Variable("data", shape=(10, 20))
+    y = sym.dense(x, units=30, name="fc")
+    g, ldict = correct_layout(y, "HW")
+    assert(ldict["data"][0] == "HW")
+    assert(ldict["fc"][0] == "HW")
+    assert(ldict["fc_bias"][0] == "__undef__")
+    # second pass will insert layout transform
+    _, ldict = correct_layout(g, "HW16w")
+    assert(ldict["data"][0] == "HW16w")
+    assert(ldict["data_HW"][0] == "HW")
+    assert(ldict["fc"][0] == "HW")
+    assert(ldict["fc_bias"][0] == "__undef__")
+
+
+def test_matmul():
+    a = sym.Variable("a", shape=(10, 20))
+    b = sym.Variable("b", shape=(20, 30))
+    c = sym.matmul(a, b, name="matmul")
+    g, ldict = correct_layout(c, {"a" : "HW", "b" : "WC"})
+    assert(ldict["a"][0] == "HW")
+    assert(ldict["b"][0] == "WC")
+    assert(ldict["matmul"][0] == "HC")
+    # second pass will insert layout transform
+    _, ldict = correct_layout(g, {"a" : "HW16w", "b" : "WC16c"})
+    assert(ldict["a"][0] == "HW16w")
+    assert(ldict["a_HW"][0] == "HW")
+    assert(ldict["b"][0] == "WC16c")
+    assert(ldict["b_WC"][0] == "WC")
+    assert(ldict["matmul"][0] == "HC")
+    a = sym.Variable("a", shape=(20, 10))
+    c = sym.matmul(a, b, name="matmul", transpose_a=True)
+    g, ldict = correct_layout(c, {"a" : "HW", "b" : "HC"})
+    assert(ldict["a"][0] == "HW")
+    assert(ldict["b"][0] == "HC")
+    assert(ldict["matmul"][0] == "WC")
+    b = sym.Variable("b", shape=(30, 20))
+    c = sym.matmul(a, b, name="matmul", transpose_b=True)
+    g, ldict = correct_layout(c, {"a" : "HW", "b" : "CW"})
+    assert(ldict["a"][0] == "HW")
+    assert(ldict["b"][0] == "CW")
+    assert(ldict["matmul"][0] == "HC")
+    a = sym.Variable("a", shape=(20, 10))
+    b = sym.Variable("b", shape=(30, 20))
+    c = sym.matmul(a, b, name="matmul", transpose_a=True, transpose_b=True)
+    g, ldict = correct_layout(c, {"a" : "HW", "b" : "CH"})
+    assert(ldict["a"][0] == "HW")
+    assert(ldict["b"][0] == "CH")
+    assert(ldict["matmul"][0] == "WC")
+
+
+def test_concatenate():
+    x1 = sym.Variable("x", shape=(10, 20))
+    x2 = sym.Variable("y", shape=(10, 30))
+    z = sym.concatenate(x1, x2, name="concat")
+    g, ldict = correct_layout(z, {"x": "HW", "y": "HW"})
+    assert(ldict["x"][0] == "HW")
+    assert(ldict["y"][0] == "HW")
+    assert(ldict["concat"][0] == "__undef__")
+    # second pass will insert layout transform
+    _, ldict = correct_layout(g, {"x": "HW16w", "y": "HW16w"})
+    assert(ldict["x"][0] == "HW16w")
+    assert(ldict["y"][0] == "HW16w")
+    assert(ldict["x_HW"][0] == "HW")
+    assert(ldict["y_HW"][0] == "HW")
+    assert(ldict["concat"][0] == "__undef__")
+
+
+def test_expand_dims():
+    x = sym.Variable("x", shape=(10, 20))
+    y = sym.expand_dims(x, axis=1, name="y")
+    g, ldict = correct_layout(y, "HW")
+    assert(ldict["x"][0] == "HW")
+    assert(ldict["y"][0] == "__undef__")
+    # second pass will insert layout transform
+    _, ldict = correct_layout(g, "HW16w")
+    assert(ldict["x"][0] == "HW16w")
+    assert(ldict["x_HW"][0] == "HW")
+    assert(ldict["y"][0] == "__undef__")
+
+
+def test_split():
+    x = sym.Variable("x", shape=(10, 20))
+    y = sym.split(x, indices_or_sections=[11], name="y")
+    g, ldict = correct_layout(y, "HW")
+    assert(ldict["x"][0] == "HW")
+    assert(ldict["y"][0] == "__undef__")
+    # second pass will insert layout transform
+    _, ldict = correct_layout(g, "HW16w")
+    assert(ldict["x"][0] == "HW16w")
+    assert(ldict["x_HW"][0] == "HW")
+    assert(ldict["y"][0] == "__undef__")
+
+
+def test_batchnorm():
+    x = sym.Variable("data", shape=(10, 20, 30, 40))
+    y = sym.batch_norm(x, axis=1, epsilon=2e-5, name="bn")
+    g, ldict = correct_layout(y, "NCHW")
+    assert(ldict["data"][0] == "NCHW")
+    assert(ldict["bn"][0] == "NCHW")
+    assert(ldict["bn"][1] == "C")
+    assert(ldict["bn"][2] == "C")
+    assert(ldict["bn_beta"][0] == "C")
+    assert(ldict["bn_gamma"][0] == "C")
+    assert(ldict["bn_moving_mean"][0] == "C")
+    assert(ldict["bn_moving_var"][0] == "C")
+    # batch_norm can deal with sub-dim of C at the last dim.
+    g, ldict = correct_layout(g, "NCHW16c")
+    assert(ldict["data"][0] == "NCHW16c")
+    assert(ldict["bn"][0] == "NCHW16c")
+    assert(ldict["bn"][1] == "C16c")
+    assert(ldict["bn"][2] == "C16c")
+    assert(ldict["bn_beta"][0] == "C")
+    assert(ldict["bn_beta_C16c"][0] == "C16c")
+    assert(ldict["bn_gamma"][0] == "C")
+    assert(ldict["bn_gamma_C16c"][0] == "C16c")
+    assert(ldict["bn_moving_mean"][0] == "C")
+    assert(ldict["bn_moving_mean_C16c"][0] == "C16c")
+    assert(ldict["bn_moving_var"][0] == "C")
+    assert(ldict["bn_moving_var_C16c"][0] == "C16c")
+    # but for other layout, it does a layout transform for data
+    g, ldict = correct_layout(g, "NCH16cW")
+    assert(ldict["data"][0] == "NCH16cW")
+    assert(ldict["data_NCHW16c"][0] == "NCHW16c")
+    assert(ldict["bn"][0] == "NCHW16c")
+    assert(ldict["bn"][1] == "C16c")
+    assert(ldict["bn"][2] == "C16c")
+    assert(ldict["bn_beta"][0] == "C")
+    assert(ldict["bn_beta_C16c"][0] == "C16c")
+    assert(ldict["bn_gamma"][0] == "C")
+    assert(ldict["bn_gamma_C16c"][0] == "C16c")
+    assert(ldict["bn_moving_mean"][0] == "C")
+    assert(ldict["bn_moving_mean_C16c"][0] == "C16c")
+    assert(ldict["bn_moving_var"][0] == "C")
+    assert(ldict["bn_moving_var_C16c"][0] == "C16c")
+
+
+def test_flatten():
+    x = sym.Variable("x", shape=(10, 20, 10, 10))
+    y = sym.flatten(x, name="y")
+    g, ldict = correct_layout(y, "NCHW")
+    assert(ldict["x"][0] == "NCHW")
+    assert(ldict["y"][0] == "__undef__")
+    # second pass will insert layout transform
+    _, ldict = correct_layout(g, "NCHW16c")
+    assert(ldict["x"][0] == "NCHW16c")
+    assert(ldict["x_NCHW"][0] == "NCHW")
+    assert(ldict["y"][0] == "__undef__")
+
+
+# Level 2
+def test_conv2d():
+    x = sym.Variable("data", shape=(1, 32, 512, 512))
+    y = sym.conv2d(x, name="conv", channels=12,
+                   kernel_size=(3,3), padding=(1,1), layout="NCHW")
+    _, ldict = correct_layout(y)
+    assert(ldict["data"][0] == "NCHW")
+    assert(ldict["conv_weight"][0] == "OIHW")
+    assert(ldict["conv_bias"][0] == "C")
+    assert(ldict["conv"][0] == "NCHW")
+    y = sym.conv2d(x, name="conv", channels=12,
+                   kernel_size=(3,3), padding=(1,1), layout="NCHW16c",
+                   kernel_layout="OIHW16i16o", out_layout="NCHW8c")
+    _, ldict = correct_layout(y)
+    assert(ldict["data"][0] == "NCHW16c")
+    assert(ldict["conv_weight"][0] == "OIHW16i16o")
+    assert(ldict["conv_bias"][0] == "C8c")
+    assert(ldict["conv"][0] == "NCHW8c")
+    y = sym.conv2d(x, name="conv", channels=12,
+                   kernel_size=(3,3), padding=(1,1), layout="N16cHWC")
+    _, ldict = correct_layout(y)
+    assert(ldict["data"][0] == "N16cHWC")
+    assert(ldict["conv_weight"][0] == "OIHW")
+    assert(ldict["conv_bias"][0] == "16cC")
+    assert(ldict["conv"][0] == "N16cHWC")
+
+
+def test_conv2d_transpose():
+    x = sym.Variable("data", shape=(1, 32, 512, 512))
+    y = sym.conv2d_transpose(x, name="conv", channels=12,
+                             kernel_size=(3,3), padding=(1,1), layout="NCHW")
+    _, ldict = correct_layout(y)
+    assert(ldict["data"][0] == "NCHW")
+    assert(ldict["conv_weight"][0] == "OIHW")
+    assert(ldict["conv_bias"][0] == "C")
+    assert(ldict["conv"][0] == "NCHW")
+
+
+def test_max_pool2d():
+    x = sym.Variable("data", shape=(1, 32, 512, 512))
+    y = sym.max_pool2d(x, name="pool", pool_size=(3,3),
+                       padding=(1,1), layout="NCHW")
+    g, ldict = correct_layout(y)
+    assert(ldict["data"][0] == "NCHW")
+    assert(ldict["pool"][0] == "NCHW")
+    # if index of H and W remain the same,
+    # pool2d does not convert the layout.
+    g, ldict = correct_layout(g, "NCHW16c")
+    assert(ldict["data"][0] == "NCHW16c")
+    assert(ldict["pool"][0] == "NCHW16c")
+    # for other layout it requires a layout transform.
+    g, ldict = correct_layout(g, "NHWC")
+    assert(ldict["data"][0] == "NHWC")
+    assert(ldict["data_NCHW"][0] == "NCHW")
+    assert(ldict["pool"][0] == "NCHW")
+
+
+def test_global_pool2d():
+    x = sym.Variable("data", shape=(1, 32, 512, 512))
+    y = sym.global_max_pool2d(x, name="pool", layout="NCHW")
+    g, ldict = correct_layout(y)
+    assert(ldict["data"][0] == "NCHW")
+    assert(ldict["pool"][0] == "NCHW")
+    # if index of H and W remain the same,
+    # pool2d does not convert the layout.
+    g, ldict = correct_layout(g, "NCHW16c")
+    assert(ldict["data"][0] == "NCHW16c")
+    assert(ldict["pool"][0] == "NCHW16c")
+    # for other layout it requires a layout transform.
+    g, ldict = correct_layout(g, "NHWC")
+    assert(ldict["data"][0] == "NHWC")
+    assert(ldict["data_NCHW"][0] == "NCHW")
+    assert(ldict["pool"][0] == "NCHW")
+
+
+# Level 3
+def test_reshape():
+    x = sym.Variable("x", shape=(4,))
+    y = sym.reshape(x, shape=(2,2), name="y")
+    g, ldict = correct_layout(y, "C")
+    assert(ldict["x"][0] == "C")
+    assert(ldict["y"][0] == "__undef__")
+    # second pass will insert layout transform
+    g, ldict = correct_layout(g, "C16c")
+    assert(ldict["x"][0] == "C16c")
+    assert(ldict["x_C"][0] == "C")
+    assert(ldict["y"][0] == "__undef__")
+
+
+def test_transpose():
+    x = sym.Variable("x", shape=(1, 32, 512, 512))
+    y = sym.transpose(x, name="y", axes=(0, 2, 3, 1))
+    g, ldict = correct_layout(y, "NCHW")
+    assert(ldict["x"][0] == "NCHW")
+    assert(ldict["y"][0] == "NHWC")
+    # second pass will insert layout transform
+    g, ldict = correct_layout(g, "NCHW16c")
+    assert(ldict["x"][0] == "NCHW16c")
+    assert(ldict["x_NCHW"][0] == "NCHW")
+    assert(ldict["y"][0] == "NHWC")
+
+
+def test_broadcast_to():
+    x = sym.Variable("x", shape=(4, 1))
+    y = sym.broadcast_to(x, shape=(0, 4), name="y")
+    g, ldict = correct_layout(y, "HW")
+    assert(ldict["x"][0] == "HW")
+    assert(ldict["y"][0] == "__undef__")
+    # second pass will insert layout transform
+    g, ldict = correct_layout(g, "HW16h")
+    assert(ldict["x"][0] == "HW16h")
+    assert(ldict["x_HW"][0] == "HW")
+    assert(ldict["y"][0] == "__undef__")
+
+
+def test_broadcast_binary():
+    x = sym.Variable("x", shape=(1, 16, 512, 512))
+    y = sym.Variable("y", shape=(16, 512, 512))
+    z = sym.broadcast_add(x, y, name="z")
+    g, ldict = correct_layout(z, {"x": "NCHW", "y": "CHW"})
+    assert(ldict["x"][0] == "NCHW")
+    assert(ldict["y"][0] == "CHW")
+    assert(ldict["z"][0] == "NCHW")
+    # prior to keep the left layout if they do not match.
+    g, ldict = correct_layout(g, {"x": "NCHW16c", "y": "CHW"})
+    assert(ldict["x"][0] == "NCHW16c")
+    assert(ldict["y"][0] == "CHW")
+    assert(ldict["y_CHW16c"][0] == "CHW16c")
+    assert(ldict["z"][0] == "NCHW16c")
+    # broadcast_add(HCW16c, N16nCH16cW)
+    g, ldict = correct_layout(z, {"x": "HCW16c", "y": "N16nCH16cW"})
+    assert(ldict["x"][0] == "HCW16c")
+    assert(ldict["y"][0] == "N16nCH16cW")
+    assert(ldict["x_CH16cW"][0] == "CH16cW")
+    assert(ldict["z"][0] == "N16nCH16cW")
+
+
+def test_reduce():
+    x = sym.Variable("x", shape=(1, 16, 512, 512))
+    y = sym.sum(x, name="y", axis=1)
+    g, ldict = correct_layout(y, "NCHW")
+    assert(ldict["x"][0] == "NCHW")
+    assert(ldict["y"][0] == "__undef__")
+    # second pass will insert layout transform
+    g, ldict = correct_layout(g, "NCHW16c")
+    assert(ldict["x"][0] == "NCHW16c")
+    assert(ldict["x_NCHW"][0] == "NCHW")
+    assert(ldict["y"][0] == "__undef__")
+
+
+if __name__ == "__main__":
+    test_dense()
+    test_matmul()
+    test_concatenate()
+    test_expand_dims()
+    test_split()
+    test_batchnorm()
+    test_flatten()
+    test_conv2d()
+    test_conv2d_transpose()
+    test_max_pool2d()
+    test_global_pool2d()
+    test_reshape()
+    test_transpose()
+    test_broadcast_to()
+    test_broadcast_binary()
+    test_reduce()
\ No newline at end of file

From 896290ea96c7328b0bbab9ab1d639214e2ca9375 Mon Sep 17 00:00:00 2001
From: Yizhi Liu <liuyizhi@apache.org>
Date: Thu, 26 Apr 2018 14:22:39 -0700
Subject: [PATCH 637/949] Rename FInferLayout -> FCorrectLayout (#453)

* rename FInferLayout -> FCorrectLayout

* correct stupid IDE

* update submodule tvm
---
 nnvm/include/nnvm/graph_attr_types.h |  2 +-
 nnvm/include/nnvm/op_attr_types.h    |  4 ++--
 nnvm/src/pass/correct_layout.cc      |  4 ++--
 nnvm/src/top/elemwise_op_common.h    |  8 +++----
 nnvm/src/top/nn/convolution.cc       | 22 ++++++++---------
 nnvm/src/top/nn/nn.cc                | 36 ++++++++++++++--------------
 nnvm/src/top/nn/pooling.cc           | 24 +++++++++----------
 nnvm/src/top/nn/upsampling.cc        |  2 +-
 nnvm/src/top/tensor/broadcast.cc     | 14 +++++------
 nnvm/src/top/tensor/elemwise.cc      |  8 +++----
 nnvm/src/top/tensor/matrix_op.cc     | 12 +++++-----
 nnvm/src/top/tensor/reduce.cc        |  2 +-
 nnvm/src/top/tensor/state_op.cc      |  4 ++--
 nnvm/src/top/tensor/transform.cc     | 36 ++++++++++++++--------------
 14 files changed, 89 insertions(+), 89 deletions(-)

diff --git a/nnvm/include/nnvm/graph_attr_types.h b/nnvm/include/nnvm/graph_attr_types.h
index c8a9e7aad6b0..87a332f2c151 100644
--- a/nnvm/include/nnvm/graph_attr_types.h
+++ b/nnvm/include/nnvm/graph_attr_types.h
@@ -66,7 +66,7 @@ using DTypeVector = std::vector<int>;
  *  int entry_layout = layouts[g.indexed_graph().entry_id(my_entry)];
  * \endcode
  *
- * \sa FInferLayout
+ * \sa FCorrectLayout
  */
 using LayoutVector = std::vector<Layout>;
 
diff --git a/nnvm/include/nnvm/op_attr_types.h b/nnvm/include/nnvm/op_attr_types.h
index 9891b82f91e8..e58e9ceb3581 100644
--- a/nnvm/include/nnvm/op_attr_types.h
+++ b/nnvm/include/nnvm/op_attr_types.h
@@ -178,7 +178,7 @@ using FSetInputVarAttrOnCompose = std::function<void(
     const int index)>;
 
 /*!
- * \brief Inference function of node layout. See \p Layout for layout convention
+ * \brief Infer & correct function of node layout. See \p Layout for layout convention
  * \param attrs The attribute of the node.
  * \param ilayouts Given the input layouts produced by ancestor nodes,
  *                 it should be filled by layouts that the node requests.
@@ -196,7 +196,7 @@ using FSetInputVarAttrOnCompose = std::function<void(
  * \param olayouts Inferred output layouts.
  * \return success flag.
  */
-using FInferLayout = std::function<bool(
+using FCorrectLayout = std::function<bool(
     const NodeAttrs& attrs,
     std::vector<Layout> *ilayouts,
     const std::vector<Layout> *last_ilayouts,
diff --git a/nnvm/src/pass/correct_layout.cc b/nnvm/src/pass/correct_layout.cc
index 0aa7d74784ae..f065d5272965 100644
--- a/nnvm/src/pass/correct_layout.cc
+++ b/nnvm/src/pass/correct_layout.cc
@@ -33,7 +33,7 @@ using LayoutAttrDict = std::unordered_map<const Node*, std::vector<Layout> >;
  */
 nnvm::Graph CorrectLayout(nnvm::Graph src) {
   static auto& op_infer_layout =
-    nnvm::Op::GetAttr<FInferLayout>("FInferLayout");
+    nnvm::Op::GetAttr<FCorrectLayout>("FCorrectLayout");
 
   const IndexedGraph& idx = src.indexed_graph();
   std::vector<nnvm::NodePtr> mirror_vec(idx.num_nodes(), nullptr);
@@ -92,7 +92,7 @@ nnvm::Graph CorrectLayout(nnvm::Graph src) {
     }
 
     const auto& flayout = op_infer_layout[new_node->op()];
-    CHECK(flayout != nullptr) << "Attribute FInferLayout"
+    CHECK(flayout != nullptr) << "Attribute FCorrectLayout"
                               << " is not registered by op " << inode.source->op()->name
                               << " we are not able to complete layout transform.";
     CHECK(flayout(new_node->attrs, &request_ilayouts, &last_request_ilayouts, &produce_olayouts))
diff --git a/nnvm/src/top/elemwise_op_common.h b/nnvm/src/top/elemwise_op_common.h
index 27a7c2f0e219..18f92cb88f7e 100644
--- a/nnvm/src/top/elemwise_op_common.h
+++ b/nnvm/src/top/elemwise_op_common.h
@@ -271,7 +271,7 @@ inline bool ElemwiseBinaryKeepLeftLayout(const NodeAttrs& attrs,
   .set_num_outputs(1)                                               \
   .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)        \
   .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)           \
-  .set_attr<FInferLayout>("FInferLayout",                           \
+  .set_attr<FCorrectLayout>("FCorrectLayout",                       \
     ElemwiseArbitraryLayout<1, 1>)                                  \
   .set_attr<FInplaceOption>("FInplaceOption",                       \
     [](const NodeAttrs& attrs){                                     \
@@ -298,7 +298,7 @@ inline bool ElemwiseBinaryKeepLeftLayout(const NodeAttrs& attrs,
   .set_num_outputs(1)                                               \
   .set_attr<FInferShape>("FInferShape", ElemwiseShape<2, 1>)        \
   .set_attr<FInferType>("FInferType", ElemwiseType<2, 1>)           \
-  .set_attr<FInferLayout>("FInferLayout",                           \
+  .set_attr<FCorrectLayout>("FCorrectLayout",                       \
     ElemwiseBinaryKeepLeftLayout)                                   \
   .set_attr<FInplaceOption>("FInplaceOption",                       \
     [](const NodeAttrs& attrs) {                                    \
@@ -319,7 +319,7 @@ inline bool ElemwiseBinaryKeepLeftLayout(const NodeAttrs& attrs,
     ParamGetAttrDict<ElementWiseReduceParam>)                       \
   .set_attr<nnvm::FInferShape>("FInferShape",                       \
     ElementWiseReduceShape)                                         \
-  .set_attr<FInferLayout>("FInferLayout",                           \
+  .set_attr<FCorrectLayout>("FCorrectLayout",                       \
     ElemwiseFixedLayoutCopyToOut<1, 1>)                             \
   .set_attr<nnvm::FInferType>("FInferType", ElementWiseReduceType)  \
   .add_argument("args", "Symbol[]", "Positional input arguments")
@@ -337,7 +337,7 @@ inline bool ElemwiseBinaryKeepLeftLayout(const NodeAttrs& attrs,
         static_cast<int>(kFloat32));                                \
       return true;                                                  \
   })                                                                \
-  .set_attr<FInferLayout>("FInferLayout",                           \
+  .set_attr<FCorrectLayout>("FCorrectLayout",                       \
     ElemwiseFixedLayoutUnknownOut<1, 1>)                            \
   .set_attr<FGradient>(                                             \
     "FGradient", [](const NodePtr& n,                               \
diff --git a/nnvm/src/top/nn/convolution.cc b/nnvm/src/top/nn/convolution.cc
index dc7451e517a5..8b66f9757fe3 100644
--- a/nnvm/src/top/nn/convolution.cc
+++ b/nnvm/src/top/nn/convolution.cc
@@ -129,10 +129,10 @@ inline bool Conv2DInferShape(const nnvm::NodeAttrs& attrs,
   return true;
 }
 
-inline bool Conv2DInferLayout(const NodeAttrs& attrs,
-                              std::vector<Layout> *ilayouts,
-                              const std::vector<Layout> *last_ilayouts,
-                              std::vector<Layout> *olayouts) {
+inline bool Conv2DCorrectLayout(const NodeAttrs& attrs,
+                                std::vector<Layout> *ilayouts,
+                                const std::vector<Layout> *last_ilayouts,
+                                std::vector<Layout> *olayouts) {
   const Conv2DParam& param = nnvm::get<Conv2DParam>(attrs.parsed);
 
   const Layout in_layout(param.layout);
@@ -189,7 +189,7 @@ a bias vector is created and added to the outputs.
 .set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<Conv2DParam>)
 .set_attr<FInferShape>("FInferShape", Conv2DInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
-.set_attr<FInferLayout>("FInferLayout", Conv2DInferLayout)
+.set_attr<FCorrectLayout>("FCorrectLayout", Conv2DCorrectLayout)
 .set_num_outputs(1)
 .set_num_inputs(UseBiasNumInputs<Conv2DParam>)
 .set_support_level(2)
@@ -214,7 +214,7 @@ NNVM_REGISTER_OP(_contrib_conv2d_NCHWc)
 .set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<Conv2DParam>)
 .set_attr<FInferShape>("FInferShape", Conv2DInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
-.set_attr<FInferLayout>("FInferLayout", Conv2DInferLayout)
+.set_attr<FCorrectLayout>("FCorrectLayout", Conv2DCorrectLayout)
 .set_num_outputs(1)
 .set_num_inputs(UseBiasNumInputs<Conv2DParam>)
 .set_support_level(2);
@@ -306,10 +306,10 @@ inline bool Conv2DTransposeInferShape(const nnvm::NodeAttrs& attrs,
   return true;
 }
 
-inline bool Conv2DTransposeInferLayout(const NodeAttrs& attrs,
-                                       std::vector<Layout> *ilayouts,
-                                       const std::vector<Layout> *last_ilayouts,
-                                       std::vector<Layout> *olayouts) {
+inline bool Conv2DTransposeCorrectLayout(const NodeAttrs& attrs,
+                                         std::vector<Layout> *ilayouts,
+                                         const std::vector<Layout> *last_ilayouts,
+                                         std::vector<Layout> *olayouts) {
   const Conv2DTransposeParam& param = nnvm::get<Conv2DTransposeParam>(attrs.parsed);
 
   const Layout in_layout(param.layout);
@@ -363,7 +363,7 @@ said convolution.
 .set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<Conv2DTransposeParam>)
 .set_attr<FInferShape>("FInferShape", Conv2DTransposeInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
-.set_attr<FInferLayout>("FInferLayout", Conv2DTransposeInferLayout)
+.set_attr<FCorrectLayout>("FCorrectLayout", Conv2DTransposeCorrectLayout)
 .set_num_outputs(1)
 .set_num_inputs(UseBiasNumInputs<Conv2DTransposeParam>)
 .set_support_level(2);
diff --git a/nnvm/src/top/nn/nn.cc b/nnvm/src/top/nn/nn.cc
index 219b5f7f9d9f..f57f6bceebd4 100644
--- a/nnvm/src/top/nn/nn.cc
+++ b/nnvm/src/top/nn/nn.cc
@@ -87,7 +87,7 @@ If ``use_bias`` is set to be false, then the ``bias`` term is ignored.
 .set_attr<FInferShape>("FInferShape", DenseInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
 // leave weight & bias layout undefined
-.set_attr<FInferLayout>("FInferLayout", ElemwiseFixedLayoutCopyToOut<1, 1>)
+.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseFixedLayoutCopyToOut<1, 1>)
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
@@ -167,7 +167,7 @@ NNVM_REGISTER_OP(dropout)
 .set_num_outputs(2)
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 2>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 2>)
-.set_attr<FInferLayout>("FInferLayout", ElemwiseArbitraryLayout<1, 1>)
+.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseArbitraryLayout<1, 1>)
 .set_attr<FNumVisibleOutputs>("FNumVisibleOutputs", [](const NodeAttrs& attrs) {
     return 1;
   })
@@ -201,10 +201,10 @@ inline bool BatchNormInferShape(const nnvm::NodeAttrs& attrs,
   return true;
 }
 
-inline bool BatchNormInferLayout(const NodeAttrs& attrs,
-                                 std::vector<Layout> *in_layouts,
-                                 const std::vector<Layout> *last_in_layouts,
-                                 std::vector<Layout> *out_layouts) {
+inline bool BatchNormCorrectLayout(const NodeAttrs& attrs,
+                                   std::vector<Layout> *in_layouts,
+                                   const std::vector<Layout> *last_in_layouts,
+                                   std::vector<Layout> *out_layouts) {
   const BatchNormParam& param = nnvm::get<BatchNormParam>(attrs.parsed);
   CHECK_EQ(in_layouts->size(), 5U);
   CHECK_EQ(last_in_layouts->size(), 5U);
@@ -307,7 +307,7 @@ axis to be the last item in the input shape.
 .add_arguments(BatchNormParam::__FIELDS__())
 .set_attr_parser(ParamParser<BatchNormParam>)
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<BatchNormParam>)
-.set_attr<FInferLayout>("FInferLayout", BatchNormInferLayout)
+.set_attr<FCorrectLayout>("FCorrectLayout", BatchNormCorrectLayout)
 .set_num_inputs(5)
 .set_num_outputs(3)
 .set_attr<FInferShape>("FInferShape", BatchNormInferShape)
@@ -345,7 +345,7 @@ NNVM_REGISTER_OP(softmax)
 .set_num_outputs(1)
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_attr<FInferLayout>("FInferLayout", ElemwiseArbitraryLayout<1, 1>)
+.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseArbitraryLayout<1, 1>)
 .set_support_level(1)
 .set_attr<FTVMCompute>(
   "FTVMCompute", [](const NodeAttrs& attrs,
@@ -402,7 +402,7 @@ NNVM_REGISTER_OP(log_softmax)
 .set_num_outputs(1)
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_attr<FInferLayout>("FInferLayout", ElemwiseArbitraryLayout<1, 1>)
+.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseArbitraryLayout<1, 1>)
 .set_attr<FTVMCompute>(
   "FTVMCompute", [](const NodeAttrs& attrs,
                     const Array<Tensor>& inputs,
@@ -460,7 +460,7 @@ NNVM_REGISTER_OP(leaky_relu)
 .set_num_outputs(1)
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_attr<FInferLayout>("FInferLayout", ElemwiseArbitraryLayout<1, 1>)
+.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseArbitraryLayout<1, 1>)
 .set_attr<FTVMCompute>(
   "FTVMCompute", [](const NodeAttrs& attrs,
                     const Array<Tensor>& inputs,
@@ -512,10 +512,10 @@ inline bool PReluInferShape(const nnvm::NodeAttrs &attrs,
   return true;
 }
 
-inline bool PReluInferLayout(const NodeAttrs& attrs,
-                             std::vector<Layout> *in_layouts,
-                             const std::vector<Layout> *last_in_layouts,
-                             std::vector<Layout> *out_layouts) {
+inline bool PReluCorrectLayout(const NodeAttrs& attrs,
+                               std::vector<Layout> *in_layouts,
+                               const std::vector<Layout> *last_in_layouts,
+                               std::vector<Layout> *out_layouts) {
   const PReLUParam& param = nnvm::get<PReLUParam>(attrs.parsed);
   CHECK_EQ(in_layouts->size(), 2U);
   CHECK_EQ(last_in_layouts->size(), 2U);
@@ -550,7 +550,7 @@ where :math:`*` is an channelwise multiplication for each sample in the
 .set_num_inputs(2)
 .set_num_outputs(1)
 .set_attr<FInferShape>("FInferShape", PReluInferShape)
-.set_attr<FInferLayout>("FInferLayout", PReluInferLayout)
+.set_attr<FCorrectLayout>("FCorrectLayout", PReluCorrectLayout)
 .set_attr<FListInputNames>("FListInputNames", [](const NodeAttrs& attrs) {
     return std::vector<std::string>{"data", "alpha"};
   })
@@ -597,7 +597,7 @@ NNVM_REGISTER_OP(pad)
 .set_num_inputs(1)
 .set_attr<FInferShape>("FInferShape", PadInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_attr<FInferLayout>("FInferLayout", ElemwiseFixedLayoutCopyToOut<1, 1>)
+.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseFixedLayoutCopyToOut<1, 1>)
 .set_attr<FTVMCompute>(
   "FTVMCompute", [](const NodeAttrs& attrs,
                     const Array<Tensor>& inputs,
@@ -651,8 +651,8 @@ the input array by output[n, c, h, w, C] = data[n, C*16+c, h, w]
 .set_attr_parser(ParamParser<LayoutTransformParam>)
 .set_attr<FInferShape>("FInferShape", LayoutTransformInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_attr<FInferLayout>(
-  "FInferLayout", [](const NodeAttrs& attrs,
+.set_attr<FCorrectLayout>(
+  "FCorrectLayout", [](const NodeAttrs& attrs,
                      std::vector<Layout> *ilayouts,
                      const std::vector<Layout> *last_ilayouts,
                      std::vector<Layout> *olayouts) {
diff --git a/nnvm/src/top/nn/pooling.cc b/nnvm/src/top/nn/pooling.cc
index e32569ce3da5..0c40da520544 100644
--- a/nnvm/src/top/nn/pooling.cc
+++ b/nnvm/src/top/nn/pooling.cc
@@ -66,10 +66,10 @@ inline bool Pool2DInferShape(const nnvm::NodeAttrs& attrs,
   return true;
 }
 
-inline bool Pool2DInferLayout(const NodeAttrs& attrs,
-                              std::vector<Layout> *ilayouts,
-                              const std::vector<Layout> *last_ilayouts,
-                              std::vector<Layout> *olayouts) {
+inline bool Pool2DCorrectLayout(const NodeAttrs& attrs,
+                                std::vector<Layout> *ilayouts,
+                                const std::vector<Layout> *last_ilayouts,
+                                std::vector<Layout> *olayouts) {
   const Pool2DParam &param = nnvm::get<Pool2DParam>(attrs.parsed);
   CHECK_EQ(ilayouts->size(), 1);
   CHECK_EQ(last_ilayouts->size(), 1);
@@ -121,7 +121,7 @@ NNVM_REGISTER_OP(max_pool2d)
 .set_num_inputs(1)
 .set_attr<FInferShape>("FInferShape", Pool2DInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_attr<FInferLayout>("FInferLayout", Pool2DInferLayout)
+.set_attr<FCorrectLayout>("FCorrectLayout", Pool2DCorrectLayout)
 .set_attr<FTVMCompute>("FTVMCompute", [](const NodeAttrs& attrs,
                                          const Array<Tensor>& inputs,
                                          const Array<Tensor>& out_info) {
@@ -192,7 +192,7 @@ NNVM_REGISTER_OP(avg_pool2d)
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<Pool2DParam>)
 .set_attr<FInferShape>("FInferShape", Pool2DInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_attr<FInferLayout>("FInferLayout", Pool2DInferLayout)
+.set_attr<FCorrectLayout>("FCorrectLayout", Pool2DCorrectLayout)
 .set_attr<FTVMCompute>("FTVMCompute", [](const NodeAttrs& attrs,
                                          const Array<Tensor>& inputs,
                                          const Array<Tensor>& out_info) {
@@ -252,10 +252,10 @@ inline bool GlobalPool2DInferShape(const nnvm::NodeAttrs& attrs,
   return true;
 }
 
-inline bool GlobalPool2DInferLayout(const NodeAttrs& attrs,
-                                    std::vector<Layout> *ilayouts,
-                                    const std::vector<Layout> *last_ilayouts,
-                                    std::vector<Layout> *olayouts) {
+inline bool GlobalPool2DCorrectLayout(const NodeAttrs& attrs,
+                                      std::vector<Layout> *ilayouts,
+                                      const std::vector<Layout> *last_ilayouts,
+                                      std::vector<Layout> *olayouts) {
   const GlobalPool2DParam &param = nnvm::get<GlobalPool2DParam>(attrs.parsed);
   CHECK_EQ(ilayouts->size(), 1);
   CHECK_EQ(last_ilayouts->size(), 1);
@@ -298,7 +298,7 @@ NNVM_REGISTER_OP(global_max_pool2d)
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<GlobalPool2DParam>)
 .set_attr<FInferShape>("FInferShape", GlobalPool2DInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_attr<FInferLayout>("FInferLayout", GlobalPool2DInferLayout)
+.set_attr<FCorrectLayout>("FCorrectLayout", GlobalPool2DCorrectLayout)
 .set_attr<FTVMCompute>(
   "FTVMCompute", [](const NodeAttrs& attrs,
                     const Array<Tensor>& inputs,
@@ -339,7 +339,7 @@ NNVM_REGISTER_OP(global_avg_pool2d)
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<GlobalPool2DParam>)
 .set_attr<FInferShape>("FInferShape", GlobalPool2DInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_attr<FInferLayout>("FInferLayout", GlobalPool2DInferLayout)
+.set_attr<FCorrectLayout>("FCorrectLayout", GlobalPool2DCorrectLayout)
 .set_attr<FTVMCompute>(
   "FTVMCompute", [](const NodeAttrs& attrs,
                     const Array<Tensor>& inputs,
diff --git a/nnvm/src/top/nn/upsampling.cc b/nnvm/src/top/nn/upsampling.cc
index 0e0f3b274103..73c9bad97282 100644
--- a/nnvm/src/top/nn/upsampling.cc
+++ b/nnvm/src/top/nn/upsampling.cc
@@ -60,7 +60,7 @@ NNVM_REGISTER_OP(upsampling)
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<UpSamplingParam>)
 .set_attr<FInferShape>("FInferShape", UpSamplingInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_attr<FInferLayout>("FInferLayout", UpsamplingLayout)
+.set_attr<FCorrectLayout>("FCorrectLayout", UpsamplingLayout)
 .set_num_outputs(1)
 .set_num_inputs(1)
 .set_support_level(2);
diff --git a/nnvm/src/top/tensor/broadcast.cc b/nnvm/src/top/tensor/broadcast.cc
index 3200f1aaf93f..c9f871873cea 100644
--- a/nnvm/src/top/tensor/broadcast.cc
+++ b/nnvm/src/top/tensor/broadcast.cc
@@ -75,7 +75,7 @@ So with `shape=(2,0)`, we will obtain the same result as in the above example.
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<BroadcastToParam>)
 .set_attr<FInferShape>("FInferShape", BroadcastToInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_attr<FInferLayout>("FInferLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
+.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
 .set_attr<FTVMCompute>(
   "FTVMCompute", [](const NodeAttrs& attrs,
     const Array<Tensor>& inputs,
@@ -128,10 +128,10 @@ inline bool BinaryBroadcastShape(const nnvm::NodeAttrs& attrs,
   return true;
 }
 
-inline bool BinaryBroadcastInferLayout(const NodeAttrs& attrs,
-                                       std::vector<Layout> *ilayouts,
-                                       const std::vector<Layout> *last_ilayouts,
-                                       std::vector<Layout> *olayouts) {
+inline bool BinaryBroadcastCorrectLayout(const NodeAttrs& attrs,
+                                         std::vector<Layout> *ilayouts,
+                                         const std::vector<Layout> *last_ilayouts,
+                                         std::vector<Layout> *olayouts) {
   CHECK_EQ(ilayouts->size(), 2U);
   CHECK_EQ(olayouts->size(), 1U);
   Layout lhs = (*ilayouts)[0];
@@ -206,8 +206,8 @@ inline bool BinaryBroadcastInferLayout(const NodeAttrs& attrs,
   .set_num_outputs(1)                                               \
   .set_attr<FInferShape>("FInferShape", BinaryBroadcastShape)       \
   .set_attr<FInferType>("FInferType", ElemwiseType<2, 1>)           \
-  .set_attr<FInferLayout>("FInferLayout",                           \
-    BinaryBroadcastInferLayout)                                     \
+  .set_attr<FCorrectLayout>("FCorrectLayout",                       \
+    BinaryBroadcastCorrectLayout)                                   \
   .set_attr<FInplaceOption>("FInplaceOption",                       \
     [](const NodeAttrs& attrs) {                                    \
       return std::vector<std::pair<int, int> >{{0, 0}, {1, 0}};     \
diff --git a/nnvm/src/top/tensor/elemwise.cc b/nnvm/src/top/tensor/elemwise.cc
index 51a55e649601..ee0d6281c65d 100644
--- a/nnvm/src/top/tensor/elemwise.cc
+++ b/nnvm/src/top/tensor/elemwise.cc
@@ -333,7 +333,7 @@ NNVM_REGISTER_INIT_OP(full)
 .add_arguments(InitOpWithScalarParam::__FIELDS__())
 .set_attr<FInferShape>("FInferShape", ZeroShape<InitOpWithScalarParam>)
 .set_attr<FInferType>("FInferType", ZeroType<InitOpWithScalarParam>)
-.set_attr<FInferLayout>("FInferLayout", ZeroLayout)
+.set_attr<FCorrectLayout>("FCorrectLayout", ZeroLayout)
 .set_support_level(4);
 
 NNVM_REGISTER_INIT_OP(zeros)
@@ -346,7 +346,7 @@ NNVM_REGISTER_INIT_OP(zeros)
 .add_arguments(InitOpParam::__FIELDS__())
 .set_attr<FInferShape>("FInferShape", ZeroShape<InitOpParam>)
 .set_attr<FInferType>("FInferType", ZeroType<InitOpParam>)
-.set_attr<FInferLayout>("FInferLayout", ZeroLayout)
+.set_attr<FCorrectLayout>("FCorrectLayout", ZeroLayout)
 .set_support_level(4);
 
 NNVM_REGISTER_INIT_OP(ones)
@@ -359,7 +359,7 @@ NNVM_REGISTER_INIT_OP(ones)
 .add_arguments(InitOpParam::__FIELDS__())
 .set_attr<FInferShape>("FInferShape", ZeroShape<InitOpParam>)
 .set_attr<FInferType>("FInferType", ZeroType<InitOpParam>)
-.set_attr<FInferLayout>("FInferLayout", ZeroLayout)
+.set_attr<FCorrectLayout>("FCorrectLayout", ZeroLayout)
 .set_support_level(4);
 
 // full_like
@@ -696,7 +696,7 @@ Example::
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<ClipParam>)
 .set_attr<nnvm::FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<nnvm::FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_attr<nnvm::FInferLayout>("FInferLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
+.set_attr<nnvm::FCorrectLayout>("FCorrectLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
 .set_attr<FTVMCompute>(
   "FTVMCompute", [](const NodeAttrs& attrs,
                     const Array<Tensor>& inputs,
diff --git a/nnvm/src/top/tensor/matrix_op.cc b/nnvm/src/top/tensor/matrix_op.cc
index d357267be40b..760896733dd5 100644
--- a/nnvm/src/top/tensor/matrix_op.cc
+++ b/nnvm/src/top/tensor/matrix_op.cc
@@ -41,10 +41,10 @@ inline bool DotShape(const nnvm::NodeAttrs& attrs,
   return true;
 }
 
-inline bool DotInferLayout(const NodeAttrs& attrs,
-                           std::vector<Layout> *ilayouts,
-                           const std::vector<Layout> *last_ilayouts,
-                           std::vector<Layout> *olayouts) {
+inline bool DotCorrectLayout(const NodeAttrs& attrs,
+                             std::vector<Layout> *ilayouts,
+                             const std::vector<Layout> *last_ilayouts,
+                             std::vector<Layout> *olayouts) {
   const MatMulParam& param = nnvm::get<MatMulParam>(attrs.parsed);
   CHECK_EQ(ilayouts->size(), 2U);
   CHECK_EQ(olayouts->size(), 1U);
@@ -67,7 +67,7 @@ inline bool DotInferLayout(const NodeAttrs& attrs,
 }
 
 NNVM_REGISTER_OP(matmul)
-  .describe(R"doc(Matrix multiplication of two arrays.
+.describe(R"doc(Matrix multiplication of two arrays.
 
 ``dot``'s behavior depends on the input array dimensions:
 
@@ -92,7 +92,7 @@ NNVM_REGISTER_OP(matmul)
 .add_argument("rhs", "NDArray-or-Symbol", "The second input")
 .set_attr<FInferShape>("FInferShape", DotShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<2, 1>)
-.set_attr<FInferLayout>("FInferLayout", DotInferLayout)
+.set_attr<FCorrectLayout>("FCorrectLayout", DotCorrectLayout)
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
diff --git a/nnvm/src/top/tensor/reduce.cc b/nnvm/src/top/tensor/reduce.cc
index d4049d362fb0..00f14f52b5c3 100644
--- a/nnvm/src/top/tensor/reduce.cc
+++ b/nnvm/src/top/tensor/reduce.cc
@@ -111,7 +111,7 @@ inline void AxesParamParser(nnvm::NodeAttrs* attrs) {
   .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<ReduceParam>) \
   .set_attr<FInferShape>("FInferShape", ReduceShape)                    \
   .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)               \
-  .set_attr<FInferLayout>("FInferLayout",                               \
+  .set_attr<FCorrectLayout>("FCorrectLayout",                           \
     ElemwiseFixedLayoutUnknownOut<1, 1>)                                \
   .set_num_inputs(1)                                                    \
   .set_num_outputs(1)
diff --git a/nnvm/src/top/tensor/state_op.cc b/nnvm/src/top/tensor/state_op.cc
index 9adbf6f36daa..13c3563c4201 100644
--- a/nnvm/src/top/tensor/state_op.cc
+++ b/nnvm/src/top/tensor/state_op.cc
@@ -45,8 +45,8 @@ This is an experimental operator.
     return Array<Tensor>{ topi::identity(inputs[1]) };
 })
 .set_attr<FInferShape>("FInferShape", SameShape)
-.set_attr<FInferLayout>(
-  "FInferLayout", [](const NodeAttrs& attrs,
+.set_attr<FCorrectLayout>(
+  "FCorrectLayout", [](const NodeAttrs& attrs,
                      std::vector<Layout> *in_layouts,
                      const std::vector<Layout> *last_in_layouts,
                      std::vector<Layout> *out_layouts) {
diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
index 034fa957bf61..6957e7175946 100644
--- a/nnvm/src/top/tensor/transform.cc
+++ b/nnvm/src/top/tensor/transform.cc
@@ -64,7 +64,7 @@ Example::
 .set_num_outputs(1)
 .set_attr<FInferShape>("FInferShape", FlattenInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_attr<FInferLayout>("FInferLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
+.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
 .add_argument("data", "Tensor", "Input data.")
 .set_attr<FTVMCompute>(
   "FTVMCompute", [](const NodeAttrs& attrs,
@@ -121,10 +121,10 @@ inline bool ConcatenateInferShape(const NodeAttrs& attrs,
   return dshape.Size() != 0;
 }
 
-inline bool ConcatenateInferLayout(const NodeAttrs& attrs,
-                                   std::vector<Layout> *ilayouts,
-                                   const std::vector<Layout> *last_ilayouts,
-                                   std::vector<Layout> *olayouts) {
+inline bool ConcatenateCorrectLayout(const NodeAttrs& attrs,
+                                     std::vector<Layout> *ilayouts,
+                                     const std::vector<Layout> *last_ilayouts,
+                                     std::vector<Layout> *olayouts) {
   CHECK_EQ(ilayouts->size(), last_ilayouts->size());
   CHECK_EQ(olayouts->size(), 1U);
 
@@ -174,7 +174,7 @@ Example::
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<ConcatenateParam>)
 .set_attr<FInferShape>("FInferShape", ConcatenateInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
-.set_attr<FInferLayout>("FInferLayout", ConcatenateInferLayout)
+.set_attr<FCorrectLayout>("FCorrectLayout", ConcatenateCorrectLayout)
 .set_attr<FTVMCompute>(
   "FTVMCompute", [](const NodeAttrs& attrs,
                     const Array<Tensor>& inputs,
@@ -227,7 +227,7 @@ will return a new array with shape ``(2,1,1,1,1,1,3,4)``.
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<ExpandDimsParam>)
 .set_attr<FInferShape>("FInferShape", ExpandDimsInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_attr<FInferLayout>("FInferLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
+.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
 .set_num_inputs(1)
 .set_num_outputs(1)
 .set_attr<FTVMCompute>(
@@ -271,7 +271,7 @@ Examples::
 .set_attr<nnvm::FInferShape>("FInferShape", AssignOutputAttr<TShape, 1, 0>)
 .set_attr<nnvm::FInferType>("FInferType", ElemwiseType<2, 1>)
 // never transform layout of the second input array.
-.set_attr<FInferLayout>("FInferLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
+.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
 .set_num_inputs(2)
 .set_num_outputs(1)
 .set_attr<FGradient>(
@@ -368,7 +368,7 @@ along which to split the array.
 .set_attr_parser(SplitParamParser)
 .set_attr<FInferShape>("FInferShape", SplitInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, -1>)
-.set_attr<FInferLayout>("FInferLayout", ElemwiseFixedLayoutUnknownOut<1, -1>)
+.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseFixedLayoutUnknownOut<1, -1>)
 .set_num_inputs(1)
 .set_num_outputs(SplitNumOutputs)
 .set_attr<FTVMCompute>(
@@ -411,7 +411,7 @@ NNVM_REGISTER_OP(cast)
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<CastParam>)
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", CastInferType)
-.set_attr<FInferLayout>("FInferLayout", ElemwiseArbitraryLayout<1, 1>)
+.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseArbitraryLayout<1, 1>)
 .set_num_inputs(1)
 .set_num_outputs(1)
 .set_support_level(1);
@@ -564,7 +564,7 @@ The significance of each is explained below:
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<ReshapeParam>)
 .set_attr<FInferShape>("FInferShape", ReshapeInferShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_attr<FInferLayout>("FInferLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
+.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
 .set_num_inputs(1)
 .set_num_outputs(1)
 .set_attr<FTVMCompute>(
@@ -605,7 +605,7 @@ the input array into an output array with the same shape as the second input arr
 })
 .set_attr<FInferType>("FInferType", ElemwiseType<2, 1>)
 // never transform layout of the second input array.
-.set_attr<FInferLayout>("FInferLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
+.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
@@ -688,7 +688,7 @@ Examples::
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<SqueezeParam>)
 .set_attr<nnvm::FInferShape>("FInferShape", SqueezeShape)
 .set_attr<nnvm::FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_attr<FInferLayout>("FInferLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
+.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
 .set_num_inputs(1)
 .set_num_outputs(1)
 .set_attr<FTVMCompute>(
@@ -737,10 +737,10 @@ inline bool TransposeShape(const nnvm::NodeAttrs& attrs,
   return true;
 }
 
-inline bool TransposeInferLayout(const NodeAttrs& attrs,
-                                 std::vector<Layout> *ilayouts,
-                                 const std::vector<Layout> *last_ilayouts,
-                                 std::vector<Layout> *olayouts) {
+inline bool TransposeCorrectLayout(const NodeAttrs& attrs,
+                                   std::vector<Layout> *ilayouts,
+                                   const std::vector<Layout> *last_ilayouts,
+                                   std::vector<Layout> *olayouts) {
   const TransposeParam& param = nnvm::get<TransposeParam>(attrs.parsed);
   CHECK_EQ(ilayouts->size(), 1U);
   CHECK_EQ(olayouts->size(), 1U);
@@ -805,7 +805,7 @@ Examples::
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<TransposeParam>)
 .set_attr<nnvm::FInferShape>("FInferShape", TransposeShape)
 .set_attr<nnvm::FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_attr<FInferLayout>("FInferLayout", TransposeInferLayout)
+.set_attr<FCorrectLayout>("FCorrectLayout", TransposeCorrectLayout)
 .set_num_inputs(1)
 .set_num_outputs(1)
 .set_support_level(4)

From 9df847e20e785176c20bdebf69a7f62ebba5c568 Mon Sep 17 00:00:00 2001
From: Tatsuya Nishiyama <nishi-t@users.noreply.github.com>
Date: Fri, 27 Apr 2018 13:20:04 +0900
Subject: [PATCH 638/949] support dilation in conv2d (#439)

---
 nnvm/python/nnvm/top/nn.py                    | 13 ++++++---
 nnvm/tests/python/compiler/test_top_level2.py | 27 +++++++++++++++++++
 2 files changed, 37 insertions(+), 3 deletions(-)

diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index 7a5144983838..ae9853db7cff 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -85,11 +85,18 @@ def compute_conv2d(attrs, inputs, _):
     channels = attrs.get_int("channels")
     layout = attrs["layout"]
     assert layout == "NCHW" or layout == "NHWC"
-    assert dilation == (1, 1), "not support dilate now"
+    (dilation_h, dilation_w) = dilation
+    if dilation_h < 1 or dilation_w < 1:
+        raise ValueError("dilation should be positive value")
+    elif layout == "NCHW":
+        kernel = topi.nn.dilate(inputs[1], [1, 1, dilation_h, dilation_w])
+    else: #layout == NHWC
+        kernel = topi.nn.dilate(inputs[1], [1, dilation_h, dilation_w, 1])
+
     if groups == 1:
-        out = topi.nn.conv2d(inputs[0], inputs[1], strides, padding, layout)
+        out = topi.nn.conv2d(inputs[0], kernel, strides, padding, layout)
     elif groups == get_const_int(inputs[0].shape[1]) and groups == channels:
-        out = topi.nn.depthwise_conv2d_nchw(inputs[0], inputs[1], strides, padding)
+        out = topi.nn.depthwise_conv2d_nchw(inputs[0], kernel, strides, padding)
     else:
         raise ValueError("not support arbitrary group number for now")
     if attrs.get_bool("use_bias"):
diff --git a/nnvm/tests/python/compiler/test_top_level2.py b/nnvm/tests/python/compiler/test_top_level2.py
index 89c582fedefa..44767da4541f 100644
--- a/nnvm/tests/python/compiler/test_top_level2.py
+++ b/nnvm/tests/python/compiler/test_top_level2.py
@@ -32,6 +32,32 @@ def test_conv2d():
         np.testing.assert_allclose(out.asnumpy(), c_np, rtol=1e-5)
 
 
+def test_dilated_conv2d():
+    dilation = 3
+    x = sym.Variable("x")
+    y = sym.conv2d(x, channels=10, kernel_size=(3, 3), dilation=(dilation, dilation),
+                   name="y", padding=(1, 1))
+    dtype = "float32"
+    dshape = (1, 3, 18, 18)
+    kshape = (10, 3, 3, 3)
+    oshape = (1, 10, 14, 14)
+    shape_dict = {"x": dshape}
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
+        m = graph_runtime.create(graph, lib, ctx)
+        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+        bias = tvm.nd.array(np.random.uniform(size=kshape[0]).astype(dtype))
+        kernel_np = np.random.uniform(size=kshape).astype(dtype)
+        kernel = tvm.nd.array(kernel_np)
+        dkernel_np = topi.testing.dilate_python(kernel_np, (1, 1, dilation, dilation))
+        m.run(x=data, y_weight=kernel, y_bias=bias)
+        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
+        c_np = topi.testing.conv2d_nchw_python(
+            data.asnumpy(), dkernel_np, 1, 1)
+        c_np = c_np + bias.asnumpy().reshape(kshape[0], 1, 1)
+        np.testing.assert_allclose(out.asnumpy(), c_np, rtol=1e-5)
+
+
 def test_grouped_conv2d():
     x = sym.Variable("x")
     y = sym.conv2d(x, channels=32, kernel_size=(3,3), groups=32,
@@ -170,6 +196,7 @@ def test_upsampling():
 
 if __name__ == "__main__":
     test_conv2d()
+    test_dilated_conv2d()
     test_grouped_conv2d()
     test_conv2d_transpose()
     test_max_pool2d()

From 7df68fdf9a319656bc1592200e4bf81d9a41dae2 Mon Sep 17 00:00:00 2001
From: Liangfu Chen <chenclf@gmail.com>
Date: Fri, 27 Apr 2018 23:54:57 +0800
Subject: [PATCH 639/949] [DOCS] improve document of symbol; (#456)

---
 nnvm/docs/api/python/symbol.rst | 3 +++
 nnvm/python/nnvm/symbol.py      | 7 +++++++
 2 files changed, 10 insertions(+)

diff --git a/nnvm/docs/api/python/symbol.rst b/nnvm/docs/api/python/symbol.rst
index c3b20d085870..c341d2ef71d7 100644
--- a/nnvm/docs/api/python/symbol.rst
+++ b/nnvm/docs/api/python/symbol.rst
@@ -3,5 +3,8 @@ nnvm.symbol
 .. automodule:: nnvm.symbol
 
 .. autoclass:: nnvm.symbol.Symbol
+    :members:
+
+.. autoclass:: nnvm.symbol.Variable
 
 .. autofunction:: nnvm.symbol.Group
diff --git a/nnvm/python/nnvm/symbol.py b/nnvm/python/nnvm/symbol.py
index 9fa0af286af8..bc479a65e03b 100644
--- a/nnvm/python/nnvm/symbol.py
+++ b/nnvm/python/nnvm/symbol.py
@@ -47,6 +47,7 @@ def _tvm_handle(self):
         return self.handle.value
 
     def __add__(self, other):
+        """x.__add__(y) <=> x+y"""
         if isinstance(other, Symbol):
             return __add_symbol__(self, other)
         elif isinstance(other, _Number):
@@ -58,6 +59,7 @@ def __radd__(self, other):
         return self.__add__(other)
 
     def __sub__(self, other):
+        """x.__sub__(y) <=> x-y"""
         if isinstance(other, Symbol):
             return __sub_symbol__(self, other)
         if isinstance(other, _Number):
@@ -72,6 +74,7 @@ def __rsub__(self, other):
             raise TypeError('type %s not supported' % str(type(other)))
 
     def __mul__(self, other):
+        """x.__mul__(y) <=> x*y"""
         if isinstance(other, Symbol):
             return __mul_symbol__(self, other)
         if isinstance(other, _Number):
@@ -83,6 +86,7 @@ def __rmul__(self, other):
         return self.__mul__(other)
 
     def __div__(self, other):
+        """x.__div__(y) <=> x/y"""
         if isinstance(other, Symbol):
             return __div_symbol__(self, other)
         if isinstance(other, _Number):
@@ -103,6 +107,7 @@ def __rtruediv__(self, other):
         return self.__rdiv__(other)
 
     def __pow__(self, other):
+        """x.__pow__(y) <=> x**y"""
         if isinstance(other, Symbol):
             return __pow_symbol__(self, other)
         if isinstance(other, _Number):
@@ -117,12 +122,14 @@ def __rpow__(self, other):
             raise TypeError('type %s not supported' % str(type(other)))
 
     def __neg__(self):
+        """x.__neg__() <=> -x"""
         return self.__mul__(-1.0)
 
     def __copy__(self):
         return self.__deepcopy__()
 
     def __deepcopy__(self, _=None):
+        """Returns a deep copy of the input object."""
         handle = _base.SymbolHandle()
         _base.check_call(_LIB.NNSymbolCopy(self.handle,
                                            _ctypes.byref(handle)))

From dba0e07f3657311d93ae8858155256cf4e506ca3 Mon Sep 17 00:00:00 2001
From: Yizhi Liu <liuyizhi@apache.org>
Date: Fri, 27 Apr 2018 17:27:37 -0700
Subject: [PATCH 640/949] allow missing FCorrectLayout (#457)

* allow missing FCorrectLayout

* misunderstood OpMap[], fix
---
 nnvm/src/pass/correct_layout.cc | 17 ++++++++---------
 1 file changed, 8 insertions(+), 9 deletions(-)

diff --git a/nnvm/src/pass/correct_layout.cc b/nnvm/src/pass/correct_layout.cc
index f065d5272965..cd088257d1b0 100644
--- a/nnvm/src/pass/correct_layout.cc
+++ b/nnvm/src/pass/correct_layout.cc
@@ -28,11 +28,11 @@ nnvm::NodePtr CreateLayoutTransformNode(const Layout& src,
 using LayoutAttrDict = std::unordered_map<const Node*, std::vector<Layout> >;
 
 /*!
- * \brief A simple layout infer pass that will
+ * \brief A simple layout infer & correct pass that will
  *        insert layout transform nodes automatically.
  */
 nnvm::Graph CorrectLayout(nnvm::Graph src) {
-  static auto& op_infer_layout =
+  static auto& op_correct_layout =
     nnvm::Op::GetAttr<FCorrectLayout>("FCorrectLayout");
 
   const IndexedGraph& idx = src.indexed_graph();
@@ -91,14 +91,13 @@ nnvm::Graph CorrectLayout(nnvm::Graph src) {
       }
     }
 
-    const auto& flayout = op_infer_layout[new_node->op()];
-    CHECK(flayout != nullptr) << "Attribute FCorrectLayout"
-                              << " is not registered by op " << inode.source->op()->name
-                              << " we are not able to complete layout transform.";
-    CHECK(flayout(new_node->attrs, &request_ilayouts, &last_request_ilayouts, &produce_olayouts))
+    if (op_correct_layout.count(new_node->op())) {
+      const auto &flayout = op_correct_layout[new_node->op()];
+      CHECK(flayout(new_node->attrs, &request_ilayouts, &last_request_ilayouts, &produce_olayouts))
         << "Layout infer fail";
-    CHECK_EQ(request_ilayouts.size(), num_inputs);
-    CHECK_EQ(produce_olayouts.size(), num_outputs);
+      CHECK_EQ(request_ilayouts.size(), num_inputs);
+      CHECK_EQ(produce_olayouts.size(), num_outputs);
+    }
 
     // update new layouts
     new_layouts[new_node.get()] = std::move(produce_olayouts);

From 59c2078069de950d136d591ca72446bdcd20d443 Mon Sep 17 00:00:00 2001
From: Yizhi Liu <liuyizhi@apache.org>
Date: Sat, 28 Apr 2018 20:14:55 -0700
Subject: [PATCH 641/949] fix restore layout in AlterOpLayout (#460)

* fix restore layout in AlterOpLayout

* lint test case
---
 nnvm/src/compiler/alter_op_layout.cc               | 5 +++--
 nnvm/tests/python/compiler/test_alter_op_layout.py | 6 +++++-
 2 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/nnvm/src/compiler/alter_op_layout.cc b/nnvm/src/compiler/alter_op_layout.cc
index 893a0d2987ae..9fdc71fa7c6e 100644
--- a/nnvm/src/compiler/alter_op_layout.cc
+++ b/nnvm/src/compiler/alter_op_layout.cc
@@ -119,8 +119,9 @@ Graph AlterOpLayout(const Graph& src) {
       if (new_nodes.count(inode.source)) {
         const std::vector<Layout>& in_layouts =
           in_layouts_of_node[new_nodes[inode.source]];
-        for (const auto& e : inode.inputs) {
-          ret_layouts[ret_idx.entry_id(e)] = in_layouts[e.index];
+        for (uint32_t i = 0; i < inode.inputs.size(); ++i) {
+          const auto& e = inode.inputs[i];
+          ret_layouts[ret_idx.entry_id(e)] = in_layouts[i];
         }
         const std::vector<Layout>& out_layouts =
           out_layouts_of_node[new_nodes[inode.source]];
diff --git a/nnvm/tests/python/compiler/test_alter_op_layout.py b/nnvm/tests/python/compiler/test_alter_op_layout.py
index d921d4a646ee..bfda1807dc21 100644
--- a/nnvm/tests/python/compiler/test_alter_op_layout.py
+++ b/nnvm/tests/python/compiler/test_alter_op_layout.py
@@ -19,10 +19,14 @@ def test_alter_conv2d_layout():
     conv = sym.conv2d(data, name="conv", channels=16,
                       kernel_size=(3,3), padding=(1,1),
                       use_bias=False, layout="NCHW")
-    relu = sym.relu(conv, name="relu")
+    # split here
+    convs = sym.split(conv, indices_or_sections=2)
+    relus = [sym.relu(x, name="relu") for x in convs]
+    relu = sym.concatenate(*relus)
     flatten = sym.flatten(relu, name="flatten")
     softmax = sym.softmax(flatten, name="softmax")
     g = graph.create(softmax)
+
     g = g.apply("CorrectLayout")
     g = graph_attr.set_dtype_inputs(g, "float32")
     g = g.apply(["InferShape", "InferType"])

From a7c7af0583611a19ffd88f62cd1ce43f7f36c6d4 Mon Sep 17 00:00:00 2001
From: Yizhi Liu <liuyizhi@apache.org>
Date: Thu, 3 May 2018 08:55:04 -0700
Subject: [PATCH 642/949] AlterOpLayout with tvm.target (#463)

* AlterOpLayout with tvm.target

* fix test
---
 nnvm/python/nnvm/compiler/build_module.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/nnvm/python/nnvm/compiler/build_module.py b/nnvm/python/nnvm/compiler/build_module.py
index 436b06c5e34c..3080baf1d79d 100644
--- a/nnvm/python/nnvm/compiler/build_module.py
+++ b/nnvm/python/nnvm/compiler/build_module.py
@@ -264,7 +264,8 @@ def build(graph, target=None, shape=None, dtype="float32",
     if _all_var_init:
         init_var = initialize_variables(shape, dtype)
     # Apply optimization
-    graph = optimize(graph, shape, dtype, layout)
+    with target:
+        graph = optimize(graph, shape, dtype, layout)
     # Precompute prune
     if params and cfg.pass_enabled("PrecomputePrune"):
         graph, params = precompute_prune(graph, params)

From 47ff68e3ad231a9fa15b4f73a4b0b9ef921b75e2 Mon Sep 17 00:00:00 2001
From: Wenhao Hu <fumihwh@gmail.com>
Date: Sun, 6 May 2018 06:36:09 +0900
Subject: [PATCH 643/949] raise error when cannot get shape in reshape (#467)

* raise error when cannot get shape in reshape

* fix pylint
---
 nnvm/python/nnvm/frontend/onnx.py | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/onnx.py b/nnvm/python/nnvm/frontend/onnx.py
index fd4f0bb47e12..2a95400295a3 100644
--- a/nnvm/python/nnvm/frontend/onnx.py
+++ b/nnvm/python/nnvm/frontend/onnx.py
@@ -246,6 +246,8 @@ def _impl_v1(cls, inputs, attr, params):
 
 
 class Reshape(OnnxOpConverter):
+    """ Operator converter for Reshape.
+    """
 
     @classmethod
     def _impl_v1(cls, inputs, attr, params):
@@ -253,9 +255,11 @@ def _impl_v1(cls, inputs, attr, params):
 
     @classmethod
     def _impl_v5(cls, inputs, attr, params):
-        return _sym.reshape(
-            inputs[0],
-            shape=tuple(params[inputs[1].list_output_names()[0]].asnumpy()))
+        if inputs[1].list_output_names()[0] in params:
+            shape = tuple(params[inputs[1].list_output_names()[0]].asnumpy())
+        else:
+            raise RuntimeError('Shape is not contained in graph initializer.')
+        return _sym.reshape(inputs[0], shape=shape)
 
 
 class Scale(OnnxOpConverter):

From cd2417ecb934901e26a70f627e5a402427f74744 Mon Sep 17 00:00:00 2001
From: MORITA Kazutaka <morita.kazutaka@gmail.com>
Date: Wed, 9 May 2018 00:29:12 +0900
Subject: [PATCH 644/949] add sanity check to input shape type (#469)

---
 nnvm/python/nnvm/compiler/build_module.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/nnvm/python/nnvm/compiler/build_module.py b/nnvm/python/nnvm/compiler/build_module.py
index 3080baf1d79d..86fa08ec1bfc 100644
--- a/nnvm/python/nnvm/compiler/build_module.py
+++ b/nnvm/python/nnvm/compiler/build_module.py
@@ -241,6 +241,10 @@ def build(graph, target=None, shape=None, dtype="float32",
     shape = shape if shape else {}
     if not isinstance(shape, dict):
         raise TypeError("require shape to be dict")
+    for value in shape.values():
+        if not all(isinstance(x, int) for x in value):
+            raise TypeError("shape value must be int iterator")
+
     cfg = BuildConfig.current
     graph = graph if isinstance(graph, _graph.Graph) else _graph.create(graph)
     shape, dtype = _update_shape_dtype(shape, dtype, params)

From e10eb91481f39e70ec1d2b8cacd6482ca8fc8262 Mon Sep 17 00:00:00 2001
From: Siva <srk.it38@gmail.com>
Date: Thu, 10 May 2018 22:37:59 +0530
Subject: [PATCH 645/949]     fix lint error (#475)

---
 nnvm/python/nnvm/testing/darknet.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/python/nnvm/testing/darknet.py b/nnvm/python/nnvm/testing/darknet.py
index 30b790bb4e6d..9e2d7a6e7258 100644
--- a/nnvm/python/nnvm/testing/darknet.py
+++ b/nnvm/python/nnvm/testing/darknet.py
@@ -7,8 +7,8 @@
 These are utility functions used for testing and tutorial file.
 """
 from __future__ import division
-from enum import IntEnum
 import math
+from enum import IntEnum
 import numpy as np
 import cv2
 from cffi import FFI

From 8123f5cc46c2135091b037b12f111fa03621ab0a Mon Sep 17 00:00:00 2001
From: Leyuan Wang <laurawly@gmail.com>
Date: Thu, 10 May 2018 18:18:30 -0700
Subject: [PATCH 646/949] dilation fixed for (1, 1) case (#477)

---
 nnvm/python/nnvm/top/nn.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index ae9853db7cff..80e2fb698163 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -88,6 +88,8 @@ def compute_conv2d(attrs, inputs, _):
     (dilation_h, dilation_w) = dilation
     if dilation_h < 1 or dilation_w < 1:
         raise ValueError("dilation should be positive value")
+    elif dilation == (1, 1):
+        kernel = inputs[1]
     elif layout == "NCHW":
         kernel = topi.nn.dilate(inputs[1], [1, 1, dilation_h, dilation_w])
     else: #layout == NHWC

From 443d601be083a247b574d2f52510df8d06c7b074 Mon Sep 17 00:00:00 2001
From: "Tang, Cheng" <souptc@gmail.com>
Date: Sat, 12 May 2018 20:19:43 -0700
Subject: [PATCH 647/949] use uint32_t (#478)

---
 nnvm/src/compiler/alter_op_layout.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/src/compiler/alter_op_layout.cc b/nnvm/src/compiler/alter_op_layout.cc
index 9fdc71fa7c6e..369338f19ee2 100644
--- a/nnvm/src/compiler/alter_op_layout.cc
+++ b/nnvm/src/compiler/alter_op_layout.cc
@@ -67,7 +67,7 @@ Graph AlterOpLayout(const Graph& src) {
       in_layouts_of_node[nid] = in_layout;
 
       std::vector<Layout> out_layout;
-      for (uint i = 0; i < inode.source->num_outputs(); ++i) {
+      for (uint32_t i = 0; i < inode.source->num_outputs(); ++i) {
         out_layout.emplace_back(layouts[idx_graph.entry_id(nid, i)]);
       }
       out_layouts_of_node[nid] = out_layout;

From 8ad45e0026d9c64a60e2d3e87f5a3144a4923809 Mon Sep 17 00:00:00 2001
From: "Tang, Cheng" <souptc@gmail.com>
Date: Sun, 13 May 2018 09:29:03 -0700
Subject: [PATCH 648/949] add PATH to dll_path on windows (#479)

---
 nnvm/python/nnvm/libinfo.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/nnvm/python/nnvm/libinfo.py b/nnvm/python/nnvm/libinfo.py
index bd802cdbf35c..86c833cf1110 100644
--- a/nnvm/python/nnvm/libinfo.py
+++ b/nnvm/python/nnvm/libinfo.py
@@ -37,6 +37,8 @@ def find_lib_path():
         dll_path.extend([p.strip() for p in os.environ['LD_LIBRARY_PATH'].split(":")])
     elif sys.platform.startswith('darwin') and os.environ.get('DYLD_LIBRARY_PATH', None):
         dll_path.extend([p.strip() for p in os.environ['DYLD_LIBRARY_PATH'].split(":")])
+    elif sys.platform.startswith('win32') and os.environ.get('PATH', None):
+        dll_path.extend([p.strip() for p in os.environ['PATH'].split(";")])
 
     if sys.platform.startswith('win32'):
         vs_configuration = 'Release'

From 23576353b10473cf65fd732b7b945dd72ab48eaa Mon Sep 17 00:00:00 2001
From: MORITA Kazutaka <morita.kazutaka@gmail.com>
Date: Tue, 15 May 2018 00:45:50 +0900
Subject: [PATCH 649/949] [Keras] ReLU6 support (#481)

---
 nnvm/python/nnvm/frontend/keras.py               |  2 +-
 nnvm/tests/python/frontend/keras/test_forward.py | 10 ++++++++++
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/nnvm/python/nnvm/frontend/keras.py b/nnvm/python/nnvm/frontend/keras.py
index e363184d730f..a4d43cd43709 100644
--- a/nnvm/python/nnvm/frontend/keras.py
+++ b/nnvm/python/nnvm/frontend/keras.py
@@ -52,7 +52,7 @@ def _convert_activation(insym, keras_layer, _):
     elif act_type == 'elu':
         raise NotImplementedError('elu not implemented')
     elif act_type == 'relu6':
-        raise NotImplementedError('relu6 not implemented')
+        return _sym.clip(insym, a_min=0, a_max=6)
     elif act_type == 'softsign':
         raise NotImplementedError('softsign not implemented')
     elif act_type == 'hard_sigmoid':
diff --git a/nnvm/tests/python/frontend/keras/test_forward.py b/nnvm/tests/python/frontend/keras/test_forward.py
index b51b97604192..58a3d8c12ff6 100644
--- a/nnvm/tests/python/frontend/keras/test_forward.py
+++ b/nnvm/tests/python/frontend/keras/test_forward.py
@@ -116,6 +116,15 @@ def test_forward_upsample():
     verify_keras_frontend(keras_model)
 
 
+def test_forward_relu6():
+    data = keras.layers.Input(shape=(32,32,3))
+    x = keras.layers.Activation(keras.applications.mobilenet.relu6)(data)
+    x = keras.layers.Concatenate()([x, x])
+    x = keras.layers.GlobalMaxPooling2D()(x)
+    keras_model = keras.models.Model(data, x)
+    verify_keras_frontend(keras_model)
+
+
 def test_forward_vgg16():
     keras_model = keras.applications.vgg16.VGG16(include_top=True, weights=None,
         input_shape=(224,224,3), classes=1000)
@@ -142,6 +151,7 @@ def test_forward_resnet50():
     test_forward_transpose_conv()
     test_forward_separable_conv()
     test_forward_upsample()
+    test_forward_relu6()
 
     test_forward_vgg16()
     test_forward_xception()

From 80b91e5cfe0fd92c00d37a519014945ae5d65437 Mon Sep 17 00:00:00 2001
From: Yizhi Liu <liuyizhi@apache.org>
Date: Mon, 14 May 2018 13:13:19 -0700
Subject: [PATCH 650/949]  enable AlterOpLayout to keep OP unchanged (#471)

---
 nnvm/include/nnvm/compiler/op_attr_types.h         | 9 ++++++---
 nnvm/python/nnvm/top/nn.py                         | 4 ++++
 nnvm/src/compiler/alter_op_layout.cc               | 8 +++++---
 nnvm/src/compiler/fold_scale_axis.cc               | 6 +++---
 nnvm/src/compiler/packed_func_ext.cc               | 9 +++++++--
 nnvm/src/top/nn/nn_common.h                        | 2 +-
 nnvm/tests/python/compiler/test_alter_op_layout.py | 2 +-
 7 files changed, 27 insertions(+), 13 deletions(-)

diff --git a/nnvm/include/nnvm/compiler/op_attr_types.h b/nnvm/include/nnvm/compiler/op_attr_types.h
index 231e85093433..497a520db78e 100644
--- a/nnvm/include/nnvm/compiler/op_attr_types.h
+++ b/nnvm/include/nnvm/compiler/op_attr_types.h
@@ -80,11 +80,14 @@ using FTVMSchedule = std::function<
  * \param attrs The attribute of the original node.
  * \param inputs The input symbols of the original node.
  * \param tinfos The inferred shape and dtype of the inputs.
+ * \param ret The replaced operator.
+ * \return Whether to replace current operator.
  */
 using FTVMAlterOpLayout = std::function<
-  Symbol(const NodeAttrs& attrs,
-         const Symbol& inputs,
-         const Array<Tensor>& tinfos)>;
+  bool(const NodeAttrs& attrs,
+       const Symbol& inputs,
+       const Array<Tensor>& tinfos,
+       Symbol* ret)>;
 
 /*!
  * \brief Transform from normal operator to vectorized operator
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index 80e2fb698163..8135474c9c67 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -120,6 +120,10 @@ def schedule_conv2d(attrs, outs, target):
             return topi.generic.schedule_conv2d_nhwc(outs)
         return topi.generic.schedule_depthwise_conv2d_nchw(outs)
 
+@reg.register_alter_op_layout("conv2d")
+def alter_conv2d_layout(attrs, inputs, tinfos):
+    return topi.nn.conv2d_alter_layout(attrs, inputs, tinfos)
+
 reg.register_pattern("conv2d", OpPattern.OUT_ELEMWISE_FUSABLE)
 
 # convolution NCHWc
diff --git a/nnvm/src/compiler/alter_op_layout.cc b/nnvm/src/compiler/alter_op_layout.cc
index 369338f19ee2..bf28df3d04f8 100644
--- a/nnvm/src/compiler/alter_op_layout.cc
+++ b/nnvm/src/compiler/alter_op_layout.cc
@@ -103,9 +103,11 @@ Graph AlterOpLayout(const Graph& src) {
       tensor_infos.push_back(op_output_tinfos[input.index]);
     }
     // callback registered function to get a new operator.
-    auto op = fn_alter_op_layout(n->attrs, Symbol::CreateGroup(op_inputs), tensor_infos);
-    *ret = op.outputs;
-    return true;
+    Symbol op;
+    bool do_alter =
+      fn_alter_op_layout(n->attrs, Symbol::CreateGroup(op_inputs), tensor_infos, &op);
+    if (do_alter) *ret = op.outputs;
+    return do_alter;
   };
 
   Graph ret = nnvm::compiler::GraphTransform(src, transform);
diff --git a/nnvm/src/compiler/fold_scale_axis.cc b/nnvm/src/compiler/fold_scale_axis.cc
index f9524eb8ed34..28796a2b0bcd 100644
--- a/nnvm/src/compiler/fold_scale_axis.cc
+++ b/nnvm/src/compiler/fold_scale_axis.cc
@@ -466,8 +466,8 @@ bool Conv2DScaleAxisBackward(
   using top::Conv2DParam;
   const Conv2DParam& param = nnvm::get<Conv2DParam>(attrs.parsed);
   if (out_info.kind != kPending) return false;
-  // only optimize for nchw for now
-  if (param.layout == "NCHW" && out_info.axis == 1) {
+  // only optimize for kernel layout OIHW for now
+  if (param.kernel_layout == "OIHW" && out_info.axis == 1) {
     (*in_axis)[1].kind = kMulConsumer;
     (*in_axis)[1].axis = 0;
     (*in_axis)[1].source = out_info.source;
@@ -492,7 +492,7 @@ bool Conv2DScaleAxisForward(
   const Conv2DParam& param = nnvm::get<Conv2DParam>(attrs.parsed);
   if ((*in_info)[0].kind != kPending) return false;
   // only optimize for nchw for now
-  if (param.layout == "NCHW" && (*in_info)[0].axis == 1) {
+  if (param.kernel_layout == "OIHW" && (*in_info)[0].axis == 1) {
     (*in_info)[1].kind = kMulConsumer;
     (*in_info)[1].axis = 1;
     (*in_info)[1].source = (*in_info)[0].source;
diff --git a/nnvm/src/compiler/packed_func_ext.cc b/nnvm/src/compiler/packed_func_ext.cc
index 2587534d7d07..d549f9e2004f 100644
--- a/nnvm/src/compiler/packed_func_ext.cc
+++ b/nnvm/src/compiler/packed_func_ext.cc
@@ -70,12 +70,17 @@ TVM_REGISTER_GLOBAL("nnvm.compiler._register_alter_op_layout")
   Op& op = ::dmlc::Registry<nnvm::Op>::Get()->__REGISTER_OR_GET__(args[0]);
   auto fpack = [f](const NodeAttrs& attrs,
                    const Symbol& inputs,
-                   const Array<Tensor>& tinfos) {
+                   const Array<Tensor>& tinfos,
+                   Symbol* ret_symbol) {
     TVMRetValue ret = (*f)(GetAttrDict(attrs), inputs, tinfos);
+    if (ret.type_code() == TVMTypeCode::kNull) {
+      return false;
+    }
     CHECK_EQ(ret.type_code(), tvm::runtime::extension_class_info<Symbol>::code)
       << " expected " << "Symbol (code = " << tvm::runtime::extension_class_info<Symbol>::code
       << ") but get code = " << ret.type_code();
-    return *(static_cast<Symbol*>(ret.value().v_handle));
+    *ret_symbol = *(static_cast<Symbol*>(ret.value().v_handle));
+    return true;
   };
   op.set_attr<FTVMAlterOpLayout>("FTVMAlterOpLayout", fpack, args[2]);
 });
diff --git a/nnvm/src/top/nn/nn_common.h b/nnvm/src/top/nn/nn_common.h
index 49a020348485..4dc9f7db54c0 100644
--- a/nnvm/src/top/nn/nn_common.h
+++ b/nnvm/src/top/nn/nn_common.h
@@ -75,7 +75,7 @@ inline TShape ConvertLayout(TShape src, const Layout& src_layout, const Layout&
         CHECK_GT(dst_factor, 0);
         CHECK_LE(dst_factor, src_dim_size) << "Converting " << src
                                            << " from " << src_layout
-                                           << " to " << dst_factor
+                                           << " to " << dst_layout
                                            << ": cannot split dimension size of "
                                            << src_dim_size << " by " << dst_factor;
         dst[dst_major_pos] /= dst_factor;
diff --git a/nnvm/tests/python/compiler/test_alter_op_layout.py b/nnvm/tests/python/compiler/test_alter_op_layout.py
index bfda1807dc21..0fbf5ad3b479 100644
--- a/nnvm/tests/python/compiler/test_alter_op_layout.py
+++ b/nnvm/tests/python/compiler/test_alter_op_layout.py
@@ -32,7 +32,7 @@ def test_alter_conv2d_layout():
     g = g.apply(["InferShape", "InferType"])
     layouts_origin = get_layouts(g)
 
-    @reg.register_alter_op_layout("conv2d")
+    @reg.register_alter_op_layout("conv2d", level=100)
     def alter_conv2d_layout(attrs, inputs, tinfos):
         new_attrs = {k : attrs[k] for k in attrs.keys()}
         new_attrs["layout"] = "NCHW16c"

From 099dccb79b8fdc4f49e9320374c8b40042f24c47 Mon Sep 17 00:00:00 2001
From: "Tang, Cheng" <souptc@gmail.com>
Date: Mon, 14 May 2018 21:09:08 -0700
Subject: [PATCH 651/949] add use_mt option, default to use md as mt won't work
 with current c++ api (#484)

---
 nnvm/CMakeLists.txt | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/nnvm/CMakeLists.txt b/nnvm/CMakeLists.txt
index 3747be06aa56..2926073eb03f 100644
--- a/nnvm/CMakeLists.txt
+++ b/nnvm/CMakeLists.txt
@@ -31,18 +31,21 @@ add_definitions(-DNNVM_EXPORTS)
 # Build a shared lib (libnnvm.so) by default
 option(BUILD_SHARED_NNVM "Build a shared nnvm lib" ON)
 option(BUILD_STATIC_NNVM "Build a static nnvm lib" OFF)
+option(USE_MSVC_MT "Build with MT" OFF)
 
 # compile
 if(MSVC)
   add_definitions(-DDMLC_USE_CXX11)
   add_definitions(-DDMLC_STRICT_CXX11)
-  foreach(flag_var
+  if (USE_MSVC_MT)
+    foreach(flag_var
         CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
         CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
-    if(${flag_var} MATCHES "/MD")
-      string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
-    endif(${flag_var} MATCHES "/MD")
-  endforeach(flag_var)
+      if(${flag_var} MATCHES "/MD")
+	    string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
+      endif(${flag_var} MATCHES "/MD")
+    endforeach(flag_var)
+  endif()
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc")
   find_library(TVM_LIB tvm
 			   HINTS ${CMAKE_CURRENT_SOURCE_DIR}/../tvm/build/Release

From 3865410a3bc68429cdf7348f104b08a922012683 Mon Sep 17 00:00:00 2001
From: Siva <srk.it38@gmail.com>
Date: Wed, 16 May 2018 07:42:55 +0530
Subject: [PATCH 652/949] Better to check Infer result with topi results at
 build time instead of leaving to a runtime error. (#476)

---
 nnvm/src/compiler/compile_engine.cc | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/nnvm/src/compiler/compile_engine.cc b/nnvm/src/compiler/compile_engine.cc
index 1ef39f851203..a9d4aa2d016a 100644
--- a/nnvm/src/compiler/compile_engine.cc
+++ b/nnvm/src/compiler/compile_engine.cc
@@ -202,6 +202,22 @@ class CompileEngine {
       Array<Tensor> out = fcompute[inode.source->op()](
           inode.source->attrs, op_inputs, out_info);
       CHECK_EQ(out.size(), inode.source->num_outputs());
+
+      // check output dimentions also match
+      // This check is to make sure the NNVM operator Infer match with Compute result.
+      // Missing this check may pass the build but leads to runtime errors.
+      for (uint32_t i = 0; i < out.size(); ++i) {
+        CHECK_EQ(out[i].ndim(), out_info[i].ndim()) << inode.source->op()->name;
+        tvm::Tensor inferred_tensor = out[i];
+        tvm::Tensor computed_tensor = out_info[i];
+        for (uint32_t j = 0; j < inferred_tensor->shape.size(); ++j) {
+          if ((as_const_int(inferred_tensor->shape[j])) &&
+              (as_const_int(computed_tensor->shape[j])))
+            CHECK_EQ((*as_const_int(inferred_tensor->shape[j])),
+                     (*as_const_int(computed_tensor->shape[j]))) << inode.source->op()->name;
+        }
+      }
+
       // schedule on root node, and use master's schedule
       for (uint32_t index = 0; index < inode.source->num_outputs(); ++index) {
         uint32_t eid = idx.entry_id(nid, index);

From 7968e36ad3beab1bedf12f3fd9c01f8db8aa44ff Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Wed, 16 May 2018 21:14:42 +0530
Subject: [PATCH 653/949] Fixed issue #483, removing enum dependancy (#485)

---
 nnvm/python/nnvm/frontend/darknet.py | 69 ++++++++++++----------------
 nnvm/python/nnvm/testing/darknet.py  |  5 +-
 2 files changed, 32 insertions(+), 42 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/darknet.py b/nnvm/python/nnvm/frontend/darknet.py
index 1c5fdfa87406..cb73b5a4345a 100644
--- a/nnvm/python/nnvm/frontend/darknet.py
+++ b/nnvm/python/nnvm/frontend/darknet.py
@@ -3,12 +3,11 @@
 """
 
 from __future__ import absolute_import as _abs
-from enum import IntEnum
 import numpy as np
 import tvm
 from .. import symbol as _sym
 
-class LAYERTYPE(IntEnum):
+class LAYERTYPE(object):
     """Darknet LAYERTYPE Class constant."""
     CONVOLUTIONAL = 0
     DECONVOLUTIONAL = 1
@@ -36,7 +35,7 @@ class LAYERTYPE(IntEnum):
     REORG = 23
     BLANK = 24
 
-class ACTIVATION(IntEnum):
+class ACTIVATION(object):
     """Darknet ACTIVATION Class constant."""
     LOGISTIC = 0
     RELU = 1
@@ -323,33 +322,31 @@ def _darknet_op_not_support(inputs, attrs):
     raise NotImplementedError(err)
 
 _DARKNET_CONVERT_MAP = {
-    'CONVOLUTIONAL'   : _darknet_conv2d,
-    'DECONVOLUTIONAL' : _darknet_conv2d_transpose,
-    'CONNECTED'       : _darknet_dense,
-    'MAXPOOL'         : _darknet_maxpooling,
-    'SOFTMAX'         : _darknet_softmax_output,
-    'DROPOUT'         : _darknet_dropout,
-    'AVGPOOL'         : _darknet_avgpooling,
-    'BATCHNORM'       : _darknet_batch_norm,
-    'RESHAPE'         : _darknet_reshape,
-    'ROUTE'           : _darknet_route,
-    'REORG'           : _darknet_reorg,
-    'REGION'          : _darknet_region,
-    'ACTIVATION'      : _darknet_activations,
-    'SHORTCUT'        : _darknet_shortcut,
-    'DETECTION'       : _darknet_op_not_support,
-    'CROP'            : _darknet_op_not_support,
-    'COST'            : _darknet_op_not_support,
-    'NORMALIZATION'   : _darknet_op_not_support,
-    'LOCAL'           : _darknet_op_not_support,
-    'ACTIVE'          : _darknet_op_not_support,
-    'RNN'             : _darknet_op_not_support,
-    'GRU'             : _darknet_op_not_support,
-    'LSTM'            : _darknet_op_not_support,
-    'CRNN'            : _darknet_op_not_support,
-    'NETWORK'         : _darknet_op_not_support,
-    'XNOR'            : _darknet_op_not_support,
-    'BLANK'           : _darknet_op_not_support,
+    LAYERTYPE.CONVOLUTIONAL   : _darknet_conv2d,
+    LAYERTYPE.DECONVOLUTIONAL : _darknet_conv2d_transpose,
+    LAYERTYPE.CONNECTED       : _darknet_dense,
+    LAYERTYPE.MAXPOOL         : _darknet_maxpooling,
+    LAYERTYPE.SOFTMAX         : _darknet_softmax_output,
+    LAYERTYPE.DROPOUT         : _darknet_dropout,
+    LAYERTYPE.AVGPOOL         : _darknet_avgpooling,
+    LAYERTYPE.BATCHNORM       : _darknet_batch_norm,
+    LAYERTYPE.ROUTE           : _darknet_route,
+    LAYERTYPE.REORG           : _darknet_reorg,
+    LAYERTYPE.REGION          : _darknet_region,
+    LAYERTYPE.SHORTCUT        : _darknet_shortcut,
+    LAYERTYPE.DETECTION       : _darknet_op_not_support,
+    LAYERTYPE.CROP            : _darknet_op_not_support,
+    LAYERTYPE.COST            : _darknet_op_not_support,
+    LAYERTYPE.NORMALIZATION   : _darknet_op_not_support,
+    LAYERTYPE.LOCAL           : _darknet_op_not_support,
+    LAYERTYPE.ACTIVE          : _darknet_op_not_support,
+    LAYERTYPE.RNN             : _darknet_op_not_support,
+    LAYERTYPE.GRU             : _darknet_op_not_support,
+    LAYERTYPE.LSTM            : _darknet_op_not_support,
+    LAYERTYPE.CRNN            : _darknet_op_not_support,
+    LAYERTYPE.NETWORK         : _darknet_op_not_support,
+    LAYERTYPE.XNOR            : _darknet_op_not_support,
+    LAYERTYPE.BLANK           : _darknet_op_not_support,
 }
 
 def _darknet_convert_symbol(op_name, inputs, attrs):
@@ -376,7 +373,7 @@ def _darknet_convert_symbol(op_name, inputs, attrs):
     if op_name in _DARKNET_CONVERT_MAP:
         sym, out_name = _DARKNET_CONVERT_MAP[op_name](inputs, attrs)
     else:
-        _darknet_raise_not_supported('Operator: ' + op_name)
+        _darknet_raise_not_supported('Operator type ' + str(op_name))
     if out_name is  None:
         out_name = sym.list_output_names()[0].replace('_output', '')
     return out_name, sym
@@ -397,10 +394,6 @@ def _read_memory_buffer(shape, data, dtype):
         data_np[i] = data[i]
     return data_np.reshape(shape)
 
-def _get_darknet_layername(layer_type):
-    """Get the layer name from the darknet enums."""
-    return str((LAYERTYPE(layer_type))).replace('LAYERTYPE.', '')
-
 def _get_convolution_weights(layer, opname, params, dtype):
     """Get the convolution layer weights and biases."""
     if layer.nweights == 0:
@@ -460,8 +453,6 @@ def _get_darknet_attrs(net, layer_num):
     attr = {}
     use_flatten = True
     layer = net.layers[layer_num]
-    op_name = _get_darknet_layername(layer.type)
-
     if LAYERTYPE.CONVOLUTIONAL == layer.type:
         attr.update({'layout' : 'NCHW'})
         attr.update({'pad' : str(layer.pad)})
@@ -551,10 +542,10 @@ def _get_darknet_attrs(net, layer_num):
         attr.update({'background' : layer.background})
         attr.update({'softmax' : layer.softmax})
     else:
-        err = "Darknet layer {} is not supported in nnvm.".format(op_name)
+        err = "Darknet layer type {} is not supported in nnvm.".format(layer.type)
         raise NotImplementedError(err)
 
-    return op_name, attr
+    return layer.type, attr
 
 def _get_tvm_params_name(opname, arg_name):
     """Makes the params name for the k,v pair."""
diff --git a/nnvm/python/nnvm/testing/darknet.py b/nnvm/python/nnvm/testing/darknet.py
index 9e2d7a6e7258..ed8bf021c462 100644
--- a/nnvm/python/nnvm/testing/darknet.py
+++ b/nnvm/python/nnvm/testing/darknet.py
@@ -8,7 +8,6 @@
 """
 from __future__ import division
 import math
-from enum import IntEnum
 import numpy as np
 import cv2
 from cffi import FFI
@@ -91,7 +90,7 @@ def load_image(image, resize_width, resize_height):
     img = load_image_color(image)
     return _letterbox_image(img, resize_width, resize_height)
 
-class LAYERTYPE(IntEnum):
+class LAYERTYPE(object):
     """Darknet LAYERTYPE Class constant."""
     CONVOLUTIONAL = 0
     DECONVOLUTIONAL = 1
@@ -119,7 +118,7 @@ class LAYERTYPE(IntEnum):
     REORG = 23
     BLANK = 24
 
-class ACTIVATION(IntEnum):
+class ACTIVATION(object):
     """Darknet ACTIVATION Class constant."""
     LOGISTIC = 0
     RELU = 1

From 6a32540313dcb3e6d288f63597caeec783a48248 Mon Sep 17 00:00:00 2001
From: "Tang, Cheng" <souptc@gmail.com>
Date: Fri, 18 May 2018 21:09:08 -0700
Subject: [PATCH 654/949] don't rename onnx input tensors (#491)

---
 nnvm/python/nnvm/frontend/onnx.py | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/onnx.py b/nnvm/python/nnvm/frontend/onnx.py
index 2a95400295a3..bd5a0f4df1c7 100644
--- a/nnvm/python/nnvm/frontend/onnx.py
+++ b/nnvm/python/nnvm/frontend/onnx.py
@@ -558,17 +558,13 @@ def from_onnx(self, graph, opset):
             i_name = self._parse_value_proto(i)
             if i_name in self._params:
                 # i is a param instead of input
-                name_param = 'param_{}'.format(self._num_param)
                 self._num_param += 1
-                self._params[name_param] = self._params.pop(i_name)
-                self._nodes[name_param] = _sym.Variable(
-                    name=name_param, shape=self._params[name_param].shape)
-                self._renames[i_name] = name_param
+                self._params[i_name] = self._params.pop(i_name)
+                self._nodes[i_name] = _sym.Variable(
+                    name=i_name, shape=self._params[i_name].shape)
             else:
-                name_input = 'input_{}'.format(self._num_input)
                 self._num_input += 1
-                self._nodes[name_input] = _sym.Variable(name=name_input)
-                self._renames[i_name] = name_input
+                self._nodes[i_name] = _sym.Variable(name=i_name)
         # construct nodes, nodes are stored as directed acyclic graph
         for node in graph.node:
             op_name = node.op_type

From 8401b3b7c1d89e179cf717b978748999bca42270 Mon Sep 17 00:00:00 2001
From: Siva <srk.it38@gmail.com>
Date: Tue, 22 May 2018 22:57:40 +0530
Subject: [PATCH 655/949]     Cleanup of '-Wsign-compare' warnnigs. (#504)

---
 nnvm/src/top/tensor/matrix_op.cc | 4 ++--
 nnvm/src/top/tensor/reduce.cc    | 2 +-
 nnvm/src/top/tensor/transform.cc | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/nnvm/src/top/tensor/matrix_op.cc b/nnvm/src/top/tensor/matrix_op.cc
index 760896733dd5..87aae9b2d43c 100644
--- a/nnvm/src/top/tensor/matrix_op.cc
+++ b/nnvm/src/top/tensor/matrix_op.cc
@@ -34,8 +34,8 @@ inline bool DotShape(const nnvm::NodeAttrs& attrs,
     << "dot shape inconsistent: " << lshape << " X " << rshape;
 
   TShape oshape(lshape.ndim() + rshape.ndim() - 2);
-  for (int i = 0; i < lshape.ndim() - 1; i++) oshape[i] = lshape[i];
-  for (int i = 1; i < rshape.ndim(); i++) oshape[i + lshape.ndim() - 2] = rshape[i];
+  for (uint32_t i = 0; i < lshape.ndim() - 1; i++) oshape[i] = lshape[i];
+  for (uint32_t i = 1; i < rshape.ndim(); i++) oshape[i + lshape.ndim() - 2] = rshape[i];
 
   NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, 0, oshape);
   return true;
diff --git a/nnvm/src/top/tensor/reduce.cc b/nnvm/src/top/tensor/reduce.cc
index 00f14f52b5c3..2befe1cab455 100644
--- a/nnvm/src/top/tensor/reduce.cc
+++ b/nnvm/src/top/tensor/reduce.cc
@@ -145,7 +145,7 @@ Example::
       for (dim_t i = 0; i < param.axis.ndim(); ++i) {
         exclude_axis.insert(param.axis[i]);
       }
-      for (dim_t i = 0; i < inputs[0].ndim(); ++i) {
+      for (dim_t i = 0; i < static_cast<int>(inputs[0].ndim()); ++i) {
         if (exclude_axis.count(i) == 0) {
           axis.push_back(make_const(Int(32), i));
         }
diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
index 6957e7175946..f4e5a7be4841 100644
--- a/nnvm/src/top/tensor/transform.cc
+++ b/nnvm/src/top/tensor/transform.cc
@@ -760,7 +760,7 @@ inline bool TransposeCorrectLayout(const NodeAttrs& attrs,
     } else {
       CHECK_EQ(input.ndim(), param.axes.ndim());
       for (size_t i = 0; i < input.ndim(); ++i) {
-        CHECK(param.axes[i] < input.ndim());
+        CHECK(param.axes[i] < static_cast<int>(input.ndim()));
         new_layout << input.at(param.axes[i]);
       }
     }

From 0f2ae41c08dfb5802fdaf0892c15d35ea2b762ea Mon Sep 17 00:00:00 2001
From: Siva <srk.it38@gmail.com>
Date: Tue, 22 May 2018 23:00:53 +0530
Subject: [PATCH 656/949]    Increase depfiles lookup. (#509)

Not able link build/src/top/tensor/*.d.
       Hence don't compile nnvm for a change in tvm/topi headers.
---
 nnvm/Makefile | 1 +
 1 file changed, 1 insertion(+)

diff --git a/nnvm/Makefile b/nnvm/Makefile
index 62a4fadad6f0..8055a7e55eea 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -104,3 +104,4 @@ clean:
 -include build/*.d
 -include build/*/*.d
 -include build/*/*/*.d
+-include build/*/*/*/*.d

From 86daf7949a0b1cc2adcc8daba2666aa5d28df7fa Mon Sep 17 00:00:00 2001
From: MORITA Kazutaka <morita.kazutaka@lab.ntt.co.jp>
Date: Wed, 23 May 2018 02:32:04 +0900
Subject: [PATCH 657/949] [FRONTEND][Keras] Fix softmax axis (#503)

---
 nnvm/python/nnvm/frontend/keras.py               |  2 +-
 nnvm/tests/python/frontend/keras/test_forward.py | 10 ++++++++++
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/nnvm/python/nnvm/frontend/keras.py b/nnvm/python/nnvm/frontend/keras.py
index a4d43cd43709..0d51487c355d 100644
--- a/nnvm/python/nnvm/frontend/keras.py
+++ b/nnvm/python/nnvm/frontend/keras.py
@@ -40,7 +40,7 @@ def _convert_activation(insym, keras_layer, _):
         return _sym.__add_scalar__(_sym.__mul_scalar__(insym, \
             scalar=alpha), scalar=beta)
     elif act_type == 'softmax':
-        return _sym.softmax(insym)
+        return _sym.softmax(insym, axis=1)
     elif act_type == 'sigmoid':
         return _sym.sigmoid(insym)
     elif act_type == 'tanh':
diff --git a/nnvm/tests/python/frontend/keras/test_forward.py b/nnvm/tests/python/frontend/keras/test_forward.py
index 58a3d8c12ff6..0147a3e2c654 100644
--- a/nnvm/tests/python/frontend/keras/test_forward.py
+++ b/nnvm/tests/python/frontend/keras/test_forward.py
@@ -59,6 +59,15 @@ def test_forward_elemwise_add():
     verify_keras_frontend(keras_model)
 
 
+def test_forward_softmax():
+    data = keras.layers.Input(shape=(32,32,3))
+    x = keras.layers.Activation('softmax')(data)
+    x = keras.layers.Concatenate()([x, x])
+    x = keras.layers.GlobalMaxPooling2D()(x)
+    keras_model = keras.models.Model(data, x)
+    verify_keras_frontend(keras_model)
+
+
 def test_forward_softrelu():
     data = keras.layers.Input(shape=(32,32,3))
     x = keras.layers.Activation('softplus')(data)
@@ -145,6 +154,7 @@ def test_forward_resnet50():
 
 if __name__ == '__main__':
     test_forward_elemwise_add()
+    test_forward_softmax()
     test_forward_softrelu()
     test_forward_leaky_relu()
     test_forward_dense()

From 918dff2dcc4bdc23704515f7c87f4ce030a1a1c5 Mon Sep 17 00:00:00 2001
From: Tatsuya Nishiyama <nishiyama.tatsuya@lab.ntt.co.jp>
Date: Wed, 23 May 2018 13:01:44 +0900
Subject: [PATCH 658/949] Add count_include_pad support (#498)

* update tvm submodule

* Add count_include_pad support to avg_pool
---
 nnvm/include/nnvm/top/nn.h                    | 33 ++++++++++++++-
 nnvm/src/compiler/fold_scale_axis.cc          | 16 ++++----
 nnvm/src/top/nn/pooling.cc                    | 40 ++++++++++---------
 nnvm/tests/python/compiler/test_top_level2.py | 36 +++++++++++++++++
 4 files changed, 97 insertions(+), 28 deletions(-)

diff --git a/nnvm/include/nnvm/top/nn.h b/nnvm/include/nnvm/top/nn.h
index 80d8e02882ef..ec21abde9b8c 100644
--- a/nnvm/include/nnvm/top/nn.h
+++ b/nnvm/include/nnvm/top/nn.h
@@ -218,14 +218,14 @@ struct Conv2DTransposeParam : public dmlc::Parameter<Conv2DTransposeParam> {
 };
 
 
-struct Pool2DParam : public dmlc::Parameter<Pool2DParam> {
+struct MaxPool2DParam : public dmlc::Parameter<MaxPool2DParam> {
   TShape pool_size;
   TShape strides;
   TShape padding;
   std::string layout;
   bool ceil_mode;
 
-  DMLC_DECLARE_PARAMETER(Pool2DParam) {
+  DMLC_DECLARE_PARAMETER(MaxPool2DParam) {
     DMLC_DECLARE_FIELD(pool_size)
       .describe("Size of the pooling windows..");
     DMLC_DECLARE_FIELD(strides).set_default(TShape({1, 1}))
@@ -244,6 +244,35 @@ struct Pool2DParam : public dmlc::Parameter<Pool2DParam> {
 };
 
 
+struct AvgPool2DParam : public dmlc::Parameter<AvgPool2DParam> {
+  TShape pool_size;
+  TShape strides;
+  TShape padding;
+  std::string layout;
+  bool ceil_mode;
+  bool count_include_pad;
+
+  DMLC_DECLARE_PARAMETER(AvgPool2DParam) {
+    DMLC_DECLARE_FIELD(pool_size)
+      .describe("Size of the pooling windows..");
+    DMLC_DECLARE_FIELD(strides).set_default(TShape({1, 1}))
+      .describe("Specifies the strides of the convolution.");
+    DMLC_DECLARE_FIELD(padding).set_default(TShape({0, 0}))
+      .describe("If padding is non-zero, then the input is implicitly zero-padded"
+                "on both sides for padding number of points");
+    DMLC_DECLARE_FIELD(layout).set_default("NCHW")
+      .describe("Dimension ordering of data and weight. Can be 'NCHW', 'NHWC', etc."
+                "'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
+                "dimensions respectively. Convolution is applied on the 'H' and"
+                "'W' dimensions.");
+    DMLC_DECLARE_FIELD(ceil_mode).set_default(false)
+      .describe("When true, will use ceil instead of floor to compute the output shape.");
+    DMLC_DECLARE_FIELD(count_include_pad).set_default(false)
+      .describe("When true, will include padding to compute the average");
+  }
+};
+
+
 struct GlobalPool2DParam : public dmlc::Parameter<GlobalPool2DParam> {
   std::string layout;
 
diff --git a/nnvm/src/compiler/fold_scale_axis.cc b/nnvm/src/compiler/fold_scale_axis.cc
index 28796a2b0bcd..e38082b69916 100644
--- a/nnvm/src/compiler/fold_scale_axis.cc
+++ b/nnvm/src/compiler/fold_scale_axis.cc
@@ -354,28 +354,28 @@ NNVM_REGISTER_OP(leaky_relu)
 .set_attr<FScaleAxisForward>("FScaleAxisForward", ReluScaleAxisForward);
 
 // property registration.
+template <typename T>
 bool Pool2DBackward(
     const NodeAttrs& attrs,
     const std::vector<TShape>& in_shape,
     const std::vector<TShape>& out_shape,
     const FoldChainInfo& out_info,
     std::vector<FoldChainInfo>* in_axis) {
-  using top::Pool2DParam;
-  const Pool2DParam& param = nnvm::get<Pool2DParam>(attrs.parsed);
+  const T& param = nnvm::get<T>(attrs.parsed);
   if (out_info.axis == 1 && param.layout == "NCHW") {
     (*in_axis)[0] = out_info;
   }
   return false;
 }
 
+template <typename T>
 bool Pool2DForward(
     const NodeAttrs& attrs,
     const std::vector<TShape>& in_shape,
     const std::vector<TShape>& out_shape,
     std::vector<FoldChainInfo>* in_info,
     FoldChainInfo* out_info) {
-  using top::Pool2DParam;
-  const Pool2DParam& param = nnvm::get<Pool2DParam>(attrs.parsed);
+  const T& param = nnvm::get<T>(attrs.parsed);
   if ((*in_info)[0].axis == 1 && param.layout == "NCHW") {
     *out_info = (*in_info)[0];
   }
@@ -383,16 +383,16 @@ bool Pool2DForward(
 }
 
 NNVM_REGISTER_OP(max_pool2d)
-.set_attr<FScaleAxisBackward>("FScaleAxisBackward", Pool2DBackward);
+.set_attr<FScaleAxisBackward>("FScaleAxisBackward", Pool2DBackward<top::MaxPool2DParam>);
 
 NNVM_REGISTER_OP(avg_pool2d)
-.set_attr<FScaleAxisBackward>("FScaleAxisBackward", Pool2DBackward);
+.set_attr<FScaleAxisBackward>("FScaleAxisBackward", Pool2DBackward<top::AvgPool2DParam>);
 
 NNVM_REGISTER_OP(max_pool2d)
-.set_attr<FScaleAxisForward>("FScaleAxisForward", Pool2DForward);
+.set_attr<FScaleAxisForward>("FScaleAxisForward", Pool2DForward<top::MaxPool2DParam>);
 
 NNVM_REGISTER_OP(avg_pool2d)
-.set_attr<FScaleAxisForward>("FScaleAxisForward", Pool2DForward);
+.set_attr<FScaleAxisForward>("FScaleAxisForward", Pool2DForward<top::AvgPool2DParam>);
 
 
 
diff --git a/nnvm/src/top/nn/pooling.cc b/nnvm/src/top/nn/pooling.cc
index 0c40da520544..54eb0d7db3be 100644
--- a/nnvm/src/top/nn/pooling.cc
+++ b/nnvm/src/top/nn/pooling.cc
@@ -19,12 +19,13 @@ namespace top {
 using namespace tvm;
 using namespace nnvm::compiler;
 
-DMLC_REGISTER_PARAMETER(Pool2DParam);
+DMLC_REGISTER_PARAMETER(MaxPool2DParam);
 
+template <typename T>
 inline bool Pool2DInferShape(const nnvm::NodeAttrs& attrs,
                              std::vector<TShape>* in_shape,
                              std::vector<TShape>* out_shape) {
-  const Pool2DParam& param = nnvm::get<Pool2DParam>(attrs.parsed);
+  const T& param = nnvm::get<T>(attrs.parsed);
   CHECK_EQ(in_shape->size(), 1U);
   CHECK_EQ(out_shape->size(), 1U);
 
@@ -66,11 +67,12 @@ inline bool Pool2DInferShape(const nnvm::NodeAttrs& attrs,
   return true;
 }
 
+template <typename T>
 inline bool Pool2DCorrectLayout(const NodeAttrs& attrs,
                                 std::vector<Layout> *ilayouts,
                                 const std::vector<Layout> *last_ilayouts,
                                 std::vector<Layout> *olayouts) {
-  const Pool2DParam &param = nnvm::get<Pool2DParam>(attrs.parsed);
+  const T &param = nnvm::get<T>(attrs.parsed);
   CHECK_EQ(ilayouts->size(), 1);
   CHECK_EQ(last_ilayouts->size(), 1);
   CHECK_EQ(olayouts->size(), 1);
@@ -114,18 +116,18 @@ NNVM_REGISTER_OP(max_pool2d)
 
 )code" NNVM_ADD_FILELINE)
 .add_argument("data", "4D Tensor", "Input data.")
-.add_arguments(Pool2DParam::__FIELDS__())
-.set_attr_parser(ParamParser<Pool2DParam>)
-.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<Pool2DParam>)
+.add_arguments(MaxPool2DParam::__FIELDS__())
+.set_attr_parser(ParamParser<MaxPool2DParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<MaxPool2DParam>)
 .set_num_outputs(1)
 .set_num_inputs(1)
-.set_attr<FInferShape>("FInferShape", Pool2DInferShape)
+.set_attr<FInferShape>("FInferShape", Pool2DInferShape<MaxPool2DParam>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_attr<FCorrectLayout>("FCorrectLayout", Pool2DCorrectLayout)
+.set_attr<FCorrectLayout>("FCorrectLayout", Pool2DCorrectLayout<MaxPool2DParam>)
 .set_attr<FTVMCompute>("FTVMCompute", [](const NodeAttrs& attrs,
                                          const Array<Tensor>& inputs,
                                          const Array<Tensor>& out_info) {
-  const Pool2DParam& param = nnvm::get<Pool2DParam>(attrs.parsed);
+  const MaxPool2DParam& param = nnvm::get<MaxPool2DParam>(attrs.parsed);
   auto pool_size = ShapeToArray(param.pool_size);
   auto strides = ShapeToArray(param.strides);
   auto padding = ShapeToArray(param.padding);
@@ -163,12 +165,13 @@ NNVM_REGISTER_OP(_max_pool2d_grad)
 .add_argument("output", "4D Tensor", "Output data of max_pool2d grad.")
 .set_num_inputs(3)
 .set_num_outputs(1)
-.set_attr_parser(ParamParser<Pool2DParam>)
-.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<Pool2DParam>)
+.set_attr_parser(ParamParser<MaxPool2DParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<MaxPool2DParam>)
 .set_attr<FInferShape>("FInferShape", AssignOutputAttr<TShape, 1, 0>)
 .set_attr<FInferType>("FInferType", ElemwiseType<3, 1>)
 .set_attr<TIsBackward>("TIsBackward", true);
 
+DMLC_REGISTER_PARAMETER(AvgPool2DParam);
 
 NNVM_REGISTER_OP(avg_pool2d)
 .describe(R"code(Average pooling operation for one dimensional data.
@@ -187,20 +190,21 @@ NNVM_REGISTER_OP(avg_pool2d)
 
 )code" NNVM_ADD_FILELINE)
 .add_argument("data", "4D Tensor", "Input data.")
-.add_arguments(Pool2DParam::__FIELDS__())
-.set_attr_parser(ParamParser<Pool2DParam>)
-.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<Pool2DParam>)
-.set_attr<FInferShape>("FInferShape", Pool2DInferShape)
+.add_arguments(AvgPool2DParam::__FIELDS__())
+.set_attr_parser(ParamParser<AvgPool2DParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<AvgPool2DParam>)
+.set_attr<FInferShape>("FInferShape", Pool2DInferShape<AvgPool2DParam>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_attr<FCorrectLayout>("FCorrectLayout", Pool2DCorrectLayout)
+.set_attr<FCorrectLayout>("FCorrectLayout", Pool2DCorrectLayout<AvgPool2DParam>)
 .set_attr<FTVMCompute>("FTVMCompute", [](const NodeAttrs& attrs,
                                          const Array<Tensor>& inputs,
                                          const Array<Tensor>& out_info) {
-  const Pool2DParam& param = nnvm::get<Pool2DParam>(attrs.parsed);
+  const AvgPool2DParam& param = nnvm::get<AvgPool2DParam>(attrs.parsed);
   auto pool_size = ShapeToArray(param.pool_size);
   auto strides = ShapeToArray(param.strides);
   auto padding = ShapeToArray(param.padding);
   auto ceil_mode = param.ceil_mode;
+  auto count_include_pad = param.count_include_pad;
 
   Layout layout(param.layout);
   CHECK(layout.convertible(Layout("NCHW")))
@@ -214,7 +218,7 @@ NNVM_REGISTER_OP(avg_pool2d)
 
   return Array<Tensor>{
     topi::nn::pool(inputs[0], pool_size, strides, padding,
-                   topi::nn::kAvgPool, ceil_mode, layout.name())};
+                   topi::nn::kAvgPool, ceil_mode, layout.name(), count_include_pad)};
 })
 .set_num_outputs(1)
 .set_num_inputs(1)
diff --git a/nnvm/tests/python/compiler/test_top_level2.py b/nnvm/tests/python/compiler/test_top_level2.py
index 44767da4541f..f752b7d44271 100644
--- a/nnvm/tests/python/compiler/test_top_level2.py
+++ b/nnvm/tests/python/compiler/test_top_level2.py
@@ -141,6 +141,41 @@ def test_avg_pool2d():
         np.testing.assert_allclose(out.asnumpy(), b_np, rtol=1e-5)
 
 
+def test_avg_pool2d_no_count_pad():
+    kh, kw = (4, 4)
+    sh, sw = (2, 2)
+    ph, pw = (2, 2)
+    
+    x = sym.Variable("x")
+    y = sym.avg_pool2d(x, pool_size=(kh, kw), strides=(sw, sw), padding=(ph, pw),
+                       name="y", count_include_pad=False)
+    dtype = "float32"
+    n = 1
+    (ic, ih, iw) = (3, 28, 28)
+    (oc, oh, ow) = (3, 15, 15)
+
+    a_np = np.random.uniform(low=0.001, size=(n, ic, ih, iw)).astype(dtype)
+    pad_np = np.zeros(shape=(n, ic, ih+2*ph, iw+2*pw)).astype(dtype)
+    no_zero = (range(n), range(ic), (range(ph, ih+ph)), (range(pw, iw+pw)))
+    pad_np[np.ix_(*no_zero)] = a_np
+    b_np = np.zeros(shape=(n, oc, oh, ow)).astype(dtype)
+    
+    for i in range(oh):
+        for j in range(ow):
+            pad_count = np.sum(pad_np[:, :, i*sh:i*sh+kh, j*sw:j*sw+kw] > 0, axis=(2,3))
+            b_np[:,:,i,j] = np.sum(pad_np[:, :, i*sh:i*sh+kh, j*sw:j*sw+kw],
+                                   axis=(2,3)) / np.maximum(pad_count, 1)
+    b_np = np.maximum(b_np, 0.0)
+    shape_dict = {"x": (n, ic, ih, iw)}
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
+        m = graph_runtime.create(graph, lib, ctx)
+        data = tvm.nd.array(a_np)
+        m.run(x=data)
+        out = m.get_output(0, tvm.nd.empty((n, oc, oh, ow), dtype))
+        np.testing.assert_allclose(out.asnumpy(), b_np, rtol=1e-5)
+
+
 def test_global_max_pool2d():
     x = sym.Variable("x")
     y = sym.global_max_pool2d(x, name="y")
@@ -201,6 +236,7 @@ def test_upsampling():
     test_conv2d_transpose()
     test_max_pool2d()
     test_avg_pool2d()
+    test_avg_pool2d_no_count_pad()
     test_global_max_pool2d()
     test_global_avg_pool2d()
     test_upsampling()

From 6c0b8ec0da2db645e6ff311ca52dcd4c2fb281c6 Mon Sep 17 00:00:00 2001
From: Siva <srk.it38@gmail.com>
Date: Wed, 23 May 2018 09:32:36 +0530
Subject: [PATCH 659/949] Squeeze bug fix. (#506)

---
 nnvm/src/top/tensor/transform.cc              |  9 ++++++---
 .../tests/python/unittest/test_infer_shape.py | 19 +++++++++++++++++++
 2 files changed, 25 insertions(+), 3 deletions(-)

diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
index f4e5a7be4841..48f8428d66a4 100644
--- a/nnvm/src/top/tensor/transform.cc
+++ b/nnvm/src/top/tensor/transform.cc
@@ -638,11 +638,14 @@ inline bool SqueezeShape(const nnvm::NodeAttrs& attrs,
   } else {
     std::unordered_set<dim_t> axis_checker;
     for (size_t i = 0; i < param.axis.ndim(); ++i) {
+      int real_axis;
       if (param.axis[i] < 0) {
-        int real_axis = param.axis[i] + static_cast<int>(shp.ndim());
-        CHECK(real_axis < static_cast<int>(shp.ndim()) && real_axis >= 0);
-        axis_checker.insert(real_axis);
+        real_axis = param.axis[i] + static_cast<int>(shp.ndim());
+      } else {
+        real_axis = param.axis[i];
       }
+      CHECK(real_axis < static_cast<int>(shp.ndim()) && real_axis >= 0);
+      axis_checker.insert(real_axis);
     }
     for (size_t i = 0; i < shp.ndim(); ++i) {
       if (axis_checker.find(i) == axis_checker.end()) {
diff --git a/nnvm/tests/python/unittest/test_infer_shape.py b/nnvm/tests/python/unittest/test_infer_shape.py
index 8011e96f3aa6..9fbc93c0757d 100644
--- a/nnvm/tests/python/unittest/test_infer_shape.py
+++ b/nnvm/tests/python/unittest/test_infer_shape.py
@@ -116,6 +116,24 @@ def test_flatten():
     sdict = infer_shape(y)
     assert(sdict["y"][0] == [10, 200])
 
+def test_squeeze():
+    x = sym.Variable("x", shape=(1, 1, 1, 10))
+    y = sym.squeeze(x, axis=(1,2), name='squeeze')
+    sdict = infer_shape(y)
+    assert(sdict['squeeze'][0] == [1, 10])
+
+    x = sym.Variable("x", shape=(1, 3, 1))
+    y = sym.squeeze(x, name='squeeze')
+    sdict = infer_shape(y)
+    assert(sdict['squeeze'][0] == [3])
+
+    y = sym.squeeze(x, axis=(0), name='squeeze')
+    sdict = infer_shape(y)
+    assert(sdict['squeeze'][0] == [3, 1])
+
+    y = sym.squeeze(x, axis=(0,2), name='squeeze')
+    sdict = infer_shape(y)
+    assert(sdict['squeeze'][0] == [3])
 
 # Level 2
 def test_conv2d():
@@ -331,3 +349,4 @@ def check(in_shape, out_shape, **kwargs):
     test_reduce()
     test_transpose()
     test_prelu()
+    test_squeeze()

From f41cb62401081da458bdb713f425846eaced4c52 Mon Sep 17 00:00:00 2001
From: Tao Lv <tao.a.lv@intel.com>
Date: Thu, 24 May 2018 00:40:10 +0800
Subject: [PATCH 660/949] fix doc (#510)

---
 nnvm/docs/dev/overview.md | 2 +-
 nnvm/include/nnvm/pass.h  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/nnvm/docs/dev/overview.md b/nnvm/docs/dev/overview.md
index 35e3e3d72ff5..4f01fdda2a03 100644
--- a/nnvm/docs/dev/overview.md
+++ b/nnvm/docs/dev/overview.md
@@ -103,7 +103,7 @@ void MyFunction() {
   static auto& finplace_option_map = Op::GetAttr<FInplaceOption>("FInplaceOption");
 
   // quick look up attribute of add, O(1) time, vector index lookup internally.
-  auto add_inplace = finplace_option_tbl[add];
+  auto add_inplace = finplace_option_map[add];
 }
 ```
 Besides making the code minimum, this attribute store enables decentralization of projects.
diff --git a/nnvm/include/nnvm/pass.h b/nnvm/include/nnvm/pass.h
index 8b731bbd0f9f..42f952a76398 100644
--- a/nnvm/include/nnvm/pass.h
+++ b/nnvm/include/nnvm/pass.h
@@ -47,7 +47,7 @@ inline Graph ApplyPass(Graph src, const std::string& pass) {
 
 
 /*!
- * \brief Registry entry for DataIterator factory functions.
+ * \brief Registry entry for pass functions.
  */
 struct PassFunctionReg
     : public dmlc::FunctionRegEntryBase<PassFunctionReg,

From cbdc028d48a2a99f8a403155fa865db7b5308c74 Mon Sep 17 00:00:00 2001
From: MORITA Kazutaka <morita.kazutaka@lab.ntt.co.jp>
Date: Fri, 25 May 2018 01:05:17 +0900
Subject: [PATCH 661/949] [FRONTEND][Keras] fix reshape (#493)

---
 nnvm/python/nnvm/frontend/keras.py               | 11 ++++++-----
 nnvm/tests/python/frontend/keras/test_forward.py |  9 +++++++++
 2 files changed, 15 insertions(+), 5 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/keras.py b/nnvm/python/nnvm/frontend/keras.py
index 0d51487c355d..a61fee7e3413 100644
--- a/nnvm/python/nnvm/frontend/keras.py
+++ b/nnvm/python/nnvm/frontend/keras.py
@@ -345,11 +345,12 @@ def _convert_concat(insym, keras_layer, _):
 
 
 def _convert_reshape(insym, keras_layer, _):
-    shape = keras_layer.shape if hasattr(keras_layer, 'shape') \
-       else keras_layer.target_shape if hasattr(keras_layer, 'target_shape') \
-       else None
-    if shape is None:
-        raise TypeError("No shape attribute in reshape layer: {}".format(keras_layer))
+    _check_data_format(keras_layer)
+    ch = keras_layer.input_shape[-1]
+    assert ch == keras_layer.target_shape[-1], \
+        "Only supports last dimension in target shape being equal to " \
+        "the channel number of input tensor."
+    shape = (-1, ch) + keras_layer.target_shape[:-1]
     return _sym.reshape(insym, shape=shape)
 
 
diff --git a/nnvm/tests/python/frontend/keras/test_forward.py b/nnvm/tests/python/frontend/keras/test_forward.py
index 0147a3e2c654..c751b64435db 100644
--- a/nnvm/tests/python/frontend/keras/test_forward.py
+++ b/nnvm/tests/python/frontend/keras/test_forward.py
@@ -134,6 +134,14 @@ def test_forward_relu6():
     verify_keras_frontend(keras_model)
 
 
+def test_forward_reshape():
+    data = keras.layers.Input(shape=(32,32,3))
+    x = keras.layers.Reshape(target_shape=(32,32,3))(data)
+    x = keras.layers.GlobalAveragePooling2D()(x)
+    keras_model = keras.models.Model(data, x)
+    verify_keras_frontend(keras_model)
+
+
 def test_forward_vgg16():
     keras_model = keras.applications.vgg16.VGG16(include_top=True, weights=None,
         input_shape=(224,224,3), classes=1000)
@@ -162,6 +170,7 @@ def test_forward_resnet50():
     test_forward_separable_conv()
     test_forward_upsample()
     test_forward_relu6()
+    test_forward_reshape()
 
     test_forward_vgg16()
     test_forward_xception()

From 9f0def182a1b0d3c4285f0a9d448992445d0cd3d Mon Sep 17 00:00:00 2001
From: MORITA Kazutaka <morita.kazutaka@lab.ntt.co.jp>
Date: Fri, 25 May 2018 01:32:34 +0900
Subject: [PATCH 662/949] [FRONTEND][Keras] Add test for MobileNet (#513)

---
 nnvm/tests/python/frontend/keras/test_forward.py | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/nnvm/tests/python/frontend/keras/test_forward.py b/nnvm/tests/python/frontend/keras/test_forward.py
index c751b64435db..5a399577f376 100644
--- a/nnvm/tests/python/frontend/keras/test_forward.py
+++ b/nnvm/tests/python/frontend/keras/test_forward.py
@@ -160,6 +160,12 @@ def test_forward_resnet50():
     verify_keras_frontend(keras_model)
 
 
+def test_forward_mobilenet():
+    keras_model = keras.applications.mobilenet.MobileNet(include_top=True, weights=None,
+        input_shape=(224,224,3), classes=1000)
+    verify_keras_frontend(keras_model)
+
+
 if __name__ == '__main__':
     test_forward_elemwise_add()
     test_forward_softmax()
@@ -175,3 +181,4 @@ def test_forward_resnet50():
     test_forward_vgg16()
     test_forward_xception()
     test_forward_resnet50()
+    test_forward_mobilenet()

From 42f971d1cc0677d482be0c9f76112130018abcb0 Mon Sep 17 00:00:00 2001
From: Siva <sivar.b@huawei.com>
Date: Thu, 24 May 2018 22:33:33 +0530
Subject: [PATCH 663/949]     Consider version too #508 (#514)

---
 nnvm/src/compiler/precompute_prune.cc | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/nnvm/src/compiler/precompute_prune.cc b/nnvm/src/compiler/precompute_prune.cc
index c3656b640891..9ded7a169bf9 100644
--- a/nnvm/src/compiler/precompute_prune.cc
+++ b/nnvm/src/compiler/precompute_prune.cc
@@ -34,6 +34,9 @@ nnvm::Graph PrecomputePrune(nnvm::Graph src) {
       }
       nnvm::NodePtr var = nnvm::Node::Create();
       var->attrs.name = e.node->attrs.name;
+      if (e.version) {
+          var->attrs.name += "_" + std::to_string(e.version);
+      }
       if (e.node->num_outputs() != 1) {
         var->attrs.name += "_output" + std::to_string(e.index);
       }

From e4deaad114c36e6759349740de96cfdf338001ee Mon Sep 17 00:00:00 2001
From: "Ehsan M. Kermani" <ehsanmo1367@gmail.com>
Date: Fri, 25 May 2018 09:26:23 -0700
Subject: [PATCH 664/949] Expose clip to frontend mxnet (#512)

---
 nnvm/python/nnvm/frontend/mxnet.py            |  7 ++
 .../python/frontend/mxnet/model_zoo/vgg.py    |  2 +-
 .../python/frontend/mxnet/test_forward.py     | 64 ++++++++++++++-----
 3 files changed, 55 insertions(+), 18 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/mxnet.py b/nnvm/python/nnvm/frontend/mxnet.py
index e671acbe7d75..3fc3ca85184a 100644
--- a/nnvm/python/nnvm/frontend/mxnet.py
+++ b/nnvm/python/nnvm/frontend/mxnet.py
@@ -205,6 +205,12 @@ def _upsampling(inputs, attrs):
     new_attrs = {'scale':int(scale)}
     return _get_nnvm_op('upsampling')(inputs[0], **new_attrs)
 
+def _clip(inputs, attrs):
+    op_name, new_attrs = "clip", {}
+    new_attrs['a_min'] = _required_attr(attrs, 'a_min')
+    new_attrs['a_max'] = _required_attr(attrs, 'a_max')
+    return _get_nnvm_op(op_name)(*inputs, **new_attrs)
+
 
 _identity_list = ['__add_scalar__', '__add_symbol__', '__div_scalar__',
                   '__div_symbol__', '__mul_scalar__', '__mul_symbol__',
@@ -248,6 +254,7 @@ def _upsampling(inputs, attrs):
     'reshape'       : _reshape,
     'sum_axis'      : _rename('sum'),
     'UpSampling'    : _upsampling,
+    'clip'          : _clip
 }
 
 def _convert_symbol(op_name, inputs, attrs,
diff --git a/nnvm/tests/python/frontend/mxnet/model_zoo/vgg.py b/nnvm/tests/python/frontend/mxnet/model_zoo/vgg.py
index c9243d3610fa..68215bb80aaa 100644
--- a/nnvm/tests/python/frontend/mxnet/model_zoo/vgg.py
+++ b/nnvm/tests/python/frontend/mxnet/model_zoo/vgg.py
@@ -71,7 +71,7 @@ def get_symbol(num_classes, num_layers=11, batch_norm=False, dtype='float32', **
                 13: ([2, 2, 2, 2, 2], [64, 128, 256, 512, 512]),
                 16: ([2, 2, 3, 3, 3], [64, 128, 256, 512, 512]),
                 19: ([2, 2, 4, 4, 4], [64, 128, 256, 512, 512])}
-    if not vgg_spec.has_key(num_layers):
+    if num_layers not in vgg_spec:
         raise ValueError("Invalide num_layers {}. Possible choices are 11,13,16,19.".format(num_layers))
     layers, filters = vgg_spec[num_layers]
     data = mx.sym.Variable(name="data")
diff --git a/nnvm/tests/python/frontend/mxnet/test_forward.py b/nnvm/tests/python/frontend/mxnet/test_forward.py
index 0f9747538ce9..fca19a693855 100644
--- a/nnvm/tests/python/frontend/mxnet/test_forward.py
+++ b/nnvm/tests/python/frontend/mxnet/test_forward.py
@@ -8,24 +8,41 @@
 from nnvm.testing.config import ctx_list
 from nnvm import frontend
 import mxnet as mx
+from mxnet import gluon
+from mxnet.gluon.model_zoo import vision
 import model_zoo
 
 
-def verify_mxnet_frontend_impl(mx_symbol, data_shape=(1, 3, 224, 224), out_shape=(1, 1000)):
+def verify_mxnet_frontend_impl(mx_symbol, data_shape=(1, 3, 224, 224), out_shape=(1, 1000),
+                               gluon_impl=False, name=None):
     """Use name different from test to avoid let nose pick it up"""
-    def get_mxnet_output(symbol, x, dtype='float32'):
-        from collections import namedtuple
-        Batch = namedtuple('Batch', ['data'])
-        mod = mx.mod.Module(symbol, label_names=None)
-        mod.bind(data_shapes=[('data', x.shape)], for_training=False)
-        mod.init_params()
-        mod.forward(Batch([mx.nd.array(x.astype(dtype))]))
-        out = mod.get_outputs()[0].asnumpy()
-        args, auxs = mod.get_params()
-        return out, args, auxs
+    if gluon_impl:
+        def get_gluon_output(name, x):
+            net = vision.get_model(name)
+            net.collect_params().initialize(mx.init.Xavier())
+            net_sym = gluon.nn.SymbolBlock(outputs=net(mx.sym.var('data')),
+                                           inputs=mx.sym.var('data'),
+                                           params=net.collect_params())
+            out = net_sym(mx.nd.array(x.astype(dtype))).asnumpy()
+            return out, net_sym
+    else:
+        def get_mxnet_output(symbol, x, dtype='float32'):
+            from collections import namedtuple
+            Batch = namedtuple('Batch', ['data'])
+            mod = mx.mod.Module(symbol, label_names=None)
+            mod.bind(data_shapes=[('data', x.shape)], for_training=False)
+            mod.init_params()
+            mod.forward(Batch([mx.nd.array(x.astype(dtype))]))
+            out = mod.get_outputs()[0].asnumpy()
+            args, auxs = mod.get_params()
+            return out, args, auxs
 
     def get_tvm_output(symbol, x, args, auxs, target, ctx, dtype='float32'):
-        new_sym, params = frontend.from_mxnet(symbol, args, auxs)
+        if gluon_impl:
+            new_sym, params = frontend.from_mxnet(symbol)
+        else:
+            new_sym, params = frontend.from_mxnet(symbol, args, auxs)
+
         dshape = x.shape
         shape_dict = {'data': dshape}
         with nnvm.compiler.build_config(opt_level=3):
@@ -42,11 +59,17 @@ def get_tvm_output(symbol, x, args, auxs, target, ctx, dtype='float32'):
     # random input
     dtype = 'float32'
     x = np.random.uniform(size=data_shape)
-    mx_out, args, auxs = get_mxnet_output(mx_symbol, x, dtype)
-    assert "data" not in args
-    for target, ctx in ctx_list():
-        tvm_out = get_tvm_output(mx_symbol, x, args, auxs, target, ctx, dtype)
-        np.testing.assert_allclose(mx_out, tvm_out, rtol=1e-5, atol=1e-5)
+    if gluon_impl:
+        gluon_out, gluon_sym = get_gluon_output(name, x)
+        for target, ctx in ctx_list():
+            tvm_out = get_tvm_output(gluon_sym, x, None, None, target, ctx, dtype)
+            np.testing.assert_allclose(gluon_out, tvm_out, rtol=1e-5, atol=1e-5)
+    else:
+        mx_out, args, auxs = get_mxnet_output(mx_symbol, x, dtype)
+        assert "data" not in args
+        for target, ctx in ctx_list():
+            tvm_out = get_tvm_output(mx_symbol, x, args, auxs, target, ctx, dtype)
+            np.testing.assert_allclose(mx_out, tvm_out, rtol=1e-5, atol=1e-5)
 
 def test_forward_mlp():
     mlp = model_zoo.mx_mlp
@@ -91,6 +114,12 @@ def test_forward_fc_flatten():
     except:
         pass
 
+def test_forward_clip():
+    data = mx.sym.var('data')
+    data = mx.sym.concat(data, -data, dim=1)  # negative part explicity
+    mx_sym = mx.sym.clip(data, a_min=0, a_max=1)
+    verify_mxnet_frontend_impl(mx_sym, (1, 3, 100, 100), (1, 6, 100, 100))
+
 if __name__ == '__main__':
     test_forward_mlp()
     test_forward_vgg()
@@ -99,3 +128,4 @@ def test_forward_fc_flatten():
     test_forward_rrelu()
     test_forward_softrelu()
     test_forward_fc_flatten()
+    test_forward_clip()

From 0cbda5d7c3d77cea6f6c7d73adeccbbfca4cb14f Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Sat, 26 May 2018 04:44:44 +0530
Subject: [PATCH 665/949] Flip operator (#505)

---
 nnvm/include/nnvm/top/tensor.h                |  8 +++
 nnvm/python/nnvm/top/transform.py             |  4 ++
 nnvm/src/top/tensor/transform.cc              | 49 +++++++++++++++++++
 nnvm/tests/python/compiler/test_top_level4.py | 23 +++++++++
 4 files changed, 84 insertions(+)

diff --git a/nnvm/include/nnvm/top/tensor.h b/nnvm/include/nnvm/top/tensor.h
index 00bad8245713..80947bd237bc 100644
--- a/nnvm/include/nnvm/top/tensor.h
+++ b/nnvm/include/nnvm/top/tensor.h
@@ -156,6 +156,14 @@ struct TransposeParam : public dmlc::Parameter<TransposeParam> {
   }
 };
 
+struct FlipParam : public dmlc::Parameter<FlipParam> {
+  int axis;
+  DMLC_DECLARE_PARAMETER(FlipParam) {
+    DMLC_DECLARE_FIELD(axis).set_default(0)
+    .describe("the axis to be reveresed.");
+  }
+};
+
 struct BroadcastToParam : public dmlc::Parameter<BroadcastToParam> {
   TShape shape;
 
diff --git a/nnvm/python/nnvm/top/transform.py b/nnvm/python/nnvm/top/transform.py
index c3ceb68682ee..b4b8779f2a68 100644
--- a/nnvm/python/nnvm/top/transform.py
+++ b/nnvm/python/nnvm/top/transform.py
@@ -41,6 +41,10 @@ def compute_reshape_like(attrs, inputs, out_info):
 reg.register_pattern("transpose", OpPattern.INJECTIVE)
 reg.register_schedule("transpose", _fschedule_injective)
 
+# flip
+reg.register_pattern("flip", OpPattern.INJECTIVE)
+reg.register_schedule("flip", _fschedule_injective)
+
 # reshape
 reg.register_pattern("reshape", OpPattern.INJECTIVE)
 reg.register_schedule("reshape", _fschedule_injective)
diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
index 48f8428d66a4..bdc8dc5a9c40 100644
--- a/nnvm/src/top/tensor/transform.cc
+++ b/nnvm/src/top/tensor/transform.cc
@@ -830,5 +830,54 @@ Examples::
     };
 });
 
+// Flip
+DMLC_REGISTER_PARAMETER(FlipParam);
+
+NNVM_REGISTER_OP(flip)
+.describe(R"code(Reverse the elements of an array.
+
+Examples::
+
+  x = [[ 1, 2],
+       [ 3, 4]]
+
+  flip(x) = [[ 3.,  4.],
+                  [ 1.,  2.]]
+
+  x = [[[ 1.,  2.],
+        [ 3.,  4.]],
+
+       [[ 5.,  6.],
+        [ 7.,  8.]]]
+
+  flip(x) = [[[ 5.,  6.],
+                   [ 7.,  8.]],
+
+                  [[ 1.,  2.],
+                   [ 3.,  4.]]]
+
+  flip(x, axis=1) = [[[ 3.,  4.],
+                                 [ 1.,  2.]],
+
+                                [[ 7.,  8.],
+                                 [ 5.,  6.]]]
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "Tensor", "Source input")
+.add_arguments(FlipParam::__FIELDS__())
+.set_attr_parser(ParamParser<FlipParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<FlipParam>)
+.set_attr<nnvm::FInferShape>("FInferShape", ElemwiseShape<1, 1>)
+.set_attr<nnvm::FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_num_inputs(1)
+.set_num_outputs(1)
+.set_support_level(4)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const FlipParam& param = nnvm::get<FlipParam>(attrs.parsed);
+    return Array<Tensor>{ topi::flip(inputs[0], param.axis) };
+});
+
 }  // namespace top
 }  // namespace nnvm
diff --git a/nnvm/tests/python/compiler/test_top_level4.py b/nnvm/tests/python/compiler/test_top_level4.py
index c6e8620fc972..819768cfb341 100644
--- a/nnvm/tests/python/compiler/test_top_level4.py
+++ b/nnvm/tests/python/compiler/test_top_level4.py
@@ -90,6 +90,28 @@ def test_reduce():
     verify_reduce((4, 4, 3), np.min, sym.min, keepdims=True)
     verify_reduce((4, 4, 3), np.sum, sym.sum, axis=(0, 2))
 
+def verify_flip(ishape, axis):
+    x = sym.Variable("x")
+    y = sym.flip(x, axis=axis) + 1
+    dtype = "float32"
+    x_np = np.random.uniform(size=ishape).astype(dtype)
+    res = np.flip(x_np, axis) + 1
+
+    for target, ctx in ctx_list():
+        # set input
+        graph, lib, _ = nnvm.compiler.build(y, target, {"x": ishape})
+        m = graph_runtime.create(graph, lib, ctx)
+        m.run(x=x_np)
+        out = m.get_output(0, tvm.nd.empty(res.shape))
+        np.testing.assert_allclose(out.asnumpy(), res, atol=1e-5, rtol=1e-5)
+
+def test_flip():
+    verify_flip((3, 4, 3), 1)
+    verify_flip((3, 4, 3), 0)
+    verify_flip((3, 4, 3), 2)
+    verify_flip((3, 4, 3), -1)
+    verify_flip((3, 4, 3), -3)
+    verify_flip((3, 4, 3), -2)
 
 def verify_reshape(dshape, oshape):
     x = sym.Variable("x")
@@ -347,4 +369,5 @@ def test_full():
     test_elemwise_sum()
     test_block_grad()
     test_full()
+    test_flip()
     print(nnvm.compiler.engine.dump())

From f34d9f7b4fbc41c93fe4553d6fd62e4640296a44 Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Fri, 25 May 2018 18:08:51 -0700
Subject: [PATCH 666/949] [DOCS] documentation merge

---
 Jenkinsfile                                   |  10 +-
 Makefile                                      |  19 +-
 docs/Doxyfile                                 |   2 +-
 docs/conf.py                                  |   5 +-
 include/tvm/api_registry.h                    |   2 +-
 include/tvm/arithmetic.h                      |   2 +-
 include/tvm/base.h                            |   2 +-
 include/tvm/buffer.h                          |   2 +-
 include/tvm/build_module.h                    |   2 +-
 include/tvm/c_dsl_api.h                       |   2 +-
 include/tvm/channel.h                         |   2 +-
 include/tvm/codegen.h                         |   2 +-
 include/tvm/expr.h                            |   2 +-
 include/tvm/ir.h                              |   2 +-
 include/tvm/ir_functor_ext.h                  |   2 +-
 include/tvm/ir_mutator.h                      |   2 +-
 include/tvm/ir_operator.h                     |   2 +-
 include/tvm/ir_pass.h                         |   2 +-
 include/tvm/ir_visitor.h                      |   2 +-
 include/tvm/logging.h                         |   2 +-
 include/tvm/lowered_func.h                    |   2 +-
 include/tvm/operation.h                       |   2 +-
 include/tvm/packed_func_ext.h                 |   2 +-
 include/tvm/runtime/c_backend_api.h           |   2 +-
 include/tvm/runtime/c_runtime_api.h           |   4 +-
 include/tvm/runtime/config.h                  |   2 +-
 include/tvm/runtime/device_api.h              |   4 +-
 include/tvm/runtime/module.h                  |   2 +-
 include/tvm/runtime/packed_func.h             |   2 +-
 include/tvm/runtime/registry.h                |   2 +-
 include/tvm/runtime/threading_backend.h       |   2 +-
 include/tvm/runtime/util.h                    |   2 +-
 include/tvm/schedule.h                        |   4 +-
 include/tvm/schedule_pass.h                   |   2 +-
 include/tvm/target_info.h                     |   2 +-
 include/tvm/tensor.h                          |   2 +-
 include/tvm/tensor_intrin.h                   |   2 +-
 include/tvm/tvm.h                             |   2 +-
 nnvm/.gitkeep                                 |   0
 nnvm/CMakeLists.txt                           |  15 +-
 nnvm/Jenkinsfile                              | 126 -----------
 nnvm/LICENSE                                  | 201 ------------------
 nnvm/Makefile                                 |  20 +-
 nnvm/NEWS.md                                  |  17 --
 nnvm/include/nnvm/base.h                      |   2 +-
 nnvm/include/nnvm/c_api.h                     |   2 +-
 nnvm/include/nnvm/compiler/packed_func_ext.h  |   2 +-
 nnvm/include/nnvm/compiler/util.h             |   2 +-
 nnvm/include/nnvm/graph.h                     |   2 +-
 nnvm/include/nnvm/graph_attr_types.h          |   2 +-
 nnvm/include/nnvm/layout.h                    |   2 +-
 nnvm/include/nnvm/node.h                      |   2 +-
 nnvm/include/nnvm/op.h                        |   2 +-
 nnvm/include/nnvm/pass.h                      |   4 +-
 nnvm/include/nnvm/pass_functions.h            |   2 +-
 nnvm/include/nnvm/symbolic.h                  |   2 +-
 nnvm/include/nnvm/top/nn.h                    |   2 +-
 nnvm/include/nnvm/top/tensor.h                |   2 +-
 nnvm/include/nnvm/tuple.h                     |   2 +-
 nnvm/src/top/tensor/matrix_op.cc              |   4 +-
 nnvm/tests/ci_build/Dockerfile.gpu            |  24 +++
 nnvm/tests/scripts/task_build.sh              |  16 --
 nnvm/tests/scripts/task_python_test.sh        |  11 -
 nnvm/tutorials/README.txt                     |   3 -
 tests/ci_build/Dockerfile.gpu                 |  44 ++--
 .../ci_build/install/ubuntu_install_coreml.sh |   1 +
 .../install/ubuntu_install_darknet.sh         |   4 +
 .../ci_build/install/ubuntu_install_keras.sh  |   1 +
 .../ci_build/install/ubuntu_install_mxnet.sh  |   2 +
 tests/ci_build/install/ubuntu_install_onnx.sh |   7 +
 tests/scripts/task_build.sh                   |   7 +
 {nnvm/tests => tests}/scripts/task_clean.sh   |   2 +-
 tests/scripts/task_python_nnvm.sh             |  20 ++
 topi/include/topi/nn.h                        |   2 +-
 tutorials/nnvm/README.txt                     |   2 +
 .../nnvm}/define_and_compile_model.py         |   0
 .../nnvm}/deploy_model_on_mali_gpu.py         |   0
 .../nnvm}/deploy_model_on_rasp.py             |   0
 .../nnvm}/from_coreml.py                      |   0
 .../nnvm}/from_darknet.py                     |   0
 .../nnvm}/from_keras.py                       |   0
 .../nnvm}/from_mxnet.py                       |   0
 .../nnvm}/from_mxnet_to_webgl.py              |   0
 .../tutorials => tutorials/nnvm}/from_onnx.py |   0
 .../nnvm}/get_started.py                      |   0
 .../nnvm}/imagenet_inference_gpu.py           |   0
 .../nnvm}/using_external_lib.py               |   0
 .../nnvm}/web/resnet.html                     |   0
 88 files changed, 181 insertions(+), 490 deletions(-)
 delete mode 100644 nnvm/.gitkeep
 delete mode 100644 nnvm/Jenkinsfile
 delete mode 100644 nnvm/LICENSE
 delete mode 100644 nnvm/NEWS.md
 delete mode 100755 nnvm/tests/scripts/task_build.sh
 delete mode 100755 nnvm/tests/scripts/task_python_test.sh
 delete mode 100644 nnvm/tutorials/README.txt
 create mode 100644 tests/ci_build/install/ubuntu_install_coreml.sh
 create mode 100644 tests/ci_build/install/ubuntu_install_darknet.sh
 create mode 100644 tests/ci_build/install/ubuntu_install_keras.sh
 create mode 100644 tests/ci_build/install/ubuntu_install_mxnet.sh
 create mode 100644 tests/ci_build/install/ubuntu_install_onnx.sh
 create mode 100755 tests/scripts/task_build.sh
 rename {nnvm/tests => tests}/scripts/task_clean.sh (88%)
 create mode 100755 tests/scripts/task_python_nnvm.sh
 create mode 100644 tutorials/nnvm/README.txt
 rename {nnvm/tutorials => tutorials/nnvm}/define_and_compile_model.py (100%)
 rename {nnvm/tutorials => tutorials/nnvm}/deploy_model_on_mali_gpu.py (100%)
 rename {nnvm/tutorials => tutorials/nnvm}/deploy_model_on_rasp.py (100%)
 rename {nnvm/tutorials => tutorials/nnvm}/from_coreml.py (100%)
 rename {nnvm/tutorials => tutorials/nnvm}/from_darknet.py (100%)
 rename {nnvm/tutorials => tutorials/nnvm}/from_keras.py (100%)
 rename {nnvm/tutorials => tutorials/nnvm}/from_mxnet.py (100%)
 rename {nnvm/tutorials => tutorials/nnvm}/from_mxnet_to_webgl.py (100%)
 rename {nnvm/tutorials => tutorials/nnvm}/from_onnx.py (100%)
 rename {nnvm/tutorials => tutorials/nnvm}/get_started.py (100%)
 rename {nnvm/tutorials => tutorials/nnvm}/imagenet_inference_gpu.py (100%)
 rename {nnvm/tutorials => tutorials/nnvm}/using_external_lib.py (100%)
 rename {nnvm/tutorials => tutorials/nnvm}/web/resnet.html (100%)

diff --git a/Jenkinsfile b/Jenkinsfile
index 13636d108379..3378a02ca2d3 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -7,7 +7,8 @@
 tvm_runtime = "lib/libtvm_runtime.so, config.mk"
 tvm_lib = "lib/libtvm.so, " + tvm_runtime
 // LLVM upstream lib
-tvm_multilib = "lib/libtvm_llvm40.so, lib/libtvm_llvm50.so, lib/libtvm_llvm60.so, lib/libtvm_topi.so, " + tvm_runtime
+tvm_multilib = "lib/libtvm_llvm40.so, lib/libtvm_llvm50.so, lib/libtvm_llvm60.so, " +
+             "lib/libtvm_topi.so, nnvm/lib/libnnvm_compiler.so, " + tvm_runtime
 
 // command to start a docker container
 docker_run = 'tests/ci_build/ci_build.sh'
@@ -50,11 +51,11 @@ stage("Sanity Check") {
 def make(docker_type, make_flag) {
   timeout(time: max_time, unit: 'MINUTES') {
     try {
-      sh "${docker_run} ${docker_type} make ${make_flag}"
+      sh "${docker_run} ${docker_type} ./tests/scripts/task_build.sh ${make_flag}"
     } catch (exc) {
       echo 'Incremental compilation failed. Fall back to build from scratch'
-      sh "${docker_run} ${docker_type} make clean"
-      sh "${docker_run} ${docker_type} make ${make_flag}"
+      sh "${docker_run} ${docker_type} ./tests/scripts/task_clean.sh"
+      sh "${docker_run} ${docker_type} ./tests/scripts/task_build.sh ${make_flag}"
     }
   }
 }
@@ -231,6 +232,7 @@ stage('Integration Test') {
           sh "${docker_run} gpu ./tests/scripts/task_python_integration.sh"
           sh "${docker_run} gpu ./tests/scripts/task_python_topi.sh"
           sh "${docker_run} gpu ./tests/scripts/task_cpp_topi.sh"
+          sh "${docker_run} gpu ./tests/scripts/task_python_nnvm.sh"
         }
       }
     }
diff --git a/Makefile b/Makefile
index ef4c7b1e91ab..b27220f01e48 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,8 @@ endif
 
 include $(config)
 
-.PHONY: clean install installdev all test doc pylint cpplint lint verilog cython cython2 cython3 web runtime
+.PHONY: clean install installdev all test doc pylint cpplint lint\
+	 verilog cython cython2 cython3 web topi nnvm runtime
 
 ifndef DMLC_CORE_PATH
   DMLC_CORE_PATH = $(ROOTDIR)/dmlc-core
@@ -36,6 +37,7 @@ EMCC_FLAGS= -std=c++11 -DDMLC_LOG_STACK_TRACE=0\
 	-s EXTRA_EXPORTED_RUNTIME_METHODS="['cwrap','getValue','setValue','addFunction']"\
 	-s USE_GLFW=3 -s USE_WEBGL2=1 -lglfw\
 	$(INCLUDE_FLAGS)
+
 # llvm configuration
 ifdef LLVM_CONFIG
 	LLVM_VERSION=$(shell $(LLVM_CONFIG) --version| cut -b 1,3)
@@ -62,6 +64,8 @@ SGX_SRC = $(wildcard src/runtime/sgx/untrusted/*.cc)
 RPC_SRC = $(wildcard src/runtime/rpc/*.cc)
 GRAPH_SRC = $(wildcard src/runtime/graph/*.cc)
 RUNTIME_SRC = $(wildcard src/runtime/*.cc)
+
+# TOPI
 TOPI_SRC = $(wildcard topi/src/*.cc)
 
 # Objectives
@@ -78,7 +82,7 @@ RPC_OBJ = $(patsubst src/%.cc, build/%.o, $(RPC_SRC))
 GRAPH_OBJ = $(patsubst src/%.cc, build/%.o, $(GRAPH_SRC))
 CC_OBJ = $(patsubst src/%.cc, build/%.o, $(CC_SRC)) $(LLVM_OBJ)
 RUNTIME_OBJ = $(patsubst src/%.cc, build/%.o, $(RUNTIME_SRC))
-TOPI_OBJ = $(patsubst topi/%.cc, build/%.o, $(TOPI_SRC))
+TOPI_OBJ = $(patsubst topi/src/%.cc, build/topi/%.o, $(TOPI_SRC))
 CONTRIB_OBJ =
 
 # Deps
@@ -245,14 +249,15 @@ else
 endif
 
 BUILD_TARGETS ?= lib/libtvm.$(SHARED_LIBRARY_SUFFIX) \
-	lib/libtvm_runtime.$(SHARED_LIBRARY_SUFFIX) \
-  lib/libtvm_topi.$(SHARED_LIBRARY_SUFFIX)
+				 lib/libtvm_runtime.$(SHARED_LIBRARY_SUFFIX) \
+				 lib/libtvm_topi.$(SHARED_LIBRARY_SUFFIX)
 
 all: ${BUILD_TARGETS}
 runtime: lib/libtvm_runtime.$(SHARED_LIBRARY_SUFFIX)
 web: lib/libtvm_web_runtime.js lib/libtvm_web_runtime.bc
 topi: lib/libtvm_topi.$(SHARED_LIBRARY_SUFFIX)
 
+
 include tests/cpp/unittest.mk
 
 test: $(TEST)
@@ -291,9 +296,9 @@ build/%.o: src/%.cc
 	$(CXX) $(CFLAGS) -MM -MT build/$*.o $< >build/$*.d
 	$(CXX) -c $(CFLAGS) -c $< -o $@
 
-build/src/%.o: topi/src/%.cc
+build/topi/%.o: topi/src/%.cc
 	@mkdir -p $(@D)
-	$(CXX) $(CFLAGS) -MM -MT build/src/$*.o $< >build/src/$*.d
+	$(CXX) $(CFLAGS) -MM -MT build/$*.o $< >build/$*.d
 	$(CXX) -c $(CFLAGS) -c $< -o $@
 
 lib/libtvm.${SHARED_LIBRARY_SUFFIX}: $(ALL_DEP) $(RUNTIME_DEP)
@@ -326,12 +331,14 @@ LIBHALIDEIR:
 
 cpplint:
 	python dmlc-core/scripts/lint.py topi cpp topi/include;
+	python dmlc-core/scripts/lint.py nnvm cpp nnvm/include nnvm/src;
 	python dmlc-core/scripts/lint.py tvm cpp include src verilog\
 	 examples/extension/src examples/graph_executor/src
 
 pylint:
 	pylint python/tvm --rcfile=$(ROOTDIR)/tests/lint/pylintrc
 	pylint topi/python/topi --rcfile=$(ROOTDIR)/tests/lint/pylintrc
+	pylint nnvm/python/nnvm --rcfile=$(ROOTDIR)/tests/lint/pylintrc
 
 jnilint:
 	python dmlc-core/scripts/lint.py tvm4j-jni cpp jvm/native/src
diff --git a/docs/Doxyfile b/docs/Doxyfile
index 87e7da7043e5..90c10ebaef79 100644
--- a/docs/Doxyfile
+++ b/docs/Doxyfile
@@ -753,7 +753,7 @@ WARN_LOGFILE           =
 # spaces.
 # Note: If this tag is empty the current directory is searched.
 
-INPUT                  = include/tvm topi/include/topi
+INPUT                  = include/tvm topi/include/topi nnvm/include/nnvm
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
diff --git a/docs/conf.py b/docs/conf.py
index 77a04bd64c77..84eeb9db79e7 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -25,6 +25,7 @@
 curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
 sys.path.insert(0, os.path.join(curr_path, '../python/'))
 sys.path.insert(0, os.path.join(curr_path, '../topi/python'))
+sys.path.insert(0, os.path.join(curr_path, '../nnvm/python'))
 
 # -- General configuration ------------------------------------------------
 
@@ -40,6 +41,7 @@
     '.md': CommonMarkParser
 }
 os.environ['TVM_BUILD_DOC'] = '1'
+os.environ['NNVM_BUILD_DOC'] = '1'
 # Version information.
 import tvm
 version = tvm.__version__
@@ -187,7 +189,8 @@ def run_doxygen(folder):
 subsection_order = ExplicitOrder(
     ['../tutorials/language',
      '../tutorials/optimize',
-     '../tutorials/deployment'])
+     '../tutorials/deployment',
+     '../tutorials/nnvm'])
 
 
 def generate_doxygen_xml(app):
diff --git a/include/tvm/api_registry.h b/include/tvm/api_registry.h
index 9c2b5194ebe9..93bff2762481 100644
--- a/include/tvm/api_registry.h
+++ b/include/tvm/api_registry.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2017 by Contributors
- * \file api_registry.h
+ * \file tvm/api_registry.h
  * \brief This files include necessary headers to
  *  be used to register an global API function.
  */
diff --git a/include/tvm/arithmetic.h b/include/tvm/arithmetic.h
index c4f338f1cd47..6a3c395fd404 100644
--- a/include/tvm/arithmetic.h
+++ b/include/tvm/arithmetic.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file arithmetic.h
+ * \file tvm/arithmetic.h
  * \brief Algebra and set operations and simplifications.
  */
 #ifndef TVM_ARITHMETIC_H_
diff --git a/include/tvm/base.h b/include/tvm/base.h
index 9a78c5ed503f..d113f45352bb 100644
--- a/include/tvm/base.h
+++ b/include/tvm/base.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file base.h
+ * \file tvm/base.h
  * \brief Defines the base data structure
  */
 #ifndef TVM_BASE_H_
diff --git a/include/tvm/buffer.h b/include/tvm/buffer.h
index 8b04bf550cb2..41fa1fa804a8 100644
--- a/include/tvm/buffer.h
+++ b/include/tvm/buffer.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file buffer.h
+ * \file tvm/buffer.h
  * \brief Symbolic n-dimensional array, to represent a memory buffer.
  */
 #ifndef TVM_BUFFER_H_
diff --git a/include/tvm/build_module.h b/include/tvm/build_module.h
index 9c54ab7b45fb..d81462982965 100644
--- a/include/tvm/build_module.h
+++ b/include/tvm/build_module.h
@@ -1,6 +1,6 @@
 /*!
 *  Copyright (c) 2017 by Contributors
-* \file build_module.h
+* \file tvm/build_module.h
 * \brief Functions for compiling ops.
 */
 #ifndef TVM_BUILD_MODULE_H_
diff --git a/include/tvm/c_dsl_api.h b/include/tvm/c_dsl_api.h
index 0ba34ed5ccee..6f15ef9a3e80 100644
--- a/include/tvm/c_dsl_api.h
+++ b/include/tvm/c_dsl_api.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file c_dsl_api.h
+ * \file tvm/c_dsl_api.h
  *
  * \brief TVM DSL Node C API, used to interact to DSL compilation.
  *
diff --git a/include/tvm/channel.h b/include/tvm/channel.h
index 56adff4ad8df..28d9b5f7ce4a 100644
--- a/include/tvm/channel.h
+++ b/include/tvm/channel.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2017 by Contributors
- * \file channel.h
+ * \file tvm/channel.h
  * \brief Channel object for pipeline.
  */
 #ifndef TVM_CHANNEL_H_
diff --git a/include/tvm/codegen.h b/include/tvm/codegen.h
index c43227bb1164..6b5116a143cc 100644
--- a/include/tvm/codegen.h
+++ b/include/tvm/codegen.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file codegen.h
+ * \file tvm/codegen.h
  * \brief Collection of Lowlevel IR pass to codegen.
  */
 #ifndef TVM_CODEGEN_H_
diff --git a/include/tvm/expr.h b/include/tvm/expr.h
index d2816358be0b..8c789f8df1dc 100644
--- a/include/tvm/expr.h
+++ b/include/tvm/expr.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file expr.h
+ * \file tvm/expr.h
  * \brief The Expr and related elements in DataFlow construction.
  */
 #ifndef TVM_EXPR_H_
diff --git a/include/tvm/ir.h b/include/tvm/ir.h
index 6c7bbda0bc27..8c398c9a5584 100644
--- a/include/tvm/ir.h
+++ b/include/tvm/ir.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file ir.h
+ * \file tvm/ir.h
  * \brief Additional high level nodes in the IR
  */
 #ifndef TVM_IR_H_
diff --git a/include/tvm/ir_functor_ext.h b/include/tvm/ir_functor_ext.h
index 55368fbea14d..3784608c8da1 100644
--- a/include/tvm/ir_functor_ext.h
+++ b/include/tvm/ir_functor_ext.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2017 by Contributors
- * \file ir_functor_ext.h
+ * \file tvm/ir_functor_ext.h
  * \brief More powerful Visitor that allows define function signatures.
  */
 #ifndef TVM_IR_FUNCTOR_EXT_H_
diff --git a/include/tvm/ir_mutator.h b/include/tvm/ir_mutator.h
index 1faf1724ddb6..b8aae3638149 100644
--- a/include/tvm/ir_mutator.h
+++ b/include/tvm/ir_mutator.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file ir_mutator.h
+ * \file tvm/ir_mutator.h
  * \brief Defines general IRMutation pass
  */
 #ifndef TVM_IR_MUTATOR_H_
diff --git a/include/tvm/ir_operator.h b/include/tvm/ir_operator.h
index a2cd01042db5..ee349c092ee6 100644
--- a/include/tvm/ir_operator.h
+++ b/include/tvm/ir_operator.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2017 by Contributors
- * \file ir_operator.h
+ * \file tvm/ir_operator.h
  * \brief Common operators of Expr
  */
 #ifndef TVM_IR_OPERATOR_H_
diff --git a/include/tvm/ir_pass.h b/include/tvm/ir_pass.h
index a5bcecda16b6..4be1643429ea 100644
--- a/include/tvm/ir_pass.h
+++ b/include/tvm/ir_pass.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file ir_pass.h
+ * \file tvm/ir_pass.h
  * \brief Collection of IR pass functions
  *
  *  When the pass functions in this file are for Stmt,
diff --git a/include/tvm/ir_visitor.h b/include/tvm/ir_visitor.h
index 7cfd45b833c8..8919b0f7a5c2 100644
--- a/include/tvm/ir_visitor.h
+++ b/include/tvm/ir_visitor.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file ir_visitor.h
+ * \file tvm/ir_visitor.h
  * \brief Visitor to quickly visit IR trees
  */
 #ifndef TVM_IR_VISITOR_H_
diff --git a/include/tvm/logging.h b/include/tvm/logging.h
index fdd063082a88..070b6e092a2e 100644
--- a/include/tvm/logging.h
+++ b/include/tvm/logging.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2018 by Contributors
- * \file logging.h
+ * \file tvm/logging.h
  * \brief logging utilities on top of dmlc-core
  */
 #ifndef TVM_LOGGING_H_
diff --git a/include/tvm/lowered_func.h b/include/tvm/lowered_func.h
index 7b7ebcf1e4d5..19f7e27f1c75 100644
--- a/include/tvm/lowered_func.h
+++ b/include/tvm/lowered_func.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2017 by Contributors
- * \file lowered_func.h
+ * \file tvm/lowered_func.h
  * \brief Information about a lowered TVM function.
  *  This data structure is final step toward codegen.
  */
diff --git a/include/tvm/operation.h b/include/tvm/operation.h
index 67dfe138cae6..5e74f6d99d76 100644
--- a/include/tvm/operation.h
+++ b/include/tvm/operation.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file operation.h
+ * \file tvm/operation.h
  * \brief Operation node can generate one or multiple Tensors
  */
 #ifndef TVM_OPERATION_H_
diff --git a/include/tvm/packed_func_ext.h b/include/tvm/packed_func_ext.h
index a598286001a3..455717ce753c 100644
--- a/include/tvm/packed_func_ext.h
+++ b/include/tvm/packed_func_ext.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file packed_func_ext.h
+ * \file tvm/packed_func_ext.h
  * \brief Extension package to PackedFunc
  *   This enales pass NodeRef types into/from PackedFunc.
  */
diff --git a/include/tvm/runtime/c_backend_api.h b/include/tvm/runtime/c_backend_api.h
index 01e8a9486655..60e284610494 100644
--- a/include/tvm/runtime/c_backend_api.h
+++ b/include/tvm/runtime/c_backend_api.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2017 by Contributors
- * \file c_backend_api.h
+ * \file tvm/runtime/c_backend_api.h
  * \brief TVM runtime backend API.
  *
  *  The functions defined in this header are intended to be
diff --git a/include/tvm/runtime/c_runtime_api.h b/include/tvm/runtime/c_runtime_api.h
index af89860b5784..7aef4396e315 100644
--- a/include/tvm/runtime/c_runtime_api.h
+++ b/include/tvm/runtime/c_runtime_api.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file c_runtime_api.h
+ * \file tvm/runtime/c_runtime_api.h
  * \brief TVM runtime library.
  *
  *  The philosophy of TVM project is to customize the compilation
@@ -438,7 +438,7 @@ TVM_DLL int TVMArrayCopyFromTo(TVMArrayHandle from,
                                TVMStreamHandle stream);
 
 /*!
- * \brief Create a new runtime stream.  
+ * \brief Create a new runtime stream.
  *
  * \param device_type The device type of context
  * \param device_id The device id of context
diff --git a/include/tvm/runtime/config.h b/include/tvm/runtime/config.h
index 73857f1a939e..c288f6048c8d 100644
--- a/include/tvm/runtime/config.h
+++ b/include/tvm/runtime/config.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2017 by Contributors
- * \file config.h
+ * \file tvm/runtime/config.h
  * \brief Runtime library related configurations.
  */
 #ifndef TVM_RUNTIME_CONFIG_H_
diff --git a/include/tvm/runtime/device_api.h b/include/tvm/runtime/device_api.h
index e43cc1f318ec..334d3e61b2b6 100644
--- a/include/tvm/runtime/device_api.h
+++ b/include/tvm/runtime/device_api.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file device_api.h
+ * \file tvm/runtime/device_api.h
  * \brief Abstract device memory management API
  */
 #ifndef TVM_RUNTIME_DEVICE_API_H_
@@ -124,7 +124,7 @@ class DeviceAPI {
   /*!
    * \brief Synchronize 2 streams of execution.
    *
-   * An event is created in event_src stream that the second then 
+   * An event is created in event_src stream that the second then
    * stream waits on.  Neither event_src or event_dst need to be of
    * the same device ID as the context, but they must be of the same
    * device type.
diff --git a/include/tvm/runtime/module.h b/include/tvm/runtime/module.h
index f8e5069f56c5..de0b02500b6d 100644
--- a/include/tvm/runtime/module.h
+++ b/include/tvm/runtime/module.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2017 by Contributors
- * \file module.h
+ * \file tvm/runtime/module.h
  * \brief Runtime container of the functions generated by TVM,
  *  This is used to support dynamically link, load and save
  *  functions from different convention under unified API.
diff --git a/include/tvm/runtime/packed_func.h b/include/tvm/runtime/packed_func.h
index c5c86535cee2..16266b4f99c9 100644
--- a/include/tvm/runtime/packed_func.h
+++ b/include/tvm/runtime/packed_func.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2017 by Contributors
- * \file packed_func.h
+ * \file tvm/runtime/packed_func.h
  * \brief Type-erased function used across TVM API.
  */
 #ifndef TVM_RUNTIME_PACKED_FUNC_H_
diff --git a/include/tvm/runtime/registry.h b/include/tvm/runtime/registry.h
index 5b0cc788de06..2a328c8086e0 100644
--- a/include/tvm/runtime/registry.h
+++ b/include/tvm/runtime/registry.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2017 by Contributors
- * \file registry.h
+ * \file tvm/runtime/registry.h
  * \brief This file defines the TVM global function registry.
  *
  *  The registered functions will be made available to front-end
diff --git a/include/tvm/runtime/threading_backend.h b/include/tvm/runtime/threading_backend.h
index 6c8c4f5eb084..66ef8612febe 100644
--- a/include/tvm/runtime/threading_backend.h
+++ b/include/tvm/runtime/threading_backend.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2018 by Contributors
- * \file threading_backend.h
+ * \file tvm/runtime/threading_backend.h
  * \brief Utilities for manipulating thread pool threads.
  */
 #ifndef TVM_RUNTIME_THREADING_BACKEND_H_
diff --git a/include/tvm/runtime/util.h b/include/tvm/runtime/util.h
index 1a4961476c79..160642ffcc85 100644
--- a/include/tvm/runtime/util.h
+++ b/include/tvm/runtime/util.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2017 by Contributors
- * \file util.h
+ * \file tvm/runtime/util.h
  * \brief Useful runtime util.
  */
 #ifndef TVM_RUNTIME_UTIL_H_
diff --git a/include/tvm/schedule.h b/include/tvm/schedule.h
index 594e8b8a7c16..a08d2dcb7881 100644
--- a/include/tvm/schedule.h
+++ b/include/tvm/schedule.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file schedule.h
+ * \file tvm/schedule.h
  * \brief Define a schedule.
  */
 #ifndef TVM_SCHEDULE_H_
@@ -317,7 +317,7 @@ class Schedule : public NodeRef {
    *  The data will be transformed back to the original layout in the original tensor.
    *  User can further call compute_inline to inline the original layout and keep
    *  the data stored in the transformed layout.
-   * 
+   *
    * \param tensor The tensor to be produced.
    * \param scope The scope of the storage.
    * \return The created tensor.
diff --git a/include/tvm/schedule_pass.h b/include/tvm/schedule_pass.h
index 3f0334facc2b..cd248f8b9b96 100644
--- a/include/tvm/schedule_pass.h
+++ b/include/tvm/schedule_pass.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file schedule_pass.h
+ * \file tvm/schedule_pass.h
  * \brief  Collection of Schedule pass functions.
  *
  *  These passes works on the schedule hyper-graph
diff --git a/include/tvm/target_info.h b/include/tvm/target_info.h
index d00cd1e2b60c..8569f188a4ab 100644
--- a/include/tvm/target_info.h
+++ b/include/tvm/target_info.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2017 by Contributors
- * \file target_info.h
+ * \file tvm/target_info.h
  * \brief Various information about target.
  */
 #ifndef TVM_TARGET_INFO_H_
diff --git a/include/tvm/tensor.h b/include/tvm/tensor.h
index 0a74f97c0640..1a6338d9058c 100644
--- a/include/tvm/tensor.h
+++ b/include/tvm/tensor.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file tensor.h
+ * \file tvm/tensor.h
  * \brief Dataflow tensor object
  */
 #ifndef TVM_TENSOR_H_
diff --git a/include/tvm/tensor_intrin.h b/include/tvm/tensor_intrin.h
index b34751dcb91a..bd3fd11021b4 100644
--- a/include/tvm/tensor_intrin.h
+++ b/include/tvm/tensor_intrin.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2017 by Contributors
- * \file tensor_intrin.h
+ * \file tvm/tensor_intrin.h
  * \brief Tensor intrinsic operations.
  */
 #ifndef TVM_TENSOR_INTRIN_H_
diff --git a/include/tvm/tvm.h b/include/tvm/tvm.h
index 3766821acd11..7e9c4305ffbb 100644
--- a/include/tvm/tvm.h
+++ b/include/tvm/tvm.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file tvm.h
+ * \file tvm/tvm.h
  * \brief Header to include all C++ API.
  */
 #ifndef TVM_TVM_H_
diff --git a/nnvm/.gitkeep b/nnvm/.gitkeep
deleted file mode 100644
index e69de29bb2d1..000000000000
diff --git a/nnvm/CMakeLists.txt b/nnvm/CMakeLists.txt
index 2926073eb03f..7523b5a19d61 100644
--- a/nnvm/CMakeLists.txt
+++ b/nnvm/CMakeLists.txt
@@ -19,10 +19,10 @@ endif()
 
 # include path
 include_directories(BEFORE "include")
-include_directories("tvm/include")
-include_directories("tvm/dlpack/include")
-include_directories("tvm/HalideIR/src")
-include_directories("tvm/topi/include")
+include_directories("../include")
+include_directories("../dlpack/include")
+include_directories("../HalideIR/src")
+include_directories("../topi/include")
 
 set(NNVM_LINKER_LIBS "")
 set(NNVM_COMPILER_LINKER_LIBS "")
@@ -48,8 +48,7 @@ if(MSVC)
   endif()
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc")
   find_library(TVM_LIB tvm
-			   HINTS ${CMAKE_CURRENT_SOURCE_DIR}/../tvm/build/Release
-			   HINTS ${CMAKE_CURRENT_SOURCE_DIR}/tvm/build/Release)
+			   HINTS ${CMAKE_CURRENT_SOURCE_DIR}/../build/Release)
   message(STATUS "Build with TVM libary: " ${TVM_LIB})
   list(APPEND NNVM_COMPILER_LINKER_LIBS ${TVM_LIB})
 else(MSVC)
@@ -86,8 +85,8 @@ file(GLOB_RECURSE COMPILER_SRCS
     src/top/*.cc
 )
 
-if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/dmlc-core/CMakeLists.txt)
-  include_directories(${CMAKE_CURRENT_SOURCE_DIR}/dmlc-core/include)
+if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/../dmlc-core/CMakeLists.txt)
+  include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../dmlc-core/include)
 elseif(DMLC_CORE_PATH)
   include_directories(${DMLC_CORE_PATH}/include)
 endif()
diff --git a/nnvm/Jenkinsfile b/nnvm/Jenkinsfile
deleted file mode 100644
index b299cee33f48..000000000000
--- a/nnvm/Jenkinsfile
+++ /dev/null
@@ -1,126 +0,0 @@
-#!groovy
-// -*- mode: groovy -*-
-// Jenkins pipeline
-// See documents at https://jenkins.io/doc/book/pipeline/jenkinsfile/
-
-// nnvm libraries
-nnvm_lib = "tvm/lib/libtvm.so, tvm/lib/libtvm_runtime.so, lib/libnnvm_compiler.so"
-
-// command to start a docker container
-docker_run = 'tests/ci_build/ci_build.sh'
-// timeout in minutes
-max_time = 60
-
-// initialize source codes
-def init_git() {
-  checkout scm
-  retry(5) {
-    timeout(time: 2, unit: 'MINUTES') {
-      sh 'git submodule update --init --recursive'
-    }
-  }
-}
-
-def init_git_win() {
-    checkout scm
-    retry(5) {
-        timeout(time: 2, unit: 'MINUTES') {
-            bat 'git submodule update --init --recursive'
-        }
-    }
-}
-
-stage("Sanity Check") {
-  timeout(time: max_time, unit: 'MINUTES') {
-    node('linux') {
-      ws('workspace/tvm/sanity') {
-        init_git()
-        sh "${docker_run} lint  ./tests/scripts/task_lint.sh"
-      }
-    }
-  }
-}
-
-// Run make. First try to do an incremental make from a previous workspace in hope to
-// accelerate the compilation. If something wrong, clean the workspace and then
-// build from scratch.
-def make(docker_type, make_flag) {
-  timeout(time: max_time, unit: 'MINUTES') {
-    try {
-      sh "${docker_run} ${docker_type} ./tests/scripts/task_build.sh ${make_flag}"
-    } catch (exc) {
-      echo 'Incremental compilation failed. Fall back to build from scratch'
-      sh "${docker_run} ${docker_type} ./tests/scripts/task_clean.sh"
-      sh "${docker_run} ${docker_type} ./tests/scripts/task_build.sh ${make_flag}"
-    }
-  }
-}
-
-// pack libraries for later use
-def pack_lib(name, libs) {
-  sh """
-     echo "Packing ${libs} into ${name}"
-     echo ${libs} | sed -e 's/,/ /g' | xargs md5sum
-     """
-  stash includes: libs, name: name
-}
-
-
-// unpack libraries saved before
-def unpack_lib(name, libs) {
-  unstash name
-  sh """
-     echo "Unpacked ${libs} from ${name}"
-     echo ${libs} | sed -e 's/,/ /g' | xargs md5sum
-     """
-}
-
-stage('Build') {
-  timeout(time: max_time, unit: 'MINUTES') {
-    node('GPU' && 'linux') {
-      ws('workspace/nnvm/build-gpu') {
-        init_git()
-        make('gpu', '-j2')
-        pack_lib('gpu', nnvm_lib)
-      }
-    }
-  }
-}
-
-stage('Tests') {
-  parallel 'python': {
-    node('GPU' && 'linux') {
-      ws('workspace/nnvm/it-python-gpu') {
-        init_git()
-        unpack_lib('gpu', nnvm_lib)
-        timeout(time: max_time, unit: 'MINUTES') {
-          sh "${docker_run} gpu ./tests/scripts/task_python_test.sh"
-          sh "${docker_run} gpu ./tests/scripts/task_frontend_test.sh"
-        }
-      }
-    }
-  },
-  'docs': {
-    node('GPU' && 'linux') {
-      ws('workspace/nnvm/docs-python-gpu') {
-        init_git()
-        unpack_lib('gpu', nnvm_lib)
-        timeout(time: max_time, unit: 'MINUTES') {
-          sh "${docker_run} gpu ./tests/scripts/task_python_docs.sh"
-        }
-        pack_lib('mydocs', 'docs.tgz')
-      }
-    }
-  }
-}
-
-stage('Deploy') {
-    node('docker' && 'doc') {
-      ws('workspace/nnvm/deploy-docs') {
-        if (env.BRANCH_NAME == "master") {
-           unpack_lib('mydocs', 'docs.tgz')
-           sh "tar xf docs.tgz -C /var/nnvm-docs"
-        }
-      }
-    }
-}
diff --git a/nnvm/LICENSE b/nnvm/LICENSE
deleted file mode 100644
index 3163d9248efc..000000000000
--- a/nnvm/LICENSE
+++ /dev/null
@@ -1,201 +0,0 @@
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "{}"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright 2016 by contributors.
-
-   Licensed 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.
diff --git a/nnvm/Makefile b/nnvm/Makefile
index 8055a7e55eea..adbae329e144 100644
--- a/nnvm/Makefile
+++ b/nnvm/Makefile
@@ -9,20 +9,16 @@ endif
 endif
 include $(config)
 
+TVMPATH = ..
+
 export LDFLAGS = -pthread -lm
 export CFLAGS = -std=c++11 -Wall -O2 -Iinclude -fPIC
-CFLAGS += -Itvm/include -Itvm/dlpack/include -Itvm/HalideIR/src -Itvm/topi/include
-
-ifdef DMLC_CORE_PATH
-  CFLAGS += -I$(DMLC_CORE_PATH)/include
-else
-  CFLAGS += -I$(ROOTDIR)/dmlc-core/include
-endif
+CFLAGS += -I$(TVMPATH)/include -I$(TVMPATH)/dlpack/include -I$(TVMPATH)/HalideIR/src -I$(TVMPATH)/topi/include
 
 ifdef DMLC_CORE_PATH
   CFLAGS += -I$(DMLC_CORE_PATH)/include
 else
-  CFLAGS += -I$(ROOTDIR)/dmlc-core/include
+  CFLAGS += -I$(ROOTDIR)/../dmlc-core/include
 endif
 
 ifneq ($(ADD_CFLAGS), NONE)
@@ -89,15 +85,15 @@ cyclean:
 
 lint: pylint cpplint
 
+doc:
+	doxygen docs/Doxyfile
+
 cpplint:
-	python dmlc-core/scripts/lint.py nnvm cpp include src
+	python ../dmlc-core/scripts/lint.py nnvm cpp include src
 
 pylint:
 	pylint python/nnvm --rcfile=$(ROOTDIR)/tests/lint/pylintrc
 
-doc:
-	doxygen docs/Doxyfile
-
 clean:
 	$(RM) -rf build lib bin *~ */*~ */*/*~ */*/*/*~ */*.o */*/*.o */*/*/*.o cli_test
 
diff --git a/nnvm/NEWS.md b/nnvm/NEWS.md
deleted file mode 100644
index 8093e9ef375c..000000000000
--- a/nnvm/NEWS.md
+++ /dev/null
@@ -1,17 +0,0 @@
-NNVM Change Log
-===============
-
-This file records the changes in TVM library in reverse chronological order.
-
-## 0.8rc
-
-- This is major change in NNVM to introduce end to end compiler stack.
-  - The NNVM compiler stack ready
-- Core tensor operators
-- integrates compiler with TVM
-- The libnnvm.a is still independent from compiler modules.
-
-## 0.7
-
-- NNVM graph
-- Basic pass of serialization, gradient, infer_shape, place_deice, plan_memory
diff --git a/nnvm/include/nnvm/base.h b/nnvm/include/nnvm/base.h
index 310fc5b6ab79..449bd2f4626e 100644
--- a/nnvm/include/nnvm/base.h
+++ b/nnvm/include/nnvm/base.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file base.h
+ * \file nnvm/base.h
  * \brief Configuration of nnvm as well as basic data structure.
  */
 #ifndef NNVM_BASE_H_
diff --git a/nnvm/include/nnvm/c_api.h b/nnvm/include/nnvm/c_api.h
index 2f6134b08b48..daf9b564f3fa 100644
--- a/nnvm/include/nnvm/c_api.h
+++ b/nnvm/include/nnvm/c_api.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file c_api.h
+ * \file nnvm/c_api.h
  * \brief C API of NNVM symbolic construction and pass.
  *  Enables construction and transformation of Graph
  *  in any other host languages.
diff --git a/nnvm/include/nnvm/compiler/packed_func_ext.h b/nnvm/include/nnvm/compiler/packed_func_ext.h
index bd768ff90a85..e289fd4efa59 100644
--- a/nnvm/include/nnvm/compiler/packed_func_ext.h
+++ b/nnvm/include/nnvm/compiler/packed_func_ext.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2017 by Contributors
- * \file packed_func_ext.h
+ * \file nnvm/compiler/packed_func_ext.h
  * \brief Extension to enable packed functionn for nnvm types
  */
 #ifndef NNVM_COMPILER_PACKED_FUNC_EXT_H_
diff --git a/nnvm/include/nnvm/compiler/util.h b/nnvm/include/nnvm/compiler/util.h
index 04095c7838af..5d5bc4478530 100644
--- a/nnvm/include/nnvm/compiler/util.h
+++ b/nnvm/include/nnvm/compiler/util.h
@@ -1,6 +1,6 @@
 /*!
 *  Copyright (c) 2016 by Contributors
-* \file util.h
+* \file nnvm/compiler/util.h
 * \brief Utility functions for nnvm compiler
 */
 #ifndef NNVM_COMPILER_UTIL_H_
diff --git a/nnvm/include/nnvm/graph.h b/nnvm/include/nnvm/graph.h
index 3f9ab59bb4c4..1d3b662ff0b8 100644
--- a/nnvm/include/nnvm/graph.h
+++ b/nnvm/include/nnvm/graph.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file graph.h
+ * \file nnvm/graph.h
  * \brief Configuation of nnvm as well as basic data structure.
  */
 #ifndef NNVM_GRAPH_H_
diff --git a/nnvm/include/nnvm/graph_attr_types.h b/nnvm/include/nnvm/graph_attr_types.h
index 87a332f2c151..2bd998fedfbb 100644
--- a/nnvm/include/nnvm/graph_attr_types.h
+++ b/nnvm/include/nnvm/graph_attr_types.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file graph_attr_types.h
+ * \file nnvm/graph_attr_types.h
  * \brief Data structures that can appear in graph attributes.
  */
 #ifndef NNVM_GRAPH_ATTR_TYPES_H_
diff --git a/nnvm/include/nnvm/layout.h b/nnvm/include/nnvm/layout.h
index 494db60c4779..bec523b34811 100644
--- a/nnvm/include/nnvm/layout.h
+++ b/nnvm/include/nnvm/layout.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2018 by Contributors
- * \file layout.h
+ * \file nnvm/layout.h
  * \brief Layout expression.
  *        The layout is composed of upper cases, lower cases and numbers,
  *        where upper case indicates a (super-)dimension and
diff --git a/nnvm/include/nnvm/node.h b/nnvm/include/nnvm/node.h
index 2c7d0ef30680..15db77ee6043 100644
--- a/nnvm/include/nnvm/node.h
+++ b/nnvm/include/nnvm/node.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file node.h
+ * \file nnvm/node.h
  * \brief Graph node data structure.
  */
 #ifndef NNVM_NODE_H_
diff --git a/nnvm/include/nnvm/op.h b/nnvm/include/nnvm/op.h
index 4d74c994277b..5bdfcaca169d 100644
--- a/nnvm/include/nnvm/op.h
+++ b/nnvm/include/nnvm/op.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file op.h
+ * \file nnvm/op.h
  * \brief Operator information structor.
  */
 #ifndef NNVM_OP_H_
diff --git a/nnvm/include/nnvm/pass.h b/nnvm/include/nnvm/pass.h
index 42f952a76398..016d5ee2a763 100644
--- a/nnvm/include/nnvm/pass.h
+++ b/nnvm/include/nnvm/pass.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file pass.h
+ * \file nnvm/pass.h
  * \brief Pass that can be applied to a graph.
  */
 #ifndef NNVM_PASS_H_
@@ -83,7 +83,7 @@ struct PassFunctionReg
     return *this;
   }
   /*!
-   * \brief Declare this pass requires the given operator attribute to be 
+   * \brief Declare this pass requires the given operator attribute to be
    *        available before being applied on the graph.
    * \param attr_name Name of the attribute.
    * \return Reference to self.
diff --git a/nnvm/include/nnvm/pass_functions.h b/nnvm/include/nnvm/pass_functions.h
index e4884a196e79..4c29e09d813a 100644
--- a/nnvm/include/nnvm/pass_functions.h
+++ b/nnvm/include/nnvm/pass_functions.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file pass_functions.h
+ * \file nnvm/pass_functions.h
  * \brief Pass functions that simply redirect the calls to ApplyPass
  *
  *  This file serves as documentation on how to use functions implemented in "src/pass".
diff --git a/nnvm/include/nnvm/symbolic.h b/nnvm/include/nnvm/symbolic.h
index f290a1683792..ebb2ab5d30d0 100644
--- a/nnvm/include/nnvm/symbolic.h
+++ b/nnvm/include/nnvm/symbolic.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file symbolic.h
+ * \file nnvm/symbolic.h
  * \brief Symbolic graph construction API
  *
  *  This API is optional, but useful to allow user
diff --git a/nnvm/include/nnvm/top/nn.h b/nnvm/include/nnvm/top/nn.h
index ec21abde9b8c..7593cf88ae8f 100644
--- a/nnvm/include/nnvm/top/nn.h
+++ b/nnvm/include/nnvm/top/nn.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2017 by Contributors
- * \file nn.h
+ * \file nnvm/top/nn.h
  * \brief Auxiliary param for tensor primitive.
  */
 #ifndef NNVM_TOP_NN_H_
diff --git a/nnvm/include/nnvm/top/tensor.h b/nnvm/include/nnvm/top/tensor.h
index 80947bd237bc..6a2beaa2309a 100644
--- a/nnvm/include/nnvm/top/tensor.h
+++ b/nnvm/include/nnvm/top/tensor.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2017 by Contributors
- * \file tensor.h
+ * \file nnvm/top/tensor.h
  * \brief Auxiliary param for tensor primitive.
  */
 #ifndef NNVM_TOP_TENSOR_H_
diff --git a/nnvm/include/nnvm/tuple.h b/nnvm/include/nnvm/tuple.h
index c5caa9bd8fe9..d2db6a4d821c 100644
--- a/nnvm/include/nnvm/tuple.h
+++ b/nnvm/include/nnvm/tuple.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2016 by Contributors
- * \file tuple.h
+ * \file nnvm/tuple.h
  * \brief Data structure Tuple and TShape to store dynamic sized shapes.
  */
 #ifndef NNVM_TUPLE_H_
diff --git a/nnvm/src/top/tensor/matrix_op.cc b/nnvm/src/top/tensor/matrix_op.cc
index 87aae9b2d43c..d28097b1090d 100644
--- a/nnvm/src/top/tensor/matrix_op.cc
+++ b/nnvm/src/top/tensor/matrix_op.cc
@@ -59,8 +59,8 @@ inline bool DotCorrectLayout(const NodeAttrs& attrs,
     // concat lhs and rhs layout
     const Layout& lhs_out = param.transpose_a ? lhs.reverse() : lhs;
     const Layout& rhs_out = param.transpose_b ? rhs.reverse() : rhs;
-    Layout out = std::move(lhs_out.sublayout(0, lhs_out.ndim()-1) +
-                           rhs_out.sublayout(1, rhs_out.ndim()-1));
+    Layout out = lhs_out.sublayout(0, lhs_out.ndim()-1) +
+        rhs_out.sublayout(1, rhs_out.ndim()-1);
     NNVM_ASSIGN_LAYOUT(*olayouts, 0, out);
   }
   return true;
diff --git a/nnvm/tests/ci_build/Dockerfile.gpu b/nnvm/tests/ci_build/Dockerfile.gpu
index 2ee5ed04e91f..1206582ee088 100644
--- a/nnvm/tests/ci_build/Dockerfile.gpu
+++ b/nnvm/tests/ci_build/Dockerfile.gpu
@@ -28,6 +28,23 @@ RUN cd recommonmark; python setup.py install
 # Enable doxygen for c++ doc build
 RUN apt-get update && apt-get install -y doxygen graphviz libprotobuf-dev protobuf-compiler
 
+
+COPY install/ubuntu_install_java.sh /install/ubuntu_install_java.sh
+RUN bash /install/ubuntu_install_java.sh
+
+COPY install/ubuntu_install_nodejs.sh /install/ubuntu_install_nodejs.sh
+RUN bash /install/ubuntu_install_nodejs.sh
+
+COPY install/ubuntu_install_rocm.sh /install/ubuntu_install_rocm.sh
+RUN bash /install/ubuntu_install_rocm.sh
+
+COPY install/ubuntu_install_opengl.sh /install/ubuntu_install_opengl.sh
+RUN bash /install/ubuntu_install_opengl.sh
+
+COPY install/ubuntu_install_vulkan.sh /install/ubuntu_install_vulkan.sh
+RUN bash /install/ubuntu_install_vulkan.sh
+
+
 # DL Frameworks
 COPY install/ubuntu_install_mxnet.sh /install/ubuntu_install_mxnet.sh
 RUN bash /install/ubuntu_install_mxnet.sh
@@ -53,3 +70,10 @@ ENV CPLUS_INCLUDE_PATH=/usr/local/cuda/include:${CPLUS_INCLUDE_PATH}
 ENV C_INCLUDE_PATH=/usr/local/cuda/include:${C_INCLUDE_PATH}
 ENV LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/nvidia/lib64:${LIBRARY_PATH}
 ENV LD_LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/nvidia/lib64:${LD_LIBRARY_PATH}
+
+ENV LD_LIBRARY_PATH=/opt/rocm/lib:${LD_LIBRARY_PATH}
+ENV PATH=/node_modules/.bin:${PATH}
+ENV VULKAN_SDK=/usr/local/VulkanSDK/1.0.65.0/x86_64
+ENV PATH=${PATH}:${VULKAN_SDK}/bin
+ENV LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${VULKAN_SDK}/lib
+ENV VK_LAYER_PATH=${VULKAN_SDK}/etc/explicit_layer.d
diff --git a/nnvm/tests/scripts/task_build.sh b/nnvm/tests/scripts/task_build.sh
deleted file mode 100755
index e8ced824ff94..000000000000
--- a/nnvm/tests/scripts/task_build.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/bash
-echo "Build TVM..."
-cd tvm
-cp make/config.mk .
-echo USE_CUDNN=1 >> config.mk
-echo USE_CUDA=1 >> config.mk
-echo USE_OPENCL=1 >> config.mk
-echo LLVM_CONFIG=llvm-config-4.0 >> config.mk
-echo USE_RPC=1 >> config.mk
-echo USE_BLAS=openblas >> config.mk
-echo USE_GRAPH_RUNTIME=1 >> config.mk
-make "$@"
-cd ..
-
-echo "Build NNVM..."
-make "$@"
diff --git a/nnvm/tests/scripts/task_python_test.sh b/nnvm/tests/scripts/task_python_test.sh
deleted file mode 100755
index c8dc662cc73c..000000000000
--- a/nnvm/tests/scripts/task_python_test.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/bash
-
-export PYTHONPATH=python:tvm/python:tvm/topi/python
-
-echo "Running unittest..."
-python -m nose -v tests/python/unittest || exit -1
-python3 -m nose -v tests/python/unittest || exit -1
-
-echo "Running compiler test..."
-python -m nose -v tests/python/compiler || exit -1
-python3 -m nose -v tests/python/compiler || exit -1
diff --git a/nnvm/tutorials/README.txt b/nnvm/tutorials/README.txt
deleted file mode 100644
index 72f772fa6feb..000000000000
--- a/nnvm/tutorials/README.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-Tutorials
-=========
-This page contains the tutorials about NNVM.
diff --git a/tests/ci_build/Dockerfile.gpu b/tests/ci_build/Dockerfile.gpu
index 19418ed97e8c..2ee5ed04e91f 100644
--- a/tests/ci_build/Dockerfile.gpu
+++ b/tests/ci_build/Dockerfile.gpu
@@ -1,6 +1,7 @@
 FROM nvidia/cuda:8.0-cudnn7-devel
 
 # Base scripts
+RUN apt-get update --fix-missing
 
 COPY install/ubuntu_install_core.sh /install/ubuntu_install_core.sh
 RUN bash /install/ubuntu_install_core.sh
@@ -11,8 +12,8 @@ RUN bash /install/ubuntu_install_python.sh
 COPY install/ubuntu_install_llvm.sh /install/ubuntu_install_llvm.sh
 RUN bash /install/ubuntu_install_llvm.sh
 
-COPY install/ubuntu_install_iverilog.sh /install/ubuntu_install_iverilog.sh
-RUN bash /install/ubuntu_install_iverilog.sh
+COPY install/ubuntu_install_opencl.sh /install/ubuntu_install_opencl.sh
+RUN bash /install/ubuntu_install_opencl.sh
 
 COPY install/ubuntu_install_python_package.sh /install/ubuntu_install_python_package.sh
 RUN bash /install/ubuntu_install_python_package.sh
@@ -24,42 +25,31 @@ RUN bash /install/ubuntu_install_sphinx.sh
 RUN git clone https://github.com/rtfd/recommonmark
 RUN cd recommonmark; python setup.py install
 
-COPY install/ubuntu_install_java.sh /install/ubuntu_install_java.sh
-RUN bash /install/ubuntu_install_java.sh
+# Enable doxygen for c++ doc build
+RUN apt-get update && apt-get install -y doxygen graphviz libprotobuf-dev protobuf-compiler
 
-COPY install/ubuntu_install_nodejs.sh /install/ubuntu_install_nodejs.sh
-RUN bash /install/ubuntu_install_nodejs.sh
+# DL Frameworks
+COPY install/ubuntu_install_mxnet.sh /install/ubuntu_install_mxnet.sh
+RUN bash /install/ubuntu_install_mxnet.sh
 
-COPY install/ubuntu_install_rocm.sh /install/ubuntu_install_rocm.sh
-RUN bash /install/ubuntu_install_rocm.sh
+COPY install/ubuntu_install_onnx.sh /install/ubuntu_install_onnx.sh
+RUN bash /install/ubuntu_install_onnx.sh
 
-COPY install/ubuntu_install_opengl.sh /install/ubuntu_install_opengl.sh
-RUN bash /install/ubuntu_install_opengl.sh
+COPY install/ubuntu_install_coreml.sh /install/ubuntu_install_coreml.sh
+RUN bash /install/ubuntu_install_coreml.sh
 
-COPY install/ubuntu_install_opencl.sh /install/ubuntu_install_opencl.sh
-RUN bash /install/ubuntu_install_opencl.sh
+COPY install/ubuntu_install_keras.sh /install/ubuntu_install_keras.sh
+RUN bash /install/ubuntu_install_keras.sh
 
-# Enable doxygen for c++ doc build
-RUN apt-get update && apt-get install -y doxygen graphviz
-
-# Install vulkan
-COPY install/ubuntu_install_vulkan.sh /install/ubuntu_install_vulkan.sh
-RUN bash /install/ubuntu_install_vulkan.sh
+COPY install/ubuntu_install_darknet.sh /install/ubuntu_install_darknet.sh
+RUN bash /install/ubuntu_install_darknet.sh
 
+RUN pip install Pillow
 
 # Environment variables
-ENV PATH=/node_modules/.bin:${PATH}
 ENV PATH=/usr/local/nvidia/bin:${PATH}
 ENV PATH=/usr/local/cuda/bin:${PATH}
 ENV CPLUS_INCLUDE_PATH=/usr/local/cuda/include:${CPLUS_INCLUDE_PATH}
 ENV C_INCLUDE_PATH=/usr/local/cuda/include:${C_INCLUDE_PATH}
 ENV LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/nvidia/lib64:${LIBRARY_PATH}
 ENV LD_LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/nvidia/lib64:${LD_LIBRARY_PATH}
-ENV LD_LIBRARY_PATH=/opt/rocm/lib:${LD_LIBRARY_PATH}
-
-ENV PATH=/node_modules/.bin:${PATH}
-
-ENV VULKAN_SDK=/usr/local/VulkanSDK/1.0.65.0/x86_64
-ENV PATH=${PATH}:${VULKAN_SDK}/bin
-ENV LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${VULKAN_SDK}/lib
-ENV VK_LAYER_PATH=${VULKAN_SDK}/etc/explicit_layer.d
diff --git a/tests/ci_build/install/ubuntu_install_coreml.sh b/tests/ci_build/install/ubuntu_install_coreml.sh
new file mode 100644
index 000000000000..3cfd6c293590
--- /dev/null
+++ b/tests/ci_build/install/ubuntu_install_coreml.sh
@@ -0,0 +1 @@
+pip2 install coremltools
diff --git a/tests/ci_build/install/ubuntu_install_darknet.sh b/tests/ci_build/install/ubuntu_install_darknet.sh
new file mode 100644
index 000000000000..f5e0c2791d80
--- /dev/null
+++ b/tests/ci_build/install/ubuntu_install_darknet.sh
@@ -0,0 +1,4 @@
+#install the necessary dependancies, cffi, opencv
+wget 'https://github.com/siju-samuel/darknet/blob/master/lib/libdarknet.so?raw=true' -O libdarknet.so
+pip2 install opencv-python cffi
+pip3 install opencv-python cffi
diff --git a/tests/ci_build/install/ubuntu_install_keras.sh b/tests/ci_build/install/ubuntu_install_keras.sh
new file mode 100644
index 000000000000..9730d83bf469
--- /dev/null
+++ b/tests/ci_build/install/ubuntu_install_keras.sh
@@ -0,0 +1 @@
+pip2 install keras tensorflow h5py
diff --git a/tests/ci_build/install/ubuntu_install_mxnet.sh b/tests/ci_build/install/ubuntu_install_mxnet.sh
new file mode 100644
index 000000000000..ebf641e2a910
--- /dev/null
+++ b/tests/ci_build/install/ubuntu_install_mxnet.sh
@@ -0,0 +1,2 @@
+pip2 install mxnet
+pip3 install mxnet
diff --git a/tests/ci_build/install/ubuntu_install_onnx.sh b/tests/ci_build/install/ubuntu_install_onnx.sh
new file mode 100644
index 000000000000..138e70d71200
--- /dev/null
+++ b/tests/ci_build/install/ubuntu_install_onnx.sh
@@ -0,0 +1,7 @@
+pip2 install onnx>=1.1.0
+pip3 install onnx>=1.1.0
+
+pip2 install http://download.pytorch.org/whl/cu75/torch-0.2.0.post3-cp27-cp27mu-manylinux1_x86_64.whl
+pip2 install torchvision
+pip3 install http://download.pytorch.org/whl/cu75/torch-0.2.0.post3-cp35-cp35m-manylinux1_x86_64.whl
+pip3 install torchvision
diff --git a/tests/scripts/task_build.sh b/tests/scripts/task_build.sh
new file mode 100755
index 000000000000..dc524f1403d2
--- /dev/null
+++ b/tests/scripts/task_build.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+echo "Build TVM..."
+make "$@"
+cd nnvm
+
+echo "Build NNVM..."
+make "$@"
diff --git a/nnvm/tests/scripts/task_clean.sh b/tests/scripts/task_clean.sh
similarity index 88%
rename from nnvm/tests/scripts/task_clean.sh
rename to tests/scripts/task_clean.sh
index f4c02ed639c3..baecd292ff8f 100755
--- a/nnvm/tests/scripts/task_clean.sh
+++ b/tests/scripts/task_clean.sh
@@ -1,6 +1,6 @@
 #!/bin/bash
 echo "Cleanup data..."
-cd tvm
+cd nnvm
 make clean
 cd ..
 make clean
diff --git a/tests/scripts/task_python_nnvm.sh b/tests/scripts/task_python_nnvm.sh
new file mode 100755
index 000000000000..5bccc27f59ba
--- /dev/null
+++ b/tests/scripts/task_python_nnvm.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+export PYTHONPATH=nnvm/python:python:topi/python
+
+echo "Running unittest..."
+python -m nose -v nnvm/tests/python/unittest || exit -1
+python3 -m nose -v nnvm/tests/python/unittest || exit -1
+
+echo "Running compiler test..."
+python -m nose -v nnvm/tests/python/compiler || exit -1
+python3 -m nose -v nnvm/tests/python/compiler || exit -1
+
+echo "Running ONNX frontend test..."
+python -m nose -v tests/python/frontend/onnx || exit -1
+
+echo "Running MXNet frontend test..."
+python -m nose -v tests/python/frontend/mxnet || exit -1
+
+echo "Running Keras frontend test..."
+python -m nose -v tests/python/frontend/keras || exit -1
diff --git a/topi/include/topi/nn.h b/topi/include/topi/nn.h
index d2aac839b7c6..5e140a893482 100644
--- a/topi/include/topi/nn.h
+++ b/topi/include/topi/nn.h
@@ -1,7 +1,7 @@
 /*!
  *  Copyright (c) 2017 by Contributors
  * \brief NN op constructions
- * \file nn.h
+ * \file topi/nn.h
  */
 #ifndef TOPI_NN_H_
 #define TOPI_NN_H_
diff --git a/tutorials/nnvm/README.txt b/tutorials/nnvm/README.txt
new file mode 100644
index 000000000000..d6629821c2ef
--- /dev/null
+++ b/tutorials/nnvm/README.txt
@@ -0,0 +1,2 @@
+Compile Deep Learning Models
+----------------------------
diff --git a/nnvm/tutorials/define_and_compile_model.py b/tutorials/nnvm/define_and_compile_model.py
similarity index 100%
rename from nnvm/tutorials/define_and_compile_model.py
rename to tutorials/nnvm/define_and_compile_model.py
diff --git a/nnvm/tutorials/deploy_model_on_mali_gpu.py b/tutorials/nnvm/deploy_model_on_mali_gpu.py
similarity index 100%
rename from nnvm/tutorials/deploy_model_on_mali_gpu.py
rename to tutorials/nnvm/deploy_model_on_mali_gpu.py
diff --git a/nnvm/tutorials/deploy_model_on_rasp.py b/tutorials/nnvm/deploy_model_on_rasp.py
similarity index 100%
rename from nnvm/tutorials/deploy_model_on_rasp.py
rename to tutorials/nnvm/deploy_model_on_rasp.py
diff --git a/nnvm/tutorials/from_coreml.py b/tutorials/nnvm/from_coreml.py
similarity index 100%
rename from nnvm/tutorials/from_coreml.py
rename to tutorials/nnvm/from_coreml.py
diff --git a/nnvm/tutorials/from_darknet.py b/tutorials/nnvm/from_darknet.py
similarity index 100%
rename from nnvm/tutorials/from_darknet.py
rename to tutorials/nnvm/from_darknet.py
diff --git a/nnvm/tutorials/from_keras.py b/tutorials/nnvm/from_keras.py
similarity index 100%
rename from nnvm/tutorials/from_keras.py
rename to tutorials/nnvm/from_keras.py
diff --git a/nnvm/tutorials/from_mxnet.py b/tutorials/nnvm/from_mxnet.py
similarity index 100%
rename from nnvm/tutorials/from_mxnet.py
rename to tutorials/nnvm/from_mxnet.py
diff --git a/nnvm/tutorials/from_mxnet_to_webgl.py b/tutorials/nnvm/from_mxnet_to_webgl.py
similarity index 100%
rename from nnvm/tutorials/from_mxnet_to_webgl.py
rename to tutorials/nnvm/from_mxnet_to_webgl.py
diff --git a/nnvm/tutorials/from_onnx.py b/tutorials/nnvm/from_onnx.py
similarity index 100%
rename from nnvm/tutorials/from_onnx.py
rename to tutorials/nnvm/from_onnx.py
diff --git a/nnvm/tutorials/get_started.py b/tutorials/nnvm/get_started.py
similarity index 100%
rename from nnvm/tutorials/get_started.py
rename to tutorials/nnvm/get_started.py
diff --git a/nnvm/tutorials/imagenet_inference_gpu.py b/tutorials/nnvm/imagenet_inference_gpu.py
similarity index 100%
rename from nnvm/tutorials/imagenet_inference_gpu.py
rename to tutorials/nnvm/imagenet_inference_gpu.py
diff --git a/nnvm/tutorials/using_external_lib.py b/tutorials/nnvm/using_external_lib.py
similarity index 100%
rename from nnvm/tutorials/using_external_lib.py
rename to tutorials/nnvm/using_external_lib.py
diff --git a/nnvm/tutorials/web/resnet.html b/tutorials/nnvm/web/resnet.html
similarity index 100%
rename from nnvm/tutorials/web/resnet.html
rename to tutorials/nnvm/web/resnet.html

From 675216fbeb94c835e8267ae97aeed58f129c1c39 Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Mon, 28 May 2018 14:56:27 -0700
Subject: [PATCH 667/949] [DOCS] Improve docs naming, fix docs warnings

---
 apps/README.md                                |    2 +-
 .../benchmark/gpu_imagenet_bench.py           |    0
 .../benchmark/rasp_imagenet_bench.py          |    0
 docs/api/python/index.rst                     |    1 +
 .../api/python/nnvm}/compiler.rst             |    0
 .../api/python/nnvm}/frontend.rst             |    0
 .../python => docs/api/python/nnvm}/graph.rst |    0
 .../python => docs/api/python/nnvm}/index.rst |    6 +-
 .../api/python/nnvm}/symbol.rst               |    0
 .../python => docs/api/python/nnvm}/top.rst   |    0
 docs/api/python/topi.rst                      |   14 +-
 docs/api_links.rst                            |    4 +-
 docs/dev/index.rst                            |    5 +-
 .../dev/nnvm_json_spec.rst                    |    0
 .../overview.md => docs/dev/nnvm_overview.md  |    0
 docs/how_to/deploy.md                         |  125 +-
 docs/how_to/install.md                        |   68 +-
 docs/how_to/nnpack.md                         |   32 +-
 docs/index.rst                                |   33 +-
 nnvm/docs/top.rst => docs/nnvm_top.rst        |    4 +-
 nnvm/docs/.gitignore                          |    4 -
 nnvm/docs/Doxyfile                            | 2353 -----------------
 nnvm/docs/Makefile                            |  193 --
 nnvm/docs/README.txt                          |    4 -
 nnvm/docs/conf.py                             |  209 --
 nnvm/docs/dev/index.rst                       |    9 -
 nnvm/docs/how_to/contribute.md                |  102 -
 nnvm/docs/how_to/deploy.md                    |  119 -
 nnvm/docs/how_to/install.md                   |   89 -
 nnvm/docs/index.rst                           |   19 -
 nnvm/examples/README.md                       |    5 -
 nnvm/tests/ci_build/Dockerfile.gpu            |   79 -
 nnvm/tests/ci_build/Dockerfile.lint           |    6 -
 nnvm/tests/ci_build/README.md                 |   36 -
 nnvm/tests/ci_build/ci_build.sh               |  126 -
 .../ci_build/install/ubuntu_install_core.sh   |    6 -
 .../ci_build/install/ubuntu_install_coreml.sh |    1 -
 .../install/ubuntu_install_darknet.sh         |    4 -
 .../ci_build/install/ubuntu_install_keras.sh  |    1 -
 .../ci_build/install/ubuntu_install_llvm.sh   |   17 -
 .../ci_build/install/ubuntu_install_mxnet.sh  |    2 -
 .../ci_build/install/ubuntu_install_onnx.sh   |    7 -
 .../ci_build/install/ubuntu_install_opencl.sh |   11 -
 .../ci_build/install/ubuntu_install_python.sh |    5 -
 .../install/ubuntu_install_python_package.sh  |    3 -
 .../ci_build/install/ubuntu_install_sphinx.sh |    1 -
 nnvm/tests/ci_build/with_the_same_user        |   34 -
 .../python/frontend/onnx/test_forward.py      |    5 +-
 nnvm/tests/python/frontend/onnx/test_graph.py |    3 +-
 nnvm/tests/scripts/task_frontend_test.sh      |   12 -
 nnvm/tests/scripts/task_lint.sh               |   15 -
 nnvm/tests/scripts/task_python_docs.sh        |   12 -
 nnvm/tests/travis/run_test.sh                 |   58 -
 nnvm/tests/travis/setup.sh                    |   15 -
 nnvm/tests/travis/travis_after_failure.sh     |    1 -
 python/tvm/_ffi/libinfo.py                    |    3 +-
 tests/ci_build/Dockerfile.gpu                 |   29 +-
 tests/ci_build/install/ubuntu_install_onnx.sh |    1 +
 tests/scripts/task_python_nnvm.sh             |    6 +-
 .../cross_compilation_and_rpc.py              |    4 +-
 tutorials/deployment/README.txt               |    2 -
 tutorials/get_started.py                      |    2 +-
 tutorials/language/README.txt                 |    4 +-
 tutorials/nnvm/deploy_model_on_mali_gpu.py    |   10 +-
 tutorials/nnvm/deploy_model_on_rasp.py        |    3 +-
 tutorials/nnvm/from_darknet.py                |   32 +-
 tutorials/nnvm/from_mxnet_to_webgl.py         |   25 +-
 tutorials/nnvm/from_onnx.py                   |    6 +-
 tutorials/nnvm/using_external_lib.py          |   14 +-
 ...d_compile_model.py => nnvm_quick_start.py} |   26 +-
 tutorials/optimize/README.txt                 |    5 +-
 tutorials/optimize/opt_gemm.py                |    6 +-
 72 files changed, 310 insertions(+), 3728 deletions(-)
 rename {nnvm/examples => apps}/benchmark/gpu_imagenet_bench.py (100%)
 rename {nnvm/examples => apps}/benchmark/rasp_imagenet_bench.py (100%)
 rename {nnvm/docs/api/python => docs/api/python/nnvm}/compiler.rst (100%)
 rename {nnvm/docs/api/python => docs/api/python/nnvm}/frontend.rst (100%)
 rename {nnvm/docs/api/python => docs/api/python/nnvm}/graph.rst (100%)
 rename {nnvm/docs/api/python => docs/api/python/nnvm}/index.rst (82%)
 rename {nnvm/docs/api/python => docs/api/python/nnvm}/symbol.rst (100%)
 rename {nnvm/docs/api/python => docs/api/python/nnvm}/top.rst (100%)
 rename nnvm/docs/json_spec.rst => docs/dev/nnvm_json_spec.rst (100%)
 rename nnvm/docs/dev/overview.md => docs/dev/nnvm_overview.md (100%)
 rename nnvm/docs/top.rst => docs/nnvm_top.rst (99%)
 delete mode 100644 nnvm/docs/.gitignore
 delete mode 100644 nnvm/docs/Doxyfile
 delete mode 100644 nnvm/docs/Makefile
 delete mode 100644 nnvm/docs/README.txt
 delete mode 100644 nnvm/docs/conf.py
 delete mode 100644 nnvm/docs/dev/index.rst
 delete mode 100644 nnvm/docs/how_to/contribute.md
 delete mode 100644 nnvm/docs/how_to/deploy.md
 delete mode 100644 nnvm/docs/how_to/install.md
 delete mode 100644 nnvm/docs/index.rst
 delete mode 100644 nnvm/examples/README.md
 delete mode 100644 nnvm/tests/ci_build/Dockerfile.gpu
 delete mode 100644 nnvm/tests/ci_build/Dockerfile.lint
 delete mode 100644 nnvm/tests/ci_build/README.md
 delete mode 100755 nnvm/tests/ci_build/ci_build.sh
 delete mode 100644 nnvm/tests/ci_build/install/ubuntu_install_core.sh
 delete mode 100644 nnvm/tests/ci_build/install/ubuntu_install_coreml.sh
 delete mode 100644 nnvm/tests/ci_build/install/ubuntu_install_darknet.sh
 delete mode 100644 nnvm/tests/ci_build/install/ubuntu_install_keras.sh
 delete mode 100644 nnvm/tests/ci_build/install/ubuntu_install_llvm.sh
 delete mode 100644 nnvm/tests/ci_build/install/ubuntu_install_mxnet.sh
 delete mode 100644 nnvm/tests/ci_build/install/ubuntu_install_onnx.sh
 delete mode 100644 nnvm/tests/ci_build/install/ubuntu_install_opencl.sh
 delete mode 100644 nnvm/tests/ci_build/install/ubuntu_install_python.sh
 delete mode 100644 nnvm/tests/ci_build/install/ubuntu_install_python_package.sh
 delete mode 100644 nnvm/tests/ci_build/install/ubuntu_install_sphinx.sh
 delete mode 100644 nnvm/tests/ci_build/with_the_same_user
 delete mode 100755 nnvm/tests/scripts/task_frontend_test.sh
 delete mode 100755 nnvm/tests/scripts/task_lint.sh
 delete mode 100755 nnvm/tests/scripts/task_python_docs.sh
 delete mode 100755 nnvm/tests/travis/run_test.sh
 delete mode 100755 nnvm/tests/travis/setup.sh
 delete mode 100755 nnvm/tests/travis/travis_after_failure.sh
 rename tutorials/{deployment => }/cross_compilation_and_rpc.py (99%)
 delete mode 100644 tutorials/deployment/README.txt
 rename tutorials/{nnvm/define_and_compile_model.py => nnvm_quick_start.py} (92%)

diff --git a/apps/README.md b/apps/README.md
index 254f8c26a510..2345cc3ab548 100644
--- a/apps/README.md
+++ b/apps/README.md
@@ -3,9 +3,9 @@ This folder contains various extension projects using TVM,
 they also serve as examples on how to use TVM in your own project.
 
 If you are interested in writing optimized kernels with TVM, checkout [TOPI: TVM Operator Inventory](../topi).
-If you are interested in end to end deep learning model compilation, checkout  [NNVM Compiler](https://github.com/dmlc/nnvm).
 
 - [extension](extension) How to extend TVM C++ api along with python API.
 - [ios_rpc](ios_rpc) iOS RPC server.
 - [android_rpc](android_rpc) Android RPC server.
+- [benchmark](benchmark) Example end to end compilation benchmarks
 - [howto_deploy](howto_deploy) Tutorial on how to deploy TVM with minimum code dependency.
diff --git a/nnvm/examples/benchmark/gpu_imagenet_bench.py b/apps/benchmark/gpu_imagenet_bench.py
similarity index 100%
rename from nnvm/examples/benchmark/gpu_imagenet_bench.py
rename to apps/benchmark/gpu_imagenet_bench.py
diff --git a/nnvm/examples/benchmark/rasp_imagenet_bench.py b/apps/benchmark/rasp_imagenet_bench.py
similarity index 100%
rename from nnvm/examples/benchmark/rasp_imagenet_bench.py
rename to apps/benchmark/rasp_imagenet_bench.py
diff --git a/docs/api/python/index.rst b/docs/api/python/index.rst
index 7b9afaca32f5..a6bed557dd3b 100644
--- a/docs/api/python/index.rst
+++ b/docs/api/python/index.rst
@@ -20,3 +20,4 @@ Python API
    contrib
    dev
    topi
+   nnvm/index
diff --git a/nnvm/docs/api/python/compiler.rst b/docs/api/python/nnvm/compiler.rst
similarity index 100%
rename from nnvm/docs/api/python/compiler.rst
rename to docs/api/python/nnvm/compiler.rst
diff --git a/nnvm/docs/api/python/frontend.rst b/docs/api/python/nnvm/frontend.rst
similarity index 100%
rename from nnvm/docs/api/python/frontend.rst
rename to docs/api/python/nnvm/frontend.rst
diff --git a/nnvm/docs/api/python/graph.rst b/docs/api/python/nnvm/graph.rst
similarity index 100%
rename from nnvm/docs/api/python/graph.rst
rename to docs/api/python/nnvm/graph.rst
diff --git a/nnvm/docs/api/python/index.rst b/docs/api/python/nnvm/index.rst
similarity index 82%
rename from nnvm/docs/api/python/index.rst
rename to docs/api/python/nnvm/index.rst
index 2c574ac60994..c0e5912c76be 100644
--- a/nnvm/docs/api/python/index.rst
+++ b/docs/api/python/nnvm/index.rst
@@ -1,9 +1,7 @@
-Python API
-==========
+NNVM API
+========
 
 This document contains the python API to NNVM compiler toolchain.
-For user
-
 
 .. toctree::
    :maxdepth: 2
diff --git a/nnvm/docs/api/python/symbol.rst b/docs/api/python/nnvm/symbol.rst
similarity index 100%
rename from nnvm/docs/api/python/symbol.rst
rename to docs/api/python/nnvm/symbol.rst
diff --git a/nnvm/docs/api/python/top.rst b/docs/api/python/nnvm/top.rst
similarity index 100%
rename from nnvm/docs/api/python/top.rst
rename to docs/api/python/nnvm/top.rst
diff --git a/docs/api/python/topi.rst b/docs/api/python/topi.rst
index ffdb076a2260..823b941dbc78 100644
--- a/docs/api/python/topi.rst
+++ b/docs/api/python/topi.rst
@@ -1,11 +1,9 @@
-TVM Operator Inventory
-----------------------
+TOPI
+----
 .. automodule:: topi
 
-Index
-~~~~~
-
-**List of operators**
+List of operators
+~~~~~~~~~~~~~~~~~
 
 .. autosummary::
 
@@ -52,8 +50,8 @@ Index
    topi.broadcast_minimum
 
 
-**List of schedules**
-
+List of schedules
+~~~~~~~~~~~~~~~~~
 .. autosummary::
 
    topi.generic.schedule_conv2d_nchw
diff --git a/docs/api_links.rst b/docs/api_links.rst
index 9a55af1728b9..d749a2440ac7 100644
--- a/docs/api_links.rst
+++ b/docs/api_links.rst
@@ -1,5 +1,5 @@
-Links to API References
-=======================
+Links to C++/JS API References
+==============================
 
 This page contains links to API references that are build with different doc build system.
 
diff --git a/docs/dev/index.rst b/docs/dev/index.rst
index 0d0ee852f6f8..5056630fa4db 100644
--- a/docs/dev/index.rst
+++ b/docs/dev/index.rst
@@ -1,11 +1,12 @@
 TVM Design and Developer Guide
 ==============================
 
-Building an IR stack for deep learning systems involves many
-many systems-level design decisions.
+Building a compiler stack for deep learning systems involves many many systems-level design decisions.
 In this part of documentation, we share the rationale for the specific choices made when designing TVM.
 
 .. toctree::
    :maxdepth: 2
 
    runtime
+   nnvm_json_spec
+   nnvm_overview
diff --git a/nnvm/docs/json_spec.rst b/docs/dev/nnvm_json_spec.rst
similarity index 100%
rename from nnvm/docs/json_spec.rst
rename to docs/dev/nnvm_json_spec.rst
diff --git a/nnvm/docs/dev/overview.md b/docs/dev/nnvm_overview.md
similarity index 100%
rename from nnvm/docs/dev/overview.md
rename to docs/dev/nnvm_overview.md
diff --git a/docs/how_to/deploy.md b/docs/how_to/deploy.md
index 841b9d54489d..68630adfaeaa 100644
--- a/docs/how_to/deploy.md
+++ b/docs/how_to/deploy.md
@@ -1,5 +1,5 @@
-How to Deploy TVM Modules
-=========================
+How to Deploy Compiled Modules
+==============================
 We provide an example on how to deploy TVM modules in [apps/howto_deploy](https://github.com/dmlc/tvm/tree/master/apps/howto_deploy)
 
 To run the example, you can use the following command
@@ -59,3 +59,124 @@ deploy_lib.so, deploy_graph.json, deploy_param.params will go to android target.
 
 Refer [here](https://github.com/dmlc/tvm/blob/master/apps/android_deploy/README.md#build-and-installation) to build CPU/OpenCL version flavor TVM runtime for android target.
 From android java TVM API to load model & execute can be refered at this [java](https://github.com/dmlc/tvm/blob/master/apps/android_deploy/app/src/main/java/ml/dmlc/tvm/android/demo/MainActivity.java) sample source.
+
+
+Deploy NNVM Modules
+-------------------
+NNVM compiled modules are fully embedded in TVM runtime as long as ```GRAPH_RUNTIME``` option
+is enabled in tvm runtime. Check out the [TVM documentation](http://docs.tvmlang.org/) for
+how to deploy TVM runtime to your system.
+
+In a nutshell, we will need three items to deploy a compiled module.
+Checkout our tutorials on getting started with NNVM compiler for more details.
+
+- The graph json data which contains the execution graph.
+- The tvm module library of compiled functions.
+- The parameter blobs for stored parameters.
+
+We can then use TVM's runtime API to deploy the compiled module.
+Here is an example in python.
+
+```python
+import tvm
+
+# tvm module for compiled functions.
+loaded_lib = tvm.module.load("deploy.so")
+# json graph
+loaded_json = open(temp.relpath("deploy.json")).read()
+# parameters in binary
+loaded_params = bytearray(open(temp.relpath("deploy.params"), "rb").read())
+
+fcreate = tvm.get_global_func("tvm.graph_runtime.create")
+ctx = tvm.gpu(0)
+gmodule = fcreate(loaded_json, loaded_lib, ctx.device_type, ctx.device_id)
+set_input, get_output, run = gmodule["set_input"], gmodule["get_output"], gmodule["run"]
+set_input("x", tvm.nd.array(x_np))
+gmodule["load_params"](loaded_params)
+run()
+out = tvm.nd.empty(shape)
+get_output(0, out)
+print(out.asnumpy())
+```
+
+An example in c++.
+```cpp
+#include <dlpack/dlpack.h>
+#include <tvm/runtime/module.h>
+#include <tvm/runtime/registry.h>
+#include <tvm/runtime/packed_func.h>
+
+#include <fstream>
+#include <iterator>
+#include <algorithm>
+
+int main()
+{
+    // tvm module for compiled functions
+    tvm::runtime::Module mod_syslib = tvm::runtime::Module::LoadFromFile("deploy.so");
+
+    // json graph
+    std::ifstream json_in("deploy.json", std::ios::in);
+    std::string json_data((std::istreambuf_iterator<char>(json_in)), std::istreambuf_iterator<char>());
+    json_in.close();
+
+    // parameters in binary
+    std::ifstream params_in("deploy.params", std::ios::binary);
+    std::string params_data((std::istreambuf_iterator<char>(params_in)), std::istreambuf_iterator<char>());
+    params_in.close();
+
+    // parameters need to be TVMByteArray type to indicate the binary data
+    TVMByteArray params_arr;
+    params_arr.data = params_data.c_str();
+    params_arr.size = params_data.length();
+
+    int dtype_code = kDLFloat;
+    int dtype_bits = 32;
+    int dtype_lanes = 1;
+    int device_type = kDLCPU;
+    int device_id = 0;
+
+    // get global function module for graph runtime
+    tvm::runtime::Module mod = (*tvm::runtime::Registry::Get("tvm.graph_runtime.create"))(json_data, mod_syslib, device_type, device_id);
+
+    DLTensor* x;
+    int in_ndim = 4;
+    int64_t in_shape[4] = {1, 3, 224, 224};
+    TVMArrayAlloc(in_shape, in_ndim, dtype_code, dtype_bits, dtype_lanes, device_type, device_id, &x);
+    // load image data saved in binary
+    std::ifstream data_fin("cat.bin", std::ios::binary);
+    data_fin.read(static_cast<char*>(x->data), 3 * 224 * 224 * 4);
+
+    // get the function from the module(set input data)
+    tvm::runtime::PackedFunc set_input = mod.GetFunction("set_input");
+    set_input("data", x);
+
+    // get the function from the module(load patameters)
+    tvm::runtime::PackedFunc load_params = mod.GetFunction("load_params");
+    load_params(params_arr);
+
+    // get the function from the module(run it)
+    tvm::runtime::PackedFunc run = mod.GetFunction("run");
+    run();
+
+    DLTensor* y;
+    int out_ndim = 1;
+    int64_t out_shape[1] = {1000, };
+    TVMArrayAlloc(out_shape, out_ndim, dtype_code, dtype_bits, dtype_lanes, device_type, device_id, &y);
+
+    // get the function from the module(get output data)
+    tvm::runtime::PackedFunc get_output = mod.GetFunction("get_output");
+    get_output(0, y);
+
+    // get the maximum position in output vector
+    auto y_iter = static_cast<float*>(y->data);
+    auto max_iter = std::max_element(y_iter, y_iter + 1000);
+    auto max_index = std::distance(y_iter, max_iter);
+    std::cout << "The maximum position in output vector is: " << max_index << std::endl;
+
+    TVMArrayFree(x);
+    TVMArrayFree(y);
+
+    return 0;
+}
+```
diff --git a/docs/how_to/install.md b/docs/how_to/install.md
index 6e81c0407550..c6210e802cab 100644
--- a/docs/how_to/install.md
+++ b/docs/how_to/install.md
@@ -22,37 +22,20 @@ git submodule update
 
 ## Build the Shared Library
 
-Our goal is to build the shared library:
-- On Linux/OSX the target library is `libtvm.so`
-- On Windows the target library is `libtvm.dll`
-
-The minimal building requirement is
-- A recent c++ compiler supporting C++ 11 (g++-4.8 or higher)
-
-You can edit `make/config.mk` to change the compile options, and then build by
-`make`. If everything goes well, we can go to the specific language installation section.
-
-### Building on Windows
-
-TVM support build via MSVC using cmake. The minimum required VS version is **Visual Studio Community 2015 Update 3**. In order to generate the VS solution file using cmake,
-make sure you have a recent version of cmake added to your path and then from the tvm directory:
-
-```bash
-mkdir build
-cd build
-cmake -G "Visual Studio 14 2015 Win64" -DCMAKE_BUILD_TYPE=Release -DCMAKE_CONFIGURATION_TYPES="Release" ..
-```
-This will generate the VS project using the MSVC 14 64 bit generator. Open the .sln file in the build directory and build with Visual Studio.
-
-### Customized Building
-
-Install prerequisites first:
+Our goal is to build the shared libraries:
+- On Linux/OSX the target library are `libtvm.so, libtvm_topi.so`
+- On Windows the target library are `libtvm.dll, libtvm_topi.dll`
 
 ```bash
 sudo apt-get update
 sudo apt-get install -y python python-dev python-setuptools gcc libtinfo-dev zlib1g-dev
 ```
 
+The minimal building requirement is
+- A recent c++ compiler supporting C++ 11 (g++-4.8 or higher)
+- We highly recommend to build with LLVM to enable all the features.
+- It is possible to build without llvm dependency if we only want to use CUDA/OpenCL
+
 The configuration of tvm can be modified by ```config.mk```
 - First copy ```make/config.mk``` to the project root, on which
   any local modification will be ignored by git, then modify the according flags.
@@ -62,8 +45,36 @@ The configuration of tvm can be modified by ```config.mk```
     [LLVM Download Page](http://releases.llvm.org/download.html).
     - Unzip to a certain location, modify ```config.mk``` to add ```LLVM_CONFIG=/path/to/your/llvm/bin/llvm-config```
   - You can also use [LLVM Nightly Ubuntu Build](https://apt.llvm.org/)
-    - Note that apt-package append ```llvm-config``` with version number. For example, set ```LLVM_CONFIG=llvm-config-4.0``` if you installed 4.0 package
-  - By default CUDA and OpenCL code generator do not require llvm.
+    - Note that apt-package append ```llvm-config``` with version number.
+      For example, set ```LLVM_CONFIG=llvm-config-4.0``` if you installed 4.0 package
+
+We can then build tvm by `make`.
+After we build the tvm, we can proceed to build nnvm using the following script.
+
+```bash
+cd nnvm
+make -j4
+```
+
+This will creates `libnnvm_compiler.so` under the `nnvm/lib` folder.
+If everything goes well, we can go to the specific language installation section.
+
+
+### Building on Windows
+
+TVM support build via MSVC using cmake. The minimum required VS version is **Visual Studio Community 2015 Update 3**.
+In order to generate the VS solution file using cmake,
+make sure you have a recent version of cmake added to your path and then from the tvm directory:
+
+```bash
+mkdir build
+cd build
+cmake -G "Visual Studio 14 2015 Win64" -DCMAKE_BUILD_TYPE=Release -DCMAKE_CONFIGURATION_TYPES="Release" ..
+```
+This will generate the VS project using the MSVC 14 64 bit generator.
+Open the .sln file in the build directory and build with Visual Studio.
+In order to build with LLVM in windows, you will need to build LLVM from source.
+You need to run build the nnvm by running the same script under the nnvm folder.
 
 ## Python Package Installation
 
@@ -77,7 +88,7 @@ There are several ways to install the package:
     The changes will be immediately reflected once you pulled the code and rebuild the project (no need to call ```setup``` again)
 
     ```bash
-    export PYTHONPATH=/path/to/tvm/python:/path/to/tvm/topi/python:${PYTHONPATH}
+    export PYTHONPATH=/path/to/tvm/python:/path/to/tvm/topi/python:/path/to/tvm/nnvm/python:${PYTHONPATH}
     ```
 
 2. Install tvm python bindings by `setup.py`:
@@ -89,4 +100,5 @@ There are several ways to install the package:
     #       providing --user flag may trigger error during installation in such case.
     cd python; python setup.py install --user; cd ..
     cd topi/python; python setup.py install --user; cd ../..
+    cd nnvm/python; python setup.py install --user; cd ../..
     ```
diff --git a/docs/how_to/nnpack.md b/docs/how_to/nnpack.md
index 060bf9b5399f..d271af86b35a 100644
--- a/docs/how_to/nnpack.md
+++ b/docs/how_to/nnpack.md
@@ -1,10 +1,11 @@
 ### NNPACK for Multi-Core CPU Support in TVM
+
 [NNPACK](https://github.com/Maratyszcza/NNPACK) is an acceleration package
 for neural network computations, which can run on x86-64, ARMv7, or ARM64 architecture CPUs.
 Using NNPACK, higher-level libraries like _MXNet_ can speed up
 the execution on multi-core CPU computers, including laptops and mobile devices.
 
-***Note***: AS TVM already has natively tuned schedules, NNPACK is here mainly for reference and comparison purpose. 
+***Note***: AS TVM already has natively tuned schedules, NNPACK is here mainly for reference and comparison purpose.
 For regular use prefer native tuned TVM implementation.
 
 _TVM_ supports NNPACK for forward propagation (inference only) in convolution, max-pooling, and fully-connected layers.
@@ -29,7 +30,7 @@ The following table explains under which conditions NNPACK will work.
 ### Build/Install LLVM
 LLVM is required for CPU codegen that needs LLVM.
 Since LLVM takes long time to build from source, you can download pre-built version of LLVM from [LLVM Download Page](http://releases.llvm.org/download.html).
-For llvm 4.0 you can do the following step : 
+For llvm 4.0 you can do the following step :
 
 ```bash
 # Add llvm repository in apt source list
@@ -63,7 +64,7 @@ apt-get install -y \
 
 If the trained model meets some conditions of using NNPACK,
 you can build TVM with NNPACK support.
-Follow these simple steps:  
+Follow these simple steps:
 * Build NNPACK shared library with the following commands. _TVM_ will link NNPACK dynamically.
 
 Note: The following NNPACK installation instructions have been tested on Ubuntu 16.04.
@@ -77,7 +78,7 @@ cd ninja
 ./configure.py --bootstrap
 ```
 
-Set the environment variable PATH to tell bash where to find the ninja executable. For example, assume we cloned ninja on the home directory ~. then we can added the following line in ~/.bashrc. 
+Set the environment variable PATH to tell bash where to find the ninja executable. For example, assume we cloned ninja on the home directory ~. then we can added the following line in ~/.bashrc.
 ```bash
 export PATH="${PATH}:~/ninja"
 ```
@@ -118,27 +119,4 @@ after configuration use `make` to build TVM
 
 ```bash
 make
-make install
-```
-
-#### Python Package Installation
-
-The python package for [tvm](https://github.com/dmlc/tvm) depends of [topi](https://github.com/dmlc/tvm/tree/master/topi).
-The tvm python package is located at `tvm/python` and topi python package is located in `tvm/topi/python` folder.
-There are several ways to install the package, in all these cases the TVM library and TOPI must be present in the python env:
-
-1. Set the environment variable PYTHONPATH to tell python where to find the libraries. For example, assume we cloned tvm on the home directory ~. then we can added the following line in ~/.bashrc. It is recommended for developers who may change the codes. The changes will be immediately reflected once you pulled the code and rebuild the project (no need to call setup again)
-
-```bash
-export PYTHONPATH=/path/to/tvm/python:/path/to/tvm/topi/python:${PYTHONPATH}
-```
-
-2. Install tvm and topi python bindings by setup.py:
-
-```bash
-# install tvm package for the current user
-cd topi/python
-python setup.py install --user; 
-cd ../../python
-python setup.py install --user; 
 ```
diff --git a/docs/index.rst b/docs/index.rst
index 3a5c8d7715c9..28f6b4052501 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,23 +1,38 @@
 TVM Documentation
 =================
 
-Welcome to TVM documentation.
-
-
-Contents
---------
-
+Get Started
+-----------
 .. toctree::
    :maxdepth: 1
 
-   self
    how_to/install
    tutorials/index
-   faq
    how_to/deploy
    how_to/integrate
    how_to/contribute
+   faq
+
+API Reference
+-------------
+.. toctree::
+   :maxdepth: 2
+
    api/python/index
-   dev/index
    api_links
+
+Developer Guide
+---------------
+.. toctree::
+   :maxdepth: 2
+
+   dev/index
+   nnvm_top
+
+
+Index
+-----
+.. toctree::
+   :maxdepth: 1
+
    genindex
diff --git a/nnvm/docs/top.rst b/docs/nnvm_top.rst
similarity index 99%
rename from nnvm/docs/top.rst
rename to docs/nnvm_top.rst
index 11d0f6093f83..3dccaddff8cd 100644
--- a/nnvm/docs/top.rst
+++ b/docs/nnvm_top.rst
@@ -1,5 +1,5 @@
-Core Tensor Operators
-=====================
+NNVM Core Tensor Operators
+==========================
 
 This page contains the list of core tensor operator primitives pre-defined in NNVM.
 The core tensor operator primitives(``nnvm.top``) covers typical workloads in deep learning.
diff --git a/nnvm/docs/.gitignore b/nnvm/docs/.gitignore
deleted file mode 100644
index d5d021127425..000000000000
--- a/nnvm/docs/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-doxygen
-_build
-gen_modules
-tutorials
diff --git a/nnvm/docs/Doxyfile b/nnvm/docs/Doxyfile
deleted file mode 100644
index 4c6c2493aa62..000000000000
--- a/nnvm/docs/Doxyfile
+++ /dev/null
@@ -1,2353 +0,0 @@
-# Doxyfile 1.8.8
-
-# This file describes the settings to be used by the documentation system
-# doxygen (www.doxygen.org) for a project.
-#
-# All text after a double hash (##) is considered a comment and is placed in
-# front of the TAG it is preceding.
-#
-# All text after a single hash (#) is considered a comment and will be ignored.
-# The format is:
-# TAG = value [value, ...]
-# For lists, items can also be appended using:
-# TAG += value [value, ...]
-# Values that contain spaces should be placed between quotes (\" \").
-
-#---------------------------------------------------------------------------
-# Project related configuration options
-#---------------------------------------------------------------------------
-
-# This tag specifies the encoding used for all characters in the config file
-# that follow. The default is UTF-8 which is also the encoding used for all text
-# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
-# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
-# for the list of possible encodings.
-# The default value is: UTF-8.
-
-DOXYFILE_ENCODING      = UTF-8
-
-# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
-# double-quotes, unless you are using Doxywizard) that should identify the
-# project for which the documentation is generated. This name is used in the
-# title of most generated pages and in a few other places.
-# The default value is: My Project.
-
-PROJECT_NAME           = "nnvm"
-
-# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
-# could be handy for archiving the generated documentation or if some version
-# control system is used.
-
-PROJECT_NUMBER         =
-
-# Using the PROJECT_BRIEF tag one can provide an optional one line description
-# for a project that appears at the top of each page and should give viewer a
-# quick idea about the purpose of the project. Keep the description short.
-
-PROJECT_BRIEF          =
-
-# With the PROJECT_LOGO tag one can specify an logo or icon that is included in
-# the documentation. The maximum height of the logo should not exceed 55 pixels
-# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo
-# to the output directory.
-
-PROJECT_LOGO           =
-
-# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
-# into which the generated documentation will be written. If a relative path is
-# entered, it will be relative to the location where doxygen was started. If
-# left blank the current directory will be used.
-
-OUTPUT_DIRECTORY       = docs/doxygen
-
-# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub-
-# directories (in 2 levels) under the output directory of each output format and
-# will distribute the generated files over these directories. Enabling this
-# option can be useful when feeding doxygen a huge amount of source files, where
-# putting all generated files in the same directory would otherwise causes
-# performance problems for the file system.
-# The default value is: NO.
-
-CREATE_SUBDIRS         = NO
-
-# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
-# characters to appear in the names of generated files. If set to NO, non-ASCII
-# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
-# U+3044.
-# The default value is: NO.
-
-#ALLOW_UNICODE_NAMES    = NO
-
-# The OUTPUT_LANGUAGE tag is used to specify the language in which all
-# documentation generated by doxygen is written. Doxygen will use this
-# information to generate all constant output in the proper language.
-# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
-# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
-# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
-# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
-# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
-# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
-# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
-# Ukrainian and Vietnamese.
-# The default value is: English.
-
-OUTPUT_LANGUAGE        = English
-
-# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member
-# descriptions after the members that are listed in the file and class
-# documentation (similar to Javadoc). Set to NO to disable this.
-# The default value is: YES.
-
-BRIEF_MEMBER_DESC      = YES
-
-# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief
-# description of a member or function before the detailed description
-#
-# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
-# brief descriptions will be completely suppressed.
-# The default value is: YES.
-
-REPEAT_BRIEF           = YES
-
-# This tag implements a quasi-intelligent brief description abbreviator that is
-# used to form the text in various listings. Each string in this list, if found
-# as the leading text of the brief description, will be stripped from the text
-# and the result, after processing the whole list, is used as the annotated
-# text. Otherwise, the brief description is used as-is. If left blank, the
-# following values are used ($name is automatically replaced with the name of
-# the entity):The $name class, The $name widget, The $name file, is, provides,
-# specifies, contains, represents, a, an and the.
-
-ABBREVIATE_BRIEF       =
-
-# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
-# doxygen will generate a detailed section even if there is only a brief
-# description.
-# The default value is: NO.
-
-ALWAYS_DETAILED_SEC    = NO
-
-# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
-# inherited members of a class in the documentation of that class as if those
-# members were ordinary class members. Constructors, destructors and assignment
-# operators of the base classes will not be shown.
-# The default value is: NO.
-
-INLINE_INHERITED_MEMB  = NO
-
-# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path
-# before files name in the file list and in the header files. If set to NO the
-# shortest path that makes the file name unique will be used
-# The default value is: YES.
-
-FULL_PATH_NAMES        = YES
-
-# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
-# Stripping is only done if one of the specified strings matches the left-hand
-# part of the path. The tag can be used to show relative paths in the file list.
-# If left blank the directory from which doxygen is run is used as the path to
-# strip.
-#
-# Note that you can specify absolute paths here, but also relative paths, which
-# will be relative from the directory where doxygen is started.
-# This tag requires that the tag FULL_PATH_NAMES is set to YES.
-
-STRIP_FROM_PATH        =
-
-# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
-# path mentioned in the documentation of a class, which tells the reader which
-# header file to include in order to use a class. If left blank only the name of
-# the header file containing the class definition is used. Otherwise one should
-# specify the list of include paths that are normally passed to the compiler
-# using the -I flag.
-
-STRIP_FROM_INC_PATH    =
-
-# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
-# less readable) file names. This can be useful is your file systems doesn't
-# support long names like on DOS, Mac, or CD-ROM.
-# The default value is: NO.
-
-SHORT_NAMES            = NO
-
-# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
-# first line (until the first dot) of a Javadoc-style comment as the brief
-# description. If set to NO, the Javadoc-style will behave just like regular Qt-
-# style comments (thus requiring an explicit @brief command for a brief
-# description.)
-# The default value is: NO.
-
-JAVADOC_AUTOBRIEF      = NO
-
-# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
-# line (until the first dot) of a Qt-style comment as the brief description. If
-# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
-# requiring an explicit \brief command for a brief description.)
-# The default value is: NO.
-
-QT_AUTOBRIEF           = NO
-
-# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
-# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
-# a brief description. This used to be the default behavior. The new default is
-# to treat a multi-line C++ comment block as a detailed description. Set this
-# tag to YES if you prefer the old behavior instead.
-#
-# Note that setting this tag to YES also means that rational rose comments are
-# not recognized any more.
-# The default value is: NO.
-
-MULTILINE_CPP_IS_BRIEF = NO
-
-# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
-# documentation from any documented member that it re-implements.
-# The default value is: YES.
-
-INHERIT_DOCS           = YES
-
-# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a
-# new page for each member. If set to NO, the documentation of a member will be
-# part of the file/class/namespace that contains it.
-# The default value is: NO.
-
-SEPARATE_MEMBER_PAGES  = NO
-
-# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
-# uses this value to replace tabs by spaces in code fragments.
-# Minimum value: 1, maximum value: 16, default value: 4.
-
-TAB_SIZE               = 8
-
-# This tag can be used to specify a number of aliases that act as commands in
-# the documentation. An alias has the form:
-# name=value
-# For example adding
-# "sideeffect=@par Side Effects:\n"
-# will allow you to put the command \sideeffect (or @sideeffect) in the
-# documentation, which will result in a user-defined paragraph with heading
-# "Side Effects:". You can put \n's in the value part of an alias to insert
-# newlines.
-
-ALIASES                =
-
-# This tag can be used to specify a number of word-keyword mappings (TCL only).
-# A mapping has the form "name=value". For example adding "class=itcl::class"
-# will allow you to use the command class in the itcl::class meaning.
-
-TCL_SUBST              =
-
-# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
-# only. Doxygen will then generate output that is more tailored for C. For
-# instance, some of the names that are used will be different. The list of all
-# members will be omitted, etc.
-# The default value is: NO.
-
-OPTIMIZE_OUTPUT_FOR_C  = NO
-
-# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
-# Python sources only. Doxygen will then generate output that is more tailored
-# for that language. For instance, namespaces will be presented as packages,
-# qualified scopes will look different, etc.
-# The default value is: NO.
-
-OPTIMIZE_OUTPUT_JAVA   = NO
-
-# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
-# sources. Doxygen will then generate output that is tailored for Fortran.
-# The default value is: NO.
-
-OPTIMIZE_FOR_FORTRAN   = NO
-
-# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
-# sources. Doxygen will then generate output that is tailored for VHDL.
-# The default value is: NO.
-
-OPTIMIZE_OUTPUT_VHDL   = NO
-
-# Doxygen selects the parser to use depending on the extension of the files it
-# parses. With this tag you can assign which parser to use for a given
-# extension. Doxygen has a built-in mapping, but you can override or extend it
-# using this tag. The format is ext=language, where ext is a file extension, and
-# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
-# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
-# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
-# Fortran. In the later case the parser tries to guess whether the code is fixed
-# or free formatted code, this is the default for Fortran type files), VHDL. For
-# instance to make doxygen treat .inc files as Fortran files (default is PHP),
-# and .f files as C (default is Fortran), use: inc=Fortran f=C.
-#
-# Note For files without extension you can use no_extension as a placeholder.
-#
-# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
-# the files are not read by doxygen.
-
-EXTENSION_MAPPING      =
-
-# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
-# according to the Markdown format, which allows for more readable
-# documentation. See http://daringfireball.net/projects/markdown/ for details.
-# The output of markdown processing is further processed by doxygen, so you can
-# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
-# case of backward compatibilities issues.
-# The default value is: YES.
-
-#MARKDOWN_SUPPORT       = YES
-
-# When enabled doxygen tries to link words that correspond to documented
-# classes, or namespaces to their corresponding documentation. Such a link can
-# be prevented in individual cases by by putting a % sign in front of the word
-# or globally by setting AUTOLINK_SUPPORT to NO.
-# The default value is: YES.
-
-#AUTOLINK_SUPPORT       = YES
-
-# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
-# to include (a tag file for) the STL sources as input, then you should set this
-# tag to YES in order to let doxygen match functions declarations and
-# definitions whose arguments contain STL classes (e.g. func(std::string);
-# versus func(std::string) {}). This also make the inheritance and collaboration
-# diagrams that involve STL classes more complete and accurate.
-# The default value is: NO.
-
-BUILTIN_STL_SUPPORT    = NO
-
-# If you use Microsoft's C++/CLI language, you should set this option to YES to
-# enable parsing support.
-# The default value is: NO.
-
-CPP_CLI_SUPPORT        = NO
-
-# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
-# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
-# will parse them like normal C++ but will assume all classes use public instead
-# of private inheritance when no explicit protection keyword is present.
-# The default value is: NO.
-
-SIP_SUPPORT            = NO
-
-# For Microsoft's IDL there are propget and propput attributes to indicate
-# getter and setter methods for a property. Setting this option to YES will make
-# doxygen to replace the get and set methods by a property in the documentation.
-# This will only work if the methods are indeed getting or setting a simple
-# type. If this is not the case, or you want to show the methods anyway, you
-# should set this option to NO.
-# The default value is: YES.
-
-IDL_PROPERTY_SUPPORT   = YES
-
-# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
-# tag is set to YES, then doxygen will reuse the documentation of the first
-# member in the group (if any) for the other members of the group. By default
-# all members of a group must be documented explicitly.
-# The default value is: NO.
-
-DISTRIBUTE_GROUP_DOC   = NO
-
-# Set the SUBGROUPING tag to YES to allow class member groups of the same type
-# (for instance a group of public functions) to be put as a subgroup of that
-# type (e.g. under the Public Functions section). Set it to NO to prevent
-# subgrouping. Alternatively, this can be done per class using the
-# \nosubgrouping command.
-# The default value is: YES.
-
-SUBGROUPING            = YES
-
-# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
-# are shown inside the group in which they are included (e.g. using \ingroup)
-# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
-# and RTF).
-#
-# Note that this feature does not work in combination with
-# SEPARATE_MEMBER_PAGES.
-# The default value is: NO.
-
-INLINE_GROUPED_CLASSES = NO
-
-# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
-# with only public data fields or simple typedef fields will be shown inline in
-# the documentation of the scope in which they are defined (i.e. file,
-# namespace, or group documentation), provided this scope is documented. If set
-# to NO, structs, classes, and unions are shown on a separate page (for HTML and
-# Man pages) or section (for LaTeX and RTF).
-# The default value is: NO.
-
-INLINE_SIMPLE_STRUCTS  = NO
-
-# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
-# enum is documented as struct, union, or enum with the name of the typedef. So
-# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
-# with name TypeT. When disabled the typedef will appear as a member of a file,
-# namespace, or class. And the struct will be named TypeS. This can typically be
-# useful for C code in case the coding convention dictates that all compound
-# types are typedef'ed and only the typedef is referenced, never the tag name.
-# The default value is: NO.
-
-TYPEDEF_HIDES_STRUCT   = NO
-
-# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
-# cache is used to resolve symbols given their name and scope. Since this can be
-# an expensive process and often the same symbol appears multiple times in the
-# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
-# doxygen will become slower. If the cache is too large, memory is wasted. The
-# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
-# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
-# symbols. At the end of a run doxygen will report the cache usage and suggest
-# the optimal cache size from a speed point of view.
-# Minimum value: 0, maximum value: 9, default value: 0.
-
-LOOKUP_CACHE_SIZE      = 0
-
-#---------------------------------------------------------------------------
-# Build related configuration options
-#---------------------------------------------------------------------------
-
-# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
-# documentation are documented, even if no documentation was available. Private
-# class members and static file members will be hidden unless the
-# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
-# Note: This will also disable the warnings about undocumented members that are
-# normally produced when WARNINGS is set to YES.
-# The default value is: NO.
-
-EXTRACT_ALL            = YES
-
-# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will
-# be included in the documentation.
-# The default value is: NO.
-
-EXTRACT_PRIVATE        = NO
-
-# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal
-# scope will be included in the documentation.
-# The default value is: NO.
-
-#EXTRACT_PACKAGE        = NO
-
-# If the EXTRACT_STATIC tag is set to YES all static members of a file will be
-# included in the documentation.
-# The default value is: NO.
-
-EXTRACT_STATIC         = NO
-
-# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined
-# locally in source files will be included in the documentation. If set to NO
-# only classes defined in header files are included. Does not have any effect
-# for Java sources.
-# The default value is: YES.
-
-EXTRACT_LOCAL_CLASSES  = YES
-
-# This flag is only useful for Objective-C code. When set to YES local methods,
-# which are defined in the implementation section but not in the interface are
-# included in the documentation. If set to NO only methods in the interface are
-# included.
-# The default value is: NO.
-
-EXTRACT_LOCAL_METHODS  = NO
-
-# If this flag is set to YES, the members of anonymous namespaces will be
-# extracted and appear in the documentation as a namespace called
-# 'anonymous_namespace{file}', where file will be replaced with the base name of
-# the file that contains the anonymous namespace. By default anonymous namespace
-# are hidden.
-# The default value is: NO.
-
-EXTRACT_ANON_NSPACES   = NO
-
-# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
-# undocumented members inside documented classes or files. If set to NO these
-# members will be included in the various overviews, but no documentation
-# section is generated. This option has no effect if EXTRACT_ALL is enabled.
-# The default value is: NO.
-
-HIDE_UNDOC_MEMBERS     = NO
-
-# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
-# undocumented classes that are normally visible in the class hierarchy. If set
-# to NO these classes will be included in the various overviews. This option has
-# no effect if EXTRACT_ALL is enabled.
-# The default value is: NO.
-
-HIDE_UNDOC_CLASSES     = NO
-
-# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
-# (class|struct|union) declarations. If set to NO these declarations will be
-# included in the documentation.
-# The default value is: NO.
-
-HIDE_FRIEND_COMPOUNDS  = NO
-
-# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
-# documentation blocks found inside the body of a function. If set to NO these
-# blocks will be appended to the function's detailed documentation block.
-# The default value is: NO.
-
-HIDE_IN_BODY_DOCS      = NO
-
-# The INTERNAL_DOCS tag determines if documentation that is typed after a
-# \internal command is included. If the tag is set to NO then the documentation
-# will be excluded. Set it to YES to include the internal documentation.
-# The default value is: NO.
-
-INTERNAL_DOCS          = NO
-
-# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
-# names in lower-case letters. If set to YES upper-case letters are also
-# allowed. This is useful if you have classes or files whose names only differ
-# in case and if your file system supports case sensitive file names. Windows
-# and Mac users are advised to set this option to NO.
-# The default value is: system dependent.
-
-CASE_SENSE_NAMES       = YES
-
-# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
-# their full class and namespace scopes in the documentation. If set to YES the
-# scope will be hidden.
-# The default value is: NO.
-
-HIDE_SCOPE_NAMES       = NO
-
-# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
-# the files that are included by a file in the documentation of that file.
-# The default value is: YES.
-
-SHOW_INCLUDE_FILES     = YES
-
-# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
-# grouped member an include statement to the documentation, telling the reader
-# which file to include in order to use the member.
-# The default value is: NO.
-
-#SHOW_GROUPED_MEMB_INC  = NO
-
-# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
-# files with double quotes in the documentation rather than with sharp brackets.
-# The default value is: NO.
-
-FORCE_LOCAL_INCLUDES   = NO
-
-# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
-# documentation for inline members.
-# The default value is: YES.
-
-INLINE_INFO            = YES
-
-# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
-# (detailed) documentation of file and class members alphabetically by member
-# name. If set to NO the members will appear in declaration order.
-# The default value is: YES.
-
-SORT_MEMBER_DOCS       = YES
-
-# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
-# descriptions of file, namespace and class members alphabetically by member
-# name. If set to NO the members will appear in declaration order. Note that
-# this will also influence the order of the classes in the class list.
-# The default value is: NO.
-
-SORT_BRIEF_DOCS        = NO
-
-# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
-# (brief and detailed) documentation of class members so that constructors and
-# destructors are listed first. If set to NO the constructors will appear in the
-# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
-# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
-# member documentation.
-# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
-# detailed member documentation.
-# The default value is: NO.
-
-SORT_MEMBERS_CTORS_1ST = NO
-
-# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
-# of group names into alphabetical order. If set to NO the group names will
-# appear in their defined order.
-# The default value is: NO.
-
-SORT_GROUP_NAMES       = NO
-
-# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
-# fully-qualified names, including namespaces. If set to NO, the class list will
-# be sorted only by class name, not including the namespace part.
-# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
-# Note: This option applies only to the class list, not to the alphabetical
-# list.
-# The default value is: NO.
-
-SORT_BY_SCOPE_NAME     = NO
-
-# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
-# type resolution of all parameters of a function it will reject a match between
-# the prototype and the implementation of a member function even if there is
-# only one candidate or it is obvious which candidate to choose by doing a
-# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
-# accept a match between prototype and implementation in such cases.
-# The default value is: NO.
-
-STRICT_PROTO_MATCHING  = NO
-
-# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the
-# todo list. This list is created by putting \todo commands in the
-# documentation.
-# The default value is: YES.
-
-GENERATE_TODOLIST      = YES
-
-# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the
-# test list. This list is created by putting \test commands in the
-# documentation.
-# The default value is: YES.
-
-GENERATE_TESTLIST      = YES
-
-# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug
-# list. This list is created by putting \bug commands in the documentation.
-# The default value is: YES.
-
-GENERATE_BUGLIST       = YES
-
-# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO)
-# the deprecated list. This list is created by putting \deprecated commands in
-# the documentation.
-# The default value is: YES.
-
-GENERATE_DEPRECATEDLIST= YES
-
-# The ENABLED_SECTIONS tag can be used to enable conditional documentation
-# sections, marked by \if <section_label> ... \endif and \cond <section_label>
-# ... \endcond blocks.
-
-ENABLED_SECTIONS       =
-
-# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
-# initial value of a variable or macro / define can have for it to appear in the
-# documentation. If the initializer consists of more lines than specified here
-# it will be hidden. Use a value of 0 to hide initializers completely. The
-# appearance of the value of individual variables and macros / defines can be
-# controlled using \showinitializer or \hideinitializer command in the
-# documentation regardless of this setting.
-# Minimum value: 0, maximum value: 10000, default value: 30.
-
-MAX_INITIALIZER_LINES  = 30
-
-# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
-# the bottom of the documentation of classes and structs. If set to YES the list
-# will mention the files that were used to generate the documentation.
-# The default value is: YES.
-
-SHOW_USED_FILES        = YES
-
-# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
-# will remove the Files entry from the Quick Index and from the Folder Tree View
-# (if specified).
-# The default value is: YES.
-
-SHOW_FILES             = YES
-
-# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
-# page. This will remove the Namespaces entry from the Quick Index and from the
-# Folder Tree View (if specified).
-# The default value is: YES.
-
-SHOW_NAMESPACES        = YES
-
-# The FILE_VERSION_FILTER tag can be used to specify a program or script that
-# doxygen should invoke to get the current version for each file (typically from
-# the version control system). Doxygen will invoke the program by executing (via
-# popen()) the command command input-file, where command is the value of the
-# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
-# by doxygen. Whatever the program writes to standard output is used as the file
-# version. For an example see the documentation.
-
-FILE_VERSION_FILTER    =
-
-# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
-# by doxygen. The layout file controls the global structure of the generated
-# output files in an output format independent way. To create the layout file
-# that represents doxygen's defaults, run doxygen with the -l option. You can
-# optionally specify a file name after the option, if omitted DoxygenLayout.xml
-# will be used as the name of the layout file.
-#
-# Note that if you run doxygen from a directory containing a file called
-# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
-# tag is left empty.
-
-LAYOUT_FILE            =
-
-# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
-# the reference definitions. This must be a list of .bib files. The .bib
-# extension is automatically appended if omitted. This requires the bibtex tool
-# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
-# For LaTeX the style of the bibliography can be controlled using
-# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
-# search path. See also \cite for info how to create references.
-
-CITE_BIB_FILES         =
-
-#---------------------------------------------------------------------------
-# Configuration options related to warning and progress messages
-#---------------------------------------------------------------------------
-
-# The QUIET tag can be used to turn on/off the messages that are generated to
-# standard output by doxygen. If QUIET is set to YES this implies that the
-# messages are off.
-# The default value is: NO.
-
-QUIET                  = NO
-
-# The WARNINGS tag can be used to turn on/off the warning messages that are
-# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES
-# this implies that the warnings are on.
-#
-# Tip: Turn warnings on while writing the documentation.
-# The default value is: YES.
-
-WARNINGS               = YES
-
-# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate
-# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
-# will automatically be disabled.
-# The default value is: YES.
-
-WARN_IF_UNDOCUMENTED   = YES
-
-# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
-# potential errors in the documentation, such as not documenting some parameters
-# in a documented function, or documenting parameters that don't exist or using
-# markup commands wrongly.
-# The default value is: YES.
-
-WARN_IF_DOC_ERROR      = YES
-
-# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
-# are documented, but have no documentation for their parameters or return
-# value. If set to NO doxygen will only warn about wrong or incomplete parameter
-# documentation, but not about the absence of documentation.
-# The default value is: NO.
-
-WARN_NO_PARAMDOC       = YES
-
-# The WARN_FORMAT tag determines the format of the warning messages that doxygen
-# can produce. The string should contain the $file, $line, and $text tags, which
-# will be replaced by the file and line number from which the warning originated
-# and the warning text. Optionally the format may contain $version, which will
-# be replaced by the version of the file (if it could be obtained via
-# FILE_VERSION_FILTER)
-# The default value is: $file:$line: $text.
-
-WARN_FORMAT            = "$file:$line: $text"
-
-# The WARN_LOGFILE tag can be used to specify a file to which warning and error
-# messages should be written. If left blank the output is written to standard
-# error (stderr).
-
-WARN_LOGFILE           =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the input files
-#---------------------------------------------------------------------------
-
-# The INPUT tag is used to specify the files and/or directories that contain
-# documented source files. You may enter file names like myfile.cpp or
-# directories like /usr/src/myproject. Separate the files or directories with
-# spaces.
-# Note: If this tag is empty the current directory is searched.
-
-INPUT                  = include/nnvm
-
-# This tag can be used to specify the character encoding of the source files
-# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
-# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
-# documentation (see: http://www.gnu.org/software/libiconv) for the list of
-# possible encodings.
-# The default value is: UTF-8.
-
-INPUT_ENCODING         = UTF-8
-
-# If the value of the INPUT tag contains directories, you can use the
-# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
-# *.h) to filter out the source-files in the directories. If left blank the
-# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii,
-# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp,
-# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown,
-# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf,
-# *.qsf, *.as and *.js.
-
-FILE_PATTERNS          = *.h
-
-# The RECURSIVE tag can be used to specify whether or not subdirectories should
-# be searched for input files as well.
-# The default value is: NO.
-
-RECURSIVE              = YES
-
-# The EXCLUDE tag can be used to specify files and/or directories that should be
-# excluded from the INPUT source files. This way you can easily exclude a
-# subdirectory from a directory tree whose root is specified with the INPUT tag.
-#
-# Note that relative paths are relative to the directory from which doxygen is
-# run.
-
-EXCLUDE                =
-
-# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
-# directories that are symbolic links (a Unix file system feature) are excluded
-# from the input.
-# The default value is: NO.
-
-EXCLUDE_SYMLINKS       = NO
-
-# If the value of the INPUT tag contains directories, you can use the
-# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
-# certain files from those directories.
-#
-# Note that the wildcards are matched against the file with absolute path, so to
-# exclude all test directories for example use the pattern */test/*
-
-EXCLUDE_PATTERNS       = */test/* \
-                         logging.h
-
-# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
-# (namespaces, classes, functions, etc.) that should be excluded from the
-# output. The symbol name can be a fully qualified name, a word, or if the
-# wildcard * is used, a substring. Examples: ANamespace, AClass,
-# AClass::ANamespace, ANamespace::*Test
-#
-# Note that the wildcards are matched against the file with absolute path, so to
-# exclude all test directories use the pattern */test/*
-
-EXCLUDE_SYMBOLS        =
-
-# The EXAMPLE_PATH tag can be used to specify one or more files or directories
-# that contain example code fragments that are included (see the \include
-# command).
-
-EXAMPLE_PATH           =
-
-# If the value of the EXAMPLE_PATH tag contains directories, you can use the
-# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
-# *.h) to filter out the source-files in the directories. If left blank all
-# files are included.
-
-EXAMPLE_PATTERNS       =
-
-# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
-# searched for input files to be used with the \include or \dontinclude commands
-# irrespective of the value of the RECURSIVE tag.
-# The default value is: NO.
-
-EXAMPLE_RECURSIVE      = NO
-
-# The IMAGE_PATH tag can be used to specify one or more files or directories
-# that contain images that are to be included in the documentation (see the
-# \image command).
-
-IMAGE_PATH             =
-
-# The INPUT_FILTER tag can be used to specify a program that doxygen should
-# invoke to filter for each input file. Doxygen will invoke the filter program
-# by executing (via popen()) the command:
-#
-# <filter> <input-file>
-#
-# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
-# name of an input file. Doxygen will then use the output that the filter
-# program writes to standard output. If FILTER_PATTERNS is specified, this tag
-# will be ignored.
-#
-# Note that the filter must not add or remove lines; it is applied before the
-# code is scanned, but not when the output code is generated. If lines are added
-# or removed, the anchors will not be placed correctly.
-
-INPUT_FILTER           =
-
-# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
-# basis. Doxygen will compare the file name with each pattern and apply the
-# filter if there is a match. The filters are a list of the form: pattern=filter
-# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
-# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
-# patterns match the file name, INPUT_FILTER is applied.
-
-FILTER_PATTERNS        =
-
-# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
-# INPUT_FILTER ) will also be used to filter the input files that are used for
-# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
-# The default value is: NO.
-
-FILTER_SOURCE_FILES    = NO
-
-# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
-# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
-# it is also possible to disable source filtering for a specific pattern using
-# *.ext= (so without naming a filter).
-# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
-
-FILTER_SOURCE_PATTERNS =
-
-# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
-# is part of the input, its contents will be placed on the main page
-# (index.html). This can be useful if you have a project on for instance GitHub
-# and want to reuse the introduction page also for the doxygen output.
-
-#USE_MDFILE_AS_MAINPAGE =
-
-#---------------------------------------------------------------------------
-# Configuration options related to source browsing
-#---------------------------------------------------------------------------
-
-# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
-# generated. Documented entities will be cross-referenced with these sources.
-#
-# Note: To get rid of all source code in the generated output, make sure that
-# also VERBATIM_HEADERS is set to NO.
-# The default value is: NO.
-
-SOURCE_BROWSER         = NO
-
-# Setting the INLINE_SOURCES tag to YES will include the body of functions,
-# classes and enums directly into the documentation.
-# The default value is: NO.
-
-INLINE_SOURCES         = NO
-
-# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
-# special comment blocks from generated source code fragments. Normal C, C++ and
-# Fortran comments will always remain visible.
-# The default value is: YES.
-
-STRIP_CODE_COMMENTS    = YES
-
-# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
-# function all documented functions referencing it will be listed.
-# The default value is: NO.
-
-REFERENCED_BY_RELATION = NO
-
-# If the REFERENCES_RELATION tag is set to YES then for each documented function
-# all documented entities called/used by that function will be listed.
-# The default value is: NO.
-
-REFERENCES_RELATION    = NO
-
-# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
-# to YES, then the hyperlinks from functions in REFERENCES_RELATION and
-# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
-# link to the documentation.
-# The default value is: YES.
-
-REFERENCES_LINK_SOURCE = YES
-
-# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
-# source code will show a tooltip with additional information such as prototype,
-# brief description and links to the definition and documentation. Since this
-# will make the HTML file larger and loading of large files a bit slower, you
-# can opt to disable this feature.
-# The default value is: YES.
-# This tag requires that the tag SOURCE_BROWSER is set to YES.
-
-#SOURCE_TOOLTIPS        = YES
-
-# If the USE_HTAGS tag is set to YES then the references to source code will
-# point to the HTML generated by the htags(1) tool instead of doxygen built-in
-# source browser. The htags tool is part of GNU's global source tagging system
-# (see http://www.gnu.org/software/global/global.html). You will need version
-# 4.8.6 or higher.
-#
-# To use it do the following:
-# - Install the latest version of global
-# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
-# - Make sure the INPUT points to the root of the source tree
-# - Run doxygen as normal
-#
-# Doxygen will invoke htags (and that will in turn invoke gtags), so these
-# tools must be available from the command line (i.e. in the search path).
-#
-# The result: instead of the source browser generated by doxygen, the links to
-# source code will now point to the output of htags.
-# The default value is: NO.
-# This tag requires that the tag SOURCE_BROWSER is set to YES.
-
-USE_HTAGS              = NO
-
-# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
-# verbatim copy of the header file for each class for which an include is
-# specified. Set to NO to disable this.
-# See also: Section \class.
-# The default value is: YES.
-
-VERBATIM_HEADERS       = YES
-
-# If the CLANG_ASSISTED_PARSING tag is set to YES, then doxygen will use the
-# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the
-# cost of reduced performance. This can be particularly helpful with template
-# rich C++ code for which doxygen's built-in parser lacks the necessary type
-# information.
-# Note: The availability of this option depends on whether or not doxygen was
-# compiled with the --with-libclang option.
-# The default value is: NO.
-
-#CLANG_ASSISTED_PARSING = NO
-
-# If clang assisted parsing is enabled you can provide the compiler with command
-# line options that you would normally use when invoking the compiler. Note that
-# the include paths will already be set by doxygen for the files and directories
-# specified with INPUT and INCLUDE_PATH.
-# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
-
-#CLANG_OPTIONS          =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the alphabetical class index
-#---------------------------------------------------------------------------
-
-# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
-# compounds will be generated. Enable this if the project contains a lot of
-# classes, structs, unions or interfaces.
-# The default value is: YES.
-
-ALPHABETICAL_INDEX     = YES
-
-# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
-# which the alphabetical index list will be split.
-# Minimum value: 1, maximum value: 20, default value: 5.
-# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
-
-COLS_IN_ALPHA_INDEX    = 5
-
-# In case all classes in a project start with a common prefix, all classes will
-# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
-# can be used to specify a prefix (or a list of prefixes) that should be ignored
-# while generating the index headers.
-# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
-
-IGNORE_PREFIX          =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the HTML output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output
-# The default value is: YES.
-
-GENERATE_HTML          = YES
-
-# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: html.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_OUTPUT            = html
-
-# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
-# generated HTML page (for example: .htm, .php, .asp).
-# The default value is: .html.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_FILE_EXTENSION    = .html
-
-# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
-# each generated HTML page. If the tag is left blank doxygen will generate a
-# standard header.
-#
-# To get valid HTML the header file that includes any scripts and style sheets
-# that doxygen needs, which is dependent on the configuration options used (e.g.
-# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
-# default header using
-# doxygen -w html new_header.html new_footer.html new_stylesheet.css
-# YourConfigFile
-# and then modify the file new_header.html. See also section "Doxygen usage"
-# for information on how to generate the default header that doxygen normally
-# uses.
-# Note: The header is subject to change so you typically have to regenerate the
-# default header when upgrading to a newer version of doxygen. For a description
-# of the possible markers and block names see the documentation.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_HEADER            =
-
-# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
-# generated HTML page. If the tag is left blank doxygen will generate a standard
-# footer. See HTML_HEADER for more information on how to generate a default
-# footer and what special commands can be used inside the footer. See also
-# section "Doxygen usage" for information on how to generate the default footer
-# that doxygen normally uses.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_FOOTER            =
-
-# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
-# sheet that is used by each HTML page. It can be used to fine-tune the look of
-# the HTML output. If left blank doxygen will generate a default style sheet.
-# See also section "Doxygen usage" for information on how to generate the style
-# sheet that doxygen normally uses.
-# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
-# it is more robust and this tag (HTML_STYLESHEET) will in the future become
-# obsolete.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_STYLESHEET        =
-
-# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
-# cascading style sheets that are included after the standard style sheets
-# created by doxygen. Using this option one can overrule certain style aspects.
-# This is preferred over using HTML_STYLESHEET since it does not replace the
-# standard style sheet and is therefor more robust against future updates.
-# Doxygen will copy the style sheet files to the output directory.
-# Note: The order of the extra stylesheet files is of importance (e.g. the last
-# stylesheet in the list overrules the setting of the previous ones in the
-# list). For an example see the documentation.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-#HTML_EXTRA_STYLESHEET  =
-
-# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
-# other source files which should be copied to the HTML output directory. Note
-# that these files will be copied to the base HTML output directory. Use the
-# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
-# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
-# files will be copied as-is; there are no commands or markers available.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_EXTRA_FILES       =
-
-# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
-# will adjust the colors in the stylesheet and background images according to
-# this color. Hue is specified as an angle on a colorwheel, see
-# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
-# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
-# purple, and 360 is red again.
-# Minimum value: 0, maximum value: 359, default value: 220.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_COLORSTYLE_HUE    = 220
-
-# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
-# in the HTML output. For a value of 0 the output will use grayscales only. A
-# value of 255 will produce the most vivid colors.
-# Minimum value: 0, maximum value: 255, default value: 100.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_COLORSTYLE_SAT    = 100
-
-# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
-# luminance component of the colors in the HTML output. Values below 100
-# gradually make the output lighter, whereas values above 100 make the output
-# darker. The value divided by 100 is the actual gamma applied, so 80 represents
-# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
-# change the gamma.
-# Minimum value: 40, maximum value: 240, default value: 80.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_COLORSTYLE_GAMMA  = 80
-
-# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
-# page will contain the date and time when the page was generated. Setting this
-# to NO can help when comparing the output of multiple runs.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_TIMESTAMP         = YES
-
-# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
-# documentation will contain sections that can be hidden and shown after the
-# page has loaded.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_DYNAMIC_SECTIONS  = NO
-
-# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
-# shown in the various tree structured indices initially; the user can expand
-# and collapse entries dynamically later on. Doxygen will expand the tree to
-# such a level that at most the specified number of entries are visible (unless
-# a fully collapsed tree already exceeds this amount). So setting the number of
-# entries 1 will produce a full collapsed tree by default. 0 is a special value
-# representing an infinite number of entries and will result in a full expanded
-# tree by default.
-# Minimum value: 0, maximum value: 9999, default value: 100.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-#HTML_INDEX_NUM_ENTRIES = 100
-
-# If the GENERATE_DOCSET tag is set to YES, additional index files will be
-# generated that can be used as input for Apple's Xcode 3 integrated development
-# environment (see: http://developer.apple.com/tools/xcode/), introduced with
-# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
-# Makefile in the HTML output directory. Running make will produce the docset in
-# that directory and running make install will install the docset in
-# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
-# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
-# for more information.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_DOCSET        = NO
-
-# This tag determines the name of the docset feed. A documentation feed provides
-# an umbrella under which multiple documentation sets from a single provider
-# (such as a company or product suite) can be grouped.
-# The default value is: Doxygen generated docs.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_FEEDNAME        = "Doxygen generated docs"
-
-# This tag specifies a string that should uniquely identify the documentation
-# set bundle. This should be a reverse domain-name style string, e.g.
-# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
-# The default value is: org.doxygen.Project.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_BUNDLE_ID       = org.doxygen.Project
-
-# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
-# the documentation publisher. This should be a reverse domain-name style
-# string, e.g. com.mycompany.MyDocSet.documentation.
-# The default value is: org.doxygen.Publisher.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
-
-# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
-# The default value is: Publisher.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_PUBLISHER_NAME  = Publisher
-
-# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
-# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
-# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
-# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
-# Windows.
-#
-# The HTML Help Workshop contains a compiler that can convert all HTML output
-# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
-# files are now used as the Windows 98 help format, and will replace the old
-# Windows help format (.hlp) on all Windows platforms in the future. Compressed
-# HTML files also contain an index, a table of contents, and you can search for
-# words in the documentation. The HTML workshop also contains a viewer for
-# compressed HTML files.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_HTMLHELP      = NO
-
-# The CHM_FILE tag can be used to specify the file name of the resulting .chm
-# file. You can add a path in front of the file if the result should not be
-# written to the html output directory.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-CHM_FILE               =
-
-# The HHC_LOCATION tag can be used to specify the location (absolute path
-# including file name) of the HTML help compiler ( hhc.exe). If non-empty
-# doxygen will try to run the HTML help compiler on the generated index.hhp.
-# The file has to be specified with full path.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-HHC_LOCATION           =
-
-# The GENERATE_CHI flag controls if a separate .chi index file is generated (
-# YES) or that it should be included in the master .chm file ( NO).
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-GENERATE_CHI           = NO
-
-# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc)
-# and project file content.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-CHM_INDEX_ENCODING     =
-
-# The BINARY_TOC flag controls whether a binary table of contents is generated (
-# YES) or a normal table of contents ( NO) in the .chm file. Furthermore it
-# enables the Previous and Next buttons.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-BINARY_TOC             = NO
-
-# The TOC_EXPAND flag can be set to YES to add extra items for group members to
-# the table of contents of the HTML help documentation and to the tree view.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-TOC_EXPAND             = NO
-
-# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
-# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
-# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
-# (.qch) of the generated HTML documentation.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_QHP           = NO
-
-# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
-# the file name of the resulting .qch file. The path specified is relative to
-# the HTML output folder.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QCH_FILE               =
-
-# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
-# Project output. For more information please see Qt Help Project / Namespace
-# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
-# The default value is: org.doxygen.Project.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_NAMESPACE          = org.doxygen.Project
-
-# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
-# Help Project output. For more information please see Qt Help Project / Virtual
-# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
-# folders).
-# The default value is: doc.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_VIRTUAL_FOLDER     = doc
-
-# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
-# filter to add. For more information please see Qt Help Project / Custom
-# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
-# filters).
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_CUST_FILTER_NAME   =
-
-# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
-# custom filter to add. For more information please see Qt Help Project / Custom
-# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
-# filters).
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_CUST_FILTER_ATTRS  =
-
-# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
-# project's filter section matches. Qt Help Project / Filter Attributes (see:
-# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_SECT_FILTER_ATTRS  =
-
-# The QHG_LOCATION tag can be used to specify the location of Qt's
-# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
-# generated .qhp file.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHG_LOCATION           =
-
-# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
-# generated, together with the HTML files, they form an Eclipse help plugin. To
-# install this plugin and make it available under the help contents menu in
-# Eclipse, the contents of the directory containing the HTML and XML files needs
-# to be copied into the plugins directory of eclipse. The name of the directory
-# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
-# After copying Eclipse needs to be restarted before the help appears.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_ECLIPSEHELP   = NO
-
-# A unique identifier for the Eclipse help plugin. When installing the plugin
-# the directory name containing the HTML and XML files should also have this
-# name. Each documentation set should have its own identifier.
-# The default value is: org.doxygen.Project.
-# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
-
-ECLIPSE_DOC_ID         = org.doxygen.Project
-
-# If you want full control over the layout of the generated HTML pages it might
-# be necessary to disable the index and replace it with your own. The
-# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
-# of each HTML page. A value of NO enables the index and the value YES disables
-# it. Since the tabs in the index contain the same information as the navigation
-# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-DISABLE_INDEX          = NO
-
-# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
-# structure should be generated to display hierarchical information. If the tag
-# value is set to YES, a side panel will be generated containing a tree-like
-# index structure (just like the one that is generated for HTML Help). For this
-# to work a browser that supports JavaScript, DHTML, CSS and frames is required
-# (i.e. any modern browser). Windows users are probably better off using the
-# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can
-# further fine-tune the look of the index. As an example, the default style
-# sheet generated by doxygen has an example that shows how to put an image at
-# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
-# the same information as the tab index, you could consider setting
-# DISABLE_INDEX to YES when enabling this option.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_TREEVIEW      = NO
-
-# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
-# doxygen will group on one line in the generated HTML documentation.
-#
-# Note that a value of 0 will completely suppress the enum values from appearing
-# in the overview section.
-# Minimum value: 0, maximum value: 20, default value: 4.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-ENUM_VALUES_PER_LINE   = 4
-
-# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
-# to set the initial width (in pixels) of the frame in which the tree is shown.
-# Minimum value: 0, maximum value: 1500, default value: 250.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-TREEVIEW_WIDTH         = 250
-
-# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to
-# external symbols imported via tag files in a separate window.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-EXT_LINKS_IN_WINDOW    = NO
-
-# Use this tag to change the font size of LaTeX formulas included as images in
-# the HTML documentation. When you change the font size after a successful
-# doxygen run you need to manually remove any form_*.png images from the HTML
-# output directory to force them to be regenerated.
-# Minimum value: 8, maximum value: 50, default value: 10.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-FORMULA_FONTSIZE       = 10
-
-# Use the FORMULA_TRANPARENT tag to determine whether or not the images
-# generated for formulas are transparent PNGs. Transparent PNGs are not
-# supported properly for IE 6.0, but are supported on all modern browsers.
-#
-# Note that when changing this option you need to delete any form_*.png files in
-# the HTML output directory before the changes have effect.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-FORMULA_TRANSPARENT    = YES
-
-# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
-# http://www.mathjax.org) which uses client side Javascript for the rendering
-# instead of using prerendered bitmaps. Use this if you do not have LaTeX
-# installed or if you want to formulas look prettier in the HTML output. When
-# enabled you may also need to install MathJax separately and configure the path
-# to it using the MATHJAX_RELPATH option.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-USE_MATHJAX            = NO
-
-# When MathJax is enabled you can set the default output format to be used for
-# the MathJax output. See the MathJax site (see:
-# http://docs.mathjax.org/en/latest/output.html) for more details.
-# Possible values are: HTML-CSS (which is slower, but has the best
-# compatibility), NativeMML (i.e. MathML) and SVG.
-# The default value is: HTML-CSS.
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-#MATHJAX_FORMAT         = HTML-CSS
-
-# When MathJax is enabled you need to specify the location relative to the HTML
-# output directory using the MATHJAX_RELPATH option. The destination directory
-# should contain the MathJax.js script. For instance, if the mathjax directory
-# is located at the same level as the HTML output directory, then
-# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
-# Content Delivery Network so you can quickly see the result without installing
-# MathJax. However, it is strongly recommended to install a local copy of
-# MathJax from http://www.mathjax.org before deployment.
-# The default value is: http://cdn.mathjax.org/mathjax/latest.
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_RELPATH        = http://www.mathjax.org/mathjax
-
-# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
-# extension names that should be enabled during MathJax rendering. For example
-# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_EXTENSIONS     =
-
-# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
-# of code that will be used on startup of the MathJax code. See the MathJax site
-# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
-# example see the documentation.
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-#MATHJAX_CODEFILE       =
-
-# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
-# the HTML output. The underlying search engine uses javascript and DHTML and
-# should work on any modern browser. Note that when using HTML help
-# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
-# there is already a search function so this one should typically be disabled.
-# For large projects the javascript based search engine can be slow, then
-# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
-# search using the keyboard; to jump to the search box use <access key> + S
-# (what the <access key> is depends on the OS and browser, but it is typically
-# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
-# key> to jump into the search results window, the results can be navigated
-# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
-# the search. The filter options can be selected when the cursor is inside the
-# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
-# to select a filter and <Enter> or <escape> to activate or cancel the filter
-# option.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-SEARCHENGINE           = YES
-
-# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
-# implemented using a web server instead of a web client using Javascript. There
-# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
-# setting. When disabled, doxygen will generate a PHP script for searching and
-# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
-# and searching needs to be provided by external tools. See the section
-# "External Indexing and Searching" for details.
-# The default value is: NO.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-SERVER_BASED_SEARCH    = NO
-
-# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
-# script for searching. Instead the search results are written to an XML file
-# which needs to be processed by an external indexer. Doxygen will invoke an
-# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
-# search results.
-#
-# Doxygen ships with an example indexer ( doxyindexer) and search engine
-# (doxysearch.cgi) which are based on the open source search engine library
-# Xapian (see: http://xapian.org/).
-#
-# See the section "External Indexing and Searching" for details.
-# The default value is: NO.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-#EXTERNAL_SEARCH        = NO
-
-# The SEARCHENGINE_URL should point to a search engine hosted by a web server
-# which will return the search results when EXTERNAL_SEARCH is enabled.
-#
-# Doxygen ships with an example indexer ( doxyindexer) and search engine
-# (doxysearch.cgi) which are based on the open source search engine library
-# Xapian (see: http://xapian.org/). See the section "External Indexing and
-# Searching" for details.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-#SEARCHENGINE_URL       =
-
-# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
-# search data is written to a file for indexing by an external tool. With the
-# SEARCHDATA_FILE tag the name of this file can be specified.
-# The default file is: searchdata.xml.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-#SEARCHDATA_FILE        = searchdata.xml
-
-# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
-# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
-# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
-# projects and redirect the results back to the right project.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-#EXTERNAL_SEARCH_ID     =
-
-# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
-# projects other than the one defined by this configuration file, but that are
-# all added to the same external search index. Each project needs to have a
-# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
-# to a relative location where the documentation can be found. The format is:
-# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-#EXTRA_SEARCH_MAPPINGS  =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the LaTeX output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_LATEX tag is set to YES doxygen will generate LaTeX output.
-# The default value is: YES.
-
-GENERATE_LATEX         = YES
-
-# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: latex.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_OUTPUT           = latex
-
-# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
-# invoked.
-#
-# Note that when enabling USE_PDFLATEX this option is only used for generating
-# bitmaps for formulas in the HTML output, but not in the Makefile that is
-# written to the output directory.
-# The default file is: latex.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_CMD_NAME         = latex
-
-# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
-# index for LaTeX.
-# The default file is: makeindex.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-MAKEINDEX_CMD_NAME     = makeindex
-
-# If the COMPACT_LATEX tag is set to YES doxygen generates more compact LaTeX
-# documents. This may be useful for small projects and may help to save some
-# trees in general.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-COMPACT_LATEX          = NO
-
-# The PAPER_TYPE tag can be used to set the paper type that is used by the
-# printer.
-# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
-# 14 inches) and executive (7.25 x 10.5 inches).
-# The default value is: a4.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-PAPER_TYPE             = a4
-
-# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
-# that should be included in the LaTeX output. To get the times font for
-# instance you can specify
-# EXTRA_PACKAGES=times
-# If left blank no extra packages will be included.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-EXTRA_PACKAGES         =
-
-# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
-# generated LaTeX document. The header should contain everything until the first
-# chapter. If it is left blank doxygen will generate a standard header. See
-# section "Doxygen usage" for information on how to let doxygen write the
-# default header to a separate file.
-#
-# Note: Only use a user-defined header if you know what you are doing! The
-# following commands have a special meaning inside the header: $title,
-# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
-# $projectbrief, $projectlogo. Doxygen will replace $title with the empy string,
-# for the replacement values of the other commands the user is refered to
-# HTML_HEADER.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_HEADER           =
-
-# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
-# generated LaTeX document. The footer should contain everything after the last
-# chapter. If it is left blank doxygen will generate a standard footer. See
-# LATEX_HEADER for more information on how to generate a default footer and what
-# special commands can be used inside the footer.
-#
-# Note: Only use a user-defined footer if you know what you are doing!
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_FOOTER           =
-
-# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
-# other source files which should be copied to the LATEX_OUTPUT output
-# directory. Note that the files will be copied as-is; there are no commands or
-# markers available.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-#LATEX_EXTRA_FILES      =
-
-# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
-# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
-# contain links (just like the HTML output) instead of page references. This
-# makes the output suitable for online browsing using a PDF viewer.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-PDF_HYPERLINKS         = YES
-
-# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
-# the PDF file directly from the LaTeX files. Set this option to YES to get a
-# higher quality PDF documentation.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-USE_PDFLATEX           = YES
-
-# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
-# command to the generated LaTeX files. This will instruct LaTeX to keep running
-# if errors occur, instead of asking the user for help. This option is also used
-# when generating formulas in HTML.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_BATCHMODE        = NO
-
-# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
-# index chapters (such as File Index, Compound Index, etc.) in the output.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_HIDE_INDICES     = NO
-
-# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
-# code with syntax highlighting in the LaTeX output.
-#
-# Note that which sources are shown also depends on other settings such as
-# SOURCE_BROWSER.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_SOURCE_CODE      = NO
-
-# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
-# bibliography, e.g. plainnat, or ieeetr. See
-# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
-# The default value is: plain.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_BIB_STYLE        = plain
-
-#---------------------------------------------------------------------------
-# Configuration options related to the RTF output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_RTF tag is set to YES doxygen will generate RTF output. The
-# RTF output is optimized for Word 97 and may not look too pretty with other RTF
-# readers/editors.
-# The default value is: NO.
-
-GENERATE_RTF           = NO
-
-# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: rtf.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_OUTPUT             = rtf
-
-# If the COMPACT_RTF tag is set to YES doxygen generates more compact RTF
-# documents. This may be useful for small projects and may help to save some
-# trees in general.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-COMPACT_RTF            = NO
-
-# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
-# contain hyperlink fields. The RTF file will contain links (just like the HTML
-# output) instead of page references. This makes the output suitable for online
-# browsing using Word or some other Word compatible readers that support those
-# fields.
-#
-# Note: WordPad (write) and others do not support links.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_HYPERLINKS         = NO
-
-# Load stylesheet definitions from file. Syntax is similar to doxygen's config
-# file, i.e. a series of assignments. You only have to provide replacements,
-# missing definitions are set to their default value.
-#
-# See also section "Doxygen usage" for information on how to generate the
-# default style sheet that doxygen normally uses.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_STYLESHEET_FILE    =
-
-# Set optional variables used in the generation of an RTF document. Syntax is
-# similar to doxygen's config file. A template extensions file can be generated
-# using doxygen -e rtf extensionFile.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_EXTENSIONS_FILE    =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the man page output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_MAN tag is set to YES doxygen will generate man pages for
-# classes and files.
-# The default value is: NO.
-
-GENERATE_MAN           = NO
-
-# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it. A directory man3 will be created inside the directory specified by
-# MAN_OUTPUT.
-# The default directory is: man.
-# This tag requires that the tag GENERATE_MAN is set to YES.
-
-MAN_OUTPUT             = man
-
-# The MAN_EXTENSION tag determines the extension that is added to the generated
-# man pages. In case the manual section does not start with a number, the number
-# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
-# optional.
-# The default value is: .3.
-# This tag requires that the tag GENERATE_MAN is set to YES.
-
-MAN_EXTENSION          = .3
-
-# The MAN_SUBDIR tag determines the name of the directory created within
-# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
-# MAN_EXTENSION with the initial . removed.
-# This tag requires that the tag GENERATE_MAN is set to YES.
-
-#MAN_SUBDIR             =
-
-# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
-# will generate one additional man file for each entity documented in the real
-# man page(s). These additional files only source the real man page, but without
-# them the man command would be unable to find the correct page.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_MAN is set to YES.
-
-MAN_LINKS              = NO
-
-#---------------------------------------------------------------------------
-# Configuration options related to the XML output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_XML tag is set to YES doxygen will generate an XML file that
-# captures the structure of the code including all documentation.
-# The default value is: NO.
-
-GENERATE_XML           = YES
-
-# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: xml.
-# This tag requires that the tag GENERATE_XML is set to YES.
-
-XML_OUTPUT             = xml
-
-# If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program
-# listings (including syntax highlighting and cross-referencing information) to
-# the XML output. Note that enabling this will significantly increase the size
-# of the XML output.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_XML is set to YES.
-
-XML_PROGRAMLISTING     = YES
-
-#---------------------------------------------------------------------------
-# Configuration options related to the DOCBOOK output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_DOCBOOK tag is set to YES doxygen will generate Docbook files
-# that can be used to generate PDF.
-# The default value is: NO.
-
-#GENERATE_DOCBOOK       = NO
-
-# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
-# front of it.
-# The default directory is: docbook.
-# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
-
-#DOCBOOK_OUTPUT         = docbook
-
-# If the DOCBOOK_PROGRAMLISTING tag is set to YES doxygen will include the
-# program listings (including syntax highlighting and cross-referencing
-# information) to the DOCBOOK output. Note that enabling this will significantly
-# increase the size of the DOCBOOK output.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
-
-#DOCBOOK_PROGRAMLISTING = NO
-
-#---------------------------------------------------------------------------
-# Configuration options for the AutoGen Definitions output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_AUTOGEN_DEF tag is set to YES doxygen will generate an AutoGen
-# Definitions (see http://autogen.sf.net) file that captures the structure of
-# the code including all documentation. Note that this feature is still
-# experimental and incomplete at the moment.
-# The default value is: NO.
-
-GENERATE_AUTOGEN_DEF   = NO
-
-#---------------------------------------------------------------------------
-# Configuration options related to the Perl module output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_PERLMOD tag is set to YES doxygen will generate a Perl module
-# file that captures the structure of the code including all documentation.
-#
-# Note that this feature is still experimental and incomplete at the moment.
-# The default value is: NO.
-
-GENERATE_PERLMOD       = NO
-
-# If the PERLMOD_LATEX tag is set to YES doxygen will generate the necessary
-# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
-# output from the Perl module output.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_PERLMOD is set to YES.
-
-PERLMOD_LATEX          = NO
-
-# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be nicely
-# formatted so it can be parsed by a human reader. This is useful if you want to
-# understand what is going on. On the other hand, if this tag is set to NO the
-# size of the Perl module output will be much smaller and Perl will parse it
-# just the same.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_PERLMOD is set to YES.
-
-PERLMOD_PRETTY         = YES
-
-# The names of the make variables in the generated doxyrules.make file are
-# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
-# so different doxyrules.make files included by the same Makefile don't
-# overwrite each other's variables.
-# This tag requires that the tag GENERATE_PERLMOD is set to YES.
-
-PERLMOD_MAKEVAR_PREFIX =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the preprocessor
-#---------------------------------------------------------------------------
-
-# If the ENABLE_PREPROCESSING tag is set to YES doxygen will evaluate all
-# C-preprocessor directives found in the sources and include files.
-# The default value is: YES.
-
-ENABLE_PREPROCESSING   = YES
-
-# If the MACRO_EXPANSION tag is set to YES doxygen will expand all macro names
-# in the source code. If set to NO only conditional compilation will be
-# performed. Macro expansion can be done in a controlled way by setting
-# EXPAND_ONLY_PREDEF to YES.
-# The default value is: NO.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-MACRO_EXPANSION        = NO
-
-# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
-# the macro expansion is limited to the macros specified with the PREDEFINED and
-# EXPAND_AS_DEFINED tags.
-# The default value is: NO.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-EXPAND_ONLY_PREDEF     = NO
-
-# If the SEARCH_INCLUDES tag is set to YES the includes files in the
-# INCLUDE_PATH will be searched if a #include is found.
-# The default value is: YES.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-SEARCH_INCLUDES        = YES
-
-# The INCLUDE_PATH tag can be used to specify one or more directories that
-# contain include files that are not input files but should be processed by the
-# preprocessor.
-# This tag requires that the tag SEARCH_INCLUDES is set to YES.
-
-INCLUDE_PATH           =
-
-# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
-# patterns (like *.h and *.hpp) to filter out the header-files in the
-# directories. If left blank, the patterns specified with FILE_PATTERNS will be
-# used.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-INCLUDE_FILE_PATTERNS  =
-
-# The PREDEFINED tag can be used to specify one or more macro names that are
-# defined before the preprocessor is started (similar to the -D option of e.g.
-# gcc). The argument of the tag is a list of macros of the form: name or
-# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
-# is assumed. To prevent a macro definition from being undefined via #undef or
-# recursively expanded use the := operator instead of the = operator.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-PREDEFINED             = DMLC_USE_CXX11
-
-# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
-# tag can be used to specify a list of macro names that should be expanded. The
-# macro definition that is found in the sources will be used. Use the PREDEFINED
-# tag if you want to use a different macro definition that overrules the
-# definition found in the source code.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-EXPAND_AS_DEFINED      =
-
-# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
-# remove all references to function-like macros that are alone on a line, have
-# an all uppercase name, and do not end with a semicolon. Such function macros
-# are typically used for boiler-plate code, and will confuse the parser if not
-# removed.
-# The default value is: YES.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-SKIP_FUNCTION_MACROS   = YES
-
-#---------------------------------------------------------------------------
-# Configuration options related to external references
-#---------------------------------------------------------------------------
-
-# The TAGFILES tag can be used to specify one or more tag files. For each tag
-# file the location of the external documentation should be added. The format of
-# a tag file without this location is as follows:
-# TAGFILES = file1 file2 ...
-# Adding location for the tag files is done as follows:
-# TAGFILES = file1=loc1 "file2 = loc2" ...
-# where loc1 and loc2 can be relative or absolute paths or URLs. See the
-# section "Linking to external documentation" for more information about the use
-# of tag files.
-# Note: Each tag file must have a unique name (where the name does NOT include
-# the path). If a tag file is not located in the directory in which doxygen is
-# run, you must also specify the path to the tagfile here.
-
-TAGFILES               =
-
-# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
-# tag file that is based on the input files it reads. See section "Linking to
-# external documentation" for more information about the usage of tag files.
-
-GENERATE_TAGFILE       =
-
-# If the ALLEXTERNALS tag is set to YES all external class will be listed in the
-# class index. If set to NO only the inherited external classes will be listed.
-# The default value is: NO.
-
-ALLEXTERNALS           = NO
-
-# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed in
-# the modules index. If set to NO, only the current project's groups will be
-# listed.
-# The default value is: YES.
-
-EXTERNAL_GROUPS        = YES
-
-# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed in
-# the related pages index. If set to NO, only the current project's pages will
-# be listed.
-# The default value is: YES.
-
-#EXTERNAL_PAGES         = YES
-
-# The PERL_PATH should be the absolute path and name of the perl script
-# interpreter (i.e. the result of 'which perl').
-# The default file (with absolute path) is: /usr/bin/perl.
-
-PERL_PATH              = /usr/bin/perl
-
-#---------------------------------------------------------------------------
-# Configuration options related to the dot tool
-#---------------------------------------------------------------------------
-
-# If the CLASS_DIAGRAMS tag is set to YES doxygen will generate a class diagram
-# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
-# NO turns the diagrams off. Note that this option also works with HAVE_DOT
-# disabled, but it is recommended to install and use dot, since it yields more
-# powerful graphs.
-# The default value is: YES.
-
-CLASS_DIAGRAMS         = YES
-
-# You can define message sequence charts within doxygen comments using the \msc
-# command. Doxygen will then run the mscgen tool (see:
-# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
-# documentation. The MSCGEN_PATH tag allows you to specify the directory where
-# the mscgen tool resides. If left empty the tool is assumed to be found in the
-# default search path.
-
-MSCGEN_PATH            =
-
-# You can include diagrams made with dia in doxygen documentation. Doxygen will
-# then run dia to produce the diagram and insert it in the documentation. The
-# DIA_PATH tag allows you to specify the directory where the dia binary resides.
-# If left empty dia is assumed to be found in the default search path.
-
-#DIA_PATH               =
-
-# If set to YES, the inheritance and collaboration graphs will hide inheritance
-# and usage relations if the target is undocumented or is not a class.
-# The default value is: YES.
-
-HIDE_UNDOC_RELATIONS   = YES
-
-# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
-# available from the path. This tool is part of Graphviz (see:
-# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
-# Bell Labs. The other options in this section have no effect if this option is
-# set to NO
-# The default value is: YES.
-
-HAVE_DOT               = YES
-
-# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
-# to run in parallel. When set to 0 doxygen will base this on the number of
-# processors available in the system. You can set it explicitly to a value
-# larger than 0 to get control over the balance between CPU load and processing
-# speed.
-# Minimum value: 0, maximum value: 32, default value: 0.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_NUM_THREADS        = 0
-
-# When you want a differently looking font in the dot files that doxygen
-# generates you can specify the font name using DOT_FONTNAME. You need to make
-# sure dot is able to find the font, which can be done by putting it in a
-# standard location or by setting the DOTFONTPATH environment variable or by
-# setting DOT_FONTPATH to the directory containing the font.
-# The default value is: Helvetica.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_FONTNAME           = Helvetica
-
-# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
-# dot graphs.
-# Minimum value: 4, maximum value: 24, default value: 10.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_FONTSIZE           = 10
-
-# By default doxygen will tell dot to use the default font as specified with
-# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
-# the path where dot can find it using this tag.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_FONTPATH           =
-
-# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
-# each documented class showing the direct and indirect inheritance relations.
-# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-CLASS_GRAPH            = YES
-
-# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
-# graph for each documented class showing the direct and indirect implementation
-# dependencies (inheritance, containment, and class references variables) of the
-# class with other documented classes.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-COLLABORATION_GRAPH    = YES
-
-# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
-# groups, showing the direct groups dependencies.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-GROUP_GRAPHS           = YES
-
-# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
-# collaboration diagrams in a style similar to the OMG's Unified Modeling
-# Language.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-UML_LOOK               = YES
-
-# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
-# class node. If there are many fields or methods and many nodes the graph may
-# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
-# number of items for each type to make the size more manageable. Set this to 0
-# for no limit. Note that the threshold may be exceeded by 50% before the limit
-# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
-# but if the number exceeds 15, the total amount of fields shown is limited to
-# 10.
-# Minimum value: 0, maximum value: 100, default value: 10.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-#UML_LIMIT_NUM_FIELDS   = 10
-
-# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
-# collaboration graphs will show the relations between templates and their
-# instances.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-TEMPLATE_RELATIONS     = NO
-
-# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
-# YES then doxygen will generate a graph for each documented file showing the
-# direct and indirect include dependencies of the file with other documented
-# files.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-INCLUDE_GRAPH          = YES
-
-# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
-# set to YES then doxygen will generate a graph for each documented file showing
-# the direct and indirect include dependencies of the file with other documented
-# files.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-INCLUDED_BY_GRAPH      = YES
-
-# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
-# dependency graph for every global function or class method.
-#
-# Note that enabling this option will significantly increase the time of a run.
-# So in most cases it will be better to enable call graphs for selected
-# functions only using the \callgraph command.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-CALL_GRAPH             = NO
-
-# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
-# dependency graph for every global function or class method.
-#
-# Note that enabling this option will significantly increase the time of a run.
-# So in most cases it will be better to enable caller graphs for selected
-# functions only using the \callergraph command.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-CALLER_GRAPH           = NO
-
-# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
-# hierarchy of all classes instead of a textual one.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-GRAPHICAL_HIERARCHY    = YES
-
-# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
-# dependencies a directory has on other directories in a graphical way. The
-# dependency relations are determined by the #include relations between the
-# files in the directories.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DIRECTORY_GRAPH        = YES
-
-# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
-# generated by dot.
-# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
-# to make the SVG files visible in IE 9+ (other browsers do not have this
-# requirement).
-# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd,
-# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo,
-# gif:cairo:gd, gif:gd, gif:gd:gd and svg.
-# The default value is: png.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_IMAGE_FORMAT       = png
-
-# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
-# enable generation of interactive SVG images that allow zooming and panning.
-#
-# Note that this requires a modern browser other than Internet Explorer. Tested
-# and working are Firefox, Chrome, Safari, and Opera.
-# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
-# the SVG files visible. Older versions of IE do not have SVG support.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-INTERACTIVE_SVG        = NO
-
-# The DOT_PATH tag can be used to specify the path where the dot tool can be
-# found. If left blank, it is assumed the dot tool can be found in the path.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_PATH               =
-
-# The DOTFILE_DIRS tag can be used to specify one or more directories that
-# contain dot files that are included in the documentation (see the \dotfile
-# command).
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOTFILE_DIRS           =
-
-# The MSCFILE_DIRS tag can be used to specify one or more directories that
-# contain msc files that are included in the documentation (see the \mscfile
-# command).
-
-MSCFILE_DIRS           =
-
-# The DIAFILE_DIRS tag can be used to specify one or more directories that
-# contain dia files that are included in the documentation (see the \diafile
-# command).
-
-#DIAFILE_DIRS           =
-
-# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
-# path where java can find the plantuml.jar file. If left blank, it is assumed
-# PlantUML is not used or called during a preprocessing step. Doxygen will
-# generate a warning when it encounters a \startuml command in this case and
-# will not generate output for the diagram.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-#PLANTUML_JAR_PATH      =
-
-# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
-# that will be shown in the graph. If the number of nodes in a graph becomes
-# larger than this value, doxygen will truncate the graph, which is visualized
-# by representing a node as a red box. Note that doxygen if the number of direct
-# children of the root node in a graph is already larger than
-# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
-# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
-# Minimum value: 0, maximum value: 10000, default value: 50.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_GRAPH_MAX_NODES    = 50
-
-# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
-# generated by dot. A depth value of 3 means that only nodes reachable from the
-# root by following a path via at most 3 edges will be shown. Nodes that lay
-# further from the root node will be omitted. Note that setting this option to 1
-# or 2 may greatly reduce the computation time needed for large code bases. Also
-# note that the size of a graph can be further restricted by
-# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
-# Minimum value: 0, maximum value: 1000, default value: 0.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-MAX_DOT_GRAPH_DEPTH    = 0
-
-# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
-# background. This is disabled by default, because dot on Windows does not seem
-# to support this out of the box.
-#
-# Warning: Depending on the platform used, enabling this option may lead to
-# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
-# read).
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_TRANSPARENT        = NO
-
-# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
-# files in one run (i.e. multiple -o and -T options on the command line). This
-# makes dot run faster, but since only newer versions of dot (>1.8.10) support
-# this, this feature is disabled by default.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_MULTI_TARGETS      = YES
-
-# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
-# explaining the meaning of the various boxes and arrows in the dot generated
-# graphs.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-GENERATE_LEGEND        = YES
-
-# If the DOT_CLEANUP tag is set to YES doxygen will remove the intermediate dot
-# files that are used to generate the various graphs.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_CLEANUP            = YES
diff --git a/nnvm/docs/Makefile b/nnvm/docs/Makefile
deleted file mode 100644
index 1e45fb5e3787..000000000000
--- a/nnvm/docs/Makefile
+++ /dev/null
@@ -1,193 +0,0 @@
-# Makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line.
-SPHINXOPTS    =
-SPHINXBUILD   = sphinx-build
-PAPER         =
-BUILDDIR      = _build
-
-# User-friendly check for sphinx-build
-ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
-$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
-endif
-
-# Internal variables.
-PAPEROPT_a4     = -D latex_paper_size=a4
-PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-# the i18n builder cannot share the environment and doctrees with the others
-I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-
-.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
-
-help:
-	@echo "Please use \`make <target>' where <target> is one of"
-	@echo "  html       to make standalone HTML files"
-	@echo "  dirhtml    to make HTML files named index.html in directories"
-	@echo "  singlehtml to make a single large HTML file"
-	@echo "  pickle     to make pickle files"
-	@echo "  json       to make JSON files"
-	@echo "  htmlhelp   to make HTML files and a HTML help project"
-	@echo "  qthelp     to make HTML files and a qthelp project"
-	@echo "  applehelp  to make an Apple Help Book"
-	@echo "  devhelp    to make HTML files and a Devhelp project"
-	@echo "  epub       to make an epub"
-	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
-	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
-	@echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
-	@echo "  text       to make text files"
-	@echo "  man        to make manual pages"
-	@echo "  texinfo    to make Texinfo files"
-	@echo "  info       to make Texinfo files and run them through makeinfo"
-	@echo "  gettext    to make PO message catalogs"
-	@echo "  changes    to make an overview of all changed/added/deprecated items"
-	@echo "  xml        to make Docutils-native XML files"
-	@echo "  pseudoxml  to make pseudoxml-XML files for display purposes"
-	@echo "  linkcheck  to check all external links for integrity"
-	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
-	@echo "  coverage   to run coverage check of the documentation (if enabled)"
-
-clean:
-	rm -rf $(BUILDDIR)/*
-	rm -rf gen_modules
-
-html:
-	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
-	@echo
-	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
-
-dirhtml:
-	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
-	@echo
-	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
-
-singlehtml:
-	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
-	@echo
-	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
-
-pickle:
-	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
-	@echo
-	@echo "Build finished; now you can process the pickle files."
-
-json:
-	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
-	@echo
-	@echo "Build finished; now you can process the JSON files."
-
-htmlhelp:
-	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
-	@echo
-	@echo "Build finished; now you can run HTML Help Workshop with the" \
-	      ".hhp project file in $(BUILDDIR)/htmlhelp."
-
-qthelp:
-	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
-	@echo
-	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
-	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
-	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/rabit.qhcp"
-	@echo "To view the help file:"
-	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/rabit.qhc"
-
-applehelp:
-	$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
-	@echo
-	@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
-	@echo "N.B. You won't be able to view it unless you put it in" \
-	      "~/Library/Documentation/Help or install it in your application" \
-	      "bundle."
-
-devhelp:
-	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
-	@echo
-	@echo "Build finished."
-	@echo "To view the help file:"
-	@echo "# mkdir -p $$HOME/.local/share/devhelp/rabit"
-	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/rabit"
-	@echo "# devhelp"
-
-epub:
-	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
-	@echo
-	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
-
-latex:
-	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
-	@echo
-	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
-	@echo "Run \`make' in that directory to run these through (pdf)latex" \
-	      "(use \`make latexpdf' here to do that automatically)."
-
-latexpdf:
-	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
-	@echo "Running LaTeX files through pdflatex..."
-	$(MAKE) -C $(BUILDDIR)/latex all-pdf
-	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
-
-latexpdfja:
-	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
-	@echo "Running LaTeX files through platex and dvipdfmx..."
-	$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
-	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
-
-text:
-	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
-	@echo
-	@echo "Build finished. The text files are in $(BUILDDIR)/text."
-
-man:
-	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
-	@echo
-	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
-
-texinfo:
-	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
-	@echo
-	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
-	@echo "Run \`make' in that directory to run these through makeinfo" \
-	      "(use \`make info' here to do that automatically)."
-
-info:
-	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
-	@echo "Running Texinfo files through makeinfo..."
-	make -C $(BUILDDIR)/texinfo info
-	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
-
-gettext:
-	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
-	@echo
-	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
-
-changes:
-	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
-	@echo
-	@echo "The overview file is in $(BUILDDIR)/changes."
-
-linkcheck:
-	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
-	@echo
-	@echo "Link check complete; look for any errors in the above output " \
-	      "or in $(BUILDDIR)/linkcheck/output.txt."
-
-doctest:
-	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
-	@echo "Testing of doctests in the sources finished, look at the " \
-	      "results in $(BUILDDIR)/doctest/output.txt."
-
-coverage:
-	$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
-	@echo "Testing of coverage in the sources finished, look at the " \
-	      "results in $(BUILDDIR)/coverage/python.txt."
-
-xml:
-	$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
-	@echo
-	@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
-
-pseudoxml:
-	$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
-	@echo
-	@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
diff --git a/nnvm/docs/README.txt b/nnvm/docs/README.txt
deleted file mode 100644
index 8b8c750822be..000000000000
--- a/nnvm/docs/README.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-The documentation of nnvm is generated with recommonmark and sphinx.
-
-- pip install sphinx>=1.5.5 sphinx-gallery sphinx_rtd_theme matplotlib Image recommonmark
-- Build tvm first in the root folder.
diff --git a/nnvm/docs/conf.py b/nnvm/docs/conf.py
deleted file mode 100644
index 64e40eb748b5..000000000000
--- a/nnvm/docs/conf.py
+++ /dev/null
@@ -1,209 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# documentation build configuration file, created by
-# sphinx-quickstart on Thu Jul 23 19:40:08 2015.
-#
-# This file is execfile()d with the current directory set to its
-# containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-import sys
-import os, subprocess
-import shlex
-import recommonmark
-import sphinx_gallery
-from tvm.contrib import rpc, graph_runtime
-from recommonmark.parser import CommonMarkParser
-from recommonmark.transform import AutoStructify
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
-sys.path.insert(0, os.path.join(curr_path, '../python/'))
-
-# -- General configuration ------------------------------------------------
-
-# General information about the project.
-project = u'nnvm'
-author = u'%s developers' % project
-copyright = u'2017, %s' % author
-github_doc_root = 'https://github.com/dmlc/nnvm/tree/master/docs/'
-
-# add markdown parser
-CommonMarkParser.github_doc_root = github_doc_root
-source_parsers = {
-    '.md': CommonMarkParser
-}
-os.environ['NNVM_BUILD_DOC'] = '1'
-# Version information.
-import nnvm
-version = nnvm.__version__
-release = nnvm.__version__
-
-# Add any Sphinx extension module names here, as strings. They can be
-# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones
-extensions = [
-    'sphinx.ext.autodoc',
-    'sphinx.ext.autosummary',
-    'sphinx.ext.intersphinx',
-    'sphinx.ext.napoleon',
-    'sphinx.ext.mathjax',
-    'sphinx_gallery.gen_gallery',
-]
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-# The suffix(es) of source filenames.
-# You can specify multiple suffix as a list of string:
-# source_suffix = ['.rst', '.md']
-source_suffix = ['.rst', '.md']
-
-# The encoding of source files.
-#source_encoding = 'utf-8-sig'
-
-# generate autosummary even if no references
-autosummary_generate = True
-
-# The master toctree document.
-master_doc = 'index'
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#
-# This is also used if you do content translation via gettext catalogs.
-# Usually you set "language" from the command line for these cases.
-language = None
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-#today = ''
-# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-exclude_patterns = ['_build']
-
-# The reST default role (used for this markup: `text`) to use for all
-# documents.
-#default_role = None
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-#add_module_names = True
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-#show_authors = False
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
-
-# If true, keep warnings as "system message" paragraphs in the built documents.
-#keep_warnings = False
-
-# If true, `todo` and `todoList` produce output, else they produce nothing.
-todo_include_todos = False
-
-# -- Options for HTML output ----------------------------------------------
-
-# The theme is set by the make target
-html_theme = os.environ.get('NNVM_THEME', 'rtd')
-
-on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
-# only import rtd theme and set it if want to build docs locally
-if not on_rtd and html_theme == 'rtd':
-    import sphinx_rtd_theme
-    html_theme = 'sphinx_rtd_theme'
-    html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-# html_static_path = ['_static']
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = project + 'doc'
-
-# -- Options for LaTeX output ---------------------------------------------
-latex_elements = {
-}
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title,
-#  author, documentclass [howto, manual, or own class]).
-latex_documents = [
-  (master_doc, '%s.tex' % project, project,
-   author, 'manual'),
-]
-
-# hook for doxygen
-def run_doxygen(folder):
-    """Run the doxygen make command in the designated folder."""
-    try:
-        #retcode = subprocess.call("cd %s; make doc" % folder, shell=True)
-        retcode = subprocess.call("rm -rf _build/html/doxygen", shell=True)
-        retcode = subprocess.call("mkdir -p _build/html", shell=True)
-        retcode = subprocess.call("cp -rf doxygen/html _build/html/doxygen", shell=True)
-        if retcode < 0:
-            sys.stderr.write("doxygen terminated by signal %s" % (-retcode))
-    except OSError as e:
-        sys.stderr.write("doxygen execution failed: %s" % e)
-
-intersphinx_mapping = {
-    'python': ('https://docs.python.org/{.major}'.format(sys.version_info), None),
-    'numpy': ('http://docs.scipy.org/doc/numpy/', None),
-    'scipy': ('http://docs.scipy.org/doc/scipy/reference', None),
-    'matplotlib': ('http://matplotlib.org/', None),
-    'tvm': ('http://docs.tvmlang.org/', None),
-}
-
-
-from sphinx_gallery.sorting import ExplicitOrder
-
-examples_dirs = ['../tutorials/']
-gallery_dirs = ['tutorials']
-
-subsection_order = ExplicitOrder([])
-
-def generate_doxygen_xml(app):
-    """Run the doxygen make commands if we're on the ReadTheDocs server"""
-    run_doxygen('..')
-
-def setup(app):
-    # Add hook for building doxygen xml when needed
-    # no c++ API for now
-    app.connect("builder-inited", generate_doxygen_xml)
-    app.add_config_value('recommonmark_config', {
-        'url_resolver': lambda url: github_doc_root + url,
-        'auto_doc_ref': True
-            }, True)
-    app.add_transform(AutoStructify)
-
-
-sphinx_gallery_conf = {
-    'backreferences_dir': 'gen_modules/backreferences',
-    'doc_module': ('tvm', 'nnvm', 'numpy'),
-'reference_url': {
-    'nnvm': None,
-    'tvm': 'http://docs.tvmlang.org',
-    'numpy': 'http://docs.scipy.org/doc/numpy-1.9.1'},
-    'examples_dirs': examples_dirs,
-    'gallery_dirs': gallery_dirs,
-    'subsection_order': subsection_order,
-    'find_mayavi_figures': False,
-    'filename_pattern': '.py',
-    'expected_failing_examples': []
-}
diff --git a/nnvm/docs/dev/index.rst b/nnvm/docs/dev/index.rst
deleted file mode 100644
index 0647c9cce586..000000000000
--- a/nnvm/docs/dev/index.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-Design Note
-===========
-
-In this part of documentation, we share the rationale for the specific choices made when designing NNVM.
-
-.. toctree::
-   :maxdepth: 2
-
-   overview
diff --git a/nnvm/docs/how_to/contribute.md b/nnvm/docs/how_to/contribute.md
deleted file mode 100644
index 34ff2f19644c..000000000000
--- a/nnvm/docs/how_to/contribute.md
+++ /dev/null
@@ -1,102 +0,0 @@
-# Contribute to NNVM
-
-NNVM has been developed by community members.
-Everyone is more than welcome to contribute.
-It is a way to make the project better and more accessible to more users.
-NNVM compiler relies on TVM to deploy to different hardware backends.
-You can improve the compiler performance by contributing to [TVM](https://github.com/dmlc/tvm)
-
-- Please update [NEWS.md](https://github.com/dmlc/nnvm/blob/master/NEWS.md) to
-  add note on your changes to the API or added a new document.
-
-## Guidelines
-* [Submit Pull Request](#submit-pull-request)
-* [Git Workflow Howtos](#git-workflow-howtos)
-  - [How to resolve conflict with master](#how-to-resolve-conflict-with-master)
-  - [How to combine multiple commits into one](#how-to-combine-multiple-commits-into-one)
-  - [What is the consequence of force push](#what-is-the-consequence-of-force-push)
-* [Document](#document)
-* [Testcases](#testcases)
-* [Examples](#examples)
-* [Core Library](#core-library)
-* [Python Package](#python-package)
-
-## Submit Pull Request
-* Before submit, please rebase your code on the most recent version of master, you can do it by
-```bash
-git remote add upstream [url to nnvm repo]
-git fetch upstream
-git rebase upstream/master
-```
-* If you have multiple small commits,
-  it might be good to merge them together(use git rebase then squash) into more meaningful groups.
-* Send the pull request!
-  - Fix the problems reported by automatic checks
-  - If you are contributing a new module or new function, add a test.
-
-## Git Workflow Howtos
-### How to resolve conflict with master
-- First rebase to most recent master
-```bash
-# The first two steps can be skipped after you do it once.
-git remote add upstream [url to nnvm repo]
-git fetch upstream
-git rebase upstream/master
-```
-- The git may show some conflicts it cannot merge, say ```conflicted.py```.
-  - Manually modify the file to resolve the conflict.
-  - After you resolved the conflict, mark it as resolved by
-```bash
-git add conflicted.py
-```
-- Then you can continue rebase by
-```bash
-git rebase --continue
-```
-- Finally push to your fork, you may need to force push here.
-```bash
-git push --force
-```
-
-### How to combine multiple commits into one
-Sometimes we want to combine multiple commits, especially when later commits are only fixes to previous ones,
-to create a PR with set of meaningful commits. You can do it by following steps.
-- Before doing so, configure the default editor of git if you haven't done so before.
-```bash
-git config core.editor the-editor-you-like
-```
-- Assume we want to merge last 3 commits, type the following commands
-```bash
-git rebase -i HEAD~3
-```
-- It will pop up an text editor. Set the first commit as ```pick```, and change later ones to ```squash```.
-- After you saved the file, it will pop up another text editor to ask you modify the combined commit message.
-- Push the changes to your fork, you need to force push.
-```bash
-git push --force
-```
-
-### Reset to the most recent master
-You can always use git reset to reset your version to the most recent master.
-Note that all your ***local changes will get lost***.
-So only do it when you do not have local changes or when your pull request just get merged.
-```bash
-git reset --hard [hash tag of master]
-git push --force
-```
-
-### What is the consequence of force push
-The previous two tips requires force push, this is because we altered the path of the commits.
-It is fine to force push to your own fork, as long as the commits changed are only yours.
-
-## Testcases
-- All the testcases are in tests
-
-## Core Library
-- Follow Google C style for C++.
-- We use doxygen to document all the interface code.
-- You can reproduce the linter checks by typing ```make lint```
-
-## Python Package
-- Always add docstring to the new functions in numpydoc format.
-- You can reproduce the linter checks by typing ```make lint```
diff --git a/nnvm/docs/how_to/deploy.md b/nnvm/docs/how_to/deploy.md
deleted file mode 100644
index d6c011bd2584..000000000000
--- a/nnvm/docs/how_to/deploy.md
+++ /dev/null
@@ -1,119 +0,0 @@
-Deploy Compiled Modules
-=======================
-NNVM compiled modules are fully embedded in TVM runtime as long as ```GRAPH_RUNTIME``` option
-is enabled in tvm runtime. Check out the [TVM documentation](http://docs.tvmlang.org/) for
-how to deploy TVM runtime to your system.
-
-In a nutshell, we will need three items to deploy a compiled module.
-Checkout our tutorials on getting started with NNVM compiler for more details.
-
-- The graph json data which contains the execution graph.
-- The tvm module library of compiled functions.
-- The parameter blobs for stored parameters.
-
-We can then use TVM's runtime API to deploy the compiled module.
-Here is an example in python.
-
-```python
-import tvm
-
-# tvm module for compiled functions.
-loaded_lib = tvm.module.load("deploy.so")
-# json graph
-loaded_json = open(temp.relpath("deploy.json")).read()
-# parameters in binary
-loaded_params = bytearray(open(temp.relpath("deploy.params"), "rb").read())
-
-fcreate = tvm.get_global_func("tvm.graph_runtime.create")
-ctx = tvm.gpu(0)
-gmodule = fcreate(loaded_json, loaded_lib, ctx.device_type, ctx.device_id)
-set_input, get_output, run = gmodule["set_input"], gmodule["get_output"], gmodule["run"]
-set_input("x", tvm.nd.array(x_np))
-gmodule["load_params"](loaded_params)
-run()
-out = tvm.nd.empty(shape)
-get_output(0, out)
-print(out.asnumpy())
-```
-
-An example in c++.
-```cpp
-#include <dlpack/dlpack.h>
-#include <tvm/runtime/module.h>
-#include <tvm/runtime/registry.h>
-#include <tvm/runtime/packed_func.h>
-
-#include <fstream>
-#include <iterator>
-#include <algorithm>
-
-int main()
-{
-    // tvm module for compiled functions
-    tvm::runtime::Module mod_syslib = tvm::runtime::Module::LoadFromFile("deploy.so");
-
-    // json graph
-    std::ifstream json_in("deploy.json", std::ios::in);
-    std::string json_data((std::istreambuf_iterator<char>(json_in)), std::istreambuf_iterator<char>());
-    json_in.close();
-
-    // parameters in binary
-    std::ifstream params_in("deploy.params", std::ios::binary);
-    std::string params_data((std::istreambuf_iterator<char>(params_in)), std::istreambuf_iterator<char>());
-    params_in.close();
-
-    // parameters need to be TVMByteArray type to indicate the binary data
-    TVMByteArray params_arr;
-    params_arr.data = params_data.c_str();
-    params_arr.size = params_data.length();
-
-    int dtype_code = kDLFloat;
-    int dtype_bits = 32;
-    int dtype_lanes = 1;
-    int device_type = kDLCPU;
-    int device_id = 0;
-
-    // get global function module for graph runtime
-    tvm::runtime::Module mod = (*tvm::runtime::Registry::Get("tvm.graph_runtime.create"))(json_data, mod_syslib, device_type, device_id);
-
-    DLTensor* x;
-    int in_ndim = 4;
-    int64_t in_shape[4] = {1, 3, 224, 224};
-    TVMArrayAlloc(in_shape, in_ndim, dtype_code, dtype_bits, dtype_lanes, device_type, device_id, &x);
-    // load image data saved in binary
-    std::ifstream data_fin("cat.bin", std::ios::binary);
-    data_fin.read(static_cast<char*>(x->data), 3 * 224 * 224 * 4);
-
-    // get the function from the module(set input data)
-    tvm::runtime::PackedFunc set_input = mod.GetFunction("set_input");
-    set_input("data", x);
-
-    // get the function from the module(load patameters)
-    tvm::runtime::PackedFunc load_params = mod.GetFunction("load_params");
-    load_params(params_arr);
-
-    // get the function from the module(run it)
-    tvm::runtime::PackedFunc run = mod.GetFunction("run");
-    run();
-
-    DLTensor* y;
-    int out_ndim = 1;
-    int64_t out_shape[1] = {1000, };
-    TVMArrayAlloc(out_shape, out_ndim, dtype_code, dtype_bits, dtype_lanes, device_type, device_id, &y);
-
-    // get the function from the module(get output data)
-    tvm::runtime::PackedFunc get_output = mod.GetFunction("get_output");
-    get_output(0, y);
-
-    // get the maximum position in output vector
-    auto y_iter = static_cast<float*>(y->data);
-    auto max_iter = std::max_element(y_iter, y_iter + 1000);
-    auto max_index = std::distance(y_iter, max_iter);
-    std::cout << "The maximum position in output vector is: " << max_index << std::endl;
-
-    TVMArrayFree(x);
-    TVMArrayFree(y);
-
-    return 0;
-}
-```
diff --git a/nnvm/docs/how_to/install.md b/nnvm/docs/how_to/install.md
deleted file mode 100644
index 0190658e2d6f..000000000000
--- a/nnvm/docs/how_to/install.md
+++ /dev/null
@@ -1,89 +0,0 @@
-Installation Guide
-==================
-This page gives instructions on how to build and install the nnvm compiler package from
-scratch on various systems. It consists of two steps:
-
-1. First build the shared library from the C++ codes (`libnnvm_compiler.so` for linux/osx and `libnnvm_compiler.dll` for windows).
-2. Setup for the language packages (e.g. Python Package).
-
-To get started, clone nnvm repo from github. It is important to clone the submodules along, with ```--recursive``` option.
-```bash
-git clone --recursive https://github.com/dmlc/nnvm
-```
-For windows users who use github tools, you can open the git shell, and type the following command.
-```bash
-git submodule init
-git submodule update --recursive
-```
-
-NNVM compiler depend on TVM and TOPI, so make sure you install them by following [TVM document](http://docs.tvmlang.org/).
-Note that it is necessary to build TVM with LLVM support to take full benefit of NNVM compiler.
-
-## Contents
-- [Build the Shared Library](#build-the-shared-library)
-- [Python Package Installation](#python-package-installation)
-- [Solution to Installation Error](#solution-to-installation-error)
-
-## Build the Shared Library
-
-Our goal is to build the shared library:
-- On Linux/OSX the target library is `libnnvm_compiler.so`
-- On Windows the target library is `libnnvm_compiler.dll`
-
-The minimal building requirement is
-- A recent c++ compiler supporting C++ 11 (g++-4.8 or higher)
-
-You can edit `make/config.mk` to change the compile options, and then build by
-`make`. If everything goes well, we can go to the specific language installation section.
-
-### Building on Windows
-
-NNVM support build via MSVC using cmake. The minimum required VS version is **Visual Studio Community 2015 Update 3**.
-In order to generate the VS solution file using cmake, make sure you have a recent version of cmake added to your path.
-NNVM compiler depend on tvm, please follow [TVM document](http://docs.tvmlang.org/how_to/install.html#building-on-windows)
-to build the TVM windows library. You can build the TVM in the submodule folder under nnvm.
-
-After tvm is built, we can then start to build nnvm, using the following command.
-
-```bash
-mkdir build
-cd build
-cmake -G "Visual Studio 14 2015 Win64" -DCMAKE_BUILD_TYPE=Release -DCMAKE_CONFIGURATION_TYPES="Release" ..
-```
-This will generate the VS project using the MSVC 14 64 bit generator. Open the .sln file in the build directory and build with Visual Studio.
-
-## Python Package Installation
-
-The python package is located at python.
-There are several ways to install the package, in all these cases the TVM library must be present in the python env:
-
-1. Set the environment variable `PYTHONPATH` to tell python where to find
-   the library. For example, assume we cloned `nnvm` on the home directory
-   `~`. then we can added the following line in `~/.bashrc`.
-    It is ***recommended for developers*** who may change the codes.
-    The changes will be immediately reflected once you pulled the code and rebuild the project (no need to call ```setup``` again)
-
-    ```bash
-    export PYTHONPATH=/path/to/nnvm/python:${PYTHONPATH}
-    ```
-
-2. Install nnvm python bindings by `setup.py`:
-
-    ```bash
-    # install nnvm package for the current user
-    # NOTE: if you installed python via homebrew, --user is not needed during installaiton
-    #       it will be automatically installed to your user directory.
-    #       providing --user flag may trigger error during installation in such case.
-    cd python; python setup.py install --user; cd ..
-    ```
-
-## Solution to Installation Error
-
-If you encounter the problem while installation process, you can solve by updating submodules to the latest commit set.
-To update submodules to the latest commit set, type the following command.
-
-```bash
-git submodule update --init --recursive
-```
-
-*WARNING: The default commit set in submodule is the recommended setting. Using the latest commit set may lead to another compilation error or something else.*
diff --git a/nnvm/docs/index.rst b/nnvm/docs/index.rst
deleted file mode 100644
index 939c46b45bb4..000000000000
--- a/nnvm/docs/index.rst
+++ /dev/null
@@ -1,19 +0,0 @@
-NNVM Documentation
-==================
-This is a document about NNVM and NNVM compiler.
-
-Contents
---------
-
-.. toctree::
-   :maxdepth: 1
-
-   self
-   how_to/install
-   tutorials/index
-   top
-   json_spec
-   how_to/contribute
-   how_to/deploy
-   api/python/index
-   dev/index
diff --git a/nnvm/examples/README.md b/nnvm/examples/README.md
deleted file mode 100644
index 112dc92caaaf..000000000000
--- a/nnvm/examples/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-NNVM Examples
-=============
-This folder contains example snippets of running NNVM Compilation.
-
-- See also [Tutorials](../tutorials) for tutorials with detailed explainations.
diff --git a/nnvm/tests/ci_build/Dockerfile.gpu b/nnvm/tests/ci_build/Dockerfile.gpu
deleted file mode 100644
index 1206582ee088..000000000000
--- a/nnvm/tests/ci_build/Dockerfile.gpu
+++ /dev/null
@@ -1,79 +0,0 @@
-FROM nvidia/cuda:8.0-cudnn7-devel
-
-# Base scripts
-RUN apt-get update --fix-missing
-
-COPY install/ubuntu_install_core.sh /install/ubuntu_install_core.sh
-RUN bash /install/ubuntu_install_core.sh
-
-COPY install/ubuntu_install_python.sh /install/ubuntu_install_python.sh
-RUN bash /install/ubuntu_install_python.sh
-
-COPY install/ubuntu_install_llvm.sh /install/ubuntu_install_llvm.sh
-RUN bash /install/ubuntu_install_llvm.sh
-
-COPY install/ubuntu_install_opencl.sh /install/ubuntu_install_opencl.sh
-RUN bash /install/ubuntu_install_opencl.sh
-
-COPY install/ubuntu_install_python_package.sh /install/ubuntu_install_python_package.sh
-RUN bash /install/ubuntu_install_python_package.sh
-
-COPY install/ubuntu_install_sphinx.sh /install/ubuntu_install_sphinx.sh
-RUN bash /install/ubuntu_install_sphinx.sh
-
-# Fix recommonmark to latest version
-RUN git clone https://github.com/rtfd/recommonmark
-RUN cd recommonmark; python setup.py install
-
-# Enable doxygen for c++ doc build
-RUN apt-get update && apt-get install -y doxygen graphviz libprotobuf-dev protobuf-compiler
-
-
-COPY install/ubuntu_install_java.sh /install/ubuntu_install_java.sh
-RUN bash /install/ubuntu_install_java.sh
-
-COPY install/ubuntu_install_nodejs.sh /install/ubuntu_install_nodejs.sh
-RUN bash /install/ubuntu_install_nodejs.sh
-
-COPY install/ubuntu_install_rocm.sh /install/ubuntu_install_rocm.sh
-RUN bash /install/ubuntu_install_rocm.sh
-
-COPY install/ubuntu_install_opengl.sh /install/ubuntu_install_opengl.sh
-RUN bash /install/ubuntu_install_opengl.sh
-
-COPY install/ubuntu_install_vulkan.sh /install/ubuntu_install_vulkan.sh
-RUN bash /install/ubuntu_install_vulkan.sh
-
-
-# DL Frameworks
-COPY install/ubuntu_install_mxnet.sh /install/ubuntu_install_mxnet.sh
-RUN bash /install/ubuntu_install_mxnet.sh
-
-COPY install/ubuntu_install_onnx.sh /install/ubuntu_install_onnx.sh
-RUN bash /install/ubuntu_install_onnx.sh
-
-COPY install/ubuntu_install_coreml.sh /install/ubuntu_install_coreml.sh
-RUN bash /install/ubuntu_install_coreml.sh
-
-COPY install/ubuntu_install_keras.sh /install/ubuntu_install_keras.sh
-RUN bash /install/ubuntu_install_keras.sh
-
-COPY install/ubuntu_install_darknet.sh /install/ubuntu_install_darknet.sh
-RUN bash /install/ubuntu_install_darknet.sh
-
-RUN pip install Pillow
-
-# Environment variables
-ENV PATH=/usr/local/nvidia/bin:${PATH}
-ENV PATH=/usr/local/cuda/bin:${PATH}
-ENV CPLUS_INCLUDE_PATH=/usr/local/cuda/include:${CPLUS_INCLUDE_PATH}
-ENV C_INCLUDE_PATH=/usr/local/cuda/include:${C_INCLUDE_PATH}
-ENV LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/nvidia/lib64:${LIBRARY_PATH}
-ENV LD_LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/nvidia/lib64:${LD_LIBRARY_PATH}
-
-ENV LD_LIBRARY_PATH=/opt/rocm/lib:${LD_LIBRARY_PATH}
-ENV PATH=/node_modules/.bin:${PATH}
-ENV VULKAN_SDK=/usr/local/VulkanSDK/1.0.65.0/x86_64
-ENV PATH=${PATH}:${VULKAN_SDK}/bin
-ENV LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${VULKAN_SDK}/lib
-ENV VK_LAYER_PATH=${VULKAN_SDK}/etc/explicit_layer.d
diff --git a/nnvm/tests/ci_build/Dockerfile.lint b/nnvm/tests/ci_build/Dockerfile.lint
deleted file mode 100644
index 4ba4ca3be294..000000000000
--- a/nnvm/tests/ci_build/Dockerfile.lint
+++ /dev/null
@@ -1,6 +0,0 @@
-# For lint test
-FROM ubuntu:16.04
-
-RUN apt-get update && apt-get install -y python-pip sudo
-RUN apt-get install -y doxygen graphviz
-RUN pip install cpplint pylint
diff --git a/nnvm/tests/ci_build/README.md b/nnvm/tests/ci_build/README.md
deleted file mode 100644
index 68f0c2d0e96a..000000000000
--- a/nnvm/tests/ci_build/README.md
+++ /dev/null
@@ -1,36 +0,0 @@
-# CI Build Scripts
-
-This directory contains the files and setup instructions to run all tests.
-
-## Run locally
-
-To run locally, we need to first install
-[docker](https://docs.docker.com/engine/installation/) and
-[nvidia-docker](https://github.com/NVIDIA/nvidia-docker/wiki).
-
-Then we can run the tasks defined in the [Jenkinsfile](../../Jenkinsfile) by
-using (`ci_build.sh`)[./ci_build.sh]. For example
-
-- lint the python codes
-
-  ```bash
-  ./ci_build.sh lint make pylint
-  ```
-
-- build codes with CUDA supports
-
-  ```bash
-  ./ci_build.sh gpu tests/scripts/task_build.sh
-  ```
-
-- do the python unittest
-
-  ```bash
-  ./ci_build.sh gpu tests/scripts/task_python_test.sh
-  ```
-
-- build the documents. The results will be available at `docs/_build/html`
-
-  ```bash
-  tests/ci_build/ci_build.sh gpu tests/scripts/task_python_docs.sh
-  ```
diff --git a/nnvm/tests/ci_build/ci_build.sh b/nnvm/tests/ci_build/ci_build.sh
deleted file mode 100755
index db4283ca9753..000000000000
--- a/nnvm/tests/ci_build/ci_build.sh
+++ /dev/null
@@ -1,126 +0,0 @@
-#!/usr/bin/env bash
-#
-# Execute command within a docker container
-#
-# Usage: ci_build.sh <CONTAINER_TYPE> [--dockerfile <DOCKERFILE_PATH>] [-it]
-#                    <COMMAND>
-#
-# CONTAINER_TYPE: Type of the docker container used the run the build: e.g.,
-#                 (cpu | gpu)
-#
-# DOCKERFILE_PATH: (Optional) Path to the Dockerfile used for docker build.  If
-#                  this optional value is not supplied (via the --dockerfile
-#                  flag), will use Dockerfile.CONTAINER_TYPE in default
-#
-# COMMAND: Command to be executed in the docker container
-#
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-
-# Get the command line arguments.
-CONTAINER_TYPE=$( echo "$1" | tr '[:upper:]' '[:lower:]' )
-shift 1
-
-# Dockerfile to be used in docker build
-DOCKERFILE_PATH="${SCRIPT_DIR}/Dockerfile.${CONTAINER_TYPE}"
-DOCKER_CONTEXT_PATH="${SCRIPT_DIR}"
-
-if [[ "$1" == "--dockerfile" ]]; then
-    DOCKERFILE_PATH="$2"
-    DOCKER_CONTEXT_PATH=$(dirname "${DOCKERFILE_PATH}")
-    echo "Using custom Dockerfile path: ${DOCKERFILE_PATH}"
-    echo "Using custom docker build context path: ${DOCKER_CONTEXT_PATH}"
-    shift 2
-fi
-
-if [[ "$1" == "-it" ]]; then
-    CI_DOCKER_EXTRA_PARAMS+=('-it')
-    shift 1
-fi
-
-if [[ ! -f "${DOCKERFILE_PATH}" ]]; then
-    echo "Invalid Dockerfile path: \"${DOCKERFILE_PATH}\""
-    exit 1
-fi
-
-COMMAND=("$@")
-
-# Validate command line arguments.
-if [ "$#" -lt 1 ] || [ ! -e "${SCRIPT_DIR}/Dockerfile.${CONTAINER_TYPE}" ]; then
-    supported_container_types=$( ls -1 ${SCRIPT_DIR}/Dockerfile.* | \
-        sed -n 's/.*Dockerfile\.\([^\/]*\)/\1/p' | tr '\n' ' ' )
-      echo "Usage: $(basename $0) CONTAINER_TYPE COMMAND"
-      echo "       CONTAINER_TYPE can be one of [${supported_container_types}]"
-      echo "       COMMAND is a command (with arguments) to run inside"
-      echo "               the container."
-      exit 1
-fi
-
-# Use nvidia-docker if the container is GPU.
-if [[ "${CONTAINER_TYPE}" == *"gpu"* ]]; then
-    DOCKER_BINARY="nvidia-docker"
-else
-    DOCKER_BINARY="docker"
-fi
-
-# Helper function to traverse directories up until given file is found.
-function upsearch () {
-    test / == "$PWD" && return || \
-        test -e "$1" && echo "$PWD" && return || \
-        cd .. && upsearch "$1"
-}
-
-# Set up WORKSPACE and BUILD_TAG. Jenkins will set them for you or we pick
-# reasonable defaults if you run it outside of Jenkins.
-WORKSPACE="${WORKSPACE:-${SCRIPT_DIR}/../../}"
-BUILD_TAG="${BUILD_TAG:-nnvm-ci}"
-
-# Determine the docker image name
-DOCKER_IMG_NAME="${BUILD_TAG}.${CONTAINER_TYPE}"
-
-# Under Jenkins matrix build, the build tag may contain characters such as
-# commas (,) and equal signs (=), which are not valid inside docker image names.
-DOCKER_IMG_NAME=$(echo "${DOCKER_IMG_NAME}" | sed -e 's/=/_/g' -e 's/,/-/g')
-
-# Convert to all lower-case, as per requirement of Docker image names
-DOCKER_IMG_NAME=$(echo "${DOCKER_IMG_NAME}" | tr '[:upper:]' '[:lower:]')
-
-# Print arguments.
-echo "WORKSPACE: ${WORKSPACE}"
-echo "CI_DOCKER_EXTRA_PARAMS: ${CI_DOCKER_EXTRA_PARAMS[@]}"
-echo "COMMAND: ${COMMAND[@]}"
-echo "CONTAINER_TYPE: ${CONTAINER_TYPE}"
-echo "BUILD_TAG: ${BUILD_TAG}"
-echo "DOCKER CONTAINER NAME: ${DOCKER_IMG_NAME}"
-echo ""
-
-
-# Build the docker container.
-echo "Building container (${DOCKER_IMG_NAME})..."
-docker build -t ${DOCKER_IMG_NAME} \
-    -f "${DOCKERFILE_PATH}" "${DOCKER_CONTEXT_PATH}"
-
-# Check docker build status
-if [[ $? != "0" ]]; then
-    echo "ERROR: docker build failed."
-    exit 1
-fi
-
-# Run the command inside the container.
-echo "Running '${COMMAND[@]}' inside ${DOCKER_IMG_NAME}..."
-
-# By default we cleanup - remove the container once it finish running (--rm)
-# and share the PID namespace (--pid=host) so the process inside does not have
-# pid 1 and SIGKILL is propagated to the process inside (jenkins can kill it).
-echo ${DOCKER_BINARY}
-${DOCKER_BINARY} run --rm --pid=host \
-    -v ${WORKSPACE}:/workspace \
-    -w /workspace \
-    -e "CI_BUILD_HOME=/workspace" \
-    -e "CI_BUILD_USER=$(id -u -n)" \
-    -e "CI_BUILD_UID=$(id -u)" \
-    -e "CI_BUILD_GROUP=$(id -g -n)" \
-    -e "CI_BUILD_GID=$(id -g)" \
-    ${CI_DOCKER_EXTRA_PARAMS[@]} \
-    ${DOCKER_IMG_NAME} \
-    bash tests/ci_build/with_the_same_user \
-    ${COMMAND[@]}
diff --git a/nnvm/tests/ci_build/install/ubuntu_install_core.sh b/nnvm/tests/ci_build/install/ubuntu_install_core.sh
deleted file mode 100644
index 9823ae0788ac..000000000000
--- a/nnvm/tests/ci_build/install/ubuntu_install_core.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-# install libraries for building c++ core on ubuntu
-apt-get install -y --no-install-recommends --force-yes \
-        git make libgtest-dev cmake wget unzip libtinfo-dev libz-dev\
-        libcurl4-openssl-dev libopenblas-dev g++ sudo
-
-cd /usr/src/gtest && cmake CMakeLists.txt && make && cp *.a /usr/lib
diff --git a/nnvm/tests/ci_build/install/ubuntu_install_coreml.sh b/nnvm/tests/ci_build/install/ubuntu_install_coreml.sh
deleted file mode 100644
index 3cfd6c293590..000000000000
--- a/nnvm/tests/ci_build/install/ubuntu_install_coreml.sh
+++ /dev/null
@@ -1 +0,0 @@
-pip2 install coremltools
diff --git a/nnvm/tests/ci_build/install/ubuntu_install_darknet.sh b/nnvm/tests/ci_build/install/ubuntu_install_darknet.sh
deleted file mode 100644
index f5e0c2791d80..000000000000
--- a/nnvm/tests/ci_build/install/ubuntu_install_darknet.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#install the necessary dependancies, cffi, opencv
-wget 'https://github.com/siju-samuel/darknet/blob/master/lib/libdarknet.so?raw=true' -O libdarknet.so
-pip2 install opencv-python cffi
-pip3 install opencv-python cffi
diff --git a/nnvm/tests/ci_build/install/ubuntu_install_keras.sh b/nnvm/tests/ci_build/install/ubuntu_install_keras.sh
deleted file mode 100644
index 9730d83bf469..000000000000
--- a/nnvm/tests/ci_build/install/ubuntu_install_keras.sh
+++ /dev/null
@@ -1 +0,0 @@
-pip2 install keras tensorflow h5py
diff --git a/nnvm/tests/ci_build/install/ubuntu_install_llvm.sh b/nnvm/tests/ci_build/install/ubuntu_install_llvm.sh
deleted file mode 100644
index e5b28b911f61..000000000000
--- a/nnvm/tests/ci_build/install/ubuntu_install_llvm.sh
+++ /dev/null
@@ -1,17 +0,0 @@
-echo deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-4.0 main\
-     >> /etc/apt/sources.list.d/llvm.list
-echo deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial-4.0 main\
-     >> /etc/apt/sources.list.d/llvm.list
-
-echo deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-5.0 main\
-     >> /etc/apt/sources.list.d/llvm.list
-echo deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial-5.0 main\
-     >> /etc/apt/sources.list.d/llvm.list
-
-echo deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial main\
-     >> /etc/apt/sources.list.d/llvm.list
-echo deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial main\
-     >> /etc/apt/sources.list.d/llvm.list
-
-wget -O - http://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
-apt-get update && apt-get install -y --force-yes llvm-4.0 llvm-5.0 llvm-6.0
diff --git a/nnvm/tests/ci_build/install/ubuntu_install_mxnet.sh b/nnvm/tests/ci_build/install/ubuntu_install_mxnet.sh
deleted file mode 100644
index ebf641e2a910..000000000000
--- a/nnvm/tests/ci_build/install/ubuntu_install_mxnet.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-pip2 install mxnet
-pip3 install mxnet
diff --git a/nnvm/tests/ci_build/install/ubuntu_install_onnx.sh b/nnvm/tests/ci_build/install/ubuntu_install_onnx.sh
deleted file mode 100644
index 138e70d71200..000000000000
--- a/nnvm/tests/ci_build/install/ubuntu_install_onnx.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-pip2 install onnx>=1.1.0
-pip3 install onnx>=1.1.0
-
-pip2 install http://download.pytorch.org/whl/cu75/torch-0.2.0.post3-cp27-cp27mu-manylinux1_x86_64.whl
-pip2 install torchvision
-pip3 install http://download.pytorch.org/whl/cu75/torch-0.2.0.post3-cp35-cp35m-manylinux1_x86_64.whl
-pip3 install torchvision
diff --git a/nnvm/tests/ci_build/install/ubuntu_install_opencl.sh b/nnvm/tests/ci_build/install/ubuntu_install_opencl.sh
deleted file mode 100644
index 636236539a98..000000000000
--- a/nnvm/tests/ci_build/install/ubuntu_install_opencl.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-# Install OpenCL runtime in nvidia docker.
-apt-get install -y --no-install-recommends --force-yes \
-        ocl-icd-libopencl1 \
-        clinfo && \
-        rm -rf /var/lib/apt/lists/*
-
-mkdir -p /etc/OpenCL/vendors && \
-    echo "libnvidia-opencl.so.1" > /etc/OpenCL/vendors/nvidia.icd
-
-echo "/usr/local/nvidia/lib" >> /etc/ld.so.conf.d/nvidia.conf && \
-    echo "/usr/local/nvidia/lib64" >> /etc/ld.so.conf.d/nvidia.conf
diff --git a/nnvm/tests/ci_build/install/ubuntu_install_python.sh b/nnvm/tests/ci_build/install/ubuntu_install_python.sh
deleted file mode 100644
index c6f6f75c564e..000000000000
--- a/nnvm/tests/ci_build/install/ubuntu_install_python.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-# install python and pip, don't modify this, modify install_python_package.sh
-apt-get update && apt-get install -y python-pip python-dev python3-dev
-
-# the version of the pip shipped with ubuntu may be too lower, install a recent version here
-cd /tmp && wget https://bootstrap.pypa.io/get-pip.py && python3 get-pip.py && python2 get-pip.py
diff --git a/nnvm/tests/ci_build/install/ubuntu_install_python_package.sh b/nnvm/tests/ci_build/install/ubuntu_install_python_package.sh
deleted file mode 100644
index fbed2e1904cd..000000000000
--- a/nnvm/tests/ci_build/install/ubuntu_install_python_package.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-# install libraries for python package on ubuntu
-pip2 install nose pylint numpy nose-timer cython decorator scipy tornado
-pip3 install nose pylint numpy nose-timer cython decorator scipy tornado
diff --git a/nnvm/tests/ci_build/install/ubuntu_install_sphinx.sh b/nnvm/tests/ci_build/install/ubuntu_install_sphinx.sh
deleted file mode 100644
index 767643f10488..000000000000
--- a/nnvm/tests/ci_build/install/ubuntu_install_sphinx.sh
+++ /dev/null
@@ -1 +0,0 @@
-pip install sphinx==1.6.2 sphinx-gallery sphinx_rtd_theme matplotlib Image commonmark>=0.7.3 docutils>=0.11
diff --git a/nnvm/tests/ci_build/with_the_same_user b/nnvm/tests/ci_build/with_the_same_user
deleted file mode 100644
index 1e6ab883694b..000000000000
--- a/nnvm/tests/ci_build/with_the_same_user
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env bash
-
-# This script is a wrapper creating the same user inside container as the one
-# running the ci_build.sh outside the container. It also set the home directory
-# for the user inside container to match the same absolute path as the workspace
-# outside of container.  Do not run this manually. It does not make sense. It is
-# intended to be called by ci_build.sh only.
-
-set -e
-
-COMMAND=("$@")
-
-if ! touch /this_is_writable_file_system; then
-  echo "You can't write to your filesystem!"
-  echo "If you are in Docker you should check you do not have too many images" \
-      "with too many files in them. Docker has some issue with it."
-  exit 1
-else
-  rm /this_is_writable_file_system
-fi
-
-getent group "${CI_BUILD_GID}" || addgroup --gid "${CI_BUILD_GID}" "${CI_BUILD_GROUP}"
-getent passwd "${CI_BUILD_UID}" || adduser --gid "${CI_BUILD_GID}" --uid "${CI_BUILD_UID}" \
-    --gecos "${CI_BUILD_USER} (generated by with_the_same_user script)" \
-    --disabled-password --home "${CI_BUILD_HOME}" --quiet "${CI_BUILD_USER}"
-usermod -a -G sudo "${CI_BUILD_USER}"
-echo "${CI_BUILD_USER} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/90-nopasswd-sudo
-
-HOME=${CI_BUILD_HOME}\
-    sudo -u "#${CI_BUILD_UID}" --preserve-env\
-    PATH=${PATH}\
-    LD_LIBRARY_PATH=${LD_LIBRARY_PATH}\
-    HOME=${CI_BUILD_HOME}\
-    ${COMMAND[@]}
diff --git a/nnvm/tests/python/frontend/onnx/test_forward.py b/nnvm/tests/python/frontend/onnx/test_forward.py
index 3a7076d17b68..9aef8b2cbe9e 100644
--- a/nnvm/tests/python/frontend/onnx/test_forward.py
+++ b/nnvm/tests/python/frontend/onnx/test_forward.py
@@ -16,11 +16,12 @@ def get_caffe2_output(model, x, dtype='float32'):
 
     def get_tvm_output(model, x, target, ctx, dtype='float32'):
         new_sym, params = nnvm.frontend.from_onnx(model)
-        shape_dict = {'input_0': x.shape}
+        input_name = model.graph.input[0].name
+        shape_dict = {input_name: x.shape}
         graph, lib, params = nnvm.compiler.build(new_sym, target, shape_dict, params=params)
         m = graph_runtime.create(graph, lib, ctx)
         # set inputs
-        m.set_input('input_0', tvm.nd.array(x.astype(dtype)))
+        m.set_input(input_name, tvm.nd.array(x.astype(dtype)))
         m.set_input(**params)
         m.run()
         # get outputs
diff --git a/nnvm/tests/python/frontend/onnx/test_graph.py b/nnvm/tests/python/frontend/onnx/test_graph.py
index 89f13b447991..7fa705ef4c65 100644
--- a/nnvm/tests/python/frontend/onnx/test_graph.py
+++ b/nnvm/tests/python/frontend/onnx/test_graph.py
@@ -9,7 +9,8 @@ def compare_graph(onnx_file, nnvm_sym, ishape):
     onnx_sym, params = nnvm.frontend.from_onnx(onnx_model)
     g1 = nnvm.graph.create(onnx_sym)
     g2 = nnvm.graph.create(nnvm_sym)
-    ishapes = {'input_0': ishape}
+    input_name = onnx_model.graph.input[0].name
+    ishapes = {input_name: ishape}
     graph_attr.set_shape_inputs(g1, ishapes)
     graph_attr.set_shape_inputs(g2, ishapes)
     g1 = g1.apply("InferShape").apply("SimplifyInference")
diff --git a/nnvm/tests/scripts/task_frontend_test.sh b/nnvm/tests/scripts/task_frontend_test.sh
deleted file mode 100755
index 5881d1846a0f..000000000000
--- a/nnvm/tests/scripts/task_frontend_test.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/bash
-
-export PYTHONPATH=python:tvm/python:tvm/topi/python
-
-echo "Running ONNX frontend test..."
-python -m nose -v tests/python/frontend/onnx || exit -1
-
-echo "Running MXNet frontend test..."
-python -m nose -v tests/python/frontend/mxnet || exit -1
-
-echo "Running Keras frontend test..."
-python -m nose -v tests/python/frontend/keras || exit -1
diff --git a/nnvm/tests/scripts/task_lint.sh b/nnvm/tests/scripts/task_lint.sh
deleted file mode 100755
index a6774a189f35..000000000000
--- a/nnvm/tests/scripts/task_lint.sh
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/bash
-echo "Check codestyle of c++ code..."
-make cpplint || exit -1
-echo "Check codestyle of python code..."
-make pylint || exit -1
-echo "Check documentations of c++ code..."
-make doc 2>log.txt
-(cat log.txt| grep -v ENABLE_PREPROCESSING |grep -v "unsupported tag") > logclean.txt
-echo "---------Error Log----------"
-cat logclean.txt
-echo "----------------------------"
-(cat logclean.txt|grep warning) && exit -1
-(cat logclean.txt|grep error) && exit -1
-rm logclean.txt
-rm log.txt
diff --git a/nnvm/tests/scripts/task_python_docs.sh b/nnvm/tests/scripts/task_python_docs.sh
deleted file mode 100755
index 94fa73e5e2de..000000000000
--- a/nnvm/tests/scripts/task_python_docs.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/bash
-mkdir -p docs/_build/html
-# C++ doc
-make doc
-
-rm -rf python/nnvm/*.pyc python/nnvm/*/*.pyc
-
-cd docs
-PYTHONPATH=../python:../tvm/python:../tvm/topi/python make html || exit -1
-cd _build/html
-tar czf docs.tgz *
-mv docs.tgz ../../../
diff --git a/nnvm/tests/travis/run_test.sh b/nnvm/tests/travis/run_test.sh
deleted file mode 100755
index ecce1f4d5a27..000000000000
--- a/nnvm/tests/travis/run_test.sh
+++ /dev/null
@@ -1,58 +0,0 @@
-#!/bin/bash
-
-
-if [ ${TASK} == "lint" ]; then
-    make lint || exit -1
-    echo "Check documentations of c++ code..."
-    make doc 2>log.txt
-    (cat log.txt| grep -v ENABLE_PREPROCESSING |grep -v "unsupported tag") > logclean.txt
-    echo "---------Error Log----------"
-    cat logclean.txt
-    echo "----------------------------"
-    (cat logclean.txt|grep warning) && exit -1
-    (cat logclean.txt|grep error) && exit -1
-    exit 0
-fi
-
-
-if [ ! ${TRAVIS_OS_NAME} == "osx" ]; then
-    # use g++-4.8 for linux
-    if [ ${CXX} == "g++" ]; then
-        export CXX=g++-4.8
-    fi
-fi
-
-if [ ${TASK} == "cpp_test" ]; then
-    make -f dmlc-core/scripts/packages.mk gtest
-    echo "GTEST_PATH="${CACHE_PREFIX} >> config.mk
-    make test || exit -1
-    for test in tests/cpp/*_test; do
-        ./$test || exit -1
-    done
-    exit 0
-fi
-
-# run two test one for cython, one for ctypes
-if [ ${TASK} == "python_test" ]; then
-    make clean
-    make -j all || exit -1
-    if [ ${TRAVIS_OS_NAME} == "osx" ]; then
-        python -m nose tests/python/unittest/ || exit -1
-        python3 -m nose tests/python/unittest/ || exit -1
-    else
-        nosetests tests/python/unittest/ || exit -1
-        nosetests3 tests/python/unittest/ || exit -1
-    fi
-
-    make cython || exit -1
-    make cython3 || exit -1
-
-    if [ ${TRAVIS_OS_NAME} == "osx" ]; then
-        python -m nose tests/python/unittest/ || exit -1
-        python3 -m nose tests/python/unittest/ || exit -1
-    else
-        nosetests tests/python/unittest/ || exit -1
-        nosetests3 tests/python/unittest/ || exit -1
-    fi
-    exit 0
-fi
diff --git a/nnvm/tests/travis/setup.sh b/nnvm/tests/travis/setup.sh
deleted file mode 100755
index 88010a0b3d7e..000000000000
--- a/nnvm/tests/travis/setup.sh
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/bash
-
-
-if [ ${TRAVIS_OS_NAME} == "osx" ]; then
-    brew update
-    brew install python3
-    if [ ${TASK} == "python_test" ]; then
-        python -m pip install --user nose numpy cython
-        python3 -m pip install --user nose numpy cython
-    fi
-fi
-
-if [ ${TASK} == "lint" ]; then
-    pip install --user cpplint 'pylint==1.4.4' 'astroid==1.3.6'
-fi
diff --git a/nnvm/tests/travis/travis_after_failure.sh b/nnvm/tests/travis/travis_after_failure.sh
deleted file mode 100755
index a9bf588e2f88..000000000000
--- a/nnvm/tests/travis/travis_after_failure.sh
+++ /dev/null
@@ -1 +0,0 @@
-#!/bin/bash
diff --git a/python/tvm/_ffi/libinfo.py b/python/tvm/_ffi/libinfo.py
index 25d111d1f74a..b449e712c32c 100644
--- a/python/tvm/_ffi/libinfo.py
+++ b/python/tvm/_ffi/libinfo.py
@@ -100,4 +100,5 @@ def find_lib_path(name=None, search_path=None, optional=False):
 
 
 # current version
-__version__ = "0.3.0"
+# We use the version of the incoming release for code that is under development
+__version__ = "0.4.0"
diff --git a/tests/ci_build/Dockerfile.gpu b/tests/ci_build/Dockerfile.gpu
index 2ee5ed04e91f..43b3ca7b394a 100644
--- a/tests/ci_build/Dockerfile.gpu
+++ b/tests/ci_build/Dockerfile.gpu
@@ -28,13 +28,26 @@ RUN cd recommonmark; python setup.py install
 # Enable doxygen for c++ doc build
 RUN apt-get update && apt-get install -y doxygen graphviz libprotobuf-dev protobuf-compiler
 
+COPY install/ubuntu_install_java.sh /install/ubuntu_install_java.sh
+RUN bash /install/ubuntu_install_java.sh
+
+COPY install/ubuntu_install_nodejs.sh /install/ubuntu_install_nodejs.sh
+RUN bash /install/ubuntu_install_nodejs.sh
+
+COPY install/ubuntu_install_rocm.sh /install/ubuntu_install_rocm.sh
+RUN bash /install/ubuntu_install_rocm.sh
+
+COPY install/ubuntu_install_opengl.sh /install/ubuntu_install_opengl.sh
+RUN bash /install/ubuntu_install_opengl.sh
+
+COPY install/ubuntu_install_vulkan.sh /install/ubuntu_install_vulkan.sh
+RUN bash /install/ubuntu_install_vulkan.sh
+
+
 # DL Frameworks
 COPY install/ubuntu_install_mxnet.sh /install/ubuntu_install_mxnet.sh
 RUN bash /install/ubuntu_install_mxnet.sh
 
-COPY install/ubuntu_install_onnx.sh /install/ubuntu_install_onnx.sh
-RUN bash /install/ubuntu_install_onnx.sh
-
 COPY install/ubuntu_install_coreml.sh /install/ubuntu_install_coreml.sh
 RUN bash /install/ubuntu_install_coreml.sh
 
@@ -44,6 +57,9 @@ RUN bash /install/ubuntu_install_keras.sh
 COPY install/ubuntu_install_darknet.sh /install/ubuntu_install_darknet.sh
 RUN bash /install/ubuntu_install_darknet.sh
 
+COPY install/ubuntu_install_onnx.sh /install/ubuntu_install_onnx.sh
+RUN bash /install/ubuntu_install_onnx.sh
+
 RUN pip install Pillow
 
 # Environment variables
@@ -53,3 +69,10 @@ ENV CPLUS_INCLUDE_PATH=/usr/local/cuda/include:${CPLUS_INCLUDE_PATH}
 ENV C_INCLUDE_PATH=/usr/local/cuda/include:${C_INCLUDE_PATH}
 ENV LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/nvidia/lib64:${LIBRARY_PATH}
 ENV LD_LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/nvidia/lib64:${LD_LIBRARY_PATH}
+
+ENV LD_LIBRARY_PATH=/opt/rocm/lib:${LD_LIBRARY_PATH}
+ENV PATH=/node_modules/.bin:${PATH}
+ENV VULKAN_SDK=/usr/local/VulkanSDK/1.0.65.0/x86_64
+ENV PATH=${PATH}:${VULKAN_SDK}/bin
+ENV LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${VULKAN_SDK}/lib
+ENV VK_LAYER_PATH=${VULKAN_SDK}/etc/explicit_layer.d
diff --git a/tests/ci_build/install/ubuntu_install_onnx.sh b/tests/ci_build/install/ubuntu_install_onnx.sh
index 138e70d71200..517ea77ab81e 100644
--- a/tests/ci_build/install/ubuntu_install_onnx.sh
+++ b/tests/ci_build/install/ubuntu_install_onnx.sh
@@ -1,3 +1,4 @@
+# fix to certain version for now
 pip2 install onnx>=1.1.0
 pip3 install onnx>=1.1.0
 
diff --git a/tests/scripts/task_python_nnvm.sh b/tests/scripts/task_python_nnvm.sh
index 5bccc27f59ba..4d44ac0e14b3 100755
--- a/tests/scripts/task_python_nnvm.sh
+++ b/tests/scripts/task_python_nnvm.sh
@@ -11,10 +11,10 @@ python -m nose -v nnvm/tests/python/compiler || exit -1
 python3 -m nose -v nnvm/tests/python/compiler || exit -1
 
 echo "Running ONNX frontend test..."
-python -m nose -v tests/python/frontend/onnx || exit -1
+python -m nose -v nnvm/tests/python/frontend/onnx || exit -1
 
 echo "Running MXNet frontend test..."
-python -m nose -v tests/python/frontend/mxnet || exit -1
+python -m nose -v nnvm/tests/python/frontend/mxnet || exit -1
 
 echo "Running Keras frontend test..."
-python -m nose -v tests/python/frontend/keras || exit -1
+python -m nose -v nnvm/tests/python/frontend/keras || exit -1
diff --git a/tutorials/deployment/cross_compilation_and_rpc.py b/tutorials/cross_compilation_and_rpc.py
similarity index 99%
rename from tutorials/deployment/cross_compilation_and_rpc.py
rename to tutorials/cross_compilation_and_rpc.py
index f06bbfca6407..3658e68fae0a 100644
--- a/tutorials/deployment/cross_compilation_and_rpc.py
+++ b/tutorials/cross_compilation_and_rpc.py
@@ -238,8 +238,8 @@
 #    The target_host should be 'llvm -target=aarch64-linux-gnu'.
 #    But here we set 'llvm' to enable this tutorial to run locally.
 #
-#    Also we need to build the runtime with the flag `USE_OPENCL=1`.
-# build kernel (different from cpu, we need bind axis for OpenCL)
+#    Also we need to build the runtime with the flag `USE_OPENCL=1` to
+#    build the kernel (different from cpu, we need bind axis for OpenCL)
 #
 # The following functions shows how we can deploy CL
 def deploy_cl():
diff --git a/tutorials/deployment/README.txt b/tutorials/deployment/README.txt
deleted file mode 100644
index 16db717c5eef..000000000000
--- a/tutorials/deployment/README.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-Run and Deploy
---------------
diff --git a/tutorials/get_started.py b/tutorials/get_started.py
index 2c6165940d05..de94827ab1e9 100644
--- a/tutorials/get_started.py
+++ b/tutorials/get_started.py
@@ -110,7 +110,7 @@
 # function(including the inputs and outputs) as well as target language
 # we want to compile to.
 #
-# The result of compilation fadd is a GPU device function(if GPU is involved) 
+# The result of compilation fadd is a GPU device function(if GPU is involved)
 # that can as well as a host wrapper that calls into the GPU function.
 # fadd is the generated host wrapper function, it contains reference
 # to the generated device function internally.
diff --git a/tutorials/language/README.txt b/tutorials/language/README.txt
index c15ab3f334ff..6da8e3c57c1f 100644
--- a/tutorials/language/README.txt
+++ b/tutorials/language/README.txt
@@ -1,2 +1,2 @@
-Lanuage and Schedules
----------------------
+Tensor Expression and Schedules
+-------------------------------
diff --git a/tutorials/nnvm/deploy_model_on_mali_gpu.py b/tutorials/nnvm/deploy_model_on_mali_gpu.py
index 53016607112d..3c0152b8c199 100644
--- a/tutorials/nnvm/deploy_model_on_mali_gpu.py
+++ b/tutorials/nnvm/deploy_model_on_mali_gpu.py
@@ -101,7 +101,7 @@
 if not use_mali:
     # run server locally
     host = 'localhost'
-    port = 9092
+    port = 9095
     server = rpc.Server(host=host, port=port, use_popen=True)
 
 ######################################################################
@@ -169,7 +169,7 @@ def transform_image(image):
 # Compile The Graph
 # -----------------
 # To compile the graph, we call the :any:`nnvm.compiler.build` function
-# with the graph configuration and parameters. As we use OpenCL for 
+# with the graph configuration and parameters. As we use OpenCL for
 # GPU computing, the tvm will generate both OpenCL kernel code and ARM
 # CPU host code. The CPU host code is used for calling OpenCL kernels.
 # In order to generate correct CPU code, we need to specify the target
@@ -182,12 +182,14 @@ def transform_image(image):
 
 if use_mali:
     target_host = "llvm -target=aarch64-linux-gnu -mattr=+neon"
+    target = tvm.target.mali()
 else:
     target_host = "llvm"
+    target = tvm.target.cuda()
 
 # set target as  `tvm.target.mali` instead of 'opencl' to enable
 # target-specified optimization
-graph, lib, params = nnvm.compiler.build(net, target=tvm.target.mali(),
+graph, lib, params = nnvm.compiler.build(net, target=target,
         shape={"data": data_shape}, params=params, target_host=target_host)
 
 # After `nnvm.compiler.build`, you will get three return values: graph,
@@ -212,7 +214,7 @@ def transform_image(image):
 remote.upload(lib_fname)
 rlib = remote.load_module('net.tar')
 
-ctx = remote.cl(0)
+ctx = remote.cl(0) if use_mali else remote.gpu(0)
 # upload the parameter
 rparams = {k: tvm.nd.array(v, ctx) for k, v in params.items()}
 
diff --git a/tutorials/nnvm/deploy_model_on_rasp.py b/tutorials/nnvm/deploy_model_on_rasp.py
index 885d928c9393..b14c8b052e65 100644
--- a/tutorials/nnvm/deploy_model_on_rasp.py
+++ b/tutorials/nnvm/deploy_model_on_rasp.py
@@ -80,7 +80,8 @@
 #
 #      Loading runtime library /home/YOURNAME/code/tvm/lib/libtvm_runtime.so... exec only
 #      INFO:root:RPCServer: bind to 0.0.0.0:9090
-#
+
+
 ######################################################################
 # For demonstration, we simply start an RPC server on the same machine,
 # if :code:`use_rasp` is False. If you have set up the remote
diff --git a/tutorials/nnvm/from_darknet.py b/tutorials/nnvm/from_darknet.py
index 7a233f56b718..9613f023c1e9 100644
--- a/tutorials/nnvm/from_darknet.py
+++ b/tutorials/nnvm/from_darknet.py
@@ -1,23 +1,18 @@
 """
-Tutorial for running Yolo-V2 in Darknet Models
-=====================
+Compile YOLO-V2 in DarkNet Models
+=================================
 **Author**: `Siju Samuel <https://siju-samuel.github.io/>`_
 
 This article is an introductory tutorial to deploy darknet models with NNVM.
-
-All the required models and libraries will be downloaded from the internet
-
-by the script.
-
+All the required models and libraries will be downloaded from the internet by the script.
 This script runs the YOLO-V2 Model with the bounding boxes
-
 Darknet parsing have dependancy with CFFI and CV2 library
-
 Please install CFFI and CV2 before executing this script
 
-pip install cffi
+.. code-block:: bash
 
-pip install opencv-python
+  pip install cffi
+  pip install opencv-python
 """
 from ctypes import *
 import math
@@ -40,12 +35,11 @@
 ######################################################################
 # Set the parameters here.
 # Supported models alexnet, resnet50, resnet152, extraction, yolo
-######################################################################
+#
 model_name = 'yolo'
 test_image = 'dog.jpg'
 target = 'llvm'
 ctx = tvm.cpu(0)
-######################################################################
 
 def dlProgress(count, block_size, total_size):
     """Show the download progress."""
@@ -105,8 +99,8 @@ def download(url, path, overwrite=False, sizecompare=False):
 
 ######################################################################
 # Prepare cfg and weights file
+# ----------------------------
 # Pretrained model available https://pjreddie.com/darknet/imagenet/
-# --------------------------------------------------------------------
 # Download cfg and weights file first time.
 
 cfg_name = model_name + '.cfg'
@@ -142,7 +136,7 @@ def download(url, path, overwrite=False, sizecompare=False):
 
 ######################################################################
 # Compile the model on NNVM
-# --------------------------------------------------------------------
+# -------------------------
 # compile the model
 data = np.empty([batch_size, net.c ,net.h, net.w], dtype);
 shape = {'data': data.shape}
@@ -151,8 +145,8 @@ def download(url, path, overwrite=False, sizecompare=False):
     graph, lib, params = nnvm.compiler.build(sym, target, shape, dtype, params)
 
 #####################################################################
-# Save the json
-# --------------------------------------------------------------------
+# Save the JSON
+# -------------
 def save_lib():
     #Save the graph, params and .so to the current directory
     print("Saving the compiled output...")
@@ -178,8 +172,8 @@ def save_lib():
 data = nnvm.testing.darknet.load_image(test_image, net.w, net.h)
 
 ######################################################################
-# Execute on TVM
-# --------------------------------------------------------------------
+# Execute on TVM Runtime
+# ----------------------
 # The process is no different from other examples.
 from tvm.contrib import graph_runtime
 
diff --git a/tutorials/nnvm/from_mxnet_to_webgl.py b/tutorials/nnvm/from_mxnet_to_webgl.py
index 8190b6a7e550..7ff553bcb368 100644
--- a/tutorials/nnvm/from_mxnet_to_webgl.py
+++ b/tutorials/nnvm/from_mxnet_to_webgl.py
@@ -1,14 +1,11 @@
 """
-Quick Start - End-to-End Tutorial for NNVM/TVM Pipeline for OpenGL and WebGL
-============================================================================
+Deploy Deep Learning Models to OpenGL and WebGL
+===============================================
 **Author**: `Zhixun Tan <https://github.com/phisiart>`_
 
 This example shows how to build a neural network with NNVM python frontend and
-generate runtime library for WebGL running in a browser with TVM. (Thanks to
-Tianqi's `tutorial for cuda <http://nnvm.tvmlang.org/tutorials/get_started.html>`_ and
-Ziheng's `tutorial for Raspberry Pi <http://nnvm.tvmlang.org/tutorials/deploy_model_on_rasp.html>`_)
-To run this notebook, you need to install tvm and nnvm following
-`these instructions <https://github.com/dmlc/nnvm/blob/master/docs/how_to/install.md>`_.
+generate runtime library for WebGL running in a browser with TVM.
+To run this notebook, you need to install tvm and nnvm.
 Notice that you need to build tvm with OpenGL.
 """
 
@@ -50,13 +47,13 @@
 run_deploy_rpc = False
 
 # To run the WebGL deploy demo, set this flag to True.
-run_deploy_web = True
+run_deploy_web = False
 
 ######################################################################
 # Download a Pre-trained Resnet18 Model
 # -------------------------------------
 # Here we define 2 functions:
-# 
+#
 # - A function that downloads a pre-trained resnet18 model from Gluon Model Zoo.
 #   The model that we download is in MXNet format, we then transform it into an
 #   NNVM computation graph.
@@ -75,7 +72,7 @@ def load_mxnet_resnet():
 
     params : dict[str -> NDArray]
         The pretrained model parameters.
-    
+
     data_shape: tuple
         The shape of the input tensor (an image).
 
@@ -116,11 +113,11 @@ def download_synset():
           "596b27d23537e5a1b5751d2b0481ef172f58b539/" + \
           "imagenet1000_clsid_to_human.txt"
     file_name = "synset.txt"
-    
+
     gluon.utils.download(url, file_name)
     with open(file_name) as f:
         synset = eval(f.read())
-    
+
     print("- Synset downloaded!")
     return synset
 
@@ -432,7 +429,7 @@ def deploy_web():
 
     from tvm.contrib import emscripten
 
-    curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
+    curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(os.getcwd())))
     working_dir = os.getcwd()
     output_dir = os.path.join(working_dir, "resnet")
     if not os.path.exists(output_dir):
@@ -471,7 +468,7 @@ def deploy_web():
                     os.path.join(output_dir, "tvm_runtime.js"))
     shutil.copyfile(os.path.join(curr_path, "web/resnet.html"),
                     os.path.join(output_dir, "resnet.html"))
-    
+
     # Now we want to save some extra files so that we can execute the model from
     # JavaScript.
     # - data shape
diff --git a/tutorials/nnvm/from_onnx.py b/tutorials/nnvm/from_onnx.py
index c5680f4ae816..8fb5a1048569 100644
--- a/tutorials/nnvm/from_onnx.py
+++ b/tutorials/nnvm/from_onnx.py
@@ -66,7 +66,9 @@ def download(url, path, overwrite=False):
 # We should be familiar with the process right now.
 import nnvm.compiler
 target = 'cuda'
-shape_dict = {'input_0': x.shape}
+# assume first input name is data
+input_name = sym.list_input_names()[0]
+shape_dict = {input_name: x.shape}
 graph, lib, params = nnvm.compiler.build(sym, target, shape_dict, params=params)
 
 ######################################################################
@@ -78,7 +80,7 @@ def download(url, path, overwrite=False):
 dtype = 'float32'
 m = graph_runtime.create(graph, lib, ctx)
 # set inputs
-m.set_input('input_0', tvm.nd.array(x.astype(dtype)))
+m.set_input(input_name, tvm.nd.array(x.astype(dtype)))
 m.set_input(**params)
 # execute
 m.run()
diff --git a/tutorials/nnvm/using_external_lib.py b/tutorials/nnvm/using_external_lib.py
index f599438f402b..fd00768b93be 100644
--- a/tutorials/nnvm/using_external_lib.py
+++ b/tutorials/nnvm/using_external_lib.py
@@ -1,6 +1,6 @@
 """
-Using external libraries with NNVM
-=====================
+Using External Libraries in NNVM
+================================
 **Author**: `Masahiro Masuda <https://github.com/masahi>`_
 
 This is a short tutorial on how to use external libraries such as cuDNN, or cuBLAS with NNVM.
@@ -24,7 +24,7 @@
 
 ######################################################################
 # Create a simple network
-# ---------------------------------------------
+# -----------------------
 # Let's create a very simple network for demonstration.
 # It consists of convolution, batch normalization, and ReLU activation.
 
@@ -40,7 +40,7 @@
 
 ######################################################################
 # Build and run with cuda backend
-# ---------------------------------------------
+# -------------------------------
 # We build and run this network with cuda backend, as usual.
 # By setting the logging level to DEBUG, the result of NNVM graph compilation will be dumped as pseudo code.
 import logging
@@ -151,7 +151,7 @@
 
 ######################################################################
 # Use cuDNN for a convolutional layer
-# ---------------------------------------------
+# -----------------------------------
 # We can use cuDNN to replace convolution kernels with cuDNN ones.
 # To do that, all we need to do is to append the option " -libs=cudnn" to the target string.
 net, params = utils.create_workload(simple_net, batch_size, data_shape[1:])
@@ -192,14 +192,14 @@
 
 ######################################################################
 # Verify the result
-# ---------------------------------------------
+# -----------------
 # We can check that the results of two runs match.
 
 np.testing.assert_allclose(out_cuda, out_cudnn, rtol=1e-5)
 
 #####################################################################
 # Conclusion
-# ---------------------------------------------
+# ----------
 # This tutorial covered the usage of cuDNN with NNVM.
 # We also have support for cuBLAS. If cuBLAS is enabled, it will be used inside a fully connected layer (nnvm.symbol.dense).
 # To use cuBLAS, set a target string as "cuda -libs=cublas".
diff --git a/tutorials/nnvm/define_and_compile_model.py b/tutorials/nnvm_quick_start.py
similarity index 92%
rename from tutorials/nnvm/define_and_compile_model.py
rename to tutorials/nnvm_quick_start.py
index 45cefcd1c81d..30650701516c 100644
--- a/tutorials/nnvm/define_and_compile_model.py
+++ b/tutorials/nnvm_quick_start.py
@@ -1,20 +1,17 @@
 """
-Quick Start - End-to-End Tutorial for NNVM/TVM Pipeline
+Quick Start Tutorial for Compiling Deep Learning Models
 =======================================================
 **Author**: `Yao Wang <https://github.com/kevinthesun>`_
 
 This example shows how to build a neural network with NNVM python frontend and
-generate runtime library for Nvidia GPU and Raspberry Pi with TVM. (Thanks to
-Tianqi's `tutorial for cuda <http://nnvm.tvmlang.org/tutorials/get_started.html>`_ and
-Ziheng's `tutorial for Raspberry Pi <http://nnvm.tvmlang.org/tutorials/deploy_model_on_rasp.html>`_)
-To run this notebook, you need to install tvm and nnvm following
-`these instructions <https://github.com/dmlc/nnvm/blob/master/docs/how_to/install.md>`_.
+generate runtime library for Nvidia GPU and Raspberry Pi with TVM.
+To run this notebook, you need to install tvm and nnvm.
 Notice that you need to build tvm with cuda and llvm.
 """
 
 ######################################################################
 # Overview for Supported Hardware Backend of TVM
-# -----------------------------
+# ----------------------------------------------
 # The image below shows hardware backend currently supported by TVM:
 #
 # .. image:: https://github.com/dmlc/web-data/raw/master/tvm/tutorial/tvm_support_list.png
@@ -52,7 +49,7 @@
 
 ######################################################################
 # Compilation
-# ----------------------------
+# -----------
 # Next step is to compile the model using the NNVM/TVM pipeline.
 # Users can specify the optimization level of the compilation.
 # Currently this value can be 0 to 2, which corresponds to
@@ -69,7 +66,7 @@
 #
 # We'll first compile for Nvidia GPU. Behind the scene, `nnvm.compiler.build`
 # first does a number of graph-level optimizations, e.g. pruning, fusing, etc.,
-# then registers the operators (i.e. the nodes of the optmized graphs) to 
+# then registers the operators (i.e. the nodes of the optmized graphs) to
 # TVM implementations to generate a `tvm.module`.
 # To generate the module library, TVM will first transfer the HLO IR into the lower
 # intrinsic IR of the specified target backend, which is CUDA in this example.
@@ -120,7 +117,7 @@
 
 ######################################################################
 # Compile and Deploy the Model to Raspberry Pi Remotely with RPC
-# ------------------------------
+# --------------------------------------------------------------
 # Following the steps above, we can also compile the model for Raspberry Pi.
 # TVM provides rpc module to help with remote deploying.
 #
@@ -130,9 +127,9 @@
 # :code:`use_rasp` to True, also change the host and port with your
 # device's host address and port number.
 
-# If we run the example locally for demonstration, we can simply set the 
-# compilation target as `llvm`. 
-# To run it on the Raspberry Pi, you need to specify its instruction set. 
+# If we run the example locally for demonstration, we can simply set the
+# compilation target as `llvm`.
+# To run it on the Raspberry Pi, you need to specify its instruction set.
 # `llvm -target=armv7l-none-linux-gnueabihf -mcpu=cortex-a53 -mattr=+neon`
 # is the recommended compilation configuration, thanks to Ziheng's work.
 
@@ -145,7 +142,7 @@
 if not use_rasp:
     # run server locally
     host = 'localhost'
-    port = 9090
+    port = 9099
     server = rpc.Server(host=host, port=port, use_popen=True)
 
 # compile and save model library
@@ -190,4 +187,3 @@
 if not use_rasp:
     # terminate the local server
     server.terminate()
-
diff --git a/tutorials/optimize/README.txt b/tutorials/optimize/README.txt
index 4118f94bd51f..b051548c5351 100644
--- a/tutorials/optimize/README.txt
+++ b/tutorials/optimize/README.txt
@@ -1,3 +1,2 @@
-Optimize Operators
-------------------
-
+Optimize Tensor Operators
+-------------------------
diff --git a/tutorials/optimize/opt_gemm.py b/tutorials/optimize/opt_gemm.py
index 44ee53a73399..803b81e7d222 100644
--- a/tutorials/optimize/opt_gemm.py
+++ b/tutorials/optimize/opt_gemm.py
@@ -174,7 +174,7 @@
 
 ###################################################################################################
 # Loop Permutation
-# -------------
+# ----------------
 # If we look at the above IR, we can see the inner loop row data is vectorized and
 # B is transformed into PackedB. The traversal of PackedB is sequential now.
 # So we will look at the access pattern of A. In current schedule, A is accessed column by column
@@ -262,7 +262,7 @@
 
 ################################################################################################
 # Write cache for blocks
-# --------
+# ----------------------
 # After blocking, the program will write result to C block by block, the access pattern
 # is not sequential. So we can use a sequential cache array to hold the block results and
 # write to C when all the block results are ready.
@@ -308,7 +308,7 @@
 
 ###################################################################################################
 # Parallel
-# -------------
+# --------
 # Futhermore, we can also utilize multi-core processors to do the thread-level parallelization.
 
 s = tvm.create_schedule(C.op)

From ccdb46a3664db8971ba48645dc44e3a43cfd5249 Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Tue, 29 May 2018 11:04:44 -0700
Subject: [PATCH 668/949] Update gitignore

---
 .gitignore | 42 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)

diff --git a/.gitignore b/.gitignore
index 4f44aeb8d952..838a2f59aad6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -132,6 +132,32 @@ xcuserdata/
 .emscripten*
 .m2
 
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
+
 ## Other
 *.moved-aside
 *.xccheckout
@@ -144,3 +170,19 @@ cscope*
 # vim temporary files
 *.swp
 *.swo
+
+# TVM generated code
+perf
+
+*.json
+*.params
+*.onnx
+*.h5
+synset.txt
+cat.jpg
+
+# Mac OS X
+.DS_Store
+
+# Jetbrain
+.idea

From 164bfa0415e9c3fb623c7a33a3450e7a2f43045d Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Tue, 29 May 2018 16:22:34 -0700
Subject: [PATCH 669/949] Homepage URL to tvm.ai

---
 README.md                                  | 10 +++----
 apps/howto_deploy/README.md                |  2 +-
 docs/README.txt                            |  2 +-
 docs/dev/runtime.md                        |  6 ++---
 docs/how_to/deploy.md                      |  4 +--
 jvm/README.md                              |  8 +++---
 nnvm/README.md                             | 31 +---------------------
 tutorials/nnvm/deploy_model_on_mali_gpu.py |  3 +--
 8 files changed, 18 insertions(+), 48 deletions(-)

diff --git a/README.md b/README.md
index 93dbb9ac3534..77d72c6418a6 100644
--- a/README.md
+++ b/README.md
@@ -1,22 +1,22 @@
-<img src=https://raw.githubusercontent.com/tqchen/tvmlang.org/master/images/logo/tvm-logo-small.png width=128/> Open Deep Learning Compiler Stack
+<img src=https://raw.githubusercontent.com/tqchen/tvm.ai/master/images/logo/tvm-logo-small.png width=128/> Open Deep Learning Compiler Stack
 ==============================================
 
 [![GitHub license](http://dmlc.github.io/img/apache2.svg)](./LICENSE)
 [![Build Status](http://mode-gpu.cs.washington.edu:8080/buildStatus/icon?job=dmlc/tvm/master)](http://mode-gpu.cs.washington.edu:8080/job/dmlc/job/tvm/job/master/)
 
 [Installation](docs/how_to/install.md) |
-[Documentation](http://docs.tvmlang.org) |
-[Tutorials](http://tutorials.tvmlang.org) |
+[Documentation](http://docs.tvm.ai) |
+[Tutorials](http://tutorials.tvm.ai) |
 [Operator Inventory](topi) |
 [FAQ](docs/faq.md) |
 [Contributors](CONTRIBUTORS.md) |
-[Community](http://tvmlang.org/community.html) |
+[Community](http://tvm.ai/community.html) |
 [Release Notes](NEWS.md)
 
 TVM is a compiler stack for deep learning systems. It is designed to close the gap between the
 productivity-focused deep learning frameworks, and the performance- and efficiency-focused hardware backends.
 TVM works with deep learning frameworks to provide end to end compilation to different backends.
-Checkout the [tvm stack homepage](http://tvmlang.org/)  for more information.
+Checkout the [tvm stack homepage](http://tvm.ai/)  for more information.
 
 License
 -------
diff --git a/apps/howto_deploy/README.md b/apps/howto_deploy/README.md
index 6c732879a6a5..570c42ed185d 100644
--- a/apps/howto_deploy/README.md
+++ b/apps/howto_deploy/README.md
@@ -8,4 +8,4 @@ Type the following command to run the sample code under the current folder(need
 ./run_example.sh
 ```
 
-Checkout [How to Deploy TVM Modules](http://docs.tvmlang.org/how_to/deploy.html) for more information.
+Checkout [How to Deploy TVM Modules](http://docs.tvm.ai/how_to/deploy.html) for more information.
diff --git a/docs/README.txt b/docs/README.txt
index b8780dd9fc87..8f62dbacc58e 100644
--- a/docs/README.txt
+++ b/docs/README.txt
@@ -1,6 +1,6 @@
 The documentation of tvm is generated with recommonmark and sphinx.
 
-- A hosted version of doc is at http://docs.tvmlang.org
+- A hosted version of doc is at http://docs.tvm.ai
 - pip install sphinx>=1.5.5 sphinx-gallery sphinx_rtd_theme matplotlib Image recommonmark
 - Build tvm first in the root folder.
 - To build locally, you need to enable USE_CUDA, USE_OPENCL, LLVM_CONFIG in config.mk and then type "make html" in this folder.
diff --git a/docs/dev/runtime.md b/docs/dev/runtime.md
index d601801a435e..a5d8138c3372 100644
--- a/docs/dev/runtime.md
+++ b/docs/dev/runtime.md
@@ -3,7 +3,7 @@
 TVM supports multiple programming languages for the compiler stack development and deployment.
 In this note, we explain the key elements of the TVM runtime.
 
-![](http://www.tvmlang.org/images/release/tvm_flexible.png)
+![](http://www.tvm.ai/images/release/tvm_flexible.png)
 
 We need to satisfy quite a few interesting requirements
 
@@ -132,11 +132,11 @@ of new device easy, and we do not need to redo the host code generation for each
 The PackedFunc and Module system also makes it easy to ship the function into remote devices directly.
 Under the hood, we have an RPCModule that serializes the arguments to do the data movement and launches the computation on the remote.
 
-![](http://www.tvmlang.org/images/release/tvm_rpc.png)
+![](http://www.tvm.ai/images/release/tvm_rpc.png)
 
 The RPC server itself is minimum and can be bundled into the runtime. We can start a minimum TVM
 RPC server on iPhone/android/raspberry pi or even the browser. The cross compilation on server and shipping of the module for testing can be done in the same script. Checkout
-[Cross compilation and RPC tutorial](http://docs.tvmlang.org/tutorials/deployment/cross_compilation_and_rpc.html#sphx-glr-tutorials-deployment-cross-compilation-and-rpc-py)  for more details.
+[Cross compilation and RPC tutorial](http://docs.tvm.ai/tutorials/deployment/cross_compilation_and_rpc.html#sphx-glr-tutorials-deployment-cross-compilation-and-rpc-py)  for more details.
 
 This instant feedback gives us a lot of advantages. For example, to test the correctness of generated code on iPhone, we no longer have to write test-cases in swift/objective-c from scratch -- We can use RPC to execute on iPhone, copy the result back and do verification on the host via numpy. We can also do the profiling using the same script.
 
diff --git a/docs/how_to/deploy.md b/docs/how_to/deploy.md
index 68630adfaeaa..69fedaa974b4 100644
--- a/docs/how_to/deploy.md
+++ b/docs/how_to/deploy.md
@@ -12,7 +12,7 @@ cd apps/howto_deploy
 Get TVM Runtime Library
 -----------------------
 
-![](http://www.tvmlang.org/images/release/tvm_flexible.png)
+![](http://www.tvm.ai/images/release/tvm_flexible.png)
 
 The only thing we need is to link to a TVM runtime in your target platform.
 TVM provides a minimum runtime, which costs around 300K to 600K depending on how much modules we use.
@@ -64,7 +64,7 @@ From android java TVM API to load model & execute can be refered at this [java](
 Deploy NNVM Modules
 -------------------
 NNVM compiled modules are fully embedded in TVM runtime as long as ```GRAPH_RUNTIME``` option
-is enabled in tvm runtime. Check out the [TVM documentation](http://docs.tvmlang.org/) for
+is enabled in tvm runtime. Check out the [TVM documentation](http://docs.tvm.ai/) for
 how to deploy TVM runtime to your system.
 
 In a nutshell, we will need three items to deploy a compiled module.
diff --git a/jvm/README.md b/jvm/README.md
index 0a113fed0e7b..2438758617a6 100644
--- a/jvm/README.md
+++ b/jvm/README.md
@@ -27,7 +27,7 @@ TVM4J contains three modules:
 
 ### Build
 
-First please refer to [Installation Guide](http://docs.tvmlang.org/how_to/install.html) and build runtime shared library from the C++ codes (libtvm\_runtime.so for Linux and libtvm\_runtime.dylib for OSX).
+First please refer to [Installation Guide](http://docs.tvm.ai/how_to/install.html) and build runtime shared library from the C++ codes (libtvm\_runtime.so for Linux and libtvm\_runtime.dylib for OSX).
 
 Then you can compile tvm4j by
 
@@ -117,12 +117,12 @@ public class LoadAddFunc {
     Module fadd = Module.load(loadingDir + File.separator + "add_cpu.so");
 
     TVMContext ctx = TVMContext.cpu();
-    
+
     long[] shape = new long[]{2};
     NDArray arr = NDArray.empty(shape, ctx);
     arr.copyFrom(new float[]{3f, 4f});
     NDArray res = NDArray.empty(shape, ctx);
-    
+
     fadd.entryFunc().pushArg(arr).pushArg(arr).pushArg(res).invoke();
     System.out.println(Arrays.toString(res.asFloatArray()));
 
@@ -135,7 +135,7 @@ public class LoadAddFunc {
 
 ## RPC Server
 
-There are two ways to start an RPC server on JVM. A standalone server can be started by 
+There are two ways to start an RPC server on JVM. A standalone server can be started by
 
 ```java
 Server server = new Server(port);
diff --git a/nnvm/README.md b/nnvm/README.md
index b733f4f852eb..ed8a18e3fa1e 100644
--- a/nnvm/README.md
+++ b/nnvm/README.md
@@ -1,23 +1,4 @@
-# NNVM: Open Compiler for AI Frameworks
-[![Build Status](http://mode-gpu.cs.washington.edu:8080/buildStatus/icon?job=dmlc/nnvm/master)](http://mode-gpu.cs.washington.edu:8080/job/dmlc/job/nnvm/job/master/)
-[![GitHub license](http://dmlc.github.io/img/apache2.svg)](./LICENSE)
-
-[Installation](docs/how_to/install.md) |
-[Documentation](http://nnvm.tvmlang.org) |
-[Tutorials](http://nnvm.tvmlang.org/tutorials/index.html) |
-[Release Notes](NEWS.md)
-
-NNVM compiler offers reusable computation graph optimization and compilation for deep learning systems.
-It is backed by the [TVM stack](http://tvmlang.org) and provides modules to:
-
-- Represent deep learning workloads from front-end frameworks via a graph IR.
-- Optimize computation graphs to improve performance.
-- Compile into executable modules and deploy to different hardware backends with minimum dependency.
-
-NNVM is designed to add new frontend, operators and graph optimizations in a decentralized fashion without changing the core interface.
-The compiled module can be deployed to server, mobile, embedded devices and browsers with minimum dependency, in languages including c++, python, javascript, java, objective-c. Checkout [our release announcement](http://www.tvmlang.org/2017/10/06/nnvm-compiler-announcement.html)
-
-The following code snippet demonstrates the general workflow of nnvm compiler.
+# NNVM Compiler Module of TVM Stack
 
 ```python
 import tvm
@@ -52,13 +33,3 @@ rmodule = graph_runtime.create(graph, rlib, remote.gpu(0))
 rmodule.set_input(**params)
 rmodule.run()
 ```
-
-License
--------
-Licensed under an [Apache-2.0](https://github.com/dmlc/nnvm/blob/master/LICENSE) license.
-
-
-Links
------
-- [TinyFlow](https://github.com/tqchen/tinyflow) on how you can use  NNVM to build a TensorFlow like API.
-- [Apache MXNet](http://mxnet.io/) uses NNVM as a backend.
diff --git a/tutorials/nnvm/deploy_model_on_mali_gpu.py b/tutorials/nnvm/deploy_model_on_mali_gpu.py
index 3c0152b8c199..116e99f76790 100644
--- a/tutorials/nnvm/deploy_model_on_mali_gpu.py
+++ b/tutorials/nnvm/deploy_model_on_mali_gpu.py
@@ -7,8 +7,7 @@
 deploy it on Firefly-RK3399 with ARM Mali GPU.  We will use the
 Mali-T860 MP4 GPU on this board to accelerate the inference.
 
-This tutorial is based on the `tutorial <http://nnvm.tvmlang.org/tutorials/deploy_model_on_rasp.html>`_
-for deploying on Raspberry Pi by `Ziheng Jiang <https://ziheng.org/>`_.
+This tutorial is based on the tutorial for deploying on Raspberry Pi by `Ziheng Jiang <https://ziheng.org/>`_.
 Great thanks to the original author, I only do several lines of modification.
 
 To begin with, we import nnvm (for compilation) and TVM (for deployment).

From 0295454c5f72e6919c630a29f4a134392a9c10c9 Mon Sep 17 00:00:00 2001
From: SRK Reddy <sivar.b@huawei.com>
Date: Wed, 30 May 2018 13:14:24 +0530
Subject: [PATCH 670/949]   [BUILD] correction in topi dependencies        
 Change at 'topi/include/topi/' not recognozed by make.

---
 Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index b27220f01e48..f2c3d3919eb5 100644
--- a/Makefile
+++ b/Makefile
@@ -298,7 +298,7 @@ build/%.o: src/%.cc
 
 build/topi/%.o: topi/src/%.cc
 	@mkdir -p $(@D)
-	$(CXX) $(CFLAGS) -MM -MT build/$*.o $< >build/$*.d
+	$(CXX) $(CFLAGS) -MM -MT build/topi/$*.o $< >build/topi/$*.d
 	$(CXX) -c $(CFLAGS) -c $< -o $@
 
 lib/libtvm.${SHARED_LIBRARY_SUFFIX}: $(ALL_DEP) $(RUNTIME_DEP)

From 54e32750a982aaf0ef1335f015e0d6b8715209ab Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Wed, 30 May 2018 10:56:03 +0530
Subject: [PATCH 671/949] Gitignore updated as NNVM untracked files not
 detected

---
 .gitignore | 1 -
 1 file changed, 1 deletion(-)

diff --git a/.gitignore b/.gitignore
index 838a2f59aad6..1d5db4cfebfd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -98,7 +98,6 @@ build_*
 Win32
 *.dir
 perf
-nnvm
 *.wasm
 .emscripten
 

From 0fe4824f6682d45e8ab6fcab0f6f8718382aef05 Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Wed, 30 May 2018 16:49:24 -0700
Subject: [PATCH 672/949] [IO] Support cross-endian

---
 dmlc-core                          |   2 +-
 include/tvm/runtime/serializer.h   |  50 ++++++++
 nnvm/src/compiler/graph_runtime.cc |  74 ++++++-----
 python/tvm/contrib/rpc/base.py     |   4 +-
 python/tvm/contrib/rpc/client.py   |   4 +-
 python/tvm/contrib/rpc/proxy.py    |  28 ++---
 python/tvm/contrib/rpc/server.py   |  22 ++--
 python/tvm/contrib/rpc/tracker.py  |  12 +-
 src/runtime/file_util.cc           |   1 +
 src/runtime/graph/graph_runtime.cc |  30 +++--
 src/runtime/rpc/rpc_session.cc     | 196 +++++++++++++++++------------
 11 files changed, 265 insertions(+), 158 deletions(-)
 create mode 100644 include/tvm/runtime/serializer.h

diff --git a/dmlc-core b/dmlc-core
index d3f7fbb53e5b..9b3f9753ae81 160000
--- a/dmlc-core
+++ b/dmlc-core
@@ -1 +1 @@
-Subproject commit d3f7fbb53e5b037c0f5bf6bd21871ccc720690cc
+Subproject commit 9b3f9753ae81d657743c555e0cacc4e43f0bed2d
diff --git a/include/tvm/runtime/serializer.h b/include/tvm/runtime/serializer.h
new file mode 100644
index 000000000000..391c7806ad9c
--- /dev/null
+++ b/include/tvm/runtime/serializer.h
@@ -0,0 +1,50 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file tvm/runtime/serializer.h
+ * \brief Serializer extension to support TVM data types
+ *  Include this file to enable serialization of DLDataType, DLContext
+ */
+#ifndef TVM_RUNTIME_SERIALIZER_H_
+#define TVM_RUNTIME_SERIALIZER_H_
+
+#include <dmlc/io.h>
+#include <dmlc/serializer.h>
+#include "./c_runtime_api.h"
+
+namespace dmlc {
+namespace serializer {
+
+template<>
+struct Handler<DLDataType> {
+  inline static void Write(Stream *strm, const DLDataType& dtype) {
+    Handler<uint8_t>::Write(strm, dtype.code);
+    Handler<uint8_t>::Write(strm, dtype.bits);
+    Handler<uint16_t>::Write(strm, dtype.lanes);
+  }
+  inline static bool Read(Stream *strm, DLDataType* dtype) {
+    if (!Handler<uint8_t>::Read(strm, &(dtype->code))) return false;
+    if (!Handler<uint8_t>::Read(strm, &(dtype->bits))) return false;
+    if (!Handler<uint16_t>::Read(strm, &(dtype->lanes))) return false;
+    return true;
+  }
+};
+
+template<>
+struct Handler<DLContext> {
+  inline static void Write(Stream *strm, const DLContext& ctx) {
+    int32_t device_type = static_cast<int32_t>(ctx.device_type);
+    Handler<int32_t>::Write(strm, device_type);
+    Handler<int32_t>::Write(strm, ctx.device_id);
+  }
+  inline static bool Read(Stream *strm, DLContext* ctx) {
+    int32_t device_type = 0;
+    if (!Handler<int32_t>::Read(strm, &(device_type))) return false;
+    ctx->device_type = static_cast<DLDeviceType>(device_type);
+    if (!Handler<int32_t>::Read(strm, &(ctx->device_id))) return false;
+    return true;
+  }
+};
+
+}  // namespace serializer
+}  // namespace dmlc
+#endif  // TVM_RUNTIME_SERIALIZER_H_
diff --git a/nnvm/src/compiler/graph_runtime.cc b/nnvm/src/compiler/graph_runtime.cc
index 51a240a46119..689ed70ce9f2 100644
--- a/nnvm/src/compiler/graph_runtime.cc
+++ b/nnvm/src/compiler/graph_runtime.cc
@@ -7,6 +7,7 @@
 #include <tvm/runtime/packed_func.h>
 #include <tvm/runtime/c_runtime_api.h>
 #include <tvm/runtime/registry.h>
+#include <tvm/runtime/serializer.h>
 #include "./graph_runtime.h"
 
 namespace nnvm {
@@ -38,46 +39,53 @@ NNVM_REGISTER_OP(tvm_op)
 
 bool SaveDLTensor(dmlc::Stream* strm, DLTensor* tensor) {
   uint64_t header = kTVMNDArrayMagic, reserved = 0;
-  strm->Write(&header, sizeof(header));
-  strm->Write(&reserved, sizeof(reserved));
-
-  strm->Write(&tensor->ctx, sizeof(tensor->ctx));
-  strm->Write(&tensor->ndim, sizeof(tensor->ndim));
-  strm->Write(&tensor->dtype, sizeof(tensor->dtype));
-
+  strm->Write(header);
+  strm->Write(reserved);
+  strm->Write(tensor->ctx);
+  strm->Write(tensor->ndim);
+  strm->Write(tensor->dtype);
   int ndim = tensor->ndim;
-  strm->Write(tensor->shape, sizeof(int64_t) * ndim);
+  strm->WriteArray(tensor->shape, ndim);
 
-  int type_size = tensor->dtype.bits / 8;
-  int64_t size = 1;
+  int type_bytes = tensor->dtype.bits / 8;
+  int64_t num_elems = 1;
   for (int i = 0; i < ndim; ++i) {
-    size *= tensor->shape[i];
+    num_elems *= tensor->shape[i];
+  }
+  int64_t data_byte_size = type_bytes * num_elems;
+  strm->Write(data_byte_size);
+  // handle endianness of data correctly.
+  if (DMLC_IO_NO_ENDIAN_SWAP) {
+    strm->Write(tensor->data, data_byte_size);
+  } else {
+    uint8_t* dptr = reinterpret_cast<uint8_t*>(tensor->data);
+    std::vector<uint8_t> bytes(dptr, dptr + data_byte_size);
+    dmlc::ByteSwap(dmlc::BeginPtr(bytes), type_bytes, num_elems);
+    strm->Write(dmlc::BeginPtr(bytes), data_byte_size);
   }
-  int64_t data_byte_size = type_size * size;
-  strm->Write(&data_byte_size, sizeof(data_byte_size));
-  strm->Write(tensor->data, data_byte_size);
   return true;
 }
 
 DLTensor* LoadDLTensor(dmlc::Stream* strm) {
   uint64_t header, reserved;
-  CHECK(strm->Read(&header, sizeof(header)))
+  CHECK(strm->Read(&header))
       << "Invalid DLTensor file format";
-  CHECK(strm->Read(&reserved, sizeof(reserved)))
+  CHECK(strm->Read(&reserved))
       << "Invalid DLTensor file format";
   CHECK(header == kTVMNDArrayMagic)
       << "Invalid DLTensor file format";
-
   DLTensor tensor;
-  CHECK(strm->Read(&tensor.ctx, sizeof(tensor.ctx)))
+  CHECK(strm->Read(&(tensor.ctx)))
       << "Invalid DLTensor file format";
-  CHECK(strm->Read(&tensor.ndim, sizeof(tensor.ndim)))
+  CHECK(strm->Read(&(tensor.ndim)))
       << "Invalid DLTensor file format";
-  CHECK(strm->Read(&tensor.dtype, sizeof(tensor.dtype)))
+  CHECK(strm->Read(&(tensor.dtype)))
       << "Invalid DLTensor file format";
   std::vector<int64_t> shape(tensor.ndim);
-  CHECK(strm->Read(&shape[0], sizeof(int64_t) * tensor.ndim))
-      << "Invalid DLTensor file format";
+  if (tensor.ndim != 0) {
+    CHECK(strm->ReadArray(&shape[0], tensor.ndim))
+        << "Invalid DLTensor file format";
+  }
   DLTensor* ret;
   CHECK_EQ(TVMArrayAlloc(shape.data(),
                          tensor.ndim,
@@ -87,18 +95,21 @@ DLTensor* LoadDLTensor(dmlc::Stream* strm) {
                          static_cast<int>(tensor.ctx.device_type),
                          tensor.ctx.device_id,
                          &ret), 0) << TVMGetLastError();
-  int64_t size = 1;
-  int type_size = ret->dtype.bits / 8;
+  int64_t num_elems = 1;
+  int elem_bytes = (ret->dtype.bits + 7) / 8;
   for (int i = 0; i < ret->ndim; ++i) {
-    size *= ret->shape[i];
+    num_elems *= ret->shape[i];
   }
   int64_t data_byte_size;
-  CHECK(strm->Read(&data_byte_size, sizeof(data_byte_size)))
+  CHECK(strm->Read(&data_byte_size))
       << "Invalid DLTensor file format";
-  CHECK(data_byte_size == type_size * size)
+  CHECK(data_byte_size == num_elems * elem_bytes)
       << "Invalid DLTensor file format";
-  CHECK(strm->Read(ret->data, type_size * size))
+  CHECK(strm->Read(ret->data, data_byte_size))
       << "Invalid DLTensor file format";
+  if (!DMLC_IO_NO_ENDIAN_SWAP) {
+    dmlc::ByteSwap(ret->data, elem_bytes, num_elems);
+  }
   return ret;
 }
 
@@ -118,12 +129,12 @@ TVM_REGISTER_GLOBAL("nnvm.compiler._save_param_dict")
     dmlc::MemoryStringStream strm(&bytes);
     dmlc::Stream* fo = &strm;
     uint64_t header = kTVMNDArrayListMagic, reserved = 0;
-    fo->Write(&header, sizeof(header));
-    fo->Write(&reserved, sizeof(reserved));
+    fo->Write(header);
+    fo->Write(reserved);
     fo->Write(names);
     {
       uint64_t sz = static_cast<uint64_t>(arrays.size());
-      fo->Write(&sz, sizeof(sz));
+      fo->Write(sz);
       for (size_t i = 0; i < sz; ++i) {
         SaveDLTensor(fo, arrays[i]);
       }
@@ -150,7 +161,6 @@ TVM_REGISTER_GLOBAL("nnvm.compiler._load_param_dict")
         << "Invalid parameters file format";
     CHECK(strm->Read(&reserved))
         << "Invalid parameters file format";
-
     CHECK(strm->Read(&names))
         << "Invalid parameters file format";
     uint64_t sz;
diff --git a/python/tvm/contrib/rpc/base.py b/python/tvm/contrib/rpc/base.py
index a9fd232f5507..67e6d6b43bd1 100644
--- a/python/tvm/contrib/rpc/base.py
+++ b/python/tvm/contrib/rpc/base.py
@@ -73,7 +73,7 @@ def sendjson(sock, data):
         Python value to be sent.
     """
     data = json.dumps(data)
-    sock.sendall(struct.pack("@i", len(data)))
+    sock.sendall(struct.pack("<i", len(data)))
     sock.sendall(data.encode("utf-8"))
 
 
@@ -90,7 +90,7 @@ def recvjson(sock):
     value : object
         The value received.
     """
-    size = struct.unpack("@i", recvall(sock, 4))[0]
+    size = struct.unpack("<i", recvall(sock, 4))[0]
     data = json.loads(py_str(recvall(sock, size)))
     return data
 
diff --git a/python/tvm/contrib/rpc/client.py b/python/tvm/contrib/rpc/client.py
index f409b2f72288..637aed6e4cc2 100644
--- a/python/tvm/contrib/rpc/client.py
+++ b/python/tvm/contrib/rpc/client.py
@@ -192,8 +192,8 @@ def __del__(self):
 
     def _connect(self):
         self._sock = base.connect_with_retry(self._addr)
-        self._sock.sendall(struct.pack("@i", base.RPC_TRACKER_MAGIC))
-        magic = struct.unpack("@i", base.recvall(self._sock, 4))[0]
+        self._sock.sendall(struct.pack("<i", base.RPC_TRACKER_MAGIC))
+        magic = struct.unpack("<i", base.recvall(self._sock, 4))[0]
         if magic != base.RPC_TRACKER_MAGIC:
             raise RuntimeError("%s is not RPC Tracker" % str(self._addr))
 
diff --git a/python/tvm/contrib/rpc/proxy.py b/python/tvm/contrib/rpc/proxy.py
index 857e69433a1c..315354edeb92 100644
--- a/python/tvm/contrib/rpc/proxy.py
+++ b/python/tvm/contrib/rpc/proxy.py
@@ -58,14 +58,14 @@ def name(self):
     def _init_step(self, message):
         if self._magic is None:
             assert len(message) == 4
-            self._magic = struct.unpack('@i', message)[0]
+            self._magic = struct.unpack('<i', message)[0]
             if self._magic != base.RPC_MAGIC:
                 logging.info("Invalid RPC magic from %s", self.name())
                 self.close()
             self._init_req_nbytes = 4
         elif self._rpc_key_length is None:
             assert len(message) == 4
-            self._rpc_key_length = struct.unpack('@i', message)[0]
+            self._rpc_key_length = struct.unpack('<i', message)[0]
             self._init_req_nbytes = self._rpc_key_length
         elif self.rpc_key is None:
             assert len(message) == self._rpc_key_length
@@ -269,12 +269,12 @@ def _pair_up(self, lhs, rhs):
         lhs.forward_proxy = rhs
         rhs.forward_proxy = lhs
 
-        lhs.send_data(struct.pack('@i', base.RPC_CODE_SUCCESS))
-        lhs.send_data(struct.pack('@i', len(rhs.rpc_key)))
+        lhs.send_data(struct.pack('<i', base.RPC_CODE_SUCCESS))
+        lhs.send_data(struct.pack('<i', len(rhs.rpc_key)))
         lhs.send_data(rhs.rpc_key.encode("utf-8"))
 
-        rhs.send_data(struct.pack('@i', base.RPC_CODE_SUCCESS))
-        rhs.send_data(struct.pack('@i', len(lhs.rpc_key)))
+        rhs.send_data(struct.pack('<i', base.RPC_CODE_SUCCESS))
+        rhs.send_data(struct.pack('<i', len(lhs.rpc_key)))
         rhs.send_data(lhs.rpc_key.encode("utf-8"))
         logging.info("Pairup connect %s  and %s", lhs.name(), rhs.name())
 
@@ -299,8 +299,8 @@ def _update_tracker(self, period_update=False):
             if self._tracker_conn is None:
                 self._tracker_conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                 self._tracker_conn.connect(self._tracker_addr)
-                self._tracker_conn.sendall(struct.pack("@i", base.RPC_TRACKER_MAGIC))
-                magic = struct.unpack("@i", base.recvall(self._tracker_conn, 4))[0]
+                self._tracker_conn.sendall(struct.pack("<i", base.RPC_TRACKER_MAGIC))
+                magic = struct.unpack("<i", base.recvall(self._tracker_conn, 4))[0]
                 if magic != base.RPC_TRACKER_MAGIC:
                     self.loop.stop()
                     raise RuntimeError("%s is not RPC Tracker" % str(self._tracker_addr))
@@ -371,7 +371,7 @@ def _handler_ready_tracker_mode(self, handler):
             if handler.match_key in self._server_pool:
                 self._pair_up(self._server_pool.pop(handler.match_key), handler)
             else:
-                handler.send_data(struct.pack('@i', base.RPC_CODE_MISMATCH))
+                handler.send_data(struct.pack('<i', base.RPC_CODE_MISMATCH))
                 handler.signal_close()
 
     def _handler_ready_proxy_mode(self, handler):
@@ -395,12 +395,12 @@ def cleanup():
                     logging.info("Timeout client connection %s, cannot find match key=%s",
                                  handler.name(), key)
                     pool_dst.pop(key)
-                    handler.send_data(struct.pack('@i', base.RPC_CODE_MISMATCH))
+                    handler.send_data(struct.pack('<i', base.RPC_CODE_MISMATCH))
                     handler.signal_close()
             self.loop.call_later(timeout, cleanup)
         else:
             logging.info("Duplicate connection with same key=%s", key)
-            handler.send_data(struct.pack('@i', base.RPC_CODE_DUPLICATE))
+            handler.send_data(struct.pack('<i', base.RPC_CODE_DUPLICATE))
             handler.signal_close()
 
     def handler_ready(self, handler):
@@ -538,13 +538,13 @@ def _connect(key):
         on_message = create_on_message(conn)
         temp = _server_env(None)
         # Start connecton
-        conn.write_message(struct.pack('@i', base.RPC_MAGIC), binary=True)
+        conn.write_message(struct.pack('<i', base.RPC_MAGIC), binary=True)
         key = "server:" + key
-        conn.write_message(struct.pack('@i', len(key)), binary=True)
+        conn.write_message(struct.pack('<i', len(key)), binary=True)
         conn.write_message(key.encode("utf-8"), binary=True)
         msg = yield conn.read_message()
         assert len(msg) >= 4
-        magic = struct.unpack('@i', msg[:4])[0]
+        magic = struct.unpack('<i', msg[:4])[0]
         if magic == base.RPC_CODE_DUPLICATE:
             raise RuntimeError("key: %s has already been used in proxy" % key)
         elif magic == base.RPC_CODE_MISMATCH:
diff --git a/python/tvm/contrib/rpc/server.py b/python/tvm/contrib/rpc/server.py
index b49a6900b295..e3c731a20eb9 100644
--- a/python/tvm/contrib/rpc/server.py
+++ b/python/tvm/contrib/rpc/server.py
@@ -124,23 +124,23 @@ def _accept_conn(listen_sock, tracker_conn, ping_period=2):
                         unmatch_period_count = 0
                     continue
             conn, addr = listen_sock.accept()
-            magic = struct.unpack("@i", base.recvall(conn, 4))[0]
+            magic = struct.unpack("<i", base.recvall(conn, 4))[0]
             if magic != base.RPC_MAGIC:
                 conn.close()
                 continue
-            keylen = struct.unpack("@i", base.recvall(conn, 4))[0]
+            keylen = struct.unpack("<i", base.recvall(conn, 4))[0]
             key = py_str(base.recvall(conn, keylen))
             arr = key.split()
             expect_header = "client:" + matchkey
             server_key = "server:" + rpc_key
             if arr[0] != expect_header:
-                conn.sendall(struct.pack("@i", base.RPC_CODE_MISMATCH))
+                conn.sendall(struct.pack("<i", base.RPC_CODE_MISMATCH))
                 conn.close()
                 logging.info("RPCServer: mismatch key from %s", addr)
                 continue
             else:
-                conn.sendall(struct.pack("@i", base.RPC_CODE_SUCCESS))
-                conn.sendall(struct.pack("@i", len(server_key)))
+                conn.sendall(struct.pack("<i", base.RPC_CODE_SUCCESS))
+                conn.sendall(struct.pack("<i", len(server_key)))
                 conn.sendall(server_key.encode("utf-8"))
                 return conn, addr, _parse_server_opt(arr[1:])
 
@@ -151,8 +151,8 @@ def _accept_conn(listen_sock, tracker_conn, ping_period=2):
             # step 1: setup tracker and report to tracker
             if tracker_addr and tracker_conn is None:
                 tracker_conn = base.connect_with_retry(tracker_addr)
-                tracker_conn.sendall(struct.pack("@i", base.RPC_TRACKER_MAGIC))
-                magic = struct.unpack("@i", base.recvall(tracker_conn, 4))[0]
+                tracker_conn.sendall(struct.pack("<i", base.RPC_TRACKER_MAGIC))
+                magic = struct.unpack("<i", base.recvall(tracker_conn, 4))[0]
                 if magic != base.RPC_TRACKER_MAGIC:
                     raise RuntimeError("%s is not RPC Tracker" % str(tracker_addr))
                 # report status of current queue
@@ -193,17 +193,17 @@ def _connect_proxy_loop(addr, key, load_library):
         try:
             sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
             sock.connect(addr)
-            sock.sendall(struct.pack("@i", base.RPC_MAGIC))
-            sock.sendall(struct.pack("@i", len(key)))
+            sock.sendall(struct.pack("<i", base.RPC_MAGIC))
+            sock.sendall(struct.pack("<i", len(key)))
             sock.sendall(key.encode("utf-8"))
-            magic = struct.unpack("@i", base.recvall(sock, 4))[0]
+            magic = struct.unpack("<i", base.recvall(sock, 4))[0]
             if magic == base.RPC_CODE_DUPLICATE:
                 raise RuntimeError("key: %s has already been used in proxy" % key)
             elif magic == base.RPC_CODE_MISMATCH:
                 logging.info("RPCProxy do not have matching client key %s", key)
             elif magic != base.RPC_CODE_SUCCESS:
                 raise RuntimeError("%s is not RPC Proxy" % str(addr))
-            keylen = struct.unpack("@i", base.recvall(sock, 4))[0]
+            keylen = struct.unpack("<i", base.recvall(sock, 4))[0]
             remote_key = py_str(base.recvall(sock, keylen))
             opts = _parse_server_opt(remote_key.split()[1:])
             logging.info("RPCProxy connected to %s", str(addr))
diff --git a/python/tvm/contrib/rpc/tracker.py b/python/tvm/contrib/rpc/tracker.py
index 5e5620042aa4..165ff5b80789 100644
--- a/python/tvm/contrib/rpc/tracker.py
+++ b/python/tvm/contrib/rpc/tracker.py
@@ -143,11 +143,11 @@ def _init_conn(self, message):
         if len(message) != 4:
             logging.info("Invalid connection from %s", self.name())
             self.close()
-        magic = struct.unpack('@i', message)[0]
+        magic = struct.unpack('<i', message)[0]
         if magic != RPC_TRACKER_MAGIC:
             logging.info("Invalid magic from %s", self.name())
             self.close()
-        self.write_message(struct.pack('@i', RPC_TRACKER_MAGIC), binary=True)
+        self.write_message(struct.pack('<i', RPC_TRACKER_MAGIC), binary=True)
         self._init_req_nbytes = 0
 
     def on_message(self, message):
@@ -168,7 +168,7 @@ def on_message(self, message):
         while True:
             if self._msg_size == 0:
                 if len(self._data) >= 4:
-                    self._msg_size = struct.unpack('@i', self._data[:4])[0]
+                    self._msg_size = struct.unpack('<i', self._data[:4])[0]
                 else:
                     return
             if self._msg_size != 0 and len(self._data) >= self._msg_size + 4:
@@ -184,7 +184,7 @@ def ret_value(self, data):
         """return value to the output"""
         data = json.dumps(data)
         self.write_message(
-            struct.pack('@i', len(data)), binary=True)
+            struct.pack('<i', len(data)), binary=True)
         self.write_message(data.encode("utf-8"), binary=True)
 
     def call_handler(self, args):
@@ -355,8 +355,8 @@ def __init__(self,
     def _stop_tracker(self):
         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         sock.connect((self.host, self.port))
-        sock.sendall(struct.pack("@i", base.RPC_TRACKER_MAGIC))
-        magic = struct.unpack("@i", base.recvall(sock, 4))[0]
+        sock.sendall(struct.pack("<i", base.RPC_TRACKER_MAGIC))
+        magic = struct.unpack("<i", base.recvall(sock, 4))[0]
         assert magic == base.RPC_TRACKER_MAGIC
         base.sendjson(sock, [TrackerCode.STOP, self.stop_key])
         assert base.recvjson(sock) == TrackerCode.SUCCESS
diff --git a/src/runtime/file_util.cc b/src/runtime/file_util.cc
index cfd2d72f15a1..7606bf89cd92 100644
--- a/src/runtime/file_util.cc
+++ b/src/runtime/file_util.cc
@@ -4,6 +4,7 @@
  */
 #include <dmlc/json.h>
 #include <dmlc/logging.h>
+#include <tvm/runtime/serializer.h>
 #include <fstream>
 
 #include "./file_util.h"
diff --git a/src/runtime/graph/graph_runtime.cc b/src/runtime/graph/graph_runtime.cc
index ce1c51ba58a6..89d5e7a28258 100644
--- a/src/runtime/graph/graph_runtime.cc
+++ b/src/runtime/graph/graph_runtime.cc
@@ -4,6 +4,7 @@
  */
 #include <tvm/runtime/packed_func.h>
 #include <tvm/runtime/registry.h>
+#include <tvm/runtime/serializer.h>
 #include <dmlc/memory_io.h>
 #include <dmlc/json.h>
 #include <numeric>
@@ -397,24 +398,25 @@ class GraphRuntime : public ModuleNode {
 
 
 void GraphRuntime::LoadDLTensor(dmlc::Stream* strm, DLTensor* dst) {
+  // always use strm->Read to maintain endianness conversion
   uint64_t header, reserved;
-  CHECK(strm->Read(&header, sizeof(header)))
+  CHECK(strm->Read(&header))
       << "Invalid DLTensor file format";
-  CHECK(strm->Read(&reserved, sizeof(reserved)))
+  CHECK(strm->Read(&reserved))
       << "Invalid DLTensor file format";
   CHECK(header == kTVMNDArrayMagic)
       << "Invalid DLTensor file format";
 
   DLTensor tensor;
-  CHECK(strm->Read(&tensor.ctx, sizeof(tensor.ctx)))
+  CHECK(strm->Read(&(tensor.ctx)))
       << "Invalid DLTensor file format";
-  CHECK(strm->Read(&tensor.ndim, sizeof(tensor.ndim)))
+  CHECK(strm->Read(&(tensor.ndim)))
       << "Invalid DLTensor file format";
-  CHECK(strm->Read(&tensor.dtype, sizeof(tensor.dtype)))
+  CHECK(strm->Read(&(tensor.dtype)))
       << "Invalid DLTensor file format";
   std::vector<int64_t> shape(tensor.ndim);
   if (tensor.ndim != 0) {
-    CHECK(strm->Read(&shape[0], sizeof(int64_t) * tensor.ndim))
+    CHECK(strm->ReadArray(&shape[0], tensor.ndim))
         << "Invalid DLTensor file format";
   }
   CHECK_EQ(tensor.ndim, dst->ndim) << "param dimension mismatch";
@@ -425,18 +427,23 @@ void GraphRuntime::LoadDLTensor(dmlc::Stream* strm, DLTensor* dst) {
     CHECK_EQ(shape[i], dst->shape[i]) << "param shape mismatch";
   }
   size_t bits = dst->dtype.bits * dst->dtype.lanes;
-  size_t size = (bits + 7) / 8;
+  size_t elem_bytes = (bits + 7) / 8;
+  size_t num_elems = 1;
   for (int i = 0; i < dst->ndim; ++i) {
-    size *= dst->shape[i];
+    num_elems *= dst->shape[i];
   }
   uint64_t data_byte_size;
-  CHECK(strm->Read(&data_byte_size, sizeof(data_byte_size)))
+  CHECK(strm->Read(&data_byte_size))
       << "Invalid DLTensor file format";
-  CHECK(data_byte_size == size)
+  CHECK_EQ(data_byte_size, elem_bytes * num_elems)
       << "Invalid DLTensor file format";
   std::vector<uint8_t> bytes(data_byte_size + 1);
   CHECK(strm->Read(&bytes[0], data_byte_size))
       << "Invalid DLTensor file format";
+  // explicitly swap endian when necessary.
+  if (!DMLC_IO_NO_ENDIAN_SWAP) {
+    dmlc::ByteSwap(&bytes[0], elem_bytes, num_elems);
+  }
   TVM_CCALL(TVMArrayCopyFromBytes(dst, &bytes[0], data_byte_size));
 }
 
@@ -453,9 +460,8 @@ void GraphRuntime::LoadParams(dmlc::Stream* strm) {
   CHECK(strm->Read(&names))
       << "Invalid parameters file format";
   uint64_t sz;
-  strm->Read(&sz, sizeof(sz));
+  strm->Read(&sz);
   size_t size = static_cast<size_t>(sz);
-
   CHECK(size == names.size())
       << "Invalid parameters file format";
   for (size_t i = 0; i < size; ++i) {
diff --git a/src/runtime/rpc/rpc_session.cc b/src/runtime/rpc/rpc_session.cc
index 923194cf4f1b..d5eee1697961 100644
--- a/src/runtime/rpc/rpc_session.cc
+++ b/src/runtime/rpc/rpc_session.cc
@@ -6,6 +6,7 @@
 #include <tvm/runtime/packed_func.h>
 #include <tvm/runtime/device_api.h>
 #include <tvm/runtime/registry.h>
+#include <tvm/runtime/serializer.h>
 #include <memory>
 #include <array>
 #include <string>
@@ -44,7 +45,7 @@ struct RPCArgBuffer {
 };
 
 // Event handler for RPC events.
-class RPCSession::EventHandler {
+class RPCSession::EventHandler : public dmlc::Stream {
  public:
   EventHandler(common::RingBuffer* reader,
                common::RingBuffer* writer,
@@ -71,6 +72,15 @@ class RPCSession::EventHandler {
       return 0;
     }
   }
+  // Request number of bytes from reader.
+  void RequestBytes(size_t nbytes) {
+    pending_request_bytes_ += nbytes;
+    reader_->Reserve(pending_request_bytes_);
+  }
+  // Whether we are ready to handle next request.
+  bool Ready() {
+    return reader_->bytes_available() >= pending_request_bytes_;
+  }
   bool CanCleanShutdown() const {
     return state_ == kRecvCode;
   }
@@ -86,12 +96,12 @@ class RPCSession::EventHandler {
         case kInitHeader: HandleInitHeader(); break;
         case kRecvCode: HandleRecvCode(); break;
         case kRecvCallHandle: {
-          this->Read(&call_handle_, sizeof(call_handle_));
+          CHECK(this->Read(&call_handle_));
           this->SwitchToState(kRecvPackedSeqNumArgs);
           break;
         }
         case kRecvPackedSeqNumArgs: {
-          this->Read(&num_packed_args_, sizeof(num_packed_args_));
+          CHECK(this->Read(&num_packed_args_));
           arg_buf_.reset(new RPCArgBuffer());
           arg_buf_->value.resize(num_packed_args_);
           arg_buf_->tcode.resize(num_packed_args_);
@@ -100,7 +110,7 @@ class RPCSession::EventHandler {
         }
         case kRecvPackedSeqTypeCode: {
           if (num_packed_args_ != 0) {
-            this->Read(arg_buf_->tcode.data(), sizeof(int) * num_packed_args_);
+            this->ReadArray(arg_buf_->tcode.data(), num_packed_args_);
           }
           arg_index_ = 0;
           arg_recv_stage_ = 0;
@@ -164,8 +174,8 @@ class RPCSession::EventHandler {
   }
   // send Packed sequence to writer.
   void SendPackedSeq(const TVMValue* arg_values, const int* type_codes, int n) {
-    writer_->Write(&n, sizeof(n));
-    writer_->Write(type_codes, sizeof(int) * n);
+    this->Write(n);
+    this->WriteArray(type_codes, n);
     // Argument packing.
     for (int i = 0; i < n; ++i) {
       int tcode = type_codes[i];
@@ -173,14 +183,20 @@ class RPCSession::EventHandler {
       switch (tcode) {
         case kDLInt:
         case kDLUInt:
-        case kDLFloat:
+        case kDLFloat: {
+          this->Write<int64_t>(value.v_int64);
+          break;
+        }
         case kTVMType: {
-          writer_->Write(&value, sizeof(TVMValue));
+          this->Write(value.v_type);
+          // padding
+          int32_t padding = 0;
+          this->Write<int32_t>(padding);
           break;
         }
         case kTVMContext: {
           value.v_ctx = StripSessMask(value.v_ctx);
-          writer_->Write(&value, sizeof(TVMValue));
+          this->Write(value.v_ctx);
           break;
         }
         case kFuncHandle:
@@ -188,7 +204,7 @@ class RPCSession::EventHandler {
         case kHandle: {
           // always send handle in 64 bit.
           uint64_t handle = reinterpret_cast<uint64_t>(value.v_handle);
-          writer_->Write(&handle, sizeof(uint64_t));
+          this->Write(handle);
           break;
         }
         case kArrayHandle: {
@@ -196,11 +212,11 @@ class RPCSession::EventHandler {
           TVMContext ctx = StripSessMask(arr->ctx);
           uint64_t data = reinterpret_cast<uint64_t>(
               static_cast<RemoteSpace*>(arr->data)->data);
-          writer_->Write(&data, sizeof(uint64_t));
-          writer_->Write(&ctx, sizeof(ctx));
-          writer_->Write(&(arr->ndim), sizeof(int));
-          writer_->Write(&(arr->dtype), sizeof(DLDataType));
-          writer_->Write(arr->shape, sizeof(int64_t) * arr->ndim);
+          this->Write(data);
+          this->Write(ctx);
+          this->Write(arr->ndim);
+          this->Write(arr->dtype);
+          this->WriteArray(arr->shape, arr->ndim);
           CHECK(arr->strides == nullptr)
               << "Donot support strided remote array";
           CHECK_EQ(arr->byte_offset, 0)
@@ -211,15 +227,15 @@ class RPCSession::EventHandler {
         case kStr: {
           const char* s = value.v_str;
           uint64_t len = strlen(s);
-          writer_->Write(&len, sizeof(len));
-          writer_->Write(s, sizeof(char) * len);
+          this->Write(len);
+          this->WriteArray(s, len);
           break;
         }
         case kBytes: {
           TVMByteArray* bytes = static_cast<TVMByteArray*>(arg_values[i].v_handle);
           uint64_t len = bytes->size;
-          writer_->Write(&len, sizeof(len));
-          writer_->Write(bytes->data, sizeof(char) * len);
+          this->Write(len);
+          this->WriteArray(bytes->data, len);
           break;
         }
         default: {
@@ -230,6 +246,23 @@ class RPCSession::EventHandler {
     }
   }
 
+  // Endian aware IO handling
+  using Stream::Read;
+  using Stream::Write;
+  using Stream::ReadArray;
+  using Stream::WriteArray;
+
+  inline bool Read(RPCCode* code) {
+    int cdata;
+    if (!this->Read(&cdata)) return false;
+    *code = static_cast<RPCCode>(cdata);
+    return true;
+  }
+  inline void Write(RPCCode code) {
+    int cdata = static_cast<int>(code);
+    this->Write(cdata);
+  }
+
  protected:
   enum State {
     kInitHeader,
@@ -370,10 +403,22 @@ class RPCSession::EventHandler {
       switch (tcode) {
         case kDLInt:
         case kDLUInt:
-        case kDLFloat:
-        case kTVMType:
+        case kDLFloat: {
+          this->Read<int64_t>(&(value.v_int64));
+          ++arg_index_;
+          this->SwitchToState(kRecvPackedSeqArg);
+          break;
+        }
+        case kTVMType: {
+          this->Read(&(value.v_type));
+          int32_t padding = 0;
+          this->Read<int32_t>(&padding);
+          ++arg_index_;
+          this->SwitchToState(kRecvPackedSeqArg);
+          break;
+        }
         case kTVMContext: {
-          this->Read(&value, sizeof(TVMValue));
+          this->Read(&(value.v_ctx));
           ++arg_index_;
           this->SwitchToState(kRecvPackedSeqArg);
           break;
@@ -383,7 +428,7 @@ class RPCSession::EventHandler {
         case kHandle: {
           // always send handle in 64 bit.
           uint64_t handle;
-          this->Read(&handle, sizeof(handle));
+          this->Read(&handle);
           value.v_handle = reinterpret_cast<void*>(handle);
           ++arg_index_;
           this->SwitchToState(kRecvPackedSeqArg);
@@ -398,7 +443,7 @@ class RPCSession::EventHandler {
         case kStr:
         case kBytes: {
           uint64_t len;
-          this->Read(&len, sizeof(len));
+          this->Read(&len);
           temp_bytes_.reset( new RPCByteArrayBuffer());
           temp_bytes_->data.resize(len);
           arg_recv_stage_ = 1;
@@ -409,12 +454,12 @@ class RPCSession::EventHandler {
       case kArrayHandle: {
         temp_array_.reset(new RPCDataArrayBuffer());
         uint64_t handle;
-        this->Read(&handle, sizeof(handle));
+        this->Read(&handle);
         DLTensor& tensor = temp_array_->tensor;
         tensor.data = reinterpret_cast<void*>(handle);
-        this->Read(&(tensor.ctx), sizeof(TVMContext));
-        this->Read(&(tensor.ndim), sizeof(int));
-        this->Read(&(tensor.dtype), sizeof(DLDataType));
+        this->Read(&(tensor.ctx));
+        this->Read(&(tensor.ndim));
+        this->Read(&(tensor.dtype));
         temp_array_->shape.resize(tensor.ndim);
         tensor.shape = temp_array_->shape.data();
         arg_recv_stage_ = 1;
@@ -432,7 +477,7 @@ class RPCSession::EventHandler {
       CHECK_EQ(arg_recv_stage_, 1);
       if (tcode == kStr || tcode == kBytes) {
         if (temp_bytes_->data.size() != 0) {
-          this->Read(&(temp_bytes_->data[0]), temp_bytes_->data.size());
+          this->ReadArray(&(temp_bytes_->data[0]), temp_bytes_->data.size());
         }
         if (tcode == kStr) {
           value.v_str = temp_bytes_->data.c_str();
@@ -445,7 +490,7 @@ class RPCSession::EventHandler {
       } else {
         CHECK_EQ(tcode, kArrayHandle);
         DLTensor& tensor = temp_array_->tensor;
-        this->Read(tensor.shape, tensor.ndim * sizeof(int64_t));
+        this->ReadArray(tensor.shape, tensor.ndim);
         value.v_handle = &tensor;
         arg_buf_->temp_array.emplace_back(std::move(temp_array_));
       }
@@ -458,20 +503,20 @@ class RPCSession::EventHandler {
   void HandleInitHeader() {
     if (init_header_step_ == 0) {
       int32_t len;
-      this->Read(&len, sizeof(len));
+      this->Read(&len);
       remote_key_->resize(len);
       init_header_step_ = 1;
       this->RequestBytes(len);
       return;
     } else {
       CHECK_EQ(init_header_step_, 1);
-      this->Read(dmlc::BeginPtr(*remote_key_), remote_key_->length());
+      this->ReadArray(dmlc::BeginPtr(*remote_key_), remote_key_->length());
       this->SwitchToState(kRecvCode);
     }
   }
   // Handler for read code.
   void HandleRecvCode() {
-    this->Read(&code_, sizeof(code_));
+    this->Read(&code_);
     if (code_ > RPCCode::kSystemFuncStart) {
       SwitchToState(kRecvPackedSeqNumArgs);
       return;
@@ -511,14 +556,14 @@ class RPCSession::EventHandler {
   void HandleCopyFromRemote() {
     uint64_t handle, offset, size;
     TVMContext ctx;
-    this->Read(&handle, sizeof(handle));
-    this->Read(&offset, sizeof(offset));
-    this->Read(&size, sizeof(size));
-    this->Read(&ctx, sizeof(ctx));
+    this->Read(&handle);
+    this->Read(&offset);
+    this->Read(&size);
+    this->Read(&ctx);
     if (ctx.device_type == kDLCPU) {
       RPCCode code = RPCCode::kCopyAck;
-      writer_->Write(&code, sizeof(code));
-      writer_->Write(reinterpret_cast<char*>(handle) + offset, size);
+      this->Write(code);
+      this->WriteArray(reinterpret_cast<char*>(handle) + offset, size);
     } else {
       temp_data_.resize(size + 1);
       try {
@@ -530,11 +575,11 @@ class RPCSession::EventHandler {
             dmlc::BeginPtr(temp_data_), 0,
             size, ctx, cpu_ctx, nullptr);
         RPCCode code = RPCCode::kCopyAck;
-        writer_->Write(&code, sizeof(code));
-        writer_->Write(&temp_data_[0], size);
+        this->Write(code);
+        this->WriteArray(&temp_data_[0], size);
       } catch (const std::runtime_error &e) {
         RPCCode code = RPCCode::kException;
-        writer_->Write(&code, sizeof(code));
+        this->Write(code);
         TVMValue ret_value;
         ret_value.v_str = e.what();
         int ret_tcode = kStr;
@@ -548,10 +593,10 @@ class RPCSession::EventHandler {
     // use static variable to persist state.
     // This only works if next stage is immediately after this.
     if (arg_recv_stage_ == 0) {
-      this->Read(&copy_handle_, sizeof(uint64_t));
-      this->Read(&copy_offset_, sizeof(uint64_t));
-      this->Read(&copy_size_, sizeof(uint64_t));
-      this->Read(&copy_ctx_, sizeof(TVMContext));
+      CHECK(this->Read(&copy_handle_));
+      CHECK(this->Read(&copy_offset_));
+      CHECK(this->Read(&copy_size_));
+      CHECK(this->Read(&copy_ctx_));
       arg_recv_stage_ = 1;
       CHECK_EQ(pending_request_bytes_, 0U);
       this->RequestBytes(copy_size_);
@@ -563,11 +608,11 @@ class RPCSession::EventHandler {
       RPCCode code = RPCCode::kReturn;
       std::string errmsg;
       if (copy_ctx_.device_type == kDLCPU) {
-        this->Read(
+        this->ReadArray(
             reinterpret_cast<char*>(copy_handle_) + copy_offset_, copy_size_);
       } else {
         temp_data_.resize(copy_size_ + 1);
-        this->Read(&temp_data_[0], copy_size_);
+        this->ReadArray(&temp_data_[0], copy_size_);
         try {
           TVMContext cpu_ctx;
           cpu_ctx.device_type = kDLCPU;
@@ -583,7 +628,7 @@ class RPCSession::EventHandler {
           ret_tcode = kStr;
         }
       }
-      writer_->Write(&code, sizeof(code));
+      this->Write(code);
       SendPackedSeq(&ret_value, &ret_tcode, 1);
       arg_recv_stage_ = 0;
       this->SwitchToState(kRecvCode);
@@ -603,7 +648,7 @@ class RPCSession::EventHandler {
       std::unique_ptr<RPCArgBuffer> args = std::move(arg_buf_);
       f(args->AsTVMArgs(), &rv);
       RPCCode code = RPCCode::kReturn;
-      writer_->Write(&code, sizeof(code));
+      this->Write(code);
       if (rv.type_code() == kStr) {
         ret_value.v_str = rv.ptr<std::string>()->c_str();
         ret_tcode = kStr;
@@ -630,7 +675,7 @@ class RPCSession::EventHandler {
       }
     } catch (const std::runtime_error& e) {
       RPCCode code = RPCCode::kException;
-      writer_->Write(&code, sizeof(code));
+      this->Write(code);
       ret_value.v_str = e.what();
       ret_tcode = kStr;
       SendPackedSeq(&ret_value, &ret_tcode, 1);
@@ -640,19 +685,14 @@ class RPCSession::EventHandler {
  private:
   // Utility functions
   // Internal read function, update pending_request_bytes_
-  void Read(void* data, size_t size) {
+  size_t Read(void* data, size_t size) final {
     CHECK_LE(size, pending_request_bytes_);
     reader_->Read(data, size);
     pending_request_bytes_ -= size;
+    return size;
   }
-  // Request number of bytes from reader.
-  void RequestBytes(size_t nbytes) {
-    pending_request_bytes_ += nbytes;
-    reader_->Reserve(pending_request_bytes_);
-  }
-  // Whether we are ready to handle next request.
-  bool Ready() {
-    return reader_->bytes_available() >= pending_request_bytes_;
+  void Write(const void* data, size_t size) final {
+    writer_->Write(data, size);
   }
   // Number of pending bytes requests
   size_t pending_request_bytes_;
@@ -766,7 +806,7 @@ RPCSession::~RPCSession() {
 void RPCSession::Shutdown() {
   if (channel_ != nullptr) {
     RPCCode code = RPCCode::kShutdown;
-    writer_.Write(&code, sizeof(code));
+    handler_->Write(code);
     // flush all writing buffer to output channel.
     try {
       while (writer_.bytes_available() != 0) {
@@ -788,7 +828,6 @@ void RPCSession::ServerLoop() {
   }
   TVMRetValue rv;
   CHECK(HandleUntilReturnEvent(&rv, false, nullptr) == RPCCode::kShutdown);
-  LOG(INFO) << "Shutdown...";
   if (const auto* f = Registry::Get("tvm.contrib.rpc.server.shutdown")) {
     (*f)();
   }
@@ -821,9 +860,9 @@ void RPCSession::CallFunc(void* h,
                           const PackedFunc* fwrap) {
   std::lock_guard<std::recursive_mutex> lock(mutex_);
   RPCCode code = RPCCode::kCallFunc;
-  writer_.Write(&code, sizeof(code));
+  handler_->Write(code);
   uint64_t handle = reinterpret_cast<uint64_t>(h);
-  writer_.Write(&handle, sizeof(handle));
+  handler_->Write(handle);
   handler_->SendPackedSeq(args.values, args.type_codes, args.num_args);
   code = HandleUntilReturnEvent(rv, true, fwrap);
   CHECK(code == RPCCode::kReturn) << "code=" << static_cast<int>(code);
@@ -838,15 +877,15 @@ void RPCSession::CopyToRemote(void* from,
   std::lock_guard<std::recursive_mutex> lock(mutex_);
   ctx_to = handler_->StripSessMask(ctx_to);
   RPCCode code = RPCCode::kCopyToRemote;
-  writer_.Write(&code, sizeof(code));
+  handler_->Write(code);
   uint64_t handle = reinterpret_cast<uint64_t>(to);
-  writer_.Write(&handle, sizeof(handle));
+  handler_->Write(handle);
   uint64_t offset = static_cast<uint64_t>(to_offset);
-  writer_.Write(&offset, sizeof(offset));
+  handler_->Write(offset);
   uint64_t size = static_cast<uint64_t>(data_size);
-  writer_.Write(&size, sizeof(size));
-  writer_.Write(&ctx_to, sizeof(ctx_to));
-  writer_.Write(reinterpret_cast<char*>(from) + from_offset, data_size);
+  handler_->Write(size);
+  handler_->Write(ctx_to);
+  handler_->WriteArray(reinterpret_cast<char*>(from) + from_offset, data_size);
   TVMRetValue rv;
   CHECK(HandleUntilReturnEvent(&rv, true, nullptr) == RPCCode::kReturn);
 }
@@ -860,26 +899,27 @@ void RPCSession::CopyFromRemote(void* from,
   std::lock_guard<std::recursive_mutex> lock(mutex_);
   ctx_from = handler_->StripSessMask(ctx_from);
   RPCCode code = RPCCode::kCopyFromRemote;
-  writer_.Write(&code, sizeof(code));
+  handler_->Write(code);
   uint64_t handle = reinterpret_cast<uint64_t>(from);
-  writer_.Write(&handle, sizeof(handle));
+  handler_->Write(handle);
   uint64_t offset = static_cast<uint64_t>(from_offset);
-  writer_.Write(&offset, sizeof(offset));
+  handler_->Write(offset);
   uint64_t size = static_cast<uint64_t>(data_size);
-  writer_.Write(&size, sizeof(size));
-  writer_.Write(&ctx_from, sizeof(ctx_from));
+  handler_->Write(size);
+  handler_->Write(ctx_from);
   TVMRetValue rv;
   CHECK(HandleUntilReturnEvent(&rv, true, nullptr) == RPCCode::kCopyAck);
   reader_.Reserve(data_size);
-  while (reader_.bytes_available() < data_size) {
-    size_t bytes_needed = data_size - reader_.bytes_available();
+  handler_->RequestBytes(data_size);
+  while (!handler_->Ready()) {
+    size_t bytes_needed = handler_->BytesNeeded();
     reader_.WriteWithCallback([this](void* data, size_t size) {
         size_t n = channel_->Recv(data, size);
         CHECK_NE(n, 0U) << "Channel closes before we get neded bytes";
         return n;
       }, bytes_needed);
   }
-  reader_.Read(reinterpret_cast<char*>(to) + to_offset, data_size);
+  handler_->ReadArray(reinterpret_cast<char*>(to) + to_offset, data_size);
   handler_->FinishCopyAck();
 }
 

From 5eac687a9be2e80d25fc5d1ed38db5d40506b011 Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Wed, 30 May 2018 20:41:30 -0700
Subject: [PATCH 673/949] [RUNTIME] update to make runtime copy type aware

---
 include/tvm/runtime/device_api.h              |  7 ++-
 nnvm/tests/python/compiler/test_param_dict.py | 44 ++++++++++++++
 src/codegen/verilog/vpi_device_api.cc         |  1 +
 src/runtime/c_runtime_api.cc                  |  6 +-
 src/runtime/cpu_device_api.cc                 |  1 +
 src/runtime/cuda/cuda_device_api.cc           |  1 +
 src/runtime/metal/metal_common.h              |  1 +
 src/runtime/metal/metal_device_api.mm         |  1 +
 src/runtime/opencl/opencl_common.h            |  1 +
 src/runtime/opencl/opencl_device_api.cc       |  1 +
 src/runtime/opengl/opengl_device_api.cc       |  1 +
 src/runtime/rocm/rocm_device_api.cc           |  1 +
 src/runtime/rpc/rpc_device_api.cc             |  8 +--
 src/runtime/rpc/rpc_session.cc                | 58 ++++++++++++++-----
 src/runtime/rpc/rpc_session.h                 | 16 +++--
 src/runtime/vulkan/vulkan_common.h            |  1 +
 src/runtime/vulkan/vulkan_device_api.cc       |  1 +
 tests/python/unittest/test_runtime_rpc.py     | 33 +++++++++++
 18 files changed, 155 insertions(+), 28 deletions(-)

diff --git a/include/tvm/runtime/device_api.h b/include/tvm/runtime/device_api.h
index 334d3e61b2b6..82128fc58d47 100644
--- a/include/tvm/runtime/device_api.h
+++ b/include/tvm/runtime/device_api.h
@@ -81,18 +81,21 @@ class DeviceAPI {
    * \param from_offset The byte offeset in the from.
    * \param to The target array.
    * \param to_offset The byte offset in the to.
-   * \param size The size of the memory
+   * \param num_bytes The size of the memory in bytes
    * \param ctx_from The source context
    * \param ctx_to The target context
+   * \param type_hint The type of elements, only neded by certain backends.
+   *                  can be useful for cross device endian converison.
    * \param stream Optional stream object.
    */
   virtual void CopyDataFromTo(const void* from,
                               size_t from_offset,
                               void* to,
                               size_t to_offset,
-                              size_t size,
+                              size_t num_bytes,
                               TVMContext ctx_from,
                               TVMContext ctx_to,
+                              TVMType type_hint,
                               TVMStreamHandle stream) = 0;
     /*!
    * \brief Create a new stream of execution.
diff --git a/nnvm/tests/python/compiler/test_param_dict.py b/nnvm/tests/python/compiler/test_param_dict.py
index 667ae4e48cec..eb547373e38f 100644
--- a/nnvm/tests/python/compiler/test_param_dict.py
+++ b/nnvm/tests/python/compiler/test_param_dict.py
@@ -1,5 +1,9 @@
+import os
 import numpy as np
 import nnvm.compiler
+import tvm
+from tvm.contrib import rpc, util, graph_runtime
+
 
 def test_save_load():
     x = np.random.uniform(size=(10, 2)).astype("float32")
@@ -15,5 +19,45 @@ def test_save_load():
     np.testing.assert_equal(param2["y"].asnumpy(), y)
 
 
+def test_bigendian_rpc_param():
+    """Test big endian rpc when there is a PowerPC RPC server available"""
+    host = os.environ.get("TVM_POWERPC_TEST_HOST", None)
+    port = os.environ.get("TVM_POWERPC_TEST_PORT", 9090)
+    if host is None:
+        return
+
+    def verify_nnvm(remote, target, shape, dtype):
+        x = nnvm.sym.Variable("x")
+        y = x + 1
+        graph, lib, _ = nnvm.compiler.build(
+            y, target,
+            shape={"x": shape},
+        dtype={"x": dtype})
+
+        temp = util.tempdir()
+        path_dso = temp.relpath("dev_lib.o")
+        lib.save(path_dso)
+        remote.upload(path_dso)
+        lib = remote.load_module("dev_lib.o")
+        a = np.random.randint(0, 256, size=shape).astype(dtype)
+        a[:] = 1
+        params = {"x" : a}
+        ctx = remote.cpu(0)
+        m = graph_runtime.create(graph, lib, ctx)
+        # uses save param_dict
+        m.load_params(nnvm.compiler.save_param_dict(params))
+        m.run()
+        out = m.get_output(0, tvm.nd.empty(shape, dtype=dtype, ctx=ctx))
+        np.testing.assert_allclose(a + 1, out.asnumpy())
+
+    print("Test RPC connection to PowerPC...")
+    remote = rpc.connect(host, port)
+    target = "llvm -mtriple=powerpc-linux-gnu"
+    for dtype in ["float32", "float64", "int32", "int8"]:
+        verify_nnvm(remote, target, (10,), dtype)
+
+
+
 if __name__ == "__main__":
     test_save_load()
+    test_bigendian_rpc_param()
diff --git a/src/codegen/verilog/vpi_device_api.cc b/src/codegen/verilog/vpi_device_api.cc
index 8efd65785547..d53a12962fd7 100644
--- a/src/codegen/verilog/vpi_device_api.cc
+++ b/src/codegen/verilog/vpi_device_api.cc
@@ -93,6 +93,7 @@ class VPIDeviceAPI final : public runtime::DeviceAPI {
                       size_t size,
                       TVMContext ctx_from,
                       TVMContext ctx_to,
+                      TVMType type_hint,
                       TVMStreamHandle stream) final {
     if (static_cast<int>(ctx_from.device_type) == kDLVPI) {
       from = RealAddr(static_cast<const char*>(from) + from_offset, size);
diff --git a/src/runtime/c_runtime_api.cc b/src/runtime/c_runtime_api.cc
index 5c4ac57fefa1..df3a19f306e0 100644
--- a/src/runtime/c_runtime_api.cc
+++ b/src/runtime/c_runtime_api.cc
@@ -434,7 +434,7 @@ int TVMArrayCopyFromTo(TVMArrayHandle from,
   DeviceAPIManager::Get(ctx)->CopyDataFromTo(
     from->data, static_cast<size_t>(from->byte_offset),
     to->data, static_cast<size_t>(to->byte_offset),
-    from_size, from->ctx, to->ctx, stream);
+    from_size, from->ctx, to->ctx, from->dtype, stream);
 
   API_END();
 }
@@ -452,7 +452,7 @@ int TVMArrayCopyFromBytes(TVMArrayHandle handle,
   DeviceAPIManager::Get(handle->ctx)->CopyDataFromTo(
       data, 0,
       handle->data, static_cast<size_t>(handle->byte_offset),
-      nbytes, cpu_ctx, handle->ctx, nullptr);
+      nbytes, cpu_ctx, handle->ctx, handle->dtype, nullptr);
   API_END();
 }
 
@@ -469,7 +469,7 @@ int TVMArrayCopyToBytes(TVMArrayHandle handle,
   DeviceAPIManager::Get(handle->ctx)->CopyDataFromTo(
       handle->data, static_cast<size_t>(handle->byte_offset),
       data, 0,
-      nbytes, handle->ctx, cpu_ctx, nullptr);
+      nbytes, handle->ctx, cpu_ctx, handle->dtype, nullptr);
   API_END();
 }
 
diff --git a/src/runtime/cpu_device_api.cc b/src/runtime/cpu_device_api.cc
index 8199988f3419..e3434e01813e 100644
--- a/src/runtime/cpu_device_api.cc
+++ b/src/runtime/cpu_device_api.cc
@@ -53,6 +53,7 @@ class CPUDeviceAPI final : public DeviceAPI {
                       size_t size,
                       TVMContext ctx_from,
                       TVMContext ctx_to,
+                      TVMType type_hint,
                       TVMStreamHandle stream) final {
     memcpy(static_cast<char*>(to) + to_offset,
            static_cast<const char*>(from) + from_offset,
diff --git a/src/runtime/cuda/cuda_device_api.cc b/src/runtime/cuda/cuda_device_api.cc
index 3f697faab72c..9aeca50584d5 100644
--- a/src/runtime/cuda/cuda_device_api.cc
+++ b/src/runtime/cuda/cuda_device_api.cc
@@ -99,6 +99,7 @@ class CUDADeviceAPI final : public DeviceAPI {
                       size_t size,
                       TVMContext ctx_from,
                       TVMContext ctx_to,
+                      TVMType type_hint,
                       TVMStreamHandle stream) final {
     cudaStream_t cu_stream = static_cast<cudaStream_t>(stream);
     from = static_cast<const char*>(from) + from_offset;
diff --git a/src/runtime/metal/metal_common.h b/src/runtime/metal/metal_common.h
index fa73b8250c33..2f75407f95ce 100644
--- a/src/runtime/metal/metal_common.h
+++ b/src/runtime/metal/metal_common.h
@@ -75,6 +75,7 @@ class MetalWorkspace final : public DeviceAPI {
                       size_t size,
                       TVMContext ctx_from,
                       TVMContext ctx_to,
+                      TVMType type_hint,
                       TVMStreamHandle stream) final;
   void StreamSync(TVMContext ctx, TVMStreamHandle stream) final;
   void* AllocWorkspace(TVMContext ctx, size_t size, TVMType type_hint) final;
diff --git a/src/runtime/metal/metal_device_api.mm b/src/runtime/metal/metal_device_api.mm
index 6d225ea7f888..dcde6945a692 100644
--- a/src/runtime/metal/metal_device_api.mm
+++ b/src/runtime/metal/metal_device_api.mm
@@ -158,6 +158,7 @@ int GetWarpSize(id<MTLDevice> dev) {
                                     size_t size,
                                     TVMContext ctx_from,
                                     TVMContext ctx_to,
+                                    TVMType type_hint,
                                     TVMStreamHandle stream) {
   this->Init();
   CHECK(stream == nullptr);
diff --git a/src/runtime/opencl/opencl_common.h b/src/runtime/opencl/opencl_common.h
index 67934a078665..a97efe408f33 100644
--- a/src/runtime/opencl/opencl_common.h
+++ b/src/runtime/opencl/opencl_common.h
@@ -154,6 +154,7 @@ class OpenCLWorkspace final : public DeviceAPI {
                       size_t size,
                       TVMContext ctx_from,
                       TVMContext ctx_to,
+                      TVMType type_hint,
                       TVMStreamHandle stream) final;
   void StreamSync(TVMContext ctx, TVMStreamHandle stream) final;
   void* AllocWorkspace(TVMContext ctx, size_t size, TVMType type_hint) final;
diff --git a/src/runtime/opencl/opencl_device_api.cc b/src/runtime/opencl/opencl_device_api.cc
index da527c76134d..07a82d34fd34 100644
--- a/src/runtime/opencl/opencl_device_api.cc
+++ b/src/runtime/opencl/opencl_device_api.cc
@@ -110,6 +110,7 @@ void OpenCLWorkspace::CopyDataFromTo(const void* from,
                                      size_t size,
                                      TVMContext ctx_from,
                                      TVMContext ctx_to,
+                                     TVMType type_hint,
                                      TVMStreamHandle stream) {
   this->Init();
   CHECK(stream == nullptr);
diff --git a/src/runtime/opengl/opengl_device_api.cc b/src/runtime/opengl/opengl_device_api.cc
index e925f863df9d..b45ee5079f19 100644
--- a/src/runtime/opengl/opengl_device_api.cc
+++ b/src/runtime/opengl/opengl_device_api.cc
@@ -119,6 +119,7 @@ void OpenGLWorkspace::CopyDataFromTo(const void* from,
                                      size_t size,
                                      TVMContext ctx_from,
                                      TVMContext ctx_to,
+                                     TVMType type_hint,
                                      TVMStreamHandle stream) {
   CHECK(stream == nullptr);
 
diff --git a/src/runtime/rocm/rocm_device_api.cc b/src/runtime/rocm/rocm_device_api.cc
index 55272561c739..47b9e8da7e2e 100644
--- a/src/runtime/rocm/rocm_device_api.cc
+++ b/src/runtime/rocm/rocm_device_api.cc
@@ -81,6 +81,7 @@ class ROCMDeviceAPI final : public DeviceAPI {
                       size_t size,
                       TVMContext ctx_from,
                       TVMContext ctx_to,
+                      TVMType type_hint,
                       TVMStreamHandle stream) final {
     hipStream_t hip_stream = static_cast<hipStream_t>(stream);
     from = static_cast<const char*>(from) + from_offset;
diff --git a/src/runtime/rpc/rpc_device_api.cc b/src/runtime/rpc/rpc_device_api.cc
index 1eff8e7466d3..5740a393c253 100644
--- a/src/runtime/rpc/rpc_device_api.cc
+++ b/src/runtime/rpc/rpc_device_api.cc
@@ -49,6 +49,7 @@ class RPCDeviceAPI final : public DeviceAPI {
                       size_t size,
                       TVMContext ctx_from,
                       TVMContext ctx_to,
+                      TVMType type_hint,
                       TVMStreamHandle stream) final {
     int from_dev_type = ctx_from.device_type;
     int to_dev_type = ctx_to.device_type;
@@ -60,19 +61,18 @@ class RPCDeviceAPI final : public DeviceAPI {
           RPCCode::kCopyAmongRemote,
           static_cast<const RemoteSpace*>(from)->data, from_offset,
           static_cast<const RemoteSpace*>(to)->data, to_offset,
-          size,  ctx_from, ctx_to, stream);
+          size,  ctx_from, ctx_to, type_hint, stream);
     } else if (from_dev_type > kRPCSessMask &&
                to_dev_type == kDLCPU) {
       GetSess(ctx_from)->CopyFromRemote(
           static_cast<const RemoteSpace*>(from)->data, from_offset,
-          to, to_offset, size,
-          ctx_from);
+          to, to_offset, size, ctx_from, type_hint);
     } else if (from_dev_type == kDLCPU &&
                to_dev_type > kRPCSessMask) {
       GetSess(ctx_to)->CopyToRemote(
           (void*)from, from_offset,  // NOLINT(*)
           static_cast<const RemoteSpace*>(to)->data, to_offset,
-          size, ctx_to);
+          size, ctx_to, type_hint);
     } else {
       LOG(FATAL) << "expect copy from/to remote or between remote";
     }
diff --git a/src/runtime/rpc/rpc_session.cc b/src/runtime/rpc/rpc_session.cc
index d5eee1697961..4d38fdc0158b 100644
--- a/src/runtime/rpc/rpc_session.cc
+++ b/src/runtime/rpc/rpc_session.cc
@@ -303,6 +303,7 @@ class RPCSession::EventHandler : public dmlc::Stream {
   std::string temp_data_;
   // Temp variables for copy request state.
   TVMContext copy_ctx_;
+  TVMType copy_dtype_;
   uint64_t copy_handle_, copy_offset_, copy_size_;
   // State switcher
   void SwitchToState(State state) {
@@ -344,11 +345,13 @@ class RPCSession::EventHandler : public dmlc::Stream {
       case kDoCopyFromRemote: {
         this->RequestBytes(sizeof(uint64_t) * 3);
         this->RequestBytes(sizeof(TVMContext));
+        this->RequestBytes(sizeof(TVMType));
         break;
       }
       case kDoCopyToRemote: {
         this->RequestBytes(sizeof(uint64_t) * 3);
         this->RequestBytes(sizeof(TVMContext));
+        this->RequestBytes(sizeof(TVMType));
         break;
       }
       case kCopyAckReceived:
@@ -554,18 +557,30 @@ class RPCSession::EventHandler : public dmlc::Stream {
   }
 
   void HandleCopyFromRemote() {
-    uint64_t handle, offset, size;
+    uint64_t handle, offset, num_bytes;
     TVMContext ctx;
+    TVMType type_hint;
     this->Read(&handle);
     this->Read(&offset);
-    this->Read(&size);
+    this->Read(&num_bytes);
     this->Read(&ctx);
+    this->Read(&type_hint);
+    size_t elem_bytes = (type_hint.bits * type_hint.lanes + 7) / 8;
+
     if (ctx.device_type == kDLCPU) {
       RPCCode code = RPCCode::kCopyAck;
       this->Write(code);
-      this->WriteArray(reinterpret_cast<char*>(handle) + offset, size);
+      char* dptr = reinterpret_cast<char*>(handle) + offset;
+      if (!DMLC_IO_NO_ENDIAN_SWAP) {
+        temp_data_.resize(0);
+        temp_data_.insert(temp_data_.end(), dptr, dptr + num_bytes);
+        dmlc::ByteSwap(dmlc::BeginPtr(temp_data_), elem_bytes, num_bytes / elem_bytes);
+        this->WriteArray(temp_data_.data(), num_bytes);
+      } else {
+        this->WriteArray(dptr, num_bytes);
+      }
     } else {
-      temp_data_.resize(size + 1);
+      temp_data_.resize(num_bytes + 1);
       try {
         TVMContext cpu_ctx;
         cpu_ctx.device_type = kDLCPU;
@@ -573,10 +588,13 @@ class RPCSession::EventHandler : public dmlc::Stream {
         DeviceAPI::Get(ctx)->CopyDataFromTo(
             reinterpret_cast<void*>(handle), offset,
             dmlc::BeginPtr(temp_data_), 0,
-            size, ctx, cpu_ctx, nullptr);
+            num_bytes, ctx, cpu_ctx, type_hint, nullptr);
         RPCCode code = RPCCode::kCopyAck;
         this->Write(code);
-        this->WriteArray(&temp_data_[0], size);
+        if (!DMLC_IO_NO_ENDIAN_SWAP) {
+          dmlc::ByteSwap(dmlc::BeginPtr(temp_data_), elem_bytes, num_bytes / elem_bytes);
+        }
+        this->WriteArray(&temp_data_[0], num_bytes);
       } catch (const std::runtime_error &e) {
         RPCCode code = RPCCode::kException;
         this->Write(code);
@@ -597,6 +615,7 @@ class RPCSession::EventHandler : public dmlc::Stream {
       CHECK(this->Read(&copy_offset_));
       CHECK(this->Read(&copy_size_));
       CHECK(this->Read(&copy_ctx_));
+      CHECK(this->Read(&copy_dtype_));
       arg_recv_stage_ = 1;
       CHECK_EQ(pending_request_bytes_, 0U);
       this->RequestBytes(copy_size_);
@@ -607,12 +626,20 @@ class RPCSession::EventHandler : public dmlc::Stream {
       int ret_tcode = kNull;
       RPCCode code = RPCCode::kReturn;
       std::string errmsg;
+
+      size_t elem_bytes = (copy_dtype_.bits * copy_dtype_.lanes + 7) / 8;
       if (copy_ctx_.device_type == kDLCPU) {
-        this->ReadArray(
-            reinterpret_cast<char*>(copy_handle_) + copy_offset_, copy_size_);
+        char* dptr = reinterpret_cast<char*>(copy_handle_) + copy_offset_;
+        this->ReadArray(dptr, copy_size_);
+        if (!DMLC_IO_NO_ENDIAN_SWAP) {
+          dmlc::ByteSwap(dptr, elem_bytes, copy_size_ / elem_bytes);
+        }
       } else {
         temp_data_.resize(copy_size_ + 1);
         this->ReadArray(&temp_data_[0], copy_size_);
+        if (!DMLC_IO_NO_ENDIAN_SWAP) {
+          dmlc::ByteSwap(dmlc::BeginPtr(temp_data_), elem_bytes, copy_size_ / elem_bytes);
+        }
         try {
           TVMContext cpu_ctx;
           cpu_ctx.device_type = kDLCPU;
@@ -620,7 +647,7 @@ class RPCSession::EventHandler : public dmlc::Stream {
           DeviceAPI::Get(copy_ctx_)->CopyDataFromTo(
               temp_data_.data(), 0,
               reinterpret_cast<void*>(copy_handle_), copy_offset_,
-              copy_size_, cpu_ctx, copy_ctx_, nullptr);
+              copy_size_, cpu_ctx, copy_ctx_, copy_dtype_, nullptr);
         } catch (const std::runtime_error &e) {
           code = RPCCode::kException;
           errmsg = e.what();
@@ -873,7 +900,8 @@ void RPCSession::CopyToRemote(void* from,
                               void* to,
                               size_t to_offset,
                               size_t data_size,
-                              TVMContext ctx_to) {
+                              TVMContext ctx_to,
+                              TVMType type_hint) {
   std::lock_guard<std::recursive_mutex> lock(mutex_);
   ctx_to = handler_->StripSessMask(ctx_to);
   RPCCode code = RPCCode::kCopyToRemote;
@@ -885,6 +913,7 @@ void RPCSession::CopyToRemote(void* from,
   uint64_t size = static_cast<uint64_t>(data_size);
   handler_->Write(size);
   handler_->Write(ctx_to);
+  handler_->Write(type_hint);
   handler_->WriteArray(reinterpret_cast<char*>(from) + from_offset, data_size);
   TVMRetValue rv;
   CHECK(HandleUntilReturnEvent(&rv, true, nullptr) == RPCCode::kReturn);
@@ -895,7 +924,8 @@ void RPCSession::CopyFromRemote(void* from,
                                 void* to,
                                 size_t to_offset,
                                 size_t data_size,
-                                TVMContext ctx_from) {
+                                TVMContext ctx_from,
+                                TVMType type_hint) {
   std::lock_guard<std::recursive_mutex> lock(mutex_);
   ctx_from = handler_->StripSessMask(ctx_from);
   RPCCode code = RPCCode::kCopyFromRemote;
@@ -907,6 +937,7 @@ void RPCSession::CopyFromRemote(void* from,
   uint64_t size = static_cast<uint64_t>(data_size);
   handler_->Write(size);
   handler_->Write(ctx_from);
+  handler_->Write(type_hint);
   TVMRetValue rv;
   CHECK(HandleUntilReturnEvent(&rv, true, nullptr) == RPCCode::kCopyAck);
   reader_.Reserve(data_size);
@@ -996,7 +1027,8 @@ void RPCCopyAmongRemote(TVMArgs args, TVMRetValue *rv) {
   uint64_t size = args[4];
   TVMContext ctx_from = args[5];
   TVMContext ctx_to = args[6];
-  TVMStreamHandle stream = args[7];
+  TVMType type_hint = args[7];
+  TVMStreamHandle stream = args[8];
   TVMContext ctx = ctx_from;
   if (ctx.device_type == kDLCPU) {
     ctx = ctx_to;
@@ -1008,7 +1040,7 @@ void RPCCopyAmongRemote(TVMArgs args, TVMRetValue *rv) {
   DeviceAPI::Get(ctx)->CopyDataFromTo(
       from, from_offset,
       to, to_offset,
-      size, ctx_from, ctx_to, stream);
+      size, ctx_from, ctx_to, type_hint, stream);
 }
 
 void RPCModuleLoad(TVMArgs args, TVMRetValue *rv) {
diff --git a/src/runtime/rpc/rpc_session.h b/src/runtime/rpc/rpc_session.h
index 5fe453924534..68f6763ae6db 100644
--- a/src/runtime/rpc/rpc_session.h
+++ b/src/runtime/rpc/rpc_session.h
@@ -116,30 +116,34 @@ class RPCSession {
    * \param from_offset The byte offeset in the from.
    * \param to The target array.
    * \param to_offset The byte offset in the to.
-   * \param size The size of the memory.
+   * \param nbytes The size of the memory in bytes.
    * \param ctx_to The target context.
+   * \param type_hint Hint of content data type.
    */
   void CopyToRemote(void* from,
                     size_t from_offset,
                     void* to,
                     size_t to_offset,
-                    size_t size,
-                    TVMContext ctx_to);
+                    size_t nbytes,
+                    TVMContext ctx_to,
+                    TVMType type_hint);
   /*!
    * \brief Copy bytes from remote array content.
    * \param from The source host data.
    * \param from_offset The byte offeset in the from.
    * \param to The target array.
    * \param to_offset The byte offset in the to.
-   * \param size The size of the memory.
+   * \param nbytes The size of the memory in bytes.
    * \param ctx_from The source context.
+   * \param type_hint Hint of content data type.
    */
   void CopyFromRemote(void* from,
                       size_t from_offset,
                       void* to,
                       size_t to_offset,
-                      size_t size,
-                      TVMContext ctx_from);
+                      size_t nbytes,
+                      TVMContext ctx_from,
+                      TVMType type_hint);
   /*!
    * \brief Get a remote timer function on ctx.
    *  This function consumes fhandle, caller should not call Free on fhandle.
diff --git a/src/runtime/vulkan/vulkan_common.h b/src/runtime/vulkan/vulkan_common.h
index 49d60925e0ba..0d02c0aaadcf 100644
--- a/src/runtime/vulkan/vulkan_common.h
+++ b/src/runtime/vulkan/vulkan_common.h
@@ -141,6 +141,7 @@ class VulkanWorkspace final : public DeviceAPI {
                       size_t size,
                       TVMContext ctx_from,
                       TVMContext ctx_to,
+                      TVMType type_hint,
                       TVMStreamHandle stream) final;
   void StreamSync(TVMContext ctx, TVMStreamHandle stream) final;
   void* AllocWorkspace(TVMContext ctx, size_t size, TVMType type_hint) final;
diff --git a/src/runtime/vulkan/vulkan_device_api.cc b/src/runtime/vulkan/vulkan_device_api.cc
index a3e4fc294e60..2abb0c277419 100644
--- a/src/runtime/vulkan/vulkan_device_api.cc
+++ b/src/runtime/vulkan/vulkan_device_api.cc
@@ -131,6 +131,7 @@ void VulkanWorkspace::CopyDataFromTo(const void* from,
                                      size_t size,
                                      TVMContext ctx_from,
                                      TVMContext ctx_to,
+                                     TVMType type_hint,
                                      TVMStreamHandle stream) {
   this->Init();
   CHECK(stream == nullptr);
diff --git a/tests/python/unittest/test_runtime_rpc.py b/tests/python/unittest/test_runtime_rpc.py
index 581890efe64f..a609f313909c 100644
--- a/tests/python/unittest/test_runtime_rpc.py
+++ b/tests/python/unittest/test_runtime_rpc.py
@@ -1,9 +1,41 @@
 import tvm
+import os
 import logging
 import numpy as np
 import time
 from tvm.contrib import rpc, util
 
+
+def test_bigendian_rpc():
+    """Test big endian rpc when there is a PowerPC RPC server available"""
+    host = os.environ.get("TVM_POWERPC_TEST_HOST", None)
+    port = os.environ.get("TVM_POWERPC_TEST_PORT", 9090)
+    if host is None:
+        return
+    def verify_rpc(remote, target, shape, dtype):
+        A = tvm.placeholder(shape, dtype=dtype)
+        B = tvm.compute(A.shape, lambda i: A[i]+tvm.const(1, A.dtype))
+        s = tvm.create_schedule(B.op)
+        f = tvm.build(s, [A, B], target, name="myadd")
+
+        ctx = remote.cpu(0)
+        a = tvm.nd.array(np.random.randint(0, 256, size=shape).astype(A.dtype), ctx=ctx)
+        b = tvm.nd.array(np.zeros(shape).astype(A.dtype), ctx=ctx)
+        temp = util.tempdir()
+        path_dso = temp.relpath("dev_lib.o")
+        f.save(path_dso)
+        remote.upload(path_dso)
+        f = remote.load_module("dev_lib.o")
+        f(a, b)
+        np.testing.assert_allclose(a.asnumpy() + 1, b.asnumpy())
+
+    print("Test RPC connection to PowerPC...")
+    remote = rpc.connect(host, port)
+    target = "llvm -mtriple=powerpc-linux-gnu"
+    for dtype in ["float32", "float64", "int32", "int8"]:
+        verify_rpc(remote, target, (10,), dtype)
+
+
 def test_rpc_simple():
     if not tvm.module.enabled("rpc"):
         return
@@ -166,6 +198,7 @@ def addone(x):
 
 if __name__ == "__main__":
     logging.basicConfig(level=logging.INFO)
+    test_bigendian_rpc()
     test_rpc_remote_module()
     test_rpc_return_func()
     test_rpc_file_exchange()

From 61debb2318f7305991727acfcf4a631939228df4 Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Wed, 30 May 2018 21:51:23 -0700
Subject: [PATCH 674/949] fix opengl runtime

---
 src/runtime/opengl/opengl_common.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/runtime/opengl/opengl_common.h b/src/runtime/opengl/opengl_common.h
index 0d247edfade4..252639f3af27 100644
--- a/src/runtime/opengl/opengl_common.h
+++ b/src/runtime/opengl/opengl_common.h
@@ -175,6 +175,7 @@ class OpenGLWorkspace final : public DeviceAPI {
                       size_t size,
                       TVMContext ctx_from,
                       TVMContext ctx_to,
+                      TVMType type_hint,
                       TVMStreamHandle stream) final;
   void StreamSync(TVMContext ctx, TVMStreamHandle stream) final;
 

From 6099a1c95a2601c3ee279b34026a3d7088c2ec1c Mon Sep 17 00:00:00 2001
From: Hiroshi Inoue <inouehrs@gmail.com>
Date: Fri, 1 Jun 2018 00:07:37 +0900
Subject: [PATCH 675/949] Fix compilation failure with latest LLVM (#1208)

* fix problem with the latest LLVM

* add if-defs to support older LLVMs
---
 src/codegen/llvm/codegen_amdgpu.cc | 12 ++++++++++++
 src/codegen/llvm/codegen_nvptx.cc  |  6 ++++++
 src/codegen/llvm/llvm_module.cc    | 12 ++++++++++++
 3 files changed, 30 insertions(+)

diff --git a/src/codegen/llvm/codegen_amdgpu.cc b/src/codegen/llvm/codegen_amdgpu.cc
index de18b2d60b86..6feb3b379d21 100644
--- a/src/codegen/llvm/codegen_amdgpu.cc
+++ b/src/codegen/llvm/codegen_amdgpu.cc
@@ -209,16 +209,28 @@ runtime::Module BuildAMDGPU(Array<LoweredFunc> funcs, std::string target) {
 #endif
   llvm::legacy::PassManager pass;
 
+#if TVM_LLVM_VERSION <= 60
   CHECK(tm->addPassesToEmitFile(
             pass, destObj, llvm::TargetMachine::CGFT_ObjectFile) == 0)
             << "Cannot emit target CGFT_ObjectFile";
+#else
+  CHECK(tm->addPassesToEmitFile(
+            pass, destObj, nullptr, llvm::TargetMachine::CGFT_ObjectFile) == 0)
+            << "Cannot emit target CGFT_ObjectFile";
+#endif
   pass.run(*mObj);
   std::string obj(dataObj.begin(), dataObj.end());
 
   llvm::legacy::PassManager passAsm;
+#if TVM_LLVM_VERSION <= 60
   CHECK(tm->addPassesToEmitFile(passAsm, destAsm,
                                 llvm::TargetMachine::CGFT_AssemblyFile) == 0)
       << "Cannot emit target CGFT_AssemblyFile";
+#else
+  CHECK(tm->addPassesToEmitFile(passAsm, destAsm, nullptr,
+                                llvm::TargetMachine::CGFT_AssemblyFile) == 0)
+      << "Cannot emit target CGFT_AssemblyFile";
+#endif
   passAsm.run(*mAsm);
   std::string assembly(dataAsm.begin(), dataAsm.end());
 
diff --git a/src/codegen/llvm/codegen_nvptx.cc b/src/codegen/llvm/codegen_nvptx.cc
index d354e3b9eaf0..a22494b72909 100644
--- a/src/codegen/llvm/codegen_nvptx.cc
+++ b/src/codegen/llvm/codegen_nvptx.cc
@@ -194,9 +194,15 @@ runtime::Module BuildNVPTX(Array<LoweredFunc> funcs, std::string target) {
   std::string ll(data_ll.begin(), data_ll.end());
   // emit ptx
   llvm::legacy::PassManager pass;
+#if TVM_LLVM_VERSION <= 60
   CHECK(tm->addPassesToEmitFile(
       pass, dest_ptx, llvm::TargetMachine::CGFT_AssemblyFile) == 0)
       << "Cannot emit target CGFT_ObjectFile";
+#else
+  CHECK(tm->addPassesToEmitFile(
+      pass, dest_ptx, nullptr, llvm::TargetMachine::CGFT_AssemblyFile) == 0)
+      << "Cannot emit target CGFT_ObjectFile";
+#endif
   pass.run(*module);
   std::string ptx(data_ptx.begin(), data_ptx.end());
   return CUDAModuleCreate(ptx, "ptx", ExtractFuncInfo(funcs), ll);
diff --git a/src/codegen/llvm/llvm_module.cc b/src/codegen/llvm/llvm_module.cc
index 3d0632f9230e..c16af511febc 100644
--- a/src/codegen/llvm/llvm_module.cc
+++ b/src/codegen/llvm/llvm_module.cc
@@ -69,9 +69,15 @@ class LLVMModuleNode final : public runtime::ModuleNode {
 #endif
       llvm::legacy::PassManager pass;
       CHECK(tm_);
+#if TVM_LLVM_VERSION <= 60
       CHECK(tm_->addPassesToEmitFile(
           pass, dest, llvm::TargetMachine::CGFT_ObjectFile) == 0)
           << "Cannot emit target CGFT_ObjectFile";
+#else
+      CHECK(tm_->addPassesToEmitFile(
+          pass, dest, nullptr, llvm::TargetMachine::CGFT_ObjectFile) == 0)
+          << "Cannot emit target CGFT_ObjectFile";
+#endif
       pass.run(*m);
     } else if (fmt == "s" || fmt == "asm") {
 #if TVM_LLVM_VERSION <= 60
@@ -81,9 +87,15 @@ class LLVMModuleNode final : public runtime::ModuleNode {
 #endif
       llvm::legacy::PassManager pass;
       CHECK(tm_);
+#if TVM_LLVM_VERSION <= 60
       CHECK(tm_->addPassesToEmitFile(
           pass, dest, llvm::TargetMachine::CGFT_AssemblyFile) == 0)
           << "Cannot emit target CGFT_AssemblyFile";
+#else
+      CHECK(tm_->addPassesToEmitFile(
+          pass, dest, nullptr, llvm::TargetMachine::CGFT_AssemblyFile) == 0)
+          << "Cannot emit target CGFT_AssemblyFile";
+#endif
       pass.run(*m);
     } else if (fmt == "ll") {
       mptr_->print(dest, nullptr);

From 434353f84863129c0f10f143af781e506419a12c Mon Sep 17 00:00:00 2001
From: Siva <sivar.b@huawei.com>
Date: Thu, 31 May 2018 22:32:35 +0530
Subject: [PATCH 676/949] [LINT] Expected, hence disable for a while. (#1212)

************* Module nnvm.top.nn
E:141, 8: Assigning to function call which doesn't return (assignment-from-no-return)
---
 nnvm/python/nnvm/top/nn.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index 8135474c9c67..dfebc368eaf3 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -138,7 +138,9 @@ def compute_contrib_conv2d_NCHWc(attrs, inputs, _):
     channels = attrs.get_int("channels")
     assert dilation == (1, 1), "not support dilate now"
     if groups == 1:
+        # pylint: disable=assignment-from-no-return
         out = topi.nn.conv2d_NCHWc(inputs[0], inputs[1], channels, (kh, kw), strides, padding)
+        # pylint: enable=assignment-from-no-return
     else:
         raise ValueError("not support arbitrary group number > 1 for now")
     if attrs.get_bool("use_bias"):

From 3f20f222b3378af136c33ac841fea0cd63b2c58f Mon Sep 17 00:00:00 2001
From: abergeron <bergearn@iro.umontreal.ca>
Date: Thu, 31 May 2018 17:04:39 -0400
Subject: [PATCH 677/949] Add some tips for building on macOS. (#1215)

---
 docs/how_to/install.md | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/docs/how_to/install.md b/docs/how_to/install.md
index c6210e802cab..a3d61343fa46 100644
--- a/docs/how_to/install.md
+++ b/docs/how_to/install.md
@@ -23,7 +23,8 @@ git submodule update
 ## Build the Shared Library
 
 Our goal is to build the shared libraries:
-- On Linux/OSX the target library are `libtvm.so, libtvm_topi.so`
+- On Linux the target library are `libtvm.so, libtvm_topi.so`
+- On OSX the target library are `libtvm.dylib, libtvm_topi.dylib`
 - On Windows the target library are `libtvm.dll, libtvm_topi.dll`
 
 ```bash
@@ -39,6 +40,7 @@ The minimal building requirement is
 The configuration of tvm can be modified by ```config.mk```
 - First copy ```make/config.mk``` to the project root, on which
   any local modification will be ignored by git, then modify the according flags.
+  - On macOS, for some versions of XCode, you need to add ```-lc++abi``` in the LDFLAGS or you'll get link errors.
 - TVM optionally depends on LLVM. LLVM is required for CPU codegen that needs LLVM.
   - LLVM 4.0 or higher is needed for build with LLVM. Note that verison of LLVM from default apt may lower than 4.0.
   - Since LLVM takes long time to build from source, you can download pre-built version of LLVM from
@@ -49,7 +51,12 @@ The configuration of tvm can be modified by ```config.mk```
       For example, set ```LLVM_CONFIG=llvm-config-4.0``` if you installed 4.0 package
 
 We can then build tvm by `make`.
-After we build the tvm, we can proceed to build nnvm using the following script.
+
+```bash
+make -j4
+```
+
+After we build tvm, we can proceed to build nnvm using the following script.
 
 ```bash
 cd nnvm
@@ -98,6 +105,7 @@ There are several ways to install the package:
     # NOTE: if you installed python via homebrew, --user is not needed during installaiton
     #       it will be automatically installed to your user directory.
     #       providing --user flag may trigger error during installation in such case.
+    export MACOSX_DEPLOYMENT_TARGET=10.9  # This is required for mac to avoid symbol conflicts with libstdc++
     cd python; python setup.py install --user; cd ..
     cd topi/python; python setup.py install --user; cd ../..
     cd nnvm/python; python setup.py install --user; cd ../..

From 203ab24aa317c7b1ba663e133c6974274dcf364f Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 31 May 2018 15:25:13 -0700
Subject: [PATCH 678/949] ROCm installation guide (#1216)

---
 docs/how_to/install.md | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/docs/how_to/install.md b/docs/how_to/install.md
index a3d61343fa46..c71c823de0c6 100644
--- a/docs/how_to/install.md
+++ b/docs/how_to/install.md
@@ -83,6 +83,12 @@ Open the .sln file in the build directory and build with Visual Studio.
 In order to build with LLVM in windows, you will need to build LLVM from source.
 You need to run build the nnvm by running the same script under the nnvm folder.
 
+### Building ROCm support
+Currently, ROCm is supported only on linux, so all the instructions are written with linux in mind.
+- Set ```USE_ROCM=1```, set ROCM_PATH to the correct path.
+- You need to first install HIP runtime from ROCm. Make sure the installation system has ROCm installed in it.
+- Install latest stable version of LLVM (v6.0.1), and LLD, make sure ```ld.lld``` is available via command line.
+
 ## Python Package Installation
 
 The python package is located at python

From 0a2280e88cb2dda4c144fb5f85890c8c10213b76 Mon Sep 17 00:00:00 2001
From: Leyuan Wang <laurawly@gmail.com>
Date: Fri, 1 Jun 2018 10:54:06 -0700
Subject: [PATCH 679/949] [Metal] Fix block launching parameter (#1219)

---
 src/runtime/metal/metal_module.mm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/runtime/metal/metal_module.mm b/src/runtime/metal/metal_module.mm
index ed7cba6e75a5..5baefd72ec62 100644
--- a/src/runtime/metal/metal_module.mm
+++ b/src/runtime/metal/metal_module.mm
@@ -215,7 +215,7 @@ void operator()(TVMArgs args,
     MTLSize dimGrid = MTLSizeMake(
         wl.grid_dim(0), wl.grid_dim(1), wl.grid_dim(2));
     MTLSize dimBlock = MTLSizeMake(
-        wl.block_dim(0), wl.block_dim(1), wl.work_size[2]);
+        wl.block_dim(0), wl.block_dim(1), wl.block_dim(2));
     [encoder dispatchThreadgroups: dimGrid
              threadsPerThreadgroup: dimBlock];
     [encoder endEncoding];

From 4e6df19abb0b95bcebfd9d616a9830b4959ba397 Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Sat, 2 Jun 2018 01:39:53 +0530
Subject: [PATCH 680/949] leaky_relu bug fix (#1218)

---
 nnvm/src/top/nn/nn.cc                   | 2 +-
 topi/include/topi/nn.h                  | 7 ++-----
 topi/src/topi.cc                        | 2 +-
 topi/tests/python_cpp/test_topi_relu.py | 4 ++--
 4 files changed, 6 insertions(+), 9 deletions(-)

diff --git a/nnvm/src/top/nn/nn.cc b/nnvm/src/top/nn/nn.cc
index f57f6bceebd4..e1dbe3cfcb06 100644
--- a/nnvm/src/top/nn/nn.cc
+++ b/nnvm/src/top/nn/nn.cc
@@ -466,7 +466,7 @@ NNVM_REGISTER_OP(leaky_relu)
                     const Array<Tensor>& inputs,
                     const Array<Tensor>& out_info) {
     const LeakyReLUParam& param = nnvm::get<LeakyReLUParam>(attrs.parsed);
-    return Array<Tensor>{ topi::leaky_relu<float>(inputs[0], 0.0, param.alpha) };
+    return Array<Tensor>{ topi::leaky_relu(inputs[0], param.alpha) };
   })
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
diff --git a/topi/include/topi/nn.h b/topi/include/topi/nn.h
index 5e140a893482..2459eb515707 100644
--- a/topi/include/topi/nn.h
+++ b/topi/include/topi/nn.h
@@ -60,17 +60,14 @@ inline tvm::Tensor relu(const tvm::Tensor& t,
 * \brief Creates an operation that performs a leaky rectified linear unit
 *
 * \param t The input tensor
-* \param threshold The relu threshold (default 0)
-* \param alpha The slope for the small gradient when t < threshold
+* \param alpha The slope for the small gradient when t < 0
 * \param name The name of the operation
 * \param tag The tag to mark the operation
 *
 * \return A Tensor whose op member is the relu operation
 */
-template <typename T>
 inline tvm::Tensor leaky_relu(const tvm::Tensor& t,
-                              T threshold = static_cast<T>(0),
-                              T alpha = static_cast<T>(0.1),
+                              double alpha = 0.1,
                               std::string name = "tensor",
                               std::string tag = kElementWise) {
   return tvm::compute(
diff --git a/topi/src/topi.cc b/topi/src/topi.cc
index f4feafb043f1..1042bfcf3f45 100644
--- a/topi/src/topi.cc
+++ b/topi/src/topi.cc
@@ -191,7 +191,7 @@ TVM_REGISTER_GLOBAL("topi.nn.relu")
 
 TVM_REGISTER_GLOBAL("topi.nn.leaky_relu")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
-  *rv = leaky_relu<float>(args[0]);
+  *rv = leaky_relu(args[0], args[1]);
   });
 
 TVM_REGISTER_GLOBAL("topi.nn.prelu")
diff --git a/topi/tests/python_cpp/test_topi_relu.py b/topi/tests/python_cpp/test_topi_relu.py
index 44890e908d42..a5064618b890 100644
--- a/topi/tests/python_cpp/test_topi_relu.py
+++ b/topi/tests/python_cpp/test_topi_relu.py
@@ -41,7 +41,7 @@ def verify_leaky_relu(m, alpha):
     target = topi.cpp.TEST_create_target(device)
     s = topi.cpp.generic.schedule_injective(target, [B])
 
-    a_np = np.random.uniform(size=get_const_tuple(A.shape)).astype(A.dtype)
+    a_np = np.random.uniform(low=-1.0, high=1.0, size=get_const_tuple(A.shape)).astype(A.dtype)
     b_np = a_np * (a_np > 0) + a_np * (a_np < 0) * alpha
     ctx = tvm.cpu(0)
     a = tvm.nd.array(a_np, ctx)
@@ -78,7 +78,7 @@ def test_relu():
         verify_relu(10, 128, dtype)
 
 def test_leaky_relu():
-    verify_leaky_relu(100, 0.1)
+    verify_leaky_relu(100, 0.5)
 
 def test_prelu():
     verify_prelu((1, 3, 2, 2), (3,))

From 8f80df0c46188cdf227764848d8aba0cba6e8273 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 1 Jun 2018 16:38:58 -0700
Subject: [PATCH 681/949] [DOCS] Detailed contributor guide, doc refactor
 (#1220)

---
 CONTRIBUTORS.md                               |   2 +-
 README.md                                     |   4 -
 apps/android_deploy/README.md                 |  10 +-
 apps/howto_deploy/README.md                   |   2 +-
 docs/api_links.rst                            |   4 +-
 docs/contribute/code_guide.rst                |  39 +++++
 docs/contribute/code_review.rst               |  47 ++++++
 docs/contribute/document.rst                  |  88 +++++++++++
 docs/contribute/git_howto.md                  |  57 +++++++
 docs/contribute/index.rst                     |  30 ++++
 docs/contribute/pull_request.rst              |  26 ++++
 docs/deploy/android.md                        |  25 ++++
 docs/deploy/cpp_deploy.md                     |  35 +++++
 docs/deploy/index.rst                         |  17 +++
 docs/{how_to => deploy}/integrate.md          |   1 -
 docs/{how_to/deploy.md => deploy/nnvm.md}     |  70 +--------
 docs/dev/index.rst                            |   4 +-
 docs/faq.md                                   |   2 +-
 docs/how_to/contribute.md                     | 102 -------------
 docs/index.rst                                |   7 +-
 docs/{how_to/install.md => install/index.rst} | 141 +++++++++++-------
 docs/{how_to => install}/nnpack.md            |  52 +------
 jvm/README.md                                 |   2 +-
 python/tvm/api.py                             |   6 +-
 24 files changed, 478 insertions(+), 295 deletions(-)
 create mode 100644 docs/contribute/code_guide.rst
 create mode 100644 docs/contribute/code_review.rst
 create mode 100644 docs/contribute/document.rst
 create mode 100644 docs/contribute/git_howto.md
 create mode 100644 docs/contribute/index.rst
 create mode 100644 docs/contribute/pull_request.rst
 create mode 100644 docs/deploy/android.md
 create mode 100644 docs/deploy/cpp_deploy.md
 create mode 100644 docs/deploy/index.rst
 rename docs/{how_to => deploy}/integrate.md (93%)
 rename docs/{how_to/deploy.md => deploy/nnvm.md} (54%)
 delete mode 100644 docs/how_to/contribute.md
 rename docs/{how_to/install.md => install/index.rst} (54%)
 rename docs/{how_to => install}/nnpack.md (65%)

diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 630e05953398..fd3a69a07e1d 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -28,7 +28,7 @@ list of contributions that are welcomed
 
 How to Contribute
 -----------------
-See [Contributor guide](docs/how_to/contribute.md) on tips for contributions.
+See [Contributor guide](http://tvm.ai/contribute/) on tips for contributions.
 
 
 Committers
diff --git a/README.md b/README.md
index 77d72c6418a6..604d30ad6e56 100644
--- a/README.md
+++ b/README.md
@@ -4,11 +4,7 @@
 [![GitHub license](http://dmlc.github.io/img/apache2.svg)](./LICENSE)
 [![Build Status](http://mode-gpu.cs.washington.edu:8080/buildStatus/icon?job=dmlc/tvm/master)](http://mode-gpu.cs.washington.edu:8080/job/dmlc/job/tvm/job/master/)
 
-[Installation](docs/how_to/install.md) |
 [Documentation](http://docs.tvm.ai) |
-[Tutorials](http://tutorials.tvm.ai) |
-[Operator Inventory](topi) |
-[FAQ](docs/faq.md) |
 [Contributors](CONTRIBUTORS.md) |
 [Community](http://tvm.ai/community.html) |
 [Release Notes](NEWS.md)
diff --git a/apps/android_deploy/README.md b/apps/android_deploy/README.md
index 9620a2f1efde..94cc9984b0c2 100644
--- a/apps/android_deploy/README.md
+++ b/apps/android_deploy/README.md
@@ -65,15 +65,15 @@ Here's a piece of example for `config.mk`.
 
 ```makefile
 APP_ABI = arm64-v8a
- 
+
 APP_PLATFORM = android-17
- 
+
 # whether enable OpenCL during compile
 USE_OPENCL = 1
- 
+
 # the additional include headers you want to add, e.g., SDK_PATH/adrenosdk/Development/Inc
 ADD_C_INCLUDES = /opt/adrenosdk-osx/Development/Inc
- 
+
 # the additional link libs you want to add, e.g., ANDROID_LIB_PATH/libOpenCL.so
 ADD_LDLIBS = libOpenCL.so
 ```
@@ -99,7 +99,7 @@ If everything goes well, you will find compile tools in `/opt/android-toolchain-
 
 ### Place compiled model on Android application assets folder
 
-Follow instruction to get compiled version model for android target [here.](https://github.com/dmlc/tvm/blob/master/docs/how_to/deploy_android.md#build-model-for-android-target)
+Follow instruction to get compiled version model for android target [here.](https://tvm.ai/deploy/android.html)
 
 Copied these compiled model deploy_lib.so, deploy_graph.json and deploy_param.params to apps/android_deploy/app/src/main/assets/ and modify TVM flavor changes on [java](https://github.com/dmlc/tvm/blob/master/apps/android_deploy/app/src/main/java/ml/dmlc/tvm/android/demo/MainActivity.java#L81)
 
diff --git a/apps/howto_deploy/README.md b/apps/howto_deploy/README.md
index 570c42ed185d..fda6251ae9c5 100644
--- a/apps/howto_deploy/README.md
+++ b/apps/howto_deploy/README.md
@@ -8,4 +8,4 @@ Type the following command to run the sample code under the current folder(need
 ./run_example.sh
 ```
 
-Checkout [How to Deploy TVM Modules](http://docs.tvm.ai/how_to/deploy.html) for more information.
+Checkout [How to Deploy TVM Modules](http://docs.tvm.ai/deploy/cpp_deploy.html) for more information.
diff --git a/docs/api_links.rst b/docs/api_links.rst
index d749a2440ac7..909cfe367f29 100644
--- a/docs/api_links.rst
+++ b/docs/api_links.rst
@@ -1,5 +1,5 @@
-Links to C++/JS API References
-==============================
+Links to C++ and JS API References
+==================================
 
 This page contains links to API references that are build with different doc build system.
 
diff --git a/docs/contribute/code_guide.rst b/docs/contribute/code_guide.rst
new file mode 100644
index 000000000000..dc7d998ca37f
--- /dev/null
+++ b/docs/contribute/code_guide.rst
@@ -0,0 +1,39 @@
+.. _code_guide:
+
+Code Guide and Tips
+===================
+
+This is a document used to record tips in tvm codebase for reviewers and contributors.
+Most of them are summarized through lessons during the contributing and process.
+
+
+C++ Code Styles
+---------------
+- Use the Google C/C++ style.
+- The public facing functions are documented in doxygen format.
+- Favor concrete type declaration over ``auto`` as long as it is short.
+- Favor passing by const reference (e.g. ``const Expr&``) over passing by value.
+  Except when the function consumes the value by copy constructor or move,
+  pass by value is better than pass by const reference in such cases.
+
+Python Code Styles
+------------------
+- The functions and classes are documented in `numpydoc <https://numpydoc.readthedocs.io/en/latest/>`_ format.
+- Check your code style using ``make pylint``
+
+
+Handle Integer Constant Expression
+----------------------------------
+We often need to handle constant integer expressions in tvm. Before we do so, the first question we want to ask is that is it really necessary to get a constant integer. If symbolic expression also works and let the logic flow, we should use symbolic expression as much as possible. So the generated code works for shapes that are not known ahead of time.
+
+Note that in some cases we cannot know certain information, e.g. sign of symbolic variable, it is ok to make assumptions in certain cases. While adding precise support if the variable is constant.
+
+If we do have to get constant integer expression, we should get the constant value using type ``int64_t`` instead of ``int``, to avoid potential integer overflow. We can always reconstruct an integer with the corresponding expression type via ``make_const``. The following code gives an example.
+
+.. code:: c++
+
+   Expr CalculateExpr(Expr value) {
+     int64_t int_value = GetConstInt<int64_t>(value);
+     int_value = CalculateExprInInt64(int_value);
+     return make_const(value.type(), int_value);
+   }
diff --git a/docs/contribute/code_review.rst b/docs/contribute/code_review.rst
new file mode 100644
index 000000000000..efbb77c67a7a
--- /dev/null
+++ b/docs/contribute/code_review.rst
@@ -0,0 +1,47 @@
+Perform Code Reviews
+====================
+
+This is a general guideline for code reviewers. First of all, while it is great to add new features to a project, we must also be aware that each line of code we introduce also brings **technical debt** that we may have to eventually pay.
+
+Open source code is maintained by a community with diverse backend, and it is even more important to bring clear, documented and maintainable code. Code reviews are shepherding process to spot potential problems, improve quality of the code. We should, however, not rely on code review process to get the code into a ready state. Contributors are encouraged to polish the code to a ready state before requesting reviews. This is especially expected for code owner and comitter candidates.
+
+Here are some checklists for code reviews, it is also helpful reference for contributors
+
+Hold the Highest Standard
+-------------------------
+The first rule for code reviewers is to always keep the highest standard, and do not approve code just to "be friendly". Good, informative critics each other learn and prevents technical debt in early stages.
+
+Ensure Test Coverage
+--------------------
+Each new change of features should introduce test cases, bug fixes should include regression tests that prevent the problem from happening again.
+
+Documentations are Mandatory
+----------------------------
+Documentation is usually a place we overlooked, new functions or change to a function should be directly updated in documents. A new feature is meaningless without documentation to make it accessible. See more at :ref:`doc_guide`
+
+Deliberate on User-facing API
+-----------------------------
+A good, minimum and stable API is critical to the project’s life. A good API makes a huge difference. Always think very carefully about all the aspects including naming, arguments definitions and behavior. One good rule to check is to be consistent with existing well-known package’s APIs if the feature overlap. For example, tensor operation APIs should always be consistent with the numpy.
+
+Minimum Dependency
+------------------
+Always be cautious in introducing dependencies. While it is important to reuse code and not reinventing the wheel, dependencies can increase burden of users in deployment. A good design principle only depends on the part when a user actually use it.
+
+Ensure Readability
+------------------
+While it is hard to implement a new feature, it is even harder to make others understand and maintain the code you wrote. It is common for a PMC or committer to not being able to understand certain contributions. In such case, a reviewer should say "I don’t understand" and ask the contributor to clarify. We highly encourage code comments which explain the code logic along with the code.
+
+Concise Implementation
+----------------------
+Some basic principles applied here: favor vectorized array code over loops, is there existing API that solves the problem.
+
+Document Lessons in Code Reviews
+--------------------------------
+When you find there are some common lessons that can be summarized in the guideline,
+add it to the :ref:`code_guide`.
+It is always good to refer to the guideline document when requesting changes,
+so the lessons can be shared to all the community.
+
+Respect each other
+------------------
+The code reviewers and contributors are paying the most precious currencies in the world -- time. We are volunteers in the community to spend the time to build good code, help each other, learn and have fun hacking.
diff --git a/docs/contribute/document.rst b/docs/contribute/document.rst
new file mode 100644
index 000000000000..ab67fbec9384
--- /dev/null
+++ b/docs/contribute/document.rst
@@ -0,0 +1,88 @@
+.. _doc_guide:
+
+Write Document and Tutorials
+============================
+
+We use the `Sphinx <http://sphinx-doc.org>`_ for the main documentation.
+Sphinx support both the reStructuredText and markdown.
+When possible, we encourage to use reStructuredText as it has richer features.
+Note that the python doc-string and tutorials allow you to embed reStructuredText syntax.
+
+
+Document Python
+---------------
+We use `numpydoc <https://numpydoc.readthedocs.io/en/latest/>`_
+format to document the function and classes.
+The following snippet gives an example docstring.
+We always document all the public functions,
+when necessary, provide an usage example of the features we support(as shown below).
+
+.. code:: python
+
+    def myfunction(arg1, arg2, arg3=3):
+        """Briefly describe my function.
+
+        Parameters
+        ----------
+        arg1 : Type1
+            Description of arg1
+
+        arg2 : Type2
+            Description of arg2
+
+        arg3 : Type3, optional
+            Description of arg3
+
+        Returns
+        -------
+        rv1 : RType1
+            Description of return type one
+
+        Examples
+        --------
+        .. code:: python
+
+            # Example usage of myfunction
+            x = myfunction(1, 2)
+        """
+        return rv1
+
+Be careful to leave blank lines between sections of your documents.
+In the above case, there has to be a blank line before `Parameters`, `Returns` and `Examples`
+in order for the doc to be built correctly. To add a new function to the doc,
+we need to add the `sphinx.autodoc <http://www.sphinx-doc.org/en/master/ext/autodoc.html>`_
+rules to the `docs/api/python <https://github.com/dmlc/tvm/tree/master/docs/api/python>`_).
+You can refer to the existing files under this folder on how to add the functions.
+
+
+Document C++
+------------
+We use the doxgen format to document c++ functions.
+The following snippet shows an example of c++ docstring.
+
+.. code:: c++
+
+    /*!
+     * \brief Description of my function
+     * \param arg1 Description of arg1
+     * \param arg2 Descroption of arg2
+     * \returns describe return value
+     */
+    int myfunction(int arg1, int arg2) {
+      // When necessary, also add comment to clarify internal logics
+    }
+
+Besides documenting function usages, we also highly recommend contributors
+to add comments about code logics to improve readability.
+
+
+Write Tutorials
+---------------
+We use the `sphinx-gallery <https://sphinx-gallery.github.io/>`_ to build python tutorials.
+You can find the source code under `tutorials <https://github.com/dmlc/tvm/tree/master/tutorials>`_ quite self explanatory.
+One thing that worth noting is that the comment blocks are written in reStructuredText instead of markdown so be aware of the syntax.
+
+The tutorial code will run on our build server to generate the document page.
+So we may have a restriction like not being able to access a remote Raspberry Pi,
+in such case add a flag variable to the tutorial (e.g. `use_rasp`) and allow users to easily switch to the real device by changing one flag.
+Then use the existing environment to demonstrate the usage.
diff --git a/docs/contribute/git_howto.md b/docs/contribute/git_howto.md
new file mode 100644
index 000000000000..53ff89b127df
--- /dev/null
+++ b/docs/contribute/git_howto.md
@@ -0,0 +1,57 @@
+# Git Usage Tips
+
+Here are some tips for git workflow.
+
+## How to resolve conflict with master
+- First rebase to most recent master
+```bash
+# The first two steps can be skipped after you do it once.
+git remote add upstream [url to tvm repo]
+git fetch upstream
+git rebase upstream/master
+```
+- The git may show some conflicts it cannot merge, say ```conflicted.py```.
+  - Manually modify the file to resolve the conflict.
+  - After you resolved the conflict, mark it as resolved by
+```bash
+git add conflicted.py
+```
+- Then you can continue rebase by
+```bash
+git rebase --continue
+```
+- Finally push to your fork, you may need to force push here.
+```bash
+git push --force
+```
+
+## How to combine multiple commits into one
+Sometimes we want to combine multiple commits, especially when later commits are only fixes to previous ones,
+to create a PR with set of meaningful commits. You can do it by following steps.
+- Before doing so, configure the default editor of git if you haven't done so before.
+```bash
+git config core.editor the-editor-you-like
+```
+- Assume we want to merge last 3 commits, type the following commands
+```bash
+git rebase -i HEAD~3
+```
+- It will pop up an text editor. Set the first commit as ```pick```, and change later ones to ```squash```.
+- After you saved the file, it will pop up another text editor to ask you modify the combined commit message.
+- Push the changes to your fork, you need to force push.
+```bash
+git push --force
+```
+
+## Reset to the most recent master
+You can always use git reset to reset your version to the most recent master.
+Note that all your ***local changes will get lost***.
+So only do it when you do not have local changes or when your pull request just get merged.
+```bash
+git reset --hard [hash tag of master]
+git push --force
+```
+
+## What is the consequence of force push
+The previous two tips requires force push, this is because we altered the path of the commits.
+It is fine to force push to your own fork, as long as the commits changed are only yours.
diff --git a/docs/contribute/index.rst b/docs/contribute/index.rst
new file mode 100644
index 000000000000..7b757aac3db0
--- /dev/null
+++ b/docs/contribute/index.rst
@@ -0,0 +1,30 @@
+Contribute to TVM
+=================
+
+TVM has been developed by community members.
+Everyone is welcomed to contribute.
+We value all forms of contributions, including, but not limited to:
+
+- Code reviewing of the existing patches.
+- Documentation and usage examples
+- Community participation in forums and issues.
+- Code readability and developer guide
+
+  - We welcome contributions that add code comments
+    to improve readability
+  - We also welcome contributions to docs to explain the
+    design choices of the internal.
+
+- Test cases to make the codebase more robust
+- Tutorials, blog posts, talks that promote the project.
+
+Here are guidelines for contributing to various aspect of the project:
+
+.. toctree::
+   :maxdepth: 2
+
+   code_review
+   document
+   code_guide
+   pull_request
+   git_howto
diff --git a/docs/contribute/pull_request.rst b/docs/contribute/pull_request.rst
new file mode 100644
index 000000000000..80a0448c08dd
--- /dev/null
+++ b/docs/contribute/pull_request.rst
@@ -0,0 +1,26 @@
+Submit a Pull Request
+=====================
+
+This is a quick guide to submit a pull request, please also refer to the detailed guidelines.
+
+- Before submit, please rebase your code on the most recent version of master, you can do it by
+
+  .. code:: bash
+
+    git remote add upstream [url to tvm repo]
+    git fetch upstream
+    git rebase upstream/master
+
+- Make sure code style check pass by typing ``make lint``, and all the existing test-cases pass.
+- Add test-cases to cover the new features or bugfix the patch introduces.
+- Document the code you wrote, see more at :ref:`doc_guide`
+- Send the pull request,  fix the problems reported by automatic checks.
+  Request code reviews from other contributors and improves your patch according to feedbacks.
+
+  - To get your code reviewed quickly, we encourage you to help review others' code so they can do the favor in return.
+  - Code review is a shepherding process that helps to improve contributor's code quality.
+    We should treat it proactively, to improve the code as much as possible before the review.
+    We highly value patches that can get in without extensive reviews.
+  - The detailed guidelines and summarizes useful lessons.
+
+- The patch can be merged after the reviewers approve the pull request.
diff --git a/docs/deploy/android.md b/docs/deploy/android.md
new file mode 100644
index 000000000000..ca431693c63a
--- /dev/null
+++ b/docs/deploy/android.md
@@ -0,0 +1,25 @@
+# Deploy to Android
+
+
+## Build model for Android Target
+
+NNVM compilation of model for android target could follow same approach like android_rpc.
+
+An reference exampe can be found at [chainer-nnvm-example](https://github.com/tkat0/chainer-nnvm-example)
+
+Above example will directly run the compiled model on RPC target. Below modification at [rum_mobile.py](https://github.com/tkat0/chainer-nnvm-example/blob/5b97fd4d41aa4dde4b0aceb0be311054fb5de451/run_mobile.py#L64) will save the compilation output which is required on android target.
+
+```
+lib.export_library("deploy_lib.so", ndk.create_shared)
+with open("deploy_graph.json", "w") as fo:
+    fo.write(graph.json())
+with open("deploy_param.params", "wb") as fo:
+    fo.write(nnvm.compiler.save_param_dict(params))
+```
+
+deploy_lib.so, deploy_graph.json, deploy_param.params will go to android target.
+
+## TVM Runtime for Android Target
+
+Refer [here](https://github.com/dmlc/tvm/blob/master/apps/android_deploy/README.md#build-and-installation) to build CPU/OpenCL version flavor TVM runtime for android target.
+From android java TVM API to load model & execute can be refered at this [java](https://github.com/dmlc/tvm/blob/master/apps/android_deploy/app/src/main/java/ml/dmlc/tvm/android/demo/MainActivity.java) sample source.
diff --git a/docs/deploy/cpp_deploy.md b/docs/deploy/cpp_deploy.md
new file mode 100644
index 000000000000..d02d33d18694
--- /dev/null
+++ b/docs/deploy/cpp_deploy.md
@@ -0,0 +1,35 @@
+Deploy TVM Module using C++ API
+===============================
+
+We provide an example on how to deploy TVM modules in [apps/howto_deploy](https://github.com/dmlc/tvm/tree/master/apps/howto_deploy)
+
+To run the example, you can use the following command
+
+```bash
+cd apps/howto_deploy
+./run_example.sh
+```
+
+Get TVM Runtime Library
+-----------------------
+
+The only thing we need is to link to a TVM runtime in your target platform.
+TVM provides a minimum runtime, which costs around 300K to 600K depending on how much modules we use.
+In most cases, we can use ```libtvm_runtime.so``` that comes with the build.
+
+If somehow you find it is hard to build ```libtvm_runtime```, checkout [tvm_runtime_pack.cc](https://github.com/dmlc/tvm/tree/master/apps/howto_deploy/tvm_runtime_pack.cc).
+It is an example all in one file that gives you TVM runtime.
+You can compile this file using your build system and include this into your project.
+
+You can also checkout [apps](https://github.com/dmlc/tvm/tree/master/apps/) for example applications build with TVM on iOS, Android and others.
+
+Dynamic Library vs. System Module
+---------------------------------
+TVM provides two ways to use the compiled library.
+You can checkout [prepare_test_libs.py](https://github.com/dmlc/tvm/tree/master/apps/howto_deploy/prepare_test_libs.py)
+on how to generate the library and [cpp_deploy.cc](https://github.com/dmlc/tvm/tree/master/apps/howto_deploy/cpp_deploy.cc) on how to use them.
+
+- Store library as a shared library and dynamically load the library into your project.
+- Bundle the compiled library into your project in system module mode.
+
+Dynamic loading is more flexible and can load new modules on the fly. System module is a more ```static``` approach.  We can use system module in places where dynamic library loading is banned.
diff --git a/docs/deploy/index.rst b/docs/deploy/index.rst
new file mode 100644
index 000000000000..bf607a4b8288
--- /dev/null
+++ b/docs/deploy/index.rst
@@ -0,0 +1,17 @@
+Deploy and Integration
+======================
+
+This page contains guidelines on how to deploy TVM to various platforms
+as well as how to integrate it with your project.
+
+.. image::  http://www.tvm.ai/images/release/tvm_flexible.png
+
+In order to integrate the compiled module, we do not have to ship the compiler stack. We only need to use a lightweight runtime API that can be integrated into various platforms.
+
+.. toctree::
+   :maxdepth: 2
+
+   cpp_deploy
+   android
+   nnvm
+   integrate
diff --git a/docs/how_to/integrate.md b/docs/deploy/integrate.md
similarity index 93%
rename from docs/how_to/integrate.md
rename to docs/deploy/integrate.md
index 9819d4add40e..b6f3b1faa3da 100644
--- a/docs/how_to/integrate.md
+++ b/docs/deploy/integrate.md
@@ -3,7 +3,6 @@ Integrate TVM into Your Project
 
 TVM's runtime is designed to be lightweight and portable.
 There are several ways you can integrate TVM into your project.
-If you are looking for minimum deployment of a compiled module, take a look at [deployment guide](deploy.md)
 
 This article introduces possible ways to integrate TVM
 as a JIT compiler to generate functions on your system.
diff --git a/docs/how_to/deploy.md b/docs/deploy/nnvm.md
similarity index 54%
rename from docs/how_to/deploy.md
rename to docs/deploy/nnvm.md
index 69fedaa974b4..aa6c39facd1f 100644
--- a/docs/how_to/deploy.md
+++ b/docs/deploy/nnvm.md
@@ -1,71 +1,7 @@
-How to Deploy Compiled Modules
-==============================
-We provide an example on how to deploy TVM modules in [apps/howto_deploy](https://github.com/dmlc/tvm/tree/master/apps/howto_deploy)
-
-To run the example, you can use the following command
-
-```bash
-cd apps/howto_deploy
-./run_example.sh
-```
-
-Get TVM Runtime Library
------------------------
-
-![](http://www.tvm.ai/images/release/tvm_flexible.png)
-
-The only thing we need is to link to a TVM runtime in your target platform.
-TVM provides a minimum runtime, which costs around 300K to 600K depending on how much modules we use.
-In most cases, we can use ```libtvm_runtime.so``` that comes with the build.
-
-If somehow you find it is hard to build ```libtvm_runtime```, checkout [tvm_runtime_pack.cc](https://github.com/dmlc/tvm/tree/master/apps/howto_deploy/tvm_runtime_pack.cc).
-It is an example all in one file that gives you TVM runtime.
-You can compile this file using your build system and include this into your project.
-
-You can also checkout [apps](https://github.com/dmlc/tvm/tree/master/apps/) for example applications build with TVM on iOS, Android and others.
-
-Dynamic Library vs. System Module
----------------------------------
-TVM provides two ways to use the compiled library.
-You can checkout [prepare_test_libs.py](https://github.com/dmlc/tvm/tree/master/apps/howto_deploy/prepare_test_libs.py)
-on how to generate the library and [cpp_deploy.cc](https://github.com/dmlc/tvm/tree/master/apps/howto_deploy/cpp_deploy.cc) on how to use them.
-
-- Store library as a shared library and dynamically load the library into your project.
-- Bundle the compiled library into your project in system module mode.
-
-Dynamic loading is more flexible and can load new modules on the fly. System module is a more ```static``` approach.  We can use system module in places where dynamic library loading is banned.
-
-Deploy to Android
------------------
-### Build model for Android Target
-
-NNVM compilation of model for android target could follow same approach like android_rpc.
-
-An reference exampe can be found at [chainer-nnvm-example](https://github.com/tkat0/chainer-nnvm-example)
-
-Above example will directly run the compiled model on RPC target. Below modification at [rum_mobile.py](https://github.com/tkat0/chainer-nnvm-example/blob/5b97fd4d41aa4dde4b0aceb0be311054fb5de451/run_mobile.py#L64) will save the compilation output which is required on android target.
-
-```
-lib.export_library("deploy_lib.so", ndk.create_shared)
-with open("deploy_graph.json", "w") as fo:
-    fo.write(graph.json())
-with open("deploy_param.params", "wb") as fo:
-    fo.write(nnvm.compiler.save_param_dict(params))
-```
-
-deploy_lib.so, deploy_graph.json, deploy_param.params will go to android target.
-
-### TVM Runtime for Android Target
-
-Refer [here](https://github.com/dmlc/tvm/blob/master/apps/android_deploy/README.md#build-and-installation) to build CPU/OpenCL version flavor TVM runtime for android target.
-From android java TVM API to load model & execute can be refered at this [java](https://github.com/dmlc/tvm/blob/master/apps/android_deploy/app/src/main/java/ml/dmlc/tvm/android/demo/MainActivity.java) sample source.
-
-
-Deploy NNVM Modules
--------------------
+# Deploy NNVM Modules
 NNVM compiled modules are fully embedded in TVM runtime as long as ```GRAPH_RUNTIME``` option
-is enabled in tvm runtime. Check out the [TVM documentation](http://docs.tvm.ai/) for
-how to deploy TVM runtime to your system.
+is enabled in tvm runtime.
+
 
 In a nutshell, we will need three items to deploy a compiled module.
 Checkout our tutorials on getting started with NNVM compiler for more details.
diff --git a/docs/dev/index.rst b/docs/dev/index.rst
index 5056630fa4db..3fb052938689 100644
--- a/docs/dev/index.rst
+++ b/docs/dev/index.rst
@@ -1,5 +1,5 @@
-TVM Design and Developer Guide
-==============================
+Design and Developer Guide
+==========================
 
 Building a compiler stack for deep learning systems involves many many systems-level design decisions.
 In this part of documentation, we share the rationale for the specific choices made when designing TVM.
diff --git a/docs/faq.md b/docs/faq.md
index 92cb886f1ca7..54df0ced8fa8 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -4,7 +4,7 @@ This document contains frequently asked questions.
 
 How to Install
 --------------
-See [Installation](https://github.com/dmlc/tvm/blob/master/docs/how_to/install.md)
+See [Installation](http://tvm.ai/install/)
 
 TVM's relation to Other IR/DSL Projects
 ---------------------------------------
diff --git a/docs/how_to/contribute.md b/docs/how_to/contribute.md
deleted file mode 100644
index 9818f829a170..000000000000
--- a/docs/how_to/contribute.md
+++ /dev/null
@@ -1,102 +0,0 @@
-# Contribute to TVM
-
-TVM has been developed by community members.
-Everyone is more than welcome to contribute. It is a way to make the project better and more accessible to more users.
-
-- Please add your name to [CONTRIBUTORS.md](https://github.com/dmlc/tvm/blob/master/CONTRIBUTORS.md)
-- Please update [NEWS.md](https://github.com/dmlc/tvm/blob/master/NEWS.md) to add note on your changes to the API or added a new document.
-
-## Guidelines
-* [Submit Pull Request](#submit-pull-request)
-* [Git Workflow Howtos](#git-workflow-howtos)
-  - [How to resolve conflict with master](#how-to-resolve-conflict-with-master)
-  - [How to combine multiple commits into one](#how-to-combine-multiple-commits-into-one)
-  - [What is the consequence of force push](#what-is-the-consequence-of-force-push)
-* [Code Quality](#code-quality)
-* [Testcases](#testcases)
-* [Core Library](#core-library)
-* [Python Package](#python-package)
-
-## Submit Pull Request
-* Before submit, please rebase your code on the most recent version of master, you can do it by
-```bash
-git remote add upstream [url to tvm repo]
-git fetch upstream
-git rebase upstream/master
-```
-* If you have multiple small commits,
-  it might be good to merge them together(use git rebase then squash) into more meaningful groups.
-* Send the pull request!
-  - Fix the problems reported by automatic checks
-  - If you are contributing a new module or new function, add a test.
-
-## Git Workflow Howtos
-### How to resolve conflict with master
-- First rebase to most recent master
-```bash
-# The first two steps can be skipped after you do it once.
-git remote add upstream [url to tvm repo]
-git fetch upstream
-git rebase upstream/master
-```
-- The git may show some conflicts it cannot merge, say ```conflicted.py```.
-  - Manually modify the file to resolve the conflict.
-  - After you resolved the conflict, mark it as resolved by
-```bash
-git add conflicted.py
-```
-- Then you can continue rebase by
-```bash
-git rebase --continue
-```
-- Finally push to your fork, you may need to force push here.
-```bash
-git push --force
-```
-
-### How to combine multiple commits into one
-Sometimes we want to combine multiple commits, especially when later commits are only fixes to previous ones,
-to create a PR with set of meaningful commits. You can do it by following steps.
-- Before doing so, configure the default editor of git if you haven't done so before.
-```bash
-git config core.editor the-editor-you-like
-```
-- Assume we want to merge last 3 commits, type the following commands
-```bash
-git rebase -i HEAD~3
-```
-- It will pop up an text editor. Set the first commit as ```pick```, and change later ones to ```squash```.
-- After you saved the file, it will pop up another text editor to ask you modify the combined commit message.
-- Push the changes to your fork, you need to force push.
-```bash
-git push --force
-```
-
-### Reset to the most recent master
-You can always use git reset to reset your version to the most recent master.
-Note that all your ***local changes will get lost***.
-So only do it when you do not have local changes or when your pull request just get merged.
-```bash
-git reset --hard [hash tag of master]
-git push --force
-```
-
-### What is the consequence of force push
-The previous two tips requires force push, this is because we altered the path of the commits.
-It is fine to force push to your own fork, as long as the commits changed are only yours.
-
-## Code Quality
-
-Each patch we made can also become a potential technical debt in the future. We value high-quality code that can be understood and maintained by the community. We encourage contributors to submit coverage test cases, write or contribute comments on their code to explain the logic and provide reviews feedbacks to hold these standards.
-
-## Testcases
-- All the testcases are in tests
-
-## Core Library
-- Follow Google C style for C++.
-- We use doxygen to document all the interface code.
-- You can reproduce the linter checks by typing ```make lint```
-
-## Python Package
-- Always add docstring to the new functions in numpydoc format.
-- You can reproduce the linter checks by typing ```make lint```
diff --git a/docs/index.rst b/docs/index.rst
index 28f6b4052501..f46775d15837 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -6,11 +6,10 @@ Get Started
 .. toctree::
    :maxdepth: 1
 
-   how_to/install
+   install/index
    tutorials/index
-   how_to/deploy
-   how_to/integrate
-   how_to/contribute
+   deploy/index
+   contribute/index
    faq
 
 API Reference
diff --git a/docs/how_to/install.md b/docs/install/index.rst
similarity index 54%
rename from docs/how_to/install.md
rename to docs/install/index.rst
index c71c823de0c6..45697d53f7aa 100644
--- a/docs/how_to/install.md
+++ b/docs/install/index.rst
@@ -1,95 +1,113 @@
-Installation Guide
-==================
+Installation
+============
 This page gives instructions on how to build and install the tvm package from
 scratch on various systems. It consists of two steps:
 
 1. First build the shared library from the C++ codes (`libtvm.so` for linux/osx and `libtvm.dll` for windows).
 2. Setup for the language packages (e.g. Python Package).
 
-To get started, clone tvm repo from github. It is important to clone the submodules along, with ```--recursive``` option.
-```bash
-git clone --recursive https://github.com/dmlc/tvm
-```
+To get started, clone tvm repo from github. It is important to clone the submodules along, with ``--recursive`` option.
+
+.. code:: bash
+
+    git clone --recursive https://github.com/dmlc/tvm
+
 For windows users who use github tools, you can open the git shell, and type the following command.
-```bash
-git submodule init
-git submodule update
-```
 
-## Contents
-- [Build the Shared Library](#build-the-shared-library)
-- [Python Package Installation](#python-package-installation)
+.. code:: bash
+
+   git submodule init
+   git submodule update
 
-## Build the Shared Library
+
+Build the Shared Library
+------------------------
 
 Our goal is to build the shared libraries:
+
 - On Linux the target library are `libtvm.so, libtvm_topi.so`
 - On OSX the target library are `libtvm.dylib, libtvm_topi.dylib`
 - On Windows the target library are `libtvm.dll, libtvm_topi.dll`
 
-```bash
-sudo apt-get update
-sudo apt-get install -y python python-dev python-setuptools gcc libtinfo-dev zlib1g-dev
-```
 
-The minimal building requirement is
+.. code:: bash
+
+    sudo apt-get update
+    sudo apt-get install -y python python-dev python-setuptools gcc libtinfo-dev zlib1g-dev
+
+The minimal building requirements are
+
 - A recent c++ compiler supporting C++ 11 (g++-4.8 or higher)
 - We highly recommend to build with LLVM to enable all the features.
 - It is possible to build without llvm dependency if we only want to use CUDA/OpenCL
 
-The configuration of tvm can be modified by ```config.mk```
-- First copy ```make/config.mk``` to the project root, on which
+The configuration of tvm can be modified by `config.mk`
+
+- First copy ``make/config.mk`` to the project root, on which
   any local modification will be ignored by git, then modify the according flags.
-  - On macOS, for some versions of XCode, you need to add ```-lc++abi``` in the LDFLAGS or you'll get link errors.
+
+  - On macOS, for some versions of XCode, you need to add ``-lc++abi`` in the LDFLAGS or you'll get link errors.
+
 - TVM optionally depends on LLVM. LLVM is required for CPU codegen that needs LLVM.
+
   - LLVM 4.0 or higher is needed for build with LLVM. Note that verison of LLVM from default apt may lower than 4.0.
   - Since LLVM takes long time to build from source, you can download pre-built version of LLVM from
     [LLVM Download Page](http://releases.llvm.org/download.html).
-    - Unzip to a certain location, modify ```config.mk``` to add ```LLVM_CONFIG=/path/to/your/llvm/bin/llvm-config```
+
+    - Unzip to a certain location, modify ``config.mk`` to add ``LLVM_CONFIG=/path/to/your/llvm/bin/llvm-config``
+
   - You can also use [LLVM Nightly Ubuntu Build](https://apt.llvm.org/)
-    - Note that apt-package append ```llvm-config``` with version number.
-      For example, set ```LLVM_CONFIG=llvm-config-4.0``` if you installed 4.0 package
+
+    - Note that apt-package append ``llvm-config`` with version number.
+      For example, set ``LLVM_CONFIG=llvm-config-4.0`` if you installed 4.0 package
 
 We can then build tvm by `make`.
 
-```bash
-make -j4
-```
+.. code:: bash
+
+  make -j4
 
 After we build tvm, we can proceed to build nnvm using the following script.
 
-```bash
-cd nnvm
-make -j4
-```
+.. code:: bash
+
+  cd nnvm
+  make -j4
+
 
 This will creates `libnnvm_compiler.so` under the `nnvm/lib` folder.
 If everything goes well, we can go to the specific language installation section.
 
 
-### Building on Windows
+Building on Windows
+~~~~~~~~~~~~~~~~~~~
 
 TVM support build via MSVC using cmake. The minimum required VS version is **Visual Studio Community 2015 Update 3**.
 In order to generate the VS solution file using cmake,
 make sure you have a recent version of cmake added to your path and then from the tvm directory:
 
-```bash
-mkdir build
-cd build
-cmake -G "Visual Studio 14 2015 Win64" -DCMAKE_BUILD_TYPE=Release -DCMAKE_CONFIGURATION_TYPES="Release" ..
-```
+.. code:: bash
+
+  mkdir build
+  cd build
+  cmake -G "Visual Studio 14 2015 Win64" -DCMAKE_BUILD_TYPE=Release -DCMAKE_CONFIGURATION_TYPES="Release" ..
+
 This will generate the VS project using the MSVC 14 64 bit generator.
 Open the .sln file in the build directory and build with Visual Studio.
 In order to build with LLVM in windows, you will need to build LLVM from source.
 You need to run build the nnvm by running the same script under the nnvm folder.
 
-### Building ROCm support
+Building ROCm support
+~~~~~~~~~~~~~~~~~~~~~
+
 Currently, ROCm is supported only on linux, so all the instructions are written with linux in mind.
-- Set ```USE_ROCM=1```, set ROCM_PATH to the correct path.
+
+- Set ``USE_ROCM=1``, set ROCM_PATH to the correct path.
 - You need to first install HIP runtime from ROCm. Make sure the installation system has ROCm installed in it.
-- Install latest stable version of LLVM (v6.0.1), and LLD, make sure ```ld.lld``` is available via command line.
+- Install latest stable version of LLVM (v6.0.1), and LLD, make sure ``ld.lld`` is available via command line.
 
-## Python Package Installation
+Python Package Installation
+---------------------------
 
 The python package is located at python
 There are several ways to install the package:
@@ -97,22 +115,31 @@ There are several ways to install the package:
 1. Set the environment variable `PYTHONPATH` to tell python where to find
    the library. For example, assume we cloned `tvm` on the home directory
    `~`. then we can added the following line in `~/.bashrc`.
-    It is ***recommended for developers*** who may change the codes.
-    The changes will be immediately reflected once you pulled the code and rebuild the project (no need to call ```setup``` again)
+   It is **recommended for developers** who may change the codes.
+   The changes will be immediately reflected once you pulled the code and rebuild the project (no need to call ``setup`` again)
+
+   .. code:: bash
+
+       export PYTHONPATH=/path/to/tvm/python:/path/to/tvm/topi/python:/path/to/tvm/nnvm/python:${PYTHONPATH}
 
-    ```bash
-    export PYTHONPATH=/path/to/tvm/python:/path/to/tvm/topi/python:/path/to/tvm/nnvm/python:${PYTHONPATH}
-    ```
 
 2. Install tvm python bindings by `setup.py`:
 
-    ```bash
-    # install tvm package for the current user
-    # NOTE: if you installed python via homebrew, --user is not needed during installaiton
-    #       it will be automatically installed to your user directory.
-    #       providing --user flag may trigger error during installation in such case.
-    export MACOSX_DEPLOYMENT_TARGET=10.9  # This is required for mac to avoid symbol conflicts with libstdc++
-    cd python; python setup.py install --user; cd ..
-    cd topi/python; python setup.py install --user; cd ../..
-    cd nnvm/python; python setup.py install --user; cd ../..
-    ```
+   .. code:: bash
+
+       # install tvm package for the current user
+       # NOTE: if you installed python via homebrew, --user is not needed during installaiton
+       #       it will be automatically installed to your user directory.
+       #       providing --user flag may trigger error during installation in such case.
+       export MACOSX_DEPLOYMENT_TARGET=10.9  # This is required for mac to avoid symbol conflicts with libstdc++
+       cd python; python setup.py install --user; cd ..
+       cd topi/python; python setup.py install --user; cd ../..
+       cd nnvm/python; python setup.py install --user; cd ../..
+
+Install Contrib Libraries
+-------------------------
+
+.. toctree::
+   :maxdepth: 1
+
+   nnpack
diff --git a/docs/how_to/nnpack.md b/docs/install/nnpack.md
similarity index 65%
rename from docs/how_to/nnpack.md
rename to docs/install/nnpack.md
index d271af86b35a..1e9bd22ba200 100644
--- a/docs/how_to/nnpack.md
+++ b/docs/install/nnpack.md
@@ -1,4 +1,4 @@
-### NNPACK for Multi-Core CPU Support in TVM
+# NNPACK Contrib Installation
 
 [NNPACK](https://github.com/Maratyszcza/NNPACK) is an acceleration package
 for neural network computations, which can run on x86-64, ARMv7, or ARM64 architecture CPUs.
@@ -11,7 +11,7 @@ For regular use prefer native tuned TVM implementation.
 _TVM_ supports NNPACK for forward propagation (inference only) in convolution, max-pooling, and fully-connected layers.
 In this document, we give a high level overview of how to use NNPACK with _TVM_.
 
-### Conditions
+## Conditions
 The underlying implementation of NNPACK utilizes several acceleration methods,
 including [fft](https://arxiv.org/abs/1312.5851) and [winograd](https://arxiv.org/abs/1509.09308).
 These algorithms work better on some special `batch size`, `kernel size`, and `stride` settings than on other,
@@ -19,48 +19,8 @@ so depending on the context, not all convolution, max-pooling, or fully-connecte
 When favorable conditions for running NNPACKS are not met,
 
 NNPACK only supports Linux and OS X systems. Windows is not supported at present.
-The following table explains under which conditions NNPACK will work.
 
-| operation      | conditions |
-|:---------      |:---------- |
-|convolution     |2d convolution `and` no-bias=False `and` dilate=(1,1) `and` num_group=1 `and` batch-size = 1 or batch-size > 1 && stride = (1,1);|
-|pooling         | max-pooling `and` kernel=(2,2) `and` stride=(2,2) `and` pooling_convention=full    |
-|fully-connected| without any restrictions |
-
-### Build/Install LLVM
-LLVM is required for CPU codegen that needs LLVM.
-Since LLVM takes long time to build from source, you can download pre-built version of LLVM from [LLVM Download Page](http://releases.llvm.org/download.html).
-For llvm 4.0 you can do the following step :
-
-```bash
-# Add llvm repository in apt source list
-echo "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-4.0 main" >> /etc/apt/sources.list
-
-# Update apt source list
-apt-get update
-# Install clang and full llvm
-apt-get install -y \
-    clang-4.0 \
-    clang-4.0-doc \
-    libclang-common-4.0-dev \
-    libclang-4.0-dev \
-    libclang1-4.0 \
-    libclang1-4.0-dbg \
-    libllvm-4.0-ocaml-dev \
-    libllvm4.0 \
-    libllvm4.0-dbg \
-    lldb-4.0 \
-    llvm-4.0 \
-    llvm-4.0-dev \
-    llvm-4.0-doc \
-    llvm-4.0-examples \
-    llvm-4.0-runtime \
-    clang-format-4.0 \
-    python-clang-4.0 \
-    libfuzzer-4.0-dev
-```
-
-### Build/Install NNPACK
+## Build/Install NNPACK
 
 If the trained model meets some conditions of using NNPACK,
 you can build TVM with NNPACK support.
@@ -69,7 +29,7 @@ Follow these simple steps:
 
 Note: The following NNPACK installation instructions have been tested on Ubuntu 16.04.
 
-#### Build [Ninja](https://ninja-build.org/)
+### Build [Ninja](https://ninja-build.org/)
 
 NNPACK need a recent version of Ninja. So we need to install ninja from source.
 ```bash
@@ -83,7 +43,7 @@ Set the environment variable PATH to tell bash where to find the ninja executabl
 export PATH="${PATH}:~/ninja"
 ```
 
-#### Build [NNPACK](https://github.com/Maratyszcza/NNPACK)
+### Build [NNPACK](https://github.com/Maratyszcza/NNPACK)
 
 The new CMAKE version of NNPACK download [Peach](https://github.com/Maratyszcza/PeachPy) and other dependencies alone
 
@@ -105,7 +65,7 @@ echo "/usr/local/lib" > /etc/ld.so.conf.d/nnpack.conf
 sudo ldconfig
 ```
 
-### Build TVM with NNPACK support
+## Build TVM with NNPACK support
 
 ```bash
 git clone --recursive https://github.com/dmlc/tvm
diff --git a/jvm/README.md b/jvm/README.md
index 2438758617a6..46b377674efc 100644
--- a/jvm/README.md
+++ b/jvm/README.md
@@ -27,7 +27,7 @@ TVM4J contains three modules:
 
 ### Build
 
-First please refer to [Installation Guide](http://docs.tvm.ai/how_to/install.html) and build runtime shared library from the C++ codes (libtvm\_runtime.so for Linux and libtvm\_runtime.dylib for OSX).
+First please refer to [Installation Guide](http://docs.tvm.ai/install/) and build runtime shared library from the C++ codes (libtvm\_runtime.so for Linux and libtvm\_runtime.dylib for OSX).
 
 Then you can compile tvm4j by
 
diff --git a/python/tvm/api.py b/python/tvm/api.py
index 71ae62a3b222..081b67cb34c4 100644
--- a/python/tvm/api.py
+++ b/python/tvm/api.py
@@ -576,13 +576,16 @@ def reduce_axis(dom, name="rv"):
 
 
 def select(cond, t, f):
-    """Construct a select branch
+    """Construct a select branch.
+
     Parameters
     ----------
     cond : Expr
         The condition
+
     t : Expr
         The result expression if cond is true.
+
     f : Expr
         The result expression if cond is false.
 
@@ -593,6 +596,7 @@ def select(cond, t, f):
     """
     return _make.Select(convert(cond), convert(t), convert(f))
 
+
 def comm_reducer(fcombine, fidentity, name="reduce"):
     """Create a commutative reducer for reduction.
 

From e3529922a6e94528558ae6b35c5216ace217b2e2 Mon Sep 17 00:00:00 2001
From: "Ehsan M. Kermani" <ehsanmo1367@gmail.com>
Date: Fri, 1 Jun 2018 20:22:00 -0700
Subject: [PATCH 682/949] [DOC] topi intro tutorial (#1217)

---
 docs/conf.py                 |   1 +
 tutorials/topi/READM.txt     |   1 +
 tutorials/topi/intro_topi.py | 122 +++++++++++++++++++++++++++++++++++
 3 files changed, 124 insertions(+)
 create mode 100644 tutorials/topi/READM.txt
 create mode 100644 tutorials/topi/intro_topi.py

diff --git a/docs/conf.py b/docs/conf.py
index 84eeb9db79e7..00089636b3af 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -189,6 +189,7 @@ def run_doxygen(folder):
 subsection_order = ExplicitOrder(
     ['../tutorials/language',
      '../tutorials/optimize',
+     '../tutorials/topi',
      '../tutorials/deployment',
      '../tutorials/nnvm'])
 
diff --git a/tutorials/topi/READM.txt b/tutorials/topi/READM.txt
new file mode 100644
index 000000000000..c58aaa9d8290
--- /dev/null
+++ b/tutorials/topi/READM.txt
@@ -0,0 +1 @@
+TOPI: TVM Operator Inventory
\ No newline at end of file
diff --git a/tutorials/topi/intro_topi.py b/tutorials/topi/intro_topi.py
new file mode 100644
index 000000000000..c5bb23abcdd7
--- /dev/null
+++ b/tutorials/topi/intro_topi.py
@@ -0,0 +1,122 @@
+"""
+Introduction to TOPI
+====================
+**Author**: `Ehsan M. Kermani <https://github.com/ehsanmok>`_
+
+This is an introductory tutorial to TVM Operator Inventory (TOPI).
+
+TOPI provides numpy-style generic operations and schedules with higher abstractions than TVM.
+
+In this tutorial, we will see how TOPI can save us from writing boilerplates code in TVM.
+"""
+from __future__ import absolute_import, print_function
+
+import tvm
+import topi
+import numpy as np
+
+######################################################################
+# Basic example
+# -------------
+# Let's revisit the sum of rows operation (equivalent to :code:`B = numpy.sum(A, axis=1)`') \
+# from :ref:`reduction` tutorial. To compute the sum of rows of a two dimensional TVM tensor A, we should
+# specify the symbolic operation as well as schedule as follows
+#
+n = tvm.var("n")
+m = tvm.var("m")
+A = tvm.placeholder((n, m), name='A')
+k = tvm.reduce_axis((0, m), "k")
+B = tvm.compute((n,), lambda i: tvm.sum(A[i, k], axis=k), name="B")
+s = tvm.create_schedule(B.op)
+
+# and to examine the IR code in human readable format, we can do
+#
+print(tvm.lower(s, [A], simple_mode=True))
+
+# However, for such a common operation we had to define the reduce axis ourselves as well as explicit computation with
+# :code: `tvm.compute`. Imagine for more complicated operations how much details we need to provide.
+# Fortunately, we can replace those two lines with simple :code:`topi.sum` much like :code`numpy.sum`
+#
+C = topi.sum(A, axis=1)
+ts = tvm.create_schedule(C.op)
+print(tvm.lower(ts, [A], simple_mode=True))
+
+######################################################################
+# Numpy-style operator overloading
+# --------------------------------
+# We can add two tensors using :code:`topi.broadcast_add` that have correct (broadcastable with specific) shapes.
+# Even shorter, TOPI provides operator overloading for such common operations. For example,
+#
+x, y = 100, 10
+a = tvm.placeholder((x, y, y), name="a")
+b = tvm.placeholder((y, y), name="b")
+c = a + b  # same as topi.broadcast_add
+d = a * b  # same as topi.broadcast_mul
+
+# Overloaded with the same syntax, TOPI handles broadcasting a primitive (`int`, `float`) to a tensor :code:`d - 3.14`.
+
+######################################################################
+# Generic schedules and fusing operations
+# ---------------------------------------
+# Up to now, we have seen an example of how TOPI can save us from writing explicit computations in lower level API.
+# But it doesn't stop here. Still we did the scheduling as before. TOPI also provides higher level
+# scheduling recipes depending on a given context. For example, for CUDA,
+# we can schedule the following series of operations ending with :code:`topi.sum` using only
+# :code:`topi.generic.schedule_reduce`
+#
+e = topi.elemwise_sum([c, d], num_args=2)
+f = e / 2.0
+g = topi.sum(f)
+with tvm.target.cuda():
+    sg = topi.generic.schedule_reduce(g)
+    print(tvm.lower(sg, [a, b], simple_mode=True))
+
+# As you can see, scheduled stages of computation have been accumulated and we can examine them by
+#
+print(sg.stages)
+
+# We can test the correctness by comparing with :code:`numpy` result as follows
+#
+func = tvm.build(sg, [a, b, g], 'cuda')
+ctx = tvm.gpu(0)
+a_np = np.random.uniform(size=(x, y, y)).astype(a.dtype)
+b_np = np.random.uniform(size=(y, y)).astype(b.dtype)
+g_np = np.sum(np.add(a_np + b_np, a_np * b_np) / 2.0)
+a_nd = tvm.nd.array(a_np, ctx)
+b_nd = tvm.nd.array(b_np, ctx)
+g_nd = tvm.nd.array(np.zeros(g_np.shape, dtype=g_np.dtype), ctx)
+func(a_nd, b_nd, g_nd)
+np.testing.assert_allclose(g_nd.asnumpy(), g_np, rtol=1e-5)
+
+# TOPI also provides common neural nets operations such as _softmax_ with optimized schedule
+#
+tarray = tvm.placeholder((512, 512), name="tarray")
+softmax_topi = topi.nn.softmax(tarray)
+with tvm.target.create("cuda"):
+    sst = topi.generic.schedule_softmax(softmax_topi)
+    print(tvm.lower(sst, [tarray], simple_mode=True))
+
+######################################################################
+# Fusing convolutions
+# ------------------
+# We can fuse :code:`topi.nn.conv2d` and :code:`topi.nn.relu` together
+#
+data = tvm.placeholder((1, 3, 224, 224))
+kernel = tvm.placeholder((10, 3, 5, 5))
+conv = topi.nn.conv2d(data, kernel, stride=1, padding=2)
+out = topi.nn.relu(conv)
+with tvm.target.create("cuda"):
+    sconv = topi.generic.nn.schedule_conv2d_nchw(out)
+    print(tvm.lower(sconv, [data, kernel], simple_mode=True))
+
+# .. note::
+# .. |Here| replace:: :mod:topi
+# |Here|, you can find the list of supported operations and schedules in TOPI.
+
+######################################################################
+# Summary
+# -------
+# In this tutorial, we have seen
+#
+# - How to use TOPI API for common operations with numpy-style operators.
+# - How TOPI facilitates generic schedules and operator fusion for a context, to generate optimized kernel codes.
\ No newline at end of file

From 9ea15afabf3f856c2d44f0d02e1286cfec52429b Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 3 Jun 2018 10:05:43 -0700
Subject: [PATCH 683/949] [DOCS] Fix topi tutorial (#1222)

---
 tutorials/topi/{READM.txt => README.txt} |  0
 tutorials/topi/intro_topi.py             | 18 +++++++++---------
 2 files changed, 9 insertions(+), 9 deletions(-)
 rename tutorials/topi/{READM.txt => README.txt} (100%)

diff --git a/tutorials/topi/READM.txt b/tutorials/topi/README.txt
similarity index 100%
rename from tutorials/topi/READM.txt
rename to tutorials/topi/README.txt
diff --git a/tutorials/topi/intro_topi.py b/tutorials/topi/intro_topi.py
index c5bb23abcdd7..a551f96c761b 100644
--- a/tutorials/topi/intro_topi.py
+++ b/tutorials/topi/intro_topi.py
@@ -4,9 +4,7 @@
 **Author**: `Ehsan M. Kermani <https://github.com/ehsanmok>`_
 
 This is an introductory tutorial to TVM Operator Inventory (TOPI).
-
 TOPI provides numpy-style generic operations and schedules with higher abstractions than TVM.
-
 In this tutorial, we will see how TOPI can save us from writing boilerplates code in TVM.
 """
 from __future__ import absolute_import, print_function
@@ -19,7 +17,7 @@
 # Basic example
 # -------------
 # Let's revisit the sum of rows operation (equivalent to :code:`B = numpy.sum(A, axis=1)`') \
-# from :ref:`reduction` tutorial. To compute the sum of rows of a two dimensional TVM tensor A, we should
+# To compute the sum of rows of a two dimensional TVM tensor A, we should
 # specify the symbolic operation as well as schedule as follows
 #
 n = tvm.var("n")
@@ -29,10 +27,12 @@
 B = tvm.compute((n,), lambda i: tvm.sum(A[i, k], axis=k), name="B")
 s = tvm.create_schedule(B.op)
 
+######################################################################
 # and to examine the IR code in human readable format, we can do
 #
 print(tvm.lower(s, [A], simple_mode=True))
 
+######################################################################
 # However, for such a common operation we had to define the reduce axis ourselves as well as explicit computation with
 # :code: `tvm.compute`. Imagine for more complicated operations how much details we need to provide.
 # Fortunately, we can replace those two lines with simple :code:`topi.sum` much like :code`numpy.sum`
@@ -53,6 +53,7 @@
 c = a + b  # same as topi.broadcast_add
 d = a * b  # same as topi.broadcast_mul
 
+######################################################################
 # Overloaded with the same syntax, TOPI handles broadcasting a primitive (`int`, `float`) to a tensor :code:`d - 3.14`.
 
 ######################################################################
@@ -71,10 +72,12 @@
     sg = topi.generic.schedule_reduce(g)
     print(tvm.lower(sg, [a, b], simple_mode=True))
 
+######################################################################
 # As you can see, scheduled stages of computation have been accumulated and we can examine them by
 #
 print(sg.stages)
 
+######################################################################
 # We can test the correctness by comparing with :code:`numpy` result as follows
 #
 func = tvm.build(sg, [a, b, g], 'cuda')
@@ -88,6 +91,7 @@
 func(a_nd, b_nd, g_nd)
 np.testing.assert_allclose(g_nd.asnumpy(), g_np, rtol=1e-5)
 
+######################################################################
 # TOPI also provides common neural nets operations such as _softmax_ with optimized schedule
 #
 tarray = tvm.placeholder((512, 512), name="tarray")
@@ -98,7 +102,7 @@
 
 ######################################################################
 # Fusing convolutions
-# ------------------
+# -------------------
 # We can fuse :code:`topi.nn.conv2d` and :code:`topi.nn.relu` together
 #
 data = tvm.placeholder((1, 3, 224, 224))
@@ -109,14 +113,10 @@
     sconv = topi.generic.nn.schedule_conv2d_nchw(out)
     print(tvm.lower(sconv, [data, kernel], simple_mode=True))
 
-# .. note::
-# .. |Here| replace:: :mod:topi
-# |Here|, you can find the list of supported operations and schedules in TOPI.
-
 ######################################################################
 # Summary
 # -------
 # In this tutorial, we have seen
 #
 # - How to use TOPI API for common operations with numpy-style operators.
-# - How TOPI facilitates generic schedules and operator fusion for a context, to generate optimized kernel codes.
\ No newline at end of file
+# - How TOPI facilitates generic schedules and operator fusion for a context, to generate optimized kernel codes.

From 3927dd124f24dfda3a74b3dc69738dffc1f4d8d9 Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Tue, 5 Jun 2018 00:42:11 +0530
Subject: [PATCH 684/949] [NNVM] Elu support added in darknet frontend (#1199)

---
 nnvm/python/nnvm/frontend/darknet.py               |  4 ++++
 nnvm/tests/python/frontend/darknet/test_forward.py | 14 +++++++++++++-
 2 files changed, 17 insertions(+), 1 deletion(-)

diff --git a/nnvm/python/nnvm/frontend/darknet.py b/nnvm/python/nnvm/frontend/darknet.py
index cb73b5a4345a..112e5294396a 100644
--- a/nnvm/python/nnvm/frontend/darknet.py
+++ b/nnvm/python/nnvm/frontend/darknet.py
@@ -302,6 +302,8 @@ def _darknet_activations(inputs, attrs):
         return inputs, None
     elif ACTIVATION.LEAKY == act:
         act_type = 'leaky_relu'
+    elif ACTIVATION.ELU == act:
+        act_type = 'elu'
     else:
         _darknet_raise_not_supported('act: ' + act)
 
@@ -312,6 +314,8 @@ def _darknet_activations(inputs, attrs):
         op_name, new_attrs = act_type, {}
         new_attrs['alpha'] = attrs.get('slope', 0.1)
         sym = _darknet_get_nnvm_op(op_name)(*inputs, **new_attrs)
+    elif act_type in ['elu']:
+        sym = -1 * _sym.relu(1 - _sym.exp(*inputs)) + _sym.relu(*inputs)
     else:
         _darknet_raise_not_supported('act_type: ' + act_type)
     return sym, None
diff --git a/nnvm/tests/python/frontend/darknet/test_forward.py b/nnvm/tests/python/frontend/darknet/test_forward.py
index ad28c49c014d..a61a8d5a9738 100644
--- a/nnvm/tests/python/frontend/darknet/test_forward.py
+++ b/nnvm/tests/python/frontend/darknet/test_forward.py
@@ -138,7 +138,7 @@ def test_forward_resnet50():
 
 def test_forward_yolo():
     '''test yolo model'''
-    model_name = 'yolo'
+    model_name = 'yolov2'
     cfg_name = model_name + '.cfg'
     weights_name = model_name + '.weights'
     cfg_url = 'https://github.com/pjreddie/darknet/blob/master/cfg/' + cfg_name + '?raw=true'
@@ -242,6 +242,17 @@ def test_forward_region():
     test_forward(net)
     LIB.free_network(net)
 
+def test_forward_elu():
+    '''test elu activation layer'''
+    net = LIB.make_network(1)
+    layer_1 = LIB.make_convolutional_layer(1, 224, 224, 3, 32, 1, 3, 2, 0, 1, 0, 0, 0, 0)
+    layer_1.activation = 8
+    net.layers[0] = layer_1
+    net.w = net.h = 224
+    LIB.resize_network(net, 224, 224)
+    test_forward(net)
+    LIB.free_network(net)
+
 if __name__ == '__main__':
     test_forward_resnet50()
     test_forward_alexnet()
@@ -255,3 +266,4 @@ def test_forward_region():
     test_forward_dense()
     test_forward_reorg()
     test_forward_region()
+    test_forward_elu()

From 133c29b6080ba98732369f9eb263c8cbe1086ae8 Mon Sep 17 00:00:00 2001
From: Liangfu Chen <liangfu.chen@icloud.com>
Date: Wed, 6 Jun 2018 04:50:31 +0800
Subject: [PATCH 685/949] add test to irbuilder for gpu execution (#1228)

---
 tests/python/unittest/test_codegen_extern.py | 45 ++++++++----
 tests/python/unittest/test_ir_builder.py     | 77 ++++++++++++++++++++
 2 files changed, 109 insertions(+), 13 deletions(-)

diff --git a/tests/python/unittest/test_codegen_extern.py b/tests/python/unittest/test_codegen_extern.py
index 1295ed26cce7..dfbf1820c21d 100644
--- a/tests/python/unittest/test_codegen_extern.py
+++ b/tests/python/unittest/test_codegen_extern.py
@@ -2,36 +2,55 @@
 import numpy as np
 
 def test_add_pipeline():
-    nn = 1024
+    nn = 64
+    max_threads = 4
     n = tvm.convert(nn)
     A = tvm.placeholder((n,), name='A')
 
     def extern_generator(ins, outs):
         """Manually write the IR for the extern function, add pipeline"""
         ib = tvm.ir_builder.create()
-        with ib.for_range(0, n/2) as i:
+        with ib.for_range(0, (n+1) // 2) as i:
             ib.emit(outs[0].vstore(i*2, ins[0].vload(i*2, "float32x2") + tvm.const(1, "float32x2")))
         return ib.get()
 
-    C = tvm.extern(A.shape, [A], extern_generator, name='C')
-    s = tvm.create_schedule(C.op)
-    print(tvm.lower(s, [A, C], simple_mode=True))
+    def extern_generator_gpu(ins, outs):
+        """Manually write the IR for the extern function, add pipeline"""
+        ib = tvm.ir_builder.create()
+        bx = tvm.thread_axis("blockIdx.x")
+        tx = tvm.thread_axis("threadIdx.x")
+        ib.scope_attr(bx, "thread_extent", (nn+max_threads-1) // max_threads)
+        ib.scope_attr(tx, "thread_extent", max_threads)
+        idx = bx.var * max_threads + tx.var
+        with ib.if_scope(ib.likely(idx < n)):
+            ib.emit(outs[0].vstore(idx*2, ins[0].vload(idx*2, "float32x2") + tvm.const(1, "float32x2")))
+        return ib.get()
 
-    def check_llvm():
-        if not tvm.module.enabled("llvm"):
+    C_cpu = tvm.extern(A.shape, [A], extern_generator, name='C')
+    C_gpu = tvm.extern(A.shape, [A], extern_generator_gpu, name='C')
+    s_cpu = tvm.create_schedule(C_cpu.op)
+    s_gpu = tvm.create_schedule(C_gpu.op)
+    print(tvm.lower(s_cpu, [A, C_cpu], simple_mode=True))
+    print(tvm.lower(s_gpu, [A, C_gpu], simple_mode=True))
+
+    def check_target(target):
+        if not tvm.module.enabled(target):
             return
+        s = s_gpu if target in ['opencl', 'cuda'] else s_cpu
+        C = C_gpu if target in ['opencl', 'cuda'] else C_cpu
         # build and invoke the kernel.
-        f = tvm.build(s, [A, C], "llvm")
-        ctx = tvm.cpu(0)
+        f = tvm.build(s, [A, C], target)
+        ctx = tvm.context(target, 0)
         # launch the kernel.
         n = nn
         a = tvm.nd.array(np.random.uniform(size=n).astype(A.dtype), ctx)
         c = tvm.nd.array(np.zeros(n, dtype=C.dtype), ctx)
         f(a, c)
-        np.testing.assert_allclose(
-            c.asnumpy(), a.asnumpy() + 1)
-    check_llvm()
-
+        np.testing.assert_allclose(c.asnumpy(), a.asnumpy() + 1)
+        
+    check_target("llvm")
+    check_target("opencl")
+    check_target("cuda")
 
 def test_pack_buffer_simple():
     nn = 1024
diff --git a/tests/python/unittest/test_ir_builder.py b/tests/python/unittest/test_ir_builder.py
index 86b43c3f7980..864455d01ad9 100644
--- a/tests/python/unittest/test_ir_builder.py
+++ b/tests/python/unittest/test_ir_builder.py
@@ -1,4 +1,5 @@
 import tvm
+import numpy as np
 
 def test_for():
     ib = tvm.ir_builder.create()
@@ -53,8 +54,84 @@ def test_prefetch():
     body = ib.get()
     assert body.body.bounds[0].extent.value == 2
 
+def test_cpu():
+    n = 1024
+    dtype = "float32"
+    A = tvm.placeholder((n,), name='A')
+    B = tvm.placeholder((n,), name='B')
+    def test_device_ir(A, B, C):
+        n = A.shape[0]
+        max_threads = 8
+        ib = tvm.ir_builder.create()
+        Aptr = ib.buffer_ptr(A)
+        Bptr = ib.buffer_ptr(B)
+        Cptr = ib.buffer_ptr(C)
+        with ib.for_range(0, n, name="i") as i:
+            Cptr[i] = Aptr[i] + Bptr[i]
+        body = ib.get()
+        return body
+    C = tvm.extern(A.shape, [A, B], lambda ins, outs: test_device_ir(ins[0], ins[1], outs[0]),
+                   name="vector_add", dtype=dtype)
+    s = tvm.create_schedule(C.op)
+    def check_target(target):
+        if not tvm.module.enabled(target):
+            return
+        # build and invoke the kernel.
+        fadd = tvm.build(s, [A, B, C], target)
+        ctx = tvm.context(target, 0)
+        # launch the kernel.
+        a = tvm.nd.array(np.random.uniform(size=n).astype(A.dtype), ctx)
+        b = tvm.nd.array(np.random.uniform(size=n).astype(B.dtype), ctx)
+        c = tvm.nd.array(np.zeros(n, dtype=C.dtype), ctx)
+        fadd(a, b, c)
+        np.testing.assert_allclose(c.asnumpy(), a.asnumpy() + b.asnumpy())
+    check_target("llvm")
+
+def test_gpu():
+    n = tvm.var('n')
+    dtype = "float32"
+    A = tvm.placeholder((n,), name='A')
+    B = tvm.placeholder((n,), name='B')
+    def test_device_ir(A, B, C):
+        n = A.shape[0]
+        max_threads = 32
+        ib = tvm.ir_builder.create()
+        bx = tvm.thread_axis("blockIdx.x")
+        tx = tvm.thread_axis("threadIdx.x")
+        ib.scope_attr(bx, "thread_extent", (n+max_threads-1) // max_threads)
+        ib.scope_attr(tx, "thread_extent", max_threads)
+        idx = bx.var * max_threads + tx.var
+        Aptr = ib.buffer_ptr(A)
+        Bptr = ib.buffer_ptr(B)
+        Cptr = ib.buffer_ptr(C)
+        with ib.if_scope(ib.likely(idx<n)):
+            Cptr[idx] = Aptr[idx] + Bptr[idx]
+        body = ib.get()
+        return body
+    C = tvm.extern(A.shape, [A, B], lambda ins, outs: test_device_ir(ins[0], ins[1], outs[0]),
+                   name="vector_add", dtype=dtype)
+    s = tvm.create_schedule(C.op)
+    bounds = tvm.schedule.InferBound(s)
+    stmt = tvm.schedule.ScheduleOps(s, bounds)
+    def check_target(target):
+        n = 1024
+        if not tvm.module.enabled(target):
+            return
+        # build and invoke the kernel.
+        fadd = tvm.build(s, [A, B, C], target)
+        ctx = tvm.context(target, 0)
+        # launch the kernel.
+        a = tvm.nd.array(np.random.uniform(size=n).astype(A.dtype), ctx)
+        b = tvm.nd.array(np.random.uniform(size=n).astype(B.dtype), ctx)
+        c = tvm.nd.array(np.zeros(n, dtype=C.dtype), ctx)
+        fadd(a, b, c)
+        np.testing.assert_allclose(c.asnumpy(), a.asnumpy() + b.asnumpy())
+    check_target("opencl")
+    check_target("cuda")
 
 if __name__ == "__main__":
     test_prefetch()
     test_if()
     test_for()
+    test_cpu()
+    test_gpu()

From cbec8f851ff23750f1366a2d0d02d1d63a0b2505 Mon Sep 17 00:00:00 2001
From: larrywyang <larrywyang@gmail.com>
Date: Tue, 5 Jun 2018 13:51:38 -0700
Subject: [PATCH 686/949] [WIP] [NNVM] Fix softmax gradient (#1201)

[NNVM] Fix softmax gradient
---
 nnvm/src/top/nn/nn.cc                         | 38 ++++++++++---------
 nnvm/tests/python/compiler/test_top_level1.py |  4 +-
 2 files changed, 23 insertions(+), 19 deletions(-)

diff --git a/nnvm/src/top/nn/nn.cc b/nnvm/src/top/nn/nn.cc
index e1dbe3cfcb06..cedfb210855e 100644
--- a/nnvm/src/top/nn/nn.cc
+++ b/nnvm/src/top/nn/nn.cc
@@ -366,22 +366,24 @@ NNVM_REGISTER_OP(softmax)
     // [ ...                  ,-ynyn + yn]
     //
     // grad_x =
-    // [-y1*(ograd1*y1 - 1 + ograd2*y2 + ..., -y2*(ograd1*y1 - 1 + ograd2*y2, ..., ...]]
+    // [-y1*(ograd1*y1 - ograd1 + ograd2*y2 + ...),
+    //  -y2*(ograd1*y1 - ograd2 + ograd2*y2 + ...),
+    //  ...
+    //  -yn*(ograd1*y1 - ogradn + ograd2*y2 + ...)]
 
     // grad_x = ograd elemwise_mul output
     // grad_x = sum(grad_x, keepdim, axis)
     // grad_x = grad_x broadcast_mul output
     // grad_x = neg grad_x
-    // grad_x = grad_x + output
+    // grad_x = grad_x + ograd elemwise_mul output
     const SoftmaxParam& param = nnvm::get<SoftmaxParam>(n->attrs.parsed);
     NodeEntry output =  NodeEntry{n, 0, 0};
     NodeEntry sub0 = MakeNode("elemwise_mul", n->attrs.name + "_grad_sub0", {ograds[0], output});
     NodeEntry sub1 = MakeNode("sum", n->attrs.name + "_grad_sub1", {sub0},
                               {{"axis", std::to_string(param.axis)}, {"keepdims", "true"}});
     NodeEntry sub2 = MakeNode("broadcast_mul", n->attrs.name + "_grad_sub2", {sub1, output});
-    NodeEntry sub3 = MakeNode("negative", n->attrs.name + "_grad_sub3", {sub2});
     return std::vector<NodeEntry> {
-      MakeNode("elemwise_add", n->attrs.name + "_grad", {sub3, output})
+      MakeNode("elemwise_sub", n->attrs.name + "_grad", {sub0, sub2})
     };
 });
 
@@ -414,31 +416,33 @@ NNVM_REGISTER_OP(log_softmax)
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
-    // grad_x = grad_y dot jacobian of softmax
+    // grad_x = grad_y dot jacobian of logsoftmax
     //
-    // jacobian of softmax
+    // jacobian of logsoftmax
     // [-y1 + 1, -y2,        ...    ]
     // [ ...   , -y2 + 1,    ...    ]
     // [ ...                 ...    ]
     // [ ...                ,-yn + 1]
     //
     // grad_x =
-    // [-(ograd1*y1 - 1 + ograd2*y2 + ..., -(ograd1*y1 - 1 + ograd2*y2, ..., ...]]
-
-    // grad_x = ograd elemwise_mul output
-    // grad_x = sum(grad_x, keepdim, axis)
+    // [ograd1 - exp(y1)*(ograd1 + ... + ogradn),
+    //  ograd2 - exp(y2)*(ograd1 + ... + ogradn),
+    //  ...
+    //  ogradn - exp(yn)*(ograd1 + ... + ogradn)]
+
+    // grad_x = sum(ograd, keepdim, axis)
+    // sigma = exp(output)
+    // grad_x = grad_x elemwise_mul sigma
     // grad_x = neg grad_x
-    // grad_x = grad_x + ones_like(grad_x)
+    // grad_x = grad_x + ograd
     const SoftmaxParam& param = nnvm::get<SoftmaxParam>(n->attrs.parsed);
     NodeEntry output =  NodeEntry{n, 0, 0};
-    NodeEntry sub0 = MakeNode("elemwise_mul", n->attrs.name + "_grad_sub0", {ograds[0], output});
-    NodeEntry sub1 = MakeNode("sum", n->attrs.name + "_grad_sub1", {sub0},
+    NodeEntry sub0 = MakeNode("sum", n->attrs.name + "_grad_sub0", {ograds[0]},
                               {{"axis", std::to_string(param.axis)}, {"keepdims", "true"}});
-    NodeEntry sub2 = MakeNode("full_like", n->attrs.name + "_grad_sub2", {n->inputs[0]},
-                              {{"fill_value", "-1"}});
-    NodeEntry sub3 = MakeNode("broadcast_mul", n->attrs.name + "_grad_sub3", {sub1, sub2});
+    NodeEntry sub1 = MakeNode("exp", n->attrs.name + "_grad_sub1", {output});
+    NodeEntry sub2 = MakeNode("broadcast_mul", n->attrs.name + "_grad_sub2", {sub0, sub1});
     return std::vector<NodeEntry> {
-      MakeNode("elemwise_add", n->attrs.name + "_grad", {sub3, ograds[0]})
+      MakeNode("elemwise_sub", n->attrs.name + "_grad", {ograds[0], sub2})
     };
 })
 .set_support_level(1);
diff --git a/nnvm/tests/python/compiler/test_top_level1.py b/nnvm/tests/python/compiler/test_top_level1.py
index ebf8c6ce6ae2..3058d6ccfc7b 100644
--- a/nnvm/tests/python/compiler/test_top_level1.py
+++ b/nnvm/tests/python/compiler/test_top_level1.py
@@ -217,7 +217,7 @@ def backward(head_grads, x):
     dtype = "float32"
     dshape = (10, 1000)
     inputs = [('x', dshape, x)]
-    helper(y, inputs, dtype, forward), backward
+    helper(y, inputs, dtype, forward, backward)
 
 
 def test_log_softmax():
@@ -229,7 +229,7 @@ def forward(x):
 
     def backward(head_grads, x):
         y = topi.testing.log_softmax_python(x)
-        grad = head_grads - np.sum(y * head_grads, axis=1, keepdims=True)
+        grad = head_grads - np.exp(y) * np.sum(head_grads, axis=1, keepdims=True)
         return [grad]
 
     dtype = "float32"

From d7d44f80b2d38086699b2c1edf29e103435f7b16 Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Wed, 6 Jun 2018 02:27:54 +0530
Subject: [PATCH 687/949] [TOPI] Slice operator (#1165)

---
 docs/api/python/topi.rst                  |  6 ++
 topi/include/topi/detail/constant_utils.h | 18 +++++
 topi/include/topi/transform.h             | 81 +++++++++++++++++++++++
 topi/python/topi/transform.py             | 26 ++++++++
 topi/src/topi.cc                          |  5 ++
 topi/tests/python/test_topi_transform.py  | 35 ++++++++++
 6 files changed, 171 insertions(+)

diff --git a/docs/api/python/topi.rst b/docs/api/python/topi.rst
index 823b941dbc78..b1f2bfc9d940 100644
--- a/docs/api/python/topi.rst
+++ b/docs/api/python/topi.rst
@@ -17,11 +17,14 @@ List of operators
    topi.clip
    topi.cast
    topi.transpose
+   topi.flip
+   topi.strided_slice
    topi.expand_dims
    topi.reshape
    topi.squeeze
    topi.concatenate
    topi.split
+   topi.take
    topi.full
    topi.full_like
    topi.greater
@@ -72,11 +75,14 @@ topi
 .. autofunction:: topi.clip
 .. autofunction:: topi.cast
 .. autofunction:: topi.transpose
+.. autofunction:: topi.flip
+.. autofunction:: topi.strided_slice
 .. autofunction:: topi.expand_dims
 .. autofunction:: topi.reshape
 .. autofunction:: topi.squeeze
 .. autofunction:: topi.concatenate
 .. autofunction:: topi.split
+.. autofunction:: topi.take
 .. autofunction:: topi.full
 .. autofunction:: topi.full_like
 .. autofunction:: topi.greater
diff --git a/topi/include/topi/detail/constant_utils.h b/topi/include/topi/detail/constant_utils.h
index adbb84421bfa..343334562349 100644
--- a/topi/include/topi/detail/constant_utils.h
+++ b/topi/include/topi/detail/constant_utils.h
@@ -66,6 +66,24 @@ inline std::vector<int> GetConstIntValues(Array<Expr> exprs, const std::string&
   return result;
 }
 
+/*!
+ * \brief Get the value of all the constant integer expressions in the given array
+ *
+ * \param exprs The array of expressions to get the values of
+ * \param var_name The name to be used when logging an error in the event that any
+ * of the expressions are not constant integers.
+ *
+ * \return A vector of the int64_t values
+ */
+inline std::vector<int64_t> GetConstInt64Values(Array<Expr> exprs, const std::string& var_name) {
+  std::vector<int64_t> result;
+  for (auto expr : exprs) {
+    CHECK(IsConstInt(expr)) << "All elements of " << var_name << " must be constant integers";
+    result.push_back(GetConstInt(expr));
+  }
+  return result;
+}
+
 /*!
  * \brief Check weather the two expressions are equal or not, if not simplify the expressions and check again
  * \note This is stronger equality check than tvm::ir::Equal
diff --git a/topi/include/topi/transform.h b/topi/include/topi/transform.h
index 941c5dbc5b60..ac408ba633b0 100644
--- a/topi/include/topi/transform.h
+++ b/topi/include/topi/transform.h
@@ -365,6 +365,87 @@ inline Array<Tensor> split(const Tensor& x,
   return result;
 }
 
+/*!
+* \brief strided_slice of a tensor
+*
+* \param x The input tensor
+* \param begin The indices to begin with in the slicing
+* \param end Indicies indicating end of the slice
+* \param strides Specifies the stride values, it can be negative
+* in that case, the input tensor will be reversed in that particular axis
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the split operation
+*/
+inline Tensor strided_slice(const Tensor& x,
+                            const Array<Expr>& begin,
+                            const Array<Expr>& end,
+                            const Array<Expr>& strides,
+                            std::string name = "tensor",
+                            std::string tag = kInjective) {
+  size_t src_tensor_dim = static_cast<size_t>(x->shape.size());
+  std::vector<int64_t> begin_vec = GetConstInt64Values(begin, "begin");
+  std::vector<int64_t> end_vec = GetConstInt64Values(end, "end");
+  std::vector<int64_t> stride_vec = GetConstInt64Values(strides, "strides");
+  // in case user has not provided begin indices for all the axes,
+  // then inflate it with default value = 0
+  for (size_t i = begin_vec.size(); i < src_tensor_dim; ++i) {
+    begin_vec.push_back(0);
+  }
+  // in case user has not provided end indices for all the axes,
+  // then inflate it with default value = input_tensor.shape[axis]
+  for (size_t i = end_vec.size(); i < src_tensor_dim; ++i) {
+    end_vec.push_back(GetConstInt(x->shape[i]));
+  }
+  // in case user has not provided stride values,
+  // then inflate it with default value = 1
+  for (size_t i = stride_vec.size(); i < src_tensor_dim; ++i) {
+    stride_vec.push_back(1);
+  }
+
+  Array<Expr> out_shape;
+  Array<Expr> begin_expr;
+  Array<Expr> strides_expr;
+
+  for (size_t i = 0; i < src_tensor_dim; ++i) {
+    int64_t begin_range = stride_vec[i] < 0 ? -1 : 0;
+    int64_t dim_i = GetConstInt(x->shape[i]);
+    int64_t end_range = stride_vec[i] < 0 ? dim_i - 1 : dim_i;
+    // transform negative indices to positive value, clips on the correct range
+    auto index_canonicalization = [dim_i, begin_range, end_range](int64_t index) {
+      if (index < 0) {
+        index += dim_i;
+      }
+      return std::min(std::max(index, begin_range), end_range);
+    };
+
+    int64_t begin_i = index_canonicalization(begin_vec[i]);
+    int64_t end_i = index_canonicalization(end_vec[i]);
+
+    int interval = std::abs(end_i - begin_i);
+    int slice_size = static_cast<int>((interval
+                                     + std::abs(stride_vec[i]) - 1) / std::abs(stride_vec[i]));
+    CHECK(stride_vec[i] < 0 ? (end_i < begin_i) : (begin_i < end_i))
+      << ": Input [Begin=" << begin_vec[i] << ", End=" << end_vec[i]
+      << "] is invalid for axis=" << i;
+
+    begin_expr.push_back(make_const(begin[0].type(), begin_i));
+    strides_expr.push_back(make_const((strides.size() != 0 ? strides[0].type() : begin[0].type()),
+                                     stride_vec[i]));
+    out_shape.push_back(slice_size);
+  }
+
+  return compute(
+    out_shape, [&](const Array<Var>& indices) {
+      Array<Expr> real_indices;
+      for (size_t i = 0; i < src_tensor_dim; ++i) {
+        real_indices.push_back(indices[i] * strides_expr[i] + begin_expr[i]);
+      }
+      return x(real_indices);
+    }, name, tag);
+}
+
 /*!
 * \brief Split a tensor into a number of sub-tensors
 *
diff --git a/topi/python/topi/transform.py b/topi/python/topi/transform.py
index 807b210bee36..871988d3c058 100644
--- a/topi/python/topi/transform.py
+++ b/topi/python/topi/transform.py
@@ -129,6 +129,32 @@ def flip(a, axis=0):
     """
     return cpp.flip(a, axis)
 
+@tvm.tag_scope(tag=tag.INJECTIVE)
+def strided_slice(a, begin, end, strides=None):
+    """Slice of an array.
+
+    Parameters
+    ----------
+    a : tvm.Tensor
+        The tensor to be sliced.
+
+    begin: list of int
+        The indices to begin with in the slicing.
+
+    end: list of int
+        Indicies indicating end of the slice.
+
+    strides: list of int, optional
+        Specifies the stride values, it can be negative
+        in that case, the input tensor will be reversed
+        in that particular axis.
+
+    Returns
+    -------
+    ret : tvm.Tensor
+    """
+    return cpp.strided_slice(a, begin, end, strides)
+
 @tvm.tag_scope(tag=tag.INJECTIVE)
 def reshape(a, newshape):
     """Reshape the array
diff --git a/topi/src/topi.cc b/topi/src/topi.cc
index 1042bfcf3f45..9c820edfeb37 100644
--- a/topi/src/topi.cc
+++ b/topi/src/topi.cc
@@ -280,6 +280,11 @@ TVM_REGISTER_GLOBAL("topi.take")
   }
   });
 
+TVM_REGISTER_GLOBAL("topi.strided_slice")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = strided_slice(args[0], args[1], args[2], args[3]);
+  });
+
 /* Ops from nn/batch_norm.h */
 TVM_REGISTER_GLOBAL("topi.nn.batch_norm_inference")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
diff --git a/topi/tests/python/test_topi_transform.py b/topi/tests/python/test_topi_transform.py
index 0454e69f2694..b62f6b43c3de 100644
--- a/topi/tests/python/test_topi_transform.py
+++ b/topi/tests/python/test_topi_transform.py
@@ -246,6 +246,40 @@ def check_device(device):
     for device in ["llvm", "opencl"]:
         check_device(device)
 
+def verify_strided_slice(in_shape, begin, end, stride=None):
+    stride = stride if stride else [1, 1, 1]
+    A = tvm.placeholder(shape=in_shape, name="A")
+    B = topi.strided_slice(A, begin, end, stride) + 1
+    def test_forward(x, begin, end, stride):
+        return x[begin[0]:end[0]:stride[0],
+                    begin[1]:end[1]:stride[1], begin[2]:end[2]:stride[2]] + 1
+    def check_device(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s = topi.generic.schedule_injective(B)
+
+        foo = tvm.build(s, [A, B], device, name="stride_slice")
+        x_np = np.random.uniform(size=in_shape).astype(A.dtype)
+        out_npy = test_forward(x_np, begin, end, stride)
+        data_nd = tvm.nd.array(x_np, ctx)
+        out_nd = tvm.nd.empty(out_npy.shape, ctx=ctx, dtype=A.dtype)
+        foo(data_nd, out_nd)
+        np.testing.assert_allclose(out_nd.asnumpy(), out_npy)
+
+    for device in ["llvm", "opencl"]:
+        check_device(device)
+
+def test_strided_slice():
+    verify_strided_slice((3, 4, 3), [0, 0, 0], [4, -5, 4], [1, -1, 2])
+    verify_strided_slice((3, 4, 3), [1, 1, 0], [4, 4, 3], [2, 1, 1])
+    verify_strided_slice((3, 4, 3), [1, -1, 0], [4, -5, 3], [2, -1, 1])
+    verify_strided_slice((3, 4, 3), [1, 0, 0], [2, 2, 3], [1, 1, 2])
+    verify_strided_slice((3, 4, 3), [1, -1, 0], [2, -3, 3], [1, -1, 1])
+    verify_strided_slice((3, 4, 3), [1, 1, 0], [4, 4, 3])
 
 def test_expand_dims():
     verify_expand_dims((3, 10), (3, 10, 1, 1), 2, 2)
@@ -322,3 +356,4 @@ def test_take():
     test_flip()
     test_expand_like()
     test_take()
+    test_strided_slice()

From ccb8fed9127f5d39f30a549e1dab175cf55998f2 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 5 Jun 2018 14:10:08 -0700
Subject: [PATCH 688/949] Update ISSUE_TEMPLATE.md

---
 .github/ISSUE_TEMPLATE.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 8b7474a8207c..d195450b10bd 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -1,9 +1,9 @@
 TVM Github Issue Guideline
 ---------------------------
 
-Thanks for participating in the TVM community! We use [TVM Discuss forum](https://discuss.tvmlang.org) for any general questions and discussions. When in doubt, you are always welcomed to post on the forum first :)
+Thanks for participating in the TVM community! We use https://discuss.tvm.ai for any general usage questions and discussions. The issue tracker is used for actionable items such as feature proposals discussion, roadmaps, and bug tracking.  You are always welcomed to post on the forum first :)
 
-The issue tracker is used for actionable items such as feature proposals, roadmaps, and bug tracking. Issues that are inactive for a period of time may get closed. We adopt this policy so that we won't lose track of actionable issues that may fall at the bottom of the pile. Feel free to reopen a new one if you feel there is an additional problem that needs attention when an old one gets closed.
+Issues that are inactive for a period of time may get closed. We adopt this policy so that we won't lose track of actionable issues that may fall at the bottom of the pile. Feel free to reopen a new one if you feel there is an additional problem that needs attention when an old one gets closed.
 
 For bug reports, to help the developer act on the issues, please include a description of your environment, preferably a minimum script to reproduce the problem.
 

From 04c756237b89bafdab63936756a7668ecdc90883 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 5 Jun 2018 14:55:55 -0700
Subject: [PATCH 689/949] update template (#1233)

---
 .github/ISSUE_TEMPLATE.md        | 3 ---
 .github/PULL_REQUEST_TEMPLATE.md | 1 +
 2 files changed, 1 insertion(+), 3 deletions(-)
 create mode 100644 .github/PULL_REQUEST_TEMPLATE.md

diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index d195450b10bd..0e2a130d489e 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -1,6 +1,3 @@
-TVM Github Issue Guideline
----------------------------
-
 Thanks for participating in the TVM community! We use https://discuss.tvm.ai for any general usage questions and discussions. The issue tracker is used for actionable items such as feature proposals discussion, roadmaps, and bug tracking.  You are always welcomed to post on the forum first :)
 
 Issues that are inactive for a period of time may get closed. We adopt this policy so that we won't lose track of actionable issues that may fall at the bottom of the pile. Feel free to reopen a new one if you feel there is an additional problem that needs attention when an old one gets closed.
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 000000000000..80742414c54f
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1 @@
+Thanks for contributing to TVM!   Please refer to guideline http://docs.tvm.ai/contribute/ for useful information and tips. After the pull request is submitted, please request code reviews from others in the community.

From cf486e35ac8189ad5fe8186ab80471d10721a4bf Mon Sep 17 00:00:00 2001
From: MORITA Kazutaka <morita.kazutaka@lab.ntt.co.jp>
Date: Thu, 7 Jun 2018 00:44:52 +0900
Subject: [PATCH 690/949] [NNVM][FRONTEND][Keras] Support for reusing layers
 (#1192)

---
 nnvm/python/nnvm/frontend/keras.py            | 46 ++++++++-------
 .../python/frontend/keras/test_forward.py     | 57 ++++++++++++++++---
 tutorials/nnvm/from_keras.py                  |  6 +-
 3 files changed, 78 insertions(+), 31 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/keras.py b/nnvm/python/nnvm/frontend/keras.py
index a61fee7e3413..0ca6ebf6db61 100644
--- a/nnvm/python/nnvm/frontend/keras.py
+++ b/nnvm/python/nnvm/frontend/keras.py
@@ -152,8 +152,8 @@ def _convert_convolution(insym, keras_layer, symtab):
         pass
     # we insert a separate pad operator
     elif keras_layer.padding == 'same':
-        in_h = keras_layer.input.shape[1].value
-        in_w = keras_layer.input.shape[2].value
+        in_h = keras_layer.input_shape[1]
+        in_w = keras_layer.input_shape[2]
         pad_t, pad_b = _get_pad_pair(in_h, kernel_h, stride_h)
         pad_l, pad_r = _get_pad_pair(in_w, kernel_w, stride_w)
         insym = _sym.pad(data=insym, pad_width=((0, 0), (0, 0), (pad_t, pad_b), (pad_l, pad_r)))
@@ -192,8 +192,8 @@ def _convert_separable_convolution(insym, keras_layer, symtab):
         pass
     # we insert a separate pad operator
     elif keras_layer.padding == 'same':
-        in_h = keras_layer.input.shape[1].value
-        in_w = keras_layer.input.shape[2].value
+        in_h = keras_layer.input_shape[1]
+        in_w = keras_layer.input_shape[2]
         pad_t, pad_b = _get_pad_pair(in_h, kernel_h, stride_h)
         pad_l, pad_r = _get_pad_pair(in_w, kernel_w, stride_w)
         insym = _sym.pad(data=insym, pad_width=(
@@ -249,8 +249,8 @@ def _convert_pooling(insym, keras_layer, symtab):
             pass
         # we insert a separate pad operator
         elif keras_layer.padding == 'same':
-            in_h = keras_layer.input.shape[1].value
-            in_w = keras_layer.input.shape[2].value
+            in_h = keras_layer.input_shape[1]
+            in_w = keras_layer.input_shape[2]
             pad_t, pad_b = _get_pad_pair(in_h, pool_h, stride_h)
             pad_l, pad_r = _get_pad_pair(in_w, pool_w, stride_w)
             insym = _sym.pad(data=insym, pad_width=(
@@ -475,25 +475,33 @@ def from_keras(model):
     symtab = SymbolTable()
     for keras_layer in model.layers:
         if isinstance(keras_layer, keras.engine.topology.InputLayer):
-            keras_layer.name = 'data'
             symtab.get_var(keras_layer.name, must_contain=False)
         else:
-            predecessors = []
             inbound_nodes = keras_layer.inbound_nodes if hasattr(keras_layer, 'inbound_nodes') \
                        else keras_layer._inbound_nodes if hasattr(keras_layer, '_inbound_nodes') \
                        else None
             if inbound_nodes is None:
                 raise TypeError("Unknown layer type or unsupported Keras version : {}"
                                 .format(keras_layer))
-            for node in inbound_nodes:
-                for pred in node.inbound_layers:
-                    predecessors.append(pred.name)
-            if len(predecessors) == 1:
-                insym = symtab.get_var(predecessors[0], must_contain=True)
-            else:
-                insym = [symtab.get_var(pred, must_contain=True) for pred in predecessors]
-            keras_op_to_nnvm(insym, keras_layer, keras_layer.name, symtab)
-
-    returns = [symtab.get_var(i.name, must_contain=False) for i in model.output_layers]
+            for my_idx, node in enumerate(inbound_nodes):
+                insym = []
+
+                # Since Keras allows creating multiple layers from the same name instance,
+                # we append node index to the symbol name to make it unique.
+                # The one exception is InputLayer.  Changing input variable names after conversion
+                # would confuse users, so we should keep them as far as possible.  Fortunately,
+                # they are named uniquely to input_1, input_2, input_3 ... by default.
+                for pred_idx, pred in zip(node.node_indices, node.inbound_layers):
+                    if isinstance(pred, keras.engine.topology.InputLayer):
+                        _sym = symtab.get_var(pred.name, must_contain=True)
+                    else:
+                        _sym = symtab.get_var(pred.name + ':' + str(pred_idx), must_contain=True)
+                    insym.append(_sym)
+
+                if len(insym) == 1:
+                    insym = insym[0]
+                keras_op_to_nnvm(insym, keras_layer, keras_layer.name + ':' + str(my_idx), symtab)
+
+    outsym = symtab.get_var(model.output_layers[0].name + ':0')
     tvmparams = {k:tvm.nd.array(np.array(v, dtype=np.float32)) for k, v in symtab.params.items()}
-    return returns[0], tvmparams
+    return outsym, tvmparams
diff --git a/nnvm/tests/python/frontend/keras/test_forward.py b/nnvm/tests/python/frontend/keras/test_forward.py
index 5a399577f376..0cc65350cbc8 100644
--- a/nnvm/tests/python/frontend/keras/test_forward.py
+++ b/nnvm/tests/python/frontend/keras/test_forward.py
@@ -14,28 +14,31 @@
 
 
 def verify_keras_frontend(keras_model):
-    in_shape = [dim.value if dim.value is not None else 1 for dim in keras_model.input_layers[0].input.shape]
+    in_shapes = []
+    for layer in keras_model.input_layers:
+        in_shapes.append(tuple(dim.value if dim.value is not None else 1 for dim in layer.input.shape))
     out_shape = [dim.value if dim.value is not None else 1 for dim in keras_model.output_layers[0].output.shape]
 
-    def get_keras_output(x, dtype='float32'):
-        return keras_model.predict(x)
+    def get_keras_output(xs, dtype='float32'):
+        return keras_model.predict(xs)
 
-    def get_tvm_output(x, target, ctx, input_name='data', dtype='float32'):
+    def get_tvm_output(xs, target, ctx, dtype='float32'):
         sym, params = nnvm.frontend.from_keras(keras_model)
-        shape_dict = {input_name : x.shape}
+        shape_dict = {name: x.shape for (name, x) in zip(keras_model.input_names, xs)}
         with nnvm.compiler.build_config(opt_level=2):
             graph, lib, params = nnvm.compiler.build(sym, target, shape_dict, params=params)
         m = graph_runtime.create(graph, lib, ctx)
-        m.set_input(input_name, tvm.nd.array(x.astype(dtype)))
+        for name, x in zip(keras_model.input_names, xs):
+            m.set_input(name, tvm.nd.array(x.astype(dtype)))
         m.set_input(**params)
         m.run()
         out = m.get_output(0, tvm.nd.empty(out_shape, dtype))
         return out.asnumpy()
 
-    x = np.random.uniform(size=in_shape)
-    keras_out = get_keras_output(x)
+    xs = [np.random.uniform(size=shape) for shape in in_shapes]
+    keras_out = get_keras_output(xs)
     for target, ctx in ctx_list():
-        tvm_out = get_tvm_output(x.transpose([0,3,1,2]), target, ctx)
+        tvm_out = get_tvm_output([x.transpose([0,3,1,2]) for x in xs], target, ctx)
         np.testing.assert_allclose(keras_out, tvm_out, rtol=1e-5, atol=1e-5)
 
     
@@ -166,6 +169,39 @@ def test_forward_mobilenet():
     verify_keras_frontend(keras_model)
 
 
+def test_forward_multi_inputs():
+    data1 = keras.layers.Input(shape=(32,32,3))
+    data2 = keras.layers.Input(shape=(32,32,3))
+    x = keras.layers.Conv2D(8, (3, 3), padding="same")(data1)
+    y = keras.layers.Conv2D(8, (3, 3), padding="same")(data2)
+    z = keras.layers.add([x, y])
+    z = keras.layers.GlobalAveragePooling2D()(z)
+    keras_model = keras.models.Model([data1, data2], z)
+    verify_keras_frontend(keras_model)
+
+
+def test_forward_reuse_layers():
+    # reuse conv2d
+    data = keras.layers.Input(shape=(32,32,3))
+    conv2d = keras.layers.Conv2D(8, (3, 3), padding="same")
+    x = conv2d(data)
+    y = conv2d(data)
+    z = keras.layers.add([x, y])
+    z = keras.layers.GlobalAveragePooling2D()(z)
+    keras_model = keras.models.Model(data, z)
+    verify_keras_frontend(keras_model)
+
+    # reuse add
+    data = keras.layers.Input(shape=(32,32,3))
+    x = keras.layers.Conv2D(8, (3, 3), padding="same")(data)
+    add = keras.layers.Add()
+    x = add([x, x])
+    x = add([x, x])
+    z = keras.layers.GlobalAveragePooling2D()(x)
+    keras_model = keras.models.Model(data, z)
+    verify_keras_frontend(keras_model)
+
+
 if __name__ == '__main__':
     test_forward_elemwise_add()
     test_forward_softmax()
@@ -182,3 +218,6 @@ def test_forward_mobilenet():
     test_forward_xception()
     test_forward_resnet50()
     test_forward_mobilenet()
+
+    test_forward_multi_inputs()
+    test_forward_reuse_layers()
diff --git a/tutorials/nnvm/from_keras.py b/tutorials/nnvm/from_keras.py
index 0466d672c1eb..402010b98634 100644
--- a/tutorials/nnvm/from_keras.py
+++ b/tutorials/nnvm/from_keras.py
@@ -63,7 +63,7 @@ def download(url, path, overwrite=False):
 # input preprocess
 data = np.array(img)[np.newaxis, :].astype('float32')
 data = preprocess_input(data).transpose([0, 3, 1, 2])
-print('data', data.shape)
+print('input_1', data.shape)
 
 ######################################################################
 # Compile the model on NNVM
@@ -74,7 +74,7 @@ def download(url, path, overwrite=False):
 sym, params = nnvm.frontend.from_keras(keras_resnet50)
 # compile the model
 target = 'cuda'
-shape_dict = {'data': data.shape}
+shape_dict = {'input_1': data.shape}
 with nnvm.compiler.build_config(opt_level=2):
 	graph, lib, params = nnvm.compiler.build(sym, target, shape_dict, params=params)
 
@@ -86,7 +86,7 @@ def download(url, path, overwrite=False):
 ctx = tvm.gpu(0)
 m = graph_runtime.create(graph, lib, ctx)
 # set inputs
-m.set_input('data', tvm.nd.array(data.astype('float32')))
+m.set_input('input_1', tvm.nd.array(data.astype('float32')))
 m.set_input(**params)
 # execute
 m.run()

From 075135cf4c1353ec7f162ac0e0493840cc7e6e9c Mon Sep 17 00:00:00 2001
From: nhynes <nhynes@berkeley.edu>
Date: Wed, 6 Jun 2018 08:46:31 -0700
Subject: [PATCH 691/949] Remove SGX demo private key from repo (#1237)

---
 apps/sgx/Makefile             |  2 +-
 apps/sgx/enclave_private.pem  | 39 ---------------------------
 apps/sgx/prepare_test_libs.py | 51 ++++++++++++++++++++++++++++++++++-
 3 files changed, 51 insertions(+), 41 deletions(-)
 delete mode 100644 apps/sgx/enclave_private.pem

diff --git a/apps/sgx/Makefile b/apps/sgx/Makefile
index e06f8a8ebfe6..1b4dce7c6c7d 100644
--- a/apps/sgx/Makefile
+++ b/apps/sgx/Makefile
@@ -78,7 +78,7 @@ lib/test_addone.so: $(TVM_ROOT)/src/runtime/sgx/trusted/runtime.cc lib/tvm_t.o l
 
 # The signed enclave
 lib/test_addone.signed.so: lib/test_addone.so enclave_config.xml
-	$(sgx_enclave_signer) sign -key enclave_private.pem -enclave $< -out $@ -config enclave_config.xml
+	$(sgx_enclave_signer) sign -key lib/enclave.pem -enclave $< -out $@ -config enclave_config.xml
 
 clean:
 	rm -rf lib
diff --git a/apps/sgx/enclave_private.pem b/apps/sgx/enclave_private.pem
deleted file mode 100644
index 529d07be3574..000000000000
--- a/apps/sgx/enclave_private.pem
+++ /dev/null
@@ -1,39 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIG4gIBAAKCAYEAroOogvsj/fZDZY8XFdkl6dJmky0lRvnWMmpeH41Bla6U1qLZ
-AmZuyIF+mQC/cgojIsrBMzBxb1kKqzATF4+XwPwgKz7fmiddmHyYz2WDJfAjIveJ
-ZjdMjM4+EytGlkkJ52T8V8ds0/L2qKexJ+NBLxkeQLfV8n1mIk7zX7jguwbCG1Pr
-nEMdJ3Sew20vnje+RsngAzdPChoJpVsWi/K7cettX/tbnre1DL02GXc5qJoQYk7b
-3zkmhz31TgFrd9VVtmUGyFXAysuSAb3EN+5VnHGr0xKkeg8utErea2FNtNIgua8H
-ONfm9Eiyaav1SVKzPHlyqLtcdxH3I8Wg7yqMsaprZ1n5A1v/levxnL8+It02KseD
-5HqV4rf/cImSlCt3lpRg8U5E1pyFQ2IVEC/XTDMiI3c+AR+w2jSRB3Bwn9zJtFlW
-KHG3m1xGI4ck+Lci1JvWWLXQagQSPtZTsubxTQNx1gsgZhgv1JHVZMdbVlAbbRMC
-1nSuJNl7KPAS/VfzAgEDAoIBgHRXxaynbVP5gkO0ug6Qw/E27wzIw4SmjsxG6Wpe
-K7kfDeRskKxESdsA/xCrKkwGwhcx1iIgS5+Qscd1Yg+1D9X9asd/P7waPmWoZd+Z
-AhlKwhdPsO7PiF3e1AzHhGQwsUTt/Y/aSI1MpHBvy2/s1h9mFCslOUxTmWw0oj/Q
-ldIEgWeNR72CE2+jFIJIyml6ftnb6qzPiga8Bm48ubKh0kvySOqnkmnPzgh+JBD6
-JnBmtZbfPT97bwTT+N6rnPqOOApvfHPf15kWI8yDbprG1l4OCUaIUH1AszxLd826
-5IPM+8gINLRDP1MA6azECPjTyHXhtnSIBZCyWSVkc05vYmNXYUNiXWMajcxW9M02
-wKzFELO8NCEAkaTPxwo4SCyIjUxiK1LbQ9h8PSy4c1+gGP4LAMR8xqP4QKg6zdu9
-osUGG/xRe/uufgTBFkcjqBHtK5L5VI0jeNIUAgW/6iNbYXjBMJ0GfauLs+g1VsOm
-WfdgXzsb9DYdMa0OXXHypmV4GwKBwQDUwQj8RKJ6c8cT4vcWCoJvJF00+RFL+P3i
-Gx2DLERxRrDa8AVGfqaCjsR+3vLgG8V/py+z+dxZYSqeB80Qeo6PDITcRKoeAYh9
-xlT3LJOS+k1cJcEmlbbO2IjLkTmzSwa80fWexKu8/Xv6vv15gpqYl1ngYoqJM3pd
-vzmTIOi7MKSZ0WmEQavrZj8zK4endE3v0eAEeQ55j1GImbypSf7Idh7wOXtjZ7WD
-Dg6yWDrri+AP/L3gClMj8wsAxMV4ZR8CgcEA0fzDHkFa6raVOxWnObmRoDhAtE0a
-cjUj976NM5yyfdf2MrKy4/RhdTiPZ6b08/lBC/+xRfV3xKVGzacm6QjqjZrUpgHC
-0LKiZaMtccCJjLtPwQd0jGQEnKfMFaPsnhOc5y8qVkCzVOSthY5qhz0XNotHHFmJ
-gffVgB0iqrMTvSL7IA2yqqpOqNRlhaYhNl8TiFP3gIeMtVa9rZy31JPgT2uJ+kfo
-gV7sdTPEjPWZd7OshGxWpT6QfVDj/T9T7L6tAoHBAI3WBf2DFvxNL2KXT2QHAZ9t
-k3imC4f7U+wSE6zILaDZyzygA4RUbwG0gv8/TJVn2P/Eynf76DuWHGlaiLWnCbSz
-Az2DHBQBBaku409zDQym3j1ugMRjzzSQWzJg0SIyBH3hTmnYcn3+Uqcp/lEBvGW6
-O+rsXFt3pukqJmIV8HzLGGaLm62BHUeZf3dyWm+i3p/hQAL7Xvu04QW70xuGqdr5
-afV7p5eaeQIJXyGQJ0eylV/90+qxjMKiB1XYg6WYvwKBwQCL/ddpgOdHJGN8uRom
-e7Zq0Csi3hGheMKlKbN3vcxT5U7MdyHtTZZOJbTvxKNNUNYH/8uD+PqDGNneb29G
-BfGzvI3EASyLIcGZF3OhKwZd0jUrWk2y7Vhob91jwp2+t73vdMbkKyI4mHOuXvGv
-fg95si9oO7EBT+Oqvhccd2J+F1IVXncccYnF4u5ZGWt5lLewN/pVr7MjjykeaHqN
-t+rfnQam2psA6fL4zS2zTmZPzR2tnY8Y1GBTi0Ko1OKd1HMCgcAb5cB/7/AQlhP9
-yQa04PLH9ygQkKKptZp7dy5WcWRx0K/hAHRoi2aw1wZqfm7VBNu2SLcs90kCCCxp
-6C5sfJi6b8NpNbIPC+sc9wsFr7pGo9SFzQ78UlcWYK2Gu2FxlMjonhka5hvo4zvg
-WxlpXKEkaFt3gLd92m/dMqBrHfafH7VwOJY2zT3WIpjwuk0ZzmRg5p0pG/svVQEH
-NZmwRwlopysbR69B/n1nefJ84UO50fLh5s5Zr3gBRwbWNZyzhXk=
------END RSA PRIVATE KEY-----
diff --git a/apps/sgx/prepare_test_libs.py b/apps/sgx/prepare_test_libs.py
index 6c848fe316b9..db537cd537ae 100644
--- a/apps/sgx/prepare_test_libs.py
+++ b/apps/sgx/prepare_test_libs.py
@@ -1,11 +1,52 @@
 """Script to prepare test_addone_sys.o"""
 
+from __future__ import print_function
 from os import path as osp
 
 import tvm
 
 CWD = osp.dirname(osp.abspath(osp.expanduser(__file__)))
 
+KEY_HEAD_FOOT = '-----{} RSA PRIVATE KEY-----'
+KEY = """ MIIG4gIBAAKCAYEAroOogvsj/fZDZY8XFdkl6dJmky0lRvnWMmpeH41Bla6U1qLZ
+AmZuyIF+mQC/cgojIsrBMzBxb1kKqzATF4+XwPwgKz7fmiddmHyYz2WDJfAjIveJ
+ZjdMjM4+EytGlkkJ52T8V8ds0/L2qKexJ+NBLxkeQLfV8n1mIk7zX7jguwbCG1Pr
+nEMdJ3Sew20vnje+RsngAzdPChoJpVsWi/K7cettX/tbnre1DL02GXc5qJoQYk7b
+3zkmhz31TgFrd9VVtmUGyFXAysuSAb3EN+5VnHGr0xKkeg8utErea2FNtNIgua8H
+ONfm9Eiyaav1SVKzPHlyqLtcdxH3I8Wg7yqMsaprZ1n5A1v/levxnL8+It02KseD
+5HqV4rf/cImSlCt3lpRg8U5E1pyFQ2IVEC/XTDMiI3c+AR+w2jSRB3Bwn9zJtFlW
+KHG3m1xGI4ck+Lci1JvWWLXQagQSPtZTsubxTQNx1gsgZhgv1JHVZMdbVlAbbRMC
+1nSuJNl7KPAS/VfzAgEDAoIBgHRXxaynbVP5gkO0ug6Qw/E27wzIw4SmjsxG6Wpe
+K7kfDeRskKxESdsA/xCrKkwGwhcx1iIgS5+Qscd1Yg+1D9X9asd/P7waPmWoZd+Z
+AhlKwhdPsO7PiF3e1AzHhGQwsUTt/Y/aSI1MpHBvy2/s1h9mFCslOUxTmWw0oj/Q
+ldIEgWeNR72CE2+jFIJIyml6ftnb6qzPiga8Bm48ubKh0kvySOqnkmnPzgh+JBD6
+JnBmtZbfPT97bwTT+N6rnPqOOApvfHPf15kWI8yDbprG1l4OCUaIUH1AszxLd826
+5IPM+8gINLRDP1MA6azECPjTyHXhtnSIBZCyWSVkc05vYmNXYUNiXWMajcxW9M02
+wKzFELO8NCEAkaTPxwo4SCyIjUxiK1LbQ9h8PSy4c1+gGP4LAMR8xqP4QKg6zdu9
+osUGG/xRe/uufgTBFkcjqBHtK5L5VI0jeNIUAgW/6iNbYXjBMJ0GfauLs+g1VsOm
+WfdgXzsb9DYdMa0OXXHypmV4GwKBwQDUwQj8RKJ6c8cT4vcWCoJvJF00+RFL+P3i
+Gx2DLERxRrDa8AVGfqaCjsR+3vLgG8V/py+z+dxZYSqeB80Qeo6PDITcRKoeAYh9
+xlT3LJOS+k1cJcEmlbbO2IjLkTmzSwa80fWexKu8/Xv6vv15gpqYl1ngYoqJM3pd
+vzmTIOi7MKSZ0WmEQavrZj8zK4endE3v0eAEeQ55j1GImbypSf7Idh7wOXtjZ7WD
+Dg6yWDrri+AP/L3gClMj8wsAxMV4ZR8CgcEA0fzDHkFa6raVOxWnObmRoDhAtE0a
+cjUj976NM5yyfdf2MrKy4/RhdTiPZ6b08/lBC/+xRfV3xKVGzacm6QjqjZrUpgHC
+0LKiZaMtccCJjLtPwQd0jGQEnKfMFaPsnhOc5y8qVkCzVOSthY5qhz0XNotHHFmJ
+gffVgB0iqrMTvSL7IA2yqqpOqNRlhaYhNl8TiFP3gIeMtVa9rZy31JPgT2uJ+kfo
+gV7sdTPEjPWZd7OshGxWpT6QfVDj/T9T7L6tAoHBAI3WBf2DFvxNL2KXT2QHAZ9t
+k3imC4f7U+wSE6zILaDZyzygA4RUbwG0gv8/TJVn2P/Eynf76DuWHGlaiLWnCbSz
+Az2DHBQBBaku409zDQym3j1ugMRjzzSQWzJg0SIyBH3hTmnYcn3+Uqcp/lEBvGW6
+O+rsXFt3pukqJmIV8HzLGGaLm62BHUeZf3dyWm+i3p/hQAL7Xvu04QW70xuGqdr5
+afV7p5eaeQIJXyGQJ0eylV/90+qxjMKiB1XYg6WYvwKBwQCL/ddpgOdHJGN8uRom
+e7Zq0Csi3hGheMKlKbN3vcxT5U7MdyHtTZZOJbTvxKNNUNYH/8uD+PqDGNneb29G
+BfGzvI3EASyLIcGZF3OhKwZd0jUrWk2y7Vhob91jwp2+t73vdMbkKyI4mHOuXvGv
+fg95si9oO7EBT+Oqvhccd2J+F1IVXncccYnF4u5ZGWt5lLewN/pVr7MjjykeaHqN
+t+rfnQam2psA6fL4zS2zTmZPzR2tnY8Y1GBTi0Ko1OKd1HMCgcAb5cB/7/AQlhP9
+yQa04PLH9ygQkKKptZp7dy5WcWRx0K/hAHRoi2aw1wZqfm7VBNu2SLcs90kCCCxp
+6C5sfJi6b8NpNbIPC+sc9wsFr7pGo9SFzQ78UlcWYK2Gu2FxlMjonhka5hvo4zvg
+WxlpXKEkaFt3gLd92m/dMqBrHfafH7VwOJY2zT3WIpjwuk0ZzmRg5p0pG/svVQEH
+NZmwRwlopysbR69B/n1nefJ84UO50fLh5s5Zr3gBRwbWNZyzhXk=
+""".strip()
+
 def prepare_test_libs(base_path):
     n = tvm.var('n')
     A = tvm.placeholder((n,), name='A')
@@ -20,7 +61,15 @@ def prepare_test_libs(base_path):
     fadd_syslib.save(syslib_path)
 
 def main():
-    prepare_test_libs(osp.join(CWD, 'lib'))
+    out_dir = osp.join(CWD, 'lib')
+
+    prepare_test_libs(out_dir)
+
+    with open(osp.join(out_dir, 'enclave.pem'), 'w') as f_key:
+        print(KEY_HEAD_FOOT.format('BEGIN'), file=f_key)
+        print(KEY, file=f_key)
+        print(KEY_HEAD_FOOT.format('END'), file=f_key)
+
 
 if __name__ == '__main__':
     main()

From d9f04b9b98ec3facb2b77a8f9b0c3f0e7331cd92 Mon Sep 17 00:00:00 2001
From: Leyuan Wang <laurawly@gmail.com>
Date: Wed, 6 Jun 2018 10:25:10 -0700
Subject: [PATCH 692/949] fix extern naming (#1238)

---
 src/codegen/codegen_c.cc | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/codegen/codegen_c.cc b/src/codegen/codegen_c.cc
index 9732f0ef65af..82d0df71a308 100644
--- a/src/codegen/codegen_c.cc
+++ b/src/codegen/codegen_c.cc
@@ -27,6 +27,7 @@ void CodeGenC::AddFunction(LoweredFunc f) {
   this->InitFuncState(f);
   // skip the first underscore, so SSA variable starts from _1
   GetUniqueName("_");
+  GetUniqueName("extern");
   // add to alloc buffer type.
   for (const auto & kv : f->handle_data_type) {
     RegisterHandleType(kv.first.get(), kv.second.type());

From 3f674b31718e8189d5a9c1d3d281499eea085507 Mon Sep 17 00:00:00 2001
From: Leyuan Wang <laurawly@gmail.com>
Date: Wed, 6 Jun 2018 22:52:03 -0700
Subject: [PATCH 693/949]  [RPC] default use spawn for fork safety (#1240)

---
 python/tvm/exec/rpc_proxy.py   | 50 ++++++++++++++++++++++----------
 python/tvm/exec/rpc_server.py  | 53 +++++++++++++++++++++++-----------
 python/tvm/exec/rpc_tracker.py | 31 ++++++++++++++++----
 3 files changed, 95 insertions(+), 39 deletions(-)

diff --git a/python/tvm/exec/rpc_proxy.py b/python/tvm/exec/rpc_proxy.py
index 1a1bdfcb09b8..eaa95e8cb79c 100644
--- a/python/tvm/exec/rpc_proxy.py
+++ b/python/tvm/exec/rpc_proxy.py
@@ -1,11 +1,15 @@
+# pylint: disable=redefined-outer-name, invalid-name
 """RPC web proxy, allows redirect to websocket based RPC servers(browsers)"""
 from __future__ import absolute_import
 
 import logging
 import argparse
+import multiprocessing
+import sys
 import os
 from ..contrib.rpc.proxy import Proxy
 
+
 def find_example_resource():
     """Find resource examples."""
     curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
@@ -22,22 +26,8 @@ def find_example_resource():
     return index_page, js_files
 
 
-def main():
+def main(args):
     """Main funciton"""
-    parser = argparse.ArgumentParser()
-    parser.add_argument('--host', type=str, default="0.0.0.0",
-                        help='the hostname of the server')
-    parser.add_argument('--port', type=int, default=9090,
-                        help='The port of the PRC')
-    parser.add_argument('--web-port', type=int, default=8888,
-                        help='The port of the http/websocket server')
-    parser.add_argument('--example-rpc', type=bool, default=False,
-                        help='Whether to switch on example rpc mode')
-    parser.add_argument('--tracker', type=str, default="",
-                        help="Report to RPC tracker")
-    args = parser.parse_args()
-    logging.basicConfig(level=logging.INFO)
-
     if args.tracker:
         url, port = args.tracker.split(":")
         port = int(port)
@@ -60,5 +50,33 @@ def main():
                      tracker_addr=tracker_addr)
     prox.proc.join()
 
+
 if __name__ == "__main__":
-    main()
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--host', type=str, default="0.0.0.0",
+                        help='the hostname of the server')
+    parser.add_argument('--port', type=int, default=9090,
+                        help='The port of the PRC')
+    parser.add_argument('--web-port', type=int, default=8888,
+                        help='The port of the http/websocket server')
+    parser.add_argument('--example-rpc', type=bool, default=False,
+                        help='Whether to switch on example rpc mode')
+    parser.add_argument('--tracker', type=str, default="",
+                        help="Report to RPC tracker")
+    parser.add_argument('--no-fork', dest='fork', action='store_false',
+                        help="Use spawn mode to avoid fork. This option \
+                         is able to avoid potential fork problems with Metal, OpenCL \
+                         and ROCM compilers.")
+    parser.set_defaults(fork=True)
+    args = parser.parse_args()
+    logging.basicConfig(level=logging.INFO)
+    if args.fork is False:
+        if sys.version_info[0] < 3:
+            raise RuntimeError(
+                "Python3 is required for spawn mode."
+            )
+        multiprocessing.set_start_method('spawn')
+    else:
+        logging.info("If you are running ROCM/Metal, \
+        fork with cause compiler internal error. Try to launch with arg ```--no-fork```")
+    main(args)
diff --git a/python/tvm/exec/rpc_server.py b/python/tvm/exec/rpc_server.py
index 46e7877a2803..773d14e2925a 100644
--- a/python/tvm/exec/rpc_server.py
+++ b/python/tvm/exec/rpc_server.py
@@ -1,26 +1,16 @@
+# pylint: disable=redefined-outer-name, invalid-name
 """Start an RPC server"""
 from __future__ import absolute_import
 
 import argparse
+import multiprocessing
+import sys
 import logging
 from ..contrib import rpc
 
-def main():
+
+def main(args):
     """Main funciton"""
-    parser = argparse.ArgumentParser()
-    parser.add_argument('--host', type=str, default="0.0.0.0",
-                        help='the hostname of the server')
-    parser.add_argument('--port', type=int, default=9090,
-                        help='The port of the PRC')
-    parser.add_argument('--port-end', type=int, default=9199,
-                        help='The end search port of the PRC')
-    parser.add_argument('--key', type=str, default="",
-                        help="RPC key used to identify the connection type.")
-    parser.add_argument('--load-library', type=str, default="",
-                        help="Additional library to load")
-    parser.add_argument('--tracker', type=str, default="",
-                        help="Report to RPC tracker")
-    args = parser.parse_args()
 
     if args.tracker:
         url, port = args.tracker.split(":")
@@ -32,7 +22,6 @@ def main():
     else:
         tracker_addr = None
 
-    logging.basicConfig(level=logging.INFO)
     server = rpc.Server(args.host,
                         args.port,
                         args.port_end,
@@ -41,5 +30,35 @@ def main():
                         load_library=args.load_library)
     server.proc.join()
 
+
 if __name__ == "__main__":
-    main()
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--host', type=str, default="0.0.0.0",
+                        help='the hostname of the server')
+    parser.add_argument('--port', type=int, default=9090,
+                        help='The port of the PRC')
+    parser.add_argument('--port-end', type=int, default=9199,
+                        help='The end search port of the PRC')
+    parser.add_argument('--key', type=str, default="",
+                        help="RPC key used to identify the connection type.")
+    parser.add_argument('--load-library', type=str, default="",
+                        help="Additional library to load")
+    parser.add_argument('--tracker', type=str, default="",
+                        help="Report to RPC tracker")
+    parser.add_argument('--no-fork', dest='fork', action='store_false',
+                        help="Use spawn mode to avoid fork. This option \
+                         is able to avoid potential fork problems with Metal, OpenCL \
+                         and ROCM compilers.")
+    parser.set_defaults(fork=True)
+    args = parser.parse_args()
+    logging.basicConfig(level=logging.INFO)
+    if args.fork is False:
+        if sys.version_info[0] < 3:
+            raise RuntimeError(
+                "Python3 is required for spawn mode."
+            )
+        multiprocessing.set_start_method('spawn')
+    else:
+        logging.info("If you are running ROCM/Metal, \
+        fork with cause compiler internal error. Try to launch with arg ```--no-fork```")
+    main(args)
diff --git a/python/tvm/exec/rpc_tracker.py b/python/tvm/exec/rpc_tracker.py
index a5b063f37908..3b76f57eb689 100644
--- a/python/tvm/exec/rpc_tracker.py
+++ b/python/tvm/exec/rpc_tracker.py
@@ -1,21 +1,40 @@
+# pylint: disable=redefined-outer-name, invalid-name
 """Tool to start RPC tracker"""
 from __future__ import absolute_import
 
 import logging
 import argparse
+import multiprocessing
+import sys
 from ..contrib.rpc.tracker import Tracker
 
-def main():
+
+def main(args):
     """Main funciton"""
+    tracker = Tracker(args.host, port=args.port)
+    tracker.proc.join()
+
+
+if __name__ == "__main__":
     parser = argparse.ArgumentParser()
     parser.add_argument('--host', type=str, default="0.0.0.0",
                         help='the hostname of the tracker')
     parser.add_argument('--port', type=int, default=9190,
                         help='The port of the PRC')
+    parser.add_argument('--no-fork', dest='fork', action='store_false',
+                        help="Use spawn mode to avoid fork. This option \
+                         is able to avoid potential fork problems with Metal, OpenCL \
+                         and ROCM compilers.")
+    parser.set_defaults(fork=True)
     args = parser.parse_args()
     logging.basicConfig(level=logging.INFO)
-    tracker = Tracker(args.host, port=args.port)
-    tracker.proc.join()
-
-if __name__ == "__main__":
-    main()
+    if args.fork is False:
+        if sys.version_info[0] < 3:
+            raise RuntimeError(
+                "Python3 is required for spawn mode."
+            )
+        multiprocessing.set_start_method('spawn')
+    else:
+        logging.info("If you are running ROCM/Metal, \
+        fork with cause compiler internal error. Try to launch with arg ```--no-fork```")
+    main(args)

From 5455c87243720efc0c4a88c161ab7cdcba7915d2 Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Thu, 7 Jun 2018 21:14:57 +0530
Subject: [PATCH 694/949] [NNVM]Activations support added in Keras Frontend
 (#1210)

* [NNVM]Activations support added in Keras Frontend

* Helper for ELU added

* All activations test cases clubbed to one
---
 nnvm/python/nnvm/frontend/keras.py            | 34 ++++++++--
 .../python/frontend/keras/test_forward.py     | 65 +++++++------------
 2 files changed, 49 insertions(+), 50 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/keras.py b/nnvm/python/nnvm/frontend/keras.py
index 0ca6ebf6db61..d8c98ec66a56 100644
--- a/nnvm/python/nnvm/frontend/keras.py
+++ b/nnvm/python/nnvm/frontend/keras.py
@@ -23,6 +23,10 @@ def _get_pad_pair(input1d, kernel1d, stride1d):
     pad_after = pad - pad_before
     return [pad_before, pad_after]
 
+def _get_elu(insym, alpha):
+    """ A helper method for elu.
+    """
+    return -alpha * _sym.relu(1 - _sym.exp(insym)) + _sym.relu(insym)
 
 def _convert_activation(insym, keras_layer, _):
     if isinstance(keras_layer, str):
@@ -50,27 +54,43 @@ def _convert_activation(insym, keras_layer, _):
     elif act_type == 'softplus':
         return _sym.log(_sym.__add_scalar__(_sym.exp(insym), scalar=1))
     elif act_type == 'elu':
-        raise NotImplementedError('elu not implemented')
+        alpha = keras_layer.alpha if hasattr(keras_layer, "alpha") else 1
+        return _get_elu(insym, alpha)
+    elif act_type == 'selu':
+        # Alpha, Gamma values, obtained from  https://arxiv.org/abs/1706.02515
+        alpha = keras_layer.alpha if hasattr(keras_layer, "alpha") else 1.6732
+        gamma = keras_layer.gamma if hasattr(keras_layer, "gamma") else 1.0507
+        return gamma * _get_elu(insym, alpha)
     elif act_type == 'relu6':
         return _sym.clip(insym, a_min=0, a_max=6)
     elif act_type == 'softsign':
-        raise NotImplementedError('softsign not implemented')
+        return insym / (1 + (_sym.relu(insym) + _sym.relu(_sym.negative(insym))))
     elif act_type == 'hard_sigmoid':
-        raise NotImplementedError('hard_sigmoid not implemented')
+        transformX = (0.2 * insym) + 0.5
+        return _sym.clip(transformX, a_min=0, a_max=1)
     else:
         raise TypeError("Unsupported activation type : {}".format(act_type))
 
 
-def _convert_advanced_activation(insym, keras_layer, _):
+def _convert_advanced_activation(insym, keras_layer, symtab):
     act_type = type(keras_layer).__name__
     if act_type == 'LeakyReLU':
         return _sym.leaky_relu(insym, alpha=keras_layer.alpha)
     elif act_type == 'ELU':
-        raise NotImplementedError('ELU not implemented')
+        alpha = keras_layer.alpha if hasattr(keras_layer, "alpha") else 1
+        return _get_elu(insym, alpha)
     elif act_type == 'PReLU':
-        raise NotImplementedError('PReLU not implemented')
+        assert hasattr(keras_layer, "alpha"), \
+            "alpha required for PReLU."
+        _check_data_format(keras_layer)
+        size = len(keras_layer.alpha.shape)
+        return -symtab.new_const(keras_layer.get_weights()[0] \
+                                 .transpose(np.roll(range(size), 1))) \
+                                 * _sym.relu(-insym) + _sym.relu(insym)
     elif act_type == 'ThresholdedReLU':
-        raise NotImplementedError('ThresholdedReLU not implemented')
+        theta = keras_layer.theta if hasattr(keras_layer, "theta") else 1.0
+        theta_tensor = _sym.full_like(insym[0], fill_value=float(theta))
+        return _sym.elemwise_mul(insym[0], _sym.greater(insym[0], theta_tensor, out_type="float32"))
     else:
         raise TypeError("Unsupported advanced activation type : {}".format(act_type))
 
diff --git a/nnvm/tests/python/frontend/keras/test_forward.py b/nnvm/tests/python/frontend/keras/test_forward.py
index 0cc65350cbc8..a5eda905ae4d 100644
--- a/nnvm/tests/python/frontend/keras/test_forward.py
+++ b/nnvm/tests/python/frontend/keras/test_forward.py
@@ -61,34 +61,6 @@ def test_forward_elemwise_add():
     keras_model = keras.models.Model(data, y)
     verify_keras_frontend(keras_model)
 
-
-def test_forward_softmax():
-    data = keras.layers.Input(shape=(32,32,3))
-    x = keras.layers.Activation('softmax')(data)
-    x = keras.layers.Concatenate()([x, x])
-    x = keras.layers.GlobalMaxPooling2D()(x)
-    keras_model = keras.models.Model(data, x)
-    verify_keras_frontend(keras_model)
-
-
-def test_forward_softrelu():
-    data = keras.layers.Input(shape=(32,32,3))
-    x = keras.layers.Activation('softplus')(data)
-    x = keras.layers.Concatenate()([x, x])
-    x = keras.layers.GlobalMaxPooling2D()(x)
-    keras_model = keras.models.Model(data, x)
-    verify_keras_frontend(keras_model)
-
-
-def test_forward_leaky_relu():
-    data = keras.layers.Input(shape=(32,32,3))
-    x = keras.layers.LeakyReLU(alpha=0.3)(data)
-    x = keras.layers.Add()([x, x])
-    x = keras.layers.GlobalAveragePooling2D()(x)
-    keras_model = keras.models.Model(data, x)
-    verify_keras_frontend(keras_model)
-
-
 def test_forward_dense():
     data = keras.layers.Input(shape=(32,32,3))
     x = keras.layers.MaxPooling2D(pool_size=(2,2))(data)
@@ -127,16 +99,6 @@ def test_forward_upsample():
     keras_model = keras.models.Model(data, x)
     verify_keras_frontend(keras_model)
 
-
-def test_forward_relu6():
-    data = keras.layers.Input(shape=(32,32,3))
-    x = keras.layers.Activation(keras.applications.mobilenet.relu6)(data)
-    x = keras.layers.Concatenate()([x, x])
-    x = keras.layers.GlobalMaxPooling2D()(x)
-    keras_model = keras.models.Model(data, x)
-    verify_keras_frontend(keras_model)
-
-
 def test_forward_reshape():
     data = keras.layers.Input(shape=(32,32,3))
     x = keras.layers.Reshape(target_shape=(32,32,3))(data)
@@ -168,6 +130,27 @@ def test_forward_mobilenet():
         input_shape=(224,224,3), classes=1000)
     verify_keras_frontend(keras_model)
 
+def test_forward_activations():
+    data = keras.layers.Input(shape=(32,32,3))
+    weights = np.random.rand(1, 32, 32, 3)
+    act_funcs = [keras.layers.Activation('softmax'),
+                 keras.layers.Activation('softplus'),
+                 keras.layers.LeakyReLU(alpha=0.3),
+                 keras.layers.Activation(keras.applications.mobilenet.relu6),
+                 keras.layers.PReLU(weights=weights, alpha_initializer="zero"),
+                 keras.layers.ELU(alpha=0.5),
+                 keras.layers.Activation('selu'),
+                 keras.layers.ThresholdedReLU(theta=0.5),
+                 keras.layers.Activation('softsign'),
+                 keras.layers.Activation('hard_sigmoid'),
+                 keras.layers.Activation('sigmoid'),
+                 keras.layers.Activation('tanh'),
+                 keras.layers.Activation('linear')]
+    for act_func in act_funcs:
+        x = act_func(data)
+        x = keras.layers.GlobalMaxPooling2D()(x)
+        keras_model = keras.models.Model(data, x)
+        verify_keras_frontend(keras_model)
 
 def test_forward_multi_inputs():
     data1 = keras.layers.Input(shape=(32,32,3))
@@ -204,16 +187,12 @@ def test_forward_reuse_layers():
 
 if __name__ == '__main__':
     test_forward_elemwise_add()
-    test_forward_softmax()
-    test_forward_softrelu()
-    test_forward_leaky_relu()
+    test_forward_activations()
     test_forward_dense()
     test_forward_transpose_conv()
     test_forward_separable_conv()
     test_forward_upsample()
-    test_forward_relu6()
     test_forward_reshape()
-
     test_forward_vgg16()
     test_forward_xception()
     test_forward_resnet50()

From f4532773a577f1d6bbafe2a30e25ff104b52d00a Mon Sep 17 00:00:00 2001
From: MORITA Kazutaka <morita.kazutaka@lab.ntt.co.jp>
Date: Fri, 8 Jun 2018 01:35:30 +0900
Subject: [PATCH 695/949] Add nnvm target to Makefile (#1245)

---
 Makefile               | 3 +++
 docs/install/index.rst | 5 ++---
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/Makefile b/Makefile
index f2c3d3919eb5..403bf0544ece 100644
--- a/Makefile
+++ b/Makefile
@@ -256,6 +256,8 @@ all: ${BUILD_TARGETS}
 runtime: lib/libtvm_runtime.$(SHARED_LIBRARY_SUFFIX)
 web: lib/libtvm_web_runtime.js lib/libtvm_web_runtime.bc
 topi: lib/libtvm_topi.$(SHARED_LIBRARY_SUFFIX)
+nnvm:
+	$(MAKE) -C nnvm
 
 
 include tests/cpp/unittest.mk
@@ -387,6 +389,7 @@ jvminstall:
 clean:
 	$(RM) -rf build lib bin *~ */*~ */*/*~ */*/*/*~ */*.o */*/*.o */*/*/*.o */*.d */*/*.d */*/*/*.d
 	cd HalideIR; make clean; cd $(ROOTDIR)
+	$(MAKE) -C nnvm clean
 
 -include build/*.d
 -include build/*/*.d
diff --git a/docs/install/index.rst b/docs/install/index.rst
index 45697d53f7aa..eee2934840a0 100644
--- a/docs/install/index.rst
+++ b/docs/install/index.rst
@@ -67,12 +67,11 @@ We can then build tvm by `make`.
 
   make -j4
 
-After we build tvm, we can proceed to build nnvm using the following script.
+After we build tvm, we can proceed to build nnvm using the following command.
 
 .. code:: bash
 
-  cd nnvm
-  make -j4
+  make nnvm -j4
 
 
 This will creates `libnnvm_compiler.so` under the `nnvm/lib` folder.

From cf1ccf602c8ee09a6d17fb2436ce31835334b697 Mon Sep 17 00:00:00 2001
From: MORITA Kazutaka <morita.kazutaka@lab.ntt.co.jp>
Date: Fri, 8 Jun 2018 08:28:55 +0900
Subject: [PATCH 696/949] Add -Bsymbolic-functions to linker option (#1244)

---
 Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index 403bf0544ece..4529ecc05ee0 100644
--- a/Makefile
+++ b/Makefile
@@ -25,7 +25,7 @@ UNAME_S := $(shell uname -s)
 
 # The flags
 LLVM_CFLAGS= -fno-rtti -DDMLC_ENABLE_RTTI=0 -DDMLC_USE_FOPEN64=0
-LDFLAGS = -pthread -lm -ldl
+LDFLAGS = -pthread -lm -ldl -Wl,-Bsymbolic-functions
 INCLUDE_FLAGS = -Iinclude -I$(DLPACK_PATH)/include -I$(DMLC_CORE_PATH)/include -IHalideIR/src -Itopi/include
 CFLAGS = -std=c++11 -Wall -O2 $(INCLUDE_FLAGS) -fPIC
 PKG_LDFLAGS =

From 088939febf18b3ee61d17427c0cafee13698f0a8 Mon Sep 17 00:00:00 2001
From: eqy <eqy@cs.washington.edu>
Date: Thu, 7 Jun 2018 21:32:37 -0700
Subject: [PATCH 697/949] support custom IP address from rpc server to tracker
 (PUT) (#1243)

---
 python/tvm/contrib/rpc/server.py  | 22 +++++++++++++---------
 python/tvm/contrib/rpc/tracker.py |  6 +++++-
 python/tvm/exec/rpc_server.py     |  9 ++++++---
 3 files changed, 24 insertions(+), 13 deletions(-)

diff --git a/python/tvm/contrib/rpc/server.py b/python/tvm/contrib/rpc/server.py
index e3c731a20eb9..bef85bc5711c 100644
--- a/python/tvm/contrib/rpc/server.py
+++ b/python/tvm/contrib/rpc/server.py
@@ -71,8 +71,7 @@ def _parse_server_opt(opts):
             ret["timeout"] = float(kv[9:])
     return ret
 
-
-def _listen_loop(sock, port, rpc_key, tracker_addr, load_library):
+def _listen_loop(sock, port, rpc_key, tracker_addr, load_library, custom_addr):
     """Lisenting loop of the server master."""
     def _accept_conn(listen_sock, tracker_conn, ping_period=2):
         """Accept connection from the other places.
@@ -93,7 +92,7 @@ def _accept_conn(listen_sock, tracker_conn, ping_period=2):
         if tracker_conn:
             matchkey = base.random_key(rpc_key + ":")
             base.sendjson(tracker_conn,
-                          [TrackerCode.PUT, rpc_key, (port, matchkey)])
+                          [TrackerCode.PUT, rpc_key, (port, matchkey), custom_addr])
             assert base.recvjson(tracker_conn) == TrackerCode.SUCCESS
         else:
             matchkey = rpc_key
@@ -109,17 +108,18 @@ def _accept_conn(listen_sock, tracker_conn, ping_period=2):
                     pending_keys = base.recvjson(tracker_conn)
                     old_keyset.add(matchkey)
                     # if match key not in pending key set
-                    # it means the key is aqquired by a client but not used.
+                    # it means the key is acquired by a client but not used.
                     if matchkey not in pending_keys:
                         unmatch_period_count += 1
                     else:
                         unmatch_period_count = 0
-                    # regenerate match key if key is aqquired but not used for a while
+                    # regenerate match key if key is acquired but not used for a while
                     if unmatch_period_count * ping_period > unmatch_timeout + ping_period:
                         logging.info("RPCServer: no incoming connections, regenerate key ...")
                         matchkey = base.random_key(rpc_key + ":", old_keyset)
                         base.sendjson(tracker_conn,
-                                      [TrackerCode.PUT, rpc_key, (port, matchkey)])
+                                      [TrackerCode.PUT, rpc_key, (port, matchkey),
+                                       custom_addr])
                         assert base.recvjson(tracker_conn) == TrackerCode.SUCCESS
                         unmatch_period_count = 0
                     continue
@@ -278,7 +278,8 @@ def __init__(self,
                  use_popen=False,
                  tracker_addr=None,
                  key="",
-                 load_library=None):
+                 load_library=None,
+                 custom_addr=None):
         try:
             if base._ServerLoop is None:
                 raise RuntimeError("Please compile with USE_RPC=1")
@@ -287,6 +288,7 @@ def __init__(self,
         self.host = host
         self.port = port
         self.libs = []
+        self.custom_addr = custom_addr
 
         if use_popen:
             cmd = ["python",
@@ -298,7 +300,9 @@ def __init__(self,
                 cmd += ["--tracker=%s:%d" % tracker_addr,
                         "--key=%s" % key]
             if load_library:
-                cmd += ["--load-libary", load_library]
+                cmd += ["--load-library", load_library]
+            if custom_addr:
+                cmd += ["--custom-addr", custom_addr]
             self.proc = multiprocessing.Process(
                 target=subprocess.check_call, args=(cmd,))
             self.proc.deamon = True
@@ -324,7 +328,7 @@ def __init__(self,
             self.sock = sock
             self.proc = multiprocessing.Process(
                 target=_listen_loop, args=(
-                    self.sock, self.port, key, tracker_addr, load_library))
+                    self.sock, self.port, key, tracker_addr, load_library, self.custom_addr))
             self.proc.deamon = True
             self.proc.start()
         else:
diff --git a/python/tvm/contrib/rpc/tracker.py b/python/tvm/contrib/rpc/tracker.py
index 165ff5b80789..812b3a9770ab 100644
--- a/python/tvm/contrib/rpc/tracker.py
+++ b/python/tvm/contrib/rpc/tracker.py
@@ -194,7 +194,11 @@ def call_handler(self, args):
             key = args[1]
             port, matchkey = args[2]
             self.pending_matchkeys.add(matchkey)
-            self._tracker.put(key, (self, self._addr[0], port, matchkey))
+            # got custom address (from rpc server)
+            if args[3] is not None:
+                self._tracker.put(key, (self, args[3], port, matchkey))
+            else:
+                self._tracker.put(key, (self, self._addr[0], port, matchkey))
             self.ret_value(TrackerCode.SUCCESS)
         elif code == TrackerCode.REQUEST:
             key = args[1]
diff --git a/python/tvm/exec/rpc_server.py b/python/tvm/exec/rpc_server.py
index 773d14e2925a..d874ed63b673 100644
--- a/python/tvm/exec/rpc_server.py
+++ b/python/tvm/exec/rpc_server.py
@@ -8,9 +8,8 @@
 import logging
 from ..contrib import rpc
 
-
 def main(args):
-    """Main funciton"""
+    """Main function"""
 
     if args.tracker:
         url, port = args.tracker.split(":")
@@ -27,7 +26,8 @@ def main(args):
                         args.port_end,
                         key=args.key,
                         tracker_addr=tracker_addr,
-                        load_library=args.load_library)
+                        load_library=args.load_library,
+                        custom_addr=args.custom_addr)
     server.proc.join()
 
 
@@ -49,6 +49,9 @@ def main(args):
                         help="Use spawn mode to avoid fork. This option \
                          is able to avoid potential fork problems with Metal, OpenCL \
                          and ROCM compilers.")
+    parser.add_argument('--custom-addr', type=str,
+                        help="Custom IP Address to Report to RPC Tracker")
+
     parser.set_defaults(fork=True)
     args = parser.parse_args()
     logging.basicConfig(level=logging.INFO)

From c77585a95e78da2e8101b739b71eb967068cabef Mon Sep 17 00:00:00 2001
From: Tatsuya Nishiyama <nishiyama.tatsuya@lab.ntt.co.jp>
Date: Fri, 8 Jun 2018 13:32:52 +0900
Subject: [PATCH 698/949] Add PReLU support to mxnet frontend (#1249)

---
 nnvm/python/nnvm/frontend/mxnet.py               | 7 ++++---
 nnvm/tests/python/frontend/mxnet/test_forward.py | 8 ++++++++
 2 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/mxnet.py b/nnvm/python/nnvm/frontend/mxnet.py
index 3fc3ca85184a..82b8e555d5ec 100644
--- a/nnvm/python/nnvm/frontend/mxnet.py
+++ b/nnvm/python/nnvm/frontend/mxnet.py
@@ -151,9 +151,10 @@ def _dropout(inputs, attrs):
 
 def _leaky_relu(inputs, attrs):
     act_type = _required_attr(attrs, 'act_type')
-    if act_type in ['leaky']:
-        op_name, new_attrs = 'leaky_relu', {}
-        new_attrs['alpha'] = attrs.get('slope', 0.25)
+    if act_type in ['leaky', 'prelu']:
+        op_name, new_attrs = act_type, {}
+        if act_type == 'leaky':
+            new_attrs['alpha'] = attrs.get('slope', 0.25)
         sym = _get_nnvm_op(op_name)(*inputs, **new_attrs)
     elif act_type == 'elu':
         slope = attrs.get('slope', 0.25)
diff --git a/nnvm/tests/python/frontend/mxnet/test_forward.py b/nnvm/tests/python/frontend/mxnet/test_forward.py
index fca19a693855..b54a5e42ec81 100644
--- a/nnvm/tests/python/frontend/mxnet/test_forward.py
+++ b/nnvm/tests/python/frontend/mxnet/test_forward.py
@@ -97,6 +97,13 @@ def test_forward_rrelu():
     mx_sym = mx.sym.LeakyReLU(data, act_type='rrelu', lower_bound=0.3, upper_bound=0.7)
     verify_mxnet_frontend_impl(mx_sym, (1, 3, 100, 100), (1, 6, 100, 100))
 
+def test_forward_prelu():
+    data = mx.sym.var('data')
+    data = mx.sym.concat(data, -data, dim=1)  # negative part explicitly
+    gamma = mx.sym.zeros(shape=(6,))
+    mx_sym = mx.sym.LeakyReLU(data, gamma=gamma, act_type='prelu')
+    verify_mxnet_frontend_impl(mx_sym, (1, 3, 100, 100), (1, 6, 100, 100))
+
 def test_forward_softrelu():
     data = mx.sym.var('data')
     data = mx.sym.concat(data, -data, dim=1)  # negative part explicitly
@@ -126,6 +133,7 @@ def test_forward_clip():
     test_forward_resnet()
     test_forward_elu()
     test_forward_rrelu()
+    test_forward_prelu()
     test_forward_softrelu()
     test_forward_fc_flatten()
     test_forward_clip()

From b877687fbeb8a6c12b62aac9869f7e54091395f4 Mon Sep 17 00:00:00 2001
From: abergeron <bergearn@iro.umontreal.ca>
Date: Fri, 8 Jun 2018 13:23:34 -0400
Subject: [PATCH 699/949] Unite cmake builds (#1248)

---
 CMakeLists.txt              |  40 +++-
 nnvm/CMakeLists.txt         | 140 -------------
 nnvm/cmake/Utils.cmake      | 402 ------------------------------------
 nnvm/python/nnvm/libinfo.py |  12 +-
 4 files changed, 40 insertions(+), 554 deletions(-)
 delete mode 100644 nnvm/CMakeLists.txt
 delete mode 100644 nnvm/cmake/Utils.cmake

diff --git a/CMakeLists.txt b/CMakeLists.txt
index ff74458f27d2..f3521433a654 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -34,6 +34,7 @@ tvm_option(INSTALL_DEV "Install compiler infrastructure" OFF)
 
 tvm_option(USE_CUDNN "Build with cuDNN" OFF)
 
+include_directories(BEFORE "nnvm/include")
 include_directories("include")
 include_directories("HalideIR/src")
 include_directories("dlpack/include")
@@ -68,8 +69,9 @@ else(MSVC)
 endif(MSVC)
 
 # add source group
-FILE(GLOB_RECURSE GROUP_SOURCE "src/*.cc" "HalideIR/src/*.cpp")
-FILE(GLOB_RECURSE GROUP_Include "src/*.h" "include/*.h" "HalideIR/src/*.h")
+FILE(GLOB_RECURSE GROUP_SOURCE "src/*.cc" "HalideIR/src/*.cpp" "nnvm/src/*.cc")
+FILE(GLOB_RECURSE GROUP_Include "src/*.h" "include/*.h" "HalideIR/src/*.h"
+                                "nnvm/src/*.h" "nnvm/include/*.h")
 assign_source_group("Source" ${GROUP_SOURCE})
 assign_source_group("Include" ${GROUP_Include})
 
@@ -82,7 +84,16 @@ file(GLOB COMPILER_SRCS
     src/pass/*.cc
     src/op/*.cc
     src/schedule/*.cc
-)
+    )
+
+file(GLOB_RECURSE NNVM_COMPILER_SRCS
+    nnvm/src/c_api/*.cc
+    nnvm/src/core/*.cc
+    nnvm/src/pass/*.cc
+    nnvm/src/compiler/*.cc
+    nnvm/src/top/*.cc
+    )
+
 include_directories(${CMAKE_CURRENT_SOURCE_DIR}/topi/include)
 file(GLOB TOPI_SRCS
     topi/src/*.cc
@@ -252,10 +263,21 @@ list(APPEND RUNTIME_SRCS ${GROUP_Include})
 add_library(tvm SHARED ${COMPILER_SRCS} ${RUNTIME_SRCS})
 add_library(tvm_topi SHARED ${TOPI_SRCS})
 add_library(tvm_runtime SHARED ${RUNTIME_SRCS})
+add_library(nnvm_compiler SHARED ${NNVM_COMPILER_SRCS})
 target_link_libraries(tvm ${TVM_LINKER_LIBS} ${TVM_RUNTIME_LINKER_LIBS})
 target_link_libraries(tvm_topi tvm ${TVM_LINKER_LIBS} ${TVM_RUNTIME_LINKER_LIBS})
-target_link_libraries(tvm_runtime  ${TVM_RUNTIME_LINKER_LIBS})
+target_link_libraries(tvm_runtime ${TVM_RUNTIME_LINKER_LIBS})
+target_link_libraries(nnvm_compiler tvm)
+
 install(TARGETS tvm_runtime DESTINATION lib${LIB_SUFFIX})
+
+if(WIN32)
+  install(TARGETS nnvm_compiler RUNTIME DESTINATION bin)
+  install(TARGETS nnvm_compiler ARCHIVE DESTINATION lib)
+else()
+  install(TARGETS nnvm_compiler LIBRARY DESTINATION lib)
+endif()
+
 if (INSTALL_DEV)
   install(TARGETS tvm DESTINATION lib${LIB_SUFFIX})
   install(
@@ -277,13 +299,18 @@ if (INSTALL_DEV)
     DIRECTORY "dlpack/include/." DESTINATION "include"
     FILES_MATCHING
     PATTERN "*.h"
-  )
+    )
+  install(
+    DIRECTORY "nnvm/include/." DESTINATION "include"
+    FILES_MATCHING
+    PATTERN "*.h"
+    )
 else(INSTALL_DEV)
   install(
     DIRECTORY "include/tvm/runtime/." DESTINATION "include/tvm/runtime"
     FILES_MATCHING
     PATTERN "*.h"
-  )
+    )
 endif(INSTALL_DEV)
 
 if(MSVC)
@@ -291,4 +318,5 @@ if(MSVC)
   target_compile_definitions(tvm_runtime PRIVATE -DHalide_EXPORTS)
   target_compile_definitions(tvm PRIVATE -DTVM_EXPORTS)
   target_compile_definitions(tvm_runtime PRIVATE -DTVM_EXPORTS)
+  target_compile_definitions(nnvm_compiler PRIVATE -DNNVM_EXPORTS)
 endif()
diff --git a/nnvm/CMakeLists.txt b/nnvm/CMakeLists.txt
deleted file mode 100644
index 7523b5a19d61..000000000000
--- a/nnvm/CMakeLists.txt
+++ /dev/null
@@ -1,140 +0,0 @@
-cmake_minimum_required(VERSION 2.8.7)
-
-project(nnvm C CXX)
-
-list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
-
-if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/build/private/local_config.cmake)
-  include(${CMAKE_CURRENT_SOURCE_DIR}/build/private/local_config.cmake)
-endif()
-
-include(cmake/Utils.cmake)
-if(EXISTS ${CMAKE_CURRENT_BINARY_DIR}/config.cmake)
-  include(${CMAKE_CURRENT_BINARY_DIR}/config.cmake)
-else()
-  if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/config.cmake)
-    include(${CMAKE_CURRENT_SOURCE_DIR}/config.cmake)
-  endif()
-endif()
-
-# include path
-include_directories(BEFORE "include")
-include_directories("../include")
-include_directories("../dlpack/include")
-include_directories("../HalideIR/src")
-include_directories("../topi/include")
-
-set(NNVM_LINKER_LIBS "")
-set(NNVM_COMPILER_LINKER_LIBS "")
-add_definitions(-DNNVM_EXPORTS)
-
-# Build a shared lib (libnnvm.so) by default
-option(BUILD_SHARED_NNVM "Build a shared nnvm lib" ON)
-option(BUILD_STATIC_NNVM "Build a static nnvm lib" OFF)
-option(USE_MSVC_MT "Build with MT" OFF)
-
-# compile
-if(MSVC)
-  add_definitions(-DDMLC_USE_CXX11)
-  add_definitions(-DDMLC_STRICT_CXX11)
-  if (USE_MSVC_MT)
-    foreach(flag_var
-        CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
-        CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
-      if(${flag_var} MATCHES "/MD")
-	    string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
-      endif(${flag_var} MATCHES "/MD")
-    endforeach(flag_var)
-  endif()
-  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc")
-  find_library(TVM_LIB tvm
-			   HINTS ${CMAKE_CURRENT_SOURCE_DIR}/../build/Release)
-  message(STATUS "Build with TVM libary: " ${TVM_LIB})
-  list(APPEND NNVM_COMPILER_LINKER_LIBS ${TVM_LIB})
-else(MSVC)
-  include(CheckCXXCompilerFlag)
-  check_cxx_compiler_flag("-std=c++11"    SUPPORT_CXX11)
-  set(CMAKE_C_FLAGS "-O3 -Wall -std=c++11 -fPIC")
-  set(CMAKE_CXX_FLAGS ${CMAKE_C_FLAGS})
-endif(MSVC)
-
-if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
-  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -undefined dynamic_lookup")
-endif()
-
-
-mxnet_source_group("Include\\c_api"   GLOB "src/c_api/*.h")
-mxnet_source_group("Include\\core"   GLOB "src/core/*.h")
-mxnet_source_group("Include\\pass"   GLOB "src/pass/*.h")
-mxnet_source_group("Include\\nnvm"   GLOB "include/nnvm/*.h")
-mxnet_source_group("Include\\dmlc"   GLOB "include/dmlc/*.h")
-
-mxnet_source_group("Source"   GLOB "src/*.cc")
-mxnet_source_group("Source\\c_api"   GLOB "src/c_api/*.cc")
-mxnet_source_group("Source\\core"   GLOB "src/core/*.cc")
-mxnet_source_group("Source\\pass"   GLOB "src/pass/*.cc")
-
-
-file(GLOB_RECURSE SOURCE
-   src/c_api/*.cc
-   src/core/*.cc
-   src/pass/*.cc)
-
-file(GLOB_RECURSE COMPILER_SRCS
-    src/compiler/*.cc
-    src/top/*.cc
-)
-
-if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/../dmlc-core/CMakeLists.txt)
-  include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../dmlc-core/include)
-elseif(DMLC_CORE_PATH)
-  include_directories(${DMLC_CORE_PATH}/include)
-endif()
-
-if(BUILD_SHARED_NNVM)
-  add_library(nnvm SHARED ${SOURCE})
-  target_link_libraries(nnvm ${nnvm_LINKER_LIBS})
-endif()
-if(BUILD_STATIC_NNVM)
-  add_library(nnvm_static STATIC ${SOURCE})
-  target_link_libraries(nnvm_static ${nnvm_LINKER_LIBS})
-  set_target_properties(nnvm_static PROPERTIES OUTPUT_NAME "nnvm")
-endif()
-
-add_library(nnvm_compiler SHARED ${COMPILER_SRCS} ${SOURCE})
-target_link_libraries(nnvm_compiler ${NNVM_COMPILER_LINKER_LIBS} ${NNVM_LINKER_LIBS})
-
-if(INSTALL_INCLUDE_DIR)
-  add_custom_command(TARGET nnvm POST_BUILD
-    COMMAND COMMAND ${CMAKE_COMMAND} -E copy_directory
-    ${CMAKE_SOURCE_DIR}/include ${INSTALL_INCLUDE_DIR}/
-    )
-endif()
-
-# ---[ Install lib, header and docs
-if(BUILD_SHARED_NNVM)
-  if(WIN32)
-    install(TARGETS nnvm RUNTIME DESTINATION bin)
-    install(TARGETS nnvm ARCHIVE DESTINATION lib)
-    install(TARGETS nnvm_compiler RUNTIME DESTINATION bin)
-    install(TARGETS nnvm_compiler ARCHIVE DESTINATION lib)
-  else()
-    install(TARGETS nnvm LIBRARY DESTINATION lib)
-    install(TARGETS nnvm_compiler LIBRARY DESTINATION lib)
-  endif()
-endif()
-if(BUILD_STATIC_NNVM)
-  install(TARGETS nnvm_static ARCHIVE DESTINATION lib)
-endif()
-install(DIRECTORY include DESTINATION .)
-install(DIRECTORY docs DESTINATION .)
-
-# ---[ Linter target
-if(MSVC)
-  find_package(PythonInterp 2)
-  set(PYTHON2_EXECUTABLE ${PYTHON_EXECUTABLE} CACHE FILEPATH "Path to the python 2.x executable")
-  find_package(PythonInterp 3)
-  set(PYTHON3_EXECUTABLE ${PYTHON_EXECUTABLE} CACHE FILEPATH "Path to the python 3.x executable")
-endif()
-set(LINT_DIRS include src scripts)
-add_custom_target(nnvm_lint COMMAND ${CMAKE_COMMAND} -DMSVC=${MSVC} -DPYTHON2_EXECUTABLE=${PYTHON2_EXECUTABLE} -DPYTHON3_EXECUTABLE=${PYTHON3_EXECUTABLE} -DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR} -DLINT_DIRS=${LINT_DIRS} -DPROJECT_NAME=dmlc -P ${PROJECT_SOURCE_DIR}/cmake/lint.cmake)
diff --git a/nnvm/cmake/Utils.cmake b/nnvm/cmake/Utils.cmake
deleted file mode 100644
index 0308645df6b8..000000000000
--- a/nnvm/cmake/Utils.cmake
+++ /dev/null
@@ -1,402 +0,0 @@
-# For cmake_parse_arguments
-include(CMakeParseArguments)
-
-################################################################################################
-# Command alias for debugging messages
-# Usage:
-#   dmsg(<message>)
-function(dmsg)
-  message(STATUS ${ARGN})
-endfunction()
-
-################################################################################################
-# Removes duplicates from list(s)
-# Usage:
-#   mxnet_list_unique(<list_variable> [<list_variable>] [...])
-macro(mxnet_list_unique)
-  foreach(__lst ${ARGN})
-    if(${__lst})
-      list(REMOVE_DUPLICATES ${__lst})
-    endif()
-  endforeach()
-endmacro()
-
-################################################################################################
-# Clears variables from list
-# Usage:
-#   mxnet_clear_vars(<variables_list>)
-macro(mxnet_clear_vars)
-  foreach(_var ${ARGN})
-    unset(${_var})
-  endforeach()
-endmacro()
-
-################################################################################################
-# Removes duplicates from string
-# Usage:
-#   mxnet_string_unique(<string_variable>)
-function(mxnet_string_unique __string)
-  if(${__string})
-    set(__list ${${__string}})
-    separate_arguments(__list)
-    list(REMOVE_DUPLICATES __list)
-    foreach(__e ${__list})
-      set(__str "${__str} ${__e}")
-    endforeach()
-    set(${__string} ${__str} PARENT_SCOPE)
-  endif()
-endfunction()
-
-################################################################################################
-# Prints list element per line
-# Usage:
-#   mxnet_print_list(<list>)
-function(mxnet_print_list)
-  foreach(e ${ARGN})
-    message(STATUS ${e})
-  endforeach()
-endfunction()
-
-################################################################################################
-# Function merging lists of compiler flags to single string.
-# Usage:
-#   mxnet_merge_flag_lists(out_variable <list1> [<list2>] [<list3>] ...)
-function(mxnet_merge_flag_lists out_var)
-  set(__result "")
-  foreach(__list ${ARGN})
-    foreach(__flag ${${__list}})
-      string(STRIP ${__flag} __flag)
-      set(__result "${__result} ${__flag}")
-    endforeach()
-  endforeach()
-  string(STRIP ${__result} __result)
-  set(${out_var} ${__result} PARENT_SCOPE)
-endfunction()
-
-################################################################################################
-# Converts all paths in list to absolute
-# Usage:
-#   mxnet_convert_absolute_paths(<list_variable>)
-function(mxnet_convert_absolute_paths variable)
-  set(__dlist "")
-  foreach(__s ${${variable}})
-    get_filename_component(__abspath ${__s} ABSOLUTE)
-    list(APPEND __list ${__abspath})
-  endforeach()
-  set(${variable} ${__list} PARENT_SCOPE)
-endfunction()
-
-################################################################################################
-# Reads set of version defines from the header file
-# Usage:
-#   mxnet_parse_header(<file> <define1> <define2> <define3> ..)
-macro(mxnet_parse_header FILENAME FILE_VAR)
-  set(vars_regex "")
-  set(__parnet_scope OFF)
-  set(__add_cache OFF)
-  foreach(name ${ARGN})
-    if("${name}" STREQUAL "PARENT_SCOPE")
-      set(__parnet_scope ON)
-    elseif("${name}" STREQUAL "CACHE")
-      set(__add_cache ON)
-    elseif(vars_regex)
-      set(vars_regex "${vars_regex}|${name}")
-    else()
-      set(vars_regex "${name}")
-    endif()
-  endforeach()
-  if(EXISTS "${FILENAME}")
-    file(STRINGS "${FILENAME}" ${FILE_VAR} REGEX "#define[ \t]+(${vars_regex})[ \t]+[0-9]+" )
-  else()
-    unset(${FILE_VAR})
-  endif()
-  foreach(name ${ARGN})
-    if(NOT "${name}" STREQUAL "PARENT_SCOPE" AND NOT "${name}" STREQUAL "CACHE")
-      if(${FILE_VAR})
-        if(${FILE_VAR} MATCHES ".+[ \t]${name}[ \t]+([0-9]+).*")
-          string(REGEX REPLACE ".+[ \t]${name}[ \t]+([0-9]+).*" "\\1" ${name} "${${FILE_VAR}}")
-        else()
-          set(${name} "")
-        endif()
-        if(__add_cache)
-          set(${name} ${${name}} CACHE INTERNAL "${name} parsed from ${FILENAME}" FORCE)
-        elseif(__parnet_scope)
-          set(${name} "${${name}}" PARENT_SCOPE)
-        endif()
-      else()
-        unset(${name} CACHE)
-      endif()
-    endif()
-  endforeach()
-endmacro()
-
-################################################################################################
-# Reads single version define from the header file and parses it
-# Usage:
-#   mxnet_parse_header_single_define(<library_name> <file> <define_name>)
-function(mxnet_parse_header_single_define LIBNAME HDR_PATH VARNAME)
-  set(${LIBNAME}_H "")
-  if(EXISTS "${HDR_PATH}")
-    file(STRINGS "${HDR_PATH}" ${LIBNAME}_H REGEX "^#define[ \t]+${VARNAME}[ \t]+\"[^\"]*\".*$" LIMIT_COUNT 1)
-  endif()
-
-  if(${LIBNAME}_H)
-    string(REGEX REPLACE "^.*[ \t]${VARNAME}[ \t]+\"([0-9]+).*$" "\\1" ${LIBNAME}_VERSION_MAJOR "${${LIBNAME}_H}")
-    string(REGEX REPLACE "^.*[ \t]${VARNAME}[ \t]+\"[0-9]+\\.([0-9]+).*$" "\\1" ${LIBNAME}_VERSION_MINOR  "${${LIBNAME}_H}")
-    string(REGEX REPLACE "^.*[ \t]${VARNAME}[ \t]+\"[0-9]+\\.[0-9]+\\.([0-9]+).*$" "\\1" ${LIBNAME}_VERSION_PATCH "${${LIBNAME}_H}")
-    set(${LIBNAME}_VERSION_MAJOR ${${LIBNAME}_VERSION_MAJOR} ${ARGN} PARENT_SCOPE)
-    set(${LIBNAME}_VERSION_MINOR ${${LIBNAME}_VERSION_MINOR} ${ARGN} PARENT_SCOPE)
-    set(${LIBNAME}_VERSION_PATCH ${${LIBNAME}_VERSION_PATCH} ${ARGN} PARENT_SCOPE)
-    set(${LIBNAME}_VERSION_STRING "${${LIBNAME}_VERSION_MAJOR}.${${LIBNAME}_VERSION_MINOR}.${${LIBNAME}_VERSION_PATCH}" PARENT_SCOPE)
-
-    # append a TWEAK version if it exists:
-    set(${LIBNAME}_VERSION_TWEAK "")
-    if("${${LIBNAME}_H}" MATCHES "^.*[ \t]${VARNAME}[ \t]+\"[0-9]+\\.[0-9]+\\.[0-9]+\\.([0-9]+).*$")
-      set(${LIBNAME}_VERSION_TWEAK "${CMAKE_MATCH_1}" ${ARGN} PARENT_SCOPE)
-    endif()
-    if(${LIBNAME}_VERSION_TWEAK)
-      set(${LIBNAME}_VERSION_STRING "${${LIBNAME}_VERSION_STRING}.${${LIBNAME}_VERSION_TWEAK}" ${ARGN} PARENT_SCOPE)
-    else()
-      set(${LIBNAME}_VERSION_STRING "${${LIBNAME}_VERSION_STRING}" ${ARGN} PARENT_SCOPE)
-    endif()
-  endif()
-endfunction()
-
-########################################################################################################
-# An option that the user can select. Can accept condition to control when option is available for user.
-# Usage:
-#   mxnet_option(<option_variable> "doc string" <initial value or boolean expression> [IF <condition>])
-function(mxnet_option variable description value)
-  set(__value ${value})
-  set(__condition "")
-  set(__varname "__value")
-  foreach(arg ${ARGN})
-    if(arg STREQUAL "IF" OR arg STREQUAL "if")
-      set(__varname "__condition")
-    else()
-      list(APPEND ${__varname} ${arg})
-    endif()
-  endforeach()
-  unset(__varname)
-  if("${__condition}" STREQUAL "")
-    set(__condition 2 GREATER 1)
-  endif()
-
-  if(${__condition})
-    if("${__value}" MATCHES ";")
-      if(${__value})
-        option(${variable} "${description}" ON)
-      else()
-        option(${variable} "${description}" OFF)
-      endif()
-    elseif(DEFINED ${__value})
-      if(${__value})
-        option(${variable} "${description}" ON)
-      else()
-        option(${variable} "${description}" OFF)
-      endif()
-    else()
-      option(${variable} "${description}" ${__value})
-    endif()
-  else()
-    unset(${variable} CACHE)
-  endif()
-endfunction()
-
-################################################################################################
-# Utility macro for comparing two lists. Used for CMake debugging purposes
-# Usage:
-#   mxnet_compare_lists(<list_variable> <list2_variable> [description])
-function(mxnet_compare_lists list1 list2 desc)
-  set(__list1 ${${list1}})
-  set(__list2 ${${list2}})
-  list(SORT __list1)
-  list(SORT __list2)
-  list(LENGTH __list1 __len1)
-  list(LENGTH __list2 __len2)
-
-  if(NOT ${__len1} EQUAL ${__len2})
-    message(FATAL_ERROR "Lists are not equal. ${__len1} != ${__len2}. ${desc}")
-  endif()
-
-  foreach(__i RANGE 1 ${__len1})
-    math(EXPR __index "${__i}- 1")
-    list(GET __list1 ${__index} __item1)
-    list(GET __list2 ${__index} __item2)
-    if(NOT ${__item1} STREQUAL ${__item2})
-      message(FATAL_ERROR "Lists are not equal. Differ at element ${__index}. ${desc}")
-    endif()
-  endforeach()
-endfunction()
-
-################################################################################################
-# Command for disabling warnings for different platforms (see below for gcc and VisualStudio)
-# Usage:
-#   mxnet_warnings_disable(<CMAKE_[C|CXX]_FLAGS[_CONFIGURATION]> -Wshadow /wd4996 ..,)
-macro(mxnet_warnings_disable)
-  set(_flag_vars "")
-  set(_msvc_warnings "")
-  set(_gxx_warnings "")
-
-  foreach(arg ${ARGN})
-    if(arg MATCHES "^CMAKE_")
-      list(APPEND _flag_vars ${arg})
-    elseif(arg MATCHES "^/wd")
-      list(APPEND _msvc_warnings ${arg})
-    elseif(arg MATCHES "^-W")
-      list(APPEND _gxx_warnings ${arg})
-    endif()
-  endforeach()
-
-  if(NOT _flag_vars)
-    set(_flag_vars CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
-  endif()
-
-  if(MSVC AND _msvc_warnings)
-    foreach(var ${_flag_vars})
-      foreach(warning ${_msvc_warnings})
-        set(${var} "${${var}} ${warning}")
-      endforeach()
-    endforeach()
-  elseif((CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX) AND _gxx_warnings)
-    foreach(var ${_flag_vars})
-      foreach(warning ${_gxx_warnings})
-        if(NOT warning MATCHES "^-Wno-")
-          string(REPLACE "${warning}" "" ${var} "${${var}}")
-          string(REPLACE "-W" "-Wno-" warning "${warning}")
-        endif()
-        set(${var} "${${var}} ${warning}")
-      endforeach()
-    endforeach()
-  endif()
-  mxnet_clear_vars(_flag_vars _msvc_warnings _gxx_warnings)
-endmacro()
-
-################################################################################################
-# Helper function get current definitions
-# Usage:
-#   mxnet_get_current_definitions(<definitions_variable>)
-function(mxnet_get_current_definitions definitions_var)
-  get_property(current_definitions DIRECTORY PROPERTY COMPILE_DEFINITIONS)
-  set(result "")
-
-  foreach(d ${current_definitions})
-    list(APPEND result -D${d})
-  endforeach()
-
-  mxnet_list_unique(result)
-  set(${definitions_var} ${result} PARENT_SCOPE)
-endfunction()
-
-################################################################################################
-# Helper function get current includes/definitions
-# Usage:
-#   mxnet_get_current_cflags(<cflagslist_variable>)
-function(mxnet_get_current_cflags cflags_var)
-  get_property(current_includes DIRECTORY PROPERTY INCLUDE_DIRECTORIES)
-  mxnet_convert_absolute_paths(current_includes)
-  mxnet_get_current_definitions(cflags)
-
-  foreach(i ${current_includes})
-    list(APPEND cflags "-I${i}")
-  endforeach()
-
-  mxnet_list_unique(cflags)
-  set(${cflags_var} ${cflags} PARENT_SCOPE)
-endfunction()
-
-################################################################################################
-# Helper function to parse current linker libs into link directories, libflags and osx frameworks
-# Usage:
-#   mxnet_parse_linker_libs(<mxnet_LINKER_LIBS_var> <directories_var> <libflags_var> <frameworks_var>)
-function(mxnet_parse_linker_libs mxnet_LINKER_LIBS_variable folders_var flags_var frameworks_var)
-
-  set(__unspec "")
-  set(__debug "")
-  set(__optimized "")
-  set(__framework "")
-  set(__varname "__unspec")
-
-  # split libs into debug, optimized, unspecified and frameworks
-  foreach(list_elem ${${mxnet_LINKER_LIBS_variable}})
-    if(list_elem STREQUAL "debug")
-      set(__varname "__debug")
-    elseif(list_elem STREQUAL "optimized")
-      set(__varname "__optimized")
-    elseif(list_elem MATCHES "^-framework[ \t]+([^ \t].*)")
-      list(APPEND __framework -framework ${CMAKE_MATCH_1})
-    else()
-      list(APPEND ${__varname} ${list_elem})
-      set(__varname "__unspec")
-    endif()
-  endforeach()
-
-  # attach debug or optimized libs to unspecified according to current configuration
-  if(CMAKE_BUILD_TYPE MATCHES "Debug")
-    set(__libs ${__unspec} ${__debug})
-  else()
-    set(__libs ${__unspec} ${__optimized})
-  endif()
-
-  set(libflags "")
-  set(folders "")
-
-  # convert linker libraries list to link flags
-  foreach(lib ${__libs})
-    if(TARGET ${lib})
-      list(APPEND folders $<TARGET_LINKER_FILE_DIR:${lib}>)
-      list(APPEND libflags -l${lib})
-    elseif(lib MATCHES "^-l.*")
-      list(APPEND libflags ${lib})
-    elseif(IS_ABSOLUTE ${lib})
-      get_filename_component(name_we ${lib} NAME_WE)
-      get_filename_component(folder  ${lib} PATH)
-
-      string(REGEX MATCH "^lib(.*)" __match ${name_we})
-      list(APPEND libflags -l${CMAKE_MATCH_1})
-      list(APPEND folders    ${folder})
-    else()
-      message(FATAL_ERROR "Logic error. Need to update cmake script")
-    endif()
-  endforeach()
-
-  mxnet_list_unique(libflags folders)
-
-  set(${folders_var} ${folders} PARENT_SCOPE)
-  set(${flags_var} ${libflags} PARENT_SCOPE)
-  set(${frameworks_var} ${__framework} PARENT_SCOPE)
-endfunction()
-
-################################################################################################
-# Helper function to detect Darwin version, i.e. 10.8, 10.9, 10.10, ....
-# Usage:
-#   mxnet_detect_darwin_version(<version_variable>)
-function(mxnet_detect_darwin_version output_var)
-  if(APPLE)
-    execute_process(COMMAND /usr/bin/sw_vers -productVersion
-                    RESULT_VARIABLE __sw_vers OUTPUT_VARIABLE __sw_vers_out
-                    ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
-
-    set(${output_var} ${__sw_vers_out} PARENT_SCOPE)
-  else()
-    set(${output_var} "" PARENT_SCOPE)
-  endif()
-endfunction()
-
-################################################################################################
-# Convenient command to setup source group for IDEs that support this feature (VS, XCode)
-# Usage:
-#   caffe_source_group(<group> GLOB[_RECURSE] <globbing_expression>)
-function(mxnet_source_group group)
-  cmake_parse_arguments(CAFFE_SOURCE_GROUP "" "" "GLOB;GLOB_RECURSE" ${ARGN})
-  if(CAFFE_SOURCE_GROUP_GLOB)
-    file(GLOB srcs1 ${CAFFE_SOURCE_GROUP_GLOB})
-    source_group(${group} FILES ${srcs1})
-  endif()
-
-  if(CAFFE_SOURCE_GROUP_GLOB_RECURSE)
-    file(GLOB_RECURSE srcs2 ${CAFFE_SOURCE_GROUP_GLOB_RECURSE})
-    source_group(${group} FILES ${srcs2})
-  endif()
-endfunction()
-
diff --git a/nnvm/python/nnvm/libinfo.py b/nnvm/python/nnvm/libinfo.py
index 86c833cf1110..694bd0f92e7c 100644
--- a/nnvm/python/nnvm/libinfo.py
+++ b/nnvm/python/nnvm/libinfo.py
@@ -29,8 +29,8 @@ def find_lib_path():
         lib_name = "nnvm_compiler" if sys.platform.startswith('win32') else "libnnvm_compiler"
 
     api_path = os.path.join(base_path, '../../lib/')
-    cmake_build_path = os.path.join(base_path, '../../build/Release/')
-    cmake_build_path = os.path.join(base_path, '../../build/')
+    cmake_build_path = os.path.join(base_path, '../../../build/Release/')
+    cmake_build_path = os.path.join(base_path, '../../../build/')
     dll_path = [base_path, api_path, cmake_build_path]
 
     if sys.platform.startswith('linux') and os.environ.get('LD_LIBRARY_PATH', None):
@@ -43,11 +43,11 @@ def find_lib_path():
     if sys.platform.startswith('win32'):
         vs_configuration = 'Release'
         if platform.architecture()[0] == '64bit':
-            dll_path.append(os.path.join(base_path, '../../build', vs_configuration))
-            dll_path.append(os.path.join(base_path, '../../windows/x64', vs_configuration))
+            dll_path.append(os.path.join(base_path, '../../../build', vs_configuration))
+            dll_path.append(os.path.join(base_path, '../../../windows/x64', vs_configuration))
         else:
-            dll_path.append(os.path.join(base_path, '../../build', vs_configuration))
-            dll_path.append(os.path.join(base_path, '../../windows', vs_configuration))
+            dll_path.append(os.path.join(base_path, '../../../build', vs_configuration))
+            dll_path.append(os.path.join(base_path, '../../../windows', vs_configuration))
         dll_path = [os.path.join(p, '%s.dll' % lib_name) for p in dll_path]
     elif sys.platform.startswith('darwin'):
         dll_path = [os.path.join(p, '%s.dylib' % lib_name) for p in dll_path]

From fed3b3df735da4a57abf0e88232e44072fefdeef Mon Sep 17 00:00:00 2001
From: Haibin Lin <linhaibin.eric@gmail.com>
Date: Sun, 10 Jun 2018 20:49:19 -0700
Subject: [PATCH 700/949] Update layout.h (#1255)

---
 nnvm/include/nnvm/layout.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/include/nnvm/layout.h b/nnvm/include/nnvm/layout.h
index bec523b34811..918eb9a25dee 100644
--- a/nnvm/include/nnvm/layout.h
+++ b/nnvm/include/nnvm/layout.h
@@ -348,7 +348,7 @@ class Layout {
     return false;
   }
 
-  inline const LayoutDim operator[](size_t i) const {
+  inline LayoutDim operator[](size_t i) const {
     return layout_simplified_[i];
   }
 

From 6207264bce32517f06cca57278ba553a03c58bda Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 10 Jun 2018 22:00:33 -0700
Subject: [PATCH 701/949] [BUILD] Switch to CMake only Infra (#1254)

---
 .gitignore                                    |   7 +-
 CMakeLists.txt                                | 227 ++++-------
 Jenkinsfile                                   | 124 +++---
 Makefile                                      | 374 +++---------------
 cmake/config.cmake                            |  87 ++++
 cmake/modules/CUDA.cmake                      |  76 ++++
 cmake/modules/LLVM.cmake                      |  26 ++
 cmake/modules/Metal.cmake                     |  18 +
 cmake/modules/OpenCL.cmake                    |  19 +
 cmake/modules/OpenGL.cmake                    |  19 +
 cmake/modules/ROCM.cmake                      |  34 ++
 cmake/modules/Vulkan.cmake                    |  31 ++
 cmake/modules/contrib/BLAS.cmake              |  24 ++
 cmake/modules/contrib/NNPack.cmake            |  14 +
 cmake/modules/contrib/Random.cmake            |   5 +
 cmake/modules/contrib/Sort.cmake              |   5 +
 cmake/util/FindLLVM.cmake                     |  39 ++
 cmake/{ => util}/Util.cmake                   |  22 +-
 docs/install/index.rst                        |  41 +-
 docs/install/nnpack.md                        |   3 +-
 include/tvm/runtime/config.h                  |  30 --
 .../src/main/assembly/assembly.xml            |   2 +-
 .../src/main/assembly/assembly.xml            |   2 +-
 .../src/main/assembly/assembly.xml            |   2 +-
 jvm/core/pom.xml                              |   4 +-
 make/config.mk                                |  95 -----
 make/contrib/cblas.mk                         |  17 -
 make/contrib/cublas.mk                        |   8 -
 make/contrib/cudnn.mk                         |   8 -
 make/contrib/miopen.mk                        |   8 -
 make/contrib/mps.mk                           |  20 -
 make/contrib/nnpack.mk                        |  12 -
 make/contrib/random.mk                        |   6 -
 make/contrib/rocblas.mk                       |   8 -
 make/contrib/sort.mk                          |   6 -
 src/codegen/codegen_cuda.cc                   |   1 -
 src/codegen/codegen_metal.cc                  |   1 -
 src/codegen/codegen_opencl.cc                 |   1 -
 src/codegen/codegen_opengl.cc                 |   1 -
 src/codegen/llvm/codegen_amdgpu.cc            |   1 -
 src/codegen/llvm/codegen_nvptx.cc             |   2 -
 src/codegen/opt/README                        |   1 +
 src/codegen/opt/build_cuda_off.cc             |  18 +
 .../{build_cuda.cc => opt/build_cuda_on.cc}   |  15 +-
 .../{build_metal.cc => opt/build_metal.h}     |  15 +-
 src/codegen/opt/build_metal_off.cc            |   6 +
 src/codegen/opt/build_metal_on.cc             |   6 +
 .../{build_opencl.cc => opt/build_opencl.h}   |  14 +-
 src/codegen/opt/build_opencl_off.cc           |   6 +
 src/codegen/opt/build_opencl_on.cc            |   6 +
 .../{build_opengl.cc => opt/build_opengl.h}   |  10 +-
 src/codegen/opt/build_opengl_off.cc           |   6 +
 src/codegen/opt/build_opengl_on.cc            |   6 +
 src/runtime/cuda/cuda_common.h                |   6 +-
 src/runtime/cuda/cuda_device_api.cc           |   3 -
 src/runtime/cuda/cuda_module.cc               |   3 -
 src/runtime/cuda/cuda_module.h                |   1 -
 src/runtime/metal/metal_common.h              |   1 -
 src/runtime/metal/metal_device_api.mm         |   6 +-
 src/runtime/metal/metal_module.h              |   1 -
 src/runtime/metal/metal_module.mm             |   6 +-
 src/runtime/opencl/opencl_common.h            |   3 -
 src/runtime/opencl/opencl_device_api.cc       |   7 +-
 src/runtime/opencl/opencl_module.cc           |  10 +-
 src/runtime/opencl/opencl_module.h            |   1 -
 src/runtime/opengl/opengl_common.h            |   1 -
 src/runtime/opengl/opengl_device_api.cc       |   9 +-
 src/runtime/opengl/opengl_module.cc           |   8 +-
 src/runtime/opengl/opengl_module.h            |   1 -
 src/runtime/rocm/rocm_common.h                |  20 +-
 src/runtime/rocm/rocm_device_api.cc           |   3 -
 src/runtime/rocm/rocm_module.cc               |   6 +-
 src/runtime/rocm/rocm_module.h                |   1 -
 src/runtime/vulkan/vulkan_common.h            |   4 -
 src/runtime/vulkan/vulkan_device_api.cc       |   8 +-
 src/runtime/vulkan/vulkan_module.cc           |   6 +-
 src/runtime/vulkan/vulkan_module.h            |   1 -
 tests/scripts/task_build.sh                   |   7 +-
 tests/scripts/task_clean.sh                   |   5 +-
 tests/scripts/task_cpp_unittest.sh            |   4 +-
 tests/scripts/task_java_unittest.sh           |   3 +-
 tests/scripts/task_python_integration.sh      |   2 +-
 82 files changed, 729 insertions(+), 947 deletions(-)
 create mode 100644 cmake/config.cmake
 create mode 100644 cmake/modules/CUDA.cmake
 create mode 100644 cmake/modules/LLVM.cmake
 create mode 100644 cmake/modules/Metal.cmake
 create mode 100644 cmake/modules/OpenCL.cmake
 create mode 100644 cmake/modules/OpenGL.cmake
 create mode 100644 cmake/modules/ROCM.cmake
 create mode 100644 cmake/modules/Vulkan.cmake
 create mode 100644 cmake/modules/contrib/BLAS.cmake
 create mode 100644 cmake/modules/contrib/NNPack.cmake
 create mode 100644 cmake/modules/contrib/Random.cmake
 create mode 100644 cmake/modules/contrib/Sort.cmake
 create mode 100644 cmake/util/FindLLVM.cmake
 rename cmake/{ => util}/Util.cmake (72%)
 delete mode 100644 include/tvm/runtime/config.h
 delete mode 100644 make/config.mk
 delete mode 100644 make/contrib/cblas.mk
 delete mode 100644 make/contrib/cublas.mk
 delete mode 100644 make/contrib/cudnn.mk
 delete mode 100644 make/contrib/miopen.mk
 delete mode 100644 make/contrib/mps.mk
 delete mode 100644 make/contrib/nnpack.mk
 delete mode 100644 make/contrib/random.mk
 delete mode 100644 make/contrib/rocblas.mk
 delete mode 100644 make/contrib/sort.mk
 create mode 100644 src/codegen/opt/README
 create mode 100644 src/codegen/opt/build_cuda_off.cc
 rename src/codegen/{build_cuda.cc => opt/build_cuda_on.cc} (91%)
 rename src/codegen/{build_metal.cc => opt/build_metal.h} (80%)
 create mode 100644 src/codegen/opt/build_metal_off.cc
 create mode 100644 src/codegen/opt/build_metal_on.cc
 rename src/codegen/{build_opencl.cc => opt/build_opencl.h} (78%)
 create mode 100644 src/codegen/opt/build_opencl_off.cc
 create mode 100644 src/codegen/opt/build_opencl_on.cc
 rename src/codegen/{build_opengl.cc => opt/build_opengl.h} (80%)
 create mode 100644 src/codegen/opt/build_opengl_off.cc
 create mode 100644 src/codegen/opt/build_opengl_on.cc

diff --git a/.gitignore b/.gitignore
index 1d5db4cfebfd..7080502aaf86 100644
--- a/.gitignore
+++ b/.gitignore
@@ -172,16 +172,19 @@ cscope*
 
 # TVM generated code
 perf
-
+.bash_history
 *.json
 *.params
 *.onnx
 *.h5
 synset.txt
 cat.jpg
-
+docs.tgz
+cat.png
+*.mlmodel
 # Mac OS X
 .DS_Store
+build*
 
 # Jetbrain
 .idea
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f3521433a654..84ce87d84e76 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,12 +1,14 @@
-cmake_minimum_required(VERSION 3.7)
+cmake_minimum_required(VERSION 3.5)
 project(tvm C CXX)
 
+# Utility functions
+include(cmake/util/Util.cmake)
+include(cmake/util/FindLLVM.cmake)
+
 if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/build/private/local_config.cmake)
   include(${CMAKE_CURRENT_SOURCE_DIR}/build/private/local_config.cmake)
 endif()
 
-include(cmake/Util.cmake)
-
 if(EXISTS ${CMAKE_CURRENT_BINARY_DIR}/config.cmake)
   include(${CMAKE_CURRENT_BINARY_DIR}/config.cmake)
 else()
@@ -19,31 +21,43 @@ endif()
 # You can create a config.cmake at build folder
 # and add set(OPTION VALUE) to override these build options.
 # Alernatively, use cmake -DOPTION=VALUE through command-line.
-
 tvm_option(USE_CUDA "Build with CUDA" OFF)
 tvm_option(USE_OPENCL "Build with OpenCL" OFF)
 tvm_option(USE_VULKAN "Build with Vulkan" OFF)
 tvm_option(USE_OPENGL "Build with OpenGL" OFF)
 tvm_option(USE_METAL "Build with Metal" OFF)
+tvm_option(USE_ROCM "Build with ROCM" OFF)
+tvm_option(ROCM_PATH "The path to rocm" /opt/rocm)
 tvm_option(USE_RPC "Build with RPC" ON)
+tvm_option(USE_LLVM "Build with LLVM, can be set to specific llvm-config path" OFF)
 tvm_option(USE_GRAPH_RUNTIME "Build with tiny graph runtime" ON)
-tvm_option(USE_LLVM "Build with LLVM" OFF)
+tvm_option(USE_GRAPH_RUNTIME_DEBUG "Build with tiny graph runtime debug mode" OFF)
 tvm_option(USE_RTTI "Build with RTTI" ON)
 tvm_option(USE_MSVC_MT "Build with MT" OFF)
 tvm_option(INSTALL_DEV "Install compiler infrastructure" OFF)
 
+# Contrib library options
+tvm_option(USE_BLAS "The blas library to be linked" none)
 tvm_option(USE_CUDNN "Build with cuDNN" OFF)
-
+tvm_option(USE_CUBLAS "Build with cuBLAS" OFF)
+tvm_option(USE_MIOPEN "Build with ROCM:MIOpen" OFF)
+tvm_option(USE_ROCBLAS "Build with ROCM:RoCBLAS" OFF)
+tvm_option(USE_SORT "Build with sort support" OFF)
+tvm_option(USE_NNPACK "Build with nnpack support" OFF)
+tvm_option(USE_RANDOM "Build with random support" OFF)
+
+# include directories
 include_directories(BEFORE "nnvm/include")
 include_directories("include")
 include_directories("HalideIR/src")
 include_directories("dlpack/include")
+include_directories("topi/include")
 
-
+# initial variables
 set(TVM_LINKER_LIBS "")
 set(TVM_RUNTIME_LINKER_LIBS "")
 
-# compile
+# Generic compilation options
 if(MSVC)
   add_definitions(-DWIN32_LEAN_AND_MEAN)
   add_definitions(-D_CRT_SECURE_NO_WARNINGS)
@@ -70,11 +84,12 @@ endif(MSVC)
 
 # add source group
 FILE(GLOB_RECURSE GROUP_SOURCE "src/*.cc" "HalideIR/src/*.cpp" "nnvm/src/*.cc")
-FILE(GLOB_RECURSE GROUP_Include "src/*.h" "include/*.h" "HalideIR/src/*.h"
+FILE(GLOB_RECURSE GROUP_INCLUDE "src/*.h" "include/*.h" "HalideIR/src/*.h"
                                 "nnvm/src/*.h" "nnvm/include/*.h")
 assign_source_group("Source" ${GROUP_SOURCE})
-assign_source_group("Include" ${GROUP_Include})
+assign_source_group("Include" ${GROUP_INCLUDE})
 
+# Source file lists
 file(GLOB COMPILER_SRCS
     src/api/*.cc
     src/arithmetic/*.cc
@@ -86,6 +101,11 @@ file(GLOB COMPILER_SRCS
     src/schedule/*.cc
     )
 
+if(NOT MSVC)
+  file(GLOB COMPILER_VERILOG_SRCS src/codegen/verilog/*.cc)
+  list(APPEND COMPILER_SRCS ${COMPILER_VERILOG_SRCS})
+endif()
+
 file(GLOB_RECURSE NNVM_COMPILER_SRCS
     nnvm/src/c_api/*.cc
     nnvm/src/core/*.cc
@@ -94,157 +114,34 @@ file(GLOB_RECURSE NNVM_COMPILER_SRCS
     nnvm/src/top/*.cc
     )
 
-include_directories(${CMAKE_CURRENT_SOURCE_DIR}/topi/include)
 file(GLOB TOPI_SRCS
     topi/src/*.cc
 )
 file(GLOB_RECURSE HALIDEIR_SRCS HalideIR/src/*.cpp)
 list(APPEND COMPILER_SRCS ${HALIDEIR_SRCS})
 file(GLOB RUNTIME_SRCS src/runtime/*.cc)
-file(GLOB COMPILER_LLVM_SRCS src/codegen/llvm/*.cc)
-file(GLOB COMPILER_VULKAN_SRCS src/codegen/spirv/*.cc)
-file(GLOB RUNTIME_CUDA_SRCS src/runtime/cuda/*.cc)
-file(GLOB RUNTIME_OPENCL_SRCS src/runtime/opencl/*.cc)
-file(GLOB RUNTIME_OPENGL_SRCS src/runtime/opengl/*.cc)
-file(GLOB RUNTIME_VULKAN_SRCS src/runtime/vulkan/*.cc)
-file(GLOB RUNTIME_METAL_SRCS src/runtime/metal/*.mm)
-file(GLOB RUNTIME_RPC_SRCS src/runtime/rpc/*.cc)
-file(GLOB RUNTIME_GRAPH_SRCS src/runtime/graph/*.cc)
-
-if(USE_CUDA)
-find_package(CUDA)
-# Find CUDA doesn't find all the libraries we need, add the extra ones
-find_library(CUDA_CUDA_LIBRARIES cuda
-  PATHS ${CUDA_TOOLKIT_ROOT_DIR}
-  PATH_SUFFIXES lib lib64 targets/x86_64-linux/lib targets/x86_64-linux/lib/stubs)
-find_library(CUDA_NVRTC_LIBRARIES nvrtc
-  PATHS ${CUDA_TOOLKIT_ROOT_DIR}
-  PATH_SUFFIXES lib lib64 targets/x86_64-linux/lib targets/x86_64-linux/lib/stubs)
-  set(CUDA_CUDA_LIBRARY ${CUDA_CUDA_LIBRARIES})
 
-  find_package(CUDA QUIET REQUIRED)
-  message(STATUS "Build with CUDA support")
-	include_directories(${CUDA_INCLUDE_DIRS})
-  list(APPEND TVM_RUNTIME_LINKER_LIBS ${CUDA_CUDART_LIBRARY})
-  list(APPEND TVM_RUNTIME_LINKER_LIBS ${CUDA_CUDA_LIBRARY})
-  list(APPEND RUNTIME_SRCS ${RUNTIME_CUDA_SRCS})
-  if(MSVC)
-    find_library(CUDA_NVRTC_LIB nvrtc
-      ${CUDA_TOOLKIT_ROOT_DIR}/lib/x64
-      ${CUDA_TOOLKIT_ROOT_DIR}/lib/win32)
-    list(APPEND TVM_LINKER_LIBS ${CUDA_NVRTC_LIB})
-  else(MSVC)
-    find_library(CUDA_NVRTC_LIB nvrtc
-      ${CUDA_TOOLKIT_ROOT_DIR}/lib64
-      ${CUDA_TOOLKIT_ROOT_DIR}/lib)
-    list(APPEND TVM_LINKER_LIBS ${CUDA_NVRTC_LIB})
-  endif(MSVC)
-
-  if(USE_CUDNN)
-    message(STATUS "Build with cuDNN support")
-    file(GLOB CONTRIB_CUDNN_SRCS src/contrib/cudnn/*.cc)
-    list(APPEND RUNTIME_SRCS ${CONTRIB_CUDNN_SRCS})
-    if(MSVC)
-      find_library(CUDA_CUDNN_LIB cudnn
-        ${CUDA_TOOLKIT_ROOT_DIR}/lib/x64
-        ${CUDA_TOOLKIT_ROOT_DIR}/lib/win32)
-      list(APPEND TVM_RUNTIME_LINKER_LIBS ${CUDA_CUDNN_LIB})
-    else(MSVC)
-      find_library(CUDA_CUDNN_LIB cudnn
-        ${CUDA_TOOLKIT_ROOT_DIR}/lib64
-        ${CUDA_TOOLKIT_ROOT_DIR}/lib)
-      list(APPEND TVM_RUNTIME_LINKER_LIBS ${CUDA_CUDNN_LIB})
-    endif(MSVC)
-  endif(USE_CUDNN)
-
-  add_definitions(-DTVM_CUDA_RUNTIME=1)
-else(USE_CUDA)
-  add_definitions(-DTVM_CUDA_RUNTIME=0)
-endif(USE_CUDA)
-
-if(USE_OPENCL)
-  find_package(OpenCL QUIET REQUIRED)
-  message(STATUS "Build with OpenCL support")
-  include_directories(${OpenCL_INCLUDE_DIRS})
-  list(APPEND TVM_RUNTIME_LINKER_LIBS ${OpenCL_LIBRARIES})
-  list(APPEND RUNTIME_SRCS ${RUNTIME_OPENCL_SRCS})
-  add_definitions(-DTVM_OPENCL_RUNTIME=1)
-else(USE_OPENCL)
-  add_definitions(-DTVM_OPENCL_RUNTIME=0)
-endif(USE_OPENCL)
-
-if(USE_OPENGL)
-  find_package(OpenGL QUIET REQUIRED)
-  find_package(glfw3 QUIET REQUIRED)
-  message(STATUS "Build with OpenGL support")
-  include_directories(${OPENGL_INCLUDE_DIRS})
-  list(APPEND TVM_RUNTIME_LINKER_LIBS ${OpenGL_LIBRARIES} glfw)
-  list(APPEND RUNTIME_SRCS ${RUNTIME_OPENGL_SRCS})
-  add_definitions(-DTVM_OPENGL_RUNTIME=1)
-else(USE_OPENGL)
-  add_definitions(-DTVM_OPENGL_RUNTIME=0)
-endif(USE_OPENGL)
-
-if(USE_VULKAN)
-  find_package(Vulkan REQUIRED)
-  message(STATUS "Build with VULKAN support")
-  include_directories(${Vulkan_INCLUDE_DIRS})
-  list(APPEND TVM_RUNTIME_LINKER_LIBS ${Vulkan_LIBRARIES})
-  list(APPEND RUNTIME_SRCS ${RUNTIME_VULKAN_SRCS})
-  list(APPEND COMPILER_SRCS ${COMPILER_VULKAN_SRCS})
-  get_filename_component(VULKAN_LIB_PATH ${Vulkan_LIBRARY} DIRECTORY)
-  find_library(SPIRV_TOOLS_LIB SPIRV-Tools
-               ${VULKAN_LIB_PATH}/spirv-tools)
-  list(APPEND TVM_LINKER_LIBS ${SPIRV_TOOLS_LIB})
-  add_definitions(-DTVM_VULKAN_RUNTIME=1)
-else(USE_VULKAN)
-  add_definitions(-DTVM_VULKAN_RUNTIME=0)
-endif(USE_VULKAN)
-
-if(USE_METAL)
-  find_package(OpenCL QUIET REQUIRED)
-  message(STATUS "Build with Metal support")
-  FIND_LIBRARY(METAL_LIB Metal)
-  FIND_LIBRARY(FOUNDATION_LIB Foundation)
-  list(APPEND TVM_RUNTIME_LINKER_LIBS ${METAL_LIB} ${FOUNDATION_LIB})
-  list(APPEND RUNTIME_SRCS ${RUNTIME_METAL_SRCS})
-  add_definitions(-DTVM_METAL_RUNTIME=1)
-else(USE_METAL)
-  add_definitions(-DTVM_METAL_RUNTIME=0)
-endif(USE_METAL)
+# Package runtime rules
+if(NOT USE_RTTI)
+  add_definitions(-DDMLC_ENABLE_RTTI=0)
+endif()
 
 if(USE_RPC)
   message(STATUS "Build with RPC support...")
+  file(GLOB RUNTIME_RPC_SRCS src/runtime/rpc/*.cc)
   list(APPEND RUNTIME_SRCS ${RUNTIME_RPC_SRCS})
 endif(USE_RPC)
 
 if(USE_GRAPH_RUNTIME)
   message(STATUS "Build with Graph runtime support...")
+  file(GLOB RUNTIME_GRAPH_SRCS src/runtime/graph/*.cc)
   list(APPEND RUNTIME_SRCS ${RUNTIME_GRAPH_SRCS})
-endif(USE_GRAPH_RUNTIME)
-
-if(USE_LLVM)
-  find_package(LLVM CONFIG REQUIRED)
-  include_directories(${LLVM_INCLUDE_DIRS})
-  add_definitions(${LLVM_DEFINITIONS})
-  set(TVM_LLVM_VERSION ${LLVM_VERSION_MAJOR}${LLVM_VERSION_MINOR})
-  message(STATUS "Build with LLVM " ${LLVM_PACKAGE_VERSION})
-  message(STATUS "Set TVM_LLVM_VERSION=" ${TVM_LLVM_VERSION})
-  add_definitions(-DTVM_LLVM_VERSION=${TVM_LLVM_VERSION})
-  add_definitions(-DDMLC_USE_FOPEN64=0)
-  llvm_map_components_to_libnames(LLVM_LIBS all)
-  list(REMOVE_ITEM LLVM_LIBS LTO)
-  list(APPEND TVM_LINKER_LIBS ${LLVM_LIBS})
-  list(APPEND COMPILER_SRCS ${COMPILER_LLVM_SRCS})
-  if(NOT MSVC)
-    set_property(SOURCE ${COMPILER_LLVM_SRCS} APPEND_STRING PROPERTY COMPILE_FLAGS
-      "-fno-rtti -DDMLC_ENABLE_RTTI=0")
-  endif()
-endif(USE_LLVM)
 
-if(NOT USE_RTTI)
-  add_definitions(-DDMLC_ENABLE_RTTI=0)
-endif()
+  if(USE_GRAPH_RUNTIME_DEBUG)
+    set_source_files_properties(${RUNTIME_GRAPH_SRCS}
+      PROPERTIES COMPILE_DEFINITIONS "TVM_GRAPH_RUNTIME_DEBUG")
+  endif(USE_GRAPH_RUNTIME_DEBUG)
+endif(USE_GRAPH_RUNTIME)
 
 if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/dmlc-core/CMakeLists.txt)
   include_directories(${CMAKE_CURRENT_SOURCE_DIR}/dmlc-core/include)
@@ -259,18 +156,55 @@ elseif(DMLC_CORE_PATH)
   include_directories(${DMLC_CORE_PATH}/include)
 endif()
 
-list(APPEND RUNTIME_SRCS ${GROUP_Include})
+# Module rules
+include(cmake/modules/CUDA.cmake)
+include(cmake/modules/OpenCL.cmake)
+include(cmake/modules/OpenGL.cmake)
+include(cmake/modules/Vulkan.cmake)
+include(cmake/modules/Metal.cmake)
+include(cmake/modules/ROCM.cmake)
+include(cmake/modules/LLVM.cmake)
+include(cmake/modules/contrib/BLAS.cmake)
+include(cmake/modules/contrib/Random.cmake)
+include(cmake/modules/contrib/Sort.cmake)
+include(cmake/modules/contrib/NNPack.cmake)
+
+# Target rrules
 add_library(tvm SHARED ${COMPILER_SRCS} ${RUNTIME_SRCS})
 add_library(tvm_topi SHARED ${TOPI_SRCS})
 add_library(tvm_runtime SHARED ${RUNTIME_SRCS})
 add_library(nnvm_compiler SHARED ${NNVM_COMPILER_SRCS})
+
 target_link_libraries(tvm ${TVM_LINKER_LIBS} ${TVM_RUNTIME_LINKER_LIBS})
 target_link_libraries(tvm_topi tvm ${TVM_LINKER_LIBS} ${TVM_RUNTIME_LINKER_LIBS})
 target_link_libraries(tvm_runtime ${TVM_RUNTIME_LINKER_LIBS})
 target_link_libraries(nnvm_compiler tvm)
 
-install(TARGETS tvm_runtime DESTINATION lib${LIB_SUFFIX})
+# Tests
+set(TEST_EXECS "")
+file(GLOB TEST_SRCS tests/cpp/*.cc)
+find_library(GTEST_LIB gtest)
+
+if(GTEST_LIB)
+  foreach(__srcpath ${TEST_SRCS})
+    get_filename_component(__srcname ${__srcpath} NAME)
+    string(REPLACE ".cc" "" __execname ${__srcname})
+    add_executable(${__execname} ${__srcpath})
+    list(APPEND TEST_EXECS ${__execname})
+    target_link_libraries(${__execname}
+      tvm ${GTEST_LIB} ${TVM_LINKER_LIBS} ${TVM_RUNTIME_LINKER_LIBS} pthread)
+    set_target_properties(${__execname} PROPERTIES EXCLUDE_FROM_ALL 1)
+    set_target_properties(${__execname} PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD 1)
+  endforeach()
+  add_custom_target(cpptest DEPENDS ${TEST_EXECS})
+endif()
 
+# Custom targets
+add_custom_target(runtime DEPENDS tvm_runtime)
+
+
+# Installation rulse
+install(TARGETS tvm_runtime DESTINATION lib${LIB_SUFFIX})
 if(WIN32)
   install(TARGETS nnvm_compiler RUNTIME DESTINATION bin)
   install(TARGETS nnvm_compiler ARCHIVE DESTINATION lib)
@@ -313,6 +247,7 @@ else(INSTALL_DEV)
     )
 endif(INSTALL_DEV)
 
+# More target definitions
 if(MSVC)
   target_compile_definitions(tvm PRIVATE -DHalide_EXPORTS)
   target_compile_definitions(tvm_runtime PRIVATE -DHalide_EXPORTS)
diff --git a/Jenkinsfile b/Jenkinsfile
index 3378a02ca2d3..5822058a5c6a 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -4,11 +4,11 @@
 // See documents at https://jenkins.io/doc/book/pipeline/jenkinsfile/
 
 // tvm libraries
-tvm_runtime = "lib/libtvm_runtime.so, config.mk"
-tvm_lib = "lib/libtvm.so, " + tvm_runtime
+tvm_runtime = "build/libtvm_runtime.so, build/config.cmake"
+tvm_lib = "build/libtvm.so, " + tvm_runtime
 // LLVM upstream lib
-tvm_multilib = "lib/libtvm_llvm40.so, lib/libtvm_llvm50.so, lib/libtvm_llvm60.so, " +
-             "lib/libtvm_topi.so, nnvm/lib/libnnvm_compiler.so, " + tvm_runtime
+tvm_multilib = "build/libtvm.so, " +
+             "build/libtvm_topi.so, build/libnnvm_compiler.so, " + tvm_runtime
 
 // command to start a docker container
 docker_run = 'tests/ci_build/ci_build.sh'
@@ -48,14 +48,14 @@ stage("Sanity Check") {
 // Run make. First try to do an incremental make from a previous workspace in hope to
 // accelerate the compilation. If something wrong, clean the workspace and then
 // build from scratch.
-def make(docker_type, make_flag) {
+def make(docker_type, path, make_flag) {
   timeout(time: max_time, unit: 'MINUTES') {
     try {
-      sh "${docker_run} ${docker_type} ./tests/scripts/task_build.sh ${make_flag}"
+      sh "${docker_run} ${docker_type} ./tests/scripts/task_build.sh ${path} ${make_flag}"
     } catch (exc) {
       echo 'Incremental compilation failed. Fall back to build from scratch'
-      sh "${docker_run} ${docker_type} ./tests/scripts/task_clean.sh"
-      sh "${docker_run} ${docker_type} ./tests/scripts/task_build.sh ${make_flag}"
+      sh "${docker_run} ${docker_type} ./tests/scripts/task_clean.sh ${path}"
+      sh "${docker_run} ${docker_type} ./tests/scripts/task_build.sh ${path} ${make_flag}"
     }
   }
 }
@@ -85,33 +85,30 @@ stage('Build') {
       ws('workspace/tvm/build-gpu') {
         init_git()
         sh """
-           cp make/config.mk .
-           echo USE_CUDNN=1 >> config.mk
-           echo USE_CUDA=1 >> config.mk
-           echo USE_OPENGL=1 >> config.mk
-           echo LLVM_CONFIG=llvm-config-4.0 >> config.mk
-           echo USE_RPC=1 >> config.mk
-           echo USE_SORT=1 >> config.mk
-           echo USE_GRAPH_RUNTIME=1 >> config.mk
-           echo USE_BLAS=openblas >> config.mk
-           rm -f lib/libtvm_runtime.so lib/libtvm.so
+           mkdir -p build
+           cd build
+           cp ../cmake/config.cmake .
+           echo set\\(USE_CUDNN ON\\) >> config.cmake
+           echo set\\(USE_CUDA ON\\) >> config.cmake
+           echo set\\(USE_OPENGL ON\\) >> config.cmake
+           echo set\\(USE_LLVM llvm-config-6.0\\) >> config.cmake
+           echo set\\(USE_RPC ON\\) >> config.cmake
+           echo set\\(USE_SORT ON\\) >> config.cmake
+           echo set\\(USE_GRAPH_RUNTIME ON\\) >> config.cmake
+           echo set\\(USE_BLAS openblas\\) >> config.cmake
            """
-        make('gpu', '-j2')
-        sh "mv lib/libtvm.so lib/libtvm_llvm40.so"
-        sh "echo LLVM_CONFIG=llvm-config-5.0 >> config.mk"
-        make('gpu', '-j2')
-        sh "mv lib/libtvm.so lib/libtvm_llvm50.so"
-        sh "echo LLVM_CONFIG=llvm-config-6.0 >> config.mk"
-        make('gpu', '-j2')
-        sh "mv lib/libtvm.so lib/libtvm_llvm60.so"
+        make('gpu', 'build', '-j2')
         pack_lib('gpu', tvm_multilib)
+        // compiler test
         sh """
-           echo USE_OPENCL=1 >> config.mk
-           echo USE_ROCM=1 >> config.mk
-           echo ROCM_PATH=/opt/rocm >> config.mk
-           echo USE_VULKAN=1 >> config.mk
+           mkdir -p build2
+           cd build2
+           cp ../cmake/config.cmake .
+           echo set\\(USE_OPENCL ON\\) >> config.cmake
+           echo set\\(USE_ROCM ON\\) >> config.cmake
+           echo set\\(USE_VULKAN ON\\) >> config.cmake
            """
-        make('gpu', '-j2')
+        make('gpu', 'build2', '-j2')
       }
     }
   },
@@ -120,17 +117,17 @@ stage('Build') {
       ws('workspace/tvm/build-cpu') {
         init_git()
         sh """
-           cp make/config.mk .
-           echo USE_CUDA=0 >> config.mk
-           echo USE_OPENCL=0 >> config.mk
-           echo USE_RPC=0 >> config.mk
-           echo USE_SORT=1 >> config.mk
-           echo USE_OPENGL=1 >> config.mk
-           echo LLVM_CONFIG=llvm-config-4.0 >> config.mk
-           echo USE_SORT=1 >> config.mk
+           mkdir -p build
+           cd build
+           cp ../cmake/config.cmake .
+           echo set\\(USE_SORT ON\\) >> config.cmake
+           echo set\\(USE_LLVM llvm-config-4.0\\) >> config.cmake
            """
-        make('cpu', '-j2')
+        make('cpu', 'build', '-j2')
         pack_lib('cpu', tvm_lib)
+        timeout(time: max_time, unit: 'MINUTES') {
+          sh "${docker_run} cpu ./tests/scripts/task_cpp_unittest.sh"
+        }
       }
     }
   },
@@ -139,21 +136,14 @@ stage('Build') {
       ws('workspace/tvm/build-i386') {
         init_git()
         sh """
-           cp make/config.mk .
-           echo USE_CUDA=0 >> config.mk
-           echo USE_OPENCL=0 >> config.mk
-           echo LLVM_CONFIG=llvm-config-4.0 >> config.mk
-           echo USE_RPC=1 >> config.mk
-           echo USE_SORT=1 >> config.mk
+           mkdir -p build
+           cd build
+           cp ../cmake/config.cmake .
+           echo set\\(USE_SORT ON\\) >> config.cmake
+           echo set\\(USE_RPC ON\\) >> config.cmake
+           echo set\\(USE_LLVM llvm-config-5.0\\) >> config.cmake
            """
-        make('i386', '-j2')
-        sh "mv lib/libtvm.so lib/libtvm_llvm40.so"
-        sh "echo LLVM_CONFIG=llvm-config-5.0 >> config.mk"
-        make('i386', '-j2')
-        sh "mv lib/libtvm.so lib/libtvm_llvm50.so"
-        sh "echo LLVM_CONFIG=llvm-config-6.0 >> config.mk"
-        make('i386', '-j2')
-        sh "mv lib/libtvm.so lib/libtvm_llvm60.so"
+        make('i386', 'build', '-j2')
         pack_lib('i386', tvm_multilib)
       }
     }
@@ -166,12 +156,6 @@ stage('Unit Test') {
       ws('workspace/tvm/ut-python-gpu') {
         init_git()
         unpack_lib('gpu', tvm_multilib)
-        sh "cp lib/libtvm_llvm40.so lib/libtvm.so"
-        timeout(time: max_time, unit: 'MINUTES') {
-          sh "${docker_run} gpu ./tests/scripts/task_python_unittest.sh"
-        }
-        // Test on the lastest mainline.
-        sh "cp lib/libtvm_llvm60.so lib/libtvm.so"
         timeout(time: max_time, unit: 'MINUTES') {
           sh "${docker_run} gpu ./tests/scripts/task_python_unittest.sh"
         }
@@ -183,27 +167,10 @@ stage('Unit Test') {
       ws('workspace/tvm/ut-python-i386') {
         init_git()
         unpack_lib('i386', tvm_multilib)
-        sh "cp lib/libtvm_llvm40.so lib/libtvm.so"
         timeout(time: max_time, unit: 'MINUTES') {
           sh "${docker_run} i386 ./tests/scripts/task_python_unittest.sh"
           sh "${docker_run} i386 ./tests/scripts/task_python_integration.sh"
         }
-        // Test on llvm 5.0
-        sh "cp lib/libtvm_llvm50.so lib/libtvm.so"
-        timeout(time: max_time, unit: 'MINUTES') {
-          sh "${docker_run} i386 ./tests/scripts/task_python_integration.sh"
-        }
-      }
-    }
-  },
-  'cpp': {
-    node('CPU' && 'linux') {
-      ws('workspace/tvm/ut-cpp') {
-        init_git()
-        unpack_lib('cpu', tvm_lib)
-        timeout(time: max_time, unit: 'MINUTES') {
-          sh "${docker_run} cpu ./tests/scripts/task_cpp_unittest.sh"
-        }
       }
     }
   },
@@ -212,7 +179,6 @@ stage('Unit Test') {
       ws('workspace/tvm/ut-java') {
         init_git()
         unpack_lib('gpu', tvm_multilib)
-        sh "cp lib/libtvm_llvm40.so lib/libtvm.so"
         timeout(time: max_time, unit: 'MINUTES') {
           sh "${docker_run} gpu ./tests/scripts/task_java_unittest.sh"
         }
@@ -227,7 +193,6 @@ stage('Integration Test') {
       ws('workspace/tvm/it-python-gpu') {
         init_git()
         unpack_lib('gpu', tvm_multilib)
-        sh "cp lib/libtvm_llvm40.so lib/libtvm.so"
         timeout(time: max_time, unit: 'MINUTES') {
           sh "${docker_run} gpu ./tests/scripts/task_python_integration.sh"
           sh "${docker_run} gpu ./tests/scripts/task_python_topi.sh"
@@ -242,7 +207,6 @@ stage('Integration Test') {
       ws('workspace/tvm/docs-python-gpu') {
         init_git()
         unpack_lib('gpu', tvm_multilib)
-        sh "cp lib/libtvm_llvm40.so lib/libtvm.so"
         timeout(time: max_time, unit: 'MINUTES') {
           sh "${docker_run} gpu ./tests/scripts/task_python_docs.sh"
         }
diff --git a/Makefile b/Makefile
index 4529ecc05ee0..3cab59d5d6b7 100644
--- a/Makefile
+++ b/Makefile
@@ -1,17 +1,7 @@
 ROOTDIR = $(CURDIR)
 
-ifndef config
-ifneq ("$(wildcard ./config.mk)","")
-	config ?= config.mk
-else
-	config ?= make/config.mk
-endif
-endif
-
-include $(config)
-
-.PHONY: clean install installdev all test doc pylint cpplint lint\
-	 verilog cython cython2 cython3 web topi nnvm runtime
+.PHONY: clean all test doc pylint cpplint lint\
+	 cython cython2 cython3 web runtime
 
 ifndef DMLC_CORE_PATH
   DMLC_CORE_PATH = $(ROOTDIR)/dmlc-core
@@ -21,316 +11,42 @@ ifndef DLPACK_PATH
   DLPACK_PATH = $(ROOTDIR)/dlpack
 endif
 
-UNAME_S := $(shell uname -s)
-
-# The flags
-LLVM_CFLAGS= -fno-rtti -DDMLC_ENABLE_RTTI=0 -DDMLC_USE_FOPEN64=0
-LDFLAGS = -pthread -lm -ldl -Wl,-Bsymbolic-functions
 INCLUDE_FLAGS = -Iinclude -I$(DLPACK_PATH)/include -I$(DMLC_CORE_PATH)/include -IHalideIR/src -Itopi/include
-CFLAGS = -std=c++11 -Wall -O2 $(INCLUDE_FLAGS) -fPIC
+PKG_CFLAGS = -std=c++11 -Wall -O2 $(INCLUDE_FLAGS) -fPIC
 PKG_LDFLAGS =
-FRAMEWORKS =
-OBJCFLAGS = -fno-objc-arc
-EMCC_FLAGS= -std=c++11 -DDMLC_LOG_STACK_TRACE=0\
-	-Oz -s RESERVED_FUNCTION_POINTERS=2 -s MAIN_MODULE=1 -s NO_EXIT_RUNTIME=1\
-	-s TOTAL_MEMORY=1073741824\
-	-s EXTRA_EXPORTED_RUNTIME_METHODS="['cwrap','getValue','setValue','addFunction']"\
-	-s USE_GLFW=3 -s USE_WEBGL2=1 -lglfw\
-	$(INCLUDE_FLAGS)
-
-# llvm configuration
-ifdef LLVM_CONFIG
-	LLVM_VERSION=$(shell $(LLVM_CONFIG) --version| cut -b 1,3)
-	LLVM_INCLUDE=$(filter -I%, $(shell $(LLVM_CONFIG) --cxxflags))
-	LDFLAGS += $(shell $(LLVM_CONFIG) --ldflags --libs --system-libs)
-	LLVM_CFLAGS += $(LLVM_INCLUDE) -DTVM_LLVM_VERSION=$(LLVM_VERSION)
-else
-	LLVM_VERSION=00
-endif
-
-# The source code dependencies
-LIB_HALIDEIR = HalideIR/lib/libHalideIR.a
-
-CC_SRC = $(filter-out src/contrib/%.cc src/runtime/%.cc src/codgen/llvm/%.cc,\
-             $(wildcard src/*/*.cc src/*/*/*.cc))
-LLVM_SRC = $(wildcard src/codegen/llvm/*.cc src/codegen/llvm/*/*.cc)
-METAL_SRC = $(wildcard src/runtime/metal/*.mm)
-CUDA_SRC = $(wildcard src/runtime/cuda/*.cc)
-ROCM_SRC = $(wildcard src/runtime/rocm/*.cc)
-OPENCL_SRC = $(wildcard src/runtime/opencl/*.cc)
-OPENGL_SRC = $(wildcard src/runtime/opengl/*.cc)
-VULKAN_SRC = $(wildcard src/runtime/vulkan/*.cc)
-SGX_SRC = $(wildcard src/runtime/sgx/untrusted/*.cc)
-RPC_SRC = $(wildcard src/runtime/rpc/*.cc)
-GRAPH_SRC = $(wildcard src/runtime/graph/*.cc)
-RUNTIME_SRC = $(wildcard src/runtime/*.cc)
-
-# TOPI
-TOPI_SRC = $(wildcard topi/src/*.cc)
-
-# Objectives
-LLVM_BUILD = build/llvm${LLVM_VERSION}
-LLVM_OBJ = $(patsubst src/%.cc, ${LLVM_BUILD}/%.o, $(LLVM_SRC))
-METAL_OBJ = $(patsubst src/%.mm, build/%.o, $(METAL_SRC))
-CUDA_OBJ = $(patsubst src/%.cc, build/%.o, $(CUDA_SRC))
-ROCM_OBJ = $(patsubst src/%.cc, build/%.o, $(ROCM_SRC))
-OPENCL_OBJ = $(patsubst src/%.cc, build/%.o, $(OPENCL_SRC))
-OPENGL_OBJ = $(patsubst src/%.cc, build/%.o, $(OPENGL_SRC))
-VULKAN_OBJ = $(patsubst src/%.cc, build/%.o, $(VULKAN_SRC))
-SGX_OBJ = $(patsubst src/%.cc, build/%.o, $(SGX_SRC)) build/runtime/sgx/untrusted/tvm_u.o
-RPC_OBJ = $(patsubst src/%.cc, build/%.o, $(RPC_SRC))
-GRAPH_OBJ = $(patsubst src/%.cc, build/%.o, $(GRAPH_SRC))
-CC_OBJ = $(patsubst src/%.cc, build/%.o, $(CC_SRC)) $(LLVM_OBJ)
-RUNTIME_OBJ = $(patsubst src/%.cc, build/%.o, $(RUNTIME_SRC))
-TOPI_OBJ = $(patsubst topi/src/%.cc, build/topi/%.o, $(TOPI_SRC))
-CONTRIB_OBJ =
-
-# Deps
-ALL_DEP = $(CC_OBJ) $(CONTRIB_OBJ) $(LIB_HALIDEIR)
-RUNTIME_DEP = $(RUNTIME_OBJ)
-TOPI_DEP = $(TOPI_OBJ)
-
-ifeq ($(UNAME_S), Darwin)
-	PKG_LDFLAGS += -undefined dynamic_lookup
-endif
-
-# Dependency specific rules
-ifdef CUDA_PATH
-	NVCC=$(CUDA_PATH)/bin/nvcc
-	CFLAGS += -I$(CUDA_PATH)/include
-	ifeq ($(UNAME_S),Darwin)
-		LDFLAGS += -L$(CUDA_PATH)/lib
-	else
-		LDFLAGS += -L$(CUDA_PATH)/lib64
-	endif
-endif
-
-ifeq ($(USE_CUDA), 1)
-	CFLAGS += -DTVM_CUDA_RUNTIME=1
-	LDFLAGS += -lcuda -lcudart -lnvrtc
-	RUNTIME_DEP += $(CUDA_OBJ)
-else
-	CFLAGS += -DTVM_CUDA_RUNTIME=0
-endif
-
-ifdef ROCM_PATH
-	CFLAGS += -I$(ROCM_PATH)/include
-	LDFLAGS += -L$(ROCM_PATH)/lib
-endif
-
-ifeq ($(USE_ROCM), 1)
-	CFLAGS += -DTVM_ROCM_RUNTIME=1 -D__HIP_PLATFORM_HCC__=1
-	LDFLAGS += -lhip_hcc
-	RUNTIME_DEP += $(ROCM_OBJ)
-else
-	CFLAGS += -DTVM_ROCM_RUNTIME=0
-endif
 
-ifeq ($(USE_OPENCL), 1)
-	CFLAGS += -DTVM_OPENCL_RUNTIME=1
-	ifeq ($(UNAME_S), Darwin)
-		FRAMEWORKS += -framework OpenCL
-	else
-		LDFLAGS += -lOpenCL
-	endif
-	RUNTIME_DEP += $(OPENCL_OBJ)
-ifdef OPENCL_PATH
-        CFLAGS += -I$(OPENCL_PATH)/include
-        LDFLAGS += -L$(OPENCL_PATH)/lib
-endif
-else
-	CFLAGS += -DTVM_OPENCL_RUNTIME=0
-endif
-
-ifdef VULKAN_SDK
-	CFLAGS += -I$(VULKAN_SDK)/include
-	LDFLAGS += -L$(VULKAN_SDK)/lib
-	LDFLAGS += -L$(VULKAN_SDK)/lib/spirv-tools
-endif
-
-ifeq ($(USE_VULKAN), 1)
-	CFLAGS += -DTVM_VULKAN_RUNTIME=1
-	LDFLAGS += -lvulkan -lSPIRV-Tools
-	RUNTIME_DEP += $(VULKAN_OBJ)
-else
-	CFLAGS += -DTVM_VULKAN_RUNTIME=0
-endif
-
-ifeq ($(USE_OPENGL), 1)
-	CFLAGS += -DTVM_OPENGL_RUNTIME=1
-	EMCC_FLAGS += -DTVM_OPENGL_RUNTIME=1
-	ifeq ($(UNAME_S), Darwin)
-		FRAMEWORKS += -framework OpenGL
-	else
-		LDFLAGS += -lGL -lglfw
-	endif
-	RUNTIME_DEP += $(OPENGL_OBJ)
-else
-	CFLAGS += -DTVM_OPENGL_RUNTIME=0
-endif
 
-ifeq ($(USE_METAL), 1)
-	CFLAGS += -DTVM_METAL_RUNTIME=1
-	LDFLAGS += -lobjc
-	RUNTIME_DEP += $(METAL_OBJ)
-	FRAMEWORKS += -framework Metal -framework Foundation
-else
-	CFLAGS += -DTVM_METAL_RUNTIME=0
-endif
+all:
+	@mkdir -p build && cd build && cmake .. && $(MAKE)
 
-ifeq ($(USE_SGX), 1)
-	EDGER8R = $(SGX_SDK)/bin/x64/sgx_edger8r
-	ifneq ($(SGX_MODE), HW)
-		sgx_sim := _sim
-	endif
-	urts_library_name := sgx_urts$(sgx_sim)
-	CFLAGS += -DTVM_SGX_RUNTIME=1
-	SGX_CFLAGS = -include "build/runtime/sgx/untrusted/tvm_u.h" -I$(SGX_SDK)/include
-	LDFLAGS += -L$(SGX_SDK)/lib64 -l$(urts_library_name)
-	RUNTIME_DEP += $(SGX_OBJ)
-else
-	CFLAGS += -DTVM_SGX_RUNTIME=0
-endif
+runtime:
 
-ifeq ($(USE_RPC), 1)
-	RUNTIME_DEP += $(RPC_OBJ)
-endif
-
-ifeq ($(USE_GRAPH_RUNTIME), 1)
-	RUNTIME_DEP += $(GRAPH_OBJ)
-endif
+	@mkdir -p build && cd build && cmake .. && $(MAKE) runtime
 
-ifeq ($(USE_GRAPH_RUNTIME_DEBUG), 1)
-	CFLAGS += -DTVM_GRAPH_RUNTIME_DEBUG
-endif
+cpptest:
+	@mkdir -p build && cd build && cmake .. && $(MAKE) cpptest
 
-include make/contrib/cblas.mk
-include make/contrib/sort.mk
-include make/contrib/random.mk
-include make/contrib/nnpack.mk
-include make/contrib/cudnn.mk
-include make/contrib/miopen.mk
-include make/contrib/mps.mk
-include make/contrib/cublas.mk
-include make/contrib/rocblas.mk
-
-ifdef ADD_CFLAGS
-	CFLAGS += $(ADD_CFLAGS)
-endif
-
-ifdef ADD_LDFLAGS
-	LDFLAGS += $(ADD_LDFLAGS)
-endif
-
-ifeq ($(OS),Windows_NT)
-	JVM_PKG_PROFILE := windows
-	SHARED_LIBRARY_SUFFIX := dll
-else
-	UNAME_S := $(shell uname -s)
-	ifeq ($(UNAME_S), Darwin)
-		JVM_PKG_PROFILE := osx-x86_64
-		SHARED_LIBRARY_SUFFIX := dylib
-	else
-		JVM_PKG_PROFILE := linux-x86_64
-		SHARED_LIBRARY_SUFFIX := so
-	endif
-endif
-
-JVM_TEST_ARGS := $(if $(JVM_TEST_ARGS),$(JVM_TEST_ARGS),-DskipTests -Dcheckstyle.skip=true)
-
-ifeq ($(USE_CUDA), 1)
-	JVM_PKG_PROFILE := $(JVM_PKG_PROFILE)-gpu
-else ifeq ($(USE_OPENCL), 1)
-	JVM_PKG_PROFILE := $(JVM_PKG_PROFILE)-gpu
-else ifeq ($(USE_METAL), 1)
-	JVM_PKG_PROFILE := $(JVM_PKG_PROFILE)-gpu
-else
-	JVM_PKG_PROFILE := $(JVM_PKG_PROFILE)-cpu
-endif
-
-BUILD_TARGETS ?= lib/libtvm.$(SHARED_LIBRARY_SUFFIX) \
-				 lib/libtvm_runtime.$(SHARED_LIBRARY_SUFFIX) \
-				 lib/libtvm_topi.$(SHARED_LIBRARY_SUFFIX)
-
-all: ${BUILD_TARGETS}
-runtime: lib/libtvm_runtime.$(SHARED_LIBRARY_SUFFIX)
-web: lib/libtvm_web_runtime.js lib/libtvm_web_runtime.bc
-topi: lib/libtvm_topi.$(SHARED_LIBRARY_SUFFIX)
-nnvm:
-	$(MAKE) -C nnvm
-
-
-include tests/cpp/unittest.mk
-
-test: $(TEST)
-
-include verilog/verilog.mk
-verilog: $(VER_LIBS)
-
-# Special rules for LLVM related modules.
-${LLVM_BUILD}/codegen/llvm/%.o: src/codegen/llvm/%.cc
-	@mkdir -p $(@D)
-	$(CXX) $(CFLAGS) $(LLVM_CFLAGS) -MM -MT ${LLVM_BUILD}/codegen/llvm/$*.o $< >${LLVM_BUILD}/codegen/llvm/$*.d
-	$(CXX) -c $(CFLAGS) $(LLVM_CFLAGS) -c $< -o $@
-
-build/runtime/metal/%.o: src/runtime/metal/%.mm
-	@mkdir -p $(@D)
-	$(CXX) $(OBJCFLAGS) $(CFLAGS) -MM -MT build/runtime/metal/$*.o $< >build/runtime/metal/$*.d
-	$(CXX) $(OBJCFLAGS) -c $(CFLAGS) -c $< -o $@
-
-build/runtime/sgx/untrusted/tvm_u.h: src/runtime/sgx/tvm.edl
-	@mkdir -p $(@D)
-	$(EDGER8R) $< --untrusted --untrusted-dir $(@D) --search-path $(SGX_SDK)/include
-	mv $@ $@.in
-	awk 'NR==4{print "#include <tvm/runtime/c_runtime_api.h>"}1' $@.in > $@
-
-build/runtime/sgx/untrusted/tvm_u.c: build/runtime/sgx/untrusted/tvm_u.h
-
-build/runtime/sgx/untrusted/tvm_u.o: build/runtime/sgx/untrusted/tvm_u.c
-	$(CC) $(CFLAGS) $(SGX_CFLAGS) -c $< -o $@
-
-build/runtime/sgx/untrusted/%.o: src/runtime/sgx/untrusted/%.cc build/runtime/sgx/untrusted/tvm_u.h
-	$(CXX) $(CFLAGS) $(SGX_CFLAGS) -MM -MT build/$*.o $< >build/$*.d
-	$(CXX) -c $(CFLAGS) $(SGX_CFLAGS) -c $< -o $@
-
-build/%.o: src/%.cc
-	@mkdir -p $(@D)
-	$(CXX) $(CFLAGS) -MM -MT build/$*.o $< >build/$*.d
-	$(CXX) -c $(CFLAGS) -c $< -o $@
-
-build/topi/%.o: topi/src/%.cc
-	@mkdir -p $(@D)
-	$(CXX) $(CFLAGS) -MM -MT build/topi/$*.o $< >build/topi/$*.d
-	$(CXX) -c $(CFLAGS) -c $< -o $@
-
-lib/libtvm.${SHARED_LIBRARY_SUFFIX}: $(ALL_DEP) $(RUNTIME_DEP)
-	@mkdir -p $(@D)
-	$(CXX) $(CFLAGS) $(FRAMEWORKS) -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
-
-lib/libtvm_topi.${SHARED_LIBRARY_SUFFIX}: $(TOPI_DEP)
-	@mkdir -p $(@D)
-	$(CXX) $(CFLAGS) $(FRAMEWORKS) -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS) $(PKG_LDFLAGS)
-
-lib/libtvm_runtime.${SHARED_LIBRARY_SUFFIX}: $(RUNTIME_DEP)
-	@mkdir -p $(@D)
-	$(CXX) $(CFLAGS) $(FRAMEWORKS) -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
+# EMCC; Web related scripts
+EMCC_FLAGS= -std=c++11 -DDMLC_LOG_STACK_TRACE=0\
+	-Oz -s RESERVED_FUNCTION_POINTERS=2 -s MAIN_MODULE=1 -s NO_EXIT_RUNTIME=1\
+	-s TOTAL_MEMORY=1073741824\
+	-s EXTRA_EXPORTED_RUNTIME_METHODS="['cwrap','getValue','setValue','addFunction']"\
+	-s USE_GLFW=3 -s USE_WEBGL2=1 -lglfw\
+	$(INCLUDE_FLAGS)
 
+web: build/libtvm_web_runtime.js build/libtvm_web_runtime.bc
 
-lib/libtvm_web_runtime.bc: web/web_runtime.cc
+build/libtvm_web_runtime.bc: web/web_runtime.cc
 	@mkdir -p build/web
 	@mkdir -p $(@D)
-	emcc $(EMCC_FLAGS) -MM -MT lib/libtvm_web_runtime.bc $< >build/web/web_runtime.d
+	emcc $(EMCC_FLAGS) -MM -MT build/libtvm_web_runtime.bc $< >build/web/web_runtime.d
 	emcc $(EMCC_FLAGS) -o $@ web/web_runtime.cc
 
-lib/libtvm_web_runtime.js: lib/libtvm_web_runtime.bc
+build/libtvm_web_runtime.js: build/libtvm_web_runtime.bc
 	@mkdir -p $(@D)
-	emcc $(EMCC_FLAGS) -o $@ lib/libtvm_web_runtime.bc
-
-$(LIB_HALIDEIR): LIBHALIDEIR
-
-LIBHALIDEIR:
-	+ cd HalideIR; make lib/libHalideIR.a DMLC_CORE_PATH=../dmlc-core; cd $(ROOTDIR)
+	emcc $(EMCC_FLAGS) -o $@ build/libtvm_web_runtime.bc
 
+# Lint scripts
 cpplint:
 	python dmlc-core/scripts/lint.py topi cpp topi/include;
 	python dmlc-core/scripts/lint.py nnvm cpp nnvm/include nnvm/src;
@@ -350,18 +66,6 @@ lint: cpplint pylint jnilint
 doc:
 	doxygen docs/Doxyfile
 
-install: lib/libtvm_runtime.$(SHARED_LIBRARY_SUFFIX)
-	mkdir -p $(DESTDIR)$(PREFIX)/include/tvm/runtime
-	cp -R include/tvm/runtime/. $(DESTDIR)$(PREFIX)/include/tvm/runtime
-	cp lib/libtvm_runtime.$(SHARED_LIBRARY_SUFFIX) $(DESTDIR)$(PREFIX)/lib
-
-installdev: lib/libtvm.$(SHARED_LIBRARY_SUFFIX) lib/libtvm_runtime.$(SHARED_LIBRARY_SUFFIX) lib/libtvm.a
-	mkdir -p $(DESTDIR)$(PREFIX)/include
-	cp -R include/tvm $(DESTDIR)$(PREFIX)/include
-	cp lib/libtvm.$(SHARED_LIBRARY_SUFFIX) $(DESTDIR)$(PREFIX)/lib
-	cp lib/libtvm_runtime.$(SHARED_LIBRARY_SUFFIX) $(DESTDIR)$(PREFIX)/lib
-	cp lib/libtvm.a $(DESTDIR)$(PREFIX)/lib
-
 # Cython build
 cython:
 	cd python; python setup.py build_ext --inplace
@@ -375,23 +79,35 @@ cython3:
 cyclean:
 	rm -rf python/tvm/*/*/*.so python/tvm/*/*/*.dylib python/tvm/*/*/*.cpp
 
+# JVM build rules
+ifeq ($(OS),Windows_NT)
+	JVM_PKG_PROFILE := windows
+	SHARED_LIBRARY_SUFFIX := dll
+else
+	UNAME_S := $(shell uname -s)
+	ifeq ($(UNAME_S), Darwin)
+		JVM_PKG_PROFILE := osx-x86_64
+		SHARED_LIBRARY_SUFFIX := dylib
+	else
+		JVM_PKG_PROFILE := linux-x86_64
+		SHARED_LIBRARY_SUFFIX := so
+	endif
+endif
+
+JVM_TEST_ARGS := $(if $(JVM_TEST_ARGS),$(JVM_TEST_ARGS),-DskipTests -Dcheckstyle.skip=true)
+JVM_PKG_PROFILE := $(JVM_PKG_PROFILE)-gpu
+
 jvmpkg:
 	(cd $(ROOTDIR)/jvm; \
 		mvn clean package -P$(JVM_PKG_PROFILE) -Dcxx="$(CXX)" \
-			-Dcflags="$(CFLAGS)" -Dldflags="$(LDFLAGS)" \
-			-Dcurrent_libdir="$(ROOTDIR)/lib" $(JVM_TEST_ARGS))
+			-Dcflags="$(PKG_CFLAGS)" -Dldflags="$(PKG_LDFLAGS)" \
+			-Dcurrent_libdir="$(ROOTDIR)/build" $(JVM_TEST_ARGS))
 jvminstall:
 	(cd $(ROOTDIR)/jvm; \
 		mvn install -P$(JVM_PKG_PROFILE) -Dcxx="$(CXX)" \
-			-Dcflags="$(CFLAGS)" -Dldflags="$(LDFLAGS)" \
-			-Dcurrent_libdir="$(ROOTDIR)/lib" $(JVM_TEST_ARGS))
+			-Dcflags="$(PKG_CFLAGS)" -Dldflags="$(PKG_LDFLAGS)" \
+			-Dcurrent_libdir="$(ROOTDIR)/build" $(JVM_TEST_ARGS))
 
+# clean rule
 clean:
 	$(RM) -rf build lib bin *~ */*~ */*/*~ */*/*/*~ */*.o */*/*.o */*/*/*.o */*.d */*/*.d */*/*/*.d
-	cd HalideIR; make clean; cd $(ROOTDIR)
-	$(MAKE) -C nnvm clean
-
--include build/*.d
--include build/*/*.d
--include build/*/*/*.d
--include build/*/*/*/*.d
diff --git a/cmake/config.cmake b/cmake/config.cmake
new file mode 100644
index 000000000000..b4896da79ef1
--- /dev/null
+++ b/cmake/config.cmake
@@ -0,0 +1,87 @@
+#--------------------------------------------------------------------
+#  Template custom cmake configuration for compiling
+#
+#  This file is used to override the build options in build.
+#  If you want to change the configuration, please use the following
+#  steps. Assume you are on the root directory. First copy the this
+#  file so that any local changes will be ignored by git
+#
+#  $ mkdir build
+#  $ cp cmake/config.cmake build
+#
+#  Next modify the according entries, and then compile by
+#
+#  $ cd build
+#  $ cmake ..
+#
+#  Then buld in parallel with 8 threads
+#
+#  $ make -j8
+#--------------------------------------------------------------------
+
+#---------------------------------------------
+# Backend runtimes.
+#---------------------------------------------
+# whether enable CUDA during compile
+set(USE_CUDA OFF)
+
+# ROCM
+set(USE_ROCM OFF)
+set(ROCM_PATH "/opt/rocm")
+
+# Whether enable OpenCL runtime
+set(USE_OPENCL OFF)
+
+# Whether enable Metal runtime
+set(USE_METAL OFF)
+
+# Whether enable Vulkan runtime
+set(USE_VULKAN OFF)
+
+# Whether enable RPC runtime
+set(USE_RPC ON)
+
+# Whether enable tiny embedded graph runtime.
+set(USE_GRAPH_RUNTIME ON)
+
+# Whether enable additional graph debug functions
+set(USE_GRAPH_RUNTIME_DEBUG OFF)
+
+# Whether build with LLVM support
+# Requires LLVM version >= 4.0
+#
+# Possible values:
+# - ON: enable llvm with cmake's find llvm
+# - OFF: disbale llvm
+# - /path/to/llvm-config enable specific LLVM when multiple llvm-dev is available.
+set(USE_LLVM OFF)
+
+#---------------------------------------------
+# Contrib libraries
+#---------------------------------------------
+# Whether use BLAS, choices: openblas, atlas, blas, apple
+set(USE_BLAS none)
+
+# Whether use contrib.random in runtime
+set(USE_RANDOM OFF)
+
+# Whether use NNPack
+set(USE_NNPACK OFF)
+
+# Whether use CuDNN
+set(USE_CUDNN OFF)
+
+# Whether use cuBLAS
+set(USE_CUBLAS OFF)
+
+# Whether use MIOpen
+set(USE_MIOPEN OFF)
+
+# Whether use MPS
+set(USE_MPS OFF)
+
+# Whether use rocBlas
+set(USE_ROCBLAS OFF)
+
+# Whether use contrib sort
+set(USE_SORT OFF)
diff --git a/cmake/modules/CUDA.cmake b/cmake/modules/CUDA.cmake
new file mode 100644
index 000000000000..83ffac79f54e
--- /dev/null
+++ b/cmake/modules/CUDA.cmake
@@ -0,0 +1,76 @@
+# CUDA Module
+find_package(CUDA QUIET)
+
+if(CUDA_FOUND)
+  # always set the includedir when cuda is available
+  # avoid global retrigger of cmake
+	include_directories(${CUDA_INCLUDE_DIRS})
+endif(CUDA_FOUND)
+
+if(USE_CUDA)
+  find_package(CUDA REQUIRED)
+  # Find CUDA doesn't find all the libraries we need, add the extra ones
+  find_library(CUDA_CUDA_LIBRARIES cuda
+    PATHS ${CUDA_TOOLKIT_ROOT_DIR}
+    PATH_SUFFIXES lib lib64 targets/x86_64-linux/lib targets/x86_64-linux/lib/stubs)
+  find_library(CUDA_NVRTC_LIBRARIES nvrtc
+    PATHS ${CUDA_TOOLKIT_ROOT_DIR}
+    PATH_SUFFIXES lib lib64 targets/x86_64-linux/lib targets/x86_64-linux/lib/stubs)
+  if(CUDA_CUDA_LIBRARIES)
+    set(CUDA_CUDA_LIBRARY ${CUDA_CUDA_LIBRARIES})
+  endif()
+  message(STATUS "Build with CUDA support")
+  file(GLOB RUNTIME_CUDA_SRCS src/runtime/cuda/*.cc)
+  list(APPEND TVM_RUNTIME_LINKER_LIBS ${CUDA_CUDART_LIBRARY})
+  list(APPEND TVM_RUNTIME_LINKER_LIBS ${CUDA_CUDA_LIBRARY})
+  list(APPEND RUNTIME_SRCS ${RUNTIME_CUDA_SRCS})
+  list(APPEND COMPILER_SRCS src/codegen/opt/build_cuda_on.cc)
+
+  if(MSVC)
+    find_library(CUDA_NVRTC_LIB nvrtc
+      ${CUDA_TOOLKIT_ROOT_DIR}/lib/x64
+      ${CUDA_TOOLKIT_ROOT_DIR}/lib/win32)
+    list(APPEND TVM_LINKER_LIBS ${CUDA_NVRTC_LIB})
+  else(MSVC)
+    find_library(CUDA_NVRTC_LIB nvrtc
+      ${CUDA_TOOLKIT_ROOT_DIR}/lib64
+      ${CUDA_TOOLKIT_ROOT_DIR}/lib)
+    list(APPEND TVM_LINKER_LIBS ${CUDA_NVRTC_LIB})
+  endif(MSVC)
+
+  if(USE_CUDNN)
+    message(STATUS "Build with cuDNN support")
+    file(GLOB CONTRIB_CUDNN_SRCS src/contrib/cudnn/*.cc)
+    list(APPEND RUNTIME_SRCS ${CONTRIB_CUDNN_SRCS})
+    if(MSVC)
+      find_library(CUDA_CUDNN_LIB cudnn
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib/x64
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib/win32)
+      list(APPEND TVM_RUNTIME_LINKER_LIBS ${CUDA_CUDNN_LIB})
+    else(MSVC)
+      find_library(CUDA_CUDNN_LIB cudnn
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib64
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib)
+      list(APPEND TVM_RUNTIME_LINKER_LIBS ${CUDA_CUDNN_LIB})
+    endif(MSVC)
+  endif(USE_CUDNN)
+
+  if(USE_CUBLAS)
+    message(STATUS "Build with cuBLAS support")
+    file(GLOB CONTRIB_CUBLAS_SRCS src/contrib/cublas/*.cc)
+    list(APPEND RUNTIME_SRCS ${CONTRIB_CUBLAS_SRCS})
+    if(MSVC)
+      find_library(CUDA_CUBLAS_LIB cublas
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib/x64
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib/win32)
+      list(APPEND TVM_RUNTIME_LINKER_LIBS ${CUDA_CUBLAS_LIB})
+    else(MSVC)
+      find_library(CUDA_CUBLAS_LIB cublas
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib64
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib)
+      list(APPEND TVM_RUNTIME_LINKER_LIBS ${CUDA_CUBLAS_LIB})
+    endif(MSVC)
+  endif(USE_CUBLAS)
+else(USE_CUDA)
+  list(APPEND COMPILER_SRCS src/codegen/opt/build_cuda_off.cc)
+endif(USE_CUDA)
diff --git a/cmake/modules/LLVM.cmake b/cmake/modules/LLVM.cmake
new file mode 100644
index 000000000000..2ea6b6d9e075
--- /dev/null
+++ b/cmake/modules/LLVM.cmake
@@ -0,0 +1,26 @@
+# LLVM rules
+add_definitions(-DDMLC_USE_FOPEN64=0)
+
+if(NOT USE_LLVM STREQUAL "OFF")
+  if(NOT USE_LLVM STREQUAL "ON")
+    set(LLVM_CONFIG "${USE_LLVM}")
+  else()
+    set(LLVM_CONFIG "")
+  endif()
+  find_llvm()
+  include_directories(${LLVM_INCLUDE_DIRS})
+  add_definitions(${LLVM_DEFINITIONS})
+  message(STATUS "Build with LLVM " ${LLVM_PACKAGE_VERSION})
+  message(STATUS "Set TVM_LLVM_VERSION=" ${TVM_LLVM_VERSION})
+  # Set flags that are only needed for LLVM target
+  add_definitions(-DTVM_LLVM_VERSION=${TVM_LLVM_VERSION})
+  file(GLOB COMPILER_LLVM_SRCS src/codegen/llvm/*.cc)
+  list(APPEND TVM_LINKER_LIBS ${LLVM_LIBS})
+  list(APPEND COMPILER_SRCS ${COMPILER_LLVM_SRCS})
+  if(NOT MSVC)
+    set_source_files_properties(${COMPILER_LLVM_SRCS}
+      PROPERTIES COMPILE_DEFINITIONS "DMLC_ENABLE_RTTI=0")
+    set_source_files_properties(${COMPILER_LLVM_SRCS}
+      PROPERTIES COMPILE_FLAGS "-fno-rtti")
+  endif()
+endif()
diff --git a/cmake/modules/Metal.cmake b/cmake/modules/Metal.cmake
new file mode 100644
index 000000000000..d48155208657
--- /dev/null
+++ b/cmake/modules/Metal.cmake
@@ -0,0 +1,18 @@
+if(USE_METAL)
+  message(STATUS "Build with Metal support")
+  find_library(METAL_LIB Metal)
+  find_library(FOUNDATION_LIB Foundation)
+  file(GLOB RUNTIME_METAL_SRCS src/runtime/metal/*.mm)
+  list(APPEND TVM_RUNTIME_LINKER_LIBS ${METAL_LIB} ${FOUNDATION_LIB})
+  list(APPEND RUNTIME_SRCS ${RUNTIME_METAL_SRCS})
+  list(APPEND COMPILER_SRCS src/codegen/opt/build_metal_on.cc)
+
+  if(USE_MPS)
+    file(GLOB MPS_CONTRIB_SRC src/contrib/mps/*.mm)
+    list(APPEND RUNTIME_SRCS ${MPS_CONTRIB_SRC})
+    find_library(MPS_CONTRIB_LIB MetalPerformanceShaders)
+    list(APPEND TVM_RUNTIME_LINKER_LIBS ${MPS_CONTRIB_LIB})
+  endif()
+else(USE_METAL)
+  list(APPEND COMPILER_SRCS src/codegen/opt/build_metal_off.cc)
+endif(USE_METAL)
diff --git a/cmake/modules/OpenCL.cmake b/cmake/modules/OpenCL.cmake
new file mode 100644
index 000000000000..93e07101b063
--- /dev/null
+++ b/cmake/modules/OpenCL.cmake
@@ -0,0 +1,19 @@
+# OPENCL Module
+find_package(OpenCL QUIET)
+
+if(OpenCL_FOUND)
+  # always set the includedir when cuda is available
+  # avoid global retrigger of cmake
+  include_directories(${OpenCL_INCLUDE_DIRS})
+endif(OpenCL_FOUND)
+
+if(USE_OPENCL)
+  find_package(OpenCL REQUIRED)
+  message(STATUS "Build with OpenCL support")
+  file(GLOB RUNTIME_OPENCL_SRCS src/runtime/opencl/*.cc)
+  list(APPEND TVM_RUNTIME_LINKER_LIBS ${OpenCL_LIBRARIES})
+  list(APPEND RUNTIME_SRCS ${RUNTIME_OPENCL_SRCS})
+  list(APPEND COMPILER_SRCS src/codegen/opt/build_opencl_on.cc)
+else()
+  list(APPEND COMPILER_SRCS src/codegen/opt/build_opencl_off.cc)
+endif(USE_OPENCL)
diff --git a/cmake/modules/OpenGL.cmake b/cmake/modules/OpenGL.cmake
new file mode 100644
index 000000000000..d43b70351152
--- /dev/null
+++ b/cmake/modules/OpenGL.cmake
@@ -0,0 +1,19 @@
+find_package(OpenGL QUIET)
+
+if(OpenGL_FOUND)
+  # always set the includedir when dir is available
+  # avoid global retrigger of cmake
+  include_directories(${OPENGL_INCLUDE_DIRS})
+endif(OpenGL_FOUND)
+
+if(USE_OPENGL)
+  find_package(OpenGL REQUIRED)
+  find_package(glfw3 QUIET REQUIRED)
+  message(STATUS "Build with OpenGL support")
+  file(GLOB RUNTIME_OPENGL_SRCS src/runtime/opengl/*.cc)
+  list(APPEND TVM_RUNTIME_LINKER_LIBS ${OpenGL_LIBRARIES} glfw)
+  list(APPEND RUNTIME_SRCS ${RUNTIME_OPENGL_SRCS})
+  list(APPEND COMPILER_SRCS src/codegen/opt/build_opengl_on.cc)
+else(USE_OPENGL)
+  list(APPEND COMPILER_SRCS src/codegen/opt/build_opengl_off.cc)
+endif(USE_OPENGL)
diff --git a/cmake/modules/ROCM.cmake b/cmake/modules/ROCM.cmake
new file mode 100644
index 000000000000..d3d4e5cc21f0
--- /dev/null
+++ b/cmake/modules/ROCM.cmake
@@ -0,0 +1,34 @@
+# ROCM Module
+if(NOT ROCM_PATH STREQUAL "")
+  include_directories(${ROCM_PATH}/include)
+  set(ROCM_LIB_PATH ${ROCM_PATH}/lib)
+else()
+  set(ROCM_LIB_PATH /lib)
+endif()
+
+if(USE_ROCM)
+  message(STATUS "Build with ROCM support")
+  find_library(ROCM_LIBS hip_hcc ${ROCM_LIB_PATH})
+  file(GLOB RUNTIME_ROCM_SRCS src/runtime/rocm/*.cc)
+  list(APPEND TVM_RUNTIME_LINKER_LIBS ${ROCM_LIBS})
+  list(APPEND RUNTIME_SRCS ${RUNTIME_ROCM_SRCS})
+  add_definitions(-DTVM_ROCM_RUNTIME=1 -D__HIP_PLATFORM_HCC__=1)
+
+  if(USE_MIOPEN)
+    message(STATUS "Build with MIOpen support")
+    file(GLOB MIOPEN_CONTRIB_SRCS src/contrib/miopen/*.cc)
+    list(APPEND RUNTIME_SRCS ${MIOPEN_CONTRIB_SRCS})
+    find_library(MIOPEN_LIBS MIOpen ${ROCM_LIB_PATH})
+    list(APPEND TVM_RUNTIME_LINKER_LIBS ${MIOPEN_LIBS})
+  endif(USE_MIOPEN)
+
+  if(USE_ROCBLAS)
+    message(STATUS "Build with RocBLAS support")
+    file(GLOB ROCBLAS_CONTRIB_SRCS src/contrib/rocblas/*.cc)
+    list(APPEND RUNTIME_SRCS ${ROCBLAS_CONTRIB_SRCS})
+    find_library(ROCBLAS_LIBS rocblas ${ROCM_LIB_PATH})
+    list(APPEND TVM_RUNTIME_LINKER_LIBS ${ROCBLAS_LIBS})
+  endif(USE_ROCBLAS)
+else(USE_ROCM)
+  add_definitions(-DTVM_ROCM_RUNTIME=0)
+endif(USE_ROCM)
diff --git a/cmake/modules/Vulkan.cmake b/cmake/modules/Vulkan.cmake
new file mode 100644
index 000000000000..0dae5fe1926a
--- /dev/null
+++ b/cmake/modules/Vulkan.cmake
@@ -0,0 +1,31 @@
+# Be compatible with older version of CMake
+if(NOT $ENV{VULKAN_SDK} STREQUAL "")
+  set(Vulkan_INCLUDE_DIRS $ENV{VULKAN_SDK}/include)
+  set(Vulkan_FOUND ON)
+else()
+  find_package(Vulkan QUIET)
+endif()
+
+if(Vulkan_FOUND)
+  # always set the includedir when cuda is available
+  # avoid global retrigger of cmake
+  include_directories(${Vulkan_INCLUDE_DIRS})
+endif(Vulkan_FOUND)
+
+if(USE_VULKAN)
+  if(NOT $ENV{VULKAN_SDK} STREQUAL "")
+    find_library(Vulkan_LIBRARY vulkan $ENV{VULKAN_SDK}/lib)
+  else()
+    find_package(Vulkan REQUIRED)
+  endif()
+  message(STATUS "Build with VULKAN support")
+  file(GLOB RUNTIME_VULKAN_SRCS src/runtime/vulkan/*.cc)
+  file(GLOB COMPILER_VULKAN_SRCS src/codegen/spirv/*.cc)
+  list(APPEND TVM_RUNTIME_LINKER_LIBS ${Vulkan_LIBRARY})
+  list(APPEND RUNTIME_SRCS ${RUNTIME_VULKAN_SRCS})
+  list(APPEND COMPILER_SRCS ${COMPILER_VULKAN_SRCS})
+  get_filename_component(VULKAN_LIB_PATH ${Vulkan_LIBRARY} DIRECTORY)
+  find_library(SPIRV_TOOLS_LIB SPIRV-Tools
+               ${VULKAN_LIB_PATH}/spirv-tools)
+  list(APPEND TVM_LINKER_LIBS ${SPIRV_TOOLS_LIB})
+endif(USE_VULKAN)
diff --git a/cmake/modules/contrib/BLAS.cmake b/cmake/modules/contrib/BLAS.cmake
new file mode 100644
index 000000000000..423cb9cd5ab3
--- /dev/null
+++ b/cmake/modules/contrib/BLAS.cmake
@@ -0,0 +1,24 @@
+# Plugin rules for cblas
+file(GLOB CBLAS_CONTRIB_SRC src/contrib/cblas/*.cc)
+
+if(USE_BLAS STREQUAL "openblas")
+  find_library(BLAS_LIBRARY openblas)
+  list(APPEND TVM_RUNTIME_LINKER_LIBS ${BLAS_LIBRARY})
+  list(APPEND RUNTIME_SRCS ${CBLAS_CONTRIB_SRC})
+  message(STATUS "Use BLAS library " ${BLAS_LIBRARY})
+elseif(USE_BLAS STREQUAL "atlas" OR USE_BLAS STREQUAL "blas")
+  find_library(BLAS_LIBRARY cblas)
+  list(APPEND TVM_RUNTIME_LINKER_LIBS ${BLAS_LIBRARY})
+  list(APPEND RUNTIME_SRCS ${CBLAS_CONTRIB_SRC})
+  message(STATUS "Use BLAS library " ${BLAS_LIBRARY})
+elseif(USE_BLAS STREQUAL "apple")
+  find_library(BLAS_LIBRARY Accelerate)
+  include_directories(${BLAS_LIBRARY}/Versions/Current/Frameworks/vecLib.framework/Versions/Current/Headers/)
+  list(APPEND TVM_RUNTIME_LINKER_LIBS ${BLAS_LIBRARY})
+  list(APPEND RUNTIME_SRCS ${CBLAS_CONTRIB_SRC})
+  message(STATUS "Use BLAS library " ${BLAS_LIBRARY})
+elseif(USE_BLAS STREQUAL "none")
+  # pass
+else()
+  message(FATAL_ERROR "Invalid option: USE_BLAS=" ${USE_BLAS})
+endif()
diff --git a/cmake/modules/contrib/NNPack.cmake b/cmake/modules/contrib/NNPack.cmake
new file mode 100644
index 000000000000..e75d19081def
--- /dev/null
+++ b/cmake/modules/contrib/NNPack.cmake
@@ -0,0 +1,14 @@
+if(USE_NNPACK)
+  if(NNPACK_PATH STREQUAL "")
+    set(NNPACK_PATH ${CMAKE_CURRENT_SOURCE_DIR}/NNPack)
+  endif()
+	set(PTHREAD_POOL_PATH ${NNPACK_PATH}/deps/pthreadpool)
+  file(GLOB NNPACK_CONTRIB_SRC src/contrib/nnpack/*.cc)
+  list(APPEND RUNTIME_SRCS ${NNPACK_CONTRIB_SRC})
+	include_directories(${NNPACK_PATH}/include)
+	include_directories(${PTHREAD_POOL_PATH}/include)
+  find_library(NNPACK_CONTRB_LIB nnpack ${NNPACK_PATH}/lib)
+  find_library(NNPACK_PTHREAD_CONTRIB_LIB pthreadpool ${NNPACK_PATH}/lib)
+  list(APPEND TVM_RUNTIME_LINKER_LIBS ${NNPACK_CONTRIB_LIB})
+  list(APPEND TVM_RUNTIME_LINKER_LIBS ${NNPACK_PTHREAD_CONTRIB_LIB})
+endif(USE_NNPACK)
diff --git a/cmake/modules/contrib/Random.cmake b/cmake/modules/contrib/Random.cmake
new file mode 100644
index 000000000000..a6980be8bb5b
--- /dev/null
+++ b/cmake/modules/contrib/Random.cmake
@@ -0,0 +1,5 @@
+if(USE_RANDOM)
+  message(STATUS "Build with contrib.random")
+  file(GLOB RANDOM_CONTRIB_SRC src/contrib/random/random.cc)
+  list(APPEND RUNTIME_SRCS ${RANDOM_CONTRIB_SRC})
+endif(USE_RANDOM)
diff --git a/cmake/modules/contrib/Sort.cmake b/cmake/modules/contrib/Sort.cmake
new file mode 100644
index 000000000000..9ef637ecd99f
--- /dev/null
+++ b/cmake/modules/contrib/Sort.cmake
@@ -0,0 +1,5 @@
+if(USE_SORT)
+  message(STATUS "Build with contrib.sort")
+  file(GLOB SORT_CONTRIB_SRC src/contrib/sort/*.cc)
+  list(APPEND RUNTIME_SRCS ${SORT_CONTRIB_SRC})
+endif(USE_SORT)
diff --git a/cmake/util/FindLLVM.cmake b/cmake/util/FindLLVM.cmake
new file mode 100644
index 000000000000..d2707e522c56
--- /dev/null
+++ b/cmake/util/FindLLVM.cmake
@@ -0,0 +1,39 @@
+#######################################################
+# Enhanced version of find llvm that allows set of LLVM_CONFIG
+# When LLVM_CONFIG_PATH is AUTO,
+# it defaults to system find llvm
+#
+# Usage:
+#   find_llvm(LLVM_CONFIG_PATH)
+#
+# Provide variables:
+# - LLVM_INCLUDE_DIRS
+# - LLVM_LIBS
+# - LLVM_DEFINITIONS
+# - LLVM_VERSION_CONCAT
+#
+macro(find_llvm)
+  if(LLVM_CONFIG STREQUAL "")
+    find_package(LLVM REQUIRED CONFIG)
+    llvm_map_components_to_libnames(LLVM_LIBS all)
+    list(REMOVE_ITEM LLVM_LIBS LTO)
+    set(TVM_LLVM_VERSION ${LLVM_VERSION_MAJOR}${LLVM_VERSION_MINOR})
+  else()
+    # use llvm config
+    message(STATUS "Use llvm-config=" ${LLVM_CONFIG})
+    execute_process(COMMAND ${LLVM_CONFIG} --includedir
+      OUTPUT_VARIABLE LLVM_INCLUDE_DIRS)
+    execute_process(COMMAND ${LLVM_CONFIG} --libfiles
+      OUTPUT_VARIABLE LLVM_LIBS)
+    execute_process(COMMAND ${LLVM_CONFIG} --cxxflags
+      OUTPUT_VARIABLE __llvm_cxxflags)
+    execute_process(COMMAND ${LLVM_CONFIG} --version
+      COMMAND cut -b 1,3
+      OUTPUT_VARIABLE TVM_LLVM_VERSION)
+    string(REGEX MATCHALL "(^| )-D[A-Za-z0-9_]*" LLVM_DEFINITIONS ${__llvm_cxxflags})
+    string(STRIP ${LLVM_LIBS} LLVM_LIBS)
+    separate_arguments(LLVM_LIBS)
+    string(STRIP ${LLVM_INCLUDE_DIRS} LLVM_INCLUDE_DIRS)
+    string(STRIP ${TVM_LLVM_VERSION} TVM_LLVM_VERSION)
+  endif()
+endmacro(find_llvm)
diff --git a/cmake/Util.cmake b/cmake/util/Util.cmake
similarity index 72%
rename from cmake/Util.cmake
rename to cmake/util/Util.cmake
index 0cc3acea5e55..fb3118bde9e0 100644
--- a/cmake/Util.cmake
+++ b/cmake/util/Util.cmake
@@ -1,8 +1,14 @@
+macro(__tvm_option variable description value)
+  if(NOT DEFINED ${variable})
+    set(${variable} ${value} CACHE STRING ${description})
+  endif()
+endmacro()
+
 #######################################################
 # An option that the user can select. Can accept condition to control when option is available for user.
 # Usage:
 #   tvm_option(<option_variable> "doc string" <initial value or boolean expression> [IF <condition>])
-function(tvm_option variable description value)
+macro(tvm_option variable description value)
   set(__value ${value})
   set(__condition "")
   set(__varname "__value")
@@ -21,23 +27,23 @@ function(tvm_option variable description value)
   if(${__condition})
     if("${__value}" MATCHES ";")
       if(${__value})
-        option(${variable} "${description}" ON)
+        __tvm_option(${variable} "${description}" ON)
       else()
-        option(${variable} "${description}" OFF)
+        __tvm_option(${variable} "${description}" OFF)
       endif()
     elseif(DEFINED ${__value})
       if(${__value})
-        option(${variable} "${description}" ON)
+        __tvm_option(${variable} "${description}" ON)
       else()
-        option(${variable} "${description}" OFF)
+        __tvm_option(${variable} "${description}" OFF)
       endif()
     else()
-      option(${variable} "${description}" ${__value})
+      __tvm_option(${variable} "${description}" "${__value}")
     endif()
   else()
     unset(${variable} CACHE)
   endif()
-endfunction()
+endmacro()
 
 function(assign_source_group group)
     foreach(_source IN ITEMS ${ARGN})
@@ -50,4 +56,4 @@ function(assign_source_group group)
         string(REPLACE "/" "\\" _source_path_msvc "${_source_path}")
         source_group("${group}\\${_source_path_msvc}" FILES "${_source}")
     endforeach()
-endfunction(assign_source_group)
\ No newline at end of file
+endfunction(assign_source_group)
diff --git a/docs/install/index.rst b/docs/install/index.rst
index eee2934840a0..076632ac21db 100644
--- a/docs/install/index.rst
+++ b/docs/install/index.rst
@@ -38,13 +38,23 @@ Our goal is to build the shared libraries:
 The minimal building requirements are
 
 - A recent c++ compiler supporting C++ 11 (g++-4.8 or higher)
+- CMake 3.7 or higher
 - We highly recommend to build with LLVM to enable all the features.
 - It is possible to build without llvm dependency if we only want to use CUDA/OpenCL
 
-The configuration of tvm can be modified by `config.mk`
+We use cmake to build the library.
+The configuration of tvm can be modified by `config.cmake`.
 
-- First copy ``make/config.mk`` to the project root, on which
-  any local modification will be ignored by git, then modify the according flags.
+
+- First, check the cmake in your system, you do not have cmake
+  you can obtain the latest version from `official website <https://cmake.org/download/>`_
+- First create a build directory, copy the ``cmake/config.cmake`` to the directory.
+  .. code:: bash
+
+      mkdir build
+      cp cmake/config.cmake build
+
+- Edit ``build/config.cmake`` to customize the compilation options
 
   - On macOS, for some versions of XCode, you need to add ``-lc++abi`` in the LDFLAGS or you'll get link errors.
 
@@ -54,27 +64,22 @@ The configuration of tvm can be modified by `config.mk`
   - Since LLVM takes long time to build from source, you can download pre-built version of LLVM from
     [LLVM Download Page](http://releases.llvm.org/download.html).
 
-    - Unzip to a certain location, modify ``config.mk`` to add ``LLVM_CONFIG=/path/to/your/llvm/bin/llvm-config``
+
+    - Unzip to a certain location, modify ``build/config.cmake`` to add ``set(USE_LLVM /path/to/your/llvm/bin/llvm-config)``
+    - You can also directly set ``set(USE_LLVM ON)`` and let cmake search for a usable version of LLVM.
 
   - You can also use [LLVM Nightly Ubuntu Build](https://apt.llvm.org/)
 
     - Note that apt-package append ``llvm-config`` with version number.
-      For example, set ``LLVM_CONFIG=llvm-config-4.0`` if you installed 4.0 package
-
-We can then build tvm by `make`.
-
-.. code:: bash
-
-  make -j4
-
-After we build tvm, we can proceed to build nnvm using the following command.
-
-.. code:: bash
+      For example, set ``set(LLVM_CONFIG llvm-config-4.0)`` if you installed 4.0 package
 
-  make nnvm -j4
+- We can then build tvm and related libraries.
 
+  .. code:: bash
+      cd build
+      cmake ..
+      make -j4
 
-This will creates `libnnvm_compiler.so` under the `nnvm/lib` folder.
 If everything goes well, we can go to the specific language installation section.
 
 
@@ -101,7 +106,7 @@ Building ROCm support
 
 Currently, ROCm is supported only on linux, so all the instructions are written with linux in mind.
 
-- Set ``USE_ROCM=1``, set ROCM_PATH to the correct path.
+- Set ``set(USE_ROCM ON)``, set ROCM_PATH to the correct path.
 - You need to first install HIP runtime from ROCm. Make sure the installation system has ROCm installed in it.
 - Install latest stable version of LLVM (v6.0.1), and LLD, make sure ``ld.lld`` is available via command line.
 
diff --git a/docs/install/nnpack.md b/docs/install/nnpack.md
index 1e9bd22ba200..d4e6e39e4023 100644
--- a/docs/install/nnpack.md
+++ b/docs/install/nnpack.md
@@ -71,9 +71,8 @@ sudo ldconfig
 git clone --recursive https://github.com/dmlc/tvm
 ```
 
-* Set `USE_NNPACK = 1` in config.mk.
+* Set `set(USE_NNPACK ON)` in config.cmake.
 * Set `NNPACK_PATH` to the $(YOUR_NNPACK_INSTALL_PATH)
-* Set `LLVM_CONFIG = llvm-config-4.0` depending of llvm version installed
 
 after configuration use `make` to build TVM
 
diff --git a/include/tvm/runtime/config.h b/include/tvm/runtime/config.h
deleted file mode 100644
index c288f6048c8d..000000000000
--- a/include/tvm/runtime/config.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*!
- *  Copyright (c) 2017 by Contributors
- * \file tvm/runtime/config.h
- * \brief Runtime library related configurations.
- */
-#ifndef TVM_RUNTIME_CONFIG_H_
-#define TVM_RUNTIME_CONFIG_H_
-
-/*!
- *\brief whether to use CUDA runtime
- */
-#ifndef TVM_CUDA_RUNTIME
-#define TVM_CUDA_RUNTIME 1
-#endif
-
-/*!
- *\brief whether to use opencl runtime
- */
-#ifndef TVM_OPENCL_RUNTIME
-#define TVM_OPENCL_RUNTIME 0
-#endif
-
-/*!
- *\brief whether to use metal runtime
- */
-#ifndef TVM_METAL_RUNTIME
-#define TVM_METAL_RUNTIME 0
-#endif
-
-#endif  // TVM_RUNTIME_CONFIG_H_
diff --git a/jvm/assembly/linux-x86_64-cpu/src/main/assembly/assembly.xml b/jvm/assembly/linux-x86_64-cpu/src/main/assembly/assembly.xml
index 936832f43005..6139d94fe5a0 100644
--- a/jvm/assembly/linux-x86_64-cpu/src/main/assembly/assembly.xml
+++ b/jvm/assembly/linux-x86_64-cpu/src/main/assembly/assembly.xml
@@ -6,7 +6,7 @@
   <includeBaseDirectory>false</includeBaseDirectory>
   <files>
     <file>
-      <source>../../../lib/libtvm_runtime.so</source>
+      <source>../../../build/libtvm_runtime.so</source>
       <outputDirectory>lib/native</outputDirectory>
       <fileMode>0644</fileMode>
     </file>
diff --git a/jvm/assembly/linux-x86_64-gpu/src/main/assembly/assembly.xml b/jvm/assembly/linux-x86_64-gpu/src/main/assembly/assembly.xml
index c0f5c053d262..6326ad432ec7 100644
--- a/jvm/assembly/linux-x86_64-gpu/src/main/assembly/assembly.xml
+++ b/jvm/assembly/linux-x86_64-gpu/src/main/assembly/assembly.xml
@@ -6,7 +6,7 @@
   <includeBaseDirectory>false</includeBaseDirectory>
   <files>
     <file>
-      <source>../../../lib/libtvm_runtime.so</source>
+      <source>../../../build/libtvm_runtime.so</source>
       <outputDirectory>lib/native</outputDirectory>
       <fileMode>0644</fileMode>
     </file>
diff --git a/jvm/assembly/osx-x86_64-cpu/src/main/assembly/assembly.xml b/jvm/assembly/osx-x86_64-cpu/src/main/assembly/assembly.xml
index 404f935a1f3e..0e8e90580957 100644
--- a/jvm/assembly/osx-x86_64-cpu/src/main/assembly/assembly.xml
+++ b/jvm/assembly/osx-x86_64-cpu/src/main/assembly/assembly.xml
@@ -6,7 +6,7 @@
   <includeBaseDirectory>false</includeBaseDirectory>
   <files>
     <file>
-      <source>../../../lib/libtvm_runtime.dylib</source>
+      <source>../../../build/libtvm_runtime.dylib</source>
       <outputDirectory>lib/native</outputDirectory>
       <fileMode>0644</fileMode>
     </file>
diff --git a/jvm/core/pom.xml b/jvm/core/pom.xml
index 8aed78525189..f8160f29dcd0 100644
--- a/jvm/core/pom.xml
+++ b/jvm/core/pom.xml
@@ -81,7 +81,7 @@
           <consoleOutput>true</consoleOutput>
         </configuration>
       </plugin>
-      <plugin>             
+      <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-surefire-plugin</artifactId>
         <version>2.7</version>
@@ -91,7 +91,7 @@
           <threadCount>1</threadCount>
           <argLine>
             -Djava.library.path=${project.parent.basedir}/native/${platform}/target
-            -Dlibtvm.so.path=${project.parent.basedir}/../lib/${libtvm.so.filename}
+            -Dlibtvm.so.path=${project.parent.basedir}/../build/${libtvm.so.filename}
           </argLine>
         </configuration>
         <executions>
diff --git a/make/config.mk b/make/config.mk
deleted file mode 100644
index 01ab0c4e8a70..000000000000
--- a/make/config.mk
+++ /dev/null
@@ -1,95 +0,0 @@
-#-------------------------------------------------------------------------------
-#  Template configuration for compiling
-#
-#  If you want to change the configuration, please use the following
-#  steps. Assume you are on the root directory. First copy the this
-#  file so that any local changes will be ignored by git
-#
-#  $ cp make/config.mk .
-#
-#  Next modify the according entries, and then compile by
-#
-#  $ make
-#
-#  or build in parallel with 8 threads
-#
-#  $ make -j8
-#-------------------------------------------------------------------------------
-
-# whether compile with debug
-DEBUG = 0
-
-# the additional link flags you want to add
-ADD_LDFLAGS =
-
-# the additional compile flags you want to add
-ADD_CFLAGS =
-
-#---------------------------------------------
-# Backend runtimes.
-#---------------------------------------------
-# whether enable CUDA during compile
-USE_CUDA = 0
-
-# add the path to CUDA library to link and compile flag
-# if you have already add them to environment variable.
-# CUDA_PATH = /usr/local/cuda
-
-# ROCM
-USE_ROCM = 0
-
-# whether enable OpenCL during compile
-USE_OPENCL = 0
-
-# whether enable Metal during compile
-USE_METAL = 0
-
-# whether enable SGX during compile
-USE_SGX = 0
-SGX_SDK = /opt/sgxsdk
-
-# Whether enable RPC during compile
-USE_RPC = 1
-
-# Whether enable tiny embedded graph runtime.
-USE_GRAPH_RUNTIME = 1
-
-#  Whether enable additional graph debug functions
-USE_GRAPH_RUNTIME_DEBUG = 0
-
-# whether build with LLVM support
-# Requires LLVM version >= 4.0
-# Set LLVM_CONFIG to your version, uncomment to build with llvm support
-#
-# LLVM_CONFIG = llvm-config
-
-#---------------------------------------------
-# Contrib optional libraries.
-#---------------------------------------------
-# Whether use BLAS, choices: openblas, atlas, blas, apple
-USE_BLAS = none
-
-# Whether use contrib.random in runtime
-USE_RANDOM = 0
-
-# Whether use NNPack
-USE_NNPACK = 0
-# NNPACK_PATH = none
-
-# Whether use CuDNN
-USE_CUDNN = 0
-
-# Whether use MIOpen
-USE_MIOPEN = 0
-
-# Whether use MPS
-USE_MPS = 0
-
-# Whether use cuBLAS
-USE_CUBLAS = 0
-
-# Whether use rocBlas
-USE_ROCBLAS = 0
-
-# Whether use contrib sort
-USE_SORT = 0
diff --git a/make/contrib/cblas.mk b/make/contrib/cblas.mk
deleted file mode 100644
index b5f3237d7270..000000000000
--- a/make/contrib/cblas.mk
+++ /dev/null
@@ -1,17 +0,0 @@
-CBLAS_CONTRIB_SRC = $(wildcard src/contrib/cblas/*.cc)
-CBLAS_CONTRIB_OBJ = $(patsubst src/%.cc, build/%.o, $(CBLAS_CONTRIB_SRC))
-
-ifeq ($(USE_BLAS), openblas)
-	ADD_LDFLAGS += -lopenblas
-	RUNTIME_DEP += $(CBLAS_CONTRIB_OBJ)
-else ifeq ($(USE_BLAS), atlas)
-	ADD_LDFLAGS += -lcblas
-	RUNTIME_DEP += $(CBLAS_CONTRIB_OBJ)
-else ifeq ($(USE_BLAS), blas)
-	ADD_LDFLAGS += -lblas
-	RUNTIME_DEP += $(CBLAS_CONTRIB_OBJ)
-else ifeq ($(USE_BLAS), apple)
-	ADD_CFLAGS += -I/System/Library/Frameworks/Accelerate.framework/Versions/Current/Frameworks/vecLib.framework/Versions/Current/Headers/
-	FRAMEWORKS += -framework Accelerate
-	RUNTIME_DEP += $(CBLAS_CONTRIB_OBJ)
-endif
diff --git a/make/contrib/cublas.mk b/make/contrib/cublas.mk
deleted file mode 100644
index 8274dc0bb378..000000000000
--- a/make/contrib/cublas.mk
+++ /dev/null
@@ -1,8 +0,0 @@
-CUBLAS_CONTRIB_SRC = $(wildcard src/contrib/cublas/*.cc)
-CUBLAS_CONTRIB_OBJ = $(patsubst src/%.cc, build/%.o, $(CUBLAS_CONTRIB_SRC))
-
-ifeq ($(USE_CUBLAS), 1)
-CFLAGS += -DTVM_USE_CUBLAS=1
-ADD_LDFLAGS += -lcublas
-RUNTIME_DEP += $(CUBLAS_CONTRIB_OBJ)
-endif
diff --git a/make/contrib/cudnn.mk b/make/contrib/cudnn.mk
deleted file mode 100644
index f3959fe6533c..000000000000
--- a/make/contrib/cudnn.mk
+++ /dev/null
@@ -1,8 +0,0 @@
-CUDNN_CONTRIB_SRC = $(wildcard src/contrib/cudnn/*.cc)
-CUDNN_CONTRIB_OBJ = $(patsubst src/%.cc, build/%.o, $(CUDNN_CONTRIB_SRC))
-
-ifeq ($(USE_CUDNN), 1)
-CFLAGS += -DTVM_USE_CUDNN=1 -I$(CUDA_PATH)/include
-ADD_LDFLAGS += -lcudnn
-RUNTIME_DEP += $(CUDNN_CONTRIB_OBJ)
-endif
diff --git a/make/contrib/miopen.mk b/make/contrib/miopen.mk
deleted file mode 100644
index 10ed6cba0801..000000000000
--- a/make/contrib/miopen.mk
+++ /dev/null
@@ -1,8 +0,0 @@
-MIOPEN_CONTRIB_SRC = $(wildcard src/contrib/miopen/*.cc)
-MIOPEN_CONTRIB_OBJ = $(patsubst src/%.cc, build/%.o, $(MIOPEN_CONTRIB_SRC))
-
-ifeq ($(USE_MIOPEN), 1)
-CFLAGS += -DTVM_USE_MIOPEN=1
-ADD_LDFLAGS += -lMIOpen
-RUNTIME_DEP += $(MIOPEN_CONTRIB_OBJ)
-endif
diff --git a/make/contrib/mps.mk b/make/contrib/mps.mk
deleted file mode 100644
index 0fe8a7f12889..000000000000
--- a/make/contrib/mps.mk
+++ /dev/null
@@ -1,20 +0,0 @@
-MPS_CONTRIB_SRC = $(wildcard src/contrib/mps/*.mm)
-MPS_CONTRIB_OBJ = $(patsubst src/%.mm, build/%.o, $(MPS_CONTRIB_SRC))
-
-ifeq ($(USE_MPS), 1)
-FRAMEWORKS += -framework MetalPerformanceShaders
-CFLAGS += 
-ADD_LDFLAGS += 
-RUNTIME_DEP += $(MPS_CONTRIB_OBJ)
-CONTRIB_OBJ += $(MPS_CONTRIB_OBJ)
-endif
-
-build/contrib/mps/%.o: src/contrib/mps/%.mm
-	@mkdir -p $(@D)
-	$(CXX) $(OBJCFLAGS) $(CFLAGS) -MM -MT build/contrib/mps/$*.o $< >build/contrib/mps/$*.d
-	$(CXX) $(OBJCFLAGS) -c $(CFLAGS) -c $< -o $@
-
-build/contrib/mps/%.o: src/contrib/mps/%.cc
-	@mkdir -p $(@D)
-	$(CXX) $(OBJCFLAGS) $(CFLAGS) -MM -MT build/contrib/mps/$*.o $< >build/contrib/mps/$*.d
-	$(CXX) $(OBJCFLAGS) -c $(CFLAGS) -c $< -o $@
diff --git a/make/contrib/nnpack.mk b/make/contrib/nnpack.mk
deleted file mode 100644
index 718bfe2ae414..000000000000
--- a/make/contrib/nnpack.mk
+++ /dev/null
@@ -1,12 +0,0 @@
-NNPACK_CONTRIB_SRC = $(wildcard src/contrib/nnpack/*.cc)
-NNPACK_CONTRIB_OBJ = $(patsubst src/%.cc, build/%.o, $(NNPACK_CONTRIB_SRC))
-
-ifeq ($(USE_NNPACK), 1)
-ifndef NNPACK_PATH
-	NNPACK_PATH = $(ROOTDIR)/NNPACK
-endif
-	PTHREAD_POOL_PATH = $(NNPACK_PATH)/deps/pthreadpool
-	CFLAGS += -DTVM_USE_NNPACK=1 -I$(NNPACK_PATH)/include -I$(PTHREAD_POOL_PATH)/include
-	LDFLAGS += -L$(NNPACK_PATH)/lib -lnnpack -lpthreadpool -lpthread
-	RUNTIME_DEP += $(NNPACK_CONTRIB_OBJ)
-endif
diff --git a/make/contrib/random.mk b/make/contrib/random.mk
deleted file mode 100644
index 3f8c03f5e4e8..000000000000
--- a/make/contrib/random.mk
+++ /dev/null
@@ -1,6 +0,0 @@
-RANDOM_CONTRIB_SRC = $(wildcard src/contrib/random/random.cc)
-RANDOM_CONTRIB_OBJ = $(patsubst src/%.cc, build/%.o, $(RANDOM_CONTRIB_SRC))
-
-ifeq ($(USE_RANDOM), 1)
-	RUNTIME_DEP += $(RANDOM_CONTRIB_OBJ)
-endif
diff --git a/make/contrib/rocblas.mk b/make/contrib/rocblas.mk
deleted file mode 100644
index ae5663099d53..000000000000
--- a/make/contrib/rocblas.mk
+++ /dev/null
@@ -1,8 +0,0 @@
-ROCBLAS_CONTRIB_SRC = $(wildcard src/contrib/rocblas/*.cc)
-ROCBLAS_CONTRIB_OBJ = $(patsubst src/%.cc, build/%.o, $(ROCBLAS_CONTRIB_SRC))
-
-ifeq ($(USE_ROCBLAS), 1)
-CFLAGS += -DTVM_USE_ROCBLAS=1
-ADD_LDFLAGS += -lrocblas
-RUNTIME_DEP += $(ROCBLAS_CONTRIB_OBJ)
-endif
diff --git a/make/contrib/sort.mk b/make/contrib/sort.mk
deleted file mode 100644
index 40afd1c581dc..000000000000
--- a/make/contrib/sort.mk
+++ /dev/null
@@ -1,6 +0,0 @@
-SORT_CONTRIB_SRC = $(wildcard src/contrib/sort/*.cc)
-SORT_CONTRIB_OBJ = $(patsubst src/%.cc, build/%.o, $(SORT_CONTRIB_SRC))
-
-ifeq ($(USE_SORT), 1)
-    RUNTIME_DEP += $(SORT_CONTRIB_OBJ)
-endif
diff --git a/src/codegen/codegen_cuda.cc b/src/codegen/codegen_cuda.cc
index 854bdd993127..f2e038c4a2a4 100644
--- a/src/codegen/codegen_cuda.cc
+++ b/src/codegen/codegen_cuda.cc
@@ -3,7 +3,6 @@
  * \file codegen_cuda.cc
  */
 #include <tvm/base.h>
-#include <tvm/runtime/config.h>
 #include <tvm/runtime/registry.h>
 #include <tvm/packed_func_ext.h>
 #include <vector>
diff --git a/src/codegen/codegen_metal.cc b/src/codegen/codegen_metal.cc
index fe8486668da0..54c0fd90814c 100644
--- a/src/codegen/codegen_metal.cc
+++ b/src/codegen/codegen_metal.cc
@@ -2,7 +2,6 @@
  *  Copyright (c) 2017 by Contributors
  * \file codegen_metal.cc
  */
-#include <tvm/runtime/config.h>
 #include <tvm/packed_func_ext.h>
 #include <vector>
 #include <string>
diff --git a/src/codegen/codegen_opencl.cc b/src/codegen/codegen_opencl.cc
index dd8bf7081a25..909aacdc4cd2 100644
--- a/src/codegen/codegen_opencl.cc
+++ b/src/codegen/codegen_opencl.cc
@@ -2,7 +2,6 @@
  *  Copyright (c) 2017 by Contributors
  * \file codegen_opencl.cc
  */
-#include <tvm/runtime/config.h>
 #include <tvm/packed_func_ext.h>
 #include <vector>
 #include <string>
diff --git a/src/codegen/codegen_opengl.cc b/src/codegen/codegen_opengl.cc
index 0e9e274b6bba..3ad87d52b0f4 100644
--- a/src/codegen/codegen_opengl.cc
+++ b/src/codegen/codegen_opengl.cc
@@ -5,7 +5,6 @@
  * We are targeting OpenGL 3.3. The reason of not targeting a recent version
  * of OpenGL is to have better compatibility of WebGL 2.
  */
-#include <tvm/runtime/config.h>
 #include <tvm/packed_func_ext.h>
 #include <vector>
 #include <string>
diff --git a/src/codegen/llvm/codegen_amdgpu.cc b/src/codegen/llvm/codegen_amdgpu.cc
index 6feb3b379d21..05bc8df355da 100644
--- a/src/codegen/llvm/codegen_amdgpu.cc
+++ b/src/codegen/llvm/codegen_amdgpu.cc
@@ -105,7 +105,6 @@ class CodeGenAMDGPU : public CodeGenLLVM {
   llvm::Value* CreateStorageSync(const Call* op) final {
     const std::string& sync = op->args[0].as<StringImm>()->value;
     if (sync == "warp") {
-      // TODO(tqchen) warp sync in CUDA9
       return nullptr;
     } else if (sync == "shared") {
       llvm::Function* f = llvm::Intrinsic::getDeclaration(
diff --git a/src/codegen/llvm/codegen_nvptx.cc b/src/codegen/llvm/codegen_nvptx.cc
index a22494b72909..3231008e8959 100644
--- a/src/codegen/llvm/codegen_nvptx.cc
+++ b/src/codegen/llvm/codegen_nvptx.cc
@@ -4,7 +4,6 @@
  * \brief NVPTX code generator.
  */
 #ifdef TVM_LLVM_VERSION
-#if TVM_CUDA_RUNTIME
 
 #include <tvm/runtime/device_api.h>
 #include "./codegen_llvm.h"
@@ -215,5 +214,4 @@ TVM_REGISTER_API("codegen.build_nvptx")
 
 }  // namespace codegen
 }  // namespace tvm
-#endif   // TVM_CUDA_RUNTIME
 #endif  // TVM_LLVM_VERSION
diff --git a/src/codegen/opt/README b/src/codegen/opt/README
new file mode 100644
index 000000000000..535e8c03d114
--- /dev/null
+++ b/src/codegen/opt/README
@@ -0,0 +1 @@
+This folder contains optional builds for codegen depending on compilation options.
diff --git a/src/codegen/opt/build_cuda_off.cc b/src/codegen/opt/build_cuda_off.cc
new file mode 100644
index 000000000000..4e2bfd03d71a
--- /dev/null
+++ b/src/codegen/opt/build_cuda_off.cc
@@ -0,0 +1,18 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ *  Optional module when build cuda is switched to off
+ */
+#include "../../runtime/cuda/cuda_module.h"
+namespace tvm {
+namespace runtime {
+
+Module CUDAModuleCreate(
+    std::string data,
+    std::string fmt,
+    std::unordered_map<std::string, FunctionInfo> fmap,
+    std::string cuda_source) {
+  LOG(FATAL) << "CUDA is not enabled";
+  return Module();
+}
+}  // namespace runtime
+}  // namespace tvm
diff --git a/src/codegen/build_cuda.cc b/src/codegen/opt/build_cuda_on.cc
similarity index 91%
rename from src/codegen/build_cuda.cc
rename to src/codegen/opt/build_cuda_on.cc
index aaac4115e4d3..267c3447e051 100644
--- a/src/codegen/build_cuda.cc
+++ b/src/codegen/opt/build_cuda_on.cc
@@ -1,17 +1,17 @@
 /*!
  *  Copyright (c) 2017 by Contributors
  *  Build cuda modules from source.
+ *  requires cuda to be available.
+ *
  * \file build_cuda.cc
  */
 #include <tvm/base.h>
-#include <tvm/runtime/config.h>
-#include "./codegen_cuda.h"
-#include "./build_common.h"
-
-#if TVM_CUDA_RUNTIME
 #include <nvrtc.h>
-#include "../runtime/cuda/cuda_common.h"
-#include "../runtime/cuda/cuda_module.h"
+
+#include "../codegen_cuda.h"
+#include "../build_common.h"
+#include "../../runtime/cuda/cuda_common.h"
+#include "../../runtime/cuda/cuda_module.h"
 
 namespace tvm {
 namespace codegen {
@@ -79,4 +79,3 @@ TVM_REGISTER_API("codegen.build_cuda")
   });
 }  // namespace codegen
 }  // namespace tvm
-#endif   // TVM_CUDA_RUNTIME
diff --git a/src/codegen/build_metal.cc b/src/codegen/opt/build_metal.h
similarity index 80%
rename from src/codegen/build_metal.cc
rename to src/codegen/opt/build_metal.h
index 42aa0965ec9d..a5c7c0ecf670 100644
--- a/src/codegen/build_metal.cc
+++ b/src/codegen/opt/build_metal.h
@@ -1,15 +1,17 @@
 /*!
  *  Copyright (c) 2017 by Contributors
  *  Build metal modules from source.
- * \file build_metal.cc
+ * \file build_metal.h
  */
-#include <tvm/base.h>
-#include <tvm/runtime/config.h>
-#include "./codegen_metal.h"
-#include "./build_common.h"
+#ifndef TVM_CODEGEN_OPT_BUILD_METAL_H_
+#define TVM_CODEGEN_OPT_BUILD_METAL_H_
+
+#include <string>
+#include "../codegen_metal.h"
+#include "../build_common.h"
 
 #if TVM_METAL_RUNTIME
-#include "../runtime/metal/metal_module.h"
+#include "../../runtime/metal/metal_module.h"
 #endif   // TVM_METAL_RUNTIME
 
 namespace tvm {
@@ -45,3 +47,4 @@ TVM_REGISTER_API("codegen.build_metal")
   });
 }  // namespace codegen
 }  // namespace tvm
+#endif  // TVM_CODEGEN_OPT_BUILD_METAL_H_
diff --git a/src/codegen/opt/build_metal_off.cc b/src/codegen/opt/build_metal_off.cc
new file mode 100644
index 000000000000..a4fc1adc3417
--- /dev/null
+++ b/src/codegen/opt/build_metal_off.cc
@@ -0,0 +1,6 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ *  Build Metal modules off
+ */
+#define TVM_METAL_RUNTIME 0
+#include "./build_metal.h"
diff --git a/src/codegen/opt/build_metal_on.cc b/src/codegen/opt/build_metal_on.cc
new file mode 100644
index 000000000000..65194884ef7f
--- /dev/null
+++ b/src/codegen/opt/build_metal_on.cc
@@ -0,0 +1,6 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ *  Build Metal modules on
+ */
+#define TVM_METAL_RUNTIME 1
+#include "./build_metal.h"
diff --git a/src/codegen/build_opencl.cc b/src/codegen/opt/build_opencl.h
similarity index 78%
rename from src/codegen/build_opencl.cc
rename to src/codegen/opt/build_opencl.h
index c4a53744d477..5756881ae315 100644
--- a/src/codegen/build_opencl.cc
+++ b/src/codegen/opt/build_opencl.h
@@ -1,15 +1,18 @@
 /*!
  *  Copyright (c) 2017 by Contributors
  *  Build opencl modules from source.
- * \file build_opencl.cc
+ * \file build_opencl.h
  */
+#ifndef TVM_CODEGEN_OPT_BUILD_OPENCL_H_
+#define TVM_CODEGEN_OPT_BUILD_OPENCL_H_
+
 #include <tvm/base.h>
-#include <tvm/runtime/config.h>
-#include "./codegen_opencl.h"
-#include "./build_common.h"
+#include <string>
+#include "../codegen_opencl.h"
+#include "../build_common.h"
 
 #if TVM_OPENCL_RUNTIME
-#include "../runtime/opencl/opencl_module.h"
+#include "../../runtime/opencl/opencl_module.h"
 #endif   // TVM_OPENCL_RUNTIME
 
 namespace tvm {
@@ -42,3 +45,4 @@ TVM_REGISTER_API("codegen.build_opencl")
   });
 }  // namespace codegen
 }  // namespace tvm
+#endif  // TVM_CODEGEN_OPT_BUILD_OPENCL_H_
diff --git a/src/codegen/opt/build_opencl_off.cc b/src/codegen/opt/build_opencl_off.cc
new file mode 100644
index 000000000000..0578d23d1b3f
--- /dev/null
+++ b/src/codegen/opt/build_opencl_off.cc
@@ -0,0 +1,6 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ *  Build opencl modules off
+ */
+#define TVM_OPENCL_RUNTIME 0
+#include "./build_opencl.h"
diff --git a/src/codegen/opt/build_opencl_on.cc b/src/codegen/opt/build_opencl_on.cc
new file mode 100644
index 000000000000..53c157883cf8
--- /dev/null
+++ b/src/codegen/opt/build_opencl_on.cc
@@ -0,0 +1,6 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ *  Build opencl modules on
+ */
+#define TVM_OPENCL_RUNTIME 1
+#include "./build_opencl.h"
diff --git a/src/codegen/build_opengl.cc b/src/codegen/opt/build_opengl.h
similarity index 80%
rename from src/codegen/build_opengl.cc
rename to src/codegen/opt/build_opengl.h
index 5e13676e8111..ed782cc04128 100644
--- a/src/codegen/build_opengl.cc
+++ b/src/codegen/opt/build_opengl.h
@@ -1,11 +1,14 @@
 /*!
  *  Copyright (c) 2017 by Contributors
  *  Build opengl modules from source.
- * \file build_opengl.cc
+ * \file build_opengl.h
  */
+#ifndef TVM_CODEGEN_OPT_BUILD_OPENGL_H_
+#define TVM_CODEGEN_OPT_BUILD_OPENGL_H_
+
 #include <tvm/base.h>
-#include "./codegen_opengl.h"
-#include "./build_common.h"
+#include "../codegen_opengl.h"
+#include "../build_common.h"
 
 namespace tvm {
 namespace codegen {
@@ -33,3 +36,4 @@ TVM_REGISTER_API("codegen.build_opengl")
 });
 }  // namespace codegen
 }  // namespace tvm
+#endif  // TVM_CODEGEN_OPT_BUILD_OPENGL_H_
diff --git a/src/codegen/opt/build_opengl_off.cc b/src/codegen/opt/build_opengl_off.cc
new file mode 100644
index 000000000000..3a88d6863b5e
--- /dev/null
+++ b/src/codegen/opt/build_opengl_off.cc
@@ -0,0 +1,6 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ *  Build OpenGL modules off
+ */
+#define TVM_OPENGL_RUNTIME 0
+#include "./build_opengl.h"
diff --git a/src/codegen/opt/build_opengl_on.cc b/src/codegen/opt/build_opengl_on.cc
new file mode 100644
index 000000000000..2670feb3d6f2
--- /dev/null
+++ b/src/codegen/opt/build_opengl_on.cc
@@ -0,0 +1,6 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ *  Build OpenGL modules on
+ */
+#define TVM_OPENGL_RUNTIME 1
+#include "./build_opengl.h"
diff --git a/src/runtime/cuda/cuda_common.h b/src/runtime/cuda/cuda_common.h
index 0b879dffe6bf..782fbc5d87e6 100644
--- a/src/runtime/cuda/cuda_common.h
+++ b/src/runtime/cuda/cuda_common.h
@@ -6,12 +6,9 @@
 #ifndef TVM_RUNTIME_CUDA_CUDA_COMMON_H_
 #define TVM_RUNTIME_CUDA_CUDA_COMMON_H_
 
-#include <tvm/runtime/config.h>
+#include <cuda_runtime.h>
 #include <tvm/runtime/packed_func.h>
 #include <string>
-
-#if TVM_CUDA_RUNTIME
-#include <cuda_runtime.h>
 #include "../workspace_pool.h"
 
 namespace tvm {
@@ -49,5 +46,4 @@ class CUDAThreadEntry {
 };
 }  // namespace runtime
 }  // namespace tvm
-#endif  // TVM_CUDA_RUNTIME
 #endif  // TVM_RUNTIME_CUDA_CUDA_COMMON_H_
diff --git a/src/runtime/cuda/cuda_device_api.cc b/src/runtime/cuda/cuda_device_api.cc
index 9aeca50584d5..9f1ea15aeeae 100644
--- a/src/runtime/cuda/cuda_device_api.cc
+++ b/src/runtime/cuda/cuda_device_api.cc
@@ -3,10 +3,8 @@
  * \file cuda_device_api.cc
  * \brief GPU specific API
  */
-#include <tvm/runtime/config.h>
 #include <tvm/runtime/device_api.h>
 
-#if TVM_CUDA_RUNTIME
 #include <dmlc/logging.h>
 #include <dmlc/thread_local.h>
 #include <tvm/runtime/registry.h>
@@ -204,4 +202,3 @@ TVM_REGISTER_GLOBAL("device_api.gpu")
 
 }  // namespace runtime
 }  // namespace tvm
-#endif  // TVM_CUDA_RUNTIME
diff --git a/src/runtime/cuda/cuda_module.cc b/src/runtime/cuda/cuda_module.cc
index fb2de868640a..a0e613107bae 100644
--- a/src/runtime/cuda/cuda_module.cc
+++ b/src/runtime/cuda/cuda_module.cc
@@ -4,8 +4,6 @@
  */
 #include "./cuda_module.h"
 
-#if TVM_CUDA_RUNTIME
-
 #include <tvm/runtime/registry.h>
 #include <cuda.h>
 #include <cuda_runtime.h>
@@ -305,4 +303,3 @@ TVM_REGISTER_GLOBAL("module.loadbinary_cuda")
   });
 }  // namespace runtime
 }  // namespace tvm
-#endif  // TVM_CUDA_RUNTIME
diff --git a/src/runtime/cuda/cuda_module.h b/src/runtime/cuda/cuda_module.h
index 6c7095734aaa..82427ca8fc22 100644
--- a/src/runtime/cuda/cuda_module.h
+++ b/src/runtime/cuda/cuda_module.h
@@ -6,7 +6,6 @@
 #ifndef TVM_RUNTIME_CUDA_CUDA_MODULE_H_
 #define TVM_RUNTIME_CUDA_CUDA_MODULE_H_
 
-#include <tvm/runtime/config.h>
 #include <tvm/runtime/module.h>
 #include <memory>
 #include <vector>
diff --git a/src/runtime/metal/metal_common.h b/src/runtime/metal/metal_common.h
index 2f75407f95ce..205af9533f3b 100644
--- a/src/runtime/metal/metal_common.h
+++ b/src/runtime/metal/metal_common.h
@@ -13,7 +13,6 @@
 #import <Metal/MTLDevice.h>
 #import <Metal/MTLLibrary.h>
 
-#include <tvm/runtime/config.h>
 #include <tvm/runtime/c_runtime_api.h>
 #include <tvm/runtime/packed_func.h>
 #include <tvm/runtime/device_api.h>
diff --git a/src/runtime/metal/metal_device_api.mm b/src/runtime/metal/metal_device_api.mm
index dcde6945a692..fae1f0ded88f 100644
--- a/src/runtime/metal/metal_device_api.mm
+++ b/src/runtime/metal/metal_device_api.mm
@@ -2,11 +2,9 @@
  *  Copyright (c) 2017 by Contributors
  * \file metal_device_api.mm
  */
-#include "./metal_common.h"
-
-#if TVM_METAL_RUNTIME
 #include <tvm/runtime/registry.h>
 #include <dmlc/thread_local.h>
+#include "./metal_common.h"
 
 namespace tvm {
 namespace runtime {
@@ -289,5 +287,3 @@ int GetWarpSize(id<MTLDevice> dev) {
 }  // namespace metal
 }  // namespace runtime
 }  // namespace tvm
-
-#endif  // TVM_METAL_RUNTIME
diff --git a/src/runtime/metal/metal_module.h b/src/runtime/metal/metal_module.h
index bb2f9c86b731..9a928e62bd1b 100644
--- a/src/runtime/metal/metal_module.h
+++ b/src/runtime/metal/metal_module.h
@@ -6,7 +6,6 @@
 #ifndef TVM_RUNTIME_METAL_METAL_MODULE_H_
 #define TVM_RUNTIME_METAL_METAL_MODULE_H_
 
-#include <tvm/runtime/config.h>
 #include <tvm/runtime/packed_func.h>
 #include <memory>
 #include <vector>
diff --git a/src/runtime/metal/metal_module.mm b/src/runtime/metal/metal_module.mm
index 5baefd72ec62..c79e2cf11ac5 100644
--- a/src/runtime/metal/metal_module.mm
+++ b/src/runtime/metal/metal_module.mm
@@ -2,16 +2,13 @@
  *  Copyright (c) 2017 by Contributors
  * \file metal_module.cc
  */
-#include "./metal_module.h"
-
-#if TVM_METAL_RUNTIME
-
 #include <dmlc/memory_io.h>
 #include <tvm/runtime/registry.h>
 #include <tvm/runtime/module.h>
 #include <array>
 #include <string>
 #include <mutex>
+#include "./metal_module.h"
 #include "./metal_common.h"
 #include "../pack_args.h"
 #include "../thread_storage_scope.h"
@@ -304,4 +301,3 @@ Module MetalModuleLoadBinary(void* strm) {
   });
 }  // namespace runtime
 }  // namespace tvm
-#endif  // TVM_METAL_RUNTIME
diff --git a/src/runtime/opencl/opencl_common.h b/src/runtime/opencl/opencl_common.h
index a97efe408f33..e2db24f85769 100644
--- a/src/runtime/opencl/opencl_common.h
+++ b/src/runtime/opencl/opencl_common.h
@@ -6,13 +6,11 @@
 #ifndef TVM_RUNTIME_OPENCL_OPENCL_COMMON_H_
 #define TVM_RUNTIME_OPENCL_OPENCL_COMMON_H_
 
-#include <tvm/runtime/config.h>
 #include <tvm/runtime/c_runtime_api.h>
 #include <tvm/runtime/packed_func.h>
 #include <tvm/runtime/device_api.h>
 #include <dmlc/logging.h>
 
-#if TVM_OPENCL_RUNTIME
 #ifdef __APPLE__
 #include <OpenCL/opencl.h>
 #else
@@ -192,5 +190,4 @@ class OpenCLThreadEntry {
 }  // namespace cl
 }  // namespace runtime
 }  // namespace tvm
-#endif  // TVM_OPENCL_RUNTIME
 #endif  // TVM_RUNTIME_OPENCL_OPENCL_COMMON_H_
diff --git a/src/runtime/opencl/opencl_device_api.cc b/src/runtime/opencl/opencl_device_api.cc
index 07a82d34fd34..759f074b2fc1 100644
--- a/src/runtime/opencl/opencl_device_api.cc
+++ b/src/runtime/opencl/opencl_device_api.cc
@@ -2,12 +2,9 @@
  *  Copyright (c) 2017 by Contributors
  * \file opencl_device_api.cc
  */
-#include "./opencl_common.h"
-
-#if TVM_OPENCL_RUNTIME
-
 #include <tvm/runtime/registry.h>
 #include <dmlc/thread_local.h>
+#include "./opencl_common.h"
 
 namespace tvm {
 namespace runtime {
@@ -277,5 +274,3 @@ TVM_REGISTER_GLOBAL("device_api.opencl")
 }  // namespace cl
 }  // namespace runtime
 }  // namespace tvm
-
-#endif  // TVM_OPENCL_RUNTIME
diff --git a/src/runtime/opencl/opencl_module.cc b/src/runtime/opencl/opencl_module.cc
index d8831880fd8d..1d8904ed6f42 100644
--- a/src/runtime/opencl/opencl_module.cc
+++ b/src/runtime/opencl/opencl_module.cc
@@ -2,16 +2,14 @@
  *  Copyright (c) 2017 by Contributors
  * \file opencl_module.cc
  */
-#include "./opencl_common.h"
-#include "./opencl_module.h"
-
-#if TVM_OPENCL_RUNTIME
-
 #include <dmlc/memory_io.h>
 #include <tvm/runtime/registry.h>
 #include <vector>
 #include <string>
 #include <unordered_map>
+#include "./opencl_common.h"
+#include "./opencl_module.h"
+
 #include "../pack_args.h"
 #include "../thread_storage_scope.h"
 #include "../meta_data.h"
@@ -318,5 +316,3 @@ TVM_REGISTER_GLOBAL("module.loadbinary_opencl")
   });
 }  // namespace runtime
 }  // namespace tvm
-
-#endif  // TVM_OPENCL_RUNTIME
diff --git a/src/runtime/opencl/opencl_module.h b/src/runtime/opencl/opencl_module.h
index 54da2d9b0443..523a67f1a41a 100644
--- a/src/runtime/opencl/opencl_module.h
+++ b/src/runtime/opencl/opencl_module.h
@@ -6,7 +6,6 @@
 #ifndef TVM_RUNTIME_OPENCL_OPENCL_MODULE_H_
 #define TVM_RUNTIME_OPENCL_OPENCL_MODULE_H_
 
-#include <tvm/runtime/config.h>
 #include <tvm/runtime/packed_func.h>
 #include <memory>
 #include <vector>
diff --git a/src/runtime/opengl/opengl_common.h b/src/runtime/opengl/opengl_common.h
index 252639f3af27..edc425dae953 100644
--- a/src/runtime/opengl/opengl_common.h
+++ b/src/runtime/opengl/opengl_common.h
@@ -6,7 +6,6 @@
 #ifndef TVM_RUNTIME_OPENGL_OPENGL_COMMON_H_
 #define TVM_RUNTIME_OPENGL_OPENGL_COMMON_H_
 
-#include <tvm/runtime/config.h>
 #include <tvm/runtime/c_runtime_api.h>
 #include <tvm/runtime/packed_func.h>
 #include <tvm/runtime/device_api.h>
diff --git a/src/runtime/opengl/opengl_device_api.cc b/src/runtime/opengl/opengl_device_api.cc
index b45ee5079f19..96ffb3192b7b 100644
--- a/src/runtime/opengl/opengl_device_api.cc
+++ b/src/runtime/opengl/opengl_device_api.cc
@@ -2,13 +2,10 @@
  *  Copyright (c) 2017 by Contributors
  * \file opengl_device_api.cc
  */
-#include "./opengl_common.h"
-#include "./opengl_module.h"
-
-#if TVM_OPENGL_RUNTIME
-
 #include <tvm/runtime/registry.h>
 #include <cstring>
+#include "./opengl_common.h"
+#include "./opengl_module.h"
 
 namespace tvm {
 namespace runtime {
@@ -613,5 +610,3 @@ TVM_REGISTER_GLOBAL("device_api.opengl")
 }  // namespace gl
 }  // namespace runtime
 }  // namespace tvm
-
-#endif  // TVM_OPENGL_RUNTIME
diff --git a/src/runtime/opengl/opengl_module.cc b/src/runtime/opengl/opengl_module.cc
index 6793bd6d29b6..d800af95f053 100644
--- a/src/runtime/opengl/opengl_module.cc
+++ b/src/runtime/opengl/opengl_module.cc
@@ -2,14 +2,10 @@
  *  Copyright (c) 2017 by Contributors
  * \file opengl_module.cc
  */
+#include <tvm/runtime/registry.h>
 #include <utility>
-
 #include "./opengl_common.h"
 #include "./opengl_module.h"
-
-#if TVM_OPENGL_RUNTIME
-
-#include <tvm/runtime/registry.h>
 #include "../pack_args.h"
 #include "../thread_storage_scope.h"
 #include "../file_util.h"
@@ -280,5 +276,3 @@ TVM_REGISTER_GLOBAL("module.loadbinary_opengl")
 
 }  // namespace runtime
 }  // namespace tvm
-
-#endif  // TVM_OPENGL_RUNTIME
diff --git a/src/runtime/opengl/opengl_module.h b/src/runtime/opengl/opengl_module.h
index a4cfa20bd734..9e5f4044b3b6 100644
--- a/src/runtime/opengl/opengl_module.h
+++ b/src/runtime/opengl/opengl_module.h
@@ -6,7 +6,6 @@
 #ifndef TVM_RUNTIME_OPENGL_OPENGL_MODULE_H_
 #define TVM_RUNTIME_OPENGL_OPENGL_MODULE_H_
 
-#include <tvm/runtime/config.h>
 #include <tvm/runtime/packed_func.h>
 #include <algorithm>
 #include <memory>
diff --git a/src/runtime/rocm/rocm_common.h b/src/runtime/rocm/rocm_common.h
index 63e4bff4582b..a6898122e389 100644
--- a/src/runtime/rocm/rocm_common.h
+++ b/src/runtime/rocm/rocm_common.h
@@ -6,12 +6,9 @@
 #ifndef TVM_RUNTIME_ROCM_ROCM_COMMON_H_
 #define TVM_RUNTIME_ROCM_ROCM_COMMON_H_
 
-#include <tvm/runtime/config.h>
 #include <tvm/runtime/packed_func.h>
-#include <string>
-
-#if TVM_ROCM_RUNTIME
 #include <hip/hip_runtime_api.h>
+#include <string>
 #include "../workspace_pool.h"
 
 namespace tvm {
@@ -19,17 +16,17 @@ namespace runtime {
 
 #define ROCM_DRIVER_CALL(x)                                             \
   {                                                                     \
-    hipError_t result = x;                                                \
-    if (result != hipSuccess && result != hipErrorDeinitialized) { \
+    hipError_t result = x;                                              \
+    if (result != hipSuccess && result != hipErrorDeinitialized) {      \
       LOG(FATAL)                                                        \
-          << "ROCM HIP Error: " #x " failed with error: " << hipGetErrorString(result);            \
+          << "ROCM HIP Error: " #x " failed with error: " << hipGetErrorString(result); \
     }                                                                   \
   }
 
-#define ROCM_CALL(func)                                            \
-  {                                                                \
-    hipError_t e = (func);                                        \
-    CHECK(e == hipSuccess)       \
+#define ROCM_CALL(func)                                               \
+  {                                                                   \
+    hipError_t e = (func);                                            \
+    CHECK(e == hipSuccess)                                            \
         << "ROCM HIP: " << hipGetErrorString(e);                      \
   }
 
@@ -47,5 +44,4 @@ class ROCMThreadEntry {
 };
 }  // namespace runtime
 }  // namespace tvm
-#endif  // TVM_ROCM_RUNTIME
 #endif  // TVM_RUNTIME_ROCM_ROCM_COMMON_H_
diff --git a/src/runtime/rocm/rocm_device_api.cc b/src/runtime/rocm/rocm_device_api.cc
index 47b9e8da7e2e..3438e8edeaec 100644
--- a/src/runtime/rocm/rocm_device_api.cc
+++ b/src/runtime/rocm/rocm_device_api.cc
@@ -3,10 +3,8 @@
  * \file rocm_device_api.cc
  * \brief GPU specific API
  */
-#include <tvm/runtime/config.h>
 #include <tvm/runtime/device_api.h>
 
-#if TVM_ROCM_RUNTIME
 #include <dmlc/logging.h>
 #include <dmlc/thread_local.h>
 #include <tvm/runtime/registry.h>
@@ -162,4 +160,3 @@ TVM_REGISTER_GLOBAL("device_api.rocm")
 
 }  // namespace runtime
 }  // namespace tvm
-#endif  // TVM_ROCM_RUNTIME
diff --git a/src/runtime/rocm/rocm_module.cc b/src/runtime/rocm/rocm_module.cc
index d93862747bba..503b04872c82 100644
--- a/src/runtime/rocm/rocm_module.cc
+++ b/src/runtime/rocm/rocm_module.cc
@@ -2,16 +2,13 @@
  *  Copyright (c) 2017 by Contributors
  * \file rocm_module.cc
  */
-#include "./rocm_module.h"
-
-#if TVM_ROCM_RUNTIME
-
 #include <tvm/runtime/registry.h>
 #include <hip/hip_runtime_api.h>
 #include <vector>
 #include <array>
 #include <string>
 #include <mutex>
+#include "./rocm_module.h"
 #include "./rocm_common.h"
 #include "../pack_args.h"
 #include "../thread_storage_scope.h"
@@ -237,4 +234,3 @@ TVM_REGISTER_GLOBAL("module.loadbinary_hip")
   });
 }  // namespace runtime
 }  // namespace tvm
-#endif  // TVM_ROCM_RUNTIME
diff --git a/src/runtime/rocm/rocm_module.h b/src/runtime/rocm/rocm_module.h
index 039326ab1223..4386157fc986 100644
--- a/src/runtime/rocm/rocm_module.h
+++ b/src/runtime/rocm/rocm_module.h
@@ -6,7 +6,6 @@
 #ifndef TVM_RUNTIME_ROCM_ROCM_MODULE_H_
 #define TVM_RUNTIME_ROCM_ROCM_MODULE_H_
 
-#include <tvm/runtime/config.h>
 #include <tvm/runtime/module.h>
 #include <memory>
 #include <vector>
diff --git a/src/runtime/vulkan/vulkan_common.h b/src/runtime/vulkan/vulkan_common.h
index 0d02c0aaadcf..9dcd5f429b68 100644
--- a/src/runtime/vulkan/vulkan_common.h
+++ b/src/runtime/vulkan/vulkan_common.h
@@ -6,14 +6,11 @@
 #ifndef TVM_RUNTIME_VULKAN_VULKAN_COMMON_H_
 #define TVM_RUNTIME_VULKAN_VULKAN_COMMON_H_
 
-#include <tvm/runtime/config.h>
 #include <tvm/runtime/c_runtime_api.h>
 #include <tvm/runtime/packed_func.h>
 #include <tvm/runtime/device_api.h>
 #include <dmlc/logging.h>
 
-#if TVM_VULKAN_RUNTIME
-
 #include <vulkan/vulkan.h>
 #include <mutex>
 #include <string>
@@ -281,5 +278,4 @@ class VulkanThreadEntry {
 }  // namespace vulkan
 }  // namespace runtime
 }  // namespace tvm
-#endif  // TVM_VULKAN_RUNTIME
 #endif  // TVM_RUNTIME_VULKAN_VULKAN_COMMON_H_
diff --git a/src/runtime/vulkan/vulkan_device_api.cc b/src/runtime/vulkan/vulkan_device_api.cc
index 2abb0c277419..891b39456558 100644
--- a/src/runtime/vulkan/vulkan_device_api.cc
+++ b/src/runtime/vulkan/vulkan_device_api.cc
@@ -2,14 +2,10 @@
  *  Copyright (c) 2017 by Contributors
  * \file vulkan_device_api.cc
  */
-#include "./vulkan_common.h"
-
-#if TVM_VULKAN_RUNTIME
-
 #include <tvm/runtime/registry.h>
 #include <dmlc/thread_local.h>
 #include <cstring>
-
+#include "./vulkan_common.h"
 
 namespace tvm {
 namespace runtime {
@@ -693,5 +689,3 @@ TVM_REGISTER_GLOBAL("device_api.vulkan")
 }  // namespace vulkan
 }  // namespace runtime
 }  // namespace tvm
-
-#endif  // TVM_VULKAN_RUNTIME
diff --git a/src/runtime/vulkan/vulkan_module.cc b/src/runtime/vulkan/vulkan_module.cc
index 50ed744c512c..56b4c79bcedf 100644
--- a/src/runtime/vulkan/vulkan_module.cc
+++ b/src/runtime/vulkan/vulkan_module.cc
@@ -2,10 +2,6 @@
  *  Copyright (c) 2018 by Contributors
  * \file vulkan_module.cc
  */
-#include "./vulkan_module.h"
-
-#if TVM_VULKAN_RUNTIME
-
 #include <dmlc/memory_io.h>
 #include <tvm/runtime/registry.h>
 #include <tvm/runtime/module.h>
@@ -13,6 +9,7 @@
 #include <string>
 #include <mutex>
 #include "./vulkan_common.h"
+#include "./vulkan_module.h"
 #include "../pack_args.h"
 #include "../thread_storage_scope.h"
 #include "../meta_data.h"
@@ -421,4 +418,3 @@ TVM_REGISTER_GLOBAL("module.loadbinary_vulkan")
   });
 }  // namespace runtime
 }  // namespace tvm
-#endif  // TVM_VULKAN_RUNTIME
diff --git a/src/runtime/vulkan/vulkan_module.h b/src/runtime/vulkan/vulkan_module.h
index 65b6d24c8ad6..41dcfd310dca 100644
--- a/src/runtime/vulkan/vulkan_module.h
+++ b/src/runtime/vulkan/vulkan_module.h
@@ -6,7 +6,6 @@
 #ifndef TVM_RUNTIME_VULKAN_VULKAN_MODULE_H_
 #define TVM_RUNTIME_VULKAN_VULKAN_MODULE_H_
 
-#include <tvm/runtime/config.h>
 #include <tvm/runtime/packed_func.h>
 #include <dmlc/type_traits.h>
 #include <memory>
diff --git a/tests/scripts/task_build.sh b/tests/scripts/task_build.sh
index dc524f1403d2..2225d6eea5c3 100755
--- a/tests/scripts/task_build.sh
+++ b/tests/scripts/task_build.sh
@@ -1,7 +1,2 @@
 #!/bin/bash
-echo "Build TVM..."
-make "$@"
-cd nnvm
-
-echo "Build NNVM..."
-make "$@"
+cd $1 && cmake .. && make $2 && cd ..
diff --git a/tests/scripts/task_clean.sh b/tests/scripts/task_clean.sh
index baecd292ff8f..68c04c332a34 100755
--- a/tests/scripts/task_clean.sh
+++ b/tests/scripts/task_clean.sh
@@ -1,6 +1,3 @@
 #!/bin/bash
 echo "Cleanup data..."
-cd nnvm
-make clean
-cd ..
-make clean
+rm -rf $1
diff --git a/tests/scripts/task_cpp_unittest.sh b/tests/scripts/task_cpp_unittest.sh
index 9a140b64e089..2996fd6099a1 100755
--- a/tests/scripts/task_cpp_unittest.sh
+++ b/tests/scripts/task_cpp_unittest.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 export LD_LIBRARY_PATH=lib:${LD_LIBRARY_PATH}
 
-make test -j8 || exit -1
-for test in tests/cpp/*_test; do
+make cpptest -j8 || exit -1
+for test in build/*_test; do
     ./$test || exit -1
 done
diff --git a/tests/scripts/task_java_unittest.sh b/tests/scripts/task_java_unittest.sh
index 9017079237f1..8ae79b5c52b2 100755
--- a/tests/scripts/task_java_unittest.sh
+++ b/tests/scripts/task_java_unittest.sh
@@ -1,6 +1,5 @@
 #!/bin/bash
-export PYTHONPATH=python:apps/extension/python
-export PYTHONPATH=${PYTHONPATH}:apps/graph_executor/python:apps/graph_executor/nnvm/python
+export PYTHONPATH=python
 export LD_LIBRARY_PATH=lib:${LD_LIBRARY_PATH}
 
 CURR_DIR=$(cd `dirname $0`; pwd)
diff --git a/tests/scripts/task_python_integration.sh b/tests/scripts/task_python_integration.sh
index 7cdade714aa5..ab56ca3e8f6f 100755
--- a/tests/scripts/task_python_integration.sh
+++ b/tests/scripts/task_python_integration.sh
@@ -1,6 +1,6 @@
 #!/bin/bash
 export PYTHONPATH=python:apps/extension/python
-export LD_LIBRARY_PATH=lib:${LD_LIBRARY_PATH}
+export LD_LIBRARY_PATH=build:${LD_LIBRARY_PATH}
 
 rm -rf python/tvm/*.pyc python/tvm/*/*.pyc
 

From 67dcc894263c6a9031b8eb5ef55b1abffa462a80 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 11 Jun 2018 16:36:52 -0700
Subject: [PATCH 702/949] [BUILD] Upgrade build system to default python3
 (#1260)

---
 Jenkinsfile                                        |  2 +-
 Makefile                                           | 14 +++++++-------
 docs/Makefile                                      |  7 +------
 docs/install/index.rst                             |  4 +++-
 nnvm/python/nnvm/frontend/coreml.py                |  3 ++-
 nnvm/python/nnvm/frontend/keras.py                 |  7 ++++---
 nnvm/tests/python/frontend/keras/test_forward.py   |  9 ++++-----
 nnvm/tests/python/frontend/mxnet/test_forward.py   |  3 +--
 tests/ci_build/Dockerfile.gpu                      | 11 ++++++-----
 tests/ci_build/Dockerfile.lint                     |  6 ++++--
 tests/ci_build/install/ubuntu_install_coreml.sh    |  2 +-
 tests/ci_build/install/ubuntu_install_keras.sh     |  1 +
 tests/ci_build/install/ubuntu_install_mxnet.sh     |  1 -
 tests/ci_build/install/ubuntu_install_python.sh    | 13 ++++++++++---
 .../install/ubuntu_install_python_package.sh       |  2 +-
 tests/ci_build/install/ubuntu_install_sphinx.sh    |  2 +-
 tests/scripts/task_clean.sh                        |  2 +-
 tests/scripts/task_python_nnvm.sh                  |  6 +++---
 18 files changed, 51 insertions(+), 44 deletions(-)

diff --git a/Jenkinsfile b/Jenkinsfile
index 5822058a5c6a..ba75ee47c0ed 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -106,7 +106,7 @@ stage('Build') {
            cp ../cmake/config.cmake .
            echo set\\(USE_OPENCL ON\\) >> config.cmake
            echo set\\(USE_ROCM ON\\) >> config.cmake
-           echo set\\(USE_VULKAN ON\\) >> config.cmake
+           echo set\\(USE_VULKAN OFF\\) >> config.cmake
            """
         make('gpu', 'build2', '-j2')
       }
diff --git a/Makefile b/Makefile
index 3cab59d5d6b7..71378753236d 100644
--- a/Makefile
+++ b/Makefile
@@ -48,18 +48,18 @@ build/libtvm_web_runtime.js: build/libtvm_web_runtime.bc
 
 # Lint scripts
 cpplint:
-	python dmlc-core/scripts/lint.py topi cpp topi/include;
-	python dmlc-core/scripts/lint.py nnvm cpp nnvm/include nnvm/src;
-	python dmlc-core/scripts/lint.py tvm cpp include src verilog\
+	python3 dmlc-core/scripts/lint.py topi cpp topi/include;
+	python3 dmlc-core/scripts/lint.py nnvm cpp nnvm/include nnvm/src;
+	python3 dmlc-core/scripts/lint.py tvm cpp include src verilog\
 	 examples/extension/src examples/graph_executor/src
 
 pylint:
-	pylint python/tvm --rcfile=$(ROOTDIR)/tests/lint/pylintrc
-	pylint topi/python/topi --rcfile=$(ROOTDIR)/tests/lint/pylintrc
-	pylint nnvm/python/nnvm --rcfile=$(ROOTDIR)/tests/lint/pylintrc
+	python3 -m pylint python/tvm --rcfile=$(ROOTDIR)/tests/lint/pylintrc
+	python3 -m pylint topi/python/topi --rcfile=$(ROOTDIR)/tests/lint/pylintrc
+	python3 -m pylint nnvm/python/nnvm --rcfile=$(ROOTDIR)/tests/lint/pylintrc
 
 jnilint:
-	python dmlc-core/scripts/lint.py tvm4j-jni cpp jvm/native/src
+	python3 dmlc-core/scripts/lint.py tvm4j-jni cpp jvm/native/src
 
 lint: cpplint pylint jnilint
 
diff --git a/docs/Makefile b/docs/Makefile
index 1e45fb5e3787..21f13caa8950 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -3,15 +3,10 @@
 
 # You can set these variables from the command line.
 SPHINXOPTS    =
-SPHINXBUILD   = sphinx-build
+SPHINXBUILD   = python3 -m sphinx
 PAPER         =
 BUILDDIR      = _build
 
-# User-friendly check for sphinx-build
-ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
-$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
-endif
-
 # Internal variables.
 PAPEROPT_a4     = -D latex_paper_size=a4
 PAPEROPT_letter = -D latex_paper_size=letter
diff --git a/docs/install/index.rst b/docs/install/index.rst
index 076632ac21db..7e010da39a76 100644
--- a/docs/install/index.rst
+++ b/docs/install/index.rst
@@ -38,7 +38,7 @@ Our goal is to build the shared libraries:
 The minimal building requirements are
 
 - A recent c++ compiler supporting C++ 11 (g++-4.8 or higher)
-- CMake 3.7 or higher
+- CMake 3.5 or higher
 - We highly recommend to build with LLVM to enable all the features.
 - It is possible to build without llvm dependency if we only want to use CUDA/OpenCL
 
@@ -49,6 +49,7 @@ The configuration of tvm can be modified by `config.cmake`.
 - First, check the cmake in your system, you do not have cmake
   you can obtain the latest version from `official website <https://cmake.org/download/>`_
 - First create a build directory, copy the ``cmake/config.cmake`` to the directory.
+
   .. code:: bash
 
       mkdir build
@@ -76,6 +77,7 @@ The configuration of tvm can be modified by `config.cmake`.
 - We can then build tvm and related libraries.
 
   .. code:: bash
+
       cd build
       cmake ..
       make -j4
diff --git a/nnvm/python/nnvm/frontend/coreml.py b/nnvm/python/nnvm/frontend/coreml.py
index 006fb9017de1..d6a9ad2e2e2d 100644
--- a/nnvm/python/nnvm/frontend/coreml.py
+++ b/nnvm/python/nnvm/frontend/coreml.py
@@ -5,6 +5,7 @@
 
 import tvm
 from .. import symbol as _sym
+from .._base import string_types
 from .common import SymbolTable
 
 __all__ = ['from_coreml']
@@ -281,7 +282,7 @@ def coreml_op_to_nnvm(op, inname, outname, symtab):
     classname = type(op).__name__
     if classname not in _convert_map:
         raise NotImplementedError("%s is not supported" % (classname))
-    if isinstance(inname, (str, unicode)):
+    if isinstance(inname, string_types):
         insym = symtab.get_var(inname)
     else:
         insym = [symtab.get_var(i) for i in inname]
diff --git a/nnvm/python/nnvm/frontend/keras.py b/nnvm/python/nnvm/frontend/keras.py
index d8c98ec66a56..2624ca0ffffc 100644
--- a/nnvm/python/nnvm/frontend/keras.py
+++ b/nnvm/python/nnvm/frontend/keras.py
@@ -487,6 +487,7 @@ def from_keras(model):
         import keras
     except ImportError:
         raise ImportError('Keras must be installed')
+
     assert isinstance(model, keras.engine.training.Model)
     if keras.backend.image_data_format() != 'channels_last':
         raise ValueError("Keras frontend currently supports data_format = channels_last only.")
@@ -494,7 +495,7 @@ def from_keras(model):
 
     symtab = SymbolTable()
     for keras_layer in model.layers:
-        if isinstance(keras_layer, keras.engine.topology.InputLayer):
+        if isinstance(keras_layer, keras.engine.InputLayer):
             symtab.get_var(keras_layer.name, must_contain=False)
         else:
             inbound_nodes = keras_layer.inbound_nodes if hasattr(keras_layer, 'inbound_nodes') \
@@ -512,7 +513,7 @@ def from_keras(model):
                 # would confuse users, so we should keep them as far as possible.  Fortunately,
                 # they are named uniquely to input_1, input_2, input_3 ... by default.
                 for pred_idx, pred in zip(node.node_indices, node.inbound_layers):
-                    if isinstance(pred, keras.engine.topology.InputLayer):
+                    if isinstance(pred, keras.engine.InputLayer):
                         _sym = symtab.get_var(pred.name, must_contain=True)
                     else:
                         _sym = symtab.get_var(pred.name + ':' + str(pred_idx), must_contain=True)
@@ -522,6 +523,6 @@ def from_keras(model):
                     insym = insym[0]
                 keras_op_to_nnvm(insym, keras_layer, keras_layer.name + ':' + str(my_idx), symtab)
 
-    outsym = symtab.get_var(model.output_layers[0].name + ':0')
+    outsym = symtab.get_var(model._output_layers[0].name + ':0')
     tvmparams = {k:tvm.nd.array(np.array(v, dtype=np.float32)) for k, v in symtab.params.items()}
     return outsym, tvmparams
diff --git a/nnvm/tests/python/frontend/keras/test_forward.py b/nnvm/tests/python/frontend/keras/test_forward.py
index a5eda905ae4d..14ffeca2b282 100644
--- a/nnvm/tests/python/frontend/keras/test_forward.py
+++ b/nnvm/tests/python/frontend/keras/test_forward.py
@@ -15,9 +15,9 @@
 
 def verify_keras_frontend(keras_model):
     in_shapes = []
-    for layer in keras_model.input_layers:
+    for layer in keras_model._input_layers:
         in_shapes.append(tuple(dim.value if dim.value is not None else 1 for dim in layer.input.shape))
-    out_shape = [dim.value if dim.value is not None else 1 for dim in keras_model.output_layers[0].output.shape]
+    out_shape = [dim.value if dim.value is not None else 1 for dim in keras_model._output_layers[0].output.shape]
 
     def get_keras_output(xs, dtype='float32'):
         return keras_model.predict(xs)
@@ -41,7 +41,7 @@ def get_tvm_output(xs, target, ctx, dtype='float32'):
         tvm_out = get_tvm_output([x.transpose([0,3,1,2]) for x in xs], target, ctx)
         np.testing.assert_allclose(keras_out, tvm_out, rtol=1e-5, atol=1e-5)
 
-    
+
 def test_forward_elemwise_add():
     r = []
     data = keras.layers.Input(shape=(32,32,3))
@@ -74,7 +74,7 @@ def test_forward_dense():
 def test_forward_transpose_conv():
     data = keras.layers.Input(shape=(32,32,3))
     x = keras.layers.Conv2D(filters=10, kernel_size=(3,3), strides=(2,2), padding='same')(data)
-    x = keras.applications.mobilenet.DepthwiseConv2D(kernel_size=(3,3), padding='same')(x)
+    x = keras.layers.DepthwiseConv2D(kernel_size=(3,3), padding='same')(x)
     x = keras.layers.Conv2DTranspose(filters=64, kernel_size=(3,3), padding='valid')(x)
     x = keras.layers.GlobalMaxPooling2D()(x)
     keras_model = keras.models.Model(data, x)
@@ -136,7 +136,6 @@ def test_forward_activations():
     act_funcs = [keras.layers.Activation('softmax'),
                  keras.layers.Activation('softplus'),
                  keras.layers.LeakyReLU(alpha=0.3),
-                 keras.layers.Activation(keras.applications.mobilenet.relu6),
                  keras.layers.PReLU(weights=weights, alpha_initializer="zero"),
                  keras.layers.ELU(alpha=0.5),
                  keras.layers.Activation('selu'),
diff --git a/nnvm/tests/python/frontend/mxnet/test_forward.py b/nnvm/tests/python/frontend/mxnet/test_forward.py
index b54a5e42ec81..d0930d75cce1 100644
--- a/nnvm/tests/python/frontend/mxnet/test_forward.py
+++ b/nnvm/tests/python/frontend/mxnet/test_forward.py
@@ -100,8 +100,7 @@ def test_forward_rrelu():
 def test_forward_prelu():
     data = mx.sym.var('data')
     data = mx.sym.concat(data, -data, dim=1)  # negative part explicitly
-    gamma = mx.sym.zeros(shape=(6,))
-    mx_sym = mx.sym.LeakyReLU(data, gamma=gamma, act_type='prelu')
+    mx_sym = mx.sym.LeakyReLU(data, act_type='prelu')
     verify_mxnet_frontend_impl(mx_sym, (1, 3, 100, 100), (1, 6, 100, 100))
 
 def test_forward_softrelu():
diff --git a/tests/ci_build/Dockerfile.gpu b/tests/ci_build/Dockerfile.gpu
index 43b3ca7b394a..3e2c6f2eb7b5 100644
--- a/tests/ci_build/Dockerfile.gpu
+++ b/tests/ci_build/Dockerfile.gpu
@@ -23,7 +23,7 @@ RUN bash /install/ubuntu_install_sphinx.sh
 
 # Fix recommonmark to latest version
 RUN git clone https://github.com/rtfd/recommonmark
-RUN cd recommonmark; python setup.py install
+RUN cd recommonmark; python3 setup.py install
 
 # Enable doxygen for c++ doc build
 RUN apt-get update && apt-get install -y doxygen graphviz libprotobuf-dev protobuf-compiler
@@ -40,9 +40,6 @@ RUN bash /install/ubuntu_install_rocm.sh
 COPY install/ubuntu_install_opengl.sh /install/ubuntu_install_opengl.sh
 RUN bash /install/ubuntu_install_opengl.sh
 
-COPY install/ubuntu_install_vulkan.sh /install/ubuntu_install_vulkan.sh
-RUN bash /install/ubuntu_install_vulkan.sh
-
 
 # DL Frameworks
 COPY install/ubuntu_install_mxnet.sh /install/ubuntu_install_mxnet.sh
@@ -60,7 +57,11 @@ RUN bash /install/ubuntu_install_darknet.sh
 COPY install/ubuntu_install_onnx.sh /install/ubuntu_install_onnx.sh
 RUN bash /install/ubuntu_install_onnx.sh
 
-RUN pip install Pillow
+RUN pip3 install Pillow
+
+# disable vulkan for now
+# COPY install/ubuntu_install_vulkan.sh /install/ubuntu_install_vulkan.sh
+# RUN bash /install/ubuntu_install_vulkan.sh
 
 # Environment variables
 ENV PATH=/usr/local/nvidia/bin:${PATH}
diff --git a/tests/ci_build/Dockerfile.lint b/tests/ci_build/Dockerfile.lint
index 4ba4ca3be294..be869d8e9faf 100644
--- a/tests/ci_build/Dockerfile.lint
+++ b/tests/ci_build/Dockerfile.lint
@@ -1,6 +1,8 @@
 # For lint test
 FROM ubuntu:16.04
 
-RUN apt-get update && apt-get install -y python-pip sudo
+RUN apt-get update && apt-get install -y sudo wget
+COPY install/ubuntu_install_python.sh /install/ubuntu_install_python.sh
+RUN bash /install/ubuntu_install_python.sh
 RUN apt-get install -y doxygen graphviz
-RUN pip install cpplint pylint
+RUN pip3 install cpplint pylint
diff --git a/tests/ci_build/install/ubuntu_install_coreml.sh b/tests/ci_build/install/ubuntu_install_coreml.sh
index 3cfd6c293590..4b0fd126c61d 100644
--- a/tests/ci_build/install/ubuntu_install_coreml.sh
+++ b/tests/ci_build/install/ubuntu_install_coreml.sh
@@ -1 +1 @@
-pip2 install coremltools
+pip3 install coremltools
diff --git a/tests/ci_build/install/ubuntu_install_keras.sh b/tests/ci_build/install/ubuntu_install_keras.sh
index 9730d83bf469..33bc38c80972 100644
--- a/tests/ci_build/install/ubuntu_install_keras.sh
+++ b/tests/ci_build/install/ubuntu_install_keras.sh
@@ -1 +1,2 @@
 pip2 install keras tensorflow h5py
+pip3 install keras tensorflow h5py
diff --git a/tests/ci_build/install/ubuntu_install_mxnet.sh b/tests/ci_build/install/ubuntu_install_mxnet.sh
index ebf641e2a910..0e7e9e3939a8 100644
--- a/tests/ci_build/install/ubuntu_install_mxnet.sh
+++ b/tests/ci_build/install/ubuntu_install_mxnet.sh
@@ -1,2 +1 @@
-pip2 install mxnet
 pip3 install mxnet
diff --git a/tests/ci_build/install/ubuntu_install_python.sh b/tests/ci_build/install/ubuntu_install_python.sh
index c6f6f75c564e..a34019e1003e 100644
--- a/tests/ci_build/install/ubuntu_install_python.sh
+++ b/tests/ci_build/install/ubuntu_install_python.sh
@@ -1,5 +1,12 @@
 # install python and pip, don't modify this, modify install_python_package.sh
-apt-get update && apt-get install -y python-pip python-dev python3-dev
+apt-get update && apt-get install -y python-dev
 
-# the version of the pip shipped with ubuntu may be too lower, install a recent version here
-cd /tmp && wget https://bootstrap.pypa.io/get-pip.py && python3 get-pip.py && python2 get-pip.py
+# python 3.6
+apt-get update && yes | apt-get install software-properties-common
+add-apt-repository ppa:jonathonf/python-3.6 &&\
+    apt-get update && apt-get install -y python-pip python-dev python3.6 python3.6-dev
+
+rm -f /usr/bin/python3 && ln -s /usr/bin/python3.6 /usr/bin/python3
+
+# Install pip
+cd /tmp && wget https://bootstrap.pypa.io/get-pip.py && python2 get-pip.py && python3.6 get-pip.py
diff --git a/tests/ci_build/install/ubuntu_install_python_package.sh b/tests/ci_build/install/ubuntu_install_python_package.sh
index fbed2e1904cd..4f1518cb1abd 100644
--- a/tests/ci_build/install/ubuntu_install_python_package.sh
+++ b/tests/ci_build/install/ubuntu_install_python_package.sh
@@ -1,3 +1,3 @@
 # install libraries for python package on ubuntu
 pip2 install nose pylint numpy nose-timer cython decorator scipy tornado
-pip3 install nose pylint numpy nose-timer cython decorator scipy tornado
+pip3 install nose pylint numpy nose-timer cython decorator scipy tornado typed_ast
diff --git a/tests/ci_build/install/ubuntu_install_sphinx.sh b/tests/ci_build/install/ubuntu_install_sphinx.sh
index 30d4596aabf2..eabae1aac4fd 100644
--- a/tests/ci_build/install/ubuntu_install_sphinx.sh
+++ b/tests/ci_build/install/ubuntu_install_sphinx.sh
@@ -1 +1 @@
-pip install sphinx sphinx-gallery sphinx_rtd_theme matplotlib Image commonmark>=0.7.3 docutils>=0.11
+pip3 install sphinx sphinx-gallery sphinx_rtd_theme matplotlib Image commonmark>=0.7.3 docutils>=0.11
diff --git a/tests/scripts/task_clean.sh b/tests/scripts/task_clean.sh
index 68c04c332a34..815680bfdc0c 100755
--- a/tests/scripts/task_clean.sh
+++ b/tests/scripts/task_clean.sh
@@ -1,3 +1,3 @@
 #!/bin/bash
 echo "Cleanup data..."
-rm -rf $1
+cd $1 && rm -rf Cmake* && cd ..
diff --git a/tests/scripts/task_python_nnvm.sh b/tests/scripts/task_python_nnvm.sh
index 4d44ac0e14b3..968101c282a3 100755
--- a/tests/scripts/task_python_nnvm.sh
+++ b/tests/scripts/task_python_nnvm.sh
@@ -11,10 +11,10 @@ python -m nose -v nnvm/tests/python/compiler || exit -1
 python3 -m nose -v nnvm/tests/python/compiler || exit -1
 
 echo "Running ONNX frontend test..."
-python -m nose -v nnvm/tests/python/frontend/onnx || exit -1
+python3 -m nose -v nnvm/tests/python/frontend/onnx || exit -1
 
 echo "Running MXNet frontend test..."
-python -m nose -v nnvm/tests/python/frontend/mxnet || exit -1
+python3 -m nose -v nnvm/tests/python/frontend/mxnet || exit -1
 
 echo "Running Keras frontend test..."
-python -m nose -v nnvm/tests/python/frontend/keras || exit -1
+python3 -m nose -v nnvm/tests/python/frontend/keras || exit -1

From 66fab7e16f073dcd1a665a163a48d05e39578160 Mon Sep 17 00:00:00 2001
From: Tatsuya Nishiyama <nishiyama.tatsuya@lab.ntt.co.jp>
Date: Tue, 12 Jun 2018 08:43:05 +0900
Subject: [PATCH 703/949] Fix the gemm conversion in onnx frontend (#1241)

---
 nnvm/python/nnvm/frontend/onnx.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/nnvm/python/nnvm/frontend/onnx.py b/nnvm/python/nnvm/frontend/onnx.py
index bd5a0f4df1c7..129c5089caee 100644
--- a/nnvm/python/nnvm/frontend/onnx.py
+++ b/nnvm/python/nnvm/frontend/onnx.py
@@ -186,6 +186,7 @@ def _impl_v1(cls, inputs, attr, params):
             inputs[0] = _sym.transpose(inputs[0], axes=(1, 0))
         if not transB:
             inputs[1] = _sym.transpose(inputs[1], axes=(1, 0))
+        inputs[0] = _sym.flatten(inputs[0])
         return _sym.dense(
             alpha * inputs[0], inputs[1], beta * inputs[2], units=channels)
 

From eb2abb4e1d5461e6f4d21278ebdef1aac4a52d4a Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 11 Jun 2018 20:26:14 -0700
Subject: [PATCH 704/949] Community guideline in effect (#1261)

---
 CONTRIBUTORS.md              | 56 +++++++++---------------------------
 docs/contribute/community.md | 41 ++++++++++++++++++++++++++
 docs/contribute/index.rst    |  1 +
 3 files changed, 55 insertions(+), 43 deletions(-)
 create mode 100644 docs/contribute/community.md

diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index fd3a69a07e1d..faca8155645c 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -1,49 +1,17 @@
-Contributors of TVM
-===================
-TVM adopts Apache style committer model.
-The package is developed and used by the community.
+TVM Contributors
+================
+TVM adopts the Apache style model and governs by merit. We believe that it is important to create an inclusive community where everyone can use,
+contribute to, and influence the direction of the project. We actively invite contributors who have earned the merit to be part of the development community.
 
-We actively seek committers that come from community contributors who:
-- Made substantial contributions to the project.
-  - All forms of contributions are valued (see detail in next section).
-- Maintain quality standards in contributions
-  - e.g. consistently made contributions without requiring extensive
-    rewrites prior to being merged.
-- Willing to spend time on maintaining and lead the project.
+See the [community structure document](http://tvm.ai/contribute/community.html) for the explanation of community structure and contribution guidelines.
 
-Contributions
--------------
-We value all forms of contributions, here is a non-comprehensive
-list of contributions that are welcomed
-
-- Documentation and usage examples
-- Community participation, answering questions and issues.
-- Code readability and developer guide
-  - We welcome contributions that add code comments
-    to improve readability
-  - We also welcome contributions to docs to explain the
-    design choices of the internal.
-- Test cases to make the codebase more robust
-- Tutorials, blog posts, talks that promote the project.
-
-How to Contribute
------------------
-See [Contributor guide](http://tvm.ai/contribute/) on tips for contributions.
-
-
-Committers
-----------
-Committers are people who have made substantial contribution to the project and granted write access to the project.
-- [Tianqi Chen](https://github.com/tqchen), University of Washington
+## Committers
+- [Tianqi Chen](https://github.com/tqchen) (PMC), University of Washington
 - [Thierry Moreau](http://homes.cs.washington.edu/~moreau/), University of Washington
-- [Haichen Shen](http://homes.cs.washington.edu/~haichen/), University of Washington
 - [Ziheng Jiang](https://github.com/ZihengJiang), Fudan University
+- [Haichen Shen](http://homes.cs.washington.edu/~haichen/), University of Washington
 
-Code Owners
------------
-[Code owners](CODEOWNERS) are people who make substantial contribution to a module
-and are qualified to lead development and review changes of the owned module.
-
+## Code Owners
 - [Aditya Atluri](https://github.com/adityaatluri) ROCM
 - [Leyuan Wang](https://github.com/Laurawly) TOPI
 - [Yuwei Hu](https://github.com/Huyuwei) TOPI
@@ -51,9 +19,11 @@ and are qualified to lead development and review changes of the owned module.
 - [Zhixun Tan](https://github.com/phisiart) OpenGL/WebGL backend
 - [Nick Hynes](https://github.com/nhynes) SGX and secured computing
 
+## Reviewers
+- [Eddie Yan](https://github.com/eqy)
+- [Lianmin Zheng](https://github.com/merrymercy)
 
-List of Contributors
---------------------
+## List of Contributors
 - [Full List of Contributors](https://github.com/dmlc/tvm/graphs/contributors)
   - To contributors: please add your name to the list.
 - [Qiao Zhang](https://github.com/zhangqiaorjc)
diff --git a/docs/contribute/community.md b/docs/contribute/community.md
new file mode 100644
index 000000000000..269aeb11d57d
--- /dev/null
+++ b/docs/contribute/community.md
@@ -0,0 +1,41 @@
+# TVM Community Structure
+
+TVM adopts the Apache style model and governs by merit. We believe that it is important to create an inclusive community where everyone can use, contribute to, and influence the direction of the project. We actively invite contributors who have earned the merit to be part of the development community. There are several roles in the community:
+
+- Project Management Committee(PMC) Small group of active committers that moderate the discussion, RFC, manage project releases.
+- Committer Individual who has made substantial contributions to the project and is granted write access to the project and oversees the general direction of the projects.
+- Code Owner Individual who is responsible for a specific area of the codebase.
+- Reviewer Individual who is qualified to review for a specific area of the codebase.
+- Contributor Anyone who contributes to the project.
+
+This document explains responsibility and criteria for each role.
+See [CONTRIBUTORS.md](https://github.com/dmlc/tvm/blob/master/CONTRIBUTORS.md) for the current list of contributors and their roles.
+
+
+## Project Management Committee
+
+The PMC consists of a small group of active committers that moderate the discussion, provide mentorship to committers and code owners and manage the project release. PMC members need to actively manage the general project directions. Note that most major design choices and proposed changes should reach consensus among committers.
+
+## Committer
+
+Committers are individuals who are granted the write access to the project. Committers oversee the general project directions and participates in the evaluation of the RFCs involving major design changes. We look for the following traits from potential comitters
+Own at least one module in the project
+Good understanding of general project structure, demonstrated by discussion over RFCs, code reviews and proposals of new features
+Active history of code reviews that demonstrate a good technical ability
+Contribution history of high-quality documentation and tutorials to the promote project
+History of creating clean, maintainable code and including good test cases.
+New committers are nominated by current committers from current code owners by providing evidence of these perspectives.
+
+## Code Owner
+
+A code owner is an individual who is responsible for a specific area of the code-base. Code owners are responsible for the areas they are in charge of and oversee the code review process of the corresponding module. Changes to a specific area need to be approved by one of its owners in order to be merged. Once a pull request is approved by the designated code owner, the code can be directly merged into the repo.
+
+Code owners are promoted from contributors. In order to become code owner, one needs to make substantial contributions to a module. A candidate for code owner also needs to demonstrate good code quality, testing, and documentation in specific areas. These traits of code owners are essential for a high quality and healthy codebase. These traits are evaluated by the contributor’s participation in the project, including, but not limited to:
+High-quality, readable code contributions indicated by pull requests that can be merged without a substantial code review
+Good coverage of tests and documentation in the contributions
+Informative code reviews to help other contributors that adhere to a good standard, spot problems in contributions etc.
+Active participation in the discussion forum
+
+## Reviewer
+
+A reviewer is an individual who actively contributed to the project and is willing to participate in the code review of new contributions. We invite reviewers from active contributors. The reviewer invitation will be sent to the potential reviewer’s email, so please log in to the discussion forum so that we can know which email address we could send an invitation to. Code owners should actively seek reviews from reviewers. High-quality code reviews prevent technical debt for long-term and are crucial to the success of the project.  A pull request to the project has to be reviewed by a reviewer in order to be merged.  The contributor and code owner can request code reviews from multiple reviewers. Code owner can decide if the code can be merged in case by case if some of the reviewers did not respond in time(e.g. a week) and existing reviews are sufficient. A good way to get quick reviews from others in the community is to also help to review other’s code -- so they can do the same in return.
diff --git a/docs/contribute/index.rst b/docs/contribute/index.rst
index 7b757aac3db0..ea358e287f60 100644
--- a/docs/contribute/index.rst
+++ b/docs/contribute/index.rst
@@ -23,6 +23,7 @@ Here are guidelines for contributing to various aspect of the project:
 .. toctree::
    :maxdepth: 2
 
+   community
    code_review
    document
    code_guide

From 3574c20734f012f2045ae8fff3628ab1f5222f87 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 11 Jun 2018 21:24:44 -0700
Subject: [PATCH 705/949] Update README.md

---
 README.md | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/README.md b/README.md
index 604d30ad6e56..7d4d6d8c26bb 100644
--- a/README.md
+++ b/README.md
@@ -21,10 +21,7 @@ License
 Contribute to TVM
 -----------------
 TVM adopts apache committer model, we aim to create an open source project that is maintained and owned by the community.
-
-- [Contributor Guide](docs/how_to/contribute.md)
-- Please add your name to [CONTRIBUTORS.md](CONTRIBUTORS.md)
-- Please also update [NEWS.md](NEWS.md) on changes and improvements in API and codes.
+Checkout the [Contributor Guide](http://docs.tvm.ai/contribute/)
 
 Acknowledgement
 ---------------

From dcdec18e3c971eb87d2a595b5d963939e1b72155 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 11 Jun 2018 21:35:35 -0700
Subject: [PATCH 706/949] [DOCS] Fix links (#1263)

---
 CONTRIBUTORS.md                                 |  2 +-
 docs/contribute/{community.md => community.rst} | 17 +++++++++++------
 2 files changed, 12 insertions(+), 7 deletions(-)
 rename docs/contribute/{community.md => community.rst} (93%)

diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index faca8155645c..932b27d9825b 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -3,7 +3,7 @@ TVM Contributors
 TVM adopts the Apache style model and governs by merit. We believe that it is important to create an inclusive community where everyone can use,
 contribute to, and influence the direction of the project. We actively invite contributors who have earned the merit to be part of the development community.
 
-See the [community structure document](http://tvm.ai/contribute/community.html) for the explanation of community structure and contribution guidelines.
+See the [community structure document](http://docs.tvm.ai/contribute/community.html) for the explanation of community structure and contribution guidelines.
 
 ## Committers
 - [Tianqi Chen](https://github.com/tqchen) (PMC), University of Washington
diff --git a/docs/contribute/community.md b/docs/contribute/community.rst
similarity index 93%
rename from docs/contribute/community.md
rename to docs/contribute/community.rst
index 269aeb11d57d..3ddc7d2030d3 100644
--- a/docs/contribute/community.md
+++ b/docs/contribute/community.rst
@@ -1,4 +1,5 @@
-# TVM Community Structure
+TVM Community Structure
+=======================
 
 TVM adopts the Apache style model and governs by merit. We believe that it is important to create an inclusive community where everyone can use, contribute to, and influence the direction of the project. We actively invite contributors who have earned the merit to be part of the development community. There are several roles in the community:
 
@@ -9,14 +10,16 @@ TVM adopts the Apache style model and governs by merit. We believe that it is im
 - Contributor Anyone who contributes to the project.
 
 This document explains responsibility and criteria for each role.
-See [CONTRIBUTORS.md](https://github.com/dmlc/tvm/blob/master/CONTRIBUTORS.md) for the current list of contributors and their roles.
+See `CONTRIBUTORS.md <https://github.com/dmlc/tvm/blob/master/CONTRIBUTORS.md>`_ for the current list of contributors and their roles.
 
 
-## Project Management Committee
+Project Management Committee
+----------------------------
 
 The PMC consists of a small group of active committers that moderate the discussion, provide mentorship to committers and code owners and manage the project release. PMC members need to actively manage the general project directions. Note that most major design choices and proposed changes should reach consensus among committers.
 
-## Committer
+Committer
+---------
 
 Committers are individuals who are granted the write access to the project. Committers oversee the general project directions and participates in the evaluation of the RFCs involving major design changes. We look for the following traits from potential comitters
 Own at least one module in the project
@@ -26,7 +29,8 @@ Contribution history of high-quality documentation and tutorials to the promote
 History of creating clean, maintainable code and including good test cases.
 New committers are nominated by current committers from current code owners by providing evidence of these perspectives.
 
-## Code Owner
+Code Owner
+----------
 
 A code owner is an individual who is responsible for a specific area of the code-base. Code owners are responsible for the areas they are in charge of and oversee the code review process of the corresponding module. Changes to a specific area need to be approved by one of its owners in order to be merged. Once a pull request is approved by the designated code owner, the code can be directly merged into the repo.
 
@@ -36,6 +40,7 @@ Good coverage of tests and documentation in the contributions
 Informative code reviews to help other contributors that adhere to a good standard, spot problems in contributions etc.
 Active participation in the discussion forum
 
-## Reviewer
+Reviewer
+--------
 
 A reviewer is an individual who actively contributed to the project and is willing to participate in the code review of new contributions. We invite reviewers from active contributors. The reviewer invitation will be sent to the potential reviewer’s email, so please log in to the discussion forum so that we can know which email address we could send an invitation to. Code owners should actively seek reviews from reviewers. High-quality code reviews prevent technical debt for long-term and are crucial to the success of the project.  A pull request to the project has to be reviewed by a reviewer in order to be merged.  The contributor and code owner can request code reviews from multiple reviewers. Code owner can decide if the code can be merged in case by case if some of the reviewers did not respond in time(e.g. a week) and existing reviews are sufficient. A good way to get quick reviews from others in the community is to also help to review other’s code -- so they can do the same in return.

From 790b015e92d451649ed884b22e2512a2680c83b2 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 11 Jun 2018 22:09:27 -0700
Subject: [PATCH 707/949] Add Item list to community guide (#1264)

---
 docs/contribute/code_review.rst |  6 ++++++
 docs/contribute/community.rst   | 25 +++++++++++++++----------
 2 files changed, 21 insertions(+), 10 deletions(-)

diff --git a/docs/contribute/code_review.rst b/docs/contribute/code_review.rst
index efbb77c67a7a..8d128163e776 100644
--- a/docs/contribute/code_review.rst
+++ b/docs/contribute/code_review.rst
@@ -45,3 +45,9 @@ so the lessons can be shared to all the community.
 Respect each other
 ------------------
 The code reviewers and contributors are paying the most precious currencies in the world -- time. We are volunteers in the community to spend the time to build good code, help each other, learn and have fun hacking.
+
+Approve Code Reviews
+--------------------
+The contributor and code owner can request code reviews from multiple reviewers.
+Code owner can decide if the code can be merged in case by case if some of the reviewers did not respond in time(e.g. a week)
+and existing reviews are sufficient. Remember to approve your code reviews when your comments are addressed.
diff --git a/docs/contribute/community.rst b/docs/contribute/community.rst
index 3ddc7d2030d3..49cc26828319 100644
--- a/docs/contribute/community.rst
+++ b/docs/contribute/community.rst
@@ -22,11 +22,13 @@ Committer
 ---------
 
 Committers are individuals who are granted the write access to the project. Committers oversee the general project directions and participates in the evaluation of the RFCs involving major design changes. We look for the following traits from potential comitters
-Own at least one module in the project
-Good understanding of general project structure, demonstrated by discussion over RFCs, code reviews and proposals of new features
-Active history of code reviews that demonstrate a good technical ability
-Contribution history of high-quality documentation and tutorials to the promote project
-History of creating clean, maintainable code and including good test cases.
+
+- Own at least one module in the project
+- Good understanding of general project structure, demonstrated by discussion over RFCs, code reviews and proposals of new features
+- Active history of code reviews that demonstrate a good technical ability
+- Contribution history of high-quality documentation and tutorials to the promote project
+- History of creating clean, maintainable code and including good test cases.
+
 New committers are nominated by current committers from current code owners by providing evidence of these perspectives.
 
 Code Owner
@@ -35,12 +37,15 @@ Code Owner
 A code owner is an individual who is responsible for a specific area of the code-base. Code owners are responsible for the areas they are in charge of and oversee the code review process of the corresponding module. Changes to a specific area need to be approved by one of its owners in order to be merged. Once a pull request is approved by the designated code owner, the code can be directly merged into the repo.
 
 Code owners are promoted from contributors. In order to become code owner, one needs to make substantial contributions to a module. A candidate for code owner also needs to demonstrate good code quality, testing, and documentation in specific areas. These traits of code owners are essential for a high quality and healthy codebase. These traits are evaluated by the contributor’s participation in the project, including, but not limited to:
-High-quality, readable code contributions indicated by pull requests that can be merged without a substantial code review
-Good coverage of tests and documentation in the contributions
-Informative code reviews to help other contributors that adhere to a good standard, spot problems in contributions etc.
-Active participation in the discussion forum
+
+- High-quality, readable code contributions indicated by pull requests that can be merged without a substantial code review
+- Good coverage of tests and documentation in the contributions
+- Informative code reviews to help other contributors that adhere to a good standard, spot problems in contributions etc.
+- Active participation in the discussion forum
 
 Reviewer
 --------
 
-A reviewer is an individual who actively contributed to the project and is willing to participate in the code review of new contributions. We invite reviewers from active contributors. The reviewer invitation will be sent to the potential reviewer’s email, so please log in to the discussion forum so that we can know which email address we could send an invitation to. Code owners should actively seek reviews from reviewers. High-quality code reviews prevent technical debt for long-term and are crucial to the success of the project.  A pull request to the project has to be reviewed by a reviewer in order to be merged.  The contributor and code owner can request code reviews from multiple reviewers. Code owner can decide if the code can be merged in case by case if some of the reviewers did not respond in time(e.g. a week) and existing reviews are sufficient. A good way to get quick reviews from others in the community is to also help to review other’s code -- so they can do the same in return.
+A reviewer is an individual who actively contributed to the project and is willing to participate in the code review of new contributions. We invite reviewers from active contributors. The reviewer invitation will be sent to the potential reviewer’s email, so please log in to the discussion forum so that we can know which email address we could send an invitation to.
+We actively seek reviews from reviewers. High-quality code reviews prevent technical debt for long-term and are crucial to the success of the project.
+A pull request to the project has to be reviewed by a reviewer in order to be merged.

From 0ef93dcb95031438a3b2ad8ced2d75881cb345d6 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 11 Jun 2018 22:10:26 -0700
Subject: [PATCH 708/949] Update code_review.rst

---
 docs/contribute/code_review.rst | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/docs/contribute/code_review.rst b/docs/contribute/code_review.rst
index 8d128163e776..b6dea53001f9 100644
--- a/docs/contribute/code_review.rst
+++ b/docs/contribute/code_review.rst
@@ -46,8 +46,8 @@ Respect each other
 ------------------
 The code reviewers and contributors are paying the most precious currencies in the world -- time. We are volunteers in the community to spend the time to build good code, help each other, learn and have fun hacking.
 
-Approve Code Reviews
---------------------
+Approve Changes
+---------------
 The contributor and code owner can request code reviews from multiple reviewers.
-Code owner can decide if the code can be merged in case by case if some of the reviewers did not respond in time(e.g. a week)
-and existing reviews are sufficient. Remember to approve your code reviews when your comments are addressed.
+Code owner can decide if the code can be merged in case by case if some of the reviewers did not respond in time(e.g. a week) and existing reviews are sufficient. 
+Remember to approve changes when your comments are addressed in a code review.

From aed5ecd32ff0dea817104361a3a977119931307f Mon Sep 17 00:00:00 2001
From: Tatsuya Nishiyama <nishiyama.tatsuya@lab.ntt.co.jp>
Date: Wed, 13 Jun 2018 02:30:31 +0900
Subject: [PATCH 709/949] Fix the building error in android_deploy (#1262)

* Fix a link in android_deploy/README.md and a error while building android_deploy.

* revert and change APP_STL in Application.mk
---
 apps/android_deploy/README.md                       | 2 +-
 apps/android_deploy/app/src/main/jni/Application.mk | 2 +-
 src/runtime/thread_pool.cc                          | 2 ++
 3 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/apps/android_deploy/README.md b/apps/android_deploy/README.md
index 94cc9984b0c2..801ca8bdf95c 100644
--- a/apps/android_deploy/README.md
+++ b/apps/android_deploy/README.md
@@ -99,7 +99,7 @@ If everything goes well, you will find compile tools in `/opt/android-toolchain-
 
 ### Place compiled model on Android application assets folder
 
-Follow instruction to get compiled version model for android target [here.](https://tvm.ai/deploy/android.html)
+Follow instruction to get compiled version model for android target [here.](http://docs.tvm.ai/deploy/android.html)
 
 Copied these compiled model deploy_lib.so, deploy_graph.json and deploy_param.params to apps/android_deploy/app/src/main/assets/ and modify TVM flavor changes on [java](https://github.com/dmlc/tvm/blob/master/apps/android_deploy/app/src/main/java/ml/dmlc/tvm/android/demo/MainActivity.java#L81)
 
diff --git a/apps/android_deploy/app/src/main/jni/Application.mk b/apps/android_deploy/app/src/main/jni/Application.mk
index 01cad9b783a7..8e81a8d6a81c 100644
--- a/apps/android_deploy/app/src/main/jni/Application.mk
+++ b/apps/android_deploy/app/src/main/jni/Application.mk
@@ -8,7 +8,7 @@ endif
 
 include $(config)
 
-APP_STL := gnustl_static
+APP_STL := c++_static
 
 APP_CPPFLAGS += -DDMLC_LOG_STACK_TRACE=0 -DTVM4J_ANDROID=1 -std=c++11 -Oz -frtti
 ifeq ($(USE_OPENCL), 1)                                                                                                                                             
diff --git a/src/runtime/thread_pool.cc b/src/runtime/thread_pool.cc
index b7ea6f5b81d3..5e5c08509a44 100644
--- a/src/runtime/thread_pool.cc
+++ b/src/runtime/thread_pool.cc
@@ -69,6 +69,8 @@ class ParallelLauncher {
       tvm::runtime::threading::Yield();
     }
     if (!has_error_.load()) return 0;
+    // the following is intended to use string due to
+    // security issue raised in SGX backend
     std::string err("");
     for (size_t i = 0; i < par_errors_.size(); ++i) {
       if (par_errors_[i].length() != 0) {

From 11f737ff4af40bf88077ca886ee7e31f04a26409 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 12 Jun 2018 11:24:51 -0700
Subject: [PATCH 710/949] [MAINTAINER] Add Pariksheet Pinjari as reviewer
 (#1266)

---
 CONTRIBUTORS.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 932b27d9825b..112d8372f455 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -20,6 +20,7 @@ See the [community structure document](http://docs.tvm.ai/contribute/community.h
 - [Nick Hynes](https://github.com/nhynes) SGX and secured computing
 
 ## Reviewers
+- [Pariksheet Pinjari](https://github.com/PariksheetPinjari909)
 - [Eddie Yan](https://github.com/eqy)
 - [Lianmin Zheng](https://github.com/merrymercy)
 

From 3160f5ffd2de8230a3c9bb33ad90ab5544daa521 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 12 Jun 2018 13:31:16 -0700
Subject: [PATCH 711/949] [INTRIN] Add support for floor and ceil (#1267)

---
 cmake/config.cmake                     |  3 +++
 include/tvm/ir_operator.h              |  2 ++
 python/tvm/intrin.py                   | 32 ++++++++++++++++++++++++
 src/codegen/intrin_rule_cuda.cc        |  6 +++++
 src/codegen/intrin_rule_metal.cc       |  6 +++++
 src/codegen/intrin_rule_opencl.cc      |  6 +++++
 src/codegen/intrin_rule_opengl.cc      |  6 +++++
 src/codegen/llvm/intrin_rule_llvm.cc   |  6 +++++
 src/codegen/llvm/intrin_rule_rocm.cc   |  6 +++++
 src/codegen/spirv/build_vulkan.cc      |  3 ---
 src/codegen/spirv/codegen_spirv.cc     |  5 ----
 src/codegen/spirv/intrin_rule_spirv.cc | 13 +++++++---
 src/codegen/spirv/ir_builder.cc        |  5 ----
 topi/include/topi/elemwise.h           |  2 ++
 topi/python/topi/math.py               | 34 ++++++++++++++++++++++++++
 topi/tests/python/test_topi_math.py    | 18 ++++++++------
 16 files changed, 128 insertions(+), 25 deletions(-)

diff --git a/cmake/config.cmake b/cmake/config.cmake
index b4896da79ef1..db7d800e918a 100644
--- a/cmake/config.cmake
+++ b/cmake/config.cmake
@@ -38,6 +38,9 @@ set(USE_METAL OFF)
 # Whether enable Vulkan runtime
 set(USE_VULKAN OFF)
 
+# Whether enable OpenGL runtime
+set(USE_OPENGL OFF)
+
 # Whether enable RPC runtime
 set(USE_RPC ON)
 
diff --git a/include/tvm/ir_operator.h b/include/tvm/ir_operator.h
index ee349c092ee6..9d72b655c484 100644
--- a/include/tvm/ir_operator.h
+++ b/include/tvm/ir_operator.h
@@ -53,6 +53,8 @@ TVM_DECLARE_INTRIN_UNARY(tanh);
 TVM_DECLARE_INTRIN_UNARY(sigmoid);
 TVM_DECLARE_INTRIN_UNARY(sqrt);
 TVM_DECLARE_INTRIN_UNARY(log);
+TVM_DECLARE_INTRIN_UNARY(floor);
+TVM_DECLARE_INTRIN_UNARY(ceil);
 
 inline Expr pow(Expr x, Expr y) {
   return ir::Call::make(x.type(), "pow", { x, y }, ir::Call::PureIntrinsic);
diff --git a/python/tvm/intrin.py b/python/tvm/intrin.py
index f8f65e25aa68..9be7502b26ee 100644
--- a/python/tvm/intrin.py
+++ b/python/tvm/intrin.py
@@ -233,6 +233,38 @@ def sqrt(x):
     return call_pure_intrin(x.dtype, "sqrt", x)
 
 
+def floor(x):
+    """Take floor of float input x.
+
+    Parameters
+    ----------
+    x : Expr
+        Input argument.
+
+    Returns
+    -------
+    y : Expr
+        The result.
+    """
+    return call_pure_intrin(x.dtype, "floor", x)
+
+
+def ceil(x):
+    """Take ceil of float input x.
+
+    Parameters
+    ----------
+    x : Expr
+        Input argument.
+
+    Returns
+    -------
+    y : Expr
+        The result.
+    """
+    return call_pure_intrin(x.dtype, "ceil", x)
+
+
 def power(x, y):
     """x power y
 
diff --git a/src/codegen/intrin_rule_cuda.cc b/src/codegen/intrin_rule_cuda.cc
index 1d199fe5af28..dccbcf8ca232 100644
--- a/src/codegen/intrin_rule_cuda.cc
+++ b/src/codegen/intrin_rule_cuda.cc
@@ -55,6 +55,12 @@ struct CUDAShuffle {
   }
 };
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.cuda.floor")
+.set_body(DispatchExtern<CUDAMath>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.cuda.ceil")
+.set_body(DispatchExtern<CUDAMath>);
+
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.cuda.exp")
 .set_body(DispatchExtern<CUDAFastMath>);
 
diff --git a/src/codegen/intrin_rule_metal.cc b/src/codegen/intrin_rule_metal.cc
index b0e41770ebff..3de5cafc69c6 100644
--- a/src/codegen/intrin_rule_metal.cc
+++ b/src/codegen/intrin_rule_metal.cc
@@ -9,6 +9,12 @@ namespace tvm {
 namespace codegen {
 namespace intrin {
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.metal.floor")
+.set_body(DispatchExtern<Direct>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.metal.ceil")
+.set_body(DispatchExtern<Direct>);
+
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.metal.exp")
 .set_body(DispatchExtern<Direct>);
 
diff --git a/src/codegen/intrin_rule_opencl.cc b/src/codegen/intrin_rule_opencl.cc
index b8b2412215d1..cdb432e5462f 100644
--- a/src/codegen/intrin_rule_opencl.cc
+++ b/src/codegen/intrin_rule_opencl.cc
@@ -9,6 +9,12 @@ namespace tvm {
 namespace codegen {
 namespace intrin {
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.opencl.floor")
+.set_body(DispatchExtern<Direct>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.opencl.ceil")
+.set_body(DispatchExtern<Direct>);
+
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.opencl.exp")
 .set_body(DispatchExtern<Direct>);
 
diff --git a/src/codegen/intrin_rule_opengl.cc b/src/codegen/intrin_rule_opengl.cc
index 6ae2ee5d2b4e..e9728a25b40c 100644
--- a/src/codegen/intrin_rule_opengl.cc
+++ b/src/codegen/intrin_rule_opengl.cc
@@ -9,6 +9,12 @@ namespace tvm {
 namespace codegen {
 namespace intrin {
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.opengl.floor")
+.set_body(DispatchExtern<Direct>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.opengl.ceil")
+.set_body(DispatchExtern<Direct>);
+
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.opengl.exp")
 .set_body(DispatchExtern<Direct>);
 
diff --git a/src/codegen/llvm/intrin_rule_llvm.cc b/src/codegen/llvm/intrin_rule_llvm.cc
index 2e6bf061eb5e..e5eff1f67ff5 100644
--- a/src/codegen/llvm/intrin_rule_llvm.cc
+++ b/src/codegen/llvm/intrin_rule_llvm.cc
@@ -25,6 +25,12 @@ TVM_REGISTER_GLOBAL("tvm.intrin.rule.llvm.log")
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.llvm.sqrt")
 .set_body(DispatchLLVMPureIntrin<::llvm::Intrinsic::sqrt, 1>);
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.llvm.floor")
+.set_body(DispatchLLVMPureIntrin<::llvm::Intrinsic::floor, 1>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.llvm.ceil")
+.set_body(DispatchLLVMPureIntrin<::llvm::Intrinsic::ceil, 1>);
+
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.llvm.tanh")
 .set_body([](const TVMArgs& targs, TVMRetValue* rv) {
   Expr e = targs[0];
diff --git a/src/codegen/llvm/intrin_rule_rocm.cc b/src/codegen/llvm/intrin_rule_rocm.cc
index 38211db0b9b1..4546ee4f039b 100644
--- a/src/codegen/llvm/intrin_rule_rocm.cc
+++ b/src/codegen/llvm/intrin_rule_rocm.cc
@@ -26,6 +26,12 @@ inline void DispatchExternOCML(const TVMArgs& args, TVMRetValue* rv) {
 
 namespace llvm {
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.rocm.floor")
+.set_body(DispatchLLVMPureIntrin<::llvm::Intrinsic::floor, 1>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.rocm.ceil")
+.set_body(DispatchLLVMPureIntrin<::llvm::Intrinsic::ceil, 1>);
+
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.rocm.exp")
 .set_body(DispatchExternOCML);
 
diff --git a/src/codegen/spirv/build_vulkan.cc b/src/codegen/spirv/build_vulkan.cc
index 719b1f30e267..2d4b35daa006 100644
--- a/src/codegen/spirv/build_vulkan.cc
+++ b/src/codegen/spirv/build_vulkan.cc
@@ -3,8 +3,6 @@
  * \file build_vulkan.cc
  * \brief Build SPIRV block
  */
-#if TVM_VULKAN_RUNTIME
-
 // Use libspirv for parsing and validating code.
 #include <vulkan/libspirv.h>
 #include <dmlc/memory_io.h>
@@ -92,4 +90,3 @@ TVM_REGISTER_API("codegen.build_vulkan")
 
 }  // namespace codegen
 }  // namespace tvm
-#endif  // TVM_VULKAN_RUNTIME
diff --git a/src/codegen/spirv/codegen_spirv.cc b/src/codegen/spirv/codegen_spirv.cc
index d9a06ab13d34..b6582ec6c0d8 100644
--- a/src/codegen/spirv/codegen_spirv.cc
+++ b/src/codegen/spirv/codegen_spirv.cc
@@ -3,9 +3,6 @@
  * \file codegen_spirv.cc
  * \brief Generate SPIRV block
  */
-
-#if TVM_VULKAN_RUNTIME
-
 #include <tvm/ir.h>
 #include <tvm/ir_pass.h>
 #include "../codegen_common.h"
@@ -634,5 +631,3 @@ void CodeGenSPIRV::VisitStmt_(const ProducerConsumer* op) {
 
 }  // namespace codegen
 }  // namespace tvm
-
-#endif  // TVM_VULKAN_RUNTIME
diff --git a/src/codegen/spirv/intrin_rule_spirv.cc b/src/codegen/spirv/intrin_rule_spirv.cc
index cef98266ca9e..1f9c56c561f8 100644
--- a/src/codegen/spirv/intrin_rule_spirv.cc
+++ b/src/codegen/spirv/intrin_rule_spirv.cc
@@ -2,8 +2,6 @@
  *  Copyright (c) 2017 by Contributors
  * \file intrin_rule_spirv.cc
  */
-#if TVM_VULKAN_RUNTIME
-
 #include <tvm/packed_func_ext.h>
 #include <tvm/ir.h>
 #include <vulkan/GLSL.std.450.h>
@@ -31,6 +29,12 @@ inline void DispatchGLSLPureIntrin(const TVMArgs& targs, TVMRetValue* rv) {
       call->type, "spirv_glsl450", cargs, ir::Call::PureIntrinsic);
 }
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.vulkan.floor")
+.set_body(DispatchGLSLPureIntrin<GLSLstd450Floor>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.vulkan.ceil")
+.set_body(DispatchGLSLPureIntrin<GLSLstd450Ceil>);
+
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.vulkan.exp")
 .set_body(DispatchGLSLPureIntrin<GLSLstd450Exp>);
 
@@ -43,8 +47,9 @@ TVM_REGISTER_GLOBAL("tvm.intrin.rule.vulkan.sqrt")
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.vulkan.pow")
 .set_body(DispatchGLSLPureIntrin<GLSLstd450Pow>);
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.vulkan.tanh")
+.set_body(DispatchGLSLPureIntrin<GLSLstd450Tanh>);
+
 }  // namespace spirv
 }  // namespace codegen
 }  // namespace tvm
-
-#endif  // TVM_VULKAN_RUNTIME
diff --git a/src/codegen/spirv/ir_builder.cc b/src/codegen/spirv/ir_builder.cc
index 2bd1d0d5da7c..f3e044596e5c 100644
--- a/src/codegen/spirv/ir_builder.cc
+++ b/src/codegen/spirv/ir_builder.cc
@@ -3,9 +3,6 @@
  * \file ir_builder.cc
  * \brief IRBuilder for SPIRV block
  */
-
-#if TVM_VULKAN_RUNTIME
-
 #include "./ir_builder.h"
 
 namespace tvm {
@@ -555,5 +552,3 @@ Value IRBuilder::Select(Value cond, Value a, Value b) {
 }  // namespace spirv
 }  // namespace codegen
 }  // namespace tvm
-
-#endif  // TVM_VULKAN_RUNTIME
diff --git a/topi/include/topi/elemwise.h b/topi/include/topi/elemwise.h
index c3797197710b..7500ce0c66b0 100644
--- a/topi/include/topi/elemwise.h
+++ b/topi/include/topi/elemwise.h
@@ -29,6 +29,8 @@ TOPI_DECLARE_UNARY_OP(tanh);
 TOPI_DECLARE_UNARY_OP(sigmoid);
 TOPI_DECLARE_UNARY_OP(sqrt);
 TOPI_DECLARE_UNARY_OP(log);
+TOPI_DECLARE_UNARY_OP(floor);
+TOPI_DECLARE_UNARY_OP(ceil);
 
 /*!
 * \brief Creates an operation that returns identity of a given tensor
diff --git a/topi/python/topi/math.py b/topi/python/topi/math.py
index 2a1de9972bfe..588c306136d9 100644
--- a/topi/python/topi/math.py
+++ b/topi/python/topi/math.py
@@ -73,6 +73,40 @@ def tanh(x):
     return tvm.compute(x.shape, lambda *i: tvm.tanh(x(*i)))
 
 
+@tvm.tag_scope(tag=tag.ELEMWISE)
+def floor(x):
+    """Take floor of input x.
+
+    Parameters
+    ----------
+    x : tvm.Tensor
+        Input argument.
+
+    Returns
+    -------
+    y : tvm.Tensor
+        The result.
+    """
+    return tvm.compute(x.shape, lambda *i: tvm.floor(x(*i)))
+
+
+@tvm.tag_scope(tag=tag.ELEMWISE)
+def ceil(x):
+    """Take ceil of input x.
+
+    Parameters
+    ----------
+    x : tvm.Tensor
+        Input argument.
+
+    Returns
+    -------
+    y : tvm.Tensor
+        The result.
+    """
+    return tvm.compute(x.shape, lambda *i: tvm.ceil(x(*i)))
+
+
 @tvm.tag_scope(tag=tag.ELEMWISE)
 def log(x):
     """Take logarithm of input x.
diff --git a/topi/tests/python/test_topi_math.py b/topi/tests/python/test_topi_math.py
index dc3c015d4f25..b9305e6e6fdf 100644
--- a/topi/tests/python/test_topi_math.py
+++ b/topi/tests/python/test_topi_math.py
@@ -18,12 +18,11 @@ def test_ewise():
 
     shape = (20, 3)
 
-    def test_apply(func, name, f_numpy):
+    def test_apply(func, name, f_numpy, low, high):
         B = func(A)
         assert tuple(B.shape) == tuple(A.shape)
         assert B.op.body[0].name == name
-        a_np = np.random.uniform(low=1e-5, size=shape).astype(A.dtype)
-        a_np = np.abs(a_np)
+        a_np = np.random.uniform(low=low, high=high, size=shape).astype(A.dtype) * 10
         b_np = f_numpy(a_np)
 
         def check_device(device):
@@ -43,11 +42,14 @@ def check_device(device):
         for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan', 'llvm']:
             check_device(device)
 
-    test_apply(topi.exp, "exp", np.exp)
-    test_apply(topi.tanh, "tanh", np.tanh)
-    test_apply(topi.sigmoid, "sigmoid", lambda x:1/(1+np.exp(-x)))
-    test_apply(topi.log, "log", np.log)
-    test_apply(topi.sqrt, "sqrt", np.sqrt)
+
+    test_apply(topi.floor, "floor", np.floor, -100, 100)
+    test_apply(topi.ceil, "ceil", np.ceil, -100, 100)
+    test_apply(topi.exp, "exp", np.exp, -1, 1)
+    test_apply(topi.tanh, "tanh", np.tanh, -10, 10)
+    test_apply(topi.sigmoid, "sigmoid", lambda x:1/(1+np.exp(-x)), -1, 1)
+    test_apply(topi.log, "log", np.log, 0, 100)
+    test_apply(topi.sqrt, "sqrt", np.sqrt, 0, 100)
 
 if __name__ == "__main__":
     test_util()

From be29ac755febb32c16e9056cf008e53fcaf83745 Mon Sep 17 00:00:00 2001
From: Meghan Cowan <cowanmeg@cs.washington.edu>
Date: Tue, 12 Jun 2018 13:45:35 -0700
Subject: [PATCH 712/949] [CODEGEN] ARM Popcount lowering rule and codegen
 updates (#1235)

---
 src/codegen/llvm/codegen_arm.cc           | 82 +++++++++++++++++++++++
 src/codegen/llvm/codegen_llvm.cc          | 26 ++++++-
 src/codegen/llvm/llvm_module.cc           | 38 +++++++++--
 tests/python/unittest/test_codegen_arm.py | 30 +++++++++
 4 files changed, 171 insertions(+), 5 deletions(-)
 create mode 100644 tests/python/unittest/test_codegen_arm.py

diff --git a/src/codegen/llvm/codegen_arm.cc b/src/codegen/llvm/codegen_arm.cc
index b87b6ec88808..18a0eb54e182 100644
--- a/src/codegen/llvm/codegen_arm.cc
+++ b/src/codegen/llvm/codegen_arm.cc
@@ -18,8 +18,90 @@ class CodeGenARM final : public CodeGenCPU {
     native_vector_bits_ = 16 * 8;
     CodeGenCPU::InitTarget(tm);
   }
+  llvm::Value* CreateIntrinsic(const Call* op) override;
+
+ private:
+  Expr ARMPopcount(const Call* op);
 };
 
+llvm::Value* CodeGenARM::CreateIntrinsic(const Call* op) {
+  if (op->is_intrinsic("llvm_intrin")) {
+    llvm::Intrinsic::ID id = static_cast<llvm::Intrinsic::ID>(
+        op->args[0].as<UIntImm>()->value);
+    if (id == ::llvm::Intrinsic::ctpop) {
+      Expr e = ARMPopcount(op);
+      return CodeGenCPU::CreateIntrinsic(e.as<Call>());
+    }
+  }
+  return CodeGenCPU::CreateIntrinsic(op);
+}
+
+Expr CodeGenARM::ARMPopcount(const Call *call) {
+  using namespace ir;
+  const Expr& e = call->args[2];
+  ::llvm::Intrinsic::ID ctpop_id = ::llvm::Intrinsic::ctpop;
+  ::llvm::Intrinsic::ID vpaddlu_id = ::llvm::Intrinsic::arm_neon_vpaddlu;
+
+  // Fallback to default llvm lowering rule if input type not a full vector or half vector length
+  int total_size =  call->type.bits() * call->type.lanes();
+  if (!call->type.is_vector() || call->type.bits() == 8 ||
+     (total_size != 128 && total_size != 64)) {
+    Array<Expr> vcnt_args;
+    vcnt_args.push_back(ir::UIntImm::make(UInt(32), ctpop_id));
+    vcnt_args.push_back(ir::UIntImm::make(UInt(32), 1));
+    vcnt_args.push_back(e);
+    return ir::Call::make(call->type,  "llvm_intrin", vcnt_args, Call::PureIntrinsic);
+  }
+
+  // Popcount lowering rule:
+  // Reinterpret input vector as a vector of 8bit values and preform popcount
+  // Pairwise add between adjacent elements and double width with vpaddlu
+  // to return back to original input type
+
+  // Dvisions are always divisible (number of bits = 64 or 128)
+  Type uint8_type = Type(e.type().code(), 8, e.type().bits() * e.type().lanes() / 8);
+  Type uint16_type = Type(uint8_type.code(), 16, uint8_type.bits() * uint8_type.lanes() / 16);
+  Type uint32_type = Type(uint16_type.code(), 32, uint8_type.bits() * uint8_type.lanes() / 32);
+
+  // Interpret input as vector of 8bit values
+  Expr input8 = reinterpret(uint8_type, e);
+  // Popcount 8bit->8bit
+  const Call* c0 = input8.as<Call>();
+  CHECK(c0 != nullptr);
+  Array<Expr> vcnt8_args;
+  vcnt8_args.push_back(ir::UIntImm::make(UInt(32), ctpop_id));
+  vcnt8_args.push_back(ir::UIntImm::make(UInt(32), 1));
+  vcnt8_args.push_back(input8);
+  Expr vcnt8 = ir::Call::make(uint8_type,  "llvm_intrin", vcnt8_args, Call::PureIntrinsic);
+
+  // Accumulation 8->16bit
+  Array<Expr> vcnt16_args;
+  vcnt16_args.push_back(ir::UIntImm::make(UInt(32), vpaddlu_id));
+  vcnt16_args.push_back(ir::UIntImm::make(UInt(32), 1));
+  vcnt16_args.push_back(vcnt8);
+  Expr vcnt16 = ir::Call::make(uint16_type, "llvm_intrin", vcnt16_args, Call::PureIntrinsic);
+  if (call->type.bits() == 16) {
+    return vcnt16;
+  }
+
+  // Accumulation 16->32bit
+  Array<Expr> vcnt32_args;
+  vcnt32_args.push_back(ir::UIntImm::make(UInt(32), vpaddlu_id));
+  vcnt32_args.push_back(ir::UIntImm::make(UInt(32), 1));
+  vcnt32_args.push_back(vcnt16);
+  Expr vcnt32 = ir::Call::make(uint32_type,  "llvm_intrin", vcnt32_args, Call::PureIntrinsic);
+  if (call->type.bits() == 32) {
+    return vcnt32;
+  }
+
+  // Accumulation 32->64bit
+  Array<Expr> vcnt64_args;
+  vcnt64_args.push_back(ir::UIntImm::make(UInt(32), vpaddlu_id));
+  vcnt64_args.push_back(ir::UIntImm::make(UInt(32), 1));
+  vcnt64_args.push_back(vcnt32);
+  return ir::Call::make(call->type,  "llvm_intrin", vcnt64_args, Call::PureIntrinsic);
+}
+
 TVM_REGISTER_GLOBAL("tvm.codegen.llvm.target_arm")
 .set_body([](const TVMArgs& targs, TVMRetValue* rv) {
     CodeGenLLVM* cg = new CodeGenARM();
diff --git a/src/codegen/llvm/codegen_llvm.cc b/src/codegen/llvm/codegen_llvm.cc
index 934398d9ce09..329d7311c9f4 100644
--- a/src/codegen/llvm/codegen_llvm.cc
+++ b/src/codegen/llvm/codegen_llvm.cc
@@ -366,7 +366,7 @@ llvm::Value* CodeGenLLVM::CreateBroadcast(llvm::Value* value, int lanes) {
 llvm::Value* CodeGenLLVM::CreateVecSlice(llvm::Value* vec, int begin, int extent) {
   int num_elems = static_cast<int>(vec->getType()->getVectorNumElements());
   if (extent == num_elems && begin == 0) return vec;
-  CHECK_LT(begin + extent, num_elems);
+  CHECK_LE(begin + extent, num_elems);
   std::vector<unsigned> indices;
   for (int i = 0; i < extent; ++i) {
     indices.push_back(begin + i);
@@ -562,6 +562,10 @@ llvm::Value* CodeGenLLVM::CreateIntrinsic(const Call* op) {
         sig_type.push_back(arg_value.back()->getType());
       }
     }
+    llvm::Type *return_type = LLVMType(op->type);
+    if (sig_type.size() > 0 && return_type != sig_type[0]) {
+      sig_type.insert(sig_type.begin(), return_type);
+    }
     llvm::Function* f = llvm::Intrinsic::getDeclaration(
         module_.get(), id, sig_type);
     return builder_->CreateCall(f, arg_value);
@@ -628,6 +632,26 @@ llvm::Value* CodeGenLLVM::CreateIntrinsic(const Call* op) {
     value->addIncoming(then_value, then_value_block);
     value->addIncoming(else_value, else_value_block);
     return value;
+  } else if (op->is_intrinsic(Call::reinterpret)) {
+    llvm::Type * target = LLVMType(op->type);
+    return builder_->CreateBitCast(MakeValue(op->args[0]), target);
+  } else if (op->is_intrinsic("vectorlow")) {
+    llvm::Value *v = MakeValue(op->args[0]);
+    int l = v->getType()->getVectorNumElements();
+    return CreateVecSlice(v, 0, l/2);
+  } else if (op->is_intrinsic("vectorhigh")) {
+    llvm::Value *v = MakeValue(op->args[0]);
+    int l = v->getType()->getVectorNumElements();
+    return CreateVecSlice(v, l/2, l/2);
+  } else if (op->is_intrinsic("vectorcombine")) {
+    llvm::Value *v0 = MakeValue(op->args[0]);
+    llvm::Value *v1 = MakeValue(op->args[1]);
+    int num_elems = static_cast<int>(v0->getType()->getVectorNumElements()) * 2;
+    std::vector<unsigned> indices;
+    for (int i = 0; i < num_elems; ++i) {
+      indices.push_back(i);
+    }
+    return builder_->CreateShuffleVector(v0, v1, indices);
   } else {
     LOG(FATAL) << "unknown intrinsic " << op->name;
     return nullptr;
diff --git a/src/codegen/llvm/llvm_module.cc b/src/codegen/llvm/llvm_module.cc
index c16af511febc..2bae52b194f5 100644
--- a/src/codegen/llvm/llvm_module.cc
+++ b/src/codegen/llvm/llvm_module.cc
@@ -117,11 +117,41 @@ class LLVMModuleNode final : public runtime::ModuleNode {
   }
 
   std::string GetSource(const std::string& format) final {
+    std::string fmt = runtime::GetFileFormat("", format);
     std::string type_str;
-    llvm::raw_string_ostream rso(type_str);
-    CHECK(mptr_ != nullptr);
-    mptr_->print(rso, nullptr);
-    return rso.str();
+    llvm::SmallString<256> str;
+    llvm::raw_svector_ostream rso(str);
+
+    if (fmt == "s" || fmt == "asm") {
+    #if TVM_LLVM_VERSION <= 60
+          std::unique_ptr<llvm::Module> m = llvm::CloneModule(mptr_);
+    #else
+          std::unique_ptr<llvm::Module> m = llvm::CloneModule(*mptr_);
+    #endif
+          llvm::legacy::PassManager pass;
+          CHECK(tm_);
+    #if TVM_LLVM_VERSION <= 60
+          CHECK(tm_->addPassesToEmitFile(
+              pass, rso, llvm::TargetMachine::CGFT_AssemblyFile) == 0)
+              << "Cannot emit target CGFT_AssemblyFile";
+    #else
+          CHECK(tm_->addPassesToEmitFile(
+              pass, rso, nullptr, llvm::TargetMachine::CGFT_AssemblyFile) == 0)
+              << "Cannot emit target CGFT_AssemblyFile";
+    #endif
+          pass.run(*m);
+          return rso.str().str();
+    } else if (fmt == "" || fmt == "ll") {
+      std::string type_str;
+      llvm::raw_string_ostream rso(type_str);
+      CHECK(mptr_ != nullptr);
+      mptr_->print(rso, nullptr);
+      return rso.str();
+    } else {
+      LOG(FATAL) << "Do not know how to get source code with format: "
+                 << format << "\'";
+    }
+    return "";
   }
 
   void Init(const Array<LoweredFunc>& funcs, std::string target) {
diff --git a/tests/python/unittest/test_codegen_arm.py b/tests/python/unittest/test_codegen_arm.py
new file mode 100644
index 000000000000..24240db72b26
--- /dev/null
+++ b/tests/python/unittest/test_codegen_arm.py
@@ -0,0 +1,30 @@
+import tvm
+import re
+import os
+import ctypes
+
+def test_popcount():
+    target = 'llvm -target=armv7l-none-linux-gnueabihf -mcpu=cortex-a53 -mattr=+neon'
+
+    def check_correct_assembly(type, elements, counts):
+        n = tvm.convert(elements)
+        A = tvm.placeholder(n, dtype=type, name='A')
+        B = tvm.compute(A.shape, lambda i: tvm.popcount(A[i]), name='B')
+        s = tvm.create_schedule(B.op)
+        s[B].vectorize(s[B].op.axis[0])
+        f = tvm.build(s, [A, B], target)
+
+        # Verify we see the correct number of vpaddl and vcnt instructions in the assembly
+        assembly = f.get_source('asm')
+        matches = re.findall("vpaddl", assembly)
+        assert (len(matches) == counts)
+        matches = re.findall("vcnt", assembly)
+        assert (len(matches) == 1)
+    check_correct_assembly('uint16', 8, 1)
+    check_correct_assembly('uint16', 4, 1)
+    check_correct_assembly('uint32', 4, 2)
+    check_correct_assembly('uint32', 2, 2)
+    check_correct_assembly('uint64', 2, 3)
+
+if __name__ == "__main__":
+    test_popcount()

From 142c7da392a232e5610b6d7bcfd5b40f9c4dd027 Mon Sep 17 00:00:00 2001
From: Da Zheng <zhengda1936@gmail.com>
Date: Tue, 12 Jun 2018 15:46:48 -0700
Subject: [PATCH 713/949] add support for subgraphs. (#1221)

* add support for subgraphs.

* fix.

* fix.

* Fix compilation error

* Fix compilation error

* add comments.

* update comments.

* Sanity check on subgraphs when creating IndexedGraph

* avoid the overhead of sanity check.

* Stop using non-recursive DFS

* Trigger CI

* trigger CI
---
 nnvm/include/nnvm/node.h          |  16 ++++
 nnvm/include/nnvm/op_attr_types.h |  12 +++
 nnvm/src/core/graph.cc            |  40 ++++++++-
 nnvm/src/core/symbolic.cc         |  81 +++++++++++++----
 nnvm/src/pass/saveload_json.cc    | 144 +++++++++++++++++++-----------
 5 files changed, 219 insertions(+), 74 deletions(-)

diff --git a/nnvm/include/nnvm/node.h b/nnvm/include/nnvm/node.h
index 15db77ee6043..57afb0c5587a 100644
--- a/nnvm/include/nnvm/node.h
+++ b/nnvm/include/nnvm/node.h
@@ -18,6 +18,7 @@ namespace nnvm {
 
 // Forward declare node.
 class Node;
+class Symbol;
 
 /*!
  * \brief we always used NodePtr for a reference pointer
@@ -90,6 +91,21 @@ struct NodeAttrs {
    * The object can be used to quickly access attributes.
    */
   any parsed;
+  /*!
+   * \brief Some operators take graphs as input. These operators include
+   * control flow operators and high-order functions.
+   * These graphs don't change when the operators are invoked for different
+   * mini-batches. In this sense, the subgraphs are kind of similar to
+   * the parameters and show be kept as node attributes.
+   *
+   * Users need to make sure the subgraphs are disjoint with the main graph.
+   * If a graph shares nodes with subgraphs, loading the graph from LoadJSON
+   * may generate a graph that has a different structure from the original graph
+   * (some of the nodes are duplicated). If nodes are shared between two graphs,
+   * shared nodes might be executed multiple times, which can be a problem for
+   * stateful operators.
+   */
+  std::vector<std::shared_ptr<Symbol> > subgraphs;
 };
 
 /*!
diff --git a/nnvm/include/nnvm/op_attr_types.h b/nnvm/include/nnvm/op_attr_types.h
index e58e9ceb3581..b7f6be408a16 100644
--- a/nnvm/include/nnvm/op_attr_types.h
+++ b/nnvm/include/nnvm/op_attr_types.h
@@ -202,6 +202,18 @@ using FCorrectLayout = std::function<bool(
     const std::vector<Layout> *last_ilayouts,
     std::vector<Layout> *olayouts)>;
 
+/*!
+ * \brief Get a list of inputs that represent graphs instead of data.
+ * Normally, input symbols are considered as data to the operator. However,
+ * control flow operators and high-order functions need to interpret symbols
+ * as graphs.
+ * \param attrs The attributes of this node.
+ * \return a list of input index that are interpreted as symbols by the operator.
+ *
+ * \note Register under "FInputGraph".
+ */
+using FInputGraph = std::function<std::vector<uint32_t>(const NodeAttrs& attrs)>;
+
 }  // namespace nnvm
 
 #endif  // NNVM_OP_ATTR_TYPES_H_
diff --git a/nnvm/src/core/graph.cc b/nnvm/src/core/graph.cc
index 62c7085c1210..b8bcae70f2e0 100644
--- a/nnvm/src/core/graph.cc
+++ b/nnvm/src/core/graph.cc
@@ -16,15 +16,51 @@ const IndexedGraph& Graph::indexed_graph() const {
   return *indexed_graph_;
 }
 
+// a subgraph should not refer to any nodes with higher level
+// where "level" refers to the nested depth of the subgraph
+// e.g. the main graph is level 0
+// subgraphs of the main graph is level 1
+// subgraphs of the subgraphs of the main graph is level 2
+static void SubgraphSanityCheck(const std::vector<std::shared_ptr<Symbol>> &subgraphs) {
+  std::vector<const std::vector<nnvm::NodeEntry>*> curr_level;
+  std::vector<const std::vector<nnvm::NodeEntry>*> next_level;
+  std::unordered_map<nnvm::Node*, uint32_t> node2level;
+  for (auto &subgraph : subgraphs)
+    next_level.push_back(&subgraph->outputs);
+  for (uint32_t level = 0; !next_level.empty(); ++level) {
+    curr_level.swap(next_level);
+    next_level.clear();
+    for (const std::vector<NodeEntry> *graph_ptr : curr_level) {
+      const std::vector<NodeEntry> &graph = *graph_ptr;
+      DFSVisit(graph, [&next_level, &node2level, level](const NodePtr& n) {
+        nnvm::Node *node = n.get();
+        // if the node is visited, but on a different level, then check failed
+        // if check failed here or before, we stop doing anything, but raise an error
+        CHECK(!node2level.count(node) || node2level[node] == level)
+          << "A subgraph should not depend on the outputs of nodes on higher levels";
+        // otherwise, this node belongs to the current level
+        node2level[node] = level;
+        // subgraphs of current node belongs to next level
+        for (const auto& subgraph : n->attrs.subgraphs) {
+          next_level.push_back(&subgraph->outputs);
+        }
+      });
+    }
+  }
+}
+
 // implement constructor from graph
 IndexedGraph::IndexedGraph(const Graph &g) {
   entry_rptr_.push_back(0);
   std::vector<size_t> inputs_rptr{0}, control_rptr{0};
+  std::vector<std::shared_ptr<Symbol>> subgraphs;
 
-  DFSVisit(g.outputs, [this, &inputs_rptr, &control_rptr]
+  DFSVisit(g.outputs, [this, &inputs_rptr, &control_rptr, &subgraphs]
              (const NodePtr& n) {
       CHECK_LT(nodes_.size(), std::numeric_limits<uint32_t>::max());
       uint32_t nid = static_cast<uint32_t>(nodes_.size());
+      for (const auto &subgraph : n->attrs.subgraphs)
+        subgraphs.push_back(subgraph);
       // nodes_
       IndexedGraph::Node new_node;
       new_node.source = n.get();
@@ -53,6 +89,8 @@ IndexedGraph::IndexedGraph(const Graph &g) {
       }
       control_rptr.push_back(control_deps_.size());
   });
+  if (!subgraphs.empty())
+    SubgraphSanityCheck(subgraphs);
 
   for (const auto& e : g.outputs) {
     outputs_.emplace_back(NodeEntry{
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index 2a2f5be50bc0..927dd2b70e44 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -267,14 +267,36 @@ void Symbol::Compose(const array_view<const Symbol*>& args,
                      const std::string& name) {
   static auto& flist_inputs = Op::GetAttr<FListInputNames>("FListInputNames");
   static auto& fset_attrs = Op::GetAttr<FSetInputVarAttrOnCompose>("FSetInputVarAttrOnCompose");
+  static auto& fgraph = Op::GetAttr<FInputGraph>("FInputGraph");
+
+  // The arguments that contain graphs.
+  Node* n = outputs[0].node.get();
+  FInputGraph fng = fgraph.get(n->op(), nullptr);
+  std::vector<uint32_t> garg_idx;
+  if (fng != nullptr)
+    garg_idx = fng(n->attrs);
+
+  // The names of the arguments that contain graphs.
+  FListInputNames name_fn = flist_inputs.get(n->op(), nullptr);
+  auto arg_names = (name_fn == nullptr) ? std::vector<std::string>{"data"} : name_fn(n->attrs);
+  std::vector<std::string> garg_names(garg_idx.size());
+  for (size_t i = 0; i < garg_idx.size(); i++) {
+    size_t idx = garg_idx[i];
+    if (idx < arg_names.size())
+      garg_names[i] = arg_names[idx];
+  }
 
   // parameter check.
   for (size_t i = 0; i < args.size(); ++i) {
-    CHECK_EQ(args[i]->outputs.size(), 1U)
+    // If the argument isn't a graph, it should have only one output.
+    if (garg_idx.empty() || std::find(garg_idx.begin(), garg_idx.end(), i) == garg_idx.end())
+      CHECK_EQ(args[i]->outputs.size(), 1U)
         << "Argument " << i << " is a tuple, single value is required";
   }
   for (const auto& kv : kwargs) {
-    CHECK_EQ(kv.second->outputs.size(), 1U)
+    if (garg_names.empty()
+        || std::find(garg_names.begin(), garg_names.end(), kv.first) == garg_names.end())
+      CHECK_EQ(kv.second->outputs.size(), 1U)
         << "Keyword Argument " << kv.first << " is a tuple, single value is required";
   }
   // assign new name
@@ -282,28 +304,49 @@ void Symbol::Compose(const array_view<const Symbol*>& args,
 
   // Atomic functor composition.
   if (IsAtomic(outputs)) {
-    Node* n = outputs[0].node.get();
     uint32_t n_req = n->num_inputs();
+    std::vector<const Symbol *> arg_vec(args.begin(), args.end());
+    std::unordered_map<std::string, const Symbol*> kwarg_map(kwargs.begin(), kwargs.end());
+    // If one of the input arguments is a graph, we need to remove it from the
+    // list.
+    if (fng != nullptr) {
+      std::vector<uint32_t> idxes = fng(n->attrs);
+      for (auto idx : idxes) {
+        const Symbol *sym;
+        if (idx < arg_vec.size()) {
+          sym = arg_vec[idx];
+          arg_vec.erase(arg_vec.begin() + idx);
+        } else {
+          auto it = kwarg_map.find(arg_names[idx]);
+          CHECK(it != kwarg_map.end());
+          sym = it->second;
+          kwarg_map.erase(it);
+        }
+
+        if (n_req != kVarg)
+          n_req--;
+        arg_names.erase(arg_names.begin() + idx);
+        n->attrs.subgraphs.push_back(std::make_shared<Symbol>(*sym));
+      }
+    }
 
     if (n_req != kVarg) {
       n->inputs.resize(n_req);
-      CHECK_LE(args.size(), n_req)
+      CHECK_LE(arg_vec.size(), n_req)
           << "Incorrect number of arguments, requires " << n_req
-          << ", provided " << args.size();
-      for (size_t i = 0; i < args.size(); ++i) {
-        n->inputs[i] = args[i]->outputs[0];
+          << ", provided " << arg_vec.size();
+      for (size_t i = 0; i < arg_vec.size(); ++i) {
+        n->inputs[i] = arg_vec[i]->outputs[0];
       }
       // switch to keyword argument matching
-      if (args.size() != n_req) {
-        FListInputNames fn = flist_inputs.get(n->op(), nullptr);
-        auto arg_names = (fn == nullptr) ? std::vector<std::string>{"data"} : fn(n->attrs);
+      if (arg_vec.size() != n_req) {
         if (arg_names.size() != n_req) {
           LOG(FATAL) << "Not enough argument to call operator " << outputs[0].node->op()->name;
         }
         size_t nmatched = 0;
-        for (size_t i = args.size(); i < n_req; ++i) {
-          auto it = kwargs.find(arg_names[i]);
-          if (it != kwargs.end() && it->first == arg_names[i]) {
+        for (size_t i = arg_vec.size(); i < n_req; ++i) {
+          auto it = kwarg_map.find(arg_names[i]);
+          if (it != kwarg_map.end() && it->first == arg_names[i]) {
             n->inputs[i] = it->second->outputs[0];
             ++nmatched;
           } else {
@@ -314,18 +357,18 @@ void Symbol::Compose(const array_view<const Symbol*>& args,
           }
         }
 
-        if (nmatched != kwargs.size()) {
+        if (nmatched != kwarg_map.size()) {
           n->inputs.clear();
-          std::vector<std::string> keys = GetKeys(kwargs);
-          array_view<std::string> view(dmlc::BeginPtr(arg_names) + args.size(),
+          std::vector<std::string> keys = GetKeys(kwarg_map);
+          array_view<std::string> view(dmlc::BeginPtr(arg_names) + arg_vec.size(),
                                        dmlc::BeginPtr(arg_names) + arg_names.size());
           KeywordArgumentMismatch("Symbol.Compose", keys, view);
         }
       }
     } else {
-      CHECK_EQ(kwargs.size(), 0U) << "Variable length function do not accept kwargs";
-      n->inputs.reserve(args.size());
-      for (const Symbol* s : args) {
+      CHECK_EQ(kwarg_map.size(), 0U) << "Variable length function do not accept kwargs";
+      n->inputs.reserve(arg_vec.size());
+      for (const Symbol* s : arg_vec) {
         n->inputs.push_back(s->outputs[0]);
       }
     }
diff --git a/nnvm/src/pass/saveload_json.cc b/nnvm/src/pass/saveload_json.cc
index 3170d245ca7a..195d49bfb9b4 100644
--- a/nnvm/src/pass/saveload_json.cc
+++ b/nnvm/src/pass/saveload_json.cc
@@ -29,6 +29,11 @@ namespace nnvm {
 namespace pass {
 namespace {
 
+// JSONNode represents an nnvm::Node in JSON
+struct JSONNode;
+// JSONGraph represents an nnvm::Graph or nnvm::Symbol in JSON
+struct JSONGraph;
+
 // auxiliary node structure for serialization.
 struct JSONNode {
   // the node entry structure in serialized format
@@ -36,6 +41,10 @@ struct JSONNode {
     uint32_t node_id;
     uint32_t index;
     uint32_t version;
+    Entry() = default;
+    Entry(uint32_t node_id, uint32_t index, uint32_t version):
+      node_id(node_id), index(index), version(version) {
+    }
     void Save(dmlc::JSONWriter *writer) const {
       writer->BeginArray(false);
       writer->WriteArrayItem(node_id);
@@ -64,6 +73,8 @@ struct JSONNode {
   std::vector<Entry> inputs;
   // control flow dependencies
   std::vector<uint32_t> control_deps;
+  // subgraphs
+  std::vector<JSONGraph> subgraphs;
 
   // function to save JSON node.
   void Save(dmlc::JSONWriter *writer) const {
@@ -85,6 +96,9 @@ struct JSONNode {
     if (control_deps.size() != 0) {
       writer->WriteObjectKeyValue("control_deps", control_deps);
     }
+    if (subgraphs.size() != 0) {
+      writer->WriteObjectKeyValue("subgraphs", subgraphs);
+    }
     writer->EndObject();
   }
 
@@ -99,6 +113,7 @@ struct JSONNode {
     helper.DeclareOptionalField("attrs", &(node->attrs.dict));
     helper.DeclareOptionalField("attr", &(node->attrs.dict));
     helper.DeclareOptionalField("control_deps", &control_deps);
+    helper.DeclareOptionalField("subgraphs", &subgraphs);
     // backward compatible code with mxnet graph.
     int backward_source_id;
     std::unordered_map<std::string, std::string> param;
@@ -154,86 +169,107 @@ struct JSONGraph {
   }
 };
 
-// Load a graph from JSON file.
-Graph LoadJSON(Graph src) {
-  CHECK_NE(src.attrs.count("json"), 0U)
-      << "Load JSON require json to be presented.";
-  const std::string &json_str =
-      nnvm::get<std::string>(*src.attrs.at("json"));
-  bool no_parse = false;
-  if (src.attrs.count("load_json_no_parse")) {
-    no_parse = nnvm::get<bool>(*src.attrs.at("load_json_no_parse"));
+void Symbol2JSONGraph(std::shared_ptr<Symbol> src, JSONGraph *jgraph) {
+  std::unordered_map<Node*, uint32_t> node2index;
+  jgraph->node_row_ptr.push_back(0);
+  DFSVisit(src->outputs, [&node2index, jgraph](const NodePtr& n) {
+    uint32_t nid = static_cast<uint32_t>(jgraph->nodes.size());
+    node2index[n.get()] = nid;
+    if (n->is_variable()) {
+      jgraph->arg_nodes.push_back(nid);
+    }
+    JSONNode jnode;
+    jnode.node = n;
+    jnode.inputs.reserve(n->inputs.size());
+    for (const NodeEntry& e : n->inputs) {
+      jnode.inputs.emplace_back(node2index.at(e.node.get()), e.index, e.version);
+    }
+    for (const NodePtr& c : n->control_deps) {
+      jnode.control_deps.push_back(node2index.at(c.get()));
+    }
+    jgraph->node_row_ptr.push_back(jgraph->node_row_ptr.back() + n->num_outputs());
+    jgraph->nodes.emplace_back(std::move(jnode));
+  });
+  for (const NodeEntry& e : src->outputs) {
+    jgraph->heads.emplace_back(node2index.at(e.node.get()), e.index, e.version);
   }
-  std::istringstream is(json_str);
-  dmlc::JSONReader reader(&is);
-  JSONGraph jgraph;
-  // load in json graph.
-  jgraph.Load(&reader);
-  // connects the nodes
-  for (JSONNode &n : jgraph.nodes) {
+  // recursively construct subgraphs
+  for (JSONNode &jnode : jgraph->nodes) {
+    // construct jnode's subgraphs
+    const std::vector<std::shared_ptr<Symbol>> &subgraphs = jnode.node->attrs.subgraphs;
+    std::vector<JSONGraph> &jsubgraphs = jnode.subgraphs;
+    jsubgraphs.resize(subgraphs.size());
+    for (uint32_t i = 0; i < subgraphs.size(); ++i) {
+      Symbol2JSONGraph(subgraphs[i], &jsubgraphs[i]);
+    }
+  }
+}
+
+std::shared_ptr<Symbol> JSONGraph2Symbol(const JSONGraph &jgraph, bool no_parse) {
+  for (const JSONNode &n : jgraph.nodes) {
     n.node->inputs.reserve(n.inputs.size());
     for (const JSONNode::Entry &e : n.inputs) {
-      n.node->inputs.emplace_back(
-          NodeEntry{jgraph.nodes[e.node_id].node, e.index, e.version});
+      n.node->inputs.emplace_back(NodeEntry{jgraph.nodes[e.node_id].node, e.index, e.version});
     }
     n.node->control_deps.reserve(n.control_deps.size());
     for (uint32_t nid : n.control_deps) {
       n.node->control_deps.push_back(jgraph.nodes[nid].node);
     }
     // rebuild attribute parser
-    if (!no_parse && n.node->op() != nullptr &&
-        n.node->op()->attr_parser != nullptr) {
+    if (!no_parse && n.node->op() != nullptr && n.node->op()->attr_parser != nullptr) {
       n.node->op()->attr_parser(&(n.node->attrs));
     }
+    for (const JSONGraph &subgraph : n.subgraphs) {
+      // The "no_parse" option here, is to be compatible with
+      // commit cfd3075e85807dcd8f9534c37e053583dee87524
+      // (https://github.com/apache/incubator-mxnet/tree/cfd3075e85807dcd8f9534c37e053583dee87524),
+      // where the parsing of main graph is deferred until
+      // incubator-mxnet/src/nnvm/legacy_json_util.cc:UpgradeJSON_Parse
+      n.node->attrs.subgraphs.push_back(JSONGraph2Symbol(subgraph, false));
+    }
   }
-  // consistent check
+  // consistency check
   for (uint32_t nid : jgraph.arg_nodes) {
     CHECK(jgraph.nodes[nid].node->is_variable());
   }
+  std::shared_ptr<Symbol> symbol = std::make_shared<Symbol>();
+  symbol->outputs.reserve(jgraph.heads.size());
+  for (const JSONNode::Entry &e : jgraph.heads) {
+    symbol->outputs.emplace_back(NodeEntry{jgraph.nodes[e.node_id].node, e.index, e.version});
+  }
+  return symbol;
+}
 
+// Load a graph from JSON file.
+Graph LoadJSON(Graph src) {
+  CHECK_NE(src.attrs.count("json"), 0U)
+      << "Load JSON require json to be presented.";
+  const std::string &json_str =
+      nnvm::get<std::string>(*src.attrs.at("json"));
+  bool no_parse = false;
+  if (src.attrs.count("load_json_no_parse")) {
+    no_parse = nnvm::get<bool>(*src.attrs.at("load_json_no_parse"));
+  }
+  std::istringstream is(json_str);
+  dmlc::JSONReader reader(&is);
+  JSONGraph jgraph;
+  // load in json graph.
+  jgraph.Load(&reader);
+  std::shared_ptr<Symbol> symbol = JSONGraph2Symbol(jgraph, no_parse);
   // return the graph
   Graph ret;
   ret.attrs = std::move(jgraph.attrs);
-  ret.outputs.reserve(jgraph.heads.size());
-  for (const JSONNode::Entry &e : jgraph.heads) {
-    ret.outputs.emplace_back(
-        NodeEntry{jgraph.nodes[e.node_id].node, e.index, e.version});
-  }
+  ret.outputs = symbol->outputs;
   return ret;
 }
 
 // save a graph to json
 Graph SaveJSON(Graph src) {
+  std::shared_ptr<Symbol> src_symbol = std::make_shared<Symbol>();
+  src_symbol->outputs = src.outputs;
   JSONGraph jgraph;
+  Symbol2JSONGraph(src_symbol, &jgraph);
   jgraph.attrs = src.attrs;
-  std::unordered_map<Node*, uint32_t> node2index;
-  jgraph.node_row_ptr.push_back(0);
-  DFSVisit(src.outputs, [&node2index, &jgraph](const NodePtr& n) {
-      uint32_t nid = static_cast<uint32_t>(jgraph.nodes.size());
-      node2index[n.get()] = nid;
-      if (n->is_variable()) {
-        jgraph.arg_nodes.push_back(nid);
-      }
-      JSONNode jnode;
-      jnode.node = n;
-      jnode.inputs.reserve(n->inputs.size());
-      for (const NodeEntry& e : n->inputs) {
-        jnode.inputs.emplace_back(
-            JSONNode::Entry{node2index.at(e.node.get()), e.index, e.version});
-      }
-      for (const NodePtr& c : n->control_deps) {
-        jnode.control_deps.push_back(node2index.at(c.get()));
-      }
-      jgraph.node_row_ptr.push_back(
-          jgraph.node_row_ptr.back() + n->num_outputs());
-      jgraph.nodes.emplace_back(std::move(jnode));
-    });
-
-  for (const NodeEntry& e : src.outputs) {
-    jgraph.heads.push_back(
-        JSONNode::Entry{node2index.at(e.node.get()), e.index, e.version});
-  }
-
   std::ostringstream os;
   dmlc::JSONWriter writer(&os);
   jgraph.Save(&writer);

From 616580f1a29fe7719d60349f740ca1f76e90fe1b Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Wed, 13 Jun 2018 10:18:15 +0800
Subject: [PATCH 714/949] Add silent mode to rpc server and rpc tracker (#1268)

---
 python/tvm/contrib/rpc/base.py    | 10 ++--
 python/tvm/contrib/rpc/proxy.py   |  2 +-
 python/tvm/contrib/rpc/server.py  | 86 +++++++++++++++++++++----------
 python/tvm/contrib/rpc/tracker.py | 15 ++++--
 python/tvm/exec/rpc_server.py     | 10 ++--
 python/tvm/exec/rpc_tracker.py    | 13 +++--
 src/runtime/rpc/rpc_server_env.cc |  1 -
 7 files changed, 95 insertions(+), 42 deletions(-)

diff --git a/python/tvm/contrib/rpc/base.py b/python/tvm/contrib/rpc/base.py
index 67e6d6b43bd1..d0004f0c86f8 100644
--- a/python/tvm/contrib/rpc/base.py
+++ b/python/tvm/contrib/rpc/base.py
@@ -120,7 +120,7 @@ def random_key(prefix, cmap=None):
         return prefix + str(random.random())
 
 
-def connect_with_retry(addr, timeout=60, retry_period=5):
+def connect_with_retry(addr, timeout=60, retry_period=5, silent=False):
     """Connect to a TPC address with retry
 
     This function is only reliable to short period of server restart.
@@ -135,6 +135,9 @@ def connect_with_retry(addr, timeout=60, retry_period=5):
 
     retry_period : float
          Number of seconds before we retry again.
+
+    silent: bool
+        whether run in silent mode
     """
     tstart = time.time()
     while True:
@@ -149,8 +152,9 @@ def connect_with_retry(addr, timeout=60, retry_period=5):
             if period > timeout:
                 raise RuntimeError(
                     "Failed to connect to server %s" % str(addr))
-            logging.info("Cannot connect to tracker%s, retry in %g secs...",
-                         str(addr), retry_period)
+            if not silent:
+                logging.info("Cannot connect to tracker%s, retry in %g secs...",
+                             str(addr), retry_period)
             time.sleep(retry_period)
 
 
diff --git a/python/tvm/contrib/rpc/proxy.py b/python/tvm/contrib/rpc/proxy.py
index 315354edeb92..b1168c1a843f 100644
--- a/python/tvm/contrib/rpc/proxy.py
+++ b/python/tvm/contrib/rpc/proxy.py
@@ -536,7 +536,7 @@ def _fsend(data):
     def _connect(key):
         conn = yield websocket.websocket_connect(url)
         on_message = create_on_message(conn)
-        temp = _server_env(None)
+        temp = _server_env(None, None)
         # Start connecton
         conn.write_message(struct.pack('<i', base.RPC_MAGIC), binary=True)
         key = "server:" + key
diff --git a/python/tvm/contrib/rpc/server.py b/python/tvm/contrib/rpc/server.py
index bef85bc5711c..938e5cdf261a 100644
--- a/python/tvm/contrib/rpc/server.py
+++ b/python/tvm/contrib/rpc/server.py
@@ -19,6 +19,7 @@
 import multiprocessing
 import subprocess
 import time
+import sys
 
 from ..._ffi.function import register_func
 from ..._ffi.base import py_str
@@ -28,9 +29,12 @@
 from . import base
 from . base import TrackerCode
 
-def _server_env(load_library):
+def _server_env(load_library, logger):
     """Server environment function return temp dir"""
     temp = util.tempdir()
+    if logger is None:
+        logger = logging.getLogger()
+
     # pylint: disable=unused-variable
     @register_func("tvm.contrib.rpc.server.workpath")
     def get_workpath(path):
@@ -41,7 +45,7 @@ def load_module(file_name):
         """Load module from remote side."""
         path = temp.relpath(file_name)
         m = _load_module(path)
-        logging.info("load_module %s", path)
+        logger.info("load_module %s", path)
         return m
 
     libs = []
@@ -49,18 +53,21 @@ def load_module(file_name):
     for file_name in load_library:
         file_name = find_lib_path(file_name)[0]
         libs.append(ctypes.CDLL(file_name, ctypes.RTLD_GLOBAL))
-        logging.info("Load additional library %s", file_name)
+        logger.info("Load additional library %s", file_name)
     temp.libs = libs
     return temp
 
 
-def _serve_loop(sock, addr, load_library):
+def _serve_loop(sock, addr, load_library, silent):
     """Server loop"""
+    logger = logging.getLogger("RPCServer")
+    if silent:
+        logger.disabled = True
     sockfd = sock.fileno()
-    temp = _server_env(load_library)
+    temp = _server_env(load_library, logger)
     base._ServerLoop(sockfd)
     temp.remove()
-    logging.info("Finish serving %s", addr)
+    logger.info("Finish serving %s", addr)
 
 
 def _parse_server_opt(opts):
@@ -71,8 +78,12 @@ def _parse_server_opt(opts):
             ret["timeout"] = float(kv[9:])
     return ret
 
-def _listen_loop(sock, port, rpc_key, tracker_addr, load_library, custom_addr):
-    """Lisenting loop of the server master."""
+def _listen_loop(sock, port, rpc_key, tracker_addr, load_library, custom_addr, silent):
+    """Listening loop of the server master."""
+    logger = logging.getLogger("RPCServer")
+    if silent:
+        logger.disabled = True
+
     def _accept_conn(listen_sock, tracker_conn, ping_period=2):
         """Accept connection from the other places.
 
@@ -115,7 +126,7 @@ def _accept_conn(listen_sock, tracker_conn, ping_period=2):
                         unmatch_period_count = 0
                     # regenerate match key if key is acquired but not used for a while
                     if unmatch_period_count * ping_period > unmatch_timeout + ping_period:
-                        logging.info("RPCServer: no incoming connections, regenerate key ...")
+                        logger.info("no incoming connections, regenerate key ...")
                         matchkey = base.random_key(rpc_key + ":", old_keyset)
                         base.sendjson(tracker_conn,
                                       [TrackerCode.PUT, rpc_key, (port, matchkey),
@@ -136,7 +147,7 @@ def _accept_conn(listen_sock, tracker_conn, ping_period=2):
             if arr[0] != expect_header:
                 conn.sendall(struct.pack("<i", base.RPC_CODE_MISMATCH))
                 conn.close()
-                logging.info("RPCServer: mismatch key from %s", addr)
+                logger.info("mismatch key from %s", addr)
                 continue
             else:
                 conn.sendall(struct.pack("<i", base.RPC_CODE_SUCCESS))
@@ -150,7 +161,7 @@ def _accept_conn(listen_sock, tracker_conn, ping_period=2):
         try:
             # step 1: setup tracker and report to tracker
             if tracker_addr and tracker_conn is None:
-                tracker_conn = base.connect_with_retry(tracker_addr)
+                tracker_conn = base.connect_with_retry(tracker_addr, silent=silent)
                 tracker_conn.sendall(struct.pack("<i", base.RPC_TRACKER_MAGIC))
                 magic = struct.unpack("<i", base.recvall(tracker_conn, 4))[0]
                 if magic != base.RPC_TRACKER_MAGIC:
@@ -169,10 +180,16 @@ def _accept_conn(listen_sock, tracker_conn, ping_period=2):
                 tracker_conn.close()
                 tracker_conn = None
             continue
+        except RuntimeError as exc:
+            if silent:
+                return
+            else:
+                raise exc
 
         # step 3: serving
-        logging.info("RPCServer: connection from %s", addr)
-        server_proc = multiprocessing.Process(target=_serve_loop, args=(conn, addr, load_library))
+        logger.info("connection from %s", addr)
+        server_proc = multiprocessing.Process(target=_serve_loop,
+                                              args=(conn, addr, load_library, silent))
         server_proc.deamon = True
         server_proc.start()
         # close from our side.
@@ -180,11 +197,14 @@ def _accept_conn(listen_sock, tracker_conn, ping_period=2):
         # wait until server process finish or timeout
         server_proc.join(opts.get("timeout", None))
         if server_proc.is_alive():
-            logging.info("RPCServer: Timeout in RPC session, kill..")
+            logger.info("Timeout in RPC session, kill..")
             server_proc.terminate()
 
 
-def _connect_proxy_loop(addr, key, load_library):
+def _connect_proxy_loop(addr, key, load_library, silent):
+    logger = logging.getLogger("RPCProxy")
+    if silent:
+        logger.disabled = True
     key = "server:" + key
     retry_count = 0
     max_retry = 5
@@ -200,26 +220,26 @@ def _connect_proxy_loop(addr, key, load_library):
             if magic == base.RPC_CODE_DUPLICATE:
                 raise RuntimeError("key: %s has already been used in proxy" % key)
             elif magic == base.RPC_CODE_MISMATCH:
-                logging.info("RPCProxy do not have matching client key %s", key)
+                logger.info("RPCProxy do not have matching client key %s", key)
             elif magic != base.RPC_CODE_SUCCESS:
                 raise RuntimeError("%s is not RPC Proxy" % str(addr))
             keylen = struct.unpack("<i", base.recvall(sock, 4))[0]
             remote_key = py_str(base.recvall(sock, keylen))
             opts = _parse_server_opt(remote_key.split()[1:])
-            logging.info("RPCProxy connected to %s", str(addr))
+            logger.info("connected to %s", str(addr))
             process = multiprocessing.Process(
-                target=_serve_loop, args=(sock, addr, load_library))
+                target=_serve_loop, args=(sock, addr, load_library, silent))
             process.deamon = True
             process.start()
             sock.close()
             process.join(opts.get("timeout", None))
             if process.is_alive():
-                logging.info("RPCProxyServer: Timeout in RPC session, kill..")
+                logger.info("Timeout in RPC session, kill..")
                 process.terminate()
             retry_count = 0
         except (socket.error, IOError) as err:
             retry_count += 1
-            logging.info("Error encountered %s, retry in %g sec", str(err), retry_period)
+            logger.info("Error encountered %s, retry in %g sec", str(err), retry_period)
             if retry_count > max_retry:
                 raise RuntimeError("Maximum retry error: last error: %s" % str(err))
             time.sleep(retry_period)
@@ -264,6 +284,9 @@ class Server(object):
         This is recommended to switch on if we want to do local RPC demonstration
         for GPU devices to avoid fork safety issues.
 
+    silent: bool, optional
+        Whether run this server in silent mode.
+
     key : str, optional
         The key used to identify the server in Proxy connection.
 
@@ -276,6 +299,7 @@ def __init__(self,
                  port_end=9199,
                  is_proxy=False,
                  use_popen=False,
+                 silent=False,
                  tracker_addr=None,
                  key="",
                  load_library=None,
@@ -290,8 +314,12 @@ def __init__(self,
         self.libs = []
         self.custom_addr = custom_addr
 
+        self.logger = logging.getLogger("RPCServer")
+        if silent:
+            self.logger.disabled = True
+
         if use_popen:
-            cmd = ["python",
+            cmd = [sys.executable,
                    "-m", "tvm.exec.rpc_server",
                    "--host=%s" % host,
                    "--port=%s" % port]
@@ -303,11 +331,14 @@ def __init__(self,
                 cmd += ["--load-library", load_library]
             if custom_addr:
                 cmd += ["--custom-addr", custom_addr]
+            if silent:
+                cmd += ["--silent"]
+
             self.proc = multiprocessing.Process(
                 target=subprocess.check_call, args=(cmd,))
             self.proc.deamon = True
             self.proc.start()
-            time.sleep(1)
+            time.sleep(0.5)
         elif not is_proxy:
             sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
             self.port = None
@@ -321,19 +352,20 @@ def __init__(self,
                         continue
                     else:
                         raise sock_err
-                if not self.port:
-                    raise ValueError("cannot bind to any port in [%d, %d)" % (port, port_end))
-            logging.info("RPCServer: bind to %s:%d", host, self.port)
+            if not self.port:
+                raise ValueError("cannot bind to any port in [%d, %d)" % (port, port_end))
+            self.logger.info("bind to %s:%d", host, self.port)
             sock.listen(1)
             self.sock = sock
             self.proc = multiprocessing.Process(
                 target=_listen_loop, args=(
-                    self.sock, self.port, key, tracker_addr, load_library, self.custom_addr))
+                    self.sock, self.port, key, tracker_addr, load_library,
+                    self.custom_addr, silent))
             self.proc.deamon = True
             self.proc.start()
         else:
             self.proc = multiprocessing.Process(
-                target=_connect_proxy_loop, args=((host, port), key, load_library))
+                target=_connect_proxy_loop, args=((host, port), key, load_library, silent))
             self.proc.deamon = True
             self.proc.start()
 
diff --git a/python/tvm/contrib/rpc/tracker.py b/python/tvm/contrib/rpc/tracker.py
index 812b3a9770ab..47e81859c934 100644
--- a/python/tvm/contrib/rpc/tracker.py
+++ b/python/tvm/contrib/rpc/tracker.py
@@ -309,7 +309,6 @@ def run(self):
 def _tracker_server(listen_sock, stop_key):
     handler = TrackerServerHandler(listen_sock, stop_key)
     handler.run()
-    logging.info("Tracker Stop signal received, terminating...")
 
 
 class Tracker(object):
@@ -327,11 +326,19 @@ class Tracker(object):
 
     port_end : int, optional
         The end TCP port to search
+
+    silent: bool, optional
+        Whether run in silent mode
     """
     def __init__(self,
                  host,
                  port=9190,
-                 port_end=9199):
+                 port_end=9199,
+                 silent=False):
+        self.logger = logging.getLogger("RPCTracker")
+        if silent:
+            self.logger.disabled = True
+
         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         self.port = None
         self.stop_key = base.random_key("tracker")
@@ -347,7 +354,7 @@ def __init__(self,
                     raise sock_err
         if not self.port:
             raise ValueError("cannot bind to any port in [%d, %d)" % (port, port_end))
-        logging.info("RPCTracker: bind to %s:%d", host, self.port)
+        self.logger.info("bind to %s:%d", host, self.port)
         sock.listen(1)
         self.proc = multiprocessing.Process(
             target=_tracker_server, args=(sock, self.stop_key))
@@ -373,7 +380,7 @@ def terminate(self):
                 self._stop_tracker()
                 self.proc.join(1)
             if self.proc.is_alive():
-                logging.info("Terminating Tracker Server...")
+                self.logger.info("Terminating Tracker Server...")
                 self.proc.terminate()
             self.proc = None
 
diff --git a/python/tvm/exec/rpc_server.py b/python/tvm/exec/rpc_server.py
index d874ed63b673..e26dbc3b2025 100644
--- a/python/tvm/exec/rpc_server.py
+++ b/python/tvm/exec/rpc_server.py
@@ -27,7 +27,8 @@ def main(args):
                         key=args.key,
                         tracker_addr=tracker_addr,
                         load_library=args.load_library,
-                        custom_addr=args.custom_addr)
+                        custom_addr=args.custom_addr,
+                        silent=args.silent)
     server.proc.join()
 
 
@@ -51,6 +52,8 @@ def main(args):
                          and ROCM compilers.")
     parser.add_argument('--custom-addr', type=str,
                         help="Custom IP Address to Report to RPC Tracker")
+    parser.add_argument('--silent', action='store_true',
+                        help="Whether run in silent mode.")
 
     parser.set_defaults(fork=True)
     args = parser.parse_args()
@@ -62,6 +65,7 @@ def main(args):
             )
         multiprocessing.set_start_method('spawn')
     else:
-        logging.info("If you are running ROCM/Metal, \
-        fork with cause compiler internal error. Try to launch with arg ```--no-fork```")
+        if not args.silent:
+            logging.info("If you are running ROCM/Metal, fork will cause "
+                         "compiler internal error. Try to launch with arg ```--no-fork```")
     main(args)
diff --git a/python/tvm/exec/rpc_tracker.py b/python/tvm/exec/rpc_tracker.py
index 3b76f57eb689..3e4c63e20f9b 100644
--- a/python/tvm/exec/rpc_tracker.py
+++ b/python/tvm/exec/rpc_tracker.py
@@ -11,7 +11,8 @@
 
 def main(args):
     """Main funciton"""
-    tracker = Tracker(args.host, port=args.port)
+    tracker = Tracker(args.host, port=args.port, port_end=args.port_end,
+                      silent=args.silent)
     tracker.proc.join()
 
 
@@ -21,10 +22,15 @@ def main(args):
                         help='the hostname of the tracker')
     parser.add_argument('--port', type=int, default=9190,
                         help='The port of the PRC')
+    parser.add_argument('--port-end', type=int, default=9199,
+                        help='The end search port of the PRC')
     parser.add_argument('--no-fork', dest='fork', action='store_false',
                         help="Use spawn mode to avoid fork. This option \
                          is able to avoid potential fork problems with Metal, OpenCL \
                          and ROCM compilers.")
+    parser.add_argument('--silent', action='store_true',
+                        help="Whether run in silent mode.")
+
     parser.set_defaults(fork=True)
     args = parser.parse_args()
     logging.basicConfig(level=logging.INFO)
@@ -35,6 +41,7 @@ def main(args):
             )
         multiprocessing.set_start_method('spawn')
     else:
-        logging.info("If you are running ROCM/Metal, \
-        fork with cause compiler internal error. Try to launch with arg ```--no-fork```")
+        if not args.silent:
+            logging.info("If you are running ROCM/Metal, fork will cause "
+                         "compiler internal error. Try to launch with arg ```--no-fork```")
     main(args)
diff --git a/src/runtime/rpc/rpc_server_env.cc b/src/runtime/rpc/rpc_server_env.cc
index 17ee2abd97b1..a995a953bf79 100644
--- a/src/runtime/rpc/rpc_server_env.cc
+++ b/src/runtime/rpc/rpc_server_env.cc
@@ -20,7 +20,6 @@ TVM_REGISTER_GLOBAL("tvm.contrib.rpc.server.upload").
 set_body([](TVMArgs args, TVMRetValue *rv) {
     std::string file_name = RPCGetPath(args[0]);
     std::string data = args[1];
-    LOG(INFO) << "Upload " << file_name << "... nbytes=" << data.length();
     SaveBinaryToFile(file_name, data);
   });
 

From d1530ec375a250086db88f6c3a631904422eabb9 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 12 Jun 2018 22:36:55 -0700
Subject: [PATCH 715/949] [BUILD] Enable path option for ROCM, CUDA, Vulkan,
 simplify optional build (#1270)

---
 CMakeLists.txt                      |  4 ++
 cmake/config.cmake                  | 25 ++++++++---
 cmake/modules/CUDA.cmake            | 56 +++++-------------------
 cmake/modules/LLVM.cmake            |  7 +--
 cmake/modules/Metal.cmake           |  1 -
 cmake/modules/OpenCL.cmake          |  1 -
 cmake/modules/OpenGL.cmake          |  1 -
 cmake/modules/ROCM.cmake            | 30 +++++++------
 cmake/modules/Vulkan.cmake          | 23 +++-------
 cmake/util/FindCUDA.cmake           | 68 +++++++++++++++++++++++++++++
 cmake/util/FindLLVM.cmake           | 18 ++++----
 cmake/util/FindROCM.cmake           | 41 +++++++++++++++++
 cmake/util/FindVulkan.cmake         | 50 +++++++++++++++++++++
 src/codegen/codegen_metal.cc        | 26 +++++++++++
 src/codegen/codegen_opencl.cc       | 23 ++++++++++
 src/codegen/codegen_opengl.cc       | 17 ++++++++
 src/codegen/llvm/codegen_amdgpu.cc  | 16 -------
 src/codegen/opt/build_metal.h       | 50 ---------------------
 src/codegen/opt/build_metal_off.cc  | 20 +++++++--
 src/codegen/opt/build_metal_on.cc   |  6 ---
 src/codegen/opt/build_opencl.h      | 48 --------------------
 src/codegen/opt/build_opencl_off.cc | 20 +++++++--
 src/codegen/opt/build_opencl_on.cc  |  6 ---
 src/codegen/opt/build_opengl.h      | 39 -----------------
 src/codegen/opt/build_opengl_off.cc | 20 +++++++--
 src/codegen/opt/build_opengl_on.cc  |  6 ---
 src/codegen/opt/build_rocm_off.cc   | 30 +++++++++++++
 27 files changed, 374 insertions(+), 278 deletions(-)
 create mode 100644 cmake/util/FindCUDA.cmake
 create mode 100644 cmake/util/FindROCM.cmake
 create mode 100644 cmake/util/FindVulkan.cmake
 delete mode 100644 src/codegen/opt/build_metal.h
 delete mode 100644 src/codegen/opt/build_metal_on.cc
 delete mode 100644 src/codegen/opt/build_opencl.h
 delete mode 100644 src/codegen/opt/build_opencl_on.cc
 delete mode 100644 src/codegen/opt/build_opengl.h
 delete mode 100644 src/codegen/opt/build_opengl_on.cc
 create mode 100644 src/codegen/opt/build_rocm_off.cc

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 84ce87d84e76..c53fb1cfe8cd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,7 +3,11 @@ project(tvm C CXX)
 
 # Utility functions
 include(cmake/util/Util.cmake)
+include(cmake/util/FindCUDA.cmake)
+include(cmake/util/FindVulkan.cmake)
 include(cmake/util/FindLLVM.cmake)
+include(cmake/util/FindROCM.cmake)
+
 
 if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/build/private/local_config.cmake)
   include(${CMAKE_CURRENT_SOURCE_DIR}/build/private/local_config.cmake)
diff --git a/cmake/config.cmake b/cmake/config.cmake
index db7d800e918a..1f02bfd7d153 100644
--- a/cmake/config.cmake
+++ b/cmake/config.cmake
@@ -22,12 +22,22 @@
 #---------------------------------------------
 # Backend runtimes.
 #---------------------------------------------
-# whether enable CUDA during compile
+
+# Whether enable CUDA during compile,
+#
+# Possible values:
+# - ON: enable CUDA with cmake's auto search
+# - OFF: disbale CUDA
+# - /path/to/cuda: use specific path to cuda toolkit
 set(USE_CUDA OFF)
 
-# ROCM
+# Whether enable ROCM runtime
+#
+# Possible values:
+# - ON: enable ROCM with cmake's auto search
+# - OFF: disbale ROCM
+# - /path/to/rocm: use specific path to rocm
 set(USE_ROCM OFF)
-set(ROCM_PATH "/opt/rocm")
 
 # Whether enable OpenCL runtime
 set(USE_OPENCL OFF)
@@ -36,6 +46,11 @@ set(USE_OPENCL OFF)
 set(USE_METAL OFF)
 
 # Whether enable Vulkan runtime
+#
+# Possible values:
+# - ON: enable Vulkan with cmake's auto search
+# - OFF: disbale vulkan
+# - /path/to/vulkan-sdk: use specific path to vulkan-sdk
 set(USE_VULKAN OFF)
 
 # Whether enable OpenGL runtime
@@ -54,9 +69,9 @@ set(USE_GRAPH_RUNTIME_DEBUG OFF)
 # Requires LLVM version >= 4.0
 #
 # Possible values:
-# - ON: enable llvm with cmake's find llvm
+# - ON: enable llvm with cmake's find search
 # - OFF: disbale llvm
-# - /path/to/llvm-config enable specific LLVM when multiple llvm-dev is available.
+# - /path/to/llvm-config: enable specific LLVM when multiple llvm-dev is available.
 set(USE_LLVM OFF)
 
 #---------------------------------------------
diff --git a/cmake/modules/CUDA.cmake b/cmake/modules/CUDA.cmake
index 83ffac79f54e..70c8c8eebe28 100644
--- a/cmake/modules/CUDA.cmake
+++ b/cmake/modules/CUDA.cmake
@@ -1,5 +1,5 @@
 # CUDA Module
-find_package(CUDA QUIET)
+find_cuda(${USE_CUDA})
 
 if(CUDA_FOUND)
   # always set the includedir when cuda is available
@@ -8,69 +8,33 @@ if(CUDA_FOUND)
 endif(CUDA_FOUND)
 
 if(USE_CUDA)
-  find_package(CUDA REQUIRED)
-  # Find CUDA doesn't find all the libraries we need, add the extra ones
-  find_library(CUDA_CUDA_LIBRARIES cuda
-    PATHS ${CUDA_TOOLKIT_ROOT_DIR}
-    PATH_SUFFIXES lib lib64 targets/x86_64-linux/lib targets/x86_64-linux/lib/stubs)
-  find_library(CUDA_NVRTC_LIBRARIES nvrtc
-    PATHS ${CUDA_TOOLKIT_ROOT_DIR}
-    PATH_SUFFIXES lib lib64 targets/x86_64-linux/lib targets/x86_64-linux/lib/stubs)
-  if(CUDA_CUDA_LIBRARIES)
-    set(CUDA_CUDA_LIBRARY ${CUDA_CUDA_LIBRARIES})
+  if(NOT CUDA_FOUND)
+    message(FATAL_ERROR "Cannot find CUDA, USE_CUDA=" ${USE_CUDA})
   endif()
   message(STATUS "Build with CUDA support")
   file(GLOB RUNTIME_CUDA_SRCS src/runtime/cuda/*.cc)
-  list(APPEND TVM_RUNTIME_LINKER_LIBS ${CUDA_CUDART_LIBRARY})
-  list(APPEND TVM_RUNTIME_LINKER_LIBS ${CUDA_CUDA_LIBRARY})
   list(APPEND RUNTIME_SRCS ${RUNTIME_CUDA_SRCS})
   list(APPEND COMPILER_SRCS src/codegen/opt/build_cuda_on.cc)
 
-  if(MSVC)
-    find_library(CUDA_NVRTC_LIB nvrtc
-      ${CUDA_TOOLKIT_ROOT_DIR}/lib/x64
-      ${CUDA_TOOLKIT_ROOT_DIR}/lib/win32)
-    list(APPEND TVM_LINKER_LIBS ${CUDA_NVRTC_LIB})
-  else(MSVC)
-    find_library(CUDA_NVRTC_LIB nvrtc
-      ${CUDA_TOOLKIT_ROOT_DIR}/lib64
-      ${CUDA_TOOLKIT_ROOT_DIR}/lib)
-    list(APPEND TVM_LINKER_LIBS ${CUDA_NVRTC_LIB})
-  endif(MSVC)
+  list(APPEND TVM_LINKER_LIBS ${CUDA_NVRTC_LIBRARY})
+  list(APPEND TVM_RUNTIME_LINKER_LIBS ${CUDA_CUDART_LIBRARY})
+  list(APPEND TVM_RUNTIME_LINKER_LIBS ${CUDA_CUDA_LIBRARY})
+  list(APPEND TVM_RUNTIME_LINKER_LIBS ${CUDA_NVRTC_LIBRARY})
 
   if(USE_CUDNN)
     message(STATUS "Build with cuDNN support")
     file(GLOB CONTRIB_CUDNN_SRCS src/contrib/cudnn/*.cc)
     list(APPEND RUNTIME_SRCS ${CONTRIB_CUDNN_SRCS})
-    if(MSVC)
-      find_library(CUDA_CUDNN_LIB cudnn
-        ${CUDA_TOOLKIT_ROOT_DIR}/lib/x64
-        ${CUDA_TOOLKIT_ROOT_DIR}/lib/win32)
-      list(APPEND TVM_RUNTIME_LINKER_LIBS ${CUDA_CUDNN_LIB})
-    else(MSVC)
-      find_library(CUDA_CUDNN_LIB cudnn
-        ${CUDA_TOOLKIT_ROOT_DIR}/lib64
-        ${CUDA_TOOLKIT_ROOT_DIR}/lib)
-      list(APPEND TVM_RUNTIME_LINKER_LIBS ${CUDA_CUDNN_LIB})
-    endif(MSVC)
+    list(APPEND TVM_RUNTIME_LINKER_LIBS ${CUDA_CUDNN_LIBRARY})
   endif(USE_CUDNN)
 
   if(USE_CUBLAS)
     message(STATUS "Build with cuBLAS support")
     file(GLOB CONTRIB_CUBLAS_SRCS src/contrib/cublas/*.cc)
     list(APPEND RUNTIME_SRCS ${CONTRIB_CUBLAS_SRCS})
-    if(MSVC)
-      find_library(CUDA_CUBLAS_LIB cublas
-        ${CUDA_TOOLKIT_ROOT_DIR}/lib/x64
-        ${CUDA_TOOLKIT_ROOT_DIR}/lib/win32)
-      list(APPEND TVM_RUNTIME_LINKER_LIBS ${CUDA_CUBLAS_LIB})
-    else(MSVC)
-      find_library(CUDA_CUBLAS_LIB cublas
-        ${CUDA_TOOLKIT_ROOT_DIR}/lib64
-        ${CUDA_TOOLKIT_ROOT_DIR}/lib)
-      list(APPEND TVM_RUNTIME_LINKER_LIBS ${CUDA_CUBLAS_LIB})
-    endif(MSVC)
+    list(APPEND TVM_RUNTIME_LINKER_LIBS ${CUDA_CUBLAS_LIBRARY})
   endif(USE_CUBLAS)
+
 else(USE_CUDA)
   list(APPEND COMPILER_SRCS src/codegen/opt/build_cuda_off.cc)
 endif(USE_CUDA)
diff --git a/cmake/modules/LLVM.cmake b/cmake/modules/LLVM.cmake
index 2ea6b6d9e075..3e896a601056 100644
--- a/cmake/modules/LLVM.cmake
+++ b/cmake/modules/LLVM.cmake
@@ -2,12 +2,7 @@
 add_definitions(-DDMLC_USE_FOPEN64=0)
 
 if(NOT USE_LLVM STREQUAL "OFF")
-  if(NOT USE_LLVM STREQUAL "ON")
-    set(LLVM_CONFIG "${USE_LLVM}")
-  else()
-    set(LLVM_CONFIG "")
-  endif()
-  find_llvm()
+  find_llvm(${USE_LLVM})
   include_directories(${LLVM_INCLUDE_DIRS})
   add_definitions(${LLVM_DEFINITIONS})
   message(STATUS "Build with LLVM " ${LLVM_PACKAGE_VERSION})
diff --git a/cmake/modules/Metal.cmake b/cmake/modules/Metal.cmake
index d48155208657..27aa5a226f2b 100644
--- a/cmake/modules/Metal.cmake
+++ b/cmake/modules/Metal.cmake
@@ -5,7 +5,6 @@ if(USE_METAL)
   file(GLOB RUNTIME_METAL_SRCS src/runtime/metal/*.mm)
   list(APPEND TVM_RUNTIME_LINKER_LIBS ${METAL_LIB} ${FOUNDATION_LIB})
   list(APPEND RUNTIME_SRCS ${RUNTIME_METAL_SRCS})
-  list(APPEND COMPILER_SRCS src/codegen/opt/build_metal_on.cc)
 
   if(USE_MPS)
     file(GLOB MPS_CONTRIB_SRC src/contrib/mps/*.mm)
diff --git a/cmake/modules/OpenCL.cmake b/cmake/modules/OpenCL.cmake
index 93e07101b063..db7a1834feb1 100644
--- a/cmake/modules/OpenCL.cmake
+++ b/cmake/modules/OpenCL.cmake
@@ -13,7 +13,6 @@ if(USE_OPENCL)
   file(GLOB RUNTIME_OPENCL_SRCS src/runtime/opencl/*.cc)
   list(APPEND TVM_RUNTIME_LINKER_LIBS ${OpenCL_LIBRARIES})
   list(APPEND RUNTIME_SRCS ${RUNTIME_OPENCL_SRCS})
-  list(APPEND COMPILER_SRCS src/codegen/opt/build_opencl_on.cc)
 else()
   list(APPEND COMPILER_SRCS src/codegen/opt/build_opencl_off.cc)
 endif(USE_OPENCL)
diff --git a/cmake/modules/OpenGL.cmake b/cmake/modules/OpenGL.cmake
index d43b70351152..2b62c9f302d4 100644
--- a/cmake/modules/OpenGL.cmake
+++ b/cmake/modules/OpenGL.cmake
@@ -13,7 +13,6 @@ if(USE_OPENGL)
   file(GLOB RUNTIME_OPENGL_SRCS src/runtime/opengl/*.cc)
   list(APPEND TVM_RUNTIME_LINKER_LIBS ${OpenGL_LIBRARIES} glfw)
   list(APPEND RUNTIME_SRCS ${RUNTIME_OPENGL_SRCS})
-  list(APPEND COMPILER_SRCS src/codegen/opt/build_opengl_on.cc)
 else(USE_OPENGL)
   list(APPEND COMPILER_SRCS src/codegen/opt/build_opengl_off.cc)
 endif(USE_OPENGL)
diff --git a/cmake/modules/ROCM.cmake b/cmake/modules/ROCM.cmake
index d3d4e5cc21f0..0e45fdac66d9 100644
--- a/cmake/modules/ROCM.cmake
+++ b/cmake/modules/ROCM.cmake
@@ -1,34 +1,36 @@
 # ROCM Module
-if(NOT ROCM_PATH STREQUAL "")
-  include_directories(${ROCM_PATH}/include)
-  set(ROCM_LIB_PATH ${ROCM_PATH}/lib)
-else()
-  set(ROCM_LIB_PATH /lib)
-endif()
+find_rocm(${USE_ROCM})
+
+if(ROCM_FOUND)
+  # always set the includedir
+  # avoid global retrigger of cmake
+  include_directories(${ROCM_INCLUDE_DIRS})
+  add_definitions(-D__HIP_PLATFORM_HCC__=1)
+endif(ROCM_FOUND)
+
 
 if(USE_ROCM)
+  if(NOT ROCM_FOUND)
+    message(FATAL_ERROR "Cannot find ROCM, USE_ROCM=" ${USE_ROCM})
+  endif()
   message(STATUS "Build with ROCM support")
-  find_library(ROCM_LIBS hip_hcc ${ROCM_LIB_PATH})
   file(GLOB RUNTIME_ROCM_SRCS src/runtime/rocm/*.cc)
-  list(APPEND TVM_RUNTIME_LINKER_LIBS ${ROCM_LIBS})
   list(APPEND RUNTIME_SRCS ${RUNTIME_ROCM_SRCS})
-  add_definitions(-DTVM_ROCM_RUNTIME=1 -D__HIP_PLATFORM_HCC__=1)
+  list(APPEND TVM_RUNTIME_LINKER_LIBS ${ROCM_HIPHCC_LIBRARY})
 
   if(USE_MIOPEN)
     message(STATUS "Build with MIOpen support")
     file(GLOB MIOPEN_CONTRIB_SRCS src/contrib/miopen/*.cc)
     list(APPEND RUNTIME_SRCS ${MIOPEN_CONTRIB_SRCS})
-    find_library(MIOPEN_LIBS MIOpen ${ROCM_LIB_PATH})
-    list(APPEND TVM_RUNTIME_LINKER_LIBS ${MIOPEN_LIBS})
+    list(APPEND TVM_RUNTIME_LINKER_LIBS ${ROCM_MIOPEN_LIBRARY})
   endif(USE_MIOPEN)
 
   if(USE_ROCBLAS)
     message(STATUS "Build with RocBLAS support")
     file(GLOB ROCBLAS_CONTRIB_SRCS src/contrib/rocblas/*.cc)
     list(APPEND RUNTIME_SRCS ${ROCBLAS_CONTRIB_SRCS})
-    find_library(ROCBLAS_LIBS rocblas ${ROCM_LIB_PATH})
-    list(APPEND TVM_RUNTIME_LINKER_LIBS ${ROCBLAS_LIBS})
+    list(APPEND TVM_RUNTIME_LINKER_LIBS ${ROCM_ROCBLAS_LIBRARY})
   endif(USE_ROCBLAS)
 else(USE_ROCM)
-  add_definitions(-DTVM_ROCM_RUNTIME=0)
+  list(APPEND COMPILER_SRCS src/codegen/opt/build_rocm_off.cc)
 endif(USE_ROCM)
diff --git a/cmake/modules/Vulkan.cmake b/cmake/modules/Vulkan.cmake
index 0dae5fe1926a..4093f88f2e10 100644
--- a/cmake/modules/Vulkan.cmake
+++ b/cmake/modules/Vulkan.cmake
@@ -1,31 +1,22 @@
 # Be compatible with older version of CMake
-if(NOT $ENV{VULKAN_SDK} STREQUAL "")
-  set(Vulkan_INCLUDE_DIRS $ENV{VULKAN_SDK}/include)
-  set(Vulkan_FOUND ON)
-else()
-  find_package(Vulkan QUIET)
-endif()
+find_vulkan(${USE_VULKAN})
 
 if(Vulkan_FOUND)
-  # always set the includedir when cuda is available
+  # always set the includedir
   # avoid global retrigger of cmake
   include_directories(${Vulkan_INCLUDE_DIRS})
 endif(Vulkan_FOUND)
 
 if(USE_VULKAN)
-  if(NOT $ENV{VULKAN_SDK} STREQUAL "")
-    find_library(Vulkan_LIBRARY vulkan $ENV{VULKAN_SDK}/lib)
-  else()
-    find_package(Vulkan REQUIRED)
+  if(NOT Vulkan_FOUND)
+    message(FATAL_ERROR "Cannot find Vulkan, USE_VULKAN=" ${USE_VULKAN})
   endif()
   message(STATUS "Build with VULKAN support")
   file(GLOB RUNTIME_VULKAN_SRCS src/runtime/vulkan/*.cc)
   file(GLOB COMPILER_VULKAN_SRCS src/codegen/spirv/*.cc)
-  list(APPEND TVM_RUNTIME_LINKER_LIBS ${Vulkan_LIBRARY})
   list(APPEND RUNTIME_SRCS ${RUNTIME_VULKAN_SRCS})
   list(APPEND COMPILER_SRCS ${COMPILER_VULKAN_SRCS})
-  get_filename_component(VULKAN_LIB_PATH ${Vulkan_LIBRARY} DIRECTORY)
-  find_library(SPIRV_TOOLS_LIB SPIRV-Tools
-               ${VULKAN_LIB_PATH}/spirv-tools)
-  list(APPEND TVM_LINKER_LIBS ${SPIRV_TOOLS_LIB})
+
+  list(APPEND TVM_LINKER_LIBS ${Vulkan_SPIRV_TOOLS_LIBRARY})
+  list(APPEND TVM_RUNTIME_LINKER_LIBS ${Vulkan_LIBRARY})
 endif(USE_VULKAN)
diff --git a/cmake/util/FindCUDA.cmake b/cmake/util/FindCUDA.cmake
new file mode 100644
index 000000000000..41936acca217
--- /dev/null
+++ b/cmake/util/FindCUDA.cmake
@@ -0,0 +1,68 @@
+#######################################################
+# Enhanced version of find CUDA.
+#
+# Usage:
+#   find_cuda(${USE_CUDA})
+#
+# - When USE_CUDA=ON, use auto search
+# - When USE_CUDA=/path/to/cuda-path, use the cuda path
+#
+# Provide variables:
+#
+# - CUDA_FOUND
+# - CUDA_INCLUDE_DIRS
+# - CUDA_TOOLKIT_ROOT_DIR
+# - CUDA_CUDA_LIBRARY
+# - CUDA_CUDART_LIBRARY
+# - CUDA_NVRTC_LIBRARY
+# - CUDA_CUDNN_LIBRARY
+# - CUDA_CUBLAS_LIBRARY
+#
+macro(find_cuda use_cuda)
+  set(__use_cuda ${use_cuda})
+  if(__use_cuda STREQUAL "ON")
+    find_package(CUDA QUIET)
+  elseif(IS_DIRECTORY ${__use_cuda})
+    set(CUDA_TOOLKIT_ROOT_DIR ${__use_cuda})
+    message(STATUS "Custom CUDA_PATH=" ${CUDA_TOOLKIT_ROOT_DIR})
+    set(CUDA_INCLUDE_DIRS ${CUDA_TOOLKIT_ROOT_DIR}/include)
+    set(CUDA_FOUND TRUE)
+    if(MSVC)
+      find_library(CUDA_CUDAT_LIBRARY cudart
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib64
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib)
+    else(MSVC)
+      find_library(CUDA_CUDAT_LIBRARY cudart
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib64
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib)
+    endif(MSVC)
+  endif()
+
+  # additional libraries
+  if(CUDA_FOUND)
+    if(MSVC)
+      find_library(CUDA_NVRTC_LIBRARY cuda
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib/x64
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib/win32)
+      find_library(CUDA_NVRTC_LIBRARY nvrtc
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib/x64
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib/win32)
+      find_library(CUDA_CUDNN_LIBRARY cudnn
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib/x64
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib/win32)
+    else(MSVC)
+      find_library(_CUDA_CUDA_LIBRARY cuda
+        PATHS ${CUDA_TOOLKIT_ROOT_DIR}
+        PATH_SUFFIXES lib lib64 targets/x86_64-linux/lib targets/x86_64-linux/lib/stubs)
+      if(_CUDA_CUDA_LIBRARY)
+        set(CUDA_CUDA_LIBRARY ${_CUDA_CUDA_LIBRARY})
+      endif()
+      find_library(CUDA_NVRTC_LIBRARY nvrtc
+        PATHS ${CUDA_TOOLKIT_ROOT_DIR}
+        PATH_SUFFIXES lib lib64 targets/x86_64-linux/lib targets/x86_64-linux/lib/stubs)
+      find_library(CUDA_CUDNN_LIBRARY cudnn
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib64
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib)
+    endif(MSVC)
+  endif(CUDA_FOUND)
+endmacro(find_cuda)
diff --git a/cmake/util/FindLLVM.cmake b/cmake/util/FindLLVM.cmake
index d2707e522c56..9a38ab07165f 100644
--- a/cmake/util/FindLLVM.cmake
+++ b/cmake/util/FindLLVM.cmake
@@ -1,24 +1,26 @@
 #######################################################
-# Enhanced version of find llvm that allows set of LLVM_CONFIG
-# When LLVM_CONFIG_PATH is AUTO,
-# it defaults to system find llvm
+# Enhanced version of find llvm.
 #
 # Usage:
-#   find_llvm(LLVM_CONFIG_PATH)
+#   find_llvm(${USE_LLVM})
+#
+# - When USE_LLVM=ON, use auto search
+# - When USE_LLVM=/path/to/llvm-config, use corresponding config
 #
 # Provide variables:
 # - LLVM_INCLUDE_DIRS
 # - LLVM_LIBS
 # - LLVM_DEFINITIONS
-# - LLVM_VERSION_CONCAT
+# - TVM_LLVM_VERISON
 #
-macro(find_llvm)
-  if(LLVM_CONFIG STREQUAL "")
+macro(find_llvm use_llvm)
+  set(LLVM_CONFIG ${use_llvm})
+  if(LLVM_CONFIG STREQUAL "ON")
     find_package(LLVM REQUIRED CONFIG)
     llvm_map_components_to_libnames(LLVM_LIBS all)
     list(REMOVE_ITEM LLVM_LIBS LTO)
     set(TVM_LLVM_VERSION ${LLVM_VERSION_MAJOR}${LLVM_VERSION_MINOR})
-  else()
+  elseif(NOT LLVM_CONFIG STREQUAL "OFF")
     # use llvm config
     message(STATUS "Use llvm-config=" ${LLVM_CONFIG})
     execute_process(COMMAND ${LLVM_CONFIG} --includedir
diff --git a/cmake/util/FindROCM.cmake b/cmake/util/FindROCM.cmake
new file mode 100644
index 000000000000..235969813382
--- /dev/null
+++ b/cmake/util/FindROCM.cmake
@@ -0,0 +1,41 @@
+#######################################################
+# Enhanced version of find rocm.
+#
+# Usage:
+#   find_rocm(${USE_ROCM})
+#
+# - When USE_VULKAN=ON, use auto search
+# - When USE_VULKAN=/path/to/vulkan-sdk-path, use the sdk
+#
+# Provide variables:
+#
+# - ROCM_FOUND
+# - ROCM_INCLUDE_DIRS
+# - ROCM_HIPHCC_LIBRARY
+# - ROCM_MIOPEN_LIBRARY
+# - ROCM_ROCBLAS_LIBRARY
+#
+
+macro(find_rocm use_rocm)
+  set(__use_rocm ${use_rocm})
+  if(IS_DIRECTORY ${__use_rocm})
+    set(__rocm_sdk ${__use_rocm})
+    message(STATUS "Custom ROCM SDK PATH=" ${__use_rocm})
+   elseif(IS_DIRECTORY $ENV{ROCM_PATH})
+     set(__rocm_sdk $ENV{ROCM_PATH})
+   elseif(IS_DIRECTORY /opt/rocm)
+     set(__rocm_sdk /opt/rocm)
+   else()
+     set(__rocm_sdk "")
+   endif()
+
+   if(__rocm_sdk)
+     set(ROCM_INCLUDE_DIRS ${__rocm_sdk}/include)
+     find_library(ROCM_HIPHCC_LIBRARY hip_hcc ${__rocm_sdk}/lib)
+     find_library(ROCM_MIOPEN_LIBRARY MIOpen ${__rocm_sdk}/lib)
+     find_library(ROCM_ROCBLAS_LIBRARY rocblas ${__rocm_sdk}/lib)
+     if(ROCM_HIPHCC_LIBRARY)
+       set(ROCM_FOUND TRUE)
+     endif()
+   endif(__rocm_sdk)
+endmacro(find_rocm)
diff --git a/cmake/util/FindVulkan.cmake b/cmake/util/FindVulkan.cmake
new file mode 100644
index 000000000000..5dfd89891de1
--- /dev/null
+++ b/cmake/util/FindVulkan.cmake
@@ -0,0 +1,50 @@
+#######################################################
+# Enhanced version of find Vulkan.
+#
+# Usage:
+#   find_vulkan(${USE_VULKAN})
+#
+# - When USE_VULKAN=ON, use auto search
+# - When USE_VULKAN=/path/to/vulkan-sdk-path, use the sdk
+#
+# Provide variables:
+#
+# - Vulkan_FOUND
+# - Vulkan_INCLUDE_DIRS
+# - Vulkan_LIBRARY
+# - Vulkan_SPIRV_TOOLS_LIBRARY
+#
+
+macro(find_vulkan use_vulkan)
+  set(__use_vulkan ${use_vulkan})
+  if(IS_DIRECTORY ${__use_vulkan})
+    set(__vulkan_sdk ${__use_vulkan})
+    message(STATUS "Custom Vulkan SDK PATH=" ${__use_vulkan})
+   elseif(IS_DIRECTORY $ENV{VULKAN_SDK})
+     set(__vulkan_sdk $ENV{VULKAN_SDK})
+   else()
+     set(__vulkan_sdk "")
+   endif()
+
+   if(__vulkan_sdk)
+     set(Vulkan_INCLUDE_DIRS ${__vulkan_sdk}/include)
+     find_library(Vulkan_LIBRARY vulkan ${__vulkan_sdk}/lib)
+     if(Vulkan_LIBRARY)
+       set(Vulkan_FOUND TRUE)
+     endif()
+   endif(__vulkan_sdk)
+
+   # resort to find vulkan of option is on
+   if(NOT Vulkan_FOUND)
+     if(__use_vulkan STREQUAL "ON")
+       find_package(Vulkan QUIET)
+     endif()
+   endif()
+   # additional libraries
+
+  if(Vulkan_FOUND)
+    get_filename_component(VULKAN_LIBRARY_PATH ${Vulkan_LIBRARY} DIRECTORY)
+    find_library(Vulkan_SPIRV_TOOLS_LIBRARY SPIRV-Tools
+      ${VULKAN_LIBRARY_PATH}/spirv-tools)
+  endif(Vulkan_FOUND)
+endmacro(find_vulkan)
diff --git a/src/codegen/codegen_metal.cc b/src/codegen/codegen_metal.cc
index 54c0fd90814c..37121ccb755c 100644
--- a/src/codegen/codegen_metal.cc
+++ b/src/codegen/codegen_metal.cc
@@ -6,6 +6,8 @@
 #include <vector>
 #include <string>
 #include "./codegen_metal.h"
+#include "./build_common.h"
+#include "../runtime/metal/metal_module.h"
 #include "../runtime/thread_storage_scope.h"
 
 namespace tvm {
@@ -220,5 +222,29 @@ void CodeGenMetal::VisitExpr_(const Broadcast* op, std::ostream& os) {   // NOLI
   }
   os << ')';
 }
+
+runtime::Module BuildMetal(Array<LoweredFunc> funcs) {
+  using tvm::runtime::Registry;
+  bool output_ssa = false;
+  CodeGenMetal cg;
+  cg.Init(output_ssa);
+  for (LoweredFunc f : funcs) {
+    cg.AddFunction(f);
+  }
+  std::string code = cg.Finish();
+  std::string fmt = "metal";
+  std::string source = "";
+  if (const auto* f = Registry::Get("tvm_callback_metal_compile")) {
+    source = code;
+    code = (*f)(code).operator std::string();
+    fmt = "metallib";
+  }
+  return MetalModuleCreate(code, fmt, ExtractFuncInfo(funcs), source);
+}
+
+TVM_REGISTER_API("codegen.build_metal")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    *rv = BuildMetal(args[0]);
+  });
 }  // namespace codegen
 }  // namespace tvm
diff --git a/src/codegen/codegen_opencl.cc b/src/codegen/codegen_opencl.cc
index 909aacdc4cd2..64e10ffbe290 100644
--- a/src/codegen/codegen_opencl.cc
+++ b/src/codegen/codegen_opencl.cc
@@ -6,7 +6,9 @@
 #include <vector>
 #include <string>
 #include "./codegen_opencl.h"
+#include "./build_common.h"
 #include "../runtime/thread_storage_scope.h"
+#include "../runtime/opencl/opencl_module.h"
 
 namespace tvm {
 namespace codegen {
@@ -202,5 +204,26 @@ void CodeGenOpenCL::VisitExpr_(const Broadcast* op, std::ostream& os) {   // NOL
   }
   os << "))";
 }
+
+
+runtime::Module BuildOpenCL(Array<LoweredFunc> funcs) {
+  using tvm::runtime::Registry;
+  bool output_ssa = false;
+  CodeGenOpenCL cg;
+  cg.Init(output_ssa);
+  for (LoweredFunc f : funcs) {
+    cg.AddFunction(f);
+  }
+  std::string code = cg.Finish();
+  if (const auto* f = Registry::Get("tvm_callback_opencl_postproc")) {
+    code = (*f)(code).operator std::string();
+  }
+  return OpenCLModuleCreate(code, "cl", ExtractFuncInfo(funcs));
+}
+
+TVM_REGISTER_API("codegen.build_opencl")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    *rv = BuildOpenCL(args[0]);
+  });
 }  // namespace codegen
 }  // namespace tvm
diff --git a/src/codegen/codegen_opengl.cc b/src/codegen/codegen_opengl.cc
index 3ad87d52b0f4..5e750a39e598 100644
--- a/src/codegen/codegen_opengl.cc
+++ b/src/codegen/codegen_opengl.cc
@@ -9,6 +9,7 @@
 #include <vector>
 #include <string>
 #include "./codegen_opengl.h"
+#include "./build_common.h"
 #include "../runtime/thread_storage_scope.h"
 
 namespace tvm {
@@ -268,5 +269,21 @@ void CodeGenOpenGL::VisitStmt_(const Evaluate* op) {
   this->stream << GetVarID(buffer) << " = " << PrintExpr(value) << ";\n";
 }
 
+runtime::Module BuildOpenGL(Array<LoweredFunc> funcs) {
+  bool output_ssa = false;
+  CodeGenOpenGL cg;
+  cg.Init(output_ssa);
+  for (LoweredFunc f : funcs) {
+    cg.AddFunction(f);
+  }
+  auto shaders = cg.Finish();
+  return OpenGLModuleCreate(shaders, "gl", ExtractFuncInfo(funcs));
+}
+
+TVM_REGISTER_API("codegen.build_opengl")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+  *rv = BuildOpenGL(args[0]);
+});
+
 }  // namespace codegen
 }  // namespace tvm
diff --git a/src/codegen/llvm/codegen_amdgpu.cc b/src/codegen/llvm/codegen_amdgpu.cc
index 05bc8df355da..9d1decb43227 100644
--- a/src/codegen/llvm/codegen_amdgpu.cc
+++ b/src/codegen/llvm/codegen_amdgpu.cc
@@ -12,10 +12,7 @@
 #include "../build_common.h"
 #include "../codegen_source_base.h"
 #include "../../pass/ir_util.h"
-
-#if TVM_ROCM_RUNTIME
 #include "../../runtime/rocm/rocm_module.h"
-#endif   // TVM_ROCM_RUNTIME
 
 namespace tvm {
 namespace codegen {
@@ -242,20 +239,7 @@ runtime::Module BuildAMDGPU(Array<LoweredFunc> funcs, std::string target) {
 
   std::string hsaco = (*f)(arr);
   std::string ll(data_ll.begin(), data_ll.end());
-
-#if TVM_ROCM_RUNTIME
   return ROCMModuleCreate(hsaco, "hsaco", ExtractFuncInfo(funcs), ll, assembly);
-#else
-  LOG(WARNING) << "ROCM runtime is not enabled, return a source module...";
-  auto fget_source = [ll, assembly](const std::string& format) {
-    if (format.length() == 0) return assembly;
-    if (format == "ll" || format == "llvm") return format;
-    if (format == "asm") return assembly;
-    return std::string("");
-  };
-  return DeviceSourceModuleCreate(
-      hsaco, "hsaco", ExtractFuncInfo(funcs), "hsaco", fget_source);
-#endif   // TVM_ROCM_RUNTIME
 }
 
 TVM_REGISTER_API("codegen.build_rocm")
diff --git a/src/codegen/opt/build_metal.h b/src/codegen/opt/build_metal.h
deleted file mode 100644
index a5c7c0ecf670..000000000000
--- a/src/codegen/opt/build_metal.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/*!
- *  Copyright (c) 2017 by Contributors
- *  Build metal modules from source.
- * \file build_metal.h
- */
-#ifndef TVM_CODEGEN_OPT_BUILD_METAL_H_
-#define TVM_CODEGEN_OPT_BUILD_METAL_H_
-
-#include <string>
-#include "../codegen_metal.h"
-#include "../build_common.h"
-
-#if TVM_METAL_RUNTIME
-#include "../../runtime/metal/metal_module.h"
-#endif   // TVM_METAL_RUNTIME
-
-namespace tvm {
-namespace codegen {
-
-runtime::Module BuildMetal(Array<LoweredFunc> funcs) {
-  using tvm::runtime::Registry;
-  bool output_ssa = false;
-  CodeGenMetal cg;
-  cg.Init(output_ssa);
-  for (LoweredFunc f : funcs) {
-    cg.AddFunction(f);
-  }
-  std::string code = cg.Finish();
-#if TVM_METAL_RUNTIME
-  std::string fmt = "metal";
-  std::string source = "";
-  if (const auto* f = Registry::Get("tvm_callback_metal_compile")) {
-    source = code;
-    code = (*f)(code).operator std::string();
-    fmt = "metallib";
-  }
-  return MetalModuleCreate(code, fmt, ExtractFuncInfo(funcs), source);
-#else
-  LOG(WARNING) << "Metal runtime not enabled, return a source module...";
-  return DeviceSourceModuleCreate(code, "metal", ExtractFuncInfo(funcs), "metal");
-#endif   // TVM_METAL_RUNTIME
-}
-
-TVM_REGISTER_API("codegen.build_metal")
-.set_body([](TVMArgs args, TVMRetValue* rv) {
-    *rv = BuildMetal(args[0]);
-  });
-}  // namespace codegen
-}  // namespace tvm
-#endif  // TVM_CODEGEN_OPT_BUILD_METAL_H_
diff --git a/src/codegen/opt/build_metal_off.cc b/src/codegen/opt/build_metal_off.cc
index a4fc1adc3417..dfa2cc28cc8b 100644
--- a/src/codegen/opt/build_metal_off.cc
+++ b/src/codegen/opt/build_metal_off.cc
@@ -1,6 +1,20 @@
 /*!
  *  Copyright (c) 2018 by Contributors
- *  Build Metal modules off
+ *  Optional module when build metal is switched to off
  */
-#define TVM_METAL_RUNTIME 0
-#include "./build_metal.h"
+#include "../codegen_source_base.h"
+#include "../../runtime/metal/metal_module.h"
+
+namespace tvm {
+namespace runtime {
+
+Module MetalModuleCreate(std::string data,
+                         std::string fmt,
+                         std::unordered_map<std::string, FunctionInfo> fmap,
+                         std::string source) {
+  LOG(WARNING) << "Metal runtime not enabled, return a source module...";
+  return codegen::DeviceSourceModuleCreate(data, fmt, fmap, "metal");
+}
+
+}  // namespace runtime
+}  // namespace tvm
diff --git a/src/codegen/opt/build_metal_on.cc b/src/codegen/opt/build_metal_on.cc
deleted file mode 100644
index 65194884ef7f..000000000000
--- a/src/codegen/opt/build_metal_on.cc
+++ /dev/null
@@ -1,6 +0,0 @@
-/*!
- *  Copyright (c) 2018 by Contributors
- *  Build Metal modules on
- */
-#define TVM_METAL_RUNTIME 1
-#include "./build_metal.h"
diff --git a/src/codegen/opt/build_opencl.h b/src/codegen/opt/build_opencl.h
deleted file mode 100644
index 5756881ae315..000000000000
--- a/src/codegen/opt/build_opencl.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*!
- *  Copyright (c) 2017 by Contributors
- *  Build opencl modules from source.
- * \file build_opencl.h
- */
-#ifndef TVM_CODEGEN_OPT_BUILD_OPENCL_H_
-#define TVM_CODEGEN_OPT_BUILD_OPENCL_H_
-
-#include <tvm/base.h>
-#include <string>
-#include "../codegen_opencl.h"
-#include "../build_common.h"
-
-#if TVM_OPENCL_RUNTIME
-#include "../../runtime/opencl/opencl_module.h"
-#endif   // TVM_OPENCL_RUNTIME
-
-namespace tvm {
-namespace codegen {
-
-runtime::Module BuildOpenCL(Array<LoweredFunc> funcs) {
-  using tvm::runtime::Registry;
-  bool output_ssa = false;
-  CodeGenOpenCL cg;
-  cg.Init(output_ssa);
-  for (LoweredFunc f : funcs) {
-    cg.AddFunction(f);
-  }
-  std::string code = cg.Finish();
-
-  if (const auto* f = Registry::Get("tvm_callback_opencl_postproc")) {
-    code = (*f)(code).operator std::string();
-  }
-#if TVM_OPENCL_RUNTIME
-  return OpenCLModuleCreate(code, "cl", ExtractFuncInfo(funcs));
-#else
-  LOG(WARNING) << "OpenCL runtime not enabled, return a source module...";
-  return DeviceSourceModuleCreate(code, "cl", ExtractFuncInfo(funcs), "opencl");
-#endif   // TVM_OPENCL_RUNTIME
-}
-
-TVM_REGISTER_API("codegen.build_opencl")
-.set_body([](TVMArgs args, TVMRetValue* rv) {
-    *rv = BuildOpenCL(args[0]);
-  });
-}  // namespace codegen
-}  // namespace tvm
-#endif  // TVM_CODEGEN_OPT_BUILD_OPENCL_H_
diff --git a/src/codegen/opt/build_opencl_off.cc b/src/codegen/opt/build_opencl_off.cc
index 0578d23d1b3f..10389cf09e83 100644
--- a/src/codegen/opt/build_opencl_off.cc
+++ b/src/codegen/opt/build_opencl_off.cc
@@ -1,6 +1,20 @@
 /*!
  *  Copyright (c) 2018 by Contributors
- *  Build opencl modules off
+ *  Optional module when build opencl is switched to off
  */
-#define TVM_OPENCL_RUNTIME 0
-#include "./build_opencl.h"
+#include "../codegen_source_base.h"
+#include "../../runtime/opencl/opencl_module.h"
+
+namespace tvm {
+namespace runtime {
+
+Module OpenCLModuleCreate(
+    std::string data,
+    std::string fmt,
+    std::unordered_map<std::string, FunctionInfo> fmap) {
+  LOG(WARNING) << "OpenCL runtime not enabled, return a source module...";
+  return codegen::DeviceSourceModuleCreate(data, fmt, fmap, "opencl");
+}
+
+}  // namespace runtime
+}  // namespace tvm
diff --git a/src/codegen/opt/build_opencl_on.cc b/src/codegen/opt/build_opencl_on.cc
deleted file mode 100644
index 53c157883cf8..000000000000
--- a/src/codegen/opt/build_opencl_on.cc
+++ /dev/null
@@ -1,6 +0,0 @@
-/*!
- *  Copyright (c) 2018 by Contributors
- *  Build opencl modules on
- */
-#define TVM_OPENCL_RUNTIME 1
-#include "./build_opencl.h"
diff --git a/src/codegen/opt/build_opengl.h b/src/codegen/opt/build_opengl.h
deleted file mode 100644
index ed782cc04128..000000000000
--- a/src/codegen/opt/build_opengl.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*!
- *  Copyright (c) 2017 by Contributors
- *  Build opengl modules from source.
- * \file build_opengl.h
- */
-#ifndef TVM_CODEGEN_OPT_BUILD_OPENGL_H_
-#define TVM_CODEGEN_OPT_BUILD_OPENGL_H_
-
-#include <tvm/base.h>
-#include "../codegen_opengl.h"
-#include "../build_common.h"
-
-namespace tvm {
-namespace codegen {
-
-runtime::Module BuildOpenGL(Array<LoweredFunc> funcs) {
-  bool output_ssa = false;
-  CodeGenOpenGL cg;
-  cg.Init(output_ssa);
-  for (LoweredFunc f : funcs) {
-    cg.AddFunction(f);
-  }
-  auto shaders = cg.Finish();
-#if TVM_OPENGL_RUNTIME
-  return OpenGLModuleCreate(shaders, "gl", ExtractFuncInfo(funcs));
-#else
-  LOG(WARNING) << "OpenGL runtime not enabled, return a source module...";
-  auto data = ToJSON(shaders);
-  return DeviceSourceModuleCreate(data, "gl", ExtractFuncInfo(funcs), "opengl");
-#endif  // TVM_OPENGL_RUNTIME
-}
-
-TVM_REGISTER_API("codegen.build_opengl")
-.set_body([](TVMArgs args, TVMRetValue* rv) {
-  *rv = BuildOpenGL(args[0]);
-});
-}  // namespace codegen
-}  // namespace tvm
-#endif  // TVM_CODEGEN_OPT_BUILD_OPENGL_H_
diff --git a/src/codegen/opt/build_opengl_off.cc b/src/codegen/opt/build_opengl_off.cc
index 3a88d6863b5e..3f01dc98f0a5 100644
--- a/src/codegen/opt/build_opengl_off.cc
+++ b/src/codegen/opt/build_opengl_off.cc
@@ -1,6 +1,20 @@
 /*!
  *  Copyright (c) 2018 by Contributors
- *  Build OpenGL modules off
+ *  Optional module when build opencl is switched to off
  */
-#define TVM_OPENGL_RUNTIME 0
-#include "./build_opengl.h"
+#include "../codegen_source_base.h"
+#include "../../runtime/opengl/opengl_module.h"
+
+namespace tvm {
+namespace runtime {
+
+Module OpenGLModuleCreate(std::unordered_map<std::string, OpenGLShader> shaders,
+                          std::string fmt,
+                          std::unordered_map<std::string, FunctionInfo> fmap) {
+  LOG(WARNING) << "OpenGL runtime not enabled, return a source module...";
+  auto data = ToJSON(shaders);
+  return codegen::DeviceSourceModuleCreate(data, "gl", fmap, "opengl");
+}
+
+}  // namespace runtime
+}  // namespace tvm
diff --git a/src/codegen/opt/build_opengl_on.cc b/src/codegen/opt/build_opengl_on.cc
deleted file mode 100644
index 2670feb3d6f2..000000000000
--- a/src/codegen/opt/build_opengl_on.cc
+++ /dev/null
@@ -1,6 +0,0 @@
-/*!
- *  Copyright (c) 2018 by Contributors
- *  Build OpenGL modules on
- */
-#define TVM_OPENGL_RUNTIME 1
-#include "./build_opengl.h"
diff --git a/src/codegen/opt/build_rocm_off.cc b/src/codegen/opt/build_rocm_off.cc
new file mode 100644
index 000000000000..009fd13668d1
--- /dev/null
+++ b/src/codegen/opt/build_rocm_off.cc
@@ -0,0 +1,30 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ *  Optional module when build rocm is switched to off
+ */
+#include "../codegen_source_base.h"
+#include "../../runtime/rocm/rocm_module.h"
+
+namespace tvm {
+namespace runtime {
+
+Module ROCMModuleCreate(
+    std::string data,
+    std::string fmt,
+    std::unordered_map<std::string, FunctionInfo> fmap,
+    std::string rocm_source,
+    std::string assembly) {
+
+  LOG(WARNING) << "ROCM runtime is not enabled, return a source module...";
+  auto fget_source = [rocm_source, assembly](const std::string& format) {
+    if (format.length() == 0) return assembly;
+    if (format == "ll" || format == "llvm") return rocm_source;
+    if (format == "asm") return assembly;
+    return std::string("");
+  };
+  return codegen::DeviceSourceModuleCreate(
+      data, fmt, fmap, "hsaco", fget_source);
+}
+
+}  // namespace runtime
+}  // namespace tvm

From 61d5303cb85522902d4e64e988e619db5a8b5b6d Mon Sep 17 00:00:00 2001
From: Siva <sivar.b@huawei.com>
Date: Wed, 13 Jun 2018 11:07:35 +0530
Subject: [PATCH 716/949] [NNVM][FRONTEND] Tensorflow frontend support (#1188)

---
 nnvm/python/nnvm/compiler/build_module.py     |  22 +
 nnvm/python/nnvm/frontend/__init__.py         |   1 +
 nnvm/python/nnvm/frontend/tensorflow.py       | 651 ++++++++++++++++++
 nnvm/python/nnvm/testing/tf.py                | 229 ++++++
 nnvm/python/nnvm/top/tensor.py                |   9 +
 .../frontend/tensorflow/test_forward.py       | 421 +++++++++++
 tests/scripts/task_python_nnvm.sh             |   3 +
 tutorials/nnvm/from_tensorflow.py             | 171 +++++
 8 files changed, 1507 insertions(+)
 create mode 100644 nnvm/python/nnvm/frontend/tensorflow.py
 create mode 100644 nnvm/python/nnvm/testing/tf.py
 create mode 100644 nnvm/tests/python/frontend/tensorflow/test_forward.py
 create mode 100644 tutorials/nnvm/from_tensorflow.py

diff --git a/nnvm/python/nnvm/compiler/build_module.py b/nnvm/python/nnvm/compiler/build_module.py
index 86fa08ec1bfc..ed75b10414c7 100644
--- a/nnvm/python/nnvm/compiler/build_module.py
+++ b/nnvm/python/nnvm/compiler/build_module.py
@@ -270,6 +270,10 @@ def build(graph, target=None, shape=None, dtype="float32",
     # Apply optimization
     with target:
         graph = optimize(graph, shape, dtype, layout)
+
+    # Clear extra params without nodes.
+    _remove_noref_params(params, graph)
+
     # Precompute prune
     if params and cfg.pass_enabled("PrecomputePrune"):
         graph, params = precompute_prune(graph, params)
@@ -296,6 +300,24 @@ def build(graph, target=None, shape=None, dtype="float32",
         params.update(init_var)
     return graph, libmod, params
 
+def _remove_noref_params(params, graph):
+    """ Helper to clear non referenced params
+
+    Parameters
+    ----------
+    graph : Graph
+        The input graph
+
+    params: dict of str to ndarray
+        The parameter dictionary
+    """
+    arg_list = set(graph.symbol.list_input_names())
+
+    if params:
+        param_keys = list(params.keys())
+        for key in param_keys:
+            if key not in arg_list:
+                params.pop(key)
 
 def _run_graph(graph, params):
     """Helper utility to build and run and get outputs, only use cpu mode.
diff --git a/nnvm/python/nnvm/frontend/__init__.py b/nnvm/python/nnvm/frontend/__init__.py
index 00ed9e51fbfc..80f66c0d35e3 100644
--- a/nnvm/python/nnvm/frontend/__init__.py
+++ b/nnvm/python/nnvm/frontend/__init__.py
@@ -5,3 +5,4 @@
 from .coreml import from_coreml
 from .keras import from_keras
 from .darknet import from_darknet
+from .tensorflow import from_tensorflow
diff --git a/nnvm/python/nnvm/frontend/tensorflow.py b/nnvm/python/nnvm/frontend/tensorflow.py
new file mode 100644
index 000000000000..17f08cc3e4a7
--- /dev/null
+++ b/nnvm/python/nnvm/frontend/tensorflow.py
@@ -0,0 +1,651 @@
+# pylint: disable=import-self, invalid-name, unused-argument
+"""TF: Tensorflow frontend."""
+from __future__ import absolute_import as _abs
+from __future__ import print_function
+
+# Numpy support
+import numpy as np
+
+import tvm
+from .. import symbol as _sym
+from .. import graph as _graph
+from .. compiler import graph_util
+from .common import get_nnvm_op, AttrConverter as AttrConvert
+
+__all__ = ['from_tensorflow']
+
+class AttrCvt(object):
+    """A Wrapper to handle some common jobs:
+    """
+    def __init__(self, op_name, transforms=None,
+                 excludes=None, disables=None, ignores=None,
+                 extras=None, custom_check=None):
+        self._op_name = op_name
+        self._transforms = transforms if transforms else {}
+        self._excludes = excludes if excludes else []
+        self._disables = disables if disables else []
+        self._ignores = ignores if ignores else []
+        self._extras = extras if extras else {}
+        self._custom_check = custom_check
+
+    def __call__(self, inputs, attrs, *args):
+        self._ignores.append('_output_shapes')
+        self._ignores.append('_input_shapes')
+        self._ignores.append('T')
+        self._ignores.append('use_cudnn_on_gpu')
+        return AttrConvert(self._op_name, self._transforms, self._excludes,
+                           self._disables, self._ignores, self._extras,
+                           self._custom_check)(inputs, attrs, *args)
+
+def _get_pad_pair(input1d, kernel1d, stride1d):
+    if input1d % stride1d == 0:
+        pad = max(kernel1d - stride1d, 0)
+    else:
+        pad = max(kernel1d - (input1d % stride1d), 0)
+
+    pad_before = pad // 2
+    pad_after = pad - pad_before
+
+    return [pad_before, pad_after]
+
+def _math_name_picker(surfix):
+    def _impl(attr):
+        return 'broadcast_' + surfix
+    return _impl
+
+def _dimension_picker(prefix, surfix=''):
+    def _impl(attr):
+        kernel = attr['kernel_shape']
+        if len(kernel) == 2:
+            return prefix + '2d' + surfix
+        else:
+            raise NotImplementedError("Only 2d kernel supported.")
+    return _impl
+
+def _dimension_constraint():
+    def _dim_check(attrs):
+        if len(attrs['kernel_shape']) == 2:
+            return True
+        return False
+    return _dim_check, "Only 2d kernel supported."
+
+def _infer_channels(inputs, params, transpose=False):
+    """A hack for getting 'channles' or 'units' since tensorflow don't provide
+    these attributes. We check the shape of weights provided to get the number.
+    """
+    g = _graph.create(inputs)
+    shape_dict = {k: v.shape for k, v in params.items()}
+    _, out_shapes = graph_util.infer_shape(g, **shape_dict)
+    channels = out_shapes[0][0] if not transpose else out_shapes[0][1]
+    return channels
+
+def _rsqrt():
+    def _impl(inputs, attr, *args):
+        return AttrCvt(op_name="__pow_scalar__", extras={'scalar': -0.5})(inputs, attr)
+    return _impl
+
+def _elemwise(name):
+    def _impl(inputs, attr, *args):
+        assert len(inputs) == 2, "Math op take 2 inputs, {} given".format(len(inputs))
+        op_name = _math_name_picker(name)(attr)
+        axis = int(attr.get('axis', 0))
+        conv_ops = ["conv2d", "conv2d_transpose"]
+        if op_name == 'broadcast_add' and inputs[0].attr('op_name') in conv_ops:
+            # TODO: remove hard coded infershape
+            inputs[1] = _sym.expand_dims(inputs[1], axis=axis, num_newaxis=2)
+        return get_nnvm_op(op_name)(*inputs)
+    return _impl
+
+def _pooling(name):
+    def _impl(inputs, attr, params):
+
+        attr['data_format'] = attr['data_format'].decode("utf-8")
+
+        if attr['data_format'] == 'NHWC':
+            attr['kernel_shape'] = (attr['ksize'][1], attr['ksize'][2])
+        elif attr['data_format'] == 'NCHW':
+            attr['kernel_shape'] = (attr['ksize'][2], attr['ksize'][3])
+        else:
+            raise TypeError("Unsupported data_format type : {}".format(attr['data_format']))
+
+        # Fix strides
+        attr['strides'] = (attr['strides'][1], attr['strides'][2])
+
+        # Fix padding
+        input_shapes = attr['_input_shapes'][inputs[0]]
+        attr['padding'] = attr['padding'].decode("utf-8")
+
+        if attr['padding'] == 'VALID':
+            attr['padding'] = [0, 0]
+        elif attr['padding'] == 'SAME':
+            stride_h, stride_w = attr['strides']
+            kernel_h, kernel_w = attr['kernel_shape']
+            if attr['data_format'] == 'NHWC':
+                in_h = input_shapes[0][1]
+                in_w = input_shapes[0][2]
+            else:
+                in_h = input_shapes[0][2]
+                in_w = input_shapes[0][3]
+
+            pad_v = _get_pad_pair(in_h, kernel_h, stride_h)
+            pad_h = _get_pad_pair(in_w, kernel_w, stride_w)
+
+            if attr['data_format'] == 'NHWC':
+                inputs[0] = _sym.pad(data=inputs[0],
+                                     pad_width=((0, 0),
+                                                (pad_v[0], pad_v[1]),
+                                                (pad_h[0], pad_h[1]),
+                                                (0, 0)))
+            else:
+                inputs[0] = _sym.pad(data=inputs[0],
+                                     pad_width=((0, 0),
+                                                (0, 0),
+                                                (pad_v[0], pad_v[1]),
+                                                (pad_h[0], pad_h[1])))
+            attr['padding'] = [0, 0]
+        else:
+            raise TypeError("Unsupported padding type : {}".format(attr['padding']))
+
+        return AttrCvt(
+            op_name=_dimension_picker(name),
+            transforms={
+                'kernel_shape':'pool_size',
+                'data_format':'layout'},
+            ignores=['ksize'],
+            extras={'ceil_mode': False},
+            custom_check=_dimension_constraint())(inputs, attr)
+    return _impl
+
+def _conv():
+    def _impl(inputs, attr, params):
+        attr['data_format'] = attr['data_format'].decode("utf-8")
+
+        # Extract kernel shape from params
+        conv_param_weights = params[inputs[1].list_output_names()[0]]
+
+        if attr['data_format'] == 'NHWC':
+            attr['kernel_shape'] = (conv_param_weights.shape[0], conv_param_weights.shape[1])
+            attr['channels'] = conv_param_weights.shape[3]
+            if 'dilations' in attr:
+                attr['dilations'] = (attr['dilations'][0], attr['dilations'][1])
+        elif attr['data_format'] == 'NCHW':
+            attr['kernel_shape'] = (conv_param_weights.shape[2], conv_param_weights.shape[3])
+            attr['channels'] = conv_param_weights.shape[1]
+            if 'dilations' in attr:
+                attr['dilations'] = (attr['dilations'][2], attr['dilations'][3])
+        else:
+            raise TypeError("Unsupported data format type : {}".format(attr['data_format']))
+
+        # Fix strides
+        attr['strides'] = (attr['strides'][1], attr['strides'][2])
+
+        # Fix padding
+        input_shapes = attr['_input_shapes'][inputs[0]]
+        attr['padding'] = attr['padding'].decode("utf-8")
+
+        if attr['padding'] == 'VALID':
+            attr['padding'] = [0, 0]
+        elif attr['padding'] == 'SAME':
+            stride_h, stride_w = attr['strides']
+            kernel_h, kernel_w = attr['kernel_shape']
+            if attr['data_format'] == 'NHWC':
+                in_h = input_shapes[0][1]
+                in_w = input_shapes[0][2]
+            else:
+                in_h = input_shapes[0][2]
+                in_w = input_shapes[0][3]
+
+            pad_v = _get_pad_pair(in_h, kernel_h, stride_h)
+            pad_h = _get_pad_pair(in_w, kernel_w, stride_w)
+
+            if attr['data_format'] == 'NHWC':
+                inputs[0] = _sym.pad(data=inputs[0],
+                                     pad_width=((0, 0),
+                                                (pad_v[0], pad_v[1]),
+                                                (pad_h[0], pad_h[1]),
+                                                (0, 0)))
+            else:
+                inputs[0] = _sym.pad(data=inputs[0],
+                                     pad_width=((0, 0),
+                                                (0, 0),
+                                                (pad_v[0], pad_v[1]),
+                                                (pad_h[0], pad_h[1])))
+
+            attr['padding'] = [0, 0]
+
+        else:
+            raise TypeError("Unsupported padding type : {}".format(attr['padding']))
+
+        if 'kernel_layout' not in attr:
+            attr['kernel_layout'] = 'HWIO' if attr['data_format'] == 'NHWC' else 'OIHW'
+
+        return AttrCvt(
+            op_name=_dimension_picker('conv'),
+            transforms={
+                'kernel_shape': 'kernel_size',
+                'data_format': 'layout',
+                'dilations': ('dilation', (0, 0)),
+                'group': ('groups', 1)},
+            extras={'use_bias': len(inputs) == 3},
+            custom_check=_dimension_constraint())(inputs, attr)
+    return _impl
+
+def _decode_image():
+    def _impl(inputs, attr, params):
+        # Image decode wrapper: Expecting user to feed decoded input to next layer drop this layer.
+        print("DecodeJpeg: It's a pass through, please handle preprocessing before input")
+        return inputs[0]
+    return _impl
+
+def _cast():
+    def _impl(inputs, attr, params):
+        # Convert from tensorflow Dtype to str
+        attr['DstT'] = attr['DstT'].name
+        return AttrCvt(op_name='cast', transforms={'DstT': 'dtype'}, ignores=['SrcT'])(inputs, attr)
+    return _impl
+
+def _expand_dims():
+    def _impl(inputs, attr, params):
+        dim_input = inputs.pop(1)
+        axis = params[dim_input.list_output_names()[0]]
+        params.pop(dim_input.list_output_names()[0])
+        return AttrCvt(op_name="expand_dims", ignores=['Tdim'],
+                       extras={'axis': axis.asnumpy()[0]})(inputs, attr)
+    return _impl
+
+def _resize_bilinear():
+    def _impl(inputs, attr, params):
+        # Change this when we have corresponding resize bilinear operation.
+        print("ResizeBilinear:Only NN (nearest neighbor) supported in symetric mode of dimensions")
+        print("Change this when we have corresponding resize bilinear operation")
+
+        # NHWC
+        input_shape = attr['_input_shapes'][inputs[0]][0]
+        in_hw = (input_shape[1], input_shape[2])
+        out_hw = params[inputs[1].list_output_names()[0]]
+        inputs.pop(1)
+        attr['layout'] = 'NHWC'
+
+        if in_hw[0] < 0 or in_hw[1] < 0:
+            scale = 1
+        else:
+            # Considering height alone for scale
+            scale = out_hw[0] / in_hw[0]
+
+        return AttrCvt(op_name="upsampling",
+                       ignores=['Tdim', 'align_corners'],
+                       extras={'scale': scale})(inputs, attr)
+    return _impl
+
+def _check_numerics():
+    def _impl(inputs, attr, params):
+        # Making a copy node assuming no need to verify
+        return AttrCvt(op_name="copy", ignores=['message'])(inputs, attr)
+    return _impl
+
+
+def _matmul():
+    def _impl(inputs, attr, params):
+        channels = _infer_channels(inputs[1], params, not attr['transpose_b'])
+        if attr['transpose_a']:
+            inputs[0] = _sym.transpose(inputs[0], axis(1, 0))
+        if not attr['transpose_b']:
+            inputs[1] = _sym.transpose(inputs[1], axes=(1, 0))
+        return AttrCvt(op_name="dense",
+                       extras={'use_bias': False, 'units': channels},
+                       ignores=['transpose_a', 'transpose_b', 'T'])(inputs, attr)
+
+    return _impl
+
+def _identity():
+    def _impl(inputs, attr, params):
+        return inputs[0]
+    return _impl
+
+def _concatV2():
+    def _impl(inputs, attr, params):
+        pop_node = inputs.pop(len(inputs)-1)
+        axis = params[pop_node.list_output_names()[0]]
+        params.pop(pop_node.list_output_names()[0])
+        return AttrCvt(
+            op_name="concatenate", ignores=['T', 'N', 'Tidx'],
+            extras={'axis': axis.asnumpy()[0]})(inputs, attr)
+    return _impl
+
+def _concat():
+    def _impl(inputs, attr, params):
+        pop_node = inputs.pop(0)
+        axis = params[pop_node.list_output_names()[0]]
+        params.pop(pop_node.list_output_names()[0])
+        return AttrCvt(
+            op_name="concatenate", ignores=['N'],
+            extras={'axis': axis.asnumpy()[0]})(inputs, attr)
+    return _impl
+
+def _reshape():
+    def _impl(inputs, attr, params):
+        pop_node = inputs.pop(1)
+        shape_arg = params[pop_node.list_output_names()[0]]
+        params.pop(pop_node.list_output_names()[0])
+        return AttrCvt(
+            op_name="reshape",
+            extras={'shape':tuple(shape_arg.asnumpy())},
+            ignores=['Tshape'])(inputs, attr)
+    return _impl
+
+def _bias_add():
+    def _impl(inputs, attr, params):
+        return _sym.broadcast_add(inputs[0], inputs[1])
+    return _impl
+
+def _squeeze():
+    def _impl(inputs, attr, params):
+        return AttrCvt(
+            op_name="squeeze",
+            transforms={'squeeze_dims':'axis'},
+            ignores=['T'])(inputs, attr)
+    return _impl
+
+def _batch_norm():
+    def _impl(inputs, attr, params):
+        # Rearrange inputs from
+        # (data, moving_mean, moving_variance, beta, gamma)
+        #     to
+        # (data, gamma, beta, moving_mean, moving_var)
+        new_inputs = [inputs[0], inputs[4], inputs[3], inputs[1], inputs[2]]
+
+        return AttrCvt(
+            op_name='batch_norm',
+            transforms={'scale_after_normalization':'scale', 'variance_epsilon':'epsilon'},
+            extras={'axis': 3}, # Fix axis
+            disables=['momentum'])(new_inputs, attr)
+    return _impl
+
+# compatible operators that do NOT require any conversion.
+_identity_list = []
+
+# _convert_map defines maps of name to converter functor(callable)
+# for 1 to 1 mapping, use Renamer if nothing but name is different
+# use AttrCvt if attributes need to be converted
+# for 1 to N mapping(composed), use custom callable functions
+# for N to 1 mapping, currently not supported(?)
+_convert_map = {
+    'AvgPool'                           : _pooling('avg_pool'),
+    'BatchNormWithGlobalNormalization'  : _batch_norm(),
+    'BiasAdd'                           : _bias_add(),
+    'Cast'                              : _cast(),
+    'CheckNumerics'                     : _check_numerics(),
+    'Concat'                            : _concat(),
+    'ConcatV2'                          : _concatV2(),
+    'Conv2D'                            : _conv(),
+    'DecodeJpeg'                        : _decode_image(),
+    'ExpandDims'                        : _expand_dims(),
+    'Identity'                          : _identity(),
+    'MatMul'                            : _matmul(),
+    'MaxPool'                           : _pooling('max_pool'),
+    'Mul'                               : _elemwise('mul'),
+    'Relu'                              : AttrCvt('relu'),
+    'Reshape'                           : _reshape(),
+    'ResizeBilinear'                    : _resize_bilinear(),
+    'Softmax'                           : AttrCvt('softmax', {'axis': ('axis', 1)}),
+    'Sub'                               : _elemwise('sub'),
+    'Add'                               : _elemwise('add'),
+    'Rsqrt'                             : _rsqrt(),
+    'Squeeze'                           : _squeeze(),
+}
+
+
+class GraphProto(object):
+    """ A helper class for handling nnvm graph copying from Tensorflow GraphDef.
+    Definition:
+        https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/framework/graph.proto
+    """
+    def __init__(self):
+        self._nodes = {}
+        self._params = {}
+        self._renames = {}
+        self._replacements = {}
+        self._output_shapes = {}
+        self._num_input = 0
+        self._num_param = 0
+        self._input_node = ''
+
+    def from_tensorflow(self, graph):
+        """Construct nnvm nodes from tensorflow  graph definition - GraphDef.
+
+        Follow the tensorflow graph definition to parse and convert it to NNVM.
+        Some of the assumptions listed below.
+
+            -> First Const or Placeholder node will be considered as graph input.
+            -> Rest all Const nodes are params.
+            -> Last node is assumed as graph output.
+            -> _output_shapes : Attribute should present in the tenserflow forzen graph.
+            -> DecodeJpeg, ResizeBilinear: These are dummy operators.
+                                           Hence user should handle preprocessing outside.
+            -> CheckNumerics: No implementation as of now for this.
+                              Just copies input to output.
+
+
+        Parameters
+        ----------
+        graph : tensorflow graph definition object
+            The loaded tensorflow GraphDef
+
+        Returns
+        -------
+        sym : nnvm.sym.Symbol
+            The returned nnvm symbol
+        params : dict
+            A dict of name: tvm.nd.array pairs, used as pretrained weights
+        """
+        # Parse throught all nodes and start extracting
+        # params aka Const nodes
+        # input nodes  : First const node
+        # normal nodes : other normal nodes
+
+        try:
+            from tensorflow.python.framework import tensor_util
+        except ImportError as e:
+            raise ImportError(
+                "Unable to import tensorflow which is required {}".format(e))
+
+        for node in graph.node:
+            # Tensorflow doesn't have seperate list for params extraction.
+            # Operator name 'Const' is treated as a parameter to build NNVM params dict.
+            if node.op == "Placeholder":
+                # Assuming only one input graph with type 'Placeholder'
+                self._input_node = node.name
+                self._num_input += 1
+                self._nodes[node.name] = _sym.Variable(name=node.name)
+
+                self._output_shapes[node.name] = \
+                     [tensor_util.TensorShapeProtoToList(shape) \
+                     for shape in self._parse_attr(node.attr)['_output_shapes']]
+            elif node.op == "Const":
+                # Assuming first Const node as Graph Input node
+                if self._input_node == '':
+                    self._input_node = node.name
+                    self._num_input += 1
+                    self._nodes[node.name] = _sym.Variable(name=node.name)
+                else:
+                    # Rest all nodes are Param nodes, lets parse
+                    self._num_param += 1
+                    for key, value in node.attr.items():
+                        self._parse_param(key, value, node.name)
+                    if node.name not in self._nodes:
+                        raise NotImplementedError( \
+                            "Const {} couldn't be converted to Param.".format(node.name))
+
+                self._output_shapes[node.name] = \
+                     [tensor_util.TensorShapeProtoToList(shape) \
+                     for shape in self._parse_attr(node.attr)['_output_shapes']]
+            else:
+                attr = self._parse_attr(node.attr)
+                self._output_shapes[node.name] = \
+                     [tensor_util.TensorShapeProtoToList(shape) for shape in attr['_output_shapes']]
+
+                # Pass the parsed shapes instead
+                attr["_output_shapes"] = self._output_shapes[node.name]
+
+                try:
+                    inputs = [self._nodes[i] for i in node.input]
+                    input_shapes = {}
+                    for i in node.input:
+                        if i not in self._params:
+                            input_shapes[self._nodes[i]] = self._output_shapes[i]
+                    attr['_input_shapes'] = input_shapes
+                except KeyError:
+                    # TODO: Need to find clean way to handle '^CheckNumerics'
+                    print("Some Exception while inputs list:", node.input, " ignoring...")
+
+                inputs = self._fix_extranodes(node.op, attr, inputs)
+
+                op = self._convert_operator(node.op, inputs, attr)
+                # Assuming only one output.
+                self._nodes[node.name] = op
+                node_output = op
+        # Assume the final node is the output node
+        out = node_output
+        return out, self._params
+
+    def _parse_param(self, key, value, name):
+        try:
+            from tensorflow.python.framework import tensor_util
+        except ImportError as e:
+            raise ImportError(
+                "Unable to import tensorflow which is required {}".format(e))
+
+        if key == 'value':
+            np_array = tensor_util.MakeNdarray(value.tensor)
+            array_ndim = len(np_array.shape)
+            if array_ndim == 0:
+                new_array = np.empty([1], dtype=np_array.dtype)
+                new_array[0] = np_array
+                self._params[name] = tvm.nd.array(new_array)
+            else:
+                self._params[name] = tvm.nd.array(np_array)
+            self._nodes[name] = _sym.Variable(name=name,
+                                              shape=self._params[name].shape)
+        else:
+            if key != 'dtype' and key != '_output_shapes':
+                raise NotImplementedError \
+                    ("Other attributes for a Const(param) Node {} ? .".format(key))
+
+    def _get_attr(self, buf):
+        """Returns the value of the attr of this buf with the given `name`.
+
+        Args:
+          buf: attrvalue protobuf.
+
+        Returns:
+          The value of the attr, as a Python object.
+
+        Raises:
+          ValueError: If this op does not have an attr with the given `name`.
+        """
+        fields = ["s", "i", "f", "b", "type", "shape", "tensor", "func"]
+
+        x = buf
+
+        ret = []
+
+        try:
+            from tensorflow.python.framework import dtypes
+        except ImportError as e:
+            raise ImportError(
+                "Unable to import tensorflow which is required {}".format(e))
+
+        # Treat an empty oneof value as an empty list.
+        if not x.WhichOneof("value"):
+            return ret
+        if x.HasField("list"):
+            for f in fields:
+                if getattr(x.list, f):
+                    if f == "type":
+                        ret = [dtypes.as_dtype(x) for x in list(getattr(x.list, f))]
+                    else:
+                        ret = list(getattr(x.list, f))
+        else:
+            for f in fields:
+                if x.HasField(f):
+                    if f == "type":
+                        ret = dtypes.as_dtype(getattr(x, f))
+                    else:
+                        ret = getattr(x, f)
+        return ret
+
+    def _parse_attr(self, attr_proto):
+        """Convert a list of AttributeProto to a dict, with names as keys."""
+        attrs = {}
+        for key, value in attr_proto.items():
+            attrs[key] = self._get_attr(value)
+
+        return attrs
+
+    def _convert_operator(self, op_name, inputs, attrs, identity_list=None, convert_map=None):
+        """Convert from Tensorflow operator to nnvm operator.
+        The converter must specify conversions explicity for incompatible name, and
+        apply handlers to operator attributes.
+
+        Parameters
+        ----------
+        op_name : str
+            Operator name, such as Conv2D, AvgPool
+        inputs : list of nnvm.Symbol
+            List of input symbols.
+        attrs : dict
+            Dict of operator attributes
+        identity_list : list
+            List of operators that don't require conversion
+        convert_map : dict
+            Dict of name : callable, where name is the op's name that
+            require conversion to nnvm, callable are functions which
+            take attrs and return (new_op_name, new_attrs)
+
+        Returns
+        -------
+        sym : nnvm.Symbol
+            Converted nnvm Symbol
+        """
+        identity_list = identity_list if identity_list else _identity_list
+        convert_map = convert_map if convert_map else _convert_map
+        if op_name in identity_list:
+            sym = get_nnvm_op(op_name)(*inputs, **attrs)
+        elif op_name in convert_map:
+            sym = convert_map[op_name](inputs, attrs, self._params)
+        else:
+            raise NotImplementedError("Operator {} not implemented.".format(op_name))
+        return sym
+
+    def _fix_extranodes(self, op_name, attr, inputs):
+        if op_name == "Softmax":
+            # Require some times flatten of data before it goes to softmax
+            # Need to relook into this with latest softmax axis support.
+            op = AttrCvt(op_name='flatten')(inputs, {})
+            node_output = op.list_output_names()
+            for k, i in zip(list(node_output), range(len(node_output))):
+                self._nodes[k] = op[i]
+            inputs = [op]
+
+        return inputs
+
+def from_tensorflow(graph):
+    """  Load tensorflow graph which is a python tensorflow graph object into nnvm graph.
+    The companion parameters will be handled automatically.
+
+    Parameters
+    ----------
+    graph : GraphDef object
+        Tensorflow GraphDef
+
+    Returns
+    -------
+    sym : nnvm.Symbol
+        Compatible nnvm symbol
+
+    params : dict of str to tvm.ndarray
+        Dict of converted parameters stored in tvm.ndarray format
+    """
+    g = GraphProto()
+    sym, params = g.from_tensorflow(graph)
+    return sym, params
diff --git a/nnvm/python/nnvm/testing/tf.py b/nnvm/python/nnvm/testing/tf.py
new file mode 100644
index 000000000000..3421573e36ad
--- /dev/null
+++ b/nnvm/python/nnvm/testing/tf.py
@@ -0,0 +1,229 @@
+# pylint: disable=invalid-name, unused-variable, unused-argument, no-init
+"""
+Tensorflow Model Helpers
+========================
+Some helper definitions for tensorflow models.
+"""
+import re
+import os.path
+import numpy as np
+
+# Tensorflow imports
+import tensorflow as tf
+from tensorflow.core.framework import graph_pb2
+
+######################################################################
+# Some helper functions
+# ---------------------
+
+def ProcessGraphDefParam(graph_def):
+    """Type-checks and possibly canonicalizes `graph_def`.
+
+    Parameters
+    ----------
+    graph_def : Obj
+        tensorflow graph definition.
+
+    Returns
+    -------
+    graph_def : Obj
+        tensorflow graph devinition
+
+    """
+
+    if not isinstance(graph_def, graph_pb2.GraphDef):
+        # `graph_def` could be a dynamically-created message, so try a duck-typed
+        # approach
+        try:
+            old_graph_def = graph_def
+            graph_def = graph_pb2.GraphDef()
+            graph_def.MergeFrom(old_graph_def)
+        except TypeError:
+            raise TypeError('graph_def must be a GraphDef proto.')
+    return graph_def
+
+class NodeLookup(object):
+    """Converts integer node ID's to human readable labels."""
+
+    def __init__(self,
+                 label_lookup_path=None,
+                 uid_lookup_path=None):
+        self.node_lookup = self.load(label_lookup_path, uid_lookup_path)
+
+    def load(self, label_lookup_path, uid_lookup_path):
+        """Loads a human readable English name for each softmax node.
+
+        Parameters
+        ----------
+        label_lookup_path: String
+            File containing String UID to integer node ID mapping .
+
+        uid_lookup_path: String
+            File containing String UID to human-readable string mapping.
+
+        Returns
+        -------
+        node_id_to_name: dict
+            dict from integer node ID to human-readable string.
+
+        """
+        if not tf.gfile.Exists(uid_lookup_path):
+            tf.logging.fatal('File does not exist %s', uid_lookup_path)
+        if not tf.gfile.Exists(label_lookup_path):
+            tf.logging.fatal('File does not exist %s', label_lookup_path)
+
+        # Loads mapping from string UID to human-readable string
+        proto_as_ascii_lines = tf.gfile.GFile(uid_lookup_path).readlines()
+        uid_to_human = {}
+        p = re.compile(r'[n\d]*[ \S,]*')
+        for line in proto_as_ascii_lines:
+            parsed_items = p.findall(line)
+            uid = parsed_items[0]
+            human_string = parsed_items[2]
+            uid_to_human[uid] = human_string
+
+        # Loads mapping from string UID to integer node ID.
+        node_id_to_uid = {}
+        proto_as_ascii = tf.gfile.GFile(label_lookup_path).readlines()
+        for line in proto_as_ascii:
+            if line.startswith('  target_class:'):
+                target_class = int(line.split(': ')[1])
+            if line.startswith('  target_class_string:'):
+                target_class_string = line.split(': ')[1]
+                node_id_to_uid[target_class] = target_class_string[1:-2]
+
+        # Loads the final mapping of integer node ID to human-readable string
+        node_id_to_name = {}
+        for key, val in node_id_to_uid.items():
+            if val not in uid_to_human:
+                tf.logging.fatal('Failed to locate: %s', val)
+            name = uid_to_human[val]
+            node_id_to_name[key] = name
+
+        return node_id_to_name
+
+    def id_to_string(self, node_id):
+        if node_id not in self.node_lookup:
+            return ''
+        return self.node_lookup[node_id]
+
+def read_normalized_tensor_from_image_file(file_name,
+                                           input_height=299,
+                                           input_width=299,
+                                           input_mean=0,
+                                           input_std=255):
+    """ Preprocessing of image
+    Parameters
+    ----------
+
+    file_name: String
+        Image filename.
+
+    input_height: int
+        model input height.
+
+    input_width: int
+        model input width
+
+    input_mean: int
+        Mean to be substracted in normalization.
+
+    input_std: int
+        Standard deviation used in normalization.
+
+    Returns
+    -------
+
+    np_array: Numpy array
+        Normalized image data as a numpy array.
+
+    """
+
+    input_name = "file_reader"
+    output_name = "normalized"
+    file_reader = tf.read_file(file_name, input_name)
+
+    image_reader = tf.image.decode_jpeg(file_reader, channels=3,
+                                        name='jpeg_reader')
+    float_caster = tf.cast(image_reader, tf.float32)
+    dims_expander = tf.expand_dims(float_caster, 0)
+    resized = tf.image.resize_bilinear(dims_expander, [input_height, input_width])
+    normalized = tf.divide(tf.subtract(resized, [input_mean]), [input_std])
+    tf.InteractiveSession()
+    np_array = normalized.eval()
+    return np_array
+
+def get_workload_inception_v3():
+    """ Import Inception V3 workload from frozen protobuf
+
+    Parameters
+    ----------
+        Nothing.
+
+    Returns
+    -------
+    (normalized, graph_def) : Tuple
+        normalized is normalized input for graph testing.
+        graph_def is the tensorflow workload for Inception V3.
+    """
+
+    repo_base = 'https://github.com/dmlc/web-data/raw/master/tensorflow/models/InceptionV3/'
+    model_name = 'inception_v3_2016_08_28_frozen-with_shapes.pb'
+    model_url = os.path.join(repo_base, model_name)
+    image_name = 'elephant-299.jpg'
+    image_url = os.path.join(repo_base, image_name)
+
+    from mxnet.gluon.utils import download
+    download(model_url, model_name)
+    download(image_url, image_name)
+
+    normalized = read_normalized_tensor_from_image_file(os.path.join("./", image_name))
+
+    # Creates graph from saved graph_def.pb.
+    with tf.gfile.FastGFile(os.path.join("./", model_name), 'rb') as f:
+        graph_def = tf.GraphDef()
+        graph_def.ParseFromString(f.read())
+        graph = tf.import_graph_def(graph_def, name='')
+        return (normalized, graph_def)
+
+def get_workload_inception_v1():
+    """ Import Inception V1 workload from frozen protobuf
+
+    Parameters
+    ----------
+        Nothing.
+
+    Returns
+    -------
+    (image_data, tvm_data, graph_def) : Tuple
+        image_data is raw encoded image data for TF input.
+        tvm_data is the decoded image data for TVM input.
+        graph_def is the tensorflow workload for Inception V1.
+
+    """
+
+    repo_base = 'https://github.com/dmlc/web-data/raw/master/tensorflow/models/InceptionV1/'
+    model_name = 'classify_image_graph_def-with_shapes.pb'
+    model_url = os.path.join(repo_base, model_name)
+    image_name = 'elephant-299.jpg'
+    image_url = os.path.join(repo_base, image_name)
+
+    from mxnet.gluon.utils import download
+    download(model_url, model_name)
+    download(image_url, image_name)
+
+    if not tf.gfile.Exists(os.path.join("./", image_name)):
+        tf.logging.fatal('File does not exist %s', image)
+    image_data = tf.gfile.FastGFile(os.path.join("./", image_name), 'rb').read()
+
+    # TVM doesn't handle decode, hence decode it.
+    from PIL import Image
+    tvm_data = Image.open(os.path.join("./", image_name)).resize((299, 299))
+    tvm_data = np.array(tvm_data)
+
+    # Creates graph from saved graph_def.pb.
+    with tf.gfile.FastGFile(os.path.join("./", model_name), 'rb') as f:
+        graph_def = tf.GraphDef()
+        graph_def.ParseFromString(f.read())
+        graph = tf.import_graph_def(graph_def, name='')
+        return (image_data, tvm_data, graph_def)
diff --git a/nnvm/python/nnvm/top/tensor.py b/nnvm/python/nnvm/top/tensor.py
index 1e8688f9f2e6..462a0ec83bf4 100644
--- a/nnvm/python/nnvm/top/tensor.py
+++ b/nnvm/python/nnvm/top/tensor.py
@@ -52,6 +52,15 @@ def _compute(attrs, x, _):
 reg.register_pattern("copy", OpPattern.ELEMWISE)
 reg.register_schedule("copy", _fschedule_broadcast)
 
+# cast
+@reg.register_compute("cast")
+def compute_cast(attrs, inputs, _):
+    """Compute definition of cast"""
+    dtype = attrs.get_string("dtype")
+    return topi.cast(inputs[0], dtype)
+reg.register_pattern("cast", OpPattern.ELEMWISE)
+reg.register_schedule("cast", _fschedule_broadcast)
+
 # exp
 reg.register_pattern("exp", OpPattern.ELEMWISE)
 reg.register_schedule("exp", _fschedule_broadcast)
diff --git a/nnvm/tests/python/frontend/tensorflow/test_forward.py b/nnvm/tests/python/frontend/tensorflow/test_forward.py
new file mode 100644
index 000000000000..4e742a4a5b36
--- /dev/null
+++ b/nnvm/tests/python/frontend/tensorflow/test_forward.py
@@ -0,0 +1,421 @@
+# pylint: disable=import-self, invalid-name, unused-argument
+"""
+Tensorflow testcases
+====================
+This article is a test script to test tensorflow operator with NNVM.
+"""
+from __future__ import print_function
+import numpy as np
+import nnvm.compiler
+import tvm
+import tensorflow as tf
+from tensorflow.python.framework import constant_op
+from tensorflow.python.ops import nn_ops
+from tensorflow.python.ops import array_ops
+from tensorflow.python.ops import gen_array_ops
+from tensorflow.core.framework import graph_pb2
+
+import nnvm.testing.tf
+
+#######################################################################
+# Generic run functions for TVM & tensorflow
+# ------------------------------------------
+def run_tvm_graph(graph_def, input_data, input_node, output_shape, output_dtype):
+    """ Generic function to compile on nnvm and execute on tvm """
+
+    sym, params = nnvm.frontend.from_tensorflow(graph_def)
+    target = 'llvm'
+    if isinstance(input_data, list):
+        shape_dict = {}
+        dtype_dict = {}
+        for i, e in enumerate(input_node):
+            shape_dict[e] = input_data[i].shape
+            dtype_dict[e] = input_data[i].dtype
+    else:
+        shape_dict = {input_node: input_data.shape}
+        dtype_dict = {input_node: input_data.dtype}
+
+    graph, lib, params = nnvm.compiler.build(sym, target, shape_dict,
+                                             dtype=dtype_dict, params=params)
+
+    ctx = tvm.cpu(0)
+    from tvm.contrib import graph_runtime
+    m = graph_runtime.create(graph, lib, ctx)
+    # set inputs
+    if isinstance(input_data, list):
+        for i, e in enumerate(input_node):
+            m.set_input(e, tvm.nd.array(input_data[i].astype(input_data[i].dtype)))
+    else:
+        m.set_input(input_node, tvm.nd.array(input_data.astype(input_data.dtype)))
+
+    m.set_input(**params)
+    # execute
+    m.run()
+    # get outputs
+    tvm_output = m.get_output(0, tvm.nd.empty((output_shape), output_dtype))
+    return tvm_output.asnumpy()
+
+def run_tf_graph(sess, input_data, input_node, output_node):
+    """ Generic function to execute tensorflow """
+
+    tensor = sess.graph.get_tensor_by_name(output_node)
+
+    if isinstance(input_data, list):
+        input_dict = {}
+        for i, e in enumerate(input_node):
+            input_dict[e] = input_data[i]
+    else:
+        input_dict = {input_node: input_data}
+
+    output_data = sess.run(tensor, input_dict)
+    return output_data
+
+#######################################################################
+# Pooling
+# -------
+def _test_pooling(input_shape, **kwargs):
+    """ One iteration of pool operation with given shapes and attributes """
+
+    x = -np.arange(
+        np.prod(input_shape), dtype=np.float32).reshape(input_shape) - 1
+
+    with tf.Graph().as_default():
+        in_data = constant_op.constant(x, shape=input_shape, dtype='float32')
+        # pylint: disable=unused-variable
+        pool = nn_ops.pool(in_data, **kwargs)
+        # pylint: enable=unused-variable
+
+        if kwargs['pooling_type'] == 'MAX':
+            out_node = 'max_pool'
+            out_name = 'max_pool:0'
+        else:
+            out_node = 'avg_pool'
+            out_name = 'avg_pool:0'
+
+        with tf.Session() as sess:
+            graph_def = tf.graph_util.convert_variables_to_constants(
+                sess,
+                sess.graph.as_graph_def(add_shapes=True),
+                [out_node],
+                )
+
+            tf_output = run_tf_graph(sess, x, 'Const:0', out_name)
+            tvm_output = run_tvm_graph(graph_def, x.astype('float32'),
+                                       "Const", tf_output.shape, 'float32')
+            np.testing.assert_allclose(tf_output, tvm_output, atol=1e-3, rtol=1e-3)
+
+            sess.close()
+
+def test_forward_pooling():
+    """ Pooling """
+
+    _test_pooling(input_shape=[2, 9, 10, 2],
+                 window_shape=[1, 1],
+                 padding='SAME',
+                 pooling_type='MAX',
+                 dilation_rate=[1, 1],
+                 strides=[1, 1])
+
+    _test_pooling(input_shape=[2, 9, 10, 2],
+                 window_shape=[1, 1],
+                 padding='SAME',
+                 pooling_type='AVG',
+                 dilation_rate=[1, 1],
+                 strides=[1, 1])
+    _test_pooling(input_shape=[2, 10, 9, 2],
+                 window_shape=[1, 1],
+                 padding='SAME',
+                 pooling_type='MAX',
+                 dilation_rate=[1, 1],
+                 strides=[1, 1])
+    _test_pooling(input_shape=[2, 10, 9, 2],
+                 window_shape=[1, 1],
+                 padding='SAME',
+                 pooling_type='AVG',
+                 dilation_rate=[1, 1],
+                 strides=[1, 1])
+
+#######################################################################
+# Convolution
+# -----------
+
+def _test_convolution(tensor_in_sizes, filter_in_sizes,
+                      dilations, strides, padding, data_format):
+    """ One iteration of convolution with given shapes and attributes """
+
+    total_size_1 = 1
+    total_size_2 = 1
+    for s in tensor_in_sizes:
+        total_size_1 *= s
+    for s in filter_in_sizes:
+        total_size_2 *= s
+    # Initializes the input tensor with array containing incrementing
+    # numbers from 1.
+    data_array = [f * 1.0 for f in range(1, total_size_1 + 1)]
+    filter_array = [f * 1.0 for f in range(1, total_size_2 + 1)]
+
+    with tf.Graph().as_default():
+        in_data = constant_op.constant(data_array, shape=tensor_in_sizes, dtype='float32')
+        in_filter = constant_op.constant(filter_array, shape=filter_in_sizes, dtype='float32')
+        strides = [1] + strides + [1]
+        dilations = [1] + dilations + [1]
+
+        # pylint: disable=unused-variable
+        conv = nn_ops.conv2d(in_data,
+                             in_filter,
+                             strides=strides,
+                             padding=padding,
+                             data_format=data_format)
+        # pylint: enable=unused-variable
+
+        with tf.Session() as sess:
+            graph_def = tf.graph_util.convert_variables_to_constants(
+                sess,
+                sess.graph.as_graph_def(add_shapes=True),
+                ['Conv2D'],
+                )
+
+            tf_output = run_tf_graph(sess, np.reshape(data_array, tensor_in_sizes),
+                                     'Const:0', 'Conv2D:0')
+            tvm_output = run_tvm_graph(graph_def,
+                                       np.reshape(data_array, tensor_in_sizes).astype('float32'),
+                                       "Const", tf_output.shape, 'float32')
+
+            np.testing.assert_allclose(tf_output, tvm_output, atol=1e-3, rtol=1e-3)
+
+            sess.close()
+
+def test_forward_convolution():
+    _test_convolution([4, 8, 8, 176], [1, 1, 176, 32], [1, 1], [1, 1], 'SAME', 'NHWC')
+    _test_convolution([4, 17, 17, 19], [3, 3, 19, 19], [1, 1], [2, 2], 'VALID', 'NHWC')
+    _test_convolution([4, 17, 17, 124], [1, 1, 124, 19], [1, 1], [1, 1], 'SAME', 'NHWC')
+    _test_convolution([4, 17, 17, 12], [3, 3, 12, 32], [1, 1], [2, 2], 'VALID', 'NHWC')
+
+#######################################################################
+# Reshape
+# -------
+
+def _test_reshape(data, out_shape):
+    """ One iteration of reshape operation with given data and out shape """
+
+    with tf.Graph().as_default():
+        in_data = constant_op.constant(data, shape=data.shape, dtype=data.dtype)
+
+        # pylint: disable=unused-variable
+        reshape_out = array_ops.reshape(in_data, out_shape)
+        # pylint: enable=unused-variable
+
+        with tf.Session() as sess:
+            graph_def = tf.graph_util.convert_variables_to_constants(
+                sess,
+                sess.graph.as_graph_def(add_shapes=True),
+                ['Reshape'],
+                )
+
+            tf_output = run_tf_graph(sess, data,
+                                     'Const:0', 'Reshape:0')
+            tvm_output = run_tvm_graph(graph_def,
+                                       data,
+                                       "Const", tf_output.shape, data.dtype)
+
+            np.testing.assert_allclose(tf_output, tvm_output)
+
+            sess.close()
+
+def test_forward_reshape():
+    _test_reshape(np.arange(6.0), [2, 3])
+    _test_reshape(np.arange(6), [-1, 2])
+    _test_reshape(np.arange(6), [3, -1])
+    _test_reshape(np.arange(6), [-1])
+
+#######################################################################
+# Squeeze
+# -------
+
+def _test_squeeze(data, squeeze_dims=None):
+    """ One iteration of squeeze """
+
+    if squeeze_dims is None:
+        squeeze_dims = []
+
+    with tf.Graph().as_default():
+        in_data = constant_op.constant(data, shape=data.shape, dtype=data.dtype)
+
+        # pylint: disable=unused-variable
+        if squeeze_dims:
+            squeeze_out = array_ops.squeeze(in_data, squeeze_dims)
+        else:
+            squeeze_out = array_ops.squeeze(in_data)
+        # pylint: enable=unused-variable
+
+        with tf.Session() as sess:
+            graph_def = tf.graph_util.convert_variables_to_constants(
+                sess,
+                sess.graph.as_graph_def(add_shapes=True),
+                ['Squeeze'],
+                )
+
+            tf_output = run_tf_graph(sess, data,
+                                     'Const:0', 'Squeeze:0')
+            tvm_output = run_tvm_graph(graph_def,
+                                       data,
+                                       "Const", tf_output.shape, data.dtype)
+
+            np.testing.assert_allclose(tf_output, tvm_output)
+
+            sess.close()
+
+def test_forward_squeeze():
+    """ Squeeze """
+
+    # Nothing to squeeze.
+    _test_squeeze(np.arange(2).reshape((2)))
+    _test_squeeze(np.arange(6).reshape((2, 3)))
+
+    # Squeeze the middle element away.
+    _test_squeeze(np.arange(4).reshape((2, 1, 2)))
+
+    # Squeeze on both ends.
+    _test_squeeze(np.arange(6).reshape((1, 2, 1, 3, 1)))
+
+    # Positive squeeze dim index.
+    _test_squeeze(np.arange(6).reshape((1, 2, 1, 3, 1)), [0])
+    _test_squeeze(np.arange(6).reshape((1, 2, 1, 3, 1)), [2, 4])
+    _test_squeeze(np.arange(6).reshape((1, 2, 1, 3, 1)), [0, 4, 2])
+
+    # Negative squeeze dim index.
+    _test_squeeze(np.arange(6).reshape((1, 2, 1, 3, 1)), [-1])
+    _test_squeeze(np.arange(6).reshape((1, 2, 1, 3, 1)), [-3, -5])
+    _test_squeeze(np.arange(6).reshape((1, 2, 1, 3, 1)), [-3, -5, -1])
+
+#######################################################################
+# ConcatV2
+# --------
+
+def _test_concat_v2(data, dim):
+    """ One iteration of ConcatV2 """
+
+    with tf.Graph().as_default():
+
+        # pylint: disable=unused-variable
+        concat_out = gen_array_ops._concat_v2(data, dim)
+        # pylint: enable=unused-variable
+
+        with tf.Session() as sess:
+            graph_def = tf.graph_util.convert_variables_to_constants(
+                sess,
+                sess.graph.as_graph_def(add_shapes=True),
+                ['ConcatV2'],
+                )
+
+            tf_output = run_tf_graph(sess, data,
+                                     ['ConcatV2/values_0:0', 'ConcatV2/values_1:0'], 'ConcatV2:0')
+            tvm_output = run_tvm_graph(graph_def,
+                                       data,
+                                       ["ConcatV2/values_0", 'ConcatV2/values_1'],
+                                       tf_output.shape, tf_output.dtype)
+
+            np.testing.assert_allclose(tf_output, tvm_output)
+
+            sess.close()
+
+def _test_forward_concat_v2():
+    t1 = np.array([])
+    t2 = np.array([])
+    test_concat_v2([t1, t2], 0)
+
+    t1 = np.array([[1, 2, 3], [4, 5, 6]])
+    t2 = np.array([[7, 8, 9], [10, 11, 12]])
+
+    _test_concat_v2([t1, t2], 1)
+
+#######################################################################
+# Multi Input to graph
+# --------------------
+
+def test_forward_multi_input():
+    with tf.Graph().as_default():
+        in1 = tf.placeholder(tf.int32, shape=[3, 3], name='in1')
+        in2 = tf.placeholder(tf.int32, shape=[3, 3], name='in2')
+        in3 = tf.placeholder(tf.int32, shape=[3, 3], name='in3')
+        in4 = tf.placeholder(tf.int32, shape=[3, 3], name='in4')
+
+        out1 = tf.add(in1, in2, name='out1')
+        out2 = tf.subtract(in3, in4, name='out2')
+
+        out = tf.multiply(out1, out2, name='out')
+
+        with tf.Session() as sess:
+            graph_def = tf.graph_util.convert_variables_to_constants(
+                sess,
+                sess.graph.as_graph_def(add_shapes=True),
+                ['out'],
+                )
+
+            in_data = np.arange(9, dtype='int32').reshape([3, 3])
+
+            tf_output = run_tf_graph(sess, [in_data, in_data, in_data, in_data ],
+                                     ['in1:0', 'in2:0', 'in3:0', 'in4:0'], 'out:0')
+            tvm_output = run_tvm_graph(graph_def,
+                                       [in_data, in_data, in_data, in_data ],
+                                       ['in1', 'in2', 'in3', 'in4'],
+                                       tf_output.shape, tf_output.dtype)
+
+            np.testing.assert_allclose(tf_output, tvm_output)
+
+            sess.close()
+
+#######################################################################
+# Inception V3
+# ------------
+def test_forward_inception_v3():
+    '''test inception V3 model'''
+    with tf.Graph().as_default():
+        (data, graph_def) = nnvm.testing.tf.get_workload_inception_v3()
+        # Call the utility to import the graph definition into default graph.
+        graph_def = nnvm.testing.tf.ProcessGraphDefParam(graph_def)
+
+        tvm_output = run_tvm_graph(graph_def, data, 'input', (1, 1001), 'float32')
+        with tf.Session() as sess:
+            tf_output = run_tf_graph(sess, data, 'input:0', 'InceptionV3/Predictions/Reshape_1:0')
+
+            top_tvm = np.squeeze(tvm_output).argsort()[-3:][::-1]
+            top_tf = np.squeeze(tf_output).argsort()[-3:][::-1]
+
+            # TVM implementation of SAME padding some times make a slight deviation.
+            # Hence check for top predictions.
+            top_tvm = np.sort(top_tvm)
+            top_tf = np.sort(top_tf)
+
+            np.testing.assert_allclose(top_tf, top_tvm)
+
+#######################################################################
+# Inception V1
+# ------------
+def test_forward_inception_v1():
+    '''test inception V1 model'''
+    with tf.Graph().as_default():
+        (data, tvm_data, graph_def) = nnvm.testing.tf.get_workload_inception_v1()
+        # Call the utility to import the graph definition into default graph.
+        graph_def = nnvm.testing.tf.ProcessGraphDefParam(graph_def)
+
+        tvm_output = run_tvm_graph(graph_def, tvm_data, 'DecodeJpeg/contents', (1, 1008), 'float32')
+
+        with tf.Session() as sess:
+            tf_output = run_tf_graph(sess, data, 'DecodeJpeg/contents:0', 'softmax:0')
+
+        np.testing.assert_allclose(tf_output, tvm_output, rtol=2e-2, atol=2e-2)
+
+#######################################################################
+# Main
+# ----
+if __name__ == '__main__':
+    test_forward_convolution()
+    test_forward_pooling()
+    test_forward_reshape()
+    test_forward_squeeze()
+    if tf.__version__ == '1.4.1':
+        _test_forward_concat_v2()
+    test_forward_multi_input()
+    test_forward_inception_v3()
+    test_forward_inception_v1()
diff --git a/tests/scripts/task_python_nnvm.sh b/tests/scripts/task_python_nnvm.sh
index 968101c282a3..2fc41980fb3d 100755
--- a/tests/scripts/task_python_nnvm.sh
+++ b/tests/scripts/task_python_nnvm.sh
@@ -18,3 +18,6 @@ python3 -m nose -v nnvm/tests/python/frontend/mxnet || exit -1
 
 echo "Running Keras frontend test..."
 python3 -m nose -v nnvm/tests/python/frontend/keras || exit -1
+
+echo "Running Tensorflow frontend test..."
+python3 -m nose -v nnvm/tests/python/frontend/tensorflow || exit -1
diff --git a/tutorials/nnvm/from_tensorflow.py b/tutorials/nnvm/from_tensorflow.py
new file mode 100644
index 000000000000..34afd0b2ef62
--- /dev/null
+++ b/tutorials/nnvm/from_tensorflow.py
@@ -0,0 +1,171 @@
+"""
+Compile Tensorflow Models
+=========================
+This article is an introductory tutorial to deploy tensorflow models with NNVM.
+
+For us to begin with, tensorflow module is required to be installed.
+
+A quick solution is to install tensorlfow from
+
+https://www.tensorflow.org/install/install_sources
+"""
+
+import nnvm
+import tvm
+import numpy as np
+import os.path
+
+# Tensorflow imports
+import tensorflow as tf
+from tensorflow.core.framework import graph_pb2
+from tensorflow.python.framework import dtypes
+from tensorflow.python.framework import tensor_util
+
+import nnvm.testing.tf
+
+repo_base = 'https://github.com/dmlc/web-data/raw/master/tensorflow/models/InceptionV1/'
+img_name = 'elephant-299.jpg'
+image_url = os.path.join(repo_base, img_name)
+model_name = 'classify_image_graph_def-with_shapes.pb'
+model_url = os.path.join(repo_base, model_name)
+map_proto = 'imagenet_2012_challenge_label_map_proto.pbtxt'
+map_proto_url = os.path.join(repo_base, map_proto)
+lable_map = 'imagenet_synset_to_human_label_map.txt'
+lable_map_url = os.path.join(repo_base, lable_map)
+
+
+######################################################################
+# Download processed tensorflow model
+# -----------------------------------
+# In this section, we download a pretrained Tensorflow model and classify an image.
+from mxnet.gluon.utils import download
+
+download(image_url, img_name)
+download(model_url, model_name)
+download(map_proto_url, map_proto)
+download(lable_map_url, lable_map)
+
+
+######################################################################
+# Creates graph from saved graph_def.pb.
+# --------------------------------------
+
+with tf.gfile.FastGFile(os.path.join(
+        "./", model_name), 'rb') as f:
+    graph_def = tf.GraphDef()
+    graph_def.ParseFromString(f.read())
+    graph = tf.import_graph_def(graph_def, name='')
+    # Call the utility to import the graph definition into default graph.
+    graph_def = nnvm.testing.tf.ProcessGraphDefParam(graph_def)
+
+
+######################################################################
+# Decode image
+# ------------
+from PIL import Image
+image = Image.open(img_name).resize((299, 299))
+
+def transform_image(image):
+    image = np.array(image)
+    return image
+
+x = transform_image(image)
+
+######################################################################
+# Import the graph to NNVM
+# ------------------------
+sym, params = nnvm.frontend.from_tensorflow(graph_def)
+
+######################################################################
+# Now compile the graph through NNVM
+import nnvm.compiler
+target = 'llvm'
+shape_dict = {'DecodeJpeg/contents': x.shape}
+dtype_dict = {'DecodeJpeg/contents': 'uint8'}
+graph, lib, params = nnvm.compiler.build(sym, target, shape_dict, dtype=dtype_dict, params=params)
+
+######################################################################
+# Execute the portable graph on TVM
+# ---------------------------------
+# Now, we would like to reproduce the same forward computation using TVM.
+from tvm.contrib import graph_runtime
+ctx = tvm.cpu(0)
+dtype = 'uint8'
+m = graph_runtime.create(graph, lib, ctx)
+# set inputs
+m.set_input('DecodeJpeg/contents', tvm.nd.array(x.astype(dtype)))
+m.set_input(**params)
+# execute
+m.run()
+# get outputs
+tvm_output = m.get_output(0, tvm.nd.empty(((1, 1008)), 'float32'))
+
+
+######################################################################
+# Process the output to human readable
+# ------------------------------------
+predictions = tvm_output.asnumpy()
+predictions = np.squeeze(predictions)
+
+# Creates node ID --> English string lookup.
+node_lookup = nnvm.testing.tf.NodeLookup(label_lookup_path=os.path.join("./", map_proto),
+                                         uid_lookup_path=os.path.join("./", lable_map))
+
+top_k = predictions.argsort()[-5:][::-1]
+for node_id in top_k:
+    human_string = node_lookup.id_to_string(node_id)
+    score = predictions[node_id]
+    print('%s (score = %.5f)' % (human_string, score))
+
+######################################################################
+# Run the same graph with tensorflow and dump output.
+# ---------------------------------------------------
+
+def create_graph():
+    """Creates a graph from saved GraphDef file and returns a saver."""
+    # Creates graph from saved graph_def.pb.
+    with tf.gfile.FastGFile(model_name, 'rb') as f:
+        graph_def = tf.GraphDef()
+        graph_def.ParseFromString(f.read())
+        graph = tf.import_graph_def(graph_def, name='')
+        # Call the utility to import the graph definition into default graph.
+        graph_def = nnvm.testing.tf.ProcessGraphDefParam(graph_def)
+
+def run_inference_on_image(image):
+    """Runs inference on an image.
+
+    Parameters
+    ----------
+    image: String
+        Image file name.
+
+    Returns
+    -------
+        Nothing
+    """
+    if not tf.gfile.Exists(image):
+        tf.logging.fatal('File does not exist %s', image)
+    image_data = tf.gfile.FastGFile(image, 'rb').read()
+
+    # Creates graph from saved GraphDef.
+    create_graph()
+
+    with tf.Session() as sess:
+        softmax_tensor = sess.graph.get_tensor_by_name('softmax:0')
+        predictions = sess.run(softmax_tensor,
+                               {'DecodeJpeg/contents:0': image_data})
+
+        predictions = np.squeeze(predictions)
+
+        # Creates node ID --> English string lookup.
+        node_lookup = nnvm.testing.tf.NodeLookup(label_lookup_path=os.path.join("./", map_proto),
+                                                 uid_lookup_path=os.path.join("./", lable_map))
+
+        top_k = predictions.argsort()[-5:][::-1]
+        print ("===== TENSORFLOW RESULTS =======")
+        for node_id in top_k:
+            human_string = node_lookup.id_to_string(node_id)
+            score = predictions[node_id]
+            print('%s (score = %.5f)' % (human_string, score))
+
+run_inference_on_image (img_name)

From d8faa5044b8d7c789f09ea21ea5c777b33d7b20c Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 13 Jun 2018 10:52:49 -0700
Subject: [PATCH 717/949] [BUILD] Add clang to build matrix, -Werror (#1273)

---
 CMakeLists.txt                                |  4 ++--
 Jenkinsfile                                   | 10 +++++++++-
 README.md                                     | 10 +++++-----
 nnvm/src/core/symbolic.cc                     |  2 +-
 src/contrib/sort/sort.cc                      | 10 ++++++----
 src/runtime/opengl/opengl_device_api.cc       |  5 +++--
 src/runtime/opengl/opengl_module.h            |  6 ++++--
 tests/ci_build/Dockerfile.gpu                 |  4 ++--
 tests/ci_build/install/ubuntu_install_llvm.sh |  2 +-
 topi/include/topi/reduction.h                 |  4 ++--
 10 files changed, 35 insertions(+), 22 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index c53fb1cfe8cd..972e2cbe712d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -82,8 +82,8 @@ if(MSVC)
 else(MSVC)
   include(CheckCXXCompilerFlag)
   check_cxx_compiler_flag("-std=c++11"    SUPPORT_CXX11)
-  set(CMAKE_C_FLAGS "-O3 -Wall -fPIC")
-  set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -std=c++11")
+  set(CMAKE_C_FLAGS "-O2 -Wall -fPIC ${CMAKE_C_FLAGS}")
+  set(CMAKE_CXX_FLAGS "-O2 -Wall -fPIC -std=c++11 ${CMAKE_CXX_FLAGS}")
 endif(MSVC)
 
 # add source group
diff --git a/Jenkinsfile b/Jenkinsfile
index ba75ee47c0ed..b677ccd2b78d 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -96,6 +96,8 @@ stage('Build') {
            echo set\\(USE_SORT ON\\) >> config.cmake
            echo set\\(USE_GRAPH_RUNTIME ON\\) >> config.cmake
            echo set\\(USE_BLAS openblas\\) >> config.cmake
+           echo set\\(CMAKE_CXX_COMPILER g++\\) >> config.cmake
+           echo set\\(CMAKE_CXX_FLAGS -Werror\\) >> config.cmake
            """
         make('gpu', 'build', '-j2')
         pack_lib('gpu', tvm_multilib)
@@ -106,7 +108,9 @@ stage('Build') {
            cp ../cmake/config.cmake .
            echo set\\(USE_OPENCL ON\\) >> config.cmake
            echo set\\(USE_ROCM ON\\) >> config.cmake
-           echo set\\(USE_VULKAN OFF\\) >> config.cmake
+           echo set\\(USE_VULKAN ON\\) >> config.cmake
+           echo set\\(CMAKE_CXX_COMPILER clang-6.0\\) >> config.cmake
+           echo set\\(CMAKE_CXX_FLAGS -Werror\\) >> config.cmake
            """
         make('gpu', 'build2', '-j2')
       }
@@ -122,6 +126,8 @@ stage('Build') {
            cp ../cmake/config.cmake .
            echo set\\(USE_SORT ON\\) >> config.cmake
            echo set\\(USE_LLVM llvm-config-4.0\\) >> config.cmake
+           echo set\\(CMAKE_CXX_COMPILER g++\\) >> config.cmake
+           echo set\\(CMAKE_CXX_FLAGS -Werror\\) >> config.cmake
            """
         make('cpu', 'build', '-j2')
         pack_lib('cpu', tvm_lib)
@@ -142,6 +148,8 @@ stage('Build') {
            echo set\\(USE_SORT ON\\) >> config.cmake
            echo set\\(USE_RPC ON\\) >> config.cmake
            echo set\\(USE_LLVM llvm-config-5.0\\) >> config.cmake
+           echo set\\(CMAKE_CXX_COMPILER g++\\) >> config.cmake
+           echo set\\(CMAKE_CXX_FLAGS -Werror\\) >> config.cmake
            """
         make('i386', 'build', '-j2')
         pack_lib('i386', tvm_multilib)
diff --git a/README.md b/README.md
index 7d4d6d8c26bb..d1e0a4472a1f 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,18 @@
 <img src=https://raw.githubusercontent.com/tqchen/tvm.ai/master/images/logo/tvm-logo-small.png width=128/> Open Deep Learning Compiler Stack
 ==============================================
 
-[![GitHub license](http://dmlc.github.io/img/apache2.svg)](./LICENSE)
+[![GitHub license](https://dmlc.github.io/img/apache2.svg)](./LICENSE)
 [![Build Status](http://mode-gpu.cs.washington.edu:8080/buildStatus/icon?job=dmlc/tvm/master)](http://mode-gpu.cs.washington.edu:8080/job/dmlc/job/tvm/job/master/)
 
-[Documentation](http://docs.tvm.ai) |
+[Documentation](https://docs.tvm.ai) |
 [Contributors](CONTRIBUTORS.md) |
-[Community](http://tvm.ai/community.html) |
+[Community](https://tvm.ai/community.html) |
 [Release Notes](NEWS.md)
 
 TVM is a compiler stack for deep learning systems. It is designed to close the gap between the
 productivity-focused deep learning frameworks, and the performance- and efficiency-focused hardware backends.
 TVM works with deep learning frameworks to provide end to end compilation to different backends.
-Checkout the [tvm stack homepage](http://tvm.ai/)  for more information.
+Checkout the [tvm stack homepage](https://tvm.ai/)  for more information.
 
 License
 -------
@@ -21,7 +21,7 @@ License
 Contribute to TVM
 -----------------
 TVM adopts apache committer model, we aim to create an open source project that is maintained and owned by the community.
-Checkout the [Contributor Guide](http://docs.tvm.ai/contribute/)
+Checkout the [Contributor Guide](https://docs.tvm.ai/contribute/)
 
 Acknowledgement
 ---------------
diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index 927dd2b70e44..89d87d32ff74 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -200,7 +200,7 @@ std::vector<NodePtr> Symbol::ListInputs(ListInputOption option) const {
     std::vector<NodePtr> vlist;
     vlist.reserve(this->outputs.size());
     static auto& fmutate_inputs = Op::GetAttr<FMutateInputs>("FMutateInputs");
-    DFSVisit(this->outputs, [&ret, &mutable_set, &vlist](const NodePtr &node) {
+    DFSVisit(this->outputs, [&mutable_set, &vlist](const NodePtr &node) {
         if (node->is_variable()) {
           vlist.push_back(node);
         } else if (fmutate_inputs.count(node->op())) {
diff --git a/src/contrib/sort/sort.cc b/src/contrib/sort/sort.cc
index ced5021fd1aa..160e479b86b5 100644
--- a/src/contrib/sort/sort.cc
+++ b/src/contrib/sort/sort.cc
@@ -78,12 +78,14 @@ TVM_REGISTER_GLOBAL("tvm.contrib.sort.argsort")
         int64_t full_idx = base_idx + k * axis_mul_after;
         sorter.emplace_back(std::make_pair(k, *(data_ptr + full_idx)));
       }
-      std::stable_sort(sorter.begin(), sorter.end(),
-                       is_descend ? CompareDescend<float>
-                                  : CompareAscend<float>);
+      if (is_descend) {
+        std::stable_sort(sorter.begin(), sorter.end(), CompareDescend<float>);
+      } else {
+        std::stable_sort(sorter.begin(), sorter.end(), CompareAscend<float>);
+      }
       for (int32_t k = 0; k < input->shape[axis]; ++k) {
         *(static_cast<int32_t *>(output->data) + base_idx + k * axis_mul_after)
-          = k < sorter.size() ? sorter[k].first : k;
+            = k < static_cast<int32_t>(sorter.size()) ? sorter[k].first : k;
       }
     }
   }
diff --git a/src/runtime/opengl/opengl_device_api.cc b/src/runtime/opengl/opengl_device_api.cc
index 96ffb3192b7b..4357e610f478 100644
--- a/src/runtime/opengl/opengl_device_api.cc
+++ b/src/runtime/opengl/opengl_device_api.cc
@@ -328,10 +328,11 @@ static TextureFormat GetTextureFormat(TVMType type) {
           LOG(FATAL) << "Unsupported type bits " << type.bits;
       }
     }
-    default:
+    default: {
       LOG(FATAL) << "Unsupported type code" << type.code;
+    }
   }
-  assert(false);
+  return {GL_R32F, GL_RED, GL_FLOAT};
 }
 
 Texture OpenGLWorkspace::CreateTexture(TVMType type, size_t nbytes) {
diff --git a/src/runtime/opengl/opengl_module.h b/src/runtime/opengl/opengl_module.h
index 9e5f4044b3b6..a55a09b6c1bd 100644
--- a/src/runtime/opengl/opengl_module.h
+++ b/src/runtime/opengl/opengl_module.h
@@ -88,8 +88,10 @@ inline std::string OpenGLArgKind2String(OpenGLArgKind kind) {
       return "input_texture";
     case OpenGLArgKind::kUniform:
       return "uniform";
+    default:
+      LOG(FATAL) << "invalid arg kind";
+      return "";
   }
-  assert(false);
 }
 
 inline OpenGLArgKind String2OpenGLArgKind(const std::string& str) {
@@ -101,7 +103,7 @@ inline OpenGLArgKind String2OpenGLArgKind(const std::string& str) {
     return OpenGLArgKind::kUniform;
   } else {
     LOG(FATAL) << "Invalid OpenGL arg kind.";
-    assert(false);
+    return OpenGLArgKind::kUniform;
   }
 }
 
diff --git a/tests/ci_build/Dockerfile.gpu b/tests/ci_build/Dockerfile.gpu
index 3e2c6f2eb7b5..0d9c1be420f7 100644
--- a/tests/ci_build/Dockerfile.gpu
+++ b/tests/ci_build/Dockerfile.gpu
@@ -60,8 +60,8 @@ RUN bash /install/ubuntu_install_onnx.sh
 RUN pip3 install Pillow
 
 # disable vulkan for now
-# COPY install/ubuntu_install_vulkan.sh /install/ubuntu_install_vulkan.sh
-# RUN bash /install/ubuntu_install_vulkan.sh
+COPY install/ubuntu_install_vulkan.sh /install/ubuntu_install_vulkan.sh
+RUN bash /install/ubuntu_install_vulkan.sh
 
 # Environment variables
 ENV PATH=/usr/local/nvidia/bin:${PATH}
diff --git a/tests/ci_build/install/ubuntu_install_llvm.sh b/tests/ci_build/install/ubuntu_install_llvm.sh
index ba0afcd18cc9..16d0fe150b7e 100644
--- a/tests/ci_build/install/ubuntu_install_llvm.sh
+++ b/tests/ci_build/install/ubuntu_install_llvm.sh
@@ -19,4 +19,4 @@ echo deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial main\
      >> /etc/apt/sources.list.d/llvm.list
 
 wget -O - http://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
-apt-get update && apt-get install -y --force-yes llvm-4.0 llvm-5.0 llvm-6.0
+apt-get update && apt-get install -y --force-yes llvm-4.0 llvm-5.0 llvm-6.0 clang-6.0
diff --git a/topi/include/topi/reduction.h b/topi/include/topi/reduction.h
index a4a3e4e999ac..cb89be30a32d 100644
--- a/topi/include/topi/reduction.h
+++ b/topi/include/topi/reduction.h
@@ -238,8 +238,8 @@ inline FCommReduce MakeCommReducer(FCombine fcombine,
     for (size_t i = 0; i < exprs.size(); ++i) {
       auto dtype = exprs[i].type();
       dtypes.push_back(dtype);
-      lhs.push_back(var("lhs_" + std::to_string(i), dtype));
-      rhs.push_back(var("rhs_" + std::to_string(i), dtype));
+      lhs.push_back(var(name + "_lhs_" + std::to_string(i), dtype));
+      rhs.push_back(var(name + "_rhs_" + std::to_string(i), dtype));
     }
 
     auto result = fcombine(lhs, rhs);

From 4c40cb06c103a850e0f8d8f9c5131c4c173e6ded Mon Sep 17 00:00:00 2001
From: libing4752 <libing475211023@sjtu.edu.cn>
Date: Thu, 14 Jun 2018 01:53:04 +0800
Subject: [PATCH 718/949] fix copro_sync.cc errors of ctx (#1274)

---
 src/pass/coproc_sync.cc                       |  2 +-
 .../python/unittest/test_pass_storage_sync.py | 37 +++++++++++++++++++
 2 files changed, 38 insertions(+), 1 deletion(-)

diff --git a/src/pass/coproc_sync.cc b/src/pass/coproc_sync.cc
index 28be8aba2057..b3e64a989702 100644
--- a/src/pass/coproc_sync.cc
+++ b/src/pass/coproc_sync.cc
@@ -385,7 +385,7 @@ class CoProcInstDepDetector : public IRVisitor {
                  &(curr_state_.exit_push),
                  &(curr_state_.enter_pop));
       curr_state_.enter_ctx = first_state_.enter_ctx;
-      curr_state_.exit_ctx = last_state_.enter_ctx;
+      curr_state_.exit_ctx = last_state_.exit_ctx;
     }
     std::swap(first_state_, temp_first);
     std::swap(last_state_, temp_last);
diff --git a/tests/python/unittest/test_pass_storage_sync.py b/tests/python/unittest/test_pass_storage_sync.py
index ce9e2f9a4af9..2286dd53e981 100644
--- a/tests/python/unittest/test_pass_storage_sync.py
+++ b/tests/python/unittest/test_pass_storage_sync.py
@@ -78,7 +78,44 @@ def test_coproc_sync2():
     stmt = ib.get()
     stmt = tvm.ir_pass.CoProcSync(stmt)
 
+def test_coproc_sync3():
+    def __check_list(tvm_array, py_list):
+        for ti, li in zip(tvm_array, py_list):
+            if ti.value != li:
+                return False
+        return True
+
+    ib = tvm.ir_builder.create()
+    n = tvm.var("n")
+    cp = tvm.thread_axis((0, 1), "cop")
+    A = ib.allocate("float32", 128, name="A", scope="global.cache")
+    with ib.for_range(0, n, name="i") as i:
+        with ib.for_range(0, n, name="i") as j:
+            with ib.new_scope():
+                ib.scope_attr(cp, "coproc_scope", 1)
+                A[i] = 1.0
+            with ib.new_scope():
+                ib.scope_attr(cp, "coproc_scope", 2)
+                A[i] = 1.0
+    with ib.new_scope():
+        ib.scope_attr(cp, "coproc_scope", 3)
+        A[0] = 0.0
+   
+    stmt = ib.get()
+    stmt = tvm.ir_pass.CoProcSync(stmt)
+    slist = tvm.make.stmt_list(stmt.first.body.body)
+    push_st = slist[2]
+    slist = tvm.make.stmt_list(slist[-1])
+    pop_st = slist[0].body.first
+
+    assert(push_st.value.name == "cop.coproc_dep_push")
+    assert(__check_list(push_st.value.args, [2,3]))
+    assert(pop_st.value.name == "cop.coproc_dep_pop")
+    assert(__check_list(pop_st.value.args, [2,3]))
+    
+
 if __name__ == "__main__":
     test_coproc_sync()
     test_storage_sync()
     test_coproc_sync2()
+    test_coproc_sync3()

From 86b063f3647e670fb0bdc2bf474ee844aa66a2ce Mon Sep 17 00:00:00 2001
From: eqy <eqy@cs.washington.edu>
Date: Wed, 13 Jun 2018 17:20:10 -0700
Subject: [PATCH 719/949] fix missing std::to_string during Android build,
 android rpc test script (#1279)

---
 apps/android_rpc/app/src/main/jni/Application.mk | 2 +-
 apps/android_rpc/tests/android_rpc_test.py       | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/apps/android_rpc/app/src/main/jni/Application.mk b/apps/android_rpc/app/src/main/jni/Application.mk
index 01cad9b783a7..8e81a8d6a81c 100644
--- a/apps/android_rpc/app/src/main/jni/Application.mk
+++ b/apps/android_rpc/app/src/main/jni/Application.mk
@@ -8,7 +8,7 @@ endif
 
 include $(config)
 
-APP_STL := gnustl_static
+APP_STL := c++_static
 
 APP_CPPFLAGS += -DDMLC_LOG_STACK_TRACE=0 -DTVM4J_ANDROID=1 -std=c++11 -Oz -frtti
 ifeq ($(USE_OPENCL), 1)                                                                                                                                             
diff --git a/apps/android_rpc/tests/android_rpc_test.py b/apps/android_rpc/tests/android_rpc_test.py
index 650892ab5735..7a5a1060efb1 100644
--- a/apps/android_rpc/tests/android_rpc_test.py
+++ b/apps/android_rpc/tests/android_rpc_test.py
@@ -6,7 +6,7 @@
 
 import tvm
 import os
-from tvm.contrib import rpc, util, ndk, rpc_proxy
+from tvm.contrib import rpc, util, ndk
 import numpy as np
 
 # Set to be address of tvm proxy.

From 7a43f449efa16d419ca4c4c9808e136711043372 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 13 Jun 2018 17:21:16 -0700
Subject: [PATCH 720/949] [MAINTAINER] add masahi as reviewer (#1277)

---
 CONTRIBUTORS.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 112d8372f455..db4e5f249fb3 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -20,6 +20,7 @@ See the [community structure document](http://docs.tvm.ai/contribute/community.h
 - [Nick Hynes](https://github.com/nhynes) SGX and secured computing
 
 ## Reviewers
+- [Masahiro Masuda](https://github.com/masahi)
 - [Pariksheet Pinjari](https://github.com/PariksheetPinjari909)
 - [Eddie Yan](https://github.com/eqy)
 - [Lianmin Zheng](https://github.com/merrymercy)

From 57f958836c1d99c69bf16ff5e12a0384c062451a Mon Sep 17 00:00:00 2001
From: Siva <sivar.b@huawei.com>
Date: Thu, 14 Jun 2018 21:23:49 +0530
Subject: [PATCH 721/949] [TOPI][IMAGE][RESIZE] Bilinear interpolation for
 resize and upsampling. (#1181)

---
 docs/api/python/topi.rst                      |   5 +
 nnvm/include/nnvm/top/nn.h                    |  10 +-
 nnvm/python/nnvm/top/__init__.py              |   1 +
 nnvm/python/nnvm/top/image.py                 |  17 +
 nnvm/python/nnvm/top/nn.py                    |  14 +-
 nnvm/src/top/image/resize.cc                  | 113 +++++++
 nnvm/src/top/image/resize.h                   |  45 +++
 nnvm/src/top/nn/upsampling.cc                 |  51 ++-
 nnvm/tests/python/compiler/test_top_level2.py |  45 ++-
 topi/include/topi/image/resize.h              | 316 ++++++++++++++++++
 topi/include/topi/nn/upsampling.h             |  44 +++
 topi/python/topi/__init__.py                  |   1 +
 topi/python/topi/cpp.py                       |   2 +
 topi/python/topi/image/__init__.py            |   5 +
 topi/python/topi/image/resize.py              |  33 ++
 topi/python/topi/nn/upsampling.py             |  72 +---
 topi/python/topi/testing/__init__.py          |   1 +
 .../topi/testing/bilinear_resize_python.py    |  79 +++++
 topi/python/topi/testing/upsampling_python.py |  27 +-
 topi/src/topi.cc                              |  16 +
 topi/tests/python/test_topi_resize.py         |  57 ++++
 topi/tests/python/test_topi_upsampling.py     |  30 +-
 22 files changed, 890 insertions(+), 94 deletions(-)
 create mode 100644 nnvm/python/nnvm/top/image.py
 create mode 100644 nnvm/src/top/image/resize.cc
 create mode 100644 nnvm/src/top/image/resize.h
 create mode 100644 topi/include/topi/image/resize.h
 create mode 100644 topi/include/topi/nn/upsampling.h
 create mode 100644 topi/python/topi/image/__init__.py
 create mode 100644 topi/python/topi/image/resize.py
 create mode 100644 topi/python/topi/testing/bilinear_resize_python.py
 create mode 100644 topi/tests/python/test_topi_resize.py

diff --git a/docs/api/python/topi.rst b/docs/api/python/topi.rst
index b1f2bfc9d940..3216c5168997 100644
--- a/docs/api/python/topi.rst
+++ b/docs/api/python/topi.rst
@@ -51,6 +51,7 @@ List of operators
    topi.broadcast_div
    topi.broadcast_maximum
    topi.broadcast_minimum
+   topi.image.resize
 
 
 List of schedules
@@ -114,6 +115,10 @@ topi.nn
 .. autofunction:: topi.nn.depthwise_conv2d_nchw
 .. autofunction:: topi.nn.depthwise_conv2d_nhwc
 
+topi.image
+~~~~~~~~~~
+.. autofunction:: topi.image.resize
+
 
 topi.generic
 ~~~~~~~~~~~~
diff --git a/nnvm/include/nnvm/top/nn.h b/nnvm/include/nnvm/top/nn.h
index 7593cf88ae8f..bee6137829c5 100644
--- a/nnvm/include/nnvm/top/nn.h
+++ b/nnvm/include/nnvm/top/nn.h
@@ -288,16 +288,22 @@ struct GlobalPool2DParam : public dmlc::Parameter<GlobalPool2DParam> {
 struct UpSamplingParam : public dmlc::Parameter<UpSamplingParam> {
   int scale;
   std::string layout;
+  std::string method;
 
   DMLC_DECLARE_PARAMETER(UpSamplingParam) {
     DMLC_DECLARE_FIELD(scale)
       .describe("upsampling scaling factor");
     DMLC_DECLARE_FIELD(layout)
       .set_default("NCHW")
-      .describe("Dimension ordering of data and weight. Can be 'NCHW', 'NHWC', etc."
+      .describe("Dimension ordering of data. Can be 'NCHW', 'NHWC', etc."
                 "'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
-                "dimensions respectively. Convolution is applied on the 'H' and"
+                "dimensions respectively. Upsampling is applied on the 'H' and"
                 "'W' dimensions.");
+    DMLC_DECLARE_FIELD(method)
+      .set_default("NEAREST_NEIGHBOR")
+      .describe("Specify the mode to use for scaling."
+                "NEAREST_NEIGHBOR -  Nearest Neighbor"
+                "BILINEAR - Bilinear Interpolation");
   }
 };
 
diff --git a/nnvm/python/nnvm/top/__init__.py b/nnvm/python/nnvm/top/__init__.py
index 12294fa0df48..d9174f8162f2 100644
--- a/nnvm/python/nnvm/top/__init__.py
+++ b/nnvm/python/nnvm/top/__init__.py
@@ -8,6 +8,7 @@
 from . import transform
 from . import reduction
 from . import vision
+from . import image
 
 from .registry import OpPattern
 from .registry import register_compute, register_schedule, register_pattern
diff --git a/nnvm/python/nnvm/top/image.py b/nnvm/python/nnvm/top/image.py
new file mode 100644
index 000000000000..f903231e3a2d
--- /dev/null
+++ b/nnvm/python/nnvm/top/image.py
@@ -0,0 +1,17 @@
+# pylint: disable=invalid-name, unused-argument
+"""Definition of image ops"""
+from __future__ import absolute_import
+
+import topi
+import tvm
+from . import registry as reg
+from .registry import OpPattern
+
+# resize
+@reg.register_schedule("resize")
+def schedule_resize(_, outs, target):
+    """Schedule definition of resize"""
+    with tvm.target.create(target):
+        return topi.generic.schedule_injective(outs)
+
+reg.register_pattern("resize", OpPattern.INJECTIVE)
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index dfebc368eaf3..b7e0d0952888 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -235,20 +235,10 @@ def schedule_global_avg_pool2d(_, outs, target):
 
 reg.register_pattern("global_avg_pool2d", OpPattern.OUT_ELEMWISE_FUSABLE)
 
-
-@reg.register_compute("upsampling")
-def compute_upsampling(attrs, inputs, _):
-    """Compute definition of upsampling"""
-    scale = attrs.get_int("scale")
-    layout = attrs["layout"]
-    if layout:
-        assert layout == "NCHW" or layout == "NHWC"
-        return topi.nn.upsampling(inputs[0], scale, layout)
-    return topi.nn.upsampling(inputs[0], scale)
-
+# upsampling
 @reg.register_schedule("upsampling")
 def schedule_upsampling(_, outs, target):
-    """Compute definition of upsampling"""
+    """Schedule definition of upsampling"""
     with tvm.target.create(target):
         return topi.generic.schedule_injective(outs)
 
diff --git a/nnvm/src/top/image/resize.cc b/nnvm/src/top/image/resize.cc
new file mode 100644
index 000000000000..151296d8ecc4
--- /dev/null
+++ b/nnvm/src/top/image/resize.cc
@@ -0,0 +1,113 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file resize.cc
+ * \brief Property def of resize operators.
+ */
+#include <tvm/tvm.h>
+#include <tvm/expr.h>
+#include <tvm/packed_func_ext.h>
+#include <nnvm/layout.h>
+#include <nnvm/compiler/op_attr_types.h>
+#include <nnvm/op.h>
+#include <nnvm/node.h>
+#include <nnvm/op_attr_types.h>
+#include "../nn/nn_common.h"
+#include "../op_common.h"
+#include "../elemwise_op_common.h"
+#include "topi/elemwise.h"
+#include "topi/transform.h"
+#include "topi/image/resize.h"
+#include "resize.h"
+
+namespace nnvm {
+namespace top {
+using tvm::Expr;
+using tvm::Array;
+using tvm::Tensor;
+using nnvm::compiler::FTVMCompute;
+
+DMLC_REGISTER_PARAMETER(ResizeParam);
+
+inline bool ResizeInferShape(const nnvm::NodeAttrs& attrs,
+                             std::vector<TShape>* in_shape,
+                             std::vector<TShape>* out_shape) {
+  static const Layout kNCHW("NCHW");
+  const ResizeParam& param = nnvm::get<ResizeParam>(attrs.parsed);
+  CHECK_EQ(in_shape->size(), 1U);
+  CHECK_EQ(out_shape->size(), 1U);
+  TShape dshape = (*in_shape)[0];
+  if (dshape.ndim() ==  0) return false;
+  dshape = ConvertLayout(dshape, param.layout, kNCHW);
+
+  TShape oshape = dshape;
+  if (param.layout == "NCHW") {
+    oshape[2] = param.size[0];
+    oshape[3] = param.size[1];
+  } else {
+    oshape[1] = param.size[0];
+    oshape[2] = param.size[1];
+  }
+  oshape = ConvertLayout(oshape, kNCHW, param.layout);
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, oshape);
+
+  return true;
+}
+
+inline bool ResizeLayout(const NodeAttrs& attrs,
+                         std::vector<Layout> *in_layouts,
+                         const std::vector<Layout> *last_in_layouts,
+                         std::vector<Layout> *out_layouts) {
+  const ResizeParam& param = nnvm::get<ResizeParam>(attrs.parsed);
+  CHECK_EQ(in_layouts->size(), 1U);
+  CHECK_EQ(out_layouts->size(), 1U);
+  const Layout layout(param.layout);
+  NNVM_ASSIGN_LAYOUT(*in_layouts, 0, layout);
+  NNVM_ASSIGN_LAYOUT(*out_layouts, 0, layout);
+  return true;
+}
+
+NNVM_REGISTER_OP(resize)
+.describe(R"(Perform resize to input array with nearest neighbour or bilinear interpolation.
+
+- **data**: data is 4D array of shape
+            (batch_size, channels, in_height, in_width) for NCHW
+            (batch_size, in_height, in_width, channels) for NHWC
+
+- **out**: Output is 4D array of shape
+           for layout NCHW
+           (batch_size, channels, size[0], size[1])
+
+           for layout NHWC
+           (batch_size, size[0], size[1], channels)
+
+)" NNVM_ADD_FILELINE)
+.add_argument("data", "4D Tensor", "Input data.")
+.add_arguments(ResizeParam::__FIELDS__())
+.set_attr_parser(ParamParser<ResizeParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<ResizeParam>)
+.set_attr<FInferShape>("FInferShape", ResizeInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FCorrectLayout>("FCorrectLayout", ResizeLayout)
+.set_num_outputs(1)
+.set_num_inputs(1)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+  const ResizeParam& param = nnvm::get<ResizeParam>(attrs.parsed);
+  Array<Expr> oshape;
+  if (param.layout == "NCHW") {
+    oshape.push_back(out_info[0]->shape[2]);
+    oshape.push_back(out_info[0]->shape[3]);
+  } else {
+    oshape.push_back(out_info[0]->shape[1]);
+    oshape.push_back(out_info[0]->shape[2]);
+  }
+
+  return Array<Tensor>{ topi::image::resize(inputs[0], oshape, param.layout,
+                                             param.align_corners, param.method)};
+})
+.set_support_level(2);
+
+}  // namespace top
+}  // namespace nnvm
diff --git a/nnvm/src/top/image/resize.h b/nnvm/src/top/image/resize.h
new file mode 100644
index 000000000000..7237152f7525
--- /dev/null
+++ b/nnvm/src/top/image/resize.h
@@ -0,0 +1,45 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file resize.h
+ */
+#ifndef NNVM_TOP_IMAGE_RESIZE_H_
+#define NNVM_TOP_IMAGE_RESIZE_H_
+
+#include <string>
+#include <vector>
+#include <utility>
+#include <iostream>
+#include <sstream>
+
+namespace nnvm {
+namespace top {
+
+struct ResizeParam : public dmlc::Parameter<ResizeParam> {
+  TShape size;
+  std::string layout;
+  std::string method;
+  bool align_corners;
+
+  DMLC_DECLARE_PARAMETER(ResizeParam) {
+    DMLC_DECLARE_FIELD(size)
+      .describe("Output size");
+    DMLC_DECLARE_FIELD(layout)
+      .set_default("NCHW")
+      .describe("Dimension ordering of data. Can be 'NCHW', 'NHWC', etc."
+                "'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
+                "dimensions respectively. Resize is applied on the 'H' and"
+                "'W' dimensions.");
+    DMLC_DECLARE_FIELD(method)
+      .set_default("BILINEAR")
+      .describe("Specify the mode to use for scaling."
+                "NEAREST_NEIGHBOR -  Nearest Neighbor"
+                "BILINEAR - Bilinear Interpolation");
+    DMLC_DECLARE_FIELD(align_corners)
+      .set_default(false)
+      .describe("Should be true to preserve the values at the corner pixels");
+  }
+};
+
+}  // namespace top
+}  // namespace nnvm
+#endif  // NNVM_TOP_IMAGE_RESIZE_H_
diff --git a/nnvm/src/top/nn/upsampling.cc b/nnvm/src/top/nn/upsampling.cc
index 73c9bad97282..6c5e13441406 100644
--- a/nnvm/src/top/nn/upsampling.cc
+++ b/nnvm/src/top/nn/upsampling.cc
@@ -1,8 +1,12 @@
 /*!
  *  Copyright (c) 2017 by Contributors
- * \file pooling.cc
- * \brief Property def of pooling operators.
+ * \file upsampling.cc
+ * \brief Property def of upsampling operators.
  */
+#include <tvm/tvm.h>
+#include <tvm/expr.h>
+#include <nnvm/layout.h>
+#include <nnvm/compiler/op_attr_types.h>
 #include <nnvm/op.h>
 #include <nnvm/node.h>
 #include <nnvm/op_attr_types.h>
@@ -10,27 +14,36 @@
 #include "./nn_common.h"
 #include "../op_common.h"
 #include "../elemwise_op_common.h"
+#include "topi/elemwise.h"
+#include "topi/transform.h"
+#include "topi/nn/upsampling.h"
 
 namespace nnvm {
 namespace top {
+using tvm::Expr;
+using tvm::Array;
+using tvm::Tensor;
+using nnvm::compiler::FTVMCompute;
 
 DMLC_REGISTER_PARAMETER(UpSamplingParam);
 
 inline bool UpSamplingInferShape(const nnvm::NodeAttrs& attrs,
-                                   std::vector<TShape>* in_shape,
-                                   std::vector<TShape>* out_shape) {
+                                 std::vector<TShape>* in_shape,
+                                 std::vector<TShape>* out_shape) {
   static const Layout kNCHW("NCHW");
   const UpSamplingParam& param = nnvm::get<UpSamplingParam>(attrs.parsed);
   CHECK_EQ(in_shape->size(), 1U);
   CHECK_EQ(out_shape->size(), 1U);
   TShape dshape = (*in_shape)[0];
   if (dshape.ndim() ==  0) return false;
+
   dshape = ConvertLayout(dshape, param.layout, kNCHW);
   TShape oshape = dshape;
   oshape[2] = oshape[2] * param.scale;
   oshape[3] = oshape[3] * param.scale;
   oshape = ConvertLayout(oshape, kNCHW, param.layout);
   NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, oshape);
+
   return true;
 }
 
@@ -48,10 +61,18 @@ inline bool UpsamplingLayout(const NodeAttrs& attrs,
 }
 
 NNVM_REGISTER_OP(upsampling)
-.describe(R"(Perform nearest neighbor upsampling to input array.
+.describe(R"(Perform upsampling to input array with nearest neighbour or bilinear interpolation.
+
+- **data**: data is 4D array of shape
+            (batch_size, channels, in_height, in_width) for NCHW
+            (batch_size, in_height, in_width, channels) for NHWC
 
-- **data**: Input is 4D array of shape (batch_size, channels, in_height, in_width).
-- **out**: Output is 4D array of shape (batch_size, channels, in_height*scale, in_width*scale).
+- **out**: Output is 4D array of shape
+           for layout NCHW
+           (batch_size, channels, in_height*scale, in_width*scale)
+
+           for layout NHWC
+           (batch_size, in_height*scale, in_width*scale, channels)
 
 )" NNVM_ADD_FILELINE)
 .add_argument("data", "4D Tensor", "Input data.")
@@ -63,6 +84,22 @@ NNVM_REGISTER_OP(upsampling)
 .set_attr<FCorrectLayout>("FCorrectLayout", UpsamplingLayout)
 .set_num_outputs(1)
 .set_num_inputs(1)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+  const UpSamplingParam& param = nnvm::get<UpSamplingParam>(attrs.parsed);
+  Array<Expr> oshape;
+  if (param.layout == "NCHW") {
+    oshape.push_back(out_info[0]->shape[2]);
+    oshape.push_back(out_info[0]->shape[3]);
+  } else {
+    oshape.push_back(out_info[0]->shape[1]);
+    oshape.push_back(out_info[0]->shape[2]);
+  }
+
+  return Array<Tensor>{ topi::nn::upsampling(inputs[0], oshape, param.layout, param.method)};
+})
 .set_support_level(2);
 
 }  // namespace top
diff --git a/nnvm/tests/python/compiler/test_top_level2.py b/nnvm/tests/python/compiler/test_top_level2.py
index f752b7d44271..ed67dab44c47 100644
--- a/nnvm/tests/python/compiler/test_top_level2.py
+++ b/nnvm/tests/python/compiler/test_top_level2.py
@@ -210,7 +210,7 @@ def test_global_avg_pool2d():
         np.testing.assert_allclose(out.asnumpy(), b_np, rtol=1e-5)
 
 
-def test_upsampling():
+def test_upsampling_nearest_neighbor():
     x = sym.Variable("x")
     scale = 2
     y = sym.upsampling(x, scale=scale, name="y")
@@ -225,9 +225,46 @@ def test_upsampling():
         data = tvm.nd.array(a_np)
         m.run(x=data)
         out = m.get_output(0, tvm.nd.empty(oshape, dtype))
-        b_np = topi.testing.upsampling_python(a_np, scale)
+        b_np = topi.testing.upsampling_python(a_np, scale, "NCHW")
         np.testing.assert_allclose(out.asnumpy(), b_np, rtol=1e-5)
 
+def test_upsampling_bilinear():
+    x = sym.Variable("x")
+    scale = 2
+    y = sym.upsampling(x, scale=scale, method="BILINEAR", name="y", layout="NCHW")
+    dtype = "float32"
+    dshape = (1, 4, 32, 32)
+    oshape = (1, 4, 32*scale, 32*scale)
+    shape_dict = {"x": dshape}
+    dtype_dict = {"x": dtype}
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, shape_dict, dtype_dict)
+        m = graph_runtime.create(graph, lib, ctx)
+        a_np = np.random.uniform(size=dshape).astype(dtype)
+        data = tvm.nd.array(a_np)
+        m.run(x=data)
+        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
+        b_np = topi.testing.bilinear_resize_python(a_np, (32*scale, 32*scale), "NCHW")
+        np.testing.assert_allclose(out.asnumpy(), b_np, rtol=1e-5, atol=1e-5)
+
+def test_resize_bilinear():
+    x = sym.Variable("x")
+    scale = 2
+    y = sym.upsampling(x, scale=scale, method="BILINEAR", name="y", layout="NHWC")
+    dtype = "float32"
+    dshape = (1, 32, 32, 4)
+    oshape = (1, 32*scale, 32*scale, 4)
+    shape_dict = {"x": dshape}
+    dtype_dict = {"x": dtype}
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, shape_dict, dtype_dict)
+        m = graph_runtime.create(graph, lib, ctx)
+        a_np = np.random.uniform(size=dshape).astype(dtype)
+        data = tvm.nd.array(a_np)
+        m.run(x=data)
+        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
+        b_np = topi.testing.bilinear_resize_python(a_np, (32*scale, 32*scale), "NHWC")
+        np.testing.assert_allclose(out.asnumpy(), b_np, rtol=1e-5, atol=1e-5)
 
 if __name__ == "__main__":
     test_conv2d()
@@ -239,4 +276,6 @@ def test_upsampling():
     test_avg_pool2d_no_count_pad()
     test_global_max_pool2d()
     test_global_avg_pool2d()
-    test_upsampling()
+    test_upsampling_nearest_neighbor()
+    test_upsampling_bilinear()
+    test_resize_bilinear()
diff --git a/topi/include/topi/image/resize.h b/topi/include/topi/image/resize.h
new file mode 100644
index 000000000000..e00fe15fdf1e
--- /dev/null
+++ b/topi/include/topi/image/resize.h
@@ -0,0 +1,316 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file topi/image/resize.h
+ * \brief image resize constructors
+ */
+#ifndef TOPI_IMAGE_RESIZE_H_
+#define TOPI_IMAGE_RESIZE_H_
+
+#include <string>
+#include <vector>
+#include <iterator>
+#include <algorithm>
+
+#include "topi/tags.h"
+#include "topi/detail/ravel_unravel.h"
+#include "topi/detail/constant_utils.h"
+#include "tvm/tvm.h"
+
+namespace topi {
+namespace image {
+using namespace tvm;
+
+/*!
+* \brief Resize given tensor to given shape using nearest neighbour for NHWC
+*
+* \param input The input tensor.
+* \param shape Output shape to resize to.
+* \param align_corners To preserve centers of 4 corner pixels
+* \param name Name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor resized to given shape
+*/
+inline Tensor resize_nearest_neighbor_nhwc(const Tensor& input,
+                                           const Array<Expr>& shape,
+                                           bool align_corners = false,
+                                           std::string name = "tensor",
+                                           std::string tag = kInjective) {
+  Array<Expr> out_shape;
+  out_shape.push_back(input->shape[0]);
+  out_shape.push_back(shape[0]);
+  out_shape.push_back(shape[1]);
+  out_shape.push_back(input->shape[3]);
+
+  Expr h_ratio = shape[0] / input->shape[1];
+  Expr w_ratio = shape[1] / input->shape[2];
+
+  return compute(
+    out_shape, [&](const Array<Var>& indices) {
+    Array<Expr> idx;
+    idx.push_back(indices[0]);
+    idx.push_back(indices[1] / h_ratio);
+    idx.push_back(indices[2] / w_ratio);
+    idx.push_back(indices[3]);
+
+    return input(idx);
+    }, name, tag);
+}
+
+/*!
+* \brief Resize given tensor to given shape using nearest neighbour for NCHW
+*
+* \param input The input tensor.
+* \param shape Output shape to resize to.
+* \param align_corners To preserve centers of 4 corner pixels
+* \param name Name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor resized to given shape
+*/
+inline Tensor resize_nearest_neighbor_nchw(const Tensor& input,
+                                           const Array<Expr>& shape,
+                                           bool align_corners = false,
+                                           std::string name = "tensor",
+                                           std::string tag = kInjective) {
+  Array<Expr> out_shape;
+  out_shape.push_back(input->shape[0]);
+  out_shape.push_back(input->shape[1]);
+  out_shape.push_back(shape[0]);
+  out_shape.push_back(shape[1]);
+
+  Expr h_ratio = shape[0] / input->shape[2];
+  Expr w_ratio = shape[1] / input->shape[3];
+
+  return compute(
+    out_shape, [&](const Array<Var>& indices) {
+    Array<Expr> idx;
+    idx.push_back(indices[0]);
+    idx.push_back(indices[1]);
+    idx.push_back(indices[2] / h_ratio);
+    idx.push_back(indices[3] / w_ratio);
+
+    return input(idx);
+    }, name, tag);
+}
+
+/*!
+* \brief Resize given tensor to given shape using nearest neighbour
+*
+* \param input The input tensor.
+* \param shape Output shape to resize to.
+* \param layout input layout
+* \param align_corners To preserve centers of 4 corner pixels
+* \param name Name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor resized to given shape
+*/
+inline Tensor resize_nearest_neighbor(const Tensor& input,
+                                      const Array<Expr>& shape,
+                                      std::string layout = "NCHW",
+                                      bool align_corners = false,
+                                      std::string name = "tensor",
+                                      std::string tag = kInjective) {
+  CHECK_EQ(align_corners, false) << "Align corners not supported for nearest neighbour";
+
+  if (layout == "NHWC") {
+    return resize_nearest_neighbor_nhwc(input, shape, align_corners);
+  } else {
+    return resize_nearest_neighbor_nchw(input, shape, align_corners);
+  }
+}
+
+/*!
+* \brief Resize given tensor to given shape using bilinear interpolation for NHWC
+*
+* \param input The input tensor.
+* \param shape Output shape to resize to.
+* \param align_corners To preserve centers of 4 corner pixels
+* \param name Name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor resized to given shape
+*/
+inline Tensor resize_bilinear_nhwc(const Tensor& input,
+                                   const Array<Expr>& shape,
+                                   bool align_corners = false,
+                                   std::string name = "tensor",
+                                   std::string tag = kInjective) {
+  Array<Expr> out_shape;
+  out_shape.push_back(input->shape[0]);
+  out_shape.push_back(shape[0]);
+  out_shape.push_back(shape[1]);
+  out_shape.push_back(input->shape[3]);
+
+  Expr cone = make_const(Int(32), 1);
+
+  auto in_height = as_const_int(input->shape[1]);
+  auto in_width = as_const_int(input->shape[2]);
+  auto out_height = as_const_int(shape[0]);
+  auto out_width = as_const_int(shape[1]);
+
+  Expr y_ratio;
+  Expr x_ratio;
+
+  if (align_corners) {
+    y_ratio = make_const(Float(32), (static_cast<float>(*in_height) /
+                                     static_cast<float>(*out_height)));
+    x_ratio = make_const(Float(32), (static_cast<float>(*in_width) /
+                                     static_cast<float>(*out_width)));
+  } else {
+    y_ratio = make_const(Float(32), (static_cast<float>(*in_height - 1) /
+                                     static_cast<float>(*out_height - 1)));
+    x_ratio = make_const(Float(32), (static_cast<float>(*in_width - 1) /
+                                     static_cast<float>(*out_width - 1)));
+  }
+
+  Expr other_y = tvm::ir::Simplify(input->shape[1] - cone);
+  Expr other_x = tvm::ir::Simplify(input->shape[2] - cone);
+
+  return compute(
+    out_shape, [&](const Array<Var>& indices) {
+    auto y0 = HalideIR::Internal::Cast::make(Int(32), tvm::floor(y_ratio * indices[1]));
+    auto x0 = HalideIR::Internal::Cast::make(Int(32), tvm::floor(x_ratio * indices[2]));
+
+    auto y1 = tvm::select(((y0 + cone) > other_y), other_y, (y0 + cone));
+    auto x1 = tvm::select(((x0 + cone) > other_x), other_x, (x0 + cone));
+
+    auto h = (y_ratio * indices[1]) - y0;
+    auto w = (x_ratio * indices[2]) - x0;;
+
+    auto A = input(indices[0], y0, x0, indices[3]);
+    auto B = input(indices[0], y0, x1, indices[3]);
+    auto C = input(indices[0], y1, x0, indices[3]);
+    auto D = input(indices[0], y1, x1, indices[3]);
+
+    return  (A*(cone-w)*(cone-h) + B*(w)*(cone-h) + C*(h)*(cone-w) + D*w*h);
+    }, name, tag);
+}
+
+/*!
+* \brief Resize given tensor to given shape using bilinear interpolation for NCHW
+*
+* \param input The input tensor.
+* \param shape Output shape to resize to.
+* \param align_corners To preserve centers of 4 corner pixels
+* \param name Name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor resized to given shape
+*/
+inline Tensor resize_bilinear_nchw(const Tensor& input,
+                                   const Array<Expr>& shape,
+                                   bool align_corners = false,
+                                   std::string name = "tensor",
+                                   std::string tag = kInjective) {
+  Array<Expr> out_shape;
+  out_shape.push_back(input->shape[0]);
+  out_shape.push_back(input->shape[1]);
+  out_shape.push_back(shape[0]);
+  out_shape.push_back(shape[1]);
+
+  Expr cone = make_const(Int(32), 1);
+
+  auto in_height = as_const_int(input->shape[2]);
+  auto in_width = as_const_int(input->shape[3]);
+  auto out_height = as_const_int(shape[0]);
+  auto out_width = as_const_int(shape[1]);
+
+  Expr y_ratio;
+  Expr x_ratio;
+
+  if (align_corners) {
+    y_ratio = make_const(Float(32), (static_cast<float>(*in_height) /
+                                     static_cast<float>(*out_height)));
+    x_ratio = make_const(Float(32), (static_cast<float>(*in_width) /
+                                     static_cast<float>(*out_width)));
+  } else {
+    y_ratio = make_const(Float(32), (static_cast<float>(*in_height - 1) /
+                                     static_cast<float>(*out_height - 1)));
+    x_ratio = make_const(Float(32), (static_cast<float>(*in_width - 1) /
+                                     static_cast<float>(*out_width - 1)));
+  }
+
+  Expr other_y = tvm::ir::Simplify(input->shape[2] - cone);
+  Expr other_x = tvm::ir::Simplify(input->shape[3] - cone);
+
+  return compute(
+    out_shape, [&](const Array<Var>& indices) {
+    auto y0 = HalideIR::Internal::Cast::make(Int(32), tvm::floor(y_ratio * indices[2]));
+    auto x0 = HalideIR::Internal::Cast::make(Int(32), tvm::floor(x_ratio * indices[3]));
+
+    auto y1 = tvm::select(((y0 + cone) > other_y), other_y, (y0 + cone));
+    auto x1 = tvm::select(((x0 + cone) > other_x), other_x, (x0 + cone));
+
+    auto h = (y_ratio * indices[2]) - y0;
+    auto w = (x_ratio * indices[3]) - x0;;
+
+    auto A = input(indices[0], indices[1], y0, x0);
+    auto B = input(indices[0], indices[1], y0, x1);
+    auto C = input(indices[0], indices[1], y1, x0);
+    auto D = input(indices[0], indices[1], y1, x1);
+
+    return  ((A*(cone-w)*(cone-h)) + (B*(w)*(cone-h)) + (C*(h)*(cone-w)) + (D*w*h));
+    }, name, tag);
+}
+
+/*!
+* \brief Resize given tensor to given shape using bilinear interpolation
+*
+* \param input The input tensor.
+* \param shape Output shape to resize to.
+* \param layout input layout
+* \param align_corners To preserve centers of 4 corner pixels
+* \param name Name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor resized to given shape
+*/
+inline Tensor resize_bilinear(const Tensor& input,
+                              const Array<Expr>& shape,
+                              std::string layout = "NCHW",
+                              bool align_corners = false,
+                              std::string name = "tensor",
+                              std::string tag = kInjective) {
+  Tensor ret;
+
+  if (layout == "NHWC") {
+    ret = resize_bilinear_nhwc(input, shape, align_corners);
+  } else {
+    ret = resize_bilinear_nchw(input, shape, align_corners);
+  }
+
+  return cast(ret, input->dtype);
+}
+
+/*!
+* \brief Resize given tensor to given shape
+*
+* \param input The input tensor.
+* \param shape Output shape to resize to.
+* \param layout input layout
+* \param align_corners To preserve centers of 4 corner pixels
+* \param mode Angorithm to use (NEAREST_NEIGHBOR / BILINEAR)
+* \param name Name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor resized to given shape
+*/
+inline Tensor resize(const Tensor& input,
+                     const Array<Expr>& shape,
+                     std::string layout = "NCHW",
+                     bool align_corners = false,
+                     std::string mode = "BILINEAR",
+                     std::string name = "tensor",
+                     std::string tag = kInjective) {
+  if (mode == "NEAREST_NEIGHBOR") {
+    return resize_nearest_neighbor(input, shape, layout, align_corners);
+  } else {
+    return resize_bilinear(input, shape, layout, align_corners);
+  }
+}
+
+}  // namespace image
+}  // namespace topi
+#endif  // TOPI_IMAGE_RESIZE_H_
diff --git a/topi/include/topi/nn/upsampling.h b/topi/include/topi/nn/upsampling.h
new file mode 100644
index 000000000000..e0fe2991c649
--- /dev/null
+++ b/topi/include/topi/nn/upsampling.h
@@ -0,0 +1,44 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file topi/nn/upsampling.h
+ * \brief upsampling op constructors
+ */
+#ifndef TOPI_NN_UPSAMPLING_H_
+#define TOPI_NN_UPSAMPLING_H_
+
+#include <string>
+#include <vector>
+#include <iterator>
+#include <algorithm>
+
+#include "topi/image/resize.h"
+
+namespace topi {
+namespace nn {
+using namespace tvm;
+using namespace topi::image;
+
+/*!
+* \brief Upsample given tensor to given shape
+*
+* \param input The input tensor.
+* \param shape Output shape to upsample.
+* \param layout input layout
+* \param mode Angorithm to use (NEAREST_NEIGHBOR / BILINEAR)
+* \param name Name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor upsampled to given shape
+*/
+inline Tensor upsampling(const Tensor& input,
+                         const Array<Expr> shape,
+                         std::string layout = "NCHW",
+                         std::string mode = "NEAREST_NEIGHBOR",
+                         std::string name = "tensor",
+                         std::string tag = kInjective) {
+  return resize(input, shape, layout, false, mode);
+}
+
+}  // namespace nn
+}  // namespace topi
+#endif  // TOPI_NN_UPSAMPLING_H_
diff --git a/topi/python/topi/__init__.py b/topi/python/topi/__init__.py
index f82a40f6b42e..59549f89905b 100644
--- a/topi/python/topi/__init__.py
+++ b/topi/python/topi/__init__.py
@@ -30,6 +30,7 @@
 from . import util
 from . import rocm
 from . import vision
+from . import image
 # not import testing by default
 # because testing can have extra deps that are not necessary
 # we can import them from test cases explicitly
diff --git a/topi/python/topi/cpp.py b/topi/python/topi/cpp.py
index ae19484ac985..4c68dd738cb6 100644
--- a/topi/python/topi/cpp.py
+++ b/topi/python/topi/cpp.py
@@ -50,6 +50,8 @@ def _create_module(name):
 _init_api_prefix("topi.cpp.vision", "topi.vision")
 yolo2 = _create_module("vision.yolo2")
 _init_api_prefix("topi.cpp.vision.yolo2", "topi.vision.yolo2")
+image = _create_module("image")
+_init_api_prefix("topi.cpp.image", "topi.image")
 
 class IntVector(object):
     """Handle to std::vector<int> instance """
diff --git a/topi/python/topi/image/__init__.py b/topi/python/topi/image/__init__.py
new file mode 100644
index 000000000000..840ae83ea8ac
--- /dev/null
+++ b/topi/python/topi/image/__init__.py
@@ -0,0 +1,5 @@
+# pylint: disable=wildcard-import
+"""IMAGE network operators"""
+from __future__ import absolute_import as _abs
+
+from .resize import *
diff --git a/topi/python/topi/image/resize.py b/topi/python/topi/image/resize.py
new file mode 100644
index 000000000000..461214cb1fbd
--- /dev/null
+++ b/topi/python/topi/image/resize.py
@@ -0,0 +1,33 @@
+"""TVM operator input resize compute."""
+from __future__ import absolute_import
+import topi
+
+def resize(data, size, layout="NCHW", align_corners=False, method="BILINEAR"):
+    """Perform resize operation on the data.
+
+    Parameters
+    ----------
+    inputs : tvm.Tensor
+        inputs is a 4-D tensor with shape
+        [batch, channel, in_height, in_width]
+        or  [batch, in_height, in_width, channel]
+
+    size: Tuple
+        Output resolution scale to
+
+    layout: string, optional
+        either "NCHW" or "NHWC"
+
+    align_corners: Boolean, optional
+        To preserve the values at the corner pixels
+
+    method: {"BILINEAR", "NEAREST_NEIGHBOR"}
+        Method to be used for resizing.
+
+    Returns
+    -------
+    output : tvm.Tensor
+        4-D with shape [batch, channel, in_height*scale, in_width*scale]
+        or [batch, in_height*scale, in_width*scale, channel]
+    """
+    return topi.cpp.image.resize(data, size, layout, align_corners, method)
diff --git a/topi/python/topi/nn/upsampling.py b/topi/python/topi/nn/upsampling.py
index 9297eb4ad06b..55f7844319f3 100644
--- a/topi/python/topi/nn/upsampling.py
+++ b/topi/python/topi/nn/upsampling.py
@@ -1,25 +1,28 @@
 """TVM operator upsampling compute."""
 from __future__ import absolute_import
-import tvm
-from .. import util
+import topi
 
 
-def upsampling(data, scale, layout="NCHW"):
-    """Perform nearest neighbor upsampling on the data.
-       Bilinear upsampling is not supported.
+def upsampling(data, scale, layout="NCHW", method='NEAREST_NEIGHBOR'):
+    """Perform upsampling on the data.
+       Nearest neighbor and bilinear upsampling are supported.
 
     Parameters
     ----------
-    data : tvm.Tensor
-        4-D with shape [batch, channel, in_height, in_width]
+    inputs : tvm.Tensor
+        inputs is a 4-D tensor with shape
+        [batch, channel, in_height, in_width]
         or  [batch, in_height, in_width, channel]
 
-    scale: int
-        upsampling scaling factor
+    scale : int
+        Scaling factor
 
-    layout: string
+    layout : string, optional
         either "NCHW" or "NHWC"
 
+    method : {"BILINEAR", "NEAREST_NEIGHBOR"}
+        Method to be used for upsampling.
+
     Returns
     -------
     output : tvm.Tensor
@@ -28,53 +31,10 @@ def upsampling(data, scale, layout="NCHW"):
     """
 
     if layout == "NCHW":
-        return upsampling_nchw(data, scale)
+        out_shape = (data.shape[2] * scale, data.shape[3] * scale)
     elif layout == "NHWC":
-        return upsampling_nhwc(data, scale)
+        out_shape = (data.shape[1] * scale, data.shape[2] * scale)
     else:
         raise ValueError("not support this layout {} yet".format(layout))
 
-
-def upsampling_nchw(data, scale):
-    """Perform nearest neighor upsampling on NCHW layout input.
-
-    Parameters
-    ----------
-    data : tvm.Tensor
-        4-D with shape [batch, channel, in_height, in_width]
-
-    scale: int
-        upsampling scaling factor
-
-    Returns
-    -------
-    output : tvm.Tensor
-        4-D with shape [batch, channel, in_height*scale, in_width*scale]
-    """
-    batch, channel, height, width = data.shape
-    out_height = util.simplify(height * scale)
-    out_width = util.simplify(width * scale)
-
-    return tvm.compute((batch, channel, out_height, out_width), \
-                        lambda n, c, h, w: data[n, c, h/scale, w/scale])
-
-
-def upsampling_nhwc(data, scale):
-    """Perform nearest neighor upsampling on NHWC layout input.
-
-    Parameters
-    ----------
-    data : tvm.Tensor
-        4-D with shape [batch, in_height, in_width, channel]
-
-    scale: int
-        upsampling scaling factor
-
-    """
-
-    batch, height, width, channel = data.shape
-    out_height = util.simplify(height * scale)
-    out_width = util.simplify(width * scale)
-
-    return tvm.compute((batch, out_height, out_width, channel), \
-                        lambda n, h, w, c: data[n, h/scale, w/scale, c])
+    return topi.cpp.nn.upsampling(data, out_shape, layout, method)
diff --git a/topi/python/topi/testing/__init__.py b/topi/python/topi/testing/__init__.py
index 66a865724dc3..3731040e3a85 100644
--- a/topi/python/topi/testing/__init__.py
+++ b/topi/python/topi/testing/__init__.py
@@ -12,6 +12,7 @@
 from .dilate_python import dilate_python
 from .softmax_python import softmax_python, log_softmax_python
 from .upsampling_python import upsampling_python
+from .bilinear_resize_python import bilinear_resize_python
 from .reorg_python import reorg_python
 from .region_python import region_python
 from .shortcut_python import shortcut_python
diff --git a/topi/python/topi/testing/bilinear_resize_python.py b/topi/python/topi/testing/bilinear_resize_python.py
new file mode 100644
index 000000000000..de931d7e1f5d
--- /dev/null
+++ b/topi/python/topi/testing/bilinear_resize_python.py
@@ -0,0 +1,79 @@
+# pylint: disable=invalid-name, line-too-long, unused-variable, too-many-locals
+"""Bilinear Scale in python"""
+import math
+import numpy as np
+
+def bilinear_weights(height, width, new_h, new_w, align_corners=False):
+    """ Helper function to generate weights for bilinear scaling """
+
+    if align_corners:
+        x_ratio = np.float32(np.float32(width)/np.float32(new_w))
+        y_ratio = np.float32(np.float32(height)/np.float32(new_h))
+    else:
+        x_ratio = np.float32(np.float32(width-1)/np.float32(new_w-1))
+        y_ratio = np.float32(np.float32(height-1)/np.float32(new_h-1))
+
+    def _bilinear_interpolation(y, x):
+        x_coord = math.floor(x_ratio * x)
+        y_coord = math.floor(y_ratio * y)
+        x_diff = np.float32((x_ratio * x) - x_coord)
+        y_diff = np.float32((y_ratio * y) - y_coord)
+
+        return [y_coord, x_coord, y_diff, x_diff]
+
+    # weights to hold (srcx, srcy, x_diff, y_diff) for each out value.
+    weights = np.empty([new_h, new_w, 4], dtype='float32')
+
+    for i in range(new_h):
+        for j in range(new_w):
+            weights[i][j] = _bilinear_interpolation(i, j)
+    return weights
+
+def bilinear_resize_python(image, out_size, layout, align_corners=False):
+    """ Bilinear scaling using python"""
+    (new_h, new_w) = out_size
+
+    if layout == 'NHWC':
+        (batch, h, w, channel) = image.shape
+        scaled_image = np.ones((batch, new_h, new_w, channel))
+    else:
+        (batch, channel, h, w) = image.shape
+        scaled_image = np.ones((batch, channel, new_h, new_w))
+
+    weights = bilinear_weights(h, w, new_h, new_w, align_corners)
+
+    for b in range(batch):
+        for i in range(channel):
+            for j in range(new_h):
+                for k in range(new_w):
+                    y0 = int(weights[j][k][0])
+                    x0 = int(weights[j][k][1])
+
+                    x1 = min((x0+1), (w-1))
+                    y1 = min((y0+1), (h-1))
+
+                    y_diff = weights[j][k][2]
+                    x_diff = weights[j][k][3]
+
+                    if layout == 'NHWC':
+                        A = image[b][y0][x0][i]
+                        B = image[b][y0][x1][i]
+                        C = image[b][y1][x0][i]
+                        D = image[b][y1][x1][i]
+                    else:
+                        A = image[b][i][y0][x0]
+                        B = image[b][i][y0][x1]
+                        C = image[b][i][y1][x0]
+                        D = image[b][i][y1][x1]
+
+                    pixel = np.float32((A*(1-x_diff)*(1-y_diff) +
+                                        B*(x_diff)*(1-y_diff) +
+                                        C*(y_diff)*(1-x_diff) +
+                                        D*(x_diff*y_diff)))
+
+                    if layout == 'NHWC':
+                        scaled_image[b][j][k][i] = pixel
+                    else:
+                        scaled_image[b][i][j][k] = pixel
+
+    return scaled_image
diff --git a/topi/python/topi/testing/upsampling_python.py b/topi/python/topi/testing/upsampling_python.py
index 328c7a5a0bc1..fc4ad652f900 100644
--- a/topi/python/topi/testing/upsampling_python.py
+++ b/topi/python/topi/testing/upsampling_python.py
@@ -3,13 +3,26 @@
 import numpy as np
 
 def upsample_nearest(arr, scale):
+    """ Populate the array by scale factor"""
     return arr.repeat(scale, axis=0).repeat(scale, axis=1)
 
-def upsampling_python(data, scale):
+def upsampling_python(data, scale, layout='NCHW'):
+    """ Python version of scaling using nearest neighbour """
+
     ishape = data.shape
-    oshape = (ishape[0], ishape[1], ishape[2]*scale, ishape[3]*scale)
-    output_np = np.zeros(oshape, dtype=data.dtype)
-    for b in range(oshape[0]):
-        for c in range(oshape[1]):
-            output_np[b, c, :, :] = upsample_nearest(data[b, c, :, :], scale)
-    return output_np
+    if layout == 'NCHW':
+        oshape = (ishape[0], ishape[1], ishape[2]*scale, ishape[3]*scale)
+        output_np = np.zeros(oshape, dtype=data.dtype)
+        for b in range(oshape[0]):
+            for c in range(oshape[1]):
+                output_np[b, c, :, :] = upsample_nearest(data[b, c, :, :], scale)
+        return output_np
+    elif layout == 'NHWC':
+        oshape = (ishape[0], ishape[1]*scale, ishape[1]*scale, ishape[3])
+        output_np = np.zeros(oshape, dtype=data.dtype)
+        for b in range(oshape[0]):
+            for c in range(oshape[3]):
+                output_np[b, :, :, c] = upsample_nearest(data[b, :, :, c], scale)
+        return output_np
+    else:
+        raise ValueError("not support this layout {} yet".format(layout))
diff --git a/topi/src/topi.cc b/topi/src/topi.cc
index 9c820edfeb37..93301d703097 100644
--- a/topi/src/topi.cc
+++ b/topi/src/topi.cc
@@ -23,8 +23,10 @@
 #include <topi/nn/mapping.h>
 #include <topi/nn/pooling.h>
 #include <topi/nn/softmax.h>
+#include <topi/nn/upsampling.h>
 
 #include <topi/vision/reorg.h>
+#include <topi/image/resize.h>
 #include <topi/vision/yolo2/region.h>
 #include <topi/generic/default.h>
 #include <topi/generic/extern.h>
@@ -285,6 +287,12 @@ TVM_REGISTER_GLOBAL("topi.strided_slice")
   *rv = strided_slice(args[0], args[1], args[2], args[3]);
   });
 
+/* Ops from nn/upsampling.h */
+TVM_REGISTER_GLOBAL("topi.nn.upsampling")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = nn::upsampling(args[0], args[1], args[2], args[3]);
+  });
+
 /* Ops from nn/batch_norm.h */
 TVM_REGISTER_GLOBAL("topi.nn.batch_norm_inference")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
@@ -366,10 +374,18 @@ TVM_REGISTER_GLOBAL("topi.vision.reorg")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
   *rv = vision::reorg(args[0], args[1]);
   });
+
 TVM_REGISTER_GLOBAL("topi.vision.yolo2.region")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
   *rv = vision::yolo2::region(args[0], args[1], args[2], args[3], args[4], args[5]);
   });
+
+/* Ops from image/resize.h */
+TVM_REGISTER_GLOBAL("topi.image.resize")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = image::resize(args[0], args[1], args[2], args[3], args[4]);
+  });
+
 /* Generic schedules */
 TVM_REGISTER_GLOBAL("topi.generic.default_schedule")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
diff --git a/topi/tests/python/test_topi_resize.py b/topi/tests/python/test_topi_resize.py
new file mode 100644
index 000000000000..cb83c18d8c1a
--- /dev/null
+++ b/topi/tests/python/test_topi_resize.py
@@ -0,0 +1,57 @@
+"""Test code for bilinear scale """
+import numpy as np
+import tvm
+import topi
+import topi.testing
+import math
+
+def verify_bilinear_scale(batch, in_channel, in_height, in_width, out_height, out_width, layout='NCHW', align_corners=False):
+
+    if layout == 'NCHW':
+        A = tvm.placeholder((batch, in_channel, in_height, in_width), name='A', dtype='float32')
+        dtype = A.dtype
+        out_shape = (batch, in_channel, out_height, out_width)
+        a_np = np.random.uniform(size=(batch, in_channel, in_height, in_width)).astype(dtype)
+    elif layout == 'NHWC':
+        A = tvm.placeholder((batch, in_height, in_width, in_channel), name='A', dtype='float32')
+        dtype = A.dtype
+        out_shape = (batch, out_height, out_width, in_channel)
+        a_np = np.random.uniform(size=(batch, in_height, in_width, in_channel)).astype(dtype)
+    else:
+        raise NotImplementedError(
+            'Layout not supported {} '.format(layout))
+
+    B = topi.image.resize(A, (out_height, out_width), layout=layout, align_corners=align_corners)
+
+    b_np = topi.testing.bilinear_resize_python(a_np, (out_height, out_width), layout, align_corners)
+
+    def check_device(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s = topi.generic.schedule_injective(B)
+        a = tvm.nd.array(a_np, ctx)
+        b = tvm.nd.array(np.zeros(out_shape, dtype=dtype), ctx)
+        f = tvm.build(s, [A, B], device)
+        f(a, b)
+
+        np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-3, atol=1e-3)
+
+    for device in ['llvm', 'cuda', 'vulkan']:
+        check_device(device)
+
+def test_resize():
+    # Scale NCHW
+    verify_bilinear_scale(4, 16, 32, 32, 50, 50, 'NCHW')
+    # Scale NCHW + Align Corners
+    verify_bilinear_scale(6, 32, 64, 64, 20, 20, 'NCHW', True)
+    # Scale NHWC
+    verify_bilinear_scale(4, 16, 32, 32, 50, 50, "NHWC")
+    # Scale NHWC + Align Corners
+    verify_bilinear_scale(6, 32, 64, 64, 20, 20, "NHWC", True)
+
+if __name__ == "__main__":
+    test_resize()
diff --git a/topi/tests/python/test_topi_upsampling.py b/topi/tests/python/test_topi_upsampling.py
index 7421dd4151e6..d03ea0bc7e14 100644
--- a/topi/tests/python/test_topi_upsampling.py
+++ b/topi/tests/python/test_topi_upsampling.py
@@ -5,14 +5,26 @@
 import topi.testing
 import math
 
-def verify_upsampling(batch, in_channel, in_height, in_width, scale):
-    A = tvm.placeholder((batch, in_channel, in_height, in_width), name='A')
-    B = topi.nn.upsampling(A, scale)
-    out_shape = (batch, in_channel, in_height*scale, in_width*scale)
-    dtype = A.dtype
+def verify_upsampling(batch, in_channel, in_height, in_width, scale, layout='NCHW'):
 
-    a_np = np.random.uniform(size=(batch, in_channel, in_height, in_width)).astype(dtype)
-    b_np = topi.testing.upsampling_python(a_np, scale)
+
+    if layout == 'NCHW':
+        A = tvm.placeholder((batch, in_channel, in_height, in_width), name='A')
+        dtype = A.dtype
+        out_shape = (batch, in_channel, in_height*scale, in_width*scale)
+        a_np = np.random.uniform(size=(batch, in_channel, in_height, in_width)).astype(dtype)
+    elif layout == 'NHWC':
+        A = tvm.placeholder((batch, in_height, in_width, in_channel), name='A')
+        dtype = A.dtype
+        out_shape = (batch, in_height*scale, in_width*scale, in_channel)
+        a_np = np.random.uniform(size=(batch, in_height, in_width, in_channel)).astype(dtype)
+    else:
+        raise NotImplementedError(
+            'Layout not supported {} '.format(layout))
+
+    B = topi.nn.upsampling(A, scale, layout=layout)
+
+    b_np = topi.testing.upsampling_python(a_np, scale, layout)
 
     def check_device(device):
         ctx = tvm.context(device, 0)
@@ -33,8 +45,12 @@ def check_device(device):
         check_device(device)
 
 def test_upsampling():
+    # NCHW
     verify_upsampling(8, 16, 32, 32, 2)
     verify_upsampling(12, 32, 64, 64, 3)
+    # NHWC
+    verify_upsampling(8, 16, 32, 32, 2, "NHWC")
+    verify_upsampling(12, 32, 64, 64, 3, "NHWC")
 
 if __name__ == "__main__":
     test_upsampling()

From 33f320bdabacb7603de70a508be4d18e1ac99fca Mon Sep 17 00:00:00 2001
From: Tatsuya Nishiyama <nishiyama.tatsuya@lab.ntt.co.jp>
Date: Fri, 15 Jun 2018 00:54:42 +0900
Subject: [PATCH 722/949] Fix missing cublas libraries (#1281)

---
 Jenkinsfile               | 1 +
 cmake/util/FindCUDA.cmake | 6 ++++++
 2 files changed, 7 insertions(+)

diff --git a/Jenkinsfile b/Jenkinsfile
index b677ccd2b78d..b46632a960d6 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -88,6 +88,7 @@ stage('Build') {
            mkdir -p build
            cd build
            cp ../cmake/config.cmake .
+           echo set\\(USE_CUBLAS ON\\) >> config.cmake
            echo set\\(USE_CUDNN ON\\) >> config.cmake
            echo set\\(USE_CUDA ON\\) >> config.cmake
            echo set\\(USE_OPENGL ON\\) >> config.cmake
diff --git a/cmake/util/FindCUDA.cmake b/cmake/util/FindCUDA.cmake
index 41936acca217..83d2e6500fea 100644
--- a/cmake/util/FindCUDA.cmake
+++ b/cmake/util/FindCUDA.cmake
@@ -50,6 +50,9 @@ macro(find_cuda use_cuda)
       find_library(CUDA_CUDNN_LIBRARY cudnn
         ${CUDA_TOOLKIT_ROOT_DIR}/lib/x64
         ${CUDA_TOOLKIT_ROOT_DIR}/lib/win32)
+      find_library(CUDA_CUBLAS_LIBRARY cublas
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib/x64
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib/win32)
     else(MSVC)
       find_library(_CUDA_CUDA_LIBRARY cuda
         PATHS ${CUDA_TOOLKIT_ROOT_DIR}
@@ -63,6 +66,9 @@ macro(find_cuda use_cuda)
       find_library(CUDA_CUDNN_LIBRARY cudnn
         ${CUDA_TOOLKIT_ROOT_DIR}/lib64
         ${CUDA_TOOLKIT_ROOT_DIR}/lib)
+      find_library(CUDA_CUBLAS_LIBRARY cublas
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib64
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib)
     endif(MSVC)
   endif(CUDA_FOUND)
 endmacro(find_cuda)

From 56e12f2acc5a287ad4690dc902e95df863284d56 Mon Sep 17 00:00:00 2001
From: MORITA Kazutaka <morita.kazutaka@lab.ntt.co.jp>
Date: Fri, 15 Jun 2018 00:55:19 +0900
Subject: [PATCH 723/949] Use CMake for make clean (#1280)

---
 Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index 71378753236d..748f8260b959 100644
--- a/Makefile
+++ b/Makefile
@@ -110,4 +110,4 @@ jvminstall:
 
 # clean rule
 clean:
-	$(RM) -rf build lib bin *~ */*~ */*/*~ */*/*/*~ */*.o */*/*.o */*/*/*.o */*.d */*/*.d */*/*/*.d
+	@mkdir -p build && cd build && cmake .. && $(MAKE) clean

From 2f2f60f46dafca84f848c3b7ef74877ac7e01f06 Mon Sep 17 00:00:00 2001
From: MORITA Kazutaka <morita.kazutaka@lab.ntt.co.jp>
Date: Fri, 15 Jun 2018 01:27:00 +0900
Subject: [PATCH 724/949] Remove incorrect extension registration of
 tvm::Target (#1272)

---
 topi/src/topi.cc | 11 -----------
 1 file changed, 11 deletions(-)

diff --git a/topi/src/topi.cc b/topi/src/topi.cc
index 93301d703097..4169f5f563ad 100644
--- a/topi/src/topi.cc
+++ b/topi/src/topi.cc
@@ -47,22 +47,11 @@
 #include <topi/rocm/dense.h>
 #include <topi/rocm/vision.h>
 
-namespace tvm {
-namespace runtime {
-template<>
-struct extension_class_info<tvm::Target> {
-  static const int code = 28;
-};
-}  // namespace tvm
-} // namespace runtime
-
 namespace topi {
 
 using namespace tvm;
 using namespace tvm::runtime;
 
-TVM_REGISTER_EXT_TYPE(tvm::Target);
-
 /*! \brief Canonicalize an argument that may be Array<Expr> or int to Array<Expr> */
 Array<Expr> ArrayOrInt(TVMArgValue arg) {
   if (arg.type_code() == kDLInt || arg.type_code() == kDLUInt) {

From cff2382ddba0b96535b24010fd45a8b11407f87d Mon Sep 17 00:00:00 2001
From: MORITA Kazutaka <morita.kazutaka@lab.ntt.co.jp>
Date: Fri, 15 Jun 2018 04:08:39 +0900
Subject: [PATCH 725/949] Link system library needed for LLVM (#1282)

---
 cmake/util/FindLLVM.cmake | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/cmake/util/FindLLVM.cmake b/cmake/util/FindLLVM.cmake
index 9a38ab07165f..3396edc7d225 100644
--- a/cmake/util/FindLLVM.cmake
+++ b/cmake/util/FindLLVM.cmake
@@ -26,14 +26,18 @@ macro(find_llvm use_llvm)
     execute_process(COMMAND ${LLVM_CONFIG} --includedir
       OUTPUT_VARIABLE LLVM_INCLUDE_DIRS)
     execute_process(COMMAND ${LLVM_CONFIG} --libfiles
-      OUTPUT_VARIABLE LLVM_LIBS)
+      OUTPUT_VARIABLE __llvm_libfiles)
+    execute_process(COMMAND ${LLVM_CONFIG} --system-libs
+      OUTPUT_VARIABLE __llvm_system_libs)
     execute_process(COMMAND ${LLVM_CONFIG} --cxxflags
       OUTPUT_VARIABLE __llvm_cxxflags)
     execute_process(COMMAND ${LLVM_CONFIG} --version
       COMMAND cut -b 1,3
       OUTPUT_VARIABLE TVM_LLVM_VERSION)
     string(REGEX MATCHALL "(^| )-D[A-Za-z0-9_]*" LLVM_DEFINITIONS ${__llvm_cxxflags})
-    string(STRIP ${LLVM_LIBS} LLVM_LIBS)
+    string(STRIP ${__llvm_libfiles} __llvm_libfiles)
+    string(STRIP ${__llvm_system_libs} __llvm_system_libs)
+    set(LLVM_LIBS "${__llvm_libfiles} ${__llvm_system_libs}")
     separate_arguments(LLVM_LIBS)
     string(STRIP ${LLVM_INCLUDE_DIRS} LLVM_INCLUDE_DIRS)
     string(STRIP ${TVM_LLVM_VERSION} TVM_LLVM_VERSION)

From d83514b3ba20f0c2cebf4532cb4c9432309858fa Mon Sep 17 00:00:00 2001
From: eqy <eqy@cs.washington.edu>
Date: Thu, 14 Jun 2018 14:00:17 -0700
Subject: [PATCH 726/949] fix proxy registration with tracker (#1283)

---
 python/tvm/contrib/rpc/proxy.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/python/tvm/contrib/rpc/proxy.py b/python/tvm/contrib/rpc/proxy.py
index b1168c1a843f..c7f66d68e492 100644
--- a/python/tvm/contrib/rpc/proxy.py
+++ b/python/tvm/contrib/rpc/proxy.py
@@ -333,7 +333,7 @@ def _update_tracker(self, period_update=False):
                 rpc_key = key.split(":")[0]
                 base.sendjson(self._tracker_conn,
                               [TrackerCode.PUT, rpc_key,
-                               (self._listen_port, key)])
+                              (self._listen_port, key), None])
                 assert base.recvjson(self._tracker_conn) == TrackerCode.SUCCESS
                 if rpc_key not in self._key_set:
                     self._key_set.add(rpc_key)

From 1b8ff3980bce6f1839fbca62d47c9b8251989dfe Mon Sep 17 00:00:00 2001
From: eqy <eqy@cs.washington.edu>
Date: Thu, 14 Jun 2018 14:58:18 -0700
Subject: [PATCH 727/949] fix lint (#1284)

---
 python/tvm/contrib/rpc/proxy.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/python/tvm/contrib/rpc/proxy.py b/python/tvm/contrib/rpc/proxy.py
index c7f66d68e492..e1e81d20b611 100644
--- a/python/tvm/contrib/rpc/proxy.py
+++ b/python/tvm/contrib/rpc/proxy.py
@@ -333,7 +333,7 @@ def _update_tracker(self, period_update=False):
                 rpc_key = key.split(":")[0]
                 base.sendjson(self._tracker_conn,
                               [TrackerCode.PUT, rpc_key,
-                              (self._listen_port, key), None])
+                               (self._listen_port, key), None])
                 assert base.recvjson(self._tracker_conn) == TrackerCode.SUCCESS
                 if rpc_key not in self._key_set:
                     self._key_set.add(rpc_key)

From 6ab4da6783417d8afdeb6b0426b44959b2afc709 Mon Sep 17 00:00:00 2001
From: Yao Wang <kevinthesunwy@gmail.com>
Date: Thu, 14 Jun 2018 15:22:08 -0700
Subject: [PATCH 728/949] SSD support in NNVM (#1214)

---
 nnvm/include/nnvm/top/nn.h                    |  49 ++++++
 nnvm/python/nnvm/frontend/mxnet.py            |  32 +++-
 nnvm/python/nnvm/testing/download.py          |  69 ++++++++
 nnvm/python/nnvm/testing/resnet.py            |   2 +-
 nnvm/python/nnvm/top/attr_dict.py             |  15 ++
 nnvm/python/nnvm/top/vision.py                |  62 ++++++-
 nnvm/src/top/vision/nms.cc                    |  80 +++++++++
 nnvm/src/top/vision/ssd/mutibox_op.cc         | 158 ++++++++++++++++++
 nnvm/tests/python/compiler/test_top_level4.py | 116 +++++++++++++
 topi/python/topi/vision/ssd/multibox.py       |  22 +--
 tutorials/nnvm/deploy_ssd.py                  | 137 +++++++++++++++
 tutorials/nnvm/from_darknet.py                |  73 +-------
 12 files changed, 731 insertions(+), 84 deletions(-)
 create mode 100644 nnvm/python/nnvm/testing/download.py
 create mode 100644 nnvm/src/top/vision/nms.cc
 create mode 100644 nnvm/src/top/vision/ssd/mutibox_op.cc
 create mode 100644 tutorials/nnvm/deploy_ssd.py

diff --git a/nnvm/include/nnvm/top/nn.h b/nnvm/include/nnvm/top/nn.h
index bee6137829c5..bbdb3b9c4f12 100644
--- a/nnvm/include/nnvm/top/nn.h
+++ b/nnvm/include/nnvm/top/nn.h
@@ -319,6 +319,55 @@ struct LayoutTransformParam : public dmlc::Parameter<LayoutTransformParam> {
   }
 };
 
+struct MultiBoxPriorParam : public dmlc::Parameter<MultiBoxPriorParam> {
+  Tuple<float> sizes;
+  Tuple<float> ratios;
+  Tuple<float> steps;
+  Tuple<float> offsets;
+  bool clip;
+
+  DMLC_DECLARE_PARAMETER(MultiBoxPriorParam) {
+    DMLC_DECLARE_FIELD(sizes).set_default(Tuple<float>({1.0}))
+      .describe("List of sizes of generated MultiBoxPriores.");
+    DMLC_DECLARE_FIELD(ratios).set_default(Tuple<float>({1.0}))
+    .describe("List of aspect ratios of generated MultiBoxPriores.");
+    DMLC_DECLARE_FIELD(steps).set_default(Tuple<float>({-1.0, -1.0}))
+    .describe("Priorbox step across y and x, -1 for auto calculation.");
+    DMLC_DECLARE_FIELD(offsets).set_default(Tuple<float>({0.5, 0.5}))
+    .describe("Priorbox center offsets, y and x respectively.");
+    DMLC_DECLARE_FIELD(clip).set_default(false)
+    .describe("Whether to clip out-of-boundary boxes.");
+  }
+};
+
+struct MultiBoxTransformLocParam : public dmlc::Parameter<MultiBoxTransformLocParam> {
+  bool clip;
+  float threshold;
+  Tuple<float> variances;
+  DMLC_DECLARE_PARAMETER(MultiBoxTransformLocParam) {
+    DMLC_DECLARE_FIELD(clip).set_default(true)
+      .describe("Clip out-of-boundary boxes.");
+    DMLC_DECLARE_FIELD(threshold).set_default(0.01)
+    .describe("Threshold to be a positive prediction.");
+    DMLC_DECLARE_FIELD(variances).set_default(Tuple<float>{0.1, 0.1, 0.2, 0.2})
+    .describe("Variances to be decoded from box regression output.");
+  }
+};
+
+struct NMSParam : public dmlc::Parameter<NMSParam> {
+  float nms_threshold;
+  bool force_suppress;
+  int nms_topk;
+  DMLC_DECLARE_PARAMETER(NMSParam) {
+    DMLC_DECLARE_FIELD(nms_threshold).set_default(0.5)
+      .describe("Non-maximum suppression threshold.");
+    DMLC_DECLARE_FIELD(force_suppress).set_default(false)
+    .describe("Suppress all detections regardless of class_id.");
+    DMLC_DECLARE_FIELD(nms_topk).set_default(-1)
+    .describe("Keep maximum top k detections before nms, -1 for no limit.");
+  }
+};
+
 }  // namespace top
 }  // namespace nnvm
 
diff --git a/nnvm/python/nnvm/frontend/mxnet.py b/nnvm/python/nnvm/frontend/mxnet.py
index 82b8e555d5ec..deae3112bf5f 100644
--- a/nnvm/python/nnvm/frontend/mxnet.py
+++ b/nnvm/python/nnvm/frontend/mxnet.py
@@ -71,7 +71,7 @@ def _batch_norm(inputs, attrs):
     new_attrs['axis'] = attrs.get('axis', 1)
     new_attrs['epsilon'] = attrs.get('eps', 0.001)
     new_attrs['center'] = True
-    new_attrs['scale'] = True
+    new_attrs['scale'] = not _parse_bool_str(attrs, 'fix_gamma', default="False")
     return _get_nnvm_op(op_name)(*inputs, **new_attrs)
 
 def _concat(inputs, attrs):
@@ -195,6 +195,12 @@ def _split(inputs, attrs):
     new_attrs['axis'] = attrs.get('axis', 1)
     return _get_nnvm_op(op_name)(*inputs, **new_attrs)
 
+def _softmax_activation(inputs, attrs):
+    op_name, new_attrs = 'softmax', {}
+    mode = attrs.get('mode', 'instance')
+    new_attrs['axis'] = 0 if mode == 'instance' else 1
+    return _get_nnvm_op(op_name)(inputs[0], **new_attrs)
+
 def _softmax_output(inputs, attrs):
     op_name, new_attrs = 'softmax', {}
     if _parse_bool_str(attrs, 'multi_output'):
@@ -212,6 +218,25 @@ def _clip(inputs, attrs):
     new_attrs['a_max'] = _required_attr(attrs, 'a_max')
     return _get_nnvm_op(op_name)(*inputs, **new_attrs)
 
+def _contrib_multibox_detection(inputs, attrs):
+    clip = _parse_bool_str(attrs, 'clip', default='True')
+    threshold = attrs.get('threshold') or 0.01
+    nms_threshold = attrs.get('nms_threshold') or 0.5
+    force_suppress = _parse_bool_str(attrs, 'force_suppress', default='False')
+    variances = tuple([float(x.strip()) for x in attrs.get('variances').strip('()').split(',')]) \
+        if attrs.get('variances') is not None else (0.1, 0.1, 0.2, 0.2)
+    nms_topk = attrs.get('nms_topk') or -1
+    new_attrs0 = {'clip': clip, 'threshold': float(threshold), 'variances': variances}
+    new_attrs1 = {'nms_threshold': float(nms_threshold), 'force_suppress': force_suppress,
+                  'nms_topk': int(nms_topk)}
+    data, valid_count = _get_nnvm_op('multibox_transform_loc')(inputs[0], inputs[1],
+                                                               inputs[2], **new_attrs0)
+    return _get_nnvm_op('nms')(data, valid_count, **new_attrs1)
+
+def _elemwise_sum(inputs, _):
+    new_attrs = {'num_args':len(inputs)}
+    return _get_nnvm_op('elemwise_sum')(*inputs, **new_attrs)
+
 
 _identity_list = ['__add_scalar__', '__add_symbol__', '__div_scalar__',
                   '__div_symbol__', '__mul_scalar__', '__mul_symbol__',
@@ -224,12 +249,15 @@ def _clip(inputs, attrs):
                   'relu', 'sigmoid', 'softmax', 'sum', 'tanh', 'transpose']
 
 _convert_map = {
+    '_copy'         : _rename('copy'),
     '_div_scalar'   : _rename('__div_scalar__'),
     '_minus_scalar' : _rename('__sub_scalar__'),
     '_mul_scalar'   : _rename('__mul_scalar__'),
     '_plus_scalar'  : _rename('__add_scalar__'),
     '_rdiv_scalar'  : _rename('__rdiv_scalar__'),
     '_rminus_scalar': _rename('__rsub_scalar__'),
+    '_contrib_MultiBoxPrior' : _rename('multibox_prior'),
+    '_contrib_MultiBoxDetection' : _contrib_multibox_detection,
     'Activation'    : _activations,
     'BatchNorm'     : _batch_norm,
     'BatchNorm_v1'  : _batch_norm,
@@ -248,7 +276,9 @@ def _clip(inputs, attrs):
     'SliceChannel'  : _split,
     'split'         : _split,
     'Softmax'       : _rename('softmax'),
+    'SoftmaxActivation' : _softmax_activation,
     'SoftmaxOutput' : _softmax_output,
+    'add_n'         : _elemwise_sum,
     'concat'        : _concat,
     'max_axis'      : _rename('max'),
     'min_axis'      : _rename('min'),
diff --git a/nnvm/python/nnvm/testing/download.py b/nnvm/python/nnvm/testing/download.py
new file mode 100644
index 000000000000..849c18bcf0f6
--- /dev/null
+++ b/nnvm/python/nnvm/testing/download.py
@@ -0,0 +1,69 @@
+# pylint: disable=invalid-name, no-member, import-error, no-name-in-module, global-variable-undefined, bare-except
+"""Helper utility for downloading"""
+from __future__ import print_function
+from __future__ import absolute_import as _abs
+
+import os
+import sys
+import time
+import urllib
+import requests
+
+if sys.version_info >= (3,):
+    import urllib.request as urllib2
+else:
+    import urllib2
+
+def _download_progress(count, block_size, total_size):
+    """Show the download progress.
+    """
+    global start_time
+    if count == 0:
+        start_time = time.time()
+        return
+    duration = time.time() - start_time
+    progress_size = int(count * block_size)
+    speed = int(progress_size / (1024 * duration))
+    percent = int(count * block_size * 100 / total_size)
+    sys.stdout.write("\r...%d%%, %d MB, %d KB/s, %d seconds passed" %
+                     (percent, progress_size / (1024 * 1024), speed, duration))
+    sys.stdout.flush()
+
+def download(url, path, overwrite=False, size_compare=False):
+    """Downloads the file from the internet.
+    Set the input options correctly to overwrite or do the size comparison
+
+    Parameters
+    ----------
+    url : str
+        Download url.
+
+    path : str
+        Local file path to save downloaded file
+
+    overwrite : bool, optional
+        Whether to overwrite existing file
+
+    size_compare : bool, optional
+        Whether to do size compare to check downloaded file.
+    """
+    if os.path.isfile(path) and not overwrite:
+        if size_compare:
+            file_size = os.path.getsize(path)
+            res_head = requests.head(url)
+            res_get = requests.get(url, stream=True)
+            if 'Content-Length' not in res_head.headers:
+                res_get = urllib2.urlopen(url)
+            url_file_size = int(res_get.headers['Content-Length'])
+            if url_file_size != file_size:
+                print("exist file got corrupted, downloading %s file freshly..." % path)
+                download(url, path, True, False)
+                return
+        print('File {} exists, skip.'.format(path))
+        return
+    print('Downloading from url {} to {}'.format(url, path))
+    try:
+        urllib.request.urlretrieve(url, path, reporthook=_download_progress)
+        print('')
+    except:
+        urllib.urlretrieve(url, path, reporthook=_download_progress)
diff --git a/nnvm/python/nnvm/testing/resnet.py b/nnvm/python/nnvm/testing/resnet.py
index 64eb63c29b7a..243cc1b65144 100644
--- a/nnvm/python/nnvm/testing/resnet.py
+++ b/nnvm/python/nnvm/testing/resnet.py
@@ -108,7 +108,7 @@ def resnet(units, num_stages, filter_list, num_classes, image_shape,
     num_unit = len(units)
     assert num_unit == num_stages
     data = sym.Variable(name='data')
-    data = sym.batch_norm(data=data, epsilon=2e-5, name='bn_data')
+    data = sym.batch_norm(data=data, epsilon=2e-5, scale=False, name='bn_data')
     (_, height, _) = image_shape
     if height <= 32:            # such as cifar10
         body = sym.conv2d(
diff --git a/nnvm/python/nnvm/top/attr_dict.py b/nnvm/python/nnvm/top/attr_dict.py
index a913a92552b2..efd439fa75fc 100644
--- a/nnvm/python/nnvm/top/attr_dict.py
+++ b/nnvm/python/nnvm/top/attr_dict.py
@@ -83,6 +83,21 @@ def get_int(self, key):
         """
         return int(self[key])
 
+    def get_float_tuple(self, key):
+        """Get tuple of float from attr dict
+
+        Parameters
+        ----------
+        key : str
+            The attr key
+
+        Returns
+        -------
+        tuple : tuple of float
+            The result tuple
+        """
+        return tuple(float(x) for x in self[key][1:-1].split(",") if x)
+
     def get_float(self, key):
         """Get float from attr dict
 
diff --git a/nnvm/python/nnvm/top/vision.py b/nnvm/python/nnvm/top/vision.py
index 89409de6263b..edbf72320a26 100644
--- a/nnvm/python/nnvm/top/vision.py
+++ b/nnvm/python/nnvm/top/vision.py
@@ -1,10 +1,9 @@
-
 # pylint: disable=invalid-name, unused-argument
 """Definition of nn ops"""
 from __future__ import absolute_import
 
-import topi
 import tvm
+import topi
 from . import registry as reg
 from .registry import OpPattern
 
@@ -38,3 +37,62 @@ def schedule_region(attrs, outs, target):
         return topi.generic.vision.schedule_region(outs)
 
 reg.register_pattern("yolo2_region", OpPattern.OPAQUE)
+
+# multibox_prior
+@reg.register_schedule("multibox_prior")
+def schedule_multibox_prior(_, outs, target):
+    """Schedule definition of multibox_prior"""
+    with tvm.target.create(target):
+        return topi.generic.schedule_multibox_prior(outs)
+
+@reg.register_compute("multibox_prior")
+def compute_multibox_prior(attrs, inputs, _):
+    """Compute definition of multibox_prior"""
+    sizes = attrs.get_float_tuple('sizes')
+    ratios = attrs.get_float_tuple('ratios')
+    steps = attrs.get_float_tuple('steps')
+    offsets = attrs.get_float_tuple('offsets')
+    clip = attrs.get_bool('clip')
+
+    return topi.vision.ssd.multibox_prior(inputs[0], sizes, ratios,
+                                          steps, offsets, clip)
+
+reg.register_pattern("multibox_prior", OpPattern.OPAQUE)
+
+# multibox_transform_loc
+@reg.register_schedule("multibox_transform_loc")
+def schedule_multibox_transform_loc(_, outs, target):
+    """Schedule definition of multibox_detection"""
+    with tvm.target.create(target):
+        return topi.generic.schedule_multibox_transform_loc(outs)
+
+@reg.register_compute("multibox_transform_loc")
+def compute_multibox_transform_loc(attrs, inputs, _):
+    """Compute definition of multibox_detection"""
+    clip = attrs.get_bool('clip')
+    threshold = attrs.get_float('threshold')
+    variance = attrs.get_float_tuple('variances')
+
+    return topi.vision.ssd.multibox_transform_loc(inputs[0], inputs[1], inputs[2],
+                                                  clip, threshold, variance)
+
+reg.register_pattern("multibox_detection", OpPattern.OPAQUE)
+
+# non-maximum suppression
+@reg.register_schedule("nms")
+def schedule_nms(_, outs, target):
+    """Schedule definition of nms"""
+    with tvm.target.create(target):
+        return topi.generic.schedule_nms(outs)
+
+@reg.register_compute("nms")
+def compute_nms(attrs, inputs, _):
+    """Compute definition of nms"""
+    nms_threshold = attrs.get_float('nms_threshold')
+    force_suppress = attrs.get_bool('force_suppress')
+    nms_topk = attrs.get_int('nms_topk')
+
+    return topi.vision.nms(inputs[0], inputs[1], nms_threshold,
+                           force_suppress, nms_topk)
+
+reg.register_pattern("nms", OpPattern.OPAQUE)
diff --git a/nnvm/src/top/vision/nms.cc b/nnvm/src/top/vision/nms.cc
new file mode 100644
index 000000000000..2680b894255b
--- /dev/null
+++ b/nnvm/src/top/vision/nms.cc
@@ -0,0 +1,80 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file nms.cc
+ * \brief Property def of SSD non-maximum suppression operator.
+ */
+
+#include <tvm/expr.h>
+#include <tvm/packed_func_ext.h>
+#include <nnvm/op.h>
+#include <nnvm/top/nn.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/compiler/op_attr_types.h>
+#include "../op_common.h"
+#include "../elemwise_op_common.h"
+
+namespace nnvm {
+namespace top {
+using compiler::FTVMCompute;
+using tvm::Tensor;
+using tvm::Array;
+
+DMLC_REGISTER_PARAMETER(NMSParam);
+
+bool NMSShape(const NodeAttrs& attrs,
+              std::vector<TShape> *in_attrs,
+              std::vector<TShape> *out_attrs) {
+  CHECK_EQ(in_attrs->size(), 2U) << "Inputs: [data, valid_count]";
+  TShape dshape = in_attrs->at(0);
+  TShape vshape = in_attrs->at(1);
+  CHECK_EQ(dshape.ndim(), 3U) << "Input data should be 3-D.";
+  CHECK_EQ(vshape.ndim(), 1U) << "Input valid count should be 1-D.";
+  CHECK_EQ(dshape[2], 6U) << "Data input should have shape "
+    "(batch_size, num_anchors, 6).";
+  CHECK_EQ(dshape[0], vshape[0]) << "batch_size mismatch.";
+  out_attrs->clear();
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, 0, dshape);
+  return true;
+}
+
+inline bool NMSInferType(const NodeAttrs &attrs,
+                         std::vector<int> *in_attrs,
+                         std::vector<int> *out_attrs) {
+  DTYPE_ASSIGN(out_attrs->at(0), in_attrs->at(0));
+  return true;
+}
+
+inline bool NMSInferLayout(const NodeAttrs& attrs,
+                           std::vector<Layout> *ilayouts,
+                           const std::vector<Layout> *last_ilayouts,
+                           std::vector<Layout> *olayouts) {
+  static const Layout kNCHW("NCHW");
+  CHECK_EQ(ilayouts->size(), 2U);
+  CHECK_EQ(olayouts->size(), 1U);
+  NNVM_ASSIGN_LAYOUT(*ilayouts, 0, kNCHW);
+  NNVM_ASSIGN_LAYOUT(*ilayouts, 1, kNCHW);
+  return true;
+}
+
+NNVM_REGISTER_OP(nms)
+  .describe(R"doc("Non-maximum suppression."
+)doc" NNVM_ADD_FILELINE)
+.set_num_inputs(2)
+.set_num_outputs(1)
+.set_attr_parser(ParamParser<NMSParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict",
+                        ParamGetAttrDict<NMSParam>)
+.add_arguments(NMSParam::__FIELDS__())
+.add_argument("data", "Tensor", "Input data.")
+.add_argument("valid_count", "Tensor", "Number of valid anchor boxes.")
+.set_attr<FListInputNames>("FListInputNames", [](const NodeAttrs& attrs) {
+  return std::vector<std::string>{"data", "valid_count"};
+})
+.set_attr<FInferShape>("FInferShape", NMSShape)
+.set_attr<FInferType>("FInferType", NMSInferType)
+.set_attr<FCorrectLayout>("FCorrectLayout", NMSInferLayout)
+.set_support_level(4);
+
+}  // namespace top
+}  // namespace nnvm
+
diff --git a/nnvm/src/top/vision/ssd/mutibox_op.cc b/nnvm/src/top/vision/ssd/mutibox_op.cc
new file mode 100644
index 000000000000..7f1aca5d2b82
--- /dev/null
+++ b/nnvm/src/top/vision/ssd/mutibox_op.cc
@@ -0,0 +1,158 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file multibox_op.cc
+ * \brief Property def of SSD multibox related operators.
+ */
+
+#include <tvm/expr.h>
+#include <tvm/packed_func_ext.h>
+#include <nnvm/op.h>
+#include <nnvm/top/nn.h>
+#include <nnvm/op_attr_types.h>
+#include <nnvm/compiler/op_attr_types.h>
+#include "../../op_common.h"
+#include "../../elemwise_op_common.h"
+
+namespace nnvm {
+namespace top {
+using compiler::FTVMCompute;
+using tvm::Tensor;
+using tvm::Array;
+
+DMLC_REGISTER_PARAMETER(MultiBoxPriorParam);
+
+bool MultiBoxPriorShape(const NodeAttrs& attrs,
+                        std::vector<TShape> *in_attrs,
+                        std::vector<TShape> *out_attrs) {
+  const MultiBoxPriorParam& param = nnvm::get<MultiBoxPriorParam>(attrs.parsed);
+  CHECK_EQ(in_attrs->size(), 1U) << "Inputs: [data]" << in_attrs->size();
+  TShape dshape = in_attrs->at(0);
+  CHECK_GE(dshape.ndim(), 4U) << "Input data should be 4D: "
+      "[batch, channel, height, width]";
+  int in_height = dshape[2];
+  CHECK_GT(in_height, 0) << "Input height should > 0";
+  int in_width = dshape[3];
+  CHECK_GT(in_width, 0) << "Input width should > 0";
+  // since input sizes are same in each batch, we could share MultiBoxPrior
+  TShape oshape = TShape(3);
+  int num_sizes = param.sizes.ndim();
+  int num_ratios = param.ratios.ndim();
+  oshape[0] = 1;
+  oshape[1] = in_height * in_width * (num_sizes + num_ratios - 1);
+  oshape[2] = 4;
+  CHECK_EQ(param.steps.ndim(), 2) << "Step ndim must be 2: (step_y, step_x)";
+  CHECK_GE(param.steps[0] * param.steps[1], 0) << "Must specify both "
+      "step_y and step_x";
+  out_attrs->clear();
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, 0, oshape);
+  return true;
+}
+
+inline bool MultiBoxPriorLayout(const NodeAttrs& attrs,
+                                std::vector<Layout> *ilayouts,
+                                const std::vector<Layout> *last_ilayouts,
+                                std::vector<Layout> *olayouts) {
+  static const Layout kNCHW("NCHW");
+  CHECK_EQ(ilayouts->size(), 1U);
+  CHECK_EQ(olayouts->size(), 1U);
+  NNVM_ASSIGN_LAYOUT(*ilayouts, 0, kNCHW);
+  return true;
+}
+
+NNVM_REGISTER_OP(multibox_prior)
+  .describe(R"doc("Generate prior(anchor) boxes from data, sizes and ratios."
+)doc" NNVM_ADD_FILELINE)
+.set_num_inputs(1)
+.set_num_outputs(1)
+.set_attr_parser(ParamParser<MultiBoxPriorParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<MultiBoxPriorParam>)
+.add_arguments(MultiBoxPriorParam::__FIELDS__())
+.add_argument("data", "Tensor", "Input data")
+.set_attr<FInferShape>("FInferShape", MultiBoxPriorShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FCorrectLayout>("FCorrectLayout", MultiBoxPriorLayout)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds) {
+    return std::vector<NodeEntry>{
+      MakeNode("zeros_like", n->attrs.name + "_zero_grad",
+      {n->inputs[0]}),
+      ograds[0]
+    };
+})
+.set_support_level(4);
+
+DMLC_REGISTER_PARAMETER(MultiBoxTransformLocParam);
+
+bool MultiBoxTransformLocShape(const NodeAttrs& attrs,
+                               std::vector<TShape> *in_attrs,
+                               std::vector<TShape> *out_attrs) {
+  CHECK_EQ(in_attrs->size(), 3U) << "Inputs: [cls_prob, loc_pred, anchor]";
+  TShape cshape = in_attrs->at(0);
+  TShape lshape = in_attrs->at(1);
+  TShape ashape = in_attrs->at(2);
+  CHECK_EQ(cshape.ndim(), 3U) << "Class probability should be 3-D.";
+  CHECK_EQ(lshape.ndim(), 2U) << "Location prediction should be 2-D.";
+  CHECK_EQ(ashape.ndim(), 3U) << "Anchor should be 3-D.";
+  CHECK_EQ(cshape[2], ashape[1]) << "Number of anchors mismatch.";
+  CHECK_EQ(cshape[2] * 4, lshape[1]) << "# anchors mismatch with # loc.";
+  CHECK_GT(ashape[1], 0U) << "Number of anchors must > 0.";
+  CHECK_EQ(ashape[2], 4U);
+  TShape oshape0 = TShape(3);
+  oshape0[0] = cshape[0];
+  oshape0[1] = ashape[1];
+  oshape0[2] = 6;  // [id, prob, xmin, ymin, xmax, ymax]
+  TShape oshape1 = TShape(1);
+  oshape1[0] = cshape[0];
+  out_attrs->clear();
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, 0, oshape0);
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, 1, oshape1);
+  return true;
+}
+
+inline bool MultiBoxTransformLocLayout(const NodeAttrs& attrs,
+                                       std::vector<Layout> *ilayouts,
+                                       const std::vector<Layout> *last_ilayouts,
+                                       std::vector<Layout> *olayouts) {
+  CHECK_EQ(ilayouts->size(), 3U);
+  CHECK_EQ(last_ilayouts->size(), 3U);
+  CHECK_EQ(olayouts->size(), 2U);
+  for (size_t i = 0; i < last_ilayouts->size(); ++i) {
+    const Layout& last_layout = last_ilayouts->at(i);
+    if (last_layout.defined()) {
+      NNVM_ASSIGN_LAYOUT(*ilayouts, i, last_layout);
+    }
+  }
+  return true;
+}
+
+inline bool MultiBoxTransformLocInferType(const NodeAttrs &attrs,
+                                          std::vector<int> *in_attrs,
+                                          std::vector<int> *out_attrs) {
+  DTYPE_ASSIGN(out_attrs->at(0), in_attrs->at(0));
+  DTYPE_ASSIGN(out_attrs->at(1), 4U);
+  return true;
+}
+
+NNVM_REGISTER_OP(multibox_transform_loc)
+  .describe(R"doc("Location transformation for multibox detection."
+)doc" NNVM_ADD_FILELINE)
+.set_num_inputs(3)
+.set_num_outputs(2)
+.set_attr_parser(ParamParser<MultiBoxTransformLocParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict",
+                        ParamGetAttrDict<MultiBoxTransformLocParam>)
+.add_arguments(MultiBoxTransformLocParam::__FIELDS__())
+.add_argument("cls_prob", "Tensor", "Class probabilities.")
+.add_argument("loc_pred", "Tensor", "Location regression predictions.")
+.add_argument("anchor", "Tensor", "Multibox prior anchor boxes")
+.set_attr<FListInputNames>("FListInputNames", [](const NodeAttrs& attrs) {
+    return std::vector<std::string>{"cls_prob", "loc_pred", "anchor"};
+})
+.set_attr<FInferShape>("FInferShape", MultiBoxTransformLocShape)
+.set_attr<FInferType>("FInferType", MultiBoxTransformLocInferType)
+.set_attr<FCorrectLayout>("FCorrectLayout", MultiBoxTransformLocLayout)
+.set_support_level(4);
+
+}  // namespace top
+}  // namespace nnvm
diff --git a/nnvm/tests/python/compiler/test_top_level4.py b/nnvm/tests/python/compiler/test_top_level4.py
index 819768cfb341..b202d1aad862 100644
--- a/nnvm/tests/python/compiler/test_top_level4.py
+++ b/nnvm/tests/python/compiler/test_top_level4.py
@@ -1,3 +1,4 @@
+import math
 import numpy as np
 import tvm
 from tvm.contrib import graph_runtime
@@ -356,6 +357,118 @@ def test_full():
             np.full(shape, fill_value=0, dtype=dtype),
             atol=1e-5, rtol=1e-5)
 
+def verify_multibox_prior(dshape, sizes=(1,), ratios=(1,), steps=(-1, -1),
+                          offsets=(0.5, 0.5), clip=False):
+    data = sym.Variable("data")
+    out = sym.multibox_prior(data=data, sizes=sizes, ratios=ratios, steps=steps,
+                             offsets=offsets, clip=clip)
+
+    in_height = dshape[2]
+    in_width = dshape[3]
+    num_sizes = len(sizes)
+    num_ratios = len(ratios)
+    size_ratio_concat = sizes + ratios
+    steps_h = steps[0] if steps[0] > 0 else 1.0 / in_height
+    steps_w = steps[1] if steps[1] > 0 else 1.0 / in_width
+    offset_h = offsets[0]
+    offset_w = offsets[1]
+
+    oshape = (1, in_height * in_width * (num_sizes + num_ratios - 1), 4)
+    dtype = "float32"
+    np_out = np.zeros(oshape).astype(dtype)
+
+    for i in range(in_height):
+        center_h = (i + offset_h) * steps_h
+        for j in range(in_width):
+            center_w = (j + offset_w) * steps_w
+            for k in range(num_sizes + num_ratios - 1):
+                w = size_ratio_concat[k] * in_height / in_width / 2.0 if k < num_sizes else \
+                    size_ratio_concat[0] * in_height / in_width * math.sqrt(size_ratio_concat[k + 1]) / 2.0
+                h = size_ratio_concat[k] / 2.0 if k < num_sizes else \
+                    size_ratio_concat[0] / math.sqrt(size_ratio_concat[k + 1]) / 2.0
+                count = i * in_width * (num_sizes + num_ratios - 1) + j * (num_sizes + num_ratios - 1) + k
+                np_out[0][count][0] = center_w - w
+                np_out[0][count][1] = center_h - h
+                np_out[0][count][2] = center_w + w
+                np_out[0][count][3] = center_h + h
+    if clip:
+        np_out = np.clip(np_out, 0, 1)
+
+    target = "llvm"
+    ctx = tvm.cpu()
+    graph, lib, _ = nnvm.compiler.build(out, target, {"data": dshape})
+    m = graph_runtime.create(graph, lib, ctx)
+    m.set_input("data", np.random.uniform(size=dshape).astype(dtype))
+    m.run()
+    out = m.get_output(0, tvm.nd.empty(np_out.shape, dtype))
+    np.testing.assert_allclose(out.asnumpy(), np_out, atol=1e-5, rtol=1e-5)
+
+def test_multibox_prior():
+    verify_multibox_prior((1, 3, 50, 50))
+    verify_multibox_prior((1, 3, 224, 224), sizes=(0.5, 0.25, 0.1), ratios=(1, 2, 0.5))
+    verify_multibox_prior((1, 32, 32, 32), sizes=(0.5, 0.25), ratios=(1, 2), steps=(2, 2), clip=True)
+
+def test_multibox_transform_loc():
+    batch_size = 1
+    num_anchors = 3
+    num_classes = 3
+    cls_prob = sym.Variable("cls_prob")
+    loc_preds = sym.Variable("loc_preds")
+    anchors = sym.Variable("anchors")
+    transform_loc_data, valid_count = sym.multibox_transform_loc(cls_prob=cls_prob, loc_pred=loc_preds,
+                                                                 anchor=anchors)
+    out = sym.nms(data=transform_loc_data, valid_count=valid_count)
+
+    # Manually create test case
+    np_cls_prob = np.array([[[0.2, 0.5, 0.3], [0.25, 0.3, 0.45], [0.7, 0.1, 0.2]]])
+    np_loc_preds = np.array([[0.1, -0.2, 0.3, 0.2, 0.2, 0.4, 0.5, -0.3, 0.7, -0.2, -0.4, -0.8]])
+    np_anchors = np.array([[[-0.1, -0.1, 0.1, 0.1], [-0.2, -0.2, 0.2, 0.2], [1.2, 1.2, 1.5, 1.5]]])
+
+    expected_np_out = np.array([[[1, 0.69999999, 0, 0, 0.10818365, 0.10008108],
+                                 [0, 0.44999999, 1, 1, 1, 1],
+                                 [0, 0.30000001, 0, 0, 0.22903419, 0.20435292]]])
+
+    target = "llvm"
+    dtype = "float32"
+    ctx = tvm.cpu()
+    graph, lib, _ = nnvm.compiler.build(out, target, {"cls_prob": (batch_size, num_anchors, num_classes),
+                                                      "loc_preds": (batch_size, num_anchors * 4),
+                                                      "anchors": (1, num_anchors, 4)})
+    m = graph_runtime.create(graph, lib, ctx)
+    m.set_input(**{"cls_prob": np_cls_prob.astype(dtype), "loc_preds": np_loc_preds.astype(dtype), "anchors": np_anchors.astype(dtype)})
+    m.run()
+    out = m.get_output(0, tvm.nd.empty(expected_np_out.shape, dtype))
+    np.testing.assert_allclose(out.asnumpy(), expected_np_out, atol=1e-5, rtol=1e-5)
+
+def test_nms():
+    dshape = (1, 5, 6)
+    data = sym.Variable("data")
+    valid_count = sym.Variable("valid_count", dtype="int32")
+    nms_threshold = 0.7
+    force_suppress = True
+    nms_topk = 2
+    out = sym.nms(data=data, valid_count=valid_count, nms_threshold=nms_threshold,
+                  force_suppress=force_suppress, nms_topk=nms_topk)
+
+    np_data = np.array([[[0, 0.8, 1, 20, 25, 45], [1, 0.7, 30, 60, 50, 80],
+                         [0, 0.4, 4, 21, 19, 40], [2, 0.9, 35, 61, 52, 79],
+                         [1, 0.5, 100, 60, 70, 110]]]).astype("float32")
+    np_valid_count = np.array([4]).astype("int32")
+    np_result = np.array([[[2, 0.9, 35, 61, 52, 79], [0, 0.8, 1, 20, 25, 45],
+                           [0, 0.4, 4, 21, 19, 40], [-1, 0.9, 35, 61, 52, 79],
+                           [-1, -1, -1, -1, -1, -1]]])
+
+    target = "llvm"
+    ctx = tvm.cpu()
+    graph, lib, _ = nnvm.compiler.build(out, target, {"data": dshape, "valid_count": (dshape[0],)},
+                                        dtype={"data": "float32", "valid_count": "int32"})
+    m = graph_runtime.create(graph, lib, ctx)
+    m.set_input(**{"data": np_data, "valid_count": np_valid_count})
+    m.run()
+    out = m.get_output(0, tvm.nd.empty(np_result.shape, "float32"))
+    np.testing.assert_allclose(out.asnumpy(), np_result, atol=1e-5, rtol=1e-5)
+
+
 
 if __name__ == "__main__":
     test_reshape()
@@ -370,4 +483,7 @@ def test_full():
     test_block_grad()
     test_full()
     test_flip()
+    test_multibox_prior()
+    test_multibox_transform_loc()
+    test_nms()
     print(nnvm.compiler.engine.dump())
diff --git a/topi/python/topi/vision/ssd/multibox.py b/topi/python/topi/vision/ssd/multibox.py
index 31a090e345d5..a8f97146519b 100644
--- a/topi/python/topi/vision/ssd/multibox.py
+++ b/topi/python/topi/vision/ssd/multibox.py
@@ -27,7 +27,7 @@ def multibox_prior_ir(data, out, sizes, ratios, steps, offsets):
     ratios : tuple of float
         Tuple of ratios for anchor boxes.
 
-    steps : Tuple of int
+    steps : Tuple of float
         Priorbox step across y and x, -1 for auto calculation.
 
     offsets : tuple of int
@@ -86,7 +86,7 @@ def multibox_prior(data, sizes=(1,), ratios=(1,), steps=(-1, -1), offsets=(0.5,
     ratios : tuple of float
         Tuple of ratios for anchor boxes.
 
-    steps : Tuple of int
+    steps : Tuple of float
         Priorbox step across y and x, -1 for auto calculation.
 
     offsets : tuple of int
@@ -211,8 +211,8 @@ def transform_loc(loc, loc_base_idx, anchor, anchor_base_idx, clip, vx, vy, vw,
 
 
 @tvm.target.generic_func
-def mutibox_transform_loc(cls_prob, loc_pred, anchor, clip=True, threshold=0.01,
-                          variances=(0.1, 0.1, 0.2, 0.2)):
+def multibox_transform_loc(cls_prob, loc_pred, anchor, clip=True, threshold=0.01,
+                           variances=(0.1, 0.1, 0.2, 0.2)):
     """Location transformation for multibox detection
 
     Parameters
@@ -237,11 +237,7 @@ def mutibox_transform_loc(cls_prob, loc_pred, anchor, clip=True, threshold=0.01,
 
     Returns
     -------
-    out : tvm.Tensor
-        3-D tensor with shape (batch_size, num_anchors, 6)
-
-    valid_count : tvm.Tensor
-        1-D tensor with shape (batch_size,), number of valid anchor boxes.
+    ret : tuple of tvm.Tensor
     """
     batch_size = cls_prob.shape[0]
     num_anchors = anchor.shape[1]
@@ -259,7 +255,7 @@ def mutibox_transform_loc(cls_prob, loc_pred, anchor, clip=True, threshold=0.01,
                    dtype=[valid_count_dtype, cls_prob.dtype],
                    out_buffers=[valid_count_buf, out_buf],
                    tag="multibox_transform_loc")
-    return out, valid_count
+    return [out, valid_count]
 
 
 @tvm.target.generic_func
@@ -301,7 +297,7 @@ def multibox_detection(cls_prob, loc_pred, anchor, clip=True, threshold=0.01, nm
     out : tvm.Tensor
         3-D tensor with shape (batch_size, num_anchors, 6)
     """
-    inter_out, valid_count = mutibox_transform_loc(cls_prob, loc_pred, anchor,
-                                                   clip, threshold, variances)
-    out = nms(inter_out, valid_count, nms_threshold, force_suppress, nms_topk)
+    inter_out = multibox_transform_loc(cls_prob, loc_pred, anchor,
+                                       clip, threshold, variances)
+    out = nms(inter_out[0], inter_out[1], nms_threshold, force_suppress, nms_topk)
     return out
diff --git a/tutorials/nnvm/deploy_ssd.py b/tutorials/nnvm/deploy_ssd.py
new file mode 100644
index 000000000000..b3b5072f28c9
--- /dev/null
+++ b/tutorials/nnvm/deploy_ssd.py
@@ -0,0 +1,137 @@
+"""
+Deploy Single Shot Multibox Detector(SSD) model
+===============================================
+**Author**: `Yao Wang <https://github.com/kevinthesun>`_
+
+This article is an introductory tutorial to deploy SSD models with TVM.
+We will use mxnet pretrained SSD model with Resnet50 as body network and
+convert it to NNVM graph.
+"""
+import os
+import zipfile
+import tvm
+import mxnet as mx
+import cv2
+import numpy as np
+
+from nnvm import compiler
+from nnvm.frontend import from_mxnet
+from nnvm.testing.download import download
+from tvm.contrib import graph_runtime
+from mxnet.model import load_checkpoint
+
+
+######################################################################
+# Set the parameters here
+# -----------------------
+# .. note::
+#
+#   Currently we support compiling SSD on CPU only.
+#   GPU support is in progress.
+
+model_name = "ssd_resnet50_512"
+model_file = "%s.zip" % model_name
+test_image = "dog.jpg"
+dshape = (1, 3, 512, 512)
+dtype = "float32"
+target = "llvm"
+ctx = tvm.cpu()
+
+######################################################################
+# Download MXNet SSD pre-trained model and demo image
+# ---------------------------------------------------
+# Pre-trained model available at
+# https://github.com/apache/incubator-\mxnet/tree/master/example/ssd
+
+model_url = "https://github.com/zhreshold/mxnet-ssd/releases/download/v0.6/" \
+            "resnet50_ssd_512_voc0712_trainval.zip"
+image_url = "https://cloud.githubusercontent.com/assets/3307514/20012567/" \
+            "cbb60336-a27d-11e6-93ff-cbc3f09f5c9e.jpg"
+inference_symbol_folder = "c1904e900848df4548ce5dfb18c719c7-a28c4856c827fe766aa3da0e35bad41d44f0fb26"
+inference_symbol_url = "https://gist.github.com/kevinthesun/c1904e900848df4548ce5dfb18c719c7/" \
+                       "archive/a28c4856c827fe766aa3da0e35bad41d44f0fb26.zip"
+            
+dir = "ssd_model"
+if not os.path.exists(dir):
+    os.makedirs(dir)
+model_file_path = "%s/%s" % (dir, model_file)
+test_image_path = "%s/%s" % (dir, test_image)
+inference_symbol_path = "%s/inference_model.zip" % dir
+download(model_url, model_file_path)
+download(image_url, test_image_path)
+download(inference_symbol_url, inference_symbol_path)
+
+zip_ref = zipfile.ZipFile(model_file_path, 'r')
+zip_ref.extractall(dir)
+zip_ref.close()
+zip_ref = zipfile.ZipFile(inference_symbol_path)
+zip_ref.extractall(dir)
+zip_ref.close()
+
+######################################################################
+# Convert and compile model with NNVM for CPU.
+
+sym = mx.sym.load("%s/%s/ssd_resnet50_inference.json" % (dir, inference_symbol_folder))
+_, arg_params, aux_params = load_checkpoint("%s/%s" % (dir, model_name), 0)
+net, params = from_mxnet(sym, arg_params, aux_params)
+with compiler.build_config(opt_level=3):
+    graph, lib, params = compiler.build(net, target, {"data": dshape}, params=params)
+
+######################################################################
+# Create TVM runtime and do inference
+
+# Preprocess image
+image = cv2.imread(test_image_path)
+img_data = cv2.resize(image, (dshape[2], dshape[3]))
+img_data = img_data[:, :, (2, 1, 0)].astype(np.float32)
+img_data -= np.array([123, 117, 104])
+img_data = np.transpose(np.array(img_data), (2, 0, 1))
+img_data = np.expand_dims(img_data, axis=0)
+# Build TVM runtime
+m = graph_runtime.create(graph, lib, ctx)
+m.set_input('data', tvm.nd.array(img_data.astype(dtype)))
+m.set_input(**params)
+# execute
+m.run()
+# get outputs
+_, oshape = compiler.graph_util.infer_shape(graph, shape={"data": dshape})
+tvm_output = m.get_output(0, tvm.nd.empty(tuple(oshape[0]), dtype))
+
+
+######################################################################
+# Display result
+
+class_names = ["aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair",
+               "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant",
+               "sheep", "sofa", "train", "tvmonitor"]
+def display(img, out, thresh=0.5):
+    import random
+    import matplotlib as mpl
+    import matplotlib.pyplot as plt
+    mpl.rcParams['figure.figsize'] = (10,10)
+    pens = dict()
+    plt.clf()
+    plt.imshow(img)
+    for det in out:
+        cid = int(det[0])
+        if cid < 0:
+            continue
+        score = det[1]
+        if score < thresh:
+            continue
+        if cid not in pens:
+            pens[cid] = (random.random(), random.random(), random.random())
+        scales = [img.shape[1], img.shape[0]] * 2
+        xmin, ymin, xmax, ymax = [int(p * s) for p, s in zip(det[2:6].tolist(), scales)]
+        rect = plt.Rectangle((xmin, ymin), xmax - xmin, ymax - ymin, fill=False,
+                             edgecolor=pens[cid], linewidth=3)
+        plt.gca().add_patch(rect)
+        text = class_names[cid]
+        plt.gca().text(xmin, ymin-2, '{:s} {:.3f}'.format(text, score),
+                       bbox=dict(facecolor=pens[cid], alpha=0.5),
+                       fontsize=12, color='white')
+    plt.show()
+
+image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
+display(image, tvm_output.asnumpy()[0], thresh=0.45)
+
diff --git a/tutorials/nnvm/from_darknet.py b/tutorials/nnvm/from_darknet.py
index 9613f023c1e9..2cd681b624ad 100644
--- a/tutorials/nnvm/from_darknet.py
+++ b/tutorials/nnvm/from_darknet.py
@@ -14,23 +14,18 @@
   pip install cffi
   pip install opencv-python
 """
-from ctypes import *
-import math
-import random
+
 import nnvm
 import nnvm.frontend.darknet
 import nnvm.testing.darknet
-from nnvm.testing.darknet import __darknetffi__
 import matplotlib.pyplot as plt
 import numpy as np
 import tvm
-import os, sys, time, urllib, requests
-if sys.version_info >= (3,):
-    import urllib.request as urllib2
-    import urllib.parse as urlparse
-else:
-    import urllib2
-    import urlparse
+import os
+
+from ctypes import *
+from nnvm.testing.download import download
+from nnvm.testing.darknet import __darknetffi__
 
 ######################################################################
 # Set the parameters here.
@@ -41,62 +36,6 @@
 target = 'llvm'
 ctx = tvm.cpu(0)
 
-def dlProgress(count, block_size, total_size):
-    """Show the download progress."""
-    global start_time
-    if count == 0:
-        start_time = time.time()
-        return
-    duration = time.time() - start_time
-    progress_size = int(count * block_size)
-    speed = int(progress_size / (1024 * duration))
-    percent = int(count * block_size * 100 / total_size)
-    sys.stdout.write("\r...%d%%, %d MB, %d KB/s, %d seconds passed" %
-          (percent, progress_size / (1024 * 1024), speed, duration))
-    sys.stdout.flush()
-
-def download(url, path, overwrite=False, sizecompare=False):
-    """Downloads the file from the internet.
-    Set the input options correctly to overwrite or do the size comparison
-
-    Parameters
-    ----------
-    url : str
-        Operator name, such as Convolution, Connected, etc
-    path : str
-        List of input symbols.
-    overwrite : dict
-        Dict of operator attributes
-    sizecompare : dict
-        Dict of operator attributes
-
-    Returns
-    -------
-    out_name : converted out name of operation
-    sym : nnvm.Symbol
-        Converted nnvm Symbol
-    """
-    if os.path.isfile(path) and not overwrite:
-        if (sizecompare):
-            fileSize = os.path.getsize(path)
-            resHead = requests.head(url)
-            resGet = requests.get(url,stream=True)
-            if 'Content-Length' not in resHead.headers :
-                resGet = urllib2.urlopen(url)
-            urlFileSize = int(resGet.headers['Content-Length'])
-            if urlFileSize != fileSize:
-                print ("exist file got corrupted, downloading", path , " file freshly")
-                download(url, path, True, False)
-                return
-        print('File {} exists, skip.'.format(path))
-        return
-    print('Downloading from url {} to {}'.format(url, path))
-    try:
-        urllib.request.urlretrieve(url, path, reporthook=dlProgress)
-        print('')
-    except:
-        urllib.urlretrieve(url, path, reporthook=dlProgress)
-
 ######################################################################
 # Prepare cfg and weights file
 # ----------------------------

From 51836ba7456ea8cffd54d44104392ad19b7d97e6 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 15 Jun 2018 10:38:21 -0700
Subject: [PATCH 729/949] [MAINTAINER] Add zhreshold as reviewer (#1287)

---
 CONTRIBUTORS.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index db4e5f249fb3..26de78f4ac25 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -23,6 +23,7 @@ See the [community structure document](http://docs.tvm.ai/contribute/community.h
 - [Masahiro Masuda](https://github.com/masahi)
 - [Pariksheet Pinjari](https://github.com/PariksheetPinjari909)
 - [Eddie Yan](https://github.com/eqy)
+- [Joshua Z. Zhang](https://github.com/zhreshold)
 - [Lianmin Zheng](https://github.com/merrymercy)
 
 ## List of Contributors

From 0a6c056f018515d4d91f8be83ba8cf22fe170cd0 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 15 Jun 2018 20:08:54 -0700
Subject: [PATCH 730/949] [CONTAINER] Introduce StrMap (#1292)

---
 CMakeLists.txt                               |   2 +-
 HalideIR                                     |   2 +-
 include/tvm/packed_func_ext.h                |  19 ++++
 python/tvm/_ffi/_ctypes/node.py              |   1 +
 python/tvm/_ffi/node_generic.py              |   6 +-
 python/tvm/container.py                      |  17 ++-
 src/api/api_lang.cc                          | 111 +++++++++++++------
 src/lang/expr.cc                             |   1 +
 tests/cpp/container_test.cc                  |   9 ++
 tests/python/unittest/test_lang_container.py |  13 +++
 10 files changed, 142 insertions(+), 39 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 972e2cbe712d..e9699b1f6736 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -196,7 +196,7 @@ if(GTEST_LIB)
     add_executable(${__execname} ${__srcpath})
     list(APPEND TEST_EXECS ${__execname})
     target_link_libraries(${__execname}
-      tvm ${GTEST_LIB} ${TVM_LINKER_LIBS} ${TVM_RUNTIME_LINKER_LIBS} pthread)
+      tvm ${GTEST_LIB} pthread)
     set_target_properties(${__execname} PROPERTIES EXCLUDE_FROM_ALL 1)
     set_target_properties(${__execname} PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD 1)
   endforeach()
diff --git a/HalideIR b/HalideIR
index a3698398faff..0b7e25275138 160000
--- a/HalideIR
+++ b/HalideIR
@@ -1 +1 @@
-Subproject commit a3698398faff7fec1c0fa4e4479357651382db75
+Subproject commit 0b7e25275138768bb05edb9b9db2c86d0fb09c9a
diff --git a/include/tvm/packed_func_ext.h b/include/tvm/packed_func_ext.h
index 455717ce753c..95964547ef8e 100644
--- a/include/tvm/packed_func_ext.h
+++ b/include/tvm/packed_func_ext.h
@@ -60,6 +60,25 @@ struct NodeTypeChecker<Array<T> > {
   }
 };
 
+template<typename V>
+struct NodeTypeChecker<Map<std::string, V> > {
+  static inline bool Check(Node* sptr) {
+    if (sptr == nullptr) return false;
+    if (!sptr->is_type<StrMapNode>()) return false;
+    StrMapNode* n = static_cast<StrMapNode*>(sptr);
+    for (const auto& kv : n->data) {
+      if (!NodeTypeChecker<V>::Check(kv.second.get())) return false;
+    }
+    return true;
+  }
+  static inline void PrintName(std::ostringstream& os) { // NOLINT(*)
+    os << "map<string";
+    os << ',';
+    NodeTypeChecker<V>::PrintName(os);
+    os << '>';
+  }
+};
+
 template<typename K, typename V>
 struct NodeTypeChecker<Map<K, V> > {
   static inline bool Check(Node* sptr) {
diff --git a/python/tvm/_ffi/_ctypes/node.py b/python/tvm/_ffi/_ctypes/node.py
index cb32b83291d1..01244519532b 100644
--- a/python/tvm/_ffi/_ctypes/node.py
+++ b/python/tvm/_ffi/_ctypes/node.py
@@ -30,6 +30,7 @@ def _return_node(x):
 C_TO_PY_ARG_SWITCH[TypeCode.NODE_HANDLE] = _wrap_arg_func(
     _return_node, TypeCode.NODE_HANDLE)
 
+
 class NodeBase(object):
     __slots__ = ["handle"]
     # pylint: disable=no-member
diff --git a/python/tvm/_ffi/node_generic.py b/python/tvm/_ffi/node_generic.py
index 7561097bf305..b7230f29da59 100644
--- a/python/tvm/_ffi/node_generic.py
+++ b/python/tvm/_ffi/node_generic.py
@@ -13,12 +13,14 @@ def _set_class_node_base(cls):
     global _CLASS_NODE_BASE
     _CLASS_NODE_BASE = cls
 
+
 class NodeGeneric(object):
     """Base class for all classes that can be converted to node."""
     def asnode(self):
         """Convert value to node"""
         raise NotImplementedError()
 
+
 def convert_to_node(value):
     """Convert a python value to corresponding node type.
 
@@ -46,7 +48,8 @@ def convert_to_node(value):
     elif isinstance(value, dict):
         vlist = []
         for item in value.items():
-            if not isinstance(item[0], _CLASS_NODE_BASE):
+            if (not isinstance(item[0], _CLASS_NODE_BASE) and
+                    not isinstance(item[0], string_types)):
                 raise ValueError("key of map must already been a container type")
             vlist.append(item[0])
             vlist.append(convert_to_node(item[1]))
@@ -56,6 +59,7 @@ def convert_to_node(value):
     else:
         raise ValueError("don't know how to convert type %s to node" % type(value))
 
+
 def const(value, dtype=None):
     """Construct a constant value for a given type.
 
diff --git a/python/tvm/container.py b/python/tvm/container.py
index d1d4546fd86a..27e533113926 100644
--- a/python/tvm/container.py
+++ b/python/tvm/container.py
@@ -32,9 +32,8 @@ class Map(NodeBase):
     """Map container of TVM.
 
     You do not need to create Map explicitly.
-    Normally python dict will be converted automatically
-    to Array during tvm function call.
-    You may get Map in return values of TVM function call.
+    Normally python dict will be converted automaticall to Map during tvm function call.
+    You can use convert to create a dict[NodeBase-> NodeBase] into a Map
     """
     def __getitem__(self, k):
         return _api_internal._MapGetItem(self, k)
@@ -51,6 +50,18 @@ def __len__(self):
         return _api_internal._MapSize(self)
 
 
+@register_node
+class StrMap(Map):
+    """A special map container that has str as key.
+
+    You can use convert to create a dict[str->NodeBase] into a Map.
+    """
+    def items(self):
+        """Get the items from the map"""
+        akvs = _api_internal._MapItems(self)
+        return [(akvs[i].value, akvs[i+1]) for i in range(0, len(akvs), 2)]
+
+
 @register_node
 class Range(NodeBase):
     """Represent range in TVM.
diff --git a/src/api/api_lang.cc b/src/api/api_lang.cc
index ba158a7c3f79..00f8ba5f3847 100644
--- a/src/api/api_lang.cc
+++ b/src/api/api_lang.cc
@@ -76,63 +76,108 @@ TVM_REGISTER_API("_ArraySize")
 TVM_REGISTER_API("_Map")
 .set_body([](TVMArgs args,  TVMRetValue* ret) {
     CHECK_EQ(args.size() % 2, 0);
-    MapNode::ContainerType data;
-    for (int i = 0; i < args.num_args; i += 2) {
-      CHECK(args[i].type_code() == kNodeHandle)
-          << "need content of array to be NodeBase";
-      CHECK(args[i + 1].type_code() == kNodeHandle)
-          << "need content of array to be NodeBase";
-      data.emplace(std::make_pair(args[i].node_sptr(),
-                                  args[i + 1].node_sptr()));
+    if (args.size() != 0 && args[0].type_code() == kStr) {
+      // StrMap
+      StrMapNode::ContainerType data;
+      for (int i = 0; i < args.num_args; i += 2) {
+        CHECK(args[i].type_code() == kStr)
+            << "key of str map need to be str";
+        CHECK(args[i + 1].type_code() == kNodeHandle)
+            << "value of the map to be NodeRef";
+        data.emplace(std::make_pair(args[i].operator std::string(),
+                                    args[i + 1].node_sptr()));
+      }
+      auto node = std::make_shared<StrMapNode>();
+      node->data = std::move(data);
+      *ret = node;
+    } else {
+      // Container node.
+      MapNode::ContainerType data;
+      for (int i = 0; i < args.num_args; i += 2) {
+        CHECK(args[i].type_code() == kNodeHandle)
+            << "key of str map need to be str";
+        CHECK(args[i + 1].type_code() == kNodeHandle)
+            << "value of map to be NodeRef";
+        data.emplace(std::make_pair(args[i].node_sptr(),
+                                    args[i + 1].node_sptr()));
+      }
+      auto node = std::make_shared<MapNode>();
+      node->data = std::move(data);
+      *ret = node;
     }
-    auto node = std::make_shared<MapNode>();
-    node->data = std::move(data);
-    *ret = node;
   });
 
 TVM_REGISTER_API("_MapSize")
 .set_body([](TVMArgs args,  TVMRetValue* ret) {
     auto& sptr = args[0].node_sptr();
-    CHECK(sptr->is_type<MapNode>());
-    auto* n = static_cast<const MapNode*>(sptr.get());
-    *ret = static_cast<int64_t>(n->data.size());
+    if (sptr->is_type<MapNode>()) {
+      auto* n = static_cast<const MapNode*>(sptr.get());
+      *ret = static_cast<int64_t>(n->data.size());
+    } else {
+      CHECK(sptr->is_type<StrMapNode>());
+      auto* n = static_cast<const StrMapNode*>(sptr.get());
+      *ret = static_cast<int64_t>(n->data.size());
+    }
   });
 
 TVM_REGISTER_API("_MapGetItem")
 .set_body([](TVMArgs args,  TVMRetValue* ret) {
     CHECK(args[0].type_code() == kNodeHandle);
-    CHECK(args[1].type_code() == kNodeHandle);
     auto& sptr = args[0].node_sptr();
-    CHECK(sptr->is_type<MapNode>());
-    auto* n = static_cast<const MapNode*>(sptr.get());
-    auto it = n->data.find(args[1].node_sptr());
-    CHECK(it != n->data.end())
-        << "cannot find the corresponding key in the Map";
-    *ret = (*it).second;
+    if (sptr->is_type<MapNode>()) {
+      CHECK(args[1].type_code() == kNodeHandle);
+      auto* n = static_cast<const MapNode*>(sptr.get());
+      auto it = n->data.find(args[1].node_sptr());
+      CHECK(it != n->data.end())
+          << "cannot find the corresponding key in the Map";
+      *ret = (*it).second;
+    } else {
+      CHECK(sptr->is_type<StrMapNode>());
+      auto* n = static_cast<const StrMapNode*>(sptr.get());
+      auto it = n->data.find(args[1].operator std::string());
+      CHECK(it != n->data.end())
+          << "cannot find the corresponding key in the Map";
+      *ret = (*it).second;
+    }
   });
 
 TVM_REGISTER_API("_MapCount")
 .set_body([](TVMArgs args,  TVMRetValue* ret) {
     CHECK(args[0].type_code() == kNodeHandle);
-    CHECK(args[1].type_code() == kNodeHandle);
     auto& sptr = args[0].node_sptr();
-    CHECK(sptr->is_type<MapNode>());
-    auto* n = static_cast<const MapNode*>(sptr.get());
-    *ret = static_cast<int64_t>(
-        n->data.count(args[1].node_sptr()));
+    if (sptr->is_type<MapNode>()) {
+      auto* n = static_cast<const MapNode*>(sptr.get());
+      CHECK(args[1].type_code() == kNodeHandle);
+      *ret = static_cast<int64_t>(
+          n->data.count(args[1].node_sptr()));
+    } else {
+      CHECK(sptr->is_type<StrMapNode>());
+      auto* n = static_cast<const StrMapNode*>(sptr.get());
+      *ret = static_cast<int64_t>(
+          n->data.count(args[1].operator std::string()));
+    }
   });
 
 TVM_REGISTER_API("_MapItems")
 .set_body([](TVMArgs args,  TVMRetValue* ret) {
     auto& sptr = args[0].node_sptr();
-    CHECK(sptr->is_type<MapNode>());
-    auto* n = static_cast<const MapNode*>(sptr.get());
-    auto rkvs = std::make_shared<ArrayNode>();
-    for (const auto& kv : n->data) {
-      rkvs->data.push_back(kv.first);
-      rkvs->data.push_back(kv.second);
+    if (sptr->is_type<MapNode>()) {
+      auto* n = static_cast<const MapNode*>(sptr.get());
+      auto rkvs = std::make_shared<ArrayNode>();
+      for (const auto& kv : n->data) {
+        rkvs->data.push_back(kv.first);
+        rkvs->data.push_back(kv.second);
+      }
+      *ret = rkvs;
+    } else {
+      auto* n = static_cast<const StrMapNode*>(sptr.get());
+      auto rkvs = std::make_shared<ArrayNode>();
+      for (const auto& kv : n->data) {
+        rkvs->data.push_back(ir::StringImm::make(kv.first).node_);
+        rkvs->data.push_back(kv.second);
+      }
+      *ret = rkvs;
     }
-    *ret = rkvs;
   });
 
 TVM_REGISTER_API("Range")
diff --git a/src/lang/expr.cc b/src/lang/expr.cc
index 0fb783d70cb8..684211079e94 100644
--- a/src/lang/expr.cc
+++ b/src/lang/expr.cc
@@ -74,6 +74,7 @@ TVM_STATIC_IR_FUNCTOR(IRPrinter, vtable)
 
 TVM_REGISTER_NODE_TYPE(ArrayNode);
 TVM_REGISTER_NODE_TYPE(MapNode);
+TVM_REGISTER_NODE_TYPE(StrMapNode);
 TVM_REGISTER_NODE_TYPE(RangeNode);
 TVM_REGISTER_NODE_TYPE(IterVarNode);
 
diff --git a/tests/cpp/container_test.cc b/tests/cpp/container_test.cc
index b5141262c0d3..4a0500bf4faf 100644
--- a/tests/cpp/container_test.cc
+++ b/tests/cpp/container_test.cc
@@ -35,6 +35,15 @@ TEST(Map, Expr) {
   CHECK(!dict.count(zz));
 }
 
+TEST(StrMap, Expr) {
+  using namespace tvm;
+  Var x("x");
+  auto z = max(x + 1 + 2, 100);
+  Map<std::string, Expr> dict{{"x", z}, {"z", 2}};
+  CHECK(dict.size() == 2);
+  CHECK(dict["x"].same_as(z));
+}
+
 TEST(Map, Mutate) {
   using namespace tvm;
   Var x("x");
diff --git a/tests/python/unittest/test_lang_container.py b/tests/python/unittest/test_lang_container.py
index d945fce31fd4..615c5ac0a8d5 100644
--- a/tests/python/unittest/test_lang_container.py
+++ b/tests/python/unittest/test_lang_container.py
@@ -10,6 +10,7 @@ def test_array_save_load_json():
     a_loaded = tvm.load_json(json_str)
     assert(a[1].value == 2)
 
+
 def test_map():
     a = tvm.var('a')
     b = tvm.var('b')
@@ -22,6 +23,17 @@ def test_map():
     assert b in dd
     assert a + 1 not in amap
 
+
+def test_str_map():
+    amap = tvm.convert({'a': 2, 'b': 3})
+    assert 'a' in amap
+    assert len(amap) == 2
+    dd = dict(amap.items())
+    assert amap['a'].value == 2
+    assert 'a' in dd
+    assert 'b' in dd
+
+
 def test_map_save_load_json():
     a = tvm.var('a')
     b = tvm.var('b')
@@ -35,6 +47,7 @@ def test_map_save_load_json():
 
 
 if __name__ == "__main__":
+    test_str_map()
     test_array()
     test_map()
     test_array_save_load_json()

From df4a6299505f7e498053e7e915a5bee1368dd30f Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 17 Jun 2018 10:23:39 -0700
Subject: [PATCH 731/949] [OP] Introduces auxiliary attrs into compute (#1293)

---
 include/tvm/operation.h                       | 52 +++++++++++++------
 include/tvm/runtime/c_runtime_api.h           |  4 ++
 python/tvm/api.py                             | 34 +++++++++---
 src/api/api_lang.cc                           |  9 ++--
 src/lang/reflection.cc                        | 28 +++++++++-
 src/op/compute_op.cc                          | 24 +++++----
 src/op/extern_op.cc                           | 14 ++---
 src/op/scan_op.cc                             | 22 ++++----
 src/schedule/schedule_dataflow_rewrite.cc     |  9 ++--
 tests/python/unittest/test_lang_reflection.py | 13 +++++
 tests/python/unittest/test_lang_tag.py        | 20 +++++--
 topi/include/topi/contrib/cublas.h            |  2 +-
 topi/include/topi/contrib/rocblas.h           |  2 +-
 topi/include/topi/detail/extern.h             | 21 ++++----
 14 files changed, 186 insertions(+), 68 deletions(-)

diff --git a/include/tvm/operation.h b/include/tvm/operation.h
index 5e74f6d99d76..d13680531af9 100644
--- a/include/tvm/operation.h
+++ b/include/tvm/operation.h
@@ -41,6 +41,8 @@ class OperationNode : public FunctionBaseNode {
   std::string name;
   /*! \brief optional tag of the operation */
   std::string tag;
+  /*! \brief addtitional attributes of the operation*/
+  Map<std::string, NodeRef> attrs;
   /*! \return name of the operation */
   const std::string& func_name() const final {
     return name;
@@ -167,6 +169,8 @@ class PlaceholderOpNode : public OperationNode {
 
   void VisitAttrs(AttrVisitor* v) final {
     v->Visit("name", &name);
+    v->Visit("tag", &tag);
+    v->Visit("attrs", &attrs);
     v->Visit("shape", &shape);
     v->Visit("dtype", &dtype);
   }
@@ -220,12 +224,14 @@ class TVM_DLL ComputeOpNode : public OperationNode {
   void VisitAttrs(AttrVisitor* v) final {
     v->Visit("name", &name);
     v->Visit("tag", &tag);
+    v->Visit("attrs", &attrs);
     v->Visit("axis", &axis);
     v->Visit("reduce_axis", &reduce_axis);
     v->Visit("body", &body);
   }
   static Operation make(std::string name,
                         std::string tag,
+                        Map<std::string, NodeRef> attrs,
                         Array<IterVar> axis,
                         Array<Expr> body);
 
@@ -292,6 +298,7 @@ class ScanOpNode : public OperationNode {
   void VisitAttrs(AttrVisitor* v) final {
     v->Visit("name", &name);
     v->Visit("tag", &tag);
+    v->Visit("attrs", &attrs);
     v->Visit("scan_axis", &scan_axis);
     v->Visit("init", &init);
     v->Visit("update", &update);
@@ -301,6 +308,7 @@ class ScanOpNode : public OperationNode {
   }
   static Operation make(std::string name,
                         std::string tag,
+                        Map<std::string, NodeRef> attrs,
                         IterVar axis,
                         Array<Tensor> init,
                         Array<Tensor> update,
@@ -356,15 +364,17 @@ class ExternOpNode : public OperationNode {
   void VisitAttrs(AttrVisitor* v) final {
     v->Visit("name", &name);
     v->Visit("tag", &tag);
+    v->Visit("attrs", &attrs);
     v->Visit("inputs", &inputs);
     v->Visit("body", &body);
   }
   EXPORT static Operation make(std::string name,
-                        std::string tag,
-                        Array<Tensor> inputs,
-                        Array<Buffer> input_placeholders,
-                        Array<Buffer> output_placeholders,
-                        Stmt body);
+                               std::string tag,
+                               Map<std::string, NodeRef> attrs,
+                               Array<Tensor> inputs,
+                               Array<Buffer> input_placeholders,
+                               Array<Buffer> output_placeholders,
+                               Stmt body);
 
   static constexpr const char* _type_key = "ExternOp";
   TVM_DECLARE_NODE_TYPE_INFO(ExternOpNode, OperationNode);
@@ -393,11 +403,13 @@ TVM_DLL Tensor placeholder(Array<Expr> shape,
  * \param fcompute The compute function to create the tensor.
  * \param name The optional name of the tensor.
  * \param tag The optional tag of the tensor.
+ * \param attrs Optional additional attributes of the compute.
  */
 TVM_DLL Tensor compute(Array<Expr> shape,
                        FCompute fcompute,
                        std::string name = "tensor",
-                       std::string tag = "");
+                       std::string tag = "",
+                       Map<std::string, NodeRef> attrs = {});
 
 /*!
  * \brief Construct a new tensor by computing over shape,
@@ -406,11 +418,13 @@ TVM_DLL Tensor compute(Array<Expr> shape,
  * \param fcompute The compute function to create the tensors.
  * \param name The optional name of the tensor.
  * \param tag The optional tag of the tensor.
+ * \param attrs Optional additional attributes of the compute.
  */
 TVM_DLL Array<Tensor> compute(Array<Expr> shape,
                               FBatchCompute fcompute,
                               std::string name = "tensor",
-                              std::string tag = "");
+                              std::string tag = "",
+                              Map<std::string, NodeRef> attrs = {});
 
 /*!
  * \brief Construct new tensors by scan.
@@ -422,42 +436,48 @@ TVM_DLL Array<Tensor> compute(Array<Expr> shape,
  *    but recommended to provide concrete information about scan body.
  * \param name The optional name of the tensor.
  * \param tag The optional tag of the tensor.
+ * \param attrs Optional additional attributes of the compute.
  */
 TVM_DLL Array<Tensor> scan(Array<Tensor> init,
                            Array<Tensor> update,
                            Array<Tensor> state_placeholder,
                            Array<Tensor> inputs = Array<Tensor>(),
                            std::string name = "scan",
-                           std::string tag = "");
+                           std::string tag = "",
+                           Map<std::string, NodeRef> attrs = {});
 
 // same as compute, specialized for different fcompute function
 inline Tensor compute(Array<Expr> shape,
                       std::function<Expr(Var)> f,
                       std::string name = "tensor",
-                      std::string tag = "") {
+                      std::string tag = "",
+                      Map<std::string, NodeRef> attrs = {}) {
   FCompute fc = [f] (const Array<Var>& i) { return f(i[0]); };
-  return compute(shape, fc, name, tag);
+  return compute(shape, fc, name, tag, attrs);
 }
 inline Tensor compute(Array<Expr> shape,
                       std::function<Expr(Var, Var)> f,
                       std::string name = "tensor",
-                      std::string tag = "") {
+                      std::string tag = "",
+                      Map<std::string, NodeRef> attrs = {}) {
   FCompute fc = [f] (const Array<Var>& i) { return f(i[0], i[1]); };
-  return compute(shape, fc, name, tag);
+  return compute(shape, fc, name, tag, attrs);
 }
 inline Tensor compute(Array<Expr> shape,
                       std::function<Expr(Var, Var, Var)> f,
                       std::string name = "tensor",
-                      std::string tag = "") {
+                      std::string tag = "",
+                      Map<std::string, NodeRef> attrs = {}) {
   FCompute fc = [f] (const Array<Var>& i) { return f(i[0], i[1], i[2]); };
-  return  compute(shape, fc, name, tag);
+  return  compute(shape, fc, name, tag, attrs);
 }
 inline Tensor compute(Array<Expr> shape,
                       std::function<Expr(Var, Var, Var, Var)> f,
                       std::string name = "tensor",
-                      std::string tag = "") {
+                      std::string tag = "",
+                      Map<std::string, NodeRef> attrs = {}) {
   FCompute fc = [f] (const Array<Var>& i) { return f(i[0], i[1], i[2], i[3]); };
-  return compute(shape, fc, name, tag);
+  return compute(shape, fc, name, tag, attrs);
 }
 
 // inline function.
diff --git a/include/tvm/runtime/c_runtime_api.h b/include/tvm/runtime/c_runtime_api.h
index 7aef4396e315..c61a4884a1f5 100644
--- a/include/tvm/runtime/c_runtime_api.h
+++ b/include/tvm/runtime/c_runtime_api.h
@@ -42,6 +42,10 @@
 #endif
 #endif
 
+// TVM version
+#define TVM_VERSION "0.4.0"
+
+
 // TVM Runtime is DLPack compatible.
 #include <dlpack/dlpack.h>
 
diff --git a/python/tvm/api.py b/python/tvm/api.py
index 081b67cb34c4..75debc33db66 100644
--- a/python/tvm/api.py
+++ b/python/tvm/api.py
@@ -189,7 +189,7 @@ def placeholder(shape, dtype=None, name="placeholder"):
         shape, dtype, name)
 
 
-def compute(shape, fcompute, name="compute", tag=""):
+def compute(shape, fcompute, name="compute", tag="", attrs=None):
     """Construct a new tensor by computing over the shape domain.
 
     The compute rule is result[axis] = fcompute(axis)
@@ -205,6 +205,12 @@ def compute(shape, fcompute, name="compute", tag=""):
     name: str, optional
         The name hint of the tensor
 
+    tag: str, optional
+        Additonal tag information about the compute.
+
+    attrs: dict, optional
+        The additional auxiliary attributes about the compute.
+
     Returns
     -------
     tensor: Tensor
@@ -232,13 +238,13 @@ def compute(shape, fcompute, name="compute", tag=""):
         body = [body]
     body = convert(body)
     op_node = _api_internal._ComputeOp(
-        name, tag, dim_var, body)
+        name, tag, attrs, dim_var, body)
     num = op_node.num_outputs
     outputs = tuple(op_node.output(i) for i in range(num))
     return outputs[0] if num == 1 else outputs
 
 
-def scan(init, update, state_placeholder, inputs=None, name="scan", tag=""):
+def scan(init, update, state_placeholder, inputs=None, name="scan", tag="", attrs=None):
     """Construct new tensors by scanning over axis.
 
     Parameters
@@ -259,6 +265,12 @@ def scan(init, update, state_placeholder, inputs=None, name="scan", tag=""):
     name: str, optional
         The name hint of the tensor
 
+    tag: str, optional
+        Additonal tag information about the compute.
+
+    attrs: dict, optional
+        The additional auxiliary attributes about the compute.
+
     Returns
     -------
     tensor: Tensor or list of Tensors
@@ -294,7 +306,8 @@ def scan(init, update, state_placeholder, inputs=None, name="scan", tag=""):
     if len(init) != len(update) or len(init) != len(state_placeholder):
         raise ValueError("init, update, state_placeholder must have same length")
     axis = _IterVar((init[0].shape[0], update[0].shape[0]), "%s.idx" % name, 3)
-    op = _api_internal._ScanOp(name, tag, axis, init, update,
+    op = _api_internal._ScanOp(name, tag, attrs,
+                               axis, init, update,
                                state_placeholder, inputs)
     res = [op.output(i) for i in range(len(update))]
     return res[0] if len(res) == 1 else res
@@ -307,7 +320,8 @@ def extern(shape,
            dtype=None,
            in_buffers=None,
            out_buffers=None,
-           tag=""):
+           tag="",
+           attrs=None):
     """Compute several tensor via extern function.
 
     Parameters
@@ -345,6 +359,13 @@ def extern(shape,
     out_buffers: Buffer or list of Buffers, optional
         Output buffers.
 
+
+    tag: str, optional
+        Additonal tag information about the compute.
+
+    attrs: dict, optional
+        The additional auxiliary attributes about the compute.
+
     Returns
     -------
     tensor: Tensor or list of Tensors
@@ -406,7 +427,8 @@ def extern(shape,
     if isinstance(body, _expr.Expr):
         body = _make.Evaluate(body)
 
-    op = _api_internal._ExternOp(name, tag, inputs, input_placeholders,
+    op = _api_internal._ExternOp(name, tag, attrs,
+                                 inputs, input_placeholders,
                                  output_placeholders, body)
     res = [op.output(i) for i in range(len(output_placeholders))]
     return res[0] if len(res) == 1 else res
diff --git a/src/api/api_lang.cc b/src/api/api_lang.cc
index 00f8ba5f3847..3d80b5c2e7a4 100644
--- a/src/api/api_lang.cc
+++ b/src/api/api_lang.cc
@@ -262,7 +262,8 @@ TVM_REGISTER_API("_ComputeOp")
     *ret = ComputeOpNode::make(args[0],
                                args[1],
                                args[2],
-                               args[3]);
+                               args[3],
+                               args[4]);
   });
 
 TVM_REGISTER_API("_ScanOp")
@@ -273,7 +274,8 @@ TVM_REGISTER_API("_ScanOp")
                             args[3],
                             args[4],
                             args[5],
-                            args[6]);
+                            args[6],
+                            args[7]);
   });
 
 TVM_REGISTER_API("_ExternOp")
@@ -283,7 +285,8 @@ TVM_REGISTER_API("_ExternOp")
                               args[2],
                               args[3],
                               args[4],
-                              args[5]);
+                              args[5],
+                              args[6]);
   });
 
 TVM_REGISTER_API("_OpGetOutput")
diff --git a/src/lang/reflection.cc b/src/lang/reflection.cc
index a8152d214d1f..ba580e75e124 100644
--- a/src/lang/reflection.cc
+++ b/src/lang/reflection.cc
@@ -84,6 +84,11 @@ class NodeIndexer : public AttrVisitor {
         MakeIndex(kv.first.get());
         MakeIndex(kv.second.get());
       }
+    } else if (node->is_type<StrMapNode>()) {
+      StrMapNode* n = static_cast<StrMapNode*>(node);
+      for (const auto& kv : n->data) {
+        MakeIndex(kv.second.get());
+      }
     } else {
       node->VisitAttrs(this);
     }
@@ -99,6 +104,8 @@ struct JSONNode {
   std::string type_key;
   // the attributes
   AttrMap attrs;
+  // container keys
+  std::vector<std::string> keys;
   // container data
   std::vector<size_t> data;
 
@@ -108,6 +115,9 @@ struct JSONNode {
     if (attrs.size() != 0) {
       writer->WriteObjectKeyValue("attrs", attrs);
     }
+    if (keys.size() != 0) {
+      writer->WriteObjectKeyValue("keys", keys);
+    }
     if (data.size() != 0) {
       writer->WriteObjectKeyValue("data", data);
     }
@@ -121,6 +131,7 @@ struct JSONNode {
     dmlc::JSONObjectReadHelper helper;
     helper.DeclareOptionalField("type_key", &type_key);
     helper.DeclareOptionalField("attrs", &attrs);
+    helper.DeclareOptionalField("keys", &keys);
     helper.DeclareOptionalField("data", &data);
     helper.ReadAllFields(reader);
   }
@@ -176,13 +187,19 @@ class JSONAttrGetter : public AttrVisitor {
       }
     } else if (node->is_type<MapNode>()) {
       MapNode* n = static_cast<MapNode*>(node);
-      std::vector<std::pair<size_t, size_t> > elems;
       for (const auto& kv : n->data) {
         node_->data.push_back(
             node_index_->at(kv.first.get()));
         node_->data.push_back(
             node_index_->at(kv.second.get()));
       }
+    } else if (node->is_type<StrMapNode>()) {
+      StrMapNode* n = static_cast<StrMapNode*>(node);
+      for (const auto& kv : n->data) {
+        node_->keys.push_back(kv.first);
+        node_->data.push_back(
+            node_index_->at(kv.second.get()));
+      }
     } else {
       node->VisitAttrs(this);
     }
@@ -256,6 +273,13 @@ class JSONAttrSetter : public AttrVisitor {
         n->data[node_list_->at(node_->data[i])]
             = node_list_->at(node_->data[i + 1]);
       }
+    } else if (node->is_type<StrMapNode>()) {
+      StrMapNode* n = static_cast<StrMapNode*>(node);
+      CHECK_EQ(node_->data.size(), node_->keys.size());
+      for (size_t i = 0; i < node_->data.size(); ++i) {
+        n->data[node_->keys[i]]
+            = node_list_->at(node_->data[i]);
+      }
     } else {
       node->VisitAttrs(this);
     }
@@ -302,7 +326,7 @@ struct JSONGraph {
       getter.Get(n);
       g.nodes.emplace_back(std::move(jnode));
     }
-    g.attrs["tvm_version"] = "0.1.0";
+    g.attrs["tvm_version"] = TVM_VERSION;
     g.root = indexer.node_index.at(root.node_.get());
     return g;
   }
diff --git a/src/op/compute_op.cc b/src/op/compute_op.cc
index 0dadf4ab3dd5..366ea2c78fe6 100644
--- a/src/op/compute_op.cc
+++ b/src/op/compute_op.cc
@@ -66,7 +66,8 @@ Array<Expr> ComputeOpNode::output_shape(size_t idx) const {
 Tensor compute(Array<Expr> shape,
                FCompute fcompute,
                std::string name,
-               std::string tag) {
+               std::string tag,
+               Map<std::string, NodeRef> attrs) {
   auto op_node = std::make_shared<ComputeOpNode>();
   // compute dimension.
   size_t ndim = shape.size();
@@ -80,13 +81,15 @@ Tensor compute(Array<Expr> shape,
     args.push_back(axis.back()->var);
   }
 
-  return ComputeOpNode::make(name, tag, axis, {fcompute(args)}).output(0);
+  return ComputeOpNode::make(
+      name, tag, attrs, axis, {fcompute(args)}).output(0);
 }
 
 Array<Tensor> compute(Array<Expr> shape,
                       FBatchCompute fcompute,
                       std::string name,
-                      std::string tag) {
+                      std::string tag,
+                      Map<std::string, NodeRef> attrs) {
   auto op_node = std::make_shared<ComputeOpNode>();
   // compute dimension.
   size_t ndim = shape.size();
@@ -100,7 +103,7 @@ Array<Tensor> compute(Array<Expr> shape,
     args.push_back(axis.back()->var);
   }
 
-  Operation op = ComputeOpNode::make(name, tag, axis, fcompute(args));
+  Operation op = ComputeOpNode::make(name, tag, attrs, axis, fcompute(args));
   Array<Tensor> outputs;
   for (int idx = 0; idx < op->num_outputs(); ++idx) {
     outputs.push_back(op.output(idx));
@@ -110,13 +113,15 @@ Array<Tensor> compute(Array<Expr> shape,
 
 Operation ComputeOpNode::make(std::string name,
                               std::string tag,
+                              Map<std::string, NodeRef> attrs,
                               Array<IterVar> axis,
                               Array<Expr> body) {
   auto n = std::make_shared<ComputeOpNode>();
-  n->name = name;
-  n->tag = tag;
-  n->axis = axis;
-  n->body = body;
+  n->name = std::move(name);
+  n->tag = std::move(tag);
+  n->attrs = std::move(attrs);
+  n->axis = std::move(axis);
+  n->body = std::move(body);
   if (n->body[0]->is_type<ir::Reduce>()) {
     const ir::Reduce* reduce = n->body[0].as<ir::Reduce>();
     n->reduce_axis = reduce->axis;
@@ -171,7 +176,8 @@ Operation ComputeOpNode::ReplaceInputs(
       });
   }
   if (!arr.same_as(this->body)) {
-    return ComputeOpNode::make(name, tag, axis, arr);
+    return ComputeOpNode::make(
+        this->name, this->tag, this->attrs, this->axis, arr);
   } else {
     return self;
   }
diff --git a/src/op/extern_op.cc b/src/op/extern_op.cc
index 8c372966cd09..759e258e90ef 100644
--- a/src/op/extern_op.cc
+++ b/src/op/extern_op.cc
@@ -38,23 +38,25 @@ Array<Expr> ExternOpNode::output_shape(size_t i) const {
 
 Operation ExternOpNode::make(std::string name,
                              std::string tag,
+                             Map<std::string, NodeRef> attrs,
                              Array<Tensor> inputs,
                              Array<Buffer> input_placeholders,
                              Array<Buffer> output_placeholders,
                              Stmt body) {
   auto n = std::make_shared<ExternOpNode>();
-  n->name = name;
-  n->tag = tag;
+  n->name = std::move(name);
+  n->tag = std::move(tag);
+  n->attrs = std::move(attrs);
   CHECK_EQ(inputs.size(), input_placeholders.size());
   for (size_t i = 0; i < inputs.size(); ++i) {
     CHECK_EQ(inputs[i]->dtype, input_placeholders[i]->dtype);
     CHECK(inputs[i]->shape.same_as(input_placeholders[i]->shape));
     CHECK_EQ(input_placeholders[i]->strides.size(), 0U);
   }
-  n->inputs = inputs;
-  n->input_placeholders = input_placeholders;
-  n->output_placeholders = output_placeholders;
-  n->body = body;
+  n->inputs = std::move(inputs);
+  n->input_placeholders = std::move(input_placeholders);
+  n->output_placeholders = std::move(output_placeholders);
+  n->body = std::move(body);
   return Operation(n);
 }
 
diff --git a/src/op/scan_op.cc b/src/op/scan_op.cc
index a3ccd70fa763..626c8eba46b4 100644
--- a/src/op/scan_op.cc
+++ b/src/op/scan_op.cc
@@ -45,6 +45,7 @@ Array<Expr> ScanOpNode::output_shape(size_t i) const {
 
 Operation ScanOpNode::make(std::string name,
                            std::string tag,
+                           Map<std::string, NodeRef> attrs,
                            IterVar axis,
                            Array<Tensor> init,
                            Array<Tensor> update,
@@ -86,13 +87,14 @@ Operation ScanOpNode::make(std::string name,
           init[i]->shape[k], state_placeholder[i]->shape[k]));
     }
   }
-  n->name = name;
-  n->tag = tag;
-  n->scan_axis = axis;
-  n->init = init;
-  n->update = update;
-  n->state_placeholder = state_placeholder;
-  n->inputs = inputs;
+  n->name = std::move(name);
+  n->tag = std::move(tag);
+  n->attrs = std::move(attrs);
+  n->scan_axis = std::move(axis);
+  n->init = std::move(init);
+  n->update = std::move(update);
+  n->state_placeholder = std::move(state_placeholder);
+  n->inputs = std::move(inputs);
   return Operation(n);
 }
 
@@ -101,14 +103,16 @@ Array<Tensor> scan(Array<Tensor> init,
                    Array<Tensor> state_placeholder,
                    Array<Tensor> inputs,
                    std::string name,
-                   std::string tag) {
+                   std::string tag,
+                   Map<std::string, NodeRef> attrs) {
   IterVar scan_axis =
       IterVarNode::make(
           Range::make_by_min_extent(
               init[0]->shape[0], update[0]->shape[0] - init[0]->shape[0]),
           Var(name + ".idx"), kOrdered);
   Operation op = ScanOpNode::make(
-      name, tag, scan_axis, init, update, state_placeholder, inputs);
+      name, tag, attrs, scan_axis,
+      init, update, state_placeholder, inputs);
   Array<Tensor> res;
   for (int i = 0; i < op->num_outputs(); ++i) {
     res.push_back(op.output(i));
diff --git a/src/schedule/schedule_dataflow_rewrite.cc b/src/schedule/schedule_dataflow_rewrite.cc
index 89390dcc048d..e9fbcba088fe 100644
--- a/src/schedule/schedule_dataflow_rewrite.cc
+++ b/src/schedule/schedule_dataflow_rewrite.cc
@@ -232,7 +232,8 @@ Array<Tensor> CacheWriteWithReLayout(Schedule sch,
     }
   }
   Operation cache_op = ComputeOpNode::make(
-      compute->name + "." + scope, compute->tag, new_axis, body_list);
+      compute->name + "." + scope, compute->tag, compute->attrs,
+      new_axis, body_list);
   Array<Tensor> cache_tensor_list;
   Array<Expr> cache_expr_list;
   for (size_t i = 0; i < tensor_size; i++) {
@@ -241,7 +242,8 @@ Array<Tensor> CacheWriteWithReLayout(Schedule sch,
     cache_expr_list.push_back(cache_tensor(args));
   }
   Operation orig_new_op = ComputeOpNode::make(
-      compute->name, compute->tag, compute->axis, cache_expr_list);
+      compute->name, compute->tag, compute->attrs,
+      compute->axis, cache_expr_list);
   // The replace of the dataflow
   std::unordered_map<Tensor, Tensor> vmap;
   std::unordered_map<Tensor, Tensor> rvmap;
@@ -430,7 +432,8 @@ void InjectInline(ScheduleNode* sch) {
       Operation op = s->op;
       if (changed[i]) {
         op = ComputeOpNode::make(
-            compute->name, compute->tag, compute->axis, new_body[i]);
+            compute->name, compute->tag, compute->attrs,
+            compute->axis, new_body[i]);
       }
       op = op->ReplaceInputs(op, repl);
       if (!op.same_as(s->op)) {
diff --git a/tests/python/unittest/test_lang_reflection.py b/tests/python/unittest/test_lang_reflection.py
index 08ba4b494f08..fefb8771a812 100644
--- a/tests/python/unittest/test_lang_reflection.py
+++ b/tests/python/unittest/test_lang_reflection.py
@@ -11,6 +11,18 @@ def test_const_saveload_json():
     assert tvm.save_json(zz) == tvm.save_json(z)
 
 
+def test_make_smap():
+    # save load json
+    x = tvm.const(1)
+    y = tvm.const(10)
+    z = x + y
+    smap = tvm.convert({"z": z, "x": x})
+    json_str = tvm.save_json(tvm.convert([smap]))
+    arr = tvm.load_json(json_str)
+    assert len(arr) == 1
+    assert arr[0]["z"].a == arr[0]["x"]
+
+
 def test_make_node():
     x = tvm.make.node("IntImm", dtype="int32", value=10)
     assert isinstance(x, tvm.expr.IntImm)
@@ -35,5 +47,6 @@ def test_make_sum():
 
 if __name__ == "__main__":
     test_make_node()
+    test_make_smap()
     test_const_saveload_json()
     test_make_sum()
diff --git a/tests/python/unittest/test_lang_tag.py b/tests/python/unittest/test_lang_tag.py
index c58c5da6e1e9..e141830b0595 100644
--- a/tests/python/unittest/test_lang_tag.py
+++ b/tests/python/unittest/test_lang_tag.py
@@ -1,3 +1,4 @@
+import json
 import tvm
 
 @tvm.tag_scope(tag="conv")
@@ -24,8 +25,19 @@ def test_with():
     B = tvm.placeholder((m, l), name='B')
     with tvm.tag_scope(tag="gemm"):
         k = tvm.reduce_axis((0, l), name='k')
-        C = tvm.compute((n, m), lambda i, j: tvm.sum(A[i, k] * B[j, k], axis=k))
+        C = tvm.compute((n, m), lambda i, j: tvm.sum(A[i, k] * B[j, k], axis=k),
+                        attrs={"hello" : 1, "arr": [10, 12]})
+
     assert C.op.tag == 'gemm'
+    assert "hello" in C.op.attrs
+    assert "xx" not in C.op.attrs
+    assert C.op.attrs["hello"].value == 1
+    CC = tvm.load_json(tvm.save_json(C))
+    assert CC.op.attrs["hello"].value == 1
+    assert CC.op.attrs["arr"][0].value == 10
+    # str format happened to be json compatible
+    assert json.loads(str(CC.op.attrs))["arr"][1] == 12
+
 
 def test_decorator():
     n = tvm.var('n')
@@ -39,6 +51,7 @@ def test_decorator():
     B = tvm.placeholder((c, c, kh, kw), name='B')
     C = compute_conv(A, B)
     assert C.op.tag == 'conv'
+    assert len(C.op.attrs) == 0
 
 def test_nested():
     n = tvm.var('n')
@@ -59,5 +72,6 @@ def test_nested():
 
 
 if __name__ == "__main__":
-    import nose
-    nose.runmodule()
+    test_with()
+    test_decorator()
+    test_nested()
diff --git a/topi/include/topi/contrib/cublas.h b/topi/include/topi/contrib/cublas.h
index 0ce09e313d37..0ec81cc405cb 100644
--- a/topi/include/topi/contrib/cublas.h
+++ b/topi/include/topi/contrib/cublas.h
@@ -40,7 +40,7 @@ inline Tensor cublas_matmul(const Tensor& lhs,
         pack_buffer(outs[0]),
         transa,
         transb });
-    }, "C", "")[0];
+    }, "C", "", {})[0];
 }
 
 }  // namespace contrib
diff --git a/topi/include/topi/contrib/rocblas.h b/topi/include/topi/contrib/rocblas.h
index dbadaf10b60b..5abdc4259c29 100644
--- a/topi/include/topi/contrib/rocblas.h
+++ b/topi/include/topi/contrib/rocblas.h
@@ -39,7 +39,7 @@ inline Tensor rocblas_matmul(const Tensor& lhs,
         pack_buffer(outs[0]),
         transa,
         transb });
-    }, "C", "")[0];
+    }, "C", "", {})[0];
 }
 
 }  // namespace contrib
diff --git a/topi/include/topi/detail/extern.h b/topi/include/topi/detail/extern.h
index 97b7a4655f0a..44170b75b9a0 100644
--- a/topi/include/topi/detail/extern.h
+++ b/topi/include/topi/detail/extern.h
@@ -6,10 +6,10 @@
 #ifndef TOPI_DETAIL_EXTERN_H_
 #define TOPI_DETAIL_EXTERN_H_
 
+#include <tvm/tvm.h>
 #include <vector>
 #include <string>
 
-#include "tvm/tvm.h"
 
 namespace topi {
 namespace detail {
@@ -25,8 +25,8 @@ using namespace tvm;
  * \return The Buffer object
  */
 inline Buffer DeclExternBuffer(Array<Expr> shape,
-                        Type dtype,
-                        std::string name) {
+                               Type dtype,
+                               std::string name) {
   auto data = var(name, Handle());
   auto elem_offset = Expr();
   return BufferNode::make(data, dtype, shape, Array<Expr>(), elem_offset, name, "",
@@ -51,17 +51,19 @@ using FExtern = std::function<Expr(Array<Buffer>, Array<Buffer>)>;
  * the external function given the input and output buffers.
  * \param name The name of the operation
  * \param tag The tag to mark the operation
+ * \param attrs The additional auxiliary attributes of the operation.
  *
  * \return An array of Tensors representing the outputs of the function invocation. There will
  * be one output Tensor for each element of out_shapes, with dtype equal to the corresponding
  * element of out_types.
  */
 inline Array<Tensor> make_extern(const Array< Array<Expr> >& out_shapes,
-                          const std::vector<Type>& out_types,
-                          const Array<Tensor>& inputs,
-                          FExtern fextern,
-                          std::string name,
-                          std::string tag) {
+                                 const std::vector<Type>& out_types,
+                                 const Array<Tensor>& inputs,
+                                 FExtern fextern,
+                                 std::string name,
+                                 std::string tag,
+                                 ::tvm::Map<std::string, NodeRef> attrs) {
   CHECK_EQ(out_shapes.size(), out_types.size())
     << "make_extern: out_shapes and out_types must have equal size";
 
@@ -78,7 +80,8 @@ inline Array<Tensor> make_extern(const Array< Array<Expr> >& out_shapes,
   auto body_stmt = tvm::ir::Evaluate::make(body);
 
   auto op = ExternOpNode::make(
-    name, tag, inputs, input_placeholders, output_placeholders, body_stmt);
+      name, tag, attrs, inputs,
+      input_placeholders, output_placeholders, body_stmt);
 
   Array<Tensor> outputs;
   for (size_t i = 0; i < output_placeholders.size(); ++i) {

From d3a7df9bb19c1865a5c2fb5e7e6a914425e21f6a Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 17 Jun 2018 12:07:11 -0700
Subject: [PATCH 732/949] [DOCS] Improve review guide, improve cmake llvm build
 (#1295)

---
 CODEOWNERS => .github/CODEOWNERS |  0
 .github/PULL_REQUEST_TEMPLATE.md |  2 +-
 cmake/util/FindLLVM.cmake        | 13 ++++++++++---
 docs/contribute/code_review.rst  | 16 +++++++++++++---
 docs/index.rst                   |  1 +
 docs/langref/index.rst           |  4 ++++
 tutorials/topi/README.txt        |  3 ++-
 7 files changed, 31 insertions(+), 8 deletions(-)
 rename CODEOWNERS => .github/CODEOWNERS (100%)
 create mode 100644 docs/langref/index.rst

diff --git a/CODEOWNERS b/.github/CODEOWNERS
similarity index 100%
rename from CODEOWNERS
rename to .github/CODEOWNERS
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 80742414c54f..313b776b0824 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1 +1 @@
-Thanks for contributing to TVM!   Please refer to guideline http://docs.tvm.ai/contribute/ for useful information and tips. After the pull request is submitted, please request code reviews from others in the community.
+Thanks for contributing to TVM!   Please refer to guideline https://docs.tvm.ai/contribute/ for useful information and tips. After the pull request is submitted, please request code reviews from others in the community.
diff --git a/cmake/util/FindLLVM.cmake b/cmake/util/FindLLVM.cmake
index 3396edc7d225..c155a34fa308 100644
--- a/cmake/util/FindLLVM.cmake
+++ b/cmake/util/FindLLVM.cmake
@@ -23,8 +23,6 @@ macro(find_llvm use_llvm)
   elseif(NOT LLVM_CONFIG STREQUAL "OFF")
     # use llvm config
     message(STATUS "Use llvm-config=" ${LLVM_CONFIG})
-    execute_process(COMMAND ${LLVM_CONFIG} --includedir
-      OUTPUT_VARIABLE LLVM_INCLUDE_DIRS)
     execute_process(COMMAND ${LLVM_CONFIG} --libfiles
       OUTPUT_VARIABLE __llvm_libfiles)
     execute_process(COMMAND ${LLVM_CONFIG} --system-libs
@@ -34,12 +32,21 @@ macro(find_llvm use_llvm)
     execute_process(COMMAND ${LLVM_CONFIG} --version
       COMMAND cut -b 1,3
       OUTPUT_VARIABLE TVM_LLVM_VERSION)
+    # definitions
     string(REGEX MATCHALL "(^| )-D[A-Za-z0-9_]*" LLVM_DEFINITIONS ${__llvm_cxxflags})
+    # include dir
+    string(REGEX MATCHALL "(^| )-I[A-Za-z0-9_/.\-]*" __llvm_include_flags ${__llvm_cxxflags})
+    set(LLVM_INCLUDE_DIRS "")
+    foreach(__flag IN ITEMS ${__llvm_include_flags})
+      string(REGEX REPLACE "(^| )-I" "" __dir "${__flag}")
+      list(APPEND LLVM_INCLUDE_DIRS "${__dir}")
+    endforeach()
+    message(STATUS ${LLVM_INCLUDE_DIRS})
+    # libfiles
     string(STRIP ${__llvm_libfiles} __llvm_libfiles)
     string(STRIP ${__llvm_system_libs} __llvm_system_libs)
     set(LLVM_LIBS "${__llvm_libfiles} ${__llvm_system_libs}")
     separate_arguments(LLVM_LIBS)
-    string(STRIP ${LLVM_INCLUDE_DIRS} LLVM_INCLUDE_DIRS)
     string(STRIP ${TVM_LLVM_VERSION} TVM_LLVM_VERSION)
   endif()
 endmacro(find_llvm)
diff --git a/docs/contribute/code_review.rst b/docs/contribute/code_review.rst
index b6dea53001f9..344296932703 100644
--- a/docs/contribute/code_review.rst
+++ b/docs/contribute/code_review.rst
@@ -7,6 +7,7 @@ Open source code is maintained by a community with diverse backend, and it is ev
 
 Here are some checklists for code reviews, it is also helpful reference for contributors
 
+
 Hold the Highest Standard
 -------------------------
 The first rule for code reviewers is to always keep the highest standard, and do not approve code just to "be friendly". Good, informative critics each other learn and prevents technical debt in early stages.
@@ -46,8 +47,17 @@ Respect each other
 ------------------
 The code reviewers and contributors are paying the most precious currencies in the world -- time. We are volunteers in the community to spend the time to build good code, help each other, learn and have fun hacking.
 
-Approve Changes
----------------
+Learn from other Code Reviews
+-----------------------------
+There can be multiple reviewers reviewing the same changes. Many cases other reviewers
+may spot things you did not find. Try to learn from other code reviews,
+when possible, document these lessons.
+
+Approve and Request Changes Explicitly
+--------------------------------------
 The contributor and code owner can request code reviews from multiple reviewers.
-Code owner can decide if the code can be merged in case by case if some of the reviewers did not respond in time(e.g. a week) and existing reviews are sufficient. 
 Remember to approve changes when your comments are addressed in a code review.
+To do so -- please click on changes tab in the pull request, then select approve,
+or comment on the code and click request changes.
+Code owner can decide if the code can be merged in case by case if some of the reviewers
+did not respond in time(e.g. a week) and existing reviews are sufficient.
diff --git a/docs/index.rst b/docs/index.rst
index f46775d15837..e3e7f48044ab 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -17,6 +17,7 @@ API Reference
 .. toctree::
    :maxdepth: 2
 
+   langref/index
    api/python/index
    api_links
 
diff --git a/docs/langref/index.rst b/docs/langref/index.rst
new file mode 100644
index 000000000000..dc51c3172c57
--- /dev/null
+++ b/docs/langref/index.rst
@@ -0,0 +1,4 @@
+Language Reference
+==================
+This document provide references to
+embedded languages in TVM stack.
diff --git a/tutorials/topi/README.txt b/tutorials/topi/README.txt
index c58aaa9d8290..eae0aafafc02 100644
--- a/tutorials/topi/README.txt
+++ b/tutorials/topi/README.txt
@@ -1 +1,2 @@
-TOPI: TVM Operator Inventory
\ No newline at end of file
+TOPI: TVM Operator Inventory
+----------------------------

From 9bc96f54b035e94717070db67d9f38799b1f9294 Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Mon, 18 Jun 2018 03:38:23 +0800
Subject: [PATCH 733/949] [NNVM][TESTING] Add two testing symbols: dqn and
 dcgan (#1294)

---
 nnvm/python/nnvm/testing/__init__.py          |  2 +
 nnvm/python/nnvm/testing/dcgan.py             | 90 +++++++++++++++++++
 nnvm/python/nnvm/testing/dqn.py               | 71 +++++++++++++++
 .../frontend/mxnet/model_zoo/__init__.py      | 10 ++-
 .../python/frontend/mxnet/model_zoo/dcgan.py  | 63 +++++++++++++
 .../python/frontend/mxnet/model_zoo/dqn.py    | 27 ++++++
 .../tests/python/frontend/mxnet/test_graph.py | 14 +++
 7 files changed, 276 insertions(+), 1 deletion(-)
 create mode 100644 nnvm/python/nnvm/testing/dcgan.py
 create mode 100644 nnvm/python/nnvm/testing/dqn.py
 create mode 100644 nnvm/tests/python/frontend/mxnet/model_zoo/dcgan.py
 create mode 100644 nnvm/tests/python/frontend/mxnet/model_zoo/dqn.py

diff --git a/nnvm/python/nnvm/testing/__init__.py b/nnvm/python/nnvm/testing/__init__.py
index 56d5a9a48b59..2700aea104f6 100644
--- a/nnvm/python/nnvm/testing/__init__.py
+++ b/nnvm/python/nnvm/testing/__init__.py
@@ -7,4 +7,6 @@
 from . import mlp
 from . import resnet
 from . import vgg
+from . import dcgan
+from . import dqn
 from . import yolo2_detection
diff --git a/nnvm/python/nnvm/testing/dcgan.py b/nnvm/python/nnvm/testing/dcgan.py
new file mode 100644
index 000000000000..421699ad4c14
--- /dev/null
+++ b/nnvm/python/nnvm/testing/dcgan.py
@@ -0,0 +1,90 @@
+# pylint: disable=unused-argument
+"""
+Symbol of the generator of DCGAN
+
+Adopted from:
+https://github.com/tqchen/mxnet-gan/blob/master/mxgan/generator.py
+
+Reference:
+Radford, Alec, Luke Metz, and Soumith Chintala.
+"Unsupervised representation learning with deep convolutional generative adversarial networks."
+arXiv preprint arXiv:1511.06434 (2015).
+"""
+from .. import symbol as sym
+from . utils import create_workload
+
+def deconv2d(data, ishape, oshape, kshape, name, stride=(2, 2)):
+    """a deconv layer that enlarges the feature map"""
+    target_shape = (oshape[-2], oshape[-1])
+
+    pad_y = (kshape[0] - 1) // 2
+    pad_x = (kshape[1] - 1) // 2
+    adj_y = (target_shape[0] + 2 * pad_y - kshape[0]) % stride[0]
+    adj_x = (target_shape[1] + 2 * pad_x - kshape[1]) % stride[1]
+
+    net = sym.conv2d_transpose(data,
+                               kernel_size=kshape,
+                               strides=stride,
+                               channels=oshape[0],
+                               padding=(pad_y, pad_x),
+                               output_padding=(adj_y, adj_x),
+                               use_bias=False,
+                               name=name)
+    return net
+
+def deconv2d_bn_relu(data, prefix, **kwargs):
+    """a block of deconv + batch norm + relu"""
+    eps = 1e-5 + 1e-12
+    net = deconv2d(data, name="%s_deconv" % prefix, **kwargs)
+    net = sym.batch_norm(net, epsilon=eps, name="%s_bn" % prefix)
+    net = sym.relu(net, name="%s_act" % prefix)
+    return net
+
+def get_symbol(oshape, ngf=128, code=None):
+    """get symbol of dcgan generator"""
+    assert oshape[-1] == 32, "Only support 32x32 image"
+    assert oshape[-2] == 32, "Only support 32x32 image"
+
+    code = sym.Variable("data") if code is None else code
+    net = sym.dense(code, name="g1", units=4*4*ngf*4, use_bias=False)
+    net = sym.relu(net)
+    # 4 x 4
+    net = sym.reshape(net, shape=(-1, ngf * 4, 4, 4))
+    # 8 x 8
+    net = deconv2d_bn_relu(
+        net, ishape=(ngf * 4, 4, 4), oshape=(ngf * 2, 8, 8), kshape=(4, 4), prefix="g2")
+    # 16x16
+    net = deconv2d_bn_relu(
+        net, ishape=(ngf * 2, 8, 8), oshape=(ngf, 16, 16), kshape=(4, 4), prefix="g3")
+    # 32x32
+    net = deconv2d(
+        net, ishape=(ngf, 16, 16), oshape=oshape[-3:], kshape=(4, 4), name="g4_deconv")
+    net = sym.tanh(net)
+    return net
+
+
+def get_workload(batch_size, oshape=(3, 32, 32), ngf=128, random_len=100, dtype="float32"):
+    """Get benchmark workload for a DCGAN generator
+
+    Parameters
+    ----------
+    batch_size : int
+        The batch size used in the model
+    oshape : tuple, optional
+        The shape of output image, layout="CHW"
+    ngf: int, optional
+        The number of final feature maps in the generator
+    random_len : int, optional
+        The length of random input
+    dtype : str, optional
+        The data type
+
+    Returns
+    -------
+    net : nnvm.symbol
+        The computational graph
+    params : dict of str to NDArray
+        The parameters.
+    """
+    net = get_symbol(oshape=oshape, ngf=ngf)
+    return create_workload(net, batch_size, (random_len, ), dtype)
diff --git a/nnvm/python/nnvm/testing/dqn.py b/nnvm/python/nnvm/testing/dqn.py
new file mode 100644
index 000000000000..b04475efa32a
--- /dev/null
+++ b/nnvm/python/nnvm/testing/dqn.py
@@ -0,0 +1,71 @@
+# 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.
+
+"""
+Symbol of Nature DQN
+
+Reference:
+Mnih, Volodymyr, et al. "Human-level control through deep reinforcement learning."
+Nature 518.7540 (2015): 529.
+"""
+
+from .. import symbol as sym
+from . utils import create_workload
+
+def get_symbol(num_actions=18):
+    """get symbol of nature dqn"""
+    data = sym.Variable(name='data')
+    net = sym.conv2d(data, kernel_size=(8, 8), strides=(4, 4), padding=(0, 0),
+                     channels=32, name='conv1')
+    net = sym.relu(net, name='relu1')
+    net = sym.conv2d(net, kernel_size=(4, 4), strides=(2, 2), padding=(0, 0),
+                     channels=64, name='conv2')
+    net = sym.relu(net, name='relu2')
+    net = sym.conv2d(net, kernel_size=(3, 3), strides=(1, 1), padding=(0, 0),
+                     channels=64, name='conv3')
+    net = sym.relu(net, name='relu3')
+    net = sym.flatten(net, name='flatten')
+    net = sym.dense(net, units=512, name='fc4')
+    net = sym.relu(net, name='relu4')
+    net = sym.dense(net, units=num_actions, name='fc5')
+
+    return net
+
+
+def get_workload(batch_size, num_actions=18, image_shape=(4, 84, 84), dtype="float32"):
+    """Get benchmark workload for a Deep Q Network
+
+    Parameters
+    ----------
+    batch_size : int
+        The batch size used in the model
+    num_actions : int, optional
+        Number of actions
+    image_shape : tuple, optional
+        The input image shape
+    dtype : str, optional
+        The data type
+
+    Returns
+    -------
+    net : nnvm.symbol
+        The computational graph
+    params : dict of str to NDArray
+        The parameters.
+    """
+    net = get_symbol(num_actions=num_actions)
+    return create_workload(net, batch_size, image_shape, dtype)
diff --git a/nnvm/tests/python/frontend/mxnet/model_zoo/__init__.py b/nnvm/tests/python/frontend/mxnet/model_zoo/__init__.py
index c39e2b21490f..6c3d07ffcd71 100644
--- a/nnvm/tests/python/frontend/mxnet/model_zoo/__init__.py
+++ b/nnvm/tests/python/frontend/mxnet/model_zoo/__init__.py
@@ -1,6 +1,6 @@
 """MXNet and NNVM model zoo."""
 from __future__ import absolute_import
-from . import mlp, resnet, vgg
+from . import mlp, resnet, vgg, dqn, dcgan
 import nnvm.testing
 
 __all__ = ['mx_mlp', 'nnvm_mlp', 'mx_resnet', 'nnvm_resnet', 'mx_vgg', 'nnvm_vgg']
@@ -26,3 +26,11 @@
     mx_vgg[num_layer] = vgg.get_symbol(_num_class, num_layer)
     nnvm_vgg[num_layer] = nnvm.testing.vgg.get_workload(
         1, _num_class, num_layers=num_layer)[0]
+
+# dqn
+mx_dqn = dqn.get_symbol()
+nnvm_dqn = nnvm.testing.dqn.get_workload(1)[0]
+
+# dcgan generator
+mx_dcgan = dcgan.get_symbol()
+nnvm_dcgan = nnvm.testing.dcgan.get_workload(1)[0]
diff --git a/nnvm/tests/python/frontend/mxnet/model_zoo/dcgan.py b/nnvm/tests/python/frontend/mxnet/model_zoo/dcgan.py
new file mode 100644
index 000000000000..98133d369b13
--- /dev/null
+++ b/nnvm/tests/python/frontend/mxnet/model_zoo/dcgan.py
@@ -0,0 +1,63 @@
+# pylint: disable=unused-argument
+"""
+The MXNet symbol of DCGAN generator
+
+Adopted from:
+https://github.com/tqchen/mxnet-gan/blob/master/mxgan/generator.py
+
+Reference:
+Radford, Alec, Luke Metz, and Soumith Chintala.
+"Unsupervised representation learning with deep convolutional generative adversarial networks."
+arXiv preprint arXiv:1511.06434 (2015).
+"""
+
+import mxnet as mx
+
+def deconv2d(data, ishape, oshape, kshape, name, stride=(2, 2)):
+    """a deconv layer that enlarges the feature map"""
+    target_shape = (oshape[-2], oshape[-1])
+    pad_y = (kshape[0] - 1) // 2
+    pad_x = (kshape[1] - 1) // 2
+    adj_y = (target_shape[0] + 2 * pad_y - kshape[0]) % stride[0]
+    adj_x = (target_shape[1] + 2 * pad_x - kshape[1]) % stride[1]
+
+    net = mx.sym.Deconvolution(data,
+                               kernel=kshape,
+                               stride=stride,
+                               pad=(pad_y, pad_x),
+                               adj=(adj_y, adj_x),
+                               num_filter=oshape[0],
+                               no_bias=True,
+                               name=name)
+    return net
+
+def deconv2d_bn_relu(data, prefix, **kwargs):
+    """a block of deconv + batch norm + relu"""
+    eps = 1e-5 + 1e-12
+
+    net = deconv2d(data, name="%s_deconv" % prefix, **kwargs)
+    net = mx.sym.BatchNorm(net, eps=eps, name="%s_bn" % prefix)
+    net = mx.sym.Activation(net, name="%s_act" % prefix, act_type='relu')
+    return net
+
+def get_symbol(oshape=(3, 32, 32), ngf=128, code=None):
+    """get symbol of dcgan generator"""
+    assert oshape[-1] == 32, "Only support 32x32 image"
+    assert oshape[-2] == 32, "Only support 32x32 image"
+
+    code = mx.sym.Variable("data") if code is None else code
+    net = mx.sym.FullyConnected(code, name="g1", num_hidden=4*4*ngf*4, no_bias=True, flatten=False)
+    net = mx.sym.Activation(net, act_type='relu')
+    # 4 x 4
+    net = mx.sym.reshape(net, shape=(-1, ngf * 4, 4, 4))
+    # 8 x 8
+    net = deconv2d_bn_relu(
+        net, ishape=(ngf * 4, 4, 4), oshape=(ngf * 2, 8, 8), kshape=(4, 4), prefix="g2")
+    # 16x16
+    net = deconv2d_bn_relu(
+        net, ishape=(ngf * 2, 8, 8), oshape=(ngf, 16, 16), kshape=(4, 4), prefix="g3")
+    # 32x32
+    net = deconv2d(
+        net, ishape=(ngf, 16, 16), oshape=oshape[-3:], kshape=(4, 4), name="g4_deconv")
+    net = mx.sym.Activation(net, act_type='tanh')
+    return net
diff --git a/nnvm/tests/python/frontend/mxnet/model_zoo/dqn.py b/nnvm/tests/python/frontend/mxnet/model_zoo/dqn.py
new file mode 100644
index 000000000000..e037511efdf2
--- /dev/null
+++ b/nnvm/tests/python/frontend/mxnet/model_zoo/dqn.py
@@ -0,0 +1,27 @@
+"""
+The mxnet symbol of Nature DQN
+
+Reference:
+Mnih, Volodymyr, et al.
+"Human-level control through deep reinforcement learning."
+Nature 518.7540 (2015): 529.
+"""
+
+import mxnet as mx
+
+def get_symbol(num_action=18):
+    data = mx.sym.Variable(name='data')
+    net = mx.sym.Convolution(data, kernel=(8, 8), stride=(4, 4),
+                             num_filter=32, name='conv1')
+    net = mx.sym.Activation(net, act_type='relu', name='relu1')
+    net = mx.sym.Convolution(net, kernel=(4, 4), stride=(2, 2),
+                             num_filter=64, name='conv2')
+    net = mx.sym.Activation(net, act_type='relu', name='relu2')
+    net = mx.sym.Convolution(net, kernel=(3, 3), stride=(1, 1),
+                             num_filter=64, name='conv3')
+    net = mx.sym.Activation(net, act_type='relu', name='relu3')
+    net = mx.sym.FullyConnected(net, num_hidden=512, name='fc4')
+    net = mx.sym.Activation(net, act_type='relu', name='relu4')
+    net = mx.sym.FullyConnected(net, num_hidden=num_action, name='fc5', flatten=False)
+
+    return net
diff --git a/nnvm/tests/python/frontend/mxnet/test_graph.py b/nnvm/tests/python/frontend/mxnet/test_graph.py
index bbbc42db1917..cd14f9c7ca98 100644
--- a/nnvm/tests/python/frontend/mxnet/test_graph.py
+++ b/nnvm/tests/python/frontend/mxnet/test_graph.py
@@ -32,6 +32,18 @@ def test_resnet():
         nnvm_sym = model_zoo.nnvm_resnet[n]
         compare_graph(from_mx_sym, nnvm_sym)
 
+def test_dqn():
+    mx_sym = model_zoo.mx_dqn
+    from_mx_sym, _ = nnvm.frontend.from_mxnet(mx_sym)
+    nnvm_sym = model_zoo.nnvm_dqn
+    compare_graph(from_mx_sym, nnvm_sym)
+
+def test_dcgan():
+    mx_sym = model_zoo.mx_dcgan
+    from_mx_sym, _ = nnvm.frontend.from_mxnet(mx_sym)
+    nnvm_sym = model_zoo.nnvm_dcgan
+    compare_graph(from_mx_sym, nnvm_sym)
+
 def test_multi_outputs():
     def compose(F, **kwargs):
         x = F.sym.Variable('x')
@@ -48,3 +60,5 @@ def compose(F, **kwargs):
     test_vgg()
     test_resnet()
     test_multi_outputs()
+    test_dqn()
+    test_dcgan()

From 90ecefdb98939e3ecb743cb77f188c943853b339 Mon Sep 17 00:00:00 2001
From: "Daniel M. Weeks" <dan@danweeks.net>
Date: Tue, 19 Jun 2018 15:58:28 -0400
Subject: [PATCH 734/949] Check common subdirs for vulkan/spirv headers (#1298)

---
 cmake/util/FindVulkan.cmake            | 5 +++++
 src/codegen/spirv/build_vulkan.cc      | 2 +-
 src/codegen/spirv/intrin_rule_spirv.cc | 2 +-
 src/codegen/spirv/ir_builder.h         | 2 +-
 4 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/cmake/util/FindVulkan.cmake b/cmake/util/FindVulkan.cmake
index 5dfd89891de1..6ca089ad11b1 100644
--- a/cmake/util/FindVulkan.cmake
+++ b/cmake/util/FindVulkan.cmake
@@ -46,5 +46,10 @@ macro(find_vulkan use_vulkan)
     get_filename_component(VULKAN_LIBRARY_PATH ${Vulkan_LIBRARY} DIRECTORY)
     find_library(Vulkan_SPIRV_TOOLS_LIBRARY SPIRV-Tools
       ${VULKAN_LIBRARY_PATH}/spirv-tools)
+
+    find_path(_libspirv libspirv.h HINTS ${Vulkan_INCLUDE_DIRS} PATH_SUFFIXES vulkan spirv-tools)
+    find_path(_spirv spirv.hpp HINTS ${Vulkan_INCLUDE_DIRS} PATH_SUFFIXES vulkan spirv/unified1)
+    find_path(_glsl_std GLSL.std.450.h HINTS ${Vulkan_INCLUDE_DIRS} PATH_SUFFIXES vulkan spirv/unified1)
+    list(APPEND Vulkan_INCLUDE_DIRS ${_libspirv} ${_spirv} ${_glsl_std})
   endif(Vulkan_FOUND)
 endmacro(find_vulkan)
diff --git a/src/codegen/spirv/build_vulkan.cc b/src/codegen/spirv/build_vulkan.cc
index 2d4b35daa006..3cd1b56cda43 100644
--- a/src/codegen/spirv/build_vulkan.cc
+++ b/src/codegen/spirv/build_vulkan.cc
@@ -4,7 +4,7 @@
  * \brief Build SPIRV block
  */
 // Use libspirv for parsing and validating code.
-#include <vulkan/libspirv.h>
+#include <libspirv.h>
 #include <dmlc/memory_io.h>
 #include <tvm/ir_pass.h>
 
diff --git a/src/codegen/spirv/intrin_rule_spirv.cc b/src/codegen/spirv/intrin_rule_spirv.cc
index 1f9c56c561f8..29d8de9df22d 100644
--- a/src/codegen/spirv/intrin_rule_spirv.cc
+++ b/src/codegen/spirv/intrin_rule_spirv.cc
@@ -4,7 +4,7 @@
  */
 #include <tvm/packed_func_ext.h>
 #include <tvm/ir.h>
-#include <vulkan/GLSL.std.450.h>
+#include <GLSL.std.450.h>
 
 namespace tvm {
 namespace codegen {
diff --git a/src/codegen/spirv/ir_builder.h b/src/codegen/spirv/ir_builder.h
index e652a0068278..fc2d5bc68b6f 100644
--- a/src/codegen/spirv/ir_builder.h
+++ b/src/codegen/spirv/ir_builder.h
@@ -15,7 +15,7 @@
 #include <string>
 #include <map>
 
-#include <vulkan/spirv.hpp>
+#include <spirv.hpp>
 
 namespace tvm {
 namespace codegen {

From c432bced07a5d7f1a50cad113ae7ac7717576f43 Mon Sep 17 00:00:00 2001
From: Jared Roesch <roeschinc@gmail.com>
Date: Wed, 20 Jun 2018 12:42:29 -0400
Subject: [PATCH 735/949] Add MyPy to lint (#1301)

---
 tests/ci_build/Dockerfile.lint | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/ci_build/Dockerfile.lint b/tests/ci_build/Dockerfile.lint
index be869d8e9faf..902c28fde9ee 100644
--- a/tests/ci_build/Dockerfile.lint
+++ b/tests/ci_build/Dockerfile.lint
@@ -5,4 +5,4 @@ RUN apt-get update && apt-get install -y sudo wget
 COPY install/ubuntu_install_python.sh /install/ubuntu_install_python.sh
 RUN bash /install/ubuntu_install_python.sh
 RUN apt-get install -y doxygen graphviz
-RUN pip3 install cpplint pylint
+RUN pip3 install cpplint pylint mypy

From a6ce6c579233054d0ce10120b7b901f59d5d33fb Mon Sep 17 00:00:00 2001
From: Tatsuya Nishiyama <nishiyama.tatsuya0@gmail.com>
Date: Thu, 21 Jun 2018 01:58:07 +0900
Subject: [PATCH 736/949] [FRONTEND][MXNET] Add squeeze_axis support to split
 operator (#1288)

---
 nnvm/python/nnvm/frontend/mxnet.py               | 11 +++++++----
 nnvm/tests/python/frontend/mxnet/test_forward.py | 12 ++++++++++++
 2 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/mxnet.py b/nnvm/python/nnvm/frontend/mxnet.py
index deae3112bf5f..2f190ab71f4d 100644
--- a/nnvm/python/nnvm/frontend/mxnet.py
+++ b/nnvm/python/nnvm/frontend/mxnet.py
@@ -188,12 +188,15 @@ def _reshape(inputs, attrs):
     return _get_nnvm_op(op_name)(*inputs, **new_attrs)
 
 def _split(inputs, attrs):
-    if _parse_bool_str(attrs, 'squeeze_axis'):
-        _raise_not_supported('squeeze_axis', 'split')
     op_name, new_attrs = 'split', {}
+    axis = attrs.get('axis', 1)
     new_attrs['indices_or_sections'] = _required_attr(attrs, 'num_outputs')
-    new_attrs['axis'] = attrs.get('axis', 1)
-    return _get_nnvm_op(op_name)(*inputs, **new_attrs)
+    new_attrs['axis'] = axis
+    outputs = _get_nnvm_op(op_name)(*inputs, **new_attrs)
+    if _parse_bool_str(attrs, 'squeeze_axis'):
+        squeeze_attrs = {'axis': axis}
+        outputs = _sym.Group([_get_nnvm_op('squeeze')(o, **squeeze_attrs) for o in outputs])
+    return outputs
 
 def _softmax_activation(inputs, attrs):
     op_name, new_attrs = 'softmax', {}
diff --git a/nnvm/tests/python/frontend/mxnet/test_forward.py b/nnvm/tests/python/frontend/mxnet/test_forward.py
index d0930d75cce1..e6b6dffa17b0 100644
--- a/nnvm/tests/python/frontend/mxnet/test_forward.py
+++ b/nnvm/tests/python/frontend/mxnet/test_forward.py
@@ -126,6 +126,16 @@ def test_forward_clip():
     mx_sym = mx.sym.clip(data, a_min=0, a_max=1)
     verify_mxnet_frontend_impl(mx_sym, (1, 3, 100, 100), (1, 6, 100, 100))
 
+def test_forward_split():
+    data = mx.sym.var('data')
+    mx_sym = mx.sym.split(data, axis=1, num_outputs=4, squeeze_axis=False)
+    verify_mxnet_frontend_impl(mx_sym, (1, 4, 2, 1), (1, 1, 2, 1))
+
+def test_forward_split_squeeze():
+    data = mx.sym.var('data')
+    mx_sym = mx.sym.split(data, axis=1, num_outputs=4, squeeze_axis=True)
+    verify_mxnet_frontend_impl(mx_sym, (1, 4, 2, 1), (1, 2, 1))
+
 if __name__ == '__main__':
     test_forward_mlp()
     test_forward_vgg()
@@ -136,3 +146,5 @@ def test_forward_clip():
     test_forward_softrelu()
     test_forward_fc_flatten()
     test_forward_clip()
+    test_forward_split()
+    test_forward_split_squeeze()

From 3611a5a57438781d7ccdbfdafde86225d820981f Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 20 Jun 2018 14:10:00 -0700
Subject: [PATCH 737/949] [BUILD] Fix reflection build for gcc-8 (#1304)

---
 dmlc-core               |  2 +-
 src/lang/reflection.cc  | 38 ++++++++++++++++++++------------------
 src/pass/unroll_loop.cc |  4 ++--
 3 files changed, 23 insertions(+), 21 deletions(-)

diff --git a/dmlc-core b/dmlc-core
index 9b3f9753ae81..e864aa6757cd 160000
--- a/dmlc-core
+++ b/dmlc-core
@@ -1 +1 @@
-Subproject commit 9b3f9753ae81d657743c555e0cacc4e43f0bed2d
+Subproject commit e864aa6757cdbe78b1296fe5231fd3050b7802c3
diff --git a/src/lang/reflection.cc b/src/lang/reflection.cc
index ba580e75e124..d0d8570067eb 100644
--- a/src/lang/reflection.cc
+++ b/src/lang/reflection.cc
@@ -375,41 +375,43 @@ class NodeAttrSetter : public AttrVisitor {
   std::string type_key;
   std::unordered_map<std::string, runtime::TVMArgValue> attrs;
 
-  template<typename T>
-  void SetValue(const char* key, T* value) {
-    auto it = attrs.find(key);
-    if (it == attrs.end()) {
-      LOG(FATAL) << type_key << ": require field " << key;
-    }
-    *value = it->second.operator T();
-    attrs.erase(it);
-  }
   void Visit(const char* key, double* value) final {
-    SetValue(key, value);
+    *value = GetAttr(key).operator double();
   }
   void Visit(const char* key, int64_t* value) final {
-    SetValue(key, value);
+    *value = GetAttr(key).operator int64_t();
   }
   void Visit(const char* key, uint64_t* value) final {
-    SetValue(key, value);
+    *value = GetAttr(key).operator uint64_t();
   }
   void Visit(const char* key, int* value) final {
-    SetValue(key, value);
+    *value = GetAttr(key).operator int();
   }
   void Visit(const char* key, bool* value) final {
-    SetValue(key, value);
+    *value = GetAttr(key).operator bool();
   }
   void Visit(const char* key, std::string* value) final {
-    SetValue(key, value);
+    *value = GetAttr(key).operator std::string();
   }
   void Visit(const char* key, void** value) final {
-    SetValue(key, value);
+    *value = GetAttr(key).operator void*();
   }
   void Visit(const char* key, Type* value) final {
-    SetValue(key, value);
+    *value = GetAttr(key).operator Type();
   }
   void Visit(const char* key, NodeRef* value) final {
-    SetValue(key, value);
+    *value = GetAttr(key).operator NodeRef();
+  }
+
+ private:
+  runtime::TVMArgValue GetAttr(const char* key) {
+    auto it = attrs.find(key);
+    if (it == attrs.end()) {
+      LOG(FATAL) << type_key << ": require field " << key;
+    }
+    runtime::TVMArgValue v = it->second;
+    attrs.erase(it);
+    return v;
   }
 };
 
diff --git a/src/pass/unroll_loop.cc b/src/pass/unroll_loop.cc
index 09844cb75531..6c0ac517553f 100644
--- a/src/pass/unroll_loop.cc
+++ b/src/pass/unroll_loop.cc
@@ -29,14 +29,14 @@ class LoopUnroller : public IRMutator {
 
   Stmt Mutate_(const AttrStmt* op, const Stmt& stmt) final {
     if (op->attr_key == "pragma_auto_unroll_max_step") {
-      int value;
+      int value = 0;
       CHECK(arith::GetConstInt(op->value, &value));
       std::swap(value, auto_max_step_);
       Stmt ret = this->Mutate(op->body);
       std::swap(value, auto_max_step_);
       return ret;
     } else if (op->attr_key == "pragma_unroll_explicit") {
-      int value;
+      int value = 0;
       CHECK(arith::GetConstInt(op->value, &value));
       bool explicit_unroll = value;
       std::swap(explicit_unroll, explicit_unroll_);

From 239b63d09d88107abfdc3098893e4828336c092c Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 20 Jun 2018 22:11:45 -0700
Subject: [PATCH 738/949] [MAINTAINER] add srkreddy1238 as reviewer (#1305)

---
 CONTRIBUTORS.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 26de78f4ac25..2460087eb0ac 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -22,6 +22,7 @@ See the [community structure document](http://docs.tvm.ai/contribute/community.h
 ## Reviewers
 - [Masahiro Masuda](https://github.com/masahi)
 - [Pariksheet Pinjari](https://github.com/PariksheetPinjari909)
+- [Siva](https://github.com/srkreddy1238)
 - [Eddie Yan](https://github.com/eqy)
 - [Joshua Z. Zhang](https://github.com/zhreshold)
 - [Lianmin Zheng](https://github.com/merrymercy)

From 90f714b4afd246827f6fc881d4c966fd0a89abb4 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 21 Jun 2018 13:23:54 -0700
Subject: [PATCH 739/949] [MATH][TOPI][NNVM] introduce trunc, round (#1310)

---
 docs/api/python/intrin.rst                    |  8 ++++
 docs/api/python/topi.rst                      |  8 ++++
 docs/nnvm_top.rst                             |  8 ++++
 include/tvm/ir_operator.h                     |  2 +
 nnvm/python/nnvm/top/tensor.py                | 16 +++++++
 nnvm/src/top/tensor/elemwise.cc               | 48 +++++++++++++++++++
 nnvm/tests/python/compiler/test_top_level3.py | 36 ++++++++++++++
 python/tvm/intrin.py                          | 36 ++++++++++++++
 src/codegen/intrin_rule_cuda.cc               |  6 +++
 src/codegen/intrin_rule_metal.cc              |  6 +++
 src/codegen/intrin_rule_opencl.cc             |  6 +++
 src/codegen/llvm/intrin_rule_llvm.cc          |  6 +++
 src/codegen/llvm/intrin_rule_rocm.cc          |  6 +++
 src/codegen/spirv/intrin_rule_spirv.cc        |  6 +++
 topi/include/topi/elemwise.h                  |  2 +
 topi/python/topi/math.py                      | 35 ++++++++++++++
 topi/tests/python/test_topi_math.py           |  4 +-
 17 files changed, 238 insertions(+), 1 deletion(-)
 create mode 100644 nnvm/tests/python/compiler/test_top_level3.py

diff --git a/docs/api/python/intrin.rst b/docs/api/python/intrin.rst
index 71ecaaa2ce8d..a455f20a5874 100644
--- a/docs/api/python/intrin.rst
+++ b/docs/api/python/intrin.rst
@@ -10,6 +10,10 @@ tvm.intrin
    tvm.register_intrin_rule
    tvm.exp
    tvm.log
+   tvm.floor
+   tvm.ceil
+   tvm.trunc
+   tvm.round
 
 
 .. autofunction:: tvm.call_packed
@@ -18,3 +22,7 @@ tvm.intrin
 .. autofunction:: tvm.register_intrin_rule
 .. autofunction:: tvm.exp
 .. autofunction:: tvm.log
+.. autofunction:: tvm.floor
+.. autofunction:: tvm.ceil
+.. autofunction:: tvm.trunc
+.. autofunction:: tvm.round
diff --git a/docs/api/python/topi.rst b/docs/api/python/topi.rst
index 3216c5168997..136ec7e4777c 100644
--- a/docs/api/python/topi.rst
+++ b/docs/api/python/topi.rst
@@ -9,6 +9,10 @@ List of operators
 
    topi.identity
    topi.negative
+   topi.floor
+   topi.ceil
+   topi.trunc
+   topi.round
    topi.exp
    topi.tanh
    topi.log
@@ -68,6 +72,10 @@ topi
 ~~~~
 .. autofunction:: topi.negative
 .. autofunction:: topi.identity
+.. autofunction:: topi.floor
+.. autofunction:: topi.ceil
+.. autofunction:: topi.trunc
+.. autofunction:: topi.round
 .. autofunction:: topi.exp
 .. autofunction:: topi.tanh
 .. autofunction:: topi.log
diff --git a/docs/nnvm_top.rst b/docs/nnvm_top.rst
index 3dccaddff8cd..5f3fd9fa1b0b 100644
--- a/docs/nnvm_top.rst
+++ b/docs/nnvm_top.rst
@@ -75,6 +75,10 @@ This level enables typical convnet models.
    nnvm.symbol.reshape
    nnvm.symbol.copy
    nnvm.symbol.negative
+   nnvm.symbol.floor
+   nnvm.symbol.ceil
+   nnvm.symbol.round
+   nnvm.symbol.trunc
    nnvm.symbol.leaky_relu
    nnvm.symbol.__add_scalar__
    nnvm.symbol.__sub_scalar__
@@ -147,6 +151,10 @@ Detailed Definitions
 .. autofunction:: nnvm.symbol.reshape
 .. autofunction:: nnvm.symbol.copy
 .. autofunction:: nnvm.symbol.negative
+.. autofunction:: nnvm.symbol.floor
+.. autofunction:: nnvm.symbol.ceil
+.. autofunction:: nnvm.symbol.round
+.. autofunction:: nnvm.symbol.trunc
 .. autofunction:: nnvm.symbol.leaky_relu
 .. autofunction:: nnvm.symbol.__add_scalar__
 .. autofunction:: nnvm.symbol.__sub_scalar__
diff --git a/include/tvm/ir_operator.h b/include/tvm/ir_operator.h
index 9d72b655c484..1324255037cb 100644
--- a/include/tvm/ir_operator.h
+++ b/include/tvm/ir_operator.h
@@ -55,6 +55,8 @@ TVM_DECLARE_INTRIN_UNARY(sqrt);
 TVM_DECLARE_INTRIN_UNARY(log);
 TVM_DECLARE_INTRIN_UNARY(floor);
 TVM_DECLARE_INTRIN_UNARY(ceil);
+TVM_DECLARE_INTRIN_UNARY(round);
+TVM_DECLARE_INTRIN_UNARY(trunc);
 
 inline Expr pow(Expr x, Expr y) {
   return ir::Call::make(x.type(), "pow", { x, y }, ir::Call::PureIntrinsic);
diff --git a/nnvm/python/nnvm/top/tensor.py b/nnvm/python/nnvm/top/tensor.py
index 462a0ec83bf4..da0db9b6cb6e 100644
--- a/nnvm/python/nnvm/top/tensor.py
+++ b/nnvm/python/nnvm/top/tensor.py
@@ -61,6 +61,22 @@ def compute_cast(attrs, inputs, _):
 reg.register_pattern("cast", OpPattern.ELEMWISE)
 reg.register_schedule("cast", _fschedule_broadcast)
 
+# floor
+reg.register_pattern("floor", OpPattern.ELEMWISE)
+reg.register_schedule("floor", _fschedule_broadcast)
+
+# ceil
+reg.register_pattern("ceil", OpPattern.ELEMWISE)
+reg.register_schedule("ceil", _fschedule_broadcast)
+
+# round
+reg.register_pattern("round", OpPattern.ELEMWISE)
+reg.register_schedule("round", _fschedule_broadcast)
+
+# trunc
+reg.register_pattern("trunc", OpPattern.ELEMWISE)
+reg.register_schedule("trunc", _fschedule_broadcast)
+
 # exp
 reg.register_pattern("exp", OpPattern.ELEMWISE)
 reg.register_schedule("exp", _fschedule_broadcast)
diff --git a/nnvm/src/top/tensor/elemwise.cc b/nnvm/src/top/tensor/elemwise.cc
index ee0d6281c65d..27e19e323bb8 100644
--- a/nnvm/src/top/tensor/elemwise.cc
+++ b/nnvm/src/top/tensor/elemwise.cc
@@ -31,6 +31,54 @@ Used to produce invalide node during optimization.
 .set_num_outputs(1)
 .set_num_inputs(0);
 
+// floor
+NNVM_REGISTER_ELEMWISE_UNARY_OP(floor)
+.describe(R"code(Take floor input array, computed element-wise.
+)code" NNVM_ADD_FILELINE)
+.set_support_level(3)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+      return Array<Tensor>{ topi::floor(inputs[0]) };
+});
+
+// ceil
+NNVM_REGISTER_ELEMWISE_UNARY_OP(ceil)
+.describe(R"code(Take ceil input array, computed element-wise.
+)code" NNVM_ADD_FILELINE)
+.set_support_level(3)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+      return Array<Tensor>{ topi::ceil(inputs[0]) };
+});
+
+// trunc
+NNVM_REGISTER_ELEMWISE_UNARY_OP(trunc)
+.describe(R"code(Take truncated value of the input, element-wise.
+)code" NNVM_ADD_FILELINE)
+.set_support_level(3)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+      return Array<Tensor>{ topi::trunc(inputs[0]) };
+});
+
+// round
+NNVM_REGISTER_ELEMWISE_UNARY_OP(round)
+.describe(R"code(Round elements of the input to nearest integer.
+)code" NNVM_ADD_FILELINE)
+.set_support_level(3)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+      return Array<Tensor>{ topi::round(inputs[0]) };
+});
+
 // sigmoid
 NNVM_REGISTER_ELEMWISE_UNARY_OP(sigmoid)
 .describe(R"code(Computes sigmoid.
diff --git a/nnvm/tests/python/compiler/test_top_level3.py b/nnvm/tests/python/compiler/test_top_level3.py
new file mode 100644
index 000000000000..27a99bfb530f
--- /dev/null
+++ b/nnvm/tests/python/compiler/test_top_level3.py
@@ -0,0 +1,36 @@
+import numpy as np
+import tvm
+from tvm.contrib import graph_runtime
+import topi.testing
+import nnvm.symbol as sym
+import nnvm.compiler
+from nnvm.testing.config import ctx_list
+from test_top_level1 import helper
+
+def check_map(symfunc, np_func, np_backward=None):
+    x = sym.Variable("x")
+    y = symfunc(x)
+    dtype = "float32"
+    dshape = (1, 3, 32, 32)
+    inputs = [('x', dshape, x)]
+    helper(y, inputs, dtype, lambda x: np_func(x), np_backward)
+
+
+def test_floor():
+    check_map(sym.floor, np.floor)
+
+def test_ceil():
+    check_map(sym.ceil, np.ceil)
+
+def test_trunc():
+    check_map(sym.trunc, np.trunc)
+
+def test_round():
+    check_map(sym.round, np.round)
+
+
+if __name__ == "__main__":
+    test_floor()
+    test_ceil()
+    test_round()
+    test_trunc()
diff --git a/python/tvm/intrin.py b/python/tvm/intrin.py
index 9be7502b26ee..fe4fb8faa9c1 100644
--- a/python/tvm/intrin.py
+++ b/python/tvm/intrin.py
@@ -1,4 +1,5 @@
 """Expression Intrinsics and math functions in TVM."""
+# pylint: disable=redefined-builtin
 from __future__ import absolute_import as _abs
 
 from ._ffi.function import register_func as _register_func
@@ -265,6 +266,41 @@ def ceil(x):
     return call_pure_intrin(x.dtype, "ceil", x)
 
 
+def trunc(x):
+    """Get truncated value of the input.
+
+    The truncated value of the scalar x is the
+    nearest integer i which is closer to zero than x is.
+
+    Parameters
+    ----------
+    x : Expr
+        Input argument.
+
+    Returns
+    -------
+    y : Expr
+        The result.
+    """
+    return call_pure_intrin(x.dtype, "trunc", x)
+
+
+def round(x):
+    """Round elements of the array to the nearest integer.
+
+    Parameters
+    ----------
+    x : Expr
+        Input argument.
+
+    Returns
+    -------
+    y : Expr
+        The result.
+    """
+    return call_pure_intrin(x.dtype, "round", x)
+
+
 def power(x, y):
     """x power y
 
diff --git a/src/codegen/intrin_rule_cuda.cc b/src/codegen/intrin_rule_cuda.cc
index dccbcf8ca232..d291463bce9f 100644
--- a/src/codegen/intrin_rule_cuda.cc
+++ b/src/codegen/intrin_rule_cuda.cc
@@ -61,6 +61,12 @@ TVM_REGISTER_GLOBAL("tvm.intrin.rule.cuda.floor")
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.cuda.ceil")
 .set_body(DispatchExtern<CUDAMath>);
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.cuda.trunc")
+.set_body(DispatchExtern<CUDAMath>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.cuda.round")
+.set_body(DispatchExtern<CUDAMath>);
+
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.cuda.exp")
 .set_body(DispatchExtern<CUDAFastMath>);
 
diff --git a/src/codegen/intrin_rule_metal.cc b/src/codegen/intrin_rule_metal.cc
index 3de5cafc69c6..659a82df0757 100644
--- a/src/codegen/intrin_rule_metal.cc
+++ b/src/codegen/intrin_rule_metal.cc
@@ -15,6 +15,12 @@ TVM_REGISTER_GLOBAL("tvm.intrin.rule.metal.floor")
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.metal.ceil")
 .set_body(DispatchExtern<Direct>);
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.metal.trunc")
+.set_body(DispatchExtern<Direct>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.metal.round")
+.set_body(DispatchExtern<Direct>);
+
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.metal.exp")
 .set_body(DispatchExtern<Direct>);
 
diff --git a/src/codegen/intrin_rule_opencl.cc b/src/codegen/intrin_rule_opencl.cc
index cdb432e5462f..127fe47d5b75 100644
--- a/src/codegen/intrin_rule_opencl.cc
+++ b/src/codegen/intrin_rule_opencl.cc
@@ -15,6 +15,12 @@ TVM_REGISTER_GLOBAL("tvm.intrin.rule.opencl.floor")
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.opencl.ceil")
 .set_body(DispatchExtern<Direct>);
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.opencl.trunc")
+.set_body(DispatchExtern<Direct>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.opencl.round")
+.set_body(DispatchExtern<Direct>);
+
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.opencl.exp")
 .set_body(DispatchExtern<Direct>);
 
diff --git a/src/codegen/llvm/intrin_rule_llvm.cc b/src/codegen/llvm/intrin_rule_llvm.cc
index e5eff1f67ff5..0d6f8d07b79f 100644
--- a/src/codegen/llvm/intrin_rule_llvm.cc
+++ b/src/codegen/llvm/intrin_rule_llvm.cc
@@ -31,6 +31,12 @@ TVM_REGISTER_GLOBAL("tvm.intrin.rule.llvm.floor")
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.llvm.ceil")
 .set_body(DispatchLLVMPureIntrin<::llvm::Intrinsic::ceil, 1>);
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.llvm.trunc")
+.set_body(DispatchLLVMPureIntrin<::llvm::Intrinsic::trunc, 1>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.llvm.round")
+.set_body(DispatchLLVMPureIntrin<::llvm::Intrinsic::round, 1>);
+
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.llvm.tanh")
 .set_body([](const TVMArgs& targs, TVMRetValue* rv) {
   Expr e = targs[0];
diff --git a/src/codegen/llvm/intrin_rule_rocm.cc b/src/codegen/llvm/intrin_rule_rocm.cc
index 4546ee4f039b..dc84b866d616 100644
--- a/src/codegen/llvm/intrin_rule_rocm.cc
+++ b/src/codegen/llvm/intrin_rule_rocm.cc
@@ -32,6 +32,12 @@ TVM_REGISTER_GLOBAL("tvm.intrin.rule.rocm.floor")
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.rocm.ceil")
 .set_body(DispatchLLVMPureIntrin<::llvm::Intrinsic::ceil, 1>);
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.rocm.round")
+.set_body(DispatchLLVMPureIntrin<::llvm::Intrinsic::round, 1>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.rocm.trunc")
+.set_body(DispatchLLVMPureIntrin<::llvm::Intrinsic::trunc, 1>);
+
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.rocm.exp")
 .set_body(DispatchExternOCML);
 
diff --git a/src/codegen/spirv/intrin_rule_spirv.cc b/src/codegen/spirv/intrin_rule_spirv.cc
index 29d8de9df22d..cf9c446ea3f8 100644
--- a/src/codegen/spirv/intrin_rule_spirv.cc
+++ b/src/codegen/spirv/intrin_rule_spirv.cc
@@ -35,6 +35,12 @@ TVM_REGISTER_GLOBAL("tvm.intrin.rule.vulkan.floor")
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.vulkan.ceil")
 .set_body(DispatchGLSLPureIntrin<GLSLstd450Ceil>);
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.vulkan.round")
+.set_body(DispatchGLSLPureIntrin<GLSLstd450Round>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.vulkan.trunc")
+.set_body(DispatchGLSLPureIntrin<GLSLstd450Trunc>);
+
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.vulkan.exp")
 .set_body(DispatchGLSLPureIntrin<GLSLstd450Exp>);
 
diff --git a/topi/include/topi/elemwise.h b/topi/include/topi/elemwise.h
index 7500ce0c66b0..cef6ea9d629b 100644
--- a/topi/include/topi/elemwise.h
+++ b/topi/include/topi/elemwise.h
@@ -31,6 +31,8 @@ TOPI_DECLARE_UNARY_OP(sqrt);
 TOPI_DECLARE_UNARY_OP(log);
 TOPI_DECLARE_UNARY_OP(floor);
 TOPI_DECLARE_UNARY_OP(ceil);
+TOPI_DECLARE_UNARY_OP(round);
+TOPI_DECLARE_UNARY_OP(trunc);
 
 /*!
 * \brief Creates an operation that returns identity of a given tensor
diff --git a/topi/python/topi/math.py b/topi/python/topi/math.py
index 588c306136d9..2809c79e518a 100644
--- a/topi/python/topi/math.py
+++ b/topi/python/topi/math.py
@@ -1,4 +1,5 @@
 """Elementwise operators"""
+# pylint: disable=redefined-builtin
 from __future__ import absolute_import as _abs
 import tvm
 from . import tag
@@ -107,6 +108,40 @@ def ceil(x):
     return tvm.compute(x.shape, lambda *i: tvm.ceil(x(*i)))
 
 
+@tvm.tag_scope(tag=tag.ELEMWISE)
+def trunc(x):
+    """Take truncated value of the input of x, element-wise.
+
+    Parameters
+    ----------
+    x : tvm.Tensor
+        Input argument.
+
+    Returns
+    -------
+    y : tvm.Tensor
+        The result.
+    """
+    return tvm.compute(x.shape, lambda *i: tvm.trunc(x(*i)))
+
+
+@tvm.tag_scope(tag=tag.ELEMWISE)
+def round(x):
+    """Round elements of x to nearest integer.
+
+    Parameters
+    ----------
+    x : tvm.Tensor
+        Input argument.
+
+    Returns
+    -------
+    y : tvm.Tensor
+        The result.
+    """
+    return tvm.compute(x.shape, lambda *i: tvm.round(x(*i)))
+
+
 @tvm.tag_scope(tag=tag.ELEMWISE)
 def log(x):
     """Take logarithm of input x.
diff --git a/topi/tests/python/test_topi_math.py b/topi/tests/python/test_topi_math.py
index b9305e6e6fdf..dd8f3fce8a14 100644
--- a/topi/tests/python/test_topi_math.py
+++ b/topi/tests/python/test_topi_math.py
@@ -33,9 +33,9 @@ def check_device(device):
             print("Running on target: %s" % device)
             with tvm.target.create(device):
                 s = topi.generic.schedule_injective(B)
+            foo = tvm.build(s, [A, B], device, name=name)
             a = tvm.nd.array(a_np, ctx)
             b = tvm.nd.array(np.zeros_like(b_np), ctx)
-            foo = tvm.build(s, [A, B], device, name=name)
             foo(a, b)
             np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5, atol=1e-5)
 
@@ -45,6 +45,8 @@ def check_device(device):
 
     test_apply(topi.floor, "floor", np.floor, -100, 100)
     test_apply(topi.ceil, "ceil", np.ceil, -100, 100)
+    test_apply(topi.trunc, "trunc", np.trunc, -100, 100)
+    test_apply(topi.round, "round", np.round, -100, 100)
     test_apply(topi.exp, "exp", np.exp, -1, 1)
     test_apply(topi.tanh, "tanh", np.tanh, -10, 10)
     test_apply(topi.sigmoid, "sigmoid", lambda x:1/(1+np.exp(-x)), -1, 1)

From da2ad32eca9e12e6a3359adb8e3819121d3dc1b3 Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@cs.washington.edu>
Date: Thu, 21 Jun 2018 15:39:43 -0700
Subject: [PATCH 740/949] [CONTRIB] TVM download utility based on
 urllib2/urlib.request (#1313)

moving nnvm/testing/download.py to python/tvm/contrib/download.py to be used as a general TVM download utility
---
 .../tvm/contrib}/download.py                  | 48 ++++++++++---------
 tutorials/nnvm/deploy_ssd.py                  |  2 +-
 tutorials/nnvm/from_darknet.py                |  2 +-
 3 files changed, 28 insertions(+), 24 deletions(-)
 rename {nnvm/python/nnvm/testing => python/tvm/contrib}/download.py (61%)

diff --git a/nnvm/python/nnvm/testing/download.py b/python/tvm/contrib/download.py
similarity index 61%
rename from nnvm/python/nnvm/testing/download.py
rename to python/tvm/contrib/download.py
index 849c18bcf0f6..7f761f69b252 100644
--- a/nnvm/python/nnvm/testing/download.py
+++ b/python/tvm/contrib/download.py
@@ -1,4 +1,3 @@
-# pylint: disable=invalid-name, no-member, import-error, no-name-in-module, global-variable-undefined, bare-except
 """Helper utility for downloading"""
 from __future__ import print_function
 from __future__ import absolute_import as _abs
@@ -6,7 +5,6 @@
 import os
 import sys
 import time
-import urllib
 import requests
 
 if sys.version_info >= (3,):
@@ -14,21 +12,6 @@
 else:
     import urllib2
 
-def _download_progress(count, block_size, total_size):
-    """Show the download progress.
-    """
-    global start_time
-    if count == 0:
-        start_time = time.time()
-        return
-    duration = time.time() - start_time
-    progress_size = int(count * block_size)
-    speed = int(progress_size / (1024 * duration))
-    percent = int(count * block_size * 100 / total_size)
-    sys.stdout.write("\r...%d%%, %d MB, %d KB/s, %d seconds passed" %
-                     (percent, progress_size / (1024 * 1024), speed, duration))
-    sys.stdout.flush()
-
 def download(url, path, overwrite=False, size_compare=False):
     """Downloads the file from the internet.
     Set the input options correctly to overwrite or do the size comparison
@@ -62,8 +45,29 @@ def download(url, path, overwrite=False, size_compare=False):
         print('File {} exists, skip.'.format(path))
         return
     print('Downloading from url {} to {}'.format(url, path))
-    try:
-        urllib.request.urlretrieve(url, path, reporthook=_download_progress)
-        print('')
-    except:
-        urllib.urlretrieve(url, path, reporthook=_download_progress)
+
+    # Stateful start time
+    start_time = time.time()
+
+    def _download_progress(count, block_size, total_size):
+        #pylint: disable=unused-argument
+        """Show the download progress.
+        """
+        if count == 0:
+            return
+        duration = time.time() - start_time
+        progress_size = int(count * block_size)
+        speed = int(progress_size / (1024 * duration))
+        percent = min(int(count * block_size * 100 / total_size), 100)
+        sys.stdout.write("\r...%d%%, %d MB, %d KB/s, %d seconds passed" %
+                         (percent, progress_size / (1024 * 1024), speed, duration))
+        sys.stdout.flush()
+
+    if sys.version_info >= (3,):
+        urllib2.urlretrieve(url, path, reporthook=_download_progress)
+        print("")
+    else:
+        f = urllib2.urlopen(url)
+        data = f.read()
+        with open(path, "wb") as code:
+            code.write(data)
diff --git a/tutorials/nnvm/deploy_ssd.py b/tutorials/nnvm/deploy_ssd.py
index b3b5072f28c9..58725a3c92d3 100644
--- a/tutorials/nnvm/deploy_ssd.py
+++ b/tutorials/nnvm/deploy_ssd.py
@@ -16,7 +16,7 @@
 
 from nnvm import compiler
 from nnvm.frontend import from_mxnet
-from nnvm.testing.download import download
+from tvm.contrib.download import download
 from tvm.contrib import graph_runtime
 from mxnet.model import load_checkpoint
 
diff --git a/tutorials/nnvm/from_darknet.py b/tutorials/nnvm/from_darknet.py
index 2cd681b624ad..883026f2af98 100644
--- a/tutorials/nnvm/from_darknet.py
+++ b/tutorials/nnvm/from_darknet.py
@@ -24,7 +24,7 @@
 import os
 
 from ctypes import *
-from nnvm.testing.download import download
+from tvm.contrib.download import download
 from nnvm.testing.darknet import __darknetffi__
 
 ######################################################################

From 2930d8ce471b583053f2b6f08b763bf4563e83dd Mon Sep 17 00:00:00 2001
From: Junru Shao <junrushao1994@users.noreply.github.com>
Date: Thu, 21 Jun 2018 19:25:55 -0700
Subject: [PATCH 741/949] Fix a bug in Symbol::Compose when using subgraphs as
 input (#1314)

---
 nnvm/src/core/symbolic.cc | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/nnvm/src/core/symbolic.cc b/nnvm/src/core/symbolic.cc
index 89d87d32ff74..680a1900c008 100644
--- a/nnvm/src/core/symbolic.cc
+++ b/nnvm/src/core/symbolic.cc
@@ -315,19 +315,26 @@ void Symbol::Compose(const array_view<const Symbol*>& args,
         const Symbol *sym;
         if (idx < arg_vec.size()) {
           sym = arg_vec[idx];
-          arg_vec.erase(arg_vec.begin() + idx);
         } else {
           auto it = kwarg_map.find(arg_names[idx]);
           CHECK(it != kwarg_map.end());
           sym = it->second;
           kwarg_map.erase(it);
         }
-
         if (n_req != kVarg)
           n_req--;
-        arg_names.erase(arg_names.begin() + idx);
         n->attrs.subgraphs.push_back(std::make_shared<Symbol>(*sym));
       }
+      // Because idxes does not contain duplicates, the loop below functions well.
+      // Note that it is as slow as O(|idxes| * |args|),
+      // but given that |idxes| is small, it is just fine
+      sort(std::begin(idxes), std::end(idxes), std::greater<int>());
+      for (auto idx : idxes) {
+        if (idx < arg_vec.size()) {
+          arg_vec.erase(arg_vec.begin() + idx);
+        }
+        arg_names.erase(arg_names.begin() + idx);
+      }
     }
 
     if (n_req != kVarg) {

From e0e0a23a4904d25696634be2ea6abf744c66a060 Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Fri, 22 Jun 2018 22:36:40 +0530
Subject: [PATCH 742/949] CPP implementation of L2Norm and LRN ops (#1157)

---
 nnvm/include/nnvm/top/nn.h                    |  35 ++++++
 nnvm/python/nnvm/top/nn.py                    |  33 ++++++
 nnvm/src/top/nn/nn.cc                         |  47 ++++++++
 nnvm/tests/python/compiler/test_top_level1.py |  62 +++++++++-
 topi/include/topi/cuda/normalization.h        | 106 ++++++++++++++++++
 topi/include/topi/nn/l2_normalize.h           |  46 ++++++++
 topi/include/topi/nn/local_response_norm.h    |  76 +++++++++++++
 topi/include/topi/rocm/normalization.h        |  41 +++++++
 topi/python/topi/cuda/__init__.py             |   2 +-
 topi/python/topi/cuda/nn.py                   |  68 ++---------
 topi/python/topi/generic/nn.py                |  17 +--
 topi/python/topi/nn/__init__.py               |   2 +-
 topi/python/topi/nn/l2_norm.py                |  35 ------
 topi/python/topi/nn/l2_normalize.py           |  29 +++++
 topi/python/topi/nn/local_response_norm.py    |  28 +----
 topi/python/topi/rocm/nn.py                   |  15 ++-
 topi/python/topi/testing/__init__.py          |   2 +
 .../topi/testing/l2_normalize_python.py       |  27 +++++
 topi/python/topi/testing/lrn_python.py        |  53 +++++++++
 topi/src/topi.cc                              |  39 +++++++
 topi/tests/python/test_topi_l2norm.py         |  61 ++++------
 topi/tests/python/test_topi_lrn.py            |  75 ++-----------
 topi/tests/python_cpp/test_topi_l2norm.py     |  48 ++++++++
 topi/tests/python_cpp/test_topi_lrn.py        |  44 ++++++++
 24 files changed, 752 insertions(+), 239 deletions(-)
 create mode 100644 topi/include/topi/cuda/normalization.h
 create mode 100644 topi/include/topi/nn/l2_normalize.h
 create mode 100644 topi/include/topi/nn/local_response_norm.h
 create mode 100644 topi/include/topi/rocm/normalization.h
 delete mode 100644 topi/python/topi/nn/l2_norm.py
 create mode 100644 topi/python/topi/nn/l2_normalize.py
 create mode 100644 topi/python/topi/testing/l2_normalize_python.py
 create mode 100644 topi/python/topi/testing/lrn_python.py
 create mode 100644 topi/tests/python_cpp/test_topi_l2norm.py
 create mode 100644 topi/tests/python_cpp/test_topi_lrn.py

diff --git a/nnvm/include/nnvm/top/nn.h b/nnvm/include/nnvm/top/nn.h
index bbdb3b9c4f12..0e0b03a72ebb 100644
--- a/nnvm/include/nnvm/top/nn.h
+++ b/nnvm/include/nnvm/top/nn.h
@@ -368,6 +368,41 @@ struct NMSParam : public dmlc::Parameter<NMSParam> {
   }
 };
 
+struct LRNParam : public dmlc::Parameter<LRNParam> {
+  int size;
+  int axis;
+  float alpha;
+  float beta;
+  float bias;
+
+  DMLC_DECLARE_PARAMETER(LRNParam) {
+    DMLC_DECLARE_FIELD(size)
+      .describe("The size of the local region to be considered for normalization.");
+    DMLC_DECLARE_FIELD(axis)
+      .describe("input data layout channel axis");
+    DMLC_DECLARE_FIELD(alpha)
+      .describe("The scaling parameter.");
+    DMLC_DECLARE_FIELD(beta)
+      .describe("The exponent parameter.");
+    DMLC_DECLARE_FIELD(bias)
+      .describe("The offset parameter.");
+  }
+  // constants
+  static const constexpr int kData = 0;
+};
+
+struct L2NormalizeParam : public dmlc::Parameter<L2NormalizeParam> {
+  float eps;
+  Tuple<int> axis;
+
+  DMLC_DECLARE_PARAMETER(L2NormalizeParam) {
+    DMLC_DECLARE_FIELD(eps)
+      .describe("float type epsilon value.");
+    DMLC_DECLARE_FIELD(axis)
+      .describe("axis over the normalization applied");
+  }
+};
+
 }  // namespace top
 }  // namespace nnvm
 
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index b7e0d0952888..5bfabdac2c8d 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -243,3 +243,36 @@ def schedule_upsampling(_, outs, target):
         return topi.generic.schedule_injective(outs)
 
 reg.register_pattern("upsampling", OpPattern.INJECTIVE)
+
+@reg.register_compute("lrn")
+def compute_lrn(attrs, inputs, _):
+    """Compute definition of lrn"""
+    size = attrs.get_int("size")
+    axis = attrs.get_int("axis")
+    alpha = attrs.get_float("alpha")
+    beta = attrs.get_float("beta")
+    bias = attrs.get_float("bias")
+    return topi.nn.lrn(inputs[0], size, axis, alpha, beta, bias)
+
+@reg.register_schedule("lrn")
+def schedule_lrn(attrs, outs, target):
+    """Schedule definition of lrn"""
+    with tvm.target.create(target):
+        return topi.generic.schedule_lrn(outs)
+
+reg.register_pattern("lrn", OpPattern.OPAQUE)
+
+@reg.register_compute("l2_normalize")
+def compute_l2_normalize(attrs, inputs, _):
+    """Compute definition of l2 normalize"""
+    eps = attrs.get_float("eps")
+    axis = attrs.get_int_tuple("axis")
+    return topi.nn.l2_normalize(inputs[0], eps, axis)
+
+@reg.register_schedule("l2_normalize")
+def schedule_l2_normalize(attrs, outs, target):
+    """Schedule definition of l2 normalize"""
+    with tvm.target.create(target):
+        return topi.generic.schedule_l2_normalize(outs)
+
+reg.register_pattern("l2_normalize", OpPattern.OUT_ELEMWISE_FUSABLE)
diff --git a/nnvm/src/top/nn/nn.cc b/nnvm/src/top/nn/nn.cc
index cedfb210855e..ab47ae521224 100644
--- a/nnvm/src/top/nn/nn.cc
+++ b/nnvm/src/top/nn/nn.cc
@@ -712,5 +712,52 @@ the input array by output[n, c, h, w, C] = data[n, C*16+c, h, w]
 })
 .set_support_level(1);
 
+DMLC_REGISTER_PARAMETER(LRNParam);
+
+inline bool LRNInferShape(const nnvm::NodeAttrs& attrs,
+                          std::vector<TShape>* in_shape,
+                          std::vector<TShape>* out_shape) {
+  TShape dshape = (*in_shape)[0];
+  TShape oshape = dshape;
+
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, oshape);
+  return true;
+}
+
+NNVM_REGISTER_OP(lrn)
+.describe(R"code(LRN layer)code" NNVM_ADD_FILELINE)
+.add_argument("data", "4D Tensor", "Input data.")
+.set_attr_parser(ParamParser<LRNParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<LRNParam>)
+.set_num_inputs(1)
+.set_num_outputs(1)
+.set_attr<FInferShape>("FInferShape", LRNInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_support_level(1);
+
+DMLC_REGISTER_PARAMETER(L2NormalizeParam);
+
+inline bool L2NormalizeInferShape(const nnvm::NodeAttrs& attrs,
+                                  std::vector<TShape>* in_shape,
+                                  std::vector<TShape>* out_shape) {
+  TShape dshape = (*in_shape)[0];
+  TShape oshape = dshape;
+
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, oshape);
+  return true;
+}
+
+NNVM_REGISTER_OP(l2_normalize)
+.describe(R"code(L2NORMALIZE layer)code" NNVM_ADD_FILELINE)
+.add_argument("data", "4D Tensor", "Input data.")
+.set_attr_parser(ParamParser<L2NormalizeParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<L2NormalizeParam>)
+.set_num_inputs(1)
+.set_num_outputs(1)
+.set_attr<FInferShape>("FInferShape", L2NormalizeInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseArbitraryLayout<1, 1>)
+.set_support_level(1);
+
 }  // namespace top
 }  // namespace nnvm
diff --git a/nnvm/tests/python/compiler/test_top_level1.py b/nnvm/tests/python/compiler/test_top_level1.py
index 3058d6ccfc7b..37798d37f400 100644
--- a/nnvm/tests/python/compiler/test_top_level1.py
+++ b/nnvm/tests/python/compiler/test_top_level1.py
@@ -6,7 +6,6 @@
 import nnvm.compiler
 from nnvm.testing.config import ctx_list
 
-
 def helper(symbol, inputs, dtype,
            np_forward, np_backward=None, need_input=True, need_head_grads=True):
     ishapes = {}
@@ -365,6 +364,65 @@ def forward(x):
     inputs = [('x', (1, 3, 28, 28), x)]
     helper(y, inputs, dtype, forward)
 
+def verify_lrn(ishape, size, axis, bias, alpha, beta):
+    x = sym.Variable("x")
+    y = sym.lrn(x, size=size, axis=axis, bias=bias, alpha=alpha, beta=beta)
+    dtype = "float32"
+    x_np = np.random.uniform(size=ishape).astype(dtype)
+
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, {"x": ishape})
+        m = graph_runtime.create(graph, lib, ctx)
+        m.run(x=x_np)
+        out = m.get_output(0, tvm.nd.empty(ishape))
+        out_np = topi.testing.lrn_python(x_np, size, axis, bias, alpha, beta)
+        np.testing.assert_allclose(out.asnumpy(), out_np, atol=1e-5, rtol=1e-5)
+
+    #Checking LRN op followed by elementwise op relu
+    z = sym.relu(y)
+    x_np = np.random.uniform(low=-10.0, high=10.0, size=ishape).astype(dtype)
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(z, target, {"x": ishape})
+        m = graph_runtime.create(graph, lib, ctx)
+        m.run(x=x_np)
+        out = m.get_output(0, tvm.nd.empty(ishape))
+        out_np = topi.testing.lrn_python(x_np, size, axis, bias, alpha, beta)
+        out_np = (out_np > 0) * out_np
+        np.testing.assert_allclose(out.asnumpy(), out_np, atol=1e-5, rtol=1e-5)
+
+def verify_l2_normalize(ishape, eps, axis):
+    x = sym.Variable("x")
+    y = sym.l2_normalize(x, eps=eps, axis=axis)
+    dtype = "float32"
+    x_np = np.random.uniform(size=ishape).astype(dtype)
+
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, {"x": ishape})
+        m = graph_runtime.create(graph, lib, ctx)
+        m.run(x=x_np)
+        out = m.get_output(0, tvm.nd.empty(ishape))
+        out_np = topi.testing.l2_normalize_python(x_np, eps, axis)
+        np.testing.assert_allclose(out.asnumpy(), out_np, atol=1e-5, rtol=1e-5)
+
+    #Checking L2 normalization op followed by elementwise op relu
+    z = sym.relu(y)
+    x_np = np.random.uniform(low=-10.0, high=10.0, size=ishape).astype(dtype)
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(z, target, {"x": ishape})
+        m = graph_runtime.create(graph, lib, ctx)
+        m.run(x=x_np)
+        out = m.get_output(0, tvm.nd.empty(ishape))
+        out_np = topi.testing.l2_normalize_python(x_np, eps, axis)
+        out_np = (out_np > 0) * out_np
+        np.testing.assert_allclose(out.asnumpy(), out_np, atol=1e-5, rtol=1e-5)
+
+def test_lrn():
+    verify_lrn((1, 3, 20, 20), 3, 1, 1.0, 1.0, 0.5)
+    verify_lrn((1, 3, 20, 20), 3, 1, 2.0, 1.0, 0.75)
+
+def test_l2_normalize():
+    verify_l2_normalize((1, 3, 20, 20), 0.001, (1,))
+    verify_l2_normalize((1, 3, 20, 20), 0.001, (1, 2))
 
 if __name__ == "__main__":
     test_split()
@@ -384,3 +442,5 @@ def forward(x):
     test_softmax()
     test_squeeze()
     test_pad()
+    test_lrn()
+    test_l2_normalize()
diff --git a/topi/include/topi/cuda/normalization.h b/topi/include/topi/cuda/normalization.h
new file mode 100644
index 000000000000..91578c46d266
--- /dev/null
+++ b/topi/include/topi/cuda/normalization.h
@@ -0,0 +1,106 @@
+/*!
+*  Copyright (c) 2018 by Contributors
+* \file cuda/normalization.h
+* \brief CUDA schedule for LRN and l2 normalization operations
+*/
+#ifndef TOPI_CUDA_NORMALIZATION_H_
+#define TOPI_CUDA_NORMALIZATION_H_
+
+#include "tvm/tvm.h"
+#include "tvm/build_module.h"
+#include "topi/tags.h"
+
+namespace topi {
+using namespace tvm;
+namespace cuda {
+/*!
+* \brief Create a CUDA schedule for LRN
+*
+* \param target The target to generate a schedule for.
+* \param outs The output tensors.
+*
+* \return A schedule for the given ops.
+*/
+inline Schedule schedule_lrn(const Target &target, const Array<Tensor>& outs) {
+  Array<Operation> out_ops;
+  for (auto t : outs) {
+    out_ops.push_back(t->op);
+  }
+  Schedule s = create_schedule(out_ops);
+  int num_thread = 64;
+  IterVar block_x = tvm::thread_axis(Range(), "blockIdx.x");
+  IterVar thread_x = tvm::thread_axis(Range(0, num_thread), "threadIdx.x");
+  Tensor lrn = outs[0];
+  Tensor sqr_sum_up = lrn->op->InputTensors()[1];
+  Tensor sqr_sum = sqr_sum_up->op->InputTensors()[0];
+  Tensor set_pad = sqr_sum->op->InputTensors()[0];
+  s[set_pad].bind(set_pad->op.as<ComputeOpNode>()->axis[0], block_x);
+  IterVar rxk = sqr_sum->op.as<ComputeOpNode>()->reduce_axis[0];
+  IterVar xko, xki;
+  s[sqr_sum].split(rxk, num_thread, &xko, &xki);
+  Tensor srf = s.rfactor(sqr_sum, xki)[0];
+  s[sqr_sum].bind(s[sqr_sum]->op.as<ComputeOpNode>()->axis[0], block_x);
+  s[sqr_sum].bind(s[sqr_sum]->op.as<ComputeOpNode>()->reduce_axis[0], thread_x);
+  s[srf].compute_at(s[sqr_sum], s[sqr_sum]->op.as<ComputeOpNode>()->reduce_axis[0]);
+  s[sqr_sum_up].bind(sqr_sum_up->op.as<ComputeOpNode>()->axis[0], block_x);
+  IterVar xto, xti;
+  s[lrn].split_by_nparts(lrn->op.as<ComputeOpNode>()->axis[1], num_thread, &xto, &xti);
+  s[lrn].bind(lrn->op.as<ComputeOpNode>()->axis[0], block_x);
+  s[lrn].bind(xto, thread_x);
+
+  return s;
+}
+
+/*!
+* \brief Create a CUDA schedule for L2 normalization
+*
+* \param target The target to generate a schedule for.
+* \param outs The output tensors.
+*
+* \return A schedule for the given ops.
+*/
+inline Schedule schedule_l2_normalize(const Target &target, const Array<Tensor>& outs) {
+  Array<Operation> out_ops;
+  for (auto t : outs) {
+    out_ops.push_back(t->op);
+  }
+  Schedule s = create_schedule(out_ops);
+
+  std::function<void(Operation)> traverse;
+  traverse = [&](const Operation& op) {
+    // Inline all one-to-one-mapping operators except the last stage (output)
+    if (is_injective(op->tag) || op->tag == "l2_normalize") {
+      if (!detail::contains(s->outputs, op)) {
+        s[op].compute_inline();
+      }
+      for (auto tensor : op->InputTensors()) {
+        if (tensor->op->InputTensors().size() > 0) {
+          traverse(tensor->op);
+        }
+      }
+    } else if (op->tag == "comm_reduce") {
+      ScheduleReduce(target, op, s, false);
+      for (auto tensor : op->InputTensors()) {
+        traverse(tensor->op);
+      }
+    } else {
+      LOG(ERROR) << "Unsupported operator " << op->tag;
+    }
+  };
+
+  traverse(outs[0]->op);
+  int num_thread = 64;
+  Tensor l2_normalize = outs[0];
+  IterVar block_x = tvm::thread_axis(Range(), "blockIdx.x");
+  IterVar thread_x = tvm::thread_axis(Range(0, num_thread), "threadIdx.x");
+  IterVar xto, xti;
+  s[l2_normalize].split_by_nparts(l2_normalize->op.as<ComputeOpNode>()->axis[1],
+                                 num_thread, &xto, &xti);
+  s[l2_normalize].bind(l2_normalize->op.as<ComputeOpNode>()->axis[0], block_x);
+  s[l2_normalize].bind(xto, thread_x);
+  return s;
+}
+}  // namespace cuda
+}  // namespace topi
+#endif  // TOPI_CUDA_NORMALIZATION_H_
+
diff --git a/topi/include/topi/nn/l2_normalize.h b/topi/include/topi/nn/l2_normalize.h
new file mode 100644
index 000000000000..079c6d467561
--- /dev/null
+++ b/topi/include/topi/nn/l2_normalize.h
@@ -0,0 +1,46 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \brief l2 normalization op constructions
+ * \file nn/l2_normalize.h
+ */
+#ifndef TOPI_NN_L2_NORMALIZE_H_
+#define TOPI_NN_L2_NORMALIZE_H_
+
+#include <string>
+#include <algorithm>
+#include "topi/tags.h"
+#include "tvm/tvm.h"
+namespace topi {
+namespace nn {
+using namespace tvm;
+
+/*!
+* \brief L2 normalization inference operator
+*
+* \param data The input tensor. 4-D with shape [batch, channel, height, width]
+* \param eps Epsilon to prevent div by 0
+* \param axis Axes over the normalization applied
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the l2 normalization operation
+*/
+inline Tensor l2_normalize(const Tensor& data,
+                              float eps,
+                              const Array<Expr>& axis,
+                              std::string name = "tensor",
+                              std::string tag = "l2_normalize") {
+  CHECK_EQ(data->shape.size(), 4) << "L2 normalization requires 4-D input";
+  auto input_shape = data->shape;
+  Tensor dot_value = pow(data, static_cast<float>(2.0));
+  Tensor sum_value = topi::sum(dot_value, axis, true);
+  Tensor expand_sum = topi::broadcast_to(sum_value, input_shape);
+  return topi::broadcast_div(data,
+                             topi::sqrt(tvm::compute(expand_sum->shape,
+                                                     [&](const Array<Var>& i){
+                                                     return (max(expand_sum(i), eps));
+                                                     }, name = name, tag = tag)));
+}
+}  // namespace nn
+}  // namespace topi
+#endif  // TOPI_NN_L2_NORMALIZE_H_
diff --git a/topi/include/topi/nn/local_response_norm.h b/topi/include/topi/nn/local_response_norm.h
new file mode 100644
index 000000000000..c956a9c253dc
--- /dev/null
+++ b/topi/include/topi/nn/local_response_norm.h
@@ -0,0 +1,76 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \brief local response normalization op constructions
+ * \file nn/local_response_norm.h
+ */
+#ifndef TOPI_NN_LOCAL_RESPONSE_NORM_H_
+#define TOPI_NN_LOCAL_RESPONSE_NORM_H_
+
+#include <string>
+
+#include "topi/tags.h"
+#include "tvm/tvm.h"
+
+namespace topi {
+namespace nn {
+using namespace tvm;
+
+/*!
+* \brief Local response normalization inference operator
+*
+* \param data The input tensor. 4-D shape NCHW or NHWC
+* \param size Integer to define normalisation window size
+* \param axis Input data layout channel axis
+* \param alpha Float scaling factor
+* \param beta Exponent value
+* \param bias Offset to avoid dividing by zero
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the Local response normalization operation
+*/
+inline Tensor lrn(const Tensor& data,
+                  int size,
+                  int axis = 1,
+                  float alpha = 0.0001,
+                  float beta = 0.75,
+                  float bias = 2,
+                  std::string name = "tensor",
+                  std::string tag = kBroadcast) {
+  CHECK_EQ(data->shape.size(), 4) << "LRN requires 4-D input";
+  CHECK_EQ(size % 2, 1) << "size should be odd number";
+  CHECK(axis == 1 || axis == 3) << "axis should be 1 or 3 for NCHW and NHWC";
+  auto input_shape = data->shape;
+  Array<Expr> pad_before{ 0, 0, 0, 0};
+  Array<Expr> pad_after{ 0, 0, 0, 0};
+  pad_before.Set(axis, static_cast<Expr>(size/2));
+  pad_after.Set(axis, static_cast<Expr>(size/2));
+  auto pad_data = pad(data, pad_before, pad_after, 0, "pad_data");
+  auto rxs = tvm::reduce_axis(Range(0, size), "rxs");
+  Tensor sqr_sum;
+  if (axis == 1) {
+    sqr_sum = tvm::compute(input_shape,
+                           [&](Var i, Var l, Var j, Var k) {
+                           return tvm::sum(pad_data(i, l + rxs, j, k) *
+                                           pad_data(i, l + rxs, j, k),
+                                           {rxs});
+                           });
+  } else if (axis == 3) {
+    sqr_sum = tvm::compute(input_shape,
+                           [&](Var i, Var l, Var j, Var k) {
+                           return tvm::sum(pad_data(i, l, j, k + rxs) *
+                                           pad_data(i, l, j, k + rxs),
+                                           {rxs});
+                           });
+  }
+  auto sqrt_sum_up = tvm::compute(input_shape,
+                                  [&](Var i, Var j, Var k, Var l) {
+                                  return tvm::pow(bias +
+                                                  (alpha * sqr_sum(i, j, k, l) / size),
+                                                  beta);
+                                  });
+  return topi::broadcast_div(data, sqrt_sum_up);
+}
+}  // namespace nn
+}  // namespace topi
+#endif  // TOPI_NN_LOCAL_RESPONSE_NORM_H_
diff --git a/topi/include/topi/rocm/normalization.h b/topi/include/topi/rocm/normalization.h
new file mode 100644
index 000000000000..b12e64aba963
--- /dev/null
+++ b/topi/include/topi/rocm/normalization.h
@@ -0,0 +1,41 @@
+/*!
+*  Copyright (c) 2018 by Contributors
+* \file rocm/normalization.h
+* \brief rocm schedule for LRN and l2 normalization operations
+*/
+#ifndef TOPI_ROCM_NORMALIZATION_H_
+#define TOPI_ROCM_NORMALIZATION_H_
+
+#include "tvm/tvm.h"
+#include "tvm/build_module.h"
+#include "topi/tags.h"
+
+namespace topi {
+using namespace tvm;
+namespace rocm {
+/*!
+* \brief Create a rocm schedule for LRN
+*
+* \param target The target to generate a schedule for.
+* \param outs The output tensors.
+*
+* \return A schedule for the given ops.
+*/
+inline Schedule schedule_lrn(const Target &target, const Array<Tensor>& outs) {
+  return topi::cuda::schedule_lrn(target, outs);
+}
+
+/*!
+* \brief Create a rocm schedule for L2 Normalization
+*
+* \param target The target to generate a schedule for.
+* \param outs The output tensors.
+*
+* \return A schedule for the given ops.
+*/
+inline Schedule schedule_l2_normalize(const Target &target, const Array<Tensor>& outs) {
+  return topi::cuda::schedule_l2_normalize(target, outs);
+}
+}  // namespace rocm
+}  // namespace topi
+#endif  // TOPI_ROCM_NORMALIZATION_H_
diff --git a/topi/python/topi/cuda/__init__.py b/topi/python/topi/cuda/__init__.py
index 3b0e38c4d3f4..dbf00ebeb52b 100644
--- a/topi/python/topi/cuda/__init__.py
+++ b/topi/python/topi/cuda/__init__.py
@@ -17,4 +17,4 @@
 from .extern import schedule_extern
 from .vision import schedule_region
 from .vision import schedule_reorg
-from .nn import schedule_lrn, schedule_l2norm
+from .nn import schedule_lrn, schedule_l2_normalize
diff --git a/topi/python/topi/cuda/nn.py b/topi/python/topi/cuda/nn.py
index e8757970505b..b503b2dad50f 100644
--- a/topi/python/topi/cuda/nn.py
+++ b/topi/python/topi/cuda/nn.py
@@ -4,8 +4,7 @@
 
 import tvm
 from .. import generic
-from .. import tag
-from .reduction import _schedule_reduce
+from .. import cpp
 
 @generic.schedule_lrn.register(["cuda"])
 def schedule_lrn(outs):
@@ -22,37 +21,18 @@ def schedule_lrn(outs):
     sch: Schedule
         The computation schedule for the op.
     """
-    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
-    s = tvm.create_schedule([x.op for x in outs])
-    num_thread = 64
-    block_x = tvm.thread_axis("blockIdx.x")
-    thread_x = tvm.thread_axis((0, num_thread), "threadIdx.x")
+    target = tvm.target.current_target(allow_none=False)
+    cpp_target = cpp.TEST_create_target(target.target_name)
+    return cpp.cuda.schedule_lrn(cpp_target, outs)
 
-    lrn = outs[0]
-    sqr_sum_up = lrn.op.input_tensors[1]
-    sqr_sum = sqr_sum_up.op.input_tensors[0]
-    set_pad = sqr_sum.op.input_tensors[0]
-    s[set_pad].bind(set_pad.op.axis[0], block_x)
-    rxk = sqr_sum.op.reduce_axis[0]
-    _, xki = s[sqr_sum].split(rxk, factor=num_thread)
-    srf = s.rfactor(sqr_sum, xki)
-    s[sqr_sum].bind(s[sqr_sum].op.axis[0], block_x)
-    s[sqr_sum].bind(s[sqr_sum].op.reduce_axis[0], thread_x)
-    s[srf].compute_at(s[sqr_sum], s[sqr_sum].op.reduce_axis[0])
-    s[sqr_sum_up].bind(sqr_sum_up.op.axis[0], block_x)
-    xto, _ = s[lrn].split(lrn.op.axis[1], nparts=num_thread)
-    s[lrn].bind(lrn.op.axis[0], block_x)
-    s[lrn].bind(xto, thread_x)
-    return s
-
-@generic.schedule_l2norm.register(["cuda"])
-def schedule_l2norm(outs):
-    """Schedule for L2norm
+@generic.schedule_l2_normalize.register(["cuda"])
+def schedule_l2_normalize(outs):
+    """Schedule for L2 normalize
 
     Parameters
     ----------
     outs: Array of Tensor
-          The computation graph description of L2norm
+          The computation graph description of L2 normalize
           in the format of an array of tensors.
 
     Returns
@@ -60,32 +40,6 @@ def schedule_l2norm(outs):
     sch: Schedule
         The computation schedule for the op.
     """
-    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
-    s = tvm.create_schedule([x.op for x in outs])
-
-    def traverse(OP):
-        '''inline all one-to-one-mapping operators
-        except the last stage (output)'''
-        if tag.is_injective(OP.tag) or OP.tag == 'l2norm':
-            if OP not in s.outputs:
-                s[OP].compute_inline()
-            for tensor in OP.input_tensors:
-                if tensor.op.input_tensors:
-                    traverse(tensor.op)
-        elif OP.tag == 'comm_reduce':
-            _schedule_reduce(OP, s, is_idx_reduce=False)
-            for tensor in OP.input_tensors:
-                traverse(tensor.op)
-        else:
-            raise RuntimeError("Unsupported operator tag: %s" % OP.tag)
-    traverse(outs[0].op)
-
-    num_thread = 64
-    l2norm = outs[0]
-    block_x = tvm.thread_axis("blockIdx.x")
-    thread_x = tvm.thread_axis((0, num_thread), "threadIdx.x")
-    xto, _ = s[l2norm].split(l2norm.op.axis[1], nparts=num_thread)
-    s[l2norm].bind(l2norm.op.axis[0], block_x)
-    s[l2norm].bind(xto, thread_x)
-
-    return s
+    target = tvm.target.current_target(allow_none=False)
+    cpp_target = cpp.TEST_create_target(target.target_name)
+    return cpp.cuda.schedule_l2_normalize(cpp_target, outs)
diff --git a/topi/python/topi/generic/nn.py b/topi/python/topi/generic/nn.py
index 5a16d12206a3..8f2f8612c7fa 100644
--- a/topi/python/topi/generic/nn.py
+++ b/topi/python/topi/generic/nn.py
@@ -2,7 +2,7 @@
 """Generic nn operators"""
 from __future__ import absolute_import as _abs
 import tvm
-
+from .. import cpp
 
 def _default_schedule(outs, auto_inline):
     """Default schedule for llvm."""
@@ -273,17 +273,18 @@ def schedule_lrn(outs):
     sch: Schedule
         The computation schedule for the op.
     """
-    return _default_schedule(outs, False)
-
+    target = tvm.target.current_target(allow_none=False)
+    cpp_target = cpp.TEST_create_target(target.target_name)
+    return cpp.generic.default_schedule(cpp_target, outs, False)
 
 @tvm.target.generic_func
-def schedule_l2norm(outs):
-    """Schedule for l2norm
+def schedule_l2_normalize(outs):
+    """Schedule for l2 normalize
 
     Parameters
     ----------
     outs: Array of Tensor
-          The computation graph description of l2norm
+          The computation graph description of l2 normalize
           in the format of an array of tensors.
 
     Returns
@@ -291,4 +292,6 @@ def schedule_l2norm(outs):
     sch: Schedule
         The computation schedule for the op.
     """
-    return _default_schedule(outs, False)
+    target = tvm.target.current_target(allow_none=False)
+    cpp_target = cpp.TEST_create_target(target.target_name)
+    return cpp.generic.default_schedule(cpp_target, outs, False)
diff --git a/topi/python/topi/nn/__init__.py b/topi/python/topi/nn/__init__.py
index 056d1a76339a..7b6ee4a86836 100644
--- a/topi/python/topi/nn/__init__.py
+++ b/topi/python/topi/nn/__init__.py
@@ -16,4 +16,4 @@
 from .bnn import *
 from .upsampling import *
 from .local_response_norm import *
-from .l2_norm import *
+from .l2_normalize import *
diff --git a/topi/python/topi/nn/l2_norm.py b/topi/python/topi/nn/l2_norm.py
deleted file mode 100644
index 6b5381a85599..000000000000
--- a/topi/python/topi/nn/l2_norm.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# pylint: disable=invalid-name
-"""TVM operator for l2norm"""
-from __future__ import absolute_import
-import tvm
-import topi
-
-@tvm.target.generic_func
-def l2norm_instance(data, eps, axis=None):
-    """Perform L2norm on the input data
-
-    For axis=None, y(i, j) = x(i, j) / sqrt(max(sum(x^2), eps))
-
-    Parameters
-    ----------
-    data : tvm.Tensor
-        4-D with NCHW or NHWC layout
-
-    eps : float
-        epsilon value
-
-    axis : list of int
-        axis over the normalization applied
-
-    Returns
-    -------
-    output : tvm.Tensor
-        4-D output with same shape
-    """
-    assert len(data.shape) == 4, "only support 4-dim lrn"
-    dot_value = topi.cpp.pow(data, 2.0)
-    sum_value = topi.sum(dot_value, axis=axis, keepdims=True)
-    expand_sum = topi.broadcast_to(sum_value, data.shape)
-    return topi.broadcast_div(data, topi.sqrt(\
-                tvm.compute(expand_sum.shape, lambda i, j, k, l:\
-                tvm.max(expand_sum[i, j, k, l], eps), tag='l2norm')))
diff --git a/topi/python/topi/nn/l2_normalize.py b/topi/python/topi/nn/l2_normalize.py
new file mode 100644
index 000000000000..951084379eec
--- /dev/null
+++ b/topi/python/topi/nn/l2_normalize.py
@@ -0,0 +1,29 @@
+# pylint: disable=invalid-name
+"""TVM operator for l2 normalize"""
+from __future__ import absolute_import
+import tvm
+from .. import cpp
+
+@tvm.target.generic_func
+def l2_normalize(data, eps, axis=None):
+    """Perform L2 normalization on the input data
+
+    For axis=None, y(i, j) = x(i, j) / sqrt(max(sum(x^2), eps))
+
+    Parameters
+    ----------
+    data : tvm.Tensor
+        4-D with NCHW or NHWC layout
+
+    eps : float
+        epsilon value
+
+    axis : list of int
+        axis over the normalization applied
+
+    Returns
+    -------
+    output : tvm.Tensor
+        4-D output with same shape
+    """
+    return cpp.nn.l2_normalize(data, eps, axis)
diff --git a/topi/python/topi/nn/local_response_norm.py b/topi/python/topi/nn/local_response_norm.py
index b44e02214acc..73eb41242513 100644
--- a/topi/python/topi/nn/local_response_norm.py
+++ b/topi/python/topi/nn/local_response_norm.py
@@ -2,8 +2,7 @@
 """TVM operator for local response norm compute."""
 from __future__ import absolute_import
 import tvm
-import topi
-from .pad import pad
+from .. import cpp
 
 @tvm.target.generic_func
 def lrn(data, size, axis=1, alpha=0.0001, beta=0.75, bias=2):
@@ -42,27 +41,4 @@ def lrn(data, size, axis=1, alpha=0.0001, beta=0.75, bias=2):
     output : tvm.Tensor
         4-D output with same shape
     """
-    assert len(data.shape) == 4, "only support 4-dim lrn"
-    assert (size % 2) == 1, "size should be odd number"
-    assert (axis == 1) or (axis == 3), "axis should 1 or 3 for NCHW and NHWC"
-    ##Add padding on left & right of size radius first
-    pad_after = pad_before = [0, 0, 0, 0]
-    pad_after[axis] = pad_before[axis] = (size//2)
-    pad_data = pad(data, pad_before, pad_after, name="pad_data")
-
-    rxs = tvm.reduce_axis((0, size), name='rxs')
-    if axis == 1:
-        #NCHW layout
-        sqr_sum = tvm.compute(data.shape, lambda i, j, k, l: tvm.sum(
-            pad_data[i, j + rxs, k, l] * pad_data[i, j + rxs, k, l],
-            axis=rxs))
-    elif axis == 3:
-        #NHWC layout
-        sqr_sum = tvm.compute(data.shape, lambda i, j, k, l: tvm.sum(
-            pad_data[i, j, k, l + rxs] * pad_data[i, j, k, l + rxs],
-            axis=rxs))
-
-    sqr_sum_up = tvm.compute(data.shape, lambda i, j, k, l: tvm.power(
-        (bias + (alpha * sqr_sum[i, j, k, l] / size)), beta))
-
-    return topi.broadcast_div(data, sqr_sum_up)
+    return cpp.nn.lrn(data, size, axis, alpha, beta, bias)
diff --git a/topi/python/topi/rocm/nn.py b/topi/python/topi/rocm/nn.py
index d9c529155f7b..5a9b2ad84db0 100644
--- a/topi/python/topi/rocm/nn.py
+++ b/topi/python/topi/rocm/nn.py
@@ -1,13 +1,18 @@
 """scheduler for normalization functions on rocm backend"""
 from __future__ import absolute_import as _abs
 
-import topi
+import tvm
 from .. import generic
+from .. import cpp
 
 @generic.schedule_lrn.register(["rocm", "gpu"])
 def schedule_lrn(outs):
-    return topi.cuda.schedule_lrn(outs)
+    target = tvm.target.current_target(allow_none=False)
+    cpp_target = cpp.TEST_create_target(target.target_name)
+    return cpp.rocm.schedule_lrn(cpp_target, outs)
 
-@generic.schedule_l2norm.register(["rocm", "gpu"])
-def schedule_l2norm(outs):
-    return topi.cuda.schedule_l2norm(outs)
+@generic.schedule_l2_normalize.register(["rocm", "gpu"])
+def schedule_l2_normalize(outs):
+    target = tvm.target.current_target(allow_none=False)
+    cpp_target = cpp.TEST_create_target(target.target_name)
+    return cpp.rocm.schedule_l2_normalize(cpp_target, outs)
diff --git a/topi/python/topi/testing/__init__.py b/topi/python/topi/testing/__init__.py
index 3731040e3a85..c91eea7958ea 100644
--- a/topi/python/topi/testing/__init__.py
+++ b/topi/python/topi/testing/__init__.py
@@ -16,3 +16,5 @@
 from .reorg_python import reorg_python
 from .region_python import region_python
 from .shortcut_python import shortcut_python
+from .lrn_python import lrn_python
+from .l2_normalize_python import l2_normalize_python
diff --git a/topi/python/topi/testing/l2_normalize_python.py b/topi/python/topi/testing/l2_normalize_python.py
new file mode 100644
index 000000000000..98f1843233a7
--- /dev/null
+++ b/topi/python/topi/testing/l2_normalize_python.py
@@ -0,0 +1,27 @@
+# pylint: disable=invalid-name, line-too-long, unused-variable, too-many-locals
+"""L2 normalize in python"""
+import numpy as np
+
+def l2_normalize_python(a_np, eps, axis=None):
+    """L2 normalize operator in NCHW layout.
+
+    Parameters
+    ----------
+    a_np : numpy.ndarray
+        4-D with shape [batch, in_channel, in_height, in_width]
+
+    eps : float
+        epsilon constant value
+    axis : list of int
+        axis over the normalization applied
+
+    Returns
+    -------
+    l2_normalize_out : np.ndarray
+        4-D with shape [batch, out_channel, out_height, out_width]
+    """
+    dot_value = np.power(a_np, 2.0)
+    sqr_sum = np.sum(dot_value, axis, keepdims=True)
+    sqrt_sum = np.sqrt(np.maximum(np.broadcast_to(sqr_sum, a_np.shape), eps))
+    l2_normalize_out = np.divide(a_np, sqrt_sum)
+    return l2_normalize_out
diff --git a/topi/python/topi/testing/lrn_python.py b/topi/python/topi/testing/lrn_python.py
new file mode 100644
index 000000000000..4e44e8bcb635
--- /dev/null
+++ b/topi/python/topi/testing/lrn_python.py
@@ -0,0 +1,53 @@
+# pylint: disable=invalid-name, line-too-long, unused-variable, too-many-locals
+"""LRN in python"""
+from itertools import product
+import numpy as np
+
+def lrn_python(a_np, size, axis, bias, alpha, beta):
+    """Local response normalization operator in NCHW layout.
+
+    Parameters
+    ----------
+    a_np : numpy.ndarray
+        4-D with shape [batch, in_channel, in_height, in_width]
+
+    size : int
+        normalization window size
+
+    axis : int
+        input data layout channel axis
+
+    bias : float
+        offset to avoid dividing by 0. constant value
+
+    alpha : float
+        constant value
+
+    beta : float
+        exponent constant value
+
+    Returns
+    -------
+    lrn_out : np.ndarray
+        4-D with shape [batch, out_channel, out_height, out_width]
+    """
+    radius = size // 2
+    sqr_sum = np.zeros(shape=a_np.shape).astype(a_np.dtype)
+    for i, j, k, l in product(*[range(_axis) for _axis in a_np.shape]):
+        axis_size = a_np.shape[axis]
+        if axis == 1:
+            #NCHW layout
+            sum_start = j-radius if j-radius >= 0 else 0
+            sum_end = j+radius+1 if j+radius+1 < axis_size else axis_size
+            sqr_sum[i, j, k, l] = sum(a_np[i, sum_start:sum_end, k, l] * \
+                                      a_np[i, sum_start:sum_end, k, l])
+        elif axis == 3:
+            #NHWC layout
+            sum_start = l-radius if l-radius >= 0 else 0
+            sum_end = l+radius+1 if l+radius+1 < axis_size else axis_size
+            sqr_sum[i, j, k, l] = sum(a_np[i, j, k, sum_start:sum_end] * \
+                                      a_np[i, j, k, sum_start:sum_end])
+
+    sqr_sum_up = np.power((bias + (alpha * sqr_sum /size)), beta)
+    lrn_out = np.divide(a_np, sqr_sum_up)
+    return lrn_out
diff --git a/topi/src/topi.cc b/topi/src/topi.cc
index 4169f5f563ad..9f2ecacd11a4 100644
--- a/topi/src/topi.cc
+++ b/topi/src/topi.cc
@@ -24,6 +24,8 @@
 #include <topi/nn/pooling.h>
 #include <topi/nn/softmax.h>
 #include <topi/nn/upsampling.h>
+#include <topi/nn/l2_normalize.h>
+#include <topi/nn/local_response_norm.h>
 
 #include <topi/vision/reorg.h>
 #include <topi/image/resize.h>
@@ -39,6 +41,7 @@
 #include <topi/cuda/reduction.h>
 #include <topi/cuda/softmax.h>
 #include <topi/cuda/vision.h>
+#include <topi/cuda/normalization.h>
 
 #include <topi/x86/bnn.h>
 #include <topi/x86/default.h>
@@ -46,6 +49,7 @@
 
 #include <topi/rocm/dense.h>
 #include <topi/rocm/vision.h>
+#include <topi/rocm/normalization.h>
 
 namespace topi {
 
@@ -359,6 +363,20 @@ TVM_REGISTER_GLOBAL("topi.nn.log_softmax")
   *rv = nn::log_softmax(args[0]);
   });
 
+/* Ops from nn/l2_normalize.h */
+TVM_REGISTER_GLOBAL("topi.nn.l2_normalize")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = nn::l2_normalize(args[0], static_cast<double>(args[1]), args[2]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.nn.lrn")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = nn::lrn(args[0], args[1], args[2],
+                static_cast<double>(args[3]),
+                static_cast<double>(args[4]),
+                static_cast<double>(args[5]));
+  });
+
 TVM_REGISTER_GLOBAL("topi.vision.reorg")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
   *rv = vision::reorg(args[0], args[1]);
@@ -435,6 +453,17 @@ TVM_REGISTER_GLOBAL("topi.rocm.schedule_region")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
   *rv = topi::rocm::schedule_region(args[0], args[1]);
   });
+
+TVM_REGISTER_GLOBAL("topi.rocm.schedule_lrn")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::rocm::schedule_lrn(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.rocm.schedule_l2_normalize")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::rocm::schedule_l2_normalize(args[0], args[1]);
+  });
+
 /* CUDA schedules */
 TVM_REGISTER_GLOBAL("topi.cuda.dense_cuda")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
@@ -481,6 +510,16 @@ TVM_REGISTER_GLOBAL("topi.cuda.schedule_region")
   *rv = topi::cuda::schedule_region(args[0], args[1]);
   });
 
+TVM_REGISTER_GLOBAL("topi.cuda.schedule_lrn")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::cuda::schedule_lrn(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.cuda.schedule_l2_normalize")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = topi::cuda::schedule_l2_normalize(args[0], args[1]);
+  });
+
 /*! \brief Builder function for instantiating schedules. */
 using FTVMScheduleBuilder = std::function<
   tvm::Schedule(const tvm::Target& target, const tvm::Array<tvm::Tensor>& outs)>;
diff --git a/topi/tests/python/test_topi_l2norm.py b/topi/tests/python/test_topi_l2norm.py
index 182099ff9367..b27a1dc27e72 100644
--- a/topi/tests/python/test_topi_l2norm.py
+++ b/topi/tests/python/test_topi_l2norm.py
@@ -1,44 +1,18 @@
-"""Test code for L2 norm"""
+"""Test code for L2 normalization"""
 import numpy as np
 import tvm
 import topi
 from topi.util import get_const_tuple
+import topi.testing
 
-def l2norm_instance_python(a_np, eps, axis=None):
-    """L2 norm operator in NCHW layout.
+def verify_l2_normalize(ishape, eps, axis=None):
 
-    Parameters
-    ----------
-    a_np : numpy.ndarray
-        4-D with shape [batch, in_channel, in_height, in_width]
-
-    eps : float
-        epsilon constant value
-    axis : list of int
-        axis over the normalization applied
-
-    Returns
-    -------
-    l2norm_out : np.ndarray
-        4-D with shape [batch, out_channel, out_height, out_width]
-    """
-    batch, axis1, axis2, axis3 = a_np.shape
-    sqr_sum = np.zeros(shape=(batch,)).astype(a_np.dtype)
-    sqrt_sum = np.zeros(shape=(batch,)).astype(a_np.dtype)
-    l2norm_out = np.zeros(shape=a_np.shape).astype(a_np.dtype)
-    dot_value = np.power(a_np, 2.0)
-    sqr_sum = np.sum(dot_value, axis, keepdims=True)
-    sqrt_sum = np.sqrt(np.maximum(np.broadcast_to(sqr_sum, a_np.shape), eps))
-    return np.divide(a_np, sqrt_sum)
-
-def verify_l2norm(n, c, h, w, eps, axis=None):
-
-    A = tvm.placeholder((n, c, h, w), name='A')
-    B = topi.nn.l2norm_instance(A, eps, axis)
+    A = tvm.placeholder(ishape, name='A')
+    B = topi.nn.l2_normalize(A, eps, axis)
     dtype = A.dtype
 
-    a_np = np.random.uniform(size=(n, c, h, w)).astype(dtype)
-    b_np = l2norm_instance_python(a_np, eps, axis)
+    a_np = np.random.uniform(size=ishape).astype(dtype)
+    b_np = topi.testing.l2_normalize_python(a_np, eps, axis)
 
     def check_device(device):
         ctx = tvm.context(device, 0)
@@ -47,7 +21,10 @@ def check_device(device):
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
-            s = topi.generic.schedule_l2norm(B)
+            if device == 'llvm':
+                s = topi.generic.schedule_l2_normalize([B])
+            else:
+                s = topi.cuda.schedule_l2_normalize([B])
         a = tvm.nd.array(a_np, ctx)
         b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=dtype), ctx)
         f = tvm.build(s, [A, B], device)
@@ -57,14 +34,14 @@ def check_device(device):
     for device in ['llvm', 'cuda', 'opencl', 'metal', 'rocm', 'vulkan']:
         check_device(device)
 
-def test_l2norm():
-    verify_l2norm(1, 3, 20, 20, 0.001)
-    verify_l2norm(1, 3, 20, 20, 0.001, 1)
-    verify_l2norm(1, 3, 20, 20, 0.001, (1, 2))
-    verify_l2norm(1, 3, 20, 20, 0.001, (2, 3))
-    verify_l2norm(1, 3, 20, 20, 0.001, (0, 3))
-    verify_l2norm(1, 3, 20, 20, 0.001, (0, 2, 3))
+def test_l2_normalize():
+    verify_l2_normalize((1, 3, 20, 20), 0.001)
+    verify_l2_normalize((1, 3, 20, 20), 0.001, (1,))
+    verify_l2_normalize((1, 3, 20, 20), 0.001, (1, 2))
+    verify_l2_normalize((1, 3, 20, 20), 0.001, (2, 3))
+    verify_l2_normalize((1, 3, 20, 20), 0.001, (0, 3))
+    verify_l2_normalize((1, 3, 20, 20), 0.001, (0, 2, 3))
 
 
 if __name__ == "__main__":
-    test_l2norm()
+    test_l2_normalize()
diff --git a/topi/tests/python/test_topi_lrn.py b/topi/tests/python/test_topi_lrn.py
index 596e5747a6c5..7d62aefe5f55 100644
--- a/topi/tests/python/test_topi_lrn.py
+++ b/topi/tests/python/test_topi_lrn.py
@@ -3,63 +3,7 @@
 import tvm
 import topi
 from topi.util import get_const_tuple
-
-def lrn_python(a_np, size, axis, bias, alpha, beta):
-    """Local response norm operator in NCHW layout.
-
-    Parameters
-    ----------
-    a_np : numpy.ndarray
-        4-D with shape [batch, in_channel, in_height, in_width]
-
-    size : int
-        normalisation window size
-
-    axis : int
-        input data layout channel axis
-
-    bias : float
-        offset to avoid dividing by 0. constant value
-
-    alpha : float
-        contant valie
-
-    beta : float
-        exponent constant value
-
-    Returns
-    -------
-    b_np : np.ndarray
-        4-D with shape [batch, out_channel, out_height, out_width]
-    """
-    axis0, axis1, axis2, axis3 = a_np.shape
-    radius = size // 2
-    sqr_sum = np.zeros(shape=a_np.shape).astype(a_np.dtype)
-    sqr_sum_up = np.zeros(shape=a_np.shape).astype(a_np.dtype)
-    lrn_out = np.zeros(shape=a_np.shape).astype(a_np.dtype)
-    def sum_dot_values(i, j, k, l):
-        axis_size = a_np.shape[axis]
-        if (axis == 1):
-            #NCHW layout
-            sum_start = j-radius if j-radius >= 0 else 0
-            sum_end = j+radius+1 if j+radius+1 < axis_size else axis_size
-            sqr_sum[i, j, k, l] = sum(a_np[i, sum_start:sum_end, k, l] * \
-                                      a_np[i, sum_start:sum_end, k, l])
-        elif (axis == 3):
-            #NHWC layout
-            sum_start = l-radius if l-radius >= 0 else 0
-            sum_end = l+radius+1 if l+radius+1 < axis_size else axis_size
-            sqr_sum[i, j, k, l] = sum(a_np[i, j, k, sum_start:sum_end] * \
-                                      a_np[i, j, k, sum_start:sum_end])
-
-    for i in range(axis0):
-        for j in range(axis1):
-            for k in range(axis2):
-                for l in range(axis3):
-                    sum_dot_values(i, j, k, l)
-
-    sqr_sum_up = np.power((bias + (alpha * sqr_sum /size)), beta)
-    return np.divide(a_np, sqr_sum_up)
+import topi.testing
 
 def verify_lrn(shape, size, axis, bias, alpha, beta):
     A = tvm.placeholder(shape, name='A')
@@ -67,16 +11,19 @@ def verify_lrn(shape, size, axis, bias, alpha, beta):
     dtype = A.dtype
 
     a_np = np.random.uniform(size=shape).astype(dtype)
-    b_np = lrn_python(a_np, size, axis, bias, alpha, beta)
+    b_np = topi.testing.lrn_python(a_np, size, axis, bias, alpha, beta)
 
     def check_device(device):
-        ctx = tvm.context(device, 0)
-        if not ctx.exist:
+        if not tvm.module.enabled(device):
             print("Skip because %s is not enabled" % device)
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
-            s = topi.generic.schedule_lrn(B)
+            if device == 'llvm':
+                s = topi.generic.schedule_lrn([B])
+            else:
+                s = topi.cuda.schedule_lrn([B])
+        ctx = tvm.context(device, 0)
         a = tvm.nd.array(a_np, ctx)
         b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=dtype), ctx)
         f = tvm.build(s, [A, B], device)
@@ -87,9 +34,9 @@ def check_device(device):
         check_device(device)
 
 def test_lrn():
-    verify_lrn((1, 3, 5, 5), 3, 1, 1, 1, 0.5)
-    verify_lrn((1, 3, 5, 5), 3, 3, 1, 1, 0.5)
-    verify_lrn((1, 3, 20, 20), 3, 1, 2, 1, 0.75)
+    verify_lrn((1, 3, 5, 5), 3, 1, 1.0, 1.0, 0.5)
+    verify_lrn((1, 3, 5, 5), 3, 3, 1.0, 1.0, 0.5)
+    verify_lrn((1, 3, 20, 20), 3, 1, 2.0, 1.0, 0.75)
 
 if __name__ == "__main__":
     test_lrn()
diff --git a/topi/tests/python_cpp/test_topi_l2norm.py b/topi/tests/python_cpp/test_topi_l2norm.py
new file mode 100644
index 000000000000..08799f76c5c3
--- /dev/null
+++ b/topi/tests/python_cpp/test_topi_l2norm.py
@@ -0,0 +1,48 @@
+"""Test code for l2 normalization"""
+import numpy as np
+import tvm
+import topi
+import logging
+from topi.util import get_const_tuple
+import topi.testing
+
+def verify_l2_normalize(shape, eps, axis=None):
+    '''Verify l2 normalization operator by comparing outputs from tvm and numpy implementation'''
+    A = tvm.placeholder(shape, name='A')
+    B = topi.cpp.nn.l2_normalize(A, eps, axis)
+    dtype = A.dtype
+
+    a_np = np.random.uniform(size=shape).astype(dtype)
+    b_np = topi.testing.l2_normalize_python(a_np, eps, axis)
+
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        target = topi.cpp.TEST_create_target(device)
+        if device == "llvm":
+            s = topi.cpp.generic.default_schedule(target, [B], False)
+        else:
+            s = topi.cpp.cuda.schedule_l2_normalize(target, [B])
+        ctx = tvm.context(device, 0)
+        a = tvm.nd.array(a_np, ctx)
+        b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
+        func = tvm.build(s, [A, B], device, name="l2_normalize")
+        func(a, b)
+        np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+    for device in ['cuda', 'opencl', 'metal', 'rocm', 'llvm']:
+        check_device(device)
+
+def test_l2_normalize():
+    verify_l2_normalize((1, 3, 20, 20), 0.001)
+    verify_l2_normalize((1, 3, 20, 20), 0.001, (1,))
+    verify_l2_normalize((1, 3, 20, 20), 0.001, (1, 2))
+    verify_l2_normalize((1, 3, 20, 20), 0.001, (2, 3))
+    verify_l2_normalize((1, 3, 20, 20), 0.001, (0, 3))
+    verify_l2_normalize((1, 3, 20, 20), 0.001, (0, 2, 3))
+
+if __name__ == "__main__":
+    logging.basicConfig(level=logging.DEBUG)
+    test_l2_normalize()
diff --git a/topi/tests/python_cpp/test_topi_lrn.py b/topi/tests/python_cpp/test_topi_lrn.py
new file mode 100644
index 000000000000..d685643a9406
--- /dev/null
+++ b/topi/tests/python_cpp/test_topi_lrn.py
@@ -0,0 +1,44 @@
+"""Test code for LRN"""
+import numpy as np
+import tvm
+import topi
+import logging
+from topi.util import get_const_tuple
+import topi.testing
+
+def verify_lrn(shape, size, axis, bias, alpha, beta):
+    '''Verify Local response normalization operator by comparing outputs from tvm and numpy implementation'''
+    A = tvm.placeholder(shape, name='A')
+    B = topi.cpp.nn.lrn(A, size, axis, alpha, beta, bias)
+    dtype = A.dtype
+
+    a_np = np.random.uniform(size=shape).astype(dtype)
+    b_np = topi.testing.lrn_python(a_np, size, axis, bias, alpha, beta)
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        target = topi.cpp.TEST_create_target(device)
+        if device == "llvm":
+            s = topi.cpp.generic.default_schedule(target, [B], False)
+        else:
+            s = topi.cpp.cuda.schedule_lrn(target, [B])
+        ctx = tvm.context(device, 0)
+        a = tvm.nd.array(a_np, ctx)
+        b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=dtype), ctx)
+        f = tvm.build(s, [A, B], device)
+        f(a, b)
+        np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-1)
+
+    for device in ['cuda', 'opencl', 'metal', 'rocm', 'llvm']:
+        check_device(device)
+
+def test_lrn():
+    verify_lrn((1, 3, 5, 5), 3, 3, 1.0, 1.0, 0.5)
+    verify_lrn((1, 3, 5, 5), 3, 3, 1.0, 1.0, 0.5)
+    verify_lrn((1, 3, 20, 20), 3, 1, 2.0, 1.0, 0.75)
+
+if __name__ == "__main__":
+    logging.basicConfig(level=logging.DEBUG)
+    test_lrn()

From 5fefedcbb62eb2d359e146789f1db40f59c36b68 Mon Sep 17 00:00:00 2001
From: Leyuan Wang <laurawly@gmail.com>
Date: Fri, 22 Jun 2018 11:46:47 -0700
Subject: [PATCH 743/949] [TOPI] Conv2d Added and Optimized for Intel HD
 Graphics (#1290)

---
 include/tvm/build_module.h                  |   6 +-
 python/tvm/target.py                        |   8 +-
 src/codegen/build_module.cc                 |   6 +-
 src/codegen/codegen_opencl.cc               |   2 +-
 src/runtime/opencl/opencl_device_api.cc     |   2 +-
 tests/python/integration/test_ewise.py      |   2 +-
 tests/python/unittest/test_lang_target.py   |   2 +-
 topi/python/topi/__init__.py                |   1 +
 topi/python/topi/cuda/pooling.py            |   5 +-
 topi/python/topi/intel_graphics/__init__.py |   5 +
 topi/python/topi/intel_graphics/conv2d.py   | 559 ++++++++++++++++++++
 11 files changed, 581 insertions(+), 17 deletions(-)
 create mode 100644 topi/python/topi/intel_graphics/__init__.py
 create mode 100644 topi/python/topi/intel_graphics/conv2d.py

diff --git a/include/tvm/build_module.h b/include/tvm/build_module.h
index d81462982965..96b876fe92f0 100644
--- a/include/tvm/build_module.h
+++ b/include/tvm/build_module.h
@@ -157,9 +157,9 @@ EXPORT Target rasp(const std::vector<std::string>& options =
 EXPORT Target mali(const std::vector<std::string>& options =
                    std::vector<std::string>());
 
-/*! \return A target for Intel GPU */
-EXPORT Target intel_gpu(const std::vector<std::string>& options =
-                   std::vector<std::string>());
+/*! \return A target for Intel Graphics */
+EXPORT Target intel_graphics(const std::vector<std::string>& options =
+                             std::vector<std::string>());
 
 /*! \return A target for stackvm */
 EXPORT Target stackvm(const std::vector<std::string>& options =
diff --git a/python/tvm/target.py b/python/tvm/target.py
index d62a7be7aac8..7706fb312f9d 100644
--- a/python/tvm/target.py
+++ b/python/tvm/target.py
@@ -76,7 +76,7 @@ class Target(NodeBase):
     - :any:`tvm.target.cuda` create CUDA target
     - :any:`tvm.target.rocm` create ROCM target
     - :any:`tvm.target.mali` create Mali target
-    - :any:`tvm.target.intel_gpu` create Intel GPU target
+    - :any:`tvm.target.intel_graphics` create Intel Graphics target
     """
     def __init__(self, handle):
         super(Target, self).__init__(handle)
@@ -402,15 +402,15 @@ def mali(options=None):
     return _api_internal._TargetCreate("opencl", *opts)
 
 
-def intel_gpu(options=None):
-    """Returns an Intel GPU target.
+def intel_graphics(options=None):
+    """Returns an Intel Graphics target.
 
     Parameters
     ----------
     options : str or list of str
         Additional options
     """
-    opts = ["-device=intel_gpu"]
+    opts = ["-device=intel_graphics"]
     opts = _merge_opts(opts, options)
     return _api_internal._TargetCreate("opencl", *opts)
 
diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc
index 318e231fc67c..ea94f05d4779 100644
--- a/src/codegen/build_module.cc
+++ b/src/codegen/build_module.cc
@@ -76,7 +76,7 @@ Target CreateTarget(const std::string& target_name,
     t->keys_array.push_back(ir::StringImm::make("rocm"));
     t->keys_array.push_back(ir::StringImm::make("gpu"));
     t->max_num_threads = 256;
-    if (t->device_name == "intel_gpu") {
+    if (t->device_name == "intel_graphics") {
       t->thread_warp_size = 16;
     }
   } else if (target_name == "metal" || target_name == "vulkan") {
@@ -274,9 +274,9 @@ Target mali(const std::vector<std::string>& options) {
   }));
 }
 
-Target intel_gpu(const std::vector<std::string>& options) {
+Target intel_graphics(const std::vector<std::string>& options) {
   return CreateTarget("opencl", MergeOptions(options, {
-    "-device=intel_gpu"
+    "-device=intel_graphics"
   }));
 }
 
diff --git a/src/codegen/codegen_opencl.cc b/src/codegen/codegen_opencl.cc
index 64e10ffbe290..20053c829be6 100644
--- a/src/codegen/codegen_opencl.cc
+++ b/src/codegen/codegen_opencl.cc
@@ -159,7 +159,7 @@ void CodeGenOpenCL::PrintStorageSync(const Call* op) {
   const std::string& sync = op->args[0].as<StringImm>()->value;
   if (sync == "warp") {
     this->PrintIndent();
-    this->stream << "sub_group_barrier(CLK_LOCAL_MEM_FENCE);\n";
+    this->stream << "barrier(CLK_LOCAL_MEM_FENCE);\n";
   } else if (sync == "shared") {
     this->PrintIndent();
     this->stream << "barrier(CLK_LOCAL_MEM_FENCE);\n";
diff --git a/src/runtime/opencl/opencl_device_api.cc b/src/runtime/opencl/opencl_device_api.cc
index 759f074b2fc1..b30b058d3acc 100644
--- a/src/runtime/opencl/opencl_device_api.cc
+++ b/src/runtime/opencl/opencl_device_api.cc
@@ -40,7 +40,7 @@ void OpenCLWorkspace::GetAttr(
     }
     case kWarpSize: {
       /* TODO: the warp size of OpenCL device is not always 1
-               e.g. Intel GPU has a sub group concept which contains 8 - 32 work items,
+               e.g. Intel Graphics has a sub group concept which contains 8 - 32 work items,
                corresponding to the number of SIMD entries the heardware configures.
                We need to figure out a way to query this information from the hardware.
       */
diff --git a/tests/python/integration/test_ewise.py b/tests/python/integration/test_ewise.py
index e9c69bb57d9f..64d164c5e644 100644
--- a/tests/python/integration/test_ewise.py
+++ b/tests/python/integration/test_ewise.py
@@ -34,7 +34,7 @@ def check_device(device, host="stackvm"):
         np.testing.assert_allclose(
             b.asnumpy(), np.exp(a.asnumpy()), rtol=1e-5)
 
-    check_device("opencl -device=intel_gpu")
+    check_device("opencl -device=intel_graphics")
     check_device("cuda", "llvm")
     check_device("vulkan")
 
diff --git a/tests/python/unittest/test_lang_target.py b/tests/python/unittest/test_lang_target.py
index 71b4f79a58a2..5f77010fa751 100644
--- a/tests/python/unittest/test_lang_target.py
+++ b/tests/python/unittest/test_lang_target.py
@@ -47,7 +47,7 @@ def test_target_string_parse():
     assert str(target) == str(tvm.target.cuda("-libs=cublas,cudnn"))
 
 
-    assert tvm.target.intel_gpu().device_name == "intel_gpu"
+    assert tvm.target.intel_graphics().device_name == "intel_graphics"
 
 if __name__ == "__main__":
     test_target_dispatch()
diff --git a/topi/python/topi/__init__.py b/topi/python/topi/__init__.py
index 59549f89905b..d58b37b04518 100644
--- a/topi/python/topi/__init__.py
+++ b/topi/python/topi/__init__.py
@@ -26,6 +26,7 @@
 from . import cuda
 from . import rasp
 from . import mali
+from . import intel_graphics
 from . import opengl
 from . import util
 from . import rocm
diff --git a/topi/python/topi/cuda/pooling.py b/topi/python/topi/cuda/pooling.py
index 017408f14b89..ed4419491428 100644
--- a/topi/python/topi/cuda/pooling.py
+++ b/topi/python/topi/cuda/pooling.py
@@ -33,9 +33,8 @@ def _schedule(Pool):
         else:
             Out = outs[0].op.output(0)
             s[Pool].set_scope("local")
-        i, c, h, w = s[Out].op.axis
-        by, ty = s[Out].split(i, factor=num_thread)
-        bx, tx = s[Out].split(c, factor=num_thread)
+        by, ty = s[Out].split(s[Out].op.axis[0], factor=num_thread)
+        bx, tx = s[Out].split(s[Out].op.axis[1], factor=num_thread)
         s[Out].reorder(by, bx, ty, tx)
         s[Out].bind(ty, thread_y)
         s[Out].bind(tx, thread_x)
diff --git a/topi/python/topi/intel_graphics/__init__.py b/topi/python/topi/intel_graphics/__init__.py
new file mode 100644
index 000000000000..336b1508f977
--- /dev/null
+++ b/topi/python/topi/intel_graphics/__init__.py
@@ -0,0 +1,5 @@
+# pylint: disable=redefined-builtin, wildcard-import
+"""Intel Gen9 GPU specific declaration and schedules."""
+from __future__ import absolute_import as _abs
+
+from .conv2d import *
diff --git a/topi/python/topi/intel_graphics/conv2d.py b/topi/python/topi/intel_graphics/conv2d.py
new file mode 100644
index 000000000000..fa16e0585d51
--- /dev/null
+++ b/topi/python/topi/intel_graphics/conv2d.py
@@ -0,0 +1,559 @@
+# pylint: disable=invalid-name,unused-variable,unused-argument,no-else-return
+"""conv2d schedule on Intel Graphics"""
+
+from __future__ import absolute_import as _abs
+
+import tvm
+
+from .. import generic
+from .. import util
+from .. import tag
+from ..nn import pad
+from ..nn.conv2d import conv2d, conv2d_NCHWc, conv2d_alter_layout, _get_workload
+from ..nn.util import get_pad_tuple
+from ..util import simplify
+
+
+
+##### SCHEDULE UTILITIES #####
+def tile_and_bind3d(s, tensor, z, y, x, z_factor=2, y_factor=None, x_factor=None):
+    """ tile and bind 3d """
+    y_factor = y_factor or z_factor
+    x_factor = x_factor or y_factor
+    zo, zi = s[tensor].split(z, z_factor)
+    yo, yi = s[tensor].split(y, y_factor)
+    xo, xi = s[tensor].split(x, x_factor)
+    s[tensor].reorder(zo, yo, xo, zi, yi, xi)
+
+    thread_z = tvm.thread_axis((0, z_factor), "threadIdx.z")
+    thread_y = tvm.thread_axis((0, y_factor), "threadIdx.y")
+    thread_x = tvm.thread_axis((0, x_factor), "threadIdx.x")
+    s[tensor].bind(zo, tvm.thread_axis("blockIdx.z"))
+    s[tensor].bind(zi, thread_z)
+    s[tensor].bind(yo, tvm.thread_axis("blockIdx.y"))
+    s[tensor].bind(yi, thread_y)
+    s[tensor].bind(xo, tvm.thread_axis("blockIdx.x"))
+    s[tensor].bind(xi, thread_x)
+    return xi, thread_z, thread_y, thread_x
+
+@conv2d_alter_layout.register(["intel_graphics"])
+def _alter_conv2d_layout(attrs, inputs, tinfos):
+    import nnvm.symbol as sym
+    copy_inputs = [s for s in inputs]
+
+    data = tinfos[0]
+    kernel = tinfos[1]
+
+    import ast
+    padding = ast.literal_eval(attrs['padding'])
+    stride = ast.literal_eval(attrs['strides'])
+
+    wkl = _get_workload(data, kernel, stride, padding, data.dtype)
+    oc_bn = 16
+
+    new_attrs = {k: attrs[k] for k in attrs.keys()}
+    new_attrs['kernel_layout'] = 'OIHW%do' % (oc_bn)
+
+    return sym.contrib.conv2d_NCHWc(*copy_inputs, **new_attrs)
+
+@conv2d_NCHWc.register(["intel_graphics"])
+def _decl_conv2d(data, kernel, num_filter, kernel_size, stride, padding, out_dtype='float32'):
+    """Conv2D operator for Intel Graphics backend.
+
+    Parameters
+    ----------
+    data : tvm.Tensor
+        4-D with shape [batch, in_channel, in_height, in_width]
+
+    kernel : tvm.Tensor
+        5-D with shape [num_filter, in_channel, filter_height, filter_width, nnum_filter_vec]
+
+    stride : int or a list/tuple of two ints
+        stride size, or [stride_height, stride_width]
+
+    padding : int or a list/tuple of two ints
+        padding size, or [pad_height, pad_width]
+
+    layout : str
+        layout of data
+
+    Returns
+    -------
+    output : tvm.Tensor
+        4-D with shape [batch, out_channel, out_height, out_width]
+    """
+    assert data.shape[0].value == 1, "only support batch size=1 convolution on intel gpu"
+    assert data.dtype == kernel.dtype, "Do not support inputs with different data types now."
+
+    out_dtype = data.dtype
+    HPAD, WPAD, _, _ = get_pad_tuple(padding, kernel)
+    kernel_shape = util.get_const_tuple(kernel.shape)
+    if isinstance(stride, (tuple, list)):
+        HSTR, WSTR = stride
+    else:
+        HSTR, WSTR = stride, stride
+
+    return _decl_cl_spatialpack_NCHWc(data, kernel, stride, padding, out_dtype)
+
+@generic.schedule_conv2d_NCHWc.register(["intel_graphics"])
+def schedule_conv2d_NCHWc(num_filter, kernel_size, stride, padding, outs):
+    """Schedule for conv2d_nchw for Intel Graphics
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+        The computation graph description of conv2d_nchw
+        in the format of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+        The computation schedule for conv2d_nchw.
+    """
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    s = tvm.create_schedule([x.op for x in outs])
+
+    def traverse(op):
+        """inline all one-to-one-mapping operators except the last stage (output)"""
+        if tag.is_broadcast(op.tag):
+            if op not in s.outputs:
+                s[op].compute_inline()
+            for tensor in op.input_tensors:
+                if tensor.op.input_tensors:
+                    traverse(tensor.op)
+        if "4_5" in op.tag or "4_4" in op.tag or "2_7" in op.tag or "2_14" in op.tag \
+           or "1_16" in op.tag:
+            _schedule_cl_spatialpack_NCHWc(s, op)
+
+    traverse(outs[0].op)
+
+    return s
+
+def _decl_cl_spatialpack_NCHWc(data, kernel, stride, padding, out_dtype='float16'):
+    batch, in_channel, in_height, in_width = [util.get_const_int(x) for x in data.shape]
+    num_filter, channel, kernel_h, kernel_w, nv = [util.get_const_int(x) for x in kernel.shape]
+    num_filter = num_filter * nv
+    pad_top, pad_left, pad_down, pad_right = get_pad_tuple(padding, kernel)
+
+    if isinstance(stride, (tuple, list)):
+        stride_h, stride_w = stride
+    else:
+        stride_h, stride_w = stride, stride
+
+    out_channel = num_filter
+    out_height = simplify((in_height - kernel_h + pad_top + pad_down) // stride_h + 1)
+    out_width = simplify((in_width - kernel_w + pad_left + pad_right) // stride_w + 1)
+    oshape = (batch, out_channel, out_height, out_width)
+    pad_before = [0, 0, pad_top, pad_left]
+    pad_after = [0, 0, pad_down, pad_right]
+    temp = pad(data, pad_before, pad_after, name="pad_temp")
+
+    rc = tvm.reduce_axis((0, in_channel), name='rc')
+    ry = tvm.reduce_axis((0, kernel_h), name='ry')
+    rx = tvm.reduce_axis((0, kernel_w), name='rx')
+
+    block_w = 0
+    block_h = 0
+    if stride_h == 2:
+        if num_filter + kernel_h == 515:
+            conv_tag = "4_4"
+            block_h = 4
+            block_w = 4
+        else:
+            conv_tag = "4_5"
+            block_h = 4
+            block_w = 5
+    elif kernel_h == 3:
+        if num_filter == 512:
+            conv_tag = "2_7"
+            block_h = 2
+            block_w = 7
+        else:
+            conv_tag = "2_14"
+            block_h = 2
+            block_w = 14
+    else:
+        conv_tag = "1_16"
+        block_h = 1
+        block_w = 16
+
+    c_h = out_height
+    c_w = out_width
+
+    if not out_height % block_h == 0:
+        c_h = (out_height // block_h + 1) * block_h
+
+    if not out_width % block_w == 0:
+        c_w = (out_width // block_w + 1) * block_w
+
+    cshape = (batch, out_channel // nv, c_h, c_w, nv)
+
+    conv = tvm.compute(
+        cshape,
+        lambda nn, ff, yy, xx, vc:\
+          tvm.sum(
+              temp[nn, rc, yy * stride_h + ry, xx * stride_w + rx].astype(out_dtype) *
+              kernel[ff, rc, ry, rx, vc].astype(out_dtype),
+              axis=[rc, ry, rx]), tag=conv_tag, name='conv')
+
+    output = tvm.compute(
+        oshape,
+        lambda nn, ff, yy, xx:
+        conv[nn][ff//nv][yy][xx][ff%nv],
+        name='output_unpack', tag=conv_tag)
+
+    return output
+
+def _schedule_cl_spatialpack_NCHWc(s, op):
+    output = op.output(0)
+    _, _, out_height, out_width = [util.get_const_int(x) for x in output.shape]
+
+    conv = op.input_tensors[0]
+    temp = s[conv].op.input_tensors[0]
+    kernel = s[conv].op.input_tensors[1]
+    temp_W = s.cache_read(temp, "warp", [conv])
+    conv_L = s.cache_write(conv, "local")
+
+    kernel_L = s.cache_read(kernel, "local", [conv_L])
+    _, in_channel, temp_h, temp_w = [util.get_const_int(x) for x in temp.shape]
+    if "1_16" in s[conv].op.tag:
+        OUTPUT_BLOCK_HEIGHT = 1
+        OUTPUT_BLOCK_WIDTH = 16
+    elif "2_14" in s[conv].op.tag:
+        OUTPUT_BLOCK_HEIGHT = 2
+        OUTPUT_BLOCK_WIDTH = 14
+    elif "2_7" in s[conv].op.tag:
+        OUTPUT_BLOCK_HEIGHT = 2
+        OUTPUT_BLOCK_WIDTH = 7
+    elif "4_5" in s[conv].op.tag:
+        OUTPUT_BLOCK_HEIGHT = 4
+        OUTPUT_BLOCK_WIDTH = 5
+    elif "4_4" in s[conv].op.tag:
+        OUTPUT_BLOCK_HEIGHT = 4
+        OUTPUT_BLOCK_WIDTH = 4
+
+    # schedule conv
+    z_factor = 1
+    y_factor = 1
+    x_factor = 16
+    thread_z = tvm.thread_axis((0, z_factor), "threadIdx.z")
+    thread_y = tvm.thread_axis((0, y_factor), "threadIdx.y")
+    thread_x = tvm.thread_axis((0, x_factor), "threadIdx.x")
+    _, co, oh, ow, vc = s[conv].op.axis
+    ooh, ioh = s[conv].split(oh, factor=OUTPUT_BLOCK_HEIGHT)
+    oow, iow = s[conv].split(ow, factor=OUTPUT_BLOCK_WIDTH)
+    s[conv].reorder(_, co, ooh, oow, vc, ioh, iow)
+    coo, coi = s[conv].split(co, nparts=1)
+    ooho, oohi = s[conv].split(ooh, factor=z_factor)
+    oowo, oowi = s[conv].split(oow, factor=y_factor)
+    vco, vci = s[conv].split(vc, factor=x_factor)
+    s[conv].reorder(_, coo, vco, ooho, oowo, coi, oohi, oowi, vci, ioh, iow)
+    s[conv].bind(oohi, thread_z)
+    s[conv].bind(oowi, thread_y)
+    s[conv].bind(vci, thread_x)
+    s[conv].bind(ooho, tvm.thread_axis("blockIdx.z"))
+    s[conv].bind(oowo, tvm.thread_axis("blockIdx.y"))
+    s[conv].bind(coi, tvm.thread_axis("blockIdx.x"))
+
+    # schedule conv_L
+    s[conv_L].compute_at(s[conv], vci)
+    i, oc, h, w, vc = s[conv_L].op.axis
+    rc, ry, rx = s[conv_L].op.reduce_axis
+    if in_channel == 2048:
+        rco, rci = s[conv_L].split(rc, nparts=128)
+        s[conv_L].unroll(rci)
+        s[conv_L].reorder(i, oc, rco, rci, ry, rx, vc, h, w)
+        s[temp_W].compute_at(s[conv_L], rco)
+    else:
+        s[conv_L].reorder(i, oc, rc, ry, rx, vc, h, w)
+        s[temp_W].compute_at(s[conv_L], rc)
+    if kernel.shape[3].value != 7:
+        s[conv_L].unroll(ry)
+        s[conv_L].unroll(rx)
+    if kernel.shape[3].value != 7:
+        s[conv_L].unroll(ry)
+        s[conv_L].unroll(rx)
+
+    # schedule temp
+    _, ci, h, w = s[temp].op.axis
+    tile_and_bind3d(s, temp, ci, h, w, 1, 16, 16)
+
+    # schedule temp_W
+    _, ci, h, w = s[temp_W].op.axis
+    zo, zi = s[temp_W].split(ci, 1)
+    yo, yi = s[temp_W].split(h, 1)
+    xo, xi = s[temp_W].split(w, 16)
+    s[temp_W].reorder(zo, yo, xo, zi, yi, xi)
+    s[temp_W].bind(zi, thread_z)
+    s[temp_W].bind(yi, thread_y)
+    s[temp_W].bind(xi, thread_x)
+    s[temp_W].storage_align(s[temp_W].op.axis[2], 16, 0)
+
+    #schedule kernel
+
+    # schedule kernel_L
+    if "2_14" in s[conv].op.tag:
+        s[kernel_L].compute_at(s[conv_L], ry)
+    else:
+        s[kernel_L].compute_at(s[conv_L], rx)
+
+    # schedule output
+    if output.op in s.outputs:
+        out = output
+    else:
+        s[output].compute_inline()
+        out = s.outputs[0]
+
+    _, co, h, w = s[out].op.axis
+    tile_and_bind3d(s, out, w, h, co, 4, 8, 8)
+
+
+@conv2d.register(["intel_graphics"])
+def decl_conv2d(data, kernel, stride, padding, layout='NCHW', out_dtype='float32'):
+    """Conv2D operator for Intel Graphics backend.
+
+    Parameters
+    ----------
+    data : tvm.Tensor
+        4-D with shape [batch, in_channel, in_height, in_width]
+    kernel : tvm.Tensor
+        4-D with shape [num_filter, in_channel, filter_height, filter_width]
+    stride : int or a list/tuple of two ints
+        stride size, or [stride_height, stride_width]
+    padding : int or a list/tuple of two ints
+        padding size, or [pad_height, pad_width]
+    layout : str
+        layout of data
+    Returns
+    -------
+    output : tvm.Tensor
+        4-D with shape [batch, out_channel, out_height, out_width]
+    """
+    assert layout == 'NCHW', "only support NCHW convolution on intel gpu"
+    assert data.shape[0].value == 1, "only support batch size=1 convolution on intel gpu"
+    assert data.dtype == kernel.dtype, "Do not support inputs with different data types now."
+
+    out_dtype = data.dtype
+    HPAD, WPAD, _, _ = get_pad_tuple(padding, kernel)
+    kernel_shape = util.get_const_tuple(kernel.shape)
+    if isinstance(stride, (tuple, list)):
+        HSTR, WSTR = stride
+    else:
+        HSTR, WSTR = stride, stride
+
+    return _decl_cl_spatialpack(data, kernel, stride, padding, layout, out_dtype)
+
+@generic.schedule_conv2d_nchw.register(["intel_graphics"])
+def schedule_conv2d_nchw(outs):
+    """Schedule for conv2d_nchw for Intel Graphics
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+        The computation graph description of conv2d_nchw
+        in the format of an array of tensors.
+    Returns
+    -------
+    s: Schedule
+        The computation schedule for conv2d_nchw.
+    """
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    s = tvm.create_schedule([x.op for x in outs])
+
+    def traverse(op):
+        """inline all one-to-one-mapping operators except the last stage (output)"""
+        if tag.is_broadcast(op.tag):
+            if op not in s.outputs:
+                s[op].compute_inline()
+            for tensor in op.input_tensors:
+                if tensor.op.input_tensors:
+                    traverse(tensor.op)
+        if "4_5" in op.tag or "4_4" in op.tag or "2_7" in op.tag or "2_14" in op.tag \
+           or "1_16" in op.tag:
+            _schedule_cl_spatialpack(s, op)
+
+    traverse(outs[0].op)
+    return s
+
+def _decl_cl_spatialpack(data, kernel, stride, padding, layout, out_dtype='float16'):
+    batch, in_channel, in_height, in_width = [util.get_const_int(x) for x in data.shape]
+    num_filter, channel, kernel_h, kernel_w = [util.get_const_int(x) for x in kernel.shape]
+    pad_top, pad_left, pad_down, pad_right = get_pad_tuple(padding, kernel)
+
+    if isinstance(stride, (tuple, list)):
+        stride_h, stride_w = stride
+    else:
+        stride_h, stride_w = stride, stride
+
+    out_channel = num_filter
+    out_height = simplify((in_height - kernel_h + pad_top + pad_down) // stride_h + 1)
+    out_width = simplify((in_width - kernel_w + pad_left + pad_right) // stride_w + 1)
+    oshape = (batch, out_channel, out_height, out_width)
+    pad_before = [0, 0, pad_top, pad_left]
+    pad_after = [0, 0, pad_down, pad_right]
+    temp = pad(data, pad_before, pad_after, name="pad_temp")
+
+    rc = tvm.reduce_axis((0, in_channel), name='rc')
+    ry = tvm.reduce_axis((0, kernel_h), name='ry')
+    rx = tvm.reduce_axis((0, kernel_w), name='rx')
+
+    block_w = 0
+    block_h = 0
+    if stride_h == 2:
+        if num_filter + kernel_h == 515:
+            conv_tag = "4_4"
+            block_h = 4
+            block_w = 4
+        else:
+            conv_tag = "4_5"
+            block_h = 4
+            block_w = 5
+    elif kernel_h == 3:
+        if num_filter == 512:
+            conv_tag = "2_7"
+            block_h = 2
+            block_w = 7
+        else:
+            conv_tag = "2_14"
+            block_h = 2
+            block_w = 14
+    else:
+        conv_tag = "1_16"
+        block_h = 1
+        block_w = 16
+
+    c_h = out_height
+    c_w = out_width
+
+    if not out_height % block_h == 0:
+        c_h = (out_height // block_h + 1) * block_h
+
+    if not out_width % block_w == 0:
+        c_w = (out_width // block_w + 1) * block_w
+
+    nv = 16
+    cshape = (batch, out_channel // nv, c_h, c_w, nv)
+    kvshape = (num_filter // nv, channel, kernel_h, kernel_w, nv)
+
+    kernel_vec = tvm.compute(
+        kvshape,
+        lambda co, ci, kh, kw, vc:
+        kernel[co*nv + vc][ci][kh][kw], name='kernel_vec')
+
+    conv = tvm.compute(
+        cshape,
+        lambda nn, ff, yy, xx, vc:\
+          tvm.sum(
+              temp[nn, rc, yy * stride_h + ry, xx * stride_w + rx].astype(out_dtype) *
+              kernel_vec[ff, rc, ry, rx, vc].astype(out_dtype),
+              axis=[rc, ry, rx]), tag=conv_tag, name='conv')
+
+    output = tvm.compute(
+        oshape,
+        lambda nn, ff, yy, xx:
+        conv[nn][ff//nv][yy][xx][ff%nv],
+        name='output_unpack', tag=conv_tag)
+
+    return output
+
+def _schedule_cl_spatialpack(s, op):
+    output = op.output(0)
+    _, _, out_height, out_width = [util.get_const_int(x) for x in output.shape]
+
+    conv = op.input_tensors[0]
+    temp = s[conv].op.input_tensors[0]
+    kernel_vec = s[conv].op.input_tensors[1]
+    kernel = s[kernel_vec].op.input_tensors[0]
+    temp_W = s.cache_read(temp, "warp", [conv])
+    conv_L = s.cache_write(conv, "local")
+
+    kernel_L = s.cache_read(kernel_vec, "local", [conv_L])
+    _, in_channel, temp_h, temp_w = [util.get_const_int(x) for x in temp.shape]
+
+    if "1_16" in s[conv].op.tag:
+        OUTPUT_BLOCK_HEIGHT = 1
+        OUTPUT_BLOCK_WIDTH = 16
+    elif "2_14" in s[conv].op.tag:
+        OUTPUT_BLOCK_HEIGHT = 2
+        OUTPUT_BLOCK_WIDTH = 14
+    elif "2_7" in s[conv].op.tag:
+        OUTPUT_BLOCK_HEIGHT = 2
+        OUTPUT_BLOCK_WIDTH = 7
+    elif "4_5" in s[conv].op.tag:
+        OUTPUT_BLOCK_HEIGHT = 4
+        OUTPUT_BLOCK_WIDTH = 5
+    elif "4_4" in s[conv].op.tag:
+        OUTPUT_BLOCK_HEIGHT = 4
+        OUTPUT_BLOCK_WIDTH = 4
+
+    # schedule conv
+    z_factor = 1
+    y_factor = 1
+    x_factor = 16
+    thread_z = tvm.thread_axis((0, z_factor), "threadIdx.z")
+    thread_y = tvm.thread_axis((0, y_factor), "threadIdx.y")
+    thread_x = tvm.thread_axis((0, x_factor), "threadIdx.x")
+    _, co, oh, ow, vc = s[conv].op.axis
+    ooh, ioh = s[conv].split(oh, factor=OUTPUT_BLOCK_HEIGHT)
+    oow, iow = s[conv].split(ow, factor=OUTPUT_BLOCK_WIDTH)
+    s[conv].reorder(_, co, ooh, oow, vc, ioh, iow)
+    coo, coi = s[conv].split(co, nparts=1)
+    ooho, oohi = s[conv].split(ooh, factor=z_factor)
+    oowo, oowi = s[conv].split(oow, factor=y_factor)
+    vco, vci = s[conv].split(vc, factor=x_factor)
+    s[conv].reorder(_, coo, vco, ooho, oowo, coi, oohi, oowi, vci, ioh, iow)
+    s[conv].bind(oohi, thread_z)
+    s[conv].bind(oowi, thread_y)
+    s[conv].bind(vci, thread_x)
+    s[conv].bind(ooho, tvm.thread_axis("blockIdx.z"))
+    s[conv].bind(oowo, tvm.thread_axis("blockIdx.y"))
+    s[conv].bind(coi, tvm.thread_axis("blockIdx.x"))
+
+    # schedule conv_L
+    s[conv_L].compute_at(s[conv], vci)
+    i, oc, h, w, vc = s[conv_L].op.axis
+    rc, ry, rx = s[conv_L].op.reduce_axis
+    if in_channel == 2048:
+        rco, rci = s[conv_L].split(rc, nparts=128)
+        s[conv_L].unroll(rci)
+        s[conv_L].reorder(i, oc, rco, rci, ry, rx, vc, h, w)
+        s[temp_W].compute_at(s[conv_L], rco)
+    else:
+        s[conv_L].reorder(i, oc, rc, ry, rx, vc, h, w)
+        s[temp_W].compute_at(s[conv_L], rc)
+    if kernel.shape[3].value != 7:
+        s[conv_L].unroll(ry)
+        s[conv_L].unroll(rx)
+
+    # schedule temp
+    _, ci, h, w = s[temp].op.axis
+    tile_and_bind3d(s, temp, ci, h, w, 1, 16, 16)
+
+    # schedule temp_W
+    _, ci, h, w = s[temp_W].op.axis
+    zo, zi = s[temp_W].split(ci, 1)
+    yo, yi = s[temp_W].split(h, 1)
+    xo, xi = s[temp_W].split(w, 16)
+    s[temp_W].reorder(zo, yo, xo, zi, yi, xi)
+    s[temp_W].bind(zi, thread_z)
+    s[temp_W].bind(yi, thread_y)
+    s[temp_W].bind(xi, thread_x)
+    s[temp_W].storage_align(s[temp_W].op.axis[2], 16, 0)
+
+    s[kernel_vec].compute_inline()
+
+    # schedule kernel_L
+    if "2_14" in s[conv].op.tag:
+        s[kernel_L].compute_at(s[conv_L], ry)
+    else:
+        s[kernel_L].compute_at(s[conv_L], rx)
+
+    # schedule output
+    if output.op in s.outputs:
+        out = output
+    else:
+        s[output].compute_inline()
+        out = s.outputs[0]
+
+    _, co, h, w = s[out].op.axis
+    tile_and_bind3d(s, out, w, h, co, 4, 8, 8)

From 5dbc74ce141616fd35dff0bfa1ae583a826b88fd Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@cs.washington.edu>
Date: Fri, 22 Jun 2018 12:09:05 -0700
Subject: [PATCH 744/949] moving module import inside of download function
 (#1319)

---
 python/tvm/contrib/download.py | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/python/tvm/contrib/download.py b/python/tvm/contrib/download.py
index 7f761f69b252..6e86ca1daabf 100644
--- a/python/tvm/contrib/download.py
+++ b/python/tvm/contrib/download.py
@@ -5,12 +5,6 @@
 import os
 import sys
 import time
-import requests
-
-if sys.version_info >= (3,):
-    import urllib.request as urllib2
-else:
-    import urllib2
 
 def download(url, path, overwrite=False, size_compare=False):
     """Downloads the file from the internet.
@@ -30,6 +24,13 @@ def download(url, path, overwrite=False, size_compare=False):
     size_compare : bool, optional
         Whether to do size compare to check downloaded file.
     """
+
+    import requests
+    if sys.version_info >= (3,):
+        import urllib.request as urllib2
+    else:
+        import urllib2
+
     if os.path.isfile(path) and not overwrite:
         if size_compare:
             file_size = os.path.getsize(path)

From 290226e1c9adbb3e598f9ed9184018df1c12be33 Mon Sep 17 00:00:00 2001
From: Jian Weng <werefluke@gmail.com>
Date: Fri, 22 Jun 2018 13:47:45 -0700
Subject: [PATCH 745/949] [FRONTEND] A Python hybrid frontend (#1251)

---
 docs/api/python/hybrid.rst                  |  15 +
 docs/api/python/index.rst                   |   1 +
 docs/dev/hybrid_script.rst                  |  76 +++++
 docs/dev/index.rst                          |   1 +
 docs/langref/hybrid_script.rst              | 172 ++++++++++
 docs/langref/index.rst                      |   5 +
 python/tvm/build_module.py                  |  18 +-
 python/tvm/hybrid/__init__.py               |  10 +
 python/tvm/hybrid/api.py                    |  46 +++
 python/tvm/hybrid/intrin.py                 | 112 +++++++
 python/tvm/hybrid/parser.py                 | 342 ++++++++++++++++++++
 python/tvm/hybrid/util.py                   |  76 +++++
 python/tvm/hybrid/var_decl.py               |  76 +++++
 tests/python/unittest/test_hybrid_script.py | 287 ++++++++++++++++
 14 files changed, 1232 insertions(+), 5 deletions(-)
 create mode 100644 docs/api/python/hybrid.rst
 create mode 100644 docs/dev/hybrid_script.rst
 create mode 100644 docs/langref/hybrid_script.rst
 create mode 100644 python/tvm/hybrid/__init__.py
 create mode 100644 python/tvm/hybrid/api.py
 create mode 100644 python/tvm/hybrid/intrin.py
 create mode 100644 python/tvm/hybrid/parser.py
 create mode 100644 python/tvm/hybrid/util.py
 create mode 100644 python/tvm/hybrid/var_decl.py
 create mode 100644 tests/python/unittest/test_hybrid_script.py

diff --git a/docs/api/python/hybrid.rst b/docs/api/python/hybrid.rst
new file mode 100644
index 000000000000..3b4c598d82dd
--- /dev/null
+++ b/docs/api/python/hybrid.rst
@@ -0,0 +1,15 @@
+tvm.hybrid
+----------
+.. automodule:: tvm.hybrid
+
+.. autosummary::
+
+   tvm.hybrid.parse
+   tvm.hybrid.script
+   tvm.hybrid.popcount
+   tvm.hybrid.sigmoid
+
+.. autofunction:: tvm.hybrid.parse
+.. autofunction:: tvm.hybrid.script
+.. autofunction:: tvm.hybrid.popcount
+.. autofunction:: tvm.hybrid.sigmoid
diff --git a/docs/api/python/index.rst b/docs/api/python/index.rst
index a6bed557dd3b..bab29b82f473 100644
--- a/docs/api/python/index.rst
+++ b/docs/api/python/index.rst
@@ -21,3 +21,4 @@ Python API
    dev
    topi
    nnvm/index
+   hybrid
diff --git a/docs/dev/hybrid_script.rst b/docs/dev/hybrid_script.rst
new file mode 100644
index 000000000000..0af02a56e72c
--- /dev/null
+++ b/docs/dev/hybrid_script.rst
@@ -0,0 +1,76 @@
+Hybrid Frontend Developer Guide
+===============================
+
+If you are a developer:
+
+1. who is trying writing some preliminary patterns that have not been supported by TVM yet,
+maybe :ref:`hybrid-langref-label` is a better place for you.
+
+2. who wants to know the implementing details of this module, you are right here!
+
+Features
+--------
+
+Software emulation
+~~~~~~~~~~~~~~~~~~
+
+In software emulation, the most intresting thing is the decorator ``tvm.hybrid.script``.
+This decorator helps 2 things:
+
+1. Importing runtime variables
+
+2. Overload the function according to the arguments passed
+
+Correct me if I am wrong: I believe that how 1. is implemented is dangerous, but I have no
+choice. What I did is add those names into python dict ``func.__global__`` and after
+the call to ``func`` is done, those names will be cleaned up. 
+
+Overload is simple: the decorator checks the arguments' types and determines which function
+should be actually called.
+
+
+Backend Compilation
+~~~~~~~~~~~~~~~~~~~
+
+Compilation is a large module, you can see ``python/tvm/hybrid/var_decl.py`` and
+``python/tvm/hybrid/parser.py`` for more details. The first stage determines the
+usage, or more accurately the declaration of each variable and the second stage does
+the actual IR generation.
+
+Attributes
+~~~~~~~~~~
+
+So far, ONLY tensors' `shape` attribute is supported. You can see ``visit_Subscript``
+in ``python/tvm/hybrid/parser.py`` for more details. This is a hacky solution, I just
+check the attributes when subscript.
+
+Loops
+~~~~~
+
+In HalideIR, loops have in total 4 types: ``serial``, ``unrolled``, ``parallel``, and ``vectorized``.
+
+
+.. note::
+
+    Unlike what that is in HalideIR, in ``loop_type(a, b)``, ``a`` is the starting point and ``b``
+    is the trip count of iterations. Here ``loop_type(a, b)`` indicates ``[a, b)``. Thus, when lowering it
+    to HalideIR, we need to do ``start, extent = a, b - a``
+
+
+.. note::
+
+    In HalideIR those are enums, they are in passive form.
+    Here we use active form to annotate loops, because they are ready to run.
+
+
+Variables
+~~~~~~~~~
+
+Because there is no variables in ``HalideIR``, all the mutatable variables will be lowered to an array with size 1.
+It takes the first store of a variable as its declaration.
+
+Math intrinsics
+~~~~~~~~~~~~~~~
+So far, these math intrinsics, ``log``, ``exp``, ``sigmoid``, ``tanh``, ``power``, and ``popcount``, are supported.
+Math intrinsics will be imported by the decorator. Most of the intrinsics are borrowed by library implementation
+except ``popcount`` and ``sigmoid``. I implemented them manually.
diff --git a/docs/dev/index.rst b/docs/dev/index.rst
index 3fb052938689..f3ab322bfe53 100644
--- a/docs/dev/index.rst
+++ b/docs/dev/index.rst
@@ -10,3 +10,4 @@ In this part of documentation, we share the rationale for the specific choices m
    runtime
    nnvm_json_spec
    nnvm_overview
+   hybrid_script
diff --git a/docs/langref/hybrid_script.rst b/docs/langref/hybrid_script.rst
new file mode 100644
index 000000000000..fdaed2b5be40
--- /dev/null
+++ b/docs/langref/hybrid_script.rst
@@ -0,0 +1,172 @@
+.. _hybrid-langref-label:
+
+Hybrid Frontend Language Reference
+==================================
+
+Overview
+--------
+
+This hybrid frontend allows users to write preliminary versions of some idioms that yet have
+been supported by TVM officially.
+
+Features
+--------
+
+Software Emulation
+~~~~~~~~~~~~~~~~~~
+
+Both software emulation and compilation are supported. To define a function,
+you need to use ``tvm.hybrid.script`` decorator to indicate this is a hybrid function:
+
+.. code-block:: python
+
+    @tvm.hybrid.script
+    def outer_product(a, b, c):
+        for i in range(a.shape[0]):
+            for j in range(b.shape[0]):
+                c[i, j] = a[i] * b[j]
+    a = numpy.random.rand(100)
+    b = numpy.random.rand(99)
+    c = numpy.zeros((100, 99))
+    outer_product(a, b, c)
+
+This decorator will import `Keywords`_ required spontaneously when software emulation.
+After software emulation is done, the imported keywords will be cleaned up. Users do not need
+worry about keyword conflict and pollution.
+
+Every element passed for software emulation in the argument list is either a python variable
+or ``numpy`` numeric type.
+
+Backend Compilation
+~~~~~~~~~~~~~~~~~~~
+
+The current parse interface looks like:
+
+.. code-block:: python
+
+   a = tvm.placeholder((100, ), name='a')
+   b = tvm.placeholder((99, ), name='b')
+   c = tvm.placeholder((100, 99), name='c')
+   tvm.hybrid.parse(outer_product, [a, b, c]) # return an ir root of this function
+
+If we pass these tvm tensors to this function, it returns a op node:
+
+**Under construction, we are still deciding what kind of node should be returned.**
+
+.. code-block:: python
+
+   a = tvm.placeholder((100, ), name='a')
+   b = tvm.placeholder((99, ), name='b')
+   c = tvm.placeholder((100, 99), name='c')
+   op = outer_product(a, b, c) # return the corresponding op node
+
+Tuning
+~~~~~~
+
+**Under construction, not truly supported yet.**
+
+Follow up the example above, you can use some tvm like interfaces to tune the code: 
+
+.. code-block:: python
+
+   sch = tvm.create_schedule(op)
+   jo, ji = sch.split(j, 4)
+   sch.vectorize(ji)
+
+``split``, ``reorder``, and loop_annotation will be supported!
+
+Loops
+~~~~~
+
+In HalideIR, loops have in total 4 types: ``serial``, ``unrolled``, ``parallel``, and ``vectorized``.
+
+Here we use ``range`` aka ``serial``, ``unroll``, ``parallel``, and ``vectorize``,
+these **4** keywords to annotate the corresponding types of for loops.
+The the usage is roughly the same as Python standard ``range``.
+
+Variables
+~~~~~~~~~
+
+All the mutatable variables will be lowered to an array with size 1.
+It regards the first store of a variable as its declaration.
+
+.. note::
+
+        Unlike conventional Python, in hybrid script, the declared variable
+        can only be used in the scope level it is declared.
+
+
+.. note::
+
+        Currently, you can ONLY use basic-typed variables, i.e. the type of the
+        variable should be either ``float32``, or ``int32``.
+
+.. code-block:: python
+
+   for i in range(5):
+       s = 0 # declaration, this s will be a 1-array in lowered IR
+       for j in range(5):
+     	  s += a[i, j] # do something with sum
+       b[i] = sum # you can still use sum in this level
+   a[0] = s # you CANNOT use s here, even though it is allowed in conventional Python
+   b = (1, 2) # this has NOT been supported yet!
+
+
+Attributes
+~~~~~~~~~~
+
+So far, ONLY tensors' ``shape`` attribute is supported! The ``shape`` atrribute is essentailly a
+tuple, so you MUST access it as an array. Also, currently, only constant-indexed access is supported.
+
+.. code-block:: python
+
+   x = a.shape[2] # OK!
+   for i in range(3):
+      for j in a.shape[i]: # BAD! i is not a constant!
+          # do something
+
+
+Conditional Statement and Expression
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+   if condition:
+        # do something
+   a = b if condition else c
+
+However, NO ``True`` and ``False`` keyword supported yet.
+
+
+Math Intrinsics
+~~~~~~~~~~~~~~~
+
+So far, these math intrinsics, ``log``, ``exp``, ``sigmoid``,
+``tanh``, ``power``, and ``popcount``, are supported.
+No import is required, just as it is mentioned in `Software Emulation`_, just use it!
+
+Array Allocation
+~~~~~~~~~~~~~~~~
+
+**Under construction, this function will be supported later!**
+
+Use a function call ``allocation(shape, type, share/local)`` to declare an array buffer.
+The basic usage is roughly the same as a normal array.
+
+
+Thread Bind
+~~~~~~~~~~~
+
+
+You can also do loop-thread bind by writing code like this:
+
+.. code-block:: python
+
+   for tx in bind("threadIdx.x", 100):
+       a[tx] = b[tx]
+
+
+Keywords
+~~~~~~~~
+- For keywords: ``serial``, ``range``, ``unroll``, ``parallel``, ``vectorize``, ``bind``
+- Math keywords: ``log``, ``exp``, ``sigmoid``, ``tanh``, ``power``, ``popcount``
diff --git a/docs/langref/index.rst b/docs/langref/index.rst
index dc51c3172c57..65f78d1d278b 100644
--- a/docs/langref/index.rst
+++ b/docs/langref/index.rst
@@ -2,3 +2,8 @@ Language Reference
 ==================
 This document provide references to
 embedded languages in TVM stack.
+
+.. toctree::
+   :maxdepth: 2
+
+   hybrid_script
diff --git a/python/tvm/build_module.py b/python/tvm/build_module.py
index 72b89af020d7..777654af6619 100755
--- a/python/tvm/build_module.py
+++ b/python/tvm/build_module.py
@@ -332,12 +332,20 @@ def lower(sch,
     lower_phase1 = [x[1] for x in add_lower_pass if x[0] == 1]
     lower_phase2 = [x[1] for x in add_lower_pass if x[0] == 2]
     lower_phase3 = [x[1] for x in add_lower_pass if x[0] > 2]
-    # normalize schedule first
-    sch = sch.normalize()
+
     # Phase 0
-    bounds = schedule.InferBound(sch)
-    stmt = schedule.ScheduleOps(sch, bounds)
-    stmt = ir_pass.InjectPrefetch(stmt)
+    if isinstance(sch, schedule.Schedule):
+        # normalize schedule first
+        sch = sch.normalize()
+        bounds = schedule.InferBound(sch)
+        stmt = schedule.ScheduleOps(sch, bounds)
+        stmt = ir_pass.InjectPrefetch(stmt)
+    else:
+        #So far there is no op for hybrid script, so a plain ir body is given
+        if not isinstance(sch, _stmt.Stmt):
+            raise ValueError("sch should be either a Schedule or a Stmt")
+        stmt = sch
+
     for f in lower_phase0:
         stmt = f(stmt)
     # Phase 1
diff --git a/python/tvm/hybrid/__init__.py b/python/tvm/hybrid/__init__.py
new file mode 100644
index 000000000000..e0a39c562f0f
--- /dev/null
+++ b/python/tvm/hybrid/__init__.py
@@ -0,0 +1,10 @@
+"""Hybrid Programming APIs of TVM Python Package.
+
+This package maps a subset of python to HalideIR so that:
+1. Users can write some preliminary versions of the computation patterns
+have not been supported yet and verify it across the real execution and
+python semantic emulation.
+2. Developers can build HalideIR by writing Python code.
+"""
+
+from .api import script, parse
diff --git a/python/tvm/hybrid/api.py b/python/tvm/hybrid/api.py
new file mode 100644
index 000000000000..bc5376509522
--- /dev/null
+++ b/python/tvm/hybrid/api.py
@@ -0,0 +1,46 @@
+"""APIs of lowering the Python subset to HalideIR"""
+from __future__ import absolute_import as _abs
+
+import types
+import decorator
+from .parser import parse_python
+
+@decorator.decorator
+def script(func, *args):
+    """If the arguments are tvm types, compile it to HalideIR.
+    O.W. return the python emulated result"""
+    from .util import _enter_hybrid_runtime, _restore_runtime, _is_tvm_arg_types
+    if _is_tvm_arg_types(args):
+        return parse(func, args)
+    else:
+        intersect = _enter_hybrid_runtime(func)
+        func(*args)
+        _restore_runtime(func, intersect)
+    return func
+
+
+def parse(func, args):
+    """Parse a subset of Python to HalideIR
+
+    Parameters
+    ----------
+    func : str or types.FunctionType
+        If it is a string, parse the source code
+        If it is a function, parse the function
+
+    args : list of Buffer or Tensor or Var
+        The argument lists to the function.
+        Leave it None if no buffer is related to the function to be parsed
+
+    Returns
+    -------
+    root : Stmt
+        The result Halide IR and the parser class instance.
+    """
+    from .util import _pruned_source
+    if isinstance(func, str):
+        src = func
+    else:
+        assert isinstance(func, types.FunctionType)
+        src = _pruned_source(func)
+    return parse_python(src, args)
diff --git a/python/tvm/hybrid/intrin.py b/python/tvm/hybrid/intrin.py
new file mode 100644
index 000000000000..93517fef4d1d
--- /dev/null
+++ b/python/tvm/hybrid/intrin.py
@@ -0,0 +1,112 @@
+"""Intrinsics of TVM-Python Hybrid Script for Python runtime"""
+
+import numpy
+from ..stmt import For
+
+class _range(object):
+    """Base class of the loop ranges in hybrid script"""
+    def __init__(self, a, b=None):
+        if b is None:
+            self.low = 0
+            self.ext = a
+        else:
+            self.low = a
+            self.ext = b
+
+    def __iter__(self):
+        i = 0
+        while i < self.ext:
+            yield i + self.low
+            i += 1
+
+
+class bind(_range): #pylint: disable=invalid-name
+    def __init__(self, tag, ext):
+        super(bind, self).__init__(ext)
+        self.tag = tag
+
+
+unroll = vectorize = parallel = _range #pylint: disable=invalid-name
+
+
+def allocate(shape, dtype='float32'):
+    """Allocate a buffer with given shape
+
+    Parameters
+    ----------
+    shape: Tuple
+        The shape of the tensor to be allocated
+    dtype: string
+        The data type of the tensor
+
+    Returns
+    -------
+    tensor: numpy.array
+        The tensor allocated
+    """
+    return numpy.zeros(shape).astype(dtype)
+
+
+def popcount(x):
+    """
+    Count ones in the binary representation of number x
+
+    Parameters
+    ----------
+    x: Integer
+        The number to be counted
+
+    Returns
+    -------
+    cnt: Integer
+        The number of ones in the binary representation of number x
+    """
+    cnt = 0
+    while x:
+        x -= x & -x
+        cnt += 1
+    return cnt
+
+
+def sigmoid(x):
+    """
+    Sigmoid function of x, aka 1/(1+exp(-x)).
+
+    Parameters
+    ----------
+    x: a real number
+
+    Returns
+    -------
+    res: a real number
+        The result of sigmoid function
+    """
+    return 1 / (1 + numpy.exp(-x))
+
+
+HYBRID_GLOBALS = {
+    'unroll'    : unroll,
+    'vectorize' : vectorize,
+    'parallel'  : parallel,
+    'allocate'  : allocate,
+    'bind'      : bind,
+    'sqrt'      : numpy.sqrt,
+    'log'       : numpy.log,
+    'tanh'      : numpy.tanh,
+    'power'     : numpy.power,
+    'exp'       : numpy.exp,
+    'sigmoid'   : sigmoid,
+    'popcount'  : popcount
+}
+
+
+LOOP_INTRIN = {
+    'range'    : For.Serial,
+    'unroll'   : For.Unrolled,
+    'parallel' : For.Parallel,
+    'vectorize': For.Vectorized,
+    'bind'     : None
+}
+
+
+MATH_INTRIN = ['sqrt', 'log', 'exp', 'tanh', 'sigmoid', 'power', 'popcount']
diff --git a/python/tvm/hybrid/parser.py b/python/tvm/hybrid/parser.py
new file mode 100644
index 000000000000..7d4c40e8c7e9
--- /dev/null
+++ b/python/tvm/hybrid/parser.py
@@ -0,0 +1,342 @@
+"""Hybrid Script Parser"""
+
+import ast
+import operator
+import sys
+from .util import make_nop, make_const_true, make_range_one, halide_imm_types
+from .intrin import LOOP_INTRIN, MATH_INTRIN
+from .var_decl import determine_variable_usage
+from ..api import thread_axis
+from .. import expr as _expr
+from .. import make as _make
+from .. import intrin
+from .. import api  as _api
+from .. import ir_pass as _ir_pass
+
+def list_to_block(visit, lst):
+    """Convert a list of Python IR nodes to HalideIR Block"""
+    lst = list(map(visit, lst))
+    lst = [stmt for stmt in lst if not _ir_pass.Equal(stmt, make_nop())]
+    if not lst:
+        return make_nop()
+    if len(lst) == 1:
+        return lst[0]
+    body = lst[0]
+    for i in lst[1:]:
+        body = _make.Block(body, i)
+    return body
+
+
+class HybridParser(ast.NodeVisitor):
+    """Python AST visitor pass which finally lowers it to HalideIR"""
+
+
+    _binop_maker = {
+        ast.Add   : operator.add,
+        ast.Sub   : operator.sub,
+        ast.Mult  : operator.mul,
+        ast.Div   : _make.Div,
+        ast.Mod   : operator.mod,
+        ast.BitOr : operator.or_,
+        ast.BitAnd: operator.and_,
+        ast.BitXor: operator.xor,
+        ast.Gt    : operator.gt,
+        ast.GtE   : operator.ge,
+        ast.Lt    : operator.lt,
+        ast.LtE   : operator.le,
+        ast.Eq    : operator.eq,
+        ast.NotEq : operator.ne,
+    }
+
+
+    _unaryop_maker = {
+        ast.USub   : operator.neg,
+        ast.Invert : operator.invert,
+        ast.Not    : operator.not_
+    }
+
+
+    def __init__(self, args, usage, func_name=None):
+        """
+        Parameters
+        ----------
+        args: A list of tvm.placeholder or tvm.var
+            Provided by the user, the argument list of the function to be lowered.
+
+        usage: A dict of variables used in last in this function
+            Provided by last lower pass, which collects this information
+
+        Returns
+        -------
+        func_name: str
+            The name of the function to be lowered; if not provided,
+            the compiler will use the name in the AST
+        """
+        self.args = args[:]
+        self.usage = usage.copy()
+        self._args = {} # Dict maps arg name to actual arg instance (either a var or a buffer)
+        self.buffers = {}
+        self.loops_above = {} # State variable that indicates loop levels above the current node
+        self.var_consts = {} # Variables that are determined as readonly in previous stage
+        self.func_name = func_name # The name of the function to be lowered
+        self.iter_axis = []
+
+
+    def wrap_up_realize(self, node, body):
+        """Wrap up all the variables which will no longer be used"""
+        for key, val in self.usage.items():
+            if key in self.var_consts.keys():
+                continue
+            _, scope, _ = val
+            if scope == node:
+                _buf = self.buffers[key]
+                _dtype = _buf.dtype
+                _one = make_range_one()
+                _true = make_const_true()
+                body = _make.Realize(_buf.op, 0, _dtype, [_one], _true, body)
+        return body
+
+
+    def _check_id_a_buffer(self, s):
+        if s not in self._args.keys():
+            raise ValueError("This %s is expected to be in argument list or allocated buffer!" % s)
+
+
+    #pylint: disable=invalid-name, missing-docstring
+    def visit_Module(self, node):
+        if len(node.body) != 1:
+            raise ValueError("Only one-function source code can be fed to this parser!")
+        return self.visit(node.body[0])
+
+
+    def visit_FunctionDef(self, node):
+        if len(node.args.args) != len(self.args):
+            raise ValueError("The number of arguments passed to the function\
+                should be the same as it is defined!")
+        for idx, arg in enumerate(node.args.args):
+            _attr = 'id' if sys.version_info[0] < 3 else 'arg' # To make py2 and 3 compatible
+            self._args[getattr(arg, _attr)] = self.args[idx]
+        res = list_to_block(self.visit, node.body)
+        res = self.wrap_up_realize(node, res)
+        if self.func_name is None:
+            self.func_name = node.name
+        return res
+
+
+    def visit_Expr(self, node):
+        return self.visit(node.value)
+
+
+    def visit_Name(self, node):
+        _id = node.id
+        if _id in self._args.keys() and isinstance(self._args[_id], _expr.Var):
+            return self._args[_id]
+        elif _id in self.loops_above.keys():
+            return self.loops_above[_id]
+        if _id in self._args.keys():
+            raise ValueError("This id %s should be handled in visit_Subscript!" % _id)
+        if _id  not in self.usage.keys():
+            raise ValueError("This id %s is expected to be a defined variable!" % _id)
+        # Buffer
+        if _id in self.buffers.keys():
+            _buf = self.buffers[_id]
+            return _make.Call(_buf.dtype, _id, [_api.const(0)], _expr.Call.Halide, _buf.op, 0)
+        # Compilation time constant
+        if _id not in self.var_consts.keys():
+            raise ValueError("This id %s is expected to a compilation time constant!" % _id)
+        return self.var_consts[_id]
+
+
+    def visit_Num(self, node):
+        return _api.const(node.n)
+
+
+    def visit_Assign(self, node):
+        if len(node.targets) != 1:
+            raise ValueError("So far only one-valued assignment is supported!")
+        lhs = node.targets[0]
+        rhs = _ir_pass.Simplify(self.visit(node.value))
+        if isinstance(lhs, ast.Name):
+            #TODO: support defined intermediate buffer later
+            lhs_ = lhs
+            lhs = lhs.id
+            if lhs in self.loops_above.keys():
+                raise ValueError("You CAN NEVER overwrite a loop variable!")
+            decl, _, rw = self.usage[lhs]
+            if decl == lhs_:
+                if lhs in self.var_consts.keys():
+                    raise ValueError("BUG: A constant cannot be overwritten!")
+                if lhs in self.buffers.keys():
+                    raise ValueError("BUG: This value should not be defined before this point!")
+                if isinstance(rhs, halide_imm_types) and ast.Store not in rw:
+                    self.var_consts[lhs] = rhs
+                else:
+                    self.buffers[lhs] = _api.placeholder((1, ), dtype=rhs.dtype, name=lhs)
+            if lhs in self.var_consts.keys():
+                return make_nop()
+            else:
+                if lhs not in self.buffers.keys():
+                    raise ValueError("BUG: This value should be defined before!")
+                return _make.Provide(self.buffers[lhs].op, 0, rhs, [_api.const(0, dtype=rhs.dtype)])
+        else:
+            lhs = self.visit(lhs)
+            if not isinstance(lhs, _expr.Call):
+                raise ValueError("An array access's LHS is expected to be a expr.Call!")
+            #TODO: support slice later
+            self._check_id_a_buffer(lhs.name)
+            return _make.Provide(self._args[lhs.name].op, 0, rhs, lhs.args)
+
+
+    def visit_Index(self, node):
+        if isinstance(node.value, ast.Tuple):
+            return [self.visit(i) for i in node.value.elts]
+        return [self.visit(node.value)]
+
+
+    def visit_Subscript(self, node):
+        args = self.visit(node.slice)
+        if isinstance(node.value, ast.Name):
+            array = node.value.id
+            self._check_id_a_buffer(array)
+            _buf = self._args[array]
+            return _make.Call(_buf.dtype, array, args, _expr.Call.Halide, _buf.op, 0)
+        elif isinstance(node.value, ast.Attribute):
+            if not isinstance(node.value.value, ast.Name):
+                raise ValueError("The root of array access is expect to be a id!")
+            if node.value.attr != "shape":
+                raise ValueError("Attribute access so far only 'shape' is supported!")
+            if len(args) != 1:
+                raise ValueError("For 'shape' access the argument should be only one!")
+            args = args[0]
+            #TODO: maybe support non-constant value later?
+            if not isinstance(args, (_expr.IntImm, _expr.UIntImm)):
+                raise ValueError("So far only constant shape access supported!")
+            self._check_id_a_buffer(node.value.value.id)
+            return self._args[node.value.value.id].shape[args.value]
+        else:
+            raise ValueError("Not supported yet!")
+
+
+    def visit_With(self, node):
+        if sys.version_info[0] < 3:
+            context = node.context_expr
+            option = node.optional_vars
+        else:
+            if len(node.items) != 1:
+                raise ValueError("Only one with element is supported so far!")
+            context = node.items[0].context_expr
+            option = node.items[0].optional_vars
+        if not isinstance(context, ast.Call):
+            raise ValueError("The object must be a Python function call!")
+        if not isinstance(option, ast.Name):
+            raise ValueError("The object after 'as' must be an id!")
+        self.annotation[option.id] = context.func.id
+        return list_to_block(self.visit, node.body)
+
+
+    def visit_If(self, node):
+        cond = self.visit(node.test)
+        if_body = list_to_block(self.visit, node.body)
+        if node.orelse:
+            else_body = list_to_block(self.visit, node.orelse)
+        else:
+            else_body = make_nop()
+        return _make.IfThenElse(cond, if_body, else_body)
+
+
+    def visit_IfExp(self, node):
+        cond = self.visit(node.test)
+        if_body = self.visit(node.body)
+        else_body = self.visit(node.orelse)
+        return _make.Select(cond, if_body, else_body)
+
+
+    def visit_Compare(self, node):
+        lhs = self.visit(node.left)
+        if len(node.ops) != 1:
+            raise ValueError("Only one compare op is supported!")
+        if len(node.comparators) != 1:
+            raise ValueError("Only one comparator is supported!")
+        rhs = self.visit(node.comparators[0])
+        return HybridParser._binop_maker[type(node.ops[0])](lhs, rhs)
+
+
+    def visit_UnaryOp(self, node):
+        operand = self.visit(node.operand)
+        return HybridParser._unaryop_maker[type(node.op)](operand)
+
+
+    def visit_BinOp(self, node):
+        lhs = self.visit(node.left)
+        rhs = self.visit(node.right)
+        return HybridParser._binop_maker[type(node.op)](lhs, rhs)
+
+
+    def visit_Call(self, node):
+        # Yet, no function pointer supported
+        if not isinstance(node.func, ast.Name):
+            raise ValueError("Only id-function function call is supported so far!")
+        func_id = node.func.id
+        n = len(node.args)
+        if func_id in LOOP_INTRIN.keys() and func_id != 'bind':
+            if n == 1:
+                low, ext = _api.const(0, dtype='int32'), self.visit(node.args[0])
+            else:
+                if n != 2:
+                    raise ValueError("A loop intrinsic should only have 1 or 2 arguments!")
+                low, ext = self.visit(node.args[0]), self.visit(node.args[1])
+            if not _ir_pass.Equal(low, _api.const(0, dtype='int32')):
+                ext = ext - low
+            for_type = LOOP_INTRIN[func_id]
+            iter_var = None
+            return iter_var, low, ext, for_type
+        elif func_id == 'bind':
+            if n != 2:
+                raise ValueError("A loop bind should only have 2 arguments!")
+            if not isinstance(node.args[0], ast.Str):
+                raise ValueError("A loop bind's first argument should be a string!")
+            _vn = node.args[0].s
+            iter_var = thread_axis(node.args[0].s)
+            low, ext = _api.const(0, dtype='int32'), self.visit(node.args[1])
+            for_type = None
+            return iter_var, low, ext, for_type
+        elif func_id in MATH_INTRIN:
+            return getattr(intrin, func_id)(*[self.visit(arg) for arg in node.args])
+        elif func_id == 'allocate':
+            #TODO: Support it later!
+            return make_nop()
+        else:
+            raise ValueError("Function call not supported yet!")
+
+
+    def visit_For(self, node):
+        iter_var, low, ext, for_type = self.visit(node.iter)
+        if not isinstance(node.target, ast.Name):
+            raise ValueError("The loop iterator should be a variable!")
+        _name = node.target.id
+        if iter_var is None:
+            if for_type is None:
+                raise ValueError("The loop bind function parse error!")
+            iter_var = _api.var(_name)
+            self.loops_above[_name] = iter_var
+        else:
+            if for_type is not None:
+                raise ValueError("The loop iterating function parse error!")
+            self.loops_above[_name] = iter_var.var
+        _body = list_to_block(self.visit, node.body)
+        _body = self.wrap_up_realize(node, _body)
+        if for_type is None:
+            res = _make.AttrStmt(iter_var, 'thread_extent', ext, _body)
+        else:
+            res = _make.For(iter_var, low, ext, for_type, 0, _body)
+        self.loops_above.pop(_name)
+        return res
+
+
+def parse_python(src, args):
+    """The helper function of calling the AST visitor"""
+    root = ast.parse(src)
+    var_usage = determine_variable_usage(root, args)
+    parser = HybridParser(args, var_usage)
+    halide_ir = parser.visit(root)
+    return halide_ir
diff --git a/python/tvm/hybrid/util.py b/python/tvm/hybrid/util.py
new file mode 100644
index 000000000000..8a5f4a62768d
--- /dev/null
+++ b/python/tvm/hybrid/util.py
@@ -0,0 +1,76 @@
+"""Internal utilities for parsing Python subset to HalideIR"""
+
+import inspect
+import numpy
+from .intrin import HYBRID_GLOBALS
+from .._ffi.base import numeric_types
+from .. import api as _api
+from .. import make as _make
+from .. import expr as _expr
+from ..tensor import Tensor
+
+
+#pylint: disable=invalid-name
+np_arg_types = tuple(list(numeric_types) + [numpy.ndarray])
+tvm_arg_types = (Tensor, _expr.Var)
+halide_imm_types = (_expr.IntImm, _expr.FloatImm, _expr.UIntImm)
+
+
+# Useful constants. In avoid of runtime dependences, we use function calls to return them.
+def make_nop():
+    """Returns a 'no operation' node in HalideIR."""
+    return _make.Evaluate(_api.const(0, dtype='int32'))
+
+
+def make_range_one():
+    """Returns a [0, 1] range node in HalideIR."""
+    return _make.range_by_min_extent(0, 1)
+
+
+def make_const_true():
+    """Returns a constant True node in HalideIR."""
+    return _api.convert(True)
+
+
+def _pruned_source(func):
+    """Prune source code's extra leading spaces"""
+    lines = inspect.getsource(func).split('\n')
+    leading_space = len(lines[0]) - len(lines[0].lstrip(' '))
+    lines = [line[leading_space:] for line in lines]
+    return '\n'.join(lines)
+
+
+def _is_tvm_arg_types(args):
+    """Determine a list of element is either a list of tvm arguments of a list of numpy arguments.
+    If neither is true, raise a value error."""
+    if isinstance(args[0], tvm_arg_types):
+        for elem in args[1:]:
+            if not isinstance(elem, tvm_arg_types):
+                raise ValueError("Expect a Var or Tensor instance but % get!" % str(type(elem)))
+        return True
+    if not isinstance(args[0], np_arg_types):
+        raise ValueError("Expect a numpy type but % get!" % str(type(args[0])))
+    for elem in args[1:]:
+        if not isinstance(elem, np_arg_types):
+            raise ValueError("Expect a numpy type but % get!" % str(type(elem)))
+    return False
+
+
+def _enter_hybrid_runtime(func):
+    """Put hybrid runtime variables into the global scope"""
+    _globals = func.__globals__
+    intersect = []
+    for elem in list(HYBRID_GLOBALS.keys()):
+        if elem in _globals.keys():
+            intersect.append((elem, _globals[elem]))
+        _globals[elem] = HYBRID_GLOBALS[elem]
+    return intersect
+
+
+def _restore_runtime(func, intersect):
+    """Rollback the modification caused by hybrid runtime"""
+    _globals = func.__globals__
+    for elem in list(HYBRID_GLOBALS.keys()):
+        _globals.pop(elem)
+    for k, v in intersect:
+        _globals[k] = v
diff --git a/python/tvm/hybrid/var_decl.py b/python/tvm/hybrid/var_decl.py
new file mode 100644
index 000000000000..940b8c088df3
--- /dev/null
+++ b/python/tvm/hybrid/var_decl.py
@@ -0,0 +1,76 @@
+"""Determines the declaration, r/w status, and last use of each variable"""
+
+import ast
+import sys
+from .intrin import HYBRID_GLOBALS
+
+
+class PyVariableUsage(ast.NodeVisitor):
+    """The vistor class to determine the declaration, r/w status, and last use of each variable"""
+    #pylint: disable=invalid-name
+    #pylint: disable=missing-docstring
+    def __init__(self, args):
+        self.status = {}
+        self.scope_level = []
+        self._args = {}
+        self.args = args
+
+
+    def visit_FunctionDef(self, node):
+        self.scope_level.append(node)
+        if len(node.args.args) != len(self.args):
+            raise ValueError('#arguments passed should be the same as #arguments defined')
+        for idx, arg in enumerate(node.args.args):
+            _attr = 'id' if sys.version_info[0] < 3 else 'arg' # To make py2 and 3 compatible
+            self._args[getattr(arg, _attr)] = self.args[idx]
+        for i in node.body:
+            self.visit(i)
+
+
+    def visit_For(self, node):
+        if not isinstance(node.target, ast.Name):
+            raise ValueError("For's iterator should be an id")
+        self.visit(node.iter)
+        self.scope_level.append(node)
+        for i in node.body:
+            self.visit(i)
+        self.scope_level.pop()
+
+
+    def visit_Call(self, node):
+        #No function pointer supported so far
+        if not isinstance(node.func, ast.Name):
+            raise ValueError("Function call should be an id")
+        if (node.func.id not in HYBRID_GLOBALS.keys()) and node.func.id != 'range':
+            raise ValueError("Function call id not in intrinsics' list")
+        for elem in node.args:
+            self.visit(elem)
+
+
+    def visit_Name(self, node):
+        # If it is from the argument list or loop variable, we do not worry about it!
+        if node.id in self._args.keys():
+            return
+        fors = [loop.target.id for loop in self.scope_level if isinstance(loop, ast.For)]
+        if node.id in fors:
+            return
+        # The loop variable cannot be overwritten when iteration
+        if isinstance(node.ctx, ast.Store) and node.id in fors:
+            raise ValueError("Iter var cannot be overwritten")
+
+        if node.id not in self.status.keys():
+            if not isinstance(node.ctx, ast.Store):
+                raise ValueError('In Python, "first store" indicates "declaration"')
+            self.status[node.id] = (node, self.scope_level[-1], set())
+        else:
+            decl, loop, usage = self.status[node.id]
+            loop = self.scope_level[-1]
+            usage.add(type(node.ctx))
+            self.status[node.id] = (decl, loop, usage)
+
+
+def determine_variable_usage(root, args):
+    """The helper function for calling the dedicated visitor."""
+    visitor = PyVariableUsage(args)
+    visitor.visit(root)
+    return visitor.status
diff --git a/tests/python/unittest/test_hybrid_script.py b/tests/python/unittest/test_hybrid_script.py
new file mode 100644
index 000000000000..fda4f52c1f19
--- /dev/null
+++ b/tests/python/unittest/test_hybrid_script.py
@@ -0,0 +1,287 @@
+import tvm, inspect, sys, traceback, numpy
+from tvm.hybrid import script
+from tvm.hybrid.intrin import HYBRID_GLOBALS
+
+@script
+def outer_product(n, m, a, b, c):
+    for i in range(n):
+        for j in range(m):
+            c[i, j] = a[i] * b[j]
+
+#Test global function
+#Test bridge between frontend and backend
+def test_outer_product():
+    n = tvm.var('n')
+    m = tvm.var('m')
+    a = tvm.placeholder((n, ), name='a')
+    b = tvm.placeholder((m, ), name='b')
+    c = tvm.placeholder((n, m), name='c')
+    ir = outer_product(n, m, a, b, c)
+    #Check for i in (0, n)
+    assert isinstance(ir, tvm.stmt.For)
+    assert ir.loop_var.name == 'i'
+    assert ir.min.value == 0
+    assert ir.extent.name == 'n'
+    ibody = ir.body
+    assert isinstance(ibody, tvm.stmt.For)
+    #Check for j in (0, m)
+    assert ibody.loop_var.name == 'j'
+    assert ibody.min.value == 0
+    assert ibody.extent.name == 'm'
+    #Check loop body
+    jbody = ibody.body
+    assert isinstance(jbody, tvm.stmt.Provide)
+    assert jbody.func.name == 'c'
+    assert len(jbody.args) == 2
+    assert jbody.args[0].name == 'i'
+    assert jbody.args[1].name == 'j'
+    assert isinstance(jbody.value, tvm.expr.Mul)
+    mul = jbody.value
+    assert isinstance(mul.a, tvm.expr.Call)
+    assert mul.a.name == 'a'
+    assert mul.b.name == 'b'
+
+    func = tvm.lower(ir, [n, m, a, b, c])
+    func = tvm.build(func)
+
+    _n = 999
+    _m = 1001
+    _a = numpy.random.rand(_n).astype('float32')
+    _b = numpy.random.rand(_m).astype('float32')
+    c_python = numpy.zeros((_n, _m), dtype='float32')
+    outer_product(_n, _m, _a, _b, c_python)
+
+    tvm_a = tvm.ndarray.array(_a)
+    tvm_b = tvm.ndarray.array(_b)
+    tvm_c = tvm.ndarray.array(numpy.zeros((_n, _m), dtype='float32'))
+    func(_n, _m, tvm_a, tvm_b, tvm_c)
+    numpy.testing.assert_allclose(tvm_c.asnumpy(), c_python, rtol=1e-5)
+    for key, _ in HYBRID_GLOBALS.items():
+        assert key not in globals().keys()
+        assert key not in outer_product.__globals__.keys()
+
+#Test local function
+#Test allocation of local variable
+def test_fanout():
+    @script
+    def fanout(n, a, b):
+        three = 3.0
+        for i in range(a.shape[0] - 3):
+            sigma = 0.0
+            for j in range(3):
+                sigma = sigma + a[i + j]
+            sigma = sigma / three
+            b[i] = sigma
+
+    n = tvm.var('n')
+    a = tvm.placeholder((n, ), name='a')
+    b = tvm.placeholder((n-3, ), name='b')
+    ir = fanout(n, a, b)
+
+    #Check for i in (0, n-3)
+    assert isinstance(ir, tvm.stmt.For)
+    assert ir.loop_var.name == 'i'
+    assert ir.min.value == 0
+    assert tvm.ir_pass.Equal(ir.extent, n - 3)
+    #Check loopbody
+    ibody = ir.body
+    assert isinstance(ibody, tvm.stmt.Realize)
+    assert ibody.bounds[0].min.value == 0
+    assert ibody.bounds[0].extent.value == 1
+    assert ibody.func.name == 'sigma'
+    #Check i loop body
+    rbody = ibody.body
+    assert isinstance(rbody.first, tvm.stmt.Provide)
+    assert rbody.first.func.name == 'sigma'
+    assert len(rbody.first.args) == 1
+    assert rbody.first.args[0].value == 0
+    #Check fanout loop
+    jloop = rbody.rest.first
+    assert jloop.loop_var.name == 'j'
+    assert jloop.min.value == 0
+    assert jloop.extent.value == 3
+    jbody = jloop.body
+    assert isinstance(jbody, tvm.stmt.Provide)
+    assert len(jbody.args) == 1
+    assert jbody.args[0].value == 0
+    assert jbody.func.name == 'sigma'
+    assert isinstance(jbody.value, tvm.expr.Add)
+    value = jbody.value
+    assert isinstance(value.a, tvm.expr.Call)
+    assert value.a.name == 'sigma'
+    assert len(value.a.args) == 1
+    assert value.a.args[0].value == 0
+    assert value.b.name == 'a'
+    assert len(value.b.args) == 1
+    assert tvm.ir_pass.Equal(value.b.args[0], ir.loop_var + jloop.loop_var)
+    divide= rbody.rest.rest.first
+    assert isinstance(divide, tvm.stmt.Provide)
+    assert len(divide.args) == 1
+    assert divide.args[0].value == 0
+    value = divide.value
+    assert isinstance(value, tvm.expr.Mul)
+    assert value.a.name == 'sigma'
+    assert len(value.a.args) == 1
+    assert value.a.args[0].value == 0
+    assert abs(value.b.value - (1 / 3.0)) < 1e-5
+    write = rbody.rest.rest.rest
+    assert isinstance(write, tvm.stmt.Provide)
+    assert write.func.name == 'b'
+    assert write.value.name == 'sigma'
+    assert len(write.value.args) == 1
+    assert write.value.args[0].value == 0
+
+@script
+def failure():
+    for i in range(1, 100):
+        i = 0
+
+def test_failure():
+    try:
+        tvm.hybrid.parse(failure, [])
+    except IOError as err:
+        assert sys.version_info[0] == 2
+        print('[Warning] Python2 cannot do the failure case because "%s"' % str(err))
+    except Exception as err:
+        assert str(err) == 'You CAN NEVER overwrite a loop variable!'
+
+
+def test_looptype():
+    @script
+    def looptype(a):
+        for i in parallel(6):
+            a[i] = i
+        for j in vectorize(6):
+            a[j] = j
+        for k in unroll(6):
+            a[k] = k
+    a = tvm.placeholder((6, ), name='a')
+    ir = looptype(a)
+    iloop = ir.first
+    jloop = ir.rest.first
+    kloop = ir.rest.rest
+    assert iloop.for_type == tvm.stmt.For.Parallel
+    assert jloop.for_type == tvm.stmt.For.Vectorized
+    assert kloop.for_type == tvm.stmt.For.Unrolled
+
+def test_if():
+    @script
+    def if_then_else(a, b):
+        for i in range(10):
+            if i % 2 == 0:
+                a[i] = -1
+            else:
+                a[i] = 1
+        for i in unroll(10):
+            b[i] = -1 if i % 2 == 0 else 1
+
+    a = tvm.placeholder((10, ), dtype='int32', name='a')
+    b = tvm.placeholder((10, ), dtype='int32', name='b')
+    ir = if_then_else(a, b)
+    func = tvm.lower(ir, [a, b])
+    func = tvm.build(func)
+    assert func
+
+    _a = numpy.zeros((10, ), dtype = 'int32')
+    _b = numpy.zeros((10, ), dtype = 'int32')
+    if_then_else(_a, _b)
+
+    tvm_a = tvm.ndarray.array(numpy.zeros((10, ), dtype='int32'))
+    tvm_b = tvm.ndarray.array(numpy.zeros((10, ), dtype='int32'))
+    func(tvm_a, tvm_b)
+
+    numpy.testing.assert_allclose(tvm_a.asnumpy(), _a, rtol=1e-5)
+    numpy.testing.assert_allclose(tvm_b.asnumpy(), _b, rtol=1e-5)
+    numpy.testing.assert_allclose(tvm_a.asnumpy(), tvm_b.asnumpy(), rtol=1e-5)
+
+def test_bind():
+    if not tvm.gpu(0).exist:
+        print('No GPU found! Skip this test!')
+        return
+    @script
+    def vec_add(a, b, c):
+        for tx in bind('threadIdx.x', 1000):
+            c[tx] = b[tx] + c[tx]
+
+    a = tvm.placeholder((1000, ), dtype='float32', name='a')
+    b = tvm.placeholder((1000, ), dtype='float32', name='b')
+    c = tvm.placeholder((1000, ), dtype='float32', name='c')
+    ir = vec_add(a, b, c)
+
+    func = tvm.lower(ir, [a, b, c])
+    func = tvm.build(func, target = 'cuda')
+
+    _a = numpy.random.rand(1000).astype('float32')
+    _b = numpy.random.rand(1000).astype('float32')
+    _c = numpy.zeros((1000, ), dtype = 'float32')
+
+
+    tvm_a = tvm.ndarray.array(_a, tvm.gpu(0))
+    tvm_b = tvm.ndarray.array(_b, tvm.gpu(0))
+    tvm_c = tvm.ndarray.array(_c, tvm.gpu(0))
+
+    func(tvm_a, tvm_b, tvm_c)
+    vec_add(_a, _b, _c)
+
+    numpy.testing.assert_allclose(_c, tvm_c.asnumpy(), rtol=1e-5)
+
+def test_math_intrin():
+    @script
+    def intrin_real(a):
+        a[0] = sqrt(a[0])
+        a[1] = log(a[1])
+        a[2] = exp(a[2])
+        a[3] = sigmoid(a[3])
+        a[4] = power(a[4], a[5])
+        a[5] = tanh(a[5])
+
+    a6 = tvm.placeholder((6, ), dtype='float32', name='a')
+    ir = intrin_real(a6)
+    func = tvm.build(tvm.lower(ir, [a6]))
+    assert func
+    a = numpy.arange(2, 8).astype('float32')
+    tvm_a = tvm.ndarray.array(a)
+    func(tvm_a)
+    intrin_real(a)
+    numpy.testing.assert_allclose(a, tvm_a.asnumpy(), rtol=1e-5)
+
+    @script
+    def intrin_int(a):
+        a[0] = popcount(a[0])
+
+    a1 = tvm.placeholder((1, ), dtype='int32')
+    ir = intrin_int(a1)
+    func = tvm.build(tvm.lower(ir, [a1]))
+    assert func
+    a = numpy.array([1234567890]).astype('int32')
+    tvm_a = tvm.ndarray.array(a)
+    intrin_int(a)
+    func(tvm_a)
+    assert tvm_a.asnumpy()[0] == a[0]
+
+def test_allocate_buffer():
+    def blur(a):
+        for i in serail(32):
+            h_blur = allocate((4, 36))
+            for j in serail(4):
+                for k in serail(36):
+                    s = allocate((1, ), 'float32')
+                    for dj in serail(4):
+                        s[0] = s[0] + a[i, j + dj]
+                    h_blur[j, k] = s[0] / 4.
+            for j in serail(32):
+                s = 0.
+                for di in serail(4):
+                    s = s + h_blur[di, j]
+                h_blur[i, j] = s / 4.
+                                
+
+if __name__ == "__main__":
+    test_outer_product()
+    test_fanout()
+    test_failure()
+    test_looptype()
+    test_if()
+    test_bind()
+    test_math_intrin()
+

From 2550864056e2a685e294c72c6c87785313f4f9c7 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 22 Jun 2018 16:49:07 -0700
Subject: [PATCH 746/949] [TOPI] Numpy consistency: always broadcast binary op.
 (#1321)

---
 docs/Doxyfile                                 |   2 +-
 docs/api/python/topi.rst                      |  37 +-
 nnvm/python/nnvm/top/nn.py                    |   6 +-
 nnvm/python/nnvm/top/tensor.py                |   4 +-
 nnvm/src/top/tensor/broadcast.cc              |  12 +-
 nnvm/src/top/tensor/elemwise.cc               |   8 +-
 python/tvm/expr.py                            |   2 +-
 python/tvm/generic.py                         |  16 +
 .../unittest/test_lang_tensor_overload_op.py  |  20 +-
 topi/include/topi/broadcast.h                 | 292 +++++++++-------
 topi/include/topi/elemwise.h                  |  49 ---
 topi/include/topi/nn/l2_normalize.h           |  20 +-
 topi/include/topi/nn/local_response_norm.h    |  15 +-
 topi/python/topi/broadcast.py                 | 318 +++++++++---------
 topi/python/topi/cpp.py                       |  40 ---
 topi/python/topi/generic_op_impl.py           |  56 +--
 topi/python/topi/math.py                      |   9 +-
 topi/python/topi/tensor.py                    |  49 ---
 topi/src/topi.cc                              |  91 +++--
 topi/tests/python/test_topi_broadcast.py      | 150 ++++++---
 topi/tests/python/test_topi_tensor.py         |  44 ---
 topi/tests/python_cpp/test_topi_broadcast.py  | 114 -------
 topi/tests/python_cpp/test_topi_transform.py  |   2 +-
 23 files changed, 564 insertions(+), 792 deletions(-)
 delete mode 100644 topi/tests/python_cpp/test_topi_broadcast.py

diff --git a/docs/Doxyfile b/docs/Doxyfile
index 90c10ebaef79..1ef942620386 100644
--- a/docs/Doxyfile
+++ b/docs/Doxyfile
@@ -1934,7 +1934,7 @@ ENABLE_PREPROCESSING   = YES
 # The default value is: NO.
 # This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
 
-MACRO_EXPANSION        = NO
+MACRO_EXPANSION        = YES
 
 # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
 # the macro expansion is limited to the macros specified with the PREDEFINED and
diff --git a/docs/api/python/topi.rst b/docs/api/python/topi.rst
index 136ec7e4777c..a2221847dae4 100644
--- a/docs/api/python/topi.rst
+++ b/docs/api/python/topi.rst
@@ -31,8 +31,6 @@ List of operators
    topi.take
    topi.full
    topi.full_like
-   topi.greater
-   topi.less
    topi.nn.relu
    topi.nn.leaky_relu
    topi.nn.dilate
@@ -49,12 +47,16 @@ List of operators
    topi.sum
    topi.min
    topi.broadcast_to
-   topi.broadcast_add
-   topi.broadcast_sub
-   topi.broadcast_mul
-   topi.broadcast_div
-   topi.broadcast_maximum
-   topi.broadcast_minimum
+   topi.add
+   topi.subtract
+   topi.multiply
+   topi.divide
+   topi.mod
+   topi.maximum
+   topi.minimum
+   topi.power
+   topi.greater
+   topi.less
    topi.image.resize
 
 
@@ -94,19 +96,20 @@ topi
 .. autofunction:: topi.take
 .. autofunction:: topi.full
 .. autofunction:: topi.full_like
-.. autofunction:: topi.greater
-.. autofunction:: topi.less
 .. autofunction:: topi.max
 .. autofunction:: topi.sum
 .. autofunction:: topi.min
 .. autofunction:: topi.broadcast_to
-.. autofunction:: topi.broadcast_add
-.. autofunction:: topi.broadcast_sub
-.. autofunction:: topi.broadcast_mul
-.. autofunction:: topi.broadcast_div
-.. autofunction:: topi.broadcast_maximum
-.. autofunction:: topi.broadcast_minimum
-
+.. autofunction:: topi.add
+.. autofunction:: topi.subtract
+.. autofunction:: topi.multiply
+.. autofunction:: topi.divide
+.. autofunction:: topi.mod
+.. autofunction:: topi.maximum
+.. autofunction:: topi.minimum
+.. autofunction:: topi.power
+.. autofunction:: topi.greater
+.. autofunction:: topi.less
 
 topi.nn
 ~~~~~~~
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index 5bfabdac2c8d..e4e5415a6ccf 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -105,7 +105,7 @@ def compute_conv2d(attrs, inputs, _):
         bias = inputs[2]
         expand_axis = 1 if layout == "NCHW" else 0
         bias = topi.expand_dims(bias, axis=expand_axis, num_newaxis=2)
-        out = topi.broadcast_add(out, bias)
+        out = topi.add(out, bias)
     return out
 
 @reg.register_schedule("conv2d")
@@ -146,7 +146,7 @@ def compute_contrib_conv2d_NCHWc(attrs, inputs, _):
     if attrs.get_bool("use_bias"):
         bias = inputs[2]
         bias = topi.expand_dims(bias, axis=1, num_newaxis=2)
-        out = topi.broadcast_add(out, bias)
+        out = topi.add(out, bias)
     return out
 
 @reg.register_schedule("_contrib_conv2d_NCHWc")
@@ -181,7 +181,7 @@ def compute_conv2d_transpose(attrs, inputs, _):
     if attrs.get_bool("use_bias"):
         bias = inputs[2]
         bias = topi.expand_dims(bias, axis=1, num_newaxis=2)
-        out = topi.broadcast_add(out, bias)
+        out = topi.add(out, bias)
     output_padding = attrs.get_int_tuple("output_padding")
     out = topi.nn.pad(out, \
         [0, 0, 0, 0], [0, 0, output_padding[0], output_padding[1]])
diff --git a/nnvm/python/nnvm/top/tensor.py b/nnvm/python/nnvm/top/tensor.py
index da0db9b6cb6e..c11486f5c77f 100644
--- a/nnvm/python/nnvm/top/tensor.py
+++ b/nnvm/python/nnvm/top/tensor.py
@@ -244,7 +244,7 @@ def compute_ones_like(_, inputs, out_info):
 @reg.register_compute("greater")
 def compute_greater(_, inputs, out_info):
     """Compute definition of greater"""
-    return topi.tensor.greater(inputs[0], inputs[1], 'float32')
+    return topi.greater(inputs[0], inputs[1]).astype('float32')
 reg.register_pattern("greater", OpPattern.ELEMWISE)
 reg.register_schedule("greater", _fschedule_elemwise)
 
@@ -252,7 +252,7 @@ def compute_greater(_, inputs, out_info):
 @reg.register_compute("less")
 def compute_less(_, inputs, out_info):
     """Compute definition of less"""
-    return topi.tensor.less(inputs[0], inputs[1], 'float32')
+    return topi.less(inputs[0], inputs[1]).astype('float32')
 reg.register_pattern("less", OpPattern.ELEMWISE)
 reg.register_schedule("less", _fschedule_elemwise)
 
diff --git a/nnvm/src/top/tensor/broadcast.cc b/nnvm/src/top/tensor/broadcast.cc
index c9f871873cea..74fc9b340606 100644
--- a/nnvm/src/top/tensor/broadcast.cc
+++ b/nnvm/src/top/tensor/broadcast.cc
@@ -200,7 +200,7 @@ inline bool BinaryBroadcastCorrectLayout(const NodeAttrs& attrs,
   return true;
 }
 
-#define NNVM_REGISTER_BINARY_BROADCAST_OP(name)                     \
+#define NNVM_REGISTER_BINARY_BROADCAST_OP(name, TOPIOp)             \
   NNVM_REGISTER_OP(name)                                            \
   .set_num_inputs(2)                                                \
   .set_num_outputs(1)                                               \
@@ -217,13 +217,13 @@ inline bool BinaryBroadcastCorrectLayout(const NodeAttrs& attrs,
       const Array<Tensor>& inputs,                                  \
       const Array<Tensor>& out_info) {                              \
         return Array<Tensor>{                                       \
-          topi::name(inputs[0], inputs[1]) };                       \
+          topi::TOPIOp(inputs[0], inputs[1]) };                     \
     })                                                              \
   .add_argument("lhs", "Tensor", "first input")                     \
   .add_argument("rhs", "Tensor", "second input")
 
 
-NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_add)
+NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_add, add)
 .add_alias("__add_symbol__")
 .describe(R"code(Returns element-wise sum of the input arrays with broadcasting.
 
@@ -241,7 +241,7 @@ Example::
 )code" NNVM_ADD_FILELINE);
 
 
-NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_sub)
+NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_sub, subtract)
 .add_alias("__sub_symbol__")
 .describe(R"code(Returns element-wise difference of the input arrays with broadcasting.
 
@@ -259,7 +259,7 @@ Example::
 )code" NNVM_ADD_FILELINE);
 
 
-NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_mul)
+NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_mul, multiply)
 .add_alias("__mul_symbol__")
 .describe(R"code(Returns element-wise product of the input arrays with broadcasting.
 
@@ -276,7 +276,7 @@ Example::
 )code" NNVM_ADD_FILELINE);
 
 
-NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_div)
+NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_div, divide)
 .add_alias("__div_symbol__")
 .describe(R"code(Returns element-wise division of the input arrays with broadcasting.
 
diff --git a/nnvm/src/top/tensor/elemwise.cc b/nnvm/src/top/tensor/elemwise.cc
index 27e19e323bb8..cff85497be4f 100644
--- a/nnvm/src/top/tensor/elemwise.cc
+++ b/nnvm/src/top/tensor/elemwise.cc
@@ -230,7 +230,7 @@ NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_add)
   "FTVMCompute", [](const NodeAttrs& attrs,
                     const Array<Tensor>& inputs,
                     const Array<Tensor>& out_info) {
-      return Array<Tensor>{ topi::broadcast_add(inputs[0], inputs[1]) };
+      return Array<Tensor>{ topi::add(inputs[0], inputs[1]) };
   })
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
@@ -253,7 +253,7 @@ NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_sub)
   "FTVMCompute", [](const NodeAttrs& attrs,
                     const Array<Tensor>& inputs,
                     const Array<Tensor>& out_info) {
-      return Array<Tensor>{ topi::broadcast_sub(inputs[0], inputs[1]) };
+    return Array<Tensor>{ topi::subtract(inputs[0], inputs[1]) };
 })
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
@@ -276,7 +276,7 @@ NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_mul)
   "FTVMCompute", [](const NodeAttrs& attrs,
                     const Array<Tensor>& inputs,
                     const Array<Tensor>& out_info) {
-      return Array<Tensor>{ topi::broadcast_mul(inputs[0], inputs[1]) };
+      return Array<Tensor>{ topi::multiply(inputs[0], inputs[1]) };
 })
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
@@ -301,7 +301,7 @@ NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_div)
   "FTVMCompute", [](const NodeAttrs& attrs,
                     const Array<Tensor>& inputs,
                     const Array<Tensor>& out_info) {
-      return Array<Tensor>{ topi::broadcast_div(inputs[0], inputs[1]) };
+      return Array<Tensor>{ topi::divide(inputs[0], inputs[1]) };
 })
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
diff --git a/python/tvm/expr.py b/python/tvm/expr.py
index 5ba2928306e8..df5e51a53be9 100644
--- a/python/tvm/expr.py
+++ b/python/tvm/expr.py
@@ -137,7 +137,7 @@ def astype(self, dtype):
         expr : Expr
             Expression with new type
         """
-        return _make.static_cast(dtype, self)
+        return _generic.cast(self, dtype)
 
 
 class EqualOp(NodeGeneric, ExprOp):
diff --git a/python/tvm/generic.py b/python/tvm/generic.py
index 7e70d66e432a..2926f73d5a02 100644
--- a/python/tvm/generic.py
+++ b/python/tvm/generic.py
@@ -79,3 +79,19 @@ def divide(lhs, rhs):
         The result Expr of divide operaton.
     """
     return _make.Div(lhs, rhs)
+
+
+def cast(src, dtype):
+    """Generic cast operator.
+
+    Parameters
+    ----------
+    src : object
+        The source operand.
+
+    Returns
+    -------
+    op : tvm.Expr
+        The result Expr of divide operaton.
+    """
+    return _make.static_cast(dtype, src)
diff --git a/tests/python/unittest/test_lang_tensor_overload_op.py b/tests/python/unittest/test_lang_tensor_overload_op.py
index c1d860b89b61..14853e89188a 100644
--- a/tests/python/unittest/test_lang_tensor_overload_op.py
+++ b/tests/python/unittest/test_lang_tensor_overload_op.py
@@ -15,11 +15,11 @@ def test_operator_type_and_tags():
 
     assert isinstance(k + n, tvm.expr.Expr)
     assert isinstance(n + n, tvm.expr.Expr)
-    assert isinstance(k + A, tvm.expr.Expr)
-    assert isinstance(A + k, tvm.expr.Expr)
-    assert isinstance(n + A, tvm.expr.Expr)
-    assert isinstance(A + n, tvm.expr.Expr)
-    assert isinstance(A + A, tvm.expr.Expr)
+    assert isinstance(k + A, tvm.tensor.Tensor)
+    assert isinstance(A + k, tvm.tensor.Tensor)
+    assert isinstance(n + A, tvm.tensor.Tensor)
+    assert isinstance(A + n, tvm.tensor.Tensor)
+    assert isinstance(A + A, tvm.tensor.Tensor)
 
     assert isinstance(k + B, tvm.tensor.Tensor)
     assert isinstance(B + k, tvm.tensor.Tensor)
@@ -33,8 +33,8 @@ def test_operator_type_and_tags():
     assert (B + k).op.tag == topi.tag.ELEMWISE
     assert (n + B).op.tag == topi.tag.ELEMWISE
     assert (B + n).op.tag == topi.tag.ELEMWISE
-    assert (A + B).op.tag == topi.tag.ELEMWISE
-    assert (B + A).op.tag == topi.tag.ELEMWISE
+    assert (A + B).op.tag == topi.tag.BROADCAST
+    assert (B + A).op.tag == topi.tag.BROADCAST
     assert (B + B).op.tag == topi.tag.BROADCAST
 
     assert isinstance(k + B2, tvm.expr.Expr)
@@ -42,8 +42,8 @@ def test_operator_type_and_tags():
     assert isinstance(n + B2, tvm.expr.Expr)
     assert isinstance(B2 + n, tvm.expr.Expr)
     assert isinstance(B2 + B2, tvm.expr.Expr)
-    assert isinstance(B2 + A, tvm.expr.Expr)
-    assert isinstance(A + B2, tvm.expr.Expr)
+    assert isinstance(B2 + A, tvm.tensor.Tensor)
+    assert isinstance(A + B2, tvm.tensor.Tensor)
     assert isinstance(B2 + B, tvm.tensor.Tensor)
     assert isinstance(B + B2, tvm.tensor.Tensor)
 
@@ -246,4 +246,4 @@ def test_conv2d_scalar_bop():
     test_combination()
     test_tensor_scalar_bop()
     test_broadcast_bop()
-    test_conv2d_scalar_bop()
\ No newline at end of file
+    test_conv2d_scalar_bop()
diff --git a/topi/include/topi/broadcast.h b/topi/include/topi/broadcast.h
index 031baff7f983..1a93d26f8c55 100644
--- a/topi/include/topi/broadcast.h
+++ b/topi/include/topi/broadcast.h
@@ -7,7 +7,7 @@
 #define TOPI_BROADCAST_H_
 
 #include <string>
-
+#include <algorithm>
 #include "topi/detail/broadcast.h"
 #include "topi/detail/constant_utils.h"
 #include "topi/tags.h"
@@ -47,167 +47,215 @@ inline tvm::Tensor broadcast_to(const tvm::Tensor& t,
       tag);
 }
 
+#define TOPI_DEFINE_BCAST_OP(Name, ComputeRule)                   \
+  inline tvm::Expr Name(const tvm::Expr& a,                       \
+                        const tvm::Expr& b) {                     \
+    ComputeRule;                                                  \
+  }                                                               \
+  inline tvm::Tensor Name(const tvm::Tensor& A,                   \
+                          const tvm::Tensor& B,                   \
+                          std::string name = "tensor",            \
+                          std::string tag = kBroadcast) {         \
+    auto l = [](tvm::Expr a, tvm::Expr b) { ComputeRule; };       \
+    return detail::WithBroadcast(l, A, B, name, tag);             \
+  }                                                               \
+  inline tvm::Tensor Name(const tvm::Tensor& A,                   \
+                          const tvm::Expr& B,                     \
+                          std::string name = "tensor",            \
+                          std::string tag = kElementWise) {       \
+    auto l = [](tvm::Expr a, tvm::Expr b) { ComputeRule; };           \
+    return compute(A->shape, [&](const ::tvm::Array<::tvm::Var>& i) { \
+        return l(A(i), B);                                        \
+      }, name, tag);                                              \
+  }                                                               \
+  inline tvm::Tensor Name(const tvm::Expr& A,                     \
+                          const tvm::Tensor& B,                   \
+                          std::string name = "tensor",            \
+                          std::string tag = kElementWise) {       \
+    auto l = [&](tvm::Expr a, tvm::Expr b) { ComputeRule; };      \
+    return compute(B->shape, [&](const ::tvm::Array<::tvm::Var>& i) { \
+        return l(A, B(i));                                        \
+      }, name, tag);                                              \
+  }
+
+
+#define TOPI_DEFINE_OP_OVERLOAD(Name, OpName)                       \
+  inline tvm::Tensor Name(const tvm::Tensor& A,                     \
+                          const tvm::Tensor& B) {                   \
+    return topi::OpName(A, B);                                      \
+  }                                                                 \
+  inline tvm::Tensor Name(const tvm::Expr& A,                       \
+                          const tvm::Tensor& B) {                   \
+    return topi::OpName(A, B);                                      \
+  }                                                                 \
+  inline tvm::Tensor Name(const tvm::Tensor& A,                     \
+                          const tvm::Expr& B) {                     \
+    return topi::OpName(A, B);                                      \
+  }
+
+
 /*!
- * \brief Creates an operation that performs pointwise addition of 2 tensors
- * and broadcasts them into a common compatible shape where necessary,
- * according to numpy's rules
+ * \fn add
+ * \brief Compute A + B with auto-broadcasting.
  *
- * \param A The first tensor to add
- * \param B The second tensor to add
+ * \param A The first tensor, or Expr
+ * \param B The second tensor, or Expr
  * \param name The name of the operation
  * \param tag The tag to mark the operation
  *
- * \return A Tensor whose op member is a pointwise addition with broadcast
+ * \return The result.
  */
-inline tvm::Tensor broadcast_add(const tvm::Tensor& A,
-                                 const tvm::Tensor& B,
-                                 std::string name = "tensor",
-                                 std::string tag = kBroadcast) {
-  auto l = [&](tvm::Expr a, tvm::Expr b) { return a + b; };
-  return detail::WithBroadcast(l, A, B, name, tag);
-}
+TOPI_DEFINE_BCAST_OP(add, { return a + b; });
+TOPI_DEFINE_OP_OVERLOAD(operator+, add);
 
 /*!
- * \brief Creates an operation that performs pointwise subtraction of 2 tensors
- * and broadcasts them into a common compatible shape where necessary,
- * according to numpy's rules
+ * \fn subtract
+ * \brief Compute A - B with auto-broadcasting.
  *
- * \param A The first tensor
- * \param B The second tensor to subtract from the first
+ * \param A The first tensor, or Expr
+ * \param B The second tensor, or Expr
  * \param name The name of the operation
  * \param tag The tag to mark the operation
  *
- * \return A Tensor whose op member is a pointwise subtraction with broadcast
+ * \return The result.
  */
-inline tvm::Tensor broadcast_sub(const tvm::Tensor& A,
-                                 const tvm::Tensor& B,
-                                 std::string name = "tensor",
-                                 std::string tag = kBroadcast) {
-  auto l = [&](tvm::Expr a, tvm::Expr b) { return a - b; };
-  return detail::WithBroadcast(l, A, B, name, tag);
-}
+TOPI_DEFINE_BCAST_OP(subtract, { return a - b; });
+TOPI_DEFINE_OP_OVERLOAD(operator-, subtract);
 
 /*!
- * \brief Creates an operation that performs pointwise multiplication of 2
- * tensors and broadcasts them into a common compatible shape where necessary,
- * according to numpy's rules
+ * \fn multiply
+ * \brief Compute A * B with auto-broadcasting.
  *
- * \param A The first tensor to multiply
- * \param B The second tensor to multiply
+ * \param A The first tensor, or Expr
+ * \param B The second tensor, or Expr
  * \param name The name of the operation
  * \param tag The tag to mark the operation
  *
- * \return A Tensor whose op member is a pointwise multiplication with broadcast
+ * \return The result.
  */
-inline tvm::Tensor broadcast_mul(const tvm::Tensor& A,
-                                 const tvm::Tensor& B,
-                                 std::string name = "tensor",
-                                 std::string tag = kBroadcast) {
-  auto l = [&](tvm::Expr a, tvm::Expr b) { return a * b; };
-  return detail::WithBroadcast(l, A, B, name, tag);
-}
+TOPI_DEFINE_BCAST_OP(multiply, { return a * b; });
+TOPI_DEFINE_OP_OVERLOAD(operator*, multiply);
 
 /*!
- * \brief Creates an operation that performs pointwise division of 2 tensors
- * and broadcasts them into a common compatible shape where necessary,
- * according to numpy's rules
+ * \fn divide
+ * \brief Compute A / B with auto-broadcasting.
  *
- * \param A The first tensor
- * \param B The second tensor to divide the first tensor with
+ * \param A The first tensor, or Expr
+ * \param B The second tensor, or Expr
  * \param name The name of the operation
  * \param tag The tag to mark the operation
  *
- * \return A Tensor whose op member is a pointwise division with broadcast
+ * \return The result.
  */
-inline tvm::Tensor broadcast_div(const tvm::Tensor& A,
-                                 const tvm::Tensor& B,
-                                 std::string name = "tensor",
-                                 std::string tag = kBroadcast) {
-  auto l = [&](tvm::Expr a, tvm::Expr b) { return a / b; };
-  return detail::WithBroadcast(l, A, B, name, tag);
-}
+TOPI_DEFINE_BCAST_OP(divide, { return a / b; });
+TOPI_DEFINE_OP_OVERLOAD(operator/, divide);
 
 /*!
- * \brief Creates an operation that performs pointwise modulo remainder of 2
- * tensors and broadcasts them into a common compatible shape where necessary,
- * according to numpy's rules
+ * \fn mod
+ * \brief Compute A % B with auto-broadcasting.
  *
- * \param A The first tensor
- * \param B The second tensor to compute A % B
+ * \param A The first tensor, or Expr
+ * \param B The second tensor, or Expr
  * \param name The name of the operation
  * \param tag The tag to mark the operation
  *
- * \return A Tensor whose op member is a pointwise modulo remainder with
- * broadcast
+ * \return The result.
  */
-inline tvm::Tensor broadcast_mod(const tvm::Tensor& A,
-                                 const tvm::Tensor& B,
-                                 std::string name = "tensor",
-                                 std::string tag = kBroadcast) {
-  auto l = [&](tvm::Expr a, tvm::Expr b) { return a % b; };
-  return detail::WithBroadcast(l, A, B, name, tag);
-}
+TOPI_DEFINE_BCAST_OP(mod, { return a % b; });
+TOPI_DEFINE_OP_OVERLOAD(operator%, mod);
 
 /*!
-* \brief Creates an operation that performs pointwise maximum of 2 tensors
-* and broadcasts them into a common compatible shape where necessary,
-* according to numpy's rules
-*
-* \param A The first tensor
-* \param B The second tensor
-* \param name The name of the operation
-* \param tag The tag to mark the operation
-*
-* \return A Tensor whose op member is a pointwise maximum with broadcast
-*/
-inline tvm::Tensor broadcast_maximum(const tvm::Tensor& A,
-                                 const tvm::Tensor& B,
-                                 std::string name = "tensor",
-                                 std::string tag = kBroadcast) {
-  auto l = [&](tvm::Expr a, tvm::Expr b) { return tvm::max(a, b); };  // NOLINT(*)
-  return detail::WithBroadcast(l, A, B, name, tag);
-}
+ * \fn maximum
+ * \brief Compute maximum(A, B) with auto-broadcasting.
+ *
+ * \param A The first tensor, or Expr
+ * \param B The second tensor, or Expr
+ * \param name The name of the operation
+ * \param tag The tag to mark the operation
+ *
+ * \return The result.
+ */
+TOPI_DEFINE_BCAST_OP(maximum, { return tvm::max(a, b); });
 
 /*!
-* \brief Creates an operation that performs pointwise minimum of 2 tensors
-* and broadcasts them into a common compatible shape where necessary,
-* according to numpy's rules
-*
-* \param A The first tensor
-* \param B The second tensor
-* \param name The name of the operation
-* \param tag The tag to mark the operation
-*
-* \return A Tensor whose op member is a pointwise minimum with broadcast
-*/
-inline tvm::Tensor broadcast_minimum(const tvm::Tensor& A,
-                                 const tvm::Tensor& B,
-                                 std::string name = "tensor",
-                                 std::string tag = kBroadcast) {
-  auto l = [&](tvm::Expr a, tvm::Expr b) { return tvm::min(a, b); };  // NOLINT(*)
-  return detail::WithBroadcast(l, A, B, name, tag);
-}
+ * \fn minimum
+ * \brief Compute minimum(A, B) with auto-broadcasting.
+ *
+ * \param A The first tensor, or Expr
+ * \param B The second tensor, or Expr
+ * \param name The name of the operation
+ * \param tag The tag to mark the operation
+ *
+ * \return The result.
+ */
+TOPI_DEFINE_BCAST_OP(minimum, { return tvm::min(a, b); });
 
 /*!
-* \brief Creates an operation that raises one tensor to the power of another
-* pointwise and broadcasts them into a common compatible shape where necessary,
-* according to numpy's rules
-*
-* \param A The first tensor
-* \param B The second tensor to compute pow(A, B)
-* \param name The name of the operation
-* \param tag The tag to mark the operation
-*
-* \return A Tensor whose op member is a pointwise pow with
-* broadcast
-*/
-inline tvm::Tensor broadcast_pow(const tvm::Tensor& A,
-                                 const tvm::Tensor& B,
-                                 std::string name = "tensor",
-                                 std::string tag = kBroadcast) {
-  auto l = [&](tvm::Expr a, tvm::Expr b) { return tvm::pow(a, b); };
-  return detail::WithBroadcast(l, A, B, name, tag);
-}
+ * \fn power
+ * \brief Compute power(A, B) with auto-broadcasting.
+ *
+ * \param A The first tensor, or Expr
+ * \param B The second tensor, or Expr
+ * \param name The name of the operation
+ * \param tag The tag to mark the operation
+ *
+ * \return The result.
+ */
+TOPI_DEFINE_BCAST_OP(power, { return tvm::pow(a, b); });
+
+/*!
+ * \fn left_shift
+ * \brief Compute A << B with auto-broadcasting.
+ *
+ * \param A The first tensor, or Expr
+ * \param B The second tensor, or Expr
+ * \param name The name of the operation
+ * \param tag The tag to mark the operation
+ *
+ * \return The result.
+ */
+TOPI_DEFINE_BCAST_OP(left_shift, { return a << b; });
+TOPI_DEFINE_OP_OVERLOAD(operator<<, left_shift);
+
+/*!
+ * \fn right_shift
+ * \brief Compute A >> B with auto-broadcasting.
+ *
+ * \param A The first tensor, or Expr
+ * \param B The second tensor, or Expr
+ * \param name The name of the operation
+ * \param tag The tag to mark the operation
+ *
+ * \return The result.
+ */
+TOPI_DEFINE_BCAST_OP(right_shift, { return a >> b; });
+TOPI_DEFINE_OP_OVERLOAD(operator>>, right_shift);
+
+/*!
+ * \fn greater
+ * \brief Compute (A > B) with auto-broadcasting.
+ *
+ * \param A The first tensor, or Expr
+ * \param B The second tensor, or Expr
+ * \param name The name of the operation
+ * \param tag The tag to mark the operation
+ *
+ * \return The result.
+ */
+TOPI_DEFINE_BCAST_OP(greater, { return (a > b); });
+
+/*!
+ * \fn less
+ * \brief Compute (A < B) with auto-broadcasting.
+ *
+ * \param A The first tensor, or Expr
+ * \param B The second tensor, or Expr
+ * \param name The name of the operation
+ * \param tag The tag to mark the operation
+ *
+ * \return The result.
+ */
+TOPI_DEFINE_BCAST_OP(less, { return (a < b); });
 
 }  // namespace topi
 
diff --git a/topi/include/topi/elemwise.h b/topi/include/topi/elemwise.h
index cef6ea9d629b..2e4a95e8691b 100644
--- a/topi/include/topi/elemwise.h
+++ b/topi/include/topi/elemwise.h
@@ -68,55 +68,6 @@ inline Tensor negative(const Tensor& x,
   }, name, tag);
 }
 
-/*!
-* \brief Creates an operation that raises each element of tensor x to power y
-*
-* \param x The input tensor
-* \param y The exponent
-* \param name The name of the operation
-* \param tag The tag to mark the operation
-*
-* \return A Tensor whose op member is the pow operation
-*/
-inline Tensor pow(const Tensor& x,
-                  const Expr& y,
-                  std::string name = "tensor",
-                  std::string tag = kElementWise) {
-  return compute(x->shape, [&](const Array<Var>& i) {
-    return tvm::pow(x(i), y);
-  }, name, tag);
-}
-
-/*!
-* \brief Creates an operation that performs pointwise left shift by n bits
-*
-* \param x The input tensor
-* \param n The number of bits to shift by
-*
-* \return A Tensor whose op member is the left shift operation
-*/
-inline Tensor operator<<(const Tensor& x,
-                         const Expr& n) {
-  return compute(x->shape, [&](const Array<Var>& i) {
-    return x(i) << n;
-  }, "tensor", kElementWise);
-}
-
-/*!
-* \brief Creates an operation that performs pointwise right shift by n bits
-*
-* \param x The input tensor
-* \param n The number of bits to shift by
-*
-* \return A Tensor whose op member is the right shift operation
-*/
-inline Tensor operator>>(const Tensor& x,
-                         const Expr& n) {
-  return compute(x->shape, [&](const Array<Var>& i) {
-    return x(i) >> n;
-  }, "tensor", kElementWise);
-}
-
 /*!
 * \brief Creates an operation that clips each element of a tensor to
 * the interval [a_min, a_max]
diff --git a/topi/include/topi/nn/l2_normalize.h b/topi/include/topi/nn/l2_normalize.h
index 079c6d467561..cda1f3b5c813 100644
--- a/topi/include/topi/nn/l2_normalize.h
+++ b/topi/include/topi/nn/l2_normalize.h
@@ -26,20 +26,20 @@ using namespace tvm;
 * \return A Tensor whose op member is the l2 normalization operation
 */
 inline Tensor l2_normalize(const Tensor& data,
-                              float eps,
-                              const Array<Expr>& axis,
-                              std::string name = "tensor",
-                              std::string tag = "l2_normalize") {
+                           float eps,
+                           const Array<Expr>& axis,
+                           std::string name = "tensor",
+                           std::string tag = "l2_normalize") {
   CHECK_EQ(data->shape.size(), 4) << "L2 normalization requires 4-D input";
   auto input_shape = data->shape;
-  Tensor dot_value = pow(data, static_cast<float>(2.0));
+  Tensor dot_value = topi::power(data, static_cast<float>(2.0));
   Tensor sum_value = topi::sum(dot_value, axis, true);
   Tensor expand_sum = topi::broadcast_to(sum_value, input_shape);
-  return topi::broadcast_div(data,
-                             topi::sqrt(tvm::compute(expand_sum->shape,
-                                                     [&](const Array<Var>& i){
-                                                     return (max(expand_sum(i), eps));
-                                                     }, name = name, tag = tag)));
+  return topi::divide(data,
+                      topi::sqrt(tvm::compute(expand_sum->shape,
+                                              [&](const Array<Var>& i){
+                                                return (max(expand_sum(i), eps));
+                                              }, name = name, tag = tag)));
 }
 }  // namespace nn
 }  // namespace topi
diff --git a/topi/include/topi/nn/local_response_norm.h b/topi/include/topi/nn/local_response_norm.h
index c956a9c253dc..96e1fdcbda48 100644
--- a/topi/include/topi/nn/local_response_norm.h
+++ b/topi/include/topi/nn/local_response_norm.h
@@ -63,13 +63,14 @@ inline Tensor lrn(const Tensor& data,
                                            {rxs});
                            });
   }
-  auto sqrt_sum_up = tvm::compute(input_shape,
-                                  [&](Var i, Var j, Var k, Var l) {
-                                  return tvm::pow(bias +
-                                                  (alpha * sqr_sum(i, j, k, l) / size),
-                                                  beta);
-                                  });
-  return topi::broadcast_div(data, sqrt_sum_up);
+  auto sqrt_sum_up = tvm::compute(
+      input_shape,
+      [&](Var i, Var j, Var k, Var l) {
+        return tvm::pow(bias +
+                        (alpha * sqr_sum(i, j, k, l) / size),
+                        beta);
+      });
+  return topi::divide(data, sqrt_sum_up);
 }
 }  // namespace nn
 }  // namespace topi
diff --git a/topi/python/topi/broadcast.py b/topi/python/topi/broadcast.py
index cf76fe93b600..caf5c77f5629 100644
--- a/topi/python/topi/broadcast.py
+++ b/topi/python/topi/broadcast.py
@@ -1,245 +1,251 @@
-# pylint: disable=no-member,consider-using-enumerate
 """Broadcast operators"""
 from __future__ import absolute_import as _abs
-import tvm
-from .import tag
-from .util import get_const_tuple, equal_const_int, get_const_int
+from .import cpp as _cpp
 
-def _get_bcast_info(original_shape, target_shape):
-    """Get the broadcasting info.
+def broadcast_to(data, shape):
+    """Broadcast the src to the target shape
+
+    We follows the numpy broadcasting rule.
+    See also https://docs.scipy.org/doc/numpy/user/basics.broadcasting.html
 
-    bcast_info = _get_bcast_info(original_shape, target_shape)
-    In bcast_info:
-      -1 means to the padding dim
-       0 means to to be the same as the original shape
-       1 means to the broadcasted dim
+    Parameters
+    ----------
+    data : tvm.Tensor
+        The input data
+
+    shape : list or tuple
+        The target shape to be broadcasted.
+
+    Returns
+    -------
+    ret : tvm.Tensor
+    """
+    return _cpp.broadcast_to(data, shape)
 
-    E.g
-    original: (2, 1, 5), target: (2, 4, 5) => bcast_info: (0, 1, 0)
-    original: (2, 5), target: (4, 2, 5) => bcast_info: (-1, 0, 0)
-    original: (1, 5), target: (4, 2, 5) => bcast_info: (-1, 1, 0)
+
+def add(lhs, rhs):
+    """Addition with auto-broadcasting
 
     Parameters
     ----------
-    original_shape : tuple of tvm.expr.IntImm
-        The original shape before broadcasting
+    lhs : tvm.Tensor or Expr
+        The left operand
+    rhs : tvm.Tensor or Expr
+        The right operand
+
+    Returns
+    -------
+    ret : tvm.Tensor or Expr
+        Returns Expr if both operands are Expr.
+        Otherwise returns Tensor.
+    """
+    return _cpp.add(lhs, rhs)
+
+
+def subtract(lhs, rhs):
+    """Subtraction with auto-broadcasting
 
-    target_shape : tuple
-        The target shape
+    Parameters
+    ----------
+    lhs : tvm.Tensor or Expr
+        The left operand
+    rhs : tvm.Tensor or Expr
+        The right operand
 
     Returns
     -------
-    bcast_info : list
+    ret : tvm.Tensor or Expr
+        Returns Expr if both operands are Expr.
+        Otherwise returns Tensor.
     """
-    assert len(target_shape) >= len(original_shape)
-    bcast_info = [-1 for _ in range(len(target_shape))]
-    original_shape = [original_shape[i] for i in range(len(original_shape))]
-    original_shape = original_shape[::-1]
-    target_shape = target_shape[::-1]
-    for i in range(len(original_shape)):
-        if equal_const_int(original_shape[i], target_shape[i]):
-            bcast_info[i] = 0
-        elif equal_const_int(original_shape[i], 1):
-            bcast_info[i] = 1
-        else:
-            raise ValueError("Original Shape: {} cannot be broadcast to  {}"
-                             .format(original_shape[::-1], target_shape[::-1]))
-    bcast_info = bcast_info[::-1]
-    return bcast_info
-
-
-def _get_binary_op_bcast_shape(lhs_shape, rhs_shape):
-    """Get the shape after binary broadcasting.
-
-    We will strictly follow the broadcasting rule in numpy.
+    return _cpp.subtract(lhs, rhs)
+
+
+def multiply(lhs, rhs):
+    """Multiplication with auto-broadcasting
 
     Parameters
     ----------
-    lhs_shape : tuple
-    rhs_shape : tuple
+    lhs : tvm.Tensor or Expr
+        The left operand
+    rhs : tvm.Tensor or Expr
+        The right operand
 
     Returns
     -------
-    ret_shape : tuple
+    ret : tvm.Tensor or Expr
+        Returns Expr if both operands are Expr.
+        Otherwise returns Tensor.
     """
-    ret_shape = []
-    if len(lhs_shape) > len(rhs_shape):
-        lhs_shape, rhs_shape = rhs_shape, lhs_shape
-    for ptr in range(len(rhs_shape)):
-        if ptr < len(lhs_shape):
-            l_val, r_val = lhs_shape[len(lhs_shape) - 1 - ptr], \
-                           rhs_shape[len(rhs_shape) - 1 - ptr]
-            assert(l_val == 1 or r_val == 1 or l_val == r_val),\
-                "Shape is NOT broadcastable, lhs=%s, rhs=%s"\
-                %(str(lhs_shape), str(rhs_shape))
-            ret_shape.append(max(l_val, r_val))
-        else:
-            ret_shape.append(rhs_shape[len(rhs_shape) - 1 - ptr])
-    ret_shape = ret_shape[::-1]
-    return ret_shape
-
-
-
-@tvm.tag_scope(tag=tag.BROADCAST)
-def broadcast_to(data, shape):
-    """Broadcast the src to the target shape
+    return _cpp.multiply(lhs, rhs)
 
-    We follows the numpy broadcasting rule.
-    See also https://docs.scipy.org/doc/numpy/user/basics.broadcasting.html
+
+def divide(lhs, rhs):
+    """Division with auto-broadcasting
 
     Parameters
     ----------
-    data : tvm.Tensor
+    lhs : tvm.Tensor or Expr
+        The left operand
+    rhs : tvm.Tensor or Expr
+        The right operand
 
-    shape : list or tuple
+    Returns
+    -------
+    ret : tvm.Tensor or Expr
+        Returns Expr if both operands are Expr.
+        Otherwise returns Tensor.
+    """
+    return _cpp.divide(lhs, rhs)
+
+
+def mod(lhs, rhs):
+    """Modulus with auto-broadcasting
+
+    Parameters
+    ----------
+    lhs : tvm.Tensor or Expr
+        The left operand
+    rhs : tvm.Tensor or Expr
+        The right operand
 
     Returns
     -------
-    ret : tvm.Tensor
+    ret : tvm.Tensor or Expr
+        Returns Expr if both operands are Expr.
+        Otherwise returns Tensor.
     """
-    def _bcast_to_arg_eval(data, bcast_info, *indices):
-        indices_tuple = []
-        for i, ind in enumerate(indices):
-            if bcast_info[i] == 0:
-                indices_tuple.append(ind)
-            elif bcast_info[i] == 1:
-                indices_tuple.append(0)
-        return data[tuple(indices_tuple)]
-    original_shape = data.shape
-    shape = [get_const_int(i) for i in shape]
-    bcast_info = _get_bcast_info(original_shape=original_shape, target_shape=shape)
-    ret = tvm.compute(shape,
-                      lambda *indices: _bcast_to_arg_eval(data,
-                                                          bcast_info,
-                                                          *indices), name=data.name + "_broadcast")
-    return ret
-
-
-@tvm.tag_scope(tag=tag.BROADCAST)
-def broadcast_binary_op(lhs, rhs, func, name="bop"):
-    """Binary operands that will automatically broadcast the inputs
+    return _cpp.mod(lhs, rhs)
 
-    We follows the numpy broadcasting rule.
-    See also https://docs.scipy.org/doc/numpy/user/basics.broadcasting.html
+
+def maximum(lhs, rhs):
+    """Take element-wise maximum of two tensors with auto-broadcasting
 
     Parameters
     ----------
-    lhs : tvm.Tensor
-    rhs : tvm.Tensor
-    func : function
+    lhs : tvm.Tensor or Expr
+        The left operand
+    rhs : tvm.Tensor or Expr
+        The right operand
 
     Returns
     -------
-    ret : tvm.Tensor
+    ret : tvm.Tensor or Expr
+        Returns Expr if both operands are Expr.
+        Otherwise returns Tensor.
     """
-    def _inner_arg_eval(lhs, rhs, lhs_bcast_info, rhs_bcast_info, func, *indices):
-        lhs_indices = []
-        rhs_indices = []
-        for i, ind in enumerate(indices):
-            if lhs_bcast_info[i] == 0:
-                lhs_indices.append(ind)
-            elif lhs_bcast_info[i] == 1:
-                lhs_indices.append(0)
-            if rhs_bcast_info[i] == 0:
-                rhs_indices.append(ind)
-            elif rhs_bcast_info[i] == 1:
-                rhs_indices.append(0)
-        return func(lhs[tuple(lhs_indices)], rhs[tuple(rhs_indices)])
-    ret_shape = _get_binary_op_bcast_shape(get_const_tuple(lhs.shape), get_const_tuple(rhs.shape))
-    lhs_bcast_info = _get_bcast_info(original_shape=lhs.shape, target_shape=ret_shape)
-    rhs_bcast_info = _get_bcast_info(original_shape=rhs.shape, target_shape=ret_shape)
-    ret = tvm.compute(ret_shape,
-                      lambda *indices: _inner_arg_eval(lhs, rhs, lhs_bcast_info, rhs_bcast_info,
-                                                       func, *indices),
-                      name=lhs.name + "_" + rhs.name + "_" + name)
-    return ret
-
-
-def broadcast_add(lhs, rhs):
-    """Binary addition with auto-broadcasting
+    return _cpp.maximum(lhs, rhs)
+
+
+def minimum(lhs, rhs):
+    """Take element-wise maximum of two tensors with auto-broadcasting
 
     Parameters
     ----------
-    lhs : tvm.Tensor
-    rhs : tvm.Tensor
+    lhs : tvm.Tensor or Expr
+        The left operand
+    rhs : tvm.Tensor or Expr
+        The right operand
 
     Returns
     -------
-    ret : tvm.Tensor
+    ret : tvm.Tensor or Expr
+        Returns Expr if both operands are Expr.
+        Otherwise returns Tensor.
     """
-    return broadcast_binary_op(lhs, rhs, lambda a, b: a + b, "add")
+    return _cpp.minimum(lhs, rhs)
 
 
-def broadcast_mul(lhs, rhs):
-    """Binary multiplication with auto-broadcasting
+def power(lhs, rhs):
+    """Power with auto-broadcasting
 
     Parameters
     ----------
-    lhs : tvm.Tensor
-    rhs : tvm.Tensor
+    lhs : tvm.Tensor or Expr
+        The left operand
+    rhs : tvm.Tensor or Expr
+        The right operand
 
     Returns
     -------
-    ret : tvm.Tensor
+    ret : tvm.Tensor or Expr
+        Returns Expr if both operands are Expr.
+        Otherwise returns Tensor.
     """
-    return broadcast_binary_op(lhs, rhs, lambda a, b: a * b, "mul")
+    return _cpp.power(lhs, rhs)
 
 
-def broadcast_div(lhs, rhs):
-    """Binary division with auto-broadcasting
+def left_shift(lhs, rhs):
+    """Left shift with auto-broadcasting
 
     Parameters
     ----------
-    lhs : tvm.Tensor
-    rhs : tvm.Tensor
+    lhs : tvm.Tensor or Expr
+        The left operand
+    rhs : tvm.Tensor or Expr
+        The right operand
 
     Returns
     -------
-    ret : tvm.Tensor
+    ret : tvm.Tensor or Expr
+        Returns Expr if both operands are Expr.
+        Otherwise returns Tensor.
     """
-    return broadcast_binary_op(lhs, rhs, lambda a, b: a / b, "div")
+    return _cpp.left_shift(lhs, rhs)
 
 
-def broadcast_sub(lhs, rhs):
-    """Binary subtraction with auto-broadcasting
+def right_shift(lhs, rhs):
+    """Right shift with auto-broadcasting
 
     Parameters
     ----------
-    lhs : tvm.Tensor
-    rhs : tvm.Tensor
+    lhs : tvm.Tensor or Expr
+        The left operand
+    rhs : tvm.Tensor or Expr
+        The right operand
 
     Returns
     -------
-    ret : tvm.Tensor
+    ret : tvm.Tensor or Expr
+        Returns Expr if both operands are Expr.
+        Otherwise returns Tensor.
     """
-    return broadcast_binary_op(lhs, rhs, lambda a, b: a - b, "sub")
+    return _cpp.left_shift(lhs, rhs)
 
 
-def broadcast_maximum(lhs, rhs):
-    """Take element-wise maximum of two tensors with auto-broadcasting
+def greater(lhs, rhs):
+    """Compute (lhs>rhs) with auto-broadcasting
 
     Parameters
     ----------
-    lhs : tvm.Tensor
-    rhs : tvm.Tensor
+    lhs : tvm.Tensor or Expr
+        The left operand
+    rhs : tvm.Tensor or Expr
+        The right operand
 
     Returns
     -------
-    ret : tvm.Tensor
+    ret : tvm.Tensor or Expr
+        Returns Expr if both operands are Expr.
+        Otherwise returns Tensor.
     """
-    return broadcast_binary_op(lhs, rhs, tvm.max, "maximum")
+    return _cpp.greater(lhs, rhs)
 
 
-def broadcast_minimum(lhs, rhs):
-    """Take element-wise minimum of two tensors with auto-broadcasting
+def less(lhs, rhs):
+    """Compute (lhs<rhs) with auto-broadcasting
 
     Parameters
     ----------
-    lhs : tvm.Tensor
-    rhs : tvm.Tensor
+    lhs : tvm.Tensor or Expr
+        The left operand
+    rhs : tvm.Tensor or Expr
+        The right operand
 
     Returns
     -------
-    ret : tvm.Tensor
+    ret : tvm.Tensor or Expr
+        Returns Expr if both operands are Expr.
+        Otherwise returns Tensor.
     """
-    return broadcast_binary_op(lhs, rhs, tvm.min, "minimum")
+    return _cpp.less(lhs, rhs)
diff --git a/topi/python/topi/cpp.py b/topi/python/topi/cpp.py
index 4c68dd738cb6..cf7314be4600 100644
--- a/topi/python/topi/cpp.py
+++ b/topi/python/topi/cpp.py
@@ -5,7 +5,6 @@
 from imp import new_module as _new_module
 from tvm._ffi.function import _init_api_prefix
 from tvm._ffi import libinfo
-import tvm as _tvm
 
 def _get_lib_names():
     if sys.platform.startswith('win32'):
@@ -35,7 +34,6 @@ def _create_module(name):
     return mod
 
 # pylint: disable-msg=C0103
-
 nn = _create_module("nn")
 _init_api_prefix("topi.cpp.nn", "topi.nn")
 generic = _create_module("generic")
@@ -52,41 +50,3 @@ def _create_module(name):
 _init_api_prefix("topi.cpp.vision.yolo2", "topi.vision.yolo2")
 image = _create_module("image")
 _init_api_prefix("topi.cpp.image", "topi.image")
-
-class IntVector(object):
-    """Handle to std::vector<int> instance """
-    _tvm_tcode = 27
-
-    def __init__(self, handle):
-        self.handle = handle
-
-    def __del__(self):
-        _tvm.nd.free_extension_handle(self.handle, 27)
-
-    @property
-    def _tvm_handle(self):
-        return self.handle.value
-
-    def __getitem__(self, idx):
-        return ivec_get(self, idx)
-
-_tvm.register_extension(IntVector, IntVector)
-
-class Target(object):
-    """Handle to C++ Target instance """
-    _tvm_tcode = 28
-
-    def __init__(self, handle):
-        self.handle = handle
-
-    def __del__(self):
-        _tvm.nd.free_extension_handle(self.handle, 28)
-
-    @property
-    def _tvm_handle(self):
-        return self.handle.value
-
-    def __getitem__(self, idx):
-        return ivec_get(self, idx)
-
-_tvm.register_extension(Target, Target)
diff --git a/topi/python/topi/generic_op_impl.py b/topi/python/topi/generic_op_impl.py
index 16a79c095783..9123b46cabbe 100644
--- a/topi/python/topi/generic_op_impl.py
+++ b/topi/python/topi/generic_op_impl.py
@@ -3,10 +3,10 @@
 from __future__ import absolute_import as _abs
 import tvm
 from . import broadcast as _broadcast
-from . import tag
+from . import math as _math
 
 
-def _make_bop(elementwise_bop, broadcast_bop, orig_bop):
+def _make_bop(broadcast_bop, orig_bop):
     """Make a specific overloaded binary operator of Tensor when applicable;
     apply the original operator if it is not supposed to be overloaded.
 
@@ -23,9 +23,6 @@ def _make_bop(elementwise_bop, broadcast_bop, orig_bop):
 
     Parameters
     ----------
-    elementwise_bop : operator function
-        Operator for element-wise tensor-scalar operation, for rule (2).
-
     broadcast_bop : operator function
         Operator for broadcast tensor-tensor operation, for rule (1).
 
@@ -66,36 +63,9 @@ def _tensor_bop_impl(lhs, rhs):
               tvm.Expr (otherwise)
             The result of {op} operation.
         """
-
-        def _get_rank(x):
-            """Get the rank of a value.
-            If x is Tensor, then return its rank;
-            if x is scalar_like (i.e., numeric types, Expr, or TensorSlice), return 0;
-            otherwise, return -1.
-            """
-            if isinstance(x, tvm.tensor.Tensor):
-                return len(x.shape)
-            elif isinstance(x, (int, float, tvm.expr.Expr, tvm.tensor.TensorSlice)):
-                return 0
-            return -1
-
-        rl = _get_rank(lhs)
-        rr = _get_rank(rhs)
-        if rl == -1 or rr == -1 or (rl == 0 and rr == 0):
+        if not isinstance(lhs, tvm.tensor.Tensor) and not isinstance(rhs, tvm.tensor.Tensor):
             return orig_bop(lhs, rhs)
-        elif rl > 0 and rr > 0:
-            return broadcast_bop(lhs, rhs)
-        elif rl == 0:
-            f = lambda *i: elementwise_bop(lhs, rhs(*i))
-            with tvm.tag_scope(tag=tag.ELEMWISE):
-                return tvm.compute(rhs.shape, f, "tensor_" + name)
-        elif rr == 0:
-            f = lambda *i: elementwise_bop(lhs(*i), rhs)
-            with tvm.tag_scope(tag=tag.ELEMWISE):
-                return tvm.compute(lhs.shape, f, "tensor_" + name)
-        else:
-            raise AssertionError("Cannot reach this line.")
-
+        return broadcast_bop(lhs, rhs)
     _tensor_bop_impl.__doc__ = _tensor_bop_impl.__doc__.format(op=name)
     return _tensor_bop_impl
 
@@ -106,18 +76,10 @@ def _bind_generic_ops():
     __op_priority__ = 1
     if __op_priority__ > tvm.generic.__op_priority__:
         tvm.generic.__op_priority__ = __op_priority__
-        tvm.generic.add = _make_bop(lambda x, y: x + y,
-                                    _broadcast.broadcast_add,
-                                    tvm.generic.add)
-        tvm.generic.subtract = _make_bop(lambda x, y: x - y,
-                                         _broadcast.broadcast_sub,
-                                         tvm.generic.subtract)
-        tvm.generic.multiply = _make_bop(lambda x, y: x * y,
-                                         _broadcast.broadcast_mul,
-                                         tvm.generic.multiply)
-        tvm.generic.divide = _make_bop(lambda x, y: x / y,
-                                       _broadcast.broadcast_div,
-                                       tvm.generic.divide)
-
+        tvm.generic.add = _make_bop(_broadcast.add, tvm.generic.add)
+        tvm.generic.subtract = _make_bop(_broadcast.subtract, tvm.generic.subtract)
+        tvm.generic.multiply = _make_bop(_broadcast.multiply, tvm.generic.multiply)
+        tvm.generic.divide = _make_bop(_broadcast.divide, tvm.generic.divide)
+        tvm.generic.cast = _math.cast
 
 _bind_generic_ops()
diff --git a/topi/python/topi/math.py b/topi/python/topi/math.py
index 2809c79e518a..62b1a34a2c96 100644
--- a/topi/python/topi/math.py
+++ b/topi/python/topi/math.py
@@ -258,14 +258,14 @@ def _compute(*indices):
     return tvm.compute(x.shape, _compute)
 
 
-@tvm.tag_scope(tag=tag.ELEMWISE)
 def cast(x, dtype):
     """Cast input to specified data type.
 
     Parameters
     ----------
-    x : tvm.Tensor
+    x : tvm.Tensor or Expr
         Input argument.
+
     dtype : str
         Data type.
 
@@ -274,4 +274,7 @@ def cast(x, dtype):
     y : tvm.Tensor
         The result.
     """
-    return tvm.compute(x.shape, lambda *i: x(*i).astype(dtype))
+    if isinstance(x, tvm.tensor.Tensor):
+        return tvm.compute(
+            x.shape, lambda *i: x(*i).astype(dtype), tag=tag.ELEMWISE)
+    return tvm.make.static_cast(dtype, x)
diff --git a/topi/python/topi/tensor.py b/topi/python/topi/tensor.py
index 74adff2cd9c0..9b15cf0d8515 100644
--- a/topi/python/topi/tensor.py
+++ b/topi/python/topi/tensor.py
@@ -68,52 +68,3 @@ def full_like(x, fill_value):
     """
     dtype = x.dtype
     return tvm.compute(x.shape, lambda *i: tvm.const(fill_value, dtype))
-
-
-@tvm.tag_scope(tag=tag.ELEMWISE)
-def greater(lhs, rhs, out_type=tvm.int8):
-    """Compare two input tensors element-wise and return an mask tensor
-       which contains 1 if lhs > rhs holds else 0
-
-    Parameters
-    ----------
-    lhs : tvm.Tensor
-        Left input argument.
-    rhs : tvm.Tensor
-        Right argument.
-    out_type: str
-        Output data type. Default is int8
-
-    Returns
-    -------
-    y : tvm.Tensor
-        The result.
-    """
-    return tvm.compute(lhs.shape,
-                       lambda *i: tvm.select(lhs(*i) > rhs(*i),
-                                             tvm.const(1, out_type),
-                                             tvm.const(0, out_type)))
-
-@tvm.tag_scope(tag=tag.ELEMWISE)
-def less(lhs, rhs, out_type=tvm.int8):
-    """Compare two input tensors element-wise and return an mask tensor
-       which contains 1 if lhs < rhs holds else 0
-
-    Parameters
-    ----------
-    lhs : tvm.Tensor
-        Left input argument.
-    rhs : tvm.Tensor
-        Right argument.
-    out_type: str
-        Output data type. Default is int8
-
-    Returns
-    -------
-    y : tvm.Tensor
-        The result.
-    """
-    return tvm.compute(lhs.shape,
-                       lambda *i: tvm.select(lhs(*i) < rhs(*i),
-                                             tvm.const(1, out_type),
-                                             tvm.const(0, out_type)))
diff --git a/topi/src/topi.cc b/topi/src/topi.cc
index 9f2ecacd11a4..19293048139a 100644
--- a/topi/src/topi.cc
+++ b/topi/src/topi.cc
@@ -67,51 +67,55 @@ Array<Expr> ArrayOrInt(TVMArgValue arg) {
   }
 }
 
+inline bool IsTensorType(TVMArgValue arg) {
+  return (arg.type_code() == kNodeHandle &&
+          arg.node_sptr()->is_type<tvm::TensorNode>());
+}
+
+
 TVM_REGISTER_GLOBAL("topi.TEST_create_target")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
   *rv = tvm::Target::create(args[0]);
   });
 
 /* Ops from broadcast.h */
+#define TOPI_REGISTER_BCAST_OP(OpName, Op)                              \
+  TVM_REGISTER_GLOBAL(OpName)                                           \
+  .set_body([](TVMArgs args, TVMRetValue *rv) {                         \
+      bool lhs_is_tensor = IsTensorType(args[0]);                       \
+      bool rhs_is_tensor = IsTensorType(args[1]);                       \
+      if (lhs_is_tensor && rhs_is_tensor) {                             \
+        *rv = Op(args[0].operator tvm::Tensor(),                        \
+                 args[1].operator tvm::Tensor());                       \
+      } else if (!lhs_is_tensor && rhs_is_tensor) {                     \
+        *rv = Op(args[0].operator tvm::Expr(),                          \
+                 args[1].operator tvm::Tensor());                       \
+      } else if (lhs_is_tensor && !rhs_is_tensor) {                     \
+        *rv = Op(args[0].operator tvm::Tensor(),                        \
+                 args[1].operator tvm::Expr());                         \
+      } else if (!lhs_is_tensor && !rhs_is_tensor) {                    \
+        *rv = Op(args[0].operator tvm::Expr(),                          \
+                 args[1].operator tvm::Expr());                         \
+      }                                                                 \
+    });                                                                 \
+
 TVM_REGISTER_GLOBAL("topi.broadcast_to")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
   *rv = broadcast_to(args[0], args[1]);
   });
 
-TVM_REGISTER_GLOBAL("topi.broadcast_add")
-.set_body([](TVMArgs args, TVMRetValue *rv) {
-  *rv = broadcast_add(args[0], args[1]);
-  });
-
-TVM_REGISTER_GLOBAL("topi.broadcast_sub")
-.set_body([](TVMArgs args, TVMRetValue *rv) {
-  *rv = broadcast_sub(args[0], args[1]);
-  });
-
-TVM_REGISTER_GLOBAL("topi.broadcast_mul")
-.set_body([](TVMArgs args, TVMRetValue *rv) {
-  *rv = broadcast_mul(args[0], args[1]);
-  });
-
-TVM_REGISTER_GLOBAL("topi.broadcast_div")
-.set_body([](TVMArgs args, TVMRetValue *rv) {
-  *rv = broadcast_div(args[0], args[1]);
-  });
-
-TVM_REGISTER_GLOBAL("topi.broadcast_maximum")
-.set_body([](TVMArgs args, TVMRetValue *rv) {
-  *rv = broadcast_maximum(args[0], args[1]);
-  });
-
-TVM_REGISTER_GLOBAL("topi.broadcast_minimum")
-.set_body([](TVMArgs args, TVMRetValue *rv) {
-  *rv = broadcast_minimum(args[0], args[1]);
-   });
-
-TVM_REGISTER_GLOBAL("topi.broadcast_pow")
-.set_body([](TVMArgs args, TVMRetValue *rv) {
-  *rv = broadcast_pow(args[0], args[1]);
-  });
+TOPI_REGISTER_BCAST_OP("topi.add", topi::add);
+TOPI_REGISTER_BCAST_OP("topi.subtract", topi::subtract);
+TOPI_REGISTER_BCAST_OP("topi.multiply", topi::multiply);
+TOPI_REGISTER_BCAST_OP("topi.divide", topi::divide);
+TOPI_REGISTER_BCAST_OP("topi.mod", topi::mod);
+TOPI_REGISTER_BCAST_OP("topi.maximum", topi::maximum);
+TOPI_REGISTER_BCAST_OP("topi.minimum", topi::minimum);
+TOPI_REGISTER_BCAST_OP("topi.power", topi::power);
+TOPI_REGISTER_BCAST_OP("topi.left_shift", topi::left_shift);
+TOPI_REGISTER_BCAST_OP("topi.right_shift", topi::right_shift);
+TOPI_REGISTER_BCAST_OP("topi.greater", topi::greater);
+TOPI_REGISTER_BCAST_OP("topi.less", topi::less);
 
 /* Ops from elemwise.h */
 TVM_REGISTER_GLOBAL("topi.exp")
@@ -149,25 +153,6 @@ TVM_REGISTER_GLOBAL("topi.negative")
   *rv = negative(args[0]);
   });
 
-TVM_REGISTER_GLOBAL("topi.pow")
-.set_body([](TVMArgs args, TVMRetValue *rv) {
-  *rv = pow(args[0], args[1]);
-  });
-
-TVM_REGISTER_GLOBAL("topi.left_shift")
-.set_body([](TVMArgs args, TVMRetValue *rv) {
-  Tensor lhs = args[0];
-  Expr rhs = args[1];
-  *rv = lhs >> rhs;
-  });
-
-TVM_REGISTER_GLOBAL("topi.right_shift")
-.set_body([](TVMArgs args, TVMRetValue *rv) {
-  Tensor lhs = args[0];
-  Expr rhs = args[1];
-  *rv = lhs << rhs;
-  });
-
 TVM_REGISTER_GLOBAL("topi.clip")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
   *rv = clip(args[0], args[1], args[2]);
diff --git a/topi/tests/python/test_topi_broadcast.py b/topi/tests/python/test_topi_broadcast.py
index 13a859063f50..05a32ca0eac5 100644
--- a/topi/tests/python/test_topi_broadcast.py
+++ b/topi/tests/python/test_topi_broadcast.py
@@ -4,10 +4,10 @@
 import tvm
 import topi
 
-def verify_broadcast_to_ele(in_shape, out_shape):
+def verify_broadcast_to_ele(in_shape, out_shape, fbcast):
     # Build the logic and compile the function
     A = tvm.placeholder(shape=in_shape, name="A")
-    B = topi.broadcast_to(A, out_shape)
+    B = fbcast(A, out_shape)
     def check_device(device):
         ctx = tvm.context(device, 0)
         if not ctx.exist:
@@ -32,24 +32,20 @@ def check_device(device):
     check_device("rocm")
 
 
-def verify_broadcast_binary_ele(lhs_shape, rhs_shape, typ="add"):
+def verify_broadcast_binary_ele(lhs_shape, rhs_shape,
+                                ftopi, fnumpy,
+                                lhs_min=-100, lhs_max=100,
+                                rhs_min=-100, rhs_max=100,
+                                dtype="float32"):
     # Build the logic and compile the function
-    A = tvm.placeholder(shape=lhs_shape, name="A")
-    B = tvm.placeholder(shape=rhs_shape, name="B")
-    if typ == "add":
-        C = topi.broadcast_add(A, B)
-    elif typ == "sub":
-        C = topi.broadcast_sub(A, B)
-    elif typ == "div":
-        C = topi.broadcast_div(A, B)
-    elif typ == "mul":
-        C = topi.broadcast_mul(A, B)
-    elif typ == "maximum":
-        C = topi.broadcast_maximum(A, B)
-    elif typ == "minimum":
-        C = topi.broadcast_minimum(A, B)
-    else:
-        raise NotImplementedError
+    A = (tvm.var("A", dtype=dtype) if lhs_shape is None
+         else tvm.placeholder(shape=lhs_shape, name="A", dtype=dtype))
+    B = (tvm.var("B", dtype=dtype) if rhs_shape is None
+         else tvm.placeholder(shape=rhs_shape, name="B", dtype=dtype))
+    C = ftopi(A, B)
+    if (isinstance(A, tvm.expr.Expr) and isinstance(B, tvm.expr.Expr)):
+        assert(isinstance(C, tvm.expr.Expr))
+        return
     def check_device(device):
         ctx = tvm.context(device, 0)
         if not ctx.exist:
@@ -58,54 +54,102 @@ def check_device(device):
         print("Running on target: %s" % device)
         with tvm.target.create(device):
             s = topi.generic.schedule_broadcast(C)
-        foo = tvm.build(s, [A, B, C], device, name="broadcast_binary" + "_" + typ)
-        lhs_npy = np.random.uniform(size=lhs_shape).astype(A.dtype)
-        rhs_npy = np.random.uniform(size=rhs_shape).astype(A.dtype)
-        if typ == "add":
-            out_npy = lhs_npy + rhs_npy
-        elif typ == "sub":
-            out_npy = lhs_npy - rhs_npy
-        elif typ == "div":
-            rhs_npy = np.abs(rhs_npy) + 0.001
-            out_npy = lhs_npy / rhs_npy
-        elif typ == "mul":
-            out_npy = lhs_npy * rhs_npy
-        elif typ == "maximum":
-            out_npy = np.maximum(lhs_npy, rhs_npy)
-        elif typ == "minimum":
-            out_npy = np.minimum(lhs_npy, rhs_npy)
+        foo = tvm.build(s, [A, B, C], device, name="broadcast_binary" + "_" + ftopi.__name__)
+        if lhs_shape is None:
+            lhs_npy = float(np.random.uniform(low=lhs_min, high=lhs_max))
+            if dtype.startswith('int'):
+                lhs_npy = int(lhs_npy)
+            lhs_nd = lhs_npy
         else:
-            raise NotImplementedError
-        lhs_nd = tvm.nd.array(lhs_npy, ctx)
-        rhs_nd = tvm.nd.array(rhs_npy, ctx)
-        out_nd = tvm.nd.array(np.empty(out_npy.shape).astype(B.dtype), ctx)
+            lhs_npy = np.random.uniform(low=lhs_min, high=lhs_max,
+                                        size=lhs_shape).astype(A.dtype)
+            lhs_nd = tvm.nd.array(lhs_npy, ctx)
+
+        if rhs_shape is None:
+            rhs_npy = float(np.random.uniform(low=rhs_min, high=rhs_max))
+            if dtype.startswith('int'):
+                lhs_npy = int(lhs_npy)
+            rhs_nd = rhs_npy
+        else:
+            rhs_npy = np.random.uniform(low=rhs_min, high=rhs_max,
+                                        size=rhs_shape).astype(A.dtype)
+            rhs_nd = tvm.nd.array(rhs_npy, ctx)
+
+        out_npy = fnumpy(lhs_npy, rhs_npy)
+        out_nd = tvm.nd.array(np.empty(out_npy.shape).astype(C.dtype), ctx)
         for _ in range(1):
             foo(lhs_nd, rhs_nd, out_nd)
         np.testing.assert_allclose(out_nd.asnumpy(), out_npy, rtol=1E-4, atol=1E-4)
 
-    check_device("vulkan")
     check_device("opencl")
+    check_device("vulkan")
     check_device("cuda")
     check_device("metal")
     check_device("rocm")
 
 def test_broadcast_to():
-    verify_broadcast_to_ele((1,), (10,))
-    verify_broadcast_to_ele((), (10,))
-    verify_broadcast_to_ele((1, 1, 5, 4), (3, 4, 4, 4, 5, 4))
-    verify_broadcast_to_ele((1, 128, 1, 32), (64, 128, 64, 32))
+    verify_broadcast_to_ele((1,), (10,), topi.broadcast_to)
+    verify_broadcast_to_ele((), (10,), topi.broadcast_to)
+    verify_broadcast_to_ele((1, 1, 5, 4), (3, 4, 4, 4, 5, 4), topi.broadcast_to)
+    verify_broadcast_to_ele((1, 128, 1, 32), (64, 128, 64, 32), topi.broadcast_to)
+
+def test_add():
+    verify_broadcast_binary_ele(
+        (5, 2, 3), (2, 1), topi.add, np.add)
+
+def test_subtract():
+    verify_broadcast_binary_ele(
+        (5, 2, 3), (), topi.subtract, np.subtract)
+    verify_broadcast_binary_ele(
+        (5, 2, 3), None, topi.subtract, np.subtract)
+    verify_broadcast_binary_ele(
+        None, None, topi.subtract, np.subtract)
+    verify_broadcast_binary_ele(
+        (1, 32), (64, 32), topi.subtract, np.subtract)
+
+def test_multiply():
+    verify_broadcast_binary_ele(
+        (5, 64, 128), (2, 5, 64, 1), topi.multiply, np.multiply)
+
+def test_divide():
+    verify_broadcast_binary_ele(
+        None, (10,), topi.divide, np.divide, rhs_min=0.0001)
+    verify_broadcast_binary_ele(
+        (2, 3, 1, 32), (64, 32), topi.divide, np.divide, rhs_min=0.0001)
+
+def test_maximum_minmum():
+    verify_broadcast_binary_ele(
+        (32,), (64, 32), topi.maximum, np.maximum)
+    verify_broadcast_binary_ele(
+        (1, 2, 2, 1, 32), (64, 32), topi.minimum, np.minimum)
+
+def test_power():
+    verify_broadcast_binary_ele(
+        (1, 2, 2), (2,), topi.power, np.power, lhs_min=0.001, rhs_min=0.001, rhs_max=2)
 
+def test_mod():
+    verify_broadcast_binary_ele(
+        (1, 2, 2), (2,), topi.mod, np.mod, lhs_min=0.001, rhs_min=1, dtype="int32")
 
-def test_broadcast_binary():
-    verify_broadcast_binary_ele((5, 2, 3), (2, 1), typ="add")
-    verify_broadcast_binary_ele((5, 2, 3), (), typ="add")
-    verify_broadcast_binary_ele((5, 64, 128), (2, 5, 64, 1), typ="mul")
-    verify_broadcast_binary_ele((2, 3, 1, 32), (64, 32), typ="div")
-    verify_broadcast_binary_ele((1, 32), (64, 32), typ="sub")
-    verify_broadcast_binary_ele((32,), (64, 32), typ="maximum")
-    verify_broadcast_binary_ele((1, 2, 2, 1, 32), (64, 32), typ="minimum")
+def test_cmp():
+    # explicit specify the output type
+    def greater(x, y):
+        return topi.greater(x, y).astype("int8")
+    def less(x, y):
+        return topi.less(x, y).astype("int8")
+    verify_broadcast_binary_ele(
+        (1, 2, 2), (2,), greater, np.greater)
+    verify_broadcast_binary_ele(
+        (2, 1, 2), (2, 3, 1), less, np.less)
 
 
 if __name__ == "__main__":
-    test_broadcast_binary()
+    test_cmp()
+    test_mod()
+    test_add()
+    test_subtract()
+    test_multiply()
+    test_divide()
+    test_maximum_minmum()
+    test_power()
     test_broadcast_to()
diff --git a/topi/tests/python/test_topi_tensor.py b/topi/tests/python/test_topi_tensor.py
index 1489ad073f1e..3ac30db78aa2 100644
--- a/topi/tests/python/test_topi_tensor.py
+++ b/topi/tests/python/test_topi_tensor.py
@@ -69,44 +69,6 @@ def check_device(device):
         check_device(device)
 
 
-def verify_comparator(shape, dtype, out_type='int8'):
-    A = tvm.placeholder(shape, dtype, name="A")
-    B = tvm.placeholder(shape, dtype, name="B")
-    C = topi.less(A, B)
-    s_less = tvm.create_schedule([C.op])
-
-    D = tvm.placeholder(shape, dtype, name="D")
-    E = tvm.placeholder(shape, dtype, name="E")
-    F = topi.greater(D, E, out_type)
-    s_greater = tvm.create_schedule([F.op])
-
-    @memoize("topi.tests.test_topi_indicator")
-    def get_ref_data():
-        return [np.random.uniform(0, 10, size=shape).astype(dtype),
-                np.random.uniform(0, 10, size=shape).astype(dtype)]
-    [np_l, np_r] = get_ref_data()
-
-    def check_device(device):
-        if not tvm.module.enabled(device):
-            print("Skip because %s is not enabled" % device)
-            return
-
-        ctx = tvm.context(device, 0)
-        out = tvm.nd.array(np.zeros(shape, dtype=out_type), ctx)
-        tvm_l = tvm.nd.array(np_l, ctx)
-        tvm_r = tvm.nd.array(np_r, ctx)
-
-        f = tvm.build(s_less, [A, B, C], device, name="less")
-        f(tvm_l, tvm_r, out)
-        np.testing.assert_allclose(out.asnumpy(), np.less(np_l, np_r).astype(out_type), rtol=1e-5)
-
-        f = tvm.build(s_greater, [D, E, F], device, name="greater")
-        f(tvm_l, tvm_r, out)
-        np.testing.assert_allclose(out.asnumpy(), np.greater(np_l, np_r).astype(out_type), rtol=1e-5)
-
-    for device in ["llvm"]:
-        check_device(device)
-
 def test_elemwise_sum():
     verify_elemwise_sum(1, "float32")
     verify_elemwise_sum(5, "float32")
@@ -118,12 +80,6 @@ def test_full():
     verify_full((10,), "int32", 7)
 
 
-def test_comparator():
-    verify_comparator((3,4,5), "float32")
-    verify_comparator((7,), "int32")
-    verify_comparator((3,4,5), "float32", "int8")
-
 if __name__ == "__main__":
     test_elemwise_sum()
     test_full()
-    test_comparator()
diff --git a/topi/tests/python_cpp/test_topi_broadcast.py b/topi/tests/python_cpp/test_topi_broadcast.py
deleted file mode 100644
index 301751c25517..000000000000
--- a/topi/tests/python_cpp/test_topi_broadcast.py
+++ /dev/null
@@ -1,114 +0,0 @@
-"""Test code for broadcasting operators."""
-import os
-import numpy as np
-import tvm
-import topi
-
-def verify_broadcast_to_ele(in_shape, out_shape):
-    # Build the logic and compile the function
-    A = tvm.placeholder(shape=in_shape, name="A")
-    B = topi.cpp.broadcast_to(A, out_shape)
-    def check_device(device):
-        if not tvm.module.enabled(device):
-            print("Skip because %s is not enabled" % device)
-            return
-        print("Running on target: %s" % device)
-        target = topi.cpp.TEST_create_target(device)
-        s = topi.cpp.cuda.schedule_injective(target, [B])
-        ctx = tvm.context(device, 0)
-        foo = tvm.build(s, [A, B], device, name="broadcast_to")
-        data_npy = np.random.uniform(size=in_shape).astype(A.dtype)
-        out_npy = np.broadcast_to(data_npy, out_shape)
-        data_nd = tvm.nd.array(data_npy, ctx)
-        out_nd = tvm.nd.array(np.empty(out_shape).astype(B.dtype), ctx)
-        for _ in range(1):
-            foo(data_nd, out_nd)
-        np.testing.assert_allclose(out_nd.asnumpy(), out_npy)
-
-    check_device("opencl")
-    check_device("cuda")
-    #check_device("metal")
-    #check_device("rocm")
-
-
-def verify_broadcast_binary_ele(lhs_shape, rhs_shape, typ="add"):
-    # Build the logic and compile the function
-    A = tvm.placeholder(shape=lhs_shape, name="A")
-    B = tvm.placeholder(shape=rhs_shape, name="B")
-    if typ == "add":
-        C = topi.cpp.broadcast_add(A, B)
-    elif typ == "sub":
-        C = topi.cpp.broadcast_sub(A, B)
-    elif typ == "div":
-        C = topi.cpp.broadcast_div(A, B)
-    elif typ == "mul":
-        C = topi.cpp.broadcast_mul(A, B)
-    elif typ == "maximum":
-        C = topi.cpp.broadcast_maximum(A, B)
-    elif typ == "minimum":
-        C = topi.cpp.broadcast_minimum(A, B)
-    elif typ == "pow":
-        C = topi.cpp.broadcast_pow(A, B)
-    else:
-        raise NotImplementedError
-    def check_device(device):
-        if not tvm.module.enabled(device):
-            print("Skip because %s is not enabled" % device)
-            return
-        print("Running on target: %s" % device)
-        target = topi.cpp.TEST_create_target(device)
-        s = topi.cpp.cuda.schedule_injective(target, [C])
-        ctx = tvm.context(device, 0)
-        foo = tvm.build(s, [A, B, C], device, name="broadcast_binary" + "_" + typ)
-        lhs_npy = np.random.uniform(size=lhs_shape).astype(A.dtype)
-        rhs_npy = np.random.uniform(size=rhs_shape).astype(A.dtype)
-        if typ == "add":
-            out_npy = lhs_npy + rhs_npy
-        elif typ == "sub":
-            out_npy = lhs_npy - rhs_npy
-        elif typ == "div":
-            rhs_npy = np.abs(rhs_npy) + 0.001
-            out_npy = lhs_npy / rhs_npy
-        elif typ == "mul":
-            out_npy = lhs_npy * rhs_npy
-        elif typ == "maximum":
-            out_npy = np.maximum(lhs_npy, rhs_npy)
-        elif typ == "minimum":
-            out_npy = np.minimum(lhs_npy, rhs_npy)
-        elif typ == "pow":
-            out_npy = lhs_npy ** rhs_npy
-        else:
-            raise NotImplementedError
-        lhs_nd = tvm.nd.array(lhs_npy, ctx)
-        rhs_nd = tvm.nd.array(rhs_npy, ctx)
-        out_nd = tvm.nd.array(np.empty(out_npy.shape).astype(B.dtype), ctx)
-        for _ in range(1):
-            foo(lhs_nd, rhs_nd, out_nd)
-        np.testing.assert_allclose(out_nd.asnumpy(), out_npy, rtol=1E-4, atol=1E-4)
-
-    check_device("opencl")
-    check_device("cuda")
-    #check_device("metal")
-    #check_device("rocm")
-
-def test_broadcast_to():
-    verify_broadcast_to_ele((1,), (10,))
-    verify_broadcast_to_ele((), (10,))
-    verify_broadcast_to_ele((1, 1, 5, 4), (3, 4, 4, 4, 5, 4))
-    verify_broadcast_to_ele((1, 128, 1, 32), (64, 128, 64, 32))
-
-
-def test_broadcast_binary():
-    verify_broadcast_binary_ele((5, 2, 3), (2, 1), typ="add")
-    verify_broadcast_binary_ele((5, 2, 3), (), typ="add")
-    verify_broadcast_binary_ele((5, 64, 128), (2, 5, 64, 1), typ="mul")
-    verify_broadcast_binary_ele((2, 3, 1, 32), (64, 32), typ="div")
-    verify_broadcast_binary_ele((1, 32), (64, 32), typ="sub")
-    verify_broadcast_binary_ele((32,), (64, 32), typ="maximum")
-    verify_broadcast_binary_ele((1, 2, 2, 1, 32), (64, 32), typ="minimum")
-    verify_broadcast_binary_ele((1, 32), (64, 32), typ="pow")
-
-
-if __name__ == "__main__":
-    test_broadcast_to()
-    test_broadcast_binary()
diff --git a/topi/tests/python_cpp/test_topi_transform.py b/topi/tests/python_cpp/test_topi_transform.py
index 6579c7791de0..6e0d13c0d629 100644
--- a/topi/tests/python_cpp/test_topi_transform.py
+++ b/topi/tests/python_cpp/test_topi_transform.py
@@ -243,7 +243,7 @@ def verify_concatenate_broadcast(shapes, axis, rhs_shape):
     for i, shape in enumerate(shapes):
         tensor_l.append(tvm.placeholder(shape, name="A" + str(i)))
     out_tensor = topi.cpp.concatenate(tensor_l, axis)
-    C = topi.cpp.broadcast_add(out_tensor, B)
+    C = out_tensor + B
     def check_device(device):
         ctx = tvm.context(device, 0)
         if not ctx.exist:

From 18b870cc24329e3b7154d833a9f54e4be3b6ee28 Mon Sep 17 00:00:00 2001
From: Jian Weng <werefluke@gmail.com>
Date: Sat, 23 Jun 2018 08:57:09 -0700
Subject: [PATCH 747/949] Import hybrid module in __init__.py (#1323)

---
 python/tvm/__init__.py      | 1 +
 python/tvm/hybrid/parser.py | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/python/tvm/__init__.py b/python/tvm/__init__.py
index e4068cca3b9f..fe8a282169a7 100644
--- a/python/tvm/__init__.py
+++ b/python/tvm/__init__.py
@@ -16,6 +16,7 @@
 from . import ir_builder
 from . import target
 from . import generic
+from . import hybrid
 
 from . import ndarray as nd
 from .ndarray import context, cpu, gpu, opencl, cl, vulkan, metal, mtl
diff --git a/python/tvm/hybrid/parser.py b/python/tvm/hybrid/parser.py
index 7d4c40e8c7e9..b3f076094a12 100644
--- a/python/tvm/hybrid/parser.py
+++ b/python/tvm/hybrid/parser.py
@@ -35,7 +35,7 @@ class HybridParser(ast.NodeVisitor):
         ast.Add   : operator.add,
         ast.Sub   : operator.sub,
         ast.Mult  : operator.mul,
-        ast.Div   : _make.Div,
+        ast.Div   : operator.div if sys.version_info[0] == 2 else operator.truediv,
         ast.Mod   : operator.mod,
         ast.BitOr : operator.or_,
         ast.BitAnd: operator.and_,

From 4538c03d79faa58c9f5204e9f2e2d5d2aff7920f Mon Sep 17 00:00:00 2001
From: Tatsuya Nishiyama <nishiyama.tatsuya0@gmail.com>
Date: Sun, 24 Jun 2018 00:57:27 +0900
Subject: [PATCH 748/949] [FRONTEND][MXNET] Add expand_dims supoort (#1317)

* [FRONTEND][MXNET] Add expand_dims supoort

* fix lint
---
 nnvm/python/nnvm/frontend/mxnet.py               | 9 ++++++++-
 nnvm/tests/python/frontend/mxnet/test_forward.py | 6 ++++++
 2 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/nnvm/python/nnvm/frontend/mxnet.py b/nnvm/python/nnvm/frontend/mxnet.py
index 2f190ab71f4d..1fc673115c68 100644
--- a/nnvm/python/nnvm/frontend/mxnet.py
+++ b/nnvm/python/nnvm/frontend/mxnet.py
@@ -241,6 +241,12 @@ def _elemwise_sum(inputs, _):
     return _get_nnvm_op('elemwise_sum')(*inputs, **new_attrs)
 
 
+def _expand_dims(inputs, attrs):
+    op_name, new_attrs = "expand_dims", {}
+    new_attrs['axis'] = _required_attr(attrs, 'axis')
+    return _get_nnvm_op(op_name)(*inputs, **new_attrs)
+
+
 _identity_list = ['__add_scalar__', '__add_symbol__', '__div_scalar__',
                   '__div_symbol__', '__mul_scalar__', '__mul_symbol__',
                   '__pow_scalar__', '__rdiv_scalar__', '__rpow_scalar__',
@@ -288,7 +294,8 @@ def _elemwise_sum(inputs, _):
     'reshape'       : _reshape,
     'sum_axis'      : _rename('sum'),
     'UpSampling'    : _upsampling,
-    'clip'          : _clip
+    'clip'          : _clip,
+    'expand_dims'   : _expand_dims
 }
 
 def _convert_symbol(op_name, inputs, attrs,
diff --git a/nnvm/tests/python/frontend/mxnet/test_forward.py b/nnvm/tests/python/frontend/mxnet/test_forward.py
index e6b6dffa17b0..cfb4e553434f 100644
--- a/nnvm/tests/python/frontend/mxnet/test_forward.py
+++ b/nnvm/tests/python/frontend/mxnet/test_forward.py
@@ -136,6 +136,11 @@ def test_forward_split_squeeze():
     mx_sym = mx.sym.split(data, axis=1, num_outputs=4, squeeze_axis=True)
     verify_mxnet_frontend_impl(mx_sym, (1, 4, 2, 1), (1, 2, 1))
 
+def test_forward_expand_dims():
+    data = mx.sym.var('data')
+    mx_sym = mx.sym.expand_dims(data, axis=1)
+    verify_mxnet_frontend_impl(mx_sym, (2, 3, 4), (2, 1, 3, 4))
+
 if __name__ == '__main__':
     test_forward_mlp()
     test_forward_vgg()
@@ -148,3 +153,4 @@ def test_forward_split_squeeze():
     test_forward_clip()
     test_forward_split()
     test_forward_split_squeeze()
+    test_forward_expand_dims()

From e66996a8ba8b4458acfea4739aa772051cfd6026 Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Sun, 24 Jun 2018 04:21:54 +0800
Subject: [PATCH 749/949] [PASS] Add GPU IR verifier (#1296)

---
 include/tvm/ir_pass.h                         |  24 +++
 include/tvm/runtime/device_api.h              |   3 +-
 python/tvm/_ffi/runtime_ctypes.py             |  13 ++
 src/api/api_pass.cc                           |   1 +
 src/pass/verify_gpu_code.cc                   | 166 +++++++++++++++++
 src/runtime/cuda/cuda_device_api.cc           |  18 +-
 src/runtime/metal/metal_device_api.mm         |   1 +
 src/runtime/opencl/opencl_device_api.cc       |  15 +-
 src/runtime/opengl/opengl_device_api.cc       |   1 +
 src/runtime/rocm/rocm_device_api.cc           |   1 +
 src/runtime/vulkan/vulkan_device_api.cc       |   1 +
 .../unittest/test_pass_verify_gpu_code.py     | 169 ++++++++++++++++++
 12 files changed, 410 insertions(+), 3 deletions(-)
 create mode 100644 src/pass/verify_gpu_code.cc
 create mode 100644 tests/python/unittest/test_pass_verify_gpu_code.py

diff --git a/include/tvm/ir_pass.h b/include/tvm/ir_pass.h
index 4be1643429ea..f6c6334c88b6 100644
--- a/include/tvm/ir_pass.h
+++ b/include/tvm/ir_pass.h
@@ -477,6 +477,30 @@ LoweredFunc LowerIntrin(LoweredFunc f, const std::string& target);
  */
 bool VerifyMemory(LoweredFunc func, int device_type);
 
+
+/*!
+ * \brief Verify the correctness of a GPU code
+ *        It will check the whether the amount of memory usage or the number of threads
+ *        in a block exceeds the limit
+ * \param stmt The statement to be checked
+ * \param constraints The dict to specify constraints to check.
+ *        Possible keys are
+ *
+ *        "max_local_memory_per_block": Total amount of local memory per block (in bytes).
+ *        "max_shared_memory_per_block": Total amount of shared memory per block (in bytes).
+ *        "max_thread_per_block": Maximum number of threads per block.
+ *        "max_thread_x": Maximum length of threadIdx.x.
+ *        "max_thread_y": Maximum length of threadIdx.y.
+ *        "max_thread_z": Maximum length of threadIdx.z.
+ *
+ *        If one key is missing in this argument, the pass won't check for that item.
+ * \return valid Whether it is a valid GPU code
+ *
+ */
+bool VerifyGPUCode(Stmt stmt,
+                   Map<std::string, Expr> constraints);
+
+
 }  // namespace ir
 }  // namespace tvm
 
diff --git a/include/tvm/runtime/device_api.h b/include/tvm/runtime/device_api.h
index 82128fc58d47..3458c143e662 100644
--- a/include/tvm/runtime/device_api.h
+++ b/include/tvm/runtime/device_api.h
@@ -23,7 +23,8 @@ enum DeviceAttrKind : int {
   kComputeVersion = 4,
   kDeviceName = 5,
   kMaxClockRate = 6,
-  kMultiProcessorCount = 7
+  kMultiProcessorCount = 7,
+  kMaxThreadDimensions = 8
 };
 
 /*! \brief Number of bytes each allocation must align to */
diff --git a/python/tvm/_ffi/runtime_ctypes.py b/python/tvm/_ffi/runtime_ctypes.py
index 3fc020c8781b..992afa4990c3 100644
--- a/python/tvm/_ffi/runtime_ctypes.py
+++ b/python/tvm/_ffi/runtime_ctypes.py
@@ -3,6 +3,7 @@
 from __future__ import absolute_import
 
 import ctypes
+import json
 import numpy as np
 from .base import _LIB, check_call
 from .. import _api_internal
@@ -178,6 +179,18 @@ def multi_processor_count(self):
         return _api_internal._GetDeviceAttr(
             self.device_type, self.device_id, 7)
 
+    @property
+    def max_thread_dimensions(self):
+        """Return the maximum size of each thread axis
+
+        Returns
+        -------
+        dims: List of int
+            The maximum length of threadIdx.x, threadIdx.y, threadIdx.z
+        """
+        return json.loads(_api_internal._GetDeviceAttr(
+            self.device_type, self.device_id, 8))
+
     def sync(self):
         """Synchronize until jobs finished at the context."""
         check_call(_LIB.TVMSynchronize(self.device_type, self.device_id, None))
diff --git a/src/api/api_pass.cc b/src/api/api_pass.cc
index 64c922559229..adfe198ebf54 100644
--- a/src/api/api_pass.cc
+++ b/src/api/api_pass.cc
@@ -131,5 +131,6 @@ REGISTER_PASS2(LowerIntrin);
 REGISTER_PASS1(LowerTVMBuiltin);
 REGISTER_PASS1(CombineContextCall);
 REGISTER_PASS2(VerifyMemory);
+REGISTER_PASS2(VerifyGPUCode);
 }  // namespace ir
 }  // namespace tvm
diff --git a/src/pass/verify_gpu_code.cc b/src/pass/verify_gpu_code.cc
new file mode 100644
index 000000000000..aa1762221d8d
--- /dev/null
+++ b/src/pass/verify_gpu_code.cc
@@ -0,0 +1,166 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file verify_gpu_code.cc
+ * \brief Verify the correctness of a GPU IR.
+ *        It will check the whether the amount of memory usage or the number of threads
+ *        in a block exceeds the limit
+ */
+
+#include <tvm/api_registry.h>
+#include <tvm/ir.h>
+#include <tvm/ir_visitor.h>
+
+namespace tvm {
+namespace ir {
+
+class GPUCodeVerifier : public IRVisitor {
+ public:
+  bool Verify(tvm::Stmt stmt,
+              int64_t max_local_memory_per_block,
+              int64_t max_shared_memory_per_block,
+              int64_t max_thread_per_block,
+              int64_t max_thread_x,
+              int64_t max_thread_y,
+              int64_t max_thread_z) {
+    max_local_memory_per_block_ = static_cast<size_t>(max_local_memory_per_block);
+    max_shared_memory_per_block_ = static_cast<size_t>(max_shared_memory_per_block);
+    max_thread_per_block_ = static_cast<size_t>(max_thread_per_block);
+    max_thread_x_ = static_cast<size_t>(max_thread_x);
+    max_thread_y_ = static_cast<size_t>(max_thread_y);
+    max_thread_z_ = static_cast<size_t>(max_thread_z);
+
+    Reset_();
+
+    this->Visit(stmt);
+
+    return valid_;
+  }
+
+  void Visit_(const ProducerConsumer *op) {
+    if (nest_level_ == 0) {
+      // enter a new kernel, reset statistics
+      Reset_();
+    }
+
+    if (op->is_producer) {
+      nest_level_++;
+      IRVisitor::Visit_(op);
+      nest_level_--;
+    } else {
+      IRVisitor::Visit_(op);
+    }
+
+    if (nest_level_ == 0) {
+      // exit a kernel, check the validity
+      valid_ &= thread_per_block_ <= max_thread_per_block_;
+
+      valid_ &= local_memory_per_block_ <= max_local_memory_per_block_;
+      valid_ &= shared_memory_per_block_ <= max_shared_memory_per_block_;
+    }
+  }
+
+  void Visit_(const Allocate *op) {
+    IRVisitor::Visit_(op);
+    // visit an allocation of a buffer in shared memory, record its size
+    if (visited_local_buffers_.count(op->buffer_var.get()) != 0) {
+      size_t size = static_cast<size_t>(op->constant_allocation_size());
+      local_memory_per_block_ += size * op->type.bytes();
+    } else if (visited_shared_buffers_.count(op->buffer_var.get()) != 0) {
+      size_t size = static_cast<size_t>(op->constant_allocation_size());
+      shared_memory_per_block_ += size * op->type.bytes();
+    }
+  }
+
+  void Visit_(const AttrStmt *op) {
+    if (op->attr_key == attr::storage_scope) {
+      if (op->value.as<StringImm>()->value == "local") {
+        visited_local_buffers_.insert(op->node.as<tvm::Variable>());
+      } else if (op->value.as<StringImm>()->value == "shared") {
+        visited_shared_buffers_.insert(op->node.as<tvm::Variable>());
+      }
+    } else if (op->attr_key == attr::thread_extent) {
+      VarExpr var = op->node.as<tvm::IterVarNode>()->var;
+      const auto *extent = op->value.as<IntImm>();
+      CHECK(extent);
+
+      // record the number of threads in a block
+      std::string name = var.get()->name_hint;
+      if (name == "threadIdx.x" || name == "threadIdx.y" || name == "threadIdx.z") {
+        if (!visited_threads_.count(name)) {
+          visited_threads_.insert(name);
+          size_t length = static_cast<size_t>(extent->value);
+          thread_per_block_ *= length;
+
+          if (name == "threadIdx.x") {
+            valid_ &= length <= max_thread_x_;
+          } else if (name == "threadIdx.y") {
+            valid_ &= length <= max_thread_y_;
+          } else if (name == "threadIdx.z") {
+            valid_ &= length <= max_thread_z_;
+          }
+        }
+      }
+    }
+    IRVisitor::Visit_(op);
+  }
+
+ private:
+  int nest_level_{0};
+
+  std::unordered_set<const tvm::Variable *> visited_local_buffers_;
+  std::unordered_set<const tvm::Variable *> visited_shared_buffers_;
+  std::unordered_set<std::string> visited_threads_;
+
+  size_t local_memory_per_block_;
+  size_t shared_memory_per_block_;
+  size_t thread_per_block_;
+
+  size_t max_local_memory_per_block_;
+  size_t max_shared_memory_per_block_;
+  size_t max_thread_per_block_;
+  size_t max_thread_x_, max_thread_y_, max_thread_z_;
+
+  bool valid_{true};
+
+  void Reset_() {
+    visited_local_buffers_.clear();
+    visited_shared_buffers_.clear();
+    local_memory_per_block_ = 0;
+    shared_memory_per_block_ = 0;
+
+    visited_threads_.clear();
+    thread_per_block_ = 1;
+  }
+};
+
+bool VerifyGPUCode(Stmt stmt,
+                   Map<std::string, Expr> constraints) {
+  GPUCodeVerifier verifier;
+
+  auto get_int = [&constraints](std::string key, int64_t def) {
+    auto iter = constraints.find(key);
+    if (iter != constraints.end()) {
+      return ((*iter).second).as<IntImm>()->value;
+    } else {
+      return def;
+    }
+  };
+
+  int64_t max_local_memory_per_block = get_int("max_local_memory_per_block", INT64_MAX);
+  int64_t max_shared_memory_per_block = get_int("max_shared_memory_per_block", INT64_MAX);
+  int64_t max_thread_per_block = get_int("max_thread_per_block", INT64_MAX);
+  int64_t max_thread_x = get_int("max_thread_x", INT64_MAX);
+  int64_t max_thread_y = get_int("max_thread_y", INT64_MAX);
+  int64_t max_thread_z = get_int("max_thread_z", INT64_MAX);
+
+  return verifier.Verify(stmt,
+                         max_local_memory_per_block,
+                         max_shared_memory_per_block,
+                         max_thread_per_block,
+                         max_thread_x,
+                         max_thread_y,
+                         max_thread_z);
+}
+
+}  // namespace ir
+}  // namespace tvm
diff --git a/src/runtime/cuda/cuda_device_api.cc b/src/runtime/cuda/cuda_device_api.cc
index 9f1ea15aeeae..4573f97ddd8a 100644
--- a/src/runtime/cuda/cuda_device_api.cc
+++ b/src/runtime/cuda/cuda_device_api.cc
@@ -5,10 +5,12 @@
  */
 #include <tvm/runtime/device_api.h>
 
-#include <dmlc/logging.h>
 #include <dmlc/thread_local.h>
 #include <tvm/runtime/registry.h>
 #include <cuda_runtime.h>
+#include <tvm/container.h>
+#include <tvm/ir.h>
+#include <tvm/packed_func_ext.h>
 #include "./cuda_common.h"
 
 namespace tvm {
@@ -70,6 +72,20 @@ class CUDADeviceAPI final : public DeviceAPI {
             &value, cudaDevAttrMultiProcessorCount, ctx.device_id));
         break;
       }
+      case kMaxThreadDimensions: {
+        int dims[3];
+        CUDA_CALL(cudaDeviceGetAttribute(
+            &dims[0], cudaDevAttrMaxBlockDimX, ctx.device_id));
+        CUDA_CALL(cudaDeviceGetAttribute(
+            &dims[1], cudaDevAttrMaxBlockDimY, ctx.device_id));
+        CUDA_CALL(cudaDeviceGetAttribute(
+            &dims[2], cudaDevAttrMaxBlockDimZ, ctx.device_id));
+
+        std::stringstream ss;  // use json string to return multiple int values;
+        ss << "[" << dims[0] <<", " << dims[1] << ", " << dims[2] << "]";
+        *rv = ss.str();
+        return;
+      }
     }
     *rv = value;
   }
diff --git a/src/runtime/metal/metal_device_api.mm b/src/runtime/metal/metal_device_api.mm
index fae1f0ded88f..47c2899cea71 100644
--- a/src/runtime/metal/metal_device_api.mm
+++ b/src/runtime/metal/metal_device_api.mm
@@ -42,6 +42,7 @@
     case kDeviceName: return;
     case kMaxClockRate: return;
     case kMultiProcessorCount: return;
+    case kMaxThreadDimensions: return;
     case kExist: break;
   }
 }
diff --git a/src/runtime/opencl/opencl_device_api.cc b/src/runtime/opencl/opencl_device_api.cc
index b30b058d3acc..8c43ec252a79 100644
--- a/src/runtime/opencl/opencl_device_api.cc
+++ b/src/runtime/opencl/opencl_device_api.cc
@@ -4,6 +4,9 @@
  */
 #include <tvm/runtime/registry.h>
 #include <dmlc/thread_local.h>
+#include <tvm/container.h>
+#include <tvm/ir.h>
+#include <tvm/packed_func_ext.h>
 #include "./opencl_common.h"
 
 namespace tvm {
@@ -30,6 +33,7 @@ void OpenCLWorkspace::GetAttr(
   CHECK_LT(index, devices.size())
       << "Invalid device id " << index;
   switch (kind) {
+    case kExist: break;
     case kMaxThreadsPerBlock: {
       size_t value;
       OPENCL_CALL(clGetDeviceInfo(
@@ -80,7 +84,16 @@ void OpenCLWorkspace::GetAttr(
       *rv = static_cast<int32_t>(value);
       break;
     }
-    case kExist: break;
+    case kMaxThreadDimensions: {
+      size_t dims[3];
+      OPENCL_CALL(clGetDeviceInfo(
+          devices[index], CL_DEVICE_MAX_WORK_ITEM_SIZES, sizeof(dims), dims, nullptr));
+
+      std::stringstream ss;  // use json string to return multiple int values;
+      ss << "[" << dims[0] <<", " << dims[1] << ", " << dims[2] << "]";
+      *rv = ss.str();
+      break;
+    }
   }
 }
 
diff --git a/src/runtime/opengl/opengl_device_api.cc b/src/runtime/opengl/opengl_device_api.cc
index 4357e610f478..3a21ed6e6d07 100644
--- a/src/runtime/opengl/opengl_device_api.cc
+++ b/src/runtime/opengl/opengl_device_api.cc
@@ -97,6 +97,7 @@ void OpenGLWorkspace::GetAttr(
     case kDeviceName: return;
     case kMaxClockRate: return;
     case kMultiProcessorCount: return;
+    case kMaxThreadDimensions: return;
   }
 }
 
diff --git a/src/runtime/rocm/rocm_device_api.cc b/src/runtime/rocm/rocm_device_api.cc
index 3438e8edeaec..6aff5e56c715 100644
--- a/src/runtime/rocm/rocm_device_api.cc
+++ b/src/runtime/rocm/rocm_device_api.cc
@@ -52,6 +52,7 @@ class ROCMDeviceAPI final : public DeviceAPI {
       case kDeviceName: return;
       case kMaxClockRate: return;
       case kMultiProcessorCount: return;
+      case kMaxThreadDimensions: return;
     }
     *rv = value;
   }
diff --git a/src/runtime/vulkan/vulkan_device_api.cc b/src/runtime/vulkan/vulkan_device_api.cc
index 891b39456558..d0f7189d982a 100644
--- a/src/runtime/vulkan/vulkan_device_api.cc
+++ b/src/runtime/vulkan/vulkan_device_api.cc
@@ -73,6 +73,7 @@ void VulkanWorkspace::GetAttr(
     case kMaxClockRate: return;
     case kMultiProcessorCount: return;
     case kExist: break;
+    case kMaxThreadDimensions: break;
   }
 }
 
diff --git a/tests/python/unittest/test_pass_verify_gpu_code.py b/tests/python/unittest/test_pass_verify_gpu_code.py
new file mode 100644
index 000000000000..3059cbef0ed6
--- /dev/null
+++ b/tests/python/unittest/test_pass_verify_gpu_code.py
@@ -0,0 +1,169 @@
+"""Test gpu code verifier"""
+import tvm
+
+def get_verify_pass(valid, **kwargs):
+    def verify_pass(stmt):
+        valid[0] = tvm.ir_pass.VerifyGPUCode(stmt, kwargs)
+        return stmt
+    return verify_pass
+
+def test_shared_memory():
+    N = 1024
+    M = 128
+
+    A = tvm.placeholder((N,), name='A', dtype='float32')
+    B = tvm.compute((N, ), lambda i: A[i], name='B')
+
+    s = tvm.create_schedule([B.op])
+    AA = s.cache_read(A, "shared", [B])
+    o, i = s[B].split(s[B].op.axis[0], M)
+    s[AA].compute_at(s[B], o)
+    s[B].bind(o, tvm.thread_axis("blockIdx.x"))
+    s[B].bind(i, tvm.thread_axis("threadIdx.x"))
+
+    # shared memory usage: M * 4B
+    # thread usage: M
+
+    for target in ['opencl', 'cuda']:
+        if not tvm.context(target).exist:
+            continue
+        valid = [None]
+        with tvm.build_config(**{"add_lower_pass": [
+            (2, get_verify_pass(valid,
+                                max_shared_memory_per_block=4 * M - 1,
+                                max_thread_per_block=M))]}):
+            tvm.build(s, [A, B], target)
+        assert not valid[0]
+
+        with tvm.build_config(**{"add_lower_pass": [
+            (2, get_verify_pass(valid,
+                                max_shared_memory_per_block=4 * M,
+                                max_thread_per_block=M))]}):
+            tvm.build(s, [A, B], target)
+        assert valid[0]
+
+def test_local_memory():
+    N = 1024
+    M = 128
+
+    A = tvm.placeholder((N,), name='A', dtype='float32')
+    B = tvm.compute((N, ), lambda i: A[i], name='B')
+
+    s = tvm.create_schedule([B.op])
+    AA = s.cache_read(A, "local", [B])
+    o, i = s[B].split(s[B].op.axis[0], M)
+    s[AA].compute_at(s[B], o)
+    s[B].bind(o, tvm.thread_axis("blockIdx.x"))
+
+    # local memory usage: M * 4B
+    # thread usage: M
+
+    for target in ['opencl', 'cuda']:
+        if not tvm.context(target).exist:
+            continue
+
+        valid = [None]
+        with tvm.build_config(**{"add_lower_pass": [
+            (2, get_verify_pass(valid,
+                                max_local_memory_per_block=4 * M - 1,
+                                max_thread_per_block=1))]}):
+            tvm.build(s, [A, B], target)
+        assert not valid[0]
+
+        with tvm.build_config(**{"add_lower_pass": [
+            (2, get_verify_pass(valid,
+                                max_local_memory_per_block=4 * M,
+                                max_thread_per_block=1))]}):
+            tvm.build(s, [A, B], target)
+        assert valid[0]
+
+def test_num_thread():
+    N = 1024
+    M = 128
+
+    A = tvm.placeholder((N,), name='A', dtype='float32')
+    B = tvm.compute((N, ), lambda i: A[i], name='B')
+
+    s = tvm.create_schedule([B.op])
+    o, i = s[B].split(s[B].op.axis[0], M)
+
+    s[B].bind(o, tvm.thread_axis('threadIdx.x'))
+    s[B].bind(i, tvm.thread_axis("threadIdx.y"))
+
+    # shared memory usage: 0
+    # thread usage: N
+
+    for target in ['opencl', 'cuda']:
+        if not tvm.context(target).exist:
+            continue
+
+        valid = [None]
+        with tvm.build_config(**{"add_lower_pass": [
+            (2, get_verify_pass(valid,
+                                max_shared_memory_per_block=0,
+                                max_thread_per_block=N - 1))]}):
+            tvm.build(s, [A, B], target)
+        assert not valid[0]
+
+        with tvm.build_config(**{"add_lower_pass": [
+            (2, get_verify_pass(valid,
+                                max_shared_memory_per_block=0,
+                                max_thread_per_block=N))]}):
+            tvm.build(s, [A, B], target)
+        assert valid[0]
+
+        with tvm.build_config(**{"add_lower_pass": [
+            (2, get_verify_pass(valid,
+                                max_shared_memory_per_block=0,
+                                max_thread_per_block=N,
+                                max_thread_y=M-1))]}):
+            tvm.build(s, [A, B], target)
+        assert not valid[0]
+
+        with tvm.build_config(**{"add_lower_pass": [
+            (2, get_verify_pass(valid,
+                                max_shared_memory_per_block=0,
+                                max_thread_per_block=N,
+                                max_thread_y=M))]}):
+            tvm.build(s, [A, B], target)
+        assert valid[0]
+
+def test_multiple_kernels():
+    N = 1024
+
+    A = tvm.placeholder((N, N), name='A')
+    B = tvm.compute((N, N), lambda i, j: A[i, j])
+    C = tvm.compute((N, N), lambda i, j: B[i, j])
+
+    s = tvm.create_schedule([C.op])
+
+    s[C].bind(s[C].op.axis[1], tvm.thread_axis("threadIdx.x"))
+    s[B].bind(s[B].op.axis[1], tvm.thread_axis("threadIdx.x"))
+
+    # shared memory usage: 0
+    # thread usage: N
+
+    for target in ['opencl', 'cuda']:
+        if not tvm.context(target).exist:
+            continue
+
+        valid = [None]
+        with tvm.build_config(**{"add_lower_pass": [
+            (2, get_verify_pass(valid,
+                                max_shared_memory_per_block=0,
+                                max_thread_per_block=N - 1))]}):
+            tvm.build(s, [A, C], target)
+        assert not valid[0]
+
+        with tvm.build_config(**{"add_lower_pass": [
+            (2, get_verify_pass(valid,
+                                max_shared_memory_per_block=0,
+                                max_thread_per_block=N))]}):
+            tvm.build(s, [A, C], target)
+        assert valid[0]
+
+if __name__ == "__main__":
+    test_local_memory()
+    test_shared_memory()
+    test_num_thread()
+    test_multiple_kernels()

From 2f36a372971debca907bebb28ebeb9d2c20c180a Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 23 Jun 2018 18:57:18 -0700
Subject: [PATCH 750/949] [NNVM] Introduce const shift ops (#1325)

---
 HalideIR                                      |  2 +-
 docs/nnvm_top.rst                             |  4 +++
 nnvm/python/nnvm/symbol.py                    | 14 ++++++++
 nnvm/python/nnvm/top/tensor.py                |  8 +++++
 nnvm/src/top/tensor/elemwise.cc               | 33 +++++++++++++++++++
 nnvm/tests/python/compiler/test_top_level1.py | 14 +++++---
 nnvm/tests/python/compiler/test_top_level3.py | 13 ++++++--
 topi/python/topi/broadcast.py                 |  2 +-
 topi/tests/python/test_topi_broadcast.py      | 20 +++++++++--
 9 files changed, 97 insertions(+), 13 deletions(-)

diff --git a/HalideIR b/HalideIR
index 0b7e25275138..9204453ae8de 160000
--- a/HalideIR
+++ b/HalideIR
@@ -1 +1 @@
-Subproject commit 0b7e25275138768bb05edb9b9db2c86d0fb09c9a
+Subproject commit 9204453ae8de77e7dfc32c4d80f58dd788ad75ff
diff --git a/docs/nnvm_top.rst b/docs/nnvm_top.rst
index 5f3fd9fa1b0b..4e1e536dbb26 100644
--- a/docs/nnvm_top.rst
+++ b/docs/nnvm_top.rst
@@ -88,6 +88,8 @@ This level enables typical convnet models.
    nnvm.symbol.__rdiv_scalar__
    nnvm.symbol.__pow_scalar__
    nnvm.symbol.__rpow_scalar__
+   nnvm.symbol.__lshift_scalar__
+   nnvm.symbol.__rshift_scalar__
 
 **Level 4: Broadcast and Reductions**
 
@@ -164,6 +166,8 @@ Detailed Definitions
 .. autofunction:: nnvm.symbol.__rdiv_scalar__
 .. autofunction:: nnvm.symbol.__pow_scalar__
 .. autofunction:: nnvm.symbol.__rpow_scalar__
+.. autofunction:: nnvm.symbol.__lshift_scalar__
+.. autofunction:: nnvm.symbol.__rshift_scalar__
 
 .. autofunction:: nnvm.symbol.transpose
 .. autofunction:: nnvm.symbol.broadcast_to
diff --git a/nnvm/python/nnvm/symbol.py b/nnvm/python/nnvm/symbol.py
index bc479a65e03b..6997ecc64654 100644
--- a/nnvm/python/nnvm/symbol.py
+++ b/nnvm/python/nnvm/symbol.py
@@ -100,6 +100,20 @@ def __rdiv__(self, other):
         else:
             raise TypeError('type %s not supported' % str(type(other)))
 
+    def __lshift__(self, other):
+        """x.__lshift__(y) <=> x << y"""
+        if isinstance(other, _Number):
+            return __lshift_scalar__(self, scalar=other)
+        else:
+            raise TypeError('type %s not supported' % str(type(other)))
+
+    def __rshift__(self, other):
+        """x.__rshift__(y) <=> x >> y"""
+        if isinstance(other, _Number):
+            return __rshift_scalar__(self, scalar=other)
+        else:
+            raise TypeError('type %s not supported' % str(type(other)))
+
     def __truediv__(self, other):
         return self.__div__(other)
 
diff --git a/nnvm/python/nnvm/top/tensor.py b/nnvm/python/nnvm/top/tensor.py
index c11486f5c77f..0250f6ddfad8 100644
--- a/nnvm/python/nnvm/top/tensor.py
+++ b/nnvm/python/nnvm/top/tensor.py
@@ -133,6 +133,14 @@ def compute_cast(attrs, inputs, _):
 reg.register_pattern("__rpow_scalar__", OpPattern.ELEMWISE)
 reg.register_schedule("__rpow_scalar__", _fschedule_broadcast)
 
+# lshift_scalar
+reg.register_pattern("__lshift_scalar__", OpPattern.ELEMWISE)
+reg.register_schedule("__lshift_scalar__", _fschedule_broadcast)
+
+# rshift_scalar
+reg.register_pattern("__rshift_scalar__", OpPattern.ELEMWISE)
+reg.register_schedule("__rshift_scalar__", _fschedule_broadcast)
+
 # elemwise_add
 reg.register_pattern("elemwise_add", OpPattern.BROADCAST)
 reg.register_schedule("elemwise_add", _fschedule_broadcast)
diff --git a/nnvm/src/top/tensor/elemwise.cc b/nnvm/src/top/tensor/elemwise.cc
index cff85497be4f..cd38817b51a5 100644
--- a/nnvm/src/top/tensor/elemwise.cc
+++ b/nnvm/src/top/tensor/elemwise.cc
@@ -512,6 +512,39 @@ NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__rsub_scalar__)
     };
 });
 
+
+NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__lshift_scalar__)
+.describe(R"code(Tensor left shift by scalar
+
+)code"  NNVM_ADD_FILELINE)
+.set_support_level(3)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const ScalarParam& param = nnvm::get<ScalarParam>(attrs.parsed);
+    int scalar_val = static_cast<int>(param.scalar);
+    return Array<Tensor>{
+      topi::left_shift(inputs[0],
+                       make_const(inputs[0]->dtype, scalar_val))};
+    });
+
+NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__rshift_scalar__)
+.describe(R"code(Tensor right shift by scalar
+
+)code"  NNVM_ADD_FILELINE)
+.set_support_level(3)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const ScalarParam& param = nnvm::get<ScalarParam>(attrs.parsed);
+    int scalar_val = static_cast<int>(param.scalar);
+    return Array<Tensor>{
+      topi::right_shift(inputs[0],
+                        make_const(inputs[0]->dtype, scalar_val))};
+  });
+
 NNVM_REGISTER_ELEMWISE_BINARY_SCALAR(__mul_scalar__)
 .describe(R"code(Tensor multiplies scalar
 
diff --git a/nnvm/tests/python/compiler/test_top_level1.py b/nnvm/tests/python/compiler/test_top_level1.py
index 37798d37f400..73391c80dd12 100644
--- a/nnvm/tests/python/compiler/test_top_level1.py
+++ b/nnvm/tests/python/compiler/test_top_level1.py
@@ -7,17 +7,21 @@
 from nnvm.testing.config import ctx_list
 
 def helper(symbol, inputs, dtype,
-           np_forward, np_backward=None, need_input=True, need_head_grads=True):
+           np_forward, np_backward=None,
+           need_input=True, need_head_grads=True,
+           rnd_min=-1, rnd_max=1):
     ishapes = {}
+    itypes = {}
     input_syms = []
     np_inputs = {}
     for (name, shape, s) in inputs:
         ishapes.update({name: shape})
-        np_inputs.update({name: np.random.uniform(size=shape).astype(dtype)})
+        itypes.update({name: dtype})
+        np_inputs.update({name: np.random.uniform(rnd_min, rnd_max, size=shape).astype(dtype)})
         input_syms.append(s)
 
     for target, ctx in ctx_list():
-        graph, lib, _ = nnvm.compiler.build(symbol, target, ishapes)
+        graph, lib, _ = nnvm.compiler.build(symbol, target, ishapes, itypes)
         m = graph_runtime.create(graph, lib, ctx)
         m.run(**np_inputs)
         y_np = np_forward(**np_inputs)
@@ -164,7 +168,7 @@ def backward(head_grads, x):
     dtype = "float32"
     dshape = (1, 3, 32, 32)
     inputs = [('x', dshape, x)]
-    helper(y, inputs, dtype, forward, backward)
+    helper(y, inputs, dtype, forward, backward, rnd_min=0.001)
 
 
 def test_tanh():
@@ -277,7 +281,7 @@ def forward(x, gamma, beta, moving_mean, moving_var):
         ('moving_var', (20,), moving_mean)
     ]
 
-    helper(y, inputs,  dtype, forward)
+    helper(y, inputs,  dtype, forward, rnd_min=0.001)
 
 
 def verify_concatenate(ishape, axis):
diff --git a/nnvm/tests/python/compiler/test_top_level3.py b/nnvm/tests/python/compiler/test_top_level3.py
index 27a99bfb530f..125836a7848e 100644
--- a/nnvm/tests/python/compiler/test_top_level3.py
+++ b/nnvm/tests/python/compiler/test_top_level3.py
@@ -7,13 +7,13 @@
 from nnvm.testing.config import ctx_list
 from test_top_level1 import helper
 
-def check_map(symfunc, np_func, np_backward=None):
+def check_map(symfunc, np_func, np_backward=None, dtype="float32", rnd_min=-1, rnd_max=1):
     x = sym.Variable("x")
     y = symfunc(x)
-    dtype = "float32"
     dshape = (1, 3, 32, 32)
     inputs = [('x', dshape, x)]
-    helper(y, inputs, dtype, lambda x: np_func(x), np_backward)
+    helper(y, inputs, dtype, lambda x: np_func(x), np_backward,
+           rnd_min=rnd_min, rnd_max=rnd_max)
 
 
 def test_floor():
@@ -29,7 +29,14 @@ def test_round():
     check_map(sym.round, np.round)
 
 
+def test_shift():
+    n = 3
+    for dtype in ["int32", "int8"]:
+        check_map(lambda x : x >> n, lambda x: x >> n, dtype=dtype, rnd_min=-100, rnd_max=100)
+        check_map(lambda x : x << n, lambda x: x << n, dtype=dtype, rnd_min=-100, rnd_max=100)
+
 if __name__ == "__main__":
+    test_shift()
     test_floor()
     test_ceil()
     test_round()
diff --git a/topi/python/topi/broadcast.py b/topi/python/topi/broadcast.py
index caf5c77f5629..f088e48b0f14 100644
--- a/topi/python/topi/broadcast.py
+++ b/topi/python/topi/broadcast.py
@@ -210,7 +210,7 @@ def right_shift(lhs, rhs):
         Returns Expr if both operands are Expr.
         Otherwise returns Tensor.
     """
-    return _cpp.left_shift(lhs, rhs)
+    return _cpp.right_shift(lhs, rhs)
 
 
 def greater(lhs, rhs):
diff --git a/topi/tests/python/test_topi_broadcast.py b/topi/tests/python/test_topi_broadcast.py
index 05a32ca0eac5..27e72e327232 100644
--- a/topi/tests/python/test_topi_broadcast.py
+++ b/topi/tests/python/test_topi_broadcast.py
@@ -68,7 +68,7 @@ def check_device(device):
         if rhs_shape is None:
             rhs_npy = float(np.random.uniform(low=rhs_min, high=rhs_max))
             if dtype.startswith('int'):
-                lhs_npy = int(lhs_npy)
+                rhs_npy = int(rhs_npy)
             rhs_nd = rhs_npy
         else:
             rhs_npy = np.random.uniform(low=rhs_min, high=rhs_max,
@@ -77,8 +77,7 @@ def check_device(device):
 
         out_npy = fnumpy(lhs_npy, rhs_npy)
         out_nd = tvm.nd.array(np.empty(out_npy.shape).astype(C.dtype), ctx)
-        for _ in range(1):
-            foo(lhs_nd, rhs_nd, out_nd)
+        foo(lhs_nd, rhs_nd, out_nd)
         np.testing.assert_allclose(out_nd.asnumpy(), out_npy, rtol=1E-4, atol=1E-4)
 
     check_device("opencl")
@@ -142,8 +141,23 @@ def less(x, y):
     verify_broadcast_binary_ele(
         (2, 1, 2), (2, 3, 1), less, np.less)
 
+def test_shift():
+    # explicit specify the output type
+    verify_broadcast_binary_ele(
+        (2, 1, 2), None, topi.right_shift, np.right_shift,
+        dtype="int32", rhs_min=0, rhs_max=32)
+
+    verify_broadcast_binary_ele(
+        (1, 2, 2), (2,), topi.left_shift, np.left_shift,
+        dtype="int32", rhs_min=0, rhs_max=32)
+
+    verify_broadcast_binary_ele(
+        (1, 2, 2), (2,), topi.left_shift, np.left_shift,
+        dtype="int8", rhs_min=0, rhs_max=32)
+
 
 if __name__ == "__main__":
+    test_shift()
     test_cmp()
     test_mod()
     test_add()

From ff39e52ce19be1ee7fa8babe0ca4aacf89e3edf4 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 23 Jun 2018 19:29:35 -0700
Subject: [PATCH 751/949] [DOCKER] update to include deps (#1326)

---
 tests/ci_build/Dockerfile.cpu                  | 9 ---------
 tests/ci_build/Dockerfile.gpu                  | 6 ++++--
 tests/ci_build/install/ubuntu_install_redis.sh | 3 +++
 3 files changed, 7 insertions(+), 11 deletions(-)
 create mode 100644 tests/ci_build/install/ubuntu_install_redis.sh

diff --git a/tests/ci_build/Dockerfile.cpu b/tests/ci_build/Dockerfile.cpu
index 57fb7e04552b..d749afd752df 100644
--- a/tests/ci_build/Dockerfile.cpu
+++ b/tests/ci_build/Dockerfile.cpu
@@ -9,17 +9,8 @@ RUN bash /install/ubuntu_install_core.sh
 COPY install/ubuntu_install_python.sh /install/ubuntu_install_python.sh
 RUN bash /install/ubuntu_install_python.sh
 
-COPY install/ubuntu_install_iverilog.sh /install/ubuntu_install_iverilog.sh
-RUN bash /install/ubuntu_install_iverilog.sh
-
 COPY install/ubuntu_install_python_package.sh /install/ubuntu_install_python_package.sh
 RUN bash /install/ubuntu_install_python_package.sh
 
-COPY install/ubuntu_install_java.sh /install/ubuntu_install_java.sh
-RUN bash /install/ubuntu_install_java.sh
-
 COPY install/ubuntu_install_llvm.sh /install/ubuntu_install_llvm.sh
 RUN bash /install/ubuntu_install_llvm.sh
-
-COPY install/ubuntu_install_opengl.sh /install/ubuntu_install_opengl.sh
-RUN bash /install/ubuntu_install_opengl.sh
diff --git a/tests/ci_build/Dockerfile.gpu b/tests/ci_build/Dockerfile.gpu
index 0d9c1be420f7..95a70d97d8b2 100644
--- a/tests/ci_build/Dockerfile.gpu
+++ b/tests/ci_build/Dockerfile.gpu
@@ -40,7 +40,6 @@ RUN bash /install/ubuntu_install_rocm.sh
 COPY install/ubuntu_install_opengl.sh /install/ubuntu_install_opengl.sh
 RUN bash /install/ubuntu_install_opengl.sh
 
-
 # DL Frameworks
 COPY install/ubuntu_install_mxnet.sh /install/ubuntu_install_mxnet.sh
 RUN bash /install/ubuntu_install_mxnet.sh
@@ -59,10 +58,13 @@ RUN bash /install/ubuntu_install_onnx.sh
 
 RUN pip3 install Pillow
 
-# disable vulkan for now
 COPY install/ubuntu_install_vulkan.sh /install/ubuntu_install_vulkan.sh
 RUN bash /install/ubuntu_install_vulkan.sh
 
+# AutoTVM deps
+COPY install/ubuntu_install_redis.sh /install/ubuntu_install_redis.sh
+RUN bash /install/ubuntu_install_redis.sh
+
 # Environment variables
 ENV PATH=/usr/local/nvidia/bin:${PATH}
 ENV PATH=/usr/local/cuda/bin:${PATH}
diff --git a/tests/ci_build/install/ubuntu_install_redis.sh b/tests/ci_build/install/ubuntu_install_redis.sh
new file mode 100644
index 000000000000..acb022a8b093
--- /dev/null
+++ b/tests/ci_build/install/ubuntu_install_redis.sh
@@ -0,0 +1,3 @@
+apt-get update && apt-get install -y redis-server
+pip2 install redis psutil
+pip3 install redis psutil

From b432a674f3867c0ad5a28bc4ba79e036ceb4b20c Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 23 Jun 2018 19:48:30 -0700
Subject: [PATCH 752/949] [DOCKER] mark tmp as temp (#1327)

---
 tests/ci_build/ci_build.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/ci_build/ci_build.sh b/tests/ci_build/ci_build.sh
index 86c138aaf3a5..53019231216a 100755
--- a/tests/ci_build/ci_build.sh
+++ b/tests/ci_build/ci_build.sh
@@ -112,7 +112,7 @@ echo "Running '${COMMAND[@]}' inside ${DOCKER_IMG_NAME}..."
 # and share the PID namespace (--pid=host) so the process inside does not have
 # pid 1 and SIGKILL is propagated to the process inside (jenkins can kill it).
 echo ${DOCKER_BINARY}
-${DOCKER_BINARY} run --rm --pid=host \
+${DOCKER_BINARY} run --rm --tmpfs=/tmp --pid=host \
     -v ${WORKSPACE}:/workspace \
     -w /workspace \
     -e "CI_BUILD_HOME=/workspace" \

From 03a52d1f938b3c536c9e94b9b4aab9a127914a70 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 23 Jun 2018 20:05:12 -0700
Subject: [PATCH 753/949] Update ci_build.sh

---
 tests/ci_build/ci_build.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/ci_build/ci_build.sh b/tests/ci_build/ci_build.sh
index 53019231216a..86c138aaf3a5 100755
--- a/tests/ci_build/ci_build.sh
+++ b/tests/ci_build/ci_build.sh
@@ -112,7 +112,7 @@ echo "Running '${COMMAND[@]}' inside ${DOCKER_IMG_NAME}..."
 # and share the PID namespace (--pid=host) so the process inside does not have
 # pid 1 and SIGKILL is propagated to the process inside (jenkins can kill it).
 echo ${DOCKER_BINARY}
-${DOCKER_BINARY} run --rm --tmpfs=/tmp --pid=host \
+${DOCKER_BINARY} run --rm --pid=host \
     -v ${WORKSPACE}:/workspace \
     -w /workspace \
     -e "CI_BUILD_HOME=/workspace" \

From 273d51639dbb7636afaaf92cd420346d1869e542 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 23 Jun 2018 22:13:50 -0700
Subject: [PATCH 754/949] [SCHEDULE] Fuse support for 0 rank tensor (#1328)

---
 include/tvm/schedule.h                      | 39 +++++++++++++++++++--
 python/tvm/schedule.py                      | 11 +++---
 src/api/api_lang.cc                         |  2 +-
 src/schedule/message_passing.cc             | 11 ++++++
 src/schedule/schedule_lang.cc               | 38 +++++++++++++++++++-
 tests/python/integration/test_ewise.py      |  9 +++--
 tests/python/unittest/test_lang_schedule.py | 14 ++++++++
 topi/tests/python/test_topi_broadcast.py    |  6 +++-
 8 files changed, 115 insertions(+), 15 deletions(-)

diff --git a/include/tvm/schedule.h b/include/tvm/schedule.h
index a08d2dcb7881..deaf74ccf222 100644
--- a/include/tvm/schedule.h
+++ b/include/tvm/schedule.h
@@ -129,6 +129,20 @@ class Stage : public NodeRef {
    * \return reference to self.
    */
   EXPORT Stage& fuse(IterVar outer, IterVar inner, IterVar* p_target);  // NOLINT(*)
+  /*!
+   * \brief Fuse all the axes together into a single axis.
+   *
+   * \param axes All the axes to be fused.
+   * \param p_target The result target domain.
+   *
+   * \note axes can be an empty array,
+   *       in that case, a singleton itervar is created and
+   *       inserted to the outermost loop.
+   *       The fuse of empty array is used to support zero-dimension tensors.
+   *
+   * \return reference to self.
+   */
+  EXPORT Stage& fuse(const Array<IterVar>& axes, IterVar* p_target);  // NOLINT(*)
   /*!
    * \brief Reorder the iteration
    * \param order The order of iteration variable.
@@ -151,9 +165,9 @@ class Stage : public NodeRef {
    * \return reference to self.
    */
   EXPORT Stage& tile(IterVar x_parent, IterVar y_parent,   // NOLINT(*)
-              Expr x_factor, Expr y_factor,
-              IterVar* p_x_outer, IterVar* p_y_outer,
-              IterVar* p_x_inner, IterVar* p_y_inner);
+                     Expr x_factor, Expr y_factor,
+                     IterVar* p_x_outer, IterVar* p_y_outer,
+                     IterVar* p_x_inner, IterVar* p_y_inner);
   /*!
    * \brief Vectorize iteration.
    * \param var The axis to be vectorized.
@@ -674,6 +688,25 @@ class RebaseNode : public IterVarRelationNode {
 };
 
 
+/*!
+ * \brief Singleton iterator [0, 1)
+ */
+class SingletonNode : public IterVarRelationNode {
+ public:
+  /*! \brief The singleton iterator */
+  IterVar iter;
+
+  void VisitAttrs(AttrVisitor* v) final {
+    v->Visit("iter", &iter);
+  }
+
+  static IterVarRelation make(IterVar iter);
+
+  static constexpr const char* _type_key = "Singleton";
+  TVM_DECLARE_NODE_TYPE_INFO(SingletonNode, IterVarRelationNode);
+};
+
+
 // implementations
 inline const StageNode* Stage::operator->() const {
   return static_cast<const StageNode*>(node_.get());
diff --git a/python/tvm/schedule.py b/python/tvm/schedule.py
index f54ae188c130..5aeac51555f7 100644
--- a/python/tvm/schedule.py
+++ b/python/tvm/schedule.py
@@ -152,6 +152,12 @@ class Fuse(NodeBase):
     pass
 
 
+@register_node
+class Singleton(NodeBase):
+    """Singleton axis."""
+    pass
+
+
 @register_node
 class IterVar(NodeBase, _expr.ExprOp):
     """Represent iteration variable.
@@ -380,10 +386,7 @@ def fuse(self, *args):
         fused : IterVar
             The fused variable of iteration.
         """
-        assert len(args) >= 1, "Length of the arguments must be >=1 for fuse."
-        fused = args[0]
-        for i in range(1, len(args)):
-            fused = _api_internal._StageFuse(self, fused, args[i])
+        fused = _api_internal._StageFuse(self, args)
         return fused
 
     def set_scope(self, scope):
diff --git a/src/api/api_lang.cc b/src/api/api_lang.cc
index 3d80b5c2e7a4..8c55684ed851 100644
--- a/src/api/api_lang.cc
+++ b/src/api/api_lang.cc
@@ -350,7 +350,7 @@ TVM_REGISTER_API("_StageFuse")
 .set_body([](TVMArgs args, TVMRetValue* ret) {
     IterVar fused;
     args[0].operator Stage()
-        .fuse(args[1], args[2], &fused);
+        .fuse(args[1], &fused);
     *ret = fused;
   });
 
diff --git a/src/schedule/message_passing.cc b/src/schedule/message_passing.cc
index 3cea560318d8..b13dcefb1b9f 100644
--- a/src/schedule/message_passing.cc
+++ b/src/schedule/message_passing.cc
@@ -82,6 +82,8 @@ void PassDownDomain(const Stage& stage,
       Update(p_state, r->rebased,
              Range::make_by_min_extent(
                  0, state.at(r->parent)->extent));
+    } else if (const SingletonNode* s = rel.as<SingletonNode>()) {
+      Update(p_state, s->iter, Range::make_by_min_extent(0, 1));
     } else {
       LOG(FATAL) << "unknown relation type";
     }
@@ -147,6 +149,7 @@ void PassUpIndex(const Stage& stage,
       } else {
         state[s->parent] = value;
       }
+    } else if (rel.as<SingletonNode>()) {
     } else {
       LOG(FATAL) << "unknown relation type";
     }
@@ -192,6 +195,8 @@ void PassDownIndex(const Stage& stage,
       Expr parent_min = dom_map.at(s->parent)->min;
       CHECK(is_zero(parent_min));
       state[s->rebased] = value;
+    } else if (const SingletonNode* s = rel.as<SingletonNode>()) {
+      state[s->iter] = make_zero(s->iter->var.type());
     } else {
       LOG(FATAL) << "unknown relation type";
     }
@@ -296,6 +301,7 @@ void PassUpDomain(const Stage& stage,
                    state.at(r->rebased),
                    &parent);
       state[r->parent] = parent;
+    } else if (rel.as<SingletonNode>()) {
     } else {
       LOG(FATAL) << "unknown relation type";
     }
@@ -344,6 +350,7 @@ void PassUpBitMaskOr(const Stage& stage,
       } else {
         state[s->parent] |= state[s->rebased];
       }
+    } else if (rel.as<SingletonNode>()) {
     } else {
       LOG(FATAL) << "unknown relation type";
     }
@@ -390,6 +397,8 @@ void PassDownBitMaskOr(const Stage& stage,
       } else {
         state[s->rebased] |= state.at(s->parent);
       }
+    } else if (const SingletonNode* s = rel.as<SingletonNode>()) {
+      state[s->iter] = 0;
     } else {
       LOG(FATAL) << "unknown relation type";
     }
@@ -438,6 +447,8 @@ void PassUpBoundCheck(const Stage& s,
     } else if (rel.as<RebaseNode>()) {
       const RebaseNode* s = rel.as<RebaseNode>();
       state[s->parent] = state.at(s->rebased);
+    } else if (rel.as<SingletonNode>()) {
+      // nop
     } else {
       LOG(FATAL) << "unknown relation type";
     }
diff --git a/src/schedule/schedule_lang.cc b/src/schedule/schedule_lang.cc
index 5af8f0ac810a..eea8aa1aae80 100644
--- a/src/schedule/schedule_lang.cc
+++ b/src/schedule/schedule_lang.cc
@@ -237,7 +237,6 @@ Stage& Stage::fuse(IterVar outer, IterVar inner, IterVar* p_target) {  // NOLINT
   IterVar fused = IterVarNode::make(
       Range(), Var(fused_name, outer->var.type()), iter_type);
 
-  *p_target = fused;
   ArrayNode* all_vars = self->all_iter_vars.CopyOnWrite();
   ArrayNode* leaf_vars = self->leaf_iter_vars.CopyOnWrite();
 
@@ -255,6 +254,31 @@ Stage& Stage::fuse(IterVar outer, IterVar inner, IterVar* p_target) {  // NOLINT
                         leaf_vars->data.begin() + pos_inner + 1);
   leaf_vars->data.insert(leaf_vars->data.begin() + pos_outer,
                          fused.node_);
+  *p_target = fused;
+  return *this;
+}
+
+Stage& Stage::fuse(const Array<IterVar>& axes, IterVar* p_target) {  // NOLINT(*)
+  if (axes.size() != 0) {
+    IterVar fused = axes[0];
+    for (size_t i = 1; i < axes.size(); ++i) {
+      this->fuse(fused, axes[i], &fused);
+    }
+    *p_target = std::move(fused);
+  } else {
+    StageNode* self = operator->();
+    // special handle fuse empty array.
+    // insert at the outer most loop
+    IterVar singleton = IterVarNode::make(
+        Range::make_by_min_extent(0, 1),
+        Var("singleton", Int(32)), kDataPar);
+    self->relations.push_back(SingletonNode::make(singleton));
+    ArrayNode* all_vars = self->all_iter_vars.CopyOnWrite();
+    ArrayNode* leaf_vars = self->leaf_iter_vars.CopyOnWrite();
+    all_vars->data.push_back(singleton.node_);
+    leaf_vars->data.insert(leaf_vars->data.begin(), singleton.node_);
+    *p_target = singleton;
+  }
   return *this;
 }
 
@@ -732,11 +756,18 @@ IterVarRelation RebaseNode::make(IterVar parent, IterVar rebased) {
   return IterVarRelation(n);
 }
 
+IterVarRelation SingletonNode::make(IterVar iter) {
+  auto n = std::make_shared<SingletonNode>();
+  n->iter = iter;
+  return IterVarRelation(n);
+}
+
 TVM_REGISTER_NODE_TYPE(StageNode);
 TVM_REGISTER_NODE_TYPE(IterVarAttrNode);
 TVM_REGISTER_NODE_TYPE(SplitNode);
 TVM_REGISTER_NODE_TYPE(FuseNode);
 TVM_REGISTER_NODE_TYPE(RebaseNode);
+TVM_REGISTER_NODE_TYPE(SingletonNode);
 TVM_REGISTER_NODE_TYPE(ScheduleNode);
 
 // Printer
@@ -778,6 +809,11 @@ TVM_STATIC_IR_FUNCTOR(IRPrinter, vtable)
     p->print(op->rebased);
     p->stream << ')';
 })
+.set_dispatch<SingletonNode>([](const SingletonNode *op, IRPrinter *p) {
+    p->stream << "singleton(";
+    p->print(op->iter);
+    p->stream << ')';
+})
 .set_dispatch<ScheduleNode>([](const ScheduleNode *op, IRPrinter *p) {
     p->stream << "schedule(" << op << ")";
   });
diff --git a/tests/python/integration/test_ewise.py b/tests/python/integration/test_ewise.py
index 64d164c5e644..f16f15325735 100644
--- a/tests/python/integration/test_ewise.py
+++ b/tests/python/integration/test_ewise.py
@@ -44,10 +44,10 @@ def test_multiple_cache_write():
     n = tvm.convert(1024)
     A0 = tvm.placeholder((n,), name='A0', dtype = "float32")
     A1 = tvm.placeholder((n,), name='A1', dtype = "float32")
-    B0, B1 = tvm.compute((n,), 
-            lambda *i: (A0(*i) + A1(*i), A0(*i) * A1(*i)), 
+    B0, B1 = tvm.compute((n,),
+            lambda *i: (A0(*i) + A1(*i), A0(*i) * A1(*i)),
             name='B')
-    C = tvm.compute((n,), lambda *i: B0(*i) + B1(*i), 
+    C = tvm.compute((n,), lambda *i: B0(*i) + B1(*i),
             name='C')
     s = tvm.create_schedule(C.op)
     # create iter var and assign them tags.
@@ -76,7 +76,7 @@ def check_device(device, host="stackvm"):
         c = tvm.nd.array(np.zeros(n, dtype=C.dtype), ctx)
         func(a0, a1, c)
         np.testing.assert_allclose(
-            c.asnumpy(), a0.asnumpy() + a1.asnumpy() + (a0.asnumpy() * a1.asnumpy()), 
+            c.asnumpy(), a0.asnumpy() + a1.asnumpy() + (a0.asnumpy() * a1.asnumpy()),
             rtol=1e-5)
 
     check_device("cuda", "llvm")
@@ -235,7 +235,6 @@ def check_device(device):
         f(a, b)
         np.testing.assert_allclose(
             b.asnumpy(), a.asnumpy() + 3, rtol=1e-6)
-
     check_device("cuda")
 
 
diff --git a/tests/python/unittest/test_lang_schedule.py b/tests/python/unittest/test_lang_schedule.py
index 07e5d98d044f..1eb42f3f0bca 100644
--- a/tests/python/unittest/test_lang_schedule.py
+++ b/tests/python/unittest/test_lang_schedule.py
@@ -84,6 +84,19 @@ def test_fuse():
     assert any(isinstance(x, tvm.schedule.Fuse) for x in s[T].relations)
     assert tuple(s[T].leaf_iter_vars) == (fused, xi, yi)
 
+
+def test_singleton():
+    A = tvm.placeholder((), name='A')
+    T = tvm.compute((), lambda : A() + 1)
+    s = tvm.create_schedule(T.op)
+    fused = s[T].fuse()
+    assert any(isinstance(x, tvm.schedule.Singleton) for x in s[T].relations)
+    assert tuple(s[T].leaf_iter_vars) == (fused,)
+    dump = pkl.dumps(s)
+    s_loaded = pkl.loads(dump)
+    assert isinstance(s_loaded, tvm.schedule.Schedule)
+
+
 def test_vectorize():
     m = tvm.var('m')
     n = tvm.var('n')
@@ -174,6 +187,7 @@ def intrin_func(ins, outs):
 
 
 if __name__ == "__main__":
+    test_singleton()
     test_pragma()
     test_tensor_intrin()
     test_rfactor()
diff --git a/topi/tests/python/test_topi_broadcast.py b/topi/tests/python/test_topi_broadcast.py
index 27e72e327232..25bb98ed163f 100644
--- a/topi/tests/python/test_topi_broadcast.py
+++ b/topi/tests/python/test_topi_broadcast.py
@@ -93,6 +93,8 @@ def test_broadcast_to():
     verify_broadcast_to_ele((1, 128, 1, 32), (64, 128, 64, 32), topi.broadcast_to)
 
 def test_add():
+    verify_broadcast_binary_ele(
+        (), (), topi.add, np.add)
     verify_broadcast_binary_ele(
         (5, 2, 3), (2, 1), topi.add, np.add)
 
@@ -113,6 +115,8 @@ def test_multiply():
 def test_divide():
     verify_broadcast_binary_ele(
         None, (10,), topi.divide, np.divide, rhs_min=0.0001)
+    verify_broadcast_binary_ele(
+        (), None, topi.divide, np.divide, rhs_min=0.0001)
     verify_broadcast_binary_ele(
         (2, 3, 1, 32), (64, 32), topi.divide, np.divide, rhs_min=0.0001)
 
@@ -157,10 +161,10 @@ def test_shift():
 
 
 if __name__ == "__main__":
+    test_add()
     test_shift()
     test_cmp()
     test_mod()
-    test_add()
     test_subtract()
     test_multiply()
     test_divide()

From f3dbee86e51fda18b07720e4ebcd4e8b45985ace Mon Sep 17 00:00:00 2001
From: rajh619 <30085127+rajh619@users.noreply.github.com>
Date: Sun, 24 Jun 2018 21:35:10 +0530
Subject: [PATCH 755/949] Windows VS2015 Compile error - 'conversion from
 'double' to 'float' requires a narrowing conversion' (#1329)

---
 nnvm/include/nnvm/top/nn.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/include/nnvm/top/nn.h b/nnvm/include/nnvm/top/nn.h
index 0e0b03a72ebb..69d49cd99696 100644
--- a/nnvm/include/nnvm/top/nn.h
+++ b/nnvm/include/nnvm/top/nn.h
@@ -349,7 +349,7 @@ struct MultiBoxTransformLocParam : public dmlc::Parameter<MultiBoxTransformLocPa
       .describe("Clip out-of-boundary boxes.");
     DMLC_DECLARE_FIELD(threshold).set_default(0.01)
     .describe("Threshold to be a positive prediction.");
-    DMLC_DECLARE_FIELD(variances).set_default(Tuple<float>{0.1, 0.1, 0.2, 0.2})
+    DMLC_DECLARE_FIELD(variances).set_default(Tuple<float>({0.1f, 0.1f, 0.2f, 0.2f}))
     .describe("Variances to be decoded from box regression output.");
   }
 };

From 1e66d3c72c7dc24c7255a946e41212e5f516b42e Mon Sep 17 00:00:00 2001
From: Siva <sivar.b@huawei.com>
Date: Sun, 24 Jun 2018 21:39:09 +0530
Subject: [PATCH 756/949] [NNVM][CONVOLUTION] Group convolution generalization
 for NHWC (#1232)

---
 nnvm/python/nnvm/frontend/tensorflow.py       | 135 ++++++++++++++++--
 nnvm/python/nnvm/top/nn.py                    |  20 ++-
 nnvm/src/top/nn/convolution.cc                |   3 +-
 nnvm/tests/python/compiler/test_top_level2.py |  27 +++-
 4 files changed, 172 insertions(+), 13 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/tensorflow.py b/nnvm/python/nnvm/frontend/tensorflow.py
index 17f08cc3e4a7..3ab997617a20 100644
--- a/nnvm/python/nnvm/frontend/tensorflow.py
+++ b/nnvm/python/nnvm/frontend/tensorflow.py
@@ -33,6 +33,8 @@ def __call__(self, inputs, attrs, *args):
         self._ignores.append('_input_shapes')
         self._ignores.append('T')
         self._ignores.append('use_cudnn_on_gpu')
+        self._ignores.append('_node_name')
+        self._ignores.append('is_training')
         return AttrConvert(self._op_name, self._transforms, self._excludes,
                            self._disables, self._ignores, self._extras,
                            self._custom_check)(inputs, attrs, *args)
@@ -230,6 +232,85 @@ def _impl(inputs, attr, params):
             custom_check=_dimension_constraint())(inputs, attr)
     return _impl
 
+def _depthwise_conv():
+    def _impl(inputs, attr, params):
+        attr['data_format'] = attr['data_format'].decode("utf-8")
+        input_shapes = attr['_input_shapes'][inputs[0]]
+
+        # Extract kernel shape from params
+        conv_param_weights = params[inputs[1].list_output_names()[0]]
+
+        if attr['data_format'] == 'NHWC':
+            kernel_h, kernel_w, _, depth_mult = conv_param_weights.shape
+            attr['kernel_shape'] = (conv_param_weights.shape[0], conv_param_weights.shape[1])
+            attr['channels'] = input_shapes[0][3] * depth_mult
+            if 'dilations' in attr:
+                attr['dilations'] = (attr['dilations'][0], attr['dilations'][1])
+        elif attr['data_format'] == 'NCHW':
+            depth_mult, _, kernel_h, kernel_w = conv_param_weights.shape
+            attr['kernel_shape'] = (conv_param_weights.shape[2], conv_param_weights.shape[3])
+            attr['channels'] = input_shapes[0][1] * depth_mult
+            if 'dilations' in attr:
+                attr['dilations'] = (attr['dilations'][2], attr['dilations'][3])
+        else:
+            raise TypeError("Unsupported data format type : {}".format(attr['data_format']))
+
+        # Fix strides
+        attr['strides'] = (attr['strides'][1], attr['strides'][2])
+
+        # Fix groups
+        attr['groups'] = attr['channels']
+
+        # Fix padding
+        attr['padding'] = attr['padding'].decode("utf-8")
+
+        if attr['padding'] == 'VALID':
+            attr['padding'] = [0, 0]
+        elif attr['padding'] == 'SAME':
+            stride_h, stride_w = attr['strides']
+            kernel_h, kernel_w = attr['kernel_shape']
+            if attr['data_format'] == 'NHWC':
+                in_h = input_shapes[0][1]
+                in_w = input_shapes[0][2]
+            else:
+                in_h = input_shapes[0][2]
+                in_w = input_shapes[0][3]
+
+            pad_v = _get_pad_pair(in_h, kernel_h, stride_h)
+            pad_h = _get_pad_pair(in_w, kernel_w, stride_w)
+
+            if attr['data_format'] == 'NHWC':
+                inputs[0] = _sym.pad(data=inputs[0],
+                                     pad_width=((0, 0),
+                                                (pad_v[0], pad_v[1]),
+                                                (pad_h[0], pad_h[1]),
+                                                (0, 0)))
+            else:
+                inputs[0] = _sym.pad(data=inputs[0],
+                                     pad_width=((0, 0),
+                                                (0, 0),
+                                                (pad_v[0], pad_v[1]),
+                                                (pad_h[0], pad_h[1])))
+
+            attr['padding'] = [0, 0]
+
+        else:
+            raise TypeError("Unsupported padding type : {}".format(attr['padding']))
+
+        if 'kernel_layout' not in attr:
+            attr['kernel_layout'] = 'HWOI' if attr['data_format'] == 'NHWC' else 'OIHW'
+
+        return AttrCvt(
+            op_name=_dimension_picker('conv'),
+            transforms={
+                'kernel_shape': 'kernel_size',
+                'data_format': 'layout',
+                'dilations': ('dilation', (0, 0)),
+                'group': ('groups', 1)},
+            extras={'use_bias': len(inputs) == 3},
+            custom_check=_dimension_constraint())(inputs, attr)
+    return _impl
+
 def _decode_image():
     def _impl(inputs, attr, params):
         # Image decode wrapper: Expecting user to feed decoded input to next layer drop this layer.
@@ -358,9 +439,27 @@ def _impl(inputs, attr, params):
             op_name='batch_norm',
             transforms={'scale_after_normalization':'scale', 'variance_epsilon':'epsilon'},
             extras={'axis': 3}, # Fix axis
+            ignores=['data_format'],
             disables=['momentum'])(new_inputs, attr)
     return _impl
 
+def _relu6():
+    def _impl(inputs, attr, params):
+        return _sym.clip(inputs[0], a_min=0, a_max=6)
+    return _impl
+
+def _shape():
+    def _impl(inputs, attr, params):
+        input_shapes = attr['_input_shapes'][inputs[0]]
+
+        # Fix the -1 dimensions to 1
+        input_shapes[0] = [1 if x == -1 else x for x in input_shapes[0]]
+        params[attr['_node_name']] = tvm.nd.array(input_shapes[0])
+
+        return _sym.Variable(name=attr['_node_name'],
+                             shape=params[attr['_node_name']].shape)
+    return _impl
+
 # compatible operators that do NOT require any conversion.
 _identity_list = []
 
@@ -392,6 +491,10 @@ def _impl(inputs, attr, params):
     'Add'                               : _elemwise('add'),
     'Rsqrt'                             : _rsqrt(),
     'Squeeze'                           : _squeeze(),
+    'FusedBatchNorm'                    : _batch_norm(),
+    'Relu6'                             : _relu6(),
+    'DepthwiseConv2dNative'             : _depthwise_conv(),
+    'Shape'                             : _shape(),
 }
 
 
@@ -458,9 +561,13 @@ def from_tensorflow(self, graph):
                 self._num_input += 1
                 self._nodes[node.name] = _sym.Variable(name=node.name)
 
-                self._output_shapes[node.name] = \
-                     [tensor_util.TensorShapeProtoToList(shape) \
-                     for shape in self._parse_attr(node.attr)['_output_shapes']]
+                try:
+                    self._output_shapes[node.name] = \
+                         [tensor_util.TensorShapeProtoToList(shape) \
+                         for shape in self._parse_attr(node.attr)['_output_shapes']]
+                except KeyError:
+                    raise NotImplementedError( \
+                        "Please freeze the graph with add_shapes=True")
             elif node.op == "Const":
                 # Assuming first Const node as Graph Input node
                 if self._input_node == '':
@@ -476,17 +583,29 @@ def from_tensorflow(self, graph):
                         raise NotImplementedError( \
                             "Const {} couldn't be converted to Param.".format(node.name))
 
-                self._output_shapes[node.name] = \
-                     [tensor_util.TensorShapeProtoToList(shape) \
-                     for shape in self._parse_attr(node.attr)['_output_shapes']]
+                try:
+                    self._output_shapes[node.name] = \
+                         [tensor_util.TensorShapeProtoToList(shape) \
+                         for shape in self._parse_attr(node.attr)['_output_shapes']]
+                except KeyError:
+                    raise NotImplementedError( \
+                        "Please freeze the graph with add_shapes=True")
             else:
                 attr = self._parse_attr(node.attr)
-                self._output_shapes[node.name] = \
-                     [tensor_util.TensorShapeProtoToList(shape) for shape in attr['_output_shapes']]
+                try:
+                    self._output_shapes[node.name] = \
+                         [tensor_util.TensorShapeProtoToList(shape) \
+                          for shape in attr['_output_shapes']]
+                except KeyError:
+                    raise NotImplementedError( \
+                        "Please freeze the graph with add_shapes=True")
 
                 # Pass the parsed shapes instead
                 attr["_output_shapes"] = self._output_shapes[node.name]
 
+                # Pass the node name too in attr
+                attr["_node_name"] = node.name
+
                 try:
                     inputs = [self._nodes[i] for i in node.input]
                     input_shapes = {}
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index e4e5415a6ccf..2432cc84fc2c 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -84,6 +84,7 @@ def compute_conv2d(attrs, inputs, _):
     groups = attrs.get_int("groups")
     channels = attrs.get_int("channels")
     layout = attrs["layout"]
+    kernel_layout = attrs["kernel_layout"]
     assert layout == "NCHW" or layout == "NHWC"
     (dilation_h, dilation_w) = dilation
     if dilation_h < 1 or dilation_w < 1:
@@ -97,10 +98,18 @@ def compute_conv2d(attrs, inputs, _):
 
     if groups == 1:
         out = topi.nn.conv2d(inputs[0], kernel, strides, padding, layout)
-    elif groups == get_const_int(inputs[0].shape[1]) and groups == channels:
+    elif layout == "NCHW" and \
+         groups == get_const_int(inputs[0].shape[1]) and \
+         groups == channels:
         out = topi.nn.depthwise_conv2d_nchw(inputs[0], kernel, strides, padding)
+    elif layout == "NHWC" and \
+         kernel_layout == "HWOI" and \
+         groups == get_const_int(inputs[0].shape[3]) and \
+         groups == channels:
+        out = topi.nn.depthwise_conv2d_nhwc(inputs[0], kernel, strides, padding)
     else:
         raise ValueError("not support arbitrary group number for now")
+
     if attrs.get_bool("use_bias"):
         bias = inputs[2]
         expand_axis = 1 if layout == "NCHW" else 0
@@ -112,13 +121,20 @@ def compute_conv2d(attrs, inputs, _):
 def schedule_conv2d(attrs, outs, target):
     """Schedule definition of conv2d"""
     groups = attrs.get_int("groups")
+    channels = attrs.get_int("channels")
     layout = attrs["layout"]
+    kernel_layout = attrs["kernel_layout"]
     with tvm.target.create(target):
         if groups == 1 and layout == "NCHW":
             return topi.generic.schedule_conv2d_nchw(outs)
         elif groups == 1 and layout == "NHWC":
             return topi.generic.schedule_conv2d_nhwc(outs)
-        return topi.generic.schedule_depthwise_conv2d_nchw(outs)
+        elif groups == channels and layout == "NCHW":
+            return topi.generic.schedule_depthwise_conv2d_nchw(outs)
+        elif groups == channels and layout == "NHWC" and kernel_layout == "HWOI":
+            return topi.generic.schedule_depthwise_conv2d_nhwc(outs)
+        else:
+            raise ValueError("No compatible schedule")
 
 @reg.register_alter_op_layout("conv2d")
 def alter_conv2d_layout(attrs, inputs, tinfos):
diff --git a/nnvm/src/top/nn/convolution.cc b/nnvm/src/top/nn/convolution.cc
index 8b66f9757fe3..40c4d7a0e8c6 100644
--- a/nnvm/src/top/nn/convolution.cc
+++ b/nnvm/src/top/nn/convolution.cc
@@ -79,7 +79,8 @@ inline bool Conv2DInferShape(const nnvm::NodeAttrs& attrs,
                  param.kernel_size[1]});
 
   wshape = ConvertLayout(wshape, kOIHW, kernel_layout);
-  wshape[0] *= param.groups;
+
+  wshape[kernel_layout.indexof('O')] *= param.groups;
 
   NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, Conv2DParam::kWeight, wshape);
   if (param.use_bias) {
diff --git a/nnvm/tests/python/compiler/test_top_level2.py b/nnvm/tests/python/compiler/test_top_level2.py
index ed67dab44c47..8f4c330d2efc 100644
--- a/nnvm/tests/python/compiler/test_top_level2.py
+++ b/nnvm/tests/python/compiler/test_top_level2.py
@@ -58,7 +58,7 @@ def test_dilated_conv2d():
         np.testing.assert_allclose(out.asnumpy(), c_np, rtol=1e-5)
 
 
-def test_grouped_conv2d():
+def test_grouped_conv2d_nchw():
     x = sym.Variable("x")
     y = sym.conv2d(x, channels=32, kernel_size=(3,3), groups=32,
                    name="y", padding=(1,1))
@@ -80,6 +80,28 @@ def test_grouped_conv2d():
         c_np = c_np + bias.asnumpy().reshape(kshape[0], 1, 1)
         np.testing.assert_allclose(out.asnumpy(), c_np, rtol=1e-5)
 
+def test_grouped_conv2d_nhwc():
+    x = sym.Variable("x")
+    y = sym.conv2d(x, channels=32, kernel_size=(3,3), groups=32,
+                   name="y", padding=(1,1), layout="NHWC", kernel_layout ='HWOI')
+    dtype = "float32"
+    dshape = (1, 18, 18, 32)
+    kshape = (3, 3, 32, 1)
+    oshape = (1, 18, 18, 32)
+    shape_dict = {"x": dshape}
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
+        m = graph_runtime.create(graph, lib, ctx)
+        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+        kernel = tvm.nd.array(np.random.uniform(size=kshape).astype(dtype))
+        bias = tvm.nd.array(np.random.uniform(size=kshape[2]).astype(dtype))
+        m.run(x=data, y_weight=kernel, y_bias=bias)
+        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
+        c_np = topi.testing.depthwise_conv2d_python_nhwc(
+            data.asnumpy(), kernel.asnumpy(), (1,1), 'SAME')
+        c_np = c_np + bias.asnumpy().reshape(1, 1, kshape[2])
+        np.testing.assert_allclose(out.asnumpy(), c_np, rtol=1e-5)
+
 
 def test_conv2d_transpose():
     x = sym.Variable("x")
@@ -269,7 +291,8 @@ def test_resize_bilinear():
 if __name__ == "__main__":
     test_conv2d()
     test_dilated_conv2d()
-    test_grouped_conv2d()
+    test_grouped_conv2d_nchw()
+    test_grouped_conv2d_nhwc()
     test_conv2d_transpose()
     test_max_pool2d()
     test_avg_pool2d()

From 54f0f13c7fe5893520fa0115bd7587a47945daf8 Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Mon, 25 Jun 2018 21:54:16 +0530
Subject: [PATCH 757/949] Documentation error fixed (#1337)

Updated documentation error
---
 topi/python/topi/nn/pooling.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/topi/python/topi/nn/pooling.py b/topi/python/topi/nn/pooling.py
index 910b556a5c9d..0660ce7f1a3a 100644
--- a/topi/python/topi/nn/pooling.py
+++ b/topi/python/topi/nn/pooling.py
@@ -69,14 +69,14 @@ def pool(data,
     stride : list/tuple of two ints
         Stride size, [stride_height, stride_width]
 
-    paddding : list/tuple of two ints
+    padding : list/tuple of two ints
         Pad size, [pad_height, pad_width]
 
     pool_type : str
         Pool type, 'max' or 'avg'
 
     ceil_mode : bool
-        Whether to use ceil when caculate output size.
+        Whether to use ceil when calculating output size.
 
     layout: string
         Layout of the input data.

From f9d842773a8ccb7aab0cf069d4df92dcb0ba3680 Mon Sep 17 00:00:00 2001
From: Tao Lv <tao.a.lv@intel.com>
Date: Tue, 26 Jun 2018 00:57:37 +0800
Subject: [PATCH 758/949] Contrib: add mkl blas support (#1336)

---
 CMakeLists.txt                   |  1 +
 cmake/config.cmake               |  7 ++++++-
 cmake/modules/contrib/BLAS.cmake | 10 ++++++++++
 src/contrib/cblas/cblas.cc       |  4 ++++
 4 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index e9699b1f6736..028c16aa5040 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -42,6 +42,7 @@ tvm_option(INSTALL_DEV "Install compiler infrastructure" OFF)
 
 # Contrib library options
 tvm_option(USE_BLAS "The blas library to be linked" none)
+tvm_option(USE_MKL_PATH "MKL root path when use MKL blas" none)
 tvm_option(USE_CUDNN "Build with cuDNN" OFF)
 tvm_option(USE_CUBLAS "Build with cuBLAS" OFF)
 tvm_option(USE_MIOPEN "Build with ROCM:MIOpen" OFF)
diff --git a/cmake/config.cmake b/cmake/config.cmake
index 1f02bfd7d153..d2576ab30e1e 100644
--- a/cmake/config.cmake
+++ b/cmake/config.cmake
@@ -77,9 +77,14 @@ set(USE_LLVM OFF)
 #---------------------------------------------
 # Contrib libraries
 #---------------------------------------------
-# Whether use BLAS, choices: openblas, atlas, blas, apple
+# Whether use BLAS, choices: openblas, mkl, atlas, apple
 set(USE_BLAS none)
 
+# /path/to/mkl: mkl root path when use mkl blas library
+# set(USE_MKL_PATH /opt/intel/mkl) for UNIX
+# set(USE_MKL_PATH ../IntelSWTools/compilers_and_libraries_2018/windows/mkl) for WIN32
+set(USE_MKL_PATH none)
+
 # Whether use contrib.random in runtime
 set(USE_RANDOM OFF)
 
diff --git a/cmake/modules/contrib/BLAS.cmake b/cmake/modules/contrib/BLAS.cmake
index 423cb9cd5ab3..45269a20715d 100644
--- a/cmake/modules/contrib/BLAS.cmake
+++ b/cmake/modules/contrib/BLAS.cmake
@@ -6,6 +6,16 @@ if(USE_BLAS STREQUAL "openblas")
   list(APPEND TVM_RUNTIME_LINKER_LIBS ${BLAS_LIBRARY})
   list(APPEND RUNTIME_SRCS ${CBLAS_CONTRIB_SRC})
   message(STATUS "Use BLAS library " ${BLAS_LIBRARY})
+elseif(USE_BLAS STREQUAL "mkl")
+  if(NOT IS_DIRECTORY ${USE_MKL_PATH})
+    set(USE_MKL_PATH /opt/intel/mkl)
+  endif()
+  find_library(BLAS_LIBRARY mkl_rt ${USE_MKL_PATH}/lib/ ${USE_MKL_PATH}/lib/intel64)
+  include_directories(${USE_MKL_PATH}/include)
+  list(APPEND TVM_RUNTIME_LINKER_LIBS ${BLAS_LIBRARY})
+  list(APPEND RUNTIME_SRCS ${CBLAS_CONTRIB_SRC})
+  add_definitions(-DUSE_MKL_BLAS=1)
+  message(STATUS "Use BLAS library " ${BLAS_LIBRARY})
 elseif(USE_BLAS STREQUAL "atlas" OR USE_BLAS STREQUAL "blas")
   find_library(BLAS_LIBRARY cblas)
   list(APPEND TVM_RUNTIME_LINKER_LIBS ${BLAS_LIBRARY})
diff --git a/src/contrib/cblas/cblas.cc b/src/contrib/cblas/cblas.cc
index c2e981ba5d89..24ed9deb97cd 100644
--- a/src/contrib/cblas/cblas.cc
+++ b/src/contrib/cblas/cblas.cc
@@ -7,7 +7,11 @@
 #include <dmlc/logging.h>
 
 extern "C" {
+#if USE_MKL_BLAS == 1
+#include <mkl_cblas.h>
+#else
 #include <cblas.h>
+#endif
 }
 
 namespace tvm {

From f721a645cd77661a8284559d676ef20f91eaa771 Mon Sep 17 00:00:00 2001
From: Jian Weng <werefluke@gmail.com>
Date: Mon, 25 Jun 2018 16:08:42 -0700
Subject: [PATCH 759/949] [FRONTEND] [HYBRID] Non-zero starting supported;
 Buffer AttrStmt add! (#1330)

---
 python/tvm/hybrid/intrin.py                 |   4 +-
 python/tvm/hybrid/parser.py                 | 101 +++++++----
 python/tvm/hybrid/util.py                   |  10 --
 python/tvm/hybrid/var_decl.py               |   4 +-
 tests/python/unittest/test_hybrid_script.py | 175 ++++++++++++++++----
 5 files changed, 219 insertions(+), 75 deletions(-)

diff --git a/python/tvm/hybrid/intrin.py b/python/tvm/hybrid/intrin.py
index 93517fef4d1d..b3fb64579b60 100644
--- a/python/tvm/hybrid/intrin.py
+++ b/python/tvm/hybrid/intrin.py
@@ -29,7 +29,7 @@ def __init__(self, tag, ext):
 unroll = vectorize = parallel = _range #pylint: disable=invalid-name
 
 
-def allocate(shape, dtype='float32'):
+def allocate(shape, dtype='float32', scope='global'): #pylint: disable=unused-argument
     """Allocate a buffer with given shape
 
     Parameters
@@ -38,6 +38,8 @@ def allocate(shape, dtype='float32'):
         The shape of the tensor to be allocated
     dtype: string
         The data type of the tensor
+    scope: string
+        The storage scope of the tensor
 
     Returns
     -------
diff --git a/python/tvm/hybrid/parser.py b/python/tvm/hybrid/parser.py
index b3f076094a12..1e532367a321 100644
--- a/python/tvm/hybrid/parser.py
+++ b/python/tvm/hybrid/parser.py
@@ -3,7 +3,7 @@
 import ast
 import operator
 import sys
-from .util import make_nop, make_const_true, make_range_one, halide_imm_types
+from .util import make_nop, halide_imm_types
 from .intrin import LOOP_INTRIN, MATH_INTRIN
 from .var_decl import determine_variable_usage
 from ..api import thread_axis
@@ -75,7 +75,8 @@ def __init__(self, args, usage, func_name=None):
         self.args = args[:]
         self.usage = usage.copy()
         self._args = {} # Dict maps arg name to actual arg instance (either a var or a buffer)
-        self.buffers = {}
+        self.var_buffers = {} # Buffers formed by mutatble variables
+        self.alloc_buffers = {} # Buffers formed by allocate instructions
         self.loops_above = {} # State variable that indicates loop levels above the current node
         self.var_consts = {} # Variables that are determined as readonly in previous stage
         self.func_name = func_name # The name of the function to be lowered
@@ -87,19 +88,30 @@ def wrap_up_realize(self, node, body):
         for key, val in self.usage.items():
             if key in self.var_consts.keys():
                 continue
-            _, scope, _ = val
-            if scope == node:
-                _buf = self.buffers[key]
+            _, level, _ = val
+            if level == node:
+                if key in self.var_buffers.keys():
+                    _buf = self.var_buffers[key]
+                    _scope = 'global'
+                else:
+                    _buf, _scope = self.alloc_buffers[key]
+                _domain = [_make.range_by_min_extent(0, i) for i in _buf.shape]
                 _dtype = _buf.dtype
-                _one = make_range_one()
-                _true = make_const_true()
-                body = _make.Realize(_buf.op, 0, _dtype, [_one], _true, body)
+                _true = _api.convert(True)
+                body = _make.Realize(_buf.op, 0, _dtype, _domain, _true, body)
+                body = _make.AttrStmt(_buf.op, 'realize_scope', _api.convert(_scope), body)
         return body
 
 
-    def _check_id_a_buffer(self, s):
-        if s not in self._args.keys():
+    def _get_buffer_from_id(self, s):
+        if s not in self._args.keys() and s not in self.alloc_buffers.keys():
             raise ValueError("This %s is expected to be in argument list or allocated buffer!" % s)
+        if s in self._args.keys() and s in self.alloc_buffers.keys():
+            raise ValueError("%s, a buffer cannot be both argument and allocated!" % s)
+        if s in self._args.keys():
+            return self._args[s]
+        return self.alloc_buffers[s][0]
+
 
 
     #pylint: disable=invalid-name, missing-docstring
@@ -138,8 +150,8 @@ def visit_Name(self, node):
         if _id  not in self.usage.keys():
             raise ValueError("This id %s is expected to be a defined variable!" % _id)
         # Buffer
-        if _id in self.buffers.keys():
-            _buf = self.buffers[_id]
+        if _id in self.var_buffers.keys():
+            _buf = self.var_buffers[_id]
             return _make.Call(_buf.dtype, _id, [_api.const(0)], _expr.Call.Halide, _buf.op, 0)
         # Compilation time constant
         if _id not in self.var_consts.keys():
@@ -155,7 +167,9 @@ def visit_Assign(self, node):
         if len(node.targets) != 1:
             raise ValueError("So far only one-valued assignment is supported!")
         lhs = node.targets[0]
-        rhs = _ir_pass.Simplify(self.visit(node.value))
+        rhs = self.visit(node.value)
+        if isinstance(rhs, _expr.Expr):
+            rhs = _ir_pass.Simplify(rhs)
         if isinstance(lhs, ast.Name):
             #TODO: support defined intermediate buffer later
             lhs_ = lhs
@@ -166,25 +180,31 @@ def visit_Assign(self, node):
             if decl == lhs_:
                 if lhs in self.var_consts.keys():
                     raise ValueError("BUG: A constant cannot be overwritten!")
-                if lhs in self.buffers.keys():
+                if lhs in self.var_buffers.keys() or lhs in self.alloc_buffers.keys():
                     raise ValueError("BUG: This value should not be defined before this point!")
+                if isinstance(rhs, tuple):
+                    shape, dtype, scope = rhs
+                    ph = _api.placeholder(shape, dtype=dtype, name=lhs)
+                    self.alloc_buffers[lhs] = (ph, scope)
+                    return make_nop()
                 if isinstance(rhs, halide_imm_types) and ast.Store not in rw:
                     self.var_consts[lhs] = rhs
                 else:
-                    self.buffers[lhs] = _api.placeholder((1, ), dtype=rhs.dtype, name=lhs)
+                    self.var_buffers[lhs] = _api.placeholder((1, ), dtype=rhs.dtype, name=lhs)
             if lhs in self.var_consts.keys():
                 return make_nop()
             else:
-                if lhs not in self.buffers.keys():
-                    raise ValueError("BUG: This value should be defined before!")
-                return _make.Provide(self.buffers[lhs].op, 0, rhs, [_api.const(0, dtype=rhs.dtype)])
+                if lhs not in self.var_buffers.keys():
+                    raise ValueError("BUG: This variable should be defined before!")
+                tgt = self.var_buffers[lhs]
+                return _make.Provide(tgt.op, 0, rhs, [_api.const(0, dtype=rhs.dtype)])
         else:
             lhs = self.visit(lhs)
             if not isinstance(lhs, _expr.Call):
                 raise ValueError("An array access's LHS is expected to be a expr.Call!")
             #TODO: support slice later
-            self._check_id_a_buffer(lhs.name)
-            return _make.Provide(self._args[lhs.name].op, 0, rhs, lhs.args)
+            buf = self._get_buffer_from_id(lhs.name)
+            return _make.Provide(buf.op, 0, rhs, lhs.args)
 
 
     def visit_Index(self, node):
@@ -197,8 +217,7 @@ def visit_Subscript(self, node):
         args = self.visit(node.slice)
         if isinstance(node.value, ast.Name):
             array = node.value.id
-            self._check_id_a_buffer(array)
-            _buf = self._args[array]
+            _buf = self._get_buffer_from_id(array)
             return _make.Call(_buf.dtype, array, args, _expr.Call.Halide, _buf.op, 0)
         elif isinstance(node.value, ast.Attribute):
             if not isinstance(node.value.value, ast.Name):
@@ -211,8 +230,8 @@ def visit_Subscript(self, node):
             #TODO: maybe support non-constant value later?
             if not isinstance(args, (_expr.IntImm, _expr.UIntImm)):
                 raise ValueError("So far only constant shape access supported!")
-            self._check_id_a_buffer(node.value.value.id)
-            return self._args[node.value.value.id].shape[args.value]
+            buf = self._get_buffer_from_id(node.value.value.id)
+            return buf.shape[args.value]
         else:
             raise ValueError("Not supported yet!")
 
@@ -303,8 +322,30 @@ def visit_Call(self, node):
         elif func_id in MATH_INTRIN:
             return getattr(intrin, func_id)(*[self.visit(arg) for arg in node.args])
         elif func_id == 'allocate':
-            #TODO: Support it later!
-            return make_nop()
+            if not isinstance(node.args[0], ast.Tuple):
+                raise ValueError("allocate's first argument should be a tuple of shape!")
+            shape = tuple(self.visit(i) for i in node.args[0].elts)
+            for i in shape:
+                if not isinstance(i, _expr.Expr):
+                    raise ValueError("The shape should be an expression")
+            if n > 1:
+                if not isinstance(node.args[1], ast.Str):
+                    raise ValueError("The data type should be an string")
+                dtype = node.args[1].s
+            else:
+                dtype = 'float32'
+            if n > 2:
+                if not isinstance(node.args[2], ast.Str):
+                    raise ValueError("The data type should be an string")
+                scope = node.args[2].s
+            else:
+                scope = 'global'
+            return (shape, dtype, scope)
+        elif func_id == 'max' or func_id == 'min':
+            if n != 2:
+                raise ValueError("Max/Min function should have 2 elements")
+            a, b = self.visit(node.args[0]), self.visit(node.args[1])
+            return getattr(_make, func_id.title())(a, b)
         else:
             raise ValueError("Function call not supported yet!")
 
@@ -317,8 +358,10 @@ def visit_For(self, node):
         if iter_var is None:
             if for_type is None:
                 raise ValueError("The loop bind function parse error!")
-            iter_var = _api.var(_name)
-            self.loops_above[_name] = iter_var
+            offset = iter_var = _api.var(_name)
+            if not _ir_pass.Equal(low, _api.const(0, dtype='int32')):
+                offset = iter_var + low
+            self.loops_above[_name] = offset
         else:
             if for_type is not None:
                 raise ValueError("The loop iterating function parse error!")
@@ -328,7 +371,7 @@ def visit_For(self, node):
         if for_type is None:
             res = _make.AttrStmt(iter_var, 'thread_extent', ext, _body)
         else:
-            res = _make.For(iter_var, low, ext, for_type, 0, _body)
+            res = _make.For(iter_var, _api.const(0, dtype='int32'), ext, for_type, 0, _body)
         self.loops_above.pop(_name)
         return res
 
diff --git a/python/tvm/hybrid/util.py b/python/tvm/hybrid/util.py
index 8a5f4a62768d..43d26e859560 100644
--- a/python/tvm/hybrid/util.py
+++ b/python/tvm/hybrid/util.py
@@ -22,16 +22,6 @@ def make_nop():
     return _make.Evaluate(_api.const(0, dtype='int32'))
 
 
-def make_range_one():
-    """Returns a [0, 1] range node in HalideIR."""
-    return _make.range_by_min_extent(0, 1)
-
-
-def make_const_true():
-    """Returns a constant True node in HalideIR."""
-    return _api.convert(True)
-
-
 def _pruned_source(func):
     """Prune source code's extra leading spaces"""
     lines = inspect.getsource(func).split('\n')
diff --git a/python/tvm/hybrid/var_decl.py b/python/tvm/hybrid/var_decl.py
index 940b8c088df3..df38bac1acba 100644
--- a/python/tvm/hybrid/var_decl.py
+++ b/python/tvm/hybrid/var_decl.py
@@ -41,7 +41,8 @@ def visit_Call(self, node):
         #No function pointer supported so far
         if not isinstance(node.func, ast.Name):
             raise ValueError("Function call should be an id")
-        if (node.func.id not in HYBRID_GLOBALS.keys()) and node.func.id != 'range':
+        func_id = node.func.id
+        if func_id not in list(HYBRID_GLOBALS.keys()) + ['range', 'max', 'min']:
             raise ValueError("Function call id not in intrinsics' list")
         for elem in node.args:
             self.visit(elem)
@@ -64,7 +65,6 @@ def visit_Name(self, node):
             self.status[node.id] = (node, self.scope_level[-1], set())
         else:
             decl, loop, usage = self.status[node.id]
-            loop = self.scope_level[-1]
             usage.add(type(node.ctx))
             self.status[node.id] = (decl, loop, usage)
 
diff --git a/tests/python/unittest/test_hybrid_script.py b/tests/python/unittest/test_hybrid_script.py
index fda4f52c1f19..06f24f2adaf5 100644
--- a/tests/python/unittest/test_hybrid_script.py
+++ b/tests/python/unittest/test_hybrid_script.py
@@ -2,6 +2,7 @@
 from tvm.hybrid import script
 from tvm.hybrid.intrin import HYBRID_GLOBALS
 
+
 @script
 def outer_product(n, m, a, b, c):
     for i in range(n):
@@ -56,6 +57,7 @@ def test_outer_product():
     tvm_c = tvm.ndarray.array(numpy.zeros((_n, _m), dtype='float32'))
     func(_n, _m, tvm_a, tvm_b, tvm_c)
     numpy.testing.assert_allclose(tvm_c.asnumpy(), c_python, rtol=1e-5)
+
     for key, _ in HYBRID_GLOBALS.items():
         assert key not in globals().keys()
         assert key not in outer_product.__globals__.keys()
@@ -74,8 +76,8 @@ def fanout(n, a, b):
             b[i] = sigma
 
     n = tvm.var('n')
-    a = tvm.placeholder((n, ), name='a')
-    b = tvm.placeholder((n-3, ), name='b')
+    a = tvm.placeholder((n, ), 'float32', name='a')
+    b = tvm.placeholder((n-3, ), 'float32', name='b')
     ir = fanout(n, a, b)
 
     #Check for i in (0, n-3)
@@ -85,12 +87,14 @@ def fanout(n, a, b):
     assert tvm.ir_pass.Equal(ir.extent, n - 3)
     #Check loopbody
     ibody = ir.body
-    assert isinstance(ibody, tvm.stmt.Realize)
-    assert ibody.bounds[0].min.value == 0
-    assert ibody.bounds[0].extent.value == 1
-    assert ibody.func.name == 'sigma'
+    assert isinstance(ibody, tvm.stmt.AttrStmt)
+    abody = ibody.body
+    assert isinstance(abody, tvm.stmt.Realize)
+    assert abody.bounds[0].min.value == 0
+    assert abody.bounds[0].extent.value == 1
+    assert abody.func.name == 'sigma'
     #Check i loop body
-    rbody = ibody.body
+    rbody = abody.body
     assert isinstance(rbody.first, tvm.stmt.Provide)
     assert rbody.first.func.name == 'sigma'
     assert len(rbody.first.args) == 1
@@ -131,6 +135,21 @@ def fanout(n, a, b):
     assert len(write.value.args) == 1
     assert write.value.args[0].value == 0
 
+    func = tvm.build(tvm.lower(ir, [n, a, b]))
+    assert func
+
+    np_a = numpy.random.randn(10).astype('float32')
+    np_b = numpy.zeros(7).astype('float32')
+
+    nd_a = tvm.ndarray.array(np_a)
+    nd_b = tvm.ndarray.array(np_b)
+
+    fanout(10, np_a, np_b)
+    func(10, nd_a, nd_b)
+
+    numpy.testing.assert_allclose(nd_b.asnumpy(), np_b, rtol=1e-5, atol=1e-5)
+
+
 @script
 def failure():
     for i in range(1, 100):
@@ -148,15 +167,18 @@ def test_failure():
 
 def test_looptype():
     @script
-    def looptype(a):
-        for i in parallel(6):
+    def looptype(a, b, c):
+        for i in parallel(8):
             a[i] = i
-        for j in vectorize(6):
-            a[j] = j
-        for k in unroll(6):
-            a[k] = k
-    a = tvm.placeholder((6, ), name='a')
-    ir = looptype(a)
+        for j in vectorize(8):
+            b[j] = j
+        for k in unroll(8):
+            c[k] = k
+
+    a = tvm.placeholder((8, ), name='a', dtype='int32')
+    b = tvm.placeholder((8, ), name='b', dtype='int32')
+    c = tvm.placeholder((8, ), name='c', dtype='int32')
+    ir = looptype(a, b, c)
     iloop = ir.first
     jloop = ir.rest.first
     kloop = ir.rest.rest
@@ -164,6 +186,24 @@ def looptype(a):
     assert jloop.for_type == tvm.stmt.For.Vectorized
     assert kloop.for_type == tvm.stmt.For.Unrolled
 
+    func = tvm.build(tvm.lower(ir, [a, b, c]))
+
+    np_a = numpy.zeros((8, )).astype('int32')
+    np_b = numpy.zeros((8, )).astype('int32')
+    np_c = numpy.zeros((8, )).astype('int32')
+
+    nd_a = tvm.ndarray.array(np_a)
+    nd_b = tvm.ndarray.array(np_b)
+    nd_c = tvm.ndarray.array(np_c)
+
+    looptype(np_a, np_b, np_c)
+    func(nd_a, nd_b, nd_c)
+
+    numpy.testing.assert_allclose(np_a, nd_a.asnumpy())
+    numpy.testing.assert_allclose(np_b, nd_b.asnumpy())
+    numpy.testing.assert_allclose(np_c, nd_c.asnumpy())
+
+
 def test_if():
     @script
     def if_then_else(a, b):
@@ -234,12 +274,14 @@ def intrin_real(a):
         a[3] = sigmoid(a[3])
         a[4] = power(a[4], a[5])
         a[5] = tanh(a[5])
+        a[6] = min(a[4], a[5])
+        a[7] = max(a[5], a[6])
 
-    a6 = tvm.placeholder((6, ), dtype='float32', name='a')
+    a6 = tvm.placeholder((8, ), dtype='float32', name='a')
     ir = intrin_real(a6)
     func = tvm.build(tvm.lower(ir, [a6]))
     assert func
-    a = numpy.arange(2, 8).astype('float32')
+    a = numpy.arange(2, 10).astype('float32')
     tvm_a = tvm.ndarray.array(a)
     func(tvm_a)
     intrin_real(a)
@@ -259,22 +301,87 @@ def intrin_int(a):
     func(tvm_a)
     assert tvm_a.asnumpy()[0] == a[0]
 
-def test_allocate_buffer():
-    def blur(a):
-        for i in serail(32):
-            h_blur = allocate((4, 36))
-            for j in serail(4):
-                for k in serail(36):
-                    s = allocate((1, ), 'float32')
-                    for dj in serail(4):
-                        s[0] = s[0] + a[i, j + dj]
-                    h_blur[j, k] = s[0] / 4.
-            for j in serail(32):
-                s = 0.
-                for di in serail(4):
-                    s = s + h_blur[di, j]
-                h_blur[i, j] = s / 4.
-                                
+def test_non_zero():
+    @tvm.hybrid.script
+    def blur(a, b):
+        for i in range(2, 32):
+            for j in range(2, 32):
+                s = 0.0
+                for di in range(3):
+                    for dj in range(3):
+                        s = s + a[i-di, j-dj]
+                b[i-2, j-2] = s / 9.0
+    try:
+        np_a = numpy.random.randn(32, 32).astype('float32')
+        np_b = numpy.zeros((30, 30), dtype='float32')
+        blur(np_a, np_b)
+
+        ph_a = tvm.placeholder((32, 32), 'float32', 'a')
+        ph_b = tvm.placeholder((30, 30), 'float32', 'b')
+        ir = tvm.hybrid.parse(blur, [ph_a, ph_b])
+        func = tvm.lower(ir, [ph_a, ph_b])
+        func = tvm.build(func)
+
+        nd_a = tvm.ndarray.array(np_a)
+        nd_b = tvm.ndarray.array(np_b)
+        func(nd_a, nd_b)
+
+        numpy.testing.assert_allclose(np_b, nd_b.asnumpy(), atol=1e-5, rtol=1e-5)
+    except IOError:
+        print('[Warning] Non-zero first test skipped by Python2')
+
+    @tvm.hybrid.script
+    def triangle(a, b, c):
+        for i in range(10):
+            for j in range(i, 10):
+                c[i, j] = a[i] * b[j]
+
+    a = tvm.placeholder((10, ), dtype='float32', name='a')
+    b = tvm.placeholder((10, ), dtype='float32', name='b')
+    c = tvm.placeholder((10, 10), dtype='float32', name='c')
+
+    np_a = numpy.random.randn(10).astype('float32')
+    np_b = numpy.random.randn(10).astype('float32')
+    np_c = numpy.zeros((10, 10)).astype('float32')
+
+    nd_a = tvm.ndarray.array(np_a)
+    nd_b = tvm.ndarray.array(np_b)
+    nd_c = tvm.ndarray.array(np_c)
+
+    triangle(np_a, np_b, np_c)
+
+    func = tvm.build(tvm.lower(triangle(a, b, c), [a, b, c]))
+    assert func
+    func(nd_a, nd_b, nd_c)
+    numpy.testing.assert_allclose(nd_c.asnumpy(), np_c)
+
+def test_allocate():
+    @tvm.hybrid.script
+    def blur2d(a, b):
+        for i in range(30):
+            ha = allocate((3, 30), 'float32')
+            for j in range(3):
+                for k in range(30):
+                    ha[j, k] = a[i+j, k] + a[i+j, k+1] + a[i+j, k+2]
+            for j in range(30):
+                b[i, j] = (ha[0, j] + ha[1, j] + ha[2, j]) / 9.0
+
+    a = tvm.placeholder((32, 32), 'float32', 'a')
+    b = tvm.placeholder((30, 30), 'float32', 'b')
+
+    func = tvm.build(tvm.lower(blur2d(a, b), [a, b]))
+    assert func
+
+    np_a = numpy.random.randn(32, 32).astype('float32')
+    np_b = numpy.zeros((30, 30)).astype('float32')
+
+    nd_a = tvm.ndarray.array(np_a)
+    nd_b = tvm.ndarray.array(np_b)
+
+    func(nd_a, nd_b)
+    blur2d(np_a, np_b)
+
+    numpy.testing.assert_allclose(nd_b.asnumpy(), np_b, atol=1e-5, rtol=1e-5)
 
 if __name__ == "__main__":
     test_outer_product()
@@ -284,4 +391,6 @@ def blur(a):
     test_if()
     test_bind()
     test_math_intrin()
+    test_non_zero()
+    test_allocate()
 

From 8ab7e0e5f23be45e396454e778d4622f3d8c87c3 Mon Sep 17 00:00:00 2001
From: nhynes <nhynes@berkeley.edu>
Date: Mon, 25 Jun 2018 20:41:01 -0700
Subject: [PATCH 760/949] [NNVM][TOPI] Add FTVMCompute for matmul (#1239)

---
 nnvm/python/nnvm/top/nn.py       |  3 +++
 nnvm/src/top/tensor/matrix_op.cc | 13 +++++++++++++
 topi/include/topi/nn.h           |  6 +++---
 topi/include/topi/tags.h         |  2 +-
 4 files changed, 20 insertions(+), 4 deletions(-)

diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index 2432cc84fc2c..78253bc5bc6b 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -73,6 +73,9 @@ def schedule_dense(_, outs, target):
 
 reg.register_pattern("dense", OpPattern.OUT_ELEMWISE_FUSABLE)
 
+#matmul
+reg.register_pattern("matmul", OpPattern.OUT_ELEMWISE_FUSABLE)
+reg.register_schedule("matmul", _fschedule_injective)
 
 # conv2d
 @reg.register_compute("conv2d")
diff --git a/nnvm/src/top/tensor/matrix_op.cc b/nnvm/src/top/tensor/matrix_op.cc
index d28097b1090d..c881e683a6c5 100644
--- a/nnvm/src/top/tensor/matrix_op.cc
+++ b/nnvm/src/top/tensor/matrix_op.cc
@@ -3,9 +3,11 @@
  * \file matrix_op.cc
  * \brief Matrix operators
  */
+#include <topi/nn.h>
 #include <nnvm/op.h>
 #include <nnvm/node.h>
 #include <nnvm/op_attr_types.h>
+#include <nnvm/compiler/op_attr_types.h>
 #include <nnvm/top/tensor.h>
 #include "../op_common.h"
 #include "../elemwise_op_common.h"
@@ -13,6 +15,8 @@
 namespace nnvm {
 namespace top {
 
+using namespace nnvm::compiler;
+
 DMLC_REGISTER_PARAMETER(MatMulParam);
 
 inline bool DotShape(const nnvm::NodeAttrs& attrs,
@@ -93,6 +97,15 @@ NNVM_REGISTER_OP(matmul)
 .set_attr<FInferShape>("FInferShape", DotShape)
 .set_attr<FInferType>("FInferType", ElemwiseType<2, 1>)
 .set_attr<FCorrectLayout>("FCorrectLayout", DotCorrectLayout)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const MatMulParam& param = nnvm::get<MatMulParam>(attrs.parsed);
+    return Array<Tensor>{
+      topi::matmul(inputs[0], inputs[1], param.transpose_a, param.transpose_b)
+    };
+  })
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds) {
diff --git a/topi/include/topi/nn.h b/topi/include/topi/nn.h
index 2459eb515707..ee3101c4cc18 100644
--- a/topi/include/topi/nn.h
+++ b/topi/include/topi/nn.h
@@ -214,14 +214,14 @@ inline tvm::Tensor pad(const tvm::Tensor& t,
  * \param name The name of the operation
  * \param tag The tag to mark the operation
  *
- * \return A Tensor whose op member is the matmult operation
+ * \return A Tensor whose op member is the matmul operation
  */
-inline tvm::Tensor matmult(const tvm::Tensor& A,
+inline tvm::Tensor matmul(const tvm::Tensor& A,
                            const tvm::Tensor& B,
                            bool trans_a = false,
                            bool trans_b = false,
                            std::string name = "tensor",
-                           std::string tag = kMatMult) {
+                           std::string tag = kMatMul) {
   tvm::Array<tvm::Expr> output_shape{A->shape[trans_a ? 1 : 0],
                                      B->shape[trans_b ? 0 : 1]};
   auto k = tvm::reduce_axis(tvm::Range{0, A->shape[trans_a ? 0 : 1]}, "k");
diff --git a/topi/include/topi/tags.h b/topi/include/topi/tags.h
index 8ba9955be050..8c92644d96d3 100644
--- a/topi/include/topi/tags.h
+++ b/topi/include/topi/tags.h
@@ -15,7 +15,7 @@ constexpr auto kInjective = "injective";
 constexpr auto kCommReduce = "comm_reduce";
 constexpr auto kCommReduceIdx = "comm_reduce_idx";
 constexpr auto kBroadcast = "broadcast";
-constexpr auto kMatMult = "matmult";
+constexpr auto kMatMul = "matmul";
 constexpr auto kConv2dNCHW = "conv2d_nchw";
 constexpr auto kConv2dHWCN = "conv2d_hwcn";
 constexpr auto kDepthwiseConv2dNCHW = "depthwise_conv2d_nchw";

From 60d669b115e0c4bc72a2df390b8564658bfc5873 Mon Sep 17 00:00:00 2001
From: Wenhao Hu <fumihwh@gmail.com>
Date: Wed, 27 Jun 2018 01:03:52 +0900
Subject: [PATCH 761/949] support t attr in onnx (#1300)

---
 nnvm/python/nnvm/frontend/onnx.py | 32 ++++++++++++++++++++++---------
 1 file changed, 23 insertions(+), 9 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/onnx.py b/nnvm/python/nnvm/frontend/onnx.py
index 129c5089caee..d92a856d7231 100644
--- a/nnvm/python/nnvm/frontend/onnx.py
+++ b/nnvm/python/nnvm/frontend/onnx.py
@@ -571,13 +571,20 @@ def from_onnx(self, graph, opset):
             op_name = node.op_type
             attr = self._parse_attr(node.attribute)
             inputs = [self._nodes[self._renames.get(i, i)] for i in node.input]
-            op = self._convert_operator(op_name, inputs, attr, opset)
-            node_output = self._fix_outputs(op_name, node.output)
-            assert len(node_output) == len(op.list_output_names()), (
-                "Number of output mismatch {} vs {} in {}.".format(
-                    len(node_output), len(op.list_output_names()), op_name))
-            for k, i in zip(list(node_output), range(len(node_output))):
-                self._nodes[k] = op[i]
+            if op_name == "Constant":
+                t_proto = self._parse_attr(node.attribute)["value"]
+                self._num_param += 1
+                self._params[node.output[0]] = self._parse_array(t_proto)
+                self._nodes[node.output[0]] = _sym.Variable(name=node.output[0],
+                                                            shape=list(t_proto.dims))
+            else:
+                op = self._convert_operator(op_name, inputs, attr, opset)
+                node_output = self._fix_outputs(op_name, node.output)
+                assert len(node_output) == len(op.list_output_names()), (
+                    "Number of output mismatch {} vs {} in {}.".format(
+                        len(node_output), len(op.list_output_names()), op_name))
+                for k, i in zip(list(node_output), range(len(node_output))):
+                    self._nodes[k] = op[i]
         # now return the outputs
         out = [self._nodes[self._parse_value_proto(i)] for i in graph.output]
         if len(out) > 1:
@@ -615,11 +622,18 @@ def _parse_attr(self, attr_proto):
                 if list(getattr(a, f)):
                     assert a.name not in attrs, "Only one type of attr is allowed"
                     attrs[a.name] = tuple(getattr(a, f))
-            for f in ['t', 'g']:
+            for f in ['t']:
+                if a.HasField(f):
+                    attrs[a.name] = getattr(a, f)
+            for f in ['tensors']:
+                if list(getattr(a, f)):
+                    assert a.name not in attrs, "Only one type of attr is allowed"
+                    attrs[a.name] = tuple(getattr(a, f))
+            for f in ['g']:
                 if a.HasField(f):
                     raise NotImplementedError(
                         "Filed {} is not supported in nnvm.".format(f))
-            for f in ['tensors', 'graphs']:
+            for f in ['graphs']:
                 if list(getattr(a, f)):
                     raise NotImplementedError(
                         "Filed {} is not supported in nnvm.".format(f))

From 9c64ab291c116b5cd8b8cb4b601cc8a578417178 Mon Sep 17 00:00:00 2001
From: MORITA Kazutaka <morita.kazutaka@lab.ntt.co.jp>
Date: Wed, 27 Jun 2018 01:07:36 +0900
Subject: [PATCH 762/949] Add support for Xilinx FPGA board with SDAccel
 (#1278)

---
 docs/deploy/aws_fpga.md                     | 153 ++++++++++++++++++++
 python/tvm/__init__.py                      |   2 +-
 python/tvm/_ffi/runtime_ctypes.py           |   1 +
 python/tvm/contrib/sdaccel.py               | 108 ++++++++++++++
 src/codegen/build_module.cc                 |   3 +
 src/codegen/codegen_c.cc                    |   1 +
 src/codegen/codegen_c.h                     |   5 +
 src/codegen/codegen_opencl.cc               |   2 +-
 src/codegen/codegen_vhls.cc                 | 103 +++++++++++++
 src/codegen/codegen_vhls.h                  |  28 ++++
 src/codegen/intrin_rule_vhls.cc             |  33 +++++
 src/codegen/opt/build_opencl_off.cc         |   3 +-
 src/pass/split_host_device.cc               |   2 +-
 src/runtime/module.cc                       |   2 +-
 src/runtime/opencl/opencl_device_api.cc     |  20 +--
 src/runtime/opencl/opencl_module.cc         |  37 ++++-
 src/runtime/opencl/opencl_module.h          |   3 +-
 tests/python/integration/test_ewise_fpga.py |  46 ++++++
 18 files changed, 530 insertions(+), 22 deletions(-)
 create mode 100644 docs/deploy/aws_fpga.md
 create mode 100644 python/tvm/contrib/sdaccel.py
 create mode 100644 src/codegen/codegen_vhls.cc
 create mode 100644 src/codegen/codegen_vhls.h
 create mode 100644 src/codegen/intrin_rule_vhls.cc
 create mode 100644 tests/python/integration/test_ewise_fpga.py

diff --git a/docs/deploy/aws_fpga.md b/docs/deploy/aws_fpga.md
new file mode 100644
index 000000000000..a3b3cda64889
--- /dev/null
+++ b/docs/deploy/aws_fpga.md
@@ -0,0 +1,153 @@
+HLS Backend Example
+===================
+
+TVM supports Xilinx FPGA board with SDAccel.  Here is a tutorial for how to deploy TVM to AWS F1 FPGA instance.
+
+***Note***: This feature is still experimental.  We cannot use SDAccel to deploy an end to end neural networks for now.
+
+We use two python scripts for this tutorial.
+
+- build.py - a script to synthesize FPGA bitstream.
+```python
+import tvm
+
+tgt_host="llvm"
+tgt="sdaccel"
+
+n = tvm.var("n")
+A = tvm.placeholder((n,), name='A')
+B = tvm.placeholder((n,), name='B')
+C = tvm.compute(A.shape, lambda i: A[i] + B[i], name="C")
+
+s = tvm.create_schedule(C.op)
+px, x = s[C].split(C.op.axis[0], nparts=1)
+
+s[C].bind(px, tvm.thread_axis("pipeline"))
+
+fadd = tvm.build(s, [A, B, C], tgt, target_host=tgt_host, name="myadd")
+
+fadd.save("myadd.o")
+fadd.imported_modules[0].save("myadd.xclbin")
+
+tvm.contrib.cc.create_shared("myadd.so", ["myadd.o"])
+```
+
+- run.py - a script to use FPGA as an accelerator.
+```python
+import tvm
+import numpy as np
+import os
+
+tgt="sdaccel"
+
+fadd = tvm.module.load("myadd.so")
+if os.environ.get("XCL_EMULATION_MODE"):
+    fadd_dev = tvm.module.load("myadd.xclbin")
+else:
+    fadd_dev = tvm.module.load("myadd.awsxclbin")
+fadd.import_module(fadd_dev)
+
+ctx = tvm.context(tgt, 0)
+
+n = 1024
+a = tvm.nd.array(np.random.uniform(size=n).astype("float32"), ctx)
+b = tvm.nd.array(np.random.uniform(size=n).astype("float32"), ctx)
+c = tvm.nd.array(np.zeros(n, dtype="float32"), ctx)
+
+fadd(a, b, c)
+np.testing.assert_allclose(c.asnumpy(), a.asnumpy() + b.asnumpy())
+```
+
+Setup
+-----
+
+- Launch an instance using the FPGA Developer AMI.  We don't need an F1 instance for emulation and synthesis, so it is recommended to use a lower cost instance for them.
+
+- Setup AWS FPGA development kit.
+```bash
+git clone https://github.com/aws/aws-fpga.git
+cd aws-fpga
+source sdaccel_setup.sh
+source ${XILINX_SDX}/settings64.sh
+```
+
+- Setup TVM with OpenCL enabled.
+
+Emulation
+---------
+
+- Create emconfig.json for emulation.
+```bash
+emconfigutil --platform ${AWS_PLATFORM} --nd 1
+```
+
+- Copy emconfig.json to the python binary directory.  It is because the current Xilinx toolkit assumes that both host binary and the emconfig.json file are in the same path.
+```bash
+cp emconfig.json $(dirname $(which python))
+```
+
+- Run software emulation
+```bash
+export XCL_EMULATION_MODE=1
+export XCL_TARGET=sw_emu
+
+python build.py
+python run.py
+```
+
+- Run hardware emulation
+```bash
+export XCL_EMULATION_MODE=1
+export XCL_TARGET=hw_emu
+
+python build.py
+python run.py
+```
+
+
+Synthesis
+---------
+
+- Run synthesis with the following script. `XCL_EMULATION_MODE` must be set to 1 at this stage.
+
+```bash
+export XCL_EMULATION_MODE=1
+export XCL_TARGET=hw
+
+python build.py
+```
+The result shows CL_INVALID_PROGRAM error.  It is because AWS SDAccel expects awsxclbin binary, but we pass xclbin instead of it.  We don't load FPGA image here, so simply ignore the error for now.
+
+- Create AWS FPGA image and upload it to AWS S3.
+```
+${SDACCEL_DIR}/tools/create_sdaccel_afi.sh -xclbin=myadd.xclbin -o=myadd \
+    -s3_bucket=<bucket-name> -s3_dcp_key=<dcp-folder-name> -s3_logs_key=<logs-folder-name>
+```
+This also generates an awsxclbin file, which is necessary to use the AWS FPGA image on F1 instances.
+
+Run
+---
+
+- Launch Amazon EC2 F1 instance.
+
+- Copy `myadd.so`, `myadd.awsxclbin`, and `run.py` to the F1 instance.
+
+- Setup AWS FPGA development kit.
+```bash
+git clone https://github.com/aws/aws-fpga.git
+cd aws-fpga
+source sdaccel_setup.sh
+```
+
+- Setup TVM with OpenCL enabled.
+
+- Become root and setup environment variables.
+```bash
+sudo sh
+source ${INSTALL_ROOT}/setup.sh
+```
+
+- Run
+```bash
+python run.py
+```
diff --git a/python/tvm/__init__.py b/python/tvm/__init__.py
index fe8a282169a7..a028dfeddf36 100644
--- a/python/tvm/__init__.py
+++ b/python/tvm/__init__.py
@@ -35,4 +35,4 @@
 from .tag import tag_scope
 
 # Contrib initializers
-from .contrib import rocm as _rocm, nvcc as _nvcc
+from .contrib import rocm as _rocm, nvcc as _nvcc, sdaccel as _sdaccel
diff --git a/python/tvm/_ffi/runtime_ctypes.py b/python/tvm/_ffi/runtime_ctypes.py
index 992afa4990c3..d862a5abd497 100644
--- a/python/tvm/_ffi/runtime_ctypes.py
+++ b/python/tvm/_ffi/runtime_ctypes.py
@@ -111,6 +111,7 @@ class TVMContext(ctypes.Structure):
         'nvptx': 2,
         'cl': 4,
         'opencl': 4,
+        'sdaccel': 4,
         'vulkan': 7,
         'metal': 8,
         'vpi': 9,
diff --git a/python/tvm/contrib/sdaccel.py b/python/tvm/contrib/sdaccel.py
new file mode 100644
index 000000000000..12dba31a4ab5
--- /dev/null
+++ b/python/tvm/contrib/sdaccel.py
@@ -0,0 +1,108 @@
+"""Utility for Interacting with SDAccel Tools"""
+import subprocess
+import os
+import re
+from . import util
+from ..api import register_func
+
+
+def _vhls_to_opencl(code):
+    """Convert source code from Vivado HLS to OpenCL."""
+    out = ''
+    for line in code.split('\n'):
+        if re.match(r'#include', line):
+            # OpenCL doesn't support include.
+            continue
+        if re.match(r'#pragma', line):
+            # Remove Vivado HLS specific pragmas.
+            continue
+
+        if re.match(r'extern "C"', line):
+            line = re.sub(r'^extern "C"', "__kernel", line)
+            # Add __global to pointer parameters.
+            line = re.sub(r'(\w+)\s*\*', r"__global \1*", line)
+
+        out += line + '\n'
+
+    return out
+
+
+def _fake_compile_vhls(code):
+    """Fake compile Vivado HLS code for SDAccel.
+
+    Compile the Vivado HLS code as an OpenCL code, and generate a program
+    binary for GPU which can be used instead of xclbin.
+
+    Parameters
+    ----------
+    code : str
+        The Vivado HLS code.
+
+    Return
+    ------
+    binary : bytearray
+        The program binary which can be passed to clCreateProgramWithBinary
+    """
+    try:
+        import pyopencl as cl
+    except ImportError:
+        raise ImportError('PyOpenCL is required for testing SDAccel backend.')
+    ctx = cl.Context(dev_type=cl.device_type.GPU)
+    program = cl.Program(ctx, _vhls_to_opencl(code)).build()
+    binary = bytearray(program.binaries[0])
+    return binary
+
+
+@register_func("tvm_callback_sdaccel_compile")
+def compile_vhls(code, kernel):
+    """Compile Vivado HLS code for SDAccel.
+
+    Parameters
+    ----------
+    code : str
+        The Vivado HLS code.
+
+    kernel : str
+        The kernel to compile or link.
+
+    Return
+    ------
+    xclbin : bytearray
+        The bytearray of the xclbin
+    """
+    tmp_dir = util.tempdir()
+    tmp_cpp = tmp_dir.relpath("input.cpp")
+    tmp_xo = tmp_dir.relpath("output.xo")
+    tmp_xclbin = tmp_dir.relpath("output.xclbin")
+
+    with open(tmp_cpp, "wb") as out_file:
+        out_file.write(bytes(code))
+
+    sdk = os.environ.get("XILINX_SDX", None)
+    xocc = os.path.join(sdk, "bin/xocc") if sdk else "xocc"
+    target = os.environ.get("XCL_TARGET",
+                            "sw_emu" if os.environ.get("XCL_EMULATION_MODE") else "hw")
+    advanced_params = ["--xp", "param:compiler.preserveHlsOutput=1",
+                       "--xp", "param:compiler.generateExtraRunData=true"]
+    platform = os.environ.get("XCL_PLATFORM", os.environ.get("AWS_PLATFORM"))
+
+    if platform is None:
+        # If we don't have the Xilinx toolchain, create a program binary for
+        # GPU and use it for testing.
+        return _fake_compile_vhls(code)
+
+    # build xo
+    args = [xocc, "-c", "-t", target, "--platform", platform, "-o", tmp_xo, "-k", kernel] + \
+           advanced_params + [tmp_cpp]
+    returncode = subprocess.call(args)
+    if returncode != 0:
+        raise RuntimeError("Compile error")
+
+    # build xclbin
+    args = [xocc, "-l", "-t", target, "--platform", platform, "-o", tmp_xclbin, tmp_xo] + \
+           advanced_params
+    returncode = subprocess.call(args)
+    if returncode != 0:
+        raise RuntimeError("Link error")
+
+    return bytearray(open(tmp_xclbin, "rb").read())
diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc
index ea94f05d4779..426b06b4239d 100644
--- a/src/codegen/build_module.cc
+++ b/src/codegen/build_module.cc
@@ -88,6 +88,9 @@ Target CreateTarget(const std::string& target_name,
     t->keys_array.push_back(ir::StringImm::make(target_name));
     t->keys_array.push_back(ir::StringImm::make("gpu"));
     t->max_num_threads = 256;
+  } else if (target_name == "sdaccel") {
+    t->device_type = kDLOpenCL;
+    t->keys_array.push_back(ir::StringImm::make("sdaccel"));
   } else if (target_name == "opengl") {
     t->device_type = kOpenGL;
     t->keys_array.push_back(ir::StringImm::make("opengl"));
diff --git a/src/codegen/codegen_c.cc b/src/codegen/codegen_c.cc
index 82d0df71a308..ec27f41cc702 100644
--- a/src/codegen/codegen_c.cc
+++ b/src/codegen/codegen_c.cc
@@ -60,6 +60,7 @@ void CodeGenC::AddFunction(LoweredFunc f) {
     stream << ' ' << vid;
   }
   stream << ") {\n";
+  this->PreFunctionBody(f);
   int func_scope = this->BeginScope();
   this->PrintStmt(f->body);
   this->EndScope(func_scope);
diff --git a/src/codegen/codegen_c.h b/src/codegen/codegen_c.h
index bc44e3812484..0f14415f2af6 100644
--- a/src/codegen/codegen_c.h
+++ b/src/codegen/codegen_c.h
@@ -73,6 +73,11 @@ class CodeGenC :
     return os.str();
   }
   // The following parts are overloadable print operations.
+  /*!
+   * \brief Insert statement before function body.
+   * \param f The function to be compiled.
+   */
+  virtual void PreFunctionBody(LoweredFunc f) {}
   /*!
    * \brief Initialize codegen state for generating f.
    * \param f The function to be compiled.
diff --git a/src/codegen/codegen_opencl.cc b/src/codegen/codegen_opencl.cc
index 20053c829be6..2d5026e827e2 100644
--- a/src/codegen/codegen_opencl.cc
+++ b/src/codegen/codegen_opencl.cc
@@ -218,7 +218,7 @@ runtime::Module BuildOpenCL(Array<LoweredFunc> funcs) {
   if (const auto* f = Registry::Get("tvm_callback_opencl_postproc")) {
     code = (*f)(code).operator std::string();
   }
-  return OpenCLModuleCreate(code, "cl", ExtractFuncInfo(funcs));
+  return OpenCLModuleCreate(code, "cl", ExtractFuncInfo(funcs), code);
 }
 
 TVM_REGISTER_API("codegen.build_opencl")
diff --git a/src/codegen/codegen_vhls.cc b/src/codegen/codegen_vhls.cc
new file mode 100644
index 000000000000..3cb8b9a346c2
--- /dev/null
+++ b/src/codegen/codegen_vhls.cc
@@ -0,0 +1,103 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file codegen_vhls.cc
+ */
+#include <vector>
+#include <string>
+#include "./codegen_vhls.h"
+#include "./build_common.h"
+#include "../runtime/opencl/opencl_module.h"
+
+namespace tvm {
+namespace codegen {
+
+void CodeGenVivadoHLS::Init(bool output_ssa) {
+  CodeGenC::Init(output_ssa);
+
+  this->stream << "#include <ap_int.h>\n\n";
+}
+
+void CodeGenVivadoHLS::PrintType(Type t, std::ostream& os) {
+  if (t.is_uint()) {
+    switch (t.bits()) {
+      case 8:
+        os << "unsigned char"; break;
+      case 16:
+        os << "unsigned short"; break;
+      case 32:
+        os << "unsigned int"; break;
+      case 64:
+        os << "unsigned long long"; break;
+      default:
+        os << "ap_uint<" << t.bits() << ">"; break;
+    }
+  } else if (t.is_int()) {
+    switch (t.bits()) {
+      case 8:
+        os << "char"; break;
+      case 16:
+        os << "short"; break;
+      case 32:
+        os << "int"; break;
+      case 64:
+        os << "long long"; break;
+      default:
+        os << "ap_int<" << t.bits() << ">"; break;
+    }
+  } else {
+    CodeGenC::PrintType(t, os);
+  }
+}
+
+void CodeGenVivadoHLS::AddFunction(LoweredFunc f) {
+  this->stream << "extern \"C\" ";
+  CodeGenC::AddFunction(f);
+}
+
+void CodeGenVivadoHLS::PreFunctionBody(LoweredFunc f) {
+  for (size_t i = 0; i < f->args.size(); ++i) {
+    Var v = f->args[i];
+    std::string vid = GetVarID(v.get());
+    if (v.type().is_handle()) {
+      this->stream << "#pragma HLS INTERFACE m_axi port=" << vid << "  offset=slave bundle=gmem\n";
+    }
+    this->stream << "#pragma HLS INTERFACE s_axilite port=" << vid << " bundle=control\n";
+  }
+  this->stream << "#pragma HLS INTERFACE s_axilite port=return bundle=control\n\n";
+}
+
+
+runtime::Module BuildSDAccel(Array<LoweredFunc> funcs) {
+  using tvm::runtime::Registry;
+  bool output_ssa = false;
+  CodeGenVivadoHLS cg;
+
+  CHECK_EQ(funcs.size(), 1);
+  const std::string funcname = funcs[0]->name;
+
+  cg.Init(output_ssa);
+
+  for (LoweredFunc f : funcs) {
+    cg.AddFunction(f);
+  }
+  std::string code = cg.Finish();
+  if (const auto* f = runtime::Registry::Get("tvm_callback_vhls_postproc")) {
+    code = (*f)(code).operator std::string();
+  }
+
+  std::string xclbin;
+  if (const auto* f = Registry::Get("tvm_callback_sdaccel_compile")) {
+    xclbin = (*f)(code, funcname).operator std::string();
+  } else {
+    LOG(FATAL) << "Cannot compile Vivado HLS code.";
+  }
+  return OpenCLModuleCreate(xclbin, "xclbin", ExtractFuncInfo(funcs), code);
+}
+
+TVM_REGISTER_API("codegen.build_sdaccel")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    *rv = BuildSDAccel(args[0]);
+  });
+
+}  // namespace codegen
+}  // namespace tvm
diff --git a/src/codegen/codegen_vhls.h b/src/codegen/codegen_vhls.h
new file mode 100644
index 000000000000..02a0da2dcfa5
--- /dev/null
+++ b/src/codegen/codegen_vhls.h
@@ -0,0 +1,28 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file codegen_vhls.h
+ * \brief Utility to generate vhls code
+ */
+#ifndef TVM_CODEGEN_CODEGEN_VHLS_H_
+#define TVM_CODEGEN_CODEGEN_VHLS_H_
+
+#include <tvm/codegen.h>
+#include <tvm/packed_func_ext.h>
+#include <string>
+#include "./codegen_c.h"
+
+namespace tvm {
+namespace codegen {
+
+class CodeGenVivadoHLS final : public CodeGenC {
+ public:
+  void Init(bool output_ssa);
+  void PrintType(Type t, std::ostream& os);
+  void AddFunction(LoweredFunc f);
+  void PreFunctionBody(LoweredFunc f);
+};
+
+}  // namespace codegen
+}  // namespace tvm
+
+#endif  // TVM_CODEGEN_CODEGEN_VHLS_H_
diff --git a/src/codegen/intrin_rule_vhls.cc b/src/codegen/intrin_rule_vhls.cc
new file mode 100644
index 000000000000..32869eec1db5
--- /dev/null
+++ b/src/codegen/intrin_rule_vhls.cc
@@ -0,0 +1,33 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file intrin_rule_vhls.cc
+ * \brief VHLS intrinsic rules.
+ */
+#include "./intrin_rule.h"
+
+namespace tvm {
+namespace codegen {
+namespace intrin {
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.sdaccel.exp")
+.set_body(DispatchExtern<Direct>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.sdaccel.log")
+.set_body(DispatchExtern<Direct>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.sdaccel.tanh")
+.set_body(DispatchExtern<Direct>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.sdaccel.sqrt")
+.set_body(DispatchExtern<Direct>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.sdaccel.pow")
+.set_body(DispatchExtern<Direct>);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.sdaccel.popcount")
+.set_body(DispatchExtern<Direct>);
+
+
+}  // namespace intrin
+}  // namespace codegen
+}  // namespace tvm
diff --git a/src/codegen/opt/build_opencl_off.cc b/src/codegen/opt/build_opencl_off.cc
index 10389cf09e83..fc962d4840e9 100644
--- a/src/codegen/opt/build_opencl_off.cc
+++ b/src/codegen/opt/build_opencl_off.cc
@@ -11,7 +11,8 @@ namespace runtime {
 Module OpenCLModuleCreate(
     std::string data,
     std::string fmt,
-    std::unordered_map<std::string, FunctionInfo> fmap) {
+    std::unordered_map<std::string, FunctionInfo> fmap,
+    std::string source) {
   LOG(WARNING) << "OpenCL runtime not enabled, return a source module...";
   return codegen::DeviceSourceModuleCreate(data, fmt, fmap, "opencl");
 }
diff --git a/src/pass/split_host_device.cc b/src/pass/split_host_device.cc
index dc326f3cb2f1..c7b20e137638 100644
--- a/src/pass/split_host_device.cc
+++ b/src/pass/split_host_device.cc
@@ -179,7 +179,7 @@ class HostDeviceSplitter : public IRMutator {
  private:
   Stmt SplitDeviceFunc(Stmt body) {
     std::ostringstream os;
-    os << name_ << "__kernel" << device_funcs_.size();
+    os << name_ << "_kernel" << device_funcs_.size();
     std::shared_ptr<LoweredFuncNode> n = std::make_shared<LoweredFuncNode>();
     // isolate the device function.
     IRUseDefAnalysis m;
diff --git a/src/runtime/module.cc b/src/runtime/module.cc
index d800d6a42ee7..f5cccecb8bdd 100644
--- a/src/runtime/module.cc
+++ b/src/runtime/module.cc
@@ -106,7 +106,7 @@ bool RuntimeEnabled(const std::string& target) {
     return true;
   } else if (target == "cuda" || target == "gpu") {
     f_name = "device_api.gpu";
-  } else if (target == "cl" || target == "opencl") {
+  } else if (target == "cl" || target == "opencl" || target == "sdaccel") {
     f_name = "device_api.opencl";
   } else if (target == "gl" || target == "opengl") {
     f_name = "device_api.opengl";
diff --git a/src/runtime/opencl/opencl_device_api.cc b/src/runtime/opencl/opencl_device_api.cc
index 8c43ec252a79..8bd86d22aee5 100644
--- a/src/runtime/opencl/opencl_device_api.cc
+++ b/src/runtime/opencl/opencl_device_api.cc
@@ -244,16 +244,18 @@ void OpenCLWorkspace::Init() {
   this->platform_id = platform_matched[0];
   LOG(INFO) << "Initialize OpenCL platform \'"
             << cl::GetPlatformInfo(this->platform_id, CL_PLATFORM_NAME) << '\'';
-  std::vector<cl_device_id> devices_matched =
-      cl::GetDeviceIDs(this->platform_id, "gpu");
-  if (devices_matched.size() == 0) {
-    LOG(WARNING) << "No OpenCL device any device matched given the options: gpu mode";
-    LOG(WARNING) << "Now try OpenCL cpu mode";
-    devices_matched = cl::GetDeviceIDs(this->platform_id, "cpu");
-    if (devices_matched.size() == 0) {
-      LOG(WARNING) << "No OpenCL device any device matched given the options: cpu mode";
-      return;
+  std::string device_types[] = {"accelerator", "gpu", "cpu"};
+  std::vector<cl_device_id> devices_matched;
+  for (auto type : device_types) {
+    devices_matched = cl::GetDeviceIDs(this->platform_id, type);
+    if (devices_matched.size() > 0) {
+      break;
     }
+    LOG(INFO) << "No OpenCL device any device matched given the options: " << type << " mode";
+  }
+  if (devices_matched.size() == 0) {
+    LOG(WARNING) << "No OpenCL device";
+    return;
   }
   this->devices = devices_matched;
   cl_int err_code;
diff --git a/src/runtime/opencl/opencl_module.cc b/src/runtime/opencl/opencl_module.cc
index 1d8904ed6f42..5d67ad286875 100644
--- a/src/runtime/opencl/opencl_module.cc
+++ b/src/runtime/opencl/opencl_module.cc
@@ -32,8 +32,9 @@ class OpenCLModuleNode : public ModuleNode {
   };
   explicit OpenCLModuleNode(std::string data,
                             std::string fmt,
-                            std::unordered_map<std::string, FunctionInfo> fmap)
-      : data_(data), fmt_(fmt), fmap_(fmap) {}
+                            std::unordered_map<std::string, FunctionInfo> fmap,
+                            std::string source)
+      : data_(data), fmt_(fmt), fmap_(fmap), source_(source) {}
   // destructor
   ~OpenCLModuleNode() {
     {
@@ -81,7 +82,7 @@ class OpenCLModuleNode : public ModuleNode {
     if (fmt_ == "cl") {
       return data_;
     } else {
-      return "";
+      return source_;
     }
   }
 
@@ -97,6 +98,15 @@ class OpenCLModuleNode : public ModuleNode {
       program_ = clCreateProgramWithSource(
           workspace_->context, 1, &s, &len, &err);
       OPENCL_CHECK_ERROR(err);
+    } else if (fmt_ == "xclbin" || fmt_ == "awsxclbin") {
+      const unsigned char* s = (const unsigned char *)data_.c_str();
+      size_t len = data_.length();
+      cl_int err;
+      program_ = clCreateProgramWithBinary(
+          workspace_->context, 1, &(workspace_->devices[0]), &len, &s, NULL, &err);
+      if (err != CL_SUCCESS) {
+        LOG(ERROR) << "OpenCL Error: " << cl::CLGetErrorString(err);
+      }
     } else {
       LOG(FATAL) << "Unknown OpenCL format " << fmt_;
     }
@@ -162,6 +172,8 @@ class OpenCLModuleNode : public ModuleNode {
   std::unordered_map<std::string, FunctionInfo> fmap_;
   // Module local mutex
   std::mutex build_lock_;
+  // The OpenCL source.
+  std::string source_;
   // the binary data
   cl_program program_{nullptr};
   // build info
@@ -270,9 +282,10 @@ PackedFunc OpenCLModuleNode::GetFunction(
 Module OpenCLModuleCreate(
     std::string data,
     std::string fmt,
-    std::unordered_map<std::string, FunctionInfo> fmap) {
+    std::unordered_map<std::string, FunctionInfo> fmap,
+    std::string source) {
   std::shared_ptr<OpenCLModuleNode> n =
-      std::make_shared<OpenCLModuleNode>(data, fmt, fmap);
+      std::make_shared<OpenCLModuleNode>(data, fmt, fmap, source);
   n->Init();
   return Module(n);
 }
@@ -286,7 +299,7 @@ Module OpenCLModuleLoadFile(const std::string& file_name,
   std::string meta_file = GetMetaFilePath(file_name);
   LoadBinaryFromFile(file_name, &data);
   LoadMetaDataFromFile(meta_file, &fmap);
-  return OpenCLModuleCreate(data, fmt, fmap);
+  return OpenCLModuleCreate(data, fmt, fmap, std::string());
 }
 
 Module OpenCLModuleLoadBinary(void* strm) {
@@ -297,7 +310,7 @@ Module OpenCLModuleLoadBinary(void* strm) {
   stream->Read(&fmt);
   stream->Read(&fmap);
   stream->Read(&data);
-  return OpenCLModuleCreate(data, fmt, fmap);
+  return OpenCLModuleCreate(data, fmt, fmap, std::string());
 }
 
 TVM_REGISTER_GLOBAL("module.loadfile_cl")
@@ -310,6 +323,16 @@ TVM_REGISTER_GLOBAL("module.loadfile_clbin")
     *rv = OpenCLModuleLoadFile(args[0], args[1]);
   });
 
+TVM_REGISTER_GLOBAL("module.loadfile_xclbin")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    *rv = OpenCLModuleLoadFile(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("module.loadfile_awsxclbin")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    *rv = OpenCLModuleLoadFile(args[0], args[1]);
+  });
+
 TVM_REGISTER_GLOBAL("module.loadbinary_opencl")
 .set_body([](TVMArgs args, TVMRetValue* rv) {
     *rv = OpenCLModuleLoadBinary(args[0]);
diff --git a/src/runtime/opencl/opencl_module.h b/src/runtime/opencl/opencl_module.h
index 523a67f1a41a..b6e2a1e0e88a 100644
--- a/src/runtime/opencl/opencl_module.h
+++ b/src/runtime/opencl/opencl_module.h
@@ -24,7 +24,8 @@ namespace runtime {
 Module OpenCLModuleCreate(
     std::string data,
     std::string fmt,
-    std::unordered_map<std::string, FunctionInfo> fmap);
+    std::unordered_map<std::string, FunctionInfo> fmap,
+    std::string source);
 }  // namespace runtime
 }  // namespace tvm
 #endif  // TVM_RUNTIME_OPENCL_OPENCL_MODULE_H_
diff --git a/tests/python/integration/test_ewise_fpga.py b/tests/python/integration/test_ewise_fpga.py
new file mode 100644
index 000000000000..fb7ca807340d
--- /dev/null
+++ b/tests/python/integration/test_ewise_fpga.py
@@ -0,0 +1,46 @@
+import tvm
+import numpy as np
+import os
+
+os.environ["XCL_EMULATION_MODE"] = "1"
+
+@tvm.register_func
+def tvm_callback_vhls_postproc(code):
+    """Hook to inspect the Vivado HLS code before actually run it"""
+    print(code)
+    return code
+
+def test_exp():
+    # graph
+    n = tvm.convert(1024)
+    A = tvm.placeholder((n,), name='A')
+    B = tvm.compute(A.shape, lambda *i: tvm.exp(A(*i)), name='B')
+    s = tvm.create_schedule(B.op)
+    # create iter var and assign them tags.
+    px, x = s[B].split(B.op.axis[0], nparts=1)
+    s[B].bind(px, tvm.thread_axis("pipeline"))
+
+    # one line to build the function.
+    def check_device(device, host="stackvm"):
+        if not tvm.module.enabled(host):
+            return
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
+            return
+        fexp = tvm.build(s, [A, B],
+                         device, host,
+                         name="myexp")
+        ctx = tvm.context(device, 0)
+        # launch the kernel.
+        n = 1024
+        a = tvm.nd.array(np.random.uniform(size=n).astype(A.dtype), ctx)
+        b = tvm.nd.array(np.zeros(n, dtype=B.dtype), ctx)
+        fexp(a, b)
+        np.testing.assert_allclose(
+            b.asnumpy(), np.exp(a.asnumpy()), rtol=1e-5)
+
+    check_device("sdaccel")
+
+
+if __name__ == "__main__":
+    test_exp()

From f633acd6151ba4f18adf0cd62d62491114a83f03 Mon Sep 17 00:00:00 2001
From: Siva <sivar.b@huawei.com>
Date: Tue, 26 Jun 2018 22:45:24 +0530
Subject: [PATCH 763/949] [NNVM][TENSORFLOW] Mobilenet support. (#1335)

---
 nnvm/python/nnvm/frontend/tensorflow.py       | 52 +++++++++-----
 nnvm/python/nnvm/testing/tf.py                | 70 +++++++++++++------
 .../frontend/tensorflow/test_forward.py       | 24 +++++++
 3 files changed, 109 insertions(+), 37 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/tensorflow.py b/nnvm/python/nnvm/frontend/tensorflow.py
index 3ab997617a20..71536517810d 100644
--- a/nnvm/python/nnvm/frontend/tensorflow.py
+++ b/nnvm/python/nnvm/frontend/tensorflow.py
@@ -35,6 +35,11 @@ def __call__(self, inputs, attrs, *args):
         self._ignores.append('use_cudnn_on_gpu')
         self._ignores.append('_node_name')
         self._ignores.append('is_training')
+        # Retain the names
+        try:
+            attrs['name'] = attrs['_node_name']
+        except KeyError:
+            pass
         return AttrConvert(self._op_name, self._transforms, self._excludes,
                            self._disables, self._ignores, self._extras,
                            self._custom_check)(inputs, attrs, *args)
@@ -405,13 +410,19 @@ def _impl(inputs, attr, params):
 
 def _reshape():
     def _impl(inputs, attr, params):
-        pop_node = inputs.pop(1)
-        shape_arg = params[pop_node.list_output_names()[0]]
-        params.pop(pop_node.list_output_names()[0])
-        return AttrCvt(
-            op_name="reshape",
-            extras={'shape':tuple(shape_arg.asnumpy())},
-            ignores=['Tshape'])(inputs, attr)
+        try:
+            pop_node = inputs[1]
+            shape_arg = params.pop(pop_node.list_output_names()[0])
+            inputs.pop(1)
+
+            return AttrCvt(
+                op_name="reshape",
+                extras={'shape':tuple(shape_arg.asnumpy())},
+                ignores=['Tshape'])(inputs, attr)
+        except KeyError:
+            return AttrCvt(
+                op_name="reshape_like",
+                ignores=['Tshape'])(inputs, attr)
     return _impl
 
 def _bias_add():
@@ -427,6 +438,18 @@ def _impl(inputs, attr, params):
             ignores=['T'])(inputs, attr)
     return _impl
 
+def _fused_batch_norm():
+    def _impl(inputs, attr, params):
+        # Tensorflow: (data, gamma, beta, moving_mean, moving_variance)
+        # NNVM:       (data, gamma, beta, moving_mean, moving_varience)
+        return AttrCvt(
+            op_name='batch_norm',
+            transforms={'scale_after_normalization':'scale', 'variance_epsilon':'epsilon'},
+            extras={'axis': 3}, # Fix axis
+            ignores=['data_format'],
+            disables=['momentum'])(inputs, attr)
+    return _impl
+
 def _batch_norm():
     def _impl(inputs, attr, params):
         # Rearrange inputs from
@@ -445,19 +468,14 @@ def _impl(inputs, attr, params):
 
 def _relu6():
     def _impl(inputs, attr, params):
-        return _sym.clip(inputs[0], a_min=0, a_max=6)
+        return _sym.clip(inputs[0], a_min=0, a_max=6, name=attr['_node_name'])
     return _impl
 
 def _shape():
     def _impl(inputs, attr, params):
-        input_shapes = attr['_input_shapes'][inputs[0]]
-
-        # Fix the -1 dimensions to 1
-        input_shapes[0] = [1 if x == -1 else x for x in input_shapes[0]]
-        params[attr['_node_name']] = tvm.nd.array(input_shapes[0])
-
-        return _sym.Variable(name=attr['_node_name'],
-                             shape=params[attr['_node_name']].shape)
+        # Result of this operator is prominently used by reshape operator.
+        # Just pass the input as it is so that reshape_like can be used there.
+        return inputs[0]
     return _impl
 
 # compatible operators that do NOT require any conversion.
@@ -491,7 +509,7 @@ def _impl(inputs, attr, params):
     'Add'                               : _elemwise('add'),
     'Rsqrt'                             : _rsqrt(),
     'Squeeze'                           : _squeeze(),
-    'FusedBatchNorm'                    : _batch_norm(),
+    'FusedBatchNorm'                    : _fused_batch_norm(),
     'Relu6'                             : _relu6(),
     'DepthwiseConv2dNative'             : _depthwise_conv(),
     'Shape'                             : _shape(),
diff --git a/nnvm/python/nnvm/testing/tf.py b/nnvm/python/nnvm/testing/tf.py
index 3421573e36ad..1762ce56596b 100644
--- a/nnvm/python/nnvm/testing/tf.py
+++ b/nnvm/python/nnvm/testing/tf.py
@@ -153,6 +153,35 @@ def read_normalized_tensor_from_image_file(file_name,
     np_array = normalized.eval()
     return np_array
 
+def get_workload(model_path):
+    """ Import workload from frozen protobuf
+
+    Parameters
+    ----------
+    model_path: str
+        model_path on remote repository to download from.
+
+    Returns
+    -------
+    graph_def: graphdef
+        graph_def is the tensorflow workload for mobilenet.
+
+    """
+
+    repo_base = 'https://github.com/dmlc/web-data/raw/master/tensorflow/models/'
+    model_name = os.path.basename(model_path)
+    model_url = os.path.join(repo_base, model_path)
+
+    from mxnet.gluon.utils import download
+    download(model_url, model_name)
+
+    # Creates graph from saved graph_def.pb.
+    with tf.gfile.FastGFile(os.path.join("./", model_name), 'rb') as f:
+        graph_def = tf.GraphDef()
+        graph_def.ParseFromString(f.read())
+        graph = tf.import_graph_def(graph_def, name='')
+        return graph_def
+
 def get_workload_inception_v3():
     """ Import Inception V3 workload from frozen protobuf
 
@@ -168,23 +197,15 @@ def get_workload_inception_v3():
     """
 
     repo_base = 'https://github.com/dmlc/web-data/raw/master/tensorflow/models/InceptionV3/'
-    model_name = 'inception_v3_2016_08_28_frozen-with_shapes.pb'
-    model_url = os.path.join(repo_base, model_name)
+    model_path = 'InceptionV3/inception_v3_2016_08_28_frozen-with_shapes.pb'
+
     image_name = 'elephant-299.jpg'
     image_url = os.path.join(repo_base, image_name)
-
     from mxnet.gluon.utils import download
-    download(model_url, model_name)
     download(image_url, image_name)
-
     normalized = read_normalized_tensor_from_image_file(os.path.join("./", image_name))
 
-    # Creates graph from saved graph_def.pb.
-    with tf.gfile.FastGFile(os.path.join("./", model_name), 'rb') as f:
-        graph_def = tf.GraphDef()
-        graph_def.ParseFromString(f.read())
-        graph = tf.import_graph_def(graph_def, name='')
-        return (normalized, graph_def)
+    return (normalized, get_workload(model_path))
 
 def get_workload_inception_v1():
     """ Import Inception V1 workload from frozen protobuf
@@ -203,13 +224,11 @@ def get_workload_inception_v1():
     """
 
     repo_base = 'https://github.com/dmlc/web-data/raw/master/tensorflow/models/InceptionV1/'
-    model_name = 'classify_image_graph_def-with_shapes.pb'
-    model_url = os.path.join(repo_base, model_name)
+    model_path = 'InceptionV1/classify_image_graph_def-with_shapes.pb'
     image_name = 'elephant-299.jpg'
     image_url = os.path.join(repo_base, image_name)
 
     from mxnet.gluon.utils import download
-    download(model_url, model_name)
     download(image_url, image_name)
 
     if not tf.gfile.Exists(os.path.join("./", image_name)):
@@ -221,9 +240,20 @@ def get_workload_inception_v1():
     tvm_data = Image.open(os.path.join("./", image_name)).resize((299, 299))
     tvm_data = np.array(tvm_data)
 
-    # Creates graph from saved graph_def.pb.
-    with tf.gfile.FastGFile(os.path.join("./", model_name), 'rb') as f:
-        graph_def = tf.GraphDef()
-        graph_def.ParseFromString(f.read())
-        graph = tf.import_graph_def(graph_def, name='')
-        return (image_data, tvm_data, graph_def)
+    return (image_data, tvm_data, get_workload(model_path))
+
+def get_workload_mobilenet():
+    """ Import mobilenet workload from frozen protobuf
+
+    Parameters
+    ----------
+        Nothing.
+
+    Returns
+    -------
+    graph_def: graphdef
+        graph_def is the tensorflow workload for mobilenet.
+
+    """
+
+    return get_workload("MobilenetV1/mobilenet_v1_1.0_224_frozen-with-shapes.pb")
diff --git a/nnvm/tests/python/frontend/tensorflow/test_forward.py b/nnvm/tests/python/frontend/tensorflow/test_forward.py
index 4e742a4a5b36..6dc8cfab2ab4 100644
--- a/nnvm/tests/python/frontend/tensorflow/test_forward.py
+++ b/nnvm/tests/python/frontend/tensorflow/test_forward.py
@@ -406,6 +406,29 @@ def test_forward_inception_v1():
 
         np.testing.assert_allclose(tf_output, tvm_output, rtol=2e-2, atol=2e-2)
 
+#######################################################################
+# Mobilenet
+# ---------
+def test_forward_mobilenet():
+    '''test mobilenet model'''
+    with tf.Graph().as_default():
+        graph_def = nnvm.testing.tf.get_workload_mobilenet()
+        # Call the utility to import the graph definition into default graph.
+        graph_def = nnvm.testing.tf.ProcessGraphDefParam(graph_def)
+
+        data = np.random.uniform(size=(1, 224, 224, 3)).astype('float32')
+        out_node = 'MobilenetV1/Predictions/Reshape_1'
+
+        with tf.Session() as sess:
+            tf_output = run_tf_graph(sess, data, 'input:0', out_node + ':0')
+
+            out_shape = tf_output.shape
+            tvm_output = run_tvm_graph(graph_def, data, 'input', out_shape, 'float32')
+            top_tvm = np.squeeze(tvm_output).argsort()[-10:][::-1]
+            top_tf = np.squeeze(tf_output).argsort()[-10:][::-1]
+
+            np.testing.assert_allclose(np.squeeze(tvm_output), np.squeeze(tf_output), rtol=1e-5, atol=1e-5)
+
 #######################################################################
 # Main
 # ----
@@ -419,3 +442,4 @@ def test_forward_inception_v1():
     test_forward_multi_input()
     test_forward_inception_v3()
     test_forward_inception_v1()
+    test_forward_mobilenet()

From 40480614e90ad7d5668ed636a91c0fae1595e1f7 Mon Sep 17 00:00:00 2001
From: Siva <sivar.b@huawei.com>
Date: Tue, 26 Jun 2018 22:45:53 +0530
Subject: [PATCH 764/949] [NNVM][ONNX] Shape operator support
 (limited/differed) - #1297 (#1333)

---
 nnvm/python/nnvm/frontend/onnx.py             | 43 ++++++++-
 .../python/frontend/onnx/test_forward.py      | 91 ++++++++++++++++---
 2 files changed, 115 insertions(+), 19 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/onnx.py b/nnvm/python/nnvm/frontend/onnx.py
index d92a856d7231..fb21f6b7b681 100644
--- a/nnvm/python/nnvm/frontend/onnx.py
+++ b/nnvm/python/nnvm/frontend/onnx.py
@@ -258,10 +258,11 @@ def _impl_v1(cls, inputs, attr, params):
     def _impl_v5(cls, inputs, attr, params):
         if inputs[1].list_output_names()[0] in params:
             shape = tuple(params[inputs[1].list_output_names()[0]].asnumpy())
+            out = _sym.reshape(inputs[0], shape=shape)
         else:
-            raise RuntimeError('Shape is not contained in graph initializer.')
-        return _sym.reshape(inputs[0], shape=shape)
+            out = _sym.reshape_like(inputs[0], inputs[1])
 
+        return out
 
 class Scale(OnnxOpConverter):
 
@@ -405,6 +406,36 @@ def _impl(inputs, attr, params):
     return _impl
 
 
+class Shape(OnnxOpConverter):
+    """ Operator converter for Shape.
+    """
+
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
+        # Result of this operator is prominently used by reshape operator.
+        # Just pass the input as it is so that reshape_like can be used there.
+        print("Shape: Differently implemented in NNVM as a bypass (dummy operator)")
+        return inputs[0]
+
+class Cast(OnnxOpConverter):
+    """ Operator converter for Cast.
+    """
+
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
+        return AttrCvt(op_name='cast', transforms={'to': 'dtype'})(inputs, attr)
+
+    @classmethod
+    def _impl_v5(cls, inputs, attr, params):
+        try:
+            from onnx.mapping import TENSOR_TYPE_TO_NP_TYPE
+            attr['to'] = TENSOR_TYPE_TO_NP_TYPE[attr['to']]
+        except ImportError as e:
+            raise ImportError(
+                "Unable to import onnx.mapping which is required {}".format(e))
+        return AttrCvt(op_name='cast', transforms={'to': 'dtype'})(inputs, attr)
+
+
 # compatible operators that do NOT require any conversion.
 _identity_list = []
 
@@ -505,7 +536,7 @@ def _get_convert_map(opset):
         # 'ArgMin'
 
         # defs/tensor
-        'Cast': AttrCvt('cast', {'to': 'dtype'}),
+        'Cast': Cast.get_converter(opset),
         'Reshape': Reshape.get_converter(opset),
         'Concat': Renamer('concatenate'),
         'Split': AttrCvt('split', {'split': 'indices_or_sections'}),
@@ -514,6 +545,7 @@ def _get_convert_map(opset):
         # 'Gather'
         # 'Squeeze'
         'Pad': Pad.get_converter(opset),
+        'Shape': Shape.get_converter(opset),
     }
 
 
@@ -719,6 +751,9 @@ def from_onnx(model):
     """
     g = GraphProto()
     graph = model.graph
-    opset = model.opset_import[0].version if model.opset_import else 1
+    try:
+        opset = model.opset_import[0].version if model.opset_import else 1
+    except AttributeError:
+        opset = 1
     sym, params = g.from_onnx(graph, opset)
     return sym, params
diff --git a/nnvm/tests/python/frontend/onnx/test_forward.py b/nnvm/tests/python/frontend/onnx/test_forward.py
index 9aef8b2cbe9e..93a458a67f78 100644
--- a/nnvm/tests/python/frontend/onnx/test_forward.py
+++ b/nnvm/tests/python/frontend/onnx/test_forward.py
@@ -5,6 +5,23 @@
 from nnvm.testing.config import ctx_list
 import onnx
 from model_zoo import super_resolution, squeezenet1_1, lenet, resnet18_1_0
+from onnx import helper, TensorProto
+
+def get_tvm_output(model, x, target, ctx, out_shape, dtype='float32'):
+    new_sym, params = nnvm.frontend.from_onnx(model)
+    input_name = model.graph.input[0].name
+    shape_dict = {input_name: x.shape}
+    dtype_dict = {input_name: dtype}
+    graph, lib, params = nnvm.compiler.build(new_sym, target, shape_dict, dtype_dict, params=params)
+    m = graph_runtime.create(graph, lib, ctx)
+    # set inputs
+    m.set_input(input_name, tvm.nd.array(x.astype(dtype)))
+    m.set_input(**params)
+    m.run()
+    # get outputs
+    out = m.get_output(0, tvm.nd.empty(out_shape, dtype))
+    return out.asnumpy()
+
 
 def verify_onnx_forward_impl(graph_file, data_shape, out_shape):
     import caffe2.python.onnx.backend
@@ -14,26 +31,12 @@ def get_caffe2_output(model, x, dtype='float32'):
         c2_out = prepared_backend.run(W)[0]
         return c2_out
 
-    def get_tvm_output(model, x, target, ctx, dtype='float32'):
-        new_sym, params = nnvm.frontend.from_onnx(model)
-        input_name = model.graph.input[0].name
-        shape_dict = {input_name: x.shape}
-        graph, lib, params = nnvm.compiler.build(new_sym, target, shape_dict, params=params)
-        m = graph_runtime.create(graph, lib, ctx)
-        # set inputs
-        m.set_input(input_name, tvm.nd.array(x.astype(dtype)))
-        m.set_input(**params)
-        m.run()
-        # get outputs
-        out = m.get_output(0, tvm.nd.empty(out_shape, dtype))
-        return out.asnumpy()
-
     dtype = 'float32'
     x = np.random.uniform(size=data_shape)
     model = onnx.load(graph_file)
     c2_out = get_caffe2_output(model, x, dtype)
     for target, ctx in ctx_list():
-        tvm_out = get_tvm_output(model, x, target, ctx, dtype)
+        tvm_out = get_tvm_output(model, x, target, ctx, out_shape, dtype)
         np.testing.assert_allclose(c2_out, tvm_out, rtol=1e-5, atol=1e-5)
 
 def verify_super_resolution_example():
@@ -48,8 +51,66 @@ def verify_lenet():
 def verify_resnet18():
     verify_onnx_forward_impl(resnet18_1_0, (1, 3, 224, 224), (1, 1000))
 
+
+def test_reshape():
+    in_shape = (4, 3, 3, 4)
+    ref_shape = (3, 4, 4, 3)
+
+    ref_array = np.array(ref_shape)
+    ref_node = onnx.helper.make_node('Constant',
+                                 inputs=[],
+                                 outputs=['ref_in'],
+                                 value=onnx.helper.make_tensor(name = 'const_tensor',
+                                                               data_type = onnx.TensorProto.INT32,
+                                                               dims = ref_array.shape,
+                                                               vals = ref_array.flatten().astype(int)))
+    reshape_node = helper.make_node("Reshape", ["in", "ref_in"], ["out"])
+
+    graph = helper.make_graph([ref_node, reshape_node],
+                              "reshape_test",
+                              inputs = [helper.make_tensor_value_info("in", TensorProto.FLOAT, list(in_shape))],
+                              outputs = [helper.make_tensor_value_info("out", TensorProto.FLOAT, list(ref_shape))])
+
+    model = helper.make_model(graph, producer_name='reshape_test')
+
+    for target, ctx in ctx_list():
+        x = np.random.uniform(size=in_shape)
+        tvm_out = get_tvm_output(model, x, target, ctx, ref_shape, 'float32')
+
+    np.testing.assert_allclose(ref_shape, tvm_out.shape)
+
+def test_reshape_like():
+    in_shape = (4, 3, 3, 4)
+    ref_shape = (3, 4, 4, 3)
+
+    ref_array = np.random.uniform(size=ref_shape).astype('float32')
+    ref_node = onnx.helper.make_node('Constant',
+                                 inputs=[],
+                                 outputs=['ref_in'],
+                                 value=onnx.helper.make_tensor(name = 'const_tensor',
+                                                               data_type = onnx.TensorProto.FLOAT,
+                                                               dims = ref_array.shape,
+                                                               vals = ref_array.flatten().astype(float)))
+    copy_node = helper.make_node("Identity", ["ref_in"], ["copy_in"])
+    reshape_node = helper.make_node("Reshape", ["in", "copy_in"], ["out"])
+
+    graph = helper.make_graph([ref_node, copy_node, reshape_node],
+                              "reshape_like_test",
+                              inputs = [helper.make_tensor_value_info("in", TensorProto.FLOAT, list(in_shape))],
+                              outputs = [helper.make_tensor_value_info("out", TensorProto.FLOAT, list(ref_shape))])
+
+    model = helper.make_model(graph, producer_name='reshape_like_test')
+
+    for target, ctx in ctx_list():
+        x = np.random.uniform(size=in_shape)
+        tvm_out = get_tvm_output(model, x, target, ctx, ref_shape, 'float32')
+
+    np.testing.assert_allclose(ref_shape, tvm_out.shape)
+
 if __name__ == '__main__':
     # verify_super_resolution_example()
     # verify_squeezenet1_1()
     # verify_lenet()
     verify_resnet18()
+    test_reshape()
+    test_reshape_like()

From 20ba308d9df2c29671833b543cede8254f648399 Mon Sep 17 00:00:00 2001
From: Siva <sivar.b@huawei.com>
Date: Wed, 27 Jun 2018 00:29:53 +0530
Subject: [PATCH 765/949] [NNVM][ONNX] Squeeze and Unsqueese operators. (#1339)

---
 nnvm/python/nnvm/frontend/onnx.py             | 13 ++++++-
 .../python/frontend/onnx/test_forward.py      | 39 +++++++++++++++++++
 2 files changed, 51 insertions(+), 1 deletion(-)

diff --git a/nnvm/python/nnvm/frontend/onnx.py b/nnvm/python/nnvm/frontend/onnx.py
index fb21f6b7b681..8e8d7f17ce0e 100644
--- a/nnvm/python/nnvm/frontend/onnx.py
+++ b/nnvm/python/nnvm/frontend/onnx.py
@@ -436,6 +436,16 @@ def _impl_v5(cls, inputs, attr, params):
         return AttrCvt(op_name='cast', transforms={'to': 'dtype'})(inputs, attr)
 
 
+class Unsqueeze(OnnxOpConverter):
+    """ Operator converter for Unsqueeze.
+    """
+
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
+        for axes in attr['axes']:
+            inputs[0] = _sym.expand_dims(inputs[0], axis=axes, num_newaxis=1)
+        return inputs[0]
+
 # compatible operators that do NOT require any conversion.
 _identity_list = []
 
@@ -543,7 +553,8 @@ def _get_convert_map(opset):
         # 'Slice'
         'Transpose': AttrCvt('transpose', {'perm': 'axes'}),
         # 'Gather'
-        # 'Squeeze'
+        'Squeeze': Renamer('squeeze'),
+        'Unsqueeze': Unsqueeze.get_converter(opset),
         'Pad': Pad.get_converter(opset),
         'Shape': Shape.get_converter(opset),
     }
diff --git a/nnvm/tests/python/frontend/onnx/test_forward.py b/nnvm/tests/python/frontend/onnx/test_forward.py
index 93a458a67f78..9e87f0d99cdd 100644
--- a/nnvm/tests/python/frontend/onnx/test_forward.py
+++ b/nnvm/tests/python/frontend/onnx/test_forward.py
@@ -107,6 +107,43 @@ def test_reshape_like():
 
     np.testing.assert_allclose(ref_shape, tvm_out.shape)
 
+def test_squeeze():
+    in_shape = (1, 3, 1, 3, 1, 1)
+    out_shape = (3, 3)
+    y = helper.make_node("Squeeze", ['in'], ['out'])
+
+    graph = helper.make_graph([y],
+                              'squeeze_test',
+                              inputs = [helper.make_tensor_value_info("in", TensorProto.FLOAT, list(in_shape))],
+                              outputs = [helper.make_tensor_value_info("out", TensorProto.FLOAT, list(out_shape))])
+
+    model = helper.make_model(graph, producer_name='squeeze_test')
+
+    for target, ctx in ctx_list():
+        x = np.random.uniform(size=in_shape)
+        tvm_out = get_tvm_output(model, x, target, ctx, out_shape, 'float32')
+
+    np.testing.assert_allclose(out_shape, tvm_out.shape)
+
+def test_unsqueeze():
+    in_shape = (3, 3)
+    axis = (0, 3, 4)
+    out_shape = (1, 3, 3, 1, 1)
+    y = helper.make_node("Unsqueeze", ['in'], ['out'], axes=list(axis))
+
+    graph = helper.make_graph([y],
+                              'squeeze_test',
+                              inputs = [helper.make_tensor_value_info("in", TensorProto.FLOAT, list(in_shape))],
+                              outputs = [helper.make_tensor_value_info("out", TensorProto.FLOAT, list(out_shape))])
+
+    model = helper.make_model(graph, producer_name='squeeze_test')
+
+    for target, ctx in ctx_list():
+        x = np.random.uniform(size=in_shape)
+        tvm_out = get_tvm_output(model, x, target, ctx, out_shape, 'float32')
+
+    np.testing.assert_allclose(out_shape, tvm_out.shape)
+
 if __name__ == '__main__':
     # verify_super_resolution_example()
     # verify_squeezenet1_1()
@@ -114,3 +151,5 @@ def test_reshape_like():
     verify_resnet18()
     test_reshape()
     test_reshape_like()
+    test_squeeze()
+    test_unsqueeze()

From 7f9e7e57404da0ef362d2f7992b409d9b9bc355b Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 26 Jun 2018 13:55:04 -0700
Subject: [PATCH 766/949] [SCHEDULE] Fix schedule for big array (#1340)

---
 topi/python/topi/cuda/extern.py     | 12 ++----------
 topi/python/topi/cuda/injective.py  | 25 +++++++++++++++++++++----
 topi/python/topi/util.py            | 22 ++++++++++++++++++++++
 topi/tests/python/test_topi_relu.py |  5 +++++
 4 files changed, 50 insertions(+), 14 deletions(-)

diff --git a/topi/python/topi/cuda/extern.py b/topi/python/topi/cuda/extern.py
index 34dae092cf69..94046196c074 100644
--- a/topi/python/topi/cuda/extern.py
+++ b/topi/python/topi/cuda/extern.py
@@ -2,15 +2,7 @@
 """Schedule for cudnn and miopen extern op"""
 import tvm
 from .. import generic
-
-def _schedule_output(op, sch):
-    x = op.output(0)
-    fused = sch[x].fuse(*sch[x].op.axis)
-    num_thread = tvm.target.current_target(allow_none=False).max_num_threads
-    bx, tx = sch[x].split(fused, factor=num_thread)
-    sch[x].bind(bx, tvm.thread_axis("blockIdx.x"))
-    sch[x].bind(tx, tvm.thread_axis("threadIdx.x"))
-    return sch
+from .injective import _schedule_injective
 
 
 @generic.schedule_extern.register(["cuda", "gpu"])
@@ -36,5 +28,5 @@ def schedule_extern(outs):
     for out in outs:
         if isinstance(out.op, tvm.tensor.ExternOp):
             continue
-        _schedule_output(out.op, s)
+        _schedule_injective(out.op, s)
     return s
diff --git a/topi/python/topi/cuda/injective.py b/topi/python/topi/cuda/injective.py
index 0143aec36a7b..4ca89fb3ecd3 100644
--- a/topi/python/topi/cuda/injective.py
+++ b/topi/python/topi/cuda/injective.py
@@ -1,15 +1,32 @@
 # pylint: disable=invalid-name, unused-variable,
 """Schedule for composition of injective operator"""
 import tvm
-from .. import generic
+from .. import generic, util
 
 def _schedule_injective(op, sch):
     x = op.output(0)
     fused = sch[x].fuse(*sch[x].op.axis)
     num_thread = tvm.target.current_target(allow_none=False).max_num_threads
-    bx, tx = sch[x].split(fused, factor=num_thread)
-    sch[x].bind(bx, tvm.thread_axis("blockIdx.x"))
-    sch[x].bind(tx, tvm.thread_axis("threadIdx.x"))
+    max_block = 256
+
+    try:
+        const_size = util.get_const_int(util.prod(x.shape))
+        max_block = 256
+        need_block_split = const_size > max_block * num_thread
+    except ValueError:
+        need_block_split = False
+
+    if need_block_split:
+        xo, xi = sch[x].split(fused, factor=num_thread * max_block)
+        bx, tx = sch[x].split(xi, factor=num_thread)
+        sch[x].reorder(bx, tx, xo)
+        sch[x].bind(bx, tvm.thread_axis("blockIdx.x"))
+        sch[x].bind(tx, tvm.thread_axis("threadIdx.x"))
+    else:
+        bx, tx = sch[x].split(fused, factor=num_thread)
+        sch[x].bind(tx, tvm.thread_axis("threadIdx.x"))
+        sch[x].bind(bx, tvm.thread_axis("blockIdx.x"))
+
     return sch
 
 
diff --git a/topi/python/topi/util.py b/topi/python/topi/util.py
index a093bfd8a35c..3625f6aaefaa 100644
--- a/topi/python/topi/util.py
+++ b/topi/python/topi/util.py
@@ -2,6 +2,28 @@
 from __future__ import absolute_import as _abs
 import tvm
 
+
+def prod(x):
+    """Get the product of every items in the tuple.
+
+    Parameters
+    ----------
+    x: tuple
+        Input tuple
+
+    Returns
+    -------
+    value : Expr
+        The result value
+    """
+    if not x:
+        return tvm.const(1, "int32")
+    res = x[0]
+    for i in range(1, len(x)):
+        res = res * x[i]
+    return res
+
+
 def get_const_int(expr):
     """Verifies expr is integer and get the constant value.
 
diff --git a/topi/tests/python/test_topi_relu.py b/topi/tests/python/test_topi_relu.py
index 6360155c5012..2f7898ff242a 100644
--- a/topi/tests/python/test_topi_relu.py
+++ b/topi/tests/python/test_topi_relu.py
@@ -71,6 +71,10 @@ def _prelu_numpy(x, W):
 def test_relu():
     verify_relu(10, 128)
 
+def test_schedule_big_array():
+    verify_relu(1024 * 100 , 512)
+
+
 def test_leaky_relu():
     verify_leaky_relu(100, 0.1)
 
@@ -78,6 +82,7 @@ def test_prelu():
     verify_prelu((1, 3, 2, 2), (3,))
 
 if __name__ == "__main__":
+    test_schedule_big_array()
     test_relu()
     test_leaky_relu()
     test_prelu()

From 195c73407dac8a5cad852c89048b0d0bb58691ad Mon Sep 17 00:00:00 2001
From: nhynes <nhynes@berkeley.edu>
Date: Tue, 26 Jun 2018 18:39:45 -0700
Subject: [PATCH 767/949] [NNVM][TOPI] Add gradients for broadcast_* ops
 (#1234)

---
 nnvm/include/nnvm/tuple.h                     |   9 +-
 nnvm/python/nnvm/top/reduction.py             |   4 +
 nnvm/src/top/tensor/broadcast.cc              |  61 +++++++-
 nnvm/src/top/tensor/reduce.cc                 | 144 +++++++++++-------
 nnvm/src/top/tensor/transform.cc              |   4 +-
 nnvm/tests/python/compiler/test_top_level4.py |  79 +++++++++-
 topi/include/topi/reduction.h                 | 109 +++++++++----
 topi/python/topi/cuda/reduction.py            |   5 +-
 topi/tests/python_cpp/test_topi_reduce.py     |   1 +
 9 files changed, 318 insertions(+), 98 deletions(-)

diff --git a/nnvm/include/nnvm/tuple.h b/nnvm/include/nnvm/tuple.h
index d2db6a4d821c..7e83aecc11f0 100644
--- a/nnvm/include/nnvm/tuple.h
+++ b/nnvm/include/nnvm/tuple.h
@@ -52,12 +52,19 @@ class Tuple {
   inline Tuple(std::initializer_list<ValueType> init) {
     this->assign(init.begin(), init.end());
   }
+  /*!
+   * \brief constructor from vector
+   * \param init the vector
+   */
+  inline Tuple(std::vector<ValueType> init) {  // NOLINT(runtime/explicit)
+    this->assign(init.begin(), init.end());
+  }
   /*!
    * \brief move constructor from Tuple
    * \param src the source shape
    */
 
-  inline Tuple(Tuple<ValueType>&& src) { // NOLINT(*)
+  inline Tuple(Tuple<ValueType>&& src) {   // NOLINT(runtime/explicit)
     this->swap(src);
   }
   /*!
diff --git a/nnvm/python/nnvm/top/reduction.py b/nnvm/python/nnvm/top/reduction.py
index 193ea6038e37..61973a752cb7 100644
--- a/nnvm/python/nnvm/top/reduction.py
+++ b/nnvm/python/nnvm/top/reduction.py
@@ -37,3 +37,7 @@ def _compute(attrs, inputs, out_info):
 # min
 reg.register_pattern("min", OpPattern.COMM_REDUCE)
 reg.register_schedule("min", _fschedule_reduce)
+
+# collapse sum
+reg.register_pattern("collapse_sum", OpPattern.COMM_REDUCE)
+reg.register_schedule("collapse_sum", _fschedule_reduce)
diff --git a/nnvm/src/top/tensor/broadcast.cc b/nnvm/src/top/tensor/broadcast.cc
index 74fc9b340606..5f6cc4e6605e 100644
--- a/nnvm/src/top/tensor/broadcast.cc
+++ b/nnvm/src/top/tensor/broadcast.cc
@@ -238,7 +238,15 @@ Example::
    broadcast_add(x, y) = [[ 1.,  1.,  1.],
                           [ 2.,  2.,  2.]]
 
-)code" NNVM_ADD_FILELINE);
+)code" NNVM_ADD_FILELINE)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds) {
+    return std::vector<NodeEntry>{
+      MakeNode("collapse_sum", n->attrs.name + "_dlhs", { ograds[0], n->inputs[0] }),
+      MakeNode("collapse_sum", n->attrs.name + "_drhs", { ograds[0], n->inputs[1] })
+    };
+});
 
 
 NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_sub, subtract)
@@ -256,7 +264,18 @@ Example::
    broadcast_sub(x, y) = [[ 1.,  1.,  1.],
                           [ 0.,  0.,  0.]]
 
-)code" NNVM_ADD_FILELINE);
+)code" NNVM_ADD_FILELINE)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds) {
+    return std::vector<NodeEntry>{
+      MakeNode("collapse_sum", n->attrs.name + "_dlhs", { ograds[0], n->inputs[0] }),
+      MakeNode("collapse_sum", n->attrs.name + "_drhs", {
+          MakeNode("negative", n->attrs.name + "_drhs_neg", {ograds[0]}),
+          n->inputs[1]
+        })
+    };
+});
 
 
 NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_mul, multiply)
@@ -273,7 +292,22 @@ Example::
 
    broadcast_mul(x, y) = [[ 0.,  0.,  0.],
                           [ 1.,  1.,  1.]]
-)code" NNVM_ADD_FILELINE);
+)code" NNVM_ADD_FILELINE)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds) {
+    NodeEntry dlhs = MakeNode("collapse_sum", n->attrs.name + "_dlhs_sum", {
+        MakeNode("broadcast_mul", n->attrs.name + "_dlhs_mul",
+                 { n->inputs[1], ograds[0] }),
+        n->inputs[0]
+      });
+    NodeEntry drhs = MakeNode("collapse_sum", n->attrs.name + "_drhs_sum", {
+        MakeNode("broadcast_mul", n->attrs.name + "_drhs_mul",
+                 { n->inputs[0], ograds[0] }),
+        n->inputs[1]
+      });
+    return std::vector<NodeEntry>{ dlhs, drhs };
+});
 
 
 NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_div, divide)
@@ -291,7 +325,26 @@ Example::
    broadcast_div(x, y) = [[ 3.,  3.,  3.],
                           [ 2.,  2.,  2.]]
 
-)code" NNVM_ADD_FILELINE);
+)code" NNVM_ADD_FILELINE)
+.set_attr<FGradient>(
+  "FGradient", [](const NodePtr& n,
+                  const std::vector<NodeEntry>& ograds) {
+    NodeEntry dlhs = MakeNode("collapse_sum", n->attrs.name + "_dlhs_sum", {
+        MakeNode("broadcast_div", n->attrs.name + "_dlhs_div",
+                 { ograds[0], n->inputs[1] }),
+        n->inputs[0]
+      });
+    NodeEntry dy = MakeNode("broadcast_div", n->attrs.name + "_drhs_div", {
+        NodeEntry{n, 0, 0},
+        MakeNode("__mul_scalar__", n->attrs.name + "_rhs_by_two",
+                 {n->inputs[1]}, {{"scalar", "2"}})
+      });
+    NodeEntry drhs = MakeNode("collapse_sum", n->attrs.name + "_drhs_sum", {
+        MakeNode("broadcast_mul", n->attrs.name + "_drhs_mul", { dy, ograds[0] }),
+        n->inputs[1]
+      });
+    return std::vector<NodeEntry>{ dlhs, drhs };
+});
 
 }  // namespace top
 }  // namespace nnvm
diff --git a/nnvm/src/top/tensor/reduce.cc b/nnvm/src/top/tensor/reduce.cc
index 2befe1cab455..afe9475f5cbb 100644
--- a/nnvm/src/top/tensor/reduce.cc
+++ b/nnvm/src/top/tensor/reduce.cc
@@ -9,9 +9,13 @@
 #include <nnvm/compiler/op_attr_types.h>
 #include <nnvm/compiler/util.h>
 #include <nnvm/top/tensor.h>
+#include <numeric>
 #include "../op_common.h"
 #include "../elemwise_op_common.h"
+#include "topi/detail/constant_utils.h"
+#include "topi/elemwise.h"
 #include "topi/reduction.h"
+#include "topi/transform.h"
 
 namespace nnvm {
 namespace top {
@@ -21,58 +25,61 @@ using namespace nnvm::compiler;
 // reduce
 DMLC_REGISTER_PARAMETER(ReduceParam);
 
-inline TShape ReduceShapeImpl(const TShape& ishape,
-                              const TShape& axis,
-                              bool keepdims,
-                              bool exclude) {
+inline TShape GetReduceAxes(const uint32_t indim,
+                            const TShape& axis,
+                            bool exclude) {
   if (axis.ndim() == 0) {
-    if (keepdims) {
-      return TShape(ishape.ndim());
-    } else {
-      return TShape(1);
-    }
+    TShape r_axes(indim);
+    std::iota(r_axes.begin(), r_axes.end(), 0);
+    return r_axes;
   }
-  CHECK_LT(axis[axis.ndim() - 1], ishape.ndim())
+
+  CHECK_LT(axis[axis.ndim() - 1], indim)
     << "Reduction axis " << axis[axis.ndim() - 1]
-    << " Exceeds input dimensions " << ishape;
+    << " exceeds input dimensions " << indim;
 
   TShape in_axis = axis;
   for (auto& i : in_axis) {
-    i = i < 0 ? i + ishape.ndim(): i;
+    i = i < 0 ? i + indim : i;
     CHECK_GE(i, 0) << "axis out of bounds in reduce operator";
-    CHECK_LT(i, ishape.ndim()) << "axis out of bounds in reduce operator";
+    CHECK_LT(i, indim) << "axis out of bounds in reduce operator";
   }
   std::sort(in_axis.begin(), in_axis.end());
+  if (!exclude) return in_axis;
+  TShape r_axis(indim - in_axis.ndim());
+  for (unsigned i = 0, j = 0, k = 0; i < indim; ++i) {
+    if (i == in_axis[j]) {
+        ++j;
+        continue;
+    }
+    r_axis[k++] = i;
+  }
+  return r_axis;
+}
+
+inline TShape ReduceShapeImpl(const TShape& ishape,
+                              const TShape& axis,
+                              bool keepdims,
+                              bool exclude) {
+  uint32_t indim = ishape.ndim();
+  TShape r_axes = GetReduceAxes(indim, axis, exclude);
+  if (!r_axes.ndim()) return ishape;
+  if (r_axes.ndim() == indim)
+    return TShape(keepdims ? indim : 1);
 
   if (keepdims) {
     TShape oshape(ishape);
-    if (exclude) {
-      for (dim_t i = 0, j = 0; i < ishape.ndim(); ++i) {
-        if (j < in_axis.ndim() && i == in_axis[j]) {
-          ++j;
-          continue;
-        }
-        oshape[i] = 1;
-      }
-      return oshape;
-    }
-
-    for (dim_t i = 0; i < in_axis.ndim(); ++i) {
-      oshape[in_axis[i]] = 1;
+    for (unsigned i = 0, j = 0; i < indim; ++i) {
+      if (i != r_axes[j]) continue;
+      oshape[i] = 1;
+      ++j;
     }
     return oshape;
   }
 
-  if (exclude) {
-    TShape oshape = TShape(in_axis.ndim());
-    for (dim_t i = 0; i < in_axis.ndim(); ++i) {
-      oshape[i] = ishape[in_axis[i]];
-    }
-    return oshape;
-  }
-  TShape oshape = TShape(std::max<dim_t>(1, ishape.ndim() - in_axis.ndim()));
-  for (dim_t i = 0, j = 0, k = 0; i < ishape.ndim(); ++i) {
-    if (j < in_axis.ndim() && i == in_axis[j]) {
+  TShape oshape(indim - r_axes.ndim());
+  for (unsigned i = 0, j = 0, k = 0; i < indim; ++i) {
+    if (i == r_axes[j]) {
       ++j;
       continue;
     }
@@ -95,6 +102,16 @@ inline bool ReduceShape(const nnvm::NodeAttrs& attrs,
   return true;
 }
 
+inline bool CollapseShape(const nnvm::NodeAttrs& attrs,
+                          std::vector<TShape>* in_attrs,
+                          std::vector<TShape>* out_attrs) {
+  CHECK_EQ(in_attrs->size(), 2U);
+  CHECK_EQ(out_attrs->size(), 1U);
+  if ((*in_attrs)[0].ndim() == 1) return false;
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, 0, (*in_attrs)[1]);
+  return true;
+}
+
 template<typename PType>
 inline void AxesParamParser(nnvm::NodeAttrs* attrs) {
   PType param;
@@ -103,18 +120,21 @@ inline void AxesParamParser(nnvm::NodeAttrs* attrs) {
   attrs->parsed = std::move(param);
 }
 
+#define NNVM_REGISTER_BASE_REDUCE_OP(op)                                 \
+  NNVM_REGISTER_OP(op)                                                   \
+  .add_arguments(ReduceParam::__FIELDS__())                              \
+  .set_attr_parser(AxesParamParser<ReduceParam>)                         \
+  .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<ReduceParam>) \
+  .set_num_outputs(1)
+
 #define NNVM_REGISTER_REDUCE_OP(op)                                     \
-  NNVM_REGISTER_OP(op)                                                  \
+  NNVM_REGISTER_BASE_REDUCE_OP(op)                                      \
   .add_argument("data", "Tensor", "The input")                          \
-  .add_arguments(ReduceParam::__FIELDS__())                             \
-  .set_attr_parser(AxesParamParser<ReduceParam>)                        \
-  .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<ReduceParam>) \
   .set_attr<FInferShape>("FInferShape", ReduceShape)                    \
   .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)               \
   .set_attr<FCorrectLayout>("FCorrectLayout",                           \
     ElemwiseFixedLayoutUnknownOut<1, 1>)                                \
-  .set_num_inputs(1)                                                    \
-  .set_num_outputs(1)
+  .set_num_inputs(1)
 
 NNVM_REGISTER_REDUCE_OP(sum)
 .describe(R"code(Computes the sum of array elements over given axes.
@@ -139,20 +159,10 @@ Example::
                     const Array<Tensor>& inputs,
                     const Array<Tensor>& out_info) {
     const ReduceParam& param = nnvm::get<ReduceParam>(attrs.parsed);
-    Array<Expr> axis;
-    if (param.exclude) {
-      std::set<dim_t> exclude_axis;
-      for (dim_t i = 0; i < param.axis.ndim(); ++i) {
-        exclude_axis.insert(param.axis[i]);
-      }
-      for (dim_t i = 0; i < static_cast<int>(inputs[0].ndim()); ++i) {
-        if (exclude_axis.count(i) == 0) {
-          axis.push_back(make_const(Int(32), i));
-        }
-      }
-    } else {
-      axis = ShapeToArray(param.axis);
-    }
+    TShape r_axes = GetReduceAxes(inputs[0]->shape.size(),
+                                  param.axis, param.exclude);
+    if (!r_axes.ndim()) return Array<Tensor> { topi::identity(inputs[0]) };
+    auto axis = ShapeToArray(r_axes);
     return Array<Tensor>{
       topi::sum(inputs[0], axis, param.keepdims) };
 })
@@ -178,7 +188,9 @@ NNVM_REGISTER_REDUCE_OP(max)
                     const Array<Tensor>& inputs,
                     const Array<Tensor>& out_info) {
     const ReduceParam& param = nnvm::get<ReduceParam>(attrs.parsed);
-    auto axis = ShapeToArray(param.axis);
+    TShape r_axes = GetReduceAxes(inputs[0]->shape.size(),
+                                  param.axis, param.exclude);
+    auto axis = ShapeToArray(r_axes);
     return Array<Tensor>{
       topi::max(inputs[0], axis, param.keepdims) };
 })
@@ -210,7 +222,9 @@ NNVM_REGISTER_REDUCE_OP(min)
                     const Array<Tensor>& inputs,
                     const Array<Tensor>& out_info) {
     const ReduceParam& param = nnvm::get<ReduceParam>(attrs.parsed);
-    auto axis = ShapeToArray(param.axis);
+    TShape r_axes = GetReduceAxes(inputs[0]->shape.size(),
+                                  param.axis, param.exclude);
+    auto axis = ShapeToArray(r_axes);
     return Array<Tensor>{
       topi::min(inputs[0], axis, param.keepdims) };
 })
@@ -233,6 +247,20 @@ NNVM_REGISTER_REDUCE_OP(min)
     };
 });
 
+NNVM_REGISTER_BASE_REDUCE_OP(collapse_sum)
+.add_argument("data", "Tensor", "The input")
+.add_argument("as", "Tensor", "The reference")
+.set_attr<FInferShape>("FInferShape", CollapseShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<2, 1>)
+.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseFixedLayoutUnknownOut<2, 1>)
+.set_num_inputs(2)
+.describe(R"code(Reduces lhs to the shape of rhs via sum)code" NNVM_ADD_FILELINE)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    return Array<Tensor>{ topi::collapse_sum(inputs[0], inputs[1]->shape) };
+});
 
 }  // namespace top
 }  // namespace nnvm
diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
index bdc8dc5a9c40..98cab9bbd1b1 100644
--- a/nnvm/src/top/tensor/transform.cc
+++ b/nnvm/src/top/tensor/transform.cc
@@ -240,10 +240,8 @@ will return a new array with shape ``(2,1,1,1,1,1,3,4)``.
 .set_attr<FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds){
-    const ExpandDimsParam& param = nnvm::get<ExpandDimsParam>(n->attrs.parsed);
     return std::vector<NodeEntry> {
-      MakeNode("sum", n->attrs.name + "_grad", {ograds[0]},
-               {{"axis", std::to_string(param.axis)}})
+      MakeNode("collapse_sum", n->attrs.name + "_grad", {ograds[0], n->inputs[0]})
     };
 })
 .set_support_level(1);
diff --git a/nnvm/tests/python/compiler/test_top_level4.py b/nnvm/tests/python/compiler/test_top_level4.py
index b202d1aad862..f6c26903fc15 100644
--- a/nnvm/tests/python/compiler/test_top_level4.py
+++ b/nnvm/tests/python/compiler/test_top_level4.py
@@ -81,7 +81,23 @@ def verify_reduce(dshape, fnp, fsym, **kwargs):
         np.testing.assert_allclose(out.asnumpy(), out_np, atol=1e-5, rtol=1e-5)
 
 
-def test_tranpose():
+def verify_collapse(dshape, target_shape, fnp):
+    x = sym.Variable("x", shape=dshape)
+    t = sym.Variable("t", shape=target_shape)
+    y = sym.collapse_sum(x, t)
+    dtype = "float32"
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target,
+                                            {"x": dshape, "t": target_shape})
+        m = graph_runtime.create(graph, lib, ctx)
+        data = np.random.uniform(size=dshape).astype(dtype)
+        m.run(x=data)
+        out = m.get_output(0, tvm.nd.empty(target_shape))
+        out_np = fnp(data)
+        np.testing.assert_allclose(out.asnumpy(), out_np, atol=1e-5, rtol=1e-5)
+
+
+def test_transpose():
     verify_transpose((2, 3, 4), (0, 2, 1))
     verify_transpose((2, 3, 4), None)
 
@@ -90,6 +106,22 @@ def test_reduce():
     verify_reduce((2, 3, 4), np.max, sym.max, axis=1, keepdims=True)
     verify_reduce((4, 4, 3), np.min, sym.min, keepdims=True)
     verify_reduce((4, 4, 3), np.sum, sym.sum, axis=(0, 2))
+    verify_reduce((4, 4, 3), np.sum, sym.sum)
+
+
+def test_collapse():
+    verify_collapse((2, 3, 4), (1,), lambda x: x.sum())
+    verify_collapse((2, 3, 4), (1, 1, 1), lambda x: x.sum(keepdims=True))
+    verify_collapse((2, 3, 4), (1, 1), lambda x: x.sum().reshape(1, 1))
+    verify_collapse((2, 3, 4), (1, 4), lambda x: x.reshape(-1, 4).sum(0, keepdims=True))
+    verify_collapse((2, 3, 4), (3, 4), lambda x: x.sum(0))
+    verify_collapse((2, 3, 4), (1, 3, 4), lambda x: x.sum(0, keepdims=True))
+    verify_collapse((2, 3, 4), (1, 1, 4), lambda x: x.sum((0, 1), keepdims=True))
+    verify_collapse((2, 3, 4), (2, 1, 4), lambda x: x.sum(1, keepdims=True))
+    verify_collapse((2, 3, 4), (2, 1, 1), lambda x: x.sum((1, 2), keepdims=True))
+    verify_collapse((2, 3, 4), (2, 3, 1), lambda x: x.sum(2, keepdims=True))
+    verify_collapse((2, 3, 4), (2, 3, 4), lambda x: x)
+
 
 def verify_flip(ishape, axis):
     x = sym.Variable("x")
@@ -106,6 +138,7 @@ def verify_flip(ishape, axis):
         out = m.get_output(0, tvm.nd.empty(res.shape))
         np.testing.assert_allclose(out.asnumpy(), res, atol=1e-5, rtol=1e-5)
 
+
 def test_flip():
     verify_flip((3, 4, 3), 1)
     verify_flip((3, 4, 3), 0)
@@ -114,6 +147,7 @@ def test_flip():
     verify_flip((3, 4, 3), -3)
     verify_flip((3, 4, 3), -2)
 
+
 def verify_reshape(dshape, oshape):
     x = sym.Variable("x")
     y = sym.reshape(x, shape=oshape)
@@ -156,6 +190,45 @@ def backward(head_grads, x):
     helper(y, inputs, dtype, forward, backward)
 
 
+def test_broadcast():
+    a = sym.Variable("a")
+    b = sym.Variable("b")
+    inputs = [('a', (3, 4, 5), a),
+              ('b', (1, 5), b)]
+    dtype = "float32"
+
+    def _collapse(g):
+        return g.reshape(-1, inputs[-1][1][-1]).sum(0, keepdims=True)
+
+    y = sym.broadcast_add(a, b)
+    def _backward_add(head_grads, a, b):
+        da = head_grads
+        db = _collapse(head_grads)
+        return da, db
+    helper(y, inputs, dtype, lambda a, b: a + b, _backward_add)
+
+    y = sym.broadcast_sub(a, b)
+    def _backward_sub(head_grads, a, b):
+        da = head_grads
+        db = -_collapse(head_grads)
+        return da, db
+    helper(y, inputs, dtype, lambda a, b: a - b, _backward_sub)
+
+    y = sym.broadcast_mul(a, b)
+    def _backward_mul(head_grads, a, b):
+        da = head_grads * b
+        db = _collapse(head_grads * a)
+        return da, db
+    helper(y, inputs, dtype, lambda a, b: a * b, _backward_mul)
+
+    y = sym.broadcast_div(a, b)
+    def _backward_div(head_grads, a, b):
+        da = head_grads / b
+        db = _collapse(head_grads * a / (2 * b**2))
+        return da, db
+    helper(y, inputs, dtype, lambda a, b: a / b, _backward_div)
+
+
 def test_greater():
     l = sym.Variable("l")
     r = sym.Variable("r")
@@ -472,8 +545,10 @@ def test_nms():
 
 if __name__ == "__main__":
     test_reshape()
+    test_broadcast()
     test_reduce()
-    test_tranpose()
+    test_collapse()
+    test_transpose()
     test_clip()
     test_greater()
     test_less()
diff --git a/topi/include/topi/reduction.h b/topi/include/topi/reduction.h
index cb89be30a32d..f14187471faf 100644
--- a/topi/include/topi/reduction.h
+++ b/topi/include/topi/reduction.h
@@ -6,12 +6,15 @@
 #ifndef TOPI_REDUCTION_H_
 #define TOPI_REDUCTION_H_
 
+#include <algorithm>
 #include <string>
 #include <set>
 #include <vector>
 #include <iterator>
 
+#include "topi/elemwise.h"
 #include "topi/tags.h"
+#include "topi/transform.h"
 #include "topi/detail/ravel_unravel.h"
 #include "topi/detail/constant_utils.h"
 #include "tvm/tvm.h"
@@ -91,6 +94,9 @@ inline Array<Expr> MakeReduceTargetShape(const std::vector<int>& real_axis,
         target_shape.push_back(data->shape[i]);
       }
     }
+    if (target_shape.size() == 0) {
+      target_shape.push_back(1);
+    }
   }
   return target_shape;
 }
@@ -99,54 +105,71 @@ inline Array<Expr> MakeReduceTargetShape(const std::vector<int>& real_axis,
  * \brief Create a reduction operation.
  *
  * \param data The input tensor.
- * \param axis The axes along which the reduction is performed.
  * \param func The reduction function eg. tvm::sum
- * \param keepdims If this is set to true, the axes which are reduced are
- * left in the result as dimensions with size one. This enables the result
- * to broadcast correctly against the input array.
+ * \param target_shape The output Tensor shape.
+ * \param reduce_axes The real axes along which the reduction is performed.
+ * \param squeeze_axes The real axes to squeeze. Unsqueezed, reduced axes will
+ *                     have shape 1 in the output tensor.
  *
  * \return The result tensor.
  */
-inline Tensor CommReduce(const Tensor& data,
-                         const Array<Expr>& axis,
-                         FReduce func,
-                         bool keepdims = false) {
-  auto ndim = data->shape.size();
-  CHECK_NE(ndim, 0) << "Cannot reduce a 0 dim Tensor";
-  auto axis_val = detail::GetConstIntValues(axis, "axis");
-  auto real_axis = GetRealAxis(static_cast<int>(ndim), axis_val);
-  auto reduce_axes = MakeReduceAxes(real_axis, data);
-  auto target_shape = MakeReduceTargetShape(real_axis, data, keepdims);
-
-  auto compute = [ndim, keepdims, &real_axis, &reduce_axes, &func, &data]
-  (const Array<Var>& indices) {
+inline Tensor DoCommReduce(const Tensor& data,
+                           FReduce func,
+                           const Array<Expr>& target_shape,
+                           const std::vector<int>& reduce_axes,
+                           const std::vector<int>& squeeze_axes) {
+  auto r_axes = MakeReduceAxes(reduce_axes, data);
+  auto compute = [&](const Array<Var>& indices) {
     Array<Expr> eval_range;
     Array<Var> eval_indices;
     int arg_counter = 0;
     int red_counter = 0;
 
-    for (size_t i = 0; i < ndim; ++i) {
-      if (std::find(real_axis.begin(), real_axis.end(), i) != real_axis.end()) {
+    for (size_t i = 0; i < data->shape.size(); ++i) {
+      bool squeeze_i = std::find(squeeze_axes.begin(), squeeze_axes.end(), i) != squeeze_axes.end();
+      if (std::find(reduce_axes.begin(), reduce_axes.end(), i) != reduce_axes.end()) {
         // real_axis contains i
-        eval_range.push_back(reduce_axes[red_counter]);
-        eval_indices.push_back(reduce_axes[red_counter]->var);
+        eval_range.push_back(r_axes[red_counter]);
+        eval_indices.push_back(r_axes[red_counter]->var);
         red_counter++;
-      } else {
-        if (!keepdims) {
-          eval_range.push_back(indices[arg_counter]);
-          arg_counter++;
-        } else {
-          eval_range.push_back(indices[i]);
-        }
+        arg_counter += !squeeze_i;
+        continue;
       }
+      eval_range.push_back(indices[arg_counter]);
+      arg_counter++;
     }
 
-    return func(data(eval_range), reduce_axes);
+    return func(data(eval_range), r_axes);
   };
 
   return tvm::compute(target_shape, compute, data->op->name + "_red", kCommReduce);
 }
 
+/*!
+ * \brief Create a reduction operation.
+ *
+ * \param data The input tensor.
+ * \param axis The axes along which the reduction is performed.
+ * \param func The reduction function eg. tvm::sum
+ * \param keepdims If this is set to true, the axes which are reduced are
+ * left in the result as dimensions with size one. This enables the result
+ * to broadcast correctly against the input array.
+ *
+ * \return The result tensor.
+ */
+inline Tensor CommReduce(const Tensor& data,
+                         const Array<Expr>& axis,
+                         FReduce func,
+                         bool keepdims = false) {
+  auto ndim = data->shape.size();
+  CHECK_NE(ndim, 0) << "Cannot reduce a 0 dim Tensor";
+  auto axis_val = detail::GetConstIntValues(axis, "axis");
+  auto real_axis = GetRealAxis(static_cast<int>(ndim), axis_val);
+  auto target_shape = MakeReduceTargetShape(real_axis, data, keepdims);
+  return DoCommReduce(data, func, target_shape, real_axis,
+      keepdims ? std::vector<int>() : real_axis);
+}
+
 /*!
 * \brief Create an index reduction operation.
 *
@@ -281,6 +304,34 @@ inline Tensor sum(const Tensor& data, Array<Expr> axis, bool keepdims = false) {
   return CommReduce(data, axis, tvm::sum, keepdims);
 }
 
+inline Tensor collapse_sum(const Tensor& data, Array<Expr> target_shape) {
+  CHECK_GE(data->shape.size(), target_shape.size());
+  auto ishape = detail::GetConstIntValues(data->shape, "ishape");
+  auto oshape = detail::GetConstIntValues(target_shape, "oshape");
+
+  std::vector<int> reduce_axes;
+  std::vector<int> squeeze_axes;
+  for (int i_ax = ishape.size() - 1,
+      o_ax = oshape.size() - 1; i_ax >= 0; --i_ax) {
+    if (o_ax >= 0 && ishape[i_ax] == oshape[o_ax]) {
+      --o_ax;
+      continue;
+    }
+    reduce_axes.push_back(i_ax);
+    if (o_ax < 0) {  // squeeze o_ax if was added during expansion
+      squeeze_axes.push_back(i_ax);
+    } else if (oshape[o_ax] == 1) {
+      --o_ax;
+    }
+  }
+
+  if (reduce_axes.size() == 0) return topi::identity(data, "tensor", kCommReduce);
+
+  std::reverse(reduce_axes.begin(), reduce_axes.end());
+  std::reverse(squeeze_axes.begin(), squeeze_axes.end());
+  return DoCommReduce(data, tvm::sum, target_shape, reduce_axes, squeeze_axes);
+}
+
 /*!
 * \brief Creates an operation that finds the minimum of elements over
 * a given axis.
diff --git a/topi/python/topi/cuda/reduction.py b/topi/python/topi/cuda/reduction.py
index 601d11ab9a2f..c1f7d19d82fb 100644
--- a/topi/python/topi/cuda/reduction.py
+++ b/topi/python/topi/cuda/reduction.py
@@ -4,6 +4,7 @@
 import tvm
 from .. import tag
 from .. import generic
+from .injective import _schedule_injective
 
 def _schedule_reduce(op, sch, is_idx_reduce=False):
     if is_idx_reduce:
@@ -11,7 +12,9 @@ def _schedule_reduce(op, sch, is_idx_reduce=False):
     else:
         data_in = op.input_tensors[0]
         data_out = op.output(0)
-    assert len(sch[data_out].op.reduce_axis) > 0, "reduce_axis must be bigger than zero!"
+
+    if not sch[data_out].op.reduce_axis:
+        return _schedule_injective(op, sch)
 
     if len(sch[data_out].op.axis) > 0:
         all_reduce = False
diff --git a/topi/tests/python_cpp/test_topi_reduce.py b/topi/tests/python_cpp/test_topi_reduce.py
index b4904d5380ca..7bf369c7f1ff 100644
--- a/topi/tests/python_cpp/test_topi_reduce.py
+++ b/topi/tests/python_cpp/test_topi_reduce.py
@@ -73,6 +73,7 @@ def check_device(device):
             out_npy = _my_npy_argmin(in_npy_map, axis=axis, keepdims=keepdims)
         else:
             raise NotImplementedError
+        out_npy = np.atleast_1d(out_npy)
         data_tvm = tvm.nd.array(in_npy, ctx=ctx)
         out_tvm = tvm.nd.empty(shape=out_npy.shape, ctx=ctx, dtype=out_dtype)
         for _ in range(1):

From c6feab2e3972055f25d66199894130431f51b82a Mon Sep 17 00:00:00 2001
From: Tatsuya Nishiyama <nishiyama.tatsuya0@gmail.com>
Date: Wed, 27 Jun 2018 10:40:31 +0900
Subject: [PATCH 768/949] [TOPI] Add C++ implementation of elementwise
 operators (#1306)

---
 nnvm/python/nnvm/top/tensor.py            |  2 +-
 topi/include/topi/elemwise.h              | 73 ++++++++++++++++++++
 topi/python/topi/tensor.py                | 17 ++---
 topi/src/topi.cc                          | 15 +++++
 topi/tests/python/test_topi_tensor.py     |  2 +-
 topi/tests/python_cpp/test_topi_tensor.py | 81 +++++++++++++++++++++++
 6 files changed, 176 insertions(+), 14 deletions(-)
 create mode 100644 topi/tests/python_cpp/test_topi_tensor.py

diff --git a/nnvm/python/nnvm/top/tensor.py b/nnvm/python/nnvm/top/tensor.py
index 0250f6ddfad8..a9ff8c8fe4c3 100644
--- a/nnvm/python/nnvm/top/tensor.py
+++ b/nnvm/python/nnvm/top/tensor.py
@@ -187,7 +187,7 @@ def compute_elemwise_sum(attrs, inputs, _):
     """Compute definition of elemwise sum"""
     num_args = attrs.get_int("num_args")
     assert num_args == len(inputs), "Number of tensors does not match num_args."
-    return topi.tensor.elemwise_sum(inputs, num_args)
+    return topi.tensor.elemwise_sum(inputs)
 reg.register_pattern("elemwise_sum", OpPattern.ELEMWISE)
 reg.register_schedule("elemwise_sum", _fschedule_elemwise)
 
diff --git a/topi/include/topi/elemwise.h b/topi/include/topi/elemwise.h
index 2e4a95e8691b..7082106d09ee 100644
--- a/topi/include/topi/elemwise.h
+++ b/topi/include/topi/elemwise.h
@@ -10,6 +10,8 @@
 
 #include "topi/tags.h"
 #include "tvm/tvm.h"
+#include "tvm/ir.h"
+#include "tvm/ir_pass.h"
 
 namespace topi {
 using namespace tvm;
@@ -122,5 +124,76 @@ inline Tensor cast(const Tensor& x,
   }, name, tag);
 }
 
+/*!
+* \brief Creates an operation that sum each element of a tensor
+*
+* \param xs The input tensor array
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the sum operation
+*/
+inline Tensor elemwise_sum(const Array<Tensor>& xs,
+                           std::string name = "tensor",
+                           std::string tag = kElementWise) {
+  CHECK_GT(xs.size(), 0) << "elemwise sum must have at least one input tensor.";
+  return compute(xs[0]->shape, [&](const Array<Var>& i) {
+      auto sum_expr = xs[0](i);
+      for (size_t j = 1; j < xs.size(); j++) {
+        sum_expr = sum_expr + xs[j](i);
+      }
+      return sum_expr;
+  }, name, tag);
+}
+
+/*!
+* \brief Creates an operation that fill a tensor with fill_value
+*
+* \param shape The shape of a tensor
+* \param dtype The Type of fill_value
+* \param fill_value The value to be filled
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the full operation
+*/
+inline Tensor full(const Array<Expr>& shape,
+                   Type dtype,
+                   const Expr fill_value,
+                   std::string name = "tensor",
+                   std::string tag = kElementWise) {
+  Expr ev = lossless_cast(dtype, fill_value);
+  if (!ev.defined()) {
+    LOG(ERROR) << "Can't cast fill_value to " << dtype;
+  }
+  return compute(shape, [&](const Array<Var>& i) {
+      return ev;
+  }, name, tag);
+}
+
+/*!
+* \brief Creates an operation that construct a tensor with same shape as input tensor, 
+* then fill a tensor with fill_value
+*
+* \param x The input tensor
+* \param fill_value The value to be filled
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op memeber is the full_like operation
+*/
+inline Tensor full_like(const Tensor& x,
+                        const Expr fill_value,
+                        std::string name = "tensor",
+                        std::string tag = kElementWise) {
+  Expr ev = lossless_cast(x->dtype, fill_value);
+  if (!ev.defined()) {
+    LOG(ERROR) << "Can't cast fill_value to " << x->dtype;
+  }
+  return compute(x->shape, [&](const Array<Var>& i) {
+      return ev;
+  }, name, tag);
+}
+
 }  // namespace topi
 #endif  // TOPI_ELEMWISE_H_
diff --git a/topi/python/topi/tensor.py b/topi/python/topi/tensor.py
index 9b15cf0d8515..6fcddedbe445 100644
--- a/topi/python/topi/tensor.py
+++ b/topi/python/topi/tensor.py
@@ -2,30 +2,24 @@
 """Elementwise operators"""
 from __future__ import absolute_import as _abs
 import tvm
+from . import cpp
 from . import tag
 
 @tvm.tag_scope(tag=tag.ELEMWISE)
-def elemwise_sum(xs, num_args):
+def elemwise_sum(xs):
     """Perform element-wise sum on inputs
 
     Parameters
     ----------
     xs : list of tvm.Tensor
         Input arguments.
-    num_args : int
-        Number of arguments
 
     Returns
     -------
     y : tvm.Tensor
         The result.
     """
-    assert len(xs) > 0, "elemwise sum must have at least one input tensor."
-
-    def _compute(*i):
-        return sum([x(*i) for x in xs])
-
-    return tvm.compute(xs[0].shape, _compute)
+    return cpp.elemwise_sum(xs)
 
 
 @tvm.tag_scope(tag=tag.ELEMWISE)
@@ -46,7 +40,7 @@ def full(shape, dtype, fill_value):
     y : tvm.Tensor
         The result.
     """
-    return tvm.compute(shape, lambda *i: tvm.const(fill_value, dtype))
+    return cpp.full(shape, dtype, fill_value)
 
 
 @tvm.tag_scope(tag=tag.ELEMWISE)
@@ -66,5 +60,4 @@ def full_like(x, fill_value):
     y : tvm.Tensor
         The result.
     """
-    dtype = x.dtype
-    return tvm.compute(x.shape, lambda *i: tvm.const(fill_value, dtype))
+    return cpp.full_like(x, fill_value)
diff --git a/topi/src/topi.cc b/topi/src/topi.cc
index 19293048139a..59652ed6680e 100644
--- a/topi/src/topi.cc
+++ b/topi/src/topi.cc
@@ -163,6 +163,21 @@ TVM_REGISTER_GLOBAL("topi.cast")
   *rv = cast(args[0], args[1]);
   });
 
+TVM_REGISTER_GLOBAL("topi.elemwise_sum")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = elemwise_sum(args[0]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.full")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = full(args[0], args[1], args[2]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.full_like")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = full_like(args[0], args[1]);
+  });
+
 /* Ops from nn.h */
 TVM_REGISTER_GLOBAL("topi.nn.relu")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
diff --git a/topi/tests/python/test_topi_tensor.py b/topi/tests/python/test_topi_tensor.py
index 3ac30db78aa2..3d563c21b5c4 100644
--- a/topi/tests/python/test_topi_tensor.py
+++ b/topi/tests/python/test_topi_tensor.py
@@ -11,7 +11,7 @@ def verify_elemwise_sum(num_args, dtype):
     for i in range(num_args):
         tvm_placeholders.append(
             tvm.placeholder(shape, name="data"+str(i), dtype=dtype))
-    esum = topi.elemwise_sum(tvm_placeholders, num_args=num_args)
+    esum = topi.elemwise_sum(tvm_placeholders)
     s = tvm.create_schedule([esum.op])
 
     @memoize("topi.tests.test_topi_elemwise_sum")
diff --git a/topi/tests/python_cpp/test_topi_tensor.py b/topi/tests/python_cpp/test_topi_tensor.py
new file mode 100644
index 000000000000..1a0a7c92db7e
--- /dev/null
+++ b/topi/tests/python_cpp/test_topi_tensor.py
@@ -0,0 +1,81 @@
+"""Test code for tensor operator"""
+import numpy as np
+import tvm
+import topi
+
+def verify_elemwise_sum(num_args, dtype):
+    shape = (3,5,4)
+
+    tvm_placeholders = []
+    for i in range(num_args):
+        tvm_placeholders.append(
+            tvm.placeholder(shape, name="data"+str(i), dtype=dtype))
+    esum = topi.cpp.elemwise_sum(tvm_placeholders)
+    s = tvm.create_schedule([esum.op])
+
+    def get_ref_data():
+        np_nd = [np.random.uniform(0, 10, size=shape).astype(dtype)
+                 for i in range(num_args)]
+        return np_nd
+    np_nd = get_ref_data()
+
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+
+        ctx = tvm.context(device, 0)
+        out = tvm.nd.array(np.zeros(shape, dtype=dtype), ctx)
+        f = tvm.build(s, tvm_placeholders + [esum], device, name="elemwise_sum")
+        tvm_nd = [tvm.nd.array(nd, ctx) for nd in np_nd] + [out]
+        f(*tvm_nd)
+        np_out = np.sum(np.array(np_nd), axis=0)
+        np.testing.assert_allclose(out.asnumpy(), np_out, rtol=1e-5)
+
+    for device in ["llvm"]:
+        check_device(device)
+
+
+def verify_full(shape, dtype, fill_value):
+    A = tvm.placeholder(shape, dtype=dtype, name="A")
+    B = topi.cpp.full_like(A, fill_value)
+    C = topi.cpp.full(shape, dtype, fill_value)
+    s1 = tvm.create_schedule([B.op])
+    s2 = tvm.create_schedule([C.op])
+
+    def get_ref_data():
+        return np.full(shape, fill_value, dtype)
+    np_nd = get_ref_data()
+
+    def check_device(device):
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        target = topi.cpp.TEST_create_target(device)
+        ctx = tvm.context(device, 0)
+        out = tvm.nd.array(np.zeros(shape, dtype=dtype), ctx)
+        f = tvm.build(s1, [A, B], device, name="full_like")
+        f(tvm.nd.array(np.zeros(shape, dtype), ctx), out)
+        np.testing.assert_allclose(out.asnumpy(), np_nd, rtol=1e-5)
+
+        f = tvm.build(s2, [C], device, name="full")
+        f(out)
+        np.testing.assert_allclose(out.asnumpy(), np_nd, rtol=1e-5)
+
+    for device in ["llvm"]:
+        check_device(device)
+
+
+def test_elemwise_sum():
+    verify_elemwise_sum(1, "float32")
+    verify_elemwise_sum(5, "float32")
+    verify_elemwise_sum(4, "int32")
+
+
+def test_full():
+    verify_full((3,4,5), "float32", 3.14)
+    verify_full((10,), "int32", 7)
+
+if __name__ == "__main__":
+    test_elemwise_sum()
+    test_full()

From 9944ec044722d04ad3621c77e3173e5c8d7dd9d9 Mon Sep 17 00:00:00 2001
From: Jian Weng <werefluke@gmail.com>
Date: Tue, 26 Jun 2018 18:41:17 -0700
Subject: [PATCH 769/949] [FRONTEND] [HYBIRD] [TEST] Add GPU shared memory
 test! (#1338)

---
 src/api/api_pass.cc                         |   9 +
 tests/python/unittest/test_hybrid_script.py | 197 ++++++++------------
 2 files changed, 85 insertions(+), 121 deletions(-)

diff --git a/src/api/api_pass.cc b/src/api/api_pass.cc
index adfe198ebf54..a0048a2ed771 100644
--- a/src/api/api_pass.cc
+++ b/src/api/api_pass.cc
@@ -47,6 +47,15 @@ TVM_REGISTER_API("ir_pass.CanonicalSimplify")
     }
   });
 
+TVM_REGISTER_API("ir_pass.Substitute")
+.set_body([](TVMArgs args, TVMRetValue *ret) {
+    if (args[0].IsNodeType<Stmt>()) {
+      *ret = Substitute(args[0].operator Stmt(), args[1].operator Map<Var, Expr>());
+    } else {
+      *ret = Substitute(args[0].operator Expr(), args[1].operator Map<Var, Expr>());
+    }
+  });
+
 TVM_REGISTER_API("ir_pass.Equal")
 .set_body([](TVMArgs args, TVMRetValue *ret) {
     if (args[0].IsNodeType<Stmt>()) {
diff --git a/tests/python/unittest/test_hybrid_script.py b/tests/python/unittest/test_hybrid_script.py
index 06f24f2adaf5..0f500d7c704f 100644
--- a/tests/python/unittest/test_hybrid_script.py
+++ b/tests/python/unittest/test_hybrid_script.py
@@ -1,7 +1,45 @@
-import tvm, inspect, sys, traceback, numpy
+import tvm, inspect, sys, traceback, numpy, nose
 from tvm.hybrid import script
 from tvm.hybrid.intrin import HYBRID_GLOBALS
 
+@nose.tools.nottest
+def run_and_check(func, args, outs, var_dict={}, target='llvm'):
+    def tvm_val_2_py_val(val):
+        val = tvm.ir_pass.Substitute(val, var_dict)
+        val = tvm.ir_pass.Simplify(val)
+        assert isinstance(val, (tvm.expr.IntImm, tvm.expr.UIntImm))
+        return val.value
+
+    ctx = tvm.context(target, 0)
+
+    emu_args = []
+    nd_args = []
+    to_check = []
+    for i in args:
+        if isinstance(i, tvm.tensor.Tensor):
+            shape = [tvm_val_2_py_val(j) for j in i.shape]
+            if i in outs:
+                emu_args.append(numpy.zeros(shape).astype(i.dtype))
+                nd_args.append(tvm.nd.array(emu_args[-1], ctx))
+                to_check.append((nd_args[-1], emu_args[-1]))
+            else:
+                emu_args.append(numpy.random.randn(*shape).astype(i.dtype))
+                nd_args.append(tvm.nd.array(emu_args[-1], ctx))
+        else:
+            assert isinstance(i, tvm.expr.Var)
+            emu_args.append(tvm_val_2_py_val(i))
+            nd_args.append(emu_args[-1])
+
+    func(*emu_args)
+
+    lowerd_func = tvm.lower(func(*args), args)
+    module = tvm.build(lowerd_func, target=target)
+    assert module
+    module(*nd_args)
+
+    for nd, np in to_check:
+        numpy.testing.assert_allclose(nd.asnumpy(), np, rtol=1e-5, atol=1e-5)
+
 
 @script
 def outer_product(n, m, a, b, c):
@@ -45,18 +83,7 @@ def test_outer_product():
     func = tvm.lower(ir, [n, m, a, b, c])
     func = tvm.build(func)
 
-    _n = 999
-    _m = 1001
-    _a = numpy.random.rand(_n).astype('float32')
-    _b = numpy.random.rand(_m).astype('float32')
-    c_python = numpy.zeros((_n, _m), dtype='float32')
-    outer_product(_n, _m, _a, _b, c_python)
-
-    tvm_a = tvm.ndarray.array(_a)
-    tvm_b = tvm.ndarray.array(_b)
-    tvm_c = tvm.ndarray.array(numpy.zeros((_n, _m), dtype='float32'))
-    func(_n, _m, tvm_a, tvm_b, tvm_c)
-    numpy.testing.assert_allclose(tvm_c.asnumpy(), c_python, rtol=1e-5)
+    run_and_check(outer_product, [n, m, a, b, c], [c], {n: 999, m: 1001})
 
     for key, _ in HYBRID_GLOBALS.items():
         assert key not in globals().keys()
@@ -135,19 +162,7 @@ def fanout(n, a, b):
     assert len(write.value.args) == 1
     assert write.value.args[0].value == 0
 
-    func = tvm.build(tvm.lower(ir, [n, a, b]))
-    assert func
-
-    np_a = numpy.random.randn(10).astype('float32')
-    np_b = numpy.zeros(7).astype('float32')
-
-    nd_a = tvm.ndarray.array(np_a)
-    nd_b = tvm.ndarray.array(np_b)
-
-    fanout(10, np_a, np_b)
-    func(10, nd_a, nd_b)
-
-    numpy.testing.assert_allclose(nd_b.asnumpy(), np_b, rtol=1e-5, atol=1e-5)
+    run_and_check(fanout, [n, a, b], [b], {n: 10})
 
 
 @script
@@ -160,7 +175,7 @@ def test_failure():
         tvm.hybrid.parse(failure, [])
     except IOError as err:
         assert sys.version_info[0] == 2
-        print('[Warning] Python2 cannot do the failure case because "%s"' % str(err))
+        print('[Warning] Case test_failure is skipped by Python2 because "%s"' % str(err))
     except Exception as err:
         assert str(err) == 'You CAN NEVER overwrite a loop variable!'
 
@@ -186,22 +201,7 @@ def looptype(a, b, c):
     assert jloop.for_type == tvm.stmt.For.Vectorized
     assert kloop.for_type == tvm.stmt.For.Unrolled
 
-    func = tvm.build(tvm.lower(ir, [a, b, c]))
-
-    np_a = numpy.zeros((8, )).astype('int32')
-    np_b = numpy.zeros((8, )).astype('int32')
-    np_c = numpy.zeros((8, )).astype('int32')
-
-    nd_a = tvm.ndarray.array(np_a)
-    nd_b = tvm.ndarray.array(np_b)
-    nd_c = tvm.ndarray.array(np_c)
-
-    looptype(np_a, np_b, np_c)
-    func(nd_a, nd_b, nd_c)
-
-    numpy.testing.assert_allclose(np_a, nd_a.asnumpy())
-    numpy.testing.assert_allclose(np_b, nd_b.asnumpy())
-    numpy.testing.assert_allclose(np_c, nd_c.asnumpy())
+    run_and_check(looptype, [a, b, c], [a, b, c])
 
 
 def test_if():
@@ -217,26 +217,13 @@ def if_then_else(a, b):
 
     a = tvm.placeholder((10, ), dtype='int32', name='a')
     b = tvm.placeholder((10, ), dtype='int32', name='b')
-    ir = if_then_else(a, b)
-    func = tvm.lower(ir, [a, b])
-    func = tvm.build(func)
-    assert func
-
-    _a = numpy.zeros((10, ), dtype = 'int32')
-    _b = numpy.zeros((10, ), dtype = 'int32')
-    if_then_else(_a, _b)
 
-    tvm_a = tvm.ndarray.array(numpy.zeros((10, ), dtype='int32'))
-    tvm_b = tvm.ndarray.array(numpy.zeros((10, ), dtype='int32'))
-    func(tvm_a, tvm_b)
+    run_and_check(if_then_else, [a, b], [a, b])
 
-    numpy.testing.assert_allclose(tvm_a.asnumpy(), _a, rtol=1e-5)
-    numpy.testing.assert_allclose(tvm_b.asnumpy(), _b, rtol=1e-5)
-    numpy.testing.assert_allclose(tvm_a.asnumpy(), tvm_b.asnumpy(), rtol=1e-5)
 
 def test_bind():
     if not tvm.gpu(0).exist:
-        print('No GPU found! Skip this test!')
+        print('[Warning] No GPU found! Skip bind test!')
         return
     @script
     def vec_add(a, b, c):
@@ -246,24 +233,8 @@ def vec_add(a, b, c):
     a = tvm.placeholder((1000, ), dtype='float32', name='a')
     b = tvm.placeholder((1000, ), dtype='float32', name='b')
     c = tvm.placeholder((1000, ), dtype='float32', name='c')
-    ir = vec_add(a, b, c)
 
-    func = tvm.lower(ir, [a, b, c])
-    func = tvm.build(func, target = 'cuda')
-
-    _a = numpy.random.rand(1000).astype('float32')
-    _b = numpy.random.rand(1000).astype('float32')
-    _c = numpy.zeros((1000, ), dtype = 'float32')
-
-
-    tvm_a = tvm.ndarray.array(_a, tvm.gpu(0))
-    tvm_b = tvm.ndarray.array(_b, tvm.gpu(0))
-    tvm_c = tvm.ndarray.array(_c, tvm.gpu(0))
-
-    func(tvm_a, tvm_b, tvm_c)
-    vec_add(_a, _b, _c)
-
-    numpy.testing.assert_allclose(_c, tvm_c.asnumpy(), rtol=1e-5)
+    run_and_check(vec_add, [a, b, c], [c], target='cuda')
 
 def test_math_intrin():
     @script
@@ -277,9 +248,9 @@ def intrin_real(a):
         a[6] = min(a[4], a[5])
         a[7] = max(a[5], a[6])
 
-    a6 = tvm.placeholder((8, ), dtype='float32', name='a')
-    ir = intrin_real(a6)
-    func = tvm.build(tvm.lower(ir, [a6]))
+    a8 = tvm.placeholder((8, ), dtype='float32', name='a')
+    ir = intrin_real(a8)
+    func = tvm.build(tvm.lower(ir, [a8]))
     assert func
     a = numpy.arange(2, 10).astype('float32')
     tvm_a = tvm.ndarray.array(a)
@@ -312,23 +283,12 @@ def blur(a, b):
                         s = s + a[i-di, j-dj]
                 b[i-2, j-2] = s / 9.0
     try:
-        np_a = numpy.random.randn(32, 32).astype('float32')
-        np_b = numpy.zeros((30, 30), dtype='float32')
-        blur(np_a, np_b)
-
-        ph_a = tvm.placeholder((32, 32), 'float32', 'a')
-        ph_b = tvm.placeholder((30, 30), 'float32', 'b')
-        ir = tvm.hybrid.parse(blur, [ph_a, ph_b])
-        func = tvm.lower(ir, [ph_a, ph_b])
-        func = tvm.build(func)
-
-        nd_a = tvm.ndarray.array(np_a)
-        nd_b = tvm.ndarray.array(np_b)
-        func(nd_a, nd_b)
-
-        numpy.testing.assert_allclose(np_b, nd_b.asnumpy(), atol=1e-5, rtol=1e-5)
-    except IOError:
-        print('[Warning] Non-zero first test skipped by Python2')
+        a = tvm.placeholder((32, 32), 'float32', 'a')
+        b = tvm.placeholder((30, 30), 'float32', 'b')
+        run_and_check(blur, [a, b], [b])
+    except IOError as err:
+        assert sys.version_info[0] == 2
+        print('[Warning] Case test_non_zero is skipped by Python2 because "%s"' % str(err))
 
     @tvm.hybrid.script
     def triangle(a, b, c):
@@ -340,20 +300,7 @@ def triangle(a, b, c):
     b = tvm.placeholder((10, ), dtype='float32', name='b')
     c = tvm.placeholder((10, 10), dtype='float32', name='c')
 
-    np_a = numpy.random.randn(10).astype('float32')
-    np_b = numpy.random.randn(10).astype('float32')
-    np_c = numpy.zeros((10, 10)).astype('float32')
-
-    nd_a = tvm.ndarray.array(np_a)
-    nd_b = tvm.ndarray.array(np_b)
-    nd_c = tvm.ndarray.array(np_c)
-
-    triangle(np_a, np_b, np_c)
-
-    func = tvm.build(tvm.lower(triangle(a, b, c), [a, b, c]))
-    assert func
-    func(nd_a, nd_b, nd_c)
-    numpy.testing.assert_allclose(nd_c.asnumpy(), np_c)
+    run_and_check(triangle, [a, b, c], [c])
 
 def test_allocate():
     @tvm.hybrid.script
@@ -369,19 +316,27 @@ def blur2d(a, b):
     a = tvm.placeholder((32, 32), 'float32', 'a')
     b = tvm.placeholder((30, 30), 'float32', 'b')
 
-    func = tvm.build(tvm.lower(blur2d(a, b), [a, b]))
-    assert func
-
-    np_a = numpy.random.randn(32, 32).astype('float32')
-    np_b = numpy.zeros((30, 30)).astype('float32')
-
-    nd_a = tvm.ndarray.array(np_a)
-    nd_b = tvm.ndarray.array(np_b)
-
-    func(nd_a, nd_b)
-    blur2d(np_a, np_b)
+    run_and_check(blur2d, [a, b], [b])
+
+    if tvm.gpu().exist:
+        @tvm.hybrid.script
+        def share_vec_add(a, b, c):
+            shared = allocate((256, ), 'float32', 'shared')
+            for i in bind("threadIdx.x", 256):
+                shared[i] = a[i]
+            local = allocate((256, ), 'float32', 'local')
+            for i in bind("threadIdx.x", 256):
+                local[i] = b[i]
+            for i in bind("threadIdx.x", 256):
+                c[i] = shared[i] + local[i]
+
+        a = tvm.placeholder((256, ), dtype='float32', name='a')
+        b = tvm.placeholder((256, ), dtype='float32', name='b')
+        c = tvm.placeholder((256, ), dtype='float32', name='c')
+        run_and_check(share_vec_add, [a, b, c], [c], target='cuda')
+    else:
+        print('[Warning] No GPU found! Skip shared mem test!')
 
-    numpy.testing.assert_allclose(nd_b.asnumpy(), np_b, atol=1e-5, rtol=1e-5)
 
 if __name__ == "__main__":
     test_outer_product()

From c72cd718d74a994fafc3270834d163bac977c42e Mon Sep 17 00:00:00 2001
From: Yao Wang <kevinthesunwy@gmail.com>
Date: Tue, 26 Jun 2018 18:42:17 -0700
Subject: [PATCH 770/949] Improve schedule load, add slice_like (#1299)

---
 nnvm/include/nnvm/top/tensor.h                |  10 ++
 nnvm/python/nnvm/frontend/mxnet.py            |  19 +++-
 nnvm/python/nnvm/top/nn.py                    |  10 +-
 nnvm/python/nnvm/top/transform.py             |   4 +
 nnvm/src/top/elemwise_op_common.h             |   2 +-
 nnvm/src/top/tensor/transform.cc              | 101 ++++++++++++++++++
 nnvm/tests/python/compiler/test_top_level4.py |  55 ++++++++++
 topi/python/topi/generic/nn.py                |  37 ++++---
 topi/python/topi/nn/conv2d.py                 |  31 +++++-
 topi/python/topi/x86/conv2d.py                |  24 +++--
 topi/tests/python_cpp/test_topi_transform.py  |   1 +
 11 files changed, 269 insertions(+), 25 deletions(-)

diff --git a/nnvm/include/nnvm/top/tensor.h b/nnvm/include/nnvm/top/tensor.h
index 6a2beaa2309a..dca02cae92fc 100644
--- a/nnvm/include/nnvm/top/tensor.h
+++ b/nnvm/include/nnvm/top/tensor.h
@@ -259,6 +259,16 @@ struct ClipParam : public dmlc::Parameter<ClipParam> {
   }
 };
 
+struct SliceLikeParam : public dmlc::Parameter<SliceLikeParam> {
+  Tuple<int> axis;
+  DMLC_DECLARE_PARAMETER(SliceLikeParam) {
+    DMLC_DECLARE_FIELD(axis).set_default(Tuple<int>())
+      .describe("List of axes on which input data will be sliced according to the "
+                "corresponding size of the second input. By default will slice "
+                "on all axes. Negative axes are supported.");
+  }
+};
+
 }  // namespace top
 }  // namespace nnvm
 
diff --git a/nnvm/python/nnvm/frontend/mxnet.py b/nnvm/python/nnvm/frontend/mxnet.py
index 1fc673115c68..8d45c6f91c54 100644
--- a/nnvm/python/nnvm/frontend/mxnet.py
+++ b/nnvm/python/nnvm/frontend/mxnet.py
@@ -240,6 +240,21 @@ def _elemwise_sum(inputs, _):
     new_attrs = {'num_args':len(inputs)}
     return _get_nnvm_op('elemwise_sum')(*inputs, **new_attrs)
 
+def _crop_like(inputs, attrs):
+    new_attrs = {}
+    offsets = \
+        tuple([float(x.strip()) for x in attrs.get('offsets').strip('()').split(',')]) \
+            if attrs.get('offsets') is not None else (0, 0)
+    if offsets != (0, 0):
+        raise RuntimeError("Currently only supports offsets to be zero.")
+    center_crop = _parse_bool_str(attrs, 'center_crop', default="False")
+    if center_crop:
+        raise RuntimeError("center crop is not supported.")
+    if len(inputs) < 2:
+        raise RuntimeError("Only support crop_like pattern.")
+    new_attrs["axis"] = [2, 3]
+    return _get_nnvm_op('slice_like')(inputs[0], inputs[1], **new_attrs)
+
 
 def _expand_dims(inputs, attrs):
     op_name, new_attrs = "expand_dims", {}
@@ -255,7 +270,8 @@ def _expand_dims(inputs, attrs):
                   'broadcast_sub', 'broadcast_to', 'cast', 'elemwise_add',
                   'elemwise_div', 'elemwise_mul', 'elemwise_sub', 'exp',
                   'flatten', 'log', 'log_softmax', 'max', 'min', 'negative',
-                  'relu', 'sigmoid', 'softmax', 'sum', 'tanh', 'transpose']
+                  'relu', 'sigmoid', 'slice_like', 'softmax', 'sum', 'tanh',
+                  'transpose']
 
 _convert_map = {
     '_copy'         : _rename('copy'),
@@ -274,6 +290,7 @@ def _expand_dims(inputs, attrs):
     'Concat'        : _concat,
     'Convolution'   : _conv2d,
     'Convolution_v1': _conv2d,
+    'Crop'          : _crop_like,
     'Deconvolution' : _conv2d_transpose,
     'Dropout'       : _dropout,
     'Flatten'       : _rename('flatten'),
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index 78253bc5bc6b..ca1fae1d2089 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -155,10 +155,13 @@ def compute_contrib_conv2d_NCHWc(attrs, inputs, _):
     kh, kw = attrs.get_int_tuple('kernel_size')
     groups = attrs.get_int("groups")
     channels = attrs.get_int("channels")
+    layout = attrs.get_string("layout")
+    out_layout = attrs.get_string("out_layout")
     assert dilation == (1, 1), "not support dilate now"
     if groups == 1:
         # pylint: disable=assignment-from-no-return
-        out = topi.nn.conv2d_NCHWc(inputs[0], inputs[1], channels, (kh, kw), strides, padding)
+        out = topi.nn.conv2d_NCHWc(inputs[0], inputs[1], channels, (kh, kw),
+                                   strides, padding, layout, out_layout)
         # pylint: enable=assignment-from-no-return
     else:
         raise ValueError("not support arbitrary group number > 1 for now")
@@ -176,9 +179,12 @@ def schedule_contrib_conv2d_NCHWc(attrs, outs, target):
     oc = attrs.get_int("channels")
     padding = attrs.get_int_tuple("padding")
     strides = attrs.get_int_tuple("strides")
+    layout = attrs.get_string("layout")
+    out_layout = attrs.get_string("out_layout")
     with tvm.target.create(target):
         if groups == 1:
-            return topi.generic.schedule_conv2d_NCHWc(oc, (kh, kw), strides, padding, outs)
+            return topi.generic.schedule_conv2d_NCHWc(oc, (kh, kw), strides, padding,
+                                                      layout, out_layout, outs)
         else:
             raise ValueError("not support group number > 1 for now")
 
diff --git a/nnvm/python/nnvm/top/transform.py b/nnvm/python/nnvm/top/transform.py
index b4b8779f2a68..6900f85f9217 100644
--- a/nnvm/python/nnvm/top/transform.py
+++ b/nnvm/python/nnvm/top/transform.py
@@ -60,3 +60,7 @@ def compute_reshape_like(attrs, inputs, out_info):
 # split
 reg.register_pattern("split", OpPattern.INJECTIVE)
 reg.register_schedule("split", _fschedule_injective)
+
+# slice_like
+reg.register_pattern("slice_like", OpPattern.INJECTIVE)
+reg.register_schedule("slice_like", _fschedule_injective)
diff --git a/nnvm/src/top/elemwise_op_common.h b/nnvm/src/top/elemwise_op_common.h
index 18f92cb88f7e..e5bb0adcb078 100644
--- a/nnvm/src/top/elemwise_op_common.h
+++ b/nnvm/src/top/elemwise_op_common.h
@@ -320,7 +320,7 @@ inline bool ElemwiseBinaryKeepLeftLayout(const NodeAttrs& attrs,
   .set_attr<nnvm::FInferShape>("FInferShape",                       \
     ElementWiseReduceShape)                                         \
   .set_attr<FCorrectLayout>("FCorrectLayout",                       \
-    ElemwiseFixedLayoutCopyToOut<1, 1>)                             \
+    ElemwiseFixedLayoutCopyToOut<-1, 1>)                             \
   .set_attr<nnvm::FInferType>("FInferType", ElementWiseReduceType)  \
   .add_argument("args", "Symbol[]", "Positional input arguments")
 
diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
index 98cab9bbd1b1..05d3cfcb5cf9 100644
--- a/nnvm/src/top/tensor/transform.cc
+++ b/nnvm/src/top/tensor/transform.cc
@@ -15,6 +15,7 @@
 #include "../elemwise_op_common.h"
 #include "topi/nn/flatten.h"
 #include "topi/transform.h"
+#include "topi/detail/constant_utils.h"
 
 namespace nnvm {
 namespace top {
@@ -877,5 +878,105 @@ Examples::
     return Array<Tensor>{ topi::flip(inputs[0], param.axis) };
 });
 
+// SliceLike
+DMLC_REGISTER_PARAMETER(SliceLikeParam);
+
+inline bool SliceLikeShape(const nnvm::NodeAttrs& attrs,
+                          std::vector<TShape>* in_attrs,
+                          std::vector<TShape>* out_attrs) {
+  CHECK_EQ(in_attrs->size(), 2U);
+  CHECK_EQ(out_attrs->size(), 1U);
+  const SliceLikeParam& param = nnvm::get<SliceLikeParam>(attrs.parsed);
+  const TShape& src_shape = in_attrs->at(0);
+  const TShape& target_shape = in_attrs->at(1);
+  Tuple<dim_t> end_idx;
+  end_idx = Tuple<dim_t>(src_shape);
+  if (param.axis.ndim() == 0) {
+    for (size_t i = 0; i < src_shape.ndim(); ++i) {
+      if (i < target_shape.ndim()) {
+        end_idx[i] = target_shape[i];
+        CHECK_LE(end_idx[i], src_shape[i])
+          << "End index of axis " << i << " exceeds input shape: "
+          << end_idx[i] << " vs " << src_shape[i];
+      }
+    }
+  } else {
+    for (auto i : param.axis) {
+      if (i < 0) {
+        i = src_shape.ndim() + i;
+      }
+      CHECK_LT(i, target_shape.ndim())
+        << "Axis " << i << " exceeds dimension "
+        << target_shape.ndim()<< " of target_shape.";
+      end_idx[i] = target_shape[i];
+      CHECK_LE(end_idx[i], src_shape[i])
+        << "End index of axis " << i << " exceeds input shape: "
+        << end_idx[i] << " vs " << src_shape[i];
+    }
+  }
+  TShape out_shape = TShape(std::move(end_idx));
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, 0, out_shape);
+  return true;
+}
+
+NNVM_REGISTER_OP(slice_like)
+.describe(R"code(Slice the first input respect to the second input.
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "Tensor", "Input data to be sliced.")
+.add_argument("slice_like", "Tensor", "Tensor with target shape")
+.set_num_inputs(2)
+.set_num_outputs(1)
+.add_arguments(SliceLikeParam::__FIELDS__())
+.set_attr_parser(ParamParser<SliceLikeParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<SliceLikeParam>)
+.set_attr<FInferShape>("FInferShape", SliceLikeShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<2, 1>)
+.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseBinaryKeepLeftLayout)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const auto& param = nnvm::get<SliceLikeParam>(attrs.parsed);
+    Array<Expr> src_shape = inputs[0]->shape;
+    Array<Expr> target_shape = inputs[1]->shape;
+    Array<Expr> begin_idx, end_idx, strides;
+    for (size_t i = 0; i < src_shape.size(); ++i) {
+      begin_idx.push_back(make_const(tvm::Int(32), 0));
+      strides.push_back(make_const(tvm::Int(32), 1));
+    }
+    end_idx = Array<Expr>(src_shape);
+    if (param.axis.ndim() == 0) {
+      for (size_t i = 0; i < src_shape.size(); ++i) {
+        if (i < target_shape.size()) {
+          end_idx.Set(i, target_shape[i]);
+          CHECK_LE(topi::GetConstInt(end_idx[i]),
+                   topi::GetConstInt(src_shape[i]))
+            << "End index of axis " << i << " exceeds input shape: "
+            << topi::GetConstInt(end_idx[i]) << " vs "
+            << topi::GetConstInt(src_shape[i]);
+        }
+      }
+    } else {
+      for (int axis : param.axis) {
+        if (axis < 0) {
+          axis = static_cast<int>(src_shape.size()) + axis;
+        }
+        end_idx.Set(static_cast<size_t>(axis), target_shape[axis]);
+        CHECK_LE(topi::GetConstInt(end_idx[axis]),
+                 topi::GetConstInt(src_shape[axis]))
+          << "End index of axis " << axis << " exceeds input shape: "
+          << topi::GetConstInt(end_idx[axis]) << " vs "
+          << topi::GetConstInt(src_shape[axis]);
+      }
+    }
+    return Array<Tensor>{
+      topi::strided_slice(inputs[0], begin_idx, end_idx, strides)
+    };
+})
+.set_attr<FListInputNames>("FListInputNames", [](const NodeAttrs& attrs) {
+    return std::vector<std::string>{"data", "slice_like"};
+})
+.set_support_level(4);
+
 }  // namespace top
 }  // namespace nnvm
diff --git a/nnvm/tests/python/compiler/test_top_level4.py b/nnvm/tests/python/compiler/test_top_level4.py
index f6c26903fc15..33f3ff3e9052 100644
--- a/nnvm/tests/python/compiler/test_top_level4.py
+++ b/nnvm/tests/python/compiler/test_top_level4.py
@@ -541,6 +541,60 @@ def test_nms():
     out = m.get_output(0, tvm.nd.empty(np_result.shape, "float32"))
     np.testing.assert_allclose(out.asnumpy(), np_result, atol=1e-5, rtol=1e-5)
 
+def np_slice_like(np_data, np_shape_like, axis=[]):
+    begin_idx = [0 for _ in np_data.shape]
+    end_idx = list(np_data.shape)
+    if len(axis) > 0:
+        for i in axis:
+            if i < 0:
+                i = len(np_data.shape) + i
+            end_idx[i] = np_shape_like.shape[i]
+    else:
+        for i in range(len(np_data.shape)):
+            if i < len(np_shape_like.shape):
+                end_idx[i] = np_shape_like.shape[i]
+    slice_idx = []
+    for b, e in zip(begin_idx, end_idx):
+        slice_idx.append(slice(b, e))
+    np_result = np_data[slice_idx]
+    return np_result
+
+def verify_slice_like(np_data, np_shape_like, axis=[]):
+    dtype = "float32"
+    np_data = np_data.astype(dtype)
+    np_shape_like = np_shape_like.astype(dtype)
+    np_result = np_slice_like(np_data, np_shape_like, axis)
+    data1 = sym.Variable("data1")
+    data2 = sym.Variable("data2")
+    net = sym.slice_like(data=data1, slice_like=data2, axis=axis)
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(net, target, {"data1": np_data.shape,
+                                                          "data2": np_shape_like.shape})
+        m = graph_runtime.create(graph, lib, ctx)
+        m.set_input(**{"data1": np_data, "data2": np_shape_like})
+        m.run()
+        out = m.get_output(0, tvm.nd.empty(np_result.shape, dtype))
+        np.testing.assert_allclose(out.asnumpy(), np_result, atol=1e-5, rtol=1e-5)
+
+def test_slice_like():
+    np_data = np.random.uniform(size=(3, 4, 5))
+    np_shape_like = np.random.uniform(size=(1, 2, 3))
+    verify_slice_like(np_data, np_shape_like)
+    np_data = np.random.uniform(size=(3, 4, 5))
+    np_shape_like = np.random.uniform(size=(1, 2))
+    verify_slice_like(np_data, np_shape_like)
+    np_data = np.random.uniform(size=(3, 4, 5))
+    np_shape_like = np.random.uniform(size=(1, 2, 3))
+    axis = (1, 2)
+    verify_slice_like(np_data, np_shape_like, axis)
+    np_data = np.random.uniform(size=(3, 4, 5))
+    np_shape_like = np.random.uniform(size=(1, 2, 3))
+    axis = (-1, -3)
+    verify_slice_like(np_data, np_shape_like, axis)
+    np_data = np.random.uniform(size=(1, 3, 224, 224))
+    np_shape_like = np.random.uniform(size=(1, 3, 112, 112))
+    axis = (2, 3)
+    verify_slice_like(np_data, np_shape_like, axis)
 
 
 if __name__ == "__main__":
@@ -561,4 +615,5 @@ def test_nms():
     test_multibox_prior()
     test_multibox_transform_loc()
     test_nms()
+    test_slice_like()
     print(nnvm.compiler.engine.dump())
diff --git a/topi/python/topi/generic/nn.py b/topi/python/topi/generic/nn.py
index 8f2f8612c7fa..08220c15bbf0 100644
--- a/topi/python/topi/generic/nn.py
+++ b/topi/python/topi/generic/nn.py
@@ -55,26 +55,37 @@ def schedule_conv2d_nhwc(outs):
 
 
 @tvm.target.generic_func
-def schedule_conv2d_NCHWc(num_filter, kernel_size, strides, padding, outs):
+def schedule_conv2d_NCHWc(num_filter, kernel_size, strides,
+                          padding, layout, out_layout, outs):
     """Schedule for conv2d_NCHW[x]c
 
     Parameters
     ----------
-    num_filter: int
-                The number of filter, i.e., the output channel.
-    kernel_size: tuple of int
-                 (kernel_height, kernel_width)
-    strides: tuple of int
-             (stride_of_height, stride_of_width)
-    padding: tuple of int
-             (pad_of_height, pad_of_width)
-    outs: Array of Tensor
-          The computation graph description of conv2d_NCHWc
-          in the format of an array of tensors.
+    num_filter : int
+        The number of filter, i.e., the output channel.
+
+    kernel_size : tuple of int
+        (kernel_height, kernel_width)
+
+    strides : tuple of int
+        (stride_of_height, stride_of_width)
+
+    padding : tuple of int
+        (pad_of_height, pad_of_width)
+
+    layout : str
+        Input data layout
+
+    out_layout : str
+        Output data layout
+
+    outs : Array of Tensor
+        The computation graph description of conv2d_NCHWc
+        in the format of an array of tensors.
 
     Returns
     -------
-    sch: Schedule
+    sch : Schedule
         The computation schedule for the op.
     """
     return _default_schedule(outs, False)
diff --git a/topi/python/topi/nn/conv2d.py b/topi/python/topi/nn/conv2d.py
index e0cee9526d14..734ccb3bf2d6 100644
--- a/topi/python/topi/nn/conv2d.py
+++ b/topi/python/topi/nn/conv2d.py
@@ -144,6 +144,17 @@ def _get_workload(data, kernel, stride, padding, out_dtype):
     return Workload(data.dtype, out_dtype, IH, IW, CI, CO, KH, KW, HPAD, WPAD, HSTR, WSTR)
 
 
+@tvm.target.generic_func
+def _get_alter_layout_schedule(wkl):
+    # pylint: disable=unreachable
+    """ Get the platform specific schedule for conv2d_alter_layout. """
+    target = tvm.target.current_target()
+    raise RuntimeError(
+        "No schedule for current target:{}".format(target))
+    # This return has no use, merely to supress pylint warning
+    return wkl
+
+
 @tvm.target.generic_func
 def _get_schedule(wkl):
     # pylint: disable=unreachable
@@ -155,6 +166,17 @@ def _get_schedule(wkl):
     return wkl
 
 
+@tvm.target.generic_func
+def _get_schedule_NCHWc(wkl, layout, out_layout):
+    # pylint: disable=unreachable
+    """ Get the platform specific schedule. """
+    target = tvm.target.current_target()
+    raise RuntimeError(
+        "No schedule for current target:{}".format(target))
+    # This return has no use, merely to supress pylint warning
+    return wkl
+
+
 def _spatial_pack(data, kernel, stride, padding, out_dtype=None):
     """ Compute convolution with pack on spatial axes. """
     if out_dtype is None:
@@ -443,7 +465,8 @@ def conv2d_nhwc(Input, Filter, stride, padding, out_dtype='float32'):
     return Output
 
 @tvm.target.generic_func
-def conv2d_NCHWc(data, kernel, num_filter, kernel_size, stride, padding, out_dtype='float32'):
+def conv2d_NCHWc(data, kernel, num_filter, kernel_size, stride,
+                 padding, layout, out_layout, out_dtype='float32'):
     """Conv2D operator for nChw[x]c layout.
 
     Parameters
@@ -468,6 +491,12 @@ def conv2d_NCHWc(data, kernel, num_filter, kernel_size, stride, padding, out_dty
     padding : int or a list/tuple of two ints
         padding size, or [pad_height, pad_width]
 
+    layout : str
+        Input data layout
+
+    out_layout : str
+        Output data layout
+
     out_dtype : str
         output data type
 
diff --git a/topi/python/topi/x86/conv2d.py b/topi/python/topi/x86/conv2d.py
index 59495d75f42d..801a264d306f 100644
--- a/topi/python/topi/x86/conv2d.py
+++ b/topi/python/topi/x86/conv2d.py
@@ -1,11 +1,12 @@
-# pylint: disable=invalid-name,unused-variable,invalid-name
+# pylint: disable=invalid-name,unused-variable,invalid-name,unused-argument
 """Conv2D schedule on x86"""
 import tvm
 from .. import generic, tag
 from .. import nn
 from ..nn.util import infer_pad, infer_stride
 from ..nn.conv2d import conv2d, conv2d_NCHWc, conv2d_alter_layout, \
-                        _get_workload, _get_schedule, Workload
+    _get_workload, _get_schedule, _get_schedule_NCHWc, \
+    _get_alter_layout_schedule, Workload
 
 from . import conv2d_avx_1x1, conv2d_avx_common
 from .conv2d_avx_common import AVXConvCommonFwd
@@ -99,6 +100,13 @@ def _get_schedule_conv(wkl):
     sch = _SCHEDULES_AVX[idx]
     return sch
 
+@_get_schedule_NCHWc.register("cpu")
+def _get_schedule_NCHWc_x86(wkl, layout, out_layout):
+    return _get_schedule_conv(wkl)
+
+@_get_alter_layout_schedule.register("cpu")
+def _get_alter_layout_schedule_x86(wkl):
+    return _get_schedule_conv(wkl)
 
 @conv2d.register("cpu")
 def _declaration_conv(data, kernel, stride, padding, layout, out_dtype):
@@ -139,7 +147,7 @@ def _alter_conv2d_layout(attrs, inputs, tinfos):
     stride = ast.literal_eval(attrs['strides'])
 
     wkl = _get_workload(data, kernel, stride, padding, data.dtype)
-    sch = _get_schedule_conv(wkl)
+    sch = _get_alter_layout_schedule(wkl)
     is_kernel_1x1 = isinstance(sch, AVXConv1x1Fwd)
     ic_bn, oc_bn = sch.ic_bn, sch.oc_bn
 
@@ -157,7 +165,8 @@ def _alter_conv2d_layout(attrs, inputs, tinfos):
 
 
 @conv2d_NCHWc.register("cpu")
-def _declaration_conv_NCHWc(data, kernel, num_filter, kernel_size, stride, padding, out_dtype):
+def _declaration_conv_NCHWc(data, kernel, num_filter, kernel_size, stride,
+                            padding, layout, out_layout, out_dtype):
     _AVX_SCH_TO_DECL_FUNC = {
         AVXConvCommonFwd: conv2d_avx_common._declaration_conv_NCHWc,
         AVXConv1x1Fwd: conv2d_avx_1x1._declaration_conv_NCHWc
@@ -168,7 +177,7 @@ def _declaration_conv_NCHWc(data, kernel, num_filter, kernel_size, stride, paddi
     wkl = _get_workload(tvm.placeholder((n, ic, h, w), dtype=out_dtype),
                         tvm.placeholder((num_filter, ic, kh, kw), dtype=out_dtype),
                         stride, padding, out_dtype)
-    sch = _get_schedule(wkl)
+    sch = _get_schedule_NCHWc(wkl, layout, out_layout)
     return _AVX_SCH_TO_DECL_FUNC[type(sch)](wkl, sch, data, kernel)
 
 
@@ -311,7 +320,8 @@ def traverse(op):
 
 
 @generic.schedule_conv2d_NCHWc.register(["cpu"])
-def schedule_conv2d_NCHWc(num_filter, kernel_size, stride, padding, outs):
+def schedule_conv2d_NCHWc(num_filter, kernel_size, stride, padding,
+                          layout, out_layout, outs):
     """Create schedule for tensors"""
     _AVX_SCH_TO_SCH_FUNC = {
         AVXConvCommonFwd: conv2d_avx_common._schedule_conv_NCHWc,
@@ -348,7 +358,7 @@ def traverse(op):
             original_kernel = tvm.placeholder((num_filter, ic, kh, kw), dtype=conv_out.dtype)
 
             wkl = _get_workload(original_data, original_kernel, stride, padding, conv_out.dtype)
-            sch = _get_schedule(wkl)
+            sch = _get_schedule_NCHWc(wkl, layout, out_layout)
             _AVX_SCH_TO_SCH_FUNC[type(sch)](s, wkl, sch, data_vec,
                                             kernel, conv_out, outs[0])
 
diff --git a/topi/tests/python_cpp/test_topi_transform.py b/topi/tests/python_cpp/test_topi_transform.py
index 6e0d13c0d629..f1355bae2341 100644
--- a/topi/tests/python_cpp/test_topi_transform.py
+++ b/topi/tests/python_cpp/test_topi_transform.py
@@ -271,6 +271,7 @@ def check_device(device):
     for device in ["llvm", "cuda", "opencl", "metal", "rocm"]:
         check_device(device)
 
+
 def test_expand_dims():
     verify_expand_dims((3, 10), (3, 10, 1, 1), 2, 2)
     verify_expand_dims((3, 10), (1, 3, 10), -3, 1)

From d770412f086ec2ccba39edccc859fd64996c493d Mon Sep 17 00:00:00 2001
From: alex-weaver <awsweaver@gmail.com>
Date: Wed, 27 Jun 2018 18:07:12 +0100
Subject: [PATCH 771/949] Fix cmake search for vulkan on Windows (#1343)

---
 cmake/util/FindVulkan.cmake | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/cmake/util/FindVulkan.cmake b/cmake/util/FindVulkan.cmake
index 6ca089ad11b1..0b85e8f47d79 100644
--- a/cmake/util/FindVulkan.cmake
+++ b/cmake/util/FindVulkan.cmake
@@ -28,7 +28,7 @@ macro(find_vulkan use_vulkan)
 
    if(__vulkan_sdk)
      set(Vulkan_INCLUDE_DIRS ${__vulkan_sdk}/include)
-     find_library(Vulkan_LIBRARY vulkan ${__vulkan_sdk}/lib)
+     find_library(Vulkan_LIBRARY NAMES vulkan vulkan-1 PATHS ${__vulkan_sdk}/lib)
      if(Vulkan_LIBRARY)
        set(Vulkan_FOUND TRUE)
      endif()

From 6c97ef2409db4e83ed2e6e2d38f1f2fe4e5a4e72 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 27 Jun 2018 19:18:24 -0700
Subject: [PATCH 772/949] [RUNTIME] keep opencl runtime deps free from node
 (#1349)

---
 CMakeLists.txt                          | 2 +-
 src/runtime/opencl/opencl_device_api.cc | 3 ---
 2 files changed, 1 insertion(+), 4 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 028c16aa5040..7edaff4bc83d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.5)
+cmake_minimum_required(VERSION 3.2)
 project(tvm C CXX)
 
 # Utility functions
diff --git a/src/runtime/opencl/opencl_device_api.cc b/src/runtime/opencl/opencl_device_api.cc
index 8bd86d22aee5..75eae6364648 100644
--- a/src/runtime/opencl/opencl_device_api.cc
+++ b/src/runtime/opencl/opencl_device_api.cc
@@ -4,9 +4,6 @@
  */
 #include <tvm/runtime/registry.h>
 #include <dmlc/thread_local.h>
-#include <tvm/container.h>
-#include <tvm/ir.h>
-#include <tvm/packed_func_ext.h>
 #include "./opencl_common.h"
 
 namespace tvm {

From 206e4ae402fa84d03bfe806f5d603d667cb35f8d Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 27 Jun 2018 19:50:30 -0700
Subject: [PATCH 773/949] [PYTHON] Make decorator optional for runtime (#1350)

---
 python/tvm/_ffi/base.py  | 15 +++++++++++++++
 python/tvm/hybrid/api.py | 32 +++++++++++++++++++++-----------
 python/tvm/tag.py        |  8 +-------
 3 files changed, 37 insertions(+), 18 deletions(-)

diff --git a/python/tvm/_ffi/base.py b/python/tvm/_ffi/base.py
index 49348f3110ad..4c1e979cb684 100644
--- a/python/tvm/_ffi/base.py
+++ b/python/tvm/_ffi/base.py
@@ -98,3 +98,18 @@ def c_array(ctype, values):
         Created ctypes array
     """
     return (ctype * len(values))(*values)
+
+
+def decorate(func, fwrapped):
+    """A wrapper call of decorator package, differs to call time
+
+    Parameters
+    ----------
+    func : function
+        The original function
+
+    fwrapped : function
+        The wrapped function
+    """
+    import decorator
+    return decorator.decorate(func, fwrapped)
diff --git a/python/tvm/hybrid/api.py b/python/tvm/hybrid/api.py
index bc5376509522..48e192d4ba39 100644
--- a/python/tvm/hybrid/api.py
+++ b/python/tvm/hybrid/api.py
@@ -2,21 +2,31 @@
 from __future__ import absolute_import as _abs
 
 import types
-import decorator
+from .._ffi.base import decorate
 from .parser import parse_python
 
-@decorator.decorator
-def script(func, *args):
-    """If the arguments are tvm types, compile it to HalideIR.
-    O.W. return the python emulated result"""
-    from .util import _enter_hybrid_runtime, _restore_runtime, _is_tvm_arg_types
-    if _is_tvm_arg_types(args):
-        return parse(func, args)
-    else:
+
+def script(pyfunc):
+    """Decorate a python function function as  hybrid script.
+
+    The hybrid function support emulation mode and parsing to
+    the internal language IR.
+
+    Returns
+    -------
+    hybrid_func : function
+        A decorated hybrid script function.
+    """
+    def wrapped_func(func, *args, **kwargs):
+        from .util import _enter_hybrid_runtime, _restore_runtime, _is_tvm_arg_types
+        if _is_tvm_arg_types(args):
+            return parse(func, args)
+
         intersect = _enter_hybrid_runtime(func)
-        func(*args)
+        value = func(*args, **kwargs)
         _restore_runtime(func, intersect)
-    return func
+        return value
+    return decorate(pyfunc, wrapped_func)
 
 
 def parse(func, args):
diff --git a/python/tvm/tag.py b/python/tvm/tag.py
index afe207f275b4..5f6091a80a17 100644
--- a/python/tvm/tag.py
+++ b/python/tvm/tag.py
@@ -1,11 +1,5 @@
 """Tag class for TVM operators."""
-from ._ffi.base import _LIB_NAME
-try:
-    from decorator import decorate
-except ImportError as err_msg:
-    # Allow decorator to be missing in runtime
-    if _LIB_NAME != "libtvm_runtime.so":
-        raise err_msg
+from ._ffi.base import decorate
 
 class TagScope(object):
     """Tag scope object to set tag for operators, working as context

From c0451291b3b9daa51f7399327abe90fdba531af1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=9C=A8=E5=8E=9F=E4=BD=90=E4=B8=BA?=
 <ariwaranosai@users.noreply.github.com>
Date: Thu, 28 Jun 2018 23:03:09 +0800
Subject: [PATCH 774/949] update include regex to work for path with symbol
 (#1354)

change regex to match all symbol except for space.
---
 cmake/util/FindLLVM.cmake | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/cmake/util/FindLLVM.cmake b/cmake/util/FindLLVM.cmake
index c155a34fa308..b45904c0d891 100644
--- a/cmake/util/FindLLVM.cmake
+++ b/cmake/util/FindLLVM.cmake
@@ -35,7 +35,7 @@ macro(find_llvm use_llvm)
     # definitions
     string(REGEX MATCHALL "(^| )-D[A-Za-z0-9_]*" LLVM_DEFINITIONS ${__llvm_cxxflags})
     # include dir
-    string(REGEX MATCHALL "(^| )-I[A-Za-z0-9_/.\-]*" __llvm_include_flags ${__llvm_cxxflags})
+    string(REGEX MATCHALL "(^| )-I[^ ]*" __llvm_include_flags ${__llvm_cxxflags})    
     set(LLVM_INCLUDE_DIRS "")
     foreach(__flag IN ITEMS ${__llvm_include_flags})
       string(REGEX REPLACE "(^| )-I" "" __dir "${__flag}")

From 84197908c1f2ceab24d8118b649f9d1a40c7d066 Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Thu, 28 Jun 2018 20:35:33 +0530
Subject: [PATCH 775/949] Strided_slice added in NNVM (#1318)

---
 nnvm/include/nnvm/top/tensor.h                |  16 +++
 nnvm/python/nnvm/top/transform.py             |   4 +
 nnvm/src/top/tensor/transform.cc              | 113 ++++++++++++++++++
 nnvm/tests/python/compiler/test_top_level1.py |  36 ++++++
 4 files changed, 169 insertions(+)

diff --git a/nnvm/include/nnvm/top/tensor.h b/nnvm/include/nnvm/top/tensor.h
index dca02cae92fc..87128a663c4a 100644
--- a/nnvm/include/nnvm/top/tensor.h
+++ b/nnvm/include/nnvm/top/tensor.h
@@ -48,6 +48,22 @@ struct SplitParam : public dmlc::Parameter<SplitParam> {
   }
 };
 
+struct StridedSliceParam : public dmlc::Parameter<StridedSliceParam> {
+  // numpy convention, only support indices, not support list.
+  Tuple<int64_t> begin;
+  Tuple<int64_t> end;
+  Tuple<int64_t> stride;
+
+  DMLC_DECLARE_PARAMETER(StridedSliceParam) {
+    DMLC_DECLARE_FIELD(begin)
+        .describe("Indices for begin of slice");
+    DMLC_DECLARE_FIELD(end)
+        .describe("Indices for end of the slice");
+    DMLC_DECLARE_FIELD(stride).set_default(Tuple<int64_t>())
+        .describe("Stride values of the slice");
+  }
+};
+
 enum TypeFlag {
   kFloat32 = 0,
   kFloat64 = 1,
diff --git a/nnvm/python/nnvm/top/transform.py b/nnvm/python/nnvm/top/transform.py
index 6900f85f9217..c87b4735d73a 100644
--- a/nnvm/python/nnvm/top/transform.py
+++ b/nnvm/python/nnvm/top/transform.py
@@ -61,6 +61,10 @@ def compute_reshape_like(attrs, inputs, out_info):
 reg.register_pattern("split", OpPattern.INJECTIVE)
 reg.register_schedule("split", _fschedule_injective)
 
+# strided_slice
+reg.register_pattern("strided_slice", OpPattern.INJECTIVE)
+reg.register_schedule("strided_slice", _fschedule_injective)
+
 # slice_like
 reg.register_pattern("slice_like", OpPattern.INJECTIVE)
 reg.register_schedule("slice_like", _fschedule_injective)
diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
index 05d3cfcb5cf9..0b0beb5b73a7 100644
--- a/nnvm/src/top/tensor/transform.cc
+++ b/nnvm/src/top/tensor/transform.cc
@@ -829,6 +829,119 @@ Examples::
     };
 });
 
+// strided_slice
+DMLC_REGISTER_PARAMETER(StridedSliceParam);
+
+inline void StridedSliceParamParser(nnvm::NodeAttrs* attrs) {
+  StridedSliceParam param;
+  param.Init(attrs->dict);
+  attrs->parsed = std::move(param);
+}
+
+inline bool StridedSliceInferShape(const NodeAttrs& attrs,
+                            std::vector<TShape>* in_shape,
+                            std::vector<TShape>* out_shape) {
+  const StridedSliceParam& param = nnvm::get<StridedSliceParam>(attrs.parsed);
+  const TShape& dshape = (*in_shape)[0];
+  if (dshape.ndim() == 0) return false;
+  TShape oshape = dshape;
+  dim_t num_axis = dshape.ndim();
+
+  std::vector<int64_t> begin_vec;
+  std::copy(param.begin.begin(), param.begin.end(), std::back_inserter(begin_vec));
+  for (dim_t i = begin_vec.size(); i < num_axis; ++i) {
+    begin_vec.push_back(0);
+  }
+
+  std::vector<int64_t> end_vec;
+  std::copy(param.end.begin(), param.end.end(), std::back_inserter(end_vec));
+  for (dim_t i = end_vec.size(); i < num_axis; ++i) {
+    end_vec.push_back(dshape[i]);
+  }
+
+  std::vector<int64_t> stride_vec;
+  std::copy(param.stride.begin(), param.stride.end(), std::back_inserter(stride_vec));
+  for (dim_t i = stride_vec.size(); i < num_axis; ++i) {
+    stride_vec.push_back(1);
+  }
+
+  for (dim_t i = 0; i < num_axis; ++i) {
+      int64_t begin_range = stride_vec[i] < 0 ? -1 : 0;
+      int64_t end_range = stride_vec[i] < 0 ? dshape[i] - 1 : dshape[i];
+      int64_t begin = begin_vec[i] < 0 ? dshape[i] + begin_vec[i] : begin_vec[i];
+      int64_t end = end_vec[i] < 0 ? dshape[i] + end_vec[i] : end_vec[i];
+      begin = std::min(std::max(begin, begin_range), end_range);
+      end = std::min(std::max(end, begin_range), end_range);
+
+      int interval = std::abs(end - begin);
+      int slice_size = static_cast<int>((interval
+                                       + std::abs(stride_vec[i]) - 1) / std::abs(stride_vec[i]));
+      CHECK(stride_vec[i] < 0 ? (end < begin) : (begin < end))
+        << ": Input [Begin=" << begin_vec[i] << ", End=" << end_vec[i]
+        << "] is invalid for axis=" << i;
+      oshape[i] = slice_size;
+  }
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, oshape);
+  return true;
+}
+
+NNVM_REGISTER_OP(strided_slice)
+.describe(R"code(Strided slice of an array.
+
+Examples::
+
+  x = [[  1.,   4.,   7.,  10.],
+       [  2.,   5.,   8.,  11.],
+       [  3.,   6.,   9.,  12.]]
+
+  strided_slice(x, begin=[0, 1], end=[2, 4], stride=[1, 1]) = [[ 4.,  7.,  10.],
+                                                               [ 5.,  8.,  11.]]
+
+  x = [[[ 1.,  2.],
+        [ 3.,  4.]],
+
+       [[ 5.,  6.],
+        [ 7.,  8.]]]
+
+  strided_slice(x, begin=[0, 0], end=[2, 2]) = [[[ 1.,  2.],
+                                                 [ 3.,  4.]],
+
+                                                [[ 5.,  6.],
+                                                 [ 7.,  8.]]]
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "Tensor", "Array to be sliced")
+.add_arguments(StridedSliceParam::__FIELDS__())
+.set_attr_parser(StridedSliceParamParser)
+.set_attr<FInferShape>("FInferShape", StridedSliceInferShape)
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseArbitraryLayout<1, 1>)
+.set_num_inputs(1)
+.set_num_outputs(1)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const StridedSliceParam& param = nnvm::get<StridedSliceParam>(attrs.parsed);
+    Array<Expr> begin;
+    Array<Expr> end;
+    Array<Expr> stride;
+
+    for (int64_t i : param.begin) {
+        begin.push_back(tvm::make_const(tvm::Int(32), i));
+    }
+
+    for (int64_t i : param.end) {
+        end.push_back(tvm::make_const(tvm::Int(32), i));
+    }
+
+    for (int64_t i : param.stride) {
+        stride.push_back(tvm::make_const(tvm::Int(32), i));
+    }
+
+    return Array<Tensor>{ topi::strided_slice(inputs[0], begin, end, stride) };
+})
+.set_support_level(1);
+
 // Flip
 DMLC_REGISTER_PARAMETER(FlipParam);
 
diff --git a/nnvm/tests/python/compiler/test_top_level1.py b/nnvm/tests/python/compiler/test_top_level1.py
index 73391c80dd12..b97aff8effb6 100644
--- a/nnvm/tests/python/compiler/test_top_level1.py
+++ b/nnvm/tests/python/compiler/test_top_level1.py
@@ -329,6 +329,41 @@ def test_split():
     verify_split((5, 3), [3], axis=0)
     verify_split((5, 9, 3), [3, 4], axis=1)
 
+def verify_strided_slice(ishape, begin, end, strideinp=None):
+    stride = strideinp if strideinp else [1, 1, 1]
+    x = sym.Variable("x")
+    if strideinp:
+        y = sym.strided_slice(x, begin = begin, end = end, stride = stride) + 1
+    else:
+        y = sym.strided_slice(x, begin = begin, end = end) + 1
+    x_np = np.random.uniform(size=ishape).astype("float32")
+    for i in range(len(begin), 3):
+        begin.append(0)
+    for i in range(len(end), 3):
+        end.append(ishape[i])
+    def test_forward(x, begin, end, stride):
+        return x[begin[0]:end[0]:stride[0],
+                    begin[1]:end[1]:stride[1], begin[2]:end[2]:stride[2]] + 1
+
+    for target, ctx in ctx_list():
+        # set input
+        graph, lib, _ = nnvm.compiler.build(y, target, {"x": ishape})
+        m = graph_runtime.create(graph, lib, ctx)
+        m.run(x=x_np)
+        res = test_forward(x_np, begin, end, stride)
+        out = m.get_output(0, tvm.nd.empty(res.shape))
+        np.testing.assert_allclose(out.asnumpy(), res, atol=1e-5, rtol=1e-5)
+
+def test_strided_slice():
+    verify_strided_slice((3, 4, 3), [0, 0, 0], [4, -5, 4], [1, -1, 2])
+    verify_strided_slice((3, 4, 3), [1, 1, 0], [4, 4, 3], [2, 1, 1])
+    verify_strided_slice((3, 4, 3), [1, -1, 0], [4, -5, 3], [2, -1, 1])
+    verify_strided_slice((3, 4, 3), [1, 0, 0], [2, 2, 3], [1, 1, 2])
+    verify_strided_slice((3, 4, 3), [1, -1, 0], [2, -3, 3], [1, -1, 1])
+    verify_strided_slice((3, 4, 3), [1, 1, 0], [4, 4, 3])
+    verify_strided_slice((3, 4, 3), [1, 1, 0], [4, 1000, 3])
+    verify_strided_slice((3, 4, 3), [1, 1, 0], [4, 4])
+    verify_strided_slice((3, 4, 3), [1, 1], [4, 4, 3])
 
 def verify_squeeze(dshape, axis):
     x = sym.Variable("x")
@@ -448,3 +483,4 @@ def test_l2_normalize():
     test_pad()
     test_lrn()
     test_l2_normalize()
+    test_strided_slice()

From c9c031a38a9917c1a0bd0f9ee2eaf61a264b7ccd Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 28 Jun 2018 10:37:04 -0700
Subject: [PATCH 776/949] [NNVM] Initial mixed precision support of conv2d
 (#1356)

---
 nnvm/include/nnvm/top/nn.h                    |  7 ++++
 nnvm/python/nnvm/top/nn.py                    | 12 +++++--
 nnvm/src/top/nn/convolution.cc                | 30 ++++++++++++++--
 nnvm/tests/python/compiler/test_top_level2.py | 34 +++++++++++++++++--
 .../tests/python/unittest/test_infer_shape.py | 22 ++++++++++++
 topi/python/topi/nn/depthwise_conv2d.py       | 17 +++++++---
 6 files changed, 110 insertions(+), 12 deletions(-)

diff --git a/nnvm/include/nnvm/top/nn.h b/nnvm/include/nnvm/top/nn.h
index 69d49cd99696..f37811315e43 100644
--- a/nnvm/include/nnvm/top/nn.h
+++ b/nnvm/include/nnvm/top/nn.h
@@ -11,6 +11,7 @@
 #include <nnvm/tuple.h>
 #include <nnvm/layout.h>
 #include <string>
+#include "./tensor.h"
 
 namespace nnvm {
 namespace top {
@@ -122,6 +123,7 @@ struct Conv2DParam : public dmlc::Parameter<Conv2DParam> {
   std::string layout;
   std::string kernel_layout;
   std::string out_layout;
+  int out_dtype;
   bool use_bias;
 
   DMLC_DECLARE_PARAMETER(Conv2DParam) {
@@ -156,6 +158,11 @@ struct Conv2DParam : public dmlc::Parameter<Conv2DParam> {
       .describe("Dimension ordering of weight. Can be 'OIHW', 'OIHW16o16i', etc."
                 "'O', 'I', 'H', 'W' stands for num_filter, input_channel, height, and width"
                 "dimensions respectively.");
+    DMLC_DECLARE_DTYPE_FIELD(out_dtype)
+      .add_enum("same", -1)
+      .set_default(-1)
+      .describe("Output data type, set to explicit type under mixed precision setting");
+
     DMLC_DECLARE_FIELD(use_bias).set_default(true)
       .describe("Whether the layer uses a bias vector.");
   }
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index ca1fae1d2089..e86d545736bd 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -88,6 +88,8 @@ def compute_conv2d(attrs, inputs, _):
     channels = attrs.get_int("channels")
     layout = attrs["layout"]
     kernel_layout = attrs["kernel_layout"]
+    out_dtype = attrs["out_dtype"]
+    out_dtype = None if out_dtype == "same" else out_dtype
     assert layout == "NCHW" or layout == "NHWC"
     (dilation_h, dilation_w) = dilation
     if dilation_h < 1 or dilation_w < 1:
@@ -100,16 +102,19 @@ def compute_conv2d(attrs, inputs, _):
         kernel = topi.nn.dilate(inputs[1], [1, dilation_h, dilation_w, 1])
 
     if groups == 1:
-        out = topi.nn.conv2d(inputs[0], kernel, strides, padding, layout)
+        out = topi.nn.conv2d(
+            inputs[0], kernel, strides, padding, layout, out_dtype=out_dtype)
     elif layout == "NCHW" and \
          groups == get_const_int(inputs[0].shape[1]) and \
          groups == channels:
-        out = topi.nn.depthwise_conv2d_nchw(inputs[0], kernel, strides, padding)
+        out = topi.nn.depthwise_conv2d_nchw(
+            inputs[0], kernel, strides, padding, out_dtype=out_dtype)
     elif layout == "NHWC" and \
          kernel_layout == "HWOI" and \
          groups == get_const_int(inputs[0].shape[3]) and \
          groups == channels:
-        out = topi.nn.depthwise_conv2d_nhwc(inputs[0], kernel, strides, padding)
+        out = topi.nn.depthwise_conv2d_nhwc(
+            inputs[0], kernel, strides, padding, out_dtype=out_dtype)
     else:
         raise ValueError("not support arbitrary group number for now")
 
@@ -127,6 +132,7 @@ def schedule_conv2d(attrs, outs, target):
     channels = attrs.get_int("channels")
     layout = attrs["layout"]
     kernel_layout = attrs["kernel_layout"]
+
     with tvm.target.create(target):
         if groups == 1 and layout == "NCHW":
             return topi.generic.schedule_conv2d_nchw(outs)
diff --git a/nnvm/src/top/nn/convolution.cc b/nnvm/src/top/nn/convolution.cc
index 40c4d7a0e8c6..6a0dad17a4c4 100644
--- a/nnvm/src/top/nn/convolution.cc
+++ b/nnvm/src/top/nn/convolution.cc
@@ -130,6 +130,30 @@ inline bool Conv2DInferShape(const nnvm::NodeAttrs& attrs,
   return true;
 }
 
+
+inline bool Conv2DInferType(const nnvm::NodeAttrs& attrs,
+                            std::vector<int>* in_type,
+                            std::vector<int>* out_type) {
+  const Conv2DParam& param = nnvm::get<Conv2DParam>(attrs.parsed);
+  if (param.use_bias) {
+    CHECK_EQ(in_type->size(), 3U) << "Input:[data, weight, bias]";
+  } else {
+    CHECK_EQ(in_type->size(), 2U) << "Input:[data, weight]";
+  }
+  CHECK_EQ(out_type->size(), 1U);
+  if (param.out_dtype != -1) {
+    CHECK(!type_is_none((*in_type)[0]));
+    for (size_t i = 1; i < in_type->size(); ++i) {
+      NNVM_ASSIGN_INPUT_TYPE(attrs, *in_type, i, (*in_type)[0]);
+    }
+    NNVM_ASSIGN_OUTPUT_TYPE(attrs, *out_type, 0, param.out_dtype);
+  } else {
+    ElemwiseType<-1, 1>(attrs, in_type, out_type);
+  }
+  return true;
+}
+
+
 inline bool Conv2DCorrectLayout(const NodeAttrs& attrs,
                                 std::vector<Layout> *ilayouts,
                                 const std::vector<Layout> *last_ilayouts,
@@ -189,7 +213,7 @@ a bias vector is created and added to the outputs.
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<Conv2DParam>)
 .set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<Conv2DParam>)
 .set_attr<FInferShape>("FInferShape", Conv2DInferShape)
-.set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
+.set_attr<FInferType>("FInferType", Conv2DInferType)
 .set_attr<FCorrectLayout>("FCorrectLayout", Conv2DCorrectLayout)
 .set_num_outputs(1)
 .set_num_inputs(UseBiasNumInputs<Conv2DParam>)
@@ -214,7 +238,7 @@ NNVM_REGISTER_OP(_contrib_conv2d_NCHWc)
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<Conv2DParam>)
 .set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<Conv2DParam>)
 .set_attr<FInferShape>("FInferShape", Conv2DInferShape)
-.set_attr<FInferType>("FInferType", ElemwiseType<-1, 1>)
+.set_attr<FInferType>("FInferType", Conv2DInferType)
 .set_attr<FCorrectLayout>("FCorrectLayout", Conv2DCorrectLayout)
 .set_num_outputs(1)
 .set_num_inputs(UseBiasNumInputs<Conv2DParam>)
@@ -348,7 +372,7 @@ said convolution.
 - **weight**: (in_channels, channels, kernel_size[0], kernel_size[1])
 - **bias**: (channels,)
 - **out**:  This depends on the `layout` parameter. Output is 4D array of shape
-            (batch_size, channels, out_height, out_width) if `layout` is `NCHW`.
+v            (batch_size, channels, out_height, out_width) if `layout` is `NCHW`.
 
             out_height and out_width are calculated as::
                 out_height = (height-1)*strides[0]-2*padding[0]+kernel_size[0]+output_padding[0]
diff --git a/nnvm/tests/python/compiler/test_top_level2.py b/nnvm/tests/python/compiler/test_top_level2.py
index 8f4c330d2efc..19387a586528 100644
--- a/nnvm/tests/python/compiler/test_top_level2.py
+++ b/nnvm/tests/python/compiler/test_top_level2.py
@@ -32,6 +32,35 @@ def test_conv2d():
         np.testing.assert_allclose(out.asnumpy(), c_np, rtol=1e-5)
 
 
+def test_mixed_precision():
+    x = sym.Variable("x")
+    dtype = "int8"
+    out_dtype="int32"
+    y = sym.conv2d(x,
+                   channels=10,
+                   kernel_size=(3,3),
+                   name="y",
+                   padding=(1,1),
+                   use_bias=False,
+                   out_dtype="int32")
+    dshape = (1, 3, 18, 18)
+    kshape = (10, 3, 3, 3)
+    oshape = (1, 10, 18, 18)
+    shape_dict = {"x": dshape}
+    dtype_dict = {"x": dtype}
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(y, target, shape_dict, dtype_dict)
+        m = graph_runtime.create(graph, lib, ctx)
+        data = tvm.nd.array(np.random.uniform(-127, 127, size=dshape).astype(dtype))
+        kernel = tvm.nd.array(np.random.uniform(-127, 127, size=kshape).astype(dtype))
+        m.run(x=data, y_weight=kernel)
+        out = m.get_output(0, tvm.nd.empty(oshape, out_dtype))
+        c_np = topi.testing.conv2d_nchw_python(
+            data.asnumpy().astype(out_dtype),
+            kernel.asnumpy().astype(out_dtype), 1, 1)
+        np.testing.assert_allclose(out.asnumpy(), c_np, rtol=1e-5)
+
+
 def test_dilated_conv2d():
     dilation = 3
     x = sym.Variable("x")
@@ -167,7 +196,7 @@ def test_avg_pool2d_no_count_pad():
     kh, kw = (4, 4)
     sh, sw = (2, 2)
     ph, pw = (2, 2)
-    
+
     x = sym.Variable("x")
     y = sym.avg_pool2d(x, pool_size=(kh, kw), strides=(sw, sw), padding=(ph, pw),
                        name="y", count_include_pad=False)
@@ -181,7 +210,7 @@ def test_avg_pool2d_no_count_pad():
     no_zero = (range(n), range(ic), (range(ph, ih+ph)), (range(pw, iw+pw)))
     pad_np[np.ix_(*no_zero)] = a_np
     b_np = np.zeros(shape=(n, oc, oh, ow)).astype(dtype)
-    
+
     for i in range(oh):
         for j in range(ow):
             pad_count = np.sum(pad_np[:, :, i*sh:i*sh+kh, j*sw:j*sw+kw] > 0, axis=(2,3))
@@ -289,6 +318,7 @@ def test_resize_bilinear():
         np.testing.assert_allclose(out.asnumpy(), b_np, rtol=1e-5, atol=1e-5)
 
 if __name__ == "__main__":
+    test_mixed_precision()
     test_conv2d()
     test_dilated_conv2d()
     test_grouped_conv2d_nchw()
diff --git a/nnvm/tests/python/unittest/test_infer_shape.py b/nnvm/tests/python/unittest/test_infer_shape.py
index 9fbc93c0757d..51e0e9576781 100644
--- a/nnvm/tests/python/unittest/test_infer_shape.py
+++ b/nnvm/tests/python/unittest/test_infer_shape.py
@@ -168,6 +168,27 @@ def check(in_shape, out_shape, **kwargs):
           layout="NHWC")
 
 
+def test_conv2d_packed():
+    def check(in_shape,
+              out_shape,
+              kernel_shape,
+              **kwargs):
+        x = sym.Variable("x", shape=in_shape)
+        y = sym.conv2d(x, name="y", **kwargs)
+        sdict = infer_shape(y)
+        assert(tuple(sdict["y"][0]) == tuple(out_shape))
+        assert(tuple(sdict["y_weight"][0]) == tuple(kernel_shape))
+
+    check((4, 10, 10, 12, 1, 8),
+          (4, 10, 10, 2, 1, 8),
+          (2, 12, 3, 3, 8, 8),
+          channels=8 * 2,
+          kernel_size=(3,3),
+          padding=(1,1),
+          layout="NHWC1n8c",
+          kernel_layout="OIHW8o8i")
+
+
 def test_conv2d_transpose():
     def check(in_shape, out_shape, **kwargs):
         x = sym.Variable("x", shape=in_shape)
@@ -332,6 +353,7 @@ def check(in_shape, out_shape, **kwargs):
 
 
 if __name__ == "__main__":
+    test_conv2d_packed()
     test_expand_dims()
     test_dense()
     test_matmul()
diff --git a/topi/python/topi/nn/depthwise_conv2d.py b/topi/python/topi/nn/depthwise_conv2d.py
index 8d348d461f82..c7906d3a4373 100644
--- a/topi/python/topi/nn/depthwise_conv2d.py
+++ b/topi/python/topi/nn/depthwise_conv2d.py
@@ -27,12 +27,15 @@ def depthwise_conv2d_nchw(Input, Filter, stride, padding, out_dtype=None):
     padding : int or str
         Padding size, or ['VALID', 'SAME']
 
+    out_dtype: str, optional
+        Output data type
+
     Returns
     -------
     Output : tvm.Tensor
         4-D with shape [batch, out_channel, out_height, out_width]
     """
-    out_dtype = Input.dtype
+    out_dtype = Input.dtype if out_dtype is None else out_dtype
 
     batch, in_channel, in_height, in_width = Input.shape
     filter_channel, channel_multiplier, filter_height, filter_width = Filter.shape
@@ -65,7 +68,7 @@ def depthwise_conv2d_nchw(Input, Filter, stride, padding, out_dtype=None):
 
 
 @tvm.target.generic_func
-def depthwise_conv2d_nhwc(Input, Filter, stride, padding):
+def depthwise_conv2d_nhwc(Input, Filter, stride, padding, out_dtype=None):
     """Depthwise convolution nhwc forward operator.
 
     Parameters
@@ -82,11 +85,16 @@ def depthwise_conv2d_nhwc(Input, Filter, stride, padding):
     padding : int or str
         Padding size, or ['VALID', 'SAME']
 
+    out_dtype: str, optional
+        Output data type
+
     Returns
     -------
     Output : tvm.Tensor
         4-D with shape [batch, out_height, out_width, out_channel]
     """
+    out_dtype = Input.dtype if out_dtype is None else out_dtype
+
     batch, in_height, in_width, in_channel = Input.shape
     filter_height, filter_width, filter_channel, channel_multiplier = Filter.shape
     if isinstance(stride, int):
@@ -110,8 +118,9 @@ def depthwise_conv2d_nhwc(Input, Filter, stride, padding):
     Output = tvm.compute(
         (batch, out_height, out_width, out_channel),
         lambda b, i, j, c: tvm.sum(
-            (PaddedInput[b, i*stride_h + di, j*stride_w + dj, c/channel_multiplier] *
-             Filter[di, dj, c/channel_multiplier, c%channel_multiplier]),
+            (PaddedInput[b, i*stride_h + di, j*stride_w + dj, c/channel_multiplier].astype(
+                out_dtype) *
+             Filter[di, dj, c/channel_multiplier, c%channel_multiplier].astype(out_dtype)),
             axis=[di, dj]),
         name='DepthwiseConv2d', tag="depthwise_conv2d_nhwc")
     return Output

From ebdde3c277a9807a67b233cecfaf6d9f96c0c1bc Mon Sep 17 00:00:00 2001
From: Tatsuya Nishiyama <nishiyama.tatsuya0@gmail.com>
Date: Fri, 29 Jun 2018 13:28:18 +0900
Subject: [PATCH 777/949] [NNVM] Move FTVMCompute registration of the
 elementwise operator to c++ (#1351)

---
 nnvm/python/nnvm/top/tensor.py  | 39 ---------------------
 nnvm/src/top/tensor/elemwise.cc | 62 +++++++++++++++++++++++++++++++++
 2 files changed, 62 insertions(+), 39 deletions(-)

diff --git a/nnvm/python/nnvm/top/tensor.py b/nnvm/python/nnvm/top/tensor.py
index a9ff8c8fe4c3..a781d17c6b11 100644
--- a/nnvm/python/nnvm/top/tensor.py
+++ b/nnvm/python/nnvm/top/tensor.py
@@ -182,69 +182,30 @@ def compute_cast(attrs, inputs, _):
 reg.register_schedule("clip", _fschedule_elemwise)
 
 # elemwise sum
-@reg.register_compute("elemwise_sum")
-def compute_elemwise_sum(attrs, inputs, _):
-    """Compute definition of elemwise sum"""
-    num_args = attrs.get_int("num_args")
-    assert num_args == len(inputs), "Number of tensors does not match num_args."
-    return topi.tensor.elemwise_sum(inputs)
 reg.register_pattern("elemwise_sum", OpPattern.ELEMWISE)
 reg.register_schedule("elemwise_sum", _fschedule_elemwise)
 
 # full
-@reg.register_compute("full")
-def compute_full(attrs, inputs, _):
-    """Compute definition of full"""
-    shape = attrs.get_int_tuple("shape")
-    dtype = attrs.get_string("dtype")
-    fill_value = attrs.get_float("fill_value")
-    return topi.tensor.full(shape, dtype, fill_value)
 reg.register_pattern("full", OpPattern.OUT_ELEMWISE_FUSABLE)
 reg.register_schedule("full", _fschedule_elemwise)
 
 # full_like
-@reg.register_compute("full_like")
-def compute_full_like(attrs, inputs, _):
-    """Compute definition of full_like"""
-    fill_value = attrs.get_float("fill_value")
-    return topi.tensor.full_like(inputs[0], fill_value)
 reg.register_pattern("full_like", OpPattern.ELEMWISE)
 reg.register_schedule("full_like", _fschedule_elemwise)
 
 # zeros
-@reg.register_compute("zeros")
-def compute_zeros(attrs, inputs, _):
-    """Compute definition of zeros"""
-    shape = attrs.get_int_tuple("shape")
-    dtype = attrs.get_string("dtype")
-    return topi.tensor.full(shape, dtype, 0)
 reg.register_pattern("zeros", OpPattern.OUT_ELEMWISE_FUSABLE)
 reg.register_schedule("zeros", _fschedule_elemwise)
 
 # zeros_like
-@reg.register_compute("zeros_like")
-def compute_zeros_like(_, inputs, out_info):
-    """Compute definition of zeros_like"""
-    return topi.tensor.full_like(inputs[0], 0)
 reg.register_pattern("zeros_like", OpPattern.ELEMWISE)
 reg.register_schedule("zeros_like", _fschedule_elemwise)
 
 # ones
-@reg.register_compute("ones")
-def compute_ones(attrs, inputs, _):
-    """Compute definition of ones"""
-    shape = attrs.get_int_tuple("shape")
-    dtype = attrs.get_string("dtype")
-    #tvm.tensor.Tensor()
-    return topi.tensor.full(shape, dtype, 1)
 reg.register_pattern("ones", OpPattern.OUT_ELEMWISE_FUSABLE)
 reg.register_schedule("ones", _fschedule_elemwise)
 
 # ones_like
-@reg.register_compute("ones_like")
-def compute_ones_like(_, inputs, out_info):
-    """Compute definition of ones_like"""
-    return topi.tensor.full_like(inputs[0], 1)
 reg.register_pattern("ones_like", OpPattern.ELEMWISE)
 reg.register_schedule("ones_like", _fschedule_elemwise)
 
diff --git a/nnvm/src/top/tensor/elemwise.cc b/nnvm/src/top/tensor/elemwise.cc
index cd38817b51a5..1aaab4725c90 100644
--- a/nnvm/src/top/tensor/elemwise.cc
+++ b/nnvm/src/top/tensor/elemwise.cc
@@ -7,6 +7,7 @@
 #include <nnvm/node.h>
 #include <nnvm/op_attr_types.h>
 #include <nnvm/compiler/op_attr_types.h>
+#include <nnvm/compiler/util.h>
 #include <nnvm/top/tensor.h>
 #include <cmath>
 #include "../op_common.h"
@@ -14,6 +15,7 @@
 #include "topi/broadcast.h"
 #include "topi/elemwise.h"
 #include "topi/tags.h"
+#include "../../compiler/compile_engine.h"
 
 namespace nnvm {
 namespace top {
@@ -382,6 +384,16 @@ NNVM_REGISTER_INIT_OP(full)
 .set_attr<FInferShape>("FInferShape", ZeroShape<InitOpWithScalarParam>)
 .set_attr<FInferType>("FInferType", ZeroType<InitOpWithScalarParam>)
 .set_attr<FCorrectLayout>("FCorrectLayout", ZeroLayout)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const InitOpWithScalarParam& param = nnvm::get<InitOpWithScalarParam>(attrs.parsed);
+    Array<Expr> shape = ShapeToArray(param.shape);
+    Type dtype = GetTVMType(param.dtype);
+    Expr fill_value = tvm::make_const(dtype, param.fill_value);
+    return Array<Tensor>{ topi::full(shape, dtype, fill_value) };
+})
 .set_support_level(4);
 
 NNVM_REGISTER_INIT_OP(zeros)
@@ -395,6 +407,16 @@ NNVM_REGISTER_INIT_OP(zeros)
 .set_attr<FInferShape>("FInferShape", ZeroShape<InitOpParam>)
 .set_attr<FInferType>("FInferType", ZeroType<InitOpParam>)
 .set_attr<FCorrectLayout>("FCorrectLayout", ZeroLayout)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const InitOpParam& param = nnvm::get<InitOpParam>(attrs.parsed);
+    Array<Expr> shape = ShapeToArray(param.shape);
+    Type dtype = GetTVMType(param.dtype);
+    Expr fill_value = tvm::make_const(dtype, 0);
+    return Array<Tensor>{ topi::full(shape, dtype, fill_value) };
+})
 .set_support_level(4);
 
 NNVM_REGISTER_INIT_OP(ones)
@@ -408,6 +430,16 @@ NNVM_REGISTER_INIT_OP(ones)
 .set_attr<FInferShape>("FInferShape", ZeroShape<InitOpParam>)
 .set_attr<FInferType>("FInferType", ZeroType<InitOpParam>)
 .set_attr<FCorrectLayout>("FCorrectLayout", ZeroLayout)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const InitOpParam& param = nnvm::get<InitOpParam>(attrs.parsed);
+    Array<Expr> shape = ShapeToArray(param.shape);
+    Type dtype = GetTVMType(param.dtype);
+    Expr fill_value = tvm::make_const(dtype, 1);
+    return Array<Tensor>{ topi::full(shape, dtype, fill_value) };
+})
 .set_support_level(4);
 
 // full_like
@@ -419,6 +451,14 @@ as the input array
 .add_arguments(FillValueParam::__FIELDS__())
 .set_attr_parser(ParamParser<FillValueParam>)
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<FillValueParam>)
+.set_attr<FTVMCompute>(
+    "FTVMCompute", [](const NodeAttrs& attrs,
+                      const Array<Tensor>& inputs,
+                      const Array<Tensor>& out_info) {
+      const FillValueParam& param = nnvm::get<FillValueParam>(attrs.parsed);
+      const Expr fill_value = tvm::make_const(out_info[0]->dtype, param.fill_value);
+      return Array<Tensor> { topi::full_like(inputs[0], fill_value) };
+})
 .set_support_level(4);
 
 NNVM_REGISTER_INIT_LIKE_OP(zeros_like)
@@ -426,6 +466,13 @@ NNVM_REGISTER_INIT_LIKE_OP(zeros_like)
 as the input array.
 
 )code")
+.set_attr<FTVMCompute>(
+    "FTVMCompute", [](const NodeAttrs& attrs,
+                      const Array<Tensor>& inputs,
+                      const Array<Tensor>& out_info) {
+      return Array<Tensor> { topi::full_like(inputs[0],
+                                             tvm::make_const(out_info[0]->dtype, 0)) };
+})
 .set_support_level(4);
 
 NNVM_REGISTER_INIT_LIKE_OP(ones_like)
@@ -433,6 +480,13 @@ NNVM_REGISTER_INIT_LIKE_OP(ones_like)
 as the input array.
 
 )code")
+.set_attr<FTVMCompute>(
+    "FTVMCompute", [](const NodeAttrs& attrs,
+                      const Array<Tensor>& inputs,
+                      const Array<Tensor>& out_info) {
+      return Array<Tensor> { topi::full_like(inputs[0],
+                                             tvm::make_const(out_info[0]->dtype, 1)) };
+})
 .set_support_level(4);
 
 // unary scalar op
@@ -684,6 +738,14 @@ NNVM_REGISTER_ELEMWISE_REDUCE_OP(elemwise_sum)
 .describe(R"code(Adds all input arguments element-wise.
 
 )code"  NNVM_ADD_FILELINE)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const ElementWiseReduceParam& param = nnvm::get<ElementWiseReduceParam>(attrs.parsed);
+    CHECK_EQ(param.num_args, inputs.size()) << """Compute definition of elemwise sum""";
+    return Array<Tensor>{ topi::elemwise_sum(inputs) };
+})
 .set_attr<nnvm::FGradient>(
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds){

From fc16c727fa8f968f7d0a68539000c4661498bb8f Mon Sep 17 00:00:00 2001
From: ANSHUMAN TRIPATHY <32511895+ANSHUMAN87@users.noreply.github.com>
Date: Sat, 30 Jun 2018 20:25:15 +0530
Subject: [PATCH 778/949] Prelu bug fix (#1358)

---
 nnvm/src/top/nn/nn.cc                   | 2 +-
 topi/include/topi/nn.h                  | 1 -
 topi/src/topi.cc                        | 2 +-
 topi/tests/python/test_topi_relu.py     | 9 +++++----
 topi/tests/python_cpp/test_topi_relu.py | 9 +++++----
 5 files changed, 12 insertions(+), 11 deletions(-)

diff --git a/nnvm/src/top/nn/nn.cc b/nnvm/src/top/nn/nn.cc
index ab47ae521224..ba89e5ceba58 100644
--- a/nnvm/src/top/nn/nn.cc
+++ b/nnvm/src/top/nn/nn.cc
@@ -563,7 +563,7 @@ where :math:`*` is an channelwise multiplication for each sample in the
                     const Array<Tensor>& inputs,
                     const Array<Tensor>& out_info) {
     const PReLUParam& param = nnvm::get<PReLUParam>(attrs.parsed);
-    return Array<Tensor>{ topi::prelu<float>(inputs[0], inputs[1], param.axis)};
+    return Array<Tensor>{ topi::prelu(inputs[0], inputs[1], param.axis)};
   })
 .set_support_level(4);
 
diff --git a/topi/include/topi/nn.h b/topi/include/topi/nn.h
index ee3101c4cc18..53b899796e37 100644
--- a/topi/include/topi/nn.h
+++ b/topi/include/topi/nn.h
@@ -92,7 +92,6 @@ inline tvm::Tensor leaky_relu(const tvm::Tensor& t,
  *
  * \return A Tensor whose op member is the relu operation
  */
-template <typename T>
 inline tvm::Tensor prelu(const tvm::Tensor &x,
                          const tvm::Tensor &slope,
                          const int axis = 1,
diff --git a/topi/src/topi.cc b/topi/src/topi.cc
index 59652ed6680e..fe1f4098570d 100644
--- a/topi/src/topi.cc
+++ b/topi/src/topi.cc
@@ -191,7 +191,7 @@ TVM_REGISTER_GLOBAL("topi.nn.leaky_relu")
 
 TVM_REGISTER_GLOBAL("topi.nn.prelu")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
-  *rv = prelu<float>(args[0], args[1]);
+  *rv = prelu(args[0], args[1], args[2]);
   });
 
 TVM_REGISTER_GLOBAL("topi.nn.pad")
diff --git a/topi/tests/python/test_topi_relu.py b/topi/tests/python/test_topi_relu.py
index 2f7898ff242a..28a86be03ea0 100644
--- a/topi/tests/python/test_topi_relu.py
+++ b/topi/tests/python/test_topi_relu.py
@@ -46,16 +46,16 @@ def verify_leaky_relu(m, alpha):
     np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
 
 
-def verify_prelu(x, w):
+def verify_prelu(x, w, axis, weight_reshape):
     X = tvm.placeholder((x), name='X')
     W = tvm.placeholder((w), name='W')
     x_np = np.random.uniform(low=-1.0, high=1.0, size=get_const_tuple(X.shape)).astype(X.dtype)
     w_np = np.random.uniform(low=-1.0, high=1.0, size=get_const_tuple(W.shape)).astype(W.dtype)
 
     def _prelu_numpy(x, W):
-        return (x < 0) * (x *W.reshape(3, 1, 1)) + (x>=0) * x
+        return (x < 0) * (x *W.reshape(weight_reshape)) + (x>=0) * x
 
-    B = topi.nn.prelu(X, W)
+    B = topi.nn.prelu(X, W, axis)
     s = tvm.create_schedule([B.op])
 
     ctx = tvm.cpu(0)
@@ -79,7 +79,8 @@ def test_leaky_relu():
     verify_leaky_relu(100, 0.1)
 
 def test_prelu():
-    verify_prelu((1, 3, 2, 2), (3,))
+    verify_prelu((1, 3, 2, 2), (3,), 1, (3, 1, 1))
+    verify_prelu((1, 3, 2, 2), (2,), 2, (2, 1))
 
 if __name__ == "__main__":
     test_schedule_big_array()
diff --git a/topi/tests/python_cpp/test_topi_relu.py b/topi/tests/python_cpp/test_topi_relu.py
index a5064618b890..6677c1bf5551 100644
--- a/topi/tests/python_cpp/test_topi_relu.py
+++ b/topi/tests/python_cpp/test_topi_relu.py
@@ -50,16 +50,16 @@ def verify_leaky_relu(m, alpha):
     foo(a, b)
     np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
 
-def verify_prelu(x, w):
+def verify_prelu(x, w, axis, weight_reshape):
     X = tvm.placeholder((x), name='X')
     W = tvm.placeholder((w), name='W')
     x_np = np.random.uniform(low=-1.0, high=1.0, size=get_const_tuple(X.shape)).astype(X.dtype)
     w_np = np.random.uniform(low=-1.0, high=1.0, size=get_const_tuple(W.shape)).astype(W.dtype)
     def _prelu_numpy(x, W):
-        return (x < 0) * (x *W.reshape(3, 1, 1)) + (x>=0) * x
+        return (x < 0) * (x *W.reshape(weight_reshape)) + (x>=0) * x
 
     out_np = _prelu_numpy(x_np, w_np)
-    B = topi.cpp.nn.prelu(X, W)
+    B = topi.cpp.nn.prelu(X, W, axis)
     device = "llvm"
     target = topi.cpp.TEST_create_target(device)
     s = topi.cpp.generic.schedule_injective(target, [B])
@@ -81,7 +81,8 @@ def test_leaky_relu():
     verify_leaky_relu(100, 0.5)
 
 def test_prelu():
-    verify_prelu((1, 3, 2, 2), (3,))
+    verify_prelu((1, 3, 2, 2), (3,), 1, (3, 1, 1))
+    verify_prelu((1, 3, 2, 2), (2,), 2, (2, 1))
 
 if __name__ == "__main__":
     test_relu()

From 2693dad5aba64a181be87fdcee53df8b39e45311 Mon Sep 17 00:00:00 2001
From: Dayananda V <dayanandasiet@gmail.com>
Date: Sat, 30 Jun 2018 20:26:26 +0530
Subject: [PATCH 779/949] JNI Crash fix (#1357)

---
 jvm/native/src/main/native/ml_dmlc_tvm_native_c_api.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/jvm/native/src/main/native/ml_dmlc_tvm_native_c_api.cc b/jvm/native/src/main/native/ml_dmlc_tvm_native_c_api.cc
index 615e4716d669..fada50ca76e2 100644
--- a/jvm/native/src/main/native/ml_dmlc_tvm_native_c_api.cc
+++ b/jvm/native/src/main/native/ml_dmlc_tvm_native_c_api.cc
@@ -384,7 +384,7 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_tvm_LibInfo_tvmArrayGetShape(
   jmethodID arrayAppend = env->GetMethodID(arrayClass, "add", "(Ljava/lang/Object;)Z");
   for (int i = 0; i < ndim; ++i) {
     jobject data = env->NewObject(longClass, newLong, static_cast<jlong>(shape[i]));
-    env->CallObjectMethod(jshape, arrayAppend, data);
+    env->CallBooleanMethod(jshape, arrayAppend, data);
     env->DeleteLocalRef(data);
   }
   env->DeleteLocalRef(longClass);

From 9aabd793a7f82932924fa0239e351c38b45fbf85 Mon Sep 17 00:00:00 2001
From: aswinjohn <aswinjohn@gmail.com>
Date: Sun, 1 Jul 2018 17:34:29 -0500
Subject: [PATCH 780/949] Minor bug: causes undefined symbol error in libtvm
 from NNPACK (#1368)

---
 cmake/modules/contrib/NNPack.cmake | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/cmake/modules/contrib/NNPack.cmake b/cmake/modules/contrib/NNPack.cmake
index e75d19081def..82de88a21e63 100644
--- a/cmake/modules/contrib/NNPack.cmake
+++ b/cmake/modules/contrib/NNPack.cmake
@@ -7,7 +7,7 @@ if(USE_NNPACK)
   list(APPEND RUNTIME_SRCS ${NNPACK_CONTRIB_SRC})
 	include_directories(${NNPACK_PATH}/include)
 	include_directories(${PTHREAD_POOL_PATH}/include)
-  find_library(NNPACK_CONTRB_LIB nnpack ${NNPACK_PATH}/lib)
+    find_library(NNPACK_CONTRIB_LIB nnpack ${NNPACK_PATH}/lib)
   find_library(NNPACK_PTHREAD_CONTRIB_LIB pthreadpool ${NNPACK_PATH}/lib)
   list(APPEND TVM_RUNTIME_LINKER_LIBS ${NNPACK_CONTRIB_LIB})
   list(APPEND TVM_RUNTIME_LINKER_LIBS ${NNPACK_PTHREAD_CONTRIB_LIB})

From 99286231e9452cca8f16caf08377ad37314a6b69 Mon Sep 17 00:00:00 2001
From: Tatsuya Nishiyama <nishiyama.tatsuya0@gmail.com>
Date: Tue, 3 Jul 2018 02:14:01 +0900
Subject: [PATCH 781/949] Fix tutorial to follow a change of elemwise_sum
 (#1374)

---
 tutorials/topi/intro_topi.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tutorials/topi/intro_topi.py b/tutorials/topi/intro_topi.py
index a551f96c761b..fb5fe7e65284 100644
--- a/tutorials/topi/intro_topi.py
+++ b/tutorials/topi/intro_topi.py
@@ -65,7 +65,7 @@
 # we can schedule the following series of operations ending with :code:`topi.sum` using only
 # :code:`topi.generic.schedule_reduce`
 #
-e = topi.elemwise_sum([c, d], num_args=2)
+e = topi.elemwise_sum([c, d])
 f = e / 2.0
 g = topi.sum(f)
 with tvm.target.cuda():

From 22586b5df20de8961c5b1ab83c8e29395cd4e1a3 Mon Sep 17 00:00:00 2001
From: Jian Weng <werefluke@gmail.com>
Date: Mon, 2 Jul 2018 13:57:31 -0700
Subject: [PATCH 782/949] fix narrow conversion on gcc-8 (#1376)

---
 src/codegen/codegen.cc | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/codegen/codegen.cc b/src/codegen/codegen.cc
index d289d627b310..8bc7d238a866 100644
--- a/src/codegen/codegen.cc
+++ b/src/codegen/codegen.cc
@@ -54,9 +54,9 @@ std::string PackImportsToC(const runtime::Module& mod, bool system_lib) {
   os << "#ifdef __cplusplus\n"
      << "extern \"C\" {\n"
      << "#endif\n";
-  os << "TVM_EXPORT extern const char " << runtime::symbol::tvm_dev_mblob << "[];\n";
+  os << "TVM_EXPORT extern const unsigned char " << runtime::symbol::tvm_dev_mblob << "[];\n";
   uint64_t nbytes = bin.length();
-  os << "const char " << runtime::symbol::tvm_dev_mblob
+  os << "const unsigned char " << runtime::symbol::tvm_dev_mblob
      << "[" << bin.length() + sizeof(nbytes) << "] = {\n  ";
   os << std::hex;
   size_t nunit = 80 / 4;

From 0fa9f6974d3e3d77122909c92b78783d9b067649 Mon Sep 17 00:00:00 2001
From: ANSHUMAN TRIPATHY <32511895+ANSHUMAN87@users.noreply.github.com>
Date: Tue, 3 Jul 2018 08:20:00 +0530
Subject: [PATCH 783/949] Transpose core dump resolved (#1355)

---
 topi/include/topi/transform.h                | 12 ++++++++++++
 topi/tests/python_cpp/test_topi_transform.py |  1 +
 2 files changed, 13 insertions(+)

diff --git a/topi/include/topi/transform.h b/topi/include/topi/transform.h
index ac408ba633b0..10fb5bc478cb 100644
--- a/topi/include/topi/transform.h
+++ b/topi/include/topi/transform.h
@@ -89,6 +89,18 @@ inline Tensor transpose(const Tensor& x,
   }
 
   auto axes_val = GetConstIntValues(axes, "axes");
+  for (size_t i = 0; i < axes_val.size(); ++i) {
+    int axis = axes_val[i];
+    if (axes_val[i] < 0) {
+      axes_val[i] = static_cast<int>(x->shape.size()) + axes_val[i];
+    }
+    CHECK((0 <= axes_val[i]) && (axes_val[i] < static_cast<int>(x->shape.size())))
+      << "axis=" << axis << " is invalid for the "
+      << static_cast<int>(x->shape.size()) << "-dimensional input tensor";
+
+    CHECK(1 == std::count(std::begin(axes_val), std::end(axes_val), axes_val[i]))
+      << "repeated axis in transpose";
+  }
 
   Array<Expr> new_shape;
   for (size_t i = 0; i < axes_val.size(); ++i) {
diff --git a/topi/tests/python_cpp/test_topi_transform.py b/topi/tests/python_cpp/test_topi_transform.py
index f1355bae2341..a94fc89328af 100644
--- a/topi/tests/python_cpp/test_topi_transform.py
+++ b/topi/tests/python_cpp/test_topi_transform.py
@@ -281,6 +281,7 @@ def test_tranpose():
     verify_tranpose((3, 10, 2), (1, 0, 2))
     verify_tranpose((3, 10, 5), (2, 0, 1))
     verify_tranpose((3, 10), None)
+    verify_tranpose((3, 10, 5), (2, -3, 1))
 
 
 def test_reshape():

From e4c099e0316eb1959b1c7afcba7b7632c3716eee Mon Sep 17 00:00:00 2001
From: masahi <masahi129@gmail.com>
Date: Wed, 4 Jul 2018 01:27:07 +0900
Subject: [PATCH 784/949] [NVPTX] libdevice support, enable NVPTX backend in
 topi tests (#1365)

---
 apps/benchmark/gpu_imagenet_bench.py          |  2 +-
 src/codegen/llvm/codegen_nvptx.cc             | 17 ++++-
 src/codegen/llvm/intrin_rule_nvptx.cc         | 64 +++++++++++++++++++
 src/codegen/llvm/intrin_rule_rocm.cc          |  2 +-
 topi/python/topi/cuda/depthwise_conv2d.py     |  2 +-
 topi/tests/python/test_topi_broadcast.py      |  2 +
 topi/tests/python/test_topi_conv2d_hwcn.py    |  2 +-
 topi/tests/python/test_topi_conv2d_nchw.py    |  5 +-
 .../python/test_topi_conv2d_transpose_nchw.py |  2 +-
 topi/tests/python/test_topi_dense.py          |  2 +-
 .../python/test_topi_depthwise_conv2d.py      |  2 +
 .../test_topi_depthwise_conv2d_back_input.py  |  1 +
 .../test_topi_depthwise_conv2d_back_weight.py |  1 +
 topi/tests/python/test_topi_l2norm.py         |  2 +-
 topi/tests/python/test_topi_lrn.py            |  2 +-
 topi/tests/python/test_topi_math.py           |  2 +-
 topi/tests/python/test_topi_pooling.py        |  4 +-
 topi/tests/python/test_topi_reduce.py         | 18 ++++--
 topi/tests/python/test_topi_relu.py           |  2 +-
 topi/tests/python/test_topi_resize.py         |  2 +-
 topi/tests/python/test_topi_softmax.py        | 15 +++--
 topi/tests/python/test_topi_upsampling.py     |  2 +-
 22 files changed, 121 insertions(+), 32 deletions(-)
 create mode 100644 src/codegen/llvm/intrin_rule_nvptx.cc

diff --git a/apps/benchmark/gpu_imagenet_bench.py b/apps/benchmark/gpu_imagenet_bench.py
index 1ffbccd90f53..fca4e35b6516 100644
--- a/apps/benchmark/gpu_imagenet_bench.py
+++ b/apps/benchmark/gpu_imagenet_bench.py
@@ -25,7 +25,7 @@ def main():
                         choices=['resnet', 'mobilenet'],
                         help="The model type.")
     parser.add_argument('--target', type=str, required=True,
-                        choices=['cuda', 'rocm', 'opencl', 'metal'],
+                        choices=['cuda', 'rocm', 'opencl', 'metal', 'nvptx'],
                         help="Compilation target.")
     parser.add_argument('--opt-level', type=int, default=1, help="Level of optimization.")
     parser.add_argument('--num-iter', type=int, default=1000, help="Number of iteration during benchmark.")
diff --git a/src/codegen/llvm/codegen_nvptx.cc b/src/codegen/llvm/codegen_nvptx.cc
index 3231008e8959..1cca1eacfe85 100644
--- a/src/codegen/llvm/codegen_nvptx.cc
+++ b/src/codegen/llvm/codegen_nvptx.cc
@@ -121,6 +121,20 @@ class CodeGenNVPTX : public CodeGenLLVM {
     // Additional optimization hook to tweak the builder.
   }
 
+  void Optimize() final {
+    for (auto& f : *module_) {
+      auto fname = static_cast<std::string>(f.getName());
+      if (fname.substr(0, 4) != "__nv") continue;
+      // This is to strip off unused __nv_* functions from the final module
+      // The one that is actually used will be inlined at call site
+      // Adapted from Halide's runtime linker
+      if (!f.isDeclaration() && !f.hasFnAttribute(llvm::Attribute::NoInline)) {
+        f.setLinkage(llvm::GlobalValue::AvailableExternallyLinkage);
+      }
+    }
+    CodeGenLLVM::Optimize();
+  }
+
  protected:
   void InitTarget(llvm::TargetMachine* tm) final {
     // Maximum vector lane = float4
@@ -179,8 +193,7 @@ runtime::Module BuildNVPTX(Array<LoweredFunc> funcs, std::string target) {
       }
       mlib->setTargetTriple(tm->getTargetTriple().str());
       mlib->setDataLayout(tm->createDataLayout());
-      // TODO(tqchen) libdevice linking not yet working.
-      // cg->AddLinkModule(std::move(mlib));
+      cg->AddLinkModule(std::move(mlib));
     }
   }
   std::unique_ptr<llvm::Module> module = cg->Finish();
diff --git a/src/codegen/llvm/intrin_rule_nvptx.cc b/src/codegen/llvm/intrin_rule_nvptx.cc
new file mode 100644
index 000000000000..60957198d5f3
--- /dev/null
+++ b/src/codegen/llvm/intrin_rule_nvptx.cc
@@ -0,0 +1,64 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file intrin_rule_nvptx.cc
+ */
+#ifdef TVM_LLVM_VERSION
+
+#include <tvm/ir.h>
+#include <tvm/expr.h>
+#include <tvm/api_registry.h>
+#include <sstream>
+
+namespace tvm {
+namespace codegen {
+
+inline void DispatchExternLibDevice(const TVMArgs& args, TVMRetValue* rv) {
+  Expr e = args[0];
+  using namespace ir;
+  const Call* call = e.as<Call>();
+  CHECK(call != nullptr);
+  CHECK(call->type.bits() == 32 || call->type.bits() == 64) << "Only support float32 or float64.";
+  std::ostringstream intrinsic_name;
+  intrinsic_name << "__nv_" << call->name;
+  if (call->type.bits() == 32) intrinsic_name << "f";
+  *rv = Call::make(call->type, intrinsic_name.str(), call->args,
+                   Call::PureExtern);
+}
+
+namespace llvm {
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.nvptx.floor")
+.set_body(DispatchExternLibDevice);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.nvptx.ceil")
+.set_body(DispatchExternLibDevice);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.nvptx.round")
+.set_body(DispatchExternLibDevice);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.nvptx.trunc")
+.set_body(DispatchExternLibDevice);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.nvptx.exp")
+.set_body(DispatchExternLibDevice);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.nvptx.fma")
+.set_body(DispatchExternLibDevice);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.nvptx.log")
+.set_body(DispatchExternLibDevice);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.nvptx.sqrt")
+.set_body(DispatchExternLibDevice);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.nvptx.pow")
+.set_body(DispatchExternLibDevice);
+
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.nvptx.tanh")
+.set_body(DispatchExternLibDevice);
+
+}  // namespace llvm
+}  // namespace codegen
+}  // namespace tvm
+
+#endif  // LLVM_VERSION
diff --git a/src/codegen/llvm/intrin_rule_rocm.cc b/src/codegen/llvm/intrin_rule_rocm.cc
index dc84b866d616..9d105a35385f 100644
--- a/src/codegen/llvm/intrin_rule_rocm.cc
+++ b/src/codegen/llvm/intrin_rule_rocm.cc
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2017 by Contributors
- * \file intrin_rule_llvm.cc
+ * \file intrin_rule_rocm.cc
  */
 #ifdef TVM_LLVM_VERSION
 
diff --git a/topi/python/topi/cuda/depthwise_conv2d.py b/topi/python/topi/cuda/depthwise_conv2d.py
index 1901fcbb7cfc..c7ab547c88ec 100644
--- a/topi/python/topi/cuda/depthwise_conv2d.py
+++ b/topi/python/topi/cuda/depthwise_conv2d.py
@@ -158,7 +158,7 @@ def _schedule(temp, Filter, DepthwiseConv2d):
         # num_thread here could be 728, it is larger than cuda.max_num_threads
         num_thread = tvm.ir_pass.Simplify(temp.shape[3]).value
         target = tvm.target.current_target()
-        if target and target.target_name != "cuda":
+        if target and (target.target_name not in ["cuda", "nvptx"]):
             num_thread = target.max_num_threads
         xoc, xic = s[Output].split(c, factor=num_thread)
         s[Output].reorder(xoc, b, h, w, xic)
diff --git a/topi/tests/python/test_topi_broadcast.py b/topi/tests/python/test_topi_broadcast.py
index 25bb98ed163f..ecd238e78761 100644
--- a/topi/tests/python/test_topi_broadcast.py
+++ b/topi/tests/python/test_topi_broadcast.py
@@ -30,6 +30,7 @@ def check_device(device):
     check_device("cuda")
     check_device("metal")
     check_device("rocm")
+    check_device("nvptx")
 
 
 def verify_broadcast_binary_ele(lhs_shape, rhs_shape,
@@ -85,6 +86,7 @@ def check_device(device):
     check_device("cuda")
     check_device("metal")
     check_device("rocm")
+    check_device("nvptx")
 
 def test_broadcast_to():
     verify_broadcast_to_ele((1,), (10,), topi.broadcast_to)
diff --git a/topi/tests/python/test_topi_conv2d_hwcn.py b/topi/tests/python/test_topi_conv2d_hwcn.py
index 645e18702946..1ff4b02470c4 100644
--- a/topi/tests/python/test_topi_conv2d_hwcn.py
+++ b/topi/tests/python/test_topi_conv2d_hwcn.py
@@ -52,7 +52,7 @@ def check_device(device):
             np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
             np.testing.assert_allclose(c.asnumpy(), c_np, rtol=1e-5)
 
-    for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan']:
+    for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan', 'nvptx']:
         check_device(device)
 
 
diff --git a/topi/tests/python/test_topi_conv2d_nchw.py b/topi/tests/python/test_topi_conv2d_nchw.py
index ed8fab524904..0d55a9163466 100644
--- a/topi/tests/python/test_topi_conv2d_nchw.py
+++ b/topi/tests/python/test_topi_conv2d_nchw.py
@@ -44,8 +44,9 @@ def check_device(device):
         w = tvm.nd.array(w_np, ctx)
         b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
         c = tvm.nd.array(np.zeros(get_const_tuple(C.shape), dtype=C.dtype), ctx)
+        no_unroll_explicit = device in ["cuda", "nvptx", "rocm"]
         with tvm.build_config(auto_unroll_max_step=1400,
-                              unroll_explicit=(device != "cuda")):
+                              unroll_explicit=not no_unroll_explicit):
             func1 = tvm.build(s1, [A, W, B], device, name="conv2d_%d_%d_%d_%d_%d_%d_%d_%d" % (batch, in_channel, in_size, num_filter, kernel, stride, padding, dilation))
             func2 = tvm.build(s2, [A, W, C], device, name="relu_%d_%d_%d_%d_%d_%d_%d_%d" % (batch, in_channel, in_size, num_filter, kernel, stride, padding, dilation))
             func1(a, w, b)
@@ -53,7 +54,7 @@ def check_device(device):
             np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
             np.testing.assert_allclose(c.asnumpy(), c_np, rtol=1e-5)
 
-    for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan']:
+    for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan', 'nvptx']:
         check_device(device)
 
 
diff --git a/topi/tests/python/test_topi_conv2d_transpose_nchw.py b/topi/tests/python/test_topi_conv2d_transpose_nchw.py
index dcdddca9107b..674fe9fd8953 100644
--- a/topi/tests/python/test_topi_conv2d_transpose_nchw.py
+++ b/topi/tests/python/test_topi_conv2d_transpose_nchw.py
@@ -51,7 +51,7 @@ def check_device(device):
             np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
             np.testing.assert_allclose(c.asnumpy(), c_np, rtol=1e-5)
 
-    for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan']:
+    for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan', 'nvptx']:
         check_device(device)
 
 
diff --git a/topi/tests/python/test_topi_dense.py b/topi/tests/python/test_topi_dense.py
index 779e1af1f35e..2df43eb30887 100644
--- a/topi/tests/python/test_topi_dense.py
+++ b/topi/tests/python/test_topi_dense.py
@@ -45,7 +45,7 @@ def check_device(device):
         f(a, b, c, d)
         np.testing.assert_allclose(d.asnumpy(), d_np, rtol=1e-5)
 
-    for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan']:
+    for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan', 'nvptx']:
         check_device(device)
 
 def test_dense():
diff --git a/topi/tests/python/test_topi_depthwise_conv2d.py b/topi/tests/python/test_topi_depthwise_conv2d.py
index 3e9794ac3ec9..3086054ba487 100644
--- a/topi/tests/python/test_topi_depthwise_conv2d.py
+++ b/topi/tests/python/test_topi_depthwise_conv2d.py
@@ -93,6 +93,7 @@ def get_ref_data():
     check_device("metal")
     check_device("rocm")
     check_device("vulkan")
+    check_device("nvptx")
 
 
 def depthwise_conv2d_with_workload_nhwc(batch, in_channel, in_height, channel_multiplier, filter_height, stride_h, padding, dilation=1):
@@ -184,6 +185,7 @@ def get_ref_data():
     check_device("metal")
     check_device("rocm")
     check_device("vulkan")
+    check_device("nvptx")
 
 def test_depthwise_conv2d():
     print("testing nchw")
diff --git a/topi/tests/python/test_topi_depthwise_conv2d_back_input.py b/topi/tests/python/test_topi_depthwise_conv2d_back_input.py
index 663364c5345b..f7c027344840 100644
--- a/topi/tests/python/test_topi_depthwise_conv2d_back_input.py
+++ b/topi/tests/python/test_topi_depthwise_conv2d_back_input.py
@@ -87,6 +87,7 @@ def get_ref_data():
     check_device("metal")
     check_device("rocm")
     check_device("vulkan")
+    check_device("nvptx")
 
 def test_topi_depthwise_conv2d_backward_input_nhwc():
     verify_depthwise_conv2d_back_input(16, 256, 56, 1, 3, 1, 1)
diff --git a/topi/tests/python/test_topi_depthwise_conv2d_back_weight.py b/topi/tests/python/test_topi_depthwise_conv2d_back_weight.py
index 25d08e1b1882..da5b0351ae3c 100644
--- a/topi/tests/python/test_topi_depthwise_conv2d_back_weight.py
+++ b/topi/tests/python/test_topi_depthwise_conv2d_back_weight.py
@@ -80,6 +80,7 @@ def get_ref_data():
     check_device("metal")
     check_device("rocm")
     check_device("vulkan")
+    check_device("nvptx")
 
 def test_topi_depthwise_conv2d_backward_weight_nhwc():
     verify_depthwise_conv2d_back_weight(16, 256, 56, 1, 3, 1, 1)
diff --git a/topi/tests/python/test_topi_l2norm.py b/topi/tests/python/test_topi_l2norm.py
index b27a1dc27e72..75dc57057893 100644
--- a/topi/tests/python/test_topi_l2norm.py
+++ b/topi/tests/python/test_topi_l2norm.py
@@ -31,7 +31,7 @@ def check_device(device):
         f(a, b)
         np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
 
-    for device in ['llvm', 'cuda', 'opencl', 'metal', 'rocm', 'vulkan']:
+    for device in ['llvm', 'cuda', 'opencl', 'metal', 'rocm', 'vulkan', 'nvptx']:
         check_device(device)
 
 def test_l2_normalize():
diff --git a/topi/tests/python/test_topi_lrn.py b/topi/tests/python/test_topi_lrn.py
index 7d62aefe5f55..478054ddb134 100644
--- a/topi/tests/python/test_topi_lrn.py
+++ b/topi/tests/python/test_topi_lrn.py
@@ -30,7 +30,7 @@ def check_device(device):
         f(a, b)
         np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
 
-    for device in ['llvm', 'cuda', 'opencl', 'metal', 'rocm', 'vulkan']:
+    for device in ['llvm', 'cuda', 'opencl', 'metal', 'rocm', 'vulkan', 'nvptx']:
         check_device(device)
 
 def test_lrn():
diff --git a/topi/tests/python/test_topi_math.py b/topi/tests/python/test_topi_math.py
index dd8f3fce8a14..0205407790d6 100644
--- a/topi/tests/python/test_topi_math.py
+++ b/topi/tests/python/test_topi_math.py
@@ -39,7 +39,7 @@ def check_device(device):
             foo(a, b)
             np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5, atol=1e-5)
 
-        for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan', 'llvm']:
+        for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan', 'llvm', 'nvptx']:
             check_device(device)
 
 
diff --git a/topi/tests/python/test_topi_pooling.py b/topi/tests/python/test_topi_pooling.py
index 14a67f11bfe1..5b6201cd65fb 100644
--- a/topi/tests/python/test_topi_pooling.py
+++ b/topi/tests/python/test_topi_pooling.py
@@ -63,7 +63,7 @@ def check_device(device):
         f(a, b)
         np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
 
-    for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan']:
+    for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan', 'nvptx']:
         check_device(device)
 
 def test_pool():
@@ -104,7 +104,7 @@ def check_device(device):
         f(a, b)
         np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
 
-    for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan']:
+    for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan', 'nvptx']:
         check_device(device)
 
 def test_global_pool():
diff --git a/topi/tests/python/test_topi_reduce.py b/topi/tests/python/test_topi_reduce.py
index 597274a987b0..331498deb10c 100644
--- a/topi/tests/python/test_topi_reduce.py
+++ b/topi/tests/python/test_topi_reduce.py
@@ -25,12 +25,11 @@ def _my_npy_argmin(arr, axis, keepdims):
         return arr.argmin(axis=axis).reshape(out_shape)
 
 
-def verify_reduce_map_ele(in_shape, axis, keepdims, type="sum"):
+def verify_reduce_map_ele(in_shape, axis, keepdims, type="sum", dtype="float32"):
     # Build the logic and compile the function
-    dat_dtype = "float32"
-    A = tvm.placeholder(shape=in_shape, name="A", dtype=dat_dtype)
+    A = tvm.placeholder(shape=in_shape, name="A", dtype=dtype)
     A1 = topi.sqrt(topi.exp(A))
-    out_dtype = "float32"
+    out_dtype = dtype
     if type == "sum":
         B = topi.sum(A1, axis=axis, keepdims=keepdims)
     elif type == "max":
@@ -57,8 +56,8 @@ def check_device(device):
 
         foo = tvm.build(s, [A, B], device, name=type)
         # Test
-        in_npy = np.random.uniform(size=in_shape).astype(np.float32)
-        in_npy_map = np.sqrt(np.exp(in_npy)).astype(np.float32)
+        in_npy = np.random.uniform(size=in_shape).astype(dtype)
+        in_npy_map = np.sqrt(np.exp(in_npy)).astype(dtype)
         if type == "sum":
             out_npy = in_npy_map.sum(axis=axis, keepdims=keepdims)
         elif type == "max":
@@ -91,7 +90,7 @@ def check_device(device):
                 np.testing.assert_allclose(out_tvm_val, in_npy_map.min(axis=axis), 1E-3, 1E-3)
         else:
             np.testing.assert_allclose(out_tvm.asnumpy(), out_npy, 1E-3, 1E-3)
-    for device in ["cuda", "opencl", "metal", "llvm", "rocm", "vulkan"]:
+    for device in ["cuda", "opencl", "metal", "llvm", "rocm", "vulkan", "nvptx"]:
         check_device(device)
 
 
@@ -128,6 +127,11 @@ def test_reduce_map():
                           axis=None,
                           keepdims=False,
                           type="sum")
+    verify_reduce_map_ele(in_shape=(128, 24, 128, 24),
+                          axis=(1, 2, 3),
+                          keepdims=True,
+                          type="sum",
+                          dtype="float64")
 
 if __name__ == "__main__":
     test_reduce_map()
diff --git a/topi/tests/python/test_topi_relu.py b/topi/tests/python/test_topi_relu.py
index 28a86be03ea0..9feeabbf122f 100644
--- a/topi/tests/python/test_topi_relu.py
+++ b/topi/tests/python/test_topi_relu.py
@@ -27,7 +27,7 @@ def check_device(device):
         foo(a, b)
         np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
 
-    for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan']:
+    for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan', 'nvptx']:
         check_device(device)
 
 
diff --git a/topi/tests/python/test_topi_resize.py b/topi/tests/python/test_topi_resize.py
index cb83c18d8c1a..cb2a69caf22b 100644
--- a/topi/tests/python/test_topi_resize.py
+++ b/topi/tests/python/test_topi_resize.py
@@ -40,7 +40,7 @@ def check_device(device):
 
         np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-3, atol=1e-3)
 
-    for device in ['llvm', 'cuda', 'vulkan']:
+    for device in ['llvm', 'cuda', 'vulkan', 'nvptx']:
         check_device(device)
 
 def test_resize():
diff --git a/topi/tests/python/test_topi_softmax.py b/topi/tests/python/test_topi_softmax.py
index c768c395a74f..f12070695220 100644
--- a/topi/tests/python/test_topi_softmax.py
+++ b/topi/tests/python/test_topi_softmax.py
@@ -7,8 +7,8 @@
 import logging
 from topi.util import get_const_tuple
 
-def verify_softmax(m, n):
-    A = tvm.placeholder((m, n), name='A')
+def verify_softmax(m, n, dtype="float32"):
+    A = tvm.placeholder((m, n), dtype=dtype, name='A')
     B = topi.nn.softmax(A)
     # confirm lower works
     s = tvm.create_schedule([B.op])
@@ -32,16 +32,16 @@ def check_device(device):
         foo(a, b)
         np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
 
-    for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan']:
+    for device in ['cuda', 'opencl', 'metal', 'rocm', 'vulkan', 'nvptx']:
         check_device(device)
 
 def test_softmax():
     verify_softmax(32, 10)
     verify_softmax(3, 4)
+    verify_softmax(32, 10, "float64")
 
-
-def verify_log_softmax(m, n):
-    A = tvm.placeholder((m, n), name='A')
+def verify_log_softmax(m, n, dtype="float32"):
+    A = tvm.placeholder((m, n), dtype=dtype, name='A')
     B = topi.nn.log_softmax(A)
     # confirm lower works
     s = tvm.create_schedule([B.op])
@@ -63,13 +63,14 @@ def check_device(device):
         foo(a, b)
         np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
 
-    for device in ["cuda", "opencl", "metal", "rocm", "vulkan"]:
+    for device in ["cuda", "opencl", "metal", "rocm", "vulkan", "nvptx"]:
         check_device(device)
 
 
 def test_log_softmax():
     verify_log_softmax(32, 10)
     verify_log_softmax(3, 4)
+    verify_log_softmax(32, 10, "float64")
 
 if __name__ == "__main__":
     logging.basicConfig(level=logging.DEBUG)
diff --git a/topi/tests/python/test_topi_upsampling.py b/topi/tests/python/test_topi_upsampling.py
index d03ea0bc7e14..3affc30a0722 100644
--- a/topi/tests/python/test_topi_upsampling.py
+++ b/topi/tests/python/test_topi_upsampling.py
@@ -41,7 +41,7 @@ def check_device(device):
 
         np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
 
-    for device in ['llvm', 'cuda', 'vulkan']:
+    for device in ['llvm', 'cuda', 'vulkan', 'nvptx']:
         check_device(device)
 
 def test_upsampling():

From d27ff243db5d95ca496a1181c1003625724802bc Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 3 Jul 2018 09:43:00 -0700
Subject: [PATCH 785/949] [TEAM] Add alex-weaver as reviewer (#1377)

---
 CONTRIBUTORS.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 2460087eb0ac..58868aa93155 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -23,6 +23,7 @@ See the [community structure document](http://docs.tvm.ai/contribute/community.h
 - [Masahiro Masuda](https://github.com/masahi)
 - [Pariksheet Pinjari](https://github.com/PariksheetPinjari909)
 - [Siva](https://github.com/srkreddy1238)
+- [Alex Weaver](https://github.com/alex-weaver)
 - [Eddie Yan](https://github.com/eqy)
 - [Joshua Z. Zhang](https://github.com/zhreshold)
 - [Lianmin Zheng](https://github.com/merrymercy)

From 035696f40ee4d97d6c15723a2c377646235465df Mon Sep 17 00:00:00 2001
From: Tatsuya Nishiyama <nishiyama.tatsuya0@gmail.com>
Date: Wed, 4 Jul 2018 01:45:11 +0900
Subject: [PATCH 786/949] [NNVM] Move FTVMCompute registration of cast, greter,
 less to C++. (#1370)

---
 nnvm/python/nnvm/top/tensor.py   | 13 -------------
 nnvm/src/top/tensor/elemwise.cc  | 12 ++++++++++++
 nnvm/src/top/tensor/transform.cc | 10 ++++++++++
 3 files changed, 22 insertions(+), 13 deletions(-)

diff --git a/nnvm/python/nnvm/top/tensor.py b/nnvm/python/nnvm/top/tensor.py
index a781d17c6b11..bd486287abb3 100644
--- a/nnvm/python/nnvm/top/tensor.py
+++ b/nnvm/python/nnvm/top/tensor.py
@@ -53,11 +53,6 @@ def _compute(attrs, x, _):
 reg.register_schedule("copy", _fschedule_broadcast)
 
 # cast
-@reg.register_compute("cast")
-def compute_cast(attrs, inputs, _):
-    """Compute definition of cast"""
-    dtype = attrs.get_string("dtype")
-    return topi.cast(inputs[0], dtype)
 reg.register_pattern("cast", OpPattern.ELEMWISE)
 reg.register_schedule("cast", _fschedule_broadcast)
 
@@ -210,18 +205,10 @@ def compute_cast(attrs, inputs, _):
 reg.register_schedule("ones_like", _fschedule_elemwise)
 
 # greater
-@reg.register_compute("greater")
-def compute_greater(_, inputs, out_info):
-    """Compute definition of greater"""
-    return topi.greater(inputs[0], inputs[1]).astype('float32')
 reg.register_pattern("greater", OpPattern.ELEMWISE)
 reg.register_schedule("greater", _fschedule_elemwise)
 
 # less
-@reg.register_compute("less")
-def compute_less(_, inputs, out_info):
-    """Compute definition of less"""
-    return topi.less(inputs[0], inputs[1]).astype('float32')
 reg.register_pattern("less", OpPattern.ELEMWISE)
 reg.register_schedule("less", _fschedule_elemwise)
 
diff --git a/nnvm/src/top/tensor/elemwise.cc b/nnvm/src/top/tensor/elemwise.cc
index 1aaab4725c90..57e7137f5151 100644
--- a/nnvm/src/top/tensor/elemwise.cc
+++ b/nnvm/src/top/tensor/elemwise.cc
@@ -781,6 +781,12 @@ with 1.0 if (left > right), otherwise 0.0 element-wise.
 .add_argument("rhs", "Tensor", "Second input")
 .set_num_inputs(2)
 .set_attr<nnvm::FInferShape>("FInferShape", ElemwiseShape<2, 1>)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    return Array<Tensor>{ topi::cast(topi::greater(inputs[0], inputs[1]), out_info[0]->dtype) };
+})
 .set_support_level(4);
 
 
@@ -793,6 +799,12 @@ with 1.0 if (left < right), otherwise 0.0 element-wise.
 .add_argument("rhs", "Tensor", "Second input")
 .set_num_inputs(2)
 .set_attr<nnvm::FInferShape>("FInferShape", ElemwiseShape<2, 1>)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    return Array<Tensor>{ topi::cast(topi::less(inputs[0], inputs[1]), out_info[0]->dtype) };
+})
 .set_support_level(4);
 
 NNVM_REGISTER_INDICATOR_OP(_max_mask)
diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
index 0b0beb5b73a7..72e49a040efe 100644
--- a/nnvm/src/top/tensor/transform.cc
+++ b/nnvm/src/top/tensor/transform.cc
@@ -15,7 +15,9 @@
 #include "../elemwise_op_common.h"
 #include "topi/nn/flatten.h"
 #include "topi/transform.h"
+#include "topi/elemwise.h"
 #include "topi/detail/constant_utils.h"
+#include "../../compiler/compile_engine.h"
 
 namespace nnvm {
 namespace top {
@@ -413,6 +415,14 @@ NNVM_REGISTER_OP(cast)
 .set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseArbitraryLayout<1, 1>)
 .set_num_inputs(1)
 .set_num_outputs(1)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const CastParam& param = nnvm::get<CastParam>(attrs.parsed);
+    Type dtype = GetTVMType(param.dtype);
+    return Array<Tensor>{ topi::cast(inputs[0], dtype) };
+})
 .set_support_level(1);
 
 

From 9ccfaad4254ed0e03be70cc3b014782f533566f4 Mon Sep 17 00:00:00 2001
From: Siva <sivar.b@huawei.com>
Date: Wed, 4 Jul 2018 07:45:02 +0530
Subject: [PATCH 787/949] [NNVM][TENSORFLOW] Sigmoid op support #1367 (#1369)

---
 nnvm/python/nnvm/frontend/tensorflow.py       |  1 +
 .../frontend/tensorflow/test_forward.py       | 38 +++++++++++++++++++
 2 files changed, 39 insertions(+)

diff --git a/nnvm/python/nnvm/frontend/tensorflow.py b/nnvm/python/nnvm/frontend/tensorflow.py
index 71536517810d..ce047d27da07 100644
--- a/nnvm/python/nnvm/frontend/tensorflow.py
+++ b/nnvm/python/nnvm/frontend/tensorflow.py
@@ -513,6 +513,7 @@ def _impl(inputs, attr, params):
     'Relu6'                             : _relu6(),
     'DepthwiseConv2dNative'             : _depthwise_conv(),
     'Shape'                             : _shape(),
+    'Sigmoid'                           : AttrCvt('sigmoid'),
 }
 
 
diff --git a/nnvm/tests/python/frontend/tensorflow/test_forward.py b/nnvm/tests/python/frontend/tensorflow/test_forward.py
index 6dc8cfab2ab4..f3dbc3cdbe40 100644
--- a/nnvm/tests/python/frontend/tensorflow/test_forward.py
+++ b/nnvm/tests/python/frontend/tensorflow/test_forward.py
@@ -13,6 +13,7 @@
 from tensorflow.python.ops import nn_ops
 from tensorflow.python.ops import array_ops
 from tensorflow.python.ops import gen_array_ops
+from tensorflow.python.ops import math_ops
 from tensorflow.core.framework import graph_pb2
 
 import nnvm.testing.tf
@@ -329,6 +330,42 @@ def _test_forward_concat_v2():
 
     _test_concat_v2([t1, t2], 1)
 
+#######################################################################
+# Sigmoid
+# -------
+
+def _test_sigmoid(data):
+    """ One iteration of sigmoid """
+
+    with tf.Graph().as_default():
+        in_data = constant_op.constant(data, shape=data.shape, dtype=data.dtype)
+
+        # pylint: disable=unused-variable
+        sigmoid_out = math_ops.sigmoid(in_data)
+        # pylint: enable=unused-variable
+
+        with tf.Session() as sess:
+            graph_def = tf.graph_util.convert_variables_to_constants(
+                sess,
+                sess.graph.as_graph_def(add_shapes=True),
+                ['Sigmoid'],
+                )
+
+            tf_output = run_tf_graph(sess, data,
+                                     'Const:0', 'Sigmoid:0')
+            tvm_output = run_tvm_graph(graph_def,
+                                       data,
+                                       "Const", tf_output.shape, data.dtype)
+
+            np.testing.assert_allclose(tf_output, tvm_output, atol=1e-5, rtol=1e-5)
+
+            sess.close()
+
+def test_forward_sigmoid():
+    """ Sigmoid """
+
+    _test_sigmoid(np.random.uniform(size=(3, 4, 4, 3)).astype('float32'))
+
 #######################################################################
 # Multi Input to graph
 # --------------------
@@ -437,6 +474,7 @@ def test_forward_mobilenet():
     test_forward_pooling()
     test_forward_reshape()
     test_forward_squeeze()
+    test_forward_sigmoid()
     if tf.__version__ == '1.4.1':
         _test_forward_concat_v2()
     test_forward_multi_input()

From d3f144f30de2f54deffb233da97c2c672d42425f Mon Sep 17 00:00:00 2001
From: Dayananda V <dayanandasiet@gmail.com>
Date: Wed, 4 Jul 2018 21:32:25 +0530
Subject: [PATCH 788/949] add take frontend (#1307)

---
 nnvm/include/nnvm/top/tensor.h                |  10 ++
 nnvm/python/nnvm/top/transform.py             |   4 +
 nnvm/src/top/tensor/transform.cc              | 120 ++++++++++++++++++
 nnvm/tests/python/compiler/test_top_level1.py |  35 +++++
 4 files changed, 169 insertions(+)

diff --git a/nnvm/include/nnvm/top/tensor.h b/nnvm/include/nnvm/top/tensor.h
index 87128a663c4a..22ee9d7118e6 100644
--- a/nnvm/include/nnvm/top/tensor.h
+++ b/nnvm/include/nnvm/top/tensor.h
@@ -48,6 +48,16 @@ struct SplitParam : public dmlc::Parameter<SplitParam> {
   }
 };
 
+
+struct TakeParam : public dmlc::Parameter<TakeParam> {
+  dmlc::optional<int> axis;
+
+  DMLC_DECLARE_PARAMETER(TakeParam) {
+    DMLC_DECLARE_FIELD(axis).set_default(dmlc::optional<int>())
+        .describe("the axis over which to select values.");
+  }
+};
+
 struct StridedSliceParam : public dmlc::Parameter<StridedSliceParam> {
   // numpy convention, only support indices, not support list.
   Tuple<int64_t> begin;
diff --git a/nnvm/python/nnvm/top/transform.py b/nnvm/python/nnvm/top/transform.py
index c87b4735d73a..b5e00f012996 100644
--- a/nnvm/python/nnvm/top/transform.py
+++ b/nnvm/python/nnvm/top/transform.py
@@ -61,6 +61,10 @@ def compute_reshape_like(attrs, inputs, out_info):
 reg.register_pattern("split", OpPattern.INJECTIVE)
 reg.register_schedule("split", _fschedule_injective)
 
+# take
+reg.register_pattern("take", OpPattern.INJECTIVE)
+reg.register_schedule("take", _fschedule_injective)
+
 # strided_slice
 reg.register_pattern("strided_slice", OpPattern.INJECTIVE)
 reg.register_schedule("strided_slice", _fschedule_injective)
diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
index 72e49a040efe..5bb2ec137594 100644
--- a/nnvm/src/top/tensor/transform.cc
+++ b/nnvm/src/top/tensor/transform.cc
@@ -1001,6 +1001,126 @@ Examples::
     return Array<Tensor>{ topi::flip(inputs[0], param.axis) };
 });
 
+
+// take
+DMLC_REGISTER_PARAMETER(TakeParam);
+
+inline bool TakeInferShape(const NodeAttrs& attrs,
+                           std::vector<TShape>* in_shape,
+                           std::vector<TShape>* out_shape) {
+  CHECK_EQ(in_shape->size(), 2U);
+  CHECK_EQ(out_shape->size(), 1U);
+  const TShape& dshape = (*in_shape)[0];
+  const TShape& indicesshape = (*in_shape)[1];
+  if (dshape.ndim() == 0) return false;
+  if (indicesshape.ndim() == 0) return false;
+
+  const TakeParam& param = nnvm::get<TakeParam>(attrs.parsed);
+  TShape oshape((!param.axis ? 0: dshape.ndim() - 1) + indicesshape.ndim());
+  if (!param.axis) {
+    for (size_t j = 0; j < indicesshape.ndim(); ++j) {
+      oshape[j] = indicesshape[j];
+    }
+  } else {
+    int axis = param.axis.value();
+    if (axis < 0) {
+      axis += dshape.ndim();
+    }
+    CHECK_LT(axis, dshape.ndim());
+
+    size_t posi = 0;
+    for (size_t i = 0; i < dshape.ndim(); ++i) {
+      if (static_cast<int>(i) == axis) {
+        for (size_t j = 0; j < indicesshape.ndim(); ++j) {
+          oshape[posi++] = indicesshape[j];
+        }
+      } else {
+        oshape[posi++] = dshape[i];
+      }
+    }
+  }
+  NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, 0, dshape);
+  NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, 1, indicesshape);
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, oshape);
+  return dshape.Size() != 0;
+}
+
+inline bool TakeInferType(const NodeAttrs& attrs,
+                          std::vector<int>* in_attrs,
+                          std::vector<int>* out_attrs) {
+  CHECK_EQ(in_attrs->size(), 2U);
+  CHECK_EQ(out_attrs->size(), 1U);
+  CHECK_EQ((*in_attrs)[1], kInt32);
+  NNVM_ASSIGN_INPUT_TYPE(attrs, *in_attrs, 0, (*in_attrs)[0]);
+  NNVM_ASSIGN_INPUT_TYPE(attrs, *in_attrs, 1, static_cast<int>(kInt32));
+  NNVM_ASSIGN_OUTPUT_TYPE(attrs, *out_attrs, 0, (*in_attrs)[0]);
+  return true;
+}
+
+inline bool TakeCorrectLayout(const NodeAttrs& attrs,
+                              std::vector<Layout> *ilayouts,
+                              const std::vector<Layout> *last_ilayouts,
+                              std::vector<Layout> *olayouts) {
+  CHECK_EQ(ilayouts->size(), last_ilayouts->size());
+  CHECK_EQ(olayouts->size(), 1U);
+
+  for (size_t i = 0; i < ilayouts->size(); ++i) {
+    const Layout& input = last_ilayouts->at(i).defined() ?
+                          last_ilayouts->at(i) : ilayouts->at(i);
+    NNVM_ASSIGN_LAYOUT(*ilayouts, i, input);
+  }
+
+  return true;
+}
+
+NNVM_REGISTER_OP(take)
+.describe(R"code(Take elements from an array along an axis.
+
+When axis is not None, this function does the same thing as 'fancy' indexing
+(indexing arrays using arrays); however, it can be easier to use if you need
+elements along a given axis.
+
+**Note** that when axis is none the flattened input array is used.
+
+Examples::
+
+  a = [[ 1, 2],
+       [ 3, 4]]
+  indices = [3, 0, 2]
+  take(a, indices) = [ 4, 1, 3]
+
+  a = [[ 1., 2.],
+       [ 3., 4.]]
+  indices = [1, 0]
+  take(a, indices, axis=1) = [[ 2., 1.],
+                              [ 4., 3.]]
+
+  )code" NNVM_ADD_FILELINE)
+.add_argument("data", "Tensor", "Array to be indexed")
+.add_argument("indices", "Tensor", "The indices of the values to extract")
+.add_arguments(TakeParam::__FIELDS__())
+.set_attr_parser(ParamParser<TakeParam>)
+.set_attr<FInferShape>("FInferShape", TakeInferShape)
+.set_attr<FInferType>("FInferType", TakeInferType)
+.set_attr<FCorrectLayout>("FCorrectLayout", TakeCorrectLayout)
+.set_num_inputs(2)
+.set_num_outputs(1)
+.set_support_level(1)
+.set_attr<FTVMCompute>(
+    "FTVMCompute", [](const NodeAttrs& attrs,
+                      const Array<Tensor>& inputs,
+                      const Array<Tensor>& out_info) {
+      const TakeParam& param = nnvm::get<TakeParam>(attrs.parsed);
+      if (!param.axis) {
+        return Array<Tensor>{
+            topi::take(inputs[0], inputs[1]) };
+      } else {
+        return Array<Tensor>{
+            topi::take(inputs[0], inputs[1], param.axis.value()) };
+      }
+  });
+
+
 // SliceLike
 DMLC_REGISTER_PARAMETER(SliceLikeParam);
 
diff --git a/nnvm/tests/python/compiler/test_top_level1.py b/nnvm/tests/python/compiler/test_top_level1.py
index b97aff8effb6..d9c6655fea1d 100644
--- a/nnvm/tests/python/compiler/test_top_level1.py
+++ b/nnvm/tests/python/compiler/test_top_level1.py
@@ -365,6 +365,40 @@ def test_strided_slice():
     verify_strided_slice((3, 4, 3), [1, 1, 0], [4, 4])
     verify_strided_slice((3, 4, 3), [1, 1], [4, 4, 3])
 
+def verify_take(src_shape, indices_src, axis=None):
+    src_dtype = "float32"
+    indices_dtype = "int32"
+    indices_src = np.array(indices_src, dtype=indices_dtype)
+    a = sym.Variable("a")
+    indices = sym.Variable("indices")
+    y = sym.take(a, indices, axis=axis)
+    for target, ctx in ctx_list():
+        # set input
+        shape_dict = {"a":src_shape, "indices":indices_src.shape}
+        type_dict = {"a":src_dtype, "indices":indices_dtype}
+        graph, lib, _ = nnvm.compiler.build(y, target, shape=shape_dict, dtype=type_dict)
+        m = graph_runtime.create(graph, lib, ctx)
+
+        shape_size = 1
+        for i in range(len(src_shape)):
+            shape_size = shape_size * src_shape[i]
+        a_src = np.arange(shape_size, dtype=src_dtype).reshape((src_shape))
+        out_np = np.take(a_src, indices_src, axis=axis)
+        m.run(a=a_src, indices=indices_src)
+        out = m.get_output(0, tvm.nd.empty(out_np.shape, dtype=src_dtype))
+        np.testing.assert_allclose(out.asnumpy(), out_np, atol=1e-5, rtol=1e-5)
+
+def test_take():
+    verify_take((4,), [1])
+    verify_take((4,), [[0,1,2,3]])
+    verify_take((3,3,3), [[11,25]])
+    verify_take((4,), [[0,1],[2,3]])
+    verify_take((4,), [1], 0)
+    verify_take((2,2), [[[1,0],[0,1]]], 0)
+    verify_take((2,2), [[[1,0],[0,1]]], 1)
+    verify_take((4,3,5,6), [[2,1,0,0]], -2)
+
+
 def verify_squeeze(dshape, axis):
     x = sym.Variable("x")
     if axis:
@@ -481,6 +515,7 @@ def test_l2_normalize():
     test_softmax()
     test_squeeze()
     test_pad()
+    test_take()
     test_lrn()
     test_l2_normalize()
     test_strided_slice()

From 5f9c63a14850df5eeebe26a26176395b123a988a Mon Sep 17 00:00:00 2001
From: Siva <sivar.b@huawei.com>
Date: Wed, 4 Jul 2018 21:33:40 +0530
Subject: [PATCH 789/949] [AVG POOL] Asymmetric padding (SAME) support. (#1346)

---
 nnvm/include/nnvm/top/nn.h                    | 10 ++-
 nnvm/python/nnvm/frontend/tensorflow.py       | 17 ++---
 nnvm/src/top/nn/pooling.cc                    | 69 +++++++++++++++----
 .../frontend/tensorflow/test_forward.py       | 36 ++++++++--
 topi/include/topi/nn/pooling.h                | 32 ++++-----
 topi/python/topi/nn/pooling.py                |  4 +-
 topi/tests/python/test_topi_pooling.py        | 37 +++++-----
 topi/tests/python_cpp/test_topi_pooling.py    | 36 +++++-----
 8 files changed, 156 insertions(+), 85 deletions(-)

diff --git a/nnvm/include/nnvm/top/nn.h b/nnvm/include/nnvm/top/nn.h
index f37811315e43..86bdc60a6236 100644
--- a/nnvm/include/nnvm/top/nn.h
+++ b/nnvm/include/nnvm/top/nn.h
@@ -239,7 +239,10 @@ struct MaxPool2DParam : public dmlc::Parameter<MaxPool2DParam> {
       .describe("Specifies the strides of the convolution.");
     DMLC_DECLARE_FIELD(padding).set_default(TShape({0, 0}))
       .describe("If padding is non-zero, then the input is implicitly zero-padded"
-                "on both sides for padding number of points");
+                "Padding support both symmetric and asymmetric as"
+                "one int : same padding used on all sides"
+                "two int : bottom, right will use same padding as top, left"
+                "four int : padding width in the order of (top, left, bottom, right)");
     DMLC_DECLARE_FIELD(layout).set_default("NCHW")
       .describe("Dimension ordering of data and weight. Can be 'NCHW', 'NHWC', etc."
                 "'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
@@ -266,7 +269,10 @@ struct AvgPool2DParam : public dmlc::Parameter<AvgPool2DParam> {
       .describe("Specifies the strides of the convolution.");
     DMLC_DECLARE_FIELD(padding).set_default(TShape({0, 0}))
       .describe("If padding is non-zero, then the input is implicitly zero-padded"
-                "on both sides for padding number of points");
+                "Padding support both symmetric and asymmetric as"
+                "one int : same padding used on all sides"
+                "two int : bottom, right will use same padding as top, left"
+                "four int : padding width in the order of (top, left, bottom, right)");
     DMLC_DECLARE_FIELD(layout).set_default("NCHW")
       .describe("Dimension ordering of data and weight. Can be 'NCHW', 'NHWC', etc."
                 "'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
diff --git a/nnvm/python/nnvm/frontend/tensorflow.py b/nnvm/python/nnvm/frontend/tensorflow.py
index ce047d27da07..fc7aeeda7101 100644
--- a/nnvm/python/nnvm/frontend/tensorflow.py
+++ b/nnvm/python/nnvm/frontend/tensorflow.py
@@ -137,22 +137,13 @@ def _impl(inputs, attr, params):
             pad_v = _get_pad_pair(in_h, kernel_h, stride_h)
             pad_h = _get_pad_pair(in_w, kernel_w, stride_w)
 
-            if attr['data_format'] == 'NHWC':
-                inputs[0] = _sym.pad(data=inputs[0],
-                                     pad_width=((0, 0),
-                                                (pad_v[0], pad_v[1]),
-                                                (pad_h[0], pad_h[1]),
-                                                (0, 0)))
-            else:
-                inputs[0] = _sym.pad(data=inputs[0],
-                                     pad_width=((0, 0),
-                                                (0, 0),
-                                                (pad_v[0], pad_v[1]),
-                                                (pad_h[0], pad_h[1])))
-            attr['padding'] = [0, 0]
+            attr['padding'] = [pad_v[0], pad_h[0], pad_v[1], pad_h[1]]
         else:
             raise TypeError("Unsupported padding type : {}".format(attr['padding']))
 
+        if name == "avg_pool":
+            attr['count_include_pad'] = False
+
         return AttrCvt(
             op_name=_dimension_picker(name),
             transforms={
diff --git a/nnvm/src/top/nn/pooling.cc b/nnvm/src/top/nn/pooling.cc
index 54eb0d7db3be..cccd5b1c710b 100644
--- a/nnvm/src/top/nn/pooling.cc
+++ b/nnvm/src/top/nn/pooling.cc
@@ -1,3 +1,4 @@
+
 /*!
  *  Copyright (c) 2017 by Contributors
  * \file pooling.cc
@@ -44,23 +45,39 @@ inline bool Pool2DInferShape(const nnvm::NodeAttrs& attrs,
   const auto hidx = layout.indexof('H');
   const auto widx = layout.indexof('W');
 
+  dim_t pad_h, pad_w;
+  if (param.padding.ndim() == 1) {
+    pad_h = param.padding[0] * 2;
+    pad_w = param.padding[0] * 2;
+  } else if (param.padding.ndim() == 2) {
+    // (top, left)
+    pad_h = param.padding[0] * 2;
+    pad_w = param.padding[1] * 2;
+  } else if (param.padding.ndim() == 4) {
+    // (top, left, bottom, right)
+    pad_h = param.padding[0] + param.padding[2];
+    pad_w = param.padding[1] + param.padding[3];
+  } else {
+    return false;
+  }
+
   TShape oshape = dshape;
-  CHECK(param.pool_size[0] <= dshape[hidx] + 2 * param.padding[0])
+  CHECK(param.pool_size[0] <= dshape[hidx] + pad_h)
       << "pool size (" << param.pool_size[0] << ") exceeds input (" << dshape[hidx]
-      << " padded to " << (dshape[hidx] + 2*param.padding[0]) << ")";
-  CHECK(param.pool_size[1] <= dshape[widx] + 2 * param.padding[1])
+      << " padded to " << (dshape[hidx] + pad_h) << ")";
+  CHECK(param.pool_size[1] <= dshape[widx] + pad_w)
       << "pool size (" << param.pool_size[1] << ") exceeds input (" << dshape[widx]
-      << " padded to " << (dshape[widx] + 2*param.padding[1]) << ")";
+      << " padded to " << (dshape[widx] + pad_w) << ")";
 
   if (!param.ceil_mode) {
-    oshape[hidx] = ((dshape[hidx] + 2 * param.padding[0] - param.pool_size[0]) /
+    oshape[hidx] = ((dshape[hidx] + pad_h - param.pool_size[0]) /
                     param.strides[0]) + 1;
-    oshape[widx] = ((dshape[widx] + 2 * param.padding[1] - param.pool_size[1]) /
+    oshape[widx] = ((dshape[widx] + pad_w - param.pool_size[1]) /
                     param.strides[1]) + 1;
   } else {
-    oshape[hidx] = ((dshape[hidx] + 2 * param.padding[0] - param.pool_size[0] +
+    oshape[hidx] = ((dshape[hidx] + pad_h - param.pool_size[0] +
                     param.strides[0] - 1) / param.strides[0]) + 1;
-    oshape[widx] = ((dshape[3] + 2 * param.padding[1] - param.pool_size[1] +
+    oshape[widx] = ((dshape[3] + pad_w - param.pool_size[1] +
                     param.strides[1] - 1) / param.strides[1]) + 1;
   }
   NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, oshape);
@@ -108,8 +125,13 @@ NNVM_REGISTER_OP(max_pool2d)
            (batch_size, channels, out_height, out_width)  if `layout` is `NCHW`.
            out_height and out_width are calculated as::
 
-               out_height = floor((height+2*padding[0]-pool_size[0])/strides[0])+1
-               out_width = floor((width+2*padding[1]-pool_size[1])/strides[1])+1
+               out_height = floor((height+padding[0]+padding[2]-pool_size[0])/strides[0])+1
+               out_width = floor((width+padding[1]+padding[3]-pool_size[1])/strides[1])+1
+
+           where padding will be an expanded array based on number of values passed as::
+               one int : all sides same padding used.
+               two int : bottom, right use same as top and left.
+               four int: padding width in the order of (top, left, bottom, right).
 
            When `ceil_mode` is `True`, ceil will be used instead of floor in this
            equation.
@@ -143,6 +165,15 @@ NNVM_REGISTER_OP(max_pool2d)
     << "Pool2D only support 4-D input (e.g., NCHW)"
     << " or 5-D input (last dimension is a split of channel)";
 
+  if (param.padding.ndim() == 1) {
+    padding.push_back(padding[0]);
+    padding.push_back(padding[0]);
+    padding.push_back(padding[0]);
+  } else if (param.padding.ndim() == 2) {
+    padding.push_back(padding[0]);
+    padding.push_back(padding[1]);
+  }
+
   return Array<Tensor>{
     topi::nn::pool(inputs[0], pool_size, strides, padding,
                    topi::nn::kMaxPool, ceil_mode, layout.name())};
@@ -182,8 +213,13 @@ NNVM_REGISTER_OP(avg_pool2d)
            (batch_size, channels, out_height, out_width)  if `layout` is `NCHW`.
            out_height and out_width are calculated as::
 
-               out_height = floor((height+2*padding[0]-pool_size[0])/strides[0])+1
-               out_width = floor((width+2*padding[1]-pool_size[1])/strides[1])+1
+               out_height = floor((height+padding[0]+padding[2]-pool_size[0])/strides[0])+1
+               out_width = floor((width+padding[1]+padding[3]-pool_size[1])/strides[1])+1
+
+           where padding will be an expanded array based on number of values passed as::
+               one int : all sides same padding used.
+               two int : bottom, right use same as top and left.
+               four int: padding width in the order of (top, left, bottom, right).
 
            When `ceil_mode` is `True`, ceil will be used instead of floor in this
            equation.
@@ -216,6 +252,15 @@ NNVM_REGISTER_OP(avg_pool2d)
     << "Pool2D only support 4-D input (e.g., NCHW)"
     << " or 5-D input (last dimension is a split of channel)";
 
+  if (param.padding.ndim() == 1) {
+    padding.push_back(padding[0]);
+    padding.push_back(padding[0]);
+    padding.push_back(padding[0]);
+  } else if (param.padding.ndim() == 2) {
+    padding.push_back(padding[0]);
+    padding.push_back(padding[1]);
+  }
+
   return Array<Tensor>{
     topi::nn::pool(inputs[0], pool_size, strides, padding,
                    topi::nn::kAvgPool, ceil_mode, layout.name(), count_include_pad)};
diff --git a/nnvm/tests/python/frontend/tensorflow/test_forward.py b/nnvm/tests/python/frontend/tensorflow/test_forward.py
index f3dbc3cdbe40..1ec8222e95f1 100644
--- a/nnvm/tests/python/frontend/tensorflow/test_forward.py
+++ b/nnvm/tests/python/frontend/tensorflow/test_forward.py
@@ -116,13 +116,13 @@ def test_forward_pooling():
                  pooling_type='MAX',
                  dilation_rate=[1, 1],
                  strides=[1, 1])
-
     _test_pooling(input_shape=[2, 9, 10, 2],
                  window_shape=[1, 1],
                  padding='SAME',
                  pooling_type='AVG',
                  dilation_rate=[1, 1],
                  strides=[1, 1])
+
     _test_pooling(input_shape=[2, 10, 9, 2],
                  window_shape=[1, 1],
                  padding='SAME',
@@ -136,6 +136,33 @@ def test_forward_pooling():
                  dilation_rate=[1, 1],
                  strides=[1, 1])
 
+    _test_pooling(input_shape=[2, 9, 10, 2],
+                 window_shape=[2, 1],
+                 padding='SAME',
+                 pooling_type='MAX',
+                 dilation_rate=[1, 1],
+                 strides=[1, 1])
+    _test_pooling(input_shape=[2, 9, 10, 2],
+                 window_shape=[2, 1],
+                 padding='SAME',
+                 pooling_type='AVG',
+                 dilation_rate=[1, 1],
+                 strides=[2, 1])
+
+    _test_pooling(input_shape=[2, 10, 9, 2],
+                 window_shape=[2, 3],
+                 padding='SAME',
+                 pooling_type='MAX',
+                 dilation_rate=[1, 1],
+                 strides=[2, 1])
+    _test_pooling(input_shape=[2, 10, 9, 2],
+                 window_shape=[2, 3],
+                 padding='SAME',
+                 pooling_type='AVG',
+                 dilation_rate=[1, 1],
+                 strides=[1, 2])
+
+
 #######################################################################
 # Convolution
 # -----------
@@ -419,12 +446,7 @@ def test_forward_inception_v3():
             top_tvm = np.squeeze(tvm_output).argsort()[-3:][::-1]
             top_tf = np.squeeze(tf_output).argsort()[-3:][::-1]
 
-            # TVM implementation of SAME padding some times make a slight deviation.
-            # Hence check for top predictions.
-            top_tvm = np.sort(top_tvm)
-            top_tf = np.sort(top_tf)
-
-            np.testing.assert_allclose(top_tf, top_tvm)
+            np.testing.assert_allclose(top_tf, top_tvm, rtol=1e-5, atol=1e-5)
 
 #######################################################################
 # Inception V1
diff --git a/topi/include/topi/nn/pooling.h b/topi/include/topi/nn/pooling.h
index 20a7ffe975bf..26d61d42991d 100644
--- a/topi/include/topi/nn/pooling.h
+++ b/topi/include/topi/nn/pooling.h
@@ -52,28 +52,25 @@ inline Tensor pool_impl(const Tensor& x,
   CHECK(x->shape.size() >= 2) << "Pooling input must >= 2-D (H, W)";
   CHECK_EQ(kernel_size.size(), 2) << "Pooling kernel_size must have 2 elements";
   CHECK_EQ(stride_size.size(), 2) << "Pooling stride_size must have 2 elements";
-  CHECK_EQ(padding_size.size(), 2) << "Pooling padding_size must have 2 elements";
+  CHECK_EQ(padding_size.size(), 4) << "Pooling padding_size must have 4 elements";
 
   auto kernel_height = kernel_size[0];
   auto kernel_width = kernel_size[1];
   auto stride_height = stride_size[0];
   auto stride_width = stride_size[1];
-  auto padding_height = padding_size[0];
-  auto padding_width = padding_size[1];
 
   auto height = x->shape[height_axis];
   auto width = x->shape[width_axis];
 
-  auto pad_tuple = detail::GetPadTuple(padding_height, padding_width);
-  auto pad_top = pad_tuple[0];
-  auto pad_left = pad_tuple[1];
-  auto pad_down = pad_tuple[2];
-  auto pad_right = pad_tuple[3];
+  auto pad_top = padding_size[0];
+  auto pad_left = padding_size[1];
+  auto pad_bottom = padding_size[2];
+  auto pad_right = padding_size[3];
 
   if (ceil_mode) {
     // Additional padding to ensure we do ceil instead of floor when
     // dividing by stride.
-    pad_down += stride_height - 1;
+    pad_bottom += stride_height - 1;
     pad_right += stride_width - 1;
   }
 
@@ -82,11 +79,11 @@ inline Tensor pool_impl(const Tensor& x,
   pad_before.Set(width_axis, pad_left);
 
   Array<Expr> pad_after(std::vector<Expr>(x->shape.size(), 0));
-  pad_after.Set(height_axis, pad_down);
+  pad_after.Set(height_axis, pad_bottom);
   pad_after.Set(width_axis, pad_right);
 
   auto out_height = tvm::ir::Simplify(
-    (height - kernel_height + pad_top + pad_down) / stride_height + 1);
+    (height - kernel_height + pad_top + pad_bottom) / stride_height + 1);
   auto out_width = tvm::ir::Simplify(
     (width - kernel_width + pad_left + pad_right) / stride_width + 1);
 
@@ -97,9 +94,12 @@ inline Tensor pool_impl(const Tensor& x,
   out_shape.Set(height_axis, out_height);
   out_shape.Set(width_axis, out_width);
 
-  const int64_t *padding_h = HalideIR::Internal::as_const_int(padding_height);
-  const int64_t *padding_w = HalideIR::Internal::as_const_int(padding_width);
-  const bool do_pad = ((padding_h && *padding_h) || (padding_w && *padding_w));
+  const int64_t *padding_h0 = HalideIR::Internal::as_const_int(pad_top);
+  const int64_t *padding_w0 = HalideIR::Internal::as_const_int(pad_left);
+  const int64_t *padding_h1 = HalideIR::Internal::as_const_int(pad_bottom);
+  const int64_t *padding_w1 = HalideIR::Internal::as_const_int(pad_right);
+  const bool do_pad = ((padding_h0 && *padding_h0) || (padding_w0 && *padding_w0)) ||
+                      ((padding_h1 && *padding_h1) || (padding_w1 && *padding_w1));
 
   if (pool_type == kMaxPool) {
     auto temp = do_pad ? pad(x, pad_before, pad_after, x->dtype.min(), "pad_temp") : x;
@@ -125,8 +125,8 @@ inline Tensor pool_impl(const Tensor& x,
       if (count_include_pad) {
         return tsum(output) / (kernel_height * kernel_width);
       } else {
-        Expr h_start = output[height_axis] * stride_height - padding_height;
-        Expr w_start = output[width_axis] * stride_width - padding_width;
+        Expr h_start = output[height_axis] * stride_height - pad_top;
+        Expr w_start = output[width_axis] * stride_width - pad_left;
         Expr h_end = ir::Min::make(h_start + kernel_height, height);
         Expr w_end = ir::Min::make(w_start + kernel_width, width);
         h_start = ir::Max::make(h_start, make_const(Int(32), 0));
diff --git a/topi/python/topi/nn/pooling.py b/topi/python/topi/nn/pooling.py
index 0660ce7f1a3a..478141ee1d7e 100644
--- a/topi/python/topi/nn/pooling.py
+++ b/topi/python/topi/nn/pooling.py
@@ -69,8 +69,8 @@ def pool(data,
     stride : list/tuple of two ints
         Stride size, [stride_height, stride_width]
 
-    padding : list/tuple of two ints
-        Pad size, [pad_height, pad_width]
+    padding : list/tuple of four ints
+        Pad size, [pad_top, pad_left, pad_bottom, pad_right]]
 
     pool_type : str
         Pool type, 'max' or 'avg'
diff --git a/topi/tests/python/test_topi_pooling.py b/topi/tests/python/test_topi_pooling.py
index 5b6201cd65fb..c9f790146b4a 100644
--- a/topi/tests/python/test_topi_pooling.py
+++ b/topi/tests/python/test_topi_pooling.py
@@ -9,7 +9,7 @@ def verify_pool(n, ic, ih, kh, sh, padding, pool_type, ceil_mode, count_include_
     iw = ih
     kw = kh
     sw = sh
-    ph, pw = padding
+    pt, pl, pb, pr = padding
     A = tvm.placeholder((n, ic, ih, iw), name='A')
     B = topi.nn.pool(A, kernel=[kh, kw], stride=[sh, sw], padding=padding,
                      pool_type=pool_type, ceil_mode=ceil_mode, count_include_pad=count_include_pad)
@@ -19,16 +19,15 @@ def verify_pool(n, ic, ih, kh, sh, padding, pool_type, ceil_mode, count_include_
     bshape = get_const_tuple(B.shape)
     ashape = get_const_tuple(A.shape)
     if ceil_mode:
-        assert bshape[2] == int(math.ceil(float(ashape[2] - kh + ph * 2) / sh) + 1)
-        assert bshape[3] == int(math.ceil(float(ashape[3] - kw + pw * 2) / sw) + 1)
+        assert bshape[2] == int(math.ceil(float(ashape[2] - kh + pt + pb) / sh) + 1)
+        assert bshape[3] == int(math.ceil(float(ashape[3] - kw + pl + pr) / sw) + 1)
     else:
-        assert bshape[2] == int(math.floor(float(ashape[2] - kh + ph * 2) / sh) + 1)
-        assert bshape[3] == int(math.floor(float(ashape[3] - kw + pw * 2) / sw) + 1)
-
+        assert bshape[2] == int(math.floor(float(ashape[2] - kh + pt + pb) / sh) + 1)
+        assert bshape[3] == int(math.floor(float(ashape[3] - kw + pl + pr) / sw) + 1)
 
     a_np = np.random.uniform(low=0.001, size=(n, ic, ih, iw)).astype(dtype)
-    pad_np = np.zeros(shape=(n, ic, ih+2*ph, iw+2*pw)).astype(dtype)
-    no_zero = (range(n), range(ic), (range(ph, ih+ph)), (range(pw, iw+pw)))
+    pad_np = np.zeros(shape=(n, ic, ih+pt+pb, iw+pl+pr)).astype(dtype)
+    no_zero = (range(n), range(ic), (range(pt, ih+pt)), (range(pl, iw+pl)))
     pad_np[np.ix_(*no_zero)] = a_np
     _, oc, oh, ow = get_const_tuple(B.shape)
     b_np = np.zeros(shape=(n, oc, oh, ow)).astype(dtype)
@@ -67,15 +66,19 @@ def check_device(device):
         check_device(device)
 
 def test_pool():
-    verify_pool(1, 256, 32, 2, 2, [0, 0], 'avg', False, True)
-    verify_pool(1, 256, 31, 3, 3, [1, 2], 'avg', False, True)
-    verify_pool(1, 256, 32, 2, 2, [1, 2], 'avg', False, False)
-    verify_pool(1, 256, 31, 4, 4, [3, 3], 'avg', False, False)
-    verify_pool(1, 256, 31, 4, 4, [0, 0], 'avg', False, False)
-    verify_pool(1, 256, 32, 2, 2, [0, 0], 'max', False)
-    verify_pool(1, 256, 31, 3, 3, [2, 1], 'max', False)
-    verify_pool(1, 256, 31, 3, 3, [2, 1], 'max', True)
-
+    verify_pool(1, 256, 32, 2, 2, [0, 0, 0, 0], 'avg', False, True)
+    verify_pool(1, 256, 31, 3, 3, [1, 2, 1, 2], 'avg', False, True)
+    verify_pool(1, 256, 32, 2, 2, [1, 2, 1, 2], 'avg', False, False)
+    verify_pool(1, 256, 31, 4, 4, [3, 3, 3, 3], 'avg', False, False)
+    verify_pool(1, 256, 31, 4, 4, [0, 0, 0, 0], 'avg', False, False)
+    verify_pool(1, 256, 32, 2, 2, [0, 0, 0, 0], 'max', False)
+    verify_pool(1, 256, 31, 3, 3, [2, 1, 2, 1], 'max', False)
+    verify_pool(1, 256, 31, 3, 3, [2, 1, 2, 1], 'max', True)
+
+    verify_pool(1, 256, 31, 3, 3, [2, 1, 0, 3], 'avg', False, True)
+    verify_pool(1, 256, 32, 2, 2, [0, 3, 2, 1], 'avg', False, False)
+    verify_pool(1, 256, 31, 3, 3, [1, 0, 3, 2], 'max', False)
+    verify_pool(1, 256, 31, 3, 3, [3, 2, 1, 0], 'max', True)
 
 
 def verify_global_pool(n, c, h, w, pool_type):
diff --git a/topi/tests/python_cpp/test_topi_pooling.py b/topi/tests/python_cpp/test_topi_pooling.py
index a2bbc3227d94..42232c8e4848 100644
--- a/topi/tests/python_cpp/test_topi_pooling.py
+++ b/topi/tests/python_cpp/test_topi_pooling.py
@@ -13,7 +13,7 @@ def verify_pool(n, ic, ih, kh, sh, padding, pool_type, ceil_mode, count_include_
     iw = ih
     kw = kh
     sw = sh
-    ph, pw = padding
+    pt, pl, pb, pr = padding
     A = tvm.placeholder((n, ic, ih, iw), name='A')
     B = topi.cpp.nn.pool(A, [kh, kw], [sh, sw], padding,
                          pool_code[pool_type], ceil_mode, "NCHW", count_include_pad)
@@ -23,16 +23,16 @@ def verify_pool(n, ic, ih, kh, sh, padding, pool_type, ceil_mode, count_include_
     bshape = get_const_tuple(B.shape)
     ashape = get_const_tuple(A.shape)
     if ceil_mode:
-        assert bshape[2] == int(math.ceil(float(ashape[2] - kh + ph * 2) / sh) + 1)
-        assert bshape[3] == int(math.ceil(float(ashape[3] - kw + pw * 2) / sw) + 1)
+        assert bshape[2] == int(math.ceil(float(ashape[2] - kh + pt + pb) / sh) + 1)
+        assert bshape[3] == int(math.ceil(float(ashape[3] - kw + pl + pr) / sw) + 1)
     else:
-        assert bshape[2] == int(math.floor(float(ashape[2] - kh + ph * 2) / sh) + 1)
-        assert bshape[3] == int(math.floor(float(ashape[3] - kw + pw * 2) / sw) + 1)
+        assert bshape[2] == int(math.floor(float(ashape[2] - kh + pt + pb) / sh) + 1)
+        assert bshape[3] == int(math.floor(float(ashape[3] - kw + pl + pr) / sw) + 1)
 
 
     a_np = np.random.uniform(size=(n, ic, ih, iw)).astype(dtype)
-    pad_np = np.zeros(shape=(n, ic, ih+2*ph, iw+2*pw)).astype(dtype)
-    no_zero = (range(n), range(ic), (range(ph, ih+ph)), (range(pw, iw+pw)))
+    pad_np = np.zeros(shape=(n, ic, ih+pt+pb, iw+pl+pr)).astype(dtype)
+    no_zero = (range(n), range(ic), (range(pt, ih+pt)), (range(pl, iw+pl)))
     pad_np[np.ix_(*no_zero)] = a_np
     _, oc, oh, ow = get_const_tuple(B.shape)
     b_np = np.zeros(shape=(n, oc, oh, ow)).astype(dtype)
@@ -73,15 +73,19 @@ def check_device(device):
         check_device(device)
 
 def test_pool():
-    verify_pool(1, 256, 32, 2, 2, [0, 0], 'avg', False, True)
-    verify_pool(1, 256, 31, 3, 3, [1, 2], 'avg', False, True)
-    verify_pool(1, 256, 32, 2, 2, [1, 2], 'avg', False, False)
-    verify_pool(1, 256, 31, 4, 4, [3, 3], 'avg', False, False)
-    verify_pool(1, 256, 31, 4, 4, [0, 0], 'avg', False, False)
-    verify_pool(1, 256, 32, 2, 2, [0, 0], 'max', False)
-    verify_pool(1, 256, 31, 3, 3, [2, 1], 'max', False)
-    verify_pool(1, 256, 31, 3, 3, [2, 1], 'max', True)
-
+    verify_pool(1, 256, 32, 2, 2, [0, 0, 0, 0], 'avg', False, True)
+    verify_pool(1, 256, 31, 3, 3, [1, 2, 1, 2], 'avg', False, True)
+    verify_pool(1, 256, 32, 2, 2, [1, 2, 1, 2], 'avg', False, False)
+    verify_pool(1, 256, 31, 4, 4, [3, 3, 3, 3], 'avg', False, False)
+    verify_pool(1, 256, 31, 4, 4, [0, 0, 0, 0], 'avg', False, False)
+    verify_pool(1, 256, 32, 2, 2, [0, 0, 0, 0], 'max', False)
+    verify_pool(1, 256, 31, 3, 3, [2, 1, 2, 1], 'max', False)
+    verify_pool(1, 256, 31, 3, 3, [2, 1, 2, 1], 'max', True)
+
+    verify_pool(1, 256, 31, 3, 3, [2, 1, 0, 3], 'avg', False, True)
+    verify_pool(1, 256, 32, 2, 2, [0, 3, 2, 1], 'avg', False, False)
+    verify_pool(1, 256, 31, 3, 3, [1, 0, 3, 2], 'max', False)
+    verify_pool(1, 256, 31, 3, 3, [3, 2, 1, 0], 'max', True)
 
 
 def verify_global_pool(n, c, h, w, pool_type):

From 922edfe0e9ee8983e39eb5569db587dcde52032d Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 4 Jul 2018 09:04:03 -0700
Subject: [PATCH 790/949] [TOPI] Update parameter name of conv2d (#1380)

---
 nnvm/python/nnvm/testing/darknet.py |  2 +-
 topi/python/topi/nn/conv2d.py       | 13 +++++++------
 topi/python/topi/rasp/conv2d.py     |  5 +----
 3 files changed, 9 insertions(+), 11 deletions(-)

diff --git a/nnvm/python/nnvm/testing/darknet.py b/nnvm/python/nnvm/testing/darknet.py
index ed8bf021c462..f6bbf8f7d951 100644
--- a/nnvm/python/nnvm/testing/darknet.py
+++ b/nnvm/python/nnvm/testing/darknet.py
@@ -9,8 +9,8 @@
 from __future__ import division
 import math
 import numpy as np
-import cv2
 from cffi import FFI
+import cv2
 
 def _resize_image(img, w_in, h_in):
     """Resize the image to the given height and width."""
diff --git a/topi/python/topi/nn/conv2d.py b/topi/python/topi/nn/conv2d.py
index 734ccb3bf2d6..43912368cf05 100644
--- a/topi/python/topi/nn/conv2d.py
+++ b/topi/python/topi/nn/conv2d.py
@@ -1,4 +1,5 @@
-# pylint: disable=invalid-name, unused-variable, too-many-locals, unused-argument
+# pylint: disable=invalid-name, unused-variable, too-many-locals
+# pylint: disable=unused-argument, redefined-builtin
 """Conv2D operators"""
 from __future__ import absolute_import as _abs
 from collections import namedtuple
@@ -75,7 +76,7 @@
 _CONV_SCHEDULE = {}
 
 @tvm.target.generic_func
-def conv2d(data, kernel, stride, padding, layout='NCHW', out_dtype=None):
+def conv2d(input, filter, strides, padding, layout='NCHW', out_dtype=None):
     """Conv2D operator.
 
     Parameters
@@ -86,7 +87,7 @@ def conv2d(data, kernel, stride, padding, layout='NCHW', out_dtype=None):
     filter : tvm.Tensor
         4-D with shape [num_filter, in_channel, filter_height, filter_width]
 
-    stride : int or a list/tuple of two ints
+    strides : int or a list/tuple of two ints
         stride size, or [stride_height, stride_width]
 
     padding : int or a list/tuple of two ints
@@ -103,11 +104,11 @@ def conv2d(data, kernel, stride, padding, layout='NCHW', out_dtype=None):
     # search platform specific declaration first
     # default declaration
     if layout == 'NCHW':
-        return conv2d_nchw(data, kernel, stride, padding, out_dtype)
+        return conv2d_nchw(input, filter, strides, padding, out_dtype)
     elif layout == 'HWCN':
-        return conv2d_hwcn(data, kernel, stride, padding, out_dtype)
+        return conv2d_hwcn(input, filter, strides, padding, out_dtype)
     elif layout == 'NHWC':
-        return conv2d_nhwc(data, kernel, stride, padding, out_dtype)
+        return conv2d_nhwc(input, filter, strides, padding, out_dtype)
     else:
         raise ValueError("not support this layout {} yet".format(layout))
 
diff --git a/topi/python/topi/rasp/conv2d.py b/topi/python/topi/rasp/conv2d.py
index 13af1b93705e..b958d00f2913 100644
--- a/topi/python/topi/rasp/conv2d.py
+++ b/topi/python/topi/rasp/conv2d.py
@@ -2,7 +2,6 @@
 """Conv2D schedule on raspberry pi"""
 from __future__ import absolute_import as _abs
 import tvm
-from tvm import target as _target
 from .. import tag
 from ..nn.conv2d import conv2d as _conv2d, _get_schedule
 from ..nn.conv2d import SpatialPack, Im2ColPack
@@ -201,9 +200,7 @@ def _schedule_im2col_conv2d(s, data, data_pad, data_col, data_vec,
     else:
         stride = infer_stride(data_pad, kernel, output)
     wkl = _get_workload(data, kernel, stride, padding, output.dtype)
-
-    with _target.rasp():
-        sch = _get_schedule(wkl)
+    sch = _get_schedule(wkl)
 
     H, W = wkl.height, wkl.width
     CI = wkl.in_filter

From 05dae3fc5be7049dd5113ad9f3b3fb9dbf359589 Mon Sep 17 00:00:00 2001
From: Liangfu Chen <liangfu.chen@icloud.com>
Date: Thu, 5 Jul 2018 00:04:41 +0800
Subject: [PATCH 791/949] support equal and not_equal in topi (#1373)

---
 docs/api/python/topi.rst                 |  6 ++
 topi/include/topi/broadcast.h            | 52 ++++++++++++++++
 topi/python/topi/broadcast.py            | 76 ++++++++++++++++++++++++
 topi/src/topi.cc                         |  4 ++
 topi/tests/python/test_topi_broadcast.py | 20 +++++++
 5 files changed, 158 insertions(+)

diff --git a/docs/api/python/topi.rst b/docs/api/python/topi.rst
index a2221847dae4..a14355d0f796 100644
--- a/docs/api/python/topi.rst
+++ b/docs/api/python/topi.rst
@@ -46,6 +46,8 @@ List of operators
    topi.max
    topi.sum
    topi.min
+   topi.argmax
+   topi.argmin
    topi.broadcast_to
    topi.add
    topi.subtract
@@ -57,6 +59,10 @@ List of operators
    topi.power
    topi.greater
    topi.less
+   topi.equal
+   topi.not_equal
+   topi.greater_equal
+   topi.less_equal
    topi.image.resize
 
 
diff --git a/topi/include/topi/broadcast.h b/topi/include/topi/broadcast.h
index 1a93d26f8c55..ad1c04ae1327 100644
--- a/topi/include/topi/broadcast.h
+++ b/topi/include/topi/broadcast.h
@@ -257,6 +257,58 @@ TOPI_DEFINE_BCAST_OP(greater, { return (a > b); });
  */
 TOPI_DEFINE_BCAST_OP(less, { return (a < b); });
 
+/*!
+ * \fn equal
+ * \brief Compute (A == B) with auto-broadcasting.
+ *
+ * \param A The first tensor, or Expr
+ * \param B The second tensor, or Expr
+ * \param name The name of the operation
+ * \param tag The tag to mark the operation
+ *
+ * \return The result.
+ */
+TOPI_DEFINE_BCAST_OP(equal, { return (a == b); });
+
+/*!
+ * \fn not_equal
+ * \brief Compute (A != B) with auto-broadcasting.
+ *
+ * \param A The first tensor, or Expr
+ * \param B The second tensor, or Expr
+ * \param name The name of the operation
+ * \param tag The tag to mark the operation
+ *
+ * \return The result.
+ */
+TOPI_DEFINE_BCAST_OP(not_equal, { return (a != b); });
+
+/*!
+ * \fn greater_equal
+ * \brief Compute (A >= B) with auto-broadcasting.
+ *
+ * \param A The first tensor, or Expr
+ * \param B The second tensor, or Expr
+ * \param name The name of the operation
+ * \param tag The tag to mark the operation
+ *
+ * \return The result.
+ */
+TOPI_DEFINE_BCAST_OP(greater_equal, { return (a >= b); });
+
+/*!
+ * \fn less_equal
+ * \brief Compute (A <= B) with auto-broadcasting.
+ *
+ * \param A The first tensor, or Expr
+ * \param B The second tensor, or Expr
+ * \param name The name of the operation
+ * \param tag The tag to mark the operation
+ *
+ * \return The result.
+ */
+TOPI_DEFINE_BCAST_OP(less_equal, { return (a <= b); });
+
 }  // namespace topi
 
 #endif  // TOPI_BROADCAST_H_
diff --git a/topi/python/topi/broadcast.py b/topi/python/topi/broadcast.py
index f088e48b0f14..9cfd99fbeec4 100644
--- a/topi/python/topi/broadcast.py
+++ b/topi/python/topi/broadcast.py
@@ -249,3 +249,79 @@ def less(lhs, rhs):
         Otherwise returns Tensor.
     """
     return _cpp.less(lhs, rhs)
+
+
+def equal(lhs, rhs):
+    """Compute (lhs==rhs) with auto-broadcasting
+
+    Parameters
+    ----------
+    lhs : tvm.Tensor or Expr
+        The left operand
+    rhs : tvm.Tensor or Expr
+        The right operand
+
+    Returns
+    -------
+    ret : tvm.Tensor or Expr
+        Returns Expr if both operands are Expr.
+        Otherwise returns Tensor.
+    """
+    return _cpp.equal(lhs, rhs)
+
+
+def not_equal(lhs, rhs):
+    """Compute (lhs!=rhs) with auto-broadcasting
+
+    Parameters
+    ----------
+    lhs : tvm.Tensor or Expr
+        The left operand
+    rhs : tvm.Tensor or Expr
+        The right operand
+
+    Returns
+    -------
+    ret : tvm.Tensor or Expr
+        Returns Expr if both operands are Expr.
+        Otherwise returns Tensor.
+    """
+    return _cpp.not_equal(lhs, rhs)
+
+
+def greater_equal(lhs, rhs):
+    """Compute (lhs>=rhs) with auto-broadcasting
+
+    Parameters
+    ----------
+    lhs : tvm.Tensor or Expr
+        The left operand
+    rhs : tvm.Tensor or Expr
+        The right operand
+
+    Returns
+    -------
+    ret : tvm.Tensor or Expr
+        Returns Expr if both operands are Expr.
+        Otherwise returns Tensor.
+    """
+    return _cpp.greater_equal(lhs, rhs)
+
+
+def less_equal(lhs, rhs):
+    """Compute (lhs<=rhs) with auto-broadcasting
+
+    Parameters
+    ----------
+    lhs : tvm.Tensor or Expr
+        The left operand
+    rhs : tvm.Tensor or Expr
+        The right operand
+
+    Returns
+    -------
+    ret : tvm.Tensor or Expr
+        Returns Expr if both operands are Expr.
+        Otherwise returns Tensor.
+    """
+    return _cpp.less_equal(lhs, rhs)
diff --git a/topi/src/topi.cc b/topi/src/topi.cc
index fe1f4098570d..a4a87b3096a3 100644
--- a/topi/src/topi.cc
+++ b/topi/src/topi.cc
@@ -116,6 +116,10 @@ TOPI_REGISTER_BCAST_OP("topi.left_shift", topi::left_shift);
 TOPI_REGISTER_BCAST_OP("topi.right_shift", topi::right_shift);
 TOPI_REGISTER_BCAST_OP("topi.greater", topi::greater);
 TOPI_REGISTER_BCAST_OP("topi.less", topi::less);
+TOPI_REGISTER_BCAST_OP("topi.equal", topi::equal);
+TOPI_REGISTER_BCAST_OP("topi.not_equal", topi::not_equal);
+TOPI_REGISTER_BCAST_OP("topi.greater_equal", topi::greater_equal);
+TOPI_REGISTER_BCAST_OP("topi.less_equal", topi::less_equal);
 
 /* Ops from elemwise.h */
 TVM_REGISTER_GLOBAL("topi.exp")
diff --git a/topi/tests/python/test_topi_broadcast.py b/topi/tests/python/test_topi_broadcast.py
index ecd238e78761..c5720050e538 100644
--- a/topi/tests/python/test_topi_broadcast.py
+++ b/topi/tests/python/test_topi_broadcast.py
@@ -142,10 +142,30 @@ def greater(x, y):
         return topi.greater(x, y).astype("int8")
     def less(x, y):
         return topi.less(x, y).astype("int8")
+    def equal(x, y):
+        return topi.equal(x, y).astype("int8")
+    def not_equal(x, y):
+        return topi.not_equal(x, y).astype("int8")
+    def greater_equal(x, y):
+        return topi.greater_equal(x, y).astype("int8")
+    def less_equal(x, y):
+        return topi.less_equal(x, y).astype("int8")
     verify_broadcast_binary_ele(
         (1, 2, 2), (2,), greater, np.greater)
     verify_broadcast_binary_ele(
         (2, 1, 2), (2, 3, 1), less, np.less)
+    verify_broadcast_binary_ele(
+        (2, 1, 2), (2, 3, 1), equal, np.equal,
+        lhs_min=-2, lhs_max=2, rhs_min=-2, rhs_max=2, dtype='int32')
+    verify_broadcast_binary_ele(
+        (2, 1, 2), (2, 3, 1), not_equal, np.not_equal,
+        lhs_min=-2, lhs_max=2, rhs_min=-2, rhs_max=2, dtype='int32')
+    verify_broadcast_binary_ele(
+        (7, 1, 5), (7, 3, 1), greater_equal, np.greater_equal,
+        lhs_min=-3, lhs_max=3, rhs_min=-3, rhs_max=3, dtype='int32')
+    verify_broadcast_binary_ele(
+        (7, 1, 5), (7, 3, 1), less_equal, np.less_equal,
+        lhs_min=-3, lhs_max=3, rhs_min=-3, rhs_max=3, dtype='int32')
 
 def test_shift():
     # explicit specify the output type

From c57027a9fa57a2f83306d415f95770d21d5a6ea9 Mon Sep 17 00:00:00 2001
From: Siva <sivar.b@huawei.com>
Date: Thu, 5 Jul 2018 00:01:02 +0530
Subject: [PATCH 792/949] [NNVM][ONNX] Slice, Floor, Ceil, Clip and MatMul
 support for frontend  #1297 (#1371)

---
 nnvm/python/nnvm/frontend/onnx.py             |  53 +++++++-
 .../python/frontend/onnx/test_forward.py      | 113 +++++++++++++++++-
 2 files changed, 154 insertions(+), 12 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/onnx.py b/nnvm/python/nnvm/frontend/onnx.py
index 8e8d7f17ce0e..0d8915ad40f6 100644
--- a/nnvm/python/nnvm/frontend/onnx.py
+++ b/nnvm/python/nnvm/frontend/onnx.py
@@ -446,6 +446,47 @@ def _impl_v1(cls, inputs, attr, params):
             inputs[0] = _sym.expand_dims(inputs[0], axis=axes, num_newaxis=1)
         return inputs[0]
 
+
+class Slice(OnnxOpConverter):
+    """ Operator converter for Slice.
+    """
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
+        if isinstance(attr['starts'], int):
+            attr['starts'] = (attr['starts'],)
+            attr['ends'] = (attr['ends'],)
+
+        try:
+            # Update the starts and ends according to axes if required.
+            if isinstance(attr['axes'], int):
+                attr['axes'] = (attr['axes'],)
+
+            if (max(attr['axes']) + 1) != len(attr['axes']):
+                new_axes = []
+                new_starts = []
+                new_ends = []
+                pop_index = 0
+                for i in range(max(attr['axes']) + 1):
+                    if i in attr['axes']:
+                        new_axes.append(i)
+                        new_starts.append(attr['starts'][pop_index])
+                        new_ends.append(attr['ends'][pop_index])
+                        pop_index += 1
+                    else:
+                        new_axes.append(i)
+                        new_starts.append(0)
+                        new_ends.append(np.iinfo(np.int32).max)
+                attr['axes'] = new_axes
+                attr['starts'] = new_starts
+                attr['ends'] = new_ends
+        except KeyError:
+            pass
+
+        return AttrCvt(op_name='strided_slice',
+                       transforms={'starts': 'begin',
+                                   'ends': 'end'},
+                       ignores=['axes'])(inputs, attr)
+
 # compatible operators that do NOT require any conversion.
 _identity_list = []
 
@@ -477,7 +518,7 @@ def _get_convert_map(opset):
         'SpatialBN': BatchNorm.get_converter(opset),
 
         # defs/generator
-        # 'Constant'
+        # 'Constant' # Implemented
         # 'RandomUniform'
         # 'RandomNormal'
         # 'RandomUniformLike'
@@ -493,8 +534,8 @@ def _get_convert_map(opset):
         'Neg': Renamer('negative'),
         'Abs': Absolute.get_converter(opset),
         'Reciprocal': Reciprocal.get_converter(opset),
-        # 'Floor'
-        # 'Ceil'
+        'Floor': Renamer('floor'),
+        'Ceil': Renamer('ceil'),
         'Sqrt': Renamer('sqrt'),
         'Relu': Renamer('relu'),
         'LeakyRelu': Renamer('leaky_relu'),
@@ -511,7 +552,7 @@ def _get_convert_map(opset):
         # 'Min' : this is the elemwise minimum
         'Sum': Sum.get_converter(opset),
         # 'Mean'
-        # 'Clip'
+        'Clip': AttrCvt('clip', transforms={'min': 'a_min', 'max': 'a_max'}),
         # softmax default axis is different in onnx
         'Softmax': AttrCvt('softmax', {'axis': ('axis', 1)}),
         'LogSoftmax': AttrCvt('log_softmax', {'axis': ('axis', 1)}),
@@ -519,7 +560,7 @@ def _get_convert_map(opset):
         'Softsign': Softsign.get_converter(opset),
         'SoftPlus': SoftPlus.get_converter(opset),
         'Gemm': Gemm.get_converter(opset),
-        # 'MatMul'  batch stacked dot operation
+        'MatMul': Renamer('matmul'),
 
         # defs/nn
         'AveragePool': AveragePool.get_converter(opset),
@@ -550,7 +591,7 @@ def _get_convert_map(opset):
         'Reshape': Reshape.get_converter(opset),
         'Concat': Renamer('concatenate'),
         'Split': AttrCvt('split', {'split': 'indices_or_sections'}),
-        # 'Slice'
+        'Slice': Slice.get_converter(opset),
         'Transpose': AttrCvt('transpose', {'perm': 'axes'}),
         # 'Gather'
         'Squeeze': Renamer('squeeze'),
diff --git a/nnvm/tests/python/frontend/onnx/test_forward.py b/nnvm/tests/python/frontend/onnx/test_forward.py
index 9e87f0d99cdd..0af92e3f4ee1 100644
--- a/nnvm/tests/python/frontend/onnx/test_forward.py
+++ b/nnvm/tests/python/frontend/onnx/test_forward.py
@@ -23,14 +23,15 @@ def get_tvm_output(model, x, target, ctx, out_shape, dtype='float32'):
     return out.asnumpy()
 
 
-def verify_onnx_forward_impl(graph_file, data_shape, out_shape):
+def get_caffe2_output(model, x, dtype='float32'):
     import caffe2.python.onnx.backend
-    def get_caffe2_output(model, x, dtype='float32'):
-        prepared_backend = caffe2.python.onnx.backend.prepare(model)
-        W = {model.graph.input[0].name: x.astype(dtype)}
-        c2_out = prepared_backend.run(W)[0]
-        return c2_out
+    prepared_backend = caffe2.python.onnx.backend.prepare(model)
+    W = {model.graph.input[0].name: x.astype(dtype)}
+    c2_out = prepared_backend.run(W)[0]
+    return c2_out
+
 
+def verify_onnx_forward_impl(graph_file, data_shape, out_shape):
     dtype = 'float32'
     x = np.random.uniform(size=data_shape)
     model = onnx.load(graph_file)
@@ -144,6 +145,101 @@ def test_unsqueeze():
 
     np.testing.assert_allclose(out_shape, tvm_out.shape)
 
+def _test_slice_iteration(indata, outdata, starts, ends, axes=None):
+    if axes:
+        y = helper.make_node("Slice", ['in'], ['out'], axes=axes, starts=starts, ends=ends)
+    else:
+        y = helper.make_node("Slice", ['in'], ['out'], starts=starts, ends=ends)
+
+    graph = helper.make_graph([y],
+                              'slice_test',
+                              inputs = [helper.make_tensor_value_info("in", TensorProto.FLOAT, list(indata.shape))],
+                              outputs = [helper.make_tensor_value_info("out", TensorProto.FLOAT, list(outdata.shape))])
+
+    model = helper.make_model(graph, producer_name='slice_test')
+
+    for target, ctx in ctx_list():
+        tvm_out = get_tvm_output(model, indata, target, ctx, outdata.shape, 'float32')
+
+    np.testing.assert_allclose(outdata, tvm_out)
+
+def test_slice():
+    x = np.random.randn(20, 10, 5).astype(np.float32)
+    _test_slice_iteration(x, x[0:3, 0:10], (0, 0), (3, 10), (0, 1))
+    _test_slice_iteration(x, x[:, :, 3:4], (0, 0, 3), (20, 10, 4))
+    _test_slice_iteration(x, x[:, 1:1000], (1), (1000), (1))
+    _test_slice_iteration(x, x[:, 0:-1], (0), (-1), (1))
+
+def _test_onnx_op_elementwise(inshape, outfunc, npargs, dtype, opname, kwargs):
+    indata = np.random.uniform(size=(2, 4, 5, 6)).astype(dtype)
+    outdata = outfunc(indata, **npargs)
+
+    y = helper.make_node(opname, ['in'], ['out'], **kwargs)
+
+    graph = helper.make_graph([y],
+                              opname+'_test',
+                              inputs = [helper.make_tensor_value_info("in", TensorProto.FLOAT, list(indata.shape))],
+                              outputs = [helper.make_tensor_value_info("out", TensorProto.FLOAT, list(outdata.shape))])
+
+    model = helper.make_model(graph, producer_name=opname+'_test')
+
+    for target, ctx in ctx_list():
+        tvm_out = get_tvm_output(model, indata, target, ctx, outdata.shape, dtype)
+
+    np.testing.assert_allclose(outdata, tvm_out)
+
+def test_floor():
+    _test_onnx_op_elementwise((2, 4, 5, 6), np.floor, {}, 'float32', 'Floor', {})
+
+def test_ceil():
+    _test_onnx_op_elementwise((2, 4, 5, 6), np.ceil, {}, 'float32', 'Ceil', {})
+
+def test_clip():
+    _test_onnx_op_elementwise((2, 4, 5, 6),
+                              np.clip,
+                              {'a_min': -1.0, 'a_max': 1.0},
+                              'float32',
+                              'Clip',
+                              {'min': -1.0, 'max': 1.0})
+
+def test_matmul():
+    a_shape = (4, 3)
+    b_shape = (3, 4)
+    out_shape = (4, 4)
+
+    a_array = np.random.uniform(size=a_shape).astype('float32')
+    b_array = np.random.uniform(size=b_shape).astype('float32')
+
+    mul_node = helper.make_node("MatMul", ["a", "b"], ["out"])
+
+    graph = helper.make_graph([mul_node],
+                              "matmul_test",
+                              inputs = [helper.make_tensor_value_info("a", TensorProto.FLOAT, list(a_shape)),
+                                        helper.make_tensor_value_info("b", TensorProto.FLOAT, list(b_shape))],
+                              outputs = [helper.make_tensor_value_info("out", TensorProto.FLOAT, list(out_shape))])
+
+    model = helper.make_model(graph, producer_name='matmul_test')
+
+    for target, ctx in ctx_list():
+        new_sym, params = nnvm.frontend.from_onnx(model)
+
+        input_name = model.graph.input[0].name
+        input_name1 = model.graph.input[1].name
+        shape_dict = {input_name: a_array.shape, input_name1: b_array.shape}
+        dtype_dict = {input_name: 'float32', input_name1: 'float32'}
+
+        graph, lib, params = nnvm.compiler.build(new_sym, target, shape_dict, dtype_dict, params=params)
+        m = graph_runtime.create(graph, lib, ctx)
+        # set inputs
+        m.set_input(input_name, tvm.nd.array(a_array.astype('float32')))
+        m.set_input(input_name1, tvm.nd.array(b_array.astype('float32')))
+        m.set_input(**params)
+        m.run()
+        # get outputs
+        tvm_out = m.get_output(0, tvm.nd.empty(out_shape, 'float32'))
+
+        np.testing.assert_allclose(np.matmul(a_array, b_array), tvm_out.asnumpy(), rtol=1e-5, atol=1e-5)
+
 if __name__ == '__main__':
     # verify_super_resolution_example()
     # verify_squeezenet1_1()
@@ -153,3 +249,8 @@ def test_unsqueeze():
     test_reshape_like()
     test_squeeze()
     test_unsqueeze()
+    test_slice()
+    test_floor()
+    test_ceil()
+    test_clip()
+    test_matmul()

From 106bbb8d2f119ce7673b25afc046edd8262dcad1 Mon Sep 17 00:00:00 2001
From: Albin Joy <albin.joy@huawei.com>
Date: Thu, 5 Jul 2018 09:15:30 +0530
Subject: [PATCH 793/949] [NNVM][TENSORFLOW] Fixed variable ops shape parsing
 issue (#1381)

---
 nnvm/python/nnvm/frontend/tensorflow.py       | 15 +++++--
 .../frontend/tensorflow/test_forward.py       | 41 +++++++++++++++++++
 2 files changed, 52 insertions(+), 4 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/tensorflow.py b/nnvm/python/nnvm/frontend/tensorflow.py
index fc7aeeda7101..aa00c51836fb 100644
--- a/nnvm/python/nnvm/frontend/tensorflow.py
+++ b/nnvm/python/nnvm/frontend/tensorflow.py
@@ -593,11 +593,18 @@ def from_tensorflow(self, graph):
                         raise NotImplementedError( \
                             "Const {} couldn't be converted to Param.".format(node.name))
 
-                try:
+                attr = self._parse_attr(node.attr)
+                #Variable converted to Const will not have only value attr
+                if 'value' in attr:
+                    tensor_value = attr['value']
                     self._output_shapes[node.name] = \
-                         [tensor_util.TensorShapeProtoToList(shape) \
-                         for shape in self._parse_attr(node.attr)['_output_shapes']]
-                except KeyError:
+                        [tensor_util.TensorShapeProtoToList( \
+                            tensor_value.tensor_shape)]
+                elif '_output_shapes' in attr:
+                    self._output_shapes[node.name] = \
+                        [tensor_util.TensorShapeProtoToList(shape) \
+                        for shape in self._parse_attr(node.attr)['_output_shapes']]
+                else:
                     raise NotImplementedError( \
                         "Please freeze the graph with add_shapes=True")
             else:
diff --git a/nnvm/tests/python/frontend/tensorflow/test_forward.py b/nnvm/tests/python/frontend/tensorflow/test_forward.py
index 1ec8222e95f1..5a37918f1189 100644
--- a/nnvm/tests/python/frontend/tensorflow/test_forward.py
+++ b/nnvm/tests/python/frontend/tensorflow/test_forward.py
@@ -14,6 +14,8 @@
 from tensorflow.python.ops import array_ops
 from tensorflow.python.ops import gen_array_ops
 from tensorflow.python.ops import math_ops
+from tensorflow.python.ops import variable_scope
+from tensorflow.python.ops import variables
 from tensorflow.core.framework import graph_pb2
 
 import nnvm.testing.tf
@@ -393,6 +395,44 @@ def test_forward_sigmoid():
 
     _test_sigmoid(np.random.uniform(size=(3, 4, 4, 3)).astype('float32'))
 
+
+#######################################################################
+# Variable
+# --------
+
+def _test_variable(data):
+    tf.reset_default_graph()
+    input_op = array_ops.placeholder(shape=data.shape, dtype=data.dtype)
+    input_tensor = array_ops.reshape(input_op, data.shape)
+
+    size = input_tensor.shape.dims[1]
+    with variable_scope.variable_scope("linear", reuse=None):
+        w = variable_scope.get_variable(
+            "w", shape=[size, size], dtype=input_tensor.dtype)
+    # pylint: disable=unused-variable
+    output_op = math_ops.matmul(input_tensor, w)
+    # pylint: enable=unused-variable
+
+    with tf.Session() as sess:
+        sess.run(variables.global_variables_initializer())
+        final_graph_def = tf.graph_util.convert_variables_to_constants(
+            sess,
+            sess.graph.as_graph_def(add_shapes=True),
+            ['MatMul'],
+            )
+
+        tf_output = run_tf_graph(sess, data, 'Placeholder:0', 'MatMul:0')
+        tvm_output = run_tvm_graph(final_graph_def, data,
+                                   "Placeholder", tf_output.shape, data.dtype)
+
+        np.testing.assert_allclose(tf_output, tvm_output, atol=1e-5, rtol=1e-5)
+        sess.close()
+
+def test_forward_variable():
+    """Variable type op test"""
+    _test_variable(np.random.uniform(size=(32, 100)).astype('float32'))
+
+
 #######################################################################
 # Multi Input to graph
 # --------------------
@@ -503,3 +543,4 @@ def test_forward_mobilenet():
     test_forward_inception_v3()
     test_forward_inception_v1()
     test_forward_mobilenet()
+    test_forward_variable()

From 941f684614bb643258986fff256f1310587471b3 Mon Sep 17 00:00:00 2001
From: nhynes <nhynes@berkeley.edu>
Date: Thu, 5 Jul 2018 20:05:16 -0700
Subject: [PATCH 794/949] Add normal distribution to random engines (#1352)

---
 python/tvm/contrib/random.py            |  25 +++
 src/contrib/random/mt_random_engine.cc  |  27 ++-
 src/contrib/random/random.cc            |  11 ++
 src/contrib/random/sgx_random_engine.cc | 215 ++++++++++++++++++++++++
 tests/python/contrib/test_random.py     |  24 +++
 5 files changed, 301 insertions(+), 1 deletion(-)

diff --git a/python/tvm/contrib/random.py b/python/tvm/contrib/random.py
index 04b018b0646f..6c32dd2d293b 100644
--- a/python/tvm/contrib/random.py
+++ b/python/tvm/contrib/random.py
@@ -55,4 +55,29 @@ def uniform(low, high, size):
     return _api.extern(size, [], lambda ins, outs: _intrin.call_packed(
         "tvm.contrib.random.uniform", float(low), float(high), outs[0]), dtype='float32')
 
+
+def normal(loc, scale, size):
+    """Draw samples from a normal distribution.
+
+    Return random samples from a normal distribution.
+
+    Parameters
+    ----------
+    loc : float
+        loc of the distribution.
+    scale : float
+        Standard deviation of the distribution.
+    size : tuple of ints
+        Output shape. If the given shape is, e.g., (m, n, k), then m * n * k
+        samples are drawn.
+
+    Returns
+    ------
+    out : Tensor
+        A tensor with specified size and dtype
+    """
+    return _api.extern(size, [], lambda ins, outs: _intrin.call_packed(
+        "tvm.contrib.random.normal", float(loc), float(scale), outs[0]), dtype='float32')
+
+
 _init_api("tvm.contrib.random")
diff --git a/src/contrib/random/mt_random_engine.cc b/src/contrib/random/mt_random_engine.cc
index b7f8db4b8596..b4fcbde02dae 100644
--- a/src/contrib/random/mt_random_engine.cc
+++ b/src/contrib/random/mt_random_engine.cc
@@ -73,7 +73,32 @@ class RandomEngine {
         return uniform_dist(rnd_engine_);
       });
     } else {
-      LOG(FATAL) << "Do not support random.randint on this device yet";
+      LOG(FATAL) << "Do not support random.uniform on this device yet";
+    }
+  }
+
+   /*!
+    * \brief Fills a tensor with values drawn from Normal(loc, scale**2)
+    */
+  void SampleNormal(DLTensor* data, float loc, float scale) {
+    CHECK_GT(scale, 0) << "standard deviation must be positive";
+    CHECK(data->strides == nullptr);
+
+    DLDataType dtype = data->dtype;
+    int64_t size = 1;
+    for (int i = 0; i < data->ndim; ++i) {
+      size *= data->shape[i];
+    }
+
+    CHECK(dtype.code == kDLFloat && dtype.bits == 32 && dtype.lanes == 1);
+
+    if (data->ctx.device_type == kDLCPU) {
+      std::normal_distribution<float> normal_dist(loc, scale);
+      std::generate_n(static_cast<float*>(data->data), size, [&] () {
+        return normal_dist(rnd_engine_);
+      });
+    } else {
+      LOG(FATAL) << "Do not support random.normal on this device yet";
     }
   }
 
diff --git a/src/contrib/random/random.cc b/src/contrib/random/random.cc
index de60da906adc..27e2b065a01b 100644
--- a/src/contrib/random/random.cc
+++ b/src/contrib/random/random.cc
@@ -87,6 +87,7 @@ TVM_REGISTER_GLOBAL("tvm.contrib.random.randint")
     })
   });
 
+
 TVM_REGISTER_GLOBAL("tvm.contrib.random.uniform")
 .set_body([](TVMArgs args, TVMRetValue *ret) {
     RandomThreadLocalEntry *entry = RandomThreadLocalEntry::ThreadLocal();
@@ -97,5 +98,15 @@ TVM_REGISTER_GLOBAL("tvm.contrib.random.uniform")
   });
 
 
+TVM_REGISTER_GLOBAL("tvm.contrib.random.normal")
+.set_body([](TVMArgs args, TVMRetValue *ret) {
+    RandomThreadLocalEntry *entry = RandomThreadLocalEntry::ThreadLocal();
+    double loc = args[0];
+    double scale = args[1];
+    DLTensor* out = args[2];
+    entry->random_engine.SampleNormal(out, loc, scale);
+  });
+
+
 }  // namespace contrib
 }  // namespace tvm
diff --git a/src/contrib/random/sgx_random_engine.cc b/src/contrib/random/sgx_random_engine.cc
index 2bdcf529ba1e..4b40cb282bcc 100644
--- a/src/contrib/random/sgx_random_engine.cc
+++ b/src/contrib/random/sgx_random_engine.cc
@@ -6,6 +6,7 @@
 #include <dmlc/logging.h>
 #include <sgx_trts.h>
 #include <algorithm>
+#include <cmath>
 #include "../../runtime/sgx/common.h"
 
 namespace tvm {
@@ -48,6 +49,24 @@ class RandomEngine {
     return rand_int;
   }
 
+   /*!
+    * \return a random integer sampled from Unif(low, high).
+    */
+  inline float GetUniform(float low, float high) {
+    float max_int = static_cast<float>(std::numeric_limits<unsigned>::max());
+    float unif01 = GetRandInt() / max_int;
+    return low + unif01 * (high - low);
+  }
+
+   /*!
+    * \return a random value sampled from Normal(loc, scale**2).
+    */
+  inline float GetNormal(float loc, float scale) {
+    float sign = GetUniform(-1, 1);
+    float sample = GetStandardNormalOneside();
+    return loc + (sign > 0 ? scale : -scale) * sample;
+  }
+
    /*!
     * \brief Fills a tensor with values drawn from Unif(low, high)
     */
@@ -69,7 +88,203 @@ class RandomEngine {
       return low + unif01 * (high - low);
     });
   }
+
+   /*!
+    * \brief Fills a tensor with values drawn from Normal(loc, scale)
+    */
+  void SampleNormal(DLTensor* data, float loc, float scale) {
+    CHECK_GT(scale, 0) << "scale must be positive";
+    CHECK(data->strides == nullptr);
+
+    DLDataType dtype = data->dtype;
+    int64_t size = 1;
+    for (int i = 0; i < data->ndim; ++i) {
+      size *= data->shape[i];
+    }
+
+    CHECK(dtype.code == kDLFloat && dtype.bits == 32 && dtype.lanes == 1);
+
+    std::generate_n(static_cast<float*>(data->data), size, [&] () {
+      return GetNormal(loc, scale);
+    });
+  }
+
+ private:
+   /*!
+    * \return a random value sampled from Normal(0, 1) such that the
+    * sampled value is greater than tail
+    */
+  inline float GetStandardNormalTail(float tail) {
+    while (true) {
+      float u1 = GetUniform(0, 1);
+      float u2 = GetUniform(0, 1);
+      float x = - log(u1) / tail;
+      float y = - log(u2);
+      if (2 * y < x * x) {
+        return x + tail;
+      }
+    }
+  }
+
+   /*!
+    * \return a random positive value sampled from Normal(0, 1).
+    */
+  inline float GetStandardNormalOneside() {
+    while (true) {
+      unsigned i = GetRandInt() & 255;
+      float x = GetUniform(0, ZIG_NORM_X[i]);
+      if (x < ZIG_NORM_X[i+1]) {
+        return x;
+      }
+      if (i == 0) {
+        return GetStandardNormalTail(ZIG_NORM_X[1]);
+      }
+      float y = GetUniform(ZIG_NORM_F[i], ZIG_NORM_F[i+1]);
+      if (y < exp(-0.5 * x * x)) {
+        return x;
+      }
+    }
+  }
+
+   /*!
+    * Tables for normal distribution which is sampled using the ziggurat algorithm.
+    */
+  static constexpr float ZIG_NORM_X[257] =
+    {3.910757959537090045, 3.654152885361008796, 3.449278298560964462, 3.320244733839166074,
+     3.224575052047029100, 3.147889289517149969, 3.083526132001233044, 3.027837791768635434,
+     2.978603279880844834, 2.934366867207854224, 2.894121053612348060, 2.857138730872132548,
+     2.822877396825325125, 2.790921174000785765, 2.760944005278822555, 2.732685359042827056,
+     2.705933656121858100, 2.680514643284522158, 2.656283037575502437, 2.633116393630324570,
+     2.610910518487548515, 2.589575986706995181, 2.569035452680536569, 2.549221550323460761,
+     2.530075232158516929, 2.511544441625342294, 2.493583041269680667, 2.476149939669143318,
+     2.459208374333311298, 2.442725318198956774, 2.426670984935725972, 2.411018413899685520,
+     2.395743119780480601, 2.380822795170626005, 2.366237056715818632, 2.351967227377659952,
+     2.337996148795031370, 2.324308018869623016, 2.310888250599850036, 2.297723348901329565,
+     2.284800802722946056, 2.272108990226823888, 2.259637095172217780, 2.247375032945807760,
+     2.235313384928327984, 2.223443340090905718, 2.211756642882544366, 2.200245546609647995,
+     2.188902771624720689, 2.177721467738641614, 2.166695180352645966, 2.155817819875063268,
+     2.145083634046203613, 2.134487182844320152, 2.124023315687815661, 2.113687150684933957,
+     2.103474055713146829, 2.093379631137050279, 2.083399693996551783, 2.073530263516978778,
+     2.063767547809956415, 2.054107931648864849, 2.044547965215732788, 2.035084353727808715,
+     2.025713947862032960, 2.016433734904371722, 2.007240830558684852, 1.998132471356564244,
+     1.989106007615571325, 1.980158896898598364, 1.971288697931769640, 1.962493064942461896,
+     1.953769742382734043, 1.945116560006753925, 1.936531428273758904, 1.928012334050718257,
+     1.919557336591228847, 1.911164563769282232, 1.902832208548446369, 1.894558525668710081,
+     1.886341828534776388, 1.878180486290977669, 1.870072921069236838, 1.862017605397632281,
+     1.854013059758148119, 1.846057850283119750, 1.838150586580728607, 1.830289919680666566,
+     1.822474540091783224, 1.814703175964167636, 1.806974591348693426, 1.799287584547580199,
+     1.791640986550010028, 1.784033659547276329, 1.776464495522344977, 1.768932414909077933,
+     1.761436365316706665, 1.753975320315455111, 1.746548278279492994, 1.739154261283669012,
+     1.731792314050707216, 1.724461502945775715, 1.717160915015540690, 1.709889657069006086,
+     1.702646854797613907, 1.695431651932238548, 1.688243209434858727, 1.681080704722823338,
+     1.673943330923760353, 1.666830296159286684, 1.659740822855789499, 1.652674147080648526,
+     1.645629517902360339, 1.638606196773111146, 1.631603456932422036, 1.624620582830568427,
+     1.617656869570534228, 1.610711622367333673, 1.603784156023583041, 1.596873794420261339,
+     1.589979870021648534, 1.583101723393471438, 1.576238702733332886, 1.569390163412534456,
+     1.562555467528439657, 1.555733983466554893, 1.548925085471535512, 1.542128153226347553,
+     1.535342571438843118, 1.528567729435024614, 1.521803020758293101, 1.515047842773992404,
+     1.508301596278571965, 1.501563685112706548, 1.494833515777718391, 1.488110497054654369,
+     1.481394039625375747, 1.474683555695025516, 1.467978458615230908, 1.461278162507407830,
+     1.454582081885523293, 1.447889631277669675, 1.441200224845798017, 1.434513276002946425,
+     1.427828197027290358, 1.421144398672323117, 1.414461289772464658, 1.407778276843371534,
+     1.401094763676202559, 1.394410150925071257, 1.387723835686884621, 1.381035211072741964,
+     1.374343665770030531, 1.367648583594317957, 1.360949343030101844, 1.354245316759430606,
+     1.347535871177359290, 1.340820365893152122, 1.334098153216083604, 1.327368577624624679,
+     1.320630975217730096, 1.313884673146868964, 1.307128989027353860, 1.300363230327433728,
+     1.293586693733517645, 1.286798664489786415, 1.279998415710333237, 1.273185207661843732,
+     1.266358287014688333, 1.259516886060144225, 1.252660221891297887, 1.245787495544997903,
+     1.238897891102027415, 1.231990574742445110, 1.225064693752808020, 1.218119375481726552,
+     1.211153726239911244, 1.204166830140560140, 1.197157747875585931, 1.190125515422801650,
+     1.183069142678760732, 1.175987612011489825, 1.168879876726833800, 1.161744859441574240,
+     1.154581450355851802, 1.147388505416733873, 1.140164844363995789, 1.132909248648336975,
+     1.125620459211294389, 1.118297174115062909, 1.110938046009249502, 1.103541679420268151,
+     1.096106627847603487, 1.088631390649514197, 1.081114409698889389, 1.073554065787871714,
+     1.065948674757506653, 1.058296483326006454, 1.050595664586207123, 1.042844313139370538,
+     1.035040439828605274, 1.027181966030751292, 1.019266717460529215, 1.011292417434978441,
+     1.003256679539591412, 0.995156999629943084, 0.986990747093846266, 0.978755155288937750,
+     0.970447311058864615, 0.962064143217605250, 0.953602409875572654, 0.945058684462571130,
+     0.936429340280896860, 0.927710533396234771, 0.918898183643734989, 0.909987953490768997,
+     0.900975224455174528, 0.891855070726792376, 0.882622229578910122, 0.873271068082494550,
+     0.863795545546826915, 0.854189171001560554, 0.844444954902423661, 0.834555354079518752,
+     0.824512208745288633, 0.814306670128064347, 0.803929116982664893, 0.793369058833152785,
+     0.782615023299588763, 0.771654424216739354, 0.760473406422083165, 0.749056662009581653,
+     0.737387211425838629, 0.725446140901303549, 0.713212285182022732, 0.700661841097584448,
+     0.687767892786257717, 0.674499822827436479, 0.660822574234205984, 0.646695714884388928,
+     0.632072236375024632, 0.616896989996235545, 0.601104617743940417, 0.584616766093722262,
+     0.567338257040473026, 0.549151702313026790, 0.529909720646495108, 0.509423329585933393,
+     0.487443966121754335, 0.463634336771763245, 0.437518402186662658, 0.408389134588000746,
+     0.375121332850465727, 0.335737519180459465, 0.286174591747260509, 0.215241895913273806,
+     0.000000000000000000};
+  static constexpr float ZIG_NORM_F[257] =
+    {0.000477467764586655, 0.001260285930498598, 0.002609072746106363, 0.004037972593371872,
+     0.005522403299264754, 0.007050875471392110, 0.008616582769422917, 0.010214971439731100,
+     0.011842757857943104, 0.013497450601780807, 0.015177088307982072, 0.016880083152595839,
+     0.018605121275783350, 0.020351096230109354, 0.022117062707379922, 0.023902203305873237,
+     0.025705804008632656, 0.027527235669693315, 0.029365939758230111, 0.031221417192023690,
+     0.033093219458688698, 0.034980941461833073, 0.036884215688691151, 0.038802707404656918,
+     0.040736110656078753, 0.042684144916619378, 0.044646552251446536, 0.046623094902089664,
+     0.048613553216035145, 0.050617723861121788, 0.052635418276973649, 0.054666461325077916,
+     0.056710690106399467, 0.058767952921137984, 0.060838108349751806, 0.062921024437977854,
+     0.065016577971470438, 0.067124653828023989, 0.069245144397250269, 0.071377949059141965,
+     0.073522973714240991, 0.075680130359194964, 0.077849336702372207, 0.080030515814947509,
+     0.082223595813495684, 0.084428509570654661, 0.086645194450867782, 0.088873592068594229,
+     0.091113648066700734, 0.093365311913026619, 0.095628536713353335, 0.097903279039215627,
+     0.100189498769172020, 0.102487158942306270, 0.104796225622867056, 0.107116667775072880,
+     0.109448457147210021, 0.111791568164245583, 0.114145977828255210, 0.116511665626037014,
+     0.118888613443345698, 0.121276805485235437, 0.123676228202051403, 0.126086870220650349,
+     0.128508722280473636, 0.130941777174128166, 0.133386029692162844, 0.135841476571757352,
+     0.138308116449064322, 0.140785949814968309, 0.143274978974047118, 0.145775208006537926,
+     0.148286642733128721, 0.150809290682410169, 0.153343161060837674, 0.155888264725064563,
+     0.158444614156520225, 0.161012223438117663, 0.163591108232982951, 0.166181285765110071,
+     0.168782774801850333, 0.171395595638155623, 0.174019770082499359, 0.176655321444406654,
+     0.179302274523530397, 0.181960655600216487, 0.184630492427504539, 0.187311814224516926,
+     0.190004651671193070, 0.192709036904328807, 0.195425003514885592, 0.198152586546538112,
+     0.200891822495431333, 0.203642749311121501, 0.206405406398679298, 0.209179834621935651,
+     0.211966076307852941, 0.214764175252008499, 0.217574176725178370, 0.220396127481011589,
+     0.223230075764789593, 0.226076071323264877, 0.228934165415577484, 0.231804410825248525,
+     0.234686861873252689, 0.237581574432173676, 0.240488605941449107, 0.243408015423711988,
+     0.246339863502238771, 0.249284212419516704, 0.252241126056943765, 0.255210669955677150,
+     0.258192911338648023, 0.261187919133763713, 0.264195763998317568, 0.267216518344631837,
+     0.270250256366959984, 0.273297054069675804, 0.276356989296781264, 0.279430141762765316,
+     0.282516593084849388, 0.285616426816658109, 0.288729728483353931, 0.291856585618280984,
+     0.294997087801162572, 0.298151326697901342, 0.301319396102034120, 0.304501391977896274,
+     0.307697412505553769, 0.310907558127563710, 0.314131931597630143, 0.317370638031222396,
+     0.320623784958230129, 0.323891482377732021, 0.327173842814958593, 0.330470981380537099,
+     0.333783015832108509, 0.337110066638412809, 0.340452257045945450, 0.343809713148291340,
+     0.347182563958251478, 0.350570941482881204, 0.353974980801569250, 0.357394820147290515,
+     0.360830600991175754, 0.364282468130549597, 0.367750569780596226, 0.371235057669821344,
+     0.374736087139491414, 0.378253817247238111, 0.381788410875031348, 0.385340034841733958,
+     0.388908860020464597, 0.392495061461010764, 0.396098818517547080, 0.399720314981931668,
+     0.403359739222868885, 0.407017284331247953, 0.410693148271983222, 0.414387534042706784,
+     0.418100649839684591, 0.421832709231353298, 0.425583931339900579, 0.429354541031341519,
+     0.433144769114574058, 0.436954852549929273, 0.440785034667769915, 0.444635565397727750,
+     0.448506701509214067, 0.452398706863882505, 0.456311852680773566, 0.460246417814923481,
+     0.464202689050278838, 0.468180961407822172, 0.472181538469883255, 0.476204732721683788,
+     0.480250865911249714, 0.484320269428911598, 0.488413284707712059, 0.492530263646148658,
+     0.496671569054796314, 0.500837575128482149, 0.505028667945828791, 0.509245245998136142,
+     0.513487720749743026, 0.517756517232200619, 0.522052074674794864, 0.526374847174186700,
+     0.530725304406193921, 0.535103932383019565, 0.539511234259544614, 0.543947731192649941,
+     0.548413963257921133, 0.552910490428519918, 0.557437893621486324, 0.561996775817277916,
+     0.566587763258951771, 0.571211506738074970, 0.575868682975210544, 0.580559996103683473,
+     0.585286179266300333, 0.590047996335791969, 0.594846243770991268, 0.599681752622167719,
+     0.604555390700549533, 0.609468064928895381, 0.614420723892076803, 0.619414360609039205,
+     0.624450015550274240, 0.629528779928128279, 0.634651799290960050, 0.639820277456438991,
+     0.645035480824251883, 0.650298743114294586, 0.655611470583224665, 0.660975147780241357,
+     0.666391343912380640, 0.671861719900766374, 0.677388036222513090, 0.682972161648791376,
+     0.688616083008527058, 0.694321916130032579, 0.700091918140490099, 0.705928501336797409,
+     0.711834248882358467, 0.717811932634901395, 0.723864533472881599, 0.729995264565802437,
+     0.736207598131266683, 0.742505296344636245, 0.748892447223726720, 0.755373506511754500,
+     0.761953346841546475, 0.768637315803334831, 0.775431304986138326, 0.782341832659861902,
+     0.789376143571198563, 0.796542330428254619, 0.803849483176389490, 0.811307874318219935,
+     0.818929191609414797, 0.826726833952094231, 0.834716292992930375, 0.842915653118441077,
+     0.851346258465123684, 0.860033621203008636, 0.869008688043793165, 0.878309655816146839,
+     0.887984660763399880, 0.898095921906304051, 0.908726440060562912, 0.919991505048360247,
+     0.932060075968990209, 0.945198953453078028, 0.959879091812415930, 0.977101701282731328,
+     1.000000000000000000};
 };
 
+constexpr float RandomEngine::ZIG_NORM_X[];
+constexpr float RandomEngine::ZIG_NORM_F[];
+
 }  // namespace contrib
 }  // namespace tvm
diff --git a/tests/python/contrib/test_random.py b/tests/python/contrib/test_random.py
index 46f2bbbfb805..a74273a0ccba 100644
--- a/tests/python/contrib/test_random.py
+++ b/tests/python/contrib/test_random.py
@@ -50,6 +50,30 @@ def verify(target="llvm"):
     verify()
 
 
+def test_normal():
+    m = 1024
+    n = 1024
+    A = random.normal(3, 4, size=(m, n))
+    s = tvm.create_schedule(A.op)
+
+    def verify(target="llvm"):
+        if not tvm.module.enabled(target):
+            print("skip because %s is not enabled..." % target)
+            return
+        if not tvm.get_global_func("tvm.contrib.random.normal", True):
+            print("skip because extern function is not avalable")
+            return
+        ctx = tvm.cpu(0)
+        f = tvm.build(s, [A], target)
+        a = tvm.nd.array(np.zeros((m, n), dtype=A.dtype), ctx)
+        f(a)
+        na = a.asnumpy()
+        assert abs(np.mean(na) - 3) < 1e-2
+        assert abs(np.std(na) - 4) < 1e-2
+    verify()
+
+
 if __name__ == "__main__":
     test_randint()
     test_uniform()
+    test_normal()

From cca57ae6d5815699206da6dfe44b31110edf5ceb Mon Sep 17 00:00:00 2001
From: Tatsuya Nishiyama <nishiyama.tatsuya0@gmail.com>
Date: Sat, 7 Jul 2018 01:12:33 +0900
Subject: [PATCH 795/949] Fix tutorial to follow the conv2d change (#1390)

---
 tutorials/topi/intro_topi.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tutorials/topi/intro_topi.py b/tutorials/topi/intro_topi.py
index fb5fe7e65284..cf21aa52261c 100644
--- a/tutorials/topi/intro_topi.py
+++ b/tutorials/topi/intro_topi.py
@@ -107,7 +107,7 @@
 #
 data = tvm.placeholder((1, 3, 224, 224))
 kernel = tvm.placeholder((10, 3, 5, 5))
-conv = topi.nn.conv2d(data, kernel, stride=1, padding=2)
+conv = topi.nn.conv2d(data, kernel, strides=1, padding=2)
 out = topi.nn.relu(conv)
 with tvm.target.create("cuda"):
     sconv = topi.generic.nn.schedule_conv2d_nchw(out)

From 7dca7e4751d2596c299c964e9f257bb3facac643 Mon Sep 17 00:00:00 2001
From: nhynes <nhynes@berkeley.edu>
Date: Fri, 6 Jul 2018 10:17:52 -0700
Subject: [PATCH 796/949] Move SGX enclave signing key to gist (#1393)

---
 apps/sgx/Makefile                     |   6 +-
 apps/sgx/prepare_test_libs.py         |  57 +----------
 nnvm/examples/sgx/.gitignore          |   2 -
 nnvm/examples/sgx/Makefile            | 102 -------------------
 nnvm/examples/sgx/README.md           |  39 --------
 nnvm/examples/sgx/app.cc              | 135 --------------------------
 nnvm/examples/sgx/build_model.py      |  74 --------------
 nnvm/examples/sgx/enclave.cc          |  98 -------------------
 nnvm/examples/sgx/enclave_config.xml  |  11 ---
 nnvm/examples/sgx/enclave_private.pem |  39 --------
 nnvm/examples/sgx/model.edl           |   7 --
 nnvm/examples/sgx/run_example.sh      |   7 --
 12 files changed, 9 insertions(+), 568 deletions(-)
 delete mode 100644 nnvm/examples/sgx/.gitignore
 delete mode 100644 nnvm/examples/sgx/Makefile
 delete mode 100644 nnvm/examples/sgx/README.md
 delete mode 100644 nnvm/examples/sgx/app.cc
 delete mode 100644 nnvm/examples/sgx/build_model.py
 delete mode 100644 nnvm/examples/sgx/enclave.cc
 delete mode 100644 nnvm/examples/sgx/enclave_config.xml
 delete mode 100644 nnvm/examples/sgx/enclave_private.pem
 delete mode 100644 nnvm/examples/sgx/model.edl
 delete mode 100755 nnvm/examples/sgx/run_example.sh

diff --git a/apps/sgx/Makefile b/apps/sgx/Makefile
index 1b4dce7c6c7d..cd7034d4c41b 100644
--- a/apps/sgx/Makefile
+++ b/apps/sgx/Makefile
@@ -76,8 +76,12 @@ lib/tvm_t.o: lib/tvm_t.c
 lib/test_addone.so: $(TVM_ROOT)/src/runtime/sgx/trusted/runtime.cc lib/tvm_t.o lib/test_addone_sys.o
 	$(CXX) $^ -o $@ $(pkg_cflags) $(pkg_ldflags) $(enclave_cxxflags) $(enclave_ldflags) -g
 
+# The demo enclave signing key
+lib/enclave.pem:
+	curl -Lso $@ https://gist.githubusercontent.com/nhynes/8a2d80068a92e672f8b0b7d710ceb404/raw/2d5ae5fbe83198ede49465fdc6535065e093543b/tvm_sgx_demo.pem
+
 # The signed enclave
-lib/test_addone.signed.so: lib/test_addone.so enclave_config.xml
+lib/test_addone.signed.so: lib/test_addone.so enclave_config.xml lib/enclave.pem
 	$(sgx_enclave_signer) sign -key lib/enclave.pem -enclave $< -out $@ -config enclave_config.xml
 
 clean:
diff --git a/apps/sgx/prepare_test_libs.py b/apps/sgx/prepare_test_libs.py
index db537cd537ae..f676f46b7ff0 100644
--- a/apps/sgx/prepare_test_libs.py
+++ b/apps/sgx/prepare_test_libs.py
@@ -1,53 +1,15 @@
 """Script to prepare test_addone_sys.o"""
 
-from __future__ import print_function
 from os import path as osp
 
 import tvm
 
 CWD = osp.dirname(osp.abspath(osp.expanduser(__file__)))
 
-KEY_HEAD_FOOT = '-----{} RSA PRIVATE KEY-----'
-KEY = """ MIIG4gIBAAKCAYEAroOogvsj/fZDZY8XFdkl6dJmky0lRvnWMmpeH41Bla6U1qLZ
-AmZuyIF+mQC/cgojIsrBMzBxb1kKqzATF4+XwPwgKz7fmiddmHyYz2WDJfAjIveJ
-ZjdMjM4+EytGlkkJ52T8V8ds0/L2qKexJ+NBLxkeQLfV8n1mIk7zX7jguwbCG1Pr
-nEMdJ3Sew20vnje+RsngAzdPChoJpVsWi/K7cettX/tbnre1DL02GXc5qJoQYk7b
-3zkmhz31TgFrd9VVtmUGyFXAysuSAb3EN+5VnHGr0xKkeg8utErea2FNtNIgua8H
-ONfm9Eiyaav1SVKzPHlyqLtcdxH3I8Wg7yqMsaprZ1n5A1v/levxnL8+It02KseD
-5HqV4rf/cImSlCt3lpRg8U5E1pyFQ2IVEC/XTDMiI3c+AR+w2jSRB3Bwn9zJtFlW
-KHG3m1xGI4ck+Lci1JvWWLXQagQSPtZTsubxTQNx1gsgZhgv1JHVZMdbVlAbbRMC
-1nSuJNl7KPAS/VfzAgEDAoIBgHRXxaynbVP5gkO0ug6Qw/E27wzIw4SmjsxG6Wpe
-K7kfDeRskKxESdsA/xCrKkwGwhcx1iIgS5+Qscd1Yg+1D9X9asd/P7waPmWoZd+Z
-AhlKwhdPsO7PiF3e1AzHhGQwsUTt/Y/aSI1MpHBvy2/s1h9mFCslOUxTmWw0oj/Q
-ldIEgWeNR72CE2+jFIJIyml6ftnb6qzPiga8Bm48ubKh0kvySOqnkmnPzgh+JBD6
-JnBmtZbfPT97bwTT+N6rnPqOOApvfHPf15kWI8yDbprG1l4OCUaIUH1AszxLd826
-5IPM+8gINLRDP1MA6azECPjTyHXhtnSIBZCyWSVkc05vYmNXYUNiXWMajcxW9M02
-wKzFELO8NCEAkaTPxwo4SCyIjUxiK1LbQ9h8PSy4c1+gGP4LAMR8xqP4QKg6zdu9
-osUGG/xRe/uufgTBFkcjqBHtK5L5VI0jeNIUAgW/6iNbYXjBMJ0GfauLs+g1VsOm
-WfdgXzsb9DYdMa0OXXHypmV4GwKBwQDUwQj8RKJ6c8cT4vcWCoJvJF00+RFL+P3i
-Gx2DLERxRrDa8AVGfqaCjsR+3vLgG8V/py+z+dxZYSqeB80Qeo6PDITcRKoeAYh9
-xlT3LJOS+k1cJcEmlbbO2IjLkTmzSwa80fWexKu8/Xv6vv15gpqYl1ngYoqJM3pd
-vzmTIOi7MKSZ0WmEQavrZj8zK4endE3v0eAEeQ55j1GImbypSf7Idh7wOXtjZ7WD
-Dg6yWDrri+AP/L3gClMj8wsAxMV4ZR8CgcEA0fzDHkFa6raVOxWnObmRoDhAtE0a
-cjUj976NM5yyfdf2MrKy4/RhdTiPZ6b08/lBC/+xRfV3xKVGzacm6QjqjZrUpgHC
-0LKiZaMtccCJjLtPwQd0jGQEnKfMFaPsnhOc5y8qVkCzVOSthY5qhz0XNotHHFmJ
-gffVgB0iqrMTvSL7IA2yqqpOqNRlhaYhNl8TiFP3gIeMtVa9rZy31JPgT2uJ+kfo
-gV7sdTPEjPWZd7OshGxWpT6QfVDj/T9T7L6tAoHBAI3WBf2DFvxNL2KXT2QHAZ9t
-k3imC4f7U+wSE6zILaDZyzygA4RUbwG0gv8/TJVn2P/Eynf76DuWHGlaiLWnCbSz
-Az2DHBQBBaku409zDQym3j1ugMRjzzSQWzJg0SIyBH3hTmnYcn3+Uqcp/lEBvGW6
-O+rsXFt3pukqJmIV8HzLGGaLm62BHUeZf3dyWm+i3p/hQAL7Xvu04QW70xuGqdr5
-afV7p5eaeQIJXyGQJ0eylV/90+qxjMKiB1XYg6WYvwKBwQCL/ddpgOdHJGN8uRom
-e7Zq0Csi3hGheMKlKbN3vcxT5U7MdyHtTZZOJbTvxKNNUNYH/8uD+PqDGNneb29G
-BfGzvI3EASyLIcGZF3OhKwZd0jUrWk2y7Vhob91jwp2+t73vdMbkKyI4mHOuXvGv
-fg95si9oO7EBT+Oqvhccd2J+F1IVXncccYnF4u5ZGWt5lLewN/pVr7MjjykeaHqN
-t+rfnQam2psA6fL4zS2zTmZPzR2tnY8Y1GBTi0Ko1OKd1HMCgcAb5cB/7/AQlhP9
-yQa04PLH9ygQkKKptZp7dy5WcWRx0K/hAHRoi2aw1wZqfm7VBNu2SLcs90kCCCxp
-6C5sfJi6b8NpNbIPC+sc9wsFr7pGo9SFzQ78UlcWYK2Gu2FxlMjonhka5hvo4zvg
-WxlpXKEkaFt3gLd92m/dMqBrHfafH7VwOJY2zT3WIpjwuk0ZzmRg5p0pG/svVQEH
-NZmwRwlopysbR69B/n1nefJ84UO50fLh5s5Zr3gBRwbWNZyzhXk=
-""".strip()
 
-def prepare_test_libs(base_path):
+def main():
+    out_dir = osp.join(CWD, 'lib')
+
     n = tvm.var('n')
     A = tvm.placeholder((n,), name='A')
     B = tvm.compute(A.shape, lambda *i: A(*i) + 1, name='B')
@@ -57,18 +19,7 @@ def prepare_test_libs(base_path):
 
     # Compile library in system library mode
     fadd_syslib = tvm.build(s, [A, B], 'llvm --system-lib')
-    syslib_path = osp.join(base_path, 'test_addone_sys.o')
-    fadd_syslib.save(syslib_path)
-
-def main():
-    out_dir = osp.join(CWD, 'lib')
-
-    prepare_test_libs(out_dir)
-
-    with open(osp.join(out_dir, 'enclave.pem'), 'w') as f_key:
-        print(KEY_HEAD_FOOT.format('BEGIN'), file=f_key)
-        print(KEY, file=f_key)
-        print(KEY_HEAD_FOOT.format('END'), file=f_key)
+    fadd_syslib.save(osp.join(out_dir, 'test_addone_sys.o'))
 
 
 if __name__ == '__main__':
diff --git a/nnvm/examples/sgx/.gitignore b/nnvm/examples/sgx/.gitignore
deleted file mode 100644
index 25f8f1058cab..000000000000
--- a/nnvm/examples/sgx/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-lib/
-bin/
diff --git a/nnvm/examples/sgx/Makefile b/nnvm/examples/sgx/Makefile
deleted file mode 100644
index 490de0aa5ba2..000000000000
--- a/nnvm/examples/sgx/Makefile
+++ /dev/null
@@ -1,102 +0,0 @@
-# Makefile for example to deploy TVM modules in SGX.
-
-PYTHON ?= python
-
-NNVM_ROOT := $(shell cd ../../; pwd)
-TVM_ROOT := $(NNVM_ROOT)/tvm
-DMLC_CORE_ROOT := $(NNVM_ROOT)/dmlc-core
-
-SGX_SDK ?= /opt/sgxsdk
-SGX_MODE ?= SIM
-SGX_ARCH ?= x64
-SGX_DEBUG ?= 1
-
-sgx_edger8r := $(SGX_SDK)/bin/x64/sgx_edger8r
-sgx_enclave_signer := $(SGX_SDK)/bin/x64/sgx_sign
-
-ifneq ($(SGX_MODE), HW)
-	sgx_sim := _sim
-endif
-urts_library_name := sgx_urts$(sgx_sim)
-trts_library_name := sgx_trts$(sgx_sim)
-tservice_library_name := sgx_tservice$(sgx_sim)
-uservice_library_name := sgx_uae_service$(sgx_sim)
-
-pkg_cflags := -std=c++11 -O2 -fPIC\
-	-I$(NNVM_ROOT)/include\
-	-I$(NNVM_ROOT)\
-	-I$(TVM_ROOT)/include\
-	-I$(TVM_ROOT)/dlpack/include\
-	-I$(DMLC_CORE_ROOT)/include\
-	-DDMLC_LOG_STACK_TRACE=0\
-
-pkg_ldflags := -L$(TVM_ROOT)/lib
-
-enclave_include_paths := -I$(SGX_SDK)/include\
-	-I$(SGX_SDK)/include/tlibc\
-	-I$(SGX_SDK)/include/libcxx\
-	-I$(SGX_SDK)/include/stdc++\
-
-enclave_cflags := -static -nostdinc\
-	-fvisibility=hidden -fpie -fstack-protector-strong\
-	-ffunction-sections -fdata-sections\
-	-DDMLC_CXX11_THREAD_LOCAL=0\
-	$(enclave_include_paths)\
-
-enclave_cxxflags := -nostdinc++ $(enclave_cflags)
-
-enclave_ldflags :=\
-	-Wl,--no-undefined -nostdlib -nodefaultlibs -nostartfiles -L$(SGX_SDK)/lib64\
-	-Wl,--whole-archive -l$(trts_library_name) -Wl,--no-whole-archive\
-	-Wl,--start-group\
-	-lsgx_tstdc -lsgx_tstdcxx -lsgx_tcxx -lsgx_tcrypto -lsgx_tkey_exchange -l$(tservice_library_name)\
-	-Wl,--end-group\
-	-Wl,-Bstatic -Wl,-Bsymbolic -Wl,--no-undefined\
-	-Wl,-pie,-eenclave_entry -Wl,--export-dynamic\
-	-Wl,--defsym,__ImageBase=0 -Wl,--gc-sections
-
-app_cflags := -I$(SGX_SDK)/include -Ilib
-
-app_ldflags := -L$(SGX_SDK)/lib64\
-	-l$(urts_library_name) -l$(uservice_library_name) -lpthread\
-
-.PHONY: clean all
-
-all: lib/model.signed.so bin/run_model
-
-# The code library built by TVM
-lib/deploy_%.o: build_model.py
-	@mkdir -p $(@D)
-	$(PYTHON) build_model.py
-
-# EDL files
-lib/model_%.c: model.edl $(sgx_edger8r)
-	@mkdir -p $(@D)
-	$(sgx_edger8r) $< --trusted-dir $(@D) --untrusted-dir $(@D) --search-path $(SGX_SDK)/include
-
-lib/model_%.o: lib/model_%.c
-	$(CC) $(enclave_cflags) -c $< -o $@
-
-# The enclave library
-lib/model.so: enclave.cc $(TVM_ROOT)/sgx/sgx_runtime.cc lib/model_t.o lib/deploy_lib.o
-	$(CXX) $^ -o $@ $(pkg_cflags) $(pkg_ldflags) $(enclave_cxxflags) $(enclave_ldflags)\
-		-Wl,--format=binary -Wl,lib/deploy_graph.json -Wl,lib/deploy_params.bin -Wl,--format=default
-
-# The signed enclave
-lib/model.signed.so: lib/model.so enclave_config.xml
-	$(sgx_enclave_signer) sign -key enclave_private.pem -enclave $< -out $@ -config enclave_config.xml
-
-# An app that runs the enclave
-bin/run_model: app.cc lib/model_u.o
-	@mkdir -p $(@D)
-	$(CXX) $^ -o $@ $(app_cflags) $(app_ldflags)
-
-# Debugging binary that runs TVM without SGX
-bin/run_model_nosgx: enclave.cc $(TVM_ROOT)/sgx/sgx_runtime.cc lib/deploy_lib.o
-	@mkdir -p $(@D)
-	$(CXX) $^ -o $@ $(pkg_cflags) $(pkg_ldflags)\
-		-Wl,--format=binary -Wl,lib/deploy_graph.json -Wl,lib/deploy_params.bin -Wl,--format=default
-
-
-clean:
-	rm -rf lib bin
diff --git a/nnvm/examples/sgx/README.md b/nnvm/examples/sgx/README.md
deleted file mode 100644
index 97ec1fcfca57..000000000000
--- a/nnvm/examples/sgx/README.md
+++ /dev/null
@@ -1,39 +0,0 @@
-# TVM in Intel SGX Example
-
-This application demonstrates running a ResNet18 using NNVM inside of an
-[Intel SGX](https://software.intel.com/en-us/blogs/2013/09/26/protecting-application-secrets-with-intel-sgx) trusted computing environment.
-
-## Prerequisites
-
-1. A GNU/Linux environment
-2. NNVM, TVM compiled with LLVM, and their corresponding Python modules
-3. The [Linux SGX SDK](https://github.com/intel/linux-sgx) [link to pre-built libraries](https://01.org/intel-software-guard-extensions/downloads)
-4. `pip install --user mxnet pillow`
-
-## Running the example
-
-`SGX_SDK=/path/to/sgxsdk bash run_example.sh`
-
-If everything goes well, you should see a lot of build messages and below them
-the text `It's a tabby!`.
-
-## High-level overview
-
-First of all, it helps to think of an SGX enclave as a library that can be called
-to perform trusted computation.
-In this library, one can use other libraries like TVM.
-
-Building this example performs the following steps:
-
-1. Downloads a pre-trained MXNet ResNet and a
-   [test image](https://github.com/BVLC/caffe/blob/master/examples/images/cat.jpg)
-2. Converts the ResNet to an NNVM graph + library
-3. Links the graph JSON definition, params, and runtime library into into an SGX
-   enclave along with some code that performs inference.
-4. Compiles and runs an executable that loads the enclave and requests that it perform
-   inference on the image.
-   which invokes the TVM module.
-
-For more information on building, please refer to the `Makefile`.  
-For more information on the TVM module, please refer to `../howto_deploy`.  
-For more in formation on SGX enclaves, please refer to the [SGX Enclave Demo](https://github.com/intel/linux-sgx/tree/master/SampleCode/SampleEnclave/)
diff --git a/nnvm/examples/sgx/app.cc b/nnvm/examples/sgx/app.cc
deleted file mode 100644
index 001c0d71d69e..000000000000
--- a/nnvm/examples/sgx/app.cc
+++ /dev/null
@@ -1,135 +0,0 @@
-#include <cstdio>
-#include <sstream>
-#include <fstream>
-#include <iostream>
-
-#include "sgx_urts.h"
-#include "sgx_eid.h"
-#include "model_u.h"
-
-#define TOKEN_FILENAME   "bin/enclave.token"
-#define ENCLAVE_FILENAME "lib/model.signed.so"
-
-sgx_enclave_id_t global_eid = 0;  // global EID shared by multiple threads
-
-typedef struct _sgx_errlist_t {
-  sgx_status_t err;
-  const char *msg;
-} sgx_errlist_t;
-
-/* Error code returned by sgx_create_enclave */
-static sgx_errlist_t sgx_errlist[] = {
-  { SGX_ERROR_DEVICE_BUSY, "SGX device was busy." },
-  { SGX_ERROR_ENCLAVE_FILE_ACCESS, "Can't open enclave file." },
-  { SGX_ERROR_ENCLAVE_LOST, "Power transition occurred." },
-  { SGX_ERROR_INVALID_ATTRIBUTE, "Enclave was not authorized." },
-  { SGX_ERROR_INVALID_ENCLAVE, "Invalid enclave image." },
-  { SGX_ERROR_INVALID_ENCLAVE_ID, "Invalid enclave identification." },
-  { SGX_ERROR_INVALID_METADATA, "Invalid enclave metadata." },
-  { SGX_ERROR_INVALID_PARAMETER, "Invalid parameter." },
-  { SGX_ERROR_INVALID_SIGNATURE, "Invalid enclave signature." },
-  { SGX_ERROR_INVALID_VERSION, "Enclave version was invalid." },
-  { SGX_ERROR_MEMORY_MAP_CONFLICT, "Memory map conflicted." },
-  { SGX_ERROR_NO_DEVICE, "Invalid SGX device." },
-  { SGX_ERROR_OUT_OF_EPC, "Out of EPC memory." },
-  { SGX_ERROR_OUT_OF_MEMORY, "Out of memory." },
-  { SGX_ERROR_UNEXPECTED, "Unexpected error occurred." },
-};
-
-/* Check error conditions for loading enclave */
-void print_error_message(sgx_status_t status)
-{
-  size_t idx = 0;
-  size_t ttl = sizeof sgx_errlist/sizeof sgx_errlist[0];
-
-  for (idx = 0; idx < ttl; idx++) {
-    if(status == sgx_errlist[idx].err) {
-      printf("Error: %s\n", sgx_errlist[idx].msg);
-      break;
-    }
-  }
-
-  if (idx == ttl)
-    printf("Error code is 0x%X. Please refer to the \"Intel SGX SDK Developer Reference\" for more details.\n", status);
-}
-
-/* Initialize the enclave:
- *   Step 1: try to retrieve the launch token saved by last transaction
- *   Step 2: call sgx_create_enclave to initialize an enclave instance
- *   Step 3: save the launch token if it is updated
- */
-int initialize_enclave(void)
-{
-  sgx_launch_token_t token = {0};
-  sgx_status_t sgx_status = SGX_ERROR_UNEXPECTED;
-  int updated = 0;
-
-  /* Step 1: try to retrieve the launch token saved by last transaction
-   *     if there is no token, then create a new one.
-   */
-  FILE *fp = fopen(TOKEN_FILENAME, "rb");
-  if (fp == NULL && (fp = fopen(TOKEN_FILENAME, "wb")) == NULL) {
-    printf("Warning: Failed to create/open the launch token file \"%s\".\n", TOKEN_FILENAME);
-    return -1;
-  }
-
-  /* read the token from saved file */
-  size_t read_num = fread(token, 1, sizeof(sgx_launch_token_t), fp);
-  if (read_num != 0 && read_num != sizeof(sgx_launch_token_t)) {
-    /* if token is invalid, clear the buffer */
-    memset(&token, 0x0, sizeof(sgx_launch_token_t));
-    printf("Warning: Invalid launch token read from \"%s\".\n", TOKEN_FILENAME);
-  }
-
-  /* Step 2: call sgx_create_enclave to initialize an enclave instance */
-  /* Debug Support: set 2nd parameter to 1 */
-  sgx_status = sgx_create_enclave(ENCLAVE_FILENAME, SGX_DEBUG_FLAG, &token, &updated, &global_eid, NULL);
-  if (sgx_status != SGX_SUCCESS) {
-    print_error_message(sgx_status);
-    if (fp != NULL) fclose(fp);
-    return -1;
-  }
-
-  /* Step 3: save the launch token if it is updated */
-  if (updated == 0 || fp == NULL) {
-    /* if the token is not updated, or file handler is invalid, do not perform saving */
-    if (fp != NULL) fclose(fp);
-    return 0;
-  }
-
-  /* reopen the file with write capablity */
-  fp = freopen(TOKEN_FILENAME, "wb", fp);
-  if (fp == NULL) return 0;
-  size_t write_num = fwrite(token, 1, sizeof(sgx_launch_token_t), fp);
-  if (write_num != sizeof(sgx_launch_token_t))
-    printf("Warning: Failed to save launch token to \"%s\".\n", TOKEN_FILENAME);
-  fclose(fp);
-  return 0;
-}
-
-int SGX_CDECL main(int argc, char *argv[]) {
-  if(initialize_enclave() < 0){
-    printf("Failed to initialize enclave.\n");
-    return -1;
-  }
-
-  std::ifstream f_img("bin/cat.bin", std::ios::binary);
-  std::string img(static_cast<std::stringstream const&>(
-                  std::stringstream() << f_img.rdbuf()).str());
-
-  unsigned predicted_class;
-  sgx_status_t sgx_status = SGX_ERROR_UNEXPECTED;
-  sgx_status = ecall_infer(global_eid, &predicted_class, img.c_str());
-  if (sgx_status != SGX_SUCCESS) {
-    print_error_message(sgx_status);
-  }
-
-  sgx_destroy_enclave(global_eid);
-  if (predicted_class == 281) {
-    std::cout << "It's a tabby!" << std::endl;
-    return 0;
-  }
-  std::cerr << "Inference failed! Predicted class: " <<
-    predicted_class << std::endl;
-  return 1;
-}
diff --git a/nnvm/examples/sgx/build_model.py b/nnvm/examples/sgx/build_model.py
deleted file mode 100644
index df3220afa840..000000000000
--- a/nnvm/examples/sgx/build_model.py
+++ /dev/null
@@ -1,74 +0,0 @@
-"""Creates a neural network graph module, the system library, and params.
-Heavily inspired by tutorials/from_mxnet.py
-"""
-from __future__ import print_function
-import ast
-import os
-from os import path as osp
-import tempfile
-
-import mxnet as mx
-from mxnet.gluon.model_zoo.vision import get_model
-from mxnet.gluon.utils import download
-import nnvm
-import nnvm.compiler
-import numpy as np
-from PIL import Image
-import tvm
-
-
-EXAMPLE_ROOT = osp.abspath(osp.join(osp.dirname(__file__)))
-BIN_DIR = osp.join(EXAMPLE_ROOT, 'bin')
-LIB_DIR = osp.join(EXAMPLE_ROOT, 'lib')
-
-TVM_TARGET = 'llvm --system-lib'
-
-
-def _download_model_and_image(out_dir):
-    mx_model = get_model('resnet18_v1', pretrained=True)
-
-    img_path = osp.join(out_dir, 'cat.png')
-    bin_img_path = osp.join(out_dir, 'cat.bin')
-    download(
-        'https://github.com/dmlc/mxnet.js/blob/master/data/cat.png?raw=true',
-        img_path)
-    img = Image.open(img_path).resize((224, 224))
-    img = _transform_image(img)
-    img.astype('float32').tofile(bin_img_path)
-    shape_dict = {'data': img.shape}
-
-    return mx_model, shape_dict
-
-
-def _transform_image(image):
-    image = np.array(image) - np.array([123., 117., 104.])
-    image /= np.array([58.395, 57.12, 57.375])
-    image = image.transpose((2, 0, 1))
-    image = image[np.newaxis, :]
-    return image
-
-
-def main():
-    # load the model, input image, and imagenet classes
-    mx_model, shape_dict = _download_model_and_image(BIN_DIR)
-
-    # convert the model, add a softmax
-    sym, params = nnvm.frontend.from_mxnet(mx_model)
-    sym = nnvm.sym.softmax(sym)
-
-    # build the graph
-    graph, lib, params = nnvm.compiler.build(
-        sym, TVM_TARGET, shape_dict, params=params)
-
-    # save the built graph
-    if not osp.isdir(LIB_DIR):
-        os.mkdir(LIB_DIR)
-    lib.save(osp.join(LIB_DIR, 'deploy_lib.o'))
-    with open(osp.join(LIB_DIR, 'deploy_graph.json'), 'w') as f_graph_json:
-        f_graph_json.write(graph.json())
-    with open(osp.join(LIB_DIR, 'deploy_params.bin'), 'wb') as f_params:
-        f_params.write(nnvm.compiler.save_param_dict(params))
-
-
-if __name__ == '__main__':
-    main()
diff --git a/nnvm/examples/sgx/enclave.cc b/nnvm/examples/sgx/enclave.cc
deleted file mode 100644
index f1210c91e046..000000000000
--- a/nnvm/examples/sgx/enclave.cc
+++ /dev/null
@@ -1,98 +0,0 @@
-#include <string.h>
-#include <dlpack/dlpack.h>
-#include <tvm/runtime/module.h>
-#include <tvm/runtime/c_runtime_api.h>
-#include <tvm/runtime/registry.h>
-#include <tvm/runtime/packed_func.h>
-#include "tvm/src/runtime/graph/graph_runtime.cc"
-#ifndef _LIBCPP_SGX_CONFIG
-#include <iostream>
-#include <fstream>
-#include <sstream>
-#include "tvm/src/runtime/file_util.cc"
-#endif
-
-// the statically linked graph json and params
-extern char _binary_lib_deploy_params_bin_start[];
-extern char _binary_lib_deploy_params_bin_end[];
-extern char _binary_lib_deploy_graph_json_start[];
-extern char _binary_lib_deploy_graph_json_end[];
-
-int RunInference(const char* img) {
-  tvm::runtime::Module graph_lib =
-    (*tvm::runtime::Registry::Get("module._GetSystemLib"))();
-
-  size_t graph_json_size = ((size_t)_binary_lib_deploy_graph_json_end -
-                            (size_t)_binary_lib_deploy_graph_json_start);
-  size_t graph_params_size = ((size_t)_binary_lib_deploy_params_bin_end -
-                              (size_t)_binary_lib_deploy_params_bin_start);
-  std::string graph_json(_binary_lib_deploy_graph_json_start, graph_json_size);
-  std::string graph_params(_binary_lib_deploy_params_bin_start, graph_params_size);
-
-  int device_type = kDLCPU;
-  int device_id = 0;
-
-  TVMContext ctx;
-  ctx.device_type = static_cast<DLDeviceType>(device_type);
-  ctx.device_id = device_id;
-  std::shared_ptr<tvm::runtime::GraphRuntime> graph_rt =
-    std::make_shared<tvm::runtime::GraphRuntime>();
-
-  graph_rt->Init(graph_json, graph_lib, ctx);
-  graph_rt->LoadParams(graph_params);
-
-  DLTensor* input;
-  DLTensor* output;
-  int ndim = 2;
-  int dtype_code = kDLFloat;
-  int dtype_bits = 32;
-  int dtype_lanes = 1;
-
-  int batch_size = 1;
-  int64_t input_shape[4] = {batch_size, 3, 224, 224};
-  int64_t output_shape[1] = {1000 /* num_classes */};
-  TVMArrayAlloc(input_shape, 4 /* ndim */, dtype_code, dtype_bits, dtype_lanes,
-                device_type, device_id, &input);
-  TVMArrayAlloc(output_shape, 1, dtype_code, dtype_bits, dtype_lanes,
-                device_type, device_id, &output);
-  memcpy(input->data, img, sizeof(float)*batch_size*3*224*224);
-
-  graph_rt->SetInput(graph_rt->GetInputIndex("data"), input);
-  graph_rt->Run();
-  graph_rt->GetOutput(0, output);
-
-  float max_prob = 0;
-  unsigned max_class = -1;
-  for (int i = 0; i < 1000; ++i) {
-    float p = static_cast<float*>(output->data)[i];
-    if (p > max_prob) {
-      max_prob = p;
-      max_class = i;
-    }
-  }
-
-  return max_class;
-}
-
-
-extern "C" {
-int ecall_infer(const char* img) {
-  return RunInference(img);
-}
-}
-
-#ifndef _LIBCPP_SGX_CONFIG
-int main(void) {
-  std::ifstream f_img("bin/cat.bin", std::ios::binary);
-  std::string img(static_cast<std::stringstream const&>(
-                  std::stringstream() << f_img.rdbuf()).str());
-  unsigned predicted_class = RunInference(img.c_str());
-  if (predicted_class == 281) {
-    std::cout << "It's a tabby!" << std::endl;
-    return 0;
-  }
-  std::cerr << "Inference failed! Predicted class: " <<
-    predicted_class << std::endl;
-  return -1;
-}
-#endif
diff --git a/nnvm/examples/sgx/enclave_config.xml b/nnvm/examples/sgx/enclave_config.xml
deleted file mode 100644
index 923d58ec8bf3..000000000000
--- a/nnvm/examples/sgx/enclave_config.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<EnclaveConfiguration>
-  <ProdID>0</ProdID>
-  <ISVSVN>0</ISVSVN>
-  <StackMaxSize>0x2000</StackMaxSize>
-  <HeapMaxSize>0x6794000</HeapMaxSize>
-  <TCSNum>1</TCSNum>
-  <TCSPolicy>1</TCSPolicy>
-  <DisableDebug>0</DisableDebug>
-  <MiscSelect>0</MiscSelect>
-  <MiscMask>0xFFFFFFFF</MiscMask>
-</EnclaveConfiguration>
diff --git a/nnvm/examples/sgx/enclave_private.pem b/nnvm/examples/sgx/enclave_private.pem
deleted file mode 100644
index 529d07be3574..000000000000
--- a/nnvm/examples/sgx/enclave_private.pem
+++ /dev/null
@@ -1,39 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIG4gIBAAKCAYEAroOogvsj/fZDZY8XFdkl6dJmky0lRvnWMmpeH41Bla6U1qLZ
-AmZuyIF+mQC/cgojIsrBMzBxb1kKqzATF4+XwPwgKz7fmiddmHyYz2WDJfAjIveJ
-ZjdMjM4+EytGlkkJ52T8V8ds0/L2qKexJ+NBLxkeQLfV8n1mIk7zX7jguwbCG1Pr
-nEMdJ3Sew20vnje+RsngAzdPChoJpVsWi/K7cettX/tbnre1DL02GXc5qJoQYk7b
-3zkmhz31TgFrd9VVtmUGyFXAysuSAb3EN+5VnHGr0xKkeg8utErea2FNtNIgua8H
-ONfm9Eiyaav1SVKzPHlyqLtcdxH3I8Wg7yqMsaprZ1n5A1v/levxnL8+It02KseD
-5HqV4rf/cImSlCt3lpRg8U5E1pyFQ2IVEC/XTDMiI3c+AR+w2jSRB3Bwn9zJtFlW
-KHG3m1xGI4ck+Lci1JvWWLXQagQSPtZTsubxTQNx1gsgZhgv1JHVZMdbVlAbbRMC
-1nSuJNl7KPAS/VfzAgEDAoIBgHRXxaynbVP5gkO0ug6Qw/E27wzIw4SmjsxG6Wpe
-K7kfDeRskKxESdsA/xCrKkwGwhcx1iIgS5+Qscd1Yg+1D9X9asd/P7waPmWoZd+Z
-AhlKwhdPsO7PiF3e1AzHhGQwsUTt/Y/aSI1MpHBvy2/s1h9mFCslOUxTmWw0oj/Q
-ldIEgWeNR72CE2+jFIJIyml6ftnb6qzPiga8Bm48ubKh0kvySOqnkmnPzgh+JBD6
-JnBmtZbfPT97bwTT+N6rnPqOOApvfHPf15kWI8yDbprG1l4OCUaIUH1AszxLd826
-5IPM+8gINLRDP1MA6azECPjTyHXhtnSIBZCyWSVkc05vYmNXYUNiXWMajcxW9M02
-wKzFELO8NCEAkaTPxwo4SCyIjUxiK1LbQ9h8PSy4c1+gGP4LAMR8xqP4QKg6zdu9
-osUGG/xRe/uufgTBFkcjqBHtK5L5VI0jeNIUAgW/6iNbYXjBMJ0GfauLs+g1VsOm
-WfdgXzsb9DYdMa0OXXHypmV4GwKBwQDUwQj8RKJ6c8cT4vcWCoJvJF00+RFL+P3i
-Gx2DLERxRrDa8AVGfqaCjsR+3vLgG8V/py+z+dxZYSqeB80Qeo6PDITcRKoeAYh9
-xlT3LJOS+k1cJcEmlbbO2IjLkTmzSwa80fWexKu8/Xv6vv15gpqYl1ngYoqJM3pd
-vzmTIOi7MKSZ0WmEQavrZj8zK4endE3v0eAEeQ55j1GImbypSf7Idh7wOXtjZ7WD
-Dg6yWDrri+AP/L3gClMj8wsAxMV4ZR8CgcEA0fzDHkFa6raVOxWnObmRoDhAtE0a
-cjUj976NM5yyfdf2MrKy4/RhdTiPZ6b08/lBC/+xRfV3xKVGzacm6QjqjZrUpgHC
-0LKiZaMtccCJjLtPwQd0jGQEnKfMFaPsnhOc5y8qVkCzVOSthY5qhz0XNotHHFmJ
-gffVgB0iqrMTvSL7IA2yqqpOqNRlhaYhNl8TiFP3gIeMtVa9rZy31JPgT2uJ+kfo
-gV7sdTPEjPWZd7OshGxWpT6QfVDj/T9T7L6tAoHBAI3WBf2DFvxNL2KXT2QHAZ9t
-k3imC4f7U+wSE6zILaDZyzygA4RUbwG0gv8/TJVn2P/Eynf76DuWHGlaiLWnCbSz
-Az2DHBQBBaku409zDQym3j1ugMRjzzSQWzJg0SIyBH3hTmnYcn3+Uqcp/lEBvGW6
-O+rsXFt3pukqJmIV8HzLGGaLm62BHUeZf3dyWm+i3p/hQAL7Xvu04QW70xuGqdr5
-afV7p5eaeQIJXyGQJ0eylV/90+qxjMKiB1XYg6WYvwKBwQCL/ddpgOdHJGN8uRom
-e7Zq0Csi3hGheMKlKbN3vcxT5U7MdyHtTZZOJbTvxKNNUNYH/8uD+PqDGNneb29G
-BfGzvI3EASyLIcGZF3OhKwZd0jUrWk2y7Vhob91jwp2+t73vdMbkKyI4mHOuXvGv
-fg95si9oO7EBT+Oqvhccd2J+F1IVXncccYnF4u5ZGWt5lLewN/pVr7MjjykeaHqN
-t+rfnQam2psA6fL4zS2zTmZPzR2tnY8Y1GBTi0Ko1OKd1HMCgcAb5cB/7/AQlhP9
-yQa04PLH9ygQkKKptZp7dy5WcWRx0K/hAHRoi2aw1wZqfm7VBNu2SLcs90kCCCxp
-6C5sfJi6b8NpNbIPC+sc9wsFr7pGo9SFzQ78UlcWYK2Gu2FxlMjonhka5hvo4zvg
-WxlpXKEkaFt3gLd92m/dMqBrHfafH7VwOJY2zT3WIpjwuk0ZzmRg5p0pG/svVQEH
-NZmwRwlopysbR69B/n1nefJ84UO50fLh5s5Zr3gBRwbWNZyzhXk=
------END RSA PRIVATE KEY-----
diff --git a/nnvm/examples/sgx/model.edl b/nnvm/examples/sgx/model.edl
deleted file mode 100644
index fd45cc318dbf..000000000000
--- a/nnvm/examples/sgx/model.edl
+++ /dev/null
@@ -1,7 +0,0 @@
-enclave {
-    from "sgx_tstdc.edl" import sgx_thread_wait_untrusted_event_ocall, sgx_thread_set_untrusted_event_ocall, sgx_thread_setwait_untrusted_events_ocall, sgx_thread_set_multiple_untrusted_events_ocall;
-
-    trusted {
-        public unsigned ecall_infer([user_check] const char* img);
-    };
-};
diff --git a/nnvm/examples/sgx/run_example.sh b/nnvm/examples/sgx/run_example.sh
deleted file mode 100755
index 4fd0e3f36a63..000000000000
--- a/nnvm/examples/sgx/run_example.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/bash
-
-sgx_sdk=${SGX_SDK:=/opt/sgxsdk}
-
-make
-echo "========================="
-LD_LIBRARY_PATH="$sgx_sdk/lib64":${LD_LIBRARY_PATH} bin/run_model

From b6471698d234193e4587b4ea34adfa7e2015e5d6 Mon Sep 17 00:00:00 2001
From: Sammy <samshang.wang@mail.utoronto.ca>
Date: Fri, 6 Jul 2018 20:34:42 -0400
Subject: [PATCH 797/949] Fixing issue #1395: Undefined symbol: cudaGetDevice
 (#1396)

---
 cmake/util/FindCUDA.cmake | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/cmake/util/FindCUDA.cmake b/cmake/util/FindCUDA.cmake
index 83d2e6500fea..84e1322c5397 100644
--- a/cmake/util/FindCUDA.cmake
+++ b/cmake/util/FindCUDA.cmake
@@ -28,11 +28,11 @@ macro(find_cuda use_cuda)
     set(CUDA_INCLUDE_DIRS ${CUDA_TOOLKIT_ROOT_DIR}/include)
     set(CUDA_FOUND TRUE)
     if(MSVC)
-      find_library(CUDA_CUDAT_LIBRARY cudart
+      find_library(CUDA_CUDART_LIBRARY cudart
         ${CUDA_TOOLKIT_ROOT_DIR}/lib64
         ${CUDA_TOOLKIT_ROOT_DIR}/lib)
     else(MSVC)
-      find_library(CUDA_CUDAT_LIBRARY cudart
+      find_library(CUDA_CUDART_LIBRARY cudart
         ${CUDA_TOOLKIT_ROOT_DIR}/lib64
         ${CUDA_TOOLKIT_ROOT_DIR}/lib)
     endif(MSVC)

From 275142ce069873909f33ccfa9517122b6a6230b8 Mon Sep 17 00:00:00 2001
From: eqy <eqy@cs.washington.edu>
Date: Sat, 7 Jul 2018 18:51:18 -0700
Subject: [PATCH 798/949] [RPC] android process isolation/watchdog (#1387)

---
 .../app/src/main/AndroidManifest.xml          |  12 ++-
 .../java/ml/dmlc/tvm/tvmrpc/MainActivity.java |  46 +++++-----
 .../java/ml/dmlc/tvm/tvmrpc/RPCProcessor.java |  72 +++++++++++----
 .../java/ml/dmlc/tvm/tvmrpc/RPCService.java   |  64 ++++++++++++++
 .../java/ml/dmlc/tvm/tvmrpc/RPCWatchdog.java  |  82 ++++++++++++++++++
 .../src/main/res/mipmap-hdpi/ic_launcher.png  | Bin 0 -> 4774 bytes
 .../src/main/res/mipmap-mdpi/ic_launcher.png  | Bin 0 -> 1599 bytes
 .../tvm/rpc/ConnectProxyServerProcessor.java  |  14 ++-
 8 files changed, 242 insertions(+), 48 deletions(-)
 create mode 100644 apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCService.java
 create mode 100644 apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCWatchdog.java
 create mode 100644 apps/android_rpc/app/src/main/res/mipmap-hdpi/ic_launcher.png
 create mode 100644 apps/android_rpc/app/src/main/res/mipmap-mdpi/ic_launcher.png

diff --git a/apps/android_rpc/app/src/main/AndroidManifest.xml b/apps/android_rpc/app/src/main/AndroidManifest.xml
index 6b0d6d995dba..d8385e3b15e2 100644
--- a/apps/android_rpc/app/src/main/AndroidManifest.xml
+++ b/apps/android_rpc/app/src/main/AndroidManifest.xml
@@ -2,11 +2,14 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="ml.dmlc.tvm.tvmrpc" >
 
+    <uses-permission android:name="android.permission.INTERNET" />
+
     <application
         android:allowBackup="true"
         android:label="@string/app_name"
         android:supportsRtl="true"
-        android:theme="@style/AppTheme" >
+        android:theme="@style/AppTheme"
+        android:icon="@mipmap/ic_launcher" >
         <activity
             android:name=".MainActivity"
             android:label="@string/app_name"
@@ -17,8 +20,9 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        <service android:name=".RPCService"
+                 android:process=":RPCServiceProcess"
+                 android:permission="android.permission.BIND_JOB_SERVICE" />
     </application>
 
-    <uses-permission android:name="android.permission.INTERNET" />
-
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/MainActivity.java b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/MainActivity.java
index 3fc3fc3f1381..0fca08a2e463 100644
--- a/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/MainActivity.java
+++ b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/MainActivity.java
@@ -25,30 +25,18 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+
 import android.support.v7.app.AppCompatActivity;
 import android.support.v7.widget.Toolbar;
 import android.widget.CompoundButton;
 import android.widget.EditText;
 import android.widget.Switch;
+import android.content.Intent;
+
 
 public class MainActivity extends AppCompatActivity {
-  static final int MSG_RPC_ERROR = 0;
-  static final String MSG_RPC_ERROR_DATA_KEY = "msg_rpc_error_data_key";
-
-  private RPCProcessor tvmServerWorker;
-  @SuppressLint("HandlerLeak")
-  private final Handler rpcHandler = new Handler() {
-    @Override
-    public void dispatchMessage(Message msg) {
-      Switch switchConnect = findViewById(R.id.switch_connect);
-      if (msg.what == MSG_RPC_ERROR && switchConnect.isChecked()) {
-        // switch off and show alert dialog.
-        switchConnect.setChecked(false);
-        String msgBody = msg.getData().getString(MSG_RPC_ERROR_DATA_KEY);
-        showDialog("Error", msgBody);
-      }
-    }
-  };
+
+  private RPCWatchdog watchdog;
 
   private void showDialog(String title, String msg) {
     AlertDialog.Builder builder = new AlertDialog.Builder(this);
@@ -71,10 +59,6 @@ protected void onCreate(Bundle savedInstanceState) {
     Toolbar toolbar = findViewById(R.id.toolbar);
     setSupportActionBar(toolbar);
 
-    tvmServerWorker = new RPCProcessor(rpcHandler);
-    tvmServerWorker.setDaemon(true);
-    tvmServerWorker.start();
-
     Switch switchConnect = findViewById(R.id.switch_connect);
     switchConnect.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
       @Override
@@ -88,25 +72,33 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
         }
       }
     });
+    
     enableInputView(true);
+
   }
 
   @Override
   protected void onDestroy() {
     super.onDestroy();
-    tvmServerWorker.disconnect();
+    if (watchdog != null) {
+        watchdog.disconnect();
+        watchdog = null;
+    }
   }
 
   private void connectProxy() {
     EditText edProxyAddress = findViewById(R.id.input_address);
     EditText edProxyPort = findViewById(R.id.input_port);
     EditText edAppKey = findViewById(R.id.input_key);
-
     final String proxyHost = edProxyAddress.getText().toString();
     final int proxyPort = Integer.parseInt(edProxyPort.getText().toString());
     final String key = edAppKey.getText().toString();
 
-    tvmServerWorker.connect(proxyHost, proxyPort, key);
+    System.err.println("creating watchdog thread...");
+    watchdog = new RPCWatchdog(proxyHost, proxyPort, key, this);
+    
+    System.err.println("starting watchdog thread...");
+    watchdog.start();
 
     SharedPreferences pref = getApplicationContext().getSharedPreferences("RPCProxyPreference", Context.MODE_PRIVATE);
     SharedPreferences.Editor editor = pref.edit();
@@ -117,8 +109,10 @@ private void connectProxy() {
   }
 
   private void disconnect() {
-    tvmServerWorker.disconnect();
-    System.err.println("Disconnected.");
+    if (watchdog != null) {
+        watchdog.disconnect();
+        watchdog = null;
+    }
   }
 
   private void enableInputView(boolean enable) {
diff --git a/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCProcessor.java b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCProcessor.java
index 2ff7fee8a6b3..4099084d4ebf 100644
--- a/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCProcessor.java
+++ b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCProcessor.java
@@ -34,10 +34,11 @@ class RPCProcessor extends Thread {
   private String host;
   private int port;
   private String key;
-
   private boolean running = false;
+  private long startTime;
   private ConnectProxyServerProcessor currProcessor;
-  private final Handler uiHandler;
+  private boolean kill = false;
+  public static final int SESSION_TIMEOUT = 30000;
 
   static final SocketFileDescriptorGetter socketFdGetter
       = new SocketFileDescriptorGetter() {
@@ -46,9 +47,18 @@ public int get(Socket socket) {
           return ParcelFileDescriptor.fromSocket(socket).getFd();
         }
       };
+  // callback to initialize the start time of an rpc session
+  class setTimeCallback implements Runnable { 
+    private RPCProcessor rPCProcessor;
+
+    public setTimeCallback(RPCProcessor rPCProcessor) {
+        this.rPCProcessor = rPCProcessor;    
+    }
 
-  RPCProcessor(Handler uiHandler) {
-    this.uiHandler = uiHandler;
+    @Override
+    public void run() {
+        rPCProcessor.setStartTime();
+    }
   }
 
   @Override public void run() {
@@ -61,23 +71,51 @@ public int get(Socket socket) {
           } catch (InterruptedException e) {
           }
         }
-        currProcessor = new ConnectProxyServerProcessor(host, port, key, socketFdGetter);
-      }
-      try {
-        currProcessor.run();
-      } catch (Throwable e) {
-        disconnect();
-        // turn connect switch off.
-        Message message = new Message();
-        message.what = MainActivity.MSG_RPC_ERROR;
-        Bundle bundle = new Bundle();
-        bundle.putString(MainActivity.MSG_RPC_ERROR_DATA_KEY, e.getMessage());
-        message.setData(bundle);
-        uiHandler.sendMessage(message);
+        // if kill, we do nothing and wait for app restart
+        // to prevent race where timedOut was reported but restart has not
+        // happened yet
+        if (kill) {
+            System.err.println("waiting for restart...");
+            currProcessor = null;
+        }
+        else {
+            startTime = 0;
+            currProcessor = new ConnectProxyServerProcessor(host, port, key, socketFdGetter);
+            currProcessor.setStartTimeCallback(new setTimeCallback(this));
+        }
       }
+        if (currProcessor != null)
+            currProcessor.run();
     }
   }
 
+  /**
+   * check if the current RPCProcessor has timed out while in a session
+   */
+  synchronized boolean timedOut(long curTime) {
+    if (startTime == 0) {
+        return false;
+    }
+    else if ((curTime - startTime) > SESSION_TIMEOUT) {
+        System.err.println("set kill flag...");
+        kill = true;
+        return true;
+    }
+    return false;
+  }
+
+  /**
+   * set the start time of the current RPC session (used in callback)
+   */
+  synchronized void setStartTime() {
+    startTime = System.currentTimeMillis();
+    System.err.println("start time set to: " + startTime);
+  }
+
+  synchronized long getStartTime() {
+    return startTime;
+  }
+
   /**
    * Disconnect from the proxy server.
    */
diff --git a/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCService.java b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCService.java
new file mode 100644
index 000000000000..facc230f60ae
--- /dev/null
+++ b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCService.java
@@ -0,0 +1,64 @@
+package ml.dmlc.tvm.tvmrpc;
+
+import android.app.Service;
+import android.os.IBinder;
+import android.content.Intent;
+
+public class RPCService extends Service {
+    private String host;
+    private int port;
+    private String key;
+    private int intentNum;
+    private RPCProcessor tvmServerWorker;
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        synchronized(this) {
+            System.err.println("start command intent");
+            // use an alternate kill to prevent android from recycling the
+            // process
+            if (intent.getBooleanExtra("kill", false)) {
+                System.err.println("rpc service received kill...");
+                System.exit(0);
+            }
+        
+            this.host = intent.getStringExtra("host");
+            this.port = intent.getIntExtra("port", 9090);
+            this.key = intent.getStringExtra("key");
+            System.err.println("got the following: " + this.host + ", " + this.port + ", " + this.key);
+            System.err.println("intent num: " + this.intentNum);
+
+            if (tvmServerWorker == null) {
+                System.err.println("service created worker...");
+                tvmServerWorker = new RPCProcessor();
+                tvmServerWorker.setDaemon(true);
+                tvmServerWorker.start();
+                tvmServerWorker.connect(this.host, this.port, this.key);
+            }
+            else if (tvmServerWorker.timedOut(System.currentTimeMillis())) {
+                System.err.println("rpc service timed out, killing self...");
+                System.exit(0);
+            }
+            this.intentNum++; 
+        }
+        // do not restart unless watchdog/app expliciltly does so
+        return START_NOT_STICKY;
+    }
+    
+    @Override
+    public IBinder onBind(Intent intent) {
+        System.err.println("rpc service got onBind, doing nothing...");
+        return null;
+    }
+
+    @Override
+    public void onCreate() {
+        System.err.println("rpc service onCreate...");
+    }
+
+    @Override
+    public void onDestroy() {
+        tvmServerWorker.disconnect();
+        System.err.println("rpc service onDestroy...");
+    }
+}
diff --git a/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCWatchdog.java b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCWatchdog.java
new file mode 100644
index 000000000000..548a2dfc0e7d
--- /dev/null
+++ b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCWatchdog.java
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+package ml.dmlc.tvm.tvmrpc;
+
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Watchdog for RPCService
+ */
+class RPCWatchdog extends Thread {
+  public static final int WATCHDOG_POLL_INTERVAL = 5000;
+  private String host;
+  private int port;
+  private String key;
+  private Context context;
+  private boolean done = false;
+
+  public RPCWatchdog(String host, int port, String key, Context context) {
+    super();
+    this.host = host;
+    this.port = port;
+    this.key = key;
+    this.context = context;
+  }
+
+  /**
+   * Polling loop to check on RPCService status
+   */ 
+  @Override public void run() {
+    try {
+        while (true) {
+          synchronized (this) {
+              if (done) {
+                System.err.println("watchdog done, returning...");
+                return;
+              }
+              else {
+                System.err.println("polling rpc service...");                                  
+                System.err.println("sending rpc service intent...");
+                Intent intent = new Intent(context, RPCService.class);
+                intent.putExtra("host", host);
+                intent.putExtra("port", port);
+                intent.putExtra("key", key);
+                // will implicilty restart the service if it died
+                context.startService(intent);
+              }
+          }
+          Thread.sleep(WATCHDOG_POLL_INTERVAL);
+        }
+    } catch (InterruptedException e) {
+    }
+  }
+
+  /**
+   * Disconnect from the proxy server.
+   */
+  synchronized void disconnect() {
+    // kill service
+    System.err.println("watchdog disconnect call...");
+    System.err.println("stopping rpc service...");
+    done = true;
+    Intent intent = new Intent(context, RPCService.class);
+    intent.putExtra("kill", true); 
+    context.startService(intent); 
+  }
+}
diff --git a/apps/android_rpc/app/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/android_rpc/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..32a4f0f9157f9959d69ad2fcfa73078a1b5471fe
GIT binary patch
diff --git a/apps/android_rpc/app/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/android_rpc/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..8e5d4dd8331e76e0f2c7af485ac37b7bb997780e
GIT binary patch
diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/ConnectProxyServerProcessor.java b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/ConnectProxyServerProcessor.java
index 208efc773b02..2fc97f65aca4 100644
--- a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/ConnectProxyServerProcessor.java
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/ConnectProxyServerProcessor.java
@@ -33,6 +33,7 @@ public class ConnectProxyServerProcessor implements ServerProcessor {
   private final SocketFileDescriptorGetter socketFileDescriptorGetter;
 
   private volatile Socket currSocket = new Socket();
+  private Runnable callback;
 
   /**
    * Construct proxy server processor.
@@ -48,6 +49,15 @@ public ConnectProxyServerProcessor(String host, int port, String key,
     this.key = "server:" + key;
     socketFileDescriptorGetter = sockFdGetter;
   }
+  
+  /** 
+   * Set a callback when a connection is received e.g., to record the time for a
+   * watchdog.
+   * @param callback Runnable object.
+   */
+  public void setStartTimeCallback(Runnable callback) {
+    this.callback = callback;
+  }
 
   /**
    * Close the socket.
@@ -78,7 +88,9 @@ public ConnectProxyServerProcessor(String host, int port, String key,
       int keylen = Utils.wrapBytes(Utils.recvAll(in, 4)).getInt();
       String remoteKey = Utils.decodeToStr(Utils.recvAll(in, keylen));
       System.err.println("RPCProxy connected to " + address);
-
+      if (callback != null) {
+        callback.run();
+      }
       final int sockFd = socketFileDescriptorGetter.get(currSocket);
       if (sockFd != -1) {
         new NativeServerLoop(sockFd).run();

From 0d570868b913dbe3032cf1f5b3ffd32d95bef7c4 Mon Sep 17 00:00:00 2001
From: Siva <sivar.b@huawei.com>
Date: Sun, 8 Jul 2018 07:21:57 +0530
Subject: [PATCH 799/949] [NNVM][TOP] broadcast versions corresponding to topi:
 mod, max, min, pow, left_shift, right_shift greater, less, equal, not_equal,
 greater_equal and less_equal. (#1383)

---
 nnvm/python/nnvm/frontend/onnx.py             |   2 +-
 nnvm/python/nnvm/top/tensor.py                |  48 ++++
 nnvm/src/top/tensor/broadcast.cc              | 247 ++++++++++++++++++
 nnvm/tests/python/compiler/test_top_level4.py |  55 +++-
 .../python/frontend/onnx/test_forward.py      |  45 ++++
 5 files changed, 393 insertions(+), 4 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/onnx.py b/nnvm/python/nnvm/frontend/onnx.py
index 0d8915ad40f6..ee7fcc3c57fd 100644
--- a/nnvm/python/nnvm/frontend/onnx.py
+++ b/nnvm/python/nnvm/frontend/onnx.py
@@ -544,7 +544,7 @@ def _get_convert_map(opset):
         'Exp': Renamer('exp'),
         'Log': Renamer('log'),
         'Tanh': Renamer('tanh'),
-        # 'Pow'
+        'Pow': Renamer('broadcast_pow'),
         'PRelu': Prelu.get_converter(opset),
         'Sigmoid': Renamer('sigmoid'),
         # 'HardSigmoid'
diff --git a/nnvm/python/nnvm/top/tensor.py b/nnvm/python/nnvm/top/tensor.py
index bd486287abb3..3ff59e5d0042 100644
--- a/nnvm/python/nnvm/top/tensor.py
+++ b/nnvm/python/nnvm/top/tensor.py
@@ -168,6 +168,54 @@ def _compute(attrs, x, _):
 reg.register_pattern("broadcast_div", OpPattern.BROADCAST)
 reg.register_schedule("broadcast_div", _fschedule_broadcast)
 
+# broadcast mod
+reg.register_pattern("broadcast_mod", OpPattern.BROADCAST)
+reg.register_schedule("broadcast_mod", _fschedule_broadcast)
+
+# broadcast max
+reg.register_pattern("broadcast_max", OpPattern.BROADCAST)
+reg.register_schedule("broadcast_max", _fschedule_broadcast)
+
+# broadcast min
+reg.register_pattern("broadcast_min", OpPattern.BROADCAST)
+reg.register_schedule("broadcast_min", _fschedule_broadcast)
+
+# broadcast pow
+reg.register_pattern("broadcast_pow", OpPattern.BROADCAST)
+reg.register_schedule("broadcast_pow", _fschedule_broadcast)
+
+# broadcast left_shift
+reg.register_pattern("broadcast_left_shift", OpPattern.BROADCAST)
+reg.register_schedule("broadcast_left_shift", _fschedule_broadcast)
+
+# broadcast right_shift
+reg.register_pattern("broadcast_right_shift", OpPattern.BROADCAST)
+reg.register_schedule("broadcast_right_shift", _fschedule_broadcast)
+
+# broadcast greater
+reg.register_pattern("broadcast_greater", OpPattern.BROADCAST)
+reg.register_schedule("broadcast_greater", _fschedule_broadcast)
+
+# broadcast less
+reg.register_pattern("broadcast_less", OpPattern.BROADCAST)
+reg.register_schedule("broadcast_less", _fschedule_broadcast)
+
+# broadcast equal
+reg.register_pattern("broadcast_equal", OpPattern.BROADCAST)
+reg.register_schedule("broadcast_equal", _fschedule_broadcast)
+
+# broadcast not_equal
+reg.register_pattern("broadcast_not_equal", OpPattern.BROADCAST)
+reg.register_schedule("broadcast_not_equal", _fschedule_broadcast)
+
+# broadcast greater_equal
+reg.register_pattern("broadcast_greater_equal", OpPattern.BROADCAST)
+reg.register_schedule("broadcast_greater_equal", _fschedule_broadcast)
+
+# broadcast less_equal
+reg.register_pattern("broadcast_less_equal", OpPattern.BROADCAST)
+reg.register_schedule("broadcast_less_equal", _fschedule_broadcast)
+
 # broadcast_to
 reg.register_pattern("broadcast_to", OpPattern.BROADCAST)
 reg.register_schedule("broadcast_to", _fschedule_broadcast)
diff --git a/nnvm/src/top/tensor/broadcast.cc b/nnvm/src/top/tensor/broadcast.cc
index 5f6cc4e6605e..edf209e3e9ec 100644
--- a/nnvm/src/top/tensor/broadcast.cc
+++ b/nnvm/src/top/tensor/broadcast.cc
@@ -15,6 +15,7 @@
 #include "../op_common.h"
 #include "../elemwise_op_common.h"
 #include "topi/broadcast.h"
+#include "topi/elemwise.h"
 
 namespace nnvm {
 namespace top {
@@ -346,5 +347,251 @@ Example::
     return std::vector<NodeEntry>{ dlhs, drhs };
 });
 
+NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_mod, mod)
+.add_alias("__mod_symbol__")
+.describe(R"code(Returns element-wise mod of the input arrays with broadcasting.
+
+Example::
+
+   x = [[ 1.,  2.,  3.],
+        [ 4.,  5.,  6.]]
+
+   y = [[ 2.],
+        [ 3.]]
+
+   broadcast_mod(x, y) = [[ 1.,  0.,  1.],
+                          [ 1.,  2.,  0.]]
+
+)code" NNVM_ADD_FILELINE);
+
+NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_max, maximum)
+.add_alias("__max_symbol__")
+.describe(R"code(Returns element-wise max of the input arrays with broadcasting.
+
+Example::
+
+   x = [[ 1.,  2.,  3.],
+        [ 4.,  5.,  6.]]
+
+   y = [[ 2.],
+        [ 3.]]
+
+   broadcast_max(x, y) = [[ 2.,  2.,  3.],
+                          [ 4.,  5.,  6.]]
+
+)code" NNVM_ADD_FILELINE);
+
+NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_min, minimum)
+.add_alias("__min_symbol__")
+.describe(R"code(Returns element-wise minimum of the input arrays with broadcasting.
+
+Example::
+
+   x = [[ 1.,  2.,  3.],
+        [ 4.,  5.,  6.]]
+
+   y = [[ 2.],
+        [ 3.]]
+
+   broadcast_min(x, y) = [[ 1.,  2.,  2.],
+                          [ 3.,  3.,  3.]]
+
+)code" NNVM_ADD_FILELINE);
+
+NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_pow, power)
+.add_alias("__pow_symbol__")
+.describe(R"code(Returns element-wise x^y of the input arrays with broadcasting.
+
+Example::
+
+   x = [[ 1.,  2.,  3.],
+        [ 4.,  5.,  6.]]
+
+   y = [[ 1.],
+        [ 2.]]
+
+   broadcast_pow(x, y) = [[ 1.,   2.,   3. ],
+                          [ 16.,  25.,  36.]]
+
+)code" NNVM_ADD_FILELINE);
+
+NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_left_shift, left_shift)
+.add_alias("__left_shift_symbol__")
+.describe(R"code(Returns element-wise x << y of the input arrays with broadcasting.
+
+Example::
+
+   x = [[ 1.,  2.,  3.],
+        [ 4.,  5.,  6.]]
+
+   y = [[ 2.],
+        [ 1.]]
+
+   broadcast_left_shift(x, y) = [[ 4.,  8.,  12.],
+                                 [ 8.,  10., 12.]]
+
+)code" NNVM_ADD_FILELINE);
+
+NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_right_shift, right_shift)
+.add_alias("__right_shift_symbol__")
+.describe(R"code(Returns element-wise x >> y of the input arrays with broadcasting.
+
+Example::
+
+   x = [[ 4.,  8.,  12.],
+        [ 8.,  10., 12.]]
+
+   y = [[ 2.],
+        [ 1.]]
+
+   broadcast_right_shift(x, y) = [[ 1.,  2.,  3.],
+                                  [ 4.,  5.,  6.]]
+
+)code" NNVM_ADD_FILELINE);
+
+NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_greater, greater)
+.add_alias("__greater_symbol__")
+.describe(R"code(Returns element-wise x > y of the input arrays with broadcasting.
+
+Example::
+
+   x = [[ 1.,  2.,  3.],
+        [ 4.,  5.,  6.]]
+
+   y = [[ 2.],
+        [ 3.]]
+
+   broadcast_greater(x, y) = [[ 0.,  0.,  1.],
+                              [ 1.,  1.,  1.]]
+
+)code" NNVM_ADD_FILELINE)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    return Array<Tensor>{ topi::cast(topi::greater(inputs[0], inputs[1]), out_info[0]->dtype) };
+}, 11);
+
+NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_less, less)
+.add_alias("__less_symbol__")
+.describe(R"code(Returns element-wise x < y of the input arrays with broadcasting.
+
+Example::
+
+   x = [[ 1.,  2.,  3.],
+        [ 4.,  5.,  6.]]
+
+   y = [[ 2.],
+        [ 3.]]
+
+   broadcast_less(x, y) = [[ 1.,  0.,  0.],
+                           [ 0.,  0.,  0.]]
+
+)code" NNVM_ADD_FILELINE)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    return Array<Tensor>{ topi::cast(topi::less(inputs[0], inputs[1]), out_info[0]->dtype) };
+}, 11);
+
+NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_equal, equal)
+.add_alias("__equal_symbol__")
+.describe(R"code(Returns element-wise x == y of the input arrays with broadcasting.
+
+Example::
+
+   x = [[ 1.,  2.,  3.],
+        [ 4.,  5.,  6.]]
+
+   y = [[ 2.],
+        [ 5.]]
+
+   broadcast_equal(x, y) = [[ 0.,  1.,  0.],
+                            [ 0.,  1.,  0.]]
+
+)code" NNVM_ADD_FILELINE)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    return Array<Tensor>{ topi::cast(topi::equal(inputs[0], inputs[1]), out_info[0]->dtype) };
+}, 11);
+
+NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_not_equal, not_equal)
+.add_alias("__not_equal_symbol__")
+.describe(R"code(Returns element-wise x != y of the input arrays with broadcasting.
+
+Example::
+
+   x = [[ 1.,  2.,  3.],
+        [ 4.,  5.,  6.]]
+
+   y = [[ 2.],
+        [ 4.]]
+
+   broadcast_not_equal(x, y) = [[ 1.,  0.,  1.],
+                                [ 0.,  1.,  1.]]
+
+)code" NNVM_ADD_FILELINE)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    return Array<Tensor>{ topi::cast(topi::not_equal(inputs[0],
+                                                     inputs[1]),
+                                                     out_info[0]->dtype) };
+}, 11);
+
+NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_greater_equal, greater_equal)
+.add_alias("__greater_equal_symbol__")
+.describe(R"code(Returns element-wise x >= y of the input arrays with broadcasting.
+
+Example::
+
+   x = [[ 1.,  2.,  3.],
+        [ 4.,  5.,  6.]]
+
+   y = [[ 2.],
+        [ 6.]]
+
+   broadcast_greater_equal(x, y) = [[ 0.,  1.,  1.],
+                                    [ 0.,  0.,  1.]]
+
+)code" NNVM_ADD_FILELINE)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    return Array<Tensor>{ topi::cast(topi::greater_equal(inputs[0],
+                                                         inputs[1]),
+                                                         out_info[0]->dtype) };
+}, 11);
+
+NNVM_REGISTER_BINARY_BROADCAST_OP(broadcast_less_equal, less_equal)
+.add_alias("__less_equal_symbol__")
+.describe(R"code(Returns element-wise x <= y of the input arrays with broadcasting.
+
+Example::
+
+   x = [[ 1.,  2.,  3.],
+        [ 4.,  5.,  6.]]
+
+   y = [[ 1.],
+        [ 5.]]
+
+   broadcast_less_equal(x, y) = [[ 1.,  0.,  0.],
+                                 [ 1.,  1.,  0.]]
+
+)code" NNVM_ADD_FILELINE)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    return Array<Tensor>{ topi::cast(topi::less_equal(inputs[0],
+                                                      inputs[1]),
+                                                      out_info[0]->dtype) };
+}, 11);
+
 }  // namespace top
 }  // namespace nnvm
diff --git a/nnvm/tests/python/compiler/test_top_level4.py b/nnvm/tests/python/compiler/test_top_level4.py
index 33f3ff3e9052..de6a3fa331bc 100644
--- a/nnvm/tests/python/compiler/test_top_level4.py
+++ b/nnvm/tests/python/compiler/test_top_level4.py
@@ -9,17 +9,23 @@
 
 
 def helper(symbol, inputs, dtype,
-           np_forward, np_backward=None, need_input=True, need_head_grads=True):
+           np_forward, np_backward=None,
+           need_input=True, need_head_grads=True, in_range={}):
     ishapes = {}
     input_syms = []
     np_inputs = {}
     for (name, shape, s) in inputs:
         ishapes.update({name: shape})
-        np_inputs.update({name: np.random.uniform(size=shape).astype(dtype)})
+        if name in in_range:
+            np_inputs.update({name: np.random.uniform(size=shape,
+                                                      low=in_range[name][0],
+                                                      high=in_range[name][1]).astype(dtype)})
+        else:
+            np_inputs.update({name: np.random.uniform(size=shape).astype(dtype)})
         input_syms.append(s)
 
     for target, ctx in ctx_list():
-        graph, lib, _ = nnvm.compiler.build(symbol, target, ishapes)
+        graph, lib, _ = nnvm.compiler.build(symbol, target, ishapes, dtype=dtype)
         m = graph_runtime.create(graph, lib, ctx)
         m.run(**np_inputs)
         y_np = np_forward(**np_inputs)
@@ -228,6 +234,49 @@ def _backward_div(head_grads, a, b):
         return da, db
     helper(y, inputs, dtype, lambda a, b: a / b, _backward_div)
 
+    y = sym.broadcast_mod(a, b)
+    helper(y, inputs, 'int32',
+           lambda a, b: np.mod(a, b),
+           in_range={'a': (0.001, 100), 'b': (1, 100)})
+
+    y = sym.broadcast_max(a, b)
+    helper(y, inputs, dtype, lambda a, b: np.maximum(a, b))
+
+    y = sym.broadcast_min(a, b)
+    helper(y, inputs, dtype, lambda a, b: np.minimum(a, b))
+
+    y = sym.broadcast_pow(a, b)
+    helper(y, inputs, dtype,
+           lambda a, b: np.power(a, b),
+           in_range={'a': (0.001, 100), 'b': (0.001, 2)})
+
+    y = sym.broadcast_left_shift(a, b)
+    helper(y, inputs, 'int32', lambda a, b: a << b)
+
+    y = sym.broadcast_right_shift(a, b)
+    helper(y, inputs, 'int32', lambda a, b: a >> b)
+
+    y = sym.broadcast_greater(a, b)
+    helper(y, inputs, dtype, lambda a, b: np.greater(a, b))
+
+    y = sym.broadcast_less(a, b)
+    helper(y, inputs, dtype, lambda a, b: np.less(a, b))
+
+    y = sym.broadcast_equal(a, b)
+    helper(y, inputs, 'int32', lambda a, b: np.equal(a, b),
+           in_range={'a': (-2, 2), 'b': (-2, 2)})
+
+    y = sym.broadcast_not_equal(a, b)
+    helper(y, inputs, 'int32', lambda a, b: np.not_equal(a, b),
+           in_range={'a': (-2, 2), 'b': (-2, 2)})
+
+    y = sym.broadcast_greater_equal(a, b)
+    helper(y, inputs, 'int32', lambda a, b: np.greater_equal(a, b),
+           in_range={'a': (-3, 3), 'b': (-3, 3)})
+
+    y = sym.broadcast_less_equal(a, b)
+    helper(y, inputs, 'int32', lambda a, b: np.less_equal(a, b),
+           in_range={'a': (-3, 3), 'b': (-3, 3)})
 
 def test_greater():
     l = sym.Variable("l")
diff --git a/nnvm/tests/python/frontend/onnx/test_forward.py b/nnvm/tests/python/frontend/onnx/test_forward.py
index 0af92e3f4ee1..56f1e006265a 100644
--- a/nnvm/tests/python/frontend/onnx/test_forward.py
+++ b/nnvm/tests/python/frontend/onnx/test_forward.py
@@ -108,6 +108,50 @@ def test_reshape_like():
 
     np.testing.assert_allclose(ref_shape, tvm_out.shape)
 
+def _test_power_iteration(x_shape, y_shape):
+    if isinstance(y_shape, int):
+        y_shape = [y_shape]
+
+    x = np.random.uniform(size=x_shape).astype(np.float32)
+    y = np.random.uniform(size=y_shape).astype(np.float32)
+
+    np_res = np.power(x, y).astype(np.float32)
+
+    res = helper.make_node("Pow", ['x', 'y'], ['out'])
+
+    graph = helper.make_graph([res],
+                              'power_test',
+                              inputs = [helper.make_tensor_value_info("x", TensorProto.FLOAT, list(x_shape)),
+                                        helper.make_tensor_value_info("y", TensorProto.FLOAT, list(y_shape))],
+                              outputs = [helper.make_tensor_value_info("out", TensorProto.FLOAT, list(np_res.shape))])
+
+    model = helper.make_model(graph, producer_name='power_test')
+
+    for target, ctx in ctx_list():
+        new_sym, params = nnvm.frontend.from_onnx(model)
+
+        input_name = model.graph.input[0].name
+        input_name1 = model.graph.input[1].name
+        shape_dict = {input_name: x.shape, input_name1: y.shape}
+        dtype_dict = {input_name: x.dtype, input_name1: y.dtype}
+
+        graph, lib, params = nnvm.compiler.build(new_sym, target, shape_dict, dtype_dict, params=params)
+        m = graph_runtime.create(graph, lib, ctx)
+        # set inputs
+        m.set_input(input_name, tvm.nd.array(x))
+        m.set_input(input_name1, tvm.nd.array(y))
+        m.set_input(**params)
+        m.run()
+        # get outputs
+        tvm_out = m.get_output(0, tvm.nd.empty(np_res.shape, np_res.dtype))
+
+        np.testing.assert_allclose(np_res, tvm_out.asnumpy(), rtol=1e-5, atol=1e-5)
+
+def test_power():
+    _test_power_iteration((1, 3), (1))
+    _test_power_iteration((2, 3), (2, 3))
+    _test_power_iteration((2, 3), (1, 3))
+
 def test_squeeze():
     in_shape = (1, 3, 1, 3, 1, 1)
     out_shape = (3, 3)
@@ -247,6 +291,7 @@ def test_matmul():
     verify_resnet18()
     test_reshape()
     test_reshape_like()
+    test_power()
     test_squeeze()
     test_unsqueeze()
     test_slice()

From 633b13bb04bab2b56106c554eacaea509587254d Mon Sep 17 00:00:00 2001
From: Leyuan Wang <laurawly@gmail.com>
Date: Sat, 7 Jul 2018 18:56:41 -0700
Subject: [PATCH 800/949] [TOPI] Add GPU SSD (#1397)

---
 topi/python/topi/cuda/__init__.py          |   6 +-
 topi/python/topi/cuda/nms.py               | 354 ++++++++++++++++++++
 topi/python/topi/cuda/ssd/__init__.py      |   5 +
 topi/python/topi/cuda/ssd/multibox.py      | 360 +++++++++++++++++++++
 topi/python/topi/cuda/vision.py            |  78 ++++-
 topi/tests/python/test_topi_clip.py        |  14 +-
 topi/tests/python/test_topi_conv2d_nchw.py |  19 +-
 topi/tests/python/test_topi_vision.py      |  25 +-
 8 files changed, 840 insertions(+), 21 deletions(-)
 create mode 100644 topi/python/topi/cuda/nms.py
 create mode 100644 topi/python/topi/cuda/ssd/__init__.py
 create mode 100644 topi/python/topi/cuda/ssd/multibox.py

diff --git a/topi/python/topi/cuda/__init__.py b/topi/python/topi/cuda/__init__.py
index dbf00ebeb52b..b8740f811ff7 100644
--- a/topi/python/topi/cuda/__init__.py
+++ b/topi/python/topi/cuda/__init__.py
@@ -15,6 +15,8 @@
 from .pooling import schedule_pool, schedule_global_pool
 from .conv2d_transpose_nchw import schedule_conv2d_transpose_nchw
 from .extern import schedule_extern
-from .vision import schedule_region
-from .vision import schedule_reorg
 from .nn import schedule_lrn, schedule_l2_normalize
+from .vision import *
+from . import ssd
+from .ssd import *
+from .nms import *
diff --git a/topi/python/topi/cuda/nms.py b/topi/python/topi/cuda/nms.py
new file mode 100644
index 000000000000..4d4e402de5c2
--- /dev/null
+++ b/topi/python/topi/cuda/nms.py
@@ -0,0 +1,354 @@
+# pylint: disable=invalid-name, no-member, too-many-locals, too-many-arguments, too-many-statements, singleton-comparison
+"""Non-maximum suppression operator"""
+import math
+import tvm
+
+from tvm import api
+from topi.vision import nms
+
+
+def sort_ir(data, index, output, axis, is_descend):
+    """Low level IR to do sorting on the GPU, same usage as tvm.contrib.sort.argsort on the CPU.
+
+    Parameters
+    ----------
+    data: Buffer
+        2D Buffer of input boxes' score with shape [batch_size, num_anchors].
+
+    index : Buffer
+        Buffer of number of valid number of boxes.
+
+    output : Buffer
+        Output buffer of indicies of sorted tensor.
+
+    axis : int
+        The axis used for sorting.
+
+    is_descend : bool
+        If the sorted data is in descending order.
+
+    Returns
+    -------
+    stmt : Stmt
+        The result IR statement.
+    """
+
+    max_threads = int(
+        tvm.target.current_target(allow_none=False).max_num_threads)
+    tx = tvm.thread_axis("threadIdx.x")
+    bx = tvm.thread_axis("blockIdx.x")
+    ib = tvm.ir_builder.create()
+    p_data = ib.buffer_ptr(data)
+    p_index = ib.buffer_ptr(index)
+    p_out = ib.buffer_ptr(output)
+    ndim = len(data.shape)
+    assert data.dtype == "float32", "Currently only supports input dtype to be float32"
+    assert axis < ndim, "Axis out of boundary for input ndim %d" % ndim
+
+    axis_mul_before = 1
+    axis_mul_after = 1
+    if axis < 0:
+        axis = ndim + axis
+    for i in range(0, ndim):
+        if i < axis:
+            axis_mul_before *= data.shape[i]
+        elif i > axis:
+            axis_mul_after *= data.shape[i]
+
+    dshape = 0
+    for i in range(0, len(index.shape)):
+        dshape += index.shape[i]
+    dshape = tvm.select(dshape > axis_mul_before*axis_mul_after, dshape,
+                        axis_mul_before*axis_mul_after)
+
+    sizes_temp = ib.allocate(
+        "int32", dshape, name="sizes_temp", scope="global")
+    sizes = ib.allocate("int32", dshape, name="sizes", scope="global")
+    temp_index = ib.allocate("int32", dshape, name="temp_index", scope="local")
+    temp_data = ib.allocate("float32", dshape, name="temp_data", scope="local")
+    data_new = ib.allocate("float32", dshape, name="data_new", scope="global")
+    index_new = ib.allocate("int32", dshape, name="index_new", scope="global")
+    nthread_tx = max_threads
+    nthread_bx = dshape // max_threads + 1
+    ib.scope_attr(tx, "thread_extent", nthread_tx)
+    ib.scope_attr(bx, "thread_extent", nthread_bx)
+    tid = bx * max_threads + tx
+
+    with ib.if_scope(tid < axis_mul_before * axis_mul_after):
+        sizes[tid] = p_index[tid]
+        sizes_temp[tid] = p_index[tid]
+
+    with ib.if_scope(tid < axis_mul_before * axis_mul_after):
+        with ib.for_range(0, tvm.floor(tvm.sqrt((axis_mul_before * axis_mul_after) \
+             .astype("float32"))) + 1, name="k") as k:
+            with ib.if_scope(tid - (tvm.const(1, "int32") << k) >= 0):
+                with ib.if_scope(k % 2 == 0):
+                    sizes[tid] += sizes_temp[tid - (
+                        tvm.const(1, "int32") << k)]
+                    sizes_temp[tid] = sizes[tid]
+                with ib.else_scope():
+                    sizes_temp[tid] += sizes[tid - (
+                        tvm.const(1, "int32") << k)]
+                    sizes[tid] = sizes_temp[tid]
+
+    with ib.if_scope(tid < axis_mul_before * axis_mul_after):
+        i = tid / axis_mul_after
+        j = tid % axis_mul_after
+        current_sort_num = p_index[tid]
+        base_idx = i * data.shape[axis] * axis_mul_after + j
+        with ib.for_range(0, current_sort_num, name="k") as k:
+            full_idx = base_idx + k * axis_mul_after
+            with ib.if_scope(tid == 0):
+                start = 0
+            with ib.else_scope():
+                start = sizes[tid-1]
+            index_new[start + k] = k
+            data_new[start + k] = p_data[full_idx]
+
+    with ib.if_scope(tid < axis_mul_before * axis_mul_after):
+        with ib.if_scope(tid == 0):
+            start = 0
+        with ib.else_scope():
+            start = sizes[tid-1]
+        # OddEvenTransposeSort
+        with ib.for_range(0, p_index[tid], name="k") as k:
+            with ib.for_range(0, p_index[tid] - 1, name="i") as i:
+                with ib.if_scope(i % 2 == (k & 1)):
+                    with ib.if_scope(((data_new[i+start] < data_new[i+start+1]) ^
+                                      is_descend) == False):
+                        temp_data[tid] = data_new[i+start]
+                        data_new[i+start] = data_new[i+start+1]
+                        data_new[i+start+1] = temp_data[tid]
+                        temp_index[tid] = index_new[i+start]
+                        index_new[i+start] = index_new[i+start+1]
+                        index_new[i+start+1] = temp_index[tid]
+
+    with ib.if_scope(tid < axis_mul_before * axis_mul_after):
+        i = tid / axis_mul_after
+        j = tid % axis_mul_after
+        current_sort_num = p_index[tid]
+        base_idx = i * data.shape[axis] * axis_mul_after + j
+        with ib.for_range(0, data.shape[axis], name="k") as k:
+            with ib.if_scope(tid == 0):
+                start = 0
+            with ib.else_scope():
+                start = sizes[tid-1]
+            p_out[base_idx + k * axis_mul_after] = tvm.select(
+                k < current_sort_num,
+                index_new[k+start], k)
+    body = ib.get()
+    return body
+
+
+def nms_ir(data, sort_result, valid_count, out, nms_threshold, force_suppress, nms_topk):
+    """Low level IR routing for transform location in multibox_detection operator.
+
+    Parameters
+    ----------
+    data: Buffer
+        Buffer of output boxes with class and score.
+
+    sort_result : Buffer
+        Buffer of output box indexes sorted by score.
+
+    valid_count : Buffer
+        Buffer of number of valid output boxes.
+
+    out : Buffer
+        Output buffer.
+
+    nms_threshold : float
+        Non-maximum suppression threshold.
+
+    force_suppress : boolean
+        Whether to suppress all detections regardless of class_id.
+
+    nms_topk : int
+        Keep maximum top k detections before nms, -1 for no limit.
+
+    Returns
+    -------
+    stmt : Stmt
+        The result IR statement.
+    """
+    def calculate_overlap(out_tensor, box_a_idx, box_b_idx):
+        """Calculate overlap of two boxes.
+        """
+        w = tvm.make.Max(0.0, tvm.make.Min(out_tensor[box_a_idx + 2], out_tensor[box_b_idx + 2])
+                         - tvm.make.Max(out_tensor[box_a_idx], out_tensor[box_b_idx]))
+        h = tvm.make.Max(0.0, tvm.make.Min(out_tensor[box_a_idx + 3], out_tensor[box_b_idx + 3])
+                         - tvm.make.Max(out_tensor[box_a_idx + 1], out_tensor[box_b_idx + 1]))
+        i = w * h
+        u = (out_tensor[box_a_idx + 2] - out_tensor[box_a_idx]) * \
+            (out_tensor[box_a_idx + 3] - out_tensor[box_a_idx + 1]) + \
+            (out_tensor[box_b_idx + 2] - out_tensor[box_b_idx]) * \
+            (out_tensor[box_b_idx + 3] - out_tensor[box_b_idx + 1]) - i
+        return tvm.select(u <= 0.0, 0.0, i / u)
+
+    max_threads = int(math.sqrt(
+        tvm.target.current_target(allow_none=False).max_num_threads))
+    tx = tvm.thread_axis("threadIdx.x")
+    ty = tvm.thread_axis("threadIdx.y")
+    bx = tvm.thread_axis("blockIdx.x")
+    by = tvm.thread_axis("blockIdx.y")
+    ib = tvm.ir_builder.create()
+    p_data = ib.buffer_ptr(data)
+    p_sort_result = ib.buffer_ptr(sort_result)
+    p_valid_count = ib.buffer_ptr(valid_count)
+    p_out = ib.buffer_ptr(out)
+    batch_size = out.shape[0]
+    num_anchors = out.shape[1]
+    nthread_tx = max_threads
+    nthread_bx = num_anchors // max_threads + 1
+    nthread_ty = max_threads
+    nthread_by = 6 // max_threads + 1
+    ib.scope_attr(tx, "thread_extent", nthread_tx)
+    ib.scope_attr(ty, "thread_extent", nthread_ty)
+    ib.scope_attr(bx, "thread_extent", nthread_bx)
+    ib.scope_attr(by, "thread_extent", nthread_by)
+    i = bx * max_threads + tx
+    j = by * max_threads + ty
+
+    nms_threshold_node = tvm.make.node(
+        "FloatImm", dtype="float32", value=nms_threshold)
+    nms_topk_node = tvm.make.node("IntImm", dtype="int32", value=nms_topk)
+    force_suppress_node = tvm.make.node(
+        "IntImm", dtype="int32", value=1 if force_suppress else 0)
+    with ib.for_range(0, batch_size, for_type="unroll", name="n") as n:
+        with ib.if_scope(
+            tvm.all(nms_threshold_node > 0, nms_threshold_node < 1,
+                    p_valid_count[0] > 0)):
+            # Reorder output
+            nkeep = tvm.select(
+                tvm.all(nms_topk_node > 0, nms_topk < p_valid_count[n]),
+                nms_topk, p_valid_count[n])
+            with ib.if_scope(i < nkeep):
+                with ib.if_scope(j < 6):
+                    p_out[(n * num_anchors * 6
+                           + i * 6 + j)] = p_data[(n * num_anchors * 6
+                                                   + p_sort_result[n * num_anchors + i] * 6 + j)]
+            with ib.if_scope(tvm.all(nms_topk_node > 0, nms_topk < p_valid_count[n])):
+                with ib.if_scope(i < p_valid_count[n] - nkeep):
+                    with ib.if_scope(j < 6):
+                        p_out[(n * num_anchors * 6
+                               + (i + nkeep) * 6 + j)] = p_data[(n * num_anchors * 6
+                                                                 + (i + nkeep) * 6 + j)]
+            # Apply nms
+            with ib.if_scope(i < p_valid_count[n]):
+                offset_i = i * 6
+                with ib.if_scope(p_out[n * num_anchors * 6 + offset_i] >= 0):
+                    with ib.if_scope(j < p_valid_count[n]):
+                        offset_j = j * 6
+                        with ib.if_scope(tvm.all(j > i, p_out[n * num_anchors * 6
+                                                              + offset_j] >= 0)):
+                            with ib.if_scope(tvm.any(force_suppress_node > 0,
+                                                     p_out[n * num_anchors * 6 + offset_i] ==
+                                                     p_out[n * num_anchors * 6 + offset_j])):
+                                # When force_suppress == True or class_id equals
+                                iou = calculate_overlap(
+                                    p_out, n * num_anchors * 6 + offset_i + 2,
+                                    n * num_anchors * 6 + offset_j + 2)
+                                with ib.if_scope(iou >= nms_threshold):
+                                    p_out[
+                                        n * num_anchors * 6 + offset_j] = -1.0
+        with ib.else_scope():
+            with ib.if_scope(i < p_valid_count[n]):
+                with ib.if_scope(j < 6):
+                    p_out[(n * num_anchors * 6
+                           + i * 6 + j)] = p_data[n * num_anchors * 6 + i * 6 + j]
+        # Set invalid entry to be -1
+        with ib.if_scope(i < num_anchors - p_valid_count[n]):
+            with ib.if_scope(j < 6):
+                p_out[n * num_anchors * 6 + (i +
+                                             p_valid_count[n]) * 6 + j] = -1.0
+    body = ib.get()
+    return body
+
+
+@nms.register(["cuda", "gpu"])
+def nms_gpu(data, valid_count, nms_threshold=0.5, force_suppress=False, nms_topk=-1):
+    """Non-maximum suppression operator for object detection.
+
+    Parameters
+    ----------
+    data: tvm.Tensor
+        3-D tensor with shape [batch_size, num_anchors, 6].
+        The last dimension should be in format of
+        [class_id, score, box_left, box_top, box_right, box_bottom].
+
+    valid_count : tvm.Tensor
+        1-D tensor for valid number of boxes.
+
+    nms_threshold : float
+        Non-maximum suppression threshold.
+
+    force_suppress : boolean
+        Whether to suppress all detections regardless of class_id.
+
+    nms_topk : int
+        Keep maximum top k detections before nms, -1 for no limit.
+
+    Returns
+    -------
+    out : tvm.Tensor
+        3-D tensor with shape [batch_size, num_anchors, 6].
+
+    Example
+    --------
+    .. code-block:: python
+
+        # An example to use nms
+        dshape = (1, 5, 6)
+        data = tvm.placeholder(dshape, name="data")
+        valid_count = tvm.placeholder(
+            (dshape[0],), dtype="int32", name="valid_count")
+        nms_threshold = 0.7
+        force_suppress = True
+        nms_topk = -1
+        out = nms(data, valid_count, nms_threshold, force_suppress, nms_topk)
+        np_data = np.random.uniform(dshape)
+        np_valid_count = np.array([4])
+        s = topi.generic.schedule_nms(out)
+        f = tvm.build(s, [data, valid_count, out], "llvm")
+        ctx = tvm.cpu()
+        tvm_data = tvm.nd.array(np_data, ctx)
+        tvm_valid_count = tvm.nd.array(np_valid_count, ctx)
+        tvm_out = tvm.nd.array(np.zeros(dshape, dtype=data.dtype), ctx)
+        f(tvm_data, tvm_valid_count, tvm_out)
+    """
+    batch_size = data.shape[0]
+    num_anchors = data.shape[1]
+    valid_count_dtype = "int32"
+    valid_count_buf = api.decl_buffer(valid_count.shape, valid_count_dtype,
+                                      "valid_count_buf", data_alignment=4)
+    data_buf = api.decl_buffer(
+        data.shape, data.dtype, "data_buf", data_alignment=8)
+    score_axis = 1
+    score_shape = (batch_size, num_anchors)
+    score_tensor = tvm.compute(
+        score_shape, lambda i, j: data[i, j, score_axis], name="score_tensor")
+    score_tensor_buf = api.decl_buffer(score_tensor.shape, data.dtype,
+                                       "score_tensor_buf", data_alignment=8)
+    sort_tensor_dtype = "int32"
+    sort_tensor_buf = api.decl_buffer(score_shape, sort_tensor_dtype,
+                                      "sort_tensor_buf", data_alignment=8)
+
+    sort_tensor = \
+        tvm.extern(score_shape,
+                   [score_tensor, valid_count],
+                   lambda ins, outs: sort_ir(
+                       ins[0], ins[1], outs[0], score_axis, True),
+                   dtype=sort_tensor_dtype,
+                   in_buffers=[score_tensor_buf, valid_count_buf],
+                   out_buffers=sort_tensor_buf,
+                   name="nms_sort")
+    out = \
+        tvm.extern(data.shape,
+                   [data, sort_tensor, valid_count],
+                   lambda ins, outs: nms_ir(
+                       ins[0], ins[1], ins[2], outs[0], nms_threshold,
+                       force_suppress, nms_topk),
+                   dtype="float32",
+                   in_buffers=[data_buf, sort_tensor_buf, valid_count_buf],
+                   tag="nms")
+    return out
diff --git a/topi/python/topi/cuda/ssd/__init__.py b/topi/python/topi/cuda/ssd/__init__.py
new file mode 100644
index 000000000000..d680c578e7aa
--- /dev/null
+++ b/topi/python/topi/cuda/ssd/__init__.py
@@ -0,0 +1,5 @@
+# pylint: disable=wildcard-import
+"""VISION network operators"""
+from __future__ import absolute_import as _abs
+
+from .multibox import *
diff --git a/topi/python/topi/cuda/ssd/multibox.py b/topi/python/topi/cuda/ssd/multibox.py
new file mode 100644
index 000000000000..c22e7a513d7d
--- /dev/null
+++ b/topi/python/topi/cuda/ssd/multibox.py
@@ -0,0 +1,360 @@
+# pylint: disable=invalid-name, no-member, too-many-locals, too-many-arguments, too-many-statements
+"""SSD multibox operators"""
+from __future__ import absolute_import as _abs
+import math
+import tvm
+
+from tvm import api
+
+import topi
+
+from topi.vision.ssd import multibox_prior
+from topi.vision.ssd import multibox_detection
+from topi.vision.ssd import multibox_transform_loc
+from ..nms import nms
+
+def multibox_prior_ir(data, out, sizes, ratios, steps, offsets):
+    """Low level IR routing for multibox_prior operator.
+
+    Parameters
+    ----------
+    data : Buffer
+        Input data buffer.
+
+    out : Buffer
+        Output buffer.
+
+    sizes : tuple of float
+        Tuple of sizes for anchor boxes.
+
+    ratios : tuple of float
+        Tuple of ratios for anchor boxes.
+
+    steps : Tuple of float
+        Priorbox step across y and x, -1 for auto calculation.
+
+    offsets : tuple of int
+        Priorbox center offsets, y and x respectively.
+
+    Returns
+    -------
+    stmt : Stmt
+        The result IR statement.
+    """
+    max_threads = int(math.sqrt(tvm.target.current_target(allow_none=False).max_num_threads))
+    tx = tvm.thread_axis("threadIdx.x")
+    ty = tvm.thread_axis("threadIdx.y")
+    bx = tvm.thread_axis("blockIdx.x")
+    by = tvm.thread_axis("blockIdx.y")
+    ib = tvm.ir_builder.create()
+    p_out = ib.buffer_ptr(out)
+    in_height = data.shape[2]
+    in_width = data.shape[3]
+    nthread_tx = max_threads
+    nthread_bx = in_height // max_threads + 1
+    nthread_ty = max_threads
+    nthread_by = in_width // max_threads + 1
+    ib.scope_attr(tx, "thread_extent", nthread_tx)
+    ib.scope_attr(ty, "thread_extent", nthread_ty)
+    ib.scope_attr(bx, "thread_extent", nthread_bx)
+    ib.scope_attr(by, "thread_extent", nthread_by)
+
+    num_sizes = len(sizes)
+    num_ratios = len(ratios)
+    size_ratio_concat = sizes + ratios
+    steps_h = steps[0] if steps[0] > 0 else 1.0 / in_height
+    steps_w = steps[1] if steps[1] > 0 else 1.0 / in_width
+    offset_h = offsets[0]
+    offset_w = offsets[1]
+
+    i = bx * max_threads + tx
+    j = by * max_threads + ty
+    with ib.if_scope((i < in_height)):
+        with ib.if_scope((j < in_width)):
+            center_h = (i + offset_h) * steps_h
+            center_w = (j + offset_w) * steps_w
+
+            for k in range(num_sizes + num_ratios - 1):
+                w = tvm.select(k < num_sizes,
+                               size_ratio_concat[k] * in_height / in_width / 2.0,
+                               size_ratio_concat[0] * in_height / in_width *
+                               math.sqrt(size_ratio_concat[k + 1]) / 2.0)
+                h = tvm.select(k < num_sizes, size_ratio_concat[k] / 2.0,
+                               size_ratio_concat[0] / math.sqrt(size_ratio_concat[k + 1]) / 2.0)
+                count = (i * in_width * (num_sizes + num_ratios - 1) +
+                         j * (num_sizes + num_ratios - 1) + k) * 4
+                p_out[count] = center_w - w
+                p_out[count + 1] = center_h - h
+                p_out[count + 2] = center_w + w
+                p_out[count + 3] = center_h + h
+
+    body = ib.get()
+    return body
+
+
+@multibox_prior.register(["cuda", "gpu"])
+def multibox_prior_gpu(data, sizes=(1,), ratios=(1,), steps=(-1, -1), \
+                       offsets=(0.5, 0.5), clip=False):
+    """Generate prior(anchor) boxes from data, sizes and ratios.
+
+    Parameters
+    ----------
+    data : tvm.Tensor
+        4-D with shape [batch, c_in, h_in, w_in]]
+
+    sizes : tuple of float
+        Tuple of sizes for anchor boxes.
+
+    ratios : tuple of float
+        Tuple of ratios for anchor boxes.
+
+    steps : Tuple of float
+        Priorbox step across y and x, -1 for auto calculation.
+
+    offsets : tuple of int
+        Priorbox center offsets, y and x respectively.
+
+    clip : boolean
+        Whether to clip out-of-boundary boxes.
+
+    Returns
+    -------
+    out : tvm.Tensor
+        3-D tensor with shape [1, h_in * w_in * (num_sizes + num_ratios - 1), 4]
+    """
+    num_sizes = len(sizes)
+    num_ratios = len(ratios)
+    oshape = (1, data.shape[2] * data.shape[3] * (num_sizes + num_ratios - 1), 4)
+    out = tvm.extern(oshape, [data], lambda ins, outs:
+                     multibox_prior_ir(ins[0], outs[0], sizes, ratios, steps, offsets),
+                     tag="multibox_prior")
+    if clip:
+        out = topi.clip(out, 0, 1)
+    return out
+
+
+def transform_loc_ir(cls_prob, loc_pred, anchor, valid_count, out, clip, threshold, variances):
+    """Low level IR routing for transform location in multibox_detection operator.
+
+    Parameters
+    ----------
+    cls_prob : Buffer
+        Buffer of class probabilities.
+
+    loc_pred : Buffer
+        Buffer of location regression predictions.
+
+    anchor : Buffer
+        Buffer of prior anchor boxes.
+
+    valid_count : Buffer
+        Buffer of number of valid output boxes.
+
+    out : Buffer
+        Output buffer.
+
+    clip : boolean
+        Whether to clip out-of-boundary boxes.
+
+    threshold : float
+        Threshold to be a positive prediction.
+
+    variances : tuple of float
+        Variances to be decoded from box regression output.
+
+    Returns
+    -------
+    stmt : Stmt
+        The result IR statement.
+    """
+    def transform_loc(loc, loc_base_idx, anchor, anchor_base_idx, clip, vx, vy, vw, vh):
+        """Transform prior anchor box to output box through location predictions.
+        """
+        al = anchor[anchor_base_idx]
+        at = anchor[anchor_base_idx + 1]
+        ar = anchor[anchor_base_idx + 2]
+        ab = anchor[anchor_base_idx + 3]
+        aw = ar - al
+        ah = ab - at
+        ax = (al + ar) / 2.0
+        ay = (at + ab) / 2.0
+        px = loc[loc_base_idx]
+        py = loc[loc_base_idx + 1]
+        pw = loc[loc_base_idx + 2]
+        ph = loc[loc_base_idx + 3]
+        ox = px * vx * aw + ax
+        oy = py * vy * ah + ay
+        ow = tvm.exp(pw * vw) * aw / 2.0
+        oh = tvm.exp(ph * vh) * ah / 2.0
+        return tvm.select(clip, tvm.make.Max(0, tvm.make.Min(1, ox - ow)), ox - ow), \
+               tvm.select(clip, tvm.make.Max(0, tvm.make.Min(1, oy - oh)), oy - oh), \
+               tvm.select(clip, tvm.make.Max(0, tvm.make.Min(1, ox + ow)), ox + ow), \
+               tvm.select(clip, tvm.make.Max(0, tvm.make.Min(1, oy + oh)), oy + oh)
+
+    batch_size = cls_prob.shape[0]
+    num_classes = cls_prob.shape[1]
+    num_anchors = cls_prob.shape[2]
+
+    ib = tvm.ir_builder.create()
+    temp_score = ib.allocate('float32', (batch_size * (num_classes -1) * num_anchors, \
+                 ), name="temp_score", scope="global")
+    score = ib.allocate('float32', (batch_size * num_anchors, ), name="score", scope="local")
+    cls_id = ib.allocate('int32', (batch_size * num_anchors, ), name="id", scope="local")
+    flag = ib.allocate('int32', (batch_size * num_anchors, ), name="flag", scope="global")
+    max_threads = int(tvm.target.current_target(allow_none=False).max_num_threads)
+    tx = tvm.thread_axis("threadIdx.x")
+    bx = tvm.thread_axis("blockIdx.x")
+    nthread_tx = max_threads
+    nthread_bx = (batch_size * num_anchors * num_classes) // max_threads + 1
+    ib.scope_attr(tx, "thread_extent", nthread_tx)
+    ib.scope_attr(bx, "thread_extent", nthread_bx)
+    tid = bx * max_threads + tx
+    p_cls_prob = ib.buffer_ptr(cls_prob)
+    p_loc_pred = ib.buffer_ptr(loc_pred)
+    p_anchor = ib.buffer_ptr(anchor)
+    p_valid_count = ib.buffer_ptr(valid_count)
+    p_out = ib.buffer_ptr(out)
+    with ib.if_scope(tid < batch_size * num_anchors * num_classes):
+        n = tid / (num_anchors * num_classes)
+        j = (tid % (num_anchors * num_classes)) / num_anchors
+        i = tid % num_anchors
+        with ib.if_scope(j > 0):
+            temp_score[n * num_anchors * num_classes + i * (num_classes - 1) + j-1] = \
+            p_cls_prob[tid]
+        p_valid_count[n] = 0
+    with ib.if_scope(tid < batch_size * num_anchors):
+        n = tid / num_anchors
+        i = tid % num_anchors
+        score[tid] = -1.0
+        cls_id[tid] = 0
+        with ib.for_range(0, num_classes-1, name="k") as k:
+            temp = temp_score[tid * (num_classes-1) + k]
+            cls_id[tid] = tvm.select(temp > score[tid], k + 1, cls_id[tid])
+            score[tid] = tvm.make.Max(temp, score[tid])
+        with ib.if_scope(tvm.all(cls_id[tid] > 0, score[tid] < threshold)):
+            cls_id[tid] = 0
+        with ib.if_scope(cls_id[tid] > 0):
+            flag[tid] = 1
+        with ib.else_scope():
+            flag[tid] = 0
+    with ib.if_scope(tid < batch_size):
+        with ib.for_range(0, num_anchors, name="k") as k:
+            with ib.if_scope(k > 0):
+                flag[tid * num_anchors + k] += flag[tid * num_anchors + k - 1]
+        p_valid_count[tid] = flag[tid * num_anchors + num_anchors - 1]
+    with ib.if_scope(tid < batch_size * num_anchors):
+        n = tid / num_anchors
+        i = tid % num_anchors
+        with ib.if_scope(cls_id[tid] > 0):
+            with ib.if_scope(tid == 0):
+                out_base_idx = n * num_anchors * 6
+            with ib.else_scope():
+                out_base_idx = n * num_anchors * 6 + flag[tid - 1] * 6
+            p_out[out_base_idx] = cls_id[tid] - 1.0
+            p_out[out_base_idx + 1] = score[tid]
+            p_out[out_base_idx + 2], p_out[out_base_idx + 3], p_out[out_base_idx + 4], \
+            p_out[out_base_idx + 5] = transform_loc(p_loc_pred, tid * 4, p_anchor, i*4,
+                                                    clip, variances[0], variances[1],
+                                                    variances[2], variances[3])
+
+    body = ib.get()
+    return body
+
+
+@multibox_transform_loc.register(["cuda", "gpu"])
+def multibox_transform_loc_gpu(cls_prob, loc_pred, anchor, clip=True, threshold=0.01,
+                               variances=(0.1, 0.1, 0.2, 0.2)):
+    """Location transformation for multibox detection
+
+    Parameters
+    ----------
+    cls_prob : tvm.Tensor
+        Class probabilities.
+
+    loc_pred : tvm.Tensor
+        Location regression predictions.
+
+    anchor : tvm.Tensor
+        Prior anchor boxes.
+
+    clip : boolean
+        Whether to clip out-of-boundary boxes.
+
+    threshold : float
+        Threshold to be a positive prediction.
+
+    variances : tuple of float
+        Variances to be decoded from box regression output.
+
+    Returns
+    -------
+    ret : tuple of tvm.Tensor composed of
+
+    out : tvm.Tensor
+        3-D tensor with shape (batch_size, num_anchors, 6)
+
+    valid_count : tvm.Tensor
+        1-D tensor with shape (batch_size,), number of valid anchor boxes.
+    """
+    batch_size = cls_prob.shape[0]
+    num_anchors = anchor.shape[1]
+    oshape = (batch_size, num_anchors, 6)
+    # Define data alignment for intermediate buffer
+    valid_count_dtype = "int32"
+    valid_count_buf = api.decl_buffer((batch_size,), valid_count_dtype,
+                                      "valid_count_buf", data_alignment=4)
+    out_buf = api.decl_buffer(oshape, cls_prob.dtype, "out_buf", data_alignment=8)
+    valid_count, out = \
+        tvm.extern([(batch_size,), oshape],
+                   [cls_prob, loc_pred, anchor],
+                   lambda ins, outs: transform_loc_ir(
+                       ins[0], ins[1], ins[2], outs[0], outs[1], clip, threshold, variances),
+                   dtype=[valid_count_dtype, cls_prob.dtype],
+                   out_buffers=[valid_count_buf, out_buf],
+                   tag="multibox_transform_loc")
+    return [out, valid_count]
+
+
+@multibox_detection.register(["cuda", "gpu"])
+def multibox_detection_gpu(cls_prob, loc_pred, anchor, clip=True, threshold=0.01, nms_threshold=0.5,
+                           force_suppress=False, variances=(0.1, 0.1, 0.2, 0.2), nms_topk=-1):
+    """Convert multibox detection predictions.
+
+    Parameters
+    ----------
+    cls_prob : tvm.Tensor
+        Class probabilities.
+
+    loc_pred : tvm.Tensor
+        Location regression predictions.
+
+    anchor : tvm.Tensor
+        Prior anchor boxes.
+
+    clip : boolean
+        Whether to clip out-of-boundary boxes.
+
+    nms_threshold : float
+        Non-maximum suppression threshold.
+
+    force_suppress : boolean
+        Whether to suppress all detections regardless of class_id.
+
+    threshold : float
+        Threshold to be a positive prediction.
+
+    variances : tuple of float
+        Variances to be decoded from box regression output.
+
+    nms_topk : int
+        Keep maximum top k detections before nms, -1 for no limit.
+
+    Returns
+    -------
+    out : tvm.Tensor
+        3-D tensor with shape (batch_size, num_anchors, 6)
+    """
+    inter_out = multibox_transform_loc(cls_prob, loc_pred, anchor,
+                                       clip, threshold, variances)
+    out = nms(inter_out[0], inter_out[1], nms_threshold, force_suppress, nms_topk)
+    return out
diff --git a/topi/python/topi/cuda/vision.py b/topi/python/topi/cuda/vision.py
index 106d13665ad8..c5d94b5ab4de 100644
--- a/topi/python/topi/cuda/vision.py
+++ b/topi/python/topi/cuda/vision.py
@@ -1,9 +1,42 @@
-# pylint: disable=invalid-name, unused-variable, unused-argument
+# pylint: disable=invalid-name, unused-variable, unused-argument, no-member
 """Schedule for vision operators"""
 from __future__ import absolute_import as _abs
 import tvm
 from .. import generic
 from .. import cpp
+from .. import tag
+
+def _default_schedule(outs):
+    """Default schedule for gpu."""
+    target = tvm.target.current_target()
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    s = tvm.create_schedule([x.op for x in outs])
+    def traverse(op):
+        """inline all one-to-one-mapping operators except the last stage (output)"""
+        if "nms" in op.tag:
+            sort = op.input_tensors[1]
+            score = s[sort].op.input_tensors[0]
+            fused = s[score].fuse(*s[score].op.axis)
+            num_thread = tvm.target.current_target(allow_none=False).max_num_threads
+            bx, tx = s[score].split(fused, factor=num_thread)
+            s[score].bind(bx, tvm.thread_axis("blockIdx.x"))
+            s[score].bind(tx, tvm.thread_axis("threadIdx.x"))
+        if tag.is_broadcast(op.tag):
+            if op not in s.outputs:
+                s[op].compute_inline()
+            else:
+                x = op.output(0)
+                fused = s[x].fuse(*s[x].op.axis)
+                num_thread = tvm.target.current_target(allow_none=False).max_num_threads
+                bx, tx = s[x].split(fused, factor=num_thread)
+                s[x].bind(bx, tvm.thread_axis("blockIdx.x"))
+                s[x].bind(tx, tvm.thread_axis("threadIdx.x"))
+            for tensor in op.input_tensors:
+                if tensor.op.input_tensors:
+                    traverse(tensor.op)
+
+    traverse(outs[0].op)
+    return s
 
 @generic.schedule_reorg.register(["cuda", "gpu"])
 def schedule_reorg(outs):
@@ -41,8 +74,25 @@ def schedule_region(outs):
     cpp_target = cpp.TEST_create_target(target.target_name)
     return cpp.cuda.schedule_region(cpp_target, outs)
 
+@generic.schedule_nms.register(["cuda", "gpu"])
+def schedule_nms(outs):
+    """Schedule for non-maximum suppression
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+      The computation graph description of nms
+      in the format of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+      The computation schedule for the op.
+    """
+    return _default_schedule(outs)
+
 @generic.schedule_multibox_prior.register(["cuda", "gpu"])
-def schedule_multibox_prior(out):
+def schedule_multibox_prior(outs):
     """Schedule for multibox_prior operator.
 
     Parameters
@@ -56,10 +106,28 @@ def schedule_multibox_prior(out):
     s: Schedule
         The computation schedule for multibox_prior.
     """
-    raise RuntimeError("Currently multibox_prior only supports CPU.")
+    return _default_schedule(outs)
+
+@generic.schedule_multibox_transform_loc.register(["cuda", "gpu"])
+def schedule_multibox_transform_loc(outs):
+    """Schedule for multibox_transform_loc
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+      The computation graph description of
+      multibox_transform_loc in the format
+      of an array of tensors.
+
+    Returns
+    -------
+    s: Schedule
+      The computation schedule for the op.
+    """
+    return _default_schedule(outs)
 
 @generic.schedule_multibox_detection.register(["cuda", "gpu"])
-def schedule_multibox_detection(out):
+def schedule_multibox_detection(outs):
     """Schedule for multibox_detection operator.
 
     Parameters
@@ -73,4 +141,4 @@ def schedule_multibox_detection(out):
     s: Schedule
         The computation schedule for multibox_detection.
     """
-    raise RuntimeError("Currently multibox_detection only supports CPU.")
+    return _default_schedule(outs)
diff --git a/topi/tests/python/test_topi_clip.py b/topi/tests/python/test_topi_clip.py
index 52da4922e1d6..041565433bcc 100644
--- a/topi/tests/python/test_topi_clip.py
+++ b/topi/tests/python/test_topi_clip.py
@@ -20,23 +20,27 @@ def get_ref_data():
     a_np, b_np = get_ref_data()
 
     def check_device(device):
-        if not tvm.module.enabled(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
             print("Skip because %s is not enabled" % device)
             return
-        ctx = tvm.cpu(0) if device == "llvm" else tvm.gpu(0)
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s = topi.generic.schedule_injective(B)
+
         a = tvm.nd.array(a_np, ctx)
         b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=dtype), ctx)
         f = tvm.build(s, [A, B], device, name="clip")
         f(a, b)
         np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
 
-    for device in ['llvm']:
+    for device in ['llvm', 'opencl']:
         check_device(device)
 
 def test_clip():
-    verify_clip(1024, -127, 127, 'int8')
-    verify_clip(1024, -127, 127, 'int16')
     verify_clip(1024, -127, 127, 'float32')
+    verify_clip(1024, -127, 127, 'int16')
+    verify_clip(1024, -127, 127, 'int8')
 
 
 if __name__ == "__main__":
diff --git a/topi/tests/python/test_topi_conv2d_nchw.py b/topi/tests/python/test_topi_conv2d_nchw.py
index 0d55a9163466..7f3935f3aad7 100644
--- a/topi/tests/python/test_topi_conv2d_nchw.py
+++ b/topi/tests/python/test_topi_conv2d_nchw.py
@@ -8,6 +8,8 @@
 from topi.util import get_const_tuple
 
 def verify_conv2d_nchw(batch, in_channel, in_size, num_filter, kernel, stride, padding, dilation=1):
+    print("Workload: (%d, %d, %d, %d, %d, %d, %d)" % (batch, in_channel, in_size, num_filter, kernel, stride, padding))
+
     in_height = in_width = in_size
 
     A = tvm.placeholder((batch, in_channel, in_height, in_width), name='A')
@@ -59,7 +61,7 @@ def check_device(device):
 
 
 def test_conv2d_nchw():
-    # ResNet18 worklaods
+    # ResNet18 workloads
     verify_conv2d_nchw(1, 3, 224, 64, 7, 2, 3)
     verify_conv2d_nchw(1, 64, 56, 64, 3, 1, 1)
     verify_conv2d_nchw(1, 64, 56, 64, 1, 1, 0)
@@ -72,6 +74,21 @@ def test_conv2d_nchw():
     verify_conv2d_nchw(1, 256, 14, 512, 3, 2, 1)
     verify_conv2d_nchw(1, 256, 14, 512, 1, 2, 0)
     verify_conv2d_nchw(1, 512, 7, 512, 3, 1, 1)
+    # ResNet 50 workloads
+    verify_conv2d_nchw(1, 64, 56, 256, 1, 1, 0)
+    verify_conv2d_nchw(1, 256, 56, 64, 1, 1, 0)
+    verify_conv2d_nchw(1, 256, 56, 128, 1, 2, 0)
+    verify_conv2d_nchw(1, 128, 28, 512, 1, 1, 0)
+    verify_conv2d_nchw(1, 256, 56, 512, 1, 2, 0)
+    verify_conv2d_nchw(1, 512, 28, 128, 1, 1, 0)
+    verify_conv2d_nchw(1, 512, 28, 256, 1, 2, 0)
+    verify_conv2d_nchw(1, 256, 14, 1024, 1, 1, 0)
+    verify_conv2d_nchw(1, 512, 28, 1024, 1, 2, 0)
+    verify_conv2d_nchw(1, 1024, 14, 256, 1, 1, 0)
+    verify_conv2d_nchw(1, 1024, 14, 512, 1, 2, 0)
+    verify_conv2d_nchw(1, 512, 7, 2048, 1, 2, 0)
+    verify_conv2d_nchw(1, 1024, 14, 2048, 1, 2, 0)
+    verify_conv2d_nchw(1, 2048, 7, 512, 1, 1, 0)
     # Vgg16 workloads
     verify_conv2d_nchw(1, 128, 122, 128, 3, 1, 1)
     # Super resolution workloads
diff --git a/topi/tests/python/test_topi_vision.py b/topi/tests/python/test_topi_vision.py
index 3c624726d562..959b10f82ca5 100644
--- a/topi/tests/python/test_topi_vision.py
+++ b/topi/tests/python/test_topi_vision.py
@@ -14,7 +14,6 @@ def test_nms():
     nms_threshold = 0.7
     force_suppress = True
     nms_topk = 2
-    out = nms(data, valid_count, nms_threshold, force_suppress, nms_topk)
 
     np_data = np.array([[[0, 0.8, 1, 20, 25, 45], [1, 0.7, 30, 60, 50, 80],
                          [0, 0.4, 4, 21, 19, 40], [2, 0.9, 35, 61, 52, 79],
@@ -31,6 +30,10 @@ def check_device(device):
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
+            if device == 'llvm':
+                out = nms(data, valid_count, nms_threshold, force_suppress, nms_topk)
+            else:
+                out = topi.cuda.nms(data, valid_count, nms_threshold, force_suppress, nms_topk)
             s = topi.generic.schedule_nms(out)
 
         tvm_data = tvm.nd.array(np_data, ctx)
@@ -40,13 +43,12 @@ def check_device(device):
         f(tvm_data, tvm_valid_count, tvm_out)
         np.testing.assert_allclose(tvm_out.asnumpy(), np_result, rtol=1e-4)
 
-    for device in ['llvm']:
+    for device in ['llvm', 'opencl']:
         check_device(device)
 
 
 def verify_multibox_prior(dshape, sizes=(1,), ratios=(1,), steps=(-1, -1), offsets=(0.5, 0.5), clip=False):
     data = tvm.placeholder(dshape, name="data")
-    out = ssd.multibox_prior(data, sizes, ratios, steps, offsets, clip)
 
     dtype = data.dtype
     input_data = np.random.uniform(size=dshape).astype(dtype)
@@ -88,15 +90,19 @@ def check_device(device):
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
+            if device == 'llvm':
+                out = ssd.multibox_prior(data, sizes, ratios, steps, offsets, clip)
+            else:
+                out = topi.cuda.ssd.multibox_prior(data, sizes, ratios, steps, offsets, clip)
             s = topi.generic.schedule_multibox_prior(out)
 
         tvm_input_data = tvm.nd.array(input_data, ctx)
         tvm_out = tvm.nd.array(np.zeros(oshape, dtype=dtype), ctx)
         f = tvm.build(s, [data, out], device)
         f(tvm_input_data, tvm_out)
-        np.testing.assert_allclose(tvm_out.asnumpy(), np_out, rtol=1e-4)
+        np.testing.assert_allclose(tvm_out.asnumpy(), np_out, rtol=1e-3)
 
-    for device in ['llvm']:
+    for device in ['llvm', 'opencl']:
         check_device(device)
 
 
@@ -113,7 +119,6 @@ def test_multibox_detection():
     cls_prob = tvm.placeholder((batch_size, num_anchors, num_classes), name="cls_prob")
     loc_preds = tvm.placeholder((batch_size, num_anchors * 4), name="loc_preds")
     anchors = tvm.placeholder((1, num_anchors, 4), name="anchors")
-    out = ssd.multibox_detection(cls_prob, loc_preds, anchors)
 
     # Manually create test case
     np_cls_prob = np.array([[[0.2, 0.5, 0.3], [0.25, 0.3, 0.45], [0.7, 0.1, 0.2]]])
@@ -131,6 +136,10 @@ def check_device(device):
             return
         print("Running on target: %s" % device)
         with tvm.target.create(device):
+            if device == 'llvm':
+                out = ssd.multibox_detection(cls_prob, loc_preds, anchors)
+            else:
+                out = topi.cuda.ssd.multibox_detection(cls_prob, loc_preds, anchors)
             s = topi.generic.schedule_multibox_detection(out)
 
         tvm_cls_prob = tvm.nd.array(np_cls_prob.astype(cls_prob.dtype), ctx)
@@ -141,11 +150,11 @@ def check_device(device):
         f(tvm_cls_prob, tvm_loc_preds, tvm_anchors, tvm_out)
         np.testing.assert_allclose(tvm_out.asnumpy(), expected_np_out, rtol=1e-4)
 
-    for device in ['llvm']:
+    for device in ['llvm', 'opencl']:
         check_device(device)
 
 
 if __name__ == "__main__":
     test_nms()
     test_multibox_prior()
-    test_multibox_detection()
\ No newline at end of file
+    test_multibox_detection()

From 90cf33030cb8655eb6240813c9320f85e838de7b Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Sat, 7 Jul 2018 21:34:03 -0700
Subject: [PATCH 801/949] [DOCKER] Fix dependency for autotvm (#1398)

---
 tests/ci_build/Dockerfile.cpu                  | 4 ++++
 tests/ci_build/Dockerfile.i386                 | 4 ++++
 tests/ci_build/install/ubuntu_install_redis.sh | 4 ++--
 3 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/tests/ci_build/Dockerfile.cpu b/tests/ci_build/Dockerfile.cpu
index d749afd752df..f29ef190328d 100644
--- a/tests/ci_build/Dockerfile.cpu
+++ b/tests/ci_build/Dockerfile.cpu
@@ -14,3 +14,7 @@ RUN bash /install/ubuntu_install_python_package.sh
 
 COPY install/ubuntu_install_llvm.sh /install/ubuntu_install_llvm.sh
 RUN bash /install/ubuntu_install_llvm.sh
+
+# AutoTVM deps
+COPY install/ubuntu_install_redis.sh /install/ubuntu_install_redis.sh
+RUN bash /install/ubuntu_install_redis.sh
diff --git a/tests/ci_build/Dockerfile.i386 b/tests/ci_build/Dockerfile.i386
index e4577c37edd1..71e151128655 100644
--- a/tests/ci_build/Dockerfile.i386
+++ b/tests/ci_build/Dockerfile.i386
@@ -13,3 +13,7 @@ RUN bash /install/ubuntu_install_python.sh
 
 COPY install/ubuntu_install_python_package.sh /install/ubuntu_install_python_package.sh
 RUN bash /install/ubuntu_install_python_package.sh
+
+# AutoTVM deps
+COPY install/ubuntu_install_redis.sh /install/ubuntu_install_redis.sh
+RUN bash /install/ubuntu_install_redis.sh
diff --git a/tests/ci_build/install/ubuntu_install_redis.sh b/tests/ci_build/install/ubuntu_install_redis.sh
index acb022a8b093..dfc9a3c381b6 100644
--- a/tests/ci_build/install/ubuntu_install_redis.sh
+++ b/tests/ci_build/install/ubuntu_install_redis.sh
@@ -1,3 +1,3 @@
 apt-get update && apt-get install -y redis-server
-pip2 install redis psutil
-pip3 install redis psutil
+pip2 install xgboost psutil
+pip3 install xgboost psutil

From befe802dffbe98e5f12497c5ba3a35d897a51a49 Mon Sep 17 00:00:00 2001
From: MORITA Kazutaka <morita.kazutaka@lab.ntt.co.jp>
Date: Mon, 9 Jul 2018 02:48:26 +0900
Subject: [PATCH 802/949] [NNVM][Keras] allow only tensorflow backend (#1392)

---
 nnvm/python/nnvm/frontend/keras.py               | 2 ++
 nnvm/tests/python/frontend/keras/test_forward.py | 3 +++
 2 files changed, 5 insertions(+)

diff --git a/nnvm/python/nnvm/frontend/keras.py b/nnvm/python/nnvm/frontend/keras.py
index 2624ca0ffffc..5481a796ed12 100644
--- a/nnvm/python/nnvm/frontend/keras.py
+++ b/nnvm/python/nnvm/frontend/keras.py
@@ -489,6 +489,8 @@ def from_keras(model):
         raise ImportError('Keras must be installed')
 
     assert isinstance(model, keras.engine.training.Model)
+    if keras.backend.backend() != 'tensorflow':
+        raise ValueError("Keras frontend currently supports tensorflow backend only.")
     if keras.backend.image_data_format() != 'channels_last':
         raise ValueError("Keras frontend currently supports data_format = channels_last only.")
     _check_unsupported_layers(model)
diff --git a/nnvm/tests/python/frontend/keras/test_forward.py b/nnvm/tests/python/frontend/keras/test_forward.py
index 14ffeca2b282..ab56698c085d 100644
--- a/nnvm/tests/python/frontend/keras/test_forward.py
+++ b/nnvm/tests/python/frontend/keras/test_forward.py
@@ -14,6 +14,9 @@
 
 
 def verify_keras_frontend(keras_model):
+    # Keras frontend currently supports tensorflow backend only.
+    assert(keras.backend.backend() == 'tensorflow')
+
     in_shapes = []
     for layer in keras_model._input_layers:
         in_shapes.append(tuple(dim.value if dim.value is not None else 1 for dim in layer.input.shape))

From 5642bc1856a215d6cd6d4ac064dd292b3b510b62 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 8 Jul 2018 18:56:52 -0700
Subject: [PATCH 803/949] [DOCKER] Start docker infratructure (#1402)

---
 Jenkinsfile                                   | 32 +++++-----
 .../Dockerfile.ci_cpu                         |  0
 .../Dockerfile.ci_emscripten                  |  0
 .../Dockerfile.ci_gpu                         |  0
 .../Dockerfile.ci_i386                        |  0
 .../Dockerfile.ci_lint                        |  0
 docker/README.md                              | 58 +++++++++++++++++++
 tests/ci_build/ci_build.sh => docker/build.sh |  6 +-
 .../install/ubuntu_install_core.sh            |  0
 .../install/ubuntu_install_coreml.sh          |  0
 .../install/ubuntu_install_darknet.sh         |  0
 .../install/ubuntu_install_emscripten.sh      |  0
 .../install/ubuntu_install_iverilog.sh        |  0
 .../install/ubuntu_install_java.sh            |  0
 .../install/ubuntu_install_keras.sh           |  0
 .../install/ubuntu_install_llvm.sh            |  0
 .../install/ubuntu_install_mxnet.sh           |  0
 .../install/ubuntu_install_nodejs.sh          |  0
 .../install/ubuntu_install_onnx.sh            |  0
 .../install/ubuntu_install_opencl.sh          |  0
 .../install/ubuntu_install_opengl.sh          |  0
 .../install/ubuntu_install_python.sh          |  0
 .../install/ubuntu_install_python_package.sh  |  0
 .../install/ubuntu_install_redis.sh           |  0
 .../install/ubuntu_install_rocm.sh            |  0
 .../install/ubuntu_install_sphinx.sh          |  0
 .../install/ubuntu_install_vulkan.sh          |  0
 {tests/ci_build => docker}/with_the_same_user |  0
 tests/ci_build/README.md                      | 36 ------------
 29 files changed, 77 insertions(+), 55 deletions(-)
 rename tests/ci_build/Dockerfile.cpu => docker/Dockerfile.ci_cpu (100%)
 rename tests/ci_build/Dockerfile.emscripten => docker/Dockerfile.ci_emscripten (100%)
 rename tests/ci_build/Dockerfile.gpu => docker/Dockerfile.ci_gpu (100%)
 rename tests/ci_build/Dockerfile.i386 => docker/Dockerfile.ci_i386 (100%)
 rename tests/ci_build/Dockerfile.lint => docker/Dockerfile.ci_lint (100%)
 create mode 100644 docker/README.md
 rename tests/ci_build/ci_build.sh => docker/build.sh (97%)
 rename {tests/ci_build => docker}/install/ubuntu_install_core.sh (100%)
 rename {tests/ci_build => docker}/install/ubuntu_install_coreml.sh (100%)
 rename {tests/ci_build => docker}/install/ubuntu_install_darknet.sh (100%)
 rename {tests/ci_build => docker}/install/ubuntu_install_emscripten.sh (100%)
 rename {tests/ci_build => docker}/install/ubuntu_install_iverilog.sh (100%)
 rename {tests/ci_build => docker}/install/ubuntu_install_java.sh (100%)
 rename {tests/ci_build => docker}/install/ubuntu_install_keras.sh (100%)
 rename {tests/ci_build => docker}/install/ubuntu_install_llvm.sh (100%)
 rename {tests/ci_build => docker}/install/ubuntu_install_mxnet.sh (100%)
 rename {tests/ci_build => docker}/install/ubuntu_install_nodejs.sh (100%)
 rename {tests/ci_build => docker}/install/ubuntu_install_onnx.sh (100%)
 rename {tests/ci_build => docker}/install/ubuntu_install_opencl.sh (100%)
 rename {tests/ci_build => docker}/install/ubuntu_install_opengl.sh (100%)
 rename {tests/ci_build => docker}/install/ubuntu_install_python.sh (100%)
 rename {tests/ci_build => docker}/install/ubuntu_install_python_package.sh (100%)
 rename {tests/ci_build => docker}/install/ubuntu_install_redis.sh (100%)
 rename {tests/ci_build => docker}/install/ubuntu_install_rocm.sh (100%)
 rename {tests/ci_build => docker}/install/ubuntu_install_sphinx.sh (100%)
 rename {tests/ci_build => docker}/install/ubuntu_install_vulkan.sh (100%)
 rename {tests/ci_build => docker}/with_the_same_user (100%)
 delete mode 100644 tests/ci_build/README.md

diff --git a/Jenkinsfile b/Jenkinsfile
index b46632a960d6..5e5837151497 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -11,7 +11,7 @@ tvm_multilib = "build/libtvm.so, " +
              "build/libtvm_topi.so, build/libnnvm_compiler.so, " + tvm_runtime
 
 // command to start a docker container
-docker_run = 'tests/ci_build/ci_build.sh'
+docker_run = 'docker/build.sh'
 // timeout in minutes
 max_time = 60
 
@@ -39,7 +39,7 @@ stage("Sanity Check") {
     node('linux') {
       ws('workspace/tvm/sanity') {
         init_git()
-        sh "${docker_run} lint  ./tests/scripts/task_lint.sh"
+        sh "${docker_run} ci_lint  ./tests/scripts/task_lint.sh"
       }
     }
   }
@@ -100,7 +100,7 @@ stage('Build') {
            echo set\\(CMAKE_CXX_COMPILER g++\\) >> config.cmake
            echo set\\(CMAKE_CXX_FLAGS -Werror\\) >> config.cmake
            """
-        make('gpu', 'build', '-j2')
+        make('ci_gpu', 'build', '-j2')
         pack_lib('gpu', tvm_multilib)
         // compiler test
         sh """
@@ -113,7 +113,7 @@ stage('Build') {
            echo set\\(CMAKE_CXX_COMPILER clang-6.0\\) >> config.cmake
            echo set\\(CMAKE_CXX_FLAGS -Werror\\) >> config.cmake
            """
-        make('gpu', 'build2', '-j2')
+        make('ci_gpu', 'build2', '-j2')
       }
     }
   },
@@ -130,10 +130,10 @@ stage('Build') {
            echo set\\(CMAKE_CXX_COMPILER g++\\) >> config.cmake
            echo set\\(CMAKE_CXX_FLAGS -Werror\\) >> config.cmake
            """
-        make('cpu', 'build', '-j2')
+        make('ci_cpu', 'build', '-j2')
         pack_lib('cpu', tvm_lib)
         timeout(time: max_time, unit: 'MINUTES') {
-          sh "${docker_run} cpu ./tests/scripts/task_cpp_unittest.sh"
+          sh "${docker_run} ci_cpu ./tests/scripts/task_cpp_unittest.sh"
         }
       }
     }
@@ -152,7 +152,7 @@ stage('Build') {
            echo set\\(CMAKE_CXX_COMPILER g++\\) >> config.cmake
            echo set\\(CMAKE_CXX_FLAGS -Werror\\) >> config.cmake
            """
-        make('i386', 'build', '-j2')
+        make('ci_i386', 'build', '-j2')
         pack_lib('i386', tvm_multilib)
       }
     }
@@ -166,7 +166,7 @@ stage('Unit Test') {
         init_git()
         unpack_lib('gpu', tvm_multilib)
         timeout(time: max_time, unit: 'MINUTES') {
-          sh "${docker_run} gpu ./tests/scripts/task_python_unittest.sh"
+          sh "${docker_run} ci_gpu ./tests/scripts/task_python_unittest.sh"
         }
       }
     }
@@ -177,8 +177,8 @@ stage('Unit Test') {
         init_git()
         unpack_lib('i386', tvm_multilib)
         timeout(time: max_time, unit: 'MINUTES') {
-          sh "${docker_run} i386 ./tests/scripts/task_python_unittest.sh"
-          sh "${docker_run} i386 ./tests/scripts/task_python_integration.sh"
+          sh "${docker_run} ci_i386 ./tests/scripts/task_python_unittest.sh"
+          sh "${docker_run} ci_i386 ./tests/scripts/task_python_integration.sh"
         }
       }
     }
@@ -189,7 +189,7 @@ stage('Unit Test') {
         init_git()
         unpack_lib('gpu', tvm_multilib)
         timeout(time: max_time, unit: 'MINUTES') {
-          sh "${docker_run} gpu ./tests/scripts/task_java_unittest.sh"
+          sh "${docker_run} ci_gpu ./tests/scripts/task_java_unittest.sh"
         }
       }
     }
@@ -203,10 +203,10 @@ stage('Integration Test') {
         init_git()
         unpack_lib('gpu', tvm_multilib)
         timeout(time: max_time, unit: 'MINUTES') {
-          sh "${docker_run} gpu ./tests/scripts/task_python_integration.sh"
-          sh "${docker_run} gpu ./tests/scripts/task_python_topi.sh"
-          sh "${docker_run} gpu ./tests/scripts/task_cpp_topi.sh"
-          sh "${docker_run} gpu ./tests/scripts/task_python_nnvm.sh"
+          sh "${docker_run} ci_gpu ./tests/scripts/task_python_integration.sh"
+          sh "${docker_run} ci_gpu ./tests/scripts/task_python_topi.sh"
+          sh "${docker_run} ci_gpu ./tests/scripts/task_cpp_topi.sh"
+          sh "${docker_run} ci_gpu ./tests/scripts/task_python_nnvm.sh"
         }
       }
     }
@@ -217,7 +217,7 @@ stage('Integration Test') {
         init_git()
         unpack_lib('gpu', tvm_multilib)
         timeout(time: max_time, unit: 'MINUTES') {
-          sh "${docker_run} gpu ./tests/scripts/task_python_docs.sh"
+          sh "${docker_run} ci_gpu ./tests/scripts/task_python_docs.sh"
         }
         pack_lib('mydocs', 'docs.tgz')
       }
diff --git a/tests/ci_build/Dockerfile.cpu b/docker/Dockerfile.ci_cpu
similarity index 100%
rename from tests/ci_build/Dockerfile.cpu
rename to docker/Dockerfile.ci_cpu
diff --git a/tests/ci_build/Dockerfile.emscripten b/docker/Dockerfile.ci_emscripten
similarity index 100%
rename from tests/ci_build/Dockerfile.emscripten
rename to docker/Dockerfile.ci_emscripten
diff --git a/tests/ci_build/Dockerfile.gpu b/docker/Dockerfile.ci_gpu
similarity index 100%
rename from tests/ci_build/Dockerfile.gpu
rename to docker/Dockerfile.ci_gpu
diff --git a/tests/ci_build/Dockerfile.i386 b/docker/Dockerfile.ci_i386
similarity index 100%
rename from tests/ci_build/Dockerfile.i386
rename to docker/Dockerfile.ci_i386
diff --git a/tests/ci_build/Dockerfile.lint b/docker/Dockerfile.ci_lint
similarity index 100%
rename from tests/ci_build/Dockerfile.lint
rename to docker/Dockerfile.ci_lint
diff --git a/docker/README.md b/docker/README.md
new file mode 100644
index 000000000000..c39cd5bd9c60
--- /dev/null
+++ b/docker/README.md
@@ -0,0 +1,58 @@
+# TVM Docker
+
+This directory contains the TVM's docker infrastructure.
+We use docker to quickly provide environments that can be
+used to build tvm in various settings.
+
+To run locally, we need to first install
+[docker](https://docs.docker.com/engine/installation/) and
+[nvidia-docker](https://github.com/NVIDIA/nvidia-docker/).
+
+
+## Use Local Build Script
+
+Each dockerfile defines a different environment.
+We use (`build.sh`)[./build.sh] to build and run the commands.
+To use the docker images, we can run the following command
+at the root of the project.
+
+```bash
+./docker/build.sh image_name [command]
+```
+
+Here image_name corresponds to the docker defined in the
+```Dockerfile.image_name```.
+
+You can also start an interactive session by typing
+
+```bash
+./docker/build.sh image_name -it bash
+```
+
+The build command will map the tvm root to /workspace/ inside the container
+with the same user as the user invoking the docker command.
+Here are some common use examples to perform CI tasks.
+
+- lint the python codes
+
+  ```bash
+    ./docker/build.sh ci_lint make pylint
+      ```
+
+- build codes with CUDA support
+
+  ```bash
+    ./docker/build.sh ci_gpu make -j$(nproc)
+      ```
+
+- do the python unittest
+
+  ```bash
+    ./docker/build.sh ci_gpu tests/scripts/task_python_unittest.sh'
+      ```
+
+- build the documents. The results will be available at `docs/_build/html`
+
+  ```bash
+    ./docker/ci_build.sh ci_gpu make -C docs html
+  ```
diff --git a/tests/ci_build/ci_build.sh b/docker/build.sh
similarity index 97%
rename from tests/ci_build/ci_build.sh
rename to docker/build.sh
index 86c138aaf3a5..ccecd0a13834 100755
--- a/tests/ci_build/ci_build.sh
+++ b/docker/build.sh
@@ -71,8 +71,8 @@ function upsearch () {
 
 # Set up WORKSPACE and BUILD_TAG. Jenkins will set them for you or we pick
 # reasonable defaults if you run it outside of Jenkins.
-WORKSPACE="${WORKSPACE:-${SCRIPT_DIR}/../../}"
-BUILD_TAG="${BUILD_TAG:-tvm-ci}"
+WORKSPACE="${WORKSPACE:-${SCRIPT_DIR}/../}"
+BUILD_TAG="${BUILD_TAG:-tvm}"
 
 # Determine the docker image name
 DOCKER_IMG_NAME="${BUILD_TAG}.${CONTAINER_TYPE}"
@@ -122,5 +122,5 @@ ${DOCKER_BINARY} run --rm --pid=host \
     -e "CI_BUILD_GID=$(id -g)" \
     ${CI_DOCKER_EXTRA_PARAMS[@]} \
     ${DOCKER_IMG_NAME} \
-    bash tests/ci_build/with_the_same_user \
+    bash docker/with_the_same_user \
     ${COMMAND[@]}
diff --git a/tests/ci_build/install/ubuntu_install_core.sh b/docker/install/ubuntu_install_core.sh
similarity index 100%
rename from tests/ci_build/install/ubuntu_install_core.sh
rename to docker/install/ubuntu_install_core.sh
diff --git a/tests/ci_build/install/ubuntu_install_coreml.sh b/docker/install/ubuntu_install_coreml.sh
similarity index 100%
rename from tests/ci_build/install/ubuntu_install_coreml.sh
rename to docker/install/ubuntu_install_coreml.sh
diff --git a/tests/ci_build/install/ubuntu_install_darknet.sh b/docker/install/ubuntu_install_darknet.sh
similarity index 100%
rename from tests/ci_build/install/ubuntu_install_darknet.sh
rename to docker/install/ubuntu_install_darknet.sh
diff --git a/tests/ci_build/install/ubuntu_install_emscripten.sh b/docker/install/ubuntu_install_emscripten.sh
similarity index 100%
rename from tests/ci_build/install/ubuntu_install_emscripten.sh
rename to docker/install/ubuntu_install_emscripten.sh
diff --git a/tests/ci_build/install/ubuntu_install_iverilog.sh b/docker/install/ubuntu_install_iverilog.sh
similarity index 100%
rename from tests/ci_build/install/ubuntu_install_iverilog.sh
rename to docker/install/ubuntu_install_iverilog.sh
diff --git a/tests/ci_build/install/ubuntu_install_java.sh b/docker/install/ubuntu_install_java.sh
similarity index 100%
rename from tests/ci_build/install/ubuntu_install_java.sh
rename to docker/install/ubuntu_install_java.sh
diff --git a/tests/ci_build/install/ubuntu_install_keras.sh b/docker/install/ubuntu_install_keras.sh
similarity index 100%
rename from tests/ci_build/install/ubuntu_install_keras.sh
rename to docker/install/ubuntu_install_keras.sh
diff --git a/tests/ci_build/install/ubuntu_install_llvm.sh b/docker/install/ubuntu_install_llvm.sh
similarity index 100%
rename from tests/ci_build/install/ubuntu_install_llvm.sh
rename to docker/install/ubuntu_install_llvm.sh
diff --git a/tests/ci_build/install/ubuntu_install_mxnet.sh b/docker/install/ubuntu_install_mxnet.sh
similarity index 100%
rename from tests/ci_build/install/ubuntu_install_mxnet.sh
rename to docker/install/ubuntu_install_mxnet.sh
diff --git a/tests/ci_build/install/ubuntu_install_nodejs.sh b/docker/install/ubuntu_install_nodejs.sh
similarity index 100%
rename from tests/ci_build/install/ubuntu_install_nodejs.sh
rename to docker/install/ubuntu_install_nodejs.sh
diff --git a/tests/ci_build/install/ubuntu_install_onnx.sh b/docker/install/ubuntu_install_onnx.sh
similarity index 100%
rename from tests/ci_build/install/ubuntu_install_onnx.sh
rename to docker/install/ubuntu_install_onnx.sh
diff --git a/tests/ci_build/install/ubuntu_install_opencl.sh b/docker/install/ubuntu_install_opencl.sh
similarity index 100%
rename from tests/ci_build/install/ubuntu_install_opencl.sh
rename to docker/install/ubuntu_install_opencl.sh
diff --git a/tests/ci_build/install/ubuntu_install_opengl.sh b/docker/install/ubuntu_install_opengl.sh
similarity index 100%
rename from tests/ci_build/install/ubuntu_install_opengl.sh
rename to docker/install/ubuntu_install_opengl.sh
diff --git a/tests/ci_build/install/ubuntu_install_python.sh b/docker/install/ubuntu_install_python.sh
similarity index 100%
rename from tests/ci_build/install/ubuntu_install_python.sh
rename to docker/install/ubuntu_install_python.sh
diff --git a/tests/ci_build/install/ubuntu_install_python_package.sh b/docker/install/ubuntu_install_python_package.sh
similarity index 100%
rename from tests/ci_build/install/ubuntu_install_python_package.sh
rename to docker/install/ubuntu_install_python_package.sh
diff --git a/tests/ci_build/install/ubuntu_install_redis.sh b/docker/install/ubuntu_install_redis.sh
similarity index 100%
rename from tests/ci_build/install/ubuntu_install_redis.sh
rename to docker/install/ubuntu_install_redis.sh
diff --git a/tests/ci_build/install/ubuntu_install_rocm.sh b/docker/install/ubuntu_install_rocm.sh
similarity index 100%
rename from tests/ci_build/install/ubuntu_install_rocm.sh
rename to docker/install/ubuntu_install_rocm.sh
diff --git a/tests/ci_build/install/ubuntu_install_sphinx.sh b/docker/install/ubuntu_install_sphinx.sh
similarity index 100%
rename from tests/ci_build/install/ubuntu_install_sphinx.sh
rename to docker/install/ubuntu_install_sphinx.sh
diff --git a/tests/ci_build/install/ubuntu_install_vulkan.sh b/docker/install/ubuntu_install_vulkan.sh
similarity index 100%
rename from tests/ci_build/install/ubuntu_install_vulkan.sh
rename to docker/install/ubuntu_install_vulkan.sh
diff --git a/tests/ci_build/with_the_same_user b/docker/with_the_same_user
similarity index 100%
rename from tests/ci_build/with_the_same_user
rename to docker/with_the_same_user
diff --git a/tests/ci_build/README.md b/tests/ci_build/README.md
deleted file mode 100644
index cd74ff2336ff..000000000000
--- a/tests/ci_build/README.md
+++ /dev/null
@@ -1,36 +0,0 @@
-# CI Build Scripts
-
-This directory contains the files and setup instructions to run all tests.
-
-## Run locally
-
-To run locally, we need to first install
-[docker](https://docs.docker.com/engine/installation/) and
-[nvidia-docker](https://github.com/NVIDIA/nvidia-docker/wiki).
-
-Then we can run the tasks defined in the [Jenkinsfile](../../Jenkinsfile) by
-using (`ci_build.sh`)[./ci_build.sh]. For example
-
-- lint the python codes
-
-  ```bash
-  ./ci_build.sh lint make pylint
-  ```
-
-- build codes with CUDA supports
-
-  ```bash
-  ./ci_build.sh gpu make -j$(nproc)
-  ```
-
-- do the python unittest
-
-  ```bash
-  ./ci_build.sh gpu PYTHONPATH=./python/ nosetests --with-timer --verbose tests/python/unittest'
-  ```
-
-- build the documents. The results will be available at `docs/_build/html`
-
-  ```bash
-  tests/ci_build/ci_build.sh gpu make -C docs html
-  ```

From a98866c22c16c7c9918ac2616939809f210cb2cb Mon Sep 17 00:00:00 2001
From: Siju <sijusamuel@gmail.com>
Date: Mon, 9 Jul 2018 10:20:28 +0530
Subject: [PATCH 804/949] =?UTF-8?q?[DARKNET=20FRONTEND]Batchnorm=20added?=
 =?UTF-8?q?=20as=20part=20of=20Dense=20op=20for=20running=20rnn=20model=20?=
 =?UTF-8?q?for=20next=20wo=E2=80=A6=20(#1385)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 nnvm/python/nnvm/frontend/darknet.py          | 20 +++++++++++++------
 .../python/frontend/darknet/test_forward.py   | 15 ++++++++++++++
 2 files changed, 29 insertions(+), 6 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/darknet.py b/nnvm/python/nnvm/frontend/darknet.py
index 112e5294396a..f2c744bea375 100644
--- a/nnvm/python/nnvm/frontend/darknet.py
+++ b/nnvm/python/nnvm/frontend/darknet.py
@@ -226,13 +226,18 @@ def _darknet_dense(inputs, attrs):
     """Process the dense operation."""
     op_name, new_attrs = 'dense', {}
     new_attrs['units'] = _darknet_required_attr(attrs, 'num_hidden')
-
+    out_name = {}
     if attrs.get('use_bias', False) is True:
         new_attrs['use_bias'] = True
     if attrs.get('use_flatten', False) is True:
         inputs[0] = _sym.flatten(inputs[0])
     sym = _darknet_get_nnvm_op(op_name)(*inputs, **new_attrs)
-    out_name = sym.list_output_names()[0].replace('_output', '')
+    out_name[0] = sym.list_output_names()[0].replace('_output', '')
+    if 'use_batchNorm' in attrs:
+        op_name, new_attrs = 'batch_norm', {}
+        new_attrs['epsilon'] = 0.000001
+        sym = _darknet_get_nnvm_op(op_name)(*sym, **new_attrs)
+        out_name[1] = sym.list_output_names()[0].replace('_output', '')
     if 'activation' in attrs:
         new_attrs = {}
         new_attrs['activation'] = attrs['activation']
@@ -430,13 +435,16 @@ def _get_connected_weights(layer, opname, params, dtype):
     weights = _read_memory_buffer((layer.outputs, layer.inputs), layer.weights, dtype)
     biases = _read_memory_buffer((layer.outputs, ), layer.biases, dtype)
 
-    k = _get_tvm_params_name(opname, 'weight')
+    k = _get_tvm_params_name(opname[0], 'weight')
     params[k] = tvm.nd.array(weights)
-    k = _get_tvm_params_name(opname, 'bias')
-    params[k] = tvm.nd.array(biases)
 
     if layer.batch_normalize == 1 and layer.dontloadscales != 1:
-        _get_batchnorm_weights(layer, opname, params, layer.outputs, dtype)
+        _get_batchnorm_weights(layer, opname[1], params, layer.outputs, dtype)
+        k = _get_tvm_params_name(opname[1], 'beta')
+        params[k] = tvm.nd.array(biases)
+    else:
+        k = _get_tvm_params_name(opname[0], 'bias')
+        params[k] = tvm.nd.array(biases)
 
 def _get_batchnorm_weights(layer, opname, params, size, dtype):
     """Parse the weights for batchnorm, which includes, scales, moving mean
diff --git a/nnvm/tests/python/frontend/darknet/test_forward.py b/nnvm/tests/python/frontend/darknet/test_forward.py
index a61a8d5a9738..6077d6b9d127 100644
--- a/nnvm/tests/python/frontend/darknet/test_forward.py
+++ b/nnvm/tests/python/frontend/darknet/test_forward.py
@@ -169,6 +169,20 @@ def test_forward_dense():
     test_forward(net)
     LIB.free_network(net)
 
+def test_forward_dense_batchnorm():
+    '''test fully connected layer with batchnorm'''
+    net = LIB.make_network(1)
+    layer = LIB.make_connected_layer(1, 12, 2, 1, 1, 0)
+    for i in range(5):
+        layer.rolling_mean[i] = np.random.rand(1)
+        layer.rolling_variance[i] = np.random.rand(1)
+        layer.scales[i] = np.random.rand(1)
+    net.layers[0] = layer
+    net.w = net.h = 2
+    LIB.resize_network(net, 2, 2)
+    test_forward(net)
+    LIB.free_network(net)
+
 def test_forward_maxpooling():
     '''test maxpooling layer'''
     net = LIB.make_network(1)
@@ -264,6 +278,7 @@ def test_forward_elu():
     test_forward_batch_norm()
     test_forward_shortcut()
     test_forward_dense()
+    test_forward_dense_batchnorm()
     test_forward_reorg()
     test_forward_region()
     test_forward_elu()

From 55fe9227b826d73c8e12bf010d7dd9e876f4f0d4 Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Mon, 9 Jul 2018 10:20:52 +0530
Subject: [PATCH 805/949] [TOPI][DARKNET]Yolo op added  (#1372)

---
 nnvm/python/nnvm/frontend/darknet.py          |  4 +-
 nnvm/python/nnvm/top/vision.py                | 14 ++---
 nnvm/src/top/vision/{yolo2 => yolo}/region.cc |  2 +-
 nnvm/src/top/vision/{yolo2 => yolo}/region.h  |  6 +-
 nnvm/src/top/vision/{yolo2 => yolo}/reorg.cc  |  2 +-
 nnvm/src/top/vision/{yolo2 => yolo}/reorg.h   |  6 +-
 .../topi/vision/{yolo2 => yolo}/region.h      | 12 ++--
 topi/include/topi/vision/yolo/yolo.h          | 58 +++++++++++++++++++
 topi/python/topi/cpp.py                       |  4 +-
 topi/python/topi/testing/__init__.py          |  1 +
 topi/python/topi/testing/yolo_python.py       | 43 ++++++++++++++
 topi/python/topi/vision/__init__.py           |  2 +-
 .../topi/vision/{yolo2 => yolo}/__init__.py   |  1 +
 .../topi/vision/{yolo2 => yolo}/region.py     |  2 +-
 topi/python/topi/vision/yolo/yolo.py          | 30 ++++++++++
 topi/src/topi.cc                              | 12 +++-
 topi/tests/python/test_topi_region.py         |  2 +-
 topi/tests/python_cpp/test_topi_region.py     |  2 +-
 topi/tests/python_cpp/test_topi_yolo.py       | 49 ++++++++++++++++
 19 files changed, 220 insertions(+), 32 deletions(-)
 rename nnvm/src/top/vision/{yolo2 => yolo}/region.cc (96%)
 rename nnvm/src/top/vision/{yolo2 => yolo}/region.h (96%)
 rename nnvm/src/top/vision/{yolo2 => yolo}/reorg.cc (98%)
 rename nnvm/src/top/vision/{yolo2 => yolo}/reorg.h (96%)
 rename topi/include/topi/vision/{yolo2 => yolo}/region.h (92%)
 create mode 100644 topi/include/topi/vision/yolo/yolo.h
 create mode 100644 topi/python/topi/testing/yolo_python.py
 rename topi/python/topi/vision/{yolo2 => yolo}/__init__.py (87%)
 rename topi/python/topi/vision/{yolo2 => yolo}/region.py (91%)
 create mode 100644 topi/python/topi/vision/yolo/yolo.py
 create mode 100644 topi/tests/python_cpp/test_topi_yolo.py

diff --git a/nnvm/python/nnvm/frontend/darknet.py b/nnvm/python/nnvm/frontend/darknet.py
index f2c744bea375..c18585a27dcc 100644
--- a/nnvm/python/nnvm/frontend/darknet.py
+++ b/nnvm/python/nnvm/frontend/darknet.py
@@ -276,14 +276,14 @@ def _darknet_route(inputs, attrs):
 
 def _darknet_reorg(inputs, attrs):
     """Process the reorg operation."""
-    op_name, new_attrs = 'yolo2_reorg', {}
+    op_name, new_attrs = 'yolo_reorg', {}
     if 'stride' in attrs:
         new_attrs = {'stride': attrs.get('stride', 1)}
     return _darknet_get_nnvm_op(op_name)(*inputs, **new_attrs), None
 
 def _darknet_region(inputs, attrs):
     """Process the region operation."""
-    op_name, new_attrs = 'yolo2_region', {}
+    op_name, new_attrs = 'yolo_region', {}
     if 'n' in attrs:
         new_attrs['n'] = attrs.get('n', 1)
     if 'classes' in attrs:
diff --git a/nnvm/python/nnvm/top/vision.py b/nnvm/python/nnvm/top/vision.py
index edbf72320a26..f2e12c0f367a 100644
--- a/nnvm/python/nnvm/top/vision.py
+++ b/nnvm/python/nnvm/top/vision.py
@@ -7,20 +7,20 @@
 from . import registry as reg
 from .registry import OpPattern
 
-@reg.register_compute("yolo2_reorg")
+@reg.register_compute("yolo_reorg")
 def compute_reorg(attrs, inputs, _):
     """Compute definition of reorg"""
     return topi.vision.reorg(inputs[0], attrs.get_int("stride"))
 
-@reg.register_schedule("yolo2_reorg")
+@reg.register_schedule("yolo_reorg")
 def schedule_reorg(attrs, outs, target):
     """Schedule definition of reorg"""
     with tvm.target.create(target):
         return topi.generic.schedule_injective(outs)
 
-reg.register_pattern("yolo2_reorg", OpPattern.INJECTIVE)
+reg.register_pattern("yolo_reorg", OpPattern.INJECTIVE)
 
-@reg.register_compute("yolo2_region")
+@reg.register_compute("yolo_region")
 def compute_region(attrs, inputs, _):
     """Compute definition of region"""
     n = attrs.get_int("n")
@@ -28,15 +28,15 @@ def compute_region(attrs, inputs, _):
     coords = attrs.get_int("coords")
     background = attrs.get_int("background")
     softmax = attrs.get_int("softmax")
-    return topi.vision.yolo2.region(inputs[0], n, classes, coords, background, softmax)
+    return topi.vision.yolo.region(inputs[0], n, classes, coords, background, softmax)
 
-@reg.register_schedule("yolo2_region")
+@reg.register_schedule("yolo_region")
 def schedule_region(attrs, outs, target):
     """Schedule definition of region"""
     with tvm.target.create(target):
         return topi.generic.vision.schedule_region(outs)
 
-reg.register_pattern("yolo2_region", OpPattern.OPAQUE)
+reg.register_pattern("yolo_region", OpPattern.OPAQUE)
 
 # multibox_prior
 @reg.register_schedule("multibox_prior")
diff --git a/nnvm/src/top/vision/yolo2/region.cc b/nnvm/src/top/vision/yolo/region.cc
similarity index 96%
rename from nnvm/src/top/vision/yolo2/region.cc
rename to nnvm/src/top/vision/yolo/region.cc
index 87860be3d03a..182c9b2ab3bc 100644
--- a/nnvm/src/top/vision/yolo2/region.cc
+++ b/nnvm/src/top/vision/yolo/region.cc
@@ -13,7 +13,7 @@
 namespace nnvm {
 namespace top {
 
-NNVM_REGISTER_OP(yolo2_region)
+NNVM_REGISTER_OP(yolo_region)
 .describe(R"code(Region layer
 )code" NNVM_ADD_FILELINE)
 .set_num_inputs(1)
diff --git a/nnvm/src/top/vision/yolo2/region.h b/nnvm/src/top/vision/yolo/region.h
similarity index 96%
rename from nnvm/src/top/vision/yolo2/region.h
rename to nnvm/src/top/vision/yolo/region.h
index cc816eab6ae1..f9dc87c59c6c 100644
--- a/nnvm/src/top/vision/yolo2/region.h
+++ b/nnvm/src/top/vision/yolo/region.h
@@ -2,8 +2,8 @@
  *  Copyright (c) 2018 by Contributors
  * \file region.h
  */
-#ifndef NNVM_TOP_VISION_YOLO2_REGION_H_
-#define NNVM_TOP_VISION_YOLO2_REGION_H_
+#ifndef NNVM_TOP_VISION_YOLO_REGION_H_
+#define NNVM_TOP_VISION_YOLO_REGION_H_
 
 #include <string>
 #include <vector>
@@ -98,4 +98,4 @@ inline bool RegionType(const NodeAttrs &attrs,
 }
 }  // namespace top
 }  // namespace nnvm
-#endif  // NNVM_TOP_VISION_YOLO2_REGION_H_
+#endif  // NNVM_TOP_VISION_YOLO_REGION_H_
diff --git a/nnvm/src/top/vision/yolo2/reorg.cc b/nnvm/src/top/vision/yolo/reorg.cc
similarity index 98%
rename from nnvm/src/top/vision/yolo2/reorg.cc
rename to nnvm/src/top/vision/yolo/reorg.cc
index e58940eb25dd..e44d77c07953 100644
--- a/nnvm/src/top/vision/yolo2/reorg.cc
+++ b/nnvm/src/top/vision/yolo/reorg.cc
@@ -34,7 +34,7 @@ inline bool ReorgInferShape(const nnvm::NodeAttrs &attrs,
   return true;
 }
 
-NNVM_REGISTER_OP(yolo2_reorg)
+NNVM_REGISTER_OP(yolo_reorg)
 .describe(R"(Perform reorg operation on input array based on the stride value.
 - **data**: Input is 4D array of shape (batch_size, channels, in_height, in_width).
 - **out**: Output is 4D array of shape (batch_size, channels/(stride*stride), in_height*stride, in_width*stride).
diff --git a/nnvm/src/top/vision/yolo2/reorg.h b/nnvm/src/top/vision/yolo/reorg.h
similarity index 96%
rename from nnvm/src/top/vision/yolo2/reorg.h
rename to nnvm/src/top/vision/yolo/reorg.h
index 87e0510e2781..a16edeceaec2 100644
--- a/nnvm/src/top/vision/yolo2/reorg.h
+++ b/nnvm/src/top/vision/yolo/reorg.h
@@ -2,8 +2,8 @@
  *  Copyright (c) 2018 by Contributors
  * \file reorg.h
  */
-#ifndef NNVM_TOP_VISION_YOLO2_REORG_H_
-#define NNVM_TOP_VISION_YOLO2_REORG_H_
+#ifndef NNVM_TOP_VISION_YOLO_REORG_H_
+#define NNVM_TOP_VISION_YOLO_REORG_H_
 
 #include <string>
 #include <vector>
@@ -107,4 +107,4 @@ struct ReorgParam : public dmlc::Parameter<ReorgParam> {
 };
 }  // namespace top
 }  // namespace nnvm
-#endif  // NNVM_TOP_VISION_YOLO2_REORG_H_
+#endif  // NNVM_TOP_VISION_YOLO_REORG_H_
diff --git a/topi/include/topi/vision/yolo2/region.h b/topi/include/topi/vision/yolo/region.h
similarity index 92%
rename from topi/include/topi/vision/yolo2/region.h
rename to topi/include/topi/vision/yolo/region.h
index a77ff49b5429..88553fc29b8a 100644
--- a/topi/include/topi/vision/yolo2/region.h
+++ b/topi/include/topi/vision/yolo/region.h
@@ -1,10 +1,10 @@
 /*!
  *  Copyright (c) 2018 by Contributors
  * \brief Region op constructions
- * \file vision/yolo2/region.h
+ * \file vision/yolo/region.h
  */
-#ifndef TOPI_VISION_YOLO2_REGION_H_
-#define TOPI_VISION_YOLO2_REGION_H_
+#ifndef TOPI_VISION_YOLO_REGION_H_
+#define TOPI_VISION_YOLO_REGION_H_
 
 #include <algorithm>
 #include <string>
@@ -19,7 +19,7 @@
 
 namespace topi {
 namespace vision {
-namespace yolo2 {
+namespace yolo {
 using namespace tvm;
 using namespace nn;
 
@@ -75,7 +75,7 @@ inline Tensor region(const Tensor &data,
   Tensor out = concatenate(split_res, 2);
   return reshape(out, input_shape);
 }
-}  // namespace yolo2
+}  // namespace yolo
 }  // namespace vision
 }  // namespace topi
-#endif  // TOPI_VISION_YOLO2_REGION_H_
+#endif  // TOPI_VISION_YOLO_REGION_H_
diff --git a/topi/include/topi/vision/yolo/yolo.h b/topi/include/topi/vision/yolo/yolo.h
new file mode 100644
index 000000000000..d2e24c01b253
--- /dev/null
+++ b/topi/include/topi/vision/yolo/yolo.h
@@ -0,0 +1,58 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \brief YOLO op constructions
+ * \file vision/yolo/yolo.h
+ */
+#ifndef TOPI_VISION_YOLO_YOLO_H_
+#define TOPI_VISION_YOLO_YOLO_H_
+
+#include <algorithm>
+#include <string>
+
+#include "topi/detail/constant_utils.h"
+#include "topi/tags.h"
+#include "topi/transform.h"
+#include "tvm/tvm.h"
+
+
+namespace topi {
+namespace vision {
+namespace yolo {
+using namespace tvm;
+using namespace nn;
+
+/*!
+* \brief yolo operation
+*
+* \param data The input tensor.
+* \param num Darknet layer parameter n
+* \param classes number of classes in the yolo model
+* \param name The name of the operation
+* \param tag The tag to mark the operation
+*
+* \return A Tensor whose op member is the yolo operation
+*/
+inline Tensor yolo(const Tensor &data,
+                   int num,
+                   int classes,
+                   std::string name = "tensor",
+                   std::string tag = "yolo_output") {
+  auto input_shape = data->shape;
+  int split_size = classes + 5;
+  Array <Expr> intermediate_shape = {input_shape[0],
+                                     num,
+                                     split_size,
+                                     input_shape[2],
+                                     input_shape[3]};
+  auto data_block = reshape(data, intermediate_shape);
+  Array <Expr> split_indices = {2, 4};
+  Array <Tensor> split_res = split(data_block, split_indices, 2);
+  split_res.Set(0, sigmoid(split_res[0]));
+  split_res.Set(2, sigmoid(split_res[2]));
+  Tensor out = concatenate(split_res, 2);
+  return reshape(out, input_shape);
+}
+}  // namespace yolo
+}  // namespace vision
+}  // namespace topi
+#endif  // TOPI_VISION_YOLO_YOLO_H_
diff --git a/topi/python/topi/cpp.py b/topi/python/topi/cpp.py
index cf7314be4600..85f203387805 100644
--- a/topi/python/topi/cpp.py
+++ b/topi/python/topi/cpp.py
@@ -46,7 +46,7 @@ def _create_module(name):
 _init_api_prefix("topi.cpp.x86", "topi.x86")
 vision = _create_module("vision")
 _init_api_prefix("topi.cpp.vision", "topi.vision")
-yolo2 = _create_module("vision.yolo2")
-_init_api_prefix("topi.cpp.vision.yolo2", "topi.vision.yolo2")
+yolo = _create_module("vision.yolo")
+_init_api_prefix("topi.cpp.vision.yolo", "topi.vision.yolo")
 image = _create_module("image")
 _init_api_prefix("topi.cpp.image", "topi.image")
diff --git a/topi/python/topi/testing/__init__.py b/topi/python/topi/testing/__init__.py
index c91eea7958ea..c9d995a38686 100644
--- a/topi/python/topi/testing/__init__.py
+++ b/topi/python/topi/testing/__init__.py
@@ -15,6 +15,7 @@
 from .bilinear_resize_python import bilinear_resize_python
 from .reorg_python import reorg_python
 from .region_python import region_python
+from .yolo_python import yolo_python
 from .shortcut_python import shortcut_python
 from .lrn_python import lrn_python
 from .l2_normalize_python import l2_normalize_python
diff --git a/topi/python/topi/testing/yolo_python.py b/topi/python/topi/testing/yolo_python.py
new file mode 100644
index 000000000000..a6b3a41203c6
--- /dev/null
+++ b/topi/python/topi/testing/yolo_python.py
@@ -0,0 +1,43 @@
+# pylint: disable=invalid-name, line-too-long, unused-variable, too-many-locals
+"""Yolo operator in python"""
+import numpy as np
+
+def entry_index(batch, w, h, outputs, classes, coords, location, entry):
+    n = int(location/(w*h))
+    loc = location%(w*h)
+    return batch*outputs + n*w*h*(coords+classes+1) + entry*w*h + loc
+
+def yolo_python(a_np, N, classes):
+    """Yolo operator
+    Parameters
+    ----------
+    a_np : numpy.ndarray
+        4-D with shape [batch, in_channel, in_height, in_width]
+
+    N : int
+        Darknet layer parameter n
+
+    classes : int
+        Darknet layer parameter classes
+
+    Returns
+    -------
+    b_np : np.ndarray
+        4-D with shape [batch, out_channel, out_height, out_width]
+    """
+
+    batch, in_channel, in_height, in_width = a_np.shape
+    a_np_temp = np.reshape(a_np, batch*in_channel*in_height*in_width)
+    outputs = batch*in_channel*in_height*in_width
+    b_np = np.zeros(batch*in_channel*in_height*in_width)
+    for i in range(batch*in_channel*in_height*in_width):
+        b_np[i] = a_np_temp[i]
+    for b in range(batch):
+        for n in range(N):
+            index = entry_index(b, in_width, in_height, outputs, classes, 4, n*in_width*in_height, 0)
+            b_np[index: index+2*in_width*in_height] = 1/(1+np.exp(-1*b_np[index: index+2*in_width*in_height]))
+            index = entry_index(b, in_width, in_height, outputs, classes, 4, n*in_width*in_height, 4)
+            b_np[index: index+(1+classes)*in_width*in_height] = 1/(1+np.exp(-1*b_np[index: index+(1+classes)*in_width*in_height]))
+
+    b_np = np.reshape(b_np, (batch, in_channel, in_height, in_width))
+    return b_np
diff --git a/topi/python/topi/vision/__init__.py b/topi/python/topi/vision/__init__.py
index 9b6d9bf5e3dc..a94cd9c7c53c 100644
--- a/topi/python/topi/vision/__init__.py
+++ b/topi/python/topi/vision/__init__.py
@@ -2,7 +2,7 @@
 """VISION network operators"""
 from __future__ import absolute_import as _abs
 
-from . import yolo2, ssd
+from . import yolo, ssd
 from .shortcut import *
 from .reorg import *
 from .nms import *
diff --git a/topi/python/topi/vision/yolo2/__init__.py b/topi/python/topi/vision/yolo/__init__.py
similarity index 87%
rename from topi/python/topi/vision/yolo2/__init__.py
rename to topi/python/topi/vision/yolo/__init__.py
index c0e9899a41aa..2c0a165f8aac 100644
--- a/topi/python/topi/vision/yolo2/__init__.py
+++ b/topi/python/topi/vision/yolo/__init__.py
@@ -3,3 +3,4 @@
 from __future__ import absolute_import as _abs
 
 from .region import *
+from .yolo import *
diff --git a/topi/python/topi/vision/yolo2/region.py b/topi/python/topi/vision/yolo/region.py
similarity index 91%
rename from topi/python/topi/vision/yolo2/region.py
rename to topi/python/topi/vision/yolo/region.py
index 79dfd6961a3a..77c1c86a8d06 100644
--- a/topi/python/topi/vision/yolo2/region.py
+++ b/topi/python/topi/vision/yolo/region.py
@@ -36,4 +36,4 @@ def region(data, num, classes, coords, background, softmax=True):
     out : tvm.Tensor
         4-D with shape [batch, c_in, h_in, w_in]
     """
-    return cpp.yolo2.region(data, num, classes, coords, background, softmax)
+    return cpp.yolo.region(data, num, classes, coords, background, softmax)
diff --git a/topi/python/topi/vision/yolo/yolo.py b/topi/python/topi/vision/yolo/yolo.py
new file mode 100644
index 000000000000..6ae630a86d8f
--- /dev/null
+++ b/topi/python/topi/vision/yolo/yolo.py
@@ -0,0 +1,30 @@
+# pylint: disable=invalid-name, unused-variable
+"""
+YOLO Operator
+=============
+YOLO operator, used in darknet.
+"""
+from __future__ import absolute_import as _abs
+import tvm
+from ... import cpp
+
+@tvm.target.generic_func
+def yolo(data, num, classes):
+    """YOLO forward operators.
+    Parameters
+    ----------
+    data : tvm.Tensor
+        4-D with shape [batch, c_in, h_in, w_in]
+
+    num : int
+        Darknet layer parameter n
+
+    classes : int
+        Darknet layer parameter classes
+
+    Returns
+    -------
+    out : tvm.Tensor
+        4-D with shape [batch, c_in, h_in, w_in]
+    """
+    return cpp.yolo.yolo(data, num, classes)
diff --git a/topi/src/topi.cc b/topi/src/topi.cc
index a4a87b3096a3..c08bd5f565d2 100644
--- a/topi/src/topi.cc
+++ b/topi/src/topi.cc
@@ -29,7 +29,8 @@
 
 #include <topi/vision/reorg.h>
 #include <topi/image/resize.h>
-#include <topi/vision/yolo2/region.h>
+#include <topi/vision/yolo/region.h>
+#include <topi/vision/yolo/yolo.h>
 #include <topi/generic/default.h>
 #include <topi/generic/extern.h>
 #include <topi/generic/injective.h>
@@ -386,9 +387,14 @@ TVM_REGISTER_GLOBAL("topi.vision.reorg")
   *rv = vision::reorg(args[0], args[1]);
   });
 
-TVM_REGISTER_GLOBAL("topi.vision.yolo2.region")
+TVM_REGISTER_GLOBAL("topi.vision.yolo.region")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
-  *rv = vision::yolo2::region(args[0], args[1], args[2], args[3], args[4], args[5]);
+  *rv = vision::yolo::region(args[0], args[1], args[2], args[3], args[4], args[5]);
+  });
+
+TVM_REGISTER_GLOBAL("topi.vision.yolo.yolo")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = vision::yolo::yolo(args[0], args[1], args[2]);
   });
 
 /* Ops from image/resize.h */
diff --git a/topi/tests/python/test_topi_region.py b/topi/tests/python/test_topi_region.py
index f83001535e35..a2835339e8eb 100644
--- a/topi/tests/python/test_topi_region.py
+++ b/topi/tests/python/test_topi_region.py
@@ -10,7 +10,7 @@ def verify_region(batch, in_size, in_channel, n, classes, coords, background, l_
     in_height = in_width = in_size
 
     A = tvm.placeholder((batch, in_channel, in_height, in_width), name='A')
-    B = topi.vision.yolo2.region(A, n, classes, coords, background, l_softmax)
+    B = topi.vision.yolo.region(A, n, classes, coords, background, l_softmax)
 
     a_shape = get_const_tuple(A.shape)
     dtype = A.dtype
diff --git a/topi/tests/python_cpp/test_topi_region.py b/topi/tests/python_cpp/test_topi_region.py
index 8d40fa1e6b3a..a37cf6610a0f 100644
--- a/topi/tests/python_cpp/test_topi_region.py
+++ b/topi/tests/python_cpp/test_topi_region.py
@@ -11,7 +11,7 @@ def verify_region(batch, in_size, in_channel, n, classes, coords, background, l_
     in_height = in_width = in_size
 
     A = tvm.placeholder((batch, in_channel, in_height, in_width), name='A')
-    B = topi.cpp.yolo2.region(A, n, classes, coords, background, l_softmax)
+    B = topi.cpp.yolo.region(A, n, classes, coords, background, l_softmax)
 
     a_shape = get_const_tuple(A.shape)
     dtype = A.dtype
diff --git a/topi/tests/python_cpp/test_topi_yolo.py b/topi/tests/python_cpp/test_topi_yolo.py
new file mode 100644
index 000000000000..ed234b7bd134
--- /dev/null
+++ b/topi/tests/python_cpp/test_topi_yolo.py
@@ -0,0 +1,49 @@
+"""Test code for yolo op"""
+import logging
+import numpy as np
+import tvm
+import topi
+import topi.testing
+from topi.util import get_const_tuple
+
+def verify_yolo(ishape, n, classes):
+    '''Verify yolo operator by comparing outputs from tvm and numpy implementation'''
+    
+    A = tvm.placeholder(ishape, name='A')
+    B = topi.cpp.yolo.yolo(A, n, classes)
+    dtype = A.dtype
+
+    def get_ref_data_yolo():
+        '''Randomly initialize the data variables and get refernce output for the yolo operation'''
+        a_np = np.random.uniform(size=ishape).astype(dtype)
+        b_np = topi.testing.yolo_python(a_np, n, classes)
+        return a_np, b_np
+
+    a_np, b_np = get_ref_data_yolo()
+    def check_device(device):
+        '''Check the device is available and if so, build and run the program'''
+        if not tvm.module.enabled(device):
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        target = topi.cpp.TEST_create_target(device)
+        if device == "llvm":
+            s = topi.cpp.generic.default_schedule(target, [B], False)
+        else:
+            s = topi.cpp.cuda.schedule_injective(target, [B])
+        ctx = tvm.context(device, 0)
+        a = tvm.nd.array(a_np, ctx)
+        b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
+        func = tvm.build(s, [A, B], device, name="yolo")
+        func(a, b)
+        np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+    for device in ['cuda', 'opencl', 'metal', 'rocm', 'llvm', 'vulkan']:
+        check_device(device)
+
+def test_yolo():
+    verify_yolo((1, 425, 19, 19), 5, 80)
+
+if __name__ == "__main__":
+    logging.basicConfig(level=logging.DEBUG)
+    test_yolo()

From a57de99e931cfa4e6f78ef3e64fdd64f7f741838 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 8 Jul 2018 21:51:36 -0700
Subject: [PATCH 806/949] [DOCKER] Add docker demo image (#1404)

---
 docker/Dockerfile.ci_cpu          |   2 +-
 docker/Dockerfile.ci_gpu          |   1 +
 docker/Dockerfile.ci_i386         |   2 +
 docker/Dockerfile.ci_lint         |   1 +
 docker/Dockerfile.demo_cpu        |  31 ++++++
 docker/README.md                  |  57 ++++++++---
 docker/bash.sh                    |  43 +++++++++
 docker/build.sh                   |   5 +
 docker/install/install_tvm_cpu.sh |  12 +++
 docker/with_the_same_user         |   3 +-
 docs/install/docker.rst           |  45 +++++++++
 docs/install/from_source.rst      | 154 ++++++++++++++++++++++++++++++
 docs/install/index.rst            | 150 +----------------------------
 13 files changed, 345 insertions(+), 161 deletions(-)
 create mode 100644 docker/Dockerfile.demo_cpu
 create mode 100755 docker/bash.sh
 create mode 100644 docker/install/install_tvm_cpu.sh
 create mode 100644 docs/install/docker.rst
 create mode 100644 docs/install/from_source.rst

diff --git a/docker/Dockerfile.ci_cpu b/docker/Dockerfile.ci_cpu
index f29ef190328d..0f0fc6f04d4c 100644
--- a/docker/Dockerfile.ci_cpu
+++ b/docker/Dockerfile.ci_cpu
@@ -1,4 +1,4 @@
-# For CPU
+# CI docker CPU env
 FROM ubuntu:16.04
 
 RUN apt-get update --fix-missing
diff --git a/docker/Dockerfile.ci_gpu b/docker/Dockerfile.ci_gpu
index 95a70d97d8b2..c177ef9d420a 100644
--- a/docker/Dockerfile.ci_gpu
+++ b/docker/Dockerfile.ci_gpu
@@ -1,3 +1,4 @@
+# CI docker GPU env
 FROM nvidia/cuda:8.0-cudnn7-devel
 
 # Base scripts
diff --git a/docker/Dockerfile.ci_i386 b/docker/Dockerfile.ci_i386
index 71e151128655..6a8394e85763 100644
--- a/docker/Dockerfile.ci_i386
+++ b/docker/Dockerfile.ci_i386
@@ -1,3 +1,5 @@
+# CI docker i386 env
+
 FROM ioft/i386-ubuntu:16.04
 
 RUN apt-get update --fix-missing
diff --git a/docker/Dockerfile.ci_lint b/docker/Dockerfile.ci_lint
index 902c28fde9ee..132e8ebb7df9 100644
--- a/docker/Dockerfile.ci_lint
+++ b/docker/Dockerfile.ci_lint
@@ -1,4 +1,5 @@
 # For lint test
+# CI docker lint env
 FROM ubuntu:16.04
 
 RUN apt-get update && apt-get install -y sudo wget
diff --git a/docker/Dockerfile.demo_cpu b/docker/Dockerfile.demo_cpu
new file mode 100644
index 000000000000..f84b245b4298
--- /dev/null
+++ b/docker/Dockerfile.demo_cpu
@@ -0,0 +1,31 @@
+# Minimum docker image for demo purposes
+# prebuilt-image: tvmai/demo_cpu
+FROM ubuntu:16.04
+
+RUN apt-get update --fix-missing
+
+COPY install/ubuntu_install_core.sh /install/ubuntu_install_core.sh
+RUN bash /install/ubuntu_install_core.sh
+
+# Python: basic dependencies
+RUN apt-get update && apt-get install -y python3-dev python3-pip
+RUN pip3 install numpy nose-timer cython decorator scipy
+
+# LLVM
+RUN echo deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-6.0 main \
+     >> /etc/apt/sources.list.d/llvm.list && \
+     wget -O - http://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add - && \
+     apt-get update && apt-get install -y --force-yes llvm-6.0
+
+# Jupyter notebook.
+RUN pip3 install matplotlib Image Pillow jupyter[notebook]
+
+# Deep learning frameworks
+RUN pip3 install mxnet tensorflow keras
+
+# Build TVM
+COPY install/install_tvm_cpu.sh /install/install_tvm_cpu.sh
+RUN bash /install/install_tvm_cpu.sh
+
+# Environment variables
+ENV PYTHONPATH=/usr/tvm/python:/usr/tvm/topi/python:/usr/tvm/nnvm/python/:/usr/tvm/vta/python:${PYTHONPATH}
diff --git a/docker/README.md b/docker/README.md
index c39cd5bd9c60..b44cd266041e 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -1,19 +1,48 @@
 # TVM Docker
 
 This directory contains the TVM's docker infrastructure.
-We use docker to quickly provide environments that can be
-used to build tvm in various settings.
+We use docker to provide build environments for CI and images for demo.
+We need [docker](https://docs.docker.com/engine/installation/) and
+[nvidia-docker](https://github.com/NVIDIA/nvidia-docker/) for GPU images.
 
-To run locally, we need to first install
-[docker](https://docs.docker.com/engine/installation/) and
-[nvidia-docker](https://github.com/NVIDIA/nvidia-docker/).
+## Start Docker Bash Session
+
+You can use the following helper script to start an
+interactive bash session with a given image_name.
+
+```bash
+/path/to/tvm/docker/bash.sh image_name
+```
+
+The script does the following things:
+- Mount current directory to /workspace and set it as home
+- Switch user to be the same user that calls the bash.sh
+- Use the host-side network
+
+The helper bash script can be useful to build demo sessions.
+
+## Prebuilt Docker Images
+
+We provide several pre-built images for doing quick exploration with TVM installed.
+For example, you can run the following command to get ```tvmai/demo_cpu``` image.
+
+```bash
+/path/to/tvm/docker/bash.sh tvmai/demo_cpu
+```
+
+Then inside the docker container, you can type the following command to start the jupyter notebook
+```bash
+jupyter notebook
+```
+
+Check out https://hub.docker.com/r/tvmai/ to get the full list of available prebuilt images.
 
 
 ## Use Local Build Script
 
-Each dockerfile defines a different environment.
+We also provide script to build docker images locally.
 We use (`build.sh`)[./build.sh] to build and run the commands.
-To use the docker images, we can run the following command
+To build and run docker images, we can run the following command
 at the root of the project.
 
 ```bash
@@ -36,23 +65,23 @@ Here are some common use examples to perform CI tasks.
 - lint the python codes
 
   ```bash
-    ./docker/build.sh ci_lint make pylint
-      ```
+  ./docker/build.sh ci_lint make pylint
+  ```
 
 - build codes with CUDA support
 
   ```bash
-    ./docker/build.sh ci_gpu make -j$(nproc)
-      ```
+  ./docker/build.sh ci_gpu make -j$(nproc)
+  ```
 
 - do the python unittest
 
   ```bash
-    ./docker/build.sh ci_gpu tests/scripts/task_python_unittest.sh'
-      ```
+  ./docker/build.sh ci_gpu tests/scripts/task_python_unittest.sh
+  ```
 
 - build the documents. The results will be available at `docs/_build/html`
 
   ```bash
-    ./docker/ci_build.sh ci_gpu make -C docs html
+  ./docker/ci_build.sh ci_gpu make -C docs html
   ```
diff --git a/docker/bash.sh b/docker/bash.sh
new file mode 100755
index 000000000000..79f989193dcf
--- /dev/null
+++ b/docker/bash.sh
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+#
+# Start a bash, mount /workspace to be current directory.
+#
+# Usage: docker/bash.sh <CONTAINER_NAME>
+#
+if [ "$#" -lt 1 ]; then
+    echo "Usage: docker/bash.sh <CONTAINER_NAME>"
+    exit -1
+fi
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+WORKSPACE="$(pwd)"
+DOCKER_IMAGE_NAME=$1
+
+# Use nvidia-docker if the container is GPU.
+if [[ "${DOCKER_IMAGE_NAME}" == *"gpu"* ]]; then
+    DOCKER_BINARY="nvidia-docker"
+else
+    DOCKER_BINARY="docker"
+fi
+
+# Print arguments.
+echo "WORKSPACE: ${WORKSPACE}"
+echo "DOCKER CONTAINER NAME: ${DOCKER_IMG_NAME}"
+echo ""
+
+# By default we cleanup - remove the container once it finish running (--rm)
+# and share the PID namespace (--pid=host) so the process inside does not have
+# pid 1 and SIGKILL is propagated to the process inside (jenkins can kill it).
+echo ${DOCKER_BINARY}
+${DOCKER_BINARY} run --rm -it --pid=host --net=host\
+    -v ${WORKSPACE}:/workspace \
+    -v ${SCRIPT_DIR}:/docker \
+    -w /workspace \
+    -e "CI_BUILD_HOME=/workspace" \
+    -e "CI_BUILD_USER=$(id -u -n)" \
+    -e "CI_BUILD_UID=$(id -u)" \
+    -e "CI_BUILD_GROUP=$(id -g -n)" \
+    -e "CI_BUILD_GID=$(id -g)" \
+    ${DOCKER_IMAGE_NAME}\
+    bash /docker/with_the_same_user \
+    bash
diff --git a/docker/build.sh b/docker/build.sh
index ccecd0a13834..c94dd5bb879a 100755
--- a/docker/build.sh
+++ b/docker/build.sh
@@ -37,6 +37,11 @@ if [[ "$1" == "-it" ]]; then
     shift 1
 fi
 
+if [[ "$1" == "--net=host" ]]; then
+    CI_DOCKER_EXTRA_PARAMS+=('--net=host')
+    shift 1
+fi
+
 if [[ ! -f "${DOCKERFILE_PATH}" ]]; then
     echo "Invalid Dockerfile path: \"${DOCKERFILE_PATH}\""
     exit 1
diff --git a/docker/install/install_tvm_cpu.sh b/docker/install/install_tvm_cpu.sh
new file mode 100644
index 000000000000..51593e66506e
--- /dev/null
+++ b/docker/install/install_tvm_cpu.sh
@@ -0,0 +1,12 @@
+cd /usr
+git clone https://github.com/dmlc/tvm --recursive
+cd /usr/tvm
+echo set\(USE_LLVM llvm-config-6.0\) >> config.cmake
+echo set\(USE_RPC ON\) >> config.cmake
+echo set\(USE_SORT ON\) >> config.cmake
+echo set\(USE_GRAPH_RUNTIME ON\) >> config.cmake
+echo set\(USE_BLAS openblas\) >> config.cmake
+mkdir -p build
+cd build
+cmake ..
+make -j10
diff --git a/docker/with_the_same_user b/docker/with_the_same_user
index 1e6ab883694b..470d64384de6 100644
--- a/docker/with_the_same_user
+++ b/docker/with_the_same_user
@@ -1,7 +1,7 @@
 #!/usr/bin/env bash
 
 # This script is a wrapper creating the same user inside container as the one
-# running the ci_build.sh outside the container. It also set the home directory
+# running the docker/build.sh outside the container. It also set the home directory
 # for the user inside container to match the same absolute path as the workspace
 # outside of container.  Do not run this manually. It does not make sense. It is
 # intended to be called by ci_build.sh only.
@@ -30,5 +30,6 @@ HOME=${CI_BUILD_HOME}\
     sudo -u "#${CI_BUILD_UID}" --preserve-env\
     PATH=${PATH}\
     LD_LIBRARY_PATH=${LD_LIBRARY_PATH}\
+    PYTHONPATH=${PYTHONPATH}\
     HOME=${CI_BUILD_HOME}\
     ${COMMAND[@]}
diff --git a/docs/install/docker.rst b/docs/install/docker.rst
new file mode 100644
index 000000000000..e2000532c22c
--- /dev/null
+++ b/docs/install/docker.rst
@@ -0,0 +1,45 @@
+.. _docker-images:
+
+Docker Images
+=============
+We provide several prebuilt docker images to quickly try out tvm.
+These images are also helpful to do demo/tutorials of TVM.
+You can get the docker images via the following steps.
+We need `docker <https://docs.docker.com/engine/installation/>`_ and
+`nvidia-docker <https://github.com/NVIDIA/nvidia-docker/>`_ if we want to use cuda.
+
+First, clone tvm repo to get the auxiliary scripts
+
+.. code:: bash
+
+    git clone --recursive https://github.com/dmlc/tvm
+
+
+We can then use the following command to launch a `tvmai/demo_cpu` image.
+
+.. code:: bash
+
+    /path/to/tvm/docker/bash.sh tvmai/demo_cpu
+
+.. note::
+    You can find all the prebuilt images in `https://hub.docker.com/r/tvmai/`_
+
+
+This auxiliary script does the following things:
+
+- Mount current directory to /workspace
+- Switch user to be the same user that calls the bash.sh (so you can read/write host system)
+- Use the host-side network (so you can use jupyter notebook)
+
+
+Then you can start a jupyter notebook by typing
+
+.. code:: bash
+
+   jupyter notebook
+
+
+Docker Source
+-------------
+Check out `https://github.com/dmlc/tvm/tree/master/docker`_ if you are interested in
+building your own docker images.
diff --git a/docs/install/from_source.rst b/docs/install/from_source.rst
new file mode 100644
index 000000000000..b65e68f3ea58
--- /dev/null
+++ b/docs/install/from_source.rst
@@ -0,0 +1,154 @@
+.. _install-from-source:
+
+Install from Source
+===================
+This page gives instructions on how to build and install the tvm package from
+scratch on various systems. It consists of two steps:
+
+1. First build the shared library from the C++ codes (`libtvm.so` for linux/osx and `libtvm.dll` for windows).
+2. Setup for the language packages (e.g. Python Package).
+
+To get started, clone tvm repo from github. It is important to clone the submodules along, with ``--recursive`` option.
+
+.. code:: bash
+
+    git clone --recursive https://github.com/dmlc/tvm
+
+For windows users who use github tools, you can open the git shell, and type the following command.
+
+.. code:: bash
+
+   git submodule init
+   git submodule update
+
+
+Build the Shared Library
+------------------------
+
+Our goal is to build the shared libraries:
+
+- On Linux the target library are `libtvm.so, libtvm_topi.so`
+- On OSX the target library are `libtvm.dylib, libtvm_topi.dylib`
+- On Windows the target library are `libtvm.dll, libtvm_topi.dll`
+
+
+.. code:: bash
+
+    sudo apt-get update
+    sudo apt-get install -y python python-dev python-setuptools gcc libtinfo-dev zlib1g-dev
+
+The minimal building requirements are
+
+- A recent c++ compiler supporting C++ 11 (g++-4.8 or higher)
+- CMake 3.5 or higher
+- We highly recommend to build with LLVM to enable all the features.
+- It is possible to build without llvm dependency if we only want to use CUDA/OpenCL
+
+We use cmake to build the library.
+The configuration of tvm can be modified by `config.cmake`.
+
+
+- First, check the cmake in your system, you do not have cmake
+  you can obtain the latest version from `official website <https://cmake.org/download/>`_
+- First create a build directory, copy the ``cmake/config.cmake`` to the directory.
+
+  .. code:: bash
+
+      mkdir build
+      cp cmake/config.cmake build
+
+- Edit ``build/config.cmake`` to customize the compilation options
+
+  - On macOS, for some versions of XCode, you need to add ``-lc++abi`` in the LDFLAGS or you'll get link errors.
+
+- TVM optionally depends on LLVM. LLVM is required for CPU codegen that needs LLVM.
+
+  - LLVM 4.0 or higher is needed for build with LLVM. Note that verison of LLVM from default apt may lower than 4.0.
+  - Since LLVM takes long time to build from source, you can download pre-built version of LLVM from
+    [LLVM Download Page](http://releases.llvm.org/download.html).
+
+
+    - Unzip to a certain location, modify ``build/config.cmake`` to add ``set(USE_LLVM /path/to/your/llvm/bin/llvm-config)``
+    - You can also directly set ``set(USE_LLVM ON)`` and let cmake search for a usable version of LLVM.
+
+  - You can also use [LLVM Nightly Ubuntu Build](https://apt.llvm.org/)
+
+    - Note that apt-package append ``llvm-config`` with version number.
+      For example, set ``set(LLVM_CONFIG llvm-config-4.0)`` if you installed 4.0 package
+
+- We can then build tvm and related libraries.
+
+  .. code:: bash
+
+      cd build
+      cmake ..
+      make -j4
+
+If everything goes well, we can go to the specific language installation section.
+
+
+Building on Windows
+~~~~~~~~~~~~~~~~~~~
+
+TVM support build via MSVC using cmake. The minimum required VS version is **Visual Studio Community 2015 Update 3**.
+In order to generate the VS solution file using cmake,
+make sure you have a recent version of cmake added to your path and then from the tvm directory:
+
+.. code:: bash
+
+  mkdir build
+  cd build
+  cmake -G "Visual Studio 14 2015 Win64" -DCMAKE_BUILD_TYPE=Release -DCMAKE_CONFIGURATION_TYPES="Release" ..
+
+This will generate the VS project using the MSVC 14 64 bit generator.
+Open the .sln file in the build directory and build with Visual Studio.
+In order to build with LLVM in windows, you will need to build LLVM from source.
+You need to run build the nnvm by running the same script under the nnvm folder.
+
+Building ROCm support
+~~~~~~~~~~~~~~~~~~~~~
+
+Currently, ROCm is supported only on linux, so all the instructions are written with linux in mind.
+
+- Set ``set(USE_ROCM ON)``, set ROCM_PATH to the correct path.
+- You need to first install HIP runtime from ROCm. Make sure the installation system has ROCm installed in it.
+- Install latest stable version of LLVM (v6.0.1), and LLD, make sure ``ld.lld`` is available via command line.
+
+Python Package Installation
+---------------------------
+
+The python package is located at python
+There are several ways to install the package:
+
+1. Set the environment variable `PYTHONPATH` to tell python where to find
+   the library. For example, assume we cloned `tvm` on the home directory
+   `~`. then we can added the following line in `~/.bashrc`.
+   It is **recommended for developers** who may change the codes.
+   The changes will be immediately reflected once you pulled the code and rebuild the project (no need to call ``setup`` again)
+
+   .. code:: bash
+
+       export PYTHONPATH=/path/to/tvm/python:/path/to/tvm/topi/python:/path/to/tvm/nnvm/python:${PYTHONPATH}
+
+
+2. Install tvm python bindings by `setup.py`:
+
+   .. code:: bash
+
+       # install tvm package for the current user
+       # NOTE: if you installed python via homebrew, --user is not needed during installaiton
+       #       it will be automatically installed to your user directory.
+       #       providing --user flag may trigger error during installation in such case.
+       export MACOSX_DEPLOYMENT_TARGET=10.9  # This is required for mac to avoid symbol conflicts with libstdc++
+       cd python; python setup.py install --user; cd ..
+       cd topi/python; python setup.py install --user; cd ../..
+       cd nnvm/python; python setup.py install --user; cd ../..
+
+
+Install Contrib Libraries
+-------------------------
+
+.. toctree::
+   :maxdepth: 1
+
+   nnpack
diff --git a/docs/install/index.rst b/docs/install/index.rst
index 7e010da39a76..0653175f1141 100644
--- a/docs/install/index.rst
+++ b/docs/install/index.rst
@@ -1,151 +1,11 @@
 Installation
 ============
-This page gives instructions on how to build and install the tvm package from
-scratch on various systems. It consists of two steps:
 
-1. First build the shared library from the C++ codes (`libtvm.so` for linux/osx and `libtvm.dll` for windows).
-2. Setup for the language packages (e.g. Python Package).
-
-To get started, clone tvm repo from github. It is important to clone the submodules along, with ``--recursive`` option.
-
-.. code:: bash
-
-    git clone --recursive https://github.com/dmlc/tvm
-
-For windows users who use github tools, you can open the git shell, and type the following command.
-
-.. code:: bash
-
-   git submodule init
-   git submodule update
-
-
-Build the Shared Library
-------------------------
-
-Our goal is to build the shared libraries:
-
-- On Linux the target library are `libtvm.so, libtvm_topi.so`
-- On OSX the target library are `libtvm.dylib, libtvm_topi.dylib`
-- On Windows the target library are `libtvm.dll, libtvm_topi.dll`
-
-
-.. code:: bash
-
-    sudo apt-get update
-    sudo apt-get install -y python python-dev python-setuptools gcc libtinfo-dev zlib1g-dev
-
-The minimal building requirements are
-
-- A recent c++ compiler supporting C++ 11 (g++-4.8 or higher)
-- CMake 3.5 or higher
-- We highly recommend to build with LLVM to enable all the features.
-- It is possible to build without llvm dependency if we only want to use CUDA/OpenCL
-
-We use cmake to build the library.
-The configuration of tvm can be modified by `config.cmake`.
-
-
-- First, check the cmake in your system, you do not have cmake
-  you can obtain the latest version from `official website <https://cmake.org/download/>`_
-- First create a build directory, copy the ``cmake/config.cmake`` to the directory.
-
-  .. code:: bash
-
-      mkdir build
-      cp cmake/config.cmake build
-
-- Edit ``build/config.cmake`` to customize the compilation options
-
-  - On macOS, for some versions of XCode, you need to add ``-lc++abi`` in the LDFLAGS or you'll get link errors.
-
-- TVM optionally depends on LLVM. LLVM is required for CPU codegen that needs LLVM.
-
-  - LLVM 4.0 or higher is needed for build with LLVM. Note that verison of LLVM from default apt may lower than 4.0.
-  - Since LLVM takes long time to build from source, you can download pre-built version of LLVM from
-    [LLVM Download Page](http://releases.llvm.org/download.html).
-
-
-    - Unzip to a certain location, modify ``build/config.cmake`` to add ``set(USE_LLVM /path/to/your/llvm/bin/llvm-config)``
-    - You can also directly set ``set(USE_LLVM ON)`` and let cmake search for a usable version of LLVM.
-
-  - You can also use [LLVM Nightly Ubuntu Build](https://apt.llvm.org/)
-
-    - Note that apt-package append ``llvm-config`` with version number.
-      For example, set ``set(LLVM_CONFIG llvm-config-4.0)`` if you installed 4.0 package
-
-- We can then build tvm and related libraries.
-
-  .. code:: bash
-
-      cd build
-      cmake ..
-      make -j4
-
-If everything goes well, we can go to the specific language installation section.
-
-
-Building on Windows
-~~~~~~~~~~~~~~~~~~~
-
-TVM support build via MSVC using cmake. The minimum required VS version is **Visual Studio Community 2015 Update 3**.
-In order to generate the VS solution file using cmake,
-make sure you have a recent version of cmake added to your path and then from the tvm directory:
-
-.. code:: bash
-
-  mkdir build
-  cd build
-  cmake -G "Visual Studio 14 2015 Win64" -DCMAKE_BUILD_TYPE=Release -DCMAKE_CONFIGURATION_TYPES="Release" ..
-
-This will generate the VS project using the MSVC 14 64 bit generator.
-Open the .sln file in the build directory and build with Visual Studio.
-In order to build with LLVM in windows, you will need to build LLVM from source.
-You need to run build the nnvm by running the same script under the nnvm folder.
-
-Building ROCm support
-~~~~~~~~~~~~~~~~~~~~~
-
-Currently, ROCm is supported only on linux, so all the instructions are written with linux in mind.
-
-- Set ``set(USE_ROCM ON)``, set ROCM_PATH to the correct path.
-- You need to first install HIP runtime from ROCm. Make sure the installation system has ROCm installed in it.
-- Install latest stable version of LLVM (v6.0.1), and LLD, make sure ``ld.lld`` is available via command line.
-
-Python Package Installation
----------------------------
-
-The python package is located at python
-There are several ways to install the package:
-
-1. Set the environment variable `PYTHONPATH` to tell python where to find
-   the library. For example, assume we cloned `tvm` on the home directory
-   `~`. then we can added the following line in `~/.bashrc`.
-   It is **recommended for developers** who may change the codes.
-   The changes will be immediately reflected once you pulled the code and rebuild the project (no need to call ``setup`` again)
-
-   .. code:: bash
-
-       export PYTHONPATH=/path/to/tvm/python:/path/to/tvm/topi/python:/path/to/tvm/nnvm/python:${PYTHONPATH}
-
-
-2. Install tvm python bindings by `setup.py`:
-
-   .. code:: bash
-
-       # install tvm package for the current user
-       # NOTE: if you installed python via homebrew, --user is not needed during installaiton
-       #       it will be automatically installed to your user directory.
-       #       providing --user flag may trigger error during installation in such case.
-       export MACOSX_DEPLOYMENT_TARGET=10.9  # This is required for mac to avoid symbol conflicts with libstdc++
-       cd python; python setup.py install --user; cd ..
-       cd topi/python; python setup.py install --user; cd ../..
-       cd nnvm/python; python setup.py install --user; cd ../..
-
-Install Contrib Libraries
--------------------------
+To install TVM, please read :ref:`install-from-source`.
+If you would like to quickly try out TVM or do demo/tutorials, checkout :ref:`docker-images`
 
 .. toctree::
-   :maxdepth: 1
+   :maxdepth: 2
 
-   nnpack
+   from_source
+   docker

From fa2e4287efc4ec08353385e776c0eea4a1376a42 Mon Sep 17 00:00:00 2001
From: MORITA Kazutaka <morita.kazutaka@lab.ntt.co.jp>
Date: Tue, 10 Jul 2018 01:41:33 +0900
Subject: [PATCH 807/949] Add support for multiple OpenCL platforms (#1345)

---
 cmake/config.cmake                            |   3 +
 cmake/modules/OpenCL.cmake                    |  12 +
 include/tvm/runtime/c_runtime_api.h           |   1 +
 python/tvm/_ffi/runtime_ctypes.py             |   3 +-
 python/tvm/contrib/sdaccel.py                 |  52 +--
 src/codegen/codegen_vhls.cc                   |   4 +-
 src/codegen/opt/build_sdaccel_off.cc          |  21 ++
 src/pass/verify_memory.cc                     |   6 +-
 src/runtime/c_runtime_api.cc                  |   1 +
 src/runtime/opencl/opencl_common.h            | 107 +++++-
 src/runtime/opencl/opencl_device_api.cc       |  54 +--
 src/runtime/opencl/opencl_module.cc           | 308 +++++++-----------
 src/runtime/opencl/opencl_module.h            |   2 +-
 src/runtime/opencl/sdaccel/sdaccel_common.h   |  42 +++
 .../opencl/sdaccel/sdaccel_device_api.cc      |  47 +++
 src/runtime/opencl/sdaccel/sdaccel_module.cc  |  78 +++++
 src/runtime/opencl/sdaccel/sdaccel_module.h   |  31 ++
 17 files changed, 500 insertions(+), 272 deletions(-)
 create mode 100644 src/codegen/opt/build_sdaccel_off.cc
 create mode 100644 src/runtime/opencl/sdaccel/sdaccel_common.h
 create mode 100644 src/runtime/opencl/sdaccel/sdaccel_device_api.cc
 create mode 100644 src/runtime/opencl/sdaccel/sdaccel_module.cc
 create mode 100644 src/runtime/opencl/sdaccel/sdaccel_module.h

diff --git a/cmake/config.cmake b/cmake/config.cmake
index d2576ab30e1e..89f5a0597e43 100644
--- a/cmake/config.cmake
+++ b/cmake/config.cmake
@@ -39,6 +39,9 @@ set(USE_CUDA OFF)
 # - /path/to/rocm: use specific path to rocm
 set(USE_ROCM OFF)
 
+# Whether enable SDAccel runtime
+set(USE_SDACCEL OFF)
+
 # Whether enable OpenCL runtime
 set(USE_OPENCL OFF)
 
diff --git a/cmake/modules/OpenCL.cmake b/cmake/modules/OpenCL.cmake
index db7a1834feb1..8ae4b29aff48 100644
--- a/cmake/modules/OpenCL.cmake
+++ b/cmake/modules/OpenCL.cmake
@@ -7,6 +7,18 @@ if(OpenCL_FOUND)
   include_directories(${OpenCL_INCLUDE_DIRS})
 endif(OpenCL_FOUND)
 
+if(USE_SDACCEL)
+  message(STATUS "Build with SDAccel support")
+  file(GLOB RUNTIME_SDACCEL_SRCS src/runtime/opencl/sdaccel/*.cc)
+  list(APPEND RUNTIME_SRCS ${RUNTIME_SDACCEL_SRCS})
+  if(NOT USE_OPENCL)
+    message(STATUS "Enable OpenCL support required for SDAccel")
+    set(USE_OPENCL ON)
+  endif()
+else()
+  list(APPEND COMPILER_SRCS src/codegen/opt/build_sdaccel_off.cc)
+endif(USE_SDACCEL)
+
 if(USE_OPENCL)
   find_package(OpenCL REQUIRED)
   message(STATUS "Build with OpenCL support")
diff --git a/include/tvm/runtime/c_runtime_api.h b/include/tvm/runtime/c_runtime_api.h
index c61a4884a1f5..7f3a11d9ddab 100644
--- a/include/tvm/runtime/c_runtime_api.h
+++ b/include/tvm/runtime/c_runtime_api.h
@@ -60,6 +60,7 @@ typedef int64_t tvm_index_t;
 
 /*! \brief Extension device types in TVM */
 typedef enum {
+  kDLSDAccel = 6,
   kDLVulkan = 7,
   kOpenGL = 11,
   // Extension DRAM type, used for quickly test extension device
diff --git a/python/tvm/_ffi/runtime_ctypes.py b/python/tvm/_ffi/runtime_ctypes.py
index d862a5abd497..9609f867576b 100644
--- a/python/tvm/_ffi/runtime_ctypes.py
+++ b/python/tvm/_ffi/runtime_ctypes.py
@@ -95,6 +95,7 @@ class TVMContext(ctypes.Structure):
         1 : 'cpu',
         2 : 'gpu',
         4 : 'opencl',
+        6 : 'sdaccel',
         7 : 'vulkan',
         8 : 'metal',
         9 : 'vpi',
@@ -111,7 +112,7 @@ class TVMContext(ctypes.Structure):
         'nvptx': 2,
         'cl': 4,
         'opencl': 4,
-        'sdaccel': 4,
+        'sdaccel': 6,
         'vulkan': 7,
         'metal': 8,
         'vpi': 9,
diff --git a/python/tvm/contrib/sdaccel.py b/python/tvm/contrib/sdaccel.py
index 12dba31a4ab5..6a0c8c3390cf 100644
--- a/python/tvm/contrib/sdaccel.py
+++ b/python/tvm/contrib/sdaccel.py
@@ -1,58 +1,10 @@
 """Utility for Interacting with SDAccel Tools"""
 import subprocess
 import os
-import re
 from . import util
 from ..api import register_func
 
 
-def _vhls_to_opencl(code):
-    """Convert source code from Vivado HLS to OpenCL."""
-    out = ''
-    for line in code.split('\n'):
-        if re.match(r'#include', line):
-            # OpenCL doesn't support include.
-            continue
-        if re.match(r'#pragma', line):
-            # Remove Vivado HLS specific pragmas.
-            continue
-
-        if re.match(r'extern "C"', line):
-            line = re.sub(r'^extern "C"', "__kernel", line)
-            # Add __global to pointer parameters.
-            line = re.sub(r'(\w+)\s*\*', r"__global \1*", line)
-
-        out += line + '\n'
-
-    return out
-
-
-def _fake_compile_vhls(code):
-    """Fake compile Vivado HLS code for SDAccel.
-
-    Compile the Vivado HLS code as an OpenCL code, and generate a program
-    binary for GPU which can be used instead of xclbin.
-
-    Parameters
-    ----------
-    code : str
-        The Vivado HLS code.
-
-    Return
-    ------
-    binary : bytearray
-        The program binary which can be passed to clCreateProgramWithBinary
-    """
-    try:
-        import pyopencl as cl
-    except ImportError:
-        raise ImportError('PyOpenCL is required for testing SDAccel backend.')
-    ctx = cl.Context(dev_type=cl.device_type.GPU)
-    program = cl.Program(ctx, _vhls_to_opencl(code)).build()
-    binary = bytearray(program.binaries[0])
-    return binary
-
-
 @register_func("tvm_callback_sdaccel_compile")
 def compile_vhls(code, kernel):
     """Compile Vivado HLS code for SDAccel.
@@ -87,9 +39,7 @@ def compile_vhls(code, kernel):
     platform = os.environ.get("XCL_PLATFORM", os.environ.get("AWS_PLATFORM"))
 
     if platform is None:
-        # If we don't have the Xilinx toolchain, create a program binary for
-        # GPU and use it for testing.
-        return _fake_compile_vhls(code)
+        raise RuntimeError("No Xlinx device specified.")
 
     # build xo
     args = [xocc, "-c", "-t", target, "--platform", platform, "-o", tmp_xo, "-k", kernel] + \
diff --git a/src/codegen/codegen_vhls.cc b/src/codegen/codegen_vhls.cc
index 3cb8b9a346c2..f27a007d37a1 100644
--- a/src/codegen/codegen_vhls.cc
+++ b/src/codegen/codegen_vhls.cc
@@ -6,7 +6,7 @@
 #include <string>
 #include "./codegen_vhls.h"
 #include "./build_common.h"
-#include "../runtime/opencl/opencl_module.h"
+#include "../runtime/opencl/sdaccel/sdaccel_module.h"
 
 namespace tvm {
 namespace codegen {
@@ -91,7 +91,7 @@ runtime::Module BuildSDAccel(Array<LoweredFunc> funcs) {
   } else {
     LOG(FATAL) << "Cannot compile Vivado HLS code.";
   }
-  return OpenCLModuleCreate(xclbin, "xclbin", ExtractFuncInfo(funcs), code);
+  return SDAccelModuleCreate(xclbin, "xclbin", ExtractFuncInfo(funcs), code);
 }
 
 TVM_REGISTER_API("codegen.build_sdaccel")
diff --git a/src/codegen/opt/build_sdaccel_off.cc b/src/codegen/opt/build_sdaccel_off.cc
new file mode 100644
index 000000000000..f5e57cb10f9e
--- /dev/null
+++ b/src/codegen/opt/build_sdaccel_off.cc
@@ -0,0 +1,21 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ *  Optional module when build opencl is switched to off
+ */
+#include "../codegen_source_base.h"
+#include "../../runtime/opencl/opencl_module.h"
+
+namespace tvm {
+namespace runtime {
+
+Module SDAccelModuleCreate(
+    std::string data,
+    std::string fmt,
+    std::unordered_map<std::string, FunctionInfo> fmap,
+    std::string source) {
+  LOG(WARNING) << "OpenCL runtime not enabled, return a source module...";
+  return codegen::DeviceSourceModuleCreate(data, fmt, fmap, "sdaccel");
+}
+
+}  // namespace runtime
+}  // namespace tvm
diff --git a/src/pass/verify_memory.cc b/src/pass/verify_memory.cc
index e928bedf266c..38cd05a4bdd0 100644
--- a/src/pass/verify_memory.cc
+++ b/src/pass/verify_memory.cc
@@ -36,7 +36,7 @@ class MemoryAccessVerifier final : protected IRVisitor {
 
   /// Interface to perform memory access verification
   void Run() {
-    if (!IsGPUDevice(dev_type_)) return;
+    if (!IsGPUDevice(dev_type_) && !IsFPGADevice(dev_type_)) return;
     IRVisitor::Visit(func_->body);
   }
 
@@ -143,6 +143,10 @@ class MemoryAccessVerifier final : protected IRVisitor {
            kDLVulkan == dev_type || kDLMetal == dev_type ||
            kDLROCM == dev_type || kOpenGL == dev_type;
   }
+  /// Check if a given DLDeviceType/TVMDeviceExtType value denotes FPGA device.
+  static bool IsFPGADevice(int dev_type) {
+    return kDLSDAccel == dev_type;
+  }
 
  private:
   /// Status of visitor
diff --git a/src/runtime/c_runtime_api.cc b/src/runtime/c_runtime_api.cc
index df3a19f306e0..5eb39abcc71a 100644
--- a/src/runtime/c_runtime_api.cc
+++ b/src/runtime/c_runtime_api.cc
@@ -31,6 +31,7 @@ inline std::string DeviceName(int type) {
     case kDLCPU: return "cpu";
     case kDLGPU: return "gpu";
     case kDLOpenCL: return "opencl";
+    case kDLSDAccel: return "sdaccel";
     case kDLVulkan: return "vulkan";
     case kDLMetal: return "metal";
     case kDLVPI: return "vpi";
diff --git a/src/runtime/opencl/opencl_common.h b/src/runtime/opencl/opencl_common.h
index e2db24f85769..46b212bee464 100644
--- a/src/runtime/opencl/opencl_common.h
+++ b/src/runtime/opencl/opencl_common.h
@@ -21,6 +21,10 @@
 #include <string>
 #include <vector>
 #include "../workspace_pool.h"
+#include "../pack_args.h"
+#include "../thread_storage_scope.h"
+#include "../meta_data.h"
+#include "../file_util.h"
 
 namespace tvm {
 namespace runtime {
@@ -97,17 +101,23 @@ inline const char* CLGetErrorString(cl_int error) {
     OPENCL_CHECK_ERROR(e);                                            \
   }
 
+class OpenCLThreadEntry;
+
 /*!
  * \brief Process global OpenCL workspace.
  */
-class OpenCLWorkspace final : public DeviceAPI {
+class OpenCLWorkspace : public DeviceAPI {
  public:
   // global platform id
   cl_platform_id platform_id;
+  // global platform name
+  std::string platform_name;
   // global context of this process
   cl_context context{nullptr};
   // whether the workspace it initialized.
   bool initialized_{false};
+  // the device type
+  std::string device_type;
   // the devices
   std::vector<cl_device_id> devices;
   // the queues
@@ -128,10 +138,17 @@ class OpenCLWorkspace final : public DeviceAPI {
     }
   }
   // Initialzie the device.
-  void Init();
+  void Init(const std::string& device_type, const std::string& platform_name = "");
+  virtual void Init() {
+    Init("gpu");
+  }
+  // Check whether the context is OpenCL or not.
+  virtual bool IsOpenCLDevice(TVMContext ctx) {
+    return ctx.device_type == kDLOpenCL;
+  }
   // get the queue of the context
   cl_command_queue GetQueue(TVMContext ctx) {
-    CHECK_EQ(ctx.device_type, kDLOpenCL);
+    CHECK(IsOpenCLDevice(ctx));
     this->Init();
     CHECK(ctx.device_id >= 0  && static_cast<size_t>(ctx.device_id) < queues.size())
         << "Invalid OpenCL device_id=" << ctx.device_id;
@@ -157,6 +174,12 @@ class OpenCLWorkspace final : public DeviceAPI {
   void StreamSync(TVMContext ctx, TVMStreamHandle stream) final;
   void* AllocWorkspace(TVMContext ctx, size_t size, TVMType type_hint) final;
   void FreeWorkspace(TVMContext ctx, void* data) final;
+
+  /*!
+   * \brief Get the thread local ThreadEntry
+   */
+  virtual OpenCLThreadEntry* GetThreadEntry();
+
   // get the global workspace
   static const std::shared_ptr<OpenCLWorkspace>& Global();
 };
@@ -179,15 +202,87 @@ class OpenCLThreadEntry {
   /*! \brief workspace pool */
   WorkspacePool pool;
   // constructor
-  OpenCLThreadEntry()
-      : pool(kDLOpenCL, OpenCLWorkspace::Global()) {
+  OpenCLThreadEntry(DLDeviceType device_type, std::shared_ptr<DeviceAPI> device)
+      : pool(device_type, device) {
     context.device_id = 0;
-    context.device_type = kDLOpenCL;
+    context.device_type = device_type;
   }
+  OpenCLThreadEntry()
+      : OpenCLThreadEntry(kDLOpenCL, OpenCLWorkspace::Global()) {}
+
   // get the global workspace
   static OpenCLThreadEntry* ThreadLocal();
 };
 }  // namespace cl
+
+// Module to support thread-safe multi-device execution.
+// OpenCL runtime is a bit tricky because clSetKernelArg is not thread-safe
+// To make the call thread-safe, we create a thread-local kernel table
+// and lazily install new kernels into the kernel table when the kernel is called.
+// The kernels are recycled when the module get destructed.
+class OpenCLModuleNode : public ModuleNode {
+ public:
+  // Kernel table reference entry.
+  struct KTRefEntry {
+    size_t kernel_id;
+    size_t version;
+  };
+  explicit OpenCLModuleNode(std::string data,
+                            std::string fmt,
+                            std::unordered_map<std::string, FunctionInfo> fmap,
+                            std::string source)
+      : data_(data), fmt_(fmt), fmap_(fmap), source_(source) {}
+  // destructor
+  ~OpenCLModuleNode();
+
+  /*!
+   * \brief Get the global workspace
+   */
+  virtual const std::shared_ptr<cl::OpenCLWorkspace>& GetGlobalWorkspace();
+
+  virtual const char* type_key() const;
+
+  PackedFunc GetFunction(
+      const std::string& name,
+      const std::shared_ptr<ModuleNode>& sptr_to_self) final;
+  void SaveToFile(const std::string& file_name,
+                  const std::string& format) final;
+  void SaveToBinary(dmlc::Stream* stream) final;
+  std::string GetSource(const std::string& format) final;
+  // Initialize the programs
+  void Init();
+  // install a new kernel to thread local entry
+  cl_kernel InstallKernel(cl::OpenCLWorkspace* w,
+                          cl::OpenCLThreadEntry* t,
+                          const std::string& func_name,
+                          const KTRefEntry& e);
+
+ protected:
+  // The workspace, need to keep reference to use it in destructor.
+  // In case of static destruction order problem.
+  std::shared_ptr<cl::OpenCLWorkspace> workspace_;
+  // the binary data
+  std::string data_;
+
+ private:
+  // The format
+  std::string fmt_;
+  // function information table.
+  std::unordered_map<std::string, FunctionInfo> fmap_;
+  // Module local mutex
+  std::mutex build_lock_;
+  // The OpenCL source.
+  std::string source_;
+  // the binary data
+  cl_program program_{nullptr};
+  // build info
+  std::vector<bool> device_built_flag_;
+  // kernel id cache
+  std::unordered_map<std::string, KTRefEntry> kid_map_;
+  // kernels build so far.
+  std::vector<cl_kernel> kernels_;
+};
+
 }  // namespace runtime
 }  // namespace tvm
 #endif  // TVM_RUNTIME_OPENCL_OPENCL_COMMON_H_
diff --git a/src/runtime/opencl/opencl_device_api.cc b/src/runtime/opencl/opencl_device_api.cc
index 75eae6364648..8c884bdc14b6 100644
--- a/src/runtime/opencl/opencl_device_api.cc
+++ b/src/runtime/opencl/opencl_device_api.cc
@@ -10,13 +10,17 @@ namespace tvm {
 namespace runtime {
 namespace cl {
 
+OpenCLThreadEntry* OpenCLWorkspace::GetThreadEntry() {
+  return OpenCLThreadEntry::ThreadLocal();
+}
+
 const std::shared_ptr<OpenCLWorkspace>& OpenCLWorkspace::Global() {
   static std::shared_ptr<OpenCLWorkspace> inst = std::make_shared<OpenCLWorkspace>();
   return inst;
 }
 
 void OpenCLWorkspace::SetDevice(TVMContext ctx) {
-  OpenCLThreadEntry::ThreadLocal()->context.device_id = ctx.device_id;
+  GetThreadEntry()->context.device_id = ctx.device_id;
 }
 
 void OpenCLWorkspace::GetAttr(
@@ -121,13 +125,13 @@ void OpenCLWorkspace::CopyDataFromTo(const void* from,
                                      TVMStreamHandle stream) {
   this->Init();
   CHECK(stream == nullptr);
-  if (ctx_from.device_type == kDLOpenCL && ctx_to.device_type == kDLOpenCL) {
+  if (IsOpenCLDevice(ctx_from) && IsOpenCLDevice(ctx_to)) {
     OPENCL_CALL(clEnqueueCopyBuffer(
         this->GetQueue(ctx_to),
         static_cast<cl_mem>((void*)from),  // NOLINT(*)
         static_cast<cl_mem>(to),
         from_offset, to_offset, size, 0, nullptr, nullptr));
-  } else if (ctx_from.device_type == kDLOpenCL && ctx_to.device_type == kDLCPU) {
+  } else if (IsOpenCLDevice(ctx_from) && ctx_to.device_type == kDLCPU) {
     OPENCL_CALL(clEnqueueReadBuffer(
         this->GetQueue(ctx_from),
         static_cast<cl_mem>((void*)from),  // NOLINT(*)
@@ -135,7 +139,7 @@ void OpenCLWorkspace::CopyDataFromTo(const void* from,
         static_cast<char*>(to) + to_offset,
         0, nullptr, nullptr));
     OPENCL_CALL(clFinish(this->GetQueue(ctx_from)));
-  } else if (ctx_from.device_type == kDLCPU && ctx_to.device_type == kDLOpenCL) {
+  } else if (ctx_from.device_type == kDLCPU && IsOpenCLDevice(ctx_to)) {
     OPENCL_CALL(clEnqueueWriteBuffer(
         this->GetQueue(ctx_to),
         static_cast<cl_mem>(to),
@@ -156,11 +160,11 @@ void OpenCLWorkspace::StreamSync(TVMContext ctx, TVMStreamHandle stream) {
 void* OpenCLWorkspace::AllocWorkspace(TVMContext ctx,
                                       size_t size,
                                       TVMType type_hint) {
-  return OpenCLThreadEntry::ThreadLocal()->pool.AllocWorkspace(ctx, size);
+  return GetThreadEntry()->pool.AllocWorkspace(ctx, size);
 }
 
 void OpenCLWorkspace::FreeWorkspace(TVMContext ctx, void* data) {
-  OpenCLThreadEntry::ThreadLocal()->pool.FreeWorkspace(ctx, data);
+  GetThreadEntry()->pool.FreeWorkspace(ctx, data);
 }
 
 typedef dmlc::ThreadLocalStore<OpenCLThreadEntry> OpenCLThreadStore;
@@ -223,38 +227,39 @@ bool MatchPlatformInfo(
   return param_value.find(value) != std::string::npos;
 }
 
-void OpenCLWorkspace::Init() {
+void OpenCLWorkspace::Init(const std::string& device_type, const std::string& platform_name) {
   if (initialized_) return;
   std::lock_guard<std::mutex> lock(this->mu);
   if (initialized_) return;
   initialized_ = true;
   if (context != nullptr) return;
   // matched platforms
-  std::vector<cl_platform_id> platform_matched = cl::GetPlatformIDs();
-  if (platform_matched.size() == 0) {
+  std::vector<cl_platform_id> platform_ids = cl::GetPlatformIDs();
+  if (platform_ids.size() == 0) {
     LOG(WARNING) << "No OpenCL platform matched given existing options ...";
     return;
   }
-  if (platform_matched.size() > 1) {
-    LOG(WARNING) << "Multiple OpenCL platforms matched, use the first one ... ";
-  }
-  this->platform_id = platform_matched[0];
-  LOG(INFO) << "Initialize OpenCL platform \'"
-            << cl::GetPlatformInfo(this->platform_id, CL_PLATFORM_NAME) << '\'';
-  std::string device_types[] = {"accelerator", "gpu", "cpu"};
-  std::vector<cl_device_id> devices_matched;
-  for (auto type : device_types) {
-    devices_matched = cl::GetDeviceIDs(this->platform_id, type);
+  this->platform_id = nullptr;
+  for (auto platform_id : platform_ids) {
+    if (!MatchPlatformInfo(platform_id, CL_PLATFORM_NAME, platform_name)) {
+      continue;
+    }
+    std::vector<cl_device_id> devices_matched = cl::GetDeviceIDs(platform_id, device_type);
     if (devices_matched.size() > 0) {
+      this->platform_id = platform_id;
+      this->platform_name = cl::GetPlatformInfo(platform_id, CL_PLATFORM_NAME);
+      this->device_type = device_type;
+      this->devices = devices_matched;
+      LOG(INFO) << "Initialize OpenCL platform \'" << this->platform_name << '\'';
       break;
     }
-    LOG(INFO) << "No OpenCL device any device matched given the options: " << type << " mode";
+    LOG(INFO) << "\'" << cl::GetPlatformInfo(platform_id, CL_PLATFORM_NAME)
+              << "\' platform has no OpenCL device: " << device_type << " mode";
   }
-  if (devices_matched.size() == 0) {
+  if (this->platform_id == nullptr) {
     LOG(WARNING) << "No OpenCL device";
     return;
   }
-  this->devices = devices_matched;
   cl_int err_code;
   this->context = clCreateContext(
       nullptr, this->devices.size(), &(this->devices[0]),
@@ -272,11 +277,6 @@ void OpenCLWorkspace::Init() {
   }
 }
 
-bool InitOpenCL(TVMArgs args, TVMRetValue* rv) {
-  cl::OpenCLWorkspace::Global()->Init();
-  return true;
-}
-
 TVM_REGISTER_GLOBAL("device_api.opencl")
 .set_body([](TVMArgs args, TVMRetValue* rv) {
     DeviceAPI* ptr = OpenCLWorkspace::Global().get();
diff --git a/src/runtime/opencl/opencl_module.cc b/src/runtime/opencl/opencl_module.cc
index 5d67ad286875..424a6dccff3d 100644
--- a/src/runtime/opencl/opencl_module.cc
+++ b/src/runtime/opencl/opencl_module.cc
@@ -10,180 +10,9 @@
 #include "./opencl_common.h"
 #include "./opencl_module.h"
 
-#include "../pack_args.h"
-#include "../thread_storage_scope.h"
-#include "../meta_data.h"
-#include "../file_util.h"
-
 namespace tvm {
 namespace runtime {
 
-// Module to support thread-safe multi-device execution.
-// OpenCL runtime is a bit tricky because clSetKernelArg is not thread-safe
-// To make the call thread-safe, we create a thread-local kernel table
-// and lazily install new kernels into the kernel table when the kernel is called.
-// The kernels are recycled when the module get destructed.
-class OpenCLModuleNode : public ModuleNode {
- public:
-  // Kernel table reference entry.
-  struct KTRefEntry {
-    size_t kernel_id;
-    size_t version;
-  };
-  explicit OpenCLModuleNode(std::string data,
-                            std::string fmt,
-                            std::unordered_map<std::string, FunctionInfo> fmap,
-                            std::string source)
-      : data_(data), fmt_(fmt), fmap_(fmap), source_(source) {}
-  // destructor
-  ~OpenCLModuleNode() {
-    {
-      // free the kernel ids in global table.
-      std::lock_guard<std::mutex> lock(workspace_->mu);
-      for (auto& kv : kid_map_) {
-        workspace_->free_kernel_ids.push_back(kv.second.kernel_id);
-      }
-    }
-    // free the kernels
-    for (cl_kernel k : kernels_) {
-      OPENCL_CALL(clReleaseKernel(k));
-    }
-    if (program_) {
-      OPENCL_CALL(clReleaseProgram(program_));
-    }
-  }
-
-  const char* type_key() const final {
-    return "opencl";
-  }
-
-  PackedFunc GetFunction(
-      const std::string& name,
-      const std::shared_ptr<ModuleNode>& sptr_to_self) final;
-
-  void SaveToFile(const std::string& file_name,
-                  const std::string& format) final {
-    std::string fmt = GetFileFormat(file_name, format);
-    CHECK_EQ(fmt, fmt_)
-        << "Can only save to format=" << fmt_;
-    std::string meta_file = GetMetaFilePath(file_name);
-    SaveMetaDataToFile(meta_file, fmap_);
-    SaveBinaryToFile(file_name, data_);
-  }
-
-  void SaveToBinary(dmlc::Stream* stream) final {
-    stream->Write(fmt_);
-    stream->Write(fmap_);
-    stream->Write(data_);
-  }
-
-  std::string GetSource(const std::string& format) final {
-    if (format == fmt_) return data_;
-    if (fmt_ == "cl") {
-      return data_;
-    } else {
-      return source_;
-    }
-  }
-
-  // Initialize the programs
-  void Init() {
-    workspace_ = cl::OpenCLWorkspace::Global();
-    workspace_->Init();
-    CHECK(workspace_->context != nullptr) << "No OpenCL device";
-    if (fmt_ == "cl") {
-      const char* s = data_.c_str();
-      size_t len = data_.length();
-      cl_int err;
-      program_ = clCreateProgramWithSource(
-          workspace_->context, 1, &s, &len, &err);
-      OPENCL_CHECK_ERROR(err);
-    } else if (fmt_ == "xclbin" || fmt_ == "awsxclbin") {
-      const unsigned char* s = (const unsigned char *)data_.c_str();
-      size_t len = data_.length();
-      cl_int err;
-      program_ = clCreateProgramWithBinary(
-          workspace_->context, 1, &(workspace_->devices[0]), &len, &s, NULL, &err);
-      if (err != CL_SUCCESS) {
-        LOG(ERROR) << "OpenCL Error: " << cl::CLGetErrorString(err);
-      }
-    } else {
-      LOG(FATAL) << "Unknown OpenCL format " << fmt_;
-    }
-    device_built_flag_.resize(workspace_->devices.size(), false);
-    // initialize the kernel id, need to lock global table.
-    std::lock_guard<std::mutex> lock(workspace_->mu);
-    for (const auto& kv : fmap_) {
-      const std::string& key = kv.first;
-      KTRefEntry e;
-      if (workspace_->free_kernel_ids.size() != 0) {
-        e.kernel_id = workspace_->free_kernel_ids.back();
-        workspace_->free_kernel_ids.pop_back();
-      } else {
-        e.kernel_id = workspace_->num_registered_kernels++;
-      }
-      e.version = workspace_->timestamp++;
-      kid_map_[key] = e;
-    }
-  }
-  // install a new kernel to thread local entry
-  cl_kernel InstallKernel(cl::OpenCLWorkspace* w,
-                          cl::OpenCLThreadEntry* t,
-                          const std::string& func_name,
-                          const KTRefEntry& e) {
-    std::lock_guard<std::mutex> lock(build_lock_);
-    int device_id = t->context.device_id;
-    if (!device_built_flag_[device_id]) {
-      // build program
-      cl_int err;
-      cl_device_id dev = w->devices[device_id];
-      err = clBuildProgram(program_, 1, &dev, nullptr, nullptr, nullptr);
-      if (err != CL_SUCCESS) {
-        size_t len;
-        std::string log;
-        clGetProgramBuildInfo(
-            program_, dev, CL_PROGRAM_BUILD_LOG, 0, nullptr, &len);
-        log.resize(len);
-        clGetProgramBuildInfo(
-            program_, dev, CL_PROGRAM_BUILD_LOG, len, &log[0], nullptr);
-        LOG(FATAL) << "OpenCL build error for device=" << dev << log;
-      }
-      device_built_flag_[device_id] = true;
-    }
-    // build kernel
-    cl_int err;
-    cl_kernel kernel = clCreateKernel(program_, func_name.c_str(), &err);
-    OPENCL_CHECK_ERROR(err);
-    t->kernel_table[e.kernel_id].kernel = kernel;
-    t->kernel_table[e.kernel_id].version = e.version;
-    kernels_.push_back(kernel);
-    return kernel;
-  }
-
- private:
-  // The workspace, need to keep reference to use it in destructor.
-  // In case of static destruction order problem.
-  std::shared_ptr<cl::OpenCLWorkspace> workspace_;
-  // the binary data
-  std::string data_;
-  // The format
-  std::string fmt_;
-  // function information table.
-  std::unordered_map<std::string, FunctionInfo> fmap_;
-  // Module local mutex
-  std::mutex build_lock_;
-  // The OpenCL source.
-  std::string source_;
-  // the binary data
-  cl_program program_{nullptr};
-  // build info
-  std::vector<bool> device_built_flag_;
-  // kernel id cache
-  std::unordered_map<std::string, KTRefEntry> kid_map_;
-  // kernels build so far.
-  std::vector<cl_kernel> kernels_;
-};
-
 class OpenCLWrappedFunc {
  public:
   // initialize the OpenCL function.
@@ -193,7 +22,7 @@ class OpenCLWrappedFunc {
             std::string func_name,
             std::vector<size_t> arg_size,
             const std::vector<std::string>& thread_axis_tags)  {
-    w_ = cl::OpenCLWorkspace::Global().get();
+    w_ = m->GetGlobalWorkspace().get();
     m_ = m;
     sptr_ = sptr;
     entry_ = entry;
@@ -205,7 +34,7 @@ class OpenCLWrappedFunc {
   void operator()(TVMArgs args,
                   TVMRetValue* rv,
                   void** void_args) const {
-    cl::OpenCLThreadEntry* t = cl::OpenCLThreadEntry::ThreadLocal();
+    cl::OpenCLThreadEntry* t = w_->GetThreadEntry();
     // get the kernel from thread local kernel table.
     if (entry_.kernel_id >= t->kernel_table.size()) {
       t->kernel_table.resize(entry_.kernel_id + 1);
@@ -250,6 +79,31 @@ class OpenCLWrappedFunc {
   ThreadAxisConfig thread_axis_cfg_;
 };
 
+OpenCLModuleNode::~OpenCLModuleNode() {
+  {
+    // free the kernel ids in global table.
+    std::lock_guard<std::mutex> lock(workspace_->mu);
+    for (auto& kv : kid_map_) {
+      workspace_->free_kernel_ids.push_back(kv.second.kernel_id);
+    }
+  }
+  // free the kernels
+  for (cl_kernel k : kernels_) {
+    OPENCL_CALL(clReleaseKernel(k));
+  }
+  if (program_) {
+    OPENCL_CALL(clReleaseProgram(program_));
+  }
+}
+
+const std::shared_ptr<cl::OpenCLWorkspace>& OpenCLModuleNode::GetGlobalWorkspace() {
+  return cl::OpenCLWorkspace::Global();
+}
+
+const char* OpenCLModuleNode::type_key() const {
+  return "opencl";
+}
+
 PackedFunc OpenCLModuleNode::GetFunction(
     const std::string& name,
     const std::shared_ptr<ModuleNode>& sptr_to_self) {
@@ -279,6 +133,104 @@ PackedFunc OpenCLModuleNode::GetFunction(
   return PackFuncVoidAddr(f, info.arg_types);
 }
 
+void OpenCLModuleNode::SaveToFile(const std::string& file_name,
+                                  const std::string& format) {
+  std::string fmt = GetFileFormat(file_name, format);
+  CHECK_EQ(fmt, fmt_)
+      << "Can only save to format=" << fmt_;
+  std::string meta_file = GetMetaFilePath(file_name);
+  SaveMetaDataToFile(meta_file, fmap_);
+  SaveBinaryToFile(file_name, data_);
+}
+
+void OpenCLModuleNode::SaveToBinary(dmlc::Stream* stream) {
+  stream->Write(fmt_);
+  stream->Write(fmap_);
+  stream->Write(data_);
+}
+
+std::string OpenCLModuleNode::GetSource(const std::string& format) {
+  if (format == fmt_) return data_;
+  if (fmt_ == "cl") {
+    return data_;
+  } else {
+    return source_;
+  }
+}
+
+void OpenCLModuleNode::Init() {
+  workspace_ = GetGlobalWorkspace();
+  workspace_->Init();
+  CHECK(workspace_->context != nullptr) << "No OpenCL device";
+  if (fmt_ == "cl") {
+    const char* s = data_.c_str();
+    size_t len = data_.length();
+    cl_int err;
+    program_ = clCreateProgramWithSource(
+        workspace_->context, 1, &s, &len, &err);
+    OPENCL_CHECK_ERROR(err);
+  } else if (fmt_ == "xclbin" || fmt_ == "awsxclbin") {
+    const unsigned char* s = (const unsigned char *)data_.c_str();
+    size_t len = data_.length();
+    cl_int err;
+    program_ = clCreateProgramWithBinary(
+        workspace_->context, 1, &(workspace_->devices[0]), &len, &s, NULL, &err);
+    if (err != CL_SUCCESS) {
+      LOG(ERROR) << "OpenCL Error: " << cl::CLGetErrorString(err);
+    }
+  } else {
+    LOG(FATAL) << "Unknown OpenCL format " << fmt_;
+  }
+  device_built_flag_.resize(workspace_->devices.size(), false);
+  // initialize the kernel id, need to lock global table.
+  std::lock_guard<std::mutex> lock(workspace_->mu);
+  for (const auto& kv : fmap_) {
+    const std::string& key = kv.first;
+    KTRefEntry e;
+    if (workspace_->free_kernel_ids.size() != 0) {
+      e.kernel_id = workspace_->free_kernel_ids.back();
+      workspace_->free_kernel_ids.pop_back();
+    } else {
+      e.kernel_id = workspace_->num_registered_kernels++;
+    }
+    e.version = workspace_->timestamp++;
+    kid_map_[key] = e;
+  }
+}
+
+cl_kernel OpenCLModuleNode::InstallKernel(cl::OpenCLWorkspace* w,
+                                          cl::OpenCLThreadEntry* t,
+                                          const std::string& func_name,
+                                          const KTRefEntry& e) {
+  std::lock_guard<std::mutex> lock(build_lock_);
+  int device_id = t->context.device_id;
+  if (!device_built_flag_[device_id]) {
+    // build program
+    cl_int err;
+    cl_device_id dev = w->devices[device_id];
+    err = clBuildProgram(program_, 1, &dev, nullptr, nullptr, nullptr);
+    if (err != CL_SUCCESS) {
+      size_t len;
+      std::string log;
+      clGetProgramBuildInfo(
+          program_, dev, CL_PROGRAM_BUILD_LOG, 0, nullptr, &len);
+      log.resize(len);
+      clGetProgramBuildInfo(
+          program_, dev, CL_PROGRAM_BUILD_LOG, len, &log[0], nullptr);
+      LOG(FATAL) << "OpenCL build error for device=" << dev << log;
+    }
+    device_built_flag_[device_id] = true;
+  }
+  // build kernel
+  cl_int err;
+  cl_kernel kernel = clCreateKernel(program_, func_name.c_str(), &err);
+  OPENCL_CHECK_ERROR(err);
+  t->kernel_table[e.kernel_id].kernel = kernel;
+  t->kernel_table[e.kernel_id].version = e.version;
+  kernels_.push_back(kernel);
+  return kernel;
+}
+
 Module OpenCLModuleCreate(
     std::string data,
     std::string fmt,
@@ -323,16 +275,6 @@ TVM_REGISTER_GLOBAL("module.loadfile_clbin")
     *rv = OpenCLModuleLoadFile(args[0], args[1]);
   });
 
-TVM_REGISTER_GLOBAL("module.loadfile_xclbin")
-.set_body([](TVMArgs args, TVMRetValue* rv) {
-    *rv = OpenCLModuleLoadFile(args[0], args[1]);
-  });
-
-TVM_REGISTER_GLOBAL("module.loadfile_awsxclbin")
-.set_body([](TVMArgs args, TVMRetValue* rv) {
-    *rv = OpenCLModuleLoadFile(args[0], args[1]);
-  });
-
 TVM_REGISTER_GLOBAL("module.loadbinary_opencl")
 .set_body([](TVMArgs args, TVMRetValue* rv) {
     *rv = OpenCLModuleLoadBinary(args[0]);
diff --git a/src/runtime/opencl/opencl_module.h b/src/runtime/opencl/opencl_module.h
index b6e2a1e0e88a..b3fe72cbd301 100644
--- a/src/runtime/opencl/opencl_module.h
+++ b/src/runtime/opencl/opencl_module.h
@@ -15,7 +15,7 @@
 namespace tvm {
 namespace runtime {
 /*!
- * \brief create a opencl module from data.
+ * \brief create a opencl module for GPU devices from data.
  *
  * \param data The module data.
  * \param fmt The format of the data, can be "clbin", "cl"
diff --git a/src/runtime/opencl/sdaccel/sdaccel_common.h b/src/runtime/opencl/sdaccel/sdaccel_common.h
new file mode 100644
index 000000000000..1975d71d7e03
--- /dev/null
+++ b/src/runtime/opencl/sdaccel/sdaccel_common.h
@@ -0,0 +1,42 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file sdaccel_common.h
+ * \brief SDAccel common header
+ */
+#ifndef TVM_RUNTIME_OPENCL_SDACCEL_SDACCEL_COMMON_H_
+#define TVM_RUNTIME_OPENCL_SDACCEL_SDACCEL_COMMON_H_
+
+#include "../opencl_common.h"
+
+namespace tvm {
+namespace runtime {
+namespace cl {
+
+/*!
+ * \brief Process global SDAccel workspace.
+ */
+class SDAccelWorkspace final : public OpenCLWorkspace {
+ public:
+  // override OpenCL device API
+  void Init() final;
+  bool IsOpenCLDevice(TVMContext ctx) final;
+  OpenCLThreadEntry* GetThreadEntry() final;
+  // get the global workspace
+  static const std::shared_ptr<OpenCLWorkspace>& Global();
+};
+
+
+/*! \brief Thread local workspace for SDAccel*/
+class SDAccelThreadEntry : public OpenCLThreadEntry {
+ public:
+  // constructor
+  SDAccelThreadEntry()
+      : OpenCLThreadEntry(static_cast<DLDeviceType>(kDLSDAccel), SDAccelWorkspace::Global()) {}
+
+  // get the global workspace
+  static SDAccelThreadEntry* ThreadLocal();
+};
+}  // namespace cl
+}  // namespace runtime
+}  // namespace tvm
+#endif  // TVM_RUNTIME_OPENCL_SDACCEL_SDACCEL_COMMON_H_
diff --git a/src/runtime/opencl/sdaccel/sdaccel_device_api.cc b/src/runtime/opencl/sdaccel/sdaccel_device_api.cc
new file mode 100644
index 000000000000..1151cc44b2ef
--- /dev/null
+++ b/src/runtime/opencl/sdaccel/sdaccel_device_api.cc
@@ -0,0 +1,47 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file sdaccel_device_api.cc
+ */
+#include <tvm/runtime/registry.h>
+#include <dmlc/thread_local.h>
+#include <tvm/container.h>
+#include <tvm/ir.h>
+#include <tvm/packed_func_ext.h>
+#include "./sdaccel_common.h"
+
+namespace tvm {
+namespace runtime {
+namespace cl {
+
+OpenCLThreadEntry* SDAccelWorkspace::GetThreadEntry() {
+  return SDAccelThreadEntry::ThreadLocal();
+}
+
+const std::shared_ptr<OpenCLWorkspace>& SDAccelWorkspace::Global() {
+  static std::shared_ptr<OpenCLWorkspace> inst = std::make_shared<SDAccelWorkspace>();
+  return inst;
+}
+
+void SDAccelWorkspace::Init() {
+  OpenCLWorkspace::Init("accelerator", "Xilinx");
+}
+
+bool SDAccelWorkspace::IsOpenCLDevice(TVMContext ctx) {
+  return ctx.device_type == static_cast<DLDeviceType>(kDLSDAccel);
+}
+
+typedef dmlc::ThreadLocalStore<SDAccelThreadEntry> SDAccelThreadStore;
+
+SDAccelThreadEntry* SDAccelThreadEntry::ThreadLocal() {
+  return SDAccelThreadStore::Get();
+}
+
+TVM_REGISTER_GLOBAL("device_api.sdaccel")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    DeviceAPI* ptr = SDAccelWorkspace::Global().get();
+    *rv = static_cast<void*>(ptr);
+  });
+
+}  // namespace cl
+}  // namespace runtime
+}  // namespace tvm
diff --git a/src/runtime/opencl/sdaccel/sdaccel_module.cc b/src/runtime/opencl/sdaccel/sdaccel_module.cc
new file mode 100644
index 000000000000..6eaa3ae88a81
--- /dev/null
+++ b/src/runtime/opencl/sdaccel/sdaccel_module.cc
@@ -0,0 +1,78 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file sdaccel_module.cc
+ */
+#include <dmlc/memory_io.h>
+#include <tvm/runtime/registry.h>
+#include <vector>
+#include <string>
+#include <unordered_map>
+#include "./sdaccel_common.h"
+#include "./sdaccel_module.h"
+
+namespace tvm {
+namespace runtime {
+
+class SDAccelModuleNode : public OpenCLModuleNode {
+ public:
+  explicit SDAccelModuleNode(std::string data,
+                             std::string fmt,
+                             std::unordered_map<std::string, FunctionInfo> fmap,
+                             std::string source)
+      : OpenCLModuleNode(data, fmt, fmap, source) {}
+  const std::shared_ptr<cl::OpenCLWorkspace>& GetGlobalWorkspace() final;
+  const char* type_key() const final;
+};
+
+const std::shared_ptr<cl::OpenCLWorkspace>& SDAccelModuleNode::GetGlobalWorkspace() {
+  return cl::SDAccelWorkspace::Global();
+}
+
+const char* SDAccelModuleNode::type_key() const {
+  return "sdaccel";
+}
+
+Module SDAccelModuleCreate(
+    std::string data,
+    std::string fmt,
+    std::unordered_map<std::string, FunctionInfo> fmap,
+    std::string source) {
+  std::shared_ptr<SDAccelModuleNode> n =
+      std::make_shared<SDAccelModuleNode>(data, fmt, fmap, source);
+  n->Init();
+  return Module(n);
+}
+
+Module SDAccelModuleLoadFile(const std::string& file_name,
+                             const std::string& format) {
+  std::string data;
+  std::unordered_map<std::string, FunctionInfo> fmap;
+  std::string fmt = GetFileFormat(file_name, format);
+  std::string meta_file = GetMetaFilePath(file_name);
+  LoadBinaryFromFile(file_name, &data);
+  LoadMetaDataFromFile(meta_file, &fmap);
+  return SDAccelModuleCreate(data, fmt, fmap, std::string());
+}
+
+Module SDAccelModuleLoadBinary(void* strm) {
+  dmlc::Stream* stream = static_cast<dmlc::Stream*>(strm);
+  std::string data;
+  std::unordered_map<std::string, FunctionInfo> fmap;
+  std::string fmt;
+  stream->Read(&fmt);
+  stream->Read(&fmap);
+  stream->Read(&data);
+  return SDAccelModuleCreate(data, fmt, fmap, std::string());
+}
+
+TVM_REGISTER_GLOBAL("module.loadfile_xclbin")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    *rv = SDAccelModuleLoadFile(args[0], args[1]);
+  });
+
+TVM_REGISTER_GLOBAL("module.loadfile_awsxclbin")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    *rv = SDAccelModuleLoadFile(args[0], args[1]);
+  });
+}  // namespace runtime
+}  // namespace tvm
diff --git a/src/runtime/opencl/sdaccel/sdaccel_module.h b/src/runtime/opencl/sdaccel/sdaccel_module.h
new file mode 100644
index 000000000000..2c615d20ed2b
--- /dev/null
+++ b/src/runtime/opencl/sdaccel/sdaccel_module.h
@@ -0,0 +1,31 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file sdaccel_module.h
+ * \brief Execution handling of OPENCL kernels for SDAccel FPGAs
+ */
+#ifndef TVM_RUNTIME_OPENCL_SDACCEL_SDACCEL_MODULE_H_
+#define TVM_RUNTIME_OPENCL_SDACCEL_SDACCEL_MODULE_H_
+
+#include <tvm/runtime/packed_func.h>
+#include <memory>
+#include <vector>
+#include <string>
+#include "../../meta_data.h"
+
+namespace tvm {
+namespace runtime {
+/*!
+ * \brief create a opencl module for SDAccel from data.
+ *
+ * \param data The module data.
+ * \param fmt The format of the data, can be "xclbin", "awsxclbin"
+ * \param fmap The map function information map of each function.
+ */
+Module SDAccelModuleCreate(
+    std::string data,
+    std::string fmt,
+    std::unordered_map<std::string, FunctionInfo> fmap,
+    std::string source);
+}  // namespace runtime
+}  // namespace tvm
+#endif  // TVM_RUNTIME_OPENCL_SDACCEL_SDACCEL_MODULE_H_

From 4b4bfd03bde6fe838c63b860ce6d0aeecb422a37 Mon Sep 17 00:00:00 2001
From: Qiao Longfei <qiaolongfei@baidu.com>
Date: Tue, 10 Jul 2018 00:54:53 +0800
Subject: [PATCH 808/949] change line seperator of tensorflow/test_forward from
 CRLF to LF (#1405)

---
 .../frontend/tensorflow/test_forward.py       | 1092 ++++++++---------
 1 file changed, 546 insertions(+), 546 deletions(-)

diff --git a/nnvm/tests/python/frontend/tensorflow/test_forward.py b/nnvm/tests/python/frontend/tensorflow/test_forward.py
index 5a37918f1189..9fcf33bdf558 100644
--- a/nnvm/tests/python/frontend/tensorflow/test_forward.py
+++ b/nnvm/tests/python/frontend/tensorflow/test_forward.py
@@ -1,546 +1,546 @@
-# pylint: disable=import-self, invalid-name, unused-argument
-"""
-Tensorflow testcases
-====================
-This article is a test script to test tensorflow operator with NNVM.
-"""
-from __future__ import print_function
-import numpy as np
-import nnvm.compiler
-import tvm
-import tensorflow as tf
-from tensorflow.python.framework import constant_op
-from tensorflow.python.ops import nn_ops
-from tensorflow.python.ops import array_ops
-from tensorflow.python.ops import gen_array_ops
-from tensorflow.python.ops import math_ops
-from tensorflow.python.ops import variable_scope
-from tensorflow.python.ops import variables
-from tensorflow.core.framework import graph_pb2
-
-import nnvm.testing.tf
-
-#######################################################################
-# Generic run functions for TVM & tensorflow
-# ------------------------------------------
-def run_tvm_graph(graph_def, input_data, input_node, output_shape, output_dtype):
-    """ Generic function to compile on nnvm and execute on tvm """
-
-    sym, params = nnvm.frontend.from_tensorflow(graph_def)
-    target = 'llvm'
-    if isinstance(input_data, list):
-        shape_dict = {}
-        dtype_dict = {}
-        for i, e in enumerate(input_node):
-            shape_dict[e] = input_data[i].shape
-            dtype_dict[e] = input_data[i].dtype
-    else:
-        shape_dict = {input_node: input_data.shape}
-        dtype_dict = {input_node: input_data.dtype}
-
-    graph, lib, params = nnvm.compiler.build(sym, target, shape_dict,
-                                             dtype=dtype_dict, params=params)
-
-    ctx = tvm.cpu(0)
-    from tvm.contrib import graph_runtime
-    m = graph_runtime.create(graph, lib, ctx)
-    # set inputs
-    if isinstance(input_data, list):
-        for i, e in enumerate(input_node):
-            m.set_input(e, tvm.nd.array(input_data[i].astype(input_data[i].dtype)))
-    else:
-        m.set_input(input_node, tvm.nd.array(input_data.astype(input_data.dtype)))
-
-    m.set_input(**params)
-    # execute
-    m.run()
-    # get outputs
-    tvm_output = m.get_output(0, tvm.nd.empty((output_shape), output_dtype))
-    return tvm_output.asnumpy()
-
-def run_tf_graph(sess, input_data, input_node, output_node):
-    """ Generic function to execute tensorflow """
-
-    tensor = sess.graph.get_tensor_by_name(output_node)
-
-    if isinstance(input_data, list):
-        input_dict = {}
-        for i, e in enumerate(input_node):
-            input_dict[e] = input_data[i]
-    else:
-        input_dict = {input_node: input_data}
-
-    output_data = sess.run(tensor, input_dict)
-    return output_data
-
-#######################################################################
-# Pooling
-# -------
-def _test_pooling(input_shape, **kwargs):
-    """ One iteration of pool operation with given shapes and attributes """
-
-    x = -np.arange(
-        np.prod(input_shape), dtype=np.float32).reshape(input_shape) - 1
-
-    with tf.Graph().as_default():
-        in_data = constant_op.constant(x, shape=input_shape, dtype='float32')
-        # pylint: disable=unused-variable
-        pool = nn_ops.pool(in_data, **kwargs)
-        # pylint: enable=unused-variable
-
-        if kwargs['pooling_type'] == 'MAX':
-            out_node = 'max_pool'
-            out_name = 'max_pool:0'
-        else:
-            out_node = 'avg_pool'
-            out_name = 'avg_pool:0'
-
-        with tf.Session() as sess:
-            graph_def = tf.graph_util.convert_variables_to_constants(
-                sess,
-                sess.graph.as_graph_def(add_shapes=True),
-                [out_node],
-                )
-
-            tf_output = run_tf_graph(sess, x, 'Const:0', out_name)
-            tvm_output = run_tvm_graph(graph_def, x.astype('float32'),
-                                       "Const", tf_output.shape, 'float32')
-            np.testing.assert_allclose(tf_output, tvm_output, atol=1e-3, rtol=1e-3)
-
-            sess.close()
-
-def test_forward_pooling():
-    """ Pooling """
-
-    _test_pooling(input_shape=[2, 9, 10, 2],
-                 window_shape=[1, 1],
-                 padding='SAME',
-                 pooling_type='MAX',
-                 dilation_rate=[1, 1],
-                 strides=[1, 1])
-    _test_pooling(input_shape=[2, 9, 10, 2],
-                 window_shape=[1, 1],
-                 padding='SAME',
-                 pooling_type='AVG',
-                 dilation_rate=[1, 1],
-                 strides=[1, 1])
-
-    _test_pooling(input_shape=[2, 10, 9, 2],
-                 window_shape=[1, 1],
-                 padding='SAME',
-                 pooling_type='MAX',
-                 dilation_rate=[1, 1],
-                 strides=[1, 1])
-    _test_pooling(input_shape=[2, 10, 9, 2],
-                 window_shape=[1, 1],
-                 padding='SAME',
-                 pooling_type='AVG',
-                 dilation_rate=[1, 1],
-                 strides=[1, 1])
-
-    _test_pooling(input_shape=[2, 9, 10, 2],
-                 window_shape=[2, 1],
-                 padding='SAME',
-                 pooling_type='MAX',
-                 dilation_rate=[1, 1],
-                 strides=[1, 1])
-    _test_pooling(input_shape=[2, 9, 10, 2],
-                 window_shape=[2, 1],
-                 padding='SAME',
-                 pooling_type='AVG',
-                 dilation_rate=[1, 1],
-                 strides=[2, 1])
-
-    _test_pooling(input_shape=[2, 10, 9, 2],
-                 window_shape=[2, 3],
-                 padding='SAME',
-                 pooling_type='MAX',
-                 dilation_rate=[1, 1],
-                 strides=[2, 1])
-    _test_pooling(input_shape=[2, 10, 9, 2],
-                 window_shape=[2, 3],
-                 padding='SAME',
-                 pooling_type='AVG',
-                 dilation_rate=[1, 1],
-                 strides=[1, 2])
-
-
-#######################################################################
-# Convolution
-# -----------
-
-def _test_convolution(tensor_in_sizes, filter_in_sizes,
-                      dilations, strides, padding, data_format):
-    """ One iteration of convolution with given shapes and attributes """
-
-    total_size_1 = 1
-    total_size_2 = 1
-    for s in tensor_in_sizes:
-        total_size_1 *= s
-    for s in filter_in_sizes:
-        total_size_2 *= s
-    # Initializes the input tensor with array containing incrementing
-    # numbers from 1.
-    data_array = [f * 1.0 for f in range(1, total_size_1 + 1)]
-    filter_array = [f * 1.0 for f in range(1, total_size_2 + 1)]
-
-    with tf.Graph().as_default():
-        in_data = constant_op.constant(data_array, shape=tensor_in_sizes, dtype='float32')
-        in_filter = constant_op.constant(filter_array, shape=filter_in_sizes, dtype='float32')
-        strides = [1] + strides + [1]
-        dilations = [1] + dilations + [1]
-
-        # pylint: disable=unused-variable
-        conv = nn_ops.conv2d(in_data,
-                             in_filter,
-                             strides=strides,
-                             padding=padding,
-                             data_format=data_format)
-        # pylint: enable=unused-variable
-
-        with tf.Session() as sess:
-            graph_def = tf.graph_util.convert_variables_to_constants(
-                sess,
-                sess.graph.as_graph_def(add_shapes=True),
-                ['Conv2D'],
-                )
-
-            tf_output = run_tf_graph(sess, np.reshape(data_array, tensor_in_sizes),
-                                     'Const:0', 'Conv2D:0')
-            tvm_output = run_tvm_graph(graph_def,
-                                       np.reshape(data_array, tensor_in_sizes).astype('float32'),
-                                       "Const", tf_output.shape, 'float32')
-
-            np.testing.assert_allclose(tf_output, tvm_output, atol=1e-3, rtol=1e-3)
-
-            sess.close()
-
-def test_forward_convolution():
-    _test_convolution([4, 8, 8, 176], [1, 1, 176, 32], [1, 1], [1, 1], 'SAME', 'NHWC')
-    _test_convolution([4, 17, 17, 19], [3, 3, 19, 19], [1, 1], [2, 2], 'VALID', 'NHWC')
-    _test_convolution([4, 17, 17, 124], [1, 1, 124, 19], [1, 1], [1, 1], 'SAME', 'NHWC')
-    _test_convolution([4, 17, 17, 12], [3, 3, 12, 32], [1, 1], [2, 2], 'VALID', 'NHWC')
-
-#######################################################################
-# Reshape
-# -------
-
-def _test_reshape(data, out_shape):
-    """ One iteration of reshape operation with given data and out shape """
-
-    with tf.Graph().as_default():
-        in_data = constant_op.constant(data, shape=data.shape, dtype=data.dtype)
-
-        # pylint: disable=unused-variable
-        reshape_out = array_ops.reshape(in_data, out_shape)
-        # pylint: enable=unused-variable
-
-        with tf.Session() as sess:
-            graph_def = tf.graph_util.convert_variables_to_constants(
-                sess,
-                sess.graph.as_graph_def(add_shapes=True),
-                ['Reshape'],
-                )
-
-            tf_output = run_tf_graph(sess, data,
-                                     'Const:0', 'Reshape:0')
-            tvm_output = run_tvm_graph(graph_def,
-                                       data,
-                                       "Const", tf_output.shape, data.dtype)
-
-            np.testing.assert_allclose(tf_output, tvm_output)
-
-            sess.close()
-
-def test_forward_reshape():
-    _test_reshape(np.arange(6.0), [2, 3])
-    _test_reshape(np.arange(6), [-1, 2])
-    _test_reshape(np.arange(6), [3, -1])
-    _test_reshape(np.arange(6), [-1])
-
-#######################################################################
-# Squeeze
-# -------
-
-def _test_squeeze(data, squeeze_dims=None):
-    """ One iteration of squeeze """
-
-    if squeeze_dims is None:
-        squeeze_dims = []
-
-    with tf.Graph().as_default():
-        in_data = constant_op.constant(data, shape=data.shape, dtype=data.dtype)
-
-        # pylint: disable=unused-variable
-        if squeeze_dims:
-            squeeze_out = array_ops.squeeze(in_data, squeeze_dims)
-        else:
-            squeeze_out = array_ops.squeeze(in_data)
-        # pylint: enable=unused-variable
-
-        with tf.Session() as sess:
-            graph_def = tf.graph_util.convert_variables_to_constants(
-                sess,
-                sess.graph.as_graph_def(add_shapes=True),
-                ['Squeeze'],
-                )
-
-            tf_output = run_tf_graph(sess, data,
-                                     'Const:0', 'Squeeze:0')
-            tvm_output = run_tvm_graph(graph_def,
-                                       data,
-                                       "Const", tf_output.shape, data.dtype)
-
-            np.testing.assert_allclose(tf_output, tvm_output)
-
-            sess.close()
-
-def test_forward_squeeze():
-    """ Squeeze """
-
-    # Nothing to squeeze.
-    _test_squeeze(np.arange(2).reshape((2)))
-    _test_squeeze(np.arange(6).reshape((2, 3)))
-
-    # Squeeze the middle element away.
-    _test_squeeze(np.arange(4).reshape((2, 1, 2)))
-
-    # Squeeze on both ends.
-    _test_squeeze(np.arange(6).reshape((1, 2, 1, 3, 1)))
-
-    # Positive squeeze dim index.
-    _test_squeeze(np.arange(6).reshape((1, 2, 1, 3, 1)), [0])
-    _test_squeeze(np.arange(6).reshape((1, 2, 1, 3, 1)), [2, 4])
-    _test_squeeze(np.arange(6).reshape((1, 2, 1, 3, 1)), [0, 4, 2])
-
-    # Negative squeeze dim index.
-    _test_squeeze(np.arange(6).reshape((1, 2, 1, 3, 1)), [-1])
-    _test_squeeze(np.arange(6).reshape((1, 2, 1, 3, 1)), [-3, -5])
-    _test_squeeze(np.arange(6).reshape((1, 2, 1, 3, 1)), [-3, -5, -1])
-
-#######################################################################
-# ConcatV2
-# --------
-
-def _test_concat_v2(data, dim):
-    """ One iteration of ConcatV2 """
-
-    with tf.Graph().as_default():
-
-        # pylint: disable=unused-variable
-        concat_out = gen_array_ops._concat_v2(data, dim)
-        # pylint: enable=unused-variable
-
-        with tf.Session() as sess:
-            graph_def = tf.graph_util.convert_variables_to_constants(
-                sess,
-                sess.graph.as_graph_def(add_shapes=True),
-                ['ConcatV2'],
-                )
-
-            tf_output = run_tf_graph(sess, data,
-                                     ['ConcatV2/values_0:0', 'ConcatV2/values_1:0'], 'ConcatV2:0')
-            tvm_output = run_tvm_graph(graph_def,
-                                       data,
-                                       ["ConcatV2/values_0", 'ConcatV2/values_1'],
-                                       tf_output.shape, tf_output.dtype)
-
-            np.testing.assert_allclose(tf_output, tvm_output)
-
-            sess.close()
-
-def _test_forward_concat_v2():
-    t1 = np.array([])
-    t2 = np.array([])
-    test_concat_v2([t1, t2], 0)
-
-    t1 = np.array([[1, 2, 3], [4, 5, 6]])
-    t2 = np.array([[7, 8, 9], [10, 11, 12]])
-
-    _test_concat_v2([t1, t2], 1)
-
-#######################################################################
-# Sigmoid
-# -------
-
-def _test_sigmoid(data):
-    """ One iteration of sigmoid """
-
-    with tf.Graph().as_default():
-        in_data = constant_op.constant(data, shape=data.shape, dtype=data.dtype)
-
-        # pylint: disable=unused-variable
-        sigmoid_out = math_ops.sigmoid(in_data)
-        # pylint: enable=unused-variable
-
-        with tf.Session() as sess:
-            graph_def = tf.graph_util.convert_variables_to_constants(
-                sess,
-                sess.graph.as_graph_def(add_shapes=True),
-                ['Sigmoid'],
-                )
-
-            tf_output = run_tf_graph(sess, data,
-                                     'Const:0', 'Sigmoid:0')
-            tvm_output = run_tvm_graph(graph_def,
-                                       data,
-                                       "Const", tf_output.shape, data.dtype)
-
-            np.testing.assert_allclose(tf_output, tvm_output, atol=1e-5, rtol=1e-5)
-
-            sess.close()
-
-def test_forward_sigmoid():
-    """ Sigmoid """
-
-    _test_sigmoid(np.random.uniform(size=(3, 4, 4, 3)).astype('float32'))
-
-
-#######################################################################
-# Variable
-# --------
-
-def _test_variable(data):
-    tf.reset_default_graph()
-    input_op = array_ops.placeholder(shape=data.shape, dtype=data.dtype)
-    input_tensor = array_ops.reshape(input_op, data.shape)
-
-    size = input_tensor.shape.dims[1]
-    with variable_scope.variable_scope("linear", reuse=None):
-        w = variable_scope.get_variable(
-            "w", shape=[size, size], dtype=input_tensor.dtype)
-    # pylint: disable=unused-variable
-    output_op = math_ops.matmul(input_tensor, w)
-    # pylint: enable=unused-variable
-
-    with tf.Session() as sess:
-        sess.run(variables.global_variables_initializer())
-        final_graph_def = tf.graph_util.convert_variables_to_constants(
-            sess,
-            sess.graph.as_graph_def(add_shapes=True),
-            ['MatMul'],
-            )
-
-        tf_output = run_tf_graph(sess, data, 'Placeholder:0', 'MatMul:0')
-        tvm_output = run_tvm_graph(final_graph_def, data,
-                                   "Placeholder", tf_output.shape, data.dtype)
-
-        np.testing.assert_allclose(tf_output, tvm_output, atol=1e-5, rtol=1e-5)
-        sess.close()
-
-def test_forward_variable():
-    """Variable type op test"""
-    _test_variable(np.random.uniform(size=(32, 100)).astype('float32'))
-
-
-#######################################################################
-# Multi Input to graph
-# --------------------
-
-def test_forward_multi_input():
-    with tf.Graph().as_default():
-        in1 = tf.placeholder(tf.int32, shape=[3, 3], name='in1')
-        in2 = tf.placeholder(tf.int32, shape=[3, 3], name='in2')
-        in3 = tf.placeholder(tf.int32, shape=[3, 3], name='in3')
-        in4 = tf.placeholder(tf.int32, shape=[3, 3], name='in4')
-
-        out1 = tf.add(in1, in2, name='out1')
-        out2 = tf.subtract(in3, in4, name='out2')
-
-        out = tf.multiply(out1, out2, name='out')
-
-        with tf.Session() as sess:
-            graph_def = tf.graph_util.convert_variables_to_constants(
-                sess,
-                sess.graph.as_graph_def(add_shapes=True),
-                ['out'],
-                )
-
-            in_data = np.arange(9, dtype='int32').reshape([3, 3])
-
-            tf_output = run_tf_graph(sess, [in_data, in_data, in_data, in_data ],
-                                     ['in1:0', 'in2:0', 'in3:0', 'in4:0'], 'out:0')
-            tvm_output = run_tvm_graph(graph_def,
-                                       [in_data, in_data, in_data, in_data ],
-                                       ['in1', 'in2', 'in3', 'in4'],
-                                       tf_output.shape, tf_output.dtype)
-
-            np.testing.assert_allclose(tf_output, tvm_output)
-
-            sess.close()
-
-#######################################################################
-# Inception V3
-# ------------
-def test_forward_inception_v3():
-    '''test inception V3 model'''
-    with tf.Graph().as_default():
-        (data, graph_def) = nnvm.testing.tf.get_workload_inception_v3()
-        # Call the utility to import the graph definition into default graph.
-        graph_def = nnvm.testing.tf.ProcessGraphDefParam(graph_def)
-
-        tvm_output = run_tvm_graph(graph_def, data, 'input', (1, 1001), 'float32')
-        with tf.Session() as sess:
-            tf_output = run_tf_graph(sess, data, 'input:0', 'InceptionV3/Predictions/Reshape_1:0')
-
-            top_tvm = np.squeeze(tvm_output).argsort()[-3:][::-1]
-            top_tf = np.squeeze(tf_output).argsort()[-3:][::-1]
-
-            np.testing.assert_allclose(top_tf, top_tvm, rtol=1e-5, atol=1e-5)
-
-#######################################################################
-# Inception V1
-# ------------
-def test_forward_inception_v1():
-    '''test inception V1 model'''
-    with tf.Graph().as_default():
-        (data, tvm_data, graph_def) = nnvm.testing.tf.get_workload_inception_v1()
-        # Call the utility to import the graph definition into default graph.
-        graph_def = nnvm.testing.tf.ProcessGraphDefParam(graph_def)
-
-        tvm_output = run_tvm_graph(graph_def, tvm_data, 'DecodeJpeg/contents', (1, 1008), 'float32')
-
-        with tf.Session() as sess:
-            tf_output = run_tf_graph(sess, data, 'DecodeJpeg/contents:0', 'softmax:0')
-
-        np.testing.assert_allclose(tf_output, tvm_output, rtol=2e-2, atol=2e-2)
-
-#######################################################################
-# Mobilenet
-# ---------
-def test_forward_mobilenet():
-    '''test mobilenet model'''
-    with tf.Graph().as_default():
-        graph_def = nnvm.testing.tf.get_workload_mobilenet()
-        # Call the utility to import the graph definition into default graph.
-        graph_def = nnvm.testing.tf.ProcessGraphDefParam(graph_def)
-
-        data = np.random.uniform(size=(1, 224, 224, 3)).astype('float32')
-        out_node = 'MobilenetV1/Predictions/Reshape_1'
-
-        with tf.Session() as sess:
-            tf_output = run_tf_graph(sess, data, 'input:0', out_node + ':0')
-
-            out_shape = tf_output.shape
-            tvm_output = run_tvm_graph(graph_def, data, 'input', out_shape, 'float32')
-            top_tvm = np.squeeze(tvm_output).argsort()[-10:][::-1]
-            top_tf = np.squeeze(tf_output).argsort()[-10:][::-1]
-
-            np.testing.assert_allclose(np.squeeze(tvm_output), np.squeeze(tf_output), rtol=1e-5, atol=1e-5)
-
-#######################################################################
-# Main
-# ----
-if __name__ == '__main__':
-    test_forward_convolution()
-    test_forward_pooling()
-    test_forward_reshape()
-    test_forward_squeeze()
-    test_forward_sigmoid()
-    if tf.__version__ == '1.4.1':
-        _test_forward_concat_v2()
-    test_forward_multi_input()
-    test_forward_inception_v3()
-    test_forward_inception_v1()
-    test_forward_mobilenet()
-    test_forward_variable()
+# pylint: disable=import-self, invalid-name, unused-argument
+"""
+Tensorflow testcases
+====================
+This article is a test script to test tensorflow operator with NNVM.
+"""
+from __future__ import print_function
+import numpy as np
+import nnvm.compiler
+import tvm
+import tensorflow as tf
+from tensorflow.python.framework import constant_op
+from tensorflow.python.ops import nn_ops
+from tensorflow.python.ops import array_ops
+from tensorflow.python.ops import gen_array_ops
+from tensorflow.python.ops import math_ops
+from tensorflow.python.ops import variable_scope
+from tensorflow.python.ops import variables
+from tensorflow.core.framework import graph_pb2
+
+import nnvm.testing.tf
+
+#######################################################################
+# Generic run functions for TVM & tensorflow
+# ------------------------------------------
+def run_tvm_graph(graph_def, input_data, input_node, output_shape, output_dtype):
+    """ Generic function to compile on nnvm and execute on tvm """
+
+    sym, params = nnvm.frontend.from_tensorflow(graph_def)
+    target = 'llvm'
+    if isinstance(input_data, list):
+        shape_dict = {}
+        dtype_dict = {}
+        for i, e in enumerate(input_node):
+            shape_dict[e] = input_data[i].shape
+            dtype_dict[e] = input_data[i].dtype
+    else:
+        shape_dict = {input_node: input_data.shape}
+        dtype_dict = {input_node: input_data.dtype}
+
+    graph, lib, params = nnvm.compiler.build(sym, target, shape_dict,
+                                             dtype=dtype_dict, params=params)
+
+    ctx = tvm.cpu(0)
+    from tvm.contrib import graph_runtime
+    m = graph_runtime.create(graph, lib, ctx)
+    # set inputs
+    if isinstance(input_data, list):
+        for i, e in enumerate(input_node):
+            m.set_input(e, tvm.nd.array(input_data[i].astype(input_data[i].dtype)))
+    else:
+        m.set_input(input_node, tvm.nd.array(input_data.astype(input_data.dtype)))
+
+    m.set_input(**params)
+    # execute
+    m.run()
+    # get outputs
+    tvm_output = m.get_output(0, tvm.nd.empty((output_shape), output_dtype))
+    return tvm_output.asnumpy()
+
+def run_tf_graph(sess, input_data, input_node, output_node):
+    """ Generic function to execute tensorflow """
+
+    tensor = sess.graph.get_tensor_by_name(output_node)
+
+    if isinstance(input_data, list):
+        input_dict = {}
+        for i, e in enumerate(input_node):
+            input_dict[e] = input_data[i]
+    else:
+        input_dict = {input_node: input_data}
+
+    output_data = sess.run(tensor, input_dict)
+    return output_data
+
+#######################################################################
+# Pooling
+# -------
+def _test_pooling(input_shape, **kwargs):
+    """ One iteration of pool operation with given shapes and attributes """
+
+    x = -np.arange(
+        np.prod(input_shape), dtype=np.float32).reshape(input_shape) - 1
+
+    with tf.Graph().as_default():
+        in_data = constant_op.constant(x, shape=input_shape, dtype='float32')
+        # pylint: disable=unused-variable
+        pool = nn_ops.pool(in_data, **kwargs)
+        # pylint: enable=unused-variable
+
+        if kwargs['pooling_type'] == 'MAX':
+            out_node = 'max_pool'
+            out_name = 'max_pool:0'
+        else:
+            out_node = 'avg_pool'
+            out_name = 'avg_pool:0'
+
+        with tf.Session() as sess:
+            graph_def = tf.graph_util.convert_variables_to_constants(
+                sess,
+                sess.graph.as_graph_def(add_shapes=True),
+                [out_node],
+                )
+
+            tf_output = run_tf_graph(sess, x, 'Const:0', out_name)
+            tvm_output = run_tvm_graph(graph_def, x.astype('float32'),
+                                       "Const", tf_output.shape, 'float32')
+            np.testing.assert_allclose(tf_output, tvm_output, atol=1e-3, rtol=1e-3)
+
+            sess.close()
+
+def test_forward_pooling():
+    """ Pooling """
+
+    _test_pooling(input_shape=[2, 9, 10, 2],
+                 window_shape=[1, 1],
+                 padding='SAME',
+                 pooling_type='MAX',
+                 dilation_rate=[1, 1],
+                 strides=[1, 1])
+    _test_pooling(input_shape=[2, 9, 10, 2],
+                 window_shape=[1, 1],
+                 padding='SAME',
+                 pooling_type='AVG',
+                 dilation_rate=[1, 1],
+                 strides=[1, 1])
+
+    _test_pooling(input_shape=[2, 10, 9, 2],
+                 window_shape=[1, 1],
+                 padding='SAME',
+                 pooling_type='MAX',
+                 dilation_rate=[1, 1],
+                 strides=[1, 1])
+    _test_pooling(input_shape=[2, 10, 9, 2],
+                 window_shape=[1, 1],
+                 padding='SAME',
+                 pooling_type='AVG',
+                 dilation_rate=[1, 1],
+                 strides=[1, 1])
+
+    _test_pooling(input_shape=[2, 9, 10, 2],
+                 window_shape=[2, 1],
+                 padding='SAME',
+                 pooling_type='MAX',
+                 dilation_rate=[1, 1],
+                 strides=[1, 1])
+    _test_pooling(input_shape=[2, 9, 10, 2],
+                 window_shape=[2, 1],
+                 padding='SAME',
+                 pooling_type='AVG',
+                 dilation_rate=[1, 1],
+                 strides=[2, 1])
+
+    _test_pooling(input_shape=[2, 10, 9, 2],
+                 window_shape=[2, 3],
+                 padding='SAME',
+                 pooling_type='MAX',
+                 dilation_rate=[1, 1],
+                 strides=[2, 1])
+    _test_pooling(input_shape=[2, 10, 9, 2],
+                 window_shape=[2, 3],
+                 padding='SAME',
+                 pooling_type='AVG',
+                 dilation_rate=[1, 1],
+                 strides=[1, 2])
+
+
+#######################################################################
+# Convolution
+# -----------
+
+def _test_convolution(tensor_in_sizes, filter_in_sizes,
+                      dilations, strides, padding, data_format):
+    """ One iteration of convolution with given shapes and attributes """
+
+    total_size_1 = 1
+    total_size_2 = 1
+    for s in tensor_in_sizes:
+        total_size_1 *= s
+    for s in filter_in_sizes:
+        total_size_2 *= s
+    # Initializes the input tensor with array containing incrementing
+    # numbers from 1.
+    data_array = [f * 1.0 for f in range(1, total_size_1 + 1)]
+    filter_array = [f * 1.0 for f in range(1, total_size_2 + 1)]
+
+    with tf.Graph().as_default():
+        in_data = constant_op.constant(data_array, shape=tensor_in_sizes, dtype='float32')
+        in_filter = constant_op.constant(filter_array, shape=filter_in_sizes, dtype='float32')
+        strides = [1] + strides + [1]
+        dilations = [1] + dilations + [1]
+
+        # pylint: disable=unused-variable
+        conv = nn_ops.conv2d(in_data,
+                             in_filter,
+                             strides=strides,
+                             padding=padding,
+                             data_format=data_format)
+        # pylint: enable=unused-variable
+
+        with tf.Session() as sess:
+            graph_def = tf.graph_util.convert_variables_to_constants(
+                sess,
+                sess.graph.as_graph_def(add_shapes=True),
+                ['Conv2D'],
+                )
+
+            tf_output = run_tf_graph(sess, np.reshape(data_array, tensor_in_sizes),
+                                     'Const:0', 'Conv2D:0')
+            tvm_output = run_tvm_graph(graph_def,
+                                       np.reshape(data_array, tensor_in_sizes).astype('float32'),
+                                       "Const", tf_output.shape, 'float32')
+
+            np.testing.assert_allclose(tf_output, tvm_output, atol=1e-3, rtol=1e-3)
+
+            sess.close()
+
+def test_forward_convolution():
+    _test_convolution([4, 8, 8, 176], [1, 1, 176, 32], [1, 1], [1, 1], 'SAME', 'NHWC')
+    _test_convolution([4, 17, 17, 19], [3, 3, 19, 19], [1, 1], [2, 2], 'VALID', 'NHWC')
+    _test_convolution([4, 17, 17, 124], [1, 1, 124, 19], [1, 1], [1, 1], 'SAME', 'NHWC')
+    _test_convolution([4, 17, 17, 12], [3, 3, 12, 32], [1, 1], [2, 2], 'VALID', 'NHWC')
+
+#######################################################################
+# Reshape
+# -------
+
+def _test_reshape(data, out_shape):
+    """ One iteration of reshape operation with given data and out shape """
+
+    with tf.Graph().as_default():
+        in_data = constant_op.constant(data, shape=data.shape, dtype=data.dtype)
+
+        # pylint: disable=unused-variable
+        reshape_out = array_ops.reshape(in_data, out_shape)
+        # pylint: enable=unused-variable
+
+        with tf.Session() as sess:
+            graph_def = tf.graph_util.convert_variables_to_constants(
+                sess,
+                sess.graph.as_graph_def(add_shapes=True),
+                ['Reshape'],
+                )
+
+            tf_output = run_tf_graph(sess, data,
+                                     'Const:0', 'Reshape:0')
+            tvm_output = run_tvm_graph(graph_def,
+                                       data,
+                                       "Const", tf_output.shape, data.dtype)
+
+            np.testing.assert_allclose(tf_output, tvm_output)
+
+            sess.close()
+
+def test_forward_reshape():
+    _test_reshape(np.arange(6.0), [2, 3])
+    _test_reshape(np.arange(6), [-1, 2])
+    _test_reshape(np.arange(6), [3, -1])
+    _test_reshape(np.arange(6), [-1])
+
+#######################################################################
+# Squeeze
+# -------
+
+def _test_squeeze(data, squeeze_dims=None):
+    """ One iteration of squeeze """
+
+    if squeeze_dims is None:
+        squeeze_dims = []
+
+    with tf.Graph().as_default():
+        in_data = constant_op.constant(data, shape=data.shape, dtype=data.dtype)
+
+        # pylint: disable=unused-variable
+        if squeeze_dims:
+            squeeze_out = array_ops.squeeze(in_data, squeeze_dims)
+        else:
+            squeeze_out = array_ops.squeeze(in_data)
+        # pylint: enable=unused-variable
+
+        with tf.Session() as sess:
+            graph_def = tf.graph_util.convert_variables_to_constants(
+                sess,
+                sess.graph.as_graph_def(add_shapes=True),
+                ['Squeeze'],
+                )
+
+            tf_output = run_tf_graph(sess, data,
+                                     'Const:0', 'Squeeze:0')
+            tvm_output = run_tvm_graph(graph_def,
+                                       data,
+                                       "Const", tf_output.shape, data.dtype)
+
+            np.testing.assert_allclose(tf_output, tvm_output)
+
+            sess.close()
+
+def test_forward_squeeze():
+    """ Squeeze """
+
+    # Nothing to squeeze.
+    _test_squeeze(np.arange(2).reshape((2)))
+    _test_squeeze(np.arange(6).reshape((2, 3)))
+
+    # Squeeze the middle element away.
+    _test_squeeze(np.arange(4).reshape((2, 1, 2)))
+
+    # Squeeze on both ends.
+    _test_squeeze(np.arange(6).reshape((1, 2, 1, 3, 1)))
+
+    # Positive squeeze dim index.
+    _test_squeeze(np.arange(6).reshape((1, 2, 1, 3, 1)), [0])
+    _test_squeeze(np.arange(6).reshape((1, 2, 1, 3, 1)), [2, 4])
+    _test_squeeze(np.arange(6).reshape((1, 2, 1, 3, 1)), [0, 4, 2])
+
+    # Negative squeeze dim index.
+    _test_squeeze(np.arange(6).reshape((1, 2, 1, 3, 1)), [-1])
+    _test_squeeze(np.arange(6).reshape((1, 2, 1, 3, 1)), [-3, -5])
+    _test_squeeze(np.arange(6).reshape((1, 2, 1, 3, 1)), [-3, -5, -1])
+
+#######################################################################
+# ConcatV2
+# --------
+
+def _test_concat_v2(data, dim):
+    """ One iteration of ConcatV2 """
+
+    with tf.Graph().as_default():
+
+        # pylint: disable=unused-variable
+        concat_out = gen_array_ops._concat_v2(data, dim)
+        # pylint: enable=unused-variable
+
+        with tf.Session() as sess:
+            graph_def = tf.graph_util.convert_variables_to_constants(
+                sess,
+                sess.graph.as_graph_def(add_shapes=True),
+                ['ConcatV2'],
+                )
+
+            tf_output = run_tf_graph(sess, data,
+                                     ['ConcatV2/values_0:0', 'ConcatV2/values_1:0'], 'ConcatV2:0')
+            tvm_output = run_tvm_graph(graph_def,
+                                       data,
+                                       ["ConcatV2/values_0", 'ConcatV2/values_1'],
+                                       tf_output.shape, tf_output.dtype)
+
+            np.testing.assert_allclose(tf_output, tvm_output)
+
+            sess.close()
+
+def _test_forward_concat_v2():
+    t1 = np.array([])
+    t2 = np.array([])
+    test_concat_v2([t1, t2], 0)
+
+    t1 = np.array([[1, 2, 3], [4, 5, 6]])
+    t2 = np.array([[7, 8, 9], [10, 11, 12]])
+
+    _test_concat_v2([t1, t2], 1)
+
+#######################################################################
+# Sigmoid
+# -------
+
+def _test_sigmoid(data):
+    """ One iteration of sigmoid """
+
+    with tf.Graph().as_default():
+        in_data = constant_op.constant(data, shape=data.shape, dtype=data.dtype)
+
+        # pylint: disable=unused-variable
+        sigmoid_out = math_ops.sigmoid(in_data)
+        # pylint: enable=unused-variable
+
+        with tf.Session() as sess:
+            graph_def = tf.graph_util.convert_variables_to_constants(
+                sess,
+                sess.graph.as_graph_def(add_shapes=True),
+                ['Sigmoid'],
+                )
+
+            tf_output = run_tf_graph(sess, data,
+                                     'Const:0', 'Sigmoid:0')
+            tvm_output = run_tvm_graph(graph_def,
+                                       data,
+                                       "Const", tf_output.shape, data.dtype)
+
+            np.testing.assert_allclose(tf_output, tvm_output, atol=1e-5, rtol=1e-5)
+
+            sess.close()
+
+def test_forward_sigmoid():
+    """ Sigmoid """
+
+    _test_sigmoid(np.random.uniform(size=(3, 4, 4, 3)).astype('float32'))
+
+
+#######################################################################
+# Variable
+# --------
+
+def _test_variable(data):
+    tf.reset_default_graph()
+    input_op = array_ops.placeholder(shape=data.shape, dtype=data.dtype)
+    input_tensor = array_ops.reshape(input_op, data.shape)
+
+    size = input_tensor.shape.dims[1]
+    with variable_scope.variable_scope("linear", reuse=None):
+        w = variable_scope.get_variable(
+            "w", shape=[size, size], dtype=input_tensor.dtype)
+    # pylint: disable=unused-variable
+    output_op = math_ops.matmul(input_tensor, w)
+    # pylint: enable=unused-variable
+
+    with tf.Session() as sess:
+        sess.run(variables.global_variables_initializer())
+        final_graph_def = tf.graph_util.convert_variables_to_constants(
+            sess,
+            sess.graph.as_graph_def(add_shapes=True),
+            ['MatMul'],
+            )
+
+        tf_output = run_tf_graph(sess, data, 'Placeholder:0', 'MatMul:0')
+        tvm_output = run_tvm_graph(final_graph_def, data,
+                                   "Placeholder", tf_output.shape, data.dtype)
+
+        np.testing.assert_allclose(tf_output, tvm_output, atol=1e-5, rtol=1e-5)
+        sess.close()
+
+def test_forward_variable():
+    """Variable type op test"""
+    _test_variable(np.random.uniform(size=(32, 100)).astype('float32'))
+
+
+#######################################################################
+# Multi Input to graph
+# --------------------
+
+def test_forward_multi_input():
+    with tf.Graph().as_default():
+        in1 = tf.placeholder(tf.int32, shape=[3, 3], name='in1')
+        in2 = tf.placeholder(tf.int32, shape=[3, 3], name='in2')
+        in3 = tf.placeholder(tf.int32, shape=[3, 3], name='in3')
+        in4 = tf.placeholder(tf.int32, shape=[3, 3], name='in4')
+
+        out1 = tf.add(in1, in2, name='out1')
+        out2 = tf.subtract(in3, in4, name='out2')
+
+        out = tf.multiply(out1, out2, name='out')
+
+        with tf.Session() as sess:
+            graph_def = tf.graph_util.convert_variables_to_constants(
+                sess,
+                sess.graph.as_graph_def(add_shapes=True),
+                ['out'],
+                )
+
+            in_data = np.arange(9, dtype='int32').reshape([3, 3])
+
+            tf_output = run_tf_graph(sess, [in_data, in_data, in_data, in_data ],
+                                     ['in1:0', 'in2:0', 'in3:0', 'in4:0'], 'out:0')
+            tvm_output = run_tvm_graph(graph_def,
+                                       [in_data, in_data, in_data, in_data ],
+                                       ['in1', 'in2', 'in3', 'in4'],
+                                       tf_output.shape, tf_output.dtype)
+
+            np.testing.assert_allclose(tf_output, tvm_output)
+
+            sess.close()
+
+#######################################################################
+# Inception V3
+# ------------
+def test_forward_inception_v3():
+    '''test inception V3 model'''
+    with tf.Graph().as_default():
+        (data, graph_def) = nnvm.testing.tf.get_workload_inception_v3()
+        # Call the utility to import the graph definition into default graph.
+        graph_def = nnvm.testing.tf.ProcessGraphDefParam(graph_def)
+
+        tvm_output = run_tvm_graph(graph_def, data, 'input', (1, 1001), 'float32')
+        with tf.Session() as sess:
+            tf_output = run_tf_graph(sess, data, 'input:0', 'InceptionV3/Predictions/Reshape_1:0')
+
+            top_tvm = np.squeeze(tvm_output).argsort()[-3:][::-1]
+            top_tf = np.squeeze(tf_output).argsort()[-3:][::-1]
+
+            np.testing.assert_allclose(top_tf, top_tvm, rtol=1e-5, atol=1e-5)
+
+#######################################################################
+# Inception V1
+# ------------
+def test_forward_inception_v1():
+    '''test inception V1 model'''
+    with tf.Graph().as_default():
+        (data, tvm_data, graph_def) = nnvm.testing.tf.get_workload_inception_v1()
+        # Call the utility to import the graph definition into default graph.
+        graph_def = nnvm.testing.tf.ProcessGraphDefParam(graph_def)
+
+        tvm_output = run_tvm_graph(graph_def, tvm_data, 'DecodeJpeg/contents', (1, 1008), 'float32')
+
+        with tf.Session() as sess:
+            tf_output = run_tf_graph(sess, data, 'DecodeJpeg/contents:0', 'softmax:0')
+
+        np.testing.assert_allclose(tf_output, tvm_output, rtol=2e-2, atol=2e-2)
+
+#######################################################################
+# Mobilenet
+# ---------
+def test_forward_mobilenet():
+    '''test mobilenet model'''
+    with tf.Graph().as_default():
+        graph_def = nnvm.testing.tf.get_workload_mobilenet()
+        # Call the utility to import the graph definition into default graph.
+        graph_def = nnvm.testing.tf.ProcessGraphDefParam(graph_def)
+
+        data = np.random.uniform(size=(1, 224, 224, 3)).astype('float32')
+        out_node = 'MobilenetV1/Predictions/Reshape_1'
+
+        with tf.Session() as sess:
+            tf_output = run_tf_graph(sess, data, 'input:0', out_node + ':0')
+
+            out_shape = tf_output.shape
+            tvm_output = run_tvm_graph(graph_def, data, 'input', out_shape, 'float32')
+            top_tvm = np.squeeze(tvm_output).argsort()[-10:][::-1]
+            top_tf = np.squeeze(tf_output).argsort()[-10:][::-1]
+
+            np.testing.assert_allclose(np.squeeze(tvm_output), np.squeeze(tf_output), rtol=1e-5, atol=1e-5)
+
+#######################################################################
+# Main
+# ----
+if __name__ == '__main__':
+    test_forward_convolution()
+    test_forward_pooling()
+    test_forward_reshape()
+    test_forward_squeeze()
+    test_forward_sigmoid()
+    if tf.__version__ == '1.4.1':
+        _test_forward_concat_v2()
+    test_forward_multi_input()
+    test_forward_inception_v3()
+    test_forward_inception_v1()
+    test_forward_mobilenet()
+    test_forward_variable()

From f09f1433122d4c8b11e09ba392eeba3a048d6836 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 9 Jul 2018 10:46:14 -0700
Subject: [PATCH 809/949] [DOCKER] Add demo-gpu image (#1407)

---
 docker/Dockerfile.demo_cpu        |  2 +-
 docker/Dockerfile.demo_gpu        | 34 +++++++++++++++++++++++++++++++
 docker/README.md                  |  4 ++--
 docker/build.sh                   |  2 +-
 docker/install/install_tvm_gpu.sh | 14 +++++++++++++
 docs/install/docker.rst           |  8 ++++----
 docs/install/from_source.rst      |  5 +++--
 7 files changed, 59 insertions(+), 10 deletions(-)
 create mode 100644 docker/Dockerfile.demo_gpu
 create mode 100644 docker/install/install_tvm_gpu.sh

diff --git a/docker/Dockerfile.demo_cpu b/docker/Dockerfile.demo_cpu
index f84b245b4298..0778b0a28784 100644
--- a/docker/Dockerfile.demo_cpu
+++ b/docker/Dockerfile.demo_cpu
@@ -1,5 +1,5 @@
 # Minimum docker image for demo purposes
-# prebuilt-image: tvmai/demo_cpu
+# prebuilt-image: tvmai/demo-cpu
 FROM ubuntu:16.04
 
 RUN apt-get update --fix-missing
diff --git a/docker/Dockerfile.demo_gpu b/docker/Dockerfile.demo_gpu
new file mode 100644
index 000000000000..6f249986e22c
--- /dev/null
+++ b/docker/Dockerfile.demo_gpu
@@ -0,0 +1,34 @@
+# Minimum docker image for demo purposes
+# prebuilt-image: tvmai/demo-gpu
+FROM nvidia/cuda:8.0-cudnn7-devel
+
+RUN apt-get update --fix-missing
+
+COPY install/ubuntu_install_core.sh /install/ubuntu_install_core.sh
+RUN bash /install/ubuntu_install_core.sh
+
+# Python: basic dependencies
+RUN apt-get update && apt-get install -y python3-dev python3-pip
+RUN pip3 install numpy nose-timer cython decorator scipy
+
+# LLVM
+RUN echo deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-6.0 main \
+     >> /etc/apt/sources.list.d/llvm.list && \
+     wget -O - http://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add - && \
+     apt-get update && apt-get install -y --force-yes llvm-6.0
+
+# Jupyter notebook.
+RUN pip3 install matplotlib Image Pillow jupyter[notebook]
+
+# Deep learning frameworks
+RUN pip3 install mxnet tensorflow keras
+
+# Build TVM
+COPY install/install_tvm_gpu.sh /install/install_tvm_gpu.sh
+RUN bash /install/install_tvm_gpu.sh
+
+# Environment variables
+ENV PYTHONPATH=/usr/tvm/python:/usr/tvm/topi/python:/usr/tvm/nnvm/python/:/usr/tvm/vta/python:${PYTHONPATH}
+ENV PATH=/usr/local/nvidia/bin:${PATH}
+ENV PATH=/usr/local/cuda/bin:${PATH}
+ENV LD_LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/nvidia/lib64:${LD_LIBRARY_PATH}
diff --git a/docker/README.md b/docker/README.md
index b44cd266041e..e9b8b503062f 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -24,10 +24,10 @@ The helper bash script can be useful to build demo sessions.
 ## Prebuilt Docker Images
 
 We provide several pre-built images for doing quick exploration with TVM installed.
-For example, you can run the following command to get ```tvmai/demo_cpu``` image.
+For example, you can run the following command to get ```tvmai/demo-cpu``` image.
 
 ```bash
-/path/to/tvm/docker/bash.sh tvmai/demo_cpu
+/path/to/tvm/docker/bash.sh tvmai/demo-cpu
 ```
 
 Then inside the docker container, you can type the following command to start the jupyter notebook
diff --git a/docker/build.sh b/docker/build.sh
index c94dd5bb879a..1d476e52e642 100755
--- a/docker/build.sh
+++ b/docker/build.sh
@@ -2,7 +2,7 @@
 #
 # Execute command within a docker container
 #
-# Usage: ci_build.sh <CONTAINER_TYPE> [--dockerfile <DOCKERFILE_PATH>] [-it]
+# Usage: build.sh <CONTAINER_TYPE> [--dockerfile <DOCKERFILE_PATH>] [-it]
 #                    <COMMAND>
 #
 # CONTAINER_TYPE: Type of the docker container used the run the build: e.g.,
diff --git a/docker/install/install_tvm_gpu.sh b/docker/install/install_tvm_gpu.sh
new file mode 100644
index 000000000000..8a1324646fd5
--- /dev/null
+++ b/docker/install/install_tvm_gpu.sh
@@ -0,0 +1,14 @@
+cd /usr
+git clone https://github.com/dmlc/tvm --recursive
+cd /usr/tvm
+echo set\(USE_LLVM llvm-config-6.0\) >> config.cmake
+echo set\(USE_CUDA ON\) >> config.cmake
+echo set\(USE_CUDNN ON\) >> config.cmake
+echo set\(USE_RPC ON\) >> config.cmake
+echo set\(USE_SORT ON\) >> config.cmake
+echo set\(USE_GRAPH_RUNTIME ON\) >> config.cmake
+echo set\(USE_BLAS openblas\) >> config.cmake
+mkdir -p build
+cd build
+cmake ..
+make -j10
diff --git a/docs/install/docker.rst b/docs/install/docker.rst
index e2000532c22c..d6cd114bd3d0 100644
--- a/docs/install/docker.rst
+++ b/docs/install/docker.rst
@@ -3,7 +3,7 @@
 Docker Images
 =============
 We provide several prebuilt docker images to quickly try out tvm.
-These images are also helpful to do demo/tutorials of TVM.
+These images are also helpful run through TVM demo and tutorials.
 You can get the docker images via the following steps.
 We need `docker <https://docs.docker.com/engine/installation/>`_ and
 `nvidia-docker <https://github.com/NVIDIA/nvidia-docker/>`_ if we want to use cuda.
@@ -15,14 +15,14 @@ First, clone tvm repo to get the auxiliary scripts
     git clone --recursive https://github.com/dmlc/tvm
 
 
-We can then use the following command to launch a `tvmai/demo_cpu` image.
+We can then use the following command to launch a `tvmai/demo-cpu` image.
 
 .. code:: bash
 
     /path/to/tvm/docker/bash.sh tvmai/demo_cpu
 
 .. note::
-    You can find all the prebuilt images in `https://hub.docker.com/r/tvmai/`_
+    You can find all the prebuilt images in `<https://hub.docker.com/r/tvmai/>`_
 
 
 This auxiliary script does the following things:
@@ -41,5 +41,5 @@ Then you can start a jupyter notebook by typing
 
 Docker Source
 -------------
-Check out `https://github.com/dmlc/tvm/tree/master/docker`_ if you are interested in
+Check out `<https://github.com/dmlc/tvm/tree/master/docker>`_ if you are interested in
 building your own docker images.
diff --git a/docs/install/from_source.rst b/docs/install/from_source.rst
index b65e68f3ea58..5709ed7f0bab 100644
--- a/docs/install/from_source.rst
+++ b/docs/install/from_source.rst
@@ -84,8 +84,7 @@ The configuration of tvm can be modified by `config.cmake`.
       cmake ..
       make -j4
 
-If everything goes well, we can go to the specific language installation section.
-
+If everything goes well, we can go to :ref:`python-package-installation`_
 
 Building on Windows
 ~~~~~~~~~~~~~~~~~~~
@@ -114,6 +113,8 @@ Currently, ROCm is supported only on linux, so all the instructions are written
 - You need to first install HIP runtime from ROCm. Make sure the installation system has ROCm installed in it.
 - Install latest stable version of LLVM (v6.0.1), and LLD, make sure ``ld.lld`` is available via command line.
 
+.. _python-package-installation:
+
 Python Package Installation
 ---------------------------
 

From 5e0fc59fc6c9b0f897e0a935d3577575e7318e69 Mon Sep 17 00:00:00 2001
From: Yizhi Liu <liuyizhi@apache.org>
Date: Mon, 9 Jul 2018 13:19:54 -0700
Subject: [PATCH 810/949] fix CorrectLayout for softmax & log_softmax (#1401)

---
 nnvm/src/top/nn/nn.cc                            |  4 ++--
 .../tests/python/unittest/test_correct_layout.py | 16 +++++++++++++++-
 2 files changed, 17 insertions(+), 3 deletions(-)

diff --git a/nnvm/src/top/nn/nn.cc b/nnvm/src/top/nn/nn.cc
index ba89e5ceba58..322d77b6d032 100644
--- a/nnvm/src/top/nn/nn.cc
+++ b/nnvm/src/top/nn/nn.cc
@@ -345,7 +345,7 @@ NNVM_REGISTER_OP(softmax)
 .set_num_outputs(1)
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseArbitraryLayout<1, 1>)
+.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseFixedLayoutCopyToOut<1, 1>)
 .set_support_level(1)
 .set_attr<FTVMCompute>(
   "FTVMCompute", [](const NodeAttrs& attrs,
@@ -404,7 +404,7 @@ NNVM_REGISTER_OP(log_softmax)
 .set_num_outputs(1)
 .set_attr<FInferShape>("FInferShape", ElemwiseShape<1, 1>)
 .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
-.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseArbitraryLayout<1, 1>)
+.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseFixedLayoutCopyToOut<1, 1>)
 .set_attr<FTVMCompute>(
   "FTVMCompute", [](const NodeAttrs& attrs,
                     const Array<Tensor>& inputs,
diff --git a/nnvm/tests/python/unittest/test_correct_layout.py b/nnvm/tests/python/unittest/test_correct_layout.py
index c428a2f837ac..6176586284a7 100644
--- a/nnvm/tests/python/unittest/test_correct_layout.py
+++ b/nnvm/tests/python/unittest/test_correct_layout.py
@@ -3,7 +3,6 @@
 import nnvm.graph as graph
 from nnvm.compiler import graph_attr
 
-# Level 1
 def correct_layout(g, layout=None):
     if isinstance(g, nnvm.symbol.Symbol):
         g = graph.create(g)
@@ -19,6 +18,7 @@ def correct_layout(g, layout=None):
     return g, ldict
 
 
+# Level 1
 def test_dense():
     x = sym.Variable("data", shape=(10, 20))
     y = sym.dense(x, units=30, name="fc")
@@ -169,6 +169,19 @@ def test_flatten():
     assert(ldict["y"][0] == "__undef__")
 
 
+def test_softmax():
+    x = sym.Variable("x", shape=(10, 20, 10, 10))
+    y = sym.softmax(x, name="y")
+    g, ldict = correct_layout(y, "NCHW")
+    assert(ldict["x"][0] == "NCHW")
+    assert(ldict["y"][0] == "NCHW")
+    # second pass will insert layout transform
+    _, ldict = correct_layout(g, "NCHW16c")
+    assert(ldict["x"][0] == "NCHW16c")
+    assert(ldict["x_NCHW"][0] == "NCHW")
+    assert(ldict["y"][0] == "NCHW")
+
+
 # Level 2
 def test_conv2d():
     x = sym.Variable("data", shape=(1, 32, 512, 512))
@@ -327,6 +340,7 @@ def test_reduce():
     test_split()
     test_batchnorm()
     test_flatten()
+    test_softmax()
     test_conv2d()
     test_conv2d_transpose()
     test_max_pool2d()

From 6f25848215e7d36de08f0346cf3e16a81d075746 Mon Sep 17 00:00:00 2001
From: MORITA Kazutaka <morita.kazutaka@lab.ntt.co.jp>
Date: Tue, 10 Jul 2018 05:20:32 +0900
Subject: [PATCH 811/949] [RUNTIME][OPENCL] Create program lazily when the
 program is built (#1408)

---
 src/runtime/opencl/opencl_module.cc | 38 ++++++++++++++---------------
 1 file changed, 19 insertions(+), 19 deletions(-)

diff --git a/src/runtime/opencl/opencl_module.cc b/src/runtime/opencl/opencl_module.cc
index 424a6dccff3d..68907d11cd75 100644
--- a/src/runtime/opencl/opencl_module.cc
+++ b/src/runtime/opencl/opencl_module.cc
@@ -162,25 +162,6 @@ void OpenCLModuleNode::Init() {
   workspace_ = GetGlobalWorkspace();
   workspace_->Init();
   CHECK(workspace_->context != nullptr) << "No OpenCL device";
-  if (fmt_ == "cl") {
-    const char* s = data_.c_str();
-    size_t len = data_.length();
-    cl_int err;
-    program_ = clCreateProgramWithSource(
-        workspace_->context, 1, &s, &len, &err);
-    OPENCL_CHECK_ERROR(err);
-  } else if (fmt_ == "xclbin" || fmt_ == "awsxclbin") {
-    const unsigned char* s = (const unsigned char *)data_.c_str();
-    size_t len = data_.length();
-    cl_int err;
-    program_ = clCreateProgramWithBinary(
-        workspace_->context, 1, &(workspace_->devices[0]), &len, &s, NULL, &err);
-    if (err != CL_SUCCESS) {
-      LOG(ERROR) << "OpenCL Error: " << cl::CLGetErrorString(err);
-    }
-  } else {
-    LOG(FATAL) << "Unknown OpenCL format " << fmt_;
-  }
   device_built_flag_.resize(workspace_->devices.size(), false);
   // initialize the kernel id, need to lock global table.
   std::lock_guard<std::mutex> lock(workspace_->mu);
@@ -205,6 +186,25 @@ cl_kernel OpenCLModuleNode::InstallKernel(cl::OpenCLWorkspace* w,
   std::lock_guard<std::mutex> lock(build_lock_);
   int device_id = t->context.device_id;
   if (!device_built_flag_[device_id]) {
+    // create program
+    if (fmt_ == "cl") {
+      if (program_ == nullptr) {
+        const char* s = data_.c_str();
+        size_t len = data_.length();
+        cl_int err;
+        program_ = clCreateProgramWithSource(w->context, 1, &s, &len, &err);
+        OPENCL_CHECK_ERROR(err);
+      }
+    } else if (fmt_ == "xclbin" || fmt_ == "awsxclbin") {
+      const unsigned char* s = (const unsigned char *)data_.c_str();
+      size_t len = data_.length();
+      cl_int err;
+      cl_device_id dev = w->devices[device_id];
+      program_ = clCreateProgramWithBinary(w->context, 1, &dev, &len, &s, NULL, &err);
+      OPENCL_CHECK_ERROR(err);
+    } else {
+      LOG(FATAL) << "Unknown OpenCL format " << fmt_;
+    }
     // build program
     cl_int err;
     cl_device_id dev = w->devices[device_id];

From 561e548dea7aaf0b3065f8894caaf67c7b15faa0 Mon Sep 17 00:00:00 2001
From: alex-weaver <awsweaver@gmail.com>
Date: Mon, 9 Jul 2018 23:02:35 +0100
Subject: [PATCH 812/949] [TVM] Fixed SPIR-V codegen incorrectly not declaring
 the interface for the entry point (#1400)

---
 src/codegen/spirv/codegen_spirv.cc |  4 +++-
 src/codegen/spirv/ir_builder.cc    | 19 ++++++++++++++-----
 src/codegen/spirv/ir_builder.h     | 13 ++++++++++---
 3 files changed, 27 insertions(+), 9 deletions(-)

diff --git a/src/codegen/spirv/codegen_spirv.cc b/src/codegen/spirv/codegen_spirv.cc
index b6582ec6c0d8..2c71d94eaba1 100644
--- a/src/codegen/spirv/codegen_spirv.cc
+++ b/src/codegen/spirv/codegen_spirv.cc
@@ -35,7 +35,7 @@ std::vector<uint32_t> CodeGenSPIRV::BuildFunction(const LoweredFunc& f) {
       pod_args.push_back(arg);
     }
   }
-  spirv::Value func_ptr = builder_->DeclareKenrelFunction(f->name);
+  spirv::Value func_ptr = builder_->NewFunction();
   builder_->StartFunction(func_ptr);
 
   // All the POD arguments are passed in through PushConstant
@@ -56,6 +56,8 @@ std::vector<uint32_t> CodeGenSPIRV::BuildFunction(const LoweredFunc& f) {
   builder_->MakeInst(spv::OpReturn);
   builder_->MakeInst(spv::OpFunctionEnd);
 
+  builder_->CommitKernelFunction(func_ptr, f->name);
+
   return builder_->Finalize();
 }
 
diff --git a/src/codegen/spirv/ir_builder.cc b/src/codegen/spirv/ir_builder.cc
index f3e044596e5c..eb7a67228e60 100644
--- a/src/codegen/spirv/ir_builder.cc
+++ b/src/codegen/spirv/ir_builder.cc
@@ -222,12 +222,21 @@ Value IRBuilder::GetPushConstant(
   return this->MakeValue(spv::OpLoad, v_type, ptr);
 }
 
-Value IRBuilder::DeclareKenrelFunction(const std::string& name) {
-  Value val = NewValue(t_void_func_, kFunction);
+Value IRBuilder::NewFunction() {
+  return NewValue(t_void_func_, kFunction);
+}
+
+void IRBuilder::CommitKernelFunction(const Value& func, const std::string& name) {
+  CHECK_EQ(func.flag, kFunction);
   ib_.Begin(spv::OpEntryPoint)
-      .AddSeq(spv::ExecutionModelGLCompute, val, name)
-      .Commit(&entry_);
-  return val;
+    .AddSeq(spv::ExecutionModelGLCompute, func, name);
+  if (workgroup_id_.id != 0) {
+    ib_.Add(workgroup_id_);
+  }
+  if (local_id_.id != 0) {
+    ib_.Add(local_id_);
+  }
+  ib_.Commit(&entry_);
 }
 
 void IRBuilder::StartFunction(const Value& func) {
diff --git a/src/codegen/spirv/ir_builder.h b/src/codegen/spirv/ir_builder.h
index fc2d5bc68b6f..7c7afabdf566 100644
--- a/src/codegen/spirv/ir_builder.h
+++ b/src/codegen/spirv/ir_builder.h
@@ -485,11 +485,18 @@ class IRBuilder {
    */
   Value GetPushConstant(Value ptr_push_const, const SType& v_type, uint32_t index);
   /*!
-   * \brief Declare a kernel function
-   * \param name Name of the entry point.
+   * \brief Declare a new function
    * \return The created function ID.
    */
-  Value DeclareKenrelFunction(const std::string& name);
+  Value NewFunction();
+  /*!
+   * \brief Declare the entry point for a kernel function. This should be
+   * invoked after building the function so the builder is aware of which
+   * variables to declare as part of the function's interface.
+   * \param func The previously declared function.
+   * \param name Name of the entry point.
+   */
+  void CommitKernelFunction(const Value& func, const std::string& name);
   /*!
    * \brief Start function scope.
    * \param func function to be started.

From afd2b9b8d484ed4819f193476d9337b5b83c87c8 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 9 Jul 2018 15:23:41 -0700
Subject: [PATCH 813/949]  [RPC] graduate tvm.contrib.rpc -> tvm.rpc (#1410)

---
 apps/android_rpc/tests/android_rpc_test.py     |  3 ++-
 apps/ios_rpc/tests/ios_rpc_test.py             |  3 ++-
 apps/ios_rpc/tvmrpc/TVMRuntime.mm              |  4 ++--
 docs/api/python/rpc.rst                        | 18 +++++++++---------
 docs/install/docker.rst                        |  6 +++---
 .../java/ml/dmlc/tvm/rpc/NativeServerLoop.java |  4 ++--
 .../src/main/java/ml/dmlc/tvm/rpc/RPC.java     |  2 +-
 .../main/java/ml/dmlc/tvm/rpc/RPCSession.java  |  4 ++--
 .../src/test/scripts/test_rpc_proxy_server.py  |  2 +-
 nnvm/tests/python/compiler/test_param_dict.py  |  3 ++-
 nnvm/tests/python/compiler/test_rpc_exec.py    |  3 ++-
 python/tvm/contrib/graph_runtime.py            |  2 +-
 python/tvm/contrib/peak.py                     | 12 ++++++------
 python/tvm/contrib/rpc.py                      |  9 +++++++++
 python/tvm/exec/query_rpc_tracker.py           |  2 +-
 python/tvm/exec/rpc_proxy.py                   |  2 +-
 python/tvm/exec/rpc_server.py                  |  2 +-
 python/tvm/exec/rpc_tracker.py                 |  2 +-
 python/tvm/{contrib => }/rpc/__init__.py       |  0
 python/tvm/{contrib => }/rpc/base.py           |  8 ++++----
 python/tvm/{contrib => }/rpc/client.py         | 14 +++++++-------
 python/tvm/{contrib => }/rpc/proxy.py          |  2 +-
 python/tvm/{contrib => }/rpc/server.py         | 14 +++++++-------
 python/tvm/{contrib => }/rpc/tornado_util.py   |  0
 python/tvm/{contrib => }/rpc/tracker.py        |  2 +-
 src/runtime/module.cc                          |  2 +-
 src/runtime/rpc/rpc_event_impl.cc              |  2 +-
 src/runtime/rpc/rpc_module.cc                  |  8 ++++----
 src/runtime/rpc/rpc_server_env.cc              | 10 +++++-----
 src/runtime/rpc/rpc_session.cc                 |  6 +++---
 src/runtime/rpc/rpc_socket_impl.cc             |  4 ++--
 tests/python/contrib/test_rpc_proxy.py         |  4 ++--
 tests/python/contrib/test_rpc_tracker.py       |  6 +++---
 .../python/unittest/test_codegen_cross_llvm.py |  3 ++-
 tests/python/unittest/test_runtime_graph.py    |  3 ++-
 tests/python/unittest/test_runtime_rpc.py      |  3 ++-
 tests/web/websock_rpc_test.py                  |  3 ++-
 tests/webgl/test_local_save_load.py            |  3 ++-
 tests/webgl/test_remote_save_load.py           |  3 ++-
 topi/recipe/gemm/android_gemm_square.py        |  7 ++++---
 tutorials/cross_compilation_and_rpc.py         |  3 ++-
 tutorials/nnvm/deploy_model_on_mali_gpu.py     |  5 ++---
 tutorials/nnvm/deploy_model_on_rasp.py         |  5 ++---
 tutorials/nnvm/from_mxnet_to_webgl.py          |  3 ++-
 tutorials/nnvm_quick_start.py                  |  2 +-
 web/tvm_runtime.js                             |  2 +-
 web/web_runtime.cc                             |  4 ++--
 47 files changed, 117 insertions(+), 97 deletions(-)
 create mode 100644 python/tvm/contrib/rpc.py
 rename python/tvm/{contrib => }/rpc/__init__.py (100%)
 rename python/tvm/{contrib => }/rpc/base.py (95%)
 rename python/tvm/{contrib => }/rpc/client.py (97%)
 rename python/tvm/{contrib => }/rpc/proxy.py (99%)
 rename python/tvm/{contrib => }/rpc/server.py (97%)
 rename python/tvm/{contrib => }/rpc/tornado_util.py (100%)
 rename python/tvm/{contrib => }/rpc/tracker.py (99%)

diff --git a/apps/android_rpc/tests/android_rpc_test.py b/apps/android_rpc/tests/android_rpc_test.py
index 7a5a1060efb1..a3362fafa63a 100644
--- a/apps/android_rpc/tests/android_rpc_test.py
+++ b/apps/android_rpc/tests/android_rpc_test.py
@@ -6,7 +6,8 @@
 
 import tvm
 import os
-from tvm.contrib import rpc, util, ndk
+from tvm import rpc
+from tvm.contrib import util, ndk
 import numpy as np
 
 # Set to be address of tvm proxy.
diff --git a/apps/ios_rpc/tests/ios_rpc_test.py b/apps/ios_rpc/tests/ios_rpc_test.py
index a3df1d3a9043..c0cfcde6e294 100644
--- a/apps/ios_rpc/tests/ios_rpc_test.py
+++ b/apps/ios_rpc/tests/ios_rpc_test.py
@@ -6,7 +6,8 @@
 
 import tvm
 import os
-from tvm.contrib import rpc, util, xcode
+from tvm import rpc
+from tvm.contrib import util, xcode
 import numpy as np
 
 # Set to be address of tvm proxy.
diff --git a/apps/ios_rpc/tvmrpc/TVMRuntime.mm b/apps/ios_rpc/tvmrpc/TVMRuntime.mm
index 23cf3e27900d..ecda4f6ee84a 100644
--- a/apps/ios_rpc/tvmrpc/TVMRuntime.mm
+++ b/apps/ios_rpc/tvmrpc/TVMRuntime.mm
@@ -103,13 +103,13 @@ void LaunchSyncServer() {
       ->ServerLoop();
 }
 
-TVM_REGISTER_GLOBAL("tvm.contrib.rpc.server.workpath")
+TVM_REGISTER_GLOBAL("tvm.rpc.server.workpath")
 .set_body([](TVMArgs args, TVMRetValue* rv) {
     static RPCEnv env;
     *rv = env.GetPath(args[0]);
   });
 
-TVM_REGISTER_GLOBAL("tvm.contrib.rpc.server.load_module")
+TVM_REGISTER_GLOBAL("tvm.rpc.server.load_module")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
     std::string name = args[0];
     std::string fmt = GetFileFormat(name, "");
diff --git a/docs/api/python/rpc.rst b/docs/api/python/rpc.rst
index 4d46bda1ab24..6c4ef59a493c 100644
--- a/docs/api/python/rpc.rst
+++ b/docs/api/python/rpc.rst
@@ -1,22 +1,22 @@
-tvm.contrib.rpc
----------------
-.. automodule:: tvm.contrib.rpc
+tvm.rpc
+-------
+.. automodule:: tvm.rpc
 
-.. autofunction:: tvm.contrib.rpc.connect
-.. autofunction:: tvm.contrib.rpc.connect_tracker
+.. autofunction:: tvm.rpc.connect
+.. autofunction:: tvm.rpc.connect_tracker
 
-.. autoclass:: tvm.contrib.rpc.TrackerSession
+.. autoclass:: tvm.rpc.TrackerSession
     :members:
     :inherited-members:
 
-.. autoclass:: tvm.contrib.rpc.RPCSession
+.. autoclass:: tvm.rpc.RPCSession
     :members:
     :inherited-members:
 
-.. autoclass:: tvm.contrib.rpc.LocalSession
+.. autoclass:: tvm.rpc.LocalSession
     :members:
     :inherited-members:
 
-.. autoclass:: tvm.contrib.rpc.Server
+.. autoclass:: tvm.rpc.Server
     :members:
     :inherited-members:
diff --git a/docs/install/docker.rst b/docs/install/docker.rst
index d6cd114bd3d0..8d089522761d 100644
--- a/docs/install/docker.rst
+++ b/docs/install/docker.rst
@@ -19,10 +19,10 @@ We can then use the following command to launch a `tvmai/demo-cpu` image.
 
 .. code:: bash
 
-    /path/to/tvm/docker/bash.sh tvmai/demo_cpu
+    /path/to/tvm/docker/bash.sh tvmai/demo-cpu
 
-.. note::
-    You can find all the prebuilt images in `<https://hub.docker.com/r/tvmai/>`_
+You can also change `demo-cpu` to `demo-gpu` to get a CUDA enabled image.
+You can find all the prebuilt images in `<https://hub.docker.com/r/tvmai/>`_
 
 
 This auxiliary script does the following things:
diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/NativeServerLoop.java b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/NativeServerLoop.java
index 329d6688fa87..4bc4c34c2d0a 100644
--- a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/NativeServerLoop.java
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/NativeServerLoop.java
@@ -70,13 +70,13 @@ private static File serverEnv() throws IOException {
       throw new IOException("Couldn't create directory " + tempDir.getAbsolutePath());
     }
 
-    Function.register("tvm.contrib.rpc.server.workpath", new Function.Callback() {
+    Function.register("tvm.rpc.server.workpath", new Function.Callback() {
       @Override public Object invoke(TVMValue... args) {
         return tempDir + File.separator + args[0].asString();
       }
     }, true);
 
-    Function.register("tvm.contrib.rpc.server.load_module", new Function.Callback() {
+    Function.register("tvm.rpc.server.load_module", new Function.Callback() {
       @Override public Object invoke(TVMValue... args) {
         String filename = args[0].asString();
         String path = tempDir + File.separator + filename;
diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPC.java b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPC.java
index e3b8b9366751..225bcaeefbd3 100644
--- a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPC.java
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPC.java
@@ -37,7 +37,7 @@ protected Map<String, Function> initialValue() {
   static Function getApi(String name) {
     Function func = apiFuncs.get().get(name);
     if (func == null) {
-      func = Function.getFunction("contrib.rpc." + name);
+      func = Function.getFunction("rpc." + name);
       if (func == null) {
         return null;
       }
diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPCSession.java b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPCSession.java
index cb4ccf49434b..59da849000e9 100644
--- a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPCSession.java
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPCSession.java
@@ -172,7 +172,7 @@ public void upload(byte[] data, String target) {
     final String funcName = "upload";
     Function remoteFunc = remoteFuncs.get(funcName);
     if (remoteFunc == null) {
-      remoteFunc = getFunction("tvm.contrib.rpc.server.upload");
+      remoteFunc = getFunction("tvm.rpc.server.upload");
       remoteFuncs.put(funcName, remoteFunc);
     }
     remoteFunc.pushArg(target).pushArg(data).invoke();
@@ -205,7 +205,7 @@ public byte[] download(String path) {
     final String name = "download";
     Function func = remoteFuncs.get(name);
     if (func == null) {
-      func = getFunction("tvm.contrib.rpc.server.download");
+      func = getFunction("tvm.rpc.server.download");
       remoteFuncs.put(name, func);
     }
     return func.pushArg(path).invoke().asBytes();
diff --git a/jvm/core/src/test/scripts/test_rpc_proxy_server.py b/jvm/core/src/test/scripts/test_rpc_proxy_server.py
index 9ad5a33f23dd..09b7e4a6fe02 100644
--- a/jvm/core/src/test/scripts/test_rpc_proxy_server.py
+++ b/jvm/core/src/test/scripts/test_rpc_proxy_server.py
@@ -1,5 +1,5 @@
 import time
-from tvm.contrib.rpc import proxy
+from tvm.rpc import proxy
 
 def start_proxy_server(port, timeout):
     prox = proxy.Proxy("localhost", port=port, port_end=port+1)
diff --git a/nnvm/tests/python/compiler/test_param_dict.py b/nnvm/tests/python/compiler/test_param_dict.py
index eb547373e38f..4fa41a3a5833 100644
--- a/nnvm/tests/python/compiler/test_param_dict.py
+++ b/nnvm/tests/python/compiler/test_param_dict.py
@@ -2,7 +2,8 @@
 import numpy as np
 import nnvm.compiler
 import tvm
-from tvm.contrib import rpc, util, graph_runtime
+from tvm import rpc
+from tvm.contrib import util, graph_runtime
 
 
 def test_save_load():
diff --git a/nnvm/tests/python/compiler/test_rpc_exec.py b/nnvm/tests/python/compiler/test_rpc_exec.py
index 61baf18d262d..03a921c0a5ae 100644
--- a/nnvm/tests/python/compiler/test_rpc_exec.py
+++ b/nnvm/tests/python/compiler/test_rpc_exec.py
@@ -1,5 +1,6 @@
 import tvm
-from tvm.contrib import util, rpc, graph_runtime
+from tvm import rpc
+from tvm.contrib import util, graph_runtime
 import nnvm.symbol as sym
 import nnvm.compiler
 import numpy as np
diff --git a/python/tvm/contrib/graph_runtime.py b/python/tvm/contrib/graph_runtime.py
index 3c1e2766aa47..9ce9dd602fa3 100644
--- a/python/tvm/contrib/graph_runtime.py
+++ b/python/tvm/contrib/graph_runtime.py
@@ -1,7 +1,7 @@
 """Minimum graph runtime that executes graph containing TVM PackedFunc."""
 from .._ffi.base import string_types
 from .._ffi.function import get_global_func
-from .rpc import base as rpc_base
+from ..rpc import base as rpc_base
 from .. import ndarray as nd
 
 
diff --git a/python/tvm/contrib/peak.py b/python/tvm/contrib/peak.py
index 36bfaaee7662..eefa7bfa0b52 100644
--- a/python/tvm/contrib/peak.py
+++ b/python/tvm/contrib/peak.py
@@ -2,9 +2,9 @@
 """measure bandwidth and compute peak"""
 
 import logging
-
 import tvm
-from tvm.contrib import rpc, util
+from . import util
+from .. import rpc
 
 def _convert_to_remote(func, remote):
     """ convert module function to remote rpc function"""
@@ -47,7 +47,7 @@ def measure_bandwidth_sum(total_item, item_per_thread, stride,
         host compilation target
     ctx: TVMcontext
         the context of array
-    remote: tvm.contrib.rpc.RPCSession
+    remote: tvm.rpc.RPCSession
         remote rpc session
     n_times: int
         number of runs for taking mean
@@ -107,7 +107,7 @@ def measure_bandwidth_all_types(total_item, item_per_thread, n_times,
         the target and option of the compilation.
     target_host : str or :any:`tvm.target.Target`
         host compilation target
-    remote: tvm.contrib.rpc.RPCSession
+    remote: tvm.rpc.RPCSession
         remote rpc session
     ctx: TVMcontext
         the context of array
@@ -165,7 +165,7 @@ def measure_compute_mad(total_item, item_per_thread, base_type, bits, lanes,
         the target and option of the compilation.
     target_host : str or :any:`tvm.target.Target`
         host compilation target
-    remote: tvm.contrib.rpc.RPCSession
+    remote: tvm.rpc.RPCSession
         if it is not None, use remote rpc session
     ctx: TVMcontext
         the context of array
@@ -250,7 +250,7 @@ def measure_compute_all_types(total_item, item_per_thread, n_times,
         the target and option of the compilation.
     target_host : str or :any:`tvm.target.Target`
         host compilation target
-    remote: tvm.contrib.rpc.RPCSession
+    remote: tvm.rpc.RPCSession
         remote rpc session
     ctx: TVMcontext
         the context of array
diff --git a/python/tvm/contrib/rpc.py b/python/tvm/contrib/rpc.py
new file mode 100644
index 000000000000..8f6611ddb905
--- /dev/null
+++ b/python/tvm/contrib/rpc.py
@@ -0,0 +1,9 @@
+"""Deprecation RPC module"""
+# pylint: disable=unused-import
+from __future__ import absolute_import as _abs
+import warnings
+from ..rpc import Server, RPCSession, LocalSession, TrackerSession, connect, connect_tracker
+
+warnings.warn(
+    "Please use tvm.rpc instead of tvm.conrtib.rpc. tvm.contrib.rpc is going to be removed in 0.5",
+    DeprecationWarning)
diff --git a/python/tvm/exec/query_rpc_tracker.py b/python/tvm/exec/query_rpc_tracker.py
index 061d1606db8e..32ec4f5da764 100644
--- a/python/tvm/exec/query_rpc_tracker.py
+++ b/python/tvm/exec/query_rpc_tracker.py
@@ -4,7 +4,7 @@
 import logging
 import argparse
 import os
-from ..contrib import rpc
+from .. import rpc
 
 def main():
     """Main funciton"""
diff --git a/python/tvm/exec/rpc_proxy.py b/python/tvm/exec/rpc_proxy.py
index eaa95e8cb79c..678023a10550 100644
--- a/python/tvm/exec/rpc_proxy.py
+++ b/python/tvm/exec/rpc_proxy.py
@@ -7,7 +7,7 @@
 import multiprocessing
 import sys
 import os
-from ..contrib.rpc.proxy import Proxy
+from ..rpc.proxy import Proxy
 
 
 def find_example_resource():
diff --git a/python/tvm/exec/rpc_server.py b/python/tvm/exec/rpc_server.py
index e26dbc3b2025..c9f0777fad57 100644
--- a/python/tvm/exec/rpc_server.py
+++ b/python/tvm/exec/rpc_server.py
@@ -6,7 +6,7 @@
 import multiprocessing
 import sys
 import logging
-from ..contrib import rpc
+from .. import rpc
 
 def main(args):
     """Main function"""
diff --git a/python/tvm/exec/rpc_tracker.py b/python/tvm/exec/rpc_tracker.py
index 3e4c63e20f9b..3ac013d649f7 100644
--- a/python/tvm/exec/rpc_tracker.py
+++ b/python/tvm/exec/rpc_tracker.py
@@ -6,7 +6,7 @@
 import argparse
 import multiprocessing
 import sys
-from ..contrib.rpc.tracker import Tracker
+from ..rpc.tracker import Tracker
 
 
 def main(args):
diff --git a/python/tvm/contrib/rpc/__init__.py b/python/tvm/rpc/__init__.py
similarity index 100%
rename from python/tvm/contrib/rpc/__init__.py
rename to python/tvm/rpc/__init__.py
diff --git a/python/tvm/contrib/rpc/base.py b/python/tvm/rpc/base.py
similarity index 95%
rename from python/tvm/contrib/rpc/base.py
rename to python/tvm/rpc/base.py
index d0004f0c86f8..9d1df9f1e7ec 100644
--- a/python/tvm/contrib/rpc/base.py
+++ b/python/tvm/rpc/base.py
@@ -9,8 +9,8 @@
 import random
 import logging
 
-from ..._ffi.function import _init_api
-from ..._ffi.base import py_str
+from .._ffi.function import _init_api
+from .._ffi.base import py_str
 
 # Magic header for RPC data plane
 RPC_MAGIC = 0xff271
@@ -158,5 +158,5 @@ def connect_with_retry(addr, timeout=60, retry_period=5, silent=False):
             time.sleep(retry_period)
 
 
-# Still use tvm.contrib.rpc for the foreign functions
-_init_api("tvm.contrib.rpc", "tvm.contrib.rpc.base")
+# Still use tvm.rpc for the foreign functions
+_init_api("tvm.rpc", "tvm.rpc.base")
diff --git a/python/tvm/contrib/rpc/client.py b/python/tvm/rpc/client.py
similarity index 97%
rename from python/tvm/contrib/rpc/client.py
rename to python/tvm/rpc/client.py
index 637aed6e4cc2..ef1e7b5393e5 100644
--- a/python/tvm/contrib/rpc/client.py
+++ b/python/tvm/rpc/client.py
@@ -7,11 +7,11 @@
 import time
 
 from . import base
-from .. import util
-from ..._ffi.base import TVMError
-from ..._ffi import function as function
-from ..._ffi import ndarray as nd
-from ...module import load as _load_module
+from ..contrib import util
+from .._ffi.base import TVMError
+from .._ffi import function as function
+from .._ffi import ndarray as nd
+from ..module import load as _load_module
 
 
 class RPCSession(object):
@@ -82,7 +82,7 @@ def upload(self, data, target=None):
 
         if "upload" not in self._remote_funcs:
             self._remote_funcs["upload"] = self.get_function(
-                "tvm.contrib.rpc.server.upload")
+                "tvm.rpc.server.upload")
         self._remote_funcs["upload"](target, blob)
 
     def download(self, path):
@@ -100,7 +100,7 @@ def download(self, path):
         """
         if "download" not in self._remote_funcs:
             self._remote_funcs["download"] = self.get_function(
-                "tvm.contrib.rpc.server.download")
+                "tvm.rpc.server.download")
         return self._remote_funcs["download"](path)
 
     def load_module(self, path):
diff --git a/python/tvm/contrib/rpc/proxy.py b/python/tvm/rpc/proxy.py
similarity index 99%
rename from python/tvm/contrib/rpc/proxy.py
rename to python/tvm/rpc/proxy.py
index e1e81d20b611..44de99e7e959 100644
--- a/python/tvm/contrib/rpc/proxy.py
+++ b/python/tvm/rpc/proxy.py
@@ -28,7 +28,7 @@
 from . import base
 from .base import TrackerCode
 from .server import _server_env
-from ..._ffi.base import py_str
+from .._ffi.base import py_str
 
 
 class ForwardHandler(object):
diff --git a/python/tvm/contrib/rpc/server.py b/python/tvm/rpc/server.py
similarity index 97%
rename from python/tvm/contrib/rpc/server.py
rename to python/tvm/rpc/server.py
index 938e5cdf261a..0d6112df6089 100644
--- a/python/tvm/contrib/rpc/server.py
+++ b/python/tvm/rpc/server.py
@@ -21,11 +21,11 @@
 import time
 import sys
 
-from ..._ffi.function import register_func
-from ..._ffi.base import py_str
-from ..._ffi.libinfo import find_lib_path
-from ...module import load as _load_module
-from .. import util
+from .._ffi.function import register_func
+from .._ffi.base import py_str
+from .._ffi.libinfo import find_lib_path
+from ..module import load as _load_module
+from ..contrib import util
 from . import base
 from . base import TrackerCode
 
@@ -36,11 +36,11 @@ def _server_env(load_library, logger):
         logger = logging.getLogger()
 
     # pylint: disable=unused-variable
-    @register_func("tvm.contrib.rpc.server.workpath")
+    @register_func("tvm.rpc.server.workpath")
     def get_workpath(path):
         return temp.relpath(path)
 
-    @register_func("tvm.contrib.rpc.server.load_module", override=True)
+    @register_func("tvm.rpc.server.load_module", override=True)
     def load_module(file_name):
         """Load module from remote side."""
         path = temp.relpath(file_name)
diff --git a/python/tvm/contrib/rpc/tornado_util.py b/python/tvm/rpc/tornado_util.py
similarity index 100%
rename from python/tvm/contrib/rpc/tornado_util.py
rename to python/tvm/rpc/tornado_util.py
diff --git a/python/tvm/contrib/rpc/tracker.py b/python/tvm/rpc/tracker.py
similarity index 99%
rename from python/tvm/contrib/rpc/tracker.py
rename to python/tvm/rpc/tracker.py
index 47e81859c934..02d226123f1c 100644
--- a/python/tvm/contrib/rpc/tracker.py
+++ b/python/tvm/rpc/tracker.py
@@ -39,7 +39,7 @@
     raise ImportError(
         "RPCTracker module requires tornado package %s" % error_msg)
 
-from ..._ffi.base import py_str
+from .._ffi.base import py_str
 from . import base
 from .base import RPC_TRACKER_MAGIC, TrackerCode
 
diff --git a/src/runtime/module.cc b/src/runtime/module.cc
index f5cccecb8bdd..dbddfde44733 100644
--- a/src/runtime/module.cc
+++ b/src/runtime/module.cc
@@ -20,7 +20,7 @@ void Module::Import(Module other) {
   if (!std::strcmp((*this)->type_key(), "rpc")) {
     static const PackedFunc* fimport_ = nullptr;
     if (fimport_ == nullptr) {
-      fimport_ = runtime::Registry::Get("contrib.rpc._ImportRemoteModule");
+      fimport_ = runtime::Registry::Get("rpc._ImportRemoteModule");
       CHECK(fimport_ != nullptr);
     }
     (*fimport_)(*this, other);
diff --git a/src/runtime/rpc/rpc_event_impl.cc b/src/runtime/rpc/rpc_event_impl.cc
index 7c3379434b9f..fc5ecca1f421 100644
--- a/src/runtime/rpc/rpc_event_impl.cc
+++ b/src/runtime/rpc/rpc_event_impl.cc
@@ -44,7 +44,7 @@ PackedFunc CreateEventDrivenServer(PackedFunc fsend,
     });
 }
 
-TVM_REGISTER_GLOBAL("contrib.rpc._CreateEventDrivenServer")
+TVM_REGISTER_GLOBAL("rpc._CreateEventDrivenServer")
 .set_body([](TVMArgs args, TVMRetValue* rv) {
     *rv = CreateEventDrivenServer(args[0], args[1], args[2]);
   });
diff --git a/src/runtime/rpc/rpc_module.cc b/src/runtime/rpc/rpc_module.cc
index 0714261bba8f..251871bf0cc1 100644
--- a/src/runtime/rpc/rpc_module.cc
+++ b/src/runtime/rpc/rpc_module.cc
@@ -165,7 +165,7 @@ TVM_REGISTER_GLOBAL("module._RPCTimeEvaluator")
     }
   });
 
-TVM_REGISTER_GLOBAL("contrib.rpc._LoadRemoteModule")
+TVM_REGISTER_GLOBAL("rpc._LoadRemoteModule")
 .set_body([](TVMArgs args, TVMRetValue* rv) {
     Module m = args[0];
     std::string tkey = m->type_key();
@@ -177,7 +177,7 @@ TVM_REGISTER_GLOBAL("contrib.rpc._LoadRemoteModule")
     *rv = Module(n);
   });
 
-TVM_REGISTER_GLOBAL("contrib.rpc._ImportRemoteModule")
+TVM_REGISTER_GLOBAL("rpc._ImportRemoteModule")
 .set_body([](TVMArgs args, TVMRetValue* rv) {
     Module parent = args[0];
     Module child = args[1];
@@ -192,7 +192,7 @@ TVM_REGISTER_GLOBAL("contrib.rpc._ImportRemoteModule")
                              cmod->module_handle());
   });
 
-TVM_REGISTER_GLOBAL("contrib.rpc._ModuleHandle")
+TVM_REGISTER_GLOBAL("rpc._ModuleHandle")
 .set_body([](TVMArgs args, TVMRetValue* rv) {
     Module m = args[0];
     std::string tkey = m->type_key();
@@ -200,7 +200,7 @@ TVM_REGISTER_GLOBAL("contrib.rpc._ModuleHandle")
     *rv = static_cast<RPCModuleNode*>(m.operator->())->module_handle();
   });
 
-TVM_REGISTER_GLOBAL("contrib.rpc._SessTableIndex")
+TVM_REGISTER_GLOBAL("rpc._SessTableIndex")
 .set_body([](TVMArgs args, TVMRetValue* rv) {
     Module m = args[0];
     std::string tkey = m->type_key();
diff --git a/src/runtime/rpc/rpc_server_env.cc b/src/runtime/rpc/rpc_server_env.cc
index a995a953bf79..ca91b88247e5 100644
--- a/src/runtime/rpc/rpc_server_env.cc
+++ b/src/runtime/rpc/rpc_server_env.cc
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2017 by Contributors
- * \file rpc_server_env
+ * \file rpc_server_env.cc
  * \brief Server environment of the RPC.
  */
 #include <tvm/runtime/registry.h>
@@ -11,19 +11,19 @@ namespace runtime {
 
 std::string RPCGetPath(const std::string& name) {
   static const PackedFunc* f =
-      runtime::Registry::Get("tvm.contrib.rpc.server.workpath");
-  CHECK(f != nullptr) << "require tvm.contrib.rpc.server.workpath";
+      runtime::Registry::Get("tvm.rpc.server.workpath");
+  CHECK(f != nullptr) << "require tvm.rpc.server.workpath";
   return (*f)(name);
 }
 
-TVM_REGISTER_GLOBAL("tvm.contrib.rpc.server.upload").
+TVM_REGISTER_GLOBAL("tvm.rpc.server.upload").
 set_body([](TVMArgs args, TVMRetValue *rv) {
     std::string file_name = RPCGetPath(args[0]);
     std::string data = args[1];
     SaveBinaryToFile(file_name, data);
   });
 
-TVM_REGISTER_GLOBAL("tvm.contrib.rpc.server.download")
+TVM_REGISTER_GLOBAL("tvm.rpc.server.download")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
     std::string file_name = RPCGetPath(args[0]);
     std::string data;
diff --git a/src/runtime/rpc/rpc_session.cc b/src/runtime/rpc/rpc_session.cc
index 4d38fdc0158b..2f181e7edf9a 100644
--- a/src/runtime/rpc/rpc_session.cc
+++ b/src/runtime/rpc/rpc_session.cc
@@ -850,12 +850,12 @@ void RPCSession::Shutdown() {
 
 void RPCSession::ServerLoop() {
   std::lock_guard<std::recursive_mutex> lock(mutex_);
-  if (const auto* f = Registry::Get("tvm.contrib.rpc.server.start")) {
+  if (const auto* f = Registry::Get("tvm.rpc.server.start")) {
     (*f)();
   }
   TVMRetValue rv;
   CHECK(HandleUntilReturnEvent(&rv, false, nullptr) == RPCCode::kShutdown);
-  if (const auto* f = Registry::Get("tvm.contrib.rpc.server.shutdown")) {
+  if (const auto* f = Registry::Get("tvm.rpc.server.shutdown")) {
     (*f)();
   }
   channel_.reset(nullptr);
@@ -1046,7 +1046,7 @@ void RPCCopyAmongRemote(TVMArgs args, TVMRetValue *rv) {
 void RPCModuleLoad(TVMArgs args, TVMRetValue *rv) {
   static const PackedFunc* fsys_load_ = nullptr;
   if (fsys_load_ == nullptr) {
-    fsys_load_ = runtime::Registry::Get("tvm.contrib.rpc.server.load_module");
+    fsys_load_ = runtime::Registry::Get("tvm.rpc.server.load_module");
     CHECK(fsys_load_ != nullptr);
   }
   std::string file_name = args[0];
diff --git a/src/runtime/rpc/rpc_socket_impl.cc b/src/runtime/rpc/rpc_socket_impl.cc
index 8348475d016e..22f221d46526 100644
--- a/src/runtime/rpc/rpc_socket_impl.cc
+++ b/src/runtime/rpc/rpc_socket_impl.cc
@@ -90,12 +90,12 @@ void RPCServerLoop(int sockfd) {
       "SockServerLoop", "")->ServerLoop();
 }
 
-TVM_REGISTER_GLOBAL("contrib.rpc._Connect")
+TVM_REGISTER_GLOBAL("rpc._Connect")
 .set_body([](TVMArgs args, TVMRetValue* rv) {
     *rv = RPCClientConnect(args[0], args[1], args[2]);
   });
 
-TVM_REGISTER_GLOBAL("contrib.rpc._ServerLoop")
+TVM_REGISTER_GLOBAL("rpc._ServerLoop")
 .set_body([](TVMArgs args, TVMRetValue* rv) {
     RPCServerLoop(args[0]);
   });
diff --git a/tests/python/contrib/test_rpc_proxy.py b/tests/python/contrib/test_rpc_proxy.py
index 1cbc0ba759e8..3a9364774ad6 100644
--- a/tests/python/contrib/test_rpc_proxy.py
+++ b/tests/python/contrib/test_rpc_proxy.py
@@ -3,7 +3,7 @@
 import numpy as np
 import time
 import multiprocessing
-from tvm.contrib import rpc
+from tvm import rpc
 
 def rpc_proxy_check():
     """This is a simple test function for RPC Proxy
@@ -17,7 +17,7 @@ def rpc_proxy_check():
     """
 
     try:
-        from tvm.contrib.rpc import proxy
+        from tvm.rpc import proxy
         web_port = 8888
         prox = proxy.Proxy("localhost", web_port=web_port)
         def check():
diff --git a/tests/python/contrib/test_rpc_tracker.py b/tests/python/contrib/test_rpc_tracker.py
index 1ff23dbd4868..d8ecbaaa46e6 100644
--- a/tests/python/contrib/test_rpc_tracker.py
+++ b/tests/python/contrib/test_rpc_tracker.py
@@ -3,13 +3,13 @@
 import numpy as np
 import time
 import multiprocessing
-from tvm.contrib import rpc
+from tvm import rpc
 
 def check_server_drop():
     """test when server drops"""
     try:
-        from tvm.contrib.rpc import tracker, proxy, base
-        from tvm.contrib.rpc.base import TrackerCode
+        from tvm.rpc import tracker, proxy, base
+        from tvm.rpc.base import TrackerCode
 
         @tvm.register_func("rpc.test2.addone")
         def addone(x):
diff --git a/tests/python/unittest/test_codegen_cross_llvm.py b/tests/python/unittest/test_codegen_cross_llvm.py
index f2a1dea62463..aa6f9d708a41 100644
--- a/tests/python/unittest/test_codegen_cross_llvm.py
+++ b/tests/python/unittest/test_codegen_cross_llvm.py
@@ -2,7 +2,8 @@
 import tvm
 import os
 import struct
-from tvm.contrib import util, cc, rpc
+from tvm import rpc
+from tvm.contrib import util, cc
 import numpy as np
 
 def test_llvm_add_pipeline():
diff --git a/tests/python/unittest/test_runtime_graph.py b/tests/python/unittest/test_runtime_graph.py
index 7177810cfa0c..9978c5992910 100644
--- a/tests/python/unittest/test_runtime_graph.py
+++ b/tests/python/unittest/test_runtime_graph.py
@@ -1,7 +1,8 @@
 import tvm
 import numpy as np
 import json
-from tvm.contrib import rpc, util, graph_runtime
+from tvm import rpc
+from tvm.contrib import util, graph_runtime
 
 def test_graph_simple():
     n = 4
diff --git a/tests/python/unittest/test_runtime_rpc.py b/tests/python/unittest/test_runtime_rpc.py
index a609f313909c..e7c0cc1bbabd 100644
--- a/tests/python/unittest/test_runtime_rpc.py
+++ b/tests/python/unittest/test_runtime_rpc.py
@@ -3,7 +3,8 @@
 import logging
 import numpy as np
 import time
-from tvm.contrib import rpc, util
+from tvm import rpc
+from tvm.contrib import util
 
 
 def test_bigendian_rpc():
diff --git a/tests/web/websock_rpc_test.py b/tests/web/websock_rpc_test.py
index ece1f8beb024..0c1c8b050720 100644
--- a/tests/web/websock_rpc_test.py
+++ b/tests/web/websock_rpc_test.py
@@ -6,7 +6,8 @@
 
 import tvm
 import os
-from tvm.contrib import rpc, util, emscripten
+from tvm import rpc
+from tvm.contrib import util, emscripten
 import numpy as np
 
 proxy_host = "localhost"
diff --git a/tests/webgl/test_local_save_load.py b/tests/webgl/test_local_save_load.py
index 9dca0d3acfca..5ed058a7461c 100644
--- a/tests/webgl/test_local_save_load.py
+++ b/tests/webgl/test_local_save_load.py
@@ -1,6 +1,7 @@
 import numpy as np
 import tvm
-from tvm.contrib import rpc, util, emscripten
+from tvm import rpc
+from tvm.contrib import util, emscripten
 
 def test_local_save_load():
     if not tvm.module.enabled("opengl"):
diff --git a/tests/webgl/test_remote_save_load.py b/tests/webgl/test_remote_save_load.py
index 507616ee9f2e..b1db6ce741c5 100644
--- a/tests/webgl/test_remote_save_load.py
+++ b/tests/webgl/test_remote_save_load.py
@@ -14,7 +14,8 @@
 
 import numpy as np
 import tvm
-from tvm.contrib import rpc, util, emscripten
+from tvm import rpc
+from tvm.contrib import util, emscripten
 
 proxy_host = "localhost"
 proxy_port = 9090
diff --git a/topi/recipe/gemm/android_gemm_square.py b/topi/recipe/gemm/android_gemm_square.py
index f6f3b5ab4589..361c56f636b2 100644
--- a/topi/recipe/gemm/android_gemm_square.py
+++ b/topi/recipe/gemm/android_gemm_square.py
@@ -1,14 +1,15 @@
 """Example code to do square matrix multiplication on Android Phone."""
 import tvm
 import os
-from tvm.contrib import rpc, util, ndk
+from tvm import rpc
+from tvm.contrib import util, ndk
 import numpy as np
 
 # Set to be address of tvm proxy.
 proxy_host = os.environ["TVM_ANDROID_RPC_PROXY_HOST"]
 proxy_port = 9090
 key = "android"
- 
+
 # Change target configuration.
 # Run `adb shell cat /proc/cpuinfo` to find the arch.
 arch = "arm64"
@@ -100,7 +101,7 @@ def test_gemm_gpu(N, times, bn, num_block, num_thread):
     print(tvm.lower(s, [A, B, C], simple_mode=True))
 
     f = tvm.build(s, [A, B, C], "opencl", target_host=target, name="gemm_gpu")
-    temp = util.tempdir()   
+    temp = util.tempdir()
     path_dso = temp.relpath("gemm_gpu.so")
     f.export_library(path_dso, ndk.create_shared)
 
diff --git a/tutorials/cross_compilation_and_rpc.py b/tutorials/cross_compilation_and_rpc.py
index 3658e68fae0a..e9f80f13fb0c 100644
--- a/tutorials/cross_compilation_and_rpc.py
+++ b/tutorials/cross_compilation_and_rpc.py
@@ -106,7 +106,8 @@
 
 import tvm
 import numpy as np
-from tvm.contrib import rpc, util
+from tvm import rpc
+from tvm.contrib import util
 
 ######################################################################
 # Declare and Cross Compile Kernel on Local Machine
diff --git a/tutorials/nnvm/deploy_model_on_mali_gpu.py b/tutorials/nnvm/deploy_model_on_mali_gpu.py
index 116e99f76790..2f3c332dae17 100644
--- a/tutorials/nnvm/deploy_model_on_mali_gpu.py
+++ b/tutorials/nnvm/deploy_model_on_mali_gpu.py
@@ -15,9 +15,8 @@
 import tvm
 import nnvm.compiler
 import nnvm.testing
-from tvm.contrib import util, rpc
-from tvm.contrib import graph_runtime as runtime
-
+from tvm import rpc
+from tvm.contrib import util, graph_runtime as runtime
 
 ######################################################################
 # Build TVM Runtime on Device
diff --git a/tutorials/nnvm/deploy_model_on_rasp.py b/tutorials/nnvm/deploy_model_on_rasp.py
index b14c8b052e65..f4527a547244 100644
--- a/tutorials/nnvm/deploy_model_on_rasp.py
+++ b/tutorials/nnvm/deploy_model_on_rasp.py
@@ -11,9 +11,8 @@
 import tvm
 import nnvm.compiler
 import nnvm.testing
-from tvm.contrib import util, rpc
-from tvm.contrib import graph_runtime as runtime
-
+from tvm import rpc
+from tvm.contrib import util, graph_runtime as runtime
 
 ######################################################################
 # Build TVM Runtime on Device
diff --git a/tutorials/nnvm/from_mxnet_to_webgl.py b/tutorials/nnvm/from_mxnet_to_webgl.py
index 7ff553bcb368..75279839bfb3 100644
--- a/tutorials/nnvm/from_mxnet_to_webgl.py
+++ b/tutorials/nnvm/from_mxnet_to_webgl.py
@@ -326,7 +326,8 @@ def deploy_local():
 def deploy_rpc():
     """Runs the demo that deploys a model remotely through RPC.
     """
-    from tvm.contrib import rpc, util, emscripten
+    from tvm import rpc
+    from tvm.contrib import util, emscripten
 
     # As usual, load the resnet18 model.
     net, params, data_shape, out_shape = load_mxnet_resnet()
diff --git a/tutorials/nnvm_quick_start.py b/tutorials/nnvm_quick_start.py
index 30650701516c..350b5b0ef25c 100644
--- a/tutorials/nnvm_quick_start.py
+++ b/tutorials/nnvm_quick_start.py
@@ -133,7 +133,7 @@
 # `llvm -target=armv7l-none-linux-gnueabihf -mcpu=cortex-a53 -mattr=+neon`
 # is the recommended compilation configuration, thanks to Ziheng's work.
 
-from tvm.contrib import rpc
+from tvm import rpc
 
 use_rasp = False
 host = 'rasp0'
diff --git a/web/tvm_runtime.js b/web/tvm_runtime.js
index a0fdd57bc205..ef594e9433fb 100644
--- a/web/tvm_runtime.js
+++ b/web/tvm_runtime.js
@@ -882,7 +882,7 @@ var tvm_runtime = tvm_runtime || {};
 
         if (typeof systemFunc.fcreateServer === "undefined") {
           systemFunc.fcreateServer =
-            getGlobalFunc("contrib.rpc._CreateEventDrivenServer");
+            getGlobalFunc("rpc._CreateEventDrivenServer");
         }
         if (systemFunc.fcreateServer == null) {
           throwError("RPCServer is not included in runtime");
diff --git a/web/web_runtime.cc b/web/web_runtime.cc
index b8db8cce7006..46fe54b9f88f 100644
--- a/web/web_runtime.cc
+++ b/web/web_runtime.cc
@@ -39,13 +39,13 @@ struct RPCEnv {
   std::string base_;
 };
 
-TVM_REGISTER_GLOBAL("tvm.contrib.rpc.server.workpath")
+TVM_REGISTER_GLOBAL("tvm.rpc.server.workpath")
 .set_body([](TVMArgs args, TVMRetValue* rv) {
     static RPCEnv env;
     *rv = env.GetPath(args[0]);
   });
 
-TVM_REGISTER_GLOBAL("tvm.contrib.rpc.server.load_module")
+TVM_REGISTER_GLOBAL("tvm.rpc.server.load_module")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
     std::string file_name = "/rpc/" + args[0].operator std::string();
     *rv = Module::LoadFromFile(file_name, "");

From 4d0c9362616351860cd8841c75638ebb1debe620 Mon Sep 17 00:00:00 2001
From: alex-weaver <awsweaver@gmail.com>
Date: Tue, 10 Jul 2018 02:56:24 +0100
Subject: [PATCH 814/949] [TVM] Fixed SPIR-V codegen for OpControlBarrier
 (#1409)

---
 src/codegen/spirv/codegen_spirv.cc | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/src/codegen/spirv/codegen_spirv.cc b/src/codegen/spirv/codegen_spirv.cc
index 2c71d94eaba1..395bdff1477d 100644
--- a/src/codegen/spirv/codegen_spirv.cc
+++ b/src/codegen/spirv/codegen_spirv.cc
@@ -93,12 +93,14 @@ spirv::Value CodeGenSPIRV::CreateStorageSync(const Call* op) {
   if (sync == "warp") {
     return value;
   } else if (sync == "shared") {
+    auto type_int = builder_->GetSType(Int(32));
     builder_->MakeInst(
-        spv::OpControlBarrier,
-        spv::ScopeWorkgroup,
-        spv::ScopeWorkgroup,
+      spv::OpControlBarrier,
+      builder_->IntImm(type_int, static_cast<int64_t>(spv::ScopeWorkgroup)),
+      builder_->IntImm(type_int, static_cast<int64_t>(spv::ScopeWorkgroup)),
+      builder_->IntImm(type_int, static_cast<int64_t>(
         spv::MemorySemanticsSequentiallyConsistentMask |
-        spv::MemorySemanticsWorkgroupMemoryMask);
+        spv::MemorySemanticsWorkgroupMemoryMask)));
   } else {
     LOG(FATAL) << "Do not support sync " << sync;
   }

From f0ae1746254d48f5df8e00c2797bf367b19f2960 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 9 Jul 2018 22:27:59 -0700
Subject: [PATCH 815/949] [DOCS] Improve documents on deployment (#1412)

* [DOCS] Improve documents on deployment

* minor updates
---
 docs/deploy/index.rst                      | 39 +++++++++++++++++++++-
 docs/install/index.rst                     |  3 ++
 tutorials/cross_compilation_and_rpc.py     |  2 ++
 tutorials/nnvm/deploy_model_on_mali_gpu.py |  4 ++-
 tutorials/nnvm/deploy_model_on_rasp.py     |  2 ++
 tutorials/nnvm_quick_start.py              |  2 ++
 6 files changed, 50 insertions(+), 2 deletions(-)

diff --git a/docs/deploy/index.rst b/docs/deploy/index.rst
index bf607a4b8288..0ef5cf5c8246 100644
--- a/docs/deploy/index.rst
+++ b/docs/deploy/index.rst
@@ -1,3 +1,5 @@
+.. _deploy-and-integration:
+
 Deploy and Integration
 ======================
 
@@ -6,7 +8,42 @@ as well as how to integrate it with your project.
 
 .. image::  http://www.tvm.ai/images/release/tvm_flexible.png
 
-In order to integrate the compiled module, we do not have to ship the compiler stack. We only need to use a lightweight runtime API that can be integrated into various platforms.
+Unlike traditional deep learning frameworks. TVM stack is divided into two major components:
+
+- TVM compiler, which does all the compilation and optimizations
+- TVM runtime, which runs on the target devices.
+
+In order to integrate the compiled module, we **do not** need to build entire TVM on the target device. You only need to build the TVM compiler stack on your desktop and use that to cross-compile modules that are deployed on the target device.
+We only need to use a light-weight runtime API that can be integrated into various platforms.
+
+For example, you can run the following commands to build the runtime API
+on a Linux based embedded system such as Raspberry Pi:
+
+.. code:: bash
+
+    git clone --recursive https://github.com/dmlc/tvm
+    cd tvm
+    mkdir build
+    cp cmake/config.cmake build
+    cd build
+    cmake ..
+    make runtime
+
+Note that we type `make runtime` to only build the runtime library.
+If you want to include additional runtime such as OpenCL,
+you can modify `config.cmake` to enable these options.
+After you get the TVM runtime library, you can link the compiled library
+
+The easiest and recommended way to test, tune and benchmark TVM kernels on
+embedded devices is through TVM's RPC API.
+Here are the links to the related tutorials.
+
+- :ref:`tutorial-cross-compilation-and-rpc`
+- :ref:`tutorial-deploy-model-on-mali-gpu`
+- :ref:`tutorial-deploy-model-on-rasp`
+
+After you finished tuning and benchmarking, you might need to deploy the model on the
+target device without relying on RPC. see the following resources on how to do so.
 
 .. toctree::
    :maxdepth: 2
diff --git a/docs/install/index.rst b/docs/install/index.rst
index 0653175f1141..d351c383476b 100644
--- a/docs/install/index.rst
+++ b/docs/install/index.rst
@@ -2,6 +2,9 @@ Installation
 ============
 
 To install TVM, please read :ref:`install-from-source`.
+If you are interested in deploying to mobile/embedded devices,
+you do not need to install the entire tvm stack on your device,
+instead, you only need the runtime, please read :ref:`deploy-and-integration`.
 If you would like to quickly try out TVM or do demo/tutorials, checkout :ref:`docker-images`
 
 .. toctree::
diff --git a/tutorials/cross_compilation_and_rpc.py b/tutorials/cross_compilation_and_rpc.py
index e9f80f13fb0c..7ef0f0fc17af 100644
--- a/tutorials/cross_compilation_and_rpc.py
+++ b/tutorials/cross_compilation_and_rpc.py
@@ -1,4 +1,6 @@
 """
+.. _tutorial-cross-compilation-and-rpc:
+
 Cross Compilation and RPC
 =========================
 **Author**: `Ziheng Jiang <https://github.com/ZihengJiang/>`_
diff --git a/tutorials/nnvm/deploy_model_on_mali_gpu.py b/tutorials/nnvm/deploy_model_on_mali_gpu.py
index 2f3c332dae17..51caf8dcbd26 100644
--- a/tutorials/nnvm/deploy_model_on_mali_gpu.py
+++ b/tutorials/nnvm/deploy_model_on_mali_gpu.py
@@ -1,6 +1,8 @@
 """
+.. _tutorial-deploy-model-on-mali-gpu:
+
 Deploy the Pretrained Model on ARM Mali GPU
-=======================================================
+===========================================
 **Author**: `Lianmin Zheng <https://lmzheng.net/>`_, `Ziheng Jiang <https://ziheng.org/>`_
 
 This is an example of using NNVM to compile a ResNet model and
diff --git a/tutorials/nnvm/deploy_model_on_rasp.py b/tutorials/nnvm/deploy_model_on_rasp.py
index f4527a547244..37354e7a3363 100644
--- a/tutorials/nnvm/deploy_model_on_rasp.py
+++ b/tutorials/nnvm/deploy_model_on_rasp.py
@@ -1,4 +1,6 @@
 """
+.. _tutorial-deploy-model-on-rasp:
+
 Deploy the Pretrained Model on Raspberry Pi
 ===========================================
 **Author**: `Ziheng Jiang <https://ziheng.org/>`_
diff --git a/tutorials/nnvm_quick_start.py b/tutorials/nnvm_quick_start.py
index 350b5b0ef25c..563d71b5e179 100644
--- a/tutorials/nnvm_quick_start.py
+++ b/tutorials/nnvm_quick_start.py
@@ -1,4 +1,6 @@
 """
+.. _tutorial-nnvm-quick-start:
+
 Quick Start Tutorial for Compiling Deep Learning Models
 =======================================================
 **Author**: `Yao Wang <https://github.com/kevinthesun>`_

From fd1a572058aef5a07e1e1032e26e67fe1906f9b2 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 10 Jul 2018 14:13:53 -0700
Subject: [PATCH 816/949] [OP] Improve bitwise op type checks (#1415)

---
 include/tvm/ir_operator.h                     |  9 ++++
 python/tvm/expr.py                            | 10 ++--
 src/api/api_ir.cc                             | 54 ++++++++++++-------
 tests/python/unittest/test_lang_basic.py      |  5 ++
 .../unittest/test_pass_inject_copy_intrin.py  |  2 +
 5 files changed, 57 insertions(+), 23 deletions(-)

diff --git a/include/tvm/ir_operator.h b/include/tvm/ir_operator.h
index 1324255037cb..268205ecb07b 100644
--- a/include/tvm/ir_operator.h
+++ b/include/tvm/ir_operator.h
@@ -42,12 +42,14 @@ TVM_DLL Expr max(Expr source, Array<IterVar> axis);
  */
 TVM_DLL Expr min(Expr source, Array<IterVar> axis);
 
+
 // Unary intrinsic operators
 #define TVM_DECLARE_INTRIN_UNARY(OpName)                                \
   inline Expr OpName(Expr x) {                                          \
     return ir::Call::make(x.type(), #OpName, {x}, ir::Call::PureIntrinsic); \
   }                                                                     \
 
+
 TVM_DECLARE_INTRIN_UNARY(exp);
 TVM_DECLARE_INTRIN_UNARY(tanh);
 TVM_DECLARE_INTRIN_UNARY(sigmoid);
@@ -58,7 +60,14 @@ TVM_DECLARE_INTRIN_UNARY(ceil);
 TVM_DECLARE_INTRIN_UNARY(round);
 TVM_DECLARE_INTRIN_UNARY(trunc);
 
+/*!
+ * \brief Calculate power(x, y)
+ * \param x The left operand.
+ * \param y The right operand.
+ */
 inline Expr pow(Expr x, Expr y) {
+  match_types(x, y);
+  CHECK(x.type().is_float()) << "power only applies to float";
   return ir::Call::make(x.type(), "pow", { x, y }, ir::Call::PureIntrinsic);
 }
 
diff --git a/python/tvm/expr.py b/python/tvm/expr.py
index df5e51a53be9..8bf46b7eee62 100644
--- a/python/tvm/expr.py
+++ b/python/tvm/expr.py
@@ -67,19 +67,19 @@ def __neg__(self):
         return self.__mul__(neg_one)
 
     def __lshift__(self, other):
-        return _make.Call(self.dtype, "shift_left", [self, other], Call.PureIntrinsic, None, 0)
+        return _make.left_shift(self, other)
 
     def __rshift__(self, other):
-        return _make.Call(self.dtype, "shift_right", [self, other], Call.PureIntrinsic, None, 0)
+        return _make.right_shift(self, other)
 
     def __and__(self, other):
-        return _make.Call(self.dtype, "bitwise_and", [self, other], Call.PureIntrinsic, None, 0)
+        return _make.bitwise_and(self, other)
 
     def __or__(self, other):
-        return _make.Call(self.dtype, "bitwise_or", [self, other], Call.PureIntrinsic, None, 0)
+        return _make.bitwise_or(self, other)
 
     def __xor__(self, other):
-        return _make.Call(self.dtype, "bitwise_xor", [self, other], Call.PureIntrinsic, None, 0)
+        return _make.bitwise_xor(self, other)
 
     def __invert__(self):
         return _make.Call(self.dtype, "bitwise_not", [self], Call.PureIntrinsic, None, 0)
diff --git a/src/api/api_ir.cc b/src/api/api_ir.cc
index d0847aceceb3..f8fbe902ca0b 100644
--- a/src/api/api_ir.cc
+++ b/src/api/api_ir.cc
@@ -111,12 +111,25 @@ TVM_REGISTER_API("make.CommReducer")
       *ret = Node::make(args[0], args[1], args[2], args[3], args[4]);   \
     })                                                                  \
 
-#define REGISTER_MAKE_BINARY_OP(Node)                        \
+#define REGISTER_MAKE_BINARY_OP(Node, Func)                  \
   TVM_REGISTER_API("make."#Node)                             \
   .set_body([](TVMArgs args,  TVMRetValue *ret) {            \
       Expr a = args[0], b = args[1];                         \
-      match_types(a, b);                                     \
-      *ret = Node::make(a, b);                               \
+      *ret = (Func(a, b));                                   \
+    })
+
+#define REGISTER_MAKE_BIT_OP(Node, Func)                                \
+  TVM_REGISTER_API("make."#Node)                                        \
+  .set_body([](TVMArgs args,  TVMRetValue *ret) {                       \
+      bool lhs_is_int = args[0].type_code() == kDLInt;                  \
+      bool rhs_is_int = args[1].type_code() == kDLInt;                  \
+      if (lhs_is_int) {                                                 \
+        *ret = (Func(args[0].operator int(), args[1].operator Expr())); \
+      } else if (rhs_is_int) {                                          \
+        *ret = (Func(args[0].operator Expr(), args[1].operator int())); \
+      } else {                                                          \
+        *ret = (Func(args[0].operator Expr(), args[1].operator Expr())); \
+      }                                                                 \
     })
 
 REGISTER_MAKE5(Reduce);
@@ -126,21 +139,26 @@ REGISTER_MAKE2(IntImm);
 REGISTER_MAKE2(UIntImm);
 REGISTER_MAKE2(FloatImm);
 REGISTER_MAKE1(StringImm);
-REGISTER_MAKE_BINARY_OP(Add);
-REGISTER_MAKE_BINARY_OP(Sub);
-REGISTER_MAKE_BINARY_OP(Mul);
-REGISTER_MAKE_BINARY_OP(Div);
-REGISTER_MAKE_BINARY_OP(Mod);
-REGISTER_MAKE_BINARY_OP(Min);
-REGISTER_MAKE_BINARY_OP(Max);
-REGISTER_MAKE_BINARY_OP(EQ);
-REGISTER_MAKE_BINARY_OP(NE);
-REGISTER_MAKE_BINARY_OP(LT);
-REGISTER_MAKE_BINARY_OP(LE);
-REGISTER_MAKE_BINARY_OP(GT);
-REGISTER_MAKE_BINARY_OP(GE);
-REGISTER_MAKE_BINARY_OP(And);
-REGISTER_MAKE_BINARY_OP(Or);
+REGISTER_MAKE_BINARY_OP(Add, operator+);
+REGISTER_MAKE_BINARY_OP(Sub, operator-);
+REGISTER_MAKE_BINARY_OP(Mul, operator*);
+REGISTER_MAKE_BINARY_OP(Div, operator/);
+REGISTER_MAKE_BINARY_OP(Mod, operator%);
+REGISTER_MAKE_BINARY_OP(Min, min);
+REGISTER_MAKE_BINARY_OP(Max, max);
+REGISTER_MAKE_BINARY_OP(EQ, operator==);
+REGISTER_MAKE_BINARY_OP(NE, operator!=);
+REGISTER_MAKE_BINARY_OP(LT, operator<); // NOLINT(*)
+REGISTER_MAKE_BINARY_OP(LE, operator<=); // NOLINT(*)
+REGISTER_MAKE_BINARY_OP(GT, operator>);  // NOLINT(*)
+REGISTER_MAKE_BINARY_OP(GE, operator>=);
+REGISTER_MAKE_BINARY_OP(And, operator&&);
+REGISTER_MAKE_BINARY_OP(Or, operator||);
+REGISTER_MAKE_BIT_OP(bitwise_and, operator&);
+REGISTER_MAKE_BIT_OP(bitwise_or, operator|);
+REGISTER_MAKE_BIT_OP(bitwise_xor, operator^);
+REGISTER_MAKE_BIT_OP(left_shift, operator<<); // NOLINT(*)
+REGISTER_MAKE_BIT_OP(right_shift, operator>>);
 REGISTER_MAKE1(Not);
 REGISTER_MAKE3(Select);
 REGISTER_MAKE3(Ramp);
diff --git a/tests/python/unittest/test_lang_basic.py b/tests/python/unittest/test_lang_basic.py
index 1461ecec100f..c9a04747b56d 100644
--- a/tests/python/unittest/test_lang_basic.py
+++ b/tests/python/unittest/test_lang_basic.py
@@ -10,6 +10,8 @@ def test_make():
     x = tvm.const(1)
     y = tvm.make.IntImm('int32', 1)
     z = x + y
+    assert isinstance(tvm.max(x, y), tvm.expr.Max)
+    assert isinstance(tvm.min(x, y), tvm.expr.Min)
 
 def test_ir():
     x = tvm.const(1)
@@ -132,6 +134,9 @@ def test_bitwise():
     assert str(x | y) == 'bitwise_or(x, y)'
     assert str(x ^ y) == 'bitwise_xor(x, y)'
     assert str(~x) == 'bitwise_not(x)'
+    assert(tvm.const(1, "int8x2") >> 1).dtype == "int8x2"
+    assert(x >> tvm.const(1, "int32x2")).dtype == "int32x2"
+    assert(tvm.var("z", "int8x2") << tvm.const(1, "int8x2")).dtype == "int8x2"
 
 
 def test_equality():
diff --git a/tests/python/unittest/test_pass_inject_copy_intrin.py b/tests/python/unittest/test_pass_inject_copy_intrin.py
index a44f3899c282..370044d85c03 100644
--- a/tests/python/unittest/test_pass_inject_copy_intrin.py
+++ b/tests/python/unittest/test_pass_inject_copy_intrin.py
@@ -64,6 +64,7 @@ def cb(src, dst, pad_before, pad_after, pad_value):
     stmt = tvm.ir_pass.InjectCopyIntrin(stmt, "memcpy", cb)
 
 def assert_expr_equal(a, b):
+    print(a, b)
     assert tvm.ir_pass.Simplify(a - b).value == 0
 
 def test_copy_pad_split():
@@ -87,6 +88,7 @@ def test_copy_pad_split():
     def cb(src, dst, pad_before, pad_after, pad_value):
         assert(dst.elem_offset.value == 0)
         assert_expr_equal(src.elem_offset, tvm.max(xo * 4, 1) - 1)
+
         rpad_before = tvm.max(1 - xo * 4, 0)
         rpad_after = tvm.max(xo * 4 - 7, 0)
         assert_expr_equal(pad_before[0], rpad_before)

From 4ae5805116c3536a1000f30eb8c5fd6faacd32c0 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 11 Jul 2018 14:30:03 -0700
Subject: [PATCH 817/949] [TEAM] New reviewer: kazum (#1417)

---
 CONTRIBUTORS.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 58868aa93155..190e206447c8 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -21,6 +21,7 @@ See the [community structure document](http://docs.tvm.ai/contribute/community.h
 
 ## Reviewers
 - [Masahiro Masuda](https://github.com/masahi)
+- [Kazutaka Morita](https://github.com/kazum)
 - [Pariksheet Pinjari](https://github.com/PariksheetPinjari909)
 - [Siva](https://github.com/srkreddy1238)
 - [Alex Weaver](https://github.com/alex-weaver)

From eda20382479e884573f3bb1c66273e4fc52d0edf Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 11 Jul 2018 18:31:29 -0700
Subject: [PATCH 818/949] [RUNTIME] Simple NDArray container API in c++ (#1418)

---
 include/tvm/runtime/c_runtime_api.h  |   6 +-
 include/tvm/runtime/ndarray.h        | 286 +++++++++++++++++++++++++++
 include/tvm/runtime/packed_func.h    |  50 ++++-
 python/tvm/_ffi/_ctypes/function.py  |   4 +-
 python/tvm/_ffi/_cython/base.pxi     |   1 +
 python/tvm/_ffi/_cython/function.pxi |   5 +-
 python/tvm/_ffi/runtime_ctypes.py    |   1 +
 src/pass/make_api.cc                 |   1 +
 src/runtime/c_runtime_api.cc         | 152 --------------
 src/runtime/ndarray.cc               | 248 +++++++++++++++++++++++
 src/runtime/rpc/rpc_session.cc       |   8 +-
 tests/cpp/packed_func_test.cc        |  25 +++
 12 files changed, 623 insertions(+), 164 deletions(-)
 create mode 100644 include/tvm/runtime/ndarray.h
 create mode 100644 src/runtime/ndarray.cc

diff --git a/include/tvm/runtime/c_runtime_api.h b/include/tvm/runtime/c_runtime_api.h
index 7f3a11d9ddab..6fb7a0f3f8b3 100644
--- a/include/tvm/runtime/c_runtime_api.h
+++ b/include/tvm/runtime/c_runtime_api.h
@@ -87,6 +87,7 @@ typedef enum {
   kFuncHandle = 10U,
   kStr = 11U,
   kBytes = 12U,
+  kNDArrayContainer = 13U,
   // Extension codes for other frameworks to integrate TVM PackedFunc.
   // To make sure each framework's id do not conflict, use first and
   // last sections to mark ranges.
@@ -121,6 +122,9 @@ typedef DLContext TVMContext;
  */
 typedef DLTensor TVMArray;
 
+/*! \brief the array handle */
+typedef TVMArray* TVMArrayHandle;
+
 /*!
  * \brief Union type of values
  *  being passed through API and function calls.
@@ -149,8 +153,6 @@ typedef void* TVMModuleHandle;
 typedef void* TVMFunctionHandle;
 /*! \brief Handle to hold return value. */
 typedef void* TVMRetValueHandle;
-/*! \brief the array handle */
-typedef TVMArray* TVMArrayHandle;
 /*!
  * \brief The stream that is specific to device
  * can be NULL, which indicates the default one.
diff --git a/include/tvm/runtime/ndarray.h b/include/tvm/runtime/ndarray.h
new file mode 100644
index 000000000000..dfb06255381a
--- /dev/null
+++ b/include/tvm/runtime/ndarray.h
@@ -0,0 +1,286 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file tvm/runtime/ndarray.h
+ * \brief Abstract device memory management API
+ */
+#ifndef TVM_RUNTIME_NDARRAY_H_
+#define TVM_RUNTIME_NDARRAY_H_
+
+#include <atomic>
+#include <vector>
+#include <utility>
+#include "./c_runtime_api.h"
+
+namespace tvm {
+namespace runtime {
+/*!
+ * \brief Managed NDArray.
+ *  The array is backed by reference counted blocks.
+ */
+class NDArray {
+ public:
+  // internal container type
+  struct Container;
+  /*! \brief default constructor */
+  NDArray() {}
+  /*!
+   * \brief cosntruct a NDArray that refers to data
+   * \param data The data this NDArray refers to
+   */
+  explicit inline NDArray(Container* data);
+  /*!
+   * \brief copy constructor
+   * \param other The value to be copied
+   */
+  inline NDArray(const NDArray& other);  // NOLINT(*)
+  /*!
+   * \brief move constructor
+   * \param other The value to be moved
+   */
+  NDArray(NDArray&& other) // NOLINT(*)
+      : data_(other.data_) {
+    other.data_ = nullptr;
+  }
+  /*! \brief destructor */
+  ~NDArray() {
+    this->reset();
+  }
+  /*!
+   * \brief Swap this array with another NDArray
+   * \param other The other NDArray
+   */
+  void swap(NDArray& other) {  // NOLINT(*)
+    std::swap(data_, other.data_);
+  }
+  /*!
+   * \brief copy assignmemt
+   * \param other The value to be assigned.
+   * \return reference to self.
+   */
+  NDArray& operator=(const NDArray& other) {  // NOLINT(*)
+    // copy-and-swap idiom
+    NDArray(other).swap(*this);  // NOLINT(*)
+    return *this;
+  }
+  /*!
+   * \brief move assignmemt
+   * \param other The value to be assigned.
+   * \return reference to self.
+   */
+  NDArray& operator=(NDArray&& other) {  // NOLINT(*)
+    // copy-and-swap idiom
+    NDArray(std::move(other)).swap(*this); // NOLINT(*)
+    return *this;
+  }
+  /*! \return If NDArray is defined */
+  bool defined() const {
+    return data_ != nullptr;
+  }
+  /*! \return If both NDArray reference the same container */
+  bool same_as(const NDArray& other) const {
+    return data_ == other.data_;
+  }
+  /*! \brief reset the content of NDArray to be nullptr */
+  inline void reset();
+  /*!
+   * \return the reference counter
+   * \note this number is approximate in multi-threaded setting.
+   */
+  inline int use_count() const;
+  /*! \return Pointer to content of DLTensor */
+  inline const DLTensor* operator->() const;
+  /*!
+   * \brief Copy data content from another array.
+   * \param other The source array to be copied from.
+   * \note The copy may happen asynchrously if it involves a GPU context.
+   *       TVMSynchronize is necessary.
+   */
+  inline void CopyFrom(DLTensor* other);
+  inline void CopyFrom(const NDArray& other);
+  /*!
+   * \brief Copy data content into another array.
+   * \param other The source array to be copied from.
+   * \note The copy may happen asynchrously if it involves a GPU context.
+   *       TVMSynchronize is necessary.
+   */
+  inline void CopyTo(DLTensor* other);
+  inline void CopyTo(const NDArray& other);
+  /*!
+   * \brief Create a NDArray that shares the data memory with the current one.
+   * \param shape The shape of the new array.
+   * \param dtype The data type of the new array.
+   * \note The memory size of new array must be smaller than the current one.
+   */
+  TVM_DLL NDArray CreateView(
+      std::vector<int64_t> shape, DLDataType dtype);
+  /*!
+   * \brief Create a reference view of NDArray that
+   *  represents as DLManagedTensor.
+   * \return A DLManagedTensor
+   */
+  TVM_DLL DLManagedTensor* ToDLPack() const;
+  /*!
+   * \brief Create an empty NDArray.
+   * \param shape The shape of the new array.
+   * \param dtype The data type of the new array.
+   * \param ctx The context of the Array.
+   * \return The created Array
+   */
+  TVM_DLL static NDArray Empty(std::vector<int64_t> shape,
+                               DLDataType dtype,
+                               DLContext ctx);
+  /*!
+   * \brief Create a NDArray backed by a dlpack tensor.
+   *
+   * This allows us to create a NDArray using the memory
+   * allocated by an external deep learning framework
+   * that is DLPack compatible.
+   *
+   * The memory is retained until the NDArray went out of scope.
+   *
+   * \return The created NDArray view.
+   */
+  TVM_DLL static NDArray FromDLPack(DLManagedTensor* tensor);
+  /*!
+   * \brief Function to copy data from one array to another.
+   * \param from The source array.
+   * \param to The target array.
+   * \param stream The stream used in copy.
+   */
+  TVM_DLL static void CopyFromTo(
+      DLTensor* from, DLTensor* to, TVMStreamHandle stream = nullptr);
+
+  // internal namespace
+  struct Internal;
+ private:
+  /*! \brief Internal Data content */
+  Container* data_{nullptr};
+  // enable internal functions
+  friend struct Internal;
+  friend class TVMRetValue;
+  friend class TVMArgsSetter;
+};
+
+/*!
+ * \brief Reference counted Container object used to back NDArray.
+ *
+ *  This object is DLTensor compatible:
+ *    the pointer to the NDArrayContainer can be directly
+ *    interpreted as a DLTensor*
+ *
+ * \note: do not use this function directly, use NDArray.
+ */
+struct NDArray::Container {
+ public:
+  // NOTE: the first part of this structure is the same as
+  // DLManagedTensor, note that, however, the deleter
+  // is only called when the reference counter goes to 0
+  /*!
+   * \brief The corresponding dl_tensor field.
+   * \note it is important that the first field is DLTensor
+   *  So that this data structure is DLTensor compatible.
+   *  The head ptr of this struct can be viewed as DLTensor*.
+   */
+  DLTensor dl_tensor;
+  /*!
+   * \brief addtional context, reserved for recycling
+   * \note We can attach additional content here
+   *  which the current container depend on
+   *  (e.g. reference to original memory when creating views).
+   */
+  void* manager_ctx{nullptr};
+  /*!
+   * \brief Customized deleter
+   *
+   * \note The customized deleter is helpful to enable
+   *  different ways of memory allocator that are not
+   *  currently defined by the system.
+   */
+  void (*deleter)(Container* self) = nullptr;
+  /*! \brief default constructor */
+  Container() {
+    dl_tensor.data = nullptr;
+    dl_tensor.ndim = 0;
+    dl_tensor.shape = nullptr;
+    dl_tensor.strides = nullptr;
+    dl_tensor.byte_offset = 0;
+  }
+  /*! \brief developer function, increases reference counter */
+  void IncRef() {
+    ref_counter_.fetch_add(1, std::memory_order_relaxed);
+  }
+  /*! \brief developer function, decrease reference counter */
+  void DecRef() {
+    if (ref_counter_.fetch_sub(1, std::memory_order_release) == 1) {
+      std::atomic_thread_fence(std::memory_order_acquire);
+      if (this->deleter != nullptr) {
+        (*this->deleter)(this);
+      }
+    }
+  }
+
+ private:
+  friend class NDArray;
+  /*!
+   * \brief The shape container,
+   *  can be used used for shape data.
+   */
+  std::vector<int64_t> shape_;
+  /*! \brief The internal array object */
+  std::atomic<int> ref_counter_{0};
+};
+
+// implementations of inline functions
+// the usages of functions are documented in place.
+inline NDArray::NDArray(Container* data)
+  : data_(data) {
+  data_->IncRef();
+}
+
+inline NDArray::NDArray(const NDArray& other)
+  : data_(other.data_) {
+  data_->IncRef();
+}
+
+inline void NDArray::reset() {
+  if (data_ != nullptr) {
+    data_->DecRef();
+    data_ = nullptr;
+  }
+}
+
+inline void NDArray::CopyFrom(DLTensor* other) {
+  CHECK(data_ != nullptr);
+  CopyFromTo(other, &(data_->dl_tensor));
+}
+
+inline void NDArray::CopyFrom(const NDArray& other) {
+  CHECK(data_ != nullptr);
+  CHECK(other.data_ != nullptr);
+  CopyFromTo(&(other.data_->dl_tensor), &(data_->dl_tensor));
+}
+
+inline void NDArray::CopyTo(DLTensor* other) {
+  CHECK(data_ != nullptr);
+  CopyFromTo(&(data_->dl_tensor), other);
+}
+
+inline void NDArray::CopyTo(const NDArray& other) {
+  CHECK(data_ != nullptr);
+  CHECK(other.data_ != nullptr);
+  CopyFromTo(&(data_->dl_tensor), &(other.data_->dl_tensor));
+}
+
+inline int NDArray::use_count() const {
+  if (data_ == nullptr) return 0;
+  return data_->ref_counter_.load(std::memory_order_relaxed);
+}
+
+inline const DLTensor* NDArray::operator->() const {
+  return &(data_->dl_tensor);
+}
+
+}  // namespace runtime
+}  // namespace tvm
+
+#endif  // TVM_RUNTIME_NDARRAY_H_
diff --git a/include/tvm/runtime/packed_func.h b/include/tvm/runtime/packed_func.h
index 16266b4f99c9..6d8df4a5e3d6 100644
--- a/include/tvm/runtime/packed_func.h
+++ b/include/tvm/runtime/packed_func.h
@@ -16,6 +16,7 @@
 #include <type_traits>
 #include "./c_runtime_api.h"
 #include "./module.h"
+#include "./ndarray.h"
 
 namespace HalideIR {
 // Forward declare type for extensions
@@ -249,10 +250,22 @@ class TVMPODValue_ {
     TVM_CHECK_TYPE_CODE(type_code_, kHandle);
     return value_.v_handle;
   }
-  operator TVMArray*() const {
-    if (type_code_ == kNull) return nullptr;
-    TVM_CHECK_TYPE_CODE(type_code_, kArrayHandle);
-    return static_cast<TVMArray*>(value_.v_handle);
+  operator DLTensor*() const {
+    if (type_code_ == kArrayHandle ||
+        type_code_ == kNDArrayContainer) {
+      return static_cast<DLTensor*>(value_.v_handle);
+    } else {
+      if (type_code_ == kNull) return nullptr;
+      LOG(FATAL) << "Expected "
+                 << "DLTensor* or NDArray but get "
+                 << TypeCode2Str(type_code_);
+      return nullptr;
+    }
+  }
+  operator NDArray() const {
+    if (type_code_ == kNull) return NDArray();
+    TVM_CHECK_TYPE_CODE(type_code_, kNDArrayContainer);
+    return NDArray(static_cast<NDArray::Container*>(value_.v_handle));
   }
   operator TVMContext() const {
     TVM_CHECK_TYPE_CODE(type_code_, kTVMContext);
@@ -312,8 +325,10 @@ class TVMArgValue : public TVMPODValue_ {
   using TVMPODValue_::operator int;
   using TVMPODValue_::operator bool;
   using TVMPODValue_::operator void*;
-  using TVMPODValue_::operator TVMArray*;
+  using TVMPODValue_::operator DLTensor*;
+  using TVMPODValue_::operator NDArray;
   using TVMPODValue_::operator TVMContext;
+
   // conversion operator.
   operator std::string() const {
     if (type_code_ == kTVMType) {
@@ -394,8 +409,9 @@ class TVMRetValue : public TVMPODValue_ {
   using TVMPODValue_::operator int;
   using TVMPODValue_::operator bool;
   using TVMPODValue_::operator void*;
-  using TVMPODValue_::operator TVMArray*;
+  using TVMPODValue_::operator DLTensor*;
   using TVMPODValue_::operator TVMContext;
+  using TVMPODValue_::operator NDArray;
   // Disable copy and assign from another value, but allow move.
   TVMRetValue(const TVMRetValue& other) {
     this->Assign(other);
@@ -477,6 +493,13 @@ class TVMRetValue : public TVMPODValue_ {
     this->SwitchToClass(kBytes, std::string(value.data, value.size));
     return *this;
   }
+  TVMRetValue& operator=(NDArray other) {
+    this->Clear();
+    type_code_ = kNDArrayContainer;
+    value_.v_handle = other.data_;
+    other.data_ = nullptr;
+    return *this;
+  }
   TVMRetValue& operator=(PackedFunc f) {
     this->SwitchToClass(kFuncHandle, f);
     return *this;
@@ -559,6 +582,10 @@ class TVMRetValue : public TVMPODValue_ {
         SwitchToClass<Module>(kModuleHandle, other);
         break;
       }
+      case kNDArrayContainer: {
+        *this = other.operator NDArray();
+        break;
+      }
       case kNodeHandle: {
         SwitchToClass<std::shared_ptr<Node> >(
             kNodeHandle, *other.template ptr<std::shared_ptr<Node> >());
@@ -607,6 +634,10 @@ class TVMRetValue : public TVMPODValue_ {
       case kFuncHandle: delete ptr<PackedFunc>(); break;
       case kModuleHandle: delete ptr<Module>(); break;
       case kNodeHandle: delete ptr<std::shared_ptr<Node> >(); break;
+      case kNDArrayContainer: {
+        static_cast<NDArray::Container*>(value_.v_handle)->DecRef();
+        break;
+      }
     }
     if (type_code_ > kExtBegin) {
 #if TVM_RUNTIME_HEADER_ONLY
@@ -635,6 +666,7 @@ inline const char* TypeCode2Str(int type_code) {
     case kTVMContext: return "TVMContext";
     case kFuncHandle: return "FunctionHandle";
     case kModuleHandle: return "ModuleHandle";
+    case kNDArrayContainer: return "NDArrayContainer";
     default: LOG(FATAL) << "unknown type_code="
                         << static_cast<int>(type_code); return "";
   }
@@ -776,7 +808,7 @@ class TVMArgsSetter {
     values_[i].v_handle = value;
     type_codes_[i] = kHandle;
   }
-  void operator()(size_t i, TVMArray* value) const {
+  void operator()(size_t i, DLTensor* value) const {
     values_[i].v_handle = value;
     type_codes_[i] = kArrayHandle;
   }
@@ -811,6 +843,10 @@ class TVMArgsSetter {
     values_[i].v_handle = const_cast<Module*>(&value);
     type_codes_[i] = kModuleHandle;
   }
+  void operator()(size_t i, const NDArray& value) const {  // NOLINT(*)
+    values_[i].v_handle = value.data_;
+    type_codes_[i] = kNDArrayContainer;
+  }
   void operator()(size_t i, const TVMRetValue& value) const {  // NOLINT(*)
     if (value.type_code() == kStr) {
       values_[i].v_str = value.ptr<std::string>()->c_str();
diff --git a/python/tvm/_ffi/_ctypes/function.py b/python/tvm/_ffi/_ctypes/function.py
index 189d9964baf5..602af3ef858b 100644
--- a/python/tvm/_ffi/_ctypes/function.py
+++ b/python/tvm/_ffi/_ctypes/function.py
@@ -94,7 +94,8 @@ def _make_tvm_args(args, temp_args):
             type_codes[i] = TypeCode.NULL
         elif isinstance(arg, NDArrayBase):
             values[i].v_handle = ctypes.cast(arg.handle, ctypes.c_void_p)
-            type_codes[i] = TypeCode.ARRAY_HANDLE
+            type_codes[i] = (TypeCode.NDARRAY_CONTAINER
+                             if not arg.is_view else TypeCode.ARRAY_HANDLE)
         elif isinstance(arg, _nd._TVM_COMPATS):
             values[i].v_handle = ctypes.c_void_p(arg._tvm_handle)
             type_codes[i] = arg.__class__._tvm_tcode
@@ -208,6 +209,7 @@ def _handle_return_func(x):
 C_TO_PY_ARG_SWITCH[TypeCode.MODULE_HANDLE] = _wrap_arg_func(
     _return_module, TypeCode.MODULE_HANDLE)
 C_TO_PY_ARG_SWITCH[TypeCode.ARRAY_HANDLE] = lambda x: _make_array(x.v_handle, True)
+C_TO_PY_ARG_SWITCH[TypeCode.NDARRAY_CONTAINER] = lambda x: _make_array(x.v_handle, False)
 
 _CLASS_MODULE = None
 _CLASS_FUNCTION = None
diff --git a/python/tvm/_ffi/_cython/base.pxi b/python/tvm/_ffi/_cython/base.pxi
index c027c723de08..50a99245f793 100644
--- a/python/tvm/_ffi/_cython/base.pxi
+++ b/python/tvm/_ffi/_cython/base.pxi
@@ -18,6 +18,7 @@ cdef enum TVMTypeCode:
     kFuncHandle = 10
     kStr = 11
     kBytes = 12
+    kNDArrayContainer = 13
     kExtBegin = 15
 
 cdef extern from "tvm/runtime/c_runtime_api.h":
diff --git a/python/tvm/_ffi/_cython/function.pxi b/python/tvm/_ffi/_cython/function.pxi
index 06cda82624b9..989f5b8e7b47 100644
--- a/python/tvm/_ffi/_cython/function.pxi
+++ b/python/tvm/_ffi/_cython/function.pxi
@@ -84,7 +84,8 @@ cdef inline int make_arg(object arg,
         tcode[0] = kNodeHandle
     elif isinstance(arg, NDArrayBase):
         value[0].v_handle = (<NDArrayBase>arg).chandle
-        tcode[0] = kArrayHandle
+        tcode[0] = (kNDArrayContainer if
+                    not (<NDArrayBase>arg).c_is_view else kArrayHandle)
     elif isinstance(arg, _TVM_COMPATS):
         ptr = arg._tvm_handle
         value[0].v_handle = (<void*>ptr)
@@ -173,6 +174,8 @@ cdef inline object make_ret(TVMValue value, int tcode):
         return value.v_int64
     elif tcode == kFloat:
         return value.v_float64
+    elif tcode == kNDArrayContainer:
+        return c_make_array(value.v_handle, False)
     elif tcode == kStr:
         return py_str(value.v_str)
     elif tcode == kBytes:
diff --git a/python/tvm/_ffi/runtime_ctypes.py b/python/tvm/_ffi/runtime_ctypes.py
index 9609f867576b..612b54649d74 100644
--- a/python/tvm/_ffi/runtime_ctypes.py
+++ b/python/tvm/_ffi/runtime_ctypes.py
@@ -25,6 +25,7 @@ class TypeCode(object):
     FUNC_HANDLE = 10
     STR = 11
     BYTES = 12
+    NDARRAY_CONTAINER = 13
     EXT_BEGIN = 15
 
 class TVMByteArray(ctypes.Structure):
diff --git a/src/pass/make_api.cc b/src/pass/make_api.cc
index 6290f63e611d..206bd95010ce 100644
--- a/src/pass/make_api.cc
+++ b/src/pass/make_api.cc
@@ -102,6 +102,7 @@ LoweredFunc MakeAPI(Stmt body,
         msg << name << ": Expect arg[" << i << "] to be pointer";
         seq_check.emplace_back(
             AssertStmt::make(tcode == kHandle ||
+                             tcode == kNDArrayContainer ||
                              tcode == kArrayHandle ||
                              tcode == kNull, msg.str(), nop));
       } else if (t.is_int() || t.is_uint()) {
diff --git a/src/runtime/c_runtime_api.cc b/src/runtime/c_runtime_api.cc
index 5eb39abcc71a..7a7d7ab9f4db 100644
--- a/src/runtime/c_runtime_api.cc
+++ b/src/runtime/c_runtime_api.cc
@@ -124,54 +124,6 @@ void DeviceAPI::SyncStreamFromTo(TVMContext ctx,
                                  TVMStreamHandle event_dst) {
   LOG(FATAL) << "Device does not support stream api.";
 }
-
-inline TVMArray* TVMArrayCreate_() {
-  TVMArray* arr = new TVMArray();
-  arr->shape = nullptr;
-  arr->strides = nullptr;
-  arr->ndim = 0;
-  arr->data = nullptr;
-  return arr;
-}
-
-inline void TVMArrayFree_(TVMArray* arr) {
-  if (arr != nullptr) {
-    // ok to delete nullptr
-    delete[] arr->shape;
-    delete[] arr->strides;
-    if (arr->data != nullptr) {
-      DeviceAPIManager::Get(arr->ctx)->FreeDataSpace(
-          arr->ctx, arr->data);
-    }
-  }
-  delete arr;
-}
-
-inline void VerifyType(int dtype_code, int dtype_bits, int dtype_lanes) {
-  CHECK_GE(dtype_lanes, 1);
-  if (dtype_code == kDLFloat) {
-    CHECK_EQ(dtype_bits % 8, 0);
-  } else {
-    CHECK_EQ(dtype_bits % 8, 0);
-  }
-  CHECK_EQ(dtype_bits & (dtype_bits - 1), 0);
-}
-
-inline size_t GetDataSize(TVMArray* arr) {
-  size_t size = 1;
-  for (tvm_index_t i = 0; i < arr->ndim; ++i) {
-    size *= arr->shape[i];
-  }
-  size *= (arr->dtype.bits * arr->dtype.lanes + 7) / 8;
-  return size;
-}
-
-inline size_t GetDataAlignment(TVMArray* arr) {
-  size_t align = (arr->dtype.bits / 8) * arr->dtype.lanes;
-  if (align < kAllocAlignment) return kAllocAlignment;
-  return align;
-}
-
 }  // namespace runtime
 }  // namespace tvm
 
@@ -370,110 +322,6 @@ int TVMFuncCreateFromCFunc(TVMPackedCFunc func,
   API_END();
 }
 
-int TVMArrayAlloc(const tvm_index_t* shape,
-                  int ndim,
-                  int dtype_code,
-                  int dtype_bits,
-                  int dtype_lanes,
-                  int device_type,
-                  int device_id,
-                  TVMArrayHandle* out) {
-  TVMArray* arr = nullptr;
-  API_BEGIN();
-  // shape
-  arr = TVMArrayCreate_();
-  // ndim
-  arr->ndim = ndim;
-  // dtype
-  VerifyType(dtype_code, dtype_bits, dtype_lanes);
-  arr->dtype.code = static_cast<uint8_t>(dtype_code);
-  arr->dtype.bits = static_cast<uint8_t>(dtype_bits);
-  arr->dtype.lanes = static_cast<uint16_t>(dtype_lanes);
-  if (ndim != 0) {
-    tvm_index_t* shape_copy = new tvm_index_t[ndim];
-    std::copy(shape, shape + ndim, shape_copy);
-    arr->shape = shape_copy;
-  } else {
-    arr->shape = nullptr;
-  }
-  // ctx
-  arr->ctx.device_type = static_cast<DLDeviceType>(device_type);
-  arr->ctx.device_id = device_id;
-  size_t size = GetDataSize(arr);
-  size_t alignment = GetDataAlignment(arr);
-  arr->data = DeviceAPIManager::Get(arr->ctx)->AllocDataSpace(
-      arr->ctx, size, alignment, arr->dtype);
-  *out = arr;
-  API_END_HANDLE_ERROR(TVMArrayFree_(arr));
-}
-
-int TVMArrayFree(TVMArrayHandle handle) {
-  API_BEGIN();
-  TVMArray* arr = handle;
-  TVMArrayFree_(arr);
-  API_END();
-}
-
-int TVMArrayCopyFromTo(TVMArrayHandle from,
-                       TVMArrayHandle to,
-                       TVMStreamHandle stream) {
-  API_BEGIN();
-  size_t from_size = GetDataSize(from);
-  size_t to_size = GetDataSize(to);
-  CHECK_EQ(from_size, to_size)
-    << "TVMArrayCopyFromTo: The size must exactly match";
-
-  CHECK(from->ctx.device_type == to->ctx.device_type
-        || from->ctx.device_type == kDLCPU
-        || to->ctx.device_type == kDLCPU)
-    << "Can not copy across different ctx types directly";
-
-  // Use the context that is *not* a cpu context to get the correct device
-  // api manager.
-  TVMContext ctx = from->ctx.device_type != kDLCPU ? from->ctx : to->ctx;
-
-  DeviceAPIManager::Get(ctx)->CopyDataFromTo(
-    from->data, static_cast<size_t>(from->byte_offset),
-    to->data, static_cast<size_t>(to->byte_offset),
-    from_size, from->ctx, to->ctx, from->dtype, stream);
-
-  API_END();
-}
-
-int TVMArrayCopyFromBytes(TVMArrayHandle handle,
-                          void* data,
-                          size_t nbytes) {
-  API_BEGIN();
-  TVMContext cpu_ctx;
-  cpu_ctx.device_type = kDLCPU;
-  cpu_ctx.device_id = 0;
-  size_t arr_size = GetDataSize(handle);
-  CHECK_EQ(arr_size, nbytes)
-      << "TVMArrayCopyFromBytes: size mismatch";
-  DeviceAPIManager::Get(handle->ctx)->CopyDataFromTo(
-      data, 0,
-      handle->data, static_cast<size_t>(handle->byte_offset),
-      nbytes, cpu_ctx, handle->ctx, handle->dtype, nullptr);
-  API_END();
-}
-
-int TVMArrayCopyToBytes(TVMArrayHandle handle,
-                        void* data,
-                        size_t nbytes) {
-  API_BEGIN();
-  TVMContext cpu_ctx;
-  cpu_ctx.device_type = kDLCPU;
-  cpu_ctx.device_id = 0;
-  size_t arr_size = GetDataSize(handle);
-  CHECK_EQ(arr_size, nbytes)
-      << "TVMArrayCopyToBytes: size mismatch";
-  DeviceAPIManager::Get(handle->ctx)->CopyDataFromTo(
-      handle->data, static_cast<size_t>(handle->byte_offset),
-      data, 0,
-      nbytes, handle->ctx, cpu_ctx, handle->dtype, nullptr);
-  API_END();
-}
-
 int TVMStreamCreate(int device_type, int device_id, TVMStreamHandle* out) {
   API_BEGIN();
   TVMContext ctx;
diff --git a/src/runtime/ndarray.cc b/src/runtime/ndarray.cc
new file mode 100644
index 000000000000..f862f32f6e99
--- /dev/null
+++ b/src/runtime/ndarray.cc
@@ -0,0 +1,248 @@
+/*!
+ *  Copyright (c) 2017 by Contributors
+ * \file ndarray.cc
+ * \brief NDArray container infratructure.
+ */
+#include <dmlc/logging.h>
+#include <tvm/runtime/ndarray.h>
+#include <tvm/runtime/c_runtime_api.h>
+#include <tvm/runtime/device_api.h>
+#include "./runtime_base.h"
+
+// deleter for arrays used by DLPack exporter
+extern "C" void NDArrayDLPackDeleter(DLManagedTensor* tensor);
+
+namespace tvm {
+namespace runtime {
+
+inline void VerifyDataType(DLDataType dtype) {
+  CHECK_GE(dtype.lanes, 1);
+  if (dtype.code == kDLFloat) {
+    CHECK_EQ(dtype.bits % 8, 0);
+  } else {
+    CHECK_EQ(dtype.bits % 8, 0);
+  }
+  CHECK_EQ(dtype.bits & (dtype.bits - 1), 0);
+}
+
+inline size_t GetDataSize(const DLTensor& arr) {
+  size_t size = 1;
+  for (tvm_index_t i = 0; i < arr.ndim; ++i) {
+    size *= arr.shape[i];
+  }
+  size *= (arr.dtype.bits * arr.dtype.lanes + 7) / 8;
+  return size;
+}
+
+inline size_t GetDataAlignment(const DLTensor& arr) {
+  size_t align = (arr.dtype.bits / 8) * arr.dtype.lanes;
+  if (align < kAllocAlignment) return kAllocAlignment;
+  return align;
+}
+
+struct NDArray::Internal {
+  // Default deleter for the container
+  static void DefaultDeleter(NDArray::Container* ptr) {
+    using tvm::runtime::NDArray;
+    if (ptr->manager_ctx != nullptr) {
+      static_cast<NDArray::Container*>(ptr->manager_ctx)->DecRef();
+    } else if (ptr->dl_tensor.data != nullptr) {
+      tvm::runtime::DeviceAPI::Get(ptr->dl_tensor.ctx)->FreeDataSpace(
+          ptr->dl_tensor.ctx, ptr->dl_tensor.data);
+    }
+    delete ptr;
+  }
+  // Deleter for NDArray converted from DLPack
+  // This is used from data which is passed from external DLPack(DLManagedTensor)
+  // that are not allocated inside of TVM.
+  // This enables us to create NDArray from memory allocated by other
+  // frameworks that are DLPack compatible
+  static void DLPackDeleter(NDArray::Container* ptr) {
+    DLManagedTensor* tensor = static_cast<DLManagedTensor*>(ptr->manager_ctx);
+    if (tensor->deleter != nullptr) {
+      (*tensor->deleter)(tensor);
+    }
+    delete ptr;
+  }
+  // Local create function which allocates tensor metadata
+  // but does not allocate space for the data.
+  static NDArray Create(std::vector<int64_t> shape,
+                        DLDataType dtype,
+                        DLContext ctx) {
+    VerifyDataType(dtype);
+    // critical zone
+    NDArray::Container* data = new NDArray::Container();
+    data->deleter = DefaultDeleter;
+    NDArray ret(data);
+    ret.data_ = data;
+    // RAII now in effect
+    // setup shape
+    data->shape_ = std::move(shape);
+    data->dl_tensor.shape = dmlc::BeginPtr(data->shape_);
+    data->dl_tensor.ndim = static_cast<int>(data->shape_.size());
+    // setup dtype
+    data->dl_tensor.dtype = dtype;
+    // setup ctx
+    data->dl_tensor.ctx = ctx;
+    return ret;
+  }
+  // Implementation of API function
+  static DLTensor* MoveAsDLTensor(NDArray arr) {
+    DLTensor* tensor = const_cast<DLTensor*>(arr.operator->());
+    CHECK(reinterpret_cast<DLTensor*>(arr.data_) == tensor);
+    arr.data_ = nullptr;
+    return tensor;
+  }
+};
+
+NDArray NDArray::CreateView(std::vector<int64_t> shape,
+                            DLDataType dtype) {
+  CHECK(data_ != nullptr);
+  CHECK(data_->dl_tensor.strides == nullptr)
+      << "Can only create view for compact tensor";
+  NDArray ret = Internal::Create(shape, dtype, data_->dl_tensor.ctx);
+  ret.data_->dl_tensor.byte_offset =
+      this->data_->dl_tensor.byte_offset;
+  size_t curr_size = GetDataSize(this->data_->dl_tensor);
+  size_t view_size = GetDataSize(ret.data_->dl_tensor);
+  CHECK_LE(view_size, curr_size)
+      << "Tries to create a view that has bigger memory than current one";
+  // increase ref count
+  this->data_->IncRef();
+  ret.data_->manager_ctx = this->data_;
+  ret.data_->dl_tensor.data = this->data_->dl_tensor.data;
+  return ret;
+}
+
+DLManagedTensor* NDArray::ToDLPack() const {
+  CHECK(data_ != nullptr);
+  DLManagedTensor* ret = new DLManagedTensor();
+  ret->dl_tensor = data_->dl_tensor;
+  ret->manager_ctx = const_cast<NDArray*>(this);
+  data_->IncRef();
+  ret->deleter = NDArrayDLPackDeleter;
+  return ret;
+}
+
+NDArray NDArray::Empty(std::vector<int64_t> shape,
+                        DLDataType dtype,
+                        DLContext ctx) {
+  NDArray ret = Internal::Create(shape, dtype, ctx);
+  // setup memory content
+  size_t size = GetDataSize(ret.data_->dl_tensor);
+  size_t alignment = GetDataAlignment(ret.data_->dl_tensor);
+  ret.data_->dl_tensor.data =
+      DeviceAPI::Get(ret->ctx)->AllocDataSpace(
+          ret->ctx, size, alignment, ret->dtype);
+  return ret;
+}
+
+NDArray NDArray::FromDLPack(DLManagedTensor* tensor) {
+  NDArray::Container* data = new NDArray::Container();
+  data->deleter = Internal::DLPackDeleter;
+  data->manager_ctx = tensor;
+  data->dl_tensor = tensor->dl_tensor;
+  return NDArray(data);
+}
+
+void NDArray::CopyFromTo(DLTensor* from,
+                         DLTensor* to,
+                         TVMStreamHandle stream) {
+  size_t from_size = GetDataSize(*from);
+  size_t to_size = GetDataSize(*to);
+  CHECK_EQ(from_size, to_size)
+    << "TVMArrayCopyFromTo: The size must exactly match";
+
+  CHECK(from->ctx.device_type == to->ctx.device_type
+        || from->ctx.device_type == kDLCPU
+        || to->ctx.device_type == kDLCPU)
+    << "Can not copy across different ctx types directly";
+
+  // Use the context that is *not* a cpu context to get the correct device
+  // api manager.
+  TVMContext ctx = from->ctx.device_type != kDLCPU ? from->ctx : to->ctx;
+
+  DeviceAPI::Get(ctx)->CopyDataFromTo(
+    from->data, static_cast<size_t>(from->byte_offset),
+    to->data, static_cast<size_t>(to->byte_offset),
+    from_size, from->ctx, to->ctx, from->dtype, stream);
+}
+
+}  // namespace runtime
+}  // namespace tvm
+
+using namespace tvm::runtime;
+
+void NDArrayDLPackDeleter(DLManagedTensor* tensor) {
+  static_cast<NDArray::Container*>(tensor->manager_ctx)->DecRef();
+  delete tensor;
+}
+
+int TVMArrayAlloc(const tvm_index_t* shape,
+                  int ndim,
+                  int dtype_code,
+                  int dtype_bits,
+                  int dtype_lanes,
+                  int device_type,
+                  int device_id,
+                  TVMArrayHandle* out) {
+  API_BEGIN();
+  DLDataType dtype;
+  dtype.code = static_cast<uint8_t>(dtype_code);
+  dtype.bits = static_cast<uint8_t>(dtype_bits);
+  dtype.lanes = static_cast<uint16_t>(dtype_lanes);
+  DLContext ctx;
+  ctx.device_type = static_cast<DLDeviceType>(device_type);
+  ctx.device_id = device_id;
+  *out = NDArray::Internal::MoveAsDLTensor(
+      NDArray::Empty(std::vector<int64_t>(shape, shape + ndim), dtype, ctx));
+  API_END();
+}
+
+int TVMArrayFree(TVMArrayHandle handle) {
+  API_BEGIN();
+  reinterpret_cast<NDArray::Container*>(handle)->DecRef();
+  API_END();
+}
+
+int TVMArrayCopyFromTo(TVMArrayHandle from,
+                       TVMArrayHandle to,
+                       TVMStreamHandle stream) {
+  API_BEGIN();
+  NDArray::CopyFromTo(from, to, stream);
+  API_END();
+}
+
+int TVMArrayCopyFromBytes(TVMArrayHandle handle,
+                          void* data,
+                          size_t nbytes) {
+  API_BEGIN();
+  TVMContext cpu_ctx;
+  cpu_ctx.device_type = kDLCPU;
+  cpu_ctx.device_id = 0;
+  size_t arr_size = GetDataSize(*handle);
+  CHECK_EQ(arr_size, nbytes)
+      << "TVMArrayCopyFromBytes: size mismatch";
+  DeviceAPI::Get(handle->ctx)->CopyDataFromTo(
+      data, 0,
+      handle->data, static_cast<size_t>(handle->byte_offset),
+      nbytes, cpu_ctx, handle->ctx, handle->dtype, nullptr);
+  API_END();
+}
+
+int TVMArrayCopyToBytes(TVMArrayHandle handle,
+                        void* data,
+                        size_t nbytes) {
+  API_BEGIN();
+  TVMContext cpu_ctx;
+  cpu_ctx.device_type = kDLCPU;
+  cpu_ctx.device_id = 0;
+  size_t arr_size = GetDataSize(*handle);
+  CHECK_EQ(arr_size, nbytes)
+      << "TVMArrayCopyToBytes: size mismatch";
+  DeviceAPI::Get(handle->ctx)->CopyDataFromTo(
+      handle->data, static_cast<size_t>(handle->byte_offset),
+      data, 0,
+      nbytes, handle->ctx, cpu_ctx, handle->dtype, nullptr);
+  API_END();
+}
diff --git a/src/runtime/rpc/rpc_session.cc b/src/runtime/rpc/rpc_session.cc
index 2f181e7edf9a..21fff7b29882 100644
--- a/src/runtime/rpc/rpc_session.cc
+++ b/src/runtime/rpc/rpc_session.cc
@@ -175,7 +175,12 @@ class RPCSession::EventHandler : public dmlc::Stream {
   // send Packed sequence to writer.
   void SendPackedSeq(const TVMValue* arg_values, const int* type_codes, int n) {
     this->Write(n);
-    this->WriteArray(type_codes, n);
+    // only handles .
+    for (int i = 0; i < n; ++i) {
+      int tcode = type_codes[i];
+      if (tcode == kNDArrayContainer) tcode = kArrayHandle;
+      this->Write(tcode);
+    }
     // Argument packing.
     for (int i = 0; i < n; ++i) {
       int tcode = type_codes[i];
@@ -207,6 +212,7 @@ class RPCSession::EventHandler : public dmlc::Stream {
           this->Write(handle);
           break;
         }
+        case kNDArrayContainer:
         case kArrayHandle: {
           DLTensor* arr = static_cast<DLTensor*>(value.v_handle);
           TVMContext ctx = StripSessMask(arr->ctx);
diff --git a/tests/cpp/packed_func_test.cc b/tests/cpp/packed_func_test.cc
index 00e428f258a9..9b2f1df73731 100644
--- a/tests/cpp/packed_func_test.cc
+++ b/tests/cpp/packed_func_test.cc
@@ -38,6 +38,31 @@ TEST(PackedFunc, Node) {
   CHECK(t.same_as(x));
 }
 
+TEST(PackedFunc, NDArray) {
+  using namespace tvm;
+  using namespace tvm::runtime;
+  auto x = NDArray::Empty(
+      {}, String2TVMType("float32"),
+      TVMContext{kDLCPU, 0});
+  reinterpret_cast<float*>(x->data)[0] = 10.0f;
+  CHECK(x.use_count() == 1);
+
+  PackedFunc forward([&](TVMArgs args, TVMRetValue* rv) {
+      *rv = args[0];
+    });
+
+  NDArray ret = PackedFunc([&](TVMArgs args, TVMRetValue* rv) {
+      NDArray y = args[0];
+      DLTensor* ptr = args[0];
+      CHECK(ptr == x.operator->());
+      CHECK(x.same_as(y));
+      CHECK(x.use_count() == 2);
+      *rv = forward(y);
+    })(x);
+  CHECK(ret.use_count() == 2);
+  CHECK(ret.same_as(x));
+}
+
 TEST(PackedFunc, str) {
   using namespace tvm;
   using namespace tvm::runtime;

From cc94467622f734d6e6cdd48ffc6736a019d98470 Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@cs.washington.edu>
Date: Thu, 1 Mar 2018 15:55:46 -0800
Subject: [PATCH 819/949] Initial commit

---
 vta/LICENSE   | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++
 vta/README.md |   2 +
 2 files changed, 203 insertions(+)
 create mode 100644 vta/LICENSE
 create mode 100644 vta/README.md

diff --git a/vta/LICENSE b/vta/LICENSE
new file mode 100644
index 000000000000..261eeb9e9f8b
--- /dev/null
+++ b/vta/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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.
diff --git a/vta/README.md b/vta/README.md
new file mode 100644
index 000000000000..1442fd2770fd
--- /dev/null
+++ b/vta/README.md
@@ -0,0 +1,2 @@
+# vta
+Open Hardware/Software Stack for Vertical Deep Learning System Optimization

From e17f9df36cdf1845c9c0540b375619d9d7068a8c Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@uw.edu>
Date: Fri, 16 Mar 2018 11:46:44 -0700
Subject: [PATCH 820/949] hardware compilation flow, and driver tests

---
 vta/README.md                           |   12 +-
 vta/docs/.gitignore                     |    1 +
 vta/docs/Doxyfile                       | 2427 +++++++++++++++++++++++
 vta/hardware/vivado/.gitignore          |    1 +
 vta/hardware/vivado/Makefile            |  106 +
 vta/hardware/vivado/scripts/hls.tcl     |  177 ++
 vta/hardware/vivado/scripts/hsi.tcl     |   11 +
 vta/hardware/vivado/scripts/vivado.tcl  |  946 +++++++++
 vta/hardware/vivado/sim/vta_test.cc     |   62 +
 vta/include/hardware/hls/vta.h          |  137 ++
 vta/include/hardware/hls/vta_typedefs.h |   97 +
 vta/include/vta_params.h                |  559 ++++++
 vta/include/vta_pynq_driver.h           |  152 ++
 vta/include/vta_test_lib.h              |  300 +++
 vta/src/driver/pynq/vta_pynq_driver.c   |   77 +
 vta/src/hardware/hls/vta.cc             |  789 ++++++++
 vta/src/test/vta_test_lib.cc            | 1110 +++++++++++
 vta/tests/driver/Makefile               |   59 +
 vta/tests/driver/driver_test.cc         |  152 ++
 19 files changed, 7174 insertions(+), 1 deletion(-)
 create mode 100644 vta/docs/.gitignore
 create mode 100644 vta/docs/Doxyfile
 create mode 100644 vta/hardware/vivado/.gitignore
 create mode 100644 vta/hardware/vivado/Makefile
 create mode 100644 vta/hardware/vivado/scripts/hls.tcl
 create mode 100644 vta/hardware/vivado/scripts/hsi.tcl
 create mode 100644 vta/hardware/vivado/scripts/vivado.tcl
 create mode 100644 vta/hardware/vivado/sim/vta_test.cc
 create mode 100644 vta/include/hardware/hls/vta.h
 create mode 100644 vta/include/hardware/hls/vta_typedefs.h
 create mode 100644 vta/include/vta_params.h
 create mode 100644 vta/include/vta_pynq_driver.h
 create mode 100644 vta/include/vta_test_lib.h
 create mode 100644 vta/src/driver/pynq/vta_pynq_driver.c
 create mode 100644 vta/src/hardware/hls/vta.cc
 create mode 100644 vta/src/test/vta_test_lib.cc
 create mode 100644 vta/tests/driver/Makefile
 create mode 100644 vta/tests/driver/driver_test.cc

diff --git a/vta/README.md b/vta/README.md
index 1442fd2770fd..39d094cc54e6 100644
--- a/vta/README.md
+++ b/vta/README.md
@@ -1,2 +1,12 @@
-# vta
 Open Hardware/Software Stack for Vertical Deep Learning System Optimization
+==============================================
+
+[![GitHub license](http://dmlc.github.io/img/apache2.svg)](./LICENSE)
+
+VTA is an open hardware/software co-design stack for deep learning systems systems.
+It provides a customizable hardware accelerator template for deep learning inference workloads,
+combined with a fully functional compiler stack built with TVM.
+
+License
+-------
+© Contributors, 2018. Licensed under an [Apache-2.0](https://github.com/tmoreau89/vta/blob/master/LICENSE) license.
diff --git a/vta/docs/.gitignore b/vta/docs/.gitignore
new file mode 100644
index 000000000000..8e786536c590
--- /dev/null
+++ b/vta/docs/.gitignore
@@ -0,0 +1 @@
+doxygen
diff --git a/vta/docs/Doxyfile b/vta/docs/Doxyfile
new file mode 100644
index 000000000000..77b0dc11b9dd
--- /dev/null
+++ b/vta/docs/Doxyfile
@@ -0,0 +1,2427 @@
+# Doxyfile 1.8.11
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all text
+# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
+# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
+# for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME           = "vta"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER         =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          =
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO           =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = doxygen
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS         = NO
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES    = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF       =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES        = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH        =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE               = 4
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines.
+
+ALIASES                =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
+
+TCL_SUBST              =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
+# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
+# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
+# Fortran. In the later case the parser tries to guess whether the code is fixed
+# or free formatted code, this is the default for Fortran type files), VHDL. For
+# instance to make doxygen treat .inc files as Fortran files (default is PHP),
+# and .f files as C (default is Fortran), use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT       = YES
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT       = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS  = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL            = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# (class|struct|union) declarations. If set to NO, these declarations will be
+# included in the documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES, upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC  = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES        = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE            =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES         =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS               = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong or incomplete
+# parameter documentation, but not about the absence of documentation.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC       = NO
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered.
+# The default value is: NO.
+
+WARN_AS_ERROR          = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
+
+INPUT                  = ../hardware/src/ ../hardware/sim/
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: http://www.gnu.org/software/libiconv) for the list of
+# possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
+# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
+# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
+# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f, *.for, *.tcl,
+# *.vhd, *.vhdl, *.ucf, *.qsf, *.as and *.js.
+
+FILE_PATTERNS          = *.h 
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS       =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# function all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS        = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see http://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS       = YES
+
+# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
+# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the
+# cost of reduced performance. This can be particularly helpful with template
+# rich C++ code for which doxygen's built-in parser lacks the necessary type
+# information.
+# Note: The availability of this option depends on whether or not doxygen was
+# generated with the -Duse-libclang=ON option for CMake.
+# The default value is: NO.
+
+CLANG_ASSISTED_PARSING = NO
+
+# If clang assisted parsing is enabled you can provide the compiler with command
+# line options that you would normally use when invoking the compiler. Note that
+# the include paths will already be set by doxygen for the files and directories
+# specified with INPUT and INCLUDE_PATH.
+# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
+
+CLANG_OPTIONS          =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX     = YES
+
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET  =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP         = NO
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: http://developer.apple.com/tools/xcode/), introduced with
+# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET        = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP      = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE               =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION           =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the master .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI           = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING     =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
+# folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX          = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW      = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH         = 250
+
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT    = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# http://www.mathjax.org) which uses client side Javascript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX            = NO
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT         = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from http://www.mathjax.org before deployment.
+# The default value is: http://cdn.mathjax.org/mathjax/latest.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS     =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE       =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE           = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using Javascript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH    = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH        = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL       =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE        = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID     =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS  =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX         = YES
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when enabling USE_PDFLATEX this option is only used for generating
+# bitmaps for formulas in the HTML output, but not in the Makefile that is
+# written to the output directory.
+# The default file is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE             = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
+# string, for the replacement values of the other commands the user is referred
+# to HTML_HEADER.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER           =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES      =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES, to get a
+# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE        = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES     = NO
+
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_SOURCE_CODE      = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE        = plain
+
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP        = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's config
+# file, i.e. a series of assignments. You only have to provide replacements,
+# missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's config file. A template extensions file can be generated
+# using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE    =
+
+# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
+# with syntax highlighting in the RTF output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_SOURCE_CODE        = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION          = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR             =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT             = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK       = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT         = docbook
+
+# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
+# program listings (including syntax highlighting and cross-referencing
+# information) to the DOCBOOK output. Note that enabling this will significantly
+# increase the size of the DOCBOOK output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_PROGRAMLISTING = NO
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://autogen.sf.net) file that captures the
+# structure of the code including all documentation. Note that this feature is
+# still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED             =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
+# The default value is: NO.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS        = YES
+
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES         = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of 'which perl').
+# The default file (with absolute path) is: /usr/bin/perl.
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see:
+# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH               =
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: YES.
+
+HAVE_DOT               = YES
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS        = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK               = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LIMIT_NUM_FIELDS   = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH          = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd,
+# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo,
+# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT       = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG        = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS           =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS           =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+
+PLANTUML_JAR_PATH      =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH  =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_CLEANUP            = YES
diff --git a/vta/hardware/vivado/.gitignore b/vta/hardware/vivado/.gitignore
new file mode 100644
index 000000000000..378eac25d311
--- /dev/null
+++ b/vta/hardware/vivado/.gitignore
@@ -0,0 +1 @@
+build
diff --git a/vta/hardware/vivado/Makefile b/vta/hardware/vivado/Makefile
new file mode 100644
index 000000000000..b52fb628cf37
--- /dev/null
+++ b/vta/hardware/vivado/Makefile
@@ -0,0 +1,106 @@
+# Directories
+ROOTDIR = $(CURDIR)
+BUILD_DIR = $(ROOTDIR)/build
+SCRIPT_DIR = $(ROOTDIR)/scripts
+SRC_DIR = $(ROOTDIR)/../../src/hardware/hls
+SIM_DIR = $(ROOTDIR)/sim
+TEST_DIR = $(ROOTDIR)/../../src/test
+INCLUDE_DIR = $(ROOTDIR)/../../include
+
+# Executables
+VIVADO_HLS = vivado_hls
+VIVADO = vivado
+HSI = hsi
+
+# Build parameters:
+#  Number of threads during compilation
+NUM_THREADS = 8
+#  Target Frequency
+CLOCK_FREQ = 100
+#  Log of input width in bits
+LOG_INP_WIDTH = 3
+#  Log of weight width in bits
+LOG_WGT_WIDTH = 3
+#  Log of accum width in bits
+LOG_ACC_WIDTH = 5
+#  Log of output width in bits
+LOG_OUT_WIDTH = $(LOG_INP_WIDTH)
+#  Log of tensor batch size (A in (A,B)x(B,C) matrix multiplication)
+LOG_BATCH = 0
+#  Log of tensor inner block size (B in (A,B)x(B,C) matrix multiplication)
+LOG_IN_BLOCK = 4
+#  Log of tensor outer block size (C in (A,B)x(B,C) matrix multiplication)
+LOG_OUT_BLOCK = 4
+#  Log of uop buffer size in Bytes
+LOG_UOP_BUFF_SIZE = 15
+#  Log of inp buffer size in Bytes
+LOG_INP_BUFF_SIZE = 15
+#  Log of wgt buffer size in Bytes
+LOG_WGT_BUFF_SIZE = 15
+#  Log of acc buffer size in Bytes
+LOG_ACC_BUFF_SIZE = 17
+#  Log of out buffer size in Bytes
+LOG_OUT_BUFF_SIZE = $(shell echo "$$(( $(LOG_ACC_BUFF_SIZE)+$(LOG_OUT_WIDTH)-$(LOG_ACC_WIDTH) ))" )
+
+# Derived parameter
+#  Input width in bits
+INP_WIDTH = $(shell echo "$$(( 1 << $(LOG_INP_WIDTH) ))" )
+#  Weight width in bits
+WGT_WIDTH = $(shell echo "$$(( 1 << $(LOG_WGT_WIDTH) ))" )
+#  Output width in bits
+OUT_WIDTH = $(shell echo "$$(( 1 << $(LOG_OUT_WIDTH) ))" )
+#  Tensor batch size
+BATCH = $(shell echo "$$(( 1 << $(LOG_BATCH) ))" )
+#  Tensor outer block size
+IN_BLOCK = $(shell echo "$$(( 1 << $(LOG_IN_BLOCK) ))" )
+#  Tensor inner block size
+OUT_BLOCK = $(shell echo "$$(( 1 << $(LOG_OUT_BLOCK) ))" )
+#  Uop buffer size in Bytes
+UOP_BUFF_SIZE = $(shell echo "$$(( 1 << $(LOG_UOP_BUFF_SIZE) ))" )
+#  Inp buffer size in Bytes
+INP_BUFF_SIZE = $(shell echo "$$(( 1 << $(LOG_INP_BUFF_SIZE) ))" )
+#  Wgt buffer size in Bytes
+WGT_BUFF_SIZE = $(shell echo "$$(( 1 << $(LOG_WGT_BUFF_SIZE) ))" )
+#  Acc buffer size in Bytes
+ACC_BUFF_SIZE = $(shell echo "$$(( 1 << $(LOG_ACC_BUFF_SIZE) ))" )
+#  Out buffer size in Bytes
+OUT_BUFF_SIZE = $(shell echo "$$(( 1 << $(LOG_OUT_BUFF_SIZE) ))" )
+
+# Derive clock target period
+TARGET_PER = $(shell echo "$$(( (1000 + $(CLOCK_FREQ) - 1) / $(CLOCK_FREQ) - 0))" )
+
+# Derive config name
+CONF = \
+	$(BATCH)x$(IN_BLOCK)x$(OUT_BLOCK)_$(INP_WIDTH)bx$(WGT_WIDTH)b_$(CLOCK_FREQ)MHz_$(TARGET_PER)ns
+IP_BUILD_PATH = $(BUILD_DIR)/hls/$(CONF)
+HW_BUILD_PATH = $(BUILD_DIR)/vivado/$(CONF)
+
+.PHONY: all ip bit driver clean
+
+all: driver
+
+ip: 
+	mkdir -p $(IP_BUILD_PATH)
+	cd $(IP_BUILD_PATH) && \
+		$(VIVADO_HLS) -f $(SCRIPT_DIR)/hls.tcl \
+			-tclargs $(SRC_DIR) $(SIM_DIR) $(TEST_DIR) $(INCLUDE_DIR) $(TARGET_PER) \
+			$(LOG_INP_WIDTH) $(LOG_WGT_WIDTH) $(LOG_ACC_WIDTH) $(LOG_OUT_WIDTH) \
+			$(LOG_BATCH) $(LOG_OUT_BLOCK) $(LOG_IN_BLOCK) \
+			$(LOG_UOP_BUFF_SIZE) $(LOG_INP_BUFF_SIZE) $(LOG_WGT_BUFF_SIZE) \
+			$(LOG_ACC_BUFF_SIZE) $(LOG_OUT_BUFF_SIZE)
+
+bit: ip
+	mkdir -p $(HW_BUILD_PATH)
+	cd $(HW_BUILD_PATH) && \
+		$(VIVADO) -mode tcl -source $(SCRIPT_DIR)/vivado.tcl \
+		-tclargs $(IP_BUILD_PATH) $(NUM_THREADS) $(CLOCK_FREQ) \
+		$(INP_WIDTH) $(WGT_WIDTH) $(OUT_WIDTH) \
+		$(BATCH) $(IN_BLOCK) $(OUT_BLOCK) \
+		$(INP_BUFF_SIZE) $(WGT_BUFF_SIZE) $(OUT_BUFF_SIZE)
+
+driver: bit
+	cd $(HW_BUILD_PATH) && $(HSI) -mode tcl -source $(SCRIPT_DIR)/hsi.tcl -nojournal -nolog
+	cd $(HW_BUILD_PATH)/bsp && make
+
+clean:
+	rm -rf build
\ No newline at end of file
diff --git a/vta/hardware/vivado/scripts/hls.tcl b/vta/hardware/vivado/scripts/hls.tcl
new file mode 100644
index 000000000000..55b3c01d5012
--- /dev/null
+++ b/vta/hardware/vivado/scripts/hls.tcl
@@ -0,0 +1,177 @@
+#
+#  Copyright (c) 2018 by Contributors
+#  file: hls.tcl
+#  brief: HLS generation script.
+#
+
+# Command line arguments:
+# Arg 1: path to design sources
+# Arg 2: path to sim sources
+# Arg 3: path to test sources
+# Arg 4: path to include sources
+# Arg 5: target clock period
+# Arg 6: input type width (log)
+# Arg 7: weight type width (log)
+# Arg 8: accum type width (log)
+# Arg 9: output type width (log)
+# Arg 10: batch size (log)
+# Arg 11: in block size (log)
+# Arg 12: out block size (log)
+# Arg 13: uop buffer size in B (log)
+# Arg 14: inp buffer size in B (log)
+# Arg 15: wgt buffer size in B (log)
+# Arg 16: acc buffer size in B (log)
+# Arg 17: out buffer size in B (log)
+
+if { [llength $argv] eq 19 } {
+	set src_dir [lindex $argv 2]
+	set sim_dir [lindex $argv 3]
+	set test_dir [lindex $argv 4]
+	set include_dir [lindex $argv 5]
+	set target_period [lindex $argv 6]
+	set inp_width [lindex $argv 7]
+	set wgt_width [lindex $argv 8]
+	set acc_width [lindex $argv 9]
+	set out_width [lindex $argv 10]
+	set batch [lindex $argv 11]
+	set block_in [lindex $argv 12]
+	set block_out [lindex $argv 13]
+	set uop_buff_size [lindex $argv 14]
+	set inp_buff_size [lindex $argv 15]
+	set wgt_buff_size [lindex $argv 16]
+	set acc_buff_size [lindex $argv 17]
+	set out_buff_size [lindex $argv 18]
+} else {
+	set src_dir "../src/"
+	set sim_dir "../sim/"
+	set test_dir "../../src/test/"
+	set include_dir "../../include"
+	set target_period 10
+	set inp_width 3
+	set wgt_width 3
+	set acc_width 5
+	set out_width 3
+	set batch 1
+	set block_out 4
+	set block_in 4
+	set uop_buff_size 15
+	set inp_buff_size 15
+	set wgt_buff_size 15
+	set acc_buff_size 17
+	set out_buff_size 15
+}
+
+# C define flags to pass to compiler
+set cflags "-I $include_dir -I $include_dir/hardware/hls \
+	-DDEBUG=0 -DLOG_WGT_WIDTH=$wgt_width -DLOG_INP_WIDTH=$inp_width \
+	-DLOG_ACC_WIDTH=$acc_width -DLOG_OUT_WIDTH=$out_width \
+	-DLOG_BATCH=$batch -DLOG_BLOCK_OUT=$block_out -DLOG_BLOCK_IN=$block_in \
+	-DLOG_UOP_BUFF_SIZE=$uop_buff_size -DLOG_INP_BUFF_SIZE=$inp_buff_size \
+	-DLOG_WGT_BUFF_SIZE=$wgt_buff_size -DLOG_ACC_BUFF_SIZE=$acc_buff_size \
+	-DLOG_OUT_BUFF_SIZE=$out_buff_size"
+
+# Initializes the HLS design and sets HLS pragmas for memory partitioning.
+# This is necessary because of a Vivado restriction that doesn't allow for
+# buses wider than 1024 bits.
+proc init_design {per inp_width wgt_width out_width batch block_in block_out} {
+
+	# Set device number
+	set_part {xc7z020clg484-1}
+
+	# Set the clock frequency
+	create_clock -period $per -name default
+
+	# Set input partition factor to (INP_VECTOR_WIDTH*BATCH/1024)
+	set inp_partition_factor [expr {(1 << ($inp_width + $block_in + $batch)) / 1024}]
+	if {$inp_partition_factor == 0} {
+		set_directive_array_reshape -type complete -dim 2 "load" inp_mem
+		set_directive_array_reshape -type complete -dim 2 "compute" inp_mem
+	} else {
+		# Set input reshaping factor below to (1024/INP_VECTOR_WIDTH)
+		set inp_reshape_factor [expr {1024 / (1 << ($inp_width + $block_in))}]
+		set_directive_array_partition -type block -factor $inp_partition_factor -dim 2 "load" inp_mem
+		set_directive_array_partition -type block -factor $inp_partition_factor -dim 2 "compute" inp_mem
+		set_directive_array_reshape -type block -factor $inp_reshape_factor -dim 2 "load" inp_mem
+		set_directive_array_reshape -type block -factor $inp_reshape_factor -dim 2 "compute" inp_mem
+	}
+	# Set weight partition factor to (WGT_VECTOR_WIDTH*BLOCK_OUT/1024)
+	set wgt_partition_factor [expr {(1 << ($wgt_width + $block_in + $block_out)) / 1024}]
+	if {$wgt_partition_factor == 0} {
+		set_directive_array_reshape -type complete -dim 2 "load" wgt_mem
+		set_directive_array_reshape -type complete -dim 2 "compute" wgt_mem
+	} else {
+		# Set weight reshaping factor below to (1024/WGT_VECTOR_WIDTH)
+		set wgt_reshape_factor [expr {1024 / (1 << ($wgt_width + $block_in))}]
+		set_directive_array_partition -type block -factor $wgt_partition_factor -dim 2 "load" wgt_mem
+		set_directive_array_partition -type block -factor $wgt_partition_factor -dim 2 "compute" wgt_mem
+		set_directive_array_reshape -type block -factor $wgt_reshape_factor -dim 2 "load" wgt_mem
+		set_directive_array_reshape -type block -factor $wgt_reshape_factor -dim 2 "compute" wgt_mem
+	}
+	# Set output partition factor to (OUT_VECTOR_WIDTH*BATCH/1024)
+	set out_partition_factor [expr {(1 << ($out_width + $block_out + $batch)) / 1024}]
+	if {$out_partition_factor == 0} {
+		set_directive_array_reshape -type complete -dim 2 "compute" out_mem
+		set_directive_array_reshape -type complete -dim 2 "store" out_mem
+	} else {
+		# Set output reshaping factor below to (1024/OUT_VECTOR_WIDTH)
+		set out_reshape_factor [expr {1024 / (1 << ($out_width + $block_out))}]
+		set_directive_array_partition -type block -factor $out_partition_factor -dim 2 "compute" out_mem
+		set_directive_array_partition -type block -factor $out_partition_factor -dim 2 "store" out_mem
+		set_directive_array_reshape -type block -factor $out_reshape_factor -dim 2 "compute" out_mem
+		set_directive_array_reshape -type block -factor $out_reshape_factor -dim 2 "store" out_mem
+	}
+}
+
+# HLS behavioral sim
+open_project vta_sim
+set_top vta
+add_files $src_dir/vta.cc -cflags $cflags
+add_files -tb $sim_dir/vta_test.cc -cflags $cflags
+add_files -tb $test_dir/vta_test_lib.cc -cflags $cflags
+open_solution "solution0"
+init_design $target_period $inp_width $wgt_width $out_width $batch $block_in $block_out
+csim_design -clean
+close_project
+
+# Generate fetch stage
+open_project vta_fetch
+set_top fetch
+add_files $src_dir/vta.cc -cflags $cflags
+open_solution "solution0"
+init_design $target_period $inp_width $wgt_width $out_width $batch $block_in $block_out
+csynth_design
+export_design -format ip_catalog
+close_project
+
+# Generate load stage
+open_project vta_load
+set_top load
+add_files $src_dir/vta.cc -cflags $cflags
+open_solution "solution0"
+init_design $target_period $inp_width $wgt_width $out_width $batch $block_in $block_out
+csynth_design
+export_design -format ip_catalog
+close_project
+
+# Generate compute stage
+open_project vta_compute
+set_top compute
+add_files $src_dir/vta.cc -cflags $cflags
+open_solution "solution0"
+init_design $target_period $inp_width $wgt_width $out_width $batch $block_in $block_out
+csynth_design
+export_design -format ip_catalog
+close_project
+
+# Generate store stage
+open_project vta_store
+set_top store
+add_files $src_dir/vta.cc -cflags $cflags
+open_solution "solution0"
+init_design $target_period $inp_width $wgt_width $out_width $batch $block_in $block_out
+csynth_design
+export_design -format ip_catalog
+close_project
+
+exit
+
diff --git a/vta/hardware/vivado/scripts/hsi.tcl b/vta/hardware/vivado/scripts/hsi.tcl
new file mode 100644
index 000000000000..1567b078a3e4
--- /dev/null
+++ b/vta/hardware/vivado/scripts/hsi.tcl
@@ -0,0 +1,11 @@
+#
+#  Copyright (c) 2018 by Contributors
+#  file: hsi.tcl
+#  brief: Driver generation script for ARMv7 driver libraries.
+#
+
+open_hw_design export/vta.hdf
+create_sw_design swdesign -proc ps7_cortexa9_0 -os standalone
+generate_bsp -dir bsp
+
+exit
diff --git a/vta/hardware/vivado/scripts/vivado.tcl b/vta/hardware/vivado/scripts/vivado.tcl
new file mode 100644
index 000000000000..c9b8f6c6317f
--- /dev/null
+++ b/vta/hardware/vivado/scripts/vivado.tcl
@@ -0,0 +1,946 @@
+#
+#  Copyright (c) 2018 by Xilinx, Contributors
+#  file: vivado.tcl
+#  brief: Vivado compilation script. Partially automatically generated
+#   by Vivado.
+#
+
+# Check if script is running in correct Vivado version.
+set scripts_vivado_version 2017.1
+set current_vivado_version [version -short]
+
+if { [string first $scripts_vivado_version $current_vivado_version] == -1 } {
+   puts ""
+   catch {common::send_msg_id "BD_TCL-109" "ERROR" "This script was generated using Vivado \
+    <$scripts_vivado_version> and is being run in <$current_vivado_version> of Vivado. \
+    Please run the script in Vivado <$scripts_vivado_version> then open the design in Vivado \
+    <$current_vivado_version>. Upgrade the design by running \"Tools => Report => Report IP \
+    Status...\", then run write_bd_tcl to create an updated script."}
+
+   return 1
+}
+
+# Parse argument list, derive the clock to utilize
+set clock_id 0
+if { [llength $argv] eq 12 } {
+  set ip_path [lindex $argv 0]
+  set num_threads [lindex $argv 1]
+  set clock_freq [lindex $argv 2]
+  set inp_width [lindex $argv 3]
+  set wgt_width [lindex $argv 4]
+  set out_width [lindex $argv 5]
+  set batch [lindex $argv 6]
+  set out_block [lindex $argv 7]
+  set in_block [lindex $argv 8]
+  set inp_mem_size [lindex $argv 9]
+  set wgt_mem_size [lindex $argv 10]
+  set out_mem_size [lindex $argv 11]
+  if {$clock_freq eq 100} {
+    set clock_id 0
+    puts "Setting clock frequency to 100MHz"
+  } elseif {$clock_freq eq 142} {
+    set clock_id 1
+    puts "Setting clock frequency to 142MHz"
+  } elseif {$clock_freq eq 167} {
+    set clock_id 3
+    puts "Setting clock frequency to 167MHz"
+  } elseif {$clock_freq eq 200} {
+    set clock_id 2
+    puts "Setting clock frequency to 200MHz"
+  } else {
+    set clock_id 0
+    puts "Unrecognized clock frequency, setting clock to 100MHz"
+  }
+} else {
+  puts "Arg list incomplete: <path to ip dir> <num threads> <clock freq> \
+    <inp width> <wgt_width> <out_width> <batch> <in_block / 1024> <out_block>"
+  return 1
+}
+
+# Derive input mem parameters
+set inp_mem_width [expr $inp_width * $batch * $in_block]
+set inp_mem_depth [expr $inp_mem_size * 8 / $inp_mem_width]
+set inp_bus_width 1024
+set inp_part [expr $inp_mem_width / $inp_bus_width]
+if {[expr $inp_part == 0]} {
+  set inp_part 1
+  set inp_bus_width $inp_mem_width
+}
+# Derive weight mem parameters
+set wgt_mem_width [expr $wgt_width * $out_block * $in_block]
+set wgt_mem_depth [expr $wgt_mem_size * 8 / $wgt_mem_width]
+set wgt_bus_width 1024
+set wgt_part [expr $wgt_mem_width / $wgt_bus_width]
+if {[expr $wgt_part == 0]} {
+  set wgt_part 1
+  set wgt_bus_width $wgt_mem_width
+}
+# Derive output mem parameters
+set out_mem_width [expr $out_width * $batch * $out_block]
+set out_mem_depth [expr $out_mem_size * 8 / $out_mem_width]
+set out_bus_width 1024
+set out_part [expr $out_mem_width / $out_bus_width]
+if {[expr $out_part == 0]} {
+  set out_part 1
+  set out_bus_width $out_mem_width
+}
+
+puts $inp_mem_width
+puts $inp_mem_depth
+puts $inp_bus_width
+puts $inp_part
+puts $wgt_mem_width
+puts $wgt_mem_depth
+puts $wgt_bus_width
+puts $wgt_part
+puts $out_mem_width
+puts $out_mem_depth
+puts $out_bus_width
+puts $out_part
+
+# User defined paths
+set proj_name vta
+set proj_path "."
+set ip_lib "ip_lib"
+set fetch_ip "${ip_path}/vta_fetch/solution0/impl/ip/xilinx_com_hls_fetch_1_0.zip"
+set load_ip "${ip_path}/vta_load/solution0/impl/ip/xilinx_com_hls_load_1_0.zip"
+set compute_ip "${ip_path}/vta_compute/solution0/impl/ip/xilinx_com_hls_compute_1_0.zip"
+set store_ip "${ip_path}/vta_store/solution0/impl/ip/xilinx_com_hls_store_1_0.zip"
+
+# Create custom project
+create_project -force $proj_name $proj_path -part xc7z020clg484-1
+
+# Update IP repository with generated IP
+file mkdir $ip_lib
+set_property ip_repo_paths $ip_lib [current_project]
+update_ip_catalog
+update_ip_catalog -add_ip $fetch_ip -repo_path $ip_lib
+update_ip_catalog -add_ip $load_ip -repo_path $ip_lib
+update_ip_catalog -add_ip $compute_ip -repo_path $ip_lib
+update_ip_catalog -add_ip $store_ip -repo_path $ip_lib
+
+# CHANGE DESIGN NAME HERE
+set design_name $proj_name
+
+# Creating design if needed
+set errMsg ""
+set nRet 0
+
+set cur_design [current_bd_design -quiet]
+set list_cells [get_bd_cells -quiet]
+
+if { ${design_name} eq "" } {
+   # USE CASES:
+   #    1) Design_name not set
+
+   set errMsg "Please set the variable <design_name> to a non-empty value."
+   set nRet 1
+
+} elseif { ${cur_design} ne "" && ${list_cells} eq "" } {
+   # USE CASES:
+   #    2): Current design opened AND is empty AND names same.
+   #    3): Current design opened AND is empty AND names diff; design_name NOT in project.
+   #    4): Current design opened AND is empty AND names diff; design_name exists in project.
+
+   if { $cur_design ne $design_name } {
+      common::send_msg_id "BD_TCL-001" "INFO" "Changing value of <design_name> from <$design_name> \
+        to <$cur_design> since current design is empty."
+      set design_name [get_property NAME $cur_design]
+   }
+   common::send_msg_id "BD_TCL-002" "INFO" "Constructing design in IPI design <$cur_design>..."
+
+} elseif { ${cur_design} ne "" && $list_cells ne "" && $cur_design eq $design_name } {
+   # USE CASES:
+   #    5) Current design opened AND has components AND same names.
+
+   set errMsg "Design <$design_name> already exists in your project, please set the variable \
+    <design_name> to another value."
+   set nRet 1
+} elseif { [get_files -quiet ${design_name}.bd] ne "" } {
+   # USE CASES:
+   #    6) Current opened design, has components, but diff names, design_name exists in project.
+   #    7) No opened design, design_name exists in project.
+
+   set errMsg "Design <$design_name> already exists in your project, please set the variable \
+    <design_name> to another value."
+   set nRet 2
+
+} else {
+   # USE CASES:
+   #    8) No opened design, design_name not in project.
+   #    9) Current opened design, has components, but diff names, design_name not in project.
+
+   common::send_msg_id "BD_TCL-003" "INFO" "Currently there is no design <$design_name> in \
+    project, so creating one..."
+
+   create_bd_design $design_name
+
+   common::send_msg_id "BD_TCL-004" "INFO" "Making design <$design_name> as current_bd_design."
+   current_bd_design $design_name
+
+}
+
+common::send_msg_id "BD_TCL-005" "INFO" "Currently the variable <design_name> is equal \
+  to \"$design_name\"."
+
+if { $nRet != 0 } {
+   catch {common::send_msg_id "BD_TCL-114" "ERROR" $errMsg}
+   return $nRet
+}
+
+##################################################################
+# DESIGN PROCs
+##################################################################
+
+
+
+# Procedure to create entire design; Provide argument to make
+# procedure reusable. If parentCell is "", will use root.
+proc create_root_design { parentCell clk inp_part wgt_part out_part inp_bus_width inp_mem_depth wgt_bus_width wgt_mem_depth out_bus_width out_mem_depth} {
+
+  variable script_folder
+
+  if { $parentCell eq "" } {
+     set parentCell [get_bd_cells /]
+  }
+
+  # Get object for parentCell
+  set parentObj [get_bd_cells $parentCell]
+  if { $parentObj == "" } {
+     catch {common::send_msg_id "BD_TCL-100" "ERROR" "Unable to find parent cell <$parentCell>!"}
+     return
+  }
+
+  # Make sure parentObj is hier blk
+  set parentType [get_property TYPE $parentObj]
+  if { $parentType ne "hier" } {
+     catch {common::send_msg_id "BD_TCL-101" "ERROR" "Parent <$parentObj> has TYPE = \
+      <$parentType>. Expected to be <hier>."}
+     return
+  }
+
+  # Save current instance; Restore later
+  set oldCurInst [current_bd_instance .]
+
+  # Set parent object as current
+  current_bd_instance $parentObj
+
+
+  # Create interface ports
+  set DDR [ create_bd_intf_port -mode Master -vlnv xilinx.com:interface:ddrx_rtl:1.0 DDR ]
+  set FIXED_IO [ create_bd_intf_port -mode Master \
+    -vlnv xilinx.com:display_processing_system7:fixedio_rtl:1.0 FIXED_IO ]
+
+  # Create ports
+
+  # Create instance: axi_interconnect_1, and set properties
+  set axi_interconnect_1 \
+    [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect:2.1 axi_interconnect_1 ]
+  set_property -dict [ list \
+    CONFIG.NUM_MI {5} \
+  ] $axi_interconnect_1
+
+  # Create instance: axi_smc, and set properties
+  set axi_smc [ create_bd_cell -type ip -vlnv xilinx.com:ip:smartconnect:1.0 axi_smc ]
+  set_property -dict [ list \
+    CONFIG.NUM_SI {5} \
+  ] $axi_smc
+
+  # Create instance: axi_timer_1, and set properties
+  set axi_timer_1 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_timer:2.0 axi_timer_1 ]
+
+  # Create instance: compute_0, and set properties
+  set compute_0 [ create_bd_cell -type ip -vlnv xilinx.com:hls:compute:1.0 compute_0 ]
+  set_property -dict [ list \
+    CONFIG.C_M_AXI_DATA_PORT_CACHE_VALUE {"1111"} \
+    CONFIG.C_M_AXI_DATA_PORT_DATA_WIDTH {64} \
+    CONFIG.C_M_AXI_UOP_PORT_CACHE_VALUE {"1111"} \
+  ] $compute_0
+
+  # Create instance: fetch_0, and set properties
+  set fetch_0 [ create_bd_cell -type ip -vlnv xilinx.com:hls:fetch:1.0 fetch_0 ]
+  set_property -dict [ list \
+    CONFIG.C_M_AXI_INS_PORT_CACHE_VALUE {"1111"} \
+    CONFIG.C_M_AXI_INS_PORT_DATA_WIDTH {64} \
+  ] $fetch_0
+
+  # Create instance: g2l_queue, and set properties
+  set g2l_queue [ create_bd_cell -type ip -vlnv xilinx.com:ip:fifo_generator:13.1 g2l_queue ]
+  set_property -dict [ list \
+    CONFIG.Empty_Threshold_Assert_Value_axis {1022} \
+    CONFIG.Empty_Threshold_Assert_Value_rach {14} \
+    CONFIG.Empty_Threshold_Assert_Value_wach {14} \
+    CONFIG.Empty_Threshold_Assert_Value_wrch {14} \
+    CONFIG.FIFO_Implementation_rach {Common_Clock_Distributed_RAM} \
+    CONFIG.FIFO_Implementation_wach {Common_Clock_Distributed_RAM} \
+    CONFIG.FIFO_Implementation_wrch {Common_Clock_Distributed_RAM} \
+    CONFIG.Full_Flags_Reset_Value {1} \
+    CONFIG.Full_Threshold_Assert_Value_axis {1023} \
+    CONFIG.Full_Threshold_Assert_Value_rach {15} \
+    CONFIG.Full_Threshold_Assert_Value_wach {15} \
+    CONFIG.Full_Threshold_Assert_Value_wrch {15} \
+    CONFIG.INTERFACE_TYPE {AXI_STREAM} \
+    CONFIG.Input_Depth_axis {1024} \
+    CONFIG.Reset_Type {Asynchronous_Reset} \
+    CONFIG.TUSER_WIDTH {0} \
+  ] $g2l_queue
+
+  # Create instance: g2s_queue, and set properties
+  set g2s_queue [ create_bd_cell -type ip -vlnv xilinx.com:ip:fifo_generator:13.1 g2s_queue ]
+  set_property -dict [ list \
+    CONFIG.Empty_Threshold_Assert_Value_axis {1022} \
+    CONFIG.Empty_Threshold_Assert_Value_rach {14} \
+    CONFIG.Empty_Threshold_Assert_Value_wach {14} \
+    CONFIG.Empty_Threshold_Assert_Value_wrch {14} \
+    CONFIG.FIFO_Implementation_rach {Common_Clock_Distributed_RAM} \
+    CONFIG.FIFO_Implementation_wach {Common_Clock_Distributed_RAM} \
+    CONFIG.FIFO_Implementation_wrch {Common_Clock_Distributed_RAM} \
+    CONFIG.Full_Flags_Reset_Value {1} \
+    CONFIG.Full_Threshold_Assert_Value_axis {1023} \
+    CONFIG.Full_Threshold_Assert_Value_rach {15} \
+    CONFIG.Full_Threshold_Assert_Value_wach {15} \
+    CONFIG.Full_Threshold_Assert_Value_wrch {15} \
+    CONFIG.INTERFACE_TYPE {AXI_STREAM} \
+    CONFIG.Input_Depth_axis {1024} \
+    CONFIG.Reset_Type {Asynchronous_Reset} \
+    CONFIG.TUSER_WIDTH {0} \
+  ] $g2s_queue
+
+  # Create instance: gemm_queue, and set properties
+  set gemm_queue [ create_bd_cell -type ip -vlnv xilinx.com:ip:fifo_generator:13.1 gemm_queue ]
+  set_property -dict [ list \
+    CONFIG.Empty_Threshold_Assert_Value_axis {510} \
+    CONFIG.Empty_Threshold_Assert_Value_rach {14} \
+    CONFIG.Empty_Threshold_Assert_Value_wach {14} \
+    CONFIG.Empty_Threshold_Assert_Value_wrch {14} \
+    CONFIG.FIFO_Implementation_rach {Common_Clock_Distributed_RAM} \
+    CONFIG.FIFO_Implementation_wach {Common_Clock_Distributed_RAM} \
+    CONFIG.FIFO_Implementation_wrch {Common_Clock_Distributed_RAM} \
+    CONFIG.Full_Flags_Reset_Value {1} \
+    CONFIG.Full_Threshold_Assert_Value_axis {511} \
+    CONFIG.Full_Threshold_Assert_Value_rach {15} \
+    CONFIG.Full_Threshold_Assert_Value_wach {15} \
+    CONFIG.Full_Threshold_Assert_Value_wrch {15} \
+    CONFIG.INTERFACE_TYPE {AXI_STREAM} \
+    CONFIG.Input_Depth_axis {512} \
+    CONFIG.Reset_Type {Asynchronous_Reset} \
+    CONFIG.TDATA_NUM_BYTES {16} \
+    CONFIG.TKEEP_WIDTH {16} \
+    CONFIG.TSTRB_WIDTH {16} \
+    CONFIG.TUSER_WIDTH {0} \
+  ] $gemm_queue
+
+  # Create instance: l2g_queue, and set properties
+  set l2g_queue [ create_bd_cell -type ip -vlnv xilinx.com:ip:fifo_generator:13.1 l2g_queue ]
+  set_property -dict [ list \
+    CONFIG.Empty_Threshold_Assert_Value_axis {1022} \
+    CONFIG.Empty_Threshold_Assert_Value_rach {14} \
+    CONFIG.Empty_Threshold_Assert_Value_wach {14} \
+    CONFIG.Empty_Threshold_Assert_Value_wrch {14} \
+    CONFIG.FIFO_Implementation_rach {Common_Clock_Distributed_RAM} \
+    CONFIG.FIFO_Implementation_wach {Common_Clock_Distributed_RAM} \
+    CONFIG.FIFO_Implementation_wrch {Common_Clock_Distributed_RAM} \
+    CONFIG.Full_Flags_Reset_Value {1} \
+    CONFIG.Full_Threshold_Assert_Value_axis {1023} \
+    CONFIG.Full_Threshold_Assert_Value_rach {15} \
+    CONFIG.Full_Threshold_Assert_Value_wach {15} \
+    CONFIG.Full_Threshold_Assert_Value_wrch {15} \
+    CONFIG.INTERFACE_TYPE {AXI_STREAM} \
+    CONFIG.Input_Depth_axis {1024} \
+    CONFIG.Reset_Type {Asynchronous_Reset} \
+    CONFIG.TUSER_WIDTH {0} \
+  ] $l2g_queue
+
+  # Create instance: load_0, and set properties
+  set load_0 [ create_bd_cell -type ip -vlnv xilinx.com:hls:load:1.0 load_0 ]
+  set_property -dict [ list \
+    CONFIG.C_M_AXI_DATA_PORT_CACHE_VALUE {"1111"} \
+  ] $load_0
+
+  # Create instance: load_queue, and set properties
+  set load_queue [ create_bd_cell -type ip -vlnv xilinx.com:ip:fifo_generator:13.1 load_queue ]
+  set_property -dict [ list \
+    CONFIG.Empty_Threshold_Assert_Value_axis {510} \
+    CONFIG.Empty_Threshold_Assert_Value_rach {14} \
+    CONFIG.Empty_Threshold_Assert_Value_wach {14} \
+    CONFIG.Empty_Threshold_Assert_Value_wrch {14} \
+    CONFIG.FIFO_Implementation_rach {Common_Clock_Distributed_RAM} \
+    CONFIG.FIFO_Implementation_wach {Common_Clock_Distributed_RAM} \
+    CONFIG.FIFO_Implementation_wrch {Common_Clock_Distributed_RAM} \
+    CONFIG.Full_Flags_Reset_Value {1} \
+    CONFIG.Full_Threshold_Assert_Value_axis {511} \
+    CONFIG.Full_Threshold_Assert_Value_rach {15} \
+    CONFIG.Full_Threshold_Assert_Value_wach {15} \
+    CONFIG.Full_Threshold_Assert_Value_wrch {15} \
+    CONFIG.INTERFACE_TYPE {AXI_STREAM} \
+    CONFIG.Input_Depth_axis {512} \
+    CONFIG.Reset_Type {Asynchronous_Reset} \
+    CONFIG.TDATA_NUM_BYTES {16} \
+    CONFIG.TKEEP_WIDTH {16} \
+    CONFIG.TSTRB_WIDTH {16} \
+    CONFIG.TUSER_WIDTH {0} \
+  ] $load_queue
+
+  # Create instance: proc_sys_reset, and set properties
+  set proc_sys_reset \
+    [ create_bd_cell -type ip -vlnv xilinx.com:ip:proc_sys_reset:5.0 proc_sys_reset ]
+
+  # Create instance: processing_system7_1, and set properties
+  set processing_system7_1 \
+    [ create_bd_cell -type ip -vlnv xilinx.com:ip:processing_system7:5.5 processing_system7_1 ]
+  set_property -dict [ list \
+    CONFIG.PCW_CAN0_PERIPHERAL_ENABLE {0} \
+    CONFIG.PCW_ENET0_PERIPHERAL_ENABLE {0} \
+    CONFIG.PCW_EN_CLK0_PORT {1} \
+    CONFIG.PCW_EN_CLK1_PORT {1} \
+    CONFIG.PCW_EN_CLK2_PORT {1} \
+    CONFIG.PCW_EN_CLK3_PORT {1} \
+    CONFIG.PCW_FPGA0_PERIPHERAL_FREQMHZ {100} \
+    CONFIG.PCW_FPGA1_PERIPHERAL_FREQMHZ {142.86} \
+    CONFIG.PCW_FPGA2_PERIPHERAL_FREQMHZ {200} \
+    CONFIG.PCW_FPGA3_PERIPHERAL_FREQMHZ {167} \
+    CONFIG.PCW_GPIO_MIO_GPIO_ENABLE {0} \
+    CONFIG.PCW_I2C0_PERIPHERAL_ENABLE {0} \
+    CONFIG.PCW_IMPORT_BOARD_PRESET {None} \
+    CONFIG.PCW_IRQ_F2P_INTR {1} \
+    CONFIG.PCW_QSPI_GRP_SINGLE_SS_ENABLE {0} \
+    CONFIG.PCW_QSPI_PERIPHERAL_ENABLE {0} \
+    CONFIG.PCW_SD0_PERIPHERAL_ENABLE {0} \
+    CONFIG.PCW_USB0_PERIPHERAL_ENABLE {0} \
+    CONFIG.PCW_USE_DEFAULT_ACP_USER_VAL {1} \
+    CONFIG.PCW_USE_FABRIC_INTERRUPT {1} \
+    CONFIG.PCW_USE_HIGH_OCM {1} \
+    CONFIG.PCW_USE_S_AXI_ACP {1} \
+    CONFIG.PCW_USE_S_AXI_HP0 {0} \
+    CONFIG.PCW_USE_S_AXI_HP1 {0} \
+    CONFIG.PCW_USE_S_AXI_HP2 {0} \
+    CONFIG.PCW_USE_S_AXI_HP3 {0} \
+    CONFIG.preset {ZC702} \
+  ] $processing_system7_1
+
+  # Create instance: s2g_queue, and set properties
+  set s2g_queue [ create_bd_cell -type ip -vlnv xilinx.com:ip:fifo_generator:13.1 s2g_queue ]
+  set_property -dict [ list \
+    CONFIG.Empty_Threshold_Assert_Value_axis {1022} \
+    CONFIG.Empty_Threshold_Assert_Value_rach {14} \
+    CONFIG.Empty_Threshold_Assert_Value_wach {14} \
+    CONFIG.Empty_Threshold_Assert_Value_wrch {14} \
+    CONFIG.FIFO_Implementation_rach {Common_Clock_Distributed_RAM} \
+    CONFIG.FIFO_Implementation_wach {Common_Clock_Distributed_RAM} \
+    CONFIG.FIFO_Implementation_wrch {Common_Clock_Distributed_RAM} \
+    CONFIG.Full_Flags_Reset_Value {1} \
+    CONFIG.Full_Threshold_Assert_Value_axis {1023} \
+    CONFIG.Full_Threshold_Assert_Value_rach {15} \
+    CONFIG.Full_Threshold_Assert_Value_wach {15} \
+    CONFIG.Full_Threshold_Assert_Value_wrch {15} \
+    CONFIG.INTERFACE_TYPE {AXI_STREAM} \
+    CONFIG.Input_Depth_axis {1024} \
+    CONFIG.Reset_Type {Asynchronous_Reset} \
+    CONFIG.TUSER_WIDTH {0} \
+  ] $s2g_queue
+
+  # Create instance: store_0, and set properties
+  set store_0 [ create_bd_cell -type ip -vlnv xilinx.com:hls:store:1.0 store_0 ]
+  set_property -dict [ list \
+CONFIG.C_M_AXI_DATA_PORT_CACHE_VALUE {"1111"} \
+  ] $store_0
+
+  # Create instance: store_queue, and set properties
+  set store_queue [ create_bd_cell -type ip -vlnv xilinx.com:ip:fifo_generator:13.1 store_queue ]
+  set_property -dict [ list \
+    CONFIG.Empty_Threshold_Assert_Value_axis {510} \
+    CONFIG.Empty_Threshold_Assert_Value_rach {14} \
+    CONFIG.Empty_Threshold_Assert_Value_wach {14} \
+    CONFIG.Empty_Threshold_Assert_Value_wrch {14} \
+    CONFIG.FIFO_Implementation_rach {Common_Clock_Distributed_RAM} \
+    CONFIG.FIFO_Implementation_wach {Common_Clock_Distributed_RAM} \
+    CONFIG.FIFO_Implementation_wrch {Common_Clock_Distributed_RAM} \
+    CONFIG.Full_Flags_Reset_Value {1} \
+    CONFIG.Full_Threshold_Assert_Value_axis {511} \
+    CONFIG.Full_Threshold_Assert_Value_rach {15} \
+    CONFIG.Full_Threshold_Assert_Value_wach {15} \
+    CONFIG.Full_Threshold_Assert_Value_wrch {15} \
+    CONFIG.INTERFACE_TYPE {AXI_STREAM} \
+    CONFIG.Input_Depth_axis {512} \
+    CONFIG.Reset_Type {Asynchronous_Reset} \
+    CONFIG.TDATA_NUM_BYTES {16} \
+    CONFIG.TKEEP_WIDTH {16} \
+    CONFIG.TSTRB_WIDTH {16} \
+    CONFIG.TUSER_WIDTH {0} \
+  ] $store_queue
+
+  # Create instance: xlconcat_1, and set properties
+  set xlconcat_1 [ create_bd_cell -type ip -vlnv xilinx.com:ip:xlconcat:2.1 xlconcat_1 ]
+  set_property -dict [ list \
+CONFIG.NUM_PORTS {5} \
+  ] $xlconcat_1
+
+  # Create and connect inp_mem partitions
+  if {${inp_part} > 1} {
+    for {set i 0} {$i < ${inp_part}} {incr i} {
+      # Create instance: inp_mem, and set properties
+      set inp_mem [ create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen:8.3 inp_mem_${i} ]
+      set_property -dict [ list \
+        CONFIG.Byte_Size {8} \
+        CONFIG.Enable_32bit_Address {true} \
+        CONFIG.Enable_B {Use_ENB_Pin} \
+        CONFIG.Memory_Type {True_Dual_Port_RAM} \
+        CONFIG.Read_Width_A $inp_bus_width \
+        CONFIG.Read_Width_B $inp_bus_width \
+        CONFIG.Register_PortA_Output_of_Memory_Primitives {false} \
+        CONFIG.Register_PortB_Output_of_Memory_Primitives {false} \
+        CONFIG.Use_Byte_Write_Enable {true} \
+        CONFIG.Use_RSTA_Pin {true} \
+        CONFIG.Use_RSTB_Pin {true} \
+        CONFIG.Write_Depth_A $inp_mem_depth \
+        CONFIG.Write_Width_A $inp_bus_width \
+        CONFIG.Write_Width_B $inp_bus_width \
+        CONFIG.use_bram_block {BRAM_Controller} \
+      ] $inp_mem
+      # Create interface connections
+      connect_bd_intf_net -intf_net load_0_inp_mem_${i}_V_PORTA \
+        [get_bd_intf_pins $inp_mem/BRAM_PORTA] \
+        [get_bd_intf_pins load_0/inp_mem_${i}_V_PORTA]
+      connect_bd_intf_net -intf_net compute_0_inp_mem_${i}_V_PORTA \
+        [get_bd_intf_pins compute_0/inp_mem_${i}_V_PORTA] \
+        [get_bd_intf_pins $inp_mem/BRAM_PORTB]
+    }
+  } else {
+      # Create instance: inp_mem, and set properties
+      set inp_mem [ create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen:8.3 inp_mem ]
+      set_property -dict [ list \
+        CONFIG.Byte_Size {8} \
+        CONFIG.Enable_32bit_Address {true} \
+        CONFIG.Enable_B {Use_ENB_Pin} \
+        CONFIG.Memory_Type {True_Dual_Port_RAM} \
+        CONFIG.Read_Width_A $inp_bus_width \
+        CONFIG.Read_Width_B $inp_bus_width \
+        CONFIG.Register_PortA_Output_of_Memory_Primitives {false} \
+        CONFIG.Register_PortB_Output_of_Memory_Primitives {false} \
+        CONFIG.Use_Byte_Write_Enable {true} \
+        CONFIG.Use_RSTA_Pin {true} \
+        CONFIG.Use_RSTB_Pin {true} \
+        CONFIG.Write_Depth_A $inp_mem_depth \
+        CONFIG.Write_Width_A $inp_bus_width \
+        CONFIG.Write_Width_B $inp_bus_width \
+        CONFIG.use_bram_block {BRAM_Controller} \
+      ] $inp_mem
+      # Create interface connections
+      connect_bd_intf_net -intf_net load_0_inp_mem_V_PORTA \
+        [get_bd_intf_pins $inp_mem/BRAM_PORTA] \
+        [get_bd_intf_pins load_0/inp_mem_V_PORTA]
+      connect_bd_intf_net -intf_net compute_0_inp_mem_V_PORTA \
+        [get_bd_intf_pins compute_0/inp_mem_V_PORTA] \
+        [get_bd_intf_pins $inp_mem/BRAM_PORTB]
+  }
+
+  # Create and connect wgt_mem partitions
+  if {${wgt_part} > 1} {
+    for {set i 0} {$i < ${wgt_part}} {incr i} {
+      # Create instance: wgt_mem, and set properties
+      set wgt_mem [ create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen:8.3 wgt_mem_${i} ]
+      set_property -dict [ list \
+        CONFIG.Assume_Synchronous_Clk {true} \
+        CONFIG.Byte_Size {8} \
+        CONFIG.Enable_32bit_Address {true} \
+        CONFIG.Enable_B {Use_ENB_Pin} \
+        CONFIG.Memory_Type {True_Dual_Port_RAM} \
+        CONFIG.Read_Width_A $wgt_bus_width \
+        CONFIG.Read_Width_B $wgt_bus_width \
+        CONFIG.Register_PortA_Output_of_Memory_Primitives {false} \
+        CONFIG.Register_PortB_Output_of_Memory_Primitives {false} \
+        CONFIG.Use_Byte_Write_Enable {true} \
+        CONFIG.Use_RSTA_Pin {true} \
+        CONFIG.Use_RSTB_Pin {true} \
+        CONFIG.Write_Depth_A $wgt_mem_depth \
+        CONFIG.Write_Width_A $wgt_bus_width \
+        CONFIG.Write_Width_B $wgt_bus_width \
+      ] $wgt_mem
+      # Create interface connections
+      connect_bd_intf_net -intf_net load_0_wgt_mem_${i}_V_PORTA \
+        [get_bd_intf_pins load_0/wgt_mem_${i}_V_PORTA] \
+        [get_bd_intf_pins $wgt_mem/BRAM_PORTA]
+      connect_bd_intf_net -intf_net compute_0_wgt_mem_${i}_V_PORTA \
+        [get_bd_intf_pins compute_0/wgt_mem_${i}_V_PORTA] \
+        [get_bd_intf_pins $wgt_mem/BRAM_PORTB]
+    }
+  } else {
+      # Create instance: wgt_mem, and set properties
+      set wgt_mem [ create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen:8.3 wgt_mem ]
+      set_property -dict [ list \
+        CONFIG.Assume_Synchronous_Clk {true} \
+        CONFIG.Byte_Size {8} \
+        CONFIG.Enable_32bit_Address {true} \
+        CONFIG.Enable_B {Use_ENB_Pin} \
+        CONFIG.Memory_Type {True_Dual_Port_RAM} \
+        CONFIG.Read_Width_A $wgt_bus_width \
+        CONFIG.Read_Width_B $wgt_bus_width \
+        CONFIG.Register_PortA_Output_of_Memory_Primitives {false} \
+        CONFIG.Register_PortB_Output_of_Memory_Primitives {false} \
+        CONFIG.Use_Byte_Write_Enable {true} \
+        CONFIG.Use_RSTA_Pin {true} \
+        CONFIG.Use_RSTB_Pin {true} \
+        CONFIG.Write_Depth_A $wgt_mem_depth \
+        CONFIG.Write_Width_A $wgt_bus_width \
+        CONFIG.Write_Width_B $wgt_bus_width \
+      ] $wgt_mem
+      # Create interface connections
+      connect_bd_intf_net -intf_net load_0_wgt_mem_V_PORTA \
+        [get_bd_intf_pins load_0/wgt_mem_V_PORTA] \
+        [get_bd_intf_pins $wgt_mem/BRAM_PORTA]
+      connect_bd_intf_net -intf_net compute_0_wgt_mem_V_PORTA \
+        [get_bd_intf_pins compute_0/wgt_mem_V_PORTA] \
+        [get_bd_intf_pins $wgt_mem/BRAM_PORTB]
+  }
+
+  # Create and connect out_mem partitions
+  if {${out_part} > 1} {
+    for {set i 0} {$i < ${out_part}} {incr i} {
+      # Create instance: out_mem, and set properties
+      set out_mem [ create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen:8.3 out_mem_${i} ]
+      set_property -dict [ list \
+        CONFIG.Byte_Size {8} \
+        CONFIG.Enable_32bit_Address {true} \
+        CONFIG.Enable_B {Use_ENB_Pin} \
+        CONFIG.Memory_Type {True_Dual_Port_RAM} \
+        CONFIG.Read_Width_A $out_bus_width \
+        CONFIG.Read_Width_B $out_bus_width \
+        CONFIG.Register_PortA_Output_of_Memory_Primitives {false} \
+        CONFIG.Register_PortB_Output_of_Memory_Primitives {false} \
+        CONFIG.Use_Byte_Write_Enable {true} \
+        CONFIG.Use_RSTA_Pin {true} \
+        CONFIG.Use_RSTB_Pin {true} \
+        CONFIG.Write_Depth_A $out_mem_depth \
+        CONFIG.Write_Width_A $out_bus_width \
+        CONFIG.Write_Width_B $out_bus_width \
+        CONFIG.use_bram_block {BRAM_Controller} \
+      ] $out_mem
+      # Create interface connections
+      connect_bd_intf_net -intf_net compute_0_out_mem_${i}_V_PORTA \
+        [get_bd_intf_pins compute_0/out_mem_${i}_V_PORTA] \
+        [get_bd_intf_pins $out_mem/BRAM_PORTA]
+      connect_bd_intf_net -intf_net store_0_out_mem_${i}_V_PORTA \
+        [get_bd_intf_pins $out_mem/BRAM_PORTB] \
+        [get_bd_intf_pins store_0/out_mem_${i}_V_PORTA]
+    }
+  } else {
+      # Create instance: out_mem, and set properties
+      set out_mem [ create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen:8.3 out_mem ]
+      set_property -dict [ list \
+        CONFIG.Byte_Size {8} \
+        CONFIG.Enable_32bit_Address {true} \
+        CONFIG.Enable_B {Use_ENB_Pin} \
+        CONFIG.Memory_Type {True_Dual_Port_RAM} \
+        CONFIG.Read_Width_A $out_bus_width \
+        CONFIG.Read_Width_B $out_bus_width \
+        CONFIG.Register_PortA_Output_of_Memory_Primitives {false} \
+        CONFIG.Register_PortB_Output_of_Memory_Primitives {false} \
+        CONFIG.Use_Byte_Write_Enable {true} \
+        CONFIG.Use_RSTA_Pin {true} \
+        CONFIG.Use_RSTB_Pin {true} \
+        CONFIG.Write_Depth_A $out_mem_depth \
+        CONFIG.Write_Width_A $out_bus_width \
+        CONFIG.Write_Width_B $out_bus_width \
+        CONFIG.use_bram_block {BRAM_Controller} \
+      ] $out_mem
+      # Create interface connections
+      connect_bd_intf_net -intf_net compute_0_out_mem_V_PORTA \
+        [get_bd_intf_pins compute_0/out_mem_V_PORTA] \
+        [get_bd_intf_pins $out_mem/BRAM_PORTA]
+      connect_bd_intf_net -intf_net store_0_out_mem_V_PORTA \
+        [get_bd_intf_pins $out_mem/BRAM_PORTB] \
+        [get_bd_intf_pins store_0/out_mem_V_PORTA]
+  }
+
+  # Create interface connections
+  connect_bd_intf_net -intf_net axi_interconnect_1_M01_AXI \
+    [get_bd_intf_pins axi_interconnect_1/M01_AXI] \
+    [get_bd_intf_pins fetch_0/s_axi_CONTROL_BUS]
+  connect_bd_intf_net -intf_net axi_interconnect_1_M02_AXI \
+    [get_bd_intf_pins axi_interconnect_1/M02_AXI] \
+    [get_bd_intf_pins load_0/s_axi_CONTROL_BUS]
+  connect_bd_intf_net -intf_net axi_interconnect_1_M03_AXI \
+    [get_bd_intf_pins axi_interconnect_1/M03_AXI] \
+    [get_bd_intf_pins compute_0/s_axi_CONTROL_BUS]
+  connect_bd_intf_net -intf_net axi_interconnect_1_M04_AXI \
+    [get_bd_intf_pins axi_interconnect_1/M04_AXI] \
+    [get_bd_intf_pins store_0/s_axi_CONTROL_BUS]
+  connect_bd_intf_net -intf_net axi_smc_M00_AXI \
+    [get_bd_intf_pins axi_smc/M00_AXI] \
+    [get_bd_intf_pins processing_system7_1/S_AXI_ACP]
+  connect_bd_intf_net -intf_net compute_0_g2l_dep_queue_V \
+    [get_bd_intf_pins compute_0/g2l_dep_queue_V] \
+    [get_bd_intf_pins g2l_queue/S_AXIS]
+  connect_bd_intf_net -intf_net compute_0_g2s_dep_queue_V \
+    [get_bd_intf_pins compute_0/g2s_dep_queue_V] \
+    [get_bd_intf_pins g2s_queue/S_AXIS]
+  connect_bd_intf_net -intf_net compute_0_m_axi_data_port \
+    [get_bd_intf_pins axi_smc/S02_AXI] \
+    [get_bd_intf_pins compute_0/m_axi_data_port]
+  connect_bd_intf_net -intf_net compute_0_m_axi_uop_port \
+    [get_bd_intf_pins axi_smc/S01_AXI] \
+    [get_bd_intf_pins compute_0/m_axi_uop_port]
+  connect_bd_intf_net -intf_net fetch_0_gemm_queue_V_V \
+    [get_bd_intf_pins fetch_0/gemm_queue_V_V] \
+    [get_bd_intf_pins gemm_queue/S_AXIS]
+  connect_bd_intf_net -intf_net fetch_0_l2g_dep_queue_V \
+    [get_bd_intf_pins l2g_queue/S_AXIS] \
+    [get_bd_intf_pins load_0/l2g_dep_queue_V]
+  connect_bd_intf_net -intf_net fetch_0_load_queue_V_V \
+    [get_bd_intf_pins fetch_0/load_queue_V_V] \
+    [get_bd_intf_pins load_queue/S_AXIS]
+  connect_bd_intf_net -intf_net fetch_0_m_axi_ins_port \
+    [get_bd_intf_pins axi_smc/S00_AXI] \
+    [get_bd_intf_pins fetch_0/m_axi_ins_port]
+  connect_bd_intf_net -intf_net fetch_0_store_queue_V_V \
+    [get_bd_intf_pins fetch_0/store_queue_V_V] \
+    [get_bd_intf_pins store_queue/S_AXIS]
+  connect_bd_intf_net -intf_net g2l_queue_M_AXIS \
+    [get_bd_intf_pins g2l_queue/M_AXIS] \
+    [get_bd_intf_pins load_0/g2l_dep_queue_V]
+  connect_bd_intf_net -intf_net g2s_queue_M_AXIS \
+    [get_bd_intf_pins g2s_queue/M_AXIS] \
+    [get_bd_intf_pins store_0/g2s_dep_queue_V]
+  connect_bd_intf_net -intf_net gemm_queue_M_AXIS \
+    [get_bd_intf_pins compute_0/gemm_queue_V_V] \
+    [get_bd_intf_pins gemm_queue/M_AXIS]
+  connect_bd_intf_net -intf_net l2g_queue_M_AXIS \
+    [get_bd_intf_pins compute_0/l2g_dep_queue_V] \
+    [get_bd_intf_pins l2g_queue/M_AXIS]
+  connect_bd_intf_net -intf_net load_0_m_axi_data_port \
+    [get_bd_intf_pins axi_smc/S03_AXI] \
+    [get_bd_intf_pins load_0/m_axi_data_port]
+  connect_bd_intf_net -intf_net load_queue_M_AXIS \
+    [get_bd_intf_pins load_0/load_queue_V_V] \
+    [get_bd_intf_pins load_queue/M_AXIS]
+  connect_bd_intf_net -intf_net processing_system7_1_axi_periph_m00_axi \
+    [get_bd_intf_pins axi_interconnect_1/M00_AXI] \
+    [get_bd_intf_pins axi_timer_1/S_AXI]
+  connect_bd_intf_net -intf_net processing_system7_1_ddr \
+    [get_bd_intf_ports DDR] \
+    [get_bd_intf_pins processing_system7_1/DDR]
+  connect_bd_intf_net -intf_net processing_system7_1_fixed_io \
+    [get_bd_intf_ports FIXED_IO] \
+    [get_bd_intf_pins processing_system7_1/FIXED_IO]
+  connect_bd_intf_net -intf_net processing_system7_1_m_axi_gp0 \
+    [get_bd_intf_pins axi_interconnect_1/S00_AXI] \
+    [get_bd_intf_pins processing_system7_1/M_AXI_GP0]
+  connect_bd_intf_net -intf_net s2g_queue_M_AXIS \
+    [get_bd_intf_pins compute_0/s2g_dep_queue_V] \
+    [get_bd_intf_pins s2g_queue/M_AXIS]
+  connect_bd_intf_net -intf_net store_0_m_axi_data_port \
+    [get_bd_intf_pins axi_smc/S04_AXI] \
+    [get_bd_intf_pins store_0/m_axi_data_port]
+  connect_bd_intf_net -intf_net store_0_s2g_dep_queue_V \
+    [get_bd_intf_pins s2g_queue/S_AXIS] \
+    [get_bd_intf_pins store_0/s2g_dep_queue_V]
+  connect_bd_intf_net -intf_net store_queue_M_AXIS \
+    [get_bd_intf_pins store_0/store_queue_V_V] \
+    [get_bd_intf_pins store_queue/M_AXIS]
+
+  # Create port connections
+  connect_bd_net -net axi_timer_1_interrupt \
+    [get_bd_pins axi_timer_1/interrupt] \
+    [get_bd_pins xlconcat_1/In0]
+  connect_bd_net -net compute_0_interrupt \
+    [get_bd_pins compute_0/interrupt] \
+    [get_bd_pins xlconcat_1/In3]
+  connect_bd_net -net fetch_0_interrupt \
+    [get_bd_pins fetch_0/interrupt] \
+    [get_bd_pins xlconcat_1/In1]
+  connect_bd_net -net load_0_interrupt \
+    [get_bd_pins load_0/interrupt] \
+    [get_bd_pins xlconcat_1/In2]
+  connect_bd_net -net proc_sys_reset_interconnect_aresetn \
+    [get_bd_pins axi_interconnect_1/ARESETN] \
+    [get_bd_pins proc_sys_reset/interconnect_aresetn]
+  connect_bd_net -net proc_sys_reset_peripheral_aresetn \
+    [get_bd_pins axi_interconnect_1/M00_ARESETN] \
+    [get_bd_pins axi_interconnect_1/M01_ARESETN] \
+    [get_bd_pins axi_interconnect_1/M02_ARESETN] \
+    [get_bd_pins axi_interconnect_1/M03_ARESETN] \
+    [get_bd_pins axi_interconnect_1/M04_ARESETN] \
+    [get_bd_pins axi_interconnect_1/S00_ARESETN] \
+    [get_bd_pins axi_smc/aresetn] \
+    [get_bd_pins axi_timer_1/s_axi_aresetn] \
+    [get_bd_pins compute_0/ap_rst_n] \
+    [get_bd_pins fetch_0/ap_rst_n] \
+    [get_bd_pins g2l_queue/s_aresetn] \
+    [get_bd_pins g2s_queue/s_aresetn] \
+    [get_bd_pins gemm_queue/s_aresetn] \
+    [get_bd_pins l2g_queue/s_aresetn] \
+    [get_bd_pins load_0/ap_rst_n] \
+    [get_bd_pins load_queue/s_aresetn] \
+    [get_bd_pins proc_sys_reset/peripheral_aresetn] \
+    [get_bd_pins s2g_queue/s_aresetn] \
+    [get_bd_pins store_0/ap_rst_n] \
+    [get_bd_pins store_queue/s_aresetn]
+  connect_bd_net -net processing_system7_1_FCLK_CLK \
+    [get_bd_pins axi_interconnect_1/ACLK] \
+    [get_bd_pins axi_interconnect_1/M00_ACLK] \
+    [get_bd_pins axi_interconnect_1/M01_ACLK] \
+    [get_bd_pins axi_interconnect_1/M02_ACLK] \
+    [get_bd_pins axi_interconnect_1/M03_ACLK] \
+    [get_bd_pins axi_interconnect_1/M04_ACLK] \
+    [get_bd_pins axi_interconnect_1/S00_ACLK] \
+    [get_bd_pins axi_smc/aclk] \
+    [get_bd_pins axi_timer_1/s_axi_aclk] \
+    [get_bd_pins compute_0/ap_clk] \
+    [get_bd_pins fetch_0/ap_clk] \
+    [get_bd_pins g2l_queue/s_aclk] \
+    [get_bd_pins g2s_queue/s_aclk] \
+    [get_bd_pins gemm_queue/s_aclk] \
+    [get_bd_pins l2g_queue/s_aclk] \
+    [get_bd_pins load_0/ap_clk] \
+    [get_bd_pins load_queue/s_aclk] \
+    [get_bd_pins proc_sys_reset/slowest_sync_clk] \
+    [get_bd_pins processing_system7_1/FCLK_CLK${clk}] \
+    [get_bd_pins processing_system7_1/M_AXI_GP0_ACLK] \
+    [get_bd_pins processing_system7_1/S_AXI_ACP_ACLK] \
+    [get_bd_pins s2g_queue/s_aclk] \
+    [get_bd_pins store_0/ap_clk] \
+    [get_bd_pins store_queue/s_aclk]
+  connect_bd_net -net processing_system7_1_fclk_reset0_n \
+    [get_bd_pins proc_sys_reset/ext_reset_in] \
+    [get_bd_pins processing_system7_1/FCLK_RESET0_N]
+  connect_bd_net -net store_0_interrupt \
+    [get_bd_pins store_0/interrupt] \
+    [get_bd_pins xlconcat_1/In4]
+  connect_bd_net -net xlconcat_1_dout \
+    [get_bd_pins processing_system7_1/IRQ_F2P] \
+    [get_bd_pins xlconcat_1/dout]
+
+  # Create address segments
+  create_bd_addr_seg -range 0x40000000 -offset 0x00000000 \
+    [get_bd_addr_spaces compute_0/Data_m_axi_uop_port] \
+    [get_bd_addr_segs processing_system7_1/S_AXI_ACP/ACP_DDR_LOWOCM] \
+    SEG_processing_system7_1_ACP_DDR_LOWOCM
+  create_bd_addr_seg -range 0x40000000 -offset 0x00000000 \
+    [get_bd_addr_spaces compute_0/Data_m_axi_data_port] \
+    [get_bd_addr_segs processing_system7_1/S_AXI_ACP/ACP_DDR_LOWOCM] \
+    SEG_processing_system7_1_ACP_DDR_LOWOCM
+  create_bd_addr_seg -range 0x00040000 -offset 0xFFFC0000 \
+    [get_bd_addr_spaces compute_0/Data_m_axi_uop_port] \
+    [get_bd_addr_segs processing_system7_1/S_AXI_ACP/ACP_HIGH_OCM] \
+    SEG_processing_system7_1_ACP_HIGH_OCM
+  create_bd_addr_seg -range 0x00040000 -offset 0xFFFC0000 \
+    [get_bd_addr_spaces compute_0/Data_m_axi_data_port] \
+    [get_bd_addr_segs processing_system7_1/S_AXI_ACP/ACP_HIGH_OCM] \
+    SEG_processing_system7_1_ACP_HIGH_OCM
+  create_bd_addr_seg -range 0x00400000 -offset 0xE0000000 \
+    [get_bd_addr_spaces compute_0/Data_m_axi_uop_port] \
+    [get_bd_addr_segs processing_system7_1/S_AXI_ACP/ACP_IOP] \
+    SEG_processing_system7_1_ACP_IOP
+  create_bd_addr_seg -range 0x00400000 -offset 0xE0000000 \
+    [get_bd_addr_spaces compute_0/Data_m_axi_data_port] \
+    [get_bd_addr_segs processing_system7_1/S_AXI_ACP/ACP_IOP] \
+    SEG_processing_system7_1_ACP_IOP
+  create_bd_addr_seg -range 0x40000000 -offset 0x40000000 \
+    [get_bd_addr_spaces compute_0/Data_m_axi_uop_port] \
+    [get_bd_addr_segs processing_system7_1/S_AXI_ACP/ACP_M_AXI_GP0] \
+    SEG_processing_system7_1_ACP_M_AXI_GP0
+  create_bd_addr_seg -range 0x40000000 -offset 0x40000000 \
+    [get_bd_addr_spaces compute_0/Data_m_axi_data_port] \
+    [get_bd_addr_segs processing_system7_1/S_AXI_ACP/ACP_M_AXI_GP0] \
+    SEG_processing_system7_1_ACP_M_AXI_GP0
+  create_bd_addr_seg -range 0x40000000 -offset 0x00000000 \
+    [get_bd_addr_spaces fetch_0/Data_m_axi_ins_port] \
+    [get_bd_addr_segs processing_system7_1/S_AXI_ACP/ACP_DDR_LOWOCM] \
+    SEG_processing_system7_1_ACP_DDR_LOWOCM
+  create_bd_addr_seg -range 0x00040000 -offset 0xFFFC0000 \
+    [get_bd_addr_spaces fetch_0/Data_m_axi_ins_port] \
+    [get_bd_addr_segs processing_system7_1/S_AXI_ACP/ACP_HIGH_OCM] \
+    SEG_processing_system7_1_ACP_HIGH_OCM
+  create_bd_addr_seg -range 0x00400000 -offset 0xE0000000 \
+    [get_bd_addr_spaces fetch_0/Data_m_axi_ins_port] \
+    [get_bd_addr_segs processing_system7_1/S_AXI_ACP/ACP_IOP] \
+    SEG_processing_system7_1_ACP_IOP
+  create_bd_addr_seg -range 0x40000000 -offset 0x40000000 \
+    [get_bd_addr_spaces fetch_0/Data_m_axi_ins_port] \
+    [get_bd_addr_segs processing_system7_1/S_AXI_ACP/ACP_M_AXI_GP0] \
+    SEG_processing_system7_1_ACP_M_AXI_GP0
+  create_bd_addr_seg -range 0x40000000 -offset 0x00000000 \
+    [get_bd_addr_spaces load_0/Data_m_axi_data_port] \
+    [get_bd_addr_segs processing_system7_1/S_AXI_ACP/ACP_DDR_LOWOCM] \
+    SEG_processing_system7_1_ACP_DDR_LOWOCM
+  create_bd_addr_seg -range 0x00040000 -offset 0xFFFC0000 \
+    [get_bd_addr_spaces load_0/Data_m_axi_data_port] \
+    [get_bd_addr_segs processing_system7_1/S_AXI_ACP/ACP_HIGH_OCM] \
+    SEG_processing_system7_1_ACP_HIGH_OCM
+  create_bd_addr_seg -range 0x00400000 -offset 0xE0000000 \
+    [get_bd_addr_spaces load_0/Data_m_axi_data_port] \
+    [get_bd_addr_segs processing_system7_1/S_AXI_ACP/ACP_IOP] \
+    SEG_processing_system7_1_ACP_IOP
+  create_bd_addr_seg -range 0x40000000 -offset 0x40000000 \
+    [get_bd_addr_spaces load_0/Data_m_axi_data_port] \
+    [get_bd_addr_segs processing_system7_1/S_AXI_ACP/ACP_M_AXI_GP0] \
+    SEG_processing_system7_1_ACP_M_AXI_GP0
+  create_bd_addr_seg -range 0x00010000 -offset 0x42800000 \
+    [get_bd_addr_spaces processing_system7_1/Data] \
+    [get_bd_addr_segs axi_timer_1/S_AXI/Reg] SEG_axi_timer_1_Reg
+  create_bd_addr_seg -range 0x00010000 -offset 0x43C10000 \
+    [get_bd_addr_spaces processing_system7_1/Data] \
+    [get_bd_addr_segs compute_0/s_axi_CONTROL_BUS/Reg] SEG_compute_0_Reg
+  create_bd_addr_seg -range 0x00010000 -offset 0x43C00000 \
+    [get_bd_addr_spaces processing_system7_1/Data] \
+    [get_bd_addr_segs fetch_0/s_axi_CONTROL_BUS/Reg] SEG_fetch_0_Reg
+  create_bd_addr_seg -range 0x00010000 -offset 0x43C20000 \
+    [get_bd_addr_spaces processing_system7_1/Data] \
+    [get_bd_addr_segs load_0/s_axi_CONTROL_BUS/Reg] SEG_load_0_Reg
+  create_bd_addr_seg -range 0x00010000 -offset 0x43C30000 \
+    [get_bd_addr_spaces processing_system7_1/Data] \
+    [get_bd_addr_segs store_0/s_axi_CONTROL_BUS/Reg] SEG_store_0_Reg
+  create_bd_addr_seg -range 0x40000000 -offset 0x00000000 \
+    [get_bd_addr_spaces store_0/Data_m_axi_data_port] \
+    [get_bd_addr_segs processing_system7_1/S_AXI_ACP/ACP_DDR_LOWOCM] \
+    SEG_processing_system7_1_ACP_DDR_LOWOCM
+  create_bd_addr_seg -range 0x00040000 -offset 0xFFFC0000 \
+    [get_bd_addr_spaces store_0/Data_m_axi_data_port] \
+    [get_bd_addr_segs processing_system7_1/S_AXI_ACP/ACP_HIGH_OCM] \
+    SEG_processing_system7_1_ACP_HIGH_OCM
+  create_bd_addr_seg -range 0x00400000 -offset 0xE0000000 \
+    [get_bd_addr_spaces store_0/Data_m_axi_data_port] \
+    [get_bd_addr_segs processing_system7_1/S_AXI_ACP/ACP_IOP] \
+    SEG_processing_system7_1_ACP_IOP
+  create_bd_addr_seg -range 0x40000000 -offset 0x40000000 \
+    [get_bd_addr_spaces store_0/Data_m_axi_data_port] \
+    [get_bd_addr_segs processing_system7_1/S_AXI_ACP/ACP_M_AXI_GP0] \
+    SEG_processing_system7_1_ACP_M_AXI_GP0
+
+
+  # Restore current instance
+  current_bd_instance $oldCurInst
+
+  save_bd_design
+}
+# End of create_root_design()
+
+
+##################################################################
+# MAIN FLOW
+##################################################################
+
+create_root_design "" $clock_id $inp_part $wgt_part $out_part $inp_bus_width \
+  $inp_mem_depth $wgt_bus_width $wgt_mem_depth $out_bus_width $out_mem_depth
+
+# Create top-level wrapper file
+make_wrapper -files \
+  [get_files $proj_path/$proj_name.srcs/sources_1/bd/$proj_name/$proj_name.bd] -top
+add_files -norecurse $proj_path/$proj_name.srcs/sources_1/bd/$proj_name/hdl/${proj_name}_wrapper.v
+update_compile_order -fileset sources_1
+update_compile_order -fileset sim_1
+
+# Run bistream generation on 8 threads with performance oriented P&R strategy
+# create_run impl_1 -parent_run synth_1 -flow {Vivado Implementation 2017} \
+#   -strategy "Performance_ExplorePostRoutePhysOpt"
+launch_runs impl_1 -to_step write_bitstream -jobs $num_threads
+wait_on_run impl_1
+
+# Export hardware description file and bitstream files to export/ dir
+file mkdir $proj_path/export
+file copy -force $proj_path/$proj_name.runs/impl_1/${proj_name}_wrapper.sysdef \
+  $proj_path/export/vta.hdf
+file copy -force $proj_path/$proj_name.runs/impl_1/${proj_name}_wrapper.bit \
+  $proj_path/export/vta.bit
+
+exit
diff --git a/vta/hardware/vivado/sim/vta_test.cc b/vta/hardware/vivado/sim/vta_test.cc
new file mode 100644
index 000000000000..858926aaa4db
--- /dev/null
+++ b/vta/hardware/vivado/sim/vta_test.cc
@@ -0,0 +1,62 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file vta_test.cpp
+ * \brief Simulation tests for the VTA design.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <iostream>
+
+#include "vta.h"
+#include "vta_test_lib.h"
+
+int main(void)
+{
+
+#if DEBUG==1
+    printParameters();
+#endif
+
+    // Buffer indexing
+    assert(LOG_ACC_BUFF_DEPTH>=LOG_INP_BUFF_DEPTH);
+    // Micro op bound
+    assert(UOP_GEM_3_1<UOP_WIDTH);
+    assert(UOP_ALU_3_1<UOP_WIDTH);
+    // Instruction alignment checks
+    assert(INSN_MEM_7_1<INSN_MEM_8_0);
+    assert(INSN_GEM_8_1<INSN_GEM_9_0);
+    // Instruction bounds
+    assert(INSN_MEM_E_1<INS_WIDTH);
+    assert(INSN_GEM_E_1<INS_WIDTH);
+    assert(INSN_ALU_F_1<INS_WIDTH);
+
+    int status = 0;
+
+    // Run ALU test (vector-scalar operators)
+    status |= alu_test(ALU_OPCODE_MIN, true, 16, 128, true);
+    status |= alu_test(ALU_OPCODE_MIN, true, 16, 128, false);
+    status |= alu_test(ALU_OPCODE_MAX, true, 16, 128, true);
+    status |= alu_test(ALU_OPCODE_MAX, true, 16, 128, false);
+    status |= alu_test(ALU_OPCODE_ADD, true, 16, 128, true);
+    status |= alu_test(ALU_OPCODE_ADD, true, 16, 128, false);
+    status |= alu_test(ALU_OPCODE_SHR, true, 16, 128, true);
+    status |= alu_test(ALU_OPCODE_SHR, true, 16, 128, false);
+
+    // Run ALU test (vector-vector operators)
+    status |= alu_test(ALU_OPCODE_MIN, false, 16, 128, true);
+    status |= alu_test(ALU_OPCODE_MIN, false, 16, 128, false);
+    status |= alu_test(ALU_OPCODE_MAX, false, 16, 128, true);
+    status |= alu_test(ALU_OPCODE_MAX, false, 16, 128, false);
+    status |= alu_test(ALU_OPCODE_ADD, false, 16, 128, true);
+    status |= alu_test(ALU_OPCODE_ADD, false, 16, 128, false);
+
+    // Run blocked GEMM test
+    status |= blocked_gemm_test(256, 256, BLOCK_OUT*4, true, 2);
+    status |= blocked_gemm_test(256, 256, BLOCK_OUT*4, false, 2);
+    status |= blocked_gemm_test(256, 256, BLOCK_OUT*4, true, 1);
+    status |= blocked_gemm_test(256, 256, BLOCK_OUT*4, false, 1);
+
+    return status;
+
+}
\ No newline at end of file
diff --git a/vta/include/hardware/hls/vta.h b/vta/include/hardware/hls/vta.h
new file mode 100644
index 000000000000..b959d8a23cba
--- /dev/null
+++ b/vta/include/hardware/hls/vta.h
@@ -0,0 +1,137 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file vta.h
+ * \brief Type definitions and prototype for VTA HLS design.
+ */
+#ifndef VTA_MAIN_H_
+#define VTA_MAIN_H_
+
+#include <assert.h>
+#include <ap_axi_sdata.h>
+#include <ap_int.h>
+#include <hls_stream.h>
+
+#include "vta_typedefs.h"
+#include "vta_params.h"
+
+/*!
+* \brief Fetch module.
+*   Reads in \a insn_count instructions via DMA and pushes them to the
+*   appropriate load, gemm or store queue.
+* \param insns Instruction data base address in DRAM. AXI-4 master port.
+* \param insn_count Total instruction count. AXI-lite memory mapped register.
+* \param load_queue Load instruction queue. AXI-stream FIFO.
+* \param gemm_queue GEMM instruction queue. AXI-stream FIFO.
+* \param store_queue Store instruction queue. AXI-stream FIFO.
+*/
+void fetch (
+  uint32_t insn_count,
+  volatile insn_T *insns,
+  hls::stream<insn_T> &load_queue,
+  hls::stream<insn_T> &gemm_queue,
+  hls::stream<insn_T> &store_queue);
+
+/*!
+* \brief Load module.
+*   Reads in load instructions from the load queue, and performs appropriate
+*   DMA load operation to the \a wgt_mem and \a inp_mem SRAM buffers from DRAM.
+*   Updates dependence queues accordingly.
+* \param inputs Input data base address in DRAM. AXI-4 master port.
+* \param weights Weight data base address in DRAM. AXI-4 master port.
+* \param load_queue Load instruction queue. AXI-stream FIFO.
+* \param g2l_dep_queue Dependence queue from GEMM to load stage.
+*   AXI-stream FIFO.
+* \param l2g_dep_queue Dependence queue from load to GEMM stage.
+*   AXI-stream FIFO.
+* \param inp_mem Local input SRAM buffer. Write only single port BRAM.
+* \param wgt_mem Local weight SRAM buffer. Write only single port BRAM.
+*/
+void load (
+  volatile inp_vec_T *inputs,
+  volatile wgt_vec_T *weights,
+  hls::stream<insn_T> &load_queue,
+  hls::stream<bool> &g2l_dep_queue,
+  hls::stream<bool> &l2g_dep_queue,
+  inp_vec_T inp_mem[INP_BUFF_DEPTH][BATCH],
+  wgt_vec_T wgt_mem[WGT_BUFF_DEPTH][BLOCK_OUT]
+  );
+
+/*!
+* \brief Compute module.
+*   Reads in GEMM instructions from the gemm queue, and performs appropriate
+*   GEMM/ALU instructions. Reads in data from the \a wgt_mem and \a inp_mem,
+*   and writes computation results into the \a out_mem. Updates dependence
+*   queues accordingly.
+* \param done Signal that indicates that VLA is done.  AXI-lite memory mapped
+*   register.
+* \param uops Micro-op data base address in DRAM. AXI-4 master port.
+* \param biases Bias data base address in DRAM. AXI-4 master port.
+* \param gemm_queue GEMM instruction queue. AXI-stream FIFO.
+* \param l2g_dep_queue Dependence queue from load to gemm stage.
+*   AXI-stream FIFO.
+* \param s2g_dep_queue Dependence queue from store to gemm stage.
+*   AXI-stream FIFO.
+* \param g2l_dep_queue Dependence queue from gemm to load stage.
+*   AXI-stream FIFO.
+* \param g2s_dep_queue Dependence queue from gemm to store stage.
+*   AXI-stream FIFO.
+* \param inp_mem Local input SRAM buffer. Read only single port BRAM.
+* \param wgt_mem Local weight SRAM buffer. Read only single port BRAM.
+* \param out_mem Local output SRAM buffer. Write only single port BRAM.
+*/
+void compute (
+  volatile uint32_t &done,
+  volatile uop_T *uops,
+  volatile acc_vec_T *biases,
+  hls::stream<insn_T> &gemm_queue,
+  hls::stream<bool> &l2g_dep_queue,
+  hls::stream<bool> &s2g_dep_queue,
+  hls::stream<bool> &g2l_dep_queue,
+  hls::stream<bool> &g2s_dep_queue,
+  out_vec_T inp_mem[INP_BUFF_DEPTH][BATCH],
+  wgt_vec_T wgt_mem[WGT_BUFF_DEPTH][BLOCK_OUT],
+  out_vec_T out_mem[ACC_BUFF_DEPTH][BATCH]
+  );
+
+/*!
+* \brief Store module.
+*   Reads in store instructions from the store queue, and performs appropriate
+*   store instructions from the output buffer in SRAM to DRAM. Updates dependence
+*   queues accordingly.
+* \param outputs Output data base address in DRAM. AXI-4 master port.
+* \param store_queue Store instruction queue. AXI-stream FIFO.
+* \param g2s_dep_queue Dependence queue from gemm to store stage.
+*   AXI-stream FIFO.
+* \param s2g_dep_queue Dependence queue from store to gemm stage.
+*   AXI-stream FIFO.
+* \param out_mem Local output SRAM buffer. Read only single port BRAM.
+*/
+void store (
+  volatile out_vec_T *outputs,
+  hls::stream<insn_T> &store_queue,
+  hls::stream<bool> &g2s_dep_queue,
+  hls::stream<bool> &s2g_dep_queue,
+  out_vec_T out_mem[ACC_BUFF_DEPTH][BATCH]
+  );
+
+/*!
+* \brief VTA wrapper for simulation purpose only.
+*   Orchestrates dataflow execution of the fetch, load, GEMM and store stages.
+* \param insn_count Total instruction count. AXI-lite memory mapped register.
+* \param insns Instruction data base address in DRAM. AXI-4 master port.
+* \param uops Micro-op data base address in DRAM. AXI-4 master port.
+* \param inputs Input data base address in DRAM. AXI-4 master port.
+* \param weights Weight data base address in DRAM. AXI-4 master port.
+* \param biases Bias data base address in DRAM. AXI-4 master port.
+* \param outputs Output data base address in DRAM. AXI-4 master port.
+*/
+void vta (
+  uint32_t insn_count,
+  volatile insn_T *insns,
+  volatile uop_T *uops,
+  volatile inp_vec_T *inputs,
+  volatile wgt_vec_T *weights,
+  volatile acc_vec_T *biases,
+  volatile out_vec_T *outputs);
+
+#endif  // VTA_MAIN_H_
\ No newline at end of file
diff --git a/vta/include/hardware/hls/vta_typedefs.h b/vta/include/hardware/hls/vta_typedefs.h
new file mode 100644
index 000000000000..b2e90e233569
--- /dev/null
+++ b/vta/include/hardware/hls/vta_typedefs.h
@@ -0,0 +1,97 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file vta_typedefs.h
+ * \brief Type definitions for VTA HLS design.
+ */
+#ifndef VTA_TYPEDEFS_H_
+#define VTA_TYPEDEFS_H_
+
+#include <assert.h>
+#include <ap_axi_sdata.h>
+#include <ap_int.h>
+#include <hls_stream.h>
+
+#include "vta_params.h"
+
+/* \typedef uop_T Micro-op datatype*/
+typedef ap_uint<UOP_WIDTH> uop_T;
+
+/* \typedef inp_T Input datatype*/
+typedef ap_int<INP_WIDTH> inp_T;
+
+/* \typedef wgt_T Weight datatype*/
+typedef ap_int<WGT_WIDTH> wgt_T;
+
+/* \typedef out_T Output datatype*/
+typedef ap_int<OUT_WIDTH> out_T;
+
+/* \typedef acc_T Accumulator datatype*/
+typedef ap_int<ACC_WIDTH> acc_T;
+
+/* \typedef mul_T Multiplier output datatype*/
+typedef ap_int<WGT_WIDTH+INP_WIDTH+1> mul_T;
+
+/* \typedef sum_T GEMM accumulator datatype*/
+typedef ap_int<WGT_WIDTH+INP_WIDTH+LOG_BLOCK_IN+1> sum_T;
+
+/* \typedef inp_vec_T Input vector datatype*/
+typedef ap_uint<INP_WIDTH*BLOCK_IN> inp_vec_T;
+
+/* \typedef wgt_vec_T Weight vector datatype*/
+typedef ap_uint<WGT_WIDTH*BLOCK_IN> wgt_vec_T;
+
+/* \typedef acc_vec_T Accumulator vector datatype*/
+typedef ap_uint<ACC_WIDTH*BLOCK_OUT> acc_vec_T;
+
+/* \typedef out_vec_T Output vector datatype*/
+typedef ap_uint<OUT_WIDTH*BLOCK_OUT> out_vec_T;
+
+/* \typedef uop_idx_T Micro-op SRAM index datatype*/
+typedef ap_uint<LOG_UOP_BUFF_DEPTH+1> uop_idx_T;
+
+/* \typedef inp_idx_T Input SRAM index datatype*/
+typedef ap_uint<LOG_INP_BUFF_DEPTH+1> inp_idx_T;
+
+/* \typedef wgt_idx_T Weight SRAM index datatype*/
+typedef ap_uint<LOG_WGT_BUFF_DEPTH+1> wgt_idx_T;
+
+/* \typedef acc_idx_T Accumulator SRAM index datatype*/
+typedef ap_uint<LOG_ACC_BUFF_DEPTH+1> acc_idx_T;
+
+/* \typedef opcode_T Opcode datatype*/
+typedef ap_uint<OPCODE_BIT_WIDTH> opcode_T;
+
+/* \typedef insn_T Instruction datatype*/
+typedef ap_uint<INS_WIDTH> insn_T;
+
+/* \typedef loop_T Loop bound datatype*/
+typedef ap_uint<LOOP_ITER_WIDTH> loop_T;
+
+/* \typedef memop_id_T Memory operation ID datatype*/
+typedef ap_uint<MEMOP_ID_BIT_WIDTH> memop_id_T;
+
+/* \typedef memop_sram_T Memory operation SRAM index datatype*/
+typedef ap_uint<MEMOP_SRAM_ADDR_BIT_WIDTH> memop_sram_T;
+
+/* \typedef memop_dram_T Memory operation DRAM index datatype*/
+typedef ap_uint<MEMOP_DRAM_ADDR_BIT_WIDTH> memop_dram_T;
+
+/* \typedef memop_size_T Memory operation range datatype*/
+typedef ap_uint<MEMOP_SIZE_BIT_WIDTH> memop_size_T;
+
+/* \typedef memop_stride_T Memory operation stride datatype*/
+typedef ap_uint<MEMOP_STRIDE_BIT_WIDTH> memop_stride_T;
+
+/* \typedef memop_pad_T Memory operation pad width datatype*/
+typedef ap_uint<MEMOP_PAD_BIT_WIDTH> memop_pad_T;
+
+/* \typedef aluop_opcode_T ALU operation opcode datatype*/
+typedef ap_uint<ALU_OPCODE_BIT_WIDTH> aluop_opcode_T;
+
+/* \typedef aluop_opcode_T ALU operation immediate datatype*/
+typedef ap_int<ALUOP_IMM_BIT_WIDTH> aluop_imm_T;
+
+/* \typedef aluop_opcode_T ALU operation shift immediate datatype*/
+typedef ap_uint<LOG_ACC_WIDTH> aluop_sh_imm_T;
+
+#endif // VTA_TYPEDEFS_H_
diff --git a/vta/include/vta_params.h b/vta/include/vta_params.h
new file mode 100644
index 000000000000..748e77d9bd27
--- /dev/null
+++ b/vta/include/vta_params.h
@@ -0,0 +1,559 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file vta_defines.h
+ * \brief Preprocessor definitions for VTA HLS design and runtime.
+ */
+#ifndef VTA_DEFINES_H_
+#define VTA_DEFINES_H_
+
+#include <stdint.h>
+
+/*! log2 of instruction data type width */
+#define LOG_INS_WIDTH 7
+/*! Instruction data type width */
+#define INS_WIDTH (1<<LOG_INS_WIDTH)
+/*! log2 of micro op data type width */
+#define LOG_UOP_WIDTH 5
+/*! Micro Op data type width */
+#define UOP_WIDTH (1<<LOG_UOP_WIDTH)
+/*! Weight data type width */
+#define WGT_WIDTH (1<<LOG_WGT_WIDTH)
+/*! Input data type width */
+#define INP_WIDTH (1<<LOG_INP_WIDTH)
+/*! Output data type width */
+#define OUT_WIDTH (1<<LOG_OUT_WIDTH)
+/*! Accumulator data type width */
+#define ACC_WIDTH (1<<LOG_ACC_WIDTH)
+/*! log2 of ALU data type width */
+#define LOG_ALU_WIDTH (LOG_ACC_WIDTH-1)
+/*! ALU data type width */
+#define ALU_WIDTH (1<<LOG_ALU_WIDTH)
+
+/*! Batch size (corresponds to A in (A,B)x(B,C) mat mult)*/
+#define BATCH (1<<LOG_BATCH)
+/*! Blocking factor of inner most loop (corresponds to B in (A,B)x(B,C) mat mult) */
+#define BLOCK_IN (1<<LOG_BLOCK_IN)
+/*! Blocking factor of the outer loop (corresponds to C in (A,B)x(B,C) mat mult) */
+#define BLOCK_OUT (1<<LOG_BLOCK_OUT)
+
+/*! Weight vector width */
+#define WGT_VECTOR_WIDTH (WGT_WIDTH*BLOCK_IN)
+/*! Input vector width */
+#define INP_VECTOR_WIDTH (INP_WIDTH*BLOCK_IN)
+/*! Accumulator vector width */
+#define ACC_VECTOR_WIDTH (ACC_WIDTH*BLOCK_OUT)
+/*! Output vector width */
+#define OUT_VECTOR_WIDTH (OUT_WIDTH*BLOCK_OUT)
+
+/*! On-chip micro-op buffer size in B */
+#define UOP_BUFF_SIZE (1<<LOG_UOP_BUFF_SIZE)
+/*! On-chip weight buffer size in B */
+#define WGT_BUFF_SIZE (1<<LOG_WGT_BUFF_SIZE)
+/*! On-chip activation buffer size in B */
+#define INP_BUFF_SIZE (1<<LOG_INP_BUFF_SIZE)
+/*! On-chip accumulator buffer size in B */
+#define ACC_BUFF_SIZE (1<<LOG_ACC_BUFF_SIZE)
+
+/*! Size of instruction buffer element in B */
+#define INS_ELEM_BYTES (INS_WIDTH/8)
+/*! Size of uop buffer element in B*/
+#define UOP_ELEM_BYTES (UOP_WIDTH/8)
+/*! Size of activation buffer element in B*/
+#define INP_ELEM_BYTES (BATCH*BLOCK_IN*INP_WIDTH/8)
+/*! Size of weight buffer element in B*/
+#define WGT_ELEM_BYTES (BLOCK_OUT*BLOCK_IN*WGT_WIDTH/8)
+/*! Size of accumulator buffer element in B*/
+#define ACC_ELEM_BYTES (BATCH*BLOCK_OUT*ACC_WIDTH/8)
+
+/*! On-chip micro-op buffer depth */
+#define UOP_BUFF_DEPTH (UOP_BUFF_SIZE/UOP_ELEM_BYTES)
+/*! log2 of on-chip micro-op buffer depth */
+#define LOG_UOP_BUFF_DEPTH (LOG_UOP_BUFF_SIZE-LOG_UOP_WIDTH+3)
+// ! \brief On-chip weight buffer depth
+#define WGT_BUFF_DEPTH (WGT_BUFF_SIZE/WGT_ELEM_BYTES)
+/*! log2 of weight micro-op buffer depth */
+#define LOG_WGT_BUFF_DEPTH (LOG_WGT_BUFF_SIZE-LOG_BLOCK_OUT-LOG_BLOCK_IN-LOG_WGT_WIDTH+3)
+/*! On-chip activation buffer depth */
+#define INP_BUFF_DEPTH (INP_BUFF_SIZE/INP_ELEM_BYTES)
+/*! log2 of activation micro-op buffer depth */
+#define LOG_INP_BUFF_DEPTH (LOG_INP_BUFF_SIZE-LOG_BATCH-LOG_BLOCK_IN-LOG_INP_WIDTH+3)
+/*! On-chip accumulator buffer depth */
+#define ACC_BUFF_DEPTH (ACC_BUFF_SIZE/ACC_ELEM_BYTES)
+/*! log2 of on-chip accumulator buffer depth */
+#define LOG_ACC_BUFF_DEPTH (LOG_ACC_BUFF_SIZE-LOG_BATCH-LOG_BLOCK_OUT-LOG_ACC_WIDTH+3)
+
+/*! Instruction opcode field bitwidth */
+#define OPCODE_BIT_WIDTH 3
+/*! ALU opcode field bitwidth */
+#define ALU_OPCODE_BIT_WIDTH 3
+/*! ALU instruction reset mode bitwidth */
+#define ALU_RESET_BIT_WIDTH 2
+
+/*! Opcode: load encoding */
+#define OPCODE_LOAD 0
+/*! Opcode: store encoding */
+#define OPCODE_STORE 1
+/*! Opcode: GEMM encoding */
+#define OPCODE_GEMM 2
+/*! Opcode: finish encoding */
+#define OPCODE_FINISH 3
+/*! Opcode: ALU encoding */
+#define OPCODE_ALU 4
+
+/*! ALU opcode: unary min op */
+#define ALU_OPCODE_MIN 0
+/*! ALU opcode: unary max op */
+#define ALU_OPCODE_MAX 1
+/*! ALU opcode: binary add op */
+#define ALU_OPCODE_ADD 2
+/*! ALU opcode: binary sub op [NOT IMPLEMENTED] */
+#define ALU_OPCODE_SUB 3
+/*! ALU opcode: binary mul op  [NOT IMPLEMENTED] */
+#define ALU_OPCODE_MUL 4
+/*! ALU opcode: shift left by immediate op */
+#define ALU_OPCODE_SHL 5
+/*! ALU opcode: shift right by immediate op [NOT IMPLEMENTED] */
+#define ALU_OPCODE_SHR 6
+
+/*! ALU instruction reset mode: set to min */
+#define ALU_RESET_MIN 3
+/*! ALU instruction reset mode: set to zero */
+#define ALU_RESET_ZERO 0
+/*! ALU instruction reset mode: no reset */
+#define ALU_NO_RESET 2
+/*! ALU instruction reset mode: set to max */
+#define ALU_RESET_MAX 1
+
+/*! Memory type field bitwidth */
+#define MEMOP_ID_BIT_WIDTH 2
+/*! Load/Store Instruction: DRAM address width*/
+#define MEMOP_SRAM_ADDR_BIT_WIDTH 16
+/*! Load/Store Instruction: DRAM address width*/
+#define MEMOP_DRAM_ADDR_BIT_WIDTH 32
+/*! Load/Store Instruction: transfer size width*/
+#define MEMOP_SIZE_BIT_WIDTH 16
+/*! Load/Store Instruction: stride size width*/
+#define MEMOP_STRIDE_BIT_WIDTH 16
+/*! Load/Store Instruction: padding width*/
+#define MEMOP_PAD_BIT_WIDTH 4
+/*! Load/Store Instruction: padding value encoding width*/
+#define MEMOP_PAD_VAL_BIT_WIDTH 2
+/*! ALU Instruction: immediate bitwidth*/
+#define ALUOP_IMM_BIT_WIDTH 16
+/*! GEMM/ALU Instruction: loop max iter bits */
+#define LOOP_ITER_WIDTH 15
+
+/*! Mem ID constant: uop memory */
+#define MEM_ID_UOP 0
+/*! Mem ID constant: weight memory */
+#define MEM_ID_WGT 1
+/*! Mem ID constant: input memory */
+#define MEM_ID_INP 2
+/*! Mem ID constant: accumulator/bias memory */
+#define MEM_ID_ACC 3
+/*! Mem ID constant: output store buffer */
+#define MEM_ID_OUT 4
+
+// Instruction organization layout:
+//
+// LOAD/STORE
+// _____________________________|_type______________|
+// arg 0: opcode                | opcode_T          |
+// arg 1: pop_prev_dependence   | bool              |
+// arg 2: pop_next_dependence   | bool              |
+// arg 3: push_prev_dependence  | bool              |
+// arg 4: push_next_dependence  | bool              |
+// arg 5: memory_type           | memop_id_T        |
+// arg 6: pad_value             | memop_pad_val_T   |
+// arg 7: sram_base             | memop_sram_T      |
+// arg 8: dram_base             | memop_dram_T      |
+// arg 9: y_size                | memop_size_T      |
+// arg a: x_size                | memop_size_T      |
+// arg b: x_stride              | memop_stride_T    |
+// arg c: y_pad_0               | memop_pad_T       |
+// arg d: y_pad_1               | memop_pad_T       |
+// arg e: x_pad_0               | memop_pad_T       |
+// arg f: x_pad_1               | memop_pad_T       |
+//
+// GEMM
+// _____________________________|_type______________|
+// arg 0: opcode                | opcode_T          |
+// arg 1: pop_prev_dependence   | bool              |
+// arg 2: pop_next_dependence   | bool              |
+// arg 3: push_prev_dependence  | bool              |
+// arg 4: push_next_dependence  | bool              |
+// arg 5: uop_bgn               | uop_idx_T         |
+// arg 6: uop_end               | uop_idx_T         |
+// arg 7: iteration count ax0   | loop_T            |
+// arg 8: iteration count ax1   | loop_T            |
+// arg 9: accum idx factor ax0  | acc_idx_T         |
+// arg a: accum idx factor ax1  | acc_idx_T         |
+// arg b: input idx factor ax0  | acc_idx_T         |
+// arg c: input idx factor ax1  | acc_idx_T         |
+// arg d: weight idx factor ax0 | wgt_idx_T         |
+// arg e: weight idx factor ax1 | wgt_idx_T         |
+//
+// ALU
+// _____________________________|_type______________|
+// arg 0: opcode                | opcode_T          |
+// arg 1: pop_prev_dependence   | bool              |
+// arg 2: pop_next_dependence   | bool              |
+// arg 3: push_prev_dependence  | bool              |
+// arg 4: push_next_dependence  | bool              |
+// arg 5: uop_bgn               | uop_idx_T         |
+// arg 6: uop_end               | uop_idx_T         |
+// arg 7: iteration count ax0   | loop_T            |
+// arg 8: iteration count ax1   | loop_T            |
+// arg 9: dst idx factor ax0    | acc_idx_T         |
+// arg a: dst idx factor ax1    | acc_idx_T         |
+// arg b: src idx factor ax0    | acc_idx_T         |
+// arg c: src idx factor ax1    | acc_idx_T         |
+// arg d: alu_opcode            | aluop_opcode_T    |
+// arg e: use_imm               | bool              |
+// arg f: imm                   | alu_imm_T         |
+
+/*! Load/Store instruction start position of the opcode field */
+#define INSN_MEM_0_0 0
+/*! Load/Store instruction end position of the opcode field */
+#define INSN_MEM_0_1 (INSN_MEM_0_0+OPCODE_BIT_WIDTH-1)
+/*! Load/Store instruction position of the pop_prev_dep field */
+#define INSN_MEM_1   (INSN_MEM_0_1+1)
+/*! Load/Store instruction position of the pop_next_dep field */
+#define INSN_MEM_2   (INSN_MEM_1+1)
+/*! Load/Store instruction position of the push_prev_dependence field */
+#define INSN_MEM_3   (INSN_MEM_2+1)
+/*! Load/Store instruction position of the push_next_dependence field */
+#define INSN_MEM_4   (INSN_MEM_3+1)
+/*! Load/Store instruction start position of the memory_type field */
+#define INSN_MEM_5_0 (INSN_MEM_4+1)
+/*! Load/Store instruction end position of the memory_type field */
+#define INSN_MEM_5_1 (INSN_MEM_5_0+MEMOP_ID_BIT_WIDTH-1)
+/*! Load/Store instruction start position of the sram_base field */
+#define INSN_MEM_6_0 (INSN_MEM_5_1+1)
+/*! Load/Store instruction end position of the sram_base field */
+#define INSN_MEM_6_1 (INSN_MEM_6_0+MEMOP_SRAM_ADDR_BIT_WIDTH-1)
+/*! Load/Store instruction start position of the dram_base field */
+#define INSN_MEM_7_0 (INSN_MEM_6_1+1)
+/*! Load/Store instruction end position of the dram_base field */
+#define INSN_MEM_7_1 (INSN_MEM_7_0+MEMOP_DRAM_ADDR_BIT_WIDTH-1)
+/*! Load/Store instruction start position of the y_size field */
+#define INSN_MEM_8_0 64
+/*! Load/Store instruction end position of the y_size field */
+#define INSN_MEM_8_1 (INSN_MEM_8_0+MEMOP_SIZE_BIT_WIDTH-1)
+/*! Load/Store instruction start position of the x_size field */
+#define INSN_MEM_9_0 (INSN_MEM_8_1+1)
+/*! Load/Store instruction start position of the x_size field */
+#define INSN_MEM_9_1 (INSN_MEM_9_0+MEMOP_SIZE_BIT_WIDTH-1)
+/*! Load/Store instruction start position of the x_stride field */
+#define INSN_MEM_A_0 (INSN_MEM_9_1+1)
+/*! Load/Store instruction end position of the x_stride field */
+#define INSN_MEM_A_1 (INSN_MEM_A_0+MEMOP_STRIDE_BIT_WIDTH-1)
+/*! Load/Store instruction start position of the y_pad_0 field */
+#define INSN_MEM_B_0 (INSN_MEM_A_1+1)
+/*! Load/Store instruction start position of the y_pad_0 field */
+#define INSN_MEM_B_1 (INSN_MEM_B_0+MEMOP_PAD_BIT_WIDTH-1)
+/*! Load/Store instruction start position of the y_pad_1 field */
+#define INSN_MEM_C_0 (INSN_MEM_B_1+1)
+/*! Load/Store instruction start position of the y_pad_1 field */
+#define INSN_MEM_C_1 (INSN_MEM_C_0+MEMOP_PAD_BIT_WIDTH-1)
+/*! Load/Store instruction start position of the x_pad_0 field */
+#define INSN_MEM_D_0 (INSN_MEM_C_1+1)
+/*! Load/Store instruction start position of the x_pad_0 field */
+#define INSN_MEM_D_1 (INSN_MEM_D_0+MEMOP_PAD_BIT_WIDTH-1)
+/*! Load/Store instruction start position of the x_pad_1 field */
+#define INSN_MEM_E_0 (INSN_MEM_D_1+1)
+/*! Load/Store instruction start position of the x_pad_1 field */
+#define INSN_MEM_E_1 (INSN_MEM_E_0+MEMOP_PAD_BIT_WIDTH-1)
+
+/*! GEMM instruction start position of the opcode field */
+#define INSN_GEM_0_0 0
+/*! GEMM instruction end position of the opcode field */
+#define INSN_GEM_0_1 (INSN_GEM_0_0+OPCODE_BIT_WIDTH-1)
+/*! GEMM instruction position of the pop_prev_dep field */
+#define INSN_GEM_1   (INSN_GEM_0_1+1)
+/*! GEMM instruction position of the pop_next_dep field */
+#define INSN_GEM_2   (INSN_GEM_1+1)
+/*! GEMM instruction position of the push_prev_dependence field */
+#define INSN_GEM_3   (INSN_GEM_2+1)
+/*! GEMM instruction position of the push_next_dependence field */
+#define INSN_GEM_4   (INSN_GEM_3+1)
+/*! GEMM instruction start position of the uop_bgn field */
+#define INSN_GEM_5_0 (INSN_GEM_4+1)
+/*! GEMM instruction end position of the uop_bgn field */
+#define INSN_GEM_5_1 (INSN_GEM_5_0+LOG_UOP_BUFF_DEPTH-1)
+/*! GEMM instruction start position of the uop_end field */
+#define INSN_GEM_6_0 (INSN_GEM_5_1+1)
+/*! GEMM instruction end position of the uop_end field */
+#define INSN_GEM_6_1 (INSN_GEM_6_0+LOG_UOP_BUFF_DEPTH+1-1)
+/*! GEMM instruction start position of the iter_out field */
+#define INSN_GEM_7_0 (INSN_GEM_6_1+1)
+/*! GEMM instruction end position of the iter_out field */
+#define INSN_GEM_7_1 (INSN_GEM_7_0+LOOP_ITER_WIDTH-1)
+/*! GEMM instruction start position of the iter_in field */
+#define INSN_GEM_8_0 (INSN_GEM_7_1+1)
+/*! GEMM instruction end position of the iter_in field */
+#define INSN_GEM_8_1 (INSN_GEM_8_0+LOOP_ITER_WIDTH-1)
+/*! GEMM instruction start position of the dst_factor_out field */
+#define INSN_GEM_9_0 64
+/*! GEMM instruction end position of the dst_factor_out field */
+#define INSN_GEM_9_1 (INSN_GEM_9_0+LOG_ACC_BUFF_DEPTH-1)
+/*! GEMM instruction start position of the dst_factor_in field */
+#define INSN_GEM_A_0 (INSN_GEM_9_1+1)
+/*! GEMM instruction end position of the dst_factor_in field */
+#define INSN_GEM_A_1 (INSN_GEM_A_0+LOG_ACC_BUFF_DEPTH-1)
+/*! GEMM instruction start position of the src_factor_out field */
+#define INSN_GEM_B_0 (INSN_GEM_A_1+1)
+/*! GEMM instruction end position of the src_factor_out field */
+#define INSN_GEM_B_1 (INSN_GEM_B_0+LOG_ACC_BUFF_DEPTH-1)
+/*! GEMM instruction start position of the src_factor_in field */
+#define INSN_GEM_C_0 (INSN_GEM_B_1+1)
+/*! GEMM instruction end position of the src_factor_in field */
+#define INSN_GEM_C_1 (INSN_GEM_C_0+LOG_ACC_BUFF_DEPTH-1)
+
+/*! GEMM instruction start position of the wgt_factor_out field */
+#define INSN_GEM_D_0 (INSN_GEM_C_1+1)
+/*! GEMM instruction end position of the wgt_factor_out field */
+#define INSN_GEM_D_1 (INSN_GEM_D_0+LOG_WGT_BUFF_DEPTH-1)
+/*! GEMM instruction start position of the wgt_factor_in field */
+#define INSN_GEM_E_0 (INSN_GEM_D_1+1)
+/*! GEMM instruction end position of the wgt_factor_in field */
+#define INSN_GEM_E_1 (INSN_GEM_E_0+LOG_WGT_BUFF_DEPTH-1)
+
+/*! ALU instruction start position of the alu_opcode field */
+#define INSN_ALU_D_0 (INSN_GEM_C_1+1)
+/*! ALU instruction end position of the alu_opcode field */
+#define INSN_ALU_D_1 (INSN_ALU_D_0+ALU_OPCODE_BIT_WIDTH-1)
+/*! ALU instruction position of the use_imm field */
+#define INSN_ALU_E   (INSN_ALU_D_1+1)
+/*! ALU instruction start position of the immediate field */
+#define INSN_ALU_F_0 (INSN_ALU_E+1)
+/*! ALU instruction end position of the immediate field */
+#define INSN_ALU_F_1 (INSN_ALU_F_0+ALUOP_IMM_BIT_WIDTH-1)
+
+/*! GEMM Micro-op position of the reset_out field */
+#define UOP_GEM_0 0
+/*! GEMM Micro-op start position of the acc_idx field */
+#define UOP_GEM_1_0 (UOP_GEM_0+1)
+/*! GEMM Micro-op end position of the acc_idx field */
+#define UOP_GEM_1_1 (UOP_GEM_1_0+LOG_ACC_BUFF_DEPTH-1)
+/*! GEMM Micro-op start position of the inp_idx field */
+#define UOP_GEM_2_0 (UOP_GEM_1_1+1)
+/*! GEMM Micro-op end position of the inp_idx field */
+#define UOP_GEM_2_1 (UOP_GEM_2_0+LOG_ACC_BUFF_DEPTH-1)
+/*! GEMM Micro-op start position of the wgt_idx field */
+#define UOP_GEM_3_0 (UOP_GEM_2_1+1)
+/*! GEMM Micro-op end position of the wgt_idx field */
+#define UOP_GEM_3_1 (UOP_GEM_3_0+LOG_WGT_BUFF_DEPTH-1)
+
+/*! GEMM Micro-op position of the reset_out field */
+#define UOP_ALU_0 0
+/*! GEMM Micro-op start position of the acc_idx field */
+#define UOP_ALU_1_0 (UOP_ALU_0+1)
+/*! GEMM Micro-op end position of the acc_idx field */
+#define UOP_ALU_1_1 (UOP_ALU_1_0+LOG_ACC_BUFF_DEPTH-1)
+/*! GEMM Micro-op start position of the inp_idx field */
+#define UOP_ALU_2_0 (UOP_ALU_1_1+1)
+/*! GEMM Micro-op end position of the inp_idx field */
+#define UOP_ALU_2_1 (UOP_ALU_2_0+LOG_ACC_BUFF_DEPTH-1)
+/*! GEMM Micro-op start position of the wgt_idx field */
+#define UOP_ALU_3_0 (UOP_ALU_2_1+1)
+/*! GEMM Micro-op end position of the wgt_idx field */
+#define UOP_ALU_3_1 (UOP_ALU_3_0+LOG_WGT_BUFF_DEPTH-1)
+
+/*! \brief VTA generic instruction */
+typedef struct {
+  uint64_t word_0     : 64;
+  uint64_t word_1     : 64;
+} VTAGenericInsn;
+
+/*! \brief VTA load/store instruction
+*   Load/store instruction can describe a 2D strided access pattern
+*   with padding, which can be useful to perform spatial padding
+*   on the fly on a tensor on which to perform 2D convolution.
+*   For instance if we try to load a 4x4 spatial tile from a 16x16
+*   matrix with padding of size 1 on all dimensions:
+*   y_size = 4, x_size = 4, x_stride = 16, y_pad_0 = 1, y_pad_1 = 1,
+*   x_pad_0 = 1, x_pad_1 = 1.
+*/
+typedef struct {
+  /*! \brief The instruction opcode */
+  uint64_t opcode         : OPCODE_BIT_WIDTH;
+  /*! \brief Unused in this instruction */
+  uint64_t pop_prev_dep   : 1;
+  /*! \brief Pop dependence token from GEMM stage */
+  uint64_t pop_next_dep   : 1;
+  /*! \brief Unused in this instruction */
+  uint64_t push_prev_dep  : 1;
+  /*! \brief Push dependence token to GEMM stage */
+  uint64_t push_next_dep  : 1;
+  /*! \brief Source/destination SRAM for store/load instruction */
+  uint64_t memory_type    : MEMOP_ID_BIT_WIDTH;
+  /*! \brief SRAM base address (pointer to memory elem type) */
+  uint64_t sram_base      : MEMOP_SRAM_ADDR_BIT_WIDTH;
+  /*! \brief DRAM base address (pointer to memory elem type) */
+  uint64_t dram_base      : MEMOP_DRAM_ADDR_BIT_WIDTH;
+  /*! \brief 2D access pattern: y-size */
+  uint64_t y_size         : MEMOP_SIZE_BIT_WIDTH;
+  /*! \brief 2D access pattern: x-size (in terms of memory elements) */
+  uint64_t x_size         : MEMOP_SIZE_BIT_WIDTH;
+  /*! \brief 2D access pattern: x-stride (in terms of memory elements) */
+  uint64_t x_stride       : MEMOP_STRIDE_BIT_WIDTH;
+  /*! \brief 2D access pattern: start padding along y dimension */
+  uint64_t y_pad_0        : MEMOP_PAD_BIT_WIDTH;
+  /*! \brief 2D access pattern: end padding along y dimension */
+  uint64_t y_pad_1        : MEMOP_PAD_BIT_WIDTH;
+  /*! \brief 2D access pattern: start padding along x dimension */
+  uint64_t x_pad_0        : MEMOP_PAD_BIT_WIDTH;
+  /*! \brief 2D access pattern: end padding along x dimension */
+  uint64_t x_pad_1        : MEMOP_PAD_BIT_WIDTH;
+} VTAMemInsn;
+
+/*! \brief VTA GEMM instruction
+*   GEMM instruction is implemented by executing a sequence of micro-operations
+*   that is read in the local micro-op memory, delimited by \a uop_bgn and
+*   \a uop_end. For improved storage-efficiency, the micro-operations can be
+*   executed in a 2-level nested loop as follows:
+*   \code{.cpp}
+*     for (i = 0; i < iter_out; i++) {
+*       for (j = 0; j < iter_in; j++) {
+*         for (k = uop_bgn; k < uop_end; k++) {
+*           // Read micro op
+*           uop_T uop = uop_mem[k];
+*           // Read in memory indices
+*           acc_idx_T acc_idx = uop.dst_idx;
+*           inp_idx_T inp_idx = uop.inp_idx;
+*           wgt_idx_T wgt_idx = uop.wgt_idx;
+*           // Update those indices with the following affine functions
+*           acc_idx += iter_in * dst_factor_in + iter_out * dst_factor_out;
+*           inp_idx += iter_in * src_factor_in + iter_out * src_factor_out;
+*           wgt_idx += iter_in * wgt_factor_in + iter_out * wgt_factor_out;
+*           // Perform GEMM operation
+*           acc_mem[acc_idx] += dot(inp_mem[inp_idx], wgt[wgt_idx]);
+*         }
+*       }
+*     }
+*   \endcode
+*
+*/
+typedef struct {
+  /*! \brief The instruction opcode */
+  uint64_t opcode         : OPCODE_BIT_WIDTH;
+  /*! \brief Pop dependence token from load stage */
+  uint64_t pop_prev_dep   : 1;
+  /*! \brief Pop dependence token from store stage */
+  uint64_t pop_next_dep   : 1;
+  /*! \brief Push dependence token to load stage */
+  uint64_t push_prev_dep  : 1;
+  /*! \brief Push dependence token to store stage */
+  uint64_t push_next_dep  : 1;
+  /*! \brief Micro-op begin address */
+  uint64_t uop_bgn        : LOG_UOP_BUFF_DEPTH;
+  /*! \brief Micro-op end address */
+  uint64_t uop_end        : LOG_UOP_BUFF_DEPTH+1;
+  /*! \brief Iterations in the outer uop execution loop */
+  uint64_t iter_out       : LOOP_ITER_WIDTH;
+  /*! \brief Iterations in the inner uop execution loop */
+  uint64_t iter_in        : LOOP_ITER_WIDTH;
+  /*! \brief Outer loop accumulator memory index factor */
+  uint64_t dst_factor_out : LOG_ACC_BUFF_DEPTH;
+  /*! \brief Inner loop accumulator memory index factor */
+  uint64_t dst_factor_in  : LOG_ACC_BUFF_DEPTH;
+  /*! \brief Outer loop input memory index factor */
+  uint64_t src_factor_out : LOG_ACC_BUFF_DEPTH;
+  /*! \brief Inner loop input memory index factor */
+  uint64_t src_factor_in  : LOG_ACC_BUFF_DEPTH;
+  /*! \brief Outer loop weight memory index factor */
+  uint64_t wgt_factor_out : LOG_WGT_BUFF_DEPTH;
+  /*! \brief Inner loop weight memory index factor */
+  uint64_t wgt_factor_in  : LOG_WGT_BUFF_DEPTH;
+} VTAGemInsn;
+
+/*! \brief VTA ALU instruction
+*   ALU instruction is implemented by executing a sequence of micro-operations
+*   that is read in the local micro-op memory, delimited by \a uop_bgn and
+*   \a uop_end. For improved storage-efficiency, the micro-operations can be
+*   executed in a 2-level nested loop as follows:
+*   \code{.cpp}
+*     for (i = 0; i < iter_out; i++) {
+*       for (j = 0; j < iter_in; j++) {
+*         for (k = uop_bgn; k < uop_end; k++) {
+*           // Read micro op
+*           uop_T uop = uop_mem[k];
+*           // Read in memory indices
+*           acc_idx_T dst_idx = uop.dst_idx;
+*           inp_idx_T src_idx = uop.inp_idx;
+*           // Update those indices with the following affine functions
+*           dst_idx += iter_in * dst_factor_in + iter_out * dst_factor_out;
+*           src_idx += iter_in * src_factor_in + iter_out * src_factor_out;
+*           // Perform ALU operation
+*           if (use_imm) {
+*             acc_mem[dst_idx] = alu_op(alu_opcode, acc_mem[dst_idx], imm);
+*           } else {
+*             acc_mem[dst_idx] = alu_op(alu_opcode, acc_mem[dst_idx], acc_mem[src_idx]);
+*           }
+*         }
+*       }
+*     }
+*   \endcode
+*
+*/
+typedef struct {
+  /*! \brief The instruction opcode */
+  uint64_t opcode         : OPCODE_BIT_WIDTH;
+  /*! \brief Pop dependence token from load stage */
+  uint64_t pop_prev_dep   : 1;
+  /*! \brief Pop dependence token from store stage */
+  uint64_t pop_next_dep   : 1;
+  /*! \brief Push dependence token to load stage */
+  uint64_t push_prev_dep  : 1;
+  /*! \brief Push dependence token to store stage */
+  uint64_t push_next_dep  : 1;
+  /*! \brief Micro-op begin address */
+  uint64_t uop_bgn        : LOG_UOP_BUFF_DEPTH;
+  /*! \brief Micro-op end address */
+  uint64_t uop_end        : LOG_UOP_BUFF_DEPTH+1;
+  /*! \brief Iterations in the outer uop execution loop */
+  uint64_t iter_out       : LOOP_ITER_WIDTH;
+  /*! \brief Iterations in the inner uop execution loop */
+  uint64_t iter_in        : LOOP_ITER_WIDTH;
+  /*! \brief Outer loop accumulator memory destination index factor */
+  uint64_t dst_factor_out : LOG_ACC_BUFF_DEPTH;
+  /*! \brief Inner loop accumulator memory destination index factor */
+  uint64_t dst_factor_in  : LOG_ACC_BUFF_DEPTH;
+  /*! \brief Outer loop accumulator memory source index factor */
+  uint64_t src_factor_out : LOG_ACC_BUFF_DEPTH;
+  /*! \brief Inner loop accumulator memory source index factor */
+  uint64_t src_factor_in  : LOG_ACC_BUFF_DEPTH;
+  /*! \brief ALU opcode */
+  uint64_t alu_opcode     : ALU_OPCODE_BIT_WIDTH;
+  /*! \brief Use immediate is true */
+  uint64_t use_imm        : 1;
+  /*! \brief Immediate value */
+  uint64_t imm            : ALUOP_IMM_BIT_WIDTH;
+} VTAAluInsn;
+
+/*! \brief VTA ALU instruction converter */
+union VTAInsn {
+  /*! \brief VTA generic instruction */
+  VTAGenericInsn generic;
+  /*! \brief VTA load/store instruction */
+  VTAMemInsn mem;
+  /*! \brief VTA GEMM instruction */
+  VTAGemInsn gemm;
+  /*! \brief VTA ALU instruction */
+  VTAAluInsn alu;
+};
+
+/*! \brief VTA micro-op for GEMM/ALU instruction */
+typedef struct {
+  /*! \brief Initialize acc_mem at index dst_idx to 0*/
+  uint32_t reset_out  : 1;
+  /*! \brief Destination index (indexes accum buffer) */
+  uint32_t dst_idx    : LOG_ACC_BUFF_DEPTH;
+  /*! \brief Source index (indexes input buffer for GEMM or accum buffer for ALU) */
+  uint32_t src_idx    : LOG_ACC_BUFF_DEPTH;
+  /*! \brief Weight index (indexes weight buffer) */
+  uint32_t wgt_idx    : LOG_WGT_BUFF_DEPTH;
+} VTAUop;
+
+#endif // VTA_DEFINES_H_
diff --git a/vta/include/vta_pynq_driver.h b/vta/include/vta_pynq_driver.h
new file mode 100644
index 000000000000..9a07846843fb
--- /dev/null
+++ b/vta/include/vta_pynq_driver.h
@@ -0,0 +1,152 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file vta_pynq_driver.h
+ * \brief VTA driver for Pynq board.
+ */
+
+#ifndef VTA_PYNQ_DRIVER_H_
+#define VTA_PYNQ_DRIVER_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#include <assert.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifdef __arm__
+#include "libxlnk_cma.h"
+#else
+void* cma_alloc(size_t size, int cached);
+void cma_free(void* buf);
+uint32_t cma_get_phy_addr(void* buf);
+void xlnkFlushCache(void* buf, int size);
+void xlnkInvalidateCache(void* buf, int size);
+#endif
+
+/*! \brief VTA command handle */
+typedef void * VTAHandle;
+
+/*! \brief DMA command handle */
+typedef struct {
+  /*! \brief Register map to the AXI DMA control registers*/
+  void *dma_register_map;
+  /*! \brief Transmit data descriptor*/
+  void *mm2s_descriptor_register_map;
+  /*! \brief Receive data descriptor*/
+  void *s2mm_descriptor_register_map;
+  /*! \brief Transmit data descriptor physical address*/
+  uint32_t mm2s_descriptor_phy;
+  /*! \brief Receive data descriptor physical address*/
+  uint32_t s2mm_descriptor_phy;
+  /*! \brief Descriptor size */
+  uint32_t descriptor_size;
+  /*! \brief Transaction count for tx channel */
+  uint32_t mm2s_count;
+  /*! \brief Transaction count for rx channel */
+  uint32_t s2mm_count;
+  /*! \brief Multi-channel mode enable */
+  int multichannel_en;
+} DMAHandle;
+
+/*! \brief partial bitstream status file path */
+#define BS_IS_PARTIAL "/sys/devices/soc0/amba/f8007000.devcfg/is_partial_bitstream"
+/*! \brief bitstream destination file path */
+#define BS_XDEVCFG "/dev/xdevcfg"
+
+/*! \brief Path to /dev/mem */
+#define DEV_MEM_PATH "/dev/mem"
+/*! \brief MMIO driver constant */
+#define MMIO_WORD_LENGTH 4
+/*! \brief MMIO driver constant */
+#define MMIO_WORD_MASK (~(MMIO_WORD_LENGTH - 1))
+
+/*! \brief VTA configuration register address range */
+#define VTA_RANGE 0x100
+/*! \brief VTA configuration register start value */
+#define VTA_START 0x1
+/*! \brief VTA configuration register auto-restart value */
+#define VTA_AUTORESTART 0x81
+/*! \brief VTA configuration register done value */
+#define VTA_DONE 0x1
+
+/*! \brief VTA fetch stage configuration register address
+*   from auto-generated XPAR_FETCH_0_S_AXI_CONTROL_BUS_BASEADDR define
+*   in xparameters.h (under build/vivado/<design name>/export/bsp/ps7_cortexa9_0/include)
+*/
+#define VTA_FETCH_ADDR    0x43C00000
+/*! \brief VTA compute stage configuration register address
+*   from auto-generated XPAR_COMPUTE_0_S_AXI_CONTROL_BUS_BASEADDR define
+*   in xparameters.h (under build/vivado/<design name>/export/bsp/ps7_cortexa9_0/include)
+*/
+#define VTA_COMPUTE_ADDR  0x43C10000
+/*! \brief VTA compute stage configuration register address
+*   from auto-generated XPAR_LOAD_0_S_AXI_CONTROL_BUS_BASEADDR define
+*   in xparameters.h (under build/vivado/<design name>/export/bsp/ps7_cortexa9_0/include)
+*/
+#define VTA_LOAD_ADDR     0x43C20000
+/*! \brief VTA store stage configuration register address
+*   from auto-generated XPAR_STORE_0_S_AXI_CONTROL_BUS_BASEADDR define
+*   in xparameters.h (under build/vivado/<design name>/export/bsp/ps7_cortexa9_0/include)
+*/
+#define VTA_STORE_ADDR    0x43C30000
+
+/*! \brief Memory management constants with libxlnk_cma */
+#define CACHED 1
+/*! \brief Memory management constants with libxlnk_cma */
+#define NOT_CACHED 0
+
+/*! \brief log2 of SDS buffer size limit */
+#define LOG_MAX_XFER 22
+/*! \brief SDS buffer size limit */
+#define MAX_XFER (1<<LOG_MAX_XFER)
+
+/*!
+ * \brief Returns a memory map to FPGA configuration registers.
+ * \param addr The base physical address of the configuration registers.
+ * \param length The size of the memory mapped region in bytes.
+ * \return A pointer to the memory mapped region.
+ */
+void *MapRegister(unsigned addr, size_t length);
+
+/*!
+ * \brief Deletes the configuration register memory map.
+ * \param vta The memory mapped region.
+ * \param length The size of the memory mapped region in bytes.
+ */
+void UnmapRegister(void *vta, size_t length);
+
+/*!
+ * \brief Writes to a memory mapped configuration register.
+ * \param vta_base The handle to the memory mapped configuration registers.
+ * \param offset The offset of the register to write to.
+ * \param val The value to be written to the memory mapped register.
+ */
+void WriteMappedReg(VTAHandle vta_base, unsigned offset, unsigned val);
+
+/*!
+ * \brief Reads from the memory mapped configuration register.
+ * \param vta_base The handle to the memory mapped configuration registers.
+ * \param offset The offset of the register to read from.
+ * \return The value read from the memory mapped register.
+ */
+unsigned ReadMappedReg(VTAHandle vta_base, unsigned offset);
+
+/*!
+ * \brief Programming the bit stream on the FPGA.
+ * \param bitstream The path to the bit stream file.
+ */
+void ProgramVTA(const char* bitstream);
+
+#ifdef __cplusplus
+}
+#endif
+#endif  // VTA_PYNQ_DRIVER_H_
\ No newline at end of file
diff --git a/vta/include/vta_test_lib.h b/vta/include/vta_test_lib.h
new file mode 100644
index 000000000000..b4eb16848d54
--- /dev/null
+++ b/vta/include/vta_test_lib.h
@@ -0,0 +1,300 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file vta_test_lib.cpp
+ * \brief Test library for the VTA design simulation and driver tests.
+ */
+
+#ifndef VTA_TESTLIB_H_
+#define VTA_TESTLIB_H_
+
+#include "vta_params.h"
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef NO_SIM
+
+#include "vta_pynq_driver.h"
+
+typedef uint64_t axi_T;
+typedef uint32_t uop_T;
+typedef int8_t wgt_T;
+typedef int8_t inp_T;
+typedef int32_t acc_T;
+
+uint64_t vta (
+  uint32_t insn_count,
+  VTAGenericInsn *insns,
+  VTAUop *uops,
+  inp_T *inputs,
+  wgt_T *weights,
+  acc_T *biases,
+  inp_T *outputs);
+
+#else //NO_SIM
+
+#include "vta.h"
+#include "vta_typedefs.h"
+
+#endif //NO_SIM
+
+/*!
+* \brief Returns opcode string.
+* \param opcode Opcode parameter (defined in vta_defines.h).
+* \param use_imm Boolean that indicates if the operation uses an immediate value.
+* \return The opcode string.
+*/
+const char* getOpcodeString(int opcode, bool use_imm);
+
+/*!
+* \brief Performs buffer data packing and tiling.
+* \param dst Pointer to the packed, and tiled destination 1D array (flattened).
+* \param src Pointer to the unpacked source 2D array.
+* \param y_size Number of rows.
+* \param x_size Number of columns.
+* \param y_block Inner tiling along row dimension.
+* \param x_block Inner tiling along column dimension.
+*/
+template <typename T, int T_WIDTH>
+void packBuffer(T *dst, T **src, int y_size, int x_size, int y_block, int x_block);
+
+/*!
+* \brief Performs buffer data unpacking.
+* \param dst Pointer to the unpacked destination 2D array.
+* \param src Pointer to the packed, and tiled source 1D array (flattened).
+* \param y_size Number of rows.
+* \param x_size Number of columns.
+* \param y_block Inner tiling along row dimension.
+* \param x_block Inner tiling along column dimension.
+*/
+template <typename T, int T_WIDTH>
+void unpackBuffer(T **dst, T *src, int y_size, int x_size, int y_block, int x_block);
+
+/*!
+* \brief Allocates and initializes a 2D array in the heap.
+* \param rows Number of rows.
+* \param cols Number of columns.
+* \return Pointer to the 2D array.
+*/
+template <typename T, int T_WIDTH>
+T ** allocInit2dArray(int rows, int cols);
+
+/*!
+* \brief Allocates a 2D array in the heap.
+* \param rows Number of rows.
+* \param cols Number of columns.
+* \return Pointer to the 2D array.
+*/
+template <typename T>
+T ** alloc2dArray(int rows, int cols);
+
+/*!
+* \brief Frees a 2D array.
+* \param array Pointer to the 2D array to be freed.
+* \param rows Number of rows.
+* \param cols Number of columns.
+*/
+template <typename T>
+void free2dArray(T **array, int rows, int cols);
+
+/*!
+* \brief Allocates a 3D array in the heap.
+* \param rows Number of rows (dim 0).
+* \param cols Number of columns (dim 1).
+* \param depth Depth of the array (dim 2).
+* \return Pointer to the 3D array.
+*/
+template <typename T>
+T *** alloc3dArray(int rows, int cols, int depth);
+
+/*!
+* \brief Frees a 3D array.
+* \param array Pointer to the 3D array.
+* \param rows Number of rows (dim 0).
+* \param cols Number of columns (dim 1).
+* \param depth Depth of the array (dim 2).
+*/
+template <typename T>
+void free3dArray(T *** array, int rows, int cols, int depth);
+
+/*!
+* \brief Performs memory allocation in a physically contiguous region of memory.
+* \param num_bytes Size of the buffer in bytes.
+* \return Pointer to the allocated buffer.
+*/
+void * allocBuffer(size_t num_bytes);
+
+/*!
+* \brief Frees buffer allocated in a physically contiguous region of memory.
+* \param buffer Pointer to the buffer to free.
+*/
+void freeBuffer(void * buffer);
+
+/*!
+* \brief Returns a VTA reset instruction on a 2D patch of the register file.
+* \param type On-chip memory target.
+* \param sram_offset Offset in SRAM.
+* \param y_size Number of rows to reset (y axis).
+* \param x_size Number of elements per row to reset (x axis).
+* \param x_stride Stride along the x axis.
+* \param pop_prev_dep Pop dependence from previous stage.
+* \param pop_next_dep Pop dependence from next stage.
+* \param push_prev_dep Push dependence to previous stage.
+* \param push_next_dep Push dependence to next stage.
+* \return A VTAGenericInsn for a reset op.
+*/
+VTAGenericInsn reset2DInsn(int type, int sram_offset, int y_size, int x_size, int x_stride,
+  int pop_prev_dep, int pop_next_dep, int push_prev_dep, int push_next_dep);
+
+/*!
+* \brief Returns a VTA 2D load or store instruction.
+* \param opcode Type of operation.
+* \param type On-chip memory target.
+* \param sram_offset Offset in SRAM.
+* \param dram_offset Offset in DRAM.
+* \param y_size Number of rows to load/store (y axis).
+* \param x_size Number of elements per row to load/store (x axis).
+* \param x_stride Stride along the x axis.
+* \param y_pad Padding along the y axis.
+* \param x_pad Padding along the x axis.
+* \param pop_prev_dep Pop dependence from previous stage.
+* \param pop_next_dep Pop dependence from next stage.
+* \param push_prev_dep Push dependence to previous stage.
+* \param push_next_dep Push dependence to next stage.
+* \return A VTAGenericInsn for a 2D load or store op.
+*/
+VTAGenericInsn get2DLoadStoreInsn(int opcode, int type, int sram_offset, int dram_offset,
+  int y_size, int x_size, int x_stride, int y_pad, int x_pad, int pop_prev_dep, int pop_next_dep,
+  int push_prev_dep, int push_next_dep);
+
+/*!
+* \brief Returns a VTA 1D load or store instruction.
+* \param opcode Type of operation.
+* \param type On-chip memory target.
+* \param sram_offset Offset in SRAM.
+* \param dram_offset Offset in DRAM.
+* \param size Number of elements to load/store.
+* \param pop_prev_dep Pop dependence from previous stage.
+* \param pop_next_dep Pop dependence from next stage.
+* \param push_prev_dep Push dependence to previous stage.
+* \param push_next_dep Push dependence to next stage.
+* \return A VTAGenericInsn for a 1D load or store op.
+*/
+VTAGenericInsn get1DLoadStoreInsn(int opcode, int type, int sram_offset, int dram_offset, int size,
+  int pop_prev_dep, int pop_next_dep, int push_prev_dep, int push_next_dep);
+
+/*!
+* \brief Returns a VTA matrix multiplication instruction of size (a, b) x (b, c).
+* \param uop_offset Offset of the micro-op in SRAM.
+* \param batch Batch size (a).
+* \param in_feat Input features (b).
+* \param out_feat Output features (c).
+* \param uop_compression Apply micro-op compression.
+* \param pop_prev_dep Pop dependence from previous stage.
+* \param pop_next_dep Pop dependence from next stage.
+* \param push_prev_dep Push dependence to previous stage.
+* \param push_next_dep Push dependence to next stage.
+* \return A VTAGenericInsn for a GEMM op.
+*/
+VTAGenericInsn getGEMMInsn(int uop_offset, int batch, int in_feat, int out_feat,
+  bool uop_compression, int pop_prev_dep, int pop_next_dep, int push_prev_dep,
+  int push_next_dep);
+
+/*!
+* \brief Returns a VTA ALU instruction for map type operation.
+* \param opcode Opcode of the ALU instruction.
+* \param use_imm Use immediate.
+* \param imm Immediate value (int16).
+* \param vector_size Vector size of the ALU operation size.
+* \param uop_compression Apply micro-op compression.
+* \param pop_prev_dep Pop dependence from previous stage.
+* \param pop_next_dep Pop dependence from next stage.
+* \param push_prev_dep Push dependence to previous stage.
+* \param push_next_dep Push dependence to next stage.
+* \return A VTAGenericInsn for a ALU op.
+*/
+VTAGenericInsn getALUInsn(int opcode, bool use_imm, int imm, int vector_size, bool uop_compression,
+  int pop_prev_dep, int pop_next_dep, int push_prev_dep, int push_next_dep);
+
+/*!
+* \brief Returns a VTA finish instruction.
+* \param pop_prev Pop dependence from previous stage.
+* \param pop_next Pop dependence from next stage.
+* \return A VTAGenericInsn for a finish op.
+*/
+VTAGenericInsn getFinishInsn(bool pop_prev, bool pop_next);
+
+/*!
+* \brief Returns an allocated buffer of VTA micro-ops to implement a copy operation.
+* \param y_size Number of rows to load/store (y axis).
+* \param x_size Number of elements per row to load/store (x axis).
+* \param uop_compression Apply micro-op compression.
+* \return A VTAUop pointer to an allocated micro-op buffer.
+*/
+VTAUop * getCopyUops(int y_size, int x_size, int uop_compression);
+
+/*!
+* \brief Returns an allocated buffer of VTA micro-ops to implement a matrix multiplication
+*   of size (a, b) x (b, c).
+* \param batch Batch size (a).
+* \param in_feat Input features (b).
+* \param out_feat Output features (c).
+* \param uop_compression Apply micro-op compression.
+* \param multi_threaded Generate micro-ops for two virtual execution threads.
+* \return A VTAUop pointer to an allocated micro-op buffer.
+*/
+VTAUop * getGEMMUops(int batch, int in_feat, int out_feat, bool uop_compression,
+  bool multi_threaded);
+
+/*!
+* \brief Returns an allocated buffer of VTA micro-ops to implement a vector-vector map operation.
+* \param vector_size Vector size.
+* \param uop_compression Apply micro-op compression.
+* \return A VTAUop pointer to an allocated micro-op buffer.
+*/
+VTAUop * getMapALUUops(int vector_size, bool uop_compression);
+
+/*!
+* \brief Print out parameters of the VTA design (for debugging purposes).
+*/
+void printParameters();
+
+/*!
+* \brief Print out instruction information (for debugging purposes).
+* \param num_insn Number of instructions.
+* \param insns Pointer to the instruction buffer.
+*/
+void printInstruction(int num_insn, VTAGenericInsn *insns);
+
+/*!
+* \brief Print out micro-op information (for debugging purposes).
+* \param num_insn Number of micro-ops.
+* \param insns Pointer to the micro-op buffer.
+*/
+void printMicroOp(int num_uop, VTAUop *uops);
+
+/*!
+* \brief VTA ALU unit test.
+* \param opcode The ALU opcode.
+* \param use_imm Use immediate.
+* \param batch Batch size.
+* \param vector_size Vector length of the ALU operation.
+* \param uop_compression Apply micro-op compression.
+* \return Number of errors from the test run.
+*/
+int alu_test(int opcode, bool use_imm, int batch, int vector_size, bool uop_compression);
+
+/*!
+* \brief VTA blocked GEMM unit test.
+* \param batch Batch size.
+* \param channels Channel width.
+* \param block Blocking size.
+* \param uop_compression Apply micro-op compression.
+* \return Number of errors from the test run.
+*/
+int blocked_gemm_test(int batch, int channels, int block, bool uop_compression,
+  int virtual_threads);
+
+#endif  // VTA_TESTLIB_H_
\ No newline at end of file
diff --git a/vta/src/driver/pynq/vta_pynq_driver.c b/vta/src/driver/pynq/vta_pynq_driver.c
new file mode 100644
index 000000000000..ca9464cfaf39
--- /dev/null
+++ b/vta/src/driver/pynq/vta_pynq_driver.c
@@ -0,0 +1,77 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file vta_pynq_driver.c
+ * \brief VTA driver for Pynq board.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#include "vta_pynq_driver.h"
+#ifdef __cplusplus
+}
+#endif
+
+void *MapRegister(uint32_t addr, size_t length) {
+
+  // Align the base address with the pages
+  uint32_t virt_base = addr & ~(getpagesize() - 1);
+  // Calculate base address offset w.r.t the base address
+  uint32_t virt_offset = addr - virt_base;
+  // Open file and mmap
+  uint32_t mmap_file = open(DEV_MEM_PATH, O_RDWR|O_SYNC);
+
+  return mmap(NULL, (length+virt_offset), PROT_READ|PROT_WRITE, MAP_SHARED, mmap_file, virt_base);
+}
+
+void UnmapRegister(void *vta, size_t length) {
+  // Unmap memory
+  int status = munmap(vta, length);
+  assert(status==0);
+}
+
+void WriteMappedReg(void* base_addr, uint32_t offset, uint32_t val) {
+  *((volatile uint32_t *) (((char *) base_addr) + offset)) = val;
+}
+
+uint32_t ReadMappedReg(void* base_addr, uint32_t offset) {
+  return *((volatile uint32_t *) (((char *) base_addr) + offset));
+}
+
+void ProgramVTA(const char* bitstream) {
+
+    int elem;
+    FILE *src, *dst, *partial;
+
+    partial = fopen(BS_IS_PARTIAL, "w");
+    if (partial == NULL) {
+        printf("Cannot open partial config file %s\n", BS_IS_PARTIAL);
+        fclose(partial);
+        exit(1);
+    }
+    fputc('0', partial);
+    fclose(partial);
+
+    src = fopen(bitstream, "rb");
+    if (src == NULL) {
+        printf("Cannot open bitstream %s\n", bitstream);
+        exit(1);
+    }
+
+    dst = fopen(BS_XDEVCFG, "wb");
+    if (dst == NULL) {
+        printf("Cannot open device file %s\n", BS_XDEVCFG);
+        fclose(dst);
+        exit(1);
+    }
+
+    elem = fgetc(src);
+    while (elem != EOF) {
+        fputc(elem, dst);
+        elem = fgetc(src);
+    }
+
+    fclose(src);
+    fclose(dst);
+
+}
\ No newline at end of file
diff --git a/vta/src/hardware/hls/vta.cc b/vta/src/hardware/hls/vta.cc
new file mode 100644
index 000000000000..206fb5fefa0d
--- /dev/null
+++ b/vta/src/hardware/hls/vta.cc
@@ -0,0 +1,789 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file vta.cpp
+ * \brief VTA HLS design.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "vta.h"
+
+void fetch (
+  uint32_t insn_count,
+  volatile insn_T *insns,
+  hls::stream<insn_T> &load_queue,
+  hls::stream<insn_T> &gemm_queue,
+  hls::stream<insn_T> &store_queue) {
+#pragma HLS INTERFACE s_axilite port=insn_count bundle=CONTROL_BUS
+#pragma HLS INTERFACE m_axi port=insns offset=slave bundle=ins_port
+#pragma HLS INTERFACE axis port=load_queue
+#pragma HLS INTERFACE axis port=gemm_queue
+#pragma HLS INTERFACE axis port=store_queue
+#pragma HLS INTERFACE s_axilite port=return bundle=CONTROL_BUS
+
+  INSN_DECODE: for (int pc = 0; pc < insn_count; pc ++) {
+#pragma HLS PIPELINE II=1
+    // Read instruction fields
+    insn_T insn = insns[pc];
+    // Do some partial decoding
+    opcode_T opcode = insn.range(INSN_MEM_0_1, INSN_MEM_0_0);
+    memop_id_T memory_type = insn.range(INSN_MEM_5_1, INSN_MEM_5_0);
+    // Push to appropriate instruction queue
+    if (opcode == OPCODE_STORE) {
+      store_queue.write(insn);
+    } else if (opcode == OPCODE_LOAD &&
+               (memory_type == MEM_ID_INP || memory_type == MEM_ID_WGT)) {
+      load_queue.write(insn);
+    } else {
+      gemm_queue.write(insn);
+    }
+  }
+
+}
+
+void load (
+  volatile inp_vec_T *inputs,
+  volatile wgt_vec_T *weights,
+  hls::stream<insn_T> &load_queue,
+  hls::stream<bool> &g2l_dep_queue,
+  hls::stream<bool> &l2g_dep_queue,
+  inp_vec_T inp_mem[INP_BUFF_DEPTH][BATCH],
+  wgt_vec_T wgt_mem[WGT_BUFF_DEPTH][BLOCK_OUT]
+  ) {
+#pragma HLS INTERFACE m_axi port=weights offset=slave bundle=data_port
+#pragma HLS INTERFACE m_axi port=inputs offset=slave bundle=data_port
+#pragma HLS INTERFACE axis port=load_queue
+#pragma HLS INTERFACE axis port=g2l_dep_queue
+#pragma HLS INTERFACE axis port=l2g_dep_queue
+#pragma HLS INTERFACE bram port=wgt_mem
+#pragma HLS INTERFACE bram port=inp_mem
+#pragma HLS INTERFACE s_axilite port=return bundle=CONTROL_BUS
+// #pragma HLS ARRAY_PARTITION variable=inp_mem complete dim=2
+
+  // Pop load instruction
+  insn_T insn = load_queue.read();
+
+  // Decode instruction
+  bool pop_prev_dependence = insn[INSN_MEM_1];
+  bool pop_next_dependence = insn[INSN_MEM_2];
+  bool push_prev_dependence = insn[INSN_MEM_3];
+  bool push_next_dependence = insn[INSN_MEM_4];
+  memop_id_T memory_type = insn.range(INSN_MEM_5_1, INSN_MEM_5_0);
+  memop_sram_T sram_base = insn.range(INSN_MEM_6_1, INSN_MEM_6_0);
+  memop_dram_T dram_base = insn.range(INSN_MEM_7_1, INSN_MEM_7_0);
+  memop_size_T y_size = insn.range(INSN_MEM_8_1, INSN_MEM_8_0);
+  memop_size_T x_size = insn.range(INSN_MEM_9_1, INSN_MEM_9_0);
+  memop_stride_T x_stride = insn.range(INSN_MEM_A_1, INSN_MEM_A_0);
+  memop_pad_T y_pad_0 = insn.range(INSN_MEM_B_1, INSN_MEM_B_0);
+  memop_pad_T y_pad_1 = insn.range(INSN_MEM_C_1, INSN_MEM_C_0);
+  memop_pad_T x_pad_0 = insn.range(INSN_MEM_D_1, INSN_MEM_D_0);
+  memop_pad_T x_pad_1 = insn.range(INSN_MEM_E_1, INSN_MEM_E_0);
+
+  // Pop dependence token if instructed
+  if (pop_next_dependence) {
+    g2l_dep_queue.read();
+  }
+
+  // Initialize indices
+  memop_sram_T sram_idx = sram_base;
+  memop_dram_T dram_idx = dram_base;
+
+  // Pre-compute dimensions, and offsets
+  memop_size_T y_size_total = y_pad_0 + y_size + y_pad_1;
+  memop_size_T x_size_total = x_pad_0 + x_size + x_pad_1;
+  memop_sram_T y_offset = x_size_total * y_pad_0;
+#pragma HLS RESOURCE variable=y_offset core=Mul_LUT
+
+  // Skip padding along y dimension
+  sram_idx += y_offset;
+
+  // Perform data transfer from DRAM
+  for (int y = 0; y < y_size; y ++) {
+#pragma HLS PIPELINE rewind
+    // Skip padding along x dimension
+    sram_idx += x_pad_0;
+    // Perform data transfer
+    if (memory_type == MEM_ID_INP) {
+      memcpy(
+        &inp_mem[sram_idx][0],
+        (const inp_vec_T*) &inputs[dram_idx * BATCH],
+        x_size * INP_ELEM_BYTES
+      );
+    } else {
+      memcpy(
+        &wgt_mem[sram_idx][0],
+        (const wgt_vec_T*) &weights[dram_idx * BLOCK_OUT],
+        x_size * WGT_ELEM_BYTES
+      );
+    }
+    sram_idx += x_size;
+    dram_idx += x_stride;
+    // Skip padding along x dimension
+    sram_idx += x_pad_1;
+  }
+
+  // Reset SRAM index
+  sram_idx = sram_base;
+  // Pad x/y edges with zeros
+  for (int y = 0; y < y_size_total; y ++) {
+    if (y < y_pad_0 || y >= y_pad_0 + y_size) {
+      for (int x = 0; x < x_size_total; x ++) {
+#pragma HLS PIPELINE II=1 rewind
+        if (memory_type == MEM_ID_INP) {
+          for (int i = 0; i < BATCH; i ++) {
+            inp_mem[sram_idx][i] = 0;
+          }
+        } else {
+          for (int i = 0; i < BLOCK_OUT; i ++) {
+            wgt_mem[sram_idx][i] = 0;
+          }
+        }
+        sram_idx ++;
+      }
+    } else {
+      for (int x = 0; x < x_pad_0; x ++) {
+#pragma HLS PIPELINE II=1 rewind
+        if (memory_type == MEM_ID_INP) {
+          for (int i = 0; i < BATCH; i ++) {
+            inp_mem[sram_idx][i] = 0;
+          }
+        } else {
+          for (int i = 0; i < BLOCK_OUT; i ++) {
+            wgt_mem[sram_idx][i] = 0;
+          }
+        }
+        sram_idx ++;
+      }
+      sram_idx += x_size;
+      for (int x = 0; x < x_pad_1; x ++) {
+#pragma HLS PIPELINE II=1 rewind
+        if (memory_type == MEM_ID_INP) {
+          for (int i = 0; i < BATCH; i ++) {
+            inp_mem[sram_idx][i] = 0;
+          }
+        } else {
+          for (int i = 0; i < BLOCK_OUT; i ++) {
+            wgt_mem[sram_idx][i] = 0;
+          }
+        }
+        sram_idx ++;
+      }
+
+    }
+  }
+
+  // Push dependence token if instructed
+  if (push_next_dependence) {
+    l2g_dep_queue.write(1);
+  }
+}
+
+void compute (
+  volatile uint32_t &done,
+  volatile uop_T *uops,
+  volatile acc_vec_T *biases,
+  hls::stream<insn_T> &gemm_queue,
+  hls::stream<bool> &l2g_dep_queue,
+  hls::stream<bool> &s2g_dep_queue,
+  hls::stream<bool> &g2l_dep_queue,
+  hls::stream<bool> &g2s_dep_queue,
+  out_vec_T inp_mem[INP_BUFF_DEPTH][BATCH],
+  wgt_vec_T wgt_mem[WGT_BUFF_DEPTH][BLOCK_OUT],
+  out_vec_T out_mem[ACC_BUFF_DEPTH][BATCH]
+  ) {
+#pragma HLS INTERFACE s_axilite port=done bundle=CONTROL_BUS
+#pragma HLS INTERFACE m_axi port=uops offset=slave bundle=uop_port
+#pragma HLS INTERFACE m_axi port=biases offset=slave bundle=data_port
+#pragma HLS INTERFACE axis port=gemm_queue
+#pragma HLS INTERFACE axis port=l2g_dep_queue
+#pragma HLS INTERFACE axis port=s2g_dep_queue
+#pragma HLS INTERFACE axis port=g2l_dep_queue
+#pragma HLS INTERFACE axis port=g2s_dep_queue
+#pragma HLS INTERFACE bram port=inp_mem
+#pragma HLS INTERFACE bram port=wgt_mem
+#pragma HLS INTERFACE bram port=out_mem
+#pragma HLS INTERFACE s_axilite port=return bundle=CONTROL_BUS
+// #pragma HLS ARRAY_PARTITION variable=inp_mem complete dim=2
+// #pragma HLS ARRAY_PARTITION variable=out_mem complete dim=2
+// This is necessary connect the SRAM to the load module
+#pragma HLS RESOURCE variable=wgt_mem core=RAM_1P
+
+  // Micro-op storage
+  static uop_T uop_mem[UOP_BUFF_DEPTH];
+
+  // Accumulator storage
+  static acc_vec_T acc_mem[ACC_BUFF_DEPTH][BATCH];
+#pragma HLS ARRAY_PARTITION variable=acc_mem complete dim=2
+
+  // Pop GEMM instruction
+  insn_T insn = gemm_queue.read();
+
+  // Decode
+  opcode_T opcode = insn.range(INSN_MEM_0_1, INSN_MEM_0_0);
+  bool pop_prev_dependence = insn[INSN_MEM_1];
+  bool pop_next_dependence = insn[INSN_MEM_2];
+  bool push_prev_dependence = insn[INSN_MEM_3];
+  bool push_next_dependence = insn[INSN_MEM_4];
+
+  // Pop dependence token if instructed
+  if (pop_prev_dependence) {
+    l2g_dep_queue.read();
+  }
+  if (pop_next_dependence) {
+    s2g_dep_queue.read();
+  }
+
+  // Perform action based on opcode
+  if (opcode == OPCODE_FINISH) {
+
+    // Set done flag if we reach a FINISH instruction
+    done = 1;
+
+  } else if (opcode == OPCODE_LOAD || opcode == OPCODE_STORE) {
+
+    // Set done value
+    done = 0;
+
+    // Decode instruction
+    memop_id_T memory_type = insn.range(INSN_MEM_5_1, INSN_MEM_5_0);
+    memop_sram_T sram_base = insn.range(INSN_MEM_6_1, INSN_MEM_6_0);
+    memop_dram_T dram_base = insn.range(INSN_MEM_7_1, INSN_MEM_7_0);
+    memop_size_T y_size = insn.range(INSN_MEM_8_1, INSN_MEM_8_0);
+    memop_size_T x_size = insn.range(INSN_MEM_9_1, INSN_MEM_9_0);
+    memop_stride_T x_stride = insn.range(INSN_MEM_A_1, INSN_MEM_A_0);
+    memop_pad_T y_pad_0 = insn.range(INSN_MEM_B_1, INSN_MEM_B_0);
+    memop_pad_T y_pad_1 = insn.range(INSN_MEM_C_1, INSN_MEM_C_0);
+    memop_pad_T x_pad_0 = insn.range(INSN_MEM_D_1, INSN_MEM_D_0);
+    memop_pad_T x_pad_1 = insn.range(INSN_MEM_E_1, INSN_MEM_E_0);
+
+    // Initialize indices
+    memop_sram_T sram_idx = sram_base;
+    memop_dram_T dram_idx = dram_base;
+
+    // Pre-compute dimensions, and offsets
+    memop_size_T y_size_total = y_pad_0 + y_size + y_pad_1;
+    memop_size_T x_size_total = x_pad_0 + x_size + x_pad_1;
+    memop_sram_T y_offset = x_size_total * y_pad_0;
+#pragma HLS RESOURCE variable=y_offset core=Mul_LUT
+
+    if (memory_type == MEM_ID_UOP) {
+      // Perform data transfer
+      memcpy(
+        &uop_mem[sram_base],
+        (const uop_T*) &uops[dram_base],
+        x_size * sizeof(uop_T)
+      );
+    } else {
+      // Skip vertical padding
+      sram_idx += y_offset;
+      // Perform data transfer from DRAM
+      for (int y = 0; y < y_size; y ++) {
+#pragma HLS PIPELINE rewind
+        // Skip padding along x dimension
+        sram_idx += x_pad_0;
+        // Perform data transfer
+        memcpy(
+            &acc_mem[sram_idx][0],
+            (const acc_vec_T*) &biases[dram_idx * BATCH],
+            x_size*ACC_ELEM_BYTES
+        );
+        sram_idx += x_size;
+        dram_idx += x_stride;
+        // Skip padding along x dimension
+        sram_idx += x_pad_1;
+      }
+    }
+
+  } else if (opcode == OPCODE_GEMM || opcode == OPCODE_ALU) {
+
+    // Set done value
+    done = 0;
+
+    // Decode
+    uop_idx_T uop_bgn = insn.range(INSN_GEM_5_1, INSN_GEM_5_0);
+    uop_idx_T uop_end = insn.range(INSN_GEM_6_1, INSN_GEM_6_0);
+    loop_T iter_out  = insn.range(INSN_GEM_7_1, INSN_GEM_7_0);
+    loop_T iter_in  = insn.range(INSN_GEM_8_1, INSN_GEM_8_0);
+    acc_idx_T dst_factor_out = insn.range(INSN_GEM_9_1, INSN_GEM_9_0);
+    acc_idx_T dst_factor_in = insn.range(INSN_GEM_A_1, INSN_GEM_A_0);
+    inp_idx_T src_factor_out = insn.range(INSN_GEM_B_1, INSN_GEM_B_0);
+    inp_idx_T src_factor_in = insn.range(INSN_GEM_C_1, INSN_GEM_C_0);
+
+    // GEMM-specific fields
+    wgt_idx_T wgt_factor_out = insn.range(INSN_GEM_D_1, INSN_GEM_D_0);
+    wgt_idx_T wgt_factor_in = insn.range(INSN_GEM_E_1, INSN_GEM_E_0);
+
+    // ALU-specific field
+    aluop_opcode_T alu_opcode = insn.range(INSN_ALU_D_1, INSN_ALU_D_0);
+    bool use_imm = insn[INSN_ALU_E];
+    aluop_imm_T imm = insn.range(INSN_ALU_F_1, INSN_ALU_F_0);
+
+    acc_idx_T dst_offset_out = 0;
+    inp_idx_T src_offset_out = 0;
+    wgt_idx_T wgt_offset_out = 0;
+
+    // Outer Loop
+    EXE_OUT_LOOP: for (int it_out = 0; it_out < iter_out; it_out ++) {
+#pragma HLS DEPENDENCE variable=acc_mem inter false
+
+      acc_idx_T dst_offset_in = dst_offset_out;
+      inp_idx_T src_offset_in = src_offset_out;
+      wgt_idx_T wgt_offset_in = wgt_offset_out;
+
+      // Inner Loop
+      EXE_IN_LOOP: for (int it_in = 0; it_in < iter_in; it_in ++) {
+
+        // Perform appropriate computation based on opcode
+        if (opcode == OPCODE_GEMM) {
+
+          // Iterate over micro op
+          READ_GEMM_UOP: for (int upc = uop_bgn; upc < uop_end; upc ++) {
+#pragma HLS PIPELINE II=1 rewind
+
+            // Read micro-op fields
+            uop_T uop = uop_mem[upc];
+
+            // Decode indices
+            bool reset_out = uop[UOP_GEM_0];
+            acc_idx_T dst_idx =
+              uop.range(UOP_GEM_1_1, UOP_GEM_1_0) + dst_offset_in;
+            acc_idx_T src_idx =
+              uop.range(UOP_GEM_2_1, UOP_GEM_2_0) + src_offset_in;
+            wgt_idx_T wgt_idx =
+              uop.range(UOP_GEM_3_1, UOP_GEM_3_0) + wgt_offset_in;
+
+            // Read weight matrix
+            wgt_vec_T w_matrix[BLOCK_OUT];
+            for (int i = 0; i < BLOCK_OUT; i ++) {
+              w_matrix[i] = wgt_mem[wgt_idx][i];
+            }
+            // Read input matrix and accum matrix
+            acc_vec_T o_matrix[BATCH];
+            out_vec_T i_matrix[BATCH];
+            for (int i = 0; i < BATCH; i ++) {
+              o_matrix[i] = acc_mem[dst_idx][i];
+              i_matrix[i] = inp_mem[src_idx][i];
+            }
+            // Result matrices
+            acc_vec_T acc_mem_val[BATCH];
+            out_vec_T st_buf_val[BATCH];
+
+            // Inner GEMM loop
+            for (int i = 0; i < BATCH; i ++) {
+              for (int b = 0; b < BLOCK_OUT; b ++) {
+                // Initialize the accumulator values
+                acc_T accum =
+                  o_matrix[i].range((b + 1) * ACC_WIDTH - 1, b * ACC_WIDTH);
+                // Dot product sum
+                sum_T tmp = 0;
+                // Inner matrix multiplication loop (input channel/feature)
+                for (int k=0; k<BLOCK_IN; k++) {
+                  wgt_T w_elem =
+                    w_matrix[b].range((k + 1) * WGT_WIDTH - 1, k * WGT_WIDTH);
+                  inp_T i_elem =
+                    i_matrix[i].range((k + 1) * INP_WIDTH - 1, k * INP_WIDTH);
+                  mul_T prod = i_elem * w_elem;
+                  tmp += (sum_T) prod;
+                }
+                // Update summation
+                accum += (acc_T) tmp;
+                // Update result vector
+                acc_mem_val[i].range((b + 1) * ACC_WIDTH - 1, b * ACC_WIDTH) =
+                  reset_out ? (acc_T) 0 : accum;
+                st_buf_val[i].range((b + 1) * INP_WIDTH - 1, b * INP_WIDTH) =
+                  (inp_T) accum.range(INP_WIDTH - 1, 0);
+              }
+              // Write to buffers
+              acc_mem[dst_idx][i] = acc_mem_val[i];
+              out_mem[dst_idx][i] = st_buf_val[i];
+            }
+          }
+
+        } else if (opcode == OPCODE_ALU) {
+
+          // Iterate over micro op
+          READ_ALU_UOP: for (int upc = uop_bgn; upc < uop_end; upc ++) {
+
+            // Read micro-op fields
+            uop_T uop = uop_mem[upc];
+
+            // Decode
+            bool reset_out = uop[UOP_ALU_0];
+            acc_idx_T dst_idx =
+              uop.range(UOP_ALU_1_1, UOP_ALU_1_0) + dst_offset_in;
+            acc_idx_T src_idx =
+              uop.range(UOP_ALU_2_1, UOP_ALU_2_0) + src_offset_in;
+
+            // Read input matrix and accum matrix
+            acc_vec_T dst_matrix[BATCH];
+            acc_vec_T src_matrix[BATCH];
+            for (int i = 0; i < BATCH; i ++) {
+#pragma HLS UNROLL complete
+              dst_matrix[i] = acc_mem[dst_idx][i];
+              src_matrix[i] = acc_mem[src_idx][i];
+            }
+
+            // Result matrices
+            acc_vec_T cmp_res[BATCH];
+            acc_vec_T add_res[BATCH];
+            acc_vec_T shr_res[BATCH];
+            out_vec_T short_cmp_res[BATCH];
+            out_vec_T short_add_res[BATCH];
+            out_vec_T short_shr_res[BATCH];
+
+            // Perform ALU op over matrix elements
+            for (int i = 0; i < BATCH; i ++) {
+#pragma HLS PIPELINE II=1 rewind
+              // Results vector
+              acc_vec_T res_vec = 0;
+              for (int b = 0; b < BLOCK_OUT; b ++) {
+                // Read in operands
+                acc_T src_0 =
+                  dst_matrix[i].range((b + 1) * ACC_WIDTH - 1, b * ACC_WIDTH);
+                acc_T src_1 =
+                  use_imm ?
+                    (acc_T) imm :
+                    src_matrix[i].range((b + 1) * ACC_WIDTH - 1, b * ACC_WIDTH);
+                // Compute Min/Max
+                acc_T mix_val =
+                  src_0 < src_1 ?
+                    (alu_opcode == ALU_OPCODE_MIN ? src_0 : src_1) :
+                    (alu_opcode == ALU_OPCODE_MIN ? src_1 : src_0);
+                cmp_res[i].range((b + 1) * ACC_WIDTH - 1, b * ACC_WIDTH) =
+                  mix_val;
+                short_cmp_res[i].range((b + 1) * INP_WIDTH - 1, b * INP_WIDTH) =
+                  (inp_T) mix_val.range(INP_WIDTH - 1, 0);
+                // Compute Sum
+                acc_T add_val =
+                  src_0.range(ACC_WIDTH - 1, 0) + src_1.range(ACC_WIDTH - 1, 0);
+                add_res[i].range((b + 1) * ACC_WIDTH - 1, b * ACC_WIDTH) =
+                  add_val;
+                short_add_res[i].range((b + 1) * INP_WIDTH - 1, b * INP_WIDTH) =
+                  (inp_T) add_val.range(INP_WIDTH - 1, 0);
+                // Compute Shift
+                acc_T shr_val =
+                  src_0 >> (aluop_sh_imm_T) src_1.range(LOG_ACC_WIDTH - 1, 0);
+                shr_res[i].range((b + 1) * ACC_WIDTH - 1, b * ACC_WIDTH) =
+                  shr_val;
+                short_shr_res[i].range((b + 1) * INP_WIDTH - 1, b * INP_WIDTH) =
+                  (inp_T) shr_val.range(INP_WIDTH-1, 0);
+              }
+
+              // Store to accum memory/store buffer
+              if (alu_opcode == ALU_OPCODE_MIN ||
+                  alu_opcode == ALU_OPCODE_MAX) {
+                acc_mem[dst_idx][i] = cmp_res[i];
+                out_mem[dst_idx][i] = short_cmp_res[i];
+              } else if (alu_opcode==ALU_OPCODE_ADD) {
+                acc_mem[dst_idx][i] = add_res[i];
+                out_mem[dst_idx][i] = short_add_res[i];
+              } else if (alu_opcode==ALU_OPCODE_SHR) {
+                acc_mem[dst_idx][i] = shr_res[i];
+                out_mem[dst_idx][i] = short_shr_res[i];
+              }
+            }
+          }
+        }
+
+        // Update offsets
+        dst_offset_in += dst_factor_in;
+        src_offset_in += src_factor_in;
+        wgt_offset_in += wgt_factor_in;
+      }
+
+      // Update offsets
+      dst_offset_out += dst_factor_out;
+      src_offset_out += src_factor_out;
+      wgt_offset_out += wgt_factor_out;
+    }
+  }
+
+  // Push dependence token if instructed
+  if (push_prev_dependence) {
+    g2l_dep_queue.write(1);
+  }
+  if (push_next_dependence) {
+    g2s_dep_queue.write(1);
+  }
+
+}
+
+void store (
+  volatile out_vec_T *outputs,
+  hls::stream<insn_T> &store_queue,
+  hls::stream<bool> &g2s_dep_queue,
+  hls::stream<bool> &s2g_dep_queue,
+  out_vec_T out_mem[ACC_BUFF_DEPTH][BATCH]
+  ) {
+#pragma HLS INTERFACE m_axi port=outputs offset=slave bundle=data_port
+#pragma HLS INTERFACE axis port=store_queue
+#pragma HLS INTERFACE axis port=g2s_dep_queue
+#pragma HLS INTERFACE axis port=s2g_dep_queue
+#pragma HLS INTERFACE bram port=out_mem
+#pragma HLS INTERFACE s_axilite port=return bundle=CONTROL_BUS
+// #pragma HLS ARRAY_PARTITION variable=out_mem complete dim=2
+
+  // Load buffer
+  insn_T insn = store_queue.read();
+
+  // Decode
+  bool pop_prev_dependence = insn[INSN_MEM_1];
+  bool pop_next_dependence = insn[INSN_MEM_2];
+  bool push_prev_dependence = insn[INSN_MEM_3];
+  bool push_next_dependence = insn[INSN_MEM_4];
+  memop_id_T memory_type = insn.range(INSN_MEM_5_1, INSN_MEM_5_0);
+  memop_sram_T sram_base = insn.range(INSN_MEM_6_1, INSN_MEM_6_0);
+  memop_dram_T dram_base = insn.range(INSN_MEM_7_1, INSN_MEM_7_0);
+  memop_size_T y_size = insn.range(INSN_MEM_8_1, INSN_MEM_8_0);
+  memop_size_T x_size = insn.range(INSN_MEM_9_1, INSN_MEM_9_0);
+  memop_stride_T x_stride = insn.range(INSN_MEM_A_1, INSN_MEM_A_0);
+  memop_pad_T y_pad_0 = insn.range(INSN_MEM_B_1, INSN_MEM_B_0);
+  memop_pad_T y_pad_1 = insn.range(INSN_MEM_C_1, INSN_MEM_C_0);
+  memop_pad_T x_pad_0 = insn.range(INSN_MEM_D_1, INSN_MEM_D_0);
+  memop_pad_T x_pad_1 = insn.range(INSN_MEM_E_1, INSN_MEM_E_0);
+
+  // Pop dependence token if instructed
+  if (pop_prev_dependence) {
+    g2s_dep_queue.read();
+  }
+
+  // Initialize indices
+  memop_sram_T sram_idx = sram_base;
+  memop_dram_T dram_idx = dram_base;
+
+  // Skip padding along y dimension
+  memop_sram_T y_offset = (x_pad_0 + x_size + x_pad_1) * y_pad_0;
+  sram_idx += y_offset;
+#pragma HLS RESOURCE variable=y_offset core=Mul_LUT
+
+  // Copy along y dimension
+  for (int y = 0; y < y_size; y ++) {
+#pragma HLS PIPELINE rewind
+    // Skip padding along x dimension
+    sram_idx += x_pad_0;
+    // Perform data transfer
+    memcpy(
+      (out_vec_T *) &outputs[dram_idx*BATCH],
+      (const out_vec_T*) &out_mem[sram_idx][0],
+      x_size * INP_ELEM_BYTES);
+    sram_idx += x_size;
+    dram_idx += x_stride;
+    // Skip padding along x dimension
+    sram_idx += x_pad_1;
+  }
+
+  // Push dependence token if instructed
+  if (push_prev_dependence) {
+    s2g_dep_queue.write(1);
+  }
+}
+
+void vta (
+  uint32_t insn_count,
+  volatile insn_T *insns,
+  volatile uop_T *uops,
+  volatile inp_vec_T *inputs,
+  volatile wgt_vec_T *weights,
+  volatile acc_vec_T *biases,
+  volatile out_vec_T *outputs) {
+#pragma HLS INTERFACE s_axilite port=insn_count bundle=CONTROL_BUS
+#pragma HLS INTERFACE m_axi port=insns offset=slave bundle=ins_port
+#pragma HLS INTERFACE m_axi port=uops offset=slave bundle=uop_port
+#pragma HLS INTERFACE m_axi port=inputs offset=slave bundle=data_port
+#pragma HLS INTERFACE m_axi port=weights offset=slave bundle=data_port
+#pragma HLS INTERFACE m_axi port=biases offset=slave bundle=data_port
+#pragma HLS INTERFACE m_axi port=outputs offset=slave bundle=data_port
+#pragma HLS INTERFACE s_axilite port=return bundle=CONTROL_BUS
+
+  // Instantiate temporary instruction queues (used for peeking)
+  hls::stream<insn_T> tmp_load_queue;
+  hls::stream<insn_T> tmp_gemm_queue;
+  hls::stream<insn_T> tmp_store_queue;
+
+  // Instatiate physical instruction queues
+  hls::stream<insn_T> load_queue;
+  hls::stream<insn_T> gemm_queue;
+  hls::stream<insn_T> store_queue;
+
+  // Dependence queues
+  hls::stream<bool> l2g_dep_queue;
+  hls::stream<bool> s2g_dep_queue;
+  hls::stream<bool> g2l_dep_queue;
+  hls::stream<bool> g2s_dep_queue;
+
+  // Instantiate memories
+  inp_vec_T inp_mem[INP_BUFF_DEPTH][BATCH];
+  wgt_vec_T wgt_mem[WGT_BUFF_DEPTH][BLOCK_OUT];
+  out_vec_T out_mem[ACC_BUFF_DEPTH][BATCH];
+
+  // Push all instructions into the queues
+  fetch(
+    insn_count,
+    insns,
+    tmp_load_queue,
+    tmp_gemm_queue,
+    tmp_store_queue
+  );
+
+  // Global done indicator
+  uint32_t done = 0;
+
+  // Temporary instructions
+  insn_T tmp_load;
+  insn_T tmp_gemv;
+  insn_T tmp_store;
+
+  // Peeking status
+  bool tmp_load_popped = false;
+  bool tmp_gemm_popped = false;
+  bool tmp_store_popped = false;
+  int exit_counter = 0;
+
+  // Main control loop
+  while (true) {
+    // First execute as many load instructions as possible
+    while (!tmp_load_queue.empty() || tmp_load_popped == true) {
+      // Pop the load instruction
+      if (!tmp_load_popped) {
+        tmp_load_queue.read(tmp_load);
+        tmp_load_popped = true;
+      }
+      // Check dependences and invoke the load stage
+      bool pop_next_dependence = tmp_load[INSN_MEM_2];
+      if ((pop_next_dependence && !g2l_dep_queue.empty()) ||
+          !pop_next_dependence) {
+        // Push the instruction in the load queue
+        load_queue.write(tmp_load);
+        tmp_load_popped = false;
+        load(
+          inputs,
+          weights,
+          load_queue,
+          g2l_dep_queue,
+          l2g_dep_queue,
+          inp_mem,
+          wgt_mem
+        );
+      } else {
+        // Execution of load stage pending on completion of other stages, so break here...
+        break;
+      }
+    }
+    // Next execute as many gemm instructions as possible
+    while (!tmp_gemm_queue.empty() || tmp_gemm_popped == true) {
+      // Pop the gemm instruction
+      if (!tmp_gemm_popped) {
+        tmp_gemm_queue.read(tmp_gemv);
+        tmp_gemm_popped = true;
+      }
+      // Check dependences and invoke the load stage
+      bool pop_prev_dependence = tmp_gemv[INSN_MEM_1];
+      bool pop_next_dependence = tmp_gemv[INSN_MEM_2];
+      if (
+        (pop_prev_dependence && !l2g_dep_queue.empty() &&
+         pop_next_dependence && !s2g_dep_queue.empty()) ||
+        (!pop_prev_dependence && pop_next_dependence &&
+         !s2g_dep_queue.empty()) ||
+        (pop_prev_dependence && !l2g_dep_queue.empty() &&
+        !pop_next_dependence) ||
+        (!pop_prev_dependence && !pop_next_dependence)
+      ) {
+        // Push the instruction in the load queue
+        gemm_queue.write(tmp_gemv);
+        tmp_gemm_popped = false;
+        compute(
+          done,
+          uops,
+          biases,
+          gemm_queue,
+          l2g_dep_queue,
+          s2g_dep_queue,
+          g2l_dep_queue,
+          g2s_dep_queue,
+          inp_mem,
+          wgt_mem,
+          out_mem
+        );
+      } else {
+        // Execution of load stage pending on completion of other stages,
+        // so break here...
+        break;
+      }
+    }
+    // Finally execute as many store instructions as possible
+    while (!tmp_store_queue.empty() || tmp_store_popped == true) {
+      // Pop the load instruction
+      if (!tmp_store_popped) {
+        tmp_store_queue.read(tmp_store);
+        tmp_store_popped = true;
+      }
+      // Check dependences and invoke the load stage
+      bool pop_prev_dependence = tmp_store[INSN_MEM_1];
+      if ((pop_prev_dependence && !g2s_dep_queue.empty()) ||
+          !pop_prev_dependence) {
+        // Push the instruction in the load queue
+        store_queue.write(tmp_store);
+        tmp_store_popped = false;
+        store(
+          outputs,
+          store_queue,
+          g2s_dep_queue,
+          s2g_dep_queue,
+          out_mem
+        );
+      } else {
+        // Execution of load stage pending on completion of other stages, so break here...
+        break;
+      }
+    }
+    // Check if we get a signal that we are done
+    if (done) {
+      break;
+    }
+    exit_counter ++;
+    if (exit_counter > 1000) {
+      if (tmp_load_popped) {
+        if (g2l_dep_queue.empty()) {
+          printf("waiting on g2l\n");
+        }
+      }
+      if (tmp_gemm_popped) {
+        if (l2g_dep_queue.empty() && tmp_gemv[INSN_MEM_1]) {
+          printf("waiting on l2g\n");
+        }
+        if (s2g_dep_queue.empty() && tmp_gemv[INSN_MEM_2]) {
+          printf("waiting on s2g\n");
+        }
+      }
+      if (tmp_store_popped) {
+        if (g2s_dep_queue.empty()) {
+          printf("waiting on g2s\n");
+        }
+      }
+      break;
+    }
+  }
+
+  // Ensure that the tokens are empty
+  bool tmp_tok;
+  int l2g_count = 0;
+  int s2g_count = 0;
+  int g2l_count = 0;
+  int g2s_count = 0;
+  while(l2g_dep_queue.read_nb(tmp_tok)) {
+    l2g_count ++;
+  }
+  while(s2g_dep_queue.read_nb(tmp_tok)) {
+    s2g_count ++;
+  }
+  while(g2l_dep_queue.read_nb(tmp_tok)) {
+    g2l_count ++;
+  }
+  while(g2s_dep_queue.read_nb(tmp_tok)) {
+    g2s_count ++;
+  }
+
+  assert(l2g_count == 0 && g2s_count == 0 && g2l_count == 0 && g2s_count == 0);
+}
diff --git a/vta/src/test/vta_test_lib.cc b/vta/src/test/vta_test_lib.cc
new file mode 100644
index 000000000000..df8fce0d87c7
--- /dev/null
+++ b/vta/src/test/vta_test_lib.cc
@@ -0,0 +1,1110 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file vta_test_lib.cpp
+ * \brief Test library for the VTA design simulation and driver tests.
+ */
+
+#include "vta_test_lib.h"
+
+const char* getOpcodeString(int opcode, bool use_imm) {
+  // Returns string name
+  if (opcode == ALU_OPCODE_MIN) {
+    if (use_imm) {
+      return "min imm";
+    } else {
+      return "min";
+    }
+  } else if (opcode == ALU_OPCODE_MAX) {
+    if (use_imm) {
+      return "max imm";
+    } else {
+      return "max";
+    }
+  } else if (opcode == ALU_OPCODE_ADD) {
+    if (use_imm) {
+      return "add imm";
+    } else {
+      return "add";
+    }
+  } else if (opcode == ALU_OPCODE_SUB) {
+    if (use_imm) {
+      return "sub imm";
+    } else {
+      return "sub";
+    }
+  } else if (opcode == ALU_OPCODE_MUL) {
+    if (use_imm) {
+      return "mul imm";
+    } else {
+      return "mul";
+    }
+  } else if (opcode == ALU_OPCODE_SHL) {
+    return "shl";
+  } else if (opcode == ALU_OPCODE_SHR) {
+    return "shr";
+  }
+  return "unknown op";
+}
+
+template <typename T, int T_WIDTH>
+void packBuffer(T *dst, T **src, int y_size, int x_size, int y_block, int x_block) {
+  int buffer_idx = 0;
+  for(int i = 0; i < y_size / y_block; i ++) {
+    for(int j = 0; j < x_size / x_block; j ++) {
+      for(int k = 0; k < y_block; k ++) {
+        if (T_WIDTH < 8) {
+          for (int l = 0; l < x_block; l += 8 / T_WIDTH) {
+            dst[buffer_idx] = 0;
+            for (int m = 0; m < 8 / T_WIDTH; m ++) {
+              dst[buffer_idx] |= (src[i * y_block + k][j * x_block + l + m] &
+                ((1ULL << T_WIDTH) - 1)) << (m * T_WIDTH);
+            }
+            buffer_idx ++;
+          }
+        } else {
+          for (int l = 0; l < x_block; l ++) {
+            dst[buffer_idx++] = src[i * y_block + k][j * x_block + l];
+          }
+        }
+      }
+    }
+  }
+}
+
+template <typename T, int T_WIDTH>
+void unpackBuffer(T **dst, T *src, int y_size, int x_size, int y_block, int x_block) {
+  int buffer_idx = 0;
+  for(int i = 0; i < y_size / y_block; i ++) {
+    for(int j = 0; j < x_size / x_block; j ++) {
+      for(int k = 0; k < y_block; k ++) {
+        if (T_WIDTH < 8) {
+          for (int l = 0; l < x_block; l += 8 / T_WIDTH) {
+            for (int m = 0; m < 8 / T_WIDTH; m ++) {
+              dst[i * y_block + k][j * x_block + l + m] = (src[buffer_idx] >> (m * T_WIDTH))
+                & ((1 << T_WIDTH) - 1);
+            }
+            buffer_idx ++;
+          }
+        } else {
+          for (int l = 0; l < x_block; l ++) {
+            dst[i * y_block + k][j * x_block + l] = src[buffer_idx ++];
+          }
+        }
+      }
+    }
+  }
+}
+
+template <typename T, int T_WIDTH>
+T ** allocInit2dArray(int rows, int cols) {
+  // Allocate
+  T **array = (T **) malloc(sizeof(T *) * rows);
+  for (int i = 0; i < rows; i ++) {
+    array[i] = (T *) malloc(sizeof(T) * cols);
+  }
+  // Init
+  for (int i = 0; i < rows; i ++) {
+    for (int j = 0; j < cols; j ++) {
+      array[i][j] = (T) (rand() % (1LL << (T_WIDTH - 1)) - (1LL << (T_WIDTH - 2)));
+    }
+  }
+  return array;
+}
+
+template <typename T>
+T ** alloc2dArray(int rows, int cols) {
+  T **array = (T **) malloc(sizeof(T *) * rows);
+  for (int i = 0; i < rows; i ++) {
+    array[i] = (T *) malloc(sizeof(T) * cols);
+  }
+  return array;
+}
+
+template <typename T>
+void free2dArray(T **array, int rows, int cols) {
+  for (int i = 0; i < rows; i ++) {
+    free(array[i]);
+  }
+  free(array);
+}
+
+template <typename T>
+T *** alloc3dArray(int rows, int cols, int depth) {
+  T ***array = (T ***) malloc(sizeof(T **) * rows);
+  for (int i = 0; i < rows; i ++) {
+    array[i] = (T **) malloc(sizeof(T *) * cols);
+    for (int j = 0; j < cols; j ++) {
+      array[i][j] = (T*) malloc(sizeof(T) * depth);
+    }
+  }
+  return array;
+}
+
+template <typename T>
+void free3dArray(T *** array, int rows, int cols, int depth) {
+  for (int i = 0; i < rows; i ++) {
+    for (int j = 0; j < cols; j ++) {
+      free(array[i][j]);
+    }
+    free(array[i]);
+  }
+  free(array);
+}
+
+void * allocBuffer(size_t num_bytes) {
+#ifdef NO_SIM
+  return cma_alloc(num_bytes, CACHED);
+#else
+  return malloc(num_bytes);
+#endif
+}
+
+void freeBuffer(void * buffer) {
+#ifdef NO_SIM
+  return cma_free(buffer);
+#else
+  return free(buffer);
+#endif
+}
+
+VTAGenericInsn reset2DInsn(int type, int sram_offset, int y_size, int x_size, int x_stride,
+    int pop_prev_dep, int pop_next_dep, int push_prev_dep, int push_next_dep) {
+  // Converter
+  union VTAInsn converter;
+  // Memory instruction initialization
+  VTAMemInsn insn = {};
+  insn.opcode = OPCODE_LOAD;
+  insn.pop_prev_dep = pop_prev_dep;
+  insn.pop_next_dep = pop_next_dep;
+  insn.push_prev_dep = push_prev_dep;
+  insn.push_next_dep = push_next_dep;
+  insn.memory_type = type;
+  insn.sram_base = sram_offset;
+  insn.dram_base = 0;
+  insn.y_size = 0;
+  insn.x_size = x_size;
+  insn.x_stride = x_stride;
+  insn.y_pad_0 = y_size;
+  insn.y_pad_1 = 0;
+  insn.x_pad_0 = 0;
+  insn.x_pad_1 = 0;
+  converter.mem = insn;
+  return converter.generic;
+}
+
+VTAGenericInsn get2DLoadStoreInsn(int opcode, int type, int sram_offset, int dram_offset,
+    int y_size, int x_size, int x_stride, int y_pad, int x_pad, int pop_prev_dep, int pop_next_dep,
+    int push_prev_dep, int push_next_dep) {
+  // Converter
+  union VTAInsn converter;
+  // Memory instruction initialization
+  VTAMemInsn insn = {};
+  insn.opcode = opcode;
+  insn.pop_prev_dep = pop_prev_dep;
+  insn.pop_next_dep = pop_next_dep;
+  insn.push_prev_dep = push_prev_dep;
+  insn.push_next_dep = push_next_dep;
+  insn.memory_type = type;
+  insn.sram_base = sram_offset;
+  insn.dram_base = dram_offset;
+  insn.y_size = y_size;
+  insn.x_size = x_size;
+  insn.x_stride = x_stride;
+  insn.y_pad_0 = y_pad;
+  insn.y_pad_1 = y_pad;
+  insn.x_pad_0 = x_pad;
+  insn.x_pad_1 = x_pad;
+  converter.mem = insn;
+  return converter.generic;
+}
+
+VTAGenericInsn get1DLoadStoreInsn(int opcode, int type, int sram_offset, int dram_offset, int size,
+    int pop_prev_dep, int pop_next_dep, int push_prev_dep, int push_next_dep) {
+  // Converter
+  union VTAInsn converter;
+  // Memory instruction initialization
+  VTAMemInsn insn = {};
+  insn.opcode = opcode;
+  insn.pop_prev_dep = pop_prev_dep;
+  insn.pop_next_dep = pop_next_dep;
+  insn.push_prev_dep = push_prev_dep;
+  insn.push_next_dep = push_next_dep;
+  insn.memory_type = type;
+  insn.sram_base = sram_offset;
+  insn.dram_base = dram_offset;
+  insn.y_size = 1;
+  insn.x_size = size;
+  insn.x_stride = size;
+  insn.y_pad_0 = 0;
+  insn.y_pad_1 = 0;
+  insn.x_pad_0 = 0;
+  insn.x_pad_1 = 0;
+  converter.mem = insn;
+  return converter.generic;
+}
+
+VTAGenericInsn getGEMMInsn(int uop_offset, int batch, int in_feat, int out_feat,
+    bool uop_compression, int pop_prev_dep, int pop_next_dep, int push_prev_dep,
+    int push_next_dep) {
+  // Converter
+  union VTAInsn converter;
+  // GEVM instruction initialization
+  VTAGemInsn insn;
+  insn.opcode = OPCODE_GEMM;
+  insn.pop_prev_dep = pop_prev_dep;
+  insn.pop_next_dep = pop_next_dep;
+  insn.push_prev_dep = push_prev_dep;
+  insn.push_next_dep = push_next_dep;
+  if (!uop_compression) {
+    insn.uop_bgn = uop_offset;
+    insn.uop_end = uop_offset + batch * in_feat * out_feat;
+    insn.iter_out = 1;
+    insn.iter_in = 1;
+    insn.dst_factor_out = 0;
+    insn.src_factor_out = 0;
+    insn.wgt_factor_out = 0;
+    insn.dst_factor_in = 0;
+    insn.src_factor_in = 0;
+    insn.wgt_factor_in = 0;
+  } else {
+    insn.uop_bgn = uop_offset;
+    insn.uop_end = uop_offset + batch;
+    insn.iter_out = in_feat;
+    insn.iter_in = out_feat;
+    insn.dst_factor_out = 0;
+    insn.src_factor_out = 1;
+    insn.wgt_factor_out = 1;
+    insn.dst_factor_in = 1;
+    insn.src_factor_in = 0;
+    insn.wgt_factor_in = in_feat;
+  }
+  converter.gemm = insn;
+  return converter.generic;
+}
+
+VTAGenericInsn getALUInsn(int opcode, int vector_size, bool use_imm, int imm, bool uop_compression,
+    int pop_prev_dep, int pop_next_dep, int push_prev_dep, int push_next_dep) {
+  // Converter
+  union VTAInsn converter;
+  // Memory instruction initialization
+  VTAAluInsn insn = {};
+  insn.opcode = OPCODE_ALU;
+  insn.pop_prev_dep = pop_prev_dep;
+  insn.pop_next_dep = pop_next_dep;
+  insn.push_prev_dep = push_prev_dep;
+  insn.push_next_dep = push_next_dep;
+  if (!uop_compression) {
+    insn.uop_bgn = 0;
+    insn.uop_end = vector_size;
+    insn.iter_out = 1;
+    insn.iter_in = 1;
+    insn.dst_factor_out = 0;
+    insn.src_factor_out = 0;
+    insn.dst_factor_in = 0;
+    insn.src_factor_in = 0;
+    insn.alu_opcode = opcode;
+    insn.use_imm = use_imm;
+    insn.imm = imm;
+  } else {
+    insn.uop_bgn = 0;
+    insn.uop_end = 1;
+    insn.iter_out = 1;
+    insn.iter_in = vector_size;
+    insn.dst_factor_out = 0;
+    insn.src_factor_out = 0;
+    insn.dst_factor_in = 1;
+    insn.src_factor_in = 1;
+    insn.alu_opcode = opcode;
+    insn.use_imm = use_imm;
+    insn.imm = imm;
+  }
+  converter.alu = insn;
+  return converter.generic;
+}
+
+VTAGenericInsn getFinishInsn(bool pop_prev, bool pop_next) {
+  // Converter
+  union VTAInsn converter;
+  // GEVM instruction initialization
+  VTAGemInsn insn;
+  insn.opcode = OPCODE_FINISH;
+  insn.pop_prev_dep = pop_prev;
+  insn.pop_next_dep = pop_next;
+  insn.push_prev_dep = 0;
+  insn.push_next_dep = 0;
+  insn.uop_bgn = 0;
+  insn.uop_end = 0;
+  insn.iter_out = 0;
+  insn.iter_in = 0;
+  insn.dst_factor_out = 0;
+  insn.src_factor_out = 0;
+  insn.wgt_factor_out = 0;
+  insn.dst_factor_in = 0;
+  insn.src_factor_in = 0;
+  insn.wgt_factor_in = 0;
+  converter.gemm = insn;
+  return converter.generic;
+}
+
+VTAUop * getCopyUops(int y_size, int x_size, int uop_compression) {
+
+  // Derive the total uop size
+  int uop_size = (uop_compression) ? 1 : y_size * x_size;
+
+  // Allocate buffer
+#ifdef NO_SIM
+  VTAUop *uop_buf = (VTAUop *) cma_alloc(sizeof(VTAUop) * uop_size, CACHED);
+#else
+  VTAUop *uop_buf = (VTAUop *) malloc(sizeof(VTAUop) * uop_size);
+#endif
+
+  if (!uop_compression) {
+    int uop_idx = 0;
+    for (int i = 0; i < y_size; i ++) {
+      for (int j = 0; j < x_size; j ++) {
+        uop_buf[uop_idx].reset_out = false;
+        uop_buf[uop_idx].dst_idx = i * x_size + j;
+        uop_buf[uop_idx].src_idx = 0;
+        uop_buf[uop_idx].wgt_idx = 0;
+        uop_idx++;
+      }
+    }
+  } else {
+    uop_buf[0].reset_out = false;
+    uop_buf[0].dst_idx = 1;
+    uop_buf[0].src_idx = 0;
+    uop_buf[0].wgt_idx = 0;
+  }
+
+  return uop_buf;
+}
+
+VTAUop * getGEMMUops(int batch, int in_feat, int out_feat, bool uop_compression,
+    bool multi_threaded) {
+
+  // Derive the total uop size
+  int uop_size = (uop_compression) ? batch : batch * in_feat * out_feat;
+  if (multi_threaded) uop_size *= 2;
+
+  // Allocate buffer
+#ifdef NO_SIM
+  VTAUop *uop_buf = (VTAUop *) cma_alloc(sizeof(VTAUop) * uop_size, CACHED);
+#else
+  VTAUop *uop_buf = (VTAUop *) malloc(sizeof(VTAUop) * uop_size);
+#endif
+
+  if (!uop_compression) {
+    int uop_idx = 0;
+    for (int i = 0; i < batch; i ++) {
+      for (int j = 0; j < in_feat; j ++) {
+        for (int k = 0; k < out_feat; k ++) {
+          uop_buf[uop_idx].reset_out = false;
+          uop_buf[uop_idx].dst_idx = i * out_feat + k;
+          uop_buf[uop_idx].src_idx = i * in_feat + j;
+          uop_buf[uop_idx].wgt_idx = k * in_feat + j;
+          uop_idx++;
+        }
+      }
+    }
+  } else {
+    for (int i = 0; i < batch; i ++) {
+      uop_buf[i].reset_out = false;
+      uop_buf[i].dst_idx = i * out_feat;
+      uop_buf[i].src_idx = i * in_feat;
+      uop_buf[i].wgt_idx = 0;
+    }
+  }
+
+  if (multi_threaded) {
+    if (!uop_compression) {
+      int uop_idx = uop_size / 2;
+      for (int i = 0; i < batch; i ++) {
+        for (int j = 0; j < in_feat; j ++) {
+          for (int k = 0; k < out_feat; k ++) {
+            uop_buf[uop_idx].reset_out = false;
+            uop_buf[uop_idx].dst_idx = i * out_feat + k;
+            uop_buf[uop_idx].src_idx = batch * in_feat + i * in_feat + j;
+            uop_buf[uop_idx].wgt_idx = out_feat * in_feat + k * in_feat + j;
+            uop_idx++;
+          }
+        }
+      }
+    } else {
+      for (int i = 0; i < batch; i ++) {
+        uop_buf[batch+i].reset_out = false;
+        uop_buf[batch+i].dst_idx = i * out_feat;
+        uop_buf[batch+i].src_idx = batch * in_feat + i * in_feat;
+        uop_buf[batch+i].wgt_idx = out_feat * in_feat;
+      }
+    }
+  }
+
+  return uop_buf;
+}
+
+VTAUop * getMapALUUops(int vector_size, bool uop_compression) {
+
+  // Derive the total uop size
+  int uop_size = (uop_compression) ? 1 : vector_size;
+
+  // Allocate buffer
+#ifdef NO_SIM
+  VTAUop *uop_buf = (VTAUop *) cma_alloc(sizeof(VTAUop) * uop_size, CACHED);
+#else
+  VTAUop *uop_buf = (VTAUop *) malloc(sizeof(VTAUop) * uop_size);
+#endif
+
+  if (!uop_compression) {
+    for (int i = 0; i < vector_size; i ++) {
+      uop_buf[i].reset_out = 0;
+      uop_buf[i].dst_idx = i;
+      uop_buf[i].src_idx = vector_size + i;
+    }
+  } else {
+    uop_buf[0].reset_out = 0;
+    uop_buf[0].dst_idx = 0;
+    uop_buf[0].src_idx = vector_size;
+  }
+
+  return uop_buf;
+}
+
+void printParameters() {
+  // Some debugging code
+  printf("Size of VTAInsn: %d\n", sizeof(VTAGenericInsn));
+  printf("Size of VTAUop: %d\n", sizeof(VTAUop));
+  printf("UOP_BUFF_DEPTH: %d\n", UOP_BUFF_DEPTH);
+  printf("LOG_UOP_BUFF_DEPTH: %d\n", LOG_UOP_BUFF_DEPTH);
+  printf("WGT_BUFF_DEPTH: %d\n", WGT_BUFF_DEPTH);
+  printf("LOG_WGT_BUFF_DEPTH: %d\n", LOG_WGT_BUFF_DEPTH);
+  printf("INP_BUFF_DEPTH: %d\n", INP_BUFF_DEPTH);
+  printf("LOG_INP_BUFF_DEPTH: %d\n", LOG_INP_BUFF_DEPTH);
+  printf("ACC_BUFF_DEPTH: %d\n", ACC_BUFF_DEPTH);
+  printf("LOG_ACC_BUFF_DEPTH: %d\n", LOG_ACC_BUFF_DEPTH);
+  printf("WGT_WORDS: %d\n", WGT_BUFF_DEPTH*BLOCK_IN*BLOCK_OUT);
+  printf("INP_WORDS: %d\n", INP_BUFF_DEPTH*BLOCK_IN);
+  printf("ACC_WORDS: %d\n", ACC_BUFF_DEPTH*BLOCK_OUT);
+  printf("INS_ELEM_BYTES: %d\n", INS_ELEM_BYTES);
+  printf("UOP_ELEM_BYTES: %d\n", UOP_ELEM_BYTES);
+  printf("INP_ELEM_BYTES: %d\n", INP_ELEM_BYTES);
+  printf("WGT_ELEM_BYTES: %d\n", WGT_ELEM_BYTES);
+  printf("ACC_ELEM_BYTES: %d\n", ACC_ELEM_BYTES);
+  printf("BLOCK_IN: %d\n", BLOCK_IN);
+  printf("BLOCK_OUT: %d\n", BLOCK_OUT);
+  printf("INSN_MEM_0 [%d-%d]\n", INSN_MEM_0_0, INSN_MEM_0_1);
+  printf("INSN_MEM_1 [%d]\n", INSN_MEM_1);
+  printf("INSN_MEM_2 [%d]\n", INSN_MEM_2);
+  printf("INSN_MEM_3 [%d]\n", INSN_MEM_3);
+  printf("INSN_MEM_4 [%d]\n", INSN_MEM_4);
+  printf("INSN_MEM_5 [%d-%d]\n", INSN_MEM_5_0, INSN_MEM_5_1);
+  printf("INSN_MEM_6 [%d-%d]\n", INSN_MEM_6_0, INSN_MEM_6_1);
+  printf("INSN_MEM_7 [%d-%d]\n", INSN_MEM_7_0, INSN_MEM_7_1);
+  printf("INSN_MEM_8 [%d-%d]\n", INSN_MEM_8_0, INSN_MEM_8_1);
+  printf("INSN_MEM_9 [%d-%d]\n", INSN_MEM_9_0, INSN_MEM_9_1);
+  printf("INSN_MEM_A [%d-%d]\n", INSN_MEM_A_0, INSN_MEM_A_1);
+  printf("INSN_MEM_B [%d-%d]\n", INSN_MEM_B_0, INSN_MEM_B_1);
+  printf("INSN_MEM_C [%d-%d]\n", INSN_MEM_C_0, INSN_MEM_C_1);
+  printf("INSN_MEM_D [%d-%d]\n", INSN_MEM_D_0, INSN_MEM_D_1);
+  printf("INSN_MEM_E [%d-%d]\n", INSN_MEM_E_0, INSN_MEM_E_1);
+  printf("INSN_GEM_0 [%d-%d]\n", INSN_GEM_0_0, INSN_GEM_0_1);
+  printf("INSN_GEM_1 [%d]\n", INSN_GEM_1);
+  printf("INSN_GEM_2 [%d]\n", INSN_GEM_2);
+  printf("INSN_GEM_3 [%d]\n", INSN_GEM_3);
+  printf("INSN_GEM_4 [%d]\n", INSN_GEM_4);
+  printf("INSN_GEM_5 [%d-%d]\n", INSN_GEM_5_0, INSN_GEM_5_1);
+  printf("INSN_GEM_6 [%d-%d]\n", INSN_GEM_6_0, INSN_GEM_6_1);
+  printf("INSN_GEM_7 [%d-%d]\n", INSN_GEM_7_0, INSN_GEM_7_1);
+  printf("INSN_GEM_8 [%d-%d]\n", INSN_GEM_8_0, INSN_GEM_8_1);
+  printf("INSN_GEM_9 [%d-%d]\n", INSN_GEM_9_0, INSN_GEM_9_1);
+  printf("INSN_GEM_A [%d-%d]\n", INSN_GEM_A_0, INSN_GEM_A_1);
+  printf("INSN_GEM_B [%d-%d]\n", INSN_GEM_B_0, INSN_GEM_B_1);
+  printf("INSN_GEM_C [%d-%d]\n", INSN_GEM_C_0, INSN_GEM_C_1);
+  printf("INSN_GEM_D [%d-%d]\n", INSN_GEM_D_0, INSN_GEM_D_1);
+  printf("INSN_GEM_E [%d-%d]\n", INSN_GEM_E_0, INSN_GEM_E_1);
+  printf("INSN_ALU_D [%d-%d]\n", INSN_ALU_D_0, INSN_ALU_D_1);
+  printf("INSN_ALU_E [%d]\n", INSN_ALU_E);
+  printf("INSN_ALU_F [%d-%d]\n", INSN_ALU_F_0, INSN_ALU_F_1);
+  printf("UOP_GEM_0 [%d]\n", UOP_GEM_0);
+  printf("UOP_GEM_1 [%d-%d]\n", UOP_GEM_1_0, UOP_GEM_1_1);
+  printf("UOP_GEM_2 [%d-%d]\n", UOP_GEM_2_0, UOP_GEM_2_1);
+  printf("UOP_GEM_3 [%d-%d]\n", UOP_GEM_3_0, UOP_GEM_3_1);
+  printf("UOP_ALU_0 [%d]\n", UOP_ALU_0);
+  printf("UOP_ALU_1 [%d-%d]\n", UOP_ALU_1_0, UOP_ALU_1_1);
+  printf("UOP_ALU_2 [%d-%d]\n", UOP_ALU_2_0, UOP_ALU_2_1);
+  printf("UOP_ALU_3 [%d-%d]\n", UOP_ALU_3_0, UOP_ALU_3_1);
+}
+
+void printInstruction(int num_insn, VTAGenericInsn *insns) {
+  // Keep tabs on dependence queues
+  int l2g_queue = 0;
+  int g2l_queue = 0;
+  int s2g_queue = 0;
+  int g2s_queue = 0;
+  // Converter
+  union VTAInsn c;
+  // Iterate over all instructions
+  printf("DEBUG - There are %u instructions\n", num_insn);
+  for (int i = 0; i < num_insn; i ++) {
+    // Fetch instruction and decode opcode
+    c.generic = insns[i];
+    printf("DEBUG - INSTRUCTION %u: ", i);
+    if (c.mem.opcode == OPCODE_LOAD || c.mem.opcode == OPCODE_STORE) {
+      // Print instruction field information
+      if (c.mem.opcode == OPCODE_LOAD) {
+        printf("LOAD ");
+        if (c.mem.memory_type == MEM_ID_UOP) printf("UOP\n");
+        if (c.mem.memory_type == MEM_ID_WGT) printf("WGT\n");
+        if (c.mem.memory_type == MEM_ID_INP) printf("INP\n");
+        if (c.mem.memory_type == MEM_ID_ACC) printf("ACC\n");
+      }
+      if (c.mem.opcode == OPCODE_STORE) {
+        printf("STORE ACC\n");
+      }
+      printf("\tdep - pop prev: %d, pop next: %d, push prev: %d, push next: %d\n",
+        (int) c.mem.pop_prev_dep, (int) c.mem.pop_next_dep,
+        (int) c.mem.push_prev_dep, (int) c.mem.push_next_dep);
+      printf("\tDRAM: 0x%08x, SRAM:0x%04x\n", (int) c.mem.dram_base, (int) c.mem.sram_base);
+      printf("\ty: size=%d, pad=[%d, %d]\n", (int) c.mem.y_size, (int) c.mem.y_pad_0,
+        (int) c.mem.y_pad_1);
+      printf("\tx: size=%d, stride=%d, pad=[%d, %d]\n", (int) c.mem.x_size, (int) c.mem.x_stride,
+        (int) c.mem.x_pad_0, (int) c.mem.x_pad_1);
+      if (c.mem.opcode == OPCODE_STORE) {
+        if (c.mem.pop_prev_dep) g2s_queue --;
+        if (c.mem.push_prev_dep) s2g_queue ++;
+      } else if (c.mem.opcode == OPCODE_LOAD &&
+        (c.mem.memory_type == MEM_ID_INP || c.mem.memory_type == MEM_ID_WGT)) {
+        if (c.mem.pop_next_dep) g2l_queue --;
+        if (c.mem.push_next_dep) l2g_queue ++;
+      } else {
+        if (c.mem.pop_prev_dep) l2g_queue --;
+        if (c.mem.push_prev_dep) g2l_queue ++;
+        if (c.mem.pop_next_dep) s2g_queue --;
+        if (c.mem.push_next_dep) g2s_queue ++;
+      }
+    } else if (c.mem.opcode == OPCODE_GEMM) {
+      // Print instruction field information
+      printf("GEVM\n");
+      printf("\tdep - pop prev: %d, pop next: %d, push prev: %d, push next: %d\n",
+        (int) c.mem.pop_prev_dep, (int) c.mem.pop_next_dep,
+        (int) c.mem.push_prev_dep, (int) c.mem.push_next_dep);
+      printf("\trange (%d, %d)\n", (int) c.gemm.uop_bgn, (int) c.gemm.uop_end);
+      printf("\touter loop - iter: %d, acc: %d, inp: %d, wgt: %d\n", (int) c.gemm.iter_out,
+        (int) c.gemm.dst_factor_out, (int) c.gemm.src_factor_out,
+        (int) c.gemm.wgt_factor_out);
+      printf("\tinner loop - iter: %d, acc: %d, inp: %d, wgt: %d\n", (int) c.gemm.iter_in,
+        (int) c.gemm.dst_factor_in, (int) c.gemm.src_factor_in,
+        (int) c.gemm.wgt_factor_in);
+      if (c.gemm.pop_prev_dep) l2g_queue --;
+      if (c.gemm.push_prev_dep) g2l_queue ++;
+      if (c.gemm.pop_next_dep) s2g_queue --;
+      if (c.gemm.push_next_dep) g2s_queue ++;
+    } else if (c.mem.opcode == OPCODE_FINISH) {
+      printf("FINISH\n");
+      printf("\tdep - pop prev: %d, pop next: %d, push prev: %d, push next: %d\n",
+        (int) c.mem.pop_prev_dep, (int) c.mem.pop_next_dep,
+        (int) c.mem.push_prev_dep, (int) c.mem.push_next_dep);
+      if (c.gemm.pop_prev_dep) l2g_queue --;
+      if (c.gemm.push_prev_dep) g2l_queue ++;
+      if (c.gemm.pop_next_dep) s2g_queue --;
+      if (c.gemm.push_next_dep) g2s_queue ++;
+    } else if (c.mem.opcode == OPCODE_ALU) {
+      // Print instruction field information
+      printf("ALU - %s\n", getOpcodeString(c.alu.alu_opcode, c.alu.use_imm));
+      printf("\tdep - pop prev: %d, pop next: %d, push prev: %d, push next: %d\n",
+        (int) c.mem.pop_prev_dep, (int) c.mem.pop_next_dep,
+        (int) c.mem.push_prev_dep, (int) c.mem.push_next_dep);
+      printf("\trange (%d, %d)\n", (int) c.alu.uop_bgn, (int) c.alu.uop_end);
+      printf("\touter loop - iter: %d, dst: %d, src: %d\n", (int) c.alu.iter_out,
+        (int) c.alu.dst_factor_out, (int) c.alu.src_factor_out);
+      printf("\tinner loop - iter: %d, dst: %d, src: %d\n", (int) c.alu.iter_in,
+        (int) c.alu.dst_factor_in, (int) c.alu.src_factor_in);
+      if (c.alu.pop_prev_dep) l2g_queue --;
+      if (c.alu.push_prev_dep) g2l_queue ++;
+      if (c.alu.pop_next_dep) s2g_queue --;
+      if (c.alu.push_next_dep) g2s_queue ++;
+    }
+  }
+  printf("DEBUG - l2g_queue = %d, g2l_queue = %d\n", l2g_queue, g2l_queue);
+  printf("DEBUG - s2g_queue = %d, g2s_queue = %d\n", s2g_queue, g2s_queue);
+}
+
+// Helper function: Print micro-ops status
+void printMicroOp(int num_uop, VTAUop *uops) {
+  // Iterate over all micro ops
+  printf("DEBUG - There are %u micro-ops\n", num_uop);
+  for (int i = 0; i < num_uop; i ++) {
+    // Read micro-op
+    printf("DEBUG - UOP %u: ", i);
+    printf("rst_out=%u, acc=%u, inp= %u, wgt=%u\n", uops[i].reset_out, uops[i].dst_idx,
+        uops[i].src_idx, uops[i].wgt_idx);
+
+  }
+}
+
+int alu_test(int opcode, bool use_imm, int batch, int vector_size, bool uop_compression) {
+
+  assert(batch % BATCH == 0);
+  assert(vector_size % BLOCK_OUT == 0);
+  assert(!(opcode == ALU_OPCODE_SHL && !use_imm));
+  assert(!(opcode == ALU_OPCODE_SHR && !use_imm));
+
+  printf("=====================================================================================\n");
+  printf("INFO - ALU test of %s: batch=%d, vector_size=%d, uop_compression=%d\n",
+    getOpcodeString(opcode, use_imm), batch, vector_size, uop_compression);
+
+  // Instruction count
+  int ins_size = 3 * batch / BATCH + 2;
+  // Micro op count
+  int uop_size = uop_compression ? 1 : vector_size / BLOCK_OUT;
+  // Input/output elements in each transfer
+  int tx_size = vector_size / BLOCK_OUT;
+  // Number of input sets to be generated
+  int input_sets = (use_imm) ? 1 : 2;
+  // Make sure we don't exceed buffer bounds
+  assert(uop_size <= UOP_BUFF_DEPTH);
+  assert(tx_size * input_sets <= ACC_BUFF_DEPTH);
+
+  // Immediate values
+  acc_T *immediate = (acc_T *) malloc(sizeof(acc_T) * batch / BATCH);
+  for (int b = 0; b < batch / BATCH; b ++) {
+    if (opcode == ALU_OPCODE_MIN) {
+      immediate[b] = (acc_T) (rand() % (1LL << (INP_WIDTH / 2)) - (1LL << (INP_WIDTH / 2 - 1)));
+    } else if (opcode == ALU_OPCODE_MAX) {
+      immediate[b] = (acc_T) (rand() % (1LL << (INP_WIDTH / 2)) - (1LL << (INP_WIDTH / 2 - 1)));
+    } else if (opcode == ALU_OPCODE_ADD) {
+      immediate[b] = (acc_T) (rand() % (1LL << (INP_WIDTH / 2)) - (1LL << (INP_WIDTH / 2 - 1)));
+    } else if (opcode == ALU_OPCODE_SUB) {
+      immediate[b] = (acc_T) (rand() % (1LL << (INP_WIDTH / 2)) - (1LL << (INP_WIDTH / 2 - 1)));
+    } else if (opcode == ALU_OPCODE_MUL) {
+      immediate[b] = (acc_T) (rand() % (1LL << (INP_WIDTH / 2)) - (1LL << (INP_WIDTH / 2 - 1)));
+    } else if (opcode == ALU_OPCODE_SHL) {
+      immediate[b] = (acc_T) (rand() % (INP_WIDTH + 1));
+    } else if (opcode == ALU_OPCODE_SHR) {
+      immediate[b] = (acc_T) (rand() % (INP_WIDTH + 1));
+    }
+  }
+
+  // Initialize instructions
+  VTAGenericInsn *insn_buf = (VTAGenericInsn *) allocBuffer(sizeof(VTAGenericInsn) * ins_size);
+  int insn_idx = 0;
+  insn_buf[insn_idx ++] = get1DLoadStoreInsn(OPCODE_LOAD, MEM_ID_UOP, 0, 0, uop_size, 0, 0, 0, 0);
+  for (int b = 0; b < batch; b += BATCH) {
+    insn_buf[insn_idx ++] = get2DLoadStoreInsn(
+      OPCODE_LOAD,                      // opcode
+      MEM_ID_ACC,                       // vector size
+      0,                                // sram offset
+      b / BATCH * tx_size * input_sets, // dram offset
+      1,                                // y size
+      tx_size * input_sets,             // x size
+      tx_size * input_sets,             // x stride
+      0,                                // y pad
+      0,                                // x pad
+      0,                                // pop prev dep
+      b > 0,                            // pop next dep
+      0,                                // push prev dep
+      0);                               // push next dep
+    insn_buf[insn_idx ++] = getALUInsn(
+      opcode,                           // opcode
+      tx_size,                          // vector size
+      use_imm,                          // use imm
+      immediate[b / BATCH],             // imm
+      uop_compression,                  // uop compression
+      0,                                // pop prev dep
+      0,                                // pop next dep
+      0,                                // push prev dep
+      1);                               // push next dep
+    insn_buf[insn_idx ++] = get2DLoadStoreInsn(
+      OPCODE_STORE,                     // opcode
+      MEM_ID_OUT,                       // vector size
+      0,                                // sram offset
+      b / BATCH * tx_size,              // dram offset
+      1,                                // y size
+      tx_size,                          // x size
+      tx_size,                          // x stride
+      0,                                // y pad
+      0,                                // x pad
+      1,                                // pop prev dep
+      0,                                // pop next dep
+      1,                                // push prev dep
+      0);                               // push next dep
+  }
+  // Finish
+  insn_buf[insn_idx ++] = getFinishInsn(0, 1);
+
+  // Prepare the uop buffer
+  VTAUop * uop_buf = getMapALUUops(tx_size, uop_compression);
+
+#if DEBUG==1
+  printInstruction(ins_size, insn_buf);
+  printMicroOp(uop_size, uop_buf);
+#endif
+
+  // Initialize the input/output data
+  acc_T **inputs = alloc2dArray<acc_T>(batch, vector_size * input_sets);
+  for (int i = 0; i < batch; i ++) {
+    for (int j = 0; j < vector_size * input_sets; j ++) {
+      if (opcode == ALU_OPCODE_MIN) {
+        inputs[i][j] = (acc_T) (rand() % (1LL << (INP_WIDTH - 1)) - (1LL << (INP_WIDTH - 2)));
+      } else if (opcode == ALU_OPCODE_MAX) {
+        inputs[i][j] = (acc_T) (rand() % (1LL << (INP_WIDTH - 1)) - (1LL << (INP_WIDTH - 2)));
+      } else if (opcode == ALU_OPCODE_ADD) {
+        inputs[i][j] = (acc_T) (rand() % (1LL << (INP_WIDTH - 1)) - (1LL << (INP_WIDTH - 2)));
+      } else if (opcode == ALU_OPCODE_SUB) {
+        inputs[i][j] = (acc_T) (rand() % (1LL << (INP_WIDTH - 1)) - (1LL << (INP_WIDTH - 2)));
+      } else if (opcode == ALU_OPCODE_MUL) {
+        inputs[i][j] = (acc_T) (rand() % (1LL << (INP_WIDTH / 2)) - (1LL << (INP_WIDTH / 2 - 1)));
+      } else if (opcode == ALU_OPCODE_SHL) {
+        inputs[i][j] = (acc_T) (rand() % (1LL << (INP_WIDTH - 1)) - (1LL << (INP_WIDTH - 2)));
+      } else if (opcode == ALU_OPCODE_SHR) {
+        inputs[i][j] = (acc_T) (rand() % (1LL << (INP_WIDTH - 1)) - (1LL << (INP_WIDTH - 2)));
+      }
+    }
+  }
+
+  // Compute reference output
+  inp_T **outputs_ref = alloc2dArray<inp_T>(batch, vector_size);
+  for (int i = 0; i < batch; i ++) {
+    for (int j = 0; j < vector_size; j ++) {
+      acc_T tmp = 0;
+      if (opcode == ALU_OPCODE_MIN) {
+        if (!use_imm) {
+          tmp = inputs[i][j] < inputs[i][j + vector_size] ? inputs[i][j] : inputs[i][j + vector_size];
+        } else {
+          tmp = inputs[i][j] < immediate[i / BATCH] ? inputs[i][j] : immediate[i / BATCH];
+        }
+      } else if (opcode == ALU_OPCODE_MAX) {
+        if (!use_imm) {
+          tmp = inputs[i][j] > inputs[i][j + vector_size] ? inputs[i][j] : inputs[i][j + vector_size];
+        } else {
+          tmp = inputs[i][j] > immediate[i / BATCH] ? inputs[i][j] : immediate[i / BATCH];
+        }
+      } else if (opcode == ALU_OPCODE_ADD) {
+        if (!use_imm) {
+          tmp = inputs[i][j] + inputs[i][j + vector_size];
+        } else {
+          tmp = inputs[i][j] + immediate[i / BATCH];
+        }
+      } else if (opcode == ALU_OPCODE_SUB) {
+        if (!use_imm) {
+          tmp = inputs[i][j] - inputs[i][j + vector_size];
+        } else {
+          tmp = inputs[i][j] - immediate[i / BATCH];
+        }
+      } else if (opcode == ALU_OPCODE_MUL) {
+        if (!use_imm) {
+          tmp = inputs[i][j] * inputs[i][j + vector_size];
+        } else {
+          tmp = inputs[i][j] * immediate[i / BATCH];
+        }
+      } else if (opcode == ALU_OPCODE_SHL) {
+        tmp = inputs[i][j] << immediate[i / BATCH];
+      } else if (opcode == ALU_OPCODE_SHR) {
+        tmp = inputs[i][j] >> immediate[i / BATCH];
+      }
+      // Set
+      outputs_ref[i][j] = (inp_T) tmp;
+    }
+  }
+
+  // Pack input buffer
+  acc_T *bias_buf = (acc_T *) allocBuffer(ACC_ELEM_BYTES * batch * tx_size * input_sets);
+  packBuffer<acc_T, ACC_WIDTH>(bias_buf, inputs, batch, vector_size * input_sets, BATCH, BLOCK_OUT);
+
+  // Prepare output buffer
+  inp_T *output_buf = (inp_T *) allocBuffer(INP_ELEM_BYTES * batch * tx_size * input_sets);
+
+#ifdef NO_SIM
+  // Invoke the VTA
+  uint64_t t_fpga = vta(ins_size, insn_buf, uop_buf, NULL, NULL, bias_buf, output_buf);
+  // Report on timining
+  printf("INFO - Synchronization time: %.3lfms\n", (double) t_fpga / 1E6);
+  printf("INFO - Throughput: %.3lfGOps/s\n", (double) vector_size * batch / t_fpga);
+#else
+  // Invoke the VTA
+  vta(
+    ins_size,
+    (volatile insn_T *) insn_buf,
+    (volatile uop_T *) uop_buf,
+    (volatile inp_vec_T *) NULL,
+    (volatile wgt_vec_T *) NULL,
+    (volatile acc_vec_T *) bias_buf,
+    (volatile inp_vec_T *) output_buf
+  );
+#endif
+
+  // Unpack output buffer
+  inp_T **outputs = alloc2dArray<inp_T>(batch, vector_size);
+  unpackBuffer<inp_T, INP_WIDTH>(outputs, output_buf, batch, vector_size, BATCH, BLOCK_OUT);
+
+  // Correctness checks
+  int err = 0;
+  for (int i = 0; i < batch; i ++) {
+    for (int j = 0; j < vector_size; j ++) {
+      if (outputs_ref[i][j] != outputs[i][j]) {
+        err++;
+#if DEBUG==1
+        printf("DEBUG - %d, %d: expected 0x%x but got 0x%x\n", i, j, (int) outputs_ref[i][j],
+            (int) outputs[i][j]);
+#endif
+      }
+    }
+  }
+
+  // Free all allocated arrays
+  free(immediate);
+  free2dArray<acc_T>(inputs, batch, vector_size * input_sets);
+  free2dArray<inp_T>(outputs_ref, batch, vector_size);
+  free2dArray<inp_T>(outputs, batch, vector_size);
+  freeBuffer(insn_buf);
+  freeBuffer(uop_buf);
+  freeBuffer(bias_buf);
+  freeBuffer(output_buf);
+
+  if (err == 0) {
+    printf("INFO - ALU test successful!\n");
+    return 0;
+  } else {
+    printf("INFO - ALU test failed, got %d errors!\n", err);
+    return -1;
+  }
+
+}
+
+int blocked_gemm_test(int batch, int channels, int block, bool uop_compression,
+    int virtual_threads) {
+
+  assert(block % BLOCK_IN == 0);
+  assert(block % BLOCK_OUT == 0);
+  assert(block % BATCH == 0);
+  assert(channels % block == 0);
+  assert(batch % block == 0);
+
+  printf("=====================================================================================\n");
+  printf("INFO - Blocked GEMM test: batch=%d, channels=%d, block=%d, uop_compression=%d, \
+virtual_threads=%d\n",
+    batch, channels, block, uop_compression, virtual_threads);
+
+  // Input/output channels
+  int in_feat = channels;
+  int out_feat = channels;
+  // Derive number of elements that need to be loaded/stored
+  int ins_size = batch / block * out_feat / block * (2 + in_feat / block * 3) + 2;
+  int uop_size = uop_compression ? block / BATCH * virtual_threads :
+    block / BATCH * block / BLOCK_IN * block / BLOCK_OUT * virtual_threads;
+  int wgt_size = in_feat / BLOCK_IN * out_feat / BLOCK_OUT;
+  int inp_size = batch / BATCH * in_feat / BLOCK_IN;
+  int out_size = batch / BATCH * out_feat / BLOCK_OUT;
+  // Blocked buffer sizes (in terms of elements)
+  int wgt_block_size = block / BLOCK_IN * block / BLOCK_OUT;
+  int inp_block_size = block / BATCH * block / BLOCK_IN;
+  int out_block_size = block / BATCH * block / BLOCK_OUT;
+  // Make sure we don't exceed buffer bounds
+  assert(uop_size <= UOP_BUFF_DEPTH);
+  assert(wgt_block_size <= WGT_BUFF_DEPTH);
+  assert(inp_block_size <= INP_BUFF_DEPTH);
+  assert(out_block_size <= ACC_BUFF_DEPTH);
+
+  // Initialize instruction buffer
+  VTAGenericInsn *insn_buf = (VTAGenericInsn *) allocBuffer(sizeof(VTAGenericInsn) * ins_size);
+  int insn_idx = 0;
+
+  // Load uops
+  insn_buf[insn_idx ++] = get1DLoadStoreInsn(OPCODE_LOAD, MEM_ID_UOP, 0, 0, uop_size, 0, 0, 0, 0);
+  // Iterate over batch blocks
+  for (int i = 0; i < batch; i += block) {
+    // Iterate over output channel blocks
+    for (int j = 0; j < out_feat; j += block) {
+      // Load bias block (pop next if not first, push prev)
+      insn_buf[insn_idx ++] = get2DLoadStoreInsn(
+        OPCODE_LOAD,                                        // opcode
+        MEM_ID_ACC,                                         // type
+        0,                                                  // sram offset
+        (i / BATCH * out_feat + j) / BLOCK_OUT,             // dram offset
+        block / BATCH,                                      // y size
+        block / BLOCK_OUT,                                  // x size
+        out_feat / BLOCK_OUT,                               // x stride
+        0,                                                  // y pad
+        0,                                                  // x pad
+        0,                                                  // pop prev dep
+        (i > 0 || j > 0),                                   // pop next dep
+        (virtual_threads == 1),                             // push prev dep
+        0);                                                 // push next dep
+      // Iterate over input channel blocks
+      for (int k = 0; k < in_feat; k += block * virtual_threads) {
+        for (int l = 0; l < block * virtual_threads; l += block) {
+          // Derive dependence flags
+          bool pop = (virtual_threads == 1) ? 
+            1 :
+            (i > 0 || j > 0 || k > 0 || l > 0) && (k + l != block * virtual_threads - block);
+          bool push_prev = (virtual_threads == 1) ?
+            ((k + l) != in_feat - block) :
+            ((k + l) != in_feat - virtual_threads * block) && 
+            (
+              (k + l != in_feat - block) ||
+              (j != out_feat - block) ||
+              (i != batch - block)
+            );
+          bool push_next = (k + l == in_feat - block);
+          // Load weight block (pop next)
+          insn_buf[insn_idx ++] = get2DLoadStoreInsn(
+            OPCODE_LOAD,                                    // opcode
+            MEM_ID_WGT,                                     // type
+            l / BLOCK_IN * block / BLOCK_OUT,               // sram offset
+            (j / BLOCK_OUT * in_feat + k + l) / BLOCK_IN,   // dram offset
+            block / BLOCK_OUT,                              // y size
+            block / BLOCK_IN,                               // x size
+            in_feat / BLOCK_IN,                             // x stride
+            0,                                              // y pad
+            0,                                              // x pad
+            0,                                              // pop prev dep
+            pop,                                            // pop next dep
+            0,                                              // push prev dep
+            0);                                             // push next dep
+          // Load input block (push next)
+          insn_buf[insn_idx ++] = get2DLoadStoreInsn(
+            OPCODE_LOAD,                                    // opcode
+            MEM_ID_INP,                                     // type
+            l / BLOCK_IN * block / BATCH,                   // sram offset
+            (i / BATCH * in_feat + k + l) / BLOCK_IN,       // dram offset
+            block / BATCH,                                  // y size
+            block / BLOCK_IN,                               // x size
+            in_feat / BLOCK_IN,                             // x stride
+            0,                                              // y pad
+            0,                                              // x pad
+            0,                                              // pop prev dep
+            0,                                              // pop next dep
+            0,                                              // push prev dep
+            1);                                             // push next dep
+          // Perform GEMM (pop prev, push prev if not last, push next if last)
+          insn_buf[insn_idx ++] = getGEMMInsn(
+            l / block * uop_size / virtual_threads,         // uop offset
+            block / BATCH,                                  // batch
+            block / BLOCK_IN,                               // in_feat
+            block / BLOCK_OUT,                              // out_feat
+            uop_compression,                                // uop_compression
+            1,                                              // pop_prev_dep
+            0,                                              // pop_next_dep
+            push_prev,                                      // push prev dep
+            push_next);                                     // push_next_dep
+        }
+      }
+      // Store output block (pop prev, push prev if not last)
+      insn_buf[insn_idx ++] = get2DLoadStoreInsn(
+        OPCODE_STORE,                                       // opcode
+        MEM_ID_OUT,                                         // type
+        0,                                                  // sram offset
+        (i / BATCH * out_feat + j) / BLOCK_OUT,             // dram offset
+        block / BATCH,                                      // y size
+        block / BLOCK_OUT,                                  // x size
+        out_feat / BLOCK_OUT,                               // x stride
+        0,                                                  // y pad
+        0,                                                  // x pad
+        1,                                                  // pop prev dep
+        0,                                                  // pop next dep
+        1,                                                  // pop prev dep
+        0);                                                 // push next dep
+    }
+  }
+  // Finish
+  insn_buf[insn_idx ++] = getFinishInsn(0, 1);
+
+  // Prepare the uop buffer
+  VTAUop * uop_buf = getGEMMUops(block / BATCH, block / BLOCK_IN, block / BLOCK_OUT, uop_compression,
+    virtual_threads > 1);
+
+#if DEBUG==1
+  printInstruction(ins_size, insn_buf);
+  printMicroOp(uop_size, uop_buf);
+#endif
+
+  // Initialize weights
+  wgt_T **weights = allocInit2dArray<wgt_T, WGT_WIDTH>(out_feat, in_feat);
+  // Initialize inputs
+  inp_T **inputs = allocInit2dArray<inp_T, INP_WIDTH>(batch, in_feat);
+  // Initialize biases
+  acc_T **biases = allocInit2dArray<acc_T, ACC_WIDTH>(batch, out_feat);
+
+  // Reference GEMM implementation
+  inp_T **outputs_ref = alloc2dArray<inp_T>(batch, out_feat);
+  for (int i = 0; i < batch; i ++) {
+    for (int j = 0; j < out_feat; j ++) {
+      acc_T sum = biases[i][j];
+      for (int k = 0; k < in_feat; k ++) {
+        sum += (acc_T) (inputs[i][k] * weights[j][k]);
+      }
+      // Set
+      outputs_ref[i][j] = (inp_T) sum;
+    }
+  }
+
+  // Prepare the weight buffer
+  wgt_T *weight_buf = (wgt_T *) allocBuffer(WGT_ELEM_BYTES * wgt_size);
+  packBuffer<wgt_T, WGT_WIDTH>(weight_buf, weights, out_feat, in_feat, BLOCK_OUT, BLOCK_IN);
+  // Prepare the input buffer
+  inp_T *input_buf = (inp_T *) allocBuffer(INP_ELEM_BYTES * inp_size);
+  packBuffer<inp_T, INP_WIDTH>(input_buf, inputs, batch, in_feat, BATCH, BLOCK_IN);
+  // Prepare the bias buffer
+  acc_T *bias_buf = (acc_T *) allocBuffer(ACC_ELEM_BYTES * out_size);
+  packBuffer<acc_T, ACC_WIDTH>(bias_buf, biases, batch, out_feat, BATCH, BLOCK_OUT);
+  // Prepare the output buffer
+  inp_T *output_buf = (inp_T *) allocBuffer(INP_ELEM_BYTES * out_size);
+
+#ifdef NO_SIM
+  // Invoke the VTA
+  uint64_t t_fpga = vta(ins_size, insn_buf, uop_buf, input_buf, weight_buf, bias_buf, output_buf);
+  // Report on timining
+  printf("INFO - Synchronization time: %.3lfms\n", (double) t_fpga / 1E6);
+  printf("INFO - Throughput: %.3lfGOPs/s\n", (double) batch * in_feat * out_feat * 2 / t_fpga);
+#else
+  // Invoke the VTA
+  vta(
+    ins_size,
+    (volatile insn_T *) insn_buf,
+    (volatile uop_T *) uop_buf,
+    (volatile inp_vec_T *) input_buf,
+    (volatile wgt_vec_T *) weight_buf,
+    (volatile acc_vec_T *) bias_buf,
+    (volatile inp_vec_T *) output_buf
+  );
+#endif
+
+  // Unpack output data
+  inp_T **outputs = alloc2dArray<inp_T>(batch, out_feat);
+  unpackBuffer<inp_T, INP_WIDTH>(outputs, output_buf, batch, out_feat, BATCH, BLOCK_OUT);
+
+  // Correctness checks
+  int err = 0;
+  for (int i = 0; i < batch; i ++) {
+    for (int j = 0; j < out_feat; j ++) {
+      if (outputs_ref[i][j] != outputs[i][j]) {
+        err++;
+#if DEBUG==1
+        printf("DEBUG - %d, %d: expected 0x%x but got 0x%x\n", i, j, (int) outputs_ref[i][j],
+            (int) outputs[i][j]);
+#endif
+      }
+    }
+  }
+
+  // Free all allocated arrays
+  free2dArray<wgt_T>(weights, out_feat, in_feat);
+  free2dArray<inp_T>(inputs, batch, in_feat);
+  free2dArray<acc_T>(biases, batch, out_feat);
+  free2dArray<inp_T>(outputs_ref, batch, out_feat);
+  free2dArray<inp_T>(outputs, batch, out_feat);
+  freeBuffer((void *) insn_buf);
+  freeBuffer((void *) uop_buf);
+  freeBuffer((void *) weight_buf);
+  freeBuffer((void *) input_buf);
+  freeBuffer((void *) bias_buf);
+  freeBuffer((void *) output_buf);
+
+  if (err == 0) {
+    printf("INFO - Blocked GEMM test successful!\n");
+    return 0;
+  } else {
+    printf("INFO - Blocked GEMM test failed, got %d errors!\n", err);
+    return -1;
+  }
+
+}
diff --git a/vta/tests/driver/Makefile b/vta/tests/driver/Makefile
new file mode 100644
index 000000000000..dad8dbed105e
--- /dev/null
+++ b/vta/tests/driver/Makefile
@@ -0,0 +1,59 @@
+CC ?= g++
+CFLAGS = -Wall -O3 -std=c++11 -I/usr/include
+LDFLAGS = -L/usr/lib -L/home/xilinx/pynq/drivers
+LIBS = -l:libsds_lib.so -l:libdma.so
+SRC_DIR = ../../src
+INCLUDE_DIR = ../../include
+DRIVER_DIR = $(SRC_DIR)/driver/pynq
+TESTLIB_DIR = $(SRC_DIR)/test
+VPATH = $(DRIVER_DIR):$(TESTLIB_DIR)
+SOURCES = vta_pynq_driver.c vta_test_lib.cc
+OBJECTS = vta_pynq_driver.o vta_test_lib.o driver_test.o
+EXECUTABLE = vta
+
+# VTA Parameters
+#  Log of input width in bits
+LOG_INP_WIDTH = 3
+#  Log of weight width in bits
+LOG_WGT_WIDTH = 3
+#  Log of accum width in bits
+LOG_ACC_WIDTH = 5
+#  Log of output width in bits
+LOG_OUT_WIDTH = $(LOG_INP_WIDTH)
+#  Log of tensor batch size (A in (A,B)x(B,C) matrix multiplication)
+LOG_BATCH = 0
+#  Log of tensor inner block size (B in (A,B)x(B,C) matrix multiplication)
+LOG_IN_BLOCK = 4
+#  Log of tensor outer block size (C in (A,B)x(B,C) matrix multiplication)
+LOG_OUT_BLOCK = 4
+#  Log of uop buffer size in Bytes
+LOG_UOP_BUFF_SIZE = 15
+#  Log of inp buffer size in Bytes
+LOG_INP_BUFF_SIZE = 15
+#  Log of wgt buffer size in Bytes
+LOG_WGT_BUFF_SIZE = 15
+#  Log of acc buffer size in Bytes
+LOG_ACC_BUFF_SIZE = 17
+#  Log of out buffer size in Bytes
+LOG_OUT_BUFF_SIZE = $(shell echo "$$(( $(LOG_ACC_BUFF_SIZE)+$(LOG_OUT_WIDTH)-$(LOG_ACC_WIDTH) ))" )
+
+# Define flags
+CFLAGS += -I $(INCLUDE_DIR) -DNO_SIM \
+	-DDEBUG=0 -DLOG_WGT_WIDTH=$(LOG_WGT_WIDTH) -DLOG_INP_WIDTH=$(LOG_INP_WIDTH) \
+	-DLOG_ACC_WIDTH=$(LOG_ACC_WIDTH) -DLOG_OUT_WIDTH=$(LOG_OUT_WIDTH) \
+	-DLOG_BATCH=$(LOG_BATCH) -DLOG_BLOCK_IN=$(LOG_IN_BLOCK) -DLOG_BLOCK_OUT=$(LOG_OUT_BLOCK) \
+	-DLOG_UOP_BUFF_SIZE=$(LOG_UOP_BUFF_SIZE) -DLOG_INP_BUFF_SIZE=$(LOG_INP_BUFF_SIZE) \
+	-DLOG_WGT_BUFF_SIZE=$(LOG_WGT_BUFF_SIZE) -DLOG_ACC_BUFF_SIZE=$(LOG_ACC_BUFF_SIZE) \
+	-DLOG_OUT_BUFF_SIZE=$(LOG_OUT_BUFF_SIZE)
+
+# All Target
+all: $(EXECUTABLE)
+
+%.o: %.cc $(SOURCES)
+	$(CC) -c -o $@ $< $(CFLAGS)
+
+$(EXECUTABLE): $(OBJECTS)
+	$(CC) $(LDFLAGS) $(OBJECTS) -o $@ $(LIBS)
+
+clean:
+	rm -rf *.o $(EXECUTABLE)
diff --git a/vta/tests/driver/driver_test.cc b/vta/tests/driver/driver_test.cc
new file mode 100644
index 000000000000..6cdc32a955d8
--- /dev/null
+++ b/vta/tests/driver/driver_test.cc
@@ -0,0 +1,152 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file driver_test.cpp
+ * \brief Bare-metal test to test driver and VTA design.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include "vta_test_lib.h"
+#include "vta_pynq_driver.h"
+
+// VTA invocation (present the same abstraction as in the simulation tests)
+uint64_t vta (
+    uint32_t insn_count,
+    VTAGenericInsn *insns,
+    VTAUop *uops,
+    inp_T *inputs,
+    wgt_T *weights,
+    acc_T *biases,
+    inp_T *outputs) {
+
+    // Performance counter variables
+    uint64_t t_fpga;
+    struct timespec start, stop;
+
+    // Derive bitstream file
+    char bitstream[64];
+    char str_batch_size[4];
+    char str_block_out_size[4];
+    char str_block_in_size[4];
+    char str_block_bit_width[4];
+    sprintf(str_batch_size, "%d", BATCH);
+    sprintf(str_block_out_size, "%d", BLOCK_OUT);
+    sprintf(str_block_in_size, "%d", BLOCK_IN);
+    sprintf(str_block_bit_width, "%d", WGT_WIDTH);
+    strcpy(bitstream, "vta.bit");
+
+#if DEBUG==1
+    printf("INFO - Programming FPGA: %s!\n", bitstream);
+#endif
+
+    // Program VTA
+    ProgramVTA(bitstream);
+    // Get VTA handles
+    VTAHandle vta_fetch_handle = MapRegister(VTA_FETCH_ADDR, VTA_RANGE);
+    VTAHandle vta_load_handle = MapRegister(VTA_LOAD_ADDR, VTA_RANGE);
+    VTAHandle vta_compute_handle = MapRegister(VTA_COMPUTE_ADDR, VTA_RANGE);
+    VTAHandle vta_store_handle = MapRegister(VTA_STORE_ADDR, VTA_RANGE);
+
+    // Physical address pointers
+    uint32_t insn_phy = insns ? cma_get_phy_addr(insns) : 0;
+    uint32_t uop_phy = uops ? cma_get_phy_addr(uops) : 0;
+    uint32_t weight_phy = weights ? cma_get_phy_addr(weights) : 0;
+    uint32_t input_phy = inputs ? cma_get_phy_addr(inputs) : 0;
+    uint32_t bias_phy = biases ? cma_get_phy_addr(biases) : 0;
+    uint32_t output_phy = outputs ? cma_get_phy_addr(outputs) : 0;
+
+#if DEBUG==1
+    printf("INFO - Starting FPGA!\n");
+#endif
+
+    clock_gettime(CLOCK_REALTIME, &start);
+
+    // FETCH @ 0x10 : Data signal of insn_count_V
+    WriteMappedReg(vta_fetch_handle, 0x10, insn_count);
+    // FETCH @ 0x18 : Data signal of insns_V
+    if (insns) WriteMappedReg(vta_fetch_handle, 0x18, insn_phy);
+    // LOAD @ 0x10 : Data signal of weight_V
+    if (weights) WriteMappedReg(vta_load_handle, 0x10, weight_phy);
+    // LOAD @ 0x18 : Data signal of inputs_V
+    if (inputs) WriteMappedReg(vta_load_handle, 0x18, input_phy);
+    // COMPUTE @ 0x20 : Data signal of uops_V
+    if (uops) WriteMappedReg(vta_compute_handle, 0x20, uop_phy);
+    // COMPUTE @ 0x28 : Data signal of biases_V
+    if (biases) WriteMappedReg(vta_compute_handle, 0x28, bias_phy);
+    // STORE @ 0x10 : Data signal of outputs_V
+    if (outputs) WriteMappedReg(vta_store_handle, 0x10, output_phy);
+
+    // VTA start
+    WriteMappedReg(vta_fetch_handle, 0x0, 0x1);
+    WriteMappedReg(vta_load_handle, 0x0, 0x81);
+    WriteMappedReg(vta_compute_handle, 0x0, 0x81);
+    WriteMappedReg(vta_store_handle, 0x0, 0x81);
+
+    int flag = 0, t = 0;
+    for (t = 0; t < 10000000; ++t) {
+      flag = ReadMappedReg(vta_compute_handle, 0x18);
+      if (flag & VTA_DONE) break;
+    }
+
+    if (t==10000000) {
+        printf("\tWARNING: VTA TIMEOUT!!!!\n");
+    }
+#if DEBUG==1
+    else {
+        printf("INFO - FPGA Finished!\n");
+    }
+#endif
+
+    clock_gettime(CLOCK_REALTIME, &stop);
+    t_fpga = 1000000000ULL * (stop.tv_sec - start.tv_sec) + (stop.tv_nsec - start.tv_nsec);
+
+    // Unmap VTA register
+    UnmapRegister(vta_fetch_handle, VTA_RANGE);
+    UnmapRegister(vta_load_handle, VTA_RANGE);
+    UnmapRegister(vta_compute_handle, VTA_RANGE);
+    UnmapRegister(vta_store_handle, VTA_RANGE);
+
+    return t_fpga;
+};
+
+int main(void)
+{
+
+#if DEBUG==1
+    printParameters();
+#endif
+
+    int status = 0;
+
+    // Run ALU test (vector-scalar operators)
+    status |= alu_test(ALU_OPCODE_MAX, true, 16, 128, true);
+    status |= alu_test(ALU_OPCODE_MAX, true, 16, 128, false);
+    status |= alu_test(ALU_OPCODE_ADD, true, 16, 128, true);
+    status |= alu_test(ALU_OPCODE_ADD, true, 16, 128, false);
+    status |= alu_test(ALU_OPCODE_SHR, true, 16, 128, true);
+    status |= alu_test(ALU_OPCODE_SHR, true, 16, 128, false);
+
+    // Run ALU test (vector-vector operators)
+    status |= alu_test(ALU_OPCODE_MAX, false, 16, 128, true);
+    status |= alu_test(ALU_OPCODE_MAX, false, 16, 128, false);
+    status |= alu_test(ALU_OPCODE_ADD, false, 16, 128, true);
+    status |= alu_test(ALU_OPCODE_ADD, false, 16, 128, false);
+
+    // Run blocked GEMM test
+    status |= blocked_gemm_test(256, 256, BLOCK_OUT*4, true, 2);
+    status |= blocked_gemm_test(256, 256, BLOCK_OUT*4, false, 2);
+    status |= blocked_gemm_test(256, 256, BLOCK_OUT*4, true, 1);
+    status |= blocked_gemm_test(256, 256, BLOCK_OUT*4, false, 1);
+
+    if (status==0) {
+        printf("\nINFO - Unit tests successful!\n");
+    } else {
+        printf("\nINTO - Unit tests failed!\n");
+    }
+
+    return status;
+
+}

From 0a875e417612b4ecbc590ffcfeb3612565da2de4 Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@uw.edu>
Date: Fri, 16 Mar 2018 11:57:52 -0700
Subject: [PATCH 821/949] doxygen path update

---
 vta/docs/Doxyfile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/vta/docs/Doxyfile b/vta/docs/Doxyfile
index 77b0dc11b9dd..6a9e3d52cfe4 100644
--- a/vta/docs/Doxyfile
+++ b/vta/docs/Doxyfile
@@ -771,7 +771,7 @@ WARN_LOGFILE           =
 # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
 # Note: If this tag is empty the current directory is searched.
 
-INPUT                  = ../hardware/src/ ../hardware/sim/
+INPUT                  = ../include
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses

From 93a02db43e8e96d9ae8f7c7ad1562c149fb332a3 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 16 Mar 2018 23:06:01 -0700
Subject: [PATCH 822/949] [DOCS] Initial docs (#4)

* [DOCS] Initial docs

* update instruction
---
 vta/CONTRIBUTORS.md                |  38 +++
 vta/Makefile                       |  89 +++++++
 vta/NEWS.md                        |  12 +
 vta/docs/.gitignore                |   3 +
 vta/docs/Doxyfile                  |   6 +-
 vta/docs/Makefile                  | 193 ++++++++++++++
 vta/docs/README.txt                |   5 +
 vta/docs/_static/css/tvm_theme.css |  11 +
 vta/docs/api_links.rst             |   6 +
 vta/docs/conf.py                   | 212 +++++++++++++++
 vta/docs/dev/index.rst             |  11 +
 vta/docs/dev/runtime.md            |   3 +
 vta/docs/how_to/contribute.md      | 102 ++++++++
 vta/docs/how_to/install.md         |  19 ++
 vta/docs/index.rst                 |  20 ++
 vta/make/config.mk                 |  27 ++
 vta/python/vta/__init__.py         |   5 +
 vta/tests/lint/pylintrc            | 406 +++++++++++++++++++++++++++++
 vta/tutorials/README.txt           |   3 +
 vta/tutorials/get_started.py       |  39 +++
 20 files changed, 1207 insertions(+), 3 deletions(-)
 create mode 100644 vta/CONTRIBUTORS.md
 create mode 100644 vta/Makefile
 create mode 100644 vta/NEWS.md
 create mode 100644 vta/docs/Makefile
 create mode 100644 vta/docs/README.txt
 create mode 100644 vta/docs/_static/css/tvm_theme.css
 create mode 100644 vta/docs/api_links.rst
 create mode 100644 vta/docs/conf.py
 create mode 100644 vta/docs/dev/index.rst
 create mode 100644 vta/docs/dev/runtime.md
 create mode 100644 vta/docs/how_to/contribute.md
 create mode 100644 vta/docs/how_to/install.md
 create mode 100644 vta/docs/index.rst
 create mode 100644 vta/make/config.mk
 create mode 100644 vta/python/vta/__init__.py
 create mode 100644 vta/tests/lint/pylintrc
 create mode 100644 vta/tutorials/README.txt
 create mode 100644 vta/tutorials/get_started.py

diff --git a/vta/CONTRIBUTORS.md b/vta/CONTRIBUTORS.md
new file mode 100644
index 000000000000..0df700b2d368
--- /dev/null
+++ b/vta/CONTRIBUTORS.md
@@ -0,0 +1,38 @@
+Contributing to VTA
+===================
+VTA is part of TVM software/hardware stack.
+We adopts Apache style committer model.
+The package is developed and used by the community.
+
+We actively seek committers that come from community contributors who:
+- Made substantial contributions to the project.
+  - All forms of contributions are valued (see detail in next section).
+- Willing to spend time on maintaining and lead the project.
+
+Contributions
+-------------
+We value all forms of contributions, here is a non-comprehensive
+list of contributions that are welcomed
+
+- Documentation and usage examples
+- Hardware implementations of the design.
+- Community participation, answering questions and issues.
+- Code readability and developer guide
+  - We welcome contributions that add code comments
+    to improve readability
+  - We also welcome contributions to docs to explain the
+    design choices of the internal.
+- Test cases to make the codebase more robust
+- Tutorials, blog posts, talks that promote the project.
+
+
+How to Contribute
+-----------------
+See [Contributor guide](docs/how_to/contribute.md) on how to contribute.
+
+Committers
+----------
+Committers are people who have made substantial contribution to the project and granted write access to the project.
+
+- [Thierry Moreau](http://homes.cs.washington.edu/~moreau/), University of Washington
+- [Tianqi Chen](https://github.com/tqchen), University of Washington
diff --git a/vta/Makefile b/vta/Makefile
new file mode 100644
index 000000000000..20414cde9304
--- /dev/null
+++ b/vta/Makefile
@@ -0,0 +1,89 @@
+ROOTDIR = $(CURDIR)
+
+ifndef config
+ifneq ("$(wildcard ./config.mk)", "")
+	config = config.mk
+else
+	config = make/config.mk
+endif
+endif
+include $(config)
+
+export LDFLAGS = -pthread -lm
+export CFLAGS = -std=c++11 -Wall -O2 -Iinclude -fPIC
+
+ifdef NNVM_PATH
+	CFLAGS += -I$(NNVM_PATH)/include
+else
+	NNVM_PATH = $(ROOTDIR)/nnvm
+	CFLAGS += -I$(NNVM_PATH)/include
+endif
+
+ifdef TVM_PATH
+	CFLAGS += -I$(TVM_PATH)/include -I$(TVM_PATH)/dlpack/include -I$(TVM_PATH)/HalideIR/src
+else
+	TVM_PATH = $(NNVM_PATH)/tvm
+	CFLAGS += -I$(TVM_PATH)/include -I$(TVM_PATH)/dlpack/include -I$(TVM_PATH)/HalideIR/src
+endif
+
+ifdef DMLC_CORE_PATH
+  CFLAGS += -I$(DMLC_CORE_PATH)/include
+else
+  CFLAGS += -I$(NNVM_PATH)/dmlc-core/include
+endif
+
+ifneq ($(ADD_CFLAGS), NONE)
+	CFLAGS += $(ADD_CFLAGS)
+endif
+
+ifneq ($(ADD_LDFLAGS), NONE)
+	LDFLAGS += $(ADD_LDFLAGS)
+endif
+
+ifeq ($(UNAME_S), Darwin)
+	SHARED_LIBRARY_SUFFIX := dylib
+	WHOLE_ARCH= -all_load
+	NO_WHOLE_ARCH= -noall_load
+	LDFLAGS += -undefined dynamic_lookup
+else
+	SHARED_LIBRARY_SUFFIX := so
+	WHOLE_ARCH= --whole-archive
+	NO_WHOLE_ARCH= --no-whole-archive
+endif
+
+
+all: lib/libvta.$(SHARED_LIBRARY_SUFFIX)
+
+SRC = $(wildcard src/*.cc src/*.cc)
+ALL_OBJ = $(patsubst %.cc, build/%.o, $(SRC))
+ALL_DEP = $(ALL_OBJ)
+
+test: $(TEST)
+
+build/src/%.o: src/%.cc
+	@mkdir -p $(@D)
+	$(CXX) $(CFLAGS) -MM -MT build/src/$*.o $< >build/src/$*.d
+	$(CXX) -c $(CFLAGS) -c $< -o $@
+
+lib/libvta.$(SHARED_LIBRARY_SUFFIX): $(ALL_DEP)
+	@mkdir -p $(@D)
+	$(CXX) $(CFLAGS) -shared -o $@ $(filter %.o, $^) $(LDFLAGS)
+
+lint: pylint cpplint
+
+cpplint:
+	python nnvm/dmlc-core/scripts/lint.py vta cpp include src
+
+pylint:
+	pylint python/vta --rcfile=$(ROOTDIR)/tests/lint/pylintrc
+
+doc:
+	doxygen docs/Doxyfile
+
+clean:
+	$(RM) -rf build lib bin *~ */*~ */*/*~ */*/*/*~ */*.o */*/*.o */*/*/*.o
+
+
+-include build/*.d
+-include build/*/*.d
+-include build/*/*/*.d
diff --git a/vta/NEWS.md b/vta/NEWS.md
new file mode 100644
index 000000000000..2c8fe5554f98
--- /dev/null
+++ b/vta/NEWS.md
@@ -0,0 +1,12 @@
+TVM Change Log
+==============
+
+This file records the changes in VTA stack in reverse chronological order.
+
+
+## Initial version
+
+- Vivado based hardware
+- Driver for PYNQ
+- Runtime library.
+- TVM compiler stack.
diff --git a/vta/docs/.gitignore b/vta/docs/.gitignore
index 8e786536c590..845005a76aa8 100644
--- a/vta/docs/.gitignore
+++ b/vta/docs/.gitignore
@@ -1 +1,4 @@
 doxygen
+modules
+tutorials
+_build
diff --git a/vta/docs/Doxyfile b/vta/docs/Doxyfile
index 6a9e3d52cfe4..77981f68f90b 100644
--- a/vta/docs/Doxyfile
+++ b/vta/docs/Doxyfile
@@ -58,7 +58,7 @@ PROJECT_LOGO           =
 # entered, it will be relative to the location where doxygen was started. If
 # left blank the current directory will be used.
 
-OUTPUT_DIRECTORY       = doxygen
+OUTPUT_DIRECTORY       = docs/doxygen
 
 # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
 # directories (in 2 levels) under the output directory of each output format and
@@ -771,7 +771,7 @@ WARN_LOGFILE           =
 # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
 # Note: If this tag is empty the current directory is searched.
 
-INPUT                  = ../include
+INPUT                  = include
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
@@ -796,7 +796,7 @@ INPUT_ENCODING         = UTF-8
 # *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f, *.for, *.tcl,
 # *.vhd, *.vhdl, *.ucf, *.qsf, *.as and *.js.
 
-FILE_PATTERNS          = *.h 
+FILE_PATTERNS          = *.h
 
 # The RECURSIVE tag can be used to specify whether or not subdirectories should
 # be searched for input files as well.
diff --git a/vta/docs/Makefile b/vta/docs/Makefile
new file mode 100644
index 000000000000..1e45fb5e3787
--- /dev/null
+++ b/vta/docs/Makefile
@@ -0,0 +1,193 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# User-friendly check for sphinx-build
+ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
+$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
+endif
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html       to make standalone HTML files"
+	@echo "  dirhtml    to make HTML files named index.html in directories"
+	@echo "  singlehtml to make a single large HTML file"
+	@echo "  pickle     to make pickle files"
+	@echo "  json       to make JSON files"
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
+	@echo "  qthelp     to make HTML files and a qthelp project"
+	@echo "  applehelp  to make an Apple Help Book"
+	@echo "  devhelp    to make HTML files and a Devhelp project"
+	@echo "  epub       to make an epub"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  texinfo    to make Texinfo files"
+	@echo "  info       to make Texinfo files and run them through makeinfo"
+	@echo "  gettext    to make PO message catalogs"
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
+	@echo "  xml        to make Docutils-native XML files"
+	@echo "  pseudoxml  to make pseudoxml-XML files for display purposes"
+	@echo "  linkcheck  to check all external links for integrity"
+	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+	@echo "  coverage   to run coverage check of the documentation (if enabled)"
+
+clean:
+	rm -rf $(BUILDDIR)/*
+	rm -rf gen_modules
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/rabit.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/rabit.qhc"
+
+applehelp:
+	$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
+	@echo
+	@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
+	@echo "N.B. You won't be able to view it unless you put it in" \
+	      "~/Library/Documentation/Help or install it in your application" \
+	      "bundle."
+
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/rabit"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/rabit"
+	@echo "# devhelp"
+
+epub:
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+	@echo
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+latexpdfja:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through platex and dvipdfmx..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+	@echo
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+	@echo
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo
+	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+	@echo "Run \`make' in that directory to run these through makeinfo" \
+	      "(use \`make info' here to do that automatically)."
+
+info:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo "Running Texinfo files through makeinfo..."
+	make -C $(BUILDDIR)/texinfo info
+	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+	@echo
+	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."
+
+coverage:
+	$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
+	@echo "Testing of coverage in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/coverage/python.txt."
+
+xml:
+	$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
+	@echo
+	@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
+
+pseudoxml:
+	$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
+	@echo
+	@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
diff --git a/vta/docs/README.txt b/vta/docs/README.txt
new file mode 100644
index 000000000000..8d93fa1004a1
--- /dev/null
+++ b/vta/docs/README.txt
@@ -0,0 +1,5 @@
+The documentation of vta is generated with recommonmark and sphinx.
+
+- pip install sphinx>=1.5.5 sphinx-gallery sphinx_rtd_theme matplotlib Image recommonmark
+- Type "make html" to generate the doc
+- If we only want to build doxygen docs: at project root, type "make doc"
diff --git a/vta/docs/_static/css/tvm_theme.css b/vta/docs/_static/css/tvm_theme.css
new file mode 100644
index 000000000000..5e0838abf6cb
--- /dev/null
+++ b/vta/docs/_static/css/tvm_theme.css
@@ -0,0 +1,11 @@
+.rst-content .hidden-section {
+    display: none;
+}
+
+.rst-toc .hidden-section {
+    display: none;
+}
+
+nav .hidden-section {
+    display: inherit;
+}
diff --git a/vta/docs/api_links.rst b/vta/docs/api_links.rst
new file mode 100644
index 000000000000..60dba9def364
--- /dev/null
+++ b/vta/docs/api_links.rst
@@ -0,0 +1,6 @@
+Links to API References
+=======================
+
+This page contains links to API references that are build with different doc build system.
+
+* `C++ doyxgen API <doxygen/index.html>`_
diff --git a/vta/docs/conf.py b/vta/docs/conf.py
new file mode 100644
index 000000000000..8464fada6352
--- /dev/null
+++ b/vta/docs/conf.py
@@ -0,0 +1,212 @@
+# -*- coding: utf-8 -*-
+#
+# documentation build configuration file, created by
+# sphinx-quickstart on Thu Jul 23 19:40:08 2015.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+import sys
+import os, subprocess
+import shlex
+import recommonmark
+import sphinx_gallery
+from recommonmark.parser import CommonMarkParser
+from recommonmark.transform import AutoStructify
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
+sys.path.insert(0, os.path.join(curr_path, '../python/'))
+
+# -- General configuration ------------------------------------------------
+
+# General information about the project.
+project = u'vta'
+author = u'%s developers' % project
+copyright = u'2018, %s' % author
+github_doc_root = 'https://github.com/uwsaml/vta/tree/master/docs/'
+
+# add markdown parser
+CommonMarkParser.github_doc_root = github_doc_root
+source_parsers = {
+    '.md': CommonMarkParser
+}
+os.environ['VTA_BUILD_DOC'] = '1'
+# Version information.
+import vta
+version = vta.__version__
+release = vta.__version__
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones
+extensions = [
+    'sphinx.ext.autodoc',
+    'sphinx.ext.autosummary',
+    'sphinx.ext.intersphinx',
+    'sphinx.ext.napoleon',
+    'sphinx.ext.mathjax',
+    'sphinx_gallery.gen_gallery',
+]
+
+
+breathe_projects = {'vta' : 'doxygen/xml/'}
+breathe_default_project = 'vta'
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+# source_suffix = ['.rst', '.md']
+source_suffix = ['.rst', '.md']
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# generate autosummary even if no references
+autosummary_generate = True
+
+# The master toctree document.
+master_doc = 'index'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all
+# documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+# If true, keep warnings as "system message" paragraphs in the built documents.
+#keep_warnings = False
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+todo_include_todos = False
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme is set by the make target
+html_theme = os.environ.get('VTA_THEME', 'rtd')
+
+on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
+# only import rtd theme and set it if want to build docs locally
+if not on_rtd and html_theme == 'rtd':
+    import sphinx_rtd_theme
+    html_theme = 'sphinx_rtd_theme'
+    html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = project + 'doc'
+
+# -- Options for LaTeX output ---------------------------------------------
+latex_elements = {
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+#  author, documentclass [howto, manual, or own class]).
+latex_documents = [
+  (master_doc, '%s.tex' % project, project,
+   author, 'manual'),
+]
+
+# hook for doxygen
+def run_doxygen(folder):
+    """Run the doxygen make command in the designated folder."""
+    try:
+        retcode = subprocess.call("cd %s; make doc" % folder, shell=True)
+        retcode = subprocess.call("rm -rf _build/html/doxygen", shell=True)
+        retcode = subprocess.call("mkdir -p _build/html", shell=True)
+        retcode = subprocess.call("cp -rf doxygen/html _build/html/doxygen", shell=True)
+        if retcode < 0:
+            sys.stderr.write("doxygen terminated by signal %s" % (-retcode))
+    except OSError as e:
+        sys.stderr.write("doxygen execution failed: %s" % e)
+
+intersphinx_mapping = {
+    'python': ('https://docs.python.org/{.major}'.format(sys.version_info), None),
+    'numpy': ('http://docs.scipy.org/doc/numpy/', None),
+    'scipy': ('http://docs.scipy.org/doc/scipy/reference', None),
+    'matplotlib': ('http://matplotlib.org/', None),
+    'tvm': ('http://docs.tvmlang.org/', None),
+}
+
+from sphinx_gallery.sorting import ExplicitOrder
+
+examples_dirs = ['../tutorials/']
+gallery_dirs = ['tutorials']
+subsection_order = ExplicitOrder([])
+
+def generate_doxygen_xml(app):
+    """Run the doxygen make commands if we're on the ReadTheDocs server"""
+    run_doxygen('..')
+
+def setup(app):
+    # Add hook for building doxygen xml when needed
+    # no c++ API for now
+    app.connect("builder-inited", generate_doxygen_xml)
+    app.add_stylesheet('css/tvm_theme.css')
+    app.add_config_value('recommonmark_config', {
+        'url_resolver': lambda url: github_doc_root + url,
+        'auto_doc_ref': True
+            }, True)
+    app.add_transform(AutoStructify)
+
+
+sphinx_gallery_conf = {
+    'backreferences_dir': 'gen_modules/backreferences',
+    'doc_module': ('vta', 'numpy'),
+'reference_url': {
+    'vta': None,
+    'tvm': 'http://docs.tvmlang.org',
+    'matplotlib': 'http://matplotlib.org',
+    'numpy': 'http://docs.scipy.org/doc/numpy-1.9.1'},
+    'examples_dirs': examples_dirs,
+    'gallery_dirs': gallery_dirs,
+    'subsection_order': subsection_order,
+    'find_mayavi_figures': False,
+    'filename_pattern': '.py',
+    'expected_failing_examples': []
+}
diff --git a/vta/docs/dev/index.rst b/vta/docs/dev/index.rst
new file mode 100644
index 000000000000..c78eda8473e0
--- /dev/null
+++ b/vta/docs/dev/index.rst
@@ -0,0 +1,11 @@
+VTA Design and Developer Guide
+==============================
+
+Building an hardware stack for deep learning involves many
+many systems-level design decisions.
+In this part of documentation, we share the rationale for the specific choices made when designing VTA.
+
+.. toctree::
+   :maxdepth: 2
+
+   runtime
diff --git a/vta/docs/dev/runtime.md b/vta/docs/dev/runtime.md
new file mode 100644
index 000000000000..7acab59ab0a1
--- /dev/null
+++ b/vta/docs/dev/runtime.md
@@ -0,0 +1,3 @@
+# VTA Runtime System
+
+TODO Document the hardware runtime system.
\ No newline at end of file
diff --git a/vta/docs/how_to/contribute.md b/vta/docs/how_to/contribute.md
new file mode 100644
index 000000000000..b0e0de93b5fe
--- /dev/null
+++ b/vta/docs/how_to/contribute.md
@@ -0,0 +1,102 @@
+# Contribute to VTA
+
+VTA has been developed by community members.
+Everyone is more than welcome to contribute.
+It is a way to make the project better and more accessible to more users.
+VTA is part of TVM software/hardware stack,
+you can improve the compiler performance by contributing to [TVM](https://github.com/dmlc/tvm)
+
+
+- Please add your name to [CONTRIBUTORS.md](https://github.com/dmlc/vta/blob/master/CONTRIBUTORS.md)
+- Please update [NEWS.md](https://github.com/dmlc/vta/blob/master/NEWS.md) to add note on your changes to the API or added a new document.
+
+## Guidelines
+* [Submit Pull Request](#submit-pull-request)
+* [Git Workflow Howtos](#git-workflow-howtos)
+  - [How to resolve conflict with master](#how-to-resolve-conflict-with-master)
+  - [How to combine multiple commits into one](#how-to-combine-multiple-commits-into-one)
+  - [What is the consequence of force push](#what-is-the-consequence-of-force-push)
+* [Document](#document)
+* [Testcases](#testcases)
+* [Core Library](#core-library)
+* [Python Package](#python-package)
+
+## Submit Pull Request
+* Before submit, please rebase your code on the most recent version of master, you can do it by
+```bash
+git remote add upstream [url to vta repo]
+git fetch upstream
+git rebase upstream/master
+```
+* If you have multiple small commits,
+  it might be good to merge them together(use git rebase then squash) into more meaningful groups.
+* Send the pull request!
+  - Fix the problems reported by automatic checks
+  - If you are contributing a new module or new function, add a test.
+
+## Git Workflow Howtos
+### How to resolve conflict with master
+- First rebase to most recent master
+```bash
+# The first two steps can be skipped after you do it once.
+git remote add upstream [url to vta repo]
+git fetch upstream
+git rebase upstream/master
+```
+- The git may show some conflicts it cannot merge, say ```conflicted.py```.
+  - Manually modify the file to resolve the conflict.
+  - After you resolved the conflict, mark it as resolved by
+```bash
+git add conflicted.py
+```
+- Then you can continue rebase by
+```bash
+git rebase --continue
+```
+- Finally push to your fork, you may need to force push here.
+```bash
+git push --force
+```
+
+### How to combine multiple commits into one
+Sometimes we want to combine multiple commits, especially when later commits are only fixes to previous ones,
+to create a PR with set of meaningful commits. You can do it by following steps.
+- Before doing so, configure the default editor of git if you haven't done so before.
+```bash
+git config core.editor the-editor-you-like
+```
+- Assume we want to merge last 3 commits, type the following commands
+```bash
+git rebase -i HEAD~3
+```
+- It will pop up an text editor. Set the first commit as ```pick```, and change later ones to ```squash```.
+- After you saved the file, it will pop up another text editor to ask you modify the combined commit message.
+- Push the changes to your fork, you need to force push.
+```bash
+git push --force
+```
+
+### Reset to the most recent master
+You can always use git reset to reset your version to the most recent master.
+Note that all your ***local changes will get lost***.
+So only do it when you do not have local changes or when your pull request just get merged.
+```bash
+git reset --hard [hash tag of master]
+git push --force
+```
+
+### What is the consequence of force push
+The previous two tips requires force push, this is because we altered the path of the commits.
+It is fine to force push to your own fork, as long as the commits changed are only yours.
+
+## Testcases
+- All the testcases are in tests
+
+## Core Library
+- Follow Google C style for C++.
+- We use doxygen to document all the interface code.
+- You can reproduce the linter checks by typing ```make lint```
+
+## Python Package
+- Always add docstring to the new functions in numpydoc format.
+- You can reproduce the linter checks by typing ```make lint```
diff --git a/vta/docs/how_to/install.md b/vta/docs/how_to/install.md
new file mode 100644
index 000000000000..c6bab2f1a660
--- /dev/null
+++ b/vta/docs/how_to/install.md
@@ -0,0 +1,19 @@
+Installation Guide
+==================
+This page gives instructions on how to build and use VTA
+
+To get started, clone tvm repo from github. It is important to clone the submodules along, with ```--recursive``` option.
+```bash
+git clone --recursive https://github.com/uwsaml/vta
+```
+For windows users who use github tools, you can open the git shell, and type the following command.
+```bash
+git submodule init
+git submodule update --init --recursive
+```
+
+## Build Hardware
+
+## Build Runtime
+
+## Use VTA Python Compiler Package
diff --git a/vta/docs/index.rst b/vta/docs/index.rst
new file mode 100644
index 000000000000..b30eac0840ab
--- /dev/null
+++ b/vta/docs/index.rst
@@ -0,0 +1,20 @@
+VTA Documentation
+=================
+
+Welcome to VTA documentation.
+
+
+Contents
+--------
+
+.. toctree::
+   :maxdepth: 1
+
+   self
+   how_to/install
+   tutorials/index
+   how_to/contribute
+   api/python/index
+   dev/index
+   api_links
+   genindex
diff --git a/vta/make/config.mk b/vta/make/config.mk
new file mode 100644
index 000000000000..76d817fb4d67
--- /dev/null
+++ b/vta/make/config.mk
@@ -0,0 +1,27 @@
+#-------------------------------------------------------------------------------
+#  Template configuration for compiling VTA runtime.
+#
+#  If you want to change the configuration, please use the following
+#  steps. Assume you are on the root directory of nnvm. First copy the this
+#  file so that any local changes will be ignored by git
+#
+#  $ cp make/config.mk .
+#
+#  Next modify the according entries, and then compile by
+#
+#  $ make
+#
+#  or build in parallel with 8 threads
+#
+#  $ make -j8
+#-------------------------------------------------------------------------------
+
+#---------------------
+# choice of compiler
+#--------------------
+
+# the additional link flags you want to add
+ADD_LDFLAGS=
+
+# the additional compile flags you want to add
+ADD_CFLAGS=
diff --git a/vta/python/vta/__init__.py b/vta/python/vta/__init__.py
new file mode 100644
index 000000000000..59a494fc7ef5
--- /dev/null
+++ b/vta/python/vta/__init__.py
@@ -0,0 +1,5 @@
+"""VTA Python package backed by TVM"""
+
+
+# version of this package
+__version__ = "0.1.0"
diff --git a/vta/tests/lint/pylintrc b/vta/tests/lint/pylintrc
new file mode 100644
index 000000000000..d9918902cb1d
--- /dev/null
+++ b/vta/tests/lint/pylintrc
@@ -0,0 +1,406 @@
+[MASTER]
+
+# Specify a configuration file.
+#rcfile=
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Add files or directories to the blacklist. They should be base names, not
+# paths.
+ignore=CVS, _cy2, _cy3
+
+# Add files or directories matching the regex patterns to the blacklist. The
+# regex matches against base names, not paths.
+ignore-patterns=
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+# Use multiple processes to speed up Pylint.
+jobs=8
+
+# Allow loading of arbitrary C extensions. Extensions are imported into the
+# active Python interpreter and may run arbitrary code.
+unsafe-load-any-extension=no
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code
+extension-pkg-whitelist=numpy,opencv
+
+# Allow optimization of some AST trees. This will activate a peephole AST
+# optimizer, which will apply various small optimizations. For instance, it can
+# be used to obtain the result of joining multiple strings with the addition
+# operator. Joining a lot of strings can lead to a maximum recursion error in
+# Pylint and this flag can prevent that. It has one side effect, the resulting
+# AST will be different than the one from reality. This option is deprecated
+# and it will be removed in Pylint 2.0.
+optimize-ast=no
+
+
+[MESSAGES CONTROL]
+
+# Only show warnings with the listed confidence levels. Leave empty to show
+# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
+confidence=
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time (only on the command line, not in the configuration file where
+# it should appear only once). See also the "--disable" option for examples.
+enable=indexing-exception,old-raise-syntax
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifiers separated by comma (,) or put this
+# option multiple times (only on the command line, not in the configuration
+# file where it should appear only once).You can also use "--disable=all" to
+# disable everything first and then reenable specific checks. For example, if
+# you want to run only the similarities checker, you can use "--disable=all
+# --enable=similarities". If you want to run only the classes checker, but have
+# no Warning level messages displayed, use"--disable=all --enable=classes
+# --disable=W"
+disable=design,similarities,no-self-use,attribute-defined-outside-init,locally-disabled,star-args,pointless-except,bad-option-value,global-statement,fixme,suppressed-message,useless-suppression,locally-enabled,no-member,no-name-in-module,import-error,unsubscriptable-object,unbalanced-tuple-unpacking,undefined-variable,protected-access
+
+[REPORTS]
+
+# Set the output format. Available formats are text, parseable, colorized, msvs
+# (visual studio) and html. You can also give a reporter class, eg
+# mypackage.mymodule.MyReporterClass.
+output-format=text
+
+# Put messages in a separate file for each module / package specified on the
+# command line instead of printing them on stdout. Reports (if any) will be
+# written in a file name "pylint_global.[txt|html]". This option is deprecated
+# and it will be removed in Pylint 2.0.
+files-output=no
+
+# Tells whether to display a full report or only the messages
+reports=no
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note). You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Template used to display messages. This is a python new-style format string
+# used to format the message information. See doc for all details
+#msg-template=
+
+
+[FORMAT]
+
+# Maximum number of characters on a single line.
+max-line-length=100
+
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=^\s*(# )?<?https?://\S+>?$
+
+# Allow the body of an if to be on the same line as the test if there is no
+# else.
+single-line-if-stmt=no
+
+# List of optional constructs for which whitespace checking is disabled. `dict-
+# separator` is used to allow tabulation in dicts, etc.: {1  : 1,\n222: 2}.
+# `trailing-comma` allows a space between comma and closing bracket: (a, ).
+# `empty-line` allows space-only lines.
+no-space-check=trailing-comma,dict-separator
+
+# Maximum number of lines in a module
+max-module-lines=1000
+
+# String used as indentation unit. This is usually "    " (4 spaces) or "\t" (1
+# tab).
+indent-string='    '
+
+# Number of spaces of indent required inside a hanging  or continued line.
+indent-after-paren=4
+
+# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
+expected-line-ending-format=
+
+
+[SPELLING]
+
+# Spelling dictionary name. Available dictionaries: none. To make it working
+# install python-enchant package.
+spelling-dict=
+
+# List of comma separated words that should not be checked.
+spelling-ignore-words=
+
+# A path to a file that contains private dictionary; one word per line.
+spelling-private-dict-file=
+
+# Tells whether to store unknown words to indicated private dictionary in
+# --spelling-private-dict-file option instead of raising a message.
+spelling-store-unknown-words=no
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,XXX
+
+
+[TYPECHECK]
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# List of module names for which member attributes should not be checked
+# (useful for modules/projects where namespaces are manipulated during runtime
+# and thus existing member attributes cannot be deduced by static analysis. It
+# supports qualified module names, as well as Unix pattern matching.
+ignored-modules=
+
+# List of class names for which member attributes should not be checked (useful
+# for classes with dynamically set attributes). This supports the use of
+# qualified names.
+ignored-classes=optparse.Values,thread._local,_thread._local
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E1101 when accessed. Python regular
+# expressions are accepted.
+generated-members=
+
+# List of decorators that produce context managers, such as
+# contextlib.contextmanager. Add to this list to register other decorators that
+# produce valid context managers.
+contextmanager-decorators=contextlib.contextmanager
+
+
+[LOGGING]
+
+# Logging modules to check that the string format arguments are in logging
+# function parameter format
+logging-modules=logging
+
+
+[SIMILARITIES]
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+# Ignore imports when computing similarities.
+ignore-imports=no
+
+
+[VARIABLES]
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# A regular expression matching the name of dummy variables (i.e. expectedly
+# not used).
+dummy-variables-rgx=(_+[a-zA-Z0-9]*?$)|dummy
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+# List of strings which can identify a callback function by name. A callback
+# name must start or end with one of those strings.
+callbacks=cb_,_cb
+
+# List of qualified module names which can have objects that can redefine
+# builtins.
+redefining-builtins-modules=six.moves,future.builtins
+
+
+[BASIC]
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=i,j,_,a,b,op,x,y,wd,lr,kv,k,v,s,p,h,c,m,n,X,t,g,f
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=
+
+# Colon-delimited sets of names that determine each other's naming style when
+# the name regexes allow several styles.
+name-group=
+
+# Include a hint for the correct naming format with invalid-name
+include-naming-hint=no
+
+# List of decorators that produce properties, such as abc.abstractproperty. Add
+# to this list to register other decorators that produce valid properties.
+property-classes=abc.abstractproperty
+
+# Regular expression matching correct module names
+module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Naming hint for module names
+module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Regular expression matching correct constant names
+const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Naming hint for constant names
+const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Regular expression matching correct inline iteration names
+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+
+# Naming hint for inline iteration names
+inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
+
+# Regular expression matching correct method names
+method-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for method names
+method-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct class attribute names
+class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
+
+# Naming hint for class attribute names
+class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
+
+# Regular expression matching correct argument names
+argument-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for argument names
+argument-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct attribute names
+attr-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for attribute names
+attr-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct variable names
+variable-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for variable names
+variable-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct function names
+function-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for function names
+function-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct class names
+class-rgx=[A-Z_][a-zA-Z0-9]+$
+
+# Naming hint for class names
+class-name-hint=[A-Z_][a-zA-Z0-9]+$
+
+# Regular expression which should only match function or class names that do
+# not require a docstring.
+no-docstring-rgx=^_
+
+# Minimum line length for functions/classes that require docstrings, shorter
+# ones are exempt.
+docstring-min-length=10
+
+
+[ELIF]
+
+# Maximum number of nested blocks for function / method body
+max-nested-blocks=5
+
+
+[CLASSES]
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,__new__,setUp
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+# List of valid names for the first argument in a metaclass class method.
+valid-metaclass-classmethod-first-arg=mcs
+
+# List of member names, which should be excluded from the protected access
+# warning.
+exclude-protected=_asdict,_fields,_replace,_source,_make
+
+
+[IMPORTS]
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=optparse
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled)
+import-graph=
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled)
+int-import-graph=
+
+# Force import order to recognize a module as part of the standard
+# compatibility libraries.
+known-standard-library=
+
+# Force import order to recognize a module as part of a third party library.
+known-third-party=enchant
+
+# Analyse import fallback blocks. This can be used to support both Python 2 and
+# 3 compatible code, which means that the block might have code that exists
+# only in one or another interpreter, leading to false positives when analysed.
+analyse-fallback-blocks=no
+
+
+[DESIGN]
+
+# Maximum number of arguments for function / method
+max-args=5
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore
+ignored-argument-names=_.*
+
+# Maximum number of locals for function / method body
+max-locals=15
+
+# Maximum number of return / yield for function / method body
+max-returns=6
+
+# Maximum number of branch for function / method body
+max-branches=12
+
+# Maximum number of statements in function / method body
+max-statements=50
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=0
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+# Maximum number of boolean expressions in a if statement
+max-bool-expr=5
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "Exception"
+overgeneral-exceptions=Exception
diff --git a/vta/tutorials/README.txt b/vta/tutorials/README.txt
new file mode 100644
index 000000000000..9e80d3587f11
--- /dev/null
+++ b/vta/tutorials/README.txt
@@ -0,0 +1,3 @@
+Tutorials
+=========
+This page contains the python tutorials about how to use TVM to program VTA.
diff --git a/vta/tutorials/get_started.py b/vta/tutorials/get_started.py
new file mode 100644
index 000000000000..d9fcb644eddf
--- /dev/null
+++ b/vta/tutorials/get_started.py
@@ -0,0 +1,39 @@
+"""
+Get Started with VTA
+====================
+**Author**: `Tianqi Chen <https://tqchen.github.io>`_
+
+This is an introduction tutorial to on how to use TVM to program VTA
+
+In this tutorial, we will demonstrate the basic workflow of VTA
+and how we can program the FPGA to run various instructions.
+
+To begin with, we need to import tvm which is our compiler stack for VTA.
+We also need to import vta python package which contains VTA specific
+extensions for compiler to generate code that runs on VTA.
+"""
+from __future__ import absolute_import, print_function
+
+import tvm
+import vta
+
+######################################################################
+# Program the FPGA with VTA Bistream
+# ----------------------------------
+# In the first step, we need to program the FPGA with VTA bitstream.
+#
+
+######################################################################
+# Run Simple Copy Instruction
+# ---------------------------
+#
+
+######################################################################
+# Run Matrix Instruction
+# ----------------------
+#
+
+######################################################################
+# Matrix Multiplication Example
+# -----------------------------
+#

From 84575393da04139544303c28f3dc2f2e518774d4 Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@cs.washington.edu>
Date: Sun, 18 Mar 2018 00:21:54 -0700
Subject: [PATCH 823/949] [REFACTOR] Code base refactoring (#5)

---
 vta/Makefile                                  |   12 +-
 vta/apps/pynq_rpc/start_rpc_server.sh         |    4 +
 vta/hardware/vivado/Makefile                  |   71 +-
 vta/hardware/vivado/scripts/hls.tcl           |    4 +-
 vta/hardware/vivado/sim/vta_test.cc           |    4 +-
 .../hls => hardware/vivado/src}/vta.cc        |    2 +-
 .../hls => hardware/vivado/src}/vta.h         |   84 +-
 vta/include/hardware/hls/vta_typedefs.h       |   97 --
 vta/include/vta/driver.h                      |  100 ++
 vta/include/{vta_params.h => vta/hw_spec.h}   |   14 +-
 vta/include/vta/runtime.h                     |  274 ++++
 vta/make/config.mk                            |   69 +
 .../vta_pynq_driver.c => pynq/pynq_driver.cc} |   38 +-
 .../pynq/pynq_driver.h}                       |   75 +-
 vta/src/runtime.cc                            | 1410 +++++++++++++++++
 vta/src/tvm/vta_device_api.cc                 |  106 ++
 vta/tests/driver/Makefile                     |   59 -
 .../hardware/common/test_lib.cc}              |   60 +-
 .../hardware/common/test_lib.h}               |   13 +-
 vta/tests/hardware/pynq/Makefile              |   37 +
 .../pynq/metal_test.cc}                       |   53 +-
 21 files changed, 2221 insertions(+), 365 deletions(-)
 create mode 100755 vta/apps/pynq_rpc/start_rpc_server.sh
 rename vta/{src/hardware/hls => hardware/vivado/src}/vta.cc (99%)
 rename vta/{include/hardware/hls => hardware/vivado/src}/vta.h (65%)
 delete mode 100644 vta/include/hardware/hls/vta_typedefs.h
 create mode 100644 vta/include/vta/driver.h
 rename vta/include/{vta_params.h => vta/hw_spec.h} (99%)
 create mode 100644 vta/include/vta/runtime.h
 rename vta/src/{driver/pynq/vta_pynq_driver.c => pynq/pynq_driver.cc} (67%)
 rename vta/{include/vta_pynq_driver.h => src/pynq/pynq_driver.h} (50%)
 create mode 100644 vta/src/runtime.cc
 create mode 100644 vta/src/tvm/vta_device_api.cc
 delete mode 100644 vta/tests/driver/Makefile
 rename vta/{src/test/vta_test_lib.cc => tests/hardware/common/test_lib.cc} (97%)
 rename vta/{include/vta_test_lib.h => tests/hardware/common/test_lib.h} (98%)
 create mode 100644 vta/tests/hardware/pynq/Makefile
 rename vta/tests/{driver/driver_test.cc => hardware/pynq/metal_test.cc} (71%)

diff --git a/vta/Makefile b/vta/Makefile
index 20414cde9304..6007ed2eeaef 100644
--- a/vta/Makefile
+++ b/vta/Makefile
@@ -54,9 +54,13 @@ endif
 
 all: lib/libvta.$(SHARED_LIBRARY_SUFFIX)
 
-SRC = $(wildcard src/*.cc src/*.cc)
-ALL_OBJ = $(patsubst %.cc, build/%.o, $(SRC))
-ALL_DEP = $(ALL_OBJ)
+VTA_LIB_SRC = $(wildcard src/*.cc src/tvm/*.cc)
+ifeq ($(TARGET), PYNQ_TARGET)
+	VTA_LIB_SRC += $(wildcard src/pynq/*.cc)
+	LDFLAGS += -L/usr/lib -lsds_lib
+	LDFLAGS += -L/opt/python3.6/lib/python3.6/site-packages/pynq/drivers/ -l:libdma.so
+endif
+VTA_LIB_OBJ = $(patsubst %.cc, build/%.o, $(VTA_LIB_SRC))
 
 test: $(TEST)
 
@@ -65,7 +69,7 @@ build/src/%.o: src/%.cc
 	$(CXX) $(CFLAGS) -MM -MT build/src/$*.o $< >build/src/$*.d
 	$(CXX) -c $(CFLAGS) -c $< -o $@
 
-lib/libvta.$(SHARED_LIBRARY_SUFFIX): $(ALL_DEP)
+lib/libvta.$(SHARED_LIBRARY_SUFFIX): $(VTA_LIB_OBJ)
 	@mkdir -p $(@D)
 	$(CXX) $(CFLAGS) -shared -o $@ $(filter %.o, $^) $(LDFLAGS)
 
diff --git a/vta/apps/pynq_rpc/start_rpc_server.sh b/vta/apps/pynq_rpc/start_rpc_server.sh
new file mode 100755
index 000000000000..d5a1202a1dfc
--- /dev/null
+++ b/vta/apps/pynq_rpc/start_rpc_server.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+export PYTHONPATH=${PYTHONPATH}:/home/xilinx/tvm/python
+export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/opt/python3.6/lib/python3.6/site-packages/pynq/drivers/
+python -m  tvm.exec.rpc_server --load-library /home/xilinx/vta/lib/libvta.so
diff --git a/vta/hardware/vivado/Makefile b/vta/hardware/vivado/Makefile
index b52fb628cf37..dfcb06316e4d 100644
--- a/vta/hardware/vivado/Makefile
+++ b/vta/hardware/vivado/Makefile
@@ -2,9 +2,9 @@
 ROOTDIR = $(CURDIR)
 BUILD_DIR = $(ROOTDIR)/build
 SCRIPT_DIR = $(ROOTDIR)/scripts
-SRC_DIR = $(ROOTDIR)/../../src/hardware/hls
+SRC_DIR = $(ROOTDIR)/src
 SIM_DIR = $(ROOTDIR)/sim
-TEST_DIR = $(ROOTDIR)/../../src/test
+TEST_DIR = $(ROOTDIR)/../../tests/hardware/common
 INCLUDE_DIR = $(ROOTDIR)/../../include
 
 # Executables
@@ -12,59 +12,28 @@ VIVADO_HLS = vivado_hls
 VIVADO = vivado
 HSI = hsi
 
-# Build parameters:
+# Include top-level config file
+ifndef config
+ifneq ("$(wildcard ../../config.mk)", "")
+	config = ../../config.mk
+else
+	config = ../../make/config.mk
+endif
+endif
+include $(config)
+
+#---------------------
+# Compilation parameters
+#--------------------
+
 #  Number of threads during compilation
 NUM_THREADS = 8
+
 #  Target Frequency
 CLOCK_FREQ = 100
-#  Log of input width in bits
-LOG_INP_WIDTH = 3
-#  Log of weight width in bits
-LOG_WGT_WIDTH = 3
-#  Log of accum width in bits
-LOG_ACC_WIDTH = 5
-#  Log of output width in bits
-LOG_OUT_WIDTH = $(LOG_INP_WIDTH)
-#  Log of tensor batch size (A in (A,B)x(B,C) matrix multiplication)
-LOG_BATCH = 0
-#  Log of tensor inner block size (B in (A,B)x(B,C) matrix multiplication)
-LOG_IN_BLOCK = 4
-#  Log of tensor outer block size (C in (A,B)x(B,C) matrix multiplication)
-LOG_OUT_BLOCK = 4
-#  Log of uop buffer size in Bytes
-LOG_UOP_BUFF_SIZE = 15
-#  Log of inp buffer size in Bytes
-LOG_INP_BUFF_SIZE = 15
-#  Log of wgt buffer size in Bytes
-LOG_WGT_BUFF_SIZE = 15
-#  Log of acc buffer size in Bytes
-LOG_ACC_BUFF_SIZE = 17
-#  Log of out buffer size in Bytes
-LOG_OUT_BUFF_SIZE = $(shell echo "$$(( $(LOG_ACC_BUFF_SIZE)+$(LOG_OUT_WIDTH)-$(LOG_ACC_WIDTH) ))" )
 
-# Derived parameter
-#  Input width in bits
-INP_WIDTH = $(shell echo "$$(( 1 << $(LOG_INP_WIDTH) ))" )
-#  Weight width in bits
-WGT_WIDTH = $(shell echo "$$(( 1 << $(LOG_WGT_WIDTH) ))" )
-#  Output width in bits
-OUT_WIDTH = $(shell echo "$$(( 1 << $(LOG_OUT_WIDTH) ))" )
-#  Tensor batch size
-BATCH = $(shell echo "$$(( 1 << $(LOG_BATCH) ))" )
-#  Tensor outer block size
-IN_BLOCK = $(shell echo "$$(( 1 << $(LOG_IN_BLOCK) ))" )
-#  Tensor inner block size
-OUT_BLOCK = $(shell echo "$$(( 1 << $(LOG_OUT_BLOCK) ))" )
-#  Uop buffer size in Bytes
-UOP_BUFF_SIZE = $(shell echo "$$(( 1 << $(LOG_UOP_BUFF_SIZE) ))" )
-#  Inp buffer size in Bytes
-INP_BUFF_SIZE = $(shell echo "$$(( 1 << $(LOG_INP_BUFF_SIZE) ))" )
-#  Wgt buffer size in Bytes
-WGT_BUFF_SIZE = $(shell echo "$$(( 1 << $(LOG_WGT_BUFF_SIZE) ))" )
-#  Acc buffer size in Bytes
-ACC_BUFF_SIZE = $(shell echo "$$(( 1 << $(LOG_ACC_BUFF_SIZE) ))" )
-#  Out buffer size in Bytes
-OUT_BUFF_SIZE = $(shell echo "$$(( 1 << $(LOG_OUT_BUFF_SIZE) ))" )
+#  Timing closure compensation (0 for none, 3 for highest)
+TIMING_CLOSURE_COMP = 0
 
 # Derive clock target period
 TARGET_PER = $(shell echo "$$(( (1000 + $(CLOCK_FREQ) - 1) / $(CLOCK_FREQ) - 0))" )
@@ -85,7 +54,7 @@ ip:
 		$(VIVADO_HLS) -f $(SCRIPT_DIR)/hls.tcl \
 			-tclargs $(SRC_DIR) $(SIM_DIR) $(TEST_DIR) $(INCLUDE_DIR) $(TARGET_PER) \
 			$(LOG_INP_WIDTH) $(LOG_WGT_WIDTH) $(LOG_ACC_WIDTH) $(LOG_OUT_WIDTH) \
-			$(LOG_BATCH) $(LOG_OUT_BLOCK) $(LOG_IN_BLOCK) \
+			$(LOG_BATCH) $(LOG_BLOCK_OUT) $(LOG_BLOCK_IN) \
 			$(LOG_UOP_BUFF_SIZE) $(LOG_INP_BUFF_SIZE) $(LOG_WGT_BUFF_SIZE) \
 			$(LOG_ACC_BUFF_SIZE) $(LOG_OUT_BUFF_SIZE)
 
diff --git a/vta/hardware/vivado/scripts/hls.tcl b/vta/hardware/vivado/scripts/hls.tcl
index 55b3c01d5012..220c8f3ba3bf 100644
--- a/vta/hardware/vivado/scripts/hls.tcl
+++ b/vta/hardware/vivado/scripts/hls.tcl
@@ -62,7 +62,7 @@ if { [llength $argv] eq 19 } {
 }
 
 # C define flags to pass to compiler
-set cflags "-I $include_dir -I $include_dir/hardware/hls \
+set cflags "-I $include_dir -I $src_dir -I $test_dir \
 	-DDEBUG=0 -DLOG_WGT_WIDTH=$wgt_width -DLOG_INP_WIDTH=$inp_width \
 	-DLOG_ACC_WIDTH=$acc_width -DLOG_OUT_WIDTH=$out_width \
 	-DLOG_BATCH=$batch -DLOG_BLOCK_OUT=$block_out -DLOG_BLOCK_IN=$block_in \
@@ -127,7 +127,7 @@ open_project vta_sim
 set_top vta
 add_files $src_dir/vta.cc -cflags $cflags
 add_files -tb $sim_dir/vta_test.cc -cflags $cflags
-add_files -tb $test_dir/vta_test_lib.cc -cflags $cflags
+add_files -tb $test_dir/test_lib.cc -cflags $cflags
 open_solution "solution0"
 init_design $target_period $inp_width $wgt_width $out_width $batch $block_in $block_out
 csim_design -clean
diff --git a/vta/hardware/vivado/sim/vta_test.cc b/vta/hardware/vivado/sim/vta_test.cc
index 858926aaa4db..2031186f31ce 100644
--- a/vta/hardware/vivado/sim/vta_test.cc
+++ b/vta/hardware/vivado/sim/vta_test.cc
@@ -8,8 +8,8 @@
 #include <stdlib.h>
 #include <iostream>
 
-#include "vta.h"
-#include "vta_test_lib.h"
+#include "../src/vta.h"
+#include "../../../tests/hardware/common/test_lib.h"
 
 int main(void)
 {
diff --git a/vta/src/hardware/hls/vta.cc b/vta/hardware/vivado/src/vta.cc
similarity index 99%
rename from vta/src/hardware/hls/vta.cc
rename to vta/hardware/vivado/src/vta.cc
index 206fb5fefa0d..7a0f9ded25ee 100644
--- a/vta/src/hardware/hls/vta.cc
+++ b/vta/hardware/vivado/src/vta.cc
@@ -8,7 +8,7 @@
 #include <stdlib.h>
 #include <string.h>
 
-#include "vta.h"
+#include "./vta.h"
 
 void fetch (
   uint32_t insn_count,
diff --git a/vta/include/hardware/hls/vta.h b/vta/hardware/vivado/src/vta.h
similarity index 65%
rename from vta/include/hardware/hls/vta.h
rename to vta/hardware/vivado/src/vta.h
index b959d8a23cba..5dd4d953e436 100644
--- a/vta/include/hardware/hls/vta.h
+++ b/vta/hardware/vivado/src/vta.h
@@ -11,8 +11,88 @@
 #include <ap_int.h>
 #include <hls_stream.h>
 
-#include "vta_typedefs.h"
-#include "vta_params.h"
+#include <vta/hw_spec.h>
+
+/* \typedef uop_T Micro-op datatype*/
+typedef ap_uint<UOP_WIDTH> uop_T;
+
+/* \typedef inp_T Input datatype*/
+typedef ap_int<INP_WIDTH> inp_T;
+
+/* \typedef wgt_T Weight datatype*/
+typedef ap_int<WGT_WIDTH> wgt_T;
+
+/* \typedef out_T Output datatype*/
+typedef ap_int<OUT_WIDTH> out_T;
+
+/* \typedef acc_T Accumulator datatype*/
+typedef ap_int<ACC_WIDTH> acc_T;
+
+/* \typedef mul_T Multiplier output datatype*/
+typedef ap_int<WGT_WIDTH+INP_WIDTH+1> mul_T;
+
+/* \typedef sum_T GEMM accumulator datatype*/
+typedef ap_int<WGT_WIDTH+INP_WIDTH+LOG_BLOCK_IN+1> sum_T;
+
+/* \typedef inp_vec_T Input vector datatype*/
+typedef ap_uint<INP_WIDTH*BLOCK_IN> inp_vec_T;
+
+/* \typedef wgt_vec_T Weight vector datatype*/
+typedef ap_uint<WGT_WIDTH*BLOCK_IN> wgt_vec_T;
+
+/* \typedef acc_vec_T Accumulator vector datatype*/
+typedef ap_uint<ACC_WIDTH*BLOCK_OUT> acc_vec_T;
+
+/* \typedef out_vec_T Output vector datatype*/
+typedef ap_uint<OUT_WIDTH*BLOCK_OUT> out_vec_T;
+
+/* \typedef uop_idx_T Micro-op SRAM index datatype*/
+typedef ap_uint<LOG_UOP_BUFF_DEPTH+1> uop_idx_T;
+
+/* \typedef inp_idx_T Input SRAM index datatype*/
+typedef ap_uint<LOG_INP_BUFF_DEPTH+1> inp_idx_T;
+
+/* \typedef wgt_idx_T Weight SRAM index datatype*/
+typedef ap_uint<LOG_WGT_BUFF_DEPTH+1> wgt_idx_T;
+
+/* \typedef acc_idx_T Accumulator SRAM index datatype*/
+typedef ap_uint<LOG_ACC_BUFF_DEPTH+1> acc_idx_T;
+
+/* \typedef opcode_T Opcode datatype*/
+typedef ap_uint<OPCODE_BIT_WIDTH> opcode_T;
+
+/* \typedef insn_T Instruction datatype*/
+typedef ap_uint<INS_WIDTH> insn_T;
+
+/* \typedef loop_T Loop bound datatype*/
+typedef ap_uint<LOOP_ITER_WIDTH> loop_T;
+
+/* \typedef memop_id_T Memory operation ID datatype*/
+typedef ap_uint<MEMOP_ID_BIT_WIDTH> memop_id_T;
+
+/* \typedef memop_sram_T Memory operation SRAM index datatype*/
+typedef ap_uint<MEMOP_SRAM_ADDR_BIT_WIDTH> memop_sram_T;
+
+/* \typedef memop_dram_T Memory operation DRAM index datatype*/
+typedef ap_uint<MEMOP_DRAM_ADDR_BIT_WIDTH> memop_dram_T;
+
+/* \typedef memop_size_T Memory operation range datatype*/
+typedef ap_uint<MEMOP_SIZE_BIT_WIDTH> memop_size_T;
+
+/* \typedef memop_stride_T Memory operation stride datatype*/
+typedef ap_uint<MEMOP_STRIDE_BIT_WIDTH> memop_stride_T;
+
+/* \typedef memop_pad_T Memory operation pad width datatype*/
+typedef ap_uint<MEMOP_PAD_BIT_WIDTH> memop_pad_T;
+
+/* \typedef aluop_opcode_T ALU operation opcode datatype*/
+typedef ap_uint<ALU_OPCODE_BIT_WIDTH> aluop_opcode_T;
+
+/* \typedef aluop_opcode_T ALU operation immediate datatype*/
+typedef ap_int<ALUOP_IMM_BIT_WIDTH> aluop_imm_T;
+
+/* \typedef aluop_opcode_T ALU operation shift immediate datatype*/
+typedef ap_uint<LOG_ACC_WIDTH> aluop_sh_imm_T;
 
 /*!
 * \brief Fetch module.
diff --git a/vta/include/hardware/hls/vta_typedefs.h b/vta/include/hardware/hls/vta_typedefs.h
deleted file mode 100644
index b2e90e233569..000000000000
--- a/vta/include/hardware/hls/vta_typedefs.h
+++ /dev/null
@@ -1,97 +0,0 @@
-/*!
- *  Copyright (c) 2018 by Contributors
- * \file vta_typedefs.h
- * \brief Type definitions for VTA HLS design.
- */
-#ifndef VTA_TYPEDEFS_H_
-#define VTA_TYPEDEFS_H_
-
-#include <assert.h>
-#include <ap_axi_sdata.h>
-#include <ap_int.h>
-#include <hls_stream.h>
-
-#include "vta_params.h"
-
-/* \typedef uop_T Micro-op datatype*/
-typedef ap_uint<UOP_WIDTH> uop_T;
-
-/* \typedef inp_T Input datatype*/
-typedef ap_int<INP_WIDTH> inp_T;
-
-/* \typedef wgt_T Weight datatype*/
-typedef ap_int<WGT_WIDTH> wgt_T;
-
-/* \typedef out_T Output datatype*/
-typedef ap_int<OUT_WIDTH> out_T;
-
-/* \typedef acc_T Accumulator datatype*/
-typedef ap_int<ACC_WIDTH> acc_T;
-
-/* \typedef mul_T Multiplier output datatype*/
-typedef ap_int<WGT_WIDTH+INP_WIDTH+1> mul_T;
-
-/* \typedef sum_T GEMM accumulator datatype*/
-typedef ap_int<WGT_WIDTH+INP_WIDTH+LOG_BLOCK_IN+1> sum_T;
-
-/* \typedef inp_vec_T Input vector datatype*/
-typedef ap_uint<INP_WIDTH*BLOCK_IN> inp_vec_T;
-
-/* \typedef wgt_vec_T Weight vector datatype*/
-typedef ap_uint<WGT_WIDTH*BLOCK_IN> wgt_vec_T;
-
-/* \typedef acc_vec_T Accumulator vector datatype*/
-typedef ap_uint<ACC_WIDTH*BLOCK_OUT> acc_vec_T;
-
-/* \typedef out_vec_T Output vector datatype*/
-typedef ap_uint<OUT_WIDTH*BLOCK_OUT> out_vec_T;
-
-/* \typedef uop_idx_T Micro-op SRAM index datatype*/
-typedef ap_uint<LOG_UOP_BUFF_DEPTH+1> uop_idx_T;
-
-/* \typedef inp_idx_T Input SRAM index datatype*/
-typedef ap_uint<LOG_INP_BUFF_DEPTH+1> inp_idx_T;
-
-/* \typedef wgt_idx_T Weight SRAM index datatype*/
-typedef ap_uint<LOG_WGT_BUFF_DEPTH+1> wgt_idx_T;
-
-/* \typedef acc_idx_T Accumulator SRAM index datatype*/
-typedef ap_uint<LOG_ACC_BUFF_DEPTH+1> acc_idx_T;
-
-/* \typedef opcode_T Opcode datatype*/
-typedef ap_uint<OPCODE_BIT_WIDTH> opcode_T;
-
-/* \typedef insn_T Instruction datatype*/
-typedef ap_uint<INS_WIDTH> insn_T;
-
-/* \typedef loop_T Loop bound datatype*/
-typedef ap_uint<LOOP_ITER_WIDTH> loop_T;
-
-/* \typedef memop_id_T Memory operation ID datatype*/
-typedef ap_uint<MEMOP_ID_BIT_WIDTH> memop_id_T;
-
-/* \typedef memop_sram_T Memory operation SRAM index datatype*/
-typedef ap_uint<MEMOP_SRAM_ADDR_BIT_WIDTH> memop_sram_T;
-
-/* \typedef memop_dram_T Memory operation DRAM index datatype*/
-typedef ap_uint<MEMOP_DRAM_ADDR_BIT_WIDTH> memop_dram_T;
-
-/* \typedef memop_size_T Memory operation range datatype*/
-typedef ap_uint<MEMOP_SIZE_BIT_WIDTH> memop_size_T;
-
-/* \typedef memop_stride_T Memory operation stride datatype*/
-typedef ap_uint<MEMOP_STRIDE_BIT_WIDTH> memop_stride_T;
-
-/* \typedef memop_pad_T Memory operation pad width datatype*/
-typedef ap_uint<MEMOP_PAD_BIT_WIDTH> memop_pad_T;
-
-/* \typedef aluop_opcode_T ALU operation opcode datatype*/
-typedef ap_uint<ALU_OPCODE_BIT_WIDTH> aluop_opcode_T;
-
-/* \typedef aluop_opcode_T ALU operation immediate datatype*/
-typedef ap_int<ALUOP_IMM_BIT_WIDTH> aluop_imm_T;
-
-/* \typedef aluop_opcode_T ALU operation shift immediate datatype*/
-typedef ap_uint<LOG_ACC_WIDTH> aluop_sh_imm_T;
-
-#endif // VTA_TYPEDEFS_H_
diff --git a/vta/include/vta/driver.h b/vta/include/vta/driver.h
new file mode 100644
index 000000000000..2b5e0ea93674
--- /dev/null
+++ b/vta/include/vta/driver.h
@@ -0,0 +1,100 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file vta_driver.h
+ * \brief General driver interface.
+ */
+
+#ifndef VTA_DRIVER_H_
+#define VTA_DRIVER_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdlib.h>
+#include <stdint.h>
+
+/*! \brief Memory management constants with libxlnk_cma */
+#define CACHED 1
+/*! \brief Memory management constants with libxlnk_cma */
+#define NOT_CACHED 0
+
+/*! \brief VTA command handle */
+typedef void * VTAHandle;
+
+/*!
+ * \brief Allocates physically contiguous region in memory (limited by MAX_XFER).
+ * \param size Size of the region in Bytes.
+ * \param cached Region can be set to not cached (write-back) if set to 0.
+ * \return A pointer to the allocated region.
+ */
+void* VTAMemAlloc(size_t size, int cached);
+
+/*!
+ * \brief Frees a physically contiguous region in memory.
+ * \param buf Buffer to free.
+ */
+void VTAMemFree(void* buf);
+
+/*!
+ * \brief Returns a physical address to the region of memory allocated with VTAMemAlloc.
+ * \param buf Pointer to memory region allocated with VTAMemAlloc.
+ * \return The physical address of the memory region.
+ */
+uint32_t VTAGetMemPhysAddr(void* buf);
+
+/*!
+ * \brief Flushes the region of memory out of the CPU cache to DRAM.
+ * \param buf Pointer to memory region allocated with VTAMemAlloc to be flushed.
+ * \param size Size of the region to flush in Bytes.
+ */
+void VTAFlushCache(void* buf, int size);
+
+/*!
+ * \brief Invalidates the region of memory that is cached.
+ * \param buf Pointer to memory region allocated with VTAMemAlloc to be invalidated.
+ * \param size Size of the region to invalidate in Bytes.
+ */
+void VTAInvalidateCache(void* buf, int size);
+
+/*!
+ * \brief Returns a memory map to FPGA configuration registers.
+ * \param addr The base physical address of the configuration registers.
+ * \param length The size of the memory mapped region in bytes.
+ * \return A pointer to the memory mapped region.
+ */
+void *VTAMapRegister(unsigned addr, size_t length);
+
+/*!
+ * \brief Deletes the configuration register memory map.
+ * \param vta The memory mapped region.
+ * \param length The size of the memory mapped region in bytes.
+ */
+void VTAUnmapRegister(void *vta, size_t length);
+
+/*!
+ * \brief Writes to a memory mapped configuration register.
+ * \param vta_base The handle to the memory mapped configuration registers.
+ * \param offset The offset of the register to write to.
+ * \param val The value to be written to the memory mapped register.
+ */
+void VTAWriteMappedReg(VTAHandle vta_base, unsigned offset, unsigned val);
+
+/*!
+ * \brief Reads from the memory mapped configuration register.
+ * \param vta_base The handle to the memory mapped configuration registers.
+ * \param offset The offset of the register to read from.
+ * \return The value read from the memory mapped register.
+ */
+unsigned VTAReadMappedReg(VTAHandle vta_base, unsigned offset);
+
+/*!
+ * \brief Programming the bit stream on the FPGA.
+ * \param bitstream The path to the bit stream file.
+ */
+void VTAProgram(const char* bitstream);
+
+#ifdef __cplusplus
+}
+#endif
+#endif // VTA_DRIVER_H_
diff --git a/vta/include/vta_params.h b/vta/include/vta/hw_spec.h
similarity index 99%
rename from vta/include/vta_params.h
rename to vta/include/vta/hw_spec.h
index 748e77d9bd27..b18e94e63a07 100644
--- a/vta/include/vta_params.h
+++ b/vta/include/vta/hw_spec.h
@@ -3,8 +3,13 @@
  * \file vta_defines.h
  * \brief Preprocessor definitions for VTA HLS design and runtime.
  */
-#ifndef VTA_DEFINES_H_
-#define VTA_DEFINES_H_
+
+#ifndef VTA_HW_SPEC_H_
+#define VTA_HW_SPEC_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
 
 #include <stdint.h>
 
@@ -556,4 +561,7 @@ typedef struct {
   uint32_t wgt_idx    : LOG_WGT_BUFF_DEPTH;
 } VTAUop;
 
-#endif // VTA_DEFINES_H_
+#ifdef __cplusplus
+}
+#endif
+#endif // VTA_HW_SPEC_H_
diff --git a/vta/include/vta/runtime.h b/vta/include/vta/runtime.h
new file mode 100644
index 000000000000..e1aae32f43a9
--- /dev/null
+++ b/vta/include/vta/runtime.h
@@ -0,0 +1,274 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file runtime.h
+ * \brief VTA runtime library.
+ */
+
+#ifndef VTA_RUNTIME_H_
+#define VTA_RUNTIME_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "./driver.h"
+
+#define VTA_MEMCPY_H2D 1
+#define VTA_MEMCPY_D2H 2
+#define VTA_MEMCPY_D2D 3
+
+#define VTA_DEBUG_DUMP_INSN (1 << 1)
+#define VTA_DEBUG_DUMP_UOP (1 << 2)
+#define VTA_DEBUG_SKIP_READ_BARRIER (1 << 3)
+#define VTA_DEBUG_SKIP_WRITE_BARRIER (1 << 4)
+#define VTA_DEBUG_FORCE_SERIAL (1 << 5)
+
+/*! \brief VTA command handle */
+typedef void * VTACommandHandle;
+
+/*! \brief Shutdown hook of VTA to cleanup resources */
+void VTARuntimeShutdown();
+
+/*!
+ * \brief Get thread local command handle.
+ * \return A thread local command handle.
+ */
+VTACommandHandle VTATLSCommandHandle();
+
+/*!
+ * \brief Allocate data buffer.
+ * \param cmd The VTA command handle.
+ * \param size Buffer size.
+ * \return A pointer to the allocated buffer.
+ */
+void* VTABufferAlloc(VTACommandHandle cmd, size_t size);
+
+/*!
+ * \brief Free data buffer.
+ * \param cmd The VTA command handle.
+ * \param buffer The data buffer to be freed.
+ */
+void VTABufferFree(VTACommandHandle cmd, void* buffer);
+
+/*!
+ * \brief Get the buffer access pointer on CPU.
+ * \param cmd The VTA command handle.
+ * \param buffer The data buffer.
+ * \return The pointer that can be accessed by the CPU.
+ */
+void* VTABufferCPUPtr(VTACommandHandle cmd, void* buffer);
+
+/*!
+ * \brief Copy data buffer from one location to another.
+ * \param cmd The VTA command handle.
+ * \param from The source buffer base address.
+ * \param from_offset The offset of the source buffer.
+ * \param to The target buffer base address.
+ * \param to_offset The offset of the target buffer.
+ * \param size Size of copy.
+ * \param kind_mask The memory copy kind.
+ */
+void VTABufferCopy(VTACommandHandle cmd,
+                   const void* from,
+                   size_t from_offset,
+                   void* to,
+                   size_t to_offset,
+                   size_t size,
+                   int kind_mask);
+
+/*!
+ * \brief Set debug mode on the command handle.
+ * \param cmd The VTA command handle.
+ * \param debug_flag The debug flag.
+ */
+void VTASetDebugMode(VTACommandHandle cmd, int debug_flag);
+
+/*!
+ * \brief Perform a write barrier to make a memory region visible to the CPU.
+ * \param cmd The VTA command handle.
+ * \param buffer The head buffer pointer.
+ * \param elem_bits The size in bits of each element.
+ * \param start The start of the region (in elements).
+ * \param extent The end of the region (in elements).
+ */
+void VTAWriteBarrier(VTACommandHandle cmd,
+                     void* buffer, uint32_t elem_bits,
+                     uint32_t start, uint32_t extent);
+
+/*!
+ * \brief Perform a read barrier to a memory region visible to VTA.
+ * \param cmd The VTA command handle.
+ * \param buffer The head buffer pointer.
+ * \param elem_bits The unit bits of each elements.
+ * \param start The start of the region (in elements).
+ * \param extent The end of the region (in elements).
+ */
+void VTAReadBarrier(VTACommandHandle cmd,
+                    void* buffer, uint32_t elem_bits,
+                    uint32_t start, uint32_t extent);
+
+/*!
+ * \brief Perform a 2D data load from DRAM.
+ *  Sizes are measured in units of vector elements.
+ * \param cmd The VTA command handle.
+ * \param src_dram_addr Source DRAM address.
+ * \param src_elem_offset The source DRAM offset in number of unit elements.
+ * \param x_size The lowest dimension (x axis) size in number of unit elements.
+ * \param y_size The number of rows (y axis).
+ * \param x_stride The x axis stride.
+ * \param x_pad_before The start padding on x axis.
+ * \param y_pad_before The start padding on y axis.
+ * \param x_pad_after The end padding on x axis.
+ * \param y_pad_after The end padding of y axis.
+ * \param dst_sram_index Destination SRAM index.
+ * \param dst_memory_type Destination memory type.
+ */
+void VTALoadBuffer2D(VTACommandHandle cmd,
+                     void* src_dram_addr,
+                     uint32_t src_elem_offset,
+                     uint32_t x_size,
+                     uint32_t y_size,
+                     uint32_t x_stride,
+                     uint32_t x_pad_before,
+                     uint32_t y_pad_before,
+                     uint32_t x_pad_after,
+                     uint32_t y_pad_after,
+                     uint32_t dst_sram_index,
+                     uint32_t dst_memory_type);
+
+/*!
+ * \brief Perform a 2D data store into DRAM
+ *  Sizes are measured in units of vector elements.
+ * \param cmd The VTA command handle.
+ * \param src_sram_index Source SRAM index.
+ * \param src_memory_type Source memory type.
+ * \param dst_dram_addr Destination DRAM address.
+ * \param x_size The lowest dimension (x axis) size in number of unit elements.
+ * \param y_size The number of rows.
+ * \param x_stride The x axis stride.
+ */
+void VTAStoreBuffer2D(VTACommandHandle cmd,
+                      uint32_t src_sram_index,
+                      uint32_t src_memory_type,
+                      void* dst_dram_addr,
+                      uint32_t dst_elem_offset,
+                      uint32_t x_size,
+                      uint32_t y_size,
+                      uint32_t x_stride);
+
+/*!
+ * \brief Push uop into kernel buffer.
+ * In GEMM mode, do a blocked GEMM with 2d access pattern.
+ * In ALU mode, do a vectorized ALU operation with 2d access pattern.
+ *
+ *  \code
+ *
+ *   DType accum[INP_BUFF_DEPTH][l][n];
+ *   DType weight[WGT_BUFF_DEPTH][n][m];
+ *   DType input[INP_BUFF_DEPTH][l][m];
+ *   if reset_out == 1
+ *    accum[dst_index] = 0
+ *   elif mode == 0
+ *    accum[dst_index] += GEMM(input[src_index], weight[wgt_index]);
+ *   else
+ *    if (use_imm)
+ *      accum[dst_index] = opcode(accum[dst_index], imm_val);
+ *    else
+ *      accum[dst_index] = opcode(accum[dst_index], accum[src_index]);
+ *
+ *  \endcode
+ *
+ * \param mode Set to GEMM mode if set to 0, ALU mode is set to 1.
+ * \param reset_out Resets the accum to 0.
+ * \param dst_index The accum memory index.
+ * \param src_index The input memory (gemm) / accum memory (alu) index.
+ * \param wgt_index The weight memory index.
+ * \param opcode The ALU opcode.
+ * \param use_imm Use immediate in ALU mode if set to true.
+ * \param imm_val Immediate value in ALU mode.
+ */
+void VTAUopPush(uint32_t mode,
+                uint32_t reset_out,
+                uint32_t dst_index,
+                uint32_t src_index,
+                uint32_t wgt_index,
+                uint32_t opcode,
+                uint32_t use_imm,
+                uint32_t imm_val);
+
+/*!
+ * \brief Mark start of a micro op loop.
+ * \param extent The extent of the loop.
+ * \param dst_factor The accum factor.
+ * \param src_factor The input factor.
+ * \param wgt_factor The weight factor.
+ */
+void VTAUopLoopBegin(uint32_t extent,
+                     uint32_t dst_factor,
+                     uint32_t src_factor,
+                     uint32_t wgt_factor);
+
+/*!
+ * \brief Mark end of a micro op loop.
+ */
+void VTAUopLoopEnd();
+
+/*!
+ * \brief Push GEMM uop kernel into the command handle.
+ * \param uop_handle The uop cache handle.
+ * \param finit The initalization function to initialize uop.
+ * \param signature The closure arguments of the finit.
+ * \param nbytes Number of bytes to in the closure arguments.
+ * \return 0 if success.
+ */
+int VTAPushGEMMOp(void** uop_handle,
+                  int (*finit)(void*),
+                  void* signature,
+                  int nbytes);
+
+/*!
+ * \brief Push ALU uop kernel into the command handle.
+ * \param uop_handle The uop cache handle.
+ * \param finit The initalization function to initialize uop.
+ * \param signature The closure arguments of the finit.
+ * \param nbytes Number of bytes to in the closure arguments.
+ * \return 0 if success.
+ */
+int VTAPushALUOp(void** uop_handle,
+                 int (*finit)(void*),
+                 void* signature,
+                 int nbytes);
+
+/*!
+ * \brief Push dependence token.
+ * \param cmd The VTA command handle.
+ * \param from_qid The source queue.
+ * \param to_qid The destination queue.
+ * \return 0 if success.
+ */
+int VTADepPush(VTACommandHandle cmd, int from_qid, int to_qid);
+
+/*!
+ * \brief Pop dependence signal.
+ * \param cmd The VTA command handle.
+ * \param from_qid The source queue.
+ * \param to_qid The destination queue.
+ * \return 0 if success.
+ */
+int VTADepPop(VTACommandHandle cmd, int from_qid, int to_qid);
+
+/*!
+ * \brief Synchronize the command handle.
+ *  Commit all the instructions to VTA and wait until
+ *  the accelerator finishes its job.
+ *  Perform all of the out-of-order DRAM stores.
+ * \param cmd The VTA command handle.
+ * \param wait_cycles The limit of poll cycles.
+ *
+ */
+void VTASynchronize(VTACommandHandle cmd, uint32_t wait_cycles);
+
+#ifdef __cplusplus
+}
+#endif
+#endif  // VTA_RUNTIME_H_
diff --git a/vta/make/config.mk b/vta/make/config.mk
index 76d817fb4d67..8142571a1cca 100644
--- a/vta/make/config.mk
+++ b/vta/make/config.mk
@@ -25,3 +25,72 @@ ADD_LDFLAGS=
 
 # the additional compile flags you want to add
 ADD_CFLAGS=
+
+# the hardware target
+TARGET=PYNQ_TARGET
+
+#---------------------
+# VTA hardware parameters
+#--------------------
+
+#  Log of input/activation width in bits (default 3 -> 8 bits)
+LOG_INP_WIDTH = 3
+#  Log of kernel weight width in bits (default 3 -> 8 bits)
+LOG_WGT_WIDTH = 3
+#  Log of accum width in bits (default 5 -> 32 bits)
+LOG_ACC_WIDTH = 5
+#  Log of tensor batch size (A in (A,B)x(B,C) matrix multiplication)
+LOG_BATCH = 0
+#  Log of tensor inner block size (B in (A,B)x(B,C) matrix multiplication)
+LOG_BLOCK_IN = 4
+#  Log of tensor outer block size (C in (A,B)x(B,C) matrix multiplication)
+LOG_BLOCK_OUT = 4
+#  Log of uop buffer size in Bytes
+LOG_UOP_BUFF_SIZE = 15
+#  Log of inp buffer size in Bytes
+LOG_INP_BUFF_SIZE = 15
+#  Log of wgt buffer size in Bytes
+LOG_WGT_BUFF_SIZE = 15
+#  Log of acc buffer size in Bytes
+LOG_ACC_BUFF_SIZE = 17
+
+#---------------------
+# Derived VTA hardware parameters
+#--------------------
+
+#  Input width in bits
+INP_WIDTH = $(shell echo "$$(( 1 << $(LOG_INP_WIDTH) ))" )
+#  Weight width in bits
+WGT_WIDTH = $(shell echo "$$(( 1 << $(LOG_WGT_WIDTH) ))" )
+#  Log of output width in bits
+LOG_OUT_WIDTH = $(LOG_INP_WIDTH)
+#  Output width in bits
+OUT_WIDTH = $(shell echo "$$(( 1 << $(LOG_OUT_WIDTH) ))" )
+#  Tensor batch size
+BATCH = $(shell echo "$$(( 1 << $(LOG_BATCH) ))" )
+#  Tensor outer block size
+IN_BLOCK = $(shell echo "$$(( 1 << $(LOG_BLOCK_IN) ))" )
+#  Tensor inner block size
+OUT_BLOCK = $(shell echo "$$(( 1 << $(LOG_BLOCK_OUT) ))" )
+#  Uop buffer size in Bytes
+UOP_BUFF_SIZE = $(shell echo "$$(( 1 << $(LOG_UOP_BUFF_SIZE) ))" )
+#  Inp buffer size in Bytes
+INP_BUFF_SIZE = $(shell echo "$$(( 1 << $(LOG_INP_BUFF_SIZE) ))" )
+#  Wgt buffer size in Bytes
+WGT_BUFF_SIZE = $(shell echo "$$(( 1 << $(LOG_WGT_BUFF_SIZE) ))" )
+#  Acc buffer size in Bytes
+ACC_BUFF_SIZE = $(shell echo "$$(( 1 << $(LOG_ACC_BUFF_SIZE) ))" )
+#  Log of out buffer size in Bytes
+LOG_OUT_BUFF_SIZE = $(shell echo "$$(( $(LOG_ACC_BUFF_SIZE)+$(LOG_OUT_WIDTH)-$(LOG_ACC_WIDTH) ))" )
+#  Out buffer size in Bytes
+OUT_BUFF_SIZE = $(shell echo "$$(( 1 << $(LOG_OUT_BUFF_SIZE) ))" )
+
+# Update ADD_CFLAGS
+ADD_CFLAGS += \
+	-D$(TARGET) \
+	-DLOG_WGT_WIDTH=$(LOG_WGT_WIDTH) -DLOG_INP_WIDTH=$(LOG_INP_WIDTH) \
+	-DLOG_ACC_WIDTH=$(LOG_ACC_WIDTH) -DLOG_OUT_WIDTH=$(LOG_OUT_WIDTH) \
+	-DLOG_BATCH=$(LOG_BATCH) -DLOG_BLOCK_IN=$(LOG_BLOCK_IN) -DLOG_BLOCK_OUT=$(LOG_BLOCK_OUT) \
+	-DLOG_UOP_BUFF_SIZE=$(LOG_UOP_BUFF_SIZE) -DLOG_INP_BUFF_SIZE=$(LOG_INP_BUFF_SIZE) \
+	-DLOG_WGT_BUFF_SIZE=$(LOG_WGT_BUFF_SIZE) -DLOG_ACC_BUFF_SIZE=$(LOG_ACC_BUFF_SIZE) \
+	-DLOG_OUT_BUFF_SIZE=$(LOG_OUT_BUFF_SIZE)
\ No newline at end of file
diff --git a/vta/src/driver/pynq/vta_pynq_driver.c b/vta/src/pynq/pynq_driver.cc
similarity index 67%
rename from vta/src/driver/pynq/vta_pynq_driver.c
rename to vta/src/pynq/pynq_driver.cc
index ca9464cfaf39..b4f78db0c160 100644
--- a/vta/src/driver/pynq/vta_pynq_driver.c
+++ b/vta/src/pynq/pynq_driver.cc
@@ -4,15 +4,31 @@
  * \brief VTA driver for Pynq board.
  */
 
-#ifdef __cplusplus
-extern "C" {
-#endif
-#include "vta_pynq_driver.h"
-#ifdef __cplusplus
+#include <vta/driver.h>
+#include "./pynq_driver.h"
+
+
+void* VTAMemAlloc(size_t size, int cached) {
+  return cma_alloc(size, cached);
+}
+
+void VTAMemFree(void* buf) {
+  cma_free(buf);
+}
+
+uint32_t VTAGetMemPhysAddr(void* buf) {
+  return cma_get_phy_addr(buf);
+}
+
+void VTAFlushCache(void* buf, int size) {
+  xlnkFlushCache(buf, size);
+}
+
+void VTAInvalidateCache(void* buf, int size) {
+  xlnkInvalidateCache(buf, size);
 }
-#endif
 
-void *MapRegister(uint32_t addr, size_t length) {
+void *VTAMapRegister(uint32_t addr, size_t length) {
 
   // Align the base address with the pages
   uint32_t virt_base = addr & ~(getpagesize() - 1);
@@ -24,21 +40,21 @@ void *MapRegister(uint32_t addr, size_t length) {
   return mmap(NULL, (length+virt_offset), PROT_READ|PROT_WRITE, MAP_SHARED, mmap_file, virt_base);
 }
 
-void UnmapRegister(void *vta, size_t length) {
+void VTAUnmapRegister(void *vta, size_t length) {
   // Unmap memory
   int status = munmap(vta, length);
   assert(status==0);
 }
 
-void WriteMappedReg(void* base_addr, uint32_t offset, uint32_t val) {
+void VTAWriteMappedReg(void* base_addr, uint32_t offset, uint32_t val) {
   *((volatile uint32_t *) (((char *) base_addr) + offset)) = val;
 }
 
-uint32_t ReadMappedReg(void* base_addr, uint32_t offset) {
+uint32_t VTAReadMappedReg(void* base_addr, uint32_t offset) {
   return *((volatile uint32_t *) (((char *) base_addr) + offset));
 }
 
-void ProgramVTA(const char* bitstream) {
+void VTAProgram(const char* bitstream) {
 
     int elem;
     FILE *src, *dst, *partial;
diff --git a/vta/include/vta_pynq_driver.h b/vta/src/pynq/pynq_driver.h
similarity index 50%
rename from vta/include/vta_pynq_driver.h
rename to vta/src/pynq/pynq_driver.h
index 9a07846843fb..9e9482822a21 100644
--- a/vta/include/vta_pynq_driver.h
+++ b/vta/src/pynq/pynq_driver.h
@@ -23,7 +23,7 @@ extern "C" {
 #include <unistd.h>
 
 #ifdef __arm__
-#include "libxlnk_cma.h"
+#include <libxlnk_cma.h>
 #else
 void* cma_alloc(size_t size, int cached);
 void cma_free(void* buf);
@@ -32,31 +32,6 @@ void xlnkFlushCache(void* buf, int size);
 void xlnkInvalidateCache(void* buf, int size);
 #endif
 
-/*! \brief VTA command handle */
-typedef void * VTAHandle;
-
-/*! \brief DMA command handle */
-typedef struct {
-  /*! \brief Register map to the AXI DMA control registers*/
-  void *dma_register_map;
-  /*! \brief Transmit data descriptor*/
-  void *mm2s_descriptor_register_map;
-  /*! \brief Receive data descriptor*/
-  void *s2mm_descriptor_register_map;
-  /*! \brief Transmit data descriptor physical address*/
-  uint32_t mm2s_descriptor_phy;
-  /*! \brief Receive data descriptor physical address*/
-  uint32_t s2mm_descriptor_phy;
-  /*! \brief Descriptor size */
-  uint32_t descriptor_size;
-  /*! \brief Transaction count for tx channel */
-  uint32_t mm2s_count;
-  /*! \brief Transaction count for rx channel */
-  uint32_t s2mm_count;
-  /*! \brief Multi-channel mode enable */
-  int multichannel_en;
-} DMAHandle;
-
 /*! \brief partial bitstream status file path */
 #define BS_IS_PARTIAL "/sys/devices/soc0/amba/f8007000.devcfg/is_partial_bitstream"
 /*! \brief bitstream destination file path */
@@ -99,52 +74,8 @@ typedef struct {
 */
 #define VTA_STORE_ADDR    0x43C30000
 
-/*! \brief Memory management constants with libxlnk_cma */
-#define CACHED 1
-/*! \brief Memory management constants with libxlnk_cma */
-#define NOT_CACHED 0
-
-/*! \brief log2 of SDS buffer size limit */
-#define LOG_MAX_XFER 22
-/*! \brief SDS buffer size limit */
-#define MAX_XFER (1<<LOG_MAX_XFER)
-
-/*!
- * \brief Returns a memory map to FPGA configuration registers.
- * \param addr The base physical address of the configuration registers.
- * \param length The size of the memory mapped region in bytes.
- * \return A pointer to the memory mapped region.
- */
-void *MapRegister(unsigned addr, size_t length);
-
-/*!
- * \brief Deletes the configuration register memory map.
- * \param vta The memory mapped region.
- * \param length The size of the memory mapped region in bytes.
- */
-void UnmapRegister(void *vta, size_t length);
-
-/*!
- * \brief Writes to a memory mapped configuration register.
- * \param vta_base The handle to the memory mapped configuration registers.
- * \param offset The offset of the register to write to.
- * \param val The value to be written to the memory mapped register.
- */
-void WriteMappedReg(VTAHandle vta_base, unsigned offset, unsigned val);
-
-/*!
- * \brief Reads from the memory mapped configuration register.
- * \param vta_base The handle to the memory mapped configuration registers.
- * \param offset The offset of the register to read from.
- * \return The value read from the memory mapped register.
- */
-unsigned ReadMappedReg(VTAHandle vta_base, unsigned offset);
-
-/*!
- * \brief Programming the bit stream on the FPGA.
- * \param bitstream The path to the bit stream file.
- */
-void ProgramVTA(const char* bitstream);
+/*! \brief Buffer size limit */
+#define MAX_XFER (1<<22)
 
 #ifdef __cplusplus
 }
diff --git a/vta/src/runtime.cc b/vta/src/runtime.cc
new file mode 100644
index 000000000000..570816e5236a
--- /dev/null
+++ b/vta/src/runtime.cc
@@ -0,0 +1,1410 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file vta_runtime.cc
+ * \brief VTA runtime for PYNQ in C++11
+ */
+
+#include <cassert>
+#include <cstring>
+#include <vector>
+#include <thread>
+#include <memory>
+#include <atomic>
+#include <vta/driver.h>
+#include <vta/hw_spec.h>
+#include <vta/runtime.h>
+
+#ifdef PYNQ_TARGET
+#include "./pynq/pynq_driver.h"
+#endif //PYNQ_TARGET
+
+namespace vta {
+
+/*! \brief Enable coherent access between VTA and CPU. */
+static const bool kBufferCoherent = true;
+
+/*!
+ * \brief Data buffer represents data on CMA.
+ */
+struct DataBuffer {
+  /*! \return Virtual address of the data. */
+  void* virt_addr() const {
+    return data_;
+  }
+  /*! \return Physical address of the data. */
+  uint32_t phy_addr() const {
+    return phy_addr_;
+  }
+  /*!
+   * \brief Invalidate the cache of given location in data buffer.
+   * \param offset The offset to the data.
+   * \param size The size of the data.
+   */
+  void InvalidateCache(size_t offset, size_t size) {
+    if (!kBufferCoherent) {
+      VTAInvalidateCache(reinterpret_cast<void*>(phy_addr_ + offset), size);
+    }
+  }
+  /*!
+   * \brief Invalidate the cache of certain location in data buffer.
+   * \param offset The offset to the data.
+   * \param size The size of the data.
+   */
+  void FlushCache(size_t offset, size_t size) {
+    if (!kBufferCoherent) {
+      VTAFlushCache(reinterpret_cast<void*>(phy_addr_ + offset), size);
+    }
+  }
+  /*!
+   * \brief Allocate a buffer of a given size.
+   * \param size The size of the buffer.
+   */
+  static DataBuffer* Alloc(size_t size) {
+    void* data = VTAMemAlloc(size, 1);
+    assert(data != nullptr);
+    DataBuffer* buffer = new DataBuffer();
+    buffer->data_ = data;
+    buffer->phy_addr_ = VTAGetMemPhysAddr(data);
+    return buffer;
+  }
+  /*!
+   * \brief Free the data buffer.
+   * \param buffer The buffer to be freed.
+   */
+  static void Free(DataBuffer* buffer) {
+    VTAMemFree(buffer->data_);
+    delete buffer;
+  }
+  /*!
+   * \brief Create data buffer header from buffer ptr.
+   * \param buffer The buffer pointer.
+   * \return The corresponding data buffer header.
+   */
+  static DataBuffer* FromHandle(const void* buffer) {
+    return const_cast<DataBuffer*>(
+        reinterpret_cast<const DataBuffer*>(buffer));
+  }
+
+ private:
+  /*! \brief The internal data. */
+  void* data_;
+  /*! \brief The physical address of the buffer, excluding header. */
+  uint32_t phy_addr_;
+};
+
+/*!
+ * \brief Micro op kernel.
+ *  Contains functions to construct the kernel with prefix Push.
+ */
+class UopKernel {
+ public:
+  /*! \brief Loop information. */
+  struct LoopEntry {
+    uint32_t extent;
+    uint32_t dst_factor;
+    uint32_t src_factor;
+    uint32_t wgt_factor;
+  };
+  /*!
+   * \brief Construct UopKernel with signature.
+   * \param signature The pointer to signature.
+   * \param nbytes Number of bytes.
+   */
+  UopKernel(const char* signature, int nbytes)
+      : signature_(signature, signature + nbytes) {
+  }
+  /*!
+   * \brief Verify if the signature is correct.
+   * \param signature Signature ptr.
+   * \param nbytes Number of bytes.
+   */
+  bool MatchSignature(void* signature, int nbytes) const {
+    if (static_cast<size_t>(nbytes) != signature_.size()) return false;
+    return memcmp(signature, signature_.data(), nbytes) == 0;
+  }
+  /*! \return Whether the kernel is cached in SRAM. */
+  bool cached() const {
+    return sram_begin_ != sram_end_;
+  }
+  /*! \return The length of the micro op sequence. */
+  size_t size() const {
+    return seq_.size();
+  }
+  /*! \return The micro-op data. */
+  const VTAUop* data() const {
+    return seq_.data();
+  }
+  /*! \return The loop structure. */
+  const std::vector<LoopEntry>& loop() const {
+    return loop_;
+  }
+  /*!
+   * \brief Declare loop start.
+   * \param extent The loop extent.
+   * \param dst_factor Loop factor of accum index.
+   * \param src_factor Loop factor of input index
+   * \param wgt_factor Loop factor of weight index.
+   */
+  void PushLoopBegin(uint32_t extent,
+                     uint32_t dst_factor,
+                     uint32_t src_factor,
+                     uint32_t wgt_factor) {
+    LoopEntry le;
+    le.extent = extent;
+    le.dst_factor = dst_factor;
+    le.src_factor = src_factor;
+    le.wgt_factor = wgt_factor;
+    assert(seq_.size() == 0);
+    assert(loop_.size() < 2);
+    loop_.push_back(le);
+    ++loop_ptr_;
+  }
+  /*!
+   * \brief Declare loop end.
+   */
+  void PushLoopEnd() {
+    --loop_ptr_;
+  }
+  /*!
+   * \brief Push micro op into kernel.
+   * \param mode Set to GEMM mode if set to 0, ALU mode is set to 1.
+   * \param reset_out Resets the accum to 0.
+   * \param dst_index The accum memory index.
+   * \param src_index The input memory (gemm) / accum memory (alu) index.
+   * \param wgt_index The weight memory index.
+   * \param opcode The ALU opcode.
+   * \param use_imm Use immediate in ALU mode if set to true.
+   * \param imm_val Immediate value in ALU mode.
+   */
+  void Push(uint32_t mode,
+            uint32_t reset_out,
+            uint32_t dst_index,
+            uint32_t src_index,
+            uint32_t wgt_index,
+            uint32_t opcode,
+            uint32_t use_imm,
+            uint32_t imm_val) {
+    // The loop nest structure
+    VerifyDep(dst_index);
+    VTAUop op;
+    op.reset_out = reset_out;
+    op.dst_idx = dst_index;
+    op.src_idx = src_index;
+    op.wgt_idx = wgt_index;
+    seq_.push_back(op);
+    // Ensure that mode is consistent if set
+    if (mode_==0xFFFFFFFF) {
+      mode_ = mode;
+    } else {
+      assert(mode_==mode);
+    }
+    // Check kernel op and imm/imm_val in ALU mode
+    if (mode==1) {
+      if (opcode_==0xFFFFFFFF) {
+        opcode_=opcode;
+        use_imm_=use_imm;
+        imm_val_=imm_val;
+      } else {
+        assert(opcode_==opcode);
+        assert(use_imm_==use_imm);
+        assert(imm_val_==imm_val);
+      }
+    }
+  }
+  /*! \brief Dump kernel micro ops to stdout. */
+  void Dump() {
+    uint32_t size = seq_.size();
+    printf("There are %u uops\n", size);
+    for (uint32_t i = 0; i < size; ++i) {
+      printf("[%04u]\t acc=%u, inp=%u, wgt=%u, reset_out=%u\n",
+             i,
+             seq_[i].dst_idx,
+             seq_[i].src_idx,
+             seq_[i].wgt_idx,
+             seq_[i].reset_out);
+
+    }
+    printf("\n");
+  }
+
+ public:
+  // The kernel's mode, opcode, immediate setting and value
+  uint32_t mode_{0xFFFFFFFF}; // UOP type: 0xFFFFFFFF - unset, 0 - GEMM, 1 - ALU
+  uint32_t opcode_{0xFFFFFFFF};
+  bool use_imm_{false};
+  uint16_t imm_val_{0};
+ private:
+  // Verify that we don't write to the same acc_mem index two cycles in a row
+  void VerifyDep(uint32_t dst_index) {
+    size_t step = std::min(static_cast<size_t>(2U), seq_.size());
+    for (size_t i = seq_.size() - step; i < seq_.size(); ++i) {
+      assert(seq_[i].dst_idx != dst_index);
+    }
+  }
+  // The uop buffer
+  template<int, bool, bool>
+  friend class UopQueue;
+  friend class CommandQueue;
+  // SRAM location if begin != end.
+  uint32_t sram_begin_{0};
+  uint32_t sram_end_{0};
+  // The signature used for verification
+  std::vector<char> signature_;
+  // Internal sequence
+  std::vector<VTAUop> seq_;
+  // The loop nest structure specific to ALU instructions
+  std::vector<LoopEntry> loop_;
+  // The loop pointer
+  size_t loop_ptr_{0};
+};
+
+/*!
+ * \brief Base class of all queues to send and recv serial data.
+ * \param kElemBytes Element unit bytes.
+ * \param kMaxBytes Maximum number of bytes.
+ * \param kCoherent Whether we have coherent access to the buffer.
+ * \param kAlwaysCache Wether we should use cached memory.
+ */
+class BaseQueue {
+ public:
+  ~BaseQueue() {
+    if (dram_buffer_ != nullptr) {
+      VTAMemFree(dram_buffer_);
+    }
+  }
+  /*! \return Content of DRAM buffer. */
+  char* dram_buffer() const {
+    return dram_buffer_;
+  }
+  /*! \return Physical address of DRAM. */
+  uint32_t dram_phy_addr() const {
+    return dram_phy_addr_;
+  }
+  /*! \return Whether there is pending information. */
+  bool pending() const {
+    return sram_begin_ != sram_end_;
+  }
+  /*! \brief Initialize the space of the buffer. */
+  void InitSpace(uint32_t elem_bytes, uint32_t max_bytes, bool coherent, bool always_cache) {
+    coherent_ = coherent;
+    always_cache_ = always_cache;
+    elem_bytes_ = elem_bytes;
+    dram_buffer_ = static_cast<char*>(VTAMemAlloc(
+        max_bytes, coherent || always_cache_));
+    assert(dram_buffer_ != nullptr);
+    dram_phy_addr_ = VTAGetMemPhysAddr(dram_buffer_);
+  }
+  /*!
+   * \brief Reset the pointer of the buffer.
+   *  Set SRAM pointer to be the current end.
+   */
+  void Reset() {
+    dram_begin_ = dram_end_ = 0;
+    sram_begin_ = sram_end_;
+  }
+  void AutoReadBarrier() {
+    ReadBarrier(elem_bytes_ * 8, 0, dram_end_);
+  }
+  /*! \brief Writer barrier to make sure that data written by CPU is visible to VTA. */
+  void ReadBarrier(uint32_t elem_bits, uint32_t dram_begin, uint32_t dram_extent) {
+    if (!coherent_ && always_cache_ && dram_extent != 0) {
+      dram_begin = dram_begin * elem_bits / 8;
+      dram_extent = dram_extent * elem_bits / 8;
+      VTAFlushCache(reinterpret_cast<void*>(dram_phy_addr_ + dram_begin),
+                     dram_extent);
+    }
+  }
+  /*! \brief Read barrier to make sure that data written by VTA is visible to CPU. */
+  void WriteBarrier(uint32_t elem_bits, uint32_t dram_begin, uint32_t dram_extent) {
+    if (!coherent_ && always_cache_ && dram_extent != 0) {
+      dram_begin = dram_begin * elem_bits / 8;
+      dram_extent = dram_extent * elem_bits / 8;
+      VTAInvalidateCache(reinterpret_cast<void*>(dram_phy_addr_ + dram_begin),
+                          dram_extent);
+    }
+  }
+
+ protected:
+  // Cache coherence access
+  bool coherent_{false};
+  // Make the buffer cacheable
+  bool always_cache_{false};
+  // Element bytes
+  uint32_t elem_bytes_{0};
+  // Begin location of current SRAM read in FIFO mode
+  uint32_t sram_begin_{0};
+  // End location of current SRAM write in FIFO mode
+  uint32_t sram_end_{0};
+  // The current pending offset in DRAM in FIFO mode
+  uint32_t dram_begin_{0};
+  // The current pending offset in DRAM in FIFO mode
+  uint32_t dram_end_{0};
+  // The buffer in DRAM
+  char* dram_buffer_{nullptr};
+  // Physics address of the buffer
+  uint32_t dram_phy_addr_;
+};
+
+/*!
+ * \brief Micro op buffer that manages the micro op cache.
+ */
+template<int kMaxBytes, bool kCoherent, bool kAlwaysCache>
+class UopQueue : public BaseQueue {
+ public:
+  void InitSpace() {
+    BaseQueue::InitSpace(kElemBytes, kMaxBytes, kCoherent, kAlwaysCache);
+  }
+  // Push data to the queue
+  template<typename FAutoSync>
+  void Push(UopKernel* kernel, FAutoSync fautosync) {
+    if (kernel->cached()) return;
+    size_t num_op = kernel->size();
+    if (dram_end_ + num_op > kMaxElems) {
+      fautosync();
+      assert(dram_end_ <= kMaxElems);
+    }
+    assert(num_op <= kMaxNumUop);
+    uint32_t uop_begin = 0;
+    if (sram_end_ + num_op > kMaxElems) {
+      // Need to evict
+      cache_ptr_ = 0;
+      sram_end_ = num_op;
+    } else {
+      uop_begin = sram_end_;
+      sram_end_ += num_op;
+    }
+    // Simple eviction policy
+    uint32_t evict_begin = cache_ptr_;
+    for (;cache_ptr_ < cache_.size(); ++cache_ptr_) {
+      if (cache_[cache_ptr_]->sram_begin_ >= sram_end_) break;
+      cache_[cache_ptr_]->sram_begin_ = 0;
+      cache_[cache_ptr_]->sram_end_ = 0;
+    }
+    memcpy(dram_buffer_ + dram_end_ * kElemBytes,
+           kernel->data(),
+           num_op * kElemBytes);
+    dram_end_ += num_op;
+    kernel->sram_begin_ = uop_begin;
+    kernel->sram_end_ = sram_end_;
+    assert(uop_begin != sram_end_);
+    cache_.insert(cache_.begin() + cache_ptr_, kernel);
+    cache_.erase(cache_.begin() + evict_begin, cache_.begin() + cache_ptr_);
+    cache_ptr_ = evict_begin + 1;
+  }
+  // Flush as weight load
+  void FlushUopLoad(VTAMemInsn* insn) {
+    if (sram_begin_ != sram_end_) {
+      assert((dram_end_ - dram_begin_) == (sram_end_ - sram_begin_));
+      insn->memory_type = MEM_ID_UOP;
+      insn->sram_base = sram_begin_;
+      insn->dram_base = dram_phy_addr_ / kElemBytes + dram_begin_;
+      insn->y_size = 1;
+      insn->x_size = (dram_end_ - dram_begin_);
+      insn->x_stride = (dram_end_ - dram_begin_);
+      insn->y_pad_0 = 0;
+      insn->y_pad_1 = 0;
+      insn->x_pad_0 = 0;
+      insn->x_pad_1 = 0;
+      // Reset indices
+      sram_begin_ = sram_end_;
+      dram_begin_ = dram_end_;
+    }
+  }
+
+ private:
+  // Cache pointer
+  uint32_t cache_ptr_{0};
+  // Cached ring, sorted by sram_begin
+  std::vector<UopKernel*> cache_;
+  // Constants
+  static constexpr int kElemBytes = sizeof(VTAUop);
+  static constexpr int kMaxNumUop = UOP_BUFF_DEPTH;
+  static constexpr int kMaxElems = kMaxBytes / kElemBytes;
+};
+
+// Internal kernel structure
+class UopKernelMap {
+ public:
+  // Simple hash map
+  UopKernel** Get(void* signature,
+                  int nbytes) {
+    uint32_t key = 0;
+    assert(nbytes == 0 || nbytes == sizeof(int));
+    if (nbytes == sizeof(int)) {
+      memcpy(&key, signature, sizeof(int));
+      key = key + 1;
+    }
+    assert(key < 100);
+    if (kmap_.size() <= key) {
+      kmap_.resize(key + 1, nullptr);
+    }
+    return &(kmap_[key]);
+  }
+
+ private:
+  std::vector<UopKernel*> kmap_;
+};
+
+enum PipelineStage : int {
+  kNoneStage = 0,
+  kLoadStage = 1,
+  kComputeStage = 2,
+  kStoreStage = 3
+};
+
+// Instruction Queue
+template<int kMaxBytes, bool kCoherent, bool kAlwaysCache>
+class InsnQueue : public BaseQueue {
+ public:
+  /*! \brief Initialize the space. */
+  void InitSpace() {
+    BaseQueue::InitSpace(kElemBytes, kMaxBytes, kCoherent, kAlwaysCache);
+    // Initialize the stage
+    std::fill(pending_pop_prev_, pending_pop_prev_ + 4, 0);
+    std::fill(pending_pop_next_, pending_pop_next_ + 4, 0);
+  }
+  /*! \return The data pointer. */
+  VTAGenericInsn* data() {
+    return reinterpret_cast<VTAGenericInsn*>(dram_buffer_);
+  }
+  /*! \return Number of instructions. */
+  uint32_t count() {
+    return dram_end_;
+  }
+  // Insert dependency push of load
+  void DepPop(int from, int to) {
+    // NOTE: This instruction executes on queue[to]
+    if (from < to) {
+      if (pending_pop_prev_[to]) {
+        this->CommitPendingPop(to);
+      }
+      pending_pop_prev_[to] = 1;
+    } else {
+      if (pending_pop_next_[to]) {
+        this->CommitPendingPop(to);
+      }
+      pending_pop_next_[to] = 1;
+    }
+    // Impossible condition
+    assert(from != kLoadStage || to != kStoreStage);
+    assert(to != kLoadStage || to != kComputeStage);
+  }
+  // Insert dependency push of load
+  void DepPush(int from, int to) {
+    // NOTE: this instruction executes on queue[from]
+    this->CommitPendingPop(from);
+    if (dram_end_ != 0) {
+      VTAMemInsn* mptr =
+          reinterpret_cast<VTAMemInsn*>(dram_buffer_) + dram_end_ - 1;
+      if (GetPipelineStage(mptr) == from) {
+        if (from < to && !mptr->push_next_dep) {
+          // push(LD->C) or push(C->ST)
+          mptr->push_next_dep = true; return;
+        } else if (from > to && !mptr->push_prev_dep) {
+          // push(C->LD) or push(ST->C)
+          mptr->push_prev_dep = true; return;
+        }
+      }
+    }
+    if (from < to) {
+      // Push next dep
+      PushNoop(from, false, true, false, false);
+    } else {
+      // Push prev dep
+      PushNoop(from, true, false, false, false);
+    }
+  }
+  // Create a new instruction for a GEMM stage
+  VTAGemInsn* CreateGemInsn() {
+    return reinterpret_cast<VTAGemInsn*>(
+        Create(kComputeStage));
+  }
+  // Create a new instruction for a ALU stage
+  VTAAluInsn* CreateAluInsn() {
+    return reinterpret_cast<VTAAluInsn*>(
+        Create(kComputeStage));
+  }
+  // Create a new instruction for a memory stage
+  VTAMemInsn* CreateMemInsn(int memory_type) {
+    return reinterpret_cast<VTAMemInsn*>(
+        Create(GetMemPipelineStage(memory_type)));
+  }
+  // create a new instruction for a store stage
+  VTAMemInsn* CreateStoreInsn() {
+    return reinterpret_cast<VTAMemInsn*>(
+        Create(kStoreStage));
+  }
+  // Rewrite instruction stream to force serial execution
+  void RewriteForceSerial() {
+    int insn_count = count();
+    VTAMemInsn* mem_ptr = reinterpret_cast<VTAMemInsn*>(data());
+    for (int i = 1; i < insn_count; ++i) {
+      PipelineStage prev = GetPipelineStage(mem_ptr + i - 1);
+      PipelineStage now = GetPipelineStage(mem_ptr + i);
+      if (prev==kLoadStage && now==kComputeStage) {
+        mem_ptr[i - 1].push_prev_dep = false;
+        mem_ptr[i - 1].push_next_dep = true;
+        mem_ptr[i].pop_prev_dep = true;
+        mem_ptr[i].pop_next_dep = false;
+      } else if (prev==kComputeStage && now==kLoadStage) {
+        mem_ptr[i - 1].push_prev_dep = true;
+        mem_ptr[i - 1].push_next_dep = false;
+        mem_ptr[i].pop_prev_dep = false;
+        mem_ptr[i].pop_next_dep = true;
+      } else if (prev==kStoreStage && now==kComputeStage) {
+        mem_ptr[i - 1].push_prev_dep = true;
+        mem_ptr[i - 1].push_next_dep = false;
+        mem_ptr[i].pop_prev_dep = false;
+        mem_ptr[i].pop_next_dep = true;
+      } else if (prev==kComputeStage && now==kStoreStage) {
+        mem_ptr[i - 1].push_prev_dep = false;
+        mem_ptr[i - 1].push_next_dep = true;
+        mem_ptr[i].pop_prev_dep = true;
+        mem_ptr[i].pop_next_dep = false;
+      } else {
+        mem_ptr[i - 1].push_prev_dep = false;
+        mem_ptr[i - 1].push_next_dep = false;
+        mem_ptr[i].pop_prev_dep = false;
+        mem_ptr[i].pop_next_dep = false;
+      }
+    }
+  }
+
+  // Helper function: Get Opcode string
+  const char* getOpcodeString(int opcode, bool use_imm) {
+      // The string name
+      if (opcode==ALU_OPCODE_MIN) {
+          if (use_imm) {
+              return "min imm";
+          } else {
+              return "min";
+          }
+      } else if (opcode==ALU_OPCODE_MAX) {
+          if (use_imm) {
+              return "max imm";
+          } else {
+              return "max";
+          }
+      } else if (opcode==ALU_OPCODE_ADD) {
+          if (use_imm) {
+              return "add imm";
+          } else {
+              return "add";
+          }
+      } else if (opcode==ALU_OPCODE_SUB) {
+          if (use_imm) {
+              return "sub imm";
+          } else {
+              return "sub";
+          }
+      } else if (opcode==ALU_OPCODE_MUL) {
+          if (use_imm) {
+              return "mul imm";
+          } else {
+              return "mul";
+          }
+      } else if (opcode==ALU_OPCODE_SHL) {
+          return "shl";
+      } else if (opcode==ALU_OPCODE_SHR) {
+          return "shr";
+      }
+
+      return "unknown op";
+  }
+
+  // Dump instructions in the queue
+  void DumpInsn() {
+    // Keep tabs on dependence queues
+    int l2g_queue = 0;
+    int g2l_queue = 0;
+    int s2g_queue = 0;
+    int g2s_queue = 0;
+    // Converter
+    union VTAInsn c;
+    // Iterate over all instructions
+    int insn_count = count();
+    const VTAGenericInsn* insn = data();
+    printf("There are %u instructions\n", insn_count);
+    for (int i = 0; i < insn_count; ++i) {
+      // Fetch instruction and decode opcode
+      c.generic = insn[i];
+      printf("INSTRUCTION %u: ", i);
+      if (c.mem.opcode == OPCODE_LOAD || c.mem.opcode == OPCODE_STORE) {
+        if (c.mem.x_size == 0) {
+          if (c.mem.opcode == OPCODE_STORE) {
+            printf("NOP-STORE-STAGE\n");
+          }
+          else if (GetMemPipelineStage(c.mem.memory_type) == kComputeStage) {
+            printf("NOP-COMPUTE-STAGE\n");
+          } else {
+            printf("NOP-MEMORY-STAGE\n");
+          }
+          printf("\tdep - pop prev: %d, pop next: %d, push prev: %d, push next: %d\n",
+                 static_cast<int>(c.mem.pop_prev_dep),
+                 static_cast<int>(c.mem.pop_next_dep),
+                 static_cast<int>(c.mem.push_prev_dep),
+                 static_cast<int>(c.mem.push_next_dep));
+          // Count status in queues
+          if (c.mem.opcode == OPCODE_LOAD || c.mem.opcode == OPCODE_STORE) {
+            if (c.mem.opcode == OPCODE_STORE) {
+                assert(c.mem.pop_next_dep == false);
+                assert(c.mem.push_next_dep == false);
+                if (c.mem.pop_prev_dep) g2s_queue--;
+                if (c.mem.push_prev_dep) s2g_queue++;
+            } else if (c.mem.opcode == OPCODE_LOAD &&
+                       (c.mem.memory_type == MEM_ID_INP ||
+                        c.mem.memory_type == MEM_ID_WGT) ) {
+                assert(c.mem.pop_prev_dep == false);
+                assert(c.mem.push_prev_dep == false);
+                if (c.mem.pop_next_dep) g2l_queue--;
+                if (c.mem.push_next_dep) l2g_queue++;
+            } else {
+                if (c.mem.pop_prev_dep) l2g_queue--;
+                if (c.mem.push_prev_dep) g2l_queue++;
+                if (c.mem.pop_next_dep) s2g_queue--;
+                if (c.mem.push_next_dep) g2s_queue++;
+            }
+          } else if (c.mem.opcode == OPCODE_GEMM) {
+            // Print instruction field information
+            if (c.gemm.pop_prev_dep) l2g_queue--;
+            if (c.gemm.push_prev_dep) g2l_queue++;
+            if (c.gemm.pop_next_dep) s2g_queue--;
+            if (c.gemm.push_next_dep) g2s_queue++;
+          }
+          printf("\tl2g_queue = %d, g2l_queue = %d\n", l2g_queue, g2l_queue);
+          printf("\ts2g_queue = %d, g2s_queue = %d\n", s2g_queue, g2s_queue);
+          continue;
+        }
+        // Print instruction field information
+        if (c.mem.opcode==OPCODE_LOAD) {
+            printf("LOAD ");
+            if (c.mem.memory_type == MEM_ID_UOP) printf("UOP\n");
+            if (c.mem.memory_type == MEM_ID_WGT) printf("WGT\n");
+            if (c.mem.memory_type == MEM_ID_INP) printf("INP\n");
+            if (c.mem.memory_type == MEM_ID_ACC) printf("ACC\n");
+        }
+        if (c.mem.opcode==OPCODE_STORE) {
+            printf("STORE\n");
+        }
+        printf("\tdep - pop prev: %d, pop next: %d, push prev: %d, push next: %d\n",
+               static_cast<int>(c.mem.pop_prev_dep),
+               static_cast<int>(c.mem.pop_next_dep),
+               static_cast<int>(c.mem.push_prev_dep),
+               static_cast<int>(c.mem.push_next_dep));
+        printf("\tDRAM: 0x%08x, SRAM:0x%04x\n",
+               static_cast<int>(c.mem.dram_base),
+               static_cast<int>(c.mem.sram_base));
+        printf("\ty: size=%d, pad=[%d, %d]\n",
+               static_cast<int>(c.mem.y_size),
+               static_cast<int>(c.mem.y_pad_0),
+               static_cast<int>(c.mem.y_pad_1));
+        printf("\tx: size=%d, stride=%d, pad=[%d, %d]\n",
+               static_cast<int>(c.mem.x_size),
+               static_cast<int>(c.mem.x_stride),
+               static_cast<int>(c.mem.x_pad_0),
+               static_cast<int>(c.mem.x_pad_1));
+      } else if (c.mem.opcode==OPCODE_GEMM) {
+        // Print instruction field information
+        printf("GEMM\n");
+
+        printf("\tdep - pop prev: %d, pop next: %d, push prev: %d, push next: %d\n",
+               static_cast<int>(c.mem.pop_prev_dep),
+               static_cast<int>(c.mem.pop_next_dep),
+               static_cast<int>(c.mem.push_prev_dep),
+               static_cast<int>(c.mem.push_next_dep));
+        printf("\trange (%d, %d)\n",
+               static_cast<int>(c.gemm.uop_bgn),
+               static_cast<int>(c.gemm.uop_end));
+        printf("\touter loop - iter: %d, wgt: %d, inp: %d, acc: %d\n",
+               static_cast<int>(c.gemm.iter_out),
+               static_cast<int>(c.gemm.wgt_factor_out),
+               static_cast<int>(c.gemm.src_factor_out),
+               static_cast<int>(c.gemm.dst_factor_out));
+        printf("\tinner loop - iter: %d, wgt: %d, inp: %d, acc: %d\n",
+               static_cast<int>(c.gemm.iter_in),
+               static_cast<int>(c.gemm.wgt_factor_in),
+               static_cast<int>(c.gemm.src_factor_in),
+               static_cast<int>(c.gemm.dst_factor_in));
+      } else if (c.mem.opcode == OPCODE_ALU) {
+        // Print instruction field information
+        printf("ALU - %s\n", getOpcodeString(c.alu.alu_opcode, c.alu.use_imm));
+        printf("\tdep - pop prev: %d, pop next: %d, push prev: %d, push next: %d\n",
+               static_cast<int>(c.mem.pop_prev_dep),
+               static_cast<int>(c.mem.pop_next_dep),
+               static_cast<int>(c.mem.push_prev_dep),
+               static_cast<int>(c.mem.push_next_dep));
+        printf("\trange (%d, %d)\n",
+               static_cast<int>(c.alu.uop_bgn),
+               static_cast<int>(c.alu.uop_end));
+        printf("\touter loop - iter: %d, dst: %d, src: %d\n",
+               static_cast<int>(c.alu.iter_out),
+               static_cast<int>(c.alu.dst_factor_out),
+               static_cast<int>(c.alu.src_factor_out));
+        printf("\tinner loop - iter: %d, dst: %d, src: %d\n",
+               static_cast<int>(c.alu.iter_in),
+               static_cast<int>(c.alu.dst_factor_in),
+               static_cast<int>(c.alu.src_factor_in));
+      } else if (c.mem.opcode == OPCODE_FINISH) {
+        printf("FINISH\n");
+      }
+
+      // Count status in queues
+      if (c.mem.opcode == OPCODE_LOAD || c.mem.opcode == OPCODE_STORE) {
+        if (c.mem.opcode == OPCODE_STORE) {
+            assert(c.mem.pop_next_dep == false);
+            assert(c.mem.push_next_dep == false);
+            if (c.mem.pop_prev_dep) g2s_queue--;
+            if (c.mem.push_prev_dep) s2g_queue++;
+        } else if (c.mem.opcode == OPCODE_LOAD &&
+                   (c.mem.memory_type == MEM_ID_INP ||
+                    c.mem.memory_type == MEM_ID_WGT) ) {
+            assert(c.mem.pop_prev_dep == false);
+            assert(c.mem.push_prev_dep == false);
+            if (c.mem.pop_next_dep) g2l_queue--;
+            if (c.mem.push_next_dep) l2g_queue++;
+        } else {
+            if (c.mem.pop_prev_dep) l2g_queue--;
+            if (c.mem.push_prev_dep) g2l_queue++;
+            if (c.mem.pop_next_dep) s2g_queue--;
+            if (c.mem.push_next_dep) g2s_queue++;
+        }
+      } else if (c.mem.opcode == OPCODE_GEMM ||
+                 c.mem.opcode == OPCODE_ALU) {
+        // Print instruction field information
+        if (c.gemm.pop_prev_dep) l2g_queue--;
+        if (c.gemm.push_prev_dep) g2l_queue++;
+        if (c.gemm.pop_next_dep) s2g_queue--;
+        if (c.gemm.push_next_dep) g2s_queue++;
+      }
+      printf("\tl2g_queue = %d, g2l_queue = %d\n", l2g_queue, g2l_queue);
+      printf("\ts2g_queue = %d, g2s_queue = %d\n", s2g_queue, g2s_queue);
+    }
+  }
+
+  // Commit all pending pop of corresponding stage
+  void CommitPendingPop(int stage) {
+    // Handle the LD<->compute queue
+    // NOTE: pop executes on target(stage)
+    assert(stage > 0 && stage < 4);
+    if (pending_pop_prev_[stage] ||
+        pending_pop_next_[stage]) {
+      PushNoop(stage, false, false,
+               pending_pop_prev_[stage],
+               pending_pop_next_[stage]);
+      pending_pop_prev_[stage] = 0;
+      pending_pop_next_[stage] = 0;
+    }
+  }
+
+  void CommitPending() {
+    for (int i = kLoadStage; i <= kStoreStage; ++i) {
+      CommitPendingPop(i);
+    }
+  }
+
+  bool PendingPop() {
+    for (int i = kLoadStage; i <= kStoreStage; ++i) {
+      if (pending_pop_prev_[i]) return true;
+      if (pending_pop_next_[i]) return true;
+    }
+    return false;
+  }
+
+ protected:
+  /*! \return Add new instruction to the buffer. */
+  VTAGenericInsn* NextInsn() {
+    VTAGenericInsn* insn  = data() + dram_end_;
+    ++dram_end_;
+    assert(dram_end_ < kMaxElems);
+    return insn;
+  }
+  // Create a new instruction for a given stage
+  VTAGenericInsn* Create(PipelineStage stage) {
+    VTAGenericInsn* gptr = NextInsn();
+    VTAMemInsn* mptr = reinterpret_cast<VTAMemInsn*>(gptr);
+    mptr->pop_prev_dep = pending_pop_prev_[stage];
+    mptr->pop_next_dep = pending_pop_next_[stage];
+    mptr->push_prev_dep = false;
+    mptr->push_next_dep = false;
+    pending_pop_prev_[stage] = 0;
+    pending_pop_next_[stage] = 0;
+    return gptr;
+  }
+  // Get stage of the memory
+  static PipelineStage GetMemPipelineStage(int memory_type) {
+    if (memory_type == MEM_ID_ACC) return kComputeStage;
+    if (memory_type == MEM_ID_UOP) return kComputeStage;
+    return kLoadStage;
+  }
+  // Get stage of the computation
+  static PipelineStage GetPipelineStage(VTAMemInsn* insn) {
+    if (insn->opcode == OPCODE_GEMM) return kComputeStage;
+    if (insn->opcode == OPCODE_ALU) return kComputeStage;
+    if (insn->opcode == OPCODE_LOAD) {
+      if (insn->x_size == 0) return kNoneStage;
+      if (insn->memory_type == MEM_ID_ACC) return kComputeStage;
+      if (insn->memory_type == MEM_ID_UOP) return kComputeStage;
+      return kLoadStage;
+    }
+    if (insn->opcode == OPCODE_STORE) {
+      // FIXME: Right now memory_type is a 2-bit field which means that MEM_ID_OUT will appear as 0
+      //        For now we'll refrain from checking the memory_type to avoid an assertion error...
+      return kStoreStage;
+    }
+    assert(false);
+    return kNoneStage;
+  }
+  // Push no-op
+  void PushNoop(int stage,
+                bool push_prev_dep, bool push_next_dep,
+                bool pop_prev_dep, bool pop_next_dep) {
+    VTAMemInsn* insn = reinterpret_cast<VTAMemInsn*>(NextInsn());
+    insn->opcode = (stage==kStoreStage ? OPCODE_STORE : OPCODE_LOAD);
+    insn->push_prev_dep = push_prev_dep;
+    insn->push_next_dep = push_next_dep;
+    insn->pop_prev_dep = pop_prev_dep;
+    insn->pop_next_dep = pop_next_dep;
+    insn->sram_base = 0;
+    insn->dram_base = 0;
+    insn->y_size = 0;
+    insn->x_size = 0;
+    insn->x_stride = 0;
+    insn->y_pad_0 = 0;
+    insn->y_pad_1 = 0;
+    insn->x_pad_0 = 0;
+    insn->x_pad_1 = 0;
+    insn->memory_type = (stage == kLoadStage ? MEM_ID_INP : MEM_ID_UOP);
+  }
+
+ private:
+  // Pending pop of each isntruction queue, qid=0 is not used
+  int pending_pop_prev_[4];
+  int pending_pop_next_[4];
+  static constexpr int kElemBytes = sizeof(VTAGenericInsn);
+  static constexpr int kMaxElems = kMaxBytes / kElemBytes;
+};
+
+/*!
+ * \brief The command queue object that handles the request.
+ */
+class CommandQueue {
+ public:
+  CommandQueue() {
+    this->InitSpace();
+  }
+  void InitSpace() {
+    uop_queue_.InitSpace();
+    insn_queue_.InitSpace();
+    // VTA stage handles
+    vta_fetch_handle_ = VTAMapRegister(VTA_FETCH_ADDR, VTA_RANGE);
+    vta_load_handle_ = VTAMapRegister(VTA_LOAD_ADDR, VTA_RANGE);
+    vta_compute_handle_ = VTAMapRegister(VTA_COMPUTE_ADDR, VTA_RANGE);
+    vta_store_handle_ = VTAMapRegister(VTA_STORE_ADDR, VTA_RANGE);
+    printf("Initialize VTACommandHandle...\n");
+  }
+
+  ~CommandQueue() {
+    // Close VTA stage handle
+    VTAUnmapRegister(vta_fetch_handle_, VTA_RANGE);
+    VTAUnmapRegister(vta_load_handle_, VTA_RANGE);
+    VTAUnmapRegister(vta_compute_handle_, VTA_RANGE);
+    VTAUnmapRegister(vta_store_handle_, VTA_RANGE);
+    printf("Close VTACommandhandle...\n");
+  }
+
+  uint32_t GetElemBytes(uint32_t memory_id) {
+    switch (memory_id){
+      case MEM_ID_UOP: return UOP_ELEM_BYTES;
+      case MEM_ID_INP: return INP_ELEM_BYTES;
+      case MEM_ID_WGT: return WGT_ELEM_BYTES;
+      case MEM_ID_ACC: return ACC_ELEM_BYTES;
+      case MEM_ID_OUT: return INP_ELEM_BYTES;
+      default: break;
+    }
+    printf("Memory id not recognized: %d\n", memory_id);
+    assert(false);
+    return 0;
+  }
+
+  void LoadBuffer2D(void* src_dram_addr,
+                    uint32_t src_elem_offset,
+                    uint32_t x_size,
+                    uint32_t y_size,
+                    uint32_t x_stride,
+                    uint32_t x_pad_before,
+                    uint32_t y_pad_before,
+                    uint32_t x_pad_after,
+                    uint32_t y_pad_after,
+                    uint32_t dst_sram_index,
+                    uint32_t dst_memory_type) {
+    VTAMemInsn* insn = insn_queue_.CreateMemInsn(dst_memory_type);
+    insn->opcode = OPCODE_LOAD;
+    insn->memory_type = dst_memory_type;
+    insn->sram_base = dst_sram_index;
+    DataBuffer* src = DataBuffer::FromHandle(src_dram_addr);
+    insn->dram_base = src->phy_addr() / GetElemBytes(dst_memory_type) + src_elem_offset;
+    insn->y_size = y_size;
+    insn->x_size = x_size;
+    insn->x_stride = x_stride;
+    insn->y_pad_0 = y_pad_before;
+    insn->y_pad_1 = y_pad_after;
+    insn->x_pad_0 = x_pad_before;
+    insn->x_pad_1 = x_pad_after;
+    this->CheckInsnOverFlow();
+  }
+
+  void StoreBuffer2D(uint32_t src_sram_index,
+                     uint32_t src_memory_type,
+                     void* dst_dram_addr,
+                     uint32_t dst_elem_offset,
+                     uint32_t x_size,
+                     uint32_t y_size,
+                     uint32_t x_stride) {
+    VTAMemInsn* insn = insn_queue_.CreateStoreInsn();
+    insn->opcode = OPCODE_STORE;
+    insn->memory_type = src_memory_type;
+    insn->sram_base = src_sram_index;
+    DataBuffer* dst = DataBuffer::FromHandle(dst_dram_addr);
+    insn->dram_base = dst->phy_addr() / GetElemBytes(src_memory_type) + dst_elem_offset;
+    insn->y_size = y_size;
+    insn->x_size = x_size;
+    insn->x_stride = x_stride;
+    insn->y_pad_0 = 0;
+    insn->y_pad_1 = 0;
+    insn->x_pad_0 = 0;
+    insn->x_pad_1 = 0;
+    this->CheckInsnOverFlow();
+  }
+
+  void DepPush(int from_qid, int to_qid) {
+    insn_queue_.DepPush(from_qid, to_qid);
+  }
+
+  void DepPop(int from_qid, int to_qid) {
+    insn_queue_.DepPop(from_qid, to_qid);
+  }
+
+  void ReadBarrier(void* buffer, uint32_t elem_bits, uint32_t start, uint32_t extent) {
+    if (!(debug_flag_ & VTA_DEBUG_SKIP_READ_BARRIER)) {
+      uint32_t elem_bytes = (elem_bits + 8 - 1) / 8;
+      DataBuffer::FromHandle(buffer)->FlushCache(
+          elem_bytes * start, elem_bytes * extent);
+    }
+  }
+
+  void WriteBarrier(void* buffer, uint32_t elem_bits, uint32_t start, uint32_t extent) {
+    if (!(debug_flag_ & VTA_DEBUG_SKIP_WRITE_BARRIER)) {
+      uint32_t elem_bytes = (elem_bits + 8 - 1) / 8;
+      DataBuffer::FromHandle(buffer)->InvalidateCache(
+          elem_bytes * start, elem_bytes * extent);
+    }
+  }
+
+  void Synchronize(uint32_t wait_cycles) {
+    // Insert dependences to force serialization
+    if (debug_flag_ & VTA_DEBUG_FORCE_SERIAL) {
+      insn_queue_.RewriteForceSerial();
+    }
+    // This will issue finish after last store finishes
+    insn_queue_.DepPush(kStoreStage, kComputeStage);
+    insn_queue_.DepPush(kLoadStage, kComputeStage);
+    insn_queue_.DepPop(kStoreStage, kComputeStage);
+    insn_queue_.DepPop(kLoadStage, kComputeStage);
+    insn_queue_.CommitPendingPop(kComputeStage);
+    // NOTE: FINISH cannot contain pop
+    VTAGemInsn* insn = insn_queue_.CreateGemInsn();
+    insn->opcode = OPCODE_FINISH;
+    assert(!insn_queue_.PendingPop());
+    // Check if there are no instruction to execute at all
+    if (insn_queue_.count() == 0) return;
+    // Synchronization for the queues
+    uop_queue_.AutoReadBarrier();
+    insn_queue_.AutoReadBarrier();
+    // Dump instructions if debug enabled
+    if (debug_flag_ & VTA_DEBUG_DUMP_INSN) {
+      insn_queue_.DumpInsn();
+    }
+    // Make sure that the last instruction is a finish instruction
+    assert(reinterpret_cast<VTAMemInsn*>(
+        insn_queue_.data())[insn_queue_.count()-1].opcode == OPCODE_FINISH);
+
+#ifdef PYNQ_TARGET
+    // Make sure that we don't exceed contiguous physical memory limits
+    assert(insn_queue_.count() < MAX_XFER);
+
+    // NOTE: Register address map is derived from the auto-generated
+    // driver files available under hardware/build/vivado/<design>/export/driver
+    // FETCH @ 0x10 : Data signal of insn_count_V
+    VTAWriteMappedReg(vta_fetch_handle_, 0x10, insn_queue_.count());
+    // FETCH @ 0x18 : Data signal of insns_V
+    VTAWriteMappedReg(vta_fetch_handle_, 0x18, insn_queue_.dram_phy_addr());
+    // LOAD @ 0x10 : Data signal of inputs_V
+    VTAWriteMappedReg(vta_load_handle_, 0x10, 0);
+    // LOAD @ 0x18 : Data signal of weight_V
+    VTAWriteMappedReg(vta_load_handle_, 0x18, 0);
+    // COMPUTE @ 0x10 : Data signal of uops_V
+    VTAWriteMappedReg(vta_compute_handle_, 0x20, 0);
+    // COMPUTE @ 0x18 : Data signal of biases_V
+    VTAWriteMappedReg(vta_compute_handle_, 0x28, 0);
+    // STORE @ 0x10 : Data signal of outputs_V
+    VTAWriteMappedReg(vta_store_handle_, 0x10, 0);
+
+    // VTA start
+    VTAWriteMappedReg(vta_fetch_handle_, 0x0, VTA_START);
+    VTAWriteMappedReg(vta_load_handle_, 0x0, VTA_AUTORESTART);
+    VTAWriteMappedReg(vta_compute_handle_, 0x0, VTA_AUTORESTART);
+    VTAWriteMappedReg(vta_store_handle_, 0x0, VTA_AUTORESTART);
+
+    // Loop until the VTA is done
+    unsigned t, flag = 0;
+    for (t = 0; t < wait_cycles; ++t) {
+      flag = VTAReadMappedReg(vta_compute_handle_, 0x18);
+      if (flag == VTA_DONE) break;
+      std::this_thread::yield();
+    }
+    // Report error if timeout
+    assert(t < wait_cycles);
+#endif //PYNQ_TARGET
+
+    // Reset buffers
+    uop_queue_.Reset();
+    insn_queue_.Reset();
+  }
+
+  // Get record kernel
+  UopKernel* record_kernel() const {
+    assert(record_kernel_ != nullptr);
+    return record_kernel_;
+  }
+
+  // Set debug flag
+  void SetDebugFlag(int debug_flag) {
+    debug_flag_ = debug_flag;
+  }
+
+  void PushGEMMOp(void** uop_handle,
+                  int (*finit)(void*),
+                  void* signature,
+                  int nbytes) {
+    UopKernelMap** uptr = reinterpret_cast<UopKernelMap**>(uop_handle);
+    if (uptr[0] == nullptr) {
+      uptr[0] = new UopKernelMap();
+    }
+    UopKernel** kptr = uptr[0]->Get(signature, nbytes);
+    if (kptr[0] == nullptr) {
+      record_kernel_ = new UopKernel(static_cast<char*>(signature), nbytes);
+      assert((*finit)(signature) == 0);
+      kptr[0] = static_cast<UopKernel*>(record_kernel_);
+      if (debug_flag_ & VTA_DEBUG_DUMP_UOP) {
+        record_kernel_->Dump();
+      }
+      record_kernel_ = nullptr;
+    }
+    this->PushGEMMOp(static_cast<UopKernel*>(kptr[0]));
+    this->CheckInsnOverFlow();
+  }
+
+  void PushALUUop(void** uop_handle,
+                  int (*finit)(void*),
+                  void* signature,
+                  int nbytes) {
+    UopKernelMap** uptr = reinterpret_cast<UopKernelMap**>(uop_handle);
+    if (uptr[0] == nullptr) {
+      uptr[0] = new UopKernelMap();
+    }
+    UopKernel** kptr = uptr[0]->Get(signature, nbytes);
+    if (kptr[0] == nullptr) {
+      record_kernel_ = new UopKernel(static_cast<char*>(signature), nbytes);
+      assert((*finit)(signature) == 0);
+      kptr[0] = static_cast<UopKernel*>(record_kernel_);
+      if (debug_flag_ & VTA_DEBUG_DUMP_UOP) {
+        record_kernel_->Dump();
+      }
+      record_kernel_ = nullptr;
+    }
+    this->PushALUUop(static_cast<UopKernel*>(kptr[0]));
+    this->CheckInsnOverFlow();
+  }
+
+  static std::shared_ptr<CommandQueue>& ThreadLocal() {
+    static std::shared_ptr<CommandQueue> inst =
+        std::make_shared<CommandQueue>();
+    return inst;
+  }
+
+  static void Shutdown() {
+    ThreadLocal().reset();
+  }
+
+ private:
+  // Push GEMM uop to the command buffer
+  void PushGEMMOp(UopKernel* kernel) {
+    uop_queue_.Push(kernel,
+                    [this]() { this->AutoSync(); });
+    if (uop_queue_.pending()) {
+      VTAMemInsn* insn = insn_queue_.CreateMemInsn(MEM_ID_UOP);
+      insn->opcode = OPCODE_LOAD;
+      uop_queue_.FlushUopLoad(insn);
+    }
+    VTAGemInsn* insn = insn_queue_.CreateGemInsn();
+    insn->opcode = OPCODE_GEMM;
+    insn->uop_bgn = kernel->sram_begin_;
+    insn->uop_end = kernel->sram_end_;
+    const std::vector<UopKernel::LoopEntry> &loop = kernel->loop();
+    if (loop.size() > 0) {
+      insn->iter_out = loop[0].extent;
+      insn->wgt_factor_out = loop[0].wgt_factor;
+      insn->src_factor_out = loop[0].src_factor;
+      insn->dst_factor_out = loop[0].dst_factor;
+    } else {
+      insn->iter_out = 1;
+      insn->wgt_factor_out = 0;
+      insn->src_factor_out = 0;
+      insn->dst_factor_out = 0;
+    }
+    if (loop.size() > 1) {
+      insn->iter_in = loop[1].extent;
+      insn->wgt_factor_in = loop[1].wgt_factor;
+      insn->src_factor_in = loop[1].src_factor;
+      insn->dst_factor_in = loop[1].dst_factor;
+    } else {
+      insn->iter_in = 1;
+      insn->wgt_factor_in = 0;
+      insn->src_factor_in = 0;
+      insn->dst_factor_in = 0;
+    }
+  }
+
+  // Push ALU uop to the command buffer
+  void PushALUUop(UopKernel* kernel) {
+    uop_queue_.Push(kernel,
+                    [this]() { this->AutoSync(); });
+    if (uop_queue_.pending()) {
+      VTAMemInsn* insn = insn_queue_.CreateMemInsn(MEM_ID_UOP);
+      insn->opcode = OPCODE_LOAD;
+      uop_queue_.FlushUopLoad(insn);
+    }
+    VTAAluInsn* insn = insn_queue_.CreateAluInsn();
+    insn->opcode = OPCODE_ALU;
+    insn->uop_bgn = kernel->sram_begin_;
+    insn->uop_end = kernel->sram_end_;
+    insn->alu_opcode = kernel->opcode_;
+    insn->use_imm = kernel->use_imm_;
+    insn->imm = kernel->imm_val_;
+    const std::vector<UopKernel::LoopEntry> &loop = kernel->loop();
+    if (loop.size() == 0) {
+      insn->iter_out = 1;
+      insn->dst_factor_out = 0;
+      insn->src_factor_out = 0;
+      insn->iter_in = 1;
+      insn->dst_factor_in = 0;
+      insn->src_factor_in = 0;
+    } else if (loop.size() == 1) {
+      insn->iter_out = 1;
+      insn->dst_factor_out = 0;
+      insn->src_factor_out = 0;
+      insn->iter_in = loop[0].extent;
+      insn->dst_factor_in = loop[0].dst_factor;
+      insn->src_factor_in = loop[0].src_factor;
+    } else {
+      insn->iter_out = loop[0].extent;
+      insn->dst_factor_out = loop[0].dst_factor;
+      insn->src_factor_out = loop[0].src_factor;
+      insn->iter_in = loop[1].extent;
+      insn->dst_factor_in = loop[1].dst_factor;
+      insn->src_factor_in = loop[1].src_factor;
+    }
+  }
+
+  void CheckInsnOverFlow() {
+    // At each API call, we can at most commit:
+    // one pending store, one pending load, and one uop
+    if (insn_queue_.count() >= MAX_XFER) {
+      this->AutoSync();
+    }
+  }
+  // Auto sync when instruction overflow
+  void AutoSync() {
+    this->Synchronize(1 << 31);
+  }
+  // VTA handles (register maps)
+  VTAHandle vta_fetch_handle_{nullptr};
+  VTAHandle vta_load_handle_{nullptr};
+  VTAHandle vta_compute_handle_{nullptr};
+  VTAHandle vta_store_handle_{nullptr};
+  // Internal debug flag
+  int debug_flag_{0};
+  // The kernel we currently recording
+  UopKernel* record_kernel_{nullptr};
+  // Micro op queue
+  UopQueue<MAX_XFER, true, true> uop_queue_;
+  // instruction queue
+  InsnQueue<MAX_XFER, true, true> insn_queue_;
+};
+
+}  // namespace vta
+
+
+VTACommandHandle VTATLSCommandHandle() {
+  return vta::CommandQueue::ThreadLocal().get();
+}
+
+void VTARuntimeShutdown() {
+  vta::CommandQueue::Shutdown();
+}
+
+void* VTABufferAlloc(VTACommandHandle cmd, size_t size) {
+  return vta::DataBuffer::Alloc(size);
+}
+
+void VTABufferFree(VTACommandHandle cmd, void* buffer) {
+  vta::DataBuffer::Free(vta::DataBuffer::FromHandle(buffer));
+}
+
+void* VTABufferCPUPtr(VTACommandHandle cmd, void* buffer) {
+  return vta::DataBuffer::FromHandle(buffer)->virt_addr();
+}
+
+void VTABufferCopy(VTACommandHandle cmd,
+                   const void* from,
+                   size_t from_offset,
+                   void* to,
+                   size_t to_offset,
+                   size_t size,
+                   int kind_mask) {
+  vta::DataBuffer* from_buffer = nullptr;
+  vta::DataBuffer* to_buffer = nullptr;
+
+  if (kind_mask & 2) {
+    from_buffer = vta::DataBuffer::FromHandle(from);
+    from = from_buffer->virt_addr();
+  }
+  if (kind_mask & 1) {
+    to_buffer = vta::DataBuffer::FromHandle(to);
+    to = to_buffer->virt_addr();
+  }
+  if (from_buffer) {
+    from_buffer->InvalidateCache(from_offset, size);
+  }
+
+  memcpy(static_cast<char*>(to) + to_offset,
+         static_cast<const char*>(from) + from_offset,
+         size);
+  if (to_buffer) {
+    to_buffer->FlushCache(to_offset, size);
+  }
+}
+
+void VTASetDebugMode(VTACommandHandle cmd, int debug_flag) {
+  static_cast<vta::CommandQueue*>(cmd)->
+      SetDebugFlag(debug_flag);
+}
+
+void VTAWriteBarrier(VTACommandHandle cmd,
+                     void* buffer, uint32_t elem_bits,
+                     uint32_t start, uint32_t extent) {
+  static_cast<vta::CommandQueue*>(cmd)->
+      WriteBarrier(buffer, elem_bits, start, extent);
+}
+
+void VTAReadBarrier(VTACommandHandle cmd,
+                    void* buffer, uint32_t elem_bits,
+                    uint32_t start, uint32_t extent) {
+  static_cast<vta::CommandQueue*>(cmd)->
+      ReadBarrier(buffer, elem_bits, start, extent);
+}
+
+void VTALoadBuffer2D(VTACommandHandle cmd,
+                     void* src_dram_addr,
+                     uint32_t src_elem_offset,
+                     uint32_t x_size,
+                     uint32_t y_size,
+                     uint32_t x_stride,
+                     uint32_t x_pad_before,
+                     uint32_t y_pad_before,
+                     uint32_t x_pad_after,
+                     uint32_t y_pad_after,
+                     uint32_t dst_sram_index,
+                     uint32_t dst_memory_type) {
+  static_cast<vta::CommandQueue*>(cmd)->
+      LoadBuffer2D(src_dram_addr, src_elem_offset,
+                   x_size, y_size, x_stride,
+                   x_pad_before, y_pad_before,
+                   x_pad_after, y_pad_after,
+                   dst_sram_index, dst_memory_type);
+}
+
+void VTAStoreBuffer2D(VTACommandHandle cmd,
+                      uint32_t src_sram_index,
+                      uint32_t src_memory_type,
+                      void* dst_dram_addr,
+                      uint32_t dst_elem_offset,
+                      uint32_t x_size,
+                      uint32_t y_size,
+                      uint32_t x_stride) {
+ static_cast<vta::CommandQueue*>(cmd)->
+     StoreBuffer2D(src_sram_index, src_memory_type,
+                   dst_dram_addr, dst_elem_offset,
+                   x_size, y_size, x_stride);
+}
+
+void VTAUopPush(uint32_t mode,
+                uint32_t reset_out,
+                uint32_t dst_index,
+                uint32_t src_index,
+                uint32_t wgt_index,
+                uint32_t opcode,
+                uint32_t use_imm,
+                uint32_t imm_val) {
+  vta::CommandQueue::ThreadLocal()->record_kernel()
+      ->Push(mode, reset_out, dst_index, src_index,
+             wgt_index, opcode, use_imm, imm_val);
+}
+
+void VTAUopLoopBegin(uint32_t extent,
+                     uint32_t dst_factor,
+                     uint32_t src_factor,
+                     uint32_t wgt_factor) {
+  vta::CommandQueue::ThreadLocal()->record_kernel()
+      ->PushLoopBegin(extent, dst_factor, src_factor, wgt_factor);
+}
+
+void VTAUopLoopEnd() {
+  vta::CommandQueue::ThreadLocal()->record_kernel()
+      ->PushLoopEnd();
+}
+
+int VTAPushGEMMOp(void** uop_handle,
+                  int (*finit)(void*),
+                  void* signature,
+                  int nbytes) {
+  vta::CommandQueue::ThreadLocal()->
+      PushGEMMOp(uop_handle, finit, signature, nbytes);
+  return 0;
+}
+
+int VTAPushALUOp(void** uop_handle,
+                 int (*finit)(void*),
+                 void* signature,
+                 int nbytes) {
+  vta::CommandQueue::ThreadLocal()->
+      PushALUUop(uop_handle, finit, signature, nbytes);
+  return 0;
+}
+
+int VTADepPush(VTACommandHandle cmd, int from_qid, int to_qid) {
+  static_cast<vta::CommandQueue*>(cmd)->
+      DepPush(from_qid, to_qid);
+  return 0;
+}
+
+int VTADepPop(VTACommandHandle cmd, int from_qid, int to_qid) {
+  static_cast<vta::CommandQueue*>(cmd)->
+      DepPop(from_qid, to_qid);
+  return 0;
+}
+
+void VTASynchronize(VTACommandHandle cmd, uint32_t wait_cycles) {
+  static_cast<vta::CommandQueue*>(cmd)->
+      Synchronize(wait_cycles);
+}
diff --git a/vta/src/tvm/vta_device_api.cc b/vta/src/tvm/vta_device_api.cc
new file mode 100644
index 000000000000..b686b65fc415
--- /dev/null
+++ b/vta/src/tvm/vta_device_api.cc
@@ -0,0 +1,106 @@
+// simply include the driver for now.
+#include <tvm/runtime/registry.h>
+#include <dmlc/thread_local.h>
+#include <vta/runtime.h>
+#include "../../tvm/src/runtime/workspace_pool.h"
+
+namespace tvm {
+namespace runtime {
+
+std::string VTARPCGetPath(const std::string& name) {
+  static const PackedFunc* f =
+      runtime::Registry::Get("tvm.contrib.rpc.server.workpath");
+  CHECK(f != nullptr) << "require tvm.contrib.rpc.server.workpath";
+  return (*f)(name);
+}
+
+// Global functions that can be called
+TVM_REGISTER_GLOBAL("tvm.contrib.vta.init")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    std::string path = VTARPCGetPath(args[0]);
+    VTAProgram(path.c_str());
+    LOG(INFO) << "VTA initialization end with bistream " << path;
+  });
+
+TVM_REGISTER_GLOBAL("tvm.contrib.rpc.server.shutdown")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    VTARuntimeShutdown();
+  });
+
+class VTADeviceAPI final : public DeviceAPI {
+ public:
+  void SetDevice(TVMContext ctx) final {}
+
+  void GetAttr(TVMContext ctx, DeviceAttrKind kind, TVMRetValue* rv) final {
+    if (kind == kExist) {
+      *rv = 1;
+    }
+  }
+
+  void* AllocDataSpace(TVMContext ctx,
+                       size_t size, size_t alignment,
+                       TVMType type_hint) final {
+    return VTABufferAlloc(VTATLSCommandHandle(), size);
+  }
+
+  void FreeDataSpace(TVMContext ctx, void* ptr) final {
+    VTABufferFree(VTATLSCommandHandle(), ptr);
+  }
+
+  void CopyDataFromTo(const void* from,
+                      size_t from_offset,
+                      void* to,
+                      size_t to_offset,
+                      size_t size,
+                      TVMContext ctx_from,
+                      TVMContext ctx_to,
+                      TVMStreamHandle stream) final {
+    int kind_mask = 0;
+    if (ctx_from.device_type != kDLCPU) {
+      kind_mask |= 2;
+    }
+    if (ctx_to.device_type != kDLCPU) {
+      kind_mask |= 1;
+    }
+    VTABufferCopy(VTATLSCommandHandle(),
+                  from, from_offset,
+                  to, to_offset,
+                  size, kind_mask);
+  }
+
+  void StreamSync(TVMContext ctx, TVMStreamHandle stream) final {
+  }
+
+  void* AllocWorkspace(TVMContext ctx, size_t size, TVMType type_hint) final;
+
+  void FreeWorkspace(TVMContext ctx, void* data) final;
+
+  static const std::shared_ptr<VTADeviceAPI>& Global() {
+    static std::shared_ptr<VTADeviceAPI> inst =
+        std::make_shared<VTADeviceAPI>();
+    return inst;
+  }
+};
+
+struct VTAWorkspacePool : public WorkspacePool {
+  VTAWorkspacePool() :
+      WorkspacePool(static_cast<DLDeviceType>(kExtDev),
+                    VTADeviceAPI::Global()) {}
+};
+
+void* VTADeviceAPI::AllocWorkspace(TVMContext ctx, size_t size, TVMType type_hint) {
+  return dmlc::ThreadLocalStore<VTAWorkspacePool>::Get()
+      ->AllocWorkspace(ctx, size);
+}
+
+void VTADeviceAPI::FreeWorkspace(TVMContext ctx, void* data) {
+  dmlc::ThreadLocalStore<VTAWorkspacePool>::Get()->FreeWorkspace(ctx, data);
+}
+
+TVM_REGISTER_GLOBAL("device_api.ext_dev")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    DeviceAPI* ptr = VTADeviceAPI::Global().get();
+    *rv = static_cast<void*>(ptr);
+  });
+}  // namespace runtime
+}  // namespace tvm
diff --git a/vta/tests/driver/Makefile b/vta/tests/driver/Makefile
deleted file mode 100644
index dad8dbed105e..000000000000
--- a/vta/tests/driver/Makefile
+++ /dev/null
@@ -1,59 +0,0 @@
-CC ?= g++
-CFLAGS = -Wall -O3 -std=c++11 -I/usr/include
-LDFLAGS = -L/usr/lib -L/home/xilinx/pynq/drivers
-LIBS = -l:libsds_lib.so -l:libdma.so
-SRC_DIR = ../../src
-INCLUDE_DIR = ../../include
-DRIVER_DIR = $(SRC_DIR)/driver/pynq
-TESTLIB_DIR = $(SRC_DIR)/test
-VPATH = $(DRIVER_DIR):$(TESTLIB_DIR)
-SOURCES = vta_pynq_driver.c vta_test_lib.cc
-OBJECTS = vta_pynq_driver.o vta_test_lib.o driver_test.o
-EXECUTABLE = vta
-
-# VTA Parameters
-#  Log of input width in bits
-LOG_INP_WIDTH = 3
-#  Log of weight width in bits
-LOG_WGT_WIDTH = 3
-#  Log of accum width in bits
-LOG_ACC_WIDTH = 5
-#  Log of output width in bits
-LOG_OUT_WIDTH = $(LOG_INP_WIDTH)
-#  Log of tensor batch size (A in (A,B)x(B,C) matrix multiplication)
-LOG_BATCH = 0
-#  Log of tensor inner block size (B in (A,B)x(B,C) matrix multiplication)
-LOG_IN_BLOCK = 4
-#  Log of tensor outer block size (C in (A,B)x(B,C) matrix multiplication)
-LOG_OUT_BLOCK = 4
-#  Log of uop buffer size in Bytes
-LOG_UOP_BUFF_SIZE = 15
-#  Log of inp buffer size in Bytes
-LOG_INP_BUFF_SIZE = 15
-#  Log of wgt buffer size in Bytes
-LOG_WGT_BUFF_SIZE = 15
-#  Log of acc buffer size in Bytes
-LOG_ACC_BUFF_SIZE = 17
-#  Log of out buffer size in Bytes
-LOG_OUT_BUFF_SIZE = $(shell echo "$$(( $(LOG_ACC_BUFF_SIZE)+$(LOG_OUT_WIDTH)-$(LOG_ACC_WIDTH) ))" )
-
-# Define flags
-CFLAGS += -I $(INCLUDE_DIR) -DNO_SIM \
-	-DDEBUG=0 -DLOG_WGT_WIDTH=$(LOG_WGT_WIDTH) -DLOG_INP_WIDTH=$(LOG_INP_WIDTH) \
-	-DLOG_ACC_WIDTH=$(LOG_ACC_WIDTH) -DLOG_OUT_WIDTH=$(LOG_OUT_WIDTH) \
-	-DLOG_BATCH=$(LOG_BATCH) -DLOG_BLOCK_IN=$(LOG_IN_BLOCK) -DLOG_BLOCK_OUT=$(LOG_OUT_BLOCK) \
-	-DLOG_UOP_BUFF_SIZE=$(LOG_UOP_BUFF_SIZE) -DLOG_INP_BUFF_SIZE=$(LOG_INP_BUFF_SIZE) \
-	-DLOG_WGT_BUFF_SIZE=$(LOG_WGT_BUFF_SIZE) -DLOG_ACC_BUFF_SIZE=$(LOG_ACC_BUFF_SIZE) \
-	-DLOG_OUT_BUFF_SIZE=$(LOG_OUT_BUFF_SIZE)
-
-# All Target
-all: $(EXECUTABLE)
-
-%.o: %.cc $(SOURCES)
-	$(CC) -c -o $@ $< $(CFLAGS)
-
-$(EXECUTABLE): $(OBJECTS)
-	$(CC) $(LDFLAGS) $(OBJECTS) -o $@ $(LIBS)
-
-clean:
-	rm -rf *.o $(EXECUTABLE)
diff --git a/vta/src/test/vta_test_lib.cc b/vta/tests/hardware/common/test_lib.cc
similarity index 97%
rename from vta/src/test/vta_test_lib.cc
rename to vta/tests/hardware/common/test_lib.cc
index df8fce0d87c7..d203b2aa1307 100644
--- a/vta/src/test/vta_test_lib.cc
+++ b/vta/tests/hardware/common/test_lib.cc
@@ -4,7 +4,7 @@
  * \brief Test library for the VTA design simulation and driver tests.
  */
 
-#include "vta_test_lib.h"
+#include "./test_lib.h"
 
 const char* getOpcodeString(int opcode, bool use_imm) {
   // Returns string name
@@ -153,7 +153,7 @@ void free3dArray(T *** array, int rows, int cols, int depth) {
 
 void * allocBuffer(size_t num_bytes) {
 #ifdef NO_SIM
-  return cma_alloc(num_bytes, CACHED);
+  return VTAMemAlloc(num_bytes, CACHED);
 #else
   return malloc(num_bytes);
 #endif
@@ -161,7 +161,7 @@ void * allocBuffer(size_t num_bytes) {
 
 void freeBuffer(void * buffer) {
 #ifdef NO_SIM
-  return cma_free(buffer);
+  return VTAMemFree(buffer);
 #else
   return free(buffer);
 #endif
@@ -353,7 +353,7 @@ VTAUop * getCopyUops(int y_size, int x_size, int uop_compression) {
 
   // Allocate buffer
 #ifdef NO_SIM
-  VTAUop *uop_buf = (VTAUop *) cma_alloc(sizeof(VTAUop) * uop_size, CACHED);
+  VTAUop *uop_buf = (VTAUop *) VTAMemAlloc(sizeof(VTAUop) * uop_size, CACHED);
 #else
   VTAUop *uop_buf = (VTAUop *) malloc(sizeof(VTAUop) * uop_size);
 #endif
@@ -388,7 +388,7 @@ VTAUop * getGEMMUops(int batch, int in_feat, int out_feat, bool uop_compression,
 
   // Allocate buffer
 #ifdef NO_SIM
-  VTAUop *uop_buf = (VTAUop *) cma_alloc(sizeof(VTAUop) * uop_size, CACHED);
+  VTAUop *uop_buf = (VTAUop *) VTAMemAlloc(sizeof(VTAUop) * uop_size, CACHED);
 #else
   VTAUop *uop_buf = (VTAUop *) malloc(sizeof(VTAUop) * uop_size);
 #endif
@@ -449,7 +449,7 @@ VTAUop * getMapALUUops(int vector_size, bool uop_compression) {
 
   // Allocate buffer
 #ifdef NO_SIM
-  VTAUop *uop_buf = (VTAUop *) cma_alloc(sizeof(VTAUop) * uop_size, CACHED);
+  VTAUop *uop_buf = (VTAUop *) VTAMemAlloc(sizeof(VTAUop) * uop_size, CACHED);
 #else
   VTAUop *uop_buf = (VTAUop *) malloc(sizeof(VTAUop) * uop_size);
 #endif
@@ -762,7 +762,7 @@ int alu_test(int opcode, bool use_imm, int batch, int vector_size, bool uop_comp
   }
 
   // Compute reference output
-  inp_T **outputs_ref = alloc2dArray<inp_T>(batch, vector_size);
+  out_T **outputs_ref = alloc2dArray<out_T>(batch, vector_size);
   for (int i = 0; i < batch; i ++) {
     for (int j = 0; j < vector_size; j ++) {
       acc_T tmp = 0;
@@ -802,7 +802,7 @@ int alu_test(int opcode, bool use_imm, int batch, int vector_size, bool uop_comp
         tmp = inputs[i][j] >> immediate[i / BATCH];
       }
       // Set
-      outputs_ref[i][j] = (inp_T) tmp;
+      outputs_ref[i][j] = (out_T) tmp;
     }
   }
 
@@ -811,7 +811,7 @@ int alu_test(int opcode, bool use_imm, int batch, int vector_size, bool uop_comp
   packBuffer<acc_T, ACC_WIDTH>(bias_buf, inputs, batch, vector_size * input_sets, BATCH, BLOCK_OUT);
 
   // Prepare output buffer
-  inp_T *output_buf = (inp_T *) allocBuffer(INP_ELEM_BYTES * batch * tx_size * input_sets);
+  out_T *output_buf = (out_T *) allocBuffer(INP_ELEM_BYTES * batch * tx_size * input_sets);
 
 #ifdef NO_SIM
   // Invoke the VTA
@@ -833,8 +833,8 @@ int alu_test(int opcode, bool use_imm, int batch, int vector_size, bool uop_comp
 #endif
 
   // Unpack output buffer
-  inp_T **outputs = alloc2dArray<inp_T>(batch, vector_size);
-  unpackBuffer<inp_T, INP_WIDTH>(outputs, output_buf, batch, vector_size, BATCH, BLOCK_OUT);
+  out_T **outputs = alloc2dArray<out_T>(batch, vector_size);
+  unpackBuffer<out_T, OUT_WIDTH>(outputs, output_buf, batch, vector_size, BATCH, BLOCK_OUT);
 
   // Correctness checks
   int err = 0;
@@ -853,8 +853,8 @@ int alu_test(int opcode, bool use_imm, int batch, int vector_size, bool uop_comp
   // Free all allocated arrays
   free(immediate);
   free2dArray<acc_T>(inputs, batch, vector_size * input_sets);
-  free2dArray<inp_T>(outputs_ref, batch, vector_size);
-  free2dArray<inp_T>(outputs, batch, vector_size);
+  free2dArray<out_T>(outputs_ref, batch, vector_size);
+  free2dArray<out_T>(outputs, batch, vector_size);
   freeBuffer(insn_buf);
   freeBuffer(uop_buf);
   freeBuffer(bias_buf);
@@ -891,17 +891,17 @@ virtual_threads=%d\n",
   int ins_size = batch / block * out_feat / block * (2 + in_feat / block * 3) + 2;
   int uop_size = uop_compression ? block / BATCH * virtual_threads :
     block / BATCH * block / BLOCK_IN * block / BLOCK_OUT * virtual_threads;
-  int wgt_size = in_feat / BLOCK_IN * out_feat / BLOCK_OUT;
   int inp_size = batch / BATCH * in_feat / BLOCK_IN;
+  int wgt_size = in_feat / BLOCK_IN * out_feat / BLOCK_OUT;
   int out_size = batch / BATCH * out_feat / BLOCK_OUT;
   // Blocked buffer sizes (in terms of elements)
-  int wgt_block_size = block / BLOCK_IN * block / BLOCK_OUT;
   int inp_block_size = block / BATCH * block / BLOCK_IN;
+  int wgt_block_size = block / BLOCK_IN * block / BLOCK_OUT;
   int out_block_size = block / BATCH * block / BLOCK_OUT;
   // Make sure we don't exceed buffer bounds
   assert(uop_size <= UOP_BUFF_DEPTH);
-  assert(wgt_block_size <= WGT_BUFF_DEPTH);
   assert(inp_block_size <= INP_BUFF_DEPTH);
+  assert(wgt_block_size <= WGT_BUFF_DEPTH);
   assert(out_block_size <= ACC_BUFF_DEPTH);
 
   // Initialize instruction buffer
@@ -1017,15 +1017,15 @@ virtual_threads=%d\n",
   printMicroOp(uop_size, uop_buf);
 #endif
 
-  // Initialize weights
-  wgt_T **weights = allocInit2dArray<wgt_T, WGT_WIDTH>(out_feat, in_feat);
   // Initialize inputs
   inp_T **inputs = allocInit2dArray<inp_T, INP_WIDTH>(batch, in_feat);
+  // Initialize weights
+  wgt_T **weights = allocInit2dArray<wgt_T, WGT_WIDTH>(out_feat, in_feat);
   // Initialize biases
   acc_T **biases = allocInit2dArray<acc_T, ACC_WIDTH>(batch, out_feat);
 
   // Reference GEMM implementation
-  inp_T **outputs_ref = alloc2dArray<inp_T>(batch, out_feat);
+  out_T **outputs_ref = alloc2dArray<out_T>(batch, out_feat);
   for (int i = 0; i < batch; i ++) {
     for (int j = 0; j < out_feat; j ++) {
       acc_T sum = biases[i][j];
@@ -1033,21 +1033,21 @@ virtual_threads=%d\n",
         sum += (acc_T) (inputs[i][k] * weights[j][k]);
       }
       // Set
-      outputs_ref[i][j] = (inp_T) sum;
+      outputs_ref[i][j] = (out_T) sum;
     }
   }
 
-  // Prepare the weight buffer
-  wgt_T *weight_buf = (wgt_T *) allocBuffer(WGT_ELEM_BYTES * wgt_size);
-  packBuffer<wgt_T, WGT_WIDTH>(weight_buf, weights, out_feat, in_feat, BLOCK_OUT, BLOCK_IN);
   // Prepare the input buffer
   inp_T *input_buf = (inp_T *) allocBuffer(INP_ELEM_BYTES * inp_size);
   packBuffer<inp_T, INP_WIDTH>(input_buf, inputs, batch, in_feat, BATCH, BLOCK_IN);
+  // Prepare the weight buffer
+  wgt_T *weight_buf = (wgt_T *) allocBuffer(WGT_ELEM_BYTES * wgt_size);
+  packBuffer<wgt_T, WGT_WIDTH>(weight_buf, weights, out_feat, in_feat, BLOCK_OUT, BLOCK_IN);
   // Prepare the bias buffer
   acc_T *bias_buf = (acc_T *) allocBuffer(ACC_ELEM_BYTES * out_size);
   packBuffer<acc_T, ACC_WIDTH>(bias_buf, biases, batch, out_feat, BATCH, BLOCK_OUT);
   // Prepare the output buffer
-  inp_T *output_buf = (inp_T *) allocBuffer(INP_ELEM_BYTES * out_size);
+  out_T *output_buf = (out_T *) allocBuffer(INP_ELEM_BYTES * out_size);
 
 #ifdef NO_SIM
   // Invoke the VTA
@@ -1069,8 +1069,8 @@ virtual_threads=%d\n",
 #endif
 
   // Unpack output data
-  inp_T **outputs = alloc2dArray<inp_T>(batch, out_feat);
-  unpackBuffer<inp_T, INP_WIDTH>(outputs, output_buf, batch, out_feat, BATCH, BLOCK_OUT);
+  out_T **outputs = alloc2dArray<out_T>(batch, out_feat);
+  unpackBuffer<out_T, OUT_WIDTH>(outputs, output_buf, batch, out_feat, BATCH, BLOCK_OUT);
 
   // Correctness checks
   int err = 0;
@@ -1087,15 +1087,15 @@ virtual_threads=%d\n",
   }
 
   // Free all allocated arrays
-  free2dArray<wgt_T>(weights, out_feat, in_feat);
   free2dArray<inp_T>(inputs, batch, in_feat);
+  free2dArray<wgt_T>(weights, out_feat, in_feat);
   free2dArray<acc_T>(biases, batch, out_feat);
-  free2dArray<inp_T>(outputs_ref, batch, out_feat);
-  free2dArray<inp_T>(outputs, batch, out_feat);
+  free2dArray<out_T>(outputs_ref, batch, out_feat);
+  free2dArray<out_T>(outputs, batch, out_feat);
   freeBuffer((void *) insn_buf);
   freeBuffer((void *) uop_buf);
-  freeBuffer((void *) weight_buf);
   freeBuffer((void *) input_buf);
+  freeBuffer((void *) weight_buf);
   freeBuffer((void *) bias_buf);
   freeBuffer((void *) output_buf);
 
diff --git a/vta/include/vta_test_lib.h b/vta/tests/hardware/common/test_lib.h
similarity index 98%
rename from vta/include/vta_test_lib.h
rename to vta/tests/hardware/common/test_lib.h
index b4eb16848d54..fad2e4daddfb 100644
--- a/vta/include/vta_test_lib.h
+++ b/vta/tests/hardware/common/test_lib.h
@@ -7,21 +7,25 @@
 #ifndef VTA_TESTLIB_H_
 #define VTA_TESTLIB_H_
 
-#include "vta_params.h"
-
 #include <assert.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <vta/hw_spec.h>
 
 #ifdef NO_SIM
 
-#include "vta_pynq_driver.h"
+#include <vta/driver.h>
+
+#ifdef PYNQ_TARGET
+#include "../../../src/pynq/pynq_driver.h"
+#endif //PYNQ_TARGET
 
 typedef uint64_t axi_T;
 typedef uint32_t uop_T;
 typedef int8_t wgt_T;
 typedef int8_t inp_T;
+typedef int8_t out_T;
 typedef int32_t acc_T;
 
 uint64_t vta (
@@ -35,8 +39,7 @@ uint64_t vta (
 
 #else //NO_SIM
 
-#include "vta.h"
-#include "vta_typedefs.h"
+#include "../../../hardware/vivado/src/vta.h"
 
 #endif //NO_SIM
 
diff --git a/vta/tests/hardware/pynq/Makefile b/vta/tests/hardware/pynq/Makefile
new file mode 100644
index 000000000000..7e70366f32c0
--- /dev/null
+++ b/vta/tests/hardware/pynq/Makefile
@@ -0,0 +1,37 @@
+CC ?= g++
+CFLAGS = -Wall -O3 -std=c++11 -I/usr/include
+LDFLAGS = -L/usr/lib -L/home/xilinx/pynq/drivers
+LIBS = -l:libsds_lib.so -l:libdma.so
+INCLUDE_DIR = ../../../include
+DRIVER_DIR = ../../../src/pynq
+TESTLIB_DIR = ../common
+VPATH = $(DRIVER_DIR):$(TESTLIB_DIR)
+SOURCES = pynq_driver.cc test_lib.cc
+OBJECTS = pynq_driver.o test_lib.o metal_test.o
+EXECUTABLE = vta
+
+# Include top-level config file
+ifndef config
+ifneq ("$(wildcard ../../../config.mk)", "")
+	config = ../../../config.mk
+else
+	config = ../../../make/config.mk
+endif
+endif
+include $(config)
+
+# Define flags
+CFLAGS += -I $(INCLUDE_DIR) -DNO_SIM -DDEBUG=0
+CFLAGS += $(ADD_CFLAGS)
+
+# All Target
+all: $(EXECUTABLE)
+
+%.o: %.cc $(SOURCES)
+	$(CC) -c -o $@ $< $(CFLAGS)
+
+$(EXECUTABLE): $(OBJECTS)
+	$(CC) $(LDFLAGS) $(OBJECTS) -o $@ $(LIBS)
+
+clean:
+	rm -rf *.o $(EXECUTABLE)
diff --git a/vta/tests/driver/driver_test.cc b/vta/tests/hardware/pynq/metal_test.cc
similarity index 71%
rename from vta/tests/driver/driver_test.cc
rename to vta/tests/hardware/pynq/metal_test.cc
index 6cdc32a955d8..b5147399c18c 100644
--- a/vta/tests/driver/driver_test.cc
+++ b/vta/tests/hardware/pynq/metal_test.cc
@@ -9,8 +9,9 @@
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
-#include "vta_test_lib.h"
-#include "vta_pynq_driver.h"
+#include <vta/driver.h>
+#include "../../../src/pynq/pynq_driver.h"
+#include "../common/test_lib.h"
 
 // VTA invocation (present the same abstraction as in the simulation tests)
 uint64_t vta (
@@ -43,18 +44,18 @@ uint64_t vta (
 #endif
 
     // Program VTA
-    ProgramVTA(bitstream);
+    VTAProgram(bitstream);
     // Get VTA handles
-    VTAHandle vta_fetch_handle = MapRegister(VTA_FETCH_ADDR, VTA_RANGE);
-    VTAHandle vta_load_handle = MapRegister(VTA_LOAD_ADDR, VTA_RANGE);
-    VTAHandle vta_compute_handle = MapRegister(VTA_COMPUTE_ADDR, VTA_RANGE);
-    VTAHandle vta_store_handle = MapRegister(VTA_STORE_ADDR, VTA_RANGE);
+    VTAHandle vta_fetch_handle = VTAMapRegister(VTA_FETCH_ADDR, VTA_RANGE);
+    VTAHandle vta_load_handle = VTAMapRegister(VTA_LOAD_ADDR, VTA_RANGE);
+    VTAHandle vta_compute_handle = VTAMapRegister(VTA_COMPUTE_ADDR, VTA_RANGE);
+    VTAHandle vta_store_handle = VTAMapRegister(VTA_STORE_ADDR, VTA_RANGE);
 
     // Physical address pointers
     uint32_t insn_phy = insns ? cma_get_phy_addr(insns) : 0;
     uint32_t uop_phy = uops ? cma_get_phy_addr(uops) : 0;
-    uint32_t weight_phy = weights ? cma_get_phy_addr(weights) : 0;
     uint32_t input_phy = inputs ? cma_get_phy_addr(inputs) : 0;
+    uint32_t weight_phy = weights ? cma_get_phy_addr(weights) : 0;
     uint32_t bias_phy = biases ? cma_get_phy_addr(biases) : 0;
     uint32_t output_phy = outputs ? cma_get_phy_addr(outputs) : 0;
 
@@ -65,29 +66,29 @@ uint64_t vta (
     clock_gettime(CLOCK_REALTIME, &start);
 
     // FETCH @ 0x10 : Data signal of insn_count_V
-    WriteMappedReg(vta_fetch_handle, 0x10, insn_count);
+    VTAWriteMappedReg(vta_fetch_handle, 0x10, insn_count);
     // FETCH @ 0x18 : Data signal of insns_V
-    if (insns) WriteMappedReg(vta_fetch_handle, 0x18, insn_phy);
-    // LOAD @ 0x10 : Data signal of weight_V
-    if (weights) WriteMappedReg(vta_load_handle, 0x10, weight_phy);
-    // LOAD @ 0x18 : Data signal of inputs_V
-    if (inputs) WriteMappedReg(vta_load_handle, 0x18, input_phy);
+    if (insns) VTAWriteMappedReg(vta_fetch_handle, 0x18, insn_phy);
+    // LOAD @ 0x10 : Data signal of inputs_V
+    if (inputs) VTAWriteMappedReg(vta_load_handle, 0x10, input_phy);
+    // LOAD @ 0x18 : Data signal of weight_V
+    if (weights) VTAWriteMappedReg(vta_load_handle, 0x18, weight_phy);
     // COMPUTE @ 0x20 : Data signal of uops_V
-    if (uops) WriteMappedReg(vta_compute_handle, 0x20, uop_phy);
+    if (uops) VTAWriteMappedReg(vta_compute_handle, 0x20, uop_phy);
     // COMPUTE @ 0x28 : Data signal of biases_V
-    if (biases) WriteMappedReg(vta_compute_handle, 0x28, bias_phy);
+    if (biases) VTAWriteMappedReg(vta_compute_handle, 0x28, bias_phy);
     // STORE @ 0x10 : Data signal of outputs_V
-    if (outputs) WriteMappedReg(vta_store_handle, 0x10, output_phy);
+    if (outputs) VTAWriteMappedReg(vta_store_handle, 0x10, output_phy);
 
     // VTA start
-    WriteMappedReg(vta_fetch_handle, 0x0, 0x1);
-    WriteMappedReg(vta_load_handle, 0x0, 0x81);
-    WriteMappedReg(vta_compute_handle, 0x0, 0x81);
-    WriteMappedReg(vta_store_handle, 0x0, 0x81);
+    VTAWriteMappedReg(vta_fetch_handle, 0x0, 0x1);
+    VTAWriteMappedReg(vta_load_handle, 0x0, 0x81);
+    VTAWriteMappedReg(vta_compute_handle, 0x0, 0x81);
+    VTAWriteMappedReg(vta_store_handle, 0x0, 0x81);
 
     int flag = 0, t = 0;
     for (t = 0; t < 10000000; ++t) {
-      flag = ReadMappedReg(vta_compute_handle, 0x18);
+      flag = VTAReadMappedReg(vta_compute_handle, 0x18);
       if (flag & VTA_DONE) break;
     }
 
@@ -104,10 +105,10 @@ uint64_t vta (
     t_fpga = 1000000000ULL * (stop.tv_sec - start.tv_sec) + (stop.tv_nsec - start.tv_nsec);
 
     // Unmap VTA register
-    UnmapRegister(vta_fetch_handle, VTA_RANGE);
-    UnmapRegister(vta_load_handle, VTA_RANGE);
-    UnmapRegister(vta_compute_handle, VTA_RANGE);
-    UnmapRegister(vta_store_handle, VTA_RANGE);
+    VTAUnmapRegister(vta_fetch_handle, VTA_RANGE);
+    VTAUnmapRegister(vta_load_handle, VTA_RANGE);
+    VTAUnmapRegister(vta_compute_handle, VTA_RANGE);
+    VTAUnmapRegister(vta_store_handle, VTA_RANGE);
 
     return t_fpga;
 };

From a72a4514f3d80db9daffdd7d73053c1d0272ff13 Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@cs.washington.edu>
Date: Sun, 18 Mar 2018 19:54:08 -0700
Subject: [PATCH 824/949] [REFACTOR] Macro standardization, lint tests (#7)

* code refactoring

* code refactoring

* code refactoring

* code refactoring

* fixing macro

* refactoring, tests, makefile

* style - making sure lint test pass

* prefixed macros with VTA, fixed bugs
---
 vta/Makefile                          |   2 +-
 vta/hardware/vivado/Makefile          |  31 +-
 vta/hardware/vivado/scripts/hls.tcl   |  12 +-
 vta/hardware/vivado/sim/vta_test.cc   |  61 +-
 vta/hardware/vivado/src/vta.cc        | 633 ++++++++---------
 vta/hardware/vivado/src/vta.h         | 117 ++-
 vta/include/vta/driver.h              |  10 +-
 vta/include/vta/hw_spec.h             | 353 ++++-----
 vta/make/config.mk                    |  62 +-
 vta/src/pynq/pynq_driver.cc           |  34 +-
 vta/src/pynq/pynq_driver.h            |  32 +-
 vta/src/runtime.cc                    | 195 ++---
 vta/src/tvm/vta_device_api.cc         |  10 +-
 vta/tests/hardware/common/test_lib.cc | 985 ++++++++++++++------------
 vta/tests/hardware/common/test_lib.h  |  16 +-
 vta/tests/hardware/pynq/metal_test.cc | 245 ++++---
 16 files changed, 1412 insertions(+), 1386 deletions(-)

diff --git a/vta/Makefile b/vta/Makefile
index 6007ed2eeaef..74c23d691c66 100644
--- a/vta/Makefile
+++ b/vta/Makefile
@@ -76,7 +76,7 @@ lib/libvta.$(SHARED_LIBRARY_SUFFIX): $(VTA_LIB_OBJ)
 lint: pylint cpplint
 
 cpplint:
-	python nnvm/dmlc-core/scripts/lint.py vta cpp include src
+	python nnvm/dmlc-core/scripts/lint.py vta cpp include src hardware tests
 
 pylint:
 	pylint python/vta --rcfile=$(ROOTDIR)/tests/lint/pylintrc
diff --git a/vta/hardware/vivado/Makefile b/vta/hardware/vivado/Makefile
index dfcb06316e4d..f3d779ee2a73 100644
--- a/vta/hardware/vivado/Makefile
+++ b/vta/hardware/vivado/Makefile
@@ -1,6 +1,6 @@
 # Directories
 ROOTDIR = $(CURDIR)
-BUILD_DIR = $(ROOTDIR)/build
+BUILD_DIR = $(ROOTDIR)/../../build/hardware/vivado
 SCRIPT_DIR = $(ROOTDIR)/scripts
 SRC_DIR = $(ROOTDIR)/src
 SIM_DIR = $(ROOTDIR)/sim
@@ -27,20 +27,21 @@ include $(config)
 #--------------------
 
 #  Number of threads during compilation
-NUM_THREADS = 8
+VTA_HW_COMP_THREADS = 8
 
 #  Target Frequency
-CLOCK_FREQ = 100
+VTA_HW_COMP_CLOCK_FREQ = 100
 
 #  Timing closure compensation (0 for none, 3 for highest)
-TIMING_CLOSURE_COMP = 0
+VTA_HW_COMP_TIMING_COMP = 0
 
 # Derive clock target period
-TARGET_PER = $(shell echo "$$(( (1000 + $(CLOCK_FREQ) - 1) / $(CLOCK_FREQ) - 0))" )
+TARGET_PER = \
+$(shell echo "$$(( (1000 + $(VTA_HW_COMP_CLOCK_FREQ) - 1) / $(VTA_HW_COMP_CLOCK_FREQ) - $(VTA_HW_COMP_TIMING_COMP)))" )
 
 # Derive config name
 CONF = \
-	$(BATCH)x$(IN_BLOCK)x$(OUT_BLOCK)_$(INP_WIDTH)bx$(WGT_WIDTH)b_$(CLOCK_FREQ)MHz_$(TARGET_PER)ns
+$(VTA_BATCH)x$(VTA_IN_BLOCK)x$(VTA_OUT_BLOCK)_$(VTA_INP_WIDTH)bx$(VTA_WGT_WIDTH)b_$(VTA_HW_COMP_CLOCK_FREQ)MHz_$(TARGET_PER)ns
 IP_BUILD_PATH = $(BUILD_DIR)/hls/$(CONF)
 HW_BUILD_PATH = $(BUILD_DIR)/vivado/$(CONF)
 
@@ -53,23 +54,23 @@ ip:
 	cd $(IP_BUILD_PATH) && \
 		$(VIVADO_HLS) -f $(SCRIPT_DIR)/hls.tcl \
 			-tclargs $(SRC_DIR) $(SIM_DIR) $(TEST_DIR) $(INCLUDE_DIR) $(TARGET_PER) \
-			$(LOG_INP_WIDTH) $(LOG_WGT_WIDTH) $(LOG_ACC_WIDTH) $(LOG_OUT_WIDTH) \
-			$(LOG_BATCH) $(LOG_BLOCK_OUT) $(LOG_BLOCK_IN) \
-			$(LOG_UOP_BUFF_SIZE) $(LOG_INP_BUFF_SIZE) $(LOG_WGT_BUFF_SIZE) \
-			$(LOG_ACC_BUFF_SIZE) $(LOG_OUT_BUFF_SIZE)
+			$(VTA_LOG_INP_WIDTH) $(VTA_LOG_WGT_WIDTH) $(VTA_LOG_ACC_WIDTH) $(VTA_LOG_OUT_WIDTH) \
+			$(VTA_LOG_BATCH) $(VTA_LOG_BLOCK_OUT) $(VTA_LOG_BLOCK_IN) \
+			$(VTA_LOG_UOP_BUFF_SIZE) $(VTA_LOG_INP_BUFF_SIZE) $(VTA_LOG_WGT_BUFF_SIZE) \
+			$(VTA_LOG_ACC_BUFF_SIZE) $(VTA_LOG_OUT_BUFF_SIZE)
 
 bit: ip
 	mkdir -p $(HW_BUILD_PATH)
 	cd $(HW_BUILD_PATH) && \
 		$(VIVADO) -mode tcl -source $(SCRIPT_DIR)/vivado.tcl \
-		-tclargs $(IP_BUILD_PATH) $(NUM_THREADS) $(CLOCK_FREQ) \
-		$(INP_WIDTH) $(WGT_WIDTH) $(OUT_WIDTH) \
-		$(BATCH) $(IN_BLOCK) $(OUT_BLOCK) \
-		$(INP_BUFF_SIZE) $(WGT_BUFF_SIZE) $(OUT_BUFF_SIZE)
+		-tclargs $(IP_BUILD_PATH) $(VTA_HW_COMP_THREADS) $(VTA_HW_COMP_CLOCK_FREQ) \
+		$(VTA_INP_WIDTH) $(VTA_WGT_WIDTH) $(OUT_WIDTH) \
+		$(VTA_BATCH) $(VTA_IN_BLOCK) $(VTA_OUT_BLOCK) \
+		$(VTA_INP_BUFF_SIZE) $(VTA_WGT_BUFF_SIZE) $(VTA_OUT_BUFF_SIZE)
 
 driver: bit
 	cd $(HW_BUILD_PATH) && $(HSI) -mode tcl -source $(SCRIPT_DIR)/hsi.tcl -nojournal -nolog
 	cd $(HW_BUILD_PATH)/bsp && make
 
 clean:
-	rm -rf build
\ No newline at end of file
+	rm -rf $(BUILD_DIR)
\ No newline at end of file
diff --git a/vta/hardware/vivado/scripts/hls.tcl b/vta/hardware/vivado/scripts/hls.tcl
index 220c8f3ba3bf..67ce742bf47a 100644
--- a/vta/hardware/vivado/scripts/hls.tcl
+++ b/vta/hardware/vivado/scripts/hls.tcl
@@ -63,12 +63,12 @@ if { [llength $argv] eq 19 } {
 
 # C define flags to pass to compiler
 set cflags "-I $include_dir -I $src_dir -I $test_dir \
-	-DDEBUG=0 -DLOG_WGT_WIDTH=$wgt_width -DLOG_INP_WIDTH=$inp_width \
-	-DLOG_ACC_WIDTH=$acc_width -DLOG_OUT_WIDTH=$out_width \
-	-DLOG_BATCH=$batch -DLOG_BLOCK_OUT=$block_out -DLOG_BLOCK_IN=$block_in \
-	-DLOG_UOP_BUFF_SIZE=$uop_buff_size -DLOG_INP_BUFF_SIZE=$inp_buff_size \
-	-DLOG_WGT_BUFF_SIZE=$wgt_buff_size -DLOG_ACC_BUFF_SIZE=$acc_buff_size \
-	-DLOG_OUT_BUFF_SIZE=$out_buff_size"
+	-DVTA_DEBUG=0 -DVTA_LOG_WGT_WIDTH=$wgt_width -DVTA_LOG_INP_WIDTH=$inp_width \
+	-DVTA_LOG_ACC_WIDTH=$acc_width -DVTA_LOG_OUT_WIDTH=$out_width \
+	-DVTA_LOG_BATCH=$batch -DVTA_LOG_BLOCK_OUT=$block_out -DVTA_LOG_BLOCK_IN=$block_in \
+	-DVTA_LOG_UOP_BUFF_SIZE=$uop_buff_size -DVTA_LOG_INP_BUFF_SIZE=$inp_buff_size \
+	-DVTA_LOG_WGT_BUFF_SIZE=$wgt_buff_size -DVTA_LOG_ACC_BUFF_SIZE=$acc_buff_size \
+	-DVTA_LOG_OUT_BUFF_SIZE=$out_buff_size"
 
 # Initializes the HLS design and sets HLS pragmas for memory partitioning.
 # This is necessary because of a Vivado restriction that doesn't allow for
diff --git a/vta/hardware/vivado/sim/vta_test.cc b/vta/hardware/vivado/sim/vta_test.cc
index 2031186f31ce..16f37a866464 100644
--- a/vta/hardware/vivado/sim/vta_test.cc
+++ b/vta/hardware/vivado/sim/vta_test.cc
@@ -11,52 +11,49 @@
 #include "../src/vta.h"
 #include "../../../tests/hardware/common/test_lib.h"
 
-int main(void)
-{
-
-#if DEBUG==1
+int main(void) {
+#if DEBUG == 1
     printParameters();
 #endif
 
     // Buffer indexing
-    assert(LOG_ACC_BUFF_DEPTH>=LOG_INP_BUFF_DEPTH);
+    assert(VTA_LOG_ACC_BUFF_DEPTH >= VTA_LOG_INP_BUFF_DEPTH);
     // Micro op bound
-    assert(UOP_GEM_3_1<UOP_WIDTH);
-    assert(UOP_ALU_3_1<UOP_WIDTH);
+    assert(VTA_UOP_GEM_3_1 < VTA_UOP_WIDTH);
+    assert(VTA_UOP_ALU_3_1 < VTA_UOP_WIDTH);
     // Instruction alignment checks
-    assert(INSN_MEM_7_1<INSN_MEM_8_0);
-    assert(INSN_GEM_8_1<INSN_GEM_9_0);
+    assert(VTA_INSN_MEM_7_1 < VTA_INSN_MEM_8_0);
+    assert(VTA_INSN_GEM_8_1 < VTA_INSN_GEM_9_0);
     // Instruction bounds
-    assert(INSN_MEM_E_1<INS_WIDTH);
-    assert(INSN_GEM_E_1<INS_WIDTH);
-    assert(INSN_ALU_F_1<INS_WIDTH);
+    assert(VTA_INSN_MEM_E_1 < VTA_INS_WIDTH);
+    assert(VTA_INSN_GEM_E_1 < VTA_INS_WIDTH);
+    assert(VTA_INSN_ALU_F_1 < VTA_INS_WIDTH);
 
     int status = 0;
 
     // Run ALU test (vector-scalar operators)
-    status |= alu_test(ALU_OPCODE_MIN, true, 16, 128, true);
-    status |= alu_test(ALU_OPCODE_MIN, true, 16, 128, false);
-    status |= alu_test(ALU_OPCODE_MAX, true, 16, 128, true);
-    status |= alu_test(ALU_OPCODE_MAX, true, 16, 128, false);
-    status |= alu_test(ALU_OPCODE_ADD, true, 16, 128, true);
-    status |= alu_test(ALU_OPCODE_ADD, true, 16, 128, false);
-    status |= alu_test(ALU_OPCODE_SHR, true, 16, 128, true);
-    status |= alu_test(ALU_OPCODE_SHR, true, 16, 128, false);
+    status |= alu_test(VTA_ALU_OPCODE_MIN, true, 16, 128, true);
+    status |= alu_test(VTA_ALU_OPCODE_MIN, true, 16, 128, false);
+    status |= alu_test(VTA_ALU_OPCODE_MAX, true, 16, 128, true);
+    status |= alu_test(VTA_ALU_OPCODE_MAX, true, 16, 128, false);
+    status |= alu_test(VTA_ALU_OPCODE_ADD, true, 16, 128, true);
+    status |= alu_test(VTA_ALU_OPCODE_ADD, true, 16, 128, false);
+    status |= alu_test(VTA_ALU_OPCODE_SHR, true, 16, 128, true);
+    status |= alu_test(VTA_ALU_OPCODE_SHR, true, 16, 128, false);
 
     // Run ALU test (vector-vector operators)
-    status |= alu_test(ALU_OPCODE_MIN, false, 16, 128, true);
-    status |= alu_test(ALU_OPCODE_MIN, false, 16, 128, false);
-    status |= alu_test(ALU_OPCODE_MAX, false, 16, 128, true);
-    status |= alu_test(ALU_OPCODE_MAX, false, 16, 128, false);
-    status |= alu_test(ALU_OPCODE_ADD, false, 16, 128, true);
-    status |= alu_test(ALU_OPCODE_ADD, false, 16, 128, false);
+    status |= alu_test(VTA_ALU_OPCODE_MIN, false, 16, 128, true);
+    status |= alu_test(VTA_ALU_OPCODE_MIN, false, 16, 128, false);
+    status |= alu_test(VTA_ALU_OPCODE_MAX, false, 16, 128, true);
+    status |= alu_test(VTA_ALU_OPCODE_MAX, false, 16, 128, false);
+    status |= alu_test(VTA_ALU_OPCODE_ADD, false, 16, 128, true);
+    status |= alu_test(VTA_ALU_OPCODE_ADD, false, 16, 128, false);
 
     // Run blocked GEMM test
-    status |= blocked_gemm_test(256, 256, BLOCK_OUT*4, true, 2);
-    status |= blocked_gemm_test(256, 256, BLOCK_OUT*4, false, 2);
-    status |= blocked_gemm_test(256, 256, BLOCK_OUT*4, true, 1);
-    status |= blocked_gemm_test(256, 256, BLOCK_OUT*4, false, 1);
+    status |= blocked_gemm_test(256, 256, VTA_BLOCK_OUT*4, true, 2);
+    status |= blocked_gemm_test(256, 256, VTA_BLOCK_OUT*4, false, 2);
+    status |= blocked_gemm_test(256, 256, VTA_BLOCK_OUT*4, true, 1);
+    status |= blocked_gemm_test(256, 256, VTA_BLOCK_OUT*4, false, 1);
 
     return status;
-
-}
\ No newline at end of file
+}
diff --git a/vta/hardware/vivado/src/vta.cc b/vta/hardware/vivado/src/vta.cc
index 7a0f9ded25ee..f628b749d23c 100644
--- a/vta/hardware/vivado/src/vta.cc
+++ b/vta/hardware/vivado/src/vta.cc
@@ -10,80 +10,78 @@
 
 #include "./vta.h"
 
-void fetch (
+void fetch(
   uint32_t insn_count,
   volatile insn_T *insns,
-  hls::stream<insn_T> &load_queue,
-  hls::stream<insn_T> &gemm_queue,
-  hls::stream<insn_T> &store_queue) {
-#pragma HLS INTERFACE s_axilite port=insn_count bundle=CONTROL_BUS
-#pragma HLS INTERFACE m_axi port=insns offset=slave bundle=ins_port
-#pragma HLS INTERFACE axis port=load_queue
-#pragma HLS INTERFACE axis port=gemm_queue
-#pragma HLS INTERFACE axis port=store_queue
-#pragma HLS INTERFACE s_axilite port=return bundle=CONTROL_BUS
-
-  INSN_DECODE: for (int pc = 0; pc < insn_count; pc ++) {
-#pragma HLS PIPELINE II=1
+  hls::stream<insn_T> *load_queue,
+  hls::stream<insn_T> *gemm_queue,
+  hls::stream<insn_T> *store_queue) {
+#pragma HLS INTERFACE s_axilite port = insn_count bundle = CONTROL_BUS
+#pragma HLS INTERFACE m_axi port = insns offset = slave bundle = ins_port
+#pragma HLS INTERFACE axis port = load_queue
+#pragma HLS INTERFACE axis port = gemm_queue
+#pragma HLS INTERFACE axis port = store_queue
+#pragma HLS INTERFACE s_axilite port = return bundle = CONTROL_BUS
+
+  INSN_DECODE: for (int pc = 0; pc < insn_count; pc++) {
+#pragma HLS PIPELINE II = 1
     // Read instruction fields
     insn_T insn = insns[pc];
     // Do some partial decoding
-    opcode_T opcode = insn.range(INSN_MEM_0_1, INSN_MEM_0_0);
-    memop_id_T memory_type = insn.range(INSN_MEM_5_1, INSN_MEM_5_0);
+    opcode_T opcode = insn.range(VTA_INSN_MEM_0_1, VTA_INSN_MEM_0_0);
+    memop_id_T memory_type = insn.range(VTA_INSN_MEM_5_1, VTA_INSN_MEM_5_0);
     // Push to appropriate instruction queue
-    if (opcode == OPCODE_STORE) {
-      store_queue.write(insn);
-    } else if (opcode == OPCODE_LOAD &&
-               (memory_type == MEM_ID_INP || memory_type == MEM_ID_WGT)) {
-      load_queue.write(insn);
+    if (opcode == VTA_OPCODE_STORE) {
+      store_queue->write(insn);
+    } else if (opcode == VTA_OPCODE_LOAD &&
+          (memory_type == VTA_MEM_ID_INP || memory_type == VTA_MEM_ID_WGT)) {
+      load_queue->write(insn);
     } else {
-      gemm_queue.write(insn);
+      gemm_queue->write(insn);
     }
   }
-
 }
 
-void load (
+void load(
   volatile inp_vec_T *inputs,
   volatile wgt_vec_T *weights,
-  hls::stream<insn_T> &load_queue,
-  hls::stream<bool> &g2l_dep_queue,
-  hls::stream<bool> &l2g_dep_queue,
-  inp_vec_T inp_mem[INP_BUFF_DEPTH][BATCH],
-  wgt_vec_T wgt_mem[WGT_BUFF_DEPTH][BLOCK_OUT]
+  hls::stream<insn_T> *load_queue,
+  hls::stream<bool> *g2l_dep_queue,
+  hls::stream<bool> *l2g_dep_queue,
+  inp_vec_T inp_mem[VTA_INP_BUFF_DEPTH][VTA_BATCH],
+  wgt_vec_T wgt_mem[VTA_WGT_BUFF_DEPTH][VTA_BLOCK_OUT]
   ) {
-#pragma HLS INTERFACE m_axi port=weights offset=slave bundle=data_port
-#pragma HLS INTERFACE m_axi port=inputs offset=slave bundle=data_port
-#pragma HLS INTERFACE axis port=load_queue
-#pragma HLS INTERFACE axis port=g2l_dep_queue
-#pragma HLS INTERFACE axis port=l2g_dep_queue
-#pragma HLS INTERFACE bram port=wgt_mem
-#pragma HLS INTERFACE bram port=inp_mem
-#pragma HLS INTERFACE s_axilite port=return bundle=CONTROL_BUS
-// #pragma HLS ARRAY_PARTITION variable=inp_mem complete dim=2
+#pragma HLS INTERFACE m_axi port = weights offset = slave bundle = data_port
+#pragma HLS INTERFACE m_axi port = inputs offset = slave bundle = data_port
+#pragma HLS INTERFACE axis port = load_queue
+#pragma HLS INTERFACE axis port = g2l_dep_queue
+#pragma HLS INTERFACE axis port = l2g_dep_queue
+#pragma HLS INTERFACE bram port = wgt_mem
+#pragma HLS INTERFACE bram port = inp_mem
+#pragma HLS INTERFACE s_axilite port = return bundle = CONTROL_BUS
 
   // Pop load instruction
-  insn_T insn = load_queue.read();
+  insn_T insn = load_queue->read();
 
   // Decode instruction
-  bool pop_prev_dependence = insn[INSN_MEM_1];
-  bool pop_next_dependence = insn[INSN_MEM_2];
-  bool push_prev_dependence = insn[INSN_MEM_3];
-  bool push_next_dependence = insn[INSN_MEM_4];
-  memop_id_T memory_type = insn.range(INSN_MEM_5_1, INSN_MEM_5_0);
-  memop_sram_T sram_base = insn.range(INSN_MEM_6_1, INSN_MEM_6_0);
-  memop_dram_T dram_base = insn.range(INSN_MEM_7_1, INSN_MEM_7_0);
-  memop_size_T y_size = insn.range(INSN_MEM_8_1, INSN_MEM_8_0);
-  memop_size_T x_size = insn.range(INSN_MEM_9_1, INSN_MEM_9_0);
-  memop_stride_T x_stride = insn.range(INSN_MEM_A_1, INSN_MEM_A_0);
-  memop_pad_T y_pad_0 = insn.range(INSN_MEM_B_1, INSN_MEM_B_0);
-  memop_pad_T y_pad_1 = insn.range(INSN_MEM_C_1, INSN_MEM_C_0);
-  memop_pad_T x_pad_0 = insn.range(INSN_MEM_D_1, INSN_MEM_D_0);
-  memop_pad_T x_pad_1 = insn.range(INSN_MEM_E_1, INSN_MEM_E_0);
+  bool pop_prev_dependence = insn[VTA_INSN_MEM_1];
+  bool pop_next_dependence = insn[VTA_INSN_MEM_2];
+  bool push_prev_dependence = insn[VTA_INSN_MEM_3];
+  bool push_next_dependence = insn[VTA_INSN_MEM_4];
+  memop_id_T memory_type = insn.range(VTA_INSN_MEM_5_1, VTA_INSN_MEM_5_0);
+  memop_sram_T sram_base = insn.range(VTA_INSN_MEM_6_1, VTA_INSN_MEM_6_0);
+  memop_dram_T dram_base = insn.range(VTA_INSN_MEM_7_1, VTA_INSN_MEM_7_0);
+  memop_size_T y_size = insn.range(VTA_INSN_MEM_8_1, VTA_INSN_MEM_8_0);
+  memop_size_T x_size = insn.range(VTA_INSN_MEM_9_1, VTA_INSN_MEM_9_0);
+  memop_stride_T x_stride = insn.range(VTA_INSN_MEM_A_1, VTA_INSN_MEM_A_0);
+  memop_pad_T y_pad_0 = insn.range(VTA_INSN_MEM_B_1, VTA_INSN_MEM_B_0);
+  memop_pad_T y_pad_1 = insn.range(VTA_INSN_MEM_C_1, VTA_INSN_MEM_C_0);
+  memop_pad_T x_pad_0 = insn.range(VTA_INSN_MEM_D_1, VTA_INSN_MEM_D_0);
+  memop_pad_T x_pad_1 = insn.range(VTA_INSN_MEM_E_1, VTA_INSN_MEM_E_0);
 
   // Pop dependence token if instructed
   if (pop_next_dependence) {
-    g2l_dep_queue.read();
+    g2l_dep_queue->read();
   }
 
   // Initialize indices
@@ -94,29 +92,26 @@ void load (
   memop_size_T y_size_total = y_pad_0 + y_size + y_pad_1;
   memop_size_T x_size_total = x_pad_0 + x_size + x_pad_1;
   memop_sram_T y_offset = x_size_total * y_pad_0;
-#pragma HLS RESOURCE variable=y_offset core=Mul_LUT
+// Force this computation to be done with LUTs to avoid using too many DSPs
+#pragma HLS RESOURCE variable = y_offset core = Mul_LUT
 
   // Skip padding along y dimension
   sram_idx += y_offset;
 
   // Perform data transfer from DRAM
-  for (int y = 0; y < y_size; y ++) {
+  for (int y = 0; y < y_size; y++) {
 #pragma HLS PIPELINE rewind
     // Skip padding along x dimension
     sram_idx += x_pad_0;
     // Perform data transfer
-    if (memory_type == MEM_ID_INP) {
-      memcpy(
-        &inp_mem[sram_idx][0],
-        (const inp_vec_T*) &inputs[dram_idx * BATCH],
-        x_size * INP_ELEM_BYTES
-      );
+    if (memory_type == VTA_MEM_ID_INP) {
+      memcpy(&inp_mem[sram_idx][0],
+             (const inp_vec_T*) &inputs[dram_idx * VTA_BATCH],
+             x_size * VTA_INP_ELEM_BYTES);
     } else {
-      memcpy(
-        &wgt_mem[sram_idx][0],
-        (const wgt_vec_T*) &weights[dram_idx * BLOCK_OUT],
-        x_size * WGT_ELEM_BYTES
-      );
+      memcpy(&wgt_mem[sram_idx][0],
+             (const wgt_vec_T*) &weights[dram_idx * VTA_BLOCK_OUT],
+             x_size * VTA_WGT_ELEM_BYTES);
     }
     sram_idx += x_size;
     dram_idx += x_stride;
@@ -127,136 +122,130 @@ void load (
   // Reset SRAM index
   sram_idx = sram_base;
   // Pad x/y edges with zeros
-  for (int y = 0; y < y_size_total; y ++) {
+  for (int y = 0; y < y_size_total; y++) {
     if (y < y_pad_0 || y >= y_pad_0 + y_size) {
-      for (int x = 0; x < x_size_total; x ++) {
-#pragma HLS PIPELINE II=1 rewind
-        if (memory_type == MEM_ID_INP) {
-          for (int i = 0; i < BATCH; i ++) {
+      for (int x = 0; x < x_size_total; x++) {
+#pragma HLS PIPELINE II = 1 rewind
+        if (memory_type == VTA_MEM_ID_INP) {
+          for (int i = 0; i < VTA_BATCH; i++) {
             inp_mem[sram_idx][i] = 0;
           }
         } else {
-          for (int i = 0; i < BLOCK_OUT; i ++) {
+          for (int i = 0; i < VTA_BLOCK_OUT; i++) {
             wgt_mem[sram_idx][i] = 0;
           }
         }
-        sram_idx ++;
+        sram_idx++;
       }
     } else {
-      for (int x = 0; x < x_pad_0; x ++) {
-#pragma HLS PIPELINE II=1 rewind
-        if (memory_type == MEM_ID_INP) {
-          for (int i = 0; i < BATCH; i ++) {
+      for (int x = 0; x < x_pad_0; x++) {
+#pragma HLS PIPELINE II = 1 rewind
+        if (memory_type == VTA_MEM_ID_INP) {
+          for (int i = 0; i < VTA_BATCH; i++) {
             inp_mem[sram_idx][i] = 0;
           }
         } else {
-          for (int i = 0; i < BLOCK_OUT; i ++) {
+          for (int i = 0; i < VTA_BLOCK_OUT; i++) {
             wgt_mem[sram_idx][i] = 0;
           }
         }
-        sram_idx ++;
+        sram_idx++;
       }
       sram_idx += x_size;
-      for (int x = 0; x < x_pad_1; x ++) {
-#pragma HLS PIPELINE II=1 rewind
-        if (memory_type == MEM_ID_INP) {
-          for (int i = 0; i < BATCH; i ++) {
+      for (int x = 0; x < x_pad_1; x++) {
+#pragma HLS PIPELINE II = 1 rewind
+        if (memory_type == VTA_MEM_ID_INP) {
+          for (int i = 0; i < VTA_BATCH; i++) {
             inp_mem[sram_idx][i] = 0;
           }
         } else {
-          for (int i = 0; i < BLOCK_OUT; i ++) {
+          for (int i = 0; i < VTA_BLOCK_OUT; i++) {
             wgt_mem[sram_idx][i] = 0;
           }
         }
-        sram_idx ++;
+        sram_idx++;
       }
-
     }
   }
 
   // Push dependence token if instructed
   if (push_next_dependence) {
-    l2g_dep_queue.write(1);
+    l2g_dep_queue->write(1);
   }
 }
 
-void compute (
-  volatile uint32_t &done,
+void compute(
+  volatile uint32_t *done,
   volatile uop_T *uops,
   volatile acc_vec_T *biases,
-  hls::stream<insn_T> &gemm_queue,
-  hls::stream<bool> &l2g_dep_queue,
-  hls::stream<bool> &s2g_dep_queue,
-  hls::stream<bool> &g2l_dep_queue,
-  hls::stream<bool> &g2s_dep_queue,
-  out_vec_T inp_mem[INP_BUFF_DEPTH][BATCH],
-  wgt_vec_T wgt_mem[WGT_BUFF_DEPTH][BLOCK_OUT],
-  out_vec_T out_mem[ACC_BUFF_DEPTH][BATCH]
+  hls::stream<insn_T> *gemm_queue,
+  hls::stream<bool> *l2g_dep_queue,
+  hls::stream<bool> *s2g_dep_queue,
+  hls::stream<bool> *g2l_dep_queue,
+  hls::stream<bool> *g2s_dep_queue,
+  out_vec_T inp_mem[VTA_INP_BUFF_DEPTH][VTA_BATCH],
+  wgt_vec_T wgt_mem[VTA_WGT_BUFF_DEPTH][VTA_BLOCK_OUT],
+  out_vec_T out_mem[VTA_ACC_BUFF_DEPTH][VTA_BATCH]
   ) {
-#pragma HLS INTERFACE s_axilite port=done bundle=CONTROL_BUS
-#pragma HLS INTERFACE m_axi port=uops offset=slave bundle=uop_port
-#pragma HLS INTERFACE m_axi port=biases offset=slave bundle=data_port
-#pragma HLS INTERFACE axis port=gemm_queue
-#pragma HLS INTERFACE axis port=l2g_dep_queue
-#pragma HLS INTERFACE axis port=s2g_dep_queue
-#pragma HLS INTERFACE axis port=g2l_dep_queue
-#pragma HLS INTERFACE axis port=g2s_dep_queue
-#pragma HLS INTERFACE bram port=inp_mem
-#pragma HLS INTERFACE bram port=wgt_mem
-#pragma HLS INTERFACE bram port=out_mem
-#pragma HLS INTERFACE s_axilite port=return bundle=CONTROL_BUS
-// #pragma HLS ARRAY_PARTITION variable=inp_mem complete dim=2
-// #pragma HLS ARRAY_PARTITION variable=out_mem complete dim=2
+#pragma HLS INTERFACE s_axilite port = done bundle = CONTROL_BUS
+#pragma HLS INTERFACE m_axi port = uops offset = slave bundle = uop_port
+#pragma HLS INTERFACE m_axi port = biases offset = slave bundle = data_port
+#pragma HLS INTERFACE axis port = gemm_queue
+#pragma HLS INTERFACE axis port = l2g_dep_queue
+#pragma HLS INTERFACE axis port = s2g_dep_queue
+#pragma HLS INTERFACE axis port = g2l_dep_queue
+#pragma HLS INTERFACE axis port = g2s_dep_queue
+#pragma HLS INTERFACE bram port = inp_mem
+#pragma HLS INTERFACE bram port = wgt_mem
+#pragma HLS INTERFACE bram port = out_mem
+#pragma HLS INTERFACE s_axilite port = return bundle = CONTROL_BUS
 // This is necessary connect the SRAM to the load module
-#pragma HLS RESOURCE variable=wgt_mem core=RAM_1P
+#pragma HLS RESOURCE variable = wgt_mem core = RAM_1P
 
   // Micro-op storage
-  static uop_T uop_mem[UOP_BUFF_DEPTH];
+  static uop_T uop_mem[VTA_UOP_BUFF_DEPTH];
 
   // Accumulator storage
-  static acc_vec_T acc_mem[ACC_BUFF_DEPTH][BATCH];
-#pragma HLS ARRAY_PARTITION variable=acc_mem complete dim=2
+  static acc_vec_T acc_mem[VTA_ACC_BUFF_DEPTH][VTA_BATCH];
+#pragma HLS ARRAY_PARTITION variable = acc_mem complete dim = 2
 
   // Pop GEMM instruction
-  insn_T insn = gemm_queue.read();
+  insn_T insn = gemm_queue->read();
 
   // Decode
-  opcode_T opcode = insn.range(INSN_MEM_0_1, INSN_MEM_0_0);
-  bool pop_prev_dependence = insn[INSN_MEM_1];
-  bool pop_next_dependence = insn[INSN_MEM_2];
-  bool push_prev_dependence = insn[INSN_MEM_3];
-  bool push_next_dependence = insn[INSN_MEM_4];
+  opcode_T opcode = insn.range(VTA_INSN_MEM_0_1, VTA_INSN_MEM_0_0);
+  bool pop_prev_dependence = insn[VTA_INSN_MEM_1];
+  bool pop_next_dependence = insn[VTA_INSN_MEM_2];
+  bool push_prev_dependence = insn[VTA_INSN_MEM_3];
+  bool push_next_dependence = insn[VTA_INSN_MEM_4];
 
   // Pop dependence token if instructed
   if (pop_prev_dependence) {
-    l2g_dep_queue.read();
+    l2g_dep_queue->read();
   }
   if (pop_next_dependence) {
-    s2g_dep_queue.read();
+    s2g_dep_queue->read();
   }
 
   // Perform action based on opcode
-  if (opcode == OPCODE_FINISH) {
-
+  if (opcode == VTA_OPCODE_FINISH) {
     // Set done flag if we reach a FINISH instruction
-    done = 1;
-
-  } else if (opcode == OPCODE_LOAD || opcode == OPCODE_STORE) {
-
+    *done = 1;
+  } else if (opcode == VTA_OPCODE_LOAD || opcode == VTA_OPCODE_STORE) {
     // Set done value
-    done = 0;
+    *done = 0;
 
     // Decode instruction
-    memop_id_T memory_type = insn.range(INSN_MEM_5_1, INSN_MEM_5_0);
-    memop_sram_T sram_base = insn.range(INSN_MEM_6_1, INSN_MEM_6_0);
-    memop_dram_T dram_base = insn.range(INSN_MEM_7_1, INSN_MEM_7_0);
-    memop_size_T y_size = insn.range(INSN_MEM_8_1, INSN_MEM_8_0);
-    memop_size_T x_size = insn.range(INSN_MEM_9_1, INSN_MEM_9_0);
-    memop_stride_T x_stride = insn.range(INSN_MEM_A_1, INSN_MEM_A_0);
-    memop_pad_T y_pad_0 = insn.range(INSN_MEM_B_1, INSN_MEM_B_0);
-    memop_pad_T y_pad_1 = insn.range(INSN_MEM_C_1, INSN_MEM_C_0);
-    memop_pad_T x_pad_0 = insn.range(INSN_MEM_D_1, INSN_MEM_D_0);
-    memop_pad_T x_pad_1 = insn.range(INSN_MEM_E_1, INSN_MEM_E_0);
+    memop_id_T memory_type = insn.range(VTA_INSN_MEM_5_1, VTA_INSN_MEM_5_0);
+    memop_sram_T sram_base = insn.range(VTA_INSN_MEM_6_1, VTA_INSN_MEM_6_0);
+    memop_dram_T dram_base = insn.range(VTA_INSN_MEM_7_1, VTA_INSN_MEM_7_0);
+    memop_size_T y_size = insn.range(VTA_INSN_MEM_8_1, VTA_INSN_MEM_8_0);
+    memop_size_T x_size = insn.range(VTA_INSN_MEM_9_1, VTA_INSN_MEM_9_0);
+    memop_stride_T x_stride = insn.range(VTA_INSN_MEM_A_1, VTA_INSN_MEM_A_0);
+    memop_pad_T y_pad_0 = insn.range(VTA_INSN_MEM_B_1, VTA_INSN_MEM_B_0);
+    memop_pad_T y_pad_1 = insn.range(VTA_INSN_MEM_C_1, VTA_INSN_MEM_C_0);
+    memop_pad_T x_pad_0 = insn.range(VTA_INSN_MEM_D_1, VTA_INSN_MEM_D_0);
+    memop_pad_T x_pad_1 = insn.range(VTA_INSN_MEM_E_1, VTA_INSN_MEM_E_0);
 
     // Initialize indices
     memop_sram_T sram_idx = sram_base;
@@ -266,220 +255,202 @@ void compute (
     memop_size_T y_size_total = y_pad_0 + y_size + y_pad_1;
     memop_size_T x_size_total = x_pad_0 + x_size + x_pad_1;
     memop_sram_T y_offset = x_size_total * y_pad_0;
-#pragma HLS RESOURCE variable=y_offset core=Mul_LUT
+// Force this computation to be done with LUTs to avoid using too many DSPs
+#pragma HLS RESOURCE variable = y_offset core = Mul_LUT
 
-    if (memory_type == MEM_ID_UOP) {
+    if (memory_type == VTA_MEM_ID_UOP) {
       // Perform data transfer
-      memcpy(
-        &uop_mem[sram_base],
-        (const uop_T*) &uops[dram_base],
-        x_size * sizeof(uop_T)
-      );
+      memcpy(&uop_mem[sram_base],
+             (const uop_T*) &uops[dram_base],
+             x_size * sizeof(uop_T));
     } else {
       // Skip vertical padding
       sram_idx += y_offset;
       // Perform data transfer from DRAM
-      for (int y = 0; y < y_size; y ++) {
+      for (int y = 0; y < y_size; y++) {
 #pragma HLS PIPELINE rewind
         // Skip padding along x dimension
         sram_idx += x_pad_0;
         // Perform data transfer
-        memcpy(
-            &acc_mem[sram_idx][0],
-            (const acc_vec_T*) &biases[dram_idx * BATCH],
-            x_size*ACC_ELEM_BYTES
-        );
+        memcpy(&acc_mem[sram_idx][0],
+               (const acc_vec_T*) &biases[dram_idx * VTA_BATCH],
+               x_size*VTA_ACC_ELEM_BYTES);
         sram_idx += x_size;
         dram_idx += x_stride;
         // Skip padding along x dimension
         sram_idx += x_pad_1;
       }
     }
-
-  } else if (opcode == OPCODE_GEMM || opcode == OPCODE_ALU) {
-
+  } else if (opcode == VTA_OPCODE_GEMM || opcode == VTA_OPCODE_ALU) {
     // Set done value
-    done = 0;
+    *done = 0;
 
     // Decode
-    uop_idx_T uop_bgn = insn.range(INSN_GEM_5_1, INSN_GEM_5_0);
-    uop_idx_T uop_end = insn.range(INSN_GEM_6_1, INSN_GEM_6_0);
-    loop_T iter_out  = insn.range(INSN_GEM_7_1, INSN_GEM_7_0);
-    loop_T iter_in  = insn.range(INSN_GEM_8_1, INSN_GEM_8_0);
-    acc_idx_T dst_factor_out = insn.range(INSN_GEM_9_1, INSN_GEM_9_0);
-    acc_idx_T dst_factor_in = insn.range(INSN_GEM_A_1, INSN_GEM_A_0);
-    inp_idx_T src_factor_out = insn.range(INSN_GEM_B_1, INSN_GEM_B_0);
-    inp_idx_T src_factor_in = insn.range(INSN_GEM_C_1, INSN_GEM_C_0);
+    uop_idx_T uop_bgn = insn.range(VTA_INSN_GEM_5_1, VTA_INSN_GEM_5_0);
+    uop_idx_T uop_end = insn.range(VTA_INSN_GEM_6_1, VTA_INSN_GEM_6_0);
+    loop_T iter_out  = insn.range(VTA_INSN_GEM_7_1, VTA_INSN_GEM_7_0);
+    loop_T iter_in  = insn.range(VTA_INSN_GEM_8_1, VTA_INSN_GEM_8_0);
+    acc_idx_T dst_factor_out = insn.range(VTA_INSN_GEM_9_1, VTA_INSN_GEM_9_0);
+    acc_idx_T dst_factor_in = insn.range(VTA_INSN_GEM_A_1, VTA_INSN_GEM_A_0);
+    inp_idx_T src_factor_out = insn.range(VTA_INSN_GEM_B_1, VTA_INSN_GEM_B_0);
+    inp_idx_T src_factor_in = insn.range(VTA_INSN_GEM_C_1, VTA_INSN_GEM_C_0);
 
     // GEMM-specific fields
-    wgt_idx_T wgt_factor_out = insn.range(INSN_GEM_D_1, INSN_GEM_D_0);
-    wgt_idx_T wgt_factor_in = insn.range(INSN_GEM_E_1, INSN_GEM_E_0);
+    wgt_idx_T wgt_factor_out = insn.range(VTA_INSN_GEM_D_1, VTA_INSN_GEM_D_0);
+    wgt_idx_T wgt_factor_in = insn.range(VTA_INSN_GEM_E_1, VTA_INSN_GEM_E_0);
 
     // ALU-specific field
-    aluop_opcode_T alu_opcode = insn.range(INSN_ALU_D_1, INSN_ALU_D_0);
-    bool use_imm = insn[INSN_ALU_E];
-    aluop_imm_T imm = insn.range(INSN_ALU_F_1, INSN_ALU_F_0);
-
+    aluop_opcode_T alu_opcode = insn.range(VTA_INSN_ALU_D_1, VTA_INSN_ALU_D_0);
+    bool use_imm = insn[VTA_INSN_ALU_E];
+    aluop_imm_T imm = insn.range(VTA_INSN_ALU_F_1, VTA_INSN_ALU_F_0);
     acc_idx_T dst_offset_out = 0;
     inp_idx_T src_offset_out = 0;
     wgt_idx_T wgt_offset_out = 0;
 
     // Outer Loop
-    EXE_OUT_LOOP: for (int it_out = 0; it_out < iter_out; it_out ++) {
-#pragma HLS DEPENDENCE variable=acc_mem inter false
-
+    EXE_OUT_LOOP: for (int it_out = 0; it_out < iter_out; it_out++) {
+#pragma HLS DEPENDENCE variable = acc_mem inter false
       acc_idx_T dst_offset_in = dst_offset_out;
       inp_idx_T src_offset_in = src_offset_out;
       wgt_idx_T wgt_offset_in = wgt_offset_out;
 
       // Inner Loop
-      EXE_IN_LOOP: for (int it_in = 0; it_in < iter_in; it_in ++) {
-
+      EXE_IN_LOOP: for (int it_in = 0; it_in < iter_in; it_in++) {
         // Perform appropriate computation based on opcode
-        if (opcode == OPCODE_GEMM) {
-
+        if (opcode == VTA_OPCODE_GEMM) {
           // Iterate over micro op
-          READ_GEMM_UOP: for (int upc = uop_bgn; upc < uop_end; upc ++) {
-#pragma HLS PIPELINE II=1 rewind
+          READ_GEMM_UOP: for (int upc = uop_bgn; upc < uop_end; upc++) {
+#pragma HLS PIPELINE II = 1 rewind
 
             // Read micro-op fields
             uop_T uop = uop_mem[upc];
 
             // Decode indices
-            bool reset_out = uop[UOP_GEM_0];
+            bool reset_out = uop[VTA_UOP_GEM_0];
             acc_idx_T dst_idx =
-              uop.range(UOP_GEM_1_1, UOP_GEM_1_0) + dst_offset_in;
+                uop.range(VTA_UOP_GEM_1_1, VTA_UOP_GEM_1_0) + dst_offset_in;
             acc_idx_T src_idx =
-              uop.range(UOP_GEM_2_1, UOP_GEM_2_0) + src_offset_in;
+                uop.range(VTA_UOP_GEM_2_1, VTA_UOP_GEM_2_0) + src_offset_in;
             wgt_idx_T wgt_idx =
-              uop.range(UOP_GEM_3_1, UOP_GEM_3_0) + wgt_offset_in;
+                uop.range(VTA_UOP_GEM_3_1, VTA_UOP_GEM_3_0) + wgt_offset_in;
 
             // Read weight matrix
-            wgt_vec_T w_matrix[BLOCK_OUT];
-            for (int i = 0; i < BLOCK_OUT; i ++) {
+            wgt_vec_T w_matrix[VTA_BLOCK_OUT];
+            for (int i = 0; i < VTA_BLOCK_OUT; i++) {
               w_matrix[i] = wgt_mem[wgt_idx][i];
             }
             // Read input matrix and accum matrix
-            acc_vec_T o_matrix[BATCH];
-            out_vec_T i_matrix[BATCH];
-            for (int i = 0; i < BATCH; i ++) {
+            acc_vec_T o_matrix[VTA_BATCH];
+            out_vec_T i_matrix[VTA_BATCH];
+            for (int i = 0; i < VTA_BATCH; i++) {
               o_matrix[i] = acc_mem[dst_idx][i];
               i_matrix[i] = inp_mem[src_idx][i];
             }
             // Result matrices
-            acc_vec_T acc_mem_val[BATCH];
-            out_vec_T st_buf_val[BATCH];
+            acc_vec_T acc_mem_val[VTA_BATCH];
+            out_vec_T st_buf_val[VTA_BATCH];
 
             // Inner GEMM loop
-            for (int i = 0; i < BATCH; i ++) {
-              for (int b = 0; b < BLOCK_OUT; b ++) {
+            for (int i = 0; i < VTA_BATCH; i++) {
+              for (int b = 0; b < VTA_BLOCK_OUT; b++) {
                 // Initialize the accumulator values
                 acc_T accum =
-                  o_matrix[i].range((b + 1) * ACC_WIDTH - 1, b * ACC_WIDTH);
+                  o_matrix[i].range((b + 1) * VTA_ACC_WIDTH - 1, b * VTA_ACC_WIDTH);
                 // Dot product sum
                 sum_T tmp = 0;
                 // Inner matrix multiplication loop (input channel/feature)
-                for (int k=0; k<BLOCK_IN; k++) {
+                for (int k = 0; k < VTA_BLOCK_IN; k++) {
                   wgt_T w_elem =
-                    w_matrix[b].range((k + 1) * WGT_WIDTH - 1, k * WGT_WIDTH);
+                      w_matrix[b].range((k + 1) * VTA_WGT_WIDTH - 1, k * VTA_WGT_WIDTH);
                   inp_T i_elem =
-                    i_matrix[i].range((k + 1) * INP_WIDTH - 1, k * INP_WIDTH);
+                      i_matrix[i].range((k + 1) * VTA_INP_WIDTH - 1, k * VTA_INP_WIDTH);
                   mul_T prod = i_elem * w_elem;
                   tmp += (sum_T) prod;
                 }
                 // Update summation
                 accum += (acc_T) tmp;
                 // Update result vector
-                acc_mem_val[i].range((b + 1) * ACC_WIDTH - 1, b * ACC_WIDTH) =
-                  reset_out ? (acc_T) 0 : accum;
-                st_buf_val[i].range((b + 1) * INP_WIDTH - 1, b * INP_WIDTH) =
-                  (inp_T) accum.range(INP_WIDTH - 1, 0);
+                acc_mem_val[i].range((b + 1) * VTA_ACC_WIDTH - 1, b * VTA_ACC_WIDTH) =
+                    reset_out ? (acc_T) 0 : accum;
+                st_buf_val[i].range((b + 1) * VTA_OUT_WIDTH - 1, b * VTA_OUT_WIDTH) =
+                    (inp_T) accum.range(VTA_OUT_WIDTH - 1, 0);
               }
               // Write to buffers
               acc_mem[dst_idx][i] = acc_mem_val[i];
               out_mem[dst_idx][i] = st_buf_val[i];
             }
           }
-
-        } else if (opcode == OPCODE_ALU) {
-
+        } else if (opcode == VTA_OPCODE_ALU) {
           // Iterate over micro op
-          READ_ALU_UOP: for (int upc = uop_bgn; upc < uop_end; upc ++) {
-
+          READ_ALU_UOP: for (int upc = uop_bgn; upc < uop_end; upc++) {
             // Read micro-op fields
             uop_T uop = uop_mem[upc];
 
             // Decode
-            bool reset_out = uop[UOP_ALU_0];
+            bool reset_out = uop[VTA_UOP_ALU_0];
             acc_idx_T dst_idx =
-              uop.range(UOP_ALU_1_1, UOP_ALU_1_0) + dst_offset_in;
+                uop.range(VTA_UOP_ALU_1_1, VTA_UOP_ALU_1_0) + dst_offset_in;
             acc_idx_T src_idx =
-              uop.range(UOP_ALU_2_1, UOP_ALU_2_0) + src_offset_in;
+                uop.range(VTA_UOP_ALU_2_1, VTA_UOP_ALU_2_0) + src_offset_in;
 
             // Read input matrix and accum matrix
-            acc_vec_T dst_matrix[BATCH];
-            acc_vec_T src_matrix[BATCH];
-            for (int i = 0; i < BATCH; i ++) {
+            acc_vec_T dst_matrix[VTA_BATCH];
+            acc_vec_T src_matrix[VTA_BATCH];
+            for (int i = 0; i < VTA_BATCH; i++) {
 #pragma HLS UNROLL complete
               dst_matrix[i] = acc_mem[dst_idx][i];
               src_matrix[i] = acc_mem[src_idx][i];
             }
 
             // Result matrices
-            acc_vec_T cmp_res[BATCH];
-            acc_vec_T add_res[BATCH];
-            acc_vec_T shr_res[BATCH];
-            out_vec_T short_cmp_res[BATCH];
-            out_vec_T short_add_res[BATCH];
-            out_vec_T short_shr_res[BATCH];
+            acc_vec_T cmp_res[VTA_BATCH];
+            acc_vec_T add_res[VTA_BATCH];
+            acc_vec_T shr_res[VTA_BATCH];
+            out_vec_T short_cmp_res[VTA_BATCH];
+            out_vec_T short_add_res[VTA_BATCH];
+            out_vec_T short_shr_res[VTA_BATCH];
 
             // Perform ALU op over matrix elements
-            for (int i = 0; i < BATCH; i ++) {
-#pragma HLS PIPELINE II=1 rewind
+            for (int i = 0; i < VTA_BATCH; i++) {
+#pragma HLS PIPELINE II = 1 rewind
               // Results vector
               acc_vec_T res_vec = 0;
-              for (int b = 0; b < BLOCK_OUT; b ++) {
+              for (int b = 0; b < VTA_BLOCK_OUT; b++) {
                 // Read in operands
-                acc_T src_0 =
-                  dst_matrix[i].range((b + 1) * ACC_WIDTH - 1, b * ACC_WIDTH);
-                acc_T src_1 =
-                  use_imm ?
+                acc_T src_0 = dst_matrix[i].range((b + 1) * VTA_ACC_WIDTH - 1, b * VTA_ACC_WIDTH);
+                acc_T src_1 = use_imm ?
                     (acc_T) imm :
-                    src_matrix[i].range((b + 1) * ACC_WIDTH - 1, b * ACC_WIDTH);
+                    src_matrix[i].range((b + 1) * VTA_ACC_WIDTH - 1, b * VTA_ACC_WIDTH);
                 // Compute Min/Max
-                acc_T mix_val =
-                  src_0 < src_1 ?
-                    (alu_opcode == ALU_OPCODE_MIN ? src_0 : src_1) :
-                    (alu_opcode == ALU_OPCODE_MIN ? src_1 : src_0);
-                cmp_res[i].range((b + 1) * ACC_WIDTH - 1, b * ACC_WIDTH) =
-                  mix_val;
-                short_cmp_res[i].range((b + 1) * INP_WIDTH - 1, b * INP_WIDTH) =
-                  (inp_T) mix_val.range(INP_WIDTH - 1, 0);
+                acc_T mix_val = src_0 < src_1 ?
+                    (alu_opcode == VTA_ALU_OPCODE_MIN ? src_0 : src_1) :
+                    (alu_opcode == VTA_ALU_OPCODE_MIN ? src_1 : src_0);
+                cmp_res[i].range((b + 1) * VTA_ACC_WIDTH - 1, b * VTA_ACC_WIDTH) = mix_val;
+                short_cmp_res[i].range((b + 1) * VTA_OUT_WIDTH - 1, b * VTA_OUT_WIDTH) =
+                    (inp_T) mix_val.range(VTA_OUT_WIDTH - 1, 0);
                 // Compute Sum
                 acc_T add_val =
-                  src_0.range(ACC_WIDTH - 1, 0) + src_1.range(ACC_WIDTH - 1, 0);
-                add_res[i].range((b + 1) * ACC_WIDTH - 1, b * ACC_WIDTH) =
-                  add_val;
-                short_add_res[i].range((b + 1) * INP_WIDTH - 1, b * INP_WIDTH) =
-                  (inp_T) add_val.range(INP_WIDTH - 1, 0);
+                    src_0.range(VTA_ACC_WIDTH - 1, 0) + src_1.range(VTA_ACC_WIDTH - 1, 0);
+                add_res[i].range((b + 1) * VTA_ACC_WIDTH - 1, b * VTA_ACC_WIDTH) = add_val;
+                short_add_res[i].range((b + 1) * VTA_OUT_WIDTH - 1, b * VTA_OUT_WIDTH) =
+                    (inp_T) add_val.range(VTA_OUT_WIDTH - 1, 0);
                 // Compute Shift
                 acc_T shr_val =
-                  src_0 >> (aluop_sh_imm_T) src_1.range(LOG_ACC_WIDTH - 1, 0);
-                shr_res[i].range((b + 1) * ACC_WIDTH - 1, b * ACC_WIDTH) =
-                  shr_val;
-                short_shr_res[i].range((b + 1) * INP_WIDTH - 1, b * INP_WIDTH) =
-                  (inp_T) shr_val.range(INP_WIDTH-1, 0);
+                    src_0 >> (aluop_sh_imm_T) src_1.range(VTA_LOG_ACC_WIDTH - 1, 0);
+                shr_res[i].range((b + 1) * VTA_ACC_WIDTH - 1, b * VTA_ACC_WIDTH) = shr_val;
+                short_shr_res[i].range((b + 1) * VTA_OUT_WIDTH - 1, b * VTA_OUT_WIDTH) =
+                    (inp_T) shr_val.range(VTA_OUT_WIDTH-1, 0);
               }
 
               // Store to accum memory/store buffer
-              if (alu_opcode == ALU_OPCODE_MIN ||
-                  alu_opcode == ALU_OPCODE_MAX) {
+              if (alu_opcode == VTA_ALU_OPCODE_MIN ||
+                  alu_opcode == VTA_ALU_OPCODE_MAX) {
                 acc_mem[dst_idx][i] = cmp_res[i];
                 out_mem[dst_idx][i] = short_cmp_res[i];
-              } else if (alu_opcode==ALU_OPCODE_ADD) {
+              } else if (alu_opcode == VTA_ALU_OPCODE_ADD) {
                 acc_mem[dst_idx][i] = add_res[i];
                 out_mem[dst_idx][i] = short_add_res[i];
-              } else if (alu_opcode==ALU_OPCODE_SHR) {
+              } else if (alu_opcode == VTA_ALU_OPCODE_SHR) {
                 acc_mem[dst_idx][i] = shr_res[i];
                 out_mem[dst_idx][i] = short_shr_res[i];
               }
@@ -502,51 +473,49 @@ void compute (
 
   // Push dependence token if instructed
   if (push_prev_dependence) {
-    g2l_dep_queue.write(1);
+    g2l_dep_queue->write(1);
   }
   if (push_next_dependence) {
-    g2s_dep_queue.write(1);
+    g2s_dep_queue->write(1);
   }
-
 }
 
-void store (
+void store(
   volatile out_vec_T *outputs,
-  hls::stream<insn_T> &store_queue,
-  hls::stream<bool> &g2s_dep_queue,
-  hls::stream<bool> &s2g_dep_queue,
-  out_vec_T out_mem[ACC_BUFF_DEPTH][BATCH]
+  hls::stream<insn_T> *store_queue,
+  hls::stream<bool> *g2s_dep_queue,
+  hls::stream<bool> *s2g_dep_queue,
+  out_vec_T out_mem[VTA_ACC_BUFF_DEPTH][VTA_BATCH]
   ) {
-#pragma HLS INTERFACE m_axi port=outputs offset=slave bundle=data_port
-#pragma HLS INTERFACE axis port=store_queue
-#pragma HLS INTERFACE axis port=g2s_dep_queue
-#pragma HLS INTERFACE axis port=s2g_dep_queue
-#pragma HLS INTERFACE bram port=out_mem
-#pragma HLS INTERFACE s_axilite port=return bundle=CONTROL_BUS
-// #pragma HLS ARRAY_PARTITION variable=out_mem complete dim=2
+#pragma HLS INTERFACE m_axi port = outputs offset = slave bundle = data_port
+#pragma HLS INTERFACE axis port = store_queue
+#pragma HLS INTERFACE axis port = g2s_dep_queue
+#pragma HLS INTERFACE axis port = s2g_dep_queue
+#pragma HLS INTERFACE bram port = out_mem
+#pragma HLS INTERFACE s_axilite port = return bundle = CONTROL_BUS
 
   // Load buffer
-  insn_T insn = store_queue.read();
+  insn_T insn = store_queue->read();
 
   // Decode
-  bool pop_prev_dependence = insn[INSN_MEM_1];
-  bool pop_next_dependence = insn[INSN_MEM_2];
-  bool push_prev_dependence = insn[INSN_MEM_3];
-  bool push_next_dependence = insn[INSN_MEM_4];
-  memop_id_T memory_type = insn.range(INSN_MEM_5_1, INSN_MEM_5_0);
-  memop_sram_T sram_base = insn.range(INSN_MEM_6_1, INSN_MEM_6_0);
-  memop_dram_T dram_base = insn.range(INSN_MEM_7_1, INSN_MEM_7_0);
-  memop_size_T y_size = insn.range(INSN_MEM_8_1, INSN_MEM_8_0);
-  memop_size_T x_size = insn.range(INSN_MEM_9_1, INSN_MEM_9_0);
-  memop_stride_T x_stride = insn.range(INSN_MEM_A_1, INSN_MEM_A_0);
-  memop_pad_T y_pad_0 = insn.range(INSN_MEM_B_1, INSN_MEM_B_0);
-  memop_pad_T y_pad_1 = insn.range(INSN_MEM_C_1, INSN_MEM_C_0);
-  memop_pad_T x_pad_0 = insn.range(INSN_MEM_D_1, INSN_MEM_D_0);
-  memop_pad_T x_pad_1 = insn.range(INSN_MEM_E_1, INSN_MEM_E_0);
+  bool pop_prev_dependence = insn[VTA_INSN_MEM_1];
+  bool pop_next_dependence = insn[VTA_INSN_MEM_2];
+  bool push_prev_dependence = insn[VTA_INSN_MEM_3];
+  bool push_next_dependence = insn[VTA_INSN_MEM_4];
+  memop_id_T memory_type = insn.range(VTA_INSN_MEM_5_1, VTA_INSN_MEM_5_0);
+  memop_sram_T sram_base = insn.range(VTA_INSN_MEM_6_1, VTA_INSN_MEM_6_0);
+  memop_dram_T dram_base = insn.range(VTA_INSN_MEM_7_1, VTA_INSN_MEM_7_0);
+  memop_size_T y_size = insn.range(VTA_INSN_MEM_8_1, VTA_INSN_MEM_8_0);
+  memop_size_T x_size = insn.range(VTA_INSN_MEM_9_1, VTA_INSN_MEM_9_0);
+  memop_stride_T x_stride = insn.range(VTA_INSN_MEM_A_1, VTA_INSN_MEM_A_0);
+  memop_pad_T y_pad_0 = insn.range(VTA_INSN_MEM_B_1, VTA_INSN_MEM_B_0);
+  memop_pad_T y_pad_1 = insn.range(VTA_INSN_MEM_C_1, VTA_INSN_MEM_C_0);
+  memop_pad_T x_pad_0 = insn.range(VTA_INSN_MEM_D_1, VTA_INSN_MEM_D_0);
+  memop_pad_T x_pad_1 = insn.range(VTA_INSN_MEM_E_1, VTA_INSN_MEM_E_0);
 
   // Pop dependence token if instructed
   if (pop_prev_dependence) {
-    g2s_dep_queue.read();
+    g2s_dep_queue->read();
   }
 
   // Initialize indices
@@ -556,18 +525,19 @@ void store (
   // Skip padding along y dimension
   memop_sram_T y_offset = (x_pad_0 + x_size + x_pad_1) * y_pad_0;
   sram_idx += y_offset;
-#pragma HLS RESOURCE variable=y_offset core=Mul_LUT
+// Force this computation to be done with LUTs to avoid using too many DSPs
+#pragma HLS RESOURCE variable = y_offset core = Mul_LUT
 
   // Copy along y dimension
-  for (int y = 0; y < y_size; y ++) {
+  for (int y = 0; y < y_size; y++) {
 #pragma HLS PIPELINE rewind
     // Skip padding along x dimension
     sram_idx += x_pad_0;
     // Perform data transfer
     memcpy(
-      (out_vec_T *) &outputs[dram_idx*BATCH],
+      const_cast<out_vec_T*>(&outputs[dram_idx*VTA_BATCH]),
       (const out_vec_T*) &out_mem[sram_idx][0],
-      x_size * INP_ELEM_BYTES);
+      x_size * VTA_INP_ELEM_BYTES);
     sram_idx += x_size;
     dram_idx += x_stride;
     // Skip padding along x dimension
@@ -576,11 +546,11 @@ void store (
 
   // Push dependence token if instructed
   if (push_prev_dependence) {
-    s2g_dep_queue.write(1);
+    s2g_dep_queue->write(1);
   }
 }
 
-void vta (
+void vta(
   uint32_t insn_count,
   volatile insn_T *insns,
   volatile uop_T *uops,
@@ -588,14 +558,14 @@ void vta (
   volatile wgt_vec_T *weights,
   volatile acc_vec_T *biases,
   volatile out_vec_T *outputs) {
-#pragma HLS INTERFACE s_axilite port=insn_count bundle=CONTROL_BUS
-#pragma HLS INTERFACE m_axi port=insns offset=slave bundle=ins_port
-#pragma HLS INTERFACE m_axi port=uops offset=slave bundle=uop_port
-#pragma HLS INTERFACE m_axi port=inputs offset=slave bundle=data_port
-#pragma HLS INTERFACE m_axi port=weights offset=slave bundle=data_port
-#pragma HLS INTERFACE m_axi port=biases offset=slave bundle=data_port
-#pragma HLS INTERFACE m_axi port=outputs offset=slave bundle=data_port
-#pragma HLS INTERFACE s_axilite port=return bundle=CONTROL_BUS
+#pragma HLS INTERFACE s_axilite port = insn_count bundle = CONTROL_BUS
+#pragma HLS INTERFACE m_axi port = insns offset = slave bundle = ins_port
+#pragma HLS INTERFACE m_axi port = uops offset = slave bundle = uop_port
+#pragma HLS INTERFACE m_axi port = inputs offset = slave bundle = data_port
+#pragma HLS INTERFACE m_axi port = weights offset = slave bundle = data_port
+#pragma HLS INTERFACE m_axi port = biases offset = slave bundle = data_port
+#pragma HLS INTERFACE m_axi port = outputs offset = slave bundle = data_port
+#pragma HLS INTERFACE s_axilite port = return bundle = CONTROL_BUS
 
   // Instantiate temporary instruction queues (used for peeking)
   hls::stream<insn_T> tmp_load_queue;
@@ -614,18 +584,12 @@ void vta (
   hls::stream<bool> g2s_dep_queue;
 
   // Instantiate memories
-  inp_vec_T inp_mem[INP_BUFF_DEPTH][BATCH];
-  wgt_vec_T wgt_mem[WGT_BUFF_DEPTH][BLOCK_OUT];
-  out_vec_T out_mem[ACC_BUFF_DEPTH][BATCH];
+  inp_vec_T inp_mem[VTA_INP_BUFF_DEPTH][VTA_BATCH];
+  wgt_vec_T wgt_mem[VTA_WGT_BUFF_DEPTH][VTA_BLOCK_OUT];
+  out_vec_T out_mem[VTA_ACC_BUFF_DEPTH][VTA_BATCH];
 
   // Push all instructions into the queues
-  fetch(
-    insn_count,
-    insns,
-    tmp_load_queue,
-    tmp_gemm_queue,
-    tmp_store_queue
-  );
+  fetch(insn_count, insns, &tmp_load_queue, &tmp_gemm_queue, &tmp_store_queue);
 
   // Global done indicator
   uint32_t done = 0;
@@ -651,21 +615,13 @@ void vta (
         tmp_load_popped = true;
       }
       // Check dependences and invoke the load stage
-      bool pop_next_dependence = tmp_load[INSN_MEM_2];
+      bool pop_next_dependence = tmp_load[VTA_INSN_MEM_2];
       if ((pop_next_dependence && !g2l_dep_queue.empty()) ||
           !pop_next_dependence) {
         // Push the instruction in the load queue
         load_queue.write(tmp_load);
         tmp_load_popped = false;
-        load(
-          inputs,
-          weights,
-          load_queue,
-          g2l_dep_queue,
-          l2g_dep_queue,
-          inp_mem,
-          wgt_mem
-        );
+        load(inputs, weights, &load_queue, &g2l_dep_queue, &l2g_dep_queue, inp_mem, wgt_mem);
       } else {
         // Execution of load stage pending on completion of other stages, so break here...
         break;
@@ -679,8 +635,8 @@ void vta (
         tmp_gemm_popped = true;
       }
       // Check dependences and invoke the load stage
-      bool pop_prev_dependence = tmp_gemv[INSN_MEM_1];
-      bool pop_next_dependence = tmp_gemv[INSN_MEM_2];
+      bool pop_prev_dependence = tmp_gemv[VTA_INSN_MEM_1];
+      bool pop_next_dependence = tmp_gemv[VTA_INSN_MEM_2];
       if (
         (pop_prev_dependence && !l2g_dep_queue.empty() &&
          pop_next_dependence && !s2g_dep_queue.empty()) ||
@@ -693,19 +649,8 @@ void vta (
         // Push the instruction in the load queue
         gemm_queue.write(tmp_gemv);
         tmp_gemm_popped = false;
-        compute(
-          done,
-          uops,
-          biases,
-          gemm_queue,
-          l2g_dep_queue,
-          s2g_dep_queue,
-          g2l_dep_queue,
-          g2s_dep_queue,
-          inp_mem,
-          wgt_mem,
-          out_mem
-        );
+        compute(&done, uops, biases, &gemm_queue, &l2g_dep_queue, &s2g_dep_queue,
+                &g2l_dep_queue, &g2s_dep_queue, inp_mem, wgt_mem, out_mem);
       } else {
         // Execution of load stage pending on completion of other stages,
         // so break here...
@@ -720,19 +665,13 @@ void vta (
         tmp_store_popped = true;
       }
       // Check dependences and invoke the load stage
-      bool pop_prev_dependence = tmp_store[INSN_MEM_1];
+      bool pop_prev_dependence = tmp_store[VTA_INSN_MEM_1];
       if ((pop_prev_dependence && !g2s_dep_queue.empty()) ||
           !pop_prev_dependence) {
         // Push the instruction in the load queue
         store_queue.write(tmp_store);
         tmp_store_popped = false;
-        store(
-          outputs,
-          store_queue,
-          g2s_dep_queue,
-          s2g_dep_queue,
-          out_mem
-        );
+        store(outputs, &store_queue, &g2s_dep_queue, &s2g_dep_queue, out_mem);
       } else {
         // Execution of load stage pending on completion of other stages, so break here...
         break;
@@ -742,7 +681,7 @@ void vta (
     if (done) {
       break;
     }
-    exit_counter ++;
+    exit_counter++;
     if (exit_counter > 1000) {
       if (tmp_load_popped) {
         if (g2l_dep_queue.empty()) {
@@ -750,10 +689,10 @@ void vta (
         }
       }
       if (tmp_gemm_popped) {
-        if (l2g_dep_queue.empty() && tmp_gemv[INSN_MEM_1]) {
+        if (l2g_dep_queue.empty() && tmp_gemv[VTA_INSN_MEM_1]) {
           printf("waiting on l2g\n");
         }
-        if (s2g_dep_queue.empty() && tmp_gemv[INSN_MEM_2]) {
+        if (s2g_dep_queue.empty() && tmp_gemv[VTA_INSN_MEM_2]) {
           printf("waiting on s2g\n");
         }
       }
@@ -772,17 +711,17 @@ void vta (
   int s2g_count = 0;
   int g2l_count = 0;
   int g2s_count = 0;
-  while(l2g_dep_queue.read_nb(tmp_tok)) {
-    l2g_count ++;
+  while (l2g_dep_queue.read_nb(tmp_tok)) {
+    l2g_count++;
   }
-  while(s2g_dep_queue.read_nb(tmp_tok)) {
-    s2g_count ++;
+  while (s2g_dep_queue.read_nb(tmp_tok)) {
+    s2g_count++;
   }
-  while(g2l_dep_queue.read_nb(tmp_tok)) {
-    g2l_count ++;
+  while (g2l_dep_queue.read_nb(tmp_tok)) {
+    g2l_count++;
   }
-  while(g2s_dep_queue.read_nb(tmp_tok)) {
-    g2s_count ++;
+  while (g2s_dep_queue.read_nb(tmp_tok)) {
+    g2s_count++;
   }
 
   assert(l2g_count == 0 && g2s_count == 0 && g2l_count == 0 && g2s_count == 0);
diff --git a/vta/hardware/vivado/src/vta.h b/vta/hardware/vivado/src/vta.h
index 5dd4d953e436..37395722f5f7 100644
--- a/vta/hardware/vivado/src/vta.h
+++ b/vta/hardware/vivado/src/vta.h
@@ -3,96 +3,96 @@
  * \file vta.h
  * \brief Type definitions and prototype for VTA HLS design.
  */
-#ifndef VTA_MAIN_H_
-#define VTA_MAIN_H_
+#ifndef VTA_VTA_H_
+#define VTA_VTA_H_
 
-#include <assert.h>
 #include <ap_axi_sdata.h>
 #include <ap_int.h>
+#include <assert.h>
 #include <hls_stream.h>
 
 #include <vta/hw_spec.h>
 
 /* \typedef uop_T Micro-op datatype*/
-typedef ap_uint<UOP_WIDTH> uop_T;
+typedef ap_uint<VTA_UOP_WIDTH> uop_T;
 
 /* \typedef inp_T Input datatype*/
-typedef ap_int<INP_WIDTH> inp_T;
+typedef ap_int<VTA_INP_WIDTH> inp_T;
 
 /* \typedef wgt_T Weight datatype*/
-typedef ap_int<WGT_WIDTH> wgt_T;
+typedef ap_int<VTA_WGT_WIDTH> wgt_T;
 
 /* \typedef out_T Output datatype*/
-typedef ap_int<OUT_WIDTH> out_T;
+typedef ap_int<VTA_OUT_WIDTH> out_T;
 
 /* \typedef acc_T Accumulator datatype*/
-typedef ap_int<ACC_WIDTH> acc_T;
+typedef ap_int<VTA_ACC_WIDTH> acc_T;
 
 /* \typedef mul_T Multiplier output datatype*/
-typedef ap_int<WGT_WIDTH+INP_WIDTH+1> mul_T;
+typedef ap_int<VTA_WGT_WIDTH+VTA_INP_WIDTH+1> mul_T;
 
 /* \typedef sum_T GEMM accumulator datatype*/
-typedef ap_int<WGT_WIDTH+INP_WIDTH+LOG_BLOCK_IN+1> sum_T;
+typedef ap_int<VTA_WGT_WIDTH+VTA_INP_WIDTH+VTA_LOG_BLOCK_IN+1> sum_T;
 
 /* \typedef inp_vec_T Input vector datatype*/
-typedef ap_uint<INP_WIDTH*BLOCK_IN> inp_vec_T;
+typedef ap_uint<VTA_INP_WIDTH*VTA_BLOCK_IN> inp_vec_T;
 
 /* \typedef wgt_vec_T Weight vector datatype*/
-typedef ap_uint<WGT_WIDTH*BLOCK_IN> wgt_vec_T;
+typedef ap_uint<VTA_WGT_WIDTH*VTA_BLOCK_IN> wgt_vec_T;
 
 /* \typedef acc_vec_T Accumulator vector datatype*/
-typedef ap_uint<ACC_WIDTH*BLOCK_OUT> acc_vec_T;
+typedef ap_uint<VTA_ACC_WIDTH*VTA_BLOCK_OUT> acc_vec_T;
 
 /* \typedef out_vec_T Output vector datatype*/
-typedef ap_uint<OUT_WIDTH*BLOCK_OUT> out_vec_T;
+typedef ap_uint<VTA_OUT_WIDTH*VTA_BLOCK_OUT> out_vec_T;
 
 /* \typedef uop_idx_T Micro-op SRAM index datatype*/
-typedef ap_uint<LOG_UOP_BUFF_DEPTH+1> uop_idx_T;
+typedef ap_uint<VTA_LOG_UOP_BUFF_DEPTH+1> uop_idx_T;
 
 /* \typedef inp_idx_T Input SRAM index datatype*/
-typedef ap_uint<LOG_INP_BUFF_DEPTH+1> inp_idx_T;
+typedef ap_uint<VTA_LOG_INP_BUFF_DEPTH+1> inp_idx_T;
 
 /* \typedef wgt_idx_T Weight SRAM index datatype*/
-typedef ap_uint<LOG_WGT_BUFF_DEPTH+1> wgt_idx_T;
+typedef ap_uint<VTA_LOG_WGT_BUFF_DEPTH+1> wgt_idx_T;
 
 /* \typedef acc_idx_T Accumulator SRAM index datatype*/
-typedef ap_uint<LOG_ACC_BUFF_DEPTH+1> acc_idx_T;
+typedef ap_uint<VTA_LOG_ACC_BUFF_DEPTH+1> acc_idx_T;
 
 /* \typedef opcode_T Opcode datatype*/
-typedef ap_uint<OPCODE_BIT_WIDTH> opcode_T;
+typedef ap_uint<VTA_OPCODE_BIT_WIDTH> opcode_T;
 
 /* \typedef insn_T Instruction datatype*/
-typedef ap_uint<INS_WIDTH> insn_T;
+typedef ap_uint<VTA_INS_WIDTH> insn_T;
 
 /* \typedef loop_T Loop bound datatype*/
-typedef ap_uint<LOOP_ITER_WIDTH> loop_T;
+typedef ap_uint<VTA_LOOP_ITER_WIDTH> loop_T;
 
 /* \typedef memop_id_T Memory operation ID datatype*/
-typedef ap_uint<MEMOP_ID_BIT_WIDTH> memop_id_T;
+typedef ap_uint<VTA_MEMOP_ID_BIT_WIDTH> memop_id_T;
 
 /* \typedef memop_sram_T Memory operation SRAM index datatype*/
-typedef ap_uint<MEMOP_SRAM_ADDR_BIT_WIDTH> memop_sram_T;
+typedef ap_uint<VTA_MEMOP_SRAM_ADDR_BIT_WIDTH> memop_sram_T;
 
 /* \typedef memop_dram_T Memory operation DRAM index datatype*/
-typedef ap_uint<MEMOP_DRAM_ADDR_BIT_WIDTH> memop_dram_T;
+typedef ap_uint<VTA_MEMOP_DRAM_ADDR_BIT_WIDTH> memop_dram_T;
 
 /* \typedef memop_size_T Memory operation range datatype*/
-typedef ap_uint<MEMOP_SIZE_BIT_WIDTH> memop_size_T;
+typedef ap_uint<VTA_MEMOP_SIZE_BIT_WIDTH> memop_size_T;
 
 /* \typedef memop_stride_T Memory operation stride datatype*/
-typedef ap_uint<MEMOP_STRIDE_BIT_WIDTH> memop_stride_T;
+typedef ap_uint<VTA_MEMOP_STRIDE_BIT_WIDTH> memop_stride_T;
 
 /* \typedef memop_pad_T Memory operation pad width datatype*/
-typedef ap_uint<MEMOP_PAD_BIT_WIDTH> memop_pad_T;
+typedef ap_uint<VTA_MEMOP_PAD_BIT_WIDTH> memop_pad_T;
 
 /* \typedef aluop_opcode_T ALU operation opcode datatype*/
-typedef ap_uint<ALU_OPCODE_BIT_WIDTH> aluop_opcode_T;
+typedef ap_uint<VTA_ALU_OPCODE_BIT_WIDTH> aluop_opcode_T;
 
 /* \typedef aluop_opcode_T ALU operation immediate datatype*/
-typedef ap_int<ALUOP_IMM_BIT_WIDTH> aluop_imm_T;
+typedef ap_int<VTA_ALUOP_IMM_BIT_WIDTH> aluop_imm_T;
 
 /* \typedef aluop_opcode_T ALU operation shift immediate datatype*/
-typedef ap_uint<LOG_ACC_WIDTH> aluop_sh_imm_T;
+typedef ap_uint<VTA_LOG_ACC_WIDTH> aluop_sh_imm_T;
 
 /*!
 * \brief Fetch module.
@@ -104,12 +104,12 @@ typedef ap_uint<LOG_ACC_WIDTH> aluop_sh_imm_T;
 * \param gemm_queue GEMM instruction queue. AXI-stream FIFO.
 * \param store_queue Store instruction queue. AXI-stream FIFO.
 */
-void fetch (
+void fetch(
   uint32_t insn_count,
   volatile insn_T *insns,
-  hls::stream<insn_T> &load_queue,
-  hls::stream<insn_T> &gemm_queue,
-  hls::stream<insn_T> &store_queue);
+  hls::stream<insn_T> *load_queue,
+  hls::stream<insn_T> *gemm_queue,
+  hls::stream<insn_T> *store_queue);
 
 /*!
 * \brief Load module.
@@ -126,15 +126,14 @@ void fetch (
 * \param inp_mem Local input SRAM buffer. Write only single port BRAM.
 * \param wgt_mem Local weight SRAM buffer. Write only single port BRAM.
 */
-void load (
+void load(
   volatile inp_vec_T *inputs,
   volatile wgt_vec_T *weights,
-  hls::stream<insn_T> &load_queue,
-  hls::stream<bool> &g2l_dep_queue,
-  hls::stream<bool> &l2g_dep_queue,
-  inp_vec_T inp_mem[INP_BUFF_DEPTH][BATCH],
-  wgt_vec_T wgt_mem[WGT_BUFF_DEPTH][BLOCK_OUT]
-  );
+  hls::stream<insn_T> *load_queue,
+  hls::stream<bool> *g2l_dep_queue,
+  hls::stream<bool> *l2g_dep_queue,
+  inp_vec_T inp_mem[VTA_INP_BUFF_DEPTH][VTA_BATCH],
+  wgt_vec_T wgt_mem[VTA_WGT_BUFF_DEPTH][VTA_BLOCK_OUT]);
 
 /*!
 * \brief Compute module.
@@ -159,19 +158,18 @@ void load (
 * \param wgt_mem Local weight SRAM buffer. Read only single port BRAM.
 * \param out_mem Local output SRAM buffer. Write only single port BRAM.
 */
-void compute (
-  volatile uint32_t &done,
+void compute(
+  volatile uint32_t *done,
   volatile uop_T *uops,
   volatile acc_vec_T *biases,
-  hls::stream<insn_T> &gemm_queue,
-  hls::stream<bool> &l2g_dep_queue,
-  hls::stream<bool> &s2g_dep_queue,
-  hls::stream<bool> &g2l_dep_queue,
-  hls::stream<bool> &g2s_dep_queue,
-  out_vec_T inp_mem[INP_BUFF_DEPTH][BATCH],
-  wgt_vec_T wgt_mem[WGT_BUFF_DEPTH][BLOCK_OUT],
-  out_vec_T out_mem[ACC_BUFF_DEPTH][BATCH]
-  );
+  hls::stream<insn_T> *gemm_queue,
+  hls::stream<bool> *l2g_dep_queue,
+  hls::stream<bool> *s2g_dep_queue,
+  hls::stream<bool> *g2l_dep_queue,
+  hls::stream<bool> *g2s_dep_queue,
+  out_vec_T inp_mem[VTA_INP_BUFF_DEPTH][VTA_BATCH],
+  wgt_vec_T wgt_mem[VTA_WGT_BUFF_DEPTH][VTA_BLOCK_OUT],
+  out_vec_T out_mem[VTA_ACC_BUFF_DEPTH][VTA_BATCH]);
 
 /*!
 * \brief Store module.
@@ -186,13 +184,12 @@ void compute (
 *   AXI-stream FIFO.
 * \param out_mem Local output SRAM buffer. Read only single port BRAM.
 */
-void store (
+void store(
   volatile out_vec_T *outputs,
-  hls::stream<insn_T> &store_queue,
-  hls::stream<bool> &g2s_dep_queue,
-  hls::stream<bool> &s2g_dep_queue,
-  out_vec_T out_mem[ACC_BUFF_DEPTH][BATCH]
-  );
+  hls::stream<insn_T> *store_queue,
+  hls::stream<bool> *g2s_dep_queue,
+  hls::stream<bool> *s2g_dep_queue,
+  out_vec_T out_mem[VTA_ACC_BUFF_DEPTH][VTA_BATCH]);
 
 /*!
 * \brief VTA wrapper for simulation purpose only.
@@ -205,7 +202,7 @@ void store (
 * \param biases Bias data base address in DRAM. AXI-4 master port.
 * \param outputs Output data base address in DRAM. AXI-4 master port.
 */
-void vta (
+void vta(
   uint32_t insn_count,
   volatile insn_T *insns,
   volatile uop_T *uops,
@@ -214,4 +211,4 @@ void vta (
   volatile acc_vec_T *biases,
   volatile out_vec_T *outputs);
 
-#endif  // VTA_MAIN_H_
\ No newline at end of file
+#endif  // VTA_VTA_H_
diff --git a/vta/include/vta/driver.h b/vta/include/vta/driver.h
index 2b5e0ea93674..c93021d96e4b 100644
--- a/vta/include/vta/driver.h
+++ b/vta/include/vta/driver.h
@@ -14,10 +14,10 @@ extern "C" {
 #include <stdlib.h>
 #include <stdint.h>
 
-/*! \brief Memory management constants with libxlnk_cma */
-#define CACHED 1
-/*! \brief Memory management constants with libxlnk_cma */
-#define NOT_CACHED 0
+/*! \brief Memory management constants */
+#define VTA_CACHED 1
+/*! \brief Memory management constants */
+#define VTA_NOT_CACHED 0
 
 /*! \brief VTA command handle */
 typedef void * VTAHandle;
@@ -97,4 +97,4 @@ void VTAProgram(const char* bitstream);
 #ifdef __cplusplus
 }
 #endif
-#endif // VTA_DRIVER_H_
+#endif  // VTA_DRIVER_H_
diff --git a/vta/include/vta/hw_spec.h b/vta/include/vta/hw_spec.h
index b18e94e63a07..0c30b344795a 100644
--- a/vta/include/vta/hw_spec.h
+++ b/vta/include/vta/hw_spec.h
@@ -14,150 +14,153 @@ extern "C" {
 #include <stdint.h>
 
 /*! log2 of instruction data type width */
-#define LOG_INS_WIDTH 7
+#define VTA_LOG_INS_WIDTH 7
 /*! Instruction data type width */
-#define INS_WIDTH (1<<LOG_INS_WIDTH)
+#define VTA_INS_WIDTH (1 << VTA_LOG_INS_WIDTH)
 /*! log2 of micro op data type width */
-#define LOG_UOP_WIDTH 5
+#define VTA_LOG_UOP_WIDTH 5
 /*! Micro Op data type width */
-#define UOP_WIDTH (1<<LOG_UOP_WIDTH)
+#define VTA_UOP_WIDTH (1 << VTA_LOG_UOP_WIDTH)
 /*! Weight data type width */
-#define WGT_WIDTH (1<<LOG_WGT_WIDTH)
+#define VTA_WGT_WIDTH (1 << VTA_LOG_WGT_WIDTH)
 /*! Input data type width */
-#define INP_WIDTH (1<<LOG_INP_WIDTH)
+#define VTA_INP_WIDTH (1 << VTA_LOG_INP_WIDTH)
 /*! Output data type width */
-#define OUT_WIDTH (1<<LOG_OUT_WIDTH)
+#define VTA_OUT_WIDTH (1 << VTA_LOG_OUT_WIDTH)
 /*! Accumulator data type width */
-#define ACC_WIDTH (1<<LOG_ACC_WIDTH)
+#define VTA_ACC_WIDTH (1 << VTA_LOG_ACC_WIDTH)
 /*! log2 of ALU data type width */
-#define LOG_ALU_WIDTH (LOG_ACC_WIDTH-1)
+#define VTA_LOG_ALU_WIDTH (VTA_LOG_ACC_WIDTH - 1)
 /*! ALU data type width */
-#define ALU_WIDTH (1<<LOG_ALU_WIDTH)
+#define VTA_ALU_WIDTH (1 << VTA_LOG_ALU_WIDTH)
 
 /*! Batch size (corresponds to A in (A,B)x(B,C) mat mult)*/
-#define BATCH (1<<LOG_BATCH)
+#define VTA_BATCH (1 << VTA_LOG_BATCH)
 /*! Blocking factor of inner most loop (corresponds to B in (A,B)x(B,C) mat mult) */
-#define BLOCK_IN (1<<LOG_BLOCK_IN)
+#define VTA_BLOCK_IN (1 << VTA_LOG_BLOCK_IN)
 /*! Blocking factor of the outer loop (corresponds to C in (A,B)x(B,C) mat mult) */
-#define BLOCK_OUT (1<<LOG_BLOCK_OUT)
+#define VTA_BLOCK_OUT (1 << VTA_LOG_BLOCK_OUT)
 
 /*! Weight vector width */
-#define WGT_VECTOR_WIDTH (WGT_WIDTH*BLOCK_IN)
+#define VTA_WGT_VECTOR_WIDTH (VTA_WGT_WIDTH * VTA_BLOCK_IN)
 /*! Input vector width */
-#define INP_VECTOR_WIDTH (INP_WIDTH*BLOCK_IN)
+#define VTA_INP_VECTOR_WIDTH (VTA_INP_WIDTH * VTA_BLOCK_IN)
 /*! Accumulator vector width */
-#define ACC_VECTOR_WIDTH (ACC_WIDTH*BLOCK_OUT)
+#define VTA_ACC_VECTOR_WIDTH (VTA_ACC_WIDTH * VTA_BLOCK_OUT)
 /*! Output vector width */
-#define OUT_VECTOR_WIDTH (OUT_WIDTH*BLOCK_OUT)
+#define VTA_OUT_VECTOR_WIDTH (VTA_OUT_WIDTH * VTA_BLOCK_OUT)
 
 /*! On-chip micro-op buffer size in B */
-#define UOP_BUFF_SIZE (1<<LOG_UOP_BUFF_SIZE)
+#define VTA_UOP_BUFF_SIZE (1 << VTA_LOG_UOP_BUFF_SIZE)
 /*! On-chip weight buffer size in B */
-#define WGT_BUFF_SIZE (1<<LOG_WGT_BUFF_SIZE)
+#define VTA_WGT_BUFF_SIZE (1 << VTA_LOG_WGT_BUFF_SIZE)
 /*! On-chip activation buffer size in B */
-#define INP_BUFF_SIZE (1<<LOG_INP_BUFF_SIZE)
+#define VTA_INP_BUFF_SIZE (1 << VTA_LOG_INP_BUFF_SIZE)
 /*! On-chip accumulator buffer size in B */
-#define ACC_BUFF_SIZE (1<<LOG_ACC_BUFF_SIZE)
+#define VTA_ACC_BUFF_SIZE (1 << VTA_LOG_ACC_BUFF_SIZE)
 
 /*! Size of instruction buffer element in B */
-#define INS_ELEM_BYTES (INS_WIDTH/8)
+#define VTA_INS_ELEM_BYTES (VTA_INS_WIDTH / 8)
 /*! Size of uop buffer element in B*/
-#define UOP_ELEM_BYTES (UOP_WIDTH/8)
+#define VTA_UOP_ELEM_BYTES (VTA_UOP_WIDTH / 8)
 /*! Size of activation buffer element in B*/
-#define INP_ELEM_BYTES (BATCH*BLOCK_IN*INP_WIDTH/8)
+#define VTA_INP_ELEM_BYTES (VTA_BATCH * VTA_BLOCK_IN * VTA_INP_WIDTH / 8)
 /*! Size of weight buffer element in B*/
-#define WGT_ELEM_BYTES (BLOCK_OUT*BLOCK_IN*WGT_WIDTH/8)
+#define VTA_WGT_ELEM_BYTES (VTA_BLOCK_OUT * VTA_BLOCK_IN * VTA_WGT_WIDTH / 8)
 /*! Size of accumulator buffer element in B*/
-#define ACC_ELEM_BYTES (BATCH*BLOCK_OUT*ACC_WIDTH/8)
+#define VTA_ACC_ELEM_BYTES (VTA_BATCH * VTA_BLOCK_OUT * VTA_ACC_WIDTH / 8)
 
 /*! On-chip micro-op buffer depth */
-#define UOP_BUFF_DEPTH (UOP_BUFF_SIZE/UOP_ELEM_BYTES)
+#define VTA_UOP_BUFF_DEPTH (VTA_UOP_BUFF_SIZE / VTA_UOP_ELEM_BYTES)
 /*! log2 of on-chip micro-op buffer depth */
-#define LOG_UOP_BUFF_DEPTH (LOG_UOP_BUFF_SIZE-LOG_UOP_WIDTH+3)
+#define VTA_LOG_UOP_BUFF_DEPTH (VTA_LOG_UOP_BUFF_SIZE - VTA_LOG_UOP_WIDTH + 3)
 // ! \brief On-chip weight buffer depth
-#define WGT_BUFF_DEPTH (WGT_BUFF_SIZE/WGT_ELEM_BYTES)
+#define VTA_WGT_BUFF_DEPTH (VTA_WGT_BUFF_SIZE / VTA_WGT_ELEM_BYTES)
 /*! log2 of weight micro-op buffer depth */
-#define LOG_WGT_BUFF_DEPTH (LOG_WGT_BUFF_SIZE-LOG_BLOCK_OUT-LOG_BLOCK_IN-LOG_WGT_WIDTH+3)
+#define VTA_LOG_WGT_BUFF_DEPTH \
+    (VTA_LOG_WGT_BUFF_SIZE - VTA_LOG_BLOCK_OUT - VTA_LOG_BLOCK_IN - VTA_LOG_WGT_WIDTH + 3)
 /*! On-chip activation buffer depth */
-#define INP_BUFF_DEPTH (INP_BUFF_SIZE/INP_ELEM_BYTES)
+#define VTA_INP_BUFF_DEPTH (VTA_INP_BUFF_SIZE / VTA_INP_ELEM_BYTES)
 /*! log2 of activation micro-op buffer depth */
-#define LOG_INP_BUFF_DEPTH (LOG_INP_BUFF_SIZE-LOG_BATCH-LOG_BLOCK_IN-LOG_INP_WIDTH+3)
+#define VTA_LOG_INP_BUFF_DEPTH \
+    (VTA_LOG_INP_BUFF_SIZE - VTA_LOG_BATCH - VTA_LOG_BLOCK_IN - VTA_LOG_INP_WIDTH + 3)
 /*! On-chip accumulator buffer depth */
-#define ACC_BUFF_DEPTH (ACC_BUFF_SIZE/ACC_ELEM_BYTES)
+#define VTA_ACC_BUFF_DEPTH (VTA_ACC_BUFF_SIZE / VTA_ACC_ELEM_BYTES)
 /*! log2 of on-chip accumulator buffer depth */
-#define LOG_ACC_BUFF_DEPTH (LOG_ACC_BUFF_SIZE-LOG_BATCH-LOG_BLOCK_OUT-LOG_ACC_WIDTH+3)
+#define VTA_LOG_ACC_BUFF_DEPTH \
+    (VTA_LOG_ACC_BUFF_SIZE - VTA_LOG_BATCH - VTA_LOG_BLOCK_OUT - VTA_LOG_ACC_WIDTH + 3)
 
 /*! Instruction opcode field bitwidth */
-#define OPCODE_BIT_WIDTH 3
+#define VTA_OPCODE_BIT_WIDTH 3
 /*! ALU opcode field bitwidth */
-#define ALU_OPCODE_BIT_WIDTH 3
+#define VTA_ALU_OPCODE_BIT_WIDTH 3
 /*! ALU instruction reset mode bitwidth */
-#define ALU_RESET_BIT_WIDTH 2
+#define VTA_ALU_RESET_BIT_WIDTH 2
 
 /*! Opcode: load encoding */
-#define OPCODE_LOAD 0
+#define VTA_OPCODE_LOAD 0
 /*! Opcode: store encoding */
-#define OPCODE_STORE 1
+#define VTA_OPCODE_STORE 1
 /*! Opcode: GEMM encoding */
-#define OPCODE_GEMM 2
+#define VTA_OPCODE_GEMM 2
 /*! Opcode: finish encoding */
-#define OPCODE_FINISH 3
+#define VTA_OPCODE_FINISH 3
 /*! Opcode: ALU encoding */
-#define OPCODE_ALU 4
+#define VTA_OPCODE_ALU 4
 
 /*! ALU opcode: unary min op */
-#define ALU_OPCODE_MIN 0
+#define VTA_ALU_OPCODE_MIN 0
 /*! ALU opcode: unary max op */
-#define ALU_OPCODE_MAX 1
+#define VTA_ALU_OPCODE_MAX 1
 /*! ALU opcode: binary add op */
-#define ALU_OPCODE_ADD 2
+#define VTA_ALU_OPCODE_ADD 2
 /*! ALU opcode: binary sub op [NOT IMPLEMENTED] */
-#define ALU_OPCODE_SUB 3
+#define VTA_ALU_OPCODE_SUB 3
 /*! ALU opcode: binary mul op  [NOT IMPLEMENTED] */
-#define ALU_OPCODE_MUL 4
+#define VTA_ALU_OPCODE_MUL 4
 /*! ALU opcode: shift left by immediate op */
-#define ALU_OPCODE_SHL 5
+#define VTA_ALU_OPCODE_SHL 5
 /*! ALU opcode: shift right by immediate op [NOT IMPLEMENTED] */
-#define ALU_OPCODE_SHR 6
+#define VTA_ALU_OPCODE_SHR 6
 
 /*! ALU instruction reset mode: set to min */
-#define ALU_RESET_MIN 3
+#define VTA_ALU_RESET_MIN 3
 /*! ALU instruction reset mode: set to zero */
-#define ALU_RESET_ZERO 0
+#define VTA_ALU_RESET_ZERO 0
 /*! ALU instruction reset mode: no reset */
-#define ALU_NO_RESET 2
+#define VTA_ALU_NO_RESET 2
 /*! ALU instruction reset mode: set to max */
-#define ALU_RESET_MAX 1
+#define VTA_ALU_RESET_MAX 1
 
 /*! Memory type field bitwidth */
-#define MEMOP_ID_BIT_WIDTH 2
+#define VTA_MEMOP_ID_BIT_WIDTH 2
 /*! Load/Store Instruction: DRAM address width*/
-#define MEMOP_SRAM_ADDR_BIT_WIDTH 16
+#define VTA_MEMOP_SRAM_ADDR_BIT_WIDTH 16
 /*! Load/Store Instruction: DRAM address width*/
-#define MEMOP_DRAM_ADDR_BIT_WIDTH 32
+#define VTA_MEMOP_DRAM_ADDR_BIT_WIDTH 32
 /*! Load/Store Instruction: transfer size width*/
-#define MEMOP_SIZE_BIT_WIDTH 16
+#define VTA_MEMOP_SIZE_BIT_WIDTH 16
 /*! Load/Store Instruction: stride size width*/
-#define MEMOP_STRIDE_BIT_WIDTH 16
+#define VTA_MEMOP_STRIDE_BIT_WIDTH 16
 /*! Load/Store Instruction: padding width*/
-#define MEMOP_PAD_BIT_WIDTH 4
+#define VTA_MEMOP_PAD_BIT_WIDTH 4
 /*! Load/Store Instruction: padding value encoding width*/
-#define MEMOP_PAD_VAL_BIT_WIDTH 2
+#define VTA_MEMOP_PAD_VAL_BIT_WIDTH 2
 /*! ALU Instruction: immediate bitwidth*/
-#define ALUOP_IMM_BIT_WIDTH 16
+#define VTA_ALUOP_IMM_BIT_WIDTH 16
 /*! GEMM/ALU Instruction: loop max iter bits */
-#define LOOP_ITER_WIDTH 15
+#define VTA_LOOP_ITER_WIDTH 15
 
 /*! Mem ID constant: uop memory */
-#define MEM_ID_UOP 0
+#define VTA_MEM_ID_UOP 0
 /*! Mem ID constant: weight memory */
-#define MEM_ID_WGT 1
+#define VTA_MEM_ID_WGT 1
 /*! Mem ID constant: input memory */
-#define MEM_ID_INP 2
+#define VTA_MEM_ID_INP 2
 /*! Mem ID constant: accumulator/bias memory */
-#define MEM_ID_ACC 3
+#define VTA_MEM_ID_ACC 3
 /*! Mem ID constant: output store buffer */
-#define MEM_ID_OUT 4
+#define VTA_MEM_ID_OUT 4
 
 // Instruction organization layout:
 //
@@ -218,152 +221,152 @@ extern "C" {
 // arg f: imm                   | alu_imm_T         |
 
 /*! Load/Store instruction start position of the opcode field */
-#define INSN_MEM_0_0 0
+#define VTA_INSN_MEM_0_0 0
 /*! Load/Store instruction end position of the opcode field */
-#define INSN_MEM_0_1 (INSN_MEM_0_0+OPCODE_BIT_WIDTH-1)
+#define VTA_INSN_MEM_0_1 (VTA_INSN_MEM_0_0 + VTA_OPCODE_BIT_WIDTH - 1)
 /*! Load/Store instruction position of the pop_prev_dep field */
-#define INSN_MEM_1   (INSN_MEM_0_1+1)
+#define VTA_INSN_MEM_1   (VTA_INSN_MEM_0_1 + 1)
 /*! Load/Store instruction position of the pop_next_dep field */
-#define INSN_MEM_2   (INSN_MEM_1+1)
+#define VTA_INSN_MEM_2   (VTA_INSN_MEM_1 + 1)
 /*! Load/Store instruction position of the push_prev_dependence field */
-#define INSN_MEM_3   (INSN_MEM_2+1)
+#define VTA_INSN_MEM_3   (VTA_INSN_MEM_2 + 1)
 /*! Load/Store instruction position of the push_next_dependence field */
-#define INSN_MEM_4   (INSN_MEM_3+1)
+#define VTA_INSN_MEM_4   (VTA_INSN_MEM_3 + 1)
 /*! Load/Store instruction start position of the memory_type field */
-#define INSN_MEM_5_0 (INSN_MEM_4+1)
+#define VTA_INSN_MEM_5_0 (VTA_INSN_MEM_4 + 1)
 /*! Load/Store instruction end position of the memory_type field */
-#define INSN_MEM_5_1 (INSN_MEM_5_0+MEMOP_ID_BIT_WIDTH-1)
+#define VTA_INSN_MEM_5_1 (VTA_INSN_MEM_5_0 + VTA_MEMOP_ID_BIT_WIDTH - 1)
 /*! Load/Store instruction start position of the sram_base field */
-#define INSN_MEM_6_0 (INSN_MEM_5_1+1)
+#define VTA_INSN_MEM_6_0 (VTA_INSN_MEM_5_1 + 1)
 /*! Load/Store instruction end position of the sram_base field */
-#define INSN_MEM_6_1 (INSN_MEM_6_0+MEMOP_SRAM_ADDR_BIT_WIDTH-1)
+#define VTA_INSN_MEM_6_1 (VTA_INSN_MEM_6_0 + VTA_MEMOP_SRAM_ADDR_BIT_WIDTH - 1)
 /*! Load/Store instruction start position of the dram_base field */
-#define INSN_MEM_7_0 (INSN_MEM_6_1+1)
+#define VTA_INSN_MEM_7_0 (VTA_INSN_MEM_6_1 + 1)
 /*! Load/Store instruction end position of the dram_base field */
-#define INSN_MEM_7_1 (INSN_MEM_7_0+MEMOP_DRAM_ADDR_BIT_WIDTH-1)
+#define VTA_INSN_MEM_7_1 (VTA_INSN_MEM_7_0 + VTA_MEMOP_DRAM_ADDR_BIT_WIDTH - 1)
 /*! Load/Store instruction start position of the y_size field */
-#define INSN_MEM_8_0 64
+#define VTA_INSN_MEM_8_0 64
 /*! Load/Store instruction end position of the y_size field */
-#define INSN_MEM_8_1 (INSN_MEM_8_0+MEMOP_SIZE_BIT_WIDTH-1)
+#define VTA_INSN_MEM_8_1 (VTA_INSN_MEM_8_0 + VTA_MEMOP_SIZE_BIT_WIDTH - 1)
 /*! Load/Store instruction start position of the x_size field */
-#define INSN_MEM_9_0 (INSN_MEM_8_1+1)
+#define VTA_INSN_MEM_9_0 (VTA_INSN_MEM_8_1 + 1)
 /*! Load/Store instruction start position of the x_size field */
-#define INSN_MEM_9_1 (INSN_MEM_9_0+MEMOP_SIZE_BIT_WIDTH-1)
+#define VTA_INSN_MEM_9_1 (VTA_INSN_MEM_9_0 + VTA_MEMOP_SIZE_BIT_WIDTH - 1)
 /*! Load/Store instruction start position of the x_stride field */
-#define INSN_MEM_A_0 (INSN_MEM_9_1+1)
+#define VTA_INSN_MEM_A_0 (VTA_INSN_MEM_9_1 + 1)
 /*! Load/Store instruction end position of the x_stride field */
-#define INSN_MEM_A_1 (INSN_MEM_A_0+MEMOP_STRIDE_BIT_WIDTH-1)
+#define VTA_INSN_MEM_A_1 (VTA_INSN_MEM_A_0 + VTA_MEMOP_STRIDE_BIT_WIDTH - 1)
 /*! Load/Store instruction start position of the y_pad_0 field */
-#define INSN_MEM_B_0 (INSN_MEM_A_1+1)
+#define VTA_INSN_MEM_B_0 (VTA_INSN_MEM_A_1 + 1)
 /*! Load/Store instruction start position of the y_pad_0 field */
-#define INSN_MEM_B_1 (INSN_MEM_B_0+MEMOP_PAD_BIT_WIDTH-1)
+#define VTA_INSN_MEM_B_1 (VTA_INSN_MEM_B_0 + VTA_MEMOP_PAD_BIT_WIDTH - 1)
 /*! Load/Store instruction start position of the y_pad_1 field */
-#define INSN_MEM_C_0 (INSN_MEM_B_1+1)
+#define VTA_INSN_MEM_C_0 (VTA_INSN_MEM_B_1 + 1)
 /*! Load/Store instruction start position of the y_pad_1 field */
-#define INSN_MEM_C_1 (INSN_MEM_C_0+MEMOP_PAD_BIT_WIDTH-1)
+#define VTA_INSN_MEM_C_1 (VTA_INSN_MEM_C_0 + VTA_MEMOP_PAD_BIT_WIDTH - 1)
 /*! Load/Store instruction start position of the x_pad_0 field */
-#define INSN_MEM_D_0 (INSN_MEM_C_1+1)
+#define VTA_INSN_MEM_D_0 (VTA_INSN_MEM_C_1 + 1)
 /*! Load/Store instruction start position of the x_pad_0 field */
-#define INSN_MEM_D_1 (INSN_MEM_D_0+MEMOP_PAD_BIT_WIDTH-1)
+#define VTA_INSN_MEM_D_1 (VTA_INSN_MEM_D_0 + VTA_MEMOP_PAD_BIT_WIDTH - 1)
 /*! Load/Store instruction start position of the x_pad_1 field */
-#define INSN_MEM_E_0 (INSN_MEM_D_1+1)
+#define VTA_INSN_MEM_E_0 (VTA_INSN_MEM_D_1 + 1)
 /*! Load/Store instruction start position of the x_pad_1 field */
-#define INSN_MEM_E_1 (INSN_MEM_E_0+MEMOP_PAD_BIT_WIDTH-1)
+#define VTA_INSN_MEM_E_1 (VTA_INSN_MEM_E_0 + VTA_MEMOP_PAD_BIT_WIDTH - 1)
 
 /*! GEMM instruction start position of the opcode field */
-#define INSN_GEM_0_0 0
+#define VTA_INSN_GEM_0_0 0
 /*! GEMM instruction end position of the opcode field */
-#define INSN_GEM_0_1 (INSN_GEM_0_0+OPCODE_BIT_WIDTH-1)
+#define VTA_INSN_GEM_0_1 (VTA_INSN_GEM_0_0 + VTA_OPCODE_BIT_WIDTH - 1)
 /*! GEMM instruction position of the pop_prev_dep field */
-#define INSN_GEM_1   (INSN_GEM_0_1+1)
+#define VTA_INSN_GEM_1   (VTA_INSN_GEM_0_1 + 1)
 /*! GEMM instruction position of the pop_next_dep field */
-#define INSN_GEM_2   (INSN_GEM_1+1)
+#define VTA_INSN_GEM_2   (VTA_INSN_GEM_1 + 1)
 /*! GEMM instruction position of the push_prev_dependence field */
-#define INSN_GEM_3   (INSN_GEM_2+1)
+#define VTA_INSN_GEM_3   (VTA_INSN_GEM_2 + 1)
 /*! GEMM instruction position of the push_next_dependence field */
-#define INSN_GEM_4   (INSN_GEM_3+1)
+#define VTA_INSN_GEM_4   (VTA_INSN_GEM_3 + 1)
 /*! GEMM instruction start position of the uop_bgn field */
-#define INSN_GEM_5_0 (INSN_GEM_4+1)
+#define VTA_INSN_GEM_5_0 (VTA_INSN_GEM_4 + 1)
 /*! GEMM instruction end position of the uop_bgn field */
-#define INSN_GEM_5_1 (INSN_GEM_5_0+LOG_UOP_BUFF_DEPTH-1)
+#define VTA_INSN_GEM_5_1 (VTA_INSN_GEM_5_0 + VTA_LOG_UOP_BUFF_DEPTH - 1)
 /*! GEMM instruction start position of the uop_end field */
-#define INSN_GEM_6_0 (INSN_GEM_5_1+1)
+#define VTA_INSN_GEM_6_0 (VTA_INSN_GEM_5_1 + 1)
 /*! GEMM instruction end position of the uop_end field */
-#define INSN_GEM_6_1 (INSN_GEM_6_0+LOG_UOP_BUFF_DEPTH+1-1)
+#define VTA_INSN_GEM_6_1 (VTA_INSN_GEM_6_0 + VTA_LOG_UOP_BUFF_DEPTH + 1 - 1)
 /*! GEMM instruction start position of the iter_out field */
-#define INSN_GEM_7_0 (INSN_GEM_6_1+1)
+#define VTA_INSN_GEM_7_0 (VTA_INSN_GEM_6_1 + 1)
 /*! GEMM instruction end position of the iter_out field */
-#define INSN_GEM_7_1 (INSN_GEM_7_0+LOOP_ITER_WIDTH-1)
+#define VTA_INSN_GEM_7_1 (VTA_INSN_GEM_7_0 + VTA_LOOP_ITER_WIDTH - 1)
 /*! GEMM instruction start position of the iter_in field */
-#define INSN_GEM_8_0 (INSN_GEM_7_1+1)
+#define VTA_INSN_GEM_8_0 (VTA_INSN_GEM_7_1 + 1)
 /*! GEMM instruction end position of the iter_in field */
-#define INSN_GEM_8_1 (INSN_GEM_8_0+LOOP_ITER_WIDTH-1)
+#define VTA_INSN_GEM_8_1 (VTA_INSN_GEM_8_0 + VTA_LOOP_ITER_WIDTH - 1)
 /*! GEMM instruction start position of the dst_factor_out field */
-#define INSN_GEM_9_0 64
+#define VTA_INSN_GEM_9_0 64
 /*! GEMM instruction end position of the dst_factor_out field */
-#define INSN_GEM_9_1 (INSN_GEM_9_0+LOG_ACC_BUFF_DEPTH-1)
+#define VTA_INSN_GEM_9_1 (VTA_INSN_GEM_9_0 + VTA_LOG_ACC_BUFF_DEPTH - 1)
 /*! GEMM instruction start position of the dst_factor_in field */
-#define INSN_GEM_A_0 (INSN_GEM_9_1+1)
+#define VTA_INSN_GEM_A_0 (VTA_INSN_GEM_9_1 + 1)
 /*! GEMM instruction end position of the dst_factor_in field */
-#define INSN_GEM_A_1 (INSN_GEM_A_0+LOG_ACC_BUFF_DEPTH-1)
+#define VTA_INSN_GEM_A_1 (VTA_INSN_GEM_A_0 + VTA_LOG_ACC_BUFF_DEPTH - 1)
 /*! GEMM instruction start position of the src_factor_out field */
-#define INSN_GEM_B_0 (INSN_GEM_A_1+1)
+#define VTA_INSN_GEM_B_0 (VTA_INSN_GEM_A_1 + 1)
 /*! GEMM instruction end position of the src_factor_out field */
-#define INSN_GEM_B_1 (INSN_GEM_B_0+LOG_ACC_BUFF_DEPTH-1)
+#define VTA_INSN_GEM_B_1 (VTA_INSN_GEM_B_0 + VTA_LOG_ACC_BUFF_DEPTH - 1)
 /*! GEMM instruction start position of the src_factor_in field */
-#define INSN_GEM_C_0 (INSN_GEM_B_1+1)
+#define VTA_INSN_GEM_C_0 (VTA_INSN_GEM_B_1 + 1)
 /*! GEMM instruction end position of the src_factor_in field */
-#define INSN_GEM_C_1 (INSN_GEM_C_0+LOG_ACC_BUFF_DEPTH-1)
+#define VTA_INSN_GEM_C_1 (VTA_INSN_GEM_C_0 + VTA_LOG_ACC_BUFF_DEPTH - 1)
 
 /*! GEMM instruction start position of the wgt_factor_out field */
-#define INSN_GEM_D_0 (INSN_GEM_C_1+1)
+#define VTA_INSN_GEM_D_0 (VTA_INSN_GEM_C_1 + 1)
 /*! GEMM instruction end position of the wgt_factor_out field */
-#define INSN_GEM_D_1 (INSN_GEM_D_0+LOG_WGT_BUFF_DEPTH-1)
+#define VTA_INSN_GEM_D_1 (VTA_INSN_GEM_D_0 + VTA_LOG_WGT_BUFF_DEPTH - 1)
 /*! GEMM instruction start position of the wgt_factor_in field */
-#define INSN_GEM_E_0 (INSN_GEM_D_1+1)
+#define VTA_INSN_GEM_E_0 (VTA_INSN_GEM_D_1 + 1)
 /*! GEMM instruction end position of the wgt_factor_in field */
-#define INSN_GEM_E_1 (INSN_GEM_E_0+LOG_WGT_BUFF_DEPTH-1)
+#define VTA_INSN_GEM_E_1 (VTA_INSN_GEM_E_0 + VTA_LOG_WGT_BUFF_DEPTH - 1)
 
 /*! ALU instruction start position of the alu_opcode field */
-#define INSN_ALU_D_0 (INSN_GEM_C_1+1)
+#define VTA_INSN_ALU_D_0 (VTA_INSN_GEM_C_1 + 1)
 /*! ALU instruction end position of the alu_opcode field */
-#define INSN_ALU_D_1 (INSN_ALU_D_0+ALU_OPCODE_BIT_WIDTH-1)
+#define VTA_INSN_ALU_D_1 (VTA_INSN_ALU_D_0 + VTA_ALU_OPCODE_BIT_WIDTH - 1)
 /*! ALU instruction position of the use_imm field */
-#define INSN_ALU_E   (INSN_ALU_D_1+1)
+#define VTA_INSN_ALU_E   (VTA_INSN_ALU_D_1 + 1)
 /*! ALU instruction start position of the immediate field */
-#define INSN_ALU_F_0 (INSN_ALU_E+1)
+#define VTA_INSN_ALU_F_0 (VTA_INSN_ALU_E + 1)
 /*! ALU instruction end position of the immediate field */
-#define INSN_ALU_F_1 (INSN_ALU_F_0+ALUOP_IMM_BIT_WIDTH-1)
+#define VTA_INSN_ALU_F_1 (VTA_INSN_ALU_F_0 + VTA_ALUOP_IMM_BIT_WIDTH - 1)
 
 /*! GEMM Micro-op position of the reset_out field */
-#define UOP_GEM_0 0
+#define VTA_UOP_GEM_0 0
 /*! GEMM Micro-op start position of the acc_idx field */
-#define UOP_GEM_1_0 (UOP_GEM_0+1)
+#define VTA_UOP_GEM_1_0 (VTA_UOP_GEM_0 + 1)
 /*! GEMM Micro-op end position of the acc_idx field */
-#define UOP_GEM_1_1 (UOP_GEM_1_0+LOG_ACC_BUFF_DEPTH-1)
+#define VTA_UOP_GEM_1_1 (VTA_UOP_GEM_1_0 + VTA_LOG_ACC_BUFF_DEPTH - 1)
 /*! GEMM Micro-op start position of the inp_idx field */
-#define UOP_GEM_2_0 (UOP_GEM_1_1+1)
+#define VTA_UOP_GEM_2_0 (VTA_UOP_GEM_1_1 + 1)
 /*! GEMM Micro-op end position of the inp_idx field */
-#define UOP_GEM_2_1 (UOP_GEM_2_0+LOG_ACC_BUFF_DEPTH-1)
+#define VTA_UOP_GEM_2_1 (VTA_UOP_GEM_2_0 + VTA_LOG_ACC_BUFF_DEPTH - 1)
 /*! GEMM Micro-op start position of the wgt_idx field */
-#define UOP_GEM_3_0 (UOP_GEM_2_1+1)
+#define VTA_UOP_GEM_3_0 (VTA_UOP_GEM_2_1 + 1)
 /*! GEMM Micro-op end position of the wgt_idx field */
-#define UOP_GEM_3_1 (UOP_GEM_3_0+LOG_WGT_BUFF_DEPTH-1)
+#define VTA_UOP_GEM_3_1 (VTA_UOP_GEM_3_0 + VTA_LOG_WGT_BUFF_DEPTH - 1)
 
 /*! GEMM Micro-op position of the reset_out field */
-#define UOP_ALU_0 0
+#define VTA_UOP_ALU_0 0
 /*! GEMM Micro-op start position of the acc_idx field */
-#define UOP_ALU_1_0 (UOP_ALU_0+1)
+#define VTA_UOP_ALU_1_0 (VTA_UOP_ALU_0 + 1)
 /*! GEMM Micro-op end position of the acc_idx field */
-#define UOP_ALU_1_1 (UOP_ALU_1_0+LOG_ACC_BUFF_DEPTH-1)
+#define VTA_UOP_ALU_1_1 (VTA_UOP_ALU_1_0 + VTA_LOG_ACC_BUFF_DEPTH - 1)
 /*! GEMM Micro-op start position of the inp_idx field */
-#define UOP_ALU_2_0 (UOP_ALU_1_1+1)
+#define VTA_UOP_ALU_2_0 (VTA_UOP_ALU_1_1 + 1)
 /*! GEMM Micro-op end position of the inp_idx field */
-#define UOP_ALU_2_1 (UOP_ALU_2_0+LOG_ACC_BUFF_DEPTH-1)
+#define VTA_UOP_ALU_2_1 (VTA_UOP_ALU_2_0 + VTA_LOG_ACC_BUFF_DEPTH - 1)
 /*! GEMM Micro-op start position of the wgt_idx field */
-#define UOP_ALU_3_0 (UOP_ALU_2_1+1)
+#define VTA_UOP_ALU_3_0 (VTA_UOP_ALU_2_1 + 1)
 /*! GEMM Micro-op end position of the wgt_idx field */
-#define UOP_ALU_3_1 (UOP_ALU_3_0+LOG_WGT_BUFF_DEPTH-1)
+#define VTA_UOP_ALU_3_1 (VTA_UOP_ALU_3_0 + VTA_LOG_WGT_BUFF_DEPTH - 1)
 
 /*! \brief VTA generic instruction */
 typedef struct {
@@ -382,7 +385,7 @@ typedef struct {
 */
 typedef struct {
   /*! \brief The instruction opcode */
-  uint64_t opcode         : OPCODE_BIT_WIDTH;
+  uint64_t opcode         : VTA_OPCODE_BIT_WIDTH;
   /*! \brief Unused in this instruction */
   uint64_t pop_prev_dep   : 1;
   /*! \brief Pop dependence token from GEMM stage */
@@ -392,25 +395,25 @@ typedef struct {
   /*! \brief Push dependence token to GEMM stage */
   uint64_t push_next_dep  : 1;
   /*! \brief Source/destination SRAM for store/load instruction */
-  uint64_t memory_type    : MEMOP_ID_BIT_WIDTH;
+  uint64_t memory_type    : VTA_MEMOP_ID_BIT_WIDTH;
   /*! \brief SRAM base address (pointer to memory elem type) */
-  uint64_t sram_base      : MEMOP_SRAM_ADDR_BIT_WIDTH;
+  uint64_t sram_base      : VTA_MEMOP_SRAM_ADDR_BIT_WIDTH;
   /*! \brief DRAM base address (pointer to memory elem type) */
-  uint64_t dram_base      : MEMOP_DRAM_ADDR_BIT_WIDTH;
+  uint64_t dram_base      : VTA_MEMOP_DRAM_ADDR_BIT_WIDTH;
   /*! \brief 2D access pattern: y-size */
-  uint64_t y_size         : MEMOP_SIZE_BIT_WIDTH;
+  uint64_t y_size         : VTA_MEMOP_SIZE_BIT_WIDTH;
   /*! \brief 2D access pattern: x-size (in terms of memory elements) */
-  uint64_t x_size         : MEMOP_SIZE_BIT_WIDTH;
+  uint64_t x_size         : VTA_MEMOP_SIZE_BIT_WIDTH;
   /*! \brief 2D access pattern: x-stride (in terms of memory elements) */
-  uint64_t x_stride       : MEMOP_STRIDE_BIT_WIDTH;
+  uint64_t x_stride       : VTA_MEMOP_STRIDE_BIT_WIDTH;
   /*! \brief 2D access pattern: start padding along y dimension */
-  uint64_t y_pad_0        : MEMOP_PAD_BIT_WIDTH;
+  uint64_t y_pad_0        : VTA_MEMOP_PAD_BIT_WIDTH;
   /*! \brief 2D access pattern: end padding along y dimension */
-  uint64_t y_pad_1        : MEMOP_PAD_BIT_WIDTH;
+  uint64_t y_pad_1        : VTA_MEMOP_PAD_BIT_WIDTH;
   /*! \brief 2D access pattern: start padding along x dimension */
-  uint64_t x_pad_0        : MEMOP_PAD_BIT_WIDTH;
+  uint64_t x_pad_0        : VTA_MEMOP_PAD_BIT_WIDTH;
   /*! \brief 2D access pattern: end padding along x dimension */
-  uint64_t x_pad_1        : MEMOP_PAD_BIT_WIDTH;
+  uint64_t x_pad_1        : VTA_MEMOP_PAD_BIT_WIDTH;
 } VTAMemInsn;
 
 /*! \brief VTA GEMM instruction
@@ -442,7 +445,7 @@ typedef struct {
 */
 typedef struct {
   /*! \brief The instruction opcode */
-  uint64_t opcode         : OPCODE_BIT_WIDTH;
+  uint64_t opcode         : VTA_OPCODE_BIT_WIDTH;
   /*! \brief Pop dependence token from load stage */
   uint64_t pop_prev_dep   : 1;
   /*! \brief Pop dependence token from store stage */
@@ -452,25 +455,25 @@ typedef struct {
   /*! \brief Push dependence token to store stage */
   uint64_t push_next_dep  : 1;
   /*! \brief Micro-op begin address */
-  uint64_t uop_bgn        : LOG_UOP_BUFF_DEPTH;
+  uint64_t uop_bgn        : VTA_LOG_UOP_BUFF_DEPTH;
   /*! \brief Micro-op end address */
-  uint64_t uop_end        : LOG_UOP_BUFF_DEPTH+1;
+  uint64_t uop_end        : VTA_LOG_UOP_BUFF_DEPTH+1;
   /*! \brief Iterations in the outer uop execution loop */
-  uint64_t iter_out       : LOOP_ITER_WIDTH;
+  uint64_t iter_out       : VTA_LOOP_ITER_WIDTH;
   /*! \brief Iterations in the inner uop execution loop */
-  uint64_t iter_in        : LOOP_ITER_WIDTH;
+  uint64_t iter_in        : VTA_LOOP_ITER_WIDTH;
   /*! \brief Outer loop accumulator memory index factor */
-  uint64_t dst_factor_out : LOG_ACC_BUFF_DEPTH;
+  uint64_t dst_factor_out : VTA_LOG_ACC_BUFF_DEPTH;
   /*! \brief Inner loop accumulator memory index factor */
-  uint64_t dst_factor_in  : LOG_ACC_BUFF_DEPTH;
+  uint64_t dst_factor_in  : VTA_LOG_ACC_BUFF_DEPTH;
   /*! \brief Outer loop input memory index factor */
-  uint64_t src_factor_out : LOG_ACC_BUFF_DEPTH;
+  uint64_t src_factor_out : VTA_LOG_ACC_BUFF_DEPTH;
   /*! \brief Inner loop input memory index factor */
-  uint64_t src_factor_in  : LOG_ACC_BUFF_DEPTH;
+  uint64_t src_factor_in  : VTA_LOG_ACC_BUFF_DEPTH;
   /*! \brief Outer loop weight memory index factor */
-  uint64_t wgt_factor_out : LOG_WGT_BUFF_DEPTH;
+  uint64_t wgt_factor_out : VTA_LOG_WGT_BUFF_DEPTH;
   /*! \brief Inner loop weight memory index factor */
-  uint64_t wgt_factor_in  : LOG_WGT_BUFF_DEPTH;
+  uint64_t wgt_factor_in  : VTA_LOG_WGT_BUFF_DEPTH;
 } VTAGemInsn;
 
 /*! \brief VTA ALU instruction
@@ -504,7 +507,7 @@ typedef struct {
 */
 typedef struct {
   /*! \brief The instruction opcode */
-  uint64_t opcode         : OPCODE_BIT_WIDTH;
+  uint64_t opcode         : VTA_OPCODE_BIT_WIDTH;
   /*! \brief Pop dependence token from load stage */
   uint64_t pop_prev_dep   : 1;
   /*! \brief Pop dependence token from store stage */
@@ -514,27 +517,27 @@ typedef struct {
   /*! \brief Push dependence token to store stage */
   uint64_t push_next_dep  : 1;
   /*! \brief Micro-op begin address */
-  uint64_t uop_bgn        : LOG_UOP_BUFF_DEPTH;
+  uint64_t uop_bgn        : VTA_LOG_UOP_BUFF_DEPTH;
   /*! \brief Micro-op end address */
-  uint64_t uop_end        : LOG_UOP_BUFF_DEPTH+1;
+  uint64_t uop_end        : VTA_LOG_UOP_BUFF_DEPTH+1;
   /*! \brief Iterations in the outer uop execution loop */
-  uint64_t iter_out       : LOOP_ITER_WIDTH;
+  uint64_t iter_out       : VTA_LOOP_ITER_WIDTH;
   /*! \brief Iterations in the inner uop execution loop */
-  uint64_t iter_in        : LOOP_ITER_WIDTH;
+  uint64_t iter_in        : VTA_LOOP_ITER_WIDTH;
   /*! \brief Outer loop accumulator memory destination index factor */
-  uint64_t dst_factor_out : LOG_ACC_BUFF_DEPTH;
+  uint64_t dst_factor_out : VTA_LOG_ACC_BUFF_DEPTH;
   /*! \brief Inner loop accumulator memory destination index factor */
-  uint64_t dst_factor_in  : LOG_ACC_BUFF_DEPTH;
+  uint64_t dst_factor_in  : VTA_LOG_ACC_BUFF_DEPTH;
   /*! \brief Outer loop accumulator memory source index factor */
-  uint64_t src_factor_out : LOG_ACC_BUFF_DEPTH;
+  uint64_t src_factor_out : VTA_LOG_ACC_BUFF_DEPTH;
   /*! \brief Inner loop accumulator memory source index factor */
-  uint64_t src_factor_in  : LOG_ACC_BUFF_DEPTH;
+  uint64_t src_factor_in  : VTA_LOG_ACC_BUFF_DEPTH;
   /*! \brief ALU opcode */
-  uint64_t alu_opcode     : ALU_OPCODE_BIT_WIDTH;
+  uint64_t alu_opcode     : VTA_ALU_OPCODE_BIT_WIDTH;
   /*! \brief Use immediate is true */
   uint64_t use_imm        : 1;
   /*! \brief Immediate value */
-  uint64_t imm            : ALUOP_IMM_BIT_WIDTH;
+  uint64_t imm            : VTA_ALUOP_IMM_BIT_WIDTH;
 } VTAAluInsn;
 
 /*! \brief VTA ALU instruction converter */
@@ -554,14 +557,14 @@ typedef struct {
   /*! \brief Initialize acc_mem at index dst_idx to 0*/
   uint32_t reset_out  : 1;
   /*! \brief Destination index (indexes accum buffer) */
-  uint32_t dst_idx    : LOG_ACC_BUFF_DEPTH;
+  uint32_t dst_idx    : VTA_LOG_ACC_BUFF_DEPTH;
   /*! \brief Source index (indexes input buffer for GEMM or accum buffer for ALU) */
-  uint32_t src_idx    : LOG_ACC_BUFF_DEPTH;
+  uint32_t src_idx    : VTA_LOG_ACC_BUFF_DEPTH;
   /*! \brief Weight index (indexes weight buffer) */
-  uint32_t wgt_idx    : LOG_WGT_BUFF_DEPTH;
+  uint32_t wgt_idx    : VTA_LOG_WGT_BUFF_DEPTH;
 } VTAUop;
 
 #ifdef __cplusplus
 }
 #endif
-#endif // VTA_HW_SPEC_H_
+#endif  // VTA_HW_SPEC_H_
diff --git a/vta/make/config.mk b/vta/make/config.mk
index 8142571a1cca..06143d777167 100644
--- a/vta/make/config.mk
+++ b/vta/make/config.mk
@@ -27,70 +27,72 @@ ADD_LDFLAGS=
 ADD_CFLAGS=
 
 # the hardware target
-TARGET=PYNQ_TARGET
+TARGET = VTA_PYNQ_TARGET
 
 #---------------------
 # VTA hardware parameters
 #--------------------
 
 #  Log of input/activation width in bits (default 3 -> 8 bits)
-LOG_INP_WIDTH = 3
+VTA_LOG_INP_WIDTH = 3
 #  Log of kernel weight width in bits (default 3 -> 8 bits)
-LOG_WGT_WIDTH = 3
+VTA_LOG_WGT_WIDTH = 3
 #  Log of accum width in bits (default 5 -> 32 bits)
-LOG_ACC_WIDTH = 5
+VTA_LOG_ACC_WIDTH = 5
 #  Log of tensor batch size (A in (A,B)x(B,C) matrix multiplication)
-LOG_BATCH = 0
+VTA_LOG_BATCH = 0
 #  Log of tensor inner block size (B in (A,B)x(B,C) matrix multiplication)
-LOG_BLOCK_IN = 4
+VTA_LOG_BLOCK_IN = 4
 #  Log of tensor outer block size (C in (A,B)x(B,C) matrix multiplication)
-LOG_BLOCK_OUT = 4
+VTA_LOG_BLOCK_OUT = 4
 #  Log of uop buffer size in Bytes
-LOG_UOP_BUFF_SIZE = 15
+VTA_LOG_UOP_BUFF_SIZE = 15
 #  Log of inp buffer size in Bytes
-LOG_INP_BUFF_SIZE = 15
+VTA_LOG_INP_BUFF_SIZE = 15
 #  Log of wgt buffer size in Bytes
-LOG_WGT_BUFF_SIZE = 15
+VTA_LOG_WGT_BUFF_SIZE = 15
 #  Log of acc buffer size in Bytes
-LOG_ACC_BUFF_SIZE = 17
+VTA_LOG_ACC_BUFF_SIZE = 17
 
 #---------------------
 # Derived VTA hardware parameters
 #--------------------
 
 #  Input width in bits
-INP_WIDTH = $(shell echo "$$(( 1 << $(LOG_INP_WIDTH) ))" )
+VTA_INP_WIDTH = $(shell echo "$$(( 1 << $(VTA_LOG_INP_WIDTH) ))" )
 #  Weight width in bits
-WGT_WIDTH = $(shell echo "$$(( 1 << $(LOG_WGT_WIDTH) ))" )
+VTA_WGT_WIDTH = $(shell echo "$$(( 1 << $(VTA_LOG_WGT_WIDTH) ))" )
 #  Log of output width in bits
-LOG_OUT_WIDTH = $(LOG_INP_WIDTH)
+VTA_LOG_OUT_WIDTH = $(VTA_LOG_INP_WIDTH)
 #  Output width in bits
-OUT_WIDTH = $(shell echo "$$(( 1 << $(LOG_OUT_WIDTH) ))" )
+VTA_OUT_WIDTH = $(shell echo "$$(( 1 << $(VTA_LOG_OUT_WIDTH) ))" )
 #  Tensor batch size
-BATCH = $(shell echo "$$(( 1 << $(LOG_BATCH) ))" )
+VTA_BATCH = $(shell echo "$$(( 1 << $(VTA_LOG_BATCH) ))" )
 #  Tensor outer block size
-IN_BLOCK = $(shell echo "$$(( 1 << $(LOG_BLOCK_IN) ))" )
+VTA_IN_BLOCK = $(shell echo "$$(( 1 << $(VTA_LOG_BLOCK_IN) ))" )
 #  Tensor inner block size
-OUT_BLOCK = $(shell echo "$$(( 1 << $(LOG_BLOCK_OUT) ))" )
+VTA_OUT_BLOCK = $(shell echo "$$(( 1 << $(VTA_LOG_BLOCK_OUT) ))" )
 #  Uop buffer size in Bytes
-UOP_BUFF_SIZE = $(shell echo "$$(( 1 << $(LOG_UOP_BUFF_SIZE) ))" )
+VTA_UOP_BUFF_SIZE = $(shell echo "$$(( 1 << $(VTA_LOG_UOP_BUFF_SIZE) ))" )
 #  Inp buffer size in Bytes
-INP_BUFF_SIZE = $(shell echo "$$(( 1 << $(LOG_INP_BUFF_SIZE) ))" )
+VTA_INP_BUFF_SIZE = $(shell echo "$$(( 1 << $(VTA_LOG_INP_BUFF_SIZE) ))" )
 #  Wgt buffer size in Bytes
-WGT_BUFF_SIZE = $(shell echo "$$(( 1 << $(LOG_WGT_BUFF_SIZE) ))" )
+VTA_WGT_BUFF_SIZE = $(shell echo "$$(( 1 << $(VTA_LOG_WGT_BUFF_SIZE) ))" )
 #  Acc buffer size in Bytes
-ACC_BUFF_SIZE = $(shell echo "$$(( 1 << $(LOG_ACC_BUFF_SIZE) ))" )
+VTA_ACC_BUFF_SIZE = $(shell echo "$$(( 1 << $(VTA_LOG_ACC_BUFF_SIZE) ))" )
 #  Log of out buffer size in Bytes
-LOG_OUT_BUFF_SIZE = $(shell echo "$$(( $(LOG_ACC_BUFF_SIZE)+$(LOG_OUT_WIDTH)-$(LOG_ACC_WIDTH) ))" )
+VTA_LOG_OUT_BUFF_SIZE = \
+$(shell echo "$$(( $(VTA_LOG_ACC_BUFF_SIZE) + $(VTA_LOG_OUT_WIDTH) - $(VTA_LOG_ACC_WIDTH) ))" )
 #  Out buffer size in Bytes
-OUT_BUFF_SIZE = $(shell echo "$$(( 1 << $(LOG_OUT_BUFF_SIZE) ))" )
+VTA_OUT_BUFF_SIZE = $(shell echo "$$(( 1 << $(LOG_OUT_BUFF_SIZE) ))" )
 
 # Update ADD_CFLAGS
 ADD_CFLAGS += \
 	-D$(TARGET) \
-	-DLOG_WGT_WIDTH=$(LOG_WGT_WIDTH) -DLOG_INP_WIDTH=$(LOG_INP_WIDTH) \
-	-DLOG_ACC_WIDTH=$(LOG_ACC_WIDTH) -DLOG_OUT_WIDTH=$(LOG_OUT_WIDTH) \
-	-DLOG_BATCH=$(LOG_BATCH) -DLOG_BLOCK_IN=$(LOG_BLOCK_IN) -DLOG_BLOCK_OUT=$(LOG_BLOCK_OUT) \
-	-DLOG_UOP_BUFF_SIZE=$(LOG_UOP_BUFF_SIZE) -DLOG_INP_BUFF_SIZE=$(LOG_INP_BUFF_SIZE) \
-	-DLOG_WGT_BUFF_SIZE=$(LOG_WGT_BUFF_SIZE) -DLOG_ACC_BUFF_SIZE=$(LOG_ACC_BUFF_SIZE) \
-	-DLOG_OUT_BUFF_SIZE=$(LOG_OUT_BUFF_SIZE)
\ No newline at end of file
+	-DVTA_LOG_WGT_WIDTH=$(VTA_LOG_WGT_WIDTH) -DVTA_LOG_INP_WIDTH=$(VTA_LOG_INP_WIDTH) \
+	-DVTA_LOG_ACC_WIDTH=$(VTA_LOG_ACC_WIDTH) -DVTA_LOG_OUT_WIDTH=$(VTA_LOG_OUT_WIDTH) \
+	-DVTA_LOG_BATCH=$(VTA_LOG_BATCH) \
+	-DVTA_LOG_BLOCK_IN=$(VTA_LOG_BLOCK_IN) -DVTA_LOG_BLOCK_OUT=$(VTA_LOG_BLOCK_OUT) \
+	-DVTA_LOG_UOP_BUFF_SIZE=$(VTA_LOG_UOP_BUFF_SIZE) -DVTA_LOG_INP_BUFF_SIZE=$(VTA_LOG_INP_BUFF_SIZE) \
+	-DVTA_LOG_WGT_BUFF_SIZE=$(VTA_LOG_WGT_BUFF_SIZE) -DVTA_LOG_ACC_BUFF_SIZE=$(VTA_LOG_ACC_BUFF_SIZE) \
+	-DVTA_LOG_OUT_BUFF_SIZE=$(VTA_LOG_OUT_BUFF_SIZE)
diff --git a/vta/src/pynq/pynq_driver.cc b/vta/src/pynq/pynq_driver.cc
index b4f78db0c160..1787af8da526 100644
--- a/vta/src/pynq/pynq_driver.cc
+++ b/vta/src/pynq/pynq_driver.cc
@@ -29,65 +29,61 @@ void VTAInvalidateCache(void* buf, int size) {
 }
 
 void *VTAMapRegister(uint32_t addr, size_t length) {
-
   // Align the base address with the pages
   uint32_t virt_base = addr & ~(getpagesize() - 1);
   // Calculate base address offset w.r.t the base address
   uint32_t virt_offset = addr - virt_base;
   // Open file and mmap
-  uint32_t mmap_file = open(DEV_MEM_PATH, O_RDWR|O_SYNC);
-
-  return mmap(NULL, (length+virt_offset), PROT_READ|PROT_WRITE, MAP_SHARED, mmap_file, virt_base);
+  uint32_t mmap_file = open(VTA_PYNQ_DEV_MEM_PATH, O_RDWR|O_SYNC);
+  return mmap(NULL,
+              (length+virt_offset),
+              PROT_READ|PROT_WRITE,
+              MAP_SHARED,
+              mmap_file,
+              virt_base);
 }
 
 void VTAUnmapRegister(void *vta, size_t length) {
   // Unmap memory
   int status = munmap(vta, length);
-  assert(status==0);
+  assert(status == 0);
 }
 
 void VTAWriteMappedReg(void* base_addr, uint32_t offset, uint32_t val) {
-  *((volatile uint32_t *) (((char *) base_addr) + offset)) = val;
+  *((volatile uint32_t *) (reinterpret_cast<char *>(base_addr) + offset)) = val;
 }
 
 uint32_t VTAReadMappedReg(void* base_addr, uint32_t offset) {
-  return *((volatile uint32_t *) (((char *) base_addr) + offset));
+  return *((volatile uint32_t *) (reinterpret_cast<char *>(base_addr) + offset));
 }
 
 void VTAProgram(const char* bitstream) {
-
     int elem;
     FILE *src, *dst, *partial;
-
-    partial = fopen(BS_IS_PARTIAL, "w");
+    partial = fopen(VTA_PYNQ_BS_IS_PARTIAL, "w");
     if (partial == NULL) {
-        printf("Cannot open partial config file %s\n", BS_IS_PARTIAL);
+        printf("Cannot open partial config file %s\n", VTA_PYNQ_BS_IS_PARTIAL);
         fclose(partial);
         exit(1);
     }
     fputc('0', partial);
     fclose(partial);
-
     src = fopen(bitstream, "rb");
     if (src == NULL) {
         printf("Cannot open bitstream %s\n", bitstream);
         exit(1);
     }
-
-    dst = fopen(BS_XDEVCFG, "wb");
+    dst = fopen(VTA_PYNQ_BS_XDEVCFG, "wb");
     if (dst == NULL) {
-        printf("Cannot open device file %s\n", BS_XDEVCFG);
+        printf("Cannot open device file %s\n", VTA_PYNQ_BS_XDEVCFG);
         fclose(dst);
         exit(1);
     }
-
     elem = fgetc(src);
     while (elem != EOF) {
         fputc(elem, dst);
         elem = fgetc(src);
     }
-
     fclose(src);
     fclose(dst);
-
-}
\ No newline at end of file
+}
diff --git a/vta/src/pynq/pynq_driver.h b/vta/src/pynq/pynq_driver.h
index 9e9482822a21..952c4cff8c59 100644
--- a/vta/src/pynq/pynq_driver.h
+++ b/vta/src/pynq/pynq_driver.h
@@ -4,8 +4,8 @@
  * \brief VTA driver for Pynq board.
  */
 
-#ifndef VTA_PYNQ_DRIVER_H_
-#define VTA_PYNQ_DRIVER_H_
+#ifndef VTA_PYNQ_PYNQ_DRIVER_H_
+#define VTA_PYNQ_PYNQ_DRIVER_H_
 
 #ifdef __cplusplus
 extern "C" {
@@ -32,17 +32,20 @@ void xlnkFlushCache(void* buf, int size);
 void xlnkInvalidateCache(void* buf, int size);
 #endif
 
-/*! \brief partial bitstream status file path */
-#define BS_IS_PARTIAL "/sys/devices/soc0/amba/f8007000.devcfg/is_partial_bitstream"
-/*! \brief bitstream destination file path */
-#define BS_XDEVCFG "/dev/xdevcfg"
+/*! \brief (Pynq only) Partial bitstream status file path */
+#define VTA_PYNQ_BS_IS_PARTIAL "/sys/devices/soc0/amba/f8007000.devcfg/is_partial_bitstream"
+/*! \brief (Pynq only) Bitstream destination file path */
+#define VTA_PYNQ_BS_XDEVCFG "/dev/xdevcfg"
 
-/*! \brief Path to /dev/mem */
-#define DEV_MEM_PATH "/dev/mem"
-/*! \brief MMIO driver constant */
-#define MMIO_WORD_LENGTH 4
-/*! \brief MMIO driver constant */
-#define MMIO_WORD_MASK (~(MMIO_WORD_LENGTH - 1))
+/*! \brief (Pynq only) Path to /dev/mem */
+#define VTA_PYNQ_DEV_MEM_PATH "/dev/mem"
+/*! \brief (Pynq only) MMIO driver constant */
+#define VTA_PYNQ_MMIO_WORD_LENGTH 4
+/*! \brief (Pynq only) MMIO driver constant */
+#define VTA_PYNQ_MMIO_WORD_MASK (~(MMIO_WORD_LENGTH - 1))
+
+/*! \brief Physically contiguous buffer size limit */
+#define VTA_MAX_XFER (1<<22)
 
 /*! \brief VTA configuration register address range */
 #define VTA_RANGE 0x100
@@ -74,10 +77,7 @@ void xlnkInvalidateCache(void* buf, int size);
 */
 #define VTA_STORE_ADDR    0x43C30000
 
-/*! \brief Buffer size limit */
-#define MAX_XFER (1<<22)
-
 #ifdef __cplusplus
 }
 #endif
-#endif  // VTA_PYNQ_DRIVER_H_
\ No newline at end of file
+#endif  // VTA_PYNQ_PYNQ_DRIVER_H_
\ No newline at end of file
diff --git a/vta/src/runtime.cc b/vta/src/runtime.cc
index 570816e5236a..dde88e8cc829 100644
--- a/vta/src/runtime.cc
+++ b/vta/src/runtime.cc
@@ -4,19 +4,20 @@
  * \brief VTA runtime for PYNQ in C++11
  */
 
+#ifdef VTA_PYNQ_TARGET
+#include "./pynq/pynq_driver.h"
+#endif  // VTA_PYNQ_TARGET
+
+#include <vta/driver.h>
+#include <vta/hw_spec.h>
+#include <vta/runtime.h>
+
 #include <cassert>
 #include <cstring>
 #include <vector>
 #include <thread>
 #include <memory>
 #include <atomic>
-#include <vta/driver.h>
-#include <vta/hw_spec.h>
-#include <vta/runtime.h>
-
-#ifdef PYNQ_TARGET
-#include "./pynq/pynq_driver.h"
-#endif //PYNQ_TARGET
 
 namespace vta {
 
@@ -193,21 +194,21 @@ class UopKernel {
     op.wgt_idx = wgt_index;
     seq_.push_back(op);
     // Ensure that mode is consistent if set
-    if (mode_==0xFFFFFFFF) {
+    if (mode_ == 0xFFFFFFFF) {
       mode_ = mode;
     } else {
-      assert(mode_==mode);
+      assert(mode_ == mode);
     }
     // Check kernel op and imm/imm_val in ALU mode
-    if (mode==1) {
-      if (opcode_==0xFFFFFFFF) {
-        opcode_=opcode;
-        use_imm_=use_imm;
-        imm_val_=imm_val;
+    if (mode == 1) {
+      if (opcode_ == 0xFFFFFFFF) {
+        opcode_ = opcode;
+        use_imm_ = use_imm;
+        imm_val_ = imm_val;
       } else {
-        assert(opcode_==opcode);
-        assert(use_imm_==use_imm);
-        assert(imm_val_==imm_val);
+        assert(opcode_ == opcode);
+        assert(use_imm_ == use_imm);
+        assert(imm_val_ == imm_val);
       }
     }
   }
@@ -222,17 +223,17 @@ class UopKernel {
              seq_[i].src_idx,
              seq_[i].wgt_idx,
              seq_[i].reset_out);
-
     }
     printf("\n");
   }
 
  public:
   // The kernel's mode, opcode, immediate setting and value
-  uint32_t mode_{0xFFFFFFFF}; // UOP type: 0xFFFFFFFF - unset, 0 - GEMM, 1 - ALU
+  uint32_t mode_{0xFFFFFFFF};  // UOP type: 0xFFFFFFFF - unset, 0 - GEMM, 1 - ALU
   uint32_t opcode_{0xFFFFFFFF};
   bool use_imm_{false};
   uint16_t imm_val_{0};
+
  private:
   // Verify that we don't write to the same acc_mem index two cycles in a row
   void VerifyDep(uint32_t dst_index) {
@@ -375,7 +376,7 @@ class UopQueue : public BaseQueue {
     }
     // Simple eviction policy
     uint32_t evict_begin = cache_ptr_;
-    for (;cache_ptr_ < cache_.size(); ++cache_ptr_) {
+    for (; cache_ptr_ < cache_.size(); ++cache_ptr_) {
       if (cache_[cache_ptr_]->sram_begin_ >= sram_end_) break;
       cache_[cache_ptr_]->sram_begin_ = 0;
       cache_[cache_ptr_]->sram_end_ = 0;
@@ -395,7 +396,7 @@ class UopQueue : public BaseQueue {
   void FlushUopLoad(VTAMemInsn* insn) {
     if (sram_begin_ != sram_end_) {
       assert((dram_end_ - dram_begin_) == (sram_end_ - sram_begin_));
-      insn->memory_type = MEM_ID_UOP;
+      insn->memory_type = VTA_MEM_ID_UOP;
       insn->sram_base = sram_begin_;
       insn->dram_base = dram_phy_addr_ / kElemBytes + dram_begin_;
       insn->y_size = 1;
@@ -418,7 +419,7 @@ class UopQueue : public BaseQueue {
   std::vector<UopKernel*> cache_;
   // Constants
   static constexpr int kElemBytes = sizeof(VTAUop);
-  static constexpr int kMaxNumUop = UOP_BUFF_DEPTH;
+  static constexpr int kMaxNumUop = VTA_UOP_BUFF_DEPTH;
   static constexpr int kMaxElems = kMaxBytes / kElemBytes;
 };
 
@@ -541,22 +542,22 @@ class InsnQueue : public BaseQueue {
     for (int i = 1; i < insn_count; ++i) {
       PipelineStage prev = GetPipelineStage(mem_ptr + i - 1);
       PipelineStage now = GetPipelineStage(mem_ptr + i);
-      if (prev==kLoadStage && now==kComputeStage) {
+      if (prev == kLoadStage && now == kComputeStage) {
         mem_ptr[i - 1].push_prev_dep = false;
         mem_ptr[i - 1].push_next_dep = true;
         mem_ptr[i].pop_prev_dep = true;
         mem_ptr[i].pop_next_dep = false;
-      } else if (prev==kComputeStage && now==kLoadStage) {
+      } else if (prev == kComputeStage && now == kLoadStage) {
         mem_ptr[i - 1].push_prev_dep = true;
         mem_ptr[i - 1].push_next_dep = false;
         mem_ptr[i].pop_prev_dep = false;
         mem_ptr[i].pop_next_dep = true;
-      } else if (prev==kStoreStage && now==kComputeStage) {
+      } else if (prev == kStoreStage && now == kComputeStage) {
         mem_ptr[i - 1].push_prev_dep = true;
         mem_ptr[i - 1].push_next_dep = false;
         mem_ptr[i].pop_prev_dep = false;
         mem_ptr[i].pop_next_dep = true;
-      } else if (prev==kComputeStage && now==kStoreStage) {
+      } else if (prev == kComputeStage && now == kStoreStage) {
         mem_ptr[i - 1].push_prev_dep = false;
         mem_ptr[i - 1].push_next_dep = true;
         mem_ptr[i].pop_prev_dep = true;
@@ -573,39 +574,39 @@ class InsnQueue : public BaseQueue {
   // Helper function: Get Opcode string
   const char* getOpcodeString(int opcode, bool use_imm) {
       // The string name
-      if (opcode==ALU_OPCODE_MIN) {
+      if (opcode == VTA_ALU_OPCODE_MIN) {
           if (use_imm) {
               return "min imm";
           } else {
               return "min";
           }
-      } else if (opcode==ALU_OPCODE_MAX) {
+      } else if (opcode == VTA_ALU_OPCODE_MAX) {
           if (use_imm) {
               return "max imm";
           } else {
               return "max";
           }
-      } else if (opcode==ALU_OPCODE_ADD) {
+      } else if (opcode == VTA_ALU_OPCODE_ADD) {
           if (use_imm) {
               return "add imm";
           } else {
               return "add";
           }
-      } else if (opcode==ALU_OPCODE_SUB) {
+      } else if (opcode == VTA_ALU_OPCODE_SUB) {
           if (use_imm) {
               return "sub imm";
           } else {
               return "sub";
           }
-      } else if (opcode==ALU_OPCODE_MUL) {
+      } else if (opcode == VTA_ALU_OPCODE_MUL) {
           if (use_imm) {
               return "mul imm";
           } else {
               return "mul";
           }
-      } else if (opcode==ALU_OPCODE_SHL) {
+      } else if (opcode == VTA_ALU_OPCODE_SHL) {
           return "shl";
-      } else if (opcode==ALU_OPCODE_SHR) {
+      } else if (opcode == VTA_ALU_OPCODE_SHR) {
           return "shr";
       }
 
@@ -629,12 +630,11 @@ class InsnQueue : public BaseQueue {
       // Fetch instruction and decode opcode
       c.generic = insn[i];
       printf("INSTRUCTION %u: ", i);
-      if (c.mem.opcode == OPCODE_LOAD || c.mem.opcode == OPCODE_STORE) {
+      if (c.mem.opcode == VTA_OPCODE_LOAD || c.mem.opcode == VTA_OPCODE_STORE) {
         if (c.mem.x_size == 0) {
-          if (c.mem.opcode == OPCODE_STORE) {
+          if (c.mem.opcode == VTA_OPCODE_STORE) {
             printf("NOP-STORE-STAGE\n");
-          }
-          else if (GetMemPipelineStage(c.mem.memory_type) == kComputeStage) {
+          } else if (GetMemPipelineStage(c.mem.memory_type) == kComputeStage) {
             printf("NOP-COMPUTE-STAGE\n");
           } else {
             printf("NOP-MEMORY-STAGE\n");
@@ -645,15 +645,15 @@ class InsnQueue : public BaseQueue {
                  static_cast<int>(c.mem.push_prev_dep),
                  static_cast<int>(c.mem.push_next_dep));
           // Count status in queues
-          if (c.mem.opcode == OPCODE_LOAD || c.mem.opcode == OPCODE_STORE) {
-            if (c.mem.opcode == OPCODE_STORE) {
+          if (c.mem.opcode == VTA_OPCODE_LOAD || c.mem.opcode == VTA_OPCODE_STORE) {
+            if (c.mem.opcode == VTA_OPCODE_STORE) {
                 assert(c.mem.pop_next_dep == false);
                 assert(c.mem.push_next_dep == false);
                 if (c.mem.pop_prev_dep) g2s_queue--;
                 if (c.mem.push_prev_dep) s2g_queue++;
-            } else if (c.mem.opcode == OPCODE_LOAD &&
-                       (c.mem.memory_type == MEM_ID_INP ||
-                        c.mem.memory_type == MEM_ID_WGT) ) {
+            } else if (c.mem.opcode == VTA_OPCODE_LOAD &&
+                       (c.mem.memory_type == VTA_MEM_ID_INP ||
+                        c.mem.memory_type == VTA_MEM_ID_WGT) ) {
                 assert(c.mem.pop_prev_dep == false);
                 assert(c.mem.push_prev_dep == false);
                 if (c.mem.pop_next_dep) g2l_queue--;
@@ -664,7 +664,7 @@ class InsnQueue : public BaseQueue {
                 if (c.mem.pop_next_dep) s2g_queue--;
                 if (c.mem.push_next_dep) g2s_queue++;
             }
-          } else if (c.mem.opcode == OPCODE_GEMM) {
+          } else if (c.mem.opcode == VTA_OPCODE_GEMM) {
             // Print instruction field information
             if (c.gemm.pop_prev_dep) l2g_queue--;
             if (c.gemm.push_prev_dep) g2l_queue++;
@@ -676,14 +676,14 @@ class InsnQueue : public BaseQueue {
           continue;
         }
         // Print instruction field information
-        if (c.mem.opcode==OPCODE_LOAD) {
+        if (c.mem.opcode == VTA_OPCODE_LOAD) {
             printf("LOAD ");
-            if (c.mem.memory_type == MEM_ID_UOP) printf("UOP\n");
-            if (c.mem.memory_type == MEM_ID_WGT) printf("WGT\n");
-            if (c.mem.memory_type == MEM_ID_INP) printf("INP\n");
-            if (c.mem.memory_type == MEM_ID_ACC) printf("ACC\n");
+            if (c.mem.memory_type == VTA_MEM_ID_UOP) printf("UOP\n");
+            if (c.mem.memory_type == VTA_MEM_ID_WGT) printf("WGT\n");
+            if (c.mem.memory_type == VTA_MEM_ID_INP) printf("INP\n");
+            if (c.mem.memory_type == VTA_MEM_ID_ACC) printf("ACC\n");
         }
-        if (c.mem.opcode==OPCODE_STORE) {
+        if (c.mem.opcode == VTA_OPCODE_STORE) {
             printf("STORE\n");
         }
         printf("\tdep - pop prev: %d, pop next: %d, push prev: %d, push next: %d\n",
@@ -703,7 +703,7 @@ class InsnQueue : public BaseQueue {
                static_cast<int>(c.mem.x_stride),
                static_cast<int>(c.mem.x_pad_0),
                static_cast<int>(c.mem.x_pad_1));
-      } else if (c.mem.opcode==OPCODE_GEMM) {
+      } else if (c.mem.opcode == VTA_OPCODE_GEMM) {
         // Print instruction field information
         printf("GEMM\n");
 
@@ -725,7 +725,7 @@ class InsnQueue : public BaseQueue {
                static_cast<int>(c.gemm.wgt_factor_in),
                static_cast<int>(c.gemm.src_factor_in),
                static_cast<int>(c.gemm.dst_factor_in));
-      } else if (c.mem.opcode == OPCODE_ALU) {
+      } else if (c.mem.opcode == VTA_OPCODE_ALU) {
         // Print instruction field information
         printf("ALU - %s\n", getOpcodeString(c.alu.alu_opcode, c.alu.use_imm));
         printf("\tdep - pop prev: %d, pop next: %d, push prev: %d, push next: %d\n",
@@ -744,20 +744,20 @@ class InsnQueue : public BaseQueue {
                static_cast<int>(c.alu.iter_in),
                static_cast<int>(c.alu.dst_factor_in),
                static_cast<int>(c.alu.src_factor_in));
-      } else if (c.mem.opcode == OPCODE_FINISH) {
+      } else if (c.mem.opcode == VTA_OPCODE_FINISH) {
         printf("FINISH\n");
       }
 
       // Count status in queues
-      if (c.mem.opcode == OPCODE_LOAD || c.mem.opcode == OPCODE_STORE) {
-        if (c.mem.opcode == OPCODE_STORE) {
+      if (c.mem.opcode == VTA_OPCODE_LOAD || c.mem.opcode == VTA_OPCODE_STORE) {
+        if (c.mem.opcode == VTA_OPCODE_STORE) {
             assert(c.mem.pop_next_dep == false);
             assert(c.mem.push_next_dep == false);
             if (c.mem.pop_prev_dep) g2s_queue--;
             if (c.mem.push_prev_dep) s2g_queue++;
-        } else if (c.mem.opcode == OPCODE_LOAD &&
-                   (c.mem.memory_type == MEM_ID_INP ||
-                    c.mem.memory_type == MEM_ID_WGT) ) {
+        } else if (c.mem.opcode == VTA_OPCODE_LOAD &&
+                   (c.mem.memory_type == VTA_MEM_ID_INP ||
+                    c.mem.memory_type == VTA_MEM_ID_WGT) ) {
             assert(c.mem.pop_prev_dep == false);
             assert(c.mem.push_prev_dep == false);
             if (c.mem.pop_next_dep) g2l_queue--;
@@ -768,8 +768,8 @@ class InsnQueue : public BaseQueue {
             if (c.mem.pop_next_dep) s2g_queue--;
             if (c.mem.push_next_dep) g2s_queue++;
         }
-      } else if (c.mem.opcode == OPCODE_GEMM ||
-                 c.mem.opcode == OPCODE_ALU) {
+      } else if (c.mem.opcode == VTA_OPCODE_GEMM ||
+                 c.mem.opcode == VTA_OPCODE_ALU) {
         // Print instruction field information
         if (c.gemm.pop_prev_dep) l2g_queue--;
         if (c.gemm.push_prev_dep) g2l_queue++;
@@ -832,23 +832,24 @@ class InsnQueue : public BaseQueue {
   }
   // Get stage of the memory
   static PipelineStage GetMemPipelineStage(int memory_type) {
-    if (memory_type == MEM_ID_ACC) return kComputeStage;
-    if (memory_type == MEM_ID_UOP) return kComputeStage;
+    if (memory_type == VTA_MEM_ID_ACC) return kComputeStage;
+    if (memory_type == VTA_MEM_ID_UOP) return kComputeStage;
     return kLoadStage;
   }
   // Get stage of the computation
   static PipelineStage GetPipelineStage(VTAMemInsn* insn) {
-    if (insn->opcode == OPCODE_GEMM) return kComputeStage;
-    if (insn->opcode == OPCODE_ALU) return kComputeStage;
-    if (insn->opcode == OPCODE_LOAD) {
+    if (insn->opcode == VTA_OPCODE_GEMM) return kComputeStage;
+    if (insn->opcode == VTA_OPCODE_ALU) return kComputeStage;
+    if (insn->opcode == VTA_OPCODE_LOAD) {
       if (insn->x_size == 0) return kNoneStage;
-      if (insn->memory_type == MEM_ID_ACC) return kComputeStage;
-      if (insn->memory_type == MEM_ID_UOP) return kComputeStage;
+      if (insn->memory_type == VTA_MEM_ID_ACC) return kComputeStage;
+      if (insn->memory_type == VTA_MEM_ID_UOP) return kComputeStage;
       return kLoadStage;
     }
-    if (insn->opcode == OPCODE_STORE) {
-      // FIXME: Right now memory_type is a 2-bit field which means that MEM_ID_OUT will appear as 0
-      //        For now we'll refrain from checking the memory_type to avoid an assertion error...
+    if (insn->opcode == VTA_OPCODE_STORE) {
+      // FIXME: Right now memory_type is a 2-bit field which means that
+      //        VTA_MEM_ID_OUT will appear as 0. For now we'll refrain from
+      //        checking the memory_type to avoid an assertion error...
       return kStoreStage;
     }
     assert(false);
@@ -859,7 +860,7 @@ class InsnQueue : public BaseQueue {
                 bool push_prev_dep, bool push_next_dep,
                 bool pop_prev_dep, bool pop_next_dep) {
     VTAMemInsn* insn = reinterpret_cast<VTAMemInsn*>(NextInsn());
-    insn->opcode = (stage==kStoreStage ? OPCODE_STORE : OPCODE_LOAD);
+    insn->opcode = (stage == kStoreStage ? VTA_OPCODE_STORE : VTA_OPCODE_LOAD);
     insn->push_prev_dep = push_prev_dep;
     insn->push_next_dep = push_next_dep;
     insn->pop_prev_dep = pop_prev_dep;
@@ -873,7 +874,7 @@ class InsnQueue : public BaseQueue {
     insn->y_pad_1 = 0;
     insn->x_pad_0 = 0;
     insn->x_pad_1 = 0;
-    insn->memory_type = (stage == kLoadStage ? MEM_ID_INP : MEM_ID_UOP);
+    insn->memory_type = (stage == kLoadStage ? VTA_MEM_ID_INP : VTA_MEM_ID_UOP);
   }
 
  private:
@@ -913,12 +914,12 @@ class CommandQueue {
   }
 
   uint32_t GetElemBytes(uint32_t memory_id) {
-    switch (memory_id){
-      case MEM_ID_UOP: return UOP_ELEM_BYTES;
-      case MEM_ID_INP: return INP_ELEM_BYTES;
-      case MEM_ID_WGT: return WGT_ELEM_BYTES;
-      case MEM_ID_ACC: return ACC_ELEM_BYTES;
-      case MEM_ID_OUT: return INP_ELEM_BYTES;
+    switch (memory_id) {
+      case VTA_MEM_ID_UOP: return VTA_UOP_ELEM_BYTES;
+      case VTA_MEM_ID_INP: return VTA_INP_ELEM_BYTES;
+      case VTA_MEM_ID_WGT: return VTA_WGT_ELEM_BYTES;
+      case VTA_MEM_ID_ACC: return VTA_ACC_ELEM_BYTES;
+      case VTA_MEM_ID_OUT: return VTA_INP_ELEM_BYTES;
       default: break;
     }
     printf("Memory id not recognized: %d\n", memory_id);
@@ -938,7 +939,7 @@ class CommandQueue {
                     uint32_t dst_sram_index,
                     uint32_t dst_memory_type) {
     VTAMemInsn* insn = insn_queue_.CreateMemInsn(dst_memory_type);
-    insn->opcode = OPCODE_LOAD;
+    insn->opcode = VTA_OPCODE_LOAD;
     insn->memory_type = dst_memory_type;
     insn->sram_base = dst_sram_index;
     DataBuffer* src = DataBuffer::FromHandle(src_dram_addr);
@@ -961,7 +962,7 @@ class CommandQueue {
                      uint32_t y_size,
                      uint32_t x_stride) {
     VTAMemInsn* insn = insn_queue_.CreateStoreInsn();
-    insn->opcode = OPCODE_STORE;
+    insn->opcode = VTA_OPCODE_STORE;
     insn->memory_type = src_memory_type;
     insn->sram_base = src_sram_index;
     DataBuffer* dst = DataBuffer::FromHandle(dst_dram_addr);
@@ -1013,7 +1014,7 @@ class CommandQueue {
     insn_queue_.CommitPendingPop(kComputeStage);
     // NOTE: FINISH cannot contain pop
     VTAGemInsn* insn = insn_queue_.CreateGemInsn();
-    insn->opcode = OPCODE_FINISH;
+    insn->opcode = VTA_OPCODE_FINISH;
     assert(!insn_queue_.PendingPop());
     // Check if there are no instruction to execute at all
     if (insn_queue_.count() == 0) return;
@@ -1026,11 +1027,11 @@ class CommandQueue {
     }
     // Make sure that the last instruction is a finish instruction
     assert(reinterpret_cast<VTAMemInsn*>(
-        insn_queue_.data())[insn_queue_.count()-1].opcode == OPCODE_FINISH);
+        insn_queue_.data())[insn_queue_.count()-1].opcode == VTA_OPCODE_FINISH);
 
-#ifdef PYNQ_TARGET
+#ifdef VTA_PYNQ_TARGET
     // Make sure that we don't exceed contiguous physical memory limits
-    assert(insn_queue_.count() < MAX_XFER);
+    assert(insn_queue_.count() < VTA_MAX_XFER);
 
     // NOTE: Register address map is derived from the auto-generated
     // driver files available under hardware/build/vivado/<design>/export/driver
@@ -1064,7 +1065,7 @@ class CommandQueue {
     }
     // Report error if timeout
     assert(t < wait_cycles);
-#endif //PYNQ_TARGET
+#endif  // VTA_PYNQ_TARGET
 
     // Reset buffers
     uop_queue_.Reset();
@@ -1142,12 +1143,12 @@ class CommandQueue {
     uop_queue_.Push(kernel,
                     [this]() { this->AutoSync(); });
     if (uop_queue_.pending()) {
-      VTAMemInsn* insn = insn_queue_.CreateMemInsn(MEM_ID_UOP);
-      insn->opcode = OPCODE_LOAD;
+      VTAMemInsn* insn = insn_queue_.CreateMemInsn(VTA_MEM_ID_UOP);
+      insn->opcode = VTA_OPCODE_LOAD;
       uop_queue_.FlushUopLoad(insn);
     }
     VTAGemInsn* insn = insn_queue_.CreateGemInsn();
-    insn->opcode = OPCODE_GEMM;
+    insn->opcode = VTA_OPCODE_GEMM;
     insn->uop_bgn = kernel->sram_begin_;
     insn->uop_end = kernel->sram_end_;
     const std::vector<UopKernel::LoopEntry> &loop = kernel->loop();
@@ -1180,12 +1181,12 @@ class CommandQueue {
     uop_queue_.Push(kernel,
                     [this]() { this->AutoSync(); });
     if (uop_queue_.pending()) {
-      VTAMemInsn* insn = insn_queue_.CreateMemInsn(MEM_ID_UOP);
-      insn->opcode = OPCODE_LOAD;
+      VTAMemInsn* insn = insn_queue_.CreateMemInsn(VTA_MEM_ID_UOP);
+      insn->opcode = VTA_OPCODE_LOAD;
       uop_queue_.FlushUopLoad(insn);
     }
     VTAAluInsn* insn = insn_queue_.CreateAluInsn();
-    insn->opcode = OPCODE_ALU;
+    insn->opcode = VTA_OPCODE_ALU;
     insn->uop_bgn = kernel->sram_begin_;
     insn->uop_end = kernel->sram_end_;
     insn->alu_opcode = kernel->opcode_;
@@ -1219,7 +1220,7 @@ class CommandQueue {
   void CheckInsnOverFlow() {
     // At each API call, we can at most commit:
     // one pending store, one pending load, and one uop
-    if (insn_queue_.count() >= MAX_XFER) {
+    if (insn_queue_.count() >= VTA_MAX_XFER) {
       this->AutoSync();
     }
   }
@@ -1237,9 +1238,9 @@ class CommandQueue {
   // The kernel we currently recording
   UopKernel* record_kernel_{nullptr};
   // Micro op queue
-  UopQueue<MAX_XFER, true, true> uop_queue_;
+  UopQueue<VTA_MAX_XFER, true, true> uop_queue_;
   // instruction queue
-  InsnQueue<MAX_XFER, true, true> insn_queue_;
+  InsnQueue<VTA_MAX_XFER, true, true> insn_queue_;
 };
 
 }  // namespace vta
@@ -1342,10 +1343,10 @@ void VTAStoreBuffer2D(VTACommandHandle cmd,
                       uint32_t x_size,
                       uint32_t y_size,
                       uint32_t x_stride) {
- static_cast<vta::CommandQueue*>(cmd)->
-     StoreBuffer2D(src_sram_index, src_memory_type,
-                   dst_dram_addr, dst_elem_offset,
-                   x_size, y_size, x_stride);
+  static_cast<vta::CommandQueue*>(cmd)->
+      StoreBuffer2D(src_sram_index, src_memory_type,
+                    dst_dram_addr, dst_elem_offset,
+                    x_size, y_size, x_stride);
 }
 
 void VTAUopPush(uint32_t mode,
diff --git a/vta/src/tvm/vta_device_api.cc b/vta/src/tvm/vta_device_api.cc
index b686b65fc415..b7b57e199f3f 100644
--- a/vta/src/tvm/vta_device_api.cc
+++ b/vta/src/tvm/vta_device_api.cc
@@ -1,8 +1,14 @@
-// simply include the driver for now.
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file vta_device_api.cc
+ * \brief VTA device API for TVM
+ */
+
 #include <tvm/runtime/registry.h>
 #include <dmlc/thread_local.h>
 #include <vta/runtime.h>
-#include "../../tvm/src/runtime/workspace_pool.h"
+
+#include "../../nnvm/tvm/src/runtime/workspace_pool.h"
 
 namespace tvm {
 namespace runtime {
diff --git a/vta/tests/hardware/common/test_lib.cc b/vta/tests/hardware/common/test_lib.cc
index d203b2aa1307..7f46a43b1867 100644
--- a/vta/tests/hardware/common/test_lib.cc
+++ b/vta/tests/hardware/common/test_lib.cc
@@ -6,41 +6,43 @@
 
 #include "./test_lib.h"
 
+uint32_t globalSeed;
+
 const char* getOpcodeString(int opcode, bool use_imm) {
   // Returns string name
-  if (opcode == ALU_OPCODE_MIN) {
+  if (opcode == VTA_ALU_OPCODE_MIN) {
     if (use_imm) {
       return "min imm";
     } else {
       return "min";
     }
-  } else if (opcode == ALU_OPCODE_MAX) {
+  } else if (opcode == VTA_ALU_OPCODE_MAX) {
     if (use_imm) {
       return "max imm";
     } else {
       return "max";
     }
-  } else if (opcode == ALU_OPCODE_ADD) {
+  } else if (opcode == VTA_ALU_OPCODE_ADD) {
     if (use_imm) {
       return "add imm";
     } else {
       return "add";
     }
-  } else if (opcode == ALU_OPCODE_SUB) {
+  } else if (opcode == VTA_ALU_OPCODE_SUB) {
     if (use_imm) {
       return "sub imm";
     } else {
       return "sub";
     }
-  } else if (opcode == ALU_OPCODE_MUL) {
+  } else if (opcode == VTA_ALU_OPCODE_MUL) {
     if (use_imm) {
       return "mul imm";
     } else {
       return "mul";
     }
-  } else if (opcode == ALU_OPCODE_SHL) {
+  } else if (opcode == VTA_ALU_OPCODE_SHL) {
     return "shl";
-  } else if (opcode == ALU_OPCODE_SHR) {
+  } else if (opcode == VTA_ALU_OPCODE_SHR) {
     return "shr";
   }
   return "unknown op";
@@ -49,20 +51,20 @@ const char* getOpcodeString(int opcode, bool use_imm) {
 template <typename T, int T_WIDTH>
 void packBuffer(T *dst, T **src, int y_size, int x_size, int y_block, int x_block) {
   int buffer_idx = 0;
-  for(int i = 0; i < y_size / y_block; i ++) {
-    for(int j = 0; j < x_size / x_block; j ++) {
-      for(int k = 0; k < y_block; k ++) {
+  for (int i = 0; i < y_size / y_block; i++) {
+    for (int j = 0; j < x_size / x_block; j++) {
+      for (int k = 0; k < y_block; k++) {
         if (T_WIDTH < 8) {
           for (int l = 0; l < x_block; l += 8 / T_WIDTH) {
             dst[buffer_idx] = 0;
-            for (int m = 0; m < 8 / T_WIDTH; m ++) {
+            for (int m = 0; m < 8 / T_WIDTH; m++) {
               dst[buffer_idx] |= (src[i * y_block + k][j * x_block + l + m] &
                 ((1ULL << T_WIDTH) - 1)) << (m * T_WIDTH);
             }
-            buffer_idx ++;
+            buffer_idx++;
           }
         } else {
-          for (int l = 0; l < x_block; l ++) {
+          for (int l = 0; l < x_block; l++) {
             dst[buffer_idx++] = src[i * y_block + k][j * x_block + l];
           }
         }
@@ -74,20 +76,20 @@ void packBuffer(T *dst, T **src, int y_size, int x_size, int y_block, int x_bloc
 template <typename T, int T_WIDTH>
 void unpackBuffer(T **dst, T *src, int y_size, int x_size, int y_block, int x_block) {
   int buffer_idx = 0;
-  for(int i = 0; i < y_size / y_block; i ++) {
-    for(int j = 0; j < x_size / x_block; j ++) {
-      for(int k = 0; k < y_block; k ++) {
+  for (int i = 0; i < y_size / y_block; i++) {
+    for (int j = 0; j < x_size / x_block; j++) {
+      for (int k = 0; k < y_block; k++) {
         if (T_WIDTH < 8) {
           for (int l = 0; l < x_block; l += 8 / T_WIDTH) {
-            for (int m = 0; m < 8 / T_WIDTH; m ++) {
+            for (int m = 0; m < 8 / T_WIDTH; m++) {
               dst[i * y_block + k][j * x_block + l + m] = (src[buffer_idx] >> (m * T_WIDTH))
                 & ((1 << T_WIDTH) - 1);
             }
-            buffer_idx ++;
+            buffer_idx++;
           }
         } else {
-          for (int l = 0; l < x_block; l ++) {
-            dst[i * y_block + k][j * x_block + l] = src[buffer_idx ++];
+          for (int l = 0; l < x_block; l++) {
+            dst[i * y_block + k][j * x_block + l] = src[buffer_idx++];
           }
         }
       }
@@ -98,14 +100,15 @@ void unpackBuffer(T **dst, T *src, int y_size, int x_size, int y_block, int x_bl
 template <typename T, int T_WIDTH>
 T ** allocInit2dArray(int rows, int cols) {
   // Allocate
-  T **array = (T **) malloc(sizeof(T *) * rows);
-  for (int i = 0; i < rows; i ++) {
-    array[i] = (T *) malloc(sizeof(T) * cols);
+  T **array = static_cast<T **>(malloc(sizeof(T *) * rows));
+  for (int i = 0; i < rows; i++) {
+    array[i] = static_cast<T *>(malloc(sizeof(T) * cols));
   }
   // Init
-  for (int i = 0; i < rows; i ++) {
-    for (int j = 0; j < cols; j ++) {
-      array[i][j] = (T) (rand() % (1LL << (T_WIDTH - 1)) - (1LL << (T_WIDTH - 2)));
+  for (int i = 0; i < rows; i++) {
+    for (int j = 0; j < cols; j++) {
+      array[i][j] =
+          static_cast<T>(rand_r(&globalSeed) % (1LL << (T_WIDTH - 1)) - (1LL << (T_WIDTH - 2)));
     }
   }
   return array;
@@ -113,16 +116,16 @@ T ** allocInit2dArray(int rows, int cols) {
 
 template <typename T>
 T ** alloc2dArray(int rows, int cols) {
-  T **array = (T **) malloc(sizeof(T *) * rows);
-  for (int i = 0; i < rows; i ++) {
-    array[i] = (T *) malloc(sizeof(T) * cols);
+  T **array = static_cast<T **>(malloc(sizeof(T *) * rows));
+  for (int i = 0; i < rows; i++) {
+    array[i] = static_cast<T *>(malloc(sizeof(T) * cols));
   }
   return array;
 }
 
 template <typename T>
 void free2dArray(T **array, int rows, int cols) {
-  for (int i = 0; i < rows; i ++) {
+  for (int i = 0; i < rows; i++) {
     free(array[i]);
   }
   free(array);
@@ -130,11 +133,11 @@ void free2dArray(T **array, int rows, int cols) {
 
 template <typename T>
 T *** alloc3dArray(int rows, int cols, int depth) {
-  T ***array = (T ***) malloc(sizeof(T **) * rows);
-  for (int i = 0; i < rows; i ++) {
-    array[i] = (T **) malloc(sizeof(T *) * cols);
-    for (int j = 0; j < cols; j ++) {
-      array[i][j] = (T*) malloc(sizeof(T) * depth);
+  T ***array = static_cast<T ***>(malloc(sizeof(T **) * rows));
+  for (int i = 0; i < rows; i++) {
+    array[i] = static_cast<T **>(malloc(sizeof(T *) * cols));
+    for (int j = 0; j < cols; j++) {
+      array[i][j] = static_cast<T*>(malloc(sizeof(T) * depth));
     }
   }
   return array;
@@ -142,8 +145,8 @@ T *** alloc3dArray(int rows, int cols, int depth) {
 
 template <typename T>
 void free3dArray(T *** array, int rows, int cols, int depth) {
-  for (int i = 0; i < rows; i ++) {
-    for (int j = 0; j < cols; j ++) {
+  for (int i = 0; i < rows; i++) {
+    for (int j = 0; j < cols; j++) {
       free(array[i][j]);
     }
     free(array[i]);
@@ -153,7 +156,7 @@ void free3dArray(T *** array, int rows, int cols, int depth) {
 
 void * allocBuffer(size_t num_bytes) {
 #ifdef NO_SIM
-  return VTAMemAlloc(num_bytes, CACHED);
+  return VTAMemAlloc(num_bytes, VTA_CACHED);
 #else
   return malloc(num_bytes);
 #endif
@@ -173,7 +176,7 @@ VTAGenericInsn reset2DInsn(int type, int sram_offset, int y_size, int x_size, in
   union VTAInsn converter;
   // Memory instruction initialization
   VTAMemInsn insn = {};
-  insn.opcode = OPCODE_LOAD;
+  insn.opcode = VTA_OPCODE_LOAD;
   insn.pop_prev_dep = pop_prev_dep;
   insn.pop_next_dep = pop_next_dep;
   insn.push_prev_dep = push_prev_dep;
@@ -250,7 +253,7 @@ VTAGenericInsn getGEMMInsn(int uop_offset, int batch, int in_feat, int out_feat,
   union VTAInsn converter;
   // GEVM instruction initialization
   VTAGemInsn insn;
-  insn.opcode = OPCODE_GEMM;
+  insn.opcode = VTA_OPCODE_GEMM;
   insn.pop_prev_dep = pop_prev_dep;
   insn.pop_next_dep = pop_next_dep;
   insn.push_prev_dep = push_prev_dep;
@@ -288,7 +291,7 @@ VTAGenericInsn getALUInsn(int opcode, int vector_size, bool use_imm, int imm, bo
   union VTAInsn converter;
   // Memory instruction initialization
   VTAAluInsn insn = {};
-  insn.opcode = OPCODE_ALU;
+  insn.opcode = VTA_OPCODE_ALU;
   insn.pop_prev_dep = pop_prev_dep;
   insn.pop_next_dep = pop_next_dep;
   insn.push_prev_dep = push_prev_dep;
@@ -327,7 +330,7 @@ VTAGenericInsn getFinishInsn(bool pop_prev, bool pop_next) {
   union VTAInsn converter;
   // GEVM instruction initialization
   VTAGemInsn insn;
-  insn.opcode = OPCODE_FINISH;
+  insn.opcode = VTA_OPCODE_FINISH;
   insn.pop_prev_dep = pop_prev;
   insn.pop_next_dep = pop_next;
   insn.push_prev_dep = 0;
@@ -347,21 +350,20 @@ VTAGenericInsn getFinishInsn(bool pop_prev, bool pop_next) {
 }
 
 VTAUop * getCopyUops(int y_size, int x_size, int uop_compression) {
-
   // Derive the total uop size
   int uop_size = (uop_compression) ? 1 : y_size * x_size;
 
   // Allocate buffer
 #ifdef NO_SIM
-  VTAUop *uop_buf = (VTAUop *) VTAMemAlloc(sizeof(VTAUop) * uop_size, CACHED);
+  VTAUop *uop_buf = static_cast<VTAUop *>(VTAMemAlloc(sizeof(VTAUop) * uop_size, VTA_CACHED));
 #else
-  VTAUop *uop_buf = (VTAUop *) malloc(sizeof(VTAUop) * uop_size);
+  VTAUop *uop_buf = static_cast<VTAUop *>(malloc(sizeof(VTAUop) * uop_size));
 #endif
 
   if (!uop_compression) {
     int uop_idx = 0;
-    for (int i = 0; i < y_size; i ++) {
-      for (int j = 0; j < x_size; j ++) {
+    for (int i = 0; i < y_size; i++) {
+      for (int j = 0; j < x_size; j++) {
         uop_buf[uop_idx].reset_out = false;
         uop_buf[uop_idx].dst_idx = i * x_size + j;
         uop_buf[uop_idx].src_idx = 0;
@@ -381,23 +383,22 @@ VTAUop * getCopyUops(int y_size, int x_size, int uop_compression) {
 
 VTAUop * getGEMMUops(int batch, int in_feat, int out_feat, bool uop_compression,
     bool multi_threaded) {
-
   // Derive the total uop size
   int uop_size = (uop_compression) ? batch : batch * in_feat * out_feat;
   if (multi_threaded) uop_size *= 2;
 
   // Allocate buffer
 #ifdef NO_SIM
-  VTAUop *uop_buf = (VTAUop *) VTAMemAlloc(sizeof(VTAUop) * uop_size, CACHED);
+  VTAUop *uop_buf = static_cast<VTAUop *>(VTAMemAlloc(sizeof(VTAUop) * uop_size, VTA_CACHED));
 #else
-  VTAUop *uop_buf = (VTAUop *) malloc(sizeof(VTAUop) * uop_size);
+  VTAUop *uop_buf = static_cast<VTAUop *>(malloc(sizeof(VTAUop) * uop_size));
 #endif
 
   if (!uop_compression) {
     int uop_idx = 0;
-    for (int i = 0; i < batch; i ++) {
-      for (int j = 0; j < in_feat; j ++) {
-        for (int k = 0; k < out_feat; k ++) {
+    for (int i = 0; i < batch; i++) {
+      for (int j = 0; j < in_feat; j++) {
+        for (int k = 0; k < out_feat; k++) {
           uop_buf[uop_idx].reset_out = false;
           uop_buf[uop_idx].dst_idx = i * out_feat + k;
           uop_buf[uop_idx].src_idx = i * in_feat + j;
@@ -407,7 +408,7 @@ VTAUop * getGEMMUops(int batch, int in_feat, int out_feat, bool uop_compression,
       }
     }
   } else {
-    for (int i = 0; i < batch; i ++) {
+    for (int i = 0; i < batch; i++) {
       uop_buf[i].reset_out = false;
       uop_buf[i].dst_idx = i * out_feat;
       uop_buf[i].src_idx = i * in_feat;
@@ -418,9 +419,9 @@ VTAUop * getGEMMUops(int batch, int in_feat, int out_feat, bool uop_compression,
   if (multi_threaded) {
     if (!uop_compression) {
       int uop_idx = uop_size / 2;
-      for (int i = 0; i < batch; i ++) {
-        for (int j = 0; j < in_feat; j ++) {
-          for (int k = 0; k < out_feat; k ++) {
+      for (int i = 0; i < batch; i++) {
+        for (int j = 0; j < in_feat; j++) {
+          for (int k = 0; k < out_feat; k++) {
             uop_buf[uop_idx].reset_out = false;
             uop_buf[uop_idx].dst_idx = i * out_feat + k;
             uop_buf[uop_idx].src_idx = batch * in_feat + i * in_feat + j;
@@ -430,7 +431,7 @@ VTAUop * getGEMMUops(int batch, int in_feat, int out_feat, bool uop_compression,
         }
       }
     } else {
-      for (int i = 0; i < batch; i ++) {
+      for (int i = 0; i < batch; i++) {
         uop_buf[batch+i].reset_out = false;
         uop_buf[batch+i].dst_idx = i * out_feat;
         uop_buf[batch+i].src_idx = batch * in_feat + i * in_feat;
@@ -443,19 +444,18 @@ VTAUop * getGEMMUops(int batch, int in_feat, int out_feat, bool uop_compression,
 }
 
 VTAUop * getMapALUUops(int vector_size, bool uop_compression) {
-
   // Derive the total uop size
   int uop_size = (uop_compression) ? 1 : vector_size;
 
   // Allocate buffer
 #ifdef NO_SIM
-  VTAUop *uop_buf = (VTAUop *) VTAMemAlloc(sizeof(VTAUop) * uop_size, CACHED);
+  VTAUop *uop_buf = static_cast<VTAUop *>(VTAMemAlloc(sizeof(VTAUop) * uop_size, VTA_CACHED));
 #else
-  VTAUop *uop_buf = (VTAUop *) malloc(sizeof(VTAUop) * uop_size);
+  VTAUop *uop_buf = static_cast<VTAUop *>(malloc(sizeof(VTAUop) * uop_size));
 #endif
 
   if (!uop_compression) {
-    for (int i = 0; i < vector_size; i ++) {
+    for (int i = 0; i < vector_size; i++) {
       uop_buf[i].reset_out = 0;
       uop_buf[i].dst_idx = i;
       uop_buf[i].src_idx = vector_size + i;
@@ -473,65 +473,65 @@ void printParameters() {
   // Some debugging code
   printf("Size of VTAInsn: %d\n", sizeof(VTAGenericInsn));
   printf("Size of VTAUop: %d\n", sizeof(VTAUop));
-  printf("UOP_BUFF_DEPTH: %d\n", UOP_BUFF_DEPTH);
-  printf("LOG_UOP_BUFF_DEPTH: %d\n", LOG_UOP_BUFF_DEPTH);
-  printf("WGT_BUFF_DEPTH: %d\n", WGT_BUFF_DEPTH);
-  printf("LOG_WGT_BUFF_DEPTH: %d\n", LOG_WGT_BUFF_DEPTH);
-  printf("INP_BUFF_DEPTH: %d\n", INP_BUFF_DEPTH);
-  printf("LOG_INP_BUFF_DEPTH: %d\n", LOG_INP_BUFF_DEPTH);
-  printf("ACC_BUFF_DEPTH: %d\n", ACC_BUFF_DEPTH);
-  printf("LOG_ACC_BUFF_DEPTH: %d\n", LOG_ACC_BUFF_DEPTH);
-  printf("WGT_WORDS: %d\n", WGT_BUFF_DEPTH*BLOCK_IN*BLOCK_OUT);
-  printf("INP_WORDS: %d\n", INP_BUFF_DEPTH*BLOCK_IN);
-  printf("ACC_WORDS: %d\n", ACC_BUFF_DEPTH*BLOCK_OUT);
-  printf("INS_ELEM_BYTES: %d\n", INS_ELEM_BYTES);
-  printf("UOP_ELEM_BYTES: %d\n", UOP_ELEM_BYTES);
-  printf("INP_ELEM_BYTES: %d\n", INP_ELEM_BYTES);
-  printf("WGT_ELEM_BYTES: %d\n", WGT_ELEM_BYTES);
-  printf("ACC_ELEM_BYTES: %d\n", ACC_ELEM_BYTES);
-  printf("BLOCK_IN: %d\n", BLOCK_IN);
-  printf("BLOCK_OUT: %d\n", BLOCK_OUT);
-  printf("INSN_MEM_0 [%d-%d]\n", INSN_MEM_0_0, INSN_MEM_0_1);
-  printf("INSN_MEM_1 [%d]\n", INSN_MEM_1);
-  printf("INSN_MEM_2 [%d]\n", INSN_MEM_2);
-  printf("INSN_MEM_3 [%d]\n", INSN_MEM_3);
-  printf("INSN_MEM_4 [%d]\n", INSN_MEM_4);
-  printf("INSN_MEM_5 [%d-%d]\n", INSN_MEM_5_0, INSN_MEM_5_1);
-  printf("INSN_MEM_6 [%d-%d]\n", INSN_MEM_6_0, INSN_MEM_6_1);
-  printf("INSN_MEM_7 [%d-%d]\n", INSN_MEM_7_0, INSN_MEM_7_1);
-  printf("INSN_MEM_8 [%d-%d]\n", INSN_MEM_8_0, INSN_MEM_8_1);
-  printf("INSN_MEM_9 [%d-%d]\n", INSN_MEM_9_0, INSN_MEM_9_1);
-  printf("INSN_MEM_A [%d-%d]\n", INSN_MEM_A_0, INSN_MEM_A_1);
-  printf("INSN_MEM_B [%d-%d]\n", INSN_MEM_B_0, INSN_MEM_B_1);
-  printf("INSN_MEM_C [%d-%d]\n", INSN_MEM_C_0, INSN_MEM_C_1);
-  printf("INSN_MEM_D [%d-%d]\n", INSN_MEM_D_0, INSN_MEM_D_1);
-  printf("INSN_MEM_E [%d-%d]\n", INSN_MEM_E_0, INSN_MEM_E_1);
-  printf("INSN_GEM_0 [%d-%d]\n", INSN_GEM_0_0, INSN_GEM_0_1);
-  printf("INSN_GEM_1 [%d]\n", INSN_GEM_1);
-  printf("INSN_GEM_2 [%d]\n", INSN_GEM_2);
-  printf("INSN_GEM_3 [%d]\n", INSN_GEM_3);
-  printf("INSN_GEM_4 [%d]\n", INSN_GEM_4);
-  printf("INSN_GEM_5 [%d-%d]\n", INSN_GEM_5_0, INSN_GEM_5_1);
-  printf("INSN_GEM_6 [%d-%d]\n", INSN_GEM_6_0, INSN_GEM_6_1);
-  printf("INSN_GEM_7 [%d-%d]\n", INSN_GEM_7_0, INSN_GEM_7_1);
-  printf("INSN_GEM_8 [%d-%d]\n", INSN_GEM_8_0, INSN_GEM_8_1);
-  printf("INSN_GEM_9 [%d-%d]\n", INSN_GEM_9_0, INSN_GEM_9_1);
-  printf("INSN_GEM_A [%d-%d]\n", INSN_GEM_A_0, INSN_GEM_A_1);
-  printf("INSN_GEM_B [%d-%d]\n", INSN_GEM_B_0, INSN_GEM_B_1);
-  printf("INSN_GEM_C [%d-%d]\n", INSN_GEM_C_0, INSN_GEM_C_1);
-  printf("INSN_GEM_D [%d-%d]\n", INSN_GEM_D_0, INSN_GEM_D_1);
-  printf("INSN_GEM_E [%d-%d]\n", INSN_GEM_E_0, INSN_GEM_E_1);
-  printf("INSN_ALU_D [%d-%d]\n", INSN_ALU_D_0, INSN_ALU_D_1);
-  printf("INSN_ALU_E [%d]\n", INSN_ALU_E);
-  printf("INSN_ALU_F [%d-%d]\n", INSN_ALU_F_0, INSN_ALU_F_1);
-  printf("UOP_GEM_0 [%d]\n", UOP_GEM_0);
-  printf("UOP_GEM_1 [%d-%d]\n", UOP_GEM_1_0, UOP_GEM_1_1);
-  printf("UOP_GEM_2 [%d-%d]\n", UOP_GEM_2_0, UOP_GEM_2_1);
-  printf("UOP_GEM_3 [%d-%d]\n", UOP_GEM_3_0, UOP_GEM_3_1);
-  printf("UOP_ALU_0 [%d]\n", UOP_ALU_0);
-  printf("UOP_ALU_1 [%d-%d]\n", UOP_ALU_1_0, UOP_ALU_1_1);
-  printf("UOP_ALU_2 [%d-%d]\n", UOP_ALU_2_0, UOP_ALU_2_1);
-  printf("UOP_ALU_3 [%d-%d]\n", UOP_ALU_3_0, UOP_ALU_3_1);
+  printf("VTA_UOP_BUFF_DEPTH: %d\n", VTA_UOP_BUFF_DEPTH);
+  printf("VTA_LOG_UOP_BUFF_DEPTH: %d\n", VTA_LOG_UOP_BUFF_DEPTH);
+  printf("VTA_WGT_BUFF_DEPTH: %d\n", VTA_WGT_BUFF_DEPTH);
+  printf("VTA_LOG_WGT_BUFF_DEPTH: %d\n", VTA_LOG_WGT_BUFF_DEPTH);
+  printf("VTA_INP_BUFF_DEPTH: %d\n", VTA_INP_BUFF_DEPTH);
+  printf("VTA_LOG_INP_BUFF_DEPTH: %d\n", VTA_LOG_INP_BUFF_DEPTH);
+  printf("VTA_ACC_BUFF_DEPTH: %d\n", VTA_ACC_BUFF_DEPTH);
+  printf("VTA_LOG_ACC_BUFF_DEPTH: %d\n", VTA_LOG_ACC_BUFF_DEPTH);
+  printf("VTA_WGT_WORDS: %d\n", VTA_WGT_BUFF_DEPTH*VTA_BLOCK_IN*VTA_BLOCK_OUT);
+  printf("VTA_INP_WORDS: %d\n", VTA_INP_BUFF_DEPTH*VTA_BLOCK_IN);
+  printf("VTA_ACC_WORDS: %d\n", VTA_ACC_BUFF_DEPTH*VTA_BLOCK_OUT);
+  printf("VTA_INS_ELEM_BYTES: %d\n", VTA_INS_ELEM_BYTES);
+  printf("VTA_UOP_ELEM_BYTES: %d\n", VTA_UOP_ELEM_BYTES);
+  printf("VTA_INP_ELEM_BYTES: %d\n", VTA_INP_ELEM_BYTES);
+  printf("VTA_WGT_ELEM_BYTES: %d\n", VTA_WGT_ELEM_BYTES);
+  printf("VTA_ACC_ELEM_BYTES: %d\n", VTA_ACC_ELEM_BYTES);
+  printf("VTA_BLOCK_IN: %d\n", VTA_BLOCK_IN);
+  printf("VTA_BLOCK_OUT: %d\n", VTA_BLOCK_OUT);
+  printf("VTA_INSN_MEM_0 [%d-%d]\n", VTA_INSN_MEM_0_0, VTA_INSN_MEM_0_1);
+  printf("VTA_INSN_MEM_1 [%d]\n", VTA_INSN_MEM_1);
+  printf("VTA_INSN_MEM_2 [%d]\n", VTA_INSN_MEM_2);
+  printf("VTA_INSN_MEM_3 [%d]\n", VTA_INSN_MEM_3);
+  printf("VTA_INSN_MEM_4 [%d]\n", VTA_INSN_MEM_4);
+  printf("VTA_INSN_MEM_5 [%d-%d]\n", VTA_INSN_MEM_5_0, VTA_INSN_MEM_5_1);
+  printf("VTA_INSN_MEM_6 [%d-%d]\n", VTA_INSN_MEM_6_0, VTA_INSN_MEM_6_1);
+  printf("VTA_INSN_MEM_7 [%d-%d]\n", VTA_INSN_MEM_7_0, VTA_INSN_MEM_7_1);
+  printf("VTA_INSN_MEM_8 [%d-%d]\n", VTA_INSN_MEM_8_0, VTA_INSN_MEM_8_1);
+  printf("VTA_INSN_MEM_9 [%d-%d]\n", VTA_INSN_MEM_9_0, VTA_INSN_MEM_9_1);
+  printf("VTA_INSN_MEM_A [%d-%d]\n", VTA_INSN_MEM_A_0, VTA_INSN_MEM_A_1);
+  printf("VTA_INSN_MEM_B [%d-%d]\n", VTA_INSN_MEM_B_0, VTA_INSN_MEM_B_1);
+  printf("VTA_INSN_MEM_C [%d-%d]\n", VTA_INSN_MEM_C_0, VTA_INSN_MEM_C_1);
+  printf("VTA_INSN_MEM_D [%d-%d]\n", VTA_INSN_MEM_D_0, VTA_INSN_MEM_D_1);
+  printf("VTA_INSN_MEM_E [%d-%d]\n", VTA_INSN_MEM_E_0, VTA_INSN_MEM_E_1);
+  printf("VTA_INSN_GEM_0 [%d-%d]\n", VTA_INSN_GEM_0_0, VTA_INSN_GEM_0_1);
+  printf("VTA_INSN_GEM_1 [%d]\n", VTA_INSN_GEM_1);
+  printf("VTA_INSN_GEM_2 [%d]\n", VTA_INSN_GEM_2);
+  printf("VTA_INSN_GEM_3 [%d]\n", VTA_INSN_GEM_3);
+  printf("VTA_INSN_GEM_4 [%d]\n", VTA_INSN_GEM_4);
+  printf("VTA_INSN_GEM_5 [%d-%d]\n", VTA_INSN_GEM_5_0, VTA_INSN_GEM_5_1);
+  printf("VTA_INSN_GEM_6 [%d-%d]\n", VTA_INSN_GEM_6_0, VTA_INSN_GEM_6_1);
+  printf("VTA_INSN_GEM_7 [%d-%d]\n", VTA_INSN_GEM_7_0, VTA_INSN_GEM_7_1);
+  printf("VTA_INSN_GEM_8 [%d-%d]\n", VTA_INSN_GEM_8_0, VTA_INSN_GEM_8_1);
+  printf("VTA_INSN_GEM_9 [%d-%d]\n", VTA_INSN_GEM_9_0, VTA_INSN_GEM_9_1);
+  printf("VTA_INSN_GEM_A [%d-%d]\n", VTA_INSN_GEM_A_0, VTA_INSN_GEM_A_1);
+  printf("VTA_INSN_GEM_B [%d-%d]\n", VTA_INSN_GEM_B_0, VTA_INSN_GEM_B_1);
+  printf("VTA_INSN_GEM_C [%d-%d]\n", VTA_INSN_GEM_C_0, VTA_INSN_GEM_C_1);
+  printf("VTA_INSN_GEM_D [%d-%d]\n", VTA_INSN_GEM_D_0, VTA_INSN_GEM_D_1);
+  printf("VTA_INSN_GEM_E [%d-%d]\n", VTA_INSN_GEM_E_0, VTA_INSN_GEM_E_1);
+  printf("VTA_INSN_ALU_D [%d-%d]\n", VTA_INSN_ALU_D_0, VTA_INSN_ALU_D_1);
+  printf("VTA_INSN_ALU_E [%d]\n", VTA_INSN_ALU_E);
+  printf("VTA_INSN_ALU_F [%d-%d]\n", VTA_INSN_ALU_F_0, VTA_INSN_ALU_F_1);
+  printf("VTA_UOP_GEM_0 [%d]\n", VTA_UOP_GEM_0);
+  printf("VTA_UOP_GEM_1 [%d-%d]\n", VTA_UOP_GEM_1_0, VTA_UOP_GEM_1_1);
+  printf("VTA_UOP_GEM_2 [%d-%d]\n", VTA_UOP_GEM_2_0, VTA_UOP_GEM_2_1);
+  printf("VTA_UOP_GEM_3 [%d-%d]\n", VTA_UOP_GEM_3_0, VTA_UOP_GEM_3_1);
+  printf("VTA_UOP_ALU_0 [%d]\n", VTA_UOP_ALU_0);
+  printf("VTA_UOP_ALU_1 [%d-%d]\n", VTA_UOP_ALU_1_0, VTA_UOP_ALU_1_1);
+  printf("VTA_UOP_ALU_2 [%d-%d]\n", VTA_UOP_ALU_2_0, VTA_UOP_ALU_2_1);
+  printf("VTA_UOP_ALU_3 [%d-%d]\n", VTA_UOP_ALU_3_0, VTA_UOP_ALU_3_1);
 }
 
 void printInstruction(int num_insn, VTAGenericInsn *insns) {
@@ -544,84 +544,111 @@ void printInstruction(int num_insn, VTAGenericInsn *insns) {
   union VTAInsn c;
   // Iterate over all instructions
   printf("DEBUG - There are %u instructions\n", num_insn);
-  for (int i = 0; i < num_insn; i ++) {
+  for (int i = 0; i < num_insn; i++) {
     // Fetch instruction and decode opcode
     c.generic = insns[i];
     printf("DEBUG - INSTRUCTION %u: ", i);
-    if (c.mem.opcode == OPCODE_LOAD || c.mem.opcode == OPCODE_STORE) {
+    if (c.mem.opcode == VTA_OPCODE_LOAD || c.mem.opcode == VTA_OPCODE_STORE) {
       // Print instruction field information
-      if (c.mem.opcode == OPCODE_LOAD) {
+      if (c.mem.opcode == VTA_OPCODE_LOAD) {
         printf("LOAD ");
-        if (c.mem.memory_type == MEM_ID_UOP) printf("UOP\n");
-        if (c.mem.memory_type == MEM_ID_WGT) printf("WGT\n");
-        if (c.mem.memory_type == MEM_ID_INP) printf("INP\n");
-        if (c.mem.memory_type == MEM_ID_ACC) printf("ACC\n");
+        if (c.mem.memory_type == VTA_MEM_ID_UOP) printf("UOP\n");
+        if (c.mem.memory_type == VTA_MEM_ID_WGT) printf("WGT\n");
+        if (c.mem.memory_type == VTA_MEM_ID_INP) printf("INP\n");
+        if (c.mem.memory_type == VTA_MEM_ID_ACC) printf("ACC\n");
       }
-      if (c.mem.opcode == OPCODE_STORE) {
+      if (c.mem.opcode == VTA_OPCODE_STORE) {
         printf("STORE ACC\n");
       }
       printf("\tdep - pop prev: %d, pop next: %d, push prev: %d, push next: %d\n",
-        (int) c.mem.pop_prev_dep, (int) c.mem.pop_next_dep,
-        (int) c.mem.push_prev_dep, (int) c.mem.push_next_dep);
-      printf("\tDRAM: 0x%08x, SRAM:0x%04x\n", (int) c.mem.dram_base, (int) c.mem.sram_base);
-      printf("\ty: size=%d, pad=[%d, %d]\n", (int) c.mem.y_size, (int) c.mem.y_pad_0,
-        (int) c.mem.y_pad_1);
-      printf("\tx: size=%d, stride=%d, pad=[%d, %d]\n", (int) c.mem.x_size, (int) c.mem.x_stride,
-        (int) c.mem.x_pad_0, (int) c.mem.x_pad_1);
-      if (c.mem.opcode == OPCODE_STORE) {
-        if (c.mem.pop_prev_dep) g2s_queue --;
-        if (c.mem.push_prev_dep) s2g_queue ++;
-      } else if (c.mem.opcode == OPCODE_LOAD &&
-        (c.mem.memory_type == MEM_ID_INP || c.mem.memory_type == MEM_ID_WGT)) {
-        if (c.mem.pop_next_dep) g2l_queue --;
-        if (c.mem.push_next_dep) l2g_queue ++;
+             static_cast<int>(c.mem.pop_prev_dep),
+             static_cast<int>(c.mem.pop_next_dep),
+             static_cast<int>(c.mem.push_prev_dep),
+             static_cast<int>(c.mem.push_next_dep));
+      printf("\tDRAM: 0x%08x, SRAM:0x%04x\n",
+             static_cast<int>(c.mem.dram_base),
+             static_cast<int>(c.mem.sram_base));
+      printf("\ty: size=%d, pad=[%d, %d]\n",
+             static_cast<int>(c.mem.y_size),
+             static_cast<int>(c.mem.y_pad_0),
+             static_cast<int>(c.mem.y_pad_1));
+      printf("\tx: size=%d, stride=%d, pad=[%d, %d]\n",
+             static_cast<int>(c.mem.x_size),
+             static_cast<int>(c.mem.x_stride),
+             static_cast<int>(c.mem.x_pad_0),
+             static_cast<int>(c.mem.x_pad_1));
+      if (c.mem.opcode == VTA_OPCODE_STORE) {
+        if (c.mem.pop_prev_dep) g2s_queue--;
+        if (c.mem.push_prev_dep) s2g_queue++;
+      } else if (c.mem.opcode == VTA_OPCODE_LOAD &&
+        (c.mem.memory_type == VTA_MEM_ID_INP || c.mem.memory_type == VTA_MEM_ID_WGT)) {
+        if (c.mem.pop_next_dep) g2l_queue--;
+        if (c.mem.push_next_dep) l2g_queue++;
       } else {
-        if (c.mem.pop_prev_dep) l2g_queue --;
-        if (c.mem.push_prev_dep) g2l_queue ++;
-        if (c.mem.pop_next_dep) s2g_queue --;
-        if (c.mem.push_next_dep) g2s_queue ++;
+        if (c.mem.pop_prev_dep) l2g_queue--;
+        if (c.mem.push_prev_dep) g2l_queue++;
+        if (c.mem.pop_next_dep) s2g_queue--;
+        if (c.mem.push_next_dep) g2s_queue++;
       }
-    } else if (c.mem.opcode == OPCODE_GEMM) {
+    } else if (c.mem.opcode == VTA_OPCODE_GEMM) {
       // Print instruction field information
       printf("GEVM\n");
       printf("\tdep - pop prev: %d, pop next: %d, push prev: %d, push next: %d\n",
-        (int) c.mem.pop_prev_dep, (int) c.mem.pop_next_dep,
-        (int) c.mem.push_prev_dep, (int) c.mem.push_next_dep);
-      printf("\trange (%d, %d)\n", (int) c.gemm.uop_bgn, (int) c.gemm.uop_end);
-      printf("\touter loop - iter: %d, acc: %d, inp: %d, wgt: %d\n", (int) c.gemm.iter_out,
-        (int) c.gemm.dst_factor_out, (int) c.gemm.src_factor_out,
-        (int) c.gemm.wgt_factor_out);
-      printf("\tinner loop - iter: %d, acc: %d, inp: %d, wgt: %d\n", (int) c.gemm.iter_in,
-        (int) c.gemm.dst_factor_in, (int) c.gemm.src_factor_in,
-        (int) c.gemm.wgt_factor_in);
-      if (c.gemm.pop_prev_dep) l2g_queue --;
-      if (c.gemm.push_prev_dep) g2l_queue ++;
-      if (c.gemm.pop_next_dep) s2g_queue --;
-      if (c.gemm.push_next_dep) g2s_queue ++;
-    } else if (c.mem.opcode == OPCODE_FINISH) {
+             static_cast<int>(c.mem.pop_prev_dep),
+             static_cast<int>(c.mem.pop_next_dep),
+             static_cast<int>(c.mem.push_prev_dep),
+             static_cast<int>(c.mem.push_next_dep));
+      printf("\trange (%d, %d)\n",
+             static_cast<int>(c.gemm.uop_bgn),
+             static_cast<int>(c.gemm.uop_end));
+      printf("\touter loop - iter: %d, acc: %d, inp: %d, wgt: %d\n",
+             static_cast<int>(c.gemm.iter_out),
+             static_cast<int>(c.gemm.dst_factor_out),
+             static_cast<int>(c.gemm.src_factor_out),
+             static_cast<int>(c.gemm.wgt_factor_out));
+      printf("\tinner loop - iter: %d, acc: %d, inp: %d, wgt: %d\n",
+             static_cast<int>(c.gemm.iter_in),
+             static_cast<int>(c.gemm.dst_factor_in),
+             static_cast<int>(c.gemm.src_factor_in),
+             static_cast<int>(c.gemm.wgt_factor_in));
+      if (c.gemm.pop_prev_dep) l2g_queue--;
+      if (c.gemm.push_prev_dep) g2l_queue++;
+      if (c.gemm.pop_next_dep) s2g_queue--;
+      if (c.gemm.push_next_dep) g2s_queue++;
+    } else if (c.mem.opcode == VTA_OPCODE_FINISH) {
       printf("FINISH\n");
       printf("\tdep - pop prev: %d, pop next: %d, push prev: %d, push next: %d\n",
-        (int) c.mem.pop_prev_dep, (int) c.mem.pop_next_dep,
-        (int) c.mem.push_prev_dep, (int) c.mem.push_next_dep);
-      if (c.gemm.pop_prev_dep) l2g_queue --;
-      if (c.gemm.push_prev_dep) g2l_queue ++;
-      if (c.gemm.pop_next_dep) s2g_queue --;
-      if (c.gemm.push_next_dep) g2s_queue ++;
-    } else if (c.mem.opcode == OPCODE_ALU) {
+             static_cast<int>(c.mem.pop_prev_dep),
+             static_cast<int>(c.mem.pop_next_dep),
+             static_cast<int>(c.mem.push_prev_dep),
+             static_cast<int>(c.mem.push_next_dep));
+      if (c.gemm.pop_prev_dep) l2g_queue--;
+      if (c.gemm.push_prev_dep) g2l_queue++;
+      if (c.gemm.pop_next_dep) s2g_queue--;
+      if (c.gemm.push_next_dep) g2s_queue++;
+    } else if (c.mem.opcode == VTA_OPCODE_ALU) {
       // Print instruction field information
       printf("ALU - %s\n", getOpcodeString(c.alu.alu_opcode, c.alu.use_imm));
       printf("\tdep - pop prev: %d, pop next: %d, push prev: %d, push next: %d\n",
-        (int) c.mem.pop_prev_dep, (int) c.mem.pop_next_dep,
-        (int) c.mem.push_prev_dep, (int) c.mem.push_next_dep);
-      printf("\trange (%d, %d)\n", (int) c.alu.uop_bgn, (int) c.alu.uop_end);
-      printf("\touter loop - iter: %d, dst: %d, src: %d\n", (int) c.alu.iter_out,
-        (int) c.alu.dst_factor_out, (int) c.alu.src_factor_out);
-      printf("\tinner loop - iter: %d, dst: %d, src: %d\n", (int) c.alu.iter_in,
-        (int) c.alu.dst_factor_in, (int) c.alu.src_factor_in);
-      if (c.alu.pop_prev_dep) l2g_queue --;
-      if (c.alu.push_prev_dep) g2l_queue ++;
-      if (c.alu.pop_next_dep) s2g_queue --;
-      if (c.alu.push_next_dep) g2s_queue ++;
+             static_cast<int>(c.mem.pop_prev_dep),
+             static_cast<int>(c.mem.pop_next_dep),
+             static_cast<int>(c.mem.push_prev_dep),
+             static_cast<int>(c.mem.push_next_dep));
+      printf("\trange (%d, %d)\n",
+             static_cast<int>(c.alu.uop_bgn),
+             static_cast<int>(c.alu.uop_end));
+      printf("\touter loop - iter: %d, dst: %d, src: %d\n",
+             static_cast<int>(c.alu.iter_out),
+             static_cast<int>(c.alu.dst_factor_out),
+             static_cast<int>(c.alu.src_factor_out));
+      printf("\tinner loop - iter: %d, dst: %d, src: %d\n",
+             static_cast<int>(c.alu.iter_in),
+             static_cast<int>(c.alu.dst_factor_in),
+             static_cast<int>(c.alu.src_factor_in));
+      if (c.alu.pop_prev_dep) l2g_queue--;
+      if (c.alu.push_prev_dep) g2l_queue++;
+      if (c.alu.pop_next_dep) s2g_queue--;
+      if (c.alu.push_next_dep) g2s_queue++;
     }
   }
   printf("DEBUG - l2g_queue = %d, g2l_queue = %d\n", l2g_queue, g2l_queue);
@@ -632,174 +659,193 @@ void printInstruction(int num_insn, VTAGenericInsn *insns) {
 void printMicroOp(int num_uop, VTAUop *uops) {
   // Iterate over all micro ops
   printf("DEBUG - There are %u micro-ops\n", num_uop);
-  for (int i = 0; i < num_uop; i ++) {
+  for (int i = 0; i < num_uop; i++) {
     // Read micro-op
     printf("DEBUG - UOP %u: ", i);
     printf("rst_out=%u, acc=%u, inp= %u, wgt=%u\n", uops[i].reset_out, uops[i].dst_idx,
         uops[i].src_idx, uops[i].wgt_idx);
-
   }
 }
 
 int alu_test(int opcode, bool use_imm, int batch, int vector_size, bool uop_compression) {
-
-  assert(batch % BATCH == 0);
-  assert(vector_size % BLOCK_OUT == 0);
-  assert(!(opcode == ALU_OPCODE_SHL && !use_imm));
-  assert(!(opcode == ALU_OPCODE_SHR && !use_imm));
-
+  // Some assertions
+  assert(batch % VTA_BATCH == 0);
+  assert(vector_size % VTA_BLOCK_OUT == 0);
+  assert(!(opcode == VTA_ALU_OPCODE_SHL && !use_imm));
+  assert(!(opcode == VTA_ALU_OPCODE_SHR && !use_imm));
   printf("=====================================================================================\n");
   printf("INFO - ALU test of %s: batch=%d, vector_size=%d, uop_compression=%d\n",
     getOpcodeString(opcode, use_imm), batch, vector_size, uop_compression);
 
   // Instruction count
-  int ins_size = 3 * batch / BATCH + 2;
+  int ins_size = 3 * batch / VTA_BATCH + 2;
   // Micro op count
-  int uop_size = uop_compression ? 1 : vector_size / BLOCK_OUT;
+  int uop_size = uop_compression ? 1 : vector_size / VTA_BLOCK_OUT;
   // Input/output elements in each transfer
-  int tx_size = vector_size / BLOCK_OUT;
+  int tx_size = vector_size / VTA_BLOCK_OUT;
   // Number of input sets to be generated
   int input_sets = (use_imm) ? 1 : 2;
   // Make sure we don't exceed buffer bounds
-  assert(uop_size <= UOP_BUFF_DEPTH);
-  assert(tx_size * input_sets <= ACC_BUFF_DEPTH);
+  assert(uop_size <= VTA_UOP_BUFF_DEPTH);
+  assert(tx_size * input_sets <= VTA_ACC_BUFF_DEPTH);
 
   // Immediate values
-  acc_T *immediate = (acc_T *) malloc(sizeof(acc_T) * batch / BATCH);
-  for (int b = 0; b < batch / BATCH; b ++) {
-    if (opcode == ALU_OPCODE_MIN) {
-      immediate[b] = (acc_T) (rand() % (1LL << (INP_WIDTH / 2)) - (1LL << (INP_WIDTH / 2 - 1)));
-    } else if (opcode == ALU_OPCODE_MAX) {
-      immediate[b] = (acc_T) (rand() % (1LL << (INP_WIDTH / 2)) - (1LL << (INP_WIDTH / 2 - 1)));
-    } else if (opcode == ALU_OPCODE_ADD) {
-      immediate[b] = (acc_T) (rand() % (1LL << (INP_WIDTH / 2)) - (1LL << (INP_WIDTH / 2 - 1)));
-    } else if (opcode == ALU_OPCODE_SUB) {
-      immediate[b] = (acc_T) (rand() % (1LL << (INP_WIDTH / 2)) - (1LL << (INP_WIDTH / 2 - 1)));
-    } else if (opcode == ALU_OPCODE_MUL) {
-      immediate[b] = (acc_T) (rand() % (1LL << (INP_WIDTH / 2)) - (1LL << (INP_WIDTH / 2 - 1)));
-    } else if (opcode == ALU_OPCODE_SHL) {
-      immediate[b] = (acc_T) (rand() % (INP_WIDTH + 1));
-    } else if (opcode == ALU_OPCODE_SHR) {
-      immediate[b] = (acc_T) (rand() % (INP_WIDTH + 1));
+  acc_T *immediate = static_cast<acc_T *>(malloc(sizeof(acc_T) * batch / VTA_BATCH));
+  for (int b = 0; b < batch / VTA_BATCH; b++) {
+    if (opcode == VTA_ALU_OPCODE_MIN) {
+      immediate[b] = static_cast<acc_T>(
+          rand_r(&globalSeed) % (1LL << (VTA_INP_WIDTH / 2)) - (1LL << (VTA_INP_WIDTH / 2 - 1)));
+    } else if (opcode == VTA_ALU_OPCODE_MAX) {
+      immediate[b] = static_cast<acc_T>(
+          rand_r(&globalSeed) % (1LL << (VTA_INP_WIDTH / 2)) - (1LL << (VTA_INP_WIDTH / 2 - 1)));
+    } else if (opcode == VTA_ALU_OPCODE_ADD) {
+      immediate[b] = static_cast<acc_T>(
+          rand_r(&globalSeed) % (1LL << (VTA_INP_WIDTH / 2)) - (1LL << (VTA_INP_WIDTH / 2 - 1)));
+    } else if (opcode == VTA_ALU_OPCODE_SUB) {
+      immediate[b] = static_cast<acc_T>(
+          rand_r(&globalSeed) % (1LL << (VTA_INP_WIDTH / 2)) - (1LL << (VTA_INP_WIDTH / 2 - 1)));
+    } else if (opcode == VTA_ALU_OPCODE_MUL) {
+      immediate[b] = static_cast<acc_T>(
+          rand_r(&globalSeed) % (1LL << (VTA_INP_WIDTH / 2)) - (1LL << (VTA_INP_WIDTH / 2 - 1)));
+    } else if (opcode == VTA_ALU_OPCODE_SHL) {
+      immediate[b] = static_cast<acc_T>(rand_r(&globalSeed) % (VTA_INP_WIDTH + 1));
+    } else if (opcode == VTA_ALU_OPCODE_SHR) {
+      immediate[b] = static_cast<acc_T>(rand_r(&globalSeed) % (VTA_INP_WIDTH + 1));
     }
   }
 
   // Initialize instructions
-  VTAGenericInsn *insn_buf = (VTAGenericInsn *) allocBuffer(sizeof(VTAGenericInsn) * ins_size);
+  VTAGenericInsn *insn_buf =
+      static_cast<VTAGenericInsn *>(allocBuffer(sizeof(VTAGenericInsn) * ins_size));
   int insn_idx = 0;
-  insn_buf[insn_idx ++] = get1DLoadStoreInsn(OPCODE_LOAD, MEM_ID_UOP, 0, 0, uop_size, 0, 0, 0, 0);
-  for (int b = 0; b < batch; b += BATCH) {
-    insn_buf[insn_idx ++] = get2DLoadStoreInsn(
-      OPCODE_LOAD,                      // opcode
-      MEM_ID_ACC,                       // vector size
-      0,                                // sram offset
-      b / BATCH * tx_size * input_sets, // dram offset
-      1,                                // y size
-      tx_size * input_sets,             // x size
-      tx_size * input_sets,             // x stride
-      0,                                // y pad
-      0,                                // x pad
-      0,                                // pop prev dep
-      b > 0,                            // pop next dep
-      0,                                // push prev dep
-      0);                               // push next dep
-    insn_buf[insn_idx ++] = getALUInsn(
-      opcode,                           // opcode
-      tx_size,                          // vector size
-      use_imm,                          // use imm
-      immediate[b / BATCH],             // imm
-      uop_compression,                  // uop compression
-      0,                                // pop prev dep
-      0,                                // pop next dep
-      0,                                // push prev dep
-      1);                               // push next dep
-    insn_buf[insn_idx ++] = get2DLoadStoreInsn(
-      OPCODE_STORE,                     // opcode
-      MEM_ID_OUT,                       // vector size
-      0,                                // sram offset
-      b / BATCH * tx_size,              // dram offset
-      1,                                // y size
-      tx_size,                          // x size
-      tx_size,                          // x stride
-      0,                                // y pad
-      0,                                // x pad
-      1,                                // pop prev dep
-      0,                                // pop next dep
-      1,                                // push prev dep
-      0);                               // push next dep
+  insn_buf[insn_idx++] =
+      get1DLoadStoreInsn(VTA_OPCODE_LOAD, VTA_MEM_ID_UOP, 0, 0, uop_size, 0, 0, 0, 0);
+  for (int b = 0; b < batch; b += VTA_BATCH) {
+    insn_buf[insn_idx++] = get2DLoadStoreInsn(
+        VTA_OPCODE_LOAD,                   // opcode
+        VTA_MEM_ID_ACC,                    // vector size
+        0,                                 // sram offset
+        b / VTA_BATCH * tx_size * input_sets,  // dram offset
+        1,                                 // y size
+        tx_size * input_sets,              // x size
+        tx_size * input_sets,              // x stride
+        0,                                 // y pad
+        0,                                 // x pad
+        0,                                 // pop prev dep
+        b > 0,                             // pop next dep
+        0,                                 // push prev dep
+        0);                                // push next dep
+    insn_buf[insn_idx++] = getALUInsn(
+        opcode,                            // opcode
+        tx_size,                           // vector size
+        use_imm,                           // use imm
+        immediate[b / VTA_BATCH],          // imm
+        uop_compression,                   // uop compression
+        0,                                 // pop prev dep
+        0,                                 // pop next dep
+        0,                                 // push prev dep
+        1);                                // push next dep
+    insn_buf[insn_idx++] = get2DLoadStoreInsn(
+        VTA_OPCODE_STORE,                  // opcode
+        VTA_MEM_ID_OUT,                    // vector size
+        0,                                 // sram offset
+        b / VTA_BATCH * tx_size,           // dram offset
+        1,                                 // y size
+        tx_size,                           // x size
+        tx_size,                           // x stride
+        0,                                 // y pad
+        0,                                 // x pad
+        1,                                 // pop prev dep
+        0,                                 // pop next dep
+        1,                                 // push prev dep
+        0);                                // push next dep
   }
   // Finish
-  insn_buf[insn_idx ++] = getFinishInsn(0, 1);
-
+  insn_buf[insn_idx++] = getFinishInsn(0, 1);
   // Prepare the uop buffer
   VTAUop * uop_buf = getMapALUUops(tx_size, uop_compression);
 
-#if DEBUG==1
+#if VTA_DEBUG == 1
   printInstruction(ins_size, insn_buf);
   printMicroOp(uop_size, uop_buf);
 #endif
 
   // Initialize the input/output data
   acc_T **inputs = alloc2dArray<acc_T>(batch, vector_size * input_sets);
-  for (int i = 0; i < batch; i ++) {
-    for (int j = 0; j < vector_size * input_sets; j ++) {
-      if (opcode == ALU_OPCODE_MIN) {
-        inputs[i][j] = (acc_T) (rand() % (1LL << (INP_WIDTH - 1)) - (1LL << (INP_WIDTH - 2)));
-      } else if (opcode == ALU_OPCODE_MAX) {
-        inputs[i][j] = (acc_T) (rand() % (1LL << (INP_WIDTH - 1)) - (1LL << (INP_WIDTH - 2)));
-      } else if (opcode == ALU_OPCODE_ADD) {
-        inputs[i][j] = (acc_T) (rand() % (1LL << (INP_WIDTH - 1)) - (1LL << (INP_WIDTH - 2)));
-      } else if (opcode == ALU_OPCODE_SUB) {
-        inputs[i][j] = (acc_T) (rand() % (1LL << (INP_WIDTH - 1)) - (1LL << (INP_WIDTH - 2)));
-      } else if (opcode == ALU_OPCODE_MUL) {
-        inputs[i][j] = (acc_T) (rand() % (1LL << (INP_WIDTH / 2)) - (1LL << (INP_WIDTH / 2 - 1)));
-      } else if (opcode == ALU_OPCODE_SHL) {
-        inputs[i][j] = (acc_T) (rand() % (1LL << (INP_WIDTH - 1)) - (1LL << (INP_WIDTH - 2)));
-      } else if (opcode == ALU_OPCODE_SHR) {
-        inputs[i][j] = (acc_T) (rand() % (1LL << (INP_WIDTH - 1)) - (1LL << (INP_WIDTH - 2)));
+  for (int i = 0; i < batch; i++) {
+    for (int j = 0; j < vector_size * input_sets; j++) {
+      if (opcode == VTA_ALU_OPCODE_MIN) {
+        inputs[i][j] = static_cast<acc_T>(
+            rand_r(&globalSeed) % (1LL << (VTA_INP_WIDTH - 1)) - (1LL << (VTA_INP_WIDTH - 2)));
+      } else if (opcode == VTA_ALU_OPCODE_MAX) {
+        inputs[i][j] = static_cast<acc_T>(
+            rand_r(&globalSeed) % (1LL << (VTA_INP_WIDTH - 1)) - (1LL << (VTA_INP_WIDTH - 2)));
+      } else if (opcode == VTA_ALU_OPCODE_ADD) {
+        inputs[i][j] = static_cast<acc_T>(
+            rand_r(&globalSeed) % (1LL << (VTA_INP_WIDTH - 1)) - (1LL << (VTA_INP_WIDTH - 2)));
+      } else if (opcode == VTA_ALU_OPCODE_SUB) {
+        inputs[i][j] = static_cast<acc_T>(
+            rand_r(&globalSeed) % (1LL << (VTA_INP_WIDTH - 1)) - (1LL << (VTA_INP_WIDTH - 2)));
+      } else if (opcode == VTA_ALU_OPCODE_MUL) {
+        inputs[i][j] = static_cast<acc_T>(
+            rand_r(&globalSeed) % (1LL << (VTA_INP_WIDTH / 2)) - (1LL << (VTA_INP_WIDTH / 2 - 1)));
+      } else if (opcode == VTA_ALU_OPCODE_SHL) {
+        inputs[i][j] = static_cast<acc_T>(
+            rand_r(&globalSeed) % (1LL << (VTA_INP_WIDTH - 1)) - (1LL << (VTA_INP_WIDTH - 2)));
+      } else if (opcode == VTA_ALU_OPCODE_SHR) {
+        inputs[i][j] = static_cast<acc_T>(
+            rand_r(&globalSeed) % (1LL << (VTA_INP_WIDTH - 1)) - (1LL << (VTA_INP_WIDTH - 2)));
       }
     }
   }
 
   // Compute reference output
   out_T **outputs_ref = alloc2dArray<out_T>(batch, vector_size);
-  for (int i = 0; i < batch; i ++) {
-    for (int j = 0; j < vector_size; j ++) {
+  for (int i = 0; i < batch; i++) {
+    for (int j = 0; j < vector_size; j++) {
       acc_T tmp = 0;
-      if (opcode == ALU_OPCODE_MIN) {
+      if (opcode == VTA_ALU_OPCODE_MIN) {
         if (!use_imm) {
-          tmp = inputs[i][j] < inputs[i][j + vector_size] ? inputs[i][j] : inputs[i][j + vector_size];
+          tmp = inputs[i][j] < inputs[i][j + vector_size] ?
+                    inputs[i][j] :
+                    inputs[i][j + vector_size];
         } else {
-          tmp = inputs[i][j] < immediate[i / BATCH] ? inputs[i][j] : immediate[i / BATCH];
+          tmp = inputs[i][j] < immediate[i / VTA_BATCH] ?
+                    inputs[i][j] :
+                    immediate[i / VTA_BATCH];
         }
-      } else if (opcode == ALU_OPCODE_MAX) {
+      } else if (opcode == VTA_ALU_OPCODE_MAX) {
         if (!use_imm) {
-          tmp = inputs[i][j] > inputs[i][j + vector_size] ? inputs[i][j] : inputs[i][j + vector_size];
+          tmp = inputs[i][j] > inputs[i][j + vector_size] ?
+                    inputs[i][j] :
+                    inputs[i][j + vector_size];
         } else {
-          tmp = inputs[i][j] > immediate[i / BATCH] ? inputs[i][j] : immediate[i / BATCH];
+          tmp = inputs[i][j] > immediate[i / VTA_BATCH] ?
+                    inputs[i][j] :
+                    immediate[i / VTA_BATCH];
         }
-      } else if (opcode == ALU_OPCODE_ADD) {
+      } else if (opcode == VTA_ALU_OPCODE_ADD) {
         if (!use_imm) {
           tmp = inputs[i][j] + inputs[i][j + vector_size];
         } else {
-          tmp = inputs[i][j] + immediate[i / BATCH];
+          tmp = inputs[i][j] + immediate[i / VTA_BATCH];
         }
-      } else if (opcode == ALU_OPCODE_SUB) {
+      } else if (opcode == VTA_ALU_OPCODE_SUB) {
         if (!use_imm) {
           tmp = inputs[i][j] - inputs[i][j + vector_size];
         } else {
-          tmp = inputs[i][j] - immediate[i / BATCH];
+          tmp = inputs[i][j] - immediate[i / VTA_BATCH];
         }
-      } else if (opcode == ALU_OPCODE_MUL) {
+      } else if (opcode == VTA_ALU_OPCODE_MUL) {
         if (!use_imm) {
           tmp = inputs[i][j] * inputs[i][j + vector_size];
         } else {
-          tmp = inputs[i][j] * immediate[i / BATCH];
+          tmp = inputs[i][j] * immediate[i / VTA_BATCH];
         }
-      } else if (opcode == ALU_OPCODE_SHL) {
-        tmp = inputs[i][j] << immediate[i / BATCH];
-      } else if (opcode == ALU_OPCODE_SHR) {
-        tmp = inputs[i][j] >> immediate[i / BATCH];
+      } else if (opcode == VTA_ALU_OPCODE_SHL) {
+        tmp = inputs[i][j] << immediate[i / VTA_BATCH];
+      } else if (opcode == VTA_ALU_OPCODE_SHR) {
+        tmp = inputs[i][j] >> immediate[i / VTA_BATCH];
       }
       // Set
       outputs_ref[i][j] = (out_T) tmp;
@@ -807,44 +853,51 @@ int alu_test(int opcode, bool use_imm, int batch, int vector_size, bool uop_comp
   }
 
   // Pack input buffer
-  acc_T *bias_buf = (acc_T *) allocBuffer(ACC_ELEM_BYTES * batch * tx_size * input_sets);
-  packBuffer<acc_T, ACC_WIDTH>(bias_buf, inputs, batch, vector_size * input_sets, BATCH, BLOCK_OUT);
+  acc_T *bias_buf =
+      static_cast<acc_T *>(allocBuffer(VTA_ACC_ELEM_BYTES * batch * tx_size * input_sets));
+  packBuffer<acc_T, VTA_ACC_WIDTH>(
+      bias_buf, inputs, batch, vector_size * input_sets, VTA_BATCH, VTA_BLOCK_OUT);
 
   // Prepare output buffer
-  out_T *output_buf = (out_T *) allocBuffer(INP_ELEM_BYTES * batch * tx_size * input_sets);
+  out_T *output_buf =
+      static_cast<out_T *>(allocBuffer(VTA_INP_ELEM_BYTES * batch * tx_size * input_sets));
 
 #ifdef NO_SIM
   // Invoke the VTA
   uint64_t t_fpga = vta(ins_size, insn_buf, uop_buf, NULL, NULL, bias_buf, output_buf);
   // Report on timining
-  printf("INFO - Synchronization time: %.3lfms\n", (double) t_fpga / 1E6);
-  printf("INFO - Throughput: %.3lfGOps/s\n", (double) vector_size * batch / t_fpga);
+  printf("INFO - Synchronization time: %.3fms\n", static_cast<float>(t_fpga) / 1E6);
+  printf("INFO - Throughput: %.3fGOps/s\n", static_cast<float>(vector_size * batch) / t_fpga);
 #else
   // Invoke the VTA
-  vta(
-    ins_size,
-    (volatile insn_T *) insn_buf,
-    (volatile uop_T *) uop_buf,
-    (volatile inp_vec_T *) NULL,
-    (volatile wgt_vec_T *) NULL,
-    (volatile acc_vec_T *) bias_buf,
-    (volatile inp_vec_T *) output_buf
-  );
+  vta(ins_size,
+      (volatile insn_T *) insn_buf,
+      (volatile uop_T *) uop_buf,
+      (volatile inp_vec_T *) NULL,
+      (volatile wgt_vec_T *) NULL,
+      (volatile acc_vec_T *) bias_buf,
+      (volatile inp_vec_T *) output_buf);
 #endif
 
   // Unpack output buffer
   out_T **outputs = alloc2dArray<out_T>(batch, vector_size);
-  unpackBuffer<out_T, OUT_WIDTH>(outputs, output_buf, batch, vector_size, BATCH, BLOCK_OUT);
+  unpackBuffer<out_T, VTA_OUT_WIDTH>(outputs,
+                                     output_buf,
+                                     batch,
+                                     vector_size,
+                                     VTA_BATCH,
+                                     VTA_BLOCK_OUT);
 
   // Correctness checks
   int err = 0;
-  for (int i = 0; i < batch; i ++) {
-    for (int j = 0; j < vector_size; j ++) {
+  for (int i = 0; i < batch; i++) {
+    for (int j = 0; j < vector_size; j++) {
       if (outputs_ref[i][j] != outputs[i][j]) {
         err++;
-#if DEBUG==1
-        printf("DEBUG - %d, %d: expected 0x%x but got 0x%x\n", i, j, (int) outputs_ref[i][j],
-            (int) outputs[i][j]);
+#if VTA_DEBUG == 1
+        printf("DEBUG - %d, %d: expected 0x%x but got 0x%x\n", i, j,
+               static_cast<int>(outputs_ref[i][j]),
+               static_cast<int>(outputs[i][j]));
 #endif
       }
     }
@@ -867,169 +920,180 @@ int alu_test(int opcode, bool use_imm, int batch, int vector_size, bool uop_comp
     printf("INFO - ALU test failed, got %d errors!\n", err);
     return -1;
   }
-
 }
 
 int blocked_gemm_test(int batch, int channels, int block, bool uop_compression,
     int virtual_threads) {
-
-  assert(block % BLOCK_IN == 0);
-  assert(block % BLOCK_OUT == 0);
-  assert(block % BATCH == 0);
+  // Some assertions
+  assert(block % VTA_BLOCK_IN == 0);
+  assert(block % VTA_BLOCK_OUT == 0);
+  assert(block % VTA_BATCH == 0);
   assert(channels % block == 0);
   assert(batch % block == 0);
 
   printf("=====================================================================================\n");
-  printf("INFO - Blocked GEMM test: batch=%d, channels=%d, block=%d, uop_compression=%d, \
-virtual_threads=%d\n",
-    batch, channels, block, uop_compression, virtual_threads);
+  printf("INFO - Blocked GEMM test: batch=%d, channels=%d, block=%d, uop_comp=%d, vt=%d\n",
+         batch, channels, block, uop_compression, virtual_threads);
 
   // Input/output channels
   int in_feat = channels;
   int out_feat = channels;
   // Derive number of elements that need to be loaded/stored
   int ins_size = batch / block * out_feat / block * (2 + in_feat / block * 3) + 2;
-  int uop_size = uop_compression ? block / BATCH * virtual_threads :
-    block / BATCH * block / BLOCK_IN * block / BLOCK_OUT * virtual_threads;
-  int inp_size = batch / BATCH * in_feat / BLOCK_IN;
-  int wgt_size = in_feat / BLOCK_IN * out_feat / BLOCK_OUT;
-  int out_size = batch / BATCH * out_feat / BLOCK_OUT;
+  int uop_size = uop_compression ?
+      block / VTA_BATCH * virtual_threads :
+      block / VTA_BATCH * block / VTA_BLOCK_IN * block / VTA_BLOCK_OUT * virtual_threads;
+  int inp_size = batch / VTA_BATCH * in_feat / VTA_BLOCK_IN;
+  int wgt_size = in_feat / VTA_BLOCK_IN * out_feat / VTA_BLOCK_OUT;
+  int out_size = batch / VTA_BATCH * out_feat / VTA_BLOCK_OUT;
   // Blocked buffer sizes (in terms of elements)
-  int inp_block_size = block / BATCH * block / BLOCK_IN;
-  int wgt_block_size = block / BLOCK_IN * block / BLOCK_OUT;
-  int out_block_size = block / BATCH * block / BLOCK_OUT;
+  int inp_block_size = block / VTA_BATCH * block / VTA_BLOCK_IN;
+  int wgt_block_size = block / VTA_BLOCK_IN * block / VTA_BLOCK_OUT;
+  int out_block_size = block / VTA_BATCH * block / VTA_BLOCK_OUT;
   // Make sure we don't exceed buffer bounds
-  assert(uop_size <= UOP_BUFF_DEPTH);
-  assert(inp_block_size <= INP_BUFF_DEPTH);
-  assert(wgt_block_size <= WGT_BUFF_DEPTH);
-  assert(out_block_size <= ACC_BUFF_DEPTH);
+  assert(uop_size <= VTA_UOP_BUFF_DEPTH);
+  assert(inp_block_size <= VTA_INP_BUFF_DEPTH);
+  assert(wgt_block_size <= VTA_WGT_BUFF_DEPTH);
+  assert(out_block_size <= VTA_ACC_BUFF_DEPTH);
 
   // Initialize instruction buffer
-  VTAGenericInsn *insn_buf = (VTAGenericInsn *) allocBuffer(sizeof(VTAGenericInsn) * ins_size);
+  VTAGenericInsn *insn_buf =
+      static_cast<VTAGenericInsn *>(allocBuffer(sizeof(VTAGenericInsn) * ins_size));
   int insn_idx = 0;
 
   // Load uops
-  insn_buf[insn_idx ++] = get1DLoadStoreInsn(OPCODE_LOAD, MEM_ID_UOP, 0, 0, uop_size, 0, 0, 0, 0);
+  insn_buf[insn_idx++] = get1DLoadStoreInsn(VTA_OPCODE_LOAD,
+                                            VTA_MEM_ID_UOP,
+                                            0,
+                                            0,
+                                            uop_size,
+                                            0,
+                                            0,
+                                            0,
+                                            0);
   // Iterate over batch blocks
   for (int i = 0; i < batch; i += block) {
     // Iterate over output channel blocks
     for (int j = 0; j < out_feat; j += block) {
       // Load bias block (pop next if not first, push prev)
-      insn_buf[insn_idx ++] = get2DLoadStoreInsn(
-        OPCODE_LOAD,                                        // opcode
-        MEM_ID_ACC,                                         // type
-        0,                                                  // sram offset
-        (i / BATCH * out_feat + j) / BLOCK_OUT,             // dram offset
-        block / BATCH,                                      // y size
-        block / BLOCK_OUT,                                  // x size
-        out_feat / BLOCK_OUT,                               // x stride
-        0,                                                  // y pad
-        0,                                                  // x pad
-        0,                                                  // pop prev dep
-        (i > 0 || j > 0),                                   // pop next dep
-        (virtual_threads == 1),                             // push prev dep
-        0);                                                 // push next dep
+      insn_buf[insn_idx++] = get2DLoadStoreInsn(
+          VTA_OPCODE_LOAD,                                    // opcode
+          VTA_MEM_ID_ACC,                                     // type
+          0,                                                  // sram offset
+          (i / VTA_BATCH * out_feat + j) / VTA_BLOCK_OUT,     // dram offset
+          block / VTA_BATCH,                                  // y size
+          block / VTA_BLOCK_OUT,                              // x size
+          out_feat / VTA_BLOCK_OUT,                           // x stride
+          0,                                                  // y pad
+          0,                                                  // x pad
+          0,                                                  // pop prev dep
+          (i > 0 || j > 0),                                   // pop next dep
+          (virtual_threads == 1),                             // push prev dep
+          0);                                                 // push next dep
       // Iterate over input channel blocks
       for (int k = 0; k < in_feat; k += block * virtual_threads) {
         for (int l = 0; l < block * virtual_threads; l += block) {
           // Derive dependence flags
-          bool pop = (virtual_threads == 1) ? 
-            1 :
-            (i > 0 || j > 0 || k > 0 || l > 0) && (k + l != block * virtual_threads - block);
+          bool pop = (virtual_threads == 1) ?
+              1 :
+              (i > 0 || j > 0 || k > 0 || l > 0) && (k + l != block * virtual_threads - block);
           bool push_prev = (virtual_threads == 1) ?
-            ((k + l) != in_feat - block) :
-            ((k + l) != in_feat - virtual_threads * block) && 
-            (
-              (k + l != in_feat - block) ||
-              (j != out_feat - block) ||
-              (i != batch - block)
-            );
+              ((k + l) != in_feat - block) :
+              ((k + l) != in_feat - virtual_threads * block) &&
+              (
+                  (k + l != in_feat - block) ||
+                  (j != out_feat - block) ||
+                  (i != batch - block));
           bool push_next = (k + l == in_feat - block);
           // Load weight block (pop next)
-          insn_buf[insn_idx ++] = get2DLoadStoreInsn(
-            OPCODE_LOAD,                                    // opcode
-            MEM_ID_WGT,                                     // type
-            l / BLOCK_IN * block / BLOCK_OUT,               // sram offset
-            (j / BLOCK_OUT * in_feat + k + l) / BLOCK_IN,   // dram offset
-            block / BLOCK_OUT,                              // y size
-            block / BLOCK_IN,                               // x size
-            in_feat / BLOCK_IN,                             // x stride
-            0,                                              // y pad
-            0,                                              // x pad
-            0,                                              // pop prev dep
-            pop,                                            // pop next dep
-            0,                                              // push prev dep
-            0);                                             // push next dep
+          insn_buf[insn_idx++] = get2DLoadStoreInsn(
+              VTA_OPCODE_LOAD,                                // opcode
+              VTA_MEM_ID_WGT,                                 // type
+              l / VTA_BLOCK_IN * block / VTA_BLOCK_OUT,       // sram offset
+              (j / VTA_BLOCK_OUT * in_feat + k + l) / VTA_BLOCK_IN,  // dram offset
+              block / VTA_BLOCK_OUT,                          // y size
+              block / VTA_BLOCK_IN,                           // x size
+              in_feat / VTA_BLOCK_IN,                         // x stride
+              0,                                              // y pad
+              0,                                              // x pad
+              0,                                              // pop prev dep
+              pop,                                            // pop next dep
+              0,                                              // push prev dep
+              0);                                             // push next dep
           // Load input block (push next)
-          insn_buf[insn_idx ++] = get2DLoadStoreInsn(
-            OPCODE_LOAD,                                    // opcode
-            MEM_ID_INP,                                     // type
-            l / BLOCK_IN * block / BATCH,                   // sram offset
-            (i / BATCH * in_feat + k + l) / BLOCK_IN,       // dram offset
-            block / BATCH,                                  // y size
-            block / BLOCK_IN,                               // x size
-            in_feat / BLOCK_IN,                             // x stride
-            0,                                              // y pad
-            0,                                              // x pad
-            0,                                              // pop prev dep
-            0,                                              // pop next dep
-            0,                                              // push prev dep
-            1);                                             // push next dep
+          insn_buf[insn_idx++] = get2DLoadStoreInsn(
+              VTA_OPCODE_LOAD,                                // opcode
+              VTA_MEM_ID_INP,                                 // type
+              l / VTA_BLOCK_IN * block / VTA_BATCH,           // sram offset
+              (i / VTA_BATCH * in_feat + k + l) / VTA_BLOCK_IN,  // dram offset
+              block / VTA_BATCH,                              // y size
+              block / VTA_BLOCK_IN,                           // x size
+              in_feat / VTA_BLOCK_IN,                         // x stride
+              0,                                              // y pad
+              0,                                              // x pad
+              0,                                              // pop prev dep
+              0,                                              // pop next dep
+              0,                                              // push prev dep
+              1);                                             // push next dep
           // Perform GEMM (pop prev, push prev if not last, push next if last)
-          insn_buf[insn_idx ++] = getGEMMInsn(
-            l / block * uop_size / virtual_threads,         // uop offset
-            block / BATCH,                                  // batch
-            block / BLOCK_IN,                               // in_feat
-            block / BLOCK_OUT,                              // out_feat
-            uop_compression,                                // uop_compression
-            1,                                              // pop_prev_dep
-            0,                                              // pop_next_dep
-            push_prev,                                      // push prev dep
-            push_next);                                     // push_next_dep
+          insn_buf[insn_idx++] = getGEMMInsn(
+              l / block * uop_size / virtual_threads,         // uop offset
+              block / VTA_BATCH,                              // batch
+              block / VTA_BLOCK_IN,                           // in_feat
+              block / VTA_BLOCK_OUT,                          // out_feat
+              uop_compression,                                // uop_compression
+              1,                                              // pop_prev_dep
+              0,                                              // pop_next_dep
+              push_prev,                                      // push prev dep
+              push_next);                                     // push_next_dep
         }
       }
       // Store output block (pop prev, push prev if not last)
-      insn_buf[insn_idx ++] = get2DLoadStoreInsn(
-        OPCODE_STORE,                                       // opcode
-        MEM_ID_OUT,                                         // type
-        0,                                                  // sram offset
-        (i / BATCH * out_feat + j) / BLOCK_OUT,             // dram offset
-        block / BATCH,                                      // y size
-        block / BLOCK_OUT,                                  // x size
-        out_feat / BLOCK_OUT,                               // x stride
-        0,                                                  // y pad
-        0,                                                  // x pad
-        1,                                                  // pop prev dep
-        0,                                                  // pop next dep
-        1,                                                  // pop prev dep
-        0);                                                 // push next dep
+      insn_buf[insn_idx++] = get2DLoadStoreInsn(
+          VTA_OPCODE_STORE,                                   // opcode
+          VTA_MEM_ID_OUT,                                     // type
+          0,                                                  // sram offset
+          (i / VTA_BATCH * out_feat + j) / VTA_BLOCK_OUT,     // dram offset
+          block / VTA_BATCH,                                  // y size
+          block / VTA_BLOCK_OUT,                              // x size
+          out_feat / VTA_BLOCK_OUT,                           // x stride
+          0,                                                  // y pad
+          0,                                                  // x pad
+          1,                                                  // pop prev dep
+          0,                                                  // pop next dep
+          1,                                                  // pop prev dep
+          0);                                                 // push next dep
     }
   }
   // Finish
-  insn_buf[insn_idx ++] = getFinishInsn(0, 1);
+  insn_buf[insn_idx++] = getFinishInsn(0, 1);
 
   // Prepare the uop buffer
-  VTAUop * uop_buf = getGEMMUops(block / BATCH, block / BLOCK_IN, block / BLOCK_OUT, uop_compression,
-    virtual_threads > 1);
-
-#if DEBUG==1
+  VTAUop * uop_buf = getGEMMUops(
+      block / VTA_BATCH,
+      block / VTA_BLOCK_IN,
+      block / VTA_BLOCK_OUT,
+      uop_compression,
+      virtual_threads > 1);
+
+#if VTA_DEBUG == 1
   printInstruction(ins_size, insn_buf);
   printMicroOp(uop_size, uop_buf);
 #endif
 
   // Initialize inputs
-  inp_T **inputs = allocInit2dArray<inp_T, INP_WIDTH>(batch, in_feat);
+  inp_T **inputs = allocInit2dArray<inp_T, VTA_INP_WIDTH>(batch, in_feat);
   // Initialize weights
-  wgt_T **weights = allocInit2dArray<wgt_T, WGT_WIDTH>(out_feat, in_feat);
+  wgt_T **weights = allocInit2dArray<wgt_T, VTA_WGT_WIDTH>(out_feat, in_feat);
   // Initialize biases
-  acc_T **biases = allocInit2dArray<acc_T, ACC_WIDTH>(batch, out_feat);
+  acc_T **biases = allocInit2dArray<acc_T, VTA_ACC_WIDTH>(batch, out_feat);
 
   // Reference GEMM implementation
   out_T **outputs_ref = alloc2dArray<out_T>(batch, out_feat);
-  for (int i = 0; i < batch; i ++) {
-    for (int j = 0; j < out_feat; j ++) {
+  for (int i = 0; i < batch; i++) {
+    for (int j = 0; j < out_feat; j++) {
       acc_T sum = biases[i][j];
-      for (int k = 0; k < in_feat; k ++) {
+      for (int k = 0; k < in_feat; k++) {
         sum += (acc_T) (inputs[i][k] * weights[j][k]);
       }
       // Set
@@ -1038,49 +1102,75 @@ virtual_threads=%d\n",
   }
 
   // Prepare the input buffer
-  inp_T *input_buf = (inp_T *) allocBuffer(INP_ELEM_BYTES * inp_size);
-  packBuffer<inp_T, INP_WIDTH>(input_buf, inputs, batch, in_feat, BATCH, BLOCK_IN);
+  inp_T *input_buf = static_cast<inp_T *>(allocBuffer(VTA_INP_ELEM_BYTES * inp_size));
+  packBuffer<inp_T, VTA_INP_WIDTH>(input_buf,
+                                   inputs,
+                                   batch,
+                                   in_feat,
+                                   VTA_BATCH,
+                                   VTA_BLOCK_IN);
   // Prepare the weight buffer
-  wgt_T *weight_buf = (wgt_T *) allocBuffer(WGT_ELEM_BYTES * wgt_size);
-  packBuffer<wgt_T, WGT_WIDTH>(weight_buf, weights, out_feat, in_feat, BLOCK_OUT, BLOCK_IN);
+  wgt_T *weight_buf = static_cast<wgt_T *>(allocBuffer(VTA_WGT_ELEM_BYTES * wgt_size));
+  packBuffer<wgt_T, VTA_WGT_WIDTH>(weight_buf,
+                                   weights,
+                                   out_feat,
+                                   in_feat,
+                                   VTA_BLOCK_OUT,
+                                   VTA_BLOCK_IN);
   // Prepare the bias buffer
-  acc_T *bias_buf = (acc_T *) allocBuffer(ACC_ELEM_BYTES * out_size);
-  packBuffer<acc_T, ACC_WIDTH>(bias_buf, biases, batch, out_feat, BATCH, BLOCK_OUT);
+  acc_T *bias_buf = static_cast<acc_T *>(allocBuffer(VTA_ACC_ELEM_BYTES * out_size));
+  packBuffer<acc_T, VTA_ACC_WIDTH>(bias_buf,
+                                   biases,
+                                   batch,
+                                   out_feat,
+                                   VTA_BATCH,
+                                   VTA_BLOCK_OUT);
   // Prepare the output buffer
-  out_T *output_buf = (out_T *) allocBuffer(INP_ELEM_BYTES * out_size);
+  out_T *output_buf = static_cast<out_T *>(allocBuffer(VTA_INP_ELEM_BYTES * out_size));
 
 #ifdef NO_SIM
   // Invoke the VTA
-  uint64_t t_fpga = vta(ins_size, insn_buf, uop_buf, input_buf, weight_buf, bias_buf, output_buf);
+  uint64_t t_fpga = vta(ins_size,
+                        insn_buf,
+                        uop_buf,
+                        input_buf,
+                        weight_buf,
+                        bias_buf,
+                        output_buf);
   // Report on timining
-  printf("INFO - Synchronization time: %.3lfms\n", (double) t_fpga / 1E6);
-  printf("INFO - Throughput: %.3lfGOPs/s\n", (double) batch * in_feat * out_feat * 2 / t_fpga);
+  printf("INFO - Synchronization time: %.3lfms\n", static_cast<float>(t_fpga) / 1E6);
+  printf("INFO - Throughput: %.3lfGOPs/s\n",
+         static_cast<float>(batch) * in_feat * out_feat * 2 / t_fpga);
 #else
   // Invoke the VTA
-  vta(
-    ins_size,
-    (volatile insn_T *) insn_buf,
-    (volatile uop_T *) uop_buf,
-    (volatile inp_vec_T *) input_buf,
-    (volatile wgt_vec_T *) weight_buf,
-    (volatile acc_vec_T *) bias_buf,
-    (volatile inp_vec_T *) output_buf
-  );
+  vta(ins_size,
+      (volatile insn_T *) insn_buf,
+      (volatile uop_T *) uop_buf,
+      (volatile inp_vec_T *) input_buf,
+      (volatile wgt_vec_T *) weight_buf,
+      (volatile acc_vec_T *) bias_buf,
+      (volatile inp_vec_T *) output_buf);
 #endif
 
   // Unpack output data
   out_T **outputs = alloc2dArray<out_T>(batch, out_feat);
-  unpackBuffer<out_T, OUT_WIDTH>(outputs, output_buf, batch, out_feat, BATCH, BLOCK_OUT);
+  unpackBuffer<out_T, VTA_OUT_WIDTH>(outputs,
+                                     output_buf,
+                                     batch,
+                                     out_feat,
+                                     VTA_BATCH,
+                                     VTA_BLOCK_OUT);
 
   // Correctness checks
   int err = 0;
-  for (int i = 0; i < batch; i ++) {
-    for (int j = 0; j < out_feat; j ++) {
+  for (int i = 0; i < batch; i++) {
+    for (int j = 0; j < out_feat; j++) {
       if (outputs_ref[i][j] != outputs[i][j]) {
         err++;
-#if DEBUG==1
-        printf("DEBUG - %d, %d: expected 0x%x but got 0x%x\n", i, j, (int) outputs_ref[i][j],
-            (int) outputs[i][j]);
+#if VTA_DEBUG == 1
+        printf("DEBUG - %d, %d: expected 0x%x but got 0x%x\n", i, j,
+               static_cast<int>(outputs_ref[i][j]),
+               static_cast<int>(outputs[i][j]));
 #endif
       }
     }
@@ -1092,12 +1182,12 @@ virtual_threads=%d\n",
   free2dArray<acc_T>(biases, batch, out_feat);
   free2dArray<out_T>(outputs_ref, batch, out_feat);
   free2dArray<out_T>(outputs, batch, out_feat);
-  freeBuffer((void *) insn_buf);
-  freeBuffer((void *) uop_buf);
-  freeBuffer((void *) input_buf);
-  freeBuffer((void *) weight_buf);
-  freeBuffer((void *) bias_buf);
-  freeBuffer((void *) output_buf);
+  freeBuffer(insn_buf);
+  freeBuffer(uop_buf);
+  freeBuffer(input_buf);
+  freeBuffer(weight_buf);
+  freeBuffer(bias_buf);
+  freeBuffer(output_buf);
 
   if (err == 0) {
     printf("INFO - Blocked GEMM test successful!\n");
@@ -1106,5 +1196,4 @@ virtual_threads=%d\n",
     printf("INFO - Blocked GEMM test failed, got %d errors!\n", err);
     return -1;
   }
-
 }
diff --git a/vta/tests/hardware/common/test_lib.h b/vta/tests/hardware/common/test_lib.h
index fad2e4daddfb..037e2fcee72f 100644
--- a/vta/tests/hardware/common/test_lib.h
+++ b/vta/tests/hardware/common/test_lib.h
@@ -4,8 +4,8 @@
  * \brief Test library for the VTA design simulation and driver tests.
  */
 
-#ifndef VTA_TESTLIB_H_
-#define VTA_TESTLIB_H_
+#ifndef TESTS_HARDWARE_COMMON_TEST_LIB_H_
+#define TESTS_HARDWARE_COMMON_TEST_LIB_H_
 
 #include <assert.h>
 #include <stdint.h>
@@ -17,9 +17,9 @@
 
 #include <vta/driver.h>
 
-#ifdef PYNQ_TARGET
+#ifdef VTA_PYNQ_TARGET
 #include "../../../src/pynq/pynq_driver.h"
-#endif //PYNQ_TARGET
+#endif  // VTA_PYNQ_TARGET
 
 typedef uint64_t axi_T;
 typedef uint32_t uop_T;
@@ -28,7 +28,7 @@ typedef int8_t inp_T;
 typedef int8_t out_T;
 typedef int32_t acc_T;
 
-uint64_t vta (
+uint64_t vta(
   uint32_t insn_count,
   VTAGenericInsn *insns,
   VTAUop *uops,
@@ -37,11 +37,11 @@ uint64_t vta (
   acc_T *biases,
   inp_T *outputs);
 
-#else //NO_SIM
+#else  // NO_SIM
 
 #include "../../../hardware/vivado/src/vta.h"
 
-#endif //NO_SIM
+#endif  // NO_SIM
 
 /*!
 * \brief Returns opcode string.
@@ -300,4 +300,4 @@ int alu_test(int opcode, bool use_imm, int batch, int vector_size, bool uop_comp
 int blocked_gemm_test(int batch, int channels, int block, bool uop_compression,
   int virtual_threads);
 
-#endif  // VTA_TESTLIB_H_
\ No newline at end of file
+#endif  //  TESTS_HARDWARE_COMMON_TEST_LIB_H_
diff --git a/vta/tests/hardware/pynq/metal_test.cc b/vta/tests/hardware/pynq/metal_test.cc
index b5147399c18c..01e73f46bd72 100644
--- a/vta/tests/hardware/pynq/metal_test.cc
+++ b/vta/tests/hardware/pynq/metal_test.cc
@@ -14,140 +14,135 @@
 #include "../common/test_lib.h"
 
 // VTA invocation (present the same abstraction as in the simulation tests)
-uint64_t vta (
-    uint32_t insn_count,
-    VTAGenericInsn *insns,
-    VTAUop *uops,
-    inp_T *inputs,
-    wgt_T *weights,
-    acc_T *biases,
-    inp_T *outputs) {
-
-    // Performance counter variables
-    uint64_t t_fpga;
-    struct timespec start, stop;
-
-    // Derive bitstream file
-    char bitstream[64];
-    char str_batch_size[4];
-    char str_block_out_size[4];
-    char str_block_in_size[4];
-    char str_block_bit_width[4];
-    sprintf(str_batch_size, "%d", BATCH);
-    sprintf(str_block_out_size, "%d", BLOCK_OUT);
-    sprintf(str_block_in_size, "%d", BLOCK_IN);
-    sprintf(str_block_bit_width, "%d", WGT_WIDTH);
-    strcpy(bitstream, "vta.bit");
-
-#if DEBUG==1
-    printf("INFO - Programming FPGA: %s!\n", bitstream);
+uint64_t vta(
+  uint32_t insn_count,
+  VTAGenericInsn *insns,
+  VTAUop *uops,
+  inp_T *inputs,
+  wgt_T *weights,
+  acc_T *biases,
+  inp_T *outputs) {
+  // Performance counter variables
+  uint64_t t_fpga;
+  struct timespec start, stop;
+
+  // Derive bitstream file
+  char bitstream[128];
+  char str_batch_size[4];
+  char str_block_out_size[4];
+  char str_block_in_size[4];
+  char str_block_bit_width[4];
+  snprintf(str_batch_size, sizeof(str_batch_size), "%d", VTA_BATCH);
+  snprintf(str_block_out_size, sizeof(str_block_out_size), "%d", VTA_BLOCK_OUT);
+  snprintf(str_block_in_size, sizeof(str_block_in_size), "%d", VTA_BLOCK_IN);
+  snprintf(str_block_bit_width, sizeof(str_block_bit_width), "%d", VTA_WGT_WIDTH);
+  snprintf(bitstream, sizeof(bitstream), "%s", "vta.bit");
+
+#if VTA_DEBUG == 1
+  printf("INFO - Programming FPGA: %s!\n", bitstream);
 #endif
 
-    // Program VTA
-    VTAProgram(bitstream);
-    // Get VTA handles
-    VTAHandle vta_fetch_handle = VTAMapRegister(VTA_FETCH_ADDR, VTA_RANGE);
-    VTAHandle vta_load_handle = VTAMapRegister(VTA_LOAD_ADDR, VTA_RANGE);
-    VTAHandle vta_compute_handle = VTAMapRegister(VTA_COMPUTE_ADDR, VTA_RANGE);
-    VTAHandle vta_store_handle = VTAMapRegister(VTA_STORE_ADDR, VTA_RANGE);
-
-    // Physical address pointers
-    uint32_t insn_phy = insns ? cma_get_phy_addr(insns) : 0;
-    uint32_t uop_phy = uops ? cma_get_phy_addr(uops) : 0;
-    uint32_t input_phy = inputs ? cma_get_phy_addr(inputs) : 0;
-    uint32_t weight_phy = weights ? cma_get_phy_addr(weights) : 0;
-    uint32_t bias_phy = biases ? cma_get_phy_addr(biases) : 0;
-    uint32_t output_phy = outputs ? cma_get_phy_addr(outputs) : 0;
-
-#if DEBUG==1
-    printf("INFO - Starting FPGA!\n");
+  // Program VTA
+  VTAProgram(bitstream);
+  // Get VTA handles
+  VTAHandle vta_fetch_handle = VTAMapRegister(VTA_FETCH_ADDR, VTA_RANGE);
+  VTAHandle vta_load_handle = VTAMapRegister(VTA_LOAD_ADDR, VTA_RANGE);
+  VTAHandle vta_compute_handle = VTAMapRegister(VTA_COMPUTE_ADDR, VTA_RANGE);
+  VTAHandle vta_store_handle = VTAMapRegister(VTA_STORE_ADDR, VTA_RANGE);
+
+  // Physical address pointers
+  uint32_t insn_phy = insns ? cma_get_phy_addr(insns) : 0;
+  uint32_t uop_phy = uops ? cma_get_phy_addr(uops) : 0;
+  uint32_t input_phy = inputs ? cma_get_phy_addr(inputs) : 0;
+  uint32_t weight_phy = weights ? cma_get_phy_addr(weights) : 0;
+  uint32_t bias_phy = biases ? cma_get_phy_addr(biases) : 0;
+  uint32_t output_phy = outputs ? cma_get_phy_addr(outputs) : 0;
+
+#if VTA_DEBUG == 1
+  printf("INFO - Starting FPGA!\n");
 #endif
 
-    clock_gettime(CLOCK_REALTIME, &start);
-
-    // FETCH @ 0x10 : Data signal of insn_count_V
-    VTAWriteMappedReg(vta_fetch_handle, 0x10, insn_count);
-    // FETCH @ 0x18 : Data signal of insns_V
-    if (insns) VTAWriteMappedReg(vta_fetch_handle, 0x18, insn_phy);
-    // LOAD @ 0x10 : Data signal of inputs_V
-    if (inputs) VTAWriteMappedReg(vta_load_handle, 0x10, input_phy);
-    // LOAD @ 0x18 : Data signal of weight_V
-    if (weights) VTAWriteMappedReg(vta_load_handle, 0x18, weight_phy);
-    // COMPUTE @ 0x20 : Data signal of uops_V
-    if (uops) VTAWriteMappedReg(vta_compute_handle, 0x20, uop_phy);
-    // COMPUTE @ 0x28 : Data signal of biases_V
-    if (biases) VTAWriteMappedReg(vta_compute_handle, 0x28, bias_phy);
-    // STORE @ 0x10 : Data signal of outputs_V
-    if (outputs) VTAWriteMappedReg(vta_store_handle, 0x10, output_phy);
-
-    // VTA start
-    VTAWriteMappedReg(vta_fetch_handle, 0x0, 0x1);
-    VTAWriteMappedReg(vta_load_handle, 0x0, 0x81);
-    VTAWriteMappedReg(vta_compute_handle, 0x0, 0x81);
-    VTAWriteMappedReg(vta_store_handle, 0x0, 0x81);
-
-    int flag = 0, t = 0;
-    for (t = 0; t < 10000000; ++t) {
-      flag = VTAReadMappedReg(vta_compute_handle, 0x18);
-      if (flag & VTA_DONE) break;
-    }
-
-    if (t==10000000) {
-        printf("\tWARNING: VTA TIMEOUT!!!!\n");
-    }
-#if DEBUG==1
-    else {
-        printf("INFO - FPGA Finished!\n");
-    }
+  clock_gettime(CLOCK_REALTIME, &start);
+
+  // FETCH @ 0x10 : Data signal of insn_count_V
+  VTAWriteMappedReg(vta_fetch_handle, 0x10, insn_count);
+  // FETCH @ 0x18 : Data signal of insns_V
+  if (insns) VTAWriteMappedReg(vta_fetch_handle, 0x18, insn_phy);
+  // LOAD @ 0x10 : Data signal of inputs_V
+  if (inputs) VTAWriteMappedReg(vta_load_handle, 0x10, input_phy);
+  // LOAD @ 0x18 : Data signal of weight_V
+  if (weights) VTAWriteMappedReg(vta_load_handle, 0x18, weight_phy);
+  // COMPUTE @ 0x20 : Data signal of uops_V
+  if (uops) VTAWriteMappedReg(vta_compute_handle, 0x20, uop_phy);
+  // COMPUTE @ 0x28 : Data signal of biases_V
+  if (biases) VTAWriteMappedReg(vta_compute_handle, 0x28, bias_phy);
+  // STORE @ 0x10 : Data signal of outputs_V
+  if (outputs) VTAWriteMappedReg(vta_store_handle, 0x10, output_phy);
+
+  // VTA start
+  VTAWriteMappedReg(vta_fetch_handle, 0x0, 0x1);
+  VTAWriteMappedReg(vta_load_handle, 0x0, 0x81);
+  VTAWriteMappedReg(vta_compute_handle, 0x0, 0x81);
+  VTAWriteMappedReg(vta_store_handle, 0x0, 0x81);
+
+  int flag = 0, t = 0;
+  for (t = 0; t < 10000000; ++t) {
+    flag = VTAReadMappedReg(vta_compute_handle, 0x18);
+    if (flag & VTA_DONE) break;
+  }
+
+  if (t == 10000000) {
+    printf("\tWARNING: VTA TIMEOUT!!!!\n");
+#if VTA_DEBUG == 1
+  } else {
+    printf("INFO - FPGA Finished!\n");
 #endif
+  }
 
-    clock_gettime(CLOCK_REALTIME, &stop);
-    t_fpga = 1000000000ULL * (stop.tv_sec - start.tv_sec) + (stop.tv_nsec - start.tv_nsec);
+  clock_gettime(CLOCK_REALTIME, &stop);
+  t_fpga = 1000000000ULL * (stop.tv_sec - start.tv_sec) + (stop.tv_nsec - start.tv_nsec);
 
-    // Unmap VTA register
-    VTAUnmapRegister(vta_fetch_handle, VTA_RANGE);
-    VTAUnmapRegister(vta_load_handle, VTA_RANGE);
-    VTAUnmapRegister(vta_compute_handle, VTA_RANGE);
-    VTAUnmapRegister(vta_store_handle, VTA_RANGE);
+  // Unmap VTA register
+  VTAUnmapRegister(vta_fetch_handle, VTA_RANGE);
+  VTAUnmapRegister(vta_load_handle, VTA_RANGE);
+  VTAUnmapRegister(vta_compute_handle, VTA_RANGE);
+  VTAUnmapRegister(vta_store_handle, VTA_RANGE);
 
-    return t_fpga;
-};
-
-int main(void)
-{
+  return t_fpga;
+}
 
-#if DEBUG==1
-    printParameters();
+int main(void) {
+#if VTA_DEBUG == 1
+  printParameters();
 #endif
 
-    int status = 0;
-
-    // Run ALU test (vector-scalar operators)
-    status |= alu_test(ALU_OPCODE_MAX, true, 16, 128, true);
-    status |= alu_test(ALU_OPCODE_MAX, true, 16, 128, false);
-    status |= alu_test(ALU_OPCODE_ADD, true, 16, 128, true);
-    status |= alu_test(ALU_OPCODE_ADD, true, 16, 128, false);
-    status |= alu_test(ALU_OPCODE_SHR, true, 16, 128, true);
-    status |= alu_test(ALU_OPCODE_SHR, true, 16, 128, false);
-
-    // Run ALU test (vector-vector operators)
-    status |= alu_test(ALU_OPCODE_MAX, false, 16, 128, true);
-    status |= alu_test(ALU_OPCODE_MAX, false, 16, 128, false);
-    status |= alu_test(ALU_OPCODE_ADD, false, 16, 128, true);
-    status |= alu_test(ALU_OPCODE_ADD, false, 16, 128, false);
-
-    // Run blocked GEMM test
-    status |= blocked_gemm_test(256, 256, BLOCK_OUT*4, true, 2);
-    status |= blocked_gemm_test(256, 256, BLOCK_OUT*4, false, 2);
-    status |= blocked_gemm_test(256, 256, BLOCK_OUT*4, true, 1);
-    status |= blocked_gemm_test(256, 256, BLOCK_OUT*4, false, 1);
-
-    if (status==0) {
-        printf("\nINFO - Unit tests successful!\n");
-    } else {
-        printf("\nINTO - Unit tests failed!\n");
-    }
-
-    return status;
-
+  int status = 0;
+
+  // Run ALU test (vector-scalar operators)
+  status |= alu_test(VTA_ALU_OPCODE_MAX, true, 16, 128, true);
+  status |= alu_test(VTA_ALU_OPCODE_MAX, true, 16, 128, false);
+  status |= alu_test(VTA_ALU_OPCODE_ADD, true, 16, 128, true);
+  status |= alu_test(VTA_ALU_OPCODE_ADD, true, 16, 128, false);
+  status |= alu_test(VTA_ALU_OPCODE_SHR, true, 16, 128, true);
+  status |= alu_test(VTA_ALU_OPCODE_SHR, true, 16, 128, false);
+
+  // Run ALU test (vector-vector operators)
+  status |= alu_test(VTA_ALU_OPCODE_MAX, false, 16, 128, true);
+  status |= alu_test(VTA_ALU_OPCODE_MAX, false, 16, 128, false);
+  status |= alu_test(VTA_ALU_OPCODE_ADD, false, 16, 128, true);
+  status |= alu_test(VTA_ALU_OPCODE_ADD, false, 16, 128, false);
+
+  // Run blocked GEMM test
+  status |= blocked_gemm_test(256, 256, VTA_BLOCK_OUT*4, true, 2);
+  status |= blocked_gemm_test(256, 256, VTA_BLOCK_OUT*4, false, 2);
+  status |= blocked_gemm_test(256, 256, VTA_BLOCK_OUT*4, true, 1);
+  status |= blocked_gemm_test(256, 256, VTA_BLOCK_OUT*4, false, 1);
+
+  if (status == 0) {
+    printf("\nINFO - Unit tests successful!\n");
+  } else {
+    printf("\nINTO - Unit tests failed!\n");
+  }
+
+  return status;
 }

From a5a176c8552b10d982dca5e50f0b9ae1d1adbd0a Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@cs.washington.edu>
Date: Thu, 22 Mar 2018 21:42:12 -0400
Subject: [PATCH 825/949] [PYTHON, TVM] Python TVM library, unit tests and end
 to end example

* VTA python library
* Python unit tests
* End to end example with Resnet18
* README instructions
* Bug fixes
---
 vta/Makefile                                  |   6 +-
 vta/apps/pynq_rpc/README.md                   |  80 ++
 vta/apps/pynq_rpc/start_rpc_server.sh         |   2 +-
 vta/examples/resnet18/pynq/.gitignore         |   5 +
 vta/examples/resnet18/pynq/README.md          |  98 +++
 .../resnet18/pynq/imagenet_predict.py         | 174 ++++
 vta/hardware/{vivado => xilinx}/.gitignore    |   0
 vta/hardware/{vivado => xilinx}/Makefile      |   4 +-
 vta/hardware/xilinx/README.md                 |  73 ++
 .../{vivado => xilinx}/scripts/hls.tcl        |   0
 .../{vivado => xilinx}/scripts/hsi.tcl        |   0
 .../{vivado => xilinx}/scripts/vivado.tcl     |   0
 .../{vivado => xilinx}/sim/vta_test.cc        |   2 +
 vta/hardware/{vivado => xilinx}/src/vta.cc    | 105 +--
 vta/hardware/{vivado => xilinx}/src/vta.h     |  30 +-
 vta/make/config.mk                            |   2 +-
 vta/python/vta/__init__.py                    |  13 +-
 vta/python/vta/arm_conv2d.py                  | 335 ++++++++
 vta/python/vta/build.py                       |  55 ++
 vta/python/vta/graph.py                       | 348 ++++++++
 vta/python/vta/hw_spec.py                     |  70 ++
 vta/python/vta/intrin.py                      | 183 +++++
 vta/python/vta/ir_pass.py                     | 762 ++++++++++++++++++
 vta/python/vta/mock.py                        |   7 +
 vta/python/vta/runtime.py                     |  42 +
 vta/python/vta/vta_conv2d.py                  | 373 +++++++++
 vta/src/pynq/pynq_driver.h                    |   2 +-
 vta/src/runtime.cc                            |   4 +-
 vta/tests/hardware/common/test_lib.h          |   2 +-
 vta/tests/hardware/pynq/Makefile              |   2 +-
 .../python/pynq/test_benchmark_conv2d.py      | 414 ++++++++++
 vta/tests/python/pynq/test_benchmark_gemm.py  | 267 ++++++
 vta/tests/python/pynq/test_benchmark_topi.py  | 144 ++++
 vta/tests/python/pynq/test_program_rpc.py     |  21 +
 vta/tests/python/pynq/test_vta_insn.py        | 498 ++++++++++++
 35 files changed, 4046 insertions(+), 77 deletions(-)
 create mode 100644 vta/apps/pynq_rpc/README.md
 create mode 100644 vta/examples/resnet18/pynq/.gitignore
 create mode 100644 vta/examples/resnet18/pynq/README.md
 create mode 100644 vta/examples/resnet18/pynq/imagenet_predict.py
 rename vta/hardware/{vivado => xilinx}/.gitignore (100%)
 rename vta/hardware/{vivado => xilinx}/Makefile (95%)
 create mode 100644 vta/hardware/xilinx/README.md
 rename vta/hardware/{vivado => xilinx}/scripts/hls.tcl (100%)
 rename vta/hardware/{vivado => xilinx}/scripts/hsi.tcl (100%)
 rename vta/hardware/{vivado => xilinx}/scripts/vivado.tcl (100%)
 rename vta/hardware/{vivado => xilinx}/sim/vta_test.cc (91%)
 rename vta/hardware/{vivado => xilinx}/src/vta.cc (90%)
 rename vta/hardware/{vivado => xilinx}/src/vta.h (93%)
 create mode 100644 vta/python/vta/arm_conv2d.py
 create mode 100644 vta/python/vta/build.py
 create mode 100644 vta/python/vta/graph.py
 create mode 100644 vta/python/vta/hw_spec.py
 create mode 100644 vta/python/vta/intrin.py
 create mode 100644 vta/python/vta/ir_pass.py
 create mode 100644 vta/python/vta/mock.py
 create mode 100644 vta/python/vta/runtime.py
 create mode 100644 vta/python/vta/vta_conv2d.py
 create mode 100644 vta/tests/python/pynq/test_benchmark_conv2d.py
 create mode 100644 vta/tests/python/pynq/test_benchmark_gemm.py
 create mode 100644 vta/tests/python/pynq/test_benchmark_topi.py
 create mode 100644 vta/tests/python/pynq/test_program_rpc.py
 create mode 100644 vta/tests/python/pynq/test_vta_insn.py

diff --git a/vta/Makefile b/vta/Makefile
index 74c23d691c66..0137929c9e73 100644
--- a/vta/Makefile
+++ b/vta/Makefile
@@ -55,10 +55,10 @@ endif
 all: lib/libvta.$(SHARED_LIBRARY_SUFFIX)
 
 VTA_LIB_SRC = $(wildcard src/*.cc src/tvm/*.cc)
-ifeq ($(TARGET), PYNQ_TARGET)
+ifeq ($(TARGET), VTA_PYNQ_TARGET)
 	VTA_LIB_SRC += $(wildcard src/pynq/*.cc)
 	LDFLAGS += -L/usr/lib -lsds_lib
-	LDFLAGS += -L/opt/python3.6/lib/python3.6/site-packages/pynq/drivers/ -l:libdma.so
+	LDFLAGS += -L/opt/python3.6/lib/python3.6/site-packages/pynq/lib/ -l:libdma.so
 endif
 VTA_LIB_OBJ = $(patsubst %.cc, build/%.o, $(VTA_LIB_SRC))
 
@@ -79,7 +79,7 @@ cpplint:
 	python nnvm/dmlc-core/scripts/lint.py vta cpp include src hardware tests
 
 pylint:
-	pylint python/vta --rcfile=$(ROOTDIR)/tests/lint/pylintrc
+	pylint python/tvm_vta --rcfile=$(ROOTDIR)/tests/lint/pylintrc
 
 doc:
 	doxygen docs/Doxyfile
diff --git a/vta/apps/pynq_rpc/README.md b/vta/apps/pynq_rpc/README.md
new file mode 100644
index 000000000000..4cc2b46fbcbd
--- /dev/null
+++ b/vta/apps/pynq_rpc/README.md
@@ -0,0 +1,80 @@
+### PYNQ RPC Server for VTA
+
+This guide describes how to setup a Pynq-based RPC server to accelerate deep learning workloads with VTA.
+
+## Pynq Setup
+
+Follow the getting started tutorial for the [Pynq board](http://pynq.readthedocs.io/en/latest/getting_started.html).
+* For this RPC setup make sure to go with the *Connect to a Computer* Ethernet setup.
+
+Make sure that you can ssh into your Pynq board successfully:
+```bash
+ssh xilinx@192.168.2.99
+```
+
+When ssh-ing onto the board, the default password for the `xilinx` account is `xilinx`.
+
+For convenience let's go ahead and mount the Pynq board's file system to easily access it and maintain it:
+```bash
+sshfs xilinx@192.168.2.99:/home/xilinx <mountpoint>
+```
+
+## Pynq TVM & VTA installation
+
+On your **host PC**, go to the `<mountpoint>` directory of your Pynq board file system.
+```bash
+cd <mountpoint>
+```
+
+From there, clone the VTA repository:
+```bash
+git clone git@github.com:uwsaml/vta.git --recursive
+```
+
+Next, clone the TVM repository:
+```bash
+git clone git@github.com:dmlc/tvm.git --recursive
+```
+
+TVM is rapidly changing, and to ensure stability, we keep track of working TVM checkpoints.
+As of now, the TVM checkpoint `e4c2af9abdcb3c7aabafba8084414d7739c17c4c` is known to work with VTA.
+```bash
+git checkout e4c2af9abdcb3c7aabafba8084414d7739c17c4c
+```
+
+Now, ssh into your **Pynq board** to build the TVM runtime with the following commands:
+```bash
+ssh xilinx@192.168.2.99 # ssh if you haven't done so
+cd ~/tvm
+cp make/config.mk .
+echo USE_RPC=1 >> config.mk
+make runtime -j2
+```
+
+## Pynq RPC server setup
+
+We're now ready to build the Pynq RPC server on the Pynq board.
+```bash
+ssh xilinx@192.168.2.99 # ssh if you haven't done so
+cd ~/vta
+export TVM_PATH = /home/xilinx/tvm
+make
+```
+
+The last stage will build the `192.168.2.99:home/xilinx/vta/lib/libvta.so` library file. We are now ready to launch the RPC server on the Pynq. In order to enable the FPGA drivers, we need to run the RPC server with administrator privileges (using `su`, account: `xilinx`, pwd: `xilinx`).
+```bash
+ssh xilinx@192.168.2.99 # ssh if you haven't done so
+cd ~/vta
+su
+./apps/pynq_rpc/start_rpc_server.sh
+```
+
+You should see the following being displayed when starting the RPC server:
+```
+INFO:root:Load additional library /home/xilinx/vta/lib/libvta.so
+INFO:root:RPCServer: bind to 0.0.0.0:9091
+```
+
+Note that it should be listening on port `9091`.
+
+To kill the RPC server, just enter the `Ctrl + c` command.
\ No newline at end of file
diff --git a/vta/apps/pynq_rpc/start_rpc_server.sh b/vta/apps/pynq_rpc/start_rpc_server.sh
index d5a1202a1dfc..950da23ceeb9 100755
--- a/vta/apps/pynq_rpc/start_rpc_server.sh
+++ b/vta/apps/pynq_rpc/start_rpc_server.sh
@@ -1,4 +1,4 @@
 #!/bin/bash
 export PYTHONPATH=${PYTHONPATH}:/home/xilinx/tvm/python
-export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/opt/python3.6/lib/python3.6/site-packages/pynq/drivers/
+export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/opt/python3.6/lib/python3.6/site-packages/pynq/lib/
 python -m  tvm.exec.rpc_server --load-library /home/xilinx/vta/lib/libvta.so
diff --git a/vta/examples/resnet18/pynq/.gitignore b/vta/examples/resnet18/pynq/.gitignore
new file mode 100644
index 000000000000..11686e2f9183
--- /dev/null
+++ b/vta/examples/resnet18/pynq/.gitignore
@@ -0,0 +1,5 @@
+quantize_graph.json
+quantize_params.pkl
+synset.txt
+*.jpg
+vta.bit
\ No newline at end of file
diff --git a/vta/examples/resnet18/pynq/README.md b/vta/examples/resnet18/pynq/README.md
new file mode 100644
index 000000000000..5d35fcbdda17
--- /dev/null
+++ b/vta/examples/resnet18/pynq/README.md
@@ -0,0 +1,98 @@
+# Resnet-18 Example on Pynq-based VTA Design
+
+In order to run this example you'll need to have:
+* VTA installed
+* TVM installed
+* NNVM installed
+* A Pynq-based RPC server running
+
+## VTA installation
+
+Clone the VTA repository in the directory of your choosing:
+```bash
+git clone git@github.com:uwsaml/vta.git --recursive
+```
+
+Update your `~/.bashrc` file to include the VTA python libraries in your `PYTHONPATH` (don't forget to source the newly modified `.bashrc` file!):
+```bash
+export PYTHONPATH=<vta root>/python:${PYTHONPATH}
+```
+
+## TVM installation
+
+Clone the TVM repository in the directory of your choosing:
+```bash
+git clone git@github.com:dmlc/tvm.git --recursive
+```
+
+TVM is rapidly changing, and to ensure stability, we keep track of working TVM checkpoints.
+As of now, the TVM checkpoint `e4c2af9abdcb3c7aabafba8084414d7739c17c4c` is known to work with VTA.
+```bash
+git checkout e4c2af9abdcb3c7aabafba8084414d7739c17c4c
+```
+
+Before building TVM, copy the `make/config.mk` file into the root TVM directory:
+```bash
+cd <tvm root>
+cp make/config.mk .
+```
+
+In the 'config.mk' file sure that:
+* `LLVM_CONFIG` points to the llvm-config executable (e.g. `LLVM_CONFIG = /usr/bin/llvm-config-4.0`). You'll need to have llvm4.0 installed or later.
+* `USE_RPC` should be set to 1
+
+Launch the compilation, this takes about 5 minutes.
+```bash
+cd <tvm root>
+make -j4
+```
+
+Finally update your `~/.bashrc` file to include the TVM python libraries in your `PYTHONPATH` (don't forget to source the newly modified `.bashrc` file!):
+```bash
+export PYTHONPATH=<tvm root>/python:<tvm root>/topi/python:${PYTHONPATH}
+```
+
+## NNVM installation
+
+Clone the NNVM repository from `tqchen` in the directory of your choosing:
+```bash
+git clone git@github.com:tqchen/nnvm.git --recursive
+```
+
+To run this example, we rely on a special branch of NNVM: `qt`:
+```bash
+cd <nnvm root>
+git checkout qt
+```
+
+Launch the compilation, this takes less a minute.
+```bash
+cd <nnvm root>
+make -j4
+```
+
+Finally update your `~/.bashrc` file to include the NNVM python libraries in your `PYTHONPATH` (don't forget to source the newly modified `.bashrc` file!):
+```bash
+export PYTHONPATH=<nnvm root>/python:${PYTHONPATH}
+```
+
+## Pynq RPC Server Setup
+
+Follow the [Pynq RPC Server Guide](https://github.com/saml/vta/tree/master/apps/pynq_rpc/README.md)
+
+## Running the example
+
+Simply run the following python script:
+```bash
+python imagenet_predict.py
+```
+
+This will run imagenet classification using the ResNet18 architecture on a VTA design that performs 8-bit integer inference, to perform classification on a cat image `cat.jpg`.
+
+The script reports runtime measured on the Pynq board, and the top-1 result category:
+```
+('x', (1, 3, 224, 224))
+Build complete...
+('TVM prediction top-1:', 281, 'tabby, tabby cat')
+t-cost=0.41906
+```
\ No newline at end of file
diff --git a/vta/examples/resnet18/pynq/imagenet_predict.py b/vta/examples/resnet18/pynq/imagenet_predict.py
new file mode 100644
index 000000000000..8221281bda15
--- /dev/null
+++ b/vta/examples/resnet18/pynq/imagenet_predict.py
@@ -0,0 +1,174 @@
+# some standard imports
+import nnvm
+import tvm
+from nnvm.compiler import graph_attr
+import vta
+import os
+import numpy as np
+from PIL import Image
+import pickle
+import json
+import logging
+import wget
+from tvm.contrib import graph_runtime, rpc, util
+
+factor = 16
+host = "pynq"
+port = 9091
+verbose = False
+# only run fpga component, mark non-conv ops as nop
+debug_fpga_only = False
+
+# Obtain model and hardware files (they're too large to check-in)
+url = "https://homes.cs.washington.edu/~moreau/media/vta/"
+TEST_FILE = 'cat.jpg'
+CATEG_FILE = 'synset.txt'
+RESNET_GRAPH_FILE = 'quantize_graph.json'
+RESNET_PARAMS_FILE = 'quantize_params.pkl'
+BITSTREAM_FILE = 'vta.bit'
+for file in [TEST_FILE, CATEG_FILE, RESNET_GRAPH_FILE, RESNET_PARAMS_FILE, BITSTREAM_FILE]:
+    if not os.path.isfile(file):
+        print "Downloading {}".format(file)
+        wget.download(url+file) 
+
+# Program the FPGA remotely
+assert tvm.module.enabled("rpc")
+remote = rpc.connect(host, port)
+remote.upload(BITSTREAM_FILE, BITSTREAM_FILE)
+fprogram = remote.get_function("tvm.contrib.vta.init")
+fprogram(BITSTREAM_FILE)
+
+if verbose:
+    logging.basicConfig(level=logging.INFO)
+
+# Change to -device=tcpu to run cpu only inference.
+target = "llvm -device=vta"
+
+synset = eval(open(os.path.join(CATEG_FILE)).read())
+image = Image.open(os.path.join(TEST_FILE)).resize((224, 224))
+
+def transform_image(image):
+    image = np.array(image) - np.array([123., 117., 104.])
+    image /= np.array([58.395, 57.12, 57.375])
+    image = image.transpose((2, 0, 1))
+    image = image[np.newaxis, :]
+    return image
+
+def mark_nop(graph, conv_layer=-1, skip_conv_layer=()):
+    """Helper function to mark certain op as nop
+
+    Useful to debug performance issues.
+    """
+    jgraph = json.loads(graph.json())
+    counter = 0
+    for nid, node in enumerate(jgraph["nodes"]):
+        op_name = node["op"]
+        if op_name != "tvm_op":
+            continue
+        attrs = node["attrs"]
+        node_name = node["name"]
+        func_name = attrs["func_name"]
+        if func_name.find("quantized_conv2d") != -1:
+            if conv_layer >= 0:
+                if counter != conv_layer:
+                    attrs["func_name"] = "__nop"
+            if counter in skip_conv_layer:
+                attrs["func_name"] = "__nop"
+            counter += 1
+        else:
+            if conv_layer >= 0:
+                attrs["func_name"] = "__nop"
+            attrs["func_name"] = "__nop"
+        if attrs["func_name"] != "__nop":
+            print("Run function %s"% func_name)
+    graph = nnvm.graph.load_json(json.dumps(jgraph))
+    return graph
+
+x = transform_image(image)
+print('x', x.shape)
+
+######################################################################
+# now compile the graph
+import nnvm.compiler
+np.random.seed(0)
+sym = nnvm.graph.load_json(
+    open(os.path.join(RESNET_GRAPH_FILE)).read())
+params = pickle.load(
+    open(os.path.join(RESNET_PARAMS_FILE)))
+
+shape_dict = {"data": x.shape}
+dtype_dict = {"data": 'float32'}
+shape_dict.update({k: v.shape for k, v in params.items()})
+dtype_dict.update({k: str(v.dtype) for k, v in params.items()})
+
+graph = nnvm.graph.create(sym)
+graph_attr.set_shape_inputs(sym, shape_dict)
+graph_attr.set_dtype_inputs(sym, dtype_dict)
+graph = graph.apply("InferShape").apply("InferType")
+
+dtype = "float32"
+sym = vta.graph.remove_stochastic(sym)
+sym = vta.graph.clean_cast(sym)
+sym = vta.graph.clean_conv_fuse(sym)
+if "vta" in target:
+    sym = vta.graph.pack(sym, shape_dict, factor)
+
+graph_attr.set_shape_inputs(sym, shape_dict)
+sym = sym.apply("InferShape")
+graph_attr.set_dtype_inputs(sym, dtype_dict)
+sym = sym.apply("InferType")
+
+with nnvm.compiler.build_config(opt_level=3):
+    bdict = {}
+    if "vta" not in target:
+        bdict = {"add_lower_pass": []}
+    else:
+        bdict = {"add_lower_pass": vta.debug_mode(0)}
+    with tvm.build_config(**bdict):
+        graph, lib, params = nnvm.compiler.build(
+            sym, target, shape_dict, dtype_dict,
+            params=params)
+
+remote = rpc.connect(host, port)
+temp = util.tempdir()
+lib.save(temp.relpath("graphlib.o"))
+remote.upload(temp.relpath("graphlib.o"))
+lib = remote.load_module("graphlib.o")
+ctx = remote.ext_dev(0) if "vta" in target else remote.cpu(0)
+
+print("Build complete...")
+
+def run_e2e(graph):
+    """Running end to end example
+    """
+    if debug_fpga_only:
+        graph = mark_nop(graph, skip_conv_layer=(0,))
+    m = graph_runtime.create(graph, lib, ctx)
+    # set inputs
+    m.set_input('data', tvm.nd.array(x.astype("float32")))
+    m.set_input(**params)
+    # execute
+    timer = m.module.time_evaluator("run", ctx, number=10)
+    tcost = timer()
+    # get outputs
+    tvm_output = m.get_output(
+        0,tvm.nd.empty((1000,), dtype, remote.cpu(0)))
+    top1 = np.argmax(tvm_output.asnumpy())
+    print('TVM prediction top-1:', top1, synset[top1])
+    print("t-cost=%g" % tcost.mean)
+
+
+def run_layer(old_graph):
+    """Run a certain layer."""
+    for layer_id in range(1, 2):
+        graph = mark_nop(old_graph, layer_id)
+        m = graph_runtime.create(graph, lib, ctx)
+        # set inputs
+        m.set_input('data', tvm.nd.array(x.astype("float32")))
+        m.set_input(**params)
+        # execute
+        timer = m.module.time_evaluator("run", ctx, number=10)
+        tcost = timer()
+        print("resnet[%d]: %g\n"% (layer_id, tcost.mean))
+
+run_e2e(graph)
diff --git a/vta/hardware/vivado/.gitignore b/vta/hardware/xilinx/.gitignore
similarity index 100%
rename from vta/hardware/vivado/.gitignore
rename to vta/hardware/xilinx/.gitignore
diff --git a/vta/hardware/vivado/Makefile b/vta/hardware/xilinx/Makefile
similarity index 95%
rename from vta/hardware/vivado/Makefile
rename to vta/hardware/xilinx/Makefile
index f3d779ee2a73..c7ffc7ec9871 100644
--- a/vta/hardware/vivado/Makefile
+++ b/vta/hardware/xilinx/Makefile
@@ -1,6 +1,6 @@
 # Directories
 ROOTDIR = $(CURDIR)
-BUILD_DIR = $(ROOTDIR)/../../build/hardware/vivado
+BUILD_DIR = $(ROOTDIR)/../../build/hardware/xilinx
 SCRIPT_DIR = $(ROOTDIR)/scripts
 SRC_DIR = $(ROOTDIR)/src
 SIM_DIR = $(ROOTDIR)/sim
@@ -64,7 +64,7 @@ bit: ip
 	cd $(HW_BUILD_PATH) && \
 		$(VIVADO) -mode tcl -source $(SCRIPT_DIR)/vivado.tcl \
 		-tclargs $(IP_BUILD_PATH) $(VTA_HW_COMP_THREADS) $(VTA_HW_COMP_CLOCK_FREQ) \
-		$(VTA_INP_WIDTH) $(VTA_WGT_WIDTH) $(OUT_WIDTH) \
+		$(VTA_INP_WIDTH) $(VTA_WGT_WIDTH) $(VTA_OUT_WIDTH) \
 		$(VTA_BATCH) $(VTA_IN_BLOCK) $(VTA_OUT_BLOCK) \
 		$(VTA_INP_BUFF_SIZE) $(VTA_WGT_BUFF_SIZE) $(VTA_OUT_BUFF_SIZE)
 
diff --git a/vta/hardware/xilinx/README.md b/vta/hardware/xilinx/README.md
new file mode 100644
index 000000000000..0c68724ea671
--- /dev/null
+++ b/vta/hardware/xilinx/README.md
@@ -0,0 +1,73 @@
+# Hardware Compilation Guide
+
+**This hardware compilation guide aims to provide guidance on generating VTA bitstreams with the Xilinx Vivado toolchains.**
+
+As of writing this guide, we recommend using `Vivado 2017.1` since our scripts have been tested to work on this version of the Xilinx toolchains.
+
+# Vivado Toolchains Installation for Pynq Board
+
+## Ubuntu instructions
+
+You’ll need to install Xilinx’ FPGA compilation toolchain, [Vivado HL WebPACK 2017.1](https://www.xilinx.com/products/design-tools/vivado.html), which a license-free version of the Vivado HLx toolchain.
+
+### Obtaining and launching the installation binary
+ 
+1. Go to the [download webpage](https://www.xilinx.com/support/download.html), and download the Linux Self Extracting Web Installer for Vivado HL 2017.1 WebPACK and Editions.
+2. You’ll have to sign in with a Xilinx account. This requires a Xilinx account creation that will take 2 minutes.
+3. Complete the Name and Address Verification by clicking “Next”, and you will get the opportunity to download a binary file, called `Xilinx_Vivado_SDK_2017.1_0415_1_Lin64.bin`.
+4. Now that the file is downloaded, go to your `Downloads` directory, and change the file permissions so it can be executed:
+```bash
+chmod u+x Xilinx_Vivado_SDK_2017.1_0415_1_Lin64.bin
+```
+5. Now you can execute the binary: 
+```bash
+./Xilinx_Vivado_SDK_2017.1_0415_1_Lin64.bin
+```
+
+### Installation Steps
+
+At this point you've launched the Vivado 2017.1 Installer GUI program.
+
+1. Click “Next” on the **Welcome** screen.
+2. Enter your Xilinx User Credentials under “User Authentication” and select the “Download and Install Now” before clicking “Next” on the **Select Install Type** screen.
+3. Accept all terms before clicking on “Next” on the **Accept License Agreements** screen.
+4. Select the “Vivado HL WebPACK” before clicking on “Next” on the **Select Edition to Install** screen.
+5. Under the **Vivado HL WebPACK** screen, before hitting “Next", check the following options (the rest should be unchecked):
+   * Design Tools -> Vivado Design Suite -> Vivado
+   * Design Tools -> Vivado Design Suite -> Vivado High Level Synthesis
+   * Devices -> Production Services -> SoCs -> Zynq-7000 Series
+6. Your total download size should be about 3GB and the amount of Disk Space Required 13GB.
+7. Set the installation directory before clicking “Next” on the **Select Destination Directory** screen. It might highlight some paths as red - that’s because the installer doesn’t have the permission to write to that directory. In that case select a path that doesn’t require special write permissions (e.g. in your home directory).
+8. Hit “Install” under the **Installation Summary** screen.
+9. An **Installation Progress Window** will pop-up to track progress of the download and the installation.
+10. This process will take about 20-30 minutes depending on your connection speed.
+11. A pop-up window will inform you that the installation completed successfully. Click "OK".
+12. Finally the **Vivado License Manager** will launch. Select "Get Free ISE WebPACK, ISE/Vivado IP or PetaLinux License" and click "Connect Now" to complete the license registration process. 
+
+### Environment Setup
+
+The last step is to update your `~/.bashrc` with the following line:
+```bash
+# Xilinx Vivado 2017.1 environment
+source <install_path>/Vivado/2017.1/settings64.sh
+```
+
+This will include all of the Xilinx binary paths so you can launch compilation scripts from the command line.
+
+Note that this will overwrite the paths to GCC required to build TVM, or NNVM. Therefore, when attempting to build TVM and NNVM, please comment this line from your `~/.bashrc` before re-sourcing it.
+
+# Bitstream compilation
+
+High-level parameters are listed under `<vta root>/make/config.mk` and can be customized by the user.
+
+Bitstream generation is driven by a makefile. All it takes is to enter the following command:
+```bash
+make
+```
+
+The local `Makefile` containts several variables that can be tweaked by the user:
+* `VTA_HW_COMP_THREADS`: determines the number of threads used for the Vivado compilation job (default 8 threads).
+* `VTA_HW_COMP_CLOCK_FREQ`: determines the target frequency of the VTA design (default 100MHz). It can only be set to 100, 142, 167 or 200MHz.
+* `VTA_HW_COMP_TIMING_COMP`: determines how much additional slack must be provided to close timing (default 0ns). Generally when utilization is high for an FPGA design, setting this paramter to 1, 2 or 3 can help close timing.
+
+Once the compilation completes, the generated bitstream can be found under `<vta root>/build/hardware/xilinx/vivado/<design name>/export/vta.bit`. 
\ No newline at end of file
diff --git a/vta/hardware/vivado/scripts/hls.tcl b/vta/hardware/xilinx/scripts/hls.tcl
similarity index 100%
rename from vta/hardware/vivado/scripts/hls.tcl
rename to vta/hardware/xilinx/scripts/hls.tcl
diff --git a/vta/hardware/vivado/scripts/hsi.tcl b/vta/hardware/xilinx/scripts/hsi.tcl
similarity index 100%
rename from vta/hardware/vivado/scripts/hsi.tcl
rename to vta/hardware/xilinx/scripts/hsi.tcl
diff --git a/vta/hardware/vivado/scripts/vivado.tcl b/vta/hardware/xilinx/scripts/vivado.tcl
similarity index 100%
rename from vta/hardware/vivado/scripts/vivado.tcl
rename to vta/hardware/xilinx/scripts/vivado.tcl
diff --git a/vta/hardware/vivado/sim/vta_test.cc b/vta/hardware/xilinx/sim/vta_test.cc
similarity index 91%
rename from vta/hardware/vivado/sim/vta_test.cc
rename to vta/hardware/xilinx/sim/vta_test.cc
index 16f37a866464..60685b384aca 100644
--- a/vta/hardware/vivado/sim/vta_test.cc
+++ b/vta/hardware/xilinx/sim/vta_test.cc
@@ -40,6 +40,8 @@ int main(void) {
     status |= alu_test(VTA_ALU_OPCODE_ADD, true, 16, 128, false);
     status |= alu_test(VTA_ALU_OPCODE_SHR, true, 16, 128, true);
     status |= alu_test(VTA_ALU_OPCODE_SHR, true, 16, 128, false);
+    status |= alu_test(VTA_ALU_OPCODE_SHL, true, 16, 128, true);
+    status |= alu_test(VTA_ALU_OPCODE_SHL, true, 16, 128, false);
 
     // Run ALU test (vector-vector operators)
     status |= alu_test(VTA_ALU_OPCODE_MIN, false, 16, 128, true);
diff --git a/vta/hardware/vivado/src/vta.cc b/vta/hardware/xilinx/src/vta.cc
similarity index 90%
rename from vta/hardware/vivado/src/vta.cc
rename to vta/hardware/xilinx/src/vta.cc
index f628b749d23c..815dc014b887 100644
--- a/vta/hardware/vivado/src/vta.cc
+++ b/vta/hardware/xilinx/src/vta.cc
@@ -13,9 +13,9 @@
 void fetch(
   uint32_t insn_count,
   volatile insn_T *insns,
-  hls::stream<insn_T> *load_queue,
-  hls::stream<insn_T> *gemm_queue,
-  hls::stream<insn_T> *store_queue) {
+  hls::stream<insn_T> &load_queue,
+  hls::stream<insn_T> &gemm_queue,
+  hls::stream<insn_T> &store_queue) {
 #pragma HLS INTERFACE s_axilite port = insn_count bundle = CONTROL_BUS
 #pragma HLS INTERFACE m_axi port = insns offset = slave bundle = ins_port
 #pragma HLS INTERFACE axis port = load_queue
@@ -32,12 +32,12 @@ void fetch(
     memop_id_T memory_type = insn.range(VTA_INSN_MEM_5_1, VTA_INSN_MEM_5_0);
     // Push to appropriate instruction queue
     if (opcode == VTA_OPCODE_STORE) {
-      store_queue->write(insn);
+      store_queue.write(insn);
     } else if (opcode == VTA_OPCODE_LOAD &&
           (memory_type == VTA_MEM_ID_INP || memory_type == VTA_MEM_ID_WGT)) {
-      load_queue->write(insn);
+      load_queue.write(insn);
     } else {
-      gemm_queue->write(insn);
+      gemm_queue.write(insn);
     }
   }
 }
@@ -45,9 +45,9 @@ void fetch(
 void load(
   volatile inp_vec_T *inputs,
   volatile wgt_vec_T *weights,
-  hls::stream<insn_T> *load_queue,
-  hls::stream<bool> *g2l_dep_queue,
-  hls::stream<bool> *l2g_dep_queue,
+  hls::stream<insn_T> &load_queue,
+  hls::stream<bool> &g2l_dep_queue,
+  hls::stream<bool> &l2g_dep_queue,
   inp_vec_T inp_mem[VTA_INP_BUFF_DEPTH][VTA_BATCH],
   wgt_vec_T wgt_mem[VTA_WGT_BUFF_DEPTH][VTA_BLOCK_OUT]
   ) {
@@ -61,7 +61,7 @@ void load(
 #pragma HLS INTERFACE s_axilite port = return bundle = CONTROL_BUS
 
   // Pop load instruction
-  insn_T insn = load_queue->read();
+  insn_T insn = load_queue.read();
 
   // Decode instruction
   bool pop_prev_dependence = insn[VTA_INSN_MEM_1];
@@ -81,7 +81,7 @@ void load(
 
   // Pop dependence token if instructed
   if (pop_next_dependence) {
-    g2l_dep_queue->read();
+    g2l_dep_queue.read();
   }
 
   // Initialize indices
@@ -170,19 +170,19 @@ void load(
 
   // Push dependence token if instructed
   if (push_next_dependence) {
-    l2g_dep_queue->write(1);
+    l2g_dep_queue.write(1);
   }
 }
 
 void compute(
-  volatile uint32_t *done,
+  volatile uint32_t &done,
   volatile uop_T *uops,
   volatile acc_vec_T *biases,
-  hls::stream<insn_T> *gemm_queue,
-  hls::stream<bool> *l2g_dep_queue,
-  hls::stream<bool> *s2g_dep_queue,
-  hls::stream<bool> *g2l_dep_queue,
-  hls::stream<bool> *g2s_dep_queue,
+  hls::stream<insn_T> &gemm_queue,
+  hls::stream<bool> &l2g_dep_queue,
+  hls::stream<bool> &s2g_dep_queue,
+  hls::stream<bool> &g2l_dep_queue,
+  hls::stream<bool> &g2s_dep_queue,
   out_vec_T inp_mem[VTA_INP_BUFF_DEPTH][VTA_BATCH],
   wgt_vec_T wgt_mem[VTA_WGT_BUFF_DEPTH][VTA_BLOCK_OUT],
   out_vec_T out_mem[VTA_ACC_BUFF_DEPTH][VTA_BATCH]
@@ -210,7 +210,7 @@ void compute(
 #pragma HLS ARRAY_PARTITION variable = acc_mem complete dim = 2
 
   // Pop GEMM instruction
-  insn_T insn = gemm_queue->read();
+  insn_T insn = gemm_queue.read();
 
   // Decode
   opcode_T opcode = insn.range(VTA_INSN_MEM_0_1, VTA_INSN_MEM_0_0);
@@ -221,19 +221,19 @@ void compute(
 
   // Pop dependence token if instructed
   if (pop_prev_dependence) {
-    l2g_dep_queue->read();
+    l2g_dep_queue.read();
   }
   if (pop_next_dependence) {
-    s2g_dep_queue->read();
+    s2g_dep_queue.read();
   }
 
   // Perform action based on opcode
   if (opcode == VTA_OPCODE_FINISH) {
     // Set done flag if we reach a FINISH instruction
-    *done = 1;
+    done = 1;
   } else if (opcode == VTA_OPCODE_LOAD || opcode == VTA_OPCODE_STORE) {
     // Set done value
-    *done = 0;
+    done = 0;
 
     // Decode instruction
     memop_id_T memory_type = insn.range(VTA_INSN_MEM_5_1, VTA_INSN_MEM_5_0);
@@ -283,7 +283,7 @@ void compute(
     }
   } else if (opcode == VTA_OPCODE_GEMM || opcode == VTA_OPCODE_ALU) {
     // Set done value
-    *done = 0;
+    done = 0;
 
     // Decode
     uop_idx_T uop_bgn = insn.range(VTA_INSN_GEM_5_1, VTA_INSN_GEM_5_0);
@@ -383,6 +383,7 @@ void compute(
         } else if (opcode == VTA_OPCODE_ALU) {
           // Iterate over micro op
           READ_ALU_UOP: for (int upc = uop_bgn; upc < uop_end; upc++) {
+#pragma HLS PIPELINE II = 2 rewind
             // Read micro-op fields
             uop_T uop = uop_mem[upc];
 
@@ -405,14 +406,15 @@ void compute(
             // Result matrices
             acc_vec_T cmp_res[VTA_BATCH];
             acc_vec_T add_res[VTA_BATCH];
-            acc_vec_T shr_res[VTA_BATCH];
+            acc_vec_T rshr_res[VTA_BATCH];
+            acc_vec_T lshr_res[VTA_BATCH];
             out_vec_T short_cmp_res[VTA_BATCH];
             out_vec_T short_add_res[VTA_BATCH];
-            out_vec_T short_shr_res[VTA_BATCH];
+            out_vec_T short_rshr_res[VTA_BATCH];
+            out_vec_T short_lshr_res[VTA_BATCH];
 
             // Perform ALU op over matrix elements
             for (int i = 0; i < VTA_BATCH; i++) {
-#pragma HLS PIPELINE II = 1 rewind
               // Results vector
               acc_vec_T res_vec = 0;
               for (int b = 0; b < VTA_BLOCK_OUT; b++) {
@@ -434,12 +436,18 @@ void compute(
                 add_res[i].range((b + 1) * VTA_ACC_WIDTH - 1, b * VTA_ACC_WIDTH) = add_val;
                 short_add_res[i].range((b + 1) * VTA_OUT_WIDTH - 1, b * VTA_OUT_WIDTH) =
                     (inp_T) add_val.range(VTA_OUT_WIDTH - 1, 0);
-                // Compute Shift
-                acc_T shr_val =
+                // Compute Right Shift
+                acc_T rshr_val =
                     src_0 >> (aluop_sh_imm_T) src_1.range(VTA_LOG_ACC_WIDTH - 1, 0);
-                shr_res[i].range((b + 1) * VTA_ACC_WIDTH - 1, b * VTA_ACC_WIDTH) = shr_val;
-                short_shr_res[i].range((b + 1) * VTA_OUT_WIDTH - 1, b * VTA_OUT_WIDTH) =
-                    (inp_T) shr_val.range(VTA_OUT_WIDTH-1, 0);
+                rshr_res[i].range((b + 1) * VTA_ACC_WIDTH - 1, b * VTA_ACC_WIDTH) = rshr_val;
+                short_rshr_res[i].range((b + 1) * VTA_OUT_WIDTH - 1, b * VTA_OUT_WIDTH) =
+                    (inp_T) rshr_val.range(VTA_OUT_WIDTH-1, 0);
+                // Compute Left Shift
+                acc_T lshr_val =
+                    src_0 << (aluop_sh_imm_T) src_1.range(VTA_LOG_ACC_WIDTH - 1, 0);
+                lshr_res[i].range((b + 1) * VTA_ACC_WIDTH - 1, b * VTA_ACC_WIDTH) = lshr_val;
+                short_lshr_res[i].range((b + 1) * VTA_OUT_WIDTH - 1, b * VTA_OUT_WIDTH) =
+                    (inp_T) lshr_val.range(VTA_OUT_WIDTH-1, 0);
               }
 
               // Store to accum memory/store buffer
@@ -451,8 +459,11 @@ void compute(
                 acc_mem[dst_idx][i] = add_res[i];
                 out_mem[dst_idx][i] = short_add_res[i];
               } else if (alu_opcode == VTA_ALU_OPCODE_SHR) {
-                acc_mem[dst_idx][i] = shr_res[i];
-                out_mem[dst_idx][i] = short_shr_res[i];
+                acc_mem[dst_idx][i] = rshr_res[i];
+                out_mem[dst_idx][i] = short_rshr_res[i];
+              } else if (alu_opcode == VTA_ALU_OPCODE_SHL) {
+                acc_mem[dst_idx][i] = lshr_res[i];
+                out_mem[dst_idx][i] = short_lshr_res[i];
               }
             }
           }
@@ -473,18 +484,18 @@ void compute(
 
   // Push dependence token if instructed
   if (push_prev_dependence) {
-    g2l_dep_queue->write(1);
+    g2l_dep_queue.write(1);
   }
   if (push_next_dependence) {
-    g2s_dep_queue->write(1);
+    g2s_dep_queue.write(1);
   }
 }
 
 void store(
   volatile out_vec_T *outputs,
-  hls::stream<insn_T> *store_queue,
-  hls::stream<bool> *g2s_dep_queue,
-  hls::stream<bool> *s2g_dep_queue,
+  hls::stream<insn_T> &store_queue,
+  hls::stream<bool> &g2s_dep_queue,
+  hls::stream<bool> &s2g_dep_queue,
   out_vec_T out_mem[VTA_ACC_BUFF_DEPTH][VTA_BATCH]
   ) {
 #pragma HLS INTERFACE m_axi port = outputs offset = slave bundle = data_port
@@ -495,7 +506,7 @@ void store(
 #pragma HLS INTERFACE s_axilite port = return bundle = CONTROL_BUS
 
   // Load buffer
-  insn_T insn = store_queue->read();
+  insn_T insn = store_queue.read();
 
   // Decode
   bool pop_prev_dependence = insn[VTA_INSN_MEM_1];
@@ -515,7 +526,7 @@ void store(
 
   // Pop dependence token if instructed
   if (pop_prev_dependence) {
-    g2s_dep_queue->read();
+    g2s_dep_queue.read();
   }
 
   // Initialize indices
@@ -546,7 +557,7 @@ void store(
 
   // Push dependence token if instructed
   if (push_prev_dependence) {
-    s2g_dep_queue->write(1);
+    s2g_dep_queue.write(1);
   }
 }
 
@@ -589,7 +600,7 @@ void vta(
   out_vec_T out_mem[VTA_ACC_BUFF_DEPTH][VTA_BATCH];
 
   // Push all instructions into the queues
-  fetch(insn_count, insns, &tmp_load_queue, &tmp_gemm_queue, &tmp_store_queue);
+  fetch(insn_count, insns, tmp_load_queue, tmp_gemm_queue, tmp_store_queue);
 
   // Global done indicator
   uint32_t done = 0;
@@ -621,7 +632,7 @@ void vta(
         // Push the instruction in the load queue
         load_queue.write(tmp_load);
         tmp_load_popped = false;
-        load(inputs, weights, &load_queue, &g2l_dep_queue, &l2g_dep_queue, inp_mem, wgt_mem);
+        load(inputs, weights, load_queue, g2l_dep_queue, l2g_dep_queue, inp_mem, wgt_mem);
       } else {
         // Execution of load stage pending on completion of other stages, so break here...
         break;
@@ -649,8 +660,8 @@ void vta(
         // Push the instruction in the load queue
         gemm_queue.write(tmp_gemv);
         tmp_gemm_popped = false;
-        compute(&done, uops, biases, &gemm_queue, &l2g_dep_queue, &s2g_dep_queue,
-                &g2l_dep_queue, &g2s_dep_queue, inp_mem, wgt_mem, out_mem);
+        compute(done, uops, biases, gemm_queue, l2g_dep_queue, s2g_dep_queue,
+                g2l_dep_queue, g2s_dep_queue, inp_mem, wgt_mem, out_mem);
       } else {
         // Execution of load stage pending on completion of other stages,
         // so break here...
@@ -671,7 +682,7 @@ void vta(
         // Push the instruction in the load queue
         store_queue.write(tmp_store);
         tmp_store_popped = false;
-        store(outputs, &store_queue, &g2s_dep_queue, &s2g_dep_queue, out_mem);
+        store(outputs, store_queue, g2s_dep_queue, s2g_dep_queue, out_mem);
       } else {
         // Execution of load stage pending on completion of other stages, so break here...
         break;
diff --git a/vta/hardware/vivado/src/vta.h b/vta/hardware/xilinx/src/vta.h
similarity index 93%
rename from vta/hardware/vivado/src/vta.h
rename to vta/hardware/xilinx/src/vta.h
index 37395722f5f7..e01ef90bb244 100644
--- a/vta/hardware/vivado/src/vta.h
+++ b/vta/hardware/xilinx/src/vta.h
@@ -107,9 +107,9 @@ typedef ap_uint<VTA_LOG_ACC_WIDTH> aluop_sh_imm_T;
 void fetch(
   uint32_t insn_count,
   volatile insn_T *insns,
-  hls::stream<insn_T> *load_queue,
-  hls::stream<insn_T> *gemm_queue,
-  hls::stream<insn_T> *store_queue);
+  hls::stream<insn_T> &load_queue,
+  hls::stream<insn_T> &gemm_queue,
+  hls::stream<insn_T> &store_queue);
 
 /*!
 * \brief Load module.
@@ -129,9 +129,9 @@ void fetch(
 void load(
   volatile inp_vec_T *inputs,
   volatile wgt_vec_T *weights,
-  hls::stream<insn_T> *load_queue,
-  hls::stream<bool> *g2l_dep_queue,
-  hls::stream<bool> *l2g_dep_queue,
+  hls::stream<insn_T> &load_queue,
+  hls::stream<bool> &g2l_dep_queue,
+  hls::stream<bool> &l2g_dep_queue,
   inp_vec_T inp_mem[VTA_INP_BUFF_DEPTH][VTA_BATCH],
   wgt_vec_T wgt_mem[VTA_WGT_BUFF_DEPTH][VTA_BLOCK_OUT]);
 
@@ -159,14 +159,14 @@ void load(
 * \param out_mem Local output SRAM buffer. Write only single port BRAM.
 */
 void compute(
-  volatile uint32_t *done,
+  volatile uint32_t &done,
   volatile uop_T *uops,
   volatile acc_vec_T *biases,
-  hls::stream<insn_T> *gemm_queue,
-  hls::stream<bool> *l2g_dep_queue,
-  hls::stream<bool> *s2g_dep_queue,
-  hls::stream<bool> *g2l_dep_queue,
-  hls::stream<bool> *g2s_dep_queue,
+  hls::stream<insn_T> &gemm_queue,
+  hls::stream<bool> &l2g_dep_queue,
+  hls::stream<bool> &s2g_dep_queue,
+  hls::stream<bool> &g2l_dep_queue,
+  hls::stream<bool> &g2s_dep_queue,
   out_vec_T inp_mem[VTA_INP_BUFF_DEPTH][VTA_BATCH],
   wgt_vec_T wgt_mem[VTA_WGT_BUFF_DEPTH][VTA_BLOCK_OUT],
   out_vec_T out_mem[VTA_ACC_BUFF_DEPTH][VTA_BATCH]);
@@ -186,9 +186,9 @@ void compute(
 */
 void store(
   volatile out_vec_T *outputs,
-  hls::stream<insn_T> *store_queue,
-  hls::stream<bool> *g2s_dep_queue,
-  hls::stream<bool> *s2g_dep_queue,
+  hls::stream<insn_T> &store_queue,
+  hls::stream<bool> &g2s_dep_queue,
+  hls::stream<bool> &s2g_dep_queue,
   out_vec_T out_mem[VTA_ACC_BUFF_DEPTH][VTA_BATCH]);
 
 /*!
diff --git a/vta/make/config.mk b/vta/make/config.mk
index 06143d777167..062dfa8c336c 100644
--- a/vta/make/config.mk
+++ b/vta/make/config.mk
@@ -84,7 +84,7 @@ VTA_ACC_BUFF_SIZE = $(shell echo "$$(( 1 << $(VTA_LOG_ACC_BUFF_SIZE) ))" )
 VTA_LOG_OUT_BUFF_SIZE = \
 $(shell echo "$$(( $(VTA_LOG_ACC_BUFF_SIZE) + $(VTA_LOG_OUT_WIDTH) - $(VTA_LOG_ACC_WIDTH) ))" )
 #  Out buffer size in Bytes
-VTA_OUT_BUFF_SIZE = $(shell echo "$$(( 1 << $(LOG_OUT_BUFF_SIZE) ))" )
+VTA_OUT_BUFF_SIZE = $(shell echo "$$(( 1 << $(VTA_LOG_OUT_BUFF_SIZE) ))" )
 
 # Update ADD_CFLAGS
 ADD_CFLAGS += \
diff --git a/vta/python/vta/__init__.py b/vta/python/vta/__init__.py
index 59a494fc7ef5..4a6f760d0bfc 100644
--- a/vta/python/vta/__init__.py
+++ b/vta/python/vta/__init__.py
@@ -1,5 +1,12 @@
-"""VTA Python package backed by TVM"""
+"""TVM VTA runtime"""
+from __future__ import absolute_import as _abs
 
+from .hw_spec import *
 
-# version of this package
-__version__ = "0.1.0"
+from .runtime import SCOPE_INP, SCOPE_OUT, SCOPE_WGT, DMA_COPY, ALU
+from .intrin import GEVM, GEMM
+from .build import debug_mode
+
+from . import mock, ir_pass
+from . import arm_conv2d, vta_conv2d
+from . import graph
diff --git a/vta/python/vta/arm_conv2d.py b/vta/python/vta/arm_conv2d.py
new file mode 100644
index 000000000000..9e46ee7f8c61
--- /dev/null
+++ b/vta/python/vta/arm_conv2d.py
@@ -0,0 +1,335 @@
+# pylint: disable=invalid-name,unused-variable,invalid-name
+"""Conv2D schedule ported from RASP
+
+Used for CPU conv2d
+"""
+from __future__ import absolute_import as _abs
+
+import tvm
+from topi import tag
+from topi.nn.conv2d import conv2d, _get_schedule
+from topi.nn.conv2d import SpatialPack, Im2ColPack, Workload
+from topi.nn.conv2d import _SCH_TO_DECL_FUNC
+from topi.nn.conv2d import _get_workload
+from topi.nn.util import infer_pad, infer_stride
+from topi import generic
+
+_WORKLOADS = [
+    Workload('int8', 'int32', 224, 224, 3, 64, 7, 7, 3, 3, 2, 2),
+    Workload('int8', 'int32', 56, 56, 64, 64, 3, 3, 1, 1, 1, 1),
+    Workload('int8', 'int32', 56, 56, 64, 64, 1, 1, 0, 0, 1, 1),
+    Workload('int8', 'int32', 56, 56, 64, 128, 3, 3, 1, 1, 2, 2),
+    Workload('int8', 'int32', 56, 56, 64, 128, 1, 1, 0, 0, 2, 2),
+    Workload('int8', 'int32', 28, 28, 128, 128, 3, 3, 1, 1, 1, 1),
+    Workload('int8', 'int32', 28, 28, 128, 256, 3, 3, 1, 1, 2, 2),
+    Workload('int8', 'int32', 28, 28, 128, 256, 1, 1, 0, 0, 2, 2),
+    Workload('int8', 'int32', 14, 14, 256, 256, 3, 3, 1, 1, 1, 1),
+    Workload('int8', 'int32', 14, 14, 256, 512, 3, 3, 1, 1, 2, 2),
+    Workload('int8', 'int32', 14, 14, 256, 512, 1, 1, 0, 0, 2, 2),
+    Workload('int8', 'int32', 7, 7, 512, 512, 3, 3, 1, 1, 1, 1),
+]
+_SCHEDULES = [
+    # float32 imagenet
+    SpatialPack(1, 8, 4, 1, 4, True),
+    SpatialPack(1, 7, 4, 2, 4, True),
+    SpatialPack(1, 4, 8, 4, 1, True),
+    SpatialPack(1, 4, 4, 1, 16, False),
+    SpatialPack(1, 4, 8, 4, 8, False),
+    SpatialPack(1, 7, 4, 3, 8, True),
+    SpatialPack(1, 2, 8, 1, 8, True),
+    SpatialPack(2, 1, 16, 1, 4, True),
+    SpatialPack(1, 7, 4, 1, 1, True),
+    Im2ColPack(7, 4, 1, 16, True),
+    Im2ColPack(7, 4, 1, 8, False),
+    Im2ColPack(7, 4, 1, 16, False),
+]
+
+@_get_schedule.register(["tcpu", "vta"])
+def _schedule_conv2d(wkl):
+    if wkl not in _WORKLOADS:
+        raise ValueError("no schedule for such workload: {}".format(wkl))
+    idx = _WORKLOADS.index(wkl)
+    sch = _SCHEDULES[idx]
+    return sch
+
+
+@conv2d.register(["tcpu", "vta"])
+def _declaration_conv2d(data, kernel, stride, padding, layout, out_dtype):
+    assert layout == 'NCHW', "only support NCHW convolution on tcpu"
+    assert data.shape[0].value == 1, "only support batch size=1 convolution on tcpu"
+    wkl = _get_workload(data, kernel, stride, padding, out_dtype)
+    sch = _get_schedule(wkl)
+    return _SCH_TO_DECL_FUNC[type(sch)](data, kernel, stride, padding, out_dtype)
+
+
+def _schedule_spatial_conv2d(s, data, data_pad, data_vec,
+                             kernel, kernel_vec,
+                             conv_out, output, last):
+    # no stride and padding info here
+    padding = infer_pad(data, data_pad)
+    if data_pad is None:
+        stride = infer_stride(data, kernel, output)
+    else:
+        stride = infer_stride(data_pad, kernel, output)
+    wkl = _get_workload(data, kernel, stride, padding, output.dtype)
+    sch = _get_schedule(wkl)
+
+    H, W = wkl.height, wkl.width
+    CI, CO = wkl.in_filter, wkl.out_filter
+    HK, WK = wkl.hkernel, wkl.wkernel
+    HPAD, WPAD = wkl.hpad, wkl.wpad
+    HSTR, WSTR = wkl.hstride, wkl.wstride
+
+    HCAT, WCAT = HK-1, WK-1
+    DOPAD = (HPAD != 0 and WPAD != 0)
+
+    VH = sch.vh
+    VW = sch.vw
+    VC = sch.vc
+    UNROLL = sch.unroll
+
+    A, B, C = data, kernel, last
+    A0, A1 = data_pad, data_vec
+    B0 = kernel_vec
+    C0, C1 = conv_out, output
+
+    CC = s.cache_write(C0, "global")
+
+    _, co, oh, ow, vh, vw, vc = s[C0].op.axis
+    if UNROLL:
+        s[C0].unroll(vw)
+    s[C0].vectorize(vc)
+
+    s[CC].compute_at(s[C0], ow)
+    _, co, oh, ow, vh, vw, vc = s[CC].op.axis
+    ci, dh, dw = s[CC].op.reduce_axis
+    s[CC].reorder(ci, dh, vh, dw, vw, vc)
+
+    if UNROLL:
+        s[CC].unroll(vw)
+    s[CC].vectorize(vc)
+
+    ##### Schedule A
+    if DOPAD:
+        s[A0].compute_inline()
+
+    _, h, _, _, _, _ = s[A1].op.axis
+    if sch.ba == 1:
+        oaxis = h
+        paxis = h
+    else:
+        oh, ih = s[A1].split(h, sch.ba)
+        oaxis = oh
+        paxis = ih
+
+    s[A1].parallel(paxis)
+    s[A1].pragma(oaxis, "parallel_launch_point")
+    s[A1].pragma(paxis, "parallel_stride_pattern")
+    s[A1].pragma(oaxis, "parallel_barrier_when_finish")
+
+
+    ##### Schedule B
+    co, _, _, _, _ = s[B0].op.axis
+    if sch.bc == 1:
+        oaxis = co
+        paxis = co
+    else:
+        oco, ico = s[B0].split(co, sch.bc)
+        oaxis = oco
+        paxis = ico
+
+    s[B0].parallel(paxis)
+    s[B0].pragma(oaxis, "parallel_launch_point")
+    s[B0].pragma(paxis, "parallel_stride_pattern")
+    s[B0].pragma(oaxis, "parallel_barrier_when_finish")
+
+
+    ##### Schedule C
+    n, co, h, w = s[C].op.axis
+    co, vc = s[C].split(co, VC)
+    oh, ow, vh, vw = s[C].tile(h, w, VH, VW)
+    s[C].reorder(n, co, oh, ow, vh, vw, vc)
+    if C != C1:
+        s[C1].compute_inline()
+    s[C0].compute_at(s[C], ow)
+
+    if sch.bc == 1:
+        oaxis = co
+        paxis = co
+    else:
+        oco, ico = s[C].split(co, sch.bc)
+        oaxis = oco
+        paxis = ico
+
+    s[C].parallel(paxis)
+    s[C].pragma(oaxis, "parallel_launch_point")
+    s[C].pragma(paxis, "parallel_stride_pattern")
+    s[C].pragma(oaxis, "parallel_barrier_when_finish")
+
+    return s
+
+def _schedule_im2col_conv2d(s, data, data_pad, data_col, data_vec,
+                            kernel, kernel_vec,
+                            conv_out, output, last):
+    # no stride and padding info here
+    padding = infer_pad(data, data_pad)
+    if data_pad is None:
+        stride = infer_stride(data, kernel, output)
+    else:
+        stride = infer_stride(data_pad, kernel, output)
+    wkl = _get_workload(data, kernel, stride, padding, output.dtype)
+
+    sch = _get_schedule(wkl)
+
+    H, W = wkl.height, wkl.width
+    CI = wkl.in_filter
+    CO = wkl.out_filter
+    HK, WK = wkl.hkernel, wkl.wkernel
+    HPAD, WPAD = wkl.hpad, wkl.wpad
+    HSTR, WSTR = wkl.hstride, wkl.wstride
+
+    HCAT, WCAT = HK-1, WK-1
+    DOPAD = (HPAD != 0 and WPAD != 0)
+
+    P = sch.vp
+    Q = sch.vq
+    UNROLL = sch.unroll
+
+    A, B, C = data, kernel, last
+    A0, A1, A2 = data_pad, data_col, data_vec
+    B0 = kernel_vec
+    C0, C1 = conv_out, output
+
+    CC = s.cache_write(C0, "global")
+    AA = s.cache_read(A2, "global", [CC])
+    BB = s.cache_read(B0, "global", [CC])
+
+
+    ##### Schedule CC
+    _, co, im, vim, vco = s[C0].op.axis
+    s[C0].unroll(vim)
+    s[C0].vectorize(vco)
+
+    s[CC].compute_at(s[C0], im)
+    _, co, im, vim, vco = s[CC].op.axis
+    ci, hk, wk = s[CC].op.reduce_axis
+    s[CC].reorder(ci, hk, wk, vim, vco)
+    s[CC].unroll(vim)
+    s[CC].vectorize(vco)
+    # s[CC].unroll(ccr)
+
+    ### Schedule C
+    _, co, h, w = s[C].op.axis
+    im = s[C].fuse(h, w)
+    im, vim = s[C].split(im, P)
+    co, vco = s[C].split(co, Q)
+    s[C].reorder(co, im, vim, vco)
+
+    if sch.bc == 1:
+        oaxis = co
+        paxis = co
+    else:
+        oco, ico = s[C].split(co, sch.bc)
+        oaxis = oco
+        paxis = ico
+
+    s[C].parallel(paxis)
+    s[C].pragma(oaxis, "parallel_launch_point")
+    s[C].pragma(paxis, "parallel_stride_pattern")
+    s[C].pragma(oaxis, "parallel_barrier_when_finish")
+    if C1 != C:
+        s[C1].compute_inline()
+
+    s[C0].compute_at(s[C], paxis)
+
+    ##### Schedule A
+    if DOPAD:
+        s[A0].compute_inline()
+    s[A1].compute_inline()
+    s[AA].compute_at(s[CC], wk)
+    s[AA].unroll(AA.op.axis[4])
+
+    _, im, _, _, _, _ = s[A2].op.axis
+    if sch.ba == 1:
+        oaxis = im
+        paxis = im
+    else:
+        oim, iim = s[A2].split(im, sch.ba)
+        oaxis = oim
+        paxis = iim
+
+    s[A2].parallel(paxis)
+    s[A2].pragma(oaxis, "parallel_launch_point")
+    s[A2].pragma(paxis, "parallel_stride_pattern")
+    s[A2].pragma(oaxis, "parallel_barrier_when_finish")
+
+
+    ##### Schedule B
+    s[BB].compute_at(s[CC], wk)
+    s[BB].vectorize(BB.op.axis[4])
+
+    co, _, _, _, _ = s[B0].op.axis
+    if sch.bc == 1:
+        oaxis = co
+        paxis = co
+    else:
+        oco, ico = s[B0].split(co, sch.bc)
+        oaxis = oco
+        paxis = ico
+
+    s[B0].parallel(paxis)
+    s[B0].pragma(oaxis, "parallel_launch_point")
+    s[B0].pragma(paxis, "parallel_stride_pattern")
+    s[B0].pragma(oaxis, "parallel_barrier_when_finish")
+
+    return s
+
+@generic.schedule_conv2d_nchw.register(["tcpu", "vta"])
+def schedule_conv2d(outs):
+    """Create schedule for tensors"""
+    s = tvm.create_schedule([x.op for x in outs])
+
+    def traverse(op):
+        """Traverse operators from computation graph"""
+        # inline all one-to-one-mapping operators except the last stage (output)
+        if tag.is_broadcast(op.tag):
+            if op not in s.outputs:
+                s[op].compute_inline()
+            for tensor in op.input_tensors:
+                if tensor.op.input_tensors:
+                    traverse(tensor.op)
+
+        if 'spatial_conv_output' in op.tag:
+            output = op.output(0)
+            conv_out = op.input_tensors[0]
+            kernel_vec = conv_out.op.input_tensors[1]
+            kernel = kernel_vec.op.input_tensors[0]
+            data_vec = conv_out.op.input_tensors[0]
+            data = data_vec.op.input_tensors[0]
+            data_pad = None
+            if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
+                data_pad = data
+                data = data_pad.op.input_tensors[0]
+
+            _schedule_spatial_conv2d(s, data, data_pad, data_vec,
+                                     kernel, kernel_vec,
+                                     conv_out, output, outs[0])
+
+        if 'im2col_conv_output' in op.tag:
+            output = op.output(0)
+            conv_out = op.input_tensors[0]
+            kernel_vec = conv_out.op.input_tensors[1]
+            kernel = kernel_vec.op.input_tensors[0]
+            data_vec = conv_out.op.input_tensors[0]
+            data_col = data_vec.op.input_tensors[0]
+            data = data_col.op.input_tensors[0]
+            data_pad = None
+            if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
+                data_pad = data
+                data = data_pad.op.input_tensors[0]
+            _schedule_im2col_conv2d(s, data, data_pad, data_col, data_vec,
+                                    kernel, kernel_vec,
+                                    conv_out, output, outs[0])
+
+    traverse(outs[0].op)
+    return s
diff --git a/vta/python/vta/build.py b/vta/python/vta/build.py
new file mode 100644
index 000000000000..8f422e1fafd5
--- /dev/null
+++ b/vta/python/vta/build.py
@@ -0,0 +1,55 @@
+"""Runtime function related hooks"""
+from __future__ import absolute_import as _abs
+
+import tvm
+from tvm import build_module
+from . runtime import CB_HANDLE
+from . import ir_pass
+
+
+def lift_coproc_scope(x):
+    x = ir_pass.lift_alloc_to_scope_begin(x)
+    x = tvm.ir_pass.LiftAttrScope(x, "coproc_scope", False)
+    return x
+
+def early_rewrite(stmt):
+    try:
+        return tvm.ir_pass.StorageRewrite(stmt)
+    except tvm.TVMError:
+        return stmt
+
+
+def debug_mode(debug_flag):
+    """Pass to enable vta debug mode.
+
+    Parameters
+    ----------
+    debug_flag : int
+        The dbeug flag to be passed.
+
+    Returns
+    -------
+    pass_list: list of function
+        The pass to set to build_config(add_lower_pass=vta.debug_mode(mode))
+    """
+    def add_debug(stmt):
+        debug = tvm.call_extern(
+            "int32", "VTASetDebugMode", CB_HANDLE, debug_flag)
+        return tvm.make.stmt_seq(debug, stmt)
+    pass_list = [(1, ir_pass.inject_dma_intrin),
+                 (1, ir_pass.inject_skip_copy),
+                 (1, ir_pass.annotate_alu_coproc_scope),
+                 (1, lambda x: tvm.ir_pass.LiftAttrScope(x, "coproc_uop_scope", True)),
+                 (1, lift_coproc_scope),
+                 (1, ir_pass.inject_coproc_sync),
+                 (1, early_rewrite)]
+    if debug_flag:
+        pass_list.append((1, add_debug))
+    pass_list.append((2, ir_pass.inject_alu_intrin))
+    pass_list.append((3, ir_pass.fold_uop_loop))
+    pass_list.append((3, ir_pass.cpu_access_rewrite))
+    return pass_list
+
+
+# Add a lower pass to sync uop
+build_module.BuildConfig.current.add_lower_pass = debug_mode(0)
diff --git a/vta/python/vta/graph.py b/vta/python/vta/graph.py
new file mode 100644
index 000000000000..b8237980d176
--- /dev/null
+++ b/vta/python/vta/graph.py
@@ -0,0 +1,348 @@
+"""Graph transformation specific to accelerator.
+
+This module provide specific NNVM graph transformations
+to transform a generic NNVM graph to a version that can
+be executed on accelerator.
+"""
+
+import nnvm
+
+from nnvm.compiler import graph_attr, graph_util
+
+
+def _pack_channel(data, dshape, factor):
+    """Pack the data channel dimension.
+    """
+    assert dshape[1] % factor == 0
+    data = nnvm.sym.reshape(data,
+                            shape=(dshape[0], dshape[1] // factor,
+                                   factor, dshape[2], dshape[3]))
+    data = nnvm.sym.transpose(
+        data, axes=(0, 1, 3, 4, 2))
+    return data
+
+
+def _unpack_channel(data, old_shape):
+    """Unpack the data channel dimension.
+    """
+    data = nnvm.sym.transpose(data, axes=(0, 1, 4, 2, 3))
+    data = nnvm.sym.reshape(data, shape=old_shape)
+    return data
+
+
+def _pack_weight(data, dshape, factor):
+    """Pack the weight into packed format.
+    """
+    assert len(dshape) == 4
+    assert dshape[0] % factor == 0
+    assert dshape[1] % factor == 0
+    data = nnvm.sym.reshape(data,
+                            shape=(dshape[0] // factor, factor,
+                                   dshape[1] // factor, factor,
+                                   dshape[2], dshape[3]))
+    data = nnvm.sym.transpose(
+        data, axes=(0, 2, 4, 5, 1, 3))
+    return data
+
+
+def _pack_bias(data, dshape, factor):
+    """Pack the bias parameter.
+    """
+    assert len(dshape) == 3
+    assert dshape[0] % factor == 0
+    data = nnvm.sym.reshape(data,
+                            shape=(dshape[0] // factor,
+                                   factor, dshape[1], dshape[2]))
+    data = nnvm.sym.transpose(
+        data, axes=(0, 2, 3, 1))
+    return data
+
+
+def _get_shape(sym, shape_dict):
+    """Get the shape of a node.
+    """
+    return graph_util.infer_shape(
+        nnvm.graph.create(sym), **shape_dict)[1][0]
+
+
+def remove_stochastic(graph):
+    """
+    Replace stochastic rounding and shift with determinstic version.
+
+    Parameters
+    ----------
+    graph : Graph
+        The input graph
+
+    Returns
+    -------
+    replaced_graph : Graph
+        The final replaced graph.
+    """
+    gidx = graph.index
+    node_map = {}
+
+    for nid, node in enumerate(gidx.nodes):
+        children = [node_map[e[0]] for e in node["inputs"]]
+        attrs = node.get("attrs", {})
+        node_name = node["name"]
+        op_name = node["op"]
+        get_clone = lambda c, o_n, n_n, a: getattr(nnvm.symbol, o_n)(
+            *c, name=n_n, **a)
+        if op_name == "null":
+            new_node = nnvm.symbol.Variable(node_name)
+        elif op_name == "stochastic_round":
+            new_node = children[0]
+        elif op_name == "noise_lshift":
+            new_node = nnvm.symbol.left_shift(
+                children[0], **attrs)
+        else:
+            new_node = get_clone(children, op_name, node_name, attrs)
+        node_map[nid] = new_node
+
+    assert len(graph.index.output_entries) == 1
+    ret = node_map[graph.index.output_entries[0][0]]
+    ret = nnvm.graph.create(ret)
+    return ret
+
+
+def clean_conv_fuse(graph):
+    """Cleanup the convolution's later fuse stages
+
+    Parameters
+    ----------
+    graph : Graph
+        Input graph
+
+    Returns
+    -------
+    graph : Graph
+        Optimized graph
+    """
+    def _clean_entry(entry):
+        node, flag = entry
+        if flag:
+            node = nnvm.symbol.clip(node, a_max=127, a_min=-127)
+            node = nnvm.symbol.cast(node, dtype="int8")
+            # Use identity as a hint to block conv2d schedules
+            node = nnvm.symbol.identity(node)
+            flag = False
+        return node, flag
+
+    gidx = graph.index
+    ref_count = {}
+    # count reference of each node
+    for nid, node in enumerate(gidx.nodes):
+        ref_count[nid] = 0
+        for elem in node["inputs"]:
+            ref_count[elem[0]] += 1
+    # construction remap
+    # entry_id->(new_node, conv_fuse)
+    # need_fold: bool indicates if we need fold
+    node_map = {}
+
+    for nid, node in enumerate(gidx.nodes):
+        children = [node_map[e[0]] for e in node["inputs"]]
+        attrs = node.get("attrs", {})
+        node_name = node["name"]
+        op_name = node["op"]
+        get_clone = lambda c, o_n, n_n, a: getattr(nnvm.symbol, o_n)(
+            *c, name=n_n, **a)
+
+        new_entry = None
+        if op_name == "null":
+            new_entry = (nnvm.symbol.Variable(node_name), False)
+        elif op_name in ("cast", "clip"):
+            if children[0][1]:
+                new_entry = children[0]
+            else:
+                new_entry = (
+                    get_clone([children[0][0]], op_name, node_name, attrs),
+                    False)
+        elif op_name == "quantized_conv2d":
+            data, weight = children
+            data = _clean_entry(data)
+            new_node = nnvm.sym.quantized_conv2d(
+                data[0], weight[0], name=node_name, **attrs)
+            new_entry = (new_node, True)
+        elif op_name in ("left_shift", "right_shift", "relu"):
+            new_entry = (
+                get_clone([children[0][0]], op_name, node_name, attrs),
+                children[0][1])
+        elif op_name in ("broadcast_add", "broadcast_mul"):
+            rhs = children[1][0]
+            lhs, _ = _clean_entry(children[0])
+            lhs = nnvm.sym.cast(lhs, dtype="int32")
+            rhs = nnvm.sym.cast(rhs, dtype="int32")
+            new_entry = (
+                get_clone([lhs, rhs], op_name, node_name, attrs),
+                False)
+
+        if new_entry is None:
+            inputs = [_clean_entry(x) for x in children]
+            new_entry = (
+                get_clone([x[0] for x in inputs], op_name, node_name, attrs),
+                False)
+        if ref_count[nid] > 1:
+            new_entry = _clean_entry(new_entry)
+        node_map[nid] = new_entry
+
+    assert len(graph.index.output_entries) == 1
+    ret = node_map[graph.index.output_entries[0][0]][0]
+    ret = nnvm.graph.create(ret)
+    return ret
+
+
+def clean_cast(graph):
+    """
+    Move the casts to early part of graph,
+    remove uncessary clip operations when possible.
+    """
+    gidx = graph.index
+    node_map = {}
+
+    def _clean_cast(node, target_type):
+        op_name = node.attr("op_name")
+        if op_name == "cast":
+            return _clean_cast(node.get_children(), target_type)
+        elif op_name == "relu":
+            data, has_clip = _clean_cast(
+                node.get_children(), target_type)
+            data = nnvm.sym.relu(data)
+            return data, has_clip
+        return nnvm.sym.cast(node, dtype=target_type), False
+
+    for nid, node in enumerate(gidx.nodes):
+        children = [node_map[e[0]] for e in node["inputs"]]
+        attrs = node.get("attrs", {})
+        node_name = node["name"]
+        op_name = node["op"]
+        get_clone = lambda c, o_n, n_n, a: getattr(nnvm.symbol, o_n)(
+            *c, name=n_n, **a)
+
+        if op_name == "null":
+            new_node = nnvm.symbol.Variable(node_name)
+        elif op_name == "cast":
+            dtype = attrs["dtype"]
+            new_node, _ = _clean_cast(children[0], dtype)
+        elif op_name == "quantized_conv2d":
+            data, weight = children
+            data, _ = _clean_cast(data, "int8")
+            weight, _ = _clean_cast(weight, "int8")
+            new_node = nnvm.sym.quantized_conv2d(
+                data, weight, name=node_name, **attrs)
+        elif op_name == "elemwise_add":
+            lhs, rhs = children
+            rhs = nnvm.sym.cast(rhs, dtype="int8")
+            new_node = nnvm.sym.elemwise_add(lhs, rhs)
+        else:
+            new_node = get_clone(children, op_name, node_name, attrs)
+        node_map[nid] = new_node
+
+    assert len(graph.index.output_entries) == 1
+    ret = node_map[graph.index.output_entries[0][0]]
+    ret = nnvm.graph.create(ret)
+    return ret
+
+
+def pack(graph, shape_dict, factor, start_name=None):
+    """Pack the graph into channel packed format.
+
+    Parameters
+    ----------
+    graph : Graph
+       The input graph.
+
+    shape_dict : dict of str to shapex
+       The input shape.
+
+    factor : int
+       The packing factor
+
+    start_name: str, optional
+       Start name start packing from certain known node.
+
+    Returns
+    -------
+    graph : Graph
+        The transformed graph.
+    """
+    graph = graph_attr.set_shape_inputs(graph, shape_dict)
+    graph = graph.apply("InferShape")
+    shape = graph.json_attr("shape")
+    gidx = graph.index
+    node_map = {}
+    dset = set()
+    counter = 0
+    start_pack = False
+
+    for nid, node in enumerate(gidx.nodes):
+        children = [node_map[e[0]] for e in node["inputs"]]
+        ishape = [shape[gidx.entry_id(e)] for e in node["inputs"]]
+        oshape = shape[gidx.entry_id(nid, 0)]
+        attrs = node.get("attrs", {})
+        node_name = node["name"]
+        op_name = node["op"]
+        get_clone = lambda c, o_n, n_n, a: getattr(nnvm.symbol, o_n)(
+            *c, name=n_n, **a)
+
+        if op_name == "null":
+            new_node = nnvm.symbol.Variable(node_name)
+            if start_name and node_name == start_name:
+                start_pack = True
+                new_node = _pack_channel(new_node, oshape, factor)
+        elif op_name == "max_pool2d":
+            assert not start_pack
+            start_pack = True
+            new_node = get_clone(children, op_name, node_name, attrs)
+            new_node = _pack_channel(new_node, oshape, factor)
+        elif op_name == "global_avg_pool2d":
+            if start_pack:
+                start_pack = False
+                children[0] = _unpack_channel(children[0], ishape[0])
+                new_node = getattr(nnvm.symbol, op_name)(
+                    *children, name=node_name, **attrs)
+            else:
+                new_node = get_clone(children, op_name, node_name, attrs)
+        elif op_name == "quantized_conv2d":
+            if start_pack:
+                attrs["pack_channel"] = str(factor)
+                data, weight = children
+                weight = _pack_weight(weight, ishape[1], factor)
+                new_node = nnvm.sym.quantized_conv2d(
+                    data, weight, name=node_name, **attrs)
+            elif counter == 1:
+                attrs["pack_channel"] = str(factor)
+                data, weight = children
+                data = _pack_channel(data, ishape[0], factor)
+                weight = _pack_weight(weight, ishape[1], factor)
+                new_node = nnvm.sym.quantized_conv2d(
+                    data, weight, name=node_name, **attrs)
+                new_node = _unpack_channel(new_node, oshape)
+                counter = counter + 1
+            else:
+                new_node = get_clone(children, op_name, node_name, attrs)
+        elif op_name.startswith("broadcast"):
+            if start_pack:
+                assert len(ishape[1]) == 3
+                children[1] = _pack_bias(children[1], ishape[1], factor)
+                new_node = getattr(nnvm.symbol, op_name)(
+                    *children, name=node_name, **attrs)
+            else:
+                new_node = get_clone(children, op_name, node_name, attrs)
+        elif op_name.startswith("elementwise_add"):
+            new_node = get_clone(children, op_name, node_name, attrs)
+        else:
+            new_node = get_clone(children, op_name, node_name, attrs)
+            dset.add(op_name)
+        node_map[nid] = new_node
+
+    assert len(graph.index.output_entries) == 1
+    ret = node_map[graph.index.output_entries[0][0]]
+    if start_pack:
+        oshape = shape[graph.index.output_entries[0][0]]
+        ret = _unpack_channel(ret, oshape)
+    graph = nnvm.graph.create(ret)
+    graph = graph_attr.set_shape_inputs(graph, shape_dict)
+    graph = graph.apply("InferShape")
+    return graph
diff --git a/vta/python/vta/hw_spec.py b/vta/python/vta/hw_spec.py
new file mode 100644
index 000000000000..b6b89df81594
--- /dev/null
+++ b/vta/python/vta/hw_spec.py
@@ -0,0 +1,70 @@
+"""VTA configuration constants (should match hw_spec.h"""
+from __future__ import absolute_import as _abs
+
+# The Constants
+VTA_WGT_WIDTH = 8
+VTA_INP_WIDTH = VTA_WGT_WIDTH
+VTA_OUT_WIDTH = 32
+
+# Dimensions of the GEMM unit
+# (BATCH,BLOCK_IN) x (BLOCK_IN,BLOCK_OUT)
+VTA_BATCH = 1
+VTA_BLOCK_IN = 16
+VTA_BLOCK_OUT = 16
+
+# log-2 On-chip wgt buffer size in Bytes
+VTA_LOG_WGT_BUFF_SIZE = 15
+# log-2 On-chip input buffer size in Bytes
+VTA_LOG_INP_BUFF_SIZE = 15
+# log-2 On-chip output buffer size in Bytes
+VTA_LOG_OUT_BUFF_SIZE = 17
+# On-chip wgt buffer size in Bytes
+VTA_WGT_BUFF_SIZE = 1 << VTA_LOG_WGT_BUFF_SIZE
+# Input buffer size
+VTA_INP_BUFF_SIZE = 1 << VTA_LOG_INP_BUFF_SIZE
+# Output buffer size.
+VTA_OUT_BUFF_SIZE = 1 << VTA_LOG_OUT_BUFF_SIZE
+
+# Number of bytes per buffer
+VTA_INP_ELEM_BYTES = (VTA_BATCH*VTA_BLOCK_IN*VTA_INP_WIDTH//8)
+VTA_WGT_ELEM_BYTES = (VTA_BLOCK_OUT*VTA_BLOCK_IN*VTA_WGT_WIDTH//8)
+VTA_OUT_ELEM_BYTES = (VTA_BATCH*VTA_BLOCK_OUT*VTA_OUT_WIDTH//8)
+
+# Maximum external buffer size in bytes
+VTA_MAX_XFER = 1 << 22
+
+# Number of elements
+VTA_INP_BUFF_DEPTH = VTA_INP_BUFF_SIZE//VTA_INP_ELEM_BYTES
+VTA_WGT_BUFF_DEPTH = VTA_WGT_BUFF_SIZE//VTA_WGT_ELEM_BYTES
+VTA_OUT_BUFF_DEPTH = VTA_OUT_BUFF_SIZE//VTA_OUT_ELEM_BYTES
+
+# Memory id for DMA
+VTA_MEM_ID_UOP = 0
+VTA_MEM_ID_WGT = 1
+VTA_MEM_ID_INP = 2
+VTA_MEM_ID_ACC = 3
+VTA_MEM_ID_OUT = 4
+
+# VTA ALU Opcodes
+VTA_ALU_OPCODE_MIN = 0
+VTA_ALU_OPCODE_MAX = 1
+VTA_ALU_OPCODE_ADD = 2
+VTA_ALU_OPCODE_SUB = 3
+VTA_ALU_OPCODE_MUL = 4
+VTA_ALU_OPCODE_SHL = 5
+VTA_ALU_OPCODE_SHR = 6
+VTA_ALU_OPCODE_UNSET = 7
+
+# Task queue id (pipeline stage)
+VTA_QID_LOAD_INP = 1
+VTA_QID_LOAD_WGT = 1
+VTA_QID_LOAD_OUT = 2
+VTA_QID_STORE_OUT = 3
+VTA_QID_COMPUTE = 2
+VTA_QID_STORE_INP = 3
+
+# Debug flags
+DEBUG_DUMP_INSN = (1 << 1)
+DEBUG_DUMP_UOP = (1 << 2)
+DEBUG_SKIP_READ_BARRIER = (1 << 3)
+DEBUG_SKIP_WRITE_BARRIER = (1 << 4)
\ No newline at end of file
diff --git a/vta/python/vta/intrin.py b/vta/python/vta/intrin.py
new file mode 100644
index 000000000000..73928aa1144b
--- /dev/null
+++ b/vta/python/vta/intrin.py
@@ -0,0 +1,183 @@
+"""VTA related intrinsics"""
+from __future__ import absolute_import as _abs
+
+import tvm
+from . import hw_spec as spec
+from .runtime import VTA_AXIS, VTA_PUSH_UOP, get_task_qid
+from .runtime import SCOPE_OUT, SCOPE_INP, SCOPE_WGT
+
+# The memory information for the compiler
+@tvm.register_func("tvm.info.mem.%s" % SCOPE_INP)
+def mem_info_inp_buffer():
+    return tvm.make.node("MemoryInfo",
+                         unit_bits=spec.VTA_INP_ELEM_BYTES * 8,
+                         max_simd_bits=spec.VTA_INP_ELEM_BYTES * 8,
+                         max_num_bits=spec.VTA_INP_BUFF_SIZE * 8,
+                         head_address=None)
+
+@tvm.register_func("tvm.info.mem.%s" % SCOPE_WGT)
+def mem_info_wgt_buffer():
+    return tvm.make.node("MemoryInfo",
+                         unit_bits=spec.VTA_WGT_ELEM_BYTES * 8,
+                         max_simd_bits=spec.VTA_WGT_ELEM_BYTES * 8,
+                         max_num_bits=spec.VTA_WGT_BUFF_SIZE * 8,
+                         head_address=None)
+
+@tvm.register_func("tvm.info.mem.%s" % SCOPE_OUT)
+def mem_info_out_buffer():
+    return tvm.make.node("MemoryInfo",
+                         unit_bits=spec.VTA_OUT_ELEM_BYTES * 8,
+                         max_simd_bits=spec.VTA_OUT_ELEM_BYTES * 8,
+                         max_num_bits=spec.VTA_OUT_BUFF_SIZE * 8,
+                         head_address=None)
+
+def intrin_gevm(mock=False):
+    """Vector-matrix multiply intrinsic"""
+    wgt_lanes = spec.VTA_WGT_ELEM_BYTES * 8 // spec.VTA_WGT_WIDTH
+    assert wgt_lanes == spec.VTA_BLOCK_OUT * spec.VTA_BLOCK_IN
+    wgt_shape = (spec.VTA_BLOCK_OUT, spec.VTA_BLOCK_IN)
+    assert wgt_shape[0] * wgt_shape[1] == wgt_lanes
+    inp_lanes = spec.VTA_INP_ELEM_BYTES * 8 // spec.VTA_INP_WIDTH
+    out_lanes = spec.VTA_OUT_ELEM_BYTES * 8 // spec.VTA_OUT_WIDTH
+    wgt = tvm.placeholder((wgt_shape[0], wgt_shape[1]),
+                          dtype="int%d" % spec.VTA_WGT_WIDTH,
+                          name=SCOPE_WGT)
+    inp = tvm.placeholder((wgt_shape[1], ),
+                          dtype="int%d" % spec.VTA_INP_WIDTH,
+                          name=SCOPE_INP)
+    k = tvm.reduce_axis((0, wgt_shape[1]), name="k")
+    out_dtype = "int%d" % spec.VTA_OUT_WIDTH
+    out = tvm.compute((wgt_shape[0],),
+                      lambda i: tvm.sum(inp[k].astype(out_dtype) *
+                                        wgt[i, k].astype(out_dtype),
+                                        axis=[k]),
+                      name="out")
+    wgt_layout = tvm.decl_buffer(
+        wgt.shape, wgt.dtype, SCOPE_WGT,
+        scope=SCOPE_WGT, offset_factor=wgt_lanes, data_alignment=wgt_lanes)
+    inp_layout = tvm.decl_buffer(
+        inp.shape, inp.dtype, SCOPE_INP,
+        scope=SCOPE_INP, offset_factor=inp_lanes, data_alignment=inp_lanes)
+    out_layout = tvm.decl_buffer(
+        out.shape, out.dtype, SCOPE_OUT,
+        scope=SCOPE_OUT, offset_factor=out_lanes, data_alignment=out_lanes)
+
+    def intrin_func(ins, outs):
+        """Vector-matrix multiply intrinsic function"""
+        dinp, dwgt = ins
+        dout = outs[0]
+        def instr(index):
+            """Generate vector-matrix multiply VTA instruction"""
+            irb = tvm.ir_builder.create()
+            irb.scope_attr(VTA_AXIS, "coproc_scope", get_task_qid(spec.VTA_QID_COMPUTE))
+            irb.scope_attr(VTA_AXIS, "coproc_uop_scope", VTA_PUSH_UOP)
+            if index == 0 or index == 2:
+                irb.emit(tvm.call_extern(
+                    "int32", "VTAUopPush",
+                    0, 0,
+                    dout.access_ptr("rw", "int32"),
+                    dinp.access_ptr("r", "int32"),
+                    dwgt.access_ptr("r", "int32"),
+                    0, 0, 0))
+            else:
+                irb.emit(tvm.call_extern(
+                    "int32", "VTAUopPush",
+                    0, 1,
+                    dout.access_ptr("rw", "int32"),
+                    0,
+                    0,
+                    0, 0, 0))
+            return irb.get()
+        # return a triple of normal-set, reset, update
+        nop = tvm.make.Evaluate(0)
+        if mock:
+            return (nop, nop, nop)
+        return (instr(0), instr(1), instr(2))
+
+    return tvm.decl_tensor_intrin(out.op, intrin_func,
+                                  name="GEVM",
+                                  binds={inp: inp_layout,
+                                         wgt: wgt_layout,
+                                         out: out_layout})
+
+
+def intrin_gemm(mock=False):
+    """Matrix-matrix multiply intrinsic"""
+    wgt_lanes = spec.VTA_WGT_ELEM_BYTES * 8 // spec.VTA_WGT_WIDTH
+    assert wgt_lanes == spec.VTA_BLOCK_OUT * spec.VTA_BLOCK_IN
+    wgt_shape = (spec.VTA_BLOCK_OUT, spec.VTA_BLOCK_IN)
+    assert wgt_shape[0] * wgt_shape[1] == wgt_lanes
+
+    inp_lanes = spec.VTA_INP_ELEM_BYTES * 8 // spec.VTA_INP_WIDTH
+    assert inp_lanes == spec.VTA_BATCH * spec.VTA_BLOCK_IN
+    inp_shape = (spec.VTA_BATCH, spec.VTA_BLOCK_IN)
+    assert inp_shape[0] * inp_shape[1] == inp_lanes
+
+    out_lanes = spec.VTA_OUT_ELEM_BYTES * 8 // spec.VTA_OUT_WIDTH
+    assert out_lanes == spec.VTA_BATCH * spec.VTA_BLOCK_OUT
+    out_shape = (spec.VTA_BATCH, spec.VTA_BLOCK_OUT)
+    assert out_shape[0] * out_shape[1] == out_lanes
+
+    wgt = tvm.placeholder((wgt_shape[0], wgt_shape[1]),
+                          dtype="int%d" % spec.VTA_WGT_WIDTH,
+                          name=SCOPE_WGT)
+    inp = tvm.placeholder((inp_shape[0], inp_shape[1]),
+                          dtype="int%d" % spec.VTA_INP_WIDTH,
+                          name=SCOPE_INP)
+    k = tvm.reduce_axis((0, wgt_shape[1]), name="k")
+    out_dtype = "int%d" % spec.VTA_OUT_WIDTH
+    out = tvm.compute((out_shape[0], out_shape[1]),
+                      lambda i, j: tvm.sum(inp[i, k].astype(out_dtype) *
+                                           wgt[j, k].astype(out_dtype),
+                                           axis=[k]),
+                      name="out")
+    wgt_layout = tvm.decl_buffer(
+        wgt.shape, wgt.dtype, SCOPE_WGT,
+        scope=SCOPE_WGT, offset_factor=wgt_lanes, data_alignment=wgt_lanes)
+    inp_layout = tvm.decl_buffer(
+        inp.shape, inp.dtype, SCOPE_INP,
+        scope=SCOPE_INP, offset_factor=inp_lanes, data_alignment=inp_lanes)
+    out_layout = tvm.decl_buffer(
+        out.shape, out.dtype, SCOPE_OUT,
+        scope=SCOPE_OUT, offset_factor=out_lanes, data_alignment=out_lanes)
+
+    def intrin_func(ins, outs):
+        """Matrix-matrix multiply intrinsic function"""
+        dinp, dwgt = ins
+        dout = outs[0]
+        def instr(index):
+            """Generate matrix-matrix multiply VTA instruction"""
+            irb = tvm.ir_builder.create()
+            irb.scope_attr(VTA_AXIS, "coproc_scope", get_task_qid(spec.VTA_QID_COMPUTE))
+            irb.scope_attr(VTA_AXIS, "coproc_uop_scope", VTA_PUSH_UOP)
+            if index == 0 or index == 2:
+                irb.emit(tvm.call_extern(
+                    "int32", "VTAUopPush",
+                    0, 0,
+                    dout.access_ptr("rw", "int32"),
+                    dinp.access_ptr("r", "int32"),
+                    dwgt.access_ptr("r", "int32"),
+                    0, 0, 0))
+            else:
+                irb.emit(tvm.call_extern(
+                    "int32", "VTAUopPush",
+                    0, 1,
+                    dout.access_ptr("rw", "int32"),
+                    0,
+                    0,
+                    0, 0, 0))
+            return irb.get()
+        # return a triple of normal-set, reset, update
+        nop = tvm.make.Evaluate(0)
+        if mock:
+            return (nop, nop, nop)
+        return (instr(0), instr(1), instr(2))
+
+    return tvm.decl_tensor_intrin(out.op, intrin_func,
+                                  name="GEMM",
+                                  binds={inp: inp_layout,
+                                         wgt: wgt_layout,
+                                         out: out_layout})
+
+GEMM = intrin_gemm()
+GEVM = intrin_gevm()
diff --git a/vta/python/vta/ir_pass.py b/vta/python/vta/ir_pass.py
new file mode 100644
index 000000000000..00c73c4ab8b3
--- /dev/null
+++ b/vta/python/vta/ir_pass.py
@@ -0,0 +1,762 @@
+"""Additional IR Pass for VTA"""
+from __future__ import absolute_import as _abs
+
+import tvm
+from topi import util as util
+
+from . import hw_spec as spec
+from . runtime import CB_HANDLE, VTA_AXIS, VTA_PUSH_UOP
+from . runtime import SCOPE_OUT, SCOPE_INP, SCOPE_WGT, DMA_COPY, get_task_qid
+
+
+def fold_uop_loop(stmt_in):
+    """Pass to fold uop loops"""
+    def _fold_outermost_loop(body):
+        stmt = body
+        while not isinstance(stmt, tvm.stmt.For):
+            if isinstance(stmt, (tvm.stmt.ProducerConsumer, )):
+                stmt = stmt.body
+            else:
+                return None, body, None
+
+        loop_var = stmt.loop_var
+        gemm_offsets = [None, None, None]
+        fail = [False]
+
+        def _post_order(op):
+            assert isinstance(op, tvm.expr.Call)
+            base_args = 2
+            if op.name == "VTAUopPush":
+                args = []
+                args += op.args[:base_args]
+                for i in range(3):
+                    m = tvm.arith.DetectLinearEquation(op.args[i + base_args], [loop_var])
+                    if not m:
+                        fail[0] = True
+                        return op
+                    if gemm_offsets[i] is not None:
+                        if not tvm.ir_pass.Equal(m[0], gemm_offsets[i]):
+                            fail[0] = True
+                            return op
+                        args.append(m[1])
+                    else:
+                        gemm_offsets[i] = m[0]
+                        args.append(m[1])
+                args += op.args[base_args+3:]
+                return tvm.call_extern("int32", "VTAUopPush", *args)
+            else:
+                if op.name not in ("VTATLSCommandHandle", "tvm_thread_context"):
+                    raise RuntimeError("unexpected op %s" % op)
+                return op
+
+        ret = tvm.ir_pass.IRTransform(
+            stmt.body, None, _post_order, ["Call"])
+
+        if not fail[0] and all(x is not None for x in gemm_offsets):
+            def _visit(op):
+                if op.same_as(loop_var):
+                    fail[0] = True
+            tvm.ir_pass.PostOrderVisit(ret, _visit)
+            if not fail[0]:
+                begin = tvm.call_extern(
+                    "int32", "VTAUopLoopBegin", stmt.extent, *gemm_offsets)
+                end = tvm.call_extern(
+                    "int32", "VTAUopLoopEnd", stmt.extent, *gemm_offsets)
+                return [begin, ret, end]
+        raise ValueError("Failed to fold the GEMM instructions..")
+
+    def _do_fold(stmt):
+        if (stmt.attr_key == "coproc_uop_scope" and
+                isinstance(stmt.value, tvm.expr.StringImm) and
+                stmt.value.value == VTA_PUSH_UOP.value):
+            body = stmt.body
+            begins = []
+            ends = []
+            try:
+                begin, body, end = _fold_outermost_loop(body)
+                if begin is not None:
+                    begins.append(begin)
+                if end is not None:
+                    ends.append(end)
+                begin, body, end = _fold_outermost_loop(body)
+                if begin is not None:
+                    begins.append(begin)
+                if end is not None:
+                    ends.append(end)
+            except ValueError:
+                pass
+            if body == stmt.body:
+                return stmt
+            ends = list(reversed(ends))
+            body = tvm.make.stmt_seq(*(begins + [body] + ends))
+            return tvm.make.AttrStmt(
+                stmt.node, stmt.attr_key, stmt.value, body)
+        return None
+    out = tvm.ir_pass.IRTransform(
+        stmt_in, _do_fold, None, ["AttrStmt"])
+    return out
+
+
+def cpu_access_rewrite(stmt_in):
+    """Rewrite the code when there is CPU access happening.
+
+    Parameters
+    ----------
+    stmt_in : Stmt
+        Input statement
+
+    Returns
+    -------
+    stmt_out : Stmt
+        Transformed statement
+    """
+    rw_info = {}
+    def _post_order(op):
+        if isinstance(op, tvm.stmt.Allocate):
+            buffer_var = op.buffer_var
+            if not buffer_var in rw_info:
+                return None
+            new_var = rw_info[buffer_var]
+            let_stmt = tvm.make.LetStmt(
+                new_var, tvm.call_extern(
+                    "handle", "VTABufferCPUPtr", CB_HANDLE,
+                    buffer_var), op.body)
+            alloc = tvm.make.Allocate(
+                buffer_var, op.dtype, op.extents,
+                op.condition, let_stmt)
+            del rw_info[buffer_var]
+            return alloc
+        elif isinstance(op, tvm.expr.Load):
+            buffer_var = op.buffer_var
+            if not buffer_var in rw_info:
+                rw_info[buffer_var] = tvm.var(
+                    buffer_var.name + "_ptr", "handle")
+            new_var = rw_info[buffer_var]
+            return tvm.make.Load(op.dtype, new_var, op.index)
+        elif isinstance(op, tvm.stmt.Store):
+            buffer_var = op.buffer_var
+            if not buffer_var in rw_info:
+                rw_info[buffer_var] = tvm.var(
+                    buffer_var.name + "_ptr", "handle")
+            new_var = rw_info[buffer_var]
+            return tvm.make.Store(new_var, op.value, op.index)
+        else:
+            raise RuntimeError("not reached")
+    stmt = tvm.ir_pass.IRTransform(
+        stmt_in, None, _post_order, ["Allocate", "Load", "Store"])
+    for buffer_var, new_var in rw_info.items():
+        stmt = tvm.make.LetStmt(
+            new_var, tvm.call_extern(
+                "handle", "VTABufferCPUPtr", CB_HANDLE,
+                buffer_var), stmt)
+    return stmt
+
+
+def lift_alloc_to_scope_begin(stmt_in):
+    """Lift allocate to beginning of the current scope.
+
+    Parameters
+    ----------
+    stmt_in : Stmt
+        Input statement
+
+    Returns
+    -------
+    stmt_out : Stmt
+        Transformed statement
+    """
+    lift_stmt = [[]]
+    def _merge_block(slist, body):
+        for op in slist:
+            if op.body == body:
+                body = op
+            elif isinstance(op, tvm.stmt.Allocate):
+                body = tvm.make.Allocate(
+                    op.buffer_var, op.dtype,
+                    op.extents, op.condition, body)
+            elif isinstance(op, tvm.stmt.AttrStmt):
+                body = tvm.make.AttrStmt(
+                    op.node, op.attr_key, op.value, body)
+            elif isinstance(op, tvm.stmt.For):
+                body = tvm.make.For(
+                    op.loop_var, op.min, op.extent, op.for_type,
+                    op.device_api, body)
+            else:
+                raise RuntimeError("unexpected op")
+        del slist[:]
+        # n = len(slist)
+        # for i in range(n):
+        #     slist.pop()
+        return body
+
+    def _pre_order(op):
+        if isinstance(op, tvm.stmt.For):
+            lift_stmt.append([])
+        elif isinstance(op, tvm.stmt.AttrStmt):
+            if op.attr_key == "virtual_thread":
+                lift_stmt.append([])
+
+        return None
+
+    def _post_order(op):
+        if isinstance(op, tvm.stmt.Allocate):
+            lift_stmt[-1].append(op)
+            return op.body
+        elif isinstance(op, tvm.stmt.AttrStmt):
+            if op.attr_key == "storage_scope":
+                lift_stmt[-1].append(op)
+                return op.body
+            elif op.attr_key == "virtual_thread":
+                return _merge_block(lift_stmt.pop() + [op], op.body)
+            return op
+        elif isinstance(op, tvm.stmt.For):
+            return _merge_block(lift_stmt.pop() + [op], op.body)
+        else:
+            raise RuntimeError("not reached")
+    stmt = tvm.ir_pass.IRTransform(
+        stmt_in, _pre_order, _post_order, ["Allocate", "AttrStmt", "For"])
+    assert len(lift_stmt) == 1
+    return _merge_block(lift_stmt[0], stmt)
+
+
+def inject_skip_copy(stmt_in):
+    """Pass to inject skip copy stmt, used in debug.
+
+    Parameters
+    ----------
+    stmt_in : Stmt
+        Input statement
+
+    Returns
+    -------
+    stmt_out : Stmt
+        Transformed statement
+    """
+    def _do_fold(stmt):
+        if stmt.attr_key == "pragma_scope" and stmt.value.value == "skip_dma_copy":
+            return tvm.make.Evaluate(0)
+        return None
+    return tvm.ir_pass.IRTransform(
+        stmt_in, _do_fold, None, ["AttrStmt"])
+
+
+def inject_coproc_sync(stmt_in):
+    """Pass to inject skip copy stmt, used in debug.
+
+    Parameters
+    ----------
+    stmt_in : Stmt
+        Input statement
+
+    Returns
+    -------
+    stmt_out : Stmt
+        Transformed statement
+    """
+    success = [False]
+    def _do_fold(stmt):
+        if stmt.attr_key == "pragma_scope" and stmt.value.value == "coproc_sync":
+            success[0] = True
+            sync = tvm.make.Call(
+                "int32", "vta.coproc_sync", [], tvm.expr.Call.Intrinsic, None, 0)
+            return tvm.make.Block(stmt.body, tvm.make.Evaluate(sync))
+        elif stmt.attr_key == "pragma_scope" and stmt.value.value == "trim_loop":
+            op = stmt.body
+            assert isinstance(op, tvm.stmt.For)
+            return tvm.make.For(
+                op.loop_var, op.min, 2, op.for_type,
+                op.device_api, op.body)
+        return None
+    stmt = tvm.ir_pass.IRTransform(
+        stmt_in, None, _do_fold, ["AttrStmt"])
+    stmt = tvm.ir_pass.CoProcSync(stmt)
+    return stmt
+
+
+def inject_dma_intrin(stmt_in):
+    """Pass to inject DMA copy intrinsics.
+
+    Parameters
+    ----------
+    stmt_in : Stmt
+        Input statement
+
+    Returns
+    -------
+    stmt_out : Stmt
+        Transformed statement
+    """
+    def _check_compact(buf):
+        ndim = len(buf.shape)
+        size = tvm.const(1, buf.shape[0].dtype)
+        for i in reversed(range(ndim)):
+            if not util.equal_const_int(size - buf.strides[i], 0):
+                raise RuntimeError(
+                    "Cannot prove compact: shape=%s, strides=%s" % (buf.shape, buf.strides))
+            size = size * buf.shape[i]
+
+    def _fold_buffer_dim(buf, scope, elem_block):
+        ndim = len(buf.shape)
+        x_size = 1
+        base = 0
+        for i in range(1, ndim + 1):
+            if not util.equal_const_int(buf.strides[ndim - i] - x_size, 0):
+                raise RuntimeError("scope %s need need to have block=%d" % (scope, elem_block))
+            x_size = x_size * buf.shape[ndim - i]
+            if util.equal_const_int(x_size - elem_block, 0):
+                base = i + 1
+                break
+        if base == 0:
+            raise RuntimeError("scope %s need to have block=%d, shape=%s" % (
+                scope, elem_block, buf.shape))
+        shape = [elem_block]
+        strides = [1]
+
+        if (base < ndim + 1 and not util.equal_const_int(buf.strides[ndim - base], elem_block)):
+            shape.append(1)
+            strides.append(elem_block)
+
+        while base < ndim + 1:
+            x_size = 1
+            x_stride = buf.strides[ndim - base]
+            next_base = base
+            if not util.equal_const_int(x_stride % elem_block, 0):
+                raise RuntimeError("scope %s need to have block=%d, shape=%s, strides=%s" % (
+                    scope, elem_block, buf.shape, buf.strides))
+            for i in range(base, ndim + 1):
+                k = ndim - i
+                if not util.equal_const_int(x_size * x_stride - buf.strides[k], 0):
+                    break
+                x_size = x_size * buf.shape[k]
+                next_base = i + 1
+            shape.append(tvm.ir_pass.Simplify(x_size))
+            strides.append(x_stride)
+            assert next_base != base
+            base = next_base
+
+        strides = list(reversed(strides))
+        shape = list(reversed(shape))
+        return shape, strides
+
+
+    def _get_2d_pattern(buf, elem_width, elem_bytes, dtype, scope, allow_fold):
+        elem_block = elem_bytes * 8 // elem_width
+        if buf.dtype != dtype:
+            raise RuntimeError("Expect buffer type to be %s instead of %s" %
+                               (dtype, buf.dtype))
+        shape, strides = buf.shape, buf.strides
+        if not util.equal_const_int(buf.elem_offset % elem_block, 0):
+            raise RuntimeError("scope %s need to have block=%d" % (scope, elem_block))
+        if allow_fold:
+            shape, strides = _fold_buffer_dim(buf, scope, elem_block)
+        else:
+            shape = list(x for x in shape)
+            strides = list(x for x in strides)
+
+        def raise_error():
+            """Internal function to raise error """
+            raise RuntimeError(
+                ("Scope[%s]: cannot detect 2d pattern with elem_block=%d:" +
+                 " shape=%s, strides=%s") % (scope, elem_block, buf.shape, buf.strides))
+
+        ndim = len(shape)
+
+        # Check if the inner-tensor is already flat
+        flat = util.equal_const_int(shape[-1], elem_block)
+
+        if flat:
+            if not util.equal_const_int(strides[-1], 1):
+                raise_error()
+
+            if ndim == 1:
+                x_size = 1
+                x_stride = 1
+                y_size = 1
+                return x_size, y_size, x_stride, buf.elem_offset / elem_block
+            if not util.equal_const_int(strides[-2] - elem_block, 0):
+                raise_error()
+
+            if ndim == 2:
+                x_size = shape[-2]
+                x_stride = shape[-2]
+                y_size = 1
+                return x_size, y_size, x_stride, buf.elem_offset / elem_block
+            if not util.equal_const_int(strides[-3] % elem_block, 0):
+                raise_error()
+
+            if ndim == 3:
+                x_size = shape[-2]
+                x_stride = strides[-3] / elem_block
+                y_size = shape[-3]
+                return x_size, y_size, x_stride, buf.elem_offset / elem_block
+
+        else:
+            if not util.equal_const_int(strides[-1], 1):
+                raise_error()
+            if not util.equal_const_int(strides[-2] - shape[-1], 0):
+                raise_error()
+            if not util.equal_const_int(shape[-1] * shape[-2], elem_block):
+                raise_error()
+
+            if ndim == 2:
+                x_size = 1
+                x_stride = 1
+                y_size = 1
+                return x_size, y_size, x_stride, buf.elem_offset / elem_block
+            if not util.equal_const_int(strides[-3], elem_block):
+                raise_error()
+
+            if ndim == 3:
+                x_size = shape[-3]
+                x_stride = shape[-3]
+                y_size = 1
+                return x_size, y_size, x_stride, buf.elem_offset / elem_block
+            if not util.equal_const_int(strides[-4] % elem_block, 0):
+                raise_error()
+
+            if ndim == 4:
+                x_size = shape[-3]
+                x_stride = strides[-4] / elem_block
+                y_size = shape[-4]
+                return x_size, y_size, x_stride, buf.elem_offset / elem_block
+
+        raise_error()
+
+
+    def _inject_copy(src, dst, pad_before, pad_after, pad_value):
+        # FIXME: pad_value is ignored...
+        if dst.scope == "global":
+            # Store
+            if pad_before or pad_after:
+                raise RuntimeError("Do not support copy into DRAM with pad")
+            if src.scope == SCOPE_OUT:
+                elem_width = spec.VTA_INP_WIDTH # output compression to inp type
+                elem_bytes = spec.VTA_INP_ELEM_BYTES # output compression to inp type
+                mem_type = spec.VTA_MEM_ID_OUT
+                data_type = "int%d" % spec.VTA_INP_WIDTH
+                task_qid = spec.VTA_QID_STORE_OUT
+            else:
+                raise RuntimeError("Do not support copy %s->dram" % (src.scope))
+            _check_compact(src)
+            x_size, y_size, x_stride, offset = _get_2d_pattern(
+                dst, elem_width, elem_bytes, data_type, src.scope, allow_fold=True)
+            irb = tvm.ir_builder.create()
+            irb.scope_attr(VTA_AXIS, "coproc_scope", get_task_qid(task_qid))
+            irb.emit(tvm.call_extern(
+                "int32", "VTAStoreBuffer2D", CB_HANDLE,
+                src.access_ptr("r", "int32"),
+                mem_type, dst.data, offset, x_size, y_size, x_stride))
+            return irb.get()
+        elif src.scope == "global":
+            if dst.scope == SCOPE_OUT:
+                elem_width = spec.VTA_OUT_WIDTH
+                elem_bytes = spec.VTA_OUT_ELEM_BYTES
+                mem_type = spec.VTA_MEM_ID_ACC
+                data_type = "int%d" % spec.VTA_OUT_WIDTH
+                task_qid = spec.VTA_QID_LOAD_OUT
+            elif dst.scope == SCOPE_INP:
+                elem_width = spec.VTA_INP_WIDTH
+                elem_bytes = spec.VTA_INP_ELEM_BYTES
+                mem_type = spec.VTA_MEM_ID_INP
+                data_type = "int%d" % spec.VTA_INP_WIDTH
+                task_qid = spec.VTA_QID_LOAD_INP
+            elif dst.scope == SCOPE_WGT:
+                elem_width = spec.VTA_WGT_WIDTH
+                elem_bytes = spec.VTA_WGT_ELEM_BYTES
+                mem_type = spec.VTA_MEM_ID_WGT
+                data_type = "int%d" % spec.VTA_WGT_WIDTH
+                task_qid = spec.VTA_QID_LOAD_WGT
+            else:
+                raise RuntimeError("Do not support copy dram->%s" % (dst.scope))
+            # collect pad statistics
+            if pad_before:
+                assert pad_after
+                ndim = len(pad_before)
+                if ndim <= 2 or ndim > 4:
+                    raise ValueError("Limitation of 2D pad load forbid ndim=%d" % ndim)
+                if ndim > 2:
+                    if not util.equal_const_int(pad_before[ndim - 1], 0):
+                        raise ValueError("Do not support pad on the innermost block")
+                    if not util.equal_const_int(pad_after[ndim - 1], 0):
+                        raise ValueError("Do not support pad on the innermost block")
+                if ndim > 3:
+                    if not util.equal_const_int(pad_before[ndim - 2], 0):
+                        raise ValueError("Do not support pad on the innermost block")
+                    if not util.equal_const_int(pad_after[ndim - 2], 0):
+                        raise ValueError("Do not support pad on the innermost block")
+                y_pad_before = pad_before[0]
+                x_pad_before = pad_before[1]
+                y_pad_after = pad_after[0]
+                x_pad_after = pad_after[1]
+                allow_fold = False
+            else:
+                x_pad_before = 0
+                y_pad_before = 0
+                x_pad_after = 0
+                y_pad_after = 0
+                allow_fold = True
+
+            _check_compact(dst)
+            x_size, y_size, x_stride, offset = _get_2d_pattern(
+                src, elem_width, elem_bytes, data_type, dst.scope, allow_fold=allow_fold)
+
+            irb = tvm.ir_builder.create()
+            irb.scope_attr(VTA_AXIS, "coproc_scope", get_task_qid(task_qid))
+
+            irb.emit(tvm.call_extern(
+                "int32", "VTALoadBuffer2D", CB_HANDLE,
+                src.data, offset, x_size, y_size, x_stride,
+                x_pad_before, y_pad_before,
+                x_pad_after, y_pad_after,
+                dst.access_ptr("r", "int32"), mem_type))
+            return irb.get()
+
+        else:
+            raise RuntimeError("Donot support copy %s->%s" % (src.scope, dst.scope))
+
+    return tvm.ir_pass.InjectCopyIntrin(stmt_in, DMA_COPY, _inject_copy)
+
+
+def annotate_alu_coproc_scope(stmt_in):
+    """Pass to insert ALU instruction.
+
+    Parameters
+    ----------
+    stmt_in : Stmt
+        Input statement
+
+    Returns
+    -------
+    stmt_out : Stmt
+        Transformed statement
+    """
+    def _do_fold(stmt):
+        if (stmt.attr_key == "pragma_scope" and stmt.value.value == "alu"):
+            irb = tvm.ir_builder.create()
+            irb.scope_attr(VTA_AXIS, "coproc_scope", get_task_qid(spec.VTA_QID_COMPUTE))
+            irb.scope_attr(VTA_AXIS, "coproc_uop_scope", tvm.make.StringImm("VTAPushALUOp"))
+            irb.emit(stmt)
+            return irb.get()
+        elif (stmt.attr_key == "pragma_scope" and stmt.value.value == "skip_alu"):
+            return tvm.make.Evaluate(0)
+        return stmt
+
+    stmt_out = tvm.ir_pass.IRTransform(
+        stmt_in, None, _do_fold, ["AttrStmt"])
+
+    return stmt_out
+
+
+def inject_alu_intrin(stmt_in):
+    """Pass to inject ALU micro-ops.
+
+    Parameters
+    ----------
+    stmt_in : Stmt
+        Input statement
+
+    Returns
+    -------
+    stmt_out : Stmt
+        Transformed statement
+    """
+    def _do_fold(stmt):
+        def _equal(x, y):
+            return tvm.ir_pass.Equal(tvm.ir_pass.Simplify(x - y), 0)
+
+        def _flatten_loop(src_coeff, dst_coeff, extents):
+            src_coeff = list(src_coeff)
+            dst_coeff = list(dst_coeff)
+            extents = list(extents)
+            rev_src_coeff = [src_coeff.pop()]
+            rev_dst_coeff = [dst_coeff.pop()]
+            rev_extents = []
+            assert src_coeff
+            vsrc = src_coeff.pop()
+            vdst = dst_coeff.pop()
+            vext = extents.pop()
+            while src_coeff:
+                next_src = src_coeff.pop()
+                next_dst = dst_coeff.pop()
+                next_ext = extents.pop()
+
+                if (_equal(next_src, vsrc * vext) and _equal(next_dst, vdst * vext)):
+                    vext = tvm.ir_pass.Simplify(vext * next_ext)
+                else:
+                    rev_src_coeff.append(vsrc)
+                    rev_dst_coeff.append(vdst)
+                    rev_extents.append(vext)
+                    vsrc = next_src
+                    vdst = next_dst
+                    vext = next_ext
+            rev_src_coeff.append(vsrc)
+            rev_dst_coeff.append(vdst)
+            rev_extents.append(vext)
+            rev_src_coeff.reverse()
+            rev_dst_coeff.reverse()
+            rev_extents.reverse()
+
+            return rev_src_coeff, rev_dst_coeff, rev_extents
+
+        if (stmt.attr_key == "pragma_scope" and stmt.value.value == "alu"):
+            # Get to the innermost loop body
+            loop_body = stmt.body
+            nest_size = 0
+            while isinstance(loop_body, tvm.stmt.For):
+                loop_body = loop_body.body
+                nest_size += 1
+            # Get the src/dst arguments
+            dst_var = loop_body.buffer_var
+            dst_idx = loop_body.index
+            # Derive loop variables and extents
+            tmp_body = stmt.body
+            indices = []
+            extents = []
+            for _ in range(nest_size):
+                indices.append(tmp_body.loop_var)
+                extents.append(tmp_body.extent)
+                tmp_body = tmp_body.body
+            # Derive opcode
+            alu_opcode = spec.VTA_ALU_OPCODE_UNSET
+            if isinstance(loop_body.value, tvm.expr.Add):
+                alu_opcode = spec.VTA_ALU_OPCODE_ADD
+                lhs = loop_body.value.a
+                rhs = loop_body.value.b
+            elif isinstance(loop_body.value, tvm.expr.Sub):
+                alu_opcode = spec.VTA_ALU_OPCODE_SUB
+                lhs = loop_body.value.a
+                rhs = loop_body.value.b
+            elif isinstance(loop_body.value, tvm.expr.Mul):
+                alu_opcode = spec.VTA_ALU_OPCODE_MUL
+                lhs = loop_body.value.a
+                rhs = loop_body.value.b
+            elif isinstance(loop_body.value, tvm.expr.Min):
+                alu_opcode = spec.VTA_ALU_OPCODE_MIN
+                lhs = loop_body.value.a
+                rhs = loop_body.value.b
+            elif isinstance(loop_body.value, tvm.expr.Max):
+                alu_opcode = spec.VTA_ALU_OPCODE_MAX
+                lhs = loop_body.value.a
+                rhs = loop_body.value.b
+            elif isinstance(loop_body.value, tvm.expr.Call):
+                if loop_body.value.name == 'shift_left':
+                    alu_opcode = spec.VTA_ALU_OPCODE_SHL
+                    lhs = loop_body.value.args[0]
+                    rhs = loop_body.value.args[1]
+                elif loop_body.value.name == 'shift_right':
+                    alu_opcode = spec.VTA_ALU_OPCODE_SHR
+                    lhs = loop_body.value.args[0]
+                    rhs = loop_body.value.args[1]
+                else:
+                    raise RuntimeError(
+                        "Function call not recognized %s" % (loop_body.value.name))
+            else:
+                raise RuntimeError("Expression not recognized %s" % (type(loop_body.value)))
+
+            # Derive array index coefficients
+            dst_coeff = tvm.arith.DetectLinearEquation(dst_idx, indices)
+            # Check if lhs/rhs is immediate
+            use_imm = False
+            imm_val = None
+            if isinstance(rhs, tvm.expr.IntImm):
+                assert lhs.buffer_var.same_as(dst_var)
+                src_coeff = tvm.arith.DetectLinearEquation(lhs.index, indices)
+                use_imm = True
+                imm_val = rhs
+            if isinstance(lhs, tvm.expr.IntImm):
+                assert rhs.buffer_var.same_as(dst_var)
+                src_coeff = tvm.arith.DetectLinearEquation(rhs.index, indices)
+                use_imm = True
+                imm_val = lhs
+            if imm_val is None:
+                imm_val = 0
+                assert lhs.buffer_var.same_as(dst_var) and rhs.buffer_var.same_as(dst_var)
+                src_lhs_coeff = tvm.arith.DetectLinearEquation(lhs.index, indices)
+                src_rhs_coeff = tvm.arith.DetectLinearEquation(rhs.index, indices)
+                # Determine which side has the same coefficients
+                lhs_equal = True
+                rhs_equal = True
+                for i, coef in enumerate(dst_coeff):
+                    if not tvm.ir_pass.Equal(coef, src_lhs_coeff[i]):
+                        lhs_equal = False
+                    if not tvm.ir_pass.Equal(coef, src_rhs_coeff[i]):
+                        rhs_equal = False
+                # Make sure at least one of the source is identical to the
+                # destination (in-place computation)
+                assert lhs_equal or rhs_equal
+                # Assign the source coefficients
+                if lhs_equal:
+                    src_coeff = src_rhs_coeff
+                else:
+                    src_coeff = src_lhs_coeff
+
+            # Ensure that we have the proper tensor dimensions in the
+            # innermost loop (pattern match)
+            src_coeff = list(src_coeff)
+            dst_coeff = list(dst_coeff)
+            extents = list(extents)
+            assert len(src_coeff) > 1
+            assert len(dst_coeff) > 1
+            assert len(extents) > 0
+            assert tvm.ir_pass.Equal(
+                tvm.ir_pass.Simplify(
+                    src_coeff[-1]%(spec.VTA_BATCH*spec.VTA_BLOCK_OUT)), 0)
+            assert tvm.ir_pass.Equal(
+                tvm.ir_pass.Simplify(
+                    dst_coeff[-1]%(spec.VTA_BATCH*spec.VTA_BLOCK_OUT)), 0)
+            assert tvm.ir_pass.Equal(src_coeff[-2], 1)
+            assert tvm.ir_pass.Equal(dst_coeff[-2], 1)
+            if spec.VTA_BATCH > 1:
+                assert len(src_coeff) > 2
+                assert len(dst_coeff) > 2
+                assert len(extents) > 1
+                assert tvm.ir_pass.Equal(src_coeff[-3], spec.VTA_BLOCK_OUT)
+                assert tvm.ir_pass.Equal(dst_coeff[-3], spec.VTA_BLOCK_OUT)
+
+            # Apply tensorization of the loop coefficients
+            src_offset = src_coeff[-1]
+            dst_offset = dst_coeff[-1]
+            if spec.VTA_BATCH == 1:
+                src_coeff = src_coeff[:-2]
+                dst_coeff = dst_coeff[:-2]
+                extents = extents[:-1]
+            else:
+                src_coeff = src_coeff[:-3]
+                dst_coeff = dst_coeff[:-3]
+                extents = extents[:-2]
+            src_coeff.append(src_offset)
+            dst_coeff.append(dst_offset)
+            src_coeff = [
+                tvm.ir_pass.Simplify(c/(spec.VTA_BATCH*spec.VTA_BLOCK_OUT)) for c in src_coeff]
+            dst_coeff = [
+                tvm.ir_pass.Simplify(c/(spec.VTA_BATCH*spec.VTA_BLOCK_OUT)) for c in dst_coeff]
+
+            # Flatten the outer loops
+            src_coeff, dst_coeff, extents = _flatten_loop(src_coeff, dst_coeff, extents)
+
+            # Insert ALU micro-ops
+            irb = tvm.ir_builder.create()
+            for idx, extent in enumerate(extents):
+                irb.emit(tvm.call_extern(
+                    "int32", "VTAUopLoopBegin", extent, dst_coeff[idx], src_coeff[idx]))
+            irb.emit(tvm.call_extern(
+                "int32", "VTAUopPush",
+                1, 0,
+                dst_coeff[len(dst_coeff)-1],
+                src_coeff[len(src_coeff)-1],
+                0,
+                alu_opcode, use_imm, imm_val))
+            for extent in extents:
+                irb.emit(tvm.call_extern(
+                    "int32", "VTAUopLoopEnd"))
+            return irb.get()
+
+        return stmt
+
+    stmt_out = tvm.ir_pass.IRTransform(
+        stmt_in, None, _do_fold, ["AttrStmt"])
+    return stmt_out
+
+def debug_print(stmt):
+    print stmt
+    return stmt
diff --git a/vta/python/vta/mock.py b/vta/python/vta/mock.py
new file mode 100644
index 000000000000..3b50bb8c7a2c
--- /dev/null
+++ b/vta/python/vta/mock.py
@@ -0,0 +1,7 @@
+"""Mock interface for skip part of compute """
+from .intrin import intrin_gevm, intrin_gemm
+
+GEMM = intrin_gemm(True)
+GEVM = intrin_gevm(True)
+DMA_COPY = "skip_dma_copy"
+ALU = "skip_alu"
diff --git a/vta/python/vta/runtime.py b/vta/python/vta/runtime.py
new file mode 100644
index 000000000000..bfcd130ff481
--- /dev/null
+++ b/vta/python/vta/runtime.py
@@ -0,0 +1,42 @@
+"""Runtime function related hooks"""
+from __future__ import absolute_import as _abs
+
+import tvm
+
+def thread_local_command_buffer():
+    """Get thread local command buffer"""
+    ctx = tvm.call_extern("handle", "VTATLSCommandHandle")
+    return tvm.make.Call(
+        "handle", "tvm_thread_context", [ctx], tvm.expr.Call.Intrinsic, None, 0)
+
+CB_HANDLE = thread_local_command_buffer()
+
+VTA_AXIS = tvm.thread_axis("vta")
+VTA_PUSH_UOP = tvm.make.StringImm("VTAPushGEMMOp")
+
+SCOPE_INP = "local.inp_buffer"
+SCOPE_OUT = "local.out_buffer"
+SCOPE_WGT = "local.wgt_buffer"
+DMA_COPY = "dma_copy"
+ALU = "alu"
+DEBUG_NO_SYNC = False
+
+def get_task_qid(qid):
+    """Get transformed queue index."""
+    return 1 if DEBUG_NO_SYNC else qid
+
+
+@tvm.register_func("tvm.intrin.rule.default.vta.coproc_sync")
+def coproc_sync(op):
+    return tvm.call_extern(
+        "int32", "VTASynchronize", CB_HANDLE, 1<<31)
+
+@tvm.register_func("tvm.intrin.rule.default.vta.coproc_dep_push")
+def coproc_dep_push(op):
+    return tvm.call_extern(
+        "int32", "VTADepPush", CB_HANDLE, op.args[0], op.args[1])
+
+@tvm.register_func("tvm.intrin.rule.default.vta.coproc_dep_pop")
+def coproc_dep_pop(op):
+    return tvm.call_extern(
+        "int32", "VTADepPop", CB_HANDLE, op.args[0], op.args[1])
diff --git a/vta/python/vta/vta_conv2d.py b/vta/python/vta/vta_conv2d.py
new file mode 100644
index 000000000000..97fa42bf1b96
--- /dev/null
+++ b/vta/python/vta/vta_conv2d.py
@@ -0,0 +1,373 @@
+"""Namespace for supporting packed_conv2d + ewise variant of nnvm."""
+
+from collections import namedtuple
+
+import logging
+import tvm
+import topi
+
+from nnvm.top import registry as reg, OpPattern
+
+from . import intrin, runtime as vta
+from intrin import GEVM
+
+TARGET_BOARD = "llvm -mtriple=armv7-none-linux-gnueabihf -mcpu=cortex-a9 -mattr=+neon"
+
+Workload = namedtuple("Conv2DWorkload",
+                      ['height', 'width', 'in_filter', 'out_filter',
+                       'hkernel', 'wkernel', 'hpad', 'wpad', 'hstride', 'wstride'])
+
+
+def packed_conv2d(data,
+                  kernel,
+                  padding,
+                  strides,
+                  out_dtype="int32"):
+    """ Packed conv2d function.
+    """
+    if padding[0]:
+        pad_data = topi.nn.pad(data, [0, 0, padding[0], padding[1], 0], name="pad_data")
+    else:
+        pad_data = data
+    assert len(data.shape) == 5
+    assert len(kernel.shape) == 6
+    oheight = topi.util.simplify((pad_data.shape[2] - kernel.shape[2]) // strides[0] + 1)
+    owidth = topi.util.simplify((pad_data.shape[3] - kernel.shape[3]) // strides[1] + 1)
+    oshape = (data.shape[0], kernel.shape[0], oheight, owidth, kernel.shape[4])
+
+    ishape = topi.util.get_const_tuple(data.shape)
+    kshape = topi.util.get_const_tuple(kernel.shape)
+    assert data.dtype == "int8", data.dtype
+    assert kernel.dtype == "int8", kernel.dtype
+    d_i = tvm.reduce_axis((0, kshape[2]), name='d_i')
+    d_j = tvm.reduce_axis((0, kshape[3]), name='d_j')
+    k_o = tvm.reduce_axis((0, ishape[1]), name='k_o')
+    k_i = tvm.reduce_axis((0, ishape[-1]), name='k_i')
+    hstride, wstride = strides
+    res = tvm.compute(
+        oshape,
+        lambda b, co, i, j, ci: tvm.sum(
+            pad_data[b, k_o, i*hstride+d_i, j*wstride+d_j, k_i].astype(out_dtype) *
+            kernel[co, k_o, d_i, d_j, ci, k_i].astype(out_dtype),
+            axis=[k_o, d_i, d_j, k_i]),
+        name="res", tag="packed_conv2d")
+    return res
+
+
+@tvm.register_func("nnvm.compiler.build_target", override=True)
+def _build(funcs, target, target_host):
+    tvm_t = tvm.target.create(target)
+    if tvm_t.device_name == "vta":
+        return tvm.build(funcs, target="ext_dev",
+                         target_host=TARGET_BOARD)
+    elif tvm_t.device_name == "rasp" or tvm_t.device_name == "tcpu":
+        return tvm.build(funcs, target=TARGET_BOARD)
+    return tvm.build(funcs, target=target)
+
+
+@tvm.register_func("nnvm.compiler.lower", override=True)
+def _lower(sch, inputs, func_name, graph):
+    import traceback
+    # pylint: disable=broad-except
+    try:
+        f = tvm.lower(sch, inputs, name=func_name)
+        if "quantized_conv2d" in func_name:
+            logging.info(graph.ir(join_entry_attrs=["shape"]))
+    except Exception:
+        msg = traceback.format_exc()
+        msg += "Error during compile graph\n"
+        msg += "--------------------------\n"
+        msg += graph.ir(join_entry_attrs=["shape"])
+        raise RuntimeError(msg)
+    return f if isinstance(
+        f, (tvm.container.Array, tuple, list)) else [f]
+
+
+@reg.register_compute("clip", level=11)
+def compute_clip(attrs, inputs, _):
+    """ Clip operator.
+    """
+    x = inputs[0]
+    a_min = attrs.get_float("a_min")
+    a_max = attrs.get_float("a_max")
+    const_min = tvm.const(a_min, x.dtype)
+    const_max = tvm.const(a_max, x.dtype)
+    with tvm.tag_scope(topi.tag.ELEMWISE):
+        x = tvm.compute(
+            x.shape, lambda *i: tvm.min(x(*i), const_max), name="clipA")
+        x = tvm.compute(
+            x.shape, lambda *i: tvm.max(x(*i), const_min), name="clipB")
+    return x
+
+
+reg.register_pattern("identity", OpPattern.INJECTIVE, level=11)
+
+@reg.register_compute("quantized_conv2d", level=11)
+def compute_quantized_conv2d(attrs, inputs, out):
+    """ 2D convolution algorithm.
+    """
+    padding = attrs.get_int_tuple("padding")
+    strides = attrs.get_int_tuple("strides")
+    dilation = attrs.get_int_tuple("dilation")
+    groups = attrs.get_int("groups")
+    channels = attrs.get_int("channels")
+    layout = attrs["layout"]
+    out_dtype = attrs['out_type']
+    cmp_dtype = 'int32' # compute data type
+
+    assert layout == "NCHW", "only support nchw for now"
+    assert dilation == (1, 1), "not support dilate now"
+    assert attrs.get_bool("use_bias") is False
+    pack_channel = attrs.get_int("pack_channel")
+    if pack_channel != 0:
+        assert groups == 1
+        return packed_conv2d(inputs[0], inputs[1],
+                             padding, strides)
+    if groups == 1:
+        out = topi.nn.conv2d(inputs[0], inputs[1], strides, padding, out_dtype=cmp_dtype)
+    elif groups == get_const_int(inputs[0].shape[1]) and groups == channels:
+        out = topi.nn.depthwise_conv2d_nchw(
+            inputs[0], inputs[1], strides, padding, out_dtype=cmp_dtype)
+    else:
+        raise ValueError("not support arbitrary group number for now")
+
+    assert out_dtype == cmp_dtype
+    return out
+
+
+@reg.register_schedule("quantized_conv2d", level=11)
+def schedule_quantized_conv2d(attrs, outs, target):
+    """ 2D convolution schedule.
+    """
+    channels = attrs.get_int("channels")
+    pack_channel = attrs.get_int("pack_channel")
+    if channels != 0 and pack_channel:
+        target = tvm.target.create(target)
+        if target.device_name == "vta":
+            return schedule_packed_conv2d(outs)
+        elif target.startswith("llvm"):
+            return tvm.create_schedule([x.op for x in outs])
+        else:
+            raise RuntimeError("not support target %s" % target)
+    with tvm.target.create(target):
+        return topi.generic.schedule_conv2d_nchw(outs)
+
+
+def _get_workload(data, pad_data, kernel, output):
+    """ Get the workload structure.
+    """
+    o_shape = topi.util.get_const_tuple(output.shape)
+    d_shape = topi.util.get_const_tuple(data.shape)
+    k_shape = topi.util.get_const_tuple(kernel.shape)
+    o_b, o_c, o_h, o_w, o_blk = o_shape
+    i_b, i_c, i_h, i_w, i_blk = d_shape
+    k_o, k_i, k_h, k_w, ko_blk, ki_blk = k_shape
+    # For now we need to assume that input channel blocking is the same
+    # as the output channel blocking
+    assert o_blk == i_blk
+    # Make sure that dimensions match
+    assert o_b == i_b
+    assert o_blk == ko_blk
+    assert i_blk == ki_blk
+    assert k_o == o_c
+    assert k_i == i_c
+    # Scale the channel size
+    i_c *= i_blk
+    o_c *= o_blk
+    if pad_data is not None:
+        p_shape = topi.util.get_const_tuple(pad_data.shape)
+        h_pad = (p_shape[2] - d_shape[2]) // 2
+        w_pad = (p_shape[3] - d_shape[3]) // 2
+    else:
+        h_pad, w_pad = 0, 0
+    h_str = (i_h + h_pad*2 - k_h) // (o_h - 1)
+    w_str = (i_w + w_pad*2 - k_w) // (o_w - 1)
+    return Workload(i_h, i_w, i_c, o_c, k_h, k_w, h_pad, w_pad, h_str, w_str)
+
+_WL2PLAN = {}
+
+def schedule_packed_conv2d(outs):
+    """ Schedule the packed conv2d.
+    """
+    assert len(outs) == 1
+    output = outs[0]
+    ewise_inputs = []
+    ewise_ops = []
+    conv2d_res = []
+    assert output.dtype == "int8"
+    assert output.op.input_tensors[0].dtype == "int32"
+
+    def _traverse(op):
+        if topi.tag.is_broadcast(op.tag):
+            if not op.same_as(output.op):
+                ewise_ops.append(op)
+            for tensor in op.input_tensors:
+                if isinstance(tensor.op, tvm.tensor.PlaceholderOp):
+                    ewise_inputs.append((op, tensor))
+                else:
+                    _traverse(tensor.op)
+        else:
+            assert op.tag == "packed_conv2d"
+            conv2d_res.append(op)
+
+    _traverse(output.op)
+    assert len(conv2d_res) == 1
+    conv2d_stage = conv2d_res[0].output(0)
+
+    data, kernel = conv2d_stage.op.input_tensors
+    if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
+        temp = data.op.input_tensors[0]
+        pad_data = data
+        data = temp
+    else:
+        pad_data = None
+    wrkld = _get_workload(data, pad_data, kernel, output)
+
+    plan = _WL2PLAN[wrkld]
+    load_inp = load_wgt = load_out = store_out = "dma_copy"
+    alu = "alu"
+    gevm = GEVM
+
+    # schedule1
+    oshape = topi.util.get_const_tuple(output.shape)
+    s = tvm.create_schedule(output.op)
+
+    # setup pad
+    if pad_data is not None:
+        cdata = pad_data
+        s[pad_data].set_scope(vta.SCOPE_INP)
+    else:
+        cdata = s.cache_read(data, vta.SCOPE_INP, [conv2d_stage])
+    ckernel = s.cache_read(kernel, vta.SCOPE_WGT, [conv2d_stage])
+    s[conv2d_stage].set_scope(vta.SCOPE_OUT)
+    # cache read input
+    cache_read_ewise = []
+
+    for consumer, tensor in ewise_inputs:
+        cache_read_ewise.append(
+            s.cache_read(tensor, vta.SCOPE_OUT, [consumer]))
+    # set ewise scope
+    for op in ewise_ops:
+        s[op].set_scope(vta.SCOPE_OUT)
+        s[op].pragma(s[op].op.axis[0], alu)
+
+    # tile
+    oc_factor = (plan.oc_factor if plan.oc_factor
+                 else wrkld.out_filter // vta.VTA_BLOCK_OUT)
+    h_factor = (plan.h_factor if plan.h_factor else oshape[2])
+    w_factor = (plan.w_factor if plan.w_factor else oshape[3])
+
+    x_b, x_oc, x_i, x_j, x_ic = s[output].op.axis
+    x_oc0, x_oc1 = s[output].split(x_oc, factor=oc_factor)
+    x_i0, x_i1 = s[output].split(x_i, factor=h_factor)
+    x_j0, x_j1 = s[output].split(x_j, factor=w_factor)
+    s[output].reorder(x_b, x_oc0, x_i0, x_j0, x_oc1, x_i1, x_j1, x_ic)
+    store_pt = x_j0
+
+    # set all compute scopes
+    s[conv2d_stage].compute_at(s[output], store_pt)
+    for op in ewise_ops:
+        s[op].compute_at(s[output], store_pt)
+
+    for tensor in cache_read_ewise:
+        s[tensor].compute_at(s[output], store_pt)
+        s[tensor].pragma(s[tensor].op.axis[0], load_out)
+
+    # virtual threading along output channel axes
+    if plan.oc_nthread:
+        _, v_t = s[output].split(x_oc0, factor=plan.oc_nthread)
+        s[output].reorder(v_t, x_b)
+        s[output].bind(v_t, tvm.thread_axis("cthread"))
+
+    # virtual threading along spatial rows
+    if plan.h_nthread:
+        _, v_t = s[output].split(x_i0, factor=plan.h_nthread)
+        s[output].reorder(v_t, x_b)
+        s[output].bind(v_t, tvm.thread_axis("cthread"))
+
+    x_b, x_oc, x_i, x_j, x_ic = s[conv2d_stage].op.axis
+    k_o, d_i, d_j, k_i = s[conv2d_stage].op.reduce_axis
+    s[conv2d_stage].reorder(k_o, x_j, d_j, d_i, x_oc, x_i, x_ic, k_i)
+
+    if plan.ko_factor:
+        k_o, _ = s[conv2d_stage].split(k_o, factor=plan.ko_factor)
+        s[cdata].compute_at(s[conv2d_stage], k_o)
+        s[ckernel].compute_at(s[conv2d_stage], k_o)
+
+    # Use VTA instructions
+    s[cdata].pragma(s[cdata].op.axis[0], load_inp)
+    s[ckernel].pragma(s[ckernel].op.axis[0], load_wgt)
+    s[conv2d_stage].tensorize(x_ic, gevm)
+    s[output].pragma(x_oc1, store_out)
+    return s
+
+
+class Conv2DSchedule(object):
+    """ 2D convolution schedule object.
+    """
+    def __init__(self,
+                 oc_factor,
+                 ko_factor=1,
+                 h_factor=1,
+                 w_factor=0,
+                 oc_nthread=0,
+                 h_nthread=0):
+        self.oc_factor = oc_factor
+        self.ko_factor = ko_factor
+        self.h_factor = h_factor
+        self.w_factor = w_factor
+        self.oc_nthread = oc_nthread
+        self.h_nthread = h_nthread
+
+Schedule = Conv2DSchedule
+
+# ResNet18 workloads
+RESNET = {
+    # Workloads of resnet18 on imagenet
+    0: Workload(224, 224, 16, 64, 7, 7, 3, 3, 2, 2),
+    1: Workload(56, 56, 64, 64, 3, 3, 1, 1, 1, 1),
+    2: Workload(56, 56, 64, 64, 1, 1, 0, 0, 1, 1),
+    3: Workload(56, 56, 64, 128, 3, 3, 1, 1, 2, 2),
+    4: Workload(56, 56, 64, 128, 1, 1, 0, 0, 2, 2),
+    5: Workload(28, 28, 128, 128, 3, 3, 1, 1, 1, 1),
+    6: Workload(28, 28, 128, 256, 3, 3, 1, 1, 2, 2),
+    7: Workload(28, 28, 128, 256, 1, 1, 0, 0, 2, 2),
+    8: Workload(14, 14, 256, 256, 3, 3, 1, 1, 1, 1),
+    9: Workload(14, 14, 256, 512, 3, 3, 1, 1, 2, 2),
+    10: Workload(14, 14, 256, 512, 1, 1, 0, 0, 2, 2),
+    11: Workload(7, 7, 512, 512, 3, 3, 1, 1, 1, 1),
+}
+
+# Serial schedule
+RESNET_SERIAL = {
+    RESNET[0]: Schedule(oc_factor=1, ko_factor=1, h_factor=4, w_factor=56),
+    RESNET[1]: Schedule(oc_factor=2, ko_factor=1, h_factor=14, w_factor=0),
+    RESNET[2]: Schedule(oc_factor=4, ko_factor=4, h_factor=8, w_factor=0),
+    RESNET[3]: Schedule(oc_factor=4, ko_factor=1, h_factor=14, w_factor=0),
+    RESNET[4]: Schedule(oc_factor=8, ko_factor=1, h_factor=4, w_factor=0),
+    RESNET[5]: Schedule(oc_factor=8, ko_factor=1, h_factor=7, w_factor=0),
+    RESNET[6]: Schedule(oc_factor=8, ko_factor=1, h_factor=14, w_factor=0),
+    RESNET[7]: Schedule(oc_factor=16, ko_factor=1, h_factor=7, w_factor=0),
+    RESNET[8]: Schedule(oc_factor=8, ko_factor=1, h_factor=7, w_factor=0),
+    RESNET[9]: Schedule(oc_factor=8, ko_factor=1, h_factor=7, w_factor=0),
+    RESNET[10]: Schedule(oc_factor=16, ko_factor=1, h_factor=7, w_factor=0),
+    RESNET[11]: Schedule(oc_factor=8, ko_factor=1, h_factor=7, w_factor=0),
+}
+
+# Latency hiding schedule
+RESNET_OPT = {
+    RESNET[0]: Schedule(oc_factor=1, ko_factor=1, h_factor=4, w_factor=56),
+    RESNET[1]: Schedule(oc_factor=2, ko_factor=1, h_factor=7, h_nthread=2),
+    RESNET[2]: Schedule(oc_factor=4, ko_factor=2, h_factor=4, w_factor=0, h_nthread=2),
+    RESNET[3]: Schedule(oc_factor=4, ko_factor=1, h_factor=7, w_factor=0, h_nthread=2),
+    RESNET[4]: Schedule(oc_factor=4, ko_factor=1, h_factor=7, h_nthread=2),
+    RESNET[5]: Schedule(oc_factor=4, ko_factor=1, h_factor=7, w_factor=0, h_nthread=2),
+    RESNET[6]: Schedule(oc_factor=4, ko_factor=1, h_factor=7, w_factor=0, oc_nthread=2),
+    RESNET[7]: Schedule(oc_factor=8, ko_factor=1, h_factor=7, w_factor=0, oc_nthread=2),
+    RESNET[8]: Schedule(oc_factor=4, ko_factor=1, h_factor=7, w_factor=0, oc_nthread=2),
+    RESNET[9]: Schedule(oc_factor=4, ko_factor=1, h_factor=7, w_factor=0, oc_nthread=2),
+    RESNET[10]: Schedule(oc_factor=8, ko_factor=1, h_factor=7, w_factor=0, oc_nthread=2),
+    RESNET[11]: Schedule(oc_factor=4, ko_factor=1, h_factor=7, w_factor=0, oc_nthread=2),
+}
+
+_WL2PLAN = RESNET_OPT
diff --git a/vta/src/pynq/pynq_driver.h b/vta/src/pynq/pynq_driver.h
index 952c4cff8c59..481df6bbe077 100644
--- a/vta/src/pynq/pynq_driver.h
+++ b/vta/src/pynq/pynq_driver.h
@@ -80,4 +80,4 @@ void xlnkInvalidateCache(void* buf, int size);
 #ifdef __cplusplus
 }
 #endif
-#endif  // VTA_PYNQ_PYNQ_DRIVER_H_
\ No newline at end of file
+#endif  // VTA_PYNQ_PYNQ_DRIVER_H_
diff --git a/vta/src/runtime.cc b/vta/src/runtime.cc
index dde88e8cc829..7c5708b4ed43 100644
--- a/vta/src/runtime.cc
+++ b/vta/src/runtime.cc
@@ -1043,9 +1043,9 @@ class CommandQueue {
     VTAWriteMappedReg(vta_load_handle_, 0x10, 0);
     // LOAD @ 0x18 : Data signal of weight_V
     VTAWriteMappedReg(vta_load_handle_, 0x18, 0);
-    // COMPUTE @ 0x10 : Data signal of uops_V
+    // COMPUTE @ 0x20 : Data signal of uops_V
     VTAWriteMappedReg(vta_compute_handle_, 0x20, 0);
-    // COMPUTE @ 0x18 : Data signal of biases_V
+    // COMPUTE @ 0x28 : Data signal of biases_V
     VTAWriteMappedReg(vta_compute_handle_, 0x28, 0);
     // STORE @ 0x10 : Data signal of outputs_V
     VTAWriteMappedReg(vta_store_handle_, 0x10, 0);
diff --git a/vta/tests/hardware/common/test_lib.h b/vta/tests/hardware/common/test_lib.h
index 037e2fcee72f..458ff7138edb 100644
--- a/vta/tests/hardware/common/test_lib.h
+++ b/vta/tests/hardware/common/test_lib.h
@@ -39,7 +39,7 @@ uint64_t vta(
 
 #else  // NO_SIM
 
-#include "../../../hardware/vivado/src/vta.h"
+#include "../../../hardware/xilinx/src/vta.h"
 
 #endif  // NO_SIM
 
diff --git a/vta/tests/hardware/pynq/Makefile b/vta/tests/hardware/pynq/Makefile
index 7e70366f32c0..dabf55e262f6 100644
--- a/vta/tests/hardware/pynq/Makefile
+++ b/vta/tests/hardware/pynq/Makefile
@@ -1,6 +1,6 @@
 CC ?= g++
 CFLAGS = -Wall -O3 -std=c++11 -I/usr/include
-LDFLAGS = -L/usr/lib -L/home/xilinx/pynq/drivers
+LDFLAGS = -L/usr/lib -L/opt/python3.6/lib/python3.6/site-packages/pynq/lib/
 LIBS = -l:libsds_lib.so -l:libdma.so
 INCLUDE_DIR = ../../../include
 DRIVER_DIR = ../../../src/pynq
diff --git a/vta/tests/python/pynq/test_benchmark_conv2d.py b/vta/tests/python/pynq/test_benchmark_conv2d.py
new file mode 100644
index 000000000000..1ffb3e73de64
--- /dev/null
+++ b/vta/tests/python/pynq/test_benchmark_conv2d.py
@@ -0,0 +1,414 @@
+import os
+import tvm
+import mxnet as mx
+import vta
+import numpy as np
+import topi
+from collections import namedtuple
+from tvm.contrib import rpc, util
+import pandas as pd
+
+host = "pynq"
+port = 9091
+target = "llvm -target=armv7-none-linux-gnueabihf -mattr=+neon"
+out_dtype = "int%d" % vta.VTA_OUT_WIDTH
+inp_dtype = "int%d" % vta.VTA_INP_WIDTH
+wgt_dtype = "int%d" % vta.VTA_WGT_WIDTH
+
+Workload = namedtuple("Conv2DWorkload",
+                      ['height', 'width', 'in_filter', 'out_filter',
+                       'hkernel', 'wkernel', 'hpad', 'wpad', 'hstride', 'wstride'])
+
+class Conv2DSchedule(object):
+    def __init__(self,
+                 oc_factor,
+                 ko_factor=1,
+                 h_factor=1,
+                 w_factor=0,
+                 oc_nthread=0,
+                 h_nthread=0,
+                 debug_sync=False):
+        self.oc_factor = oc_factor
+        self.ko_factor = ko_factor
+        self.h_factor = h_factor
+        self.w_factor = w_factor
+        self.oc_nthread = oc_nthread
+        self.h_nthread = h_nthread
+        self.debug_sync = debug_sync
+
+Schedule = Conv2DSchedule
+
+def test_conv2d_chwv(key, batch_size, wl, plan, log_frame, profile=True):
+    assert batch_size % vta.VTA_BATCH == 0
+    assert wl.in_filter % vta.VTA_BLOCK_IN == 0
+    assert wl.out_filter % vta.VTA_BLOCK_OUT == 0
+    data_shape = (batch_size//vta.VTA_BATCH, wl.in_filter//vta.VTA_BLOCK_IN,
+                  wl.height, wl.width, vta.VTA_BATCH, vta.VTA_BLOCK_IN)
+    kernel_shape = (wl.out_filter//vta.VTA_BLOCK_OUT, wl.in_filter//vta.VTA_BLOCK_IN,
+                    wl.hkernel, wl.wkernel, vta.VTA_BLOCK_OUT, vta.VTA_BLOCK_IN)
+    fout_height = (wl.height + 2 * wl.hpad - wl.hkernel) // wl.hstride + 1
+    fout_width = (wl.width + 2 * wl.wpad - wl.wkernel) // wl.wstride + 1
+    res_shape = (batch_size//vta.VTA_BATCH, wl.out_filter//vta.VTA_BLOCK_OUT,
+                 fout_height, fout_width, vta.VTA_BATCH, vta.VTA_BLOCK_OUT)
+    data = tvm.placeholder(data_shape, name="data", dtype=inp_dtype)
+    kernel = tvm.placeholder(kernel_shape, name="kernel", dtype=wgt_dtype)
+    if wl.hpad or wl.wpad:
+        data_buf = topi.nn.pad(data, [0, 0, wl.hpad, wl.wpad, 0, 0], name="data_buf")
+    else:
+        data_buf = tvm.compute(data_shape, lambda *i: data(*i), "data_buf")
+    kernel_buf = tvm.compute(kernel_shape, lambda *i: kernel(*i), "kernel_buf")
+    di = tvm.reduce_axis((0, wl.hkernel), name='di')
+    dj = tvm.reduce_axis((0, wl.wkernel), name='dj')
+    ko = tvm.reduce_axis((0, wl.in_filter//vta.VTA_BLOCK_IN), name='ko')
+    ki = tvm.reduce_axis((0, vta.VTA_BLOCK_IN), name='ki')
+    res_cnv = tvm.compute(
+        res_shape,
+        lambda bo, co, i, j, bi, ci: tvm.sum(
+            data_buf[bo, ko, i*wl.hstride+di, j*wl.wstride+dj, bi, ki].astype(out_dtype) *
+            kernel_buf[co, ko, di, dj, ci, ki].astype(out_dtype),
+        axis=[ko, di, dj, ki]),
+        name="res_cnv")
+    res_shf = tvm.compute(res_shape, lambda *i: res_cnv(*i) >> 8, name="res_shf")
+    res = tvm.compute(res_shape, lambda *i: res_shf(*i).astype(inp_dtype), name="res")
+    num_ops = batch_size * fout_height * fout_width * wl.hkernel * wl.wkernel * wl.out_filter * wl.in_filter
+
+    def verify(s, check_correctness):
+        mod = tvm.build(s, [data, kernel, res], "ext_dev", target, name="conv2d")
+        temp = util.tempdir()
+        remote = rpc.connect(host, port)
+        mod.save(temp.relpath("conv2d.o"))
+        remote.upload(temp.relpath("conv2d.o"))
+        f = remote.load_module("conv2d.o")
+        # verify
+        ctx = remote.ext_dev(0)
+        # Data in original format
+        data_orig = np.random.randint(
+            -128, 128, size=(batch_size, wl.in_filter, wl.height, wl.width)).astype(data.dtype)
+        kernel_orig = np.random.randint(
+            -128, 128, size=(wl.out_filter, wl.in_filter, wl.hkernel, wl.wkernel)).astype(kernel.dtype)
+        data_packed = data_orig.reshape(
+            batch_size//vta.VTA_BATCH, vta.VTA_BATCH,
+            wl.in_filter//vta.VTA_BLOCK_IN, vta.VTA_BLOCK_IN,
+            wl.height, wl.width).transpose((0, 2, 4, 5, 1, 3))
+        kernel_packed = kernel_orig.reshape(
+            wl.out_filter//vta.VTA_BLOCK_OUT, vta.VTA_BLOCK_OUT,
+            wl.in_filter//vta.VTA_BLOCK_IN, vta.VTA_BLOCK_IN,
+            wl.hkernel, wl.wkernel).transpose((0, 2, 4, 5, 1, 3))
+        res_np = np.zeros(res_shape).astype(res.dtype)
+        data_arr = tvm.nd.array(data_packed, ctx)
+        kernel_arr = tvm.nd.array(kernel_packed, ctx)
+        res_arr = tvm.nd.array(res_np, ctx)
+        time_f = f.time_evaluator("conv2d", ctx, number=10)
+        cost = time_f(data_arr, kernel_arr, res_arr)
+        res_unpack = res_arr.asnumpy().transpose(
+            (0, 4, 1, 5, 2, 3)).reshape(batch_size, wl.out_filter, fout_height, fout_width)
+        if check_correctness:
+            res_ref = mx.nd.Convolution(
+                mx.nd.array(data_orig.astype(out_dtype), mx.cpu(0)),
+                mx.nd.array(kernel_orig.astype(out_dtype), mx.cpu(0)),
+                stride=(wl.hstride, wl.wstride),
+                kernel=(wl.hkernel, wl.wkernel),
+                num_filter=wl.out_filter,
+                no_bias=True,
+                pad=(wl.hpad, wl.wpad)).asnumpy().astype(out_dtype)
+            res_ref = np.right_shift(res_ref, 8).astype(res.dtype)
+            np.testing.assert_allclose(res_unpack, res_ref)
+            print("Correctness check pass...")
+        return cost
+
+    def run_schedule(load_inp, load_wgt, gemm, alu, store_out,
+                     print_ir, check_correctness):
+        # schedule1
+        s = tvm.create_schedule(res.op)
+        s[data_buf].set_scope(vta.SCOPE_INP)
+        s[kernel_buf].set_scope(vta.SCOPE_WGT)
+        s[res_cnv].set_scope(vta.SCOPE_OUT)
+        s[res_shf].set_scope(vta.SCOPE_OUT)
+        # tile
+        oc_factor = (plan.oc_factor if plan.oc_factor
+                     else wl.out_filter // vta.VTA_BLOCK_OUT)
+        h_factor = (plan.h_factor if plan.h_factor else fout_height)
+        w_factor = (plan.w_factor if plan.w_factor else fout_width)
+        xbo, xco, xi, xj, xbi, xci = s[res].op.axis
+        xco0, xco1 = s[res].split(xco, factor=oc_factor)
+        xi0, xi1 = s[res].split(xi, factor=h_factor)
+        xj0, xj1 = s[res].split(xj, factor=w_factor)
+        s[res].reorder(xbo, xi0, xco0, xj0, xco1, xi1, xj1, xbi, xci)
+        s[res_cnv].compute_at(s[res], xj0)
+        s[res_shf].compute_at(s[res], xj0)
+
+        if plan.oc_nthread:
+            _, tx = s[res].split(xco0, factor=plan.oc_nthread)
+            s[res].reorder(tx, xbo)
+            s[res].bind(tx, tvm.thread_axis("cthread"))
+
+        if plan.h_nthread:
+            xo, tx = s[res].split(xi0, factor=plan.h_nthread)
+            s[res].reorder(tx, xbo)
+            s[res].bind(tx, tvm.thread_axis("cthread"))
+
+        xbo, xco, xi, xj, xbi, xci = s[res_cnv].op.axis
+        s[res_cnv].reorder(xbo, ko, xj, dj, di, xco, xi, xbi, xci, ki)
+
+        if plan.ko_factor:
+            ko0, ko1 = s[res_cnv].split(ko, factor=plan.ko_factor)
+            s[data_buf].compute_at(s[res_cnv], ko0)
+            s[kernel_buf].compute_at(s[res_cnv], ko0)
+        # Use VTA instructions
+        s[data_buf].pragma(s[data_buf].op.axis[0], load_inp)
+        s[kernel_buf].pragma(s[kernel_buf].op.axis[0], load_wgt)
+        s[res_cnv].tensorize(xbi, gemm)
+        s[res_shf].pragma(s[res_shf].op.axis[0], alu)
+        s[res].pragma(xco1, store_out)
+        if plan.debug_sync:
+            s[res].pragma(xco0, "coproc_sync")
+        if print_ir:
+            print(tvm.lower(s, [data, kernel, res], simple_mode=True))
+        return verify(s, check_correctness)
+
+    def conv_normal(print_ir):
+        print("----- CONV2D End-to-End Test-------")
+        def run_test(header, print_ir, check_correctness):
+            cost = run_schedule(
+                vta.DMA_COPY, vta.DMA_COPY,
+                vta.GEMM, vta.ALU, vta.DMA_COPY,
+                print_ir, check_correctness)
+            gops = (num_ops / cost.mean) / float(10 ** 9)
+            print(header)
+            print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
+            log_frame["key"].append(key)
+            log_frame["total-gops"].append(gops)
+            log_frame["total-cost"].append(cost.mean)
+
+        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+            run_test("NORMAL", print_ir, True)
+
+    def skip_alu_unittest(print_ir):
+        mock = vta.mock
+        print("----- Skip ALU Unit Test-------")
+        def run_test(header, print_ir):
+            cost = run_schedule(
+                vta.DMA_COPY, vta.DMA_COPY,
+                vta.GEMM, mock.ALU, vta.DMA_COPY,
+                print_ir, False)
+            gops = (num_ops / cost.mean) / float(10 ** 9)
+            print(header)
+            print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
+            log_frame["skip-alu-gops"].append(gops)
+            log_frame["skip-alu-cost"].append(cost.mean)
+
+        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+            run_test("NORMAL", print_ir)
+        print("")
+
+    def gemm_unittest(print_ir):
+        mock = vta.mock
+        print("----- GEMM Unit Test-------")
+        def run_test(header, print_ir):
+            cost = run_schedule(
+                mock.DMA_COPY, mock.DMA_COPY,
+                vta.GEMM, mock.ALU, mock.DMA_COPY,
+                print_ir, False)
+            gops = (num_ops / cost.mean) / float(10 ** 9)
+            print(header)
+            print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
+            log_frame["gemm-gops"].append(gops)
+            log_frame["gemm-cost"].append(cost.mean)
+        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+            run_test("NORMAL", print_ir)
+        print("")
+
+    def alu_unittest(print_ir):
+        mock = vta.mock
+        print("----- ALU Unit Test-------")
+        def run_test(header, print_ir):
+            cost = run_schedule(
+                mock.DMA_COPY, mock.DMA_COPY,
+                mock.GEMM, vta.ALU, mock.DMA_COPY,
+                print_ir, False)
+            gops = (num_ops / cost.mean) / float(10 ** 9)
+            print(header)
+            print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
+            log_frame["alu-gops"].append(gops)
+            log_frame["alu-cost"].append(cost.mean)
+        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+            run_test("NORMAL", print_ir)
+        print("")
+
+    def load_inp_unittest(print_ir):
+        mock = vta.mock
+        print("----- LoadInp Unit Test-------")
+        def run_test(header, print_ir):
+            cost = run_schedule(
+                vta.DMA_COPY, mock.DMA_COPY,
+                mock.GEMM, mock.ALU, mock.DMA_COPY,
+                print_ir, False)
+            gops = (num_ops / cost.mean) / float(10 ** 9)
+            bandwith = (batch_size * wl.in_filter * wl.height *
+                        wl.width * vta.INP_WIDTH / cost.mean) / float(10 ** 9)
+            print(header)
+            print("\tTime cost = %g sec/op, %g GFLOPS, bandwith=%g gbits" % (
+                cost.mean, gops, bandwith))
+            log_frame["ld-inp-gbits"].append(bandwith)
+            log_frame["ld-inp-cost"].append(cost.mean)
+        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+            run_test("NORMAL", print_ir)
+        print("")
+
+    def load_wgt_unittest(print_ir):
+        mock = vta.mock
+        print("----- LoadWgt Unit Test-------")
+        def run_test(header, print_ir):
+            cost = run_schedule(
+                mock.DMA_COPY, vta.DMA_COPY,
+                mock.GEMM, mock.ALU, mock.DMA_COPY, print_ir,
+                False)
+            gops = (num_ops / cost.mean) / float(10 ** 9)
+            bandwith = (wl.out_filter * wl.in_filter * wl.hkernel *
+                        wl.wkernel * vta.WGT_WIDTH / cost.mean) / float(10 ** 9)
+            print(header)
+            print("\tTime cost = %g sec/op, %g GFLOPS, bandwith=%g gbits" % (
+                cost.mean, gops, bandwith))
+            log_frame["ld-wgt-gbits"].append(bandwith)
+            log_frame["ld-wgt-cost"].append(cost.mean)
+        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+            run_test("NORMAL", print_ir)
+        print("")
+
+    def store_out_unittest(print_ir):
+        mock = vta.mock
+        print("----- StoreOut Unit Test-------")
+        def run_test(header, print_ir):
+            cost = run_schedule(
+                mock.DMA_COPY, mock.DMA_COPY,
+                mock.GEMM, mock.ALU, vta.DMA_COPY, print_ir,
+                False)
+            gops = (num_ops / cost.mean) / float(10 ** 9)
+            bandwith = (batch_size * wl.out_filter * fout_height *
+                        fout_width * vta.OUT_WIDTH / cost.mean) / float(10 ** 9)
+            print(header)
+            print("\tTime cost = %g sec/op, %g GFLOPS, bandwith=%g gbits" % (
+                cost.mean, gops, bandwith))
+            log_frame["st-out-gbits"].append(bandwith)
+            log_frame["st-out-cost"].append(cost.mean)
+        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+            run_test("NORMAL", print_ir)
+        print("")
+
+    def manual_unittest(print_ir):
+        # Manual section used to teak the components
+        mock = vta.mock
+        print("----- Manual Unit Test-------")
+        def run_test(header, print_ir):
+            cost = run_schedule(
+                vta.DMA_COPY, vta.DMA_COPY,
+                vta.GEMM, vta.ALU, mock.DMA_COPY, print_ir,
+                False)
+            gops = (num_ops / cost.mean) / float(10 ** 9)
+            print(header)
+            print("\tTime cost = %g sec/op, %g GFLOPS" % (
+                cost.mean, gops))
+        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+            run_test("NORMAL", print_ir)
+        print("")
+
+    print("=================================")
+    print("key=%s" % key)
+    print(wl)
+    conv_normal(False)
+    if not profile:
+        return
+    skip_alu_unittest(False)
+    gemm_unittest(False)
+    alu_unittest(False)
+    load_inp_unittest(False)
+    load_wgt_unittest(False)
+    store_out_unittest(False)
+
+# ResNet18 workloads
+resnet = {
+    # Workloads of resnet18 on imagenet
+    0: Workload(224, 224, 16, 64, 7, 7, 3, 3, 2, 2),
+    1: Workload(56, 56, 64, 64, 3, 3, 1, 1, 1, 1),
+    2: Workload(56, 56, 64, 64, 1, 1, 0, 0, 1, 1),
+    3: Workload(56, 56, 64, 128, 3, 3, 1, 1, 2, 2),
+    4: Workload(56, 56, 64, 128, 1, 1, 0, 0, 2, 2),
+    5: Workload(28, 28, 128, 128, 3, 3, 1, 1, 1, 1),
+    6: Workload(28, 28, 128, 256, 3, 3, 1, 1, 2, 2),
+    7: Workload(28, 28, 128, 256, 1, 1, 0, 0, 2, 2),
+    8: Workload(14, 14, 256, 256, 3, 3, 1, 1, 1, 1),
+    9: Workload(14, 14, 256, 512, 3, 3, 1, 1, 2, 2),
+    10: Workload(14, 14, 256, 512, 1, 1, 0, 0, 2, 2),
+    11: Workload(7, 7, 512, 512, 3, 3, 1, 1, 1, 1),
+}
+
+# List of simple benchmarks
+simple = [
+    Workload(height=22, width=22, in_filter=256, out_filter=64,
+             hkernel=3, wkernel=3, hpad=1, wpad=1, hstride=1, wstride=1)
+]
+
+# Serial schedule
+resnet_serial = [
+    [None, None],
+    [resnet[1], Schedule(oc_factor=2, ko_factor=1, h_factor=14, w_factor=0)],
+    [resnet[2], Schedule(oc_factor=4, ko_factor=4, h_factor=8, w_factor=0)],
+    [resnet[3], Schedule(oc_factor=4, ko_factor=1, h_factor=14, w_factor=0)],
+    [resnet[4], Schedule(oc_factor=8, ko_factor=1, h_factor=4, w_factor=0)],
+    [resnet[5], Schedule(oc_factor=8, ko_factor=1, h_factor=7, w_factor=0)],
+    [resnet[6], Schedule(oc_factor=8, ko_factor=1, h_factor=14, w_factor=0)],
+    [resnet[7], Schedule(oc_factor=16, ko_factor=1, h_factor=7, w_factor=0)],
+    [resnet[8], Schedule(oc_factor=8, ko_factor=1, h_factor=7, w_factor=0)],
+    [resnet[9], Schedule(oc_factor=8, ko_factor=1, h_factor=7, w_factor=0)],
+    [resnet[10], Schedule(oc_factor=16, ko_factor=1, h_factor=7, w_factor=0)],
+    [resnet[11], Schedule(oc_factor=8, ko_factor=1, h_factor=7, w_factor=0)],
+]
+
+# SMT schedule
+resnet_smt = [
+    [resnet[0], Schedule(oc_factor=1, ko_factor=1, h_factor=4, w_factor=56)],
+    [resnet[1], Schedule(oc_factor=2, ko_factor=1, h_factor=7, h_nthread=2)],
+    [resnet[2], Schedule(oc_factor=4, ko_factor=2, h_factor=4, w_factor=0, h_nthread=2)],
+    [resnet[3], Schedule(oc_factor=4, ko_factor=1, h_factor=7, w_factor=0, h_nthread=2)],
+    [resnet[4], Schedule(oc_factor=4, ko_factor=1, h_factor=7, h_nthread=2)],
+    [resnet[5], Schedule(oc_factor=4, ko_factor=1, h_factor=7, w_factor=0, h_nthread=2)],
+    [resnet[6], Schedule(oc_factor=4, ko_factor=1, h_factor=7, w_factor=0, oc_nthread=2)],
+    [resnet[7], Schedule(oc_factor=8, ko_factor=1, h_factor=7, w_factor=0, oc_nthread=2)],
+    [resnet[8], Schedule(oc_factor=4, ko_factor=1, h_factor=7, w_factor=0, oc_nthread=2)],
+    [resnet[9], Schedule(oc_factor=4, ko_factor=1, h_factor=7, w_factor=0, oc_nthread=2)],
+    [resnet[10], Schedule(oc_factor=8, ko_factor=1, h_factor=7, w_factor=0, oc_nthread=2)],
+    [resnet[11], Schedule(oc_factor=4, ko_factor=1, h_factor=7, w_factor=0, oc_nthread=2)],
+]
+
+# Perform profiling
+profile = False
+# Whether use SMT
+use_smt = True
+# Data set batch size
+batch_size = 1
+
+resnet_schedule = resnet_smt if use_smt else resnet_serial
+
+begin = 0
+end = len(resnet_schedule)
+keys = ["key", "total-gops", "total-cost",
+        "skip-alu-gops", "skip-alu-cost",
+        "gemm-gops", "gemm-cost", "alu-gops", "alu-cost",
+        "ld-inp-cost", "ld-wgt-cost", "st-out-cost",
+        "ld-inp-gbits", "ld-wgt-gbits", "st-out-gbits",]
+log_frame = {
+    k : [] for k in keys
+}
+for i, x in enumerate(resnet_schedule[begin:end]):
+    wl, plan = x
+    if not wl:
+        continue
+    key = "resnet-cfg[%d]" % i
+    test_conv2d_chwv(key, batch_size, wl, plan, log_frame, profile)
+
+if profile:
+    pd.set_option('expand_frame_repr', False)
+    log_df = pd.DataFrame()
+    for k  in keys:
+        log_df[k] = log_frame[k]
+    print(log_df)
diff --git a/vta/tests/python/pynq/test_benchmark_gemm.py b/vta/tests/python/pynq/test_benchmark_gemm.py
new file mode 100644
index 000000000000..e5180c1440db
--- /dev/null
+++ b/vta/tests/python/pynq/test_benchmark_gemm.py
@@ -0,0 +1,267 @@
+import os
+import tvm
+import vta
+import numpy as np
+import time
+from tvm.contrib import rpc, util
+
+host = "pynq"
+port = 9091
+target = "llvm -target=armv7-none-linux-gnueabihf"
+out_dtype = "int%d" % vta.VTA_OUT_WIDTH
+inp_dtype = "int%d" % vta.VTA_INP_WIDTH
+wgt_dtype = "int%d" % vta.VTA_WGT_WIDTH
+
+def test_gemm_packed(batch_size, channel, block):
+    data_shape = (batch_size//vta.VTA_BATCH,
+                  channel//vta.VTA_BLOCK_IN,
+                  vta.VTA_BATCH,
+                  vta.VTA_BLOCK_IN)
+    weight_shape = (channel//vta.VTA_BLOCK_OUT,
+                    channel//vta.VTA_BLOCK_IN,
+                    vta.VTA_BLOCK_OUT,
+                    vta.VTA_BLOCK_IN)
+    res_shape = (batch_size//vta.VTA_BATCH,
+                 channel//vta.VTA_BLOCK_OUT,
+                 vta.VTA_BATCH,
+                 vta.VTA_BLOCK_OUT)
+    num_ops = channel * channel * batch_size
+
+    ko = tvm.reduce_axis((0, channel//vta.VTA_BLOCK_IN), name='ko')
+    ki = tvm.reduce_axis((0, vta.VTA_BLOCK_IN), name='ki')
+
+    data = tvm.placeholder(data_shape,
+                           name="data",
+                           dtype=inp_dtype)
+    weight = tvm.placeholder(weight_shape,
+                             name="weight",
+                             dtype=wgt_dtype)
+    data_buf = tvm.compute(data_shape,
+                           lambda *i: data(*i),
+                           "data_buf")
+    weight_buf = tvm.compute(weight_shape,
+                             lambda *i: weight(*i),
+                             "weight_buf")
+    res_gem = tvm.compute(res_shape,
+                          lambda bo, co, bi, ci: tvm.sum(
+                              data_buf[bo, ko, bi, ki].astype(out_dtype) *
+                              weight_buf[co, ko, ci, ki].astype(out_dtype),
+                              axis=[ko, ki]),
+                          name="res_gem")
+    res_shf = tvm.compute(res_shape,
+                          lambda *i: res_gem(*i)>>8,
+                          name="res_shf")
+    res_max = tvm.compute(res_shape,
+                          lambda *i: tvm.max(res_shf(*i), 0),
+                          "res_max") #relu
+    res_min = tvm.compute(res_shape,
+                          lambda *i: tvm.min(res_max(*i), (1<<(vta.VTA_INP_WIDTH-1))-1),
+                          "res_min") #relu
+    res = tvm.compute(res_shape,
+                      lambda *i: res_min(*i).astype(inp_dtype),
+                      name="res")
+
+    def verify(s, check_correctness=True):
+        mod = tvm.build(s, [data, weight, res], "ext_dev", target, name="gemm")
+        temp = util.tempdir()
+        remote = rpc.connect(host, port)
+        mod.save(temp.relpath("gemm.o"))
+        remote.upload(temp.relpath("gemm.o"))
+        f = remote.load_module("gemm.o")
+        # verify
+        ctx = remote.ext_dev(0)
+        # Data in original format
+        data_orig = np.random.randint(
+            -128, 128, size=(batch_size, channel)).astype(data.dtype)
+        weight_orig = np.random.randint(
+            -128, 128, size=(channel, channel)).astype(weight.dtype)
+        data_packed = data_orig.reshape(
+            batch_size//vta.VTA_BATCH, vta.VTA_BATCH,
+            channel//vta.VTA_BLOCK_IN, vta.VTA_BLOCK_IN).transpose((0, 2, 1, 3))
+        weight_packed = weight_orig.reshape(
+            channel//vta.VTA_BLOCK_OUT, vta.VTA_BLOCK_OUT,
+            channel//vta.VTA_BLOCK_IN, vta.VTA_BLOCK_IN).transpose((0, 2, 1, 3))
+        res_np = np.zeros(res_shape).astype(res.dtype)
+        data_arr = tvm.nd.array(data_packed, ctx)
+        weight_arr = tvm.nd.array(weight_packed, ctx)
+        res_arr = tvm.nd.array(res_np, ctx)
+        res_ref = np.zeros(res_shape).astype(out_dtype)
+        for b in range(batch_size//vta.VTA_BATCH):
+            for i in range(channel//vta.VTA_BLOCK_OUT):
+                for j in range(channel//vta.VTA_BLOCK_IN):
+                    res_ref[b,i,:] += np.dot(data_packed[b,j,:].astype(out_dtype),
+                                             weight_packed[i,j].T.astype(out_dtype))
+        res_ref = np.right_shift(res_ref, 8)
+        res_ref = np.clip(res_ref, 0, (1<<(vta.VTA_INP_WIDTH-1))-1).astype(res.dtype)
+        time_f = f.time_evaluator("gemm", ctx, number=20)
+        cost = time_f(data_arr, weight_arr, res_arr)
+        res_unpack = res_arr.asnumpy().reshape(batch_size//vta.VTA_BATCH,
+                                               channel//vta.VTA_BLOCK_OUT,
+                                               vta.VTA_BATCH,
+                                               vta.VTA_BLOCK_OUT)
+        if check_correctness:
+            np.testing.assert_allclose(res_unpack, res_ref)
+        return cost
+
+    def run_schedule(load_inp,
+                     load_wgt,
+                     gemm,
+                     alu,
+                     store_out,
+                     print_ir,
+                     check_correctness):
+        s = tvm.create_schedule(res.op)
+        s[data_buf].set_scope(vta.SCOPE_INP)
+        s[weight_buf].set_scope(vta.SCOPE_WGT)
+        s[res_gem].set_scope(vta.SCOPE_OUT)
+        s[res_shf].set_scope(vta.SCOPE_OUT)
+        s[res_min].set_scope(vta.SCOPE_OUT)
+        s[res_max].set_scope(vta.SCOPE_OUT)
+
+        if block:
+            bblock = block // vta.VTA_BATCH
+            iblock = block // vta.VTA_BLOCK_IN
+            oblock = block // vta.VTA_BLOCK_OUT
+            xbo, xco, xbi, xci = s[res].op.axis
+            xb1, xco1, xb2, xco2 = s[res].tile(xbo, xco, bblock, oblock)
+            store_pt = xb2
+
+            s[res_gem].compute_at(s[res], xco1)
+            s[res_shf].compute_at(s[res], xco1)
+            s[res_min].compute_at(s[res], xco1)
+            s[res_max].compute_at(s[res], xco1)
+
+            xbo, xco, xbi, xci = s[res_gem].op.axis
+            # Compute one line at a time
+            ko1, ko2 = s[res_gem].split(ko, iblock)
+            s[res_gem].reorder(ko1, ko2, xbo, xco, xbi, xci, ki)
+            s[data_buf].compute_at(s[res_gem], ko1)
+            s[weight_buf].compute_at(s[res_gem], ko1)
+            # Use VTA instructions
+            s[data_buf].pragma(s[data_buf].op.axis[0], load_inp)
+            s[weight_buf].pragma(s[weight_buf].op.axis[0], load_wgt)
+            s[res_gem].tensorize(xbi, gemm)
+            s[res_shf].pragma(s[res_shf].op.axis[0], alu)
+            s[res_min].pragma(s[res_min].op.axis[0], alu)
+            s[res_max].pragma(s[res_max].op.axis[0], alu)
+            s[res].pragma(store_pt, store_out)
+        else:
+            xbo, xco, xbi, xci = s[res_gem].op.axis
+            s[res_gem].reorder(ko, xbo, xco, xbi, xci, ki)
+            # Use VTA instructions
+            s[data_buf].pragma(s[data_buf].op.axis[0], load_inp)
+            s[weight_buf].pragma(s[weight_buf].op.axis[0], load_wgt)
+            s[res_gem].tensorize(xbi, gemm)
+            s[res_shf].pragma(s[res_shf].op.axis[0], alu)
+            s[res_min].pragma(s[res_min].op.axis[0], alu)
+            s[res_max].pragma(s[res_max].op.axis[0], alu)
+            s[res].pragma(s[res].op.axis[0], store_out)
+
+        if print_ir:
+            print(tvm.lower(s, [data, weight, res], simple_mode=True))
+        return verify(s, check_correctness)
+
+    def gemm_normal(print_ir):
+        mock = vta.mock
+        print("----- GEMM GFLOPS End-to-End Test-------")
+        def run_test(header, print_ir, check_correctness):
+            cost = run_schedule(
+                vta.DMA_COPY, vta.DMA_COPY, vta.GEMM, vta.ALU, vta.DMA_COPY,
+                print_ir, check_correctness)
+            gops = (num_ops / cost.mean) / float(10 ** 9)
+            print(header)
+            print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
+        with tvm.build_config(add_lower_pass=vta.debug_mode(vta.DEBUG_DUMP_INSN)):
+            run_test("NORMAL", print_ir, True)
+
+        print("")
+
+    def gevm_unittest(print_ir):
+        mock = vta.mock
+        print("----- GEMM Unit Test-------")
+        def run_test(header, print_ir):
+            cost = run_schedule(
+                mock.DMA_COPY, mock.DMA_COPY, vta.GEMM, mock.ALU, mock.DMA_COPY,
+                print_ir, False)
+            gops = (num_ops / cost.mean) / float(10 ** 9)
+            print(header)
+            print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
+        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+            run_test("NORMAL", print_ir)
+        print("")
+
+    def alu_unittest(print_ir):
+        mock = vta.mock
+        print("----- ALU Unit Test-------")
+        def run_test(header, print_ir):
+            cost = run_schedule(
+                mock.DMA_COPY, mock.DMA_COPY, mock.GEMM, vta.ALU, mock.DMA_COPY,
+                print_ir, False)
+            gops = (num_ops / cost.mean) / float(10 ** 9)
+            print(header)
+            print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
+        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+            run_test("NORMAL", print_ir)
+        print("")
+
+    def load_inp_unittest(print_ir):
+        mock = vta.mock
+        print("----- LoadInp Unit Test-------")
+        def run_test(header, print_ir):
+            cost = run_schedule(
+                vta.DMA_COPY, mock.DMA_COPY, mock.GEMM, mock.ALU, mock.DMA_COPY, print_ir, False)
+            gops = (num_ops / cost.mean) / float(10 ** 9)
+            bandwith = (batch_size * channel * vta.VTA_INP_WIDTH / cost.mean) / float(10 ** 9)
+            print(header)
+            print("\tTime cost = %g sec/op, %g GFLOPS, bandwidth=%g Gbits" % (
+                cost.mean, gops, bandwith))
+        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+            run_test("NORMAL", print_ir)
+        print("")
+
+    def load_wgt_unittest(print_ir):
+        mock = vta.mock
+        print("----- LoadWgt Unit Test-------")
+        def run_test(header, print_ir):
+            cost = run_schedule(
+                mock.DMA_COPY, vta.DMA_COPY, mock.GEMM, mock.ALU, mock.DMA_COPY, print_ir, False)
+            gops = (num_ops / cost.mean) / float(10 ** 9)
+            bandwith = (channel * channel * vta.VTA_WGT_WIDTH / cost.mean) / float(10 ** 9)
+            print(header)
+            print("\tTime cost = %g sec/op, %g GFLOPS, bandwidth=%g Gbits" % (
+                cost.mean, gops, bandwith))
+        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+            run_test("NORMAL", print_ir)
+        print("")
+
+    def store_out_unittest(print_ir):
+        mock = vta.mock
+        print("----- StoreOut Unit Test-------")
+        def run_test(header, print_ir):
+            cost = run_schedule(
+                mock.DMA_COPY, mock.DMA_COPY, mock.GEMM, mock.ALU, vta.DMA_COPY,
+                print_ir, False)
+            gops = (num_ops / cost.mean) / float(10 ** 9)
+            bandwith = (batch_size * channel * vta.VTA_OUT_WIDTH / cost.mean) / float(10 ** 9)
+            print(header)
+            print("\tTime cost = %g sec/op, %g GFLOPS, bandwidth=%g Gbits" % (
+                cost.mean, gops, bandwith))
+        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+            run_test("NORMAL", print_ir)
+        print("")
+
+    gemm_normal(False)
+    gevm_unittest(False)
+    alu_unittest(False)
+    # FIXME: report time that is too short
+    # load_inp_unittest(False)
+    # load_wgt_unittest(False)
+    # store_out_unittest(False)
+
+
+print("========GEMM 128=========")
+test_gemm_packed(128, 128, 128)
+
+# FIXME: hanging run
+# print("========GEMM 1024========")
+# test_gemm_packed(1024, 1024, 128)
diff --git a/vta/tests/python/pynq/test_benchmark_topi.py b/vta/tests/python/pynq/test_benchmark_topi.py
new file mode 100644
index 000000000000..f98e70f01b3c
--- /dev/null
+++ b/vta/tests/python/pynq/test_benchmark_topi.py
@@ -0,0 +1,144 @@
+"""Testing if we can generate code in topi style"""
+
+import topi
+import tvm
+from tvm.contrib import util, rpc
+import vta
+from vta import vta_conv2d
+import numpy as np
+import mxnet as mx
+
+Workload = vta_conv2d.Workload
+
+@tvm.tag_scope(tag=topi.tag.ELEMWISE)
+def my_clip(x, a_min, a_max):
+    """Unlike topi's current clip, put min and max into two stages."""
+    const_min = tvm.const(a_min, x.dtype)
+    const_max = tvm.const(a_max, x.dtype)
+    x = tvm.compute(x.shape, lambda *i: tvm.min(x(*i), const_max), name="clipA")
+    x = tvm.compute(x.shape, lambda *i: tvm.max(x(*i), const_min), name="clipB")
+    return x
+
+host = "pynq"
+port = 9091
+out_dtype = "int%d" % vta.VTA_OUT_WIDTH
+wgt_dtype = "int%d" % vta.VTA_WGT_WIDTH
+inp_dtype = "int%d" % vta.VTA_INP_WIDTH
+target = "llvm -target=armv7-none-linux-gnueabihf -mattr=+neon"
+print_ir = False
+
+def test_vta_conv2d(key, batch_size, wl, profile=True):
+    data_shape = (batch_size, wl.in_filter//vta.VTA_BLOCK_IN,
+                  wl.height, wl.width, vta.VTA_BLOCK_IN)
+    kernel_shape = (wl.out_filter//vta.VTA_BLOCK_OUT, wl.in_filter//vta.VTA_BLOCK_IN,
+                    wl.hkernel, wl.wkernel, vta.VTA_BLOCK_OUT, vta.VTA_BLOCK_IN)
+    bias_shape = (wl.out_filter//vta.VTA_BLOCK_OUT, 1, 1, vta.VTA_BLOCK_OUT)
+
+
+    fout_height = (wl.height + 2 * wl.hpad - wl.hkernel) // wl.hstride + 1
+    fout_width = (wl.width + 2 * wl.wpad - wl.wkernel) // wl.wstride + 1
+    data = tvm.placeholder(data_shape, name="data", dtype=inp_dtype)
+    kernel = tvm.placeholder(kernel_shape, name="kernel", dtype=wgt_dtype)
+    bias = tvm.placeholder(bias_shape, name="kernel", dtype=out_dtype)
+
+    res_conv = vta_conv2d.packed_conv2d(
+        data, kernel, padding=(wl.hpad, wl.wpad), strides=(wl.hstride, wl.wstride))
+    res = topi.right_shift(res_conv, 8)
+    res = topi.broadcast_add(res, bias)
+    res = my_clip(res, 0, 127)
+    res = topi.cast(res, "int8")
+
+    num_ops = fout_height * fout_width * wl.hkernel * wl.wkernel * wl.out_filter * wl.in_filter
+
+    def verify(s, check_correctness):
+        mod = tvm.build(s, [data, kernel, bias, res], "ext_dev", target, name="conv2d")
+        temp = util.tempdir()
+        remote = rpc.connect(host, port)
+        mod.save(temp.relpath("conv2d.o"))
+        remote.upload(temp.relpath("conv2d.o"))
+        f = remote.load_module("conv2d.o")
+        # verify
+        ctx = remote.ext_dev(0)
+        # Data in original format
+        data_orig = (np.random.uniform(
+            size=(batch_size, wl.in_filter, wl.height, wl.width)) * 4).astype(data.dtype)
+        kernel_orig = (np.random.uniform(
+            size=(wl.out_filter, wl.in_filter, wl.hkernel, wl.wkernel)) * 4).astype(kernel.dtype)
+        bias_orig = (np.random.uniform(size=(wl.out_filter,)) * 4).astype("int32")
+
+        data_orig = np.abs(data_orig)
+        kernel_orig = np.abs(kernel_orig)
+        bias_orig = np.abs(bias_orig)
+
+        data_packed = data_orig.reshape(
+            batch_size, wl.in_filter//vta.VTA_BLOCK_IN, vta.VTA_BLOCK_IN,
+            wl.height, wl.width).transpose((0, 1, 3, 4, 2))
+        kernel_packed = kernel_orig.reshape(
+            wl.out_filter//vta.VTA_BLOCK_OUT, vta.VTA_BLOCK_OUT,
+            wl.in_filter//vta.VTA_BLOCK_IN, vta.VTA_BLOCK_IN,
+            wl.hkernel, wl.wkernel).transpose((0, 2, 4, 5, 1, 3))
+        bias_packed = bias_orig.reshape(
+            wl.out_filter//vta.VTA_BLOCK_OUT, 1, 1, vta.VTA_BLOCK_OUT)
+        res_shape = topi.util.get_const_tuple(res.shape)
+
+        res_np = np.zeros(res_shape).astype(res.dtype)
+        data_arr = tvm.nd.array(data_packed, ctx)
+        kernel_arr = tvm.nd.array(kernel_packed, ctx)
+        bias_arr = tvm.nd.array(bias_packed, ctx)
+        res_arr = tvm.nd.array(res_np, ctx)
+        time_f = f.time_evaluator("conv2d", ctx, number=10)
+        cost = time_f(data_arr, kernel_arr, bias_arr, res_arr)
+        res_unpack = res_arr.asnumpy().transpose(
+            (0, 1, 4, 2, 3)).reshape(batch_size, wl.out_filter, fout_height, fout_width)
+        if check_correctness:
+            res_ref = mx.nd.Convolution(
+                mx.nd.array(data_orig.astype(out_dtype), mx.cpu(0)),
+                mx.nd.array(kernel_orig.astype(out_dtype), mx.cpu(0)),
+                stride=(wl.hstride, wl.wstride),
+                kernel=(wl.hkernel, wl.wkernel),
+                num_filter=wl.out_filter,
+                no_bias=True,
+                pad=(wl.hpad, wl.wpad)).asnumpy().astype(out_dtype)
+            res_ref = res_ref >> 8
+            res_ref += bias_orig.reshape(wl.out_filter, 1, 1)
+            res_ref = np.clip(res_ref, 0, 127).astype("int8")
+            np.testing.assert_allclose(res_unpack, res_ref)
+            print("Correctness check pass...")
+        return cost
+
+    def conv_normal(print_ir):
+        print("----- CONV2D End-to-End Test-------")
+        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+            s = vta_conv2d.schedule_packed_conv2d([res])
+            if print_ir:
+                print(tvm.lower(s, [data, kernel, bias, res], simple_mode=True))
+            cost = verify(s, True)
+        gops = (num_ops / cost.mean) / float(10 ** 9)
+        print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
+
+    conv_normal(print_ir)
+
+# ResNet18 workloads
+resnet = {
+    # Workloads of resnet18 on imagenet
+    0: Workload(224, 224, 16, 64, 7, 7, 3, 3, 2, 2),
+    1: Workload(56, 56, 64, 64, 3, 3, 1, 1, 1, 1),
+    2: Workload(56, 56, 64, 64, 1, 1, 0, 0, 1, 1),
+    3: Workload(56, 56, 64, 128, 3, 3, 1, 1, 2, 2),
+    4: Workload(56, 56, 64, 128, 1, 1, 0, 0, 2, 2),
+    5: Workload(28, 28, 128, 128, 3, 3, 1, 1, 1, 1),
+    6: Workload(28, 28, 128, 256, 3, 3, 1, 1, 2, 2),
+    7: Workload(28, 28, 128, 256, 1, 1, 0, 0, 2, 2),
+    8: Workload(14, 14, 256, 256, 3, 3, 1, 1, 1, 1),
+    9: Workload(14, 14, 256, 512, 3, 3, 1, 1, 2, 2),
+    10: Workload(14, 14, 256, 512, 1, 1, 0, 0, 2, 2),
+    11: Workload(7, 7, 512, 512, 3, 3, 1, 1, 1, 1),
+}
+
+batch_size = 1
+for i in range(0, len(resnet)):
+    wl = resnet[i]
+    key = "resnet-cfg[%d]" % i
+    print "key=%s" % key
+    print wl
+    test_vta_conv2d(key, batch_size, wl)
diff --git a/vta/tests/python/pynq/test_program_rpc.py b/vta/tests/python/pynq/test_program_rpc.py
new file mode 100644
index 000000000000..cc3b7928179a
--- /dev/null
+++ b/vta/tests/python/pynq/test_program_rpc.py
@@ -0,0 +1,21 @@
+import tvm
+import vta
+import os
+from tvm.contrib import rpc, util
+
+host = "pynq"
+port = 9091
+target = "llvm -target=armv7-none-linux-gnueabihf"
+bit = "vta.bit"
+
+curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
+bitstream = os.path.join(curr_path, "./", bit)
+
+def test_program_rpc():
+    assert tvm.module.enabled("rpc")
+    remote = rpc.connect(host, port)
+    remote.upload(bitstream, bit)
+    fprogram = remote.get_function("tvm.contrib.vta.init")
+    fprogram(bit)
+
+test_program_rpc()
diff --git a/vta/tests/python/pynq/test_vta_insn.py b/vta/tests/python/pynq/test_vta_insn.py
new file mode 100644
index 000000000000..b1d17b6d8519
--- /dev/null
+++ b/vta/tests/python/pynq/test_vta_insn.py
@@ -0,0 +1,498 @@
+"""Unit test TPU's instructions """
+import tvm
+import vta
+import mxnet as mx
+import numpy as np
+import topi
+from tvm.contrib import rpc, util
+
+host = "pynq"
+port = 9091
+target = "llvm -target=armv7-none-linux-gnueabihf"
+out_dtype = "int%d" % vta.VTA_OUT_WIDTH
+inp_dtype = "int%d" % vta.VTA_INP_WIDTH
+wgt_dtype = "int%d" % vta.VTA_WGT_WIDTH
+do_verify = True
+print_ir = False
+
+
+def test_save_load_out():
+    """Test save/store output command"""
+    n = 4
+    x = tvm.placeholder(
+        (n, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        name="x",
+        dtype=out_dtype)
+    x_buf = tvm.compute(
+        (n, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        lambda *i: x(*i),
+        "x_buf")
+    # insert no-op that won't be optimized away
+    y_buf = tvm.compute(
+        (n, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        lambda *i: x_buf(*i)>>0,
+        "y_buf")
+    y = tvm.compute(
+        (n, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        lambda *i: y_buf(*i).astype(inp_dtype),
+        "y")
+    # schedule
+    s = tvm.create_schedule(y.op)
+    s[x_buf].set_scope(vta.SCOPE_OUT)
+    s[x_buf].pragma(x_buf.op.axis[0], vta.DMA_COPY)
+    s[y_buf].set_scope(vta.SCOPE_OUT)
+    s[y_buf].pragma(y_buf.op.axis[0], vta.ALU)
+    s[y].pragma(y.op.axis[0], vta.DMA_COPY)
+
+    def verify():
+        # build
+        m = tvm.build(s, [x, y], "ext_dev", target)
+        temp = util.tempdir()
+        remote = rpc.connect(host, port)
+        m.save(temp.relpath("load_act.o"))
+        remote.upload(temp.relpath("load_act.o"))
+        f = remote.load_module("load_act.o")
+        # verify
+        ctx = remote.ext_dev(0)
+        x_np = np.random.randint(
+            1, 10, size=(n, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT)).astype(x.dtype)
+        y_np = x_np.astype(y.dtype)
+        x_nd = tvm.nd.array(x_np, ctx)
+        y_nd = tvm.nd.empty(y_np.shape, ctx=ctx, dtype=y_np.dtype)
+        f(x_nd, y_nd)
+        np.testing.assert_equal(y_np, y_nd.asnumpy())
+        print("\tFinished verification...")
+    if do_verify:
+        verify()
+
+def test_padded_load():
+    """Test padded load."""
+    # declare
+    n = 21
+    m = 20
+    pad_before = [0, 1, 0, 0]
+    pad_after = [1, 3, 0, 0]
+    x = tvm.placeholder(
+        (n, m, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        name="x",
+        dtype=out_dtype)
+    x_buf = topi.nn.pad(x, pad_before, pad_after, name="y")
+    # insert no-op that won't be optimized away
+    y_buf = tvm.compute((n + pad_before[0] + pad_after[0],
+                         m + pad_before[1] + pad_after[1],
+                         vta.VTA_BATCH,
+                         vta.VTA_BLOCK_OUT), lambda *i: x_buf(*i)>>0, "y_buf")
+    y = tvm.compute((n + pad_before[0] + pad_after[0],
+                     m + pad_before[1] + pad_after[1],
+                     vta.VTA_BATCH,
+                     vta.VTA_BLOCK_OUT), lambda *i: y_buf(*i).astype(inp_dtype), "y")
+    # schedule
+    s = tvm.create_schedule(y.op)
+    s[x_buf].set_scope(vta.SCOPE_OUT)
+    s[x_buf].pragma(x_buf.op.axis[0], vta.DMA_COPY)
+    s[y_buf].set_scope(vta.SCOPE_OUT)
+    s[y_buf].pragma(y_buf.op.axis[0], vta.ALU)
+    s[y].pragma(y.op.axis[0], vta.DMA_COPY)
+
+    def verify():
+        # build
+        mod = tvm.build(s, [x, y], "ext_dev", target)
+        temp = util.tempdir()
+        remote = rpc.connect(host, port)
+        mod.save(temp.relpath("padded_load.o"))
+        remote.upload(temp.relpath("padded_load.o"))
+        f = remote.load_module("padded_load.o")
+        # verify
+        ctx = remote.ext_dev(0)
+        x_np = np.random.randint(1, 2, size=(
+            n, m, vta.VTA_BATCH, vta.VTA_BLOCK_OUT)).astype(x.dtype)
+        y_np = np.zeros((n + pad_before[0] + pad_after[0],
+                         m + pad_before[1] + pad_after[1],
+                         vta.VTA_BATCH,
+                         vta.VTA_BLOCK_OUT)).astype(y.dtype)
+        y_np[pad_before[0]:pad_before[0] + n,
+             pad_before[1]:pad_before[1] + m,
+             :] = x_np
+        x_nd = tvm.nd.array(x_np, ctx)
+        y_nd = tvm.nd.empty(y_np.shape, ctx=ctx, dtype=y_np.dtype)
+        f(x_nd, y_nd)
+        np.testing.assert_equal(y_np, y_nd.asnumpy())
+        print("\tFinished verification...")
+    if print_ir:
+        print(tvm.lower(s, [y, x], simple_mode=True))
+    if do_verify:
+        with tvm.build_config(add_lower_pass=vta.debug_mode(
+                vta.DEBUG_DUMP_INSN)):
+            verify()
+
+def test_gemm():
+    """Test GEMM."""
+    # declare
+    o = 4
+    n = 4
+    m = 4
+    x = tvm.placeholder((o, n, vta.VTA_BATCH, vta.VTA_BLOCK_IN), name="x", dtype=inp_dtype)
+    w = tvm.placeholder((m, n, vta.VTA_BLOCK_OUT, vta.VTA_BLOCK_IN), name="w", dtype=wgt_dtype)
+    x_buf = tvm.compute((o, n, vta.VTA_BATCH, vta.VTA_BLOCK_IN), lambda *i: x(*i), "x_buf")
+    w_buf = tvm.compute((m, n, vta.VTA_BLOCK_OUT, vta.VTA_BLOCK_IN), lambda *i: w(*i), "w_buf")
+    ko = tvm.reduce_axis((0, n), name="ko")
+    ki = tvm.reduce_axis((0, vta.VTA_BLOCK_IN), name="ki")
+    y_gem = tvm.compute(
+        (o, m, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        lambda bo, co, bi, ci:
+            tvm.sum(x_buf[bo, ko, bi, ki].astype(out_dtype) *
+                    w_buf[co, ko, ci, ki].astype(out_dtype),
+                    axis=[ko, ki]),
+        name="y_gem")
+    y_shf = tvm.compute(
+        (o, m, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        lambda *i: y_gem(*i)>>8,
+        name="y_shf")
+    y_max = tvm.compute(
+        (o, m, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        lambda *i: tvm.max(y_shf(*i), 0),
+        "y_max") #relu
+    y_min = tvm.compute(
+        (o, m, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        lambda *i: tvm.min(y_max(*i), (1<<(vta.VTA_INP_WIDTH-1))-1),
+        "y_min") #relu
+    y = tvm.compute(
+        (o, m, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        lambda *i: y_min(*i).astype(inp_dtype),
+        name="y")
+
+    def verify(s):
+        mod = tvm.build(s, [x, w, y], "ext_dev", target)
+        temp = util.tempdir()
+        remote = rpc.connect(host, port)
+        mod.save(temp.relpath("gemm.o"))
+        remote.upload(temp.relpath("gemm.o"))
+        f = remote.load_module("gemm.o")
+        # verify
+        ctx = remote.ext_dev(0)
+        x_np = np.random.randint(
+            -128, 128, size=(o, n, vta.VTA_BATCH, vta.VTA_BLOCK_IN)).astype(x.dtype)
+        w_np = np.random.randint(
+            -128, 128, size=(m, n, vta.VTA_BLOCK_OUT, vta.VTA_BLOCK_IN)).astype(w.dtype)
+        y_np = np.zeros((o, m, vta.VTA_BATCH, vta.VTA_BLOCK_OUT)).astype(y.dtype)
+        x_nd = tvm.nd.array(x_np, ctx)
+        w_nd = tvm.nd.array(w_np, ctx)
+        y_nd = tvm.nd.array(y_np, ctx)
+        y_np = y_np.astype(out_dtype)
+        for b in range(o):
+            for i in range(m):
+                for j in range(n):
+                    y_np[b,i,:] += np.dot(x_np[b,j,:].astype(out_dtype),
+                                          w_np[i,j].T.astype(out_dtype))
+        y_np = np.right_shift(y_np, 8)
+        y_np = np.clip(y_np, 0, (1<<(vta.VTA_INP_WIDTH-1))-1).astype(y.dtype)
+        f(x_nd, w_nd, y_nd)
+        np.testing.assert_equal(y_np, y_nd.asnumpy())
+        print("\tFinished verification...")
+
+    def test_schedule1():
+        # default schedule with no smt
+        s = tvm.create_schedule(y.op)
+        # set the scope of the SRAM buffers
+        s[x_buf].set_scope(vta.SCOPE_INP)
+        s[w_buf].set_scope(vta.SCOPE_WGT)
+        s[y_gem].set_scope(vta.SCOPE_OUT)
+        s[y_shf].set_scope(vta.SCOPE_OUT)
+        s[y_max].set_scope(vta.SCOPE_OUT)
+        s[y_min].set_scope(vta.SCOPE_OUT)
+        # set pragmas for DMA transfer and ALU ops
+        s[x_buf].pragma(s[x_buf].op.axis[0], vta.DMA_COPY)
+        s[w_buf].pragma(s[w_buf].op.axis[0], vta.DMA_COPY)
+        s[y_shf].pragma(s[y_shf].op.axis[0], vta.ALU)
+        s[y_max].pragma(s[y_max].op.axis[0], vta.ALU)
+        s[y_min].pragma(s[y_min].op.axis[0], vta.ALU)
+        s[y].pragma(s[y].op.axis[0], vta.DMA_COPY)
+        # tensorization
+        s[y_gem].reorder(
+            ko,
+            s[y_gem].op.axis[0],
+            s[y_gem].op.axis[1],
+            s[y_gem].op.axis[2],
+            s[y_gem].op.axis[3],
+            ki)
+        s[y_gem].tensorize(s[y_gem].op.axis[2], vta.GEMM)
+        if print_ir:
+            print(tvm.lower(s, [x, w, y], simple_mode=True))
+        if do_verify:
+            with tvm.build_config(
+                    add_lower_pass=vta.debug_mode(vta.DEBUG_DUMP_INSN)):
+                verify(s)
+
+    def test_smt():
+        # test smt schedule
+        s = tvm.create_schedule(y.op)
+        s[x_buf].set_scope(vta.SCOPE_INP)
+        s[w_buf].set_scope(vta.SCOPE_WGT)
+        s[y_gem].set_scope(vta.SCOPE_OUT)
+        s[y_shf].set_scope(vta.SCOPE_OUT)
+        s[y_max].set_scope(vta.SCOPE_OUT)
+        s[y_min].set_scope(vta.SCOPE_OUT)
+        abo, aco, abi, aci = s[y].op.axis
+        abo1, abo2 = s[y].split(abo, nparts=2)
+        s[y].bind(abo1, tvm.thread_axis("cthread"))
+        s[y_gem].compute_at(s[y], abo1)
+        s[y_shf].compute_at(s[y], abo1)
+        s[y_max].compute_at(s[y], abo1)
+        s[y_min].compute_at(s[y], abo1)
+        s[y_gem].reorder(
+            ko,
+            s[y_gem].op.axis[0],
+            s[y_gem].op.axis[1],
+            s[y_gem].op.axis[2],
+            s[y_gem].op.axis[3],
+            ki)
+        s[y_gem].tensorize(s[y_gem].op.axis[2], vta.GEMM)
+        s[y_shf].pragma(s[y_shf].op.axis[0], vta.ALU)
+        s[y_max].pragma(s[y_max].op.axis[0], vta.ALU)
+        s[y_min].pragma(s[y_min].op.axis[0], vta.ALU)
+        s[x_buf].compute_at(s[y_gem], ko)
+        s[x_buf].pragma(s[x_buf].op.axis[0], vta.DMA_COPY)
+        s[w_buf].compute_at(s[y_gem], ko)
+        s[w_buf].pragma(s[w_buf].op.axis[0], vta.DMA_COPY)
+        s[y].pragma(abo2, vta.DMA_COPY)
+        if print_ir:
+            print(tvm.lower(s, [x, y, w], simple_mode=True))
+        if do_verify:
+            with tvm.build_config(
+                    add_lower_pass=vta.debug_mode(vta.DEBUG_DUMP_INSN)):
+                verify(s)
+
+    test_schedule1()
+    test_smt()
+
+def test_alu(tvm_op, np_op=None, use_imm=False):
+    """Test ALU"""
+    m = 8
+    n = 8
+    imm = np.random.randint(1,5)
+    # compute
+    a = tvm.placeholder(
+        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        name="a",
+        dtype=out_dtype)
+    a_buf = tvm.compute(
+        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        lambda *i: a(*i),
+        "a_buf") #DRAM->SRAM
+    if use_imm:
+        res_buf = tvm.compute(
+            (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+            lambda *i: tvm_op(a_buf(*i), imm),
+            "res_buf") #compute
+    else:
+        b = tvm.placeholder(
+            (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+            name="b",
+            dtype=out_dtype)
+        b_buf = tvm.compute(
+            (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+            lambda *i: b(*i),
+            "b_buf") #DRAM->SRAM
+        res_buf = tvm.compute(
+            (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+            lambda *i: tvm_op(a_buf(*i), b_buf(*i)),
+            "res_buf") #compute
+    res = tvm.compute(
+        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        lambda *i: res_buf(*i).astype(inp_dtype),
+        "res") #SRAM->DRAM
+    # schedule
+    s = tvm.create_schedule(res.op)
+    s[a_buf].set_scope(vta.SCOPE_OUT) # SRAM
+    s[a_buf].pragma(a_buf.op.axis[0], vta.DMA_COPY) # DRAM->SRAM
+    s[res_buf].set_scope(vta.SCOPE_OUT) # SRAM
+    s[res_buf].pragma(res_buf.op.axis[0], vta.ALU) # compute
+    s[res].pragma(res.op.axis[0], vta.DMA_COPY) # SRAM->DRAM
+    if use_imm:
+        if print_ir:
+            print(tvm.lower(s, [a, res], simple_mode=True))
+    else:
+        s[b_buf].set_scope(vta.SCOPE_OUT) # SRAM
+        s[b_buf].pragma(b_buf.op.axis[0], vta.DMA_COPY) # DRAM->SRAM
+        if print_ir:
+            print(tvm.lower(s, [a, b, res], simple_mode=True))
+
+    def verify():
+        # build
+        if use_imm:
+            mod = tvm.build(s, [a, res], "ext_dev", target)
+        else:
+            mod = tvm.build(s, [a, b, res], "ext_dev", target)
+        temp = util.tempdir()
+        remote = rpc.connect(host, port)
+        mod.save(temp.relpath("load_act.o"))
+        remote.upload(temp.relpath("load_act.o"))
+        f = remote.load_module("load_act.o")
+        # verify
+        ctx = remote.ext_dev(0)
+        a_np = np.random.randint(
+            -16, 16, size=(m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT)).astype(a.dtype)
+        if use_imm:
+            res_np = np_op(a_np, imm) if np_op else tvm_op(a_np, imm)
+        else:
+            b_np = np.random.randint(
+                -16, 16, size=(m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT)).astype(b.dtype)
+            res_np = np_op(a_np, b_np) if np_op else tvm_op(a_np, b_np)
+        res_np = res_np.astype(res.dtype)
+        a_nd = tvm.nd.array(a_np, ctx)
+        res_nd = tvm.nd.array(
+            np.zeros((m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT)).astype(res.dtype), ctx)
+        if use_imm:
+            f(a_nd, res_nd)
+        else:
+            b_nd = tvm.nd.array(b_np, ctx)
+            f(a_nd, b_nd, res_nd)
+        np.testing.assert_equal(res_np, res_nd.asnumpy())
+        print("\tFinished verification...")
+
+    if do_verify:
+        verify()
+
+def test_relu():
+    """Test RELU on ALU"""
+    m = 8
+    n = 8
+    # compute
+    a = tvm.placeholder(
+        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        name="a",
+        dtype=out_dtype)
+    a_buf = tvm.compute(
+        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        lambda *i: a(*i),
+        "a_buf") # DRAM->SRAM
+    max_buf = tvm.compute(
+        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        lambda *i: tvm.max(a_buf(*i), 0),
+        "res_buf") # relu
+    min_buf = tvm.compute(
+        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        lambda *i: tvm.min(max_buf(*i), (1<<(vta.VTA_INP_WIDTH-1))-1),
+        "max_buf") # relu
+    res = tvm.compute(
+        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        lambda *i: min_buf(*i).astype(inp_dtype),
+        "min_buf") # SRAM->DRAM
+    # schedule
+    s = tvm.create_schedule(res.op)
+    s[a_buf].set_scope(vta.SCOPE_OUT) # SRAM
+    s[a_buf].pragma(a_buf.op.axis[0], vta.DMA_COPY) # DRAM->SRAM
+    s[max_buf].set_scope(vta.SCOPE_OUT) # SRAM
+    s[min_buf].set_scope(vta.SCOPE_OUT) # SRAM
+    s[max_buf].pragma(max_buf.op.axis[0], vta.ALU) # compute
+    s[min_buf].pragma(min_buf.op.axis[0], vta.ALU) # compute
+    s[res].pragma(res.op.axis[0], vta.DMA_COPY) # SRAM->DRAM
+    if print_ir:
+        print(tvm.lower(s, [a, res], simple_mode=True))
+
+    def verify():
+        # build
+        mod = tvm.build(s, [a, res], "ext_dev", target)
+        temp = util.tempdir()
+        remote = rpc.connect(host, port)
+        mod.save(temp.relpath("load_act.o"))
+        remote.upload(temp.relpath("load_act.o"))
+        f = remote.load_module("load_act.o")
+        # verify
+        ctx = remote.ext_dev(0)
+        a_np = np.random.randint(
+            -256, 256, size=(m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT)).astype(a.dtype)
+        res_np = np.clip(a_np, 0, (1<<(vta.VTA_INP_WIDTH-1))-1).astype(res.dtype)
+        a_nd = tvm.nd.array(a_np, ctx)
+        res_nd = tvm.nd.array(
+            np.zeros((m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT)).astype(res.dtype), ctx)
+        f(a_nd, res_nd)
+        np.testing.assert_equal(res_np, res_nd.asnumpy())
+        print("\tFinished verification...")
+
+    if do_verify:
+        verify()
+
+def test_shift_and_scale():
+    """Test shift and scale on ALU"""
+    m = 8
+    n = 8
+    imm_shift = np.random.randint(-10,10)
+    imm_scale = np.random.randint(1,5)
+    # compute
+    a = tvm.placeholder(
+        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        name="a", dtype=out_dtype)
+    a_buf = tvm.compute(
+        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        lambda *i: a(*i),
+        "a_buf") # DRAM->SRAM
+    res_shift = tvm.compute(
+        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        lambda *i: a_buf(*i)+imm_shift,
+        "res_shift") # compute
+    res_scale = tvm.compute(
+        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        lambda *i: res_shift(*i)>>imm_scale,
+        "res_scale") # compute
+    res = tvm.compute(
+        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        lambda *i: res_scale(*i).astype(inp_dtype),
+        "res") # SRAM->DRAM
+    # schedule
+    s = tvm.create_schedule(res.op)
+    s[a_buf].set_scope(vta.SCOPE_OUT) # SRAM
+    s[res_shift].set_scope(vta.SCOPE_OUT) # SRAM
+    s[res_scale].set_scope(vta.SCOPE_OUT) # SRAM
+    s[a_buf].pragma(a_buf.op.axis[0], vta.DMA_COPY) # DRAM->SRAM
+    s[res_shift].pragma(res_shift.op.axis[0], vta.ALU) # compute
+    s[res_scale].pragma(res_scale.op.axis[0], vta.ALU) # compute
+    s[res].pragma(res.op.axis[0], vta.DMA_COPY) # SRAM->DRAM
+    if print_ir:
+        print(tvm.lower(s, [a, res], simple_mode=True))
+
+    def verify():
+        # build
+        mod = tvm.build(s, [a, res], "ext_dev", target)
+        temp = util.tempdir()
+        remote = rpc.connect(host, port)
+        mod.save(temp.relpath("load_act.o"))
+        remote.upload(temp.relpath("load_act.o"))
+        f = remote.load_module("load_act.o")
+        # verify
+        ctx = remote.ext_dev(0)
+        a_np = np.random.randint(
+            -10, 10, size=(m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT)).astype(a.dtype)
+        res_np = np.right_shift((a_np + imm_shift), imm_scale)
+        res_np = res_np.astype(res.dtype)
+        a_nd = tvm.nd.array(a_np, ctx)
+        res_nd = tvm.nd.array(
+            np.zeros((m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT)).astype(res.dtype), ctx)
+        f(a_nd, res_nd)
+        np.testing.assert_equal(res_np, res_nd.asnumpy())
+        print("\tFinished verification...")
+
+    if do_verify:
+        verify()
+
+if __name__ == "__main__":
+    print("Padded load test")
+    test_padded_load()
+    print("Load/store test")
+    test_save_load_out()
+    print("GEMM test")
+    test_gemm()
+    print("Max immediate")
+    test_alu(tvm.max, np.maximum, use_imm=True)
+    print("Max")
+    test_alu(tvm.max, np.maximum)
+    print("Add immediate")
+    test_alu(lambda x, y: x + y, use_imm=True)
+    print("Add")
+    test_alu(lambda x, y: x + y)
+    print("Shift right immediate")
+    test_alu(lambda x, y: x >> y, np.right_shift, use_imm=True)
+    print("Relu")
+    test_relu()
+    # print("Shift and scale")
+    # test_shift_and_scale()

From bfc2367381d0e770fa27d73b805cce620e8884fb Mon Sep 17 00:00:00 2001
From: Grigori Fursin <gfursin@users.noreply.github.com>
Date: Sun, 8 Apr 2018 02:26:31 +0200
Subject: [PATCH 826/949] fixing URL; adding () to print (#17)

---
 vta/examples/resnet18/pynq/README.md           | 4 ++--
 vta/examples/resnet18/pynq/imagenet_predict.py | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/vta/examples/resnet18/pynq/README.md b/vta/examples/resnet18/pynq/README.md
index 5d35fcbdda17..51a2bc4bed3e 100644
--- a/vta/examples/resnet18/pynq/README.md
+++ b/vta/examples/resnet18/pynq/README.md
@@ -77,8 +77,8 @@ export PYTHONPATH=<nnvm root>/python:${PYTHONPATH}
 ```
 
 ## Pynq RPC Server Setup
-
-Follow the [Pynq RPC Server Guide](https://github.com/saml/vta/tree/master/apps/pynq_rpc/README.md)
+                                                       
+Follow the [Pynq RPC Server Guide](https://github.com/uwsaml/vta/tree/master/apps/pynq_rpc/README.md)
 
 ## Running the example
 
diff --git a/vta/examples/resnet18/pynq/imagenet_predict.py b/vta/examples/resnet18/pynq/imagenet_predict.py
index 8221281bda15..4ea95125333e 100644
--- a/vta/examples/resnet18/pynq/imagenet_predict.py
+++ b/vta/examples/resnet18/pynq/imagenet_predict.py
@@ -28,7 +28,7 @@
 BITSTREAM_FILE = 'vta.bit'
 for file in [TEST_FILE, CATEG_FILE, RESNET_GRAPH_FILE, RESNET_PARAMS_FILE, BITSTREAM_FILE]:
     if not os.path.isfile(file):
-        print "Downloading {}".format(file)
+        print ("Downloading {}".format(file))
         wget.download(url+file) 
 
 # Program the FPGA remotely

From c1aa268298d48d76dcb02ce456b46c5b65d0ff53 Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Sat, 7 Apr 2018 21:57:56 -0700
Subject: [PATCH 827/949] Upgrade TVM to latest version

---
 vta/Makefile                                   | 2 +-
 vta/apps/pynq_rpc/start_rpc_server.sh          | 2 +-
 vta/examples/resnet18/pynq/imagenet_predict.py | 3 +--
 vta/python/vta/build.py                        | 2 +-
 4 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/vta/Makefile b/vta/Makefile
index 0137929c9e73..cdea90e6f36c 100644
--- a/vta/Makefile
+++ b/vta/Makefile
@@ -58,7 +58,7 @@ VTA_LIB_SRC = $(wildcard src/*.cc src/tvm/*.cc)
 ifeq ($(TARGET), VTA_PYNQ_TARGET)
 	VTA_LIB_SRC += $(wildcard src/pynq/*.cc)
 	LDFLAGS += -L/usr/lib -lsds_lib
-	LDFLAGS += -L/opt/python3.6/lib/python3.6/site-packages/pynq/lib/ -l:libdma.so
+	LDFLAGS += -L/opt/python3.6/lib/python3.6/site-packages/pynq/drivers/ -l:libdma.so
 endif
 VTA_LIB_OBJ = $(patsubst %.cc, build/%.o, $(VTA_LIB_SRC))
 
diff --git a/vta/apps/pynq_rpc/start_rpc_server.sh b/vta/apps/pynq_rpc/start_rpc_server.sh
index 950da23ceeb9..d5a1202a1dfc 100755
--- a/vta/apps/pynq_rpc/start_rpc_server.sh
+++ b/vta/apps/pynq_rpc/start_rpc_server.sh
@@ -1,4 +1,4 @@
 #!/bin/bash
 export PYTHONPATH=${PYTHONPATH}:/home/xilinx/tvm/python
-export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/opt/python3.6/lib/python3.6/site-packages/pynq/lib/
+export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/opt/python3.6/lib/python3.6/site-packages/pynq/drivers/
 python -m  tvm.exec.rpc_server --load-library /home/xilinx/vta/lib/libvta.so
diff --git a/vta/examples/resnet18/pynq/imagenet_predict.py b/vta/examples/resnet18/pynq/imagenet_predict.py
index 4ea95125333e..b9e6ab56a7f7 100644
--- a/vta/examples/resnet18/pynq/imagenet_predict.py
+++ b/vta/examples/resnet18/pynq/imagenet_predict.py
@@ -29,7 +29,7 @@
 for file in [TEST_FILE, CATEG_FILE, RESNET_GRAPH_FILE, RESNET_PARAMS_FILE, BITSTREAM_FILE]:
     if not os.path.isfile(file):
         print ("Downloading {}".format(file))
-        wget.download(url+file) 
+        wget.download(url+file)
 
 # Program the FPGA remotely
 assert tvm.module.enabled("rpc")
@@ -129,7 +129,6 @@ def mark_nop(graph, conv_layer=-1, skip_conv_layer=()):
             sym, target, shape_dict, dtype_dict,
             params=params)
 
-remote = rpc.connect(host, port)
 temp = util.tempdir()
 lib.save(temp.relpath("graphlib.o"))
 remote.upload(temp.relpath("graphlib.o"))
diff --git a/vta/python/vta/build.py b/vta/python/vta/build.py
index 8f422e1fafd5..06d30229faef 100644
--- a/vta/python/vta/build.py
+++ b/vta/python/vta/build.py
@@ -52,4 +52,4 @@ def add_debug(stmt):
 
 
 # Add a lower pass to sync uop
-build_module.BuildConfig.current.add_lower_pass = debug_mode(0)
+build_module.current_build_config().add_lower_pass = debug_mode(0)

From b8ea5a0168f06da9a67b1c32085215c5e9b47d82 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 8 Apr 2018 21:43:54 -0700
Subject: [PATCH 828/949] [RPC][RUNTIME] Support dynamic reload of runtime API
 according to config (#19)

---
 vta/Makefile                                  |  33 ++--
 vta/apps/pynq_rpc/start_rpc_server.sh         |   4 +-
 .../resnet18/pynq/imagenet_predict.py         |   4 +-
 vta/include/vta/runtime.h                     |  66 ++++----
 vta/make/config.mk                            |   1 +
 vta/python/vta/__init__.py                    |  22 ++-
 vta/python/vta/exec/__init__.py               |   1 +
 vta/python/vta/exec/rpc_server.py             | 104 +++++++++++++
 vta/python/vta/hw_spec.py                     |  22 ++-
 vta/python/vta/rpc_client.py                  |  45 ++++++
 vta/python/vta/runtime.py                     |   1 -
 vta/src/data_buffer.cc                        |  44 ++++++
 vta/src/data_buffer.h                         |  90 +++++++++++
 vta/src/runtime.cc                            | 144 +++---------------
 vta/src/tvm/vta_device_api.cc                 |  51 ++++---
 vta/tests/python/pynq/test_benchmark_topi.py  |   2 +
 vta/tests/python/pynq/test_program_rpc.py     |  10 +-
 17 files changed, 433 insertions(+), 211 deletions(-)
 create mode 100644 vta/python/vta/exec/__init__.py
 create mode 100644 vta/python/vta/exec/rpc_server.py
 create mode 100644 vta/python/vta/rpc_client.py
 create mode 100644 vta/src/data_buffer.cc
 create mode 100644 vta/src/data_buffer.h

diff --git a/vta/Makefile b/vta/Makefile
index cdea90e6f36c..069f6e01c3ea 100644
--- a/vta/Makefile
+++ b/vta/Makefile
@@ -40,36 +40,31 @@ ifneq ($(ADD_LDFLAGS), NONE)
 	LDFLAGS += $(ADD_LDFLAGS)
 endif
 
-ifeq ($(UNAME_S), Darwin)
-	SHARED_LIBRARY_SUFFIX := dylib
-	WHOLE_ARCH= -all_load
-	NO_WHOLE_ARCH= -noall_load
-	LDFLAGS += -undefined dynamic_lookup
-else
-	SHARED_LIBRARY_SUFFIX := so
-	WHOLE_ARCH= --whole-archive
-	NO_WHOLE_ARCH= --no-whole-archive
-endif
 
-
-all: lib/libvta.$(SHARED_LIBRARY_SUFFIX)
+all: lib/libvta.so lib/libvta_runtime.so
 
 VTA_LIB_SRC = $(wildcard src/*.cc src/tvm/*.cc)
+
 ifeq ($(TARGET), VTA_PYNQ_TARGET)
 	VTA_LIB_SRC += $(wildcard src/pynq/*.cc)
 	LDFLAGS += -L/usr/lib -lsds_lib
-	LDFLAGS += -L/opt/python3.6/lib/python3.6/site-packages/pynq/drivers/ -l:libdma.so
+	LDFLAGS += -L/opt/python3.6/lib/python3.6/site-packages/pynq/drivers/
+	LDFLAGS += -L/opt/python3.6/lib/python3.6/site-packages/pynq/lib/
+	LDFLAGS += -l:libdma.so
 endif
-VTA_LIB_OBJ = $(patsubst %.cc, build/%.o, $(VTA_LIB_SRC))
 
-test: $(TEST)
+VTA_LIB_OBJ = $(patsubst src/%.cc, build/%.o, $(VTA_LIB_SRC))
 
-build/src/%.o: src/%.cc
+build/%.o: src/%.cc
 	@mkdir -p $(@D)
-	$(CXX) $(CFLAGS) -MM -MT build/src/$*.o $< >build/src/$*.d
+	$(CXX) $(CFLAGS) -MM -MT build/src/$*.o $< >build/$*.d
 	$(CXX) -c $(CFLAGS) -c $< -o $@
 
-lib/libvta.$(SHARED_LIBRARY_SUFFIX): $(VTA_LIB_OBJ)
+lib/libvta.so: $(filter-out build/runtime.o, $(VTA_LIB_OBJ))
+	@mkdir -p $(@D)
+	$(CXX) $(CFLAGS) -shared -o $@ $(filter %.o, $^) $(LDFLAGS)
+
+lib/libvta_runtime.so: build/runtime.o
 	@mkdir -p $(@D)
 	$(CXX) $(CFLAGS) -shared -o $@ $(filter %.o, $^) $(LDFLAGS)
 
@@ -79,7 +74,7 @@ cpplint:
 	python nnvm/dmlc-core/scripts/lint.py vta cpp include src hardware tests
 
 pylint:
-	pylint python/tvm_vta --rcfile=$(ROOTDIR)/tests/lint/pylintrc
+	pylint python/vta --rcfile=$(ROOTDIR)/tests/lint/pylintrc
 
 doc:
 	doxygen docs/Doxyfile
diff --git a/vta/apps/pynq_rpc/start_rpc_server.sh b/vta/apps/pynq_rpc/start_rpc_server.sh
index d5a1202a1dfc..445b72ea797d 100755
--- a/vta/apps/pynq_rpc/start_rpc_server.sh
+++ b/vta/apps/pynq_rpc/start_rpc_server.sh
@@ -1,4 +1,4 @@
 #!/bin/bash
-export PYTHONPATH=${PYTHONPATH}:/home/xilinx/tvm/python
+export PYTHONPATH=${PYTHONPATH}:/home/xilinx/tvm/python:/home/xilinx/vta/python
 export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/opt/python3.6/lib/python3.6/site-packages/pynq/drivers/
-python -m  tvm.exec.rpc_server --load-library /home/xilinx/vta/lib/libvta.so
+python -m vta.exec.rpc_server
diff --git a/vta/examples/resnet18/pynq/imagenet_predict.py b/vta/examples/resnet18/pynq/imagenet_predict.py
index b9e6ab56a7f7..74b34fecfc0b 100644
--- a/vta/examples/resnet18/pynq/imagenet_predict.py
+++ b/vta/examples/resnet18/pynq/imagenet_predict.py
@@ -34,9 +34,7 @@
 # Program the FPGA remotely
 assert tvm.module.enabled("rpc")
 remote = rpc.connect(host, port)
-remote.upload(BITSTREAM_FILE, BITSTREAM_FILE)
-fprogram = remote.get_function("tvm.contrib.vta.init")
-fprogram(BITSTREAM_FILE)
+vta.program_fpga(remote, BITSTREAM_FILE)
 
 if verbose:
     logging.basicConfig(level=logging.INFO)
diff --git a/vta/include/vta/runtime.h b/vta/include/vta/runtime.h
index e1aae32f43a9..c9373846d903 100644
--- a/vta/include/vta/runtime.h
+++ b/vta/include/vta/runtime.h
@@ -23,40 +23,20 @@ extern "C" {
 #define VTA_DEBUG_SKIP_WRITE_BARRIER (1 << 4)
 #define VTA_DEBUG_FORCE_SERIAL (1 << 5)
 
-/*! \brief VTA command handle */
-typedef void * VTACommandHandle;
-
-/*! \brief Shutdown hook of VTA to cleanup resources */
-void VTARuntimeShutdown();
-
-/*!
- * \brief Get thread local command handle.
- * \return A thread local command handle.
- */
-VTACommandHandle VTATLSCommandHandle();
-
 /*!
  * \brief Allocate data buffer.
  * \param cmd The VTA command handle.
  * \param size Buffer size.
  * \return A pointer to the allocated buffer.
  */
-void* VTABufferAlloc(VTACommandHandle cmd, size_t size);
+void* VTABufferAlloc(size_t size);
 
 /*!
  * \brief Free data buffer.
  * \param cmd The VTA command handle.
  * \param buffer The data buffer to be freed.
  */
-void VTABufferFree(VTACommandHandle cmd, void* buffer);
-
-/*!
- * \brief Get the buffer access pointer on CPU.
- * \param cmd The VTA command handle.
- * \param buffer The data buffer.
- * \return The pointer that can be accessed by the CPU.
- */
-void* VTABufferCPUPtr(VTACommandHandle cmd, void* buffer);
+void VTABufferFree(void* buffer);
 
 /*!
  * \brief Copy data buffer from one location to another.
@@ -68,20 +48,32 @@ void* VTABufferCPUPtr(VTACommandHandle cmd, void* buffer);
  * \param size Size of copy.
  * \param kind_mask The memory copy kind.
  */
-void VTABufferCopy(VTACommandHandle cmd,
-                   const void* from,
+void VTABufferCopy(const void* from,
                    size_t from_offset,
                    void* to,
                    size_t to_offset,
                    size_t size,
                    int kind_mask);
 
+/*! \brief VTA command handle */
+typedef void* VTACommandHandle;
+
+/*! \brief Shutdown hook of VTA to cleanup resources */
+void VTARuntimeShutdown();
+
 /*!
- * \brief Set debug mode on the command handle.
+ * \brief Get thread local command handle.
+ * \return A thread local command handle.
+ */
+VTACommandHandle VTATLSCommandHandle();
+
+/*!
+ * \brief Get the buffer access pointer on CPU.
  * \param cmd The VTA command handle.
- * \param debug_flag The debug flag.
+ * \param buffer The data buffer.
+ * \return The pointer that can be accessed by the CPU.
  */
-void VTASetDebugMode(VTACommandHandle cmd, int debug_flag);
+void* VTABufferCPUPtr(VTACommandHandle cmd, void* buffer);
 
 /*!
  * \brief Perform a write barrier to make a memory region visible to the CPU.
@@ -92,9 +84,10 @@ void VTASetDebugMode(VTACommandHandle cmd, int debug_flag);
  * \param extent The end of the region (in elements).
  */
 void VTAWriteBarrier(VTACommandHandle cmd,
-                     void* buffer, uint32_t elem_bits,
-                     uint32_t start, uint32_t extent);
-
+                     void* buffer,
+                     uint32_t elem_bits,
+                     uint32_t start,
+                     uint32_t extent);
 /*!
  * \brief Perform a read barrier to a memory region visible to VTA.
  * \param cmd The VTA command handle.
@@ -104,8 +97,17 @@ void VTAWriteBarrier(VTACommandHandle cmd,
  * \param extent The end of the region (in elements).
  */
 void VTAReadBarrier(VTACommandHandle cmd,
-                    void* buffer, uint32_t elem_bits,
-                    uint32_t start, uint32_t extent);
+                    void* buffer,
+                    uint32_t elem_bits,
+                    uint32_t start,
+                    uint32_t extent);
+
+/*!
+ * \brief Set debug mode on the command handle.
+ * \param cmd The VTA command handle.
+ * \param debug_flag The debug flag.
+ */
+void VTASetDebugMode(VTACommandHandle cmd, int debug_flag);
 
 /*!
  * \brief Perform a 2D data load from DRAM.
diff --git a/vta/make/config.mk b/vta/make/config.mk
index 062dfa8c336c..9f611896a8ba 100644
--- a/vta/make/config.mk
+++ b/vta/make/config.mk
@@ -54,6 +54,7 @@ VTA_LOG_WGT_BUFF_SIZE = 15
 #  Log of acc buffer size in Bytes
 VTA_LOG_ACC_BUFF_SIZE = 17
 
+
 #---------------------
 # Derived VTA hardware parameters
 #--------------------
diff --git a/vta/python/vta/__init__.py b/vta/python/vta/__init__.py
index 4a6f760d0bfc..dc06b7ad0bfe 100644
--- a/vta/python/vta/__init__.py
+++ b/vta/python/vta/__init__.py
@@ -1,12 +1,20 @@
-"""TVM VTA runtime"""
+"""TVM-based VTA Compiler Toolchain"""
 from __future__ import absolute_import as _abs
 
 from .hw_spec import *
 
-from .runtime import SCOPE_INP, SCOPE_OUT, SCOPE_WGT, DMA_COPY, ALU
-from .intrin import GEVM, GEMM
-from .build import debug_mode
+try:
+    from .runtime import SCOPE_INP, SCOPE_OUT, SCOPE_WGT, DMA_COPY, ALU
+    from .intrin import GEVM, GEMM
+    from .build import debug_mode
+    from . import mock, ir_pass
+    from . import arm_conv2d, vta_conv2d
+except AttributeError:
+    pass
 
-from . import mock, ir_pass
-from . import arm_conv2d, vta_conv2d
-from . import graph
+from .rpc_client import reconfig_runtime, program_fpga
+
+try:
+    from . import graph
+except ImportError:
+    pass
diff --git a/vta/python/vta/exec/__init__.py b/vta/python/vta/exec/__init__.py
new file mode 100644
index 000000000000..2fa9de930455
--- /dev/null
+++ b/vta/python/vta/exec/__init__.py
@@ -0,0 +1 @@
+"""VTA Command line utils."""
diff --git a/vta/python/vta/exec/rpc_server.py b/vta/python/vta/exec/rpc_server.py
new file mode 100644
index 000000000000..8a27859f7ad4
--- /dev/null
+++ b/vta/python/vta/exec/rpc_server.py
@@ -0,0 +1,104 @@
+"""VTA customized TVM RPC Server
+
+Provides additional runtime function and library loading.
+"""
+from __future__ import absolute_import
+
+import logging
+import argparse
+import os
+import ctypes
+import tvm
+from tvm.contrib import rpc, util, cc
+
+
+@tvm.register_func("tvm.contrib.rpc.server.start", override=True)
+def server_start():
+    curr_path = os.path.dirname(
+        os.path.abspath(os.path.expanduser(__file__)))
+    dll_path = os.path.abspath(
+        os.path.join(curr_path, "../../../lib/libvta_runtime.so"))
+    runtime_dll = []
+    _load_module = tvm.get_global_func("tvm.contrib.rpc.server.load_module")
+
+    @tvm.register_func("tvm.contrib.rpc.server.load_module", override=True)
+    def load_module(file_name):
+        if not runtime_dll:
+            runtime_dll.append(ctypes.CDLL(dll_path, ctypes.RTLD_GLOBAL))
+        return _load_module(file_name)
+
+    @tvm.register_func("tvm.contrib.rpc.server.shutdown", override=True)
+    def server_shutdown():
+        if runtime_dll:
+            runtime_dll[0].VTARuntimeShutdown()
+            runtime_dll.pop()
+
+    @tvm.register_func("tvm.contrib.vta.reconfig_runtime", override=True)
+    def reconfig_runtime(cflags):
+        """Rebuild and reload runtime with new configuration.
+
+        Parameters
+        ----------
+        cfg_json : str
+        JSON string used for configurations.
+        """
+        if runtime_dll:
+            raise RuntimeError("Can only reconfig in the beginning of session...")
+        cflags = cflags.split()
+        cflags += ["-O2", "-std=c++11"]
+        lib_name = dll_path
+        curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
+        proj_root = os.path.abspath(os.path.join(curr_path, "../../../"))
+        runtime_source = os.path.join(proj_root, "src/runtime.cc")
+        cflags += ["-I%s/include" % proj_root]
+        cflags += ["-I%s/nnvm/tvm/include" % proj_root]
+        cflags += ["-I%s/nnvm/tvm/dlpack/include" % proj_root]
+        cflags += ["-I%s/nnvm/dmlc-core/include" % proj_root]
+        logging.info("Rebuild runtime dll with %s", str(cflags))
+        cc.create_shared(lib_name, [runtime_source], cflags)
+
+
+def main():
+    """Main funciton"""
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--host', type=str, default="0.0.0.0",
+                        help='the hostname of the server')
+    parser.add_argument('--port', type=int, default=9090,
+                        help='The port of the PRC')
+    parser.add_argument('--port-end', type=int, default=9199,
+                        help='The end search port of the PRC')
+    parser.add_argument('--key', type=str, default="",
+                        help="RPC key used to identify the connection type.")
+    parser.add_argument('--tracker', type=str, default="",
+                        help="Report to RPC tracker")
+    args = parser.parse_args()
+    logging.basicConfig(level=logging.INFO)
+    curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
+    proj_root = os.path.abspath(os.path.join(curr_path, "../../../"))
+    lib_path = os.path.abspath(os.path.join(proj_root, "lib/libvta.so"))
+
+    libs = []
+    for file_name in [lib_path]:
+        libs.append(ctypes.CDLL(file_name, ctypes.RTLD_GLOBAL))
+        logging.info("Load additional library %s", file_name)
+
+    if args.tracker:
+        url, port = args.tracker.split(":")
+        port = int(port)
+        tracker_addr = (url, port)
+        if not args.key:
+            raise RuntimeError(
+                "Need key to present type of resource when tracker is available")
+    else:
+        tracker_addr = None
+
+    server = rpc.Server(args.host,
+                        args.port,
+                        args.port_end,
+                        key=args.key,
+                        tracker_addr=tracker_addr)
+    server.libs += libs
+    server.proc.join()
+
+if __name__ == "__main__":
+    main()
diff --git a/vta/python/vta/hw_spec.py b/vta/python/vta/hw_spec.py
index b6b89df81594..ec7595f4c740 100644
--- a/vta/python/vta/hw_spec.py
+++ b/vta/python/vta/hw_spec.py
@@ -1,11 +1,31 @@
 """VTA configuration constants (should match hw_spec.h"""
 from __future__ import absolute_import as _abs
 
+#  Log of input/activation width in bits (default 3 -> 8 bits)
+VTA_LOG_INP_WIDTH = 3
+#  Log of kernel weight width in bits (default 3 -> 8 bits)
+VTA_LOG_WGT_WIDTH = 3
+#  Log of accum width in bits (default 5 -> 32 bits)
+VTA_LOG_ACC_WIDTH = 5
+#  Log of tensor batch size (A in (A,B)x(B,C) matrix multiplication)
+VTA_LOG_BATCH = 0
+#  Log of tensor inner block size (B in (A,B)x(B,C) matrix multiplication)
+VTA_LOG_BLOCK_IN = 4
+#  Log of tensor outer block size (C in (A,B)x(B,C) matrix multiplication)
+VTA_LOG_BLOCK_OUT = 4
+VTA_LOG_OUT_WIDTH = VTA_LOG_INP_WIDTH
+#  Log of uop buffer size in Bytes
+VTA_LOG_UOP_BUFF_SIZE = 15
+#  Log of acc buffer size in Bytes
+VTA_LOG_ACC_BUFF_SIZE = 17
+
 # The Constants
 VTA_WGT_WIDTH = 8
 VTA_INP_WIDTH = VTA_WGT_WIDTH
 VTA_OUT_WIDTH = 32
 
+VTA_TARGET = "VTA_PYNQ_TARGET"
+
 # Dimensions of the GEMM unit
 # (BATCH,BLOCK_IN) x (BLOCK_IN,BLOCK_OUT)
 VTA_BATCH = 1
@@ -67,4 +87,4 @@
 DEBUG_DUMP_INSN = (1 << 1)
 DEBUG_DUMP_UOP = (1 << 2)
 DEBUG_SKIP_READ_BARRIER = (1 << 3)
-DEBUG_SKIP_WRITE_BARRIER = (1 << 4)
\ No newline at end of file
+DEBUG_SKIP_WRITE_BARRIER = (1 << 4)
diff --git a/vta/python/vta/rpc_client.py b/vta/python/vta/rpc_client.py
new file mode 100644
index 000000000000..fb51113e1d62
--- /dev/null
+++ b/vta/python/vta/rpc_client.py
@@ -0,0 +1,45 @@
+"""VTA RPC client function"""
+import os
+from . import hw_spec as spec
+
+def reconfig_runtime(remote):
+    """Reconfigure remote runtime based on current hardware spec.
+
+    Parameters
+    ----------
+    remote : RPCSession
+        The TVM RPC session
+    """
+    keys = ["VTA_LOG_WGT_WIDTH",
+            "VTA_LOG_INP_WIDTH",
+            "VTA_LOG_ACC_WIDTH",
+            "VTA_LOG_OUT_WIDTH",
+            "VTA_LOG_BATCH",
+            "VTA_LOG_BLOCK_IN",
+            "VTA_LOG_BLOCK_OUT",
+            "VTA_LOG_UOP_BUFF_SIZE",
+            "VTA_LOG_INP_BUFF_SIZE",
+            "VTA_LOG_WGT_BUFF_SIZE",
+            "VTA_LOG_ACC_BUFF_SIZE",
+            "VTA_LOG_OUT_BUFF_SIZE"]
+    cflags = ["-D%s" % spec.VTA_TARGET]
+    for k in keys:
+        cflags += ["-D%s=%s" % (k, str(getattr(spec, k)))]
+    freconfig = remote.get_function("tvm.contrib.vta.reconfig_runtime")
+    freconfig(" ".join(cflags))
+
+
+def program_fpga(remote, bitstream):
+    """Upload and program bistream
+
+    Parameters
+    ----------
+    remote : RPCSession
+        The TVM RPC session
+
+    bitstream : str
+        Path to a local bistream file.
+    """
+    fprogram = remote.get_function("tvm.contrib.vta.init")
+    remote.upload(bitstream)
+    fprogram(os.path.basename(bitstream))
diff --git a/vta/python/vta/runtime.py b/vta/python/vta/runtime.py
index bfcd130ff481..dfbdfe670e8b 100644
--- a/vta/python/vta/runtime.py
+++ b/vta/python/vta/runtime.py
@@ -25,7 +25,6 @@ def get_task_qid(qid):
     """Get transformed queue index."""
     return 1 if DEBUG_NO_SYNC else qid
 
-
 @tvm.register_func("tvm.intrin.rule.default.vta.coproc_sync")
 def coproc_sync(op):
     return tvm.call_extern(
diff --git a/vta/src/data_buffer.cc b/vta/src/data_buffer.cc
new file mode 100644
index 000000000000..99f959ad8c8b
--- /dev/null
+++ b/vta/src/data_buffer.cc
@@ -0,0 +1,44 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file data_buffer.cc
+ * \brief Buffer related API for VTA.
+ * \note Buffer API remains stable across VTA designes.
+ */
+#include "./data_buffer.h"
+
+void* VTABufferAlloc(size_t size) {
+  return vta::DataBuffer::Alloc(size);
+}
+
+void VTABufferFree(void* buffer) {
+  vta::DataBuffer::Free(vta::DataBuffer::FromHandle(buffer));
+}
+
+void VTABufferCopy(const void* from,
+                   size_t from_offset,
+                   void* to,
+                   size_t to_offset,
+                   size_t size,
+                   int kind_mask) {
+  vta::DataBuffer* from_buffer = nullptr;
+  vta::DataBuffer* to_buffer = nullptr;
+
+  if (kind_mask & 2) {
+    from_buffer = vta::DataBuffer::FromHandle(from);
+    from = from_buffer->virt_addr();
+  }
+  if (kind_mask & 1) {
+    to_buffer = vta::DataBuffer::FromHandle(to);
+    to = to_buffer->virt_addr();
+  }
+  if (from_buffer) {
+    from_buffer->InvalidateCache(from_offset, size);
+  }
+
+  memcpy(static_cast<char*>(to) + to_offset,
+         static_cast<const char*>(from) + from_offset,
+         size);
+  if (to_buffer) {
+    to_buffer->FlushCache(to_offset, size);
+  }
+}
diff --git a/vta/src/data_buffer.h b/vta/src/data_buffer.h
new file mode 100644
index 000000000000..117a423d034e
--- /dev/null
+++ b/vta/src/data_buffer.h
@@ -0,0 +1,90 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file data_buffer.h
+ * \brief VTA runtime internal data buffer structure.
+ */
+#ifndef VTA_DATA_BUFFER_H_
+#define VTA_DATA_BUFFER_H_
+
+#include <vta/driver.h>
+#include <vta/runtime.h>
+#include <cassert>
+#include <cstring>
+
+namespace vta {
+
+/*! \brief Enable coherent access between VTA and CPU. */
+static const bool kBufferCoherent = true;
+
+/*!
+ * \brief Data buffer represents data on CMA.
+ */
+struct DataBuffer {
+  /*! \return Virtual address of the data. */
+  void* virt_addr() const {
+    return data_;
+  }
+  /*! \return Physical address of the data. */
+  uint32_t phy_addr() const {
+    return phy_addr_;
+  }
+  /*!
+   * \brief Invalidate the cache of given location in data buffer.
+   * \param offset The offset to the data.
+   * \param size The size of the data.
+   */
+  void InvalidateCache(size_t offset, size_t size) {
+    if (!kBufferCoherent) {
+      VTAInvalidateCache(reinterpret_cast<void*>(phy_addr_ + offset), size);
+    }
+  }
+  /*!
+   * \brief Invalidate the cache of certain location in data buffer.
+   * \param offset The offset to the data.
+   * \param size The size of the data.
+   */
+  void FlushCache(size_t offset, size_t size) {
+    if (!kBufferCoherent) {
+      VTAFlushCache(reinterpret_cast<void*>(phy_addr_ + offset), size);
+    }
+  }
+  /*!
+   * \brief Allocate a buffer of a given size.
+   * \param size The size of the buffer.
+   */
+  static DataBuffer* Alloc(size_t size) {
+    void* data = VTAMemAlloc(size, 1);
+    assert(data != nullptr);
+    DataBuffer* buffer = new DataBuffer();
+    buffer->data_ = data;
+    buffer->phy_addr_ = VTAGetMemPhysAddr(data);
+    return buffer;
+  }
+  /*!
+   * \brief Free the data buffer.
+   * \param buffer The buffer to be freed.
+   */
+  static void Free(DataBuffer* buffer) {
+    VTAMemFree(buffer->data_);
+    delete buffer;
+  }
+  /*!
+   * \brief Create data buffer header from buffer ptr.
+   * \param buffer The buffer pointer.
+   * \return The corresponding data buffer header.
+   */
+  static DataBuffer* FromHandle(const void* buffer) {
+    return const_cast<DataBuffer*>(
+        reinterpret_cast<const DataBuffer*>(buffer));
+  }
+
+ private:
+  /*! \brief The internal data. */
+  void* data_;
+  /*! \brief The physical address of the buffer, excluding header. */
+  uint32_t phy_addr_;
+};
+
+}  // namespace vta
+
+#endif  // VTA_DATA_BUFFER_H_
diff --git a/vta/src/runtime.cc b/vta/src/runtime.cc
index 7c5708b4ed43..8c7c9fdc9bee 100644
--- a/vta/src/runtime.cc
+++ b/vta/src/runtime.cc
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2018 by Contributors
- * \file vta_runtime.cc
+ * \file runtime.cc
  * \brief VTA runtime for PYNQ in C++11
  */
 
@@ -13,85 +13,14 @@
 #include <vta/runtime.h>
 
 #include <cassert>
-#include <cstring>
 #include <vector>
 #include <thread>
 #include <memory>
 #include <atomic>
 
-namespace vta {
-
-/*! \brief Enable coherent access between VTA and CPU. */
-static const bool kBufferCoherent = true;
+#include "./data_buffer.h"
 
-/*!
- * \brief Data buffer represents data on CMA.
- */
-struct DataBuffer {
-  /*! \return Virtual address of the data. */
-  void* virt_addr() const {
-    return data_;
-  }
-  /*! \return Physical address of the data. */
-  uint32_t phy_addr() const {
-    return phy_addr_;
-  }
-  /*!
-   * \brief Invalidate the cache of given location in data buffer.
-   * \param offset The offset to the data.
-   * \param size The size of the data.
-   */
-  void InvalidateCache(size_t offset, size_t size) {
-    if (!kBufferCoherent) {
-      VTAInvalidateCache(reinterpret_cast<void*>(phy_addr_ + offset), size);
-    }
-  }
-  /*!
-   * \brief Invalidate the cache of certain location in data buffer.
-   * \param offset The offset to the data.
-   * \param size The size of the data.
-   */
-  void FlushCache(size_t offset, size_t size) {
-    if (!kBufferCoherent) {
-      VTAFlushCache(reinterpret_cast<void*>(phy_addr_ + offset), size);
-    }
-  }
-  /*!
-   * \brief Allocate a buffer of a given size.
-   * \param size The size of the buffer.
-   */
-  static DataBuffer* Alloc(size_t size) {
-    void* data = VTAMemAlloc(size, 1);
-    assert(data != nullptr);
-    DataBuffer* buffer = new DataBuffer();
-    buffer->data_ = data;
-    buffer->phy_addr_ = VTAGetMemPhysAddr(data);
-    return buffer;
-  }
-  /*!
-   * \brief Free the data buffer.
-   * \param buffer The buffer to be freed.
-   */
-  static void Free(DataBuffer* buffer) {
-    VTAMemFree(buffer->data_);
-    delete buffer;
-  }
-  /*!
-   * \brief Create data buffer header from buffer ptr.
-   * \param buffer The buffer pointer.
-   * \return The corresponding data buffer header.
-   */
-  static DataBuffer* FromHandle(const void* buffer) {
-    return const_cast<DataBuffer*>(
-        reinterpret_cast<const DataBuffer*>(buffer));
-  }
-
- private:
-  /*! \brief The internal data. */
-  void* data_;
-  /*! \brief The physical address of the buffer, excluding header. */
-  uint32_t phy_addr_;
-};
+namespace vta {
 
 /*!
  * \brief Micro op kernel.
@@ -1130,6 +1059,9 @@ class CommandQueue {
   static std::shared_ptr<CommandQueue>& ThreadLocal() {
     static std::shared_ptr<CommandQueue> inst =
         std::make_shared<CommandQueue>();
+    if (inst == nullptr) {
+      inst = std::make_shared<CommandQueue>();
+    }
     return inst;
   }
 
@@ -1254,63 +1186,29 @@ void VTARuntimeShutdown() {
   vta::CommandQueue::Shutdown();
 }
 
-void* VTABufferAlloc(VTACommandHandle cmd, size_t size) {
-  return vta::DataBuffer::Alloc(size);
-}
-
-void VTABufferFree(VTACommandHandle cmd, void* buffer) {
-  vta::DataBuffer::Free(vta::DataBuffer::FromHandle(buffer));
+void VTASetDebugMode(VTACommandHandle cmd, int debug_flag) {
+  static_cast<vta::CommandQueue*>(cmd)->
+      SetDebugFlag(debug_flag);
 }
 
 void* VTABufferCPUPtr(VTACommandHandle cmd, void* buffer) {
   return vta::DataBuffer::FromHandle(buffer)->virt_addr();
 }
 
-void VTABufferCopy(VTACommandHandle cmd,
-                   const void* from,
-                   size_t from_offset,
-                   void* to,
-                   size_t to_offset,
-                   size_t size,
-                   int kind_mask) {
-  vta::DataBuffer* from_buffer = nullptr;
-  vta::DataBuffer* to_buffer = nullptr;
-
-  if (kind_mask & 2) {
-    from_buffer = vta::DataBuffer::FromHandle(from);
-    from = from_buffer->virt_addr();
-  }
-  if (kind_mask & 1) {
-    to_buffer = vta::DataBuffer::FromHandle(to);
-    to = to_buffer->virt_addr();
-  }
-  if (from_buffer) {
-    from_buffer->InvalidateCache(from_offset, size);
-  }
-
-  memcpy(static_cast<char*>(to) + to_offset,
-         static_cast<const char*>(from) + from_offset,
-         size);
-  if (to_buffer) {
-    to_buffer->FlushCache(to_offset, size);
-  }
-}
-
-void VTASetDebugMode(VTACommandHandle cmd, int debug_flag) {
-  static_cast<vta::CommandQueue*>(cmd)->
-      SetDebugFlag(debug_flag);
-}
-
 void VTAWriteBarrier(VTACommandHandle cmd,
-                     void* buffer, uint32_t elem_bits,
-                     uint32_t start, uint32_t extent) {
+                     void* buffer,
+                     uint32_t elem_bits,
+                     uint32_t start,
+                     uint32_t extent) {
   static_cast<vta::CommandQueue*>(cmd)->
       WriteBarrier(buffer, elem_bits, start, extent);
 }
 
 void VTAReadBarrier(VTACommandHandle cmd,
-                    void* buffer, uint32_t elem_bits,
-                    uint32_t start, uint32_t extent) {
+                    void* buffer,
+                    uint32_t elem_bits,
+                    uint32_t start,
+                    uint32_t extent) {
   static_cast<vta::CommandQueue*>(cmd)->
       ReadBarrier(buffer, elem_bits, start, extent);
 }
@@ -1409,3 +1307,11 @@ void VTASynchronize(VTACommandHandle cmd, uint32_t wait_cycles) {
   static_cast<vta::CommandQueue*>(cmd)->
       Synchronize(wait_cycles);
 }
+
+extern "C" int VTARuntimeDynamicMagic() {
+#ifdef VTA_DYNAMIC_MAGIC
+  return VTA_DYNAMIC_MAGIC;
+#else
+  return 0;
+#endif
+}
diff --git a/vta/src/tvm/vta_device_api.cc b/vta/src/tvm/vta_device_api.cc
index b7b57e199f3f..ce864df09402 100644
--- a/vta/src/tvm/vta_device_api.cc
+++ b/vta/src/tvm/vta_device_api.cc
@@ -7,31 +7,17 @@
 #include <tvm/runtime/registry.h>
 #include <dmlc/thread_local.h>
 #include <vta/runtime.h>
+#include <dlfcn.h>
 
 #include "../../nnvm/tvm/src/runtime/workspace_pool.h"
 
-namespace tvm {
-namespace runtime {
-
-std::string VTARPCGetPath(const std::string& name) {
-  static const PackedFunc* f =
-      runtime::Registry::Get("tvm.contrib.rpc.server.workpath");
-  CHECK(f != nullptr) << "require tvm.contrib.rpc.server.workpath";
-  return (*f)(name);
+extern "C" {
+  typedef void (*FShutdown)();
+  typedef int (*FDynamicMagic)();
 }
 
-// Global functions that can be called
-TVM_REGISTER_GLOBAL("tvm.contrib.vta.init")
-.set_body([](TVMArgs args, TVMRetValue* rv) {
-    std::string path = VTARPCGetPath(args[0]);
-    VTAProgram(path.c_str());
-    LOG(INFO) << "VTA initialization end with bistream " << path;
-  });
-
-TVM_REGISTER_GLOBAL("tvm.contrib.rpc.server.shutdown")
-.set_body([](TVMArgs args, TVMRetValue* rv) {
-    VTARuntimeShutdown();
-  });
+namespace tvm {
+namespace runtime {
 
 class VTADeviceAPI final : public DeviceAPI {
  public:
@@ -46,11 +32,11 @@ class VTADeviceAPI final : public DeviceAPI {
   void* AllocDataSpace(TVMContext ctx,
                        size_t size, size_t alignment,
                        TVMType type_hint) final {
-    return VTABufferAlloc(VTATLSCommandHandle(), size);
+    return VTABufferAlloc(size);
   }
 
   void FreeDataSpace(TVMContext ctx, void* ptr) final {
-    VTABufferFree(VTATLSCommandHandle(), ptr);
+    VTABufferFree(ptr);
   }
 
   void CopyDataFromTo(const void* from,
@@ -68,8 +54,7 @@ class VTADeviceAPI final : public DeviceAPI {
     if (ctx_to.device_type != kDLCPU) {
       kind_mask |= 1;
     }
-    VTABufferCopy(VTATLSCommandHandle(),
-                  from, from_offset,
+    VTABufferCopy(from, from_offset,
                   to, to_offset,
                   size, kind_mask);
   }
@@ -86,6 +71,9 @@ class VTADeviceAPI final : public DeviceAPI {
         std::make_shared<VTADeviceAPI>();
     return inst;
   }
+
+ private:
+  void* runtime_dll_{nullptr};
 };
 
 struct VTAWorkspacePool : public WorkspacePool {
@@ -103,6 +91,21 @@ void VTADeviceAPI::FreeWorkspace(TVMContext ctx, void* data) {
   dmlc::ThreadLocalStore<VTAWorkspacePool>::Get()->FreeWorkspace(ctx, data);
 }
 
+std::string VTARPCGetPath(const std::string& name) {
+  static const PackedFunc* f =
+      runtime::Registry::Get("tvm.contrib.rpc.server.workpath");
+  CHECK(f != nullptr) << "require tvm.contrib.rpc.server.workpath";
+  return (*f)(name);
+}
+
+// Global functions that can be called
+TVM_REGISTER_GLOBAL("tvm.contrib.vta.init")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    std::string path = VTARPCGetPath(args[0]);
+    VTAProgram(path.c_str());
+    LOG(INFO) << "VTA initialization end with bistream " << path;
+  });
+
 TVM_REGISTER_GLOBAL("device_api.ext_dev")
 .set_body([](TVMArgs args, TVMRetValue* rv) {
     DeviceAPI* ptr = VTADeviceAPI::Global().get();
diff --git a/vta/tests/python/pynq/test_benchmark_topi.py b/vta/tests/python/pynq/test_benchmark_topi.py
index f98e70f01b3c..e6dea3e29d42 100644
--- a/vta/tests/python/pynq/test_benchmark_topi.py
+++ b/vta/tests/python/pynq/test_benchmark_topi.py
@@ -27,6 +27,7 @@ def my_clip(x, a_min, a_max):
 target = "llvm -target=armv7-none-linux-gnueabihf -mattr=+neon"
 print_ir = False
 
+
 def test_vta_conv2d(key, batch_size, wl, profile=True):
     data_shape = (batch_size, wl.in_filter//vta.VTA_BLOCK_IN,
                   wl.height, wl.width, vta.VTA_BLOCK_IN)
@@ -54,6 +55,7 @@ def verify(s, check_correctness):
         mod = tvm.build(s, [data, kernel, bias, res], "ext_dev", target, name="conv2d")
         temp = util.tempdir()
         remote = rpc.connect(host, port)
+
         mod.save(temp.relpath("conv2d.o"))
         remote.upload(temp.relpath("conv2d.o"))
         f = remote.load_module("conv2d.o")
diff --git a/vta/tests/python/pynq/test_program_rpc.py b/vta/tests/python/pynq/test_program_rpc.py
index cc3b7928179a..eaf09577d6a7 100644
--- a/vta/tests/python/pynq/test_program_rpc.py
+++ b/vta/tests/python/pynq/test_program_rpc.py
@@ -14,8 +14,12 @@
 def test_program_rpc():
     assert tvm.module.enabled("rpc")
     remote = rpc.connect(host, port)
-    remote.upload(bitstream, bit)
-    fprogram = remote.get_function("tvm.contrib.vta.init")
-    fprogram(bit)
+    vta.program_fpga(remote, bit)
+
+def test_reconfig_runtime():
+    assert tvm.module.enabled("rpc")
+    remote = rpc.connect(host, port)
+    vta.reconfig_runtime(remote)
 
 test_program_rpc()
+test_reconfig_runtime()

From aa84689f2cfe73647d1ab654f7752065e464acc8 Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@cs.washington.edu>
Date: Mon, 9 Apr 2018 12:01:53 -0700
Subject: [PATCH 829/949] [SCHEDULER, HW] Auto scheduler for conv2d, hardware
 generation (#20)

* Hardware generation fixes/sweep, auto scheduling for VTA conv2d

* Hardware generation fixes/sweep, auto scheduling for VTA conv2d

* derive hw spec from config file

* up to date hardware spec
---
 vta/hardware/xilinx/.gitignore                |   3 +
 vta/hardware/xilinx/Makefile                  |  41 ++-
 .../xilinx/scripts/compile_designs.py         | 209 ++++++++++++
 vta/hardware/xilinx/scripts/hls.tcl           | 141 ++++----
 vta/hardware/xilinx/scripts/vivado.tcl        |  31 +-
 vta/hardware/xilinx/sim/vta_test.cc           |  44 ++-
 vta/hardware/xilinx/src/vta.cc                | 133 ++++----
 vta/hardware/xilinx/src/vta.h                 |   2 +-
 vta/include/vta/hw_spec.h                     | 173 +++++-----
 vta/python/vta/hw_spec.py                     |  78 +++--
 vta/python/vta/ir_pass.py                     |   8 +-
 vta/src/runtime.cc                            |  31 +-
 vta/tests/hardware/common/test_lib.cc         | 117 ++-----
 .../python/pynq/test_benchmark_conv2d.py      | 304 +++++++++++++-----
 vta/tests/python/pynq/test_program_rpc.py     |   8 +-
 vta/tests/python/pynq/test_vta_insn.py        |   6 +-
 16 files changed, 808 insertions(+), 521 deletions(-)
 create mode 100644 vta/hardware/xilinx/scripts/compile_designs.py

diff --git a/vta/hardware/xilinx/.gitignore b/vta/hardware/xilinx/.gitignore
index 378eac25d311..54ff70b80cd1 100644
--- a/vta/hardware/xilinx/.gitignore
+++ b/vta/hardware/xilinx/.gitignore
@@ -1 +1,4 @@
 build
+*.out
+*.log
+*.sb
diff --git a/vta/hardware/xilinx/Makefile b/vta/hardware/xilinx/Makefile
index c7ffc7ec9871..8208de7d010d 100644
--- a/vta/hardware/xilinx/Makefile
+++ b/vta/hardware/xilinx/Makefile
@@ -1,6 +1,7 @@
 # Directories
 ROOTDIR = $(CURDIR)
-BUILD_DIR = $(ROOTDIR)/../../build/hardware/xilinx
+BUILD_NAME = build
+BUILD_DIR = $(ROOTDIR)/../../$(BUILD_NAME)/hardware/xilinx
 SCRIPT_DIR = $(ROOTDIR)/scripts
 SRC_DIR = $(ROOTDIR)/src
 SIM_DIR = $(ROOTDIR)/sim
@@ -12,6 +13,15 @@ VIVADO_HLS = vivado_hls
 VIVADO = vivado
 HSI = hsi
 
+# HLS Mode
+MODE = all
+# SLURM
+SLURM = false
+# Prevent generation of DSP
+NO_DSP = false
+# Prevent generation of ALU
+NO_ALU = false
+
 # Include top-level config file
 ifndef config
 ifneq ("$(wildcard ../../config.mk)", "")
@@ -41,13 +51,18 @@ $(shell echo "$$(( (1000 + $(VTA_HW_COMP_CLOCK_FREQ) - 1) / $(VTA_HW_COMP_CLOCK_
 
 # Derive config name
 CONF = \
-$(VTA_BATCH)x$(VTA_IN_BLOCK)x$(VTA_OUT_BLOCK)_$(VTA_INP_WIDTH)bx$(VTA_WGT_WIDTH)b_$(VTA_HW_COMP_CLOCK_FREQ)MHz_$(TARGET_PER)ns
+$(VTA_BATCH)x$(VTA_IN_BLOCK)x$(VTA_OUT_BLOCK)_$(VTA_INP_WIDTH)bx$(VTA_WGT_WIDTH)b_$(VTA_LOG_UOP_BUFF_SIZE)_$(VTA_LOG_INP_BUFF_SIZE)_$(VTA_LOG_WGT_BUFF_SIZE)_$(VTA_LOG_ACC_BUFF_SIZE)_$(VTA_HW_COMP_CLOCK_FREQ)MHz_$(TARGET_PER)ns
 IP_BUILD_PATH = $(BUILD_DIR)/hls/$(CONF)
 HW_BUILD_PATH = $(BUILD_DIR)/vivado/$(CONF)
 
-.PHONY: all ip bit driver clean
+ifeq ($(SLURM), true)
+	IP_BUILD_PATH = /scratch/hls/$(CONF)
+	HW_BUILD_PATH = /scratch/vivado/$(CONF)
+endif
+
+.PHONY: all ip bit driver clean clean_all
 
-all: driver
+all: bit
 
 ip: 
 	mkdir -p $(IP_BUILD_PATH)
@@ -57,20 +72,32 @@ ip:
 			$(VTA_LOG_INP_WIDTH) $(VTA_LOG_WGT_WIDTH) $(VTA_LOG_ACC_WIDTH) $(VTA_LOG_OUT_WIDTH) \
 			$(VTA_LOG_BATCH) $(VTA_LOG_BLOCK_OUT) $(VTA_LOG_BLOCK_IN) \
 			$(VTA_LOG_UOP_BUFF_SIZE) $(VTA_LOG_INP_BUFF_SIZE) $(VTA_LOG_WGT_BUFF_SIZE) \
-			$(VTA_LOG_ACC_BUFF_SIZE) $(VTA_LOG_OUT_BUFF_SIZE)
+			$(VTA_LOG_ACC_BUFF_SIZE) $(VTA_LOG_OUT_BUFF_SIZE) \
+			$(MODE) $(NO_DSP) $(NO_ALU)
+ifeq ($(SLURM), true)
+	mkdir -p $(BUILD_DIR)/hls
+	mv $(IP_BUILD_PATH) $(BUILD_DIR)/hls/.
+endif
 
 bit: ip
 	mkdir -p $(HW_BUILD_PATH)
 	cd $(HW_BUILD_PATH) && \
 		$(VIVADO) -mode tcl -source $(SCRIPT_DIR)/vivado.tcl \
-		-tclargs $(IP_BUILD_PATH) $(VTA_HW_COMP_THREADS) $(VTA_HW_COMP_CLOCK_FREQ) \
+		-tclargs $(BUILD_DIR)/hls/$(CONF) $(VTA_HW_COMP_THREADS) $(VTA_HW_COMP_CLOCK_FREQ) \
 		$(VTA_INP_WIDTH) $(VTA_WGT_WIDTH) $(VTA_OUT_WIDTH) \
 		$(VTA_BATCH) $(VTA_IN_BLOCK) $(VTA_OUT_BLOCK) \
 		$(VTA_INP_BUFF_SIZE) $(VTA_WGT_BUFF_SIZE) $(VTA_OUT_BUFF_SIZE)
+ifeq ($(SLURM), true)
+	mkdir -p $(BUILD_DIR)/vivado
+	mv $(HW_BUILD_PATH) $(BUILD_DIR)/vivado/.
+endif
 
 driver: bit
 	cd $(HW_BUILD_PATH) && $(HSI) -mode tcl -source $(SCRIPT_DIR)/hsi.tcl -nojournal -nolog
 	cd $(HW_BUILD_PATH)/bsp && make
 
 clean:
-	rm -rf $(BUILD_DIR)
\ No newline at end of file
+	rm -rf *.out *.log *.sb figures
+
+clean_all: clean
+	rm -rf $(BUILD_DIR)
diff --git a/vta/hardware/xilinx/scripts/compile_designs.py b/vta/hardware/xilinx/scripts/compile_designs.py
new file mode 100644
index 000000000000..a064a7198014
--- /dev/null
+++ b/vta/hardware/xilinx/scripts/compile_designs.py
@@ -0,0 +1,209 @@
+#!/usr/bin/env python
+import argparse
+import datetime
+import logging
+import numpy as np
+import os
+import pandas as pd
+import re
+import time
+from collections import namedtuple
+from numpy import floor, ceil, log2, log10
+from subprocess import call
+
+FPGA = namedtuple("FPGAConstraints",
+                  ['bram_w', 'bram_d', 'num_bram'])
+
+Hardware = namedtuple("HWConfig",
+                      ['batch', 'block_in', 'block_out',
+                       'input_w', 'weight_w', 'accum_w', 'out_w', 'uop_w'])
+
+def find_bram_confs(fpga, hw_conf, log_uop_sizeB):
+  # Derive sizes
+  input_elem_size_b = hw_conf.batch*hw_conf.block_in*hw_conf.input_w
+  weight_elem_size_b = hw_conf.block_in*hw_conf.block_out*hw_conf.weight_w
+  accum_elem_size_b = hw_conf.batch*hw_conf.block_out*hw_conf.accum_w
+  input_min_bram = (input_elem_size_b+fpga.bram_w-1)/fpga.bram_w
+  weight_min_bram = (weight_elem_size_b+fpga.bram_w-1)/fpga.bram_w
+  accum_min_bram = (accum_elem_size_b+fpga.bram_w-1)/fpga.bram_w
+  # Exploring all possible BRAM distributions
+  bram_confs = []
+  uop_bram = pow(2, log_uop_sizeB) * 8 / (fpga.bram_w * fpga.bram_d)
+  for log_i_bram in range(int(log2(input_min_bram)), int(ceil(log2(fpga.num_bram)))):
+    i_bram = pow(2, log_i_bram)
+    for log_w_bram in range(int(log2(weight_min_bram)), int(ceil(log2(fpga.num_bram)))):
+      w_bram = pow(2, log_w_bram)
+      for log_a_bram in range(int(log2(accum_min_bram)), int(ceil(log2(fpga.num_bram)))):
+        a_bram = pow(2, log_a_bram)
+        total_bram = uop_bram + i_bram + w_bram + a_bram + a_bram / hw_conf.accum_w * hw_conf.out_w
+        if total_bram <= fpga.num_bram:
+          # Right now we need to restrict uop width
+          input_elems = i_bram * fpga.bram_w * fpga.bram_d / input_elem_size_b
+          weight_elems = w_bram * fpga.bram_w * fpga.bram_d / weight_elem_size_b
+          accum_elems = a_bram * fpga.bram_w * fpga.bram_d / accum_elem_size_b
+          if log2(input_elems) + log2(weight_elems) + log2(accum_elems) <= hw_conf.uop_w:
+            log_inp_sizeB = int(log2(i_bram*fpga.bram_d*fpga.bram_w/8))
+            log_wgt_sizeB = int(log2(w_bram*fpga.bram_d*fpga.bram_w/8))
+            log_acc_sizeB = int(log2(a_bram*fpga.bram_d*fpga.bram_w/8))
+            bram_confs.append([log_uop_sizeB, log_inp_sizeB, log_wgt_sizeB, log_acc_sizeB])
+  # Filter out configs that are suboptimal
+  suboptimal = [False] * len(bram_confs)
+  for i in range(0, len(bram_confs)):
+    for j in range(i + 1, len(bram_confs)):
+      leq_list = [a <= b for a, b in zip(bram_confs[i], bram_confs[j])]
+      geq_list = [a >= b for a, b in zip(bram_confs[i], bram_confs[j])]
+      leq = all(leq_list)
+      geq = all(geq_list)
+      if leq:
+        suboptimal[i] = True
+      if geq:
+        suboptimal[j] = True
+  opt_bram_confs = [x[0] for x in zip(bram_confs, suboptimal) if not x[1]]
+  return opt_bram_confs
+
+def get_make_command(job, build_dir, hw_conf, bram_conf, mode, slurm=False):
+  cmd = ""
+  if slurm:
+    cmd += "#!/bin/bash\n"
+    cmd += "#SBATCH --job-name={}\n".format(job)
+    cmd += "#SBATCH --output={}.out\n".format(job)
+    cmd += "srun "
+  if mode=="hls":
+    cmd += "make ip"
+  else:
+    cmd += "make"
+  cmd += " SLURM={} MODE=skip_sim NO_DSP=false NO_ALU=false".format("true" if slurm else "false")
+  cmd += " BUILD_NAME={}".format(build_dir)
+  cmd += " VTA_LOG_INP_WIDTH={}".format(int(log2(hw_conf.input_w)))
+  cmd += " VTA_LOG_WGT_WIDTH={}".format(int(log2(hw_conf.weight_w)))
+  cmd += " VTA_LOG_BATCH={}".format(int(log2(hw_conf.batch)))
+  cmd += " VTA_LOG_BLOCK_IN={}".format(int(log2(hw_conf.block_in)))
+  cmd += " VTA_LOG_BLOCK_OUT={}".format(int(log2(hw_conf.block_out)))
+  cmd += " VTA_LOG_UOP_BUFF_SIZE={}".format(bram_conf[0])
+  cmd += " VTA_LOG_INP_BUFF_SIZE={}".format(bram_conf[1])
+  cmd += " VTA_LOG_WGT_BUFF_SIZE={}".format(bram_conf[2])
+  cmd += " VTA_LOG_ACC_BUFF_SIZE={}\n".format(bram_conf[3])
+  return cmd
+
+def cli():
+  parser = argparse.ArgumentParser(
+      description='Analyze HLS experiments'
+  )
+  parser.add_argument(
+      '-mode', dest='mode', action='store', type=str, required=True,
+      choices=["hls", "vivado"], help='hls synthesis or full compilation'
+  )
+  parser.add_argument(
+      '-base_dir', dest='base_dir', action='store', type=str, required=False,
+      default="../../build/hardware/xilinx/", help='path to build directory'
+  )
+  parser.add_argument(
+      '-min_ibw', dest='min_ibw', action='store', type=int, required=False,
+      default=3, help='log2 of minimum input bit-width'
+  )
+  parser.add_argument(
+      '-max_ibw', dest='max_ibw', action='store', type=int, required=False,
+      default=3, help='log2 of maximum input bit-width'
+  )
+  parser.add_argument(
+      '-min_wbw', dest='min_wbw', action='store', type=int, required=False,
+      default=3, help='log2 of minimum weight bit-width'
+  )
+  parser.add_argument(
+      '-max_wbw', dest='max_wbw', action='store', type=int, required=False,
+      default=3, help='log2 of maximum weight bit-width'
+  )
+  parser.add_argument(
+      '-acc_bw', dest='acc_bw', action='store', type=int, required=False,
+      default=32, help='accumulator bit-width'
+  )
+  parser.add_argument(
+      '-uop_bw', dest='uop_bw', action='store', type=int, required=False,
+      default=32, help='micro-op bit-width'
+  )
+  parser.add_argument(
+      '-min_batch', dest='min_batch', action='store', type=int, required=False,
+      default=0, help='log2 of minimum batch size'
+  )
+  parser.add_argument(
+      '-max_batch', dest='max_batch', action='store', type=int, required=False,
+      default=8, help='log2 of maximum batch size'
+  )
+  parser.add_argument(
+      '-min_ic', dest='min_ic', action='store', type=int, required=False,
+      default=0, help='log2 of minimum input channels'
+  )
+  parser.add_argument(
+      '-max_ic', dest='max_ic', action='store', type=int, required=False,
+      default=8, help='log2 of maximum input channels'
+  )
+  parser.add_argument(
+      '-min_oc', dest='min_oc', action='store', type=int, required=False,
+      default=0, help='log2 of minimum output channels'
+  )
+  parser.add_argument(
+      '-max_oc', dest='max_oc', action='store', type=int, required=False,
+      default=8, help='log2 of maximum output channels'
+  )
+  parser.add_argument(
+      '-uop_sizeB', dest='uop_sizeB', action='store', type=int, required=False,
+      default=14, help='log2 of uop buffer in B'
+  )
+  parser.add_argument(
+      '-bram_w', dest='bram_w', action='store', type=int, required=False,
+      default=32, help='FPGA BRAM port width in b'
+  )
+  parser.add_argument(
+      '-bram_d', dest='bram_d', action='store', type=int, required=False,
+      default=1024, help='FPGA BRAM depth'
+  )
+  parser.add_argument(
+      '-num_bram', dest='num_bram', action='store', type=int, required=False,
+      default=124, help='FPGA total BRAM'
+  )
+  parser.add_argument(
+      '-slurm', dest='slurm', action='store_true',
+      help='Run on cluster using slurm'
+  )
+  args = parser.parse_args()
+
+  # Logging
+  logging.basicConfig(filename='compile_designs.log',level=logging.DEBUG)
+
+  # FPGA config
+  pynq = FPGA(args.bram_w, args.bram_d, args.num_bram)
+
+  # Get timestamp
+  timestamp = datetime.datetime.fromtimestamp(time.time()).strftime('%Y_%m_%d_%H_%M_%S')
+  build_dir = "build_{}".format(timestamp)
+
+  num_confs = 0
+  for log_ibw in range(args.min_ibw, args.max_ibw+1):
+    ibw = pow(2, log_ibw)
+    for log_wbw in range(args.min_wbw, args.max_wbw+1):
+      wbw = pow(2, log_wbw)
+      for log_batch in range(args.min_batch, args.max_batch+1):
+        batch = pow(2, log_batch)
+        for log_ic in range(args.min_ic, args.max_ic+1):
+          ic = pow(2, log_ic)
+          for log_oc in range(args.min_oc, args.max_oc+1):
+            oc = pow(2, log_oc)
+            conf = Hardware(batch, ic, oc, ibw, wbw, args.acc_bw, ibw, args.uop_bw)
+            bram_confs = find_bram_confs(pynq, conf, args.uop_sizeB)
+            for b in bram_confs:
+              job = "{}x{}x{}_{}bx{}b_{}_{}_{}_{}_100MHz_10ns".format(
+                batch, ic, oc, ibw, wbw, b[0], b[1], b[2], b[3])
+              num_confs += 1
+              cmd = get_make_command(job, build_dir, conf, b, args.mode, args.slurm)
+              sb_file = job+".sb"
+              file = open(sb_file,"w") 
+              file.write(cmd)
+              file.close() 
+              call(["echo", cmd])
+              if args.slurm:
+                call(["sbatch", sb_file])
+              else:
+                call(cmd.split(" "))
+
+if __name__ == '__main__':
+  cli()
diff --git a/vta/hardware/xilinx/scripts/hls.tcl b/vta/hardware/xilinx/scripts/hls.tcl
index 67ce742bf47a..a1270189db4c 100644
--- a/vta/hardware/xilinx/scripts/hls.tcl
+++ b/vta/hardware/xilinx/scripts/hls.tcl
@@ -22,8 +22,11 @@
 # Arg 15: wgt buffer size in B (log)
 # Arg 16: acc buffer size in B (log)
 # Arg 17: out buffer size in B (log)
+# Arg 18: mode
+# Arg 19: no_dsp
+# Arg 20: no_alu
 
-if { [llength $argv] eq 19 } {
+if { [llength $argv] eq 22 } {
 	set src_dir [lindex $argv 2]
 	set sim_dir [lindex $argv 3]
 	set test_dir [lindex $argv 4]
@@ -41,10 +44,13 @@ if { [llength $argv] eq 19 } {
 	set wgt_buff_size [lindex $argv 16]
 	set acc_buff_size [lindex $argv 17]
 	set out_buff_size [lindex $argv 18]
+	set mode [lindex $argv 19]
+	set no_dsp [lindex $argv 20]
+	set no_alu [lindex $argv 21]
 } else {
-	set src_dir "../src/"
-	set sim_dir "../sim/"
-	set test_dir "../../src/test/"
+	set src_dir "../src"
+	set sim_dir "../sim"
+	set test_dir "../../src/test"
 	set include_dir "../../include"
 	set target_period 10
 	set inp_width 3
@@ -59,17 +65,11 @@ if { [llength $argv] eq 19 } {
 	set wgt_buff_size 15
 	set acc_buff_size 17
 	set out_buff_size 15
+	set mode "all"
+	set no_dsp "true"
+	set no_alu "false"
 }
 
-# C define flags to pass to compiler
-set cflags "-I $include_dir -I $src_dir -I $test_dir \
-	-DVTA_DEBUG=0 -DVTA_LOG_WGT_WIDTH=$wgt_width -DVTA_LOG_INP_WIDTH=$inp_width \
-	-DVTA_LOG_ACC_WIDTH=$acc_width -DVTA_LOG_OUT_WIDTH=$out_width \
-	-DVTA_LOG_BATCH=$batch -DVTA_LOG_BLOCK_OUT=$block_out -DVTA_LOG_BLOCK_IN=$block_in \
-	-DVTA_LOG_UOP_BUFF_SIZE=$uop_buff_size -DVTA_LOG_INP_BUFF_SIZE=$inp_buff_size \
-	-DVTA_LOG_WGT_BUFF_SIZE=$wgt_buff_size -DVTA_LOG_ACC_BUFF_SIZE=$acc_buff_size \
-	-DVTA_LOG_OUT_BUFF_SIZE=$out_buff_size"
-
 # Initializes the HLS design and sets HLS pragmas for memory partitioning.
 # This is necessary because of a Vivado restriction that doesn't allow for
 # buses wider than 1024 bits.
@@ -122,56 +122,89 @@ proc init_design {per inp_width wgt_width out_width batch block_in block_out} {
 	}
 }
 
+# C define flags to pass to compiler
+set cflags "-I $include_dir -I $src_dir -I $test_dir \
+	-DVTA_DEBUG=0 -DVTA_LOG_WGT_WIDTH=$wgt_width -DVTA_LOG_INP_WIDTH=$inp_width \
+	-DVTA_LOG_ACC_WIDTH=$acc_width -DVTA_LOG_OUT_WIDTH=$out_width \
+	-DVTA_LOG_BATCH=$batch -DVTA_LOG_BLOCK_OUT=$block_out -DVTA_LOG_BLOCK_IN=$block_in \
+	-DVTA_LOG_UOP_BUFF_SIZE=$uop_buff_size -DVTA_LOG_INP_BUFF_SIZE=$inp_buff_size \
+	-DVTA_LOG_WGT_BUFF_SIZE=$wgt_buff_size -DVTA_LOG_ACC_BUFF_SIZE=$acc_buff_size \
+	-DVTA_LOG_OUT_BUFF_SIZE=$out_buff_size"
+if {$no_dsp=="true"} {
+	append cflags " -DNO_DSP"
+}
+if {$no_alu=="true"} {
+	append cflags " -DNO_ALU"
+}
+
 # HLS behavioral sim
-open_project vta_sim
-set_top vta
-add_files $src_dir/vta.cc -cflags $cflags
-add_files -tb $sim_dir/vta_test.cc -cflags $cflags
-add_files -tb $test_dir/test_lib.cc -cflags $cflags
-open_solution "solution0"
-init_design $target_period $inp_width $wgt_width $out_width $batch $block_in $block_out
-csim_design -clean
-close_project
+if {$mode=="all" || $mode=="sim"} {
+	open_project vta_sim
+	set_top vta
+	add_files $src_dir/vta.cc -cflags $cflags
+	add_files -tb $sim_dir/vta_test.cc -cflags $cflags
+	add_files -tb $test_dir/test_lib.cc -cflags $cflags
+	open_solution "solution0"
+	init_design $target_period $inp_width $wgt_width $out_width $batch $block_in $block_out
+	csim_design -clean
+	close_project
+}
 
 # Generate fetch stage
-open_project vta_fetch
-set_top fetch
-add_files $src_dir/vta.cc -cflags $cflags
-open_solution "solution0"
-init_design $target_period $inp_width $wgt_width $out_width $batch $block_in $block_out
-csynth_design
-export_design -format ip_catalog
-close_project
+if {$mode=="all" || $mode=="skip_sim" || $mode=="fetch"} {
+	open_project vta_fetch
+	set_top fetch
+	add_files $src_dir/vta.cc -cflags $cflags
+	open_solution "solution0"
+	init_design $target_period $inp_width $wgt_width $out_width $batch $block_in $block_out
+	csynth_design
+	if {$mode=="all" || $mode=="skip_sim"} {
+		export_design -format ip_catalog
+	}
+	close_project
+}
 
 # Generate load stage
-open_project vta_load
-set_top load
-add_files $src_dir/vta.cc -cflags $cflags
-open_solution "solution0"
-init_design $target_period $inp_width $wgt_width $out_width $batch $block_in $block_out
-csynth_design
-export_design -format ip_catalog
-close_project
+if {$mode=="all" || $mode=="skip_sim" || $mode=="load"} {
+	open_project vta_load
+	set_top load
+	add_files $src_dir/vta.cc -cflags $cflags
+	open_solution "solution0"
+	init_design $target_period $inp_width $wgt_width $out_width $batch $block_in $block_out
+	csynth_design
+	if {$mode=="all" || $mode=="skip_sim"} {
+		export_design -format ip_catalog
+	}
+	close_project
+}
 
 # Generate compute stage
-open_project vta_compute
-set_top compute
-add_files $src_dir/vta.cc -cflags $cflags
-open_solution "solution0"
-init_design $target_period $inp_width $wgt_width $out_width $batch $block_in $block_out
-csynth_design
-export_design -format ip_catalog
-close_project
+if {$mode=="all" || $mode=="skip_sim" || $mode=="compute"} {
+	open_project vta_compute
+	set_top compute
+	add_files $src_dir/vta.cc -cflags $cflags
+	open_solution "solution0"
+	init_design $target_period $inp_width $wgt_width $out_width $batch $block_in $block_out
+	csynth_design
+	if {$mode=="all" || $mode=="skip_sim"} {
+		export_design -format ip_catalog
+	}
+	close_project
+}
 
 # Generate store stage
-open_project vta_store
-set_top store
-add_files $src_dir/vta.cc -cflags $cflags
-open_solution "solution0"
-init_design $target_period $inp_width $wgt_width $out_width $batch $block_in $block_out
-csynth_design
-export_design -format ip_catalog
-close_project
+if {$mode=="all" || $mode=="skip_sim" || $mode=="store"} {
+	open_project vta_store
+	set_top store
+	add_files $src_dir/vta.cc -cflags $cflags
+	open_solution "solution0"
+	init_design $target_period $inp_width $wgt_width $out_width $batch $block_in $block_out
+	csynth_design
+	if {$mode=="all" || $mode=="skip_sim"} {
+		export_design -format ip_catalog
+	}
+	close_project
+}
 
 exit
 
diff --git a/vta/hardware/xilinx/scripts/vivado.tcl b/vta/hardware/xilinx/scripts/vivado.tcl
index c9b8f6c6317f..bdfd24c0ab09 100644
--- a/vta/hardware/xilinx/scripts/vivado.tcl
+++ b/vta/hardware/xilinx/scripts/vivado.tcl
@@ -59,44 +59,31 @@ if { [llength $argv] eq 12 } {
 
 # Derive input mem parameters
 set inp_mem_width [expr $inp_width * $batch * $in_block]
-set inp_mem_depth [expr $inp_mem_size * 8 / $inp_mem_width]
 set inp_bus_width 1024
 set inp_part [expr $inp_mem_width / $inp_bus_width]
 if {[expr $inp_part == 0]} {
   set inp_part 1
   set inp_bus_width $inp_mem_width
 }
+set inp_mem_depth [expr $inp_mem_size * 8 / ($inp_mem_width * $inp_part)]
 # Derive weight mem parameters
 set wgt_mem_width [expr $wgt_width * $out_block * $in_block]
-set wgt_mem_depth [expr $wgt_mem_size * 8 / $wgt_mem_width]
 set wgt_bus_width 1024
 set wgt_part [expr $wgt_mem_width / $wgt_bus_width]
 if {[expr $wgt_part == 0]} {
   set wgt_part 1
   set wgt_bus_width $wgt_mem_width
 }
+set wgt_mem_depth [expr $wgt_mem_size * 8 / ($wgt_mem_width * $wgt_part)]
 # Derive output mem parameters
 set out_mem_width [expr $out_width * $batch * $out_block]
-set out_mem_depth [expr $out_mem_size * 8 / $out_mem_width]
 set out_bus_width 1024
 set out_part [expr $out_mem_width / $out_bus_width]
 if {[expr $out_part == 0]} {
   set out_part 1
   set out_bus_width $out_mem_width
 }
-
-puts $inp_mem_width
-puts $inp_mem_depth
-puts $inp_bus_width
-puts $inp_part
-puts $wgt_mem_width
-puts $wgt_mem_depth
-puts $wgt_bus_width
-puts $wgt_part
-puts $out_mem_width
-puts $out_mem_depth
-puts $out_bus_width
-puts $out_part
+set out_mem_depth [expr $out_mem_size * 8 / ($out_mem_width * $out_part)]
 
 # User defined paths
 set proj_name vta
@@ -937,10 +924,12 @@ launch_runs impl_1 -to_step write_bitstream -jobs $num_threads
 wait_on_run impl_1
 
 # Export hardware description file and bitstream files to export/ dir
-file mkdir $proj_path/export
-file copy -force $proj_path/$proj_name.runs/impl_1/${proj_name}_wrapper.sysdef \
-  $proj_path/export/vta.hdf
-file copy -force $proj_path/$proj_name.runs/impl_1/${proj_name}_wrapper.bit \
-  $proj_path/export/vta.bit
+if {[file exist $proj_path/$proj_name.runs/impl_1/${proj_name}_wrapper.bit]} {
+  file mkdir $proj_path/export
+  file copy -force $proj_path/$proj_name.runs/impl_1/${proj_name}_wrapper.sysdef \
+    $proj_path/export/vta.hdf
+  file copy -force $proj_path/$proj_name.runs/impl_1/${proj_name}_wrapper.bit \
+    $proj_path/export/vta.bit
+}
 
 exit
diff --git a/vta/hardware/xilinx/sim/vta_test.cc b/vta/hardware/xilinx/sim/vta_test.cc
index 60685b384aca..79f4a1f676ff 100644
--- a/vta/hardware/xilinx/sim/vta_test.cc
+++ b/vta/hardware/xilinx/sim/vta_test.cc
@@ -16,40 +16,36 @@ int main(void) {
     printParameters();
 #endif
 
-    // Buffer indexing
-    assert(VTA_LOG_ACC_BUFF_DEPTH >= VTA_LOG_INP_BUFF_DEPTH);
     // Micro op bound
-    assert(VTA_UOP_GEM_3_1 < VTA_UOP_WIDTH);
-    assert(VTA_UOP_ALU_3_1 < VTA_UOP_WIDTH);
-    // Instruction alignment checks
+    assert(VTA_UOP_GEM_2_1 < VTA_UOP_WIDTH);
+    assert(VTA_UOP_ALU_1_1 < VTA_UOP_WIDTH);
+    // Make sure there is no misaligment
+    assert(VTA_INSN_GEM_9_1 < VTA_INSN_GEM_A_0);
     assert(VTA_INSN_MEM_7_1 < VTA_INSN_MEM_8_0);
-    assert(VTA_INSN_GEM_8_1 < VTA_INSN_GEM_9_0);
     // Instruction bounds
     assert(VTA_INSN_MEM_E_1 < VTA_INS_WIDTH);
-    assert(VTA_INSN_GEM_E_1 < VTA_INS_WIDTH);
-    assert(VTA_INSN_ALU_F_1 < VTA_INS_WIDTH);
+    assert(VTA_INSN_GEM_F_1 < VTA_INS_WIDTH);
+    assert(VTA_INSN_ALU_G_1 < VTA_INS_WIDTH);
 
     int status = 0;
 
     // Run ALU test (vector-scalar operators)
-    status |= alu_test(VTA_ALU_OPCODE_MIN, true, 16, 128, true);
-    status |= alu_test(VTA_ALU_OPCODE_MIN, true, 16, 128, false);
-    status |= alu_test(VTA_ALU_OPCODE_MAX, true, 16, 128, true);
-    status |= alu_test(VTA_ALU_OPCODE_MAX, true, 16, 128, false);
-    status |= alu_test(VTA_ALU_OPCODE_ADD, true, 16, 128, true);
-    status |= alu_test(VTA_ALU_OPCODE_ADD, true, 16, 128, false);
-    status |= alu_test(VTA_ALU_OPCODE_SHR, true, 16, 128, true);
-    status |= alu_test(VTA_ALU_OPCODE_SHR, true, 16, 128, false);
-    status |= alu_test(VTA_ALU_OPCODE_SHL, true, 16, 128, true);
-    status |= alu_test(VTA_ALU_OPCODE_SHL, true, 16, 128, false);
+    status |= alu_test(VTA_ALU_OPCODE_MIN, true, VTA_BLOCK_OUT, 128, true);
+    status |= alu_test(VTA_ALU_OPCODE_MIN, true, VTA_BLOCK_OUT, 128, false);
+    status |= alu_test(VTA_ALU_OPCODE_MAX, true, VTA_BLOCK_OUT, 128, true);
+    status |= alu_test(VTA_ALU_OPCODE_MAX, true, VTA_BLOCK_OUT, 128, false);
+    status |= alu_test(VTA_ALU_OPCODE_ADD, true, VTA_BLOCK_OUT, 128, true);
+    status |= alu_test(VTA_ALU_OPCODE_ADD, true, VTA_BLOCK_OUT, 128, false);
+    status |= alu_test(VTA_ALU_OPCODE_SHR, true, VTA_BLOCK_OUT, 128, true);
+    status |= alu_test(VTA_ALU_OPCODE_SHR, true, VTA_BLOCK_OUT, 128, false);
 
     // Run ALU test (vector-vector operators)
-    status |= alu_test(VTA_ALU_OPCODE_MIN, false, 16, 128, true);
-    status |= alu_test(VTA_ALU_OPCODE_MIN, false, 16, 128, false);
-    status |= alu_test(VTA_ALU_OPCODE_MAX, false, 16, 128, true);
-    status |= alu_test(VTA_ALU_OPCODE_MAX, false, 16, 128, false);
-    status |= alu_test(VTA_ALU_OPCODE_ADD, false, 16, 128, true);
-    status |= alu_test(VTA_ALU_OPCODE_ADD, false, 16, 128, false);
+    status |= alu_test(VTA_ALU_OPCODE_MIN, false, VTA_BLOCK_OUT, 128, true);
+    status |= alu_test(VTA_ALU_OPCODE_MIN, false, VTA_BLOCK_OUT, 128, false);
+    status |= alu_test(VTA_ALU_OPCODE_MAX, false, VTA_BLOCK_OUT, 128, true);
+    status |= alu_test(VTA_ALU_OPCODE_MAX, false, VTA_BLOCK_OUT, 128, false);
+    status |= alu_test(VTA_ALU_OPCODE_ADD, false, VTA_BLOCK_OUT, 128, true);
+    status |= alu_test(VTA_ALU_OPCODE_ADD, false, VTA_BLOCK_OUT, 128, false);
 
     // Run blocked GEMM test
     status |= blocked_gemm_test(256, 256, VTA_BLOCK_OUT*4, true, 2);
diff --git a/vta/hardware/xilinx/src/vta.cc b/vta/hardware/xilinx/src/vta.cc
index 815dc014b887..8d0432477486 100644
--- a/vta/hardware/xilinx/src/vta.cc
+++ b/vta/hardware/xilinx/src/vta.cc
@@ -183,7 +183,7 @@ void compute(
   hls::stream<bool> &s2g_dep_queue,
   hls::stream<bool> &g2l_dep_queue,
   hls::stream<bool> &g2s_dep_queue,
-  out_vec_T inp_mem[VTA_INP_BUFF_DEPTH][VTA_BATCH],
+  inp_vec_T inp_mem[VTA_INP_BUFF_DEPTH][VTA_BATCH],
   wgt_vec_T wgt_mem[VTA_WGT_BUFF_DEPTH][VTA_BLOCK_OUT],
   out_vec_T out_mem[VTA_ACC_BUFF_DEPTH][VTA_BATCH]
   ) {
@@ -286,23 +286,24 @@ void compute(
     done = 0;
 
     // Decode
-    uop_idx_T uop_bgn = insn.range(VTA_INSN_GEM_5_1, VTA_INSN_GEM_5_0);
-    uop_idx_T uop_end = insn.range(VTA_INSN_GEM_6_1, VTA_INSN_GEM_6_0);
-    loop_T iter_out  = insn.range(VTA_INSN_GEM_7_1, VTA_INSN_GEM_7_0);
-    loop_T iter_in  = insn.range(VTA_INSN_GEM_8_1, VTA_INSN_GEM_8_0);
-    acc_idx_T dst_factor_out = insn.range(VTA_INSN_GEM_9_1, VTA_INSN_GEM_9_0);
-    acc_idx_T dst_factor_in = insn.range(VTA_INSN_GEM_A_1, VTA_INSN_GEM_A_0);
-    inp_idx_T src_factor_out = insn.range(VTA_INSN_GEM_B_1, VTA_INSN_GEM_B_0);
-    inp_idx_T src_factor_in = insn.range(VTA_INSN_GEM_C_1, VTA_INSN_GEM_C_0);
+    bool reset_out = insn[VTA_INSN_GEM_5];
+    uop_idx_T uop_bgn = insn.range(VTA_INSN_GEM_6_1, VTA_INSN_GEM_6_0);
+    uop_idx_T uop_end = insn.range(VTA_INSN_GEM_7_1, VTA_INSN_GEM_7_0);
+    loop_T iter_out  = insn.range(VTA_INSN_GEM_8_1, VTA_INSN_GEM_8_0);
+    loop_T iter_in  = insn.range(VTA_INSN_GEM_9_1, VTA_INSN_GEM_9_0);
+    acc_idx_T dst_factor_out = insn.range(VTA_INSN_GEM_A_1, VTA_INSN_GEM_A_0);
+    acc_idx_T dst_factor_in = insn.range(VTA_INSN_GEM_B_1, VTA_INSN_GEM_B_0);
+    inp_idx_T src_factor_out = insn.range(VTA_INSN_GEM_C_1, VTA_INSN_GEM_C_0);
+    inp_idx_T src_factor_in = insn.range(VTA_INSN_GEM_D_1, VTA_INSN_GEM_D_0);
 
     // GEMM-specific fields
-    wgt_idx_T wgt_factor_out = insn.range(VTA_INSN_GEM_D_1, VTA_INSN_GEM_D_0);
-    wgt_idx_T wgt_factor_in = insn.range(VTA_INSN_GEM_E_1, VTA_INSN_GEM_E_0);
+    wgt_idx_T wgt_factor_out = insn.range(VTA_INSN_GEM_E_1, VTA_INSN_GEM_E_0);
+    wgt_idx_T wgt_factor_in = insn.range(VTA_INSN_GEM_F_1, VTA_INSN_GEM_F_0);
 
     // ALU-specific field
-    aluop_opcode_T alu_opcode = insn.range(VTA_INSN_ALU_D_1, VTA_INSN_ALU_D_0);
-    bool use_imm = insn[VTA_INSN_ALU_E];
-    aluop_imm_T imm = insn.range(VTA_INSN_ALU_F_1, VTA_INSN_ALU_F_0);
+    aluop_opcode_T alu_opcode = insn.range(VTA_INSN_ALU_E_1, VTA_INSN_ALU_E_0);
+    bool use_imm = insn[VTA_INSN_ALU_F];
+    aluop_imm_T imm = insn.range(VTA_INSN_ALU_G_1, VTA_INSN_ALU_G_0);
     acc_idx_T dst_offset_out = 0;
     inp_idx_T src_offset_out = 0;
     wgt_idx_T wgt_offset_out = 0;
@@ -326,13 +327,12 @@ void compute(
             uop_T uop = uop_mem[upc];
 
             // Decode indices
-            bool reset_out = uop[VTA_UOP_GEM_0];
             acc_idx_T dst_idx =
-                uop.range(VTA_UOP_GEM_1_1, VTA_UOP_GEM_1_0) + dst_offset_in;
-            acc_idx_T src_idx =
-                uop.range(VTA_UOP_GEM_2_1, VTA_UOP_GEM_2_0) + src_offset_in;
+                uop.range(VTA_UOP_GEM_0_1, VTA_UOP_GEM_0_0) + dst_offset_in;
+            inp_idx_T src_idx =
+                uop.range(VTA_UOP_GEM_1_1, VTA_UOP_GEM_1_0) + src_offset_in;
             wgt_idx_T wgt_idx =
-                uop.range(VTA_UOP_GEM_3_1, VTA_UOP_GEM_3_0) + wgt_offset_in;
+                uop.range(VTA_UOP_GEM_2_1, VTA_UOP_GEM_2_0) + wgt_offset_in;
 
             // Read weight matrix
             wgt_vec_T w_matrix[VTA_BLOCK_OUT];
@@ -341,7 +341,7 @@ void compute(
             }
             // Read input matrix and accum matrix
             acc_vec_T o_matrix[VTA_BATCH];
-            out_vec_T i_matrix[VTA_BATCH];
+            inp_vec_T i_matrix[VTA_BATCH];
             for (int i = 0; i < VTA_BATCH; i++) {
               o_matrix[i] = acc_mem[dst_idx][i];
               i_matrix[i] = inp_mem[src_idx][i];
@@ -365,6 +365,9 @@ void compute(
                   inp_T i_elem =
                       i_matrix[i].range((k + 1) * VTA_INP_WIDTH - 1, k * VTA_INP_WIDTH);
                   mul_T prod = i_elem * w_elem;
+#ifdef NO_DSP
+#pragma HLS RESOURCE variable = prod core = Mul_LUT
+#endif //  NO_DSP
                   tmp += (sum_T) prod;
                 }
                 // Update summation
@@ -373,101 +376,85 @@ void compute(
                 acc_mem_val[i].range((b + 1) * VTA_ACC_WIDTH - 1, b * VTA_ACC_WIDTH) =
                     reset_out ? (acc_T) 0 : accum;
                 st_buf_val[i].range((b + 1) * VTA_OUT_WIDTH - 1, b * VTA_OUT_WIDTH) =
-                    (inp_T) accum.range(VTA_OUT_WIDTH - 1, 0);
+                    (out_T) accum.range(VTA_OUT_WIDTH - 1, 0);
               }
               // Write to buffers
               acc_mem[dst_idx][i] = acc_mem_val[i];
               out_mem[dst_idx][i] = st_buf_val[i];
             }
           }
-        } else if (opcode == VTA_OPCODE_ALU) {
+        }
+#ifndef NO_ALU
+        else if (opcode == VTA_OPCODE_ALU) {
           // Iterate over micro op
           READ_ALU_UOP: for (int upc = uop_bgn; upc < uop_end; upc++) {
-#pragma HLS PIPELINE II = 2 rewind
             // Read micro-op fields
             uop_T uop = uop_mem[upc];
 
             // Decode
-            bool reset_out = uop[VTA_UOP_ALU_0];
             acc_idx_T dst_idx =
-                uop.range(VTA_UOP_ALU_1_1, VTA_UOP_ALU_1_0) + dst_offset_in;
+                uop.range(VTA_UOP_ALU_0_1, VTA_UOP_ALU_0_0) + dst_offset_in;
             acc_idx_T src_idx =
-                uop.range(VTA_UOP_ALU_2_1, VTA_UOP_ALU_2_0) + src_offset_in;
-
-            // Read input matrix and accum matrix
-            acc_vec_T dst_matrix[VTA_BATCH];
-            acc_vec_T src_matrix[VTA_BATCH];
-            for (int i = 0; i < VTA_BATCH; i++) {
-#pragma HLS UNROLL complete
-              dst_matrix[i] = acc_mem[dst_idx][i];
-              src_matrix[i] = acc_mem[src_idx][i];
-            }
-
-            // Result matrices
-            acc_vec_T cmp_res[VTA_BATCH];
-            acc_vec_T add_res[VTA_BATCH];
-            acc_vec_T rshr_res[VTA_BATCH];
-            acc_vec_T lshr_res[VTA_BATCH];
-            out_vec_T short_cmp_res[VTA_BATCH];
-            out_vec_T short_add_res[VTA_BATCH];
-            out_vec_T short_rshr_res[VTA_BATCH];
-            out_vec_T short_lshr_res[VTA_BATCH];
+                uop.range(VTA_UOP_ALU_1_1, VTA_UOP_ALU_1_0) + src_offset_in;
 
             // Perform ALU op over matrix elements
             for (int i = 0; i < VTA_BATCH; i++) {
+              // Read input matrix and accum matrix
+              acc_vec_T dst_vector = acc_mem[dst_idx][i];
+              acc_vec_T src_vector = acc_mem[src_idx][i];
+              // Result matrices
+              acc_vec_T cmp_res;
+              acc_vec_T add_res;
+              acc_vec_T shr_res;
+              out_vec_T short_cmp_res;
+              out_vec_T short_add_res;
+              out_vec_T short_shr_res;
               // Results vector
               acc_vec_T res_vec = 0;
               for (int b = 0; b < VTA_BLOCK_OUT; b++) {
+#pragma HLS PIPELINE II = 1 rewind
                 // Read in operands
-                acc_T src_0 = dst_matrix[i].range((b + 1) * VTA_ACC_WIDTH - 1, b * VTA_ACC_WIDTH);
+                acc_T src_0 = dst_vector.range((b + 1) * VTA_ACC_WIDTH - 1, b * VTA_ACC_WIDTH);
                 acc_T src_1 = use_imm ?
                     (acc_T) imm :
-                    src_matrix[i].range((b + 1) * VTA_ACC_WIDTH - 1, b * VTA_ACC_WIDTH);
+                    src_vector.range((b + 1) * VTA_ACC_WIDTH - 1, b * VTA_ACC_WIDTH);
                 // Compute Min/Max
                 acc_T mix_val = src_0 < src_1 ?
                     (alu_opcode == VTA_ALU_OPCODE_MIN ? src_0 : src_1) :
                     (alu_opcode == VTA_ALU_OPCODE_MIN ? src_1 : src_0);
-                cmp_res[i].range((b + 1) * VTA_ACC_WIDTH - 1, b * VTA_ACC_WIDTH) = mix_val;
-                short_cmp_res[i].range((b + 1) * VTA_OUT_WIDTH - 1, b * VTA_OUT_WIDTH) =
-                    (inp_T) mix_val.range(VTA_OUT_WIDTH - 1, 0);
+                cmp_res.range((b + 1) * VTA_ACC_WIDTH - 1, b * VTA_ACC_WIDTH) = mix_val;
+                short_cmp_res.range((b + 1) * VTA_OUT_WIDTH - 1, b * VTA_OUT_WIDTH) =
+                    (out_T) mix_val.range(VTA_OUT_WIDTH - 1, 0);
                 // Compute Sum
                 acc_T add_val =
                     src_0.range(VTA_ACC_WIDTH - 1, 0) + src_1.range(VTA_ACC_WIDTH - 1, 0);
-                add_res[i].range((b + 1) * VTA_ACC_WIDTH - 1, b * VTA_ACC_WIDTH) = add_val;
-                short_add_res[i].range((b + 1) * VTA_OUT_WIDTH - 1, b * VTA_OUT_WIDTH) =
-                    (inp_T) add_val.range(VTA_OUT_WIDTH - 1, 0);
-                // Compute Right Shift
-                acc_T rshr_val =
+                add_res.range((b + 1) * VTA_ACC_WIDTH - 1, b * VTA_ACC_WIDTH) = add_val;
+                short_add_res.range((b + 1) * VTA_OUT_WIDTH - 1, b * VTA_OUT_WIDTH) =
+                    (out_T) add_val.range(VTA_OUT_WIDTH - 1, 0);
+                // Compute Shift Right
+                acc_T shr_val =
                     src_0 >> (aluop_sh_imm_T) src_1.range(VTA_LOG_ACC_WIDTH - 1, 0);
-                rshr_res[i].range((b + 1) * VTA_ACC_WIDTH - 1, b * VTA_ACC_WIDTH) = rshr_val;
-                short_rshr_res[i].range((b + 1) * VTA_OUT_WIDTH - 1, b * VTA_OUT_WIDTH) =
-                    (inp_T) rshr_val.range(VTA_OUT_WIDTH-1, 0);
-                // Compute Left Shift
-                acc_T lshr_val =
-                    src_0 << (aluop_sh_imm_T) src_1.range(VTA_LOG_ACC_WIDTH - 1, 0);
-                lshr_res[i].range((b + 1) * VTA_ACC_WIDTH - 1, b * VTA_ACC_WIDTH) = lshr_val;
-                short_lshr_res[i].range((b + 1) * VTA_OUT_WIDTH - 1, b * VTA_OUT_WIDTH) =
-                    (inp_T) lshr_val.range(VTA_OUT_WIDTH-1, 0);
+                shr_res.range((b + 1) * VTA_ACC_WIDTH - 1, b * VTA_ACC_WIDTH) = shr_val;
+                short_shr_res.range((b + 1) * VTA_OUT_WIDTH - 1, b * VTA_OUT_WIDTH) =
+                    (out_T) shr_val.range(VTA_OUT_WIDTH-1, 0);
               }
 
               // Store to accum memory/store buffer
               if (alu_opcode == VTA_ALU_OPCODE_MIN ||
                   alu_opcode == VTA_ALU_OPCODE_MAX) {
-                acc_mem[dst_idx][i] = cmp_res[i];
-                out_mem[dst_idx][i] = short_cmp_res[i];
+                acc_mem[dst_idx][i] = cmp_res;
+                out_mem[dst_idx][i] = short_cmp_res;
               } else if (alu_opcode == VTA_ALU_OPCODE_ADD) {
-                acc_mem[dst_idx][i] = add_res[i];
-                out_mem[dst_idx][i] = short_add_res[i];
+                acc_mem[dst_idx][i] = add_res;
+                out_mem[dst_idx][i] = short_add_res;
               } else if (alu_opcode == VTA_ALU_OPCODE_SHR) {
-                acc_mem[dst_idx][i] = rshr_res[i];
-                out_mem[dst_idx][i] = short_rshr_res[i];
-              } else if (alu_opcode == VTA_ALU_OPCODE_SHL) {
-                acc_mem[dst_idx][i] = lshr_res[i];
-                out_mem[dst_idx][i] = short_lshr_res[i];
+                acc_mem[dst_idx][i] = shr_res;
+                out_mem[dst_idx][i] = short_shr_res;
               }
             }
           }
         }
+#endif  // NO_ALU
 
         // Update offsets
         dst_offset_in += dst_factor_in;
diff --git a/vta/hardware/xilinx/src/vta.h b/vta/hardware/xilinx/src/vta.h
index e01ef90bb244..6118f867fd0c 100644
--- a/vta/hardware/xilinx/src/vta.h
+++ b/vta/hardware/xilinx/src/vta.h
@@ -92,7 +92,7 @@ typedef ap_uint<VTA_ALU_OPCODE_BIT_WIDTH> aluop_opcode_T;
 typedef ap_int<VTA_ALUOP_IMM_BIT_WIDTH> aluop_imm_T;
 
 /* \typedef aluop_opcode_T ALU operation shift immediate datatype*/
-typedef ap_uint<VTA_LOG_ACC_WIDTH> aluop_sh_imm_T;
+typedef ap_int<VTA_LOG_ACC_WIDTH> aluop_sh_imm_T;
 
 /*!
 * \brief Fetch module.
diff --git a/vta/include/vta/hw_spec.h b/vta/include/vta/hw_spec.h
index 0c30b344795a..7eae322a0aca 100644
--- a/vta/include/vta/hw_spec.h
+++ b/vta/include/vta/hw_spec.h
@@ -93,9 +93,7 @@ extern "C" {
 /*! Instruction opcode field bitwidth */
 #define VTA_OPCODE_BIT_WIDTH 3
 /*! ALU opcode field bitwidth */
-#define VTA_ALU_OPCODE_BIT_WIDTH 3
-/*! ALU instruction reset mode bitwidth */
-#define VTA_ALU_RESET_BIT_WIDTH 2
+#define VTA_ALU_OPCODE_BIT_WIDTH 2
 
 /*! Opcode: load encoding */
 #define VTA_OPCODE_LOAD 0
@@ -114,23 +112,8 @@ extern "C" {
 #define VTA_ALU_OPCODE_MAX 1
 /*! ALU opcode: binary add op */
 #define VTA_ALU_OPCODE_ADD 2
-/*! ALU opcode: binary sub op [NOT IMPLEMENTED] */
-#define VTA_ALU_OPCODE_SUB 3
-/*! ALU opcode: binary mul op  [NOT IMPLEMENTED] */
-#define VTA_ALU_OPCODE_MUL 4
-/*! ALU opcode: shift left by immediate op */
-#define VTA_ALU_OPCODE_SHL 5
-/*! ALU opcode: shift right by immediate op [NOT IMPLEMENTED] */
-#define VTA_ALU_OPCODE_SHR 6
-
-/*! ALU instruction reset mode: set to min */
-#define VTA_ALU_RESET_MIN 3
-/*! ALU instruction reset mode: set to zero */
-#define VTA_ALU_RESET_ZERO 0
-/*! ALU instruction reset mode: no reset */
-#define VTA_ALU_NO_RESET 2
-/*! ALU instruction reset mode: set to max */
-#define VTA_ALU_RESET_MAX 1
+/*! ALU opcode: shift right by immediate op */
+#define VTA_ALU_OPCODE_SHR 3
 
 /*! Memory type field bitwidth */
 #define VTA_MEMOP_ID_BIT_WIDTH 2
@@ -149,7 +132,7 @@ extern "C" {
 /*! ALU Instruction: immediate bitwidth*/
 #define VTA_ALUOP_IMM_BIT_WIDTH 16
 /*! GEMM/ALU Instruction: loop max iter bits */
-#define VTA_LOOP_ITER_WIDTH 15
+#define VTA_LOOP_ITER_WIDTH 14
 
 /*! Mem ID constant: uop memory */
 #define VTA_MEM_ID_UOP 0
@@ -190,16 +173,17 @@ extern "C" {
 // arg 2: pop_next_dependence   | bool              |
 // arg 3: push_prev_dependence  | bool              |
 // arg 4: push_next_dependence  | bool              |
-// arg 5: uop_bgn               | uop_idx_T         |
-// arg 6: uop_end               | uop_idx_T         |
-// arg 7: iteration count ax0   | loop_T            |
-// arg 8: iteration count ax1   | loop_T            |
-// arg 9: accum idx factor ax0  | acc_idx_T         |
-// arg a: accum idx factor ax1  | acc_idx_T         |
-// arg b: input idx factor ax0  | acc_idx_T         |
-// arg c: input idx factor ax1  | acc_idx_T         |
-// arg d: weight idx factor ax0 | wgt_idx_T         |
-// arg e: weight idx factor ax1 | wgt_idx_T         |
+// arg 5: reset_reg             | bool              |
+// arg 6: uop_bgn               | uop_idx_T         |
+// arg 7: uop_end               | uop_idx_T         |
+// arg 8: iteration count ax0   | loop_T            |
+// arg 9: iteration count ax1   | loop_T            |
+// arg a: accum idx factor ax0  | acc_idx_T         |
+// arg b: accum idx factor ax1  | acc_idx_T         |
+// arg c: input idx factor ax0  | inp_idx_T         |
+// arg d: input idx factor ax1  | inp_idx_T         |
+// arg e: weight idx factor ax0 | wgt_idx_T         |
+// arg f: weight idx factor ax1 | wgt_idx_T         |
 //
 // ALU
 // _____________________________|_type______________|
@@ -208,17 +192,18 @@ extern "C" {
 // arg 2: pop_next_dependence   | bool              |
 // arg 3: push_prev_dependence  | bool              |
 // arg 4: push_next_dependence  | bool              |
-// arg 5: uop_bgn               | uop_idx_T         |
-// arg 6: uop_end               | uop_idx_T         |
-// arg 7: iteration count ax0   | loop_T            |
-// arg 8: iteration count ax1   | loop_T            |
-// arg 9: dst idx factor ax0    | acc_idx_T         |
-// arg a: dst idx factor ax1    | acc_idx_T         |
-// arg b: src idx factor ax0    | acc_idx_T         |
-// arg c: src idx factor ax1    | acc_idx_T         |
-// arg d: alu_opcode            | aluop_opcode_T    |
-// arg e: use_imm               | bool              |
-// arg f: imm                   | alu_imm_T         |
+// arg 5: reset_reg             | bool              |
+// arg 6: uop_bgn               | uop_idx_T         |
+// arg 7: uop_end               | uop_idx_T         |
+// arg 8: iteration count ax0   | loop_T            |
+// arg 9: iteration count ax1   | loop_T            |
+// arg a: dst idx factor ax0    | acc_idx_T         |
+// arg b: dst idx factor ax1    | acc_idx_T         |
+// arg c: src idx factor ax0    | inp_idx_T         |
+// arg d: src idx factor ax1    | inp_idx_T         |
+// arg e: alu_opcode            | aluop_opcode_T    |
+// arg f: use_imm               | bool              |
+// arg g: imm                   | alu_imm_T         |
 
 /*! Load/Store instruction start position of the opcode field */
 #define VTA_INSN_MEM_0_0 0
@@ -285,93 +270,87 @@ extern "C" {
 #define VTA_INSN_GEM_3   (VTA_INSN_GEM_2 + 1)
 /*! GEMM instruction position of the push_next_dependence field */
 #define VTA_INSN_GEM_4   (VTA_INSN_GEM_3 + 1)
+/*! GEMM instruction position of the reset register bit */
+#define VTA_INSN_GEM_5   (VTA_INSN_GEM_4 + 1)
 /*! GEMM instruction start position of the uop_bgn field */
-#define VTA_INSN_GEM_5_0 (VTA_INSN_GEM_4 + 1)
+#define VTA_INSN_GEM_6_0 (VTA_INSN_GEM_5 + 1)
 /*! GEMM instruction end position of the uop_bgn field */
-#define VTA_INSN_GEM_5_1 (VTA_INSN_GEM_5_0 + VTA_LOG_UOP_BUFF_DEPTH - 1)
+#define VTA_INSN_GEM_6_1 (VTA_INSN_GEM_6_0 + VTA_LOG_UOP_BUFF_DEPTH - 1)
 /*! GEMM instruction start position of the uop_end field */
-#define VTA_INSN_GEM_6_0 (VTA_INSN_GEM_5_1 + 1)
+#define VTA_INSN_GEM_7_0 (VTA_INSN_GEM_6_1 + 1)
 /*! GEMM instruction end position of the uop_end field */
-#define VTA_INSN_GEM_6_1 (VTA_INSN_GEM_6_0 + VTA_LOG_UOP_BUFF_DEPTH + 1 - 1)
+#define VTA_INSN_GEM_7_1 (VTA_INSN_GEM_7_0 + VTA_LOG_UOP_BUFF_DEPTH + 1 - 1)
 /*! GEMM instruction start position of the iter_out field */
-#define VTA_INSN_GEM_7_0 (VTA_INSN_GEM_6_1 + 1)
+#define VTA_INSN_GEM_8_0 (VTA_INSN_GEM_7_1 + 1)
 /*! GEMM instruction end position of the iter_out field */
-#define VTA_INSN_GEM_7_1 (VTA_INSN_GEM_7_0 + VTA_LOOP_ITER_WIDTH - 1)
+#define VTA_INSN_GEM_8_1 (VTA_INSN_GEM_8_0 + VTA_LOOP_ITER_WIDTH - 1)
 /*! GEMM instruction start position of the iter_in field */
-#define VTA_INSN_GEM_8_0 (VTA_INSN_GEM_7_1 + 1)
+#define VTA_INSN_GEM_9_0 (VTA_INSN_GEM_8_1 + 1)
 /*! GEMM instruction end position of the iter_in field */
-#define VTA_INSN_GEM_8_1 (VTA_INSN_GEM_8_0 + VTA_LOOP_ITER_WIDTH - 1)
+#define VTA_INSN_GEM_9_1 (VTA_INSN_GEM_9_0 + VTA_LOOP_ITER_WIDTH - 1)
 /*! GEMM instruction start position of the dst_factor_out field */
-#define VTA_INSN_GEM_9_0 64
+#define VTA_INSN_GEM_A_0 64
 /*! GEMM instruction end position of the dst_factor_out field */
-#define VTA_INSN_GEM_9_1 (VTA_INSN_GEM_9_0 + VTA_LOG_ACC_BUFF_DEPTH - 1)
+#define VTA_INSN_GEM_A_1 (VTA_INSN_GEM_A_0 + VTA_LOG_ACC_BUFF_DEPTH - 1)
 /*! GEMM instruction start position of the dst_factor_in field */
-#define VTA_INSN_GEM_A_0 (VTA_INSN_GEM_9_1 + 1)
+#define VTA_INSN_GEM_B_0 (VTA_INSN_GEM_A_1 + 1)
 /*! GEMM instruction end position of the dst_factor_in field */
-#define VTA_INSN_GEM_A_1 (VTA_INSN_GEM_A_0 + VTA_LOG_ACC_BUFF_DEPTH - 1)
+#define VTA_INSN_GEM_B_1 (VTA_INSN_GEM_B_0 + VTA_LOG_ACC_BUFF_DEPTH - 1)
 /*! GEMM instruction start position of the src_factor_out field */
-#define VTA_INSN_GEM_B_0 (VTA_INSN_GEM_A_1 + 1)
+#define VTA_INSN_GEM_C_0 (VTA_INSN_GEM_B_1 + 1)
 /*! GEMM instruction end position of the src_factor_out field */
-#define VTA_INSN_GEM_B_1 (VTA_INSN_GEM_B_0 + VTA_LOG_ACC_BUFF_DEPTH - 1)
+#define VTA_INSN_GEM_C_1 (VTA_INSN_GEM_C_0 + VTA_LOG_INP_BUFF_DEPTH - 1)
 /*! GEMM instruction start position of the src_factor_in field */
-#define VTA_INSN_GEM_C_0 (VTA_INSN_GEM_B_1 + 1)
+#define VTA_INSN_GEM_D_0 (VTA_INSN_GEM_C_1 + 1)
 /*! GEMM instruction end position of the src_factor_in field */
-#define VTA_INSN_GEM_C_1 (VTA_INSN_GEM_C_0 + VTA_LOG_ACC_BUFF_DEPTH - 1)
+#define VTA_INSN_GEM_D_1 (VTA_INSN_GEM_D_0 + VTA_LOG_INP_BUFF_DEPTH - 1)
 
 /*! GEMM instruction start position of the wgt_factor_out field */
-#define VTA_INSN_GEM_D_0 (VTA_INSN_GEM_C_1 + 1)
+#define VTA_INSN_GEM_E_0 (VTA_INSN_GEM_D_1 + 1)
 /*! GEMM instruction end position of the wgt_factor_out field */
-#define VTA_INSN_GEM_D_1 (VTA_INSN_GEM_D_0 + VTA_LOG_WGT_BUFF_DEPTH - 1)
+#define VTA_INSN_GEM_E_1 (VTA_INSN_GEM_E_0 + VTA_LOG_WGT_BUFF_DEPTH - 1)
 /*! GEMM instruction start position of the wgt_factor_in field */
-#define VTA_INSN_GEM_E_0 (VTA_INSN_GEM_D_1 + 1)
+#define VTA_INSN_GEM_F_0 (VTA_INSN_GEM_E_1 + 1)
 /*! GEMM instruction end position of the wgt_factor_in field */
-#define VTA_INSN_GEM_E_1 (VTA_INSN_GEM_E_0 + VTA_LOG_WGT_BUFF_DEPTH - 1)
+#define VTA_INSN_GEM_F_1 (VTA_INSN_GEM_F_0 + VTA_LOG_WGT_BUFF_DEPTH - 1)
 
 /*! ALU instruction start position of the alu_opcode field */
-#define VTA_INSN_ALU_D_0 (VTA_INSN_GEM_C_1 + 1)
+#define VTA_INSN_ALU_E_0 (VTA_INSN_GEM_D_1 + 1)
 /*! ALU instruction end position of the alu_opcode field */
-#define VTA_INSN_ALU_D_1 (VTA_INSN_ALU_D_0 + VTA_ALU_OPCODE_BIT_WIDTH - 1)
+#define VTA_INSN_ALU_E_1 (VTA_INSN_ALU_E_0 + VTA_ALU_OPCODE_BIT_WIDTH - 1)
 /*! ALU instruction position of the use_imm field */
-#define VTA_INSN_ALU_E   (VTA_INSN_ALU_D_1 + 1)
+#define VTA_INSN_ALU_F   (VTA_INSN_ALU_E_1 + 1)
 /*! ALU instruction start position of the immediate field */
-#define VTA_INSN_ALU_F_0 (VTA_INSN_ALU_E + 1)
+#define VTA_INSN_ALU_G_0 (VTA_INSN_ALU_F + 1)
 /*! ALU instruction end position of the immediate field */
-#define VTA_INSN_ALU_F_1 (VTA_INSN_ALU_F_0 + VTA_ALUOP_IMM_BIT_WIDTH - 1)
+#define VTA_INSN_ALU_G_1 (VTA_INSN_ALU_G_0 + VTA_ALUOP_IMM_BIT_WIDTH - 1)
 
-/*! GEMM Micro-op position of the reset_out field */
-#define VTA_UOP_GEM_0 0
 /*! GEMM Micro-op start position of the acc_idx field */
-#define VTA_UOP_GEM_1_0 (VTA_UOP_GEM_0 + 1)
+#define VTA_UOP_GEM_0_0 0
 /*! GEMM Micro-op end position of the acc_idx field */
-#define VTA_UOP_GEM_1_1 (VTA_UOP_GEM_1_0 + VTA_LOG_ACC_BUFF_DEPTH - 1)
+#define VTA_UOP_GEM_0_1 (VTA_UOP_GEM_0_0 + VTA_LOG_ACC_BUFF_DEPTH - 1)
 /*! GEMM Micro-op start position of the inp_idx field */
-#define VTA_UOP_GEM_2_0 (VTA_UOP_GEM_1_1 + 1)
+#define VTA_UOP_GEM_1_0 (VTA_UOP_GEM_0_1 + 1)
 /*! GEMM Micro-op end position of the inp_idx field */
-#define VTA_UOP_GEM_2_1 (VTA_UOP_GEM_2_0 + VTA_LOG_ACC_BUFF_DEPTH - 1)
+#define VTA_UOP_GEM_1_1 (VTA_UOP_GEM_1_0 + VTA_LOG_INP_BUFF_DEPTH - 1)
 /*! GEMM Micro-op start position of the wgt_idx field */
-#define VTA_UOP_GEM_3_0 (VTA_UOP_GEM_2_1 + 1)
+#define VTA_UOP_GEM_2_0 (VTA_UOP_GEM_1_1 + 1)
 /*! GEMM Micro-op end position of the wgt_idx field */
-#define VTA_UOP_GEM_3_1 (VTA_UOP_GEM_3_0 + VTA_LOG_WGT_BUFF_DEPTH - 1)
+#define VTA_UOP_GEM_2_1 (VTA_UOP_GEM_2_0 + VTA_LOG_WGT_BUFF_DEPTH - 1)
 
-/*! GEMM Micro-op position of the reset_out field */
-#define VTA_UOP_ALU_0 0
 /*! GEMM Micro-op start position of the acc_idx field */
-#define VTA_UOP_ALU_1_0 (VTA_UOP_ALU_0 + 1)
+#define VTA_UOP_ALU_0_0 0
 /*! GEMM Micro-op end position of the acc_idx field */
-#define VTA_UOP_ALU_1_1 (VTA_UOP_ALU_1_0 + VTA_LOG_ACC_BUFF_DEPTH - 1)
+#define VTA_UOP_ALU_0_1 (VTA_UOP_ALU_0_0 + VTA_LOG_ACC_BUFF_DEPTH - 1)
 /*! GEMM Micro-op start position of the inp_idx field */
-#define VTA_UOP_ALU_2_0 (VTA_UOP_ALU_1_1 + 1)
+#define VTA_UOP_ALU_1_0 (VTA_UOP_ALU_0_1 + 1)
 /*! GEMM Micro-op end position of the inp_idx field */
-#define VTA_UOP_ALU_2_1 (VTA_UOP_ALU_2_0 + VTA_LOG_ACC_BUFF_DEPTH - 1)
-/*! GEMM Micro-op start position of the wgt_idx field */
-#define VTA_UOP_ALU_3_0 (VTA_UOP_ALU_2_1 + 1)
-/*! GEMM Micro-op end position of the wgt_idx field */
-#define VTA_UOP_ALU_3_1 (VTA_UOP_ALU_3_0 + VTA_LOG_WGT_BUFF_DEPTH - 1)
+#define VTA_UOP_ALU_1_1 (VTA_UOP_ALU_1_0 + VTA_LOG_INP_BUFF_DEPTH - 1)
 
 /*! \brief VTA generic instruction */
 typedef struct {
-  uint64_t word_0     : 64;
-  uint64_t word_1     : 64;
+  uint64_t word_0         : 64;
+  uint64_t word_1         : 64;
 } VTAGenericInsn;
 
 /*! \brief VTA load/store instruction
@@ -454,10 +433,12 @@ typedef struct {
   uint64_t push_prev_dep  : 1;
   /*! \brief Push dependence token to store stage */
   uint64_t push_next_dep  : 1;
+  /*! \brief Reset register */
+  uint64_t reset_reg      : 1;
   /*! \brief Micro-op begin address */
   uint64_t uop_bgn        : VTA_LOG_UOP_BUFF_DEPTH;
   /*! \brief Micro-op end address */
-  uint64_t uop_end        : VTA_LOG_UOP_BUFF_DEPTH+1;
+  uint64_t uop_end        : VTA_LOG_UOP_BUFF_DEPTH + 1;
   /*! \brief Iterations in the outer uop execution loop */
   uint64_t iter_out       : VTA_LOOP_ITER_WIDTH;
   /*! \brief Iterations in the inner uop execution loop */
@@ -467,9 +448,9 @@ typedef struct {
   /*! \brief Inner loop accumulator memory index factor */
   uint64_t dst_factor_in  : VTA_LOG_ACC_BUFF_DEPTH;
   /*! \brief Outer loop input memory index factor */
-  uint64_t src_factor_out : VTA_LOG_ACC_BUFF_DEPTH;
+  uint64_t src_factor_out : VTA_LOG_INP_BUFF_DEPTH;
   /*! \brief Inner loop input memory index factor */
-  uint64_t src_factor_in  : VTA_LOG_ACC_BUFF_DEPTH;
+  uint64_t src_factor_in  : VTA_LOG_INP_BUFF_DEPTH;
   /*! \brief Outer loop weight memory index factor */
   uint64_t wgt_factor_out : VTA_LOG_WGT_BUFF_DEPTH;
   /*! \brief Inner loop weight memory index factor */
@@ -516,10 +497,12 @@ typedef struct {
   uint64_t push_prev_dep  : 1;
   /*! \brief Push dependence token to store stage */
   uint64_t push_next_dep  : 1;
+  /*! \brief Reset register */
+  uint64_t reset_reg      : 1;
   /*! \brief Micro-op begin address */
   uint64_t uop_bgn        : VTA_LOG_UOP_BUFF_DEPTH;
   /*! \brief Micro-op end address */
-  uint64_t uop_end        : VTA_LOG_UOP_BUFF_DEPTH+1;
+  uint64_t uop_end        : VTA_LOG_UOP_BUFF_DEPTH + 1;
   /*! \brief Iterations in the outer uop execution loop */
   uint64_t iter_out       : VTA_LOOP_ITER_WIDTH;
   /*! \brief Iterations in the inner uop execution loop */
@@ -529,9 +512,9 @@ typedef struct {
   /*! \brief Inner loop accumulator memory destination index factor */
   uint64_t dst_factor_in  : VTA_LOG_ACC_BUFF_DEPTH;
   /*! \brief Outer loop accumulator memory source index factor */
-  uint64_t src_factor_out : VTA_LOG_ACC_BUFF_DEPTH;
+  uint64_t src_factor_out : VTA_LOG_INP_BUFF_DEPTH;
   /*! \brief Inner loop accumulator memory source index factor */
-  uint64_t src_factor_in  : VTA_LOG_ACC_BUFF_DEPTH;
+  uint64_t src_factor_in  : VTA_LOG_INP_BUFF_DEPTH;
   /*! \brief ALU opcode */
   uint64_t alu_opcode     : VTA_ALU_OPCODE_BIT_WIDTH;
   /*! \brief Use immediate is true */
@@ -554,12 +537,10 @@ union VTAInsn {
 
 /*! \brief VTA micro-op for GEMM/ALU instruction */
 typedef struct {
-  /*! \brief Initialize acc_mem at index dst_idx to 0*/
-  uint32_t reset_out  : 1;
   /*! \brief Destination index (indexes accum buffer) */
   uint32_t dst_idx    : VTA_LOG_ACC_BUFF_DEPTH;
   /*! \brief Source index (indexes input buffer for GEMM or accum buffer for ALU) */
-  uint32_t src_idx    : VTA_LOG_ACC_BUFF_DEPTH;
+  uint32_t src_idx    : VTA_LOG_INP_BUFF_DEPTH;
   /*! \brief Weight index (indexes weight buffer) */
   uint32_t wgt_idx    : VTA_LOG_WGT_BUFF_DEPTH;
 } VTAUop;
diff --git a/vta/python/vta/hw_spec.py b/vta/python/vta/hw_spec.py
index ec7595f4c740..3a66835d2d93 100644
--- a/vta/python/vta/hw_spec.py
+++ b/vta/python/vta/hw_spec.py
@@ -1,47 +1,61 @@
 """VTA configuration constants (should match hw_spec.h"""
 from __future__ import absolute_import as _abs
 
-#  Log of input/activation width in bits (default 3 -> 8 bits)
-VTA_LOG_INP_WIDTH = 3
-#  Log of kernel weight width in bits (default 3 -> 8 bits)
-VTA_LOG_WGT_WIDTH = 3
-#  Log of accum width in bits (default 5 -> 32 bits)
-VTA_LOG_ACC_WIDTH = 5
-#  Log of tensor batch size (A in (A,B)x(B,C) matrix multiplication)
-VTA_LOG_BATCH = 0
-#  Log of tensor inner block size (B in (A,B)x(B,C) matrix multiplication)
-VTA_LOG_BLOCK_IN = 4
-#  Log of tensor outer block size (C in (A,B)x(B,C) matrix multiplication)
-VTA_LOG_BLOCK_OUT = 4
-VTA_LOG_OUT_WIDTH = VTA_LOG_INP_WIDTH
-#  Log of uop buffer size in Bytes
-VTA_LOG_UOP_BUFF_SIZE = 15
-#  Log of acc buffer size in Bytes
-VTA_LOG_ACC_BUFF_SIZE = 17
+import os
+python_vta_dir = os.path.dirname(__file__)
+print python_vta_dir
+filename = os.path.join(python_vta_dir, '../../config.mk')
+
+VTA_PYNQ_BRAM_W = 32
+VTA_PYNQ_BRAM_D = 1024
+VTA_PYNQ_NUM_BRAM = 124
+
+keys = ["VTA_LOG_INP_WIDTH", "VTA_LOG_WGT_WIDTH", "VTA_LOG_ACC_WIDTH",
+        "VTA_LOG_BATCH", "VTA_LOG_BLOCK_IN", "VTA_LOG_BLOCK_OUT",
+        "VTA_LOG_UOP_BUFF_SIZE", "VTA_LOG_INP_BUFF_SIZE",
+        "VTA_LOG_WGT_BUFF_SIZE", "VTA_LOG_ACC_BUFF_SIZE"]
+
+params = {}
+
+if os.path.isfile(filename):
+  with open(filename) as f:
+    for line in f:
+      for k in keys:
+        if k+" =" in line:
+          val = line.split("=")[1].strip()
+          params[k] = int(val)
+  # print params
+else:
+  print "Error: {} not found. Please make sure you have config.mk in your vta root".format(filename)
+  exit()
 
 # The Constants
-VTA_WGT_WIDTH = 8
-VTA_INP_WIDTH = VTA_WGT_WIDTH
-VTA_OUT_WIDTH = 32
+VTA_INP_WIDTH = 1 << params["VTA_LOG_INP_WIDTH"]
+VTA_WGT_WIDTH = 1 << params["VTA_LOG_WGT_WIDTH"]
+VTA_OUT_WIDTH = 1 << params["VTA_LOG_ACC_WIDTH"]
 
 VTA_TARGET = "VTA_PYNQ_TARGET"
 
 # Dimensions of the GEMM unit
 # (BATCH,BLOCK_IN) x (BLOCK_IN,BLOCK_OUT)
-VTA_BATCH = 1
-VTA_BLOCK_IN = 16
-VTA_BLOCK_OUT = 16
+VTA_BATCH = 1 << params["VTA_LOG_BATCH"]
+VTA_BLOCK_IN = 1 << params["VTA_LOG_BLOCK_IN"]
+VTA_BLOCK_OUT = 1 << params["VTA_LOG_BLOCK_OUT"]
 
-# log-2 On-chip wgt buffer size in Bytes
-VTA_LOG_WGT_BUFF_SIZE = 15
+# log-2 On-chip uop buffer size in Bytes
+VTA_LOG_UOP_BUFF_SIZE = params["VTA_LOG_UOP_BUFF_SIZE"]
 # log-2 On-chip input buffer size in Bytes
-VTA_LOG_INP_BUFF_SIZE = 15
+VTA_LOG_INP_BUFF_SIZE = params["VTA_LOG_INP_BUFF_SIZE"]
+# log-2 On-chip wgt buffer size in Bytes
+VTA_LOG_WGT_BUFF_SIZE = params["VTA_LOG_WGT_BUFF_SIZE"]
 # log-2 On-chip output buffer size in Bytes
-VTA_LOG_OUT_BUFF_SIZE = 17
-# On-chip wgt buffer size in Bytes
-VTA_WGT_BUFF_SIZE = 1 << VTA_LOG_WGT_BUFF_SIZE
+VTA_LOG_OUT_BUFF_SIZE = params["VTA_LOG_ACC_BUFF_SIZE"]
+# Uop buffer size
+VTA_UOP_BUFF_SIZE = 1 << VTA_LOG_UOP_BUFF_SIZE
 # Input buffer size
 VTA_INP_BUFF_SIZE = 1 << VTA_LOG_INP_BUFF_SIZE
+# On-chip wgt buffer size in Bytes
+VTA_WGT_BUFF_SIZE = 1 << VTA_LOG_WGT_BUFF_SIZE
 # Output buffer size.
 VTA_OUT_BUFF_SIZE = 1 << VTA_LOG_OUT_BUFF_SIZE
 
@@ -69,11 +83,7 @@
 VTA_ALU_OPCODE_MIN = 0
 VTA_ALU_OPCODE_MAX = 1
 VTA_ALU_OPCODE_ADD = 2
-VTA_ALU_OPCODE_SUB = 3
-VTA_ALU_OPCODE_MUL = 4
-VTA_ALU_OPCODE_SHL = 5
-VTA_ALU_OPCODE_SHR = 6
-VTA_ALU_OPCODE_UNSET = 7
+VTA_ALU_OPCODE_SHR = 3
 
 # Task queue id (pipeline stage)
 VTA_QID_LOAD_INP = 1
diff --git a/vta/python/vta/ir_pass.py b/vta/python/vta/ir_pass.py
index 00c73c4ab8b3..86329cf8d2c7 100644
--- a/vta/python/vta/ir_pass.py
+++ b/vta/python/vta/ir_pass.py
@@ -617,7 +617,6 @@ def _flatten_loop(src_coeff, dst_coeff, extents):
                 extents.append(tmp_body.extent)
                 tmp_body = tmp_body.body
             # Derive opcode
-            alu_opcode = spec.VTA_ALU_OPCODE_UNSET
             if isinstance(loop_body.value, tvm.expr.Add):
                 alu_opcode = spec.VTA_ALU_OPCODE_ADD
                 lhs = loop_body.value.a
@@ -640,9 +639,9 @@ def _flatten_loop(src_coeff, dst_coeff, extents):
                 rhs = loop_body.value.b
             elif isinstance(loop_body.value, tvm.expr.Call):
                 if loop_body.value.name == 'shift_left':
-                    alu_opcode = spec.VTA_ALU_OPCODE_SHL
+                    alu_opcode = spec.VTA_ALU_OPCODE_SHR
                     lhs = loop_body.value.args[0]
-                    rhs = loop_body.value.args[1]
+                    rhs = tvm.ir_pass.Simplify(-loop_body.value.args[1])
                 elif loop_body.value.name == 'shift_right':
                     alu_opcode = spec.VTA_ALU_OPCODE_SHR
                     lhs = loop_body.value.args[0]
@@ -732,7 +731,8 @@ def _flatten_loop(src_coeff, dst_coeff, extents):
                 tvm.ir_pass.Simplify(c/(spec.VTA_BATCH*spec.VTA_BLOCK_OUT)) for c in dst_coeff]
 
             # Flatten the outer loops
-            src_coeff, dst_coeff, extents = _flatten_loop(src_coeff, dst_coeff, extents)
+            if extents:
+                src_coeff, dst_coeff, extents = _flatten_loop(src_coeff, dst_coeff, extents)
 
             # Insert ALU micro-ops
             irb = tvm.ir_builder.create()
diff --git a/vta/src/runtime.cc b/vta/src/runtime.cc
index 8c7c9fdc9bee..9333e393fc12 100644
--- a/vta/src/runtime.cc
+++ b/vta/src/runtime.cc
@@ -117,7 +117,6 @@ class UopKernel {
     // The loop nest structure
     VerifyDep(dst_index);
     VTAUop op;
-    op.reset_out = reset_out;
     op.dst_idx = dst_index;
     op.src_idx = src_index;
     op.wgt_idx = wgt_index;
@@ -128,6 +127,12 @@ class UopKernel {
     } else {
       assert(mode_ == mode);
     }
+    // Set reset_out field if unset
+    if (reset_out_ == 0xFFFFFFFF) {
+      reset_out_ = reset_out;
+    } else {
+      assert(reset_out_ == reset_out);
+    }
     // Check kernel op and imm/imm_val in ALU mode
     if (mode == 1) {
       if (opcode_ == 0xFFFFFFFF) {
@@ -146,12 +151,11 @@ class UopKernel {
     uint32_t size = seq_.size();
     printf("There are %u uops\n", size);
     for (uint32_t i = 0; i < size; ++i) {
-      printf("[%04u]\t acc=%u, inp=%u, wgt=%u, reset_out=%u\n",
+      printf("[%04u]\t acc=%u, inp=%u, wgt=%u\n",
              i,
              seq_[i].dst_idx,
              seq_[i].src_idx,
-             seq_[i].wgt_idx,
-             seq_[i].reset_out);
+             seq_[i].wgt_idx);
     }
     printf("\n");
   }
@@ -160,6 +164,7 @@ class UopKernel {
   // The kernel's mode, opcode, immediate setting and value
   uint32_t mode_{0xFFFFFFFF};  // UOP type: 0xFFFFFFFF - unset, 0 - GEMM, 1 - ALU
   uint32_t opcode_{0xFFFFFFFF};
+  uint32_t reset_out_{0xFFFFFFFF};
   bool use_imm_{false};
   uint16_t imm_val_{0};
 
@@ -521,20 +526,6 @@ class InsnQueue : public BaseQueue {
           } else {
               return "add";
           }
-      } else if (opcode == VTA_ALU_OPCODE_SUB) {
-          if (use_imm) {
-              return "sub imm";
-          } else {
-              return "sub";
-          }
-      } else if (opcode == VTA_ALU_OPCODE_MUL) {
-          if (use_imm) {
-              return "mul imm";
-          } else {
-              return "mul";
-          }
-      } else if (opcode == VTA_ALU_OPCODE_SHL) {
-          return "shl";
       } else if (opcode == VTA_ALU_OPCODE_SHR) {
           return "shr";
       }
@@ -641,6 +632,7 @@ class InsnQueue : public BaseQueue {
                static_cast<int>(c.mem.pop_next_dep),
                static_cast<int>(c.mem.push_prev_dep),
                static_cast<int>(c.mem.push_next_dep));
+        printf("\treset_out: %d\n", static_cast<int>(c.gemm.reset_reg));
         printf("\trange (%d, %d)\n",
                static_cast<int>(c.gemm.uop_bgn),
                static_cast<int>(c.gemm.uop_end));
@@ -662,6 +654,7 @@ class InsnQueue : public BaseQueue {
                static_cast<int>(c.mem.pop_next_dep),
                static_cast<int>(c.mem.push_prev_dep),
                static_cast<int>(c.mem.push_next_dep));
+        printf("\treset_out: %d\n", static_cast<int>(c.alu.reset_reg));
         printf("\trange (%d, %d)\n",
                static_cast<int>(c.alu.uop_bgn),
                static_cast<int>(c.alu.uop_end));
@@ -1081,6 +1074,7 @@ class CommandQueue {
     }
     VTAGemInsn* insn = insn_queue_.CreateGemInsn();
     insn->opcode = VTA_OPCODE_GEMM;
+    insn->reset_reg = kernel->reset_out_;
     insn->uop_bgn = kernel->sram_begin_;
     insn->uop_end = kernel->sram_end_;
     const std::vector<UopKernel::LoopEntry> &loop = kernel->loop();
@@ -1119,6 +1113,7 @@ class CommandQueue {
     }
     VTAAluInsn* insn = insn_queue_.CreateAluInsn();
     insn->opcode = VTA_OPCODE_ALU;
+    insn->reset_reg = kernel->reset_out_;
     insn->uop_bgn = kernel->sram_begin_;
     insn->uop_end = kernel->sram_end_;
     insn->alu_opcode = kernel->opcode_;
diff --git a/vta/tests/hardware/common/test_lib.cc b/vta/tests/hardware/common/test_lib.cc
index 7f46a43b1867..ab4e8fcc4218 100644
--- a/vta/tests/hardware/common/test_lib.cc
+++ b/vta/tests/hardware/common/test_lib.cc
@@ -28,20 +28,6 @@ const char* getOpcodeString(int opcode, bool use_imm) {
     } else {
       return "add";
     }
-  } else if (opcode == VTA_ALU_OPCODE_SUB) {
-    if (use_imm) {
-      return "sub imm";
-    } else {
-      return "sub";
-    }
-  } else if (opcode == VTA_ALU_OPCODE_MUL) {
-    if (use_imm) {
-      return "mul imm";
-    } else {
-      return "mul";
-    }
-  } else if (opcode == VTA_ALU_OPCODE_SHL) {
-    return "shl";
   } else if (opcode == VTA_ALU_OPCODE_SHR) {
     return "shr";
   }
@@ -170,31 +156,6 @@ void freeBuffer(void * buffer) {
 #endif
 }
 
-VTAGenericInsn reset2DInsn(int type, int sram_offset, int y_size, int x_size, int x_stride,
-    int pop_prev_dep, int pop_next_dep, int push_prev_dep, int push_next_dep) {
-  // Converter
-  union VTAInsn converter;
-  // Memory instruction initialization
-  VTAMemInsn insn = {};
-  insn.opcode = VTA_OPCODE_LOAD;
-  insn.pop_prev_dep = pop_prev_dep;
-  insn.pop_next_dep = pop_next_dep;
-  insn.push_prev_dep = push_prev_dep;
-  insn.push_next_dep = push_next_dep;
-  insn.memory_type = type;
-  insn.sram_base = sram_offset;
-  insn.dram_base = 0;
-  insn.y_size = 0;
-  insn.x_size = x_size;
-  insn.x_stride = x_stride;
-  insn.y_pad_0 = y_size;
-  insn.y_pad_1 = 0;
-  insn.x_pad_0 = 0;
-  insn.x_pad_1 = 0;
-  converter.mem = insn;
-  return converter.generic;
-}
-
 VTAGenericInsn get2DLoadStoreInsn(int opcode, int type, int sram_offset, int dram_offset,
     int y_size, int x_size, int x_stride, int y_pad, int x_pad, int pop_prev_dep, int pop_next_dep,
     int push_prev_dep, int push_next_dep) {
@@ -258,6 +219,7 @@ VTAGenericInsn getGEMMInsn(int uop_offset, int batch, int in_feat, int out_feat,
   insn.pop_next_dep = pop_next_dep;
   insn.push_prev_dep = push_prev_dep;
   insn.push_next_dep = push_next_dep;
+  insn.reset_reg = false;
   if (!uop_compression) {
     insn.uop_bgn = uop_offset;
     insn.uop_end = uop_offset + batch * in_feat * out_feat;
@@ -296,6 +258,7 @@ VTAGenericInsn getALUInsn(int opcode, int vector_size, bool use_imm, int imm, bo
   insn.pop_next_dep = pop_next_dep;
   insn.push_prev_dep = push_prev_dep;
   insn.push_next_dep = push_next_dep;
+  insn.reset_reg = false;
   if (!uop_compression) {
     insn.uop_bgn = 0;
     insn.uop_end = vector_size;
@@ -335,6 +298,7 @@ VTAGenericInsn getFinishInsn(bool pop_prev, bool pop_next) {
   insn.pop_next_dep = pop_next;
   insn.push_prev_dep = 0;
   insn.push_next_dep = 0;
+  insn.reset_reg = false;
   insn.uop_bgn = 0;
   insn.uop_end = 0;
   insn.iter_out = 0;
@@ -364,7 +328,6 @@ VTAUop * getCopyUops(int y_size, int x_size, int uop_compression) {
     int uop_idx = 0;
     for (int i = 0; i < y_size; i++) {
       for (int j = 0; j < x_size; j++) {
-        uop_buf[uop_idx].reset_out = false;
         uop_buf[uop_idx].dst_idx = i * x_size + j;
         uop_buf[uop_idx].src_idx = 0;
         uop_buf[uop_idx].wgt_idx = 0;
@@ -372,7 +335,6 @@ VTAUop * getCopyUops(int y_size, int x_size, int uop_compression) {
       }
     }
   } else {
-    uop_buf[0].reset_out = false;
     uop_buf[0].dst_idx = 1;
     uop_buf[0].src_idx = 0;
     uop_buf[0].wgt_idx = 0;
@@ -399,7 +361,6 @@ VTAUop * getGEMMUops(int batch, int in_feat, int out_feat, bool uop_compression,
     for (int i = 0; i < batch; i++) {
       for (int j = 0; j < in_feat; j++) {
         for (int k = 0; k < out_feat; k++) {
-          uop_buf[uop_idx].reset_out = false;
           uop_buf[uop_idx].dst_idx = i * out_feat + k;
           uop_buf[uop_idx].src_idx = i * in_feat + j;
           uop_buf[uop_idx].wgt_idx = k * in_feat + j;
@@ -409,7 +370,6 @@ VTAUop * getGEMMUops(int batch, int in_feat, int out_feat, bool uop_compression,
     }
   } else {
     for (int i = 0; i < batch; i++) {
-      uop_buf[i].reset_out = false;
       uop_buf[i].dst_idx = i * out_feat;
       uop_buf[i].src_idx = i * in_feat;
       uop_buf[i].wgt_idx = 0;
@@ -422,7 +382,6 @@ VTAUop * getGEMMUops(int batch, int in_feat, int out_feat, bool uop_compression,
       for (int i = 0; i < batch; i++) {
         for (int j = 0; j < in_feat; j++) {
           for (int k = 0; k < out_feat; k++) {
-            uop_buf[uop_idx].reset_out = false;
             uop_buf[uop_idx].dst_idx = i * out_feat + k;
             uop_buf[uop_idx].src_idx = batch * in_feat + i * in_feat + j;
             uop_buf[uop_idx].wgt_idx = out_feat * in_feat + k * in_feat + j;
@@ -432,7 +391,6 @@ VTAUop * getGEMMUops(int batch, int in_feat, int out_feat, bool uop_compression,
       }
     } else {
       for (int i = 0; i < batch; i++) {
-        uop_buf[batch+i].reset_out = false;
         uop_buf[batch+i].dst_idx = i * out_feat;
         uop_buf[batch+i].src_idx = batch * in_feat + i * in_feat;
         uop_buf[batch+i].wgt_idx = out_feat * in_feat;
@@ -456,12 +414,10 @@ VTAUop * getMapALUUops(int vector_size, bool uop_compression) {
 
   if (!uop_compression) {
     for (int i = 0; i < vector_size; i++) {
-      uop_buf[i].reset_out = 0;
       uop_buf[i].dst_idx = i;
       uop_buf[i].src_idx = vector_size + i;
     }
   } else {
-    uop_buf[0].reset_out = 0;
     uop_buf[0].dst_idx = 0;
     uop_buf[0].src_idx = vector_size;
   }
@@ -511,7 +467,7 @@ void printParameters() {
   printf("VTA_INSN_GEM_2 [%d]\n", VTA_INSN_GEM_2);
   printf("VTA_INSN_GEM_3 [%d]\n", VTA_INSN_GEM_3);
   printf("VTA_INSN_GEM_4 [%d]\n", VTA_INSN_GEM_4);
-  printf("VTA_INSN_GEM_5 [%d-%d]\n", VTA_INSN_GEM_5_0, VTA_INSN_GEM_5_1);
+  printf("VTA_INSN_GEM_5 [%d]\n", VTA_INSN_GEM_5);
   printf("VTA_INSN_GEM_6 [%d-%d]\n", VTA_INSN_GEM_6_0, VTA_INSN_GEM_6_1);
   printf("VTA_INSN_GEM_7 [%d-%d]\n", VTA_INSN_GEM_7_0, VTA_INSN_GEM_7_1);
   printf("VTA_INSN_GEM_8 [%d-%d]\n", VTA_INSN_GEM_8_0, VTA_INSN_GEM_8_1);
@@ -521,17 +477,15 @@ void printParameters() {
   printf("VTA_INSN_GEM_C [%d-%d]\n", VTA_INSN_GEM_C_0, VTA_INSN_GEM_C_1);
   printf("VTA_INSN_GEM_D [%d-%d]\n", VTA_INSN_GEM_D_0, VTA_INSN_GEM_D_1);
   printf("VTA_INSN_GEM_E [%d-%d]\n", VTA_INSN_GEM_E_0, VTA_INSN_GEM_E_1);
-  printf("VTA_INSN_ALU_D [%d-%d]\n", VTA_INSN_ALU_D_0, VTA_INSN_ALU_D_1);
-  printf("VTA_INSN_ALU_E [%d]\n", VTA_INSN_ALU_E);
-  printf("VTA_INSN_ALU_F [%d-%d]\n", VTA_INSN_ALU_F_0, VTA_INSN_ALU_F_1);
-  printf("VTA_UOP_GEM_0 [%d]\n", VTA_UOP_GEM_0);
+  printf("VTA_INSN_GEM_F [%d-%d]\n", VTA_INSN_GEM_F_0, VTA_INSN_GEM_F_1);
+  printf("VTA_INSN_ALU_E [%d-%d]\n", VTA_INSN_ALU_E_0, VTA_INSN_ALU_E_1);
+  printf("VTA_INSN_ALU_F [%d]\n", VTA_INSN_ALU_F);
+  printf("VTA_INSN_ALU_G [%d-%d]\n", VTA_INSN_ALU_G_0, VTA_INSN_ALU_G_1);
+  printf("VTA_UOP_GEM_0 [%d-%d]\n", VTA_UOP_GEM_0_0, VTA_UOP_GEM_0_1);
   printf("VTA_UOP_GEM_1 [%d-%d]\n", VTA_UOP_GEM_1_0, VTA_UOP_GEM_1_1);
   printf("VTA_UOP_GEM_2 [%d-%d]\n", VTA_UOP_GEM_2_0, VTA_UOP_GEM_2_1);
-  printf("VTA_UOP_GEM_3 [%d-%d]\n", VTA_UOP_GEM_3_0, VTA_UOP_GEM_3_1);
-  printf("VTA_UOP_ALU_0 [%d]\n", VTA_UOP_ALU_0);
+  printf("VTA_UOP_ALU_0 [%d-%d]\n", VTA_UOP_ALU_0_0, VTA_UOP_ALU_0_1);
   printf("VTA_UOP_ALU_1 [%d-%d]\n", VTA_UOP_ALU_1_0, VTA_UOP_ALU_1_1);
-  printf("VTA_UOP_ALU_2 [%d-%d]\n", VTA_UOP_ALU_2_0, VTA_UOP_ALU_2_1);
-  printf("VTA_UOP_ALU_3 [%d-%d]\n", VTA_UOP_ALU_3_0, VTA_UOP_ALU_3_1);
 }
 
 void printInstruction(int num_insn, VTAGenericInsn *insns) {
@@ -601,6 +555,7 @@ void printInstruction(int num_insn, VTAGenericInsn *insns) {
       printf("\trange (%d, %d)\n",
              static_cast<int>(c.gemm.uop_bgn),
              static_cast<int>(c.gemm.uop_end));
+      printf("\treset_out: %d\n", static_cast<int>(c.gemm.reset_reg));
       printf("\touter loop - iter: %d, acc: %d, inp: %d, wgt: %d\n",
              static_cast<int>(c.gemm.iter_out),
              static_cast<int>(c.gemm.dst_factor_out),
@@ -634,6 +589,7 @@ void printInstruction(int num_insn, VTAGenericInsn *insns) {
              static_cast<int>(c.mem.pop_next_dep),
              static_cast<int>(c.mem.push_prev_dep),
              static_cast<int>(c.mem.push_next_dep));
+      printf("\treset_out: %d\n", static_cast<int>(c.alu.reset_reg));
       printf("\trange (%d, %d)\n",
              static_cast<int>(c.alu.uop_bgn),
              static_cast<int>(c.alu.uop_end));
@@ -662,8 +618,7 @@ void printMicroOp(int num_uop, VTAUop *uops) {
   for (int i = 0; i < num_uop; i++) {
     // Read micro-op
     printf("DEBUG - UOP %u: ", i);
-    printf("rst_out=%u, acc=%u, inp= %u, wgt=%u\n", uops[i].reset_out, uops[i].dst_idx,
-        uops[i].src_idx, uops[i].wgt_idx);
+    printf("acc=%u, inp= %u, wgt=%u\n", uops[i].dst_idx, uops[i].src_idx, uops[i].wgt_idx);
   }
 }
 
@@ -671,7 +626,6 @@ int alu_test(int opcode, bool use_imm, int batch, int vector_size, bool uop_comp
   // Some assertions
   assert(batch % VTA_BATCH == 0);
   assert(vector_size % VTA_BLOCK_OUT == 0);
-  assert(!(opcode == VTA_ALU_OPCODE_SHL && !use_imm));
   assert(!(opcode == VTA_ALU_OPCODE_SHR && !use_imm));
   printf("=====================================================================================\n");
   printf("INFO - ALU test of %s: batch=%d, vector_size=%d, uop_compression=%d\n",
@@ -701,16 +655,9 @@ int alu_test(int opcode, bool use_imm, int batch, int vector_size, bool uop_comp
     } else if (opcode == VTA_ALU_OPCODE_ADD) {
       immediate[b] = static_cast<acc_T>(
           rand_r(&globalSeed) % (1LL << (VTA_INP_WIDTH / 2)) - (1LL << (VTA_INP_WIDTH / 2 - 1)));
-    } else if (opcode == VTA_ALU_OPCODE_SUB) {
-      immediate[b] = static_cast<acc_T>(
-          rand_r(&globalSeed) % (1LL << (VTA_INP_WIDTH / 2)) - (1LL << (VTA_INP_WIDTH / 2 - 1)));
-    } else if (opcode == VTA_ALU_OPCODE_MUL) {
-      immediate[b] = static_cast<acc_T>(
-          rand_r(&globalSeed) % (1LL << (VTA_INP_WIDTH / 2)) - (1LL << (VTA_INP_WIDTH / 2 - 1)));
-    } else if (opcode == VTA_ALU_OPCODE_SHL) {
-      immediate[b] = static_cast<acc_T>(rand_r(&globalSeed) % (VTA_INP_WIDTH + 1));
     } else if (opcode == VTA_ALU_OPCODE_SHR) {
-      immediate[b] = static_cast<acc_T>(rand_r(&globalSeed) % (VTA_INP_WIDTH + 1));
+      immediate[b] = static_cast<acc_T>(
+          rand_r(&globalSeed) % VTA_ACC_WIDTH - VTA_ACC_WIDTH/2);
     }
   }
 
@@ -783,18 +730,6 @@ int alu_test(int opcode, bool use_imm, int batch, int vector_size, bool uop_comp
       } else if (opcode == VTA_ALU_OPCODE_ADD) {
         inputs[i][j] = static_cast<acc_T>(
             rand_r(&globalSeed) % (1LL << (VTA_INP_WIDTH - 1)) - (1LL << (VTA_INP_WIDTH - 2)));
-      } else if (opcode == VTA_ALU_OPCODE_SUB) {
-        inputs[i][j] = static_cast<acc_T>(
-            rand_r(&globalSeed) % (1LL << (VTA_INP_WIDTH - 1)) - (1LL << (VTA_INP_WIDTH - 2)));
-      } else if (opcode == VTA_ALU_OPCODE_MUL) {
-        inputs[i][j] = static_cast<acc_T>(
-            rand_r(&globalSeed) % (1LL << (VTA_INP_WIDTH / 2)) - (1LL << (VTA_INP_WIDTH / 2 - 1)));
-      } else if (opcode == VTA_ALU_OPCODE_SHL) {
-        inputs[i][j] = static_cast<acc_T>(
-            rand_r(&globalSeed) % (1LL << (VTA_INP_WIDTH - 1)) - (1LL << (VTA_INP_WIDTH - 2)));
-      } else if (opcode == VTA_ALU_OPCODE_SHR) {
-        inputs[i][j] = static_cast<acc_T>(
-            rand_r(&globalSeed) % (1LL << (VTA_INP_WIDTH - 1)) - (1LL << (VTA_INP_WIDTH - 2)));
       }
     }
   }
@@ -830,22 +765,12 @@ int alu_test(int opcode, bool use_imm, int batch, int vector_size, bool uop_comp
         } else {
           tmp = inputs[i][j] + immediate[i / VTA_BATCH];
         }
-      } else if (opcode == VTA_ALU_OPCODE_SUB) {
-        if (!use_imm) {
-          tmp = inputs[i][j] - inputs[i][j + vector_size];
-        } else {
-          tmp = inputs[i][j] - immediate[i / VTA_BATCH];
-        }
-      } else if (opcode == VTA_ALU_OPCODE_MUL) {
-        if (!use_imm) {
-          tmp = inputs[i][j] * inputs[i][j + vector_size];
+      } else if (opcode == VTA_ALU_OPCODE_SHR) {
+        if (immediate[i / VTA_BATCH] >= 0) {
+          tmp = inputs[i][j] >> immediate[i / VTA_BATCH];
         } else {
-          tmp = inputs[i][j] * immediate[i / VTA_BATCH];
+          tmp = inputs[i][j] << (0 - immediate[i / VTA_BATCH]);
         }
-      } else if (opcode == VTA_ALU_OPCODE_SHL) {
-        tmp = inputs[i][j] << immediate[i / VTA_BATCH];
-      } else if (opcode == VTA_ALU_OPCODE_SHR) {
-        tmp = inputs[i][j] >> immediate[i / VTA_BATCH];
       }
       // Set
       outputs_ref[i][j] = (out_T) tmp;
@@ -876,7 +801,7 @@ int alu_test(int opcode, bool use_imm, int batch, int vector_size, bool uop_comp
       (volatile inp_vec_T *) NULL,
       (volatile wgt_vec_T *) NULL,
       (volatile acc_vec_T *) bias_buf,
-      (volatile inp_vec_T *) output_buf);
+      (volatile out_vec_T *) output_buf);
 #endif
 
   // Unpack output buffer
@@ -1149,7 +1074,7 @@ int blocked_gemm_test(int batch, int channels, int block, bool uop_compression,
       (volatile inp_vec_T *) input_buf,
       (volatile wgt_vec_T *) weight_buf,
       (volatile acc_vec_T *) bias_buf,
-      (volatile inp_vec_T *) output_buf);
+      (volatile out_vec_T *) output_buf);
 #endif
 
   // Unpack output data
diff --git a/vta/tests/python/pynq/test_benchmark_conv2d.py b/vta/tests/python/pynq/test_benchmark_conv2d.py
index 1ffb3e73de64..c4dfe01d7508 100644
--- a/vta/tests/python/pynq/test_benchmark_conv2d.py
+++ b/vta/tests/python/pynq/test_benchmark_conv2d.py
@@ -16,18 +16,20 @@
 wgt_dtype = "int%d" % vta.VTA_WGT_WIDTH
 
 Workload = namedtuple("Conv2DWorkload",
-                      ['height', 'width', 'in_filter', 'out_filter',
+                      ['batch', 'height', 'width', 'in_filter', 'out_filter',
                        'hkernel', 'wkernel', 'hpad', 'wpad', 'hstride', 'wstride'])
 
 class Conv2DSchedule(object):
     def __init__(self,
-                 oc_factor,
+                 b_factor=1,
+                 oc_factor=1,
                  ko_factor=1,
                  h_factor=1,
                  w_factor=0,
                  oc_nthread=0,
                  h_nthread=0,
                  debug_sync=False):
+        self.b_factor = b_factor
         self.oc_factor = oc_factor
         self.ko_factor = ko_factor
         self.h_factor = h_factor
@@ -35,10 +37,140 @@ def __init__(self,
         self.oc_nthread = oc_nthread
         self.h_nthread = h_nthread
         self.debug_sync = debug_sync
+    def __str__(self):
+        return "{}.{}.{}.{}.{}.{}.{}".format(
+            self.b_factor, self.oc_factor, self.ko_factor,
+            self.h_factor, self.w_factor,
+            self.oc_nthread, self.h_nthread)
 
 Schedule = Conv2DSchedule
 
-def test_conv2d_chwv(key, batch_size, wl, plan, log_frame, profile=True):
+def get_insn_count(layer, sched):
+    b, h, w, ci, co = sched
+    b_factor = b
+    h_factor = layer.height / h
+    w_factor = layer.width / w
+    ci_factor = int(np.ceil(float(layer.in_filter) / (ci * vta.VTA_BLOCK_IN)))
+    co_factor = int(np.ceil(float(layer.out_filter) / (co * vta.VTA_BLOCK_OUT)))
+    input_xfers = b_factor * h_factor * w_factor * co_factor * ci_factor
+    weight_xfers = b_factor * h_factor * w_factor * co_factor * ci_factor
+    output_xfers = b_factor * h_factor * w_factor * co_factor
+    # compute instruction count
+    # output transfer factor: 4 (INIT, GEMM, ALU, STORE)
+    # offset: 5 (3 uop kernels, 1 initial dep push, 1 finish, co_factor)
+    insn_count = input_xfers + weight_xfers + (output_xfers * 4) + 5 + co_factor
+    return insn_count
+
+def find_schedules(layer, mtOnly=False, bestOnly=False):
+    # Helper function to get factors
+    def find_factors(n):
+        factors = []
+        for i in range(1, n+1):
+            if n % i == 0:
+                factors.append(i)
+        return factors
+    # Scheduling exploration
+    batch_factors = find_factors(int(np.ceil(float(layer.batch) / vta.VTA_BATCH)))
+    height_factors = find_factors(layer.height / layer.hstride)
+    width_factors = find_factors(layer.width / layer.wstride)
+    cin_factors = find_factors(int(np.ceil(float(layer.in_filter) / vta.VTA_BLOCK_IN)))
+    cout_factors = find_factors(int(np.ceil(float(layer.out_filter) / vta.VTA_BLOCK_OUT)))
+    ht_factors = [1, 2]
+    cot_factors = [1, 2]
+    # Explore schedules
+    schedules = []
+    for b in batch_factors:
+        for h in height_factors:
+            for w in width_factors:
+                for ci in cin_factors:
+                    for co in cout_factors:
+                        # FIXME: filter because of 2D load
+                        if w == layer.width/layer.wstride or (w != layer.width/layer.wstride and co == 1):
+                            # FIXME: filter because of 2D load
+                            if ci == 1:
+                                schedules.append([b, h, w, ci, co])
+    # Filter the schedules that wouldn't work in the available BRAM sizes
+    input_elem_size_b = vta.VTA_BATCH * vta.VTA_BLOCK_IN * vta.VTA_INP_WIDTH
+    weight_elem_size_b = vta.VTA_BLOCK_IN * vta.VTA_BLOCK_OUT * vta.VTA_WGT_WIDTH
+    output_elem_size_b = vta.VTA_BATCH * vta.VTA_BLOCK_OUT * vta.VTA_OUT_WIDTH
+    input_brams_capacity_b = vta.VTA_INP_BUFF_SIZE * 8
+    weight_brams_capacity_b = vta.VTA_WGT_BUFF_SIZE * 8
+    output_brams_capacity_b = vta.VTA_OUT_BUFF_SIZE * 8
+    fil_sched = []
+    xfer_size = []
+    for sched in schedules:
+        b, h, w, ci, co = sched
+        for ht in [1, 2]:
+            for cot in [1, 2]:
+                # Make sure to filter cases where we apply threading on two axes
+                # or cases where the threading factors for h and co are not
+                # factors of h and co
+                if not (ht == 2 and cot == 2) and h % ht == 0 and co % cot == 0:
+                    # If in multi-threaded mode, only allow for mt configs:
+                    if (mtOnly and (ht == 2 or cot == 2)) or not mtOnly:
+                        h /= ht
+                        co /= cot
+                        input_tile_elems = b * \
+                                ((h - 1) * layer.hstride + layer.hkernel) * \
+                                ((w - 1) * layer.wstride + layer.wkernel) * ci
+                        weight_tile_elems = layer.hkernel * layer.wkernel * ci * co
+                        output_tile_elems = b * h * w * co
+                        insn_count = get_insn_count(layer, sched)
+                        # 1. Check input capacity
+                        # 2. Check weight capacity
+                        # 3. Check output capacity
+                        # 4. Check instruction capacity
+                        # 5. Make sure that we don't write to the same acc location
+                        #    within 2 consecutive cycles
+                        if input_tile_elems*input_elem_size_b <= input_brams_capacity_b/(cot*ht) and \
+                           weight_tile_elems*weight_elem_size_b <= weight_brams_capacity_b and \
+                           output_tile_elems*output_elem_size_b <= output_brams_capacity_b/(cot*ht) and \
+                           insn_count <= vta.VTA_MAX_XFER / 16 and \
+                           h > 2 and w > 2:
+                            schedule = Schedule(oc_factor=co, ko_factor=ci, h_factor=h,
+                                                w_factor=w, oc_nthread=cot, h_nthread=ht)
+                            fil_sched.append(schedule)
+                            xfer_size.append(get_data_movementB(schedule, layer))
+    if bestOnly:
+        return [fil_sched[xfer_size.index(min(xfer_size))]]
+    else:
+        return fil_sched
+
+def get_data_movementB(sched, layer):
+    # Derive data movement
+    input_elem_size_b = vta.VTA_BATCH * vta.VTA_BLOCK_IN * vta.VTA_INP_WIDTH
+    weight_elem_size_b = vta.VTA_BLOCK_IN * vta.VTA_BLOCK_OUT * vta.VTA_WGT_WIDTH
+    output_elem_size_b = vta.VTA_BATCH * vta.VTA_BLOCK_OUT * vta.VTA_OUT_WIDTH
+    b = sched.b_factor
+    h = sched.h_factor
+    w = sched.w_factor
+    ci = sched.ko_factor
+    co = sched.oc_factor
+    ht = sched.h_nthread
+    cot = sched.oc_nthread
+    input_tile_elems = b * \
+            ((h - 1) * layer.hstride + layer.hkernel) * \
+            ((w - 1) * layer.wstride + layer.wkernel) * ci
+    weight_tile_elems = layer.hkernel * layer.wkernel * ci
+    output_tile_elems = b * h * w * co
+    # Derive factors
+    b_factor = int(np.ceil(float(layer.batch) / (b * vta.VTA_BATCH)))
+    h_factor = (layer.height / layer.hstride) / h
+    w_factor = (layer.width / layer.wstride) / w
+    ci_factor = int(np.ceil(float(layer.in_filter) / (ci * vta.VTA_BLOCK_IN)))
+    co_factor = int(np.ceil(float(layer.out_filter) / (co * vta.VTA_BLOCK_OUT)))
+    # Derive transfers
+    input_xfers = b_factor * h_factor * w_factor * co_factor * ci_factor
+    weight_xfers = b_factor * h_factor * w_factor * co_factor * ci_factor
+    output_xfers = b_factor * h_factor * w_factor * co_factor
+    # Compute total transfer sizes
+    input_xfer_B = input_tile_elems * input_xfers * input_elem_size_b / 8
+    weight_xfer_B = weight_tile_elems * weight_xfers * weight_elem_size_b / 8
+    output_xfer_B = output_tile_elems * output_xfers * output_elem_size_b / 8
+    total_xfer_B = input_xfer_B + weight_xfer_B + output_xfer_B
+    return total_xfer_B
+
+def test_conv2d_chwv(layer, key, batch_size, wl, sched, log_frame, profile=True):
     assert batch_size % vta.VTA_BATCH == 0
     assert wl.in_filter % vta.VTA_BLOCK_IN == 0
     assert wl.out_filter % vta.VTA_BLOCK_OUT == 0
@@ -71,6 +203,7 @@ def test_conv2d_chwv(key, batch_size, wl, plan, log_frame, profile=True):
     res_shf = tvm.compute(res_shape, lambda *i: res_cnv(*i) >> 8, name="res_shf")
     res = tvm.compute(res_shape, lambda *i: res_shf(*i).astype(inp_dtype), name="res")
     num_ops = batch_size * fout_height * fout_width * wl.hkernel * wl.wkernel * wl.out_filter * wl.in_filter
+    total_xfer_B = get_data_movementB(sched, wl)
 
     def verify(s, check_correctness):
         mod = tvm.build(s, [data, kernel, res], "ext_dev", target, name="conv2d")
@@ -98,7 +231,7 @@ def verify(s, check_correctness):
         data_arr = tvm.nd.array(data_packed, ctx)
         kernel_arr = tvm.nd.array(kernel_packed, ctx)
         res_arr = tvm.nd.array(res_np, ctx)
-        time_f = f.time_evaluator("conv2d", ctx, number=10)
+        time_f = f.time_evaluator("conv2d", ctx, number=1)
         cost = time_f(data_arr, kernel_arr, res_arr)
         res_unpack = res_arr.asnumpy().transpose(
             (0, 4, 1, 5, 2, 3)).reshape(batch_size, wl.out_filter, fout_height, fout_width)
@@ -125,10 +258,10 @@ def run_schedule(load_inp, load_wgt, gemm, alu, store_out,
         s[res_cnv].set_scope(vta.SCOPE_OUT)
         s[res_shf].set_scope(vta.SCOPE_OUT)
         # tile
-        oc_factor = (plan.oc_factor if plan.oc_factor
+        oc_factor = (sched.oc_factor if sched.oc_factor
                      else wl.out_filter // vta.VTA_BLOCK_OUT)
-        h_factor = (plan.h_factor if plan.h_factor else fout_height)
-        w_factor = (plan.w_factor if plan.w_factor else fout_width)
+        h_factor = (sched.h_factor if sched.h_factor else fout_height)
+        w_factor = (sched.w_factor if sched.w_factor else fout_width)
         xbo, xco, xi, xj, xbi, xci = s[res].op.axis
         xco0, xco1 = s[res].split(xco, factor=oc_factor)
         xi0, xi1 = s[res].split(xi, factor=h_factor)
@@ -137,21 +270,21 @@ def run_schedule(load_inp, load_wgt, gemm, alu, store_out,
         s[res_cnv].compute_at(s[res], xj0)
         s[res_shf].compute_at(s[res], xj0)
 
-        if plan.oc_nthread:
-            _, tx = s[res].split(xco0, factor=plan.oc_nthread)
+        if sched.oc_nthread > 1:
+            _, tx = s[res].split(xco0, factor=sched.oc_nthread)
             s[res].reorder(tx, xbo)
             s[res].bind(tx, tvm.thread_axis("cthread"))
 
-        if plan.h_nthread:
-            xo, tx = s[res].split(xi0, factor=plan.h_nthread)
+        if sched.h_nthread > 1:
+            xo, tx = s[res].split(xi0, factor=sched.h_nthread)
             s[res].reorder(tx, xbo)
             s[res].bind(tx, tvm.thread_axis("cthread"))
 
         xbo, xco, xi, xj, xbi, xci = s[res_cnv].op.axis
         s[res_cnv].reorder(xbo, ko, xj, dj, di, xco, xi, xbi, xci, ki)
 
-        if plan.ko_factor:
-            ko0, ko1 = s[res_cnv].split(ko, factor=plan.ko_factor)
+        if sched.ko_factor:
+            ko0, ko1 = s[res_cnv].split(ko, factor=sched.ko_factor)
             s[data_buf].compute_at(s[res_cnv], ko0)
             s[kernel_buf].compute_at(s[res_cnv], ko0)
         # Use VTA instructions
@@ -160,7 +293,7 @@ def run_schedule(load_inp, load_wgt, gemm, alu, store_out,
         s[res_cnv].tensorize(xbi, gemm)
         s[res_shf].pragma(s[res_shf].op.axis[0], alu)
         s[res].pragma(xco1, store_out)
-        if plan.debug_sync:
+        if sched.debug_sync:
             s[res].pragma(xco0, "coproc_sync")
         if print_ir:
             print(tvm.lower(s, [data, kernel, res], simple_mode=True))
@@ -169,6 +302,7 @@ def run_schedule(load_inp, load_wgt, gemm, alu, store_out,
     def conv_normal(print_ir):
         print("----- CONV2D End-to-End Test-------")
         def run_test(header, print_ir, check_correctness):
+            s = [1, sched.oc_factor, sched.ko_factor, sched.h_factor, sched.w_factor]
             cost = run_schedule(
                 vta.DMA_COPY, vta.DMA_COPY,
                 vta.GEMM, vta.ALU, vta.DMA_COPY,
@@ -177,8 +311,27 @@ def run_test(header, print_ir, check_correctness):
             print(header)
             print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
             log_frame["key"].append(key)
+            log_frame["layer"].append(layer)
+            log_frame["total-data"].append(total_xfer_B)
             log_frame["total-gops"].append(gops)
             log_frame["total-cost"].append(cost.mean)
+            log_frame["total-insn"].append(get_insn_count(wl, s))
+            log_frame["block-batch"].append(vta.VTA_BATCH)
+            log_frame["block-in"].append(vta.VTA_BLOCK_IN)
+            log_frame["block-out"].append(vta.VTA_BLOCK_OUT)
+            log_frame["inp-width"].append(vta.VTA_INP_WIDTH)
+            log_frame["wgt-width"].append(vta.VTA_WGT_WIDTH)
+            log_frame["uop-size"].append(vta.VTA_UOP_BUFF_SIZE)
+            log_frame["inp-size"].append(vta.VTA_INP_BUFF_SIZE)
+            log_frame["wgt-size"].append(vta.VTA_WGT_BUFF_SIZE)
+            log_frame["out-size"].append(vta.VTA_OUT_BUFF_SIZE)
+            log_frame["oc-factor"].append(sched.oc_factor)
+            log_frame["ic-factor"].append(sched.ko_factor)
+            log_frame["h-factor"].append(sched.h_factor)
+            log_frame["w-factor"].append(sched.w_factor)
+            log_frame["oc-threads"].append(sched.oc_nthread)
+            log_frame["h-threads"].append(sched.h_nthread)
+            log_frame["threaded"].append(True if sched.oc_nthread > 1 or sched.h_nthread > 1 else False)
 
         with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
             run_test("NORMAL", print_ir, True)
@@ -325,90 +478,63 @@ def run_test(header, print_ir):
     load_wgt_unittest(False)
     store_out_unittest(False)
 
+# Perform profiling
+profile = False
+# Data set batch size
+batch_size = 1
+# Use multi-threading for latency hiding
+multi_threaded = False
+
 # ResNet18 workloads
 resnet = {
     # Workloads of resnet18 on imagenet
-    0: Workload(224, 224, 16, 64, 7, 7, 3, 3, 2, 2),
-    1: Workload(56, 56, 64, 64, 3, 3, 1, 1, 1, 1),
-    2: Workload(56, 56, 64, 64, 1, 1, 0, 0, 1, 1),
-    3: Workload(56, 56, 64, 128, 3, 3, 1, 1, 2, 2),
-    4: Workload(56, 56, 64, 128, 1, 1, 0, 0, 2, 2),
-    5: Workload(28, 28, 128, 128, 3, 3, 1, 1, 1, 1),
-    6: Workload(28, 28, 128, 256, 3, 3, 1, 1, 2, 2),
-    7: Workload(28, 28, 128, 256, 1, 1, 0, 0, 2, 2),
-    8: Workload(14, 14, 256, 256, 3, 3, 1, 1, 1, 1),
-    9: Workload(14, 14, 256, 512, 3, 3, 1, 1, 2, 2),
-    10: Workload(14, 14, 256, 512, 1, 1, 0, 0, 2, 2),
-    11: Workload(7, 7, 512, 512, 3, 3, 1, 1, 1, 1),
+    0: Workload(1, 224, 224, 16, 64, 7, 7, 3, 3, 2, 2),
+    1: Workload(1, 56, 56, 64, 64, 3, 3, 1, 1, 1, 1),
+    2: Workload(1, 56, 56, 64, 64, 1, 1, 0, 0, 1, 1),
+    3: Workload(1, 56, 56, 64, 128, 3, 3, 1, 1, 2, 2),
+    4: Workload(1, 56, 56, 64, 128, 1, 1, 0, 0, 2, 2),
+    5: Workload(1, 28, 28, 128, 128, 3, 3, 1, 1, 1, 1),
+    6: Workload(1, 28, 28, 128, 256, 3, 3, 1, 1, 2, 2),
+    7: Workload(1, 28, 28, 128, 256, 1, 1, 0, 0, 2, 2),
+    8: Workload(1, 14, 14, 256, 256, 3, 3, 1, 1, 1, 1),
+    9: Workload(1, 14, 14, 256, 512, 3, 3, 1, 1, 2, 2),
+    10: Workload(1, 14, 14, 256, 512, 1, 1, 0, 0, 2, 2),
+    11: Workload(1, 7, 7, 512, 512, 3, 3, 1, 1, 1, 1),
 }
 
-# List of simple benchmarks
-simple = [
-    Workload(height=22, width=22, in_filter=256, out_filter=64,
-             hkernel=3, wkernel=3, hpad=1, wpad=1, hstride=1, wstride=1)
-]
-
-# Serial schedule
-resnet_serial = [
-    [None, None],
-    [resnet[1], Schedule(oc_factor=2, ko_factor=1, h_factor=14, w_factor=0)],
-    [resnet[2], Schedule(oc_factor=4, ko_factor=4, h_factor=8, w_factor=0)],
-    [resnet[3], Schedule(oc_factor=4, ko_factor=1, h_factor=14, w_factor=0)],
-    [resnet[4], Schedule(oc_factor=8, ko_factor=1, h_factor=4, w_factor=0)],
-    [resnet[5], Schedule(oc_factor=8, ko_factor=1, h_factor=7, w_factor=0)],
-    [resnet[6], Schedule(oc_factor=8, ko_factor=1, h_factor=14, w_factor=0)],
-    [resnet[7], Schedule(oc_factor=16, ko_factor=1, h_factor=7, w_factor=0)],
-    [resnet[8], Schedule(oc_factor=8, ko_factor=1, h_factor=7, w_factor=0)],
-    [resnet[9], Schedule(oc_factor=8, ko_factor=1, h_factor=7, w_factor=0)],
-    [resnet[10], Schedule(oc_factor=16, ko_factor=1, h_factor=7, w_factor=0)],
-    [resnet[11], Schedule(oc_factor=8, ko_factor=1, h_factor=7, w_factor=0)],
-]
-
-# SMT schedule
-resnet_smt = [
-    [resnet[0], Schedule(oc_factor=1, ko_factor=1, h_factor=4, w_factor=56)],
-    [resnet[1], Schedule(oc_factor=2, ko_factor=1, h_factor=7, h_nthread=2)],
-    [resnet[2], Schedule(oc_factor=4, ko_factor=2, h_factor=4, w_factor=0, h_nthread=2)],
-    [resnet[3], Schedule(oc_factor=4, ko_factor=1, h_factor=7, w_factor=0, h_nthread=2)],
-    [resnet[4], Schedule(oc_factor=4, ko_factor=1, h_factor=7, h_nthread=2)],
-    [resnet[5], Schedule(oc_factor=4, ko_factor=1, h_factor=7, w_factor=0, h_nthread=2)],
-    [resnet[6], Schedule(oc_factor=4, ko_factor=1, h_factor=7, w_factor=0, oc_nthread=2)],
-    [resnet[7], Schedule(oc_factor=8, ko_factor=1, h_factor=7, w_factor=0, oc_nthread=2)],
-    [resnet[8], Schedule(oc_factor=4, ko_factor=1, h_factor=7, w_factor=0, oc_nthread=2)],
-    [resnet[9], Schedule(oc_factor=4, ko_factor=1, h_factor=7, w_factor=0, oc_nthread=2)],
-    [resnet[10], Schedule(oc_factor=8, ko_factor=1, h_factor=7, w_factor=0, oc_nthread=2)],
-    [resnet[11], Schedule(oc_factor=4, ko_factor=1, h_factor=7, w_factor=0, oc_nthread=2)],
-]
+begin = 0
+end = len(resnet)
 
-# Perform profiling
-profile = False
-# Whether use SMT
-use_smt = True
-# Data set batch size
-batch_size = 1
+resnet_schedules = []
+for i in range(begin, end):
+    scheds = find_schedules(resnet[i], mtOnly=multi_threaded, bestOnly=True)
+    resnet_schedules.append([i, scheds])
 
-resnet_schedule = resnet_smt if use_smt else resnet_serial
+keys = ["key", "layer", "total-data", "total-gops", "total-cost", "total-insn",
+        "block-batch", "block-in", "block-out", "wgt-width", "inp-width",
+        "uop-size", "inp-size", "wgt-size", "out-size",
+        "oc-factor", "ic-factor", "h-factor", "w-factor",
+        "oc-threads", "h-threads", "threaded"]
 
-begin = 0
-end = len(resnet_schedule)
-keys = ["key", "total-gops", "total-cost",
-        "skip-alu-gops", "skip-alu-cost",
-        "gemm-gops", "gemm-cost", "alu-gops", "alu-cost",
-        "ld-inp-cost", "ld-wgt-cost", "st-out-cost",
-        "ld-inp-gbits", "ld-wgt-gbits", "st-out-gbits",]
+if profile:
+    keys += ["skip-alu-gops", "skip-alu-cost",
+             "gemm-gops", "gemm-cost", "alu-gops", "alu-cost",
+             "ld-inp-cost", "ld-wgt-cost", "st-out-cost",
+             "ld-inp-gbits", "ld-wgt-gbits", "st-out-gbits"]
 log_frame = {
     k : [] for k in keys
 }
-for i, x in enumerate(resnet_schedule[begin:end]):
-    wl, plan = x
-    if not wl:
-        continue
-    key = "resnet-cfg[%d]" % i
-    test_conv2d_chwv(key, batch_size, wl, plan, log_frame, profile)
 
-if profile:
-    pd.set_option('expand_frame_repr', False)
-    log_df = pd.DataFrame()
-    for k  in keys:
-        log_df[k] = log_frame[k]
-    print(log_df)
+for x in resnet_schedules:
+    l, plans = x
+    for plan in plans:
+        key = "resnet-cfg[{}-{}]".format(l, plan)
+        test_conv2d_chwv(l, key, batch_size, resnet[l], plan, log_frame, profile)
+
+pd.set_option('expand_frame_repr', False)
+log_df = pd.DataFrame()
+for k  in keys:
+    log_df[k] = log_frame[k]
+print(log_df)
+
+log_df.to_csv("conv2d.csv")
diff --git a/vta/tests/python/pynq/test_program_rpc.py b/vta/tests/python/pynq/test_program_rpc.py
index eaf09577d6a7..ad67f3987378 100644
--- a/vta/tests/python/pynq/test_program_rpc.py
+++ b/vta/tests/python/pynq/test_program_rpc.py
@@ -6,10 +6,14 @@
 host = "pynq"
 port = 9091
 target = "llvm -target=armv7-none-linux-gnueabihf"
-bit = "vta.bit"
+bit = "{}x{}x{}_{}bx{}b_{}_{}_{}_{}_100MHz_10ns.bit".format(
+		vta.VTA_BATCH, vta.VTA_BLOCK_IN, vta.VTA_BLOCK_OUT,
+		vta.VTA_INP_WIDTH, vta.VTA_WGT_WIDTH,
+		vta.VTA_LOG_UOP_BUFF_SIZE, vta.VTA_LOG_INP_BUFF_SIZE,
+		vta.VTA_LOG_WGT_BUFF_SIZE, vta.VTA_LOG_OUT_BUFF_SIZE)
 
 curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
-bitstream = os.path.join(curr_path, "./", bit)
+bitstream = os.path.join(curr_path, "../../../../vta_bitstreams/bitstreams/", bit)
 
 def test_program_rpc():
     assert tvm.module.enabled("rpc")
diff --git a/vta/tests/python/pynq/test_vta_insn.py b/vta/tests/python/pynq/test_vta_insn.py
index b1d17b6d8519..7239bf163342 100644
--- a/vta/tests/python/pynq/test_vta_insn.py
+++ b/vta/tests/python/pynq/test_vta_insn.py
@@ -480,8 +480,8 @@ def verify():
     test_padded_load()
     print("Load/store test")
     test_save_load_out()
-    print("GEMM test")
-    test_gemm()
+    # print("GEMM test")
+    # test_gemm()
     print("Max immediate")
     test_alu(tvm.max, np.maximum, use_imm=True)
     print("Max")
@@ -492,6 +492,8 @@ def verify():
     test_alu(lambda x, y: x + y)
     print("Shift right immediate")
     test_alu(lambda x, y: x >> y, np.right_shift, use_imm=True)
+    print("Shift left immediate")
+    test_alu(lambda x, y: x << y, np.left_shift, use_imm=True)
     print("Relu")
     test_relu()
     # print("Shift and scale")

From 45cf405270ad856db711ca412c4d294a30de8672 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 10 Apr 2018 20:35:22 -0700
Subject: [PATCH 830/949] [COMPILER] Refactor compiler to enable configuration
 (#21)

---
 .../resnet18/pynq/imagenet_predict.py         |  16 +-
 vta/python/vta/__init__.py                    |  15 +-
 vta/python/vta/build.py                       |  55 ---
 vta/python/vta/build_module.py                | 101 ++++++
 vta/python/vta/environment.py                 | 297 ++++++++++++++++
 vta/python/vta/exec/rpc_server.py             |   4 +-
 vta/python/vta/hw_spec.py                     | 100 ------
 vta/python/vta/intrin.py                      | 139 ++++----
 vta/python/vta/ir_pass.py                     | 182 ++++++----
 vta/python/vta/mock.py                        |   7 -
 vta/python/vta/rpc_client.py                  |   9 +-
 vta/python/vta/runtime.py                     |  41 ---
 vta/python/vta/vta_conv2d.py                  |  36 +-
 .../python/pynq/test_benchmark_conv2d.py      | 187 +++++-----
 vta/tests/python/pynq/test_benchmark_gemm.py  | 137 ++++----
 vta/tests/python/pynq/test_benchmark_topi.py  |  40 +--
 vta/tests/python/pynq/test_program_rpc.py     |   9 +-
 vta/tests/python/pynq/test_vta_insn.py        | 320 +++++++++---------
 vta/tests/python/unittest/test_environment.py |  11 +
 19 files changed, 977 insertions(+), 729 deletions(-)
 delete mode 100644 vta/python/vta/build.py
 create mode 100644 vta/python/vta/build_module.py
 create mode 100644 vta/python/vta/environment.py
 delete mode 100644 vta/python/vta/hw_spec.py
 delete mode 100644 vta/python/vta/mock.py
 delete mode 100644 vta/python/vta/runtime.py
 create mode 100644 vta/tests/python/unittest/test_environment.py

diff --git a/vta/examples/resnet18/pynq/imagenet_predict.py b/vta/examples/resnet18/pynq/imagenet_predict.py
index 74b34fecfc0b..eb660ea12a71 100644
--- a/vta/examples/resnet18/pynq/imagenet_predict.py
+++ b/vta/examples/resnet18/pynq/imagenet_predict.py
@@ -39,8 +39,9 @@
 if verbose:
     logging.basicConfig(level=logging.INFO)
 
-# Change to -device=tcpu to run cpu only inference.
+# Change to -device=vta-cpu to run cpu only inference.
 target = "llvm -device=vta"
+target_host = "llvm -mtriple=armv7-none-linux-gnueabihf -mcpu=cortex-a9 -mattr=+neon"
 
 synset = eval(open(os.path.join(CATEG_FILE)).read())
 image = Image.open(os.path.join(TEST_FILE)).resize((224, 224))
@@ -117,15 +118,16 @@ def mark_nop(graph, conv_layer=-1, skip_conv_layer=()):
 sym = sym.apply("InferType")
 
 with nnvm.compiler.build_config(opt_level=3):
-    bdict = {}
     if "vta" not in target:
-        bdict = {"add_lower_pass": []}
-    else:
-        bdict = {"add_lower_pass": vta.debug_mode(0)}
-    with tvm.build_config(**bdict):
         graph, lib, params = nnvm.compiler.build(
             sym, target, shape_dict, dtype_dict,
-            params=params)
+            params=params, target_host=target_host)
+    else:
+        with vta.build_config():
+            graph, lib, params = nnvm.compiler.build(
+                sym, target, shape_dict, dtype_dict,
+                params=params, target_host=target_host)
+
 
 temp = util.tempdir()
 lib.save(temp.relpath("graphlib.o"))
diff --git a/vta/python/vta/__init__.py b/vta/python/vta/__init__.py
index dc06b7ad0bfe..275c15b227a3 100644
--- a/vta/python/vta/__init__.py
+++ b/vta/python/vta/__init__.py
@@ -1,20 +1,13 @@
 """TVM-based VTA Compiler Toolchain"""
 from __future__ import absolute_import as _abs
 
-from .hw_spec import *
+from .environment import get_env, Environment
 
 try:
-    from .runtime import SCOPE_INP, SCOPE_OUT, SCOPE_WGT, DMA_COPY, ALU
-    from .intrin import GEVM, GEMM
-    from .build import debug_mode
-    from . import mock, ir_pass
+    # allow optional import in config mode.
     from . import arm_conv2d, vta_conv2d
-except AttributeError:
-    pass
-
-from .rpc_client import reconfig_runtime, program_fpga
-
-try:
+    from .build_module import build_config, lower, build
+    from .rpc_client import reconfig_runtime, program_fpga
     from . import graph
 except ImportError:
     pass
diff --git a/vta/python/vta/build.py b/vta/python/vta/build.py
deleted file mode 100644
index 06d30229faef..000000000000
--- a/vta/python/vta/build.py
+++ /dev/null
@@ -1,55 +0,0 @@
-"""Runtime function related hooks"""
-from __future__ import absolute_import as _abs
-
-import tvm
-from tvm import build_module
-from . runtime import CB_HANDLE
-from . import ir_pass
-
-
-def lift_coproc_scope(x):
-    x = ir_pass.lift_alloc_to_scope_begin(x)
-    x = tvm.ir_pass.LiftAttrScope(x, "coproc_scope", False)
-    return x
-
-def early_rewrite(stmt):
-    try:
-        return tvm.ir_pass.StorageRewrite(stmt)
-    except tvm.TVMError:
-        return stmt
-
-
-def debug_mode(debug_flag):
-    """Pass to enable vta debug mode.
-
-    Parameters
-    ----------
-    debug_flag : int
-        The dbeug flag to be passed.
-
-    Returns
-    -------
-    pass_list: list of function
-        The pass to set to build_config(add_lower_pass=vta.debug_mode(mode))
-    """
-    def add_debug(stmt):
-        debug = tvm.call_extern(
-            "int32", "VTASetDebugMode", CB_HANDLE, debug_flag)
-        return tvm.make.stmt_seq(debug, stmt)
-    pass_list = [(1, ir_pass.inject_dma_intrin),
-                 (1, ir_pass.inject_skip_copy),
-                 (1, ir_pass.annotate_alu_coproc_scope),
-                 (1, lambda x: tvm.ir_pass.LiftAttrScope(x, "coproc_uop_scope", True)),
-                 (1, lift_coproc_scope),
-                 (1, ir_pass.inject_coproc_sync),
-                 (1, early_rewrite)]
-    if debug_flag:
-        pass_list.append((1, add_debug))
-    pass_list.append((2, ir_pass.inject_alu_intrin))
-    pass_list.append((3, ir_pass.fold_uop_loop))
-    pass_list.append((3, ir_pass.cpu_access_rewrite))
-    return pass_list
-
-
-# Add a lower pass to sync uop
-build_module.current_build_config().add_lower_pass = debug_mode(0)
diff --git a/vta/python/vta/build_module.py b/vta/python/vta/build_module.py
new file mode 100644
index 000000000000..e6804aa53191
--- /dev/null
+++ b/vta/python/vta/build_module.py
@@ -0,0 +1,101 @@
+"""VTA specific buildin for runtime."""
+from __future__ import absolute_import as _abs
+
+import tvm
+from . import ir_pass
+from .environment import get_env
+
+
+def lift_coproc_scope(x):
+    """Lift coprocessings cope to the """
+    x = ir_pass.lift_alloc_to_scope_begin(x)
+    x = tvm.ir_pass.LiftAttrScope(x, "coproc_scope", False)
+    return x
+
+def early_rewrite(stmt):
+    """Try to do storage rewrite in early pass."""
+    try:
+        return tvm.ir_pass.StorageRewrite(stmt)
+    except tvm.TVMError:
+        return stmt
+
+
+def build_config(debug_flag=0, **kwargs):
+    """Build a build config for VTA.
+
+    Parameters
+    ----------
+    debug_flag : int
+        The dbeug flag to be passed.
+
+    kwargs : dict
+        Additional configurations.
+
+    Returns
+    -------
+    build_config: BuildConfig
+        The build config that can be used in TVM.
+
+    Example
+    --------
+    .. code-block:: python
+
+      # build a vta module.
+      with vta.build_config():
+          vta_module = tvm.build(s, ...)
+    """
+    env = get_env()
+    def add_debug(stmt):
+        debug = tvm.call_extern(
+            "int32", "VTASetDebugMode",
+            env.dev.command_handle,
+            debug_flag)
+
+        return tvm.make.stmt_seq(debug, stmt)
+    pass_list = [(1, ir_pass.inject_dma_intrin),
+                 (1, ir_pass.inject_skip_copy),
+                 (1, ir_pass.annotate_alu_coproc_scope),
+                 (1, lambda x: tvm.ir_pass.LiftAttrScope(x, "coproc_uop_scope", True)),
+                 (1, lift_coproc_scope),
+                 (1, ir_pass.inject_coproc_sync),
+                 (1, early_rewrite)]
+    if debug_flag:
+        pass_list.append((1, add_debug))
+    pass_list.append((2, ir_pass.inject_alu_intrin))
+    pass_list.append((3, ir_pass.fold_uop_loop))
+    pass_list.append((3, ir_pass.cpu_access_rewrite))
+    return tvm.build_config(add_lower_pass=pass_list, **kwargs)
+
+
+def lower(*args, **kwargs):
+    """Thin wrapper of tvm.lower
+
+    This wrapper automatically applies VTA's build_config
+    if there is no user specified build_config in context.
+
+    See Also
+    --------
+    tvm.lower : The original TVM's lower function
+    """
+    cfg = tvm.build_module.current_build_config()
+    if not cfg.add_lower_pass:
+        with build_config():
+            return tvm.lower(*args, **kwargs)
+    return tvm.lower(*args, **kwargs)
+
+
+def build(*args, **kwargs):
+    """Thin wrapper of tvm.build
+
+    This wrapper automatically applies VTA's build_config
+    if there is no user specified build_config in context.
+
+    See Also
+    --------
+    tvm.build : The original TVM's build function
+    """
+    cfg = tvm.build_module.current_build_config()
+    if not cfg.add_lower_pass:
+        with build_config():
+            return tvm.build(*args, **kwargs)
+    return tvm.build(*args, **kwargs)
diff --git a/vta/python/vta/environment.py b/vta/python/vta/environment.py
new file mode 100644
index 000000000000..b0d7d170fd7d
--- /dev/null
+++ b/vta/python/vta/environment.py
@@ -0,0 +1,297 @@
+"""Configurable VTA Hareware Environment scope."""
+# pylint: disable=invalid-name
+from __future__ import absolute_import as _abs
+
+import os
+import copy
+
+try:
+    # Allow missing import in config mode.
+    import tvm
+    from . import intrin
+except ImportError:
+    pass
+
+
+class DevContext(object):
+    """Internal development context
+
+    This contains all the non-user facing compiler
+    internal context that is hold by the Environment.
+
+    Parameters
+    ----------
+    env : Environment
+        The environment hosting the DevContext
+
+    Note
+    ----
+    This class is introduced so we have a clear separation
+    of developer related stuffs and user facing attributes.
+    """
+    # Memory id for DMA
+    MEM_ID_UOP = 0
+    MEM_ID_WGT = 1
+    MEM_ID_INP = 2
+    MEM_ID_ACC = 3
+    MEM_ID_OUT = 4
+    # VTA ALU Opcodes
+    ALU_OPCODE_MIN = 0
+    ALU_OPCODE_MAX = 1
+    ALU_OPCODE_ADD = 2
+    ALU_OPCODE_SHR = 3
+    # Task queue id (pipeline stage)
+    QID_LOAD_INP = 1
+    QID_LOAD_WGT = 1
+    QID_LOAD_OUT = 2
+    QID_STORE_OUT = 3
+    QID_COMPUTE = 2
+    QID_STORE_INP = 3
+
+    def __init__(self, env):
+        self.vta_axis = tvm.thread_axis("vta")
+        self.vta_push_uop = tvm.make.StringImm("VTAPushGEMMOp")
+        ctx = tvm.call_extern("handle", "VTATLSCommandHandle")
+        self.command_handle = tvm.make.Call(
+            "handle", "tvm_thread_context", [ctx],
+            tvm.expr.Call.Intrinsic, None, 0)
+        self.DEBUG_NO_SYNC = False
+        env._dev_ctx = self
+        self.gemm = intrin.gemm(env, env.mock_mode)
+        self.gevm = intrin.gevm(env, env.mock_mode)
+
+    def get_task_qid(self, qid):
+        """Get transformed queue index."""
+        return 1 if self.DEBUG_NO_SYNC else qid
+
+
+class Environment(object):
+    """Hareware configuration object.
+
+    This object contains all the information
+    needed for compiling to a specific VTA backend.
+
+    Parameters
+    ----------
+    cfg : dict of str to value.
+        The configuration parameters.
+
+    Example
+    --------
+    .. code-block:: python
+
+      # the following code reconfigures the environment
+      # temporarily to attributes specified in new_cfg.json
+      new_cfg = json.load(json.load(open("new_cfg.json")))
+      with vta.Environment(new_cfg):
+          # env works on the new environment
+          env = vta.get_env()
+    """
+    current = None
+    cfg_keys = [
+        "target",
+        "LOG_INP_WIDTH",
+        "LOG_WGT_WIDTH",
+        "LOG_ACC_WIDTH",
+        "LOG_BATCH",
+        "LOG_BLOCK_IN",
+        "LOG_BLOCK_OUT",
+        "LOG_UOP_BUFF_SIZE",
+        "LOG_INP_BUFF_SIZE",
+        "LOG_WGT_BUFF_SIZE",
+        "LOG_ACC_BUFF_SIZE",
+    ]
+    # constants
+    MAX_XFER = 1 << 22
+    # debug flags
+    DEBUG_DUMP_INSN = (1 << 1)
+    DEBUG_DUMP_UOP = (1 << 2)
+    DEBUG_SKIP_READ_BARRIER = (1 << 3)
+    DEBUG_SKIP_WRITE_BARRIER = (1 << 4)
+    # memory scopes
+    inp_scope = "local.inp_buffer"
+    wgt_scope = "local.wgt_buffer"
+    acc_scope = "local.acc_buffer"
+
+    # initialization function
+    def __init__(self, cfg):
+        # Log of input/activation width in bits
+        self.__dict__.update(cfg)
+        for key in self.cfg_keys:
+            if key not in cfg:
+                raise ValueError("Expect key %s in cfg" % key)
+        self.LOG_OUT_WIDTH = self.LOG_INP_WIDTH
+        self.LOG_OUT_BUFF_SIZE = (
+            self.LOG_ACC_BUFF_SIZE +
+            self.LOG_OUT_WIDTH -
+            self.LOG_ACC_WIDTH)
+        # width
+        self.INP_WIDTH = 1 << self.LOG_INP_WIDTH
+        self.WGT_WIDTH = 1 << self.LOG_WGT_WIDTH
+        self.ACC_WIDTH = 1 << self.LOG_ACC_WIDTH
+        self.BATCH = 1 << self.LOG_BATCH
+        self.BLOCK_IN = 1 << self.LOG_BLOCK_IN
+        self.BLOCK_OUT = 1 << self.LOG_BLOCK_OUT
+        self.OUT_WIDTH = self.INP_WIDTH
+        # buffer size
+        self.UOP_BUFF_SIZE = 1 << self.LOG_UOP_BUFF_SIZE
+        self.INP_BUFF_SIZE = 1 << self.LOG_INP_BUFF_SIZE
+        self.WGT_BUFF_SIZE = 1 << self.LOG_WGT_BUFF_SIZE
+        self.ACC_BUFF_SIZE = 1 << self.LOG_ACC_BUFF_SIZE
+        self.OUT_BUFF_SIZE = 1 << self.LOG_OUT_BUFF_SIZE
+        # bytes per buffer
+        self.INP_ELEM_BITS = (self.BATCH *
+                              self.BLOCK_IN *
+                              self.INP_WIDTH)
+        self.WGT_ELEM_BITS = (self.BLOCK_OUT *
+                              self.BLOCK_IN *
+                              self.WGT_WIDTH)
+        self.ACC_ELEM_BITS = (self.BATCH *
+                              self.BLOCK_IN *
+                              self.ACC_WIDTH)
+        self.INP_ELEM_BYTES = self.INP_ELEM_BITS // 8
+        self.WGT_ELEM_BYTES = self.WGT_ELEM_BITS // 8
+        self.ACC_ELEM_BYTES = self.ACC_ELEM_BITS // 8
+        # dtypes
+        self.acc_dtype = "int%d" % self.ACC_WIDTH
+        self.inp_dtype = "int%d" % self.INP_WIDTH
+        self.wgt_dtype = "int%d" % self.WGT_WIDTH
+        # lazy cached members
+        self.mock_mode = False
+        self._mock_env = None
+        self._dev_ctx = None
+
+    @property
+    def dev(self):
+        """Developer context"""
+        if self._dev_ctx is None:
+            self._dev_ctx = DevContext(self)
+        return self._dev_ctx
+
+    @property
+    def mock(self):
+        """A mock version of the Environment
+
+        The ALU, dma_copy and intrinsics will be
+        mocked to be nop.
+        """
+        if self.mock_mode:
+            return self
+        if self._mock_env is None:
+            self._mock_env = copy.copy(self)
+            self._mock_env._dev_ctx = None
+            self._mock_env.mock_mode = True
+        return self._mock_env
+
+    @property
+    def dma_copy(self):
+        """DMA copy pragma"""
+        return ("dma_copy"
+                if not self.mock_mode
+                else "skip_dma_copy")
+
+    @property
+    def alu(self):
+        """ALU pragma"""
+        return ("alu"
+                if not self.mock_mode
+                else "skip_alu")
+
+    @property
+    def gemm(self):
+        """GEMM intrinsic"""
+        return self.dev.gemm
+
+    @property
+    def gevm(self):
+        """GEMM intrinsic"""
+        return self.dev.gevm
+
+
+def get_env():
+    """Get the current VTA Environment.
+
+    Returns
+    -------
+    env : Environment
+        The current environment.
+    """
+    return Environment.current
+
+
+# The memory information for the compiler
+@tvm.register_func("tvm.info.mem.%s" % Environment.inp_scope)
+def mem_info_inp_buffer():
+    spec = get_env()
+    return tvm.make.node("MemoryInfo",
+                         unit_bits=spec.INP_ELEM_BITS,
+                         max_simd_bits=spec.INP_ELEM_BITS,
+                         max_num_bits=spec.INP_BUFF_SIZE * 8,
+                         head_address=None)
+
+@tvm.register_func("tvm.info.mem.%s" % Environment.wgt_scope)
+def mem_info_wgt_buffer():
+    spec = get_env()
+    return tvm.make.node("MemoryInfo",
+                         unit_bits=spec.WGT_ELEM_BITS,
+                         max_simd_bits=spec.WGT_ELEM_BITS,
+                         max_num_bits=spec.WGT_BUFF_SIZE * 8,
+                         head_address=None)
+
+@tvm.register_func("tvm.info.mem.%s" % Environment.acc_scope)
+def mem_info_out_buffer():
+    spec = get_env()
+    return tvm.make.node("MemoryInfo",
+                         unit_bits=spec.ACC_ELEM_BITS,
+                         max_simd_bits=spec.ACC_ELEM_BITS,
+                         max_num_bits=spec.ACC_BUFF_SIZE * 8,
+                         head_address=None)
+
+# TVM related registration
+@tvm.register_func("tvm.intrin.rule.default.vta.coproc_sync")
+def coproc_sync(op):
+    _ = op
+    return tvm.call_extern(
+        "int32", "VTASynchronize",
+        get_env().dev.command_handle, 1<<31)
+
+@tvm.register_func("tvm.intrin.rule.default.vta.coproc_dep_push")
+def coproc_dep_push(op):
+    return tvm.call_extern(
+        "int32", "VTADepPush",
+        get_env().dev.command_handle,
+        op.args[0], op.args[1])
+
+@tvm.register_func("tvm.intrin.rule.default.vta.coproc_dep_pop")
+def coproc_dep_pop(op):
+    return tvm.call_extern(
+        "int32", "VTADepPop",
+        get_env().dev.command_handle,
+        op.args[0], op.args[1])
+
+
+def _init_env():
+    """Iniitalize the default global env"""
+    python_vta_dir = os.path.dirname(__file__)
+    filename = os.path.join(python_vta_dir, '../../config.mk')
+    keys = set()
+
+    for k in Environment.cfg_keys:
+        keys.add("VTA_" + k)
+
+    if not os.path.isfile(filename):
+        raise RuntimeError(
+            "Error: {} not found.make sure you have config.mk in your vta root"
+            .format(filename))
+
+    cfg = {}
+    with open(filename) as f:
+        for line in f:
+            for k in keys:
+                if k  +" =" in line:
+                    val = line.split("=")[1].strip()
+                    cfg[k[4:]] = int(val)
+    cfg["target"] = "pynq"
+    return Environment(cfg)
+
+Environment.current = _init_env()
diff --git a/vta/python/vta/exec/rpc_server.py b/vta/python/vta/exec/rpc_server.py
index 8a27859f7ad4..ebaf15f8dc37 100644
--- a/vta/python/vta/exec/rpc_server.py
+++ b/vta/python/vta/exec/rpc_server.py
@@ -9,11 +9,13 @@
 import os
 import ctypes
 import tvm
-from tvm.contrib import rpc, util, cc
+from tvm.contrib import rpc, cc
 
 
 @tvm.register_func("tvm.contrib.rpc.server.start", override=True)
 def server_start():
+    """callback when server starts."""
+    # pylint: disable=unused-variable
     curr_path = os.path.dirname(
         os.path.abspath(os.path.expanduser(__file__)))
     dll_path = os.path.abspath(
diff --git a/vta/python/vta/hw_spec.py b/vta/python/vta/hw_spec.py
deleted file mode 100644
index 3a66835d2d93..000000000000
--- a/vta/python/vta/hw_spec.py
+++ /dev/null
@@ -1,100 +0,0 @@
-"""VTA configuration constants (should match hw_spec.h"""
-from __future__ import absolute_import as _abs
-
-import os
-python_vta_dir = os.path.dirname(__file__)
-print python_vta_dir
-filename = os.path.join(python_vta_dir, '../../config.mk')
-
-VTA_PYNQ_BRAM_W = 32
-VTA_PYNQ_BRAM_D = 1024
-VTA_PYNQ_NUM_BRAM = 124
-
-keys = ["VTA_LOG_INP_WIDTH", "VTA_LOG_WGT_WIDTH", "VTA_LOG_ACC_WIDTH",
-        "VTA_LOG_BATCH", "VTA_LOG_BLOCK_IN", "VTA_LOG_BLOCK_OUT",
-        "VTA_LOG_UOP_BUFF_SIZE", "VTA_LOG_INP_BUFF_SIZE",
-        "VTA_LOG_WGT_BUFF_SIZE", "VTA_LOG_ACC_BUFF_SIZE"]
-
-params = {}
-
-if os.path.isfile(filename):
-  with open(filename) as f:
-    for line in f:
-      for k in keys:
-        if k+" =" in line:
-          val = line.split("=")[1].strip()
-          params[k] = int(val)
-  # print params
-else:
-  print "Error: {} not found. Please make sure you have config.mk in your vta root".format(filename)
-  exit()
-
-# The Constants
-VTA_INP_WIDTH = 1 << params["VTA_LOG_INP_WIDTH"]
-VTA_WGT_WIDTH = 1 << params["VTA_LOG_WGT_WIDTH"]
-VTA_OUT_WIDTH = 1 << params["VTA_LOG_ACC_WIDTH"]
-
-VTA_TARGET = "VTA_PYNQ_TARGET"
-
-# Dimensions of the GEMM unit
-# (BATCH,BLOCK_IN) x (BLOCK_IN,BLOCK_OUT)
-VTA_BATCH = 1 << params["VTA_LOG_BATCH"]
-VTA_BLOCK_IN = 1 << params["VTA_LOG_BLOCK_IN"]
-VTA_BLOCK_OUT = 1 << params["VTA_LOG_BLOCK_OUT"]
-
-# log-2 On-chip uop buffer size in Bytes
-VTA_LOG_UOP_BUFF_SIZE = params["VTA_LOG_UOP_BUFF_SIZE"]
-# log-2 On-chip input buffer size in Bytes
-VTA_LOG_INP_BUFF_SIZE = params["VTA_LOG_INP_BUFF_SIZE"]
-# log-2 On-chip wgt buffer size in Bytes
-VTA_LOG_WGT_BUFF_SIZE = params["VTA_LOG_WGT_BUFF_SIZE"]
-# log-2 On-chip output buffer size in Bytes
-VTA_LOG_OUT_BUFF_SIZE = params["VTA_LOG_ACC_BUFF_SIZE"]
-# Uop buffer size
-VTA_UOP_BUFF_SIZE = 1 << VTA_LOG_UOP_BUFF_SIZE
-# Input buffer size
-VTA_INP_BUFF_SIZE = 1 << VTA_LOG_INP_BUFF_SIZE
-# On-chip wgt buffer size in Bytes
-VTA_WGT_BUFF_SIZE = 1 << VTA_LOG_WGT_BUFF_SIZE
-# Output buffer size.
-VTA_OUT_BUFF_SIZE = 1 << VTA_LOG_OUT_BUFF_SIZE
-
-# Number of bytes per buffer
-VTA_INP_ELEM_BYTES = (VTA_BATCH*VTA_BLOCK_IN*VTA_INP_WIDTH//8)
-VTA_WGT_ELEM_BYTES = (VTA_BLOCK_OUT*VTA_BLOCK_IN*VTA_WGT_WIDTH//8)
-VTA_OUT_ELEM_BYTES = (VTA_BATCH*VTA_BLOCK_OUT*VTA_OUT_WIDTH//8)
-
-# Maximum external buffer size in bytes
-VTA_MAX_XFER = 1 << 22
-
-# Number of elements
-VTA_INP_BUFF_DEPTH = VTA_INP_BUFF_SIZE//VTA_INP_ELEM_BYTES
-VTA_WGT_BUFF_DEPTH = VTA_WGT_BUFF_SIZE//VTA_WGT_ELEM_BYTES
-VTA_OUT_BUFF_DEPTH = VTA_OUT_BUFF_SIZE//VTA_OUT_ELEM_BYTES
-
-# Memory id for DMA
-VTA_MEM_ID_UOP = 0
-VTA_MEM_ID_WGT = 1
-VTA_MEM_ID_INP = 2
-VTA_MEM_ID_ACC = 3
-VTA_MEM_ID_OUT = 4
-
-# VTA ALU Opcodes
-VTA_ALU_OPCODE_MIN = 0
-VTA_ALU_OPCODE_MAX = 1
-VTA_ALU_OPCODE_ADD = 2
-VTA_ALU_OPCODE_SHR = 3
-
-# Task queue id (pipeline stage)
-VTA_QID_LOAD_INP = 1
-VTA_QID_LOAD_WGT = 1
-VTA_QID_LOAD_OUT = 2
-VTA_QID_STORE_OUT = 3
-VTA_QID_COMPUTE = 2
-VTA_QID_STORE_INP = 3
-
-# Debug flags
-DEBUG_DUMP_INSN = (1 << 1)
-DEBUG_DUMP_UOP = (1 << 2)
-DEBUG_SKIP_READ_BARRIER = (1 << 3)
-DEBUG_SKIP_WRITE_BARRIER = (1 << 4)
diff --git a/vta/python/vta/intrin.py b/vta/python/vta/intrin.py
index 73928aa1144b..8373a2c05d4f 100644
--- a/vta/python/vta/intrin.py
+++ b/vta/python/vta/intrin.py
@@ -2,65 +2,46 @@
 from __future__ import absolute_import as _abs
 
 import tvm
-from . import hw_spec as spec
-from .runtime import VTA_AXIS, VTA_PUSH_UOP, get_task_qid
-from .runtime import SCOPE_OUT, SCOPE_INP, SCOPE_WGT
 
-# The memory information for the compiler
-@tvm.register_func("tvm.info.mem.%s" % SCOPE_INP)
-def mem_info_inp_buffer():
-    return tvm.make.node("MemoryInfo",
-                         unit_bits=spec.VTA_INP_ELEM_BYTES * 8,
-                         max_simd_bits=spec.VTA_INP_ELEM_BYTES * 8,
-                         max_num_bits=spec.VTA_INP_BUFF_SIZE * 8,
-                         head_address=None)
+def gevm(env, mock=False):
+    """Vector-matrix multiply intrinsic
 
-@tvm.register_func("tvm.info.mem.%s" % SCOPE_WGT)
-def mem_info_wgt_buffer():
-    return tvm.make.node("MemoryInfo",
-                         unit_bits=spec.VTA_WGT_ELEM_BYTES * 8,
-                         max_simd_bits=spec.VTA_WGT_ELEM_BYTES * 8,
-                         max_num_bits=spec.VTA_WGT_BUFF_SIZE * 8,
-                         head_address=None)
+    Parameters
+    ----------
+    env : Environment
+        The Environment
 
-@tvm.register_func("tvm.info.mem.%s" % SCOPE_OUT)
-def mem_info_out_buffer():
-    return tvm.make.node("MemoryInfo",
-                         unit_bits=spec.VTA_OUT_ELEM_BYTES * 8,
-                         max_simd_bits=spec.VTA_OUT_ELEM_BYTES * 8,
-                         max_num_bits=spec.VTA_OUT_BUFF_SIZE * 8,
-                         head_address=None)
-
-def intrin_gevm(mock=False):
-    """Vector-matrix multiply intrinsic"""
-    wgt_lanes = spec.VTA_WGT_ELEM_BYTES * 8 // spec.VTA_WGT_WIDTH
-    assert wgt_lanes == spec.VTA_BLOCK_OUT * spec.VTA_BLOCK_IN
-    wgt_shape = (spec.VTA_BLOCK_OUT, spec.VTA_BLOCK_IN)
+    mock : bool
+        Whether create a mock version.
+    """
+    wgt_lanes = env.WGT_ELEM_BITS // env.WGT_WIDTH
+    assert wgt_lanes == env.BLOCK_OUT * env.BLOCK_IN
+    wgt_shape = (env.BLOCK_OUT, env.BLOCK_IN)
     assert wgt_shape[0] * wgt_shape[1] == wgt_lanes
-    inp_lanes = spec.VTA_INP_ELEM_BYTES * 8 // spec.VTA_INP_WIDTH
-    out_lanes = spec.VTA_OUT_ELEM_BYTES * 8 // spec.VTA_OUT_WIDTH
+    inp_lanes = env.INP_ELEM_BITS // env.INP_WIDTH
+    out_lanes = env.ACC_ELEM_BITS // env.ACC_WIDTH
     wgt = tvm.placeholder((wgt_shape[0], wgt_shape[1]),
-                          dtype="int%d" % spec.VTA_WGT_WIDTH,
-                          name=SCOPE_WGT)
+                          dtype="int%d" % env.WGT_WIDTH,
+                          name=env.wgt_scope)
     inp = tvm.placeholder((wgt_shape[1], ),
-                          dtype="int%d" % spec.VTA_INP_WIDTH,
-                          name=SCOPE_INP)
+                          dtype="int%d" % env.INP_WIDTH,
+                          name=env.inp_scope)
     k = tvm.reduce_axis((0, wgt_shape[1]), name="k")
-    out_dtype = "int%d" % spec.VTA_OUT_WIDTH
+    out_dtype = "int%d" % env.ACC_WIDTH
     out = tvm.compute((wgt_shape[0],),
                       lambda i: tvm.sum(inp[k].astype(out_dtype) *
                                         wgt[i, k].astype(out_dtype),
                                         axis=[k]),
                       name="out")
     wgt_layout = tvm.decl_buffer(
-        wgt.shape, wgt.dtype, SCOPE_WGT,
-        scope=SCOPE_WGT, offset_factor=wgt_lanes, data_alignment=wgt_lanes)
+        wgt.shape, wgt.dtype, env.wgt_scope,
+        scope=env.wgt_scope, offset_factor=wgt_lanes, data_alignment=wgt_lanes)
     inp_layout = tvm.decl_buffer(
-        inp.shape, inp.dtype, SCOPE_INP,
-        scope=SCOPE_INP, offset_factor=inp_lanes, data_alignment=inp_lanes)
+        inp.shape, inp.dtype, env.inp_scope,
+        scope=env.inp_scope, offset_factor=inp_lanes, data_alignment=inp_lanes)
     out_layout = tvm.decl_buffer(
-        out.shape, out.dtype, SCOPE_OUT,
-        scope=SCOPE_OUT, offset_factor=out_lanes, data_alignment=out_lanes)
+        out.shape, out.dtype, env.acc_scope,
+        scope=env.acc_scope, offset_factor=out_lanes, data_alignment=out_lanes)
 
     def intrin_func(ins, outs):
         """Vector-matrix multiply intrinsic function"""
@@ -69,8 +50,11 @@ def intrin_func(ins, outs):
         def instr(index):
             """Generate vector-matrix multiply VTA instruction"""
             irb = tvm.ir_builder.create()
-            irb.scope_attr(VTA_AXIS, "coproc_scope", get_task_qid(spec.VTA_QID_COMPUTE))
-            irb.scope_attr(VTA_AXIS, "coproc_uop_scope", VTA_PUSH_UOP)
+            dev = env.dev
+            irb.scope_attr(dev.vta_axis, "coproc_scope",
+                           dev.get_task_qid(dev.QID_COMPUTE))
+            irb.scope_attr(dev.vta_axis, "coproc_uop_scope",
+                           dev.vta_push_uop)
             if index == 0 or index == 2:
                 irb.emit(tvm.call_extern(
                     "int32", "VTAUopPush",
@@ -101,45 +85,54 @@ def instr(index):
                                          out: out_layout})
 
 
-def intrin_gemm(mock=False):
-    """Matrix-matrix multiply intrinsic"""
-    wgt_lanes = spec.VTA_WGT_ELEM_BYTES * 8 // spec.VTA_WGT_WIDTH
-    assert wgt_lanes == spec.VTA_BLOCK_OUT * spec.VTA_BLOCK_IN
-    wgt_shape = (spec.VTA_BLOCK_OUT, spec.VTA_BLOCK_IN)
+def gemm(env, mock=False):
+    """Matrix-matrix multiply intrinsic
+
+    Parameters
+    ----------
+    env : Environment
+        The Environment
+
+    mock : bool
+        Whether create a mock version.
+    """
+    wgt_lanes = env.WGT_ELEM_BITS // env.WGT_WIDTH
+    assert wgt_lanes == env.BLOCK_OUT * env.BLOCK_IN
+    wgt_shape = (env.BLOCK_OUT, env.BLOCK_IN)
     assert wgt_shape[0] * wgt_shape[1] == wgt_lanes
 
-    inp_lanes = spec.VTA_INP_ELEM_BYTES * 8 // spec.VTA_INP_WIDTH
-    assert inp_lanes == spec.VTA_BATCH * spec.VTA_BLOCK_IN
-    inp_shape = (spec.VTA_BATCH, spec.VTA_BLOCK_IN)
+    inp_lanes = env.INP_ELEM_BITS // env.INP_WIDTH
+    assert inp_lanes == env.BATCH * env.BLOCK_IN
+    inp_shape = (env.BATCH, env.BLOCK_IN)
     assert inp_shape[0] * inp_shape[1] == inp_lanes
 
-    out_lanes = spec.VTA_OUT_ELEM_BYTES * 8 // spec.VTA_OUT_WIDTH
-    assert out_lanes == spec.VTA_BATCH * spec.VTA_BLOCK_OUT
-    out_shape = (spec.VTA_BATCH, spec.VTA_BLOCK_OUT)
+    out_lanes = env.ACC_ELEM_BITS // env.ACC_WIDTH
+    assert out_lanes == env.BATCH * env.BLOCK_OUT
+    out_shape = (env.BATCH, env.BLOCK_OUT)
     assert out_shape[0] * out_shape[1] == out_lanes
 
     wgt = tvm.placeholder((wgt_shape[0], wgt_shape[1]),
-                          dtype="int%d" % spec.VTA_WGT_WIDTH,
-                          name=SCOPE_WGT)
+                          dtype="int%d" % env.WGT_WIDTH,
+                          name=env.wgt_scope)
     inp = tvm.placeholder((inp_shape[0], inp_shape[1]),
-                          dtype="int%d" % spec.VTA_INP_WIDTH,
-                          name=SCOPE_INP)
+                          dtype="int%d" % env.INP_WIDTH,
+                          name=env.inp_scope)
     k = tvm.reduce_axis((0, wgt_shape[1]), name="k")
-    out_dtype = "int%d" % spec.VTA_OUT_WIDTH
+    out_dtype = "int%d" % env.ACC_WIDTH
     out = tvm.compute((out_shape[0], out_shape[1]),
                       lambda i, j: tvm.sum(inp[i, k].astype(out_dtype) *
                                            wgt[j, k].astype(out_dtype),
                                            axis=[k]),
                       name="out")
     wgt_layout = tvm.decl_buffer(
-        wgt.shape, wgt.dtype, SCOPE_WGT,
-        scope=SCOPE_WGT, offset_factor=wgt_lanes, data_alignment=wgt_lanes)
+        wgt.shape, wgt.dtype, env.wgt_scope,
+        scope=env.wgt_scope, offset_factor=wgt_lanes, data_alignment=wgt_lanes)
     inp_layout = tvm.decl_buffer(
-        inp.shape, inp.dtype, SCOPE_INP,
-        scope=SCOPE_INP, offset_factor=inp_lanes, data_alignment=inp_lanes)
+        inp.shape, inp.dtype, env.inp_scope,
+        scope=env.inp_scope, offset_factor=inp_lanes, data_alignment=inp_lanes)
     out_layout = tvm.decl_buffer(
-        out.shape, out.dtype, SCOPE_OUT,
-        scope=SCOPE_OUT, offset_factor=out_lanes, data_alignment=out_lanes)
+        out.shape, out.dtype, env.acc_scope,
+        scope=env.acc_scope, offset_factor=out_lanes, data_alignment=out_lanes)
 
     def intrin_func(ins, outs):
         """Matrix-matrix multiply intrinsic function"""
@@ -148,8 +141,11 @@ def intrin_func(ins, outs):
         def instr(index):
             """Generate matrix-matrix multiply VTA instruction"""
             irb = tvm.ir_builder.create()
-            irb.scope_attr(VTA_AXIS, "coproc_scope", get_task_qid(spec.VTA_QID_COMPUTE))
-            irb.scope_attr(VTA_AXIS, "coproc_uop_scope", VTA_PUSH_UOP)
+            dev = env.dev
+            irb.scope_attr(dev.vta_axis, "coproc_scope",
+                           dev.get_task_qid(dev.QID_COMPUTE))
+            irb.scope_attr(dev.vta_axis, "coproc_uop_scope",
+                           dev.vta_push_uop)
             if index == 0 or index == 2:
                 irb.emit(tvm.call_extern(
                     "int32", "VTAUopPush",
@@ -178,6 +174,3 @@ def instr(index):
                                   binds={inp: inp_layout,
                                          wgt: wgt_layout,
                                          out: out_layout})
-
-GEMM = intrin_gemm()
-GEVM = intrin_gevm()
diff --git a/vta/python/vta/ir_pass.py b/vta/python/vta/ir_pass.py
index 86329cf8d2c7..f85f1760e72b 100644
--- a/vta/python/vta/ir_pass.py
+++ b/vta/python/vta/ir_pass.py
@@ -1,20 +1,37 @@
 """Additional IR Pass for VTA"""
+# pylint: disable=len-as-condition
 from __future__ import absolute_import as _abs
 
 import tvm
 from topi import util as util
 
-from . import hw_spec as spec
-from . runtime import CB_HANDLE, VTA_AXIS, VTA_PUSH_UOP
-from . runtime import SCOPE_OUT, SCOPE_INP, SCOPE_WGT, DMA_COPY, get_task_qid
+from .environment import get_env
 
 
 def fold_uop_loop(stmt_in):
-    """Pass to fold uop loops"""
+    """Detect and fold uop loop.
+
+    VTA support uop programming model
+    that recognizes loop structure.
+    This pass detect the loop structure
+    and extract that into uop loop AST.
+
+    Parameters
+    ----------
+    stmt_in : Stmt
+        Input statement
+
+    Returns
+    -------
+    stmt_out : Stmt
+        Output statement.
+    """
+    env = get_env()
+
     def _fold_outermost_loop(body):
         stmt = body
         while not isinstance(stmt, tvm.stmt.For):
-            if isinstance(stmt, (tvm.stmt.ProducerConsumer, )):
+            if isinstance(stmt, (tvm.stmt.ProducerConsumer,)):
                 stmt = stmt.body
             else:
                 return None, body, None
@@ -30,7 +47,8 @@ def _post_order(op):
                 args = []
                 args += op.args[:base_args]
                 for i in range(3):
-                    m = tvm.arith.DetectLinearEquation(op.args[i + base_args], [loop_var])
+                    m = tvm.arith.DetectLinearEquation(
+                        op.args[i + base_args], [loop_var])
                     if not m:
                         fail[0] = True
                         return op
@@ -68,7 +86,7 @@ def _visit(op):
     def _do_fold(stmt):
         if (stmt.attr_key == "coproc_uop_scope" and
                 isinstance(stmt.value, tvm.expr.StringImm) and
-                stmt.value.value == VTA_PUSH_UOP.value):
+                stmt.value.value == env.dev.vta_push_uop.value):
             body = stmt.body
             begins = []
             ends = []
@@ -98,7 +116,12 @@ def _do_fold(stmt):
 
 
 def cpu_access_rewrite(stmt_in):
-    """Rewrite the code when there is CPU access happening.
+    """Detect CPU access to VTA buffer and get address correctly.
+
+    VTA's buffer is an opaque handle that do not
+    correspond to address in CPU.
+    This pass detect CPU access and rewrite to use pointer
+    returned VTABufferCPUPtr for CPU access.
 
     Parameters
     ----------
@@ -110,6 +133,7 @@ def cpu_access_rewrite(stmt_in):
     stmt_out : Stmt
         Transformed statement
     """
+    env = get_env()
     rw_info = {}
     def _post_order(op):
         if isinstance(op, tvm.stmt.Allocate):
@@ -119,7 +143,8 @@ def _post_order(op):
             new_var = rw_info[buffer_var]
             let_stmt = tvm.make.LetStmt(
                 new_var, tvm.call_extern(
-                    "handle", "VTABufferCPUPtr", CB_HANDLE,
+                    "handle", "VTABufferCPUPtr",
+                    env.dev.command_handle,
                     buffer_var), op.body)
             alloc = tvm.make.Allocate(
                 buffer_var, op.dtype, op.extents,
@@ -147,7 +172,8 @@ def _post_order(op):
     for buffer_var, new_var in rw_info.items():
         stmt = tvm.make.LetStmt(
             new_var, tvm.call_extern(
-                "handle", "VTABufferCPUPtr", CB_HANDLE,
+                "handle", "VTABufferCPUPtr",
+                env.dev.command_handle,
                 buffer_var), stmt)
     return stmt
 
@@ -184,9 +210,6 @@ def _merge_block(slist, body):
             else:
                 raise RuntimeError("unexpected op")
         del slist[:]
-        # n = len(slist)
-        # for i in range(n):
-        #     slist.pop()
         return body
 
     def _pre_order(op):
@@ -220,7 +243,7 @@ def _post_order(op):
 
 
 def inject_skip_copy(stmt_in):
-    """Pass to inject skip copy stmt, used in debug.
+    """Pass to inject skip copy stmt, used for debug purpose.
 
     Parameters
     ----------
@@ -233,7 +256,7 @@ def inject_skip_copy(stmt_in):
         Transformed statement
     """
     def _do_fold(stmt):
-        if stmt.attr_key == "pragma_scope" and stmt.value.value == "skip_dma_copy":
+        if (stmt.attr_key == "pragma_scope" and stmt.value.value == "skip_dma_copy"):
             return tvm.make.Evaluate(0)
         return None
     return tvm.ir_pass.IRTransform(
@@ -286,6 +309,7 @@ def inject_dma_intrin(stmt_in):
     stmt_out : Stmt
         Transformed statement
     """
+    env = get_env()
     def _check_compact(buf):
         ndim = len(buf.shape)
         size = tvm.const(1, buf.shape[0].dtype)
@@ -321,8 +345,9 @@ def _fold_buffer_dim(buf, scope, elem_block):
             x_stride = buf.strides[ndim - base]
             next_base = base
             if not util.equal_const_int(x_stride % elem_block, 0):
-                raise RuntimeError("scope %s need to have block=%d, shape=%s, strides=%s" % (
-                    scope, elem_block, buf.shape, buf.strides))
+                raise RuntimeError(
+                    "scope %s need to have block=%d, shape=%s, strides=%s" % (
+                        scope, elem_block, buf.shape, buf.strides))
             for i in range(base, ndim + 1):
                 k = ndim - i
                 if not util.equal_const_int(x_size * x_stride - buf.strides[k], 0):
@@ -338,7 +363,6 @@ def _fold_buffer_dim(buf, scope, elem_block):
         shape = list(reversed(shape))
         return shape, strides
 
-
     def _get_2d_pattern(buf, elem_width, elem_bytes, dtype, scope, allow_fold):
         elem_block = elem_bytes * 8 // elem_width
         if buf.dtype != dtype:
@@ -425,47 +449,50 @@ def raise_error():
 
     def _inject_copy(src, dst, pad_before, pad_after, pad_value):
         # FIXME: pad_value is ignored...
+        _ = pad_value
         if dst.scope == "global":
             # Store
             if pad_before or pad_after:
                 raise RuntimeError("Do not support copy into DRAM with pad")
-            if src.scope == SCOPE_OUT:
-                elem_width = spec.VTA_INP_WIDTH # output compression to inp type
-                elem_bytes = spec.VTA_INP_ELEM_BYTES # output compression to inp type
-                mem_type = spec.VTA_MEM_ID_OUT
-                data_type = "int%d" % spec.VTA_INP_WIDTH
-                task_qid = spec.VTA_QID_STORE_OUT
+            if src.scope == env.acc_scope:
+                elem_width = env.INP_WIDTH # output compression to inp type
+                elem_bytes = env.INP_ELEM_BYTES # output compression to inp type
+                mem_type = env.dev.MEM_ID_OUT
+                data_type = "int%d" % env.INP_WIDTH
+                task_qid = env.dev.QID_STORE_OUT
             else:
                 raise RuntimeError("Do not support copy %s->dram" % (src.scope))
             _check_compact(src)
             x_size, y_size, x_stride, offset = _get_2d_pattern(
                 dst, elem_width, elem_bytes, data_type, src.scope, allow_fold=True)
             irb = tvm.ir_builder.create()
-            irb.scope_attr(VTA_AXIS, "coproc_scope", get_task_qid(task_qid))
+            irb.scope_attr(env.dev.vta_axis, "coproc_scope",
+                           env.dev.get_task_qid(task_qid))
             irb.emit(tvm.call_extern(
-                "int32", "VTAStoreBuffer2D", CB_HANDLE,
+                "int32", "VTAStoreBuffer2D",
+                env.dev.command_handle,
                 src.access_ptr("r", "int32"),
                 mem_type, dst.data, offset, x_size, y_size, x_stride))
             return irb.get()
         elif src.scope == "global":
-            if dst.scope == SCOPE_OUT:
-                elem_width = spec.VTA_OUT_WIDTH
-                elem_bytes = spec.VTA_OUT_ELEM_BYTES
-                mem_type = spec.VTA_MEM_ID_ACC
-                data_type = "int%d" % spec.VTA_OUT_WIDTH
-                task_qid = spec.VTA_QID_LOAD_OUT
-            elif dst.scope == SCOPE_INP:
-                elem_width = spec.VTA_INP_WIDTH
-                elem_bytes = spec.VTA_INP_ELEM_BYTES
-                mem_type = spec.VTA_MEM_ID_INP
-                data_type = "int%d" % spec.VTA_INP_WIDTH
-                task_qid = spec.VTA_QID_LOAD_INP
-            elif dst.scope == SCOPE_WGT:
-                elem_width = spec.VTA_WGT_WIDTH
-                elem_bytes = spec.VTA_WGT_ELEM_BYTES
-                mem_type = spec.VTA_MEM_ID_WGT
-                data_type = "int%d" % spec.VTA_WGT_WIDTH
-                task_qid = spec.VTA_QID_LOAD_WGT
+            if dst.scope == env.acc_scope:
+                elem_width = env.ACC_WIDTH
+                elem_bytes = env.ACC_ELEM_BYTES
+                mem_type = env.dev.MEM_ID_ACC
+                data_type = "int%d" % env.ACC_WIDTH
+                task_qid = env.dev.QID_LOAD_OUT
+            elif dst.scope == env.inp_scope:
+                elem_width = env.INP_WIDTH
+                elem_bytes = env.INP_ELEM_BYTES
+                mem_type = env.dev.MEM_ID_INP
+                data_type = "int%d" % env.INP_WIDTH
+                task_qid = env.dev.QID_LOAD_INP
+            elif dst.scope == env.wgt_scope:
+                elem_width = env.WGT_WIDTH
+                elem_bytes = env.WGT_ELEM_BYTES
+                mem_type = env.dev.MEM_ID_WGT
+                data_type = "int%d" % env.WGT_WIDTH
+                task_qid = env.dev.QID_LOAD_WGT
             else:
                 raise RuntimeError("Do not support copy dram->%s" % (dst.scope))
             # collect pad statistics
@@ -498,13 +525,16 @@ def _inject_copy(src, dst, pad_before, pad_after, pad_value):
 
             _check_compact(dst)
             x_size, y_size, x_stride, offset = _get_2d_pattern(
-                src, elem_width, elem_bytes, data_type, dst.scope, allow_fold=allow_fold)
+                src, elem_width, elem_bytes, data_type,
+                dst.scope, allow_fold=allow_fold)
 
             irb = tvm.ir_builder.create()
-            irb.scope_attr(VTA_AXIS, "coproc_scope", get_task_qid(task_qid))
+            irb.scope_attr(env.dev.vta_axis, "coproc_scope",
+                           env.dev.get_task_qid(task_qid))
 
             irb.emit(tvm.call_extern(
-                "int32", "VTALoadBuffer2D", CB_HANDLE,
+                "int32", "VTALoadBuffer2D",
+                env.dev.command_handle,
                 src.data, offset, x_size, y_size, x_stride,
                 x_pad_before, y_pad_before,
                 x_pad_after, y_pad_after,
@@ -514,7 +544,7 @@ def _inject_copy(src, dst, pad_before, pad_after, pad_value):
         else:
             raise RuntimeError("Donot support copy %s->%s" % (src.scope, dst.scope))
 
-    return tvm.ir_pass.InjectCopyIntrin(stmt_in, DMA_COPY, _inject_copy)
+    return tvm.ir_pass.InjectCopyIntrin(stmt_in, "dma_copy", _inject_copy)
 
 
 def annotate_alu_coproc_scope(stmt_in):
@@ -530,11 +560,14 @@ def annotate_alu_coproc_scope(stmt_in):
     stmt_out : Stmt
         Transformed statement
     """
+    env = get_env()
     def _do_fold(stmt):
         if (stmt.attr_key == "pragma_scope" and stmt.value.value == "alu"):
             irb = tvm.ir_builder.create()
-            irb.scope_attr(VTA_AXIS, "coproc_scope", get_task_qid(spec.VTA_QID_COMPUTE))
-            irb.scope_attr(VTA_AXIS, "coproc_uop_scope", tvm.make.StringImm("VTAPushALUOp"))
+            irb.scope_attr(env.dev.vta_axis, "coproc_scope",
+                           env.dev.get_task_qid(env.dev.QID_COMPUTE))
+            irb.scope_attr(env.dev.vta_axis, "coproc_uop_scope",
+                           tvm.make.StringImm("VTAPushALUOp"))
             irb.emit(stmt)
             return irb.get()
         elif (stmt.attr_key == "pragma_scope" and stmt.value.value == "skip_alu"):
@@ -560,6 +593,7 @@ def inject_alu_intrin(stmt_in):
     stmt_out : Stmt
         Transformed statement
     """
+    env = get_env()
     def _do_fold(stmt):
         def _equal(x, y):
             return tvm.ir_pass.Equal(tvm.ir_pass.Simplify(x - y), 0)
@@ -618,32 +652,32 @@ def _flatten_loop(src_coeff, dst_coeff, extents):
                 tmp_body = tmp_body.body
             # Derive opcode
             if isinstance(loop_body.value, tvm.expr.Add):
-                alu_opcode = spec.VTA_ALU_OPCODE_ADD
+                alu_opcode = env.dev.ALU_OPCODE_ADD
                 lhs = loop_body.value.a
                 rhs = loop_body.value.b
             elif isinstance(loop_body.value, tvm.expr.Sub):
-                alu_opcode = spec.VTA_ALU_OPCODE_SUB
+                alu_opcode = env.dev.ALU_OPCODE_SUB
                 lhs = loop_body.value.a
                 rhs = loop_body.value.b
             elif isinstance(loop_body.value, tvm.expr.Mul):
-                alu_opcode = spec.VTA_ALU_OPCODE_MUL
+                alu_opcode = env.dev.ALU_OPCODE_MUL
                 lhs = loop_body.value.a
                 rhs = loop_body.value.b
             elif isinstance(loop_body.value, tvm.expr.Min):
-                alu_opcode = spec.VTA_ALU_OPCODE_MIN
+                alu_opcode = env.dev.ALU_OPCODE_MIN
                 lhs = loop_body.value.a
                 rhs = loop_body.value.b
             elif isinstance(loop_body.value, tvm.expr.Max):
-                alu_opcode = spec.VTA_ALU_OPCODE_MAX
+                alu_opcode = env.dev.ALU_OPCODE_MAX
                 lhs = loop_body.value.a
                 rhs = loop_body.value.b
             elif isinstance(loop_body.value, tvm.expr.Call):
                 if loop_body.value.name == 'shift_left':
-                    alu_opcode = spec.VTA_ALU_OPCODE_SHR
+                    alu_opcode = env.dev.ALU_OPCODE_SHR
                     lhs = loop_body.value.args[0]
                     rhs = tvm.ir_pass.Simplify(-loop_body.value.args[1])
                 elif loop_body.value.name == 'shift_right':
-                    alu_opcode = spec.VTA_ALU_OPCODE_SHR
+                    alu_opcode = env.dev.ALU_OPCODE_SHR
                     lhs = loop_body.value.args[0]
                     rhs = loop_body.value.args[1]
                 else:
@@ -696,26 +730,26 @@ def _flatten_loop(src_coeff, dst_coeff, extents):
             extents = list(extents)
             assert len(src_coeff) > 1
             assert len(dst_coeff) > 1
-            assert len(extents) > 0
+            assert len(extents) != 0
             assert tvm.ir_pass.Equal(
                 tvm.ir_pass.Simplify(
-                    src_coeff[-1]%(spec.VTA_BATCH*spec.VTA_BLOCK_OUT)), 0)
+                    src_coeff[-1] % (env.BATCH * env.BLOCK_OUT)), 0)
             assert tvm.ir_pass.Equal(
                 tvm.ir_pass.Simplify(
-                    dst_coeff[-1]%(spec.VTA_BATCH*spec.VTA_BLOCK_OUT)), 0)
+                    dst_coeff[-1] % (env.BATCH * env.BLOCK_OUT)), 0)
             assert tvm.ir_pass.Equal(src_coeff[-2], 1)
             assert tvm.ir_pass.Equal(dst_coeff[-2], 1)
-            if spec.VTA_BATCH > 1:
+            if env.BATCH > 1:
                 assert len(src_coeff) > 2
                 assert len(dst_coeff) > 2
                 assert len(extents) > 1
-                assert tvm.ir_pass.Equal(src_coeff[-3], spec.VTA_BLOCK_OUT)
-                assert tvm.ir_pass.Equal(dst_coeff[-3], spec.VTA_BLOCK_OUT)
+                assert tvm.ir_pass.Equal(src_coeff[-3], env.BLOCK_OUT)
+                assert tvm.ir_pass.Equal(dst_coeff[-3], env.BLOCK_OUT)
 
             # Apply tensorization of the loop coefficients
             src_offset = src_coeff[-1]
             dst_offset = dst_coeff[-1]
-            if spec.VTA_BATCH == 1:
+            if env.BATCH == 1:
                 src_coeff = src_coeff[:-2]
                 dst_coeff = dst_coeff[:-2]
                 extents = extents[:-1]
@@ -726,9 +760,9 @@ def _flatten_loop(src_coeff, dst_coeff, extents):
             src_coeff.append(src_offset)
             dst_coeff.append(dst_offset)
             src_coeff = [
-                tvm.ir_pass.Simplify(c/(spec.VTA_BATCH*spec.VTA_BLOCK_OUT)) for c in src_coeff]
+                tvm.ir_pass.Simplify(c // (env.BATCH * env.BLOCK_OUT)) for c in src_coeff]
             dst_coeff = [
-                tvm.ir_pass.Simplify(c/(spec.VTA_BATCH*spec.VTA_BLOCK_OUT)) for c in dst_coeff]
+                tvm.ir_pass.Simplify(c // (env.BATCH * env.BLOCK_OUT)) for c in dst_coeff]
 
             # Flatten the outer loops
             if extents:
@@ -750,13 +784,25 @@ def _flatten_loop(src_coeff, dst_coeff, extents):
                 irb.emit(tvm.call_extern(
                     "int32", "VTAUopLoopEnd"))
             return irb.get()
-
         return stmt
 
     stmt_out = tvm.ir_pass.IRTransform(
         stmt_in, None, _do_fold, ["AttrStmt"])
     return stmt_out
 
+
 def debug_print(stmt):
-    print stmt
+    """A debug pass that print the stmt
+
+    Parameters
+    ----------
+    stmt : Stmt
+        The input statement
+
+    Returns
+    -------
+    stmt : Stmt
+        The
+    """
+    print(stmt)
     return stmt
diff --git a/vta/python/vta/mock.py b/vta/python/vta/mock.py
deleted file mode 100644
index 3b50bb8c7a2c..000000000000
--- a/vta/python/vta/mock.py
+++ /dev/null
@@ -1,7 +0,0 @@
-"""Mock interface for skip part of compute """
-from .intrin import intrin_gevm, intrin_gemm
-
-GEMM = intrin_gemm(True)
-GEVM = intrin_gevm(True)
-DMA_COPY = "skip_dma_copy"
-ALU = "skip_alu"
diff --git a/vta/python/vta/rpc_client.py b/vta/python/vta/rpc_client.py
index fb51113e1d62..a6355a592279 100644
--- a/vta/python/vta/rpc_client.py
+++ b/vta/python/vta/rpc_client.py
@@ -1,6 +1,7 @@
 """VTA RPC client function"""
 import os
-from . import hw_spec as spec
+
+from .environment import get_env
 
 def reconfig_runtime(remote):
     """Reconfigure remote runtime based on current hardware spec.
@@ -10,6 +11,7 @@ def reconfig_runtime(remote):
     remote : RPCSession
         The TVM RPC session
     """
+    env = get_env()
     keys = ["VTA_LOG_WGT_WIDTH",
             "VTA_LOG_INP_WIDTH",
             "VTA_LOG_ACC_WIDTH",
@@ -22,9 +24,10 @@ def reconfig_runtime(remote):
             "VTA_LOG_WGT_BUFF_SIZE",
             "VTA_LOG_ACC_BUFF_SIZE",
             "VTA_LOG_OUT_BUFF_SIZE"]
-    cflags = ["-D%s" % spec.VTA_TARGET]
+
+    cflags = ["-DVTA_%s_TARGET" % env.target.upper()]
     for k in keys:
-        cflags += ["-D%s=%s" % (k, str(getattr(spec, k)))]
+        cflags += ["-D%s=%s" % (k, str(getattr(env, k[4:])))]
     freconfig = remote.get_function("tvm.contrib.vta.reconfig_runtime")
     freconfig(" ".join(cflags))
 
diff --git a/vta/python/vta/runtime.py b/vta/python/vta/runtime.py
deleted file mode 100644
index dfbdfe670e8b..000000000000
--- a/vta/python/vta/runtime.py
+++ /dev/null
@@ -1,41 +0,0 @@
-"""Runtime function related hooks"""
-from __future__ import absolute_import as _abs
-
-import tvm
-
-def thread_local_command_buffer():
-    """Get thread local command buffer"""
-    ctx = tvm.call_extern("handle", "VTATLSCommandHandle")
-    return tvm.make.Call(
-        "handle", "tvm_thread_context", [ctx], tvm.expr.Call.Intrinsic, None, 0)
-
-CB_HANDLE = thread_local_command_buffer()
-
-VTA_AXIS = tvm.thread_axis("vta")
-VTA_PUSH_UOP = tvm.make.StringImm("VTAPushGEMMOp")
-
-SCOPE_INP = "local.inp_buffer"
-SCOPE_OUT = "local.out_buffer"
-SCOPE_WGT = "local.wgt_buffer"
-DMA_COPY = "dma_copy"
-ALU = "alu"
-DEBUG_NO_SYNC = False
-
-def get_task_qid(qid):
-    """Get transformed queue index."""
-    return 1 if DEBUG_NO_SYNC else qid
-
-@tvm.register_func("tvm.intrin.rule.default.vta.coproc_sync")
-def coproc_sync(op):
-    return tvm.call_extern(
-        "int32", "VTASynchronize", CB_HANDLE, 1<<31)
-
-@tvm.register_func("tvm.intrin.rule.default.vta.coproc_dep_push")
-def coproc_dep_push(op):
-    return tvm.call_extern(
-        "int32", "VTADepPush", CB_HANDLE, op.args[0], op.args[1])
-
-@tvm.register_func("tvm.intrin.rule.default.vta.coproc_dep_pop")
-def coproc_dep_pop(op):
-    return tvm.call_extern(
-        "int32", "VTADepPop", CB_HANDLE, op.args[0], op.args[1])
diff --git a/vta/python/vta/vta_conv2d.py b/vta/python/vta/vta_conv2d.py
index 97fa42bf1b96..0baca7ba5b5e 100644
--- a/vta/python/vta/vta_conv2d.py
+++ b/vta/python/vta/vta_conv2d.py
@@ -7,17 +7,13 @@
 import topi
 
 from nnvm.top import registry as reg, OpPattern
+from . import environment as vta
 
-from . import intrin, runtime as vta
-from intrin import GEVM
-
-TARGET_BOARD = "llvm -mtriple=armv7-none-linux-gnueabihf -mcpu=cortex-a9 -mattr=+neon"
 
 Workload = namedtuple("Conv2DWorkload",
                       ['height', 'width', 'in_filter', 'out_filter',
                        'hkernel', 'wkernel', 'hpad', 'wpad', 'hstride', 'wstride'])
 
-
 def packed_conv2d(data,
                   kernel,
                   padding,
@@ -58,10 +54,9 @@ def packed_conv2d(data,
 def _build(funcs, target, target_host):
     tvm_t = tvm.target.create(target)
     if tvm_t.device_name == "vta":
-        return tvm.build(funcs, target="ext_dev",
-                         target_host=TARGET_BOARD)
-    elif tvm_t.device_name == "rasp" or tvm_t.device_name == "tcpu":
-        return tvm.build(funcs, target=TARGET_BOARD)
+        return tvm.build(funcs, target="ext_dev", target_host=target_host)
+    elif tvm_t.device_name == "rasp" or tvm_t.device_name == "vta-cpu":
+        return tvm.build(funcs, target=target_host)
     return tvm.build(funcs, target=target)
 
 
@@ -224,36 +219,39 @@ def _traverse(op):
     wrkld = _get_workload(data, pad_data, kernel, output)
 
     plan = _WL2PLAN[wrkld]
-    load_inp = load_wgt = load_out = store_out = "dma_copy"
-    alu = "alu"
-    gevm = GEVM
+    env = vta.get_env()
+
+    load_inp = load_wgt = load_out = store_out = env.dma_copy
+    alu = env.alu
+    gevm = env.gevm
 
     # schedule1
     oshape = topi.util.get_const_tuple(output.shape)
     s = tvm.create_schedule(output.op)
 
+
     # setup pad
     if pad_data is not None:
         cdata = pad_data
-        s[pad_data].set_scope(vta.SCOPE_INP)
+        s[pad_data].set_scope(env.inp_scope)
     else:
-        cdata = s.cache_read(data, vta.SCOPE_INP, [conv2d_stage])
-    ckernel = s.cache_read(kernel, vta.SCOPE_WGT, [conv2d_stage])
-    s[conv2d_stage].set_scope(vta.SCOPE_OUT)
+        cdata = s.cache_read(data, env.inp_scope, [conv2d_stage])
+    ckernel = s.cache_read(kernel, env.wgt_scope, [conv2d_stage])
+    s[conv2d_stage].set_scope(env.acc_scope)
     # cache read input
     cache_read_ewise = []
 
     for consumer, tensor in ewise_inputs:
         cache_read_ewise.append(
-            s.cache_read(tensor, vta.SCOPE_OUT, [consumer]))
+            s.cache_read(tensor, env.acc_scope, [consumer]))
     # set ewise scope
     for op in ewise_ops:
-        s[op].set_scope(vta.SCOPE_OUT)
+        s[op].set_scope(env.acc_scope)
         s[op].pragma(s[op].op.axis[0], alu)
 
     # tile
     oc_factor = (plan.oc_factor if plan.oc_factor
-                 else wrkld.out_filter // vta.VTA_BLOCK_OUT)
+                 else wrkld.out_filter // vta.BLOCK_OUT)
     h_factor = (plan.h_factor if plan.h_factor else oshape[2])
     w_factor = (plan.w_factor if plan.w_factor else oshape[3])
 
diff --git a/vta/tests/python/pynq/test_benchmark_conv2d.py b/vta/tests/python/pynq/test_benchmark_conv2d.py
index c4dfe01d7508..7b54e3dc27ce 100644
--- a/vta/tests/python/pynq/test_benchmark_conv2d.py
+++ b/vta/tests/python/pynq/test_benchmark_conv2d.py
@@ -11,9 +11,6 @@
 host = "pynq"
 port = 9091
 target = "llvm -target=armv7-none-linux-gnueabihf -mattr=+neon"
-out_dtype = "int%d" % vta.VTA_OUT_WIDTH
-inp_dtype = "int%d" % vta.VTA_INP_WIDTH
-wgt_dtype = "int%d" % vta.VTA_WGT_WIDTH
 
 Workload = namedtuple("Conv2DWorkload",
                       ['batch', 'height', 'width', 'in_filter', 'out_filter',
@@ -46,12 +43,13 @@ def __str__(self):
 Schedule = Conv2DSchedule
 
 def get_insn_count(layer, sched):
+    env = vta.get_env()
     b, h, w, ci, co = sched
     b_factor = b
     h_factor = layer.height / h
     w_factor = layer.width / w
-    ci_factor = int(np.ceil(float(layer.in_filter) / (ci * vta.VTA_BLOCK_IN)))
-    co_factor = int(np.ceil(float(layer.out_filter) / (co * vta.VTA_BLOCK_OUT)))
+    ci_factor = int(np.ceil(float(layer.in_filter) / (ci * env.BLOCK_IN)))
+    co_factor = int(np.ceil(float(layer.out_filter) / (co * env.BLOCK_OUT)))
     input_xfers = b_factor * h_factor * w_factor * co_factor * ci_factor
     weight_xfers = b_factor * h_factor * w_factor * co_factor * ci_factor
     output_xfers = b_factor * h_factor * w_factor * co_factor
@@ -62,6 +60,7 @@ def get_insn_count(layer, sched):
     return insn_count
 
 def find_schedules(layer, mtOnly=False, bestOnly=False):
+    env = vta.get_env()
     # Helper function to get factors
     def find_factors(n):
         factors = []
@@ -70,11 +69,11 @@ def find_factors(n):
                 factors.append(i)
         return factors
     # Scheduling exploration
-    batch_factors = find_factors(int(np.ceil(float(layer.batch) / vta.VTA_BATCH)))
+    batch_factors = find_factors(int(np.ceil(float(layer.batch) / env.BATCH)))
     height_factors = find_factors(layer.height / layer.hstride)
     width_factors = find_factors(layer.width / layer.wstride)
-    cin_factors = find_factors(int(np.ceil(float(layer.in_filter) / vta.VTA_BLOCK_IN)))
-    cout_factors = find_factors(int(np.ceil(float(layer.out_filter) / vta.VTA_BLOCK_OUT)))
+    cin_factors = find_factors(int(np.ceil(float(layer.in_filter) / env.BLOCK_IN)))
+    cout_factors = find_factors(int(np.ceil(float(layer.out_filter) / env.BLOCK_OUT)))
     ht_factors = [1, 2]
     cot_factors = [1, 2]
     # Explore schedules
@@ -90,12 +89,12 @@ def find_factors(n):
                             if ci == 1:
                                 schedules.append([b, h, w, ci, co])
     # Filter the schedules that wouldn't work in the available BRAM sizes
-    input_elem_size_b = vta.VTA_BATCH * vta.VTA_BLOCK_IN * vta.VTA_INP_WIDTH
-    weight_elem_size_b = vta.VTA_BLOCK_IN * vta.VTA_BLOCK_OUT * vta.VTA_WGT_WIDTH
-    output_elem_size_b = vta.VTA_BATCH * vta.VTA_BLOCK_OUT * vta.VTA_OUT_WIDTH
-    input_brams_capacity_b = vta.VTA_INP_BUFF_SIZE * 8
-    weight_brams_capacity_b = vta.VTA_WGT_BUFF_SIZE * 8
-    output_brams_capacity_b = vta.VTA_OUT_BUFF_SIZE * 8
+    input_elem_size_b = env.BATCH * env.BLOCK_IN * env.INP_WIDTH
+    weight_elem_size_b = env.BLOCK_IN * env.BLOCK_OUT * env.WGT_WIDTH
+    output_elem_size_b = env.BATCH * env.BLOCK_OUT * env.OUT_WIDTH
+    input_brams_capacity_b = env.INP_BUFF_SIZE * 8
+    weight_brams_capacity_b = env.WGT_BUFF_SIZE * 8
+    output_brams_capacity_b = env.OUT_BUFF_SIZE * 8
     fil_sched = []
     xfer_size = []
     for sched in schedules:
@@ -125,7 +124,7 @@ def find_factors(n):
                         if input_tile_elems*input_elem_size_b <= input_brams_capacity_b/(cot*ht) and \
                            weight_tile_elems*weight_elem_size_b <= weight_brams_capacity_b and \
                            output_tile_elems*output_elem_size_b <= output_brams_capacity_b/(cot*ht) and \
-                           insn_count <= vta.VTA_MAX_XFER / 16 and \
+                           insn_count <= env.MAX_XFER / 16 and \
                            h > 2 and w > 2:
                             schedule = Schedule(oc_factor=co, ko_factor=ci, h_factor=h,
                                                 w_factor=w, oc_nthread=cot, h_nthread=ht)
@@ -137,10 +136,11 @@ def find_factors(n):
         return fil_sched
 
 def get_data_movementB(sched, layer):
+    env = vta.get_env()
     # Derive data movement
-    input_elem_size_b = vta.VTA_BATCH * vta.VTA_BLOCK_IN * vta.VTA_INP_WIDTH
-    weight_elem_size_b = vta.VTA_BLOCK_IN * vta.VTA_BLOCK_OUT * vta.VTA_WGT_WIDTH
-    output_elem_size_b = vta.VTA_BATCH * vta.VTA_BLOCK_OUT * vta.VTA_OUT_WIDTH
+    input_elem_size_b = env.BATCH * env.BLOCK_IN * env.INP_WIDTH
+    weight_elem_size_b = env.BLOCK_IN * env.BLOCK_OUT * env.WGT_WIDTH
+    output_elem_size_b = env.BATCH * env.BLOCK_OUT * env.OUT_WIDTH
     b = sched.b_factor
     h = sched.h_factor
     w = sched.w_factor
@@ -154,11 +154,11 @@ def get_data_movementB(sched, layer):
     weight_tile_elems = layer.hkernel * layer.wkernel * ci
     output_tile_elems = b * h * w * co
     # Derive factors
-    b_factor = int(np.ceil(float(layer.batch) / (b * vta.VTA_BATCH)))
+    b_factor = int(np.ceil(float(layer.batch) / (b * env.BATCH)))
     h_factor = (layer.height / layer.hstride) / h
     w_factor = (layer.width / layer.wstride) / w
-    ci_factor = int(np.ceil(float(layer.in_filter) / (ci * vta.VTA_BLOCK_IN)))
-    co_factor = int(np.ceil(float(layer.out_filter) / (co * vta.VTA_BLOCK_OUT)))
+    ci_factor = int(np.ceil(float(layer.in_filter) / (ci * env.BLOCK_IN)))
+    co_factor = int(np.ceil(float(layer.out_filter) / (co * env.BLOCK_OUT)))
     # Derive transfers
     input_xfers = b_factor * h_factor * w_factor * co_factor * ci_factor
     weight_xfers = b_factor * h_factor * w_factor * co_factor * ci_factor
@@ -171,19 +171,20 @@ def get_data_movementB(sched, layer):
     return total_xfer_B
 
 def test_conv2d_chwv(layer, key, batch_size, wl, sched, log_frame, profile=True):
-    assert batch_size % vta.VTA_BATCH == 0
-    assert wl.in_filter % vta.VTA_BLOCK_IN == 0
-    assert wl.out_filter % vta.VTA_BLOCK_OUT == 0
-    data_shape = (batch_size//vta.VTA_BATCH, wl.in_filter//vta.VTA_BLOCK_IN,
-                  wl.height, wl.width, vta.VTA_BATCH, vta.VTA_BLOCK_IN)
-    kernel_shape = (wl.out_filter//vta.VTA_BLOCK_OUT, wl.in_filter//vta.VTA_BLOCK_IN,
-                    wl.hkernel, wl.wkernel, vta.VTA_BLOCK_OUT, vta.VTA_BLOCK_IN)
+    env = vta.get_env()
+    assert batch_size % env.BATCH == 0
+    assert wl.in_filter % env.BLOCK_IN == 0
+    assert wl.out_filter % env.BLOCK_OUT == 0
+    data_shape = (batch_size//env.BATCH, wl.in_filter//env.BLOCK_IN,
+                  wl.height, wl.width, env.BATCH, env.BLOCK_IN)
+    kernel_shape = (wl.out_filter//env.BLOCK_OUT, wl.in_filter//env.BLOCK_IN,
+                    wl.hkernel, wl.wkernel, env.BLOCK_OUT, env.BLOCK_IN)
     fout_height = (wl.height + 2 * wl.hpad - wl.hkernel) // wl.hstride + 1
     fout_width = (wl.width + 2 * wl.wpad - wl.wkernel) // wl.wstride + 1
-    res_shape = (batch_size//vta.VTA_BATCH, wl.out_filter//vta.VTA_BLOCK_OUT,
-                 fout_height, fout_width, vta.VTA_BATCH, vta.VTA_BLOCK_OUT)
-    data = tvm.placeholder(data_shape, name="data", dtype=inp_dtype)
-    kernel = tvm.placeholder(kernel_shape, name="kernel", dtype=wgt_dtype)
+    res_shape = (batch_size//env.BATCH, wl.out_filter//env.BLOCK_OUT,
+                 fout_height, fout_width, env.BATCH, env.BLOCK_OUT)
+    data = tvm.placeholder(data_shape, name="data", dtype=env.inp_dtype)
+    kernel = tvm.placeholder(kernel_shape, name="kernel", dtype=env.wgt_dtype)
     if wl.hpad or wl.wpad:
         data_buf = topi.nn.pad(data, [0, 0, wl.hpad, wl.wpad, 0, 0], name="data_buf")
     else:
@@ -191,22 +192,22 @@ def test_conv2d_chwv(layer, key, batch_size, wl, sched, log_frame, profile=True)
     kernel_buf = tvm.compute(kernel_shape, lambda *i: kernel(*i), "kernel_buf")
     di = tvm.reduce_axis((0, wl.hkernel), name='di')
     dj = tvm.reduce_axis((0, wl.wkernel), name='dj')
-    ko = tvm.reduce_axis((0, wl.in_filter//vta.VTA_BLOCK_IN), name='ko')
-    ki = tvm.reduce_axis((0, vta.VTA_BLOCK_IN), name='ki')
+    ko = tvm.reduce_axis((0, wl.in_filter//env.BLOCK_IN), name='ko')
+    ki = tvm.reduce_axis((0, env.BLOCK_IN), name='ki')
     res_cnv = tvm.compute(
         res_shape,
         lambda bo, co, i, j, bi, ci: tvm.sum(
-            data_buf[bo, ko, i*wl.hstride+di, j*wl.wstride+dj, bi, ki].astype(out_dtype) *
-            kernel_buf[co, ko, di, dj, ci, ki].astype(out_dtype),
+            data_buf[bo, ko, i*wl.hstride+di, j*wl.wstride+dj, bi, ki].astype(env.acc_dtype) *
+            kernel_buf[co, ko, di, dj, ci, ki].astype(env.acc_dtype),
         axis=[ko, di, dj, ki]),
         name="res_cnv")
     res_shf = tvm.compute(res_shape, lambda *i: res_cnv(*i) >> 8, name="res_shf")
-    res = tvm.compute(res_shape, lambda *i: res_shf(*i).astype(inp_dtype), name="res")
+    res = tvm.compute(res_shape, lambda *i: res_shf(*i).astype(env.inp_dtype), name="res")
     num_ops = batch_size * fout_height * fout_width * wl.hkernel * wl.wkernel * wl.out_filter * wl.in_filter
     total_xfer_B = get_data_movementB(sched, wl)
 
     def verify(s, check_correctness):
-        mod = tvm.build(s, [data, kernel, res], "ext_dev", target, name="conv2d")
+        mod = vta.build(s, [data, kernel, res], "ext_dev", target, name="conv2d")
         temp = util.tempdir()
         remote = rpc.connect(host, port)
         mod.save(temp.relpath("conv2d.o"))
@@ -220,12 +221,12 @@ def verify(s, check_correctness):
         kernel_orig = np.random.randint(
             -128, 128, size=(wl.out_filter, wl.in_filter, wl.hkernel, wl.wkernel)).astype(kernel.dtype)
         data_packed = data_orig.reshape(
-            batch_size//vta.VTA_BATCH, vta.VTA_BATCH,
-            wl.in_filter//vta.VTA_BLOCK_IN, vta.VTA_BLOCK_IN,
+            batch_size//env.BATCH, env.BATCH,
+            wl.in_filter//env.BLOCK_IN, env.BLOCK_IN,
             wl.height, wl.width).transpose((0, 2, 4, 5, 1, 3))
         kernel_packed = kernel_orig.reshape(
-            wl.out_filter//vta.VTA_BLOCK_OUT, vta.VTA_BLOCK_OUT,
-            wl.in_filter//vta.VTA_BLOCK_IN, vta.VTA_BLOCK_IN,
+            wl.out_filter//env.BLOCK_OUT, env.BLOCK_OUT,
+            wl.in_filter//env.BLOCK_IN, env.BLOCK_IN,
             wl.hkernel, wl.wkernel).transpose((0, 2, 4, 5, 1, 3))
         res_np = np.zeros(res_shape).astype(res.dtype)
         data_arr = tvm.nd.array(data_packed, ctx)
@@ -237,13 +238,13 @@ def verify(s, check_correctness):
             (0, 4, 1, 5, 2, 3)).reshape(batch_size, wl.out_filter, fout_height, fout_width)
         if check_correctness:
             res_ref = mx.nd.Convolution(
-                mx.nd.array(data_orig.astype(out_dtype), mx.cpu(0)),
-                mx.nd.array(kernel_orig.astype(out_dtype), mx.cpu(0)),
+                mx.nd.array(data_orig.astype(env.acc_dtype), mx.cpu(0)),
+                mx.nd.array(kernel_orig.astype(env.acc_dtype), mx.cpu(0)),
                 stride=(wl.hstride, wl.wstride),
                 kernel=(wl.hkernel, wl.wkernel),
                 num_filter=wl.out_filter,
                 no_bias=True,
-                pad=(wl.hpad, wl.wpad)).asnumpy().astype(out_dtype)
+                pad=(wl.hpad, wl.wpad)).asnumpy().astype(env.acc_dtype)
             res_ref = np.right_shift(res_ref, 8).astype(res.dtype)
             np.testing.assert_allclose(res_unpack, res_ref)
             print("Correctness check pass...")
@@ -253,13 +254,13 @@ def run_schedule(load_inp, load_wgt, gemm, alu, store_out,
                      print_ir, check_correctness):
         # schedule1
         s = tvm.create_schedule(res.op)
-        s[data_buf].set_scope(vta.SCOPE_INP)
-        s[kernel_buf].set_scope(vta.SCOPE_WGT)
-        s[res_cnv].set_scope(vta.SCOPE_OUT)
-        s[res_shf].set_scope(vta.SCOPE_OUT)
+        s[data_buf].set_scope(env.inp_scope)
+        s[kernel_buf].set_scope(env.wgt_scope)
+        s[res_cnv].set_scope(env.acc_scope)
+        s[res_shf].set_scope(env.acc_scope)
         # tile
         oc_factor = (sched.oc_factor if sched.oc_factor
-                     else wl.out_filter // vta.VTA_BLOCK_OUT)
+                     else wl.out_filter // env.BLOCK_OUT)
         h_factor = (sched.h_factor if sched.h_factor else fout_height)
         w_factor = (sched.w_factor if sched.w_factor else fout_width)
         xbo, xco, xi, xj, xbi, xci = s[res].op.axis
@@ -304,8 +305,8 @@ def conv_normal(print_ir):
         def run_test(header, print_ir, check_correctness):
             s = [1, sched.oc_factor, sched.ko_factor, sched.h_factor, sched.w_factor]
             cost = run_schedule(
-                vta.DMA_COPY, vta.DMA_COPY,
-                vta.GEMM, vta.ALU, vta.DMA_COPY,
+                env.dma_copy, env.dma_copy,
+                env.gemm, env.alu, env.dma_copy,
                 print_ir, check_correctness)
             gops = (num_ops / cost.mean) / float(10 ** 9)
             print(header)
@@ -316,15 +317,15 @@ def run_test(header, print_ir, check_correctness):
             log_frame["total-gops"].append(gops)
             log_frame["total-cost"].append(cost.mean)
             log_frame["total-insn"].append(get_insn_count(wl, s))
-            log_frame["block-batch"].append(vta.VTA_BATCH)
-            log_frame["block-in"].append(vta.VTA_BLOCK_IN)
-            log_frame["block-out"].append(vta.VTA_BLOCK_OUT)
-            log_frame["inp-width"].append(vta.VTA_INP_WIDTH)
-            log_frame["wgt-width"].append(vta.VTA_WGT_WIDTH)
-            log_frame["uop-size"].append(vta.VTA_UOP_BUFF_SIZE)
-            log_frame["inp-size"].append(vta.VTA_INP_BUFF_SIZE)
-            log_frame["wgt-size"].append(vta.VTA_WGT_BUFF_SIZE)
-            log_frame["out-size"].append(vta.VTA_OUT_BUFF_SIZE)
+            log_frame["block-batch"].append(env.BATCH)
+            log_frame["block-in"].append(env.BLOCK_IN)
+            log_frame["block-out"].append(env.BLOCK_OUT)
+            log_frame["inp-width"].append(env.INP_WIDTH)
+            log_frame["wgt-width"].append(env.WGT_WIDTH)
+            log_frame["uop-size"].append(env.UOP_BUFF_SIZE)
+            log_frame["inp-size"].append(env.INP_BUFF_SIZE)
+            log_frame["wgt-size"].append(env.WGT_BUFF_SIZE)
+            log_frame["out-size"].append(env.OUT_BUFF_SIZE)
             log_frame["oc-factor"].append(sched.oc_factor)
             log_frame["ic-factor"].append(sched.ko_factor)
             log_frame["h-factor"].append(sched.h_factor)
@@ -333,16 +334,16 @@ def run_test(header, print_ir, check_correctness):
             log_frame["h-threads"].append(sched.h_nthread)
             log_frame["threaded"].append(True if sched.oc_nthread > 1 or sched.h_nthread > 1 else False)
 
-        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+        with vta.build_config():
             run_test("NORMAL", print_ir, True)
 
     def skip_alu_unittest(print_ir):
-        mock = vta.mock
+        mock = env.mock
         print("----- Skip ALU Unit Test-------")
         def run_test(header, print_ir):
             cost = run_schedule(
-                vta.DMA_COPY, vta.DMA_COPY,
-                vta.GEMM, mock.ALU, vta.DMA_COPY,
+                env.dma_copy, env.dma_copy,
+                env.gemm, mock.alu, env.dma_copy,
                 print_ir, False)
             gops = (num_ops / cost.mean) / float(10 ** 9)
             print(header)
@@ -350,118 +351,118 @@ def run_test(header, print_ir):
             log_frame["skip-alu-gops"].append(gops)
             log_frame["skip-alu-cost"].append(cost.mean)
 
-        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+        with vta.build_config():
             run_test("NORMAL", print_ir)
         print("")
 
     def gemm_unittest(print_ir):
-        mock = vta.mock
+        mock = env.mock
         print("----- GEMM Unit Test-------")
         def run_test(header, print_ir):
             cost = run_schedule(
-                mock.DMA_COPY, mock.DMA_COPY,
-                vta.GEMM, mock.ALU, mock.DMA_COPY,
+                mock.dma_copy, mock.dma_copy,
+                env.gemm, mock.alu, mock.dma_copy,
                 print_ir, False)
             gops = (num_ops / cost.mean) / float(10 ** 9)
             print(header)
             print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
             log_frame["gemm-gops"].append(gops)
             log_frame["gemm-cost"].append(cost.mean)
-        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+        with vta.build_config():
             run_test("NORMAL", print_ir)
         print("")
 
     def alu_unittest(print_ir):
-        mock = vta.mock
+        mock = env.mock
         print("----- ALU Unit Test-------")
         def run_test(header, print_ir):
             cost = run_schedule(
-                mock.DMA_COPY, mock.DMA_COPY,
-                mock.GEMM, vta.ALU, mock.DMA_COPY,
+                mock.dma_copy, mock.dma_copy,
+                env.gemm, env.alu, mock.dma_copy,
                 print_ir, False)
             gops = (num_ops / cost.mean) / float(10 ** 9)
             print(header)
             print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
             log_frame["alu-gops"].append(gops)
             log_frame["alu-cost"].append(cost.mean)
-        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+        with vta.build_config():
             run_test("NORMAL", print_ir)
         print("")
 
     def load_inp_unittest(print_ir):
-        mock = vta.mock
+        mock = env.mock
         print("----- LoadInp Unit Test-------")
         def run_test(header, print_ir):
             cost = run_schedule(
-                vta.DMA_COPY, mock.DMA_COPY,
-                mock.GEMM, mock.ALU, mock.DMA_COPY,
+                env.dma_copy, mock.dma_copy,
+                env.gemm, mock.alu, mock.dma_copy,
                 print_ir, False)
             gops = (num_ops / cost.mean) / float(10 ** 9)
             bandwith = (batch_size * wl.in_filter * wl.height *
-                        wl.width * vta.INP_WIDTH / cost.mean) / float(10 ** 9)
+                        wl.width * env.INP_WIDTH / cost.mean) / float(10 ** 9)
             print(header)
             print("\tTime cost = %g sec/op, %g GFLOPS, bandwith=%g gbits" % (
                 cost.mean, gops, bandwith))
             log_frame["ld-inp-gbits"].append(bandwith)
             log_frame["ld-inp-cost"].append(cost.mean)
-        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+        with vta.build_config():
             run_test("NORMAL", print_ir)
         print("")
 
     def load_wgt_unittest(print_ir):
-        mock = vta.mock
+        mock = env.mock
         print("----- LoadWgt Unit Test-------")
         def run_test(header, print_ir):
             cost = run_schedule(
-                mock.DMA_COPY, vta.DMA_COPY,
-                mock.GEMM, mock.ALU, mock.DMA_COPY, print_ir,
+                mock.dma_copy, env.dma_copy,
+                env.gemm, mock.alu, mock.dma_copy, print_ir,
                 False)
             gops = (num_ops / cost.mean) / float(10 ** 9)
             bandwith = (wl.out_filter * wl.in_filter * wl.hkernel *
-                        wl.wkernel * vta.WGT_WIDTH / cost.mean) / float(10 ** 9)
+                        wl.wkernel * env.WGT_WIDTH / cost.mean) / float(10 ** 9)
             print(header)
             print("\tTime cost = %g sec/op, %g GFLOPS, bandwith=%g gbits" % (
                 cost.mean, gops, bandwith))
             log_frame["ld-wgt-gbits"].append(bandwith)
             log_frame["ld-wgt-cost"].append(cost.mean)
-        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+        with vta.build_config():
             run_test("NORMAL", print_ir)
         print("")
 
     def store_out_unittest(print_ir):
-        mock = vta.mock
+        mock = env.mock
         print("----- StoreOut Unit Test-------")
         def run_test(header, print_ir):
             cost = run_schedule(
-                mock.DMA_COPY, mock.DMA_COPY,
-                mock.GEMM, mock.ALU, vta.DMA_COPY, print_ir,
+                mock.dma_copy, mock.dma_copy,
+                env.gemm, mock.alu, env.dma_copy, print_ir,
                 False)
             gops = (num_ops / cost.mean) / float(10 ** 9)
             bandwith = (batch_size * wl.out_filter * fout_height *
-                        fout_width * vta.OUT_WIDTH / cost.mean) / float(10 ** 9)
+                        fout_width * env.OUT_WIDTH / cost.mean) / float(10 ** 9)
             print(header)
             print("\tTime cost = %g sec/op, %g GFLOPS, bandwith=%g gbits" % (
                 cost.mean, gops, bandwith))
             log_frame["st-out-gbits"].append(bandwith)
             log_frame["st-out-cost"].append(cost.mean)
-        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+        with vta.build_config():
             run_test("NORMAL", print_ir)
         print("")
 
     def manual_unittest(print_ir):
         # Manual section used to teak the components
-        mock = vta.mock
+        mock = env.mock
         print("----- Manual Unit Test-------")
         def run_test(header, print_ir):
             cost = run_schedule(
-                vta.DMA_COPY, vta.DMA_COPY,
-                vta.GEMM, vta.ALU, mock.DMA_COPY, print_ir,
+                env.dma_copy, env.dma_copy,
+                env.gemm, env.alu, mock.dma_copy, print_ir,
                 False)
             gops = (num_ops / cost.mean) / float(10 ** 9)
             print(header)
             print("\tTime cost = %g sec/op, %g GFLOPS" % (
                 cost.mean, gops))
-        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+        with vta.build_config():
             run_test("NORMAL", print_ir)
         print("")
 
diff --git a/vta/tests/python/pynq/test_benchmark_gemm.py b/vta/tests/python/pynq/test_benchmark_gemm.py
index e5180c1440db..0f1d4d217fe4 100644
--- a/vta/tests/python/pynq/test_benchmark_gemm.py
+++ b/vta/tests/python/pynq/test_benchmark_gemm.py
@@ -8,34 +8,33 @@
 host = "pynq"
 port = 9091
 target = "llvm -target=armv7-none-linux-gnueabihf"
-out_dtype = "int%d" % vta.VTA_OUT_WIDTH
-inp_dtype = "int%d" % vta.VTA_INP_WIDTH
-wgt_dtype = "int%d" % vta.VTA_WGT_WIDTH
+
 
 def test_gemm_packed(batch_size, channel, block):
-    data_shape = (batch_size//vta.VTA_BATCH,
-                  channel//vta.VTA_BLOCK_IN,
-                  vta.VTA_BATCH,
-                  vta.VTA_BLOCK_IN)
-    weight_shape = (channel//vta.VTA_BLOCK_OUT,
-                    channel//vta.VTA_BLOCK_IN,
-                    vta.VTA_BLOCK_OUT,
-                    vta.VTA_BLOCK_IN)
-    res_shape = (batch_size//vta.VTA_BATCH,
-                 channel//vta.VTA_BLOCK_OUT,
-                 vta.VTA_BATCH,
-                 vta.VTA_BLOCK_OUT)
+    env = vta.get_env()
+    data_shape = (batch_size // env.BATCH,
+                  channel // env.BLOCK_IN,
+                  env.BATCH,
+                  env.BLOCK_IN)
+    weight_shape = (channel // env.BLOCK_OUT,
+                    channel // env.BLOCK_IN,
+                    env.BLOCK_OUT,
+                    env.BLOCK_IN)
+    res_shape = (batch_size // env.BATCH,
+                 channel // env.BLOCK_OUT,
+                 env.BATCH,
+                 env.BLOCK_OUT)
     num_ops = channel * channel * batch_size
 
-    ko = tvm.reduce_axis((0, channel//vta.VTA_BLOCK_IN), name='ko')
-    ki = tvm.reduce_axis((0, vta.VTA_BLOCK_IN), name='ki')
+    ko = tvm.reduce_axis((0, channel // env.BLOCK_IN), name='ko')
+    ki = tvm.reduce_axis((0, env.BLOCK_IN), name='ki')
 
     data = tvm.placeholder(data_shape,
                            name="data",
-                           dtype=inp_dtype)
+                           dtype=env.inp_dtype)
     weight = tvm.placeholder(weight_shape,
                              name="weight",
-                             dtype=wgt_dtype)
+                             dtype=env.wgt_dtype)
     data_buf = tvm.compute(data_shape,
                            lambda *i: data(*i),
                            "data_buf")
@@ -44,8 +43,8 @@ def test_gemm_packed(batch_size, channel, block):
                              "weight_buf")
     res_gem = tvm.compute(res_shape,
                           lambda bo, co, bi, ci: tvm.sum(
-                              data_buf[bo, ko, bi, ki].astype(out_dtype) *
-                              weight_buf[co, ko, ci, ki].astype(out_dtype),
+                              data_buf[bo, ko, bi, ki].astype(env.acc_dtype) *
+                              weight_buf[co, ko, ci, ki].astype(env.acc_dtype),
                               axis=[ko, ki]),
                           name="res_gem")
     res_shf = tvm.compute(res_shape,
@@ -55,14 +54,14 @@ def test_gemm_packed(batch_size, channel, block):
                           lambda *i: tvm.max(res_shf(*i), 0),
                           "res_max") #relu
     res_min = tvm.compute(res_shape,
-                          lambda *i: tvm.min(res_max(*i), (1<<(vta.VTA_INP_WIDTH-1))-1),
+                          lambda *i: tvm.min(res_max(*i), (1<<(env.INP_WIDTH-1))-1),
                           "res_min") #relu
     res = tvm.compute(res_shape,
-                      lambda *i: res_min(*i).astype(inp_dtype),
+                      lambda *i: res_min(*i).astype(env.inp_dtype),
                       name="res")
 
     def verify(s, check_correctness=True):
-        mod = tvm.build(s, [data, weight, res], "ext_dev", target, name="gemm")
+        mod = vta.build(s, [data, weight, res], "ext_dev", target, name="gemm")
         temp = util.tempdir()
         remote = rpc.connect(host, port)
         mod.save(temp.relpath("gemm.o"))
@@ -76,29 +75,29 @@ def verify(s, check_correctness=True):
         weight_orig = np.random.randint(
             -128, 128, size=(channel, channel)).astype(weight.dtype)
         data_packed = data_orig.reshape(
-            batch_size//vta.VTA_BATCH, vta.VTA_BATCH,
-            channel//vta.VTA_BLOCK_IN, vta.VTA_BLOCK_IN).transpose((0, 2, 1, 3))
+            batch_size // env.BATCH, env.BATCH,
+            channel // env.BLOCK_IN, env.BLOCK_IN).transpose((0, 2, 1, 3))
         weight_packed = weight_orig.reshape(
-            channel//vta.VTA_BLOCK_OUT, vta.VTA_BLOCK_OUT,
-            channel//vta.VTA_BLOCK_IN, vta.VTA_BLOCK_IN).transpose((0, 2, 1, 3))
+            channel // env.BLOCK_OUT, env.BLOCK_OUT,
+            channel // env.BLOCK_IN, env.BLOCK_IN).transpose((0, 2, 1, 3))
         res_np = np.zeros(res_shape).astype(res.dtype)
         data_arr = tvm.nd.array(data_packed, ctx)
         weight_arr = tvm.nd.array(weight_packed, ctx)
         res_arr = tvm.nd.array(res_np, ctx)
-        res_ref = np.zeros(res_shape).astype(out_dtype)
-        for b in range(batch_size//vta.VTA_BATCH):
-            for i in range(channel//vta.VTA_BLOCK_OUT):
-                for j in range(channel//vta.VTA_BLOCK_IN):
-                    res_ref[b,i,:] += np.dot(data_packed[b,j,:].astype(out_dtype),
-                                             weight_packed[i,j].T.astype(out_dtype))
+        res_ref = np.zeros(res_shape).astype(env.acc_dtype)
+        for b in range(batch_size // env.BATCH):
+            for i in range(channel // env.BLOCK_OUT):
+                for j in range(channel // env.BLOCK_IN):
+                    res_ref[b,i,:] += np.dot(data_packed[b,j,:].astype(env.acc_dtype),
+                                             weight_packed[i,j].T.astype(env.acc_dtype))
         res_ref = np.right_shift(res_ref, 8)
-        res_ref = np.clip(res_ref, 0, (1<<(vta.VTA_INP_WIDTH-1))-1).astype(res.dtype)
+        res_ref = np.clip(res_ref, 0, (1<<(env.INP_WIDTH-1))-1).astype(res.dtype)
         time_f = f.time_evaluator("gemm", ctx, number=20)
         cost = time_f(data_arr, weight_arr, res_arr)
-        res_unpack = res_arr.asnumpy().reshape(batch_size//vta.VTA_BATCH,
-                                               channel//vta.VTA_BLOCK_OUT,
-                                               vta.VTA_BATCH,
-                                               vta.VTA_BLOCK_OUT)
+        res_unpack = res_arr.asnumpy().reshape(batch_size // env.BATCH,
+                                               channel // env.BLOCK_OUT,
+                                               env.BATCH,
+                                               env.BLOCK_OUT)
         if check_correctness:
             np.testing.assert_allclose(res_unpack, res_ref)
         return cost
@@ -111,17 +110,17 @@ def run_schedule(load_inp,
                      print_ir,
                      check_correctness):
         s = tvm.create_schedule(res.op)
-        s[data_buf].set_scope(vta.SCOPE_INP)
-        s[weight_buf].set_scope(vta.SCOPE_WGT)
-        s[res_gem].set_scope(vta.SCOPE_OUT)
-        s[res_shf].set_scope(vta.SCOPE_OUT)
-        s[res_min].set_scope(vta.SCOPE_OUT)
-        s[res_max].set_scope(vta.SCOPE_OUT)
+        s[data_buf].set_scope(env.inp_scope)
+        s[weight_buf].set_scope(env.wgt_scope)
+        s[res_gem].set_scope(env.acc_scope)
+        s[res_shf].set_scope(env.acc_scope)
+        s[res_min].set_scope(env.acc_scope)
+        s[res_max].set_scope(env.acc_scope)
 
         if block:
-            bblock = block // vta.VTA_BATCH
-            iblock = block // vta.VTA_BLOCK_IN
-            oblock = block // vta.VTA_BLOCK_OUT
+            bblock = block // env.BATCH
+            iblock = block // env.BLOCK_IN
+            oblock = block // env.BLOCK_OUT
             xbo, xco, xbi, xci = s[res].op.axis
             xb1, xco1, xb2, xco2 = s[res].tile(xbo, xco, bblock, oblock)
             store_pt = xb2
@@ -162,91 +161,91 @@ def run_schedule(load_inp,
         return verify(s, check_correctness)
 
     def gemm_normal(print_ir):
-        mock = vta.mock
+        mock = env.mock
         print("----- GEMM GFLOPS End-to-End Test-------")
         def run_test(header, print_ir, check_correctness):
             cost = run_schedule(
-                vta.DMA_COPY, vta.DMA_COPY, vta.GEMM, vta.ALU, vta.DMA_COPY,
+                env.dma_copy, env.dma_copy, env.gemm, env.alu, env.dma_copy,
                 print_ir, check_correctness)
             gops = (num_ops / cost.mean) / float(10 ** 9)
             print(header)
             print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
-        with tvm.build_config(add_lower_pass=vta.debug_mode(vta.DEBUG_DUMP_INSN)):
+        with vta.build_config():
             run_test("NORMAL", print_ir, True)
 
         print("")
 
     def gevm_unittest(print_ir):
-        mock = vta.mock
+        mock = env.mock
         print("----- GEMM Unit Test-------")
         def run_test(header, print_ir):
             cost = run_schedule(
-                mock.DMA_COPY, mock.DMA_COPY, vta.GEMM, mock.ALU, mock.DMA_COPY,
+                mock.dma_copy, mock.dma_copy, env.gemm, mock.alu, mock.dma_copy,
                 print_ir, False)
             gops = (num_ops / cost.mean) / float(10 ** 9)
             print(header)
             print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
-        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+        with vta.build_config():
             run_test("NORMAL", print_ir)
         print("")
 
     def alu_unittest(print_ir):
-        mock = vta.mock
+        mock = env.mock
         print("----- ALU Unit Test-------")
         def run_test(header, print_ir):
             cost = run_schedule(
-                mock.DMA_COPY, mock.DMA_COPY, mock.GEMM, vta.ALU, mock.DMA_COPY,
+                mock.dma_copy, mock.dma_copy, mock.gemm, env.alu, mock.dma_copy,
                 print_ir, False)
             gops = (num_ops / cost.mean) / float(10 ** 9)
             print(header)
             print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
-        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+        with vta.build_config():
             run_test("NORMAL", print_ir)
         print("")
 
     def load_inp_unittest(print_ir):
-        mock = vta.mock
+        mock = env.mock
         print("----- LoadInp Unit Test-------")
         def run_test(header, print_ir):
             cost = run_schedule(
-                vta.DMA_COPY, mock.DMA_COPY, mock.GEMM, mock.ALU, mock.DMA_COPY, print_ir, False)
+                env.dma_copy, mock.dma_copy, mock.gemm, mock.alu, mock.dma_copy, print_ir, False)
             gops = (num_ops / cost.mean) / float(10 ** 9)
-            bandwith = (batch_size * channel * vta.VTA_INP_WIDTH / cost.mean) / float(10 ** 9)
+            bandwith = (batch_size * channel * env.INP_WIDTH / cost.mean) / float(10 ** 9)
             print(header)
             print("\tTime cost = %g sec/op, %g GFLOPS, bandwidth=%g Gbits" % (
                 cost.mean, gops, bandwith))
-        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+        with vta.build_config():
             run_test("NORMAL", print_ir)
         print("")
 
     def load_wgt_unittest(print_ir):
-        mock = vta.mock
+        mock = env.mock
         print("----- LoadWgt Unit Test-------")
         def run_test(header, print_ir):
             cost = run_schedule(
-                mock.DMA_COPY, vta.DMA_COPY, mock.GEMM, mock.ALU, mock.DMA_COPY, print_ir, False)
+                mock.dma_copy, env.dma_copy, mock.gemm, mock.alu, mock.dma_copy, print_ir, False)
             gops = (num_ops / cost.mean) / float(10 ** 9)
-            bandwith = (channel * channel * vta.VTA_WGT_WIDTH / cost.mean) / float(10 ** 9)
+            bandwith = (channel * channel * env.WGT_WIDTH / cost.mean) / float(10 ** 9)
             print(header)
             print("\tTime cost = %g sec/op, %g GFLOPS, bandwidth=%g Gbits" % (
                 cost.mean, gops, bandwith))
-        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+        with vta.build_config():
             run_test("NORMAL", print_ir)
         print("")
 
     def store_out_unittest(print_ir):
-        mock = vta.mock
+        mock = env.mock
         print("----- StoreOut Unit Test-------")
         def run_test(header, print_ir):
             cost = run_schedule(
-                mock.DMA_COPY, mock.DMA_COPY, mock.GEMM, mock.ALU, vta.DMA_COPY,
+                mock.dma_copy, mock.dma_copy, mock.gemm, mock.alu, env.dma_copy,
                 print_ir, False)
             gops = (num_ops / cost.mean) / float(10 ** 9)
-            bandwith = (batch_size * channel * vta.VTA_OUT_WIDTH / cost.mean) / float(10 ** 9)
+            bandwith = (batch_size * channel * env.OUT_WIDTH / cost.mean) / float(10 ** 9)
             print(header)
             print("\tTime cost = %g sec/op, %g GFLOPS, bandwidth=%g Gbits" % (
                 cost.mean, gops, bandwith))
-        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+        with vta.build_config():
             run_test("NORMAL", print_ir)
         print("")
 
diff --git a/vta/tests/python/pynq/test_benchmark_topi.py b/vta/tests/python/pynq/test_benchmark_topi.py
index e6dea3e29d42..3e2d19a67b2f 100644
--- a/vta/tests/python/pynq/test_benchmark_topi.py
+++ b/vta/tests/python/pynq/test_benchmark_topi.py
@@ -21,26 +21,26 @@ def my_clip(x, a_min, a_max):
 
 host = "pynq"
 port = 9091
-out_dtype = "int%d" % vta.VTA_OUT_WIDTH
-wgt_dtype = "int%d" % vta.VTA_WGT_WIDTH
-inp_dtype = "int%d" % vta.VTA_INP_WIDTH
 target = "llvm -target=armv7-none-linux-gnueabihf -mattr=+neon"
 print_ir = False
 
 
 def test_vta_conv2d(key, batch_size, wl, profile=True):
-    data_shape = (batch_size, wl.in_filter//vta.VTA_BLOCK_IN,
-                  wl.height, wl.width, vta.VTA_BLOCK_IN)
-    kernel_shape = (wl.out_filter//vta.VTA_BLOCK_OUT, wl.in_filter//vta.VTA_BLOCK_IN,
-                    wl.hkernel, wl.wkernel, vta.VTA_BLOCK_OUT, vta.VTA_BLOCK_IN)
-    bias_shape = (wl.out_filter//vta.VTA_BLOCK_OUT, 1, 1, vta.VTA_BLOCK_OUT)
+    env = vta.get_env()
+    data_shape = (batch_size, wl.in_filter // env.BLOCK_IN,
+                  wl.height, wl.width, env.BLOCK_IN)
+    kernel_shape = (wl.out_filter // env.BLOCK_OUT,
+                    wl.in_filter // env.BLOCK_IN,
+                    wl.hkernel, wl.wkernel,
+                    env.BLOCK_OUT, env.BLOCK_IN)
+    bias_shape = (wl.out_filter // env.BLOCK_OUT, 1, 1, env.BLOCK_OUT)
 
 
     fout_height = (wl.height + 2 * wl.hpad - wl.hkernel) // wl.hstride + 1
     fout_width = (wl.width + 2 * wl.wpad - wl.wkernel) // wl.wstride + 1
-    data = tvm.placeholder(data_shape, name="data", dtype=inp_dtype)
-    kernel = tvm.placeholder(kernel_shape, name="kernel", dtype=wgt_dtype)
-    bias = tvm.placeholder(bias_shape, name="kernel", dtype=out_dtype)
+    data = tvm.placeholder(data_shape, name="data", dtype=env.inp_dtype)
+    kernel = tvm.placeholder(kernel_shape, name="kernel", dtype=env.wgt_dtype)
+    bias = tvm.placeholder(bias_shape, name="kernel", dtype=env.acc_dtype)
 
     res_conv = vta_conv2d.packed_conv2d(
         data, kernel, padding=(wl.hpad, wl.wpad), strides=(wl.hstride, wl.wstride))
@@ -73,14 +73,14 @@ def verify(s, check_correctness):
         bias_orig = np.abs(bias_orig)
 
         data_packed = data_orig.reshape(
-            batch_size, wl.in_filter//vta.VTA_BLOCK_IN, vta.VTA_BLOCK_IN,
+            batch_size, wl.in_filter // env.BLOCK_IN, env.BLOCK_IN,
             wl.height, wl.width).transpose((0, 1, 3, 4, 2))
         kernel_packed = kernel_orig.reshape(
-            wl.out_filter//vta.VTA_BLOCK_OUT, vta.VTA_BLOCK_OUT,
-            wl.in_filter//vta.VTA_BLOCK_IN, vta.VTA_BLOCK_IN,
+            wl.out_filter // env.BLOCK_OUT, env.BLOCK_OUT,
+            wl.in_filter // env.BLOCK_IN, env.BLOCK_IN,
             wl.hkernel, wl.wkernel).transpose((0, 2, 4, 5, 1, 3))
         bias_packed = bias_orig.reshape(
-            wl.out_filter//vta.VTA_BLOCK_OUT, 1, 1, vta.VTA_BLOCK_OUT)
+            wl.out_filter // env.BLOCK_OUT, 1, 1, env.BLOCK_OUT)
         res_shape = topi.util.get_const_tuple(res.shape)
 
         res_np = np.zeros(res_shape).astype(res.dtype)
@@ -94,13 +94,13 @@ def verify(s, check_correctness):
             (0, 1, 4, 2, 3)).reshape(batch_size, wl.out_filter, fout_height, fout_width)
         if check_correctness:
             res_ref = mx.nd.Convolution(
-                mx.nd.array(data_orig.astype(out_dtype), mx.cpu(0)),
-                mx.nd.array(kernel_orig.astype(out_dtype), mx.cpu(0)),
+                mx.nd.array(data_orig.astype(env.acc_dtype), mx.cpu(0)),
+                mx.nd.array(kernel_orig.astype(env.acc_dtype), mx.cpu(0)),
                 stride=(wl.hstride, wl.wstride),
                 kernel=(wl.hkernel, wl.wkernel),
                 num_filter=wl.out_filter,
                 no_bias=True,
-                pad=(wl.hpad, wl.wpad)).asnumpy().astype(out_dtype)
+                pad=(wl.hpad, wl.wpad)).asnumpy().astype(env.acc_dtype)
             res_ref = res_ref >> 8
             res_ref += bias_orig.reshape(wl.out_filter, 1, 1)
             res_ref = np.clip(res_ref, 0, 127).astype("int8")
@@ -110,10 +110,10 @@ def verify(s, check_correctness):
 
     def conv_normal(print_ir):
         print("----- CONV2D End-to-End Test-------")
-        with tvm.build_config(add_lower_pass=vta.debug_mode(0)):
+        with vta.build_config():
             s = vta_conv2d.schedule_packed_conv2d([res])
             if print_ir:
-                print(tvm.lower(s, [data, kernel, bias, res], simple_mode=True))
+                print(vta.lower(s, [data, kernel, bias, res], simple_mode=True))
             cost = verify(s, True)
         gops = (num_ops / cost.mean) / float(10 ** 9)
         print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
diff --git a/vta/tests/python/pynq/test_program_rpc.py b/vta/tests/python/pynq/test_program_rpc.py
index ad67f3987378..4c252acbae7c 100644
--- a/vta/tests/python/pynq/test_program_rpc.py
+++ b/vta/tests/python/pynq/test_program_rpc.py
@@ -3,14 +3,15 @@
 import os
 from tvm.contrib import rpc, util
 
+env = vta.get_env()
 host = "pynq"
 port = 9091
 target = "llvm -target=armv7-none-linux-gnueabihf"
 bit = "{}x{}x{}_{}bx{}b_{}_{}_{}_{}_100MHz_10ns.bit".format(
-		vta.VTA_BATCH, vta.VTA_BLOCK_IN, vta.VTA_BLOCK_OUT,
-		vta.VTA_INP_WIDTH, vta.VTA_WGT_WIDTH,
-		vta.VTA_LOG_UOP_BUFF_SIZE, vta.VTA_LOG_INP_BUFF_SIZE,
-		vta.VTA_LOG_WGT_BUFF_SIZE, vta.VTA_LOG_OUT_BUFF_SIZE)
+		env.BATCH, env.BLOCK_IN, env.BLOCK_OUT,
+		env.INP_WIDTH, env.WGT_WIDTH,
+		env.LOG_UOP_BUFF_SIZE, env.LOG_INP_BUFF_SIZE,
+		env.LOG_WGT_BUFF_SIZE, env.LOG_ACC_BUFF_SIZE)
 
 curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
 bitstream = os.path.join(curr_path, "../../../../vta_bitstreams/bitstreams/", bit)
diff --git a/vta/tests/python/pynq/test_vta_insn.py b/vta/tests/python/pynq/test_vta_insn.py
index 7239bf163342..14baede4e44a 100644
--- a/vta/tests/python/pynq/test_vta_insn.py
+++ b/vta/tests/python/pynq/test_vta_insn.py
@@ -1,4 +1,4 @@
-"""Unit test TPU's instructions """
+"""Unit test VTA's instructions """
 import tvm
 import vta
 import mxnet as mx
@@ -9,44 +9,42 @@
 host = "pynq"
 port = 9091
 target = "llvm -target=armv7-none-linux-gnueabihf"
-out_dtype = "int%d" % vta.VTA_OUT_WIDTH
-inp_dtype = "int%d" % vta.VTA_INP_WIDTH
-wgt_dtype = "int%d" % vta.VTA_WGT_WIDTH
 do_verify = True
 print_ir = False
 
-
 def test_save_load_out():
+    env = vta.get_env()
     """Test save/store output command"""
     n = 4
     x = tvm.placeholder(
-        (n, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        (n, n, env.BATCH, env.BLOCK_OUT),
         name="x",
-        dtype=out_dtype)
+        dtype=env.acc_dtype)
     x_buf = tvm.compute(
-        (n, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        (n, n, env.BATCH, env.BLOCK_OUT),
         lambda *i: x(*i),
         "x_buf")
     # insert no-op that won't be optimized away
     y_buf = tvm.compute(
-        (n, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        (n, n, env.BATCH, env.BLOCK_OUT),
         lambda *i: x_buf(*i)>>0,
         "y_buf")
     y = tvm.compute(
-        (n, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
-        lambda *i: y_buf(*i).astype(inp_dtype),
+        (n, n, env.BATCH, env.BLOCK_OUT),
+        lambda *i: y_buf(*i).astype(env.inp_dtype),
         "y")
     # schedule
     s = tvm.create_schedule(y.op)
-    s[x_buf].set_scope(vta.SCOPE_OUT)
-    s[x_buf].pragma(x_buf.op.axis[0], vta.DMA_COPY)
-    s[y_buf].set_scope(vta.SCOPE_OUT)
-    s[y_buf].pragma(y_buf.op.axis[0], vta.ALU)
-    s[y].pragma(y.op.axis[0], vta.DMA_COPY)
+    s[x_buf].set_scope(env.acc_scope)
+    s[x_buf].pragma(x_buf.op.axis[0], env.dma_copy)
+    s[y_buf].set_scope(env.acc_scope)
+    s[y_buf].pragma(y_buf.op.axis[0], env.alu)
+    s[y].pragma(y.op.axis[0], env.dma_copy)
 
     def verify():
         # build
-        m = tvm.build(s, [x, y], "ext_dev", target)
+        with vta.build_config(env.DEBUG_DUMP_INSN):
+            m = vta.build(s, [x, y], "ext_dev", target)
         temp = util.tempdir()
         remote = rpc.connect(host, port)
         m.save(temp.relpath("load_act.o"))
@@ -55,7 +53,7 @@ def verify():
         # verify
         ctx = remote.ext_dev(0)
         x_np = np.random.randint(
-            1, 10, size=(n, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT)).astype(x.dtype)
+            1, 10, size=(n, n, env.BATCH, env.BLOCK_OUT)).astype(x.dtype)
         y_np = x_np.astype(y.dtype)
         x_nd = tvm.nd.array(x_np, ctx)
         y_nd = tvm.nd.empty(y_np.shape, ctx=ctx, dtype=y_np.dtype)
@@ -65,38 +63,41 @@ def verify():
     if do_verify:
         verify()
 
+
 def test_padded_load():
     """Test padded load."""
+    env = vta.get_env()
     # declare
     n = 21
     m = 20
     pad_before = [0, 1, 0, 0]
     pad_after = [1, 3, 0, 0]
     x = tvm.placeholder(
-        (n, m, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        (n, m, env.BATCH, env.BLOCK_OUT),
         name="x",
-        dtype=out_dtype)
+        dtype=env.acc_dtype)
     x_buf = topi.nn.pad(x, pad_before, pad_after, name="y")
     # insert no-op that won't be optimized away
     y_buf = tvm.compute((n + pad_before[0] + pad_after[0],
                          m + pad_before[1] + pad_after[1],
-                         vta.VTA_BATCH,
-                         vta.VTA_BLOCK_OUT), lambda *i: x_buf(*i)>>0, "y_buf")
+                         env.BATCH,
+                         env.BLOCK_OUT), lambda *i: x_buf(*i)>>0, "y_buf")
     y = tvm.compute((n + pad_before[0] + pad_after[0],
                      m + pad_before[1] + pad_after[1],
-                     vta.VTA_BATCH,
-                     vta.VTA_BLOCK_OUT), lambda *i: y_buf(*i).astype(inp_dtype), "y")
+                     env.BATCH,
+                     env.BLOCK_OUT), lambda *i: y_buf(*i).astype(env.inp_dtype), "y")
     # schedule
     s = tvm.create_schedule(y.op)
-    s[x_buf].set_scope(vta.SCOPE_OUT)
-    s[x_buf].pragma(x_buf.op.axis[0], vta.DMA_COPY)
-    s[y_buf].set_scope(vta.SCOPE_OUT)
-    s[y_buf].pragma(y_buf.op.axis[0], vta.ALU)
-    s[y].pragma(y.op.axis[0], vta.DMA_COPY)
+    s[x_buf].set_scope(env.acc_scope)
+    s[x_buf].pragma(x_buf.op.axis[0], env.dma_copy)
+    s[y_buf].set_scope(env.acc_scope)
+    s[y_buf].pragma(y_buf.op.axis[0], env.alu)
+    s[y].pragma(y.op.axis[0], env.dma_copy)
 
     def verify():
         # build
-        mod = tvm.build(s, [x, y], "ext_dev", target)
+        with vta.build_config(env.DEBUG_DUMP_INSN):
+            mod = vta.build(s, [x, y], "ext_dev", target)
         temp = util.tempdir()
         remote = rpc.connect(host, port)
         mod.save(temp.relpath("padded_load.o"))
@@ -105,11 +106,11 @@ def verify():
         # verify
         ctx = remote.ext_dev(0)
         x_np = np.random.randint(1, 2, size=(
-            n, m, vta.VTA_BATCH, vta.VTA_BLOCK_OUT)).astype(x.dtype)
+            n, m, env.BATCH, env.BLOCK_OUT)).astype(x.dtype)
         y_np = np.zeros((n + pad_before[0] + pad_after[0],
                          m + pad_before[1] + pad_after[1],
-                         vta.VTA_BATCH,
-                         vta.VTA_BLOCK_OUT)).astype(y.dtype)
+                         env.BATCH,
+                         env.BLOCK_OUT)).astype(y.dtype)
         y_np[pad_before[0]:pad_before[0] + n,
              pad_before[1]:pad_before[1] + m,
              :] = x_np
@@ -118,51 +119,50 @@ def verify():
         f(x_nd, y_nd)
         np.testing.assert_equal(y_np, y_nd.asnumpy())
         print("\tFinished verification...")
+
     if print_ir:
-        print(tvm.lower(s, [y, x], simple_mode=True))
-    if do_verify:
-        with tvm.build_config(add_lower_pass=vta.debug_mode(
-                vta.DEBUG_DUMP_INSN)):
-            verify()
+        print(vta.lower(s, [y, x], simple_mode=True))
+
 
 def test_gemm():
     """Test GEMM."""
+    env = vta.get_env()
     # declare
     o = 4
     n = 4
     m = 4
-    x = tvm.placeholder((o, n, vta.VTA_BATCH, vta.VTA_BLOCK_IN), name="x", dtype=inp_dtype)
-    w = tvm.placeholder((m, n, vta.VTA_BLOCK_OUT, vta.VTA_BLOCK_IN), name="w", dtype=wgt_dtype)
-    x_buf = tvm.compute((o, n, vta.VTA_BATCH, vta.VTA_BLOCK_IN), lambda *i: x(*i), "x_buf")
-    w_buf = tvm.compute((m, n, vta.VTA_BLOCK_OUT, vta.VTA_BLOCK_IN), lambda *i: w(*i), "w_buf")
+    x = tvm.placeholder((o, n, env.BATCH, env.BLOCK_IN), name="x", dtype=env.inp_dtype)
+    w = tvm.placeholder((m, n, env.BLOCK_OUT, env.BLOCK_IN), name="w", dtype=env.wgt_dtype)
+    x_buf = tvm.compute((o, n, env.BATCH, env.BLOCK_IN), lambda *i: x(*i), "x_buf")
+    w_buf = tvm.compute((m, n, env.BLOCK_OUT, env.BLOCK_IN), lambda *i: w(*i), "w_buf")
     ko = tvm.reduce_axis((0, n), name="ko")
-    ki = tvm.reduce_axis((0, vta.VTA_BLOCK_IN), name="ki")
+    ki = tvm.reduce_axis((0, env.BLOCK_IN), name="ki")
     y_gem = tvm.compute(
-        (o, m, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        (o, m, env.BATCH, env.BLOCK_OUT),
         lambda bo, co, bi, ci:
-            tvm.sum(x_buf[bo, ko, bi, ki].astype(out_dtype) *
-                    w_buf[co, ko, ci, ki].astype(out_dtype),
+            tvm.sum(x_buf[bo, ko, bi, ki].astype(env.acc_dtype) *
+                    w_buf[co, ko, ci, ki].astype(env.acc_dtype),
                     axis=[ko, ki]),
         name="y_gem")
     y_shf = tvm.compute(
-        (o, m, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        (o, m, env.BATCH, env.BLOCK_OUT),
         lambda *i: y_gem(*i)>>8,
         name="y_shf")
     y_max = tvm.compute(
-        (o, m, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        (o, m, env.BATCH, env.BLOCK_OUT),
         lambda *i: tvm.max(y_shf(*i), 0),
         "y_max") #relu
     y_min = tvm.compute(
-        (o, m, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
-        lambda *i: tvm.min(y_max(*i), (1<<(vta.VTA_INP_WIDTH-1))-1),
+        (o, m, env.BATCH, env.BLOCK_OUT),
+        lambda *i: tvm.min(y_max(*i), (1<<(env.INP_WIDTH-1))-1),
         "y_min") #relu
     y = tvm.compute(
-        (o, m, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
-        lambda *i: y_min(*i).astype(inp_dtype),
+        (o, m, env.BATCH, env.BLOCK_OUT),
+        lambda *i: y_min(*i).astype(env.inp_dtype),
         name="y")
 
     def verify(s):
-        mod = tvm.build(s, [x, w, y], "ext_dev", target)
+        mod = vta.build(s, [x, w, y], "ext_dev", target)
         temp = util.tempdir()
         remote = rpc.connect(host, port)
         mod.save(temp.relpath("gemm.o"))
@@ -171,21 +171,21 @@ def verify(s):
         # verify
         ctx = remote.ext_dev(0)
         x_np = np.random.randint(
-            -128, 128, size=(o, n, vta.VTA_BATCH, vta.VTA_BLOCK_IN)).astype(x.dtype)
+            -128, 128, size=(o, n, env.BATCH, env.BLOCK_IN)).astype(x.dtype)
         w_np = np.random.randint(
-            -128, 128, size=(m, n, vta.VTA_BLOCK_OUT, vta.VTA_BLOCK_IN)).astype(w.dtype)
-        y_np = np.zeros((o, m, vta.VTA_BATCH, vta.VTA_BLOCK_OUT)).astype(y.dtype)
+            -128, 128, size=(m, n, env.BLOCK_OUT, env.BLOCK_IN)).astype(w.dtype)
+        y_np = np.zeros((o, m, env.BATCH, env.BLOCK_OUT)).astype(y.dtype)
         x_nd = tvm.nd.array(x_np, ctx)
         w_nd = tvm.nd.array(w_np, ctx)
         y_nd = tvm.nd.array(y_np, ctx)
-        y_np = y_np.astype(out_dtype)
+        y_np = y_np.astype(env.acc_dtype)
         for b in range(o):
             for i in range(m):
                 for j in range(n):
-                    y_np[b,i,:] += np.dot(x_np[b,j,:].astype(out_dtype),
-                                          w_np[i,j].T.astype(out_dtype))
+                    y_np[b,i,:] += np.dot(x_np[b,j,:].astype(env.acc_dtype),
+                                          w_np[i,j].T.astype(env.acc_dtype))
         y_np = np.right_shift(y_np, 8)
-        y_np = np.clip(y_np, 0, (1<<(vta.VTA_INP_WIDTH-1))-1).astype(y.dtype)
+        y_np = np.clip(y_np, 0, (1<<(env.INP_WIDTH-1))-1).astype(y.dtype)
         f(x_nd, w_nd, y_nd)
         np.testing.assert_equal(y_np, y_nd.asnumpy())
         print("\tFinished verification...")
@@ -194,19 +194,19 @@ def test_schedule1():
         # default schedule with no smt
         s = tvm.create_schedule(y.op)
         # set the scope of the SRAM buffers
-        s[x_buf].set_scope(vta.SCOPE_INP)
-        s[w_buf].set_scope(vta.SCOPE_WGT)
-        s[y_gem].set_scope(vta.SCOPE_OUT)
-        s[y_shf].set_scope(vta.SCOPE_OUT)
-        s[y_max].set_scope(vta.SCOPE_OUT)
-        s[y_min].set_scope(vta.SCOPE_OUT)
+        s[x_buf].set_scope(env.SCOPE_INP)
+        s[w_buf].set_scope(env.SCOPE_WGT)
+        s[y_gem].set_scope(env.acc_scope)
+        s[y_shf].set_scope(env.acc_scope)
+        s[y_max].set_scope(env.acc_scope)
+        s[y_min].set_scope(env.acc_scope)
         # set pragmas for DMA transfer and ALU ops
-        s[x_buf].pragma(s[x_buf].op.axis[0], vta.DMA_COPY)
-        s[w_buf].pragma(s[w_buf].op.axis[0], vta.DMA_COPY)
-        s[y_shf].pragma(s[y_shf].op.axis[0], vta.ALU)
-        s[y_max].pragma(s[y_max].op.axis[0], vta.ALU)
-        s[y_min].pragma(s[y_min].op.axis[0], vta.ALU)
-        s[y].pragma(s[y].op.axis[0], vta.DMA_COPY)
+        s[x_buf].pragma(s[x_buf].op.axis[0], env.dma_copy)
+        s[w_buf].pragma(s[w_buf].op.axis[0], env.dma_copy)
+        s[y_shf].pragma(s[y_shf].op.axis[0], env.alu)
+        s[y_max].pragma(s[y_max].op.axis[0], env.alu)
+        s[y_min].pragma(s[y_min].op.axis[0], env.alu)
+        s[y].pragma(s[y].op.axis[0], env.dma_copy)
         # tensorization
         s[y_gem].reorder(
             ko,
@@ -215,23 +215,22 @@ def test_schedule1():
             s[y_gem].op.axis[2],
             s[y_gem].op.axis[3],
             ki)
-        s[y_gem].tensorize(s[y_gem].op.axis[2], vta.GEMM)
+        s[y_gem].tensorize(s[y_gem].op.axis[2], env.GEMM)
         if print_ir:
-            print(tvm.lower(s, [x, w, y], simple_mode=True))
+            print(vta.lower(s, [x, w, y], simple_mode=True))
         if do_verify:
-            with tvm.build_config(
-                    add_lower_pass=vta.debug_mode(vta.DEBUG_DUMP_INSN)):
+            with vta.build_config(env.DEBUG_DUMP_INSN):
                 verify(s)
 
     def test_smt():
         # test smt schedule
         s = tvm.create_schedule(y.op)
-        s[x_buf].set_scope(vta.SCOPE_INP)
-        s[w_buf].set_scope(vta.SCOPE_WGT)
-        s[y_gem].set_scope(vta.SCOPE_OUT)
-        s[y_shf].set_scope(vta.SCOPE_OUT)
-        s[y_max].set_scope(vta.SCOPE_OUT)
-        s[y_min].set_scope(vta.SCOPE_OUT)
+        s[x_buf].set_scope(env.SCOPE_INP)
+        s[w_buf].set_scope(env.SCOPE_WGT)
+        s[y_gem].set_scope(env.acc_scope)
+        s[y_shf].set_scope(env.acc_scope)
+        s[y_max].set_scope(env.acc_scope)
+        s[y_min].set_scope(env.acc_scope)
         abo, aco, abi, aci = s[y].op.axis
         abo1, abo2 = s[y].split(abo, nparts=2)
         s[y].bind(abo1, tvm.thread_axis("cthread"))
@@ -246,20 +245,19 @@ def test_smt():
             s[y_gem].op.axis[2],
             s[y_gem].op.axis[3],
             ki)
-        s[y_gem].tensorize(s[y_gem].op.axis[2], vta.GEMM)
-        s[y_shf].pragma(s[y_shf].op.axis[0], vta.ALU)
-        s[y_max].pragma(s[y_max].op.axis[0], vta.ALU)
-        s[y_min].pragma(s[y_min].op.axis[0], vta.ALU)
+        s[y_gem].tensorize(s[y_gem].op.axis[2], env.GEMM)
+        s[y_shf].pragma(s[y_shf].op.axis[0], env.alu)
+        s[y_max].pragma(s[y_max].op.axis[0], env.alu)
+        s[y_min].pragma(s[y_min].op.axis[0], env.alu)
         s[x_buf].compute_at(s[y_gem], ko)
-        s[x_buf].pragma(s[x_buf].op.axis[0], vta.DMA_COPY)
+        s[x_buf].pragma(s[x_buf].op.axis[0], env.dma_copy)
         s[w_buf].compute_at(s[y_gem], ko)
-        s[w_buf].pragma(s[w_buf].op.axis[0], vta.DMA_COPY)
-        s[y].pragma(abo2, vta.DMA_COPY)
+        s[w_buf].pragma(s[w_buf].op.axis[0], env.dma_copy)
+        s[y].pragma(abo2, env.dma_copy)
         if print_ir:
-            print(tvm.lower(s, [x, y, w], simple_mode=True))
+            print(vta.lower(s, [x, y, w], simple_mode=True))
         if do_verify:
-            with tvm.build_config(
-                    add_lower_pass=vta.debug_mode(vta.DEBUG_DUMP_INSN)):
+            with vta.build_config(env.DEBUG_DUMP_INSN):
                 verify(s)
 
     test_schedule1()
@@ -267,62 +265,64 @@ def test_smt():
 
 def test_alu(tvm_op, np_op=None, use_imm=False):
     """Test ALU"""
+    env = vta.get_env()
     m = 8
     n = 8
     imm = np.random.randint(1,5)
     # compute
     a = tvm.placeholder(
-        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        (m, n, env.BATCH, env.BLOCK_OUT),
         name="a",
-        dtype=out_dtype)
+        dtype=env.acc_dtype)
     a_buf = tvm.compute(
-        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        (m, n, env.BATCH, env.BLOCK_OUT),
         lambda *i: a(*i),
         "a_buf") #DRAM->SRAM
     if use_imm:
         res_buf = tvm.compute(
-            (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+            (m, n, env.BATCH, env.BLOCK_OUT),
             lambda *i: tvm_op(a_buf(*i), imm),
             "res_buf") #compute
     else:
         b = tvm.placeholder(
-            (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+            (m, n, env.BATCH, env.BLOCK_OUT),
             name="b",
-            dtype=out_dtype)
+            dtype=env.acc_dtype)
         b_buf = tvm.compute(
-            (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+            (m, n, env.BATCH, env.BLOCK_OUT),
             lambda *i: b(*i),
             "b_buf") #DRAM->SRAM
         res_buf = tvm.compute(
-            (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+            (m, n, env.BATCH, env.BLOCK_OUT),
             lambda *i: tvm_op(a_buf(*i), b_buf(*i)),
             "res_buf") #compute
     res = tvm.compute(
-        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
-        lambda *i: res_buf(*i).astype(inp_dtype),
+        (m, n, env.BATCH, env.BLOCK_OUT),
+        lambda *i: res_buf(*i).astype(env.inp_dtype),
         "res") #SRAM->DRAM
     # schedule
     s = tvm.create_schedule(res.op)
-    s[a_buf].set_scope(vta.SCOPE_OUT) # SRAM
-    s[a_buf].pragma(a_buf.op.axis[0], vta.DMA_COPY) # DRAM->SRAM
-    s[res_buf].set_scope(vta.SCOPE_OUT) # SRAM
-    s[res_buf].pragma(res_buf.op.axis[0], vta.ALU) # compute
-    s[res].pragma(res.op.axis[0], vta.DMA_COPY) # SRAM->DRAM
+    s[a_buf].set_scope(env.acc_scope) # SRAM
+    s[a_buf].pragma(a_buf.op.axis[0], env.dma_copy) # DRAM->SRAM
+    s[res_buf].set_scope(env.acc_scope) # SRAM
+    s[res_buf].pragma(res_buf.op.axis[0], env.alu) # compute
+    s[res].pragma(res.op.axis[0], env.dma_copy) # SRAM->DRAM
     if use_imm:
         if print_ir:
-            print(tvm.lower(s, [a, res], simple_mode=True))
+            print(vta.lower(s, [a, res], simple_mode=True))
     else:
-        s[b_buf].set_scope(vta.SCOPE_OUT) # SRAM
-        s[b_buf].pragma(b_buf.op.axis[0], vta.DMA_COPY) # DRAM->SRAM
+        s[b_buf].set_scope(env.acc_scope) # SRAM
+        s[b_buf].pragma(b_buf.op.axis[0], env.dma_copy) # DRAM->SRAM
         if print_ir:
-            print(tvm.lower(s, [a, b, res], simple_mode=True))
+            print(vta.lower(s, [a, b, res], simple_mode=True))
 
     def verify():
         # build
-        if use_imm:
-            mod = tvm.build(s, [a, res], "ext_dev", target)
-        else:
-            mod = tvm.build(s, [a, b, res], "ext_dev", target)
+        with vta.build_config():
+            if use_imm:
+                mod = vta.build(s, [a, res], "ext_dev", target)
+            else:
+                mod = vta.build(s, [a, b, res], "ext_dev", target)
         temp = util.tempdir()
         remote = rpc.connect(host, port)
         mod.save(temp.relpath("load_act.o"))
@@ -331,17 +331,17 @@ def verify():
         # verify
         ctx = remote.ext_dev(0)
         a_np = np.random.randint(
-            -16, 16, size=(m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT)).astype(a.dtype)
+            -16, 16, size=(m, n, env.BATCH, env.BLOCK_OUT)).astype(a.dtype)
         if use_imm:
             res_np = np_op(a_np, imm) if np_op else tvm_op(a_np, imm)
         else:
             b_np = np.random.randint(
-                -16, 16, size=(m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT)).astype(b.dtype)
+                -16, 16, size=(m, n, env.BATCH, env.BLOCK_OUT)).astype(b.dtype)
             res_np = np_op(a_np, b_np) if np_op else tvm_op(a_np, b_np)
         res_np = res_np.astype(res.dtype)
         a_nd = tvm.nd.array(a_np, ctx)
         res_nd = tvm.nd.array(
-            np.zeros((m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT)).astype(res.dtype), ctx)
+            np.zeros((m, n, env.BATCH, env.BLOCK_OUT)).astype(res.dtype), ctx)
         if use_imm:
             f(a_nd, res_nd)
         else:
@@ -355,44 +355,46 @@ def verify():
 
 def test_relu():
     """Test RELU on ALU"""
+    env = vta.get_env()
     m = 8
     n = 8
     # compute
     a = tvm.placeholder(
-        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        (m, n, env.BATCH, env.BLOCK_OUT),
         name="a",
-        dtype=out_dtype)
+        dtype=env.acc_dtype)
     a_buf = tvm.compute(
-        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        (m, n, env.BATCH, env.BLOCK_OUT),
         lambda *i: a(*i),
         "a_buf") # DRAM->SRAM
     max_buf = tvm.compute(
-        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        (m, n, env.BATCH, env.BLOCK_OUT),
         lambda *i: tvm.max(a_buf(*i), 0),
         "res_buf") # relu
     min_buf = tvm.compute(
-        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
-        lambda *i: tvm.min(max_buf(*i), (1<<(vta.VTA_INP_WIDTH-1))-1),
+        (m, n, env.BATCH, env.BLOCK_OUT),
+        lambda *i: tvm.min(max_buf(*i), (1<<(env.INP_WIDTH-1))-1),
         "max_buf") # relu
     res = tvm.compute(
-        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
-        lambda *i: min_buf(*i).astype(inp_dtype),
+        (m, n, env.BATCH, env.BLOCK_OUT),
+        lambda *i: min_buf(*i).astype(env.inp_dtype),
         "min_buf") # SRAM->DRAM
     # schedule
     s = tvm.create_schedule(res.op)
-    s[a_buf].set_scope(vta.SCOPE_OUT) # SRAM
-    s[a_buf].pragma(a_buf.op.axis[0], vta.DMA_COPY) # DRAM->SRAM
-    s[max_buf].set_scope(vta.SCOPE_OUT) # SRAM
-    s[min_buf].set_scope(vta.SCOPE_OUT) # SRAM
-    s[max_buf].pragma(max_buf.op.axis[0], vta.ALU) # compute
-    s[min_buf].pragma(min_buf.op.axis[0], vta.ALU) # compute
-    s[res].pragma(res.op.axis[0], vta.DMA_COPY) # SRAM->DRAM
+    s[a_buf].set_scope(env.acc_scope) # SRAM
+    s[a_buf].pragma(a_buf.op.axis[0], env.dma_copy) # DRAM->SRAM
+    s[max_buf].set_scope(env.acc_scope) # SRAM
+    s[min_buf].set_scope(env.acc_scope) # SRAM
+    s[max_buf].pragma(max_buf.op.axis[0], env.alu) # compute
+    s[min_buf].pragma(min_buf.op.axis[0], env.alu) # compute
+    s[res].pragma(res.op.axis[0], env.dma_copy) # SRAM->DRAM
     if print_ir:
-        print(tvm.lower(s, [a, res], simple_mode=True))
+        print(vta.lower(s, [a, res], simple_mode=True))
 
     def verify():
         # build
-        mod = tvm.build(s, [a, res], "ext_dev", target)
+        with vta.build_config(env.DEBUG_DUMP_INSN):
+            mod = vta.build(s, [a, res], "ext_dev", target)
         temp = util.tempdir()
         remote = rpc.connect(host, port)
         mod.save(temp.relpath("load_act.o"))
@@ -401,11 +403,11 @@ def verify():
         # verify
         ctx = remote.ext_dev(0)
         a_np = np.random.randint(
-            -256, 256, size=(m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT)).astype(a.dtype)
-        res_np = np.clip(a_np, 0, (1<<(vta.VTA_INP_WIDTH-1))-1).astype(res.dtype)
+            -256, 256, size=(m, n, env.BATCH, env.BLOCK_OUT)).astype(a.dtype)
+        res_np = np.clip(a_np, 0, (1<<(env.INP_WIDTH-1))-1).astype(res.dtype)
         a_nd = tvm.nd.array(a_np, ctx)
         res_nd = tvm.nd.array(
-            np.zeros((m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT)).astype(res.dtype), ctx)
+            np.zeros((m, n, env.BATCH, env.BLOCK_OUT)).astype(res.dtype), ctx)
         f(a_nd, res_nd)
         np.testing.assert_equal(res_np, res_nd.asnumpy())
         print("\tFinished verification...")
@@ -415,45 +417,47 @@ def verify():
 
 def test_shift_and_scale():
     """Test shift and scale on ALU"""
+    env = vta.get_env()
     m = 8
     n = 8
     imm_shift = np.random.randint(-10,10)
     imm_scale = np.random.randint(1,5)
     # compute
     a = tvm.placeholder(
-        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
-        name="a", dtype=out_dtype)
+        (m, n, env.BATCH, env.BLOCK_OUT),
+        name="a", dtype=env.acc_dtype)
     a_buf = tvm.compute(
-        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        (m, n, env.BATCH, env.BLOCK_OUT),
         lambda *i: a(*i),
         "a_buf") # DRAM->SRAM
     res_shift = tvm.compute(
-        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        (m, n, env.BATCH, env.BLOCK_OUT),
         lambda *i: a_buf(*i)+imm_shift,
         "res_shift") # compute
     res_scale = tvm.compute(
-        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
+        (m, n, env.BATCH, env.BLOCK_OUT),
         lambda *i: res_shift(*i)>>imm_scale,
         "res_scale") # compute
     res = tvm.compute(
-        (m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT),
-        lambda *i: res_scale(*i).astype(inp_dtype),
+        (m, n, env.BATCH, env.BLOCK_OUT),
+        lambda *i: res_scale(*i).astype(env.inp_dtype),
         "res") # SRAM->DRAM
     # schedule
     s = tvm.create_schedule(res.op)
-    s[a_buf].set_scope(vta.SCOPE_OUT) # SRAM
-    s[res_shift].set_scope(vta.SCOPE_OUT) # SRAM
-    s[res_scale].set_scope(vta.SCOPE_OUT) # SRAM
-    s[a_buf].pragma(a_buf.op.axis[0], vta.DMA_COPY) # DRAM->SRAM
-    s[res_shift].pragma(res_shift.op.axis[0], vta.ALU) # compute
-    s[res_scale].pragma(res_scale.op.axis[0], vta.ALU) # compute
-    s[res].pragma(res.op.axis[0], vta.DMA_COPY) # SRAM->DRAM
+    s[a_buf].set_scope(env.acc_scope) # SRAM
+    s[res_shift].set_scope(env.acc_scope) # SRAM
+    s[res_scale].set_scope(env.acc_scope) # SRAM
+    s[a_buf].pragma(a_buf.op.axis[0], env.dma_copy) # DRAM->SRAM
+    s[res_shift].pragma(res_shift.op.axis[0], env.alu) # compute
+    s[res_scale].pragma(res_scale.op.axis[0], env.alu) # compute
+    s[res].pragma(res.op.axis[0], env.dma_copy) # SRAM->DRAM
+
     if print_ir:
-        print(tvm.lower(s, [a, res], simple_mode=True))
+        print(vta.lower(s, [a, res], simple_mode=True))
 
     def verify():
         # build
-        mod = tvm.build(s, [a, res], "ext_dev", target)
+        mod = vta.build(s, [a, res], "ext_dev", target)
         temp = util.tempdir()
         remote = rpc.connect(host, port)
         mod.save(temp.relpath("load_act.o"))
@@ -462,12 +466,12 @@ def verify():
         # verify
         ctx = remote.ext_dev(0)
         a_np = np.random.randint(
-            -10, 10, size=(m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT)).astype(a.dtype)
+            -10, 10, size=(m, n, env.BATCH, env.BLOCK_OUT)).astype(a.dtype)
         res_np = np.right_shift((a_np + imm_shift), imm_scale)
         res_np = res_np.astype(res.dtype)
         a_nd = tvm.nd.array(a_np, ctx)
         res_nd = tvm.nd.array(
-            np.zeros((m, n, vta.VTA_BATCH, vta.VTA_BLOCK_OUT)).astype(res.dtype), ctx)
+            np.zeros((m, n, env.BATCH, env.BLOCK_OUT)).astype(res.dtype), ctx)
         f(a_nd, res_nd)
         np.testing.assert_equal(res_np, res_nd.asnumpy())
         print("\tFinished verification...")
@@ -476,10 +480,10 @@ def verify():
         verify()
 
 if __name__ == "__main__":
-    print("Padded load test")
-    test_padded_load()
     print("Load/store test")
     test_save_load_out()
+    print("Padded load test")
+    test_padded_load()
     # print("GEMM test")
     # test_gemm()
     print("Max immediate")
diff --git a/vta/tests/python/unittest/test_environment.py b/vta/tests/python/unittest/test_environment.py
new file mode 100644
index 000000000000..d97f107f37c4
--- /dev/null
+++ b/vta/tests/python/unittest/test_environment.py
@@ -0,0 +1,11 @@
+import vta
+
+
+def test_env():
+    env = vta.get_env()
+    mock = env.mock
+    assert mock.alu == "skip_alu"
+
+
+if __name__ == "__main__":
+    test_env()

From f7852677f4ddc65b04d840f531c59f249a7d7a45 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 11 Apr 2018 13:45:11 -0700
Subject: [PATCH 831/949] [DRIVER][RUNTIME] Make runtime fully device agnostic
 (#23)

---
 vta/include/vta/driver.h      |  87 +++++++++++----------
 vta/src/data_buffer.h         |   4 +-
 vta/src/pynq/pynq_driver.cc   | 140 ++++++++++++++++++++++++++--------
 vta/src/pynq/pynq_driver.h    |   8 +-
 vta/src/runtime.cc            |  95 ++++++-----------------
 vta/src/tvm/vta_device_api.cc |   4 -
 6 files changed, 188 insertions(+), 150 deletions(-)

diff --git a/vta/include/vta/driver.h b/vta/include/vta/driver.h
index c93021d96e4b..8a29fc47aa84 100644
--- a/vta/include/vta/driver.h
+++ b/vta/include/vta/driver.h
@@ -1,7 +1,9 @@
 /*!
  *  Copyright (c) 2018 by Contributors
  * \file vta_driver.h
- * \brief General driver interface.
+ * \brief Driver interface that is used by runtime.
+ *
+ * Driver's implementation is device specific.
  */
 
 #ifndef VTA_DRIVER_H_
@@ -11,16 +13,50 @@
 extern "C" {
 #endif
 
-#include <stdlib.h>
 #include <stdint.h>
+#include <stdlib.h>
 
-/*! \brief Memory management constants */
+/*! \brief Memory management constants for cached memory */
 #define VTA_CACHED 1
-/*! \brief Memory management constants */
+/*! \brief Memory management constants for non-cached memory */
 #define VTA_NOT_CACHED 0
 
-/*! \brief VTA command handle */
-typedef void * VTAHandle;
+/*! \brief Physically contiguous buffer size limit */
+#ifndef VTA_MAX_XFER
+#define VTA_MAX_XFER (1<<22)
+#endif
+
+/*! \brief Device resource context  */
+typedef void * VTADeviceHandle;
+
+/*! \brief physical address */
+typedef uint32_t vta_phy_addr_t;
+
+/*!
+ * \brief Allocate a device resource handle
+ * \return The device handle.
+ */
+VTADeviceHandle VTADeviceAlloc();
+
+/*!
+ * \brief Free a device handle
+ * \param handle The device handle to be freed.
+ */
+void VTADeviceFree(VTADeviceHandle handle);
+
+/*!
+ * \brief Launch the instructions block until done.
+ * \param The device handle.
+ * \param insn_phy_addr The physical address of instruction stream.
+ * \param insn_count Instruction count.
+ * \param wait_cycles The maximum of cycles to wait
+ *
+ * \return 0 if running is successful, 1 if timeout.
+ */
+int VTADeviceRun(VTADeviceHandle device,
+                 vta_phy_addr_t insn_phy_addr,
+                 uint32_t insn_count,
+                 uint32_t wait_cycles);
 
 /*!
  * \brief Allocates physically contiguous region in memory (limited by MAX_XFER).
@@ -41,52 +77,23 @@ void VTAMemFree(void* buf);
  * \param buf Pointer to memory region allocated with VTAMemAlloc.
  * \return The physical address of the memory region.
  */
-uint32_t VTAGetMemPhysAddr(void* buf);
+vta_phy_addr_t VTAGetMemPhysAddr(void* buf);
 
 /*!
  * \brief Flushes the region of memory out of the CPU cache to DRAM.
  * \param buf Pointer to memory region allocated with VTAMemAlloc to be flushed.
+ *            This need to be the physical address.
  * \param size Size of the region to flush in Bytes.
  */
-void VTAFlushCache(void* buf, int size);
+void VTAFlushCache(vta_phy_addr_t buf, int size);
 
 /*!
  * \brief Invalidates the region of memory that is cached.
  * \param buf Pointer to memory region allocated with VTAMemAlloc to be invalidated.
+ *            This need to be the physical address.
  * \param size Size of the region to invalidate in Bytes.
  */
-void VTAInvalidateCache(void* buf, int size);
-
-/*!
- * \brief Returns a memory map to FPGA configuration registers.
- * \param addr The base physical address of the configuration registers.
- * \param length The size of the memory mapped region in bytes.
- * \return A pointer to the memory mapped region.
- */
-void *VTAMapRegister(unsigned addr, size_t length);
-
-/*!
- * \brief Deletes the configuration register memory map.
- * \param vta The memory mapped region.
- * \param length The size of the memory mapped region in bytes.
- */
-void VTAUnmapRegister(void *vta, size_t length);
-
-/*!
- * \brief Writes to a memory mapped configuration register.
- * \param vta_base The handle to the memory mapped configuration registers.
- * \param offset The offset of the register to write to.
- * \param val The value to be written to the memory mapped register.
- */
-void VTAWriteMappedReg(VTAHandle vta_base, unsigned offset, unsigned val);
-
-/*!
- * \brief Reads from the memory mapped configuration register.
- * \param vta_base The handle to the memory mapped configuration registers.
- * \param offset The offset of the register to read from.
- * \return The value read from the memory mapped register.
- */
-unsigned VTAReadMappedReg(VTAHandle vta_base, unsigned offset);
+void VTAInvalidateCache(vta_phy_addr_t buf, int size);
 
 /*!
  * \brief Programming the bit stream on the FPGA.
diff --git a/vta/src/data_buffer.h b/vta/src/data_buffer.h
index 117a423d034e..aed92c49e795 100644
--- a/vta/src/data_buffer.h
+++ b/vta/src/data_buffer.h
@@ -35,7 +35,7 @@ struct DataBuffer {
    */
   void InvalidateCache(size_t offset, size_t size) {
     if (!kBufferCoherent) {
-      VTAInvalidateCache(reinterpret_cast<void*>(phy_addr_ + offset), size);
+      VTAInvalidateCache(phy_addr_ + offset, size);
     }
   }
   /*!
@@ -45,7 +45,7 @@ struct DataBuffer {
    */
   void FlushCache(size_t offset, size_t size) {
     if (!kBufferCoherent) {
-      VTAFlushCache(reinterpret_cast<void*>(phy_addr_ + offset), size);
+      VTAFlushCache(phy_addr_ + offset, size);
     }
   }
   /*!
diff --git a/vta/src/pynq/pynq_driver.cc b/vta/src/pynq/pynq_driver.cc
index 1787af8da526..0330450db285 100644
--- a/vta/src/pynq/pynq_driver.cc
+++ b/vta/src/pynq/pynq_driver.cc
@@ -5,6 +5,7 @@
  */
 
 #include <vta/driver.h>
+#include <thread>
 #include "./pynq_driver.h"
 
 
@@ -16,16 +17,16 @@ void VTAMemFree(void* buf) {
   cma_free(buf);
 }
 
-uint32_t VTAGetMemPhysAddr(void* buf) {
+vta_phy_addr_t VTAGetMemPhysAddr(void* buf) {
   return cma_get_phy_addr(buf);
 }
 
-void VTAFlushCache(void* buf, int size) {
-  xlnkFlushCache(buf, size);
+void VTAFlushCache(vta_phy_addr_t buf, int size) {
+  xlnkFlushCache(reinterpret_cast<void*>(buf), size);
 }
 
-void VTAInvalidateCache(void* buf, int size) {
-  xlnkInvalidateCache(buf, size);
+void VTAInvalidateCache(vta_phy_addr_t buf, int size) {
+  xlnkInvalidateCache(reinterpret_cast<void*>(buf), size);
 }
 
 void *VTAMapRegister(uint32_t addr, size_t length) {
@@ -57,33 +58,112 @@ uint32_t VTAReadMappedReg(void* base_addr, uint32_t offset) {
   return *((volatile uint32_t *) (reinterpret_cast<char *>(base_addr) + offset));
 }
 
+class VTADevice {
+ public:
+  VTADevice() {
+    // VTA stage handles
+    vta_fetch_handle_ = VTAMapRegister(VTA_FETCH_ADDR, VTA_RANGE);
+    vta_load_handle_ = VTAMapRegister(VTA_LOAD_ADDR, VTA_RANGE);
+    vta_compute_handle_ = VTAMapRegister(VTA_COMPUTE_ADDR, VTA_RANGE);
+    vta_store_handle_ = VTAMapRegister(VTA_STORE_ADDR, VTA_RANGE);
+  }
+
+  ~VTADevice() {
+    // Close VTA stage handle
+    VTAUnmapRegister(vta_fetch_handle_, VTA_RANGE);
+    VTAUnmapRegister(vta_load_handle_, VTA_RANGE);
+    VTAUnmapRegister(vta_compute_handle_, VTA_RANGE);
+    VTAUnmapRegister(vta_store_handle_, VTA_RANGE);
+  }
+
+  int Run(vta_phy_addr_t insn_phy_addr,
+          uint32_t insn_count,
+          uint32_t wait_cycles) {
+    // NOTE: Register address map is derived from the auto-generated
+    // driver files available under hardware/build/vivado/<design>/export/driver
+    // FETCH @ 0x10 : Data signal of insn_count_V
+    VTAWriteMappedReg(vta_fetch_handle_, 0x10, insn_count);
+    // FETCH @ 0x18 : Data signal of insns_V
+    VTAWriteMappedReg(vta_fetch_handle_, 0x18, insn_phy_addr);
+    // LOAD @ 0x10 : Data signal of inputs_V
+    VTAWriteMappedReg(vta_load_handle_, 0x10, 0);
+    // LOAD @ 0x18 : Data signal of weight_V
+    VTAWriteMappedReg(vta_load_handle_, 0x18, 0);
+    // COMPUTE @ 0x20 : Data signal of uops_V
+    VTAWriteMappedReg(vta_compute_handle_, 0x20, 0);
+    // COMPUTE @ 0x28 : Data signal of biases_V
+    VTAWriteMappedReg(vta_compute_handle_, 0x28, 0);
+    // STORE @ 0x10 : Data signal of outputs_V
+    VTAWriteMappedReg(vta_store_handle_, 0x10, 0);
+
+    // VTA start
+    VTAWriteMappedReg(vta_fetch_handle_, 0x0, VTA_START);
+    VTAWriteMappedReg(vta_load_handle_, 0x0, VTA_AUTORESTART);
+    VTAWriteMappedReg(vta_compute_handle_, 0x0, VTA_AUTORESTART);
+    VTAWriteMappedReg(vta_store_handle_, 0x0, VTA_AUTORESTART);
+
+    // Loop until the VTA is done
+    unsigned t, flag = 0;
+    for (t = 0; t < wait_cycles; ++t) {
+      flag = VTAReadMappedReg(vta_compute_handle_, 0x18);
+      if (flag == VTA_DONE) break;
+      std::this_thread::yield();
+    }
+    // Report error if timeout
+    return t < wait_cycles ? 0 : 1;
+  }
+
+ private:
+  // VTA handles (register maps)
+  void* vta_fetch_handle_{nullptr};
+  void* vta_load_handle_{nullptr};
+  void* vta_compute_handle_{nullptr};
+  void* vta_store_handle_{nullptr};
+};
+
+VTADeviceHandle VTADeviceAlloc() {
+  return new VTADevice();
+}
+
+void VTADeviceFree(VTADeviceHandle handle) {
+  delete static_cast<VTADevice*>(handle);
+}
+
+int VTADeviceRun(VTADeviceHandle handle,
+                 vta_phy_addr_t insn_phy_addr,
+                 uint32_t insn_count,
+                 uint32_t wait_cycles) {
+  return static_cast<VTADevice*>(handle)->Run(
+      insn_phy_addr, insn_count, wait_cycles);
+}
+
 void VTAProgram(const char* bitstream) {
-    int elem;
-    FILE *src, *dst, *partial;
-    partial = fopen(VTA_PYNQ_BS_IS_PARTIAL, "w");
-    if (partial == NULL) {
-        printf("Cannot open partial config file %s\n", VTA_PYNQ_BS_IS_PARTIAL);
+  int elem;
+  FILE *src, *dst, *partial;
+  partial = fopen(VTA_PYNQ_BS_IS_PARTIAL, "w");
+  if (partial == NULL) {
+    printf("Cannot open partial config file %s\n", VTA_PYNQ_BS_IS_PARTIAL);
         fclose(partial);
         exit(1);
-    }
-    fputc('0', partial);
-    fclose(partial);
-    src = fopen(bitstream, "rb");
-    if (src == NULL) {
-        printf("Cannot open bitstream %s\n", bitstream);
-        exit(1);
-    }
-    dst = fopen(VTA_PYNQ_BS_XDEVCFG, "wb");
-    if (dst == NULL) {
-        printf("Cannot open device file %s\n", VTA_PYNQ_BS_XDEVCFG);
-        fclose(dst);
-        exit(1);
-    }
-    elem = fgetc(src);
-    while (elem != EOF) {
-        fputc(elem, dst);
-        elem = fgetc(src);
-    }
-    fclose(src);
+  }
+  fputc('0', partial);
+  fclose(partial);
+  src = fopen(bitstream, "rb");
+  if (src == NULL) {
+    printf("Cannot open bitstream %s\n", bitstream);
+    exit(1);
+  }
+  dst = fopen(VTA_PYNQ_BS_XDEVCFG, "wb");
+  if (dst == NULL) {
+    printf("Cannot open device file %s\n", VTA_PYNQ_BS_XDEVCFG);
     fclose(dst);
+    exit(1);
+  }
+  elem = fgetc(src);
+  while (elem != EOF) {
+    fputc(elem, dst);
+    elem = fgetc(src);
+  }
+  fclose(src);
+  fclose(dst);
 }
diff --git a/vta/src/pynq/pynq_driver.h b/vta/src/pynq/pynq_driver.h
index 481df6bbe077..74da67d7c035 100644
--- a/vta/src/pynq/pynq_driver.h
+++ b/vta/src/pynq/pynq_driver.h
@@ -32,6 +32,11 @@ void xlnkFlushCache(void* buf, int size);
 void xlnkInvalidateCache(void* buf, int size);
 #endif
 
+void *VTAMapRegister(uint32_t addr, size_t length);
+void VTAUnmapRegister(void *vta, size_t length);
+void VTAWriteMappedReg(void* base_addr, uint32_t offset, uint32_t val);
+uint32_t VTAReadMappedReg(void* base_addr, uint32_t offset);
+
 /*! \brief (Pynq only) Partial bitstream status file path */
 #define VTA_PYNQ_BS_IS_PARTIAL "/sys/devices/soc0/amba/f8007000.devcfg/is_partial_bitstream"
 /*! \brief (Pynq only) Bitstream destination file path */
@@ -44,9 +49,6 @@ void xlnkInvalidateCache(void* buf, int size);
 /*! \brief (Pynq only) MMIO driver constant */
 #define VTA_PYNQ_MMIO_WORD_MASK (~(MMIO_WORD_LENGTH - 1))
 
-/*! \brief Physically contiguous buffer size limit */
-#define VTA_MAX_XFER (1<<22)
-
 /*! \brief VTA configuration register address range */
 #define VTA_RANGE 0x100
 /*! \brief VTA configuration register start value */
diff --git a/vta/src/runtime.cc b/vta/src/runtime.cc
index 9333e393fc12..a8819323fc11 100644
--- a/vta/src/runtime.cc
+++ b/vta/src/runtime.cc
@@ -1,13 +1,13 @@
 /*!
  *  Copyright (c) 2018 by Contributors
  * \file runtime.cc
- * \brief VTA runtime for PYNQ in C++11
+ * \brief Generic VTA runtime in C++11.
+ *
+ *  The runtime depends on specific instruction
+ *  stream spec as specified in hw_spec.h
+ *  It is intended to be used as a dynamic library
+ *  to enable hot swapping of hardware configurations.
  */
-
-#ifdef VTA_PYNQ_TARGET
-#include "./pynq/pynq_driver.h"
-#endif  // VTA_PYNQ_TARGET
-
 #include <vta/driver.h>
 #include <vta/hw_spec.h>
 #include <vta/runtime.h>
@@ -245,8 +245,8 @@ class BaseQueue {
     if (!coherent_ && always_cache_ && dram_extent != 0) {
       dram_begin = dram_begin * elem_bits / 8;
       dram_extent = dram_extent * elem_bits / 8;
-      VTAFlushCache(reinterpret_cast<void*>(dram_phy_addr_ + dram_begin),
-                     dram_extent);
+      VTAFlushCache(dram_phy_addr_ + dram_begin,
+                    dram_extent);
     }
   }
   /*! \brief Read barrier to make sure that data written by VTA is visible to CPU. */
@@ -254,8 +254,8 @@ class BaseQueue {
     if (!coherent_ && always_cache_ && dram_extent != 0) {
       dram_begin = dram_begin * elem_bits / 8;
       dram_extent = dram_extent * elem_bits / 8;
-      VTAInvalidateCache(reinterpret_cast<void*>(dram_phy_addr_ + dram_begin),
-                          dram_extent);
+      VTAInvalidateCache(dram_phy_addr_ + dram_begin,
+                         dram_extent);
     }
   }
 
@@ -818,20 +818,13 @@ class CommandQueue {
   void InitSpace() {
     uop_queue_.InitSpace();
     insn_queue_.InitSpace();
-    // VTA stage handles
-    vta_fetch_handle_ = VTAMapRegister(VTA_FETCH_ADDR, VTA_RANGE);
-    vta_load_handle_ = VTAMapRegister(VTA_LOAD_ADDR, VTA_RANGE);
-    vta_compute_handle_ = VTAMapRegister(VTA_COMPUTE_ADDR, VTA_RANGE);
-    vta_store_handle_ = VTAMapRegister(VTA_STORE_ADDR, VTA_RANGE);
+    device_ = VTADeviceAlloc();
+    assert(device_ != nullptr);
     printf("Initialize VTACommandHandle...\n");
   }
 
   ~CommandQueue() {
-    // Close VTA stage handle
-    VTAUnmapRegister(vta_fetch_handle_, VTA_RANGE);
-    VTAUnmapRegister(vta_load_handle_, VTA_RANGE);
-    VTAUnmapRegister(vta_compute_handle_, VTA_RANGE);
-    VTAUnmapRegister(vta_store_handle_, VTA_RANGE);
+    VTADeviceFree(device_);
     printf("Close VTACommandhandle...\n");
   }
 
@@ -951,44 +944,14 @@ class CommandQueue {
     assert(reinterpret_cast<VTAMemInsn*>(
         insn_queue_.data())[insn_queue_.count()-1].opcode == VTA_OPCODE_FINISH);
 
-#ifdef VTA_PYNQ_TARGET
     // Make sure that we don't exceed contiguous physical memory limits
-    assert(insn_queue_.count() < VTA_MAX_XFER);
-
-    // NOTE: Register address map is derived from the auto-generated
-    // driver files available under hardware/build/vivado/<design>/export/driver
-    // FETCH @ 0x10 : Data signal of insn_count_V
-    VTAWriteMappedReg(vta_fetch_handle_, 0x10, insn_queue_.count());
-    // FETCH @ 0x18 : Data signal of insns_V
-    VTAWriteMappedReg(vta_fetch_handle_, 0x18, insn_queue_.dram_phy_addr());
-    // LOAD @ 0x10 : Data signal of inputs_V
-    VTAWriteMappedReg(vta_load_handle_, 0x10, 0);
-    // LOAD @ 0x18 : Data signal of weight_V
-    VTAWriteMappedReg(vta_load_handle_, 0x18, 0);
-    // COMPUTE @ 0x20 : Data signal of uops_V
-    VTAWriteMappedReg(vta_compute_handle_, 0x20, 0);
-    // COMPUTE @ 0x28 : Data signal of biases_V
-    VTAWriteMappedReg(vta_compute_handle_, 0x28, 0);
-    // STORE @ 0x10 : Data signal of outputs_V
-    VTAWriteMappedReg(vta_store_handle_, 0x10, 0);
-
-    // VTA start
-    VTAWriteMappedReg(vta_fetch_handle_, 0x0, VTA_START);
-    VTAWriteMappedReg(vta_load_handle_, 0x0, VTA_AUTORESTART);
-    VTAWriteMappedReg(vta_compute_handle_, 0x0, VTA_AUTORESTART);
-    VTAWriteMappedReg(vta_store_handle_, 0x0, VTA_AUTORESTART);
-
-    // Loop until the VTA is done
-    unsigned t, flag = 0;
-    for (t = 0; t < wait_cycles; ++t) {
-      flag = VTAReadMappedReg(vta_compute_handle_, 0x18);
-      if (flag == VTA_DONE) break;
-      std::this_thread::yield();
-    }
-    // Report error if timeout
-    assert(t < wait_cycles);
-#endif  // VTA_PYNQ_TARGET
-
+    assert(insn_queue_.count() * sizeof(VTAGenericInsn) < VTA_MAX_XFER);
+    int timeout = VTADeviceRun(
+        device_,
+        insn_queue_.dram_phy_addr(),
+        insn_queue_.count(),
+        wait_cycles);
+    assert(timeout == 0);
     // Reset buffers
     uop_queue_.Reset();
     insn_queue_.Reset();
@@ -1147,7 +1110,7 @@ class CommandQueue {
   void CheckInsnOverFlow() {
     // At each API call, we can at most commit:
     // one pending store, one pending load, and one uop
-    if (insn_queue_.count() >= VTA_MAX_XFER) {
+    if ((insn_queue_.count() + 4) * sizeof(VTAGenericInsn) >= VTA_MAX_XFER) {
       this->AutoSync();
     }
   }
@@ -1155,11 +1118,7 @@ class CommandQueue {
   void AutoSync() {
     this->Synchronize(1 << 31);
   }
-  // VTA handles (register maps)
-  VTAHandle vta_fetch_handle_{nullptr};
-  VTAHandle vta_load_handle_{nullptr};
-  VTAHandle vta_compute_handle_{nullptr};
-  VTAHandle vta_store_handle_{nullptr};
+
   // Internal debug flag
   int debug_flag_{0};
   // The kernel we currently recording
@@ -1168,6 +1127,8 @@ class CommandQueue {
   UopQueue<VTA_MAX_XFER, true, true> uop_queue_;
   // instruction queue
   InsnQueue<VTA_MAX_XFER, true, true> insn_queue_;
+  // Device handle
+  VTADeviceHandle device_{nullptr};
 };
 
 }  // namespace vta
@@ -1302,11 +1263,3 @@ void VTASynchronize(VTACommandHandle cmd, uint32_t wait_cycles) {
   static_cast<vta::CommandQueue*>(cmd)->
       Synchronize(wait_cycles);
 }
-
-extern "C" int VTARuntimeDynamicMagic() {
-#ifdef VTA_DYNAMIC_MAGIC
-  return VTA_DYNAMIC_MAGIC;
-#else
-  return 0;
-#endif
-}
diff --git a/vta/src/tvm/vta_device_api.cc b/vta/src/tvm/vta_device_api.cc
index ce864df09402..450b23b05fee 100644
--- a/vta/src/tvm/vta_device_api.cc
+++ b/vta/src/tvm/vta_device_api.cc
@@ -11,10 +11,6 @@
 
 #include "../../nnvm/tvm/src/runtime/workspace_pool.h"
 
-extern "C" {
-  typedef void (*FShutdown)();
-  typedef int (*FDynamicMagic)();
-}
 
 namespace tvm {
 namespace runtime {

From 63b33124bd31309ce98fb53c59bdeee03caee7ae Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 12 Apr 2018 22:12:43 -0700
Subject: [PATCH 832/949] [DRIVER] Add simulator, unify testcase to unittest
 (#25)

---
 vta/Makefile                               |  20 +-
 vta/include/vta/driver.h                   |   2 +-
 vta/include/vta/hw_spec.h                  |   4 +-
 vta/include/vta/runtime.h                  |   2 +-
 vta/make/config.mk                         |   3 +-
 vta/python/vta/__init__.py                 |   1 +
 vta/python/vta/environment.py              |  22 +-
 vta/python/vta/ir_pass.py                  |  16 +-
 vta/python/vta/rpc_client.py               |   3 +-
 vta/python/vta/testing/__init__.py         |   3 +
 vta/python/vta/testing/simulator.py        |  51 ++
 vta/python/vta/testing/util.py             |  30 ++
 vta/src/data_buffer.h                      |   2 +-
 vta/src/pynq/pynq_driver.cc                |   4 +-
 vta/src/runtime.cc                         |  25 +-
 vta/src/sim/sim_driver.cc                  | 581 +++++++++++++++++++++
 vta/src/tvm/vta_device_api.cc              |   3 -
 vta/tests/python/pynq/test_vta_insn.py     | 504 ------------------
 vta/tests/python/unittest/test_vta_insn.py | 482 +++++++++++++++++
 19 files changed, 1217 insertions(+), 541 deletions(-)
 create mode 100644 vta/python/vta/testing/__init__.py
 create mode 100644 vta/python/vta/testing/simulator.py
 create mode 100644 vta/python/vta/testing/util.py
 create mode 100644 vta/src/sim/sim_driver.cc
 delete mode 100644 vta/tests/python/pynq/test_vta_insn.py
 create mode 100644 vta/tests/python/unittest/test_vta_insn.py

diff --git a/vta/Makefile b/vta/Makefile
index 069f6e01c3ea..6bfa82dc2e10 100644
--- a/vta/Makefile
+++ b/vta/Makefile
@@ -40,6 +40,19 @@ ifneq ($(ADD_LDFLAGS), NONE)
 	LDFLAGS += $(ADD_LDFLAGS)
 endif
 
+UNAME_S := $(shell uname -s)
+
+ifeq ($(UNAME_S), Darwin)
+	SHARED_LIBRARY_SUFFIX := dylib
+	WHOLE_ARCH= -all_load
+	NO_WHOLE_ARCH= -noall_load
+	LDFLAGS += -undefined dynamic_lookup
+else
+	SHARED_LIBRARY_SUFFIX := so
+	WHOLE_ARCH= --whole-archive
+	NO_WHOLE_ARCH= --no-whole-archive
+endif
+
 
 all: lib/libvta.so lib/libvta_runtime.so
 
@@ -53,6 +66,10 @@ ifeq ($(TARGET), VTA_PYNQ_TARGET)
 	LDFLAGS += -l:libdma.so
 endif
 
+ifeq ($(TARGET), sim)
+	VTA_LIB_SRC += $(wildcard src/sim/*.cc)
+endif
+
 VTA_LIB_OBJ = $(patsubst src/%.cc, build/%.o, $(VTA_LIB_SRC))
 
 build/%.o: src/%.cc
@@ -71,7 +88,7 @@ lib/libvta_runtime.so: build/runtime.o
 lint: pylint cpplint
 
 cpplint:
-	python nnvm/dmlc-core/scripts/lint.py vta cpp include src hardware tests
+	python nnvm/dmlc-core/scripts/lint.py vta cpp include src
 
 pylint:
 	pylint python/vta --rcfile=$(ROOTDIR)/tests/lint/pylintrc
@@ -86,3 +103,4 @@ clean:
 -include build/*.d
 -include build/*/*.d
 -include build/*/*/*.d
+-include build/*/*/*/*.d
diff --git a/vta/include/vta/driver.h b/vta/include/vta/driver.h
index 8a29fc47aa84..58f778806208 100644
--- a/vta/include/vta/driver.h
+++ b/vta/include/vta/driver.h
@@ -77,7 +77,7 @@ void VTAMemFree(void* buf);
  * \param buf Pointer to memory region allocated with VTAMemAlloc.
  * \return The physical address of the memory region.
  */
-vta_phy_addr_t VTAGetMemPhysAddr(void* buf);
+vta_phy_addr_t VTAMemGetPhyAddr(void* buf);
 
 /*!
  * \brief Flushes the region of memory out of the CPU cache to DRAM.
diff --git a/vta/include/vta/hw_spec.h b/vta/include/vta/hw_spec.h
index 7eae322a0aca..9d62d4e7d57e 100644
--- a/vta/include/vta/hw_spec.h
+++ b/vta/include/vta/hw_spec.h
@@ -519,8 +519,8 @@ typedef struct {
   uint64_t alu_opcode     : VTA_ALU_OPCODE_BIT_WIDTH;
   /*! \brief Use immediate is true */
   uint64_t use_imm        : 1;
-  /*! \brief Immediate value */
-  uint64_t imm            : VTA_ALUOP_IMM_BIT_WIDTH;
+  /*! \brief Immediate value: allow negative value */
+  int64_t imm            : VTA_ALUOP_IMM_BIT_WIDTH;
 } VTAAluInsn;
 
 /*! \brief VTA ALU instruction converter */
diff --git a/vta/include/vta/runtime.h b/vta/include/vta/runtime.h
index c9373846d903..479540129226 100644
--- a/vta/include/vta/runtime.h
+++ b/vta/include/vta/runtime.h
@@ -196,7 +196,7 @@ void VTAUopPush(uint32_t mode,
                 uint32_t wgt_index,
                 uint32_t opcode,
                 uint32_t use_imm,
-                uint32_t imm_val);
+                int32_t imm_val);
 
 /*!
  * \brief Mark start of a micro op loop.
diff --git a/vta/make/config.mk b/vta/make/config.mk
index 9f611896a8ba..e329dcf987b8 100644
--- a/vta/make/config.mk
+++ b/vta/make/config.mk
@@ -27,7 +27,7 @@ ADD_LDFLAGS=
 ADD_CFLAGS=
 
 # the hardware target
-TARGET = VTA_PYNQ_TARGET
+TARGET = pynq
 
 #---------------------
 # VTA hardware parameters
@@ -89,7 +89,6 @@ VTA_OUT_BUFF_SIZE = $(shell echo "$$(( 1 << $(VTA_LOG_OUT_BUFF_SIZE) ))" )
 
 # Update ADD_CFLAGS
 ADD_CFLAGS += \
-	-D$(TARGET) \
 	-DVTA_LOG_WGT_WIDTH=$(VTA_LOG_WGT_WIDTH) -DVTA_LOG_INP_WIDTH=$(VTA_LOG_INP_WIDTH) \
 	-DVTA_LOG_ACC_WIDTH=$(VTA_LOG_ACC_WIDTH) -DVTA_LOG_OUT_WIDTH=$(VTA_LOG_OUT_WIDTH) \
 	-DVTA_LOG_BATCH=$(VTA_LOG_BATCH) \
diff --git a/vta/python/vta/__init__.py b/vta/python/vta/__init__.py
index 275c15b227a3..693a4124f40b 100644
--- a/vta/python/vta/__init__.py
+++ b/vta/python/vta/__init__.py
@@ -8,6 +8,7 @@
     from . import arm_conv2d, vta_conv2d
     from .build_module import build_config, lower, build
     from .rpc_client import reconfig_runtime, program_fpga
+
     from . import graph
 except ImportError:
     pass
diff --git a/vta/python/vta/environment.py b/vta/python/vta/environment.py
index b0d7d170fd7d..8ff2bbce2787 100644
--- a/vta/python/vta/environment.py
+++ b/vta/python/vta/environment.py
@@ -89,7 +89,7 @@ class Environment(object):
     """
     current = None
     cfg_keys = [
-        "target",
+        "TARGET",
         "LOG_INP_WIDTH",
         "LOG_WGT_WIDTH",
         "LOG_ACC_WIDTH",
@@ -204,9 +204,19 @@ def gemm(self):
 
     @property
     def gevm(self):
-        """GEMM intrinsic"""
+        """GEVM intrinsic"""
         return self.dev.gevm
 
+    @property
+    def target_host(self):
+        """The target host"""
+        if self.TARGET == "pynq":
+            return "llvm -target=armv7-none-linux-gnueabihf"
+        elif self.TARGET == "sim":
+            return "llvm"
+        else:
+            raise ValueError("Unknown target %s" % self.TARGET)
+
 
 def get_env():
     """Get the current VTA Environment.
@@ -278,6 +288,7 @@ def _init_env():
 
     for k in Environment.cfg_keys:
         keys.add("VTA_" + k)
+    keys.add("TARGET")
 
     if not os.path.isfile(filename):
         raise RuntimeError(
@@ -290,8 +301,11 @@ def _init_env():
             for k in keys:
                 if k  +" =" in line:
                     val = line.split("=")[1].strip()
-                    cfg[k[4:]] = int(val)
-    cfg["target"] = "pynq"
+                    if k.startswith("VTA_"):
+                        k = k[4:]
+                        cfg[k] = int(val)
+                    else:
+                        cfg[k] = val
     return Environment(cfg)
 
 Environment.current = _init_env()
diff --git a/vta/python/vta/ir_pass.py b/vta/python/vta/ir_pass.py
index f85f1760e72b..9310d46dce88 100644
--- a/vta/python/vta/ir_pass.py
+++ b/vta/python/vta/ir_pass.py
@@ -78,8 +78,7 @@ def _visit(op):
             if not fail[0]:
                 begin = tvm.call_extern(
                     "int32", "VTAUopLoopBegin", stmt.extent, *gemm_offsets)
-                end = tvm.call_extern(
-                    "int32", "VTAUopLoopEnd", stmt.extent, *gemm_offsets)
+                end = tvm.call_extern("int32", "VTAUopLoopEnd")
                 return [begin, ret, end]
         raise ValueError("Failed to fold the GEMM instructions..")
 
@@ -683,8 +682,14 @@ def _flatten_loop(src_coeff, dst_coeff, extents):
                 else:
                     raise RuntimeError(
                         "Function call not recognized %s" % (loop_body.value.name))
+            elif isinstance(loop_body.value, tvm.expr.Load):
+                alu_opcode = env.dev.ALU_OPCODE_SHR
+                lhs = loop_body.value
+                rhs = tvm.const(0)
             else:
-                raise RuntimeError("Expression not recognized %s" % (type(loop_body.value)))
+                raise RuntimeError(
+                    "Expression not recognized %s, %s, %s" % (
+                        type(loop_body.value), str(loop_body.value), str(stmt)))
 
             # Derive array index coefficients
             dst_coeff = tvm.arith.DetectLinearEquation(dst_idx, indices)
@@ -772,7 +777,9 @@ def _flatten_loop(src_coeff, dst_coeff, extents):
             irb = tvm.ir_builder.create()
             for idx, extent in enumerate(extents):
                 irb.emit(tvm.call_extern(
-                    "int32", "VTAUopLoopBegin", extent, dst_coeff[idx], src_coeff[idx]))
+                    "int32", "VTAUopLoopBegin",
+                    extent, dst_coeff[idx], src_coeff[idx], 0))
+            use_imm = int(use_imm)
             irb.emit(tvm.call_extern(
                 "int32", "VTAUopPush",
                 1, 0,
@@ -804,5 +811,6 @@ def debug_print(stmt):
     stmt : Stmt
         The
     """
+    # pylint: disable=superfluous-parens
     print(stmt)
     return stmt
diff --git a/vta/python/vta/rpc_client.py b/vta/python/vta/rpc_client.py
index a6355a592279..f0a02d7cc045 100644
--- a/vta/python/vta/rpc_client.py
+++ b/vta/python/vta/rpc_client.py
@@ -24,8 +24,7 @@ def reconfig_runtime(remote):
             "VTA_LOG_WGT_BUFF_SIZE",
             "VTA_LOG_ACC_BUFF_SIZE",
             "VTA_LOG_OUT_BUFF_SIZE"]
-
-    cflags = ["-DVTA_%s_TARGET" % env.target.upper()]
+    cflags = []
     for k in keys:
         cflags += ["-D%s=%s" % (k, str(getattr(env, k[4:])))]
     freconfig = remote.get_function("tvm.contrib.vta.reconfig_runtime")
diff --git a/vta/python/vta/testing/__init__.py b/vta/python/vta/testing/__init__.py
new file mode 100644
index 000000000000..513fa1e99a52
--- /dev/null
+++ b/vta/python/vta/testing/__init__.py
@@ -0,0 +1,3 @@
+"""Testing utilities, this namespace is not imported by default."""
+
+from . util import run
diff --git a/vta/python/vta/testing/simulator.py b/vta/python/vta/testing/simulator.py
new file mode 100644
index 000000000000..bb436a1853a8
--- /dev/null
+++ b/vta/python/vta/testing/simulator.py
@@ -0,0 +1,51 @@
+"""Utilities to start simulator."""
+import os
+import ctypes
+import json
+import tvm
+
+def _load_lib():
+    """Load local library, assuming they are simulator."""
+    # pylint: disable=unused-variable
+    curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
+    dll_path = [
+        os.path.abspath(os.path.join(curr_path, "../../../lib/libvta.so")),
+        os.path.abspath(os.path.join(curr_path, "../../../lib/libvta_runtime.so"))
+    ]
+    runtime_dll = []
+    if not all(os.path.exists(f) for f in dll_path):
+        return []
+    try:
+        for fname in dll_path:
+            runtime_dll.append(ctypes.CDLL(fname, ctypes.RTLD_GLOBAL))
+        return runtime_dll
+    except OSError:
+        return []
+
+
+def enabled():
+    """Check if simulator is enabled."""
+    f = tvm.get_global_func("vta.simulator.profiler_clear", True)
+    return f is not None
+
+
+def clear_stats():
+    """Clear profiler statistics"""
+    f = tvm.get_global_func("vta.simulator.profiler_clear", True)
+    if f:
+        f()
+
+
+def stats():
+    """Clear profiler statistics
+
+    Returns
+    -------
+    stats : dict
+        Current profiler statistics
+    """
+    x = tvm.get_global_func("vta.simulator.profiler_status")()
+    return json.loads(x)
+
+
+LIBS = _load_lib()
diff --git a/vta/python/vta/testing/util.py b/vta/python/vta/testing/util.py
new file mode 100644
index 000000000000..bbf6417a167e
--- /dev/null
+++ b/vta/python/vta/testing/util.py
@@ -0,0 +1,30 @@
+"""Test Utilities"""
+from __future__ import absolute_import as _abs
+
+import os
+from tvm.contrib import rpc
+from ..environment import get_env
+from . import simulator
+
+
+def run(run_func):
+    """Run test function on all available env.
+
+    Parameters
+    ----------
+    run_func : function(env, remote)
+    """
+    env = get_env()
+    # run on simulator
+    if simulator.enabled():
+        env.TARGET = "sim"
+        run_func(env, rpc.LocalSession())
+
+    # Run on PYNQ if env variable exists
+    pynq_host = os.environ.get("VTA_PYNQ_RPC_HOST", None)
+    if pynq_host:
+        env.TARGET = "pynq"
+        port = os.environ.get("VTA_PYNQ_RPC_PORT", "9091")
+        port = int(port)
+        remote = rpc.connect(pynq_host, port)
+        run_func(env, remote)
diff --git a/vta/src/data_buffer.h b/vta/src/data_buffer.h
index aed92c49e795..fba46dc07efa 100644
--- a/vta/src/data_buffer.h
+++ b/vta/src/data_buffer.h
@@ -57,7 +57,7 @@ struct DataBuffer {
     assert(data != nullptr);
     DataBuffer* buffer = new DataBuffer();
     buffer->data_ = data;
-    buffer->phy_addr_ = VTAGetMemPhysAddr(data);
+    buffer->phy_addr_ = VTAMemGetPhyAddr(data);
     return buffer;
   }
   /*!
diff --git a/vta/src/pynq/pynq_driver.cc b/vta/src/pynq/pynq_driver.cc
index 0330450db285..e2630b14acde 100644
--- a/vta/src/pynq/pynq_driver.cc
+++ b/vta/src/pynq/pynq_driver.cc
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2018 by Contributors
- * \file vta_pynq_driver.c
+ * \file pynq_driver.c
  * \brief VTA driver for Pynq board.
  */
 
@@ -17,7 +17,7 @@ void VTAMemFree(void* buf) {
   cma_free(buf);
 }
 
-vta_phy_addr_t VTAGetMemPhysAddr(void* buf) {
+vta_phy_addr_t VTAMemGetPhyAddr(void* buf) {
   return cma_get_phy_addr(buf);
 }
 
diff --git a/vta/src/runtime.cc b/vta/src/runtime.cc
index a8819323fc11..da5109c141ee 100644
--- a/vta/src/runtime.cc
+++ b/vta/src/runtime.cc
@@ -11,6 +11,7 @@
 #include <vta/driver.h>
 #include <vta/hw_spec.h>
 #include <vta/runtime.h>
+#include <dmlc/logging.h>
 
 #include <cassert>
 #include <vector>
@@ -113,7 +114,7 @@ class UopKernel {
             uint32_t wgt_index,
             uint32_t opcode,
             uint32_t use_imm,
-            uint32_t imm_val) {
+            int32_t imm_val) {
     // The loop nest structure
     VerifyDep(dst_index);
     VTAUop op;
@@ -166,7 +167,7 @@ class UopKernel {
   uint32_t opcode_{0xFFFFFFFF};
   uint32_t reset_out_{0xFFFFFFFF};
   bool use_imm_{false};
-  uint16_t imm_val_{0};
+  int16_t imm_val_{0};
 
  private:
   // Verify that we don't write to the same acc_mem index two cycles in a row
@@ -195,10 +196,6 @@ class UopKernel {
 
 /*!
  * \brief Base class of all queues to send and recv serial data.
- * \param kElemBytes Element unit bytes.
- * \param kMaxBytes Maximum number of bytes.
- * \param kCoherent Whether we have coherent access to the buffer.
- * \param kAlwaysCache Wether we should use cached memory.
  */
 class BaseQueue {
  public:
@@ -227,7 +224,7 @@ class BaseQueue {
     dram_buffer_ = static_cast<char*>(VTAMemAlloc(
         max_bytes, coherent || always_cache_));
     assert(dram_buffer_ != nullptr);
-    dram_phy_addr_ = VTAGetMemPhysAddr(dram_buffer_);
+    dram_phy_addr_ = VTAMemGetPhyAddr(dram_buffer_);
   }
   /*!
    * \brief Reset the pointer of the buffer.
@@ -597,14 +594,14 @@ class InsnQueue : public BaseQueue {
         }
         // Print instruction field information
         if (c.mem.opcode == VTA_OPCODE_LOAD) {
-            printf("LOAD ");
-            if (c.mem.memory_type == VTA_MEM_ID_UOP) printf("UOP\n");
-            if (c.mem.memory_type == VTA_MEM_ID_WGT) printf("WGT\n");
-            if (c.mem.memory_type == VTA_MEM_ID_INP) printf("INP\n");
-            if (c.mem.memory_type == VTA_MEM_ID_ACC) printf("ACC\n");
+          printf("LOAD ");
+          if (c.mem.memory_type == VTA_MEM_ID_UOP) printf("UOP\n");
+          if (c.mem.memory_type == VTA_MEM_ID_WGT) printf("WGT\n");
+          if (c.mem.memory_type == VTA_MEM_ID_INP) printf("INP\n");
+          if (c.mem.memory_type == VTA_MEM_ID_ACC) printf("ACC\n");
         }
         if (c.mem.opcode == VTA_OPCODE_STORE) {
-            printf("STORE\n");
+          printf("STORE:\n");
         }
         printf("\tdep - pop prev: %d, pop next: %d, push prev: %d, push next: %d\n",
                static_cast<int>(c.mem.pop_prev_dep),
@@ -1210,7 +1207,7 @@ void VTAUopPush(uint32_t mode,
                 uint32_t wgt_index,
                 uint32_t opcode,
                 uint32_t use_imm,
-                uint32_t imm_val) {
+                int32_t imm_val) {
   vta::CommandQueue::ThreadLocal()->record_kernel()
       ->Push(mode, reset_out, dst_index, src_index,
              wgt_index, opcode, use_imm, imm_val);
diff --git a/vta/src/sim/sim_driver.cc b/vta/src/sim/sim_driver.cc
new file mode 100644
index 000000000000..a88ab2466a25
--- /dev/null
+++ b/vta/src/sim/sim_driver.cc
@@ -0,0 +1,581 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file sim_driver.cc
+ * \brief VTA driver for simulated backend.
+ */
+#include <vta/driver.h>
+#include <vta/hw_spec.h>
+#include <tvm/runtime/registry.h>
+#include <type_traits>
+#include <mutex>
+#include <map>
+#include <unordered_map>
+#include <cstring>
+#include <sstream>
+
+namespace vta {
+namespace sim {
+
+/*!
+ * \brief Helper class to pack and unpack bits
+ *  Applies truncation when pack to low level bits.
+ *
+ * \tparam bits The number of bits in integer.
+ * \note This implementation relies on little endian.
+ */
+template<uint32_t bits>
+class BitPacker {
+ public:
+  explicit BitPacker(void* data) {
+    data_ = static_cast<uint32_t*>(data);
+  }
+
+  uint32_t GetUnsigned(uint32_t index) const {
+    if (bits == 32) {
+      return data_[index];
+    } else if (bits == 16) {
+      return reinterpret_cast<uint16_t*>(data_)[index];
+    } else if (bits == 8) {
+      return reinterpret_cast<uint8_t*>(data_)[index];
+    } else {
+      uint32_t offset = index / kNumPackElem;
+      uint32_t shift = index % kNumPackElem;
+      return (data_[offset] >> shift) & kMask;
+    }
+  }
+
+  int32_t GetSigned(uint32_t index) const {
+    if (bits == 32) {
+      return reinterpret_cast<int32_t*>(data_)[index];
+    } else if (bits == 16) {
+      return reinterpret_cast<int16_t*>(data_)[index];
+    } else if (bits == 8) {
+      return reinterpret_cast<int8_t*>(data_)[index];
+    } else {
+      uint32_t offset = index / kNumPackElem;
+      uint32_t shift = (index % kNumPackElem) * bits;
+      int32_t uvalue = static_cast<int32_t>(
+          (data_[offset] >> shift) & kMask);
+      int kleft = 32 - bits;
+      return (uvalue << kleft) >> kleft;
+    }
+  }
+
+  void SetUnsigned(uint32_t index, uint32_t value) {
+    if (bits == 32) {
+      data_[index] = value;
+    } else if (bits == 16) {
+      reinterpret_cast<uint16_t*>(data_)[index] = value;
+    } else if (bits == 8) {
+      reinterpret_cast<uint8_t*>(data_)[index] = value;
+    } else {
+      uint32_t offset = index / kNumPackElem;
+      uint32_t shift = (index % kNumPackElem) * bits;
+      data_[offset] &= (~(kMask << shift));
+      data_[offset] |= (value & kMask) << shift;
+    }
+  }
+
+  void SetSigned(uint32_t index, int32_t value) {
+    if (bits == 32) {
+      reinterpret_cast<int32_t*>(data_)[index] = value;
+    } else if (bits == 16) {
+      reinterpret_cast<int16_t*>(data_)[index] = value;
+    } else if (bits == 8) {
+      reinterpret_cast<int8_t*>(data_)[index] = value;
+    } else {
+      uint32_t offset = index / kNumPackElem;
+      uint32_t shift = (index % kNumPackElem) * bits;
+      data_[offset] &= (~(kMask << shift));
+      data_[offset] |= static_cast<uint32_t>(value & kMask) << shift;
+    }
+  }
+
+ private:
+  uint32_t* data_;
+  static constexpr uint32_t kNumPackElem = 32 / bits;
+  static constexpr uint32_t kMask = (1U << (bits >= 32U ? 31U : bits)) - 1U;
+};
+
+/*!
+ * \brief DRAM memory manager
+ *  Implements simple paging to allow physical address translation.
+ */
+class DRAM {
+ public:
+  /*!
+   * \brief Get virtual address given physical address.
+   * \param phy_addr The simulator phyiscal address.
+   * \return The true virtual address;
+   */
+  void* GetAddr(uint64_t phy_addr) {
+    std::lock_guard<std::mutex> lock(mutex_);
+    uint64_t loc = (phy_addr >> kPageBits) - 1;
+    CHECK_LT(loc, ptable_.size());
+    Page* p = ptable_[loc];
+    CHECK(p != nullptr);
+    size_t offset = (loc - p->ptable_begin) << kPageBits;
+    offset += phy_addr & (kPageSize - 1);
+    return reinterpret_cast<char*>(p->data) + offset;
+  }
+  /*!
+   * \brief Get physical address
+   * \param buf The virtual address.
+   * \return The true physical address;
+   */
+  vta_phy_addr_t GetPhyAddr(void* buf) {
+    std::lock_guard<std::mutex> lock(mutex_);
+    auto it = pmap_.find(buf);
+    CHECK(it != pmap_.end());
+    Page* p = it->second.get();
+    return (p->ptable_begin + 1) << kPageBits;
+  }
+  /*!
+   * \brief Allocate memory from manager
+   * \param size The size of memory
+   * \return The virtual address
+   */
+  void* Alloc(size_t size) {
+    std::lock_guard<std::mutex> lock(mutex_);
+    size_t npage = (size + kPageSize - 1) / kPageSize;
+    auto it = free_map_.lower_bound(npage);
+    if (it != free_map_.end()) {
+      Page* p = it->second;
+      free_map_.erase(it);
+      return p->data;
+    }
+    size_t start = ptable_.size();
+    std::unique_ptr<Page> p(new Page(start, npage));
+    // insert page entry
+    ptable_.resize(start + npage, p.get());
+    void* data = p->data;
+    pmap_[data] = std::move(p);
+    return data;
+  }
+  /*!
+   * \brief Free the memory.
+   * \param size The size of memory
+   * \return The virtual address
+   */
+  void Free(void* data) {
+    std::lock_guard<std::mutex> lock(mutex_);
+    auto it = pmap_.find(data);
+    CHECK(it != pmap_.end());
+    Page* p = it->second.get();
+    free_map_.insert(std::make_pair(p->num_pages, p));
+  }
+
+  static DRAM* Global() {
+    static DRAM inst;
+    return &inst;
+  }
+
+
+ private:
+  // The bits in page table
+  static constexpr vta_phy_addr_t kPageBits = 16;
+  // page size, also the maximum allocable size 16 K
+  static constexpr vta_phy_addr_t kPageSize = 1 << kPageBits;
+  /*! \brief A page in the DRAM */
+  struct Page {
+    /*! \brief Data Type */
+    using DType = typename std::aligned_storage<kPageSize, 256>::type;
+    /*! \brief Start location in page table */
+    size_t ptable_begin;
+    /*! \brief The total number of pages */
+    size_t num_pages;
+    /*! \brief Data */
+    DType* data{nullptr};
+    // construct a new page
+    explicit Page(size_t ptable_begin, size_t num_pages)
+        : ptable_begin(ptable_begin), num_pages(num_pages) {
+      data = new DType[num_pages];
+    }
+    ~Page() {
+      delete [] data;
+    }
+  };
+  // Internal lock
+  std::mutex mutex_;
+  // Physical address -> page
+  std::vector<Page*> ptable_;
+  // virtual addres -> page
+  std::unordered_map<void*, std::unique_ptr<Page> > pmap_;
+  // Free map
+  std::multimap<size_t, Page*> free_map_;
+};
+
+/*!
+ * \brief Register file.
+ * \tparam kBits Number of bits of one value.
+ * \tparam kLane Number of lanes in one element.
+ * \tparam kMaxNumElem Maximum number of element.
+ */
+template<int kBits, int kLane, int kMaxNumElem>
+class SRAM {
+ public:
+  /*! \brief Bytes of single vector element */
+  static const int kElemBytes = (kBits * kLane + 7) / 8;
+  /*! \brief content data type */
+  using DType = typename std::aligned_storage<kElemBytes, kElemBytes>::type;
+  SRAM() {
+    data_ = new DType[kMaxNumElem];
+  }
+  ~SRAM() {
+    delete [] data_;
+  }
+  // Get the i-th index
+  void* BeginPtr(uint32_t index) {
+    CHECK_LT(index, kMaxNumElem);
+    return &(data_[index]);
+  }
+  // Execute the load instruction on this SRAM
+  void Load(const VTAMemInsn* op, DRAM* dram, uint64_t* load_counter) {
+    load_counter[0] += (op->x_size * op->y_size) * kElemBytes;
+    DType* sram_ptr = data_ + op->sram_base;
+    uint8_t* dram_ptr = static_cast<uint8_t*>(dram->GetAddr(
+        op->dram_base * kElemBytes));
+    uint64_t xtotal = op->x_size + op->x_pad_0 + op->x_pad_1;
+    uint32_t ytotal = op->y_size + op->y_pad_0 + op->y_pad_1;
+    uint64_t sram_end = op->sram_base + xtotal * ytotal;
+    CHECK_LE(sram_end, kMaxNumElem);
+    memset(sram_ptr, 0, kElemBytes * xtotal * op->y_pad_0);
+    sram_ptr += xtotal * op->y_pad_0;
+    for (uint32_t y = 0; y < op->y_size; ++y) {
+      memset(sram_ptr, 0, kElemBytes * op->x_pad_0);
+      sram_ptr += op->x_pad_0;
+      memcpy(sram_ptr, dram_ptr, kElemBytes * op->x_size);
+      sram_ptr += op->x_size;
+      BitPacker<kBits> src(sram_ptr);
+      memset(sram_ptr, 0, kElemBytes * op->x_pad_1);
+      sram_ptr += op->x_pad_1;
+      dram_ptr += kElemBytes * op->x_stride;
+    }
+    memset(sram_ptr, 0, kElemBytes * xtotal * op->y_pad_1);
+  }
+  // Execute the store instruction on this SRAM apply trucation.
+  // This relies on the elements is 32 bits
+  template<int target_bits>
+  void TruncStore(const VTAMemInsn* op, DRAM* dram) {
+    CHECK_EQ(op->x_pad_0, 0);
+    CHECK_EQ(op->x_pad_1, 0);
+    CHECK_EQ(op->y_pad_0, 0);
+    CHECK_EQ(op->y_pad_1, 0);
+    int target_width = (target_bits * kLane + 7) / 8;
+    BitPacker<kBits> src(data_ + op->sram_base);
+    BitPacker<target_bits> dst(dram->GetAddr(op->dram_base * target_width));
+    for (uint32_t y = 0; y < op->y_size; ++y) {
+      for (uint32_t x = 0; x < op->x_size; ++x) {
+        uint32_t sram_base = y * op->x_size + x;
+        uint32_t dram_base = y * op->x_stride + x;
+        for (int i = 0; i < kLane; ++i) {
+          dst.SetSigned(dram_base * kLane + i,
+                        src.GetSigned(sram_base * kLane +i));
+        }
+      }
+    }
+  }
+
+ private:
+  /*! \brief internal data content */
+  DType* data_;
+};
+
+
+/*!
+ * \brief Memory information of special memory region.
+ *  Use MemoryInfo as its container type
+ */
+class Profiler {
+ public:
+  /*! \brief The memory load statistics */
+  uint64_t inp_load_nbytes{0};
+  /*! \brief The memory load statistics */
+  uint64_t wgt_load_nbytes{0};
+  /*! \brief The ACC memory load statistics */
+  uint64_t acc_load_nbytes{0};
+  /*! \brief The ACC memory load statistics */
+  uint64_t uop_load_nbytes{0};
+  /*! \brief The ACC memory load statistics */
+  uint64_t out_store_nbytes{0};
+  /*! \brief instr counter for gemm */
+  uint64_t gemm_counter{0};
+  /*! \brief instr counter for ALU ops */
+  uint64_t alu_counter{0};
+  /*! \brief clear the profiler */
+  void Clear() {
+    inp_load_nbytes = 0;
+    wgt_load_nbytes = 0;
+    acc_load_nbytes = 0;
+    uop_load_nbytes = 0;
+    out_store_nbytes = 0;
+    gemm_counter = 0;
+    alu_counter = 0;
+  }
+
+  std::string AsJSON() {
+    std::ostringstream os;
+    os << "{\n"
+       << " \"inp_load_nbytes\":" << inp_load_nbytes << ",\n"
+       << " \"wgt_load_nbytes\":" << wgt_load_nbytes << ",\n"
+       << " \"acc_load_nbytes\":" << acc_load_nbytes << ",\n"
+       << " \"uop_load_nbytes\":" << uop_load_nbytes << ",\n"
+       << " \"out_store_nbytes\":" << out_store_nbytes << ",\n"
+       << " \"gemm_counter\":" << gemm_counter << ",\n"
+       << " \"alu_counter\":" << alu_counter << "\n"
+       <<"}\n";
+    return os.str();
+  }
+
+  static Profiler* ThreadLocal() {
+    static thread_local Profiler inst;
+    return &inst;
+  }
+};
+
+
+// Simulate device
+// TODO(tqchen,thierry): queue based event driven simulation.
+class Device {
+ public:
+  Device() {
+    prof_ = Profiler::ThreadLocal();
+    dram_ = DRAM::Global();
+  }
+
+  int Run(vta_phy_addr_t insn_phy_addr,
+          uint32_t insn_count,
+          uint32_t wait_cycles) {
+    VTAGenericInsn* insn = static_cast<VTAGenericInsn*>(
+        dram_->GetAddr(insn_phy_addr));
+    finish_counter_ = 0;
+    for (uint32_t i = 0; i < insn_count; ++i) {
+      this->Run(insn + i);
+    }
+    return 0;
+  }
+
+ private:
+  void Run(const VTAGenericInsn* insn) {
+    const VTAMemInsn* mem = reinterpret_cast<const VTAMemInsn*>(insn);
+    const VTAGemInsn* gem = reinterpret_cast<const VTAGemInsn*>(insn);
+    const VTAAluInsn* alu = reinterpret_cast<const VTAAluInsn*>(insn);
+    switch (mem->opcode) {
+      case VTA_OPCODE_LOAD: RunLoad(mem); break;
+      case VTA_OPCODE_STORE: RunStore(mem); break;
+      case VTA_OPCODE_GEMM: RunGEMM(gem); break;
+      case VTA_OPCODE_ALU: RunALU(alu); break;
+      case VTA_OPCODE_FINISH: ++finish_counter_; break;
+      default: {
+        LOG(FATAL) << "Unknown op_code" << mem->opcode;
+      }
+    }
+  }
+
+  void RunLoad(const VTAMemInsn* op) {
+    if (op->x_size == 0) return;
+    if (op->memory_type == VTA_MEM_ID_INP) {
+      inp_.Load(op, dram_, &(prof_->inp_load_nbytes));
+    } else if (op->memory_type == VTA_MEM_ID_WGT) {
+      wgt_.Load(op, dram_, &(prof_->wgt_load_nbytes));
+    } else if (op->memory_type == VTA_MEM_ID_ACC) {
+      acc_.Load(op, dram_, &(prof_->acc_load_nbytes));
+    } else if (op->memory_type == VTA_MEM_ID_UOP) {
+      uop_.Load(op, dram_, &(prof_->uop_load_nbytes));
+    } else {
+      LOG(FATAL) << "Unknown memory_type=" << op->memory_type;
+    }
+  }
+
+  void RunStore(const VTAMemInsn* op) {
+    if (op->memory_type == VTA_MEM_ID_ACC ||
+        op->memory_type == VTA_MEM_ID_UOP) {
+      prof_->out_store_nbytes += (
+          op->x_size * op->y_size * VTA_BATCH * VTA_BLOCK_OUT * VTA_OUT_WIDTH / 8);
+      acc_.TruncStore<VTA_OUT_WIDTH>(op, dram_);
+    } else {
+      LOG(FATAL) << "Store do not support memory_type="
+                 << op->memory_type;
+    }
+  }
+
+  void RunGEMM(const VTAGemInsn* op) {
+    if (!op->reset_reg) {
+      prof_->gemm_counter += op->iter_out * op->iter_in;
+      for (uint32_t y = 0; y < op->iter_out; ++y) {
+        for (uint32_t x = 0; x < op->iter_in; ++x) {
+          for (uint32_t uindex = op->uop_bgn; uindex < op->uop_end; ++uindex) {
+            VTAUop* uop_ptr = static_cast<VTAUop*>(uop_.BeginPtr(uindex));
+            // Read in memory indices
+            uint32_t acc_idx = uop_ptr->dst_idx;
+            uint32_t inp_idx = uop_ptr->src_idx;
+            uint32_t wgt_idx = uop_ptr->wgt_idx;
+            acc_idx += y * op->dst_factor_out + x * op->dst_factor_in;
+            inp_idx += y * op->src_factor_out + x * op->src_factor_in;
+            wgt_idx += y * op->wgt_factor_out + x * op->wgt_factor_in;
+            BitPacker<VTA_ACC_WIDTH> acc(acc_.BeginPtr(acc_idx));
+            BitPacker<VTA_INP_WIDTH> inp(inp_.BeginPtr(inp_idx));
+            BitPacker<VTA_WGT_WIDTH> wgt(wgt_.BeginPtr(wgt_idx));
+            // gemm loop
+            for (uint32_t i = 0; i < VTA_BATCH; ++i) {
+              for (uint32_t j = 0; j < VTA_BLOCK_OUT; ++j) {
+                uint32_t acc_offset = i * VTA_BLOCK_OUT + j;
+                int32_t sum = acc.GetSigned(acc_offset);
+                for (uint32_t k = 0; k < VTA_BLOCK_IN; ++k) {
+                  sum +=
+                      inp.GetSigned(i * VTA_BLOCK_IN + k) *
+                      wgt.GetSigned(j * VTA_BLOCK_IN + k);
+                }
+                acc.SetSigned(acc_offset, sum);
+              }
+            }
+          }
+        }
+      }
+    } else {
+      // reset
+      for (uint32_t y = 0; y < op->iter_out; ++y) {
+        for (uint32_t x = 0; x < op->iter_in; ++x) {
+          for (uint32_t uindex = op->uop_bgn; uindex < op->uop_end; ++uindex) {
+            VTAUop* uop_ptr = static_cast<VTAUop*>(uop_.BeginPtr(uindex));
+            uint32_t acc_idx = uop_ptr->dst_idx;
+            acc_idx += y * op->dst_factor_out + x * op->dst_factor_in;
+            BitPacker<VTA_ACC_WIDTH> acc(acc_.BeginPtr(acc_idx));
+            for (uint32_t i = 0; i < VTA_BATCH * VTA_BLOCK_OUT; ++i) {
+              acc.SetSigned(i, 0);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  void RunALU(const VTAAluInsn* op) {
+    prof_->alu_counter += op->iter_out * op->iter_in;
+    if (op->use_imm) {
+      RunALU_<true>(op);
+    } else {
+      RunALU_<false>(op);
+    }
+  }
+
+  template<bool use_imm>
+  void RunALU_(const VTAAluInsn* op) {
+    switch (op->alu_opcode) {
+      case VTA_ALU_OPCODE_ADD: {
+        return RunALULoop<use_imm>(op, [](int32_t x, int32_t y) {
+            return x + y;
+          });
+      }
+      case VTA_ALU_OPCODE_MAX: {
+        return RunALULoop<use_imm>(op, [](int32_t x, int32_t y) {
+            return std::max(x, y);
+          });
+      }
+      case VTA_ALU_OPCODE_MIN: {
+        return RunALULoop<use_imm>(op, [](int32_t x, int32_t y) {
+            return std::min(x, y);
+          });
+      }
+      case VTA_ALU_OPCODE_SHR: {
+        return RunALULoop<use_imm>(op, [](int32_t x, int32_t y) {
+            if (y >= 0) {
+              return x >> y;
+            } else {
+              return x << (-y);
+            }
+          });
+      }
+      default: {
+        LOG(FATAL) << "Unknown ALU code " << op->alu_opcode;
+      }
+    }
+  }
+
+  template<bool use_imm, typename F>
+  void RunALULoop(const VTAAluInsn* op, F func) {
+    for (int y = 0; y < op->iter_out; ++y) {
+      for (int x = 0; x < op->iter_in; ++x) {
+        for (int k = op->uop_bgn; k < op->uop_end; ++k) {
+          // Read micro op
+          VTAUop* uop_ptr = static_cast<VTAUop*>(uop_.BeginPtr(k));
+          uint32_t dst_index = uop_ptr->dst_idx;
+          uint32_t src_index = uop_ptr->src_idx;
+          dst_index += y * op->dst_factor_out + x * op->dst_factor_in;
+          src_index += y * op->src_factor_out + x * op->src_factor_in;
+          BitPacker<VTA_ACC_WIDTH> dst(acc_.BeginPtr(dst_index));
+          BitPacker<VTA_ACC_WIDTH> src(acc_.BeginPtr(src_index));
+          for (int k = 0; k < VTA_BLOCK_OUT; ++k) {
+            if (use_imm) {
+              dst.SetSigned(k, func(dst.GetSigned(k), op->imm));
+            } else {
+              dst.SetSigned(k, func(dst.GetSigned(k), src.GetSigned(k)));
+            }
+          }
+        }
+      }
+    }
+  }
+  // the finish counter
+  int finish_counter_{0};
+  // Prof_
+  Profiler* prof_;
+  // The DRAM interface
+  DRAM* dram_;
+  // The SRAM
+  SRAM<VTA_INP_WIDTH, VTA_BATCH * VTA_BLOCK_IN, VTA_INP_BUFF_DEPTH> inp_;
+  SRAM<VTA_WGT_WIDTH, VTA_BLOCK_IN * VTA_BLOCK_OUT, VTA_WGT_BUFF_DEPTH> wgt_;
+  SRAM<VTA_ACC_WIDTH, VTA_BATCH * VTA_BLOCK_OUT, VTA_ACC_BUFF_DEPTH> acc_;
+  SRAM<VTA_UOP_WIDTH, 1, VTA_UOP_BUFF_DEPTH> uop_;
+};
+
+using tvm::runtime::TVMRetValue;
+using tvm::runtime::TVMArgs;
+
+TVM_REGISTER_GLOBAL("vta.simulator.profiler_clear")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    Profiler::ThreadLocal()->Clear();
+  });
+TVM_REGISTER_GLOBAL("vta.simulator.profiler_status")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    *rv = Profiler::ThreadLocal()->AsJSON();
+  });
+}  // namespace sim
+}  // namespace vta
+
+void* VTAMemAlloc(size_t size, int cached) {
+  return vta::sim::DRAM::Global()->Alloc(size);
+}
+
+void VTAMemFree(void* buf) {
+  vta::sim::DRAM::Global()->Free(buf);
+}
+
+vta_phy_addr_t VTAMemGetPhyAddr(void* buf) {
+  return vta::sim::DRAM::Global()->GetPhyAddr(buf);
+}
+
+void VTAFlushCache(vta_phy_addr_t buf, int size) {
+}
+
+void VTAInvalidateCache(vta_phy_addr_t buf, int size) {
+}
+
+VTADeviceHandle VTADeviceAlloc() {
+  return new vta::sim::Device();
+}
+
+void VTADeviceFree(VTADeviceHandle handle) {
+  delete static_cast<vta::sim::Device*>(handle);
+}
+
+int VTADeviceRun(VTADeviceHandle handle,
+                 vta_phy_addr_t insn_phy_addr,
+                 uint32_t insn_count,
+                 uint32_t wait_cycles) {
+  return static_cast<vta::sim::Device*>(handle)->Run(
+      insn_phy_addr, insn_count, wait_cycles);
+}
+
+void VTAProgram(const char* bitstream) {
+}
diff --git a/vta/src/tvm/vta_device_api.cc b/vta/src/tvm/vta_device_api.cc
index 450b23b05fee..e4671d8a0207 100644
--- a/vta/src/tvm/vta_device_api.cc
+++ b/vta/src/tvm/vta_device_api.cc
@@ -67,9 +67,6 @@ class VTADeviceAPI final : public DeviceAPI {
         std::make_shared<VTADeviceAPI>();
     return inst;
   }
-
- private:
-  void* runtime_dll_{nullptr};
 };
 
 struct VTAWorkspacePool : public WorkspacePool {
diff --git a/vta/tests/python/pynq/test_vta_insn.py b/vta/tests/python/pynq/test_vta_insn.py
deleted file mode 100644
index 14baede4e44a..000000000000
--- a/vta/tests/python/pynq/test_vta_insn.py
+++ /dev/null
@@ -1,504 +0,0 @@
-"""Unit test VTA's instructions """
-import tvm
-import vta
-import mxnet as mx
-import numpy as np
-import topi
-from tvm.contrib import rpc, util
-
-host = "pynq"
-port = 9091
-target = "llvm -target=armv7-none-linux-gnueabihf"
-do_verify = True
-print_ir = False
-
-def test_save_load_out():
-    env = vta.get_env()
-    """Test save/store output command"""
-    n = 4
-    x = tvm.placeholder(
-        (n, n, env.BATCH, env.BLOCK_OUT),
-        name="x",
-        dtype=env.acc_dtype)
-    x_buf = tvm.compute(
-        (n, n, env.BATCH, env.BLOCK_OUT),
-        lambda *i: x(*i),
-        "x_buf")
-    # insert no-op that won't be optimized away
-    y_buf = tvm.compute(
-        (n, n, env.BATCH, env.BLOCK_OUT),
-        lambda *i: x_buf(*i)>>0,
-        "y_buf")
-    y = tvm.compute(
-        (n, n, env.BATCH, env.BLOCK_OUT),
-        lambda *i: y_buf(*i).astype(env.inp_dtype),
-        "y")
-    # schedule
-    s = tvm.create_schedule(y.op)
-    s[x_buf].set_scope(env.acc_scope)
-    s[x_buf].pragma(x_buf.op.axis[0], env.dma_copy)
-    s[y_buf].set_scope(env.acc_scope)
-    s[y_buf].pragma(y_buf.op.axis[0], env.alu)
-    s[y].pragma(y.op.axis[0], env.dma_copy)
-
-    def verify():
-        # build
-        with vta.build_config(env.DEBUG_DUMP_INSN):
-            m = vta.build(s, [x, y], "ext_dev", target)
-        temp = util.tempdir()
-        remote = rpc.connect(host, port)
-        m.save(temp.relpath("load_act.o"))
-        remote.upload(temp.relpath("load_act.o"))
-        f = remote.load_module("load_act.o")
-        # verify
-        ctx = remote.ext_dev(0)
-        x_np = np.random.randint(
-            1, 10, size=(n, n, env.BATCH, env.BLOCK_OUT)).astype(x.dtype)
-        y_np = x_np.astype(y.dtype)
-        x_nd = tvm.nd.array(x_np, ctx)
-        y_nd = tvm.nd.empty(y_np.shape, ctx=ctx, dtype=y_np.dtype)
-        f(x_nd, y_nd)
-        np.testing.assert_equal(y_np, y_nd.asnumpy())
-        print("\tFinished verification...")
-    if do_verify:
-        verify()
-
-
-def test_padded_load():
-    """Test padded load."""
-    env = vta.get_env()
-    # declare
-    n = 21
-    m = 20
-    pad_before = [0, 1, 0, 0]
-    pad_after = [1, 3, 0, 0]
-    x = tvm.placeholder(
-        (n, m, env.BATCH, env.BLOCK_OUT),
-        name="x",
-        dtype=env.acc_dtype)
-    x_buf = topi.nn.pad(x, pad_before, pad_after, name="y")
-    # insert no-op that won't be optimized away
-    y_buf = tvm.compute((n + pad_before[0] + pad_after[0],
-                         m + pad_before[1] + pad_after[1],
-                         env.BATCH,
-                         env.BLOCK_OUT), lambda *i: x_buf(*i)>>0, "y_buf")
-    y = tvm.compute((n + pad_before[0] + pad_after[0],
-                     m + pad_before[1] + pad_after[1],
-                     env.BATCH,
-                     env.BLOCK_OUT), lambda *i: y_buf(*i).astype(env.inp_dtype), "y")
-    # schedule
-    s = tvm.create_schedule(y.op)
-    s[x_buf].set_scope(env.acc_scope)
-    s[x_buf].pragma(x_buf.op.axis[0], env.dma_copy)
-    s[y_buf].set_scope(env.acc_scope)
-    s[y_buf].pragma(y_buf.op.axis[0], env.alu)
-    s[y].pragma(y.op.axis[0], env.dma_copy)
-
-    def verify():
-        # build
-        with vta.build_config(env.DEBUG_DUMP_INSN):
-            mod = vta.build(s, [x, y], "ext_dev", target)
-        temp = util.tempdir()
-        remote = rpc.connect(host, port)
-        mod.save(temp.relpath("padded_load.o"))
-        remote.upload(temp.relpath("padded_load.o"))
-        f = remote.load_module("padded_load.o")
-        # verify
-        ctx = remote.ext_dev(0)
-        x_np = np.random.randint(1, 2, size=(
-            n, m, env.BATCH, env.BLOCK_OUT)).astype(x.dtype)
-        y_np = np.zeros((n + pad_before[0] + pad_after[0],
-                         m + pad_before[1] + pad_after[1],
-                         env.BATCH,
-                         env.BLOCK_OUT)).astype(y.dtype)
-        y_np[pad_before[0]:pad_before[0] + n,
-             pad_before[1]:pad_before[1] + m,
-             :] = x_np
-        x_nd = tvm.nd.array(x_np, ctx)
-        y_nd = tvm.nd.empty(y_np.shape, ctx=ctx, dtype=y_np.dtype)
-        f(x_nd, y_nd)
-        np.testing.assert_equal(y_np, y_nd.asnumpy())
-        print("\tFinished verification...")
-
-    if print_ir:
-        print(vta.lower(s, [y, x], simple_mode=True))
-
-
-def test_gemm():
-    """Test GEMM."""
-    env = vta.get_env()
-    # declare
-    o = 4
-    n = 4
-    m = 4
-    x = tvm.placeholder((o, n, env.BATCH, env.BLOCK_IN), name="x", dtype=env.inp_dtype)
-    w = tvm.placeholder((m, n, env.BLOCK_OUT, env.BLOCK_IN), name="w", dtype=env.wgt_dtype)
-    x_buf = tvm.compute((o, n, env.BATCH, env.BLOCK_IN), lambda *i: x(*i), "x_buf")
-    w_buf = tvm.compute((m, n, env.BLOCK_OUT, env.BLOCK_IN), lambda *i: w(*i), "w_buf")
-    ko = tvm.reduce_axis((0, n), name="ko")
-    ki = tvm.reduce_axis((0, env.BLOCK_IN), name="ki")
-    y_gem = tvm.compute(
-        (o, m, env.BATCH, env.BLOCK_OUT),
-        lambda bo, co, bi, ci:
-            tvm.sum(x_buf[bo, ko, bi, ki].astype(env.acc_dtype) *
-                    w_buf[co, ko, ci, ki].astype(env.acc_dtype),
-                    axis=[ko, ki]),
-        name="y_gem")
-    y_shf = tvm.compute(
-        (o, m, env.BATCH, env.BLOCK_OUT),
-        lambda *i: y_gem(*i)>>8,
-        name="y_shf")
-    y_max = tvm.compute(
-        (o, m, env.BATCH, env.BLOCK_OUT),
-        lambda *i: tvm.max(y_shf(*i), 0),
-        "y_max") #relu
-    y_min = tvm.compute(
-        (o, m, env.BATCH, env.BLOCK_OUT),
-        lambda *i: tvm.min(y_max(*i), (1<<(env.INP_WIDTH-1))-1),
-        "y_min") #relu
-    y = tvm.compute(
-        (o, m, env.BATCH, env.BLOCK_OUT),
-        lambda *i: y_min(*i).astype(env.inp_dtype),
-        name="y")
-
-    def verify(s):
-        mod = vta.build(s, [x, w, y], "ext_dev", target)
-        temp = util.tempdir()
-        remote = rpc.connect(host, port)
-        mod.save(temp.relpath("gemm.o"))
-        remote.upload(temp.relpath("gemm.o"))
-        f = remote.load_module("gemm.o")
-        # verify
-        ctx = remote.ext_dev(0)
-        x_np = np.random.randint(
-            -128, 128, size=(o, n, env.BATCH, env.BLOCK_IN)).astype(x.dtype)
-        w_np = np.random.randint(
-            -128, 128, size=(m, n, env.BLOCK_OUT, env.BLOCK_IN)).astype(w.dtype)
-        y_np = np.zeros((o, m, env.BATCH, env.BLOCK_OUT)).astype(y.dtype)
-        x_nd = tvm.nd.array(x_np, ctx)
-        w_nd = tvm.nd.array(w_np, ctx)
-        y_nd = tvm.nd.array(y_np, ctx)
-        y_np = y_np.astype(env.acc_dtype)
-        for b in range(o):
-            for i in range(m):
-                for j in range(n):
-                    y_np[b,i,:] += np.dot(x_np[b,j,:].astype(env.acc_dtype),
-                                          w_np[i,j].T.astype(env.acc_dtype))
-        y_np = np.right_shift(y_np, 8)
-        y_np = np.clip(y_np, 0, (1<<(env.INP_WIDTH-1))-1).astype(y.dtype)
-        f(x_nd, w_nd, y_nd)
-        np.testing.assert_equal(y_np, y_nd.asnumpy())
-        print("\tFinished verification...")
-
-    def test_schedule1():
-        # default schedule with no smt
-        s = tvm.create_schedule(y.op)
-        # set the scope of the SRAM buffers
-        s[x_buf].set_scope(env.SCOPE_INP)
-        s[w_buf].set_scope(env.SCOPE_WGT)
-        s[y_gem].set_scope(env.acc_scope)
-        s[y_shf].set_scope(env.acc_scope)
-        s[y_max].set_scope(env.acc_scope)
-        s[y_min].set_scope(env.acc_scope)
-        # set pragmas for DMA transfer and ALU ops
-        s[x_buf].pragma(s[x_buf].op.axis[0], env.dma_copy)
-        s[w_buf].pragma(s[w_buf].op.axis[0], env.dma_copy)
-        s[y_shf].pragma(s[y_shf].op.axis[0], env.alu)
-        s[y_max].pragma(s[y_max].op.axis[0], env.alu)
-        s[y_min].pragma(s[y_min].op.axis[0], env.alu)
-        s[y].pragma(s[y].op.axis[0], env.dma_copy)
-        # tensorization
-        s[y_gem].reorder(
-            ko,
-            s[y_gem].op.axis[0],
-            s[y_gem].op.axis[1],
-            s[y_gem].op.axis[2],
-            s[y_gem].op.axis[3],
-            ki)
-        s[y_gem].tensorize(s[y_gem].op.axis[2], env.GEMM)
-        if print_ir:
-            print(vta.lower(s, [x, w, y], simple_mode=True))
-        if do_verify:
-            with vta.build_config(env.DEBUG_DUMP_INSN):
-                verify(s)
-
-    def test_smt():
-        # test smt schedule
-        s = tvm.create_schedule(y.op)
-        s[x_buf].set_scope(env.SCOPE_INP)
-        s[w_buf].set_scope(env.SCOPE_WGT)
-        s[y_gem].set_scope(env.acc_scope)
-        s[y_shf].set_scope(env.acc_scope)
-        s[y_max].set_scope(env.acc_scope)
-        s[y_min].set_scope(env.acc_scope)
-        abo, aco, abi, aci = s[y].op.axis
-        abo1, abo2 = s[y].split(abo, nparts=2)
-        s[y].bind(abo1, tvm.thread_axis("cthread"))
-        s[y_gem].compute_at(s[y], abo1)
-        s[y_shf].compute_at(s[y], abo1)
-        s[y_max].compute_at(s[y], abo1)
-        s[y_min].compute_at(s[y], abo1)
-        s[y_gem].reorder(
-            ko,
-            s[y_gem].op.axis[0],
-            s[y_gem].op.axis[1],
-            s[y_gem].op.axis[2],
-            s[y_gem].op.axis[3],
-            ki)
-        s[y_gem].tensorize(s[y_gem].op.axis[2], env.GEMM)
-        s[y_shf].pragma(s[y_shf].op.axis[0], env.alu)
-        s[y_max].pragma(s[y_max].op.axis[0], env.alu)
-        s[y_min].pragma(s[y_min].op.axis[0], env.alu)
-        s[x_buf].compute_at(s[y_gem], ko)
-        s[x_buf].pragma(s[x_buf].op.axis[0], env.dma_copy)
-        s[w_buf].compute_at(s[y_gem], ko)
-        s[w_buf].pragma(s[w_buf].op.axis[0], env.dma_copy)
-        s[y].pragma(abo2, env.dma_copy)
-        if print_ir:
-            print(vta.lower(s, [x, y, w], simple_mode=True))
-        if do_verify:
-            with vta.build_config(env.DEBUG_DUMP_INSN):
-                verify(s)
-
-    test_schedule1()
-    test_smt()
-
-def test_alu(tvm_op, np_op=None, use_imm=False):
-    """Test ALU"""
-    env = vta.get_env()
-    m = 8
-    n = 8
-    imm = np.random.randint(1,5)
-    # compute
-    a = tvm.placeholder(
-        (m, n, env.BATCH, env.BLOCK_OUT),
-        name="a",
-        dtype=env.acc_dtype)
-    a_buf = tvm.compute(
-        (m, n, env.BATCH, env.BLOCK_OUT),
-        lambda *i: a(*i),
-        "a_buf") #DRAM->SRAM
-    if use_imm:
-        res_buf = tvm.compute(
-            (m, n, env.BATCH, env.BLOCK_OUT),
-            lambda *i: tvm_op(a_buf(*i), imm),
-            "res_buf") #compute
-    else:
-        b = tvm.placeholder(
-            (m, n, env.BATCH, env.BLOCK_OUT),
-            name="b",
-            dtype=env.acc_dtype)
-        b_buf = tvm.compute(
-            (m, n, env.BATCH, env.BLOCK_OUT),
-            lambda *i: b(*i),
-            "b_buf") #DRAM->SRAM
-        res_buf = tvm.compute(
-            (m, n, env.BATCH, env.BLOCK_OUT),
-            lambda *i: tvm_op(a_buf(*i), b_buf(*i)),
-            "res_buf") #compute
-    res = tvm.compute(
-        (m, n, env.BATCH, env.BLOCK_OUT),
-        lambda *i: res_buf(*i).astype(env.inp_dtype),
-        "res") #SRAM->DRAM
-    # schedule
-    s = tvm.create_schedule(res.op)
-    s[a_buf].set_scope(env.acc_scope) # SRAM
-    s[a_buf].pragma(a_buf.op.axis[0], env.dma_copy) # DRAM->SRAM
-    s[res_buf].set_scope(env.acc_scope) # SRAM
-    s[res_buf].pragma(res_buf.op.axis[0], env.alu) # compute
-    s[res].pragma(res.op.axis[0], env.dma_copy) # SRAM->DRAM
-    if use_imm:
-        if print_ir:
-            print(vta.lower(s, [a, res], simple_mode=True))
-    else:
-        s[b_buf].set_scope(env.acc_scope) # SRAM
-        s[b_buf].pragma(b_buf.op.axis[0], env.dma_copy) # DRAM->SRAM
-        if print_ir:
-            print(vta.lower(s, [a, b, res], simple_mode=True))
-
-    def verify():
-        # build
-        with vta.build_config():
-            if use_imm:
-                mod = vta.build(s, [a, res], "ext_dev", target)
-            else:
-                mod = vta.build(s, [a, b, res], "ext_dev", target)
-        temp = util.tempdir()
-        remote = rpc.connect(host, port)
-        mod.save(temp.relpath("load_act.o"))
-        remote.upload(temp.relpath("load_act.o"))
-        f = remote.load_module("load_act.o")
-        # verify
-        ctx = remote.ext_dev(0)
-        a_np = np.random.randint(
-            -16, 16, size=(m, n, env.BATCH, env.BLOCK_OUT)).astype(a.dtype)
-        if use_imm:
-            res_np = np_op(a_np, imm) if np_op else tvm_op(a_np, imm)
-        else:
-            b_np = np.random.randint(
-                -16, 16, size=(m, n, env.BATCH, env.BLOCK_OUT)).astype(b.dtype)
-            res_np = np_op(a_np, b_np) if np_op else tvm_op(a_np, b_np)
-        res_np = res_np.astype(res.dtype)
-        a_nd = tvm.nd.array(a_np, ctx)
-        res_nd = tvm.nd.array(
-            np.zeros((m, n, env.BATCH, env.BLOCK_OUT)).astype(res.dtype), ctx)
-        if use_imm:
-            f(a_nd, res_nd)
-        else:
-            b_nd = tvm.nd.array(b_np, ctx)
-            f(a_nd, b_nd, res_nd)
-        np.testing.assert_equal(res_np, res_nd.asnumpy())
-        print("\tFinished verification...")
-
-    if do_verify:
-        verify()
-
-def test_relu():
-    """Test RELU on ALU"""
-    env = vta.get_env()
-    m = 8
-    n = 8
-    # compute
-    a = tvm.placeholder(
-        (m, n, env.BATCH, env.BLOCK_OUT),
-        name="a",
-        dtype=env.acc_dtype)
-    a_buf = tvm.compute(
-        (m, n, env.BATCH, env.BLOCK_OUT),
-        lambda *i: a(*i),
-        "a_buf") # DRAM->SRAM
-    max_buf = tvm.compute(
-        (m, n, env.BATCH, env.BLOCK_OUT),
-        lambda *i: tvm.max(a_buf(*i), 0),
-        "res_buf") # relu
-    min_buf = tvm.compute(
-        (m, n, env.BATCH, env.BLOCK_OUT),
-        lambda *i: tvm.min(max_buf(*i), (1<<(env.INP_WIDTH-1))-1),
-        "max_buf") # relu
-    res = tvm.compute(
-        (m, n, env.BATCH, env.BLOCK_OUT),
-        lambda *i: min_buf(*i).astype(env.inp_dtype),
-        "min_buf") # SRAM->DRAM
-    # schedule
-    s = tvm.create_schedule(res.op)
-    s[a_buf].set_scope(env.acc_scope) # SRAM
-    s[a_buf].pragma(a_buf.op.axis[0], env.dma_copy) # DRAM->SRAM
-    s[max_buf].set_scope(env.acc_scope) # SRAM
-    s[min_buf].set_scope(env.acc_scope) # SRAM
-    s[max_buf].pragma(max_buf.op.axis[0], env.alu) # compute
-    s[min_buf].pragma(min_buf.op.axis[0], env.alu) # compute
-    s[res].pragma(res.op.axis[0], env.dma_copy) # SRAM->DRAM
-    if print_ir:
-        print(vta.lower(s, [a, res], simple_mode=True))
-
-    def verify():
-        # build
-        with vta.build_config(env.DEBUG_DUMP_INSN):
-            mod = vta.build(s, [a, res], "ext_dev", target)
-        temp = util.tempdir()
-        remote = rpc.connect(host, port)
-        mod.save(temp.relpath("load_act.o"))
-        remote.upload(temp.relpath("load_act.o"))
-        f = remote.load_module("load_act.o")
-        # verify
-        ctx = remote.ext_dev(0)
-        a_np = np.random.randint(
-            -256, 256, size=(m, n, env.BATCH, env.BLOCK_OUT)).astype(a.dtype)
-        res_np = np.clip(a_np, 0, (1<<(env.INP_WIDTH-1))-1).astype(res.dtype)
-        a_nd = tvm.nd.array(a_np, ctx)
-        res_nd = tvm.nd.array(
-            np.zeros((m, n, env.BATCH, env.BLOCK_OUT)).astype(res.dtype), ctx)
-        f(a_nd, res_nd)
-        np.testing.assert_equal(res_np, res_nd.asnumpy())
-        print("\tFinished verification...")
-
-    if do_verify:
-        verify()
-
-def test_shift_and_scale():
-    """Test shift and scale on ALU"""
-    env = vta.get_env()
-    m = 8
-    n = 8
-    imm_shift = np.random.randint(-10,10)
-    imm_scale = np.random.randint(1,5)
-    # compute
-    a = tvm.placeholder(
-        (m, n, env.BATCH, env.BLOCK_OUT),
-        name="a", dtype=env.acc_dtype)
-    a_buf = tvm.compute(
-        (m, n, env.BATCH, env.BLOCK_OUT),
-        lambda *i: a(*i),
-        "a_buf") # DRAM->SRAM
-    res_shift = tvm.compute(
-        (m, n, env.BATCH, env.BLOCK_OUT),
-        lambda *i: a_buf(*i)+imm_shift,
-        "res_shift") # compute
-    res_scale = tvm.compute(
-        (m, n, env.BATCH, env.BLOCK_OUT),
-        lambda *i: res_shift(*i)>>imm_scale,
-        "res_scale") # compute
-    res = tvm.compute(
-        (m, n, env.BATCH, env.BLOCK_OUT),
-        lambda *i: res_scale(*i).astype(env.inp_dtype),
-        "res") # SRAM->DRAM
-    # schedule
-    s = tvm.create_schedule(res.op)
-    s[a_buf].set_scope(env.acc_scope) # SRAM
-    s[res_shift].set_scope(env.acc_scope) # SRAM
-    s[res_scale].set_scope(env.acc_scope) # SRAM
-    s[a_buf].pragma(a_buf.op.axis[0], env.dma_copy) # DRAM->SRAM
-    s[res_shift].pragma(res_shift.op.axis[0], env.alu) # compute
-    s[res_scale].pragma(res_scale.op.axis[0], env.alu) # compute
-    s[res].pragma(res.op.axis[0], env.dma_copy) # SRAM->DRAM
-
-    if print_ir:
-        print(vta.lower(s, [a, res], simple_mode=True))
-
-    def verify():
-        # build
-        mod = vta.build(s, [a, res], "ext_dev", target)
-        temp = util.tempdir()
-        remote = rpc.connect(host, port)
-        mod.save(temp.relpath("load_act.o"))
-        remote.upload(temp.relpath("load_act.o"))
-        f = remote.load_module("load_act.o")
-        # verify
-        ctx = remote.ext_dev(0)
-        a_np = np.random.randint(
-            -10, 10, size=(m, n, env.BATCH, env.BLOCK_OUT)).astype(a.dtype)
-        res_np = np.right_shift((a_np + imm_shift), imm_scale)
-        res_np = res_np.astype(res.dtype)
-        a_nd = tvm.nd.array(a_np, ctx)
-        res_nd = tvm.nd.array(
-            np.zeros((m, n, env.BATCH, env.BLOCK_OUT)).astype(res.dtype), ctx)
-        f(a_nd, res_nd)
-        np.testing.assert_equal(res_np, res_nd.asnumpy())
-        print("\tFinished verification...")
-
-    if do_verify:
-        verify()
-
-if __name__ == "__main__":
-    print("Load/store test")
-    test_save_load_out()
-    print("Padded load test")
-    test_padded_load()
-    # print("GEMM test")
-    # test_gemm()
-    print("Max immediate")
-    test_alu(tvm.max, np.maximum, use_imm=True)
-    print("Max")
-    test_alu(tvm.max, np.maximum)
-    print("Add immediate")
-    test_alu(lambda x, y: x + y, use_imm=True)
-    print("Add")
-    test_alu(lambda x, y: x + y)
-    print("Shift right immediate")
-    test_alu(lambda x, y: x >> y, np.right_shift, use_imm=True)
-    print("Shift left immediate")
-    test_alu(lambda x, y: x << y, np.left_shift, use_imm=True)
-    print("Relu")
-    test_relu()
-    # print("Shift and scale")
-    # test_shift_and_scale()
diff --git a/vta/tests/python/unittest/test_vta_insn.py b/vta/tests/python/unittest/test_vta_insn.py
new file mode 100644
index 000000000000..339d8d31e238
--- /dev/null
+++ b/vta/tests/python/unittest/test_vta_insn.py
@@ -0,0 +1,482 @@
+"""Unit test VTA's instructions """
+import tvm
+import numpy as np
+import topi
+from tvm.contrib import rpc, util
+
+import vta
+import vta.testing
+from vta.testing import simulator
+
+
+def test_save_load_out():
+    """Test save/store output command"""
+    def _run(env, remote):
+        n = 6
+        x = tvm.placeholder(
+            (n, n, env.BATCH, env.BLOCK_OUT),
+            name="x",
+            dtype=env.acc_dtype)
+        x_buf = tvm.compute(
+            (n, n, env.BATCH, env.BLOCK_OUT),
+            lambda *i: x(*i), "x_buf")
+        # insert no-op that won't be optimized away
+        y_buf = tvm.compute(
+            (n, n, env.BATCH, env.BLOCK_OUT),
+            lambda *i: x_buf(*i)>>0, "y_buf")
+        y = tvm.compute(
+            (n, n, env.BATCH, env.BLOCK_OUT),
+            lambda *i: y_buf(*i).astype(env.inp_dtype), "y")
+        # schedule
+        s = tvm.create_schedule(y.op)
+        s[x_buf].set_scope(env.acc_scope)
+        s[x_buf].pragma(x_buf.op.axis[0], env.dma_copy)
+        s[y_buf].set_scope(env.acc_scope)
+        s[y_buf].pragma(y_buf.op.axis[0], env.alu)
+        s[y].pragma(y.op.axis[0], env.dma_copy)
+
+        # verification
+        with vta.build_config():
+            m = vta.build(s, [x, y], "ext_dev", env.target_host)
+
+        if not remote:
+            return
+        temp = util.tempdir()
+        m.save(temp.relpath("load_act.o"))
+        remote.upload(temp.relpath("load_act.o"))
+        f = remote.load_module("load_act.o")
+        # verify
+        ctx = remote.ext_dev(0)
+        x_np = np.random.randint(
+            1, 10, size=(n, n, env.BATCH, env.BLOCK_OUT)).astype(x.dtype)
+        y_np = x_np.astype(y.dtype)
+        x_nd = tvm.nd.array(x_np, ctx)
+        y_nd = tvm.nd.empty(y_np.shape, ctx=ctx, dtype=y_np.dtype)
+        f(x_nd, y_nd)
+        np.testing.assert_equal(y_np, y_nd.asnumpy())
+
+    vta.testing.run(_run)
+
+
+def test_padded_load():
+    """Test padded load."""
+    def _run(env, remote):
+        # declare
+        n = 21
+        m = 20
+        pad_before = [0, 1, 0, 0]
+        pad_after = [1, 3, 0, 0]
+        x = tvm.placeholder(
+            (n, m, env.BATCH, env.BLOCK_OUT),
+            name="x",
+            dtype=env.acc_dtype)
+        x_buf = topi.nn.pad(x, pad_before, pad_after, name="y")
+        # insert no-op that won't be optimized away
+        y_buf = tvm.compute((n + pad_before[0] + pad_after[0],
+                             m + pad_before[1] + pad_after[1],
+                             env.BATCH,
+                             env.BLOCK_OUT), lambda *i: x_buf(*i)>>0, "y_buf")
+        y = tvm.compute((n + pad_before[0] + pad_after[0],
+                         m + pad_before[1] + pad_after[1],
+                         env.BATCH,
+                         env.BLOCK_OUT), lambda *i: y_buf(*i).astype(env.inp_dtype), "y")
+        # schedule
+        s = tvm.create_schedule(y.op)
+        s[x_buf].set_scope(env.acc_scope)
+        s[x_buf].pragma(x_buf.op.axis[0], env.dma_copy)
+        s[y_buf].set_scope(env.acc_scope)
+        s[y_buf].pragma(y_buf.op.axis[0], env.alu)
+        s[y].pragma(y.op.axis[0], env.dma_copy)
+        # build
+        with vta.build_config():
+            mod = vta.build(s, [x, y], "ext_dev", env.target_host)
+
+        if not remote:
+            return
+        temp = util.tempdir()
+        mod.save(temp.relpath("padded_load.o"))
+        remote.upload(temp.relpath("padded_load.o"))
+        f = remote.load_module("padded_load.o")
+        # verify
+        ctx = remote.ext_dev(0)
+        x_np = np.random.randint(1, 2, size=(
+            n, m, env.BATCH, env.BLOCK_OUT)).astype(x.dtype)
+        y_np = np.zeros((n + pad_before[0] + pad_after[0],
+                         m + pad_before[1] + pad_after[1],
+                         env.BATCH,
+                         env.BLOCK_OUT)).astype(y.dtype)
+        y_np[pad_before[0]:pad_before[0] + n,
+             pad_before[1]:pad_before[1] + m,
+             :] = x_np
+        x_nd = tvm.nd.array(x_np, ctx)
+        y_nd = tvm.nd.empty(y_np.shape, ctx=ctx, dtype=y_np.dtype)
+        f(x_nd, y_nd)
+        np.testing.assert_equal(y_np, y_nd.asnumpy())
+
+    vta.testing.run(_run)
+
+
+def test_gemm():
+    """Test GEMM."""
+    def _run(env, remote):
+        # declare
+        o = 4
+        n = 1
+        m = 4
+        x = tvm.placeholder((o, n, env.BATCH, env.BLOCK_IN), name="x", dtype=env.inp_dtype)
+        w = tvm.placeholder((m, n, env.BLOCK_OUT, env.BLOCK_IN), name="w", dtype=env.wgt_dtype)
+        x_buf = tvm.compute((o, n, env.BATCH, env.BLOCK_IN), lambda *i: x(*i), "x_buf")
+        w_buf = tvm.compute((m, n, env.BLOCK_OUT, env.BLOCK_IN), lambda *i: w(*i), "w_buf")
+        ko = tvm.reduce_axis((0, n), name="ko")
+        ki = tvm.reduce_axis((0, env.BLOCK_IN), name="ki")
+        y_gem = tvm.compute(
+            (o, m, env.BATCH, env.BLOCK_OUT),
+            lambda bo, co, bi, ci:
+            tvm.sum(x_buf[bo, ko, bi, ki].astype(env.acc_dtype) *
+                    w_buf[co, ko, ci, ki].astype(env.acc_dtype),
+                    axis=[ko, ki]),
+            name="y_gem")
+        y_shf = tvm.compute(
+            (o, m, env.BATCH, env.BLOCK_OUT),
+            lambda *i: y_gem(*i)>>8,
+            name="y_shf")
+        y_max = tvm.compute(
+            (o, m, env.BATCH, env.BLOCK_OUT),
+            lambda *i: tvm.max(y_shf(*i), 0),
+            "y_max") #relu
+        y_min = tvm.compute(
+            (o, m, env.BATCH, env.BLOCK_OUT),
+            lambda *i: tvm.min(y_max(*i), (1<<(env.INP_WIDTH-1))-1),
+            "y_min") #relu
+        y = tvm.compute(
+            (o, m, env.BATCH, env.BLOCK_OUT),
+            lambda *i: y_min(*i).astype(env.inp_dtype),
+            name="y")
+
+        if not remote:
+            return
+
+        def verify(s):
+            mod = vta.build(s, [x, w, y], "ext_dev", env.target_host)
+            temp = util.tempdir()
+            mod.save(temp.relpath("gemm.o"))
+            remote.upload(temp.relpath("gemm.o"))
+            f = remote.load_module("gemm.o")
+            # verify
+            ctx = remote.ext_dev(0)
+            x_np = np.random.randint(
+                -128, 128, size=(o, n, env.BATCH, env.BLOCK_IN)).astype(x.dtype)
+            w_np = np.random.randint(
+                -128, 128, size=(m, n, env.BLOCK_OUT, env.BLOCK_IN)).astype(w.dtype)
+            y_np = np.zeros((o, m, env.BATCH, env.BLOCK_OUT)).astype(y.dtype)
+            x_nd = tvm.nd.array(x_np, ctx)
+            w_nd = tvm.nd.array(w_np, ctx)
+            y_nd = tvm.nd.array(y_np, ctx)
+            y_np = y_np.astype(env.acc_dtype)
+            for b in range(o):
+                for i in range(m):
+                    for j in range(n):
+                        y_np[b,i,:] += np.dot(x_np[b,j,:].astype(env.acc_dtype),
+                                              w_np[i,j].T.astype(env.acc_dtype))
+            y_np = np.right_shift(y_np, 8)
+            y_np = np.clip(y_np, 0, (1<<(env.INP_WIDTH-1))-1).astype(y.dtype)
+
+            if env.TARGET == "sim":
+                simulator.clear_stats()
+                f(x_nd, w_nd, y_nd)
+                print(simulator.stats())
+            else:
+                f(x_nd, w_nd, y_nd)
+
+            np.testing.assert_equal(y_np, y_nd.asnumpy())
+
+        def test_schedule1():
+            # default schedule with no smt
+            s = tvm.create_schedule(y.op)
+            # set the scope of the SRAM buffers
+            s[x_buf].set_scope(env.inp_scope)
+            s[w_buf].set_scope(env.wgt_scope)
+            s[y_gem].set_scope(env.acc_scope)
+            s[y_shf].set_scope(env.acc_scope)
+            s[y_max].set_scope(env.acc_scope)
+            s[y_min].set_scope(env.acc_scope)
+            # set pragmas for DMA transfer and ALU ops
+            s[x_buf].compute_at(s[y_gem], ko)
+            s[x_buf].pragma(s[x_buf].op.axis[0], env.dma_copy)
+            s[w_buf].compute_at(s[y_gem], ko)
+            s[w_buf].pragma(s[w_buf].op.axis[0], env.dma_copy)
+            s[y_shf].pragma(s[y_shf].op.axis[0], env.alu)
+            s[y_max].pragma(s[y_max].op.axis[0], env.alu)
+            s[y_min].pragma(s[y_min].op.axis[0], env.alu)
+            s[y].pragma(s[y].op.axis[0], env.dma_copy)
+            # tensorization
+            s[y_gem].reorder(
+                ko,
+                s[y_gem].op.axis[0],
+                s[y_gem].op.axis[1],
+                s[y_gem].op.axis[2],
+                s[y_gem].op.axis[3],
+                ki)
+            s[y_gem].tensorize(s[y_gem].op.axis[2], env.gemm)
+            verify(s)
+
+        def test_smt():
+            # test smt schedule
+            s = tvm.create_schedule(y.op)
+            s[x_buf].set_scope(env.inp_scope)
+            s[w_buf].set_scope(env.wgt_scope)
+            s[y_gem].set_scope(env.acc_scope)
+            s[y_shf].set_scope(env.acc_scope)
+            s[y_max].set_scope(env.acc_scope)
+            s[y_min].set_scope(env.acc_scope)
+            abo, aco, abi, aci = s[y].op.axis
+            abo1, abo2 = s[y].split(abo, nparts=2)
+            s[y].bind(abo1, tvm.thread_axis("cthread"))
+            s[y_gem].compute_at(s[y], abo1)
+            s[y_shf].compute_at(s[y], abo1)
+            s[y_max].compute_at(s[y], abo1)
+            s[y_min].compute_at(s[y], abo1)
+            s[y_gem].reorder(
+                ko,
+                s[y_gem].op.axis[0],
+                s[y_gem].op.axis[1],
+                s[y_gem].op.axis[2],
+                s[y_gem].op.axis[3],
+                ki)
+            s[y_gem].tensorize(s[y_gem].op.axis[2], env.gemm)
+            s[y_shf].pragma(s[y_shf].op.axis[0], env.alu)
+            s[y_max].pragma(s[y_max].op.axis[0], env.alu)
+            s[y_min].pragma(s[y_min].op.axis[0], env.alu)
+            s[x_buf].compute_at(s[y_gem], ko)
+            s[x_buf].pragma(s[x_buf].op.axis[0], env.dma_copy)
+            s[w_buf].compute_at(s[y_gem], ko)
+            s[w_buf].pragma(s[w_buf].op.axis[0], env.dma_copy)
+            s[y].pragma(abo2, env.dma_copy)
+            verify(s)
+
+        test_schedule1()
+        test_smt()
+    vta.testing.run(_run)
+
+
+def test_alu():
+    def _run(env, remote):
+        def check_alu(tvm_op, np_op=None, use_imm=False):
+            """Test ALU"""
+            m = 8
+            n = 8
+            imm = np.random.randint(1,5)
+            # compute
+            a = tvm.placeholder(
+                (m, n, env.BATCH, env.BLOCK_OUT),
+                name="a",
+                dtype=env.acc_dtype)
+            a_buf = tvm.compute(
+                (m, n, env.BATCH, env.BLOCK_OUT),
+                lambda *i: a(*i),
+                "a_buf") #DRAM->SRAM
+            if use_imm:
+                res_buf = tvm.compute(
+                    (m, n, env.BATCH, env.BLOCK_OUT),
+                    lambda *i: tvm_op(a_buf(*i), imm),
+                    "res_buf") #compute
+            else:
+                b = tvm.placeholder(
+                    (m, n, env.BATCH, env.BLOCK_OUT),
+                    name="b",
+                    dtype=env.acc_dtype)
+                b_buf = tvm.compute(
+                    (m, n, env.BATCH, env.BLOCK_OUT),
+                    lambda *i: b(*i),
+                    "b_buf") #DRAM->SRAM
+                res_buf = tvm.compute(
+                    (m, n, env.BATCH, env.BLOCK_OUT),
+                    lambda *i: tvm_op(a_buf(*i), b_buf(*i)),
+                    "res_buf") #compute5B
+            res = tvm.compute(
+                (m, n, env.BATCH, env.BLOCK_OUT),
+                lambda *i: res_buf(*i).astype(env.inp_dtype),
+                "res") #SRAM->DRAM
+            # schedule
+            s = tvm.create_schedule(res.op)
+            s[a_buf].set_scope(env.acc_scope) # SRAM
+            s[a_buf].pragma(a_buf.op.axis[0], env.dma_copy) # DRAM->SRAM
+            s[res_buf].set_scope(env.acc_scope) # SRAM
+            s[res_buf].pragma(res_buf.op.axis[0], env.alu) # compute
+            s[res].pragma(res.op.axis[0], env.dma_copy) # SRAM->DRAM
+            if not use_imm:
+                s[b_buf].set_scope(env.acc_scope) # SRAM
+                s[b_buf].pragma(b_buf.op.axis[0], env.dma_copy) # DRAM->SRAM
+
+            if not remote:
+                return
+
+            # build
+            with vta.build_config():
+                if use_imm:
+                    mod = vta.build(s, [a, res], "ext_dev", env.target_host)
+                else:
+                    mod = vta.build(s, [a, b, res], "ext_dev", env.target_host)
+            temp = util.tempdir()
+            mod.save(temp.relpath("load_act.o"))
+            remote.upload(temp.relpath("load_act.o"))
+            f = remote.load_module("load_act.o")
+            # verify
+            ctx = remote.ext_dev(0)
+            a_np = np.random.randint(
+                -16, 16, size=(m, n, env.BATCH, env.BLOCK_OUT)).astype(a.dtype)
+            if use_imm:
+                res_np = np_op(a_np, imm) if np_op else tvm_op(a_np, imm)
+            else:
+                b_np = np.random.randint(
+                    -16, 16, size=(m, n, env.BATCH, env.BLOCK_OUT)).astype(b.dtype)
+                res_np = np_op(a_np, b_np) if np_op else tvm_op(a_np, b_np)
+            res_np = res_np.astype(res.dtype)
+            a_nd = tvm.nd.array(a_np, ctx)
+            res_nd = tvm.nd.array(
+                np.zeros((m, n, env.BATCH, env.BLOCK_OUT)).astype(res.dtype), ctx)
+            if use_imm:
+                f(a_nd, res_nd)
+            else:
+                b_nd = tvm.nd.array(b_np, ctx)
+                f(a_nd, b_nd, res_nd)
+            np.testing.assert_equal(res_np, res_nd.asnumpy())
+
+        check_alu(lambda x, y: x << y, np.left_shift, use_imm=True)
+        check_alu(tvm.max, np.maximum, use_imm=True)
+        check_alu(tvm.max, np.maximum)
+        check_alu(lambda x, y: x + y, use_imm=True)
+        check_alu(lambda x, y: x + y)
+        check_alu(lambda x, y: x >> y, np.right_shift, use_imm=True)
+
+    vta.testing.run(_run)
+
+
+def test_relu():
+    """Test RELU on ALU"""
+    def _run(env, remote):
+        m = 8
+        n = 10
+        # compute
+        a = tvm.placeholder(
+            (m, n, env.BATCH, env.BLOCK_OUT),
+            name="a",
+            dtype=env.acc_dtype)
+        a_buf = tvm.compute(
+            (m, n, env.BATCH, env.BLOCK_OUT),
+            lambda *i: a(*i),
+            "a_buf") # DRAM->SRAM
+        max_buf = tvm.compute(
+            (m, n, env.BATCH, env.BLOCK_OUT),
+            lambda *i: tvm.max(a_buf(*i), 0),
+            "res_buf") # relu
+        min_buf = tvm.compute(
+            (m, n, env.BATCH, env.BLOCK_OUT),
+            lambda *i: tvm.min(max_buf(*i), (1<<(env.INP_WIDTH-1))-1),
+            "max_buf") # relu
+        res = tvm.compute(
+            (m, n, env.BATCH, env.BLOCK_OUT),
+            lambda *i: min_buf(*i).astype(env.inp_dtype),
+            "min_buf") # SRAM->DRAM
+        # schedule
+        s = tvm.create_schedule(res.op)
+        s[a_buf].set_scope(env.acc_scope) # SRAM
+        s[a_buf].pragma(a_buf.op.axis[0], env.dma_copy) # DRAM->SRAM
+        s[max_buf].set_scope(env.acc_scope) # SRAM
+        s[min_buf].set_scope(env.acc_scope) # SRAM
+        s[max_buf].pragma(max_buf.op.axis[0], env.alu) # compute
+        s[min_buf].pragma(min_buf.op.axis[0], env.alu) # compute
+        s[res].pragma(res.op.axis[0], env.dma_copy) # SRAM->DRAM
+        # build
+        with vta.build_config():
+            mod = vta.build(s, [a, res], "ext_dev", env.target_host)
+        if not remote:
+            return
+        temp = util.tempdir()
+        mod.save(temp.relpath("load_act.o"))
+        remote.upload(temp.relpath("load_act.o"))
+        f = remote.load_module("load_act.o")
+        # verify
+        ctx = remote.ext_dev(0)
+        a_np = np.random.randint(
+            -256, 256, size=(m, n, env.BATCH, env.BLOCK_OUT)).astype(a.dtype)
+        res_np = np.clip(a_np, 0, (1<<(env.INP_WIDTH-1))-1).astype(res.dtype)
+        a_nd = tvm.nd.array(a_np, ctx)
+        res_nd = tvm.nd.array(
+            np.zeros((m, n, env.BATCH, env.BLOCK_OUT)).astype(res.dtype), ctx)
+        f(a_nd, res_nd)
+        np.testing.assert_equal(res_np, res_nd.asnumpy())
+
+    vta.testing.run(_run)
+
+
+def test_shift_and_scale():
+    """Test shift and scale on ALU"""
+    def _run(env, remote):
+        m = 2
+        n = 8
+        imm_shift = np.random.randint(0,8)
+        imm_scale = np.random.randint(1,5)
+        # compute
+        a = tvm.placeholder(
+            (m, n, env.BATCH, env.BLOCK_OUT),
+            name="a", dtype=env.acc_dtype)
+        a_buf = tvm.compute(
+            (m, n, env.BATCH, env.BLOCK_OUT),
+            lambda *i: a(*i),
+            "a_buf") # DRAM->SRAM
+        res_shift = tvm.compute(
+            (m, n, env.BATCH, env.BLOCK_OUT),
+            lambda *i: a_buf(*i)+imm_shift,
+            "res_shift") # compute
+        res_scale = tvm.compute(
+            (m, n, env.BATCH, env.BLOCK_OUT),
+            lambda *i: res_shift(*i)>>imm_scale,
+            "res_scale") # compute
+        res = tvm.compute(
+            (m, n, env.BATCH, env.BLOCK_OUT),
+            lambda *i: res_scale(*i).astype(env.inp_dtype),
+            "res") # SRAM->DRAM
+        # schedule
+        s = tvm.create_schedule(res.op)
+        s[a_buf].set_scope(env.acc_scope) # SRAM
+        s[res_shift].set_scope(env.acc_scope) # SRAM
+        s[res_scale].set_scope(env.acc_scope) # SRAM
+        s[a_buf].pragma(a_buf.op.axis[0], env.dma_copy) # DRAM->SRAM
+        s[res_shift].pragma(res_shift.op.axis[0], env.alu) # compute
+        s[res_scale].pragma(res_scale.op.axis[0], env.alu) # compute
+        s[res].pragma(res.op.axis[0], env.dma_copy) # SRAM->DRAM
+        # build
+        mod = vta.build(s, [a, res], "ext_dev", env.target_host)
+        if not remote:
+            return
+        temp = util.tempdir()
+        mod.save(temp.relpath("load_act.o"))
+        remote.upload(temp.relpath("load_act.o"))
+        f = remote.load_module("load_act.o")
+        # verify
+        ctx = remote.ext_dev(0)
+        a_np = np.random.randint(
+            -10, 10, size=(m, n, env.BATCH, env.BLOCK_OUT)).astype(a.dtype)
+        res_np = np.right_shift((a_np + imm_shift), imm_scale)
+        res_np = res_np.astype(res.dtype)
+        a_nd = tvm.nd.array(a_np, ctx)
+        res_nd = tvm.nd.array(
+            np.zeros((m, n, env.BATCH, env.BLOCK_OUT)).astype(res.dtype), ctx)
+        f(a_nd, res_nd)
+        np.testing.assert_equal(res_np, res_nd.asnumpy())
+
+    vta.testing.run(_run)
+
+if __name__ == "__main__":
+    print("Load/store test")
+    test_save_load_out()
+    print("Padded load test")
+    #test_padded_load()
+    print("GEMM test")
+    test_gemm()
+    test_alu()
+    print("ALU test")
+    test_relu()
+    print("Shift and scale")
+    test_shift_and_scale()

From 5481665381ff31fb6f8323e7d3f17801435c976d Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 13 Apr 2018 22:05:09 -0700
Subject: [PATCH 833/949] [RUNTIME] Simplify dynamic library and code path.
 (#27)

* [RUNTIME] Simplify dynamic library and code path.

* reword the readme
---
 vta/Makefile                                  |  16 +--
 vta/README.md                                 |  24 +++-
 vta/make/config.mk                            |   7 +-
 vta/python/vta/__init__.py                    |   3 +-
 vta/python/vta/environment.py                 |  65 ++++++++--
 vta/python/vta/exec/rpc_server.py             |  53 +++++----
 vta/python/vta/testing/simulator.py           |   1 -
 vta/python/vta/testing/util.py                |  14 ++-
 vta/src/data_buffer.cc                        |  44 -------
 vta/src/data_buffer.h                         |  90 --------------
 .../{tvm/vta_device_api.cc => device_api.cc}  |  29 ++---
 vta/src/runtime.cc                            | 112 +++++++++++++++++-
 vta/tests/python/unittest/test_vta_insn.py    |  15 +++
 13 files changed, 260 insertions(+), 213 deletions(-)
 delete mode 100644 vta/src/data_buffer.cc
 delete mode 100644 vta/src/data_buffer.h
 rename vta/src/{tvm/vta_device_api.cc => device_api.cc} (75%)

diff --git a/vta/Makefile b/vta/Makefile
index 6bfa82dc2e10..e112b39c9f58 100644
--- a/vta/Makefile
+++ b/vta/Makefile
@@ -53,12 +53,9 @@ else
 	NO_WHOLE_ARCH= --no-whole-archive
 endif
 
-
-all: lib/libvta.so lib/libvta_runtime.so
-
 VTA_LIB_SRC = $(wildcard src/*.cc src/tvm/*.cc)
 
-ifeq ($(TARGET), VTA_PYNQ_TARGET)
+ifeq ($(VTA_TARGET), pynq)
 	VTA_LIB_SRC += $(wildcard src/pynq/*.cc)
 	LDFLAGS += -L/usr/lib -lsds_lib
 	LDFLAGS += -L/opt/python3.6/lib/python3.6/site-packages/pynq/drivers/
@@ -66,24 +63,23 @@ ifeq ($(TARGET), VTA_PYNQ_TARGET)
 	LDFLAGS += -l:libdma.so
 endif
 
-ifeq ($(TARGET), sim)
+ifeq ($(VTA_TARGET), sim)
 	VTA_LIB_SRC += $(wildcard src/sim/*.cc)
 endif
 
 VTA_LIB_OBJ = $(patsubst src/%.cc, build/%.o, $(VTA_LIB_SRC))
 
+all: lib/libvta.so
+
 build/%.o: src/%.cc
 	@mkdir -p $(@D)
-	$(CXX) $(CFLAGS) -MM -MT build/src/$*.o $< >build/$*.d
+	$(CXX) $(CFLAGS) -MM -MT build/$*.o $< >build/$*.d
 	$(CXX) -c $(CFLAGS) -c $< -o $@
 
-lib/libvta.so: $(filter-out build/runtime.o, $(VTA_LIB_OBJ))
+lib/libvta.so: $(VTA_LIB_OBJ)
 	@mkdir -p $(@D)
 	$(CXX) $(CFLAGS) -shared -o $@ $(filter %.o, $^) $(LDFLAGS)
 
-lib/libvta_runtime.so: build/runtime.o
-	@mkdir -p $(@D)
-	$(CXX) $(CFLAGS) -shared -o $@ $(filter %.o, $^) $(LDFLAGS)
 
 lint: pylint cpplint
 
diff --git a/vta/README.md b/vta/README.md
index 39d094cc54e6..5408f3b950b1 100644
--- a/vta/README.md
+++ b/vta/README.md
@@ -1,11 +1,25 @@
-Open Hardware/Software Stack for Vertical Deep Learning System Optimization
-==============================================
+VTA: Open, Modular, Deep Learning Accelerator Stack
+===================================================
 
 [![GitHub license](http://dmlc.github.io/img/apache2.svg)](./LICENSE)
 
-VTA is an open hardware/software co-design stack for deep learning systems systems.
-It provides a customizable hardware accelerator template for deep learning inference workloads,
-combined with a fully functional compiler stack built with TVM.
+VTA(versatile tensor accelerator) is an open-source deep learning accelerator stack.
+It is not just an open-source hardware, but is an end to end solution that includes
+the entire software stack on top of VTA open-source hardware.
+
+
+The key features include:
+
+- Generic, modular open-source hardware
+  - Streamlined workflow to deploy to FPGAs.
+  - Simulator support
+- Driver and JIT runtime for both simulated backend and FPGA.
+- End to end TVM stack integration
+  - Direct optimization and deploy models from deep learning frameworks via TVM stack.
+  - Customized and extendible TVM compiler backend
+  - Flexible RPC support to ease the deployment, you can program it with python :)
+
+VTA is part of our effort on [TVM Stack](http://www.tvmlang.org/).
 
 License
 -------
diff --git a/vta/make/config.mk b/vta/make/config.mk
index e329dcf987b8..2bf25132e245 100644
--- a/vta/make/config.mk
+++ b/vta/make/config.mk
@@ -26,8 +26,8 @@ ADD_LDFLAGS=
 # the additional compile flags you want to add
 ADD_CFLAGS=
 
-# the hardware target
-TARGET = pynq
+# the hardware target, can be [sim, pynq]
+VTA_TARGET = pynq
 
 #---------------------
 # VTA hardware parameters
@@ -88,7 +88,8 @@ $(shell echo "$$(( $(VTA_LOG_ACC_BUFF_SIZE) + $(VTA_LOG_OUT_WIDTH) - $(VTA_LOG_A
 VTA_OUT_BUFF_SIZE = $(shell echo "$$(( 1 << $(VTA_LOG_OUT_BUFF_SIZE) ))" )
 
 # Update ADD_CFLAGS
-ADD_CFLAGS += \
+ADD_CFLAGS +=
+	-DVTA_TARGET=$(VTA_TARGET)\
 	-DVTA_LOG_WGT_WIDTH=$(VTA_LOG_WGT_WIDTH) -DVTA_LOG_INP_WIDTH=$(VTA_LOG_INP_WIDTH) \
 	-DVTA_LOG_ACC_WIDTH=$(VTA_LOG_ACC_WIDTH) -DVTA_LOG_OUT_WIDTH=$(VTA_LOG_OUT_WIDTH) \
 	-DVTA_LOG_BATCH=$(VTA_LOG_BATCH) \
diff --git a/vta/python/vta/__init__.py b/vta/python/vta/__init__.py
index 693a4124f40b..80091f80d164 100644
--- a/vta/python/vta/__init__.py
+++ b/vta/python/vta/__init__.py
@@ -1,5 +1,6 @@
 """TVM-based VTA Compiler Toolchain"""
 from __future__ import absolute_import as _abs
+import sys
 
 from .environment import get_env, Environment
 
@@ -10,5 +11,5 @@
     from .rpc_client import reconfig_runtime, program_fpga
 
     from . import graph
-except ImportError:
+except (ImportError, RuntimeError):
     pass
diff --git a/vta/python/vta/environment.py b/vta/python/vta/environment.py
index 8ff2bbce2787..a59e66a564b2 100644
--- a/vta/python/vta/environment.py
+++ b/vta/python/vta/environment.py
@@ -3,14 +3,10 @@
 from __future__ import absolute_import as _abs
 
 import os
+import glob
 import copy
-
-try:
-    # Allow missing import in config mode.
-    import tvm
-    from . import intrin
-except ImportError:
-    pass
+import tvm
+from . import intrin
 
 
 class DevContext(object):
@@ -65,6 +61,45 @@ def get_task_qid(self, qid):
         return 1 if self.DEBUG_NO_SYNC else qid
 
 
+class PkgConfig(object):
+    """Simple package config tool for VTA.
+
+    This is used to provide runtime specific configurations.
+    """
+    def __init__(self, env):
+        curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
+        proj_root = os.path.abspath(os.path.join(curr_path, "../../"))
+        # include path
+        self.include_path = [
+            "-I%s/include" % proj_root,
+            "-I%s/nnvm/tvm/include" % proj_root,
+            "-I%s/nnvm/tvm/dlpack/include" % proj_root,
+            "-I%s/nnvm/dmlc-core/include" % proj_root
+        ]
+        # List of source files that can be used to build standalone library.
+        self.lib_source = []
+        self.lib_source += glob.glob("%s/src/*.cc" % proj_root)
+        self.lib_source += glob.glob("%s/src/%s/*.cc" % (proj_root, env.TARGET))
+        # macro keys
+        self.macro_defs = []
+        for key in env.cfg_keys:
+            self.macro_defs.append("-DVTA_%s=%s" % (key, str(getattr(env, key))))
+
+        if env.TARGET == "pynq":
+            self.ldflags = [
+                "-L/usr/lib",
+                "-lsds_lib",
+                "-L/opt/python3.6/lib/python3.6/site-packages/pynq/drivers/",
+                "-L/opt/python3.6/lib/python3.6/site-packages/pynq/lib/",
+                "-l:libdma.so"]
+        else:
+            self.ldflags = []
+
+    @property
+    def cflags(self):
+        return self.include_path + self.macro_defs
+
+
 class Environment(object):
     """Hareware configuration object.
 
@@ -160,6 +195,7 @@ def __init__(self, cfg):
         self.mock_mode = False
         self._mock_env = None
         self._dev_ctx = None
+        self._pkg_config = None
 
     @property
     def dev(self):
@@ -168,6 +204,13 @@ def dev(self):
             self._dev_ctx = DevContext(self)
         return self._dev_ctx
 
+    @property
+    def pkg_config(self):
+        """PkgConfig instance"""
+        if self._pkg_config is None:
+            self._pkg_config = PkgConfig(self)
+        return self._pkg_config
+
     @property
     def mock(self):
         """A mock version of the Environment
@@ -249,7 +292,7 @@ def mem_info_wgt_buffer():
                          head_address=None)
 
 @tvm.register_func("tvm.info.mem.%s" % Environment.acc_scope)
-def mem_info_out_buffer():
+def mem_info_acc_buffer():
     spec = get_env()
     return tvm.make.node("MemoryInfo",
                          unit_bits=spec.ACC_ELEM_BITS,
@@ -265,6 +308,7 @@ def coproc_sync(op):
         "int32", "VTASynchronize",
         get_env().dev.command_handle, 1<<31)
 
+
 @tvm.register_func("tvm.intrin.rule.default.vta.coproc_dep_push")
 def coproc_dep_push(op):
     return tvm.call_extern(
@@ -272,6 +316,7 @@ def coproc_dep_push(op):
         get_env().dev.command_handle,
         op.args[0], op.args[1])
 
+
 @tvm.register_func("tvm.intrin.rule.default.vta.coproc_dep_pop")
 def coproc_dep_pop(op):
     return tvm.call_extern(
@@ -288,7 +333,6 @@ def _init_env():
 
     for k in Environment.cfg_keys:
         keys.add("VTA_" + k)
-    keys.add("TARGET")
 
     if not os.path.isfile(filename):
         raise RuntimeError(
@@ -303,8 +347,9 @@ def _init_env():
                     val = line.split("=")[1].strip()
                     if k.startswith("VTA_"):
                         k = k[4:]
+                    try:
                         cfg[k] = int(val)
-                    else:
+                    except ValueError:
                         cfg[k] = val
     return Environment(cfg)
 
diff --git a/vta/python/vta/exec/rpc_server.py b/vta/python/vta/exec/rpc_server.py
index ebaf15f8dc37..ca99416f7818 100644
--- a/vta/python/vta/exec/rpc_server.py
+++ b/vta/python/vta/exec/rpc_server.py
@@ -9,26 +9,46 @@
 import os
 import ctypes
 import tvm
+from tvm._ffi.base import c_str
 from tvm.contrib import rpc, cc
 
+from ..environment import get_env
+
 
 @tvm.register_func("tvm.contrib.rpc.server.start", override=True)
 def server_start():
-    """callback when server starts."""
+    """VTA RPC server extension."""
     # pylint: disable=unused-variable
     curr_path = os.path.dirname(
         os.path.abspath(os.path.expanduser(__file__)))
     dll_path = os.path.abspath(
-        os.path.join(curr_path, "../../../lib/libvta_runtime.so"))
+        os.path.join(curr_path, "../../../lib/libvta.so"))
     runtime_dll = []
     _load_module = tvm.get_global_func("tvm.contrib.rpc.server.load_module")
 
-    @tvm.register_func("tvm.contrib.rpc.server.load_module", override=True)
-    def load_module(file_name):
+    def load_vta_dll():
+        """Try to load vta dll"""
         if not runtime_dll:
             runtime_dll.append(ctypes.CDLL(dll_path, ctypes.RTLD_GLOBAL))
+        logging.info("Loading VTA library: %s", dll_path)
+        return runtime_dll[0]
+
+    @tvm.register_func("tvm.contrib.rpc.server.load_module", override=True)
+    def load_module(file_name):
+        load_vta_dll()
         return _load_module(file_name)
 
+    @tvm.register_func("device_api.ext_dev")
+    def ext_dev_callback():
+        load_vta_dll()
+        return tvm.get_global_func("device_api.ext_dev")()
+
+    @tvm.register_func("tvm.contrib.vta.init", override=True)
+    def program_fpga(file_name):
+        path = tvm.get_global_func("tvm.contrib.rpc.server.workpath")(file_name)
+        load_vta_dll().VTAProgram(c_str(path))
+        logging.info("Program FPGA with %s", file_name)
+
     @tvm.register_func("tvm.contrib.rpc.server.shutdown", override=True)
     def server_shutdown():
         if runtime_dll:
@@ -47,17 +67,15 @@ def reconfig_runtime(cflags):
         if runtime_dll:
             raise RuntimeError("Can only reconfig in the beginning of session...")
         cflags = cflags.split()
+        env = get_env()
         cflags += ["-O2", "-std=c++11"]
+        cflags += env.pkg_config.include_path
+        ldflags = env.pkg_config.ldflags
         lib_name = dll_path
-        curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
-        proj_root = os.path.abspath(os.path.join(curr_path, "../../../"))
-        runtime_source = os.path.join(proj_root, "src/runtime.cc")
-        cflags += ["-I%s/include" % proj_root]
-        cflags += ["-I%s/nnvm/tvm/include" % proj_root]
-        cflags += ["-I%s/nnvm/tvm/dlpack/include" % proj_root]
-        cflags += ["-I%s/nnvm/dmlc-core/include" % proj_root]
-        logging.info("Rebuild runtime dll with %s", str(cflags))
-        cc.create_shared(lib_name, [runtime_source], cflags)
+        source = env.pkg_config.lib_source
+        logging.info("Rebuild runtime: output=%s, cflags=%s, source=%s, ldflags=%s",
+                     dll_path, str(cflags), str(source), str(ldflags))
+        cc.create_shared(lib_name, source, cflags + ldflags)
 
 
 def main():
@@ -75,14 +93,6 @@ def main():
                         help="Report to RPC tracker")
     args = parser.parse_args()
     logging.basicConfig(level=logging.INFO)
-    curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
-    proj_root = os.path.abspath(os.path.join(curr_path, "../../../"))
-    lib_path = os.path.abspath(os.path.join(proj_root, "lib/libvta.so"))
-
-    libs = []
-    for file_name in [lib_path]:
-        libs.append(ctypes.CDLL(file_name, ctypes.RTLD_GLOBAL))
-        logging.info("Load additional library %s", file_name)
 
     if args.tracker:
         url, port = args.tracker.split(":")
@@ -99,7 +109,6 @@ def main():
                         args.port_end,
                         key=args.key,
                         tracker_addr=tracker_addr)
-    server.libs += libs
     server.proc.join()
 
 if __name__ == "__main__":
diff --git a/vta/python/vta/testing/simulator.py b/vta/python/vta/testing/simulator.py
index bb436a1853a8..3505e49eeb04 100644
--- a/vta/python/vta/testing/simulator.py
+++ b/vta/python/vta/testing/simulator.py
@@ -10,7 +10,6 @@ def _load_lib():
     curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
     dll_path = [
         os.path.abspath(os.path.join(curr_path, "../../../lib/libvta.so")),
-        os.path.abspath(os.path.join(curr_path, "../../../lib/libvta_runtime.so"))
     ]
     runtime_dll = []
     if not all(os.path.exists(f) for f in dll_path):
diff --git a/vta/python/vta/testing/util.py b/vta/python/vta/testing/util.py
index bbf6417a167e..402546c0efa1 100644
--- a/vta/python/vta/testing/util.py
+++ b/vta/python/vta/testing/util.py
@@ -15,10 +15,18 @@ def run(run_func):
     run_func : function(env, remote)
     """
     env = get_env()
-    # run on simulator
-    if simulator.enabled():
+
+    # Run on local sim rpc if necessary
+    local_rpc = int(os.environ.get("VTA_LOCAL_SIM_RPC", "0"))
+    if local_rpc:
         env.TARGET = "sim"
-        run_func(env, rpc.LocalSession())
+        remote = rpc.connect("localhost", local_rpc)
+        run_func(env, remote)
+    else:
+        # run on simulator
+        if simulator.enabled():
+            env.TARGET = "sim"
+            run_func(env, rpc.LocalSession())
 
     # Run on PYNQ if env variable exists
     pynq_host = os.environ.get("VTA_PYNQ_RPC_HOST", None)
diff --git a/vta/src/data_buffer.cc b/vta/src/data_buffer.cc
deleted file mode 100644
index 99f959ad8c8b..000000000000
--- a/vta/src/data_buffer.cc
+++ /dev/null
@@ -1,44 +0,0 @@
-/*!
- *  Copyright (c) 2018 by Contributors
- * \file data_buffer.cc
- * \brief Buffer related API for VTA.
- * \note Buffer API remains stable across VTA designes.
- */
-#include "./data_buffer.h"
-
-void* VTABufferAlloc(size_t size) {
-  return vta::DataBuffer::Alloc(size);
-}
-
-void VTABufferFree(void* buffer) {
-  vta::DataBuffer::Free(vta::DataBuffer::FromHandle(buffer));
-}
-
-void VTABufferCopy(const void* from,
-                   size_t from_offset,
-                   void* to,
-                   size_t to_offset,
-                   size_t size,
-                   int kind_mask) {
-  vta::DataBuffer* from_buffer = nullptr;
-  vta::DataBuffer* to_buffer = nullptr;
-
-  if (kind_mask & 2) {
-    from_buffer = vta::DataBuffer::FromHandle(from);
-    from = from_buffer->virt_addr();
-  }
-  if (kind_mask & 1) {
-    to_buffer = vta::DataBuffer::FromHandle(to);
-    to = to_buffer->virt_addr();
-  }
-  if (from_buffer) {
-    from_buffer->InvalidateCache(from_offset, size);
-  }
-
-  memcpy(static_cast<char*>(to) + to_offset,
-         static_cast<const char*>(from) + from_offset,
-         size);
-  if (to_buffer) {
-    to_buffer->FlushCache(to_offset, size);
-  }
-}
diff --git a/vta/src/data_buffer.h b/vta/src/data_buffer.h
deleted file mode 100644
index fba46dc07efa..000000000000
--- a/vta/src/data_buffer.h
+++ /dev/null
@@ -1,90 +0,0 @@
-/*!
- *  Copyright (c) 2018 by Contributors
- * \file data_buffer.h
- * \brief VTA runtime internal data buffer structure.
- */
-#ifndef VTA_DATA_BUFFER_H_
-#define VTA_DATA_BUFFER_H_
-
-#include <vta/driver.h>
-#include <vta/runtime.h>
-#include <cassert>
-#include <cstring>
-
-namespace vta {
-
-/*! \brief Enable coherent access between VTA and CPU. */
-static const bool kBufferCoherent = true;
-
-/*!
- * \brief Data buffer represents data on CMA.
- */
-struct DataBuffer {
-  /*! \return Virtual address of the data. */
-  void* virt_addr() const {
-    return data_;
-  }
-  /*! \return Physical address of the data. */
-  uint32_t phy_addr() const {
-    return phy_addr_;
-  }
-  /*!
-   * \brief Invalidate the cache of given location in data buffer.
-   * \param offset The offset to the data.
-   * \param size The size of the data.
-   */
-  void InvalidateCache(size_t offset, size_t size) {
-    if (!kBufferCoherent) {
-      VTAInvalidateCache(phy_addr_ + offset, size);
-    }
-  }
-  /*!
-   * \brief Invalidate the cache of certain location in data buffer.
-   * \param offset The offset to the data.
-   * \param size The size of the data.
-   */
-  void FlushCache(size_t offset, size_t size) {
-    if (!kBufferCoherent) {
-      VTAFlushCache(phy_addr_ + offset, size);
-    }
-  }
-  /*!
-   * \brief Allocate a buffer of a given size.
-   * \param size The size of the buffer.
-   */
-  static DataBuffer* Alloc(size_t size) {
-    void* data = VTAMemAlloc(size, 1);
-    assert(data != nullptr);
-    DataBuffer* buffer = new DataBuffer();
-    buffer->data_ = data;
-    buffer->phy_addr_ = VTAMemGetPhyAddr(data);
-    return buffer;
-  }
-  /*!
-   * \brief Free the data buffer.
-   * \param buffer The buffer to be freed.
-   */
-  static void Free(DataBuffer* buffer) {
-    VTAMemFree(buffer->data_);
-    delete buffer;
-  }
-  /*!
-   * \brief Create data buffer header from buffer ptr.
-   * \param buffer The buffer pointer.
-   * \return The corresponding data buffer header.
-   */
-  static DataBuffer* FromHandle(const void* buffer) {
-    return const_cast<DataBuffer*>(
-        reinterpret_cast<const DataBuffer*>(buffer));
-  }
-
- private:
-  /*! \brief The internal data. */
-  void* data_;
-  /*! \brief The physical address of the buffer, excluding header. */
-  uint32_t phy_addr_;
-};
-
-}  // namespace vta
-
-#endif  // VTA_DATA_BUFFER_H_
diff --git a/vta/src/tvm/vta_device_api.cc b/vta/src/device_api.cc
similarity index 75%
rename from vta/src/tvm/vta_device_api.cc
rename to vta/src/device_api.cc
index e4671d8a0207..687ec625a02c 100644
--- a/vta/src/tvm/vta_device_api.cc
+++ b/vta/src/device_api.cc
@@ -1,15 +1,14 @@
 /*!
  *  Copyright (c) 2018 by Contributors
- * \file vta_device_api.cc
- * \brief VTA device API for TVM
+ * \file device_api.cc
+ * \brief TVM device API for VTA
  */
 
 #include <tvm/runtime/registry.h>
 #include <dmlc/thread_local.h>
 #include <vta/runtime.h>
-#include <dlfcn.h>
 
-#include "../../nnvm/tvm/src/runtime/workspace_pool.h"
+#include "../nnvm/tvm/src/runtime/workspace_pool.h"
 
 
 namespace tvm {
@@ -26,7 +25,8 @@ class VTADeviceAPI final : public DeviceAPI {
   }
 
   void* AllocDataSpace(TVMContext ctx,
-                       size_t size, size_t alignment,
+                       size_t size,
+                       size_t alignment,
                        TVMType type_hint) final {
     return VTABufferAlloc(size);
   }
@@ -84,22 +84,9 @@ void VTADeviceAPI::FreeWorkspace(TVMContext ctx, void* data) {
   dmlc::ThreadLocalStore<VTAWorkspacePool>::Get()->FreeWorkspace(ctx, data);
 }
 
-std::string VTARPCGetPath(const std::string& name) {
-  static const PackedFunc* f =
-      runtime::Registry::Get("tvm.contrib.rpc.server.workpath");
-  CHECK(f != nullptr) << "require tvm.contrib.rpc.server.workpath";
-  return (*f)(name);
-}
-
-// Global functions that can be called
-TVM_REGISTER_GLOBAL("tvm.contrib.vta.init")
-.set_body([](TVMArgs args, TVMRetValue* rv) {
-    std::string path = VTARPCGetPath(args[0]);
-    VTAProgram(path.c_str());
-    LOG(INFO) << "VTA initialization end with bistream " << path;
-  });
-
-TVM_REGISTER_GLOBAL("device_api.ext_dev")
+// Register device api with override.
+static TVM_ATTRIBUTE_UNUSED auto& __register_dev__ =
+::tvm::runtime::Registry::Register("device_api.ext_dev", true)
 .set_body([](TVMArgs args, TVMRetValue* rv) {
     DeviceAPI* ptr = VTADeviceAPI::Global().get();
     *rv = static_cast<void*>(ptr);
diff --git a/vta/src/runtime.cc b/vta/src/runtime.cc
index da5109c141ee..c0de87fa339d 100644
--- a/vta/src/runtime.cc
+++ b/vta/src/runtime.cc
@@ -5,8 +5,6 @@
  *
  *  The runtime depends on specific instruction
  *  stream spec as specified in hw_spec.h
- *  It is intended to be used as a dynamic library
- *  to enable hot swapping of hardware configurations.
  */
 #include <vta/driver.h>
 #include <vta/hw_spec.h>
@@ -14,15 +12,87 @@
 #include <dmlc/logging.h>
 
 #include <cassert>
+#include <cstring>
 #include <vector>
 #include <thread>
 #include <memory>
 #include <atomic>
 
-#include "./data_buffer.h"
 
 namespace vta {
 
+/*! \brief Enable coherent access between VTA and CPU. */
+static const bool kBufferCoherent = true;
+
+/*!
+ * \brief Data buffer represents data on CMA.
+ */
+struct DataBuffer {
+  /*! \return Virtual address of the data. */
+  void* virt_addr() const {
+    return data_;
+  }
+  /*! \return Physical address of the data. */
+  uint32_t phy_addr() const {
+    return phy_addr_;
+  }
+  /*!
+   * \brief Invalidate the cache of given location in data buffer.
+   * \param offset The offset to the data.
+   * \param size The size of the data.
+   */
+  void InvalidateCache(size_t offset, size_t size) {
+    if (!kBufferCoherent) {
+      VTAInvalidateCache(phy_addr_ + offset, size);
+    }
+  }
+  /*!
+   * \brief Invalidate the cache of certain location in data buffer.
+   * \param offset The offset to the data.
+   * \param size The size of the data.
+   */
+  void FlushCache(size_t offset, size_t size) {
+    if (!kBufferCoherent) {
+      VTAFlushCache(phy_addr_ + offset, size);
+    }
+  }
+  /*!
+   * \brief Allocate a buffer of a given size.
+   * \param size The size of the buffer.
+   */
+  static DataBuffer* Alloc(size_t size) {
+    void* data = VTAMemAlloc(size, 1);
+    CHECK(data != nullptr);
+    DataBuffer* buffer = new DataBuffer();
+    buffer->data_ = data;
+    buffer->phy_addr_ = VTAMemGetPhyAddr(data);
+    return buffer;
+  }
+  /*!
+   * \brief Free the data buffer.
+   * \param buffer The buffer to be freed.
+   */
+  static void Free(DataBuffer* buffer) {
+    VTAMemFree(buffer->data_);
+    delete buffer;
+  }
+  /*!
+   * \brief Create data buffer header from buffer ptr.
+   * \param buffer The buffer pointer.
+   * \return The corresponding data buffer header.
+   */
+  static DataBuffer* FromHandle(const void* buffer) {
+    return const_cast<DataBuffer*>(
+        reinterpret_cast<const DataBuffer*>(buffer));
+  }
+
+ private:
+  /*! \brief The internal data. */
+  void* data_;
+  /*! \brief The physical address of the buffer, excluding header. */
+  uint32_t phy_addr_;
+};
+
 /*!
  * \brief Micro op kernel.
  *  Contains functions to construct the kernel with prefix Push.
@@ -1130,6 +1200,42 @@ class CommandQueue {
 
 }  // namespace vta
 
+void* VTABufferAlloc(size_t size) {
+  return vta::DataBuffer::Alloc(size);
+}
+
+void VTABufferFree(void* buffer) {
+  vta::DataBuffer::Free(vta::DataBuffer::FromHandle(buffer));
+}
+
+void VTABufferCopy(const void* from,
+                   size_t from_offset,
+                   void* to,
+                   size_t to_offset,
+                   size_t size,
+                   int kind_mask) {
+  vta::DataBuffer* from_buffer = nullptr;
+  vta::DataBuffer* to_buffer = nullptr;
+
+  if (kind_mask & 2) {
+    from_buffer = vta::DataBuffer::FromHandle(from);
+    from = from_buffer->virt_addr();
+  }
+  if (kind_mask & 1) {
+    to_buffer = vta::DataBuffer::FromHandle(to);
+    to = to_buffer->virt_addr();
+  }
+  if (from_buffer) {
+    from_buffer->InvalidateCache(from_offset, size);
+  }
+
+  memcpy(static_cast<char*>(to) + to_offset,
+         static_cast<const char*>(from) + from_offset,
+         size);
+  if (to_buffer) {
+    to_buffer->FlushCache(to_offset, size);
+  }
+}
 
 VTACommandHandle VTATLSCommandHandle() {
   return vta::CommandQueue::ThreadLocal().get();
diff --git a/vta/tests/python/unittest/test_vta_insn.py b/vta/tests/python/unittest/test_vta_insn.py
index 339d8d31e238..79a20be13de9 100644
--- a/vta/tests/python/unittest/test_vta_insn.py
+++ b/vta/tests/python/unittest/test_vta_insn.py
@@ -468,7 +468,22 @@ def _run(env, remote):
 
     vta.testing.run(_run)
 
+
+def test_runtime_array():
+    def _run(env, remote):
+        n = 100
+        ctx = remote.ext_dev(0)
+        x_np = np.random.randint(
+            1, 10, size=(n, n, env.BATCH, env.BLOCK_OUT)).astype("int8")
+        x_nd = tvm.nd.array(x_np, ctx)
+        np.testing.assert_equal(x_np, x_nd.asnumpy())
+
+    vta.testing.run(_run)
+
+
 if __name__ == "__main__":
+    print("Array test")
+    test_runtime_array()
     print("Load/store test")
     test_save_load_out()
     print("Padded load test")

From a24fadb02aa13937ebd99dd07b319708e4ecf617 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 14 Apr 2018 19:02:23 -0700
Subject: [PATCH 834/949] [INFRASTRUCTURE] Migrate to json based config. Move
 gemm test to integration. (#28)

* Migrate to json based config. Move gemm test to integration.

* temp checkin

* checkin  example json
---
 vta/Makefile                                  |  57 +---
 vta/make/README.md                            |   8 +
 vta/make/config.json                          |  14 +
 vta/make/config.mk                            |  99 -------
 vta/make/vta_config.py                        |  65 +++++
 vta/python/vta/__init__.py                    |  10 +-
 vta/python/vta/environment.py                 | 107 ++-----
 vta/python/vta/exec/rpc_server.py             |  27 +-
 vta/python/vta/pkg_config.py                  |  94 +++++++
 vta/python/vta/rpc_client.py                  |  17 +-
 vta/src/sim/sim_driver.cc                     |   8 +-
 .../python/integration/test_benchmark_gemm.py | 257 +++++++++++++++++
 vta/tests/python/pynq/test_benchmark_gemm.py  | 266 ------------------
 13 files changed, 503 insertions(+), 526 deletions(-)
 create mode 100644 vta/make/README.md
 create mode 100644 vta/make/config.json
 delete mode 100644 vta/make/config.mk
 create mode 100644 vta/make/vta_config.py
 create mode 100644 vta/python/vta/pkg_config.py
 create mode 100644 vta/tests/python/integration/test_benchmark_gemm.py
 delete mode 100644 vta/tests/python/pynq/test_benchmark_gemm.py

diff --git a/vta/Makefile b/vta/Makefile
index e112b39c9f58..ead168fafa18 100644
--- a/vta/Makefile
+++ b/vta/Makefile
@@ -1,44 +1,12 @@
 ROOTDIR = $(CURDIR)
 
-ifndef config
-ifneq ("$(wildcard ./config.mk)", "")
-	config = config.mk
-else
-	config = make/config.mk
-endif
-endif
-include $(config)
-
 export LDFLAGS = -pthread -lm
 export CFLAGS = -std=c++11 -Wall -O2 -Iinclude -fPIC
 
-ifdef NNVM_PATH
-	CFLAGS += -I$(NNVM_PATH)/include
-else
-	NNVM_PATH = $(ROOTDIR)/nnvm
-	CFLAGS += -I$(NNVM_PATH)/include
-endif
-
-ifdef TVM_PATH
-	CFLAGS += -I$(TVM_PATH)/include -I$(TVM_PATH)/dlpack/include -I$(TVM_PATH)/HalideIR/src
-else
-	TVM_PATH = $(NNVM_PATH)/tvm
-	CFLAGS += -I$(TVM_PATH)/include -I$(TVM_PATH)/dlpack/include -I$(TVM_PATH)/HalideIR/src
-endif
-
-ifdef DMLC_CORE_PATH
-  CFLAGS += -I$(DMLC_CORE_PATH)/include
-else
-  CFLAGS += -I$(NNVM_PATH)/dmlc-core/include
-endif
-
-ifneq ($(ADD_CFLAGS), NONE)
-	CFLAGS += $(ADD_CFLAGS)
-endif
-
-ifneq ($(ADD_LDFLAGS), NONE)
-	LDFLAGS += $(ADD_LDFLAGS)
-endif
+VTA_CONFIG = python make/vta_config.py
+CFLAGS += `${VTA_CONFIG} --cflags`
+LDFLAGS += `${VTA_CONFIG} --ldflags`
+VTA_TARGET := $(shell ${VTA_CONFIG} --target)
 
 UNAME_S := $(shell uname -s)
 
@@ -53,29 +21,30 @@ else
 	NO_WHOLE_ARCH= --no-whole-archive
 endif
 
-VTA_LIB_SRC = $(wildcard src/*.cc src/tvm/*.cc)
 
-ifeq ($(VTA_TARGET), pynq)
+VTA_LIB_SRC = $(wildcard src/*.cc)
+
+ifeq (${VTA_TARGET}, pynq)
 	VTA_LIB_SRC += $(wildcard src/pynq/*.cc)
-	LDFLAGS += -L/usr/lib -lsds_lib
-	LDFLAGS += -L/opt/python3.6/lib/python3.6/site-packages/pynq/drivers/
-	LDFLAGS += -L/opt/python3.6/lib/python3.6/site-packages/pynq/lib/
-	LDFLAGS += -l:libdma.so
 endif
 
-ifeq ($(VTA_TARGET), sim)
+ifeq (${VTA_TARGET}, sim)
 	VTA_LIB_SRC += $(wildcard src/sim/*.cc)
 endif
 
 VTA_LIB_OBJ = $(patsubst src/%.cc, build/%.o, $(VTA_LIB_SRC))
 
-all: lib/libvta.so
+all: lib/libvta.so lib/libvta.so.json
 
 build/%.o: src/%.cc
 	@mkdir -p $(@D)
 	$(CXX) $(CFLAGS) -MM -MT build/$*.o $< >build/$*.d
 	$(CXX) -c $(CFLAGS) -c $< -o $@
 
+lib/libvta.so.json: lib/libvta.so
+	@mkdir -p $(@D)
+	${VTA_CONFIG} --cfg-json > $@
+
 lib/libvta.so: $(VTA_LIB_OBJ)
 	@mkdir -p $(@D)
 	$(CXX) $(CFLAGS) -shared -o $@ $(filter %.o, $^) $(LDFLAGS)
diff --git a/vta/make/README.md b/vta/make/README.md
new file mode 100644
index 000000000000..6bf3e6ff9ee4
--- /dev/null
+++ b/vta/make/README.md
@@ -0,0 +1,8 @@
+# VTA Configuration
+
+Each VTA runtime/hardware configuration is specified by config.json file.
+You can copy the config.json to project root and modify the configuration
+before you type make.
+
+The config is going to affect the behavior of python package as well as
+the hardware runtime build.
\ No newline at end of file
diff --git a/vta/make/config.json b/vta/make/config.json
new file mode 100644
index 000000000000..8a7dbcf6a8f7
--- /dev/null
+++ b/vta/make/config.json
@@ -0,0 +1,14 @@
+{
+  "TARGET" : "pynq",
+  "LOG_INP_WIDTH" : 3,
+  "LOG_WGT_WIDTH" : 3,
+  "LOG_ACC_WIDTH" : 5,
+  "LOG_OUT_WIDTH" : 3,
+  "LOG_BATCH" : 0,
+  "LOG_BLOCK_IN" : 4,
+  "LOG_BLOCK_OUT" : 4,
+  "LOG_UOP_BUFF_SIZE" : 15,
+  "LOG_INP_BUFF_SIZE" : 15,
+  "LOG_WGT_BUFF_SIZE" : 15,
+  "LOG_ACC_BUFF_SIZE" : 17
+}
diff --git a/vta/make/config.mk b/vta/make/config.mk
deleted file mode 100644
index 2bf25132e245..000000000000
--- a/vta/make/config.mk
+++ /dev/null
@@ -1,99 +0,0 @@
-#-------------------------------------------------------------------------------
-#  Template configuration for compiling VTA runtime.
-#
-#  If you want to change the configuration, please use the following
-#  steps. Assume you are on the root directory of nnvm. First copy the this
-#  file so that any local changes will be ignored by git
-#
-#  $ cp make/config.mk .
-#
-#  Next modify the according entries, and then compile by
-#
-#  $ make
-#
-#  or build in parallel with 8 threads
-#
-#  $ make -j8
-#-------------------------------------------------------------------------------
-
-#---------------------
-# choice of compiler
-#--------------------
-
-# the additional link flags you want to add
-ADD_LDFLAGS=
-
-# the additional compile flags you want to add
-ADD_CFLAGS=
-
-# the hardware target, can be [sim, pynq]
-VTA_TARGET = pynq
-
-#---------------------
-# VTA hardware parameters
-#--------------------
-
-#  Log of input/activation width in bits (default 3 -> 8 bits)
-VTA_LOG_INP_WIDTH = 3
-#  Log of kernel weight width in bits (default 3 -> 8 bits)
-VTA_LOG_WGT_WIDTH = 3
-#  Log of accum width in bits (default 5 -> 32 bits)
-VTA_LOG_ACC_WIDTH = 5
-#  Log of tensor batch size (A in (A,B)x(B,C) matrix multiplication)
-VTA_LOG_BATCH = 0
-#  Log of tensor inner block size (B in (A,B)x(B,C) matrix multiplication)
-VTA_LOG_BLOCK_IN = 4
-#  Log of tensor outer block size (C in (A,B)x(B,C) matrix multiplication)
-VTA_LOG_BLOCK_OUT = 4
-#  Log of uop buffer size in Bytes
-VTA_LOG_UOP_BUFF_SIZE = 15
-#  Log of inp buffer size in Bytes
-VTA_LOG_INP_BUFF_SIZE = 15
-#  Log of wgt buffer size in Bytes
-VTA_LOG_WGT_BUFF_SIZE = 15
-#  Log of acc buffer size in Bytes
-VTA_LOG_ACC_BUFF_SIZE = 17
-
-
-#---------------------
-# Derived VTA hardware parameters
-#--------------------
-
-#  Input width in bits
-VTA_INP_WIDTH = $(shell echo "$$(( 1 << $(VTA_LOG_INP_WIDTH) ))" )
-#  Weight width in bits
-VTA_WGT_WIDTH = $(shell echo "$$(( 1 << $(VTA_LOG_WGT_WIDTH) ))" )
-#  Log of output width in bits
-VTA_LOG_OUT_WIDTH = $(VTA_LOG_INP_WIDTH)
-#  Output width in bits
-VTA_OUT_WIDTH = $(shell echo "$$(( 1 << $(VTA_LOG_OUT_WIDTH) ))" )
-#  Tensor batch size
-VTA_BATCH = $(shell echo "$$(( 1 << $(VTA_LOG_BATCH) ))" )
-#  Tensor outer block size
-VTA_IN_BLOCK = $(shell echo "$$(( 1 << $(VTA_LOG_BLOCK_IN) ))" )
-#  Tensor inner block size
-VTA_OUT_BLOCK = $(shell echo "$$(( 1 << $(VTA_LOG_BLOCK_OUT) ))" )
-#  Uop buffer size in Bytes
-VTA_UOP_BUFF_SIZE = $(shell echo "$$(( 1 << $(VTA_LOG_UOP_BUFF_SIZE) ))" )
-#  Inp buffer size in Bytes
-VTA_INP_BUFF_SIZE = $(shell echo "$$(( 1 << $(VTA_LOG_INP_BUFF_SIZE) ))" )
-#  Wgt buffer size in Bytes
-VTA_WGT_BUFF_SIZE = $(shell echo "$$(( 1 << $(VTA_LOG_WGT_BUFF_SIZE) ))" )
-#  Acc buffer size in Bytes
-VTA_ACC_BUFF_SIZE = $(shell echo "$$(( 1 << $(VTA_LOG_ACC_BUFF_SIZE) ))" )
-#  Log of out buffer size in Bytes
-VTA_LOG_OUT_BUFF_SIZE = \
-$(shell echo "$$(( $(VTA_LOG_ACC_BUFF_SIZE) + $(VTA_LOG_OUT_WIDTH) - $(VTA_LOG_ACC_WIDTH) ))" )
-#  Out buffer size in Bytes
-VTA_OUT_BUFF_SIZE = $(shell echo "$$(( 1 << $(VTA_LOG_OUT_BUFF_SIZE) ))" )
-
-# Update ADD_CFLAGS
-ADD_CFLAGS +=
-	-DVTA_TARGET=$(VTA_TARGET)\
-	-DVTA_LOG_WGT_WIDTH=$(VTA_LOG_WGT_WIDTH) -DVTA_LOG_INP_WIDTH=$(VTA_LOG_INP_WIDTH) \
-	-DVTA_LOG_ACC_WIDTH=$(VTA_LOG_ACC_WIDTH) -DVTA_LOG_OUT_WIDTH=$(VTA_LOG_OUT_WIDTH) \
-	-DVTA_LOG_BATCH=$(VTA_LOG_BATCH) \
-	-DVTA_LOG_BLOCK_IN=$(VTA_LOG_BLOCK_IN) -DVTA_LOG_BLOCK_OUT=$(VTA_LOG_BLOCK_OUT) \
-	-DVTA_LOG_UOP_BUFF_SIZE=$(VTA_LOG_UOP_BUFF_SIZE) -DVTA_LOG_INP_BUFF_SIZE=$(VTA_LOG_INP_BUFF_SIZE) \
-	-DVTA_LOG_WGT_BUFF_SIZE=$(VTA_LOG_WGT_BUFF_SIZE) -DVTA_LOG_ACC_BUFF_SIZE=$(VTA_LOG_ACC_BUFF_SIZE) \
-	-DVTA_LOG_OUT_BUFF_SIZE=$(VTA_LOG_OUT_BUFF_SIZE)
diff --git a/vta/make/vta_config.py b/vta/make/vta_config.py
new file mode 100644
index 000000000000..a25a9ca8d51c
--- /dev/null
+++ b/vta/make/vta_config.py
@@ -0,0 +1,65 @@
+"""VTA config tool"""
+import os
+import sys
+import json
+import argparse
+
+def get_pkg_config(cfg):
+    """Get the pkg config object."""
+    curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
+    proj_root = os.path.abspath(os.path.join(curr_path, "../"))
+    pkg_config_py = os.path.join(proj_root, "python/vta/pkg_config.py")
+    libpkg = {"__file__": pkg_config_py}
+    exec(compile(open(pkg_config_py, "rb").read(), pkg_config_py, "exec"), libpkg, libpkg)
+    PkgConfig = libpkg["PkgConfig"]
+    return PkgConfig(cfg, proj_root)
+
+
+def main():
+    """Main funciton"""
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--cflags", action="store_true",
+                        help="print the cflags")
+    parser.add_argument("--update", action="store_true",
+                        help="Print out the json option.")
+    parser.add_argument("--ldflags", action="store_true",
+                        help="print the cflags")
+    parser.add_argument("--cfg-json", action="store_true",
+                        help="print all the config json")
+    parser.add_argument("--target", action="store_true",
+                        help="print the target")
+    args = parser.parse_args()
+
+    if len(sys.argv) == 1:
+        parser.print_help()
+        return
+
+    curr_path = os.path.dirname(
+        os.path.abspath(os.path.expanduser(__file__)))
+    proj_root = os.path.abspath(os.path.join(curr_path, "../"))
+    path_list = [
+        os.path.join(proj_root, "config.json"),
+        os.path.join(proj_root, "make/config.json")
+    ]
+    ok_path_list = [p for p in path_list if os.path.exists(p)]
+    if not ok_path_list:
+        raise RuntimeError("Cannot find config in %s" % str(path_list))
+    cfg = json.load(open(ok_path_list[0]))
+    cfg["LOG_OUT_WIDTH"] = cfg["LOG_INP_WIDTH"]
+    pkg = get_pkg_config(cfg)
+
+    if args.target:
+        print(pkg.target)
+
+    if args.cflags:
+        print(" ".join(pkg.cflags))
+
+    if args.ldflags:
+        print(" ".join(pkg.ldflags))
+
+    if args.cfg_json:
+        print(pkg.cfg_json)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/vta/python/vta/__init__.py b/vta/python/vta/__init__.py
index 80091f80d164..d0a3279f1915 100644
--- a/vta/python/vta/__init__.py
+++ b/vta/python/vta/__init__.py
@@ -1,15 +1,13 @@
 """TVM-based VTA Compiler Toolchain"""
 from __future__ import absolute_import as _abs
-import sys
+
 
 from .environment import get_env, Environment
+from . import arm_conv2d, vta_conv2d
+from .build_module import build_config, lower, build
+from .rpc_client import reconfig_runtime, program_fpga
 
 try:
-    # allow optional import in config mode.
-    from . import arm_conv2d, vta_conv2d
-    from .build_module import build_config, lower, build
-    from .rpc_client import reconfig_runtime, program_fpga
-
     from . import graph
 except (ImportError, RuntimeError):
     pass
diff --git a/vta/python/vta/environment.py b/vta/python/vta/environment.py
index a59e66a564b2..36139a39c8a9 100644
--- a/vta/python/vta/environment.py
+++ b/vta/python/vta/environment.py
@@ -3,10 +3,11 @@
 from __future__ import absolute_import as _abs
 
 import os
-import glob
+import json
 import copy
 import tvm
 from . import intrin
+from .pkg_config import PkgConfig
 
 
 class DevContext(object):
@@ -61,45 +62,6 @@ def get_task_qid(self, qid):
         return 1 if self.DEBUG_NO_SYNC else qid
 
 
-class PkgConfig(object):
-    """Simple package config tool for VTA.
-
-    This is used to provide runtime specific configurations.
-    """
-    def __init__(self, env):
-        curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
-        proj_root = os.path.abspath(os.path.join(curr_path, "../../"))
-        # include path
-        self.include_path = [
-            "-I%s/include" % proj_root,
-            "-I%s/nnvm/tvm/include" % proj_root,
-            "-I%s/nnvm/tvm/dlpack/include" % proj_root,
-            "-I%s/nnvm/dmlc-core/include" % proj_root
-        ]
-        # List of source files that can be used to build standalone library.
-        self.lib_source = []
-        self.lib_source += glob.glob("%s/src/*.cc" % proj_root)
-        self.lib_source += glob.glob("%s/src/%s/*.cc" % (proj_root, env.TARGET))
-        # macro keys
-        self.macro_defs = []
-        for key in env.cfg_keys:
-            self.macro_defs.append("-DVTA_%s=%s" % (key, str(getattr(env, key))))
-
-        if env.TARGET == "pynq":
-            self.ldflags = [
-                "-L/usr/lib",
-                "-lsds_lib",
-                "-L/opt/python3.6/lib/python3.6/site-packages/pynq/drivers/",
-                "-L/opt/python3.6/lib/python3.6/site-packages/pynq/lib/",
-                "-l:libdma.so"]
-        else:
-            self.ldflags = []
-
-    @property
-    def cflags(self):
-        return self.include_path + self.macro_defs
-
-
 class Environment(object):
     """Hareware configuration object.
 
@@ -123,19 +85,6 @@ class Environment(object):
           env = vta.get_env()
     """
     current = None
-    cfg_keys = [
-        "TARGET",
-        "LOG_INP_WIDTH",
-        "LOG_WGT_WIDTH",
-        "LOG_ACC_WIDTH",
-        "LOG_BATCH",
-        "LOG_BLOCK_IN",
-        "LOG_BLOCK_OUT",
-        "LOG_UOP_BUFF_SIZE",
-        "LOG_INP_BUFF_SIZE",
-        "LOG_WGT_BUFF_SIZE",
-        "LOG_ACC_BUFF_SIZE",
-    ]
     # constants
     MAX_XFER = 1 << 22
     # debug flags
@@ -152,10 +101,9 @@ class Environment(object):
     def __init__(self, cfg):
         # Log of input/activation width in bits
         self.__dict__.update(cfg)
-        for key in self.cfg_keys:
+        for key in PkgConfig.cfg_keys:
             if key not in cfg:
                 raise ValueError("Expect key %s in cfg" % key)
-        self.LOG_OUT_WIDTH = self.LOG_INP_WIDTH
         self.LOG_OUT_BUFF_SIZE = (
             self.LOG_ACC_BUFF_SIZE +
             self.LOG_OUT_WIDTH -
@@ -195,7 +143,12 @@ def __init__(self, cfg):
         self.mock_mode = False
         self._mock_env = None
         self._dev_ctx = None
-        self._pkg_config = None
+
+    def pkg_config(self):
+        """PkgConfig instance"""
+        curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
+        proj_root = os.path.abspath(os.path.join(curr_path, "../../"))
+        return PkgConfig(self.__dict__, proj_root)
 
     @property
     def dev(self):
@@ -204,13 +157,6 @@ def dev(self):
             self._dev_ctx = DevContext(self)
         return self._dev_ctx
 
-    @property
-    def pkg_config(self):
-        """PkgConfig instance"""
-        if self._pkg_config is None:
-            self._pkg_config = PkgConfig(self)
-        return self._pkg_config
-
     @property
     def mock(self):
         """A mock version of the Environment
@@ -327,30 +273,19 @@ def coproc_dep_pop(op):
 
 def _init_env():
     """Iniitalize the default global env"""
-    python_vta_dir = os.path.dirname(__file__)
-    filename = os.path.join(python_vta_dir, '../../config.mk')
-    keys = set()
-
-    for k in Environment.cfg_keys:
-        keys.add("VTA_" + k)
-
-    if not os.path.isfile(filename):
+    curr_path = os.path.dirname(
+        os.path.abspath(os.path.expanduser(__file__)))
+    proj_root = os.path.abspath(os.path.join(curr_path, "../../"))
+    path_list = [
+        os.path.join(curr_path, "config.json"),
+        os.path.join(proj_root, "config.json"),
+        os.path.join(proj_root, "make/config.json")
+    ]
+    path_list = [p for p in path_list if os.path.exists(p)]
+    if not path_list:
         raise RuntimeError(
-            "Error: {} not found.make sure you have config.mk in your vta root"
+            "Error: {} not found.make sure you have config.json in your vta root"
             .format(filename))
-
-    cfg = {}
-    with open(filename) as f:
-        for line in f:
-            for k in keys:
-                if k  +" =" in line:
-                    val = line.split("=")[1].strip()
-                    if k.startswith("VTA_"):
-                        k = k[4:]
-                    try:
-                        cfg[k] = int(val)
-                    except ValueError:
-                        cfg[k] = val
-    return Environment(cfg)
+    return Environment(json.load(open(path_list[0])))
 
 Environment.current = _init_env()
diff --git a/vta/python/vta/exec/rpc_server.py b/vta/python/vta/exec/rpc_server.py
index ca99416f7818..014b40564d4d 100644
--- a/vta/python/vta/exec/rpc_server.py
+++ b/vta/python/vta/exec/rpc_server.py
@@ -8,11 +8,13 @@
 import argparse
 import os
 import ctypes
+import json
 import tvm
 from tvm._ffi.base import c_str
 from tvm.contrib import rpc, cc
 
 from ..environment import get_env
+from ..pkg_config import PkgConfig
 
 
 @tvm.register_func("tvm.contrib.rpc.server.start", override=True)
@@ -21,8 +23,9 @@ def server_start():
     # pylint: disable=unused-variable
     curr_path = os.path.dirname(
         os.path.abspath(os.path.expanduser(__file__)))
-    dll_path = os.path.abspath(
-        os.path.join(curr_path, "../../../lib/libvta.so"))
+    proj_root = os.path.abspath(os.path.join(curr_path, "../../.."))
+    dll_path = os.path.abspath(os.path.join(proj_root, "lib/libvta.so"))
+    cfg_path = os.path.abspath(os.path.join(proj_root, "lib/libvta.so.json"))
     runtime_dll = []
     _load_module = tvm.get_global_func("tvm.contrib.rpc.server.load_module")
 
@@ -56,26 +59,36 @@ def server_shutdown():
             runtime_dll.pop()
 
     @tvm.register_func("tvm.contrib.vta.reconfig_runtime", override=True)
-    def reconfig_runtime(cflags):
+    def reconfig_runtime(cfg_json):
         """Rebuild and reload runtime with new configuration.
 
         Parameters
         ----------
         cfg_json : str
-        JSON string used for configurations.
+            JSON string used for configurations.
         """
         if runtime_dll:
             raise RuntimeError("Can only reconfig in the beginning of session...")
-        cflags = cflags.split()
         env = get_env()
+        cfg = json.loads(cfg_json)
+        cfg["TARGET"] = env.TARGET
+        pkg = PkgConfig(cfg, proj_root)
+        # check if the configuration is already the same
+        if os.path.isfile(cfg_path):
+            old_cfg = json.load(open(cfg_path))
+            if pkg.same_config(old_cfg):
+                logging.info("Skip reconfiguration because runtime config is the same")
+                return
         cflags += ["-O2", "-std=c++11"]
-        cflags += env.pkg_config.include_path
-        ldflags = env.pkg_config.ldflags
+        cflags += pkg.cflags
+        ldflags = pkg.ldflags
         lib_name = dll_path
         source = env.pkg_config.lib_source
         logging.info("Rebuild runtime: output=%s, cflags=%s, source=%s, ldflags=%s",
                      dll_path, str(cflags), str(source), str(ldflags))
         cc.create_shared(lib_name, source, cflags + ldflags)
+        with open(cfg_path, "w") as outputfile:
+            json.dump(pkg.cfg_json, outputfile)
 
 
 def main():
diff --git a/vta/python/vta/pkg_config.py b/vta/python/vta/pkg_config.py
new file mode 100644
index 000000000000..36655982dbe9
--- /dev/null
+++ b/vta/python/vta/pkg_config.py
@@ -0,0 +1,94 @@
+"""VTA Package configuration module
+
+This module is dependency free and can be used to configure package.
+"""
+from __future__ import absolute_import as _abs
+
+import json
+import glob
+
+class PkgConfig(object):
+    """Simple package config tool for VTA.
+
+    This is used to provide runtime specific configurations.
+
+    Parameters
+    ----------
+    cfg : dict
+        The config dictionary
+
+    proj_root : str
+        Path to the project root
+    """
+    cfg_keys = [
+        "TARGET",
+        "LOG_INP_WIDTH",
+        "LOG_WGT_WIDTH",
+        "LOG_ACC_WIDTH",
+        "LOG_OUT_WIDTH",
+        "LOG_BATCH",
+        "LOG_BLOCK_IN",
+        "LOG_BLOCK_OUT",
+        "LOG_UOP_BUFF_SIZE",
+        "LOG_INP_BUFF_SIZE",
+        "LOG_WGT_BUFF_SIZE",
+        "LOG_ACC_BUFF_SIZE",
+    ]
+    def __init__(self, cfg, proj_root):
+        # include path
+        self.include_path = [
+            "-I%s/include" % proj_root,
+            "-I%s/nnvm/tvm/include" % proj_root,
+            "-I%s/nnvm/tvm/dlpack/include" % proj_root,
+            "-I%s/nnvm/dmlc-core/include" % proj_root
+        ]
+        # List of source files that can be used to build standalone library.
+        self.lib_source = []
+        self.lib_source += glob.glob("%s/src/*.cc" % proj_root)
+        self.lib_source += glob.glob("%s/src/%s/*.cc" % (proj_root, cfg["TARGET"]))
+        # macro keys
+        self.macro_defs = []
+        self.cfg_dict = {}
+        for key in self.cfg_keys:
+            self.macro_defs.append("-DVTA_%s=%s" % (key, str(cfg[key])))
+            self.cfg_dict[key] = cfg[key]
+
+        self.target = cfg["TARGET"]
+
+        if self.target == "pynq":
+            self.ldflags = [
+                "-L/usr/lib",
+                "-lsds_lib",
+                "-L/opt/python3.6/lib/python3.6/site-packages/pynq/drivers/",
+                "-L/opt/python3.6/lib/python3.6/site-packages/pynq/lib/",
+                "-l:libdma.so"]
+        else:
+            self.ldflags = []
+
+    @property
+    def cflags(self):
+        return self.include_path + self.macro_defs
+
+    @property
+    def cfg_json(self):
+        return json.dumps(self.cfg_dict, indent=2)
+
+    def same_config(self, cfg):
+        """Compare if cfg is same as current config.
+
+        Parameters
+        ----------
+        cfg : the configuration
+            The configuration
+
+        Returns
+        -------
+        equal : bool
+            Whether the configuration is the same.
+        """
+        for k, v in self.cfg_dict.items():
+            if k not in cfg:
+                return False
+            if cfg[k] != v:
+                return False
+        return True
diff --git a/vta/python/vta/rpc_client.py b/vta/python/vta/rpc_client.py
index f0a02d7cc045..341130ed7a1c 100644
--- a/vta/python/vta/rpc_client.py
+++ b/vta/python/vta/rpc_client.py
@@ -12,23 +12,8 @@ def reconfig_runtime(remote):
         The TVM RPC session
     """
     env = get_env()
-    keys = ["VTA_LOG_WGT_WIDTH",
-            "VTA_LOG_INP_WIDTH",
-            "VTA_LOG_ACC_WIDTH",
-            "VTA_LOG_OUT_WIDTH",
-            "VTA_LOG_BATCH",
-            "VTA_LOG_BLOCK_IN",
-            "VTA_LOG_BLOCK_OUT",
-            "VTA_LOG_UOP_BUFF_SIZE",
-            "VTA_LOG_INP_BUFF_SIZE",
-            "VTA_LOG_WGT_BUFF_SIZE",
-            "VTA_LOG_ACC_BUFF_SIZE",
-            "VTA_LOG_OUT_BUFF_SIZE"]
-    cflags = []
-    for k in keys:
-        cflags += ["-D%s=%s" % (k, str(getattr(env, k[4:])))]
     freconfig = remote.get_function("tvm.contrib.vta.reconfig_runtime")
-    freconfig(" ".join(cflags))
+    freconfig(env.pkg_config().cfg_json)
 
 
 def program_fpga(remote, bitstream):
diff --git a/vta/src/sim/sim_driver.cc b/vta/src/sim/sim_driver.cc
index a88ab2466a25..57bc21c9805c 100644
--- a/vta/src/sim/sim_driver.cc
+++ b/vta/src/sim/sim_driver.cc
@@ -109,9 +109,12 @@ class DRAM {
    * \return The true virtual address;
    */
   void* GetAddr(uint64_t phy_addr) {
+    CHECK_NE(phy_addr, 0)
+        << "trying to get address that is nullptr";
     std::lock_guard<std::mutex> lock(mutex_);
     uint64_t loc = (phy_addr >> kPageBits) - 1;
-    CHECK_LT(loc, ptable_.size());
+    CHECK_LT(loc, ptable_.size())
+        << "phy_addr=" << phy_addr;
     Page* p = ptable_[loc];
     CHECK(p != nullptr);
     size_t offset = (loc - p->ptable_begin) << kPageBits;
@@ -173,7 +176,7 @@ class DRAM {
 
  private:
   // The bits in page table
-  static constexpr vta_phy_addr_t kPageBits = 16;
+  static constexpr vta_phy_addr_t kPageBits = 12;
   // page size, also the maximum allocable size 16 K
   static constexpr vta_phy_addr_t kPageSize = 1 << kPageBits;
   /*! \brief A page in the DRAM */
@@ -388,6 +391,7 @@ class Device {
   }
 
   void RunStore(const VTAMemInsn* op) {
+    if (op->x_size == 0) return;
     if (op->memory_type == VTA_MEM_ID_ACC ||
         op->memory_type == VTA_MEM_ID_UOP) {
       prof_->out_store_nbytes += (
diff --git a/vta/tests/python/integration/test_benchmark_gemm.py b/vta/tests/python/integration/test_benchmark_gemm.py
new file mode 100644
index 000000000000..b651fad0f251
--- /dev/null
+++ b/vta/tests/python/integration/test_benchmark_gemm.py
@@ -0,0 +1,257 @@
+import tvm
+import numpy as np
+from tvm.contrib import util
+import vta.testing
+
+
+def test_gemm():
+    def run_gemm_packed(env, remote, batch_size, channel, block):
+        data_shape = (batch_size // env.BATCH,
+                      channel // env.BLOCK_IN,
+                      env.BATCH,
+                      env.BLOCK_IN)
+        weight_shape = (channel // env.BLOCK_OUT,
+                        channel // env.BLOCK_IN,
+                        env.BLOCK_OUT,
+                        env.BLOCK_IN)
+        res_shape = (batch_size // env.BATCH,
+                     channel // env.BLOCK_OUT,
+                     env.BATCH,
+                     env.BLOCK_OUT)
+        num_ops = channel * channel * batch_size
+
+        ko = tvm.reduce_axis((0, channel // env.BLOCK_IN), name='ko')
+        ki = tvm.reduce_axis((0, env.BLOCK_IN), name='ki')
+
+        data = tvm.placeholder(data_shape,
+                               name="data",
+                               dtype=env.inp_dtype)
+        weight = tvm.placeholder(weight_shape,
+                                 name="weight",
+                                 dtype=env.wgt_dtype)
+        data_buf = tvm.compute(data_shape,
+                               lambda *i: data(*i),
+                               "data_buf")
+        weight_buf = tvm.compute(weight_shape,
+                                 lambda *i: weight(*i),
+                                 "weight_buf")
+        res_gem = tvm.compute(res_shape,
+                              lambda bo, co, bi, ci: tvm.sum(
+                                  data_buf[bo, ko, bi, ki].astype(env.acc_dtype) *
+                                  weight_buf[co, ko, ci, ki].astype(env.acc_dtype),
+                                  axis=[ko, ki]),
+                              name="res_gem")
+        res_shf = tvm.compute(res_shape,
+                              lambda *i: res_gem(*i)>>8,
+                            name="res_shf")
+        res_max = tvm.compute(res_shape,
+                              lambda *i: tvm.max(res_shf(*i), 0),
+                              "res_max") #relu
+        res_min = tvm.compute(res_shape,
+                              lambda *i: tvm.min(res_max(*i), (1<<(env.INP_WIDTH-1))-1),
+                              "res_min") #relu
+        res = tvm.compute(res_shape,
+                          lambda *i: res_min(*i).astype(env.inp_dtype),
+                          name="res")
+
+        def verify(s, check_correctness=True):
+            mod = vta.build(s, [data, weight, res],
+                            "ext_dev", env.target_host, name="gemm")
+            temp = util.tempdir()
+            mod.save(temp.relpath("gemm.o"))
+            remote.upload(temp.relpath("gemm.o"))
+            f = remote.load_module("gemm.o")
+            # verify
+            ctx = remote.ext_dev(0)
+            # Data in original format
+            data_orig = np.random.randint(
+                -128, 128, size=(batch_size, channel)).astype(data.dtype)
+            weight_orig = np.random.randint(
+                -128, 128, size=(channel, channel)).astype(weight.dtype)
+            data_packed = data_orig.reshape(
+                batch_size // env.BATCH, env.BATCH,
+                channel // env.BLOCK_IN, env.BLOCK_IN).transpose((0, 2, 1, 3))
+            weight_packed = weight_orig.reshape(
+                channel // env.BLOCK_OUT, env.BLOCK_OUT,
+                channel // env.BLOCK_IN, env.BLOCK_IN).transpose((0, 2, 1, 3))
+            res_np = np.zeros(res_shape).astype(res.dtype)
+            data_arr = tvm.nd.array(data_packed, ctx)
+            weight_arr = tvm.nd.array(weight_packed, ctx)
+            res_arr = tvm.nd.array(res_np, ctx)
+            res_ref = np.zeros(res_shape).astype(env.acc_dtype)
+            for b in range(batch_size // env.BATCH):
+                for i in range(channel // env.BLOCK_OUT):
+                    for j in range(channel // env.BLOCK_IN):
+                        res_ref[b,i,:] += np.dot(data_packed[b,j,:].astype(env.acc_dtype),
+                                                 weight_packed[i,j].T.astype(env.acc_dtype))
+            res_ref = np.right_shift(res_ref, 8)
+            res_ref = np.clip(res_ref, 0, (1<<(env.INP_WIDTH-1))-1).astype(res.dtype)
+            time_f = f.time_evaluator("gemm", ctx, number=20)
+            cost = time_f(data_arr, weight_arr, res_arr)
+            res_unpack = res_arr.asnumpy().reshape(batch_size // env.BATCH,
+                                                   channel // env.BLOCK_OUT,
+                                                   env.BATCH,
+                                                   env.BLOCK_OUT)
+            if check_correctness:
+                np.testing.assert_allclose(res_unpack, res_ref)
+            return cost
+
+        def run_schedule(load_inp,
+                         load_wgt,
+                         gemm,
+                         alu,
+                         store_out,
+                         print_ir,
+                         check_correctness):
+            s = tvm.create_schedule(res.op)
+            s[data_buf].set_scope(env.inp_scope)
+            s[weight_buf].set_scope(env.wgt_scope)
+            s[res_gem].set_scope(env.acc_scope)
+            s[res_shf].set_scope(env.acc_scope)
+            s[res_min].set_scope(env.acc_scope)
+            s[res_max].set_scope(env.acc_scope)
+
+            if block:
+                bblock = block // env.BATCH
+                iblock = block // env.BLOCK_IN
+                oblock = block // env.BLOCK_OUT
+                xbo, xco, xbi, xci = s[res].op.axis
+                xb1, xco1, xb2, xco2 = s[res].tile(xbo, xco, bblock, oblock)
+                store_pt = xb2
+
+                s[res_gem].compute_at(s[res], xco1)
+                s[res_shf].compute_at(s[res], xco1)
+                s[res_min].compute_at(s[res], xco1)
+                s[res_max].compute_at(s[res], xco1)
+
+                xbo, xco, xbi, xci = s[res_gem].op.axis
+                # Compute one line at a time
+                ko1, ko2 = s[res_gem].split(ko, iblock)
+                s[res_gem].reorder(ko1, ko2, xbo, xco, xbi, xci, ki)
+                s[data_buf].compute_at(s[res_gem], ko1)
+                s[weight_buf].compute_at(s[res_gem], ko1)
+                # Use VTA instructions
+                s[data_buf].pragma(s[data_buf].op.axis[0], load_inp)
+                s[weight_buf].pragma(s[weight_buf].op.axis[0], load_wgt)
+                s[res_gem].tensorize(xbi, gemm)
+                s[res_shf].pragma(s[res_shf].op.axis[0], alu)
+                s[res_min].pragma(s[res_min].op.axis[0], alu)
+                s[res_max].pragma(s[res_max].op.axis[0], alu)
+                s[res].pragma(store_pt, store_out)
+            else:
+                xbo, xco, xbi, xci = s[res_gem].op.axis
+                s[res_gem].reorder(ko, xbo, xco, xbi, xci, ki)
+                # Use VTA instructions
+                s[data_buf].pragma(s[data_buf].op.axis[0], load_inp)
+                s[weight_buf].pragma(s[weight_buf].op.axis[0], load_wgt)
+                s[res_gem].tensorize(xbi, gemm)
+                s[res_shf].pragma(s[res_shf].op.axis[0], alu)
+                s[res_min].pragma(s[res_min].op.axis[0], alu)
+                s[res_max].pragma(s[res_max].op.axis[0], alu)
+                s[res].pragma(s[res].op.axis[0], store_out)
+
+
+            if print_ir:
+                print(tvm.lower(s, [data, weight, res], simple_mode=True))
+            return verify(s, check_correctness)
+
+        def gemm_normal(print_ir):
+            mock = env.mock
+            print("----- GEMM GFLOPS End-to-End Test-------")
+            def run_test(header, print_ir, check_correctness):
+                cost = run_schedule(
+                    env.dma_copy, env.dma_copy, env.gemm, env.alu, env.dma_copy,
+                    print_ir, check_correctness)
+                gops = (num_ops / cost.mean) / float(10 ** 9)
+                print(header)
+                print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
+            with vta.build_config():
+                run_test("NORMAL", print_ir, True)
+
+        def gevm_unittest(print_ir):
+            mock = env.mock
+            print("----- GEMM Unit Test-------")
+            def run_test(header, print_ir):
+                cost = run_schedule(
+                    mock.dma_copy, mock.dma_copy, env.gemm, mock.alu, mock.dma_copy,
+                    print_ir, False)
+                gops = (num_ops / cost.mean) / float(10 ** 9)
+                print(header)
+                print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
+            with vta.build_config():
+                run_test("NORMAL", print_ir)
+
+        def alu_unittest(print_ir):
+            mock = env.mock
+            print("----- ALU Unit Test-------")
+            def run_test(header, print_ir):
+                cost = run_schedule(
+                    mock.dma_copy, mock.dma_copy, mock.gemm, env.alu, mock.dma_copy,
+                    print_ir, False)
+                gops = (num_ops / cost.mean) / float(10 ** 9)
+                print(header)
+                print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
+            with vta.build_config():
+                run_test("NORMAL", print_ir)
+            print("")
+
+        def load_inp_unittest(print_ir):
+            mock = env.mock
+            print("----- LoadInp Unit Test-------")
+            def run_test(header, print_ir):
+                cost = run_schedule(
+                    env.dma_copy, mock.dma_copy, mock.gemm, mock.alu, mock.dma_copy, print_ir, False)
+                gops = (num_ops / cost.mean) / float(10 ** 9)
+                bandwith = (batch_size * channel * env.INP_WIDTH / cost.mean) / float(10 ** 9)
+                print(header)
+                print("\tTime cost = %g sec/op, %g GFLOPS, bandwidth=%g Gbits" % (
+                    cost.mean, gops, bandwith))
+            with vta.build_config():
+                run_test("NORMAL", print_ir)
+            print("")
+
+        def load_wgt_unittest(print_ir):
+            mock = env.mock
+            print("----- LoadWgt Unit Test-------")
+            def run_test(header, print_ir):
+                cost = run_schedule(
+                    mock.dma_copy, env.dma_copy, mock.gemm, mock.alu, mock.dma_copy, print_ir, False)
+                gops = (num_ops / cost.mean) / float(10 ** 9)
+                bandwith = (channel * channel * env.WGT_WIDTH / cost.mean) / float(10 ** 9)
+                print(header)
+                print("\tTime cost = %g sec/op, %g GFLOPS, bandwidth=%g Gbits" % (
+                    cost.mean, gops, bandwith))
+            with vta.build_config():
+                run_test("NORMAL", print_ir)
+            print("")
+
+        def store_out_unittest(print_ir):
+            mock = env.mock
+            print("----- StoreOut Unit Test-------")
+            def run_test(header, print_ir):
+                cost = run_schedule(
+                    mock.dma_copy, mock.dma_copy, mock.gemm, mock.alu, env.dma_copy,
+                    print_ir, False)
+                gops = (num_ops / cost.mean) / float(10 ** 9)
+                bandwith = (batch_size * channel * env.OUT_WIDTH / cost.mean) / float(10 ** 9)
+                print(header)
+                print("\tTime cost = %g sec/op, %g GFLOPS, bandwidth=%g Gbits" % (
+                    cost.mean, gops, bandwith))
+            with vta.build_config():
+                run_test("NORMAL", print_ir)
+            print("")
+
+
+
+        gemm_normal(False)
+        gevm_unittest(False)
+        alu_unittest(False)
+
+    def _run(env, remote):
+        print("========GEMM 128=========")
+        run_gemm_packed(env, remote, 128, 128, 128)
+
+    vta.testing.run(_run)
+
+if __name__ == "__main__":
+    test_gemm()
diff --git a/vta/tests/python/pynq/test_benchmark_gemm.py b/vta/tests/python/pynq/test_benchmark_gemm.py
deleted file mode 100644
index 0f1d4d217fe4..000000000000
--- a/vta/tests/python/pynq/test_benchmark_gemm.py
+++ /dev/null
@@ -1,266 +0,0 @@
-import os
-import tvm
-import vta
-import numpy as np
-import time
-from tvm.contrib import rpc, util
-
-host = "pynq"
-port = 9091
-target = "llvm -target=armv7-none-linux-gnueabihf"
-
-
-def test_gemm_packed(batch_size, channel, block):
-    env = vta.get_env()
-    data_shape = (batch_size // env.BATCH,
-                  channel // env.BLOCK_IN,
-                  env.BATCH,
-                  env.BLOCK_IN)
-    weight_shape = (channel // env.BLOCK_OUT,
-                    channel // env.BLOCK_IN,
-                    env.BLOCK_OUT,
-                    env.BLOCK_IN)
-    res_shape = (batch_size // env.BATCH,
-                 channel // env.BLOCK_OUT,
-                 env.BATCH,
-                 env.BLOCK_OUT)
-    num_ops = channel * channel * batch_size
-
-    ko = tvm.reduce_axis((0, channel // env.BLOCK_IN), name='ko')
-    ki = tvm.reduce_axis((0, env.BLOCK_IN), name='ki')
-
-    data = tvm.placeholder(data_shape,
-                           name="data",
-                           dtype=env.inp_dtype)
-    weight = tvm.placeholder(weight_shape,
-                             name="weight",
-                             dtype=env.wgt_dtype)
-    data_buf = tvm.compute(data_shape,
-                           lambda *i: data(*i),
-                           "data_buf")
-    weight_buf = tvm.compute(weight_shape,
-                             lambda *i: weight(*i),
-                             "weight_buf")
-    res_gem = tvm.compute(res_shape,
-                          lambda bo, co, bi, ci: tvm.sum(
-                              data_buf[bo, ko, bi, ki].astype(env.acc_dtype) *
-                              weight_buf[co, ko, ci, ki].astype(env.acc_dtype),
-                              axis=[ko, ki]),
-                          name="res_gem")
-    res_shf = tvm.compute(res_shape,
-                          lambda *i: res_gem(*i)>>8,
-                          name="res_shf")
-    res_max = tvm.compute(res_shape,
-                          lambda *i: tvm.max(res_shf(*i), 0),
-                          "res_max") #relu
-    res_min = tvm.compute(res_shape,
-                          lambda *i: tvm.min(res_max(*i), (1<<(env.INP_WIDTH-1))-1),
-                          "res_min") #relu
-    res = tvm.compute(res_shape,
-                      lambda *i: res_min(*i).astype(env.inp_dtype),
-                      name="res")
-
-    def verify(s, check_correctness=True):
-        mod = vta.build(s, [data, weight, res], "ext_dev", target, name="gemm")
-        temp = util.tempdir()
-        remote = rpc.connect(host, port)
-        mod.save(temp.relpath("gemm.o"))
-        remote.upload(temp.relpath("gemm.o"))
-        f = remote.load_module("gemm.o")
-        # verify
-        ctx = remote.ext_dev(0)
-        # Data in original format
-        data_orig = np.random.randint(
-            -128, 128, size=(batch_size, channel)).astype(data.dtype)
-        weight_orig = np.random.randint(
-            -128, 128, size=(channel, channel)).astype(weight.dtype)
-        data_packed = data_orig.reshape(
-            batch_size // env.BATCH, env.BATCH,
-            channel // env.BLOCK_IN, env.BLOCK_IN).transpose((0, 2, 1, 3))
-        weight_packed = weight_orig.reshape(
-            channel // env.BLOCK_OUT, env.BLOCK_OUT,
-            channel // env.BLOCK_IN, env.BLOCK_IN).transpose((0, 2, 1, 3))
-        res_np = np.zeros(res_shape).astype(res.dtype)
-        data_arr = tvm.nd.array(data_packed, ctx)
-        weight_arr = tvm.nd.array(weight_packed, ctx)
-        res_arr = tvm.nd.array(res_np, ctx)
-        res_ref = np.zeros(res_shape).astype(env.acc_dtype)
-        for b in range(batch_size // env.BATCH):
-            for i in range(channel // env.BLOCK_OUT):
-                for j in range(channel // env.BLOCK_IN):
-                    res_ref[b,i,:] += np.dot(data_packed[b,j,:].astype(env.acc_dtype),
-                                             weight_packed[i,j].T.astype(env.acc_dtype))
-        res_ref = np.right_shift(res_ref, 8)
-        res_ref = np.clip(res_ref, 0, (1<<(env.INP_WIDTH-1))-1).astype(res.dtype)
-        time_f = f.time_evaluator("gemm", ctx, number=20)
-        cost = time_f(data_arr, weight_arr, res_arr)
-        res_unpack = res_arr.asnumpy().reshape(batch_size // env.BATCH,
-                                               channel // env.BLOCK_OUT,
-                                               env.BATCH,
-                                               env.BLOCK_OUT)
-        if check_correctness:
-            np.testing.assert_allclose(res_unpack, res_ref)
-        return cost
-
-    def run_schedule(load_inp,
-                     load_wgt,
-                     gemm,
-                     alu,
-                     store_out,
-                     print_ir,
-                     check_correctness):
-        s = tvm.create_schedule(res.op)
-        s[data_buf].set_scope(env.inp_scope)
-        s[weight_buf].set_scope(env.wgt_scope)
-        s[res_gem].set_scope(env.acc_scope)
-        s[res_shf].set_scope(env.acc_scope)
-        s[res_min].set_scope(env.acc_scope)
-        s[res_max].set_scope(env.acc_scope)
-
-        if block:
-            bblock = block // env.BATCH
-            iblock = block // env.BLOCK_IN
-            oblock = block // env.BLOCK_OUT
-            xbo, xco, xbi, xci = s[res].op.axis
-            xb1, xco1, xb2, xco2 = s[res].tile(xbo, xco, bblock, oblock)
-            store_pt = xb2
-
-            s[res_gem].compute_at(s[res], xco1)
-            s[res_shf].compute_at(s[res], xco1)
-            s[res_min].compute_at(s[res], xco1)
-            s[res_max].compute_at(s[res], xco1)
-
-            xbo, xco, xbi, xci = s[res_gem].op.axis
-            # Compute one line at a time
-            ko1, ko2 = s[res_gem].split(ko, iblock)
-            s[res_gem].reorder(ko1, ko2, xbo, xco, xbi, xci, ki)
-            s[data_buf].compute_at(s[res_gem], ko1)
-            s[weight_buf].compute_at(s[res_gem], ko1)
-            # Use VTA instructions
-            s[data_buf].pragma(s[data_buf].op.axis[0], load_inp)
-            s[weight_buf].pragma(s[weight_buf].op.axis[0], load_wgt)
-            s[res_gem].tensorize(xbi, gemm)
-            s[res_shf].pragma(s[res_shf].op.axis[0], alu)
-            s[res_min].pragma(s[res_min].op.axis[0], alu)
-            s[res_max].pragma(s[res_max].op.axis[0], alu)
-            s[res].pragma(store_pt, store_out)
-        else:
-            xbo, xco, xbi, xci = s[res_gem].op.axis
-            s[res_gem].reorder(ko, xbo, xco, xbi, xci, ki)
-            # Use VTA instructions
-            s[data_buf].pragma(s[data_buf].op.axis[0], load_inp)
-            s[weight_buf].pragma(s[weight_buf].op.axis[0], load_wgt)
-            s[res_gem].tensorize(xbi, gemm)
-            s[res_shf].pragma(s[res_shf].op.axis[0], alu)
-            s[res_min].pragma(s[res_min].op.axis[0], alu)
-            s[res_max].pragma(s[res_max].op.axis[0], alu)
-            s[res].pragma(s[res].op.axis[0], store_out)
-
-        if print_ir:
-            print(tvm.lower(s, [data, weight, res], simple_mode=True))
-        return verify(s, check_correctness)
-
-    def gemm_normal(print_ir):
-        mock = env.mock
-        print("----- GEMM GFLOPS End-to-End Test-------")
-        def run_test(header, print_ir, check_correctness):
-            cost = run_schedule(
-                env.dma_copy, env.dma_copy, env.gemm, env.alu, env.dma_copy,
-                print_ir, check_correctness)
-            gops = (num_ops / cost.mean) / float(10 ** 9)
-            print(header)
-            print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
-        with vta.build_config():
-            run_test("NORMAL", print_ir, True)
-
-        print("")
-
-    def gevm_unittest(print_ir):
-        mock = env.mock
-        print("----- GEMM Unit Test-------")
-        def run_test(header, print_ir):
-            cost = run_schedule(
-                mock.dma_copy, mock.dma_copy, env.gemm, mock.alu, mock.dma_copy,
-                print_ir, False)
-            gops = (num_ops / cost.mean) / float(10 ** 9)
-            print(header)
-            print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
-        with vta.build_config():
-            run_test("NORMAL", print_ir)
-        print("")
-
-    def alu_unittest(print_ir):
-        mock = env.mock
-        print("----- ALU Unit Test-------")
-        def run_test(header, print_ir):
-            cost = run_schedule(
-                mock.dma_copy, mock.dma_copy, mock.gemm, env.alu, mock.dma_copy,
-                print_ir, False)
-            gops = (num_ops / cost.mean) / float(10 ** 9)
-            print(header)
-            print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
-        with vta.build_config():
-            run_test("NORMAL", print_ir)
-        print("")
-
-    def load_inp_unittest(print_ir):
-        mock = env.mock
-        print("----- LoadInp Unit Test-------")
-        def run_test(header, print_ir):
-            cost = run_schedule(
-                env.dma_copy, mock.dma_copy, mock.gemm, mock.alu, mock.dma_copy, print_ir, False)
-            gops = (num_ops / cost.mean) / float(10 ** 9)
-            bandwith = (batch_size * channel * env.INP_WIDTH / cost.mean) / float(10 ** 9)
-            print(header)
-            print("\tTime cost = %g sec/op, %g GFLOPS, bandwidth=%g Gbits" % (
-                cost.mean, gops, bandwith))
-        with vta.build_config():
-            run_test("NORMAL", print_ir)
-        print("")
-
-    def load_wgt_unittest(print_ir):
-        mock = env.mock
-        print("----- LoadWgt Unit Test-------")
-        def run_test(header, print_ir):
-            cost = run_schedule(
-                mock.dma_copy, env.dma_copy, mock.gemm, mock.alu, mock.dma_copy, print_ir, False)
-            gops = (num_ops / cost.mean) / float(10 ** 9)
-            bandwith = (channel * channel * env.WGT_WIDTH / cost.mean) / float(10 ** 9)
-            print(header)
-            print("\tTime cost = %g sec/op, %g GFLOPS, bandwidth=%g Gbits" % (
-                cost.mean, gops, bandwith))
-        with vta.build_config():
-            run_test("NORMAL", print_ir)
-        print("")
-
-    def store_out_unittest(print_ir):
-        mock = env.mock
-        print("----- StoreOut Unit Test-------")
-        def run_test(header, print_ir):
-            cost = run_schedule(
-                mock.dma_copy, mock.dma_copy, mock.gemm, mock.alu, env.dma_copy,
-                print_ir, False)
-            gops = (num_ops / cost.mean) / float(10 ** 9)
-            bandwith = (batch_size * channel * env.OUT_WIDTH / cost.mean) / float(10 ** 9)
-            print(header)
-            print("\tTime cost = %g sec/op, %g GFLOPS, bandwidth=%g Gbits" % (
-                cost.mean, gops, bandwith))
-        with vta.build_config():
-            run_test("NORMAL", print_ir)
-        print("")
-
-    gemm_normal(False)
-    gevm_unittest(False)
-    alu_unittest(False)
-    # FIXME: report time that is too short
-    # load_inp_unittest(False)
-    # load_wgt_unittest(False)
-    # store_out_unittest(False)
-
-
-print("========GEMM 128=========")
-test_gemm_packed(128, 128, 128)
-
-# FIXME: hanging run
-# print("========GEMM 1024========")
-# test_gemm_packed(1024, 1024, 128)

From c58850284a52ca3bb9e59fdb29fccb373e0307a3 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 14 Apr 2018 19:25:26 -0700
Subject: [PATCH 835/949] Refactor, refactor code structure, fix pynq rpc (#29)

---
 .../resnet18/pynq/imagenet_predict.py         |  16 +-
 vta/python/vta/__init__.py                    |   5 +-
 vta/python/vta/exec/rpc_server.py             |  10 +-
 vta/python/vta/top/__init__.py                |   5 +
 vta/python/vta/{ => top}/arm_conv2d.py        |  10 +-
 vta/python/vta/{ => top}/vta_conv2d.py        |   7 +-
 .../integration/test_benchmark_topi_conv2d.py | 155 ++++++++++++++++++
 vta/tests/python/pynq/test_benchmark_topi.py  | 146 -----------------
 8 files changed, 185 insertions(+), 169 deletions(-)
 create mode 100644 vta/python/vta/top/__init__.py
 rename vta/python/vta/{ => top}/arm_conv2d.py (97%)
 rename vta/python/vta/{ => top}/vta_conv2d.py (98%)
 create mode 100644 vta/tests/python/integration/test_benchmark_topi_conv2d.py
 delete mode 100644 vta/tests/python/pynq/test_benchmark_topi.py

diff --git a/vta/examples/resnet18/pynq/imagenet_predict.py b/vta/examples/resnet18/pynq/imagenet_predict.py
index eb660ea12a71..ae8d49017255 100644
--- a/vta/examples/resnet18/pynq/imagenet_predict.py
+++ b/vta/examples/resnet18/pynq/imagenet_predict.py
@@ -37,10 +37,10 @@
 vta.program_fpga(remote, BITSTREAM_FILE)
 
 if verbose:
-    logging.basicConfig(level=logging.INFO)
+    logging.basicConfig(level=logging.DEBUG)
 
-# Change to -device=vta-cpu to run cpu only inference.
-target = "llvm -device=vta"
+# Change to -device=vtacpu to run cpu only inference.
+target = tvm.target.create("llvm -device=vta")
 target_host = "llvm -mtriple=armv7-none-linux-gnueabihf -mcpu=cortex-a9 -mattr=+neon"
 
 synset = eval(open(os.path.join(CATEG_FILE)).read())
@@ -109,7 +109,7 @@ def mark_nop(graph, conv_layer=-1, skip_conv_layer=()):
 sym = vta.graph.remove_stochastic(sym)
 sym = vta.graph.clean_cast(sym)
 sym = vta.graph.clean_conv_fuse(sym)
-if "vta" in target:
+if target.device_name == "vta":
     sym = vta.graph.pack(sym, shape_dict, factor)
 
 graph_attr.set_shape_inputs(sym, shape_dict)
@@ -118,10 +118,10 @@ def mark_nop(graph, conv_layer=-1, skip_conv_layer=()):
 sym = sym.apply("InferType")
 
 with nnvm.compiler.build_config(opt_level=3):
-    if "vta" not in target:
+    if target.device_name != "vta":
         graph, lib, params = nnvm.compiler.build(
-            sym, target, shape_dict, dtype_dict,
-            params=params, target_host=target_host)
+            sym, target_host, shape_dict, dtype_dict,
+            params=params)
     else:
         with vta.build_config():
             graph, lib, params = nnvm.compiler.build(
@@ -133,7 +133,7 @@ def mark_nop(graph, conv_layer=-1, skip_conv_layer=()):
 lib.save(temp.relpath("graphlib.o"))
 remote.upload(temp.relpath("graphlib.o"))
 lib = remote.load_module("graphlib.o")
-ctx = remote.ext_dev(0) if "vta" in target else remote.cpu(0)
+ctx = remote.ext_dev(0) if target.device_name == "vta" else remote.cpu(0)
 
 print("Build complete...")
 
diff --git a/vta/python/vta/__init__.py b/vta/python/vta/__init__.py
index d0a3279f1915..4be16ccfb5a6 100644
--- a/vta/python/vta/__init__.py
+++ b/vta/python/vta/__init__.py
@@ -3,11 +3,12 @@
 
 
 from .environment import get_env, Environment
-from . import arm_conv2d, vta_conv2d
-from .build_module import build_config, lower, build
 from .rpc_client import reconfig_runtime, program_fpga
 
+
 try:
+    from . import top
+    from .build_module import build_config, lower, build
     from . import graph
 except (ImportError, RuntimeError):
     pass
diff --git a/vta/python/vta/exec/rpc_server.py b/vta/python/vta/exec/rpc_server.py
index 014b40564d4d..f3db38c929bd 100644
--- a/vta/python/vta/exec/rpc_server.py
+++ b/vta/python/vta/exec/rpc_server.py
@@ -75,20 +75,20 @@ def reconfig_runtime(cfg_json):
         pkg = PkgConfig(cfg, proj_root)
         # check if the configuration is already the same
         if os.path.isfile(cfg_path):
-            old_cfg = json.load(open(cfg_path))
+            old_cfg = json.loads(open(cfg_path, "r").read())
             if pkg.same_config(old_cfg):
-                logging.info("Skip reconfiguration because runtime config is the same")
+                logging.info("Skip reconfig_runtime due to same config.")
                 return
-        cflags += ["-O2", "-std=c++11"]
+        cflags = ["-O2", "-std=c++11"]
         cflags += pkg.cflags
         ldflags = pkg.ldflags
         lib_name = dll_path
-        source = env.pkg_config.lib_source
+        source = pkg.lib_source
         logging.info("Rebuild runtime: output=%s, cflags=%s, source=%s, ldflags=%s",
                      dll_path, str(cflags), str(source), str(ldflags))
         cc.create_shared(lib_name, source, cflags + ldflags)
         with open(cfg_path, "w") as outputfile:
-            json.dump(pkg.cfg_json, outputfile)
+            outputfile.write(pkg.cfg_json)
 
 
 def main():
diff --git a/vta/python/vta/top/__init__.py b/vta/python/vta/top/__init__.py
new file mode 100644
index 000000000000..614ed2347181
--- /dev/null
+++ b/vta/python/vta/top/__init__.py
@@ -0,0 +1,5 @@
+"""TVM TOPI connector, eventually most of these should go to TVM repo"""
+
+from .vta_conv2d import packed_conv2d, schedule_packed_conv2d
+from . import vta_conv2d
+from . import arm_conv2d
diff --git a/vta/python/vta/arm_conv2d.py b/vta/python/vta/top/arm_conv2d.py
similarity index 97%
rename from vta/python/vta/arm_conv2d.py
rename to vta/python/vta/top/arm_conv2d.py
index 9e46ee7f8c61..c959f1ee9967 100644
--- a/vta/python/vta/arm_conv2d.py
+++ b/vta/python/vta/top/arm_conv2d.py
@@ -44,7 +44,7 @@
     Im2ColPack(7, 4, 1, 16, False),
 ]
 
-@_get_schedule.register(["tcpu", "vta"])
+@_get_schedule.register(["vtacpu", "vta"])
 def _schedule_conv2d(wkl):
     if wkl not in _WORKLOADS:
         raise ValueError("no schedule for such workload: {}".format(wkl))
@@ -53,10 +53,10 @@ def _schedule_conv2d(wkl):
     return sch
 
 
-@conv2d.register(["tcpu", "vta"])
+@conv2d.register(["vtacpu", "vta"])
 def _declaration_conv2d(data, kernel, stride, padding, layout, out_dtype):
-    assert layout == 'NCHW', "only support NCHW convolution on tcpu"
-    assert data.shape[0].value == 1, "only support batch size=1 convolution on tcpu"
+    assert layout == 'NCHW', "only support NCHW convolution on vtacpu"
+    assert data.shape[0].value == 1, "only support batch size=1 convolution on vtacpu"
     wkl = _get_workload(data, kernel, stride, padding, out_dtype)
     sch = _get_schedule(wkl)
     return _SCH_TO_DECL_FUNC[type(sch)](data, kernel, stride, padding, out_dtype)
@@ -284,7 +284,7 @@ def _schedule_im2col_conv2d(s, data, data_pad, data_col, data_vec,
 
     return s
 
-@generic.schedule_conv2d_nchw.register(["tcpu", "vta"])
+@generic.schedule_conv2d_nchw.register(["vtacpu", "vta"])
 def schedule_conv2d(outs):
     """Create schedule for tensors"""
     s = tvm.create_schedule([x.op for x in outs])
diff --git a/vta/python/vta/vta_conv2d.py b/vta/python/vta/top/vta_conv2d.py
similarity index 98%
rename from vta/python/vta/vta_conv2d.py
rename to vta/python/vta/top/vta_conv2d.py
index 0baca7ba5b5e..577eac8e143f 100644
--- a/vta/python/vta/vta_conv2d.py
+++ b/vta/python/vta/top/vta_conv2d.py
@@ -1,4 +1,5 @@
 """Namespace for supporting packed_conv2d + ewise variant of nnvm."""
+from __future__ import absolute_import as _abs
 
 from collections import namedtuple
 
@@ -7,7 +8,7 @@
 import topi
 
 from nnvm.top import registry as reg, OpPattern
-from . import environment as vta
+from ..environment import get_env
 
 
 Workload = namedtuple("Conv2DWorkload",
@@ -219,7 +220,7 @@ def _traverse(op):
     wrkld = _get_workload(data, pad_data, kernel, output)
 
     plan = _WL2PLAN[wrkld]
-    env = vta.get_env()
+    env = get_env()
 
     load_inp = load_wgt = load_out = store_out = env.dma_copy
     alu = env.alu
@@ -251,7 +252,7 @@ def _traverse(op):
 
     # tile
     oc_factor = (plan.oc_factor if plan.oc_factor
-                 else wrkld.out_filter // vta.BLOCK_OUT)
+                 else plan.out_filter // env.BLOCK_OUT)
     h_factor = (plan.h_factor if plan.h_factor else oshape[2])
     w_factor = (plan.w_factor if plan.w_factor else oshape[3])
 
diff --git a/vta/tests/python/integration/test_benchmark_topi_conv2d.py b/vta/tests/python/integration/test_benchmark_topi_conv2d.py
new file mode 100644
index 000000000000..0a5edfdc7518
--- /dev/null
+++ b/vta/tests/python/integration/test_benchmark_topi_conv2d.py
@@ -0,0 +1,155 @@
+"""Testing if we can generate code in topi style"""
+
+import tvm
+from tvm.contrib import util
+from tvm.contrib.pickle_memoize import memoize
+import topi
+import topi.testing
+import vta
+import vta.testing
+import numpy as np
+
+Workload = vta.top.vta_conv2d.Workload
+
+@tvm.tag_scope(tag=topi.tag.ELEMWISE)
+def my_clip(x, a_min, a_max):
+    """Unlike topi's current clip, put min and max into two stages."""
+    const_min = tvm.const(a_min, x.dtype)
+    const_max = tvm.const(a_max, x.dtype)
+    x = tvm.compute(x.shape, lambda *i: tvm.min(x(*i), const_max), name="clipA")
+    x = tvm.compute(x.shape, lambda *i: tvm.max(x(*i), const_min), name="clipB")
+    return x
+
+
+def test_vta_conv2d():
+    def run_vta_conv2d(env, remote, key, batch_size, wl, profile=True):
+        data_shape = (batch_size, wl.in_filter // env.BLOCK_IN,
+                      wl.height, wl.width, env.BLOCK_IN)
+        kernel_shape = (wl.out_filter // env.BLOCK_OUT,
+                        wl.in_filter // env.BLOCK_IN,
+                        wl.hkernel, wl.wkernel,
+                        env.BLOCK_OUT, env.BLOCK_IN)
+        bias_shape = (wl.out_filter // env.BLOCK_OUT, 1, 1, env.BLOCK_OUT)
+
+
+        fout_height = (wl.height + 2 * wl.hpad - wl.hkernel) // wl.hstride + 1
+        fout_width = (wl.width + 2 * wl.wpad - wl.wkernel) // wl.wstride + 1
+        data = tvm.placeholder(data_shape, name="data", dtype=env.inp_dtype)
+        kernel = tvm.placeholder(kernel_shape, name="kernel", dtype=env.wgt_dtype)
+        bias = tvm.placeholder(bias_shape, name="kernel", dtype=env.acc_dtype)
+
+        res_conv = vta.top.packed_conv2d(
+            data, kernel, padding=(wl.hpad, wl.wpad), strides=(wl.hstride, wl.wstride))
+        res = topi.right_shift(res_conv, 8)
+        res = topi.broadcast_add(res, bias)
+        res = my_clip(res, 0, 127)
+        res = topi.cast(res, "int8")
+
+        num_ops = fout_height * fout_width * wl.hkernel * wl.wkernel * wl.out_filter * wl.in_filter
+
+        a_shape = (batch_size, wl.in_filter, wl.height, wl.width)
+        w_shape = (wl.out_filter, wl.in_filter, wl.hkernel, wl.wkernel)
+        stride = (wl.hstride, wl.wstride)
+        data_dtype = data.dtype
+        acc_dtype = env.acc_dtype
+        assert wl.hpad == wl.wpad
+        padding = wl.hpad
+
+        @memoize("vta.tests.test_benchmark_topi.conv2d,verify_nhwc")
+        def get_ref_data():
+            a_np = (np.random.uniform(size=a_shape) * 4).astype(data_dtype)
+            w_np = (np.random.uniform(size=w_shape) * 4).astype(data_dtype)
+            a_np = np.abs(a_np)
+            w_np = np.abs(w_np)
+            b_np = topi.testing.conv2d_nchw_python(
+                a_np.astype(acc_dtype), w_np.astype(acc_dtype), stride, padding).astype(acc_dtype)
+            return a_np, w_np, b_np
+
+
+        def verify(s, check_correctness):
+            mod = vta.build(s, [data, kernel, bias, res], "ext_dev",
+                            env.target_host, name="conv2d")
+            temp = util.tempdir()
+
+            mod.save(temp.relpath("conv2d.o"))
+            remote.upload(temp.relpath("conv2d.o"))
+            f = remote.load_module("conv2d.o")
+            # verify
+            ctx = remote.ext_dev(0)
+            # Data in original format
+            data_orig, kernel_orig, res_ref = get_ref_data()
+            bias_orig = (np.random.uniform(size=(wl.out_filter,)) * 4).astype("int32")
+            bias_orig = np.abs(bias_orig)
+
+            data_packed = data_orig.reshape(
+                batch_size, wl.in_filter // env.BLOCK_IN, env.BLOCK_IN,
+                wl.height, wl.width).transpose((0, 1, 3, 4, 2))
+            kernel_packed = kernel_orig.reshape(
+                wl.out_filter // env.BLOCK_OUT, env.BLOCK_OUT,
+                wl.in_filter // env.BLOCK_IN, env.BLOCK_IN,
+                wl.hkernel, wl.wkernel).transpose((0, 2, 4, 5, 1, 3))
+            bias_packed = bias_orig.reshape(
+                wl.out_filter // env.BLOCK_OUT, 1, 1, env.BLOCK_OUT)
+            res_shape = topi.util.get_const_tuple(res.shape)
+
+            res_np = np.zeros(res_shape).astype(res.dtype)
+            data_arr = tvm.nd.array(data_packed, ctx)
+            kernel_arr = tvm.nd.array(kernel_packed, ctx)
+            bias_arr = tvm.nd.array(bias_packed, ctx)
+            res_arr = tvm.nd.array(res_np, ctx)
+            time_f = f.time_evaluator("conv2d", ctx, number=5)
+            cost = time_f(data_arr, kernel_arr, bias_arr, res_arr)
+            res_unpack = res_arr.asnumpy().transpose(
+                (0, 1, 4, 2, 3)).reshape(batch_size, wl.out_filter, fout_height, fout_width)
+            if check_correctness:
+                assert wl.hpad == wl.wpad
+                stride = (wl.hstride, wl.wstride)
+                padding = wl.hpad
+                res_ref = res_ref >> 8
+                res_ref += bias_orig.reshape(wl.out_filter, 1, 1)
+                res_ref = np.clip(res_ref, 0, 127).astype("int8")
+                np.testing.assert_allclose(res_unpack, res_ref)
+            return cost
+
+        def conv_normal(print_ir):
+            print("----- CONV2D End-to-End Test-------")
+            with vta.build_config():
+                s = vta.top.schedule_packed_conv2d([res])
+                if print_ir:
+                    print(vta.lower(s, [data, kernel, bias, res], simple_mode=True))
+            cost = verify(s, True)
+            gops = (num_ops / cost.mean) / float(10 ** 9)
+            print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
+
+        conv_normal(False)
+
+    def _run(env, remote):
+        # ResNet18 workloads
+        resnet = {
+            # Workloads of resnet18 on imagenet
+            0: Workload(224, 224, 16, 64, 7, 7, 3, 3, 2, 2),
+            1: Workload(56, 56, 64, 64, 3, 3, 1, 1, 1, 1),
+            2: Workload(56, 56, 64, 64, 1, 1, 0, 0, 1, 1),
+            3: Workload(56, 56, 64, 128, 3, 3, 1, 1, 2, 2),
+            4: Workload(56, 56, 64, 128, 1, 1, 0, 0, 2, 2),
+            5: Workload(28, 28, 128, 128, 3, 3, 1, 1, 1, 1),
+            6: Workload(28, 28, 128, 256, 3, 3, 1, 1, 2, 2),
+            7: Workload(28, 28, 128, 256, 1, 1, 0, 0, 2, 2),
+            8: Workload(14, 14, 256, 256, 3, 3, 1, 1, 1, 1),
+            9: Workload(14, 14, 256, 512, 3, 3, 1, 1, 2, 2),
+            10: Workload(14, 14, 256, 512, 1, 1, 0, 0, 2, 2),
+            11: Workload(7, 7, 512, 512, 3, 3, 1, 1, 1, 1),
+        }
+
+        batch_size = 1
+        for i in range(0, len(resnet)):
+            wl = resnet[i]
+            key = "resnet-cfg[%d]" % i
+            print("key=%s" % key)
+            print(wl)
+            run_vta_conv2d(env, remote, key, batch_size, wl)
+    vta.testing.run(_run)
+
+
+if __name__ == "__main__":
+    test_vta_conv2d()
diff --git a/vta/tests/python/pynq/test_benchmark_topi.py b/vta/tests/python/pynq/test_benchmark_topi.py
deleted file mode 100644
index 3e2d19a67b2f..000000000000
--- a/vta/tests/python/pynq/test_benchmark_topi.py
+++ /dev/null
@@ -1,146 +0,0 @@
-"""Testing if we can generate code in topi style"""
-
-import topi
-import tvm
-from tvm.contrib import util, rpc
-import vta
-from vta import vta_conv2d
-import numpy as np
-import mxnet as mx
-
-Workload = vta_conv2d.Workload
-
-@tvm.tag_scope(tag=topi.tag.ELEMWISE)
-def my_clip(x, a_min, a_max):
-    """Unlike topi's current clip, put min and max into two stages."""
-    const_min = tvm.const(a_min, x.dtype)
-    const_max = tvm.const(a_max, x.dtype)
-    x = tvm.compute(x.shape, lambda *i: tvm.min(x(*i), const_max), name="clipA")
-    x = tvm.compute(x.shape, lambda *i: tvm.max(x(*i), const_min), name="clipB")
-    return x
-
-host = "pynq"
-port = 9091
-target = "llvm -target=armv7-none-linux-gnueabihf -mattr=+neon"
-print_ir = False
-
-
-def test_vta_conv2d(key, batch_size, wl, profile=True):
-    env = vta.get_env()
-    data_shape = (batch_size, wl.in_filter // env.BLOCK_IN,
-                  wl.height, wl.width, env.BLOCK_IN)
-    kernel_shape = (wl.out_filter // env.BLOCK_OUT,
-                    wl.in_filter // env.BLOCK_IN,
-                    wl.hkernel, wl.wkernel,
-                    env.BLOCK_OUT, env.BLOCK_IN)
-    bias_shape = (wl.out_filter // env.BLOCK_OUT, 1, 1, env.BLOCK_OUT)
-
-
-    fout_height = (wl.height + 2 * wl.hpad - wl.hkernel) // wl.hstride + 1
-    fout_width = (wl.width + 2 * wl.wpad - wl.wkernel) // wl.wstride + 1
-    data = tvm.placeholder(data_shape, name="data", dtype=env.inp_dtype)
-    kernel = tvm.placeholder(kernel_shape, name="kernel", dtype=env.wgt_dtype)
-    bias = tvm.placeholder(bias_shape, name="kernel", dtype=env.acc_dtype)
-
-    res_conv = vta_conv2d.packed_conv2d(
-        data, kernel, padding=(wl.hpad, wl.wpad), strides=(wl.hstride, wl.wstride))
-    res = topi.right_shift(res_conv, 8)
-    res = topi.broadcast_add(res, bias)
-    res = my_clip(res, 0, 127)
-    res = topi.cast(res, "int8")
-
-    num_ops = fout_height * fout_width * wl.hkernel * wl.wkernel * wl.out_filter * wl.in_filter
-
-    def verify(s, check_correctness):
-        mod = tvm.build(s, [data, kernel, bias, res], "ext_dev", target, name="conv2d")
-        temp = util.tempdir()
-        remote = rpc.connect(host, port)
-
-        mod.save(temp.relpath("conv2d.o"))
-        remote.upload(temp.relpath("conv2d.o"))
-        f = remote.load_module("conv2d.o")
-        # verify
-        ctx = remote.ext_dev(0)
-        # Data in original format
-        data_orig = (np.random.uniform(
-            size=(batch_size, wl.in_filter, wl.height, wl.width)) * 4).astype(data.dtype)
-        kernel_orig = (np.random.uniform(
-            size=(wl.out_filter, wl.in_filter, wl.hkernel, wl.wkernel)) * 4).astype(kernel.dtype)
-        bias_orig = (np.random.uniform(size=(wl.out_filter,)) * 4).astype("int32")
-
-        data_orig = np.abs(data_orig)
-        kernel_orig = np.abs(kernel_orig)
-        bias_orig = np.abs(bias_orig)
-
-        data_packed = data_orig.reshape(
-            batch_size, wl.in_filter // env.BLOCK_IN, env.BLOCK_IN,
-            wl.height, wl.width).transpose((0, 1, 3, 4, 2))
-        kernel_packed = kernel_orig.reshape(
-            wl.out_filter // env.BLOCK_OUT, env.BLOCK_OUT,
-            wl.in_filter // env.BLOCK_IN, env.BLOCK_IN,
-            wl.hkernel, wl.wkernel).transpose((0, 2, 4, 5, 1, 3))
-        bias_packed = bias_orig.reshape(
-            wl.out_filter // env.BLOCK_OUT, 1, 1, env.BLOCK_OUT)
-        res_shape = topi.util.get_const_tuple(res.shape)
-
-        res_np = np.zeros(res_shape).astype(res.dtype)
-        data_arr = tvm.nd.array(data_packed, ctx)
-        kernel_arr = tvm.nd.array(kernel_packed, ctx)
-        bias_arr = tvm.nd.array(bias_packed, ctx)
-        res_arr = tvm.nd.array(res_np, ctx)
-        time_f = f.time_evaluator("conv2d", ctx, number=10)
-        cost = time_f(data_arr, kernel_arr, bias_arr, res_arr)
-        res_unpack = res_arr.asnumpy().transpose(
-            (0, 1, 4, 2, 3)).reshape(batch_size, wl.out_filter, fout_height, fout_width)
-        if check_correctness:
-            res_ref = mx.nd.Convolution(
-                mx.nd.array(data_orig.astype(env.acc_dtype), mx.cpu(0)),
-                mx.nd.array(kernel_orig.astype(env.acc_dtype), mx.cpu(0)),
-                stride=(wl.hstride, wl.wstride),
-                kernel=(wl.hkernel, wl.wkernel),
-                num_filter=wl.out_filter,
-                no_bias=True,
-                pad=(wl.hpad, wl.wpad)).asnumpy().astype(env.acc_dtype)
-            res_ref = res_ref >> 8
-            res_ref += bias_orig.reshape(wl.out_filter, 1, 1)
-            res_ref = np.clip(res_ref, 0, 127).astype("int8")
-            np.testing.assert_allclose(res_unpack, res_ref)
-            print("Correctness check pass...")
-        return cost
-
-    def conv_normal(print_ir):
-        print("----- CONV2D End-to-End Test-------")
-        with vta.build_config():
-            s = vta_conv2d.schedule_packed_conv2d([res])
-            if print_ir:
-                print(vta.lower(s, [data, kernel, bias, res], simple_mode=True))
-            cost = verify(s, True)
-        gops = (num_ops / cost.mean) / float(10 ** 9)
-        print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
-
-    conv_normal(print_ir)
-
-# ResNet18 workloads
-resnet = {
-    # Workloads of resnet18 on imagenet
-    0: Workload(224, 224, 16, 64, 7, 7, 3, 3, 2, 2),
-    1: Workload(56, 56, 64, 64, 3, 3, 1, 1, 1, 1),
-    2: Workload(56, 56, 64, 64, 1, 1, 0, 0, 1, 1),
-    3: Workload(56, 56, 64, 128, 3, 3, 1, 1, 2, 2),
-    4: Workload(56, 56, 64, 128, 1, 1, 0, 0, 2, 2),
-    5: Workload(28, 28, 128, 128, 3, 3, 1, 1, 1, 1),
-    6: Workload(28, 28, 128, 256, 3, 3, 1, 1, 2, 2),
-    7: Workload(28, 28, 128, 256, 1, 1, 0, 0, 2, 2),
-    8: Workload(14, 14, 256, 256, 3, 3, 1, 1, 1, 1),
-    9: Workload(14, 14, 256, 512, 3, 3, 1, 1, 2, 2),
-    10: Workload(14, 14, 256, 512, 1, 1, 0, 0, 2, 2),
-    11: Workload(7, 7, 512, 512, 3, 3, 1, 1, 1, 1),
-}
-
-batch_size = 1
-for i in range(0, len(resnet)):
-    wl = resnet[i]
-    key = "resnet-cfg[%d]" % i
-    print "key=%s" % key
-    print wl
-    test_vta_conv2d(key, batch_size, wl)

From b8a6095812d595796384ee4c7cdbb75377a7a7de Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 14 Apr 2018 20:56:28 -0700
Subject: [PATCH 836/949] [TEST] CI infrastructure (#30)

---
 vta/Jenkinsfile                               | 128 ++++++++++++++++++
 vta/docs/conf.py                              |   1 +
 vta/include/vta/driver.h                      |   4 +-
 vta/include/vta/hw_spec.h                     |   2 +-
 vta/include/vta/runtime.h                     |   4 +-
 vta/make/sim_sample.json                      |  14 ++
 vta/python/vta/__init__.py                    |   2 +
 vta/tests/ci_build/Dockerfile.cpu             |  26 ++++
 vta/tests/ci_build/Dockerfile.lint            |   6 +
 vta/tests/ci_build/README.md                  |  35 +++++
 vta/tests/ci_build/ci_build.sh                | 126 +++++++++++++++++
 .../ci_build/install/ubuntu_install_core.sh   |   6 +
 .../ci_build/install/ubuntu_install_llvm.sh   |  22 +++
 .../ci_build/install/ubuntu_install_python.sh |   5 +
 .../install/ubuntu_install_python_package.sh  |   3 +
 .../ci_build/install/ubuntu_install_sphinx.sh |   1 +
 vta/tests/ci_build/with_the_same_user         |  34 +++++
 vta/tests/scripts/task_build.sh               |  21 +++
 vta/tests/scripts/task_clean.sh               |  10 ++
 vta/tests/scripts/task_lint.sh                |  15 ++
 vta/tests/scripts/task_python_docs.sh         |  12 ++
 vta/tests/scripts/task_python_test.sh         |  11 ++
 vta/tutorials/get_started.py                  |   2 +-
 23 files changed, 483 insertions(+), 7 deletions(-)
 create mode 100644 vta/Jenkinsfile
 create mode 100644 vta/make/sim_sample.json
 create mode 100644 vta/tests/ci_build/Dockerfile.cpu
 create mode 100644 vta/tests/ci_build/Dockerfile.lint
 create mode 100644 vta/tests/ci_build/README.md
 create mode 100755 vta/tests/ci_build/ci_build.sh
 create mode 100644 vta/tests/ci_build/install/ubuntu_install_core.sh
 create mode 100644 vta/tests/ci_build/install/ubuntu_install_llvm.sh
 create mode 100644 vta/tests/ci_build/install/ubuntu_install_python.sh
 create mode 100644 vta/tests/ci_build/install/ubuntu_install_python_package.sh
 create mode 100644 vta/tests/ci_build/install/ubuntu_install_sphinx.sh
 create mode 100644 vta/tests/ci_build/with_the_same_user
 create mode 100755 vta/tests/scripts/task_build.sh
 create mode 100755 vta/tests/scripts/task_clean.sh
 create mode 100755 vta/tests/scripts/task_lint.sh
 create mode 100755 vta/tests/scripts/task_python_docs.sh
 create mode 100755 vta/tests/scripts/task_python_test.sh

diff --git a/vta/Jenkinsfile b/vta/Jenkinsfile
new file mode 100644
index 000000000000..90dfc27af434
--- /dev/null
+++ b/vta/Jenkinsfile
@@ -0,0 +1,128 @@
+#!groovy
+// -*- mode: groovy -*-
+// Jenkins pipeline
+// See documents at https://jenkins.io/doc/book/pipeline/jenkinsfile/
+
+// nnvm libraries
+vta_lib += "lib/libvta.so, lib/libvta.so.json"
+vta_lib += ", nnvm/tvm/lib/libtvm.so, nnvm/tvm/lib/libtopi.so, nnvm/lib/libnnvm_compiler.so"
+
+
+// command to start a docker container
+docker_run = 'tests/ci_build/ci_build.sh'
+// timeout in minutes
+max_time = 60
+
+// initialize source codes
+def init_git() {
+  checkout scm
+  retry(5) {
+    timeout(time: 2, unit: 'MINUTES') {
+      sh 'git submodule update --init --recursive'
+    }
+  }
+}
+
+def init_git_win() {
+    checkout scm
+    retry(5) {
+        timeout(time: 2, unit: 'MINUTES') {
+            bat 'git submodule update --init --recursive'
+        }
+    }
+}
+
+stage("Sanity Check") {
+  timeout(time: max_time, unit: 'MINUTES') {
+    node('linux') {
+      ws('workspace/vta/sanity') {
+        init_git()
+        sh "${docker_run} lint  ./tests/scripts/task_lint.sh"
+      }
+    }
+  }
+}
+
+// Run make. First try to do an incremental make from a previous workspace in hope to
+// accelerate the compilation. If something wrong, clean the workspace and then
+// build from scratch.
+def make(docker_type, make_flag) {
+  timeout(time: max_time, unit: 'MINUTES') {
+    sh "${docker_run} ${docker_type} cp make/sim_sample.json config.json"
+    try {
+      sh "${docker_run} ${docker_type} ./tests/scripts/task_build.sh ${make_flag}"
+    } catch (exc) {
+      echo 'Incremental compilation failed. Fall back to build from scratch'
+      sh "${docker_run} ${docker_type} ./tests/scripts/task_clean.sh"
+      sh "${docker_run} ${docker_type} ./tests/scripts/task_build.sh ${make_flag}"
+    }
+  }
+}
+
+// pack libraries for later use
+def pack_lib(name, libs) {
+  sh """
+     echo "Packing ${libs} into ${name}"
+     echo ${libs} | sed -e 's/,/ /g' | xargs md5sum
+     """
+  stash includes: libs, name: name
+}
+
+
+// unpack libraries saved before
+def unpack_lib(name, libs) {
+  unstash name
+  sh """
+     echo "Unpacked ${libs} from ${name}"
+     echo ${libs} | sed -e 's/,/ /g' | xargs md5sum
+     """
+}
+
+stage('Build') {
+  timeout(time: max_time, unit: 'MINUTES') {
+    node('linux') {
+      ws('workspace/vta/build') {
+        init_git()
+        make('cpu', '-j2')
+        pack_lib('cpu', vta_lib)
+      }
+    }
+  }
+}
+
+stage('Tests') {
+  parallel 'python': {
+    node('linux') {
+      ws('workspace/vta/it-python') {
+        init_git()
+        unpack_lib('cpu', vta_lib)
+        timeout(time: max_time, unit: 'MINUTES') {
+          sh "${docker_run} cpu ./tests/scripts/task_python_test.sh"
+        }
+      }
+    }
+  },
+  'docs': {
+    node('linux') {
+      ws('workspace/vta/docs-python') {
+        init_git()
+        unpack_lib('cpu', vta_lib)
+        timeout(time: max_time, unit: 'MINUTES') {
+          sh "${docker_run} cpu ./tests/scripts/task_python_docs.sh"
+        }
+        pack_lib('mydocs', 'docs.tgz')
+      }
+    }
+  }
+}
+
+stage('Deploy') {
+    node('docker' && 'doc') {
+      ws('workspace/vta/deploy-docs') {
+        if (env.BRANCH_NAME == "master") {
+           unpack_lib('mydocs', 'docs.tgz')
+           sh "tar xf docs.tgz -C /var/vta-docs"
+        }
+      }
+    }
+}
diff --git a/vta/docs/conf.py b/vta/docs/conf.py
index 8464fada6352..bdc1029223b0 100644
--- a/vta/docs/conf.py
+++ b/vta/docs/conf.py
@@ -16,6 +16,7 @@
 import shlex
 import recommonmark
 import sphinx_gallery
+from tvm.contrib import rpc, graph_runtime
 from recommonmark.parser import CommonMarkParser
 from recommonmark.transform import AutoStructify
 
diff --git a/vta/include/vta/driver.h b/vta/include/vta/driver.h
index 58f778806208..269728c51cda 100644
--- a/vta/include/vta/driver.h
+++ b/vta/include/vta/driver.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2018 by Contributors
- * \file vta_driver.h
+ * \file driver.h
  * \brief Driver interface that is used by runtime.
  *
  * Driver's implementation is device specific.
@@ -46,7 +46,7 @@ void VTADeviceFree(VTADeviceHandle handle);
 
 /*!
  * \brief Launch the instructions block until done.
- * \param The device handle.
+ * \param device The device handle.
  * \param insn_phy_addr The physical address of instruction stream.
  * \param insn_count Instruction count.
  * \param wait_cycles The maximum of cycles to wait
diff --git a/vta/include/vta/hw_spec.h b/vta/include/vta/hw_spec.h
index 9d62d4e7d57e..5bcd44c16bdf 100644
--- a/vta/include/vta/hw_spec.h
+++ b/vta/include/vta/hw_spec.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2018 by Contributors
- * \file vta_defines.h
+ * \file hw_spec.h
  * \brief Preprocessor definitions for VTA HLS design and runtime.
  */
 
diff --git a/vta/include/vta/runtime.h b/vta/include/vta/runtime.h
index 479540129226..6d77067be931 100644
--- a/vta/include/vta/runtime.h
+++ b/vta/include/vta/runtime.h
@@ -25,7 +25,6 @@ extern "C" {
 
 /*!
  * \brief Allocate data buffer.
- * \param cmd The VTA command handle.
  * \param size Buffer size.
  * \return A pointer to the allocated buffer.
  */
@@ -33,14 +32,12 @@ void* VTABufferAlloc(size_t size);
 
 /*!
  * \brief Free data buffer.
- * \param cmd The VTA command handle.
  * \param buffer The data buffer to be freed.
  */
 void VTABufferFree(void* buffer);
 
 /*!
  * \brief Copy data buffer from one location to another.
- * \param cmd The VTA command handle.
  * \param from The source buffer base address.
  * \param from_offset The offset of the source buffer.
  * \param to The target buffer base address.
@@ -145,6 +142,7 @@ void VTALoadBuffer2D(VTACommandHandle cmd,
  * \param src_sram_index Source SRAM index.
  * \param src_memory_type Source memory type.
  * \param dst_dram_addr Destination DRAM address.
+ * \param dst_elem_offset The destination DRAM offset in number of unit elements.
  * \param x_size The lowest dimension (x axis) size in number of unit elements.
  * \param y_size The number of rows.
  * \param x_stride The x axis stride.
diff --git a/vta/make/sim_sample.json b/vta/make/sim_sample.json
new file mode 100644
index 000000000000..57aa89ad9547
--- /dev/null
+++ b/vta/make/sim_sample.json
@@ -0,0 +1,14 @@
+{
+  "TARGET" : "sim",
+  "LOG_INP_WIDTH" : 3,
+  "LOG_WGT_WIDTH" : 3,
+  "LOG_ACC_WIDTH" : 5,
+  "LOG_OUT_WIDTH" : 3,
+  "LOG_BATCH" : 0,
+  "LOG_BLOCK_IN" : 4,
+  "LOG_BLOCK_OUT" : 4,
+  "LOG_UOP_BUFF_SIZE" : 15,
+  "LOG_INP_BUFF_SIZE" : 15,
+  "LOG_WGT_BUFF_SIZE" : 15,
+  "LOG_ACC_BUFF_SIZE" : 17
+}
diff --git a/vta/python/vta/__init__.py b/vta/python/vta/__init__.py
index 4be16ccfb5a6..c56e9a444ac0 100644
--- a/vta/python/vta/__init__.py
+++ b/vta/python/vta/__init__.py
@@ -1,6 +1,8 @@
 """TVM-based VTA Compiler Toolchain"""
 from __future__ import absolute_import as _abs
 
+__version__ = "0.1.0"
+
 
 from .environment import get_env, Environment
 from .rpc_client import reconfig_runtime, program_fpga
diff --git a/vta/tests/ci_build/Dockerfile.cpu b/vta/tests/ci_build/Dockerfile.cpu
new file mode 100644
index 000000000000..ceb242925476
--- /dev/null
+++ b/vta/tests/ci_build/Dockerfile.cpu
@@ -0,0 +1,26 @@
+# For CPU
+FROM ubuntu:16.04
+
+RUN apt-get update --fix-missing
+
+COPY install/ubuntu_install_core.sh /install/ubuntu_install_core.sh
+RUN bash /install/ubuntu_install_core.sh
+
+COPY install/ubuntu_install_llvm.sh /install/ubuntu_install_llvm.sh
+RUN bash /install/ubuntu_install_llvm.sh
+
+COPY install/ubuntu_install_python.sh /install/ubuntu_install_python.sh
+RUN bash /install/ubuntu_install_python.sh
+
+COPY install/ubuntu_install_python_package.sh /install/ubuntu_install_python_package.sh
+RUN bash /install/ubuntu_install_python_package.sh
+
+COPY install/ubuntu_install_sphinx.sh /install/ubuntu_install_sphinx.sh
+RUN bash /install/ubuntu_install_sphinx.sh
+
+# Fix recommonmark to latest version
+RUN git clone https://github.com/rtfd/recommonmark
+RUN cd recommonmark; python setup.py install
+
+# Enable doxygen for c++ doc build
+RUN apt-get update && apt-get install -y doxygen graphviz
\ No newline at end of file
diff --git a/vta/tests/ci_build/Dockerfile.lint b/vta/tests/ci_build/Dockerfile.lint
new file mode 100644
index 000000000000..4ba4ca3be294
--- /dev/null
+++ b/vta/tests/ci_build/Dockerfile.lint
@@ -0,0 +1,6 @@
+# For lint test
+FROM ubuntu:16.04
+
+RUN apt-get update && apt-get install -y python-pip sudo
+RUN apt-get install -y doxygen graphviz
+RUN pip install cpplint pylint
diff --git a/vta/tests/ci_build/README.md b/vta/tests/ci_build/README.md
new file mode 100644
index 000000000000..9813ee08424a
--- /dev/null
+++ b/vta/tests/ci_build/README.md
@@ -0,0 +1,35 @@
+# CI Build Scripts
+
+This directory contains the files and setup instructions to run all tests.
+
+## Run locally
+
+To run locally, we need to first install
+[docker](https://docs.docker.com/engine/installation/)
+
+Then we can run the tasks defined in the [Jenkinsfile](../../Jenkinsfile) by
+using (`ci_build.sh`)[./ci_build.sh]. For example
+
+- lint the python codes
+
+  ```bash
+  ./ci_build.sh lint make pylint
+  ```
+
+- build codes with CUDA supports
+
+  ```bash
+  ./ci_build.sh gpu tests/scripts/task_build.sh
+  ```
+
+- do the python unittest
+
+  ```bash
+  ./ci_build.sh gpu tests/scripts/task_python_test.sh
+  ```
+
+- build the documents. The results will be available at `docs/_build/html`
+
+  ```bash
+  tests/ci_build/ci_build.sh gpu tests/scripts/task_python_docs.sh
+  ```
diff --git a/vta/tests/ci_build/ci_build.sh b/vta/tests/ci_build/ci_build.sh
new file mode 100755
index 000000000000..db4283ca9753
--- /dev/null
+++ b/vta/tests/ci_build/ci_build.sh
@@ -0,0 +1,126 @@
+#!/usr/bin/env bash
+#
+# Execute command within a docker container
+#
+# Usage: ci_build.sh <CONTAINER_TYPE> [--dockerfile <DOCKERFILE_PATH>] [-it]
+#                    <COMMAND>
+#
+# CONTAINER_TYPE: Type of the docker container used the run the build: e.g.,
+#                 (cpu | gpu)
+#
+# DOCKERFILE_PATH: (Optional) Path to the Dockerfile used for docker build.  If
+#                  this optional value is not supplied (via the --dockerfile
+#                  flag), will use Dockerfile.CONTAINER_TYPE in default
+#
+# COMMAND: Command to be executed in the docker container
+#
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+# Get the command line arguments.
+CONTAINER_TYPE=$( echo "$1" | tr '[:upper:]' '[:lower:]' )
+shift 1
+
+# Dockerfile to be used in docker build
+DOCKERFILE_PATH="${SCRIPT_DIR}/Dockerfile.${CONTAINER_TYPE}"
+DOCKER_CONTEXT_PATH="${SCRIPT_DIR}"
+
+if [[ "$1" == "--dockerfile" ]]; then
+    DOCKERFILE_PATH="$2"
+    DOCKER_CONTEXT_PATH=$(dirname "${DOCKERFILE_PATH}")
+    echo "Using custom Dockerfile path: ${DOCKERFILE_PATH}"
+    echo "Using custom docker build context path: ${DOCKER_CONTEXT_PATH}"
+    shift 2
+fi
+
+if [[ "$1" == "-it" ]]; then
+    CI_DOCKER_EXTRA_PARAMS+=('-it')
+    shift 1
+fi
+
+if [[ ! -f "${DOCKERFILE_PATH}" ]]; then
+    echo "Invalid Dockerfile path: \"${DOCKERFILE_PATH}\""
+    exit 1
+fi
+
+COMMAND=("$@")
+
+# Validate command line arguments.
+if [ "$#" -lt 1 ] || [ ! -e "${SCRIPT_DIR}/Dockerfile.${CONTAINER_TYPE}" ]; then
+    supported_container_types=$( ls -1 ${SCRIPT_DIR}/Dockerfile.* | \
+        sed -n 's/.*Dockerfile\.\([^\/]*\)/\1/p' | tr '\n' ' ' )
+      echo "Usage: $(basename $0) CONTAINER_TYPE COMMAND"
+      echo "       CONTAINER_TYPE can be one of [${supported_container_types}]"
+      echo "       COMMAND is a command (with arguments) to run inside"
+      echo "               the container."
+      exit 1
+fi
+
+# Use nvidia-docker if the container is GPU.
+if [[ "${CONTAINER_TYPE}" == *"gpu"* ]]; then
+    DOCKER_BINARY="nvidia-docker"
+else
+    DOCKER_BINARY="docker"
+fi
+
+# Helper function to traverse directories up until given file is found.
+function upsearch () {
+    test / == "$PWD" && return || \
+        test -e "$1" && echo "$PWD" && return || \
+        cd .. && upsearch "$1"
+}
+
+# Set up WORKSPACE and BUILD_TAG. Jenkins will set them for you or we pick
+# reasonable defaults if you run it outside of Jenkins.
+WORKSPACE="${WORKSPACE:-${SCRIPT_DIR}/../../}"
+BUILD_TAG="${BUILD_TAG:-nnvm-ci}"
+
+# Determine the docker image name
+DOCKER_IMG_NAME="${BUILD_TAG}.${CONTAINER_TYPE}"
+
+# Under Jenkins matrix build, the build tag may contain characters such as
+# commas (,) and equal signs (=), which are not valid inside docker image names.
+DOCKER_IMG_NAME=$(echo "${DOCKER_IMG_NAME}" | sed -e 's/=/_/g' -e 's/,/-/g')
+
+# Convert to all lower-case, as per requirement of Docker image names
+DOCKER_IMG_NAME=$(echo "${DOCKER_IMG_NAME}" | tr '[:upper:]' '[:lower:]')
+
+# Print arguments.
+echo "WORKSPACE: ${WORKSPACE}"
+echo "CI_DOCKER_EXTRA_PARAMS: ${CI_DOCKER_EXTRA_PARAMS[@]}"
+echo "COMMAND: ${COMMAND[@]}"
+echo "CONTAINER_TYPE: ${CONTAINER_TYPE}"
+echo "BUILD_TAG: ${BUILD_TAG}"
+echo "DOCKER CONTAINER NAME: ${DOCKER_IMG_NAME}"
+echo ""
+
+
+# Build the docker container.
+echo "Building container (${DOCKER_IMG_NAME})..."
+docker build -t ${DOCKER_IMG_NAME} \
+    -f "${DOCKERFILE_PATH}" "${DOCKER_CONTEXT_PATH}"
+
+# Check docker build status
+if [[ $? != "0" ]]; then
+    echo "ERROR: docker build failed."
+    exit 1
+fi
+
+# Run the command inside the container.
+echo "Running '${COMMAND[@]}' inside ${DOCKER_IMG_NAME}..."
+
+# By default we cleanup - remove the container once it finish running (--rm)
+# and share the PID namespace (--pid=host) so the process inside does not have
+# pid 1 and SIGKILL is propagated to the process inside (jenkins can kill it).
+echo ${DOCKER_BINARY}
+${DOCKER_BINARY} run --rm --pid=host \
+    -v ${WORKSPACE}:/workspace \
+    -w /workspace \
+    -e "CI_BUILD_HOME=/workspace" \
+    -e "CI_BUILD_USER=$(id -u -n)" \
+    -e "CI_BUILD_UID=$(id -u)" \
+    -e "CI_BUILD_GROUP=$(id -g -n)" \
+    -e "CI_BUILD_GID=$(id -g)" \
+    ${CI_DOCKER_EXTRA_PARAMS[@]} \
+    ${DOCKER_IMG_NAME} \
+    bash tests/ci_build/with_the_same_user \
+    ${COMMAND[@]}
diff --git a/vta/tests/ci_build/install/ubuntu_install_core.sh b/vta/tests/ci_build/install/ubuntu_install_core.sh
new file mode 100644
index 000000000000..efc69c946b97
--- /dev/null
+++ b/vta/tests/ci_build/install/ubuntu_install_core.sh
@@ -0,0 +1,6 @@
+# install libraries for building c++ core on ubuntu
+apt-get update && apt-get install -y --no-install-recommends --force-yes \
+        git make libgtest-dev cmake wget unzip libtinfo-dev libz-dev\
+        libcurl4-openssl-dev libopenblas-dev g++ sudo
+
+cd /usr/src/gtest && cmake CMakeLists.txt && make && cp *.a /usr/lib
diff --git a/vta/tests/ci_build/install/ubuntu_install_llvm.sh b/vta/tests/ci_build/install/ubuntu_install_llvm.sh
new file mode 100644
index 000000000000..ba0afcd18cc9
--- /dev/null
+++ b/vta/tests/ci_build/install/ubuntu_install_llvm.sh
@@ -0,0 +1,22 @@
+echo deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-4.0 main\
+     >> /etc/apt/sources.list.d/llvm.list
+echo deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial-4.0 main\
+     >> /etc/apt/sources.list.d/llvm.list
+
+echo deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-5.0 main\
+     >> /etc/apt/sources.list.d/llvm.list
+echo deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial-5.0 main\
+     >> /etc/apt/sources.list.d/llvm.list
+
+echo deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-6.0 main\
+     >> /etc/apt/sources.list.d/llvm.list
+echo deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial-6.0 main\
+     >> /etc/apt/sources.list.d/llvm.list
+
+echo deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial main\
+     >> /etc/apt/sources.list.d/llvm.list
+echo deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial main\
+     >> /etc/apt/sources.list.d/llvm.list
+
+wget -O - http://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
+apt-get update && apt-get install -y --force-yes llvm-4.0 llvm-5.0 llvm-6.0
diff --git a/vta/tests/ci_build/install/ubuntu_install_python.sh b/vta/tests/ci_build/install/ubuntu_install_python.sh
new file mode 100644
index 000000000000..c6f6f75c564e
--- /dev/null
+++ b/vta/tests/ci_build/install/ubuntu_install_python.sh
@@ -0,0 +1,5 @@
+# install python and pip, don't modify this, modify install_python_package.sh
+apt-get update && apt-get install -y python-pip python-dev python3-dev
+
+# the version of the pip shipped with ubuntu may be too lower, install a recent version here
+cd /tmp && wget https://bootstrap.pypa.io/get-pip.py && python3 get-pip.py && python2 get-pip.py
diff --git a/vta/tests/ci_build/install/ubuntu_install_python_package.sh b/vta/tests/ci_build/install/ubuntu_install_python_package.sh
new file mode 100644
index 000000000000..fbed2e1904cd
--- /dev/null
+++ b/vta/tests/ci_build/install/ubuntu_install_python_package.sh
@@ -0,0 +1,3 @@
+# install libraries for python package on ubuntu
+pip2 install nose pylint numpy nose-timer cython decorator scipy tornado
+pip3 install nose pylint numpy nose-timer cython decorator scipy tornado
diff --git a/vta/tests/ci_build/install/ubuntu_install_sphinx.sh b/vta/tests/ci_build/install/ubuntu_install_sphinx.sh
new file mode 100644
index 000000000000..30d4596aabf2
--- /dev/null
+++ b/vta/tests/ci_build/install/ubuntu_install_sphinx.sh
@@ -0,0 +1 @@
+pip install sphinx sphinx-gallery sphinx_rtd_theme matplotlib Image commonmark>=0.7.3 docutils>=0.11
diff --git a/vta/tests/ci_build/with_the_same_user b/vta/tests/ci_build/with_the_same_user
new file mode 100644
index 000000000000..1e6ab883694b
--- /dev/null
+++ b/vta/tests/ci_build/with_the_same_user
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+
+# This script is a wrapper creating the same user inside container as the one
+# running the ci_build.sh outside the container. It also set the home directory
+# for the user inside container to match the same absolute path as the workspace
+# outside of container.  Do not run this manually. It does not make sense. It is
+# intended to be called by ci_build.sh only.
+
+set -e
+
+COMMAND=("$@")
+
+if ! touch /this_is_writable_file_system; then
+  echo "You can't write to your filesystem!"
+  echo "If you are in Docker you should check you do not have too many images" \
+      "with too many files in them. Docker has some issue with it."
+  exit 1
+else
+  rm /this_is_writable_file_system
+fi
+
+getent group "${CI_BUILD_GID}" || addgroup --gid "${CI_BUILD_GID}" "${CI_BUILD_GROUP}"
+getent passwd "${CI_BUILD_UID}" || adduser --gid "${CI_BUILD_GID}" --uid "${CI_BUILD_UID}" \
+    --gecos "${CI_BUILD_USER} (generated by with_the_same_user script)" \
+    --disabled-password --home "${CI_BUILD_HOME}" --quiet "${CI_BUILD_USER}"
+usermod -a -G sudo "${CI_BUILD_USER}"
+echo "${CI_BUILD_USER} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/90-nopasswd-sudo
+
+HOME=${CI_BUILD_HOME}\
+    sudo -u "#${CI_BUILD_UID}" --preserve-env\
+    PATH=${PATH}\
+    LD_LIBRARY_PATH=${LD_LIBRARY_PATH}\
+    HOME=${CI_BUILD_HOME}\
+    ${COMMAND[@]}
diff --git a/vta/tests/scripts/task_build.sh b/vta/tests/scripts/task_build.sh
new file mode 100755
index 000000000000..8cf01d46a600
--- /dev/null
+++ b/vta/tests/scripts/task_build.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+echo "Build TVM..."
+cd nnvm/tvm
+cp make/config.mk .
+echo USE_CUDA=0 >> config.mk
+echo LLVM_CONFIG=llvm-config-4.0 >> config.mk
+echo USE_RPC=1 >> config.mk
+echo USE_BLAS=openblas >> config.mk
+echo USE_GRAPH_RUNTIME=1 >> config.mk
+make "$@"
+make cython
+make cython3
+cd ..
+
+echo "Build NNVM..."
+make "$@"
+
+cd ..
+
+echo "Build VTA..."
+make "$@"
diff --git a/vta/tests/scripts/task_clean.sh b/vta/tests/scripts/task_clean.sh
new file mode 100755
index 000000000000..60c70fadf459
--- /dev/null
+++ b/vta/tests/scripts/task_clean.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+echo "Cleanup data..."
+cd nnvm
+make clean
+
+cd tvm
+make clean
+
+cd ../..
+make clean
diff --git a/vta/tests/scripts/task_lint.sh b/vta/tests/scripts/task_lint.sh
new file mode 100755
index 000000000000..a6774a189f35
--- /dev/null
+++ b/vta/tests/scripts/task_lint.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+echo "Check codestyle of c++ code..."
+make cpplint || exit -1
+echo "Check codestyle of python code..."
+make pylint || exit -1
+echo "Check documentations of c++ code..."
+make doc 2>log.txt
+(cat log.txt| grep -v ENABLE_PREPROCESSING |grep -v "unsupported tag") > logclean.txt
+echo "---------Error Log----------"
+cat logclean.txt
+echo "----------------------------"
+(cat logclean.txt|grep warning) && exit -1
+(cat logclean.txt|grep error) && exit -1
+rm logclean.txt
+rm log.txt
diff --git a/vta/tests/scripts/task_python_docs.sh b/vta/tests/scripts/task_python_docs.sh
new file mode 100755
index 000000000000..8f8b4804bdcc
--- /dev/null
+++ b/vta/tests/scripts/task_python_docs.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+mkdir -p docs/_build/html
+# C++ doc
+make doc
+
+rm -rf python/vta/*.pyc python/vta/*/*.pyc
+
+cd docs
+PYTHONPATH=../python:../nnvm/tvm/python:../nnvm/tvm/topi/python:../nnvm/python make html || exit -1
+cd _build/html
+tar czf docs.tgz *
+mv docs.tgz ../../../
diff --git a/vta/tests/scripts/task_python_test.sh b/vta/tests/scripts/task_python_test.sh
new file mode 100755
index 000000000000..d552aae0e87f
--- /dev/null
+++ b/vta/tests/scripts/task_python_test.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+export PYTHONPATH=python:nnvm/python:nnvm/tvm/python:nnvm/tvm/topi/python
+
+echo "Running unittest..."
+python -m nose -v tests/python/unittest || exit -1
+python3 -m nose -v tests/python/unittest || exit -1
+
+echo "Running integration test..."
+python -m nose -v tests/python/integration || exit -1
+python3 -m nose -v tests/python/integration || exit -1
diff --git a/vta/tutorials/get_started.py b/vta/tutorials/get_started.py
index d9fcb644eddf..73839196145d 100644
--- a/vta/tutorials/get_started.py
+++ b/vta/tutorials/get_started.py
@@ -14,9 +14,9 @@
 """
 from __future__ import absolute_import, print_function
 
-import tvm
 import vta
 
+
 ######################################################################
 # Program the FPGA with VTA Bistream
 # ----------------------------------

From b668e4d760df23f2bc2c03f7b1f19c3de437fab2 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 14 Apr 2018 21:53:53 -0700
Subject: [PATCH 837/949] fix jenkins (#31)

---
 vta/Jenkinsfile                       |  4 ++--
 vta/README.md                         |  2 +-
 vta/docs/api/python/index.rst         | 28 +++++++++++++++++++++++++++
 vta/python/vta/__init__.py            |  7 ++++++-
 vta/tests/scripts/task_python_docs.sh |  5 +++++
 5 files changed, 42 insertions(+), 4 deletions(-)
 create mode 100644 vta/docs/api/python/index.rst

diff --git a/vta/Jenkinsfile b/vta/Jenkinsfile
index 90dfc27af434..a1f59206f29c 100644
--- a/vta/Jenkinsfile
+++ b/vta/Jenkinsfile
@@ -4,8 +4,8 @@
 // See documents at https://jenkins.io/doc/book/pipeline/jenkinsfile/
 
 // nnvm libraries
-vta_lib += "lib/libvta.so, lib/libvta.so.json"
-vta_lib += ", nnvm/tvm/lib/libtvm.so, nnvm/tvm/lib/libtopi.so, nnvm/lib/libnnvm_compiler.so"
+vta_lib = "lib/libvta.so, lib/libvta.so.json"
+vta_lib += ", nnvm/tvm/lib/libtvm.so, nnvm/tvm/lib/libtvm_topi.so, nnvm/lib/libnnvm_compiler.so"
 
 
 // command to start a docker container
diff --git a/vta/README.md b/vta/README.md
index 5408f3b950b1..cd3a490ccb6f 100644
--- a/vta/README.md
+++ b/vta/README.md
@@ -1,6 +1,6 @@
 VTA: Open, Modular, Deep Learning Accelerator Stack
 ===================================================
-
+[![Build Status](http://mode-gpu.cs.washington.edu:8080/buildStatus/icon?job=uwsaml/vta/master)](http://mode-gpu.cs.washington.edu:8080/job/uwsaml/job/vta/job/master/)
 [![GitHub license](http://dmlc.github.io/img/apache2.svg)](./LICENSE)
 
 VTA(versatile tensor accelerator) is an open-source deep learning accelerator stack.
diff --git a/vta/docs/api/python/index.rst b/vta/docs/api/python/index.rst
new file mode 100644
index 000000000000..4555a739a8d8
--- /dev/null
+++ b/vta/docs/api/python/index.rst
@@ -0,0 +1,28 @@
+Python API
+==========
+
+This document contains the python API to VTA compiler toolchain.
+
+.. automodule:: vta
+
+Hardware Information
+--------------------
+
+.. autofunction:: vta.Environment
+.. autofunction:: vta.get_env
+
+RPC Utilities
+-------------
+
+.. autofunction:: vta.reconfig_runtime
+.. autofunction:: vta.program_fpga
+
+
+Compiler API
+------------
+We program VTA using TVM, so the compiler API in vta package
+is only a thin wrapper to provide VTA specific extensions.
+
+.. autofunction:: vta.build_config
+.. autofunction:: vta.build
+.. autofunction:: vta.lower
diff --git a/vta/python/vta/__init__.py b/vta/python/vta/__init__.py
index c56e9a444ac0..37c2e7f34792 100644
--- a/vta/python/vta/__init__.py
+++ b/vta/python/vta/__init__.py
@@ -1,4 +1,9 @@
-"""TVM-based VTA Compiler Toolchain"""
+"""VTA Package is a TVM backend extension to support VTA hardwares
+
+Besides the compiler toolchain.
+It also include utility functions to
+configure the hardware Environment and  access remote through RPC
+"""
 from __future__ import absolute_import as _abs
 
 __version__ = "0.1.0"
diff --git a/vta/tests/scripts/task_python_docs.sh b/vta/tests/scripts/task_python_docs.sh
index 8f8b4804bdcc..7fac9f692c8a 100755
--- a/vta/tests/scripts/task_python_docs.sh
+++ b/vta/tests/scripts/task_python_docs.sh
@@ -1,4 +1,9 @@
 #!/bin/bash
+cd nnvm/tvm
+make cython
+make cython3
+cd ../../
+
 mkdir -p docs/_build/html
 # C++ doc
 make doc

From 8ae7ae83bf98786bce15f00fd6a6766351280238 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sun, 15 Apr 2018 15:19:04 -0700
Subject: [PATCH 838/949] [COMPILER] Upgrade to meet latest TVM IR pragma
 convention (#32)

---
 vta/python/vta/ir_pass.py | 27 +++++++++++++++++++++------
 1 file changed, 21 insertions(+), 6 deletions(-)

diff --git a/vta/python/vta/ir_pass.py b/vta/python/vta/ir_pass.py
index 9310d46dce88..ff18bf5bd0f1 100644
--- a/vta/python/vta/ir_pass.py
+++ b/vta/python/vta/ir_pass.py
@@ -8,6 +8,21 @@
 from .environment import get_env
 
 
+def _match_pragma(stmt, key):
+    """Internal helper to match stmt to pragma stmt.
+
+    Parameters
+    ----------
+    stmt : Stmt
+        The AttrStmt
+
+    key : str
+        The pragma key
+    """
+    return ((stmt.attr_key == "pragma_" + key) or
+            (stmt.attr_key == "pragma_scope" and stmt.value.value == key))
+
+
 def fold_uop_loop(stmt_in):
     """Detect and fold uop loop.
 
@@ -255,7 +270,7 @@ def inject_skip_copy(stmt_in):
         Transformed statement
     """
     def _do_fold(stmt):
-        if (stmt.attr_key == "pragma_scope" and stmt.value.value == "skip_dma_copy"):
+        if _match_pragma(stmt, "skip_dma_copy"):
             return tvm.make.Evaluate(0)
         return None
     return tvm.ir_pass.IRTransform(
@@ -277,12 +292,12 @@ def inject_coproc_sync(stmt_in):
     """
     success = [False]
     def _do_fold(stmt):
-        if stmt.attr_key == "pragma_scope" and stmt.value.value == "coproc_sync":
+        if _match_pragma(stmt, "coproc_sync"):
             success[0] = True
             sync = tvm.make.Call(
                 "int32", "vta.coproc_sync", [], tvm.expr.Call.Intrinsic, None, 0)
             return tvm.make.Block(stmt.body, tvm.make.Evaluate(sync))
-        elif stmt.attr_key == "pragma_scope" and stmt.value.value == "trim_loop":
+        elif _match_pragma(stmt, "trim_loop"):
             op = stmt.body
             assert isinstance(op, tvm.stmt.For)
             return tvm.make.For(
@@ -561,7 +576,7 @@ def annotate_alu_coproc_scope(stmt_in):
     """
     env = get_env()
     def _do_fold(stmt):
-        if (stmt.attr_key == "pragma_scope" and stmt.value.value == "alu"):
+        if _match_pragma(stmt, "alu"):
             irb = tvm.ir_builder.create()
             irb.scope_attr(env.dev.vta_axis, "coproc_scope",
                            env.dev.get_task_qid(env.dev.QID_COMPUTE))
@@ -569,7 +584,7 @@ def _do_fold(stmt):
                            tvm.make.StringImm("VTAPushALUOp"))
             irb.emit(stmt)
             return irb.get()
-        elif (stmt.attr_key == "pragma_scope" and stmt.value.value == "skip_alu"):
+        elif _match_pragma(stmt, "skip_alu"):
             return tvm.make.Evaluate(0)
         return stmt
 
@@ -631,7 +646,7 @@ def _flatten_loop(src_coeff, dst_coeff, extents):
 
             return rev_src_coeff, rev_dst_coeff, rev_extents
 
-        if (stmt.attr_key == "pragma_scope" and stmt.value.value == "alu"):
+        if _match_pragma(stmt, "alu"):
             # Get to the innermost loop body
             loop_body = stmt.body
             nest_size = 0

From ff226da1974652a9cea6d1b1ba9598a90896dc96 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 1 May 2018 08:48:34 -0700
Subject: [PATCH 839/949] [PYTHON] Enable environment scoping (#33)

---
 vta/python/vta/environment.py                 | 9 +++++++++
 vta/tests/python/unittest/test_environment.py | 9 +++++++++
 2 files changed, 18 insertions(+)

diff --git a/vta/python/vta/environment.py b/vta/python/vta/environment.py
index 36139a39c8a9..494e27c86d03 100644
--- a/vta/python/vta/environment.py
+++ b/vta/python/vta/environment.py
@@ -143,6 +143,15 @@ def __init__(self, cfg):
         self.mock_mode = False
         self._mock_env = None
         self._dev_ctx = None
+        self._last_env = None
+
+    def __enter__(self):
+        self._last_env = Environment.current
+        Environment.current = self
+        return self
+
+    def __exit__(self, ptype, value, trace):
+        Environment.current = self._last_env
 
     def pkg_config(self):
         """PkgConfig instance"""
diff --git a/vta/tests/python/unittest/test_environment.py b/vta/tests/python/unittest/test_environment.py
index d97f107f37c4..9d6597abe200 100644
--- a/vta/tests/python/unittest/test_environment.py
+++ b/vta/tests/python/unittest/test_environment.py
@@ -6,6 +6,15 @@ def test_env():
     mock = env.mock
     assert mock.alu == "skip_alu"
 
+def test_env_scope():
+    env = vta.get_env()
+    cfg = env.pkg_config().cfg_dict
+    cfg["TARGET"] = "xyz"
+    with vta.Environment(cfg):
+        assert vta.get_env().TARGET == "xyz"
+    assert vta.get_env().TARGET == env.TARGET
+
 
 if __name__ == "__main__":
     test_env()
+    test_env_scope()

From b78471c06d983df0ed391ac45dae8c4efc8e6d82 Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@cs.washington.edu>
Date: Wed, 2 May 2018 18:13:33 -0700
Subject: [PATCH 840/949] [HARDWARE, TEST] Fixed hardware generation flow (#34)

---
 vta/apps/pynq_rpc/start_rpc_server.sh  |   2 +-
 vta/hardware/xilinx/Makefile           |  71 ++++--
 vta/hardware/xilinx/scripts/hls.tcl    |  83 +++---
 vta/hardware/xilinx/scripts/vivado.tcl |  18 +-
 vta/hardware/xilinx/sim/vta_test.cc    |   3 +
 vta/make/config.json                   |   4 +-
 vta/make/sim_sample.json               |   4 +-
 vta/make/vta_config.py                 |  80 +++++-
 vta/python/vta/environment.py          |   6 +-
 vta/python/vta/ir_pass.py              |   8 +-
 vta/tests/hardware/common/test_lib.cc  | 334 ++++++++++++++++++++++++-
 vta/tests/hardware/common/test_lib.h   |  16 +-
 vta/tests/hardware/pynq/Makefile       |  19 +-
 vta/tests/hardware/pynq/metal_test.cc  | 100 +-------
 14 files changed, 550 insertions(+), 198 deletions(-)

diff --git a/vta/apps/pynq_rpc/start_rpc_server.sh b/vta/apps/pynq_rpc/start_rpc_server.sh
index 445b72ea797d..fac12e82bb55 100755
--- a/vta/apps/pynq_rpc/start_rpc_server.sh
+++ b/vta/apps/pynq_rpc/start_rpc_server.sh
@@ -1,4 +1,4 @@
 #!/bin/bash
-export PYTHONPATH=${PYTHONPATH}:/home/xilinx/tvm/python:/home/xilinx/vta/python
+export PYTHONPATH=${PYTHONPATH}:/home/xilinx/vta/nnvm/tvm/python:/home/xilinx/vta/python
 export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/opt/python3.6/lib/python3.6/site-packages/pynq/drivers/
 python -m vta.exec.rpc_server
diff --git a/vta/hardware/xilinx/Makefile b/vta/hardware/xilinx/Makefile
index 8208de7d010d..9ed7654edb84 100644
--- a/vta/hardware/xilinx/Makefile
+++ b/vta/hardware/xilinx/Makefile
@@ -13,8 +13,10 @@ VIVADO_HLS = vivado_hls
 VIVADO = vivado
 HSI = hsi
 
-# HLS Mode
-MODE = all
+# HLS mode
+MODE = skip_sim
+# Debug flag
+DEBUG = false
 # SLURM
 SLURM = false
 # Prevent generation of DSP
@@ -22,15 +24,26 @@ NO_DSP = false
 # Prevent generation of ALU
 NO_ALU = false
 
-# Include top-level config file
-ifndef config
-ifneq ("$(wildcard ../../config.mk)", "")
-	config = ../../config.mk
-else
-	config = ../../make/config.mk
-endif
-endif
-include $(config)
+# Process VTA JSON config
+VTA_CONFIG = python $(CURDIR)/../../make/vta_config.py
+CFLAGS := $(shell ${VTA_CONFIG} --cflags)
+VTA_TARGET := $(shell ${VTA_CONFIG} --target)
+
+#---------------------
+# VTA Parameters
+#--------------------
+VTA_INP_WIDTH := $(shell ${VTA_CONFIG} --get-inpwidth)
+VTA_WGT_WIDTH := $(shell ${VTA_CONFIG} --get-wgtwidth)
+VTA_ACC_WIDTH := $(shell ${VTA_CONFIG} --get-accwidth)
+VTA_OUT_WIDTH := $(shell ${VTA_CONFIG} --get-outwidth)
+VTA_BATCH := $(shell ${VTA_CONFIG} --get-batch)
+VTA_IN_BLOCK := $(shell ${VTA_CONFIG} --get-blockin)
+VTA_OUT_BLOCK := $(shell ${VTA_CONFIG} --get-blockout)
+VTA_UOP_BUFF_SIZE := $(shell ${VTA_CONFIG} --get-uopbuffsize)
+VTA_INP_BUFF_SIZE := $(shell ${VTA_CONFIG} --get-inpbuffsize)
+VTA_WGT_BUFF_SIZE := $(shell ${VTA_CONFIG} --get-wgtbuffsize)
+VTA_ACC_BUFF_SIZE := $(shell ${VTA_CONFIG} --get-accbuffsize)
+VTA_OUT_BUFF_SIZE := $(shell ${VTA_CONFIG} --get-outbuffsize)
 
 #---------------------
 # Compilation parameters
@@ -50,8 +63,8 @@ TARGET_PER = \
 $(shell echo "$$(( (1000 + $(VTA_HW_COMP_CLOCK_FREQ) - 1) / $(VTA_HW_COMP_CLOCK_FREQ) - $(VTA_HW_COMP_TIMING_COMP)))" )
 
 # Derive config name
-CONF = \
-$(VTA_BATCH)x$(VTA_IN_BLOCK)x$(VTA_OUT_BLOCK)_$(VTA_INP_WIDTH)bx$(VTA_WGT_WIDTH)b_$(VTA_LOG_UOP_BUFF_SIZE)_$(VTA_LOG_INP_BUFF_SIZE)_$(VTA_LOG_WGT_BUFF_SIZE)_$(VTA_LOG_ACC_BUFF_SIZE)_$(VTA_HW_COMP_CLOCK_FREQ)MHz_$(TARGET_PER)ns
+CONF_ROOT = $(shell ${VTA_CONFIG} --cfg-str)
+CONF = $(CONF_ROOT)_$(VTA_HW_COMP_CLOCK_FREQ)MHz_$(TARGET_PER)ns
 IP_BUILD_PATH = $(BUILD_DIR)/hls/$(CONF)
 HW_BUILD_PATH = $(BUILD_DIR)/vivado/$(CONF)
 
@@ -60,26 +73,34 @@ ifeq ($(SLURM), true)
 	HW_BUILD_PATH = /scratch/vivado/$(CONF)
 endif
 
-.PHONY: all ip bit driver clean clean_all
+# IP file path
+IP_PATH = $(BUILD_DIR)/hls/$(CONF)/solution0/impl/ip/xilinx_com_hls_vta_1_0.zip
+
+# Bitstream file path
+BIT_PATH = $(BUILD_DIR)/vivado/$(CONF)/export/$(CONF).bit
 
-all: bit
+.PHONY: all ip bit bsp clean clean_all
 
-ip: 
+all: bsp
+ip: $(IP_PATH)
+bit: $(BIT_PATH)
+
+$(IP_PATH): $(SRC_DIR)/*
 	mkdir -p $(IP_BUILD_PATH)
 	cd $(IP_BUILD_PATH) && \
 		$(VIVADO_HLS) -f $(SCRIPT_DIR)/hls.tcl \
-			-tclargs $(SRC_DIR) $(SIM_DIR) $(TEST_DIR) $(INCLUDE_DIR) $(TARGET_PER) \
-			$(VTA_LOG_INP_WIDTH) $(VTA_LOG_WGT_WIDTH) $(VTA_LOG_ACC_WIDTH) $(VTA_LOG_OUT_WIDTH) \
-			$(VTA_LOG_BATCH) $(VTA_LOG_BLOCK_OUT) $(VTA_LOG_BLOCK_IN) \
-			$(VTA_LOG_UOP_BUFF_SIZE) $(VTA_LOG_INP_BUFF_SIZE) $(VTA_LOG_WGT_BUFF_SIZE) \
-			$(VTA_LOG_ACC_BUFF_SIZE) $(VTA_LOG_OUT_BUFF_SIZE) \
-			$(MODE) $(NO_DSP) $(NO_ALU)
+		-tclargs $(SRC_DIR) $(SIM_DIR) $(TEST_DIR) $(INCLUDE_DIR) \
+		$(MODE) $(DEBUG) $(NO_DSP) $(NO_ALU) $(TARGET_PER) \
+		$(VTA_INP_WIDTH) $(VTA_WGT_WIDTH) $(VTA_ACC_WIDTH) $(VTA_OUT_WIDTH) \
+		$(VTA_BATCH) $(VTA_IN_BLOCK) $(VTA_OUT_BLOCK) \
+		$(VTA_UOP_BUFF_SIZE) $(VTA_INP_BUFF_SIZE) $(VTA_WGT_BUFF_SIZE) \
+		$(VTA_ACC_BUFF_SIZE) $(VTA_OUT_BUFF_SIZE)
 ifeq ($(SLURM), true)
 	mkdir -p $(BUILD_DIR)/hls
 	mv $(IP_BUILD_PATH) $(BUILD_DIR)/hls/.
 endif
 
-bit: ip
+$(BIT_PATH): $(IP_PATH)
 	mkdir -p $(HW_BUILD_PATH)
 	cd $(HW_BUILD_PATH) && \
 		$(VIVADO) -mode tcl -source $(SCRIPT_DIR)/vivado.tcl \
@@ -92,12 +113,12 @@ ifeq ($(SLURM), true)
 	mv $(HW_BUILD_PATH) $(BUILD_DIR)/vivado/.
 endif
 
-driver: bit
+bsp: $(BIT_PATH)
 	cd $(HW_BUILD_PATH) && $(HSI) -mode tcl -source $(SCRIPT_DIR)/hsi.tcl -nojournal -nolog
 	cd $(HW_BUILD_PATH)/bsp && make
 
 clean:
 	rm -rf *.out *.log *.sb figures
 
-clean_all: clean
+cleanall: clean
 	rm -rf $(BUILD_DIR)
diff --git a/vta/hardware/xilinx/scripts/hls.tcl b/vta/hardware/xilinx/scripts/hls.tcl
index a1270189db4c..57efe9c28132 100644
--- a/vta/hardware/xilinx/scripts/hls.tcl
+++ b/vta/hardware/xilinx/scripts/hls.tcl
@@ -9,65 +9,69 @@
 # Arg 2: path to sim sources
 # Arg 3: path to test sources
 # Arg 4: path to include sources
-# Arg 5: target clock period
-# Arg 6: input type width (log)
-# Arg 7: weight type width (log)
-# Arg 8: accum type width (log)
-# Arg 9: output type width (log)
-# Arg 10: batch size (log)
-# Arg 11: in block size (log)
-# Arg 12: out block size (log)
-# Arg 13: uop buffer size in B (log)
-# Arg 14: inp buffer size in B (log)
-# Arg 15: wgt buffer size in B (log)
-# Arg 16: acc buffer size in B (log)
-# Arg 17: out buffer size in B (log)
-# Arg 18: mode
-# Arg 19: no_dsp
-# Arg 20: no_alu
+# Arg 5: mode
+# Arg 6: debug
+# Arg 7: no_dsp
+# Arg 8: no_alu
+# Arg 9: target clock period
+# Arg 10: input type width (log)
+# Arg 11: weight type width (log)
+# Arg 12: accum type width (log)
+# Arg 13: output type width (log)
+# Arg 14: batch size (log)
+# Arg 15: in block size (log)
+# Arg 16: out block size (log)
+# Arg 17: uop buffer size in B (log)
+# Arg 18: inp buffer size in B (log)
+# Arg 19: wgt buffer size in B (log)
+# Arg 20: acc buffer size in B (log)
+# Arg 21: out buffer size in B (log)
 
-if { [llength $argv] eq 22 } {
+if { [llength $argv] eq 23 } {
 	set src_dir [lindex $argv 2]
 	set sim_dir [lindex $argv 3]
 	set test_dir [lindex $argv 4]
 	set include_dir [lindex $argv 5]
-	set target_period [lindex $argv 6]
-	set inp_width [lindex $argv 7]
-	set wgt_width [lindex $argv 8]
-	set acc_width [lindex $argv 9]
-	set out_width [lindex $argv 10]
-	set batch [lindex $argv 11]
-	set block_in [lindex $argv 12]
-	set block_out [lindex $argv 13]
-	set uop_buff_size [lindex $argv 14]
-	set inp_buff_size [lindex $argv 15]
-	set wgt_buff_size [lindex $argv 16]
-	set acc_buff_size [lindex $argv 17]
-	set out_buff_size [lindex $argv 18]
-	set mode [lindex $argv 19]
-	set no_dsp [lindex $argv 20]
-	set no_alu [lindex $argv 21]
+	set mode [lindex $argv 6]
+	set debug [lindex $argv 7]
+	set no_dsp [lindex $argv 8]
+	set no_alu [lindex $argv 9]
+	set target_period [lindex $argv 10]
+	set inp_width [lindex $argv 11]
+	set wgt_width [lindex $argv 12]
+	set acc_width [lindex $argv 13]
+	set out_width [lindex $argv 14]
+	set batch [lindex $argv 15]
+	set block_in [lindex $argv 16]
+	set block_out [lindex $argv 17]
+	set uop_buff_size [lindex $argv 18]
+	set inp_buff_size [lindex $argv 19]
+	set wgt_buff_size [lindex $argv 20]
+	set acc_buff_size [lindex $argv 21]
+	set out_buff_size [lindex $argv 22]
 } else {
 	set src_dir "../src"
 	set sim_dir "../sim"
 	set test_dir "../../src/test"
 	set include_dir "../../include"
+	set mode "all"
+	set debug "false"
+	set no_dsp "true"
+	set no_alu "false"
 	set target_period 10
 	set inp_width 3
 	set wgt_width 3
 	set acc_width 5
 	set out_width 3
 	set batch 1
-	set block_out 4
 	set block_in 4
+	set block_out 4
 	set uop_buff_size 15
 	set inp_buff_size 15
 	set wgt_buff_size 15
 	set acc_buff_size 17
 	set out_buff_size 15
-	set mode "all"
-	set no_dsp "true"
-	set no_alu "false"
+	exit
 }
 
 # Initializes the HLS design and sets HLS pragmas for memory partitioning.
@@ -124,12 +128,15 @@ proc init_design {per inp_width wgt_width out_width batch block_in block_out} {
 
 # C define flags to pass to compiler
 set cflags "-I $include_dir -I $src_dir -I $test_dir \
-	-DVTA_DEBUG=0 -DVTA_LOG_WGT_WIDTH=$wgt_width -DVTA_LOG_INP_WIDTH=$inp_width \
+	-DVTA_LOG_WGT_WIDTH=$wgt_width -DVTA_LOG_INP_WIDTH=$inp_width \
 	-DVTA_LOG_ACC_WIDTH=$acc_width -DVTA_LOG_OUT_WIDTH=$out_width \
 	-DVTA_LOG_BATCH=$batch -DVTA_LOG_BLOCK_OUT=$block_out -DVTA_LOG_BLOCK_IN=$block_in \
 	-DVTA_LOG_UOP_BUFF_SIZE=$uop_buff_size -DVTA_LOG_INP_BUFF_SIZE=$inp_buff_size \
 	-DVTA_LOG_WGT_BUFF_SIZE=$wgt_buff_size -DVTA_LOG_ACC_BUFF_SIZE=$acc_buff_size \
 	-DVTA_LOG_OUT_BUFF_SIZE=$out_buff_size"
+if {$debug=="true"} {
+	append cflags " -DVTA_DEBUG=1"
+}
 if {$no_dsp=="true"} {
 	append cflags " -DNO_DSP"
 }
diff --git a/vta/hardware/xilinx/scripts/vivado.tcl b/vta/hardware/xilinx/scripts/vivado.tcl
index bdfd24c0ab09..58a1f5008e6d 100644
--- a/vta/hardware/xilinx/scripts/vivado.tcl
+++ b/vta/hardware/xilinx/scripts/vivado.tcl
@@ -26,15 +26,15 @@ if { [llength $argv] eq 12 } {
   set ip_path [lindex $argv 0]
   set num_threads [lindex $argv 1]
   set clock_freq [lindex $argv 2]
-  set inp_width [lindex $argv 3]
-  set wgt_width [lindex $argv 4]
-  set out_width [lindex $argv 5]
-  set batch [lindex $argv 6]
-  set out_block [lindex $argv 7]
-  set in_block [lindex $argv 8]
-  set inp_mem_size [lindex $argv 9]
-  set wgt_mem_size [lindex $argv 10]
-  set out_mem_size [lindex $argv 11]
+  set inp_width [expr 1 << [lindex $argv 3]]
+  set wgt_width [expr 1 << [lindex $argv 4]]
+  set out_width [expr 1 << [lindex $argv 5]]
+  set batch [expr 1 << [lindex $argv 6]]
+  set out_block [expr 1 << [lindex $argv 7]]
+  set in_block [expr 1 << [lindex $argv 8]]
+  set inp_mem_size [expr 1 << [lindex $argv 9]]
+  set wgt_mem_size [expr 1 << [lindex $argv 10]]
+  set out_mem_size [expr 1 << [lindex $argv 11]]
   if {$clock_freq eq 100} {
     set clock_id 0
     puts "Setting clock frequency to 100MHz"
diff --git a/vta/hardware/xilinx/sim/vta_test.cc b/vta/hardware/xilinx/sim/vta_test.cc
index 79f4a1f676ff..e1c28834a691 100644
--- a/vta/hardware/xilinx/sim/vta_test.cc
+++ b/vta/hardware/xilinx/sim/vta_test.cc
@@ -53,5 +53,8 @@ int main(void) {
     status |= blocked_gemm_test(256, 256, VTA_BLOCK_OUT*4, true, 1);
     status |= blocked_gemm_test(256, 256, VTA_BLOCK_OUT*4, false, 1);
 
+    // Simple GEMM unit test
+    status |= gemm_test(64, 64, 64, true);
+
     return status;
 }
diff --git a/vta/make/config.json b/vta/make/config.json
index 8a7dbcf6a8f7..c64473e74489 100644
--- a/vta/make/config.json
+++ b/vta/make/config.json
@@ -7,8 +7,8 @@
   "LOG_BATCH" : 0,
   "LOG_BLOCK_IN" : 4,
   "LOG_BLOCK_OUT" : 4,
-  "LOG_UOP_BUFF_SIZE" : 15,
+  "LOG_UOP_BUFF_SIZE" : 14,
   "LOG_INP_BUFF_SIZE" : 15,
-  "LOG_WGT_BUFF_SIZE" : 15,
+  "LOG_WGT_BUFF_SIZE" : 18,
   "LOG_ACC_BUFF_SIZE" : 17
 }
diff --git a/vta/make/sim_sample.json b/vta/make/sim_sample.json
index 57aa89ad9547..747e22fce265 100644
--- a/vta/make/sim_sample.json
+++ b/vta/make/sim_sample.json
@@ -7,8 +7,8 @@
   "LOG_BATCH" : 0,
   "LOG_BLOCK_IN" : 4,
   "LOG_BLOCK_OUT" : 4,
-  "LOG_UOP_BUFF_SIZE" : 15,
+  "LOG_UOP_BUFF_SIZE" : 14,
   "LOG_INP_BUFF_SIZE" : 15,
-  "LOG_WGT_BUFF_SIZE" : 15,
+  "LOG_WGT_BUFF_SIZE" : 18,
   "LOG_ACC_BUFF_SIZE" : 17
 }
diff --git a/vta/make/vta_config.py b/vta/make/vta_config.py
index a25a9ca8d51c..c64a11f6a594 100644
--- a/vta/make/vta_config.py
+++ b/vta/make/vta_config.py
@@ -28,6 +28,32 @@ def main():
                         help="print all the config json")
     parser.add_argument("--target", action="store_true",
                         help="print the target")
+    parser.add_argument("--cfg-str", action="store_true",
+                        help="print the configuration string")
+    parser.add_argument("--get-inpwidth", action="store_true",
+                        help="returns log of input bitwidth")
+    parser.add_argument("--get-wgtwidth", action="store_true",
+                        help="returns log of weight bitwidth")
+    parser.add_argument("--get-accwidth", action="store_true",
+                        help="returns log of accum bitwidth")
+    parser.add_argument("--get-outwidth", action="store_true",
+                        help="returns log of output bitwidth")
+    parser.add_argument("--get-batch", action="store_true",
+                        help="returns log of tensor batch dimension")
+    parser.add_argument("--get-blockin", action="store_true",
+                        help="returns log of tensor block in dimension")
+    parser.add_argument("--get-blockout", action="store_true",
+                        help="returns log of tensor block out dimension")
+    parser.add_argument("--get-uopbuffsize", action="store_true",
+                        help="returns log of micro-op buffer size in B")
+    parser.add_argument("--get-inpbuffsize", action="store_true",
+                        help="returns log of input buffer size in B")
+    parser.add_argument("--get-wgtbuffsize", action="store_true",
+                        help="returns log of weight buffer size in B")
+    parser.add_argument("--get-accbuffsize", action="store_true",
+                        help="returns log of accum buffer size in B")
+    parser.add_argument("--get-outbuffsize", action="store_true",
+                        help="returns log of output buffer size in B")
     args = parser.parse_args()
 
     if len(sys.argv) == 1:
@@ -46,13 +72,17 @@ def main():
         raise RuntimeError("Cannot find config in %s" % str(path_list))
     cfg = json.load(open(ok_path_list[0]))
     cfg["LOG_OUT_WIDTH"] = cfg["LOG_INP_WIDTH"]
+    cfg["LOG_OUT_BUFF_SIZE"] = cfg["LOG_ACC_BUFF_SIZE"] + cfg["LOG_ACC_WIDTH"] - cfg["LOG_OUT_WIDTH"]
     pkg = get_pkg_config(cfg)
 
     if args.target:
         print(pkg.target)
 
     if args.cflags:
-        print(" ".join(pkg.cflags))
+        cflags_str = " ".join(pkg.cflags)
+        if cfg["TARGET"] == "pynq":
+            cflags_str += " -DVTA_TARGET_PYNQ"
+        print(cflags_str)
 
     if args.ldflags:
         print(" ".join(pkg.ldflags))
@@ -60,6 +90,54 @@ def main():
     if args.cfg_json:
         print(pkg.cfg_json)
 
+    if args.cfg_str:
+        cfg_str = "{}x{}x{}_{}bx{}b_{}_{}_{}_{}".format(
+            (1 << cfg["LOG_BATCH"]),
+            (1 << cfg["LOG_BLOCK_IN"]),
+            (1 << cfg["LOG_BLOCK_OUT"]),
+            (1 << cfg["LOG_INP_WIDTH"]),
+            (1 << cfg["LOG_WGT_WIDTH"]),
+            cfg["LOG_UOP_BUFF_SIZE"],
+            cfg["LOG_INP_BUFF_SIZE"],
+            cfg["LOG_WGT_BUFF_SIZE"],
+            cfg["LOG_ACC_BUFF_SIZE"])
+        print cfg_str
+
+    if args.get_inpwidth:
+        print(cfg["LOG_INP_WIDTH"])
+
+    if args.get_wgtwidth:
+        print(cfg["LOG_WGT_WIDTH"])
+
+    if args.get_accwidth:
+        print(cfg["LOG_ACC_WIDTH"])
+
+    if args.get_outwidth:
+        print(cfg["LOG_OUT_WIDTH"])
+
+    if args.get_batch:
+        print(cfg["LOG_BATCH"])
+
+    if args.get_blockin:
+        print(cfg["LOG_BLOCK_IN"])
+
+    if args.get_blockout:
+        print(cfg["LOG_BLOCK_OUT"])
+
+    if args.get_uopbuffsize:
+        print(cfg["LOG_UOP_BUFF_SIZE"])
+
+    if args.get_inpbuffsize:
+        print(cfg["LOG_INP_BUFF_SIZE"])
+
+    if args.get_wgtbuffsize:
+        print(cfg["LOG_WGT_BUFF_SIZE"])
+
+    if args.get_outbuffsize:
+        print(cfg["LOG_OUT_BUFF_SIZE"])
+
+    if args.get_accbuffsize:
+        print(cfg["LOG_ACC_BUFF_SIZE"])
 
 if __name__ == "__main__":
     main()
diff --git a/vta/python/vta/environment.py b/vta/python/vta/environment.py
index 494e27c86d03..feaacbe4017d 100644
--- a/vta/python/vta/environment.py
+++ b/vta/python/vta/environment.py
@@ -130,11 +130,15 @@ def __init__(self, cfg):
                               self.BLOCK_IN *
                               self.WGT_WIDTH)
         self.ACC_ELEM_BITS = (self.BATCH *
-                              self.BLOCK_IN *
+                              self.BLOCK_OUT *
                               self.ACC_WIDTH)
+        self.OUT_ELEM_BITS = (self.BATCH *
+                              self.BLOCK_OUT *
+                              self.OUT_WIDTH)
         self.INP_ELEM_BYTES = self.INP_ELEM_BITS // 8
         self.WGT_ELEM_BYTES = self.WGT_ELEM_BITS // 8
         self.ACC_ELEM_BYTES = self.ACC_ELEM_BITS // 8
+        self.OUT_ELEM_BYTES = self.OUT_ELEM_BITS // 8
         # dtypes
         self.acc_dtype = "int%d" % self.ACC_WIDTH
         self.inp_dtype = "int%d" % self.INP_WIDTH
diff --git a/vta/python/vta/ir_pass.py b/vta/python/vta/ir_pass.py
index ff18bf5bd0f1..07000e2ccf64 100644
--- a/vta/python/vta/ir_pass.py
+++ b/vta/python/vta/ir_pass.py
@@ -339,7 +339,7 @@ def _fold_buffer_dim(buf, scope, elem_block):
         base = 0
         for i in range(1, ndim + 1):
             if not util.equal_const_int(buf.strides[ndim - i] - x_size, 0):
-                raise RuntimeError("scope %s need need to have block=%d" % (scope, elem_block))
+                raise RuntimeError("scope %s needs to have block=%d" % (scope, elem_block))
             x_size = x_size * buf.shape[ndim - i]
             if util.equal_const_int(x_size - elem_block, 0):
                 base = i + 1
@@ -469,10 +469,10 @@ def _inject_copy(src, dst, pad_before, pad_after, pad_value):
             if pad_before or pad_after:
                 raise RuntimeError("Do not support copy into DRAM with pad")
             if src.scope == env.acc_scope:
-                elem_width = env.INP_WIDTH # output compression to inp type
-                elem_bytes = env.INP_ELEM_BYTES # output compression to inp type
+                elem_width = env.OUT_WIDTH
+                elem_bytes = env.OUT_ELEM_BYTES
                 mem_type = env.dev.MEM_ID_OUT
-                data_type = "int%d" % env.INP_WIDTH
+                data_type = "int%d" % env.OUT_WIDTH
                 task_qid = env.dev.QID_STORE_OUT
             else:
                 raise RuntimeError("Do not support copy %s->dram" % (src.scope))
diff --git a/vta/tests/hardware/common/test_lib.cc b/vta/tests/hardware/common/test_lib.cc
index ab4e8fcc4218..1cb845765583 100644
--- a/vta/tests/hardware/common/test_lib.cc
+++ b/vta/tests/hardware/common/test_lib.cc
@@ -1,11 +1,114 @@
 /*!
  *  Copyright (c) 2018 by Contributors
- * \file vta_test_lib.cpp
+ * \file test_lib.cpp
  * \brief Test library for the VTA design simulation and driver tests.
  */
 
 #include "./test_lib.h"
 
+#ifdef NO_SIM
+#ifdef VTA_TARGET_PYNQ
+
+uint64_t vta(
+  uint32_t insn_count,
+  VTAGenericInsn *insns,
+  VTAUop *uops,
+  inp_T *inputs,
+  wgt_T *weights,
+  acc_T *biases,
+  inp_T *outputs) {
+  // Performance counter variables
+  uint64_t t_fpga;
+  struct timespec start, stop;
+
+  // Derive bitstream file
+  char bitstream[128];
+  char str_batch_size[4];
+  char str_block_out_size[4];
+  char str_block_in_size[4];
+  char str_block_bit_width[4];
+  snprintf(str_batch_size, sizeof(str_batch_size), "%d", VTA_BATCH);
+  snprintf(str_block_out_size, sizeof(str_block_out_size), "%d", VTA_BLOCK_OUT);
+  snprintf(str_block_in_size, sizeof(str_block_in_size), "%d", VTA_BLOCK_IN);
+  snprintf(str_block_bit_width, sizeof(str_block_bit_width), "%d", VTA_WGT_WIDTH);
+  snprintf(bitstream, sizeof(bitstream), "%s", "vta.bit");
+
+#if VTA_DEBUG == 1
+  printf("INFO - Programming FPGA: %s!\n", bitstream);
+#endif
+
+  // Program VTA
+  VTAProgram(bitstream);
+  // Get VTA handles
+  void* vta_fetch_handle = VTAMapRegister(VTA_FETCH_ADDR, VTA_RANGE);
+  void* vta_load_handle = VTAMapRegister(VTA_LOAD_ADDR, VTA_RANGE);
+  void* vta_compute_handle = VTAMapRegister(VTA_COMPUTE_ADDR, VTA_RANGE);
+  void* vta_store_handle = VTAMapRegister(VTA_STORE_ADDR, VTA_RANGE);
+
+  // Physical address pointers
+  uint32_t insn_phy = insns ? cma_get_phy_addr(insns) : 0;
+  uint32_t uop_phy = uops ? cma_get_phy_addr(uops) : 0;
+  uint32_t input_phy = inputs ? cma_get_phy_addr(inputs) : 0;
+  uint32_t weight_phy = weights ? cma_get_phy_addr(weights) : 0;
+  uint32_t bias_phy = biases ? cma_get_phy_addr(biases) : 0;
+  uint32_t output_phy = outputs ? cma_get_phy_addr(outputs) : 0;
+
+#if VTA_DEBUG == 1
+  printf("INFO - Starting FPGA!\n");
+#endif
+
+  clock_gettime(CLOCK_REALTIME, &start);
+
+  // FETCH @ 0x10 : Data signal of insn_count_V
+  VTAWriteMappedReg(vta_fetch_handle, 0x10, insn_count);
+  // FETCH @ 0x18 : Data signal of insns_V
+  if (insns) VTAWriteMappedReg(vta_fetch_handle, 0x18, insn_phy);
+  // LOAD @ 0x10 : Data signal of inputs_V
+  if (inputs) VTAWriteMappedReg(vta_load_handle, 0x10, input_phy);
+  // LOAD @ 0x18 : Data signal of weight_V
+  if (weights) VTAWriteMappedReg(vta_load_handle, 0x18, weight_phy);
+  // COMPUTE @ 0x20 : Data signal of uops_V
+  if (uops) VTAWriteMappedReg(vta_compute_handle, 0x20, uop_phy);
+  // COMPUTE @ 0x28 : Data signal of biases_V
+  if (biases) VTAWriteMappedReg(vta_compute_handle, 0x28, bias_phy);
+  // STORE @ 0x10 : Data signal of outputs_V
+  if (outputs) VTAWriteMappedReg(vta_store_handle, 0x10, output_phy);
+
+  // VTA start
+  VTAWriteMappedReg(vta_fetch_handle, 0x0, 0x1);
+  VTAWriteMappedReg(vta_load_handle, 0x0, 0x81);
+  VTAWriteMappedReg(vta_compute_handle, 0x0, 0x81);
+  VTAWriteMappedReg(vta_store_handle, 0x0, 0x81);
+
+  int flag = 0, t = 0;
+  for (t = 0; t < 10000000; ++t) {
+    flag = VTAReadMappedReg(vta_compute_handle, 0x18);
+    if (flag & VTA_DONE) break;
+  }
+
+  if (t == 10000000) {
+    printf("\tWARNING: VTA TIMEOUT!!!!\n");
+#if VTA_DEBUG == 1
+  } else {
+    printf("INFO - FPGA Finished!\n");
+#endif
+  }
+
+  clock_gettime(CLOCK_REALTIME, &stop);
+  t_fpga = 1000000000ULL * (stop.tv_sec - start.tv_sec) + (stop.tv_nsec - start.tv_nsec);
+
+  // Unmap VTA register
+  VTAUnmapRegister(vta_fetch_handle, VTA_RANGE);
+  VTAUnmapRegister(vta_load_handle, VTA_RANGE);
+  VTAUnmapRegister(vta_compute_handle, VTA_RANGE);
+  VTAUnmapRegister(vta_store_handle, VTA_RANGE);
+
+  return t_fpga;
+}
+
+#endif  // VTA_TARGET_PYNQ
+#endif  // NO_SIM
+
 uint32_t globalSeed;
 
 const char* getOpcodeString(int opcode, bool use_imm) {
@@ -1122,3 +1225,232 @@ int blocked_gemm_test(int batch, int channels, int block, bool uop_compression,
     return -1;
   }
 }
+
+
+int gemm_test(int batch, int in_channels, int out_channels, bool uop_compression) {
+  // Some assertions
+  assert(batch % VTA_BATCH == 0);
+  assert(in_channels % VTA_BLOCK_IN == 0);
+  assert(out_channels % VTA_BLOCK_OUT == 0);
+
+  printf("=====================================================================================\n");
+  printf("INFO - Blocked GEMM test: batch=%d, in_channels=%d, out_channels=%d, uop_comp=%d\n",
+         batch, in_channels, out_channels, uop_compression);
+
+  // Derive number of elements that need to be loaded/stored
+  int ins_size = 7;
+  int uop_size = uop_compression ?
+      batch / VTA_BATCH :
+      batch / VTA_BATCH * in_channels / VTA_BLOCK_IN * out_channels / VTA_BLOCK_OUT;
+  int inp_size = batch / VTA_BATCH * in_channels / VTA_BLOCK_IN;
+  int wgt_size = in_channels / VTA_BLOCK_IN * out_channels / VTA_BLOCK_OUT;
+  int out_size = batch / VTA_BATCH * out_channels / VTA_BLOCK_OUT;
+  // Make sure we don't exceed buffer bounds
+  assert(uop_size <= VTA_UOP_BUFF_DEPTH);
+  assert(inp_size <= VTA_INP_BUFF_DEPTH);
+  assert(wgt_size <= VTA_WGT_BUFF_DEPTH);
+  assert(out_size <= VTA_ACC_BUFF_DEPTH);
+
+  // Initialize instruction buffer
+  VTAGenericInsn *insn_buf =
+      static_cast<VTAGenericInsn *>(allocBuffer(sizeof(VTAGenericInsn) * ins_size));
+  int insn_idx = 0;
+
+  // Load uops
+  insn_buf[insn_idx++] = get1DLoadStoreInsn(
+      VTA_OPCODE_LOAD,
+      VTA_MEM_ID_UOP,
+      0,
+      0,
+      uop_size,
+      0,
+      0,
+      0,
+      0);
+  // Load bias
+  insn_buf[insn_idx++] = get1DLoadStoreInsn(
+      VTA_OPCODE_LOAD,                                    // opcode
+      VTA_MEM_ID_ACC,                                     // type
+      0,                                                  // sram offset
+      0,                                                  // dram offset
+      out_size,                                           // size
+      0,                                                  // pop prev dep
+      0,                                                  // pop next dep
+      1,                                                  // push prev dep
+      0);                                                 // push next dep
+  // Load weight block (pop next)
+  insn_buf[insn_idx++] = get1DLoadStoreInsn(
+      VTA_OPCODE_LOAD,                                    // opcode
+      VTA_MEM_ID_WGT,                                     // type
+      0,                                                  // sram offset
+      0,                                                  // dram offset
+      wgt_size,                                           // size
+      0,                                                  // pop prev dep
+      1,                                                  // pop next dep
+      0,                                                  // push prev dep
+      0);                                                 // push next dep
+  // Load input block (push next)
+  insn_buf[insn_idx++] = get1DLoadStoreInsn(
+      VTA_OPCODE_LOAD,                                    // opcode
+      VTA_MEM_ID_INP,                                     // type
+      0,                                                  // sram offset
+      0,                                                  // dram offset
+      inp_size,                                           // size
+      0,                                                  // pop prev dep
+      0,                                                  // pop next dep
+      0,                                                  // push prev dep
+      1);                                                 // push next dep
+  // Perform GEMM (pop prev, push prev if not last, push next if last)
+  insn_buf[insn_idx++] = getGEMMInsn(
+      0,                                                  // uop offset
+      batch / VTA_BATCH,                                  // batch
+      in_channels / VTA_BLOCK_IN,                         // in_channels
+      out_channels / VTA_BLOCK_OUT,                       // out_channels
+      uop_compression,                                    // uop_compression
+      1,                                                  // pop_prev_dep
+      0,                                                  // pop_next_dep
+      0,                                                  // push prev dep
+      1);                                                 // push_next_dep
+  // Store output block (pop prev, push prev if not last)
+  insn_buf[insn_idx++] = get1DLoadStoreInsn(
+      VTA_OPCODE_STORE,                                   // opcode
+      VTA_MEM_ID_OUT,                                     // type
+      0,                                                  // sram offset
+      0,                                                  // dram offset
+      out_size,                                           // size
+      1,                                                  // pop prev dep
+      0,                                                  // pop next dep
+      1,                                                  // push prev dep
+      0);                                                 // push next dep
+  // Finish
+  insn_buf[insn_idx++] = getFinishInsn(0, 1);
+
+  // Prepare the uop buffer
+  VTAUop * uop_buf = getGEMMUops(
+      batch / VTA_BATCH,
+      in_channels / VTA_BLOCK_IN,
+      out_channels / VTA_BLOCK_OUT,
+      uop_compression,
+      0);
+
+#if VTA_DEBUG == 1
+  printInstruction(ins_size, insn_buf);
+  printMicroOp(uop_size, uop_buf);
+#endif
+
+  // Initialize inputs
+  inp_T **inputs = allocInit2dArray<inp_T, VTA_INP_WIDTH>(batch, in_channels);
+  // Initialize weights
+  wgt_T **weights = allocInit2dArray<wgt_T, VTA_WGT_WIDTH>(out_channels, in_channels);
+  // Initialize biases
+  acc_T **biases = allocInit2dArray<acc_T, VTA_ACC_WIDTH>(batch, out_channels);
+
+  // Reference GEMM implementation
+  out_T **outputs_ref = alloc2dArray<out_T>(batch, out_channels);
+  for (int i = 0; i < batch; i++) {
+    for (int j = 0; j < out_channels; j++) {
+      acc_T sum = biases[i][j];
+      for (int k = 0; k < in_channels; k++) {
+        sum += (acc_T) (inputs[i][k] * weights[j][k]);
+      }
+      // Set
+      outputs_ref[i][j] = (out_T) sum;
+    }
+  }
+
+  // Prepare the input buffer
+  inp_T *input_buf = static_cast<inp_T *>(allocBuffer(VTA_INP_ELEM_BYTES * inp_size));
+  packBuffer<inp_T, VTA_INP_WIDTH>(input_buf,
+                                   inputs,
+                                   batch,
+                                   in_channels,
+                                   VTA_BATCH,
+                                   VTA_BLOCK_IN);
+  // Prepare the weight buffer
+  wgt_T *weight_buf = static_cast<wgt_T *>(allocBuffer(VTA_WGT_ELEM_BYTES * wgt_size));
+  packBuffer<wgt_T, VTA_WGT_WIDTH>(weight_buf,
+                                   weights,
+                                   out_channels,
+                                   in_channels,
+                                   VTA_BLOCK_OUT,
+                                   VTA_BLOCK_IN);
+  // Prepare the bias buffer
+  acc_T *bias_buf = static_cast<acc_T *>(allocBuffer(VTA_ACC_ELEM_BYTES * out_size));
+  packBuffer<acc_T, VTA_ACC_WIDTH>(bias_buf,
+                                   biases,
+                                   batch,
+                                   out_channels,
+                                   VTA_BATCH,
+                                   VTA_BLOCK_OUT);
+  // Prepare the output buffer
+  out_T *output_buf = static_cast<out_T *>(allocBuffer(VTA_INP_ELEM_BYTES * out_size));
+
+#ifdef NO_SIM
+  // Invoke the VTA
+  uint64_t t_fpga = vta(ins_size,
+                        insn_buf,
+                        uop_buf,
+                        input_buf,
+                        weight_buf,
+                        bias_buf,
+                        output_buf);
+  // Report on timining
+  printf("INFO - Synchronization time: %.3lfms\n", static_cast<float>(t_fpga) / 1E6);
+  printf("INFO - Throughput: %.3lfGOPs/s\n",
+         static_cast<float>(batch) * in_channels * out_channels * 2 / t_fpga);
+#else
+  // Invoke the VTA
+  vta(ins_size,
+      (volatile insn_T *) insn_buf,
+      (volatile uop_T *) uop_buf,
+      (volatile inp_vec_T *) input_buf,
+      (volatile wgt_vec_T *) weight_buf,
+      (volatile acc_vec_T *) bias_buf,
+      (volatile out_vec_T *) output_buf);
+#endif
+
+  // Unpack output data
+  out_T **outputs = alloc2dArray<out_T>(batch, out_channels);
+  unpackBuffer<out_T, VTA_OUT_WIDTH>(outputs,
+                                     output_buf,
+                                     batch,
+                                     out_channels,
+                                     VTA_BATCH,
+                                     VTA_BLOCK_OUT);
+
+  // Correctness checks
+  int err = 0;
+  for (int i = 0; i < batch; i++) {
+    for (int j = 0; j < out_channels; j++) {
+      if (outputs_ref[i][j] != outputs[i][j]) {
+        err++;
+#if VTA_DEBUG == 1
+        printf("DEBUG - %d, %d: expected 0x%x but got 0x%x\n", i, j,
+               static_cast<int>(outputs_ref[i][j]),
+               static_cast<int>(outputs[i][j]));
+#endif
+      }
+    }
+  }
+
+  // Free all allocated arrays
+  free2dArray<inp_T>(inputs, batch, in_channels);
+  free2dArray<wgt_T>(weights, out_channels, in_channels);
+  free2dArray<acc_T>(biases, batch, out_channels);
+  free2dArray<out_T>(outputs_ref, batch, out_channels);
+  free2dArray<out_T>(outputs, batch, out_channels);
+  freeBuffer(insn_buf);
+  freeBuffer(uop_buf);
+  freeBuffer(input_buf);
+  freeBuffer(weight_buf);
+  freeBuffer(bias_buf);
+  freeBuffer(output_buf);
+
+  if (err == 0) {
+    printf("INFO - Blocked GEMM test successful!\n");
+    return 0;
+  } else {
+    printf("INFO - Blocked GEMM test failed, got %d errors!\n", err);
+    return -1;
+  }
+}
\ No newline at end of file
diff --git a/vta/tests/hardware/common/test_lib.h b/vta/tests/hardware/common/test_lib.h
index 458ff7138edb..0e8f30df7d92 100644
--- a/vta/tests/hardware/common/test_lib.h
+++ b/vta/tests/hardware/common/test_lib.h
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2018 by Contributors
- * \file vta_test_lib.cpp
+ * \file test_lib.cpp
  * \brief Test library for the VTA design simulation and driver tests.
  */
 
@@ -17,9 +17,9 @@
 
 #include <vta/driver.h>
 
-#ifdef VTA_PYNQ_TARGET
+#ifdef VTA_TARGET_PYNQ
 #include "../../../src/pynq/pynq_driver.h"
-#endif  // VTA_PYNQ_TARGET
+#endif  // VTA_TARGET_PYNQ
 
 typedef uint64_t axi_T;
 typedef uint32_t uop_T;
@@ -300,4 +300,14 @@ int alu_test(int opcode, bool use_imm, int batch, int vector_size, bool uop_comp
 int blocked_gemm_test(int batch, int channels, int block, bool uop_compression,
   int virtual_threads);
 
+/*!
+* \brief VTA GEMM unit test.
+* \param batch Batch size.
+* \param in_channels Input channels.
+* \param out_channels Output channels.
+* \param uop_compression Apply micro-op compression.
+* \return Number of errors from the test run.
+*/
+int gemm_test(int batch, int in_channels, int out_channels, bool uop_compression);
+
 #endif  //  TESTS_HARDWARE_COMMON_TEST_LIB_H_
diff --git a/vta/tests/hardware/pynq/Makefile b/vta/tests/hardware/pynq/Makefile
index dabf55e262f6..7a862e22eff9 100644
--- a/vta/tests/hardware/pynq/Makefile
+++ b/vta/tests/hardware/pynq/Makefile
@@ -1,7 +1,7 @@
 CC ?= g++
 CFLAGS = -Wall -O3 -std=c++11 -I/usr/include
 LDFLAGS = -L/usr/lib -L/opt/python3.6/lib/python3.6/site-packages/pynq/lib/
-LIBS = -l:libsds_lib.so -l:libdma.so
+LIBS = -l:libsds_lib.so -l:libdma.so -lstdc++
 INCLUDE_DIR = ../../../include
 DRIVER_DIR = ../../../src/pynq
 TESTLIB_DIR = ../common
@@ -10,19 +10,14 @@ SOURCES = pynq_driver.cc test_lib.cc
 OBJECTS = pynq_driver.o test_lib.o metal_test.o
 EXECUTABLE = vta
 
-# Include top-level config file
-ifndef config
-ifneq ("$(wildcard ../../../config.mk)", "")
-	config = ../../../config.mk
-else
-	config = ../../../make/config.mk
-endif
-endif
-include $(config)
+# Include VTA config
+VTA_CONFIG = python ../../../make/vta_config.py
+CFLAGS += `${VTA_CONFIG} --cflags`
+LDFLAGS += `${VTA_CONFIG} --ldflags`
+VTA_TARGET := $(shell ${VTA_CONFIG} --target)
 
 # Define flags
-CFLAGS += -I $(INCLUDE_DIR) -DNO_SIM -DDEBUG=0
-CFLAGS += $(ADD_CFLAGS)
+CFLAGS += -I $(INCLUDE_DIR) -DNO_SIM -DVTA_DEBUG=0
 
 # All Target
 all: $(EXECUTABLE)
diff --git a/vta/tests/hardware/pynq/metal_test.cc b/vta/tests/hardware/pynq/metal_test.cc
index 01e73f46bd72..56be244baa79 100644
--- a/vta/tests/hardware/pynq/metal_test.cc
+++ b/vta/tests/hardware/pynq/metal_test.cc
@@ -1,6 +1,6 @@
 /*!
  *  Copyright (c) 2018 by Contributors
- * \file driver_test.cpp
+ * \file metal_test.cpp
  * \brief Bare-metal test to test driver and VTA design.
  */
 
@@ -13,104 +13,6 @@
 #include "../../../src/pynq/pynq_driver.h"
 #include "../common/test_lib.h"
 
-// VTA invocation (present the same abstraction as in the simulation tests)
-uint64_t vta(
-  uint32_t insn_count,
-  VTAGenericInsn *insns,
-  VTAUop *uops,
-  inp_T *inputs,
-  wgt_T *weights,
-  acc_T *biases,
-  inp_T *outputs) {
-  // Performance counter variables
-  uint64_t t_fpga;
-  struct timespec start, stop;
-
-  // Derive bitstream file
-  char bitstream[128];
-  char str_batch_size[4];
-  char str_block_out_size[4];
-  char str_block_in_size[4];
-  char str_block_bit_width[4];
-  snprintf(str_batch_size, sizeof(str_batch_size), "%d", VTA_BATCH);
-  snprintf(str_block_out_size, sizeof(str_block_out_size), "%d", VTA_BLOCK_OUT);
-  snprintf(str_block_in_size, sizeof(str_block_in_size), "%d", VTA_BLOCK_IN);
-  snprintf(str_block_bit_width, sizeof(str_block_bit_width), "%d", VTA_WGT_WIDTH);
-  snprintf(bitstream, sizeof(bitstream), "%s", "vta.bit");
-
-#if VTA_DEBUG == 1
-  printf("INFO - Programming FPGA: %s!\n", bitstream);
-#endif
-
-  // Program VTA
-  VTAProgram(bitstream);
-  // Get VTA handles
-  VTAHandle vta_fetch_handle = VTAMapRegister(VTA_FETCH_ADDR, VTA_RANGE);
-  VTAHandle vta_load_handle = VTAMapRegister(VTA_LOAD_ADDR, VTA_RANGE);
-  VTAHandle vta_compute_handle = VTAMapRegister(VTA_COMPUTE_ADDR, VTA_RANGE);
-  VTAHandle vta_store_handle = VTAMapRegister(VTA_STORE_ADDR, VTA_RANGE);
-
-  // Physical address pointers
-  uint32_t insn_phy = insns ? cma_get_phy_addr(insns) : 0;
-  uint32_t uop_phy = uops ? cma_get_phy_addr(uops) : 0;
-  uint32_t input_phy = inputs ? cma_get_phy_addr(inputs) : 0;
-  uint32_t weight_phy = weights ? cma_get_phy_addr(weights) : 0;
-  uint32_t bias_phy = biases ? cma_get_phy_addr(biases) : 0;
-  uint32_t output_phy = outputs ? cma_get_phy_addr(outputs) : 0;
-
-#if VTA_DEBUG == 1
-  printf("INFO - Starting FPGA!\n");
-#endif
-
-  clock_gettime(CLOCK_REALTIME, &start);
-
-  // FETCH @ 0x10 : Data signal of insn_count_V
-  VTAWriteMappedReg(vta_fetch_handle, 0x10, insn_count);
-  // FETCH @ 0x18 : Data signal of insns_V
-  if (insns) VTAWriteMappedReg(vta_fetch_handle, 0x18, insn_phy);
-  // LOAD @ 0x10 : Data signal of inputs_V
-  if (inputs) VTAWriteMappedReg(vta_load_handle, 0x10, input_phy);
-  // LOAD @ 0x18 : Data signal of weight_V
-  if (weights) VTAWriteMappedReg(vta_load_handle, 0x18, weight_phy);
-  // COMPUTE @ 0x20 : Data signal of uops_V
-  if (uops) VTAWriteMappedReg(vta_compute_handle, 0x20, uop_phy);
-  // COMPUTE @ 0x28 : Data signal of biases_V
-  if (biases) VTAWriteMappedReg(vta_compute_handle, 0x28, bias_phy);
-  // STORE @ 0x10 : Data signal of outputs_V
-  if (outputs) VTAWriteMappedReg(vta_store_handle, 0x10, output_phy);
-
-  // VTA start
-  VTAWriteMappedReg(vta_fetch_handle, 0x0, 0x1);
-  VTAWriteMappedReg(vta_load_handle, 0x0, 0x81);
-  VTAWriteMappedReg(vta_compute_handle, 0x0, 0x81);
-  VTAWriteMappedReg(vta_store_handle, 0x0, 0x81);
-
-  int flag = 0, t = 0;
-  for (t = 0; t < 10000000; ++t) {
-    flag = VTAReadMappedReg(vta_compute_handle, 0x18);
-    if (flag & VTA_DONE) break;
-  }
-
-  if (t == 10000000) {
-    printf("\tWARNING: VTA TIMEOUT!!!!\n");
-#if VTA_DEBUG == 1
-  } else {
-    printf("INFO - FPGA Finished!\n");
-#endif
-  }
-
-  clock_gettime(CLOCK_REALTIME, &stop);
-  t_fpga = 1000000000ULL * (stop.tv_sec - start.tv_sec) + (stop.tv_nsec - start.tv_nsec);
-
-  // Unmap VTA register
-  VTAUnmapRegister(vta_fetch_handle, VTA_RANGE);
-  VTAUnmapRegister(vta_load_handle, VTA_RANGE);
-  VTAUnmapRegister(vta_compute_handle, VTA_RANGE);
-  VTAUnmapRegister(vta_store_handle, VTA_RANGE);
-
-  return t_fpga;
-}
-
 int main(void) {
 #if VTA_DEBUG == 1
   printParameters();

From bfe2128411ff52078ad1bb87f02cf6ecaab08d80 Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@cs.washington.edu>
Date: Fri, 11 May 2018 17:17:33 -0700
Subject: [PATCH 841/949] [TOPI] Automated schedule in conv2d TOPI lib, moving
 to GEMM intrinsic (#35)

* removing programming out of end to end example for now

* updating TOPI library to use gemm tensor intrinsic

* bug fix, autoschedule in TOPI conv lib

* removing the deprecated GEVM intrinsic

* refactoring, fixed lint test

* fix for integer division bug

* python3 bug fix for non matching types due to float division

* comment
---
 .../resnet18/pynq/imagenet_predict.py         |   7 +-
 vta/python/vta/environment.py                 |   6 -
 vta/python/vta/intrin.py                      |  82 ------
 vta/python/vta/top/vta_conv2d.py              | 272 +++++++++++++-----
 vta/tests/hardware/common/test_lib.cc         |   6 +-
 .../python/integration/test_benchmark_gemm.py |   4 +-
 .../integration/test_benchmark_topi_conv2d.py |  56 ++--
 7 files changed, 229 insertions(+), 204 deletions(-)

diff --git a/vta/examples/resnet18/pynq/imagenet_predict.py b/vta/examples/resnet18/pynq/imagenet_predict.py
index ae8d49017255..e4f82b175e3c 100644
--- a/vta/examples/resnet18/pynq/imagenet_predict.py
+++ b/vta/examples/resnet18/pynq/imagenet_predict.py
@@ -31,11 +31,6 @@
         print ("Downloading {}".format(file))
         wget.download(url+file)
 
-# Program the FPGA remotely
-assert tvm.module.enabled("rpc")
-remote = rpc.connect(host, port)
-vta.program_fpga(remote, BITSTREAM_FILE)
-
 if verbose:
     logging.basicConfig(level=logging.DEBUG)
 
@@ -129,8 +124,10 @@ def mark_nop(graph, conv_layer=-1, skip_conv_layer=()):
                 params=params, target_host=target_host)
 
 
+assert tvm.module.enabled("rpc")
 temp = util.tempdir()
 lib.save(temp.relpath("graphlib.o"))
+remote = rpc.connect(host, port)
 remote.upload(temp.relpath("graphlib.o"))
 lib = remote.load_module("graphlib.o")
 ctx = remote.ext_dev(0) if target.device_name == "vta" else remote.cpu(0)
diff --git a/vta/python/vta/environment.py b/vta/python/vta/environment.py
index feaacbe4017d..41ec38aefc05 100644
--- a/vta/python/vta/environment.py
+++ b/vta/python/vta/environment.py
@@ -55,7 +55,6 @@ def __init__(self, env):
         self.DEBUG_NO_SYNC = False
         env._dev_ctx = self
         self.gemm = intrin.gemm(env, env.mock_mode)
-        self.gevm = intrin.gevm(env, env.mock_mode)
 
     def get_task_qid(self, qid):
         """Get transformed queue index."""
@@ -204,11 +203,6 @@ def gemm(self):
         """GEMM intrinsic"""
         return self.dev.gemm
 
-    @property
-    def gevm(self):
-        """GEVM intrinsic"""
-        return self.dev.gevm
-
     @property
     def target_host(self):
         """The target host"""
diff --git a/vta/python/vta/intrin.py b/vta/python/vta/intrin.py
index 8373a2c05d4f..b366287568e7 100644
--- a/vta/python/vta/intrin.py
+++ b/vta/python/vta/intrin.py
@@ -3,88 +3,6 @@
 
 import tvm
 
-def gevm(env, mock=False):
-    """Vector-matrix multiply intrinsic
-
-    Parameters
-    ----------
-    env : Environment
-        The Environment
-
-    mock : bool
-        Whether create a mock version.
-    """
-    wgt_lanes = env.WGT_ELEM_BITS // env.WGT_WIDTH
-    assert wgt_lanes == env.BLOCK_OUT * env.BLOCK_IN
-    wgt_shape = (env.BLOCK_OUT, env.BLOCK_IN)
-    assert wgt_shape[0] * wgt_shape[1] == wgt_lanes
-    inp_lanes = env.INP_ELEM_BITS // env.INP_WIDTH
-    out_lanes = env.ACC_ELEM_BITS // env.ACC_WIDTH
-    wgt = tvm.placeholder((wgt_shape[0], wgt_shape[1]),
-                          dtype="int%d" % env.WGT_WIDTH,
-                          name=env.wgt_scope)
-    inp = tvm.placeholder((wgt_shape[1], ),
-                          dtype="int%d" % env.INP_WIDTH,
-                          name=env.inp_scope)
-    k = tvm.reduce_axis((0, wgt_shape[1]), name="k")
-    out_dtype = "int%d" % env.ACC_WIDTH
-    out = tvm.compute((wgt_shape[0],),
-                      lambda i: tvm.sum(inp[k].astype(out_dtype) *
-                                        wgt[i, k].astype(out_dtype),
-                                        axis=[k]),
-                      name="out")
-    wgt_layout = tvm.decl_buffer(
-        wgt.shape, wgt.dtype, env.wgt_scope,
-        scope=env.wgt_scope, offset_factor=wgt_lanes, data_alignment=wgt_lanes)
-    inp_layout = tvm.decl_buffer(
-        inp.shape, inp.dtype, env.inp_scope,
-        scope=env.inp_scope, offset_factor=inp_lanes, data_alignment=inp_lanes)
-    out_layout = tvm.decl_buffer(
-        out.shape, out.dtype, env.acc_scope,
-        scope=env.acc_scope, offset_factor=out_lanes, data_alignment=out_lanes)
-
-    def intrin_func(ins, outs):
-        """Vector-matrix multiply intrinsic function"""
-        dinp, dwgt = ins
-        dout = outs[0]
-        def instr(index):
-            """Generate vector-matrix multiply VTA instruction"""
-            irb = tvm.ir_builder.create()
-            dev = env.dev
-            irb.scope_attr(dev.vta_axis, "coproc_scope",
-                           dev.get_task_qid(dev.QID_COMPUTE))
-            irb.scope_attr(dev.vta_axis, "coproc_uop_scope",
-                           dev.vta_push_uop)
-            if index == 0 or index == 2:
-                irb.emit(tvm.call_extern(
-                    "int32", "VTAUopPush",
-                    0, 0,
-                    dout.access_ptr("rw", "int32"),
-                    dinp.access_ptr("r", "int32"),
-                    dwgt.access_ptr("r", "int32"),
-                    0, 0, 0))
-            else:
-                irb.emit(tvm.call_extern(
-                    "int32", "VTAUopPush",
-                    0, 1,
-                    dout.access_ptr("rw", "int32"),
-                    0,
-                    0,
-                    0, 0, 0))
-            return irb.get()
-        # return a triple of normal-set, reset, update
-        nop = tvm.make.Evaluate(0)
-        if mock:
-            return (nop, nop, nop)
-        return (instr(0), instr(1), instr(2))
-
-    return tvm.decl_tensor_intrin(out.op, intrin_func,
-                                  name="GEVM",
-                                  binds={inp: inp_layout,
-                                         wgt: wgt_layout,
-                                         out: out_layout})
-
-
 def gemm(env, mock=False):
     """Matrix-matrix multiply intrinsic
 
diff --git a/vta/python/vta/top/vta_conv2d.py b/vta/python/vta/top/vta_conv2d.py
index 577eac8e143f..489acb1ce12d 100644
--- a/vta/python/vta/top/vta_conv2d.py
+++ b/vta/python/vta/top/vta_conv2d.py
@@ -12,9 +12,146 @@
 
 
 Workload = namedtuple("Conv2DWorkload",
-                      ['height', 'width', 'in_filter', 'out_filter',
+                      ['batch', 'height', 'width', 'in_filter', 'out_filter',
                        'hkernel', 'wkernel', 'hpad', 'wpad', 'hstride', 'wstride'])
 
+def find_schedules(layer, vt_only=False, best_only=False):
+    """ Returns a schedule for a given a layer.
+
+    Parameters
+    ----------
+    layer : Workload
+        Convolutional layer description.
+    vt_only : Boolean
+        Produce a schedule plan with virtual threading.
+    best_only : Boolean
+        Return the "best" schedule plan.
+
+    Returns
+    -------
+    fil_sched : list
+        List of valid schedules.
+
+    """
+    # pylint: disable=too-many-nested-blocks
+    env = get_env()
+
+    # Helper function to get factors
+    def _find_factors(n):
+        factors = []
+        for f in range(1, n + 1):
+            if n % f == 0:
+                factors.append(f)
+        return factors
+
+    def _get_data_movement_byte(schedule, layer):
+        """ Estimate data movement in bytes for the schedule plan
+        """
+        env = get_env()
+        b_f = schedule.b_factor
+        h_f = schedule.h_factor
+        w_f = schedule.w_factor
+        ci_f = schedule.ic_factor
+        co_f = schedule.oc_factor
+        # Derive data movement
+        inp_elem_sizeb = env.BATCH * env.BLOCK_IN * env.INP_WIDTH
+        wgt_elem_sizeb = env.BLOCK_IN * env.BLOCK_OUT * env.WGT_WIDTH
+        out_elem_sizeb = env.BATCH * env.BLOCK_OUT * env.OUT_WIDTH
+        input_tile_elems = b_f * \
+                ((h_f - 1) * layer.hstride + layer.hkernel) * \
+                ((w_f - 1) * layer.wstride + layer.wkernel) * ci_f
+        weight_tile_elems = layer.hkernel * layer.wkernel * ci_f
+        output_tile_elems = b_f * h_f * w_f * co_f
+        # Derive tiling factors
+        b_factor = layer.batch // (b_f * env.BATCH)
+        h_factor = (layer.height // layer.hstride) // h_f
+        w_factor = (layer.width // layer.wstride) // w_f
+        ci_factor = layer.in_filter // (ci_f * env.BLOCK_IN)
+        co_factor = layer.out_filter // (co_f * env.BLOCK_OUT)
+        # Compute input transaction count
+        input_xfers = b_factor * h_factor * w_factor * co_factor * ci_factor
+        weight_xfers = b_factor * h_factor * w_factor * co_factor * ci_factor
+        output_xfers = b_factor * h_factor * w_factor * co_factor
+        # Compute total transfer sizes
+        input_xfer_byte = input_tile_elems * input_xfers * inp_elem_sizeb // 8
+        weight_xfer_byte = weight_tile_elems * weight_xfers * wgt_elem_sizeb // 8
+        output_xfer_byte = output_tile_elems * output_xfers * out_elem_sizeb // 8
+        total_xfer_byte = input_xfer_byte + weight_xfer_byte + output_xfer_byte
+        return total_xfer_byte
+
+    # Scheduling exploration
+    batch_factors = _find_factors(layer.batch // env.BATCH)
+    height_factors = _find_factors(layer.height // layer.hstride)
+    width_factors = _find_factors(layer.width // layer.wstride)
+    cin_factors = _find_factors(layer.in_filter // env.BLOCK_IN)
+    cout_factors = _find_factors(layer.out_filter // env.BLOCK_OUT)
+    ht_factors = [1, 2]
+    cot_factors = [1, 2]
+
+    # Explore schedules
+    schedules = []
+    for b_f in batch_factors:
+        for h_f in height_factors:
+            for w_f in width_factors:
+                for ci_f in cin_factors:
+                    for co_f in cout_factors:
+                        # FIXME: 2D load pattern matching imposes restrictions on schedule
+                        valid = (w_f == layer.width // layer.wstride) or \
+                                (w_f != layer.width // layer.wstride and co_f == 1) and \
+                                ci_f == 1
+                        if valid:
+                            schedules.append([b_f, h_f, w_f, ci_f, co_f])
+
+    # Filter the schedules that wouldn't work in the available BRAM sizes
+    inp_elem_sizeb = env.BATCH * env.BLOCK_IN * env.INP_WIDTH
+    wgt_elem_sizeb = env.BLOCK_IN * env.BLOCK_OUT * env.WGT_WIDTH
+    out_elem_sizeb = env.BATCH * env.BLOCK_OUT * env.OUT_WIDTH
+    inp_brams_sizeb = env.INP_BUFF_SIZE * 8
+    wgt_brams_sizeb = env.WGT_BUFF_SIZE * 8
+    out_brams_sizeb = env.OUT_BUFF_SIZE * 8
+    fil_sched = []
+    xfer_size = []
+    for sched in schedules:
+        b_f, h_f, w_f, ci_f, co_f = sched
+        for h_t in ht_factors:
+            for co_t in cot_factors:
+                # Make sure to filter cases where we apply threading on two axes
+                # or cases where the threading factors for h and co are not
+                # factors of h and co
+                if (h_t == 2 and co_t == 2) or (h_f % h_t != 0) or (co_f % co_t != 0):
+                    continue
+                # Adjust tile sizes if threading is applied
+                h_f //= h_t
+                co_f //= co_t
+                # Derive tile sizes
+                input_tile_elems = b_f * \
+                        ((h_f - 1) * layer.hstride + layer.hkernel) * \
+                        ((w_f - 1) * layer.wstride + layer.wkernel) * ci_f
+                weight_tile_elems = layer.hkernel * layer.wkernel * ci_f * co_f
+                output_tile_elems = b_f * h_f * w_f * co_f
+
+                # Derive valid schedule filter
+                valid = True
+                # If in vitrual-threaded mode, only allow for threaded plans
+                valid &= (vt_only and (h_t == 2 or co_t == 2)) or not vt_only
+                # Check that we don't exceed input/weight/output capacity
+                valid &= input_tile_elems * inp_elem_sizeb <= inp_brams_sizeb // (co_t * h_t)
+                valid &= weight_tile_elems * wgt_elem_sizeb <= wgt_brams_sizeb
+                valid &= output_tile_elems * out_elem_sizeb <= out_brams_sizeb // (co_t * h_t)
+                # Make sure that we don't write to the same acc location within 2 consecutive cycles
+                valid &= h_f > 2 and w_f > 2
+                # TODO: check that we don't exceed instruction or micro-op count
+
+                if valid:
+                    schedule = Schedule(b_factor=b_f, oc_factor=co_f, ic_factor=ci_f, h_factor=h_f,
+                                        w_factor=w_f, oc_nthread=co_t, h_nthread=h_t)
+                    fil_sched.append(schedule)
+                    xfer_size.append(_get_data_movement_byte(schedule, layer))
+
+    if best_only:
+        return [fil_sched[xfer_size.index(min(xfer_size))]]
+    return fil_sched
+
 def packed_conv2d(data,
                   kernel,
                   padding,
@@ -23,14 +160,14 @@ def packed_conv2d(data,
     """ Packed conv2d function.
     """
     if padding[0]:
-        pad_data = topi.nn.pad(data, [0, 0, padding[0], padding[1], 0], name="pad_data")
+        pad_data = topi.nn.pad(data, [0, 0, padding[0], padding[1], 0, 0], name="pad_data")
     else:
         pad_data = data
-    assert len(data.shape) == 5
+    assert len(data.shape) == 6
     assert len(kernel.shape) == 6
     oheight = topi.util.simplify((pad_data.shape[2] - kernel.shape[2]) // strides[0] + 1)
     owidth = topi.util.simplify((pad_data.shape[3] - kernel.shape[3]) // strides[1] + 1)
-    oshape = (data.shape[0], kernel.shape[0], oheight, owidth, kernel.shape[4])
+    oshape = (data.shape[0], kernel.shape[0], oheight, owidth, data.shape[4], kernel.shape[4])
 
     ishape = topi.util.get_const_tuple(data.shape)
     kshape = topi.util.get_const_tuple(kernel.shape)
@@ -43,14 +180,13 @@ def packed_conv2d(data,
     hstride, wstride = strides
     res = tvm.compute(
         oshape,
-        lambda b, co, i, j, ci: tvm.sum(
-            pad_data[b, k_o, i*hstride+d_i, j*wstride+d_j, k_i].astype(out_dtype) *
-            kernel[co, k_o, d_i, d_j, ci, k_i].astype(out_dtype),
+        lambda b_o, c_o, i, j, b_i, c_i: tvm.sum(
+            pad_data[b_o, k_o, i*hstride+d_i, j*wstride+d_j, b_i, k_i].astype(out_dtype) *
+            kernel[c_o, k_o, d_i, d_j, c_i, k_i].astype(out_dtype),
             axis=[k_o, d_i, d_j, k_i]),
         name="res", tag="packed_conv2d")
     return res
 
-
 @tvm.register_func("nnvm.compiler.build_target", override=True)
 def _build(funcs, target, target_host):
     tvm_t = tvm.target.create(target)
@@ -155,12 +291,13 @@ def _get_workload(data, pad_data, kernel, output):
     o_shape = topi.util.get_const_tuple(output.shape)
     d_shape = topi.util.get_const_tuple(data.shape)
     k_shape = topi.util.get_const_tuple(kernel.shape)
-    o_b, o_c, o_h, o_w, o_blk = o_shape
-    i_b, i_c, i_h, i_w, i_blk = d_shape
+    o_b, o_c, o_h, o_w, ob_blk, o_blk = o_shape
+    i_b, i_c, i_h, i_w, ib_blk, i_blk = d_shape
     k_o, k_i, k_h, k_w, ko_blk, ki_blk = k_shape
     # For now we need to assume that input channel blocking is the same
     # as the output channel blocking
     assert o_blk == i_blk
+    assert ob_blk == ib_blk
     # Make sure that dimensions match
     assert o_b == i_b
     assert o_blk == ko_blk
@@ -178,7 +315,7 @@ def _get_workload(data, pad_data, kernel, output):
         h_pad, w_pad = 0, 0
     h_str = (i_h + h_pad*2 - k_h) // (o_h - 1)
     w_str = (i_w + w_pad*2 - k_w) // (o_w - 1)
-    return Workload(i_h, i_w, i_c, o_c, k_h, k_w, h_pad, w_pad, h_str, w_str)
+    return Workload(i_b, i_h, i_w, i_c, o_c, k_h, k_w, h_pad, w_pad, h_str, w_str)
 
 _WL2PLAN = {}
 
@@ -224,7 +361,7 @@ def _traverse(op):
 
     load_inp = load_wgt = load_out = store_out = env.dma_copy
     alu = env.alu
-    gevm = env.gevm
+    gemm = env.gemm
 
     # schedule1
     oshape = topi.util.get_const_tuple(output.shape)
@@ -256,11 +393,12 @@ def _traverse(op):
     h_factor = (plan.h_factor if plan.h_factor else oshape[2])
     w_factor = (plan.w_factor if plan.w_factor else oshape[3])
 
-    x_b, x_oc, x_i, x_j, x_ic = s[output].op.axis
-    x_oc0, x_oc1 = s[output].split(x_oc, factor=oc_factor)
+
+    x_bo, x_co, x_i, x_j, x_bi, x_ci = s[output].op.axis
+    x_co0, x_co1 = s[output].split(x_co, factor=oc_factor)
     x_i0, x_i1 = s[output].split(x_i, factor=h_factor)
     x_j0, x_j1 = s[output].split(x_j, factor=w_factor)
-    s[output].reorder(x_b, x_oc0, x_i0, x_j0, x_oc1, x_i1, x_j1, x_ic)
+    s[output].reorder(x_bo, x_i0, x_co0, x_j0, x_co1, x_i1, x_j1, x_bi, x_ci)
     store_pt = x_j0
 
     # set all compute scopes
@@ -273,100 +411,78 @@ def _traverse(op):
         s[tensor].pragma(s[tensor].op.axis[0], load_out)
 
     # virtual threading along output channel axes
-    if plan.oc_nthread:
-        _, v_t = s[output].split(x_oc0, factor=plan.oc_nthread)
-        s[output].reorder(v_t, x_b)
+    if plan.oc_nthread > 1:
+        _, v_t = s[output].split(x_co0, factor=plan.oc_nthread)
+        s[output].reorder(v_t, x_bo)
         s[output].bind(v_t, tvm.thread_axis("cthread"))
 
     # virtual threading along spatial rows
-    if plan.h_nthread:
+    if plan.h_nthread > 1:
         _, v_t = s[output].split(x_i0, factor=plan.h_nthread)
-        s[output].reorder(v_t, x_b)
+        s[output].reorder(v_t, x_bo)
         s[output].bind(v_t, tvm.thread_axis("cthread"))
 
-    x_b, x_oc, x_i, x_j, x_ic = s[conv2d_stage].op.axis
+    x_bo, x_co, x_i, x_j, x_bi, x_ci = s[conv2d_stage].op.axis
     k_o, d_i, d_j, k_i = s[conv2d_stage].op.reduce_axis
-    s[conv2d_stage].reorder(k_o, x_j, d_j, d_i, x_oc, x_i, x_ic, k_i)
+    s[conv2d_stage].reorder(x_bo, k_o, x_j, d_j, d_i, x_co, x_i, x_bi, x_ci, k_i)
 
-    if plan.ko_factor:
-        k_o, _ = s[conv2d_stage].split(k_o, factor=plan.ko_factor)
+    if plan.ic_factor:
+        k_o, _ = s[conv2d_stage].split(k_o, factor=plan.ic_factor)
         s[cdata].compute_at(s[conv2d_stage], k_o)
         s[ckernel].compute_at(s[conv2d_stage], k_o)
 
     # Use VTA instructions
     s[cdata].pragma(s[cdata].op.axis[0], load_inp)
     s[ckernel].pragma(s[ckernel].op.axis[0], load_wgt)
-    s[conv2d_stage].tensorize(x_ic, gevm)
-    s[output].pragma(x_oc1, store_out)
+    s[conv2d_stage].tensorize(x_bi, gemm)
+    s[output].pragma(x_co1, store_out)
     return s
 
-
 class Conv2DSchedule(object):
     """ 2D convolution schedule object.
     """
     def __init__(self,
-                 oc_factor,
-                 ko_factor=1,
+                 b_factor=1,
+                 oc_factor=1,
+                 ic_factor=1,
                  h_factor=1,
                  w_factor=0,
                  oc_nthread=0,
-                 h_nthread=0):
+                 h_nthread=0,
+                 debug_sync=False):
+        self.b_factor = b_factor
         self.oc_factor = oc_factor
-        self.ko_factor = ko_factor
+        self.ic_factor = ic_factor
         self.h_factor = h_factor
         self.w_factor = w_factor
         self.oc_nthread = oc_nthread
         self.h_nthread = h_nthread
+        self.debug_sync = debug_sync
+    def __str__(self):
+        return "{}.{}.{}.{}.{}.{}.{}".format(
+            self.b_factor, self.oc_factor, self.ic_factor,
+            self.h_factor, self.w_factor,
+            self.oc_nthread, self.h_nthread)
 
 Schedule = Conv2DSchedule
 
-# ResNet18 workloads
+# Layer description of the ResNet18
 RESNET = {
-    # Workloads of resnet18 on imagenet
-    0: Workload(224, 224, 16, 64, 7, 7, 3, 3, 2, 2),
-    1: Workload(56, 56, 64, 64, 3, 3, 1, 1, 1, 1),
-    2: Workload(56, 56, 64, 64, 1, 1, 0, 0, 1, 1),
-    3: Workload(56, 56, 64, 128, 3, 3, 1, 1, 2, 2),
-    4: Workload(56, 56, 64, 128, 1, 1, 0, 0, 2, 2),
-    5: Workload(28, 28, 128, 128, 3, 3, 1, 1, 1, 1),
-    6: Workload(28, 28, 128, 256, 3, 3, 1, 1, 2, 2),
-    7: Workload(28, 28, 128, 256, 1, 1, 0, 0, 2, 2),
-    8: Workload(14, 14, 256, 256, 3, 3, 1, 1, 1, 1),
-    9: Workload(14, 14, 256, 512, 3, 3, 1, 1, 2, 2),
-    10: Workload(14, 14, 256, 512, 1, 1, 0, 0, 2, 2),
-    11: Workload(7, 7, 512, 512, 3, 3, 1, 1, 1, 1),
-}
-
-# Serial schedule
-RESNET_SERIAL = {
-    RESNET[0]: Schedule(oc_factor=1, ko_factor=1, h_factor=4, w_factor=56),
-    RESNET[1]: Schedule(oc_factor=2, ko_factor=1, h_factor=14, w_factor=0),
-    RESNET[2]: Schedule(oc_factor=4, ko_factor=4, h_factor=8, w_factor=0),
-    RESNET[3]: Schedule(oc_factor=4, ko_factor=1, h_factor=14, w_factor=0),
-    RESNET[4]: Schedule(oc_factor=8, ko_factor=1, h_factor=4, w_factor=0),
-    RESNET[5]: Schedule(oc_factor=8, ko_factor=1, h_factor=7, w_factor=0),
-    RESNET[6]: Schedule(oc_factor=8, ko_factor=1, h_factor=14, w_factor=0),
-    RESNET[7]: Schedule(oc_factor=16, ko_factor=1, h_factor=7, w_factor=0),
-    RESNET[8]: Schedule(oc_factor=8, ko_factor=1, h_factor=7, w_factor=0),
-    RESNET[9]: Schedule(oc_factor=8, ko_factor=1, h_factor=7, w_factor=0),
-    RESNET[10]: Schedule(oc_factor=16, ko_factor=1, h_factor=7, w_factor=0),
-    RESNET[11]: Schedule(oc_factor=8, ko_factor=1, h_factor=7, w_factor=0),
+    0: Workload(1, 224, 224, 16, 64, 7, 7, 3, 3, 2, 2),
+    1: Workload(1, 56, 56, 64, 64, 3, 3, 1, 1, 1, 1),
+    2: Workload(1, 56, 56, 64, 64, 1, 1, 0, 0, 1, 1),
+    3: Workload(1, 56, 56, 64, 128, 3, 3, 1, 1, 2, 2),
+    4: Workload(1, 56, 56, 64, 128, 1, 1, 0, 0, 2, 2),
+    5: Workload(1, 28, 28, 128, 128, 3, 3, 1, 1, 1, 1),
+    6: Workload(1, 28, 28, 128, 256, 3, 3, 1, 1, 2, 2),
+    7: Workload(1, 28, 28, 128, 256, 1, 1, 0, 0, 2, 2),
+    8: Workload(1, 14, 14, 256, 256, 3, 3, 1, 1, 1, 1),
+    9: Workload(1, 14, 14, 256, 512, 3, 3, 1, 1, 2, 2),
+    10: Workload(1, 14, 14, 256, 512, 1, 1, 0, 0, 2, 2),
+    11: Workload(1, 7, 7, 512, 512, 3, 3, 1, 1, 1, 1),
 }
 
-# Latency hiding schedule
-RESNET_OPT = {
-    RESNET[0]: Schedule(oc_factor=1, ko_factor=1, h_factor=4, w_factor=56),
-    RESNET[1]: Schedule(oc_factor=2, ko_factor=1, h_factor=7, h_nthread=2),
-    RESNET[2]: Schedule(oc_factor=4, ko_factor=2, h_factor=4, w_factor=0, h_nthread=2),
-    RESNET[3]: Schedule(oc_factor=4, ko_factor=1, h_factor=7, w_factor=0, h_nthread=2),
-    RESNET[4]: Schedule(oc_factor=4, ko_factor=1, h_factor=7, h_nthread=2),
-    RESNET[5]: Schedule(oc_factor=4, ko_factor=1, h_factor=7, w_factor=0, h_nthread=2),
-    RESNET[6]: Schedule(oc_factor=4, ko_factor=1, h_factor=7, w_factor=0, oc_nthread=2),
-    RESNET[7]: Schedule(oc_factor=8, ko_factor=1, h_factor=7, w_factor=0, oc_nthread=2),
-    RESNET[8]: Schedule(oc_factor=4, ko_factor=1, h_factor=7, w_factor=0, oc_nthread=2),
-    RESNET[9]: Schedule(oc_factor=4, ko_factor=1, h_factor=7, w_factor=0, oc_nthread=2),
-    RESNET[10]: Schedule(oc_factor=8, ko_factor=1, h_factor=7, w_factor=0, oc_nthread=2),
-    RESNET[11]: Schedule(oc_factor=4, ko_factor=1, h_factor=7, w_factor=0, oc_nthread=2),
-}
-
-_WL2PLAN = RESNET_OPT
+_WL2PLAN = {}
+for idx in RESNET:
+    scheds = find_schedules(RESNET[idx], vt_only=True, best_only=True)[0]
+    _WL2PLAN[RESNET[idx]] = scheds
diff --git a/vta/tests/hardware/common/test_lib.cc b/vta/tests/hardware/common/test_lib.cc
index 1cb845765583..6c6d28ec0c69 100644
--- a/vta/tests/hardware/common/test_lib.cc
+++ b/vta/tests/hardware/common/test_lib.cc
@@ -315,7 +315,7 @@ VTAGenericInsn getGEMMInsn(int uop_offset, int batch, int in_feat, int out_feat,
     int push_next_dep) {
   // Converter
   union VTAInsn converter;
-  // GEVM instruction initialization
+  // GEMM instruction initialization
   VTAGemInsn insn;
   insn.opcode = VTA_OPCODE_GEMM;
   insn.pop_prev_dep = pop_prev_dep;
@@ -394,7 +394,7 @@ VTAGenericInsn getALUInsn(int opcode, int vector_size, bool use_imm, int imm, bo
 VTAGenericInsn getFinishInsn(bool pop_prev, bool pop_next) {
   // Converter
   union VTAInsn converter;
-  // GEVM instruction initialization
+  // GEMM instruction initialization
   VTAGemInsn insn;
   insn.opcode = VTA_OPCODE_FINISH;
   insn.pop_prev_dep = pop_prev;
@@ -649,7 +649,7 @@ void printInstruction(int num_insn, VTAGenericInsn *insns) {
       }
     } else if (c.mem.opcode == VTA_OPCODE_GEMM) {
       // Print instruction field information
-      printf("GEVM\n");
+      printf("GEMM\n");
       printf("\tdep - pop prev: %d, pop next: %d, push prev: %d, push next: %d\n",
              static_cast<int>(c.mem.pop_prev_dep),
              static_cast<int>(c.mem.pop_next_dep),
diff --git a/vta/tests/python/integration/test_benchmark_gemm.py b/vta/tests/python/integration/test_benchmark_gemm.py
index b651fad0f251..4668acffc45a 100644
--- a/vta/tests/python/integration/test_benchmark_gemm.py
+++ b/vta/tests/python/integration/test_benchmark_gemm.py
@@ -168,7 +168,7 @@ def run_test(header, print_ir, check_correctness):
             with vta.build_config():
                 run_test("NORMAL", print_ir, True)
 
-        def gevm_unittest(print_ir):
+        def gemm_unittest(print_ir):
             mock = env.mock
             print("----- GEMM Unit Test-------")
             def run_test(header, print_ir):
@@ -244,7 +244,7 @@ def run_test(header, print_ir):
 
 
         gemm_normal(False)
-        gevm_unittest(False)
+        gemm_unittest(False)
         alu_unittest(False)
 
     def _run(env, remote):
diff --git a/vta/tests/python/integration/test_benchmark_topi_conv2d.py b/vta/tests/python/integration/test_benchmark_topi_conv2d.py
index 0a5edfdc7518..9721487ab4e8 100644
--- a/vta/tests/python/integration/test_benchmark_topi_conv2d.py
+++ b/vta/tests/python/integration/test_benchmark_topi_conv2d.py
@@ -23,14 +23,11 @@ def my_clip(x, a_min, a_max):
 
 def test_vta_conv2d():
     def run_vta_conv2d(env, remote, key, batch_size, wl, profile=True):
-        data_shape = (batch_size, wl.in_filter // env.BLOCK_IN,
-                      wl.height, wl.width, env.BLOCK_IN)
-        kernel_shape = (wl.out_filter // env.BLOCK_OUT,
-                        wl.in_filter // env.BLOCK_IN,
-                        wl.hkernel, wl.wkernel,
-                        env.BLOCK_OUT, env.BLOCK_IN)
-        bias_shape = (wl.out_filter // env.BLOCK_OUT, 1, 1, env.BLOCK_OUT)
-
+        data_shape = (batch_size//env.BATCH, wl.in_filter//env.BLOCK_IN,
+                      wl.height, wl.width, env.BATCH, env.BLOCK_IN)
+        kernel_shape = (wl.out_filter//env.BLOCK_OUT, wl.in_filter//env.BLOCK_IN,
+                        wl.hkernel, wl.wkernel, env.BLOCK_OUT, env.BLOCK_IN)
+        bias_shape = (1, wl.out_filter//env.BLOCK_OUT, 1, 1, env.BATCH, env.BLOCK_OUT)
 
         fout_height = (wl.height + 2 * wl.hpad - wl.hkernel) // wl.hstride + 1
         fout_width = (wl.width + 2 * wl.wpad - wl.wkernel) // wl.wstride + 1
@@ -45,12 +42,13 @@ def run_vta_conv2d(env, remote, key, batch_size, wl, profile=True):
         res = my_clip(res, 0, 127)
         res = topi.cast(res, "int8")
 
-        num_ops = fout_height * fout_width * wl.hkernel * wl.wkernel * wl.out_filter * wl.in_filter
+        num_ops = 2 * batch_size * fout_height * fout_width * wl.hkernel * wl.wkernel * wl.out_filter * wl.in_filter
 
         a_shape = (batch_size, wl.in_filter, wl.height, wl.width)
         w_shape = (wl.out_filter, wl.in_filter, wl.hkernel, wl.wkernel)
         stride = (wl.hstride, wl.wstride)
         data_dtype = data.dtype
+        kernel_dtype = kernel.dtype
         acc_dtype = env.acc_dtype
         assert wl.hpad == wl.wpad
         padding = wl.hpad
@@ -58,7 +56,7 @@ def run_vta_conv2d(env, remote, key, batch_size, wl, profile=True):
         @memoize("vta.tests.test_benchmark_topi.conv2d,verify_nhwc")
         def get_ref_data():
             a_np = (np.random.uniform(size=a_shape) * 4).astype(data_dtype)
-            w_np = (np.random.uniform(size=w_shape) * 4).astype(data_dtype)
+            w_np = (np.random.uniform(size=w_shape) * 4).astype(kernel_dtype)
             a_np = np.abs(a_np)
             w_np = np.abs(w_np)
             b_np = topi.testing.conv2d_nchw_python(
@@ -82,14 +80,15 @@ def verify(s, check_correctness):
             bias_orig = np.abs(bias_orig)
 
             data_packed = data_orig.reshape(
-                batch_size, wl.in_filter // env.BLOCK_IN, env.BLOCK_IN,
-                wl.height, wl.width).transpose((0, 1, 3, 4, 2))
+                batch_size//env.BATCH, env.BATCH,
+                wl.in_filter//env.BLOCK_IN, env.BLOCK_IN,
+                wl.height, wl.width).transpose((0, 2, 4, 5, 1, 3))
             kernel_packed = kernel_orig.reshape(
-                wl.out_filter // env.BLOCK_OUT, env.BLOCK_OUT,
-                wl.in_filter // env.BLOCK_IN, env.BLOCK_IN,
+                wl.out_filter//env.BLOCK_OUT, env.BLOCK_OUT,
+                wl.in_filter//env.BLOCK_IN, env.BLOCK_IN,
                 wl.hkernel, wl.wkernel).transpose((0, 2, 4, 5, 1, 3))
             bias_packed = bias_orig.reshape(
-                wl.out_filter // env.BLOCK_OUT, 1, 1, env.BLOCK_OUT)
+                1, wl.out_filter // env.BLOCK_OUT, 1, 1, env.BATCH, env.BLOCK_OUT)
             res_shape = topi.util.get_const_tuple(res.shape)
 
             res_np = np.zeros(res_shape).astype(res.dtype)
@@ -100,7 +99,7 @@ def verify(s, check_correctness):
             time_f = f.time_evaluator("conv2d", ctx, number=5)
             cost = time_f(data_arr, kernel_arr, bias_arr, res_arr)
             res_unpack = res_arr.asnumpy().transpose(
-                (0, 1, 4, 2, 3)).reshape(batch_size, wl.out_filter, fout_height, fout_width)
+                (0, 4, 1, 5, 2, 3)).reshape(batch_size, wl.out_filter, fout_height, fout_width)
             if check_correctness:
                 assert wl.hpad == wl.wpad
                 stride = (wl.hstride, wl.wstride)
@@ -127,18 +126,18 @@ def _run(env, remote):
         # ResNet18 workloads
         resnet = {
             # Workloads of resnet18 on imagenet
-            0: Workload(224, 224, 16, 64, 7, 7, 3, 3, 2, 2),
-            1: Workload(56, 56, 64, 64, 3, 3, 1, 1, 1, 1),
-            2: Workload(56, 56, 64, 64, 1, 1, 0, 0, 1, 1),
-            3: Workload(56, 56, 64, 128, 3, 3, 1, 1, 2, 2),
-            4: Workload(56, 56, 64, 128, 1, 1, 0, 0, 2, 2),
-            5: Workload(28, 28, 128, 128, 3, 3, 1, 1, 1, 1),
-            6: Workload(28, 28, 128, 256, 3, 3, 1, 1, 2, 2),
-            7: Workload(28, 28, 128, 256, 1, 1, 0, 0, 2, 2),
-            8: Workload(14, 14, 256, 256, 3, 3, 1, 1, 1, 1),
-            9: Workload(14, 14, 256, 512, 3, 3, 1, 1, 2, 2),
-            10: Workload(14, 14, 256, 512, 1, 1, 0, 0, 2, 2),
-            11: Workload(7, 7, 512, 512, 3, 3, 1, 1, 1, 1),
+            0: Workload(1, 224, 224, 16, 64, 7, 7, 3, 3, 2, 2),
+            1: Workload(1, 56, 56, 64, 64, 3, 3, 1, 1, 1, 1),
+            2: Workload(1, 56, 56, 64, 64, 1, 1, 0, 0, 1, 1),
+            3: Workload(1, 56, 56, 64, 128, 3, 3, 1, 1, 2, 2),
+            4: Workload(1, 56, 56, 64, 128, 1, 1, 0, 0, 2, 2),
+            5: Workload(1, 28, 28, 128, 128, 3, 3, 1, 1, 1, 1),
+            6: Workload(1, 28, 28, 128, 256, 3, 3, 1, 1, 2, 2),
+            7: Workload(1, 28, 28, 128, 256, 1, 1, 0, 0, 2, 2),
+            8: Workload(1, 14, 14, 256, 256, 3, 3, 1, 1, 1, 1),
+            9: Workload(1, 14, 14, 256, 512, 3, 3, 1, 1, 2, 2),
+            10: Workload(1, 14, 14, 256, 512, 1, 1, 0, 0, 2, 2),
+            11: Workload(1, 7, 7, 512, 512, 3, 3, 1, 1, 1, 1),
         }
 
         batch_size = 1
@@ -148,6 +147,7 @@ def _run(env, remote):
             print("key=%s" % key)
             print(wl)
             run_vta_conv2d(env, remote, key, batch_size, wl)
+
     vta.testing.run(_run)
 
 

From 5d9cc1e95548abae22f83c0ce320644427f10f85 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 21 May 2018 12:23:31 -0700
Subject: [PATCH 842/949] Update Graph Support for Batching, Fix Swapping (#37)

* fix graph transform for batch dimension

* fix

* fix
---
 .../resnet18/pynq/imagenet_predict.py         | 25 ++++--
 vta/python/vta/graph.py                       | 76 +++++++++++--------
 vta/src/runtime.cc                            |  4 +-
 vta/src/sim/sim_driver.cc                     |  1 +
 4 files changed, 67 insertions(+), 39 deletions(-)

diff --git a/vta/examples/resnet18/pynq/imagenet_predict.py b/vta/examples/resnet18/pynq/imagenet_predict.py
index e4f82b175e3c..554cceabd1d9 100644
--- a/vta/examples/resnet18/pynq/imagenet_predict.py
+++ b/vta/examples/resnet18/pynq/imagenet_predict.py
@@ -3,6 +3,7 @@
 import tvm
 from nnvm.compiler import graph_attr
 import vta
+import vta.testing
 import os
 import numpy as np
 from PIL import Image
@@ -12,7 +13,8 @@
 import wget
 from tvm.contrib import graph_runtime, rpc, util
 
-factor = 16
+bfactor = 1
+cfactor = 16
 host = "pynq"
 port = 9091
 verbose = False
@@ -38,6 +40,10 @@
 target = tvm.target.create("llvm -device=vta")
 target_host = "llvm -mtriple=armv7-none-linux-gnueabihf -mcpu=cortex-a9 -mattr=+neon"
 
+if vta.get_env().TARGET == "sim":
+    target_host = "llvm"
+
+
 synset = eval(open(os.path.join(CATEG_FILE)).read())
 image = Image.open(os.path.join(TEST_FILE)).resize((224, 224))
 
@@ -105,7 +111,7 @@ def mark_nop(graph, conv_layer=-1, skip_conv_layer=()):
 sym = vta.graph.clean_cast(sym)
 sym = vta.graph.clean_conv_fuse(sym)
 if target.device_name == "vta":
-    sym = vta.graph.pack(sym, shape_dict, factor)
+    sym = vta.graph.pack(sym, shape_dict, bfactor, cfactor)
 
 graph_attr.set_shape_inputs(sym, shape_dict)
 sym = sym.apply("InferShape")
@@ -127,7 +133,13 @@ def mark_nop(graph, conv_layer=-1, skip_conv_layer=()):
 assert tvm.module.enabled("rpc")
 temp = util.tempdir()
 lib.save(temp.relpath("graphlib.o"))
-remote = rpc.connect(host, port)
+
+if vta.get_env().TARGET == "sim":
+    remote = rpc.LocalSession()
+    print("local session")
+else:
+    remote = rpc.connect(host, port)
+
 remote.upload(temp.relpath("graphlib.o"))
 lib = remote.load_module("graphlib.o")
 ctx = remote.ext_dev(0) if target.device_name == "vta" else remote.cpu(0)
@@ -154,16 +166,17 @@ def run_e2e(graph):
     print("t-cost=%g" % tcost.mean)
 
 
-def run_layer(old_graph):
+def run_layer(old_graph, layer_begin, layer_end):
     """Run a certain layer."""
-    for layer_id in range(1, 2):
+    for layer_id in range(layer_begin, layer_end):
+        print("run resnet[%d]..."% (layer_id))
         graph = mark_nop(old_graph, layer_id)
         m = graph_runtime.create(graph, lib, ctx)
         # set inputs
         m.set_input('data', tvm.nd.array(x.astype("float32")))
         m.set_input(**params)
         # execute
-        timer = m.module.time_evaluator("run", ctx, number=10)
+        timer = m.module.time_evaluator("run", ctx, number=1)
         tcost = timer()
         print("resnet[%d]: %g\n"% (layer_id, tcost.mean))
 
diff --git a/vta/python/vta/graph.py b/vta/python/vta/graph.py
index b8237980d176..41a38c2b655b 100644
--- a/vta/python/vta/graph.py
+++ b/vta/python/vta/graph.py
@@ -10,51 +10,58 @@
 from nnvm.compiler import graph_attr, graph_util
 
 
-def _pack_channel(data, dshape, factor):
+def _pack_batch_channel(data, dshape, bfactor, cfactor):
     """Pack the data channel dimension.
     """
-    assert dshape[1] % factor == 0
+    assert dshape[0] % bfactor == 0
+    assert dshape[1] % cfactor == 0
     data = nnvm.sym.reshape(data,
-                            shape=(dshape[0], dshape[1] // factor,
-                                   factor, dshape[2], dshape[3]))
+                            shape=(dshape[0] // bfactor, bfactor,
+                                   dshape[1] // cfactor, cfactor,
+                                   dshape[2], dshape[3]))
     data = nnvm.sym.transpose(
-        data, axes=(0, 1, 3, 4, 2))
+        data, axes=(0, 2, 4, 5, 1, 3))
     return data
 
 
-def _unpack_channel(data, old_shape):
+def _unpack_batch_channel(data, old_shape):
     """Unpack the data channel dimension.
     """
-    data = nnvm.sym.transpose(data, axes=(0, 1, 4, 2, 3))
+    data = nnvm.sym.transpose(data, axes=(0, 4, 1, 5, 2, 3))
     data = nnvm.sym.reshape(data, shape=old_shape)
     return data
 
 
-def _pack_weight(data, dshape, factor):
+def _pack_weight(data, dshape, cfactor):
     """Pack the weight into packed format.
     """
     assert len(dshape) == 4
-    assert dshape[0] % factor == 0
-    assert dshape[1] % factor == 0
+    assert dshape[0] % cfactor == 0
+    assert dshape[1] % cfactor == 0
     data = nnvm.sym.reshape(data,
-                            shape=(dshape[0] // factor, factor,
-                                   dshape[1] // factor, factor,
+                            shape=(dshape[0] // cfactor, cfactor,
+                                   dshape[1] // cfactor, cfactor,
                                    dshape[2], dshape[3]))
     data = nnvm.sym.transpose(
         data, axes=(0, 2, 4, 5, 1, 3))
     return data
 
 
-def _pack_bias(data, dshape, factor):
+def _pack_bias(data, dshape, bfactor, cfactor):
     """Pack the bias parameter.
     """
     assert len(dshape) == 3
-    assert dshape[0] % factor == 0
+    assert dshape[0] % cfactor == 0
     data = nnvm.sym.reshape(data,
-                            shape=(dshape[0] // factor,
-                                   factor, dshape[1], dshape[2]))
+                            shape=(dshape[0] // cfactor,
+                                   cfactor, dshape[1],
+                                   dshape[2], 1))
     data = nnvm.sym.transpose(
-        data, axes=(0, 2, 3, 1))
+        data, axes=(0, 2, 3, 4, 1))
+    # broadcast batch dimension to bfactor
+    data = nnvm.sym.broadcast_to(
+        data,
+        shape=(dshape[0] // cfactor, dshape[1], dshape[2], bfactor, cfactor))
     return data
 
 
@@ -245,8 +252,8 @@ def _clean_cast(node, target_type):
     return ret
 
 
-def pack(graph, shape_dict, factor, start_name=None):
-    """Pack the graph into channel packed format.
+def pack(graph, shape_dict, bfactor, cfactor, start_name=None):
+    """Pack the graph into batch&channel packed format.
 
     Parameters
     ----------
@@ -256,8 +263,11 @@ def pack(graph, shape_dict, factor, start_name=None):
     shape_dict : dict of str to shapex
        The input shape.
 
-    factor : int
-       The packing factor
+    bfactor : int
+       The packing factor in batch
+
+    cfactor : int
+       The packing factor in channel
 
     start_name: str, optional
        Start name start packing from certain known node.
@@ -290,42 +300,44 @@ def pack(graph, shape_dict, factor, start_name=None):
             new_node = nnvm.symbol.Variable(node_name)
             if start_name and node_name == start_name:
                 start_pack = True
-                new_node = _pack_channel(new_node, oshape, factor)
+                new_node = _pack_batch_channel(new_node, oshape, bfactor, cfactor)
         elif op_name == "max_pool2d":
             assert not start_pack
             start_pack = True
             new_node = get_clone(children, op_name, node_name, attrs)
-            new_node = _pack_channel(new_node, oshape, factor)
+            new_node = _pack_batch_channel(new_node, oshape, bfactor, cfactor)
         elif op_name == "global_avg_pool2d":
             if start_pack:
                 start_pack = False
-                children[0] = _unpack_channel(children[0], ishape[0])
+                children[0] = _unpack_batch_channel(children[0], ishape[0])
                 new_node = getattr(nnvm.symbol, op_name)(
                     *children, name=node_name, **attrs)
             else:
                 new_node = get_clone(children, op_name, node_name, attrs)
         elif op_name == "quantized_conv2d":
             if start_pack:
-                attrs["pack_channel"] = str(factor)
+                attrs["pack_batch"] = str(bfactor)
+                attrs["pack_channel"] = str(cfactor)
                 data, weight = children
-                weight = _pack_weight(weight, ishape[1], factor)
+                weight = _pack_weight(weight, ishape[1], cfactor)
                 new_node = nnvm.sym.quantized_conv2d(
                     data, weight, name=node_name, **attrs)
             elif counter == 1:
-                attrs["pack_channel"] = str(factor)
+                attrs["pack_batch"] = str(bfactor)
+                attrs["pack_channel"] = str(cfactor)
                 data, weight = children
-                data = _pack_channel(data, ishape[0], factor)
-                weight = _pack_weight(weight, ishape[1], factor)
+                data = _pack_batch_channel(data, ishape[0], bfactor, cfactor)
+                weight = _pack_weight(weight, ishape[1], cfactor)
                 new_node = nnvm.sym.quantized_conv2d(
                     data, weight, name=node_name, **attrs)
-                new_node = _unpack_channel(new_node, oshape)
+                new_node = _unpack_batch_channel(new_node, oshape)
                 counter = counter + 1
             else:
                 new_node = get_clone(children, op_name, node_name, attrs)
         elif op_name.startswith("broadcast"):
             if start_pack:
                 assert len(ishape[1]) == 3
-                children[1] = _pack_bias(children[1], ishape[1], factor)
+                children[1] = _pack_bias(children[1], ishape[1], bfactor, cfactor)
                 new_node = getattr(nnvm.symbol, op_name)(
                     *children, name=node_name, **attrs)
             else:
@@ -341,7 +353,7 @@ def pack(graph, shape_dict, factor, start_name=None):
     ret = node_map[graph.index.output_entries[0][0]]
     if start_pack:
         oshape = shape[graph.index.output_entries[0][0]]
-        ret = _unpack_channel(ret, oshape)
+        ret = _unpack_batch_channel(ret, oshape)
     graph = nnvm.graph.create(ret)
     graph = graph_attr.set_shape_inputs(graph, shape_dict)
     graph = graph.apply("InferShape")
diff --git a/vta/src/runtime.cc b/vta/src/runtime.cc
index c0de87fa339d..9e84acfc047f 100644
--- a/vta/src/runtime.cc
+++ b/vta/src/runtime.cc
@@ -367,9 +367,10 @@ class UopQueue : public BaseQueue {
     }
     assert(num_op <= kMaxNumUop);
     uint32_t uop_begin = 0;
-    if (sram_end_ + num_op > kMaxElems) {
+    if (sram_end_ + num_op > kMaxNumUop) {
       // Need to evict
       cache_ptr_ = 0;
+      sram_begin_ = 0;
       sram_end_ = num_op;
     } else {
       uop_begin = sram_end_;
@@ -388,6 +389,7 @@ class UopQueue : public BaseQueue {
     dram_end_ += num_op;
     kernel->sram_begin_ = uop_begin;
     kernel->sram_end_ = sram_end_;
+    CHECK(kernel->cached());
     assert(uop_begin != sram_end_);
     cache_.insert(cache_.begin() + cache_ptr_, kernel);
     cache_.erase(cache_.begin() + evict_begin, cache_.begin() + cache_ptr_);
diff --git a/vta/src/sim/sim_driver.cc b/vta/src/sim/sim_driver.cc
index 57bc21c9805c..9a953e7aeadb 100644
--- a/vta/src/sim/sim_driver.cc
+++ b/vta/src/sim/sim_driver.cc
@@ -162,6 +162,7 @@ class DRAM {
    */
   void Free(void* data) {
     std::lock_guard<std::mutex> lock(mutex_);
+    if (pmap_.size() == 0) return;
     auto it = pmap_.find(data);
     CHECK(it != pmap_.end());
     Page* p = it->second.get();

From 81f6f1d746d3a338a1aba5119e15109cf82999ee Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@cs.washington.edu>
Date: Wed, 23 May 2018 15:39:12 -0700
Subject: [PATCH 843/949] [BITSTREAM SERVER] Bitstream server integration (#38)

---
 vta/NEWS.md                                   |  7 ++-
 vta/apps/pynq_rpc/README.md                   | 14 +----
 vta/examples/resnet18/pynq/README.md          | 11 +++-
 .../resnet18/pynq/imagenet_predict.py         | 15 +++--
 vta/hardware/xilinx/Makefile                  | 23 +++-----
 vta/hardware/xilinx/README.md                 |  2 -
 vta/make/config.json                          |  5 +-
 vta/make/sim_sample.json                      |  5 +-
 vta/make/vta_config.py                        | 20 ++++++-
 vta/python/vta/__init__.py                    |  3 +-
 vta/python/vta/bitstream.py                   | 55 +++++++++++++++++++
 vta/python/vta/environment.py                 | 25 +++++++--
 vta/python/vta/pkg_config.py                  |  3 +
 vta/python/vta/rpc_client.py                  | 14 ++++-
 vta/python/vta/testing/util.py                |  7 +--
 vta/tests/python/pynq/test_program_rpc.py     | 50 +++++++++--------
 16 files changed, 178 insertions(+), 81 deletions(-)
 create mode 100644 vta/python/vta/bitstream.py

diff --git a/vta/NEWS.md b/vta/NEWS.md
index 2c8fe5554f98..93b26f80b7c2 100644
--- a/vta/NEWS.md
+++ b/vta/NEWS.md
@@ -1,4 +1,4 @@
-TVM Change Log
+VTA Change Log
 ==============
 
 This file records the changes in VTA stack in reverse chronological order.
@@ -6,7 +6,8 @@ This file records the changes in VTA stack in reverse chronological order.
 
 ## Initial version
 
-- Vivado based hardware
-- Driver for PYNQ
+- Vivado based hardware.
+- Driver for PYNQ board.
 - Runtime library.
 - TVM compiler stack.
+- Resnet-18 example.
diff --git a/vta/apps/pynq_rpc/README.md b/vta/apps/pynq_rpc/README.md
index 4cc2b46fbcbd..2025cac23abb 100644
--- a/vta/apps/pynq_rpc/README.md
+++ b/vta/apps/pynq_rpc/README.md
@@ -31,21 +31,10 @@ From there, clone the VTA repository:
 git clone git@github.com:uwsaml/vta.git --recursive
 ```
 
-Next, clone the TVM repository:
-```bash
-git clone git@github.com:dmlc/tvm.git --recursive
-```
-
-TVM is rapidly changing, and to ensure stability, we keep track of working TVM checkpoints.
-As of now, the TVM checkpoint `e4c2af9abdcb3c7aabafba8084414d7739c17c4c` is known to work with VTA.
-```bash
-git checkout e4c2af9abdcb3c7aabafba8084414d7739c17c4c
-```
-
 Now, ssh into your **Pynq board** to build the TVM runtime with the following commands:
 ```bash
 ssh xilinx@192.168.2.99 # ssh if you haven't done so
-cd ~/tvm
+cd ~/vta/nnvm/tvm
 cp make/config.mk .
 echo USE_RPC=1 >> config.mk
 make runtime -j2
@@ -57,7 +46,6 @@ We're now ready to build the Pynq RPC server on the Pynq board.
 ```bash
 ssh xilinx@192.168.2.99 # ssh if you haven't done so
 cd ~/vta
-export TVM_PATH = /home/xilinx/tvm
 make
 ```
 
diff --git a/vta/examples/resnet18/pynq/README.md b/vta/examples/resnet18/pynq/README.md
index 51a2bc4bed3e..aa521392a077 100644
--- a/vta/examples/resnet18/pynq/README.md
+++ b/vta/examples/resnet18/pynq/README.md
@@ -4,6 +4,7 @@ In order to run this example you'll need to have:
 * VTA installed
 * TVM installed
 * NNVM installed
+* MxNet installed
 * A Pynq-based RPC server running
 
 ## VTA installation
@@ -26,9 +27,9 @@ git clone git@github.com:dmlc/tvm.git --recursive
 ```
 
 TVM is rapidly changing, and to ensure stability, we keep track of working TVM checkpoints.
-As of now, the TVM checkpoint `e4c2af9abdcb3c7aabafba8084414d7739c17c4c` is known to work with VTA.
+As of now, the TVM checkpoint `168f099155106d1188dbc54ac00acc02900a3c6f` is known to work with VTA.
 ```bash
-git checkout e4c2af9abdcb3c7aabafba8084414d7739c17c4c
+git checkout 168f099155106d1188dbc54ac00acc02900a3c6f
 ```
 
 Before building TVM, copy the `make/config.mk` file into the root TVM directory:
@@ -59,7 +60,7 @@ Clone the NNVM repository from `tqchen` in the directory of your choosing:
 git clone git@github.com:tqchen/nnvm.git --recursive
 ```
 
-To run this example, we rely on a special branch of NNVM: `qt`:
+To run this example, we rely on a special branch of NNVM until these changes get merged back into the main repo: `qt`:
 ```bash
 cd <nnvm root>
 git checkout qt
@@ -76,6 +77,10 @@ Finally update your `~/.bashrc` file to include the NNVM python libraries in you
 export PYTHONPATH=<nnvm root>/python:${PYTHONPATH}
 ```
 
+## MxNet Installation
+
+Follow the [MxNet Installation Instructions](https://mxnet.incubator.apache.org)
+
 ## Pynq RPC Server Setup
                                                        
 Follow the [Pynq RPC Server Guide](https://github.com/uwsaml/vta/tree/master/apps/pynq_rpc/README.md)
diff --git a/vta/examples/resnet18/pynq/imagenet_predict.py b/vta/examples/resnet18/pynq/imagenet_predict.py
index 554cceabd1d9..8ec0d5a2761e 100644
--- a/vta/examples/resnet18/pynq/imagenet_predict.py
+++ b/vta/examples/resnet18/pynq/imagenet_predict.py
@@ -15,8 +15,6 @@
 
 bfactor = 1
 cfactor = 16
-host = "pynq"
-port = 9091
 verbose = False
 # only run fpga component, mark non-conv ops as nop
 debug_fpga_only = False
@@ -27,8 +25,7 @@
 CATEG_FILE = 'synset.txt'
 RESNET_GRAPH_FILE = 'quantize_graph.json'
 RESNET_PARAMS_FILE = 'quantize_params.pkl'
-BITSTREAM_FILE = 'vta.bit'
-for file in [TEST_FILE, CATEG_FILE, RESNET_GRAPH_FILE, RESNET_PARAMS_FILE, BITSTREAM_FILE]:
+for file in [TEST_FILE, CATEG_FILE, RESNET_GRAPH_FILE, RESNET_PARAMS_FILE]:
     if not os.path.isfile(file):
         print ("Downloading {}".format(file))
         wget.download(url+file)
@@ -43,7 +40,6 @@
 if vta.get_env().TARGET == "sim":
     target_host = "llvm"
 
-
 synset = eval(open(os.path.join(CATEG_FILE)).read())
 image = Image.open(os.path.join(TEST_FILE)).resize((224, 224))
 
@@ -138,8 +134,17 @@ def mark_nop(graph, conv_layer=-1, skip_conv_layer=()):
     remote = rpc.LocalSession()
     print("local session")
 else:
+    host = os.environ.get("VTA_PYNQ_RPC_HOST", None)
+    assert host
+    port = os.environ.get("VTA_PYNQ_RPC_PORT", "9091")
+    port = int(port)
     remote = rpc.connect(host, port)
 
+# Program FPGA, and build runtime if necessary
+# Overwrite bitstream with a path to your own if you built it yourself
+vta.reconfig_runtime(remote)
+vta.program_fpga(remote, bitstream=None)
+
 remote.upload(temp.relpath("graphlib.o"))
 lib = remote.load_module("graphlib.o")
 ctx = remote.ext_dev(0) if target.device_name == "vta" else remote.cpu(0)
diff --git a/vta/hardware/xilinx/Makefile b/vta/hardware/xilinx/Makefile
index 9ed7654edb84..106d5d25b348 100644
--- a/vta/hardware/xilinx/Makefile
+++ b/vta/hardware/xilinx/Makefile
@@ -45,6 +45,12 @@ VTA_WGT_BUFF_SIZE := $(shell ${VTA_CONFIG} --get-wgtbuffsize)
 VTA_ACC_BUFF_SIZE := $(shell ${VTA_CONFIG} --get-accbuffsize)
 VTA_OUT_BUFF_SIZE := $(shell ${VTA_CONFIG} --get-outbuffsize)
 
+#---------------------
+# FPGA Parameters
+#--------------------
+VTA_CLOCK_FREQ = $(shell ${VTA_CONFIG} --get-fpgafreq)
+VTA_TARGET_PER = $(shell ${VTA_CONFIG} --get-fpgaper)
+
 #---------------------
 # Compilation parameters
 #--------------------
@@ -52,19 +58,8 @@ VTA_OUT_BUFF_SIZE := $(shell ${VTA_CONFIG} --get-outbuffsize)
 #  Number of threads during compilation
 VTA_HW_COMP_THREADS = 8
 
-#  Target Frequency
-VTA_HW_COMP_CLOCK_FREQ = 100
-
-#  Timing closure compensation (0 for none, 3 for highest)
-VTA_HW_COMP_TIMING_COMP = 0
-
-# Derive clock target period
-TARGET_PER = \
-$(shell echo "$$(( (1000 + $(VTA_HW_COMP_CLOCK_FREQ) - 1) / $(VTA_HW_COMP_CLOCK_FREQ) - $(VTA_HW_COMP_TIMING_COMP)))" )
-
 # Derive config name
-CONF_ROOT = $(shell ${VTA_CONFIG} --cfg-str)
-CONF = $(CONF_ROOT)_$(VTA_HW_COMP_CLOCK_FREQ)MHz_$(TARGET_PER)ns
+CONF = $(shell ${VTA_CONFIG} --cfg-str)
 IP_BUILD_PATH = $(BUILD_DIR)/hls/$(CONF)
 HW_BUILD_PATH = $(BUILD_DIR)/vivado/$(CONF)
 
@@ -90,7 +85,7 @@ $(IP_PATH): $(SRC_DIR)/*
 	cd $(IP_BUILD_PATH) && \
 		$(VIVADO_HLS) -f $(SCRIPT_DIR)/hls.tcl \
 		-tclargs $(SRC_DIR) $(SIM_DIR) $(TEST_DIR) $(INCLUDE_DIR) \
-		$(MODE) $(DEBUG) $(NO_DSP) $(NO_ALU) $(TARGET_PER) \
+		$(MODE) $(DEBUG) $(NO_DSP) $(NO_ALU) $(VTA_TARGET_PER) \
 		$(VTA_INP_WIDTH) $(VTA_WGT_WIDTH) $(VTA_ACC_WIDTH) $(VTA_OUT_WIDTH) \
 		$(VTA_BATCH) $(VTA_IN_BLOCK) $(VTA_OUT_BLOCK) \
 		$(VTA_UOP_BUFF_SIZE) $(VTA_INP_BUFF_SIZE) $(VTA_WGT_BUFF_SIZE) \
@@ -104,7 +99,7 @@ $(BIT_PATH): $(IP_PATH)
 	mkdir -p $(HW_BUILD_PATH)
 	cd $(HW_BUILD_PATH) && \
 		$(VIVADO) -mode tcl -source $(SCRIPT_DIR)/vivado.tcl \
-		-tclargs $(BUILD_DIR)/hls/$(CONF) $(VTA_HW_COMP_THREADS) $(VTA_HW_COMP_CLOCK_FREQ) \
+		-tclargs $(BUILD_DIR)/hls/$(CONF) $(VTA_HW_COMP_THREADS) $(VTA_CLOCK_FREQ) \
 		$(VTA_INP_WIDTH) $(VTA_WGT_WIDTH) $(VTA_OUT_WIDTH) \
 		$(VTA_BATCH) $(VTA_IN_BLOCK) $(VTA_OUT_BLOCK) \
 		$(VTA_INP_BUFF_SIZE) $(VTA_WGT_BUFF_SIZE) $(VTA_OUT_BUFF_SIZE)
diff --git a/vta/hardware/xilinx/README.md b/vta/hardware/xilinx/README.md
index 0c68724ea671..69f66d654e6d 100644
--- a/vta/hardware/xilinx/README.md
+++ b/vta/hardware/xilinx/README.md
@@ -67,7 +67,5 @@ make
 
 The local `Makefile` containts several variables that can be tweaked by the user:
 * `VTA_HW_COMP_THREADS`: determines the number of threads used for the Vivado compilation job (default 8 threads).
-* `VTA_HW_COMP_CLOCK_FREQ`: determines the target frequency of the VTA design (default 100MHz). It can only be set to 100, 142, 167 or 200MHz.
-* `VTA_HW_COMP_TIMING_COMP`: determines how much additional slack must be provided to close timing (default 0ns). Generally when utilization is high for an FPGA design, setting this paramter to 1, 2 or 3 can help close timing.
 
 Once the compilation completes, the generated bitstream can be found under `<vta root>/build/hardware/xilinx/vivado/<design name>/export/vta.bit`. 
\ No newline at end of file
diff --git a/vta/make/config.json b/vta/make/config.json
index c64473e74489..5c37108e6b12 100644
--- a/vta/make/config.json
+++ b/vta/make/config.json
@@ -1,5 +1,8 @@
 {
   "TARGET" : "pynq",
+  "HW_FREQ" : 100,
+  "HW_CLK_TARGET" : 8,
+  "HW_VER" : "0.0.0",
   "LOG_INP_WIDTH" : 3,
   "LOG_WGT_WIDTH" : 3,
   "LOG_ACC_WIDTH" : 5,
@@ -7,7 +10,7 @@
   "LOG_BATCH" : 0,
   "LOG_BLOCK_IN" : 4,
   "LOG_BLOCK_OUT" : 4,
-  "LOG_UOP_BUFF_SIZE" : 14,
+  "LOG_UOP_BUFF_SIZE" : 15,
   "LOG_INP_BUFF_SIZE" : 15,
   "LOG_WGT_BUFF_SIZE" : 18,
   "LOG_ACC_BUFF_SIZE" : 17
diff --git a/vta/make/sim_sample.json b/vta/make/sim_sample.json
index 747e22fce265..73a00e33d488 100644
--- a/vta/make/sim_sample.json
+++ b/vta/make/sim_sample.json
@@ -1,5 +1,8 @@
 {
   "TARGET" : "sim",
+  "HW_FREQ" : 100,
+  "HW_CLK_TARGET" : 8,
+  "HW_VER" : "0.0.0",
   "LOG_INP_WIDTH" : 3,
   "LOG_WGT_WIDTH" : 3,
   "LOG_ACC_WIDTH" : 5,
@@ -7,7 +10,7 @@
   "LOG_BATCH" : 0,
   "LOG_BLOCK_IN" : 4,
   "LOG_BLOCK_OUT" : 4,
-  "LOG_UOP_BUFF_SIZE" : 14,
+  "LOG_UOP_BUFF_SIZE" : 15,
   "LOG_INP_BUFF_SIZE" : 15,
   "LOG_WGT_BUFF_SIZE" : 18,
   "LOG_ACC_BUFF_SIZE" : 17
diff --git a/vta/make/vta_config.py b/vta/make/vta_config.py
index c64a11f6a594..9788e433e308 100644
--- a/vta/make/vta_config.py
+++ b/vta/make/vta_config.py
@@ -54,6 +54,10 @@ def main():
                         help="returns log of accum buffer size in B")
     parser.add_argument("--get-outbuffsize", action="store_true",
                         help="returns log of output buffer size in B")
+    parser.add_argument("--get-fpgafreq", action="store_true",
+                        help="returns FPGA frequency")
+    parser.add_argument("--get-fpgaper", action="store_true",
+                        help="returns HLS target clock period")
     args = parser.parse_args()
 
     if len(sys.argv) == 1:
@@ -91,7 +95,8 @@ def main():
         print(pkg.cfg_json)
 
     if args.cfg_str:
-        cfg_str = "{}x{}x{}_{}bx{}b_{}_{}_{}_{}".format(
+        # Needs to match the BITSTREAM string in python/vta/environment.py
+        cfg_str = "{}x{}x{}_{}bx{}b_{}_{}_{}_{}_{}MHz_{}ns_v{}".format(
             (1 << cfg["LOG_BATCH"]),
             (1 << cfg["LOG_BLOCK_IN"]),
             (1 << cfg["LOG_BLOCK_OUT"]),
@@ -100,8 +105,11 @@ def main():
             cfg["LOG_UOP_BUFF_SIZE"],
             cfg["LOG_INP_BUFF_SIZE"],
             cfg["LOG_WGT_BUFF_SIZE"],
-            cfg["LOG_ACC_BUFF_SIZE"])
-        print cfg_str
+            cfg["LOG_ACC_BUFF_SIZE"],
+            cfg["HW_FREQ"],
+            cfg["HW_CLK_TARGET"],
+            cfg["HW_VER"].replace('.', '_'))
+        print(cfg_str)
 
     if args.get_inpwidth:
         print(cfg["LOG_INP_WIDTH"])
@@ -139,5 +147,11 @@ def main():
     if args.get_accbuffsize:
         print(cfg["LOG_ACC_BUFF_SIZE"])
 
+    if args.get_fpgafreq:
+        print(cfg["HW_FREQ"])
+
+    if args.get_fpgaper:
+        print(cfg["HW_CLK_TARGET"])
+
 if __name__ == "__main__":
     main()
diff --git a/vta/python/vta/__init__.py b/vta/python/vta/__init__.py
index 37c2e7f34792..c2dc7c30d190 100644
--- a/vta/python/vta/__init__.py
+++ b/vta/python/vta/__init__.py
@@ -8,11 +8,10 @@
 
 __version__ = "0.1.0"
 
-
+from .bitstream import get_bitstream_path, download_bitstream
 from .environment import get_env, Environment
 from .rpc_client import reconfig_runtime, program_fpga
 
-
 try:
     from . import top
     from .build_module import build_config, lower, build
diff --git a/vta/python/vta/bitstream.py b/vta/python/vta/bitstream.py
new file mode 100644
index 000000000000..dd0a9afdb640
--- /dev/null
+++ b/vta/python/vta/bitstream.py
@@ -0,0 +1,55 @@
+"""VTA specific bitstream management library."""
+from __future__ import absolute_import as _abs
+
+import os
+import urllib
+from .environment import get_env
+
+# bitstream repo
+BITSTREAM_URL = "https://github.com/uwsaml/vta-distro/raw/master/bitstreams/"
+
+def get_bitstream_path():
+    """Returns the path to the cached bitstream corresponding to the current config
+
+    Returns
+    -------
+    bit_path: str
+        Corresponding to the filepath of the bitstream
+    """
+
+    env = get_env()
+
+    # Derive destination path
+    cache_dir = os.getenv("VTA_CACHE_PATH", os.path.join(os.getenv("HOME"), ".vta_cache/"))
+    cache_dir = os.path.join(cache_dir, env.TARGET)
+    # Create the directory if it didn't exist
+    if not os.path.exists(cache_dir):
+        os.makedirs(cache_dir)
+    bit_path = os.path.join(cache_dir, env.BITSTREAM)
+
+    return bit_path
+
+
+def download_bitstream():
+    """Downloads a cached bitstream corresponding to the current config
+    """
+
+    env = get_env()
+
+    success = False
+    bit = get_bitstream_path()
+    url = os.path.join(BITSTREAM_URL, env.TARGET)
+    url = os.path.join(url, env.HW_VER)
+    url = os.path.join(url, env.BITSTREAM)
+    # Check that the bitstream is accessible from the server
+    if urllib.urlopen(url).getcode() == 404:
+        # Raise error - the solution when this happens it to build your own bitstream and add it
+        # to your VTA_CACHE_PATH
+        raise RuntimeError(
+            "Error: {} is not available. It appears that this configuration has not been built."
+            .format(url))
+    else:
+        urllib.urlretrieve(url, bit)
+        success = True
+
+    return success
diff --git a/vta/python/vta/environment.py b/vta/python/vta/environment.py
index 41ec38aefc05..7391d56e2774 100644
--- a/vta/python/vta/environment.py
+++ b/vta/python/vta/environment.py
@@ -24,7 +24,7 @@ class DevContext(object):
     Note
     ----
     This class is introduced so we have a clear separation
-    of developer related stuffs and user facing attributes.
+    of developer related, and user facing attributes.
     """
     # Memory id for DMA
     MEM_ID_UOP = 0
@@ -62,7 +62,7 @@ def get_task_qid(self, qid):
 
 
 class Environment(object):
-    """Hareware configuration object.
+    """Hardware configuration object.
 
     This object contains all the information
     needed for compiling to a specific VTA backend.
@@ -98,23 +98,24 @@ class Environment(object):
 
     # initialization function
     def __init__(self, cfg):
-        # Log of input/activation width in bits
         self.__dict__.update(cfg)
         for key in PkgConfig.cfg_keys:
             if key not in cfg:
                 raise ValueError("Expect key %s in cfg" % key)
+        # derive output buffer size
         self.LOG_OUT_BUFF_SIZE = (
             self.LOG_ACC_BUFF_SIZE +
             self.LOG_OUT_WIDTH -
             self.LOG_ACC_WIDTH)
-        # width
+        # data type width
         self.INP_WIDTH = 1 << self.LOG_INP_WIDTH
         self.WGT_WIDTH = 1 << self.LOG_WGT_WIDTH
         self.ACC_WIDTH = 1 << self.LOG_ACC_WIDTH
+        self.OUT_WIDTH = self.INP_WIDTH
+        # tensor intrinsic shape
         self.BATCH = 1 << self.LOG_BATCH
         self.BLOCK_IN = 1 << self.LOG_BLOCK_IN
         self.BLOCK_OUT = 1 << self.LOG_BLOCK_OUT
-        self.OUT_WIDTH = self.INP_WIDTH
         # buffer size
         self.UOP_BUFF_SIZE = 1 << self.LOG_UOP_BUFF_SIZE
         self.INP_BUFF_SIZE = 1 << self.LOG_INP_BUFF_SIZE
@@ -138,6 +139,20 @@ def __init__(self, cfg):
         self.WGT_ELEM_BYTES = self.WGT_ELEM_BITS // 8
         self.ACC_ELEM_BYTES = self.ACC_ELEM_BITS // 8
         self.OUT_ELEM_BYTES = self.OUT_ELEM_BITS // 8
+        # Configuration bitstream name
+        self.BITSTREAM = "{}x{}x{}_{}bx{}b_{}_{}_{}_{}_{}MHz_{}ns_v{}.bit".format(
+            (1 << cfg["LOG_BATCH"]),
+            (1 << cfg["LOG_BLOCK_IN"]),
+            (1 << cfg["LOG_BLOCK_OUT"]),
+            (1 << cfg["LOG_INP_WIDTH"]),
+            (1 << cfg["LOG_WGT_WIDTH"]),
+            cfg["LOG_UOP_BUFF_SIZE"],
+            cfg["LOG_INP_BUFF_SIZE"],
+            cfg["LOG_WGT_BUFF_SIZE"],
+            cfg["LOG_ACC_BUFF_SIZE"],
+            cfg["HW_FREQ"],
+            cfg["HW_CLK_TARGET"],
+            cfg["HW_VER"].replace('.', '_'))
         # dtypes
         self.acc_dtype = "int%d" % self.ACC_WIDTH
         self.inp_dtype = "int%d" % self.INP_WIDTH
diff --git a/vta/python/vta/pkg_config.py b/vta/python/vta/pkg_config.py
index 36655982dbe9..f657b2b32fc8 100644
--- a/vta/python/vta/pkg_config.py
+++ b/vta/python/vta/pkg_config.py
@@ -22,6 +22,9 @@ class PkgConfig(object):
     """
     cfg_keys = [
         "TARGET",
+        "HW_FREQ",
+        "HW_CLK_TARGET",
+        "HW_VER",
         "LOG_INP_WIDTH",
         "LOG_WGT_WIDTH",
         "LOG_ACC_WIDTH",
diff --git a/vta/python/vta/rpc_client.py b/vta/python/vta/rpc_client.py
index 341130ed7a1c..999250cd789d 100644
--- a/vta/python/vta/rpc_client.py
+++ b/vta/python/vta/rpc_client.py
@@ -2,6 +2,7 @@
 import os
 
 from .environment import get_env
+from .bitstream import download_bitstream, get_bitstream_path
 
 def reconfig_runtime(remote):
     """Reconfigure remote runtime based on current hardware spec.
@@ -16,7 +17,7 @@ def reconfig_runtime(remote):
     freconfig(env.pkg_config().cfg_json)
 
 
-def program_fpga(remote, bitstream):
+def program_fpga(remote, bitstream=None):
     """Upload and program bistream
 
     Parameters
@@ -24,9 +25,16 @@ def program_fpga(remote, bitstream):
     remote : RPCSession
         The TVM RPC session
 
-    bitstream : str
-        Path to a local bistream file.
+    bitstream : str, optional
+        Path to a local bistream file. If unset, tries to download from cache server.
     """
+    if bitstream:
+        assert os.path.isfile(bitstream)
+    else:
+        bitstream = get_bitstream_path()
+        if not os.path.isfile(bitstream):
+            download_bitstream()
+
     fprogram = remote.get_function("tvm.contrib.vta.init")
     remote.upload(bitstream)
     fprogram(os.path.basename(bitstream))
diff --git a/vta/python/vta/testing/util.py b/vta/python/vta/testing/util.py
index 402546c0efa1..18701320ffef 100644
--- a/vta/python/vta/testing/util.py
+++ b/vta/python/vta/testing/util.py
@@ -6,7 +6,6 @@
 from ..environment import get_env
 from . import simulator
 
-
 def run(run_func):
     """Run test function on all available env.
 
@@ -29,10 +28,10 @@ def run(run_func):
             run_func(env, rpc.LocalSession())
 
     # Run on PYNQ if env variable exists
-    pynq_host = os.environ.get("VTA_PYNQ_RPC_HOST", None)
-    if pynq_host:
+    host = os.environ.get("VTA_PYNQ_RPC_HOST", None)
+    if host:
         env.TARGET = "pynq"
         port = os.environ.get("VTA_PYNQ_RPC_PORT", "9091")
         port = int(port)
-        remote = rpc.connect(pynq_host, port)
+        remote = rpc.connect(host, port)
         run_func(env, remote)
diff --git a/vta/tests/python/pynq/test_program_rpc.py b/vta/tests/python/pynq/test_program_rpc.py
index 4c252acbae7c..22a1e69dec6b 100644
--- a/vta/tests/python/pynq/test_program_rpc.py
+++ b/vta/tests/python/pynq/test_program_rpc.py
@@ -1,30 +1,36 @@
-import tvm
-import vta
 import os
-from tvm.contrib import rpc, util
-
-env = vta.get_env()
-host = "pynq"
-port = 9091
-target = "llvm -target=armv7-none-linux-gnueabihf"
-bit = "{}x{}x{}_{}bx{}b_{}_{}_{}_{}_100MHz_10ns.bit".format(
-		env.BATCH, env.BLOCK_IN, env.BLOCK_OUT,
-		env.INP_WIDTH, env.WGT_WIDTH,
-		env.LOG_UOP_BUFF_SIZE, env.LOG_INP_BUFF_SIZE,
-		env.LOG_WGT_BUFF_SIZE, env.LOG_ACC_BUFF_SIZE)
+import tvm
+from tvm.contrib import rpc
+from vta import get_bitstream_path, download_bitstream, program_fpga, reconfig_runtime
 
-curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
-bitstream = os.path.join(curr_path, "../../../../vta_bitstreams/bitstreams/", bit)
+def program_rpc_bitstream(path=None):
+    """Program the FPGA on the RPC server
 
-def test_program_rpc():
+    Parameters
+    ----------
+    path : path to bitstream (optional)
+    """
     assert tvm.module.enabled("rpc")
+    host = os.environ.get("VTA_PYNQ_RPC_HOST", None)
+    if not host:
+        raise RuntimeError(
+            "Error: VTA_PYNQ_RPC_HOST environment variable not set.")
+    # If a path to a bitstream is passed, make sure that it point to a valid bitstream
+    port = os.environ.get("VTA_PYNQ_RPC_PORT", "9091")
+    port = int(port)
     remote = rpc.connect(host, port)
-    vta.program_fpga(remote, bit)
+    program_fpga(remote, path)
 
-def test_reconfig_runtime():
+def reconfig_rpc_runtime():
+    """Reconfig the RPC server runtime
+    """
     assert tvm.module.enabled("rpc")
-    remote = rpc.connect(host, port)
-    vta.reconfig_runtime(remote)
+    host = os.environ.get("VTA_PYNQ_RPC_HOST", None)
+    if host:
+        port = os.environ.get("VTA_PYNQ_RPC_PORT", "9091")
+        port = int(port)
+        remote = rpc.connect(host, port)
+        reconfig_runtime(remote)
 
-test_program_rpc()
-test_reconfig_runtime()
+program_rpc_bitstream()
+reconfig_rpc_runtime()

From c4a46b178eba556e915dbd74990df61400877b5c Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 24 May 2018 10:36:46 -0700
Subject: [PATCH 844/949] [INIT] Allow proper throw in compiler (#39)

---
 vta/python/vta/__init__.py | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/vta/python/vta/__init__.py b/vta/python/vta/__init__.py
index c2dc7c30d190..926d73649b31 100644
--- a/vta/python/vta/__init__.py
+++ b/vta/python/vta/__init__.py
@@ -6,15 +6,17 @@
 """
 from __future__ import absolute_import as _abs
 
-__version__ = "0.1.0"
+import sys
 
 from .bitstream import get_bitstream_path, download_bitstream
 from .environment import get_env, Environment
 from .rpc_client import reconfig_runtime, program_fpga
 
-try:
+__version__ = "0.1.0"
+
+# do not import nnvm/topi when running vta.exec.rpc_server
+# to maintain minimum dependency on the board
+if sys.argv[0] not in ("-c", "-m"):
     from . import top
     from .build_module import build_config, lower, build
     from . import graph
-except (ImportError, RuntimeError):
-    pass

From ec7ce88fbda3092ebbfd3e2ce201307e1d1ae83f Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 24 May 2018 15:40:14 -0700
Subject: [PATCH 845/949] [EXAMPLE] Fix example for simulator (#40)

---
 vta/examples/resnet18/pynq/imagenet_predict.py | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/vta/examples/resnet18/pynq/imagenet_predict.py b/vta/examples/resnet18/pynq/imagenet_predict.py
index 8ec0d5a2761e..e5b4a1131461 100644
--- a/vta/examples/resnet18/pynq/imagenet_predict.py
+++ b/vta/examples/resnet18/pynq/imagenet_predict.py
@@ -139,11 +139,10 @@ def mark_nop(graph, conv_layer=-1, skip_conv_layer=()):
     port = os.environ.get("VTA_PYNQ_RPC_PORT", "9091")
     port = int(port)
     remote = rpc.connect(host, port)
-
-# Program FPGA, and build runtime if necessary
-# Overwrite bitstream with a path to your own if you built it yourself
-vta.reconfig_runtime(remote)
-vta.program_fpga(remote, bitstream=None)
+    # Program FPGA, and build runtime if necessary
+    # Overwrite bitstream with a path to your own if you built it yourself
+    vta.reconfig_runtime(remote)
+    vta.program_fpga(remote, bitstream=None)
 
 remote.upload(temp.relpath("graphlib.o"))
 lib = remote.load_module("graphlib.o")

From 5c5408fd970e23edd596767afff81d37c00f228b Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@cs.washington.edu>
Date: Thu, 24 May 2018 20:13:15 -0700
Subject: [PATCH 846/949] [DOC, EXAMPLE] Updated READMEs, tests, etc. (#41)

* bug fix for new drivers in new PYNQ image v2.1

* updating instructions for resnet inference

* updated the instructions for starting the RPC server

* deriving host/port from env for unit tests
---
 vta/apps/pynq_rpc/README.md                   | 30 +++++------
 vta/examples/resnet18/pynq/README.md          | 52 ++++++++++++++++---
 vta/src/pynq/pynq_driver.h                    |  2 +-
 .../python/pynq/test_benchmark_conv2d.py      |  4 +-
 vta/tests/python/pynq/test_program_rpc.py     | 18 ++-----
 5 files changed, 67 insertions(+), 39 deletions(-)

diff --git a/vta/apps/pynq_rpc/README.md b/vta/apps/pynq_rpc/README.md
index 2025cac23abb..fb9688853690 100644
--- a/vta/apps/pynq_rpc/README.md
+++ b/vta/apps/pynq_rpc/README.md
@@ -1,21 +1,24 @@
-### PYNQ RPC Server for VTA
+# PYNQ RPC Server for VTA
 
 This guide describes how to setup a Pynq-based RPC server to accelerate deep learning workloads with VTA.
 
 ## Pynq Setup
 
 Follow the getting started tutorial for the [Pynq board](http://pynq.readthedocs.io/en/latest/getting_started.html).
-* For this RPC setup make sure to go with the *Connect to a Computer* Ethernet setup.
+* This assumes that you've downloaded the latest Pynq image, PYNQ-Z1 v2.1 (released 21 Feb 2018).
+* For this RPC setup, follow the ["Connect to a Computer"](http://pynq.readthedocs.io/en/latest/getting_started.html#connect-to-a-computer) Pynq setup instructions.
+* To be able to talk to the board, you'll need to make sure that you've followed the steps to [assign a static IP address](http://pynq.readthedocs.io/en/latest/appendix.html#assign-your-computer-a-static-ip)
 
-Make sure that you can ssh into your Pynq board successfully:
+Make sure that you can talk to your Pynq board successfully:
 ```bash
-ssh xilinx@192.168.2.99
+ping 192.168.2.99
 ```
 
-When ssh-ing onto the board, the default password for the `xilinx` account is `xilinx`.
+When ssh-ing onto the board, the password for the `xilinx` username is `xilinx`.
 
-For convenience let's go ahead and mount the Pynq board's file system to easily access it and maintain it:
+For convenience let's go ahead and mount the Pynq board's file system to easily access it (this will require sshfs to be installed):
 ```bash
+mkdir <mountpoint>
 sshfs xilinx@192.168.2.99:/home/xilinx <mountpoint>
 ```
 
@@ -31,7 +34,7 @@ From there, clone the VTA repository:
 git clone git@github.com:uwsaml/vta.git --recursive
 ```
 
-Now, ssh into your **Pynq board** to build the TVM runtime with the following commands:
+Now, ssh into your **Pynq board** to build the TVM runtime with the following commands. This build should take about 5 minutes.
 ```bash
 ssh xilinx@192.168.2.99 # ssh if you haven't done so
 cd ~/vta/nnvm/tvm
@@ -40,21 +43,18 @@ echo USE_RPC=1 >> config.mk
 make runtime -j2
 ```
 
-## Pynq RPC server setup
-
-We're now ready to build the Pynq RPC server on the Pynq board.
+We're now ready to build the Pynq RPC server on the Pynq board, which should take less than 30 seconds.
 ```bash
 ssh xilinx@192.168.2.99 # ssh if you haven't done so
 cd ~/vta
-make
+make -j2
 ```
 
-The last stage will build the `192.168.2.99:home/xilinx/vta/lib/libvta.so` library file. We are now ready to launch the RPC server on the Pynq. In order to enable the FPGA drivers, we need to run the RPC server with administrator privileges (using `su`, account: `xilinx`, pwd: `xilinx`).
+The last stage will build the `vta/lib/libvta.so` library file. We are now ready to launch the RPC server on the Pynq. In order to enable the FPGA drivers, we need to run the RPC server with `sudo` privileges.
 ```bash
 ssh xilinx@192.168.2.99 # ssh if you haven't done so
 cd ~/vta
-su
-./apps/pynq_rpc/start_rpc_server.sh
+sudo ./apps/pynq_rpc/start_rpc_server.sh # pw is xilinx
 ```
 
 You should see the following being displayed when starting the RPC server:
@@ -65,4 +65,4 @@ INFO:root:RPCServer: bind to 0.0.0.0:9091
 
 Note that it should be listening on port `9091`.
 
-To kill the RPC server, just enter the `Ctrl + c` command.
\ No newline at end of file
+To kill the RPC server, just enter the `Ctrl + c` command.
diff --git a/vta/examples/resnet18/pynq/README.md b/vta/examples/resnet18/pynq/README.md
index aa521392a077..1906ca082378 100644
--- a/vta/examples/resnet18/pynq/README.md
+++ b/vta/examples/resnet18/pynq/README.md
@@ -2,10 +2,14 @@
 
 In order to run this example you'll need to have:
 * VTA installed
+* LLVM 4.0 or newer installed
 * TVM installed
 * NNVM installed
 * MxNet installed
 * A Pynq-based RPC server running
+* Python packages installed
+
+Required setup time from scratch: ~15 mins.
 
 ## VTA installation
 
@@ -19,6 +23,23 @@ Update your `~/.bashrc` file to include the VTA python libraries in your `PYTHON
 export PYTHONPATH=<vta root>/python:${PYTHONPATH}
 ```
 
+## LLVM installation
+
+We provide the set of commands to install LLVM 6.0 (stable branch) on Ubuntu Xenial. Note that the [LLVM installation process](apt.llvm.org) can be adapted to different LLVM branches, and operating systems/distros.
+
+```bash
+wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
+sudo apt-add-repository "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-6.0 main”
+sudo apt-get update
+apt-get install clang-6.0 lldb-6.0 lld-6.0
+```
+
+To ensure that LLVM 6.0 was properly installed, check that the following command gives the path to your `llvm-config` binary.
+
+```bash
+which llvm-config-6.0
+```
+
 ## TVM installation
 
 Clone the TVM repository in the directory of your choosing:
@@ -29,6 +50,7 @@ git clone git@github.com:dmlc/tvm.git --recursive
 TVM is rapidly changing, and to ensure stability, we keep track of working TVM checkpoints.
 As of now, the TVM checkpoint `168f099155106d1188dbc54ac00acc02900a3c6f` is known to work with VTA.
 ```bash
+cd <tvm root>
 git checkout 168f099155106d1188dbc54ac00acc02900a3c6f
 ```
 
@@ -39,13 +61,13 @@ cp make/config.mk .
 ```
 
 In the 'config.mk' file sure that:
-* `LLVM_CONFIG` points to the llvm-config executable (e.g. `LLVM_CONFIG = /usr/bin/llvm-config-4.0`). You'll need to have llvm4.0 installed or later.
+* `LLVM_CONFIG` points to the `llvm-config` executable which path was derived in the TVM installation instructions above (e.g. `LLVM_CONFIG = /usr/bin/llvm-config-6.0`)
 * `USE_RPC` should be set to 1
 
-Launch the compilation, this takes about 5 minutes.
+Launch the compilation, this takes about 5-10 minutes on two threads.
 ```bash
 cd <tvm root>
-make -j4
+make -j2
 ```
 
 Finally update your `~/.bashrc` file to include the TVM python libraries in your `PYTHONPATH` (don't forget to source the newly modified `.bashrc` file!):
@@ -60,16 +82,16 @@ Clone the NNVM repository from `tqchen` in the directory of your choosing:
 git clone git@github.com:tqchen/nnvm.git --recursive
 ```
 
-To run this example, we rely on a special branch of NNVM until these changes get merged back into the main repo: `qt`:
+To run this example, we rely on a special branch of NNVM `qt`:
 ```bash
 cd <nnvm root>
 git checkout qt
 ```
 
-Launch the compilation, this takes less a minute.
+Launch the compilation, this takes about a minute on two threads.
 ```bash
 cd <nnvm root>
-make -j4
+make -j2
 ```
 
 Finally update your `~/.bashrc` file to include the NNVM python libraries in your `PYTHONPATH` (don't forget to source the newly modified `.bashrc` file!):
@@ -85,8 +107,22 @@ Follow the [MxNet Installation Instructions](https://mxnet.incubator.apache.org)
                                                        
 Follow the [Pynq RPC Server Guide](https://github.com/uwsaml/vta/tree/master/apps/pynq_rpc/README.md)
 
+## Python packages
+
+You'll need the following packages to be installed for the example to run properly. You can use `pip` to install those packages:
+* `decorator` (for TVM)
+* `enum34` (for NNVM)
+* `Pillow`
+* `wget`
+
 ## Running the example
 
+Configure your environment with the following:
+```bash
+export VTA_PYNQ_RPC_HOST=192.168.2.99
+export VTA_PYNQ_RPC_PORT=9091
+```
+
 Simply run the following python script:
 ```bash
 python imagenet_predict.py
@@ -94,10 +130,10 @@ python imagenet_predict.py
 
 This will run imagenet classification using the ResNet18 architecture on a VTA design that performs 8-bit integer inference, to perform classification on a cat image `cat.jpg`.
 
-The script reports runtime measured on the Pynq board, and the top-1 result category:
+The script reports runtime measured on the Pynq board (in seconds), and the top-1 result category:
 ```
 ('x', (1, 3, 224, 224))
 Build complete...
 ('TVM prediction top-1:', 281, 'tabby, tabby cat')
 t-cost=0.41906
-```
\ No newline at end of file
+```
diff --git a/vta/src/pynq/pynq_driver.h b/vta/src/pynq/pynq_driver.h
index 74da67d7c035..7aba00441abd 100644
--- a/vta/src/pynq/pynq_driver.h
+++ b/vta/src/pynq/pynq_driver.h
@@ -28,9 +28,9 @@ extern "C" {
 void* cma_alloc(size_t size, int cached);
 void cma_free(void* buf);
 uint32_t cma_get_phy_addr(void* buf);
+#endif
 void xlnkFlushCache(void* buf, int size);
 void xlnkInvalidateCache(void* buf, int size);
-#endif
 
 void *VTAMapRegister(uint32_t addr, size_t length);
 void VTAUnmapRegister(void *vta, size_t length);
diff --git a/vta/tests/python/pynq/test_benchmark_conv2d.py b/vta/tests/python/pynq/test_benchmark_conv2d.py
index 7b54e3dc27ce..e02448a8d993 100644
--- a/vta/tests/python/pynq/test_benchmark_conv2d.py
+++ b/vta/tests/python/pynq/test_benchmark_conv2d.py
@@ -8,8 +8,8 @@
 from tvm.contrib import rpc, util
 import pandas as pd
 
-host = "pynq"
-port = 9091
+host = os.environ.get("VTA_PYNQ_RPC_HOST", "pynq")
+port = int(os.environ.get("VTA_PYNQ_RPC_PORT", "9091"))
 target = "llvm -target=armv7-none-linux-gnueabihf -mattr=+neon"
 
 Workload = namedtuple("Conv2DWorkload",
diff --git a/vta/tests/python/pynq/test_program_rpc.py b/vta/tests/python/pynq/test_program_rpc.py
index 22a1e69dec6b..8c998a58a6c8 100644
--- a/vta/tests/python/pynq/test_program_rpc.py
+++ b/vta/tests/python/pynq/test_program_rpc.py
@@ -3,6 +3,9 @@
 from tvm.contrib import rpc
 from vta import get_bitstream_path, download_bitstream, program_fpga, reconfig_runtime
 
+host = os.environ.get("VTA_PYNQ_RPC_HOST", "pynq")
+port = int(os.environ.get("VTA_PYNQ_RPC_PORT", "9091"))
+
 def program_rpc_bitstream(path=None):
     """Program the FPGA on the RPC server
 
@@ -11,13 +14,6 @@ def program_rpc_bitstream(path=None):
     path : path to bitstream (optional)
     """
     assert tvm.module.enabled("rpc")
-    host = os.environ.get("VTA_PYNQ_RPC_HOST", None)
-    if not host:
-        raise RuntimeError(
-            "Error: VTA_PYNQ_RPC_HOST environment variable not set.")
-    # If a path to a bitstream is passed, make sure that it point to a valid bitstream
-    port = os.environ.get("VTA_PYNQ_RPC_PORT", "9091")
-    port = int(port)
     remote = rpc.connect(host, port)
     program_fpga(remote, path)
 
@@ -25,12 +21,8 @@ def reconfig_rpc_runtime():
     """Reconfig the RPC server runtime
     """
     assert tvm.module.enabled("rpc")
-    host = os.environ.get("VTA_PYNQ_RPC_HOST", None)
-    if host:
-        port = os.environ.get("VTA_PYNQ_RPC_PORT", "9091")
-        port = int(port)
-        remote = rpc.connect(host, port)
-        reconfig_runtime(remote)
+    remote = rpc.connect(host, port)
+    reconfig_runtime(remote)
 
 program_rpc_bitstream()
 reconfig_rpc_runtime()

From 75acb108e4472446cc0e707f5c01d84e0fe92aae Mon Sep 17 00:00:00 2001
From: Yaman Umuroglu <maltanar@gmail.com>
Date: Tue, 29 May 2018 18:20:16 +0200
Subject: [PATCH 847/949] VTA and TVM on PYTHONPATH, also pass to sudo (#42)

---
 vta/apps/pynq_rpc/README.md | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/vta/apps/pynq_rpc/README.md b/vta/apps/pynq_rpc/README.md
index fb9688853690..bb5f9b0b6c0b 100644
--- a/vta/apps/pynq_rpc/README.md
+++ b/vta/apps/pynq_rpc/README.md
@@ -50,11 +50,16 @@ cd ~/vta
 make -j2
 ```
 
+Add VTA and TVM to PYTHONPATH:
+```
+export PYTHONPATH=$PYTHONPATH:/home/xilinx/vta/python:/home/xilinx/vta/nnvm/tvm/python
+```
+
 The last stage will build the `vta/lib/libvta.so` library file. We are now ready to launch the RPC server on the Pynq. In order to enable the FPGA drivers, we need to run the RPC server with `sudo` privileges.
 ```bash
 ssh xilinx@192.168.2.99 # ssh if you haven't done so
 cd ~/vta
-sudo ./apps/pynq_rpc/start_rpc_server.sh # pw is xilinx
+sudo PYTHONPATH=$PYTHONPATH ./apps/pynq_rpc/start_rpc_server.sh # pw is xilinx
 ```
 
 You should see the following being displayed when starting the RPC server:

From 539013c2a33a019cbf473e7b343f0077631f19ea Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 19 Jun 2018 12:59:52 -0700
Subject: [PATCH 848/949] Update TVM Version and CI scripts (#46)

---
 vta/Jenkinsfile                               |  2 +-
 vta/Makefile                                  |  4 ++--
 vta/docs/Makefile                             |  8 +++----
 vta/docs/conf.py                              |  4 ++--
 vta/docs/index.rst                            |  1 -
 vta/python/vta/ir_pass.py                     |  4 ++--
 vta/python/vta/pkg_config.py                  |  6 ++---
 vta/src/device_api.cc                         |  3 ++-
 vta/tests/ci_build/Dockerfile.cpu             | 24 +++++++++++++------
 vta/tests/ci_build/Dockerfile.lint            |  6 +++--
 .../install/ubuntu_install_iverilog.sh        |  8 +++++++
 .../ci_build/install/ubuntu_install_java.sh   |  1 +
 .../ci_build/install/ubuntu_install_llvm.sh   |  2 +-
 .../ci_build/install/ubuntu_install_opengl.sh |  4 ++++
 .../ci_build/install/ubuntu_install_python.sh | 13 +++++++---
 .../install/ubuntu_install_python_package.sh  |  2 +-
 .../ci_build/install/ubuntu_install_sphinx.sh |  2 +-
 vta/tests/scripts/task_build.sh               | 18 +++++---------
 vta/tests/scripts/task_clean.sh               |  4 +---
 vta/tests/scripts/task_python_docs.sh         |  6 ++---
 vta/tests/scripts/task_python_test.sh         |  2 +-
 21 files changed, 74 insertions(+), 50 deletions(-)
 create mode 100644 vta/tests/ci_build/install/ubuntu_install_iverilog.sh
 create mode 100644 vta/tests/ci_build/install/ubuntu_install_java.sh
 create mode 100644 vta/tests/ci_build/install/ubuntu_install_opengl.sh

diff --git a/vta/Jenkinsfile b/vta/Jenkinsfile
index a1f59206f29c..775e25fb94a6 100644
--- a/vta/Jenkinsfile
+++ b/vta/Jenkinsfile
@@ -5,7 +5,7 @@
 
 // nnvm libraries
 vta_lib = "lib/libvta.so, lib/libvta.so.json"
-vta_lib += ", nnvm/tvm/lib/libtvm.so, nnvm/tvm/lib/libtvm_topi.so, nnvm/lib/libnnvm_compiler.so"
+vta_lib += ", tvm/build/libtvm.so, tvm/build/libtvm_topi.so, tvm/build/libnnvm_compiler.so"
 
 
 // command to start a docker container
diff --git a/vta/Makefile b/vta/Makefile
index ead168fafa18..c43c49bddaca 100644
--- a/vta/Makefile
+++ b/vta/Makefile
@@ -53,10 +53,10 @@ lib/libvta.so: $(VTA_LIB_OBJ)
 lint: pylint cpplint
 
 cpplint:
-	python nnvm/dmlc-core/scripts/lint.py vta cpp include src
+	python3 tvm/dmlc-core/scripts/lint.py vta cpp include src
 
 pylint:
-	pylint python/vta --rcfile=$(ROOTDIR)/tests/lint/pylintrc
+	python3 -m pylint python/vta --rcfile=$(ROOTDIR)/tests/lint/pylintrc
 
 doc:
 	doxygen docs/Doxyfile
diff --git a/vta/docs/Makefile b/vta/docs/Makefile
index 1e45fb5e3787..3a8551525181 100644
--- a/vta/docs/Makefile
+++ b/vta/docs/Makefile
@@ -3,14 +3,14 @@
 
 # You can set these variables from the command line.
 SPHINXOPTS    =
-SPHINXBUILD   = sphinx-build
+SPHINXBUILD   = python3 -m sphinx
 PAPER         =
 BUILDDIR      = _build
 
 # User-friendly check for sphinx-build
-ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
-$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
-endif
+#ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
+#$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively# you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
+#endif
 
 # Internal variables.
 PAPEROPT_a4     = -D latex_paper_size=a4
diff --git a/vta/docs/conf.py b/vta/docs/conf.py
index bdc1029223b0..5c4429589129 100644
--- a/vta/docs/conf.py
+++ b/vta/docs/conf.py
@@ -171,7 +171,7 @@ def run_doxygen(folder):
     'numpy': ('http://docs.scipy.org/doc/numpy/', None),
     'scipy': ('http://docs.scipy.org/doc/scipy/reference', None),
     'matplotlib': ('http://matplotlib.org/', None),
-    'tvm': ('http://docs.tvmlang.org/', None),
+    'tvm': ('https://docs.tvm.ai/', None),
 }
 
 from sphinx_gallery.sorting import ExplicitOrder
@@ -201,7 +201,7 @@ def setup(app):
     'doc_module': ('vta', 'numpy'),
 'reference_url': {
     'vta': None,
-    'tvm': 'http://docs.tvmlang.org',
+    'tvm': 'https://docs.tvm.ai',
     'matplotlib': 'http://matplotlib.org',
     'numpy': 'http://docs.scipy.org/doc/numpy-1.9.1'},
     'examples_dirs': examples_dirs,
diff --git a/vta/docs/index.rst b/vta/docs/index.rst
index b30eac0840ab..5aa1dc72e2d0 100644
--- a/vta/docs/index.rst
+++ b/vta/docs/index.rst
@@ -17,4 +17,3 @@ Contents
    api/python/index
    dev/index
    api_links
-   genindex
diff --git a/vta/python/vta/ir_pass.py b/vta/python/vta/ir_pass.py
index 07000e2ccf64..90df67c53278 100644
--- a/vta/python/vta/ir_pass.py
+++ b/vta/python/vta/ir_pass.py
@@ -350,7 +350,7 @@ def _fold_buffer_dim(buf, scope, elem_block):
         shape = [elem_block]
         strides = [1]
 
-        if (base < ndim + 1 and not util.equal_const_int(buf.strides[ndim - base], elem_block)):
+        if base < ndim + 1 and not util.equal_const_int(buf.strides[ndim - base], elem_block):
             shape.append(1)
             strides.append(elem_block)
 
@@ -628,7 +628,7 @@ def _flatten_loop(src_coeff, dst_coeff, extents):
                 next_dst = dst_coeff.pop()
                 next_ext = extents.pop()
 
-                if (_equal(next_src, vsrc * vext) and _equal(next_dst, vdst * vext)):
+                if _equal(next_src, vsrc * vext) and _equal(next_dst, vdst * vext):
                     vext = tvm.ir_pass.Simplify(vext * next_ext)
                 else:
                     rev_src_coeff.append(vsrc)
diff --git a/vta/python/vta/pkg_config.py b/vta/python/vta/pkg_config.py
index f657b2b32fc8..3ca5d052277b 100644
--- a/vta/python/vta/pkg_config.py
+++ b/vta/python/vta/pkg_config.py
@@ -41,9 +41,9 @@ def __init__(self, cfg, proj_root):
         # include path
         self.include_path = [
             "-I%s/include" % proj_root,
-            "-I%s/nnvm/tvm/include" % proj_root,
-            "-I%s/nnvm/tvm/dlpack/include" % proj_root,
-            "-I%s/nnvm/dmlc-core/include" % proj_root
+            "-I%s/tvm/include" % proj_root,
+            "-I%s/tvm/dlpack/include" % proj_root,
+            "-I%s/tvm/dmlc-core/include" % proj_root
         ]
         # List of source files that can be used to build standalone library.
         self.lib_source = []
diff --git a/vta/src/device_api.cc b/vta/src/device_api.cc
index 687ec625a02c..3f312f94f0dd 100644
--- a/vta/src/device_api.cc
+++ b/vta/src/device_api.cc
@@ -8,7 +8,7 @@
 #include <dmlc/thread_local.h>
 #include <vta/runtime.h>
 
-#include "../nnvm/tvm/src/runtime/workspace_pool.h"
+#include "../tvm/src/runtime/workspace_pool.h"
 
 
 namespace tvm {
@@ -42,6 +42,7 @@ class VTADeviceAPI final : public DeviceAPI {
                       size_t size,
                       TVMContext ctx_from,
                       TVMContext ctx_to,
+                      TVMType type_hint,
                       TVMStreamHandle stream) final {
     int kind_mask = 0;
     if (ctx_from.device_type != kDLCPU) {
diff --git a/vta/tests/ci_build/Dockerfile.cpu b/vta/tests/ci_build/Dockerfile.cpu
index ceb242925476..5c7e526f4879 100644
--- a/vta/tests/ci_build/Dockerfile.cpu
+++ b/vta/tests/ci_build/Dockerfile.cpu
@@ -6,21 +6,31 @@ RUN apt-get update --fix-missing
 COPY install/ubuntu_install_core.sh /install/ubuntu_install_core.sh
 RUN bash /install/ubuntu_install_core.sh
 
-COPY install/ubuntu_install_llvm.sh /install/ubuntu_install_llvm.sh
-RUN bash /install/ubuntu_install_llvm.sh
-
 COPY install/ubuntu_install_python.sh /install/ubuntu_install_python.sh
 RUN bash /install/ubuntu_install_python.sh
 
+COPY install/ubuntu_install_iverilog.sh /install/ubuntu_install_iverilog.sh
+RUN bash /install/ubuntu_install_iverilog.sh
+
 COPY install/ubuntu_install_python_package.sh /install/ubuntu_install_python_package.sh
 RUN bash /install/ubuntu_install_python_package.sh
 
+COPY install/ubuntu_install_java.sh /install/ubuntu_install_java.sh
+RUN bash /install/ubuntu_install_java.sh
+
+COPY install/ubuntu_install_llvm.sh /install/ubuntu_install_llvm.sh
+RUN bash /install/ubuntu_install_llvm.sh
+
+COPY install/ubuntu_install_opengl.sh /install/ubuntu_install_opengl.sh
+RUN bash /install/ubuntu_install_opengl.sh
+
+
 COPY install/ubuntu_install_sphinx.sh /install/ubuntu_install_sphinx.sh
 RUN bash /install/ubuntu_install_sphinx.sh
 
+# Enable doxygen for c++ doc build
+RUN apt-get update && apt-get install -y doxygen graphviz
+
 # Fix recommonmark to latest version
 RUN git clone https://github.com/rtfd/recommonmark
-RUN cd recommonmark; python setup.py install
-
-# Enable doxygen for c++ doc build
-RUN apt-get update && apt-get install -y doxygen graphviz
\ No newline at end of file
+RUN cd recommonmark; python3 setup.py install
diff --git a/vta/tests/ci_build/Dockerfile.lint b/vta/tests/ci_build/Dockerfile.lint
index 4ba4ca3be294..be869d8e9faf 100644
--- a/vta/tests/ci_build/Dockerfile.lint
+++ b/vta/tests/ci_build/Dockerfile.lint
@@ -1,6 +1,8 @@
 # For lint test
 FROM ubuntu:16.04
 
-RUN apt-get update && apt-get install -y python-pip sudo
+RUN apt-get update && apt-get install -y sudo wget
+COPY install/ubuntu_install_python.sh /install/ubuntu_install_python.sh
+RUN bash /install/ubuntu_install_python.sh
 RUN apt-get install -y doxygen graphviz
-RUN pip install cpplint pylint
+RUN pip3 install cpplint pylint
diff --git a/vta/tests/ci_build/install/ubuntu_install_iverilog.sh b/vta/tests/ci_build/install/ubuntu_install_iverilog.sh
new file mode 100644
index 000000000000..bf7a0001dc70
--- /dev/null
+++ b/vta/tests/ci_build/install/ubuntu_install_iverilog.sh
@@ -0,0 +1,8 @@
+apt-get install -y --no-install-recommends --force-yes make bison flex
+wget ftp://icarus.com/pub/eda/verilog/v10/verilog-10.1.tar.gz
+tar xf verilog-10.1.tar.gz
+cd verilog-10.1
+./configure --prefix=/usr
+make install -j8
+cd ..
+rm -rf verilog-10.1 verilog-10.1.tar.gz
diff --git a/vta/tests/ci_build/install/ubuntu_install_java.sh b/vta/tests/ci_build/install/ubuntu_install_java.sh
new file mode 100644
index 000000000000..ba07b2985efb
--- /dev/null
+++ b/vta/tests/ci_build/install/ubuntu_install_java.sh
@@ -0,0 +1 @@
+apt-get update && apt-get install -y openjdk-8-jdk maven
diff --git a/vta/tests/ci_build/install/ubuntu_install_llvm.sh b/vta/tests/ci_build/install/ubuntu_install_llvm.sh
index ba0afcd18cc9..16d0fe150b7e 100644
--- a/vta/tests/ci_build/install/ubuntu_install_llvm.sh
+++ b/vta/tests/ci_build/install/ubuntu_install_llvm.sh
@@ -19,4 +19,4 @@ echo deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial main\
      >> /etc/apt/sources.list.d/llvm.list
 
 wget -O - http://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
-apt-get update && apt-get install -y --force-yes llvm-4.0 llvm-5.0 llvm-6.0
+apt-get update && apt-get install -y --force-yes llvm-4.0 llvm-5.0 llvm-6.0 clang-6.0
diff --git a/vta/tests/ci_build/install/ubuntu_install_opengl.sh b/vta/tests/ci_build/install/ubuntu_install_opengl.sh
new file mode 100644
index 000000000000..f8be6e351581
--- /dev/null
+++ b/vta/tests/ci_build/install/ubuntu_install_opengl.sh
@@ -0,0 +1,4 @@
+apt-get update --fix-missing
+
+apt-get install -y --no-install-recommends --force-yes \
+        libgl1-mesa-dev libglfw3-dev
\ No newline at end of file
diff --git a/vta/tests/ci_build/install/ubuntu_install_python.sh b/vta/tests/ci_build/install/ubuntu_install_python.sh
index c6f6f75c564e..a34019e1003e 100644
--- a/vta/tests/ci_build/install/ubuntu_install_python.sh
+++ b/vta/tests/ci_build/install/ubuntu_install_python.sh
@@ -1,5 +1,12 @@
 # install python and pip, don't modify this, modify install_python_package.sh
-apt-get update && apt-get install -y python-pip python-dev python3-dev
+apt-get update && apt-get install -y python-dev
 
-# the version of the pip shipped with ubuntu may be too lower, install a recent version here
-cd /tmp && wget https://bootstrap.pypa.io/get-pip.py && python3 get-pip.py && python2 get-pip.py
+# python 3.6
+apt-get update && yes | apt-get install software-properties-common
+add-apt-repository ppa:jonathonf/python-3.6 &&\
+    apt-get update && apt-get install -y python-pip python-dev python3.6 python3.6-dev
+
+rm -f /usr/bin/python3 && ln -s /usr/bin/python3.6 /usr/bin/python3
+
+# Install pip
+cd /tmp && wget https://bootstrap.pypa.io/get-pip.py && python2 get-pip.py && python3.6 get-pip.py
diff --git a/vta/tests/ci_build/install/ubuntu_install_python_package.sh b/vta/tests/ci_build/install/ubuntu_install_python_package.sh
index fbed2e1904cd..4f1518cb1abd 100644
--- a/vta/tests/ci_build/install/ubuntu_install_python_package.sh
+++ b/vta/tests/ci_build/install/ubuntu_install_python_package.sh
@@ -1,3 +1,3 @@
 # install libraries for python package on ubuntu
 pip2 install nose pylint numpy nose-timer cython decorator scipy tornado
-pip3 install nose pylint numpy nose-timer cython decorator scipy tornado
+pip3 install nose pylint numpy nose-timer cython decorator scipy tornado typed_ast
diff --git a/vta/tests/ci_build/install/ubuntu_install_sphinx.sh b/vta/tests/ci_build/install/ubuntu_install_sphinx.sh
index 30d4596aabf2..eabae1aac4fd 100644
--- a/vta/tests/ci_build/install/ubuntu_install_sphinx.sh
+++ b/vta/tests/ci_build/install/ubuntu_install_sphinx.sh
@@ -1 +1 @@
-pip install sphinx sphinx-gallery sphinx_rtd_theme matplotlib Image commonmark>=0.7.3 docutils>=0.11
+pip3 install sphinx sphinx-gallery sphinx_rtd_theme matplotlib Image commonmark>=0.7.3 docutils>=0.11
diff --git a/vta/tests/scripts/task_build.sh b/vta/tests/scripts/task_build.sh
index 8cf01d46a600..bffd418fd393 100755
--- a/vta/tests/scripts/task_build.sh
+++ b/vta/tests/scripts/task_build.sh
@@ -1,21 +1,15 @@
 #!/bin/bash
 echo "Build TVM..."
-cd nnvm/tvm
-cp make/config.mk .
-echo USE_CUDA=0 >> config.mk
-echo LLVM_CONFIG=llvm-config-4.0 >> config.mk
-echo USE_RPC=1 >> config.mk
-echo USE_BLAS=openblas >> config.mk
-echo USE_GRAPH_RUNTIME=1 >> config.mk
+cd tvm
+cp cmake/config.cmake .
+echo set\(USE_LLVM llvm-config-5.0\) >> config.cmake
+echo set\(USE_RPC ON\) >> config.cmake
+echo set\(USE_BLAS openblas\) >> config.cmake
+echo set\(USE_GRAPH_RUNTIME ON\) >> config.cmake
 make "$@"
 make cython
 make cython3
 cd ..
 
-echo "Build NNVM..."
-make "$@"
-
-cd ..
-
 echo "Build VTA..."
 make "$@"
diff --git a/vta/tests/scripts/task_clean.sh b/vta/tests/scripts/task_clean.sh
index 60c70fadf459..815225341ae1 100755
--- a/vta/tests/scripts/task_clean.sh
+++ b/vta/tests/scripts/task_clean.sh
@@ -1,10 +1,8 @@
 #!/bin/bash
 echo "Cleanup data..."
-cd nnvm
-make clean
 
 cd tvm
 make clean
 
-cd ../..
+cd ..
 make clean
diff --git a/vta/tests/scripts/task_python_docs.sh b/vta/tests/scripts/task_python_docs.sh
index 7fac9f692c8a..fd9d6211b223 100755
--- a/vta/tests/scripts/task_python_docs.sh
+++ b/vta/tests/scripts/task_python_docs.sh
@@ -1,8 +1,8 @@
 #!/bin/bash
-cd nnvm/tvm
+cd tvm
 make cython
 make cython3
-cd ../../
+cd ../
 
 mkdir -p docs/_build/html
 # C++ doc
@@ -11,7 +11,7 @@ make doc
 rm -rf python/vta/*.pyc python/vta/*/*.pyc
 
 cd docs
-PYTHONPATH=../python:../nnvm/tvm/python:../nnvm/tvm/topi/python:../nnvm/python make html || exit -1
+PYTHONPATH=../python:../tvm/python:../tvm/topi/python:../tvm/nnvm/python make html || exit -1
 cd _build/html
 tar czf docs.tgz *
 mv docs.tgz ../../../
diff --git a/vta/tests/scripts/task_python_test.sh b/vta/tests/scripts/task_python_test.sh
index d552aae0e87f..b146d5c9fca9 100755
--- a/vta/tests/scripts/task_python_test.sh
+++ b/vta/tests/scripts/task_python_test.sh
@@ -1,6 +1,6 @@
 #!/bin/bash
 
-export PYTHONPATH=python:nnvm/python:nnvm/tvm/python:nnvm/tvm/topi/python
+export PYTHONPATH=python:tvm/nnvm/python:tvm/python:tvm/topi/python
 
 echo "Running unittest..."
 python -m nose -v tests/python/unittest || exit -1

From 63e6474ae288726885cbedcb4107892359342271 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 19 Jun 2018 20:38:55 -0700
Subject: [PATCH 849/949] Update Jenkinsfile

---
 vta/Jenkinsfile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/vta/Jenkinsfile b/vta/Jenkinsfile
index 775e25fb94a6..526cdcaffbc4 100644
--- a/vta/Jenkinsfile
+++ b/vta/Jenkinsfile
@@ -4,7 +4,7 @@
 // See documents at https://jenkins.io/doc/book/pipeline/jenkinsfile/
 
 // nnvm libraries
-vta_lib = "lib/libvta.so, lib/libvta.so.json"
+vta_lib = "lib/libvta.so, lib/libvta.so.json, config.json"
 vta_lib += ", tvm/build/libtvm.so, tvm/build/libtvm_topi.so, tvm/build/libnnvm_compiler.so"
 
 

From 0afa8bf52c15d04cfd180abf92926a3a90c163bd Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@cs.washington.edu>
Date: Wed, 20 Jun 2018 14:16:21 -0700
Subject: [PATCH 850/949] [DOC] VTA installation & basic tutorials (#47)

---
 vta/Makefile                          |   1 +
 vta/README.md                         |  17 +-
 vta/apps/pynq_rpc/README.md           |  73 -----
 vta/apps/pynq_rpc/start_rpc_server.sh |   2 +-
 vta/docs/how_to/install.md            | 369 ++++++++++++++++++++-
 vta/examples/resnet18/pynq/README.md  | 123 +------
 vta/python/vta/environment.py         |   2 +-
 vta/tutorials/get_started.py          | 380 ++++++++++++++++++++-
 vta/tutorials/matrix_multiply.py      | 453 ++++++++++++++++++++++++++
 vta/tutorials/matrix_multiply_opt.py  | 362 ++++++++++++++++++++
 10 files changed, 1554 insertions(+), 228 deletions(-)
 delete mode 100644 vta/apps/pynq_rpc/README.md
 create mode 100644 vta/tutorials/matrix_multiply.py
 create mode 100644 vta/tutorials/matrix_multiply_opt.py

diff --git a/vta/Makefile b/vta/Makefile
index c43c49bddaca..60e93c0bd347 100644
--- a/vta/Makefile
+++ b/vta/Makefile
@@ -63,6 +63,7 @@ doc:
 
 clean:
 	$(RM) -rf build lib bin *~ */*~ */*/*~ */*/*/*~ */*.o */*/*.o */*/*/*.o
+	$(RM) -rf cat.jpg quantize_graph.json quantize_params.pkl synset.txt
 
 
 -include build/*.d
diff --git a/vta/README.md b/vta/README.md
index cd3a490ccb6f..bb347f02b798 100644
--- a/vta/README.md
+++ b/vta/README.md
@@ -7,20 +7,27 @@ VTA(versatile tensor accelerator) is an open-source deep learning accelerator st
 It is not just an open-source hardware, but is an end to end solution that includes
 the entire software stack on top of VTA open-source hardware.
 
-
 The key features include:
 
 - Generic, modular open-source hardware
   - Streamlined workflow to deploy to FPGAs.
-  - Simulator support
-- Driver and JIT runtime for both simulated backend and FPGA.
+  - Simulator support to protoype compilation passes on regular workstations.
+- Driver and JIT runtime for both simulated and FPGA hardware backend.
 - End to end TVM stack integration
   - Direct optimization and deploy models from deep learning frameworks via TVM stack.
-  - Customized and extendible TVM compiler backend
-  - Flexible RPC support to ease the deployment, you can program it with python :)
+  - Customized and extendible TVM compiler backend.
+  - Flexible RPC support to ease the deployment, and program FPGAs with Python
 
 VTA is part of our effort on [TVM Stack](http://www.tvmlang.org/).
 
+VTA Installation
+----------------
+To get started with VTA, please follow the [Installation Guide](docs/how_to/install.md)
+
+ResNet-18 Inference Example
+---------------------------
+To offload ResNet-18 inference, follow the [ResNet-18 Guide](examples/resnet18/pynq/README.md)
+
 License
 -------
 © Contributors, 2018. Licensed under an [Apache-2.0](https://github.com/tmoreau89/vta/blob/master/LICENSE) license.
diff --git a/vta/apps/pynq_rpc/README.md b/vta/apps/pynq_rpc/README.md
deleted file mode 100644
index bb5f9b0b6c0b..000000000000
--- a/vta/apps/pynq_rpc/README.md
+++ /dev/null
@@ -1,73 +0,0 @@
-# PYNQ RPC Server for VTA
-
-This guide describes how to setup a Pynq-based RPC server to accelerate deep learning workloads with VTA.
-
-## Pynq Setup
-
-Follow the getting started tutorial for the [Pynq board](http://pynq.readthedocs.io/en/latest/getting_started.html).
-* This assumes that you've downloaded the latest Pynq image, PYNQ-Z1 v2.1 (released 21 Feb 2018).
-* For this RPC setup, follow the ["Connect to a Computer"](http://pynq.readthedocs.io/en/latest/getting_started.html#connect-to-a-computer) Pynq setup instructions.
-* To be able to talk to the board, you'll need to make sure that you've followed the steps to [assign a static IP address](http://pynq.readthedocs.io/en/latest/appendix.html#assign-your-computer-a-static-ip)
-
-Make sure that you can talk to your Pynq board successfully:
-```bash
-ping 192.168.2.99
-```
-
-When ssh-ing onto the board, the password for the `xilinx` username is `xilinx`.
-
-For convenience let's go ahead and mount the Pynq board's file system to easily access it (this will require sshfs to be installed):
-```bash
-mkdir <mountpoint>
-sshfs xilinx@192.168.2.99:/home/xilinx <mountpoint>
-```
-
-## Pynq TVM & VTA installation
-
-On your **host PC**, go to the `<mountpoint>` directory of your Pynq board file system.
-```bash
-cd <mountpoint>
-```
-
-From there, clone the VTA repository:
-```bash
-git clone git@github.com:uwsaml/vta.git --recursive
-```
-
-Now, ssh into your **Pynq board** to build the TVM runtime with the following commands. This build should take about 5 minutes.
-```bash
-ssh xilinx@192.168.2.99 # ssh if you haven't done so
-cd ~/vta/nnvm/tvm
-cp make/config.mk .
-echo USE_RPC=1 >> config.mk
-make runtime -j2
-```
-
-We're now ready to build the Pynq RPC server on the Pynq board, which should take less than 30 seconds.
-```bash
-ssh xilinx@192.168.2.99 # ssh if you haven't done so
-cd ~/vta
-make -j2
-```
-
-Add VTA and TVM to PYTHONPATH:
-```
-export PYTHONPATH=$PYTHONPATH:/home/xilinx/vta/python:/home/xilinx/vta/nnvm/tvm/python
-```
-
-The last stage will build the `vta/lib/libvta.so` library file. We are now ready to launch the RPC server on the Pynq. In order to enable the FPGA drivers, we need to run the RPC server with `sudo` privileges.
-```bash
-ssh xilinx@192.168.2.99 # ssh if you haven't done so
-cd ~/vta
-sudo PYTHONPATH=$PYTHONPATH ./apps/pynq_rpc/start_rpc_server.sh # pw is xilinx
-```
-
-You should see the following being displayed when starting the RPC server:
-```
-INFO:root:Load additional library /home/xilinx/vta/lib/libvta.so
-INFO:root:RPCServer: bind to 0.0.0.0:9091
-```
-
-Note that it should be listening on port `9091`.
-
-To kill the RPC server, just enter the `Ctrl + c` command.
diff --git a/vta/apps/pynq_rpc/start_rpc_server.sh b/vta/apps/pynq_rpc/start_rpc_server.sh
index fac12e82bb55..e36d80ccc260 100755
--- a/vta/apps/pynq_rpc/start_rpc_server.sh
+++ b/vta/apps/pynq_rpc/start_rpc_server.sh
@@ -1,4 +1,4 @@
 #!/bin/bash
-export PYTHONPATH=${PYTHONPATH}:/home/xilinx/vta/nnvm/tvm/python:/home/xilinx/vta/python
+export PYTHONPATH=${PYTHONPATH}:/home/xilinx/vta/tvm/python:/home/xilinx/vta/python
 export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/opt/python3.6/lib/python3.6/site-packages/pynq/drivers/
 python -m vta.exec.rpc_server
diff --git a/vta/docs/how_to/install.md b/vta/docs/how_to/install.md
index c6bab2f1a660..e2ccf63e9080 100644
--- a/vta/docs/how_to/install.md
+++ b/vta/docs/how_to/install.md
@@ -1,19 +1,368 @@
-Installation Guide
-==================
-This page gives instructions on how to build and use VTA
+Installation Guides
+===================
+
+We present three installation guides, each extending on the previous one:
+1. VTA simulation-only installation
+2. VTA hardware testing setup with the [Pynq](http://www.pynq.io/) FPGA development board
+3. VTA hardware compilation tool chain installation
+
+## VTA Simulation-Only Installation
+
+This first guide details the installation of the VTA package to run hardware simulation tests locally on your development machine (in case you don't own the Pynq FPGA development board).
+This guide includes:
+1. Software dependences installation
+2. Simulation library compilation
+3. Python package installation
+4. Test examples to ensure that the VTA package was correctly installed
+
+To get started, clone vta repo from [github](https://github.com/uwsaml/vta). It is important to clone the submodules along with ```--recursive``` option.
+```bash
+git clone --recursive https://github.com/uwsaml/vta
+```
+
+### VTA Dependences
+
+The VTA package depends on several other packages that need to be manually installed beforehand.
+
+We list the dependences below:
+* LLVM 4.0 or newer
+* TVM
+* MxNet (to run the end-to-end examples)
+* Additional python packages
+
+#### LLVM Installation
+
+We provide the set of commands to install LLVM 6.0 (stable branch) on Ubuntu Xenial. Note that the [LLVM installation process](apt.llvm.org) can be adapted to different LLVM branches, and operating systems.
+
+```bash
+wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
+sudo apt-add-repository "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-6.0 main"
+sudo apt-get update
+apt-get install clang-6.0 lldb-6.0 lld-6.0
+```
+
+To ensure that LLVM 6.0 was properly installed, check that the following command gives the path to your `llvm-config` binary (you may have to append the version number to the executable name):
+
+```bash
+which llvm-config-6.0
+```
+
+#### TVM Installation
+
+TVM is included as a top-level submodule to VTA, and can be found under `<vta root>/tvm`.
+
+Follow the [installation instructions](https://docs.tvm.ai/install/index.html).
+
+In the 'config.mk' file, make sure that:
+* `LLVM_CONFIG` points to the `llvm-config` executable which path was derived in the LLVM installation instructions above (e.g. `LLVM_CONFIG = /usr/bin/llvm-config-6.0`)
+* `USE_RPC` should be set to 1
+
+For the *Python Package Installation*, we recommend updating your `~/.bashrc` file to extend your `PYTHONPATH` with the TVM Python libraries.
+```bash
+export PYTHONPATH=<tvm root>/python:<tvm root>/topi/python:${PYTHONPATH}
+```
+
+#### NNVM Installation
+
+Clone the NNVM repository from `tqchen` in the directory of your choosing:
+```bash
+git clone git@github.com:tqchen/nnvm.git --recursive
+```
+
+To run this example, we rely on a special branch of NNVM `qt`:
+```bash
+cd <nnvm root>
+git checkout qt
+```
+
+Launch the compilation, this takes about a minute on two threads.
+```bash
+cd <nnvm root>
+make -j2
+```
+
+Finally update your `~/.bashrc` file to include the NNVM python libraries in your `PYTHONPATH`:
+```bash
+export PYTHONPATH=<nnvm root>/python:${PYTHONPATH}
+```
+
+#### MxNet Installation
+
+Follow the [MxNet Installation Instructions](https://mxnet.incubator.apache.org)
+
+#### Python Dependences
+
+You'll need the following packages to be installed for the example to run properly. You can use `pip` to install those packages:
+* `decorator`
+* `enum34`
+* `Pillow`
+* `wget`
+
+### VTA Shared Library Compilation
+
+Before building the VTA shared library, the VTA configuration can be modified by changing `config.json` file.
+This file provides an architectural specification of the VTA accelerator that can be understood by both the TVM compiler stack and the VTA hardware stack.
+It also specifies the TVM compiler target. When `TARGET` is set to `sim`, it tells the TVM compiler to execute the TVM workloads on the VTA simulator.
+
+To build the simulator library, copy the simulation configuration file `make/sim_sample.json` to the project root.
+Next, you can build the VTA simulation dynamic library with `make`.
+
+```bash
+cd <vta root>
+cp make/sim_sample.json config.json
+make -j4
+```
+
+### VTA Python Package Installation
+
+The Python package can installed by extending your `PYTHONPATH` environment variable to point to the VTA python library path.
+You can run the following line in a terminal, or add it to your `~/.bashrc` file if you plan on using VTA regularly.
+
+```bash
+export PYTHONPATH=<vta root>/python:${PYTHONPATH}
+```
+
+### Testing your VTA Simulation Setup
+
+Finally to ensure that you've properly installed the VTA package, we can run simple unit tests and the ResNet-18 inference example.
+
+Let's first run the 2D convolution test bench that will only run the ResNet-18 convolution layers.
+
+```bash
+python tests/python/integration/test_benchmark_topi_conv2d.py
+```
+
+> Note: You'll notice that for every convolution layer, the throughput gets reported in GOPS. These numbers are actually the computational throughput that the simulator achieves, by evaluating the convolution in software.
+
+Next we can also run the ResNet-18 end to end example in the VTA simulator.
+This test will download the following files in your root:
+* `cat.jpg` the test image to classify
+* `synset.txt` the ImageNet categories
+* `quantize_graph.json` the 8-bit ResNet-18 inference NNVM graph
+* `quantize_params.plk` the 8-bit ResNet-18 model parameters
+
+```bash
+python examples/resnet18/pynq/imagenet_predict.py
+```
+> Note: This will run ResNet inference by offloading the compute-heavy convolution layers to the VTA simulator, and report the top-1 category, and the inference time cost in seconds.
+
+## VTA Pynq-Based Testing Setup
+
+This second guide extends the *VTA Simulation-Only Installation* guide above to allow FPGA-based hardware tests of the full TVM and VTA software-hardware stack.
+In terms of hardware components you'll need:
+* The [Pynq](http://www.pynq.io/) FPGA development board which can be acquired for $200, or $150 for academics from [Digilent](https://store.digilentinc.com/pynq-z1-python-productivity-for-zynq/).
+* An Ethernet-to-USB adapter to connect the Pynq board to your development computer.
+* An 8+GB micro SD card the (can be ordered with the Pynq dev kit).
+* An AC to DC 12V 3A power adapter (can be ordered with the Pynq dev kit).
+
+This guide includes:
+1. Pynq board setup instructions
+2. Pynq-side RPC server build and deployment
+3. Revisiting the test examples from the *VTA Simulation-Only Installation* guide, this time executing on the Pynq board
+
+### Pynq Board Setup
+
+Setup your Pynq board based on the *Getting Started* tutorial for the [Pynq board](http://pynq.readthedocs.io/en/latest/getting_started.html). You should follow the instructions up to and including the *Turning On the PYNQ-Z1* steps (no need to pursue *Getting Started* tutorial beyond this point).
+* Make sure that you've downloaded the latest Pynq image, PYNQ-Z1 v2.1 (released 21 Feb 2018), and have imaged your SD card with it.
+* For this particular setup, follow the ["Connect to a Computer"](http://pynq.readthedocs.io/en/latest/getting_started.html#connect-to-a-computer) Ethernet setup instructions.
+  * To be able to talk to the board, make sure to [assign your computer a static IP address](http://pynq.readthedocs.io/en/latest/appendix.html#assign-your-computer-a-static-ip)
+
+Once the board is powered on and connected to your development host machine, try connecting to it to make sure you've properly set up your Pynq board:
+```bash
+# To connect to the Pynq board use the [username, password] combo: [xilinx, xilinx]
+ssh xilinx@192.168.2.99
+```
+
+### Pynq-Side RPC Server Build & Deployment
+
+Because the direct board-to-computer connection prevents the board from directly accessing the internet, we'll need to mount the Pynq's file system to your development machine's file system with [sshfs](https://www.digitalocean.com/community/tutorials/how-to-use-sshfs-to-mount-remote-file-systems-over-ssh). Next we directly clone the VTA repository into the mountpoint from your development machine.
 
-To get started, clone tvm repo from github. It is important to clone the submodules along, with ```--recursive``` option.
 ```bash
+mkdir <mountpoint>
+sshfs xilinx@192.168.2.99:/home/xilinx <mountpoint>
+cd <mountpoint>
 git clone --recursive https://github.com/uwsaml/vta
+# When finished, you can leave the moutpoint and unmount the directory
+cd ~
+sudo umount <mountpoint>
+```
+
+Now that we've cloned the VTA repository in the Pynq's file system, we can ssh into it and launch the build of the TVM-based RPC server.
+The build process should take roughly 5 minutes.
+
+```bash
+ssh xilinx@192.168.2.99
+# Build TVM runtime library (takes 5 mins)
+cd /home/xilinx/vta/tvm
+mkdir build
+cp cmake/config.cmake build/.
+cd build
+cmake ..
+make runtime -j2
+# Build VTA RPC server (takes 1 min)
+cd /home/xilinx/vta
+sudo ./apps/pynq_rpc/start_rpc_server.sh # pw is 'xilinx'
+```
+
+You should see the following being displayed when starting the RPC server. In order to run the next examples, you'll need to leave the RPC server running in an `ssh` session.
+```
+INFO:root:Load additional library /home/xilinx/vta/lib/libvta.so
+INFO:root:RPCServer: bind to 0.0.0.0:9091
+```
+
+Tips regarding the Pynq RPC Server:
+* The RPC server should be listening on port `9091`. If not, an earlier process might have terminated unexpectedly and it's recommended in this case to just reboot the Pynq, and re-run the RPC server.
+* To kill the RPC server, just send the `Ctrl + c` command. You can re-run it with `sudo ./apps/pynq_rpc/start_rpc_server.sh`.
+* If unresponsive, the board can be rebooted by power-cycling it with the physical power switch.
+
+### Testing your VTA Pynq-based Hardware Setup
+
+Before running the examples you'll need to configure your environment as follows:
+```bash
+export VTA_PYNQ_RPC_HOST=192.168.2.99
+export VTA_PYNQ_RPC_PORT=9091
+```
+
+In addition, you'll need to edit the `config.json` file to indicate that we are targeting the Pynq platform, by setting the `TARGET` field to the `"pynq"` value. Alternatively, you can copy the default `make/config.json` into the VTA root.
+> Note: in contrast to our simulation setup, there are no libraries to compile on the host side since the host offloads all of the computation to the Pynq board.
+
+```bash
+cd <vta root>
+cp make/config.json .
+```
+
+This time again, we will run the 2D convolution testbench. But beforehand, we'll need to program the Pynq's own FPGA with a VTA bitstream, and build the VTA runtime on the Pynq via RPC. The following `test_program_rpc.py` script will perform two operations:
+* FPGA programming, by downloading a pre-compiled bitstream from a [VTA bitstream repository](https://github.com/uwsaml/vta-distro) that matches the default `config.json` configuration set by the host, and sending it over to the Pynq via RPC to program the Pynq's FPGA.
+* Runtime building on the Pynq, which needs to be run everytime the `config.json` configuration is modified. This ensures that the VTA software runtime that generates the accelerator's executable via just-in-time (JIT) compilation matches the specifications of the VTA design that is programmed on the FPGA. The build process takes about 30 seconds to complete.
+
+```bash
+python tests/python/pynq/test_program_rpc.py 
+```
+
+> Tip: You can track progress of the FPGA programming and the runtime rebuilding steps by looking at the RPC server's logging messages in your Pynq `ssh` session.
+
+We are now ready to run the 2D convolution testbench for the ResNet-15 workload in hardware.
+
+```bash
+python tests/python/pynq/test_benchmark_conv2d.py 
 ```
-For windows users who use github tools, you can open the git shell, and type the following command.
+
+The performance metrics measured on the Pynq board will be reported for each convolutional layer.
+
+Finally, we run the ResNet-18 end-to-end example on the Pynq.
+
 ```bash
-git submodule init
-git submodule update --init --recursive
+python examples/resnet18/pynq/imagenet_predict.py
 ```
+This will run ResNet inference by offloading the compute-heavy convolution layers to the Pynq's FPGA-based VTA accelerator. The time cost is also measured in seconds here.
+
+## VTA Hardware Toolchain Installation
+
+This third and last guide allows users to generate custom VTA bitstreams using free-to-use Xilinx compilation toolchains.
+
+This guide includes:
+1. Xilinx toolchain installation (for Linux)
+2. Custom VTA bitstream compilation
+3. Running the end to end ResNet-18 test with the new bitstream
+
+### Xilinx Toolchain Installation
 
-## Build Hardware
+We recommend using `Vivado 2017.1` since our scripts have been tested to work on this version of the Xilinx toolchains. Our guide is written for Linux installation.
+
+You’ll need to install Xilinx’ FPGA compilation toolchain, [Vivado HL WebPACK 2017.1](https://www.xilinx.com/products/design-tools/vivado.html), which a license-free version of the Vivado HLx toolchain.
+
+#### Obtaining and Launching the Vivado GUI Installer
+
+1. Go to the [download webpage](https://www.xilinx.com/support/download.html), and download the Linux Self Extracting Web Installer for Vivado HL 2017.1 WebPACK and Editions.
+2. You’ll have to sign in with a Xilinx account. This requires a Xilinx account creation that will take 2 minutes.
+3. Complete the Name and Address Verification by clicking “Next”, and you will get the opportunity to download a binary file, called `Xilinx_Vivado_SDK_2017.1_0415_1_Lin64.bin`.
+4. Now that the file is downloaded, go to your `Downloads` directory, and change the file permissions so it can be executed:
+```bash
+chmod u+x Xilinx_Vivado_SDK_2017.1_0415_1_Lin64.bin
+```
+5. Now you can execute the binary: 
+```bash
+./Xilinx_Vivado_SDK_2017.1_0415_1_Lin64.bin
+```
+
+#### Xilinx Vivado GUI Installer Steps
+
+At this point you've launched the Vivado 2017.1 Installer GUI program.
+
+1. Click “Next” on the *Welcome* screen.
+2. Enter your Xilinx User Credentials under “User Authentication” and select the “Download and Install Now” before clicking “Next” on the *Select Install Type* screen.
+3. Accept all terms before clicking on “Next” on the *Accept License Agreements* screen.
+4. Select the “Vivado HL WebPACK” before clicking on “Next” on the *Select Edition to Install* screen.
+5. Under the *Vivado HL WebPACK* screen, before hitting “Next", check the following options (the rest should be unchecked):
+   * Design Tools -> Vivado Design Suite -> Vivado
+   * Design Tools -> Vivado Design Suite -> Vivado High Level Synthesis
+   * Devices -> Production Services -> SoCs -> Zynq-7000 Series
+6. Your total download size should be about 3GB and the amount of Disk Space Required 13GB.
+7. Set the installation directory before clicking “Next” on the *Select Destination Directory* screen. It might highlight some paths as red - that’s because the installer doesn’t have the permission to write to that directory. In that case select a path that doesn’t require special write permissions (e.g. in your home directory).
+8. Hit “Install” under the *Installation Summary* screen.
+9. An *Installation Progress Window* will pop-up to track progress of the download and the installation.
+10. This process will take about 20-30 minutes depending on your connection speed.
+11. A pop-up window will inform you that the installation completed successfully. Click "OK".
+12. Finally the *Vivado License Manager* will launch. Select "Get Free ISE WebPACK, ISE/Vivado IP or PetaLinux License" and click "Connect Now" to complete the license registration process.
+
+#### Environment Setup
+
+The last step is to update your `~/.bashrc` with the following lines. This will include all of the Xilinx binary paths so you can launch compilation scripts from the command line.
+```bash
+# Xilinx Vivado 2017.1 environmentexport XILINX_VIVADO=/home/moreau/Xilinx/SDx/2017.1/Vivado
+export XILINX_VIVADO=/home/moreau/Xilinx/SDx/2017.1/Vivado
+export XILINX_HLS=/home/moreau/Xilinx/SDx/2017.1/Vivado_HLS
+export XILINX_SDK=/home/moreau/Xilinx/SDx/2017.1/SDK
+export PATH=${XILINX_VIVADO}/bin:${PATH}
+export PATH=${XILINX_HLS}/bin:${PATH}
+export PATH=${XILINX_SDK}/bin:${PATH}
+```
+
+### Custom VTA Bitstream Compilation
+
+High-level parameters are listed under `<vta root>/make/config.json` and can be customized by the user. For this custom VTA Bitstream Compilation exercise, we'll change the frequency of our design, so it can be clocked a little faster.
+* Set the `HW_FREQ` field to `142`. The Pynq board supports 100, 142, 167 and 200MHz clocks. Note that the higher the frequency, the harder it will be to close timing. Increasing the frequency can lead to timing violation and thus faulty hardware.
+* Set the `HW_CLK_TARGET` to `6`. This parameters refers to the target clock period in ns passed to HLS - a lower clock period leads to more aggressive pipelining to achieve timing closure at higher frequencies. Technically a 142MHz clock would require a 7ns target, but we intentionally lower the clock target to 6ns to more aggressively pipeline our design.
+
+Bitstream generation is driven by a top-level `Makefile` under `<vta root>/hardware/xilinx/`.
+
+If you just want to simulate the VTA design in software emulation to make sure that it is functional, enter:
+```bash
+cd <vta root>/hardware/xilinx
+make ip MODE=sim
+```
+
+If you just want to generate the HLS-based VTA IP cores without launching the entire design place and route, enter:
+```bash
+make ip
+```
+You'll be able to view the HLS synthesis reports under `<vta root>/build/hardware/xilinx/hls/<configuration>/<block>/solution0/syn/report/<block>_csynth.rpt`
+> Note: The `<configuration>` name is a string that summarizes the VTA configuration parameters specified in the `config.json`. The `<block>` name refers to the specific module in the VTA pipeline. 
+
+Finally to run the full hardware compilation and generate the bitstream, run:
+
+```bash
+make
+```
+
+This process is lenghty, and can take around up to an hour to complete depending on your machine's specs. We recommend setting the `VTA_HW_COMP_THREADS` variable in the Makefile to take full advantage of all the cores on your development machine.
+
+Once the compilation completes, the generated bitstream can be found under `<vta root>/build/hardware/xilinx/vivado/<configuration>/export/vta.bit`.
+
+### End-to-end ResNet-18 Example with the Custom Bitstream
+
+Let's run the ResNet-18 example with our newly generated bitstream.
+
+In `<vta root>/examples/resnet18/pynq/imagenet_predict.py`, change the line:
+```python
+vta.program_fpga(remote, bitstream=None)
+```
+to
+
+```python
+vta.program_fpga(remote, bitstream="<vta root>/build/hardware/xilinx/vivado/<configuration>/export/vta.bit")
+```
 
-## Build Runtime
+Instead of downloading the bitstream from the bitstream repository, the programmer will instead use the custom bitstream you just generated, which is a VTA design clocked at a higher frequency.
 
-## Use VTA Python Compiler Package
+Do you observe a noticable performance increase on the ImageNet inference workload?
diff --git a/vta/examples/resnet18/pynq/README.md b/vta/examples/resnet18/pynq/README.md
index 1906ca082378..1213d94ec6b1 100644
--- a/vta/examples/resnet18/pynq/README.md
+++ b/vta/examples/resnet18/pynq/README.md
@@ -1,127 +1,6 @@
 # Resnet-18 Example on Pynq-based VTA Design
 
-In order to run this example you'll need to have:
-* VTA installed
-* LLVM 4.0 or newer installed
-* TVM installed
-* NNVM installed
-* MxNet installed
-* A Pynq-based RPC server running
-* Python packages installed
-
-Required setup time from scratch: ~15 mins.
-
-## VTA installation
-
-Clone the VTA repository in the directory of your choosing:
-```bash
-git clone git@github.com:uwsaml/vta.git --recursive
-```
-
-Update your `~/.bashrc` file to include the VTA python libraries in your `PYTHONPATH` (don't forget to source the newly modified `.bashrc` file!):
-```bash
-export PYTHONPATH=<vta root>/python:${PYTHONPATH}
-```
-
-## LLVM installation
-
-We provide the set of commands to install LLVM 6.0 (stable branch) on Ubuntu Xenial. Note that the [LLVM installation process](apt.llvm.org) can be adapted to different LLVM branches, and operating systems/distros.
-
-```bash
-wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
-sudo apt-add-repository "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-6.0 main”
-sudo apt-get update
-apt-get install clang-6.0 lldb-6.0 lld-6.0
-```
-
-To ensure that LLVM 6.0 was properly installed, check that the following command gives the path to your `llvm-config` binary.
-
-```bash
-which llvm-config-6.0
-```
-
-## TVM installation
-
-Clone the TVM repository in the directory of your choosing:
-```bash
-git clone git@github.com:dmlc/tvm.git --recursive
-```
-
-TVM is rapidly changing, and to ensure stability, we keep track of working TVM checkpoints.
-As of now, the TVM checkpoint `168f099155106d1188dbc54ac00acc02900a3c6f` is known to work with VTA.
-```bash
-cd <tvm root>
-git checkout 168f099155106d1188dbc54ac00acc02900a3c6f
-```
-
-Before building TVM, copy the `make/config.mk` file into the root TVM directory:
-```bash
-cd <tvm root>
-cp make/config.mk .
-```
-
-In the 'config.mk' file sure that:
-* `LLVM_CONFIG` points to the `llvm-config` executable which path was derived in the TVM installation instructions above (e.g. `LLVM_CONFIG = /usr/bin/llvm-config-6.0`)
-* `USE_RPC` should be set to 1
-
-Launch the compilation, this takes about 5-10 minutes on two threads.
-```bash
-cd <tvm root>
-make -j2
-```
-
-Finally update your `~/.bashrc` file to include the TVM python libraries in your `PYTHONPATH` (don't forget to source the newly modified `.bashrc` file!):
-```bash
-export PYTHONPATH=<tvm root>/python:<tvm root>/topi/python:${PYTHONPATH}
-```
-
-## NNVM installation
-
-Clone the NNVM repository from `tqchen` in the directory of your choosing:
-```bash
-git clone git@github.com:tqchen/nnvm.git --recursive
-```
-
-To run this example, we rely on a special branch of NNVM `qt`:
-```bash
-cd <nnvm root>
-git checkout qt
-```
-
-Launch the compilation, this takes about a minute on two threads.
-```bash
-cd <nnvm root>
-make -j2
-```
-
-Finally update your `~/.bashrc` file to include the NNVM python libraries in your `PYTHONPATH` (don't forget to source the newly modified `.bashrc` file!):
-```bash
-export PYTHONPATH=<nnvm root>/python:${PYTHONPATH}
-```
-
-## MxNet Installation
-
-Follow the [MxNet Installation Instructions](https://mxnet.incubator.apache.org)
-
-## Pynq RPC Server Setup
-                                                       
-Follow the [Pynq RPC Server Guide](https://github.com/uwsaml/vta/tree/master/apps/pynq_rpc/README.md)
-
-## Python packages
-
-You'll need the following packages to be installed for the example to run properly. You can use `pip` to install those packages:
-* `decorator` (for TVM)
-* `enum34` (for NNVM)
-* `Pillow`
-* `wget`
-
-## Running the example
-
-Configure your environment with the following:
-```bash
-export VTA_PYNQ_RPC_HOST=192.168.2.99
-export VTA_PYNQ_RPC_PORT=9091
-```
+Follow the first two parts of the [Installation Guide](../../../docs/how_to/install.md) to make sure that the VTA python libraries are installed, and that the RPC server is running on the Pynq FPGA dev board.
 
 Simply run the following python script:
 ```bash
diff --git a/vta/python/vta/environment.py b/vta/python/vta/environment.py
index 7391d56e2774..3f0717f5b08b 100644
--- a/vta/python/vta/environment.py
+++ b/vta/python/vta/environment.py
@@ -43,7 +43,6 @@ class DevContext(object):
     QID_LOAD_OUT = 2
     QID_STORE_OUT = 3
     QID_COMPUTE = 2
-    QID_STORE_INP = 3
 
     def __init__(self, env):
         self.vta_axis = tvm.thread_axis("vta")
@@ -157,6 +156,7 @@ def __init__(self, cfg):
         self.acc_dtype = "int%d" % self.ACC_WIDTH
         self.inp_dtype = "int%d" % self.INP_WIDTH
         self.wgt_dtype = "int%d" % self.WGT_WIDTH
+        self.out_dtype = "int%d" % self.OUT_WIDTH
         # lazy cached members
         self.mock_mode = False
         self._mock_env = None
diff --git a/vta/tutorials/get_started.py b/vta/tutorials/get_started.py
index 73839196145d..d3f334875469 100644
--- a/vta/tutorials/get_started.py
+++ b/vta/tutorials/get_started.py
@@ -1,39 +1,387 @@
 """
+.. _get-started:
+
 Get Started with VTA
 ====================
-**Author**: `Tianqi Chen <https://tqchen.github.io>`_
+**Author**: `Thierry Moreau <https://homes.cs.washington.edu/~moreau/>`_
 
-This is an introduction tutorial to on how to use TVM to program VTA
+This is an introduction tutorial on how to use TVM to program the VTA design.
 
-In this tutorial, we will demonstrate the basic workflow of VTA
-and how we can program the FPGA to run various instructions.
+In this tutorial, we will demonstrate the basic TVM workflow to implement
+a vector addition on the VTA design's vector ALU.
+This process includes specific scheduling transformations necessary to lower
+computation down to low-level accelerator operations.
 
-To begin with, we need to import tvm which is our compiler stack for VTA.
-We also need to import vta python package which contains VTA specific
-extensions for compiler to generate code that runs on VTA.
+To begin, we need to import TVM which is our deep learning optimizing compiler.
+We also need to import the VTA python package which contains VTA specific
+extensions for TVM to target the VTA design.
 """
 from __future__ import absolute_import, print_function
 
+import os
+import tvm
 import vta
+import numpy as np
+
+######################################################################
+# Loading in VTA Parameters
+# ~~~~~~~~~~~~~~~~~~~~~~~~~
+# VTA is a modular and customizable design. Consequently, the user
+# is free to modify high-level hardware parameters that affect
+# the hardware design layout.
+# These parameters are specified in the :code:`config.json` file by their
+# :code:`log2` values.
+# These VTA parameters can be loaded with the :code:`vta.get_env`
+# function.
+#
+# Finally, the TVM target is specified in the :code:`config.json` file.
+# When set to *sim*, execution will take place inside of a behavioral
+# VTA simulator.
+# If you want to run this tutorial on the Pynq FPGA development platform,
+# follow the *VTA Pynq-Based Testing Setup* guide.
+
+env = vta.get_env()
+
+######################################################################
+# FPGA Programming
+# ----------------
+# When targeting the Pynq FPGA development board, we need to configure
+# the board with a VTA bitstream.
+
+# We'll need the TVM RPC module and the VTA simulator module
+from tvm.contrib import rpc, util
+from vta.testing import simulator
+
+# We read the Pynq RPC host IP address and port number from the OS environment
+host = os.environ.get("VTA_PYNQ_RPC_HOST", "192.168.2.99")
+port = int(os.environ.get("VTA_PYNQ_RPC_PORT", "9091"))
+
+# We configure both the bitstream and the runtime system on the Pynq
+# to match the VTA configuration specified by the config.json file.
+if env.TARGET == "pynq":
+
+    # Make sure that TVM was compiled with RPC=1
+    assert tvm.module.enabled("rpc")
+    remote = rpc.connect(host, port)
+
+    # Reconfigure the JIT runtime
+    vta.reconfig_runtime(remote)
+
+    # Program the FPGA with a pre-compiled VTA bitstream.
+    # You can program the FPGA with your own custom bitstream
+    # by passing the path to the bitstream file instead of None.
+    vta.program_fpga(remote, bitstream=None)
+
+# In simulation mode, host the RPC server locally.
+elif env.TARGET == "sim":
+    remote = rpc.LocalSession()
+
+######################################################################
+# Computation Declaration
+# -----------------------
+# As a first step, we need to describe our computation.
+# TVM adopts tensor semantics, with each intermediate result
+# represented as multi-dimensional array. The user needs to describe
+# the computation rule that generates the output tensors.
+#
+# In this example we describe a vector addition, which requires multiple
+# computation stages, as shown in the dataflow diagram below.
+# First we describe the input tensors :code:`A` and :code:`B` that are living
+# in main memory.
+# Second, we need to declare intermediate tensors :code:`A_buf` and
+# :code:`B_buf`, which will live in VTA's on-chip buffers.
+# Having this extra computational stage allows us to explicitly
+# stage cached reads and writes.
+# Third, we describe the vector addition computation which will
+# add :code:`A_buf` to :code:`B_buf` to produce :code:`C_buf`.
+# The last operation is a cast and copy back to DRAM, into results tensor
+# :code:`C`.
+#
+# .. image:: https://raw.githubusercontent.com/uwsaml/web-data/master/vta/tutorial/vadd_dataflow.png
+#      :align: center
+
+######################################################################
+# Input Placeholders
+# ~~~~~~~~~~~~~~~~~~
+# We describe the placeholder tensors :code:`A`, and :code:`B` in a tiled data
+# format to match the data layout requirements imposed by the VTA vector ALU.
+#
+# For VTA's general purpose operations such as vector adds, the tile size is
+# :code:`(env.BATCH, env.BLOCK_OUT)`.
+# The dimensions are specified in
+# the :code:`config.json` configuration file and are set by default to
+# a (1, 16) vector.
+#
+# In addition, A and B's data types also needs to match the :code:`env.acc_dtype`
+# which is set by the :code:`config.json` file to be a 32-bit integer.
+
+# Output channel factor m - total 64 x 16 = 1024 output channels
+m = 64
+# Batch factor o - total 1 x 1 = 1
+o = 1
+# A placeholder tensor in tiled data format
+A = tvm.placeholder((o, m, env.BATCH, env.BLOCK_OUT), name="A", dtype=env.acc_dtype)
+# B placeholder tensor in tiled data format
+B = tvm.placeholder((o, m, env.BATCH, env.BLOCK_OUT), name="B", dtype=env.acc_dtype)
+
+######################################################################
+# Copy Buffers
+# ~~~~~~~~~~~~
+# One specificity of hardware accelerators, is that on-chip memory has to be
+# explicitly managed.
+# This means that we'll need to describe intermediate tensors :code:`A_buf`
+# and :code:`B_buf` that can have a different memory scope than the original
+# placeholder tensors :code:`A` and :code:`B`.
+#
+# Later in the scheduling phase, we can tell the compiler that :code:`A_buf`
+# and :code:`B_buf` will live in the VTA's on-chip buffers (SRAM), while
+# :code:`A` and :code:`B` will live in main memory (DRAM).
+# We describe A_buf and B_buf as the result of a compute
+# operation that is the identity function.
+# This can later be interpreted by the compiler as a cached read operation.
+
+# A copy buffer
+A_buf = tvm.compute((o, m, env.BATCH, env.BLOCK_OUT), lambda *i: A(*i), "A_buf")
+# B copy buffer
+B_buf = tvm.compute((o, m, env.BATCH, env.BLOCK_OUT), lambda *i: B(*i), "B_buf")
+
+######################################################################
+# Vector Addition
+# ~~~~~~~~~~~~~~~
+# Now we're ready to describe the vector addition result tensor :code:`C`,
+# with another compute operation.
+# The compute function takes the shape of the tensor, as well as a lambda
+# function that describes the computation rule for each position of the tensor.
+#
+# No computation happens during this phase, as we are only declaring how
+# the computation should be done.
+
+# Describe the in-VTA vector addition
+C_buf = tvm.compute(
+    (o, m, env.BATCH, env.BLOCK_OUT),
+    lambda *i: A_buf(*i).astype(env.acc_dtype) + B_buf(*i).astype(env.acc_dtype),
+    name="C_buf")
+
+######################################################################
+# Casting the Results
+# ~~~~~~~~~~~~~~~~~~~
+# After the computation is done, we'll need to send the results computed by VTA
+# back to main memory.
+
+######################################################################
+# .. note::
+#
+#   **Memory Store Restrictions**
+#
+#   One specificity of VTA is that it only supports DRAM stores in the narrow
+#   :code:`env.inp_dtype` data type format.
+#   This lets us reduce the data footprint for memory transfers (more on this
+#   in the basic matrix multiply example).
+#
+# We perform one last typecast operation to the narrow
+# input activation data format.
+
+# Cast to output type, and send to main memory
+C = tvm.compute(
+    (o, m, env.BATCH, env.BLOCK_OUT),
+    lambda *i: C_buf(*i).astype(env.inp_dtype),
+    name="C")
+
+######################################################################
+# This concludes the computation declaration part of this tutorial.
 
 
 ######################################################################
-# Program the FPGA with VTA Bistream
-# ----------------------------------
-# In the first step, we need to program the FPGA with VTA bitstream.
+# Scheduling the Computation
+# --------------------------
+# While the above lines describes the computation rule, we can obtain
+# :code:`C` in many ways.
+# TVM asks the user to provide an implementation of the computation called
+# *schedule*.
 #
+# A schedule is a set of transformations to an original computation that
+# transforms the implementation of the computation without affecting
+# correctness.
+# This simple VTA programming tutorial aims to demonstrate basic schedule
+# transformations that will map the original schedule down to VTA hardware
+# primitives.
+
+
+######################################################################
+# Default Schedule
+# ~~~~~~~~~~~~~~~~
+# After we construct the schedule, by default the schedule computes
+# :code:`C` in the following way:
+
+# Let's take a look at the generated schedule
+s = tvm.create_schedule(C.op)
+
+print(tvm.lower(s, [A, B, C], simple_mode=True))
 
 ######################################################################
-# Run Simple Copy Instruction
-# ---------------------------
+# Although this schedule makes sense, it won't compile to VTA.
+# In order to obtain correct code generation, we need to apply scheduling
+# primitives and code annotation that will transform the schedule into
+# one that can be directly lowered onto VTA hardware intrinsics.
+# Those include:
 #
+#  - DMA copy operations which will take globally-scoped tensors and copy
+#    those into locally-scoped tensors.
+#  - Vector ALU operations that will perform the vector add.
 
 ######################################################################
-# Run Matrix Instruction
-# ----------------------
+# Buffer Scopes
+# ~~~~~~~~~~~~~
+# First, we set the scope of the copy buffers to indicate to TVM that these
+# intermediate tensors will be stored in the VTA's on-chip SRAM buffers.
+# Below, we tell TVM that :code:`A_buf`, :code:`B_buf`, :code:`C_buf`
+# will live in VTA's on-chip *accumulator buffer* which serves as
+# VTA's general purpose register file.
 #
+# Set the intermediate tensors' scope to VTA's on-chip accumulator buffer
+s[A_buf].set_scope(env.acc_scope)
+s[B_buf].set_scope(env.acc_scope)
+s[C_buf].set_scope(env.acc_scope)
 
 ######################################################################
-# Matrix Multiplication Example
-# -----------------------------
+# DMA Transfers
+# ~~~~~~~~~~~~~
+# We need to schedule DMA transfers to move data living in DRAM to
+# and from the VTA on-chip buffers.
+# We insert :code:`dma_copy` pragmas to indicate to the compiler
+# that the copy operations will be performed in bulk via DMA,
+# which is common in hardware accelerators.
+
+# Tag the buffer copies with the DMA pragma to map a copy loop to a
+# DMA transfer operation
+s[A_buf].pragma(s[A_buf].op.axis[0], env.dma_copy)
+s[B_buf].pragma(s[B_buf].op.axis[0], env.dma_copy)
+s[C].pragma(s[C].op.axis[0], env.dma_copy)
+
+######################################################################
+# ALU Operations
+# ~~~~~~~~~~~~~~
+# VTA has a vector ALU that can perform vector operations on tensors
+# in the accumulator buffer.
+# In order to tell TVM that a given operation needs to be mapped to the
+# VTA's vector ALU, we need to explicitly tag the vector addition loop
+# with an :code:`env.alu` pragma.
+
+# Tell TVM that the computation needs to be performed
+# on VTA's vector ALU
+s[C_buf].pragma(C_buf.op.axis[0], env.alu)
+
+# Let's take a look at the finalized schedule
+print(vta.lower(s, [A, B, C], simple_mode=True))
+
+######################################################################
+# This concludes the scheduling portion of this tutorial.
+
+######################################################################
+# TVM Compilation
+# ---------------
+# After we have finished specifying the schedule, we can compile it
+# into a TVM function. By default TVM compiles into a type-erased
+# function that can be directly called from python side.
 #
+# In the following line, we use :code:`tvm.build` to create a function.
+# The build function takes the schedule, the desired signature of the
+# function(including the inputs and outputs) as well as target language
+# we want to compile to.
+#
+my_vadd = vta.build(s, [A, B, C], "ext_dev", env.target_host, name="my_vadd")
+
+######################################################################
+# Saving the Module
+# ~~~~~~~~~~~~~~~~~
+# TVM lets us save our module into a file so it can loaded back later. This
+# is called ahead-of-time compilation and allows us to save some compilation
+# time.
+# More importantly, this allows us to cross-compile the executable on our
+# development machine and send it over to the Pynq FPGA board over RPC for
+# execution.
+
+# Write the compiled module into an object file.
+temp = util.tempdir()
+my_vadd.save(temp.relpath("vadd.o"))
+
+# Send the executable over RPC
+remote.upload(temp.relpath("vadd.o"))
+
+######################################################################
+# Loading the Module
+# ~~~~~~~~~~~~~~~~~~
+# We can load the compiled module from the file system to run the code.
+
+f = remote.load_module("vadd.o")
+
+######################################################################
+# Running the Function
+# --------------------
+# The compiled TVM function uses a concise C API and can be invoked from
+# any language.
+#
+# TVM provides an array API in python to aid quick testing and prototyping.
+# The array API is based on `DLPack <https://github.com/dmlc/dlpack>`_ standard.
+#
+# - We first create a remote context (for remote execution on the Pynq).
+# - Then :code:`tvm.nd.array` formats the data accordingly.
+# - :code:`f()` runs the actual computation.
+# - :code:`asnumpy()` copies the result array back in a format that can be
+#   interpreted.
+#
+
+# Get the remote device context
+ctx = remote.ext_dev(0)
+
+# Initialize the A and B arrays randomly in the int range of (-128, 128]
+A_orig = np.random.randint(
+    -128, 128, size=(o * env.BATCH, m * env.BLOCK_OUT)).astype(A.dtype)
+B_orig = np.random.randint(
+    -128, 128, size=(o * env.BATCH, m * env.BLOCK_OUT)).astype(B.dtype)
+
+# Apply packing to the A and B arrays from a 2D to a 4D packed layout
+A_packed = A_orig.reshape(
+    o, env.BATCH, m, env.BLOCK_OUT).transpose((0, 2, 1, 3))
+B_packed = B_orig.reshape(
+    o, env.BATCH, m, env.BLOCK_OUT).transpose((0, 2, 1, 3))
+
+# Format the input/output arrays with tvm.nd.array to the DLPack standard
+A_nd = tvm.nd.array(A_packed, ctx)
+B_nd = tvm.nd.array(B_packed, ctx)
+C_nd = tvm.nd.array(np.zeros((o, m, env.BATCH, env.BLOCK_OUT)).astype(C.dtype), ctx)
+
+# Invoke the module to perform the computation
+f(A_nd, B_nd, C_nd)
+
+######################################################################
+# Verifying Correctness
+# ---------------------
+# Compute the reference result with numpy and assert that the output of the
+# matrix multiplication indeed is correct
+
+# Compute reference result with numpy
+C_ref = (A_orig.astype(env.acc_dtype) + B_orig.astype(env.acc_dtype)).astype(C.dtype)
+C_ref = C_ref.reshape(
+    o, env.BATCH, m, env.BLOCK_OUT).transpose((0, 2, 1, 3))
+np.testing.assert_equal(C_ref, C_nd.asnumpy())
+print("Successful vector add test!")
+
+######################################################################
+# Summary
+# -------
+# This tutorial provides a walk-through of TVM for programming the
+# deep learning accelerator VTA with a simple vector addition example.
+# The general workflow includes:
+#
+# - Programming the FPGA with the VTA bitstream over RPC.
+# - Describing the vector add computation via a series of computations.
+# - Describing how we want to perform the computation using schedule primitives.
+# - Compiling the function to the VTA target.
+# - Running the compiled module and verifying it against a numpy implementation.
+#
+# You are more than welcome to check other examples out and tutorials
+# to learn more about the supported operations, schedule primitives
+# and other features supported by TVM to program VTA.
+#
+
diff --git a/vta/tutorials/matrix_multiply.py b/vta/tutorials/matrix_multiply.py
new file mode 100644
index 000000000000..f9d4d9fc7094
--- /dev/null
+++ b/vta/tutorials/matrix_multiply.py
@@ -0,0 +1,453 @@
+"""
+.. _basic-mat-mult:
+
+Simple Matrix Multiply
+======================
+**Author**: `Thierry Moreau <https://homes.cs.washington.edu/~moreau/>`_
+
+In this tutorial, we will build on top of the :ref:`get-started` tutorial
+and introduce additional concepts required to implement matrix multiplication
+on VTA with the TVM workflow.
+"""
+
+######################################################################
+# RPC Setup
+# ---------
+# We start by programming the Pynq's FPGA and building its RPC runtime
+# as we did in the VTA introductory tutorial.
+
+from __future__ import absolute_import, print_function
+
+import os
+import tvm
+import vta
+import numpy as np
+from tvm.contrib import rpc, util
+from vta.testing import simulator
+
+# Load VTA parameters from the config.json file
+env = vta.get_env()
+
+# We read the Pynq RPC host IP address and port number from the OS environment
+host = os.environ.get("VTA_PYNQ_RPC_HOST", "192.168.2.99")
+port = int(os.environ.get("VTA_PYNQ_RPC_PORT", "9091"))
+
+# We configure both the bitstream and the runtime system on the Pynq
+# to match the VTA configuration specified by the config.json file.
+if env.TARGET == "pynq":
+
+    # Make sure that TVM was compiled with RPC=1
+    assert tvm.module.enabled("rpc")
+    remote = rpc.connect(host, port)
+
+    # Reconfigure the JIT runtime
+    vta.reconfig_runtime(remote)
+
+    # Program the FPGA with a pre-compiled VTA bitstream.
+    # You can program the FPGA with your own custom bitstream
+    # by passing the path to the bitstream file instead of None.
+    vta.program_fpga(remote, bitstream=None)
+
+# In simulation mode, host the RPC server locally.
+elif env.TARGET == "sim":
+    remote = rpc.LocalSession()
+
+######################################################################
+# Computation Declaration
+# -----------------------
+# In this example we describe a simple matrix multiplication addition, which
+# requires multiple computation stages, as shown in the dataflow diagram below.
+# First we describe the input tensors :code:`A` and :code:`B` that are living
+# in main memory.
+# Second, we need to declare intermediate tensors :code:`A_buf` and
+# :code:`B_buf`, which will live in VTA's on-chip buffers.
+# Having this extra computational stage allows us to explicitly
+# stage cached reads and writes.
+# Third, we describe the matrix multiplication computation over
+# :code:`A_buf` and :code:`B_buf` to produce the product matrix :code:`C_buf`.
+# The last operation is a cast and copy back to DRAM, into results tensor
+# :code:`C`.
+#
+# .. image:: https://raw.githubusercontent.com/uwsaml/web-data/master/vta/tutorial/gemm_dataflow.png
+#      :align: center
+
+######################################################################
+# Data Layout
+# ~~~~~~~~~~~
+# We describe the placeholder tensors :code:`A`, and :code:`B` in a tiled data
+# format to match the data layout requirements imposed by the VTA tensor core.
+
+######################################################################
+# .. note::
+#
+#   **Data Tiling**
+#
+#   One source of complexity when targeting accelerators is to make sure
+#   that the data layout matches the layout imposed by the accelerator design.
+#   VTA is designed around a *tensor core* that performs, one matrix-matrix
+#   operation per cycle between an activation matrix and a weight matrix,
+#   adding the result matrix to an accumulator matrix, as shown in the
+#   figure below.
+#
+#   .. image:: https://raw.githubusercontent.com/uwsaml/web-data/master/vta/tutorial/tensor_core.png
+#        :align: center
+#
+#   The dimensions of that matrix-matrix multiplication are specified in
+#   the :code:`config.json` configuration file.
+#   The activation matrix has a :code:`(BATCH, BLOCK_IN)` shape
+#   and the transposed weight matrix has a :code:`(BLOCK_OUT, BLOCK_IN)` shape,
+#   thus inferring that the resulting output matrix has a
+#   :code:`(BATCH, BLOCK_OUT)` shape.
+#   Consequently input and output tensors processed by VTA need to be
+#   tiled according to these aforementioned dimension.
+#
+#   The diagram below shows the impact of data tiling on a matrix that is
+#   originally of shape (4, 8).
+#   Tiling by a (2, 2) tile shape ensures that data within each tile is
+#   contiguous.
+#   The resulting tiled tensor has a shape of (2, 4, 2, 2).
+# 
+#   .. image:: https://raw.githubusercontent.com/uwsaml/web-data/master/vta/tutorial/data_tiling.png
+#        :align: center
+#
+# We first define the variables :code:`m`, :code:`n`, :code:`o` to represent
+# the shape of the matrix multiplication. These variables are multiplicative
+# factors over the :code:`BLOCK_OUT`, :code:`BLOCK_IN`, and :code:`BATCH`
+# tensor dimensions respectively.
+# By default, the configuration file sets :code:`BATCH`, :code:`BLOCK_IN`, and
+# :code:`BLOCK_OUT` to be 1, 16 and 16 respectively (:code:`BATCH` being set to
+# 1 implies that our compute building block is vector-matrix multiply).
+#
+
+
+######################################################################
+# .. note::
+#
+#   **Data Types**
+#
+#   It's important to not only match the inner-tile
+#   dimension of VTA's tensor core, but also to match the specific data types
+#   expected by VTA.
+#   VTA for now only supports fixed point data types, which integer width is
+#   specified in the :code:`config.json` file by :code:`INP_WIDTH` and
+#   :code:`WGT_WIDTH` for the activations and weights data types respectively.
+#   In addition, the accumulator data type integer width is specified by
+#   :code:`ACC_WIDTH`.
+#
+# By default, the configuration file sets :code:`INP_WIDTH`
+# and :code:`WGT_WIDTH` to 8.
+# The accumulator width :code:`ACC_WIDTH` is set to 32, in order to avoid
+# overflow during accumulation.
+# As a result, :code:`env.inp_dtype` and :code:`env.wgt_dtype` are all
+# narrow 8-bit integers, while :code:`env.acc_dtype` is a standard 32-bit
+# integer.
+
+# Output channel factor m - total 16x16=256 output channels
+m = 16
+# Input channel factor n - total 16x16=256 input channels
+n = 16
+# Batch factor o (we use single batch inference)
+o = 1
+# A placeholder tensor in tiled data format
+A = tvm.placeholder((o, n, env.BATCH, env.BLOCK_IN), name="A", dtype=env.inp_dtype)
+# B placeholder tensor in tiled data format
+B = tvm.placeholder((m, n, env.BLOCK_OUT, env.BLOCK_IN), name="B", dtype=env.wgt_dtype)
+# A copy buffer
+A_buf = tvm.compute((o, n, env.BATCH, env.BLOCK_IN), lambda *i: A(*i), "A_buf")
+# B copy buffer
+B_buf = tvm.compute((m, n, env.BLOCK_OUT, env.BLOCK_IN), lambda *i: B(*i), "B_buf")
+
+######################################################################
+# Matrix Multiplication
+# ~~~~~~~~~~~~~~~~~~~~~
+# Now we're ready to describe the matrix multiplication result tensor :code:`C`,
+# with another compute operation.
+# The compute function takes the shape of the tensor, as well as a lambda
+# function that describes the computation rule for each position of the tensor.
+#
+# In order to implement matrix multiplication, the lambda function needs to
+# include a reduction formula over the input channel dimension axes.
+# To create a reduction formula, we can declare a reduction axis using
+# :code:`tvm.reduce_axis`, which takes in the range of reductions.
+# :code:`tvm.sum` takes in the expression to be reduced as well as
+# the reduction axes to compute the sum of value over all k in the declared
+# ranges.
+#
+# Note that the reduction needs to be performed over 32-bit :code:`env.acc_dtype`
+# accumulator data types.
+#
+# No computation happens during this phase, as we are only declaring how
+# the computation should be done.
+
+# Outer input feature reduction axis
+ko = tvm.reduce_axis((0, n), name="ko")
+# Inner input feature reduction axis
+ki = tvm.reduce_axis((0, env.BLOCK_IN), name="ki")
+# Describe the in-VTA matrix multiplication
+C_buf = tvm.compute(
+    (o, m, env.BATCH, env.BLOCK_OUT),
+    lambda bo, co, bi, ci:
+        tvm.sum(A_buf[bo, ko, bi, ki].astype(env.acc_dtype) *
+                B_buf[co, ko, ci, ki].astype(env.acc_dtype),
+                axis=[ko, ki]),
+    name="C_buf")
+
+######################################################################
+# Casting the Results
+# ~~~~~~~~~~~~~~~~~~~
+# After the computation is done, we'll need to send the results computed by VTA
+# back to main memory.
+
+######################################################################
+# .. note::
+#
+#   **Memory Store Restrictions**
+#
+#   One specificity of VTA is that it only supports DRAM stores in the narrow
+#   :code:`env.inp_dtype` data type format.
+#   This lets us reduce the data footprint for memory transfers, but also lets
+#   us quantize the wide accumulator data type down to a data format that
+#   matches the input activation data type.
+#   This means that in the context of neural network inference, the outputs
+#   of a given layer after activation can be consumed directly by the next
+#   layer.
+#
+# We perform one last typecast operation to the narrow
+# input activation data format.
+
+# Cast to output type, and send to main memory
+C = tvm.compute(
+    (o, m, env.BATCH, env.BLOCK_OUT),
+    lambda *i: C_buf(*i).astype(env.inp_dtype),
+    name="C")
+
+######################################################################
+# This concludes the computation declaration part of this tutorial.
+
+######################################################################
+# Scheduling the Computation
+# --------------------------
+# While the above lines describes the computation rule, we can obtain
+# :code:`C` in many ways.
+# TVM asks the user to provide an implementation of the computation called
+# *schedule*.
+#
+# A schedule is a set of transformations to an original computation that
+# transforms the implementation of the computation without affecting
+# correctness.
+# This simple VTA programming tutorial aims to demonstrate basic schedule
+# transformations that will map the original schedule down to VTA hardware
+# primitives.
+
+
+######################################################################
+# Default Schedule
+# ~~~~~~~~~~~~~~~~
+# After we construct the schedule, by default the schedule computes
+# :code:`C` in the following way:
+
+# Let's take a look at the generated schedule
+s = tvm.create_schedule(C.op)
+print(tvm.lower(s, [A, B, C], simple_mode=True))
+
+######################################################################
+# Although this schedule makes sense, it won't compile to VTA.
+# In order to obtain correct code generation, we need to apply scheduling
+# primitives and code annotation that will transform the schedule into
+# one that can be directly lowered onto VTA hardware intrinsics.
+# Those include:
+#
+#  - DMA copy operations which will take globally-scoped tensors and copy
+#    those into locally-scoped tensors.
+#  - Tensor operations that will perform the matrix multiplication.
+
+######################################################################
+# Buffer Scopes
+# ~~~~~~~~~~~~~
+# First, we set the scope of the buffers to tell TVM that these buffers
+# will be living in the VTA's on-chip SRAM caches.
+# Below, we tell TVM that :code:`A_buf`, :code:`B_buf`, :code:`C_buf`
+# will respectively live in VTA's on-chip input, weight and accumulator
+# memory.
+
+######################################################################
+# .. note::
+#
+#   **VTA's On-Chip SRAMs**
+#
+#   VTA has three different memory scopes, each corresponding to different
+#   on-chip SRAM buffers.
+#
+#    - :code:`env.inp_scope`: Input buffer, which is a read-only SRAM buffer
+#      that stores input matrices of shape :code:`(env.BATCH, env.BLOCK_IN)`
+#      of type :code:`env.inp_dtype`. The input buffer contains
+#      `2 ^ LOG_INP_BUFF_SIZE` matrix elements (as specified in the
+#      :code:`config.json` file).
+#    - :code:`env.wgt_scope`: Weight buffer, which is a read-only SRAM buffer
+#      that stores weight matrices of shape :code:`(env.BLOCK_OUT, env.BLOCK_IN)`
+#      of type :code:`env.wgt_dtype`. The weight buffer contains
+#      `2 ^ LOG_WGT_BUFF_SIZE` matrix elements.
+#    - :code:`env.acc_scope`: Accumulator buffer, which is a read/write SRAM
+#      buffer that stores accumulator matrices of shape
+#      :code:`(env.BATCH, env.BLOCK_OUT)` of type :code:`env.acc_dtype`.
+#      The accumulator buffer is VTA's general purpose register file: it holds
+#      both intermediate results of convolutions and matrix multiplications
+#      as well as intermediate results of pooling, batch normalization, and
+#      activation layers. The accumulator buffer contains
+#      `2 ^ LOG_ACC_BUFF_SIZE` matrix elements.
+
+# Set the intermediate tensor's scope to VTA's on-chip buffers
+s[A_buf].set_scope(env.inp_scope)
+s[B_buf].set_scope(env.wgt_scope)
+s[C_buf].set_scope(env.acc_scope)
+
+######################################################################
+# DMA Transfers
+# ~~~~~~~~~~~~~
+# We need to schedule DMA transfers to move data living in DRAM to
+# and from the VTA on-chip buffers.
+# This can be achieved using the :code:`compute_at` schedule primitive
+# which nests the copying of the buffers into the computation loop
+# that performs the matrix multiplication.
+#
+# We insert :code:`dma_copy` pragmas to indicate to the compiler
+# that the copy operations will be performed in bulk via DMA,
+# which is common in hardware accelerators.
+# Finally, we print the temporary schedule to observe the effects of
+# moving the copy operations into the matrix multiplication loop.
+
+# Move buffer copy into matrix multiply loop
+s[A_buf].compute_at(s[C_buf], ko)
+s[B_buf].compute_at(s[C_buf], ko)
+
+# Tag the buffer copies with the DMA pragma to insert a DMA transfer
+s[A_buf].pragma(s[A_buf].op.axis[0], env.dma_copy)
+s[B_buf].pragma(s[B_buf].op.axis[0], env.dma_copy)
+s[C].pragma(s[C].op.axis[0], env.dma_copy)
+
+# Let's take a look at the transformed schedule
+print(tvm.lower(s, [A, B, C], simple_mode=True))
+
+######################################################################
+# Tensorization
+# ~~~~~~~~~~~~~
+# The last step of the schedule transformation consists in applying
+# *tensorization* to our schedule.
+# Tensorization is analogous to vectorization, but extends the concept
+# to a higher-dimensional unit of computation.
+# Consequently, tensorization imposes data layout constraints as discussed
+# earlier when declaring the data layout input placeholders.
+# We've already arranged our tensors in a tiled format, so the next thing
+# we need to perform is loop reordering to accommodate for tensorization.
+#
+# Here we choose to move the outermost reduction axis all the way out.
+# This dictates that we first iterate over input channels, then batch
+# dimensions, and finally output channels.
+# Lastly, we apply the tensorization scheduling primitive :code:`tensorize`
+# along the outer axis of the inner-most matrix matrix multiplication tensor
+# block.
+# We print the finalized schedule that is ready for code-generation
+# by the VTA runtime JIT compiler.
+
+s[C_buf].reorder(
+    ko,
+    s[C_buf].op.axis[0],
+    s[C_buf].op.axis[1],
+    s[C_buf].op.axis[2],
+    s[C_buf].op.axis[3],
+    ki)
+s[C_buf].tensorize(s[C_buf].op.axis[2], env.gemm)
+
+# Let's take a look at the finalized schedule
+print(vta.lower(s, [A, B, C], simple_mode=True))
+
+######################################################################
+# This concludes the scheduling portion of this tutorial.
+
+######################################################################
+# TVM Compilation
+# ---------------
+# After we have finished specifying the schedule, we can compile it
+# into a TVM function.
+
+# Build GEMM VTA kernel
+my_gemm = vta.build(s, [A, B, C], "ext_dev", env.target_host, name="my_gemm")
+
+# Write the compiled module into an object file.
+temp = util.tempdir()
+my_gemm.save(temp.relpath("gemm.o"))
+
+# Send the executable over RPC
+remote.upload(temp.relpath("gemm.o"))
+
+# Load the compiled module
+f = remote.load_module("gemm.o")
+
+######################################################################
+# Running the Function
+# --------------------
+# The compiled TVM function uses a concise C API and can be invoked from
+# code language.
+#
+# TVM provides an array API in python to aid quick testing and prototyping.
+# The array API is based on `DLPack <https://github.com/dmlc/dlpack>`_ standard.
+#
+# - We first create a remote context (for remote execution on the Pynq).
+# - Then :code:`tvm.nd.array` formats the data accordingly.
+# - :code:`f()` runs the actual computation.
+# - :code:`asnumpy()` copies the result array back in a format that can be
+#   interpreted.
+#
+
+# Get the remote device context
+ctx = remote.ext_dev(0)
+
+# Initialize the A and B arrays randomly in the int range of (-128, 128]
+A_orig = np.random.randint(
+    -128, 128, size=(o * env.BATCH, n * env.BLOCK_IN)).astype(A.dtype)
+B_orig = np.random.randint(
+    -128, 128, size=(m * env.BLOCK_OUT, n * env.BLOCK_IN)).astype(B.dtype)
+
+# Apply packing to the A and B arrays from a 2D to a 4D packed layout
+A_packed = A_orig.reshape(
+    o, env.BATCH, n, env.BLOCK_IN).transpose((0, 2, 1, 3))
+B_packed = B_orig.reshape(
+    m, env.BLOCK_OUT, n, env.BLOCK_IN).transpose((0, 2, 1, 3))
+
+# Format the input/output arrays with tvm.nd.array to the DLPack standard
+A_nd = tvm.nd.array(A_packed, ctx)
+B_nd = tvm.nd.array(B_packed, ctx)
+C_nd = tvm.nd.array(np.zeros((o, m, env.BATCH, env.BLOCK_OUT)).astype(C.dtype), ctx)
+
+# Invoke the module to perform the computation
+f(A_nd, B_nd, C_nd)
+
+######################################################################
+# Verifying Correctness
+# ---------------------
+# Compute the reference result with numpy and assert that the output of the
+# matrix multiplication indeed is correct
+
+# Compute reference result with numpy
+C_ref = np.dot(A_orig.astype(env.acc_dtype),
+               B_orig.T.astype(env.acc_dtype)).astype(C.dtype)
+C_ref = C_ref.reshape(
+    o, env.BATCH, m, env.BLOCK_OUT).transpose((0, 2, 1, 3))
+np.testing.assert_equal(C_ref, C_nd.asnumpy())
+print("Successful matrix multiply test!")
+
+
+######################################################################
+# Summary
+# -------
+# This tutorial showcases the TVM workflow to implement a simple matrix
+# multiplication example on VTA.
+# The general workflow includes:
+#
+# - Programming the FPGA with the VTA bitstream over RPC.
+# - Describing matrix multiplication via a series of computations.
+# - Describing how we want to perform the computation using schedule primitives.
+# - Compiling the function to the VTA target.
+# - Running the compiled module and verifying it against a numpy implementation.
+#
+
diff --git a/vta/tutorials/matrix_multiply_opt.py b/vta/tutorials/matrix_multiply_opt.py
new file mode 100644
index 000000000000..9b62504a7007
--- /dev/null
+++ b/vta/tutorials/matrix_multiply_opt.py
@@ -0,0 +1,362 @@
+"""
+.. _mat-mult-opt:
+
+Matrix Multiply Blocking
+========================
+**Author**: `Thierry Moreau <https://homes.cs.washington.edu/~moreau/>`_
+
+This tutorial provides an overview on how to use TVM to map matrix
+multiplication efficiently on the VTA design.
+We recommend covering the :ref:`basic-mat-mult` tutorial first.
+
+In this tutorial, we will demonstrate TVM schedule optimizations to break large
+neural network operators down onto smaller blocks to achieve computation within
+limited hardware accelerator resources.
+"""
+
+######################################################################
+# RPC Setup
+# ---------
+# We start by programming the Pynq's FPGA and building its RPC runtime.
+
+from __future__ import absolute_import, print_function
+
+import os
+import tvm
+import vta
+import numpy as np
+from tvm.contrib import rpc, util
+from vta.testing import simulator
+
+# Load VTA parameters from the config.json file
+env = vta.get_env()
+
+# We read the Pynq RPC host IP address and port number from the OS environment
+host = os.environ.get("VTA_PYNQ_RPC_HOST", "192.168.2.99")
+port = int(os.environ.get("VTA_PYNQ_RPC_PORT", "9091"))
+
+# We configure both the bitstream and the runtime system on the Pynq
+# to match the VTA configuration specified by the config.json file.
+if env.TARGET == "pynq":
+
+    # Make sure that TVM was compiled with RPC=1
+    assert tvm.module.enabled("rpc")
+    remote = rpc.connect(host, port)
+
+    # Reconfigure the JIT runtime
+    vta.reconfig_runtime(remote)
+
+    # Program the FPGA with a pre-compiled VTA bitstream.
+    # You can program the FPGA with your own custom bitstream
+    # by passing the path to the bitstream file instead of None.
+    vta.program_fpga(remote, bitstream=None)
+
+# In simulation mode, host the RPC server locally.
+elif env.TARGET == "sim":
+    remote = rpc.LocalSession()
+
+######################################################################
+# Computation Declaration
+# -----------------------
+# As a first step, we need to describe our matrix multiplication computation.
+# We define the matrix multiplication as the computation one would find in a
+# fully connected layer, defined by its batch size, input channels, and output
+# channels.
+# These have to be integer multiples of the VTA tensor shape:
+# :code:`BATCH`, :code:`BLOCK_IN`, and :code:`BLOCK_OUT` respectively.
+#
+# We've added extra operators to the matrix multiplication that apply
+# shifting and clipping to the output in order to mimic the a fixed-point
+# matrix multiplication followed by a rectified linear activation.
+# We describe the TVM dataflow graph of the fully connected layer below:
+#
+# .. image:: https://raw.githubusercontent.com/uwsaml/web-data/master/vta/tutorial/fc_dataflow.png
+#      :align: center
+#
+# This computation is intentionally too large to fit onto VTA's on-chip
+# buffers all at once. Therefore in the scheduling phase we'll
+# rely on computation blocking strategies to break the computation down into
+# manageable chunks.
+
+# Fully connected layer dimensions: 1024 x 1024
+batch_size = 1
+in_channels = 1024
+out_channels = 1024
+assert batch_size % env.BATCH == 0
+assert in_channels % env.BLOCK_IN == 0
+assert out_channels % env.BLOCK_OUT == 0
+
+# Let's derive the tiled input tensor shapes
+data_shape = (batch_size // env.BATCH,
+              in_channels // env.BLOCK_IN,
+              env.BATCH,
+              env.BLOCK_IN)
+weight_shape = (out_channels // env.BLOCK_OUT,
+                in_channels // env.BLOCK_IN,
+                env.BLOCK_OUT,
+                env.BLOCK_IN)
+output_shape = (batch_size // env.BATCH,
+                out_channels // env.BLOCK_OUT,
+                env.BATCH,
+                env.BLOCK_OUT)
+num_ops = in_channels * out_channels * batch_size * 2
+
+# Reduction axes
+ic = tvm.reduce_axis((0, in_channels // env.BLOCK_IN), name='ic')
+ic_tns = tvm.reduce_axis((0, env.BLOCK_IN), name='ic_tns')
+
+# Input placeholder tensors
+data = tvm.placeholder(data_shape, name="data", dtype=env.inp_dtype)
+weight = tvm.placeholder(weight_shape, name="weight", dtype=env.wgt_dtype)
+
+# Copy buffers
+data_buf = tvm.compute(data_shape,
+                       lambda *i: data(*i),
+                       "data_buf")
+weight_buf = tvm.compute(weight_shape,
+                         lambda *i: weight(*i),
+                         "weight_buf")
+
+# Declare matrix multiply computation
+res_gemm = tvm.compute(output_shape,
+                       lambda bo, co, bi, ci: tvm.sum(
+                            data_buf[bo, ic, bi, ic_tns].astype(env.acc_dtype) *
+                            weight_buf[co, ic, ci, ic_tns].astype(env.acc_dtype),
+                            axis=[ic, ic_tns]),
+                       name="res_gem")
+
+# Add shift stage for fix-point normalization
+res_shr = tvm.compute(output_shape,
+                      lambda *i: res_gemm(*i) >> env.INP_WIDTH,
+                      name="res_shr")
+
+# Apply clipping between (0, input max value)
+inp_max = (1<<(env.INP_WIDTH-1))-1
+res_max = tvm.compute(output_shape,
+                      lambda *i: tvm.max(res_shr(*i), 0),
+                      "res_max")
+res_min = tvm.compute(output_shape,
+                      lambda *i: tvm.min(res_max(*i), inp_max),
+                      "res_min")
+
+# Apply typecast to input data type before sending results back
+res = tvm.compute(output_shape,
+                  lambda *i: res_min(*i).astype(env.inp_dtype),
+                  name="res")
+
+######################################################################
+# Scheduling the Computation
+# --------------------------
+# We'll look at a set of schedule transformations necessary to map the
+# matrix multiplications onto VTA in an efficient fashion.
+# Those include:
+#
+# - Computation blocking
+# - Computation lowering to VTA hardware intrinsics
+
+
+# Create TVM schedule
+s = tvm.create_schedule(res.op)
+# Let's look at the default TVM schedule
+print(tvm.lower(s, [data, weight, res], simple_mode=True))
+
+######################################################################
+# Tiling the Computation
+# ~~~~~~~~~~~~~~~~~~~~~~
+# The matrix multiplication is by default too large for activations or weights
+# to fit on VTA's on-chip buffers all at once.
+# We block the (1, 1024) by (1024, 1024) matrix multiplication into
+# smaller (1, 256) by (256, 256) matrix multiplications so the intermediate
+# tensors can fit on the accelerator's on-chip SRAM.
+# This approach is similar to blocking techniques applied to CPUs and GPUs in
+# order to increase cache hit rate.
+#
+# We perform blocking along each axes (the batch axis being untouched since
+# we are performing singe-batch inference).
+# We also leave the inner-most tensorization axes as-is in order to allow
+# TVM to pattern-match tensorization.
+# We show the outcome of blocking on the computation schedule in the diagram
+# below:
+#
+# .. image:: https://raw.githubusercontent.com/uwsaml/web-data/master/vta/tutorial/blocking.png
+#      :align: center
+#      :height: 367px
+#      :width: 387px
+#
+# .. note::
+# 
+#   The code after loop splitting and reordering is equivalent to the following
+#   pseudo-code. We ignore the batch axis since we are only performing single-batch
+#   inference in this example:
+#
+#   .. code-block:: c
+#
+#      for (int oc_out = 0; oc_out < 4; ++oc_out) {
+#        // Initialization loop
+#        for (int oc_inn = 0; oc_inn < 16; ++oc_inn) {
+#         for (int oc_tns = 0; oc_tns < 16; ++oc_tns) {
+#          int j = (oc_out * 16 + oc_inn) * 16 + oc_tns;
+#          C[0][j] = 0;
+#         }
+#        }
+#        for (int ic_out = 0; ic_out < 4; ++ic_out) {
+#         // Block loop
+#         for (int oc_inn = 0; oc_inn < 16; ++oc_inn) {
+#          for (int ic_inn = 0; ic_inn < 16; ++ic_inn) {
+#           // Tensorization loop
+#           for (int oc_tns = 0; oc_tns < 16; ++oc_tns) {
+#            for (int ic_tns = 0; ic_tns < 16; ++ic_tns) {
+#             int i = (ic_out * 16 + ic_inn) * 16 + ic_tns;
+#             int j = (oc_out * 16 + oc_inn) * 16 + oc_tns;
+#             C[0][i] = C[0][i] + A[0][i] * B[j][i];
+#            }
+#           }
+#          }
+#         }
+#        }
+#       }
+#      }
+
+# Let's define tiling sizes (expressed in multiples of VTA tensor shape size)
+b_block = 1 // env.BATCH
+i_block = 256 // env.BLOCK_IN
+o_block = 256 // env.BLOCK_OUT
+
+# Tile the output tensor along the batch and output channel dimensions
+# (since by default we are doing single batch inference, the split along
+#  the batch dimension has no effect)
+b, oc, b_tns, oc_tns = s[res].op.axis
+b_out, b_inn = s[res].split(b, b_block)
+oc_out, oc_inn = s[res].split(oc, o_block)
+s[res].reorder(b_out, oc_out, b_inn, oc_inn)
+
+# Move intermediate computation into each output compute tile
+s[res_gemm].compute_at(s[res], oc_out)
+s[res_shr].compute_at(s[res], oc_out)
+s[res_max].compute_at(s[res], oc_out)
+s[res_min].compute_at(s[res], oc_out)
+
+# Apply additional loop split along input channel axis
+b_inn, oc_inn, b_tns, oc_tns = s[res_gemm].op.axis
+ic_out, ic_inn = s[res_gemm].split(ic, i_block)
+
+# Reorder axes. We move the ic_out axis all the way out of the GEMM
+# loop to block along the reduction axis
+s[res_gemm].reorder(ic_out, b_inn, oc_inn, ic_inn, b_tns, oc_tns, ic_tns)
+
+# Let's look at the current TVM schedule after blocking
+print(tvm.lower(s, [data, weight, res], simple_mode=True))
+
+######################################################################
+# Lowering Copies to DMA Transfers
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+# Next we set the buffer scopes to the corresponding on-chip VTA SRAM buffers.
+# We move the load loops into the matrix multiply computation loop to stage
+# memory loads such that they fit in the on-chip SRAM buffers.
+# Finally we annotate the load/store loop outer axes with the DMA copy pragma
+# to perform bulk memory transfers on VTA.
+
+# Set scope of SRAM buffers
+s[data_buf].set_scope(env.inp_scope)
+s[weight_buf].set_scope(env.wgt_scope)
+s[res_gemm].set_scope(env.acc_scope)
+s[res_shr].set_scope(env.acc_scope)
+s[res_min].set_scope(env.acc_scope)
+s[res_max].set_scope(env.acc_scope)
+
+# Block data and weight cache reads
+s[data_buf].compute_at(s[res_gemm], ic_out)
+s[weight_buf].compute_at(s[res_gemm], ic_out)
+
+# Use DMA copy pragma on DRAM->SRAM operations
+s[data_buf].pragma(s[data_buf].op.axis[0], env.dma_copy)
+s[weight_buf].pragma(s[weight_buf].op.axis[0], env.dma_copy)
+
+# Use DMA copy pragma on SRAM->DRAM operation
+s[res].pragma(s[res].op.axis[2], env.dma_copy)
+
+######################################################################
+# Lowering Computation to VTA Compute Intrinsics
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+# The last phase is to lower the computation loops down to VTA hardware
+# intrinsics by mapping the matrix multiplication to tensor intrinsics,
+# and mapping the shift, and clipping computation to the vector ALU.
+
+# Apply tensorization over the batch tensor tile axis
+s[res_gemm].tensorize(b_tns, env.gemm)
+
+# Add an ALU pragma over the shift and clipping operations
+s[res_shr].pragma(s[res_shr].op.axis[0], env.alu)
+s[res_min].pragma(s[res_min].op.axis[0], env.alu)
+s[res_max].pragma(s[res_max].op.axis[0], env.alu)
+
+# Let's look at the final lowered TVM schedule after lowering memory
+# loads/stores down to DMA copy intrinsics, and the computation down to
+# VTA compute intrinsics.
+print(vta.lower(s, [data, weight, res], simple_mode=True))
+
+######################################################################
+# TVM Compilation and Verification
+# --------------------------------
+# After specifying the schedule, we can compile it into a TVM function.
+# We save the module so we can send it over RPC.
+# We run the function and verify it against a numpy implementation to
+# ensure correctness.
+
+# Compile the TVM module
+my_gemm = vta.build(s, [data, weight, res], "ext_dev", env.target_host, name="my_gemm")
+temp = util.tempdir()
+my_gemm.save(temp.relpath("gemm.o"))
+remote.upload(temp.relpath("gemm.o"))
+f = remote.load_module("gemm.o")
+
+# Get the remote device context
+ctx = remote.ext_dev(0)
+
+# Initialize the A and B arrays randomly in the int range of (-128, 128]
+data = np.random.randint(
+    -128, 128, size=(batch_size, in_channels)).astype(data.dtype)
+weight = np.random.randint(
+    -128, 128, size=(out_channels, in_channels)).astype(weight.dtype)
+
+# Apply packing to the A and B arrays from a 2D to a 4D packed layout
+data_packed = data.reshape(batch_size // env.BATCH,
+                           env.BATCH,
+                           in_channels // env.BLOCK_IN,
+                           env.BLOCK_IN).transpose((0, 2, 1, 3))
+weight_packed = weight.reshape(out_channels // env.BLOCK_OUT,
+                               env.BLOCK_OUT,
+                               in_channels // env.BLOCK_IN,
+                               env.BLOCK_IN).transpose((0, 2, 1, 3))
+
+# Format the input/output arrays with tvm.nd.array to the DLPack standard
+data_nd = tvm.nd.array(data_packed, ctx)
+weight_nd = tvm.nd.array(weight_packed, ctx)
+res_nd = tvm.nd.array(np.zeros(output_shape).astype(res.dtype), ctx)
+
+# Invoke the module to perform the computation
+f(data_nd, weight_nd, res_nd)
+
+# Verify against numpy implementation
+res_ref = np.dot(data.astype(env.acc_dtype),
+               weight.T.astype(env.acc_dtype))
+res_ref = res_ref >> env.INP_WIDTH
+res_ref = np.clip(res_ref, 0, inp_max)
+res_ref = res_ref.astype(res.dtype)
+res_ref = res_ref.reshape(batch_size // env.BATCH,
+                          env.BATCH,
+                          out_channels // env.BLOCK_OUT,
+                          env.BLOCK_OUT).transpose((0, 2, 1, 3))
+np.testing.assert_equal(res_ref, res_nd.asnumpy())
+print("Successful blocked matrix multiply test!")
+
+######################################################################
+# Summary
+# -------
+# This tutorial demonstrates how TVM scheduling primitives can achieve
+# computation blocking for a matrix multiplication example.
+# This allows us to map arbitrarily large computation onto limited
+# hardware accelerator resources.
+#
+
+

From bc806614afeb6664852226977aa364fb997212f8 Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@cs.washington.edu>
Date: Fri, 22 Jun 2018 13:40:22 -0700
Subject: [PATCH 851/949] [UTILS, DOC] Use TVM file downloading utility, conv2d
 tutorial (#48)

---
 vta/examples/resnet18/pynq/README.md          | 14 +++-
 .../resnet18/pynq/imagenet_predict.py         | 24 +++---
 vta/python/vta/bitstream.py                   | 39 +++++++---
 vta/python/vta/testing/util.py                | 47 +++++++-----
 .../python/integration/test_benchmark_gemm.py | 17 +++--
 .../integration/test_benchmark_topi_conv2d.py |  3 +-
 .../python/pynq/test_benchmark_conv2d.py      | 74 ++++++++++---------
 vta/tutorials/matrix_multiply.py              |  3 +-
 vta/tutorials/matrix_multiply_opt.py          | 43 +++++------
 9 files changed, 156 insertions(+), 108 deletions(-)

diff --git a/vta/examples/resnet18/pynq/README.md b/vta/examples/resnet18/pynq/README.md
index 1213d94ec6b1..e3bd3d85b1a2 100644
--- a/vta/examples/resnet18/pynq/README.md
+++ b/vta/examples/resnet18/pynq/README.md
@@ -2,12 +2,20 @@
 
 Follow the first two parts of the [Installation Guide](../../../docs/how_to/install.md) to make sure that the VTA python libraries are installed, and that the RPC server is running on the Pynq FPGA dev board.
 
-Simply run the following python script:
+We recommend leaving the `config.json` to its default parameterization (of course you can change the target between "sim" and "pynq").
+
+Simply run the example program. We rely on pickle to store parameters which now only works with python2.
 ```bash
-python imagenet_predict.py
+python2 imagenet_predict.py
 ```
 
-This will run imagenet classification using the ResNet18 architecture on a VTA design that performs 8-bit integer inference, to perform classification on a cat image `cat.jpg`.
+The script will first download the following files into `_data/` directory:
+* `cat.jpg` which provides a test sample for the ImageNet classifier
+* `quantize_graph.json` which describes the NNVM graph of the 8-bit ResNet-18
+* `quantize_params.plk` which contains the network parameters
+* `synset.txt` which contains the ImageNet categories
+
+Next, it will run imagenet classification using the ResNet18 architecture on a VTA design that performs 8-bit integer inference, to perform classification on a cat image `cat.jpg`.
 
 The script reports runtime measured on the Pynq board (in seconds), and the top-1 result category:
 ```
diff --git a/vta/examples/resnet18/pynq/imagenet_predict.py b/vta/examples/resnet18/pynq/imagenet_predict.py
index e5b4a1131461..5ff6cc626d69 100644
--- a/vta/examples/resnet18/pynq/imagenet_predict.py
+++ b/vta/examples/resnet18/pynq/imagenet_predict.py
@@ -1,17 +1,18 @@
 # some standard imports
 import nnvm
 import tvm
-from nnvm.compiler import graph_attr
 import vta
 import vta.testing
 import os
 import numpy as np
-from PIL import Image
 import pickle
 import json
 import logging
-import wget
+
+from PIL import Image
+from nnvm.compiler import graph_attr
 from tvm.contrib import graph_runtime, rpc, util
+from tvm.contrib.download import download
 
 bfactor = 1
 cfactor = 16
@@ -20,15 +21,20 @@
 debug_fpga_only = False
 
 # Obtain model and hardware files (they're too large to check-in)
+# Download them into _data dir
+data_dir = "_data/"
 url = "https://homes.cs.washington.edu/~moreau/media/vta/"
 TEST_FILE = 'cat.jpg'
 CATEG_FILE = 'synset.txt'
 RESNET_GRAPH_FILE = 'quantize_graph.json'
 RESNET_PARAMS_FILE = 'quantize_params.pkl'
+# Create data dir
+if not os.path.exists(data_dir):
+    os.makedirs(data_dir)
+# Download files
 for file in [TEST_FILE, CATEG_FILE, RESNET_GRAPH_FILE, RESNET_PARAMS_FILE]:
     if not os.path.isfile(file):
-        print ("Downloading {}".format(file))
-        wget.download(url+file)
+        download(os.path.join(url, file), os.path.join(data_dir, file))
 
 if verbose:
     logging.basicConfig(level=logging.DEBUG)
@@ -40,8 +46,8 @@
 if vta.get_env().TARGET == "sim":
     target_host = "llvm"
 
-synset = eval(open(os.path.join(CATEG_FILE)).read())
-image = Image.open(os.path.join(TEST_FILE)).resize((224, 224))
+synset = eval(open(os.path.join(data_dir, CATEG_FILE)).read())
+image = Image.open(os.path.join(data_dir, TEST_FILE)).resize((224, 224))
 
 def transform_image(image):
     image = np.array(image) - np.array([123., 117., 104.])
@@ -88,9 +94,9 @@ def mark_nop(graph, conv_layer=-1, skip_conv_layer=()):
 import nnvm.compiler
 np.random.seed(0)
 sym = nnvm.graph.load_json(
-    open(os.path.join(RESNET_GRAPH_FILE)).read())
+    open(os.path.join(data_dir, RESNET_GRAPH_FILE)).read())
 params = pickle.load(
-    open(os.path.join(RESNET_PARAMS_FILE)))
+    open(os.path.join(data_dir, RESNET_PARAMS_FILE), 'rb'))
 
 shape_dict = {"data": x.shape}
 dtype_dict = {"data": 'float32'}
diff --git a/vta/python/vta/bitstream.py b/vta/python/vta/bitstream.py
index dd0a9afdb640..329e741f7d1c 100644
--- a/vta/python/vta/bitstream.py
+++ b/vta/python/vta/bitstream.py
@@ -2,9 +2,16 @@
 from __future__ import absolute_import as _abs
 
 import os
-import urllib
+import sys
+
+from tvm.contrib.download import download
 from .environment import get_env
 
+if sys.version_info >= (3,):
+    import urllib.error as urllib2
+else:
+    import urllib2
+
 # bitstream repo
 BITSTREAM_URL = "https://github.com/uwsaml/vta-distro/raw/master/bitstreams/"
 
@@ -41,15 +48,25 @@ def download_bitstream():
     url = os.path.join(BITSTREAM_URL, env.TARGET)
     url = os.path.join(url, env.HW_VER)
     url = os.path.join(url, env.BITSTREAM)
-    # Check that the bitstream is accessible from the server
-    if urllib.urlopen(url).getcode() == 404:
-        # Raise error - the solution when this happens it to build your own bitstream and add it
-        # to your VTA_CACHE_PATH
-        raise RuntimeError(
-            "Error: {} is not available. It appears that this configuration has not been built."
-            .format(url))
-    else:
-        urllib.urlretrieve(url, bit)
-        success = True
+
+    try:
+        download(url, bit)
+    except urllib2.HTTPError as err:
+        if err.code == 404:
+            raise RuntimeError(
+                # Raise error - the solution when this happens it to build your
+                # own bitstream and add it to your $VTA_CACHE_PATH
+                "{} is not available. It appears that this configuration \
+bistream has not been cached. Please compile your own bitstream (see hardware \
+compilation guide to get Xilinx toolchains setup) and add it to your \
+$VTA_CACHE_PATH. Alternatively edit your config.json back to its default \
+settings. You can see the list of available bitstreams under {}"
+                .format(url, BITSTREAM_URL))
+        else:
+            raise RuntimeError(
+                # This could happen when trying to access the URL behind a proxy
+                "Something went wrong when trying to access {}. Check your \
+internet connection or proxy settings."
+                .format(url))
 
     return success
diff --git a/vta/python/vta/testing/util.py b/vta/python/vta/testing/util.py
index 18701320ffef..67df2bfc2c43 100644
--- a/vta/python/vta/testing/util.py
+++ b/vta/python/vta/testing/util.py
@@ -15,23 +15,34 @@ def run(run_func):
     """
     env = get_env()
 
-    # Run on local sim rpc if necessary
-    local_rpc = int(os.environ.get("VTA_LOCAL_SIM_RPC", "0"))
-    if local_rpc:
-        env.TARGET = "sim"
-        remote = rpc.connect("localhost", local_rpc)
-        run_func(env, remote)
-    else:
-        # run on simulator
-        if simulator.enabled():
-            env.TARGET = "sim"
+    if env.TARGET == "sim":
+
+        # Talk to local RPC if necessary to debug RPC server.
+        # Compile vta on your host with make at the root.
+        # Make sure TARGET is set to "sim" in the config.json file.
+        # Then launch the RPC server on the host machine
+        # with ./apps/pynq_rpc/start_rpc_server.sh
+        # Set your VTA_LOCAL_SIM_RPC environment variable to
+        # the port it's listening to, e.g. 9090
+        local_rpc = int(os.environ.get("VTA_LOCAL_SIM_RPC", "0"))
+        if local_rpc:
+            remote = rpc.connect("localhost", local_rpc)
+            run_func(env, remote)
+        else:
+            # Make sure simulation library exists
+            # If this fails, build vta on host (make)
+            # with TARGET="sim" in the json.config file.
+            assert simulator.enabled()
             run_func(env, rpc.LocalSession())
 
-    # Run on PYNQ if env variable exists
-    host = os.environ.get("VTA_PYNQ_RPC_HOST", None)
-    if host:
-        env.TARGET = "pynq"
-        port = os.environ.get("VTA_PYNQ_RPC_PORT", "9091")
-        port = int(port)
-        remote = rpc.connect(host, port)
-        run_func(env, remote)
+    elif env.TARGET == "pynq":
+
+        # Run on PYNQ if env variable exists
+        host = os.environ.get("VTA_PYNQ_RPC_HOST", None)
+        port = int(os.environ.get("VTA_PYNQ_RPC_PORT", None))
+        if host and port:
+            remote = rpc.connect(host, port)
+            run_func(env, remote)
+        else:
+            raise RuntimeError(
+                "Please set the VTA_PYNQ_RPC_HOST and VTA_PYNQ_RPC_PORT environment variables")
diff --git a/vta/tests/python/integration/test_benchmark_gemm.py b/vta/tests/python/integration/test_benchmark_gemm.py
index 4668acffc45a..7201038b7be0 100644
--- a/vta/tests/python/integration/test_benchmark_gemm.py
+++ b/vta/tests/python/integration/test_benchmark_gemm.py
@@ -18,7 +18,8 @@ def run_gemm_packed(env, remote, batch_size, channel, block):
                      channel // env.BLOCK_OUT,
                      env.BATCH,
                      env.BLOCK_OUT)
-        num_ops = channel * channel * batch_size
+        # To compute number of ops, use a x2 factor for FMA
+        num_ops = 2 * channel * channel * batch_size
 
         ko = tvm.reduce_axis((0, channel // env.BLOCK_IN), name='ko')
         ki = tvm.reduce_axis((0, env.BLOCK_IN), name='ki')
@@ -157,14 +158,14 @@ def run_schedule(load_inp,
 
         def gemm_normal(print_ir):
             mock = env.mock
-            print("----- GEMM GFLOPS End-to-End Test-------")
+            print("----- GEMM GOPS End-to-End Test-------")
             def run_test(header, print_ir, check_correctness):
                 cost = run_schedule(
                     env.dma_copy, env.dma_copy, env.gemm, env.alu, env.dma_copy,
                     print_ir, check_correctness)
                 gops = (num_ops / cost.mean) / float(10 ** 9)
                 print(header)
-                print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
+                print("\tTime cost = %g sec/op, %g GOPS" % (cost.mean, gops))
             with vta.build_config():
                 run_test("NORMAL", print_ir, True)
 
@@ -177,7 +178,7 @@ def run_test(header, print_ir):
                     print_ir, False)
                 gops = (num_ops / cost.mean) / float(10 ** 9)
                 print(header)
-                print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
+                print("\tTime cost = %g sec/op, %g GOPS" % (cost.mean, gops))
             with vta.build_config():
                 run_test("NORMAL", print_ir)
 
@@ -190,7 +191,7 @@ def run_test(header, print_ir):
                     print_ir, False)
                 gops = (num_ops / cost.mean) / float(10 ** 9)
                 print(header)
-                print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
+                print("\tTime cost = %g sec/op, %g GOPS" % (cost.mean, gops))
             with vta.build_config():
                 run_test("NORMAL", print_ir)
             print("")
@@ -204,7 +205,7 @@ def run_test(header, print_ir):
                 gops = (num_ops / cost.mean) / float(10 ** 9)
                 bandwith = (batch_size * channel * env.INP_WIDTH / cost.mean) / float(10 ** 9)
                 print(header)
-                print("\tTime cost = %g sec/op, %g GFLOPS, bandwidth=%g Gbits" % (
+                print("\tTime cost = %g sec/op, %g GOPS, bandwidth=%g Gbits" % (
                     cost.mean, gops, bandwith))
             with vta.build_config():
                 run_test("NORMAL", print_ir)
@@ -219,7 +220,7 @@ def run_test(header, print_ir):
                 gops = (num_ops / cost.mean) / float(10 ** 9)
                 bandwith = (channel * channel * env.WGT_WIDTH / cost.mean) / float(10 ** 9)
                 print(header)
-                print("\tTime cost = %g sec/op, %g GFLOPS, bandwidth=%g Gbits" % (
+                print("\tTime cost = %g sec/op, %g GOPS, bandwidth=%g Gbits" % (
                     cost.mean, gops, bandwith))
             with vta.build_config():
                 run_test("NORMAL", print_ir)
@@ -235,7 +236,7 @@ def run_test(header, print_ir):
                 gops = (num_ops / cost.mean) / float(10 ** 9)
                 bandwith = (batch_size * channel * env.OUT_WIDTH / cost.mean) / float(10 ** 9)
                 print(header)
-                print("\tTime cost = %g sec/op, %g GFLOPS, bandwidth=%g Gbits" % (
+                print("\tTime cost = %g sec/op, %g GOPS, bandwidth=%g Gbits" % (
                     cost.mean, gops, bandwith))
             with vta.build_config():
                 run_test("NORMAL", print_ir)
diff --git a/vta/tests/python/integration/test_benchmark_topi_conv2d.py b/vta/tests/python/integration/test_benchmark_topi_conv2d.py
index 9721487ab4e8..b70fc9cfbf85 100644
--- a/vta/tests/python/integration/test_benchmark_topi_conv2d.py
+++ b/vta/tests/python/integration/test_benchmark_topi_conv2d.py
@@ -42,6 +42,7 @@ def run_vta_conv2d(env, remote, key, batch_size, wl, profile=True):
         res = my_clip(res, 0, 127)
         res = topi.cast(res, "int8")
 
+        # To compute number of ops, use a x2 factor for FMA
         num_ops = 2 * batch_size * fout_height * fout_width * wl.hkernel * wl.wkernel * wl.out_filter * wl.in_filter
 
         a_shape = (batch_size, wl.in_filter, wl.height, wl.width)
@@ -118,7 +119,7 @@ def conv_normal(print_ir):
                     print(vta.lower(s, [data, kernel, bias, res], simple_mode=True))
             cost = verify(s, True)
             gops = (num_ops / cost.mean) / float(10 ** 9)
-            print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
+            print("\tTime cost = %g sec/op, %g GOPS" % (cost.mean, gops))
 
         conv_normal(False)
 
diff --git a/vta/tests/python/pynq/test_benchmark_conv2d.py b/vta/tests/python/pynq/test_benchmark_conv2d.py
index e02448a8d993..5a26acb8b432 100644
--- a/vta/tests/python/pynq/test_benchmark_conv2d.py
+++ b/vta/tests/python/pynq/test_benchmark_conv2d.py
@@ -46,10 +46,10 @@ def get_insn_count(layer, sched):
     env = vta.get_env()
     b, h, w, ci, co = sched
     b_factor = b
-    h_factor = layer.height / h
-    w_factor = layer.width / w
-    ci_factor = int(np.ceil(float(layer.in_filter) / (ci * env.BLOCK_IN)))
-    co_factor = int(np.ceil(float(layer.out_filter) / (co * env.BLOCK_OUT)))
+    h_factor = layer.height // h
+    w_factor = layer.width // w
+    ci_factor = layer.in_filter // (ci * env.BLOCK_IN)
+    co_factor = layer.out_filter // (co * env.BLOCK_OUT)
     input_xfers = b_factor * h_factor * w_factor * co_factor * ci_factor
     weight_xfers = b_factor * h_factor * w_factor * co_factor * ci_factor
     output_xfers = b_factor * h_factor * w_factor * co_factor
@@ -69,11 +69,11 @@ def find_factors(n):
                 factors.append(i)
         return factors
     # Scheduling exploration
-    batch_factors = find_factors(int(np.ceil(float(layer.batch) / env.BATCH)))
-    height_factors = find_factors(layer.height / layer.hstride)
-    width_factors = find_factors(layer.width / layer.wstride)
-    cin_factors = find_factors(int(np.ceil(float(layer.in_filter) / env.BLOCK_IN)))
-    cout_factors = find_factors(int(np.ceil(float(layer.out_filter) / env.BLOCK_OUT)))
+    batch_factors = find_factors(layer.batch // env.BATCH)
+    height_factors = find_factors(layer.height // layer.hstride)
+    width_factors = find_factors(layer.width // layer.wstride)
+    cin_factors = find_factors(layer.in_filter // env.BLOCK_IN)
+    cout_factors = find_factors(layer.out_filter // env.BLOCK_OUT)
     ht_factors = [1, 2]
     cot_factors = [1, 2]
     # Explore schedules
@@ -124,7 +124,7 @@ def find_factors(n):
                         if input_tile_elems*input_elem_size_b <= input_brams_capacity_b/(cot*ht) and \
                            weight_tile_elems*weight_elem_size_b <= weight_brams_capacity_b and \
                            output_tile_elems*output_elem_size_b <= output_brams_capacity_b/(cot*ht) and \
-                           insn_count <= env.MAX_XFER / 16 and \
+                           insn_count <= env.MAX_XFER // 16 and \
                            h > 2 and w > 2:
                             schedule = Schedule(oc_factor=co, ko_factor=ci, h_factor=h,
                                                 w_factor=w, oc_nthread=cot, h_nthread=ht)
@@ -154,19 +154,19 @@ def get_data_movementB(sched, layer):
     weight_tile_elems = layer.hkernel * layer.wkernel * ci
     output_tile_elems = b * h * w * co
     # Derive factors
-    b_factor = int(np.ceil(float(layer.batch) / (b * env.BATCH)))
-    h_factor = (layer.height / layer.hstride) / h
-    w_factor = (layer.width / layer.wstride) / w
-    ci_factor = int(np.ceil(float(layer.in_filter) / (ci * env.BLOCK_IN)))
-    co_factor = int(np.ceil(float(layer.out_filter) / (co * env.BLOCK_OUT)))
+    b_factor = layer.batch // (b * env.BATCH)
+    h_factor = (layer.height // layer.hstride) // h
+    w_factor = (layer.width // layer.wstride) // w
+    ci_factor = int(np.ceil(float(layer.in_filter) // (ci * env.BLOCK_IN)))
+    co_factor = int(np.ceil(float(layer.out_filter) // (co * env.BLOCK_OUT)))
     # Derive transfers
     input_xfers = b_factor * h_factor * w_factor * co_factor * ci_factor
     weight_xfers = b_factor * h_factor * w_factor * co_factor * ci_factor
     output_xfers = b_factor * h_factor * w_factor * co_factor
     # Compute total transfer sizes
-    input_xfer_B = input_tile_elems * input_xfers * input_elem_size_b / 8
-    weight_xfer_B = weight_tile_elems * weight_xfers * weight_elem_size_b / 8
-    output_xfer_B = output_tile_elems * output_xfers * output_elem_size_b / 8
+    input_xfer_B = input_tile_elems * input_xfers * input_elem_size_b // 8
+    weight_xfer_B = weight_tile_elems * weight_xfers * weight_elem_size_b // 8
+    output_xfer_B = output_tile_elems * output_xfers * output_elem_size_b // 8
     total_xfer_B = input_xfer_B + weight_xfer_B + output_xfer_B
     return total_xfer_B
 
@@ -175,13 +175,13 @@ def test_conv2d_chwv(layer, key, batch_size, wl, sched, log_frame, profile=True)
     assert batch_size % env.BATCH == 0
     assert wl.in_filter % env.BLOCK_IN == 0
     assert wl.out_filter % env.BLOCK_OUT == 0
-    data_shape = (batch_size//env.BATCH, wl.in_filter//env.BLOCK_IN,
+    data_shape = (batch_size // env.BATCH, wl.in_filter // env.BLOCK_IN,
                   wl.height, wl.width, env.BATCH, env.BLOCK_IN)
-    kernel_shape = (wl.out_filter//env.BLOCK_OUT, wl.in_filter//env.BLOCK_IN,
+    kernel_shape = (wl.out_filter // env.BLOCK_OUT, wl.in_filter // env.BLOCK_IN,
                     wl.hkernel, wl.wkernel, env.BLOCK_OUT, env.BLOCK_IN)
     fout_height = (wl.height + 2 * wl.hpad - wl.hkernel) // wl.hstride + 1
     fout_width = (wl.width + 2 * wl.wpad - wl.wkernel) // wl.wstride + 1
-    res_shape = (batch_size//env.BATCH, wl.out_filter//env.BLOCK_OUT,
+    res_shape = (batch_size // env.BATCH, wl.out_filter // env.BLOCK_OUT,
                  fout_height, fout_width, env.BATCH, env.BLOCK_OUT)
     data = tvm.placeholder(data_shape, name="data", dtype=env.inp_dtype)
     kernel = tvm.placeholder(kernel_shape, name="kernel", dtype=env.wgt_dtype)
@@ -201,7 +201,8 @@ def test_conv2d_chwv(layer, key, batch_size, wl, sched, log_frame, profile=True)
             kernel_buf[co, ko, di, dj, ci, ki].astype(env.acc_dtype),
         axis=[ko, di, dj, ki]),
         name="res_cnv")
-    res_shf = tvm.compute(res_shape, lambda *i: res_cnv(*i) >> 8, name="res_shf")
+    # res_shf = tvm.compute(res_shape, lambda *i: res_cnv(*i) >> 8, name="res_shf")
+    res_shf = topi.right_shift(res_cnv, 8)
     res = tvm.compute(res_shape, lambda *i: res_shf(*i).astype(env.inp_dtype), name="res")
     num_ops = batch_size * fout_height * fout_width * wl.hkernel * wl.wkernel * wl.out_filter * wl.in_filter
     total_xfer_B = get_data_movementB(sched, wl)
@@ -310,7 +311,7 @@ def run_test(header, print_ir, check_correctness):
                 print_ir, check_correctness)
             gops = (num_ops / cost.mean) / float(10 ** 9)
             print(header)
-            print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
+            print("\tTime cost = %g sec/op, %g GOPS" % (cost.mean, gops))
             log_frame["key"].append(key)
             log_frame["layer"].append(layer)
             log_frame["total-data"].append(total_xfer_B)
@@ -347,7 +348,7 @@ def run_test(header, print_ir):
                 print_ir, False)
             gops = (num_ops / cost.mean) / float(10 ** 9)
             print(header)
-            print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
+            print("\tTime cost = %g sec/op, %g GOPS" % (cost.mean, gops))
             log_frame["skip-alu-gops"].append(gops)
             log_frame["skip-alu-cost"].append(cost.mean)
 
@@ -365,7 +366,7 @@ def run_test(header, print_ir):
                 print_ir, False)
             gops = (num_ops / cost.mean) / float(10 ** 9)
             print(header)
-            print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
+            print("\tTime cost = %g sec/op, %g GOPS" % (cost.mean, gops))
             log_frame["gemm-gops"].append(gops)
             log_frame["gemm-cost"].append(cost.mean)
         with vta.build_config():
@@ -382,7 +383,7 @@ def run_test(header, print_ir):
                 print_ir, False)
             gops = (num_ops / cost.mean) / float(10 ** 9)
             print(header)
-            print("\tTime cost = %g sec/op, %g GFLOPS" % (cost.mean, gops))
+            print("\tTime cost = %g sec/op, %g GOPS" % (cost.mean, gops))
             log_frame["alu-gops"].append(gops)
             log_frame["alu-cost"].append(cost.mean)
         with vta.build_config():
@@ -401,7 +402,7 @@ def run_test(header, print_ir):
             bandwith = (batch_size * wl.in_filter * wl.height *
                         wl.width * env.INP_WIDTH / cost.mean) / float(10 ** 9)
             print(header)
-            print("\tTime cost = %g sec/op, %g GFLOPS, bandwith=%g gbits" % (
+            print("\tTime cost = %g sec/op, %g GOPS, bandwith=%g gbits" % (
                 cost.mean, gops, bandwith))
             log_frame["ld-inp-gbits"].append(bandwith)
             log_frame["ld-inp-cost"].append(cost.mean)
@@ -421,7 +422,7 @@ def run_test(header, print_ir):
             bandwith = (wl.out_filter * wl.in_filter * wl.hkernel *
                         wl.wkernel * env.WGT_WIDTH / cost.mean) / float(10 ** 9)
             print(header)
-            print("\tTime cost = %g sec/op, %g GFLOPS, bandwith=%g gbits" % (
+            print("\tTime cost = %g sec/op, %g GOPS, bandwith=%g gbits" % (
                 cost.mean, gops, bandwith))
             log_frame["ld-wgt-gbits"].append(bandwith)
             log_frame["ld-wgt-cost"].append(cost.mean)
@@ -441,7 +442,7 @@ def run_test(header, print_ir):
             bandwith = (batch_size * wl.out_filter * fout_height *
                         fout_width * env.OUT_WIDTH / cost.mean) / float(10 ** 9)
             print(header)
-            print("\tTime cost = %g sec/op, %g GFLOPS, bandwith=%g gbits" % (
+            print("\tTime cost = %g sec/op, %g GOPS, bandwith=%g gbits" % (
                 cost.mean, gops, bandwith))
             log_frame["st-out-gbits"].append(bandwith)
             log_frame["st-out-cost"].append(cost.mean)
@@ -460,7 +461,7 @@ def run_test(header, print_ir):
                 False)
             gops = (num_ops / cost.mean) / float(10 ** 9)
             print(header)
-            print("\tTime cost = %g sec/op, %g GFLOPS" % (
+            print("\tTime cost = %g sec/op, %g GOPS" % (
                 cost.mean, gops))
         with vta.build_config():
             run_test("NORMAL", print_ir)
@@ -532,10 +533,11 @@ def run_test(header, print_ir):
         key = "resnet-cfg[{}-{}]".format(l, plan)
         test_conv2d_chwv(l, key, batch_size, resnet[l], plan, log_frame, profile)
 
-pd.set_option('expand_frame_repr', False)
-log_df = pd.DataFrame()
-for k  in keys:
-    log_df[k] = log_frame[k]
-print(log_df)
 
-log_df.to_csv("conv2d.csv")
+if profile:
+    pd.set_option('expand_frame_repr', False)
+    log_df = pd.DataFrame()
+    for k  in keys:
+        log_df[k] = log_frame[k]
+    print(log_df)
+    log_df.to_csv("conv2d.csv")
diff --git a/vta/tutorials/matrix_multiply.py b/vta/tutorials/matrix_multiply.py
index f9d4d9fc7094..0d1872170a4f 100644
--- a/vta/tutorials/matrix_multiply.py
+++ b/vta/tutorials/matrix_multiply.py
@@ -91,6 +91,7 @@
 #
 #   .. image:: https://raw.githubusercontent.com/uwsaml/web-data/master/vta/tutorial/tensor_core.png
 #        :align: center
+#        :width: 480px
 #
 #   The dimensions of that matrix-matrix multiplication are specified in
 #   the :code:`config.json` configuration file.
@@ -109,6 +110,7 @@
 # 
 #   .. image:: https://raw.githubusercontent.com/uwsaml/web-data/master/vta/tutorial/data_tiling.png
 #        :align: center
+#        :width: 480px
 #
 # We first define the variables :code:`m`, :code:`n`, :code:`o` to represent
 # the shape of the matrix multiplication. These variables are multiplicative
@@ -119,7 +121,6 @@
 # 1 implies that our compute building block is vector-matrix multiply).
 #
 
-
 ######################################################################
 # .. note::
 #
diff --git a/vta/tutorials/matrix_multiply_opt.py b/vta/tutorials/matrix_multiply_opt.py
index 9b62504a7007..4c916c7cddbc 100644
--- a/vta/tutorials/matrix_multiply_opt.py
+++ b/vta/tutorials/matrix_multiply_opt.py
@@ -66,7 +66,7 @@
 # :code:`BATCH`, :code:`BLOCK_IN`, and :code:`BLOCK_OUT` respectively.
 #
 # We've added extra operators to the matrix multiplication that apply
-# shifting and clipping to the output in order to mimic the a fixed-point
+# shifting and clipping to the output in order to mimic a fixed-point
 # matrix multiplication followed by a rectified linear activation.
 # We describe the TVM dataflow graph of the fully connected layer below:
 #
@@ -152,7 +152,7 @@
 # Those include:
 #
 # - Computation blocking
-# - Computation lowering to VTA hardware intrinsics
+# - Lowering to VTA hardware intrinsics
 
 
 # Create TVM schedule
@@ -161,8 +161,8 @@
 print(tvm.lower(s, [data, weight, res], simple_mode=True))
 
 ######################################################################
-# Tiling the Computation
-# ~~~~~~~~~~~~~~~~~~~~~~
+# Blocking the Computation
+# ~~~~~~~~~~~~~~~~~~~~~~~~
 # The matrix multiplication is by default too large for activations or weights
 # to fit on VTA's on-chip buffers all at once.
 # We block the (1, 1024) by (1024, 1024) matrix multiplication into
@@ -180,8 +180,7 @@
 #
 # .. image:: https://raw.githubusercontent.com/uwsaml/web-data/master/vta/tutorial/blocking.png
 #      :align: center
-#      :height: 367px
-#      :width: 387px
+#      :width: 480px
 #
 # .. note::
 # 
@@ -236,7 +235,7 @@
 s[res_max].compute_at(s[res], oc_out)
 s[res_min].compute_at(s[res], oc_out)
 
-# Apply additional loop split along input channel axis
+# Apply additional loop split along reduction axis (input channel)
 b_inn, oc_inn, b_tns, oc_tns = s[res_gemm].op.axis
 ic_out, ic_inn = s[res_gemm].split(ic, i_block)
 
@@ -273,6 +272,8 @@
 s[weight_buf].pragma(s[weight_buf].op.axis[0], env.dma_copy)
 
 # Use DMA copy pragma on SRAM->DRAM operation
+# (this implies that these copies should be performed along b_inn,
+# or result axis 2)
 s[res].pragma(s[res].op.axis[2], env.dma_copy)
 
 ######################################################################
@@ -313,21 +314,21 @@
 # Get the remote device context
 ctx = remote.ext_dev(0)
 
-# Initialize the A and B arrays randomly in the int range of (-128, 128]
-data = np.random.randint(
+# Initialize the data and weight arrays randomly in the int range of (-128, 128]
+data_np = np.random.randint(
     -128, 128, size=(batch_size, in_channels)).astype(data.dtype)
-weight = np.random.randint(
+weight_np = np.random.randint(
     -128, 128, size=(out_channels, in_channels)).astype(weight.dtype)
 
-# Apply packing to the A and B arrays from a 2D to a 4D packed layout
-data_packed = data.reshape(batch_size // env.BATCH,
-                           env.BATCH,
-                           in_channels // env.BLOCK_IN,
-                           env.BLOCK_IN).transpose((0, 2, 1, 3))
-weight_packed = weight.reshape(out_channels // env.BLOCK_OUT,
-                               env.BLOCK_OUT,
-                               in_channels // env.BLOCK_IN,
-                               env.BLOCK_IN).transpose((0, 2, 1, 3))
+# Apply packing to the data and weight arrays from a 2D to a 4D packed layout
+data_packed = data_np.reshape(batch_size // env.BATCH,
+                              env.BATCH,
+                              in_channels // env.BLOCK_IN,
+                              env.BLOCK_IN).transpose((0, 2, 1, 3))
+weight_packed = weight_np.reshape(out_channels // env.BLOCK_OUT,
+                                  env.BLOCK_OUT,
+                                  in_channels // env.BLOCK_IN,
+                                  env.BLOCK_IN).transpose((0, 2, 1, 3))
 
 # Format the input/output arrays with tvm.nd.array to the DLPack standard
 data_nd = tvm.nd.array(data_packed, ctx)
@@ -338,8 +339,8 @@
 f(data_nd, weight_nd, res_nd)
 
 # Verify against numpy implementation
-res_ref = np.dot(data.astype(env.acc_dtype),
-               weight.T.astype(env.acc_dtype))
+res_ref = np.dot(data_np.astype(env.acc_dtype),
+                 weight_np.T.astype(env.acc_dtype))
 res_ref = res_ref >> env.INP_WIDTH
 res_ref = np.clip(res_ref, 0, inp_max)
 res_ref = res_ref.astype(res.dtype)

From 81378935fa03119ddee3f1682a498b2700f1a38e Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 23 Jun 2018 21:51:32 -0700
Subject: [PATCH 852/949] [DOCKER] Cleanup docker image (#50)

---
 vta/tests/ci_build/Dockerfile.cpu                      | 10 ----------
 vta/tests/ci_build/install/ubuntu_install_iverilog.sh  |  8 --------
 vta/tests/ci_build/install/ubuntu_install_java.sh      |  1 -
 vta/tests/ci_build/install/ubuntu_install_opengl.sh    |  4 ----
 .../python/integration/test_benchmark_topi_conv2d.py   |  2 +-
 5 files changed, 1 insertion(+), 24 deletions(-)
 delete mode 100644 vta/tests/ci_build/install/ubuntu_install_iverilog.sh
 delete mode 100644 vta/tests/ci_build/install/ubuntu_install_java.sh
 delete mode 100644 vta/tests/ci_build/install/ubuntu_install_opengl.sh

diff --git a/vta/tests/ci_build/Dockerfile.cpu b/vta/tests/ci_build/Dockerfile.cpu
index 5c7e526f4879..43bc7e4027bf 100644
--- a/vta/tests/ci_build/Dockerfile.cpu
+++ b/vta/tests/ci_build/Dockerfile.cpu
@@ -9,22 +9,12 @@ RUN bash /install/ubuntu_install_core.sh
 COPY install/ubuntu_install_python.sh /install/ubuntu_install_python.sh
 RUN bash /install/ubuntu_install_python.sh
 
-COPY install/ubuntu_install_iverilog.sh /install/ubuntu_install_iverilog.sh
-RUN bash /install/ubuntu_install_iverilog.sh
-
 COPY install/ubuntu_install_python_package.sh /install/ubuntu_install_python_package.sh
 RUN bash /install/ubuntu_install_python_package.sh
 
-COPY install/ubuntu_install_java.sh /install/ubuntu_install_java.sh
-RUN bash /install/ubuntu_install_java.sh
-
 COPY install/ubuntu_install_llvm.sh /install/ubuntu_install_llvm.sh
 RUN bash /install/ubuntu_install_llvm.sh
 
-COPY install/ubuntu_install_opengl.sh /install/ubuntu_install_opengl.sh
-RUN bash /install/ubuntu_install_opengl.sh
-
-
 COPY install/ubuntu_install_sphinx.sh /install/ubuntu_install_sphinx.sh
 RUN bash /install/ubuntu_install_sphinx.sh
 
diff --git a/vta/tests/ci_build/install/ubuntu_install_iverilog.sh b/vta/tests/ci_build/install/ubuntu_install_iverilog.sh
deleted file mode 100644
index bf7a0001dc70..000000000000
--- a/vta/tests/ci_build/install/ubuntu_install_iverilog.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-apt-get install -y --no-install-recommends --force-yes make bison flex
-wget ftp://icarus.com/pub/eda/verilog/v10/verilog-10.1.tar.gz
-tar xf verilog-10.1.tar.gz
-cd verilog-10.1
-./configure --prefix=/usr
-make install -j8
-cd ..
-rm -rf verilog-10.1 verilog-10.1.tar.gz
diff --git a/vta/tests/ci_build/install/ubuntu_install_java.sh b/vta/tests/ci_build/install/ubuntu_install_java.sh
deleted file mode 100644
index ba07b2985efb..000000000000
--- a/vta/tests/ci_build/install/ubuntu_install_java.sh
+++ /dev/null
@@ -1 +0,0 @@
-apt-get update && apt-get install -y openjdk-8-jdk maven
diff --git a/vta/tests/ci_build/install/ubuntu_install_opengl.sh b/vta/tests/ci_build/install/ubuntu_install_opengl.sh
deleted file mode 100644
index f8be6e351581..000000000000
--- a/vta/tests/ci_build/install/ubuntu_install_opengl.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-apt-get update --fix-missing
-
-apt-get install -y --no-install-recommends --force-yes \
-        libgl1-mesa-dev libglfw3-dev
\ No newline at end of file
diff --git a/vta/tests/python/integration/test_benchmark_topi_conv2d.py b/vta/tests/python/integration/test_benchmark_topi_conv2d.py
index b70fc9cfbf85..24d9968dc31a 100644
--- a/vta/tests/python/integration/test_benchmark_topi_conv2d.py
+++ b/vta/tests/python/integration/test_benchmark_topi_conv2d.py
@@ -38,7 +38,7 @@ def run_vta_conv2d(env, remote, key, batch_size, wl, profile=True):
         res_conv = vta.top.packed_conv2d(
             data, kernel, padding=(wl.hpad, wl.wpad), strides=(wl.hstride, wl.wstride))
         res = topi.right_shift(res_conv, 8)
-        res = topi.broadcast_add(res, bias)
+        res = topi.add(res, bias)
         res = my_clip(res, 0, 127)
         res = topi.cast(res, "int8")
 

From 2973392fcdecaa11c42d8601913a44b3a87b816c Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@cs.washington.edu>
Date: Thu, 28 Jun 2018 13:28:03 -0700
Subject: [PATCH 853/949] [DOC, TVM] ResNet tutorial, updated TVM (#51)

---
 .../resnet18/pynq/imagenet_predict.py         |   7 +-
 .../integration/test_benchmark_topi_conv2d.py |   2 +-
 vta/tutorials/convolution_opt.py              | 427 ++++++++++++++++++
 3 files changed, 429 insertions(+), 7 deletions(-)
 create mode 100644 vta/tutorials/convolution_opt.py

diff --git a/vta/examples/resnet18/pynq/imagenet_predict.py b/vta/examples/resnet18/pynq/imagenet_predict.py
index 5ff6cc626d69..5fce83231c5c 100644
--- a/vta/examples/resnet18/pynq/imagenet_predict.py
+++ b/vta/examples/resnet18/pynq/imagenet_predict.py
@@ -20,7 +20,7 @@
 # only run fpga component, mark non-conv ops as nop
 debug_fpga_only = False
 
-# Obtain model and hardware files (they're too large to check-in)
+# Obtain model files (they're too large to check-in)
 # Download them into _data dir
 data_dir = "_data/"
 url = "https://homes.cs.washington.edu/~moreau/media/vta/"
@@ -115,11 +115,6 @@ def mark_nop(graph, conv_layer=-1, skip_conv_layer=()):
 if target.device_name == "vta":
     sym = vta.graph.pack(sym, shape_dict, bfactor, cfactor)
 
-graph_attr.set_shape_inputs(sym, shape_dict)
-sym = sym.apply("InferShape")
-graph_attr.set_dtype_inputs(sym, dtype_dict)
-sym = sym.apply("InferType")
-
 with nnvm.compiler.build_config(opt_level=3):
     if target.device_name != "vta":
         graph, lib, params = nnvm.compiler.build(
diff --git a/vta/tests/python/integration/test_benchmark_topi_conv2d.py b/vta/tests/python/integration/test_benchmark_topi_conv2d.py
index 24d9968dc31a..b70fc9cfbf85 100644
--- a/vta/tests/python/integration/test_benchmark_topi_conv2d.py
+++ b/vta/tests/python/integration/test_benchmark_topi_conv2d.py
@@ -38,7 +38,7 @@ def run_vta_conv2d(env, remote, key, batch_size, wl, profile=True):
         res_conv = vta.top.packed_conv2d(
             data, kernel, padding=(wl.hpad, wl.wpad), strides=(wl.hstride, wl.wstride))
         res = topi.right_shift(res_conv, 8)
-        res = topi.add(res, bias)
+        res = topi.broadcast_add(res, bias)
         res = my_clip(res, 0, 127)
         res = topi.cast(res, "int8")
 
diff --git a/vta/tutorials/convolution_opt.py b/vta/tutorials/convolution_opt.py
new file mode 100644
index 000000000000..62efe8c0156b
--- /dev/null
+++ b/vta/tutorials/convolution_opt.py
@@ -0,0 +1,427 @@
+"""
+2D Convolution Optimization
+===========================
+**Author**: `Thierry Moreau <https://homes.cs.washington.edu/~moreau/>`_
+
+This tutorial provides an overview on how to use TVM to map a 2D convolution
+workload efficiently on the VTA design.
+We recommend covering the :ref:`mat-mult-opt` tutorial first.
+
+2D convolution is dominant in most computer vision deep neural networks.
+In this tutorial, we will demonstrate TVM schedule optimizations to map
+2D convolution operators in NCHW layout onto VTA.
+We also introduce the notion of latency hiding, which allows us to
+maximize VTA's compute and memory resource utilization.
+"""
+
+######################################################################
+# RPC Setup
+# ---------
+# We start by programming the Pynq's FPGA and building its RPC runtime.
+
+from __future__ import absolute_import, print_function
+
+import os
+import tvm
+import vta
+import numpy as np
+
+from tvm.contrib import rpc, util
+from vta.testing import simulator
+
+# Load VTA parameters from the config.json file
+env = vta.get_env()
+
+# We read the Pynq RPC host IP address and port number from the OS environment
+host = os.environ.get("VTA_PYNQ_RPC_HOST", "192.168.2.99")
+port = int(os.environ.get("VTA_PYNQ_RPC_PORT", "9091"))
+
+# We configure both the bitstream and the runtime system on the Pynq
+# to match the VTA configuration specified by the config.json file.
+if env.TARGET == "pynq":
+
+    # Make sure that TVM was compiled with RPC=1
+    assert tvm.module.enabled("rpc")
+    remote = rpc.connect(host, port)
+
+    # Reconfigure the JIT runtime
+    vta.reconfig_runtime(remote)
+
+    # Program the FPGA with a pre-compiled VTA bitstream.
+    # You can program the FPGA with your own custom bitstream
+    # by passing the path to the bitstream file instead of None.
+    vta.program_fpga(remote, bitstream=None)
+
+# In simulation mode, host the RPC server locally.
+elif env.TARGET == "sim":
+    remote = rpc.LocalSession()
+
+######################################################################
+# Computation Declaration
+# -----------------------
+# As a first step, we need to describe our 2D convolution computation
+# in NCHW format.
+#
+# We define the 2D convolution shape by the batch size,
+# spatial dimensions, input channels, output channels, kernel dimensions,
+# kernel dimensions, padding dimensions, and stride dimensions.
+#
+# We pick the shape of the 9th convolutional layer of the ResNet-18
+# architecture as our convolution workload parameters.
+#
+# We've added extra operators to the 2D convolution that apply
+# shifting and clipping to the output in order to mimic a fixed-point
+# convolution followed by a rectified linear activation.
+# We describe the TVM dataflow graph of the 2D convolution layer below:
+#
+# .. image:: https://raw.githubusercontent.com/uwsaml/web-data/master/vta/tutorial/conv2d_dataflow.png
+#      :align: center
+#
+# This computation is intentionally too large to fit onto VTA's on-chip
+# buffers all at once. Therefore in the scheduling phase we'll
+# rely on computation blocking strategies to break the computation down into
+# manageable chunks.
+#
+# .. note::
+#
+#   *Spatial padding*
+#
+#   Note that we'll need to import the TOPI library to apply spatial padding
+#   on the input feature map tensor.
+#   Spatial padding facilitates blocking in the context of 2D convolutions
+#   due to the fact that the same (x, y) spatial location of the input
+#   feature map of any given layer is read more than once if the convolution
+#   kernel window size is greater than one.
+#   On CPUs, and GPUs, one way to increase efficiency of memory accesses
+#   when parallelizing work is spatial packing, which requires data re-layout.
+#   VTA load DMA engine can insert padding automatically so that the original
+#   input feature map does not have to be re-packed in memory.
+#
+#   We show the effect of VTA's on the fly spatial padding when data is being
+#   loaded from DRAM into VTA's SRAM, following a 2D strided and padded memory
+#   read.
+#
+#   .. image:: https://raw.githubusercontent.com/uwsaml/web-data/master/vta/tutorial/padding.png
+#        :align: center
+#        :width: 480px
+
+import topi
+
+# 2D convolution layer dimensions taken from ResNet-18 architecture
+# (9th convolutional layer)
+batch_size = 1
+height = 14
+width = 14
+in_channels = 256
+out_channels = 256
+kernel_h = 3
+kernel_w = 3
+pad_h = 1
+pad_w = 1
+stride_h = 1
+stride_w = 1
+assert batch_size % env.BATCH == 0
+assert in_channels % env.BLOCK_IN == 0
+assert out_channels % env.BLOCK_OUT == 0
+
+# Input feature map: (N, IC, H, W, n, ic)
+data_shape = (batch_size // env.BATCH,
+              in_channels // env.BLOCK_IN,
+              height,
+              width,
+              env.BATCH,
+              env.BLOCK_IN)
+# Kernel: (OC, IC, H, W, oc, ic)
+kernel_shape = (out_channels // env.BLOCK_OUT,
+                in_channels // env.BLOCK_IN,
+                kernel_h,
+                kernel_w,
+                env.BLOCK_OUT,
+                env.BLOCK_IN)
+# Derive output feature map dimensions
+fout_height = (height + 2 * pad_h - kernel_h) // stride_h + 1
+fout_width = (width + 2 * pad_w - kernel_w) // stride_w + 1
+# Output feature map: (N, OC, H, W, n, oc)
+output_shape = (batch_size // env.BATCH,
+                out_channels // env.BLOCK_OUT,
+                fout_height,
+                fout_width,
+                env.BATCH,
+                env.BLOCK_OUT)
+
+# Convolution reduction axes
+dy = tvm.reduce_axis((0, kernel_h), name='dy')
+dx = tvm.reduce_axis((0, kernel_w), name='dx')
+ic = tvm.reduce_axis((0, in_channels // env.BLOCK_IN), name='ic')
+ic_tns = tvm.reduce_axis((0, env.BLOCK_IN), name='ic_tns')
+
+# Input placeholder tensors
+data = tvm.placeholder(data_shape,
+                       name="data",
+                       dtype=env.inp_dtype)
+kernel = tvm.placeholder(kernel_shape,
+                         name="kernel",
+                         dtype=env.wgt_dtype)
+
+# Copy buffers:
+#   Apply spatial padding to input feature map
+data_buf = topi.nn.pad(data,
+                       [0, 0, pad_h, pad_w, 0, 0],
+                       name="data_buf")
+kernel_buf = tvm.compute(kernel_shape, lambda *i: kernel(*i), "kernel_buf")
+
+# Declare 2D convolution
+res_conv = tvm.compute(
+    output_shape,
+    lambda bo, co, i, j, bi, ci: tvm.sum(
+      data_buf[bo, ic, i*stride_h+dy, j*stride_w+dx, bi, ic_tns].astype(env.acc_dtype) *
+      kernel_buf[co, ic, dy, dx, ci, ic_tns].astype(env.acc_dtype),
+    axis=[ic, dy, dx, ic_tns]),
+    name="res_conv")
+
+# Add shift stage for fix-point normalization
+res_shr = tvm.compute(output_shape,
+                      lambda *i: res_conv(*i) >> 8,
+                      name="res_shr")
+
+# Apply clipping between (0, input max value)
+inp_max = (1 << (env.INP_WIDTH - 1)) - 1
+res_max = tvm.compute(output_shape,
+                      lambda *i: tvm.max(res_shr(*i), 0),
+                      "res_max")
+res_min = tvm.compute(output_shape,
+                      lambda *i: tvm.min(res_max(*i), inp_max),
+                      "res_min")
+
+# Result Tensor
+res = tvm.compute(output_shape,
+                  lambda *i: res_min(*i).astype(env.inp_dtype),
+                  name="res")
+
+
+######################################################################
+# Scheduling the Computation
+# --------------------------
+# We'll look at a set of schedule transformations necessary to map the
+# 2D convolution onto VTA in an efficient fashion.
+# Those include:
+#
+# - Computation blocking
+# - Virtual threading to increase compute utilization
+# - Lowering to VTA hardware intrinsics
+
+# Create TVM schedule
+s = tvm.create_schedule(res.op)
+# Let's look at the default TVM schedule
+print(tvm.lower(s, [data, kernel, res], simple_mode=True))
+
+######################################################################
+# Blocking the Computation
+# ~~~~~~~~~~~~~~~~~~~~~~~~
+# The 2D convolution is by default too large for activations or kernel weights
+# to fit on VTA's on-chip buffers all at once.
+# We apply blocking along input channels, output channels, and along
+# the height spatial dimensions.
+# We don't apply blocking along the width spatial dimension since it's
+# the innermost dimension in the NCHW layout (and consequently to increase
+# locality, it's best not to block along the innermost dimension).
+
+# Let's define tiling sizes
+b_block = 1 // env.BATCH
+oc_block = 128 // env.BLOCK_OUT
+ic_block = 16 // env.BLOCK_IN
+h_block = 7
+w_block = 14
+
+# Tile the output tensor along the spatial and output channel dimensions
+# (since by default we are doing single batch inference, the split along
+#  the batch dimension has no effect)
+b, oc, y, x, b_tns, oc_tns = s[res].op.axis
+b_out, b_inn = s[res].split(b, factor=b_block)
+oc_out, oc_inn = s[res].split(oc, factor=oc_block)
+y_out, y_inn = s[res].split(y, factor=h_block)
+x_out, x_inn = s[res].split(x, factor=w_block)
+s[res].reorder(b_out, oc_out, y_out, x_out, b_inn, oc_inn, y_inn, x_inn, b_tns, oc_tns)
+
+# Move intermediate computation into each output compute tile
+s[res_conv].compute_at(s[res], x_out)
+s[res_shr].compute_at(s[res], x_out)
+s[res_max].compute_at(s[res], x_out)
+s[res_min].compute_at(s[res], x_out)
+
+# Apply additional loop split along reduction axis (input channel)
+b_inn, oc_inn, y_inn, x_inn, b_tns, oc_tns = s[res_conv].op.axis
+ic_out, ic_inn = s[res_conv].split(ic, factor=ic_block)
+
+# Reorder axes.
+# 1) Group the VTA tensor axes in the inner most position: b_tns, oc_tns, ic_tns
+#    to allow TVM to tensorize.
+# 2) We move the ic_out axis all the way out of the convolution loop to block
+#    along the reduction axis.
+# 3) Now we re-order the block axes: b_inn, oc_inn, y_inn, x_inn, ic_inn, dy, dx.
+#    VTA runtime/hardware requires us to write to a different output feature map
+#    location for every VTA tensor operation.
+#    This restriction requires us to order one of oc_inn, y_inn or x_inn right
+#    before b_tns, since they all affect output feature map indexing.
+#    Therefore, we choose to bring x_inn inside as shown below.
+s[res_conv].reorder(ic_out, b_inn, oc_inn, y_inn, ic_inn, dy, dx, x_inn, b_tns, oc_tns, ic_tns)
+
+######################################################################
+# Virtual Threading
+# ~~~~~~~~~~~~~~~~~
+# Virtual threading is a mechanism that increases task-level pipeline
+# parallelism in the VTA hardware design.
+# Put it another way, it increases compute resource utilization by hiding
+# memory access latency.
+#
+# In the implementation below, virtual threading distributes work across two
+# threads split along the output channel axis.
+# We show how work is split when computing the 2D convolution in the figure
+# below.
+#
+# .. image:: https://raw.githubusercontent.com/uwsaml/web-data/master/vta/tutorial/virtual_threading.png
+#      :align: center
+#      :width: 480px
+
+# VTA only supports 2 virtual threads
+v_threads = 2
+
+# Perform virtual thread split along output channel outer axis
+_, tx = s[res].split(oc_out, factor=v_threads)
+s[res].reorder(tx, b_out)
+s[res].bind(tx, tvm.thread_axis("cthread"))
+
+# Let's look at the current TVM schedule after blocking and virtual threading
+print(tvm.lower(s, [data, kernel, res], simple_mode=True))
+
+######################################################################
+# Lowering Copies to DMA Transfers
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+# Next we set the buffer scopes to the corresponding on-chip VTA SRAM buffers.
+# We move the load loops into the 2D convolution computation loop to stage
+# memory loads such that they fit in the on-chip SRAM buffers.
+# Finally we annotate the load/store loop outer axes with the DMA copy pragma
+# to perform bulk memory transfers on VTA.
+
+# Set scope of SRAM buffers
+s[data_buf].set_scope(env.inp_scope)
+s[kernel_buf].set_scope(env.wgt_scope)
+s[res_conv].set_scope(env.acc_scope)
+s[res_shr].set_scope(env.acc_scope)
+s[res_min].set_scope(env.acc_scope)
+s[res_max].set_scope(env.acc_scope)
+
+# Block data and kernel cache reads
+s[data_buf].compute_at(s[res_conv], ic_out)
+s[kernel_buf].compute_at(s[res_conv], ic_out)
+
+# Use DMA copy pragma on DRAM->SRAM operations
+s[data_buf].pragma(s[data_buf].op.axis[0], env.dma_copy)
+s[kernel_buf].pragma(s[kernel_buf].op.axis[0], env.dma_copy)
+
+# Use DMA copy pragma on SRAM->DRAM operation in each result block
+# (this implies that these copies should be performed along b_inn,
+# or result axis 4)
+s[res].pragma(s[res].op.axis[4], env.dma_copy)
+
+######################################################################
+# Lowering Computation to VTA Compute Intrinsics
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+# The last phase is to lower the computation loops down to VTA hardware
+# intrinsics by mapping the 2D convolution to tensor intrinsics,
+# and mapping the shift, and clipping computation to the vector ALU.
+
+# Apply tensorization over the batch tensor tile axis
+s[res_conv].tensorize(b_tns, env.gemm)
+
+# Add an ALU pragma over the shift and clipping operations
+s[res_shr].pragma(s[res_shr].op.axis[0], env.alu)
+s[res_min].pragma(s[res_min].op.axis[0], env.alu)
+s[res_max].pragma(s[res_max].op.axis[0], env.alu)
+
+# Let's look at the final lowered TVM schedule after lowering memory
+# loads/stores down to DMA copy intrinsics, and the computation down to
+# VTA compute intrinsics.
+print(vta.lower(s, [data, kernel, res], simple_mode=True))
+
+######################################################################
+# TVM Compilation and Verification
+# --------------------------------
+# After specifying the schedule, we can compile it into a TVM function.
+# We save the module so we can send it over RPC.
+# We run the function and verify it against a numpy implementation to
+# ensure correctness.
+
+# This library facilitates 2D convolution testing
+from topi.testing import conv2d_nchw_python
+
+# Compile the TVM module
+my_conv = vta.build(s, [data, kernel, res], "ext_dev", env.target_host, name="my_conv")
+temp = util.tempdir()
+my_conv.save(temp.relpath("conv2d.o"))
+remote.upload(temp.relpath("conv2d.o"))
+f = remote.load_module("conv2d.o")
+
+# Get the remote device context
+ctx = remote.ext_dev(0)
+
+# Initialize the data and kernel arrays randomly in the int range
+# of (-128, 128] in NCHW layout
+data_np = np.random.randint(
+    -128, 128,
+    size=(batch_size, in_channels, height, width)).astype(data.dtype)
+kernel_np = np.random.randint(
+    -128, 128,
+    size=(out_channels, in_channels, kernel_h, kernel_w)).astype(kernel.dtype)
+
+# Apply packing to the data and kernel arrays from a 2D NCHW
+# to a 4D NCHWnc packed layout
+data_packed = data_np.reshape(batch_size // env.BATCH,
+                              env.BATCH,
+                              in_channels // env.BLOCK_IN,
+                              env.BLOCK_IN,
+                              height,
+                              width).transpose((0, 2, 4, 5, 1, 3))
+
+kernel_packed = kernel_np.reshape(out_channels // env.BLOCK_OUT,
+                                  env.BLOCK_OUT,
+                                  in_channels // env.BLOCK_IN,
+                                  env.BLOCK_IN,
+                                  kernel_h,
+                                  kernel_w).transpose((0, 2, 4, 5, 1, 3))
+
+# Format the input/output arrays with tvm.nd.array to the DLPack standard
+data_nd = tvm.nd.array(data_packed, ctx)
+kernel_nd = tvm.nd.array(kernel_packed, ctx)
+res_nd = tvm.nd.array(np.zeros(output_shape).astype(res.dtype), ctx)
+
+# Invoke the module to perform the computation
+f(data_nd, kernel_nd, res_nd)
+
+# Verify against numpy implementation
+res_ref = conv2d_nchw_python(data_np.astype(env.acc_dtype),
+                            kernel_np.astype(env.acc_dtype),
+                            (stride_h, stride_w),
+                            (pad_h, pad_w)).astype(env.acc_dtype)
+res_ref = res_ref >> env.INP_WIDTH
+res_ref = np.clip(res_ref, 0, inp_max)
+res_ref = res_ref.astype(res.dtype)
+res_ref = res_ref.reshape((batch_size // env.BATCH,
+                           env.BATCH,
+                           out_channels // env.BLOCK_OUT,
+                           env.BLOCK_OUT,
+                           fout_height,
+                           fout_width)).transpose((0, 2, 4, 5, 1, 3))
+np.testing.assert_allclose(res_ref, res_nd.asnumpy())
+print("Successful 2D convolution test!")
+
+######################################################################
+# Summary
+# -------
+# This tutorial demonstrates how TVM scheduling primitives can be used to
+# lower 2D convolution onto hardware accelerator intrinsics, making
+# use of hardware specific optimizations, such as latency hiding with
+# virtual threading.
+#
+
+

From cc3205726379e5614676b1f3c6deda1ed56ce049 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 2 Jul 2018 16:52:18 -0700
Subject: [PATCH 854/949] [TVM] Upgrade TVM Support

---
 vta/docs/how_to/install.md                    | 34 ++-------
 .../resnet18/pynq/imagenet_predict.py         | 14 ++--
 vta/python/vta/graph.py                       | 71 ++++---------------
 vta/python/vta/top/vta_conv2d.py              | 45 +++++++-----
 .../integration/test_benchmark_topi_conv2d.py |  2 +-
 5 files changed, 54 insertions(+), 112 deletions(-)

diff --git a/vta/docs/how_to/install.md b/vta/docs/how_to/install.md
index e2ccf63e9080..816dd6e307ea 100644
--- a/vta/docs/how_to/install.md
+++ b/vta/docs/how_to/install.md
@@ -59,31 +59,7 @@ In the 'config.mk' file, make sure that:
 
 For the *Python Package Installation*, we recommend updating your `~/.bashrc` file to extend your `PYTHONPATH` with the TVM Python libraries.
 ```bash
-export PYTHONPATH=<tvm root>/python:<tvm root>/topi/python:${PYTHONPATH}
-```
-
-#### NNVM Installation
-
-Clone the NNVM repository from `tqchen` in the directory of your choosing:
-```bash
-git clone git@github.com:tqchen/nnvm.git --recursive
-```
-
-To run this example, we rely on a special branch of NNVM `qt`:
-```bash
-cd <nnvm root>
-git checkout qt
-```
-
-Launch the compilation, this takes about a minute on two threads.
-```bash
-cd <nnvm root>
-make -j2
-```
-
-Finally update your `~/.bashrc` file to include the NNVM python libraries in your `PYTHONPATH`:
-```bash
-export PYTHONPATH=<nnvm root>/python:${PYTHONPATH}
+export PYTHONPATH=<tvm root>/python:<tvm root>/topi/python:<tvm root>/nnvm/python:${PYTHONPATH}
 ```
 
 #### MxNet Installation
@@ -236,7 +212,7 @@ This time again, we will run the 2D convolution testbench. But beforehand, we'll
 * Runtime building on the Pynq, which needs to be run everytime the `config.json` configuration is modified. This ensures that the VTA software runtime that generates the accelerator's executable via just-in-time (JIT) compilation matches the specifications of the VTA design that is programmed on the FPGA. The build process takes about 30 seconds to complete.
 
 ```bash
-python tests/python/pynq/test_program_rpc.py 
+python tests/python/pynq/test_program_rpc.py
 ```
 
 > Tip: You can track progress of the FPGA programming and the runtime rebuilding steps by looking at the RPC server's logging messages in your Pynq `ssh` session.
@@ -244,7 +220,7 @@ python tests/python/pynq/test_program_rpc.py
 We are now ready to run the 2D convolution testbench for the ResNet-15 workload in hardware.
 
 ```bash
-python tests/python/pynq/test_benchmark_conv2d.py 
+python tests/python/pynq/test_benchmark_conv2d.py
 ```
 
 The performance metrics measured on the Pynq board will be reported for each convolutional layer.
@@ -280,7 +256,7 @@ You’ll need to install Xilinx’ FPGA compilation toolchain, [Vivado HL WebPAC
 ```bash
 chmod u+x Xilinx_Vivado_SDK_2017.1_0415_1_Lin64.bin
 ```
-5. Now you can execute the binary: 
+5. Now you can execute the binary:
 ```bash
 ./Xilinx_Vivado_SDK_2017.1_0415_1_Lin64.bin
 ```
@@ -337,7 +313,7 @@ If you just want to generate the HLS-based VTA IP cores without launching the en
 make ip
 ```
 You'll be able to view the HLS synthesis reports under `<vta root>/build/hardware/xilinx/hls/<configuration>/<block>/solution0/syn/report/<block>_csynth.rpt`
-> Note: The `<configuration>` name is a string that summarizes the VTA configuration parameters specified in the `config.json`. The `<block>` name refers to the specific module in the VTA pipeline. 
+> Note: The `<configuration>` name is a string that summarizes the VTA configuration parameters specified in the `config.json`. The `<block>` name refers to the specific module in the VTA pipeline.
 
 Finally to run the full hardware compilation and generate the bitstream, run:
 
diff --git a/vta/examples/resnet18/pynq/imagenet_predict.py b/vta/examples/resnet18/pynq/imagenet_predict.py
index 5fce83231c5c..f22ec0a8e977 100644
--- a/vta/examples/resnet18/pynq/imagenet_predict.py
+++ b/vta/examples/resnet18/pynq/imagenet_predict.py
@@ -26,8 +26,8 @@
 url = "https://homes.cs.washington.edu/~moreau/media/vta/"
 TEST_FILE = 'cat.jpg'
 CATEG_FILE = 'synset.txt'
-RESNET_GRAPH_FILE = 'quantize_graph.json'
-RESNET_PARAMS_FILE = 'quantize_params.pkl'
+RESNET_GRAPH_FILE = 'resnet18_qt8.json'
+RESNET_PARAMS_FILE = 'resnet18_qt8_params.pkl'
 # Create data dir
 if not os.path.exists(data_dir):
     os.makedirs(data_dir)
@@ -70,7 +70,7 @@ def mark_nop(graph, conv_layer=-1, skip_conv_layer=()):
         attrs = node["attrs"]
         node_name = node["name"]
         func_name = attrs["func_name"]
-        if func_name.find("quantized_conv2d") != -1:
+        if func_name.find("conv2d") != -1:
             if conv_layer >= 0:
                 if counter != conv_layer:
                     attrs["func_name"] = "__nop"
@@ -109,9 +109,9 @@ def mark_nop(graph, conv_layer=-1, skip_conv_layer=()):
 graph = graph.apply("InferShape").apply("InferType")
 
 dtype = "float32"
-sym = vta.graph.remove_stochastic(sym)
 sym = vta.graph.clean_cast(sym)
 sym = vta.graph.clean_conv_fuse(sym)
+
 if target.device_name == "vta":
     sym = vta.graph.pack(sym, shape_dict, bfactor, cfactor)
 
@@ -166,8 +166,10 @@ def run_e2e(graph):
     # get outputs
     tvm_output = m.get_output(
         0,tvm.nd.empty((1000,), dtype, remote.cpu(0)))
-    top1 = np.argmax(tvm_output.asnumpy())
-    print('TVM prediction top-1:', top1, synset[top1])
+
+    top = list(reversed(np.argsort(tvm_output.asnumpy())))
+    for i in range(5):
+        print('TVM prediction top-%d: %s' % (i, synset[top[i]]))
     print("t-cost=%g" % tcost.mean)
 
 
diff --git a/vta/python/vta/graph.py b/vta/python/vta/graph.py
index 41a38c2b655b..7f2a26fdc4bf 100644
--- a/vta/python/vta/graph.py
+++ b/vta/python/vta/graph.py
@@ -71,48 +71,6 @@ def _get_shape(sym, shape_dict):
     return graph_util.infer_shape(
         nnvm.graph.create(sym), **shape_dict)[1][0]
 
-
-def remove_stochastic(graph):
-    """
-    Replace stochastic rounding and shift with determinstic version.
-
-    Parameters
-    ----------
-    graph : Graph
-        The input graph
-
-    Returns
-    -------
-    replaced_graph : Graph
-        The final replaced graph.
-    """
-    gidx = graph.index
-    node_map = {}
-
-    for nid, node in enumerate(gidx.nodes):
-        children = [node_map[e[0]] for e in node["inputs"]]
-        attrs = node.get("attrs", {})
-        node_name = node["name"]
-        op_name = node["op"]
-        get_clone = lambda c, o_n, n_n, a: getattr(nnvm.symbol, o_n)(
-            *c, name=n_n, **a)
-        if op_name == "null":
-            new_node = nnvm.symbol.Variable(node_name)
-        elif op_name == "stochastic_round":
-            new_node = children[0]
-        elif op_name == "noise_lshift":
-            new_node = nnvm.symbol.left_shift(
-                children[0], **attrs)
-        else:
-            new_node = get_clone(children, op_name, node_name, attrs)
-        node_map[nid] = new_node
-
-    assert len(graph.index.output_entries) == 1
-    ret = node_map[graph.index.output_entries[0][0]]
-    ret = nnvm.graph.create(ret)
-    return ret
-
-
 def clean_conv_fuse(graph):
     """Cleanup the convolution's later fuse stages
 
@@ -131,8 +89,8 @@ def _clean_entry(entry):
         if flag:
             node = nnvm.symbol.clip(node, a_max=127, a_min=-127)
             node = nnvm.symbol.cast(node, dtype="int8")
-            # Use identity as a hint to block conv2d schedules
-            node = nnvm.symbol.identity(node)
+            # Use copy as a hint to block conv2d schedules
+            node = nnvm.symbol.copy(node)
             flag = False
         return node, flag
 
@@ -166,13 +124,13 @@ def _clean_entry(entry):
                 new_entry = (
                     get_clone([children[0][0]], op_name, node_name, attrs),
                     False)
-        elif op_name == "quantized_conv2d":
+        elif op_name == "conv2d" and attrs["out_dtype"] == "int32":
             data, weight = children
             data = _clean_entry(data)
-            new_node = nnvm.sym.quantized_conv2d(
+            new_node = nnvm.sym.conv2d(
                 data[0], weight[0], name=node_name, **attrs)
             new_entry = (new_node, True)
-        elif op_name in ("left_shift", "right_shift", "relu"):
+        elif op_name in ("__lshift_scalar__", "__rshift_scalar__", "relu"):
             new_entry = (
                 get_clone([children[0][0]], op_name, node_name, attrs),
                 children[0][1])
@@ -199,7 +157,6 @@ def _clean_entry(entry):
     ret = nnvm.graph.create(ret)
     return ret
 
-
 def clean_cast(graph):
     """
     Move the casts to early part of graph,
@@ -232,11 +189,11 @@ def _clean_cast(node, target_type):
         elif op_name == "cast":
             dtype = attrs["dtype"]
             new_node, _ = _clean_cast(children[0], dtype)
-        elif op_name == "quantized_conv2d":
+        elif op_name == "conv2d" and attrs["out_dtype"] == "int32":
             data, weight = children
             data, _ = _clean_cast(data, "int8")
             weight, _ = _clean_cast(weight, "int8")
-            new_node = nnvm.sym.quantized_conv2d(
+            new_node = nnvm.sym.conv2d(
                 data, weight, name=node_name, **attrs)
         elif op_name == "elemwise_add":
             lhs, rhs = children
@@ -314,21 +271,21 @@ def pack(graph, shape_dict, bfactor, cfactor, start_name=None):
                     *children, name=node_name, **attrs)
             else:
                 new_node = get_clone(children, op_name, node_name, attrs)
-        elif op_name == "quantized_conv2d":
+        elif op_name == "conv2d" and attrs["out_dtype"] == "int32":
             if start_pack:
-                attrs["pack_batch"] = str(bfactor)
-                attrs["pack_channel"] = str(cfactor)
+                attrs["layout"] = "NCHW%dn%dc" % (bfactor, cfactor)
+                attrs["kernel_layout"] = "OIHW%do%di" % (cfactor, cfactor)
                 data, weight = children
                 weight = _pack_weight(weight, ishape[1], cfactor)
-                new_node = nnvm.sym.quantized_conv2d(
+                new_node = nnvm.sym.conv2d(
                     data, weight, name=node_name, **attrs)
             elif counter == 1:
-                attrs["pack_batch"] = str(bfactor)
-                attrs["pack_channel"] = str(cfactor)
+                attrs["layout"] = "NCHW%dn%dc" % (bfactor, cfactor)
+                attrs["kernel_layout"] = "OIHW%do%di" % (cfactor, cfactor)
                 data, weight = children
                 data = _pack_batch_channel(data, ishape[0], bfactor, cfactor)
                 weight = _pack_weight(weight, ishape[1], cfactor)
-                new_node = nnvm.sym.quantized_conv2d(
+                new_node = nnvm.sym.conv2d(
                     data, weight, name=node_name, **attrs)
                 new_node = _unpack_batch_channel(new_node, oshape)
                 counter = counter + 1
diff --git a/vta/python/vta/top/vta_conv2d.py b/vta/python/vta/top/vta_conv2d.py
index 489acb1ce12d..258d2b20bb85 100644
--- a/vta/python/vta/top/vta_conv2d.py
+++ b/vta/python/vta/top/vta_conv2d.py
@@ -215,7 +215,7 @@ def _lower(sch, inputs, func_name, graph):
         f, (tvm.container.Array, tuple, list)) else [f]
 
 
-@reg.register_compute("clip", level=11)
+@reg.register_compute("clip", level=15)
 def compute_clip(attrs, inputs, _):
     """ Clip operator.
     """
@@ -231,11 +231,24 @@ def compute_clip(attrs, inputs, _):
             x.shape, lambda *i: tvm.max(x(*i), const_min), name="clipB")
     return x
 
+# override to force partition at copy
+reg.register_pattern("copy", OpPattern.INJECTIVE, level=15)
 
-reg.register_pattern("identity", OpPattern.INJECTIVE, level=11)
+def is_packed_layout(layout):
+    """Check if layout is packed layout"""
+    if layout == "NCHW":
+        return False
+    assert "n" in layout
+    assert "c" in layout
+    return True
 
-@reg.register_compute("quantized_conv2d", level=11)
-def compute_quantized_conv2d(attrs, inputs, out):
+@reg.register_alter_op_layout("conv2d", level=15)
+def alter_conv2d_layout(*_):
+    return None
+
+
+@reg.register_compute("conv2d", level=15)
+def compute_conv2d(attrs, inputs, out):
     """ 2D convolution algorithm.
     """
     padding = attrs.get_int_tuple("padding")
@@ -244,36 +257,30 @@ def compute_quantized_conv2d(attrs, inputs, out):
     groups = attrs.get_int("groups")
     channels = attrs.get_int("channels")
     layout = attrs["layout"]
-    out_dtype = attrs['out_type']
-    cmp_dtype = 'int32' # compute data type
-
-    assert layout == "NCHW", "only support nchw for now"
+    out_dtype = attrs['out_dtype']
     assert dilation == (1, 1), "not support dilate now"
     assert attrs.get_bool("use_bias") is False
-    pack_channel = attrs.get_int("pack_channel")
-    if pack_channel != 0:
+    if is_packed_layout(layout):
         assert groups == 1
         return packed_conv2d(inputs[0], inputs[1],
-                             padding, strides)
+                             padding, strides, out_dtype=out_dtype)
     if groups == 1:
-        out = topi.nn.conv2d(inputs[0], inputs[1], strides, padding, out_dtype=cmp_dtype)
+        out = topi.nn.conv2d(inputs[0], inputs[1], strides, padding, out_dtype=out_dtype)
     elif groups == get_const_int(inputs[0].shape[1]) and groups == channels:
         out = topi.nn.depthwise_conv2d_nchw(
-            inputs[0], inputs[1], strides, padding, out_dtype=cmp_dtype)
+            inputs[0], inputs[1], strides, padding, out_dtype=out_dtype)
     else:
         raise ValueError("not support arbitrary group number for now")
-
-    assert out_dtype == cmp_dtype
     return out
 
 
-@reg.register_schedule("quantized_conv2d", level=11)
+@reg.register_schedule("conv2d", level=15)
 def schedule_quantized_conv2d(attrs, outs, target):
     """ 2D convolution schedule.
     """
-    channels = attrs.get_int("channels")
-    pack_channel = attrs.get_int("pack_channel")
-    if channels != 0 and pack_channel:
+    layout = attrs["layout"]
+
+    if is_packed_layout(layout):
         target = tvm.target.create(target)
         if target.device_name == "vta":
             return schedule_packed_conv2d(outs)
diff --git a/vta/tests/python/integration/test_benchmark_topi_conv2d.py b/vta/tests/python/integration/test_benchmark_topi_conv2d.py
index b70fc9cfbf85..24d9968dc31a 100644
--- a/vta/tests/python/integration/test_benchmark_topi_conv2d.py
+++ b/vta/tests/python/integration/test_benchmark_topi_conv2d.py
@@ -38,7 +38,7 @@ def run_vta_conv2d(env, remote, key, batch_size, wl, profile=True):
         res_conv = vta.top.packed_conv2d(
             data, kernel, padding=(wl.hpad, wl.wpad), strides=(wl.hstride, wl.wstride))
         res = topi.right_shift(res_conv, 8)
-        res = topi.broadcast_add(res, bias)
+        res = topi.add(res, bias)
         res = my_clip(res, 0, 127)
         res = topi.cast(res, "int8")
 

From 2940f764fc1bfd1b10e48062bb198678f7e4d44c Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 2 Jul 2018 21:18:01 -0700
Subject: [PATCH 855/949] [NNVM] Make param file python version agnostic

---
 vta/examples/resnet18/pynq/imagenet_predict.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/vta/examples/resnet18/pynq/imagenet_predict.py b/vta/examples/resnet18/pynq/imagenet_predict.py
index f22ec0a8e977..db31616003cc 100644
--- a/vta/examples/resnet18/pynq/imagenet_predict.py
+++ b/vta/examples/resnet18/pynq/imagenet_predict.py
@@ -27,7 +27,7 @@
 TEST_FILE = 'cat.jpg'
 CATEG_FILE = 'synset.txt'
 RESNET_GRAPH_FILE = 'resnet18_qt8.json'
-RESNET_PARAMS_FILE = 'resnet18_qt8_params.pkl'
+RESNET_PARAMS_FILE = 'resnet18_qt8.params'
 # Create data dir
 if not os.path.exists(data_dir):
     os.makedirs(data_dir)
@@ -95,8 +95,8 @@ def mark_nop(graph, conv_layer=-1, skip_conv_layer=()):
 np.random.seed(0)
 sym = nnvm.graph.load_json(
     open(os.path.join(data_dir, RESNET_GRAPH_FILE)).read())
-params = pickle.load(
-    open(os.path.join(data_dir, RESNET_PARAMS_FILE), 'rb'))
+params = nnvm.compiler.load_param_dict(
+    open(os.path.join(data_dir, RESNET_PARAMS_FILE), 'rb').read())
 
 shape_dict = {"data": x.shape}
 dtype_dict = {"data": 'float32'}

From dcc019892002a6467e2847efbfe60d1a1c4dbd22 Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@cs.washington.edu>
Date: Tue, 3 Jul 2018 09:47:31 -0700
Subject: [PATCH 856/949] [TUTORIAL] Resnet-18 end to end tutorial example
 (#55)

---
 vta/tutorials/resnet.py | 326 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 326 insertions(+)
 create mode 100644 vta/tutorials/resnet.py

diff --git a/vta/tutorials/resnet.py b/vta/tutorials/resnet.py
new file mode 100644
index 000000000000..72ed5d7d2184
--- /dev/null
+++ b/vta/tutorials/resnet.py
@@ -0,0 +1,326 @@
+"""
+ResNet Inference Example
+========================
+**Author**: `Thierry Moreau <https://homes.cs.washington.edu/~moreau/>`_
+
+This tutorial provides an end-to-end demo, on how to run ResNet-18 inference
+onto the VTA accelerator design to perform ImageNet classification tasks.
+
+"""
+
+
+######################################################################
+# Import Libraries
+# ----------------
+# We start by importing the tvm, vta, nnvm libraries to run this example.
+
+from __future__ import absolute_import, print_function
+
+import os
+import sys
+import nnvm
+import nnvm.compiler
+import tvm
+import vta
+import vta.testing
+import numpy as np
+import json
+import requests
+import time
+
+from nnvm.compiler import graph_attr
+from tvm.contrib import graph_runtime, rpc, util
+from tvm.contrib.download import download
+from vta.testing import simulator
+
+from io import BytesIO
+from matplotlib import pyplot as plt
+from PIL import Image
+
+# Load VTA parameters from the config.json file
+env = vta.get_env()
+
+# Helper to crop an image to a square (224, 224)
+# Takes in an Image object, returns an Image object
+def thumbnailify(image, pad=15):
+    w, h = image.size
+    crop = ((w-h)//2+pad, pad, h+(w-h)//2-pad, h-pad)
+    image = image.crop(crop)
+    image = image.resize((224, 224))
+    return image
+
+# Helper function to read in image
+# Takes in Image object, returns an ND array
+def process_image(image):
+    # Convert to neural network input format
+    image = np.array(image) - np.array([123., 117., 104.])
+    image /= np.array([58.395, 57.12, 57.375])
+    image = image.transpose((2, 0, 1))
+    image = image[np.newaxis, :]
+
+    return tvm.nd.array(image.astype("float32"))
+
+# Classification helper function
+# Takes in the graph runtime, and an image, and returns top result and time
+def classify(m, image):
+    m.set_input('data', image)
+    timer = m.module.time_evaluator("run", ctx, number=1)
+    tcost = timer()
+    tvm_output = m.get_output(0, tvm.nd.empty((1000,), "float32", remote.cpu(0)))
+    top = np.argmax(tvm_output.asnumpy())
+    tcost = "t={0:.2f}s".format(tcost.mean)
+    return tcost + " {}".format(synset[top])
+
+# Helper function to compile the NNVM graph
+# Takes in a path to a graph file, params file, and device target
+# Returns the NNVM graph object, a compiled library object, and the params dict
+def generate_graph(graph_fn, params_fn, device="vta"):
+
+    # Measure build start time
+    build_start = time.time()
+
+    # Derive the TVM target
+    target = tvm.target.create("llvm -device={}".format(device))
+
+    # Derive the LLVM compiler flags
+    # When targetting the Pynq, cross-compile to ARMv7 ISA
+    if env.TARGET == "sim":
+        target_host = "llvm"
+    elif env.TARGET == "pynq":
+        target_host = "llvm -mtriple=armv7-none-linux-gnueabihf -mcpu=cortex-a9 -mattr=+neon"
+
+    # Load the ResNet-18 graph and parameters
+    sym = nnvm.graph.load_json(open(graph_fn).read())
+    params = nnvm.compiler.load_param_dict(open(params_fn, 'rb').read())
+
+    # Populate the shape and data type dictionary
+    shape_dict = {"data": (1, 3, 224, 224)}
+    dtype_dict = {"data": 'float32'}
+    shape_dict.update({k: v.shape for k, v in params.items()})
+    dtype_dict.update({k: str(v.dtype) for k, v in params.items()})
+
+    # Create NNVM graph
+    graph = nnvm.graph.create(sym)
+    graph_attr.set_shape_inputs(sym, shape_dict)
+    graph_attr.set_dtype_inputs(sym, dtype_dict)
+    graph = graph.apply("InferShape").apply("InferType")
+
+    # Apply NNVM graph optimization passes
+    sym = vta.graph.clean_cast(sym)
+    sym = vta.graph.clean_conv_fuse(sym)
+    if target.device_name == "vta":
+        assert env.BLOCK_IN == env.BLOCK_OUT
+        sym = vta.graph.pack(sym, shape_dict, env.BATCH, env.BLOCK_OUT)
+
+    # Compile NNVM graph
+    with nnvm.compiler.build_config(opt_level=3):
+        if target.device_name != "vta":
+            graph, lib, params = nnvm.compiler.build(
+                sym, target_host, shape_dict, dtype_dict,
+                params=params)
+        else:
+            with vta.build_config():
+                graph, lib, params = nnvm.compiler.build(
+                    sym, target, shape_dict, dtype_dict,
+                    params=params, target_host=target_host)
+
+    # Save the compiled inference graph library
+    assert tvm.module.enabled("rpc")
+    temp = util.tempdir()
+    lib.save(temp.relpath("graphlib.o"))
+
+    # Send the inference library over to the remote RPC server
+    remote.upload(temp.relpath("graphlib.o"))
+    lib = remote.load_module("graphlib.o")
+
+    # Measure build time
+    build_time = time.time() - build_start
+    print("ResNet-18 inference graph built in {0:.2f}s!".format(build_time))
+
+    return graph, lib, params
+
+
+######################################################################
+# Download ResNet Model
+# --------------------------------------------
+# Download the necessary files to run ResNet-18.
+#
+
+# Obtain ResNet model and download them into _data dir
+url = "https://github.com/uwsaml/web-data/raw/master/vta/models/"
+categ_fn = 'synset.txt'
+graph_fn = 'resnet18_qt8.json'
+params_fn = 'resnet18_qt8.params'
+
+# Create data dir
+data_dir = "_data/"
+if not os.path.exists(data_dir):
+    os.makedirs(data_dir)
+
+# Download files
+for file in [categ_fn, graph_fn, params_fn]:
+    if not os.path.isfile(file):
+        download(os.path.join(url, file), os.path.join(data_dir, file))
+
+# Read in ImageNet Categories
+synset = eval(open(os.path.join(data_dir, categ_fn)).read())
+
+
+######################################################################
+# Setup the Pynq Board's RPC Server
+# ---------------------------------
+# Build the RPC server's VTA runtime and program the Pynq FPGA.
+
+# Measure build start time
+reconfig_start = time.time()
+
+# We read the Pynq RPC host IP address and port number from the OS environment
+host = os.environ.get("VTA_PYNQ_RPC_HOST", "192.168.2.99")
+port = int(os.environ.get("VTA_PYNQ_RPC_PORT", "9091"))
+
+# We configure both the bitstream and the runtime system on the Pynq
+# to match the VTA configuration specified by the config.json file.
+if env.TARGET == "pynq":
+
+    # Make sure that TVM was compiled with RPC=1
+    assert tvm.module.enabled("rpc")
+    remote = rpc.connect(host, port)
+
+    # Reconfigure the JIT runtime
+    vta.reconfig_runtime(remote)
+
+    # Program the FPGA with a pre-compiled VTA bitstream.
+    # You can program the FPGA with your own custom bitstream
+    # by passing the path to the bitstream file instead of None.
+    vta.program_fpga(remote, bitstream=None)
+
+    # Report on reconfiguration time
+    reconfig_time = time.time() - reconfig_start
+    print("Reconfigured FPGA and RPC runtime in {0:.2f}s!".format(reconfig_time))
+
+# In simulation mode, host the RPC server locally.
+elif env.TARGET == "sim":
+    remote = rpc.LocalSession()
+
+
+######################################################################
+# Build the ResNet Runtime
+# ------------------------
+# Build the ResNet graph runtime, and configure the parameters.
+
+# Set ``device=cpu`` to run inference on the CPU,
+# or ``device=vtacpu`` to run inference on the FPGA.
+device = "vta"
+
+# Device context
+ctx = remote.ext_dev(0) if device == "vta" else remote.cpu(0)
+
+# Build the graph runtime
+graph, lib, params = generate_graph(os.path.join(data_dir, graph_fn),
+                                    os.path.join(data_dir, params_fn),
+                                    device)
+m = graph_runtime.create(graph, lib, ctx)
+
+# Set the parameters
+m.set_input(**params)
+
+
+######################################################################
+# Run ResNet-18 inference on a sample image
+# -----------------------------------------
+# Perform image classification on test image.
+# You can change the test image URL to any image of your choosing.
+
+# Read in test image
+image_url = 'https://homes.cs.washington.edu/~moreau/media/vta/cat.jpg'
+# Read in test image
+response = requests.get(image_url)
+image = Image.open(BytesIO(response.content)).resize((224, 224))
+# Show Image
+plt.imshow(image)
+plt.show()
+# Set the input
+image = process_image(image)
+m.set_input('data', image)
+
+# Perform inference
+timer = m.module.time_evaluator("run", ctx, number=1)
+tcost = timer()
+
+# Get classification results
+tvm_output = m.get_output(0, tvm.nd.empty((1000,), "float32", remote.cpu(0)))
+top_categories = np.argsort(tvm_output.asnumpy())
+
+# Report top-5 classification results
+print("ResNet-18 Prediction #1:", synset[top_categories[-1]])
+print("                     #2:", synset[top_categories[-2]])
+print("                     #3:", synset[top_categories[-3]])
+print("                     #4:", synset[top_categories[-4]])
+print("                     #5:", synset[top_categories[-5]])
+print("Performed inference in {0:.2f}s".format(tcost.mean))
+
+
+######################################################################
+# Run a Youtube Video Image Classifier
+# ------------------------------------
+# Perform image classification on test stream on 1 frame every 48 frames.
+# Comment the `if False:` out to run the demo
+
+# Early exit - remove for Demo
+if False:
+
+    import cv2
+    import pafy
+    from IPython.display import clear_output
+
+    # Helper to crop an image to a square (224, 224)
+    # Takes in an Image object, returns an Image object
+    def thumbnailify(image, pad=15):
+        w, h = image.size
+        crop = ((w-h)//2+pad, pad, h+(w-h)//2-pad, h-pad)
+        image = image.crop(crop)
+        image = image.resize((224, 224))
+        return image
+
+    # 16:16 inches
+    plt.rcParams['figure.figsize'] = [16, 16]
+
+    # Stream the video in
+    url = "https://www.youtube.com/watch?v=PJlmYh27MHg&t=2s"
+    video = pafy.new(url)
+    best = video.getbest(preftype="mp4")
+    cap = cv2.VideoCapture(best.url)
+
+    # Process one frame out of every 48 for variety
+    count = 0
+    guess = ""
+    while(count<2400):
+
+        # Capture frame-by-frame
+        ret, frame = cap.read()
+
+        # Process one every 48 frames
+        if count % 48 == 1:
+            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
+            frame = Image.fromarray(frame)
+            # Crop and resize
+            thumb = np.array(thumbnailify(frame))
+            image = process_image(thumb)
+            guess = classify(m, image)
+
+            # Insert guess in frame
+            frame = cv2.rectangle(thumb,(0,0),(200,0),(0,0,0),50)
+            cv2.putText(frame, guess, (5,15), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (256,256,256), 1, cv2.LINE_AA)
+
+            plt.imshow(thumb)
+            plt.axis('off')
+            plt.show()
+            if cv2.waitKey(1) & 0xFF == ord('q'):
+                break
+            clear_output(wait=True)
+
+        count += 1
+
+    # When everything done, release the capture
+    cap.release()
+    cv2.destroyAllWindows()
\ No newline at end of file

From 0af71310bbadf73e52a1c0130b7fabb9c3ebe2a0 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 5 Jul 2018 09:12:46 -0700
Subject: [PATCH 857/949] [TOPI] Fix the CPU op perf (#56)

---
 vta/Makefile                                  |   1 -
 vta/docs/.gitignore                           |   1 +
 .../resnet18/pynq/imagenet_predict.py         | 190 ------------
 vta/python/vta/top/arm_conv2d.py              | 290 +-----------------
 vta/python/vta/top/vta_conv2d.py              |   2 +-
 .../integration/test_benchmark_topi_conv2d.py | 112 ++++++-
 vta/tutorials/resnet.py                       |   6 +-
 7 files changed, 121 insertions(+), 481 deletions(-)
 delete mode 100644 vta/examples/resnet18/pynq/imagenet_predict.py

diff --git a/vta/Makefile b/vta/Makefile
index 60e93c0bd347..c43c49bddaca 100644
--- a/vta/Makefile
+++ b/vta/Makefile
@@ -63,7 +63,6 @@ doc:
 
 clean:
 	$(RM) -rf build lib bin *~ */*~ */*/*~ */*/*/*~ */*.o */*/*.o */*/*/*.o
-	$(RM) -rf cat.jpg quantize_graph.json quantize_params.pkl synset.txt
 
 
 -include build/*.d
diff --git a/vta/docs/.gitignore b/vta/docs/.gitignore
index 845005a76aa8..eee93ee4003a 100644
--- a/vta/docs/.gitignore
+++ b/vta/docs/.gitignore
@@ -2,3 +2,4 @@ doxygen
 modules
 tutorials
 _build
+gen_modules
\ No newline at end of file
diff --git a/vta/examples/resnet18/pynq/imagenet_predict.py b/vta/examples/resnet18/pynq/imagenet_predict.py
deleted file mode 100644
index db31616003cc..000000000000
--- a/vta/examples/resnet18/pynq/imagenet_predict.py
+++ /dev/null
@@ -1,190 +0,0 @@
-# some standard imports
-import nnvm
-import tvm
-import vta
-import vta.testing
-import os
-import numpy as np
-import pickle
-import json
-import logging
-
-from PIL import Image
-from nnvm.compiler import graph_attr
-from tvm.contrib import graph_runtime, rpc, util
-from tvm.contrib.download import download
-
-bfactor = 1
-cfactor = 16
-verbose = False
-# only run fpga component, mark non-conv ops as nop
-debug_fpga_only = False
-
-# Obtain model files (they're too large to check-in)
-# Download them into _data dir
-data_dir = "_data/"
-url = "https://homes.cs.washington.edu/~moreau/media/vta/"
-TEST_FILE = 'cat.jpg'
-CATEG_FILE = 'synset.txt'
-RESNET_GRAPH_FILE = 'resnet18_qt8.json'
-RESNET_PARAMS_FILE = 'resnet18_qt8.params'
-# Create data dir
-if not os.path.exists(data_dir):
-    os.makedirs(data_dir)
-# Download files
-for file in [TEST_FILE, CATEG_FILE, RESNET_GRAPH_FILE, RESNET_PARAMS_FILE]:
-    if not os.path.isfile(file):
-        download(os.path.join(url, file), os.path.join(data_dir, file))
-
-if verbose:
-    logging.basicConfig(level=logging.DEBUG)
-
-# Change to -device=vtacpu to run cpu only inference.
-target = tvm.target.create("llvm -device=vta")
-target_host = "llvm -mtriple=armv7-none-linux-gnueabihf -mcpu=cortex-a9 -mattr=+neon"
-
-if vta.get_env().TARGET == "sim":
-    target_host = "llvm"
-
-synset = eval(open(os.path.join(data_dir, CATEG_FILE)).read())
-image = Image.open(os.path.join(data_dir, TEST_FILE)).resize((224, 224))
-
-def transform_image(image):
-    image = np.array(image) - np.array([123., 117., 104.])
-    image /= np.array([58.395, 57.12, 57.375])
-    image = image.transpose((2, 0, 1))
-    image = image[np.newaxis, :]
-    return image
-
-def mark_nop(graph, conv_layer=-1, skip_conv_layer=()):
-    """Helper function to mark certain op as nop
-
-    Useful to debug performance issues.
-    """
-    jgraph = json.loads(graph.json())
-    counter = 0
-    for nid, node in enumerate(jgraph["nodes"]):
-        op_name = node["op"]
-        if op_name != "tvm_op":
-            continue
-        attrs = node["attrs"]
-        node_name = node["name"]
-        func_name = attrs["func_name"]
-        if func_name.find("conv2d") != -1:
-            if conv_layer >= 0:
-                if counter != conv_layer:
-                    attrs["func_name"] = "__nop"
-            if counter in skip_conv_layer:
-                attrs["func_name"] = "__nop"
-            counter += 1
-        else:
-            if conv_layer >= 0:
-                attrs["func_name"] = "__nop"
-            attrs["func_name"] = "__nop"
-        if attrs["func_name"] != "__nop":
-            print("Run function %s"% func_name)
-    graph = nnvm.graph.load_json(json.dumps(jgraph))
-    return graph
-
-x = transform_image(image)
-print('x', x.shape)
-
-######################################################################
-# now compile the graph
-import nnvm.compiler
-np.random.seed(0)
-sym = nnvm.graph.load_json(
-    open(os.path.join(data_dir, RESNET_GRAPH_FILE)).read())
-params = nnvm.compiler.load_param_dict(
-    open(os.path.join(data_dir, RESNET_PARAMS_FILE), 'rb').read())
-
-shape_dict = {"data": x.shape}
-dtype_dict = {"data": 'float32'}
-shape_dict.update({k: v.shape for k, v in params.items()})
-dtype_dict.update({k: str(v.dtype) for k, v in params.items()})
-
-graph = nnvm.graph.create(sym)
-graph_attr.set_shape_inputs(sym, shape_dict)
-graph_attr.set_dtype_inputs(sym, dtype_dict)
-graph = graph.apply("InferShape").apply("InferType")
-
-dtype = "float32"
-sym = vta.graph.clean_cast(sym)
-sym = vta.graph.clean_conv_fuse(sym)
-
-if target.device_name == "vta":
-    sym = vta.graph.pack(sym, shape_dict, bfactor, cfactor)
-
-with nnvm.compiler.build_config(opt_level=3):
-    if target.device_name != "vta":
-        graph, lib, params = nnvm.compiler.build(
-            sym, target_host, shape_dict, dtype_dict,
-            params=params)
-    else:
-        with vta.build_config():
-            graph, lib, params = nnvm.compiler.build(
-                sym, target, shape_dict, dtype_dict,
-                params=params, target_host=target_host)
-
-
-assert tvm.module.enabled("rpc")
-temp = util.tempdir()
-lib.save(temp.relpath("graphlib.o"))
-
-if vta.get_env().TARGET == "sim":
-    remote = rpc.LocalSession()
-    print("local session")
-else:
-    host = os.environ.get("VTA_PYNQ_RPC_HOST", None)
-    assert host
-    port = os.environ.get("VTA_PYNQ_RPC_PORT", "9091")
-    port = int(port)
-    remote = rpc.connect(host, port)
-    # Program FPGA, and build runtime if necessary
-    # Overwrite bitstream with a path to your own if you built it yourself
-    vta.reconfig_runtime(remote)
-    vta.program_fpga(remote, bitstream=None)
-
-remote.upload(temp.relpath("graphlib.o"))
-lib = remote.load_module("graphlib.o")
-ctx = remote.ext_dev(0) if target.device_name == "vta" else remote.cpu(0)
-
-print("Build complete...")
-
-def run_e2e(graph):
-    """Running end to end example
-    """
-    if debug_fpga_only:
-        graph = mark_nop(graph, skip_conv_layer=(0,))
-    m = graph_runtime.create(graph, lib, ctx)
-    # set inputs
-    m.set_input('data', tvm.nd.array(x.astype("float32")))
-    m.set_input(**params)
-    # execute
-    timer = m.module.time_evaluator("run", ctx, number=10)
-    tcost = timer()
-    # get outputs
-    tvm_output = m.get_output(
-        0,tvm.nd.empty((1000,), dtype, remote.cpu(0)))
-
-    top = list(reversed(np.argsort(tvm_output.asnumpy())))
-    for i in range(5):
-        print('TVM prediction top-%d: %s' % (i, synset[top[i]]))
-    print("t-cost=%g" % tcost.mean)
-
-
-def run_layer(old_graph, layer_begin, layer_end):
-    """Run a certain layer."""
-    for layer_id in range(layer_begin, layer_end):
-        print("run resnet[%d]..."% (layer_id))
-        graph = mark_nop(old_graph, layer_id)
-        m = graph_runtime.create(graph, lib, ctx)
-        # set inputs
-        m.set_input('data', tvm.nd.array(x.astype("float32")))
-        m.set_input(**params)
-        # execute
-        timer = m.module.time_evaluator("run", ctx, number=1)
-        tcost = timer()
-        print("resnet[%d]: %g\n"% (layer_id, tcost.mean))
-
-run_e2e(graph)
diff --git a/vta/python/vta/top/arm_conv2d.py b/vta/python/vta/top/arm_conv2d.py
index c959f1ee9967..d8f749b3b908 100644
--- a/vta/python/vta/top/arm_conv2d.py
+++ b/vta/python/vta/top/arm_conv2d.py
@@ -5,13 +5,9 @@
 """
 from __future__ import absolute_import as _abs
 
-import tvm
-from topi import tag
 from topi.nn.conv2d import conv2d, _get_schedule
 from topi.nn.conv2d import SpatialPack, Im2ColPack, Workload
-from topi.nn.conv2d import _SCH_TO_DECL_FUNC
-from topi.nn.conv2d import _get_workload
-from topi.nn.util import infer_pad, infer_stride
+from topi.rasp import conv2d as _rasp_conv2d
 from topi import generic
 
 _WORKLOADS = [
@@ -52,284 +48,8 @@ def _schedule_conv2d(wkl):
     sch = _SCHEDULES[idx]
     return sch
 
+conv2d.register(["vtacpu", "vta"], _rasp_conv2d._declaration_conv2d)
 
-@conv2d.register(["vtacpu", "vta"])
-def _declaration_conv2d(data, kernel, stride, padding, layout, out_dtype):
-    assert layout == 'NCHW', "only support NCHW convolution on vtacpu"
-    assert data.shape[0].value == 1, "only support batch size=1 convolution on vtacpu"
-    wkl = _get_workload(data, kernel, stride, padding, out_dtype)
-    sch = _get_schedule(wkl)
-    return _SCH_TO_DECL_FUNC[type(sch)](data, kernel, stride, padding, out_dtype)
-
-
-def _schedule_spatial_conv2d(s, data, data_pad, data_vec,
-                             kernel, kernel_vec,
-                             conv_out, output, last):
-    # no stride and padding info here
-    padding = infer_pad(data, data_pad)
-    if data_pad is None:
-        stride = infer_stride(data, kernel, output)
-    else:
-        stride = infer_stride(data_pad, kernel, output)
-    wkl = _get_workload(data, kernel, stride, padding, output.dtype)
-    sch = _get_schedule(wkl)
-
-    H, W = wkl.height, wkl.width
-    CI, CO = wkl.in_filter, wkl.out_filter
-    HK, WK = wkl.hkernel, wkl.wkernel
-    HPAD, WPAD = wkl.hpad, wkl.wpad
-    HSTR, WSTR = wkl.hstride, wkl.wstride
-
-    HCAT, WCAT = HK-1, WK-1
-    DOPAD = (HPAD != 0 and WPAD != 0)
-
-    VH = sch.vh
-    VW = sch.vw
-    VC = sch.vc
-    UNROLL = sch.unroll
-
-    A, B, C = data, kernel, last
-    A0, A1 = data_pad, data_vec
-    B0 = kernel_vec
-    C0, C1 = conv_out, output
-
-    CC = s.cache_write(C0, "global")
-
-    _, co, oh, ow, vh, vw, vc = s[C0].op.axis
-    if UNROLL:
-        s[C0].unroll(vw)
-    s[C0].vectorize(vc)
-
-    s[CC].compute_at(s[C0], ow)
-    _, co, oh, ow, vh, vw, vc = s[CC].op.axis
-    ci, dh, dw = s[CC].op.reduce_axis
-    s[CC].reorder(ci, dh, vh, dw, vw, vc)
-
-    if UNROLL:
-        s[CC].unroll(vw)
-    s[CC].vectorize(vc)
-
-    ##### Schedule A
-    if DOPAD:
-        s[A0].compute_inline()
-
-    _, h, _, _, _, _ = s[A1].op.axis
-    if sch.ba == 1:
-        oaxis = h
-        paxis = h
-    else:
-        oh, ih = s[A1].split(h, sch.ba)
-        oaxis = oh
-        paxis = ih
-
-    s[A1].parallel(paxis)
-    s[A1].pragma(oaxis, "parallel_launch_point")
-    s[A1].pragma(paxis, "parallel_stride_pattern")
-    s[A1].pragma(oaxis, "parallel_barrier_when_finish")
-
-
-    ##### Schedule B
-    co, _, _, _, _ = s[B0].op.axis
-    if sch.bc == 1:
-        oaxis = co
-        paxis = co
-    else:
-        oco, ico = s[B0].split(co, sch.bc)
-        oaxis = oco
-        paxis = ico
-
-    s[B0].parallel(paxis)
-    s[B0].pragma(oaxis, "parallel_launch_point")
-    s[B0].pragma(paxis, "parallel_stride_pattern")
-    s[B0].pragma(oaxis, "parallel_barrier_when_finish")
-
-
-    ##### Schedule C
-    n, co, h, w = s[C].op.axis
-    co, vc = s[C].split(co, VC)
-    oh, ow, vh, vw = s[C].tile(h, w, VH, VW)
-    s[C].reorder(n, co, oh, ow, vh, vw, vc)
-    if C != C1:
-        s[C1].compute_inline()
-    s[C0].compute_at(s[C], ow)
-
-    if sch.bc == 1:
-        oaxis = co
-        paxis = co
-    else:
-        oco, ico = s[C].split(co, sch.bc)
-        oaxis = oco
-        paxis = ico
-
-    s[C].parallel(paxis)
-    s[C].pragma(oaxis, "parallel_launch_point")
-    s[C].pragma(paxis, "parallel_stride_pattern")
-    s[C].pragma(oaxis, "parallel_barrier_when_finish")
-
-    return s
-
-def _schedule_im2col_conv2d(s, data, data_pad, data_col, data_vec,
-                            kernel, kernel_vec,
-                            conv_out, output, last):
-    # no stride and padding info here
-    padding = infer_pad(data, data_pad)
-    if data_pad is None:
-        stride = infer_stride(data, kernel, output)
-    else:
-        stride = infer_stride(data_pad, kernel, output)
-    wkl = _get_workload(data, kernel, stride, padding, output.dtype)
-
-    sch = _get_schedule(wkl)
-
-    H, W = wkl.height, wkl.width
-    CI = wkl.in_filter
-    CO = wkl.out_filter
-    HK, WK = wkl.hkernel, wkl.wkernel
-    HPAD, WPAD = wkl.hpad, wkl.wpad
-    HSTR, WSTR = wkl.hstride, wkl.wstride
-
-    HCAT, WCAT = HK-1, WK-1
-    DOPAD = (HPAD != 0 and WPAD != 0)
-
-    P = sch.vp
-    Q = sch.vq
-    UNROLL = sch.unroll
-
-    A, B, C = data, kernel, last
-    A0, A1, A2 = data_pad, data_col, data_vec
-    B0 = kernel_vec
-    C0, C1 = conv_out, output
-
-    CC = s.cache_write(C0, "global")
-    AA = s.cache_read(A2, "global", [CC])
-    BB = s.cache_read(B0, "global", [CC])
-
-
-    ##### Schedule CC
-    _, co, im, vim, vco = s[C0].op.axis
-    s[C0].unroll(vim)
-    s[C0].vectorize(vco)
-
-    s[CC].compute_at(s[C0], im)
-    _, co, im, vim, vco = s[CC].op.axis
-    ci, hk, wk = s[CC].op.reduce_axis
-    s[CC].reorder(ci, hk, wk, vim, vco)
-    s[CC].unroll(vim)
-    s[CC].vectorize(vco)
-    # s[CC].unroll(ccr)
-
-    ### Schedule C
-    _, co, h, w = s[C].op.axis
-    im = s[C].fuse(h, w)
-    im, vim = s[C].split(im, P)
-    co, vco = s[C].split(co, Q)
-    s[C].reorder(co, im, vim, vco)
-
-    if sch.bc == 1:
-        oaxis = co
-        paxis = co
-    else:
-        oco, ico = s[C].split(co, sch.bc)
-        oaxis = oco
-        paxis = ico
-
-    s[C].parallel(paxis)
-    s[C].pragma(oaxis, "parallel_launch_point")
-    s[C].pragma(paxis, "parallel_stride_pattern")
-    s[C].pragma(oaxis, "parallel_barrier_when_finish")
-    if C1 != C:
-        s[C1].compute_inline()
-
-    s[C0].compute_at(s[C], paxis)
-
-    ##### Schedule A
-    if DOPAD:
-        s[A0].compute_inline()
-    s[A1].compute_inline()
-    s[AA].compute_at(s[CC], wk)
-    s[AA].unroll(AA.op.axis[4])
-
-    _, im, _, _, _, _ = s[A2].op.axis
-    if sch.ba == 1:
-        oaxis = im
-        paxis = im
-    else:
-        oim, iim = s[A2].split(im, sch.ba)
-        oaxis = oim
-        paxis = iim
-
-    s[A2].parallel(paxis)
-    s[A2].pragma(oaxis, "parallel_launch_point")
-    s[A2].pragma(paxis, "parallel_stride_pattern")
-    s[A2].pragma(oaxis, "parallel_barrier_when_finish")
-
-
-    ##### Schedule B
-    s[BB].compute_at(s[CC], wk)
-    s[BB].vectorize(BB.op.axis[4])
-
-    co, _, _, _, _ = s[B0].op.axis
-    if sch.bc == 1:
-        oaxis = co
-        paxis = co
-    else:
-        oco, ico = s[B0].split(co, sch.bc)
-        oaxis = oco
-        paxis = ico
-
-    s[B0].parallel(paxis)
-    s[B0].pragma(oaxis, "parallel_launch_point")
-    s[B0].pragma(paxis, "parallel_stride_pattern")
-    s[B0].pragma(oaxis, "parallel_barrier_when_finish")
-
-    return s
-
-@generic.schedule_conv2d_nchw.register(["vtacpu", "vta"])
-def schedule_conv2d(outs):
-    """Create schedule for tensors"""
-    s = tvm.create_schedule([x.op for x in outs])
-
-    def traverse(op):
-        """Traverse operators from computation graph"""
-        # inline all one-to-one-mapping operators except the last stage (output)
-        if tag.is_broadcast(op.tag):
-            if op not in s.outputs:
-                s[op].compute_inline()
-            for tensor in op.input_tensors:
-                if tensor.op.input_tensors:
-                    traverse(tensor.op)
-
-        if 'spatial_conv_output' in op.tag:
-            output = op.output(0)
-            conv_out = op.input_tensors[0]
-            kernel_vec = conv_out.op.input_tensors[1]
-            kernel = kernel_vec.op.input_tensors[0]
-            data_vec = conv_out.op.input_tensors[0]
-            data = data_vec.op.input_tensors[0]
-            data_pad = None
-            if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
-                data_pad = data
-                data = data_pad.op.input_tensors[0]
-
-            _schedule_spatial_conv2d(s, data, data_pad, data_vec,
-                                     kernel, kernel_vec,
-                                     conv_out, output, outs[0])
-
-        if 'im2col_conv_output' in op.tag:
-            output = op.output(0)
-            conv_out = op.input_tensors[0]
-            kernel_vec = conv_out.op.input_tensors[1]
-            kernel = kernel_vec.op.input_tensors[0]
-            data_vec = conv_out.op.input_tensors[0]
-            data_col = data_vec.op.input_tensors[0]
-            data = data_col.op.input_tensors[0]
-            data_pad = None
-            if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
-                data_pad = data
-                data = data_pad.op.input_tensors[0]
-            _schedule_im2col_conv2d(s, data, data_pad, data_col, data_vec,
-                                    kernel, kernel_vec,
-                                    conv_out, output, outs[0])
-
-    traverse(outs[0].op)
-    return s
+generic.schedule_conv2d_nchw.register(
+    ["vtacpu", "vta"],
+    _rasp_conv2d.schedule_conv2d_nchw)
diff --git a/vta/python/vta/top/vta_conv2d.py b/vta/python/vta/top/vta_conv2d.py
index 258d2b20bb85..ab3b4290bb2a 100644
--- a/vta/python/vta/top/vta_conv2d.py
+++ b/vta/python/vta/top/vta_conv2d.py
@@ -192,7 +192,7 @@ def _build(funcs, target, target_host):
     tvm_t = tvm.target.create(target)
     if tvm_t.device_name == "vta":
         return tvm.build(funcs, target="ext_dev", target_host=target_host)
-    elif tvm_t.device_name == "rasp" or tvm_t.device_name == "vta-cpu":
+    elif tvm_t.device_name == "rasp" or tvm_t.device_name == "vtacpu":
         return tvm.build(funcs, target=target_host)
     return tvm.build(funcs, target=target)
 
diff --git a/vta/tests/python/integration/test_benchmark_topi_conv2d.py b/vta/tests/python/integration/test_benchmark_topi_conv2d.py
index 24d9968dc31a..1ee5cdb01167 100644
--- a/vta/tests/python/integration/test_benchmark_topi_conv2d.py
+++ b/vta/tests/python/integration/test_benchmark_topi_conv2d.py
@@ -20,6 +20,114 @@ def my_clip(x, a_min, a_max):
     x = tvm.compute(x.shape, lambda *i: tvm.max(x(*i), const_min), name="clipB")
     return x
 
+def test_cpu_conv2d():
+    def run_cpu_conv2d(env, remote, key, batch_size, wl, profile=True):
+        data_shape = (batch_size, wl.in_filter, wl.height, wl.width)
+        kernel_shape = (wl.out_filter, wl.in_filter, wl.hkernel, wl.wkernel)
+
+        fout_height = (wl.height + 2 * wl.hpad - wl.hkernel) // wl.hstride + 1
+        fout_width = (wl.width + 2 * wl.wpad - wl.wkernel) // wl.wstride + 1
+        data = tvm.placeholder(data_shape, name="data", dtype=env.inp_dtype)
+        kernel = tvm.placeholder(kernel_shape, name="kernel", dtype=env.wgt_dtype)
+        res_conv = topi.nn.conv2d(
+            data, kernel, padding=(wl.hpad, wl.wpad),
+            strides=(wl.hstride, wl.wstride),
+            out_dtype="int32")
+        res = topi.right_shift(res_conv, 8)
+        res = my_clip(res, 0, 127)
+        res = topi.cast(res, "int8")
+
+        # To compute number of ops, use a x2 factor for FMA
+        num_ops = 2 * batch_size * fout_height * fout_width * wl.hkernel * wl.wkernel * wl.out_filter * wl.in_filter
+
+        a_shape = (batch_size, wl.in_filter, wl.height, wl.width)
+        w_shape = (wl.out_filter, wl.in_filter, wl.hkernel, wl.wkernel)
+        stride = (wl.hstride, wl.wstride)
+        data_dtype = data.dtype
+        kernel_dtype = kernel.dtype
+        acc_dtype = env.acc_dtype
+        assert wl.hpad == wl.wpad
+        padding = wl.hpad
+
+        @memoize("vta.tests.test_benchmark_topi.conv2d.cpu.verify_nhwc")
+        def get_ref_data():
+            a_np = (np.random.uniform(size=a_shape) * 4).astype(data_dtype)
+            w_np = (np.random.uniform(size=w_shape) * 4).astype(kernel_dtype)
+            a_np = np.abs(a_np)
+            w_np = np.abs(w_np)
+            b_np = topi.testing.conv2d_nchw_python(
+                a_np.astype(acc_dtype), w_np.astype(acc_dtype), stride, padding).astype(acc_dtype)
+            return a_np, w_np, b_np
+
+
+        def verify(s, check_correctness):
+            mod = tvm.build(s, [data, kernel, res],
+                            "llvm -device=vtacpu",
+                            env.target_host,
+                            name="conv2d")
+            temp = util.tempdir()
+            mod.save(temp.relpath("conv2d.o"))
+            remote.upload(temp.relpath("conv2d.o"))
+            f = remote.load_module("conv2d.o")
+            # verify
+            ctx = remote.cpu(0)
+            # Data in original format
+            data_orig, kernel_orig, res_ref = get_ref_data()
+            res_shape = topi.util.get_const_tuple(res.shape)
+            res_np = np.zeros(res_shape).astype(res.dtype)
+            data_arr = tvm.nd.array(data_orig, ctx)
+            kernel_arr = tvm.nd.array(kernel_orig, ctx)
+            res_arr = tvm.nd.array(res_np, ctx)
+            time_f = f.time_evaluator("conv2d", ctx, number=5)
+            cost = time_f(data_arr, kernel_arr, res_arr)
+            res_unpack = res_arr.asnumpy()
+            if check_correctness:
+                assert wl.hpad == wl.wpad
+                stride = (wl.hstride, wl.wstride)
+                padding = wl.hpad
+                res_ref = res_ref >> 8
+                res_ref = np.clip(res_ref, 0, 127).astype("int8")
+                np.testing.assert_allclose(res_unpack, res_ref)
+            return cost
+
+        def conv_normal(print_ir):
+            print("----- CONV2D CPU End-to-End Test-------")
+            s = topi.generic.schedule_conv2d_nchw([res])
+            if print_ir:
+                print(tvm.lower(s, [data, kernel, res], simple_mode=True))
+            cost = verify(s, True)
+            gops = (num_ops / cost.mean) / float(10 ** 9)
+            print("\tTime cost = %g sec/op, %g GOPS" % (cost.mean, gops))
+
+        conv_normal(False)
+
+    def _run(env, remote):
+        # ResNet18 workloads
+        resnet = {
+            # Workloads of resnet18 on imagenet
+            0: Workload(1, 224, 224, 16, 64, 7, 7, 3, 3, 2, 2),
+            1: Workload(1, 56, 56, 64, 64, 3, 3, 1, 1, 1, 1),
+            2: Workload(1, 56, 56, 64, 64, 1, 1, 0, 0, 1, 1),
+            3: Workload(1, 56, 56, 64, 128, 3, 3, 1, 1, 2, 2),
+            4: Workload(1, 56, 56, 64, 128, 1, 1, 0, 0, 2, 2),
+            5: Workload(1, 28, 28, 128, 128, 3, 3, 1, 1, 1, 1),
+            6: Workload(1, 28, 28, 128, 256, 3, 3, 1, 1, 2, 2),
+            7: Workload(1, 28, 28, 128, 256, 1, 1, 0, 0, 2, 2),
+            8: Workload(1, 14, 14, 256, 256, 3, 3, 1, 1, 1, 1),
+            9: Workload(1, 14, 14, 256, 512, 3, 3, 1, 1, 2, 2),
+            10: Workload(1, 14, 14, 256, 512, 1, 1, 0, 0, 2, 2),
+            11: Workload(1, 7, 7, 512, 512, 3, 3, 1, 1, 1, 1),
+        }
+        batch_size = 1
+        for i in range(1, len(resnet)):
+            wl = resnet[i]
+            key = "resnet-cfg[%d]" % i
+            print("key=%s" % key)
+            print(wl)
+            with tvm.target.create("llvm -device=vtacpu"):
+                run_cpu_conv2d(env, remote, key, batch_size, wl)
+    vta.testing.run(_run)
+
 
 def test_vta_conv2d():
     def run_vta_conv2d(env, remote, key, batch_size, wl, profile=True):
@@ -54,7 +162,7 @@ def run_vta_conv2d(env, remote, key, batch_size, wl, profile=True):
         assert wl.hpad == wl.wpad
         padding = wl.hpad
 
-        @memoize("vta.tests.test_benchmark_topi.conv2d,verify_nhwc")
+        @memoize("vta.tests.test_benchmark_topi.conv2d.verify_nhwc")
         def get_ref_data():
             a_np = (np.random.uniform(size=a_shape) * 4).astype(data_dtype)
             w_np = (np.random.uniform(size=w_shape) * 4).astype(kernel_dtype)
@@ -153,4 +261,6 @@ def _run(env, remote):
 
 
 if __name__ == "__main__":
+    test_cpu_conv2d()
+    exit(0)
     test_vta_conv2d()
diff --git a/vta/tutorials/resnet.py b/vta/tutorials/resnet.py
index 72ed5d7d2184..ebe580ce184a 100644
--- a/vta/tutorials/resnet.py
+++ b/vta/tutorials/resnet.py
@@ -116,8 +116,8 @@ def generate_graph(graph_fn, params_fn, device="vta"):
     with nnvm.compiler.build_config(opt_level=3):
         if target.device_name != "vta":
             graph, lib, params = nnvm.compiler.build(
-                sym, target_host, shape_dict, dtype_dict,
-                params=params)
+                sym, target, shape_dict, dtype_dict,
+                params=params, target_host=target_host)
         else:
             with vta.build_config():
                 graph, lib, params = nnvm.compiler.build(
@@ -323,4 +323,4 @@ def thumbnailify(image, pad=15):
 
     # When everything done, release the capture
     cap.release()
-    cv2.destroyAllWindows()
\ No newline at end of file
+    cv2.destroyAllWindows()

From 600d106b0893c2cf650a8ed528decafbd3114d17 Mon Sep 17 00:00:00 2001
From: tqchen <tianqi.tchen@gmail.com>
Date: Fri, 6 Jul 2018 19:22:50 -0700
Subject: [PATCH 858/949] [BUILD][DOCS] Migrate VTA CI, test, build, docs

---
 CMakeLists.txt                                |   26 +-
 Jenkinsfile                                   |    4 +-
 Makefile                                      |    8 +-
 apps/pynq_rpc/start_rpc_server.sh             |    5 +
 cmake/modules/VTA.cmake                       |   51 +
 docs/Doxyfile                                 |    2 +-
 docs/Makefile                                 |    2 +
 docs/README.txt                               |   26 +-
 docs/api/python/hybrid.rst                    |    4 -
 docs/api/python/index.rst                     |    1 +
 .../python => docs/api/python/vta}/index.rst  |    4 +-
 docs/conf.py                                  |   10 +-
 docs/index.rst                                |    1 +
 docs/install/index.rst                        |    1 -
 docs/vta/.gitignore                           |    1 +
 docs/vta/hardware.rst                         |    2 +
 docs/vta/index.rst                            |   23 +
 {vta/docs/how_to => docs/vta}/install.md      |  155 +-
 tests/scripts/task_python_vta.sh              |   11 +
 vta/CONTRIBUTORS.md                           |   38 -
 vta/Jenkinsfile                               |  128 -
 vta/LICENSE                                   |  201 --
 vta/Makefile                                  |   71 -
 vta/NEWS.md                                   |   13 -
 vta/README.md                                 |   17 +-
 vta/apps/pynq_rpc/start_rpc_server.sh         |    4 -
 vta/config/README.md                          |    8 +
 .../config.json => config/pynq_sample.json}   |    0
 .../vta_config.json}                          |    0
 vta/{make => config}/vta_config.py            |   31 +-
 vta/docs/.gitignore                           |    5 -
 vta/docs/Doxyfile                             | 2427 -----------------
 vta/docs/Makefile                             |  193 --
 vta/docs/README.txt                           |    5 -
 vta/docs/_static/css/tvm_theme.css            |   11 -
 vta/docs/api_links.rst                        |    6 -
 vta/docs/conf.py                              |  213 --
 vta/docs/dev/index.rst                        |   11 -
 vta/docs/dev/runtime.md                       |    3 -
 vta/docs/how_to/contribute.md                 |  102 -
 vta/docs/index.rst                            |   19 -
 vta/examples/resnet18/pynq/.gitignore         |    5 -
 vta/examples/resnet18/pynq/README.md          |   26 -
 vta/hardware/xilinx/Makefile                  |    2 +-
 vta/make/README.md                            |    8 -
 vta/python/vta/environment.py                 |    9 +-
 vta/python/vta/exec/rpc_server.py             |   20 +-
 vta/python/vta/libinfo.py                     |   25 +
 vta/python/vta/pkg_config.py                  |   10 +-
 vta/python/vta/testing/simulator.py           |   15 +-
 vta/python/vta/testing/util.py                |    2 +-
 vta/python/vta/top/vta_conv2d.py              |   23 +-
 vta/src/device_api.cc                         |    2 +-
 vta/src/runtime.cc                            |   77 +-
 vta/tests/ci_build/Dockerfile.cpu             |   26 -
 vta/tests/ci_build/Dockerfile.lint            |    8 -
 vta/tests/ci_build/README.md                  |   35 -
 vta/tests/ci_build/ci_build.sh                |  126 -
 .../ci_build/install/ubuntu_install_core.sh   |    6 -
 .../ci_build/install/ubuntu_install_llvm.sh   |   22 -
 .../ci_build/install/ubuntu_install_python.sh |   12 -
 .../install/ubuntu_install_python_package.sh  |    3 -
 .../ci_build/install/ubuntu_install_sphinx.sh |    1 -
 vta/tests/ci_build/with_the_same_user         |   34 -
 vta/tests/lint/pylintrc                       |  406 ---
 .../integration/test_benchmark_topi_conv2d.py |    1 -
 .../python/pynq/test_benchmark_conv2d.py      |    3 +-
 vta/tests/python/pynq/test_program_rpc.py     |    2 +-
 vta/tests/python/unittest/test_vta_insn.py    |    2 +-
 vta/tests/scripts/task_build.sh               |   15 -
 vta/tests/scripts/task_clean.sh               |    8 -
 vta/tests/scripts/task_lint.sh                |   15 -
 vta/tests/scripts/task_python_docs.sh         |   17 -
 vta/tests/scripts/task_python_test.sh         |   11 -
 vta/tutorials/README.txt                      |    5 +-
 vta/tutorials/convolution_opt.py              |    7 +-
 vta/tutorials/matrix_multiply.py              |    8 +-
 vta/tutorials/matrix_multiply_opt.py          |   11 +-
 vta/tutorials/resnet.py                       |    3 +-
 .../{get_started.py => vta_get_started.py}    |    6 +-
 80 files changed, 334 insertions(+), 4526 deletions(-)
 create mode 100755 apps/pynq_rpc/start_rpc_server.sh
 create mode 100644 cmake/modules/VTA.cmake
 rename {vta/docs/api/python => docs/api/python/vta}/index.rst (96%)
 create mode 100644 docs/vta/.gitignore
 create mode 100644 docs/vta/hardware.rst
 create mode 100644 docs/vta/index.rst
 rename {vta/docs/how_to => docs/vta}/install.md (71%)
 create mode 100755 tests/scripts/task_python_vta.sh
 delete mode 100644 vta/CONTRIBUTORS.md
 delete mode 100644 vta/Jenkinsfile
 delete mode 100644 vta/LICENSE
 delete mode 100644 vta/Makefile
 delete mode 100644 vta/NEWS.md
 delete mode 100755 vta/apps/pynq_rpc/start_rpc_server.sh
 create mode 100644 vta/config/README.md
 rename vta/{make/config.json => config/pynq_sample.json} (100%)
 rename vta/{make/sim_sample.json => config/vta_config.json} (100%)
 rename vta/{make => config}/vta_config.py (82%)
 delete mode 100644 vta/docs/.gitignore
 delete mode 100644 vta/docs/Doxyfile
 delete mode 100644 vta/docs/Makefile
 delete mode 100644 vta/docs/README.txt
 delete mode 100644 vta/docs/_static/css/tvm_theme.css
 delete mode 100644 vta/docs/api_links.rst
 delete mode 100644 vta/docs/conf.py
 delete mode 100644 vta/docs/dev/index.rst
 delete mode 100644 vta/docs/dev/runtime.md
 delete mode 100644 vta/docs/how_to/contribute.md
 delete mode 100644 vta/docs/index.rst
 delete mode 100644 vta/examples/resnet18/pynq/.gitignore
 delete mode 100644 vta/examples/resnet18/pynq/README.md
 delete mode 100644 vta/make/README.md
 create mode 100644 vta/python/vta/libinfo.py
 delete mode 100644 vta/tests/ci_build/Dockerfile.cpu
 delete mode 100644 vta/tests/ci_build/Dockerfile.lint
 delete mode 100644 vta/tests/ci_build/README.md
 delete mode 100755 vta/tests/ci_build/ci_build.sh
 delete mode 100644 vta/tests/ci_build/install/ubuntu_install_core.sh
 delete mode 100644 vta/tests/ci_build/install/ubuntu_install_llvm.sh
 delete mode 100644 vta/tests/ci_build/install/ubuntu_install_python.sh
 delete mode 100644 vta/tests/ci_build/install/ubuntu_install_python_package.sh
 delete mode 100644 vta/tests/ci_build/install/ubuntu_install_sphinx.sh
 delete mode 100644 vta/tests/ci_build/with_the_same_user
 delete mode 100644 vta/tests/lint/pylintrc
 delete mode 100755 vta/tests/scripts/task_build.sh
 delete mode 100755 vta/tests/scripts/task_clean.sh
 delete mode 100755 vta/tests/scripts/task_lint.sh
 delete mode 100755 vta/tests/scripts/task_python_docs.sh
 delete mode 100755 vta/tests/scripts/task_python_test.sh
 rename vta/tutorials/{get_started.py => vta_get_started.py} (99%)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7edaff4bc83d..889b285c02a2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -8,11 +8,6 @@ include(cmake/util/FindVulkan.cmake)
 include(cmake/util/FindLLVM.cmake)
 include(cmake/util/FindROCM.cmake)
 
-
-if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/build/private/local_config.cmake)
-  include(${CMAKE_CURRENT_SOURCE_DIR}/build/private/local_config.cmake)
-endif()
-
 if(EXISTS ${CMAKE_CURRENT_BINARY_DIR}/config.cmake)
   include(${CMAKE_CURRENT_BINARY_DIR}/config.cmake)
 else()
@@ -40,6 +35,8 @@ tvm_option(USE_RTTI "Build with RTTI" ON)
 tvm_option(USE_MSVC_MT "Build with MT" OFF)
 tvm_option(INSTALL_DEV "Install compiler infrastructure" OFF)
 
+tvm_option(USE_VTA_CFG "Use a specific json file for VTA runtime" "")
+
 # Contrib library options
 tvm_option(USE_BLAS "The blas library to be linked" none)
 tvm_option(USE_MKL_PATH "MKL root path when use MKL blas" none)
@@ -52,8 +49,9 @@ tvm_option(USE_NNPACK "Build with nnpack support" OFF)
 tvm_option(USE_RANDOM "Build with random support" OFF)
 
 # include directories
-include_directories(BEFORE "nnvm/include")
 include_directories("include")
+include_directories("nnvm/include")
+include_directories("dmlc-core/include")
 include_directories("HalideIR/src")
 include_directories("dlpack/include")
 include_directories("topi/include")
@@ -148,20 +146,8 @@ if(USE_GRAPH_RUNTIME)
   endif(USE_GRAPH_RUNTIME_DEBUG)
 endif(USE_GRAPH_RUNTIME)
 
-if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/dmlc-core/CMakeLists.txt)
-  include_directories(${CMAKE_CURRENT_SOURCE_DIR}/dmlc-core/include)
-  if (INSTALL_DEV)
-    install(
-      DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/dmlc-core/include/." DESTINATION "include"
-      FILES_MATCHING
-      PATTERN "*.h"
-    )
-  endif()
-elseif(DMLC_CORE_PATH)
-  include_directories(${DMLC_CORE_PATH}/include)
-endif()
-
 # Module rules
+include(cmake/modules/VTA.cmake)
 include(cmake/modules/CUDA.cmake)
 include(cmake/modules/OpenCL.cmake)
 include(cmake/modules/OpenGL.cmake)
@@ -174,7 +160,6 @@ include(cmake/modules/contrib/Random.cmake)
 include(cmake/modules/contrib/Sort.cmake)
 include(cmake/modules/contrib/NNPack.cmake)
 
-# Target rrules
 add_library(tvm SHARED ${COMPILER_SRCS} ${RUNTIME_SRCS})
 add_library(tvm_topi SHARED ${TOPI_SRCS})
 add_library(tvm_runtime SHARED ${RUNTIME_SRCS})
@@ -207,7 +192,6 @@ endif()
 # Custom targets
 add_custom_target(runtime DEPENDS tvm_runtime)
 
-
 # Installation rulse
 install(TARGETS tvm_runtime DESTINATION lib${LIB_SUFFIX})
 if(WIN32)
diff --git a/Jenkinsfile b/Jenkinsfile
index 5e5837151497..27236852dd54 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -8,7 +8,7 @@ tvm_runtime = "build/libtvm_runtime.so, build/config.cmake"
 tvm_lib = "build/libtvm.so, " + tvm_runtime
 // LLVM upstream lib
 tvm_multilib = "build/libtvm.so, " +
-             "build/libtvm_topi.so, build/libnnvm_compiler.so, " + tvm_runtime
+             "build/libvta.so, build/libtvm_topi.so, build/libnnvm_compiler.so, " + tvm_runtime
 
 // command to start a docker container
 docker_run = 'docker/build.sh'
@@ -134,6 +134,7 @@ stage('Build') {
         pack_lib('cpu', tvm_lib)
         timeout(time: max_time, unit: 'MINUTES') {
           sh "${docker_run} ci_cpu ./tests/scripts/task_cpp_unittest.sh"
+          sh "${docker_run} ci_cpu ./tests/scripts/task_python_vta.sh"
         }
       }
     }
@@ -179,6 +180,7 @@ stage('Unit Test') {
         timeout(time: max_time, unit: 'MINUTES') {
           sh "${docker_run} ci_i386 ./tests/scripts/task_python_unittest.sh"
           sh "${docker_run} ci_i386 ./tests/scripts/task_python_integration.sh"
+          sh "${docker_run} ci_i386 ./tests/scripts/task_python_vta.sh"
         }
       }
     }
diff --git a/Makefile b/Makefile
index 748f8260b959..a3ff3d7074dd 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
 ROOTDIR = $(CURDIR)
 
 .PHONY: clean all test doc pylint cpplint lint\
-	 cython cython2 cython3 web runtime
+	 cython cython2 cython3 web runtime vta
 
 ifndef DMLC_CORE_PATH
   DMLC_CORE_PATH = $(ROOTDIR)/dmlc-core
@@ -20,9 +20,11 @@ all:
 	@mkdir -p build && cd build && cmake .. && $(MAKE)
 
 runtime:
-
 	@mkdir -p build && cd build && cmake .. && $(MAKE) runtime
 
+vta:
+	@mkdir -p build && cd build && cmake .. && $(MAKE) vta
+
 cpptest:
 	@mkdir -p build && cd build && cmake .. && $(MAKE) cpptest
 
@@ -48,6 +50,7 @@ build/libtvm_web_runtime.js: build/libtvm_web_runtime.bc
 
 # Lint scripts
 cpplint:
+	python3 dmlc-core/scripts/lint.py vta cpp vta/include vta/src
 	python3 dmlc-core/scripts/lint.py topi cpp topi/include;
 	python3 dmlc-core/scripts/lint.py nnvm cpp nnvm/include nnvm/src;
 	python3 dmlc-core/scripts/lint.py tvm cpp include src verilog\
@@ -57,6 +60,7 @@ pylint:
 	python3 -m pylint python/tvm --rcfile=$(ROOTDIR)/tests/lint/pylintrc
 	python3 -m pylint topi/python/topi --rcfile=$(ROOTDIR)/tests/lint/pylintrc
 	python3 -m pylint nnvm/python/nnvm --rcfile=$(ROOTDIR)/tests/lint/pylintrc
+	python3 -m pylint vta/python/vta --rcfile=$(ROOTDIR)/tests/lint/pylintrc
 
 jnilint:
 	python3 dmlc-core/scripts/lint.py tvm4j-jni cpp jvm/native/src
diff --git a/apps/pynq_rpc/start_rpc_server.sh b/apps/pynq_rpc/start_rpc_server.sh
new file mode 100755
index 000000000000..30b3c9a90d6b
--- /dev/null
+++ b/apps/pynq_rpc/start_rpc_server.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+PROJROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../../" && pwd )"
+
+export PYTHONPATH=${PYTHONPATH}:${PROJROOT}/python:${PROJROOT}/vta/python
+python -m vta.exec.rpc_server
diff --git a/cmake/modules/VTA.cmake b/cmake/modules/VTA.cmake
new file mode 100644
index 000000000000..1356c8560f3b
--- /dev/null
+++ b/cmake/modules/VTA.cmake
@@ -0,0 +1,51 @@
+# CMake Build rules for VTA
+find_program(PYTHON python)
+
+if(MSVC)
+  message(STATUS "VTA build is skipped in Windows..")
+elseif(PYTHON)
+  set(VTA_CONFIG ${PYTHON} ${CMAKE_CURRENT_SOURCE_DIR}/vta/config/vta_config.py)
+
+  if(EXISTS ${CMAKE_CURRENT_BINARY_DIR}/vta_config.json)
+    message(STATUS "Use VTA config " ${CMAKE_CURRENT_BINARY_DIR}/vta_config.json)
+    set(VTA_CONFIG ${PYTHON} ${CMAKE_CURRENT_SOURCE_DIR}/vta/config/vta_config.py
+      --use-cfg=${CMAKE_CURRENT_BINARY_DIR}/vta_config.json)
+  endif()
+
+  execute_process(COMMAND ${VTA_CONFIG} --target OUTPUT_VARIABLE __vta_target)
+  string(STRIP ${__vta_target} VTA_TARGET)
+
+  message(STATUS "Build VTA runtime with target: " ${VTA_TARGET})
+
+  execute_process(COMMAND ${VTA_CONFIG} --defs OUTPUT_VARIABLE __vta_defs)
+
+  string(REGEX MATCHALL "(^| )-D[A-Za-z0-9_=.]*" VTA_DEFINITIONS "${__vta_defs}")
+
+  file(GLOB VTA_RUNTIME_SRCS vta/src/*.cc)
+  file(GLOB __vta_target_srcs vta/src/${VTA_TARGET}/*.cc)
+  list(APPEND VTA_RUNTIME_SRCS ${__vta_target_srcs})
+
+  add_library(vta SHARED ${VTA_RUNTIME_SRCS})
+
+  target_include_directories(vta PUBLIC vta/include)
+
+  foreach(__def ${VTA_DEFINITIONS})
+    string(SUBSTRING ${__def} 3 -1 __strip_def)
+    target_compile_definitions(vta PUBLIC ${__strip_def})
+  endforeach()
+
+  if(APPLE)
+    set_target_properties(vta PROPERTIES LINK_FLAGS "-undefined dynamic_lookup")
+  endif(APPLE)
+
+  # PYNQ rules
+  if(${VTA_TARGET} STREQUAL "pynq")
+    find_library(__sds_lib NAMES sds_lib PATHS /usr/lib)
+    find_library(__dma_lib NAMES dma PATHS
+      "/opt/python3.6/lib/python3.6/site-packages/pynq/drivers/"
+      "/opt/python3.6/lib/python3.6/site-packages/pynq/lib/")
+    target_link_libraries(vta ${__sds_lib} ${__dma_lib})
+  endif()
+else()
+  message(STATUS "Cannot found python in env, VTA build is skipped..")
+endif()
diff --git a/docs/Doxyfile b/docs/Doxyfile
index 1ef942620386..7bb47ccab4c5 100644
--- a/docs/Doxyfile
+++ b/docs/Doxyfile
@@ -753,7 +753,7 @@ WARN_LOGFILE           =
 # spaces.
 # Note: If this tag is empty the current directory is searched.
 
-INPUT                  = include/tvm topi/include/topi nnvm/include/nnvm
+INPUT                  = include/tvm topi/include/topi nnvm/include/nnvm vta/include/vta
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
diff --git a/docs/Makefile b/docs/Makefile
index 21f13caa8950..d7a12839ba3d 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -46,6 +46,8 @@ help:
 clean:
 	rm -rf $(BUILDDIR)/*
 	rm -rf gen_modules
+	rm -rf tutorials
+	rm -rf vta/tutorials
 
 html:
 	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
diff --git a/docs/README.txt b/docs/README.txt
index 8f62dbacc58e..fffdaa233ef8 100644
--- a/docs/README.txt
+++ b/docs/README.txt
@@ -1,6 +1,28 @@
-The documentation of tvm is generated with recommonmark and sphinx.
+TVM Documentations
+==================
+This folder contains the source of TVM documents
 
 - A hosted version of doc is at http://docs.tvm.ai
-- pip install sphinx>=1.5.5 sphinx-gallery sphinx_rtd_theme matplotlib Image recommonmark
+- pip install sphinx>=1.5.5 sphinx-gallery sphinx_rtd_theme matplotlib Image recommonmark Pillow
 - Build tvm first in the root folder.
 - To build locally, you need to enable USE_CUDA, USE_OPENCL, LLVM_CONFIG in config.mk and then type "make html" in this folder.
+
+Only Execute Specified Tutorials
+--------------------------------
+The document build process will execute all the tutorials in the sphinx gallery.
+This will cause failure in some cases when certain machines do not have necessary
+environment. You can set ```TVM_TUTORIAL_EXEC_PATTERN``` to only execute
+the path that matches the regular expression pattern.
+
+For example, to only build tutorials under /vta/tutorials, run
+
+```bash
+TVM_TUTORIAL_EXEC_PATTERN=/vta/tutorials make html
+```
+
+To only build one specific file, do
+
+```bash
+# The slash \ is used to get . in regular expression
+TVM_TUTORIAL_EXEC_PATTERN=file_name\.py make html
+```
diff --git a/docs/api/python/hybrid.rst b/docs/api/python/hybrid.rst
index 3b4c598d82dd..ac4111cfe768 100644
--- a/docs/api/python/hybrid.rst
+++ b/docs/api/python/hybrid.rst
@@ -6,10 +6,6 @@ tvm.hybrid
 
    tvm.hybrid.parse
    tvm.hybrid.script
-   tvm.hybrid.popcount
-   tvm.hybrid.sigmoid
 
 .. autofunction:: tvm.hybrid.parse
 .. autofunction:: tvm.hybrid.script
-.. autofunction:: tvm.hybrid.popcount
-.. autofunction:: tvm.hybrid.sigmoid
diff --git a/docs/api/python/index.rst b/docs/api/python/index.rst
index bab29b82f473..ea304ecbf0a8 100644
--- a/docs/api/python/index.rst
+++ b/docs/api/python/index.rst
@@ -20,5 +20,6 @@ Python API
    contrib
    dev
    topi
+   vta/index
    nnvm/index
    hybrid
diff --git a/vta/docs/api/python/index.rst b/docs/api/python/vta/index.rst
similarity index 96%
rename from vta/docs/api/python/index.rst
rename to docs/api/python/vta/index.rst
index 4555a739a8d8..014b789e5aa0 100644
--- a/vta/docs/api/python/index.rst
+++ b/docs/api/python/vta/index.rst
@@ -1,5 +1,5 @@
-Python API
-==========
+VTA API
+=======
 
 This document contains the python API to VTA compiler toolchain.
 
diff --git a/docs/conf.py b/docs/conf.py
index 00089636b3af..f13721aa3d62 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -26,6 +26,7 @@
 sys.path.insert(0, os.path.join(curr_path, '../python/'))
 sys.path.insert(0, os.path.join(curr_path, '../topi/python'))
 sys.path.insert(0, os.path.join(curr_path, '../nnvm/python'))
+sys.path.insert(0, os.path.join(curr_path, '../vta/python'))
 
 # -- General configuration ------------------------------------------------
 
@@ -184,16 +185,17 @@ def run_doxygen(folder):
 
 from sphinx_gallery.sorting import ExplicitOrder
 
-examples_dirs = ['../tutorials/']
-gallery_dirs = ['tutorials']
+examples_dirs = ["../tutorials/", "../vta/tutorials/"]
+gallery_dirs = ["tutorials", "vta/tutorials"]
+
 subsection_order = ExplicitOrder(
     ['../tutorials/language',
      '../tutorials/optimize',
+     '../tutorials/vta',
      '../tutorials/topi',
      '../tutorials/deployment',
      '../tutorials/nnvm'])
 
-
 def generate_doxygen_xml(app):
     """Run the doxygen make commands if we're on the ReadTheDocs server"""
     run_doxygen('..')
@@ -220,7 +222,7 @@ def setup(app):
     'examples_dirs': examples_dirs,
     'gallery_dirs': gallery_dirs,
     'subsection_order': subsection_order,
+    'filename_pattern': os.environ.get("TVM_TUTORIAL_EXEC_PATTERN", ".py"),
     'find_mayavi_figures': False,
-    'filename_pattern': '.py',
     'expected_failing_examples': []
 }
diff --git a/docs/index.rst b/docs/index.rst
index e3e7f48044ab..20e64bfef641 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -8,6 +8,7 @@ Get Started
 
    install/index
    tutorials/index
+   vta/index
    deploy/index
    contribute/index
    faq
diff --git a/docs/install/index.rst b/docs/install/index.rst
index d351c383476b..cc39f2433c7e 100644
--- a/docs/install/index.rst
+++ b/docs/install/index.rst
@@ -1,6 +1,5 @@
 Installation
 ============
-
 To install TVM, please read :ref:`install-from-source`.
 If you are interested in deploying to mobile/embedded devices,
 you do not need to install the entire tvm stack on your device,
diff --git a/docs/vta/.gitignore b/docs/vta/.gitignore
new file mode 100644
index 000000000000..a07068979a60
--- /dev/null
+++ b/docs/vta/.gitignore
@@ -0,0 +1 @@
+tutorials
\ No newline at end of file
diff --git a/docs/vta/hardware.rst b/docs/vta/hardware.rst
new file mode 100644
index 000000000000..294b99a8269f
--- /dev/null
+++ b/docs/vta/hardware.rst
@@ -0,0 +1,2 @@
+VTA Hardware Design Overview
+============================
diff --git a/docs/vta/index.rst b/docs/vta/index.rst
new file mode 100644
index 000000000000..fa20cf779065
--- /dev/null
+++ b/docs/vta/index.rst
@@ -0,0 +1,23 @@
+VTA: Deep Learning Accelerator Stack
+====================================
+Specialized accelerators are key enablers of future deep learning workloads. TVM stack targets specialized accelerators.
+VTA(versatile tensor accelerator) is a generic, modular open-source deep learning accelerator.
+This page contains links to all the resources related to VTA:
+
+.. toctree::
+   :maxdepth: 1
+
+   install
+   tutorials/index
+   hardware
+
+
+Features
+--------
+VTA have the following key features:
+
+- Generic, modular open-source hardware
+- Streamlined workflow to deploy to FPGAs.
+- Simulator support to protoype compilation passes on regular workstations.
+- Driver and JIT runtime for both simulated and FPGA hardware backend.
+- End to end TVM stack integration
diff --git a/vta/docs/how_to/install.md b/docs/vta/install.md
similarity index 71%
rename from vta/docs/how_to/install.md
rename to docs/vta/install.md
index 816dd6e307ea..f05a0ccad3ed 100644
--- a/vta/docs/how_to/install.md
+++ b/docs/vta/install.md
@@ -1,5 +1,5 @@
-Installation Guides
-===================
+VTA Installation Guide
+======================
 
 We present three installation guides, each extending on the previous one:
 1. VTA simulation-only installation
@@ -8,120 +8,41 @@ We present three installation guides, each extending on the previous one:
 
 ## VTA Simulation-Only Installation
 
-This first guide details the installation of the VTA package to run hardware simulation tests locally on your development machine (in case you don't own the Pynq FPGA development board).
-This guide includes:
-1. Software dependences installation
-2. Simulation library compilation
-3. Python package installation
-4. Test examples to ensure that the VTA package was correctly installed
-
-To get started, clone vta repo from [github](https://github.com/uwsaml/vta). It is important to clone the submodules along with ```--recursive``` option.
-```bash
-git clone --recursive https://github.com/uwsaml/vta
-```
-
-### VTA Dependences
-
-The VTA package depends on several other packages that need to be manually installed beforehand.
-
-We list the dependences below:
-* LLVM 4.0 or newer
-* TVM
-* MxNet (to run the end-to-end examples)
-* Additional python packages
-
-#### LLVM Installation
-
-We provide the set of commands to install LLVM 6.0 (stable branch) on Ubuntu Xenial. Note that the [LLVM installation process](apt.llvm.org) can be adapted to different LLVM branches, and operating systems.
-
-```bash
-wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
-sudo apt-add-repository "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-6.0 main"
-sudo apt-get update
-apt-get install clang-6.0 lldb-6.0 lld-6.0
-```
-
-To ensure that LLVM 6.0 was properly installed, check that the following command gives the path to your `llvm-config` binary (you may have to append the version number to the executable name):
+Please follow the guide on install TVM from source.
+VTA simulator is library is built by default along with TVM.
+You only have to add vta to your python path.
 
 ```bash
-which llvm-config-6.0
+export PYTHONPATH=/path/to/vta/python:${PYTHONPATH}
 ```
 
-#### TVM Installation
-
-TVM is included as a top-level submodule to VTA, and can be found under `<vta root>/tvm`.
+### Testing your VTA Simulation Setup
 
-Follow the [installation instructions](https://docs.tvm.ai/install/index.html).
+Finally to ensure that you've properly installed the VTA package, we can run simple unit tests and the ResNet-18 inference example.
 
-In the 'config.mk' file, make sure that:
-* `LLVM_CONFIG` points to the `llvm-config` executable which path was derived in the LLVM installation instructions above (e.g. `LLVM_CONFIG = /usr/bin/llvm-config-6.0`)
-* `USE_RPC` should be set to 1
+Let's first run the 2D convolution test bench that will only run the ResNet-18 convolution layers.
 
-For the *Python Package Installation*, we recommend updating your `~/.bashrc` file to extend your `PYTHONPATH` with the TVM Python libraries.
 ```bash
-export PYTHONPATH=<tvm root>/python:<tvm root>/topi/python:<tvm root>/nnvm/python:${PYTHONPATH}
+python vta/tests/python/integration/test_benchmark_topi_conv2d.py
 ```
 
-#### MxNet Installation
-
-Follow the [MxNet Installation Instructions](https://mxnet.incubator.apache.org)
+> Note: You'll notice that for every convolution layer, the throughput gets reported in GOPS. These numbers are actually the computational throughput that the simulator achieves, by evaluating the convolution in software. You can also try out other tutorials.
 
-#### Python Dependences
 
-You'll need the following packages to be installed for the example to run properly. You can use `pip` to install those packages:
-* `decorator`
-* `enum34`
-* `Pillow`
-* `wget`
+### Advanced Configuration
 
-### VTA Shared Library Compilation
-
-Before building the VTA shared library, the VTA configuration can be modified by changing `config.json` file.
+VTA is a generic configurable hardware. The configuration is specified by a `vta_config.json` under root of the TVM folder.
 This file provides an architectural specification of the VTA accelerator that can be understood by both the TVM compiler stack and the VTA hardware stack.
 It also specifies the TVM compiler target. When `TARGET` is set to `sim`, it tells the TVM compiler to execute the TVM workloads on the VTA simulator.
-
-To build the simulator library, copy the simulation configuration file `make/sim_sample.json` to the project root.
-Next, you can build the VTA simulation dynamic library with `make`.
+You can modify the content to reconfigure VTA to a different mode. To do so,
 
 ```bash
-cd <vta root>
-cp make/sim_sample.json config.json
-make -j4
+cd <tvm root>
+cp vta/config/vta_config.json vta_config.json
+edit vta_config.json
+make vta
 ```
 
-### VTA Python Package Installation
-
-The Python package can installed by extending your `PYTHONPATH` environment variable to point to the VTA python library path.
-You can run the following line in a terminal, or add it to your `~/.bashrc` file if you plan on using VTA regularly.
-
-```bash
-export PYTHONPATH=<vta root>/python:${PYTHONPATH}
-```
-
-### Testing your VTA Simulation Setup
-
-Finally to ensure that you've properly installed the VTA package, we can run simple unit tests and the ResNet-18 inference example.
-
-Let's first run the 2D convolution test bench that will only run the ResNet-18 convolution layers.
-
-```bash
-python tests/python/integration/test_benchmark_topi_conv2d.py
-```
-
-> Note: You'll notice that for every convolution layer, the throughput gets reported in GOPS. These numbers are actually the computational throughput that the simulator achieves, by evaluating the convolution in software.
-
-Next we can also run the ResNet-18 end to end example in the VTA simulator.
-This test will download the following files in your root:
-* `cat.jpg` the test image to classify
-* `synset.txt` the ImageNet categories
-* `quantize_graph.json` the 8-bit ResNet-18 inference NNVM graph
-* `quantize_params.plk` the 8-bit ResNet-18 model parameters
-
-```bash
-python examples/resnet18/pynq/imagenet_predict.py
-```
-> Note: This will run ResNet inference by offloading the compute-heavy convolution layers to the VTA simulator, and report the top-1 category, and the inference time cost in seconds.
-
 ## VTA Pynq-Based Testing Setup
 
 This second guide extends the *VTA Simulation-Only Installation* guide above to allow FPGA-based hardware tests of the full TVM and VTA software-hardware stack.
@@ -157,7 +78,7 @@ Because the direct board-to-computer connection prevents the board from directly
 mkdir <mountpoint>
 sshfs xilinx@192.168.2.99:/home/xilinx <mountpoint>
 cd <mountpoint>
-git clone --recursive https://github.com/uwsaml/vta
+git clone --recursive https://github.com/dmlc/tvm
 # When finished, you can leave the moutpoint and unmount the directory
 cd ~
 sudo umount <mountpoint>
@@ -169,20 +90,24 @@ The build process should take roughly 5 minutes.
 ```bash
 ssh xilinx@192.168.2.99
 # Build TVM runtime library (takes 5 mins)
-cd /home/xilinx/vta/tvm
+cd /home/xilinx/tvm/vta
 mkdir build
 cp cmake/config.cmake build/.
+# copy pynq specific configuration
+cp vta/config/pynq_sample.json build/vta_config.json
 cd build
 cmake ..
-make runtime -j2
+make runtime vta -j2
 # Build VTA RPC server (takes 1 min)
-cd /home/xilinx/vta
+cd ..
 sudo ./apps/pynq_rpc/start_rpc_server.sh # pw is 'xilinx'
 ```
 
+Note that one key difference between the simulator build is that we changed the VTA configuration
+to be `vta/config/pynq_sample.json`, which specifies PYNQ as target.
+
 You should see the following being displayed when starting the RPC server. In order to run the next examples, you'll need to leave the RPC server running in an `ssh` session.
 ```
-INFO:root:Load additional library /home/xilinx/vta/lib/libvta.so
 INFO:root:RPCServer: bind to 0.0.0.0:9091
 ```
 
@@ -199,12 +124,12 @@ export VTA_PYNQ_RPC_HOST=192.168.2.99
 export VTA_PYNQ_RPC_PORT=9091
 ```
 
-In addition, you'll need to edit the `config.json` file to indicate that we are targeting the Pynq platform, by setting the `TARGET` field to the `"pynq"` value. Alternatively, you can copy the default `make/config.json` into the VTA root.
+In addition, you'll need to edit the `vta_config.json` file to indicate that we are targeting the Pynq platform, by setting the `TARGET` field to the `"pynq"` value. Alternatively, you can copy the default `make/config.json` into the VTA root.
 > Note: in contrast to our simulation setup, there are no libraries to compile on the host side since the host offloads all of the computation to the Pynq board.
 
 ```bash
-cd <vta root>
-cp make/config.json .
+cd <tvm root>
+cp vta/config/pynq_sample.json .
 ```
 
 This time again, we will run the 2D convolution testbench. But beforehand, we'll need to program the Pynq's own FPGA with a VTA bitstream, and build the VTA runtime on the Pynq via RPC. The following `test_program_rpc.py` script will perform two operations:
@@ -224,13 +149,8 @@ python tests/python/pynq/test_benchmark_conv2d.py
 ```
 
 The performance metrics measured on the Pynq board will be reported for each convolutional layer.
+You can also try out other tutorials.
 
-Finally, we run the ResNet-18 end-to-end example on the Pynq.
-
-```bash
-python examples/resnet18/pynq/imagenet_predict.py
-```
-This will run ResNet inference by offloading the compute-heavy convolution layers to the Pynq's FPGA-based VTA accelerator. The time cost is also measured in seconds here.
 
 ## VTA Hardware Toolchain Installation
 
@@ -296,7 +216,7 @@ export PATH=${XILINX_SDK}/bin:${PATH}
 
 ### Custom VTA Bitstream Compilation
 
-High-level parameters are listed under `<vta root>/make/config.json` and can be customized by the user. For this custom VTA Bitstream Compilation exercise, we'll change the frequency of our design, so it can be clocked a little faster.
+High-level parameters are listed under `tvm/vta/config/vta_config.json` and can be customized by the user. For this custom VTA Bitstream Compilation exercise, we'll change the frequency of our design, so it can be clocked a little faster.
 * Set the `HW_FREQ` field to `142`. The Pynq board supports 100, 142, 167 and 200MHz clocks. Note that the higher the frequency, the harder it will be to close timing. Increasing the frequency can lead to timing violation and thus faulty hardware.
 * Set the `HW_CLK_TARGET` to `6`. This parameters refers to the target clock period in ns passed to HLS - a lower clock period leads to more aggressive pipelining to achieve timing closure at higher frequencies. Technically a 142MHz clock would require a 7ns target, but we intentionally lower the clock target to 6ns to more aggressively pipeline our design.
 
@@ -325,20 +245,13 @@ This process is lenghty, and can take around up to an hour to complete depending
 
 Once the compilation completes, the generated bitstream can be found under `<vta root>/build/hardware/xilinx/vivado/<configuration>/export/vta.bit`.
 
-### End-to-end ResNet-18 Example with the Custom Bitstream
+### Use the Custom Bitstream
 
-Let's run the ResNet-18 example with our newly generated bitstream.
-
-In `<vta root>/examples/resnet18/pynq/imagenet_predict.py`, change the line:
-```python
-vta.program_fpga(remote, bitstream=None)
-```
-to
+We can change the FPGA bitstream by simply change the bistream path to the configuring API.
 
 ```python
 vta.program_fpga(remote, bitstream="<vta root>/build/hardware/xilinx/vivado/<configuration>/export/vta.bit")
 ```
 
 Instead of downloading the bitstream from the bitstream repository, the programmer will instead use the custom bitstream you just generated, which is a VTA design clocked at a higher frequency.
-
 Do you observe a noticable performance increase on the ImageNet inference workload?
diff --git a/tests/scripts/task_python_vta.sh b/tests/scripts/task_python_vta.sh
new file mode 100755
index 000000000000..cd2e0d0b9bae
--- /dev/null
+++ b/tests/scripts/task_python_vta.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+export PYTHONPATH=python:nnvm/python:vta/python:topi/python
+
+echo "Running unittest..."
+python -m nose -v vta/tests/python/unittest || exit -1
+python3 -m nose -v vta/tests/python/unittest || exit -1
+
+echo "Running integration test..."
+python -m nose -v vta/tests/python/integration || exit -1
+python3 -m nose -v vta/tests/python/integration || exit -1
diff --git a/vta/CONTRIBUTORS.md b/vta/CONTRIBUTORS.md
deleted file mode 100644
index 0df700b2d368..000000000000
--- a/vta/CONTRIBUTORS.md
+++ /dev/null
@@ -1,38 +0,0 @@
-Contributing to VTA
-===================
-VTA is part of TVM software/hardware stack.
-We adopts Apache style committer model.
-The package is developed and used by the community.
-
-We actively seek committers that come from community contributors who:
-- Made substantial contributions to the project.
-  - All forms of contributions are valued (see detail in next section).
-- Willing to spend time on maintaining and lead the project.
-
-Contributions
--------------
-We value all forms of contributions, here is a non-comprehensive
-list of contributions that are welcomed
-
-- Documentation and usage examples
-- Hardware implementations of the design.
-- Community participation, answering questions and issues.
-- Code readability and developer guide
-  - We welcome contributions that add code comments
-    to improve readability
-  - We also welcome contributions to docs to explain the
-    design choices of the internal.
-- Test cases to make the codebase more robust
-- Tutorials, blog posts, talks that promote the project.
-
-
-How to Contribute
------------------
-See [Contributor guide](docs/how_to/contribute.md) on how to contribute.
-
-Committers
-----------
-Committers are people who have made substantial contribution to the project and granted write access to the project.
-
-- [Thierry Moreau](http://homes.cs.washington.edu/~moreau/), University of Washington
-- [Tianqi Chen](https://github.com/tqchen), University of Washington
diff --git a/vta/Jenkinsfile b/vta/Jenkinsfile
deleted file mode 100644
index 526cdcaffbc4..000000000000
--- a/vta/Jenkinsfile
+++ /dev/null
@@ -1,128 +0,0 @@
-#!groovy
-// -*- mode: groovy -*-
-// Jenkins pipeline
-// See documents at https://jenkins.io/doc/book/pipeline/jenkinsfile/
-
-// nnvm libraries
-vta_lib = "lib/libvta.so, lib/libvta.so.json, config.json"
-vta_lib += ", tvm/build/libtvm.so, tvm/build/libtvm_topi.so, tvm/build/libnnvm_compiler.so"
-
-
-// command to start a docker container
-docker_run = 'tests/ci_build/ci_build.sh'
-// timeout in minutes
-max_time = 60
-
-// initialize source codes
-def init_git() {
-  checkout scm
-  retry(5) {
-    timeout(time: 2, unit: 'MINUTES') {
-      sh 'git submodule update --init --recursive'
-    }
-  }
-}
-
-def init_git_win() {
-    checkout scm
-    retry(5) {
-        timeout(time: 2, unit: 'MINUTES') {
-            bat 'git submodule update --init --recursive'
-        }
-    }
-}
-
-stage("Sanity Check") {
-  timeout(time: max_time, unit: 'MINUTES') {
-    node('linux') {
-      ws('workspace/vta/sanity') {
-        init_git()
-        sh "${docker_run} lint  ./tests/scripts/task_lint.sh"
-      }
-    }
-  }
-}
-
-// Run make. First try to do an incremental make from a previous workspace in hope to
-// accelerate the compilation. If something wrong, clean the workspace and then
-// build from scratch.
-def make(docker_type, make_flag) {
-  timeout(time: max_time, unit: 'MINUTES') {
-    sh "${docker_run} ${docker_type} cp make/sim_sample.json config.json"
-    try {
-      sh "${docker_run} ${docker_type} ./tests/scripts/task_build.sh ${make_flag}"
-    } catch (exc) {
-      echo 'Incremental compilation failed. Fall back to build from scratch'
-      sh "${docker_run} ${docker_type} ./tests/scripts/task_clean.sh"
-      sh "${docker_run} ${docker_type} ./tests/scripts/task_build.sh ${make_flag}"
-    }
-  }
-}
-
-// pack libraries for later use
-def pack_lib(name, libs) {
-  sh """
-     echo "Packing ${libs} into ${name}"
-     echo ${libs} | sed -e 's/,/ /g' | xargs md5sum
-     """
-  stash includes: libs, name: name
-}
-
-
-// unpack libraries saved before
-def unpack_lib(name, libs) {
-  unstash name
-  sh """
-     echo "Unpacked ${libs} from ${name}"
-     echo ${libs} | sed -e 's/,/ /g' | xargs md5sum
-     """
-}
-
-stage('Build') {
-  timeout(time: max_time, unit: 'MINUTES') {
-    node('linux') {
-      ws('workspace/vta/build') {
-        init_git()
-        make('cpu', '-j2')
-        pack_lib('cpu', vta_lib)
-      }
-    }
-  }
-}
-
-stage('Tests') {
-  parallel 'python': {
-    node('linux') {
-      ws('workspace/vta/it-python') {
-        init_git()
-        unpack_lib('cpu', vta_lib)
-        timeout(time: max_time, unit: 'MINUTES') {
-          sh "${docker_run} cpu ./tests/scripts/task_python_test.sh"
-        }
-      }
-    }
-  },
-  'docs': {
-    node('linux') {
-      ws('workspace/vta/docs-python') {
-        init_git()
-        unpack_lib('cpu', vta_lib)
-        timeout(time: max_time, unit: 'MINUTES') {
-          sh "${docker_run} cpu ./tests/scripts/task_python_docs.sh"
-        }
-        pack_lib('mydocs', 'docs.tgz')
-      }
-    }
-  }
-}
-
-stage('Deploy') {
-    node('docker' && 'doc') {
-      ws('workspace/vta/deploy-docs') {
-        if (env.BRANCH_NAME == "master") {
-           unpack_lib('mydocs', 'docs.tgz')
-           sh "tar xf docs.tgz -C /var/vta-docs"
-        }
-      }
-    }
-}
diff --git a/vta/LICENSE b/vta/LICENSE
deleted file mode 100644
index 261eeb9e9f8b..000000000000
--- a/vta/LICENSE
+++ /dev/null
@@ -1,201 +0,0 @@
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed 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.
diff --git a/vta/Makefile b/vta/Makefile
deleted file mode 100644
index c43c49bddaca..000000000000
--- a/vta/Makefile
+++ /dev/null
@@ -1,71 +0,0 @@
-ROOTDIR = $(CURDIR)
-
-export LDFLAGS = -pthread -lm
-export CFLAGS = -std=c++11 -Wall -O2 -Iinclude -fPIC
-
-VTA_CONFIG = python make/vta_config.py
-CFLAGS += `${VTA_CONFIG} --cflags`
-LDFLAGS += `${VTA_CONFIG} --ldflags`
-VTA_TARGET := $(shell ${VTA_CONFIG} --target)
-
-UNAME_S := $(shell uname -s)
-
-ifeq ($(UNAME_S), Darwin)
-	SHARED_LIBRARY_SUFFIX := dylib
-	WHOLE_ARCH= -all_load
-	NO_WHOLE_ARCH= -noall_load
-	LDFLAGS += -undefined dynamic_lookup
-else
-	SHARED_LIBRARY_SUFFIX := so
-	WHOLE_ARCH= --whole-archive
-	NO_WHOLE_ARCH= --no-whole-archive
-endif
-
-
-VTA_LIB_SRC = $(wildcard src/*.cc)
-
-ifeq (${VTA_TARGET}, pynq)
-	VTA_LIB_SRC += $(wildcard src/pynq/*.cc)
-endif
-
-ifeq (${VTA_TARGET}, sim)
-	VTA_LIB_SRC += $(wildcard src/sim/*.cc)
-endif
-
-VTA_LIB_OBJ = $(patsubst src/%.cc, build/%.o, $(VTA_LIB_SRC))
-
-all: lib/libvta.so lib/libvta.so.json
-
-build/%.o: src/%.cc
-	@mkdir -p $(@D)
-	$(CXX) $(CFLAGS) -MM -MT build/$*.o $< >build/$*.d
-	$(CXX) -c $(CFLAGS) -c $< -o $@
-
-lib/libvta.so.json: lib/libvta.so
-	@mkdir -p $(@D)
-	${VTA_CONFIG} --cfg-json > $@
-
-lib/libvta.so: $(VTA_LIB_OBJ)
-	@mkdir -p $(@D)
-	$(CXX) $(CFLAGS) -shared -o $@ $(filter %.o, $^) $(LDFLAGS)
-
-
-lint: pylint cpplint
-
-cpplint:
-	python3 tvm/dmlc-core/scripts/lint.py vta cpp include src
-
-pylint:
-	python3 -m pylint python/vta --rcfile=$(ROOTDIR)/tests/lint/pylintrc
-
-doc:
-	doxygen docs/Doxyfile
-
-clean:
-	$(RM) -rf build lib bin *~ */*~ */*/*~ */*/*/*~ */*.o */*/*.o */*/*/*.o
-
-
--include build/*.d
--include build/*/*.d
--include build/*/*/*.d
--include build/*/*/*/*.d
diff --git a/vta/NEWS.md b/vta/NEWS.md
deleted file mode 100644
index 93b26f80b7c2..000000000000
--- a/vta/NEWS.md
+++ /dev/null
@@ -1,13 +0,0 @@
-VTA Change Log
-==============
-
-This file records the changes in VTA stack in reverse chronological order.
-
-
-## Initial version
-
-- Vivado based hardware.
-- Driver for PYNQ board.
-- Runtime library.
-- TVM compiler stack.
-- Resnet-18 example.
diff --git a/vta/README.md b/vta/README.md
index bb347f02b798..677c9b9103ca 100644
--- a/vta/README.md
+++ b/vta/README.md
@@ -1,8 +1,5 @@
 VTA: Open, Modular, Deep Learning Accelerator Stack
 ===================================================
-[![Build Status](http://mode-gpu.cs.washington.edu:8080/buildStatus/icon?job=uwsaml/vta/master)](http://mode-gpu.cs.washington.edu:8080/job/uwsaml/job/vta/job/master/)
-[![GitHub license](http://dmlc.github.io/img/apache2.svg)](./LICENSE)
-
 VTA(versatile tensor accelerator) is an open-source deep learning accelerator stack.
 It is not just an open-source hardware, but is an end to end solution that includes
 the entire software stack on top of VTA open-source hardware.
@@ -18,16 +15,4 @@ The key features include:
   - Customized and extendible TVM compiler backend.
   - Flexible RPC support to ease the deployment, and program FPGAs with Python
 
-VTA is part of our effort on [TVM Stack](http://www.tvmlang.org/).
-
-VTA Installation
-----------------
-To get started with VTA, please follow the [Installation Guide](docs/how_to/install.md)
-
-ResNet-18 Inference Example
----------------------------
-To offload ResNet-18 inference, follow the [ResNet-18 Guide](examples/resnet18/pynq/README.md)
-
-License
--------
-© Contributors, 2018. Licensed under an [Apache-2.0](https://github.com/tmoreau89/vta/blob/master/LICENSE) license.
+VTA is part of our effort on TVM Stack.
diff --git a/vta/apps/pynq_rpc/start_rpc_server.sh b/vta/apps/pynq_rpc/start_rpc_server.sh
deleted file mode 100755
index e36d80ccc260..000000000000
--- a/vta/apps/pynq_rpc/start_rpc_server.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/bash
-export PYTHONPATH=${PYTHONPATH}:/home/xilinx/vta/tvm/python:/home/xilinx/vta/python
-export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/opt/python3.6/lib/python3.6/site-packages/pynq/drivers/
-python -m vta.exec.rpc_server
diff --git a/vta/config/README.md b/vta/config/README.md
new file mode 100644
index 000000000000..152621666ef1
--- /dev/null
+++ b/vta/config/README.md
@@ -0,0 +1,8 @@
+# VTA Configuration
+
+Each VTA runtime/hardware configuration is specified by vta_config.json file.
+You can copy the vta_config.json to tvm project root and modify the configuration
+before you type make.
+
+The config is going to affect the behavior of python package as well as
+the hardware runtime build.
diff --git a/vta/make/config.json b/vta/config/pynq_sample.json
similarity index 100%
rename from vta/make/config.json
rename to vta/config/pynq_sample.json
diff --git a/vta/make/sim_sample.json b/vta/config/vta_config.json
similarity index 100%
rename from vta/make/sim_sample.json
rename to vta/config/vta_config.json
diff --git a/vta/make/vta_config.py b/vta/config/vta_config.py
similarity index 82%
rename from vta/make/vta_config.py
rename to vta/config/vta_config.py
index 9788e433e308..b9c784a71d34 100644
--- a/vta/make/vta_config.py
+++ b/vta/config/vta_config.py
@@ -7,8 +7,8 @@
 def get_pkg_config(cfg):
     """Get the pkg config object."""
     curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
-    proj_root = os.path.abspath(os.path.join(curr_path, "../"))
-    pkg_config_py = os.path.join(proj_root, "python/vta/pkg_config.py")
+    proj_root = os.path.abspath(os.path.join(curr_path, "../../"))
+    pkg_config_py = os.path.join(proj_root, "vta/python/vta/pkg_config.py")
     libpkg = {"__file__": pkg_config_py}
     exec(compile(open(pkg_config_py, "rb").read(), pkg_config_py, "exec"), libpkg, libpkg)
     PkgConfig = libpkg["PkgConfig"]
@@ -18,14 +18,22 @@ def get_pkg_config(cfg):
 def main():
     """Main funciton"""
     parser = argparse.ArgumentParser()
+    parser.add_argument("--use-cfg", type=str, default="",
+                        help="path to the config json")
     parser.add_argument("--cflags", action="store_true",
                         help="print the cflags")
+    parser.add_argument("--defs", action="store_true",
+                        help="print the macro defs")
+    parser.add_argument("--sources", action="store_true",
+                        help="print the source file paths")
     parser.add_argument("--update", action="store_true",
                         help="Print out the json option.")
     parser.add_argument("--ldflags", action="store_true",
                         help="print the cflags")
     parser.add_argument("--cfg-json", action="store_true",
                         help="print all the config json")
+    parser.add_argument("--save-cfg-json", type=str, default="",
+                        help="save config json to file")
     parser.add_argument("--target", action="store_true",
                         help="print the target")
     parser.add_argument("--cfg-str", action="store_true",
@@ -66,11 +74,14 @@ def main():
 
     curr_path = os.path.dirname(
         os.path.abspath(os.path.expanduser(__file__)))
-    proj_root = os.path.abspath(os.path.join(curr_path, "../"))
+    proj_root = os.path.abspath(os.path.join(curr_path, "../../"))
     path_list = [
-        os.path.join(proj_root, "config.json"),
-        os.path.join(proj_root, "make/config.json")
+        os.path.join(proj_root, "vta_config.json"),
+        os.path.join(proj_root, "build", "vta_config.json"),
+        os.path.join(proj_root, "vta/config/vta_config.json")
     ]
+    if args.use_cfg:
+        path_list = [args.use_cfg]
     ok_path_list = [p for p in path_list if os.path.exists(p)]
     if not ok_path_list:
         raise RuntimeError("Cannot find config in %s" % str(path_list))
@@ -82,6 +93,12 @@ def main():
     if args.target:
         print(pkg.target)
 
+    if args.defs:
+        print(" ".join(pkg.macro_defs))
+
+    if args.sources:
+        print(" ".join(pkg.lib_source))
+
     if args.cflags:
         cflags_str = " ".join(pkg.cflags)
         if cfg["TARGET"] == "pynq":
@@ -94,6 +111,10 @@ def main():
     if args.cfg_json:
         print(pkg.cfg_json)
 
+    if args.save_cfg_json:
+        with open(args.save_cfg_json, "w") as fo:
+            fo.write(pkg.cfg_json)
+
     if args.cfg_str:
         # Needs to match the BITSTREAM string in python/vta/environment.py
         cfg_str = "{}x{}x{}_{}bx{}b_{}_{}_{}_{}_{}MHz_{}ns_v{}".format(
diff --git a/vta/docs/.gitignore b/vta/docs/.gitignore
deleted file mode 100644
index eee93ee4003a..000000000000
--- a/vta/docs/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-doxygen
-modules
-tutorials
-_build
-gen_modules
\ No newline at end of file
diff --git a/vta/docs/Doxyfile b/vta/docs/Doxyfile
deleted file mode 100644
index 77981f68f90b..000000000000
--- a/vta/docs/Doxyfile
+++ /dev/null
@@ -1,2427 +0,0 @@
-# Doxyfile 1.8.11
-
-# This file describes the settings to be used by the documentation system
-# doxygen (www.doxygen.org) for a project.
-#
-# All text after a double hash (##) is considered a comment and is placed in
-# front of the TAG it is preceding.
-#
-# All text after a single hash (#) is considered a comment and will be ignored.
-# The format is:
-# TAG = value [value, ...]
-# For lists, items can also be appended using:
-# TAG += value [value, ...]
-# Values that contain spaces should be placed between quotes (\" \").
-
-#---------------------------------------------------------------------------
-# Project related configuration options
-#---------------------------------------------------------------------------
-
-# This tag specifies the encoding used for all characters in the config file
-# that follow. The default is UTF-8 which is also the encoding used for all text
-# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
-# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
-# for the list of possible encodings.
-# The default value is: UTF-8.
-
-DOXYFILE_ENCODING      = UTF-8
-
-# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
-# double-quotes, unless you are using Doxywizard) that should identify the
-# project for which the documentation is generated. This name is used in the
-# title of most generated pages and in a few other places.
-# The default value is: My Project.
-
-PROJECT_NAME           = "vta"
-
-# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
-# could be handy for archiving the generated documentation or if some version
-# control system is used.
-
-PROJECT_NUMBER         =
-
-# Using the PROJECT_BRIEF tag one can provide an optional one line description
-# for a project that appears at the top of each page and should give viewer a
-# quick idea about the purpose of the project. Keep the description short.
-
-PROJECT_BRIEF          =
-
-# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
-# in the documentation. The maximum height of the logo should not exceed 55
-# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
-# the logo to the output directory.
-
-PROJECT_LOGO           =
-
-# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
-# into which the generated documentation will be written. If a relative path is
-# entered, it will be relative to the location where doxygen was started. If
-# left blank the current directory will be used.
-
-OUTPUT_DIRECTORY       = docs/doxygen
-
-# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
-# directories (in 2 levels) under the output directory of each output format and
-# will distribute the generated files over these directories. Enabling this
-# option can be useful when feeding doxygen a huge amount of source files, where
-# putting all generated files in the same directory would otherwise causes
-# performance problems for the file system.
-# The default value is: NO.
-
-CREATE_SUBDIRS         = NO
-
-# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
-# characters to appear in the names of generated files. If set to NO, non-ASCII
-# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
-# U+3044.
-# The default value is: NO.
-
-ALLOW_UNICODE_NAMES    = NO
-
-# The OUTPUT_LANGUAGE tag is used to specify the language in which all
-# documentation generated by doxygen is written. Doxygen will use this
-# information to generate all constant output in the proper language.
-# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
-# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
-# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
-# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
-# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
-# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
-# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
-# Ukrainian and Vietnamese.
-# The default value is: English.
-
-OUTPUT_LANGUAGE        = English
-
-# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
-# descriptions after the members that are listed in the file and class
-# documentation (similar to Javadoc). Set to NO to disable this.
-# The default value is: YES.
-
-BRIEF_MEMBER_DESC      = YES
-
-# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
-# description of a member or function before the detailed description
-#
-# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
-# brief descriptions will be completely suppressed.
-# The default value is: YES.
-
-REPEAT_BRIEF           = YES
-
-# This tag implements a quasi-intelligent brief description abbreviator that is
-# used to form the text in various listings. Each string in this list, if found
-# as the leading text of the brief description, will be stripped from the text
-# and the result, after processing the whole list, is used as the annotated
-# text. Otherwise, the brief description is used as-is. If left blank, the
-# following values are used ($name is automatically replaced with the name of
-# the entity):The $name class, The $name widget, The $name file, is, provides,
-# specifies, contains, represents, a, an and the.
-
-ABBREVIATE_BRIEF       =
-
-# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
-# doxygen will generate a detailed section even if there is only a brief
-# description.
-# The default value is: NO.
-
-ALWAYS_DETAILED_SEC    = NO
-
-# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
-# inherited members of a class in the documentation of that class as if those
-# members were ordinary class members. Constructors, destructors and assignment
-# operators of the base classes will not be shown.
-# The default value is: NO.
-
-INLINE_INHERITED_MEMB  = NO
-
-# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
-# before files name in the file list and in the header files. If set to NO the
-# shortest path that makes the file name unique will be used
-# The default value is: YES.
-
-FULL_PATH_NAMES        = YES
-
-# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
-# Stripping is only done if one of the specified strings matches the left-hand
-# part of the path. The tag can be used to show relative paths in the file list.
-# If left blank the directory from which doxygen is run is used as the path to
-# strip.
-#
-# Note that you can specify absolute paths here, but also relative paths, which
-# will be relative from the directory where doxygen is started.
-# This tag requires that the tag FULL_PATH_NAMES is set to YES.
-
-STRIP_FROM_PATH        =
-
-# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
-# path mentioned in the documentation of a class, which tells the reader which
-# header file to include in order to use a class. If left blank only the name of
-# the header file containing the class definition is used. Otherwise one should
-# specify the list of include paths that are normally passed to the compiler
-# using the -I flag.
-
-STRIP_FROM_INC_PATH    =
-
-# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
-# less readable) file names. This can be useful is your file systems doesn't
-# support long names like on DOS, Mac, or CD-ROM.
-# The default value is: NO.
-
-SHORT_NAMES            = NO
-
-# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
-# first line (until the first dot) of a Javadoc-style comment as the brief
-# description. If set to NO, the Javadoc-style will behave just like regular Qt-
-# style comments (thus requiring an explicit @brief command for a brief
-# description.)
-# The default value is: NO.
-
-JAVADOC_AUTOBRIEF      = NO
-
-# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
-# line (until the first dot) of a Qt-style comment as the brief description. If
-# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
-# requiring an explicit \brief command for a brief description.)
-# The default value is: NO.
-
-QT_AUTOBRIEF           = NO
-
-# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
-# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
-# a brief description. This used to be the default behavior. The new default is
-# to treat a multi-line C++ comment block as a detailed description. Set this
-# tag to YES if you prefer the old behavior instead.
-#
-# Note that setting this tag to YES also means that rational rose comments are
-# not recognized any more.
-# The default value is: NO.
-
-MULTILINE_CPP_IS_BRIEF = NO
-
-# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
-# documentation from any documented member that it re-implements.
-# The default value is: YES.
-
-INHERIT_DOCS           = YES
-
-# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
-# page for each member. If set to NO, the documentation of a member will be part
-# of the file/class/namespace that contains it.
-# The default value is: NO.
-
-SEPARATE_MEMBER_PAGES  = NO
-
-# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
-# uses this value to replace tabs by spaces in code fragments.
-# Minimum value: 1, maximum value: 16, default value: 4.
-
-TAB_SIZE               = 4
-
-# This tag can be used to specify a number of aliases that act as commands in
-# the documentation. An alias has the form:
-# name=value
-# For example adding
-# "sideeffect=@par Side Effects:\n"
-# will allow you to put the command \sideeffect (or @sideeffect) in the
-# documentation, which will result in a user-defined paragraph with heading
-# "Side Effects:". You can put \n's in the value part of an alias to insert
-# newlines.
-
-ALIASES                =
-
-# This tag can be used to specify a number of word-keyword mappings (TCL only).
-# A mapping has the form "name=value". For example adding "class=itcl::class"
-# will allow you to use the command class in the itcl::class meaning.
-
-TCL_SUBST              =
-
-# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
-# only. Doxygen will then generate output that is more tailored for C. For
-# instance, some of the names that are used will be different. The list of all
-# members will be omitted, etc.
-# The default value is: NO.
-
-OPTIMIZE_OUTPUT_FOR_C  = NO
-
-# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
-# Python sources only. Doxygen will then generate output that is more tailored
-# for that language. For instance, namespaces will be presented as packages,
-# qualified scopes will look different, etc.
-# The default value is: NO.
-
-OPTIMIZE_OUTPUT_JAVA   = NO
-
-# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
-# sources. Doxygen will then generate output that is tailored for Fortran.
-# The default value is: NO.
-
-OPTIMIZE_FOR_FORTRAN   = NO
-
-# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
-# sources. Doxygen will then generate output that is tailored for VHDL.
-# The default value is: NO.
-
-OPTIMIZE_OUTPUT_VHDL   = NO
-
-# Doxygen selects the parser to use depending on the extension of the files it
-# parses. With this tag you can assign which parser to use for a given
-# extension. Doxygen has a built-in mapping, but you can override or extend it
-# using this tag. The format is ext=language, where ext is a file extension, and
-# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
-# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
-# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
-# Fortran. In the later case the parser tries to guess whether the code is fixed
-# or free formatted code, this is the default for Fortran type files), VHDL. For
-# instance to make doxygen treat .inc files as Fortran files (default is PHP),
-# and .f files as C (default is Fortran), use: inc=Fortran f=C.
-#
-# Note: For files without extension you can use no_extension as a placeholder.
-#
-# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
-# the files are not read by doxygen.
-
-EXTENSION_MAPPING      =
-
-# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
-# according to the Markdown format, which allows for more readable
-# documentation. See http://daringfireball.net/projects/markdown/ for details.
-# The output of markdown processing is further processed by doxygen, so you can
-# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
-# case of backward compatibilities issues.
-# The default value is: YES.
-
-MARKDOWN_SUPPORT       = YES
-
-# When enabled doxygen tries to link words that correspond to documented
-# classes, or namespaces to their corresponding documentation. Such a link can
-# be prevented in individual cases by putting a % sign in front of the word or
-# globally by setting AUTOLINK_SUPPORT to NO.
-# The default value is: YES.
-
-AUTOLINK_SUPPORT       = YES
-
-# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
-# to include (a tag file for) the STL sources as input, then you should set this
-# tag to YES in order to let doxygen match functions declarations and
-# definitions whose arguments contain STL classes (e.g. func(std::string);
-# versus func(std::string) {}). This also make the inheritance and collaboration
-# diagrams that involve STL classes more complete and accurate.
-# The default value is: NO.
-
-BUILTIN_STL_SUPPORT    = NO
-
-# If you use Microsoft's C++/CLI language, you should set this option to YES to
-# enable parsing support.
-# The default value is: NO.
-
-CPP_CLI_SUPPORT        = NO
-
-# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
-# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
-# will parse them like normal C++ but will assume all classes use public instead
-# of private inheritance when no explicit protection keyword is present.
-# The default value is: NO.
-
-SIP_SUPPORT            = NO
-
-# For Microsoft's IDL there are propget and propput attributes to indicate
-# getter and setter methods for a property. Setting this option to YES will make
-# doxygen to replace the get and set methods by a property in the documentation.
-# This will only work if the methods are indeed getting or setting a simple
-# type. If this is not the case, or you want to show the methods anyway, you
-# should set this option to NO.
-# The default value is: YES.
-
-IDL_PROPERTY_SUPPORT   = YES
-
-# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
-# tag is set to YES then doxygen will reuse the documentation of the first
-# member in the group (if any) for the other members of the group. By default
-# all members of a group must be documented explicitly.
-# The default value is: NO.
-
-DISTRIBUTE_GROUP_DOC   = NO
-
-# If one adds a struct or class to a group and this option is enabled, then also
-# any nested class or struct is added to the same group. By default this option
-# is disabled and one has to add nested compounds explicitly via \ingroup.
-# The default value is: NO.
-
-GROUP_NESTED_COMPOUNDS = NO
-
-# Set the SUBGROUPING tag to YES to allow class member groups of the same type
-# (for instance a group of public functions) to be put as a subgroup of that
-# type (e.g. under the Public Functions section). Set it to NO to prevent
-# subgrouping. Alternatively, this can be done per class using the
-# \nosubgrouping command.
-# The default value is: YES.
-
-SUBGROUPING            = YES
-
-# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
-# are shown inside the group in which they are included (e.g. using \ingroup)
-# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
-# and RTF).
-#
-# Note that this feature does not work in combination with
-# SEPARATE_MEMBER_PAGES.
-# The default value is: NO.
-
-INLINE_GROUPED_CLASSES = NO
-
-# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
-# with only public data fields or simple typedef fields will be shown inline in
-# the documentation of the scope in which they are defined (i.e. file,
-# namespace, or group documentation), provided this scope is documented. If set
-# to NO, structs, classes, and unions are shown on a separate page (for HTML and
-# Man pages) or section (for LaTeX and RTF).
-# The default value is: NO.
-
-INLINE_SIMPLE_STRUCTS  = NO
-
-# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
-# enum is documented as struct, union, or enum with the name of the typedef. So
-# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
-# with name TypeT. When disabled the typedef will appear as a member of a file,
-# namespace, or class. And the struct will be named TypeS. This can typically be
-# useful for C code in case the coding convention dictates that all compound
-# types are typedef'ed and only the typedef is referenced, never the tag name.
-# The default value is: NO.
-
-TYPEDEF_HIDES_STRUCT   = NO
-
-# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
-# cache is used to resolve symbols given their name and scope. Since this can be
-# an expensive process and often the same symbol appears multiple times in the
-# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
-# doxygen will become slower. If the cache is too large, memory is wasted. The
-# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
-# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
-# symbols. At the end of a run doxygen will report the cache usage and suggest
-# the optimal cache size from a speed point of view.
-# Minimum value: 0, maximum value: 9, default value: 0.
-
-LOOKUP_CACHE_SIZE      = 0
-
-#---------------------------------------------------------------------------
-# Build related configuration options
-#---------------------------------------------------------------------------
-
-# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
-# documentation are documented, even if no documentation was available. Private
-# class members and static file members will be hidden unless the
-# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
-# Note: This will also disable the warnings about undocumented members that are
-# normally produced when WARNINGS is set to YES.
-# The default value is: NO.
-
-EXTRACT_ALL            = YES
-
-# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
-# be included in the documentation.
-# The default value is: NO.
-
-EXTRACT_PRIVATE        = NO
-
-# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
-# scope will be included in the documentation.
-# The default value is: NO.
-
-EXTRACT_PACKAGE        = NO
-
-# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
-# included in the documentation.
-# The default value is: NO.
-
-EXTRACT_STATIC         = NO
-
-# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
-# locally in source files will be included in the documentation. If set to NO,
-# only classes defined in header files are included. Does not have any effect
-# for Java sources.
-# The default value is: YES.
-
-EXTRACT_LOCAL_CLASSES  = YES
-
-# This flag is only useful for Objective-C code. If set to YES, local methods,
-# which are defined in the implementation section but not in the interface are
-# included in the documentation. If set to NO, only methods in the interface are
-# included.
-# The default value is: NO.
-
-EXTRACT_LOCAL_METHODS  = NO
-
-# If this flag is set to YES, the members of anonymous namespaces will be
-# extracted and appear in the documentation as a namespace called
-# 'anonymous_namespace{file}', where file will be replaced with the base name of
-# the file that contains the anonymous namespace. By default anonymous namespace
-# are hidden.
-# The default value is: NO.
-
-EXTRACT_ANON_NSPACES   = NO
-
-# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
-# undocumented members inside documented classes or files. If set to NO these
-# members will be included in the various overviews, but no documentation
-# section is generated. This option has no effect if EXTRACT_ALL is enabled.
-# The default value is: NO.
-
-HIDE_UNDOC_MEMBERS     = NO
-
-# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
-# undocumented classes that are normally visible in the class hierarchy. If set
-# to NO, these classes will be included in the various overviews. This option
-# has no effect if EXTRACT_ALL is enabled.
-# The default value is: NO.
-
-HIDE_UNDOC_CLASSES     = NO
-
-# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
-# (class|struct|union) declarations. If set to NO, these declarations will be
-# included in the documentation.
-# The default value is: NO.
-
-HIDE_FRIEND_COMPOUNDS  = NO
-
-# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
-# documentation blocks found inside the body of a function. If set to NO, these
-# blocks will be appended to the function's detailed documentation block.
-# The default value is: NO.
-
-HIDE_IN_BODY_DOCS      = NO
-
-# The INTERNAL_DOCS tag determines if documentation that is typed after a
-# \internal command is included. If the tag is set to NO then the documentation
-# will be excluded. Set it to YES to include the internal documentation.
-# The default value is: NO.
-
-INTERNAL_DOCS          = NO
-
-# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
-# names in lower-case letters. If set to YES, upper-case letters are also
-# allowed. This is useful if you have classes or files whose names only differ
-# in case and if your file system supports case sensitive file names. Windows
-# and Mac users are advised to set this option to NO.
-# The default value is: system dependent.
-
-CASE_SENSE_NAMES       = YES
-
-# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
-# their full class and namespace scopes in the documentation. If set to YES, the
-# scope will be hidden.
-# The default value is: NO.
-
-HIDE_SCOPE_NAMES       = NO
-
-# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
-# append additional text to a page's title, such as Class Reference. If set to
-# YES the compound reference will be hidden.
-# The default value is: NO.
-
-HIDE_COMPOUND_REFERENCE= NO
-
-# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
-# the files that are included by a file in the documentation of that file.
-# The default value is: YES.
-
-SHOW_INCLUDE_FILES     = YES
-
-# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
-# grouped member an include statement to the documentation, telling the reader
-# which file to include in order to use the member.
-# The default value is: NO.
-
-SHOW_GROUPED_MEMB_INC  = NO
-
-# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
-# files with double quotes in the documentation rather than with sharp brackets.
-# The default value is: NO.
-
-FORCE_LOCAL_INCLUDES   = NO
-
-# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
-# documentation for inline members.
-# The default value is: YES.
-
-INLINE_INFO            = YES
-
-# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
-# (detailed) documentation of file and class members alphabetically by member
-# name. If set to NO, the members will appear in declaration order.
-# The default value is: YES.
-
-SORT_MEMBER_DOCS       = YES
-
-# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
-# descriptions of file, namespace and class members alphabetically by member
-# name. If set to NO, the members will appear in declaration order. Note that
-# this will also influence the order of the classes in the class list.
-# The default value is: NO.
-
-SORT_BRIEF_DOCS        = NO
-
-# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
-# (brief and detailed) documentation of class members so that constructors and
-# destructors are listed first. If set to NO the constructors will appear in the
-# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
-# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
-# member documentation.
-# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
-# detailed member documentation.
-# The default value is: NO.
-
-SORT_MEMBERS_CTORS_1ST = NO
-
-# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
-# of group names into alphabetical order. If set to NO the group names will
-# appear in their defined order.
-# The default value is: NO.
-
-SORT_GROUP_NAMES       = NO
-
-# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
-# fully-qualified names, including namespaces. If set to NO, the class list will
-# be sorted only by class name, not including the namespace part.
-# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
-# Note: This option applies only to the class list, not to the alphabetical
-# list.
-# The default value is: NO.
-
-SORT_BY_SCOPE_NAME     = NO
-
-# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
-# type resolution of all parameters of a function it will reject a match between
-# the prototype and the implementation of a member function even if there is
-# only one candidate or it is obvious which candidate to choose by doing a
-# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
-# accept a match between prototype and implementation in such cases.
-# The default value is: NO.
-
-STRICT_PROTO_MATCHING  = NO
-
-# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
-# list. This list is created by putting \todo commands in the documentation.
-# The default value is: YES.
-
-GENERATE_TODOLIST      = YES
-
-# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
-# list. This list is created by putting \test commands in the documentation.
-# The default value is: YES.
-
-GENERATE_TESTLIST      = YES
-
-# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
-# list. This list is created by putting \bug commands in the documentation.
-# The default value is: YES.
-
-GENERATE_BUGLIST       = YES
-
-# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
-# the deprecated list. This list is created by putting \deprecated commands in
-# the documentation.
-# The default value is: YES.
-
-GENERATE_DEPRECATEDLIST= YES
-
-# The ENABLED_SECTIONS tag can be used to enable conditional documentation
-# sections, marked by \if <section_label> ... \endif and \cond <section_label>
-# ... \endcond blocks.
-
-ENABLED_SECTIONS       =
-
-# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
-# initial value of a variable or macro / define can have for it to appear in the
-# documentation. If the initializer consists of more lines than specified here
-# it will be hidden. Use a value of 0 to hide initializers completely. The
-# appearance of the value of individual variables and macros / defines can be
-# controlled using \showinitializer or \hideinitializer command in the
-# documentation regardless of this setting.
-# Minimum value: 0, maximum value: 10000, default value: 30.
-
-MAX_INITIALIZER_LINES  = 30
-
-# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
-# the bottom of the documentation of classes and structs. If set to YES, the
-# list will mention the files that were used to generate the documentation.
-# The default value is: YES.
-
-SHOW_USED_FILES        = YES
-
-# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
-# will remove the Files entry from the Quick Index and from the Folder Tree View
-# (if specified).
-# The default value is: YES.
-
-SHOW_FILES             = YES
-
-# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
-# page. This will remove the Namespaces entry from the Quick Index and from the
-# Folder Tree View (if specified).
-# The default value is: YES.
-
-SHOW_NAMESPACES        = YES
-
-# The FILE_VERSION_FILTER tag can be used to specify a program or script that
-# doxygen should invoke to get the current version for each file (typically from
-# the version control system). Doxygen will invoke the program by executing (via
-# popen()) the command command input-file, where command is the value of the
-# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
-# by doxygen. Whatever the program writes to standard output is used as the file
-# version. For an example see the documentation.
-
-FILE_VERSION_FILTER    =
-
-# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
-# by doxygen. The layout file controls the global structure of the generated
-# output files in an output format independent way. To create the layout file
-# that represents doxygen's defaults, run doxygen with the -l option. You can
-# optionally specify a file name after the option, if omitted DoxygenLayout.xml
-# will be used as the name of the layout file.
-#
-# Note that if you run doxygen from a directory containing a file called
-# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
-# tag is left empty.
-
-LAYOUT_FILE            =
-
-# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
-# the reference definitions. This must be a list of .bib files. The .bib
-# extension is automatically appended if omitted. This requires the bibtex tool
-# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
-# For LaTeX the style of the bibliography can be controlled using
-# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
-# search path. See also \cite for info how to create references.
-
-CITE_BIB_FILES         =
-
-#---------------------------------------------------------------------------
-# Configuration options related to warning and progress messages
-#---------------------------------------------------------------------------
-
-# The QUIET tag can be used to turn on/off the messages that are generated to
-# standard output by doxygen. If QUIET is set to YES this implies that the
-# messages are off.
-# The default value is: NO.
-
-QUIET                  = NO
-
-# The WARNINGS tag can be used to turn on/off the warning messages that are
-# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
-# this implies that the warnings are on.
-#
-# Tip: Turn warnings on while writing the documentation.
-# The default value is: YES.
-
-WARNINGS               = YES
-
-# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
-# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
-# will automatically be disabled.
-# The default value is: YES.
-
-WARN_IF_UNDOCUMENTED   = YES
-
-# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
-# potential errors in the documentation, such as not documenting some parameters
-# in a documented function, or documenting parameters that don't exist or using
-# markup commands wrongly.
-# The default value is: YES.
-
-WARN_IF_DOC_ERROR      = YES
-
-# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
-# are documented, but have no documentation for their parameters or return
-# value. If set to NO, doxygen will only warn about wrong or incomplete
-# parameter documentation, but not about the absence of documentation.
-# The default value is: NO.
-
-WARN_NO_PARAMDOC       = NO
-
-# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
-# a warning is encountered.
-# The default value is: NO.
-
-WARN_AS_ERROR          = NO
-
-# The WARN_FORMAT tag determines the format of the warning messages that doxygen
-# can produce. The string should contain the $file, $line, and $text tags, which
-# will be replaced by the file and line number from which the warning originated
-# and the warning text. Optionally the format may contain $version, which will
-# be replaced by the version of the file (if it could be obtained via
-# FILE_VERSION_FILTER)
-# The default value is: $file:$line: $text.
-
-WARN_FORMAT            = "$file:$line: $text"
-
-# The WARN_LOGFILE tag can be used to specify a file to which warning and error
-# messages should be written. If left blank the output is written to standard
-# error (stderr).
-
-WARN_LOGFILE           =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the input files
-#---------------------------------------------------------------------------
-
-# The INPUT tag is used to specify the files and/or directories that contain
-# documented source files. You may enter file names like myfile.cpp or
-# directories like /usr/src/myproject. Separate the files or directories with
-# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
-# Note: If this tag is empty the current directory is searched.
-
-INPUT                  = include
-
-# This tag can be used to specify the character encoding of the source files
-# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
-# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
-# documentation (see: http://www.gnu.org/software/libiconv) for the list of
-# possible encodings.
-# The default value is: UTF-8.
-
-INPUT_ENCODING         = UTF-8
-
-# If the value of the INPUT tag contains directories, you can use the
-# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
-# *.h) to filter out the source-files in the directories.
-#
-# Note that for custom extensions or not directly supported extensions you also
-# need to set EXTENSION_MAPPING for the extension otherwise the files are not
-# read by doxygen.
-#
-# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
-# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
-# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
-# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f, *.for, *.tcl,
-# *.vhd, *.vhdl, *.ucf, *.qsf, *.as and *.js.
-
-FILE_PATTERNS          = *.h
-
-# The RECURSIVE tag can be used to specify whether or not subdirectories should
-# be searched for input files as well.
-# The default value is: NO.
-
-RECURSIVE              = YES
-
-# The EXCLUDE tag can be used to specify files and/or directories that should be
-# excluded from the INPUT source files. This way you can easily exclude a
-# subdirectory from a directory tree whose root is specified with the INPUT tag.
-#
-# Note that relative paths are relative to the directory from which doxygen is
-# run.
-
-EXCLUDE                =
-
-# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
-# directories that are symbolic links (a Unix file system feature) are excluded
-# from the input.
-# The default value is: NO.
-
-EXCLUDE_SYMLINKS       = NO
-
-# If the value of the INPUT tag contains directories, you can use the
-# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
-# certain files from those directories.
-#
-# Note that the wildcards are matched against the file with absolute path, so to
-# exclude all test directories for example use the pattern */test/*
-
-EXCLUDE_PATTERNS       =
-
-# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
-# (namespaces, classes, functions, etc.) that should be excluded from the
-# output. The symbol name can be a fully qualified name, a word, or if the
-# wildcard * is used, a substring. Examples: ANamespace, AClass,
-# AClass::ANamespace, ANamespace::*Test
-#
-# Note that the wildcards are matched against the file with absolute path, so to
-# exclude all test directories use the pattern */test/*
-
-EXCLUDE_SYMBOLS        =
-
-# The EXAMPLE_PATH tag can be used to specify one or more files or directories
-# that contain example code fragments that are included (see the \include
-# command).
-
-EXAMPLE_PATH           =
-
-# If the value of the EXAMPLE_PATH tag contains directories, you can use the
-# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
-# *.h) to filter out the source-files in the directories. If left blank all
-# files are included.
-
-EXAMPLE_PATTERNS       =
-
-# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
-# searched for input files to be used with the \include or \dontinclude commands
-# irrespective of the value of the RECURSIVE tag.
-# The default value is: NO.
-
-EXAMPLE_RECURSIVE      = NO
-
-# The IMAGE_PATH tag can be used to specify one or more files or directories
-# that contain images that are to be included in the documentation (see the
-# \image command).
-
-IMAGE_PATH             =
-
-# The INPUT_FILTER tag can be used to specify a program that doxygen should
-# invoke to filter for each input file. Doxygen will invoke the filter program
-# by executing (via popen()) the command:
-#
-# <filter> <input-file>
-#
-# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
-# name of an input file. Doxygen will then use the output that the filter
-# program writes to standard output. If FILTER_PATTERNS is specified, this tag
-# will be ignored.
-#
-# Note that the filter must not add or remove lines; it is applied before the
-# code is scanned, but not when the output code is generated. If lines are added
-# or removed, the anchors will not be placed correctly.
-#
-# Note that for custom extensions or not directly supported extensions you also
-# need to set EXTENSION_MAPPING for the extension otherwise the files are not
-# properly processed by doxygen.
-
-INPUT_FILTER           =
-
-# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
-# basis. Doxygen will compare the file name with each pattern and apply the
-# filter if there is a match. The filters are a list of the form: pattern=filter
-# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
-# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
-# patterns match the file name, INPUT_FILTER is applied.
-#
-# Note that for custom extensions or not directly supported extensions you also
-# need to set EXTENSION_MAPPING for the extension otherwise the files are not
-# properly processed by doxygen.
-
-FILTER_PATTERNS        =
-
-# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
-# INPUT_FILTER) will also be used to filter the input files that are used for
-# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
-# The default value is: NO.
-
-FILTER_SOURCE_FILES    = NO
-
-# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
-# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
-# it is also possible to disable source filtering for a specific pattern using
-# *.ext= (so without naming a filter).
-# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
-
-FILTER_SOURCE_PATTERNS =
-
-# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
-# is part of the input, its contents will be placed on the main page
-# (index.html). This can be useful if you have a project on for instance GitHub
-# and want to reuse the introduction page also for the doxygen output.
-
-USE_MDFILE_AS_MAINPAGE =
-
-#---------------------------------------------------------------------------
-# Configuration options related to source browsing
-#---------------------------------------------------------------------------
-
-# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
-# generated. Documented entities will be cross-referenced with these sources.
-#
-# Note: To get rid of all source code in the generated output, make sure that
-# also VERBATIM_HEADERS is set to NO.
-# The default value is: NO.
-
-SOURCE_BROWSER         = NO
-
-# Setting the INLINE_SOURCES tag to YES will include the body of functions,
-# classes and enums directly into the documentation.
-# The default value is: NO.
-
-INLINE_SOURCES         = NO
-
-# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
-# special comment blocks from generated source code fragments. Normal C, C++ and
-# Fortran comments will always remain visible.
-# The default value is: YES.
-
-STRIP_CODE_COMMENTS    = YES
-
-# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
-# function all documented functions referencing it will be listed.
-# The default value is: NO.
-
-REFERENCED_BY_RELATION = NO
-
-# If the REFERENCES_RELATION tag is set to YES then for each documented function
-# all documented entities called/used by that function will be listed.
-# The default value is: NO.
-
-REFERENCES_RELATION    = NO
-
-# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
-# to YES then the hyperlinks from functions in REFERENCES_RELATION and
-# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
-# link to the documentation.
-# The default value is: YES.
-
-REFERENCES_LINK_SOURCE = YES
-
-# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
-# source code will show a tooltip with additional information such as prototype,
-# brief description and links to the definition and documentation. Since this
-# will make the HTML file larger and loading of large files a bit slower, you
-# can opt to disable this feature.
-# The default value is: YES.
-# This tag requires that the tag SOURCE_BROWSER is set to YES.
-
-SOURCE_TOOLTIPS        = YES
-
-# If the USE_HTAGS tag is set to YES then the references to source code will
-# point to the HTML generated by the htags(1) tool instead of doxygen built-in
-# source browser. The htags tool is part of GNU's global source tagging system
-# (see http://www.gnu.org/software/global/global.html). You will need version
-# 4.8.6 or higher.
-#
-# To use it do the following:
-# - Install the latest version of global
-# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
-# - Make sure the INPUT points to the root of the source tree
-# - Run doxygen as normal
-#
-# Doxygen will invoke htags (and that will in turn invoke gtags), so these
-# tools must be available from the command line (i.e. in the search path).
-#
-# The result: instead of the source browser generated by doxygen, the links to
-# source code will now point to the output of htags.
-# The default value is: NO.
-# This tag requires that the tag SOURCE_BROWSER is set to YES.
-
-USE_HTAGS              = NO
-
-# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
-# verbatim copy of the header file for each class for which an include is
-# specified. Set to NO to disable this.
-# See also: Section \class.
-# The default value is: YES.
-
-VERBATIM_HEADERS       = YES
-
-# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
-# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the
-# cost of reduced performance. This can be particularly helpful with template
-# rich C++ code for which doxygen's built-in parser lacks the necessary type
-# information.
-# Note: The availability of this option depends on whether or not doxygen was
-# generated with the -Duse-libclang=ON option for CMake.
-# The default value is: NO.
-
-CLANG_ASSISTED_PARSING = NO
-
-# If clang assisted parsing is enabled you can provide the compiler with command
-# line options that you would normally use when invoking the compiler. Note that
-# the include paths will already be set by doxygen for the files and directories
-# specified with INPUT and INCLUDE_PATH.
-# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
-
-CLANG_OPTIONS          =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the alphabetical class index
-#---------------------------------------------------------------------------
-
-# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
-# compounds will be generated. Enable this if the project contains a lot of
-# classes, structs, unions or interfaces.
-# The default value is: YES.
-
-ALPHABETICAL_INDEX     = YES
-
-# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
-# which the alphabetical index list will be split.
-# Minimum value: 1, maximum value: 20, default value: 5.
-# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
-
-COLS_IN_ALPHA_INDEX    = 5
-
-# In case all classes in a project start with a common prefix, all classes will
-# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
-# can be used to specify a prefix (or a list of prefixes) that should be ignored
-# while generating the index headers.
-# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
-
-IGNORE_PREFIX          =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the HTML output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
-# The default value is: YES.
-
-GENERATE_HTML          = YES
-
-# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: html.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_OUTPUT            = html
-
-# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
-# generated HTML page (for example: .htm, .php, .asp).
-# The default value is: .html.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_FILE_EXTENSION    = .html
-
-# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
-# each generated HTML page. If the tag is left blank doxygen will generate a
-# standard header.
-#
-# To get valid HTML the header file that includes any scripts and style sheets
-# that doxygen needs, which is dependent on the configuration options used (e.g.
-# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
-# default header using
-# doxygen -w html new_header.html new_footer.html new_stylesheet.css
-# YourConfigFile
-# and then modify the file new_header.html. See also section "Doxygen usage"
-# for information on how to generate the default header that doxygen normally
-# uses.
-# Note: The header is subject to change so you typically have to regenerate the
-# default header when upgrading to a newer version of doxygen. For a description
-# of the possible markers and block names see the documentation.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_HEADER            =
-
-# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
-# generated HTML page. If the tag is left blank doxygen will generate a standard
-# footer. See HTML_HEADER for more information on how to generate a default
-# footer and what special commands can be used inside the footer. See also
-# section "Doxygen usage" for information on how to generate the default footer
-# that doxygen normally uses.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_FOOTER            =
-
-# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
-# sheet that is used by each HTML page. It can be used to fine-tune the look of
-# the HTML output. If left blank doxygen will generate a default style sheet.
-# See also section "Doxygen usage" for information on how to generate the style
-# sheet that doxygen normally uses.
-# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
-# it is more robust and this tag (HTML_STYLESHEET) will in the future become
-# obsolete.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_STYLESHEET        =
-
-# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
-# cascading style sheets that are included after the standard style sheets
-# created by doxygen. Using this option one can overrule certain style aspects.
-# This is preferred over using HTML_STYLESHEET since it does not replace the
-# standard style sheet and is therefore more robust against future updates.
-# Doxygen will copy the style sheet files to the output directory.
-# Note: The order of the extra style sheet files is of importance (e.g. the last
-# style sheet in the list overrules the setting of the previous ones in the
-# list). For an example see the documentation.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_EXTRA_STYLESHEET  =
-
-# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
-# other source files which should be copied to the HTML output directory. Note
-# that these files will be copied to the base HTML output directory. Use the
-# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
-# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
-# files will be copied as-is; there are no commands or markers available.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_EXTRA_FILES       =
-
-# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
-# will adjust the colors in the style sheet and background images according to
-# this color. Hue is specified as an angle on a colorwheel, see
-# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
-# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
-# purple, and 360 is red again.
-# Minimum value: 0, maximum value: 359, default value: 220.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_COLORSTYLE_HUE    = 220
-
-# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
-# in the HTML output. For a value of 0 the output will use grayscales only. A
-# value of 255 will produce the most vivid colors.
-# Minimum value: 0, maximum value: 255, default value: 100.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_COLORSTYLE_SAT    = 100
-
-# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
-# luminance component of the colors in the HTML output. Values below 100
-# gradually make the output lighter, whereas values above 100 make the output
-# darker. The value divided by 100 is the actual gamma applied, so 80 represents
-# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
-# change the gamma.
-# Minimum value: 40, maximum value: 240, default value: 80.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_COLORSTYLE_GAMMA  = 80
-
-# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
-# page will contain the date and time when the page was generated. Setting this
-# to YES can help to show when doxygen was last run and thus if the
-# documentation is up to date.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_TIMESTAMP         = NO
-
-# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
-# documentation will contain sections that can be hidden and shown after the
-# page has loaded.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_DYNAMIC_SECTIONS  = NO
-
-# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
-# shown in the various tree structured indices initially; the user can expand
-# and collapse entries dynamically later on. Doxygen will expand the tree to
-# such a level that at most the specified number of entries are visible (unless
-# a fully collapsed tree already exceeds this amount). So setting the number of
-# entries 1 will produce a full collapsed tree by default. 0 is a special value
-# representing an infinite number of entries and will result in a full expanded
-# tree by default.
-# Minimum value: 0, maximum value: 9999, default value: 100.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_INDEX_NUM_ENTRIES = 100
-
-# If the GENERATE_DOCSET tag is set to YES, additional index files will be
-# generated that can be used as input for Apple's Xcode 3 integrated development
-# environment (see: http://developer.apple.com/tools/xcode/), introduced with
-# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
-# Makefile in the HTML output directory. Running make will produce the docset in
-# that directory and running make install will install the docset in
-# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
-# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
-# for more information.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_DOCSET        = NO
-
-# This tag determines the name of the docset feed. A documentation feed provides
-# an umbrella under which multiple documentation sets from a single provider
-# (such as a company or product suite) can be grouped.
-# The default value is: Doxygen generated docs.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_FEEDNAME        = "Doxygen generated docs"
-
-# This tag specifies a string that should uniquely identify the documentation
-# set bundle. This should be a reverse domain-name style string, e.g.
-# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
-# The default value is: org.doxygen.Project.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_BUNDLE_ID       = org.doxygen.Project
-
-# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
-# the documentation publisher. This should be a reverse domain-name style
-# string, e.g. com.mycompany.MyDocSet.documentation.
-# The default value is: org.doxygen.Publisher.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
-
-# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
-# The default value is: Publisher.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_PUBLISHER_NAME  = Publisher
-
-# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
-# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
-# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
-# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
-# Windows.
-#
-# The HTML Help Workshop contains a compiler that can convert all HTML output
-# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
-# files are now used as the Windows 98 help format, and will replace the old
-# Windows help format (.hlp) on all Windows platforms in the future. Compressed
-# HTML files also contain an index, a table of contents, and you can search for
-# words in the documentation. The HTML workshop also contains a viewer for
-# compressed HTML files.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_HTMLHELP      = NO
-
-# The CHM_FILE tag can be used to specify the file name of the resulting .chm
-# file. You can add a path in front of the file if the result should not be
-# written to the html output directory.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-CHM_FILE               =
-
-# The HHC_LOCATION tag can be used to specify the location (absolute path
-# including file name) of the HTML help compiler (hhc.exe). If non-empty,
-# doxygen will try to run the HTML help compiler on the generated index.hhp.
-# The file has to be specified with full path.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-HHC_LOCATION           =
-
-# The GENERATE_CHI flag controls if a separate .chi index file is generated
-# (YES) or that it should be included in the master .chm file (NO).
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-GENERATE_CHI           = NO
-
-# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
-# and project file content.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-CHM_INDEX_ENCODING     =
-
-# The BINARY_TOC flag controls whether a binary table of contents is generated
-# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
-# enables the Previous and Next buttons.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-BINARY_TOC             = NO
-
-# The TOC_EXPAND flag can be set to YES to add extra items for group members to
-# the table of contents of the HTML help documentation and to the tree view.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-TOC_EXPAND             = NO
-
-# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
-# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
-# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
-# (.qch) of the generated HTML documentation.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_QHP           = NO
-
-# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
-# the file name of the resulting .qch file. The path specified is relative to
-# the HTML output folder.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QCH_FILE               =
-
-# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
-# Project output. For more information please see Qt Help Project / Namespace
-# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
-# The default value is: org.doxygen.Project.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_NAMESPACE          = org.doxygen.Project
-
-# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
-# Help Project output. For more information please see Qt Help Project / Virtual
-# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
-# folders).
-# The default value is: doc.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_VIRTUAL_FOLDER     = doc
-
-# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
-# filter to add. For more information please see Qt Help Project / Custom
-# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
-# filters).
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_CUST_FILTER_NAME   =
-
-# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
-# custom filter to add. For more information please see Qt Help Project / Custom
-# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
-# filters).
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_CUST_FILTER_ATTRS  =
-
-# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
-# project's filter section matches. Qt Help Project / Filter Attributes (see:
-# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_SECT_FILTER_ATTRS  =
-
-# The QHG_LOCATION tag can be used to specify the location of Qt's
-# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
-# generated .qhp file.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHG_LOCATION           =
-
-# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
-# generated, together with the HTML files, they form an Eclipse help plugin. To
-# install this plugin and make it available under the help contents menu in
-# Eclipse, the contents of the directory containing the HTML and XML files needs
-# to be copied into the plugins directory of eclipse. The name of the directory
-# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
-# After copying Eclipse needs to be restarted before the help appears.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_ECLIPSEHELP   = NO
-
-# A unique identifier for the Eclipse help plugin. When installing the plugin
-# the directory name containing the HTML and XML files should also have this
-# name. Each documentation set should have its own identifier.
-# The default value is: org.doxygen.Project.
-# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
-
-ECLIPSE_DOC_ID         = org.doxygen.Project
-
-# If you want full control over the layout of the generated HTML pages it might
-# be necessary to disable the index and replace it with your own. The
-# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
-# of each HTML page. A value of NO enables the index and the value YES disables
-# it. Since the tabs in the index contain the same information as the navigation
-# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-DISABLE_INDEX          = NO
-
-# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
-# structure should be generated to display hierarchical information. If the tag
-# value is set to YES, a side panel will be generated containing a tree-like
-# index structure (just like the one that is generated for HTML Help). For this
-# to work a browser that supports JavaScript, DHTML, CSS and frames is required
-# (i.e. any modern browser). Windows users are probably better off using the
-# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
-# further fine-tune the look of the index. As an example, the default style
-# sheet generated by doxygen has an example that shows how to put an image at
-# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
-# the same information as the tab index, you could consider setting
-# DISABLE_INDEX to YES when enabling this option.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_TREEVIEW      = NO
-
-# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
-# doxygen will group on one line in the generated HTML documentation.
-#
-# Note that a value of 0 will completely suppress the enum values from appearing
-# in the overview section.
-# Minimum value: 0, maximum value: 20, default value: 4.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-ENUM_VALUES_PER_LINE   = 4
-
-# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
-# to set the initial width (in pixels) of the frame in which the tree is shown.
-# Minimum value: 0, maximum value: 1500, default value: 250.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-TREEVIEW_WIDTH         = 250
-
-# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
-# external symbols imported via tag files in a separate window.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-EXT_LINKS_IN_WINDOW    = NO
-
-# Use this tag to change the font size of LaTeX formulas included as images in
-# the HTML documentation. When you change the font size after a successful
-# doxygen run you need to manually remove any form_*.png images from the HTML
-# output directory to force them to be regenerated.
-# Minimum value: 8, maximum value: 50, default value: 10.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-FORMULA_FONTSIZE       = 10
-
-# Use the FORMULA_TRANPARENT tag to determine whether or not the images
-# generated for formulas are transparent PNGs. Transparent PNGs are not
-# supported properly for IE 6.0, but are supported on all modern browsers.
-#
-# Note that when changing this option you need to delete any form_*.png files in
-# the HTML output directory before the changes have effect.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-FORMULA_TRANSPARENT    = YES
-
-# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
-# http://www.mathjax.org) which uses client side Javascript for the rendering
-# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
-# installed or if you want to formulas look prettier in the HTML output. When
-# enabled you may also need to install MathJax separately and configure the path
-# to it using the MATHJAX_RELPATH option.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-USE_MATHJAX            = NO
-
-# When MathJax is enabled you can set the default output format to be used for
-# the MathJax output. See the MathJax site (see:
-# http://docs.mathjax.org/en/latest/output.html) for more details.
-# Possible values are: HTML-CSS (which is slower, but has the best
-# compatibility), NativeMML (i.e. MathML) and SVG.
-# The default value is: HTML-CSS.
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_FORMAT         = HTML-CSS
-
-# When MathJax is enabled you need to specify the location relative to the HTML
-# output directory using the MATHJAX_RELPATH option. The destination directory
-# should contain the MathJax.js script. For instance, if the mathjax directory
-# is located at the same level as the HTML output directory, then
-# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
-# Content Delivery Network so you can quickly see the result without installing
-# MathJax. However, it is strongly recommended to install a local copy of
-# MathJax from http://www.mathjax.org before deployment.
-# The default value is: http://cdn.mathjax.org/mathjax/latest.
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest
-
-# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
-# extension names that should be enabled during MathJax rendering. For example
-# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_EXTENSIONS     =
-
-# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
-# of code that will be used on startup of the MathJax code. See the MathJax site
-# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
-# example see the documentation.
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_CODEFILE       =
-
-# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
-# the HTML output. The underlying search engine uses javascript and DHTML and
-# should work on any modern browser. Note that when using HTML help
-# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
-# there is already a search function so this one should typically be disabled.
-# For large projects the javascript based search engine can be slow, then
-# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
-# search using the keyboard; to jump to the search box use <access key> + S
-# (what the <access key> is depends on the OS and browser, but it is typically
-# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
-# key> to jump into the search results window, the results can be navigated
-# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
-# the search. The filter options can be selected when the cursor is inside the
-# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
-# to select a filter and <Enter> or <escape> to activate or cancel the filter
-# option.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-SEARCHENGINE           = YES
-
-# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
-# implemented using a web server instead of a web client using Javascript. There
-# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
-# setting. When disabled, doxygen will generate a PHP script for searching and
-# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
-# and searching needs to be provided by external tools. See the section
-# "External Indexing and Searching" for details.
-# The default value is: NO.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-SERVER_BASED_SEARCH    = NO
-
-# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
-# script for searching. Instead the search results are written to an XML file
-# which needs to be processed by an external indexer. Doxygen will invoke an
-# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
-# search results.
-#
-# Doxygen ships with an example indexer (doxyindexer) and search engine
-# (doxysearch.cgi) which are based on the open source search engine library
-# Xapian (see: http://xapian.org/).
-#
-# See the section "External Indexing and Searching" for details.
-# The default value is: NO.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-EXTERNAL_SEARCH        = NO
-
-# The SEARCHENGINE_URL should point to a search engine hosted by a web server
-# which will return the search results when EXTERNAL_SEARCH is enabled.
-#
-# Doxygen ships with an example indexer (doxyindexer) and search engine
-# (doxysearch.cgi) which are based on the open source search engine library
-# Xapian (see: http://xapian.org/). See the section "External Indexing and
-# Searching" for details.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-SEARCHENGINE_URL       =
-
-# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
-# search data is written to a file for indexing by an external tool. With the
-# SEARCHDATA_FILE tag the name of this file can be specified.
-# The default file is: searchdata.xml.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-SEARCHDATA_FILE        = searchdata.xml
-
-# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
-# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
-# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
-# projects and redirect the results back to the right project.
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-EXTERNAL_SEARCH_ID     =
-
-# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
-# projects other than the one defined by this configuration file, but that are
-# all added to the same external search index. Each project needs to have a
-# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
-# to a relative location where the documentation can be found. The format is:
-# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
-# This tag requires that the tag SEARCHENGINE is set to YES.
-
-EXTRA_SEARCH_MAPPINGS  =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the LaTeX output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
-# The default value is: YES.
-
-GENERATE_LATEX         = YES
-
-# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: latex.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_OUTPUT           = latex
-
-# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
-# invoked.
-#
-# Note that when enabling USE_PDFLATEX this option is only used for generating
-# bitmaps for formulas in the HTML output, but not in the Makefile that is
-# written to the output directory.
-# The default file is: latex.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_CMD_NAME         = latex
-
-# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
-# index for LaTeX.
-# The default file is: makeindex.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-MAKEINDEX_CMD_NAME     = makeindex
-
-# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
-# documents. This may be useful for small projects and may help to save some
-# trees in general.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-COMPACT_LATEX          = NO
-
-# The PAPER_TYPE tag can be used to set the paper type that is used by the
-# printer.
-# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
-# 14 inches) and executive (7.25 x 10.5 inches).
-# The default value is: a4.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-PAPER_TYPE             = a4
-
-# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
-# that should be included in the LaTeX output. The package can be specified just
-# by its name or with the correct syntax as to be used with the LaTeX
-# \usepackage command. To get the times font for instance you can specify :
-# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
-# To use the option intlimits with the amsmath package you can specify:
-# EXTRA_PACKAGES=[intlimits]{amsmath}
-# If left blank no extra packages will be included.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-EXTRA_PACKAGES         =
-
-# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
-# generated LaTeX document. The header should contain everything until the first
-# chapter. If it is left blank doxygen will generate a standard header. See
-# section "Doxygen usage" for information on how to let doxygen write the
-# default header to a separate file.
-#
-# Note: Only use a user-defined header if you know what you are doing! The
-# following commands have a special meaning inside the header: $title,
-# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
-# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
-# string, for the replacement values of the other commands the user is referred
-# to HTML_HEADER.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_HEADER           =
-
-# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
-# generated LaTeX document. The footer should contain everything after the last
-# chapter. If it is left blank doxygen will generate a standard footer. See
-# LATEX_HEADER for more information on how to generate a default footer and what
-# special commands can be used inside the footer.
-#
-# Note: Only use a user-defined footer if you know what you are doing!
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_FOOTER           =
-
-# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
-# LaTeX style sheets that are included after the standard style sheets created
-# by doxygen. Using this option one can overrule certain style aspects. Doxygen
-# will copy the style sheet files to the output directory.
-# Note: The order of the extra style sheet files is of importance (e.g. the last
-# style sheet in the list overrules the setting of the previous ones in the
-# list).
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_EXTRA_STYLESHEET =
-
-# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
-# other source files which should be copied to the LATEX_OUTPUT output
-# directory. Note that the files will be copied as-is; there are no commands or
-# markers available.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_EXTRA_FILES      =
-
-# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
-# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
-# contain links (just like the HTML output) instead of page references. This
-# makes the output suitable for online browsing using a PDF viewer.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-PDF_HYPERLINKS         = YES
-
-# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
-# the PDF file directly from the LaTeX files. Set this option to YES, to get a
-# higher quality PDF documentation.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-USE_PDFLATEX           = YES
-
-# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
-# command to the generated LaTeX files. This will instruct LaTeX to keep running
-# if errors occur, instead of asking the user for help. This option is also used
-# when generating formulas in HTML.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_BATCHMODE        = NO
-
-# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
-# index chapters (such as File Index, Compound Index, etc.) in the output.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_HIDE_INDICES     = NO
-
-# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
-# code with syntax highlighting in the LaTeX output.
-#
-# Note that which sources are shown also depends on other settings such as
-# SOURCE_BROWSER.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_SOURCE_CODE      = NO
-
-# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
-# bibliography, e.g. plainnat, or ieeetr. See
-# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
-# The default value is: plain.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_BIB_STYLE        = plain
-
-# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
-# page will contain the date and time when the page was generated. Setting this
-# to NO can help when comparing the output of multiple runs.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_TIMESTAMP        = NO
-
-#---------------------------------------------------------------------------
-# Configuration options related to the RTF output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
-# RTF output is optimized for Word 97 and may not look too pretty with other RTF
-# readers/editors.
-# The default value is: NO.
-
-GENERATE_RTF           = NO
-
-# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: rtf.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_OUTPUT             = rtf
-
-# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
-# documents. This may be useful for small projects and may help to save some
-# trees in general.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-COMPACT_RTF            = NO
-
-# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
-# contain hyperlink fields. The RTF file will contain links (just like the HTML
-# output) instead of page references. This makes the output suitable for online
-# browsing using Word or some other Word compatible readers that support those
-# fields.
-#
-# Note: WordPad (write) and others do not support links.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_HYPERLINKS         = NO
-
-# Load stylesheet definitions from file. Syntax is similar to doxygen's config
-# file, i.e. a series of assignments. You only have to provide replacements,
-# missing definitions are set to their default value.
-#
-# See also section "Doxygen usage" for information on how to generate the
-# default style sheet that doxygen normally uses.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_STYLESHEET_FILE    =
-
-# Set optional variables used in the generation of an RTF document. Syntax is
-# similar to doxygen's config file. A template extensions file can be generated
-# using doxygen -e rtf extensionFile.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_EXTENSIONS_FILE    =
-
-# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
-# with syntax highlighting in the RTF output.
-#
-# Note that which sources are shown also depends on other settings such as
-# SOURCE_BROWSER.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_SOURCE_CODE        = NO
-
-#---------------------------------------------------------------------------
-# Configuration options related to the man page output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
-# classes and files.
-# The default value is: NO.
-
-GENERATE_MAN           = NO
-
-# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it. A directory man3 will be created inside the directory specified by
-# MAN_OUTPUT.
-# The default directory is: man.
-# This tag requires that the tag GENERATE_MAN is set to YES.
-
-MAN_OUTPUT             = man
-
-# The MAN_EXTENSION tag determines the extension that is added to the generated
-# man pages. In case the manual section does not start with a number, the number
-# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
-# optional.
-# The default value is: .3.
-# This tag requires that the tag GENERATE_MAN is set to YES.
-
-MAN_EXTENSION          = .3
-
-# The MAN_SUBDIR tag determines the name of the directory created within
-# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
-# MAN_EXTENSION with the initial . removed.
-# This tag requires that the tag GENERATE_MAN is set to YES.
-
-MAN_SUBDIR             =
-
-# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
-# will generate one additional man file for each entity documented in the real
-# man page(s). These additional files only source the real man page, but without
-# them the man command would be unable to find the correct page.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_MAN is set to YES.
-
-MAN_LINKS              = NO
-
-#---------------------------------------------------------------------------
-# Configuration options related to the XML output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
-# captures the structure of the code including all documentation.
-# The default value is: NO.
-
-GENERATE_XML           = NO
-
-# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: xml.
-# This tag requires that the tag GENERATE_XML is set to YES.
-
-XML_OUTPUT             = xml
-
-# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
-# listings (including syntax highlighting and cross-referencing information) to
-# the XML output. Note that enabling this will significantly increase the size
-# of the XML output.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_XML is set to YES.
-
-XML_PROGRAMLISTING     = YES
-
-#---------------------------------------------------------------------------
-# Configuration options related to the DOCBOOK output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
-# that can be used to generate PDF.
-# The default value is: NO.
-
-GENERATE_DOCBOOK       = NO
-
-# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
-# front of it.
-# The default directory is: docbook.
-# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
-
-DOCBOOK_OUTPUT         = docbook
-
-# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
-# program listings (including syntax highlighting and cross-referencing
-# information) to the DOCBOOK output. Note that enabling this will significantly
-# increase the size of the DOCBOOK output.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
-
-DOCBOOK_PROGRAMLISTING = NO
-
-#---------------------------------------------------------------------------
-# Configuration options for the AutoGen Definitions output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
-# AutoGen Definitions (see http://autogen.sf.net) file that captures the
-# structure of the code including all documentation. Note that this feature is
-# still experimental and incomplete at the moment.
-# The default value is: NO.
-
-GENERATE_AUTOGEN_DEF   = NO
-
-#---------------------------------------------------------------------------
-# Configuration options related to the Perl module output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
-# file that captures the structure of the code including all documentation.
-#
-# Note that this feature is still experimental and incomplete at the moment.
-# The default value is: NO.
-
-GENERATE_PERLMOD       = NO
-
-# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
-# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
-# output from the Perl module output.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_PERLMOD is set to YES.
-
-PERLMOD_LATEX          = NO
-
-# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
-# formatted so it can be parsed by a human reader. This is useful if you want to
-# understand what is going on. On the other hand, if this tag is set to NO, the
-# size of the Perl module output will be much smaller and Perl will parse it
-# just the same.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_PERLMOD is set to YES.
-
-PERLMOD_PRETTY         = YES
-
-# The names of the make variables in the generated doxyrules.make file are
-# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
-# so different doxyrules.make files included by the same Makefile don't
-# overwrite each other's variables.
-# This tag requires that the tag GENERATE_PERLMOD is set to YES.
-
-PERLMOD_MAKEVAR_PREFIX =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the preprocessor
-#---------------------------------------------------------------------------
-
-# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
-# C-preprocessor directives found in the sources and include files.
-# The default value is: YES.
-
-ENABLE_PREPROCESSING   = YES
-
-# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
-# in the source code. If set to NO, only conditional compilation will be
-# performed. Macro expansion can be done in a controlled way by setting
-# EXPAND_ONLY_PREDEF to YES.
-# The default value is: NO.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-MACRO_EXPANSION        = NO
-
-# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
-# the macro expansion is limited to the macros specified with the PREDEFINED and
-# EXPAND_AS_DEFINED tags.
-# The default value is: NO.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-EXPAND_ONLY_PREDEF     = NO
-
-# If the SEARCH_INCLUDES tag is set to YES, the include files in the
-# INCLUDE_PATH will be searched if a #include is found.
-# The default value is: YES.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-SEARCH_INCLUDES        = YES
-
-# The INCLUDE_PATH tag can be used to specify one or more directories that
-# contain include files that are not input files but should be processed by the
-# preprocessor.
-# This tag requires that the tag SEARCH_INCLUDES is set to YES.
-
-INCLUDE_PATH           =
-
-# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
-# patterns (like *.h and *.hpp) to filter out the header-files in the
-# directories. If left blank, the patterns specified with FILE_PATTERNS will be
-# used.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-INCLUDE_FILE_PATTERNS  =
-
-# The PREDEFINED tag can be used to specify one or more macro names that are
-# defined before the preprocessor is started (similar to the -D option of e.g.
-# gcc). The argument of the tag is a list of macros of the form: name or
-# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
-# is assumed. To prevent a macro definition from being undefined via #undef or
-# recursively expanded use the := operator instead of the = operator.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-PREDEFINED             =
-
-# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
-# tag can be used to specify a list of macro names that should be expanded. The
-# macro definition that is found in the sources will be used. Use the PREDEFINED
-# tag if you want to use a different macro definition that overrules the
-# definition found in the source code.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-EXPAND_AS_DEFINED      =
-
-# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
-# remove all references to function-like macros that are alone on a line, have
-# an all uppercase name, and do not end with a semicolon. Such function macros
-# are typically used for boiler-plate code, and will confuse the parser if not
-# removed.
-# The default value is: YES.
-# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-
-SKIP_FUNCTION_MACROS   = YES
-
-#---------------------------------------------------------------------------
-# Configuration options related to external references
-#---------------------------------------------------------------------------
-
-# The TAGFILES tag can be used to specify one or more tag files. For each tag
-# file the location of the external documentation should be added. The format of
-# a tag file without this location is as follows:
-# TAGFILES = file1 file2 ...
-# Adding location for the tag files is done as follows:
-# TAGFILES = file1=loc1 "file2 = loc2" ...
-# where loc1 and loc2 can be relative or absolute paths or URLs. See the
-# section "Linking to external documentation" for more information about the use
-# of tag files.
-# Note: Each tag file must have a unique name (where the name does NOT include
-# the path). If a tag file is not located in the directory in which doxygen is
-# run, you must also specify the path to the tagfile here.
-
-TAGFILES               =
-
-# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
-# tag file that is based on the input files it reads. See section "Linking to
-# external documentation" for more information about the usage of tag files.
-
-GENERATE_TAGFILE       =
-
-# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
-# the class index. If set to NO, only the inherited external classes will be
-# listed.
-# The default value is: NO.
-
-ALLEXTERNALS           = NO
-
-# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
-# in the modules index. If set to NO, only the current project's groups will be
-# listed.
-# The default value is: YES.
-
-EXTERNAL_GROUPS        = YES
-
-# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
-# the related pages index. If set to NO, only the current project's pages will
-# be listed.
-# The default value is: YES.
-
-EXTERNAL_PAGES         = YES
-
-# The PERL_PATH should be the absolute path and name of the perl script
-# interpreter (i.e. the result of 'which perl').
-# The default file (with absolute path) is: /usr/bin/perl.
-
-PERL_PATH              = /usr/bin/perl
-
-#---------------------------------------------------------------------------
-# Configuration options related to the dot tool
-#---------------------------------------------------------------------------
-
-# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
-# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
-# NO turns the diagrams off. Note that this option also works with HAVE_DOT
-# disabled, but it is recommended to install and use dot, since it yields more
-# powerful graphs.
-# The default value is: YES.
-
-CLASS_DIAGRAMS         = YES
-
-# You can define message sequence charts within doxygen comments using the \msc
-# command. Doxygen will then run the mscgen tool (see:
-# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
-# documentation. The MSCGEN_PATH tag allows you to specify the directory where
-# the mscgen tool resides. If left empty the tool is assumed to be found in the
-# default search path.
-
-MSCGEN_PATH            =
-
-# You can include diagrams made with dia in doxygen documentation. Doxygen will
-# then run dia to produce the diagram and insert it in the documentation. The
-# DIA_PATH tag allows you to specify the directory where the dia binary resides.
-# If left empty dia is assumed to be found in the default search path.
-
-DIA_PATH               =
-
-# If set to YES the inheritance and collaboration graphs will hide inheritance
-# and usage relations if the target is undocumented or is not a class.
-# The default value is: YES.
-
-HIDE_UNDOC_RELATIONS   = YES
-
-# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
-# available from the path. This tool is part of Graphviz (see:
-# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
-# Bell Labs. The other options in this section have no effect if this option is
-# set to NO
-# The default value is: YES.
-
-HAVE_DOT               = YES
-
-# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
-# to run in parallel. When set to 0 doxygen will base this on the number of
-# processors available in the system. You can set it explicitly to a value
-# larger than 0 to get control over the balance between CPU load and processing
-# speed.
-# Minimum value: 0, maximum value: 32, default value: 0.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_NUM_THREADS        = 0
-
-# When you want a differently looking font in the dot files that doxygen
-# generates you can specify the font name using DOT_FONTNAME. You need to make
-# sure dot is able to find the font, which can be done by putting it in a
-# standard location or by setting the DOTFONTPATH environment variable or by
-# setting DOT_FONTPATH to the directory containing the font.
-# The default value is: Helvetica.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_FONTNAME           = Helvetica
-
-# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
-# dot graphs.
-# Minimum value: 4, maximum value: 24, default value: 10.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_FONTSIZE           = 10
-
-# By default doxygen will tell dot to use the default font as specified with
-# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
-# the path where dot can find it using this tag.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_FONTPATH           =
-
-# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
-# each documented class showing the direct and indirect inheritance relations.
-# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-CLASS_GRAPH            = YES
-
-# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
-# graph for each documented class showing the direct and indirect implementation
-# dependencies (inheritance, containment, and class references variables) of the
-# class with other documented classes.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-COLLABORATION_GRAPH    = YES
-
-# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
-# groups, showing the direct groups dependencies.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-GROUP_GRAPHS           = YES
-
-# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
-# collaboration diagrams in a style similar to the OMG's Unified Modeling
-# Language.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-UML_LOOK               = NO
-
-# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
-# class node. If there are many fields or methods and many nodes the graph may
-# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
-# number of items for each type to make the size more manageable. Set this to 0
-# for no limit. Note that the threshold may be exceeded by 50% before the limit
-# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
-# but if the number exceeds 15, the total amount of fields shown is limited to
-# 10.
-# Minimum value: 0, maximum value: 100, default value: 10.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-UML_LIMIT_NUM_FIELDS   = 10
-
-# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
-# collaboration graphs will show the relations between templates and their
-# instances.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-TEMPLATE_RELATIONS     = NO
-
-# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
-# YES then doxygen will generate a graph for each documented file showing the
-# direct and indirect include dependencies of the file with other documented
-# files.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-INCLUDE_GRAPH          = YES
-
-# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
-# set to YES then doxygen will generate a graph for each documented file showing
-# the direct and indirect include dependencies of the file with other documented
-# files.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-INCLUDED_BY_GRAPH      = YES
-
-# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
-# dependency graph for every global function or class method.
-#
-# Note that enabling this option will significantly increase the time of a run.
-# So in most cases it will be better to enable call graphs for selected
-# functions only using the \callgraph command. Disabling a call graph can be
-# accomplished by means of the command \hidecallgraph.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-CALL_GRAPH             = NO
-
-# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
-# dependency graph for every global function or class method.
-#
-# Note that enabling this option will significantly increase the time of a run.
-# So in most cases it will be better to enable caller graphs for selected
-# functions only using the \callergraph command. Disabling a caller graph can be
-# accomplished by means of the command \hidecallergraph.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-CALLER_GRAPH           = NO
-
-# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
-# hierarchy of all classes instead of a textual one.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-GRAPHICAL_HIERARCHY    = YES
-
-# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
-# dependencies a directory has on other directories in a graphical way. The
-# dependency relations are determined by the #include relations between the
-# files in the directories.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DIRECTORY_GRAPH        = YES
-
-# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
-# generated by dot. For an explanation of the image formats see the section
-# output formats in the documentation of the dot tool (Graphviz (see:
-# http://www.graphviz.org/)).
-# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
-# to make the SVG files visible in IE 9+ (other browsers do not have this
-# requirement).
-# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd,
-# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo,
-# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo,
-# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
-# png:gdiplus:gdiplus.
-# The default value is: png.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_IMAGE_FORMAT       = png
-
-# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
-# enable generation of interactive SVG images that allow zooming and panning.
-#
-# Note that this requires a modern browser other than Internet Explorer. Tested
-# and working are Firefox, Chrome, Safari, and Opera.
-# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
-# the SVG files visible. Older versions of IE do not have SVG support.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-INTERACTIVE_SVG        = NO
-
-# The DOT_PATH tag can be used to specify the path where the dot tool can be
-# found. If left blank, it is assumed the dot tool can be found in the path.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_PATH               =
-
-# The DOTFILE_DIRS tag can be used to specify one or more directories that
-# contain dot files that are included in the documentation (see the \dotfile
-# command).
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOTFILE_DIRS           =
-
-# The MSCFILE_DIRS tag can be used to specify one or more directories that
-# contain msc files that are included in the documentation (see the \mscfile
-# command).
-
-MSCFILE_DIRS           =
-
-# The DIAFILE_DIRS tag can be used to specify one or more directories that
-# contain dia files that are included in the documentation (see the \diafile
-# command).
-
-DIAFILE_DIRS           =
-
-# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
-# path where java can find the plantuml.jar file. If left blank, it is assumed
-# PlantUML is not used or called during a preprocessing step. Doxygen will
-# generate a warning when it encounters a \startuml command in this case and
-# will not generate output for the diagram.
-
-PLANTUML_JAR_PATH      =
-
-# When using plantuml, the specified paths are searched for files specified by
-# the !include statement in a plantuml block.
-
-PLANTUML_INCLUDE_PATH  =
-
-# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
-# that will be shown in the graph. If the number of nodes in a graph becomes
-# larger than this value, doxygen will truncate the graph, which is visualized
-# by representing a node as a red box. Note that doxygen if the number of direct
-# children of the root node in a graph is already larger than
-# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
-# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
-# Minimum value: 0, maximum value: 10000, default value: 50.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_GRAPH_MAX_NODES    = 50
-
-# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
-# generated by dot. A depth value of 3 means that only nodes reachable from the
-# root by following a path via at most 3 edges will be shown. Nodes that lay
-# further from the root node will be omitted. Note that setting this option to 1
-# or 2 may greatly reduce the computation time needed for large code bases. Also
-# note that the size of a graph can be further restricted by
-# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
-# Minimum value: 0, maximum value: 1000, default value: 0.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-MAX_DOT_GRAPH_DEPTH    = 0
-
-# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
-# background. This is disabled by default, because dot on Windows does not seem
-# to support this out of the box.
-#
-# Warning: Depending on the platform used, enabling this option may lead to
-# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
-# read).
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_TRANSPARENT        = NO
-
-# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
-# files in one run (i.e. multiple -o and -T options on the command line). This
-# makes dot run faster, but since only newer versions of dot (>1.8.10) support
-# this, this feature is disabled by default.
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_MULTI_TARGETS      = NO
-
-# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
-# explaining the meaning of the various boxes and arrows in the dot generated
-# graphs.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-GENERATE_LEGEND        = YES
-
-# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
-# files that are used to generate the various graphs.
-# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_CLEANUP            = YES
diff --git a/vta/docs/Makefile b/vta/docs/Makefile
deleted file mode 100644
index 3a8551525181..000000000000
--- a/vta/docs/Makefile
+++ /dev/null
@@ -1,193 +0,0 @@
-# Makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line.
-SPHINXOPTS    =
-SPHINXBUILD   = python3 -m sphinx
-PAPER         =
-BUILDDIR      = _build
-
-# User-friendly check for sphinx-build
-#ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
-#$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively# you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
-#endif
-
-# Internal variables.
-PAPEROPT_a4     = -D latex_paper_size=a4
-PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-# the i18n builder cannot share the environment and doctrees with the others
-I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-
-.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
-
-help:
-	@echo "Please use \`make <target>' where <target> is one of"
-	@echo "  html       to make standalone HTML files"
-	@echo "  dirhtml    to make HTML files named index.html in directories"
-	@echo "  singlehtml to make a single large HTML file"
-	@echo "  pickle     to make pickle files"
-	@echo "  json       to make JSON files"
-	@echo "  htmlhelp   to make HTML files and a HTML help project"
-	@echo "  qthelp     to make HTML files and a qthelp project"
-	@echo "  applehelp  to make an Apple Help Book"
-	@echo "  devhelp    to make HTML files and a Devhelp project"
-	@echo "  epub       to make an epub"
-	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
-	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
-	@echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
-	@echo "  text       to make text files"
-	@echo "  man        to make manual pages"
-	@echo "  texinfo    to make Texinfo files"
-	@echo "  info       to make Texinfo files and run them through makeinfo"
-	@echo "  gettext    to make PO message catalogs"
-	@echo "  changes    to make an overview of all changed/added/deprecated items"
-	@echo "  xml        to make Docutils-native XML files"
-	@echo "  pseudoxml  to make pseudoxml-XML files for display purposes"
-	@echo "  linkcheck  to check all external links for integrity"
-	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
-	@echo "  coverage   to run coverage check of the documentation (if enabled)"
-
-clean:
-	rm -rf $(BUILDDIR)/*
-	rm -rf gen_modules
-
-html:
-	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
-	@echo
-	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
-
-dirhtml:
-	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
-	@echo
-	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
-
-singlehtml:
-	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
-	@echo
-	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
-
-pickle:
-	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
-	@echo
-	@echo "Build finished; now you can process the pickle files."
-
-json:
-	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
-	@echo
-	@echo "Build finished; now you can process the JSON files."
-
-htmlhelp:
-	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
-	@echo
-	@echo "Build finished; now you can run HTML Help Workshop with the" \
-	      ".hhp project file in $(BUILDDIR)/htmlhelp."
-
-qthelp:
-	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
-	@echo
-	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
-	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
-	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/rabit.qhcp"
-	@echo "To view the help file:"
-	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/rabit.qhc"
-
-applehelp:
-	$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
-	@echo
-	@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
-	@echo "N.B. You won't be able to view it unless you put it in" \
-	      "~/Library/Documentation/Help or install it in your application" \
-	      "bundle."
-
-devhelp:
-	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
-	@echo
-	@echo "Build finished."
-	@echo "To view the help file:"
-	@echo "# mkdir -p $$HOME/.local/share/devhelp/rabit"
-	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/rabit"
-	@echo "# devhelp"
-
-epub:
-	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
-	@echo
-	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
-
-latex:
-	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
-	@echo
-	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
-	@echo "Run \`make' in that directory to run these through (pdf)latex" \
-	      "(use \`make latexpdf' here to do that automatically)."
-
-latexpdf:
-	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
-	@echo "Running LaTeX files through pdflatex..."
-	$(MAKE) -C $(BUILDDIR)/latex all-pdf
-	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
-
-latexpdfja:
-	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
-	@echo "Running LaTeX files through platex and dvipdfmx..."
-	$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
-	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
-
-text:
-	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
-	@echo
-	@echo "Build finished. The text files are in $(BUILDDIR)/text."
-
-man:
-	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
-	@echo
-	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
-
-texinfo:
-	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
-	@echo
-	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
-	@echo "Run \`make' in that directory to run these through makeinfo" \
-	      "(use \`make info' here to do that automatically)."
-
-info:
-	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
-	@echo "Running Texinfo files through makeinfo..."
-	make -C $(BUILDDIR)/texinfo info
-	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
-
-gettext:
-	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
-	@echo
-	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
-
-changes:
-	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
-	@echo
-	@echo "The overview file is in $(BUILDDIR)/changes."
-
-linkcheck:
-	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
-	@echo
-	@echo "Link check complete; look for any errors in the above output " \
-	      "or in $(BUILDDIR)/linkcheck/output.txt."
-
-doctest:
-	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
-	@echo "Testing of doctests in the sources finished, look at the " \
-	      "results in $(BUILDDIR)/doctest/output.txt."
-
-coverage:
-	$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
-	@echo "Testing of coverage in the sources finished, look at the " \
-	      "results in $(BUILDDIR)/coverage/python.txt."
-
-xml:
-	$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
-	@echo
-	@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
-
-pseudoxml:
-	$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
-	@echo
-	@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
diff --git a/vta/docs/README.txt b/vta/docs/README.txt
deleted file mode 100644
index 8d93fa1004a1..000000000000
--- a/vta/docs/README.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-The documentation of vta is generated with recommonmark and sphinx.
-
-- pip install sphinx>=1.5.5 sphinx-gallery sphinx_rtd_theme matplotlib Image recommonmark
-- Type "make html" to generate the doc
-- If we only want to build doxygen docs: at project root, type "make doc"
diff --git a/vta/docs/_static/css/tvm_theme.css b/vta/docs/_static/css/tvm_theme.css
deleted file mode 100644
index 5e0838abf6cb..000000000000
--- a/vta/docs/_static/css/tvm_theme.css
+++ /dev/null
@@ -1,11 +0,0 @@
-.rst-content .hidden-section {
-    display: none;
-}
-
-.rst-toc .hidden-section {
-    display: none;
-}
-
-nav .hidden-section {
-    display: inherit;
-}
diff --git a/vta/docs/api_links.rst b/vta/docs/api_links.rst
deleted file mode 100644
index 60dba9def364..000000000000
--- a/vta/docs/api_links.rst
+++ /dev/null
@@ -1,6 +0,0 @@
-Links to API References
-=======================
-
-This page contains links to API references that are build with different doc build system.
-
-* `C++ doyxgen API <doxygen/index.html>`_
diff --git a/vta/docs/conf.py b/vta/docs/conf.py
deleted file mode 100644
index 5c4429589129..000000000000
--- a/vta/docs/conf.py
+++ /dev/null
@@ -1,213 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# documentation build configuration file, created by
-# sphinx-quickstart on Thu Jul 23 19:40:08 2015.
-#
-# This file is execfile()d with the current directory set to its
-# containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-import sys
-import os, subprocess
-import shlex
-import recommonmark
-import sphinx_gallery
-from tvm.contrib import rpc, graph_runtime
-from recommonmark.parser import CommonMarkParser
-from recommonmark.transform import AutoStructify
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
-sys.path.insert(0, os.path.join(curr_path, '../python/'))
-
-# -- General configuration ------------------------------------------------
-
-# General information about the project.
-project = u'vta'
-author = u'%s developers' % project
-copyright = u'2018, %s' % author
-github_doc_root = 'https://github.com/uwsaml/vta/tree/master/docs/'
-
-# add markdown parser
-CommonMarkParser.github_doc_root = github_doc_root
-source_parsers = {
-    '.md': CommonMarkParser
-}
-os.environ['VTA_BUILD_DOC'] = '1'
-# Version information.
-import vta
-version = vta.__version__
-release = vta.__version__
-
-# Add any Sphinx extension module names here, as strings. They can be
-# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones
-extensions = [
-    'sphinx.ext.autodoc',
-    'sphinx.ext.autosummary',
-    'sphinx.ext.intersphinx',
-    'sphinx.ext.napoleon',
-    'sphinx.ext.mathjax',
-    'sphinx_gallery.gen_gallery',
-]
-
-
-breathe_projects = {'vta' : 'doxygen/xml/'}
-breathe_default_project = 'vta'
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-# The suffix(es) of source filenames.
-# You can specify multiple suffix as a list of string:
-# source_suffix = ['.rst', '.md']
-source_suffix = ['.rst', '.md']
-
-# The encoding of source files.
-#source_encoding = 'utf-8-sig'
-
-# generate autosummary even if no references
-autosummary_generate = True
-
-# The master toctree document.
-master_doc = 'index'
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#
-# This is also used if you do content translation via gettext catalogs.
-# Usually you set "language" from the command line for these cases.
-language = None
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-#today = ''
-# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-exclude_patterns = ['_build']
-
-# The reST default role (used for this markup: `text`) to use for all
-# documents.
-#default_role = None
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-#add_module_names = True
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-#show_authors = False
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
-
-# If true, keep warnings as "system message" paragraphs in the built documents.
-#keep_warnings = False
-
-# If true, `todo` and `todoList` produce output, else they produce nothing.
-todo_include_todos = False
-
-# -- Options for HTML output ----------------------------------------------
-
-# The theme is set by the make target
-html_theme = os.environ.get('VTA_THEME', 'rtd')
-
-on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
-# only import rtd theme and set it if want to build docs locally
-if not on_rtd and html_theme == 'rtd':
-    import sphinx_rtd_theme
-    html_theme = 'sphinx_rtd_theme'
-    html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = project + 'doc'
-
-# -- Options for LaTeX output ---------------------------------------------
-latex_elements = {
-}
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title,
-#  author, documentclass [howto, manual, or own class]).
-latex_documents = [
-  (master_doc, '%s.tex' % project, project,
-   author, 'manual'),
-]
-
-# hook for doxygen
-def run_doxygen(folder):
-    """Run the doxygen make command in the designated folder."""
-    try:
-        retcode = subprocess.call("cd %s; make doc" % folder, shell=True)
-        retcode = subprocess.call("rm -rf _build/html/doxygen", shell=True)
-        retcode = subprocess.call("mkdir -p _build/html", shell=True)
-        retcode = subprocess.call("cp -rf doxygen/html _build/html/doxygen", shell=True)
-        if retcode < 0:
-            sys.stderr.write("doxygen terminated by signal %s" % (-retcode))
-    except OSError as e:
-        sys.stderr.write("doxygen execution failed: %s" % e)
-
-intersphinx_mapping = {
-    'python': ('https://docs.python.org/{.major}'.format(sys.version_info), None),
-    'numpy': ('http://docs.scipy.org/doc/numpy/', None),
-    'scipy': ('http://docs.scipy.org/doc/scipy/reference', None),
-    'matplotlib': ('http://matplotlib.org/', None),
-    'tvm': ('https://docs.tvm.ai/', None),
-}
-
-from sphinx_gallery.sorting import ExplicitOrder
-
-examples_dirs = ['../tutorials/']
-gallery_dirs = ['tutorials']
-subsection_order = ExplicitOrder([])
-
-def generate_doxygen_xml(app):
-    """Run the doxygen make commands if we're on the ReadTheDocs server"""
-    run_doxygen('..')
-
-def setup(app):
-    # Add hook for building doxygen xml when needed
-    # no c++ API for now
-    app.connect("builder-inited", generate_doxygen_xml)
-    app.add_stylesheet('css/tvm_theme.css')
-    app.add_config_value('recommonmark_config', {
-        'url_resolver': lambda url: github_doc_root + url,
-        'auto_doc_ref': True
-            }, True)
-    app.add_transform(AutoStructify)
-
-
-sphinx_gallery_conf = {
-    'backreferences_dir': 'gen_modules/backreferences',
-    'doc_module': ('vta', 'numpy'),
-'reference_url': {
-    'vta': None,
-    'tvm': 'https://docs.tvm.ai',
-    'matplotlib': 'http://matplotlib.org',
-    'numpy': 'http://docs.scipy.org/doc/numpy-1.9.1'},
-    'examples_dirs': examples_dirs,
-    'gallery_dirs': gallery_dirs,
-    'subsection_order': subsection_order,
-    'find_mayavi_figures': False,
-    'filename_pattern': '.py',
-    'expected_failing_examples': []
-}
diff --git a/vta/docs/dev/index.rst b/vta/docs/dev/index.rst
deleted file mode 100644
index c78eda8473e0..000000000000
--- a/vta/docs/dev/index.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-VTA Design and Developer Guide
-==============================
-
-Building an hardware stack for deep learning involves many
-many systems-level design decisions.
-In this part of documentation, we share the rationale for the specific choices made when designing VTA.
-
-.. toctree::
-   :maxdepth: 2
-
-   runtime
diff --git a/vta/docs/dev/runtime.md b/vta/docs/dev/runtime.md
deleted file mode 100644
index 7acab59ab0a1..000000000000
--- a/vta/docs/dev/runtime.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# VTA Runtime System
-
-TODO Document the hardware runtime system.
\ No newline at end of file
diff --git a/vta/docs/how_to/contribute.md b/vta/docs/how_to/contribute.md
deleted file mode 100644
index b0e0de93b5fe..000000000000
--- a/vta/docs/how_to/contribute.md
+++ /dev/null
@@ -1,102 +0,0 @@
-# Contribute to VTA
-
-VTA has been developed by community members.
-Everyone is more than welcome to contribute.
-It is a way to make the project better and more accessible to more users.
-VTA is part of TVM software/hardware stack,
-you can improve the compiler performance by contributing to [TVM](https://github.com/dmlc/tvm)
-
-
-- Please add your name to [CONTRIBUTORS.md](https://github.com/dmlc/vta/blob/master/CONTRIBUTORS.md)
-- Please update [NEWS.md](https://github.com/dmlc/vta/blob/master/NEWS.md) to add note on your changes to the API or added a new document.
-
-## Guidelines
-* [Submit Pull Request](#submit-pull-request)
-* [Git Workflow Howtos](#git-workflow-howtos)
-  - [How to resolve conflict with master](#how-to-resolve-conflict-with-master)
-  - [How to combine multiple commits into one](#how-to-combine-multiple-commits-into-one)
-  - [What is the consequence of force push](#what-is-the-consequence-of-force-push)
-* [Document](#document)
-* [Testcases](#testcases)
-* [Core Library](#core-library)
-* [Python Package](#python-package)
-
-## Submit Pull Request
-* Before submit, please rebase your code on the most recent version of master, you can do it by
-```bash
-git remote add upstream [url to vta repo]
-git fetch upstream
-git rebase upstream/master
-```
-* If you have multiple small commits,
-  it might be good to merge them together(use git rebase then squash) into more meaningful groups.
-* Send the pull request!
-  - Fix the problems reported by automatic checks
-  - If you are contributing a new module or new function, add a test.
-
-## Git Workflow Howtos
-### How to resolve conflict with master
-- First rebase to most recent master
-```bash
-# The first two steps can be skipped after you do it once.
-git remote add upstream [url to vta repo]
-git fetch upstream
-git rebase upstream/master
-```
-- The git may show some conflicts it cannot merge, say ```conflicted.py```.
-  - Manually modify the file to resolve the conflict.
-  - After you resolved the conflict, mark it as resolved by
-```bash
-git add conflicted.py
-```
-- Then you can continue rebase by
-```bash
-git rebase --continue
-```
-- Finally push to your fork, you may need to force push here.
-```bash
-git push --force
-```
-
-### How to combine multiple commits into one
-Sometimes we want to combine multiple commits, especially when later commits are only fixes to previous ones,
-to create a PR with set of meaningful commits. You can do it by following steps.
-- Before doing so, configure the default editor of git if you haven't done so before.
-```bash
-git config core.editor the-editor-you-like
-```
-- Assume we want to merge last 3 commits, type the following commands
-```bash
-git rebase -i HEAD~3
-```
-- It will pop up an text editor. Set the first commit as ```pick```, and change later ones to ```squash```.
-- After you saved the file, it will pop up another text editor to ask you modify the combined commit message.
-- Push the changes to your fork, you need to force push.
-```bash
-git push --force
-```
-
-### Reset to the most recent master
-You can always use git reset to reset your version to the most recent master.
-Note that all your ***local changes will get lost***.
-So only do it when you do not have local changes or when your pull request just get merged.
-```bash
-git reset --hard [hash tag of master]
-git push --force
-```
-
-### What is the consequence of force push
-The previous two tips requires force push, this is because we altered the path of the commits.
-It is fine to force push to your own fork, as long as the commits changed are only yours.
-
-## Testcases
-- All the testcases are in tests
-
-## Core Library
-- Follow Google C style for C++.
-- We use doxygen to document all the interface code.
-- You can reproduce the linter checks by typing ```make lint```
-
-## Python Package
-- Always add docstring to the new functions in numpydoc format.
-- You can reproduce the linter checks by typing ```make lint```
diff --git a/vta/docs/index.rst b/vta/docs/index.rst
deleted file mode 100644
index 5aa1dc72e2d0..000000000000
--- a/vta/docs/index.rst
+++ /dev/null
@@ -1,19 +0,0 @@
-VTA Documentation
-=================
-
-Welcome to VTA documentation.
-
-
-Contents
---------
-
-.. toctree::
-   :maxdepth: 1
-
-   self
-   how_to/install
-   tutorials/index
-   how_to/contribute
-   api/python/index
-   dev/index
-   api_links
diff --git a/vta/examples/resnet18/pynq/.gitignore b/vta/examples/resnet18/pynq/.gitignore
deleted file mode 100644
index 11686e2f9183..000000000000
--- a/vta/examples/resnet18/pynq/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-quantize_graph.json
-quantize_params.pkl
-synset.txt
-*.jpg
-vta.bit
\ No newline at end of file
diff --git a/vta/examples/resnet18/pynq/README.md b/vta/examples/resnet18/pynq/README.md
deleted file mode 100644
index e3bd3d85b1a2..000000000000
--- a/vta/examples/resnet18/pynq/README.md
+++ /dev/null
@@ -1,26 +0,0 @@
-# Resnet-18 Example on Pynq-based VTA Design
-
-Follow the first two parts of the [Installation Guide](../../../docs/how_to/install.md) to make sure that the VTA python libraries are installed, and that the RPC server is running on the Pynq FPGA dev board.
-
-We recommend leaving the `config.json` to its default parameterization (of course you can change the target between "sim" and "pynq").
-
-Simply run the example program. We rely on pickle to store parameters which now only works with python2.
-```bash
-python2 imagenet_predict.py
-```
-
-The script will first download the following files into `_data/` directory:
-* `cat.jpg` which provides a test sample for the ImageNet classifier
-* `quantize_graph.json` which describes the NNVM graph of the 8-bit ResNet-18
-* `quantize_params.plk` which contains the network parameters
-* `synset.txt` which contains the ImageNet categories
-
-Next, it will run imagenet classification using the ResNet18 architecture on a VTA design that performs 8-bit integer inference, to perform classification on a cat image `cat.jpg`.
-
-The script reports runtime measured on the Pynq board (in seconds), and the top-1 result category:
-```
-('x', (1, 3, 224, 224))
-Build complete...
-('TVM prediction top-1:', 281, 'tabby, tabby cat')
-t-cost=0.41906
-```
diff --git a/vta/hardware/xilinx/Makefile b/vta/hardware/xilinx/Makefile
index 106d5d25b348..4a6d38d34aa8 100644
--- a/vta/hardware/xilinx/Makefile
+++ b/vta/hardware/xilinx/Makefile
@@ -25,7 +25,7 @@ NO_DSP = false
 NO_ALU = false
 
 # Process VTA JSON config
-VTA_CONFIG = python $(CURDIR)/../../make/vta_config.py
+VTA_CONFIG = python $(CURDIR)/../../config/vta_config.py
 CFLAGS := $(shell ${VTA_CONFIG} --cflags)
 VTA_TARGET := $(shell ${VTA_CONFIG} --target)
 
diff --git a/vta/make/README.md b/vta/make/README.md
deleted file mode 100644
index 6bf3e6ff9ee4..000000000000
--- a/vta/make/README.md
+++ /dev/null
@@ -1,8 +0,0 @@
-# VTA Configuration
-
-Each VTA runtime/hardware configuration is specified by config.json file.
-You can copy the config.json to project root and modify the configuration
-before you type make.
-
-The config is going to affect the behavior of python package as well as
-the hardware runtime build.
\ No newline at end of file
diff --git a/vta/python/vta/environment.py b/vta/python/vta/environment.py
index 3f0717f5b08b..e97b6d87f891 100644
--- a/vta/python/vta/environment.py
+++ b/vta/python/vta/environment.py
@@ -297,11 +297,12 @@ def _init_env():
     """Iniitalize the default global env"""
     curr_path = os.path.dirname(
         os.path.abspath(os.path.expanduser(__file__)))
-    proj_root = os.path.abspath(os.path.join(curr_path, "../../"))
+    proj_root = os.path.abspath(os.path.join(curr_path, "../../../"))
     path_list = [
-        os.path.join(curr_path, "config.json"),
-        os.path.join(proj_root, "config.json"),
-        os.path.join(proj_root, "make/config.json")
+        os.path.join(curr_path, "vta_config.json"),
+        os.path.join(proj_root, "build", "vta_config.json"),
+        os.path.join(proj_root, "vta_config.json"),
+        os.path.join(proj_root, "vta/config/vta_config.json")
     ]
     path_list = [p for p in path_list if os.path.exists(p)]
     if not path_list:
diff --git a/vta/python/vta/exec/rpc_server.py b/vta/python/vta/exec/rpc_server.py
index f3db38c929bd..233d37ccad7c 100644
--- a/vta/python/vta/exec/rpc_server.py
+++ b/vta/python/vta/exec/rpc_server.py
@@ -11,23 +11,25 @@
 import json
 import tvm
 from tvm._ffi.base import c_str
-from tvm.contrib import rpc, cc
+from tvm import rpc
+from tvm.contrib import cc
 
 from ..environment import get_env
 from ..pkg_config import PkgConfig
+from ..libinfo import find_libvta
 
 
-@tvm.register_func("tvm.contrib.rpc.server.start", override=True)
+@tvm.register_func("tvm.rpc.server.start", override=True)
 def server_start():
     """VTA RPC server extension."""
     # pylint: disable=unused-variable
     curr_path = os.path.dirname(
         os.path.abspath(os.path.expanduser(__file__)))
-    proj_root = os.path.abspath(os.path.join(curr_path, "../../.."))
-    dll_path = os.path.abspath(os.path.join(proj_root, "lib/libvta.so"))
-    cfg_path = os.path.abspath(os.path.join(proj_root, "lib/libvta.so.json"))
+    proj_root = os.path.abspath(os.path.join(curr_path, "../../../../"))
+    dll_path = find_libvta()[0]
+    cfg_path = os.path.abspath(os.path.join(proj_root, "build/vta_config.json"))
     runtime_dll = []
-    _load_module = tvm.get_global_func("tvm.contrib.rpc.server.load_module")
+    _load_module = tvm.get_global_func("tvm.rpc.server.load_module")
 
     def load_vta_dll():
         """Try to load vta dll"""
@@ -36,7 +38,7 @@ def load_vta_dll():
         logging.info("Loading VTA library: %s", dll_path)
         return runtime_dll[0]
 
-    @tvm.register_func("tvm.contrib.rpc.server.load_module", override=True)
+    @tvm.register_func("tvm.rpc.server.load_module", override=True)
     def load_module(file_name):
         load_vta_dll()
         return _load_module(file_name)
@@ -48,11 +50,11 @@ def ext_dev_callback():
 
     @tvm.register_func("tvm.contrib.vta.init", override=True)
     def program_fpga(file_name):
-        path = tvm.get_global_func("tvm.contrib.rpc.server.workpath")(file_name)
+        path = tvm.get_global_func("tvm.rpc.server.workpath")(file_name)
         load_vta_dll().VTAProgram(c_str(path))
         logging.info("Program FPGA with %s", file_name)
 
-    @tvm.register_func("tvm.contrib.rpc.server.shutdown", override=True)
+    @tvm.register_func("tvm.rpc.server.shutdown", override=True)
     def server_shutdown():
         if runtime_dll:
             runtime_dll[0].VTARuntimeShutdown()
diff --git a/vta/python/vta/libinfo.py b/vta/python/vta/libinfo.py
new file mode 100644
index 000000000000..6cda7dfdeb7d
--- /dev/null
+++ b/vta/python/vta/libinfo.py
@@ -0,0 +1,25 @@
+"""Library information."""
+from __future__ import absolute_import
+import sys
+import os
+
+def _get_lib_name():
+    if sys.platform.startswith('win32'):
+        return "vta.dll"
+    if sys.platform.startswith('darwin'):
+        return "libvta.dylib"
+    return "libvta.so"
+
+
+def find_libvta(optional=False):
+    """Find VTA library"""
+    curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
+    lib_search = [curr_path]
+    lib_search += [os.path.join(curr_path, "..", "..", "..", "build",)]
+    lib_search += [os.path.join(curr_path, "..", "..", "..", "build", "Release")]
+    lib_name = _get_lib_name()
+    lib_path = [os.path.join(x, lib_name) for x in lib_search]
+    lib_found = [x for x in lib_path if os.path.exists(x)]
+    if not lib_found and not optional:
+        raise RuntimeError("Cannot find libvta: candidates are: " % str(lib_path))
+    return lib_found
diff --git a/vta/python/vta/pkg_config.py b/vta/python/vta/pkg_config.py
index 3ca5d052277b..c3fe09effb76 100644
--- a/vta/python/vta/pkg_config.py
+++ b/vta/python/vta/pkg_config.py
@@ -41,14 +41,14 @@ def __init__(self, cfg, proj_root):
         # include path
         self.include_path = [
             "-I%s/include" % proj_root,
-            "-I%s/tvm/include" % proj_root,
-            "-I%s/tvm/dlpack/include" % proj_root,
-            "-I%s/tvm/dmlc-core/include" % proj_root
+            "-I%s/vta/include" % proj_root,
+            "-I%s/dlpack/include" % proj_root,
+            "-I%s/dmlc-core/include" % proj_root
         ]
         # List of source files that can be used to build standalone library.
         self.lib_source = []
-        self.lib_source += glob.glob("%s/src/*.cc" % proj_root)
-        self.lib_source += glob.glob("%s/src/%s/*.cc" % (proj_root, cfg["TARGET"]))
+        self.lib_source += glob.glob("%s/vta/src/*.cc" % proj_root)
+        self.lib_source += glob.glob("%s/vta/src/%s/*.cc" % (proj_root, cfg["TARGET"]))
         # macro keys
         self.macro_defs = []
         self.cfg_dict = {}
diff --git a/vta/python/vta/testing/simulator.py b/vta/python/vta/testing/simulator.py
index 3505e49eeb04..5695c9419c8b 100644
--- a/vta/python/vta/testing/simulator.py
+++ b/vta/python/vta/testing/simulator.py
@@ -1,23 +1,16 @@
 """Utilities to start simulator."""
-import os
 import ctypes
 import json
 import tvm
+from ..libinfo import find_libvta
 
 def _load_lib():
     """Load local library, assuming they are simulator."""
-    # pylint: disable=unused-variable
-    curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
-    dll_path = [
-        os.path.abspath(os.path.join(curr_path, "../../../lib/libvta.so")),
-    ]
-    runtime_dll = []
-    if not all(os.path.exists(f) for f in dll_path):
+    lib_path = find_libvta(optional=True)
+    if not lib_path:
         return []
     try:
-        for fname in dll_path:
-            runtime_dll.append(ctypes.CDLL(fname, ctypes.RTLD_GLOBAL))
-        return runtime_dll
+        return [ctypes.CDLL(lib_path[0], ctypes.RTLD_GLOBAL)]
     except OSError:
         return []
 
diff --git a/vta/python/vta/testing/util.py b/vta/python/vta/testing/util.py
index 67df2bfc2c43..c01d206e9126 100644
--- a/vta/python/vta/testing/util.py
+++ b/vta/python/vta/testing/util.py
@@ -2,7 +2,7 @@
 from __future__ import absolute_import as _abs
 
 import os
-from tvm.contrib import rpc
+from tvm import rpc
 from ..environment import get_env
 from . import simulator
 
diff --git a/vta/python/vta/top/vta_conv2d.py b/vta/python/vta/top/vta_conv2d.py
index ab3b4290bb2a..5547faef743b 100644
--- a/vta/python/vta/top/vta_conv2d.py
+++ b/vta/python/vta/top/vta_conv2d.py
@@ -8,6 +8,7 @@
 import topi
 
 from nnvm.top import registry as reg, OpPattern
+from nnvm.top import nn as _nn
 from ..environment import get_env
 
 
@@ -238,9 +239,9 @@ def is_packed_layout(layout):
     """Check if layout is packed layout"""
     if layout == "NCHW":
         return False
-    assert "n" in layout
-    assert "c" in layout
-    return True
+    if "n" in layout and "c" in layout:
+        return True
+    return False
 
 @reg.register_alter_op_layout("conv2d", level=15)
 def alter_conv2d_layout(*_):
@@ -255,27 +256,18 @@ def compute_conv2d(attrs, inputs, out):
     strides = attrs.get_int_tuple("strides")
     dilation = attrs.get_int_tuple("dilation")
     groups = attrs.get_int("groups")
-    channels = attrs.get_int("channels")
     layout = attrs["layout"]
     out_dtype = attrs['out_dtype']
     assert dilation == (1, 1), "not support dilate now"
-    assert attrs.get_bool("use_bias") is False
     if is_packed_layout(layout):
         assert groups == 1
         return packed_conv2d(inputs[0], inputs[1],
                              padding, strides, out_dtype=out_dtype)
-    if groups == 1:
-        out = topi.nn.conv2d(inputs[0], inputs[1], strides, padding, out_dtype=out_dtype)
-    elif groups == get_const_int(inputs[0].shape[1]) and groups == channels:
-        out = topi.nn.depthwise_conv2d_nchw(
-            inputs[0], inputs[1], strides, padding, out_dtype=out_dtype)
-    else:
-        raise ValueError("not support arbitrary group number for now")
-    return out
+    return _nn.compute_conv2d(attrs, inputs, out)
 
 
 @reg.register_schedule("conv2d", level=15)
-def schedule_quantized_conv2d(attrs, outs, target):
+def schedule_conv2d(attrs, outs, target):
     """ 2D convolution schedule.
     """
     layout = attrs["layout"]
@@ -288,8 +280,7 @@ def schedule_quantized_conv2d(attrs, outs, target):
             return tvm.create_schedule([x.op for x in outs])
         else:
             raise RuntimeError("not support target %s" % target)
-    with tvm.target.create(target):
-        return topi.generic.schedule_conv2d_nchw(outs)
+    return _nn.schedule_conv2d(attrs, outs, target)
 
 
 def _get_workload(data, pad_data, kernel, output):
diff --git a/vta/src/device_api.cc b/vta/src/device_api.cc
index 3f312f94f0dd..88990e1b1331 100644
--- a/vta/src/device_api.cc
+++ b/vta/src/device_api.cc
@@ -8,7 +8,7 @@
 #include <dmlc/thread_local.h>
 #include <vta/runtime.h>
 
-#include "../tvm/src/runtime/workspace_pool.h"
+#include "../../src/runtime/workspace_pool.h"
 
 
 namespace tvm {
diff --git a/vta/src/runtime.cc b/vta/src/runtime.cc
index 9e84acfc047f..4d45159a10e1 100644
--- a/vta/src/runtime.cc
+++ b/vta/src/runtime.cc
@@ -155,8 +155,8 @@ class UopKernel {
     le.dst_factor = dst_factor;
     le.src_factor = src_factor;
     le.wgt_factor = wgt_factor;
-    assert(seq_.size() == 0);
-    assert(loop_.size() < 2);
+    CHECK_EQ(seq_.size(), 0U);
+    CHECK_LT(loop_.size(), 2U);
     loop_.push_back(le);
     ++loop_ptr_;
   }
@@ -196,13 +196,13 @@ class UopKernel {
     if (mode_ == 0xFFFFFFFF) {
       mode_ = mode;
     } else {
-      assert(mode_ == mode);
+      CHECK(mode_ == mode);
     }
     // Set reset_out field if unset
     if (reset_out_ == 0xFFFFFFFF) {
       reset_out_ = reset_out;
     } else {
-      assert(reset_out_ == reset_out);
+      CHECK(reset_out_ == reset_out);
     }
     // Check kernel op and imm/imm_val in ALU mode
     if (mode == 1) {
@@ -211,9 +211,9 @@ class UopKernel {
         use_imm_ = use_imm;
         imm_val_ = imm_val;
       } else {
-        assert(opcode_ == opcode);
-        assert(use_imm_ == use_imm);
-        assert(imm_val_ == imm_val);
+        CHECK(opcode_ == opcode);
+        CHECK(use_imm_ == use_imm);
+        CHECK(imm_val_ == imm_val);
       }
     }
   }
@@ -244,7 +244,7 @@ class UopKernel {
   void VerifyDep(uint32_t dst_index) {
     size_t step = std::min(static_cast<size_t>(2U), seq_.size());
     for (size_t i = seq_.size() - step; i < seq_.size(); ++i) {
-      assert(seq_[i].dst_idx != dst_index);
+      CHECK(seq_[i].dst_idx != dst_index);
     }
   }
   // The uop buffer
@@ -293,7 +293,7 @@ class BaseQueue {
     elem_bytes_ = elem_bytes;
     dram_buffer_ = static_cast<char*>(VTAMemAlloc(
         max_bytes, coherent || always_cache_));
-    assert(dram_buffer_ != nullptr);
+    CHECK(dram_buffer_ != nullptr);
     dram_phy_addr_ = VTAMemGetPhyAddr(dram_buffer_);
   }
   /*!
@@ -363,9 +363,9 @@ class UopQueue : public BaseQueue {
     size_t num_op = kernel->size();
     if (dram_end_ + num_op > kMaxElems) {
       fautosync();
-      assert(dram_end_ <= kMaxElems);
+      CHECK(dram_end_ <= kMaxElems);
     }
-    assert(num_op <= kMaxNumUop);
+    CHECK(num_op <= kMaxNumUop);
     uint32_t uop_begin = 0;
     if (sram_end_ + num_op > kMaxNumUop) {
       // Need to evict
@@ -390,7 +390,7 @@ class UopQueue : public BaseQueue {
     kernel->sram_begin_ = uop_begin;
     kernel->sram_end_ = sram_end_;
     CHECK(kernel->cached());
-    assert(uop_begin != sram_end_);
+    CHECK(uop_begin != sram_end_);
     cache_.insert(cache_.begin() + cache_ptr_, kernel);
     cache_.erase(cache_.begin() + evict_begin, cache_.begin() + cache_ptr_);
     cache_ptr_ = evict_begin + 1;
@@ -398,7 +398,7 @@ class UopQueue : public BaseQueue {
   // Flush as weight load
   void FlushUopLoad(VTAMemInsn* insn) {
     if (sram_begin_ != sram_end_) {
-      assert((dram_end_ - dram_begin_) == (sram_end_ - sram_begin_));
+      CHECK((dram_end_ - dram_begin_) == (sram_end_ - sram_begin_));
       insn->memory_type = VTA_MEM_ID_UOP;
       insn->sram_base = sram_begin_;
       insn->dram_base = dram_phy_addr_ / kElemBytes + dram_begin_;
@@ -433,12 +433,12 @@ class UopKernelMap {
   UopKernel** Get(void* signature,
                   int nbytes) {
     uint32_t key = 0;
-    assert(nbytes == 0 || nbytes == sizeof(int));
+    CHECK(nbytes == 0 || nbytes == sizeof(int));
     if (nbytes == sizeof(int)) {
       memcpy(&key, signature, sizeof(int));
       key = key + 1;
     }
-    assert(key < 100);
+    CHECK_LT(key, 100);
     if (kmap_.size() <= key) {
       kmap_.resize(key + 1, nullptr);
     }
@@ -490,8 +490,8 @@ class InsnQueue : public BaseQueue {
       pending_pop_next_[to] = 1;
     }
     // Impossible condition
-    assert(from != kLoadStage || to != kStoreStage);
-    assert(to != kLoadStage || to != kComputeStage);
+    CHECK(from != kLoadStage || to != kStoreStage);
+    CHECK(to != kLoadStage || to != kComputeStage);
   }
   // Insert dependency push of load
   void DepPush(int from, int to) {
@@ -636,15 +636,15 @@ class InsnQueue : public BaseQueue {
           // Count status in queues
           if (c.mem.opcode == VTA_OPCODE_LOAD || c.mem.opcode == VTA_OPCODE_STORE) {
             if (c.mem.opcode == VTA_OPCODE_STORE) {
-                assert(c.mem.pop_next_dep == false);
-                assert(c.mem.push_next_dep == false);
+                CHECK(c.mem.pop_next_dep == false);
+                CHECK(c.mem.push_next_dep == false);
                 if (c.mem.pop_prev_dep) g2s_queue--;
                 if (c.mem.push_prev_dep) s2g_queue++;
             } else if (c.mem.opcode == VTA_OPCODE_LOAD &&
                        (c.mem.memory_type == VTA_MEM_ID_INP ||
                         c.mem.memory_type == VTA_MEM_ID_WGT) ) {
-                assert(c.mem.pop_prev_dep == false);
-                assert(c.mem.push_prev_dep == false);
+                CHECK(c.mem.pop_prev_dep == false);
+                CHECK(c.mem.push_prev_dep == false);
                 if (c.mem.pop_next_dep) g2l_queue--;
                 if (c.mem.push_next_dep) l2g_queue++;
             } else {
@@ -742,15 +742,15 @@ class InsnQueue : public BaseQueue {
       // Count status in queues
       if (c.mem.opcode == VTA_OPCODE_LOAD || c.mem.opcode == VTA_OPCODE_STORE) {
         if (c.mem.opcode == VTA_OPCODE_STORE) {
-            assert(c.mem.pop_next_dep == false);
-            assert(c.mem.push_next_dep == false);
+            CHECK(c.mem.pop_next_dep == false);
+            CHECK(c.mem.push_next_dep == false);
             if (c.mem.pop_prev_dep) g2s_queue--;
             if (c.mem.push_prev_dep) s2g_queue++;
         } else if (c.mem.opcode == VTA_OPCODE_LOAD &&
                    (c.mem.memory_type == VTA_MEM_ID_INP ||
                     c.mem.memory_type == VTA_MEM_ID_WGT) ) {
-            assert(c.mem.pop_prev_dep == false);
-            assert(c.mem.push_prev_dep == false);
+            CHECK(c.mem.pop_prev_dep == false);
+            CHECK(c.mem.push_prev_dep == false);
             if (c.mem.pop_next_dep) g2l_queue--;
             if (c.mem.push_next_dep) l2g_queue++;
         } else {
@@ -776,7 +776,7 @@ class InsnQueue : public BaseQueue {
   void CommitPendingPop(int stage) {
     // Handle the LD<->compute queue
     // NOTE: pop executes on target(stage)
-    assert(stage > 0 && stage < 4);
+    CHECK(stage > 0 && stage < 4);
     if (pending_pop_prev_[stage] ||
         pending_pop_next_[stage]) {
       PushNoop(stage, false, false,
@@ -806,7 +806,7 @@ class InsnQueue : public BaseQueue {
   VTAGenericInsn* NextInsn() {
     VTAGenericInsn* insn  = data() + dram_end_;
     ++dram_end_;
-    assert(dram_end_ < kMaxElems);
+    CHECK(dram_end_ < kMaxElems);
     return insn;
   }
   // Create a new instruction for a given stage
@@ -840,10 +840,10 @@ class InsnQueue : public BaseQueue {
     if (insn->opcode == VTA_OPCODE_STORE) {
       // FIXME: Right now memory_type is a 2-bit field which means that
       //        VTA_MEM_ID_OUT will appear as 0. For now we'll refrain from
-      //        checking the memory_type to avoid an assertion error...
+      //        checking the memory_type to avoid an CHECKion error...
       return kStoreStage;
     }
-    assert(false);
+    LOG(FATAL) << "not reached";
     return kNoneStage;
   }
   // Push no-op
@@ -888,7 +888,7 @@ class CommandQueue {
     uop_queue_.InitSpace();
     insn_queue_.InitSpace();
     device_ = VTADeviceAlloc();
-    assert(device_ != nullptr);
+    CHECK(device_ != nullptr);
     printf("Initialize VTACommandHandle...\n");
   }
 
@@ -906,8 +906,7 @@ class CommandQueue {
       case VTA_MEM_ID_OUT: return VTA_INP_ELEM_BYTES;
       default: break;
     }
-    printf("Memory id not recognized: %d\n", memory_id);
-    assert(false);
+    LOG(FATAL) << "Memory id not recognized:" << memory_id;
     return 0;
   }
 
@@ -999,7 +998,7 @@ class CommandQueue {
     // NOTE: FINISH cannot contain pop
     VTAGemInsn* insn = insn_queue_.CreateGemInsn();
     insn->opcode = VTA_OPCODE_FINISH;
-    assert(!insn_queue_.PendingPop());
+    CHECK(!insn_queue_.PendingPop());
     // Check if there are no instruction to execute at all
     if (insn_queue_.count() == 0) return;
     // Synchronization for the queues
@@ -1010,17 +1009,17 @@ class CommandQueue {
       insn_queue_.DumpInsn();
     }
     // Make sure that the last instruction is a finish instruction
-    assert(reinterpret_cast<VTAMemInsn*>(
+    CHECK(reinterpret_cast<VTAMemInsn*>(
         insn_queue_.data())[insn_queue_.count()-1].opcode == VTA_OPCODE_FINISH);
 
     // Make sure that we don't exceed contiguous physical memory limits
-    assert(insn_queue_.count() * sizeof(VTAGenericInsn) < VTA_MAX_XFER);
+    CHECK(insn_queue_.count() * sizeof(VTAGenericInsn) < VTA_MAX_XFER);
     int timeout = VTADeviceRun(
         device_,
         insn_queue_.dram_phy_addr(),
         insn_queue_.count(),
         wait_cycles);
-    assert(timeout == 0);
+    CHECK_EQ(timeout, 0);
     // Reset buffers
     uop_queue_.Reset();
     insn_queue_.Reset();
@@ -1028,7 +1027,7 @@ class CommandQueue {
 
   // Get record kernel
   UopKernel* record_kernel() const {
-    assert(record_kernel_ != nullptr);
+    CHECK(record_kernel_ != nullptr);
     return record_kernel_;
   }
 
@@ -1048,7 +1047,7 @@ class CommandQueue {
     UopKernel** kptr = uptr[0]->Get(signature, nbytes);
     if (kptr[0] == nullptr) {
       record_kernel_ = new UopKernel(static_cast<char*>(signature), nbytes);
-      assert((*finit)(signature) == 0);
+      CHECK_EQ((*finit)(signature), 0);
       kptr[0] = static_cast<UopKernel*>(record_kernel_);
       if (debug_flag_ & VTA_DEBUG_DUMP_UOP) {
         record_kernel_->Dump();
@@ -1070,7 +1069,7 @@ class CommandQueue {
     UopKernel** kptr = uptr[0]->Get(signature, nbytes);
     if (kptr[0] == nullptr) {
       record_kernel_ = new UopKernel(static_cast<char*>(signature), nbytes);
-      assert((*finit)(signature) == 0);
+      CHECK_EQ((*finit)(signature), 0);
       kptr[0] = static_cast<UopKernel*>(record_kernel_);
       if (debug_flag_ & VTA_DEBUG_DUMP_UOP) {
         record_kernel_->Dump();
diff --git a/vta/tests/ci_build/Dockerfile.cpu b/vta/tests/ci_build/Dockerfile.cpu
deleted file mode 100644
index 43bc7e4027bf..000000000000
--- a/vta/tests/ci_build/Dockerfile.cpu
+++ /dev/null
@@ -1,26 +0,0 @@
-# For CPU
-FROM ubuntu:16.04
-
-RUN apt-get update --fix-missing
-
-COPY install/ubuntu_install_core.sh /install/ubuntu_install_core.sh
-RUN bash /install/ubuntu_install_core.sh
-
-COPY install/ubuntu_install_python.sh /install/ubuntu_install_python.sh
-RUN bash /install/ubuntu_install_python.sh
-
-COPY install/ubuntu_install_python_package.sh /install/ubuntu_install_python_package.sh
-RUN bash /install/ubuntu_install_python_package.sh
-
-COPY install/ubuntu_install_llvm.sh /install/ubuntu_install_llvm.sh
-RUN bash /install/ubuntu_install_llvm.sh
-
-COPY install/ubuntu_install_sphinx.sh /install/ubuntu_install_sphinx.sh
-RUN bash /install/ubuntu_install_sphinx.sh
-
-# Enable doxygen for c++ doc build
-RUN apt-get update && apt-get install -y doxygen graphviz
-
-# Fix recommonmark to latest version
-RUN git clone https://github.com/rtfd/recommonmark
-RUN cd recommonmark; python3 setup.py install
diff --git a/vta/tests/ci_build/Dockerfile.lint b/vta/tests/ci_build/Dockerfile.lint
deleted file mode 100644
index be869d8e9faf..000000000000
--- a/vta/tests/ci_build/Dockerfile.lint
+++ /dev/null
@@ -1,8 +0,0 @@
-# For lint test
-FROM ubuntu:16.04
-
-RUN apt-get update && apt-get install -y sudo wget
-COPY install/ubuntu_install_python.sh /install/ubuntu_install_python.sh
-RUN bash /install/ubuntu_install_python.sh
-RUN apt-get install -y doxygen graphviz
-RUN pip3 install cpplint pylint
diff --git a/vta/tests/ci_build/README.md b/vta/tests/ci_build/README.md
deleted file mode 100644
index 9813ee08424a..000000000000
--- a/vta/tests/ci_build/README.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# CI Build Scripts
-
-This directory contains the files and setup instructions to run all tests.
-
-## Run locally
-
-To run locally, we need to first install
-[docker](https://docs.docker.com/engine/installation/)
-
-Then we can run the tasks defined in the [Jenkinsfile](../../Jenkinsfile) by
-using (`ci_build.sh`)[./ci_build.sh]. For example
-
-- lint the python codes
-
-  ```bash
-  ./ci_build.sh lint make pylint
-  ```
-
-- build codes with CUDA supports
-
-  ```bash
-  ./ci_build.sh gpu tests/scripts/task_build.sh
-  ```
-
-- do the python unittest
-
-  ```bash
-  ./ci_build.sh gpu tests/scripts/task_python_test.sh
-  ```
-
-- build the documents. The results will be available at `docs/_build/html`
-
-  ```bash
-  tests/ci_build/ci_build.sh gpu tests/scripts/task_python_docs.sh
-  ```
diff --git a/vta/tests/ci_build/ci_build.sh b/vta/tests/ci_build/ci_build.sh
deleted file mode 100755
index db4283ca9753..000000000000
--- a/vta/tests/ci_build/ci_build.sh
+++ /dev/null
@@ -1,126 +0,0 @@
-#!/usr/bin/env bash
-#
-# Execute command within a docker container
-#
-# Usage: ci_build.sh <CONTAINER_TYPE> [--dockerfile <DOCKERFILE_PATH>] [-it]
-#                    <COMMAND>
-#
-# CONTAINER_TYPE: Type of the docker container used the run the build: e.g.,
-#                 (cpu | gpu)
-#
-# DOCKERFILE_PATH: (Optional) Path to the Dockerfile used for docker build.  If
-#                  this optional value is not supplied (via the --dockerfile
-#                  flag), will use Dockerfile.CONTAINER_TYPE in default
-#
-# COMMAND: Command to be executed in the docker container
-#
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-
-# Get the command line arguments.
-CONTAINER_TYPE=$( echo "$1" | tr '[:upper:]' '[:lower:]' )
-shift 1
-
-# Dockerfile to be used in docker build
-DOCKERFILE_PATH="${SCRIPT_DIR}/Dockerfile.${CONTAINER_TYPE}"
-DOCKER_CONTEXT_PATH="${SCRIPT_DIR}"
-
-if [[ "$1" == "--dockerfile" ]]; then
-    DOCKERFILE_PATH="$2"
-    DOCKER_CONTEXT_PATH=$(dirname "${DOCKERFILE_PATH}")
-    echo "Using custom Dockerfile path: ${DOCKERFILE_PATH}"
-    echo "Using custom docker build context path: ${DOCKER_CONTEXT_PATH}"
-    shift 2
-fi
-
-if [[ "$1" == "-it" ]]; then
-    CI_DOCKER_EXTRA_PARAMS+=('-it')
-    shift 1
-fi
-
-if [[ ! -f "${DOCKERFILE_PATH}" ]]; then
-    echo "Invalid Dockerfile path: \"${DOCKERFILE_PATH}\""
-    exit 1
-fi
-
-COMMAND=("$@")
-
-# Validate command line arguments.
-if [ "$#" -lt 1 ] || [ ! -e "${SCRIPT_DIR}/Dockerfile.${CONTAINER_TYPE}" ]; then
-    supported_container_types=$( ls -1 ${SCRIPT_DIR}/Dockerfile.* | \
-        sed -n 's/.*Dockerfile\.\([^\/]*\)/\1/p' | tr '\n' ' ' )
-      echo "Usage: $(basename $0) CONTAINER_TYPE COMMAND"
-      echo "       CONTAINER_TYPE can be one of [${supported_container_types}]"
-      echo "       COMMAND is a command (with arguments) to run inside"
-      echo "               the container."
-      exit 1
-fi
-
-# Use nvidia-docker if the container is GPU.
-if [[ "${CONTAINER_TYPE}" == *"gpu"* ]]; then
-    DOCKER_BINARY="nvidia-docker"
-else
-    DOCKER_BINARY="docker"
-fi
-
-# Helper function to traverse directories up until given file is found.
-function upsearch () {
-    test / == "$PWD" && return || \
-        test -e "$1" && echo "$PWD" && return || \
-        cd .. && upsearch "$1"
-}
-
-# Set up WORKSPACE and BUILD_TAG. Jenkins will set them for you or we pick
-# reasonable defaults if you run it outside of Jenkins.
-WORKSPACE="${WORKSPACE:-${SCRIPT_DIR}/../../}"
-BUILD_TAG="${BUILD_TAG:-nnvm-ci}"
-
-# Determine the docker image name
-DOCKER_IMG_NAME="${BUILD_TAG}.${CONTAINER_TYPE}"
-
-# Under Jenkins matrix build, the build tag may contain characters such as
-# commas (,) and equal signs (=), which are not valid inside docker image names.
-DOCKER_IMG_NAME=$(echo "${DOCKER_IMG_NAME}" | sed -e 's/=/_/g' -e 's/,/-/g')
-
-# Convert to all lower-case, as per requirement of Docker image names
-DOCKER_IMG_NAME=$(echo "${DOCKER_IMG_NAME}" | tr '[:upper:]' '[:lower:]')
-
-# Print arguments.
-echo "WORKSPACE: ${WORKSPACE}"
-echo "CI_DOCKER_EXTRA_PARAMS: ${CI_DOCKER_EXTRA_PARAMS[@]}"
-echo "COMMAND: ${COMMAND[@]}"
-echo "CONTAINER_TYPE: ${CONTAINER_TYPE}"
-echo "BUILD_TAG: ${BUILD_TAG}"
-echo "DOCKER CONTAINER NAME: ${DOCKER_IMG_NAME}"
-echo ""
-
-
-# Build the docker container.
-echo "Building container (${DOCKER_IMG_NAME})..."
-docker build -t ${DOCKER_IMG_NAME} \
-    -f "${DOCKERFILE_PATH}" "${DOCKER_CONTEXT_PATH}"
-
-# Check docker build status
-if [[ $? != "0" ]]; then
-    echo "ERROR: docker build failed."
-    exit 1
-fi
-
-# Run the command inside the container.
-echo "Running '${COMMAND[@]}' inside ${DOCKER_IMG_NAME}..."
-
-# By default we cleanup - remove the container once it finish running (--rm)
-# and share the PID namespace (--pid=host) so the process inside does not have
-# pid 1 and SIGKILL is propagated to the process inside (jenkins can kill it).
-echo ${DOCKER_BINARY}
-${DOCKER_BINARY} run --rm --pid=host \
-    -v ${WORKSPACE}:/workspace \
-    -w /workspace \
-    -e "CI_BUILD_HOME=/workspace" \
-    -e "CI_BUILD_USER=$(id -u -n)" \
-    -e "CI_BUILD_UID=$(id -u)" \
-    -e "CI_BUILD_GROUP=$(id -g -n)" \
-    -e "CI_BUILD_GID=$(id -g)" \
-    ${CI_DOCKER_EXTRA_PARAMS[@]} \
-    ${DOCKER_IMG_NAME} \
-    bash tests/ci_build/with_the_same_user \
-    ${COMMAND[@]}
diff --git a/vta/tests/ci_build/install/ubuntu_install_core.sh b/vta/tests/ci_build/install/ubuntu_install_core.sh
deleted file mode 100644
index efc69c946b97..000000000000
--- a/vta/tests/ci_build/install/ubuntu_install_core.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-# install libraries for building c++ core on ubuntu
-apt-get update && apt-get install -y --no-install-recommends --force-yes \
-        git make libgtest-dev cmake wget unzip libtinfo-dev libz-dev\
-        libcurl4-openssl-dev libopenblas-dev g++ sudo
-
-cd /usr/src/gtest && cmake CMakeLists.txt && make && cp *.a /usr/lib
diff --git a/vta/tests/ci_build/install/ubuntu_install_llvm.sh b/vta/tests/ci_build/install/ubuntu_install_llvm.sh
deleted file mode 100644
index 16d0fe150b7e..000000000000
--- a/vta/tests/ci_build/install/ubuntu_install_llvm.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-echo deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-4.0 main\
-     >> /etc/apt/sources.list.d/llvm.list
-echo deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial-4.0 main\
-     >> /etc/apt/sources.list.d/llvm.list
-
-echo deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-5.0 main\
-     >> /etc/apt/sources.list.d/llvm.list
-echo deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial-5.0 main\
-     >> /etc/apt/sources.list.d/llvm.list
-
-echo deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-6.0 main\
-     >> /etc/apt/sources.list.d/llvm.list
-echo deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial-6.0 main\
-     >> /etc/apt/sources.list.d/llvm.list
-
-echo deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial main\
-     >> /etc/apt/sources.list.d/llvm.list
-echo deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial main\
-     >> /etc/apt/sources.list.d/llvm.list
-
-wget -O - http://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
-apt-get update && apt-get install -y --force-yes llvm-4.0 llvm-5.0 llvm-6.0 clang-6.0
diff --git a/vta/tests/ci_build/install/ubuntu_install_python.sh b/vta/tests/ci_build/install/ubuntu_install_python.sh
deleted file mode 100644
index a34019e1003e..000000000000
--- a/vta/tests/ci_build/install/ubuntu_install_python.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-# install python and pip, don't modify this, modify install_python_package.sh
-apt-get update && apt-get install -y python-dev
-
-# python 3.6
-apt-get update && yes | apt-get install software-properties-common
-add-apt-repository ppa:jonathonf/python-3.6 &&\
-    apt-get update && apt-get install -y python-pip python-dev python3.6 python3.6-dev
-
-rm -f /usr/bin/python3 && ln -s /usr/bin/python3.6 /usr/bin/python3
-
-# Install pip
-cd /tmp && wget https://bootstrap.pypa.io/get-pip.py && python2 get-pip.py && python3.6 get-pip.py
diff --git a/vta/tests/ci_build/install/ubuntu_install_python_package.sh b/vta/tests/ci_build/install/ubuntu_install_python_package.sh
deleted file mode 100644
index 4f1518cb1abd..000000000000
--- a/vta/tests/ci_build/install/ubuntu_install_python_package.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-# install libraries for python package on ubuntu
-pip2 install nose pylint numpy nose-timer cython decorator scipy tornado
-pip3 install nose pylint numpy nose-timer cython decorator scipy tornado typed_ast
diff --git a/vta/tests/ci_build/install/ubuntu_install_sphinx.sh b/vta/tests/ci_build/install/ubuntu_install_sphinx.sh
deleted file mode 100644
index eabae1aac4fd..000000000000
--- a/vta/tests/ci_build/install/ubuntu_install_sphinx.sh
+++ /dev/null
@@ -1 +0,0 @@
-pip3 install sphinx sphinx-gallery sphinx_rtd_theme matplotlib Image commonmark>=0.7.3 docutils>=0.11
diff --git a/vta/tests/ci_build/with_the_same_user b/vta/tests/ci_build/with_the_same_user
deleted file mode 100644
index 1e6ab883694b..000000000000
--- a/vta/tests/ci_build/with_the_same_user
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env bash
-
-# This script is a wrapper creating the same user inside container as the one
-# running the ci_build.sh outside the container. It also set the home directory
-# for the user inside container to match the same absolute path as the workspace
-# outside of container.  Do not run this manually. It does not make sense. It is
-# intended to be called by ci_build.sh only.
-
-set -e
-
-COMMAND=("$@")
-
-if ! touch /this_is_writable_file_system; then
-  echo "You can't write to your filesystem!"
-  echo "If you are in Docker you should check you do not have too many images" \
-      "with too many files in them. Docker has some issue with it."
-  exit 1
-else
-  rm /this_is_writable_file_system
-fi
-
-getent group "${CI_BUILD_GID}" || addgroup --gid "${CI_BUILD_GID}" "${CI_BUILD_GROUP}"
-getent passwd "${CI_BUILD_UID}" || adduser --gid "${CI_BUILD_GID}" --uid "${CI_BUILD_UID}" \
-    --gecos "${CI_BUILD_USER} (generated by with_the_same_user script)" \
-    --disabled-password --home "${CI_BUILD_HOME}" --quiet "${CI_BUILD_USER}"
-usermod -a -G sudo "${CI_BUILD_USER}"
-echo "${CI_BUILD_USER} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/90-nopasswd-sudo
-
-HOME=${CI_BUILD_HOME}\
-    sudo -u "#${CI_BUILD_UID}" --preserve-env\
-    PATH=${PATH}\
-    LD_LIBRARY_PATH=${LD_LIBRARY_PATH}\
-    HOME=${CI_BUILD_HOME}\
-    ${COMMAND[@]}
diff --git a/vta/tests/lint/pylintrc b/vta/tests/lint/pylintrc
deleted file mode 100644
index d9918902cb1d..000000000000
--- a/vta/tests/lint/pylintrc
+++ /dev/null
@@ -1,406 +0,0 @@
-[MASTER]
-
-# Specify a configuration file.
-#rcfile=
-
-# Python code to execute, usually for sys.path manipulation such as
-# pygtk.require().
-#init-hook=
-
-# Add files or directories to the blacklist. They should be base names, not
-# paths.
-ignore=CVS, _cy2, _cy3
-
-# Add files or directories matching the regex patterns to the blacklist. The
-# regex matches against base names, not paths.
-ignore-patterns=
-
-# Pickle collected data for later comparisons.
-persistent=yes
-
-# List of plugins (as comma separated values of python modules names) to load,
-# usually to register additional checkers.
-load-plugins=
-
-# Use multiple processes to speed up Pylint.
-jobs=8
-
-# Allow loading of arbitrary C extensions. Extensions are imported into the
-# active Python interpreter and may run arbitrary code.
-unsafe-load-any-extension=no
-
-# A comma-separated list of package or module names from where C extensions may
-# be loaded. Extensions are loading into the active Python interpreter and may
-# run arbitrary code
-extension-pkg-whitelist=numpy,opencv
-
-# Allow optimization of some AST trees. This will activate a peephole AST
-# optimizer, which will apply various small optimizations. For instance, it can
-# be used to obtain the result of joining multiple strings with the addition
-# operator. Joining a lot of strings can lead to a maximum recursion error in
-# Pylint and this flag can prevent that. It has one side effect, the resulting
-# AST will be different than the one from reality. This option is deprecated
-# and it will be removed in Pylint 2.0.
-optimize-ast=no
-
-
-[MESSAGES CONTROL]
-
-# Only show warnings with the listed confidence levels. Leave empty to show
-# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
-confidence=
-
-# Enable the message, report, category or checker with the given id(s). You can
-# either give multiple identifier separated by comma (,) or put this option
-# multiple time (only on the command line, not in the configuration file where
-# it should appear only once). See also the "--disable" option for examples.
-enable=indexing-exception,old-raise-syntax
-
-# Disable the message, report, category or checker with the given id(s). You
-# can either give multiple identifiers separated by comma (,) or put this
-# option multiple times (only on the command line, not in the configuration
-# file where it should appear only once).You can also use "--disable=all" to
-# disable everything first and then reenable specific checks. For example, if
-# you want to run only the similarities checker, you can use "--disable=all
-# --enable=similarities". If you want to run only the classes checker, but have
-# no Warning level messages displayed, use"--disable=all --enable=classes
-# --disable=W"
-disable=design,similarities,no-self-use,attribute-defined-outside-init,locally-disabled,star-args,pointless-except,bad-option-value,global-statement,fixme,suppressed-message,useless-suppression,locally-enabled,no-member,no-name-in-module,import-error,unsubscriptable-object,unbalanced-tuple-unpacking,undefined-variable,protected-access
-
-[REPORTS]
-
-# Set the output format. Available formats are text, parseable, colorized, msvs
-# (visual studio) and html. You can also give a reporter class, eg
-# mypackage.mymodule.MyReporterClass.
-output-format=text
-
-# Put messages in a separate file for each module / package specified on the
-# command line instead of printing them on stdout. Reports (if any) will be
-# written in a file name "pylint_global.[txt|html]". This option is deprecated
-# and it will be removed in Pylint 2.0.
-files-output=no
-
-# Tells whether to display a full report or only the messages
-reports=no
-
-# Python expression which should return a note less than 10 (10 is the highest
-# note). You have access to the variables errors warning, statement which
-# respectively contain the number of errors / warnings messages and the total
-# number of statements analyzed. This is used by the global evaluation report
-# (RP0004).
-evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
-
-# Template used to display messages. This is a python new-style format string
-# used to format the message information. See doc for all details
-#msg-template=
-
-
-[FORMAT]
-
-# Maximum number of characters on a single line.
-max-line-length=100
-
-# Regexp for a line that is allowed to be longer than the limit.
-ignore-long-lines=^\s*(# )?<?https?://\S+>?$
-
-# Allow the body of an if to be on the same line as the test if there is no
-# else.
-single-line-if-stmt=no
-
-# List of optional constructs for which whitespace checking is disabled. `dict-
-# separator` is used to allow tabulation in dicts, etc.: {1  : 1,\n222: 2}.
-# `trailing-comma` allows a space between comma and closing bracket: (a, ).
-# `empty-line` allows space-only lines.
-no-space-check=trailing-comma,dict-separator
-
-# Maximum number of lines in a module
-max-module-lines=1000
-
-# String used as indentation unit. This is usually "    " (4 spaces) or "\t" (1
-# tab).
-indent-string='    '
-
-# Number of spaces of indent required inside a hanging  or continued line.
-indent-after-paren=4
-
-# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
-expected-line-ending-format=
-
-
-[SPELLING]
-
-# Spelling dictionary name. Available dictionaries: none. To make it working
-# install python-enchant package.
-spelling-dict=
-
-# List of comma separated words that should not be checked.
-spelling-ignore-words=
-
-# A path to a file that contains private dictionary; one word per line.
-spelling-private-dict-file=
-
-# Tells whether to store unknown words to indicated private dictionary in
-# --spelling-private-dict-file option instead of raising a message.
-spelling-store-unknown-words=no
-
-
-[MISCELLANEOUS]
-
-# List of note tags to take in consideration, separated by a comma.
-notes=FIXME,XXX
-
-
-[TYPECHECK]
-
-# Tells whether missing members accessed in mixin class should be ignored. A
-# mixin class is detected if its name ends with "mixin" (case insensitive).
-ignore-mixin-members=yes
-
-# List of module names for which member attributes should not be checked
-# (useful for modules/projects where namespaces are manipulated during runtime
-# and thus existing member attributes cannot be deduced by static analysis. It
-# supports qualified module names, as well as Unix pattern matching.
-ignored-modules=
-
-# List of class names for which member attributes should not be checked (useful
-# for classes with dynamically set attributes). This supports the use of
-# qualified names.
-ignored-classes=optparse.Values,thread._local,_thread._local
-
-# List of members which are set dynamically and missed by pylint inference
-# system, and so shouldn't trigger E1101 when accessed. Python regular
-# expressions are accepted.
-generated-members=
-
-# List of decorators that produce context managers, such as
-# contextlib.contextmanager. Add to this list to register other decorators that
-# produce valid context managers.
-contextmanager-decorators=contextlib.contextmanager
-
-
-[LOGGING]
-
-# Logging modules to check that the string format arguments are in logging
-# function parameter format
-logging-modules=logging
-
-
-[SIMILARITIES]
-
-# Minimum lines number of a similarity.
-min-similarity-lines=4
-
-# Ignore comments when computing similarities.
-ignore-comments=yes
-
-# Ignore docstrings when computing similarities.
-ignore-docstrings=yes
-
-# Ignore imports when computing similarities.
-ignore-imports=no
-
-
-[VARIABLES]
-
-# Tells whether we should check for unused import in __init__ files.
-init-import=no
-
-# A regular expression matching the name of dummy variables (i.e. expectedly
-# not used).
-dummy-variables-rgx=(_+[a-zA-Z0-9]*?$)|dummy
-
-# List of additional names supposed to be defined in builtins. Remember that
-# you should avoid to define new builtins when possible.
-additional-builtins=
-
-# List of strings which can identify a callback function by name. A callback
-# name must start or end with one of those strings.
-callbacks=cb_,_cb
-
-# List of qualified module names which can have objects that can redefine
-# builtins.
-redefining-builtins-modules=six.moves,future.builtins
-
-
-[BASIC]
-
-# Good variable names which should always be accepted, separated by a comma
-good-names=i,j,_,a,b,op,x,y,wd,lr,kv,k,v,s,p,h,c,m,n,X,t,g,f
-
-# Bad variable names which should always be refused, separated by a comma
-bad-names=
-
-# Colon-delimited sets of names that determine each other's naming style when
-# the name regexes allow several styles.
-name-group=
-
-# Include a hint for the correct naming format with invalid-name
-include-naming-hint=no
-
-# List of decorators that produce properties, such as abc.abstractproperty. Add
-# to this list to register other decorators that produce valid properties.
-property-classes=abc.abstractproperty
-
-# Regular expression matching correct module names
-module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
-
-# Naming hint for module names
-module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
-
-# Regular expression matching correct constant names
-const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
-
-# Naming hint for constant names
-const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
-
-# Regular expression matching correct inline iteration names
-inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
-
-# Naming hint for inline iteration names
-inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
-
-# Regular expression matching correct method names
-method-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Naming hint for method names
-method-name-hint=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression matching correct class attribute names
-class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
-
-# Naming hint for class attribute names
-class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
-
-# Regular expression matching correct argument names
-argument-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Naming hint for argument names
-argument-name-hint=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression matching correct attribute names
-attr-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Naming hint for attribute names
-attr-name-hint=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression matching correct variable names
-variable-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Naming hint for variable names
-variable-name-hint=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression matching correct function names
-function-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Naming hint for function names
-function-name-hint=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression matching correct class names
-class-rgx=[A-Z_][a-zA-Z0-9]+$
-
-# Naming hint for class names
-class-name-hint=[A-Z_][a-zA-Z0-9]+$
-
-# Regular expression which should only match function or class names that do
-# not require a docstring.
-no-docstring-rgx=^_
-
-# Minimum line length for functions/classes that require docstrings, shorter
-# ones are exempt.
-docstring-min-length=10
-
-
-[ELIF]
-
-# Maximum number of nested blocks for function / method body
-max-nested-blocks=5
-
-
-[CLASSES]
-
-# List of method names used to declare (i.e. assign) instance attributes.
-defining-attr-methods=__init__,__new__,setUp
-
-# List of valid names for the first argument in a class method.
-valid-classmethod-first-arg=cls
-
-# List of valid names for the first argument in a metaclass class method.
-valid-metaclass-classmethod-first-arg=mcs
-
-# List of member names, which should be excluded from the protected access
-# warning.
-exclude-protected=_asdict,_fields,_replace,_source,_make
-
-
-[IMPORTS]
-
-# Deprecated modules which should not be used, separated by a comma
-deprecated-modules=optparse
-
-# Create a graph of every (i.e. internal and external) dependencies in the
-# given file (report RP0402 must not be disabled)
-import-graph=
-
-# Create a graph of external dependencies in the given file (report RP0402 must
-# not be disabled)
-ext-import-graph=
-
-# Create a graph of internal dependencies in the given file (report RP0402 must
-# not be disabled)
-int-import-graph=
-
-# Force import order to recognize a module as part of the standard
-# compatibility libraries.
-known-standard-library=
-
-# Force import order to recognize a module as part of a third party library.
-known-third-party=enchant
-
-# Analyse import fallback blocks. This can be used to support both Python 2 and
-# 3 compatible code, which means that the block might have code that exists
-# only in one or another interpreter, leading to false positives when analysed.
-analyse-fallback-blocks=no
-
-
-[DESIGN]
-
-# Maximum number of arguments for function / method
-max-args=5
-
-# Argument names that match this expression will be ignored. Default to name
-# with leading underscore
-ignored-argument-names=_.*
-
-# Maximum number of locals for function / method body
-max-locals=15
-
-# Maximum number of return / yield for function / method body
-max-returns=6
-
-# Maximum number of branch for function / method body
-max-branches=12
-
-# Maximum number of statements in function / method body
-max-statements=50
-
-# Maximum number of parents for a class (see R0901).
-max-parents=7
-
-# Maximum number of attributes for a class (see R0902).
-max-attributes=7
-
-# Minimum number of public methods for a class (see R0903).
-min-public-methods=0
-
-# Maximum number of public methods for a class (see R0904).
-max-public-methods=20
-
-# Maximum number of boolean expressions in a if statement
-max-bool-expr=5
-
-
-[EXCEPTIONS]
-
-# Exceptions that will emit a warning when being caught. Defaults to
-# "Exception"
-overgeneral-exceptions=Exception
diff --git a/vta/tests/python/integration/test_benchmark_topi_conv2d.py b/vta/tests/python/integration/test_benchmark_topi_conv2d.py
index 1ee5cdb01167..9cf8909bc9ba 100644
--- a/vta/tests/python/integration/test_benchmark_topi_conv2d.py
+++ b/vta/tests/python/integration/test_benchmark_topi_conv2d.py
@@ -262,5 +262,4 @@ def _run(env, remote):
 
 if __name__ == "__main__":
     test_cpu_conv2d()
-    exit(0)
     test_vta_conv2d()
diff --git a/vta/tests/python/pynq/test_benchmark_conv2d.py b/vta/tests/python/pynq/test_benchmark_conv2d.py
index 5a26acb8b432..3548d2cf983e 100644
--- a/vta/tests/python/pynq/test_benchmark_conv2d.py
+++ b/vta/tests/python/pynq/test_benchmark_conv2d.py
@@ -5,7 +5,8 @@
 import numpy as np
 import topi
 from collections import namedtuple
-from tvm.contrib import rpc, util
+from tvm import rpc
+from tvm.contrib import util
 import pandas as pd
 
 host = os.environ.get("VTA_PYNQ_RPC_HOST", "pynq")
diff --git a/vta/tests/python/pynq/test_program_rpc.py b/vta/tests/python/pynq/test_program_rpc.py
index 8c998a58a6c8..3bccb963bb67 100644
--- a/vta/tests/python/pynq/test_program_rpc.py
+++ b/vta/tests/python/pynq/test_program_rpc.py
@@ -1,6 +1,6 @@
 import os
 import tvm
-from tvm.contrib import rpc
+from tvm import rpc
 from vta import get_bitstream_path, download_bitstream, program_fpga, reconfig_runtime
 
 host = os.environ.get("VTA_PYNQ_RPC_HOST", "pynq")
diff --git a/vta/tests/python/unittest/test_vta_insn.py b/vta/tests/python/unittest/test_vta_insn.py
index 79a20be13de9..b9803684b22f 100644
--- a/vta/tests/python/unittest/test_vta_insn.py
+++ b/vta/tests/python/unittest/test_vta_insn.py
@@ -2,7 +2,7 @@
 import tvm
 import numpy as np
 import topi
-from tvm.contrib import rpc, util
+from tvm.contrib import util
 
 import vta
 import vta.testing
diff --git a/vta/tests/scripts/task_build.sh b/vta/tests/scripts/task_build.sh
deleted file mode 100755
index bffd418fd393..000000000000
--- a/vta/tests/scripts/task_build.sh
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/bash
-echo "Build TVM..."
-cd tvm
-cp cmake/config.cmake .
-echo set\(USE_LLVM llvm-config-5.0\) >> config.cmake
-echo set\(USE_RPC ON\) >> config.cmake
-echo set\(USE_BLAS openblas\) >> config.cmake
-echo set\(USE_GRAPH_RUNTIME ON\) >> config.cmake
-make "$@"
-make cython
-make cython3
-cd ..
-
-echo "Build VTA..."
-make "$@"
diff --git a/vta/tests/scripts/task_clean.sh b/vta/tests/scripts/task_clean.sh
deleted file mode 100755
index 815225341ae1..000000000000
--- a/vta/tests/scripts/task_clean.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/bash
-echo "Cleanup data..."
-
-cd tvm
-make clean
-
-cd ..
-make clean
diff --git a/vta/tests/scripts/task_lint.sh b/vta/tests/scripts/task_lint.sh
deleted file mode 100755
index a6774a189f35..000000000000
--- a/vta/tests/scripts/task_lint.sh
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/bash
-echo "Check codestyle of c++ code..."
-make cpplint || exit -1
-echo "Check codestyle of python code..."
-make pylint || exit -1
-echo "Check documentations of c++ code..."
-make doc 2>log.txt
-(cat log.txt| grep -v ENABLE_PREPROCESSING |grep -v "unsupported tag") > logclean.txt
-echo "---------Error Log----------"
-cat logclean.txt
-echo "----------------------------"
-(cat logclean.txt|grep warning) && exit -1
-(cat logclean.txt|grep error) && exit -1
-rm logclean.txt
-rm log.txt
diff --git a/vta/tests/scripts/task_python_docs.sh b/vta/tests/scripts/task_python_docs.sh
deleted file mode 100755
index fd9d6211b223..000000000000
--- a/vta/tests/scripts/task_python_docs.sh
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-cd tvm
-make cython
-make cython3
-cd ../
-
-mkdir -p docs/_build/html
-# C++ doc
-make doc
-
-rm -rf python/vta/*.pyc python/vta/*/*.pyc
-
-cd docs
-PYTHONPATH=../python:../tvm/python:../tvm/topi/python:../tvm/nnvm/python make html || exit -1
-cd _build/html
-tar czf docs.tgz *
-mv docs.tgz ../../../
diff --git a/vta/tests/scripts/task_python_test.sh b/vta/tests/scripts/task_python_test.sh
deleted file mode 100755
index b146d5c9fca9..000000000000
--- a/vta/tests/scripts/task_python_test.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/bash
-
-export PYTHONPATH=python:tvm/nnvm/python:tvm/python:tvm/topi/python
-
-echo "Running unittest..."
-python -m nose -v tests/python/unittest || exit -1
-python3 -m nose -v tests/python/unittest || exit -1
-
-echo "Running integration test..."
-python -m nose -v tests/python/integration || exit -1
-python3 -m nose -v tests/python/integration || exit -1
diff --git a/vta/tutorials/README.txt b/vta/tutorials/README.txt
index 9e80d3587f11..1ba48b0b1fad 100644
--- a/vta/tutorials/README.txt
+++ b/vta/tutorials/README.txt
@@ -1,3 +1,2 @@
-Tutorials
-=========
-This page contains the python tutorials about how to use TVM to program VTA.
+VTA Tutorials
+=============
diff --git a/vta/tutorials/convolution_opt.py b/vta/tutorials/convolution_opt.py
index 62efe8c0156b..e581b0c4082f 100644
--- a/vta/tutorials/convolution_opt.py
+++ b/vta/tutorials/convolution_opt.py
@@ -5,7 +5,7 @@
 
 This tutorial provides an overview on how to use TVM to map a 2D convolution
 workload efficiently on the VTA design.
-We recommend covering the :ref:`mat-mult-opt` tutorial first.
+We recommend covering the :ref:`vta-mat-mult-opt` tutorial first.
 
 2D convolution is dominant in most computer vision deep neural networks.
 In this tutorial, we will demonstrate TVM schedule optimizations to map
@@ -26,7 +26,8 @@
 import vta
 import numpy as np
 
-from tvm.contrib import rpc, util
+from tvm import rpc
+from tvm.contrib import util
 from vta.testing import simulator
 
 # Load VTA parameters from the config.json file
@@ -423,5 +424,3 @@
 # use of hardware specific optimizations, such as latency hiding with
 # virtual threading.
 #
-
-
diff --git a/vta/tutorials/matrix_multiply.py b/vta/tutorials/matrix_multiply.py
index 0d1872170a4f..f7e9e9f66f2d 100644
--- a/vta/tutorials/matrix_multiply.py
+++ b/vta/tutorials/matrix_multiply.py
@@ -5,7 +5,7 @@
 ======================
 **Author**: `Thierry Moreau <https://homes.cs.washington.edu/~moreau/>`_
 
-In this tutorial, we will build on top of the :ref:`get-started` tutorial
+In this tutorial, we will build on top of the :ref:`vta-get-started` tutorial
 and introduce additional concepts required to implement matrix multiplication
 on VTA with the TVM workflow.
 """
@@ -22,7 +22,8 @@
 import tvm
 import vta
 import numpy as np
-from tvm.contrib import rpc, util
+from tvm import rpc
+from tvm.contrib import util
 from vta.testing import simulator
 
 # Load VTA parameters from the config.json file
@@ -107,7 +108,7 @@
 #   Tiling by a (2, 2) tile shape ensures that data within each tile is
 #   contiguous.
 #   The resulting tiled tensor has a shape of (2, 4, 2, 2).
-# 
+#
 #   .. image:: https://raw.githubusercontent.com/uwsaml/web-data/master/vta/tutorial/data_tiling.png
 #        :align: center
 #        :width: 480px
@@ -451,4 +452,3 @@
 # - Compiling the function to the VTA target.
 # - Running the compiled module and verifying it against a numpy implementation.
 #
-
diff --git a/vta/tutorials/matrix_multiply_opt.py b/vta/tutorials/matrix_multiply_opt.py
index 4c916c7cddbc..0150f1ed460b 100644
--- a/vta/tutorials/matrix_multiply_opt.py
+++ b/vta/tutorials/matrix_multiply_opt.py
@@ -1,5 +1,5 @@
 """
-.. _mat-mult-opt:
+.. _vta-mat-mult-opt:
 
 Matrix Multiply Blocking
 ========================
@@ -7,7 +7,7 @@
 
 This tutorial provides an overview on how to use TVM to map matrix
 multiplication efficiently on the VTA design.
-We recommend covering the :ref:`basic-mat-mult` tutorial first.
+We recommend covering the :ref:`vta-basic-mat-mult` tutorial first.
 
 In this tutorial, we will demonstrate TVM schedule optimizations to break large
 neural network operators down onto smaller blocks to achieve computation within
@@ -25,7 +25,8 @@
 import tvm
 import vta
 import numpy as np
-from tvm.contrib import rpc, util
+from tvm import rpc
+from tvm.contrib import util
 from vta.testing import simulator
 
 # Load VTA parameters from the config.json file
@@ -183,7 +184,7 @@
 #      :width: 480px
 #
 # .. note::
-# 
+#
 #   The code after loop splitting and reordering is equivalent to the following
 #   pseudo-code. We ignore the batch axis since we are only performing single-batch
 #   inference in this example:
@@ -359,5 +360,3 @@
 # This allows us to map arbitrarily large computation onto limited
 # hardware accelerator resources.
 #
-
-
diff --git a/vta/tutorials/resnet.py b/vta/tutorials/resnet.py
index ebe580ce184a..d33085c8dc73 100644
--- a/vta/tutorials/resnet.py
+++ b/vta/tutorials/resnet.py
@@ -29,7 +29,8 @@
 import time
 
 from nnvm.compiler import graph_attr
-from tvm.contrib import graph_runtime, rpc, util
+from tvm import rpc
+from tvm.contrib import graph_runtime, util
 from tvm.contrib.download import download
 from vta.testing import simulator
 
diff --git a/vta/tutorials/get_started.py b/vta/tutorials/vta_get_started.py
similarity index 99%
rename from vta/tutorials/get_started.py
rename to vta/tutorials/vta_get_started.py
index d3f334875469..67018372fba6 100644
--- a/vta/tutorials/get_started.py
+++ b/vta/tutorials/vta_get_started.py
@@ -1,5 +1,5 @@
 """
-.. _get-started:
+.. _vta-get-started:
 
 Get Started with VTA
 ====================
@@ -49,7 +49,8 @@
 # the board with a VTA bitstream.
 
 # We'll need the TVM RPC module and the VTA simulator module
-from tvm.contrib import rpc, util
+from tvm import rpc
+from tvm.contrib import util
 from vta.testing import simulator
 
 # We read the Pynq RPC host IP address and port number from the OS environment
@@ -384,4 +385,3 @@
 # to learn more about the supported operations, schedule primitives
 # and other features supported by TVM to program VTA.
 #
-

From c893d48fbe55dd65c2e7f0ccc9e3622a47b43540 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 12 Jul 2018 08:44:19 -0700
Subject: [PATCH 859/949] Update index.rst

---
 docs/vta/index.rst | 1 -
 1 file changed, 1 deletion(-)

diff --git a/docs/vta/index.rst b/docs/vta/index.rst
index fa20cf779065..e240fac5cdbb 100644
--- a/docs/vta/index.rst
+++ b/docs/vta/index.rst
@@ -9,7 +9,6 @@ This page contains links to all the resources related to VTA:
 
    install
    tutorials/index
-   hardware
 
 
 Features

From d8739309be62d20ead3aeb859872eb8af8adc92b Mon Sep 17 00:00:00 2001
From: Alex Weaver <awsweaver@gmail.com>
Date: Thu, 12 Jul 2018 18:36:12 +0100
Subject: [PATCH 860/949] Added option to build android rpc app with vulkan
 support

---
 .../app/src/main/jni/Application.mk           | 10 ++++++++++
 .../app/src/main/jni/make/config.mk           |  8 ++++++++
 .../app/src/main/jni/tvm_runtime.h            |  5 +++++
 src/runtime/vulkan/vulkan_common.h            | 18 ++++++++---------
 src/runtime/vulkan/vulkan_device_api.cc       | 20 +++++++++----------
 src/runtime/vulkan/vulkan_module.cc           | 14 ++++++-------
 6 files changed, 49 insertions(+), 26 deletions(-)

diff --git a/apps/android_rpc/app/src/main/jni/Application.mk b/apps/android_rpc/app/src/main/jni/Application.mk
index 8e81a8d6a81c..09ee723a10d2 100644
--- a/apps/android_rpc/app/src/main/jni/Application.mk
+++ b/apps/android_rpc/app/src/main/jni/Application.mk
@@ -8,9 +8,19 @@ endif
 
 include $(config)
 
+# We target every architecture except armeabi here, for two reasons:
+# 1) armeabi is deprecated in NDK r16 and removed in r17
+# 2) vulkan is not supported in armeabi
+APP_ABI := armeabi-v7a arm64-v8a x86 x86_64 mips
+
 APP_STL := c++_static
 
 APP_CPPFLAGS += -DDMLC_LOG_STACK_TRACE=0 -DTVM4J_ANDROID=1 -std=c++11 -Oz -frtti
 ifeq ($(USE_OPENCL), 1)                                                                                                                                             
 	APP_CPPFLAGS += -DTVM_OPENCL_RUNTIME=1
 endif
+
+ifeq ($(USE_VULKAN), 1)                                                                                                                                             
+	APP_CPPFLAGS += -DTVM_VULKAN_RUNTIME=1
+	APP_LDFLAGS += -lvulkan
+endif
diff --git a/apps/android_rpc/app/src/main/jni/make/config.mk b/apps/android_rpc/app/src/main/jni/make/config.mk
index 8d6f5a56dd5b..e57a907441b0 100644
--- a/apps/android_rpc/app/src/main/jni/make/config.mk
+++ b/apps/android_rpc/app/src/main/jni/make/config.mk
@@ -19,6 +19,14 @@ APP_PLATFORM = android-17
 # whether enable OpenCL during compile
 USE_OPENCL = 0
 
+# whether to enable Vulkan during compile
+USE_VULKAN = 0
+
+ifeq ($(USE_VULKAN), 1)
+  # Statically linking vulkan requires API Level 24 or higher
+  APP_PLATFORM = android-24
+endif
+
 # the additional include headers you want to add, e.g., SDK_PATH/adrenosdk/Development/Inc
 ADD_C_INCLUDES =
 
diff --git a/apps/android_rpc/app/src/main/jni/tvm_runtime.h b/apps/android_rpc/app/src/main/jni/tvm_runtime.h
index 0bb1ff553947..c24abbec0075 100644
--- a/apps/android_rpc/app/src/main/jni/tvm_runtime.h
+++ b/apps/android_rpc/app/src/main/jni/tvm_runtime.h
@@ -28,3 +28,8 @@
 #include "../src/runtime/opencl/opencl_device_api.cc"
 #include "../src/runtime/opencl/opencl_module.cc"
 #endif
+
+#ifdef TVM_VULKAN_RUNTIME
+#include "../src/runtime/vulkan/vulkan_device_api.cc"
+#include "../src/runtime/vulkan/vulkan_module.cc"
+#endif
diff --git a/src/runtime/vulkan/vulkan_common.h b/src/runtime/vulkan/vulkan_common.h
index 9dcd5f429b68..b1770fd3b243 100644
--- a/src/runtime/vulkan/vulkan_common.h
+++ b/src/runtime/vulkan/vulkan_common.h
@@ -86,9 +86,9 @@ struct VulkanContext {
 /*! \brief The buffer object */
 struct VulkanBuffer {
   /*! \brief underlying buffer */
-  VkBuffer buffer{nullptr};
+  VkBuffer buffer{VK_NULL_HANDLE};
   /*! \brief underlying buffer */
-  VkDeviceMemory memory{nullptr};
+  VkDeviceMemory memory{VK_NULL_HANDLE};
 };
 
 /*! \brief Buffer only used for stagging */
@@ -96,9 +96,9 @@ struct VulkanStagingBuffer {
   /*! \brief the corresponding device */
   VkDevice device{nullptr};
   /*! \brief underlying buffer */
-  VkBuffer buffer{nullptr};
+  VkBuffer buffer{VK_NULL_HANDLE};
   /*! \brief underlying buffer */
-  VkDeviceMemory memory{nullptr};
+  VkDeviceMemory memory{VK_NULL_HANDLE};
   /*! \brief host address */
   void* host_addr{nullptr};
   /*! \brief size of the memory */
@@ -150,18 +150,18 @@ class VulkanWorkspace final : public DeviceAPI {
 /*! \brief Helper command buffer resource */
 struct VulkanCommandBuffer {
   /*! \brief fence to signal the resource is ready to use */
-  VkFence fence{nullptr};
+  VkFence fence{VK_NULL_HANDLE};
   /*! \brief The internal command buffer */
   VkCommandBuffer cmd_buffer{nullptr};
   /*! \brief Descriptor set used to bind arguments */
-  VkDescriptorSet descriptor_set{nullptr};
+  VkDescriptorSet descriptor_set{VK_NULL_HANDLE};
   /*! \brief Internal utilities for write command */
   VkWriteDescriptorSet write_descriptor_set;
 
   VulkanCommandBuffer() {
     write_descriptor_set.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
     write_descriptor_set.pNext = nullptr;
-    write_descriptor_set.dstSet = nullptr;
+    write_descriptor_set.dstSet = VK_NULL_HANDLE;
     write_descriptor_set.dstBinding = 0;
     write_descriptor_set.dstArrayElement = 0;
     write_descriptor_set.descriptorCount = 1;
@@ -230,9 +230,9 @@ class VulkanCommandPool {
   /*! \brief the corresponding device*/
   VkDevice device_{nullptr};
   /*! \brief internal command buffer pool */
-  VkCommandPool cmd_pool_{nullptr};
+  VkCommandPool cmd_pool_{VK_NULL_HANDLE};
   /*! \brief Descriptor pool */
-  VkDescriptorPool descriptor_pool_{nullptr};
+  VkDescriptorPool descriptor_pool_{VK_NULL_HANDLE};
 };
 
 /*! \brief Thread local workspace */
diff --git a/src/runtime/vulkan/vulkan_device_api.cc b/src/runtime/vulkan/vulkan_device_api.cc
index d0f7189d982a..45f8549d54f2 100644
--- a/src/runtime/vulkan/vulkan_device_api.cc
+++ b/src/runtime/vulkan/vulkan_device_api.cc
@@ -349,7 +349,7 @@ VulkanCommandPool::~VulkanCommandPool() {
       vkFreeCommandBuffers(device_, cmd_pool_, 1, &(ring_[i].cmd_buffer));
       ring_[i].cmd_buffer = nullptr;
     }
-    if (ring_[i].fence != nullptr) {
+    if (ring_[i].fence != VK_NULL_HANDLE) {
       vkDestroyFence(device_, ring_[i].fence, nullptr);
     }
   }
@@ -376,10 +376,10 @@ VulkanCommandBuffer* VulkanCommandPool::Alloc(
   }
   VULKAN_CHECK_ERROR(res);
   vkResetFences(device_, 1, (&e->fence));
-  if (e->descriptor_set != nullptr) {
+  if (e->descriptor_set != VK_NULL_HANDLE) {
     VULKAN_CALL(vkFreeDescriptorSets(
         device_, descriptor_pool_, 1, &(e->descriptor_set)));
-    e->descriptor_set = nullptr;
+    e->descriptor_set = VK_NULL_HANDLE;
   }
   if (dlayout != nullptr) {
     VkDescriptorSetAllocateInfo alloc_info;
@@ -425,15 +425,15 @@ VulkanThreadEntry::StagingBuffer(int device_id, size_t size) {
     if (buf.host_addr != nullptr) {
       vkUnmapMemory(buf.device, buf.memory);
     }
-    if (buf.memory != nullptr) {
+    if (buf.memory != VK_NULL_HANDLE) {
       vkFreeMemory(buf.device, buf.memory, nullptr);
     }
-    if (buf.buffer != nullptr) {
+    if (buf.buffer != VK_NULL_HANDLE) {
       vkDestroyBuffer(buf.device, buf.buffer, nullptr);
     }
     buf.host_addr = nullptr;
-    buf.memory = nullptr;
-    buf.buffer = nullptr;
+    buf.memory = VK_NULL_HANDLE;
+    buf.buffer = VK_NULL_HANDLE;
   }
   const VulkanContext& vctx =
       VulkanWorkspace::Global()->context_[device_id];
@@ -441,7 +441,7 @@ VulkanThreadEntry::StagingBuffer(int device_id, size_t size) {
   if (buf.device == nullptr) {
     buf.device = vctx.device;
   }
-  if (buf.memory == nullptr) {
+  if (buf.memory == VK_NULL_HANDLE) {
     // allocate the stagging buffer memory if necessary
     VkBufferCreateInfo info;
     info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
@@ -479,10 +479,10 @@ VulkanThreadEntry::~VulkanThreadEntry() {
     if (buf.host_addr != nullptr) {
       vkUnmapMemory(buf.device, buf.memory);
     }
-    if (buf.memory != nullptr) {
+    if (buf.memory != VK_NULL_HANDLE) {
       vkFreeMemory(buf.device, buf.memory, nullptr);
     }
-    if (buf.buffer != nullptr) {
+    if (buf.buffer != VK_NULL_HANDLE) {
       vkDestroyBuffer(buf.device, buf.buffer, nullptr);
     }
   }
diff --git a/src/runtime/vulkan/vulkan_module.cc b/src/runtime/vulkan/vulkan_module.cc
index 56b4c79bcedf..b5425dd8fbc5 100644
--- a/src/runtime/vulkan/vulkan_module.cc
+++ b/src/runtime/vulkan/vulkan_module.cc
@@ -35,10 +35,10 @@ class VulkanModuleNode final :public runtime::ModuleNode {
  public:
   // Pipeline cache states
   struct PipelineEntry {
-    VkShaderModule shader{nullptr};
-    VkPipelineLayout pipeline_layout{nullptr};
-    VkDescriptorSetLayout descriptor_layout{nullptr};
-    VkPipeline pipeline{nullptr};
+    VkShaderModule shader{VK_NULL_HANDLE};
+    VkPipelineLayout pipeline_layout{VK_NULL_HANDLE};
+    VkDescriptorSetLayout descriptor_layout{VK_NULL_HANDLE};
+    VkPipeline pipeline{VK_NULL_HANDLE};
   };
   // constructor
   explicit VulkanModuleNode(std::unordered_map<std::string, VulkanShader> smap,
@@ -193,10 +193,10 @@ class VulkanModuleNode final :public runtime::ModuleNode {
     pipeline_cinfo.stage.pName = func_name.c_str();
     pipeline_cinfo.stage.pSpecializationInfo = nullptr;
     pipeline_cinfo.layout = pe.pipeline_layout;
-    pipeline_cinfo.basePipelineHandle = nullptr;
+    pipeline_cinfo.basePipelineHandle = VK_NULL_HANDLE;
     pipeline_cinfo.basePipelineIndex = 0;
     VULKAN_CALL(vkCreateComputePipelines(
-        e.device, nullptr, 1, &pipeline_cinfo, nullptr, &(pe.pipeline)));
+        e.device, VK_NULL_HANDLE, 1, &pipeline_cinfo, nullptr, &(pe.pipeline)));
     e.smap[func_name] = pe;
     return pe;
   }
@@ -250,7 +250,7 @@ class VulkanWrappedFunc {
     CHECK_LT(device_id, kVulkanMaxNumDevice);
     const vulkan::VulkanContext& vctx = w_->context_[device_id];
     VulkanModuleNode::PipelineEntry& pe = scache_[device_id];
-    if (pe.pipeline == nullptr) {
+    if (pe.pipeline == VK_NULL_HANDLE) {
       pe = m_->GetPipeline(device_id, func_name_, num_pack_args_);
     }
     ThreadWorkLoad wl = thread_axis_cfg_.Extract(args);

From dec8d38d3056740e2ce10b8b43802104492aa63f Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@uw.edu>
Date: Thu, 12 Jul 2018 15:04:53 -0700
Subject: [PATCH 861/949] fixed path in installation/setup guide

---
 docs/vta/install.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/vta/install.md b/docs/vta/install.md
index f05a0ccad3ed..11116f730e7f 100644
--- a/docs/vta/install.md
+++ b/docs/vta/install.md
@@ -90,7 +90,7 @@ The build process should take roughly 5 minutes.
 ```bash
 ssh xilinx@192.168.2.99
 # Build TVM runtime library (takes 5 mins)
-cd /home/xilinx/tvm/vta
+cd /home/xilinx/tvm
 mkdir build
 cp cmake/config.cmake build/.
 # copy pynq specific configuration

From 5e3fcf1bf2d3d854d3af5d62e98618bbf535842d Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@uw.edu>
Date: Thu, 12 Jul 2018 15:06:47 -0700
Subject: [PATCH 862/949] minor style edit

---
 docs/vta/install.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/docs/vta/install.md b/docs/vta/install.md
index 11116f730e7f..6c2023afe2fc 100644
--- a/docs/vta/install.md
+++ b/docs/vta/install.md
@@ -8,9 +8,9 @@ We present three installation guides, each extending on the previous one:
 
 ## VTA Simulation-Only Installation
 
-Please follow the guide on install TVM from source.
-VTA simulator is library is built by default along with TVM.
-You only have to add vta to your python path.
+You need [TVM installed](https://docs.tvm.ai/install/index.html) on your machine.
+VTA simulator is library will be built by default along with TVM.
+All you need to run the simulator is to add the vta library to your python path.
 
 ```bash
 export PYTHONPATH=/path/to/vta/python:${PYTHONPATH}

From 95a97f303f2e178d23724b311b0c75ffd34bfbad Mon Sep 17 00:00:00 2001
From: eqy <eqy@cs.washington.edu>
Date: Thu, 12 Jul 2018 16:53:34 -0700
Subject: [PATCH 863/949] [RUNTIME] Support setting CPU affinity (#1403)

---
 include/tvm/runtime/threading_backend.h      |  20 ++++
 src/runtime/sgx/trusted/threading_backend.cc |   7 ++
 src/runtime/thread_pool.cc                   |  37 ++++++-
 src/runtime/threading_backend.cc             | 107 ++++++++++++++++---
 4 files changed, 155 insertions(+), 16 deletions(-)

diff --git a/include/tvm/runtime/threading_backend.h b/include/tvm/runtime/threading_backend.h
index 66ef8612febe..e2da0a3c4446 100644
--- a/include/tvm/runtime/threading_backend.h
+++ b/include/tvm/runtime/threading_backend.h
@@ -44,6 +44,25 @@ class ThreadGroup {
     */
   void Join();
 
+  enum AffinityMode : int {
+    kBig = 1,
+    kLittle = -1,
+  };
+
+  /*!
+   * \brief configure the CPU id affinity
+   *
+   * \param mode The preferred CPU type (1 = big, -1 = little).
+   * \param nthreads The number of threads to use (0 = use all).
+   * \param exclude_worker0 Whether to use the main thread as a worker.
+   *        If  `true`, worker0 will not be launched in a new thread and
+   *        `worker_callback` will only be called for values >= 1. This
+   *        allows use of the main thread as a worker.
+   *
+   * \return The number of workers to use.
+   */
+  int Configure(AffinityMode mode, int nthreads, bool exclude_worker0);
+
  private:
   Impl* impl_;
 };
@@ -58,6 +77,7 @@ void Yield();
  */
 int MaxConcurrency();
 
+
 }  // namespace threading
 }  // namespace runtime
 }  // namespace tvm
diff --git a/src/runtime/sgx/trusted/threading_backend.cc b/src/runtime/sgx/trusted/threading_backend.cc
index 662e824f5078..fd9112c20228 100644
--- a/src/runtime/sgx/trusted/threading_backend.cc
+++ b/src/runtime/sgx/trusted/threading_backend.cc
@@ -53,6 +53,13 @@ ThreadGroup::ThreadGroup(int num_workers,
                          bool exclude_worker0)
   : impl_(new ThreadGroup::Impl(num_workers, worker_callback, exclude_worker0)) {}
 void ThreadGroup::Join() {}
+int ThreadGroup::Configure(AffinityMode mode, int nthreads, bool exclude_worker0) {
+  int max_conc = MaxConcurrency();
+  if (!nthreads || ntheads > max_conc) {
+    return max_conc;
+  }
+  return nthreads;
+}
 ThreadGroup::~ThreadGroup() { delete impl_; }
 
 void Yield() {}
diff --git a/src/runtime/thread_pool.cc b/src/runtime/thread_pool.cc
index 5e5c08509a44..5e7cf779c88f 100644
--- a/src/runtime/thread_pool.cc
+++ b/src/runtime/thread_pool.cc
@@ -5,6 +5,8 @@
  */
 #include <tvm/runtime/c_runtime_api.h>
 #include <tvm/runtime/c_backend_api.h>
+#include <tvm/runtime/registry.h>
+#include <tvm/runtime/packed_func.h>
 #include <tvm/runtime/threading_backend.h>
 #include <dmlc/thread_local.h>
 #include <dmlc/logging.h>
@@ -250,6 +252,10 @@ class ThreadPool {
         new tvm::runtime::threading::ThreadGroup(
           num_workers_, [this](int worker_id) { this->RunWorker(worker_id); },
           exclude_worker0_ /* include_main_thread */));
+    num_workers_used_ = threads_->Configure(threading::ThreadGroup::kBig, 0, exclude_worker0_);
+    // if MaxConcurrency restricted the number of workers (e.g., due to
+    // hyperthreading), respect the restriction
+    num_workers_used_ = std::min(num_workers_, num_workers_used_);
   }
   ~ThreadPool() {
     for (std::unique_ptr<SpscTaskQueue>& q : queues_) {
@@ -265,12 +271,12 @@ class ThreadPool {
     CHECK(!launcher->is_worker)
         << "Cannot launch parallel job inside worker, consider fuse then parallel";
     if (num_task == 0) {
-      num_task = num_workers_;
+      num_task = num_workers_used_;
     }
     if (need_sync != 0) {
-      CHECK_LE(num_task, num_workers_)
-          << "Request parallel sync task larger than number of threads available "
-          << " workers=" << num_workers_ << " request=" << num_task;
+      CHECK_LE(num_task, num_workers_used_)
+          << "Request parallel sync task larger than number of threads used "
+          << " workers=" << num_workers_used_ << " request=" << num_task;
     }
     launcher->Init(flambda, cdata, num_task, need_sync != 0);
     SpscTaskQueue::Task tsk;
@@ -297,6 +303,16 @@ class ThreadPool {
     return dmlc::ThreadLocalStore<ThreadPool>::Get();
   }
 
+  void UpdateWorkerConfiguration(threading::ThreadGroup::AffinityMode mode, int nthreads) {
+    // this will also reset the affinity of the ThreadGroup
+    // may use less than the MaxConcurrency number of workers
+    num_workers_used_ = threads_->Configure(mode, nthreads,
+                                            exclude_worker0_);
+    // if MaxConcurrency restricted the number of workers (e.g., due to
+    // hyperthreading), respect the restriction
+    num_workers_used_ = std::min(num_workers_, num_workers_used_);
+  }
+
  private:
   // Internal worker function.
   void RunWorker(int worker_id) {
@@ -315,6 +331,8 @@ class ThreadPool {
     }
   }
   int num_workers_;
+  // number of workers used (can be restricted with affinity pref)
+  int num_workers_used_;
   // if excluding worker 0 and using master to run task 0
 #ifndef _LIBCPP_SGX_CONFIG
   bool exclude_worker0_{true};
@@ -325,9 +343,20 @@ class ThreadPool {
   std::unique_ptr<tvm::runtime::threading::ThreadGroup> threads_;
 };
 
+TVM_REGISTER_GLOBAL("runtime.config_threadpool")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    threading::ThreadGroup::AffinityMode mode =\
+    static_cast<threading::ThreadGroup::AffinityMode>(\
+    static_cast<int>(args[0]));
+    int nthreads = args[1];
+    ThreadPool::ThreadLocal()->UpdateWorkerConfiguration(mode, nthreads);
+});
+
+
 }  // namespace runtime
 }  // namespace tvm
 
+
 int TVMBackendParallelLaunch(
     FTVMParallelLambda flambda,
     void* cdata,
diff --git a/src/runtime/threading_backend.cc b/src/runtime/threading_backend.cc
index aa15c229cfca..58d5fb4530dd 100644
--- a/src/runtime/threading_backend.cc
+++ b/src/runtime/threading_backend.cc
@@ -7,6 +7,10 @@
 #include <dmlc/logging.h>
 #include <thread>
 #include <algorithm>
+#if defined(__linux__) || defined(__ANDROID__)
+#include <fstream>
+#else
+#endif
 #if defined(__linux__)
 #include <sched.h>
 #endif
@@ -26,30 +30,49 @@ class ThreadGroup::Impl {
     for (int i = exclude_worker0; i < num_workers_; ++i) {
       threads_.emplace_back([worker_callback, i] { worker_callback(i); });
     }
+    InitSortedOrder();
+  }
+  ~Impl() { Join(); }
+
+  void Join() {
+    for (auto& t : threads_) {
+      if (t.joinable()) t.join();
+    }
+  }
+
+  int Configure(AffinityMode mode, int nthreads, bool exclude_worker0) {
+    int num_workers_used = 0;
+    if (mode == kLittle) {
+      num_workers_used = little_count_;
+    } else if (mode == kBig) {
+      num_workers_used = big_count_;
+    } else {
+      // use default
+      num_workers_used = threading::MaxConcurrency();
+    }
+    // if a specific number was given, use that
+    if (nthreads) {
+      num_workers_used = nthreads;
+    }
     const char *val = getenv("TVM_BIND_THREADS");
     if (val == nullptr || atoi(val) == 1) {
-      if (static_cast<size_t>(num_workers_) <= std::thread::hardware_concurrency()) {
-        SetAffinity(exclude_worker0);
+      // Skip if sorted_order.size() is bigger than the number of workers (threads_)
+      if (!(sorted_order_.size() > static_cast<unsigned int>(num_workers_))) {
+          SetAffinity(exclude_worker0, mode == kLittle);
       } else {
         LOG(WARNING)
           << "The thread affinity cannot be set when the number of workers"
           << "is larger than the number of available cores in the system.";
       }
     }
-  }
-  ~Impl() { Join(); }
-
-  void Join() {
-    for (auto& t : threads_) {
-      if (t.joinable()) t.join();
-    }
+    return num_workers_used;
   }
 
  private:
   // bind worker threads to disjoint cores
   // if worker 0 is offloaded to master, i.e. exclude_worker0 is true,
   // the master thread is bound to core 0.
-  void SetAffinity(bool exclude_worker0) {
+  void SetAffinity(bool exclude_worker0, bool reverse = false) {
 #if defined(__ANDROID__)
 #ifndef CPU_SET
 #define CPU_SETSIZE 1024
@@ -65,8 +88,15 @@ class ThreadGroup::Impl {
 #endif
 #endif
 #if defined(__linux__) || defined(__ANDROID__)
+    CHECK_GE(sorted_order_.size(), num_workers_);
+
     for (unsigned i = 0; i < threads_.size(); ++i) {
-      unsigned core_id = i + exclude_worker0;
+      unsigned core_id;
+      if (reverse) {
+        core_id = sorted_order_[sorted_order_.size() - (i + exclude_worker0) - 1];
+      } else {
+        core_id = sorted_order_[i + exclude_worker0];
+      }
       cpu_set_t cpuset;
       CPU_ZERO(&cpuset);
       CPU_SET(core_id, &cpuset);
@@ -80,7 +110,11 @@ class ThreadGroup::Impl {
     if (exclude_worker0) {  // bind the master thread to core 0
       cpu_set_t cpuset;
       CPU_ZERO(&cpuset);
-      CPU_SET(0, &cpuset);
+      if (reverse) {
+        CPU_SET(sorted_order_[sorted_order_.size() - 1], &cpuset);
+      } else {
+        CPU_SET(sorted_order_[0], &cpuset);
+      }
 #if defined(__ANDROID__)
       sched_setaffinity(pthread_self(),
         sizeof(cpu_set_t), &cpuset);
@@ -92,8 +126,52 @@ class ThreadGroup::Impl {
 #endif
   }
 
+  void InitSortedOrder() {
+    unsigned int threads = std::thread::hardware_concurrency();
+    std::vector<std::pair <unsigned int, int64_t> > max_freqs;
+
+    for (unsigned int i = 0; i < threads; ++i) {
+      int64_t cur_freq = 0;
+      #if defined(__linux__) || defined(__ANDROID__)
+        std::ostringstream filepath;
+        filepath << "/sys/devices/system/cpu/cpu"  << i << "/cpufreq/cpuinfo_max_freq";
+        std::ifstream ifs(filepath.str());
+        if (!ifs.fail()) {
+          if (!(ifs >> cur_freq)) {
+            cur_freq = -1;
+          }
+          ifs.close();
+        }
+      #endif
+      max_freqs.push_back(std::make_pair(i, cur_freq));
+    }
+
+    auto fcmpbyfreq = [] (const std::pair<unsigned int, int64_t> &a,
+                          const std::pair<unsigned int, int64_t> &b) {
+        return a.second == b.second ? a.first < b.first : a.second > b.second;
+    };
+    std::sort(max_freqs.begin(), max_freqs.end(), fcmpbyfreq);
+    int64_t big_freq = max_freqs.begin()->second;
+    int64_t little_freq = max_freqs.rbegin()->second;
+    for (auto it = max_freqs.begin(); it != max_freqs.end(); it++) {
+      sorted_order_.push_back(it->first);
+      if (big_freq == it->second) {
+        big_count_++;
+      }
+      if (big_freq != little_freq && little_freq == it->second) {
+        little_count_++;
+      }
+    }
+    if (big_count_ + little_count_ != static_cast<int>(sorted_order_.size())) {
+      LOG(WARNING) << "more than two frequencies detected!";
+    }
+  }
+
   int num_workers_;
   std::vector<std::thread> threads_;
+  std::vector<unsigned int> sorted_order_;
+  int big_count_ = 0;
+  int little_count_ = 0;
 };
 
 ThreadGroup::ThreadGroup(int num_workers,
@@ -103,6 +181,10 @@ ThreadGroup::ThreadGroup(int num_workers,
 ThreadGroup::~ThreadGroup() { delete impl_; }
 void ThreadGroup::Join() { impl_->Join(); }
 
+int ThreadGroup::Configure(AffinityMode mode, int nthreads, bool exclude_worker0) {
+  return impl_->Configure(mode, nthreads, exclude_worker0);
+}
+
 void Yield() {
   std::this_thread::yield();
 }
@@ -124,6 +206,7 @@ int MaxConcurrency() {
   return std::max(max_concurrency, 1);
 }
 
+
 }  // namespace threading
 }  // namespace runtime
 }  // namespace tvm

From 5980b5df5d4e9670b89a6e0648a07336f1757fb3 Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Thu, 12 Jul 2018 16:54:15 -0700
Subject: [PATCH 864/949] [AUTOTVM] Core part of auto-tuning module (#1312)

---
 CMakeLists.txt                                |   1 +
 docs/api/python/autotvm.rst                   |  61 ++
 docs/api/python/index.rst                     |   1 +
 docs/conf.py                                  |   1 +
 include/tvm/ir_pass.h                         |   2 +-
 python/tvm/autotvm/__init__.py                |  26 +
 python/tvm/autotvm/database.py                | 181 ++++
 python/tvm/autotvm/env.py                     |  12 +
 python/tvm/autotvm/feature.py                 | 181 ++++
 python/tvm/autotvm/measure/__init__.py        |   8 +
 python/tvm/autotvm/measure/executor.py        |  83 ++
 python/tvm/autotvm/measure/local_executor.py  | 131 +++
 python/tvm/autotvm/measure/measure.py         | 338 +++++++
 python/tvm/autotvm/measure/measure_methods.py | 296 ++++++
 python/tvm/autotvm/record.py                  | 332 +++++++
 python/tvm/autotvm/task/__init__.py           |  12 +
 python/tvm/autotvm/task/code_hash.py          |  43 +
 python/tvm/autotvm/task/dispatcher.py         | 139 +++
 python/tvm/autotvm/task/space.py              | 886 ++++++++++++++++++
 python/tvm/autotvm/task/task.py               | 359 +++++++
 python/tvm/autotvm/tuner/__init__.py          |  14 +
 python/tvm/autotvm/tuner/callback.py          | 112 +++
 python/tvm/autotvm/tuner/cost_model.py        |   0
 python/tvm/autotvm/tuner/ga_tuner.py          | 119 +++
 python/tvm/autotvm/tuner/gridsearch_tuner.py  |  63 ++
 python/tvm/autotvm/tuner/metric.py            | 106 +++
 python/tvm/autotvm/tuner/model_based_tuner.py | 343 +++++++
 .../tvm/autotvm/tuner/sa_model_optimizer.py   | 148 +++
 python/tvm/autotvm/tuner/tuner.py             | 138 +++
 .../tvm/autotvm/tuner/xgboost_cost_model.py   | 482 ++++++++++
 python/tvm/autotvm/tuner/xgboost_tuner.py     |  59 ++
 python/tvm/autotvm/util.py                    | 149 +++
 python/tvm/rpc/client.py                      |  10 +-
 src/autotvm/feature_visitor.cc                |  93 ++
 src/autotvm/feature_visitor.h                 |  67 ++
 src/autotvm/touch_extractor.cc                | 510 ++++++++++
 src/autotvm/touch_extractor.h                 | 124 +++
 src/codegen/build_module.cc                   |   8 +-
 src/pass/verify_gpu_code.cc                   |  48 +-
 tests/python/integration/test_tuning.py       | 158 ++++
 tests/python/unittest/test_autotvm_common.py  |  50 +
 .../python/unittest/test_autotvm_database.py  | 234 +++++
 .../unittest/test_autotvm_dispatch_context.py |  36 +
 .../python/unittest/test_autotvm_executor.py  |  47 +
 tests/python/unittest/test_autotvm_feature.py |  99 ++
 .../unittest/test_autotvm_flop_calculator.py  |  77 ++
 tests/python/unittest/test_autotvm_record.py  |  65 ++
 tests/python/unittest/test_autotvm_space.py   |  30 +
 .../unittest/test_pass_verify_gpu_code.py     |  20 +-
 tests/scripts/task_python_docs.sh             |   2 +-
 tests/scripts/task_python_integration.sh      |   2 +-
 tests/scripts/task_python_unittest.sh         |   2 +-
 tutorials/autotvm/README.txt                  |   3 +
 tutorials/autotvm/tune_cuda_conv2d.py         | 160 ++++
 tutorials/autotvm/tune_simple_template.py     | 284 ++++++
 55 files changed, 6913 insertions(+), 42 deletions(-)
 create mode 100644 docs/api/python/autotvm.rst
 create mode 100644 python/tvm/autotvm/__init__.py
 create mode 100644 python/tvm/autotvm/database.py
 create mode 100644 python/tvm/autotvm/env.py
 create mode 100644 python/tvm/autotvm/feature.py
 create mode 100644 python/tvm/autotvm/measure/__init__.py
 create mode 100644 python/tvm/autotvm/measure/executor.py
 create mode 100644 python/tvm/autotvm/measure/local_executor.py
 create mode 100644 python/tvm/autotvm/measure/measure.py
 create mode 100644 python/tvm/autotvm/measure/measure_methods.py
 create mode 100644 python/tvm/autotvm/record.py
 create mode 100644 python/tvm/autotvm/task/__init__.py
 create mode 100644 python/tvm/autotvm/task/code_hash.py
 create mode 100644 python/tvm/autotvm/task/dispatcher.py
 create mode 100644 python/tvm/autotvm/task/space.py
 create mode 100644 python/tvm/autotvm/task/task.py
 create mode 100644 python/tvm/autotvm/tuner/__init__.py
 create mode 100644 python/tvm/autotvm/tuner/callback.py
 create mode 100644 python/tvm/autotvm/tuner/cost_model.py
 create mode 100644 python/tvm/autotvm/tuner/ga_tuner.py
 create mode 100644 python/tvm/autotvm/tuner/gridsearch_tuner.py
 create mode 100644 python/tvm/autotvm/tuner/metric.py
 create mode 100644 python/tvm/autotvm/tuner/model_based_tuner.py
 create mode 100644 python/tvm/autotvm/tuner/sa_model_optimizer.py
 create mode 100644 python/tvm/autotvm/tuner/tuner.py
 create mode 100644 python/tvm/autotvm/tuner/xgboost_cost_model.py
 create mode 100644 python/tvm/autotvm/tuner/xgboost_tuner.py
 create mode 100644 python/tvm/autotvm/util.py
 create mode 100644 src/autotvm/feature_visitor.cc
 create mode 100644 src/autotvm/feature_visitor.h
 create mode 100644 src/autotvm/touch_extractor.cc
 create mode 100644 src/autotvm/touch_extractor.h
 create mode 100644 tests/python/integration/test_tuning.py
 create mode 100644 tests/python/unittest/test_autotvm_common.py
 create mode 100644 tests/python/unittest/test_autotvm_database.py
 create mode 100644 tests/python/unittest/test_autotvm_dispatch_context.py
 create mode 100644 tests/python/unittest/test_autotvm_executor.py
 create mode 100644 tests/python/unittest/test_autotvm_feature.py
 create mode 100644 tests/python/unittest/test_autotvm_flop_calculator.py
 create mode 100644 tests/python/unittest/test_autotvm_record.py
 create mode 100644 tests/python/unittest/test_autotvm_space.py
 create mode 100644 tutorials/autotvm/README.txt
 create mode 100644 tutorials/autotvm/tune_cuda_conv2d.py
 create mode 100644 tutorials/autotvm/tune_simple_template.py

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 889b285c02a2..b43d37696b37 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -96,6 +96,7 @@ assign_source_group("Include" ${GROUP_INCLUDE})
 file(GLOB COMPILER_SRCS
     src/api/*.cc
     src/arithmetic/*.cc
+    src/autotvm/*.cc
     src/codegen/*.cc
     src/codegen/stack_vm/*.cc
     src/lang/*.cc
diff --git a/docs/api/python/autotvm.rst b/docs/api/python/autotvm.rst
new file mode 100644
index 000000000000..e1f4906af0e7
--- /dev/null
+++ b/docs/api/python/autotvm.rst
@@ -0,0 +1,61 @@
+tvm.autotvm
+-----------
+.. automodule:: tvm.autotvm
+
+tvm.autotvm.measure
+~~~~~~~~~~~~~~~~~~~
+.. automodule:: tvm.autotvm.measure.measure
+
+.. autoclass:: tvm.autotvm.measure.MeasureInput
+    :members:
+
+.. autoclass:: tvm.autotvm.measure.MeasureResult
+    :members:
+
+.. autofunction:: tvm.autotvm.measure.measure_option
+
+.. autofunction:: tvm.autotvm.measure.create_measure_batch
+
+
+tvm.autotvm.tuner
+~~~~~~~~~~~~~~~~~
+.. automodule:: tvm.autotvm.tuner
+    :members:
+
+.. autoclass:: tvm.autotvm.tuner.Tuner
+    :members:
+
+.. autoclass:: tvm.autotvm.tuner.RandomTuner
+    :members:
+    :inherited-members:
+
+.. autoclass:: tvm.autotvm.tuner.GridSearchTuner
+    :members:
+    :inherited-members:
+
+.. autoclass:: tvm.autotvm.tuner.GATuner
+    :members:
+    :inherited-members:
+
+.. autoclass:: tvm.autotvm.tuner.XGBTuner
+    :members:
+    :inherited-members:
+
+.. automodule:: tvm.autotvm.tuner.callback
+    :members:
+
+tvm.autotvm.task
+~~~~~~~~~~~~~~~~
+.. automodule:: tvm.autotvm.task
+    :members:
+
+.. automodule:: tvm.autotvm.task.task
+    :members:
+
+.. automodule:: tvm.autotvm.task.space
+    :members:
+
+tvm.autotvm.record
+~~~~~~~~~~~~~~~~~~
+.. automodule:: tvm.autotvm.record
+    :members:
diff --git a/docs/api/python/index.rst b/docs/api/python/index.rst
index ea304ecbf0a8..59bd1795b7ec 100644
--- a/docs/api/python/index.rst
+++ b/docs/api/python/index.rst
@@ -14,6 +14,7 @@ Python API
    ndarray
    container
    function
+   autotvm
    graph_runtime
    rpc
    bridge
diff --git a/docs/conf.py b/docs/conf.py
index f13721aa3d62..989d26f87d3e 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -191,6 +191,7 @@ def run_doxygen(folder):
 subsection_order = ExplicitOrder(
     ['../tutorials/language',
      '../tutorials/optimize',
+     '../tutorials/autotvm',
      '../tutorials/vta',
      '../tutorials/topi',
      '../tutorials/deployment',
diff --git a/include/tvm/ir_pass.h b/include/tvm/ir_pass.h
index f6c6334c88b6..d875621a3f5e 100644
--- a/include/tvm/ir_pass.h
+++ b/include/tvm/ir_pass.h
@@ -488,7 +488,7 @@ bool VerifyMemory(LoweredFunc func, int device_type);
  *
  *        "max_local_memory_per_block": Total amount of local memory per block (in bytes).
  *        "max_shared_memory_per_block": Total amount of shared memory per block (in bytes).
- *        "max_thread_per_block": Maximum number of threads per block.
+ *        "max_threads_per_block": Maximum number of threads per block.
  *        "max_thread_x": Maximum length of threadIdx.x.
  *        "max_thread_y": Maximum length of threadIdx.y.
  *        "max_thread_z": Maximum length of threadIdx.z.
diff --git a/python/tvm/autotvm/__init__.py b/python/tvm/autotvm/__init__.py
new file mode 100644
index 000000000000..45d053ef96f7
--- /dev/null
+++ b/python/tvm/autotvm/__init__.py
@@ -0,0 +1,26 @@
+"""The auto-tuning module of tvm
+
+This module includes:
+
+* Tuning space definition API
+
+* Efficient auto-tuners
+
+* Tuning result and database support
+
+* Distributed measurement to scale up tuning
+"""
+
+from . import database
+from . import feature
+from . import measure
+from . import record
+from . import task
+from . import tuner
+from . import util
+
+# some shortcuts
+from .measure import measure_option, MeasureInput, MeasureResult, MeasureErrorNo
+from .tuner import callback
+from .task import template, get_config, create, ConfigSpace, ConfigEntity
+from .record import ApplyHistoryBest as apply_history_best
diff --git a/python/tvm/autotvm/database.py b/python/tvm/autotvm/database.py
new file mode 100644
index 000000000000..c0c041cb18c9
--- /dev/null
+++ b/python/tvm/autotvm/database.py
@@ -0,0 +1,181 @@
+# pylint: disable=consider-using-enumerate,invalid-name
+"""
+Database of MeasureInput/MeasureResult pair.
+This can be used for replaying measurement.
+"""
+import os
+
+from .record import encode, decode, measure_str_key
+
+
+class Database(object):
+    """
+    Base class for a record database object.
+    """
+    def load(self, inp, get_all=False):
+        """
+        Load a result based on an input's string key
+
+        Parameters
+        ----------
+        inp: MeasureInput
+            to be translated into key for RedisDB
+        get_all: bool, optional
+            Whether the latest result (or all matching results) should be returned
+
+        Returns
+        -------
+        rec: MeasureResult if previously saved, otherwise None
+        """
+        raise NotImplementedError()
+
+    def save(self, inp, res, extend=False):
+        """
+        Save a result based on an input's string key
+
+        Parameters
+        ----------
+        inp: MeasureInput
+            to be translated into key for RedisDB
+        res: MeasureResult
+            to associate with key
+        extend:
+            Whether to extend existing MeasureResults if they exist
+        """
+        raise NotImplementedError()
+
+
+def filter_inputs(db, measure_inputs, retry=False):
+    """
+    Filter a measure_inputs batch based on saved db results
+
+    Parameters
+    ----------
+    db: Database
+        database object
+    measure_inputs: Array of MeasureInput
+        measure_inputs as expected in measure_batch
+    retry: bool
+        whether to retry if the saved result is a failure
+
+    Returns
+    -------
+    partial_results: Array of MeasureResult
+        a full list of result, where None denotes no corresponding saved result
+    unsaved: Array of MeasureInput
+        a list that only contains unsaved inputs
+    """
+    partial_results = list()
+    unsaved = list()
+    for inp in measure_inputs:
+        res = db.load(inp)
+        if res is None or (retry and res.error_no != 0):
+            unsaved.append(inp)
+            partial_results.append(None)
+        else:
+            partial_results.append(res)
+    return partial_results, unsaved
+
+class RedisDatabase(Database):
+    """
+    Redis version of record database
+    """
+    REDIS_PROD = 15
+    REDIS_LOCA = 14
+    REDIS_TEST = 13        # for unit test
+    REDIS_NIGHT_TEMP = 12  # for nightly report (will be flushed after every workload)
+
+    MAGIC_SPLIT = "$"
+
+    def __init__(self, db_index=REDIS_PROD):
+        import redis
+
+        if db_index == RedisDatabase.REDIS_TEST:
+            host = 'localhost'
+        else:
+            host = os.environ.get('TVM_FLEET_HOST')
+        self.db = redis.StrictRedis(host=host, port=6379, db=db_index)
+        self.db_index = db_index
+
+    def set(self, key, value):
+        self.db.set(key, value)
+
+    def get(self, key):
+        return self.db.get(key)
+
+    def load(self, inp, get_all=False):
+        current = self.get(measure_str_key(inp))
+        if current is not None:
+            current = str(current)
+            records = [decode(x) for x in current.split(RedisDatabase.MAGIC_SPLIT)]
+            results = [rec[1] for rec in records]
+            if get_all:
+                return results
+            return max(results, key=lambda result: result.timestamp)
+        return current
+
+    def save(self, inp, res, extend=False):
+        current = self.get(measure_str_key(inp))
+        if not extend or current is None:
+            self.set(measure_str_key(inp),
+                     RedisDatabase.MAGIC_SPLIT.join([encode(inp, res)]))
+        else:
+            current = current.split(RedisDatabase.MAGIC_SPLIT)
+            self.set(measure_str_key(inp),
+                     RedisDatabase.MAGIC_SPLIT.join(current + [encode(inp, res)]))
+
+    def filter(self, func):
+        """
+        Dump all of the records for a particular target
+
+        Parameters
+        ----------
+        func: callable
+            The signature of the function is bool (MeasureInput, Array of MeasureResult)
+
+        Returns
+        -------
+        list of records (inp, result) matching the target
+
+        Examples
+        --------
+        get records for a target
+        >>> db.filter(lambda inp, resulst: "cuda" in inp.target.keys)
+        """
+        matched_records = list()
+        # may consider filtering in iterator in the future
+        for key in self.db:
+            current = self.get(key)
+            try:
+                records = [decode(x) for x in current.spilt(RedisDatabase.MAGIC_SPLIT)]
+            except TypeError:  # got a badly formatted/old format record
+                continue
+
+            inps, results = zip(*records)
+            inp = inps[0]
+            if not func(inp, results):
+                continue
+            result = max(results, key=lambda res: res.timestamp)
+            matched_records.append((inp, result))
+        return matched_records
+
+    def flush(self):
+        self.db.flushdb()
+
+class DummyDatabase(RedisDatabase):
+    """
+    A database based on python dictionary for testing.
+    """
+
+    def __init__(self):
+        # pylint: disable=super-init-not-called
+        self.db = {}
+
+    def set(self, key, value):
+        self.db[key] = value
+
+    def get(self, key):
+        return self.db.get(key)
+
+    def flush(self):
+        self.db = {}
diff --git a/python/tvm/autotvm/env.py b/python/tvm/autotvm/env.py
new file mode 100644
index 000000000000..3b04c98ea7ce
--- /dev/null
+++ b/python/tvm/autotvm/env.py
@@ -0,0 +1,12 @@
+"""Global configuration/variable scope for autotvm"""
+
+class AutotvmGlobalScope(object):
+    current = None
+
+    def __init__(self):
+        self._old = AutotvmGlobalScope.current
+        AutotvmGlobalScope.current = self
+
+        self.cuda_target_arch = None
+
+GLOBAL_SCOPE = AutotvmGlobalScope()
diff --git a/python/tvm/autotvm/feature.py b/python/tvm/autotvm/feature.py
new file mode 100644
index 000000000000..e0054628e686
--- /dev/null
+++ b/python/tvm/autotvm/feature.py
@@ -0,0 +1,181 @@
+# pylint: disable=invalid-name
+"""Extract feature of iter vars
+
+There are two types of feature
+1) Itervar feature
+   This feature is extracted based on loop variables.
+   Different loop structures will result in different shapes of feature
+2) Curve sample feature (relation feature)
+   This feature is extracted by sampling relation curve.
+   This feature is invariant of loop structure.
+"""
+
+import struct
+import numpy as np
+
+from tvm import schedule, ir_pass, build_module, get_global_func, target as _target
+
+def ana_lower(sch, args,
+              binds=None,
+              simple_mode=True):
+    """Do lower while keeping all axes in IR
+    i.e. Do not eliminate loop with extent of 1, do not vectorize, unroll or inject virtual threads
+    """
+    binds, _ = build_module.get_binds(args, binds)
+    sch = sch.normalize()
+    # Phase 0
+    bounds = schedule.InferBound(sch)
+    stmt = schedule.ScheduleOps(sch, bounds, True)
+    stmt = ir_pass.StorageFlatten(stmt, binds, 64)
+    stmt = ir_pass.CanonicalSimplify(stmt)
+    assert simple_mode
+    return stmt
+
+try:
+    _get_buffer_curve_sample_flatten = get_global_func(
+        "autotvm.feature.GetCurveSampleFeatureFlatten")
+    _get_itervar_feature = get_global_func("autotvm.feature.GetItervarFeature")
+    _get_itervar_feature_flatten = get_global_func("autotvm.feature.GetItervarFeatureFlatten")
+except ValueError as e:
+    def raise_error(*args, **kwargs):  # pylint: disable=unused-argument
+        raise RuntimeError("Cannot load autotvm c++ API")
+    _get_buffer_curve_sample_flatten = _get_itervar_feature = _get_itervar_feature_flatten = \
+        raise_error
+
+def get_itervar_feature(sch, args, take_log=False):
+    """get features of iter vars
+
+    Parameters
+    ----------
+    sch: tvm.schedule.Schedule
+    args: Array of tvm.tensor.Tensor
+        the buffer args for lower
+    take_log: bool
+        whether take log of numerical statics
+
+    Returns
+    -------
+    features of every axis in the IR, see doc/features.md for detail
+    """
+    stmt = ana_lower(sch, args, simple_mode=True)
+    feas = _get_itervar_feature(stmt, take_log)
+
+    # convert tvm node to python type
+    ret = []
+    for row in feas:
+        tmp = []
+        tmp.append([row[0][0].value, row[0][1]])
+        for item in row[1:]:
+            tmp.append([item[0].value] + [x.value for x in item[1:]])
+        ret.append(tmp)
+    return ret
+
+def flatten_itervar_feature(fea):
+    """flatten features into one-dimensional feature vectors
+
+    Parameters
+    ----------
+    fea: list
+        return value of get_itervar_feature
+
+    Returns
+    -------
+    flatten_feature: np.ndarray
+        one-dimensional vector
+    """
+    flatten = []
+    for axis in fea:
+        for pair in axis[1:]:
+            flatten.append(pair[1:])
+    return np.concatenate(flatten)
+
+def get_itervar_feature_flatten(sch, args, take_log=True):
+    """get flatten features of iter vars
+    this is equivalent to get_itervar_feature + flatten_itervar_feature, but much faster.
+
+    Parameters
+    ----------
+    sch: tvm.schedule.Schedule
+    args: Array of tvm.tensor.Tensor
+        the buffer args for lower
+    take_log: bool
+        whether take log of numerical statics
+
+    Returns
+    -------
+    flatten_feature: np.ndarray
+        one-dimensional vector
+    """
+    stmt = ana_lower(sch, args, simple_mode=True)
+    feas = _get_itervar_feature_flatten(stmt, take_log)
+    feas = struct.unpack('%df' % (len(feas)//4), feas)
+    return feas
+
+def get_flatten_name(fea):
+    """ Get names of feature after flatten.
+
+    Parameters
+    ----------
+    fea: list or str
+        return value of get_itervar_feature or a line of logfile
+
+    Returns
+    -------
+    feature_names: Array of str
+    """
+
+    feature_name = {
+        "_attr_": ["length", "nest_level", "topdown", "bottomup"] +
+                  ["ann_%d" % i for i in range(20)],
+        "_arith_": ["add", "mul", "div"],
+        "buf_touch": ["stride", "mod", "count", "reuse", "T_count", "T_reuse"],
+    }
+
+    if isinstance(fea, str):
+        from .record import decode
+        # flatten line to feature
+        line = fea
+        inp, _ = decode(line)
+        target = _target.create(inp.target)
+        with target:
+            s, args = inp.template.instantiate(inp.config)
+        fea = get_itervar_feature(s, args)
+
+    names = []
+    ct = 0
+    for row in fea:
+        var_name = str(row[0][1])
+        for pair in row[1:]:
+            key = pair[0]
+            if key in feature_name:
+                name_list = feature_name[key]
+            else:
+                name_list = feature_name["buf_touch"]
+
+            for i in range(len((pair[1:]))):
+                names.append(".".join(["f%d" % ct, var_name, key, name_list[i]]))
+                ct += 1
+    return names
+
+
+def get_buffer_curve_sample_flatten(sch, args, sample_n=30):
+    """
+    Get flatten curve sample feature (relation feature)
+
+    Parameters
+    ----------
+    sch: tvm.schedule.Schedule
+    args: Array of tvm.tensor.Tensor
+        the buffer args for lower
+    sample_n: int
+        number of sample points along one dimension
+
+    Returns
+    -------
+    flatten_feature: np.ndarray
+        one-dimensional vector
+    """
+    stmt = ana_lower(sch, args, simple_mode=True)
+    feas = _get_buffer_curve_sample_flatten(stmt, sample_n, False)
+    feas = struct.unpack('%df' % (len(feas)//4), feas)
+    return feas
diff --git a/python/tvm/autotvm/measure/__init__.py b/python/tvm/autotvm/measure/__init__.py
new file mode 100644
index 000000000000..ac87194ce3e2
--- /dev/null
+++ b/python/tvm/autotvm/measure/__init__.py
@@ -0,0 +1,8 @@
+"""Distributed executor infrastructure to scale up the tuning"""
+
+from .measure import MeasureInput, MeasureResult, MeasureErrorNo
+from .measure import create_measure_batch, measure_option
+from .measure_methods import request_remote
+
+from .local_executor import LocalExecutor
+from .executor import Future, Executor
diff --git a/python/tvm/autotvm/measure/executor.py b/python/tvm/autotvm/measure/executor.py
new file mode 100644
index 000000000000..17ea1d7fda9e
--- /dev/null
+++ b/python/tvm/autotvm/measure/executor.py
@@ -0,0 +1,83 @@
+""" Abstraction for asynchronous job execution """
+
+class Executor(object):
+    """
+    Base abstract executor interface for asynchronous job submission.
+    Allows submit asynchronous jobs and returns the Future object.
+    """
+    # timeout for jobs that may hang
+    DEFAULT_TIMEOUT = 60
+
+    def submit(self, func, *args, **kwargs):
+        """
+        Pass task (function, arguments) to the Executor.
+
+        Parameters
+        ----------
+        func : callable
+            function to be run by a worker
+        args : list or tuple, optional
+            arguments passed to the function
+        kwargs : dict, optional
+            The keyword arguments
+
+        Returns
+        -------
+        future : Future
+            Future object wrapping the task which can be used to
+            collect the task's result.
+        """
+        raise NotImplementedError()
+
+
+class Future(object):
+    """
+    Base class of the future object.
+    The implementations can return object of subclass of this.
+    This objects encapsulates the asynchronous execution of task
+    submitted to another thread, or another worker for execution.
+
+    Future objects store the state of tasks--can be polled for
+    result or a blocking call to retrieve the result can be used.
+    """
+    def done(self):
+        """
+        Return True if job was successfully cancelled or finished running.
+        """
+        raise NotImplementedError()
+
+    def get(self, timeout=None):
+        """
+        Get the result. This will block until the result is available.
+
+        Parameters
+        ----------
+        timeout : int or float, optional
+            Maximum number of seconds to wait before it timeouts.
+            If not specified, it means we block until the result is available.
+
+        Returns
+        -------
+        result : Any
+            The result returned by the submitted function.
+
+        Raises
+        ------
+        TimeoutError : if the result call timeouts.
+        """
+        raise NotImplementedError()
+
+class FutureError(RuntimeError):
+    """Base error class of all future events"""
+    pass
+
+# pylint:disable=redefined-builtin
+class TimeoutError(FutureError):
+    """Error raised when a task is timeout."""
+    pass
+
+class ExecutionError(FutureError):
+    """
+    Error raised when future execution crashes or failed.
+    """
+    pass
diff --git a/python/tvm/autotvm/measure/local_executor.py b/python/tvm/autotvm/measure/local_executor.py
new file mode 100644
index 000000000000..2ab09dbb18c0
--- /dev/null
+++ b/python/tvm/autotvm/measure/local_executor.py
@@ -0,0 +1,131 @@
+"""Local based implementation of the executor using multiprocessing"""
+
+import signal
+
+from multiprocessing import Process, Queue
+try:
+    from queue import Empty
+except ImportError:
+    from Queue import Empty
+
+import psutil
+
+from . import executor
+
+
+def kill_child_processes(parent_pid, sig=signal.SIGTERM):
+    """kill all child processes recursively"""
+    try:
+        parent = psutil.Process(parent_pid)
+    except psutil.NoSuchProcess:
+        return
+    children = parent.children(recursive=True)
+    for process in children:
+        try:
+            process.send_signal(sig)
+        except psutil.NoSuchProcess:
+            return
+
+def _execute_func(func, queue, args, kwargs):
+    """execute function and return the result or exception to a queue"""
+    try:
+        res = func(*args, **kwargs)
+    except Exception as exc:  # pylint: disable=broad-except
+        res = exc
+    queue.put(res)
+
+def timeout_monitor(queue, timeout, func, args, kwargs):
+    """A wrapper to support timeout of a function call"""
+
+    # start a new process for timeout (cannot use thread because we have c function)
+    p = Process(target=_execute_func, args=(func, queue, args, kwargs))
+    p.start()
+    p.join(timeout=timeout)
+
+    alive = p.is_alive()
+    kill_child_processes(p.pid)
+    p.terminate()
+    p.join()
+
+    if alive:
+        queue.put(executor.TimeoutError())
+    else:
+        if queue.empty():
+            queue.put(executor.ExecutionError("Fatal error in local executor"))
+
+
+class LocalFuture(executor.Future):
+    """Local wrapper for the future
+
+    Parameters
+    ----------
+    process: multiprocessing.Process
+        process for running this task
+    queue: multiprocessing.Queue
+        queue for receiving the result of this task
+    """
+    def __init__(self, process, queue):
+        self._done = False
+        self._process = process
+        self._queue = queue
+
+    def done(self):
+        self._done = self._done or not self._queue.empty()
+        return self._done
+
+    def get(self, timeout=None):
+        try:
+            res = self._queue.get(block=True, timeout=timeout)
+        except Empty:
+            raise executor.TimeoutError()
+        if self._process.is_alive():
+            kill_child_processes(self._process.pid)
+            self._process.terminate()
+        self._process.join()
+        self._queue.close()
+        self._queue.join_thread()
+        self._done = True
+        del self._queue
+        del self._process
+        return res
+
+
+class LocalFutureNoFork(executor.Future):
+    """Local wrapper for the future.
+    This is a none-fork version of LocalFuture.
+    Use this for the runtime that does not support fork (like cudnn)
+    """
+    def __init__(self, result):
+        self._result = result
+
+    def done(self):
+        return True
+
+    def get(self, timeout=None):
+        return self._result
+
+
+class LocalExecutor(executor.Executor):
+    """Local executor that runs workers on the same machine with multiprocessing."""
+    def __init__(self, timeout=None):
+        self.timeout = timeout or executor.Executor.DEFAULT_TIMEOUT
+
+    def submit(self, func, *args, **kwargs):
+        """
+
+        Note
+        ----------
+        By default, the executor will fork a new process for a new job
+        But some runtime does not support fork (e.g. cuda runtime, cudnn).
+        In this circumstance, you should set 'fork_new_process' to False in kwargs
+        """
+        fork_new_process = kwargs.pop('fork_new_process', True)
+
+        if not fork_new_process:
+            return LocalFutureNoFork(func(*args, **kwargs))
+
+        queue = Queue(1)
+        process = Process(target=timeout_monitor,
+                          args=(queue, self.timeout, func, args, kwargs))
+        process.start()
+        return LocalFuture(process, queue)
diff --git a/python/tvm/autotvm/measure/measure.py b/python/tvm/autotvm/measure/measure.py
new file mode 100644
index 000000000000..e301aaee8b1d
--- /dev/null
+++ b/python/tvm/autotvm/measure/measure.py
@@ -0,0 +1,338 @@
+# pylint: disable=pointless-string-statement,consider-using-enumerate,invalid-name
+"""User facing API for specifying how to measure the generated code"""
+import time
+from collections import namedtuple
+
+import numpy as np
+
+from ... import build, nd, target as _target
+from ...contrib.util import tempdir
+from ...rpc.tracker import Tracker
+from ...rpc.server import Server
+
+from ..util import get_const_tuple
+from .local_executor import LocalExecutor
+
+
+class MeasureInput(namedtuple("MeasureInput", ["target", "task", "config"])):
+    """
+    Stores all the necessary inputs for a measurement.
+
+    Parameters
+    ----------
+    target : tvm.target.Target
+        The target device
+    task : task.Task
+        Task function
+    config : ConfigEntity
+        Specific configuration.
+    """
+
+class MeasureResult(namedtuple("MeasureResult", ["costs", "error_no", "all_cost", "timestamp"])):
+    """
+    Stores all the results of a measurement
+
+    Parameters
+    ----------
+    costs: Array of float or Array of Exception
+        If no error occurs for this measurement, it is an array of measured running times.
+        If some error occurs during the measurement, it is an array of the exception objections.
+    error_no: int
+        Denote error type, defined by MeasureErrorNo
+    all_cost: float
+        All cost of this measure, including rpc, compilation, test runs
+    timestamp: float
+        The absolute time stamp when we finish measurement.
+    """
+
+class MeasureErrorNo(object):
+    """Error type for MeasureResult"""
+    NO_ERROR = 0              # no error
+    INSTANTIATION_ERROR = 1   # error when calling template function
+    COMPILE_HOST = 2          # error when compiling code on host (e.g. tvm.build)
+    COMPILE_DEVICE = 3        # error when compiling code on device (e.g. opencl JIT on device)
+    RUNTIME_DEVICE = 4        # error when run program on device
+    WRONG_ANSWER = 5          # answer is wrong when compared to a golden output
+    FLEET_ERROR = 6           # error of measure infrastructure
+
+
+def measure_option(mode,
+                   number=1,
+                   repeat=1,
+                   timeout=60,
+                   parallel_num=1,
+                   pack_size=1,
+                   check_correctness=False,
+                   build_option=None,
+                   replay_db=None,
+                   save_to_replay_db=True,
+                   rpc_device_key=None,
+                   rpc_priority=1,
+                   rpc_timeout=60,
+                   rpc_tracker_addr=None,
+                   use_ndk=False,
+                   custom_measure_batch=None):
+    """Configure how to do measurement
+
+    Parameters
+    ----------
+    mode: str
+        'local': use the local device for measurement. In this mode,
+        the tuner starts a tracker and a RPC server silently for the user.
+
+        'rpc': request devices for measurement from rpc tracker. In this mode,
+        you should start a rpc tracker in a separate processing.
+
+        'custom': use custom measure function
+
+        'local-nofork': use local device for measure but does not use multiprocessing.
+        This mode is suitable for debug, but does not support timeout and parallel.
+
+    number : int, optional
+        Number of times to do the measurement for average
+    repeat : int, optional
+        Number of times to repeat the measurement.
+        In total, the generated code will be run (1 + number x repeat) times,
+        where the first one is warm up. The returned result contains `repeat` costs,
+        each of which is the average of `number` test run.
+    timeout: int, optional
+        Timeout for a whole batch. TimeoutError will be returned as the result if a
+        task timeouts.
+    parallel_num: int, optional
+        The number of measurement task that can run in parallel.
+        Set this according to the number of cpu cores (for compilation) and
+        the number of devices you have (for measuring generate code).
+    pack_size : int, optional
+        Number of configs to measure in one RPC call.
+        Usually this can be set to 1. If your device has high cost to establish a rpc connection,
+        set this higher.
+    check_correctness: bool
+        Whether check correctness after measurement.
+    build_option: Dict, optional
+        Build options for tvm.build_config
+
+    replay_db : Database, optional
+        The database that we retrieve saved MeasureResults from
+    save_to_replay_db: bool, optional
+        Whether save measure result to database. This is useless when replay_db is None
+
+    rpc_priority: int, optional
+        Priority of this task, used by scheduler in tracker
+    rpc_device_key: str, optional
+        The device key of registered devices in tracker
+    rpc_timeout: int, optional
+        Timeout of rpc session
+    rpc_tracker_addr: Tuple(str, int), optional
+        The address of rpc tracker in Tuple(host, port) format.
+        If is set, will use this address.
+        If is not set, will use environment variable "TVM_TRACKER_HOST" and "TVM_TRACKER_PORT"
+
+    use_ndk: bool, option
+        Whether export requires ndk
+    custom_measure_batch: callable, optional
+        custom measure function
+
+    Returns
+    -------
+    options: dict
+        A dict to store all options
+    """
+    return {
+        'mode': mode,
+        'number': number,
+        'repeat': repeat,
+        'timeout': timeout,
+        'parallel_num': parallel_num,
+        'pack_size': pack_size,
+        'check_correctness': check_correctness,
+        'build_option': build_option,
+
+        'replay_db': replay_db,
+        'save_to_replay_db': save_to_replay_db,
+
+        'rpc_device_key': rpc_device_key,
+        'rpc_priority': rpc_priority,
+        'rpc_timeout': rpc_timeout,
+        'rpc_tracker_addr': rpc_tracker_addr,
+
+        'use_ndk': use_ndk,
+        'custom_measure_batch': custom_measure_batch
+    }
+
+
+def create_measure_batch(task, options):
+    """Get a standard measure_batch function.
+
+    Parameters
+    ----------
+    task: tvm.autotvm.task.Task
+        The tuning task
+    options: dict
+        The option for measuring generated code.
+        You should use the return value of :any:`autotvm.measure_option` for this argument
+
+    Returns
+    -------
+    measure_batch: callable
+        a callback function to measure a batch of configs
+    """
+    from . import measure_methods
+    from ..database import filter_inputs
+
+    mode = options['mode']
+    number, repeat = options['number'], options['repeat']
+    timeout, parallel_num = options['timeout'], options['parallel_num']
+    pack_size = options['pack_size']
+    check_correctness = options['check_correctness']
+    build_option = options['build_option']
+    replay_db = options['replay_db']
+    save_to_replay_db = options['save_to_replay_db']
+    rpc_device_key = options['rpc_device_key']
+    rpc_priority, rpc_timeout = options['rpc_priority'], options['rpc_timeout']
+    use_ndk = options['use_ndk']
+    custom_measure_batch = options['custom_measure_batch']
+
+    kwargs = {}
+    executor = LocalExecutor(timeout=timeout)
+
+    if mode == 'local':
+        # start temporary rpc tracker and rpc server for the user
+        tracker = Tracker('localhost', port=9000, port_end=10000,
+                          silent=True)
+        rpc_device_key = '$local$device$%d' % tracker.port
+        server = Server('localhost', port=9000, port_end=10000,
+                        key=rpc_device_key,
+                        use_popen=True, silent=True,
+                        tracker_addr=(tracker.host, tracker.port))
+
+        fmeasure = measure_methods.measure_rpc
+        kwargs['rpc_device_key'] = rpc_device_key
+        kwargs['rpc_tracker_addr'] = (tracker.host, tracker.port)
+        kwargs['rpc_timeout'] = timeout
+        kwargs['tmp_dir'] = tempdir()
+    elif mode == 'rpc':
+        fmeasure = measure_methods.measure_rpc
+        kwargs['rpc_device_key'] = rpc_device_key
+        kwargs['rpc_priority'] = rpc_priority
+        kwargs['rpc_timeout'] = rpc_timeout
+        kwargs['use_ndk'] = use_ndk
+        kwargs['tmp_dir'] = tempdir()
+        assert rpc_device_key, "In rpc mode, a rpc_device_key must be provided"
+    elif mode == "custom":
+        assert callable(custom_measure_batch), "In custom mode, custom_measure_func " \
+                                               "must be a callable object"
+    elif mode == 'local-nofork':
+        fmeasure = measure_methods.measure_local
+        kwargs['fork_new_process'] = False
+    else:
+        raise RuntimeError("Invalid mode: " + mode)
+
+    if 'cuda' in task.target.keys and 'rpc_device_key' in kwargs:  # query cuda device info
+        add_cuda_device_info(kwargs['rpc_device_key'], kwargs.get('rpc_tracker_addr'), kwargs)
+    if 'opencl' in task.target.keys and 'rpc_device_key' in kwargs:
+        add_opencl_device_info(kwargs['rpc_device_key'], kwargs.get('rpc_tracker_addr'), kwargs)
+
+    if check_correctness:
+        # use llvm to generate a reference input/output
+        # this option works for tuning topi, but might not work for you custom op
+        with _target.create("llvm"):
+            s, arg_bufs = task.instantiate(task.config_space.get(0))
+        ref_input = [np.random.uniform(size=get_const_tuple(x.shape)).astype(x.dtype)
+                     for x in arg_bufs]
+        func = build(s, arg_bufs, "llvm")
+        tvm_buf = [nd.array(x) for x in ref_input]
+        func(*tvm_buf)
+        ref_output = [x.asnumpy() for x in tvm_buf]
+        kwargs['ref_input'], kwargs['ref_outpu'] = ref_input, ref_output
+
+    def measure_batch(measure_inputs):
+        """measure the time cost for a batch of configs in real machines"""
+        if replay_db is not None:
+            partial_results, measure_inputs =\
+                filter_inputs(replay_db, measure_inputs, retry=False)
+
+        # pack configs
+        input_packs = []
+        for i in range(0, len(measure_inputs), pack_size):
+            input_packs.append(measure_inputs[i:i + pack_size])
+
+        # send to measure
+        futures = []
+        for input_pack in input_packs:
+            future = executor.submit(
+                fmeasure, input_pack,
+                number=number,
+                repeat=repeat,
+                build_option=build_option,
+                **kwargs
+            )
+            futures.append(future)
+
+        # transform results
+        results = []
+        for future in futures:
+            result = future.get()
+            if isinstance(result, Exception):
+                if mode == 'local-nofork':
+                    # debug usage, raise exception
+                    raise result
+                tstamp = time.time()
+                results.extend([MeasureResult((result,), MeasureErrorNo.FLEET_ERROR,
+                                              timeout, tstamp)] * pack_size)
+            else:
+                results.extend(result)
+
+        if replay_db is not None:
+            if save_to_replay_db:  # save result to database
+                for measure_input, result in zip(measure_inputs, results):
+                    replay_db.save(measure_input, result)
+
+            result_idx = 0
+            for i in range(len(partial_results)):
+                if partial_results[i] is None:
+                    partial_results[i] = results[result_idx]
+                    result_idx += 1
+            return partial_results
+        return results
+
+    if mode == 'custom':
+        measure_batch = custom_measure_batch
+
+    measure_batch.parallel_num = parallel_num
+    if mode == 'local':
+        measure_batch.aux_objects = {"server": server, "tracker": tracker}
+    return measure_batch
+
+
+def add_cuda_device_info(device_key, rpc_tracker_addr, kwargs):
+    """Query cuda device info. This is used to set the flags for nvcc compiler
+    and check the validity of a generated code."""
+    from .measure_methods import request_remote
+
+    remote = request_remote(device_key, rpc_tracker_addr)
+    ctx = remote.context('cuda', 0)
+    max_dims = ctx.max_thread_dimensions
+    kwargs['check_gpu'] = {
+        'max_shared_memory_per_block': ctx.max_shared_memory_per_block,
+        'max_threads_per_block': ctx.max_threads_per_block,
+        'max_thread_x': max_dims[0],
+        'max_thread_y': max_dims[1],
+        'max_thread_z': max_dims[2],
+    }
+
+    kwargs["cuda_arch"] = "sm_" + "".join(ctx.compute_version.split('.'))
+
+def add_opencl_device_info(device_key, rpc_tracker_addr, kwargs):
+    """Query opencl device info. This is used to check the validity of a generated code."""
+    from .measure_methods import request_remote
+
+    remote = request_remote(device_key, rpc_tracker_addr)
+    ctx = remote.context('opencl', 0)
+    max_dims = ctx.max_thread_dimensions
+    kwargs['check_gpu'] = {
+        'max_shared_memory_per_block': ctx.max_shared_memory_per_block,
+        'max_threads_per_block': ctx.max_threads_per_block,
+        'max_thread_x': max_dims[0],
+        'max_thread_y': max_dims[1],
+        'max_thread_z': max_dims[2],
+    }
diff --git a/python/tvm/autotvm/measure/measure_methods.py b/python/tvm/autotvm/measure/measure_methods.py
new file mode 100644
index 000000000000..b10869bde4cc
--- /dev/null
+++ b/python/tvm/autotvm/measure/measure_methods.py
@@ -0,0 +1,296 @@
+# pylint: disable=consider-using-enumerate,invalid-name,too-many-function-args
+"""
+Functions that run on executor for measurement.
+These functions are responsible for building tvm module, uploading it to
+remote devices, recording the running time costs and checking the correctness of output
+"""
+
+import logging
+import os
+import time
+from random import getrandbits
+
+import numpy as np
+
+from ...contrib import ndk, nvcc
+from ... import rpc, ir_pass, build, build_config, nd, context, TVMError, register_func
+
+from ..util import get_const_tuple
+from ..env import AutotvmGlobalScope
+from .measure import MeasureResult, MeasureErrorNo
+from ..task.space import InstantiationError
+
+
+class HashMismatchError(ValueError):
+    """Raised when the code hash of a submitted config doesn't match that on the
+       measure side """
+    pass
+
+def request_remote(device_key, tracker_addr=None, priority=1, timeout=60):
+    """request a remote session
+
+    Parameters
+    ----------
+    device_key: string
+        device key of registered device in tracker
+    tracker_addr: Tuple(string, int), optional
+        The address of rpc tracker in (host, port) format
+    priority: int, optional
+        priority of this request, larger is more prior
+    timeout: float, optional
+        timeout of this session (units: seconds)
+
+    Returns
+    ------
+    session: RPCSession
+    """
+    # connect to the tracker
+    if tracker_addr:
+        host = tracker_addr[0]
+        port = tracker_addr[1]
+    else:
+        host = os.environ['TVM_TRACKER_HOST']
+        port = int(os.environ['TVM_TRACKER_PORT'])
+
+    tracker = rpc.connect_tracker(host, port)
+    remote = tracker.request(device_key, priority=priority,
+                             session_timeout=timeout)
+    return remote
+
+
+def _measure_generic(fbuild, input_pack, ref_input, ref_output):
+    """Generic measurement function
+
+    Parameters
+    ----------
+    fbuild : function takes MeasureInput returns tuple of (time_func, ctx)
+        The build function used to build each input.
+    input_pack : list of MeasureInput
+        The inputs we need to evaluate
+    ref_input: Array of np.ndarray
+        Reference input for checking correctness
+    ref_output: Array of np.ndarray
+        Reference output for checking correctness
+
+    Returns
+    -------
+    res_pack : array of MeasureResult
+        The list of execution result of measurement.
+    """
+    res_pack = []
+    for inp in input_pack:
+        tic = time.time()
+        try:
+            time_f, ctx, arg_bufs = fbuild(inp)
+        except TVMError as exc:
+            tstamp = time.time()
+            msg = str(exc)
+            if "Stack trace returned" in msg:
+                msg = msg[:msg.index("Stack trace returned")]
+            if "InstantiationError" in msg:
+                try:
+                    msg = msg.split('\n')[-2].split(": ")[1]
+                except Exception:  # pylint: disable=broad-except
+                    pass
+                res_pack.append(MeasureResult((InstantiationError(msg),),
+                                              MeasureErrorNo.INSTANTIATION_ERROR,
+                                              tstamp - tic, tstamp))
+            else:
+                res_pack.append(MeasureResult((RuntimeError(msg),),
+                                              MeasureErrorNo.COMPILE_HOST,
+                                              tstamp - tic, tstamp))
+            continue
+        except InstantiationError as e:
+            tstamp = time.time()
+            res_pack.append(MeasureResult((e,),
+                                          MeasureErrorNo.INSTANTIATION_ERROR,
+                                          tstamp - tic, tstamp))
+            continue
+
+        # measure time
+        errno = MeasureErrorNo.NO_ERROR
+        try:
+            if ref_input:
+                args = [nd.array(x, ctx) for x in ref_input]
+            else:
+                args = [nd.array(np.random.uniform(size=get_const_tuple(x.shape)).astype(x.dtype),
+                                 ctx) for x in arg_bufs]
+            costs = time_f(*args).results
+            if len(costs) > 2:  # remove largest and smallest value to reduce variance
+                costs = list(costs)
+                costs.sort()
+                costs = tuple(costs[1:-1])
+            if ref_output:
+                for expected, real in zip(ref_output, args):
+                    if not np.allclose(expected, real.asnumpy(), rtol=1e-4):
+                        logging.warning("Wrong Answer!")
+                        errno = MeasureErrorNo.WRONG_ANSWER
+        except TVMError as exc:
+            msg = str(exc)
+            if "Stack trace returned" in msg:
+                msg = msg[:msg.index("Stack trace returned")]
+            costs = (RuntimeError(msg),)
+            errno = MeasureErrorNo.RUNTIME_DEVICE
+        tstamp = time.time()
+        res_pack.append(MeasureResult(costs, errno, tstamp - tic, tstamp))
+    return res_pack
+
+def _build_func(inp, build_option, kwargs):
+    """Build function module. Exception will be raised when error occurs"""
+    with inp.target:
+        s, args = inp.task.instantiate(inp.config)
+        if not inp.config.valid():
+            raise InstantiationError(inp.config.errors)
+        code_hash = getattr(s, 'code_hash', None)
+        if inp.config.code_hash != code_hash:
+            raise HashMismatchError('got {0:s}, expected {1:s}'
+                                    .format(str(inp.config.code_hash), str(code_hash)))
+
+        opts = build_option or {}
+        if "check_gpu" in kwargs:
+            values = kwargs['check_gpu']
+            # Add gpu verify pass to filter out invalid configs in advance.
+            # This can accelerate the tuning process
+            check_keys = ['max_shared_memory_per_block', 'max_threads_per_block',
+                          'max_thread_x', 'max_thread_y', 'max_thread_z']
+            opts["add_lower_pass"] = [
+                (2, gpu_verify_pass(**{key: values[key] for key in check_keys}))]
+
+        if 'cuda_arch' in kwargs:
+            set_cuda_target_arch(kwargs['cuda_arch'])
+
+        with build_config(**opts):
+            func = build(s, args, target_host=inp.task.target_host)
+
+    return func, args
+
+
+def measure_rpc(input_pack,
+                rpc_device_key,
+                number,
+                repeat=1,
+                build_option=None,
+                rpc_tracker_addr=None,
+                rpc_priority=1,
+                rpc_timeout=60,
+                tmp_dir=None,
+                **kwargs):
+    """Measure the time cost on a device by rpc
+
+    Parameters
+    ----------
+    input_pack : list of MeasureInput
+        The inputs we need to evaluate
+    rpc_device_key: str
+        The device key of registered devices in tracker
+    number : int
+        Number of times to get the running measurement
+    repeat : int, optional
+        How many times we want to repeat the measurement.
+    build_option: Dict
+        build options for tvm.build_config
+
+    rpc_tracker_addr: Tuple(string, int), optional
+        The address of rpc tracker in (host, port) format
+        If is none, will use environment variable
+    rpc_priority: int, optional
+        priority of this task, used by scheduler in tracker
+    rpc_timeout: int, optional
+        timeout of the rpc session
+
+    tmp_dir: tvm.contrib.util.TempDirectory, optional
+        directory to store temp file
+
+    kwargs: dict, optional
+        Additional key word arguments
+
+    Returns
+    -------
+    res_pack : Array of MeasureResult
+        The list of execution results of measurement.
+    """
+    def _fbuild(inp):
+        """ Local build function."""
+        func, args = _build_func(inp, build_option, kwargs)
+
+        if not kwargs.get('use_ndk', False):
+            file_name = "tmp_func_%0x.tar" % getrandbits(64)
+            path = tmp_dir.relpath(file_name)
+            func.export_library(path)
+        else:
+            file_name = "tmp_func_%0x.so" % getrandbits(64)
+            path = tmp_dir.relpath(file_name)
+            func.export_library(path, ndk.create_shared)
+        remote = request_remote(rpc_device_key, rpc_tracker_addr, rpc_priority, rpc_timeout)
+        remote.upload(path)
+        func = remote.load_module(file_name)
+        ctx = remote.context(str(inp.target), 0)
+        time_f = func.time_evaluator(
+            func.entry_name, ctx, number=number, repeat=repeat)
+        return time_f, ctx, args
+
+    ret = _measure_generic(_fbuild, input_pack,
+                           kwargs.get("ref_input", None), kwargs.get("ref_output", None))
+    return ret
+
+
+def measure_local(input_pack,
+                  number,
+                  repeat=1,
+                  build_option=None,
+                  **kwargs):
+    """Measure the time cost on a local machine.
+
+    Parameters
+    ----------
+    input_pack : list of MeasureInput
+        The inputs we need to evaluate
+    number : int
+        Number of times to get the running measurement
+    repeat : int, optional
+        How many times we want to repeat the measurement.
+    build_option: dict, optional
+        Build options for tvm.build_config
+    kwargs: dict, optional
+        Additional key word arguments
+
+    Returns
+    -------
+    res_pack : Array of MeasureResult
+        The list of execution results of measurement.
+    """
+
+    def _fbuild(inp):
+        """ Local build function """
+        func, args = _build_func(inp, build_option, kwargs)
+        ctx = context(str(inp.target), 0)
+        time_f = func.time_evaluator(
+            func.entry_name, ctx, number=number, repeat=repeat)
+        return time_f, ctx, args
+
+    ret = _measure_generic(_fbuild, input_pack,
+                           kwargs.get("ref_input", None), kwargs.get("ref_output", None))
+    return ret
+
+
+def gpu_verify_pass(**kwargs):
+    """Verify the validity of a gpu kernel
+    This pass will check shared memory size and number of threads per block.
+    """
+    def verify_pass(stmt):
+        valid = ir_pass.VerifyGPUCode(stmt, kwargs)
+        if not valid:
+            raise InstantiationError("Skipped because of invalid gpu kernel")
+        return stmt
+    return verify_pass
+
+
+@register_func
+def tvm_callback_cuda_compile(code):
+    """use nvcc to generate ptx code for better optimization"""
+    ptx = nvcc.compile_cuda(code, target="ptx", arch=AutotvmGlobalScope.current.cuda_target_arch)
+    return ptx
+
+def set_cuda_target_arch(arch):
+    """set target architecture of nvcc compiler"""
+    AutotvmGlobalScope.current.cuda_target_arch = arch
diff --git a/python/tvm/autotvm/record.py b/python/tvm/autotvm/record.py
new file mode 100644
index 000000000000..72335fcad20c
--- /dev/null
+++ b/python/tvm/autotvm/record.py
@@ -0,0 +1,332 @@
+# pylint: disable=superfluous-parens, redefined-outer-name, redefined-outer-name,pointless-string-statement
+# pylint: disable=consider-using-enumerate,invalid-name
+"""Tuning record and serialization format"""
+
+import argparse
+import base64
+import logging
+import multiprocessing
+import pickle
+import json
+import time
+from collections import OrderedDict
+
+import numpy as np
+
+from .. import target, build, lower
+
+from . import task
+from .task import DispatchContext, ConfigEntity
+from .measure import MeasureInput, MeasureResult
+
+AUTOTVM_LOG_VERSION = 0.1
+
+try:  # convert unicode to str for python2
+    _unicode = unicode
+except NameError:
+    _unicode = ()
+
+
+def measure_str_key(inp, include_config=True):
+    """ get unique str key for MeasureInput
+
+    Parameters
+    ----------
+    inp: MeasureInput
+        input for the measure
+    include_config: bool, optional
+        whether includes config in the str key
+
+    Returns
+    -------
+    key: str
+        The str representation of key
+    """
+    config_str = str(inp.config) if include_config else ""
+    return "".join([str(inp.target), inp.task.name, str(inp.task.args),
+                    str(inp.task.kwargs), config_str])
+
+
+def encode(inp, result, protocol='json'):
+    """encode (MeasureInput, MeasureResult) pair to a string
+
+    Parameters
+    ----------
+    inp: autotvm.tuner.MeasureInput
+    result: autotvm.tuner.MeasureResult
+        pair of input/result
+    protocol: str
+        log protocol, json or pickle
+
+    Returns
+    -------
+    row: str
+        a row in the logger file
+    """
+
+    if protocol == 'json':
+        json_dict = {
+            "i": (str(inp.target),
+                  inp.task.name, inp.task.args, inp.task.kwargs,
+                  inp.task.workload,
+                  inp.config.to_json_dict()),
+
+            "r": (result.costs if result.error_no == 0 else (1e9,),
+                  result.error_no,
+                  result.all_cost,
+                  result.timestamp),
+
+            "v": AUTOTVM_LOG_VERSION
+        }
+        return json.dumps(json_dict)
+    elif protocol == 'pickle':
+        row = (str(inp.target),
+               str(base64.b64encode(pickle.dumps([inp.task.name,
+                                                  inp.task.args,
+                                                  inp.task.kwargs,
+                                                  inp.task.workload])).decode()),
+               str(base64.b64encode(pickle.dumps(inp.config)).decode()),
+               str(base64.b64encode(pickle.dumps(tuple(result))).decode()))
+        return '\t'.join(row)
+    else:
+        raise RuntimeError("Invalid log protocol: " + protocol)
+
+
+def decode(row, protocol='json'):
+    """Decode encoded record string to python object
+
+    Parameters
+    ----------
+    row: str
+        a row in the logger file
+    protocol: str
+        log protocol, json or pickle
+
+    Returns
+    -------
+    input: autotvm.tuner.MeasureInput
+    result: autotvm.tuner.MeasureResult
+    """
+    # pylint: disable=unused-variable
+    if protocol == 'json':
+        row = json.loads(row)
+        tgt, task_name, task_args, task_kwargs, workload, config = row['i']
+        tgt = target.create(str(tgt))
+
+        def clean_json_to_python(x):
+            """1. convert all list in x to tuple (hashable)
+               2. convert unicode to str for python2
+            """
+            if isinstance(x, list):
+                return tuple([clean_json_to_python(a) for a in x])
+            if isinstance(x, _unicode):
+                return str(x)
+            return x
+
+        tsk = task.Task(clean_json_to_python(task_name), clean_json_to_python(task_args))
+        tsk.workload = clean_json_to_python(workload)
+        config = ConfigEntity.from_json_dict(config)
+        inp = MeasureInput(tgt, tsk, config)
+        result = MeasureResult(*[tuple(x) if isinstance(x, list) else x for x in row["r"]])
+
+        return inp, result
+    elif protocol == 'pickle':
+        items = row.split("\t")
+        tgt = target.create(items[0])
+        task_tuple = pickle.loads(base64.b64decode(items[1].encode()))
+        config = pickle.loads(base64.b64decode(items[2].encode()))
+        result = pickle.loads(base64.b64decode(items[3].encode()))
+
+        tsk = task.Task(task_tuple[0], task_tuple[1])
+        tsk.workload = task_tuple[3]
+        return MeasureInput(tgt, tsk, config), MeasureResult(*result)
+    else:
+        raise RuntimeError("Invalid log protocol: " + protocol)
+
+def load_from_file(filename):
+    """Generator: load records from file.
+    This is a generator that yields the records.
+
+    Parameters
+    ----------
+    filename: str
+
+    Yields
+    ------
+    input: autotvm.tuner.MeasureInput
+    result: autotvm.tuner.MeasureResult
+    """
+    for row in open(filename):
+        yield decode(row)
+
+
+class ApplyHistoryBest(DispatchContext):
+    """
+    Apply the history best config
+
+    Parameters
+    ----------
+    records : str or iterator of (MeasureInput, MeasureResult)
+        Collection of tuning records.
+        if is str, then it should be the filename of a records log file.
+                   Each row of this file is an encoded record pair.
+        otherwise, it is an iterator
+    default: ConfigEntity, optional
+        default config to return when no history records
+    """
+    def __init__(self, records, default=None):
+        super(ApplyHistoryBest, self).__init__()
+
+        if isinstance(records, str):
+            records = load_from_file(records)
+
+        counter = 0
+        best_map = {}
+        for inp, res in records:
+            counter += 1
+            if res.error_no != 0:
+                continue
+            for k in inp.target.keys:
+                key = (k, inp.task.workload)
+                if key not in best_map:
+                    best_map[key] = (inp, res)
+                else:
+                    _, other_res = best_map[key]
+                    if np.mean(other_res.costs) > np.mean(res.costs):
+                        best_map[key] = (inp, res)
+        logging.info(
+            "Finish load %d records, %d entries selected", counter, len(best_map))
+        self._best_map = best_map
+        self._default = default
+
+    def query(self, target, workload):
+        if target is None:
+            raise RuntimeError("Need a target context to find the history best. "
+                               "Hint: If your target is llvm, use `with tvm.target.create('llvm'):`"
+                               " above the dispatcher call. So does other target. ")
+
+        for k in target.keys:
+            key = (k, workload)
+            if key in self._best_map:
+                return self._best_map[key][0].config
+
+        if self._default:
+            return self._default
+        raise RuntimeError(
+            "Cannot find config for target=%s, workload=%s" % (target, workload))
+
+    def dump_best(self, out_file):
+        """Dump the best records for each workload to a file
+
+        Parameters
+        ----------
+        out_file: str
+            filename
+        """
+        fout = open(out_file, 'a')
+        for val in self._best_map.values():
+            inp, res = val
+            fout.write(encode(inp, res) + '\n')
+
+
+def split_workload(in_file, clean=True):
+    """Split a log file into separate files, each of which contains only a single workload
+    This function can also delete duplicated records in log file
+
+    Parameters
+    ----------
+    in_file: str
+        input filename
+    clean: bool
+        whether delete duplicated items
+    """
+    tic = time.time()
+    lines = list(open(in_file).readlines())
+
+    logging.info("start convert...")
+    pool = multiprocessing.Pool()
+    lines = pool.map(decode, lines)
+    logging.info("map done %.2f", time.time() - tic)
+
+    wkl_dict = OrderedDict()
+    for inp, res in lines:
+        wkl = measure_str_key(inp, False)
+        if wkl not in wkl_dict:
+            wkl_dict[wkl] = []
+        wkl_dict[wkl].append([inp, res])
+
+    if clean:
+        for i, (k, v) in enumerate(wkl_dict.items()):
+            # clean duplicated items
+            added = set()
+            cleaned = []
+            for inp, res in v:
+                str_key = measure_str_key(inp)
+                if str_key in added:
+                    continue
+                added.add(str_key)
+                cleaned.append([inp, res])
+
+            # write to file
+            logging.info("Key: %s\tValid: %d\tDup: %d\t", k, len(cleaned), len(v) - len(cleaned))
+            with open(args.i + ".%03d.wkl" % i, 'w') as fout:
+                for inp, res in cleaned:
+                    fout.write(encode(inp, res) + '\n')
+    else:
+        for i, (k, v) in enumerate(wkl_dict.items()):
+            logging.info("Key: %s\tNum: %d", k, len(v))
+            with open(args.i + ".%03d.wkl" % i, 'w') as fout:
+                for inp, res in v:
+                    fout.write(encode(inp, res) + '\n')
+
+
+"""
+Usage:
+This record executable module has three modes.
+
+* Print log file in readable format
+e.g. python -m autotvm.record --mode read --i collect_conv.tsv --begin 0 --end 5 --ir --code
+
+* Extract history best from a large log file
+e.g. python -m autotvm.record --mode best --i collect.tsv
+
+* Split a log file into separate files, each of which contains only a single wkl
+e.g. python -m autotvm.record --mode split --i collect.tsv
+"""
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--mode", choices=['read', 'best', 'split'], default='read')
+    parser.add_argument("--i", type=str, help="input file")
+    parser.add_argument("--o", type=str, default=None, help='output file')
+    parser.add_argument("--begin", type=int, default=0)
+    parser.add_argument("--end", type=int, default=5)
+    parser.add_argument("--ir", action='store_true')
+    parser.add_argument("--code", action='store_true')
+
+    args = parser.parse_args()
+    logging.basicConfig(level=logging.INFO)
+
+    if args.mode == 'best':
+        args.o = args.o or args.i + ".best"
+        hist_best = ApplyHistoryBest(load_from_file(args.i))
+        hist_best.dump_best(args.o)
+    elif args.mode == 'read':
+        for i, (inp, result) in enumerate(load_from_file(args.i)):
+            if args.begin <= i < args.end:
+                with inp.target:
+                    s, arg_bufs = inp.task.instantiate(inp.config)
+
+                print("")
+                print(inp.target, inp.task, inp.config)
+                print(result)
+
+                if args.ir:
+                    with inp.target:
+                        print(lower(s, arg_bufs, simple_mode=True))
+
+                if args.code:
+                    with inp.target:
+                        func = build(s, arg_bufs)
+                        print(func.imported_modules[0].get_source())
+    elif args.mode == 'split':
+        split_workload(args.i)
diff --git a/python/tvm/autotvm/task/__init__.py b/python/tvm/autotvm/task/__init__.py
new file mode 100644
index 000000000000..ff59351ef33e
--- /dev/null
+++ b/python/tvm/autotvm/task/__init__.py
@@ -0,0 +1,12 @@
+"""Task is a tunable composition of template functions.
+
+Tuner takes a tunable task and optimizes the joint configuration
+space of all the template functions in the task.
+This module defines the task data structure, as well as a collection(zoo)
+of typical tasks of interest.
+"""
+
+from .task import Task, create, register, template, get_config
+from .space import ConfigSpace, ConfigEntity
+from .code_hash import attach_code_hash, attach_code_hash_to_arg
+from .dispatcher import DispatchContext, ApplyConfig, dispatcher
diff --git a/python/tvm/autotvm/task/code_hash.py b/python/tvm/autotvm/task/code_hash.py
new file mode 100644
index 000000000000..48343272bbdc
--- /dev/null
+++ b/python/tvm/autotvm/task/code_hash.py
@@ -0,0 +1,43 @@
+"""
+Decorator functions for hashing schedule code
+
+code hashing is used to check the consistence of schedule code and the parameters loaded from log
+"""
+import inspect
+import zlib
+
+from tvm import schedule
+
+def attach_code_hash(s):
+    """Decorator for attaching a code hash to a schedule
+
+    Parameters
+    ----------
+    s: Schedule
+        tvm.schedule.Schedule to attach the hash to
+    """
+    def decorator(func):
+        def wrapper(*args, **kwargs):
+            func(*args, **kwargs)
+            raw_hash = zlib.crc32(''.join(inspect.getsourcelines(func)[0]).encode())
+            s.code_hash = hex(raw_hash)[2:]
+        return wrapper
+    return decorator
+
+def attach_code_hash_to_arg(arg_idx=1):
+    """Decorator for attaching a code hash to a schedule
+
+    Parameters
+    ----------
+    arg_idx: int
+        index of the argument (expected to be a Schedule) to attach the code
+        hash to
+    """
+    def decorator(func):
+        def wrapper(*args, **kwargs):
+            func(*args, **kwargs)
+            assert isinstance(args[arg_idx], schedule.Schedule)
+            raw_hash = zlib.crc32(''.join(inspect.getsourcelines(func)[0]).encode())
+            args[arg_idx].code_hash = hex(raw_hash)[2:]
+        return wrapper
+    return decorator
diff --git a/python/tvm/autotvm/task/dispatcher.py b/python/tvm/autotvm/task/dispatcher.py
new file mode 100644
index 000000000000..df118e2c69ad
--- /dev/null
+++ b/python/tvm/autotvm/task/dispatcher.py
@@ -0,0 +1,139 @@
+"""
+Template dispatcher module.
+
+A dispatcher is a function that can contains multiple behaviors.
+Its specific behavior is can be controlled by DispatchContext.
+
+DispatchContext is used in two ways, usually via different implementation
+of the DispatchContext base class.
+
+- During search, we can use it to pass the current proposal from tuner.
+- During evaluation, we can use it to set pick the best policy.
+"""
+from __future__ import absolute_import as _abs
+
+from decorator import decorate
+
+from tvm import target as _target
+
+class DispatchContext(object):
+    """
+    Base class of dispatch context.
+
+    DispatchContext enables the target and workload
+    specific dispatch mechanism for templates.
+    """
+    current = None
+
+    def query(self, target, workload):
+        """
+        Query the context to get the specific implementation.
+
+        Parameters
+        ----------
+        target: Target
+            The current target
+        workload : Workload
+            The current workload.
+
+        Returns
+        -------
+        cfg : ConfigSpace
+            The specific configuration.
+        """
+        raise NotImplementedError()
+
+    def __enter__(self):
+        self._old_ctx = DispatchContext.current
+        DispatchContext.current = self
+        return self
+
+    def __exit__(self, ptype, value, trace):
+        DispatchContext.current = self._old_ctx
+
+
+class ApplyConfig(DispatchContext):
+    """Apply a specific config entity during query.
+
+    Parameters
+    ----------
+    config : ConfigSpace or ConfigEntity
+        The specific configuration we care about.
+    """
+    def __init__(self, config):
+        super(ApplyConfig, self).__init__()
+        self._config = config
+        self.workload = None
+
+    def query(self, target, workload):
+        """Override query"""
+        self.workload = workload
+        return self._config
+
+
+def dispatcher(fworkload):
+    """Wrap a workload dispatcher function.
+
+    Parameters
+    ----------
+    fworkload : function
+        The workload extraction function from arguments.
+
+    Returns
+    -------
+    fdispatcher : function
+        A wrapped dispatcher function, which will
+        dispatch based on DispatchContext and
+        the current workload.
+    """
+    dispatch_dict = {}
+    func_name = fworkload.__name__
+
+    def register(key, func=None, override=False):
+        """Register template function.
+
+        Parameters
+        ----------
+        key : str or List of str
+            The template key to identify the template
+            under this dispatcher.
+        func : function
+            The function to be registered.
+            The first argument of the function is always
+            cfg returned by DispatchContext,
+            the rest arguments are the same as the fworkload.
+        override : bool
+            Whether override existing registration.
+
+        Returns
+        -------
+        The register function if necessary.
+        """
+        if isinstance(key, str):
+            key = [key]
+
+        def _do_reg(myf):
+            for x in key:
+                if x in dispatch_dict and not override:
+                    raise ValueError(
+                        "Key %s is already registered for %s" % (x, func_name))
+                dispatch_dict[x] = myf
+            return myf
+
+        if func:
+            return _do_reg(func)
+        return _do_reg
+
+    def dispatch_func(func, *args, **kwargs):
+        """The wrapped dispatch function"""
+        tgt = _target.current_target()
+        context = DispatchContext.current
+        if context is None:
+            raise RuntimeError("DispatchContext is not initialized")
+        workload = func(*args, **kwargs)
+        cfg = context.query(tgt, workload)
+        return dispatch_dict[cfg.template_key](cfg, *args, **kwargs)
+
+    fdecorate = decorate(fworkload, dispatch_func)
+    fdecorate.register = register
+    return fdecorate
diff --git a/python/tvm/autotvm/task/space.py b/python/tvm/autotvm/task/space.py
new file mode 100644
index 000000000000..dd0efdc61635
--- /dev/null
+++ b/python/tvm/autotvm/task/space.py
@@ -0,0 +1,886 @@
+# pylint: disable=too-few-public-methods,invalid-name,unused-argument,arguments-differ
+# pylint: disable=consider-using-enumerate
+"""
+Template configuration space.
+
+Each template function can be parametrized by a ConfigSpace.
+The space is declared when we invoke the template function with ConfigSpace.
+During evaluation, we pass in a ConfigEntity, which contains a specific
+entity in the space. This entity contains deterministic parameters.
+"""
+from __future__ import absolute_import as _abs
+
+import itertools
+import functools
+import math
+from collections import namedtuple, OrderedDict
+import numpy as np
+
+from tvm import schedule, thread_axis
+from tvm.autotvm.util import get_const_int
+
+Axis = namedtuple('Axis', ['space', 'index'])
+
+
+class InstantiationError(ValueError):
+    """Actively detected error in instantiating a template with a config,
+     raised by cfg.raise_error
+     e.g. too many unrolling, too many threads in a block
+    """
+    pass
+
+
+class TransformSpace(object):
+    """Base class for transform space
+    TransformSpace is the node in the computation graph of axes
+
+    Note
+    ----
+    We can regard our schedule code as a transformation graph of axes.
+    Starting from raw axes in the definition of tvm.compute, we can transform these axes
+    by some operators. The operator includes 'split', 'reorder' and 'annotate'.
+    Each operator has some tunable parameters (e.g. the split factor).
+    Then the tuning process is just to find good parameters of these op.
+
+    So the all the combinations of the parameters of these op forms our search space.
+
+    Naming convention:
+    We call the set of all possible values as XXXSpace. (XXX can be Split, Reorder, Config ...)
+    We call a specific entity in a space as XXXEntity.
+    """
+    def __init__(self):
+        self.ins = []
+        self.num_output = 0
+        self.entities = []
+
+    def __len__(self):
+        return len(self.entities)
+
+    def __getitem__(self, index):
+        """Get an entity of the space by index
+
+        Parameters
+        ----------
+        index: int
+
+        Returns
+        -------
+        transform entity
+        """
+        return self.entities[index]
+
+    @staticmethod
+    def get_num_output():
+        """get number of output axes after this transform
+
+        Returns
+        -------
+        n: int
+            number of output axes
+        """
+        return 0
+
+
+class VirtualAxis(TransformSpace):
+    """Axis placeholder in template
+
+    Parameters
+    ----------
+    var: int or tvm.schedule.IterVar
+        If is int, return a virtual axis whose length is the provided argument.
+        If is IterVar, return a virtual axis whose length is extracted from
+                       the IterVar's extent domain.
+    name: str
+    """
+    name_ct = 0
+
+    def __init__(self, var, name=None):
+        super(VirtualAxis, self).__init__()
+        self.num_output = 1
+
+        if name is None:
+            name = 'axis_%d' % VirtualAxis.name_ct
+            VirtualAxis.name_ct += 1
+
+        self.name = name
+        if isinstance(var, int):
+            self.length = var
+        elif isinstance(var, schedule.IterVar):
+            self.name = var.var.name
+            if var.dom is None:
+                self.length = -1
+            else:
+                self.length = get_const_int(var.dom.extent)
+        elif isinstance(var, VirtualAxis):
+            self.length = var.length
+        else:
+            raise RuntimeError("Invalid type of axis")
+
+    @staticmethod
+    def get_num_output(var, name=None):
+        return 1
+
+    def __repr__(self):
+        return "vaxis(%s)" % self.name
+
+
+def get_factors(n):
+    """return all factors of an integer
+
+    Parameters
+    ----------
+    n: int
+        integer to factorize
+
+    Returns
+    -------
+    factors: list
+        List of all factors
+    """
+    step = 2 if n % 2 else 1
+    ret = list(set(
+        functools.reduce(
+            list.__add__, ([i, n//i] for i in range(1, int(math.sqrt(n)) + 1, step)
+                           if n % i == 0))))
+    ret.sort()
+    return ret
+
+
+class SplitSpace(TransformSpace):
+    """Split an axis for several times"""
+    def __init__(self, axes, policy, **kwargs):
+        super(SplitSpace, self).__init__()
+        axis = axes[0]
+
+        self.policy = policy
+        self.entities = []
+
+        if policy == 'all':
+            num_outputs = kwargs["num_outputs"]
+            max_factor = kwargs.get("max_factor", 1 << 31)
+            fil = kwargs.get("filter", lambda x: True)
+
+            length = axis.length
+            factors = get_factors(length)
+            factors = [x for x in factors if x <= max_factor]
+            # copy factors for every level
+            self.product = length
+            self.num_outputs = num_outputs
+            self.factors = [factors] * (num_outputs-1)
+            self._generate_space(0, [None] * (num_outputs - 1))
+            self.entities = list(filter(fil, self.entities))
+            self.num_output = num_outputs
+        elif policy == 'candidate':
+            self.product = axis.length
+            self.num_outputs = kwargs["num_outputs"]
+            for size in kwargs["candidate"]:
+                assert len(size) == self.num_outputs
+                # assert np.prod(size) == self.product
+                self.entities.append(SplitEntity(size))
+            self.num_output = self.num_outputs
+        else:
+            raise RuntimeError("Invalid policy: " + policy)
+
+    def _generate_space(self, now, tmp_stack):
+        """Generate space by DFS"""
+        if now == self.num_outputs - 1:
+            if self.product % np.prod(tmp_stack) == 0:
+                first = int(self.product // int(np.prod(tmp_stack)))
+                self.entities.append(SplitEntity([first] + tmp_stack[::-1]))
+        else:
+            for factor in self.factors[now]:
+                tmp_stack[now] = factor
+                self._generate_space(now + 1, tmp_stack)
+
+    @staticmethod
+    def get_num_output(axes, policy, **kwargs):
+        return kwargs["num_outputs"]
+
+    def __repr__(self):
+        return ("Split(policy=%s, product=%d, num_outputs=%d) len=%d" %
+                (self.policy, self.product, self.num_outputs, len(self)))
+
+
+class SplitEntity(object):
+    """
+    A split operation with detailed parameters
+    that can apply to an axis
+
+    Parameters
+    ----------
+    size: Array of int
+        the size of every axis after split
+        e.g. an axis of extent 128, we split it into 3 axes, a possible
+             size is [4, 4, 8] (4x4x8 = 128)
+    """
+    def __init__(self, size):
+        self.size = size
+
+    def apply(self, sch, op, axis):
+        """Apply split to an axis
+
+        Parameters
+        ----------
+        sch: tvm.schedule.Schedule
+            The tvm schedule
+        op: tvm.tensor.Operation
+            The stage to be applied
+        axis: tvm.schedule.IterVar
+            axis to split
+
+        Returns
+        -------
+        axes : list of Axis
+            The transformed axes.
+        """
+        ret = []
+        for i in range(1, len(self.size)):
+            ax0, ax1 = sch[op].split(axis, int(np.prod(self.size[i:])))
+            ret.append(ax0)
+            axis = ax1
+        return ret + [axis]
+
+    def __repr__(self):
+        return str(self.size)
+
+
+class ReorderSpace(TransformSpace):
+    """The parameter space for ordering an array of axes"""
+    def __init__(self, axes, policy, **kwargs):
+        super(ReorderSpace, self).__init__()
+        self.ins = axes
+        self.policy = policy
+        self.num_output = len(axes)
+
+        if policy == 'identity':
+            self.entities = [ReorderEntity(range(len(axes)))]
+        elif policy == 'all':
+            self.entities = [
+                ReorderEntity(x) for x in itertools.permutations(range(len(axes)))]
+        elif policy == 'interval_all':
+            begin, end = kwargs['interval']
+            sub_space = list(itertools.permutations(range(begin, end)))
+            prefix, suffix = tuple(range(begin)), tuple(range(end, len(axes)))
+            self.entities = [ReorderEntity(prefix + x + suffix) for x in sub_space]
+        elif policy == 'candidate':
+            candidate = kwargs["candidate"]
+            for can in candidate:
+                perm = [axes.index(x) for x in can]
+                self.entities.append(ReorderEntity(perm))
+        elif policy == 'interleave':
+            spatial, reduce = kwargs['spatial'], kwargs['reduce']
+
+            spatial = [[axes.index(x) for x in ch] for ch in spatial]
+            reduce = [[axes.index(x) for x in ch] for ch in reduce]
+
+            outer_merged = self._merge_chain([x[:-1] for x in spatial])
+            inner_merged = self._merge_chain([x[-1:] for x in spatial] + reduce)
+
+            for o in outer_merged:
+                for i in inner_merged:
+                    self.entities.append(ReorderEntity(o + i))
+        elif policy == 'interleave_cuda':
+            spatial, reduce = kwargs['spatial'], kwargs['reduce']
+
+            spatial = [[axes.index(x) for x in ch] for ch in spatial]
+            reduce = [[axes.index(x) for x in ch] for ch in reduce]
+
+            outer_merged = self._merge_chain([x[:-1] for x in spatial])
+            reduce_merged = self._merge_chain(reduce)
+            inner_merged = [x[-1] for x in spatial]
+
+            for o in outer_merged:
+                for r in reduce_merged:
+                    self.entities.append(ReorderEntity(o + r + inner_merged))
+        else:
+            raise RuntimeError("Invalid policy: " + policy)
+
+    @staticmethod
+    def get_num_output(axes, policy, **kwargs):
+        return len(axes)
+
+    def __repr__(self):
+        return "Reorder(policy=%s) len=%d" % (self.policy, len(self))
+
+    def _merge_chain(self, chains):
+        """generate all combinations of merge some chains"""
+        merged = []
+        tmp_pt = [0] * len(chains)
+        tmp_stack = []
+
+        size = np.sum([len(x) for x in chains])
+        self._merge_dfs(chains, size, tmp_pt, tmp_stack, merged)
+        return merged
+
+    def _merge_dfs(self, chains, size, tmp_pt, tmp_stack, merged):
+        if np.sum(tmp_pt) == size:
+            merged.append(list(tmp_stack))
+            return
+        else:
+            for i in range(len(chains)):
+                # use i == np.argmax(....) here to take spatial order into consideration
+                # if we don't want to consider spatial order, we can use tmp_pt[i] == np.max(....)
+                if (tmp_pt[i] < len(chains[i]) and
+                        (i == np.argmax([len(chains[x]) - tmp_pt[x] for x in range(len(chains))]))):
+                    tmp_stack.append(chains[i][tmp_pt[i]])
+                    tmp_pt[i] += 1
+                    self._merge_dfs(chains, size, tmp_pt, tmp_stack, merged)
+                    tmp_pt[i] -= 1
+                    tmp_stack.pop()
+
+
+class ReorderEntity(object):
+    """A reorder operation with detailed parameters that can apply to axes
+
+    Parameters
+    ----------
+    perm: Array of int
+        define the permutation
+    """
+    def __init__(self, perm):
+        self.perm = perm
+
+    def apply(self, sch, op, axes):
+        """Apply reorder to an array of axes
+
+        Parameters
+        ----------
+        sch: tvm.schedule.Schedule
+            The tvm schedule
+        op: tvm.tensor.Operation
+            The stage to be applied
+        axis: tvm.schedule.IterVar
+            axis to split
+
+        Returns
+        -------
+        axes : list of Axis
+            The transformed axes.
+        """
+        if len(axes) == len(self.perm):
+            new_order = [axes[i] for i in self.perm]
+        else:
+            new_order = [axes[i] for i in self.perm if i < len(axes)]
+        sch[op].reorder(*new_order)
+        return new_order
+
+    def __repr__(self):
+        return str(self.perm)
+
+
+class AnnotateSpace(TransformSpace):
+    """The parameter space for annotating an array of axes"""
+    def __init__(self, axes, policy, **kwargs):
+        super(AnnotateSpace, self).__init__()
+
+        self.ins = axes
+        self.policy = policy
+        self.num_output = len(axes)
+
+        if policy == 'bind_gpu':
+            self.num_axis = len(axes)
+            if self.num_axis >= 6:
+                self.entities.append(AnnotateEntity(
+                    ['fuse'] * (self.num_axis - 6) +
+                    ['blockIdx.z', 'blockIdx.y', 'blockIdx.x',
+                     'threadIdx.z', 'threadIdx.y', 'threadIdx.x']))
+            elif self.num_axis >= 4:
+                self.entities.append(AnnotateEntity(
+                    ['fuse'] * (self.num_axis - 4) +
+                    ['blockIdx.y', 'blockIdx.x',
+                     'threadIdx.y', 'threadIdx.x']))
+            elif self.num_axis >= 2:
+                self.entities.append(AnnotateEntity(
+                    ['fuse'] * (self.num_axis - 2) +
+                    ['blockIdx.x', 'threadIdx.x']))
+            else:
+                raise RuntimeError("Unhandled case in bind_gpu")
+        elif policy == 'bind_gpu_virtual':
+            self.num_axis = len(axes)
+            if self.num_axis >= 9:
+                self.entities.append(AnnotateEntity(
+                    ['fuse'] * (self.num_axis - 9) +
+                    ['blockIdx.z', 'blockIdx.y', 'blockIdx.x',
+                     'vthread', 'vthread', 'vthread',
+                     'threadIdx.z', 'threadIdx.y', 'threadIdx.x']))
+            elif self.num_axis >= 6:
+                self.entities.append(AnnotateEntity(
+                    ['fuse'] * (self.num_axis - 6) +
+                    ['blockIdx.y', 'blockIdx.x',
+                     'vthread', 'vthread',
+                     'threadIdx.y', 'threadIdx.x']))
+            elif self.num_axis >= 3:
+                self.entities.append(AnnotateEntity(
+                    ['fuse'] * (self.num_axis - 3) +
+                    ['blockIdx.x', 'vthread', 'threadIdx.x']))
+            else:
+                raise RuntimeError("Unhandled case in bind_gpu")
+        elif policy == 'locate_cache':
+            self.num_axis = len(axes)
+            num_anchor = kwargs["num_anchor"]
+            self.anns = list(itertools.combinations(np.arange(self.num_axis), num_anchor))
+            self.entities = [AnnotateEntity(x) for x in self.anns]
+        else:  # none, vec, unroll, try_vec, try_unroll, try_vec_unroll, ...
+            anns = policy.replace('try', 'none').split('_')
+
+            for ann in anns:
+                if ann not in ['none', 'unroll', 'vec']:
+                    raise RuntimeError("Invalid policy: " + policy)
+
+            self.num_axis = len(axes)
+            self.anns = [anns] * self.num_axis
+            self._generate_space(0, [""] * self.num_axis)
+
+    def _generate_space(self, now, tmp_stack):
+        """Generate space by DFS"""
+        if now == self.num_axis:
+            # only vectorize inner most dimension
+            vec_ct = tmp_stack.count('vec')
+            if vec_ct == 0 or vec_ct == 1:
+                self.entities.append(AnnotateEntity(list(tmp_stack)))
+        else:
+            for ann in self.anns[now]:
+                tmp_stack[now] = ann
+                self._generate_space(now + 1, tmp_stack)
+
+    @staticmethod
+    def get_num_output(axes, policy, **kwargs):
+        return len(axes)
+
+    def __repr__(self):
+        return "Annotate(policy=%s) len=%d" % (self.policy, len(self))
+
+
+class AnnotateEntity(object):
+    """An annotation operation with detailed parameters that can apply to axes
+
+    Parameters
+    ----------
+    anns: Array of string
+        The annotations of axes
+    """
+    def __init__(self, anns):
+        self.anns = anns
+
+    def apply(self, sch, op, axes, axis_lens=None,
+              max_unroll=None, vec_size=None, cfg=None, source=None):
+        """Apply annotation to an array of axes
+
+        Parameters
+        ----------
+        sch: tvm.schedule.Schedule
+            The tvm schedule
+        op: tvm.tensor.Operation
+            The stage to be applied
+        axes: Array of tvm.schedule.IterVar
+            axis to split
+        axis_lens: Array of int, optional
+            the length of axes
+        max_unroll: int, optional
+            maximum unroll step
+        vec_size: Array of int, optional
+            valid vector lanes for vectorization
+        cfg: ConfigEntity, optional
+            cfg for recording error
+        source: Array of Array tensor, optional
+            source tensor for attaching cache
+
+        Returns
+        -------
+        axes : list of tvm.schedule.IterVar
+            The transformed axes
+        """
+        if source is not None:  # special case : attach cache_read/cache_write
+            for src, to in zip(source, self.anns):
+                for t in src:
+                    sch[t].compute_at(sch[op], axes[to])
+        else:  # other cases
+            for i, ann in enumerate(self.anns):
+                if ann == 'none':
+                    pass
+                elif ann == 'unroll':
+                    if max_unroll and axis_lens[i] > max_unroll:
+                        cfg.raise_error("Too large factor for unrolling")
+                    sch[op].unroll(axes[i])
+                elif ann == 'vec':
+                    if vec_size and axis_lens[i] not in vec_size:
+                        cfg.raise_error("Wrong size of lanes in vectorization")
+                    sch[op].vectorize(axes[i])
+                elif ann == 'blockIdx.x':
+                    sch[op].bind(axes[i], thread_axis('blockIdx.x'))
+                elif ann == 'blockIdx.y':
+                    sch[op].bind(axes[i], thread_axis('blockIdx.y'))
+                elif ann == 'blockIdx.z':
+                    sch[op].bind(axes[i], thread_axis('blockIdx.z'))
+                elif ann == 'threadIdx.x':
+                    sch[op].bind(axes[i], thread_axis('threadIdx.x'))
+                elif ann == 'threadIdx.y':
+                    sch[op].bind(axes[i], thread_axis('threadIdx.y'))
+                elif ann == 'threadIdx.z':
+                    sch[op].bind(axes[i], thread_axis('threadIdx.z'))
+                elif ann == 'vthread':
+                    sch[op].bind(axes[i], thread_axis("vthread"))
+                elif ann == 'fuse':
+                    assert i < len(axes) - 1
+                    axes[i+1] = sch[op].fuse(axes[i], axes[i+1])
+                else:
+                    raise RuntimeError("Invalid annotation " + ann)
+        return axes
+
+    def __repr__(self):
+        return str(self.anns)
+
+
+class OtherOptionSpace(TransformSpace):
+    """The parameter space for general option"""
+    def __init__(self, axes, policy, **kwargs):
+        super(OtherOptionSpace, self).__init__()
+
+        candidate = kwargs["candidate"]
+        self.entities = [OtherOptionEntity(x) for x in candidate]
+
+    @staticmethod
+    def get_num_output(axes, policy, **kwargs):
+        return 0
+
+    def __repr__(self):
+        return "OtherOption(%s) len=%d" % (self.entities, len(self))
+
+
+class OtherOptionEntity(object):
+    """The parameter entity for general option, with a detailed value"""
+    def __init__(self, val):
+        self.val = val
+
+    def __repr__(self):
+        return str(self.val)
+
+
+class ConfigSpace(object):
+    """The configuration space of a schedule. Pass it as config in template to
+       collect transformation space and build transform graph of axes
+    """
+    def __init__(self):
+        # private dict to provide sugar
+        self.space_map = OrderedDict()  # name -> space
+        self._collect = True
+        self._length = None
+        self._entity_map = OrderedDict()
+        self._constraints = []
+        self.errors = []
+        self.template_key = None
+        self.code_hash = None
+        self.flop = 0
+
+    @staticmethod
+    def axis(var):
+        """get a virtual axis (axis placeholder)
+
+        Parameters
+        ----------
+        var: int or tvm.schedule.IterVar
+            If is int, return an axis whose length is the provided argument.
+            If is IterVar, return an axis whose length is extracted from the
+                           IterVar's extent domain.
+        """
+        return VirtualAxis(var)
+
+    reduce_axis = axis
+
+    def define_split(self, name, axis, policy='all', **kwargs):
+        """Define a new tunable knob which splits an axis into a list of axes
+
+        Parameters
+        ----------
+        name: str
+            name to index the entity of this space
+        axis: tvm.schedule.IterVar
+            axis to split
+        policy: str
+            name of policy.
+            If is 'all', the tuner will try all divisible factors.
+            If is 'candidate', try listed candidate.
+        kwargs: dict
+            extra arguments for policy
+        """
+        axes = [axis]
+        return self._add_new_transform(SplitSpace, name, axes, policy, **kwargs)
+
+    def define_reorder(self, name, axes, policy, **kwargs):
+        """Define a new tunable knob which reorders a list of axes
+
+        Parameters
+        ----------
+        name: str
+            name to index the entity of this space
+        axes: Array of tvm.schedule.IterVar
+            axes to reorder
+        policy: str
+            name of policy
+            If is 'identity', do an identity permutation.
+            If is 'all', try all permutations.
+            If is 'interval_all', try all permutations of an interval of axes.
+            If is 'candidate', try listed candidate.
+            If is 'interleave', interleave chains of spatial axes and chains of reduction axes.
+        kwargs: dict
+            extra arguments for policy
+        """
+        return self._add_new_transform(ReorderSpace, name, axes, policy, **kwargs)
+
+    def define_annotate(self, name, axes, policy, **kwargs):
+        """Define a new tunable knob which annotates a list of axes
+
+        Parameters
+        ----------
+        name: str
+            name to index the entity of this space
+        axes: Array of tvm.schedule.IterVar
+            axes to annotate
+        policy: str
+            name of policy
+            If is 'unroll', unroll the axes.
+            If is 'try_unroll', try to unroll the axes.
+            If is 'try_unroll_vec', try to unroll or vectorize the axes.
+            If is 'bind_gpu', bind the first few axes to gpu threads.
+            If is 'locate_cache', choose n axes to attach shared/local cache.
+        kwargs: dict
+            extra arguments for policy
+        """
+        return self._add_new_transform(AnnotateSpace, name, axes, policy, **kwargs)
+
+    def define_knob(self, name, candidate):
+        """Define a tunable knob with a list of candidates
+
+        Parameters
+        ----------
+        name: str
+            name key of that option
+        candidate: list
+            list of candidates
+        """
+        return self._add_new_transform(OtherOptionSpace, name, [], None, candidate=candidate)
+
+    def add_flop(self, flop):
+        """Add float operation statistics for this tuning task
+
+        Parameters
+        ---------
+        flop: int or float
+            number of float operations
+        """
+        self.flop += flop
+
+    def raise_error(self, msg):
+        """register error in config
+        Using this to actively detect error when scheudling.
+        Otherwise these error will occur during runtime, which
+        will cost more time.
+
+        Parameters
+        ----------
+        msg: str
+        """
+        self.errors.append(msg)
+
+    def valid(self):
+        """Check whether the config meets all the constraints
+        Note: This check should be called after instantiation of task,
+              because the ConfigEntity/ConfigSpace collects errors during instantiation
+
+        Returns
+        -------
+        valid: bool
+            whether the config meets all the constraints
+        """
+        return not bool(self.errors)
+
+    def _add_new_transform(self, space_class, name, axes, policy, **kwargs):
+        """Add a new transform space in template"""
+        if self._collect:
+            # convert schedule axis to space definition axis
+            axes = [x if isinstance(x, (VirtualAxis, Axis)) else self.axis(x) for x in axes]
+
+            # add subspace (knob)
+            space = space_class(axes, policy, **kwargs)
+            self.space_map[name] = space
+            self._entity_map[name] = space[0]
+            return [Axis(space, i) for i in range(space.num_output)]
+        return [Axis(None, i) for i in range(space_class.get_num_output(axes, policy, **kwargs))]
+
+    def __len__(self):
+        if self._length is None:
+            self._length = int(np.prod([len(x) for x in self.space_map.values()]))
+        return self._length
+
+    def get(self, index):
+        """Get a config entity with detailed parameters from this space
+
+        Parameters
+        ----------
+        index: int
+            index in the space
+        """
+        entities = OrderedDict()
+        t = index
+        for name, space in self.space_map.items():
+            entities[name] = space[t % len(space)]
+            t //= len(space)
+        ret = ConfigEntity(index, self.code_hash, self.template_key, entities, self._constraints)
+        return ret
+
+    def __iter__(self):
+        return self._entity_map.__iter__()
+
+    def __getitem__(self, name):
+        """get the transform entity(knob) of this entity by name
+           do not use this to get a ConfigEntity of this space (should use ConfigSpace.get instead)
+
+        Parameters
+        ----------
+        name: str
+            name of the transform
+        """
+        return self._entity_map[name]
+
+    def __repr__(self):
+        res = "ConfigSpace (len=%d, space_map=\n" % len(self)
+        for i, (name, space) in enumerate(self.space_map.items()):
+            res += "  %2d %s: %s\n" % (i, name, space)
+        return res + ")"
+
+
+_ann_to_number = {
+    'none': 0, 'vec': 1, 'unroll': 2,
+    'blockIdx.x': 3, 'blockIdx.y': 4, 'blockIdx.z': 5,
+    'threadIdx.x': 6, 'threadIdx.y': 7, 'threadIdx.z': 8,
+    'vthread': 9, 'fuse': 10
+}
+
+class ConfigEntity(ConfigSpace):
+    """A configuration with detailed parameters
+
+    Parameters
+    ----------
+    index: int
+        index of this config in space
+    code_hash: str
+        hash of schedule code
+    template_key : str
+        The specific template key
+    entity_map: dict
+        map name to transform entity
+    constraints : list
+        List of constraints
+    """
+    def __init__(self, index, code_hash, template_key, entity_map, constraints):
+        super(ConfigEntity, self).__init__()
+        self.index = index
+        self.template_key = template_key
+        self._collect = False
+        self._entity_map = entity_map
+        self._space_map = None
+        self._constraints = constraints
+        self.code_hash = code_hash
+
+    def get_flatten_feature(self):
+        """ flatten entities to a numerical one-dimensional feature vector
+
+        Returns
+        -------
+        fea: np.array
+            one dimensional float32 array
+        """
+        fea = []
+        for _, v in self._entity_map.items():
+            if isinstance(v, SplitEntity):
+                fea.extend(v.size)
+            elif isinstance(v, ReorderEntity):
+                # use a naive way: directly copy the permutation
+                fea.extend(v.perm)
+            elif isinstance(v, AnnotateEntity):
+                # one-hot encoding
+                for ann in v.anns:
+                    tmp = [0] * len(_ann_to_number)
+                    tmp[_ann_to_number[ann]] = 1
+                    fea.extend(tmp)
+            elif isinstance(v, OtherOptionEntity):
+                fea.append(v.val)
+        return np.array(fea, dtype=np.float32)
+
+    def get_other_option(self):
+        """
+        Returns
+        -------
+        other_option: dict
+            other tunable parameters (tunable parameters defined by `cfg.define_knob`)
+        """
+        return {x: x.val for x in self._entity_map.values() if isinstance(x, OtherOptionEntity)}
+
+    def to_json_dict(self):
+        """convert to a json serializable dictionary
+
+        Return
+        ------
+        json_dict: dict
+            a json serializable dictionary
+        """
+        ret = {}
+        ret['i'] = int(self.index)
+        ret['t'] = self.template_key
+        ret['c'] = self.code_hash
+        entity_map = []
+        for k, v in self._entity_map.items():
+            if isinstance(v, SplitEntity):
+                entity_map.append((k, 'sp', v.size))
+            elif isinstance(v, ReorderEntity):
+                entity_map.append((k, 're', v.perm))
+            elif isinstance(v, AnnotateEntity):
+                entity_map.append((k, 'an', v.anns))
+            elif isinstance(v, OtherOptionEntity):
+                entity_map.append((k, 'ot', v.val))
+            else:
+                raise RuntimeError("Invalid entity instance: " + v)
+        ret['e'] = entity_map
+        return ret
+
+    @staticmethod
+    def from_json_dict(json_dict):
+        """Build a ConfigEntity from json serializable dictionary
+
+        Parameters
+        ----------
+        json_dict: dict
+            Json serializable dictionary. This should be the return value
+            of :any:`to_json_dict`.
+
+        Returns
+        -------
+        config: ConfigEntity
+            The corresponding config object
+
+        """
+        index = json_dict["i"]
+        code_hash = json_dict["c"]
+        template_key = json_dict["t"]
+        constraints = []
+        entity_map = OrderedDict()
+
+        for item in json_dict["e"]:
+            key, knob_type, knob_args = item
+            if knob_type == 'sp':
+                entity = SplitEntity(knob_args)
+            elif knob_type == 're':
+                entity = ReorderEntity(knob_args)
+            elif knob_type == 'an':
+                entity = AnnotateEntity(knob_args)
+            elif knob_type == 'ot':
+                entity = OtherOptionEntity(knob_args)
+            else:
+                raise RuntimeError("Invalid config knob type: " + knob_type)
+            entity_map[str(key)] = entity
+
+        return ConfigEntity(index, code_hash, template_key, entity_map, constraints)
+
+    def __repr__(self):
+        return "%s,%s,%s,%d" % (str(self._entity_map)[12:-1], self.template_key,
+                                self.code_hash, self.index)
diff --git a/python/tvm/autotvm/task/task.py b/python/tvm/autotvm/task/task.py
new file mode 100644
index 000000000000..3b9a3291e6af
--- /dev/null
+++ b/python/tvm/autotvm/task/task.py
@@ -0,0 +1,359 @@
+# pylint: disable=unused-variable
+"""Definition of task function.
+
+Task can be constructed from tuple of func, args, and kwargs.
+func is a state-less function, or a string that
+registers the standard task.
+"""
+
+import numpy as np
+
+from ... import tensor, expr, container, target as _target
+
+from ..util import get_const_int, get_const_tuple, get_func_name
+from .dispatcher import DispatchContext, ApplyConfig, dispatcher
+from .space import ConfigSpace
+
+def _raise_error(*args, **kwargs):  # pylint: disable=unused-argument
+    raise RuntimeError("The function of this task is not found. Possibly the function "
+                       "of this task is registered in another python file "
+                       "which is not imported in this run")
+
+class Task(object):
+    """A Tunable Task
+
+    Parameters
+    ----------
+    name: str
+        The name of the task.
+    args: Tuple
+        Positional argument of func
+    """
+    def __init__(self, name, args):
+        self.name = name
+        self.args = args
+        self.kwargs = {}  # currently unused
+
+        # init null config space
+        self.config_space = None
+        self.func = TASK_TABLE.get(name, _raise_error)
+
+        # auxiliary info, available after `init_space` is called
+        self.workload = None
+        self.flop = None
+        self.target = None
+        self.target_host = None
+
+    def instantiate(self, config):
+        """Instantiate this task function (template) with a config.
+        Returns corresponding schedule.
+
+        Parameters
+        ----------
+        config: template.ConfigEntity
+            parameter config for this template
+
+        Returns
+        -------
+        sch: tvm.schedule.Schedule
+            The tvm schedule
+        arg_bufs: Array of tvm.tensor.Tensor
+            The input/output buffers
+        """
+        config.flop = 0
+        with ApplyConfig(config):
+            sch, arg_bufs = self.func(*self.args, **self.kwargs)
+        if not self.flop:
+            config.flop = config.flop or compute_flop(sch)
+            self.flop = config.flop
+        return sch, arg_bufs
+
+    def __repr__(self):
+        return "Task(func_name=%s, args=%s, kwargs=%s, workload=%s)" % (
+            self.name, self.args, self.kwargs, self.workload
+        )
+
+TASK_TABLE = {
+}
+
+def register(name, func=None, override=False):
+    """Register a task function.
+
+    Parameters
+    ----------
+    name : str
+        The name to identify the task.
+    func : callable
+        The function to be registered.
+    override : bool
+        Whether override existing registration.
+
+    Returns
+    -------
+    func: callable
+        The registered function
+    """
+    def _do_reg(myf):
+        if name in TASK_TABLE and not override:
+            raise ValueError(
+                "Key %s is already registered" % name)
+        TASK_TABLE[name] = myf
+        return myf
+    if func:
+        return _do_reg(func)
+    return _do_reg
+
+def create(func_name, args, target, target_host=None, template_key=None):
+    """Create a tuning task and initialize its search space
+
+    Parameters
+    ----------
+    func_name : str or callable
+        The task function
+    args : List
+        Positional arguments
+    target : Target
+        The compilation target
+    target_host: Target, optional
+        The compilation target for host side
+
+    Returns
+    -------
+    tsk: Task
+        a task object
+    """
+    if callable(func_name):
+        # register this function if it is not registered before
+        func = func_name
+        func_name = func.func_name if hasattr(func, 'func_name') else func.__name__
+        if func_name in TASK_TABLE:
+            assert func == TASK_TABLE[func_name], "Find name conflict in task registration. " \
+                                                  "Consider to choose another name for this task"
+        else:
+            register(func_name, func=func)
+
+    func = TASK_TABLE[func_name]
+    ret = Task(func_name, args)
+
+    if isinstance(target, str):
+        target = _target.create(target)
+
+    # init config space
+    ret.config_space = ConfigSpace()
+    ret.config_space.template_key = template_key or ""
+
+    ctx = ApplyConfig(ret.config_space)
+    with ctx:
+        with target:
+            sch, _ = func(*args)
+            ret.config_space.code_hash = getattr(sch, 'code_hash', None)
+
+    ret.workload = ctx.workload
+    ret.flop = ret.config_space.flop or compute_flop(sch)
+    ret.target = target
+    ret.target_host = target_host
+
+    return ret
+
+def args_to_workload(x):
+    """Convert argument list to hashable workload tuple.
+    This function will convert list to tuple, tvm node to python value and
+    flatten tvm.tensor.Tensor to a tuple
+
+    Parameters
+    ----------
+    x: primitive hashable types or tensor.Tensor
+        The original value
+
+    Returns
+    -------
+    ret: hashable
+        The hashable value
+    """
+    if isinstance(x, tensor.Tensor):
+        return get_const_tuple(x.shape) + (x.dtype, )
+    elif isinstance(x, (tuple, list, container.Array)):
+        return tuple([args_to_workload(a) for a in x])
+    elif isinstance(x, (str, int, float, np.int, np.float)):
+        return x
+    elif isinstance(x, (expr.StringImm, expr.IntImm, expr.FloatImm)):
+        return x.value
+    elif x is None:
+        return None
+    else:
+        raise RuntimeError('Do not support type "%s" in argument. Consider to use'
+                           'primitive types only' % type(x))
+
+def template(func):
+    """
+    Decorate a function as a tunable schedule template
+
+    Parameters
+    ----------
+    func: callable
+        A callable template function.
+        Its argument should be hashable values.
+        Its return value should be a Tuple(Schedule, Array of Tensor)
+
+    Returns
+    -------
+    func: callable
+        The decorated function
+
+    Examples
+    --------
+    The following code is a tunable template for a blocked matrix multiplication
+
+    .. code-block:: python
+
+        @autotvm.template
+        def matmul(N, L, M, dtype):
+            A = tvm.placeholder((N, L), name='A', dtype=dtype)
+            B = tvm.placeholder((L, M), name='B', dtype=dtype)
+
+            k = tvm.reduce_axis((0, L), name='k')
+            C = tvm.compute((N, M), lambda i, j: tvm.sum(A[i, k] * B[k, j], axis=k), name='C')
+            s = tvm.create_schedule(C.op)
+
+            # schedule
+            y, x = s[C].op.axis
+            k = s[C].op.reduce_axis[0]
+
+            ##### define space begin #####
+            cfg = autotvm.get_config()
+            cfg.define_split("tile_y", y, num_outputs=2)
+            cfg.define_split("tile_x", x, num_outputs=2)
+            ##### define space end #####
+
+            # schedule according to config
+            yo, yi = cfg["tile_y"].apply(s, C, y)
+            xo, xi = cfg["tile_x"].apply(s, C, x)
+
+            s[C].reorder(yo, xo, k, yi, xi)
+
+            return s, [A, B, C]
+    """
+    # pylint: disable=unused-variable
+
+    fname = get_func_name(func)
+
+    @register(fname)
+    @dispatcher
+    def config_dispatcher(*args, **kwargs):
+        assert not kwargs, "Do not support kwargs in template function call"
+        return (fname, ) + args_to_workload(args)
+
+    @config_dispatcher.register("")
+    def template_call(cfg, *args, **kwargs):
+        assert not kwargs, "Do not support kwargs in template function call"
+        with ApplyConfig(cfg):
+            return func(*args, **kwargs)
+
+    config_dispatcher.func_name = fname
+    return config_dispatcher
+
+def get_config():
+    """Get current config object
+
+    Returns
+    -------
+    cfg: ConfigSpace or ConfigEntity
+        The current config
+    """
+    return DispatchContext.current.query(None, None)
+
+class FlopCalculationError(RuntimeError):
+    """Error happens when estimating FLOP for a compute op"""
+    pass
+
+def compute_flop(sch):
+    """Calculate number of FLOP (floating number operations) of the compute ops in a schedule
+
+    Parameters
+    ----------
+    sch: tvm.schedule.Schedule
+        schedule
+
+    Returns
+    -------
+    flop: int
+        number of FLOP in this schedule
+    """
+    def _prod_length(axes):
+        """compute product of the lengths of a list of axes"""
+        try:
+            num_iter = int(np.prod([get_const_int(axis.dom.extent) for axis in axes]))
+        except ValueError:
+            raise FlopCalculationError("The length of axis is not constant. ")
+        return num_iter
+
+    def _count_flop(exp):
+        """compute flop for a single expression"""
+        if isinstance(exp, expr.Reduce):
+            num_iter = _prod_length(exp.axis)
+            combiner = exp.combiner.result
+            source = exp.source
+            if len(combiner) != 1:
+                raise FlopCalculationError("Found multiple output in the combiner of reduce op")
+            if len(source) != 1:
+                raise FlopCalculationError("Found multiple output in the source of reduce op")
+            return num_iter * (_count_flop(combiner[0]) + _count_flop(source[0]))
+        elif isinstance(exp, (expr.FloatImm, expr.IntImm, expr.UIntImm)):
+            return 0
+        elif isinstance(exp, expr.Cast):
+            return _count_flop(exp.value)
+        elif isinstance(exp, expr.Var):
+            return 0
+        elif isinstance(exp, (expr.Add, expr.Sub, expr.Mul, expr.Div, expr.Mod,
+                              expr.Max, expr.Min,
+                              expr.EQ, expr.NE, expr.LT, expr.LE, expr.GT, expr.GE,
+                              expr.And, expr.Or, expr.Not)):
+            base = 1 if "float" in exp.a.dtype else 0
+
+            if isinstance(exp, expr.Not):  # unary
+                return base + _count_flop(exp.a)
+
+            return base + _count_flop(exp.a) + _count_flop(exp.b)
+        elif isinstance(exp, expr.Select):
+            return _count_flop(exp.condition) + max(_count_flop(exp.true_value),
+                                                    _count_flop(exp.false_value))
+        elif isinstance(exp, expr.Call):
+            return sum([_count_flop(x) for x in exp.args])
+        else:
+            raise FlopCalculationError("Found unsupported operator in the compute expr")
+
+    def traverse(ops):
+        """accumulate flops"""
+        ret = 0
+        for op in ops:
+            if isinstance(op, tensor.ComputeOp):
+                num_element = _prod_length(op.axis)
+
+                body = op.body
+                if len(body) != 1:
+                    raise FlopCalculationError("Found multiple output in the compute")
+                exp = body[0]
+
+                ret += num_element * _count_flop(exp)
+                ret += traverse([sch[t].op for t in op.input_tensors])
+
+            elif isinstance(op, tensor.PlaceholderOp):
+                pass
+            else:
+                raise FlopCalculationError("Only support tvm.compute currently. "
+                                           "Other ops like tvm.scan is not supported")
+        return ret
+
+    try:
+        ret = traverse(sch.outputs)
+    except FlopCalculationError as exc:
+        raise RuntimeError("FLOP estimator fails for this operator. Error msg: "
+                           + str(exc) + ". Please use `cfg.add_flop` to manually set "
+                                        "FLOP for this operator")
+
+    if ret == 0:
+        raise RuntimeError("Cannot find float number operation in this operator. "
+                           "Please use `cfg.add_flop` to manually set "
+                           "FLOP for this operator")
+
+    return ret
diff --git a/python/tvm/autotvm/tuner/__init__.py b/python/tvm/autotvm/tuner/__init__.py
new file mode 100644
index 000000000000..af81442e79f4
--- /dev/null
+++ b/python/tvm/autotvm/tuner/__init__.py
@@ -0,0 +1,14 @@
+"""
+A tuner takes a task as input. It proposes some promising :any:`ConfigEntity`
+in the :any:`ConfigSpace` and measure them on the real hardware. Then it
+proposed the next batch of :any:`ConfigEntity` according to the measure results.
+This tuning loop is repeated.
+"""
+
+from . import callback
+
+from .tuner import Tuner
+
+from .gridsearch_tuner import GridSearchTuner, RandomTuner
+from .ga_tuner import GATuner
+from .xgboost_tuner import XGBTuner
diff --git a/python/tvm/autotvm/tuner/callback.py b/python/tvm/autotvm/tuner/callback.py
new file mode 100644
index 000000000000..f8265ca70fd7
--- /dev/null
+++ b/python/tvm/autotvm/tuner/callback.py
@@ -0,0 +1,112 @@
+# pylint: disable=consider-using-enumerate,invalid-name
+"""Namespace of callback utilities of AutoTVM"""
+
+import numpy as np
+
+from .. import record
+
+def log_to_file(file_out, protocol='json'):
+    """Log the tuning records into file.
+    The rows of the log are stored in the format of autotvm.record.encode.
+
+    Parameters
+    ----------
+    file_out : File or str
+        The file to log to.
+    protocol: str, optional
+        The log protocol. Can be 'json' or 'pickle'
+
+    Returns
+    -------
+    callback : callable
+        Callback function to do the logging.
+    """
+
+    def _callback(_, inputs, results):
+        """Callback implementation"""
+        if isinstance(file_out, str):
+            with open(file_out, "a") as f:
+                for inp, result in zip(inputs, results):
+                    f.write(record.encode(inp, result, protocol) + "\n")
+        else:
+            for inp, result in zip(inputs, results):
+                file_out.write(record.encode(inp, result, protocol) + "\n")
+    return _callback
+
+
+def save_tuner_state(prefix, save_every_sample=100):
+    """Save the state of tuner
+
+    Parameters
+    ----------
+    prefix : srt
+        prefix of the filename to store state
+    save_every_sample: int
+        save the state every x samples
+
+    Returns
+    -------
+    callback : function
+        Callback function to do the auto saving.
+    """
+    def _callback(tuner, inputs, results):
+        for _, __ in zip(inputs, results):
+            try:
+                ct = len(tuner.visited)
+            except AttributeError:
+                ct = 0
+            if ct % save_every_sample == 0:
+                tuner.save_state(prefix + "_%d.state" % ct)
+
+    return _callback
+
+
+def log_to_redis(host="localhost", port=6379, dbn=11):
+    """Record the tuning record to a redis DB.
+
+    Parameters
+    ----------
+    host: str, optional
+        Host address of redis db
+    port: int, optional
+        Port of redis db
+    dbn: int, optional
+        which redis db to use, default 11
+    """
+    # import here so only depend on redis when necessary
+    import redis
+    red = redis.StrictRedis(host=host, port=port, db=dbn)
+
+    def _callback(_, inputs, results):
+        """Callback implementation"""
+        for inp, result in zip(inputs, results):
+            red.set(inp, result)
+    return _callback
+
+class Monitor(object):
+    """A monitor to collect statistic during tuning"""
+    def __init__(self):
+        self.scores = []
+        self.timestamps = []
+
+    def __call__(self, tuner, inputs, results):
+        for inp, res in zip(inputs, results):
+            if res.error_no == 0:
+                flops = inp.task.flop / np.mean(res.costs)
+                self.scores.append(flops)
+            else:
+                self.scores.append(0)
+
+            self.timestamps.append(res.timestamp)
+
+    def reset(self):
+        self.scores = []
+        self.timestamps = []
+
+    def trial_scores(self):
+        """get scores (currently is flops) of all trials"""
+        return np.array(self.scores)
+
+    def trial_timestamps(self):
+        """get wall clock time stamp of all trials"""
+        return np.array(self.timestamps)
diff --git a/python/tvm/autotvm/tuner/cost_model.py b/python/tvm/autotvm/tuner/cost_model.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/python/tvm/autotvm/tuner/ga_tuner.py b/python/tvm/autotvm/tuner/ga_tuner.py
new file mode 100644
index 000000000000..aed39a4e2d10
--- /dev/null
+++ b/python/tvm/autotvm/tuner/ga_tuner.py
@@ -0,0 +1,119 @@
+# pylint: disable=consider-using-enumerate,invalid-name,abstract-method
+
+"""Tuner with genetic algorithm"""
+
+import numpy as np
+
+from .tuner import Tuner
+from .model_based_tuner import knob2point, point2knob
+
+
+class GATuner(Tuner):
+    """Tuner with genetic algorithm.
+    This tuner does not have a cost model so it always run measurement on real machines.
+    This tuner expands the :code:`ConfigEntity` as gene.
+
+    Parameters
+    ----------
+    pop_size: int
+        number of genes in one generation
+    elite_num: int
+        number of elite to keep
+    mutation_prob: float
+        probability of mutation of a knob in a gene
+    """
+    def __init__(self, task, pop_size, elite_num=3, mutation_prob=0.1):
+        super(GATuner, self).__init__(task)
+
+        # algorithm configurations
+        self.pop_size = pop_size
+        self.elite_num = elite_num
+        self.mutation_prob = mutation_prob
+
+        assert elite_num <= pop_size, "The number of elites must be less than population size"
+
+        # space info
+        self.space = task.config_space
+        self.dims = [len(x) for x in self.space.space_map.values()]
+
+        self.visited = set([])
+
+        # current generation
+        self.genes = []
+        self.scores = []
+        self.elites = []
+        self.elite_scores = []
+        self.trial_pt = 0
+
+        # random initialization
+        self.pop_size = min(self.pop_size, len(self.space))
+        for _ in range(self.pop_size):
+            tmp_gene = point2knob(np.random.randint(len(self.space)), self.dims)
+            while knob2point(tmp_gene, self.dims) in self.visited:
+                tmp_gene = point2knob(np.random.randint(len(self.space)), self.dims)
+
+            self.genes.append(tmp_gene)
+            self.visited.add(knob2point(tmp_gene, self.dims))
+
+    def next_batch(self, batch_size):
+        ret = []
+        for _ in range(batch_size):
+            gene = self.genes[self.trial_pt % self.pop_size]
+            self.trial_pt += 1
+            ret.append(self.space.get(knob2point(gene, self.dims)))
+
+        return ret
+
+    def update(self, inputs, results):
+        for inp, res in zip(inputs, results):
+            if res.error_no == 0:
+                y = inp.task.flop / np.mean(res.costs)
+                self.scores.append(y)
+            else:
+                self.scores.append(0)
+
+        if len(self.scores) >= len(self.genes):
+            genes = self.genes + self.elites
+            scores = np.array(self.scores[:len(self.genes)] + self.elite_scores)
+
+            # reserve elite
+            self.elites, self.elite_scores = [], []
+            elite_indexes = np.argpartition(scores, -self.elite_num)[-self.elite_num:]
+            for ind in elite_indexes:
+                self.elites.append(genes[ind])
+                self.elite_scores.append(scores[ind])
+
+            # cross over
+            indices = np.arange(len(genes))
+            scores /= np.max(scores)
+            probs = scores / np.sum(scores)
+            tmp_genes = []
+            for _ in range(self.pop_size):
+                p1, p2 = np.random.choice(indices, size=2, replace=False, p=probs)
+                p1, p2 = genes[p1], genes[p2]
+                point = np.random.randint(len(self.dims))
+                tmp_gene = p1[:point] + p2[point:]
+                tmp_genes.append(tmp_gene)
+
+            # mutation
+            next_genes = []
+            for tmp_gene in tmp_genes:
+                for j, dim in enumerate(self.dims):
+                    if np.random.random() < self.mutation_prob:
+                        tmp_gene[j] = np.random.randint(dim)
+
+                if len(self.visited) < len(self.space):
+                    while knob2point(tmp_gene, self.dims) in self.visited:
+                        j = np.random.randint(len(self.dims))
+                        tmp_gene[j] = np.random.randint(self.dims[j])
+                    next_genes.append(tmp_gene)
+                    self.visited.add(knob2point(tmp_gene, self.dims))
+                else:
+                    break
+
+            self.genes = next_genes
+            self.trial_pt = 0
+            self.scores = []
+
+    def has_next(self):
+        return len(self.visited) - (len(self.genes) - self.trial_pt) < len(self.space)
diff --git a/python/tvm/autotvm/tuner/gridsearch_tuner.py b/python/tvm/autotvm/tuner/gridsearch_tuner.py
new file mode 100644
index 000000000000..cb1a0832b506
--- /dev/null
+++ b/python/tvm/autotvm/tuner/gridsearch_tuner.py
@@ -0,0 +1,63 @@
+# pylint: disable=abstract-method
+"""Grid search tuner and random tuner"""
+
+import numpy as np
+
+from .tuner import Tuner
+
+
+class GridSearchTuner(Tuner):
+    """Enumerate the search space in a grid search order"""
+    def __init__(self, task):
+        super(GridSearchTuner, self).__init__(task)
+        self.counter = 0
+
+    def next_batch(self, batch_size):
+        ret = []
+        for _ in range(batch_size):
+            if self.counter >= len(self.task.config_space):
+                continue
+            index = self.counter
+            ret.append(self.task.config_space.get(index))
+            self.counter = self.counter + 1
+        return ret
+
+    def has_next(self):
+        return self.counter < len(self.task.config_space)
+
+    def __getstate__(self):
+        return {"counter": self.counter}
+
+    def __setstate__(self, state):
+        self.counter = state['counter']
+
+
+class RandomTuner(Tuner):
+    """Enumerate the search space in a random order"""
+    def __init__(self, task):
+        super(RandomTuner, self).__init__(task)
+        self.visited = set()
+
+    def next_batch(self, batch_size):
+        ret = []
+        counter = 0
+        while counter < batch_size:
+            if len(self.visited) >= len(self.task.config_space):
+                break
+            index = np.random.randint(len(self.task.config_space))
+            while index in self.visited:
+                index = np.random.randint(len(self.task.config_space))
+
+            ret.append(self.task.config_space.get(index))
+            self.visited.add(index)
+            counter += 1
+        return ret
+
+    def has_next(self):
+        return len(self.visited) < len(self.task.config_space)
+
+    def __getstate__(self):
+        return {"visited": self.counter}
+
+    def __setstate__(self, state):
+        self.counter = state['visited']
diff --git a/python/tvm/autotvm/tuner/metric.py b/python/tvm/autotvm/tuner/metric.py
new file mode 100644
index 000000000000..c18f080ec0cb
--- /dev/null
+++ b/python/tvm/autotvm/tuner/metric.py
@@ -0,0 +1,106 @@
+# pylint: disable=invalid-name
+"""Metrics for evaluating tuning process"""
+
+import numpy as np
+
+from ..util import get_rank
+
+def max_curve(trial_scores):
+    """ f(n) = max([s[i] fo i < n])
+
+    Parameters
+    ----------
+    trial_scores: Array of float
+        the score of i th trial
+
+    Returns
+    -------
+    curve: Array of float
+        function values
+    """
+    ret = np.empty(len(trial_scores))
+    keep = -1e9
+    for i, score in enumerate(trial_scores):
+        keep = max(keep, score)
+        ret[i] = keep
+    return ret
+
+def mean_curve(trial_scores):
+    """ f(n) = mean([s[i] fo i < n])
+
+    Parameters
+    ----------
+    trial_scores: Array of float
+        the score of i th trial
+
+    Returns
+    -------
+    curve: Array of float
+        function values
+    """
+    ret = np.empty(len(trial_scores))
+    keep = 0
+    for i, score in enumerate(trial_scores):
+        keep += score
+        ret[i] = keep / (i+1)
+    return ret
+
+def recall_curve(trial_ranks, top=None):
+    """
+    if top is None, f(n) = sum([I(rank[i] < n) for i < n]) / n
+    if top is K,    f(n) = sum([I(rank[i] < K) for i < n]) / K
+
+    Parameters
+    ----------
+    trial_ranks: Array of int
+        the rank of i th trial in labels
+    top: int or None
+        top-n recall
+
+    Returns
+    -------
+    curve: Array of float
+        function values
+    """
+    if not isinstance(trial_ranks, np.ndarray):
+        trial_ranks = np.array(trial_ranks)
+
+    ret = np.zeros(len(trial_ranks))
+    if top is None:
+        for i in range(len(trial_ranks)):
+            ret[i] = np.sum(trial_ranks[:i] <= i) / (i+1)
+    else:
+        for i in range(len(trial_ranks)):
+            ret[i] = 1.0 * np.sum(trial_ranks[:i] < top) / top
+    return ret
+
+def cover_curve(trial_ranks):
+    """
+    f(n) = max k s.t. {1,2,...,k} is a subset of {ranks[i] for i < n}
+
+    Parameters
+    ----------
+    trial_ranks: Array of int
+        the rank of i th trial in labels
+
+    Returns
+    -------
+    curve: Array of float
+        function values
+    """
+    ret = np.empty(len(trial_ranks))
+    keep = -1
+    cover = set()
+    for i, rank in enumerate(trial_ranks):
+        cover.add(rank)
+        while keep+1 in cover:
+            keep += 1
+        ret[i] = keep + 1
+    return ret / len(trial_ranks)
+
+def average_recall(preds, labels, N):
+    """evaluate average recall-n for predictions and labels"""
+    trials = np.argsort(preds)[::-1]
+    ranks = get_rank(labels[trials])
+    curve = recall_curve(ranks)
+    return np.sum(curve[:N]) / N
diff --git a/python/tvm/autotvm/tuner/model_based_tuner.py b/python/tvm/autotvm/tuner/model_based_tuner.py
new file mode 100644
index 000000000000..93d5cd704d18
--- /dev/null
+++ b/python/tvm/autotvm/tuner/model_based_tuner.py
@@ -0,0 +1,343 @@
+# pylint: disable=no-else-return,invalid-name,consider-using-enumerate,abstract-method
+"""Base class for model-based tuner
+This type of tuner will fit a cost model and use some optimization methods to
+find optimums points of cost model in space.
+"""
+import gc
+
+import numpy as np
+
+from .tuner import Tuner
+
+
+class FeatureCache(object):
+    """Feature cache manager for cache sharing between different cost models"""
+    def __init__(self):
+        self.feature_cache = {}
+
+    def get(self, key):
+        """ Get feature cache dictionary for a key
+
+        Parameters
+        ----------
+        key: str
+            The key of a feature type
+
+        Returns
+        -------
+        fea_cache: dict
+            cache dictionary
+        """
+        if key not in self.feature_cache:
+            self.feature_cache[key] = {}
+
+        return self.feature_cache[key]
+
+    def size(self, key):
+        """" Get the size of a feature cache dictionary
+
+        Parameters
+        ----------
+        key: str
+            The key of a feature type
+
+        Returns
+        -------
+        n: int
+        """
+        return len(self.feature_cache.get(key, tuple()))
+
+    def clear(self, key):
+        """Clear feature cache for a key
+
+        Parameters
+        ----------
+        key: str
+            The key of a feature type
+        """
+        del self.feature_cache[key]
+        self.feature_cache[key] = {}
+        gc.collect()
+
+
+class CostModel(object):
+    """Cost model to predict the speed of a config"""
+    def __init__(self):
+        pass
+
+    def fit(self, xs, ys, plan_size):
+        """Fit to training data
+
+        Parameters
+        ----------
+        xs: Array of int
+            indexes of configs in the config space
+        ys: Array of float
+            The speed (flop, float number operations per second)
+        plan_size: int
+            The plan size of tuner
+        """
+        raise NotImplementedError()
+
+    def fit_log(self, records, plan_size):
+        """Fit training data from log.
+
+        Parameters
+        ----------
+        records: Array of Tuple(MeasureInput, MeasureResult)
+            The tuning records
+        plan_size: int
+            The plan size of tuner
+        """
+        raise NotImplementedError()
+
+    def predict(self, xs, output_margin=False):
+        """Predict the speed of configs
+
+        Parameters
+        ----------
+        xs: Array of int
+            The indexes of configs to predict
+        output_margin: bool, optional
+            Whether output the untransformed margin.
+            When a model is used as base model, it should output untransformed margin
+
+        Returns
+        -------
+        preds: Array of float
+            The prediction
+        """
+        raise NotImplementedError()
+
+    def load_basemodel(self, base_model):
+        """Load base model for transfer learning
+
+        Parameters
+        ----------
+        base_model: CostModel
+                base model
+        """
+        raise NotImplementedError()
+
+    def clone_new(self):
+        """Clone a new model with the same parameters.
+        This function will only copy hyperparameters of the tuner, not all the trained model
+
+        This is used for deriving a base model conveniently
+
+        Returns
+        -------
+        model: CostModel
+            A model with the same hyperparameter (argument)
+        """
+        raise NotImplementedError()
+
+
+class ModelOptimizer(object):
+    """Optimizer used to find optimal points of cost model"""
+    def __init__(self):
+        pass
+
+    def find_maximums(self, model, num, exclusive):
+        """Find maximum of a cost model
+
+        Note we use cost model to predict GFLOPS, so we should find the maximum
+
+        Parameters
+        ----------
+        model: CostModel
+            Cost model
+        num: int
+            The number of returned maximum points
+        exclusive: set, optional
+            The excluded set of this optimizer. Return results won't include any
+            elements in this set.
+        """
+        raise NotImplementedError()
+
+
+class ModelBasedTuner(Tuner):
+    """Base class for model based tuner
+    This type of tuner will fit a cost model and use an optimizer to
+    find the maximums of the cost model as next trials
+
+    Parameters
+    ----------
+    task: autotvm.task.Task
+        The tuning task
+    cost_model: CostModel
+        The cost model that predicts the speed of a config (IR)
+    model_optimizer:
+        The optimizer to find local optimum points of cost model in tuning search space
+    plan_size: int
+        Tuner will re-fit model per `plan_size` new measure samples
+    diversity_filter_ratio: int or float, optional
+        If is not None, the tuner will first select
+        top-(plan_size * diversity_filter_ratio) candidates according to the cost model
+        and then pick plan_size of them according to the diversity metric.
+    """
+
+    def __init__(self, task, cost_model, model_optimizer, plan_size, diversity_filter_ratio=None):
+        super(ModelBasedTuner, self).__init__(task)
+
+        # space
+        self.task = task
+        self.target = task.target
+        self.plan_size = plan_size
+        self.space = task.config_space
+        self.space_len = len(task.config_space)
+        self.dims = [len(x) for x in self.space.space_map.values()]
+
+        self.cost_model = cost_model
+        self.model_optimizer = model_optimizer
+        self.diversity_filter_ratio = diversity_filter_ratio
+
+        if self.diversity_filter_ratio:
+            assert self.diversity_filter_ratio >= 1, "Diversity filter ratio " \
+                                                     "must be larger than one"
+
+        # trial plan
+        self.trials = []
+        self.trial_pt = 0
+        self.visited = set()
+
+        # observed samples
+        self.xs = []
+        self.ys = []
+        self.flops_max = 0.0
+        self.train_ct = 0
+
+    def next_batch(self, batch_size):
+        ret = []
+
+        counter = 0
+        while counter < batch_size:
+            if len(self.visited) >= len(self.space):
+                break
+
+            while self.trial_pt < len(self.trials):
+                index = self.trials[self.trial_pt]
+                if index not in self.visited:
+                    break
+                self.trial_pt += 1
+
+            if self.trial_pt >= len(self.trials):  # trial list is empty, choose randomly
+                index = np.random.randint(len(self.space))
+                while index in self.visited:
+                    index = np.random.randint(len(self.space))
+
+            ret.append(self.space.get(index))
+            self.visited.add(index)
+
+            counter += 1
+        return ret
+
+    def update(self, inputs, results):
+        for inp, res in zip(inputs, results):
+            index = inp.config.index
+            if res.error_no == 0:
+                self.xs.append(index)
+                flops = inp.task.flop / np.mean(res.costs)
+                self.flops_max = max(self.flops_max, flops)
+                self.ys.append(flops)
+            else:
+                self.xs.append(index)
+                self.ys.append(0)
+
+        # if we have enough new training samples
+        if len(self.xs) >= self.plan_size * (self.train_ct + 1) \
+                and self.flops_max > 1e-6:
+            self.cost_model.fit(self.xs, self.ys, self.plan_size)
+            if self.diversity_filter_ratio:
+                candidate = self.model_optimizer.find_maximums(
+                    self.cost_model, self.plan_size * self.diversity_filter_ratio, self.visited)
+                scores = self.cost_model.predict(candidate)
+                knobs = [point2knob(x, self.dims) for x in candidate]
+                pick_index = submodular_pick(0 * scores, knobs, self.plan_size, knob_weight=1)
+                maximums = np.array(candidate)[pick_index]
+            else:
+                maximums = self.model_optimizer.find_maximums(
+                    self.cost_model, self.plan_size, self.visited)
+
+            self.trials = maximums
+            self.trial_pt = 0
+            self.train_ct += 1
+
+    def load_history(self, data_set):
+        base_model = self.cost_model.clone_new()
+        base_model.fit_log(data_set, self.plan_size)
+
+        if not self.trials:
+            # no plan yet, use base model to select initial trials
+            maximums = self.model_optimizer.find_maximums(base_model, self.visited)
+            self.trials = maximums
+            self.trial_pt = 0
+
+        self.cost_model.load_basemodel(base_model)
+
+    def has_next(self):
+        return len(self.visited) < len(self.space)
+
+
+def point2knob(p, dims):
+    """convert point form (single integer) to knob form (vector)"""
+    knob = []
+    for dim in dims:
+        knob.append(p % dim)
+        p //= dim
+    return knob
+
+
+def knob2point(knob, dims):
+    """convert knob form (vector) to point form (single integer)"""
+    p = 0
+    for j, k in enumerate(knob):
+        p += int(np.prod(dims[:j])) * k
+    return p
+
+
+def submodular_pick(scores, knobs, n_pick, knob_weight=1.0):
+    """Run greedy optimization to pick points with regard to both score and diversity.
+    DiversityScore = knob_weight * number of unique knobs in the selected set
+    Obj = sum(scores[i] for i in pick) + DiversityScore
+    Note that this objective function is a monotone submodular function.
+
+    Parameters
+    ----------
+    scores: Array of float
+        score of every points
+    knobs: Array of Array of int
+        feature vector (tunable knobs) of every points
+    n_pick: int
+        number of points to pick
+    knob_weight: float
+        weight of an unique knob feature
+    """
+    n = len(scores)
+    assert n == len(knobs)
+    n_knobs = len(knobs[0])
+
+    knobs_set = [set() for _ in range(n_knobs)]
+
+    ret = []
+    remain = list(range(len(scores)))
+
+    for _ in range(n_pick):
+        max_x = -1
+        max_delta = -1e9
+
+        for x in remain:
+            tmp_delta = scores[x]
+            for i in range(n_knobs):
+                if knobs[x][i] not in knobs_set[i]:
+                    tmp_delta += knob_weight
+
+            if tmp_delta > max_delta:
+                max_delta, max_x = tmp_delta, x
+
+        ret.append(max_x)
+        remain.remove(max_x)
+        for i in range(n_knobs):
+            knobs_set[i].add(knobs[max_x][i])
+
+    return ret
diff --git a/python/tvm/autotvm/tuner/sa_model_optimizer.py b/python/tvm/autotvm/tuner/sa_model_optimizer.py
new file mode 100644
index 000000000000..264b00eeaf26
--- /dev/null
+++ b/python/tvm/autotvm/tuner/sa_model_optimizer.py
@@ -0,0 +1,148 @@
+# pylint: disable=consider-using-enumerate
+"""
+Cost model optimizer based on simulated annealing
+"""
+
+import heapq
+import logging
+import time
+
+import numpy as np
+
+from ..util import sample_ints
+from .model_based_tuner import ModelOptimizer, knob2point, point2knob
+
+class SimulatedAnnealingOptimizer(ModelOptimizer):
+    """parallel simulated annealing optimization algorithm
+
+    Parameters
+    ----------
+    task: Task
+        The tuning task
+    n_iter: int
+        The number of iterations of simulated annealing
+    temp: float or Array of float
+        If is a single float, then use a constant temperature.
+        If is an Array, then perform linear cooling from temp[0] to temp[1]
+    early_stop: int, optional
+        Stop iteration if the optimal set do not change in `early_stop` rounds
+    verbose: int, optional
+        Print log every `verbose` iterations
+    """
+    def __init__(self, task, n_iter=500, temp=(1, 0), persistent=True, parallel_size=128,
+                 early_stop=30, verbose=50):
+        super(SimulatedAnnealingOptimizer, self).__init__()
+
+        self.task = task
+        self.dims = [len(x) for x in self.task.config_space.space_map.values()]
+
+        self.n_iter = n_iter
+        self.temp = temp
+        self.persistent = persistent
+        self.parallel_size = parallel_size
+        self.early_stop = early_stop
+        self.verbose = verbose
+        self.points = None
+
+    def find_maximums(self, model, num, exclusive):
+        tic = time.time()
+        temp, n_iter, early_stop, verbose = self.temp, self.n_iter, self.early_stop, self.verbose
+
+        if self.persistent and self.points is not None:
+            points = self.points
+        else:
+            points = np.array(sample_ints(0, len(self.task.config_space), self.parallel_size))
+
+        scores = model.predict(points)
+
+        # build heap and insert initial points
+        heap_items = [(float('-inf'), -i) for i in range(num)]
+        heapq.heapify(heap_items)
+        in_heap = set(exclusive)
+        in_heap.update([-i for i in range(num)])
+
+        for s, p in zip(scores, points):
+            if s > heap_items[0][0] and p not in in_heap:
+                pop = heapq.heapreplace(heap_items, (s, p))
+                in_heap.remove(pop[1])
+                in_heap.add(p)
+
+        k = 0
+        k_last_modify = 0
+
+        if isinstance(temp, (tuple, list, np.ndarray)):
+            t = temp[0]
+            cool = 1.0 * (temp[0] - temp[1]) / (n_iter + 1)
+        else:
+            t = temp
+            cool = 0
+
+        while k < n_iter and k < k_last_modify + early_stop:
+            new_points = np.empty_like(points)
+            for i, p in enumerate(points):
+                new_points[i] = random_walk(p, self.dims)
+
+            new_scores = model.predict(new_points)
+
+            ac_prob = np.exp((new_scores - scores) / t)
+            ac_index = np.random.random(len(ac_prob)) < ac_prob
+
+            points[ac_index] = new_points[ac_index]
+            scores[ac_index] = new_scores[ac_index]
+
+            for s, p in zip(new_scores, new_points):
+                if s > heap_items[0][0] and p not in in_heap:
+                    pop = heapq.heapreplace(heap_items, (s, p))
+                    in_heap.remove(pop[1])
+                    in_heap.add(p)
+                    k_last_modify = k
+
+            k += 1
+            t -= cool
+
+            if verbose >= 1 and k % verbose == 0:
+                t_str = "%.2f" % t
+                logging.info("SA iter: %d\tlast_update: %d\tmax-0: %.2f\tmax-1: %.2f\ttemp: %s\t"
+                             "elapsed: %.2f",
+                             k, k_last_modify, heap_items[0][0],
+                             np.max([v for v, _ in heap_items]), t_str,
+                             time.time() - tic)
+
+        heap_items.sort(key=lambda item: -item[0])
+        if verbose:
+            logging.info("SA iter: %d\tlast_update: %d\tmax-0: %.2f\tmax-1: %.2f\telapsed: %.2f",
+                         k, k_last_modify, heap_items[-1][0], heap_items[0][0], time.time() - tic)
+            logging.info("SA Maximums: %s", heap_items)
+
+        if self.persistent:
+            self.points = points
+
+        return [x[1] for x in heap_items]
+
+def random_walk(p, dims):
+    """random walk as local transition
+
+    Parameters
+    ----------
+    p: int
+        index of the ConfigEntity
+    dims: Array of int
+        sizes of each dimension
+
+    Returns
+    -------
+    new_p: int
+        new neighborhood index
+    """
+    # transform to knob form
+    old = point2knob(p, dims)
+    new = list(old)
+
+    # mutate
+    while new == old:
+        from_i = np.random.randint(len(old))
+        to_v = np.random.randint(dims[from_i])
+        new[from_i] = to_v
+
+    # transform to index form
+    return knob2point(new, dims)
diff --git a/python/tvm/autotvm/tuner/tuner.py b/python/tvm/autotvm/tuner/tuner.py
new file mode 100644
index 000000000000..26a6049b4cba
--- /dev/null
+++ b/python/tvm/autotvm/tuner/tuner.py
@@ -0,0 +1,138 @@
+# pylint: disable=unused-argument, no-self-use, invalid-name
+"""Base class of tuner"""
+import logging
+
+import numpy as np
+
+from ..measure import MeasureInput
+from ..measure import create_measure_batch
+
+
+class Tuner(object):
+    """Base class for tuners
+
+    Parameters
+    ----------
+    task: autotvm.task.Task
+        Tuning Task
+    """
+
+    def __init__(self, task, **kwargs):
+        self.param = kwargs
+        self.recorder = None
+
+        self.task = task
+
+        # keep the current best
+        self.best_config = None
+        self.best_flops = 0
+        self.best_measure_pair = None
+
+    def has_next(self):
+        """Whether has next untried config in the space
+
+        Returns
+        -------
+        has_next: bool
+        """
+        raise NotImplementedError()
+
+    def next_batch(self, batch_size):
+        """get the next batch of configs to be measure on real hardware
+
+        Parameters
+        ----------
+        batch_size: int
+            The size of the batch
+
+        Returns
+        -------
+        a batch of configs
+        """
+        raise NotImplementedError()
+
+    def update(self, inputs, results):
+        """Update parameters of the tuner according to measurement results
+
+        Parameters
+        ----------
+        inputs: Array of autotvm.measure.MeasureInput
+            The input for measurement
+        results: Array of autotvm.measure.MeasureResult
+            result for measurement
+        """
+        pass
+
+    def tune(self, n_trial, measure_option, verbose=1, callbacks=()):
+        """Begin tuning
+
+        Parameters
+        ----------
+        n_trial: int
+            Maximum number of configs to try (measure on real hardware)
+        measure_option: dict
+            The options for how to measure generated code.
+            You should use the return value ot autotvm.measure_option for this argument.
+        verbose: int
+            0: silent mode, no output
+            1: print every measurement result
+        callbacks: List of callable
+            A list of callback functions. The signature of callback function is
+            (Tuner, List of MeasureInput, List of MeasureResult)
+            with no return value. These callback functions will be called on
+            every measurement pair. See autotvm/tuner/callback.py for some examples.
+        """
+        measure_batch = create_measure_batch(self.task, measure_option)
+        parallel_num = getattr(measure_batch, 'parallel_num', 1)
+
+        i = 0
+        while i < n_trial:
+            if not self.has_next():
+                break
+
+            configs = self.next_batch(min(parallel_num, n_trial - i))
+
+            inputs = [MeasureInput(self.task.target, self.task, config) for config in configs]
+            results = measure_batch(inputs)
+
+            # print info
+            if verbose >= 1:
+                for k, (inp, res) in enumerate(zip(inputs, results)):
+                    config = inp.config
+                    if res.error_no == 0:
+                        flops = inp.task.flop / np.mean(res.costs)
+                    else:
+                        flops = 0
+                    if flops > self.best_flops:
+                        self.best_flops = flops
+                        self.best_config = config
+                        self.best_measure_pair = (inp, res)
+
+                    logging.info("No: %d\tGFLOPS: %.2f/%.2f\tresult: %s\t%s",
+                                 i + k + 1, flops / 1e9, self.best_flops / 1e9,
+                                 res, config)
+
+            i += len(results)
+
+            self.update(inputs, results)
+
+            for callback in callbacks:
+                callback(self, inputs, results)
+
+        del measure_batch
+
+    def reset(self):
+        """reset the status of tuner"""
+        self.best_config = None
+        self.best_flops = 0
+        self.best_measure_pair = None
+
+    def load_history(self, data_set):
+        """load history data for transfer learning
+
+        Parameters
+        ----------
+        data_set: Array of (MeasureInput, MeasureResult) pair
+            Previous tuning records
+        """
+        raise NotImplementedError()
diff --git a/python/tvm/autotvm/tuner/xgboost_cost_model.py b/python/tvm/autotvm/tuner/xgboost_cost_model.py
new file mode 100644
index 000000000000..657a75e6268b
--- /dev/null
+++ b/python/tvm/autotvm/tuner/xgboost_cost_model.py
@@ -0,0 +1,482 @@
+# pylint: disable=invalid-name
+"""XGBoost as cost model"""
+
+import multiprocessing
+import logging
+import time
+
+import numpy as np
+try:
+    import xgboost as xgb
+except ImportError:
+    xgb = None
+
+from .. import feature
+from ..util import get_rank
+from .metric import max_curve, recall_curve, cover_curve
+from .model_based_tuner import CostModel, FeatureCache
+
+class XGBoostCostModel(CostModel):
+    """XGBoost as cost model
+
+    Parameters
+    ----------
+    task: Task
+        The tuning task
+    feature_type: str, optional
+        If is 'itervar', use features extracted from IterVar (loop variable).
+        If is 'knob', use flatten ConfigEntity directly.
+        If is 'curve', use sampled curve feature (relation feature).
+
+        Note on choosing feature type:
+        For single task tuning, 'itervar' and 'knob' is good.
+                                'itervar' is more accurate but 'knob' is much faster.
+        For cross-shape tuning (e.g. many convolutions with different shapes),
+                               'itervar' and 'curve' has better transferability,
+                               'knob' is faster.
+        For cross-device or cross-operator tuning, you can use 'curve' only.
+    loss_type: str
+        If is 'reg', use regression loss to train cost model.
+                     The cost model predicts the normalized flops.
+        If is 'rank', use pairwise rank loss to train cost model.
+                     The cost model predicts relative rank score.
+    num_threads: int, optional
+        The number of threads.
+    verbose: int, optional
+        If is not none, the cost model will print training log every `verbose` iterations.
+    """
+    def __init__(self, task, feature_type, loss_type, num_threads=None, verbose=20):
+        super(XGBoostCostModel, self).__init__()
+
+        if xgb is None:
+            raise RuntimeError("XGBoost is required for XGBoostCostModel. "
+                               "Please install its python package first. "
+                               "Help: (https://xgboost.readthedocs.io/en/latest/) ")
+
+        self.task = task
+        self.target = task.target
+        self.space = task.config_space
+
+        self.fea_type = feature_type
+        self.loss_type = loss_type
+        self.num_threads = num_threads
+        self.verbose = verbose
+
+        if loss_type == 'reg':
+            self.xgb_params = {
+                'max_depth': 3,
+                'gamma': 0.0001,
+                'min_child_weight': 1,
+
+                'subsample': 1.0,
+
+                'eta': 0.3,
+                'lambda': 1.00,
+                'alpha': 0,
+
+                'objective': 'reg:linear',
+            }
+        elif loss_type == 'rank':
+            self.xgb_params = {
+                'max_depth': 3,
+                'gamma': 0.0001,
+                'min_child_weight': 1,
+
+                'subsample': 1.0,
+
+                'eta': 0.3,
+                'lambda': 1.00,
+                'alpha': 0,
+
+                'objective': 'rank:pairwise',
+            }
+        else:
+            raise RuntimeError("Invalid loss type: " + loss_type)
+
+        self.xgb_params['silent'] = 1
+        if num_threads:
+            self.xgb_params['nthread'] = num_threads
+        self.bst = None
+
+        if feature_type == 'itervar':
+            self.feature_extract_func = _extract_itervar_feature_index
+        elif feature_type == 'knob':
+            self.feature_extract_func = _extract_knob_feature_index
+        elif feature_type == 'curve':
+            self.feature_extract_func = _extract_curve_feature_index
+        else:
+            raise RuntimeError("Invalid feature type " + feature_type)
+
+        self.feature_cache = FeatureCache()
+        self.feature_extra_ct = 0
+        self.pool = None
+        self.base_model = None
+
+        self._reset_pool()
+
+    def _reset_pool(self):
+        # reset processing pool for feature extraction
+        if self.pool:
+            self.pool.terminate()
+            self.pool.join()
+            del self.pool
+        # use global variable to pass common arguments
+        global _extract_space, _extract_target, _extract_task
+        _extract_space = self.space
+        _extract_target = self.target
+        _extract_task = self.task
+        self.pool = multiprocessing.Pool(self.num_threads)
+
+    def fit(self, xs, ys, plan_size):
+        tic = time.time()
+        self._reset_pool()
+
+        x_train = self._get_feature(xs)
+        y_train = np.array(ys)
+        y_train /= np.max(y_train)
+
+        valid_index = y_train > 1e-6
+        index = np.random.permutation(len(x_train))
+        dtrain = xgb.DMatrix(x_train[index], y_train[index])
+
+        if self.base_model:
+            dtrain.set_base_margin(self.base_model.predict(xs, output_margin=True))
+
+        self.bst = xgb.train(self.xgb_params, dtrain,
+                             num_boost_round=8000,
+                             callbacks=[custom_callback(
+                                 stopping_rounds=20,
+                                 metric='tr-a-recall@%d' % plan_size,
+                                 evals=[(dtrain, 'tr')],
+                                 maximize=True,
+                                 fevals=[
+                                     xgb_average_recalln_curve_score(plan_size),
+                                 ],
+                                 verbose_eval=self.verbose)])
+
+        logging.info("train: %.2f\tobs: %d\terror: %d\tn_cache: %d",
+                     time.time() - tic, len(xs),
+                     len(xs) - np.sum(valid_index),
+                     self.feature_cache.size(self.fea_type))
+
+    def fit_log(self, records, plan_size):
+        tic = time.time()
+        self._reset_pool()
+
+        args = list(records)
+        if self.fea_type == 'itervar':
+            feature_extract_func = _extract_itervar_feature_log
+        elif self.fea_type == 'knob':
+            feature_extract_func = _extract_knob_feature_log
+        elif self.fea_type == 'curve':
+            feature_extract_func = _extract_curve_feature_log
+        else:
+            raise RuntimeError("Invalid feature type: " + self.fea_type)
+        res = self.pool.map(feature_extract_func, args)
+        xs, ys = zip(*res)
+        xs, ys = np.array(xs), np.array(ys)
+
+        x_train = xs
+        y_train = ys
+        y_train /= np.max(y_train)
+
+        index = np.random.permutation(len(x_train))
+        dtrain = xgb.DMatrix(x_train[index], y_train[index])
+
+        plan_size *= 2
+        self.bst = xgb.train(self.xgb_params, dtrain,
+                             num_boost_round=200,
+                             callbacks=[custom_callback(
+                                 stopping_rounds=100,
+                                 metric='tr-a-recall@%d' % plan_size,
+                                 evals=[(dtrain, 'tr')],
+                                 maximize=True,
+                                 fevals=[
+                                     xgb_average_recalln_curve_score(plan_size),
+                                 ],
+                                 verbose_eval=self.verbose)])
+
+        logging.info("train: %.2f\tobs: %d", time.time() - tic, len(xs))
+
+    def predict(self, xs, output_margin=False):
+        feas = self._get_feature(xs)
+        dtest = xgb.DMatrix(feas)
+
+        if self.base_model:
+            dtest.set_base_margin(self.base_model.predict(xs, output_margin=True))
+
+        return self.bst.predict(dtest, output_margin=output_margin)
+
+    def load_basemodel(self, base_model):
+        self.base_model = base_model
+
+    def clone_new(self):
+        return XGBoostCostModel(self.task, self.fea_type, self.loss_type,
+                                self.num_threads, self.verbose)
+
+    def _get_feature(self, indexes):
+        """get features for indexes, run extraction if we do not have cache for them"""
+        # free feature cache
+        if self.feature_cache.size(self.fea_type) >= 100000:
+            self.feature_cache.clear(self.fea_type)
+
+        fea_cache = self.feature_cache.get(self.fea_type)
+
+        indexes = np.array(indexes)
+        need_extract = [x for x in indexes if x not in fea_cache]
+
+        if need_extract:
+            feas = self.pool.map(self.feature_extract_func, need_extract)
+            for i, fea in zip(need_extract, feas):
+                fea_cache[i] = fea
+
+        ret = np.empty((len(indexes), fea_cache[indexes[0]].shape[-1]), dtype=np.float32)
+        for i, ii in enumerate(indexes):
+            ret[i, :] = fea_cache[ii]
+        return ret
+
+
+_extract_space = None
+_extract_target = None
+_extract_task = None
+
+def _extract_itervar_feature_index(index):
+    """extract iteration var feature for an index in extract_space"""
+    config = _extract_space.get(index)
+    with _extract_target:
+        sch, args = _extract_task.instantiate(config)
+    fea = feature.get_itervar_feature_flatten(sch, args, take_log=True)
+    fea = np.concatenate((fea, list(config.get_other_option().values())))
+    return fea
+
+def _extract_itervar_feature_log(arg):
+    """extract iteration var feature for log items"""
+    inp, res = arg
+    config = inp.config
+    with inp.target:
+        sch, args = inp.task.instantiate(config)
+    fea = feature.get_itervar_feature_flatten(sch, args, take_log=True)
+    x = np.concatenate((fea, list(config.get_other_option().values())))
+
+    if res.error_no == 0:
+        y = inp.task.flop / np.mean(res.costs)
+    else:
+        y = 0
+    return x, y
+
+def _extract_knob_feature_index(index):
+    """extract knob feature for an index in extract_space"""
+    config = _extract_space.get(index)
+    return config.get_flatten_feature()
+
+def _extract_knob_feature_log(arg):
+    """extract knob feature for log items"""
+    inp, res = arg
+    config = inp.config
+    x = config.get_flatten_feature()
+
+    if res.error_no == 0:
+        with inp.target:  # necessary, for calculating flops of this task
+            inp.task.instantiate(config)
+        y = inp.task.flop / np.mean(res.costs)
+    else:
+        y = 0
+    return x, y
+
+def _extract_curve_feature_index(index):
+    """extract sampled curve feature for an index in extract_space"""
+    config = _extract_space.get(index)
+    with _extract_target:
+        sch, args = _extract_task.instantiate(config)
+    fea = feature.get_buffer_curve_sample_flatten(sch, args, sample_n=20)
+    fea = np.concatenate((fea, list(config.get_other_option().values())))
+    return np.array(fea)
+
+def _extract_curve_feature_log(arg):
+    """extract sampled curve feature for log items"""
+    inp, res = arg
+    config = inp.config
+    with inp.target:
+        sch, args = inp.task.instantiate(config)
+    fea = feature.get_buffer_curve_sample_flatten(sch, args, sample_n=20)
+    x = np.concatenate((fea, list(config.get_other_option().values())))
+
+    if res.error_no == 0:
+        y = inp.task.flop / np.mean(res.costs)
+    else:
+        y = 0
+    return x, y
+
+
+def custom_callback(stopping_rounds, metric, fevals, evals=(), log_file=None,
+                    save_file="xgb_checkpoint", save_every=None,
+                    maximize=False, verbose_eval=True):
+    """callback function for xgboost to support multiple custom evaluation functions"""
+    from xgboost.core import EarlyStopException
+    from xgboost.callback import _fmt_metric
+    from xgboost.training import aggcv
+
+    state = {}
+    metric_shortname = metric.split("-")[1]
+
+    def init(env):
+        """internal function"""
+        bst = env.model
+
+        state['maximize_score'] = maximize
+        state['best_iteration'] = 0
+        if maximize:
+            state['best_score'] = float('-inf')
+        else:
+            state['best_score'] = float('inf')
+
+        if bst is not None:
+            if bst.attr('best_score') is not None:
+                state['best_score'] = float(bst.attr('best_score'))
+                state['best_iteration'] = int(bst.attr('best_iteration'))
+                state['best_msg'] = bst.attr('best_msg')
+            else:
+                bst.set_attr(best_iteration=str(state['best_iteration']))
+                bst.set_attr(best_score=str(state['best_score']))
+        else:
+            assert env.cvfolds is not None
+
+    def callback(env):
+        """internal function"""
+        if not state:
+            init(env)
+
+        bst = env.model
+        i = env.iteration
+        cvfolds = env.cvfolds
+
+        res_dict = {}
+
+        ##### evaluation #####
+        if cvfolds is not None:
+            for feval in fevals:
+                tmp = aggcv([f.eval(i, feval) for f in cvfolds])
+                for k, mean, std in tmp:
+                    res_dict[k] = [mean, std]
+        else:
+            for feval in fevals:
+                bst_eval = bst.eval_set(evals, i, feval)
+                res = [x.split(':') for x in bst_eval.split()]
+                for kv in res[1:]:
+                    res_dict[kv[0]] = [float(kv[1])]
+
+        eval_res = []
+        keys = list(res_dict.keys())
+        keys.sort(key=lambda x: x if metric_shortname not in x else "a" + x)
+        for key in keys:
+            v = res_dict[key]
+            eval_res.append([key] + v)
+
+        ##### print eval result #####
+        infos = ["XGB iter: %3d" % i]
+        for item in eval_res:
+            if 'null' in item[0]:
+                continue
+            infos.append("%s: %.6f" % (item[0], item[1]))
+
+        if not isinstance(verbose_eval, bool) and i % verbose_eval == 0:
+            logging.info("\t".join(infos))
+        if log_file:
+            with open(log_file, "a") as fout:
+                fout.write("\t".join(infos) + '\n')
+
+        ##### save model #####
+        if save_every and i % save_every == 0:
+            filename = save_file + ".%05d.bst" % i
+            logging.info("save model to %s ...", filename)
+            bst.save_model(filename)
+
+        ##### choose score and do early stopping #####
+        score = None
+        for item in eval_res:
+            if item[0] == metric:
+                score = item[1]
+                break
+        assert score is not None
+
+        best_score = state['best_score']
+        best_iteration = state['best_iteration']
+        maximize_score = state['maximize_score']
+        if (maximize_score and score > best_score) or \
+                (not maximize_score and score < best_score):
+            msg = '[%d] %s' % (
+                env.iteration,
+                '\t'.join([_fmt_metric(x) for x in eval_res]))
+            state['best_msg'] = msg
+            state['best_score'] = score
+            state['best_iteration'] = env.iteration
+            # save the property to attributes, so they will occur in checkpoint.
+            if env.model is not None:
+                env.model.set_attr(best_score=str(state['best_score']),
+                                   best_iteration=str(state['best_iteration']),
+                                   best_msg=state['best_msg'])
+        elif env.iteration - best_iteration >= stopping_rounds:
+            best_msg = state['best_msg']
+            if verbose_eval and env.rank == 0:
+                logging.info("Stopping. Best iteration: %s ", best_msg)
+            raise EarlyStopException(best_iteration)
+
+    return callback
+
+
+# feval wrapper for xgboost
+def xgb_max_curve_score(N):
+    """evaluate max curve score for xgb"""
+    def feval(preds, labels):
+        labels = labels.get_label()
+        trials = np.argsort(preds)[::-1]
+        scores = labels[trials]
+        curve = max_curve(scores)
+        return "Smax@%d" % N, curve[N] / np.max(labels)
+    return feval
+
+def xgb_recalln_curve_score(N):
+    """evaluate recall-n curve score for xgb"""
+    def feval(preds, labels):
+        labels = labels.get_label()
+        trials = np.argsort(preds)[::-1]
+        ranks = get_rank(labels[trials])
+        curve = recall_curve(ranks)
+        return "recall@%d" % N, curve[N]
+    return feval
+
+def xgb_average_recalln_curve_score(N):
+    """evaluate average recall-n curve score for xgb"""
+    def feval(preds, labels):
+        labels = labels.get_label()
+        trials = np.argsort(preds)[::-1]
+        ranks = get_rank(labels[trials])
+        curve = recall_curve(ranks)
+        return "a-recall@%d" % N, np.sum(curve[:N]) / N
+    return feval
+
+def xgb_recallk_curve_score(N, topk):
+    """evaluate recall-k curve score for xgb"""
+    def feval(preds, labels):
+        labels = labels.get_label()
+        trials = np.argsort(preds)[::-1]
+        ranks = get_rank(labels[trials])
+        curve = recall_curve(ranks, topk)
+        return "recall@%d" % topk, curve[N]
+    return feval
+
+def xgb_cover_curve_score(N):
+    """evaluate cover curve score for xgb"""
+    def feval(preds, labels):
+        labels = labels.get_label()
+        trials = np.argsort(preds)[::-1]
+        ranks = get_rank(labels[trials])
+        curve = cover_curve(ranks)
+        return "cover@%d" % N, curve[N]
+    return feval
+
+def xgb_null_score(_):
+    """empty score function for xgb"""
+    def feval(__, ___):
+        return "null", 0
+    return feval
diff --git a/python/tvm/autotvm/tuner/xgboost_tuner.py b/python/tvm/autotvm/tuner/xgboost_tuner.py
new file mode 100644
index 000000000000..eb3978b5c135
--- /dev/null
+++ b/python/tvm/autotvm/tuner/xgboost_tuner.py
@@ -0,0 +1,59 @@
+"""Tuner that uses xgboost as cost model"""
+
+from .model_based_tuner import ModelBasedTuner, ModelOptimizer
+from .xgboost_cost_model import XGBoostCostModel
+from .sa_model_optimizer import SimulatedAnnealingOptimizer
+
+class XGBTuner(ModelBasedTuner):
+    """Tuner that uses xgboost as cost model
+
+    Parameters
+    ----------
+    task: Task
+        The tuning task
+    plan_size: int
+        The size of a plan. After `plan_size` trials, the tuner will refit a new cost model
+        and do planing for the next `plan_size` trials.
+    feature_type: str, optional
+        If is 'itervar', use features extracted from IterVar (loop variable).
+        If is 'knob', use flatten ConfigEntity directly.
+        If is 'curve', use sampled curve feature (relation feature).
+
+        Note on choosing feature type:
+        For single task tuning, 'itervar' and 'knob' is good.
+                                'itervar' is more accurate but 'knob' is much faster.
+        For cross-shape tuning (e.g. many convolutions with different shapes),
+                               'itervar' and 'curve' has better transferability,
+                               'knob' is faster.
+        For cross-device or cross-operator tuning, you can use 'curve' only.
+    loss_type: str
+        If is 'reg', use regression loss to train cost model.
+                     The cost model predicts the normalized flops.
+        If is 'rank', use pairwise rank loss to train cost model.
+                     The cost model predicts relative rank score.
+    num_threads: int, optional
+        The number of threads.
+    optimizer: str or ModelOptimizer, optional
+        If is 'sa', use a default simulated annealing optimizer.
+        Otherwise it should be a ModelOptimizer object.
+    diversity_filter_ratio: int or float, optional
+        If is not None, the tuner will first select
+        top-(plan_size * diversity_filter_ratio) candidates according to the cost model
+        and then pick batch_size of them according to the diversity metric.
+    """
+    def __init__(self, task, plan_size=32,
+                 feature_type='itervar', loss_type='rank', num_threads=None,
+                 optimizer='sa', diversity_filter_ratio=None):
+        cost_model = XGBoostCostModel(task,
+                                      feature_type=feature_type,
+                                      loss_type=loss_type,
+                                      num_threads=num_threads)
+        if optimizer == 'sa':
+            optimizer = SimulatedAnnealingOptimizer(task)
+        else:
+            assert isinstance(optimizer, ModelOptimizer), "Optimizer must be " \
+                                                          "a supported name string" \
+                                                          "or a ModelOptimizer object."
+
+        super(XGBTuner, self).__init__(task, cost_model, optimizer,
+                                       plan_size, diversity_filter_ratio)
diff --git a/python/tvm/autotvm/util.py b/python/tvm/autotvm/util.py
new file mode 100644
index 000000000000..df58bde38558
--- /dev/null
+++ b/python/tvm/autotvm/util.py
@@ -0,0 +1,149 @@
+# pylint: disable=invalid-name
+"""Utilities"""
+import logging
+import multiprocessing
+import time
+
+import numpy as np
+
+from .. import expr, ir_pass
+
+def get_rank(values):
+    """get rank of items
+
+    Parameters
+    ----------
+    values: Array
+
+    Returns
+    -------
+    ranks: Array of int
+        the rank of this item in the input (the largest value ranks first)
+    """
+    tmp = np.argsort(-values)
+    ranks = np.empty_like(tmp)
+    ranks[tmp] = np.arange(len(tmp))
+    return ranks
+
+
+def sample_ints(low, high, m):
+    """
+    Sample m different integer numbers from [low, high) without replacement
+    This function is an alternative of `np.random.choice` when (high - low) > 2 ^ 32, in
+    which case numpy does not work.
+
+    Parameters
+    ----------
+    low: int
+        low point of sample range
+    high: int
+        high point of sample range
+    m: int
+        The number of sampled int
+
+    Returns
+    -------
+    ints: an array of size m
+    """
+    vis = set()
+    assert m <= high - low
+    while len(vis) < m:
+        new = np.random.randint(low, high)
+        while new in vis:
+            new = np.random.randint(low, high)
+        vis.add(new)
+
+    return list(vis)
+
+
+def pool_map(func, args, batch_size, verbose=False, pool=None):
+    """A wrapper of multiprocessing.pool.Pool.map to support small-batch mapping
+    for large argument list. This can reduce memory usage
+
+    Parameters
+    ----------
+    func: Func(arg) -> np.ndarray
+        mapping function
+    args: List
+        list of arguments
+    batch_size: int
+        batch size in mapping
+    verbose: bool, optional
+        whether print progress
+    pool: multiprocessing.Pool, optional
+        pool objection
+
+    Returns
+    -------
+    converted numpy array
+    """
+
+    ret = None
+    tic = time.time()
+    local_pool = pool or multiprocessing.Pool()
+    if verbose:
+        logging.info("mapping begin")
+    for i in range(0, len(args), batch_size):
+        if verbose:
+            logging.info("mapping %d/%d elapsed %.2f", i, len(args),
+                         time.time() - tic)
+        tmp = np.array(local_pool.map(func, args[i:i+batch_size]))
+        ret = tmp if ret is None else np.concatenate((ret, tmp))
+    if verbose:
+        logging.info("mapping done")
+    if not pool:
+        local_pool.close()
+    return ret
+
+def get_func_name(func):
+    """Get name of a function
+
+    Parameters
+    ----------
+    func: Function
+        The function
+    Returns
+    -------
+    name: str
+        The name
+    """
+
+    return func.func_name if hasattr(func, 'func_name') else func.__name__
+
+
+def get_const_int(exp):
+    """Verifies expr is integer and get the constant value.
+
+    Parameters
+    ----------
+    exp : tvm.Expr or int
+        The input expression.
+
+    Returns
+    -------
+    out_value : int
+        The output.
+    """
+    if isinstance(exp, int):
+        return exp
+    if not isinstance(exp, (expr.IntImm, expr.UIntImm)):
+        exp = ir_pass.Simplify(expr)
+    if not isinstance(exp, (expr.IntImm, expr.UIntImm)):
+        raise ValueError("Expect value to be constant int")
+    return exp.value
+
+
+def get_const_tuple(in_tuple):
+    """Verifies input tuple is IntImm, returns tuple of int.
+
+    Parameters
+    ----------
+    in_tuple : tuple of Expr
+        The input.
+
+    Returns
+    -------
+    out_tuple : tuple of int
+        The output.
+    """
+    return tuple(get_const_int(x) for x in in_tuple)
diff --git a/python/tvm/rpc/client.py b/python/tvm/rpc/client.py
index ef1e7b5393e5..70af8c48538a 100644
--- a/python/tvm/rpc/client.py
+++ b/python/tvm/rpc/client.py
@@ -229,8 +229,14 @@ def text_summary(self):
         res += "----------------------------\n"
         res += "key\tfree\tpending\n"
         res += "----------------------------\n"
-        for k, v in data["queue_info"].items():
-            res += "%s\t%d\t%g\n" % (k, v["free"], v["pending"])
+        queue_info = data['queue_info']
+        keys = list(queue_info.keys())
+        if keys:
+            keys.sort()
+            max_key_len = max([len(k) for k in keys])
+            for k in keys:
+                res += ("%%-%d" % max_key_len + "s\t%d\t%g\n") % \
+                       (k, queue_info[k]["free"], queue_info[k]["pending"])
         res += "----------------------------\n"
         return res
 
diff --git a/src/autotvm/feature_visitor.cc b/src/autotvm/feature_visitor.cc
new file mode 100644
index 000000000000..01330e97b886
--- /dev/null
+++ b/src/autotvm/feature_visitor.cc
@@ -0,0 +1,93 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file feature_visitor.cc
+ * \brief Base class for feature extractor.
+ *        These features are used for machine learning cost model
+ */
+
+#include "feature_visitor.h"
+
+namespace tvm {
+namespace autotvm {
+
+// for loop
+void FeatureVisitor::Visit_(const For *op) {
+  const auto *extent = op->extent.as<IntImm>();
+  int64_t loop_extent = -1;
+  if (extent != nullptr)
+    loop_extent = extent->value;
+  AnnotationType ann = kSerial;
+  switch (op->for_type) {
+    case ForType ::Parallel:
+      ann = kParallel;
+      break;
+    case ForType::Unrolled:
+      ann = kUnrolled;
+      break;
+    case ForType::Vectorized:
+      ann = kVectorized;
+      break;
+    case ForType::Serial:
+      ann = kSerial;
+      break;
+  }
+
+  if (EnterItervar_(op->loop_var, loop_extent, ann)) {
+    IRVisitor::Visit_(op);
+    ExitItervar_();
+  }
+}
+
+// parallel axis, virtual thread
+void FeatureVisitor::Visit_(const AttrStmt *op) {
+  if (op->attr_key == attr::thread_extent ||
+      op->attr_key == attr::virtual_thread) {
+    VarExpr var = op->node.as<tvm::IterVarNode>()->var;
+    const auto *extent = op->value.as<IntImm>();
+    CHECK(extent);
+
+    std::string name = var.get()->name_hint;
+    AnnotationType ann = kParallel;
+    if (op->attr_key == attr::thread_extent) {
+      if (name == "blockIdx.x")
+        ann = kBlockX;
+      else if (name == "blockIdx.y")
+        ann = kBlockY;
+      else if (name == "blockIdx.z")
+        ann = kBlockZ;
+      else if (name == "threadIdx.x")
+        ann = kThreadX;
+      else if (name == "threadIdx.y")
+        ann = kThreadY;
+      else if (name == "threadIdx.z")
+        ann = kThreadZ;
+      else
+        LOG(FATAL) << "invalid thread itervar " + name;
+    } else {
+      ann = kVirtualThread;
+    }
+
+    if (EnterItervar_(var, extent->value, ann)) {
+      IRVisitor::Visit_(op);
+      ExitItervar_();
+    }
+  } else {
+    IRVisitor::Visit_(op);
+  }
+}
+
+// memory access
+void FeatureVisitor::Visit_(const Load *op) {
+  EnterMem_(op->buffer_var, op->index);
+  IRVisitor::Visit_(op);
+  ExitMem_();
+}
+
+void FeatureVisitor::Visit_(const Store *op) {
+  EnterMem_(op->buffer_var, op->index);
+  IRVisitor::Visit_(op);
+  ExitMem_();
+}
+
+}  // namespace autotvm
+}  // namespace tvm
diff --git a/src/autotvm/feature_visitor.h b/src/autotvm/feature_visitor.h
new file mode 100644
index 000000000000..509a27e2638c
--- /dev/null
+++ b/src/autotvm/feature_visitor.h
@@ -0,0 +1,67 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file feature_visitor.h
+ * \brief Base class for feature extractor.
+ *        These features are used for machine learning cost model
+ */
+
+#ifndef TVM_AUTOTVM_FEATURE_VISITOR_H_
+#define TVM_AUTOTVM_FEATURE_VISITOR_H_
+
+#include <tvm/ir.h>
+#include <tvm/ir_visitor.h>
+#include <string>
+
+namespace tvm {
+namespace autotvm {
+
+using namespace tvm::ir;
+
+/*!
+ * \brief Type of for loop, used as one-hot encoding in features
+ */
+enum AnnotationType {
+  kBlockX, kBlockY, kBlockZ, kThreadX, kThreadY, kThreadZ,
+  kUnrolled, kVectorized, kParallel, kSerial, kVirtualThread,
+  kNum,
+};
+
+/*!
+ * \brief A base class for feature extractor, used for processing
+ * for loop and memory access in the IR
+ */
+class FeatureVisitor : public IRVisitor {
+ public:
+  // for loop
+  void Visit_(const For *op);
+  void Visit_(const AttrStmt *op);
+
+  // memory access
+  void Visit_(const Load *op);
+  void Visit_(const Store *op);
+
+ protected:
+  /*!
+ * \brief Enter a for loop node
+ * \param var The expression to be printed.
+ * \param length The output stream
+ * \param ann_type The type for the for loop
+ * \return skip Whether skip this node
+ */
+  virtual bool EnterItervar_(tvm::VarExpr var, int64_t length, AnnotationType ann_type) = 0;
+  /*! \brief Exit a for loop subtree */
+  virtual void ExitItervar_() = 0;
+  /*!
+   * \brief Enter a memory access node
+   * \param buffer_var The buffer to access.
+   * \param index Index expression
+   */
+  virtual void EnterMem_(tvm::VarExpr buffer_var, tvm::Expr index) = 0;
+  /*! \brief Exit a memory access node */
+  virtual void ExitMem_() = 0;
+};
+
+}  // namespace autotvm
+}  // namespace tvm
+
+#endif  // TVM_AUTOTVM_FEATURE_VISITOR_H_
diff --git a/src/autotvm/touch_extractor.cc b/src/autotvm/touch_extractor.cc
new file mode 100644
index 000000000000..a220bab042c4
--- /dev/null
+++ b/src/autotvm/touch_extractor.cc
@@ -0,0 +1,510 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file touch_extractor.cc
+ * \brief Extract feature of touch pattern of axes in lowered IR
+ */
+
+#include "touch_extractor.h"
+
+#include <set>
+#include <algorithm>
+#include <cmath>
+
+namespace tvm {
+namespace autotvm {
+
+int ParallelLevel(AnnotationType ann) {
+  switch (ann) {
+    case kBlockX: case kBlockY: case kBlockZ:
+      return 2;
+    case kThreadX: case kThreadY: case kThreadZ: case kParallel:
+      return 1;
+    default:
+      return 0;
+  }
+}
+
+// get touch pattern from index expression
+class IndexParser: public IRVisitor {
+ public:
+  void Parse(Expr expr) {
+    pattern_map.clear();
+    this->Visit(expr);
+  }
+
+  void Visit_(const Variable *op) {
+    // TODO(lmzheng): handle more index types (multiple occurrence)
+    if (pattern_map.count(op) == 0) {
+      pattern_map[op] = TouchPattern();
+      pattern_map[op].stride = next_stride_;
+      next_stride_ = 1;
+    }
+  }
+
+  void Visit_(const Mul *op) {
+    if (op->a.as<Variable>()) {
+      if (const auto stride = op->b.as<IntImm>()) {
+        next_stride_ = stride->value;
+      }
+    }
+    IRVisitor::Visit_(op);
+  }
+
+  std::unordered_map<const Variable*, TouchPattern> pattern_map;
+
+ private:
+  int64_t next_stride_ = 1;
+};
+
+// extract iter vars and their touch pattern from ir
+bool TouchExtractor::EnterItervar_(VarExpr var, int64_t length, AnnotationType ann_type) {
+  // do not insert duplicated occurrences of virtual thread
+  if (ann_type == kVirtualThread && itervar_map.count(var) != 0) {
+    skip_stack_size_.push_back(itervar_stack_.size());
+    return true;
+  } else {
+    itervar_stack_.push_back(var);
+    topdown_product_ *= length;
+
+    if (itervar_map.count(var) != 0) {
+      // find two duplicated axes
+      // these happens when we create tvm.thread_axis("threadIdx.x") once and
+      // bind it twice. Here we treat them as two axes
+      // so we create a snapshot for the old one and freeze it
+      VarExpr old = VarExpr(var.get()->name_hint);
+      itervar_map.insert({old, itervar_map[var]});
+      itervar_map.erase(var);
+    }
+
+    itervar_map.insert({var, ItervarFeature(var, length,
+                                            static_cast<int>(itervar_stack_.size()),
+                                            ann_type,
+                                            topdown_product_,
+                                            static_cast<int>(itervar_counter_++))});
+  }
+
+  return true;
+}
+
+void TouchExtractor::ExitItervar_() {
+  if (!skip_stack_size_.empty() && skip_stack_size_.back() == itervar_stack_.size()) {
+    skip_stack_size_.pop_back();
+    return;
+  }
+  VarExpr var = itervar_stack_.back();
+
+  // update count and reuse ratio for upper iter vars (includes self)
+  for (auto kv : itervar_map[var].touch_feature) {
+    if (kv.second.stride != 0) {  // multiply count
+      for (auto stack_var : itervar_stack_) {
+        auto touch_pattern = itervar_map[stack_var].touch_feature.find(kv.first);
+        CHECK(touch_pattern != itervar_map[stack_var].touch_feature.end());
+        touch_pattern->second.count *= itervar_map[var].length;
+      }
+    } else {                      // multiply reuse ratio
+      for (auto stack_var : itervar_stack_) {
+        auto touch_pattern = itervar_map[stack_var].touch_feature.find(kv.first);
+        CHECK(touch_pattern != itervar_map[stack_var].touch_feature.end());
+        touch_pattern->second.reuse *= itervar_map[var].length;
+      }
+    }
+  }
+  itervar_stack_.pop_back();
+
+  topdown_product_ /= itervar_map[var].length;
+  int64_t bottomup_product = -1;
+  for (auto kv : itervar_map[var].touch_feature) {
+    bottomup_product = std::max(bottomup_product, kv.second.count * kv.second.reuse);
+  }
+
+  itervar_map[var].bottomup_product = bottomup_product;
+
+  // push base to upper parallel axis
+  int para_level = ParallelLevel(itervar_map[var].ann);
+  // if is the separate line of parallel level, push the base to upper parallel level
+  if (!itervar_stack_.empty() &&
+      ParallelLevel(itervar_map[itervar_stack_.back()].ann) == para_level + 1) {
+    for (auto kv : itervar_map[var].touch_feature) {
+      for (auto stack_var : itervar_stack_) {
+        if (ParallelLevel(itervar_map[stack_var].ann) == para_level + 1) {
+          auto touch_pattern = itervar_map[stack_var].touch_feature.find(kv.first);
+          CHECK(touch_pattern != itervar_map[stack_var].touch_feature.end());
+          touch_pattern->second.thread_reuse = -kv.second.reuse;
+          touch_pattern->second.thread_count = -kv.second.count;
+          // NOTE: use minus as a flag to denote it is a base,
+          // indicating it is not the final value
+        }
+      }
+    }
+  }
+
+  for (auto kv : itervar_map[var].touch_feature) {
+    if (kv.second.thread_count < 0) {
+      itervar_map[var].touch_feature[kv.first].thread_count =
+          kv.second.count / (-kv.second.thread_count);
+      itervar_map[var].touch_feature[kv.first].thread_reuse =
+          kv.second.reuse / (-kv.second.thread_reuse);
+    }
+  }
+}
+
+void TouchExtractor::EnterMem_(VarExpr buffer_var, Expr index) {
+  std::string name = buffer_var.get()->name_hint;
+  TouchedBuffer buf = name + "_" + std::to_string(buffer_counter_[name]++);
+
+  // extract touch pattern from index
+  IndexParser parser;
+  parser.Parse(index);
+
+  // push up mem access info
+  for (auto var : itervar_stack_) {
+    auto x = parser.pattern_map.find(var.get());
+    if (x != parser.pattern_map.end()) {
+      itervar_map[var].touch_feature[buf] = x->second;
+    } else {
+      itervar_map[var].touch_feature[buf] = TouchPattern();
+    }
+  }
+}
+
+void TouchExtractor::ExitMem_() {
+}
+
+/*!
+ * \brief Get axis-based feature for all axes
+ * \param stmt The statement to be extracted
+ * \param bool Whether take log for numerical feature
+ * \param ret_feature The buffer where the return value is stored
+ *
+ * \note The format of return value is
+ * ((
+ *   ('_itervar_',  var),
+ *   ('_attr_',     length, nest_level, topdown, bottomup, one_hot_annotation),
+ *   ('_arith_',    add_ct, mul_ct, div_ct),
+ *   ('data_vec_0', stride, mod, count, reuse, thread_count, thread_reuse),
+ *   ('conv_0',     stride, mod, count, reuse, thread_count, thread_reuse),
+ * ),
+ * (
+ *   ('_itervar_',    var2),
+ *   ('_attr_',       length, nest_level, one_hot_annotation),
+ *   ('_arith_',      add_ct, mul_ct, div_ct),
+ *   ('kernel_vec_0', stride, mod, count, reuse, thread_count, thread_reuse),
+ *   ('conv_1',       stride, mod, count, reuse, thread_count, thread_reuse),
+ * ))
+ *
+ * Itervars are sorted according to their first occurrence position in IR.
+ * Buffers touched by an itervar are sorted by their unique names.
+ *
+ * \note If you want to flatten these features as the input of your model,
+ * You can use the faster one GetItervarFeatureFlatten below.
+ */
+void GetItervarFeature(Stmt stmt, bool take_log, Array<Array<Array<Expr> > > *ret_feature) {
+  // extract
+  TouchExtractor touch_analyzer;
+  touch_analyzer.Analyze(stmt);
+
+  // sort according to order
+  std::vector<VarExpr> vars;
+  for (auto kv : touch_analyzer.itervar_map) {
+    vars.push_back(kv.first);
+  }
+  std::sort(vars.begin(), vars.end(), [&](const VarExpr &lhs, const VarExpr &rhs) -> bool {
+    return touch_analyzer.itervar_map[lhs].order < touch_analyzer.itervar_map[rhs].order;
+  });
+
+  // whether take log for numerical feature
+  std::function<double(int64_t)> trans;
+  if (take_log) {
+    trans = [](int64_t x) {
+      if (x < 0)
+        return -std::log(-x+1) / std::log(2);
+      x = x + 1;
+      return std::log(x) / std::log(2);
+    };
+  } else {
+    trans = [](int64_t x) {
+      return x;
+    };
+  }
+
+  // serialize for front end
+  for (auto var : vars) {
+    Array<Array<Expr> > feature_row;
+    ItervarFeature &fea = touch_analyzer.itervar_map[var];
+    feature_row.push_back(Array<Expr>{std::string("_itervar_"), var});
+
+    Array<Expr> attr{std::string("_attr_"),
+                     FloatImm::make(Float(32), trans(fea.length)),
+                     IntImm::make(Int(32), fea.nest_level),
+                     FloatImm::make(Float(32), trans(fea.topdown_product)),
+                     FloatImm::make(Float(32), trans(fea.bottomup_product)),
+    };
+    // one hot annotation
+    for (int i = 0; i < kNum; i++) {
+      attr.push_back(i == fea.ann);
+    }
+    feature_row.push_back(attr);
+
+    // arithmetic
+    feature_row.push_back(Array<Expr>{std::string("_arith_"),
+                                      FloatImm::make(Float(32), trans(fea.add_ct)),
+                                      FloatImm::make(Float(32), trans(fea.mul_ct)),
+                                      FloatImm::make(Float(32), trans(fea.div_ct)),
+    });
+
+    // touch map
+    std::vector<TouchedBuffer> bufs;
+    for (auto kv : fea.touch_feature) {
+      bufs.push_back(kv.first);
+    }
+    std::sort(bufs.begin(), bufs.end());
+    for (auto k : bufs) {
+      TouchPattern &v = fea.touch_feature[k];
+      feature_row.push_back(Array<Expr>{k,
+                                        FloatImm::make(Float(32), trans(v.stride)),
+                                        FloatImm::make(Float(32), trans(v.mod)),
+                                        FloatImm::make(Float(32), trans(v.count)),
+                                        FloatImm::make(Float(32), trans(v.reuse)),
+                                        FloatImm::make(Float(32), trans(v.thread_count)),
+                                        FloatImm::make(Float(32), trans(v.thread_reuse)),
+      });
+    }
+
+    ret_feature->push_back(feature_row);
+  }
+}
+
+/*!
+ * \brief Get axis-based feature for all axes and flatten them into a one-dimensional vector.
+ * \param stmt The statement to be extracted
+ * \param bool Whether take log for numerical feature
+ * \param ret_feature The buffer where the return value is stored
+ *
+ * \note See GetItervarFeature for more details about the return value.
+ *       This is an optimized version of GetItervarFeature + Flatten. This runs much faster.
+ */
+void GetItervarFeatureFlatten(Stmt stmt, bool take_log, std::vector<float> *ret_feature) {
+  // extract touch feature
+  TouchExtractor touch_analyzer;
+  touch_analyzer.Analyze(stmt);
+
+  // sort according to order
+  std::vector<VarExpr> vars;
+  for (auto kv : touch_analyzer.itervar_map) {
+    vars.push_back(kv.first);
+  }
+  std::sort(vars.begin(), vars.end(), [&](const VarExpr &lhs, const VarExpr &rhs) -> bool {
+    return touch_analyzer.itervar_map[lhs].order < touch_analyzer.itervar_map[rhs].order;
+  });
+
+  // whether take log for numerical feature
+  std::function<float(int64_t)> trans;
+  if (take_log) {
+    trans = [](int64_t x) {
+      if (x < 0)
+        return -std::log(-x+1) / std::log(2);
+      x = x + 1;
+      return std::log(x) / std::log(2);
+    };
+  } else {
+    trans = [](int64_t x) {
+      return x;
+    };
+  }
+
+  // serialize for front end
+  for (auto var : vars) {
+    ItervarFeature &fea = touch_analyzer.itervar_map[var];
+
+    ret_feature->push_back(trans(fea.length));
+    ret_feature->push_back(fea.nest_level);
+    ret_feature->push_back(trans(fea.topdown_product));
+    ret_feature->push_back(trans(fea.bottomup_product));
+
+    // one hot annotation
+    for (int i = 0; i < kNum; i++) {
+      ret_feature->push_back(i == fea.ann);
+    }
+
+    // arithmetic
+    ret_feature->push_back(trans(fea.add_ct));
+    ret_feature->push_back(trans(fea.mul_ct));
+    ret_feature->push_back(trans(fea.div_ct));
+
+    // touch map
+    std::vector<TouchedBuffer> bufs;
+    for (auto kv : fea.touch_feature) {
+      bufs.push_back(kv.first);
+    }
+    std::sort(bufs.begin(), bufs.end());
+    for (auto k : bufs) {
+      TouchPattern &v = fea.touch_feature[k];
+      ret_feature->push_back(trans(v.stride));
+      ret_feature->push_back(trans(v.mod));
+      ret_feature->push_back(trans(v.count));
+      ret_feature->push_back(trans(v.reuse));
+      ret_feature->push_back(trans(v.thread_count));
+      ret_feature->push_back(trans(v.thread_reuse));
+    }
+  }
+}
+
+/*!
+ * \brief Get curve sample feature (relation feature) and flatten them into a one-dimensional vector.
+ * \param stmt The statement to be extracted
+ * \param sample_n The number of points used for sampling a curve (along one dimension)
+ * \param ret_feature The buffer where the return value is stored
+ */
+void GetCurveSampleFeatureFlatten(Stmt stmt, int sample_n, std::vector<float> *ret_feature) {
+  // extract touch feature
+  TouchExtractor touch_ext;
+  touch_ext.Analyze(stmt);
+
+  // sort according to order
+  std::vector<VarExpr> vars;
+  for (auto kv : touch_ext.itervar_map) {
+    vars.push_back(kv.first);
+  }
+  std::sort(vars.begin(), vars.end(), [&](const VarExpr &lhs, const VarExpr &rhs) -> bool {
+    return touch_ext.itervar_map[lhs].order < touch_ext.itervar_map[rhs].order;
+  });
+
+  int max_depth = 0;
+  std::map<TouchedBuffer, std::vector<double> > reuse_curve;
+  std::map<TouchedBuffer, std::vector<double> > count_curve;
+  std::map<TouchedBuffer, std::vector<double> > topdown_curve;
+  std::map<TouchedBuffer, std::vector<double> > bottomup_curve;
+  std::set<TouchedBuffer> innermost_buffers;
+  std::set<std::string> added;
+
+  // find maximum depth of loop nest
+  for (auto var : vars) {
+    ItervarFeature &fea = touch_ext.itervar_map[var];
+    max_depth = std::max(max_depth, fea.nest_level);
+  }
+
+  // mark inner most buffer
+  for (auto iter = vars.rbegin(); iter != vars.rend(); iter++) {
+    auto var = *iter;
+    ItervarFeature &fea = touch_ext.itervar_map[var];
+    if (fea.nest_level == max_depth) {
+      for (auto kv : fea.touch_feature) {
+        // delete buffer no (e.g. 'A_0' -> 'A', 'A_1' -> 'A')
+        std::string raw_name = kv.first.substr(0, kv.first.rfind("_"));
+
+        // delete memory scope (e.g. 'A.local' -> 'A', 'A.shared' -> 'A')
+        size_t pos = raw_name.find(".");
+        if (pos < kv.first.size())
+          raw_name = raw_name.substr(0, pos);
+
+        // If there are multiple innermost buffers that are derived from a same raw buffer
+        // We only record the last occurrence (note the `iter` is in reverse order)
+        // e.g. `A.local`, `A.shared` are derived from `A`, if they all occurred at the inner most
+        // level, we will only record the last occurrence,
+        if (added.find(raw_name) == added.end()) {
+          innermost_buffers.insert(kv.first);
+          added.insert(raw_name);
+        }
+      }
+    }
+  }
+
+  // pad the first point (zero) for all curves
+  for (auto buf : innermost_buffers) {
+    reuse_curve[buf].push_back(0);
+    count_curve[buf].push_back(0);
+    topdown_curve[buf].push_back(0);
+    bottomup_curve[buf].push_back(0);
+  }
+
+  // extract curves
+  for (auto var : vars) {
+    ItervarFeature &fea = touch_ext.itervar_map[var];
+    for (auto kv : fea.touch_feature) {
+      if (innermost_buffers.find(kv.first) != innermost_buffers.end()) {
+        reuse_curve[kv.first].emplace_back(std::log(kv.second.reuse) / std::log(2));
+        count_curve[kv.first].emplace_back(std::log(kv.second.count) / std::log(2));
+        topdown_curve[kv.first].emplace_back(std::log(fea.topdown_product) / std::log(2));
+        bottomup_curve[kv.first].emplace_back(std::log(fea.bottomup_product) / std::log(2));
+      }
+    }
+  }
+
+  // sample relation in the curve
+  auto sample_curve = [&](const std::vector<double> &x, const std::vector<double> &y,
+                          double weight) {
+    for (int i = 0; i < sample_n; i++) {
+      double xx = i * weight;
+      for (int j = static_cast<int>(x.size()) - 1; j >= 0; j--) {
+        if (xx > x[j] - 1e-6) {
+          ret_feature->emplace_back(y[j]);
+          ret_feature->emplace_back(xx - x[j]);
+          break;
+        }
+      }
+    }
+  };
+
+  // serialize to frontend
+  for (auto k : innermost_buffers) {
+    std::vector<double> &count = count_curve[k];
+    std::vector<double> &reuse = reuse_curve[k];
+    std::vector<double> &top_down = topdown_curve[k];
+
+    std::sort(count.begin(), count.end());
+    std::sort(reuse.begin(), reuse.end());
+    std::sort(top_down.begin(), top_down.end());
+
+    sample_curve(count, reuse, 1);
+    sample_curve(reuse, count, 1);
+    sample_curve(count, top_down, 1);
+    sample_curve(top_down, count, 1);
+  }
+}
+
+
+// register API for front end
+TVM_REGISTER_API("autotvm.feature.GetItervarFeature")
+.set_body([](TVMArgs args, TVMRetValue *ret) {
+  Stmt stmt = args[0];
+  bool take_log = args[1];
+  Array<Array<Array<Expr > > > ret_feature;
+
+  GetItervarFeature(stmt, take_log, &ret_feature);
+
+  *ret = ret_feature;
+});
+
+
+TVM_REGISTER_API("autotvm.feature.GetItervarFeatureFlatten")
+.set_body([](TVMArgs args, TVMRetValue *ret) {
+  Stmt stmt = args[0];
+  bool take_log = args[1];
+  std::vector<float> ret_feature;
+
+  GetItervarFeatureFlatten(stmt, take_log, &ret_feature);
+
+  TVMByteArray arr;
+  arr.size = sizeof(float) * ret_feature.size();
+  arr.data = reinterpret_cast<char *>(ret_feature.data());
+  *ret = arr;
+});
+
+
+TVM_REGISTER_API("autotvm.feature.GetCurveSampleFeatureFlatten")
+.set_body([](TVMArgs args, TVMRetValue *ret) {
+  Stmt stmt = args[0];
+  bool take_log = args[1];
+  std::vector<float> ret_feature;
+
+  GetCurveSampleFeatureFlatten(stmt, take_log, &ret_feature);
+
+  TVMByteArray arr;
+  arr.size = sizeof(float) * ret_feature.size();
+  arr.data = reinterpret_cast<char *>(ret_feature.data());
+  *ret = arr;
+});
+
+
+}  // namespace autotvm
+}  // namespace tvm
diff --git a/src/autotvm/touch_extractor.h b/src/autotvm/touch_extractor.h
new file mode 100644
index 000000000000..1fe5ee20f70c
--- /dev/null
+++ b/src/autotvm/touch_extractor.h
@@ -0,0 +1,124 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file touch_extractor.h
+ * \brief Extract feature of touch pattern of axes in lowered IR
+ */
+
+#ifndef TVM_AUTOTVM_TOUCH_EXTRACTOR_H_
+#define TVM_AUTOTVM_TOUCH_EXTRACTOR_H_
+
+#include <tvm/ir.h>
+#include <tvm/ir_visitor.h>
+#include <tvm/api_registry.h>
+#include <stack>
+#include <vector>
+#include <map>
+#include <string>
+#include <deque>
+#include "feature_visitor.h"
+
+namespace tvm {
+namespace autotvm {
+
+using TouchedBuffer = std::string;
+
+// touch pattern buf[(stride * var) % mod) + other]
+struct TouchPattern {
+  int64_t stride{0};
+  int64_t mod{-1};  // -1 for +inf
+
+  int64_t count{1};
+  int64_t reuse{1};
+  int64_t thread_count{0};  // count when move thread axis into innermost
+  int64_t thread_reuse{0};  // reuse ratio move thread axis into innermost
+};
+
+// all the feature of an iter var
+struct ItervarFeature {
+  ItervarFeature(VarExpr var,
+                 int64_t extent,
+                 int nest,
+                 AnnotationType ann_type,
+                 int64_t topdown,
+                 int counter)
+      : length(extent), nest_level(nest), ann(ann_type), topdown_product(topdown), order(counter) {}
+  ItervarFeature() {}
+
+  // Axis Attributes
+  int64_t length;
+  int nest_level;
+  AnnotationType ann;         // one-hot axis type
+  int64_t topdown_product;    // accumulative product of axis length, in top-down order
+  int64_t bottomup_product;   // accumulative product of axis length, in bottom-up order
+  // bottomup_product = reuse * count for any touched buffer
+
+  int order;  // used for soring axis
+
+  // Arithmetic feature
+  int add_ct{0};
+  int mul_ct{0};
+  int div_ct{0};
+
+  // Memory Touch Feature
+  std::unordered_map<TouchedBuffer, TouchPattern> touch_feature;
+};
+
+// extract iter vars and their touch pattern from ir
+class TouchExtractor : public FeatureVisitor {
+ public:
+  void Analyze(Stmt stmt) {
+    this->Visit(stmt);
+  }
+
+  // arithmetic stats
+  void Visit_(const Add *op) {
+    if (op->type.is_float())
+      itervar_map[itervar_stack_.back()].add_ct++;
+    IRVisitor::Visit_(op);
+  }
+
+  void Visit_(const Sub *op) {
+    if (op->type.is_float())
+      itervar_map[itervar_stack_.back()].add_ct++;
+    IRVisitor::Visit_(op);
+  }
+
+  void Visit_(const Mul *op) {
+    if (op->type.is_float())
+      itervar_map[itervar_stack_.back()].mul_ct++;
+    IRVisitor::Visit_(op);
+  }
+
+  void Visit_(const Div *op) {
+    if (op->type.is_float())
+      itervar_map[itervar_stack_.back()].div_ct++;
+    IRVisitor::Visit_(op);
+  }
+
+  void Visit_(const Mod *op) {
+    if (op->type.is_float())
+      itervar_map[itervar_stack_.back()].div_ct++;
+    IRVisitor::Visit_(op);
+  }
+
+  std::unordered_map<VarExpr, ItervarFeature, tvm::ExprHash, tvm::ExprEqual> itervar_map;
+
+ private:
+  bool EnterItervar_(VarExpr var, int64_t length, AnnotationType ann_type);
+  void ExitItervar_();
+  void EnterMem_(VarExpr buffer_var, Expr index);
+  void ExitMem_();
+
+  int64_t topdown_product_{1};
+  std::map<std::string, size_t> buffer_counter_;
+  size_t itervar_counter_{0};
+  std::deque<VarExpr> itervar_stack_;  // use deque instead of stack for indexing
+  std::deque<size_t> skip_stack_size_;
+
+  using IRVisitor::Visit_;
+};
+
+}  // namespace autotvm
+}  // namespace tvm
+
+#endif  // TVM_AUTOTVM_TOUCH_EXTRACTOR_H_
diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc
index 426b06b4239d..f9b6226f86c4 100644
--- a/src/codegen/build_module.cc
+++ b/src/codegen/build_module.cc
@@ -73,7 +73,7 @@ Target CreateTarget(const std::string& target_name,
     } else {
       t->device_type = kDLROCM;
     }
-    t->keys_array.push_back(ir::StringImm::make("rocm"));
+    t->keys_array.push_back(ir::StringImm::make(target_name));
     t->keys_array.push_back(ir::StringImm::make("gpu"));
     t->max_num_threads = 256;
     if (t->device_name == "intel_graphics") {
@@ -195,11 +195,7 @@ Target Target::create(const std::string& target_str) {
     options.push_back(item);
   }
 
-  if (device_name == "rasp") {
-    return target::rasp(options);
-  } else {
-    return CreateTarget(target_name, options);
-  }
+  return CreateTarget(target_name, options);
 }
 
 /*! \brief Entry to hold the Target context stack. */
diff --git a/src/pass/verify_gpu_code.cc b/src/pass/verify_gpu_code.cc
index aa1762221d8d..363b7c4cf7cc 100644
--- a/src/pass/verify_gpu_code.cc
+++ b/src/pass/verify_gpu_code.cc
@@ -18,13 +18,13 @@ class GPUCodeVerifier : public IRVisitor {
   bool Verify(tvm::Stmt stmt,
               int64_t max_local_memory_per_block,
               int64_t max_shared_memory_per_block,
-              int64_t max_thread_per_block,
+              int64_t max_threads_per_block,
               int64_t max_thread_x,
               int64_t max_thread_y,
               int64_t max_thread_z) {
     max_local_memory_per_block_ = static_cast<size_t>(max_local_memory_per_block);
     max_shared_memory_per_block_ = static_cast<size_t>(max_shared_memory_per_block);
-    max_thread_per_block_ = static_cast<size_t>(max_thread_per_block);
+    max_threads_per_block_ = static_cast<size_t>(max_threads_per_block);
     max_thread_x_ = static_cast<size_t>(max_thread_x);
     max_thread_y_ = static_cast<size_t>(max_thread_y);
     max_thread_z_ = static_cast<size_t>(max_thread_z);
@@ -52,7 +52,7 @@ class GPUCodeVerifier : public IRVisitor {
 
     if (nest_level_ == 0) {
       // exit a kernel, check the validity
-      valid_ &= thread_per_block_ <= max_thread_per_block_;
+      valid_ &= thread_per_block_ <= max_threads_per_block_;
 
       valid_ &= local_memory_per_block_ <= max_local_memory_per_block_;
       valid_ &= shared_memory_per_block_ <= max_shared_memory_per_block_;
@@ -117,7 +117,7 @@ class GPUCodeVerifier : public IRVisitor {
 
   size_t max_local_memory_per_block_;
   size_t max_shared_memory_per_block_;
-  size_t max_thread_per_block_;
+  size_t max_threads_per_block_;
   size_t max_thread_x_, max_thread_y_, max_thread_z_;
 
   bool valid_{true};
@@ -137,26 +137,34 @@ bool VerifyGPUCode(Stmt stmt,
                    Map<std::string, Expr> constraints) {
   GPUCodeVerifier verifier;
 
-  auto get_int = [&constraints](std::string key, int64_t def) {
-    auto iter = constraints.find(key);
-    if (iter != constraints.end()) {
-      return ((*iter).second).as<IntImm>()->value;
-    } else {
-      return def;
-    }
-  };
-
-  int64_t max_local_memory_per_block = get_int("max_local_memory_per_block", INT64_MAX);
-  int64_t max_shared_memory_per_block = get_int("max_shared_memory_per_block", INT64_MAX);
-  int64_t max_thread_per_block = get_int("max_thread_per_block", INT64_MAX);
-  int64_t max_thread_x = get_int("max_thread_x", INT64_MAX);
-  int64_t max_thread_y = get_int("max_thread_y", INT64_MAX);
-  int64_t max_thread_z = get_int("max_thread_z", INT64_MAX);
+  int64_t max_local_memory_per_block = INT64_MAX;
+  int64_t max_shared_memory_per_block = INT64_MAX;
+  int64_t max_threads_per_block = INT64_MAX;
+  int64_t max_thread_x = INT64_MAX;
+  int64_t max_thread_y = INT64_MAX;
+  int64_t max_thread_z = INT64_MAX;
+
+  for (auto iter : constraints) {
+    if (iter.first == "max_local_memory_per_block")
+      max_local_memory_per_block = (iter.second).as<IntImm>()->value;
+    else if (iter.first == "max_shared_memory_per_block")
+      max_shared_memory_per_block = (iter.second).as<IntImm>()->value;
+    else if (iter.first == "max_threads_per_block")
+      max_threads_per_block = (iter.second).as<IntImm>()->value;
+    else if (iter.first == "max_thread_x")
+      max_thread_x = (iter.second).as<IntImm>()->value;
+    else if (iter.first == "max_thread_y")
+      max_thread_y = (iter.second).as<IntImm>()->value;
+    else if (iter.first == "max_thread_z")
+      max_thread_z = (iter.second).as<IntImm>()->value;
+    else
+      LOG(FATAL) << "Invalid check item: " << iter.first;
+  }
 
   return verifier.Verify(stmt,
                          max_local_memory_per_block,
                          max_shared_memory_per_block,
-                         max_thread_per_block,
+                         max_threads_per_block,
                          max_thread_x,
                          max_thread_y,
                          max_thread_z);
diff --git a/tests/python/integration/test_tuning.py b/tests/python/integration/test_tuning.py
new file mode 100644
index 000000000000..23816b2b6f78
--- /dev/null
+++ b/tests/python/integration/test_tuning.py
@@ -0,0 +1,158 @@
+"""
+Test the tuner
+"""
+import logging
+import time
+
+import tvm
+
+from tvm import autotvm
+from tvm.autotvm.tuner import RandomTuner
+
+@autotvm.template
+def conv2d_no_batching(N, H, W, CI, CO, KH, KW):
+    """An example template for testing"""
+    assert N == 1, "Only consider batch_size = 1 in this template"
+
+    data = tvm.placeholder((N, CI, H, W), name='data')
+    kernel = tvm.placeholder((CO, CI, KH, KW), name='kernel')
+
+    rc = tvm.reduce_axis((0, CI), name='rc')
+    ry = tvm.reduce_axis((0, KH), name='ry')
+    rx = tvm.reduce_axis((0, KW), name='rx')
+
+    conv = tvm.compute(
+        (N, CO, H - KH + 1, W - KW + 1),
+        lambda nn, ff, yy, xx: tvm.sum(
+            data[nn, rc, yy + ry, xx + rx] * kernel[ff, rc, ry, rx],
+            axis=[rc, ry, rx]), tag="conv2d_nchw")
+
+    s = tvm.create_schedule([conv.op])
+
+    output = conv
+    OL = s.cache_write(conv, 'local')
+
+    # create cache stage
+    AA = s.cache_read(data, 'shared', [OL])
+    WW = s.cache_read(kernel, 'shared', [OL])
+    AL = s.cache_read(AA, 'local', [OL])
+    WL = s.cache_read(WW, 'local', [OL])
+
+    # tile and bind spatial axes
+    n, f, y, x = s[output].op.axis
+    cfg = autotvm.get_config()
+    cfg.define_split("tile_f", cfg.axis(f), num_outputs=4)
+    cfg.define_split("tile_y", cfg.axis(y), num_outputs=4)
+    cfg.define_split("tile_x", cfg.axis(x), num_outputs=4)
+    bf, vf, tf, fi = cfg["tile_f"].apply(s, output, f)
+    by, vy, ty, yi = cfg["tile_y"].apply(s, output, y)
+    bx, vx, tx, xi = cfg["tile_x"].apply(s, output, x)
+    kernel_scope = n  # this is the scope to attach global config inside this kernel
+
+    s[output].bind(bf, tvm.thread_axis("blockIdx.z"))
+    s[output].bind(by, tvm.thread_axis("blockIdx.y"))
+    s[output].bind(bx, tvm.thread_axis("blockIdx.x"))
+    s[output].bind(vf, tvm.thread_axis("vthread"))
+    s[output].bind(vy, tvm.thread_axis("vthread"))
+    s[output].bind(vx, tvm.thread_axis("vthread"))
+    s[output].bind(tf, tvm.thread_axis("threadIdx.z"))
+    s[output].bind(ty, tvm.thread_axis("threadIdx.y"))
+    s[output].bind(tx, tvm.thread_axis("threadIdx.x"))
+    s[output].reorder(n, bf, by, bx, vf, vy, vx, tf, ty, tx, fi, yi, xi)
+    s[OL].compute_at(s[output], tx)
+
+    # tile and bind reduction axes
+    n, f, y, x = s[OL].op.axis
+    rc, ry, rx = s[OL].op.reduce_axis
+    cfg.define_split("tile_rc", cfg.axis(rc), num_outputs=3)
+    cfg.define_split("tile_ry", cfg.axis(ry), num_outputs=3)
+    cfg.define_split("tile_rx", cfg.axis(rx), num_outputs=3)
+    rco, rcm, rci = cfg['tile_rc'].apply(s, OL, rc)
+    ryo, rym, ryi = cfg['tile_rx'].apply(s, OL, ry)
+    rxo, rxm, rxi = cfg['tile_ry'].apply(s, OL, rx)
+    s[OL].reorder(rco, ryo, rxo, rcm, rym, rxm, rci, ryi, rxi, n, f, y, x)
+
+    s[AA].compute_at(s[OL], rxo)
+    s[WW].compute_at(s[OL], rxo)
+    s[AL].compute_at(s[OL], rxm)
+    s[WL].compute_at(s[OL], rxm)
+
+    # cooperative fetching
+    for load in [AA, WW]:
+        n, f, y, x = s[load].op.axis
+        fused = s[load].fuse(n, f, y, x)
+        tz, fused = s[load].split(fused, nparts=cfg["tile_f"].size[2])
+        ty, fused = s[load].split(fused, nparts=cfg["tile_y"].size[2])
+        tx, fused = s[load].split(fused, nparts=cfg["tile_x"].size[2])
+        s[load].bind(tz, tvm.thread_axis("threadIdx.z"))
+        s[load].bind(ty, tvm.thread_axis("threadIdx.y"))
+        s[load].bind(tx, tvm.thread_axis("threadIdx.x"))
+
+    # tune unroll
+    cfg.define_knob("auto_unroll_max_step", [0, 512, 1500])
+    cfg.define_knob("unroll_explicit", [0, 1])
+    s[output].pragma(kernel_scope, 'auto_unroll_max_step', cfg['auto_unroll_max_step'].val)
+    s[output].pragma(kernel_scope, 'unroll_explicit', cfg['unroll_explicit'].val)
+
+    return s, [data, kernel, conv]
+
+def get_sample_task(target=tvm.target.cuda(), target_host=None):
+    """return a sample task for testing"""
+    task = autotvm.task.create(conv2d_no_batching,
+                               args=(1, 7, 7, 512, 512, 3, 3),
+                               target=target, target_host=target_host)
+    return task, target
+
+
+def test_task_tuner_without_measurement():
+    """test task and tuner without measurement"""
+    task, target = get_sample_task()
+
+    def measure_batch(inputs):
+        from tvm.autotvm import MeasureResult
+
+        results = []
+        for inp in inputs:
+            tic = time.time()
+            # do nothing
+            time.sleep(0.001)
+            results.append(MeasureResult([time.time() - tic], 0,
+                                         time.time() - tic, time.time()))
+        return results
+    measure_option = autotvm.measure_option(mode='custom',
+                                            custom_measure_batch=measure_batch)
+
+    logging.info("%s", task.config_space)
+
+    # new tuner and recorder
+    for tuner_class in [autotvm.tuner.RandomTuner, autotvm.tuner.GridSearchTuner]:
+        tuner = tuner_class(task)
+        tuner.tune(n_trial=10, measure_option=measure_option)
+
+def test_tuning_with_measure():
+    def check(target, target_host):
+        ctx = tvm.context(target, 0)
+        if not ctx.exist:
+            logging.info("Skip test because %s is not available" % target)
+            return
+
+        # init task
+        task, target = get_sample_task(target, target_host)
+        logging.info("%s", task.config_space)
+
+        measure_option = autotvm.measure_option(mode='local',
+                                                timeout=4,
+                                                number=2)
+
+        tuner = RandomTuner(task)
+        tuner.tune(n_trial=10, measure_option=measure_option)
+
+    check("cuda", None)
+    check("opencl", None)
+
+if __name__ == "__main__":
+    # only print log when invoked from main
+    logging.basicConfig(level=logging.INFO)
+
+    test_task_tuner_without_measurement()
+    test_tuning_with_measure()
diff --git a/tests/python/unittest/test_autotvm_common.py b/tests/python/unittest/test_autotvm_common.py
new file mode 100644
index 000000000000..3a6883f69489
--- /dev/null
+++ b/tests/python/unittest/test_autotvm_common.py
@@ -0,0 +1,50 @@
+"""Common utilities for testing autotvm"""
+import time
+
+import tvm
+from tvm import autotvm
+from tvm.autotvm import MeasureInput, MeasureResult
+
+@autotvm.template
+def matmul(N, L, M, dtype):
+    A = tvm.placeholder((N, L), name='A', dtype=dtype)
+    B = tvm.placeholder((L, M), name='B', dtype=dtype)
+
+    k = tvm.reduce_axis((0, L), name='k')
+    C = tvm.compute((N, M), lambda i, j: tvm.sum(A[i, k] * B[k, j], axis=k), name='C')
+    s = tvm.create_schedule(C.op)
+
+    # schedule
+    y, x = s[C].op.axis
+    k = s[C].op.reduce_axis[0]
+
+    ##### define space begin #####
+    cfg = autotvm.get_config()
+    cfg.define_split("tile_y", y, num_outputs=2)
+    cfg.define_split("tile_x", x, num_outputs=2)
+    ##### define space end #####
+
+    # schedule according to config
+    yo, yi = cfg["tile_y"].apply(s, C, y)
+    xo, xi = cfg["tile_x"].apply(s, C, x)
+
+    s[C].reorder(yo, xo, k, yi, xi)
+
+    return s, [A, B, C]
+
+def get_sample_task(n=128):
+    """return a sample task for testing"""
+    target = tvm.target.create("llvm")
+    task = autotvm.task.create(matmul, args=(n, n, n, 'float32'), target=target)
+    return task, target
+
+def get_sample_records(n):
+    """get sample records for testing"""
+    tsk, target = get_sample_task()
+
+    inps, ress = [], []
+    for i in range(n):
+        inps.append(MeasureInput(target, tsk, tsk.config_space.get(i)))
+        ress.append(MeasureResult((i+1,), 0, i, time.time()))
+    return list(zip(inps, ress))
+
diff --git a/tests/python/unittest/test_autotvm_database.py b/tests/python/unittest/test_autotvm_database.py
new file mode 100644
index 000000000000..72f0e082ca91
--- /dev/null
+++ b/tests/python/unittest/test_autotvm_database.py
@@ -0,0 +1,234 @@
+"""Test database"""
+import copy
+import logging
+import time
+
+import numpy as np
+import tvm
+
+from tvm import autotvm
+from tvm.autotvm import database
+from tvm.autotvm.measure.measure_methods import HashMismatchError
+from tvm.autotvm.record import encode, MeasureInput, MeasureResult
+
+from test_autotvm_common import get_sample_task, get_sample_records
+
+def test_save_load():
+    logging.info("test basic db load/save ...")
+    records = get_sample_records(3)
+    inp1, res1 = records[0]
+    inp2, res2 = records[1]
+    inp3, _ = records[2]
+
+    _db = database.DummyDatabase()
+    _db.flush()
+    _db.save(inp1, res1)
+    _db.save(inp2, res2)
+
+    load1 = _db.load(inp1)
+    load2 = _db.load(inp2)
+    load3 = _db.load(inp3)
+    assert load1 == res1
+    assert load2 == res2
+    assert load3 is None
+    assert load1 != load2
+
+TRIAL_LIMIT = 2
+
+def test_db_filter():
+    logging.info("test db filter ...")
+
+    # Pick a GPU target because there are more likely to be failures/invalid configs
+    task, target = get_sample_task()
+
+    ctx = tvm.context(str(target))
+    if not ctx.exist:
+        logging.warning("Skip this test because there is no supported device for test")
+
+    batch_size = 2
+
+    measure_option = autotvm.measure_option(mode='local-nofork', timeout=2)
+    measure_batch = autotvm.measure.create_measure_batch(task, measure_option)
+
+    ct = 0
+    all_inputs = list()
+    all_results = list()
+    batches = list()
+    tuner = autotvm.tuner.RandomTuner(task)
+    while ct < TRIAL_LIMIT:
+        inputs = list()
+        for i in range(batch_size):
+            cfg = tuner.next_batch(1)[0]
+            inputs.append((MeasureInput(target, task, cfg)))
+            all_inputs.append(inputs[-1])
+        batches.append(inputs)
+        results = measure_batch(inputs)
+        all_results += results
+        ct += 1
+
+    del measure_batch
+
+    db = database.DummyDatabase()
+    db.flush()
+
+    # First setting, memoize one input at a time, check that each is saved and replayed
+    measure_option = autotvm.measure_option(mode='local-nofork', timeout=2, replay_db=db)
+    measure_batch = autotvm.measure.create_measure_batch(task, measure_option)
+
+    for i in range(len(all_inputs)+1):
+        db.flush()
+        for j in range(i):
+            db.save(all_inputs[j], all_results[j])
+
+        for k in range(len(batches)):
+            batch = batches[k]
+            batch_result = measure_batch(batch)
+            for l in range(batch_size):
+                all_idx = k*batch_size + l
+                assert batch_result[l] is not None
+                if all_idx < i:
+                    assert encode(batch[l], batch_result[l]) == encode(batch[l], all_results[all_idx]), \
+                        "(no retry) EXPECTED MATCH, GOT MISMATCH"
+                else:
+                    assert encode(batch[l], batch_result[l]) != encode(batch[l], all_results[all_idx]), \
+                        "(no retry) EXPECTED MISMATCH, GOT MATCH"
+
+    del measure_batch
+
+def test_db_hash():
+    logging.info("test db hash check ...")
+    inp1, res1 = get_sample_records(1)[0]
+    inp2 = copy.deepcopy(inp1)
+    inp1.config.code_hash = 'cafecafe'
+    inp2.config.code_hash = 'dbffdbff'
+    res2l = list(tuple(res1))
+
+    # set timestamp
+    res2l[-1] = -1
+    res2 = MeasureResult(*res2l)
+    _db = database.DummyDatabase()
+    _db.flush()
+    _db.save(inp1, res1, extend=True)
+    _db.save(inp2, res2, extend=True)
+
+    load1 = _db.load(inp1)
+    load2 = _db.load(inp2)
+    assert load1 != load2
+    assert load1.timestamp != -1
+    assert load2.timestamp == -1
+
+def test_db_latest_all():
+    logging.info("test db load w/ multiple results ...")
+    inp1, res1 = get_sample_records(1)[0]
+    lis1 = list(tuple(res1))
+    lis2 = list(tuple(res1))
+    lis3 = list(tuple(res1))
+
+    # set timestamp
+    lis1[-1] = 0.0
+    lis2[-1] = 1.1
+    lis3[-1] = 9999.9999
+    res1 = MeasureResult(*lis1)
+    res2 = MeasureResult(*lis2)
+    res3 = MeasureResult(*lis3)
+
+    _db = database.DummyDatabase()
+    _db.flush()
+    _db.save(inp1, res1, extend=True)
+    load1 = _db.load(inp1)
+    assert load1.timestamp == 0.0
+    _db.save(inp1, res2, extend=True)
+    load2 = _db.load(inp1)
+    assert load2.timestamp == 1.1
+    _db.save(inp1, res3, extend=True)
+    load3 = _db.load(inp1)
+    assert load3.timestamp == 9999.9999
+
+    load4 = _db.load(inp1, get_all=True)
+    assert encode(inp1, load4[0]) == encode(inp1, res1)
+    assert encode(inp1, load4[1]) == encode(inp1, res2)
+    assert encode(inp1, load4[2]) == encode(inp1, res3)
+
+def test_db_save_replay():
+    logging.info("test db save (from measure_batch) and replay ...")
+    _db = database.DummyDatabase()
+    _db.flush()
+
+    task, target = get_sample_task()
+
+    ctx = tvm.context(str(target))
+    if not ctx.exist:
+        logging.warning("Skip this test because there is no supported device for test")
+
+    measure_option = autotvm.measure_option(mode='local-nofork',
+                                            timeout=2,
+                                            replay_db=_db, save_to_replay_db=True)
+    measure_batch = autotvm.measure.create_measure_batch(task, measure_option)
+
+    batch_size = 2
+
+    ct = 0
+    all_inputs = list()
+    all_results = list()
+    batches = list()
+    tuner = autotvm.tuner.RandomTuner(task)
+    while ct < TRIAL_LIMIT:
+        inputs = list()
+        for i in range(batch_size):
+            cfg = tuner.next_batch(1)[0]
+            inputs.append((MeasureInput(target, task, cfg)))
+            all_inputs.append(inputs[-1])
+        batches.append(inputs)
+        results = measure_batch(inputs)
+        all_results += results
+        ct += 1
+
+    assert len(_db.db.keys()) == batch_size * TRIAL_LIMIT, \
+        "%d vs %d" % (len(_db.db.keys()), batch_size * TRIAL_LIMIT)
+
+    all_results_2 = measure_batch(all_inputs)
+    all_results_3 = measure_batch(all_inputs)
+
+    for i in range(len(all_results)):
+        encr1 = encode(all_inputs[i], all_results[i])
+        encr2 = encode(all_inputs[i], all_results_2[i])
+        encr3 = encode(all_inputs[i], all_results_3[i])
+        assert encr1 == encr2, "EXPECTED MATCH WITH SAVE REPLAY (first replay), got MISMATCH"
+        assert encr2 == encr3, "EXPECTED MATCH WITH SAVE REPLAY (second replay), got MISMATCH"
+
+    del measure_batch
+
+def test_check_hashmismatch():
+    logging.info("test hash mismatch check")
+
+    task, target = get_sample_task()
+
+    ctx = tvm.context(str(target))
+    if not ctx.exist:
+        logging.warning("Skip this test because there is no supported device for test")
+
+    measure_option = autotvm.measure_option(mode='local-nofork')
+    measure_batch = autotvm.measure.create_measure_batch(task, measure_option)
+
+    inputs = list()
+    cfg = task.config_space.get(np.random.randint(len(task.config_space)))
+    # notvalidh is not a valid CRC32 hash (not hex)
+    cfg.code_hash = 'notvalidh'
+    inputs.append((MeasureInput(target, task, cfg)))
+
+    try:
+        results = measure_batch(inputs)
+        assert False, "HashMismatchError should be raised"
+    except HashMismatchError:
+        pass
+
+    del measure_batch
+
+if __name__ == '__main__':
+    logging.basicConfig(level=logging.INFO)
+    test_save_load()
+    test_db_filter()
+    test_db_hash()
+    test_db_latest_all()
+    test_db_save_replay()
+    test_check_hashmismatch()
diff --git a/tests/python/unittest/test_autotvm_dispatch_context.py b/tests/python/unittest/test_autotvm_dispatch_context.py
new file mode 100644
index 000000000000..6c718e5bd041
--- /dev/null
+++ b/tests/python/unittest/test_autotvm_dispatch_context.py
@@ -0,0 +1,36 @@
+"""Test dispatcher.
+The dispatcher can choose which template to use according
+to the parameters of workload"""
+
+from collections import namedtuple
+from tvm.autotvm.task import dispatcher, DispatchContext
+
+SimpleWorkload = namedtuple("SimpleWorkload", ["key"])
+SimpleConfig = namedtuple("SimpleConfig", ["template_key"])
+
+def test_dispatch():
+    @dispatcher
+    def my_dispatcher(a, b):
+        return SimpleWorkload(key=a + b)
+
+    @my_dispatcher.register("spatial_pack")
+    def _sp_pack_add(cfg, a, b):
+        return b + 100
+
+    @my_dispatcher.register("im2col")
+    def _im2col_add(cfg, a, b):
+        return a + 1
+
+    class SimpleDispatcher(DispatchContext):
+        def query(self, target, workload):
+            tkey = "spatial_pack" if workload.key > 2 else "im2col"
+            return SimpleConfig(tkey)
+
+    with SimpleDispatcher():
+        # im2col
+        assert my_dispatcher(1, 0) == 2
+        # spack
+        assert my_dispatcher(1, 100) == 200
+
+if __name__ == "__main__":
+    test_dispatch()
diff --git a/tests/python/unittest/test_autotvm_executor.py b/tests/python/unittest/test_autotvm_executor.py
new file mode 100644
index 000000000000..6dd104210ccf
--- /dev/null
+++ b/tests/python/unittest/test_autotvm_executor.py
@@ -0,0 +1,47 @@
+"""Test local executor"""
+import time
+
+from tvm.autotvm.measure import LocalExecutor, executor
+
+def slow(n):
+    r = 0
+    for i in range(0, n+1):
+        r += i
+    return r
+
+def fast(n):
+    return n*(n+1)//2
+
+def test_local_measure_async():
+    ex = LocalExecutor()
+    f1 = ex.submit(slow, 9999999)
+    f2 = ex.submit(fast, 9999999)
+    t1 = 0
+    t2 = 0
+    while True:
+        if t1 == 0 and f1.done():
+            t1 = time.time()
+        if t2 == 0 and f2.done():
+            t2 = time.time()
+        if t1 != 0 and t2 != 0:
+            break
+    assert t2 < t1, "Expected fast async job to finish first!"
+    assert f1.get() == f2.get()
+
+def timeout_job(n):
+    time.sleep(n * 1.5)
+
+def test_timeout():
+    timeout = 0.5
+
+    ex = LocalExecutor(timeout=timeout)
+
+    f1 = ex.submit(timeout_job, timeout)
+    while not f1.done():
+        pass
+    res = f1.get()
+    assert isinstance(res, executor.TimeoutError)
+
+if __name__ == "__main__":
+    test_local_measure_async()
+    test_timeout()
diff --git a/tests/python/unittest/test_autotvm_feature.py b/tests/python/unittest/test_autotvm_feature.py
new file mode 100644
index 000000000000..1b806ded4393
--- /dev/null
+++ b/tests/python/unittest/test_autotvm_feature.py
@@ -0,0 +1,99 @@
+"""Test feature extraction"""
+
+import numpy as np
+
+import tvm
+from tvm.autotvm import feature
+
+def test_iter_feature_gemm():
+    N = 128
+
+    k = tvm.reduce_axis((0, N), 'k')
+    A = tvm.placeholder((N, N), name='A')
+    B = tvm.placeholder((N, N), name='B')
+    C = tvm.compute(
+        A.shape,
+        lambda y, x: tvm.sum(A[y, k] * B[k, x], axis=k),
+        name='C')
+
+    s = tvm.create_schedule(C.op)
+
+    feas = feature.get_itervar_feature(s, [A, B, C], take_log=False)
+
+    expected = [
+        {
+            '_attr_': [128, 1, 128, 2097152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
+            'A_0': [128, -1, 16384, 128, 0, 0], 'B_0': [0, -1, 16384, 128, 0, 0],
+            'C_0': [128, -1, 16384, 128, 0, 0], 'C_1': [128, -1, 16384, 128, 0, 0],
+        },
+        {
+            '_attr_': [128, 2, 16384, 16384, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
+            'A_0': [0, -1, 128, 128, 0, 0], 'B_0': [1, -1, 16384, 1, 0, 0],
+            'C_0': [1, -1, 128, 128, 0, 0], 'C_1': [1, -1, 128, 128, 0, 0],
+        },
+        {
+            '_attr_': [128, 3, 2097152, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
+            'A_0': [1, -1, 128, 1, 0, 0], 'B_0': [128, -1, 128, 1, 0, 0],
+            'C_1': [0, -1, 1, 128, 0, 0], 'C_2':  [0, -1, 1, 128, 0, 0],
+        }
+    ]
+
+    for ans, row in zip(expected, feas):
+        for pair in row:
+            if pair[0] not in ans:
+                continue
+            assert ans[pair[0]] == pair[1:], "%s: %s vs %s" % (pair[0], ans[pair[0]], pair[1:])
+
+
+def test_feature_shape():
+    """test the dimensions of flatten feature are the same"""
+
+    N = 1024
+    n_sample = 100
+
+    def get_gemm_feature(target):
+        k = tvm.reduce_axis((0, N), 'k')
+        A = tvm.placeholder((N, N), name='A')
+        B = tvm.placeholder((N, N), name='B')
+        C = tvm.compute(A.shape, lambda y, x: tvm.sum(A[y, k] * B[k, x], axis=k),
+                        name='C')
+
+        s = tvm.create_schedule(C.op)
+
+        y, x = s[C].op.axis
+        axes = list(s[C].tile(y, x, 8, 8)) + [k]
+        perm = np.random.permutation(5)
+        axes = [axes[x] for x in perm]
+        s[C].reorder(*axes)
+
+        if "gpu" in target.keys:
+            pick = []
+            # filter out reduction axis
+            for i in range(len(perm)):
+                if perm[i] != 4:
+                    pick.append(axes[i])
+            s[C].bind(pick[0], tvm.thread_axis("blockIdx.x"))
+            s[C].bind(pick[1], tvm.thread_axis("vthread"))
+            s[C].bind(pick[2], tvm.thread_axis("threadIdx.y"))
+
+        with target:
+            feas = feature.get_itervar_feature(s, [A, B, C])
+            feas = feature.flatten_itervar_feature(feas)
+        return feas
+
+    targets = [
+        tvm.target.cuda(),
+        tvm.target.mali(),
+        tvm.target.rasp(),
+    ]
+
+    for target in targets:
+        dim = len(get_gemm_feature(target))
+        for i in range(n_sample):
+            assert dim == len(get_gemm_feature(target)), "dimensions of feature do not match" \
+                                                   " for different configurations"
+
+
+if __name__ == "__main__":
+    test_iter_feature_gemm()
+    test_feature_shape()
diff --git a/tests/python/unittest/test_autotvm_flop_calculator.py b/tests/python/unittest/test_autotvm_flop_calculator.py
new file mode 100644
index 000000000000..27bd49fe14df
--- /dev/null
+++ b/tests/python/unittest/test_autotvm_flop_calculator.py
@@ -0,0 +1,77 @@
+"""Test flop calculation"""
+
+import tvm
+import numpy as np
+
+from tvm.autotvm.task.task import compute_flop
+
+def test_conv():
+    for i in range(5):
+        N, H, W, CO, CI, KH, KW = [np.random.randint(10, 32) for _ in range(7)]
+        D = tvm.placeholder((N, CI, H, W))
+        K = tvm.placeholder((CO, CI, KH, KW))
+
+        KH = min(H, KH)
+        KW = min(W, KW)
+
+        ci = tvm.reduce_axis((0, CI))
+        kh = tvm.reduce_axis((0, KH))
+        kw = tvm.reduce_axis((0, KW))
+
+        OH = (H - KH) + 1
+        OW = (W - KW) + 1
+
+        C = tvm.compute((N, CO, OH, OW), lambda n, co, h, w:
+        tvm.sum(D[n][ci][h][w] * K[co][ci][h][w], axis=[ci, kh, kw]))
+
+        s = tvm.create_schedule([C.op])
+
+        assert compute_flop(s) == 2 * N * CO * OH * OW * CI * KH * KW
+
+def test_pack_gemm():
+    for i in range(5):
+        N, L, M = [np.random.randint(10, 128) * 4 for _ in range(3)]
+        A = tvm.placeholder((N, L))
+        B = tvm.placeholder((M, L))
+        k = tvm.reduce_axis((0, L))
+
+        bn = 4
+        A_pack = tvm.compute((N // bn, L, bn), lambda i, j, k: A[i * bn + k][j])
+        B_pack = tvm.compute((M // bn, L, bn), lambda i, j, k: B[i * bn + k][j])
+        C_pack = tvm.compute((N // bn, M // bn, bn, bn), lambda i, j, ii, jj:
+        tvm.sum(A_pack[i, k, ii] * B_pack[j, k, jj], axis=[k]))
+        C = tvm.compute((N, M), lambda i, j: C_pack[i // bn][j // bn][i % bn][j % bn])
+
+        s = tvm.create_schedule([C.op])
+        assert compute_flop(s) == 2 * N * L * M
+
+def test_outer_dot():
+    for i in range(5):
+        N, M = [np.random.randint(10, 128) * 4 for _ in range(2)]
+        A = tvm.placeholder((N,))
+        B = tvm.placeholder((M,))
+
+        C = tvm.compute((N, M), lambda i, j: A[i] * B[j])
+
+        s = tvm.create_schedule([C.op])
+        assert compute_flop(s) == N * M
+
+def test_move():
+    """No float number operation in simple move. So the estimator should raise an error """
+    N = 1024
+
+    A = tvm.placeholder((N,))
+    C = tvm.compute((N,), lambda i: A[i])
+    s = tvm.create_schedule([C.op])
+
+    try:
+        compute_flop(s)
+        assert False
+    except RuntimeError:
+        pass
+
+if __name__ == '__main__':
+    test_conv()
+    test_pack_gemm()
+    test_outer_dot()
+    test_move()
diff --git a/tests/python/unittest/test_autotvm_record.py b/tests/python/unittest/test_autotvm_record.py
new file mode 100644
index 000000000000..0d8bc9dcdfac
--- /dev/null
+++ b/tests/python/unittest/test_autotvm_record.py
@@ -0,0 +1,65 @@
+"""test the correctness of dump and load of data log"""
+import time
+
+import tvm
+from tvm.contrib import util
+
+from tvm import autotvm
+from tvm.autotvm.measure import MeasureInput, MeasureResult, MeasureErrorNo
+from tvm.autotvm.record import encode, decode, ApplyHistoryBest, measure_str_key
+
+from test_autotvm_common import get_sample_task
+
+def test_load_dump():
+    task, target = get_sample_task()
+
+    inp = MeasureInput(target, task, task.config_space.get(0))
+    result = MeasureResult((2.0, 2.23, 0.23, 0.123, 0.234, 0.123), MeasureErrorNo.NO_ERROR,
+                           2.3, time.time())
+
+    for protocol in ['json', 'pickle']:
+        row = encode(inp, result, protocol=protocol)
+        inp_2, result_2 = decode(row, protocol=protocol)
+
+        assert measure_str_key(inp) == measure_str_key(inp_2), \
+            "%s vs %s" % (measure_str_key(inp), measure_str_key(inp_2))
+        assert result.costs == result_2.costs
+        assert result.error_no == result_2.error_no
+        assert result.timestamp == result_2.timestamp
+
+
+def test_file_io():
+    temp = util.tempdir()
+    file_path = temp.relpath("temp.log")
+
+    tsk, target = get_sample_task()
+    inputs = [MeasureInput(target, tsk, tsk.config_space.get(i)) for i in range(0, 10)]
+    results = [MeasureResult((i, ), 0, 0, 0) for i in range(0, 10)]
+
+    with open(file_path, "w") as fo:
+        cb = autotvm.callback.log_to_file(fo)
+        cb(None, inputs, results)
+
+    ref = zip(inputs, results)
+    for x, y in zip(ref, autotvm.record.load_from_file(file_path)):
+        assert x[1] == y[1]
+
+
+def test_apply_history_best():
+    tsk, target = get_sample_task()
+
+    records = [
+        (MeasureInput(target, tsk, tsk.config_space.get(0)), MeasureResult((0.1,), 0, 2.3, 0)),
+        (MeasureInput(target, tsk, tsk.config_space.get(1)), MeasureResult((0.3,), 0, 2.3, 0)),
+        (MeasureInput(target, tsk, tsk.config_space.get(2)), MeasureResult((0.01,), 0, 2.3, 0)),
+        (MeasureInput(target, tsk, tsk.config_space.get(4)), MeasureResult((0.4,), 0, 2.3, 0))
+    ]
+    hist_best = ApplyHistoryBest(records)
+    x = hist_best.query(target, tsk.workload)
+    assert str(x) == str(tsk.config_space.get(2))
+
+
+if __name__ == "__main__":
+    test_load_dump()
+    test_apply_history_best()
+    test_file_io()
diff --git a/tests/python/unittest/test_autotvm_space.py b/tests/python/unittest/test_autotvm_space.py
new file mode 100644
index 000000000000..0320ef1c6f3c
--- /dev/null
+++ b/tests/python/unittest/test_autotvm_space.py
@@ -0,0 +1,30 @@
+"""Test space definition primitives"""
+
+import tvm
+from tvm.autotvm.task.space import ConfigSpace
+
+def gemm_func(cfg, N):
+    A = tvm.placeholder((N, N), name='A')
+    B = tvm.placeholder((N, N), name='B')
+
+    k = tvm.reduce_axis((0, N), name='k')
+    C = tvm.compute((N, N), lambda i, j: tvm.sum(A[i, k] * B[k, j], axis=[k]), name='C')
+
+    s = tvm.create_schedule([C.op])
+
+    y, x = s[C].op.axis
+
+    cfg.define_split('tile_y', cfg.axis(y), num_outputs=2)
+    cfg.define_split('tile_x', cfg.axis(x), num_outputs=2)
+
+    return s, [A, B, C]
+
+def test_split():
+    cfg = ConfigSpace()
+
+    gemm_func(cfg, 128)
+    assert len(cfg) == 64
+    assert len(cfg.space_map['tile_y']) == 8
+
+if __name__ == '__main__':
+    test_split()
diff --git a/tests/python/unittest/test_pass_verify_gpu_code.py b/tests/python/unittest/test_pass_verify_gpu_code.py
index 3059cbef0ed6..6fc0387cf144 100644
--- a/tests/python/unittest/test_pass_verify_gpu_code.py
+++ b/tests/python/unittest/test_pass_verify_gpu_code.py
@@ -31,14 +31,14 @@ def test_shared_memory():
         with tvm.build_config(**{"add_lower_pass": [
             (2, get_verify_pass(valid,
                                 max_shared_memory_per_block=4 * M - 1,
-                                max_thread_per_block=M))]}):
+                                max_threads_per_block=M))]}):
             tvm.build(s, [A, B], target)
         assert not valid[0]
 
         with tvm.build_config(**{"add_lower_pass": [
             (2, get_verify_pass(valid,
                                 max_shared_memory_per_block=4 * M,
-                                max_thread_per_block=M))]}):
+                                max_threads_per_block=M))]}):
             tvm.build(s, [A, B], target)
         assert valid[0]
 
@@ -66,14 +66,14 @@ def test_local_memory():
         with tvm.build_config(**{"add_lower_pass": [
             (2, get_verify_pass(valid,
                                 max_local_memory_per_block=4 * M - 1,
-                                max_thread_per_block=1))]}):
+                                max_threads_per_block=1))]}):
             tvm.build(s, [A, B], target)
         assert not valid[0]
 
         with tvm.build_config(**{"add_lower_pass": [
             (2, get_verify_pass(valid,
                                 max_local_memory_per_block=4 * M,
-                                max_thread_per_block=1))]}):
+                                max_threads_per_block=1))]}):
             tvm.build(s, [A, B], target)
         assert valid[0]
 
@@ -101,21 +101,21 @@ def test_num_thread():
         with tvm.build_config(**{"add_lower_pass": [
             (2, get_verify_pass(valid,
                                 max_shared_memory_per_block=0,
-                                max_thread_per_block=N - 1))]}):
+                                max_threads_per_block=N - 1))]}):
             tvm.build(s, [A, B], target)
         assert not valid[0]
 
         with tvm.build_config(**{"add_lower_pass": [
             (2, get_verify_pass(valid,
                                 max_shared_memory_per_block=0,
-                                max_thread_per_block=N))]}):
+                                max_threads_per_block=N))]}):
             tvm.build(s, [A, B], target)
         assert valid[0]
 
         with tvm.build_config(**{"add_lower_pass": [
             (2, get_verify_pass(valid,
                                 max_shared_memory_per_block=0,
-                                max_thread_per_block=N,
+                                max_threads_per_block=N,
                                 max_thread_y=M-1))]}):
             tvm.build(s, [A, B], target)
         assert not valid[0]
@@ -123,7 +123,7 @@ def test_num_thread():
         with tvm.build_config(**{"add_lower_pass": [
             (2, get_verify_pass(valid,
                                 max_shared_memory_per_block=0,
-                                max_thread_per_block=N,
+                                max_threads_per_block=N,
                                 max_thread_y=M))]}):
             tvm.build(s, [A, B], target)
         assert valid[0]
@@ -151,14 +151,14 @@ def test_multiple_kernels():
         with tvm.build_config(**{"add_lower_pass": [
             (2, get_verify_pass(valid,
                                 max_shared_memory_per_block=0,
-                                max_thread_per_block=N - 1))]}):
+                                max_threads_per_block=N - 1))]}):
             tvm.build(s, [A, C], target)
         assert not valid[0]
 
         with tvm.build_config(**{"add_lower_pass": [
             (2, get_verify_pass(valid,
                                 max_shared_memory_per_block=0,
-                                max_thread_per_block=N))]}):
+                                max_threads_per_block=N))]}):
             tvm.build(s, [A, C], target)
         assert valid[0]
 
diff --git a/tests/scripts/task_python_docs.sh b/tests/scripts/task_python_docs.sh
index 77c6f22acca5..4ff7c490935e 100755
--- a/tests/scripts/task_python_docs.sh
+++ b/tests/scripts/task_python_docs.sh
@@ -8,7 +8,7 @@ make doc
 jsdoc web/tvm_runtime.js web/README.md || exit -1
 mv out docs/_build/html/jsdoc || exit -1
 
-rm -rf python/tvm/*.pyc python/tvm/*/*.pyc
+rm -rf python/tvm/*.pyc python/tvm/*/*.pyc python/tvm/*/*/*.pyc
 
 cd docs
 PYTHONPATH=`pwd`/../python make html || exit -1
diff --git a/tests/scripts/task_python_integration.sh b/tests/scripts/task_python_integration.sh
index ab56ca3e8f6f..8104bf079502 100755
--- a/tests/scripts/task_python_integration.sh
+++ b/tests/scripts/task_python_integration.sh
@@ -2,7 +2,7 @@
 export PYTHONPATH=python:apps/extension/python
 export LD_LIBRARY_PATH=build:${LD_LIBRARY_PATH}
 
-rm -rf python/tvm/*.pyc python/tvm/*/*.pyc
+rm -rf python/tvm/*.pyc python/tvm/*/*.pyc python/tvm/*/*/*.pyc
 
 # Test TVM
 make cython || exit -1
diff --git a/tests/scripts/task_python_unittest.sh b/tests/scripts/task_python_unittest.sh
index f0331609479c..6ffd5675534a 100755
--- a/tests/scripts/task_python_unittest.sh
+++ b/tests/scripts/task_python_unittest.sh
@@ -2,7 +2,7 @@
 
 export PYTHONPATH=python:topi/python
 
-rm -rf python/tvm/*.pyc python/tvm/*/*.pyc
+rm -rf python/tvm/*.pyc python/tvm/*/*.pyc python/tvm/*/*/*.pyc
 
 TVM_FFI=ctypes python -m nose -v tests/python/unittest || exit -1
 TVM_FFI=ctypes python3 -m nose -v tests/python/unittest || exit -1
diff --git a/tutorials/autotvm/README.txt b/tutorials/autotvm/README.txt
new file mode 100644
index 000000000000..c511381dd57d
--- /dev/null
+++ b/tutorials/autotvm/README.txt
@@ -0,0 +1,3 @@
+Auto tuning
+-------------
+
diff --git a/tutorials/autotvm/tune_cuda_conv2d.py b/tutorials/autotvm/tune_cuda_conv2d.py
new file mode 100644
index 000000000000..0f6d8026f9fa
--- /dev/null
+++ b/tutorials/autotvm/tune_cuda_conv2d.py
@@ -0,0 +1,160 @@
+"""
+How to get high performance convolution kernel on NVIDIA GPU by auto-tuning
+=========================================================================
+**Author**: `Lianmin Zheng <https://https://github.com/merrymercy>`_
+
+This is an advanced tutorial for writing high performance tunable template for 
+NVIDIA GPU. By running auto-tuner on this template, we can outperform the
+vendor provided library CuDNN in many cases.
+"""
+
+import logging
+import sys
+
+import tvm
+import topi
+
+from tvm import autotvm
+
+######################################################################
+# Step 1:  Define the search space
+# ---------------------------------
+# There are plenty of useful schedule primitives in tvm. You can also find 
+# some tutorials that describe them in more details, such as 
+# (1). :doc:``Optimizing Conv2d on NVIDIA GPU <../optimize/opt_conv_cuda>`
+# (2). `Optimizing DepthwiseConv on NVIDIA GPU <https://tvm.ai/2017/08/22/Optimize-Deep-Learning-GPU-Operators-with-TVM-A-Depthwise-Convolution-Example.html>`_
+# 
+# However, their implementations are manually tuned for some special input
+# shapes. In this section, we build a large enough space to cover
+# the techniques used in these tutorials. Then we rely on the efficient auto-tuner
+# to search through this space and pick some good configurations.
+# 
+# If you are familiar with writing cuda schedule, you can find the following
+# template is very general. Actually this template can be easily modified 
+# to tune other operators such as depthwise convolution and gemm.
+# In order to fully understand this template, you should be familiar with
+# the schedule primitives and auto tuning API. You can refer to the above
+# tutorials and :doc:`autotvm tutorial <tune_simple_template>`
+#
+# It is worth noting that the search space for a conv2d operator
+# can be very large (at the level of 10^9 for some input shapes)
+#
+
+@autotvm.template
+def conv2d_no_batching(N, H, W, CI, CO, KH, KW, stride, padding):
+    assert N == 1, "Only consider batch_size = 1 in this template"
+
+    data = tvm.placeholder((N, CI, H, W), name='data')
+    kernel = tvm.placeholder((CO, CI, KH, KW), name='kernel')
+    conv = topi.nn.conv2d_nchw(data, kernel, stride, padding, 'float32')
+    s = tvm.create_schedule([conv.op])
+
+    # inline padding
+    pad_data = s[conv].op.input_tensors[0]
+    s[pad_data].compute_inline()
+    data, raw_data = pad_data, data
+
+    output = conv
+    OL = s.cache_write(conv, 'local')
+
+    # create cache stage
+    AA = s.cache_read(data, 'shared', [OL])
+    WW = s.cache_read(kernel, 'shared', [OL])
+    AL = s.cache_read(AA, 'local', [OL])
+    WL = s.cache_read(WW, 'local', [OL])
+
+    # tile and bind spatial axes
+    n, f, y, x = s[output].op.axis
+    cfg = autotvm.get_config()
+    cfg.define_split("tile_f", cfg.axis(f), num_outputs=4)
+    cfg.define_split("tile_y", cfg.axis(y), num_outputs=4)
+    cfg.define_split("tile_x", cfg.axis(x), num_outputs=4)
+    bf, vf, tf, fi = cfg["tile_f"].apply(s, output, f)
+    by, vy, ty, yi = cfg["tile_y"].apply(s, output, y)
+    bx, vx, tx, xi = cfg["tile_x"].apply(s, output, x)
+    kernel_scope = n  # this is the scope to attach global config inside this kernel
+
+    s[output].bind(bf, tvm.thread_axis("blockIdx.z"))
+    s[output].bind(by, tvm.thread_axis("blockIdx.y"))
+    s[output].bind(bx, tvm.thread_axis("blockIdx.x"))
+    s[output].bind(vf, tvm.thread_axis("vthread"))
+    s[output].bind(vy, tvm.thread_axis("vthread"))
+    s[output].bind(vx, tvm.thread_axis("vthread"))
+    s[output].bind(tf, tvm.thread_axis("threadIdx.z"))
+    s[output].bind(ty, tvm.thread_axis("threadIdx.y"))
+    s[output].bind(tx, tvm.thread_axis("threadIdx.x"))
+    s[output].reorder(n, bf, by, bx, vf, vy, vx, tf, ty, tx, fi, yi, xi)
+    s[OL].compute_at(s[output], tx)
+
+    # tile and bind reduction axes
+    n, f, y, x = s[OL].op.axis
+    rc, ry, rx = s[OL].op.reduce_axis
+    cfg.define_split("tile_rc", cfg.axis(rc), num_outputs=3)
+    cfg.define_split("tile_ry", cfg.axis(ry), num_outputs=3)
+    cfg.define_split("tile_rx", cfg.axis(rx), num_outputs=3)
+    rco, rcm, rci = cfg['tile_rc'].apply(s, OL, rc)
+    ryo, rym, ryi = cfg['tile_rx'].apply(s, OL, ry)
+    rxo, rxm, rxi = cfg['tile_ry'].apply(s, OL, rx)
+    s[OL].reorder(rco, ryo, rxo, rcm, rym, rxm, rci, ryi, rxi, n, f, y, x)
+
+    s[AA].compute_at(s[OL], rxo)
+    s[WW].compute_at(s[OL], rxo)
+    s[AL].compute_at(s[OL], rxm)
+    s[WL].compute_at(s[OL], rxm)
+
+    # cooperative fetching
+    for load in [AA, WW]:
+        n, f, y, x = s[load].op.axis
+        fused = s[load].fuse(n, f, y, x)
+        tz, fused = s[load].split(fused, nparts=cfg["tile_f"].size[2])
+        ty, fused = s[load].split(fused, nparts=cfg["tile_y"].size[2])
+        tx, fused = s[load].split(fused, nparts=cfg["tile_x"].size[2])
+        s[load].bind(tz, tvm.thread_axis("threadIdx.z"))
+        s[load].bind(ty, tvm.thread_axis("threadIdx.y"))
+        s[load].bind(tx, tvm.thread_axis("threadIdx.x"))
+
+    # tune unroll
+    cfg.define_knob("auto_unroll_max_step", [0, 512, 1500])
+    cfg.define_knob("unroll_explicit", [0, 1])
+    s[output].pragma(kernel_scope, 'auto_unroll_max_step', cfg['auto_unroll_max_step'].val)
+    s[output].pragma(kernel_scope, 'unroll_explicit', cfg['unroll_explicit'].val)
+
+    return s, [raw_data, kernel, conv]
+
+######################################################################
+# Step 2:  Search through the space
+# ---------------------------------
+# We pick the last layer on resnet as test case.
+# Since our space is very large, :code:`XGBoostTuner` is most suitable
+# for our case. Here we only do 20 trials for demonstration.
+# In practice, making 1000 trials usually can find some good kernels
+# for this template
+
+# logging config (for printing tuning log to screen)
+logging.basicConfig(level=logging.INFO, stream=sys.stdout)
+
+# the last layer in resnet 
+task = autotvm.task.create(conv2d_no_batching,
+                           args=(1, 7, 7, 512, 512, 3, 3, (1, 1), (1, 1)),
+                           target='cuda')
+print(task.config_space)
+
+# use local gpu, measure 5 times for every config to reduce variance
+# run 8 parallel threads for compilation
+measure_option = autotvm.measure_option(mode='local',
+                                        number=10,
+                                        parallel_num=8,
+                                        timeout=20)
+
+# begin tuning, log records to file `cache.tsv`
+tuner = autotvm.tuner.XGBTuner(task)
+tuner.tune(n_trial=20,
+           measure_option=measure_option,
+           callbacks=[autotvm.callback.log_to_file('cache.tsv')])
+
+# get best config from cache file
+dispatch_context = autotvm.apply_history_best("cache.tsv")
+best_config = dispatch_context.query(task.target, task.workload)
+print("\nBest config:")
+print(best_config)
+
diff --git a/tutorials/autotvm/tune_simple_template.py b/tutorials/autotvm/tune_simple_template.py
new file mode 100644
index 000000000000..f6a0c63766cd
--- /dev/null
+++ b/tutorials/autotvm/tune_simple_template.py
@@ -0,0 +1,284 @@
+"""
+Writing tunable template and Using auto-tuner
+=============================================
+**Author**: `Lianmin Zheng <https://https://github.com/merrymercy>`_
+
+This is an introduction tutorial to the auto-tuning module in tvm.
+
+There are two steps in auto-tuning.
+The first step is defining a search space.
+The second step is running a search algorithm to explore through this space.
+In this tutorial, you can learn how to perform these two steps in tvm.
+The whole workflow is illustrated by a matrix multiplication example.
+"""
+
+import logging
+import sys
+
+import numpy as np
+import tvm
+
+# the module is called `autotvm`
+from tvm import autotvm
+
+######################################################################
+# Step 1:  Define the search space
+# ---------------------------------
+# In this section, we will rewrite a deterministic tvm schedule code to a
+# tunable schedule template. You can regard the process of search space definition
+# as the parametrization of our exiting schedule code.
+#
+# To begin with, here is how we implement a blocked matrix multiplication in tvm
+
+# Matmul V0: Constant tiling factor
+def matmul_v0(N, L, M, dtype):
+    A = tvm.placeholder((N, L), name='A', dtype=dtype)
+    B = tvm.placeholder((L, M), name='B', dtype=dtype)
+
+    k = tvm.reduce_axis((0, L), name='k')
+    C = tvm.compute((N, M), lambda i, j: tvm.sum(A[i, k] * B[k, j], axis=k), name='C')
+    s = tvm.create_schedule(C.op)
+
+    # schedule
+    y, x = s[C].op.axis
+    k = s[C].op.reduce_axis[0]
+
+    yo, yi = s[C].split(y, 8)
+    xo, xi = s[C].split(x, 8)
+
+    s[C].reorder(yo, xo, k, yi, xi)
+
+    return s, [A, B, C]
+
+#####################################################################
+# Parametrize the schedule
+# ^^^^^^^^^^^^^^^^^^^^^^^^^
+# In the previous schedule code, we use a constant "8" as tiling factor.
+# However, it might not be the best one because the best tiling factor depends
+# on real hardware environment and input shape.
+#
+# If you want the schedule code to be portable across a wider range of input shapes
+# and target hardware, it is better to define a set of candidate values and
+# pick the best one according to the measurement results on target hardware.
+#
+# In autotvm, we can define a tunable parameter, or a "knob" for such kind of value.
+
+# Matmul V1: List candidate values
+@autotvm.template  # 1. use a decorator
+def matmul_v1(N, L, M, dtype):
+    A = tvm.placeholder((N, L), name='A', dtype=dtype)
+    B = tvm.placeholder((L, M), name='B', dtype=dtype)
+
+    k = tvm.reduce_axis((0, L), name='k')
+    C = tvm.compute((N, M), lambda i, j: tvm.sum(A[i, k] * B[k, j], axis=k), name='C')
+    s = tvm.create_schedule(C.op)
+
+    # schedule
+    y, x = s[C].op.axis
+    k = s[C].op.reduce_axis[0]
+
+    # 2. get the config object
+    cfg = autotvm.get_config()
+
+    # 3. define search space
+    cfg.define_knob("tile_y", [1, 2, 4, 8, 16])
+    cfg.define_knob("tile_x", [1, 2, 4, 8, 16])
+
+    # 4. schedule according to config
+    yo, yi = s[C].split(y, cfg['tile_y'].val)
+    xo, xi = s[C].split(x, cfg['tile_x'].val)
+
+    s[C].reorder(yo, xo, k, yi, xi)
+
+    return s, [A, B, C]
+
+###############################################################################
+# Here we make four modifications to the previous schedule code and get
+# a tunable "template". We can explain the modifications one by one.
+#
+# 1. Use a decorator to mark this function as a simple template
+# 2. Get a config object:
+#    You can regard this :code:`cfg` as an argument of this function but
+#    we obtain it in a different way. With this argument, this function is no longer
+#    a deterministic schedule code. Instead, we can pass different configurations to
+#    this function and get different schedules, so this function is a "template".
+#
+#    To make the template function more compact, we do two things in a single function.
+#    (1) define a search space and (2) schedule according to an entity in this space.
+#    To achieve this, we make :code:`cfg` be either
+#    a :any:`ConfigSpace` or a :any:`ConfigEntity` object.
+#
+#    When it is a :any:`ConfigSpace`, it will collect all tunable knobs in this function and
+#    build the search space.
+#    When it is a :any:`ConfigEntity`, it will ignore all space definition API
+#    (namely, :code:`cfg.define_XXXXX(...)`).   Instead, it stores deterministic values for
+#    all tunable knobs, and we schedule according to these values.
+#
+#    During auto-tuning, we will first call this template with a :any:`ConfigSpace`
+#    object to build the search space. Then we call this template with different :any:`ConfigEntity`
+#    in the built space to get different schedules. Finally we will measure the code generated by
+#    different schedules and pick the best one.
+#
+# 3. Define two tunable knobs. The first one is :code:`tile_y` with
+#    5 possible values. The second one is :code:`tile_x` with a same
+#    list of possible values. These two knobs are independent, so they
+#    span a search space with size = 5x5 = 25
+# 4. Schedule according to the deterministic values in :code:`cfg`
+#
+
+#####################################################################
+# Use better space definition API
+# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+# In the previous template, we manually list all possible values for a knob.
+# This is the lowest level API to define the space.
+# However, we also provide another set of API to make the space definition
+# easier and smarter. It is recommended to use this set of high level API.
+#
+# In the flowing example, we use :any:`ConfigSpace.define_split` to define a split
+# knob. It will enumerate all the possible ways to split an axis and construct
+# the space.
+#
+# We also have :any:`ConfigSpace.define_reorder` for reorder knob and
+# :any:`ConfigSpace.define_annotate` for annotation like unroll, vectorization,
+# thread binding.
+# When the high level API cannot meet your requirement, you can always fall
+# back to use low level API.
+
+@autotvm.template
+def matmul(N, L, M, dtype):
+    A = tvm.placeholder((N, L), name='A', dtype=dtype)
+    B = tvm.placeholder((L, M), name='B', dtype=dtype)
+
+    k = tvm.reduce_axis((0, L), name='k')
+    C = tvm.compute((N, M), lambda i, j: tvm.sum(A[i, k] * B[k, j], axis=k), name='C')
+    s = tvm.create_schedule(C.op)
+
+    # schedule
+    y, x = s[C].op.axis
+    k = s[C].op.reduce_axis[0]
+
+    ##### define space begin #####
+    cfg = autotvm.get_config()
+    cfg.define_split("tile_y", y, num_outputs=2)
+    cfg.define_split("tile_x", x, num_outputs=2)
+    ##### define space end #####
+
+    # schedule according to config
+    yo, yi = cfg["tile_y"].apply(s, C, y)
+    xo, xi = cfg["tile_x"].apply(s, C, x)
+
+    s[C].reorder(yo, xo, k, yi, xi)
+
+    return s, [A, B, C]
+
+######################################################################
+# .. note:: More Explanation on :code:`cfg.defile_split`
+#
+#  In this template, :code:`cfg.define_split("tile_y", y, num_outputs=2)` will enumerate
+#  all possible combinations that can split axis y into two axes with factors of the length of y.
+#  For example, if the length of y is 32 and we want to split it into two axes
+#  using factors of 32, then there are 6 possible values for
+#  (length of outer axis, length of inner axis) pair, namely
+#  (32, 1), (16, 2), (8, 4), (4, 8), (2, 16) or (1, 32).
+#  They are just the 6 possible values of `tile_y`.
+#
+#  During schedule, :code:`cfg["tile_y"]` is a :code:`SplitEntity` object.
+#  We stores the lengths of outer axes and inner axes in :code:`cfg['tile_y'].size`
+#  (a tuple with two elements).
+#  In this template, we apply it by using :code:`yo, yi = cfg['tile_y'].apply(s, C, y)`.
+#  Actually, this is equivalent to
+#  :code:`yo, yi = s[C].split(y, cfg["tile_y"].size[1])`
+#  or  :code:`yo, yi = s[C].split(y, nparts=cfg['tile_y"].size[0])`
+#
+#  The advantage of using cfg.apply API is that it makes multi-level split
+#  (when num_outputs >= 3) easier.
+
+######################################################################
+# Step 2:  Search through the space
+# ---------------------------------
+# In step 1, we build the search space by extending our old schedule code
+# into a template. The next step is to pick a tuner and explore in this space.
+#
+# Auto-tuners in tvm
+# ^^^^^^^^^^^^^^^^^^
+# The job for a tuner can be described by following pseudo code
+#
+#   .. code-block:: c
+#
+#    ct = 0
+#    while ct < max_number_of_trials:
+#        propose a batch of configs
+#        measure this batch of configs on real hardware and get results
+#        ct += batch_size
+#
+# When proposing the next batch of configs, the tuner can take different strategies. We
+# provide four tuners with different strategies in autotvm.
+#
+# * :any:`RandomTuner`: Enumerate the space in a random order
+# * :any:`GridSearchTuner`: Enumerate the space in a grid search order
+# * :any:`GATuner`: Using genetic algorithm to search through the space
+# * :any:`XGBTuner`: Uses a model based method. Train a XGBoost model to predict the speed of lowered IR and pick the next batch according to the prediction.
+#
+# You can choose the tuner according to the size of your space, your time budget and other factors.
+# For example, if your space is very small (less than 1000), a gridsearch tuner or a
+# random tuner is good enough. If your space is at the level of 10^9 (this is the space
+# size of a conv2d operator on CUDA GPU), XGBoostTuner can explore more efficiently
+# and find better configs.
+
+################################################################
+# Begin tuning
+# ^^^^^^^^^^^^
+# Here we continue our matrix multiplication example.
+# First we should create a tuning task.
+# We can also inspect the initialized search space.
+# In this case, for a 512x512 square matrix multiplication, the space size
+# is 10x10=100
+N, L, M = 512, 512, 512
+task = autotvm.task.create(matmul, args=(N, L, M, 'float32'), target='llvm')
+print(task.config_space)
+
+################################################################
+# Then we need to define how to measure the generated code and pick a tuner.
+# Since our space is small, a random tuner is just okay.
+# 
+# We only make 10 trials in this tutorial for demonstration. In practice,
+# you can do more trials according to your time budget.
+# We will log the tuning results into a cache file. This file can be 
+# used to get the best config later.
+
+# logging config (for printing tuning log to screen)
+logging.basicConfig(level=logging.INFO, stream=sys.stdout)
+
+# use local cpu, measure 5 times for every config to reduce variance
+measure_option = autotvm.measure_option(mode='local',
+                                        number=5)
+
+# begin tuning, log records to file `cache.tsv`
+tuner = autotvm.tuner.RandomTuner(task)
+tuner.tune(n_trial=10,
+           measure_option=measure_option,
+           callbacks=[autotvm.callback.log_to_file('cache.tsv')])
+
+#########################################################################
+# Finally we apply history best from the cache file and check its correctness.
+# We can call the function :code:`matmul` directly under the 
+# :any:`autotvm.apply_history_best` context. When we call this function,
+# it will query the dispatch context with its argument and get the best config 
+# with the same argument.
+
+# apply history best from log file
+with autotvm.apply_history_best('cache.tsv'):
+    with tvm.target.create("llvm"):
+        s, arg_bufs = matmul(N, L, M, 'float32')
+        func = tvm.build(s, arg_bufs)
+
+# check correctness
+a_np = np.random.uniform(size=(N, L)).astype(np.float32)
+b_np = np.random.uniform(size=(L, M)).astype(np.float32)
+c_np = a_np.dot(b_np)
+
+c_tvm = tvm.nd.empty(c_np.shape)
+func(tvm.nd.array(a_np), tvm.nd.array(b_np), c_tvm)
+
+np.testing.assert_allclose(c_np, c_tvm.asnumpy(), rtol=1e-2)
+

From 3e77cc3b776547bcffb68524848d7766e8ab357f Mon Sep 17 00:00:00 2001
From: Yao Wang <kevinthesunwy@gmail.com>
Date: Thu, 12 Jul 2018 20:04:41 -0700
Subject: [PATCH 865/949] [TOPI]Add where operator (#1416)

---
 nnvm/python/nnvm/top/transform.py             |  4 +
 nnvm/src/top/tensor/transform.cc              | 97 ++++++++++++++++++-
 nnvm/tests/python/compiler/test_top_level4.py | 31 ++++++
 topi/include/topi/transform.h                 | 48 +++++++++
 topi/src/topi.cc                              |  5 +
 topi/tests/python_cpp/test_topi_transform.py  | 42 ++++++++
 6 files changed, 225 insertions(+), 2 deletions(-)

diff --git a/nnvm/python/nnvm/top/transform.py b/nnvm/python/nnvm/top/transform.py
index b5e00f012996..5f136365c898 100644
--- a/nnvm/python/nnvm/top/transform.py
+++ b/nnvm/python/nnvm/top/transform.py
@@ -72,3 +72,7 @@ def compute_reshape_like(attrs, inputs, out_info):
 # slice_like
 reg.register_pattern("slice_like", OpPattern.INJECTIVE)
 reg.register_schedule("slice_like", _fschedule_injective)
+
+# where
+reg.register_pattern("where", OpPattern.INJECTIVE)
+reg.register_schedule("where", _fschedule_injective)
diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
index 5bb2ec137594..05adc06a801b 100644
--- a/nnvm/src/top/tensor/transform.cc
+++ b/nnvm/src/top/tensor/transform.cc
@@ -1125,8 +1125,8 @@ Examples::
 DMLC_REGISTER_PARAMETER(SliceLikeParam);
 
 inline bool SliceLikeShape(const nnvm::NodeAttrs& attrs,
-                          std::vector<TShape>* in_attrs,
-                          std::vector<TShape>* out_attrs) {
+                           std::vector<TShape>* in_attrs,
+                           std::vector<TShape>* out_attrs) {
   CHECK_EQ(in_attrs->size(), 2U);
   CHECK_EQ(out_attrs->size(), 1U);
   const SliceLikeParam& param = nnvm::get<SliceLikeParam>(attrs.parsed);
@@ -1221,5 +1221,98 @@ NNVM_REGISTER_OP(slice_like)
 })
 .set_support_level(4);
 
+// where
+inline bool WhereShape(const nnvm::NodeAttrs& attrs,
+                       std::vector<TShape>* in_attrs,
+                       std::vector<TShape>* out_attrs) {
+  CHECK_EQ(in_attrs->size(), 3U);
+  CHECK_EQ(out_attrs->size(), 1U);
+  const TShape& cond_shape = in_attrs->at(0);
+  const TShape& x_shape = in_attrs->at(1);
+  const TShape& y_shape = in_attrs->at(2);
+  CHECK_EQ(x_shape, y_shape) << "x and y must have the same shape: "
+                             << x_shape << " vs " << y_shape;
+  if (cond_shape != x_shape) {
+    CHECK_EQ(cond_shape.ndim(), 1)
+      << "Shape of condition " << cond_shape
+      << " must be either equal to x or has dimension of 1.";
+  }
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_attrs, 0, x_shape);
+  return true;
+}
+
+inline bool WhereInferType(const NodeAttrs &attrs,
+                           std::vector<int> *in_attrs,
+                           std::vector<int> *out_attrs) {
+  DTYPE_ASSIGN(out_attrs->at(0), in_attrs->at(1));
+  return true;
+}
+
+inline bool WhereCorrectLayout(const NodeAttrs& attrs,
+                               std::vector<Layout> *ilayouts,
+                               const std::vector<Layout> *last_ilayouts,
+                               std::vector<Layout> *olayouts) {
+  CHECK_EQ(ilayouts->size(), last_ilayouts->size());
+  CHECK_EQ(olayouts->size(), 1U);
+
+  for (size_t i = 0; i < ilayouts->size(); ++i) {
+    const Layout& input = last_ilayouts->at(i).defined() ?
+                          last_ilayouts->at(i) : ilayouts->at(i);
+    NNVM_ASSIGN_LAYOUT(*ilayouts, i, input);
+  }
+
+  return true;
+}
+
+NNVM_REGISTER_OP(where)
+.describe(R"code(
+Return the elements, either from x or y, depending on the condition.
+
+Given three ndarrays, condition, x, and y, return an ndarray with the elements
+from x or y, depending on the elements from condition are true or false.
+x and y must have the same shape. If condition has the same shape as x,
+each element in the output array is from x if the corresponding element
+in the condition is true, and from y if false.
+
+If condition does not have the same shape as x, it must be a 1D array whose
+size is the same as x’s first dimension size. Each row of the output array
+is from x’s row if the corresponding element from condition is true, and
+from y’s row if false.
+
+Note that all non-zero values are interpreted as True in condition.
+
+Examples::
+
+  x = [[1, 2], [3, 4]]
+  y = [[5, 6], [7, 8]]
+  cond = [[0, 1], [-1, 0]]
+  where(cond, x, y) = [[5, 2], [3, 8]]
+
+
+  cond = [1, 0]
+  where(cond, x, y) = [[1, 2], [7, 8]]
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("condition", "Tensor", "Condition array")
+.add_argument("x", "Tensor", "First array to be selected")
+.add_argument("y", "Tensor", "Second array to be selected")
+.set_num_inputs(3)
+.set_num_outputs(1)
+.set_attr<FInferShape>("FInferShape", WhereShape)
+.set_attr<FInferType>("FInferType", WhereInferType)
+.set_attr<FCorrectLayout>("FCorrectLayout", WhereCorrectLayout)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    return Array<Tensor>{
+      topi::where(inputs[0], inputs[1], inputs[2])
+    };
+  })
+.set_attr<FListInputNames>("FListInputNames", [](const NodeAttrs& attrs) {
+  return std::vector<std::string>{"condition", "x", "y"};
+})
+.set_support_level(4);
+
 }  // namespace top
 }  // namespace nnvm
diff --git a/nnvm/tests/python/compiler/test_top_level4.py b/nnvm/tests/python/compiler/test_top_level4.py
index de6a3fa331bc..236ac8e82578 100644
--- a/nnvm/tests/python/compiler/test_top_level4.py
+++ b/nnvm/tests/python/compiler/test_top_level4.py
@@ -645,6 +645,36 @@ def test_slice_like():
     axis = (2, 3)
     verify_slice_like(np_data, np_shape_like, axis)
 
+def verify_where(condition, x, y):
+    dtype = "float32"
+    if len(condition.shape) == 1:
+        np_out = np.array([xv if c else yv for (c,xv,yv) in zip(condition,x,y)])
+    else:
+        np_out = np.where(condition, x, y)
+    cond_var = sym.Variable("condition")
+    x_var = sym.Variable("x")
+    y_var = sym.Variable("y")
+    net = sym.where(cond_var, x_var, y_var)
+    for target, ctx in ctx_list():
+        graph, lib, _ = nnvm.compiler.build(net, target, {"condition": condition.shape,
+                                                          "x": x.shape, "y": y.shape})
+        m = graph_runtime.create(graph, lib, ctx)
+        m.set_input(**{"condition": condition, "x": x, "y": y})
+        m.run()
+        out = m.get_output(0, tvm.nd.empty(x.shape, dtype))
+        np.testing.assert_allclose(out.asnumpy(), np_out, atol=1e-5, rtol=1e-5)
+
+def test_where():
+    shape = (13, 8, 224, 224, 6)
+    condition = np.random.uniform(low=-1, high=1, size=shape).astype("float32")
+    x = np.random.uniform(size=shape).astype("float32")
+    y = np.random.uniform(size=shape).astype("float32")
+    verify_where(condition, x, y)
+    condition = np.random.uniform(low=-1, high=1, size=(shape[0],)).astype("float32")
+    x = np.random.uniform(size=shape).astype("float32")
+    y = np.random.uniform(size=shape).astype("float32")
+    verify_where(condition, x, y)
+
 
 if __name__ == "__main__":
     test_reshape()
@@ -665,4 +695,5 @@ def test_slice_like():
     test_multibox_transform_loc()
     test_nms()
     test_slice_like()
+    test_where()
     print(nnvm.compiler.engine.dump())
diff --git a/topi/include/topi/transform.h b/topi/include/topi/transform.h
index 10fb5bc478cb..09af612b957b 100644
--- a/topi/include/topi/transform.h
+++ b/topi/include/topi/transform.h
@@ -575,5 +575,53 @@ inline Tensor take(const Tensor& a,
         }, name, tag);
 }
 
+/*!
+* \brief Return the elements, either from x or y, depending on the condition.
+*
+* \param condition The condition array.
+* \param x First array to be selected.
+* \param y Second array to be selected.
+* \param name The name of the operation.
+* \param tag The tag to mark the operation.
+*
+* \return A Tensor selected from x or y depending on condition.
+*/
+inline Tensor where(const Tensor& condition,
+                    const Tensor& x,
+                    const Tensor& y,
+                    std::string name = "tensor",
+                    std::string tag = kInjective) {
+  CHECK_EQ(x->shape.size(), y->shape.size())
+    << "x and y must have the same shape.Got different number of dimension: "
+    << x->shape.size() << " vs " << y->shape.size();
+  CHECK_EQ(x->dtype, y->dtype) << "x and y must have the same dtype: "
+                               << x->dtype << " vs " << y->dtype;
+  Array<Expr> oshape = x->shape;
+  Tensor out;
+
+  if (condition->shape.size() != 1) {
+    CHECK_EQ(condition->shape.size(), x->shape.size())
+      << "condition array must be either have the same shape as x or to be a "
+         "1-D array.Got different number of dimension: "
+      << condition->shape.size() << " vs " << x->shape.size();
+    out = compute(
+      oshape, [&](const Array<Var>& indices) {
+        return tvm::select(condition(indices) != 0, x(indices), y(indices));
+      }, name, tag);
+  } else {
+    CHECK_EQ(topi::GetConstInt(condition->shape[0]), topi::GetConstInt(x->shape[0]))
+      << "If condition is 1-D, the first dimension must be the same as x: "
+      << condition->shape[0] << " vs " << x->shape[0];
+    out = compute(
+      oshape, [&](const Array<Var>& indices) {
+        Array<Expr> condition_idx{indices[0]};
+        return tvm::select(condition(condition_idx) != 0,
+                           x(indices), y(indices));
+      }, name, tag);
+  }
+  return out;
+}
+
+
 }  // namespace topi
 #endif  // TOPI_TRANSFORM_H_
diff --git a/topi/src/topi.cc b/topi/src/topi.cc
index c08bd5f565d2..4cdab4401459 100644
--- a/topi/src/topi.cc
+++ b/topi/src/topi.cc
@@ -280,6 +280,11 @@ TVM_REGISTER_GLOBAL("topi.take")
   }
   });
 
+TVM_REGISTER_GLOBAL("topi.where")
+.set_body([](TVMArgs args, TVMRetValue *rv) {
+  *rv = where(args[0], args[1], args[2]);
+});
+
 TVM_REGISTER_GLOBAL("topi.strided_slice")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
   *rv = strided_slice(args[0], args[1], args[2], args[3]);
diff --git a/topi/tests/python_cpp/test_topi_transform.py b/topi/tests/python_cpp/test_topi_transform.py
index a94fc89328af..c8b7c3906caa 100644
--- a/topi/tests/python_cpp/test_topi_transform.py
+++ b/topi/tests/python_cpp/test_topi_transform.py
@@ -206,6 +206,35 @@ def check_device(device):
     for device in ["llvm", "opencl"]:
         check_device(device)
 
+def verify_where(condition, x, y):
+    dtype = "float32"
+    if len(condition.shape) == 1:
+        np_out = np.array([xv if c else yv for (c,xv,yv) in zip(condition,x,y)])
+    else:
+        np_out = np.where(condition, x, y)
+    A = tvm.placeholder(shape=condition.shape, dtype=dtype, name="condition")
+    B = tvm.placeholder(shape=x.shape, dtype=dtype, name="x")
+    C = tvm.placeholder(shape=y.shape, dtype=dtype, name="y")
+    out_tensor = topi.cpp.where(A, B, C)
+
+    def check_device(device):
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
+            print("Skip because %s is not enabled" % device)
+            return
+        print("Running on target: %s" % device)
+        with tvm.target.create(device):
+            s = topi.generic.schedule_injective(out_tensor)
+
+        foo = tvm.build(s, [A, B, C, out_tensor], device, name="where")
+        tvm_out = tvm.nd.empty(x.shape, ctx=ctx, dtype=dtype)
+        foo(tvm.nd.array(condition, ctx), tvm.nd.array(x, ctx),
+            tvm.nd.array(y, ctx), tvm_out)
+        np.testing.assert_allclose(tvm_out.asnumpy(), np_out)
+
+    for device in ["llvm", "nvptx", "cuda", "opencl", "metal", "rocm"]:
+        check_device(device)
+
 def verify_concatenate_split(shapes, axis, indices_or_sections):
     tensor_l_concatenate = []
     for i, shape in enumerate(shapes):
@@ -324,6 +353,18 @@ def test_take():
     verify_take((2,2), [[[1,0],[0,1]]], 1)
     verify_take((4,3,5,6), [[2,1,0,0]], -2)
 
+def test_where():
+    shape = (10, 3, 7, 13)
+    condition = np.random.uniform(low=-1, high=1, size=shape).astype("float32")
+    x = np.random.uniform(size=shape).astype("float32")
+    y = np.random.uniform(size=shape).astype("float32")
+    verify_where(condition, x, y)
+    condition = np.random.uniform(low=-1, high=1, size=(shape[0],)).astype("float32")
+    x = np.random.uniform(size=shape).astype("float32")
+    y = np.random.uniform(size=shape).astype("float32")
+    verify_where(condition, x, y)
+
+
 def test_regression_1():
     verify_concatenate_split([(2, 3, 4), (2, 2, 4), (2, 5, 4)], 1, [3, 7])
     verify_concatenate_split([(3, 4), (2, 4), (3, 4)], 0, [1, 2, 3, 4])
@@ -340,5 +381,6 @@ def test_regression_2():
     test_squeeze()
     test_split()
     test_take()
+    test_where()
     test_regression_1()
     test_regression_2()

From 467d795178c79cbb5d8f2a75a3393bc213acebe5 Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@cs.washington.edu>
Date: Thu, 12 Jul 2018 20:05:17 -0700
Subject: [PATCH 866/949] [DOCS] VTA installation guide (#1428)

---
 docs/vta/install.md                           |  52 +-
 .../python/pynq/test_benchmark_conv2d.py      | 544 ------------------
 2 files changed, 24 insertions(+), 572 deletions(-)
 delete mode 100644 vta/tests/python/pynq/test_benchmark_conv2d.py

diff --git a/docs/vta/install.md b/docs/vta/install.md
index 6c2023afe2fc..a827990d6e05 100644
--- a/docs/vta/install.md
+++ b/docs/vta/install.md
@@ -8,7 +8,7 @@ We present three installation guides, each extending on the previous one:
 
 ## VTA Simulation-Only Installation
 
-You need [TVM installed](https://docs.tvm.ai/install/index.html) on your machine.
+You need [TVM installed](https://docs.tvm.ai/install/index.html) on your machine. For a quick and easy start, use the pre-built Docker image.
 VTA simulator is library will be built by default along with TVM.
 All you need to run the simulator is to add the vta library to your python path.
 
@@ -23,10 +23,12 @@ Finally to ensure that you've properly installed the VTA package, we can run sim
 Let's first run the 2D convolution test bench that will only run the ResNet-18 convolution layers.
 
 ```bash
-python vta/tests/python/integration/test_benchmark_topi_conv2d.py
+python <tvm root>/vta/tests/python/integration/test_benchmark_topi_conv2d.py
 ```
 
-> Note: You'll notice that for every convolution layer, the throughput gets reported in GOPS. These numbers are actually the computational throughput that the simulator achieves, by evaluating the convolution in software. You can also try out other tutorials.
+> Note: You'll notice that for every convolution layer, the throughput gets reported in GOPS. These numbers are actually the computational throughput that the simulator achieves, by evaluating the convolution in software.
+
+You can also try out our [VTA programming tutorials](https://docs.tvm.ai/vta/tutorials/index.html) on the VTA simulator.
 
 
 ### Advanced Configuration
@@ -39,7 +41,7 @@ You can modify the content to reconfigure VTA to a different mode. To do so,
 ```bash
 cd <tvm root>
 cp vta/config/vta_config.json vta_config.json
-edit vta_config.json
+# edit vta_config.json
 make vta
 ```
 
@@ -103,9 +105,6 @@ cd ..
 sudo ./apps/pynq_rpc/start_rpc_server.sh # pw is 'xilinx'
 ```
 
-Note that one key difference between the simulator build is that we changed the VTA configuration
-to be `vta/config/pynq_sample.json`, which specifies PYNQ as target.
-
 You should see the following being displayed when starting the RPC server. In order to run the next examples, you'll need to leave the RPC server running in an `ssh` session.
 ```
 INFO:root:RPCServer: bind to 0.0.0.0:9091
@@ -118,49 +117,46 @@ Tips regarding the Pynq RPC Server:
 
 ### Testing your VTA Pynq-based Hardware Setup
 
-Before running the examples you'll need to configure your environment as follows:
+Before running the examples you'll need to configure your host environment as follows:
 ```bash
 export VTA_PYNQ_RPC_HOST=192.168.2.99
 export VTA_PYNQ_RPC_PORT=9091
 ```
 
-In addition, you'll need to edit the `vta_config.json` file to indicate that we are targeting the Pynq platform, by setting the `TARGET` field to the `"pynq"` value. Alternatively, you can copy the default `make/config.json` into the VTA root.
+In addition, you'll need to edit the `vta_config.json` file on the host to indicate that we are targeting the Pynq platform, by setting the `TARGET` field to `"pynq"`.
+Alternatively, you can copy the default `vta/config/pynq_sample.json` into the TVM root as `vta_config.json`.
 > Note: in contrast to our simulation setup, there are no libraries to compile on the host side since the host offloads all of the computation to the Pynq board.
 
 ```bash
 cd <tvm root>
-cp vta/config/pynq_sample.json .
+cp vta/config/pynq_sample.json vta_config.json
 ```
 
 This time again, we will run the 2D convolution testbench. But beforehand, we'll need to program the Pynq's own FPGA with a VTA bitstream, and build the VTA runtime on the Pynq via RPC. The following `test_program_rpc.py` script will perform two operations:
-* FPGA programming, by downloading a pre-compiled bitstream from a [VTA bitstream repository](https://github.com/uwsaml/vta-distro) that matches the default `config.json` configuration set by the host, and sending it over to the Pynq via RPC to program the Pynq's FPGA.
-* Runtime building on the Pynq, which needs to be run everytime the `config.json` configuration is modified. This ensures that the VTA software runtime that generates the accelerator's executable via just-in-time (JIT) compilation matches the specifications of the VTA design that is programmed on the FPGA. The build process takes about 30 seconds to complete.
+* FPGA programming, by downloading a pre-compiled bitstream from a [VTA bitstream repository](https://github.com/uwsaml/vta-distro) that matches the default `vta_config.json` configuration set by the host, and sending it over to the Pynq via RPC to program the Pynq's FPGA.
+* Runtime building on the Pynq, which needs to be run everytime the `vta_config.json` configuration is modified. This ensures that the VTA software runtime that generates the accelerator's executable via just-in-time (JIT) compilation matches the specifications of the VTA design that is programmed on the FPGA. The build process takes about 30 seconds to complete.
 
 ```bash
-python tests/python/pynq/test_program_rpc.py
+python <tvm root>/vta/tests/python/pynq/test_program_rpc.py
 ```
 
 > Tip: You can track progress of the FPGA programming and the runtime rebuilding steps by looking at the RPC server's logging messages in your Pynq `ssh` session.
 
-We are now ready to run the 2D convolution testbench for the ResNet-15 workload in hardware.
+We are now ready to run the 2D convolution testbench for the ResNet-18 workload in hardware.
 
 ```bash
-python tests/python/pynq/test_benchmark_conv2d.py
+python <tvm root>/vta/tests/python/integration/test_benchmark_topi_conv2d.py
 ```
 
 The performance metrics measured on the Pynq board will be reported for each convolutional layer.
-You can also try out other tutorials.
+
+You can also try out our [VTA programming tutorials](https://docs.tvm.ai/vta/tutorials/index.html).
 
 
 ## VTA Hardware Toolchain Installation
 
 This third and last guide allows users to generate custom VTA bitstreams using free-to-use Xilinx compilation toolchains.
 
-This guide includes:
-1. Xilinx toolchain installation (for Linux)
-2. Custom VTA bitstream compilation
-3. Running the end to end ResNet-18 test with the new bitstream
-
 ### Xilinx Toolchain Installation
 
 We recommend using `Vivado 2017.1` since our scripts have been tested to work on this version of the Xilinx toolchains. Our guide is written for Linux installation.
@@ -216,15 +212,15 @@ export PATH=${XILINX_SDK}/bin:${PATH}
 
 ### Custom VTA Bitstream Compilation
 
-High-level parameters are listed under `tvm/vta/config/vta_config.json` and can be customized by the user. For this custom VTA Bitstream Compilation exercise, we'll change the frequency of our design, so it can be clocked a little faster.
+High-level parameters are listed under `<tvm root>/vta/config/vta_config.json` and can be customized by the user. For this custom VTA Bitstream Compilation exercise, we'll change the frequency of our design, so it can be clocked a little faster.
 * Set the `HW_FREQ` field to `142`. The Pynq board supports 100, 142, 167 and 200MHz clocks. Note that the higher the frequency, the harder it will be to close timing. Increasing the frequency can lead to timing violation and thus faulty hardware.
 * Set the `HW_CLK_TARGET` to `6`. This parameters refers to the target clock period in ns passed to HLS - a lower clock period leads to more aggressive pipelining to achieve timing closure at higher frequencies. Technically a 142MHz clock would require a 7ns target, but we intentionally lower the clock target to 6ns to more aggressively pipeline our design.
 
-Bitstream generation is driven by a top-level `Makefile` under `<vta root>/hardware/xilinx/`.
+Bitstream generation is driven by a top-level `Makefile` under `<tvm root>/vta/hardware/xilinx/`.
 
 If you just want to simulate the VTA design in software emulation to make sure that it is functional, enter:
 ```bash
-cd <vta root>/hardware/xilinx
+cd <tvm root>/vta/hardware/xilinx
 make ip MODE=sim
 ```
 
@@ -232,8 +228,8 @@ If you just want to generate the HLS-based VTA IP cores without launching the en
 ```bash
 make ip
 ```
-You'll be able to view the HLS synthesis reports under `<vta root>/build/hardware/xilinx/hls/<configuration>/<block>/solution0/syn/report/<block>_csynth.rpt`
-> Note: The `<configuration>` name is a string that summarizes the VTA configuration parameters specified in the `config.json`. The `<block>` name refers to the specific module in the VTA pipeline.
+You'll be able to view the HLS synthesis reports under `<tvm root>/vta/build/hardware/xilinx/hls/` `<configuration>/<block>/solution0/syn/report/<block>_csynth.rpt`
+> Note: The `<configuration>` name is a string that summarizes the VTA configuration parameters specified in the `vta_config.json`. The `<block>` name refers to the specific module in the VTA pipeline.
 
 Finally to run the full hardware compilation and generate the bitstream, run:
 
@@ -243,14 +239,14 @@ make
 
 This process is lenghty, and can take around up to an hour to complete depending on your machine's specs. We recommend setting the `VTA_HW_COMP_THREADS` variable in the Makefile to take full advantage of all the cores on your development machine.
 
-Once the compilation completes, the generated bitstream can be found under `<vta root>/build/hardware/xilinx/vivado/<configuration>/export/vta.bit`.
+Once the compilation completes, the generated bitstream can be found under `<tvm root>/vta/build/hardware/xilinx/vivado/<configuration>/export/vta.bit`.
 
 ### Use the Custom Bitstream
 
 We can change the FPGA bitstream by simply change the bistream path to the configuring API.
 
 ```python
-vta.program_fpga(remote, bitstream="<vta root>/build/hardware/xilinx/vivado/<configuration>/export/vta.bit")
+vta.program_fpga(remote, bitstream="<tvm root>/vta/build/hardware/xilinx/vivado/<configuration>/export/vta.bit")
 ```
 
 Instead of downloading the bitstream from the bitstream repository, the programmer will instead use the custom bitstream you just generated, which is a VTA design clocked at a higher frequency.
diff --git a/vta/tests/python/pynq/test_benchmark_conv2d.py b/vta/tests/python/pynq/test_benchmark_conv2d.py
deleted file mode 100644
index 3548d2cf983e..000000000000
--- a/vta/tests/python/pynq/test_benchmark_conv2d.py
+++ /dev/null
@@ -1,544 +0,0 @@
-import os
-import tvm
-import mxnet as mx
-import vta
-import numpy as np
-import topi
-from collections import namedtuple
-from tvm import rpc
-from tvm.contrib import util
-import pandas as pd
-
-host = os.environ.get("VTA_PYNQ_RPC_HOST", "pynq")
-port = int(os.environ.get("VTA_PYNQ_RPC_PORT", "9091"))
-target = "llvm -target=armv7-none-linux-gnueabihf -mattr=+neon"
-
-Workload = namedtuple("Conv2DWorkload",
-                      ['batch', 'height', 'width', 'in_filter', 'out_filter',
-                       'hkernel', 'wkernel', 'hpad', 'wpad', 'hstride', 'wstride'])
-
-class Conv2DSchedule(object):
-    def __init__(self,
-                 b_factor=1,
-                 oc_factor=1,
-                 ko_factor=1,
-                 h_factor=1,
-                 w_factor=0,
-                 oc_nthread=0,
-                 h_nthread=0,
-                 debug_sync=False):
-        self.b_factor = b_factor
-        self.oc_factor = oc_factor
-        self.ko_factor = ko_factor
-        self.h_factor = h_factor
-        self.w_factor = w_factor
-        self.oc_nthread = oc_nthread
-        self.h_nthread = h_nthread
-        self.debug_sync = debug_sync
-    def __str__(self):
-        return "{}.{}.{}.{}.{}.{}.{}".format(
-            self.b_factor, self.oc_factor, self.ko_factor,
-            self.h_factor, self.w_factor,
-            self.oc_nthread, self.h_nthread)
-
-Schedule = Conv2DSchedule
-
-def get_insn_count(layer, sched):
-    env = vta.get_env()
-    b, h, w, ci, co = sched
-    b_factor = b
-    h_factor = layer.height // h
-    w_factor = layer.width // w
-    ci_factor = layer.in_filter // (ci * env.BLOCK_IN)
-    co_factor = layer.out_filter // (co * env.BLOCK_OUT)
-    input_xfers = b_factor * h_factor * w_factor * co_factor * ci_factor
-    weight_xfers = b_factor * h_factor * w_factor * co_factor * ci_factor
-    output_xfers = b_factor * h_factor * w_factor * co_factor
-    # compute instruction count
-    # output transfer factor: 4 (INIT, GEMM, ALU, STORE)
-    # offset: 5 (3 uop kernels, 1 initial dep push, 1 finish, co_factor)
-    insn_count = input_xfers + weight_xfers + (output_xfers * 4) + 5 + co_factor
-    return insn_count
-
-def find_schedules(layer, mtOnly=False, bestOnly=False):
-    env = vta.get_env()
-    # Helper function to get factors
-    def find_factors(n):
-        factors = []
-        for i in range(1, n+1):
-            if n % i == 0:
-                factors.append(i)
-        return factors
-    # Scheduling exploration
-    batch_factors = find_factors(layer.batch // env.BATCH)
-    height_factors = find_factors(layer.height // layer.hstride)
-    width_factors = find_factors(layer.width // layer.wstride)
-    cin_factors = find_factors(layer.in_filter // env.BLOCK_IN)
-    cout_factors = find_factors(layer.out_filter // env.BLOCK_OUT)
-    ht_factors = [1, 2]
-    cot_factors = [1, 2]
-    # Explore schedules
-    schedules = []
-    for b in batch_factors:
-        for h in height_factors:
-            for w in width_factors:
-                for ci in cin_factors:
-                    for co in cout_factors:
-                        # FIXME: filter because of 2D load
-                        if w == layer.width/layer.wstride or (w != layer.width/layer.wstride and co == 1):
-                            # FIXME: filter because of 2D load
-                            if ci == 1:
-                                schedules.append([b, h, w, ci, co])
-    # Filter the schedules that wouldn't work in the available BRAM sizes
-    input_elem_size_b = env.BATCH * env.BLOCK_IN * env.INP_WIDTH
-    weight_elem_size_b = env.BLOCK_IN * env.BLOCK_OUT * env.WGT_WIDTH
-    output_elem_size_b = env.BATCH * env.BLOCK_OUT * env.OUT_WIDTH
-    input_brams_capacity_b = env.INP_BUFF_SIZE * 8
-    weight_brams_capacity_b = env.WGT_BUFF_SIZE * 8
-    output_brams_capacity_b = env.OUT_BUFF_SIZE * 8
-    fil_sched = []
-    xfer_size = []
-    for sched in schedules:
-        b, h, w, ci, co = sched
-        for ht in [1, 2]:
-            for cot in [1, 2]:
-                # Make sure to filter cases where we apply threading on two axes
-                # or cases where the threading factors for h and co are not
-                # factors of h and co
-                if not (ht == 2 and cot == 2) and h % ht == 0 and co % cot == 0:
-                    # If in multi-threaded mode, only allow for mt configs:
-                    if (mtOnly and (ht == 2 or cot == 2)) or not mtOnly:
-                        h /= ht
-                        co /= cot
-                        input_tile_elems = b * \
-                                ((h - 1) * layer.hstride + layer.hkernel) * \
-                                ((w - 1) * layer.wstride + layer.wkernel) * ci
-                        weight_tile_elems = layer.hkernel * layer.wkernel * ci * co
-                        output_tile_elems = b * h * w * co
-                        insn_count = get_insn_count(layer, sched)
-                        # 1. Check input capacity
-                        # 2. Check weight capacity
-                        # 3. Check output capacity
-                        # 4. Check instruction capacity
-                        # 5. Make sure that we don't write to the same acc location
-                        #    within 2 consecutive cycles
-                        if input_tile_elems*input_elem_size_b <= input_brams_capacity_b/(cot*ht) and \
-                           weight_tile_elems*weight_elem_size_b <= weight_brams_capacity_b and \
-                           output_tile_elems*output_elem_size_b <= output_brams_capacity_b/(cot*ht) and \
-                           insn_count <= env.MAX_XFER // 16 and \
-                           h > 2 and w > 2:
-                            schedule = Schedule(oc_factor=co, ko_factor=ci, h_factor=h,
-                                                w_factor=w, oc_nthread=cot, h_nthread=ht)
-                            fil_sched.append(schedule)
-                            xfer_size.append(get_data_movementB(schedule, layer))
-    if bestOnly:
-        return [fil_sched[xfer_size.index(min(xfer_size))]]
-    else:
-        return fil_sched
-
-def get_data_movementB(sched, layer):
-    env = vta.get_env()
-    # Derive data movement
-    input_elem_size_b = env.BATCH * env.BLOCK_IN * env.INP_WIDTH
-    weight_elem_size_b = env.BLOCK_IN * env.BLOCK_OUT * env.WGT_WIDTH
-    output_elem_size_b = env.BATCH * env.BLOCK_OUT * env.OUT_WIDTH
-    b = sched.b_factor
-    h = sched.h_factor
-    w = sched.w_factor
-    ci = sched.ko_factor
-    co = sched.oc_factor
-    ht = sched.h_nthread
-    cot = sched.oc_nthread
-    input_tile_elems = b * \
-            ((h - 1) * layer.hstride + layer.hkernel) * \
-            ((w - 1) * layer.wstride + layer.wkernel) * ci
-    weight_tile_elems = layer.hkernel * layer.wkernel * ci
-    output_tile_elems = b * h * w * co
-    # Derive factors
-    b_factor = layer.batch // (b * env.BATCH)
-    h_factor = (layer.height // layer.hstride) // h
-    w_factor = (layer.width // layer.wstride) // w
-    ci_factor = int(np.ceil(float(layer.in_filter) // (ci * env.BLOCK_IN)))
-    co_factor = int(np.ceil(float(layer.out_filter) // (co * env.BLOCK_OUT)))
-    # Derive transfers
-    input_xfers = b_factor * h_factor * w_factor * co_factor * ci_factor
-    weight_xfers = b_factor * h_factor * w_factor * co_factor * ci_factor
-    output_xfers = b_factor * h_factor * w_factor * co_factor
-    # Compute total transfer sizes
-    input_xfer_B = input_tile_elems * input_xfers * input_elem_size_b // 8
-    weight_xfer_B = weight_tile_elems * weight_xfers * weight_elem_size_b // 8
-    output_xfer_B = output_tile_elems * output_xfers * output_elem_size_b // 8
-    total_xfer_B = input_xfer_B + weight_xfer_B + output_xfer_B
-    return total_xfer_B
-
-def test_conv2d_chwv(layer, key, batch_size, wl, sched, log_frame, profile=True):
-    env = vta.get_env()
-    assert batch_size % env.BATCH == 0
-    assert wl.in_filter % env.BLOCK_IN == 0
-    assert wl.out_filter % env.BLOCK_OUT == 0
-    data_shape = (batch_size // env.BATCH, wl.in_filter // env.BLOCK_IN,
-                  wl.height, wl.width, env.BATCH, env.BLOCK_IN)
-    kernel_shape = (wl.out_filter // env.BLOCK_OUT, wl.in_filter // env.BLOCK_IN,
-                    wl.hkernel, wl.wkernel, env.BLOCK_OUT, env.BLOCK_IN)
-    fout_height = (wl.height + 2 * wl.hpad - wl.hkernel) // wl.hstride + 1
-    fout_width = (wl.width + 2 * wl.wpad - wl.wkernel) // wl.wstride + 1
-    res_shape = (batch_size // env.BATCH, wl.out_filter // env.BLOCK_OUT,
-                 fout_height, fout_width, env.BATCH, env.BLOCK_OUT)
-    data = tvm.placeholder(data_shape, name="data", dtype=env.inp_dtype)
-    kernel = tvm.placeholder(kernel_shape, name="kernel", dtype=env.wgt_dtype)
-    if wl.hpad or wl.wpad:
-        data_buf = topi.nn.pad(data, [0, 0, wl.hpad, wl.wpad, 0, 0], name="data_buf")
-    else:
-        data_buf = tvm.compute(data_shape, lambda *i: data(*i), "data_buf")
-    kernel_buf = tvm.compute(kernel_shape, lambda *i: kernel(*i), "kernel_buf")
-    di = tvm.reduce_axis((0, wl.hkernel), name='di')
-    dj = tvm.reduce_axis((0, wl.wkernel), name='dj')
-    ko = tvm.reduce_axis((0, wl.in_filter//env.BLOCK_IN), name='ko')
-    ki = tvm.reduce_axis((0, env.BLOCK_IN), name='ki')
-    res_cnv = tvm.compute(
-        res_shape,
-        lambda bo, co, i, j, bi, ci: tvm.sum(
-            data_buf[bo, ko, i*wl.hstride+di, j*wl.wstride+dj, bi, ki].astype(env.acc_dtype) *
-            kernel_buf[co, ko, di, dj, ci, ki].astype(env.acc_dtype),
-        axis=[ko, di, dj, ki]),
-        name="res_cnv")
-    # res_shf = tvm.compute(res_shape, lambda *i: res_cnv(*i) >> 8, name="res_shf")
-    res_shf = topi.right_shift(res_cnv, 8)
-    res = tvm.compute(res_shape, lambda *i: res_shf(*i).astype(env.inp_dtype), name="res")
-    num_ops = batch_size * fout_height * fout_width * wl.hkernel * wl.wkernel * wl.out_filter * wl.in_filter
-    total_xfer_B = get_data_movementB(sched, wl)
-
-    def verify(s, check_correctness):
-        mod = vta.build(s, [data, kernel, res], "ext_dev", target, name="conv2d")
-        temp = util.tempdir()
-        remote = rpc.connect(host, port)
-        mod.save(temp.relpath("conv2d.o"))
-        remote.upload(temp.relpath("conv2d.o"))
-        f = remote.load_module("conv2d.o")
-        # verify
-        ctx = remote.ext_dev(0)
-        # Data in original format
-        data_orig = np.random.randint(
-            -128, 128, size=(batch_size, wl.in_filter, wl.height, wl.width)).astype(data.dtype)
-        kernel_orig = np.random.randint(
-            -128, 128, size=(wl.out_filter, wl.in_filter, wl.hkernel, wl.wkernel)).astype(kernel.dtype)
-        data_packed = data_orig.reshape(
-            batch_size//env.BATCH, env.BATCH,
-            wl.in_filter//env.BLOCK_IN, env.BLOCK_IN,
-            wl.height, wl.width).transpose((0, 2, 4, 5, 1, 3))
-        kernel_packed = kernel_orig.reshape(
-            wl.out_filter//env.BLOCK_OUT, env.BLOCK_OUT,
-            wl.in_filter//env.BLOCK_IN, env.BLOCK_IN,
-            wl.hkernel, wl.wkernel).transpose((0, 2, 4, 5, 1, 3))
-        res_np = np.zeros(res_shape).astype(res.dtype)
-        data_arr = tvm.nd.array(data_packed, ctx)
-        kernel_arr = tvm.nd.array(kernel_packed, ctx)
-        res_arr = tvm.nd.array(res_np, ctx)
-        time_f = f.time_evaluator("conv2d", ctx, number=1)
-        cost = time_f(data_arr, kernel_arr, res_arr)
-        res_unpack = res_arr.asnumpy().transpose(
-            (0, 4, 1, 5, 2, 3)).reshape(batch_size, wl.out_filter, fout_height, fout_width)
-        if check_correctness:
-            res_ref = mx.nd.Convolution(
-                mx.nd.array(data_orig.astype(env.acc_dtype), mx.cpu(0)),
-                mx.nd.array(kernel_orig.astype(env.acc_dtype), mx.cpu(0)),
-                stride=(wl.hstride, wl.wstride),
-                kernel=(wl.hkernel, wl.wkernel),
-                num_filter=wl.out_filter,
-                no_bias=True,
-                pad=(wl.hpad, wl.wpad)).asnumpy().astype(env.acc_dtype)
-            res_ref = np.right_shift(res_ref, 8).astype(res.dtype)
-            np.testing.assert_allclose(res_unpack, res_ref)
-            print("Correctness check pass...")
-        return cost
-
-    def run_schedule(load_inp, load_wgt, gemm, alu, store_out,
-                     print_ir, check_correctness):
-        # schedule1
-        s = tvm.create_schedule(res.op)
-        s[data_buf].set_scope(env.inp_scope)
-        s[kernel_buf].set_scope(env.wgt_scope)
-        s[res_cnv].set_scope(env.acc_scope)
-        s[res_shf].set_scope(env.acc_scope)
-        # tile
-        oc_factor = (sched.oc_factor if sched.oc_factor
-                     else wl.out_filter // env.BLOCK_OUT)
-        h_factor = (sched.h_factor if sched.h_factor else fout_height)
-        w_factor = (sched.w_factor if sched.w_factor else fout_width)
-        xbo, xco, xi, xj, xbi, xci = s[res].op.axis
-        xco0, xco1 = s[res].split(xco, factor=oc_factor)
-        xi0, xi1 = s[res].split(xi, factor=h_factor)
-        xj0, xj1 = s[res].split(xj, factor=w_factor)
-        s[res].reorder(xbo, xi0, xco0, xj0, xco1, xi1, xj1, xbi, xci)
-        s[res_cnv].compute_at(s[res], xj0)
-        s[res_shf].compute_at(s[res], xj0)
-
-        if sched.oc_nthread > 1:
-            _, tx = s[res].split(xco0, factor=sched.oc_nthread)
-            s[res].reorder(tx, xbo)
-            s[res].bind(tx, tvm.thread_axis("cthread"))
-
-        if sched.h_nthread > 1:
-            xo, tx = s[res].split(xi0, factor=sched.h_nthread)
-            s[res].reorder(tx, xbo)
-            s[res].bind(tx, tvm.thread_axis("cthread"))
-
-        xbo, xco, xi, xj, xbi, xci = s[res_cnv].op.axis
-        s[res_cnv].reorder(xbo, ko, xj, dj, di, xco, xi, xbi, xci, ki)
-
-        if sched.ko_factor:
-            ko0, ko1 = s[res_cnv].split(ko, factor=sched.ko_factor)
-            s[data_buf].compute_at(s[res_cnv], ko0)
-            s[kernel_buf].compute_at(s[res_cnv], ko0)
-        # Use VTA instructions
-        s[data_buf].pragma(s[data_buf].op.axis[0], load_inp)
-        s[kernel_buf].pragma(s[kernel_buf].op.axis[0], load_wgt)
-        s[res_cnv].tensorize(xbi, gemm)
-        s[res_shf].pragma(s[res_shf].op.axis[0], alu)
-        s[res].pragma(xco1, store_out)
-        if sched.debug_sync:
-            s[res].pragma(xco0, "coproc_sync")
-        if print_ir:
-            print(tvm.lower(s, [data, kernel, res], simple_mode=True))
-        return verify(s, check_correctness)
-
-    def conv_normal(print_ir):
-        print("----- CONV2D End-to-End Test-------")
-        def run_test(header, print_ir, check_correctness):
-            s = [1, sched.oc_factor, sched.ko_factor, sched.h_factor, sched.w_factor]
-            cost = run_schedule(
-                env.dma_copy, env.dma_copy,
-                env.gemm, env.alu, env.dma_copy,
-                print_ir, check_correctness)
-            gops = (num_ops / cost.mean) / float(10 ** 9)
-            print(header)
-            print("\tTime cost = %g sec/op, %g GOPS" % (cost.mean, gops))
-            log_frame["key"].append(key)
-            log_frame["layer"].append(layer)
-            log_frame["total-data"].append(total_xfer_B)
-            log_frame["total-gops"].append(gops)
-            log_frame["total-cost"].append(cost.mean)
-            log_frame["total-insn"].append(get_insn_count(wl, s))
-            log_frame["block-batch"].append(env.BATCH)
-            log_frame["block-in"].append(env.BLOCK_IN)
-            log_frame["block-out"].append(env.BLOCK_OUT)
-            log_frame["inp-width"].append(env.INP_WIDTH)
-            log_frame["wgt-width"].append(env.WGT_WIDTH)
-            log_frame["uop-size"].append(env.UOP_BUFF_SIZE)
-            log_frame["inp-size"].append(env.INP_BUFF_SIZE)
-            log_frame["wgt-size"].append(env.WGT_BUFF_SIZE)
-            log_frame["out-size"].append(env.OUT_BUFF_SIZE)
-            log_frame["oc-factor"].append(sched.oc_factor)
-            log_frame["ic-factor"].append(sched.ko_factor)
-            log_frame["h-factor"].append(sched.h_factor)
-            log_frame["w-factor"].append(sched.w_factor)
-            log_frame["oc-threads"].append(sched.oc_nthread)
-            log_frame["h-threads"].append(sched.h_nthread)
-            log_frame["threaded"].append(True if sched.oc_nthread > 1 or sched.h_nthread > 1 else False)
-
-        with vta.build_config():
-            run_test("NORMAL", print_ir, True)
-
-    def skip_alu_unittest(print_ir):
-        mock = env.mock
-        print("----- Skip ALU Unit Test-------")
-        def run_test(header, print_ir):
-            cost = run_schedule(
-                env.dma_copy, env.dma_copy,
-                env.gemm, mock.alu, env.dma_copy,
-                print_ir, False)
-            gops = (num_ops / cost.mean) / float(10 ** 9)
-            print(header)
-            print("\tTime cost = %g sec/op, %g GOPS" % (cost.mean, gops))
-            log_frame["skip-alu-gops"].append(gops)
-            log_frame["skip-alu-cost"].append(cost.mean)
-
-        with vta.build_config():
-            run_test("NORMAL", print_ir)
-        print("")
-
-    def gemm_unittest(print_ir):
-        mock = env.mock
-        print("----- GEMM Unit Test-------")
-        def run_test(header, print_ir):
-            cost = run_schedule(
-                mock.dma_copy, mock.dma_copy,
-                env.gemm, mock.alu, mock.dma_copy,
-                print_ir, False)
-            gops = (num_ops / cost.mean) / float(10 ** 9)
-            print(header)
-            print("\tTime cost = %g sec/op, %g GOPS" % (cost.mean, gops))
-            log_frame["gemm-gops"].append(gops)
-            log_frame["gemm-cost"].append(cost.mean)
-        with vta.build_config():
-            run_test("NORMAL", print_ir)
-        print("")
-
-    def alu_unittest(print_ir):
-        mock = env.mock
-        print("----- ALU Unit Test-------")
-        def run_test(header, print_ir):
-            cost = run_schedule(
-                mock.dma_copy, mock.dma_copy,
-                env.gemm, env.alu, mock.dma_copy,
-                print_ir, False)
-            gops = (num_ops / cost.mean) / float(10 ** 9)
-            print(header)
-            print("\tTime cost = %g sec/op, %g GOPS" % (cost.mean, gops))
-            log_frame["alu-gops"].append(gops)
-            log_frame["alu-cost"].append(cost.mean)
-        with vta.build_config():
-            run_test("NORMAL", print_ir)
-        print("")
-
-    def load_inp_unittest(print_ir):
-        mock = env.mock
-        print("----- LoadInp Unit Test-------")
-        def run_test(header, print_ir):
-            cost = run_schedule(
-                env.dma_copy, mock.dma_copy,
-                env.gemm, mock.alu, mock.dma_copy,
-                print_ir, False)
-            gops = (num_ops / cost.mean) / float(10 ** 9)
-            bandwith = (batch_size * wl.in_filter * wl.height *
-                        wl.width * env.INP_WIDTH / cost.mean) / float(10 ** 9)
-            print(header)
-            print("\tTime cost = %g sec/op, %g GOPS, bandwith=%g gbits" % (
-                cost.mean, gops, bandwith))
-            log_frame["ld-inp-gbits"].append(bandwith)
-            log_frame["ld-inp-cost"].append(cost.mean)
-        with vta.build_config():
-            run_test("NORMAL", print_ir)
-        print("")
-
-    def load_wgt_unittest(print_ir):
-        mock = env.mock
-        print("----- LoadWgt Unit Test-------")
-        def run_test(header, print_ir):
-            cost = run_schedule(
-                mock.dma_copy, env.dma_copy,
-                env.gemm, mock.alu, mock.dma_copy, print_ir,
-                False)
-            gops = (num_ops / cost.mean) / float(10 ** 9)
-            bandwith = (wl.out_filter * wl.in_filter * wl.hkernel *
-                        wl.wkernel * env.WGT_WIDTH / cost.mean) / float(10 ** 9)
-            print(header)
-            print("\tTime cost = %g sec/op, %g GOPS, bandwith=%g gbits" % (
-                cost.mean, gops, bandwith))
-            log_frame["ld-wgt-gbits"].append(bandwith)
-            log_frame["ld-wgt-cost"].append(cost.mean)
-        with vta.build_config():
-            run_test("NORMAL", print_ir)
-        print("")
-
-    def store_out_unittest(print_ir):
-        mock = env.mock
-        print("----- StoreOut Unit Test-------")
-        def run_test(header, print_ir):
-            cost = run_schedule(
-                mock.dma_copy, mock.dma_copy,
-                env.gemm, mock.alu, env.dma_copy, print_ir,
-                False)
-            gops = (num_ops / cost.mean) / float(10 ** 9)
-            bandwith = (batch_size * wl.out_filter * fout_height *
-                        fout_width * env.OUT_WIDTH / cost.mean) / float(10 ** 9)
-            print(header)
-            print("\tTime cost = %g sec/op, %g GOPS, bandwith=%g gbits" % (
-                cost.mean, gops, bandwith))
-            log_frame["st-out-gbits"].append(bandwith)
-            log_frame["st-out-cost"].append(cost.mean)
-        with vta.build_config():
-            run_test("NORMAL", print_ir)
-        print("")
-
-    def manual_unittest(print_ir):
-        # Manual section used to teak the components
-        mock = env.mock
-        print("----- Manual Unit Test-------")
-        def run_test(header, print_ir):
-            cost = run_schedule(
-                env.dma_copy, env.dma_copy,
-                env.gemm, env.alu, mock.dma_copy, print_ir,
-                False)
-            gops = (num_ops / cost.mean) / float(10 ** 9)
-            print(header)
-            print("\tTime cost = %g sec/op, %g GOPS" % (
-                cost.mean, gops))
-        with vta.build_config():
-            run_test("NORMAL", print_ir)
-        print("")
-
-    print("=================================")
-    print("key=%s" % key)
-    print(wl)
-    conv_normal(False)
-    if not profile:
-        return
-    skip_alu_unittest(False)
-    gemm_unittest(False)
-    alu_unittest(False)
-    load_inp_unittest(False)
-    load_wgt_unittest(False)
-    store_out_unittest(False)
-
-# Perform profiling
-profile = False
-# Data set batch size
-batch_size = 1
-# Use multi-threading for latency hiding
-multi_threaded = False
-
-# ResNet18 workloads
-resnet = {
-    # Workloads of resnet18 on imagenet
-    0: Workload(1, 224, 224, 16, 64, 7, 7, 3, 3, 2, 2),
-    1: Workload(1, 56, 56, 64, 64, 3, 3, 1, 1, 1, 1),
-    2: Workload(1, 56, 56, 64, 64, 1, 1, 0, 0, 1, 1),
-    3: Workload(1, 56, 56, 64, 128, 3, 3, 1, 1, 2, 2),
-    4: Workload(1, 56, 56, 64, 128, 1, 1, 0, 0, 2, 2),
-    5: Workload(1, 28, 28, 128, 128, 3, 3, 1, 1, 1, 1),
-    6: Workload(1, 28, 28, 128, 256, 3, 3, 1, 1, 2, 2),
-    7: Workload(1, 28, 28, 128, 256, 1, 1, 0, 0, 2, 2),
-    8: Workload(1, 14, 14, 256, 256, 3, 3, 1, 1, 1, 1),
-    9: Workload(1, 14, 14, 256, 512, 3, 3, 1, 1, 2, 2),
-    10: Workload(1, 14, 14, 256, 512, 1, 1, 0, 0, 2, 2),
-    11: Workload(1, 7, 7, 512, 512, 3, 3, 1, 1, 1, 1),
-}
-
-begin = 0
-end = len(resnet)
-
-resnet_schedules = []
-for i in range(begin, end):
-    scheds = find_schedules(resnet[i], mtOnly=multi_threaded, bestOnly=True)
-    resnet_schedules.append([i, scheds])
-
-keys = ["key", "layer", "total-data", "total-gops", "total-cost", "total-insn",
-        "block-batch", "block-in", "block-out", "wgt-width", "inp-width",
-        "uop-size", "inp-size", "wgt-size", "out-size",
-        "oc-factor", "ic-factor", "h-factor", "w-factor",
-        "oc-threads", "h-threads", "threaded"]
-
-if profile:
-    keys += ["skip-alu-gops", "skip-alu-cost",
-             "gemm-gops", "gemm-cost", "alu-gops", "alu-cost",
-             "ld-inp-cost", "ld-wgt-cost", "st-out-cost",
-             "ld-inp-gbits", "ld-wgt-gbits", "st-out-gbits"]
-log_frame = {
-    k : [] for k in keys
-}
-
-for x in resnet_schedules:
-    l, plans = x
-    for plan in plans:
-        key = "resnet-cfg[{}-{}]".format(l, plan)
-        test_conv2d_chwv(l, key, batch_size, resnet[l], plan, log_frame, profile)
-
-
-if profile:
-    pd.set_option('expand_frame_repr', False)
-    log_df = pd.DataFrame()
-    for k  in keys:
-        log_df[k] = log_frame[k]
-    print(log_df)
-    log_df.to_csv("conv2d.csv")

From 3887886c0717e9923e405d7c5ebea73dc2cd03ea Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Fri, 13 Jul 2018 22:19:49 +0530
Subject: [PATCH 867/949] Add support for absolute opeartion (#1406)

---
 docs/api/python/intrin.rst                    |  3 ++-
 docs/api/python/topi.rst                      |  2 ++
 docs/nnvm_top.rst                             |  2 ++
 include/tvm/ir_operator.h                     | 21 ++++++++++++++++++-
 nnvm/python/nnvm/top/tensor.py                |  4 ++++
 nnvm/src/top/tensor/elemwise.cc               | 12 +++++++++++
 nnvm/tests/python/compiler/test_top_level3.py |  5 +++++
 python/tvm/intrin.py                          | 16 ++++++++++++++
 src/api/api_ir.cc                             |  6 ++++++
 src/codegen/intrin_rule_cuda.cc               |  3 +++
 src/codegen/intrin_rule_metal.cc              |  3 +++
 src/codegen/intrin_rule_opencl.cc             |  3 +++
 src/codegen/llvm/intrin_rule_llvm.cc          |  3 +++
 src/codegen/llvm/intrin_rule_nvptx.cc         |  3 +++
 src/codegen/llvm/intrin_rule_rocm.cc          |  3 +++
 src/codegen/spirv/intrin_rule_spirv.cc        |  3 +++
 topi/include/topi/elemwise.h                  |  1 +
 topi/python/topi/math.py                      | 17 +++++++++++++++
 topi/tests/python/test_topi_math.py           |  1 +
 19 files changed, 109 insertions(+), 2 deletions(-)

diff --git a/docs/api/python/intrin.rst b/docs/api/python/intrin.rst
index a455f20a5874..3942c57f1a04 100644
--- a/docs/api/python/intrin.rst
+++ b/docs/api/python/intrin.rst
@@ -14,7 +14,7 @@ tvm.intrin
    tvm.ceil
    tvm.trunc
    tvm.round
-
+   tvm.abs
 
 .. autofunction:: tvm.call_packed
 .. autofunction:: tvm.call_pure_intrin
@@ -26,3 +26,4 @@ tvm.intrin
 .. autofunction:: tvm.ceil
 .. autofunction:: tvm.trunc
 .. autofunction:: tvm.round
+.. autofunction:: tvm.abs
diff --git a/docs/api/python/topi.rst b/docs/api/python/topi.rst
index a14355d0f796..7f150ddbf7cd 100644
--- a/docs/api/python/topi.rst
+++ b/docs/api/python/topi.rst
@@ -13,6 +13,7 @@ List of operators
    topi.ceil
    topi.trunc
    topi.round
+   topi.abs
    topi.exp
    topi.tanh
    topi.log
@@ -84,6 +85,7 @@ topi
 .. autofunction:: topi.ceil
 .. autofunction:: topi.trunc
 .. autofunction:: topi.round
+.. autofunction:: topi.abs
 .. autofunction:: topi.exp
 .. autofunction:: topi.tanh
 .. autofunction:: topi.log
diff --git a/docs/nnvm_top.rst b/docs/nnvm_top.rst
index 4e1e536dbb26..96a37b779e1e 100644
--- a/docs/nnvm_top.rst
+++ b/docs/nnvm_top.rst
@@ -79,6 +79,7 @@ This level enables typical convnet models.
    nnvm.symbol.ceil
    nnvm.symbol.round
    nnvm.symbol.trunc
+   nnvm.symbol.abs
    nnvm.symbol.leaky_relu
    nnvm.symbol.__add_scalar__
    nnvm.symbol.__sub_scalar__
@@ -157,6 +158,7 @@ Detailed Definitions
 .. autofunction:: nnvm.symbol.ceil
 .. autofunction:: nnvm.symbol.round
 .. autofunction:: nnvm.symbol.trunc
+.. autofunction:: nnvm.symbol.abs
 .. autofunction:: nnvm.symbol.leaky_relu
 .. autofunction:: nnvm.symbol.__add_scalar__
 .. autofunction:: nnvm.symbol.__sub_scalar__
diff --git a/include/tvm/ir_operator.h b/include/tvm/ir_operator.h
index 268205ecb07b..947c3b736d80 100644
--- a/include/tvm/ir_operator.h
+++ b/include/tvm/ir_operator.h
@@ -18,7 +18,6 @@ using HalideIR::likely_if_innermost;
 using HalideIR::cast;
 using HalideIR::min;
 using HalideIR::max;
-using HalideIR::abs;
 using HalideIR::select;
 
 /*!
@@ -71,6 +70,26 @@ inline Expr pow(Expr x, Expr y) {
   return ir::Call::make(x.type(), "pow", { x, y }, ir::Call::PureIntrinsic);
 }
 
+/*!
+ * \brief Calculate absolute value of x, elementwise
+ * \param x The input data
+ *
+ * \return The aboslute value of input data x
+ */
+inline Expr abs(Expr x) {
+  if (x.type().is_int()) {
+    return select(x >= make_zero(x.type()), x, -x);
+  } else if (x.type().is_float()) {
+    return ir::Call::make(x.type(), "fabs", {x}, ir::Call::PureIntrinsic);
+  } else if (x.type().is_uint()) {
+    return x;
+  } else {
+    LOG(WARNING) << "Warning: Data type " << x.type()
+      <<" not supported for absolute op. Skipping absolute op...";
+    return x;
+  }
+}
+
 }  // namespace tvm
 
 #endif  // TVM_IR_OPERATOR_H_
diff --git a/nnvm/python/nnvm/top/tensor.py b/nnvm/python/nnvm/top/tensor.py
index 3ff59e5d0042..9a8caab29479 100644
--- a/nnvm/python/nnvm/top/tensor.py
+++ b/nnvm/python/nnvm/top/tensor.py
@@ -68,6 +68,10 @@ def _compute(attrs, x, _):
 reg.register_pattern("round", OpPattern.ELEMWISE)
 reg.register_schedule("round", _fschedule_broadcast)
 
+# abs
+reg.register_pattern("abs", OpPattern.ELEMWISE)
+reg.register_schedule("abs", _fschedule_broadcast)
+
 # trunc
 reg.register_pattern("trunc", OpPattern.ELEMWISE)
 reg.register_schedule("trunc", _fschedule_broadcast)
diff --git a/nnvm/src/top/tensor/elemwise.cc b/nnvm/src/top/tensor/elemwise.cc
index 57e7137f5151..403e08f95653 100644
--- a/nnvm/src/top/tensor/elemwise.cc
+++ b/nnvm/src/top/tensor/elemwise.cc
@@ -81,6 +81,18 @@ NNVM_REGISTER_ELEMWISE_UNARY_OP(round)
       return Array<Tensor>{ topi::round(inputs[0]) };
 });
 
+// abs
+NNVM_REGISTER_ELEMWISE_UNARY_OP(abs)
+.describe(R"code(Take absolute value of elements of the input.
+)code" NNVM_ADD_FILELINE)
+.set_support_level(3)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+      return Array<Tensor>{ topi::abs(inputs[0]) };
+});
+
 // sigmoid
 NNVM_REGISTER_ELEMWISE_UNARY_OP(sigmoid)
 .describe(R"code(Computes sigmoid.
diff --git a/nnvm/tests/python/compiler/test_top_level3.py b/nnvm/tests/python/compiler/test_top_level3.py
index 125836a7848e..c8bd37c38e5b 100644
--- a/nnvm/tests/python/compiler/test_top_level3.py
+++ b/nnvm/tests/python/compiler/test_top_level3.py
@@ -28,6 +28,10 @@ def test_trunc():
 def test_round():
     check_map(sym.round, np.round)
 
+def test_abs():
+    check_map(sym.abs, np.abs)
+    check_map(sym.abs, np.abs, dtype = "int32")
+    check_map(sym.abs, np.abs, dtype = "int8")
 
 def test_shift():
     n = 3
@@ -40,4 +44,5 @@ def test_shift():
     test_floor()
     test_ceil()
     test_round()
+    test_abs()
     test_trunc()
diff --git a/python/tvm/intrin.py b/python/tvm/intrin.py
index fe4fb8faa9c1..422f2d682d2b 100644
--- a/python/tvm/intrin.py
+++ b/python/tvm/intrin.py
@@ -285,6 +285,22 @@ def trunc(x):
     return call_pure_intrin(x.dtype, "trunc", x)
 
 
+def abs(x):
+    """Get absolute value of the input element-wise.
+
+    Parameters
+    ----------
+    x : Expr
+        Input argument.
+
+    Returns
+    -------
+    y : Expr
+        The result.
+    """
+    return _make.abs(x)
+
+
 def round(x):
     """Round elements of the array to the nearest integer.
 
diff --git a/src/api/api_ir.cc b/src/api/api_ir.cc
index f8fbe902ca0b..bc9293c20b7a 100644
--- a/src/api/api_ir.cc
+++ b/src/api/api_ir.cc
@@ -7,6 +7,7 @@
 #include <tvm/ir.h>
 #include <ir/IROperator.h>
 #include <tvm/api_registry.h>
+#include <tvm/ir_operator.h>
 
 namespace tvm {
 namespace ir {
@@ -16,6 +17,11 @@ TVM_REGISTER_API("_Var")
     *ret = Variable::make(args[1], args[0]);
   });
 
+TVM_REGISTER_API("make.abs")
+.set_body([](TVMArgs args,  TVMRetValue *ret) {
+    *ret = tvm::abs(args[0]);
+  });
+
 TVM_REGISTER_API("make._range_by_min_extent")
 .set_body([](TVMArgs args,  TVMRetValue *ret) {
     *ret = Range::make_by_min_extent(args[0], args[1]);
diff --git a/src/codegen/intrin_rule_cuda.cc b/src/codegen/intrin_rule_cuda.cc
index d291463bce9f..43461a15932d 100644
--- a/src/codegen/intrin_rule_cuda.cc
+++ b/src/codegen/intrin_rule_cuda.cc
@@ -64,6 +64,9 @@ TVM_REGISTER_GLOBAL("tvm.intrin.rule.cuda.ceil")
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.cuda.trunc")
 .set_body(DispatchExtern<CUDAMath>);
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.cuda.fabs")
+.set_body(DispatchExtern<CUDAMath>);
+
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.cuda.round")
 .set_body(DispatchExtern<CUDAMath>);
 
diff --git a/src/codegen/intrin_rule_metal.cc b/src/codegen/intrin_rule_metal.cc
index 659a82df0757..3c210919132e 100644
--- a/src/codegen/intrin_rule_metal.cc
+++ b/src/codegen/intrin_rule_metal.cc
@@ -18,6 +18,9 @@ TVM_REGISTER_GLOBAL("tvm.intrin.rule.metal.ceil")
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.metal.trunc")
 .set_body(DispatchExtern<Direct>);
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.metal.fabs")
+.set_body(DispatchExtern<Direct>);
+
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.metal.round")
 .set_body(DispatchExtern<Direct>);
 
diff --git a/src/codegen/intrin_rule_opencl.cc b/src/codegen/intrin_rule_opencl.cc
index 127fe47d5b75..d91deaeda5fe 100644
--- a/src/codegen/intrin_rule_opencl.cc
+++ b/src/codegen/intrin_rule_opencl.cc
@@ -18,6 +18,9 @@ TVM_REGISTER_GLOBAL("tvm.intrin.rule.opencl.ceil")
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.opencl.trunc")
 .set_body(DispatchExtern<Direct>);
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.opencl.fabs")
+.set_body(DispatchExtern<Direct>);
+
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.opencl.round")
 .set_body(DispatchExtern<Direct>);
 
diff --git a/src/codegen/llvm/intrin_rule_llvm.cc b/src/codegen/llvm/intrin_rule_llvm.cc
index 0d6f8d07b79f..4b2a3ca5bd02 100644
--- a/src/codegen/llvm/intrin_rule_llvm.cc
+++ b/src/codegen/llvm/intrin_rule_llvm.cc
@@ -34,6 +34,9 @@ TVM_REGISTER_GLOBAL("tvm.intrin.rule.llvm.ceil")
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.llvm.trunc")
 .set_body(DispatchLLVMPureIntrin<::llvm::Intrinsic::trunc, 1>);
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.llvm.fabs")
+.set_body(DispatchLLVMPureIntrin<::llvm::Intrinsic::fabs, 1>);
+
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.llvm.round")
 .set_body(DispatchLLVMPureIntrin<::llvm::Intrinsic::round, 1>);
 
diff --git a/src/codegen/llvm/intrin_rule_nvptx.cc b/src/codegen/llvm/intrin_rule_nvptx.cc
index 60957198d5f3..d0b9f3693192 100644
--- a/src/codegen/llvm/intrin_rule_nvptx.cc
+++ b/src/codegen/llvm/intrin_rule_nvptx.cc
@@ -39,6 +39,9 @@ TVM_REGISTER_GLOBAL("tvm.intrin.rule.nvptx.round")
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.nvptx.trunc")
 .set_body(DispatchExternLibDevice);
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.nvptx.fabs")
+.set_body(DispatchExternLibDevice);
+
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.nvptx.exp")
 .set_body(DispatchExternLibDevice);
 
diff --git a/src/codegen/llvm/intrin_rule_rocm.cc b/src/codegen/llvm/intrin_rule_rocm.cc
index 9d105a35385f..b9bee94e9c24 100644
--- a/src/codegen/llvm/intrin_rule_rocm.cc
+++ b/src/codegen/llvm/intrin_rule_rocm.cc
@@ -38,6 +38,9 @@ TVM_REGISTER_GLOBAL("tvm.intrin.rule.rocm.round")
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.rocm.trunc")
 .set_body(DispatchLLVMPureIntrin<::llvm::Intrinsic::trunc, 1>);
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.rocm.fabs")
+.set_body(DispatchLLVMPureIntrin<::llvm::Intrinsic::fabs, 1>);
+
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.rocm.exp")
 .set_body(DispatchExternOCML);
 
diff --git a/src/codegen/spirv/intrin_rule_spirv.cc b/src/codegen/spirv/intrin_rule_spirv.cc
index cf9c446ea3f8..a7fa46bda60a 100644
--- a/src/codegen/spirv/intrin_rule_spirv.cc
+++ b/src/codegen/spirv/intrin_rule_spirv.cc
@@ -41,6 +41,9 @@ TVM_REGISTER_GLOBAL("tvm.intrin.rule.vulkan.round")
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.vulkan.trunc")
 .set_body(DispatchGLSLPureIntrin<GLSLstd450Trunc>);
 
+TVM_REGISTER_GLOBAL("tvm.intrin.rule.vulkan.fabs")
+.set_body(DispatchGLSLPureIntrin<GLSLstd450FAbs>);
+
 TVM_REGISTER_GLOBAL("tvm.intrin.rule.vulkan.exp")
 .set_body(DispatchGLSLPureIntrin<GLSLstd450Exp>);
 
diff --git a/topi/include/topi/elemwise.h b/topi/include/topi/elemwise.h
index 7082106d09ee..88c77f0afc52 100644
--- a/topi/include/topi/elemwise.h
+++ b/topi/include/topi/elemwise.h
@@ -35,6 +35,7 @@ TOPI_DECLARE_UNARY_OP(floor);
 TOPI_DECLARE_UNARY_OP(ceil);
 TOPI_DECLARE_UNARY_OP(round);
 TOPI_DECLARE_UNARY_OP(trunc);
+TOPI_DECLARE_UNARY_OP(abs);
 
 /*!
 * \brief Creates an operation that returns identity of a given tensor
diff --git a/topi/python/topi/math.py b/topi/python/topi/math.py
index 62b1a34a2c96..a5d28d351719 100644
--- a/topi/python/topi/math.py
+++ b/topi/python/topi/math.py
@@ -125,6 +125,23 @@ def trunc(x):
     return tvm.compute(x.shape, lambda *i: tvm.trunc(x(*i)))
 
 
+@tvm.tag_scope(tag=tag.ELEMWISE)
+def abs(x):
+    """Take absolute value of the input of x, element-wise.
+
+    Parameters
+    ----------
+    x : tvm.Tensor
+        Input argument.
+
+    Returns
+    -------
+    y : tvm.Tensor
+        The result.
+    """
+    return tvm.compute(x.shape, lambda *i: tvm.abs(x(*i)))
+
+
 @tvm.tag_scope(tag=tag.ELEMWISE)
 def round(x):
     """Round elements of x to nearest integer.
diff --git a/topi/tests/python/test_topi_math.py b/topi/tests/python/test_topi_math.py
index 0205407790d6..4190c8e1d213 100644
--- a/topi/tests/python/test_topi_math.py
+++ b/topi/tests/python/test_topi_math.py
@@ -46,6 +46,7 @@ def check_device(device):
     test_apply(topi.floor, "floor", np.floor, -100, 100)
     test_apply(topi.ceil, "ceil", np.ceil, -100, 100)
     test_apply(topi.trunc, "trunc", np.trunc, -100, 100)
+    test_apply(topi.abs, "fabs", np.abs, -100, 100)
     test_apply(topi.round, "round", np.round, -100, 100)
     test_apply(topi.exp, "exp", np.exp, -1, 1)
     test_apply(topi.tanh, "tanh", np.tanh, -10, 10)

From 68428b8d09ab2435947fe2f3b32d5665da098737 Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Fri, 13 Jul 2018 09:50:43 -0700
Subject: [PATCH 868/949] fix android packed runtime (#1430)

---
 apps/android_rpc/app/src/main/jni/tvm_runtime.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/apps/android_rpc/app/src/main/jni/tvm_runtime.h b/apps/android_rpc/app/src/main/jni/tvm_runtime.h
index c24abbec0075..b4d5284863a1 100644
--- a/apps/android_rpc/app/src/main/jni/tvm_runtime.h
+++ b/apps/android_rpc/app/src/main/jni/tvm_runtime.h
@@ -23,6 +23,7 @@
 #include "../src/runtime/thread_pool.cc"
 #include "../src/runtime/threading_backend.cc"
 #include "../src/runtime/graph/graph_runtime.cc"
+#include "../src/runtime/ndarray.cc"
 
 #ifdef TVM_OPENCL_RUNTIME
 #include "../src/runtime/opencl/opencl_device_api.cc"

From df95eb040a98e7d3e0f743a68724c097e4aa627b Mon Sep 17 00:00:00 2001
From: Siju <sijusamuel@gmail.com>
Date: Fri, 13 Jul 2018 22:22:14 +0530
Subject: [PATCH 869/949] [Darknet] softmax temperature in frontend (#1429)

---
 nnvm/python/nnvm/frontend/darknet.py          |   5 +
 .../python/frontend/darknet/test_forward.py   | 592 +++++++++---------
 2 files changed, 313 insertions(+), 284 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/darknet.py b/nnvm/python/nnvm/frontend/darknet.py
index c18585a27dcc..878b31846406 100644
--- a/nnvm/python/nnvm/frontend/darknet.py
+++ b/nnvm/python/nnvm/frontend/darknet.py
@@ -260,6 +260,9 @@ def _darknet_reshape(inputs, attrs):
 
 def _darknet_softmax_output(inputs, attrs):
     """Process the softmax operation."""
+    temperature = attrs.get('temperature', 1)
+    if temperature != 1:
+        inputs[0] = inputs[0] / float(temperature)
     op_name, new_attrs = 'softmax', {}
     if _darknet_parse_bool_str(attrs, 'multi_output'):
         new_attrs['axis'] = 1
@@ -529,6 +532,8 @@ def _get_darknet_attrs(net, layer_num):
     elif LAYERTYPE.SOFTMAX == layer.type:
         attr.update({'axis' : 1})
         attr.update({'use_flatten' : True})
+        if layer.temperature:
+            attr.update({'temperature' : str(layer.temperature)})
 
     elif LAYERTYPE.SHORTCUT == layer.type:
         add_layer = net.layers[layer.index]
diff --git a/nnvm/tests/python/frontend/darknet/test_forward.py b/nnvm/tests/python/frontend/darknet/test_forward.py
index 6077d6b9d127..0abc595c426f 100644
--- a/nnvm/tests/python/frontend/darknet/test_forward.py
+++ b/nnvm/tests/python/frontend/darknet/test_forward.py
@@ -1,284 +1,308 @@
-"""
-Compile Darknet Models
-=====================
-This article is a test script to test darknet models with NNVM.
-All the required models and libraries will be downloaded from the internet
-by the script.
-"""
-import os
-import requests
-import numpy as np
-from nnvm import frontend
-from nnvm.testing.darknet import __darknetffi__
-import nnvm.compiler
-import tvm
-import sys
-import urllib
-if sys.version_info >= (3,):
-    import urllib.request as urllib2
-else:
-    import urllib2
-
-def _download(url, path, overwrite=False, sizecompare=False):
-    ''' Download from internet'''
-    if os.path.isfile(path) and not overwrite:
-        if sizecompare:
-            file_size = os.path.getsize(path)
-            res_head = requests.head(url)
-            res_get = requests.get(url, stream=True)
-            if 'Content-Length' not in res_head.headers:
-                res_get = urllib2.urlopen(url)
-            urlfile_size = int(res_get.headers['Content-Length'])
-            if urlfile_size != file_size:
-                print("exist file got corrupted, downloading", path, " file freshly")
-                _download(url, path, True, False)
-                return
-        print('File {} exists, skip.'.format(path))
-        return
-    print('Downloading from url {} to {}'.format(url, path))
-    try:
-        urllib.request.urlretrieve(url, path)
-        print('')
-    except:
-        urllib.urlretrieve(url, path)
-
-DARKNET_LIB = 'libdarknet.so'
-DARKNETLIB_URL = 'https://github.com/siju-samuel/darknet/blob/master/lib/' \
-                                    + DARKNET_LIB + '?raw=true'
-_download(DARKNETLIB_URL, DARKNET_LIB)
-LIB = __darknetffi__.dlopen('./' + DARKNET_LIB)
-
-def test_forward(net):
-    '''Test network with given input image on both darknet and tvm'''
-    def get_darknet_output(net, img):
-        return LIB.network_predict_image(net, img)
-
-    def get_tvm_output(net, img):
-        '''Compute TVM output'''
-        dtype = 'float32'
-        batch_size = 1
-        sym, params = frontend.darknet.from_darknet(net, dtype)
-        data = np.empty([batch_size, img.c, img.h, img.w], dtype)
-        i = 0
-        for c in range(img.c):
-            for h in range(img.h):
-                for k in range(img.w):
-                    data[0][c][h][k] = img.data[i]
-                    i = i + 1
-
-        target = 'llvm'
-        shape_dict = {'data': data.shape}
-        #with nnvm.compiler.build_config(opt_level=2):
-        graph, library, params = nnvm.compiler.build(sym, target, shape_dict, dtype, params=params)
-        ######################################################################
-        # Execute on TVM
-        # ---------------
-        # The process is no different from other examples.
-        from tvm.contrib import graph_runtime
-        ctx = tvm.cpu(0)
-        m = graph_runtime.create(graph, library, ctx)
-        # set inputs
-        m.set_input('data', tvm.nd.array(data.astype(dtype)))
-        m.set_input(**params)
-        m.run()
-        # get outputs
-        out_shape = (net.outputs,)
-        tvm_out = m.get_output(0, tvm.nd.empty(out_shape, dtype)).asnumpy()
-        return tvm_out
-
-    test_image = 'dog.jpg'
-    img_url = 'https://github.com/siju-samuel/darknet/blob/master/data/' + test_image   +'?raw=true'
-    _download(img_url, test_image)
-    img = LIB.letterbox_image(LIB.load_image_color(test_image.encode('utf-8'), 0, 0), net.w, net.h)
-    darknet_output = get_darknet_output(net, img)
-    darknet_out = np.zeros(net.outputs, dtype='float32')
-    for i in range(net.outputs):
-        darknet_out[i] = darknet_output[i]
-    tvm_out = get_tvm_output(net, img)
-    np.testing.assert_allclose(darknet_out, tvm_out, rtol=1e-3, atol=1e-3)
-
-def test_forward_extraction():
-    '''test extraction model'''
-    model_name = 'extraction'
-    cfg_name = model_name + '.cfg'
-    weights_name = model_name + '.weights'
-    cfg_url = 'https://github.com/pjreddie/darknet/blob/master/cfg/' + cfg_name + '?raw=true'
-    weights_url = 'http://pjreddie.com/media/files/' + weights_name + '?raw=true'
-    _download(cfg_url, cfg_name)
-    _download(weights_url, weights_name)
-    net = LIB.load_network(cfg_name.encode('utf-8'), weights_name.encode('utf-8'), 0)
-    test_forward(net)
-    LIB.free_network(net)
-
-def test_forward_alexnet():
-    '''test alexnet model'''
-    model_name = 'alexnet'
-    cfg_name = model_name + '.cfg'
-    weights_name = model_name + '.weights'
-    cfg_url = 'https://github.com/pjreddie/darknet/blob/master/cfg/' + cfg_name + '?raw=true'
-    weights_url = 'http://pjreddie.com/media/files/' + weights_name + '?raw=true'
-    _download(cfg_url, cfg_name)
-    _download(weights_url, weights_name)
-    net = LIB.load_network(cfg_name.encode('utf-8'), weights_name.encode('utf-8'), 0)
-    test_forward(net)
-    LIB.free_network(net)
-
-def test_forward_resnet50():
-    '''test resnet50 model'''
-    model_name = 'resnet50'
-    cfg_name = model_name + '.cfg'
-    weights_name = model_name + '.weights'
-    cfg_url = 'https://github.com/pjreddie/darknet/blob/master/cfg/' + cfg_name + '?raw=true'
-    weights_url = 'http://pjreddie.com/media/files/' + weights_name + '?raw=true'
-    _download(cfg_url, cfg_name)
-    _download(weights_url, weights_name)
-    net = LIB.load_network(cfg_name.encode('utf-8'), weights_name.encode('utf-8'), 0)
-    test_forward(net)
-    LIB.free_network(net)
-
-def test_forward_yolo():
-    '''test yolo model'''
-    model_name = 'yolov2'
-    cfg_name = model_name + '.cfg'
-    weights_name = model_name + '.weights'
-    cfg_url = 'https://github.com/pjreddie/darknet/blob/master/cfg/' + cfg_name + '?raw=true'
-    weights_url = 'http://pjreddie.com/media/files/' + weights_name + '?raw=true'
-    _download(cfg_url, cfg_name)
-    _download(weights_url, weights_name)
-    net = LIB.load_network(cfg_name.encode('utf-8'), weights_name.encode('utf-8'), 0)
-    test_forward(net)
-    LIB.free_network(net)
-
-def test_forward_convolutional():
-    '''test convolutional layer'''
-    net = LIB.make_network(1)
-    layer = LIB.make_convolutional_layer(1, 224, 224, 3, 32, 1, 3, 2, 0, 1, 0, 0, 0, 0)
-    net.layers[0] = layer
-    net.w = net.h = 224
-    LIB.resize_network(net, 224, 224)
-    test_forward(net)
-    LIB.free_network(net)
-
-def test_forward_dense():
-    '''test fully connected layer'''
-    net = LIB.make_network(1)
-    layer = LIB.make_connected_layer(1, 75, 20, 1, 0, 0)
-    net.layers[0] = layer
-    net.w = net.h = 5
-    LIB.resize_network(net, 5, 5)
-    test_forward(net)
-    LIB.free_network(net)
-
-def test_forward_dense_batchnorm():
-    '''test fully connected layer with batchnorm'''
-    net = LIB.make_network(1)
-    layer = LIB.make_connected_layer(1, 12, 2, 1, 1, 0)
-    for i in range(5):
-        layer.rolling_mean[i] = np.random.rand(1)
-        layer.rolling_variance[i] = np.random.rand(1)
-        layer.scales[i] = np.random.rand(1)
-    net.layers[0] = layer
-    net.w = net.h = 2
-    LIB.resize_network(net, 2, 2)
-    test_forward(net)
-    LIB.free_network(net)
-
-def test_forward_maxpooling():
-    '''test maxpooling layer'''
-    net = LIB.make_network(1)
-    layer = LIB.make_maxpool_layer(1, 224, 224, 3, 2, 2, 0)
-    net.layers[0] = layer
-    net.w = net.h = 224
-    LIB.resize_network(net, 224, 224)
-    test_forward(net)
-    LIB.free_network(net)
-
-def test_forward_avgpooling():
-    '''test avgerage pooling layer'''
-    net = LIB.make_network(1)
-    layer = LIB.make_avgpool_layer(1, 224, 224, 3)
-    net.layers[0] = layer
-    net.w = net.h = 224
-    LIB.resize_network(net, 224, 224)
-    test_forward(net)
-    LIB.free_network(net)
-
-def test_forward_batch_norm():
-    '''test batch normalization layer'''
-    net = LIB.make_network(1)
-    layer = LIB.make_convolutional_layer(1, 224, 224, 3, 32, 1, 3, 2, 0, 1, 1, 0, 0, 0)
-    for i in range(32):
-        layer.rolling_mean[i] = np.random.rand(1)
-        layer.rolling_variance[i] = np.random.rand(1)
-    net.layers[0] = layer
-    net.w = net.h = 224
-    LIB.resize_network(net, 224, 224)
-    test_forward(net)
-    LIB.free_network(net)
-
-def test_forward_shortcut():
-    '''test shortcut layer'''
-    net = LIB.make_network(3)
-    layer_1 = LIB.make_convolutional_layer(1, 224, 224, 3, 32, 1, 3, 2, 0, 1, 0, 0, 0, 0)
-    layer_2 = LIB.make_convolutional_layer(1, 111, 111, 32, 32, 1, 1, 1, 0, 1, 0, 0, 0, 0)
-    layer_3 = LIB.make_shortcut_layer(1, 0, 111, 111, 32, 111, 111, 32)
-    layer_3.activation = 1
-    net.layers[0] = layer_1
-    net.layers[1] = layer_2
-    net.layers[2] = layer_3
-    net.w = net.h = 224
-    LIB.resize_network(net, 224, 224)
-    test_forward(net)
-    LIB.free_network(net)
-
-def test_forward_reorg():
-    '''test reorg layer'''
-    net = LIB.make_network(2)
-    layer_1 = LIB.make_convolutional_layer(1, 222, 222, 3, 32, 1, 3, 2, 0, 1, 0, 0, 0, 0)
-    layer_2 = LIB.make_reorg_layer(1, 110, 110, 32, 2, 0, 0, 0)
-    net.layers[0] = layer_1
-    net.layers[1] = layer_2
-    net.w = net.h = 222
-    LIB.resize_network(net, 222, 222)
-    test_forward(net)
-    LIB.free_network(net)
-
-def test_forward_region():
-    '''test region layer'''
-    net = LIB.make_network(2)
-    layer_1 = LIB.make_convolutional_layer(1, 224, 224, 3, 8, 1, 3, 2, 0, 1, 0, 0, 0, 0)
-    layer_2 = LIB.make_region_layer(1, 111, 111, 2, 2, 1)
-    layer_2.softmax = 1
-    net.layers[0] = layer_1
-    net.layers[1] = layer_2
-    net.w = net.h = 224
-    LIB.resize_network(net, 224, 224)
-    test_forward(net)
-    LIB.free_network(net)
-
-def test_forward_elu():
-    '''test elu activation layer'''
-    net = LIB.make_network(1)
-    layer_1 = LIB.make_convolutional_layer(1, 224, 224, 3, 32, 1, 3, 2, 0, 1, 0, 0, 0, 0)
-    layer_1.activation = 8
-    net.layers[0] = layer_1
-    net.w = net.h = 224
-    LIB.resize_network(net, 224, 224)
-    test_forward(net)
-    LIB.free_network(net)
-
-if __name__ == '__main__':
-    test_forward_resnet50()
-    test_forward_alexnet()
-    test_forward_extraction()
-    test_forward_yolo()
-    test_forward_convolutional()
-    test_forward_maxpooling()
-    test_forward_avgpooling()
-    test_forward_batch_norm()
-    test_forward_shortcut()
-    test_forward_dense()
-    test_forward_dense_batchnorm()
-    test_forward_reorg()
-    test_forward_region()
-    test_forward_elu()
+"""
+Compile Darknet Models
+=====================
+This article is a test script to test darknet models with NNVM.
+All the required models and libraries will be downloaded from the internet
+by the script.
+"""
+import os
+import requests
+import numpy as np
+from nnvm import frontend
+from nnvm.testing.darknet import __darknetffi__
+import nnvm.compiler
+import tvm
+import sys
+import urllib
+if sys.version_info >= (3,):
+    import urllib.request as urllib2
+else:
+    import urllib2
+
+def _download(url, path, overwrite=False, sizecompare=False):
+    ''' Download from internet'''
+    if os.path.isfile(path) and not overwrite:
+        if sizecompare:
+            file_size = os.path.getsize(path)
+            res_head = requests.head(url)
+            res_get = requests.get(url, stream=True)
+            if 'Content-Length' not in res_head.headers:
+                res_get = urllib2.urlopen(url)
+            urlfile_size = int(res_get.headers['Content-Length'])
+            if urlfile_size != file_size:
+                print("exist file got corrupted, downloading", path, " file freshly")
+                _download(url, path, True, False)
+                return
+        print('File {} exists, skip.'.format(path))
+        return
+    print('Downloading from url {} to {}'.format(url, path))
+    try:
+        urllib.request.urlretrieve(url, path)
+        print('')
+    except:
+        urllib.urlretrieve(url, path)
+
+DARKNET_LIB = 'libdarknet.so'
+DARKNETLIB_URL = 'https://github.com/siju-samuel/darknet/blob/master/lib/' \
+                                    + DARKNET_LIB + '?raw=true'
+_download(DARKNETLIB_URL, DARKNET_LIB)
+LIB = __darknetffi__.dlopen('./' + DARKNET_LIB)
+
+def test_forward(net):
+    '''Test network with given input image on both darknet and tvm'''
+    def get_darknet_output(net, img):
+        return LIB.network_predict_image(net, img)
+
+    def get_tvm_output(net, img):
+        '''Compute TVM output'''
+        dtype = 'float32'
+        batch_size = 1
+        sym, params = frontend.darknet.from_darknet(net, dtype)
+        data = np.empty([batch_size, img.c, img.h, img.w], dtype)
+        i = 0
+        for c in range(img.c):
+            for h in range(img.h):
+                for k in range(img.w):
+                    data[0][c][h][k] = img.data[i]
+                    i = i + 1
+
+        target = 'llvm'
+        shape_dict = {'data': data.shape}
+        #with nnvm.compiler.build_config(opt_level=2):
+        graph, library, params = nnvm.compiler.build(sym, target, shape_dict, dtype, params=params)
+        ######################################################################
+        # Execute on TVM
+        # ---------------
+        # The process is no different from other examples.
+        from tvm.contrib import graph_runtime
+        ctx = tvm.cpu(0)
+        m = graph_runtime.create(graph, library, ctx)
+        # set inputs
+        m.set_input('data', tvm.nd.array(data.astype(dtype)))
+        m.set_input(**params)
+        m.run()
+        # get outputs
+        out_shape = (net.outputs,)
+        tvm_out = m.get_output(0, tvm.nd.empty(out_shape, dtype)).asnumpy()
+        return tvm_out
+
+    test_image = 'dog.jpg'
+    img_url = 'https://github.com/siju-samuel/darknet/blob/master/data/' + test_image   +'?raw=true'
+    _download(img_url, test_image)
+    img = LIB.letterbox_image(LIB.load_image_color(test_image.encode('utf-8'), 0, 0), net.w, net.h)
+    darknet_output = get_darknet_output(net, img)
+    darknet_out = np.zeros(net.outputs, dtype='float32')
+    for i in range(net.outputs):
+        darknet_out[i] = darknet_output[i]
+    tvm_out = get_tvm_output(net, img)
+    np.testing.assert_allclose(darknet_out, tvm_out, rtol=1e-3, atol=1e-3)
+
+def test_forward_extraction():
+    '''test extraction model'''
+    model_name = 'extraction'
+    cfg_name = model_name + '.cfg'
+    weights_name = model_name + '.weights'
+    cfg_url = 'https://github.com/pjreddie/darknet/blob/master/cfg/' + cfg_name + '?raw=true'
+    weights_url = 'http://pjreddie.com/media/files/' + weights_name + '?raw=true'
+    _download(cfg_url, cfg_name)
+    _download(weights_url, weights_name)
+    net = LIB.load_network(cfg_name.encode('utf-8'), weights_name.encode('utf-8'), 0)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_alexnet():
+    '''test alexnet model'''
+    model_name = 'alexnet'
+    cfg_name = model_name + '.cfg'
+    weights_name = model_name + '.weights'
+    cfg_url = 'https://github.com/pjreddie/darknet/blob/master/cfg/' + cfg_name + '?raw=true'
+    weights_url = 'http://pjreddie.com/media/files/' + weights_name + '?raw=true'
+    _download(cfg_url, cfg_name)
+    _download(weights_url, weights_name)
+    net = LIB.load_network(cfg_name.encode('utf-8'), weights_name.encode('utf-8'), 0)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_resnet50():
+    '''test resnet50 model'''
+    model_name = 'resnet50'
+    cfg_name = model_name + '.cfg'
+    weights_name = model_name + '.weights'
+    cfg_url = 'https://github.com/pjreddie/darknet/blob/master/cfg/' + cfg_name + '?raw=true'
+    weights_url = 'http://pjreddie.com/media/files/' + weights_name + '?raw=true'
+    _download(cfg_url, cfg_name)
+    _download(weights_url, weights_name)
+    net = LIB.load_network(cfg_name.encode('utf-8'), weights_name.encode('utf-8'), 0)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_yolo():
+    '''test yolo model'''
+    model_name = 'yolov2'
+    cfg_name = model_name + '.cfg'
+    weights_name = model_name + '.weights'
+    cfg_url = 'https://github.com/pjreddie/darknet/blob/master/cfg/' + cfg_name + '?raw=true'
+    weights_url = 'http://pjreddie.com/media/files/' + weights_name + '?raw=true'
+    _download(cfg_url, cfg_name)
+    _download(weights_url, weights_name)
+    net = LIB.load_network(cfg_name.encode('utf-8'), weights_name.encode('utf-8'), 0)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_convolutional():
+    '''test convolutional layer'''
+    net = LIB.make_network(1)
+    layer = LIB.make_convolutional_layer(1, 224, 224, 3, 32, 1, 3, 2, 0, 1, 0, 0, 0, 0)
+    net.layers[0] = layer
+    net.w = net.h = 224
+    LIB.resize_network(net, 224, 224)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_dense():
+    '''test fully connected layer'''
+    net = LIB.make_network(1)
+    layer = LIB.make_connected_layer(1, 75, 20, 1, 0, 0)
+    net.layers[0] = layer
+    net.w = net.h = 5
+    LIB.resize_network(net, 5, 5)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_dense_batchnorm():
+    '''test fully connected layer with batchnorm'''
+    net = LIB.make_network(1)
+    layer = LIB.make_connected_layer(1, 12, 2, 1, 1, 0)
+    for i in range(5):
+        layer.rolling_mean[i] = np.random.rand(1)
+        layer.rolling_variance[i] = np.random.rand(1)
+        layer.scales[i] = np.random.rand(1)
+    net.layers[0] = layer
+    net.w = net.h = 2
+    LIB.resize_network(net, 2, 2)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_maxpooling():
+    '''test maxpooling layer'''
+    net = LIB.make_network(1)
+    layer = LIB.make_maxpool_layer(1, 224, 224, 3, 2, 2, 0)
+    net.layers[0] = layer
+    net.w = net.h = 224
+    LIB.resize_network(net, 224, 224)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_avgpooling():
+    '''test avgerage pooling layer'''
+    net = LIB.make_network(1)
+    layer = LIB.make_avgpool_layer(1, 224, 224, 3)
+    net.layers[0] = layer
+    net.w = net.h = 224
+    LIB.resize_network(net, 224, 224)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_batch_norm():
+    '''test batch normalization layer'''
+    net = LIB.make_network(1)
+    layer = LIB.make_convolutional_layer(1, 224, 224, 3, 32, 1, 3, 2, 0, 1, 1, 0, 0, 0)
+    for i in range(32):
+        layer.rolling_mean[i] = np.random.rand(1)
+        layer.rolling_variance[i] = np.random.rand(1)
+    net.layers[0] = layer
+    net.w = net.h = 224
+    LIB.resize_network(net, 224, 224)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_shortcut():
+    '''test shortcut layer'''
+    net = LIB.make_network(3)
+    layer_1 = LIB.make_convolutional_layer(1, 224, 224, 3, 32, 1, 3, 2, 0, 1, 0, 0, 0, 0)
+    layer_2 = LIB.make_convolutional_layer(1, 111, 111, 32, 32, 1, 1, 1, 0, 1, 0, 0, 0, 0)
+    layer_3 = LIB.make_shortcut_layer(1, 0, 111, 111, 32, 111, 111, 32)
+    layer_3.activation = 1
+    net.layers[0] = layer_1
+    net.layers[1] = layer_2
+    net.layers[2] = layer_3
+    net.w = net.h = 224
+    LIB.resize_network(net, 224, 224)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_reorg():
+    '''test reorg layer'''
+    net = LIB.make_network(2)
+    layer_1 = LIB.make_convolutional_layer(1, 222, 222, 3, 32, 1, 3, 2, 0, 1, 0, 0, 0, 0)
+    layer_2 = LIB.make_reorg_layer(1, 110, 110, 32, 2, 0, 0, 0)
+    net.layers[0] = layer_1
+    net.layers[1] = layer_2
+    net.w = net.h = 222
+    LIB.resize_network(net, 222, 222)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_region():
+    '''test region layer'''
+    net = LIB.make_network(2)
+    layer_1 = LIB.make_convolutional_layer(1, 224, 224, 3, 8, 1, 3, 2, 0, 1, 0, 0, 0, 0)
+    layer_2 = LIB.make_region_layer(1, 111, 111, 2, 2, 1)
+    layer_2.softmax = 1
+    net.layers[0] = layer_1
+    net.layers[1] = layer_2
+    net.w = net.h = 224
+    LIB.resize_network(net, 224, 224)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_elu():
+    '''test elu activation layer'''
+    net = LIB.make_network(1)
+    layer_1 = LIB.make_convolutional_layer(1, 224, 224, 3, 32, 1, 3, 2, 0, 1, 0, 0, 0, 0)
+    layer_1.activation = 8
+    net.layers[0] = layer_1
+    net.w = net.h = 224
+    LIB.resize_network(net, 224, 224)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_softmax():
+    '''test softmax layer'''
+    net = LIB.make_network(1)
+    layer_1 = LIB.make_softmax_layer(1, 75, 1)
+    layer_1.temperature=1
+    net.layers[0] = layer_1
+    net.w = net.h = 5
+    LIB.resize_network(net, net.w, net.h)
+    test_forward(net)
+    LIB.free_network(net)
+
+def test_forward_softmax_temperature():
+    '''test softmax layer'''
+    net = LIB.make_network(1)
+    layer_1 = LIB.make_softmax_layer(1, 75, 1)
+    layer_1.temperature=0.8
+    net.layers[0] = layer_1
+    net.w = net.h = 5
+    LIB.resize_network(net, net.w, net.h)
+    test_forward(net)
+    LIB.free_network(net)
+
+if __name__ == '__main__':
+    test_forward_resnet50()
+    test_forward_alexnet()
+    test_forward_extraction()
+    test_forward_yolo()
+    test_forward_convolutional()
+    test_forward_maxpooling()
+    test_forward_avgpooling()
+    test_forward_batch_norm()
+    test_forward_shortcut()
+    test_forward_dense()
+    test_forward_dense_batchnorm()
+    test_forward_softmax()
+    test_forward_softmax_temperature()
+    test_forward_reorg()
+    test_forward_region()
+    test_forward_elu()

From 30c1cd653ae61bced7d4428949fd69bd9147fbd4 Mon Sep 17 00:00:00 2001
From: Siva <sivar.b@huawei.com>
Date: Sat, 14 Jul 2018 00:01:47 +0530
Subject: [PATCH 870/949] [TVM][RUNTIME] dependencied update for ndarray.
 (#1431)

---
 apps/android_deploy/app/src/main/jni/tvm_runtime.h | 1 +
 apps/howto_deploy/tvm_runtime_pack.cc              | 1 +
 apps/ios_rpc/tvmrpc/TVMRuntime.mm                  | 1 +
 3 files changed, 3 insertions(+)

diff --git a/apps/android_deploy/app/src/main/jni/tvm_runtime.h b/apps/android_deploy/app/src/main/jni/tvm_runtime.h
index 69b1f5c83f1f..0b5f4ee67237 100644
--- a/apps/android_deploy/app/src/main/jni/tvm_runtime.h
+++ b/apps/android_deploy/app/src/main/jni/tvm_runtime.h
@@ -17,6 +17,7 @@
 #include "../src/runtime/dso_module.cc"
 #include "../src/runtime/thread_pool.cc"
 #include "../src/runtime/threading_backend.cc"
+#include "../src/runtime/ndarray.cc"
 
 #include "../src/runtime/graph/graph_runtime.cc"
 
diff --git a/apps/howto_deploy/tvm_runtime_pack.cc b/apps/howto_deploy/tvm_runtime_pack.cc
index e35aa2f90561..27f95e9e6065 100644
--- a/apps/howto_deploy/tvm_runtime_pack.cc
+++ b/apps/howto_deploy/tvm_runtime_pack.cc
@@ -27,6 +27,7 @@
 #include "../../src/runtime/file_util.cc"
 #include "../../src/runtime/threading_backend.cc"
 #include "../../src/runtime/thread_pool.cc"
+#include "../../src/runtime/ndarray.cc"
 
 // NOTE: all the files after this are optional modules
 // that you can include remove, depending on how much feature you use.
diff --git a/apps/ios_rpc/tvmrpc/TVMRuntime.mm b/apps/ios_rpc/tvmrpc/TVMRuntime.mm
index ecda4f6ee84a..53e58edc5b5d 100644
--- a/apps/ios_rpc/tvmrpc/TVMRuntime.mm
+++ b/apps/ios_rpc/tvmrpc/TVMRuntime.mm
@@ -15,6 +15,7 @@
 #include "../../src/runtime/registry.cc"
 #include "../../src/runtime/file_util.cc"
 #include "../../src/runtime/dso_module.cc"
+#include "../../src/runtime/ndarray.cc"
 // RPC server
 #include "../../src/runtime/rpc/rpc_session.cc"
 #include "../../src/runtime/rpc/rpc_server_env.cc"

From 1af1287ea1094d5c268160f2f33e819ef17b3687 Mon Sep 17 00:00:00 2001
From: MORITA Kazutaka <morita.kazutaka@lab.ntt.co.jp>
Date: Sun, 15 Jul 2018 06:14:46 +0900
Subject: [PATCH 871/949] [RUNTIME][SDACCEL] Add support for multiple kernels
 (#1424)

---
 python/tvm/contrib/sdaccel.py               | 44 ++++++++++++---------
 src/codegen/codegen_vhls.cc                 | 25 +++++++-----
 tests/python/integration/test_ewise_fpga.py | 41 ++++++++++++++++++-
 3 files changed, 81 insertions(+), 29 deletions(-)

diff --git a/python/tvm/contrib/sdaccel.py b/python/tvm/contrib/sdaccel.py
index 6a0c8c3390cf..3e38c6edea0e 100644
--- a/python/tvm/contrib/sdaccel.py
+++ b/python/tvm/contrib/sdaccel.py
@@ -6,16 +6,14 @@
 
 
 @register_func("tvm_callback_sdaccel_compile")
-def compile_vhls(code, kernel):
+def compile_vhls(kernel_info):
     """Compile Vivado HLS code for SDAccel.
 
     Parameters
     ----------
-    code : str
-        The Vivado HLS code.
-
-    kernel : str
-        The kernel to compile or link.
+    kernel_info : list of (str, str)
+        List of kernel information.  The kernel information is a tuple of
+        function name and source code.
 
     Return
     ------
@@ -23,12 +21,6 @@ def compile_vhls(code, kernel):
         The bytearray of the xclbin
     """
     tmp_dir = util.tempdir()
-    tmp_cpp = tmp_dir.relpath("input.cpp")
-    tmp_xo = tmp_dir.relpath("output.xo")
-    tmp_xclbin = tmp_dir.relpath("output.xclbin")
-
-    with open(tmp_cpp, "wb") as out_file:
-        out_file.write(bytes(code))
 
     sdk = os.environ.get("XILINX_SDX", None)
     xocc = os.path.join(sdk, "bin/xocc") if sdk else "xocc"
@@ -41,15 +33,29 @@ def compile_vhls(code, kernel):
     if platform is None:
         raise RuntimeError("No Xlinx device specified.")
 
-    # build xo
-    args = [xocc, "-c", "-t", target, "--platform", platform, "-o", tmp_xo, "-k", kernel] + \
-           advanced_params + [tmp_cpp]
-    returncode = subprocess.call(args)
-    if returncode != 0:
-        raise RuntimeError("Compile error")
+    tmp_xo_files = []
+    for funcname, code  in kernel_info:
+        funcname = funcname.value
+        code = code.value
+
+        tmp_cpp = tmp_dir.relpath(funcname + ".cpp")
+        tmp_xo = tmp_dir.relpath(funcname + ".xo")
+
+        with open(tmp_cpp, "wb") as out_file:
+            out_file.write(bytes(code))
+
+        # build xo
+        args = [xocc, "-c", "-t", target, "--platform", platform, "-o", tmp_xo, "-k", funcname] + \
+               advanced_params + [tmp_cpp]
+        returncode = subprocess.call(args)
+        if returncode != 0:
+            raise RuntimeError("Compile error")
+
+        tmp_xo_files.append(tmp_xo)
 
     # build xclbin
-    args = [xocc, "-l", "-t", target, "--platform", platform, "-o", tmp_xclbin, tmp_xo] + \
+    tmp_xclbin = tmp_dir.relpath("output.xclbin")
+    args = [xocc, "-l", "-t", target, "--platform", platform, "-o", tmp_xclbin] + tmp_xo_files + \
            advanced_params
     returncode = subprocess.call(args)
     if returncode != 0:
diff --git a/src/codegen/codegen_vhls.cc b/src/codegen/codegen_vhls.cc
index f27a007d37a1..2831a3d98af2 100644
--- a/src/codegen/codegen_vhls.cc
+++ b/src/codegen/codegen_vhls.cc
@@ -72,26 +72,33 @@ runtime::Module BuildSDAccel(Array<LoweredFunc> funcs) {
   bool output_ssa = false;
   CodeGenVivadoHLS cg;
 
-  CHECK_EQ(funcs.size(), 1);
-  const std::string funcname = funcs[0]->name;
-
+  // Generate source code for get_source().
   cg.Init(output_ssa);
-
   for (LoweredFunc f : funcs) {
     cg.AddFunction(f);
   }
-  std::string code = cg.Finish();
-  if (const auto* f = runtime::Registry::Get("tvm_callback_vhls_postproc")) {
-    code = (*f)(code).operator std::string();
+  std::string whole_code = cg.Finish();
+
+  // Generate source code for compilation.
+  Array<Array<Expr> > kernel_info;
+  for (LoweredFunc f : funcs) {
+    CodeGenVivadoHLS cg;
+    cg.Init(output_ssa);
+    cg.AddFunction(f);
+    std::string code = cg.Finish();
+    if (const auto* f = runtime::Registry::Get("tvm_callback_vhls_postproc")) {
+      code = (*f)(code).operator std::string();
+    }
+    kernel_info.push_back(Array<Expr>({f->name, code}));
   }
 
   std::string xclbin;
   if (const auto* f = Registry::Get("tvm_callback_sdaccel_compile")) {
-    xclbin = (*f)(code, funcname).operator std::string();
+    xclbin = (*f)(kernel_info).operator std::string();
   } else {
     LOG(FATAL) << "Cannot compile Vivado HLS code.";
   }
-  return SDAccelModuleCreate(xclbin, "xclbin", ExtractFuncInfo(funcs), code);
+  return SDAccelModuleCreate(xclbin, "xclbin", ExtractFuncInfo(funcs), whole_code);
 }
 
 TVM_REGISTER_API("codegen.build_sdaccel")
diff --git a/tests/python/integration/test_ewise_fpga.py b/tests/python/integration/test_ewise_fpga.py
index fb7ca807340d..1c46b6be170b 100644
--- a/tests/python/integration/test_ewise_fpga.py
+++ b/tests/python/integration/test_ewise_fpga.py
@@ -21,7 +21,7 @@ def test_exp():
     s[B].bind(px, tvm.thread_axis("pipeline"))
 
     # one line to build the function.
-    def check_device(device, host="stackvm"):
+    def check_device(device, host="llvm"):
         if not tvm.module.enabled(host):
             return
         ctx = tvm.context(device, 0)
@@ -42,5 +42,44 @@ def check_device(device, host="stackvm"):
     check_device("sdaccel")
 
 
+def test_multi_kernel():
+    # graph
+    n = tvm.convert(1024)
+    A = tvm.placeholder((n,), name='A')
+    B = tvm.placeholder((n,), name='B')
+    C = tvm.compute(A.shape, lambda *i: A(*i) + B(*i), name='C')
+    D = tvm.compute(A.shape, lambda *i: A(*i) + C(*i), name='D')
+    s = tvm.create_schedule(D.op)
+    # create iter var and assign them tags.
+    px, x = s[C].split(C.op.axis[0], nparts=1)
+    s[C].bind(px, tvm.thread_axis("pipeline"))
+    px, x = s[D].split(D.op.axis[0], nparts=1)
+    s[D].bind(px, tvm.thread_axis("pipeline"))
+
+    # one line to build the function.
+    def check_device(device, host="llvm"):
+        if not tvm.module.enabled(host):
+            return
+        ctx = tvm.context(device, 0)
+        if not ctx.exist:
+            return
+        fadd = tvm.build(s, [A, B, C, D],
+                         device, host,
+                         name="myadd")
+        ctx = tvm.context(device, 0)
+        # launch the kernel.
+        n = 1024
+        a = tvm.nd.array(np.random.uniform(size=n).astype(A.dtype), ctx)
+        b = tvm.nd.array(np.random.uniform(size=n).astype(B.dtype), ctx)
+        c = tvm.nd.array(np.random.uniform(size=n).astype(C.dtype), ctx)
+        d = tvm.nd.array(np.random.uniform(size=n).astype(D.dtype), ctx)
+        fadd(a, b, c, d)
+        np.testing.assert_allclose(
+            d.asnumpy(), a.asnumpy() * 2 + b.asnumpy(), rtol=1e-5)
+
+    check_device("sdaccel")
+
+
 if __name__ == "__main__":
     test_exp()
+    test_multi_kernel()

From c53b23ffb2ac9511d5b02b91177aaeeba2f9ef91 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Sat, 14 Jul 2018 17:39:50 -0700
Subject: [PATCH 872/949] [BUILD] enhance vta build (#1434)

---
 CMakeLists.txt          | 2 --
 cmake/modules/VTA.cmake | 2 +-
 2 files changed, 1 insertion(+), 3 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index b43d37696b37..4ec5ea779f96 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -35,8 +35,6 @@ tvm_option(USE_RTTI "Build with RTTI" ON)
 tvm_option(USE_MSVC_MT "Build with MT" OFF)
 tvm_option(INSTALL_DEV "Install compiler infrastructure" OFF)
 
-tvm_option(USE_VTA_CFG "Use a specific json file for VTA runtime" "")
-
 # Contrib library options
 tvm_option(USE_BLAS "The blas library to be linked" none)
 tvm_option(USE_MKL_PATH "MKL root path when use MKL blas" none)
diff --git a/cmake/modules/VTA.cmake b/cmake/modules/VTA.cmake
index 1356c8560f3b..43fb700203c7 100644
--- a/cmake/modules/VTA.cmake
+++ b/cmake/modules/VTA.cmake
@@ -1,5 +1,5 @@
 # CMake Build rules for VTA
-find_program(PYTHON python)
+find_program(PYTHON NAMES python python3 python3.6)
 
 if(MSVC)
   message(STATUS "VTA build is skipped in Windows..")

From 18ac95c351e4c3a46060917601af4b67e77e030b Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Sun, 15 Jul 2018 15:23:18 -0700
Subject: [PATCH 873/949] [NNVM] Add symbol squeezenet (#1436)

---
 nnvm/python/nnvm/testing/__init__.py          |   1 +
 nnvm/python/nnvm/testing/mobilenet.py         |   2 +-
 nnvm/python/nnvm/testing/resnet.py            |   2 +-
 nnvm/python/nnvm/testing/squeezenet.py        | 132 ++++++++++++++++++
 .../frontend/mxnet/model_zoo/__init__.py      |  12 +-
 .../frontend/mxnet/model_zoo/squeezenet.py    |  76 ++++++++++
 .../tests/python/frontend/mxnet/test_graph.py |   8 ++
 tutorials/autotvm/tune_cuda_conv2d.py         |   2 +-
 tutorials/nnvm/imagenet_inference_gpu.py      |   2 +-
 tutorials/optimize/opt_conv_cuda.py           |   5 +-
 10 files changed, 235 insertions(+), 7 deletions(-)
 create mode 100644 nnvm/python/nnvm/testing/squeezenet.py
 create mode 100644 nnvm/tests/python/frontend/mxnet/model_zoo/squeezenet.py

diff --git a/nnvm/python/nnvm/testing/__init__.py b/nnvm/python/nnvm/testing/__init__.py
index 2700aea104f6..bff828d68280 100644
--- a/nnvm/python/nnvm/testing/__init__.py
+++ b/nnvm/python/nnvm/testing/__init__.py
@@ -7,6 +7,7 @@
 from . import mlp
 from . import resnet
 from . import vgg
+from . import squeezenet
 from . import dcgan
 from . import dqn
 from . import yolo2_detection
diff --git a/nnvm/python/nnvm/testing/mobilenet.py b/nnvm/python/nnvm/testing/mobilenet.py
index 1d59df64ab4a..feab1ca765bc 100644
--- a/nnvm/python/nnvm/testing/mobilenet.py
+++ b/nnvm/python/nnvm/testing/mobilenet.py
@@ -86,7 +86,7 @@ def get_workload(batch_size, num_classes=1000, image_shape=(3, 224, 224), dtype=
         The batch size used in the model
 
     num_classes : int, optional
-        Number of claseses
+        Number of classes
 
     image_shape : tuple, optional
         The input image shape
diff --git a/nnvm/python/nnvm/testing/resnet.py b/nnvm/python/nnvm/testing/resnet.py
index 243cc1b65144..6de0213679d1 100644
--- a/nnvm/python/nnvm/testing/resnet.py
+++ b/nnvm/python/nnvm/testing/resnet.py
@@ -199,7 +199,7 @@ def get_workload(batch_size=1, num_classes=1000, num_layers=18,
         The batch size used in the model
 
     num_classes : int, optional
-        Number of claseses
+        Number of classes
 
     num_layers : int, optional
         Number of layers
diff --git a/nnvm/python/nnvm/testing/squeezenet.py b/nnvm/python/nnvm/testing/squeezenet.py
new file mode 100644
index 000000000000..a445e8cfb7da
--- /dev/null
+++ b/nnvm/python/nnvm/testing/squeezenet.py
@@ -0,0 +1,132 @@
+# 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.
+
+# coding: utf-8
+# pylint: disable=unused-argument
+
+"""
+Symbol of SqueezeNet
+
+Reference:
+Iandola, Forrest N., et al.
+"Squeezenet: Alexnet-level accuracy with 50x fewer parameters and< 0.5 mb model size." (2016).
+"""
+
+from .. import symbol as sym
+from . utils import create_workload
+
+# Helpers
+def _make_fire(net, squeeze_channels, expand1x1_channels, expand3x3_channels):
+    net = _make_fire_conv(net, squeeze_channels, 1, 0)
+
+    left = _make_fire_conv(net, expand1x1_channels, 1, 0)
+    right = _make_fire_conv(net, expand3x3_channels, 3, 1)
+    # NOTE : Assume NCHW layout here
+    net = sym.concatenate(left, right, axis=1)
+
+    return net
+
+def _make_fire_conv(net, channels, kernel_size, padding=0):
+    net = sym.conv2d(net, channels=channels, kernel_size=(kernel_size, kernel_size),
+                     padding=(padding, padding))
+    net = sym.relu(net)
+    return net
+
+# Net
+def get_symbol(num_classes, version, **kwargs):
+    """Get symbol of SqueezeNet
+
+    Parameters
+    ----------
+    num_classes: int
+        The number of classification results
+
+    version : str, optional
+        "1.0" or "1.1" of SqueezeNet
+    """
+    assert version in ['1.0', '1.1'], ("Unsupported SqueezeNet version {version}:"
+                                       "1.0 or 1.1 expected".format(version=version))
+    net = sym.Variable("data")
+    if version == '1.0':
+        net = sym.conv2d(net, channels=96, kernel_size=(7, 7), strides=(2, 2), padding=(3, 3))
+        net = sym.relu(net)
+        net = sym.max_pool2d(net, pool_size=(3, 3), strides=(2, 2))
+        net = _make_fire(net, 16, 64, 64)
+        net = _make_fire(net, 16, 64, 64)
+        net = _make_fire(net, 32, 128, 128)
+        net = sym.max_pool2d(net, pool_size=(3, 3), strides=(2, 2))
+        net = _make_fire(net, 32, 128, 128)
+        net = _make_fire(net, 48, 192, 192)
+        net = _make_fire(net, 48, 192, 192)
+        net = _make_fire(net, 64, 256, 256)
+        net = sym.max_pool2d(net, pool_size=(3, 3), strides=(2, 2))
+        net = _make_fire(net, 64, 256, 256)
+    else:
+        net = sym.conv2d(net, channels=64, kernel_size=(3, 3), strides=(2, 2), padding=(1, 1))
+        net = sym.relu(net)
+        net = sym.max_pool2d(net, pool_size=(3, 3), strides=(2, 2))
+        net = _make_fire(net, 16, 64, 64)
+        net = _make_fire(net, 16, 64, 64)
+        net = sym.max_pool2d(net, pool_size=(3, 3), strides=(2, 2))
+        net = _make_fire(net, 32, 128, 128)
+        net = _make_fire(net, 32, 128, 128)
+        net = sym.max_pool2d(net, pool_size=(3, 3), strides=(2, 2))
+        net = _make_fire(net, 48, 192, 192)
+        net = _make_fire(net, 48, 192, 192)
+        net = _make_fire(net, 64, 256, 256)
+        net = _make_fire(net, 64, 256, 256)
+    net = sym.dropout(net, rate=0.5)
+    net = sym.conv2d(net, channels=num_classes, kernel_size=(1, 1))
+    net = sym.relu(net)
+    net = sym.global_avg_pool2d(net)
+    net = sym.flatten(net)
+    return sym.softmax(net)
+
+def get_workload(batch_size=1, num_classes=1000, version='1.0',
+                 image_shape=(3, 224, 224), dtype="float32", **kwargs):
+    """Get benchmark workload for resnet
+
+    Parameters
+    ----------
+    batch_size : int
+        The batch size used in the model
+
+    num_classes : int, optional
+        Number of classes
+
+    version : str, optional
+        "1.0" or "1.1" of SqueezeNet
+
+    image_shape : tuple, optional
+        The input image shape
+
+    dtype : str, optional
+        The data type
+
+    kwargs : dict
+        Extra arguments
+
+    Returns
+    -------
+    net : nnvm.Symbol
+        The computational graph
+
+    params : dict of str to NDArray
+        The parameters.
+    """
+    net = get_symbol(num_classes=num_classes, version=version, **kwargs)
+    return create_workload(net, batch_size, image_shape, dtype)
diff --git a/nnvm/tests/python/frontend/mxnet/model_zoo/__init__.py b/nnvm/tests/python/frontend/mxnet/model_zoo/__init__.py
index 6c3d07ffcd71..e3c9acdf23ef 100644
--- a/nnvm/tests/python/frontend/mxnet/model_zoo/__init__.py
+++ b/nnvm/tests/python/frontend/mxnet/model_zoo/__init__.py
@@ -1,9 +1,10 @@
 """MXNet and NNVM model zoo."""
 from __future__ import absolute_import
-from . import mlp, resnet, vgg, dqn, dcgan
+from . import mlp, resnet, vgg, dqn, dcgan, squeezenet
 import nnvm.testing
 
-__all__ = ['mx_mlp', 'nnvm_mlp', 'mx_resnet', 'nnvm_resnet', 'mx_vgg', 'nnvm_vgg']
+__all__ = ['mx_mlp', 'nnvm_mlp', 'mx_resnet', 'nnvm_resnet', 'mx_vgg', 'nnvm_vgg',
+           'mx_squeezenet', 'nnvm_squeezenet']
 
 _num_class = 1000
 
@@ -27,6 +28,13 @@
     nnvm_vgg[num_layer] = nnvm.testing.vgg.get_workload(
         1, _num_class, num_layers=num_layer)[0]
 
+# squeezenet
+mx_squeezenet = {}
+nnvm_squeezenet = {}
+for version in ['1.0', '1.1']:
+    mx_squeezenet[version] = squeezenet.get_symbol(version=version)
+    nnvm_squeezenet[version] = nnvm.testing.squeezenet.get_workload(1, version=version)[0]
+
 # dqn
 mx_dqn = dqn.get_symbol()
 nnvm_dqn = nnvm.testing.dqn.get_workload(1)[0]
diff --git a/nnvm/tests/python/frontend/mxnet/model_zoo/squeezenet.py b/nnvm/tests/python/frontend/mxnet/model_zoo/squeezenet.py
new file mode 100644
index 000000000000..deb896a21385
--- /dev/null
+++ b/nnvm/tests/python/frontend/mxnet/model_zoo/squeezenet.py
@@ -0,0 +1,76 @@
+"""
+Symbol of SqueezeNet
+
+Reference:
+Iandola, Forrest N., et al.
+"Squeezenet: Alexnet-level accuracy with 50x fewer parameters and< 0.5 mb model size." (2016).
+"""
+
+import mxnet as mx
+
+# Helpers
+def _make_fire(net, squeeze_channels, expand1x1_channels, expand3x3_channels):
+    net = _make_fire_conv(net, squeeze_channels, 1, 0)
+
+    left = _make_fire_conv(net, expand1x1_channels, 1, 0)
+    right = _make_fire_conv(net, expand3x3_channels, 3, 1)
+    # NOTE : Assume NCHW layout here
+    net = mx.sym.concat(left, right, dim=1)
+
+    return net
+
+def _make_fire_conv(net, channels, kernel_size, padding=0):
+    net = mx.sym.Convolution(net, num_filter=channels, kernel=(kernel_size, kernel_size),
+                             pad=(padding, padding))
+    net = mx.sym.Activation(net, act_type='relu')
+    return net
+
+# Net
+def get_symbol(num_classes=1000, version='1.0', **kwargs):
+    """Get symbol of SqueezeNet
+
+    Parameters
+    ----------
+    num_classes: int
+        The number of classification results
+
+    version : str, optional
+        "1.0" or "1.1" of SqueezeNet
+    """
+    assert version in ['1.0', '1.1'], ("Unsupported SqueezeNet version {version}:"
+                                       "1.0 or 1.1 expected".format(version=version))
+    net = mx.sym.Variable("data")
+    if version == '1.0':
+        net = mx.sym.Convolution(net, num_filter=96, kernel=(7, 7), stride=(2, 2), pad=(3, 3))
+        net = mx.sym.Activation(net, act_type='relu')
+        net = mx.sym.Pooling(data=net, kernel=(3, 3), pool_type='max', stride=(2, 2))
+        net = _make_fire(net, 16, 64, 64)
+        net = _make_fire(net, 16, 64, 64)
+        net = _make_fire(net, 32, 128, 128)
+        net = mx.sym.Pooling(data=net, kernel=(3, 3), pool_type='max', stride=(2, 2))
+        net = _make_fire(net, 32, 128, 128)
+        net = _make_fire(net, 48, 192, 192)
+        net = _make_fire(net, 48, 192, 192)
+        net = _make_fire(net, 64, 256, 256)
+        net = mx.sym.Pooling(data=net, kernel=(3, 3), pool_type='max', stride=(2, 2))
+        net = _make_fire(net, 64, 256, 256)
+    else:
+        net = mx.sym.Convolution(net, num_filter=64, kernel=(3, 3), stride=(2, 2), pad=(1, 1))
+        net = mx.sym.Activation(net, act_type='relu')
+        net = mx.sym.Pooling(data=net, kernel=(3, 3), pool_type='max', stride=(2, 2))
+        net = _make_fire(net, 16, 64, 64)
+        net = _make_fire(net, 16, 64, 64)
+        net = mx.sym.Pooling(data=net, kernel=(3, 3), pool_type='max',  stride=(2, 2))
+        net = _make_fire(net, 32, 128, 128)
+        net = _make_fire(net, 32, 128, 128)
+        net = mx.sym.Pooling(data=net, kernel=(3, 3), pool_type='max',  stride=(2, 2))
+        net = _make_fire(net, 48, 192, 192)
+        net = _make_fire(net, 48, 192, 192)
+        net = _make_fire(net, 64, 256, 256)
+        net = _make_fire(net, 64, 256, 256)
+    net = mx.sym.Dropout(net, p=0.5)
+    net = mx.sym.Convolution(net, num_filter=num_classes, kernel=(1, 1))
+    net = mx.sym.Activation(net, act_type='relu')
+    net = mx.sym.Pooling(data=net, global_pool=True, kernel=(13, 13), pool_type='avg')
+    net = mx.sym.flatten(net)
+    return mx.sym.softmax(net)
diff --git a/nnvm/tests/python/frontend/mxnet/test_graph.py b/nnvm/tests/python/frontend/mxnet/test_graph.py
index cd14f9c7ca98..18e124ad6ffc 100644
--- a/nnvm/tests/python/frontend/mxnet/test_graph.py
+++ b/nnvm/tests/python/frontend/mxnet/test_graph.py
@@ -32,6 +32,13 @@ def test_resnet():
         nnvm_sym = model_zoo.nnvm_resnet[n]
         compare_graph(from_mx_sym, nnvm_sym)
 
+def test_squeezenet():
+    for version in ['1.0', '1.1']:
+        mx_sym = model_zoo.mx_squeezenet[version]
+        from_mx_sym, _ = nnvm.frontend.from_mxnet(mx_sym)
+        nnvm_sym = model_zoo.nnvm_squeezenet[version]
+        compare_graph(from_mx_sym, nnvm_sym)
+
 def test_dqn():
     mx_sym = model_zoo.mx_dqn
     from_mx_sym, _ = nnvm.frontend.from_mxnet(mx_sym)
@@ -62,3 +69,4 @@ def compose(F, **kwargs):
     test_multi_outputs()
     test_dqn()
     test_dcgan()
+    test_squeezenet()
diff --git a/tutorials/autotvm/tune_cuda_conv2d.py b/tutorials/autotvm/tune_cuda_conv2d.py
index 0f6d8026f9fa..5c3facf6184a 100644
--- a/tutorials/autotvm/tune_cuda_conv2d.py
+++ b/tutorials/autotvm/tune_cuda_conv2d.py
@@ -21,7 +21,7 @@
 # ---------------------------------
 # There are plenty of useful schedule primitives in tvm. You can also find 
 # some tutorials that describe them in more details, such as 
-# (1). :doc:``Optimizing Conv2d on NVIDIA GPU <../optimize/opt_conv_cuda>`
+# (1). :ref:`opt-conv-gpu`
 # (2). `Optimizing DepthwiseConv on NVIDIA GPU <https://tvm.ai/2017/08/22/Optimize-Deep-Learning-GPU-Operators-with-TVM-A-Depthwise-Convolution-Example.html>`_
 # 
 # However, their implementations are manually tuned for some special input
diff --git a/tutorials/nnvm/imagenet_inference_gpu.py b/tutorials/nnvm/imagenet_inference_gpu.py
index 09c90c2c54a5..9179dedfb6fc 100644
--- a/tutorials/nnvm/imagenet_inference_gpu.py
+++ b/tutorials/nnvm/imagenet_inference_gpu.py
@@ -20,7 +20,7 @@
 # To get the maximum performance, we need to enable nvcc's compiler hook.
 # This usually gives better performance than nvrtc mode.
 
-@tvm.register_func
+@tvm.register_func("tvm_callback_cuda_compile", override=True)
 def tvm_callback_cuda_compile(code):
     ptx = nvcc.compile_cuda(code, target="ptx")
     return ptx
diff --git a/tutorials/optimize/opt_conv_cuda.py b/tutorials/optimize/opt_conv_cuda.py
index ecacc59e4673..52e0417f20ca 100644
--- a/tutorials/optimize/opt_conv_cuda.py
+++ b/tutorials/optimize/opt_conv_cuda.py
@@ -1,4 +1,7 @@
-"""How to optimize convolution on GPU
+"""
+.. _opt-conv-gpu:
+
+How to optimize convolution on GPU
 ==================================
 **Author**: `Haichen Shen <https://homes.cs.washington.edu/~haichen/>`_
 

From a516ea6f67cd1cd0f29b5308520355753e2e976e Mon Sep 17 00:00:00 2001
From: Luis Vega <vegaluisjose@users.noreply.github.com>
Date: Sun, 15 Jul 2018 15:24:10 -0700
Subject: [PATCH 874/949] add generic home path (#1435)

---
 docs/vta/install.md | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/docs/vta/install.md b/docs/vta/install.md
index a827990d6e05..8ce92e5f79d3 100644
--- a/docs/vta/install.md
+++ b/docs/vta/install.md
@@ -201,10 +201,10 @@ At this point you've launched the Vivado 2017.1 Installer GUI program.
 
 The last step is to update your `~/.bashrc` with the following lines. This will include all of the Xilinx binary paths so you can launch compilation scripts from the command line.
 ```bash
-# Xilinx Vivado 2017.1 environmentexport XILINX_VIVADO=/home/moreau/Xilinx/SDx/2017.1/Vivado
-export XILINX_VIVADO=/home/moreau/Xilinx/SDx/2017.1/Vivado
-export XILINX_HLS=/home/moreau/Xilinx/SDx/2017.1/Vivado_HLS
-export XILINX_SDK=/home/moreau/Xilinx/SDx/2017.1/SDK
+# Xilinx Vivado 2017.1 environmentexport XILINX_VIVADO=${HOME}/Xilinx/SDx/2017.1/Vivado
+export XILINX_VIVADO=${HOME}/Xilinx/SDx/2017.1/Vivado
+export XILINX_HLS=${HOME}/Xilinx/SDx/2017.1/Vivado_HLS
+export XILINX_SDK=${HOME}/Xilinx/SDx/2017.1/SDK
 export PATH=${XILINX_VIVADO}/bin:${PATH}
 export PATH=${XILINX_HLS}/bin:${PATH}
 export PATH=${XILINX_SDK}/bin:${PATH}

From 4bb76fe3ca0fead2744bca6677a47d0d98c25868 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 16 Jul 2018 20:02:54 -0700
Subject: [PATCH 875/949] [CI] Switch to use prebuilt docker instead of build
 from scratch (#1442)

---
 Jenkinsfile         | 36 ++++++++++++++++++------------------
 docker/bash.sh      | 26 +++++++++++++++++++++-----
 tests/lint/pylintrc |  2 +-
 3 files changed, 40 insertions(+), 24 deletions(-)

diff --git a/Jenkinsfile b/Jenkinsfile
index 27236852dd54..8d76ebedeaae 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -11,7 +11,7 @@ tvm_multilib = "build/libtvm.so, " +
              "build/libvta.so, build/libtvm_topi.so, build/libnnvm_compiler.so, " + tvm_runtime
 
 // command to start a docker container
-docker_run = 'docker/build.sh'
+docker_run = 'docker/bash.sh'
 // timeout in minutes
 max_time = 60
 
@@ -39,7 +39,7 @@ stage("Sanity Check") {
     node('linux') {
       ws('workspace/tvm/sanity') {
         init_git()
-        sh "${docker_run} ci_lint  ./tests/scripts/task_lint.sh"
+        sh "${docker_run} tvmai/ci-lint  ./tests/scripts/task_lint.sh"
       }
     }
   }
@@ -100,7 +100,7 @@ stage('Build') {
            echo set\\(CMAKE_CXX_COMPILER g++\\) >> config.cmake
            echo set\\(CMAKE_CXX_FLAGS -Werror\\) >> config.cmake
            """
-        make('ci_gpu', 'build', '-j2')
+        make('tvmai/ci-gpu', 'build', '-j2')
         pack_lib('gpu', tvm_multilib)
         // compiler test
         sh """
@@ -113,7 +113,7 @@ stage('Build') {
            echo set\\(CMAKE_CXX_COMPILER clang-6.0\\) >> config.cmake
            echo set\\(CMAKE_CXX_FLAGS -Werror\\) >> config.cmake
            """
-        make('ci_gpu', 'build2', '-j2')
+        make('tvmai/ci-gpu', 'build2', '-j2')
       }
     }
   },
@@ -130,11 +130,11 @@ stage('Build') {
            echo set\\(CMAKE_CXX_COMPILER g++\\) >> config.cmake
            echo set\\(CMAKE_CXX_FLAGS -Werror\\) >> config.cmake
            """
-        make('ci_cpu', 'build', '-j2')
+        make('tvmai/ci-cpu', 'build', '-j2')
         pack_lib('cpu', tvm_lib)
         timeout(time: max_time, unit: 'MINUTES') {
-          sh "${docker_run} ci_cpu ./tests/scripts/task_cpp_unittest.sh"
-          sh "${docker_run} ci_cpu ./tests/scripts/task_python_vta.sh"
+          sh "${docker_run} tvmai/ci-cpu ./tests/scripts/task_cpp_unittest.sh"
+          sh "${docker_run} tvmai/ci-cpu ./tests/scripts/task_python_vta.sh"
         }
       }
     }
@@ -153,7 +153,7 @@ stage('Build') {
            echo set\\(CMAKE_CXX_COMPILER g++\\) >> config.cmake
            echo set\\(CMAKE_CXX_FLAGS -Werror\\) >> config.cmake
            """
-        make('ci_i386', 'build', '-j2')
+        make('tvmai/ci-i386', 'build', '-j2')
         pack_lib('i386', tvm_multilib)
       }
     }
@@ -167,7 +167,7 @@ stage('Unit Test') {
         init_git()
         unpack_lib('gpu', tvm_multilib)
         timeout(time: max_time, unit: 'MINUTES') {
-          sh "${docker_run} ci_gpu ./tests/scripts/task_python_unittest.sh"
+          sh "${docker_run} tvmai/ci-gpu ./tests/scripts/task_python_unittest.sh"
         }
       }
     }
@@ -178,9 +178,9 @@ stage('Unit Test') {
         init_git()
         unpack_lib('i386', tvm_multilib)
         timeout(time: max_time, unit: 'MINUTES') {
-          sh "${docker_run} ci_i386 ./tests/scripts/task_python_unittest.sh"
-          sh "${docker_run} ci_i386 ./tests/scripts/task_python_integration.sh"
-          sh "${docker_run} ci_i386 ./tests/scripts/task_python_vta.sh"
+          sh "${docker_run} tvmai/ci-i386 ./tests/scripts/task_python_unittest.sh"
+          sh "${docker_run} tvmai/ci-i386 ./tests/scripts/task_python_integration.sh"
+          sh "${docker_run} tvmai/ci-i386 ./tests/scripts/task_python_vta.sh"
         }
       }
     }
@@ -191,7 +191,7 @@ stage('Unit Test') {
         init_git()
         unpack_lib('gpu', tvm_multilib)
         timeout(time: max_time, unit: 'MINUTES') {
-          sh "${docker_run} ci_gpu ./tests/scripts/task_java_unittest.sh"
+          sh "${docker_run} tvmai/ci-gpu ./tests/scripts/task_java_unittest.sh"
         }
       }
     }
@@ -205,10 +205,10 @@ stage('Integration Test') {
         init_git()
         unpack_lib('gpu', tvm_multilib)
         timeout(time: max_time, unit: 'MINUTES') {
-          sh "${docker_run} ci_gpu ./tests/scripts/task_python_integration.sh"
-          sh "${docker_run} ci_gpu ./tests/scripts/task_python_topi.sh"
-          sh "${docker_run} ci_gpu ./tests/scripts/task_cpp_topi.sh"
-          sh "${docker_run} ci_gpu ./tests/scripts/task_python_nnvm.sh"
+          sh "${docker_run} tvmai/ci-gpu ./tests/scripts/task_python_integration.sh"
+          sh "${docker_run} tvmai/ci-gpu ./tests/scripts/task_python_topi.sh"
+          sh "${docker_run} tvmai/ci-gpu ./tests/scripts/task_cpp_topi.sh"
+          sh "${docker_run} tvmai/ci-gpu ./tests/scripts/task_python_nnvm.sh"
         }
       }
     }
@@ -219,7 +219,7 @@ stage('Integration Test') {
         init_git()
         unpack_lib('gpu', tvm_multilib)
         timeout(time: max_time, unit: 'MINUTES') {
-          sh "${docker_run} ci_gpu ./tests/scripts/task_python_docs.sh"
+          sh "${docker_run} tvmai/ci-gpu ./tests/scripts/task_python_docs.sh"
         }
         pack_lib('mydocs', 'docs.tgz')
       }
diff --git a/docker/bash.sh b/docker/bash.sh
index 79f989193dcf..ba935d7ed089 100755
--- a/docker/bash.sh
+++ b/docker/bash.sh
@@ -3,15 +3,28 @@
 # Start a bash, mount /workspace to be current directory.
 #
 # Usage: docker/bash.sh <CONTAINER_NAME>
+#     Starts an interactive session
+#
+# Usage2: docker/bash.sh <CONTAINER_NAME> [COMMAND]
+#     Execute command in the docker image, non-interactive
 #
 if [ "$#" -lt 1 ]; then
-    echo "Usage: docker/bash.sh <CONTAINER_NAME>"
+    echo "Usage: docker/bash.sh <CONTAINER_NAME> [COMMAND]"
     exit -1
 fi
 
+DOCKER_IMAGE_NAME=("$1")
+
+if [ "$#" -eq 1 ]; then
+    COMMAND="bash"
+    CI_DOCKER_EXTRA_PARAMS=("-it --net=host")
+else
+    shift 1
+    COMMAND=("$@")
+fi
+
 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
 WORKSPACE="$(pwd)"
-DOCKER_IMAGE_NAME=$1
 
 # Use nvidia-docker if the container is GPU.
 if [[ "${DOCKER_IMAGE_NAME}" == *"gpu"* ]]; then
@@ -22,14 +35,16 @@ fi
 
 # Print arguments.
 echo "WORKSPACE: ${WORKSPACE}"
-echo "DOCKER CONTAINER NAME: ${DOCKER_IMG_NAME}"
+echo "DOCKER CONTAINER NAME: ${DOCKER_IMAGE_NAME}"
 echo ""
 
+echo "Running '${COMMAND[@]}' inside ${DOCKER_IMAGE_NAME}..."
+
 # By default we cleanup - remove the container once it finish running (--rm)
 # and share the PID namespace (--pid=host) so the process inside does not have
 # pid 1 and SIGKILL is propagated to the process inside (jenkins can kill it).
 echo ${DOCKER_BINARY}
-${DOCKER_BINARY} run --rm -it --pid=host --net=host\
+${DOCKER_BINARY} run --rm --pid=host\
     -v ${WORKSPACE}:/workspace \
     -v ${SCRIPT_DIR}:/docker \
     -w /workspace \
@@ -38,6 +53,7 @@ ${DOCKER_BINARY} run --rm -it --pid=host --net=host\
     -e "CI_BUILD_UID=$(id -u)" \
     -e "CI_BUILD_GROUP=$(id -g -n)" \
     -e "CI_BUILD_GID=$(id -g)" \
+    ${CI_DOCKER_EXTRA_PARAMS[@]} \
     ${DOCKER_IMAGE_NAME}\
     bash /docker/with_the_same_user \
-    bash
+    ${COMMAND[@]}
diff --git a/tests/lint/pylintrc b/tests/lint/pylintrc
index ef5a780f3d42..f5c4452cfa16 100644
--- a/tests/lint/pylintrc
+++ b/tests/lint/pylintrc
@@ -65,7 +65,7 @@ enable=indexing-exception,old-raise-syntax
 # --enable=similarities". If you want to run only the classes checker, but have
 # no Warning level messages displayed, use"--disable=all --enable=classes
 # --disable=W"
-disable=design,similarities,no-self-use,attribute-defined-outside-init,locally-disabled,star-args,pointless-except,bad-option-value,global-statement,fixme,suppressed-message,useless-suppression,locally-enabled,no-member,no-name-in-module,import-error,unsubscriptable-object,unbalanced-tuple-unpacking,undefined-variable,protected-access
+disable=design,similarities,no-self-use,attribute-defined-outside-init,locally-disabled,star-args,pointless-except,bad-option-value,global-statement,fixme,suppressed-message,useless-suppression,locally-enabled,no-member,no-name-in-module,import-error,unsubscriptable-object,unbalanced-tuple-unpacking,undefined-variable,protected-access,useless-object-inheritance
 
 [REPORTS]
 

From 250531b0d47728338984ee9c0a84c89895a2811e Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 16 Jul 2018 21:08:20 -0700
Subject: [PATCH 876/949] Update README.md

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index d1e0a4472a1f..561ca91d5abe 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
 ==============================================
 
 [![GitHub license](https://dmlc.github.io/img/apache2.svg)](./LICENSE)
-[![Build Status](http://mode-gpu.cs.washington.edu:8080/buildStatus/icon?job=dmlc/tvm/master)](http://mode-gpu.cs.washington.edu:8080/job/dmlc/job/tvm/job/master/)
+[![Build Status](http://mode-gpu.cs.washington.edu:8080/buildStatus/icon?job=tvm/master)](http://mode-gpu.cs.washington.edu:8080/job/tvm/job/master/)
 
 [Documentation](https://docs.tvm.ai) |
 [Contributors](CONTRIBUTORS.md) |

From d91e15c35d6faaff12bbb89c78cf8bb525a0cc3f Mon Sep 17 00:00:00 2001
From: Tatsuya Nishiyama <nishiyama.tatsuya0@gmail.com>
Date: Wed, 18 Jul 2018 01:20:14 +0900
Subject: [PATCH 877/949] Fix cmake for building with cuda in msvc (#1437)

---
 cmake/util/FindCUDA.cmake                        | 14 +++++++-------
 src/runtime/cuda/cuda_device_api.cc              |  3 ---
 src/runtime/opencl/sdaccel/sdaccel_device_api.cc |  3 ---
 3 files changed, 7 insertions(+), 13 deletions(-)

diff --git a/cmake/util/FindCUDA.cmake b/cmake/util/FindCUDA.cmake
index 84e1322c5397..3ce0cc40a5e5 100644
--- a/cmake/util/FindCUDA.cmake
+++ b/cmake/util/FindCUDA.cmake
@@ -29,8 +29,8 @@ macro(find_cuda use_cuda)
     set(CUDA_FOUND TRUE)
     if(MSVC)
       find_library(CUDA_CUDART_LIBRARY cudart
-        ${CUDA_TOOLKIT_ROOT_DIR}/lib64
-        ${CUDA_TOOLKIT_ROOT_DIR}/lib)
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib/x64
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib/Win32)
     else(MSVC)
       find_library(CUDA_CUDART_LIBRARY cudart
         ${CUDA_TOOLKIT_ROOT_DIR}/lib64
@@ -41,18 +41,18 @@ macro(find_cuda use_cuda)
   # additional libraries
   if(CUDA_FOUND)
     if(MSVC)
-      find_library(CUDA_NVRTC_LIBRARY cuda
+      find_library(CUDA_CUDA_LIBRARY cuda
         ${CUDA_TOOLKIT_ROOT_DIR}/lib/x64
-        ${CUDA_TOOLKIT_ROOT_DIR}/lib/win32)
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib/Win32)
       find_library(CUDA_NVRTC_LIBRARY nvrtc
         ${CUDA_TOOLKIT_ROOT_DIR}/lib/x64
-        ${CUDA_TOOLKIT_ROOT_DIR}/lib/win32)
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib/Win32)
       find_library(CUDA_CUDNN_LIBRARY cudnn
         ${CUDA_TOOLKIT_ROOT_DIR}/lib/x64
-        ${CUDA_TOOLKIT_ROOT_DIR}/lib/win32)
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib/Win32)
       find_library(CUDA_CUBLAS_LIBRARY cublas
         ${CUDA_TOOLKIT_ROOT_DIR}/lib/x64
-        ${CUDA_TOOLKIT_ROOT_DIR}/lib/win32)
+        ${CUDA_TOOLKIT_ROOT_DIR}/lib/Win32)
     else(MSVC)
       find_library(_CUDA_CUDA_LIBRARY cuda
         PATHS ${CUDA_TOOLKIT_ROOT_DIR}
diff --git a/src/runtime/cuda/cuda_device_api.cc b/src/runtime/cuda/cuda_device_api.cc
index 4573f97ddd8a..98accdf1b0aa 100644
--- a/src/runtime/cuda/cuda_device_api.cc
+++ b/src/runtime/cuda/cuda_device_api.cc
@@ -8,9 +8,6 @@
 #include <dmlc/thread_local.h>
 #include <tvm/runtime/registry.h>
 #include <cuda_runtime.h>
-#include <tvm/container.h>
-#include <tvm/ir.h>
-#include <tvm/packed_func_ext.h>
 #include "./cuda_common.h"
 
 namespace tvm {
diff --git a/src/runtime/opencl/sdaccel/sdaccel_device_api.cc b/src/runtime/opencl/sdaccel/sdaccel_device_api.cc
index 1151cc44b2ef..3e040f01b9a9 100644
--- a/src/runtime/opencl/sdaccel/sdaccel_device_api.cc
+++ b/src/runtime/opencl/sdaccel/sdaccel_device_api.cc
@@ -4,9 +4,6 @@
  */
 #include <tvm/runtime/registry.h>
 #include <dmlc/thread_local.h>
-#include <tvm/container.h>
-#include <tvm/ir.h>
-#include <tvm/packed_func_ext.h>
 #include "./sdaccel_common.h"
 
 namespace tvm {

From 22ae64582bc069d87d66ebef1e05bc73105774be Mon Sep 17 00:00:00 2001
From: MORITA Kazutaka <morita.kazutaka@lab.ntt.co.jp>
Date: Wed, 18 Jul 2018 01:36:30 +0900
Subject: [PATCH 878/949] [RUNTIME][OPENCL] show correct device type name
 (#1441)

---
 src/runtime/opencl/opencl_common.h               | 9 ++++++---
 src/runtime/opencl/opencl_device_api.cc          | 6 ++++--
 src/runtime/opencl/opencl_module.cc              | 4 ----
 src/runtime/opencl/sdaccel/sdaccel_device_api.cc | 2 +-
 src/runtime/opencl/sdaccel/sdaccel_module.cc     | 5 -----
 5 files changed, 11 insertions(+), 15 deletions(-)

diff --git a/src/runtime/opencl/opencl_common.h b/src/runtime/opencl/opencl_common.h
index 46b212bee464..c37dbaa94d7a 100644
--- a/src/runtime/opencl/opencl_common.h
+++ b/src/runtime/opencl/opencl_common.h
@@ -108,6 +108,8 @@ class OpenCLThreadEntry;
  */
 class OpenCLWorkspace : public DeviceAPI {
  public:
+  // type key
+  std::string type_key;
   // global platform id
   cl_platform_id platform_id;
   // global platform name
@@ -138,9 +140,10 @@ class OpenCLWorkspace : public DeviceAPI {
     }
   }
   // Initialzie the device.
-  void Init(const std::string& device_type, const std::string& platform_name = "");
+  void Init(const std::string& type_key, const std::string& device_type,
+            const std::string& platform_name = "");
   virtual void Init() {
-    Init("gpu");
+    Init("opencl", "gpu");
   }
   // Check whether the context is OpenCL or not.
   virtual bool IsOpenCLDevice(TVMContext ctx) {
@@ -240,7 +243,7 @@ class OpenCLModuleNode : public ModuleNode {
    */
   virtual const std::shared_ptr<cl::OpenCLWorkspace>& GetGlobalWorkspace();
 
-  virtual const char* type_key() const;
+  const char* type_key() const final { return workspace_->type_key.c_str(); }
 
   PackedFunc GetFunction(
       const std::string& name,
diff --git a/src/runtime/opencl/opencl_device_api.cc b/src/runtime/opencl/opencl_device_api.cc
index 8c884bdc14b6..ac9373f1375b 100644
--- a/src/runtime/opencl/opencl_device_api.cc
+++ b/src/runtime/opencl/opencl_device_api.cc
@@ -227,7 +227,8 @@ bool MatchPlatformInfo(
   return param_value.find(value) != std::string::npos;
 }
 
-void OpenCLWorkspace::Init(const std::string& device_type, const std::string& platform_name) {
+void OpenCLWorkspace::Init(const std::string& type_key, const std::string& device_type,
+                           const std::string& platform_name) {
   if (initialized_) return;
   std::lock_guard<std::mutex> lock(this->mu);
   if (initialized_) return;
@@ -246,6 +247,7 @@ void OpenCLWorkspace::Init(const std::string& device_type, const std::string& pl
     }
     std::vector<cl_device_id> devices_matched = cl::GetDeviceIDs(platform_id, device_type);
     if (devices_matched.size() > 0) {
+      this->type_key = type_key;
       this->platform_id = platform_id;
       this->platform_name = cl::GetPlatformInfo(platform_id, CL_PLATFORM_NAME);
       this->device_type = device_type;
@@ -271,7 +273,7 @@ void OpenCLWorkspace::Init(const std::string& device_type, const std::string& pl
     this->queues.push_back(
         clCreateCommandQueue(this->context, did, 0, &err_code));
     OPENCL_CHECK_ERROR(err_code);
-    LOG(INFO) << "opencl(" << i
+    LOG(INFO) << type_key << "(" << i
               << ")=\'" << cl::GetDeviceInfo(did, CL_DEVICE_NAME)
               << "\' cl_device_id=" << did;
   }
diff --git a/src/runtime/opencl/opencl_module.cc b/src/runtime/opencl/opencl_module.cc
index 68907d11cd75..aa51f17d4021 100644
--- a/src/runtime/opencl/opencl_module.cc
+++ b/src/runtime/opencl/opencl_module.cc
@@ -100,10 +100,6 @@ const std::shared_ptr<cl::OpenCLWorkspace>& OpenCLModuleNode::GetGlobalWorkspace
   return cl::OpenCLWorkspace::Global();
 }
 
-const char* OpenCLModuleNode::type_key() const {
-  return "opencl";
-}
-
 PackedFunc OpenCLModuleNode::GetFunction(
     const std::string& name,
     const std::shared_ptr<ModuleNode>& sptr_to_self) {
diff --git a/src/runtime/opencl/sdaccel/sdaccel_device_api.cc b/src/runtime/opencl/sdaccel/sdaccel_device_api.cc
index 3e040f01b9a9..4b057b7e009a 100644
--- a/src/runtime/opencl/sdaccel/sdaccel_device_api.cc
+++ b/src/runtime/opencl/sdaccel/sdaccel_device_api.cc
@@ -20,7 +20,7 @@ const std::shared_ptr<OpenCLWorkspace>& SDAccelWorkspace::Global() {
 }
 
 void SDAccelWorkspace::Init() {
-  OpenCLWorkspace::Init("accelerator", "Xilinx");
+  OpenCLWorkspace::Init("sdaccel", "accelerator", "Xilinx");
 }
 
 bool SDAccelWorkspace::IsOpenCLDevice(TVMContext ctx) {
diff --git a/src/runtime/opencl/sdaccel/sdaccel_module.cc b/src/runtime/opencl/sdaccel/sdaccel_module.cc
index 6eaa3ae88a81..c99e78c8e347 100644
--- a/src/runtime/opencl/sdaccel/sdaccel_module.cc
+++ b/src/runtime/opencl/sdaccel/sdaccel_module.cc
@@ -21,17 +21,12 @@ class SDAccelModuleNode : public OpenCLModuleNode {
                              std::string source)
       : OpenCLModuleNode(data, fmt, fmap, source) {}
   const std::shared_ptr<cl::OpenCLWorkspace>& GetGlobalWorkspace() final;
-  const char* type_key() const final;
 };
 
 const std::shared_ptr<cl::OpenCLWorkspace>& SDAccelModuleNode::GetGlobalWorkspace() {
   return cl::SDAccelWorkspace::Global();
 }
 
-const char* SDAccelModuleNode::type_key() const {
-  return "sdaccel";
-}
-
 Module SDAccelModuleCreate(
     std::string data,
     std::string fmt,

From f46c0101ce10c49ba60ec8ab51d593e892d0c9b2 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 17 Jul 2018 12:01:56 -0700
Subject: [PATCH 879/949] [BUILD] restrict runtime include headers (#1444)

---
 CMakeLists.txt             | 18 ++++++++++++++----
 Makefile                   |  2 +-
 apps/howto_deploy/Makefile |  2 +-
 3 files changed, 16 insertions(+), 6 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4ec5ea779f96..23d146a65f2c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -48,11 +48,8 @@ tvm_option(USE_RANDOM "Build with random support" OFF)
 
 # include directories
 include_directories("include")
-include_directories("nnvm/include")
-include_directories("dmlc-core/include")
-include_directories("HalideIR/src")
 include_directories("dlpack/include")
-include_directories("topi/include")
+include_directories("dmlc-core/include")
 
 # initial variables
 set(TVM_LINKER_LIBS "")
@@ -169,6 +166,19 @@ target_link_libraries(tvm_topi tvm ${TVM_LINKER_LIBS} ${TVM_RUNTIME_LINKER_LIBS}
 target_link_libraries(tvm_runtime ${TVM_RUNTIME_LINKER_LIBS})
 target_link_libraries(nnvm_compiler tvm)
 
+# Related headers
+target_include_directories(
+  tvm
+  PUBLIC "HalideIR/src"
+  PUBLIC "topi/include")
+target_include_directories(
+  tvm_topi
+  PUBLIC "topi/include")
+target_include_directories(
+  nnvm_compiler
+  PUBLIC "nnvm/include"
+  PUBLIC "topi/include")
+
 # Tests
 set(TEST_EXECS "")
 file(GLOB TEST_SRCS tests/cpp/*.cc)
diff --git a/Makefile b/Makefile
index a3ff3d7074dd..d077b6cc4173 100644
--- a/Makefile
+++ b/Makefile
@@ -11,7 +11,7 @@ ifndef DLPACK_PATH
   DLPACK_PATH = $(ROOTDIR)/dlpack
 endif
 
-INCLUDE_FLAGS = -Iinclude -I$(DLPACK_PATH)/include -I$(DMLC_CORE_PATH)/include -IHalideIR/src -Itopi/include
+INCLUDE_FLAGS = -Iinclude -I$(DLPACK_PATH)/include -I$(DMLC_CORE_PATH)/include
 PKG_CFLAGS = -std=c++11 -Wall -O2 $(INCLUDE_FLAGS) -fPIC
 PKG_LDFLAGS =
 
diff --git a/apps/howto_deploy/Makefile b/apps/howto_deploy/Makefile
index 8e59084c60ae..ad4e56680d21 100644
--- a/apps/howto_deploy/Makefile
+++ b/apps/howto_deploy/Makefile
@@ -8,7 +8,7 @@ PKG_CFLAGS = -std=c++11 -O2 -fPIC\
 	-I${DMLC_CORE}/include\
 	-I${TVM_ROOT}/dlpack/include\
 
-PKG_LDFLAGS = -L${TVM_ROOT}/lib -ldl -lpthread
+PKG_LDFLAGS = -L${TVM_ROOT}/build -ldl -lpthread
 
 .PHONY: clean all
 

From b4c637d58abd6196f9cd4c40e4cc377c7b8fe171 Mon Sep 17 00:00:00 2001
From: abergeron <bergearn@iro.umontreal.ca>
Date: Tue, 17 Jul 2018 16:45:08 -0400
Subject: [PATCH 880/949] Fix conda package builds (#1445)

---
 CMakeLists.txt                              | 12 ++----
 conda/conda_build_config.yaml               |  4 ++
 conda/nnvm/build.sh                         |  6 +++
 conda/nnvm/meta.yaml                        | 39 +++++++++++++++++
 conda/topi/build.sh                         |  6 +++
 {topi/python/conda => conda/topi}/meta.yaml | 22 ++++++----
 conda/tvm-libs/build.sh                     | 15 +++++++
 conda/tvm-libs/meta.yaml                    | 25 +++++++++++
 conda/tvm/build.sh                          |  6 +++
 {python/conda => conda/tvm}/meta.yaml       | 25 ++++++-----
 nnvm/python/conda/build.sh                  | 28 ------------
 nnvm/python/conda/meta.yaml                 | 32 --------------
 nnvm/python/nnvm/libinfo.py                 | 19 ++++----
 nnvm/python/setup.py                        | 19 +++++---
 python/conda/build.sh                       | 46 --------------------
 python/setup.py                             | 48 +++++++++++++--------
 python/tvm/_ffi/libinfo.py                  |  4 +-
 topi/python/setup.py                        | 34 +++++++++------
 18 files changed, 208 insertions(+), 182 deletions(-)
 create mode 100644 conda/conda_build_config.yaml
 create mode 100644 conda/nnvm/build.sh
 create mode 100644 conda/nnvm/meta.yaml
 create mode 100644 conda/topi/build.sh
 rename {topi/python/conda => conda/topi}/meta.yaml (52%)
 create mode 100644 conda/tvm-libs/build.sh
 create mode 100644 conda/tvm-libs/meta.yaml
 create mode 100644 conda/tvm/build.sh
 rename {python/conda => conda/tvm}/meta.yaml (55%)
 delete mode 100644 nnvm/python/conda/build.sh
 delete mode 100644 nnvm/python/conda/meta.yaml
 delete mode 100644 python/conda/build.sh

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 23d146a65f2c..81630841f525 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -201,17 +201,13 @@ endif()
 # Custom targets
 add_custom_target(runtime DEPENDS tvm_runtime)
 
-# Installation rulse
+# Installation rules
+install(TARGETS tvm DESTINATION lib${LIB_SUFFIX})
+install(TARGETS tvm_topi DESTINATION lib${LIB_SUFFIX})
 install(TARGETS tvm_runtime DESTINATION lib${LIB_SUFFIX})
-if(WIN32)
-  install(TARGETS nnvm_compiler RUNTIME DESTINATION bin)
-  install(TARGETS nnvm_compiler ARCHIVE DESTINATION lib)
-else()
-  install(TARGETS nnvm_compiler LIBRARY DESTINATION lib)
-endif()
+install(TARGETS nnvm_compiler DESTINATION lib${LIB_SUFFIX})
 
 if (INSTALL_DEV)
-  install(TARGETS tvm DESTINATION lib${LIB_SUFFIX})
   install(
     DIRECTORY "include/." DESTINATION "include"
     FILES_MATCHING
diff --git a/conda/conda_build_config.yaml b/conda/conda_build_config.yaml
new file mode 100644
index 000000000000..7f18f5eea432
--- /dev/null
+++ b/conda/conda_build_config.yaml
@@ -0,0 +1,4 @@
+python:
+  - 3.5
+  - 3.6
+  - 3.7
\ No newline at end of file
diff --git a/conda/nnvm/build.sh b/conda/nnvm/build.sh
new file mode 100644
index 000000000000..9f7889e610e7
--- /dev/null
+++ b/conda/nnvm/build.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+set -e
+
+cd nnvm/python
+$PYTHON setup.py install --single-version-externally-managed --record=/tmp/record.txt
diff --git a/conda/nnvm/meta.yaml b/conda/nnvm/meta.yaml
new file mode 100644
index 000000000000..a8b47d0de118
--- /dev/null
+++ b/conda/nnvm/meta.yaml
@@ -0,0 +1,39 @@
+{% set version = "0.4.dev" %}
+
+package:
+  name: nnvm
+  version: {{ version }}
+
+source:
+  path: ../..
+
+build:
+  number: 0
+  skip: True  # [win]
+
+requirements:
+  build:
+    - {{ compiler('cxx') }}
+  host:
+    - python {{ python }}
+    - cython
+    - numpy
+    - setuptools
+    - decorator
+    - tvm-libs =={{ version }}
+  run:
+    - tvm =={{ version }}
+    - topi =={{ version }}
+    - tvm-libs =={{ version }}
+    - python
+    - {{ pin_compatible('numpy') }}
+    - decorator
+
+test:
+  imports:
+    - nnvm
+
+about:
+  home: https://github.com/dmlc/nnvm
+  license: Apache2
+  summary: Bring deep learning to bare metal
diff --git a/conda/topi/build.sh b/conda/topi/build.sh
new file mode 100644
index 000000000000..a1f5e491c8eb
--- /dev/null
+++ b/conda/topi/build.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+set -e
+
+cd topi/python
+$PYTHON setup.py install --single-version-externally-managed --record=/tmp/record.txt
diff --git a/topi/python/conda/meta.yaml b/conda/topi/meta.yaml
similarity index 52%
rename from topi/python/conda/meta.yaml
rename to conda/topi/meta.yaml
index 37d3a35da433..af2fb4fd4228 100644
--- a/topi/python/conda/meta.yaml
+++ b/conda/topi/meta.yaml
@@ -1,28 +1,32 @@
-{% set version = "0.1.dev" %}
+{% set version = "0.4.dev" %}
 
 package:
   name: topi
   version: {{ version }}
 
 source:
-  path: ..
+  path: ../..
 
 build:
   number: 0
-  script: $PYTHON setup.py install
 
 requirements:
-  build:
-    - cmake
-    - python >=3
+  host:
+    - python {{ python }}
     - numpy
     - setuptools
-    - nose
     - decorator
+    - tvm-libs =={{ version }}
   run:
-    - python >=3
-    - numpy
+    - python
+    - {{ pin_compatible('numpy') }}
     - decorator
+    - tvm-libs =={{ version }}
+    - tvm =={{ version }}
+
+test:
+  imports:
+    - topi
 
 about:
   home: https://github.com/dmlc/tvm
diff --git a/conda/tvm-libs/build.sh b/conda/tvm-libs/build.sh
new file mode 100644
index 000000000000..d427d922a21e
--- /dev/null
+++ b/conda/tvm-libs/build.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+set -e
+
+if [ -z "$PREFIX" ]; then
+  PREFIX="$CONDA_PREFIX"
+fi
+
+rm -rf build || true
+mkdir -p build
+cd build
+cmake -DUSE_LLVM=ON -DINSTALL_DEV=ON -DCMAKE_INSTALL_PREFIX="$PREFIX" ..
+make -j2 VERBOSE=1
+make install
+cd ..
diff --git a/conda/tvm-libs/meta.yaml b/conda/tvm-libs/meta.yaml
new file mode 100644
index 000000000000..7875931fc124
--- /dev/null
+++ b/conda/tvm-libs/meta.yaml
@@ -0,0 +1,25 @@
+{% set version = "0.4.dev" %}
+
+package:
+  name: tvm-libs
+  version: {{ version }}
+
+source:
+  path: ../..
+
+build:
+  number: 0
+
+requirements:
+  host:
+    # The OS X build will require some manual setup or it will break
+    # See https://conda.io/docs/user-guide/tasks/build-packages/compiler-tools.html#macos-sdk
+    - {{ compiler('cxx') }}
+  build:
+    - llvmdev ==6.0.0
+    - cmake
+
+about:
+  home: https://github.com/dmlc/tvm
+  license: Apache2
+  summary: a low level domain specific language for compiling tensor computation pipelines
\ No newline at end of file
diff --git a/conda/tvm/build.sh b/conda/tvm/build.sh
new file mode 100644
index 000000000000..9c958a32e629
--- /dev/null
+++ b/conda/tvm/build.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+set -e
+
+cd python
+$PYTHON setup.py install --single-version-externally-managed --record=/tmp/record.txt
diff --git a/python/conda/meta.yaml b/conda/tvm/meta.yaml
similarity index 55%
rename from python/conda/meta.yaml
rename to conda/tvm/meta.yaml
index f4e64dfd6bef..478e095322eb 100644
--- a/python/conda/meta.yaml
+++ b/conda/tvm/meta.yaml
@@ -1,4 +1,4 @@
-{% set version = "0.1.dev" %}
+{% set version = "0.4.dev" %}
 
 package:
   name: tvm
@@ -8,24 +8,27 @@ source:
   path: ../..
 
 build:
-  number: 1
-  skip: True  # [win]
-  script_env:
-    - CONDA_CUDA_HOME
+  number: 0
 
 requirements:
   build:
-    - llvmdev ==4.0.0
-    - cmake
-    - python >=3
+    - {{ compiler('cxx') }}
+  host:
+    - python {{ python }}
+    - cython
     - numpy
     - setuptools
-    - nose
     - decorator
+    - tvm-libs =={{ version }}
   run:
-    - python >=3
-    - numpy
+    - python
+    - {{ pin_compatible('numpy') }}
     - decorator
+    - tvm-libs =={{ version }}
+
+test:
+  imports:
+    - tvm
 
 about:
   home: https://github.com/dmlc/tvm
diff --git a/nnvm/python/conda/build.sh b/nnvm/python/conda/build.sh
deleted file mode 100644
index 484d0626ae03..000000000000
--- a/nnvm/python/conda/build.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/bash
-
-set -e
-
-if [ -z "$PREFIX" ]; then
-  PREFIX="$CONDA_PREFIX"
-fi
-
-if [ "$(uname)" = 'Darwin' ]
-then
-    # Without this, Apple's default shipped clang will refuse to see any
-    # headers like mutex.
-    export MACOSX_DEPLOYMENT_TARGET=10.9
-fi
-
-rm -rf build || true
-mkdir -p build
-cd build
-# Enable static-libstdc++ to make it easier to link this library with
-# other C++ compilers
-CXXFLAGS=-static-libstdc++ cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} ..
-make -j4 VERBOSE=1
-make install/fast
-cd ..
-
-cd python
-$PYTHON setup.py install
-cd ..
diff --git a/nnvm/python/conda/meta.yaml b/nnvm/python/conda/meta.yaml
deleted file mode 100644
index 56e052000b40..000000000000
--- a/nnvm/python/conda/meta.yaml
+++ /dev/null
@@ -1,32 +0,0 @@
-{% set version = "0.1.dev" %}
-
-package:
-  name: nnvm
-  version: {{ version }}
-
-source:
-  path: ../..
-
-build:
-  number: 1
-  skip: True  # [win]
-
-requirements:
-  build:
-    - cmake
-    - python >=3
-    - numpy
-    - setuptools
-    - nose
-    - decorator
-  run:
-    - tvm
-    - topi
-    - python >=3
-    - numpy
-    - decorator
-
-about:
-  home: https://github.com/dmlc/nnvm
-  license: Apache2
-  summary: Bring deep learning to bare metal
diff --git a/nnvm/python/nnvm/libinfo.py b/nnvm/python/nnvm/libinfo.py
index 694bd0f92e7c..652433fc3d8c 100644
--- a/nnvm/python/nnvm/libinfo.py
+++ b/nnvm/python/nnvm/libinfo.py
@@ -28,10 +28,12 @@ def find_lib_path():
     else:
         lib_name = "nnvm_compiler" if sys.platform.startswith('win32') else "libnnvm_compiler"
 
-    api_path = os.path.join(base_path, '../../lib/')
-    cmake_build_path = os.path.join(base_path, '../../../build/Release/')
-    cmake_build_path = os.path.join(base_path, '../../../build/')
-    dll_path = [base_path, api_path, cmake_build_path]
+    api_path = os.path.join(base_path, '..', '..', 'lib')
+    cmake_build_path_win = os.path.join(base_path, '..', '..', '..', 'build', 'Release')
+    cmake_build_path = os.path.join(base_path, '..', '..', '..', 'build')
+    install_path = os.path.join(base_path, '..', '..', '..')
+    dll_path = [base_path, api_path, cmake_build_path_win, cmake_build_path,
+                install_path]
 
     if sys.platform.startswith('linux') and os.environ.get('LD_LIBRARY_PATH', None):
         dll_path.extend([p.strip() for p in os.environ['LD_LIBRARY_PATH'].split(":")])
@@ -43,11 +45,12 @@ def find_lib_path():
     if sys.platform.startswith('win32'):
         vs_configuration = 'Release'
         if platform.architecture()[0] == '64bit':
-            dll_path.append(os.path.join(base_path, '../../../build', vs_configuration))
-            dll_path.append(os.path.join(base_path, '../../../windows/x64', vs_configuration))
+            dll_path.append(os.path.join(base_path, '..', '..', '..', 'build', vs_configuration))
+            dll_path.append(os.path.join(base_path, '..', '..', '..', 'windows', 'x64',
+                                         vs_configuration))
         else:
-            dll_path.append(os.path.join(base_path, '../../../build', vs_configuration))
-            dll_path.append(os.path.join(base_path, '../../../windows', vs_configuration))
+            dll_path.append(os.path.join(base_path, '..', '..', '..', 'build', vs_configuration))
+            dll_path.append(os.path.join(base_path, '..', '..', '..', 'windows', vs_configuration))
         dll_path = [os.path.join(p, '%s.dll' % lib_name) for p in dll_path]
     elif sys.platform.startswith('darwin'):
         dll_path = [os.path.join(p, '%s.dylib' % lib_name) for p in dll_path]
diff --git a/nnvm/python/setup.py b/nnvm/python/setup.py
index 2b2102e319eb..f680690a2e10 100644
--- a/nnvm/python/setup.py
+++ b/nnvm/python/setup.py
@@ -37,11 +37,18 @@ def config_cython():
 libinfo = {'__file__': libinfo_py}
 exec(compile(open(libinfo_py, "rb").read(), libinfo_py, 'exec'), libinfo, libinfo)
 
-LIB_PATH = libinfo['find_lib_path']()
-_, LIB_NAME = os.path.split(LIB_PATH[0])
 __version__ = libinfo['__version__']
-curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
-rpath = os.path.relpath(LIB_PATH[0], curr_path)
+if not os.getenv('CONDA_BUILD'):
+    LIB_PATH = libinfo['find_lib_path']()
+    _, LIB_NAME = os.path.split(LIB_PATH[0])
+    curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
+    rpath = os.path.relpath(LIB_PATH[0], curr_path)
+    setup_kwargs = dict(
+        include_package_data=True,
+        data_files=[('nnvm', [rpath])]
+    )
+else:
+    setup_kwargs = {}
 
 setup(name='nnvm',
       version=__version__,      
@@ -52,6 +59,4 @@ def config_cython():
       ],
       packages=find_packages(),
       url='https://github.com/dmlc/nnvm',
-      include_package_data=True,
-      data_files=[('nnvm', [rpath])])
-
+      **setup_kwargs)
diff --git a/python/conda/build.sh b/python/conda/build.sh
deleted file mode 100644
index 60582de9523b..000000000000
--- a/python/conda/build.sh
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/bin/bash
-
-set -e
-
-# See Note [CUDA_TOOLKIT_ROOT_DIR versus CUDA_BIN_PATH]
-if [ -z "$CONDA_CUDA_HOME" ]; then
-  CUDA_ARGS=""
-else
-  # See Note [Bash argument quoting]
-  CUDA_ARGS="-DCUDA_TOOLKIT_ROOT_DIR=$(printf %q "$CONDA_CUDA_HOME")"
-fi
-
-if [ -z "$PREFIX" ]; then
-  PREFIX="$CONDA_PREFIX"
-fi
-
-if [ "$(uname)" = 'Darwin' ]
-then
-    # Without this, Apple's default shipped clang will refuse to see any
-    # headers like mutex.
-    export MACOSX_DEPLOYMENT_TARGET=10.9
-fi
-
-rm -rf build || true
-mkdir -p build
-cd build
-# Enable static-libstdc++ to make it easier to link this library with
-# other C++ compilers
-CXXFLAGS=-static-libstdc++ cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DUSE_CUDA=1 -DUSE_LLVM=1 -DINSTALL_DEV=1 $CUDA_ARGS ..
-make -j20 VERBOSE=1
-make install/fast
-cd ..
-
-# Also install the headers for libraries that TVM vendored
-mkdir -p "$PREFIX/include"
-# TODO: arguably dlpack and dmlc-core should get its own packaging and
-# install their headers themselves
-cp -R dlpack/include/. "$PREFIX/include"
-cp -R dmlc-core/include/. "$PREFIX/include"
-# TODO: HalideIR's includes could conflict, but TVM currently assumes they
-# are installed here, awfully enough
-cp -R HalideIR/src/. "$PREFIX/include"
-
-cd python
-$PYTHON setup.py install
-cd ..
diff --git a/python/setup.py b/python/setup.py
index 188938d76c3d..5ae7a6d909e4 100644
--- a/python/setup.py
+++ b/python/setup.py
@@ -27,14 +27,17 @@ def get_lib_path():
     libinfo_py = os.path.join(CURRENT_DIR, './tvm/_ffi/libinfo.py')
     libinfo = {'__file__': libinfo_py}
     exec(compile(open(libinfo_py, "rb").read(), libinfo_py, 'exec'), libinfo, libinfo)
-    lib_path = libinfo['find_lib_path']()
     version = libinfo['__version__']
-    libs = [lib_path[0]]
-    if libs[0].find("runtime") == -1:
-        for name in lib_path[1:]:
-            if name.find("runtime") != -1:
-                libs.append(name)
-                break
+    if not os.getenv('CONDA_BUILD'):
+        lib_path = libinfo['find_lib_path']()
+        libs = [lib_path[0]]
+        if libs[0].find("runtime") == -1:
+            for name in lib_path[1:]:
+                if name.find("runtime") != -1:
+                    libs.append(name)
+                    break
+    else:
+        libs = None
     return libs, version
 
 LIB_LIST, __version__ = get_lib_path()
@@ -89,23 +92,33 @@ def has_ext_modules(self):
     def is_pure(self):
         return False
 
+include_libs = False
+wheel_include_libs = False
+if not os.getenv('CONDA_BUILD'):
+    if "bdist_wheel" in sys.argv:
+        wheel_include_libs = True
+    else:
+        include_libs = True
+
+setup_kwargs = {}
+
 # For bdist_wheel only
-if "bdist_wheel" in sys.argv:
-    with open("MANIFEST.in", "w") as fo:
-        for path in LIB_LIST:
-            shutil.copy(path, os.path.join(CURRENT_DIR, 'tvm'))
-            _, libname = os.path.split(path)
-            fo.write("include tvm/%s\n" % libname)
+if wheel_include_libs:
+    for path in LIB_LIST:
+        shutil.copy(path, os.path.join(CURRENT_DIR, 'tvm'))
+        _, libname = os.path.split(path)
+        fo.write("include tvm/%s\n" % libname)
     setup_kwargs = {
         "include_package_data": True
     }
-else:
+
+if include_libs:
     curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
     for i, path in enumerate(LIB_LIST):
         LIB_LIST[i] = os.path.relpath(path, curr_path)
     setup_kwargs = {
         "include_package_data": True,
-        "data_files": [('tvm', LIB_LIST)]
+        "package_data": {'tvm': LIB_LIST}
     }
 
 setup(name='tvm',
@@ -122,8 +135,9 @@ def is_pure(self):
       ext_modules=config_cython(),
       **setup_kwargs)
 
-# Wheel cleanup
-if "bdist_wheel" in sys.argv:
+
+if wheel_include_libs:
+    # Wheel cleanup
     os.remove("MANIFEST.in")
     for path in LIB_LIST:
         _, libname = os.path.split(path)
diff --git a/python/tvm/_ffi/libinfo.py b/python/tvm/_ffi/libinfo.py
index b449e712c32c..390849f8536d 100644
--- a/python/tvm/_ffi/libinfo.py
+++ b/python/tvm/_ffi/libinfo.py
@@ -40,11 +40,11 @@ def find_lib_path(name=None, search_path=None, optional=False):
         dll_path.extend([p.strip() for p in os.environ['DYLD_LIBRARY_PATH'].split(":")])
 
     # Pip lib directory
-    dll_path.append(os.path.join(ffi_dir, "../"))
+    dll_path.append(os.path.join(ffi_dir, ".."))
     # Default cmake build directory
     dll_path.append(os.path.join(source_dir, "build"))
     dll_path.append(os.path.join(source_dir, "build", "Release"))
-    # Default mkae build directory
+    # Default make build directory
     dll_path.append(os.path.join(source_dir, "lib"))
 
     dll_path.append(install_lib_dir)
diff --git a/topi/python/setup.py b/topi/python/setup.py
index 6967051acbd8..dadb79453857 100644
--- a/topi/python/setup.py
+++ b/topi/python/setup.py
@@ -29,25 +29,31 @@ def get_lib_path():
     libinfo_py = os.path.join(CURRENT_DIR, '../../python/tvm/_ffi/libinfo.py')
     libinfo = {'__file__': libinfo_py}
     exec(compile(open(libinfo_py, "rb").read(), libinfo_py, 'exec'), libinfo, libinfo)
-    lib_path = libinfo['find_lib_path'](get_lib_names())
     version = libinfo['__version__']
-    libs = [lib_path[0]]
-    if libs[0].find("runtime") == -1:
-        for name in lib_path[1:]:
-            if name.find("runtime") != -1:
-                libs.append(name)
-                break
+    if not os.getenv('CONDA_BUILD'):
+        lib_path = libinfo['find_lib_path'](get_lib_names())
+        libs = [lib_path[0]]
+        if libs[0].find("runtime") == -1:
+            for name in lib_path[1:]:
+                if name.find("runtime") != -1:
+                    libs.append(name)
+                    break
+    else:
+        libs = None
     return libs, version
 
 LIB_LIST, __version__ = get_lib_path()
 
-curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
-for i, path in enumerate(LIB_LIST):
-    LIB_LIST[i] = os.path.relpath(path, curr_path)
-setup_kwargs = {
-    "include_package_data": True,
-    "data_files": [('topi', LIB_LIST)]
-}
+if not os.getenv('CONDA_BUILD'):
+    curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
+    for i, path in enumerate(LIB_LIST):
+        LIB_LIST[i] = os.path.relpath(path, curr_path)
+    setup_kwargs = {
+        "include_package_data": True,
+        "data_files": [('topi', LIB_LIST)]
+    }
+else:
+    setup_kwargs = {}
 
 setup(name='topi',
       version=__version__,

From bf62a36f77fff42de4c6b3fecf240c979d5e9460 Mon Sep 17 00:00:00 2001
From: MORITA Kazutaka <morita.kazutaka@lab.ntt.co.jp>
Date: Wed, 18 Jul 2018 13:19:51 +0900
Subject: [PATCH 881/949] [DOCS][SDACCEL] Update AWS F1 deployment (#1447)

---
 docs/deploy/aws_fpga.md | 1 -
 1 file changed, 1 deletion(-)

diff --git a/docs/deploy/aws_fpga.md b/docs/deploy/aws_fpga.md
index a3b3cda64889..7554ce7f64cd 100644
--- a/docs/deploy/aws_fpga.md
+++ b/docs/deploy/aws_fpga.md
@@ -116,7 +116,6 @@ export XCL_TARGET=hw
 
 python build.py
 ```
-The result shows CL_INVALID_PROGRAM error.  It is because AWS SDAccel expects awsxclbin binary, but we pass xclbin instead of it.  We don't load FPGA image here, so simply ignore the error for now.
 
 - Create AWS FPGA image and upload it to AWS S3.
 ```

From b6418b99c9695c20d3b31ae82ca281ca1fdba5dd Mon Sep 17 00:00:00 2001
From: alex-weaver <awsweaver@gmail.com>
Date: Wed, 18 Jul 2018 16:55:38 +0100
Subject: [PATCH 882/949] [RPC] Added native debug logging to Android RPC
 (#1432)

---
 .../app/src/main/jni/tvm_runtime.h            | 21 +++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/apps/android_rpc/app/src/main/jni/tvm_runtime.h b/apps/android_rpc/app/src/main/jni/tvm_runtime.h
index b4d5284863a1..c3c33b0fde37 100644
--- a/apps/android_rpc/app/src/main/jni/tvm_runtime.h
+++ b/apps/android_rpc/app/src/main/jni/tvm_runtime.h
@@ -6,6 +6,18 @@
 #include <sys/stat.h>
 #include <fstream>
 
+/* Enable custom logging - this will cause TVM to pass every log message
+ * through CustomLogMessage instead of LogMessage. By enabling this, we must
+ * implement dmlc::CustomLogMessage::Log. We use this to pass TVM log
+ * messages to Android logcat.
+ */
+#define DMLC_LOG_CUSTOMIZE 1
+
+/* Ensure that fatal errors are passed to the logger before throwing
+ * in LogMessageFatal
+ */
+#define DMLC_LOG_BEFORE_THROW 1
+
 #include "../src/runtime/c_runtime_api.cc"
 #include "../src/runtime/cpu_device_api.cc"
 #include "../src/runtime/workspace_pool.cc"
@@ -34,3 +46,12 @@
 #include "../src/runtime/vulkan/vulkan_device_api.cc"
 #include "../src/runtime/vulkan/vulkan_module.cc"
 #endif
+
+
+#include <android/log.h>
+
+void dmlc::CustomLogMessage::Log(const std::string& msg) {
+  // This is called for every message logged by TVM.
+  // We pass the message to logcat.
+  __android_log_write(ANDROID_LOG_DEBUG, "TVM_RUNTIME", msg.c_str());
+}

From 9ed52648f7a3abc746ad8bad5612333f05881389 Mon Sep 17 00:00:00 2001
From: Siva <sivar.b@huawei.com>
Date: Wed, 18 Jul 2018 21:34:48 +0530
Subject: [PATCH 883/949] [NNVM][TENSORFLOW] bug fix on bilinear and resize op
 integration in frontend. (#1440)

---
 nnvm/python/nnvm/frontend/tensorflow.py       |  22 +---
 nnvm/python/nnvm/testing/tf.py                | 123 ------------------
 nnvm/src/top/image/resize.cc                  |  10 +-
 nnvm/tests/python/compiler/test_top_level2.py |   7 +-
 .../frontend/tensorflow/test_forward.py       |  92 ++++++++++---
 topi/include/topi/image/resize.h              |  52 +++++---
 .../topi/testing/bilinear_resize_python.py    |  60 ++++-----
 7 files changed, 144 insertions(+), 222 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/tensorflow.py b/nnvm/python/nnvm/frontend/tensorflow.py
index aa00c51836fb..31ee0a8b116f 100644
--- a/nnvm/python/nnvm/frontend/tensorflow.py
+++ b/nnvm/python/nnvm/frontend/tensorflow.py
@@ -332,26 +332,14 @@ def _impl(inputs, attr, params):
 
 def _resize_bilinear():
     def _impl(inputs, attr, params):
-        # Change this when we have corresponding resize bilinear operation.
-        print("ResizeBilinear:Only NN (nearest neighbor) supported in symetric mode of dimensions")
-        print("Change this when we have corresponding resize bilinear operation")
-
-        # NHWC
-        input_shape = attr['_input_shapes'][inputs[0]][0]
-        in_hw = (input_shape[1], input_shape[2])
-        out_hw = params[inputs[1].list_output_names()[0]]
+        attr['size'] = attr['_output_shapes'][0][1:3]
         inputs.pop(1)
+        # NHWC
         attr['layout'] = 'NHWC'
 
-        if in_hw[0] < 0 or in_hw[1] < 0:
-            scale = 1
-        else:
-            # Considering height alone for scale
-            scale = out_hw[0] / in_hw[0]
-
-        return AttrCvt(op_name="upsampling",
-                       ignores=['Tdim', 'align_corners'],
-                       extras={'scale': scale})(inputs, attr)
+        return AttrCvt(op_name="resize",
+                       ignores=['Tdim'],
+                       extras={'method': "BILINEAR"})(inputs, attr)
     return _impl
 
 def _check_numerics():
diff --git a/nnvm/python/nnvm/testing/tf.py b/nnvm/python/nnvm/testing/tf.py
index 1762ce56596b..f5ca0e11d194 100644
--- a/nnvm/python/nnvm/testing/tf.py
+++ b/nnvm/python/nnvm/testing/tf.py
@@ -6,7 +6,6 @@
 """
 import re
 import os.path
-import numpy as np
 
 # Tensorflow imports
 import tensorflow as tf
@@ -107,52 +106,6 @@ def id_to_string(self, node_id):
             return ''
         return self.node_lookup[node_id]
 
-def read_normalized_tensor_from_image_file(file_name,
-                                           input_height=299,
-                                           input_width=299,
-                                           input_mean=0,
-                                           input_std=255):
-    """ Preprocessing of image
-    Parameters
-    ----------
-
-    file_name: String
-        Image filename.
-
-    input_height: int
-        model input height.
-
-    input_width: int
-        model input width
-
-    input_mean: int
-        Mean to be substracted in normalization.
-
-    input_std: int
-        Standard deviation used in normalization.
-
-    Returns
-    -------
-
-    np_array: Numpy array
-        Normalized image data as a numpy array.
-
-    """
-
-    input_name = "file_reader"
-    output_name = "normalized"
-    file_reader = tf.read_file(file_name, input_name)
-
-    image_reader = tf.image.decode_jpeg(file_reader, channels=3,
-                                        name='jpeg_reader')
-    float_caster = tf.cast(image_reader, tf.float32)
-    dims_expander = tf.expand_dims(float_caster, 0)
-    resized = tf.image.resize_bilinear(dims_expander, [input_height, input_width])
-    normalized = tf.divide(tf.subtract(resized, [input_mean]), [input_std])
-    tf.InteractiveSession()
-    np_array = normalized.eval()
-    return np_array
-
 def get_workload(model_path):
     """ Import workload from frozen protobuf
 
@@ -181,79 +134,3 @@ def get_workload(model_path):
         graph_def.ParseFromString(f.read())
         graph = tf.import_graph_def(graph_def, name='')
         return graph_def
-
-def get_workload_inception_v3():
-    """ Import Inception V3 workload from frozen protobuf
-
-    Parameters
-    ----------
-        Nothing.
-
-    Returns
-    -------
-    (normalized, graph_def) : Tuple
-        normalized is normalized input for graph testing.
-        graph_def is the tensorflow workload for Inception V3.
-    """
-
-    repo_base = 'https://github.com/dmlc/web-data/raw/master/tensorflow/models/InceptionV3/'
-    model_path = 'InceptionV3/inception_v3_2016_08_28_frozen-with_shapes.pb'
-
-    image_name = 'elephant-299.jpg'
-    image_url = os.path.join(repo_base, image_name)
-    from mxnet.gluon.utils import download
-    download(image_url, image_name)
-    normalized = read_normalized_tensor_from_image_file(os.path.join("./", image_name))
-
-    return (normalized, get_workload(model_path))
-
-def get_workload_inception_v1():
-    """ Import Inception V1 workload from frozen protobuf
-
-    Parameters
-    ----------
-        Nothing.
-
-    Returns
-    -------
-    (image_data, tvm_data, graph_def) : Tuple
-        image_data is raw encoded image data for TF input.
-        tvm_data is the decoded image data for TVM input.
-        graph_def is the tensorflow workload for Inception V1.
-
-    """
-
-    repo_base = 'https://github.com/dmlc/web-data/raw/master/tensorflow/models/InceptionV1/'
-    model_path = 'InceptionV1/classify_image_graph_def-with_shapes.pb'
-    image_name = 'elephant-299.jpg'
-    image_url = os.path.join(repo_base, image_name)
-
-    from mxnet.gluon.utils import download
-    download(image_url, image_name)
-
-    if not tf.gfile.Exists(os.path.join("./", image_name)):
-        tf.logging.fatal('File does not exist %s', image)
-    image_data = tf.gfile.FastGFile(os.path.join("./", image_name), 'rb').read()
-
-    # TVM doesn't handle decode, hence decode it.
-    from PIL import Image
-    tvm_data = Image.open(os.path.join("./", image_name)).resize((299, 299))
-    tvm_data = np.array(tvm_data)
-
-    return (image_data, tvm_data, get_workload(model_path))
-
-def get_workload_mobilenet():
-    """ Import mobilenet workload from frozen protobuf
-
-    Parameters
-    ----------
-        Nothing.
-
-    Returns
-    -------
-    graph_def: graphdef
-        graph_def is the tensorflow workload for mobilenet.
-
-    """
-
-    return get_workload("MobilenetV1/mobilenet_v1_1.0_224_frozen-with-shapes.pb")
diff --git a/nnvm/src/top/image/resize.cc b/nnvm/src/top/image/resize.cc
index 151296d8ecc4..b89070fe3897 100644
--- a/nnvm/src/top/image/resize.cc
+++ b/nnvm/src/top/image/resize.cc
@@ -40,13 +40,9 @@ inline bool ResizeInferShape(const nnvm::NodeAttrs& attrs,
   dshape = ConvertLayout(dshape, param.layout, kNCHW);
 
   TShape oshape = dshape;
-  if (param.layout == "NCHW") {
-    oshape[2] = param.size[0];
-    oshape[3] = param.size[1];
-  } else {
-    oshape[1] = param.size[0];
-    oshape[2] = param.size[1];
-  }
+  oshape[2] = param.size[0];
+  oshape[3] = param.size[1];
+
   oshape = ConvertLayout(oshape, kNCHW, param.layout);
   NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, oshape);
 
diff --git a/nnvm/tests/python/compiler/test_top_level2.py b/nnvm/tests/python/compiler/test_top_level2.py
index 19387a586528..46809b0d658c 100644
--- a/nnvm/tests/python/compiler/test_top_level2.py
+++ b/nnvm/tests/python/compiler/test_top_level2.py
@@ -300,11 +300,10 @@ def test_upsampling_bilinear():
 
 def test_resize_bilinear():
     x = sym.Variable("x")
-    scale = 2
-    y = sym.upsampling(x, scale=scale, method="BILINEAR", name="y", layout="NHWC")
+    y = sym.resize(x, size=(60, 60), method="BILINEAR", name="y", layout="NHWC")
     dtype = "float32"
     dshape = (1, 32, 32, 4)
-    oshape = (1, 32*scale, 32*scale, 4)
+    oshape = (1, 60, 60, 4)
     shape_dict = {"x": dshape}
     dtype_dict = {"x": dtype}
     for target, ctx in ctx_list():
@@ -314,7 +313,7 @@ def test_resize_bilinear():
         data = tvm.nd.array(a_np)
         m.run(x=data)
         out = m.get_output(0, tvm.nd.empty(oshape, dtype))
-        b_np = topi.testing.bilinear_resize_python(a_np, (32*scale, 32*scale), "NHWC")
+        b_np = topi.testing.bilinear_resize_python(a_np, (60, 60), "NHWC")
         np.testing.assert_allclose(out.asnumpy(), b_np, rtol=1e-5, atol=1e-5)
 
 if __name__ == "__main__":
diff --git a/nnvm/tests/python/frontend/tensorflow/test_forward.py b/nnvm/tests/python/frontend/tensorflow/test_forward.py
index 9fcf33bdf558..3275d6215309 100644
--- a/nnvm/tests/python/frontend/tensorflow/test_forward.py
+++ b/nnvm/tests/python/frontend/tensorflow/test_forward.py
@@ -469,24 +469,65 @@ def test_forward_multi_input():
 
             sess.close()
 
+#######################################################################
+# Resize Bilinear
+# ---------------
+
+def _test_resize_bilinear(in_shape, to_shape, align_corners):
+    """ One iteration of resize bilinear """
+
+    data = np.random.uniform(size=in_shape).astype('float32')
+    shape_data = np.array(to_shape).astype('int32')
+
+    with tf.Graph().as_default():
+        in_data = constant_op.constant(data, shape=data.shape, dtype=data.dtype)
+        shape_data = constant_op.constant(shape_data, shape=shape_data.shape, dtype=shape_data.dtype)
+
+        # pylint: disable=unused-variable
+        resize_out = tf.image.resize_bilinear(in_data, shape_data, align_corners=align_corners)
+        # pylint: enable=unused-variable
+
+        with tf.Session() as sess:
+            graph_def = tf.graph_util.convert_variables_to_constants(
+                sess,
+                sess.graph.as_graph_def(add_shapes=True),
+                ['ResizeBilinear'],
+                )
+
+            tf_output = run_tf_graph(sess, data,
+                    'Const:0', 'ResizeBilinear:0')
+
+            tvm_output = run_tvm_graph(graph_def,
+                                       data,
+                                       "Const", tf_output.shape, data.dtype)
+
+            np.testing.assert_allclose(tf_output, tvm_output, atol=1e-3, rtol=1e-3)
+
+            sess.close()
+
+def test_forward_resize_bilinear():
+    """ Resize Bilinear """
+
+    _test_resize_bilinear((4, 16, 32, 32), [50, 50], False)
+    _test_resize_bilinear((6, 32, 64, 64), [20, 20], True)
+
+
 #######################################################################
 # Inception V3
 # ------------
 def test_forward_inception_v3():
     '''test inception V3 model'''
     with tf.Graph().as_default():
-        (data, graph_def) = nnvm.testing.tf.get_workload_inception_v3()
+        graph_def = nnvm.testing.tf.get_workload('InceptionV3/inception_v3_2016_08_28_frozen-with_shapes.pb')
         # Call the utility to import the graph definition into default graph.
         graph_def = nnvm.testing.tf.ProcessGraphDefParam(graph_def)
 
-        tvm_output = run_tvm_graph(graph_def, data, 'input', (1, 1001), 'float32')
+        data = np.random.uniform(size=(1, 299, 299, 3)).astype('float32')
+
         with tf.Session() as sess:
             tf_output = run_tf_graph(sess, data, 'input:0', 'InceptionV3/Predictions/Reshape_1:0')
-
-            top_tvm = np.squeeze(tvm_output).argsort()[-3:][::-1]
-            top_tf = np.squeeze(tf_output).argsort()[-3:][::-1]
-
-            np.testing.assert_allclose(top_tf, top_tvm, rtol=1e-5, atol=1e-5)
+            tvm_output = run_tvm_graph(graph_def, data, 'input', tf_output.shape, 'float32')
+            np.testing.assert_allclose(tf_output, tvm_output, rtol=1e-5, atol=1e-5)
 
 #######################################################################
 # Inception V1
@@ -494,16 +535,35 @@ def test_forward_inception_v3():
 def test_forward_inception_v1():
     '''test inception V1 model'''
     with tf.Graph().as_default():
-        (data, tvm_data, graph_def) = nnvm.testing.tf.get_workload_inception_v1()
+        graph_def = nnvm.testing.tf.get_workload("InceptionV1/classify_image_graph_def-with_shapes.pb")
         # Call the utility to import the graph definition into default graph.
         graph_def = nnvm.testing.tf.ProcessGraphDefParam(graph_def)
 
-        tvm_output = run_tvm_graph(graph_def, tvm_data, 'DecodeJpeg/contents', (1, 1008), 'float32')
+        # Build an image from random data.
+        from PIL import Image
+        from tvm.contrib import util
+
+        img_array = np.random.uniform(size=(1, 600, 600, 3)).astype("uint8")
+        img = Image.frombuffer('RGB', (600, 600), img_array.tostring(), 'raw', 'RGB', 0, 1)
+        temp = util.tempdir()
+        img_path = temp.relpath("tf-test.jpg")
+        img.save(img_path);
+
+        import os.path
+        if not tf.gfile.Exists(os.path.join(img_path)):
+            tf.logging.fatal('File does not exist %s', image)
+        data = tf.gfile.FastGFile(os.path.join(img_path), 'rb').read()
+
+        temp.remove()
 
+        # Extract tensorflow decoded image frame for tvm input
         with tf.Session() as sess:
-            tf_output = run_tf_graph(sess, data, 'DecodeJpeg/contents:0', 'softmax:0')
+            tvm_data = run_tf_graph(sess, data, 'DecodeJpeg/contents:0', 'DecodeJpeg:0')
 
-        np.testing.assert_allclose(tf_output, tvm_output, rtol=2e-2, atol=2e-2)
+        with tf.Session() as sess:
+            tf_output = run_tf_graph(sess, data, 'DecodeJpeg/contents:0', 'softmax:0')
+            tvm_output = run_tvm_graph(graph_def, tvm_data, 'DecodeJpeg/contents', tf_output.shape, 'float32')
+            np.testing.assert_allclose(tf_output, tvm_output, rtol=1e-5, atol=1e-5)
 
 #######################################################################
 # Mobilenet
@@ -511,7 +571,7 @@ def test_forward_inception_v1():
 def test_forward_mobilenet():
     '''test mobilenet model'''
     with tf.Graph().as_default():
-        graph_def = nnvm.testing.tf.get_workload_mobilenet()
+        graph_def = nnvm.testing.tf.get_workload("MobilenetV1/mobilenet_v1_1.0_224_frozen-with-shapes.pb")
         # Call the utility to import the graph definition into default graph.
         graph_def = nnvm.testing.tf.ProcessGraphDefParam(graph_def)
 
@@ -520,12 +580,7 @@ def test_forward_mobilenet():
 
         with tf.Session() as sess:
             tf_output = run_tf_graph(sess, data, 'input:0', out_node + ':0')
-
-            out_shape = tf_output.shape
-            tvm_output = run_tvm_graph(graph_def, data, 'input', out_shape, 'float32')
-            top_tvm = np.squeeze(tvm_output).argsort()[-10:][::-1]
-            top_tf = np.squeeze(tf_output).argsort()[-10:][::-1]
-
+            tvm_output = run_tvm_graph(graph_def, data, 'input', tf_output.shape, 'float32')
             np.testing.assert_allclose(np.squeeze(tvm_output), np.squeeze(tf_output), rtol=1e-5, atol=1e-5)
 
 #######################################################################
@@ -544,3 +599,4 @@ def test_forward_mobilenet():
     test_forward_inception_v1()
     test_forward_mobilenet()
     test_forward_variable()
+    test_forward_resize_bilinear()
diff --git a/topi/include/topi/image/resize.h b/topi/include/topi/image/resize.h
index e00fe15fdf1e..b6bd51ef0fd2 100644
--- a/topi/include/topi/image/resize.h
+++ b/topi/include/topi/image/resize.h
@@ -153,7 +153,7 @@ inline Tensor resize_bilinear_nhwc(const Tensor& input,
   Expr y_ratio;
   Expr x_ratio;
 
-  if (align_corners) {
+  if (!align_corners) {
     y_ratio = make_const(Float(32), (static_cast<float>(*in_height) /
                                      static_cast<float>(*out_height)));
     x_ratio = make_const(Float(32), (static_cast<float>(*in_width) /
@@ -170,21 +170,31 @@ inline Tensor resize_bilinear_nhwc(const Tensor& input,
 
   return compute(
     out_shape, [&](const Array<Var>& indices) {
-    auto y0 = HalideIR::Internal::Cast::make(Int(32), tvm::floor(y_ratio * indices[1]));
-    auto x0 = HalideIR::Internal::Cast::make(Int(32), tvm::floor(x_ratio * indices[2]));
+    auto in_y = indices[1] * y_ratio;
+    auto yf = tvm::floor(in_y);
+    auto yc = HalideIR::Internal::Cast::make(Int(32), tvm::ceil(in_y));
 
-    auto y1 = tvm::select(((y0 + cone) > other_y), other_y, (y0 + cone));
-    auto x1 = tvm::select(((x0 + cone) > other_x), other_x, (x0 + cone));
+    auto y0 = HalideIR::Internal::Cast::make(Int(32), tvm::floor(in_y));
+    auto y1 = tvm::select((yc > other_y), other_y, yc);
+    auto y_lerp  = in_y - yf;
 
-    auto h = (y_ratio * indices[1]) - y0;
-    auto w = (x_ratio * indices[2]) - x0;;
+    auto in_x = indices[2] * x_ratio;
+    auto xf = tvm::floor(in_x);
+    auto xc = HalideIR::Internal::Cast::make(Int(32), tvm::ceil(in_x));
+
+    auto x0 = HalideIR::Internal::Cast::make(Int(32), tvm::floor(in_x));
+    auto x1 = tvm::select((xc > other_x), other_x, xc);
+    auto x_lerp  = in_x - xf;
 
     auto A = input(indices[0], y0, x0, indices[3]);
     auto B = input(indices[0], y0, x1, indices[3]);
     auto C = input(indices[0], y1, x0, indices[3]);
     auto D = input(indices[0], y1, x1, indices[3]);
 
-    return  (A*(cone-w)*(cone-h) + B*(w)*(cone-h) + C*(h)*(cone-w) + D*w*h);
+    auto top = A + (B - A) * x_lerp;
+    auto bottom = C + (D - C) * x_lerp;
+
+    return  (top + (bottom - top) * y_lerp);
     }, name, tag);
 }
 
@@ -220,7 +230,7 @@ inline Tensor resize_bilinear_nchw(const Tensor& input,
   Expr y_ratio;
   Expr x_ratio;
 
-  if (align_corners) {
+  if (!align_corners) {
     y_ratio = make_const(Float(32), (static_cast<float>(*in_height) /
                                      static_cast<float>(*out_height)));
     x_ratio = make_const(Float(32), (static_cast<float>(*in_width) /
@@ -237,21 +247,31 @@ inline Tensor resize_bilinear_nchw(const Tensor& input,
 
   return compute(
     out_shape, [&](const Array<Var>& indices) {
-    auto y0 = HalideIR::Internal::Cast::make(Int(32), tvm::floor(y_ratio * indices[2]));
-    auto x0 = HalideIR::Internal::Cast::make(Int(32), tvm::floor(x_ratio * indices[3]));
+    auto in_y = indices[2] * y_ratio;
+    auto yf = tvm::floor(in_y);
+    auto yc = HalideIR::Internal::Cast::make(Int(32), tvm::ceil(in_y));
 
-    auto y1 = tvm::select(((y0 + cone) > other_y), other_y, (y0 + cone));
-    auto x1 = tvm::select(((x0 + cone) > other_x), other_x, (x0 + cone));
+    auto y0 = HalideIR::Internal::Cast::make(Int(32), tvm::floor(in_y));
+    auto y1 = tvm::select((yc > other_y), other_y, yc);
+    auto y_lerp  = in_y - yf;
 
-    auto h = (y_ratio * indices[2]) - y0;
-    auto w = (x_ratio * indices[3]) - x0;;
+    auto in_x = indices[3] * x_ratio;
+    auto xf = tvm::floor(in_x);
+    auto xc = HalideIR::Internal::Cast::make(Int(32), tvm::ceil(in_x));
+
+    auto x0 = HalideIR::Internal::Cast::make(Int(32), tvm::floor(in_x));
+    auto x1 = tvm::select((xc > other_x), other_x, xc);
+    auto x_lerp  = in_x - xf;
 
     auto A = input(indices[0], indices[1], y0, x0);
     auto B = input(indices[0], indices[1], y0, x1);
     auto C = input(indices[0], indices[1], y1, x0);
     auto D = input(indices[0], indices[1], y1, x1);
 
-    return  ((A*(cone-w)*(cone-h)) + (B*(w)*(cone-h)) + (C*(h)*(cone-w)) + (D*w*h));
+    auto top = A + (B - A) * x_lerp;
+    auto bottom = C + (D - C) * x_lerp;
+
+    return  (top + (bottom - top) * y_lerp);
     }, name, tag);
 }
 
diff --git a/topi/python/topi/testing/bilinear_resize_python.py b/topi/python/topi/testing/bilinear_resize_python.py
index de931d7e1f5d..c014b075681a 100644
--- a/topi/python/topi/testing/bilinear_resize_python.py
+++ b/topi/python/topi/testing/bilinear_resize_python.py
@@ -3,32 +3,6 @@
 import math
 import numpy as np
 
-def bilinear_weights(height, width, new_h, new_w, align_corners=False):
-    """ Helper function to generate weights for bilinear scaling """
-
-    if align_corners:
-        x_ratio = np.float32(np.float32(width)/np.float32(new_w))
-        y_ratio = np.float32(np.float32(height)/np.float32(new_h))
-    else:
-        x_ratio = np.float32(np.float32(width-1)/np.float32(new_w-1))
-        y_ratio = np.float32(np.float32(height-1)/np.float32(new_h-1))
-
-    def _bilinear_interpolation(y, x):
-        x_coord = math.floor(x_ratio * x)
-        y_coord = math.floor(y_ratio * y)
-        x_diff = np.float32((x_ratio * x) - x_coord)
-        y_diff = np.float32((y_ratio * y) - y_coord)
-
-        return [y_coord, x_coord, y_diff, x_diff]
-
-    # weights to hold (srcx, srcy, x_diff, y_diff) for each out value.
-    weights = np.empty([new_h, new_w, 4], dtype='float32')
-
-    for i in range(new_h):
-        for j in range(new_w):
-            weights[i][j] = _bilinear_interpolation(i, j)
-    return weights
-
 def bilinear_resize_python(image, out_size, layout, align_corners=False):
     """ Bilinear scaling using python"""
     (new_h, new_w) = out_size
@@ -40,20 +14,32 @@ def bilinear_resize_python(image, out_size, layout, align_corners=False):
         (batch, channel, h, w) = image.shape
         scaled_image = np.ones((batch, channel, new_h, new_w))
 
-    weights = bilinear_weights(h, w, new_h, new_w, align_corners)
+    if align_corners:
+        height_scale = np.float32(h-1) / np.float32(out_size[0]-1)
+        width_scale = np.float32(w-1) / np.float32(out_size[1]-1)
+    else:
+        height_scale = np.float32(h) / np.float32(out_size[0])
+        width_scale = np.float32(w) / np.float32(out_size[1])
 
     for b in range(batch):
         for i in range(channel):
             for j in range(new_h):
                 for k in range(new_w):
-                    y0 = int(weights[j][k][0])
-                    x0 = int(weights[j][k][1])
+                    in_y = j * height_scale
+                    y0 = math.floor(in_y)
+                    y1 = min(math.ceil(in_y), h - 1)
+                    y_lerp = in_y - y0
+
+                    y0 = int(y0)
+                    y1 = int(y1)
 
-                    x1 = min((x0+1), (w-1))
-                    y1 = min((y0+1), (h-1))
+                    in_x = k * width_scale
+                    x0 = math.floor(in_x)
+                    x1 = min(math.ceil(in_x), w - 1)
+                    x_lerp = in_x - x0
 
-                    y_diff = weights[j][k][2]
-                    x_diff = weights[j][k][3]
+                    x0 = int(x0)
+                    x1 = int(x1)
 
                     if layout == 'NHWC':
                         A = image[b][y0][x0][i]
@@ -66,10 +52,10 @@ def bilinear_resize_python(image, out_size, layout, align_corners=False):
                         C = image[b][i][y1][x0]
                         D = image[b][i][y1][x1]
 
-                    pixel = np.float32((A*(1-x_diff)*(1-y_diff) +
-                                        B*(x_diff)*(1-y_diff) +
-                                        C*(y_diff)*(1-x_diff) +
-                                        D*(x_diff*y_diff)))
+                    top = A + (B - A) * x_lerp
+                    bottom = C + (D - C) * x_lerp
+
+                    pixel = np.float32(top + (bottom - top) * y_lerp)
 
                     if layout == 'NHWC':
                         scaled_image[b][j][k][i] = pixel

From 4dff5096dc31ca0d9d82fb5a3ea1cd45f79820b7 Mon Sep 17 00:00:00 2001
From: MORITA Kazutaka <morita.kazutaka@lab.ntt.co.jp>
Date: Thu, 19 Jul 2018 01:06:08 +0900
Subject: [PATCH 884/949] [CODEGEN][SDACCEL] add support for specifying FPGA
 device name (#1448)

---
 python/tvm/contrib/sdaccel.py               | 9 +++++++--
 src/codegen/codegen_vhls.cc                 | 8 +++++---
 tests/python/integration/test_ewise_fpga.py | 2 ++
 3 files changed, 14 insertions(+), 5 deletions(-)

diff --git a/python/tvm/contrib/sdaccel.py b/python/tvm/contrib/sdaccel.py
index 3e38c6edea0e..0f89911dbdad 100644
--- a/python/tvm/contrib/sdaccel.py
+++ b/python/tvm/contrib/sdaccel.py
@@ -6,7 +6,7 @@
 
 
 @register_func("tvm_callback_sdaccel_compile")
-def compile_vhls(kernel_info):
+def compile_vhls(kernel_info, device_name):
     """Compile Vivado HLS code for SDAccel.
 
     Parameters
@@ -15,6 +15,9 @@ def compile_vhls(kernel_info):
         List of kernel information.  The kernel information is a tuple of
         function name and source code.
 
+    device_name : str
+        The name of the target device
+
     Return
     ------
     xclbin : bytearray
@@ -28,7 +31,9 @@ def compile_vhls(kernel_info):
                             "sw_emu" if os.environ.get("XCL_EMULATION_MODE") else "hw")
     advanced_params = ["--xp", "param:compiler.preserveHlsOutput=1",
                        "--xp", "param:compiler.generateExtraRunData=true"]
-    platform = os.environ.get("XCL_PLATFORM", os.environ.get("AWS_PLATFORM"))
+    platform = device_name
+    if not platform:
+        platform = os.environ.get("XCL_PLATFORM", os.environ.get("AWS_PLATFORM"))
 
     if platform is None:
         raise RuntimeError("No Xlinx device specified.")
diff --git a/src/codegen/codegen_vhls.cc b/src/codegen/codegen_vhls.cc
index 2831a3d98af2..0795670bded6 100644
--- a/src/codegen/codegen_vhls.cc
+++ b/src/codegen/codegen_vhls.cc
@@ -2,6 +2,7 @@
  *  Copyright (c) 2018 by Contributors
  * \file codegen_vhls.cc
  */
+#include <tvm/build_module.h>
 #include <vector>
 #include <string>
 #include "./codegen_vhls.h"
@@ -67,7 +68,7 @@ void CodeGenVivadoHLS::PreFunctionBody(LoweredFunc f) {
 }
 
 
-runtime::Module BuildSDAccel(Array<LoweredFunc> funcs) {
+runtime::Module BuildSDAccel(Array<LoweredFunc> funcs, std::string target_str) {
   using tvm::runtime::Registry;
   bool output_ssa = false;
   CodeGenVivadoHLS cg;
@@ -94,7 +95,8 @@ runtime::Module BuildSDAccel(Array<LoweredFunc> funcs) {
 
   std::string xclbin;
   if (const auto* f = Registry::Get("tvm_callback_sdaccel_compile")) {
-    xclbin = (*f)(kernel_info).operator std::string();
+    Target target = Target::create(target_str);
+    xclbin = (*f)(kernel_info, target->device_name).operator std::string();
   } else {
     LOG(FATAL) << "Cannot compile Vivado HLS code.";
   }
@@ -103,7 +105,7 @@ runtime::Module BuildSDAccel(Array<LoweredFunc> funcs) {
 
 TVM_REGISTER_API("codegen.build_sdaccel")
 .set_body([](TVMArgs args, TVMRetValue* rv) {
-    *rv = BuildSDAccel(args[0]);
+    *rv = BuildSDAccel(args[0], args[1]);
   });
 
 }  // namespace codegen
diff --git a/tests/python/integration/test_ewise_fpga.py b/tests/python/integration/test_ewise_fpga.py
index 1c46b6be170b..c0a568faca2d 100644
--- a/tests/python/integration/test_ewise_fpga.py
+++ b/tests/python/integration/test_ewise_fpga.py
@@ -40,6 +40,8 @@ def check_device(device, host="llvm"):
             b.asnumpy(), np.exp(a.asnumpy()), rtol=1e-5)
 
     check_device("sdaccel")
+    if "AWS_PLATFORM" in os.environ:
+        check_device("sdaccel -device=" + os.environ.get("AWS_PLATFORM"))
 
 
 def test_multi_kernel():

From 4a464c65bf2566cb1f5b6a240dcb598dd4c4ef9e Mon Sep 17 00:00:00 2001
From: Yao Wang <kevinthesunwy@gmail.com>
Date: Wed, 18 Jul 2018 09:08:07 -0700
Subject: [PATCH 885/949] Fix inceptionv3 (#1446)

---
 nnvm/python/nnvm/frontend/mxnet.py            |  2 +
 nnvm/tests/python/compiler/test_top_level2.py | 37 +++++++++++++------
 .../python/frontend/mxnet/test_forward.py     |  9 +++++
 .../python/topi/testing/conv2d_nchw_python.py | 26 +++++++------
 topi/python/topi/x86/conv2d_avx_1x1.py        |  6 +--
 topi/python/topi/x86/conv2d_avx_common.py     |  6 +--
 6 files changed, 57 insertions(+), 29 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/mxnet.py b/nnvm/python/nnvm/frontend/mxnet.py
index 8d45c6f91c54..e741be6e3744 100644
--- a/nnvm/python/nnvm/frontend/mxnet.py
+++ b/nnvm/python/nnvm/frontend/mxnet.py
@@ -56,6 +56,8 @@ def _pooling(inputs, attrs):
         new_attrs['strides'] = attrs.get('stride', (1, 1))
         new_attrs['padding'] = attrs.get('pad', (0, 0))
         new_attrs['ceil_mode'] = (attrs.get('pooling_convention', 'valid') == 'full')
+        if pool_type == 'avg':
+            new_attrs['count_include_pad'] = attrs.get('count_include_pad', True)
     return _get_nnvm_op(op_name)(*inputs, **new_attrs)
 
 def _batch_norm(inputs, attrs):
diff --git a/nnvm/tests/python/compiler/test_top_level2.py b/nnvm/tests/python/compiler/test_top_level2.py
index 46809b0d658c..c26f5356557f 100644
--- a/nnvm/tests/python/compiler/test_top_level2.py
+++ b/nnvm/tests/python/compiler/test_top_level2.py
@@ -10,6 +10,20 @@
 
 
 def test_conv2d():
+    def run_test_conv2d(sym, dtype, dshape, kshape, oshape, shape_dict, padding):
+        for target, ctx in ctx_list():
+            graph, lib, _ = nnvm.compiler.build(sym, target, shape_dict)
+            m = graph_runtime.create(graph, lib, ctx)
+            data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
+            kernel = tvm.nd.array(np.random.uniform(size=kshape).astype(dtype))
+            bias = tvm.nd.array(np.random.uniform(size=kshape[0]).astype(dtype))
+            m.run(x=data, y_weight=kernel, y_bias=bias)
+            out = m.get_output(0, tvm.nd.empty(oshape, dtype))
+            c_np = topi.testing.conv2d_nchw_python(
+                data.asnumpy(), kernel.asnumpy(), 1, padding)
+            c_np = c_np + bias.asnumpy().reshape(kshape[0], 1, 1)
+            np.testing.assert_allclose(out.asnumpy(), c_np, rtol=1e-5)
+
     x = sym.Variable("x")
     y = sym.conv2d(x, channels=10, kernel_size=(3,3),
                    name="y", padding=(1,1))
@@ -18,18 +32,17 @@ def test_conv2d():
     kshape = (10, 3, 3, 3)
     oshape = (1, 10, 18, 18)
     shape_dict = {"x": dshape}
-    for target, ctx in ctx_list():
-        graph, lib, _ = nnvm.compiler.build(y, target, shape_dict)
-        m = graph_runtime.create(graph, lib, ctx)
-        data = tvm.nd.array(np.random.uniform(size=dshape).astype(dtype))
-        kernel = tvm.nd.array(np.random.uniform(size=kshape).astype(dtype))
-        bias = tvm.nd.array(np.random.uniform(size=kshape[0]).astype(dtype))
-        m.run(x=data, y_weight=kernel, y_bias=bias)
-        out = m.get_output(0, tvm.nd.empty(oshape, dtype))
-        c_np = topi.testing.conv2d_nchw_python(
-            data.asnumpy(), kernel.asnumpy(), 1, 1)
-        c_np = c_np + bias.asnumpy().reshape(kshape[0], 1, 1)
-        np.testing.assert_allclose(out.asnumpy(), c_np, rtol=1e-5)
+    run_test_conv2d(y, dtype, dshape, kshape, oshape, shape_dict, (1,1))
+
+    x = sym.Variable("x")
+    y = sym.conv2d(x, channels=10, kernel_size=(1,3),
+                   name="y", padding=(0,1))
+    dtype = "float32"
+    dshape = (1, 3, 224, 224)
+    kshape = (10, 3, 1, 3)
+    oshape = (1, 10, 224, 224)
+    shape_dict = {"x": dshape}
+    run_test_conv2d(y, dtype, dshape, kshape, oshape, shape_dict, (0,1))
 
 
 def test_mixed_precision():
diff --git a/nnvm/tests/python/frontend/mxnet/test_forward.py b/nnvm/tests/python/frontend/mxnet/test_forward.py
index cfb4e553434f..a3356689b6e4 100644
--- a/nnvm/tests/python/frontend/mxnet/test_forward.py
+++ b/nnvm/tests/python/frontend/mxnet/test_forward.py
@@ -141,6 +141,14 @@ def test_forward_expand_dims():
     mx_sym = mx.sym.expand_dims(data, axis=1)
     verify_mxnet_frontend_impl(mx_sym, (2, 3, 4), (2, 1, 3, 4))
 
+def test_forward_pooling():
+    data = mx.sym.var('data')
+    mx_sym = mx.sym.Pooling(data, kernel=(3, 3), pad=(1, 1), pool_type='avg')
+    verify_mxnet_frontend_impl(mx_sym, (1, 20, 8, 8), (1, 20, 8, 8))
+
+    mx_sym = mx.sym.Pooling(data, kernel=(3, 3), pad=(1, 1), pool_type='max')
+    verify_mxnet_frontend_impl(mx_sym, (1, 20, 8, 8), (1, 20, 8, 8))
+
 if __name__ == '__main__':
     test_forward_mlp()
     test_forward_vgg()
@@ -154,3 +162,4 @@ def test_forward_expand_dims():
     test_forward_split()
     test_forward_split_squeeze()
     test_forward_expand_dims()
+    test_forward_pooling()
diff --git a/topi/python/topi/testing/conv2d_nchw_python.py b/topi/python/topi/testing/conv2d_nchw_python.py
index 20bcddd1d069..4a40d02d215c 100644
--- a/topi/python/topi/testing/conv2d_nchw_python.py
+++ b/topi/python/topi/testing/conv2d_nchw_python.py
@@ -1,4 +1,4 @@
-# pylint: disable=invalid-name, line-too-long, unused-variable, too-many-locals
+# pylint: disable=invalid-name, line-too-long, unused-variable, too-many-locals, too-many-branches
 """Convolution in python"""
 import numpy as np
 import scipy.signal
@@ -18,8 +18,8 @@ def conv2d_nchw_python(a_np, w_np, stride, padding):
     stride : int or a list/tuple of two ints
         Stride size, or [stride_height, stride_width]
 
-    padding : int or str
-        Padding size, or ['VALID', 'SAME']
+    padding : int or str or a list/tuple of two ints
+        Padding size, or ['VALID', 'SAME'], or [pad_height, pad_width]
 
     Returns
     -------
@@ -34,12 +34,11 @@ def conv2d_nchw_python(a_np, w_np, stride, padding):
         stride_h, stride_w = stride
     if isinstance(padding, int):
         pad_h = pad_w = padding * 2
-    elif padding == 'VALID':
-        pad_h = 0
-        pad_w = 0
-    else: # 'SAME'
-        pad_h = kernel_h - 1
-        pad_w = kernel_w - 1
+    elif isinstance(padding, (list, tuple)):
+        pad_h, pad_w = padding[0] * 2, padding[1] * 2
+    else:
+        pad_h = 0 if padding == 'VALID' else kernel_h - 1
+        pad_w = 0 if padding == 'VALID' else kernel_w - 1
     pad_top = int(np.ceil(float(pad_h) / 2))
     pad_bottom = pad_h - pad_top
     pad_left = int(np.ceil(float(pad_w) / 2))
@@ -53,9 +52,14 @@ def conv2d_nchw_python(a_np, w_np, stride, padding):
     for n in range(batch):
         for f in range(out_channel):
             for c in range(in_channel):
-                if pad_h > 0:
+                if pad_h > 0 or pad_w > 0:
                     apad = np.zeros((in_height + pad_h, in_width + pad_w))
-                    apad[pad_top:-pad_bottom, pad_left:-pad_right] = a_np[n, c]
+                    if pad_h == 0:
+                        apad[:, pad_left:-pad_right] = a_np[n, c]
+                    elif pad_w == 0:
+                        apad[pad_top:-pad_bottom, :] = a_np[n, c]
+                    else:
+                        apad[pad_top:-pad_bottom, pad_left:-pad_right] = a_np[n, c]
                 else:
                     apad = a_np[n, c]
                 out = scipy.signal.convolve2d(
diff --git a/topi/python/topi/x86/conv2d_avx_1x1.py b/topi/python/topi/x86/conv2d_avx_1x1.py
index ecbae0cc3128..7d820701e1f4 100644
--- a/topi/python/topi/x86/conv2d_avx_1x1.py
+++ b/topi/python/topi/x86/conv2d_avx_1x1.py
@@ -56,7 +56,7 @@ def _declaration_conv(data, kernel, stride, padding, layout, out_dtype):
     out_height = (in_height + 2 * HPAD - kernel_height) // HSTR + 1
     out_width = (in_width + 2 * WPAD - kernel_width) // WSTR + 1
 
-    DOPAD = (HPAD != 0 and WPAD != 0)
+    DOPAD = (HPAD != 0 or WPAD != 0)
     if DOPAD:
         data_pad = pad(data, (0, 0, HPAD, WPAD), name="data_pad")
     else:
@@ -95,7 +95,7 @@ def _schedule_conv(s, data, data_pad, data_vec, kernel, kernel_vec, conv_out, ou
     sch = _get_schedule(wkl)
 
     HPAD, WPAD = wkl.hpad, wkl.wpad
-    DOPAD = (HPAD != 0 and WPAD != 0)
+    DOPAD = (HPAD != 0 or WPAD != 0)
 
     A, W = data, kernel_vec
     A0, A1 = data_pad, data_vec
@@ -163,7 +163,7 @@ def _declaration_conv_NCHWc(wkl, sch, data, kernel):
     out_height = (wkl.height + 2 * HPAD - wkl.hkernel) // HSTR + 1
     out_width = (wkl.width + 2 * WPAD - wkl.wkernel) // WSTR + 1
 
-    DOPAD = (HPAD != 0 and WPAD != 0)
+    DOPAD = (HPAD != 0 or WPAD != 0)
     if DOPAD:
         data_pad = pad(data, (0, 0, HPAD, WPAD, 0), name="data_pad")
     else:
diff --git a/topi/python/topi/x86/conv2d_avx_common.py b/topi/python/topi/x86/conv2d_avx_common.py
index 063759998108..108f5e7248e3 100644
--- a/topi/python/topi/x86/conv2d_avx_common.py
+++ b/topi/python/topi/x86/conv2d_avx_common.py
@@ -58,7 +58,7 @@ def _declaration_conv(data, kernel, stride, padding, layout, out_dtype):
     out_width = (in_width + 2 * WPAD - kernel_width) // WSTR + 1
 
     # pack data
-    DOPAD = (HPAD != 0 and WPAD != 0)
+    DOPAD = (HPAD != 0 or WPAD != 0)
     if DOPAD:
         data_pad = pad(data, (0, 0, HPAD, WPAD), name="data_pad")
     else:
@@ -108,7 +108,7 @@ def _schedule_conv(s, data, data_pad, data_vec, kernel, kernel_vec, conv_out, ou
     sch = _get_schedule(wkl)
 
     HPAD, WPAD = wkl.hpad, wkl.wpad
-    DOPAD = (HPAD != 0 and WPAD != 0)
+    DOPAD = (HPAD != 0 or WPAD != 0)
 
     A, W = data, kernel_vec
     A0, A1 = data_pad, data_vec
@@ -181,7 +181,7 @@ def _declaration_conv_NCHWc(wkl, sch, data, kernel):
     out_width = (wkl.width + 2 * WPAD - wkl.wkernel) // WSTR + 1
 
     # pack data
-    DOPAD = (HPAD != 0 and WPAD != 0)
+    DOPAD = (HPAD != 0 or WPAD != 0)
     if DOPAD:
         data_pad = pad(data, (0, 0, HPAD, WPAD, 0), name="data_pad")
     else:

From f32841fa839ecf09e512f702d3e9e28c27fe603f Mon Sep 17 00:00:00 2001
From: eqy <eqy@cs.washington.edu>
Date: Wed, 18 Jul 2018 14:12:56 -0700
Subject: [PATCH 886/949] use SetAffinity when logical cores > physical cores
 (hyperthreading) (#1453)

---
 src/runtime/thread_pool.cc       |  3 ---
 src/runtime/threading_backend.cc | 10 ++++++++--
 2 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/src/runtime/thread_pool.cc b/src/runtime/thread_pool.cc
index 5e7cf779c88f..e10738ab8bbe 100644
--- a/src/runtime/thread_pool.cc
+++ b/src/runtime/thread_pool.cc
@@ -253,9 +253,6 @@ class ThreadPool {
           num_workers_, [this](int worker_id) { this->RunWorker(worker_id); },
           exclude_worker0_ /* include_main_thread */));
     num_workers_used_ = threads_->Configure(threading::ThreadGroup::kBig, 0, exclude_worker0_);
-    // if MaxConcurrency restricted the number of workers (e.g., due to
-    // hyperthreading), respect the restriction
-    num_workers_used_ = std::min(num_workers_, num_workers_used_);
   }
   ~ThreadPool() {
     for (std::unique_ptr<SpscTaskQueue>& q : queues_) {
diff --git a/src/runtime/threading_backend.cc b/src/runtime/threading_backend.cc
index 58d5fb4530dd..ad63493fb94f 100644
--- a/src/runtime/threading_backend.cc
+++ b/src/runtime/threading_backend.cc
@@ -54,10 +54,16 @@ class ThreadGroup::Impl {
     if (nthreads) {
       num_workers_used = nthreads;
     }
+    // if MaxConcurrency restricted the number of workers (e.g., due to
+    // hyperthreading), respect the restriction. On CPUs with N logical cores
+    // and N/2 physical cores this will set affinity to the first N/2 logical
+    // ones.
+    num_workers_used = std::min(num_workers_, num_workers_used);
+
     const char *val = getenv("TVM_BIND_THREADS");
     if (val == nullptr || atoi(val) == 1) {
-      // Skip if sorted_order.size() is bigger than the number of workers (threads_)
-      if (!(sorted_order_.size() > static_cast<unsigned int>(num_workers_))) {
+      // Do not set affinity if there are more workers than found cores
+      if (sorted_order_.size() >= static_cast<unsigned int>(num_workers_)) {
           SetAffinity(exclude_worker0, mode == kLittle);
       } else {
         LOG(WARNING)

From 5b5465b59e1d5f943a46db84d43076a93d8a7003 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 18 Jul 2018 16:18:58 -0700
Subject: [PATCH 887/949] [NODE][REFLECTION] Support NDArray as field (#1452)

---
 HalideIR                                      |   2 +-
 include/tvm/runtime/ndarray.h                 | 143 ++++++++-
 include/tvm/runtime/serializer.h              |   1 +
 nnvm/python/nnvm/compiler/param_dict.py       |  12 +-
 nnvm/src/compiler/graph_runtime.cc            | 109 +------
 nnvm/src/compiler/graph_runtime.h             |  26 +-
 nnvm/tests/python/compiler/test_param_dict.py |  20 ++
 python/tvm/_ffi/_ctypes/function.py           |   1 +
 python/tvm/_ffi/_cython/node.pxi              |   2 +
 python/tvm/_ffi/ndarray.py                    |  26 ++
 src/api/dsl_api.cc                            |  15 +-
 src/common/base64.h                           | 284 ++++++++++++++++++
 src/lang/reflection.cc                        |  58 +++-
 src/runtime/graph/graph_runtime.cc            |  51 +---
 src/runtime/graph/graph_runtime.h             |   2 -
 15 files changed, 582 insertions(+), 170 deletions(-)
 create mode 100644 src/common/base64.h

diff --git a/HalideIR b/HalideIR
index 9204453ae8de..a5a80bdc8232 160000
--- a/HalideIR
+++ b/HalideIR
@@ -1 +1 @@
-Subproject commit 9204453ae8de77e7dfc32c4d80f58dd788ad75ff
+Subproject commit a5a80bdc8232c9dbfe508bb5c46e8f58cdf7ec20
diff --git a/include/tvm/runtime/ndarray.h b/include/tvm/runtime/ndarray.h
index dfb06255381a..2b51b2e0fcfe 100644
--- a/include/tvm/runtime/ndarray.h
+++ b/include/tvm/runtime/ndarray.h
@@ -10,6 +10,7 @@
 #include <vector>
 #include <utility>
 #include "./c_runtime_api.h"
+#include "./serializer.h"
 
 namespace tvm {
 namespace runtime {
@@ -103,8 +104,25 @@ class NDArray {
    * \note The copy may happen asynchrously if it involves a GPU context.
    *       TVMSynchronize is necessary.
    */
-  inline void CopyTo(DLTensor* other);
-  inline void CopyTo(const NDArray& other);
+  inline void CopyTo(DLTensor* other) const;
+  inline void CopyTo(const NDArray& other) const;
+  /*!
+   * \brief Copy the data to another context.
+   * \param ctx The target context.
+   * \return The array under another context.
+   */
+  inline NDArray CopyTo(const DLContext& ctx) const;
+  /*!
+   * \brief Load NDArray from stream
+   * \param stream The input data stream
+   * \return Whether load is successful
+   */
+  inline bool Load(dmlc::Stream* stream);
+  /*!
+   * \brief Save NDArray to stream
+   * \param stream The output data stream
+   */
+  inline void Save(dmlc::Stream* stream) const;
   /*!
    * \brief Create a NDArray that shares the data memory with the current one.
    * \param shape The shape of the new array.
@@ -161,6 +179,13 @@ class NDArray {
   friend class TVMArgsSetter;
 };
 
+/*!
+ * \brief Save a DLTensor to stream
+ * \param strm The outpu stream
+ * \param tensor The tensor to be saved.
+ */
+inline bool SaveDLTensor(dmlc::Stream* strm, const DLTensor* tensor);
+
 /*!
  * \brief Reference counted Container object used to back NDArray.
  *
@@ -260,17 +285,26 @@ inline void NDArray::CopyFrom(const NDArray& other) {
   CopyFromTo(&(other.data_->dl_tensor), &(data_->dl_tensor));
 }
 
-inline void NDArray::CopyTo(DLTensor* other) {
+inline void NDArray::CopyTo(DLTensor* other) const {
   CHECK(data_ != nullptr);
   CopyFromTo(&(data_->dl_tensor), other);
 }
 
-inline void NDArray::CopyTo(const NDArray& other) {
+inline void NDArray::CopyTo(const NDArray& other) const {
   CHECK(data_ != nullptr);
   CHECK(other.data_ != nullptr);
   CopyFromTo(&(data_->dl_tensor), &(other.data_->dl_tensor));
 }
 
+inline NDArray NDArray::CopyTo(const DLContext& ctx) const {
+  CHECK(data_ != nullptr);
+  const DLTensor* dptr = operator->();
+  NDArray ret = Empty(std::vector<int64_t>(dptr->shape, dptr->shape + dptr->ndim),
+                      dptr->dtype, ctx);
+  this->CopyTo(ret);
+  return ret;
+}
+
 inline int NDArray::use_count() const {
   if (data_ == nullptr) return 0;
   return data_->ref_counter_.load(std::memory_order_relaxed);
@@ -280,7 +314,106 @@ inline const DLTensor* NDArray::operator->() const {
   return &(data_->dl_tensor);
 }
 
+/*! \brief Magic number for NDArray file */
+constexpr uint64_t kTVMNDArrayMagic = 0xDD5E40F096B4A13F;
+
+inline bool SaveDLTensor(dmlc::Stream* strm,
+                         DLTensor* tensor) {
+  uint64_t header = kTVMNDArrayMagic, reserved = 0;
+  strm->Write(header);
+  strm->Write(reserved);
+  // Always save data as CPU context
+  //
+  // Parameters that get serialized should be in CPU by default.
+  // So even the array's context is GPU, it will be stored as CPU array.
+  // This is used to prevent case when another user loads the parameters
+  // back on machine that do not have GPU or related context.
+  //
+  // We can always do array.CopyTo(target_ctx) to get a corresponding
+  // array in the target context.
+  DLContext cpu_ctx;
+  cpu_ctx.device_type = kDLCPU;
+  cpu_ctx.device_id = 0;
+  strm->Write(cpu_ctx);
+  strm->Write(tensor->ndim);
+  strm->Write(tensor->dtype);
+  int ndim = tensor->ndim;
+  strm->WriteArray(tensor->shape, ndim);
+  int type_bytes = tensor->dtype.bits / 8;
+  int64_t num_elems = 1;
+  for (int i = 0; i < ndim; ++i) {
+    num_elems *= tensor->shape[i];
+  }
+  int64_t data_byte_size = type_bytes * num_elems;
+  strm->Write(data_byte_size);
+
+  if (DMLC_IO_NO_ENDIAN_SWAP &&
+      tensor->ctx.device_type == kDLCPU &&
+      tensor->strides == nullptr &&
+      tensor->byte_offset == 0) {
+    // quick path
+    strm->Write(tensor->data, data_byte_size);
+  } else {
+    std::vector<uint8_t> bytes(data_byte_size);
+    CHECK_EQ(TVMArrayCopyToBytes(
+        tensor, dmlc::BeginPtr(bytes), data_byte_size), 0)
+        << TVMGetLastError();
+    if (!DMLC_IO_NO_ENDIAN_SWAP) {
+      dmlc::ByteSwap(dmlc::BeginPtr(bytes), type_bytes, num_elems);
+    }
+    strm->Write(dmlc::BeginPtr(bytes), data_byte_size);
+  }
+  return true;
+}
+
+inline void NDArray::Save(dmlc::Stream* strm) const {
+  SaveDLTensor(strm, const_cast<DLTensor*>(operator->()));
+}
+
+inline bool NDArray::Load(dmlc::Stream* strm) {
+  uint64_t header, reserved;
+  CHECK(strm->Read(&header))
+      << "Invalid DLTensor file format";
+  CHECK(strm->Read(&reserved))
+      << "Invalid DLTensor file format";
+  CHECK(header == kTVMNDArrayMagic)
+      << "Invalid DLTensor file format";
+  DLContext ctx;
+  int ndim;
+  DLDataType dtype;
+  CHECK(strm->Read(&ctx))
+      << "Invalid DLTensor file format";
+  CHECK(strm->Read(&ndim))
+      << "Invalid DLTensor file format";
+  CHECK(strm->Read(&dtype))
+      << "Invalid DLTensor file format";
+  CHECK_EQ(ctx.device_type, kDLCPU)
+      << "Invalid DLTensor context: can only save as CPU tensor";
+  std::vector<int64_t> shape(ndim);
+  if (ndim != 0) {
+    CHECK(strm->ReadArray(&shape[0], ndim))
+        << "Invalid DLTensor file format";
+  }
+  NDArray ret = NDArray::Empty(shape, dtype, ctx);
+  int64_t num_elems = 1;
+  int elem_bytes = (ret->dtype.bits + 7) / 8;
+  for (int i = 0; i < ret->ndim; ++i) {
+    num_elems *= ret->shape[i];
+  }
+  int64_t data_byte_size;
+  CHECK(strm->Read(&data_byte_size))
+      << "Invalid DLTensor file format";
+  CHECK(data_byte_size == num_elems * elem_bytes)
+      << "Invalid DLTensor file format";
+  CHECK(strm->Read(ret->data, data_byte_size))
+      << "Invalid DLTensor file format";
+  if (!DMLC_IO_NO_ENDIAN_SWAP) {
+    dmlc::ByteSwap(ret->data, elem_bytes, num_elems);
+  }
+  *this = ret;
+  return true;
+}
+
 }  // namespace runtime
 }  // namespace tvm
-
 #endif  // TVM_RUNTIME_NDARRAY_H_
diff --git a/include/tvm/runtime/serializer.h b/include/tvm/runtime/serializer.h
index 391c7806ad9c..b2ab5483a22d 100644
--- a/include/tvm/runtime/serializer.h
+++ b/include/tvm/runtime/serializer.h
@@ -10,6 +10,7 @@
 #include <dmlc/io.h>
 #include <dmlc/serializer.h>
 #include "./c_runtime_api.h"
+#include "./ndarray.h"
 
 namespace dmlc {
 namespace serializer {
diff --git a/nnvm/python/nnvm/compiler/param_dict.py b/nnvm/python/nnvm/compiler/param_dict.py
index e5db3ce9fc81..3bb30e20bbc2 100644
--- a/nnvm/python/nnvm/compiler/param_dict.py
+++ b/nnvm/python/nnvm/compiler/param_dict.py
@@ -1,8 +1,6 @@
 # pylint: disable=invalid-name
 """Helper utility to save parameter dict"""
-import ctypes
 import tvm
-from tvm._ffi.runtime_ctypes import TVMArrayHandle
 
 _save_param_dict = tvm.get_global_func("nnvm.compiler._save_param_dict")
 _load_param_dict = tvm.get_global_func("nnvm.compiler._load_param_dict")
@@ -59,11 +57,5 @@ def load_param_dict(param_bytes):
     """
     if isinstance(param_bytes, (bytes, str)):
         param_bytes = bytearray(param_bytes)
-    load_mod = _load_param_dict(param_bytes)
-    size = load_mod(0)
-    param_dict = {}
-    for i in range(size):
-        key = load_mod(1, i)
-        dltensor_handle = ctypes.cast(load_mod(2, i), TVMArrayHandle)
-        param_dict[key] = tvm.nd.NDArray(dltensor_handle, False)
-    return param_dict
+    load_arr = _load_param_dict(param_bytes)
+    return {v.name : v.array for v in load_arr}
diff --git a/nnvm/src/compiler/graph_runtime.cc b/nnvm/src/compiler/graph_runtime.cc
index 689ed70ce9f2..e623192258de 100644
--- a/nnvm/src/compiler/graph_runtime.cc
+++ b/nnvm/src/compiler/graph_runtime.cc
@@ -4,10 +4,6 @@
  * \brief Interface code with TVM graph runtime.
 */
 #include <dmlc/memory_io.h>
-#include <tvm/runtime/packed_func.h>
-#include <tvm/runtime/c_runtime_api.h>
-#include <tvm/runtime/registry.h>
-#include <tvm/runtime/serializer.h>
 #include "./graph_runtime.h"
 
 namespace nnvm {
@@ -37,81 +33,6 @@ NNVM_REGISTER_OP(tvm_op)
     return param.num_outputs;
   });
 
-bool SaveDLTensor(dmlc::Stream* strm, DLTensor* tensor) {
-  uint64_t header = kTVMNDArrayMagic, reserved = 0;
-  strm->Write(header);
-  strm->Write(reserved);
-  strm->Write(tensor->ctx);
-  strm->Write(tensor->ndim);
-  strm->Write(tensor->dtype);
-  int ndim = tensor->ndim;
-  strm->WriteArray(tensor->shape, ndim);
-
-  int type_bytes = tensor->dtype.bits / 8;
-  int64_t num_elems = 1;
-  for (int i = 0; i < ndim; ++i) {
-    num_elems *= tensor->shape[i];
-  }
-  int64_t data_byte_size = type_bytes * num_elems;
-  strm->Write(data_byte_size);
-  // handle endianness of data correctly.
-  if (DMLC_IO_NO_ENDIAN_SWAP) {
-    strm->Write(tensor->data, data_byte_size);
-  } else {
-    uint8_t* dptr = reinterpret_cast<uint8_t*>(tensor->data);
-    std::vector<uint8_t> bytes(dptr, dptr + data_byte_size);
-    dmlc::ByteSwap(dmlc::BeginPtr(bytes), type_bytes, num_elems);
-    strm->Write(dmlc::BeginPtr(bytes), data_byte_size);
-  }
-  return true;
-}
-
-DLTensor* LoadDLTensor(dmlc::Stream* strm) {
-  uint64_t header, reserved;
-  CHECK(strm->Read(&header))
-      << "Invalid DLTensor file format";
-  CHECK(strm->Read(&reserved))
-      << "Invalid DLTensor file format";
-  CHECK(header == kTVMNDArrayMagic)
-      << "Invalid DLTensor file format";
-  DLTensor tensor;
-  CHECK(strm->Read(&(tensor.ctx)))
-      << "Invalid DLTensor file format";
-  CHECK(strm->Read(&(tensor.ndim)))
-      << "Invalid DLTensor file format";
-  CHECK(strm->Read(&(tensor.dtype)))
-      << "Invalid DLTensor file format";
-  std::vector<int64_t> shape(tensor.ndim);
-  if (tensor.ndim != 0) {
-    CHECK(strm->ReadArray(&shape[0], tensor.ndim))
-        << "Invalid DLTensor file format";
-  }
-  DLTensor* ret;
-  CHECK_EQ(TVMArrayAlloc(shape.data(),
-                         tensor.ndim,
-                         tensor.dtype.code,
-                         tensor.dtype.bits,
-                         tensor.dtype.lanes,
-                         static_cast<int>(tensor.ctx.device_type),
-                         tensor.ctx.device_id,
-                         &ret), 0) << TVMGetLastError();
-  int64_t num_elems = 1;
-  int elem_bytes = (ret->dtype.bits + 7) / 8;
-  for (int i = 0; i < ret->ndim; ++i) {
-    num_elems *= ret->shape[i];
-  }
-  int64_t data_byte_size;
-  CHECK(strm->Read(&data_byte_size))
-      << "Invalid DLTensor file format";
-  CHECK(data_byte_size == num_elems * elem_bytes)
-      << "Invalid DLTensor file format";
-  CHECK(strm->Read(ret->data, data_byte_size))
-      << "Invalid DLTensor file format";
-  if (!DMLC_IO_NO_ENDIAN_SWAP) {
-    dmlc::ByteSwap(ret->data, elem_bytes, num_elems);
-  }
-  return ret;
-}
 
 TVM_REGISTER_GLOBAL("nnvm.compiler._save_param_dict")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
@@ -136,7 +57,7 @@ TVM_REGISTER_GLOBAL("nnvm.compiler._save_param_dict")
       uint64_t sz = static_cast<uint64_t>(arrays.size());
       fo->Write(sz);
       for (size_t i = 0; i < sz; ++i) {
-        SaveDLTensor(fo, arrays[i]);
+        tvm::runtime::SaveDLTensor(fo, arrays[i]);
       }
     }
     TVMByteArray arr;
@@ -149,11 +70,9 @@ TVM_REGISTER_GLOBAL("nnvm.compiler._save_param_dict")
 TVM_REGISTER_GLOBAL("nnvm.compiler._load_param_dict")
 .set_body([](TVMArgs args, TVMRetValue *rv) {
     std::string bytes = args[0];
-    std::vector<DLTensor*> data;
     std::vector<std::string> names;
     dmlc::MemoryStringStream memstrm(&bytes);
     dmlc::Stream* strm = &memstrm;
-
     uint64_t header, reserved;
     CHECK(strm->Read(&header))
         << "Invalid parameters file format";
@@ -168,23 +87,19 @@ TVM_REGISTER_GLOBAL("nnvm.compiler._load_param_dict")
     size_t size = static_cast<size_t>(sz);
     CHECK(size == names.size())
         << "Invalid parameters file format";
+    tvm::Array<NDArrayWrapper> ret;
     for (size_t i = 0; i < size; ++i) {
-      data.push_back(LoadDLTensor(strm));
+      tvm::runtime::NDArray temp;
+      temp.Load(strm);
+      std::shared_ptr<NDArrayWrapperNode> n
+          = std::make_shared<NDArrayWrapperNode>();
+      n->name = std::move(names[i]);
+      n->array = temp;
+      ret.push_back(NDArrayWrapper(n));
     }
-    auto packed = [data, names](TVMArgs args, TVMRetValue* rv) {
-      int code = args[0];
-      if (code == 0) {
-        *rv = static_cast<int64_t>(data.size());
-      } else if (code == 1) {
-        int index = args[1];
-        *rv = names[index];
-      } else {
-        CHECK_EQ(code, 2);
-        int index = args[1];
-        *rv = static_cast<void*>(data[index]);
-      }
-    };
-    *rv = PackedFunc(packed);
+    *rv = ret;
   });
+
+TVM_REGISTER_NODE_TYPE(NDArrayWrapperNode);
 }  // namespace compiler
 }  // namespace nnvm
diff --git a/nnvm/src/compiler/graph_runtime.h b/nnvm/src/compiler/graph_runtime.h
index 49a2e9bfc975..272e2be7f251 100644
--- a/nnvm/src/compiler/graph_runtime.h
+++ b/nnvm/src/compiler/graph_runtime.h
@@ -7,14 +7,16 @@
 #define NNVM_COMPILER_GRAPH_RUNTIME_H_
 
 #include <nnvm/graph.h>
+#include <tvm/base.h>
+#include <tvm/expr.h>
+#include <tvm/packed_func_ext.h>
+#include <tvm/runtime/ndarray.h>
 #include <vector>
 #include <string>
 
 namespace nnvm {
 namespace compiler {
 
-/*! \brief Magic number for NDArray file */
-constexpr uint64_t kTVMNDArrayMagic = 0xDD5E40F096B4A13F;
 /*! \brief Magic number for NDArray list file  */
 constexpr uint64_t kTVMNDArrayListMagic = 0xF7E58D4F05049CB7;
 
@@ -32,6 +34,26 @@ struct TVMOpParam : public dmlc::Parameter<TVMOpParam> {
   }
 };
 
+
+/*!
+ * \brief wrapper node container for exchange.
+ */
+struct NDArrayWrapperNode : public ::tvm::Node {
+  std::string name;
+  tvm::runtime::NDArray array;
+
+  void VisitAttrs(tvm::AttrVisitor* v) final {
+    v->Visit("name", &name);
+    v->Visit("array", &array);
+  }
+
+  static constexpr const char* _type_key = "NDArrayWrapper";
+  TVM_DECLARE_NODE_TYPE_INFO(NDArrayWrapperNode, Node);
+};
+
+TVM_DEFINE_NODE_REF(NDArrayWrapper, NDArrayWrapperNode);
+
 }  // namespace compiler
 }  // namespace nnvm
+
 #endif   // NNVM_COMPILER_GRAPH_RUNTIME_H_
diff --git a/nnvm/tests/python/compiler/test_param_dict.py b/nnvm/tests/python/compiler/test_param_dict.py
index 4fa41a3a5833..a6605123fa0d 100644
--- a/nnvm/tests/python/compiler/test_param_dict.py
+++ b/nnvm/tests/python/compiler/test_param_dict.py
@@ -2,6 +2,9 @@
 import numpy as np
 import nnvm.compiler
 import tvm
+import json
+import base64
+from tvm._ffi.base import py_str
 from tvm import rpc
 from tvm.contrib import util, graph_runtime
 
@@ -20,6 +23,22 @@ def test_save_load():
     np.testing.assert_equal(param2["y"].asnumpy(), y)
 
 
+def test_ndarray_reflection():
+    x = np.random.uniform(size=(10, 2)).astype("float32")
+    xx = tvm.nd.array(x)
+    xnode = tvm.make.node("NDArrayWrapper", name="xx", array=xx)
+    xnode2 = tvm.make.node("NDArrayWrapper", name="x2", array=xx)
+    assert xnode.array.same_as(xx)
+    json_str = tvm.save_json([xnode, xnode2])
+    json_dict = json.loads(json_str)
+    b64_str = json_dict["b64ndarrays"][0]
+    decoded = py_str(base64.b64encode(base64.b64decode(b64_str)))
+    assert b64_str == decoded
+    xlist = tvm.load_json(json_str)
+    np.testing.assert_equal(xlist[0].array.asnumpy(), xx.asnumpy())
+    assert xlist[1].array == xlist[0].array
+
+
 def test_bigendian_rpc_param():
     """Test big endian rpc when there is a PowerPC RPC server available"""
     host = os.environ.get("TVM_POWERPC_TEST_HOST", None)
@@ -60,5 +79,6 @@ def verify_nnvm(remote, target, shape, dtype):
 
 
 if __name__ == "__main__":
+    test_ndarray_reflection()
     test_save_load()
     test_bigendian_rpc_param()
diff --git a/python/tvm/_ffi/_ctypes/function.py b/python/tvm/_ffi/_ctypes/function.py
index 602af3ef858b..79f3c6033a1f 100644
--- a/python/tvm/_ffi/_ctypes/function.py
+++ b/python/tvm/_ffi/_ctypes/function.py
@@ -204,6 +204,7 @@ def _handle_return_func(x):
 # setup return handle for function type
 RETURN_SWITCH[TypeCode.FUNC_HANDLE] = _handle_return_func
 RETURN_SWITCH[TypeCode.MODULE_HANDLE] = _return_module
+RETURN_SWITCH[TypeCode.NDARRAY_CONTAINER] = lambda x: _make_array(x.v_handle, False)
 C_TO_PY_ARG_SWITCH[TypeCode.FUNC_HANDLE] = _wrap_arg_func(
     _handle_return_func, TypeCode.FUNC_HANDLE)
 C_TO_PY_ARG_SWITCH[TypeCode.MODULE_HANDLE] = _wrap_arg_func(
diff --git a/python/tvm/_ffi/_cython/node.pxi b/python/tvm/_ffi/_cython/node.pxi
index 93b3fb124f0a..a563af5237f9 100644
--- a/python/tvm/_ffi/_cython/node.pxi
+++ b/python/tvm/_ffi/_cython/node.pxi
@@ -23,6 +23,8 @@ cdef inline object make_ret_node(void* chandle):
             obj = cls(None)
         else:
             obj = NodeBase(None)
+    else:
+        obj = NodeBase(None)
     (<NodeBase>obj).chandle = chandle
     return obj
 
diff --git a/python/tvm/_ffi/ndarray.py b/python/tvm/_ffi/ndarray.py
index 135701a803c0..3788c07ac440 100644
--- a/python/tvm/_ffi/ndarray.py
+++ b/python/tvm/_ffi/ndarray.py
@@ -134,6 +134,32 @@ def context(self):
         """context of this array"""
         return self.ctx
 
+    def __hash__(self):
+        return ctypes.cast(self.handle, ctypes.c_void_p).value
+
+    def __eq__(self, other):
+        return self.same_as(other)
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def same_as(self, other):
+        """Check object identity equality
+
+        Parameters
+        ----------
+        other : object
+            The other object to compare to
+
+        Returns
+        -------
+        same : bool
+            Whether other is same as self.
+        """
+        if not isinstance(other, NDArrayBase):
+            return False
+        return self.__hash__() == other.__hash__()
+
     def __setitem__(self, in_slice, value):
         """Set ndarray value"""
         if (not isinstance(in_slice, slice) or
diff --git a/src/api/dsl_api.cc b/src/api/dsl_api.cc
index 4e247ed2bf4c..80d7c3163e10 100644
--- a/src/api/dsl_api.cc
+++ b/src/api/dsl_api.cc
@@ -32,7 +32,7 @@ using TVMAPINode = std::shared_ptr<Node>;
 struct APIAttrGetter : public AttrVisitor {
   std::string skey;
   TVMRetValue* ret;
-  bool found_node_ref{false};
+  bool found_ref_object{false};
 
   void Visit(const char* key, double* value) final {
     if (skey == key) *ret = value[0];
@@ -63,7 +63,13 @@ struct APIAttrGetter : public AttrVisitor {
   void Visit(const char* key, NodeRef* value) final {
     if (skey == key) {
       *ret = value[0];
-      found_node_ref = true;
+      found_ref_object = true;
+    }
+  }
+  void Visit(const char* key, runtime::NDArray* value) final {
+    if (skey == key) {
+      *ret = value[0];
+      found_ref_object = true;
     }
   }
 };
@@ -98,6 +104,9 @@ struct APIAttrDir : public AttrVisitor {
   void Visit(const char* key, NodeRef* value) final {
     names->push_back(key);
   }
+  void Visit(const char* key, runtime::NDArray* value) final {
+    names->push_back(key);
+  }
 };
 
 class DSLAPIImpl : public DSLAPI {
@@ -130,7 +139,7 @@ class DSLAPIImpl : public DSLAPI {
       *ret_success = 1;
     } else {
       (*tnode)->VisitAttrs(&getter);
-      *ret_success = getter.found_node_ref || rv.type_code() != kNull;
+      *ret_success = getter.found_ref_object || rv.type_code() != kNull;
       if (rv.type_code() == kStr ||
           rv.type_code() == kTVMType) {
         TVMAPIThreadLocalEntry *e = TVMAPIThreadLocalStore::Get();
diff --git a/src/common/base64.h b/src/common/base64.h
new file mode 100644
index 000000000000..31b02d3ca2a3
--- /dev/null
+++ b/src/common/base64.h
@@ -0,0 +1,284 @@
+/*!
+ * Copyright 2018 by Contributors
+ *
+ * \file base64.h
+ * \brief data stream support to input and output from/to base64 stream
+ *   base64 is easier to store and pass as text format in mapreduce
+ */
+#ifndef TVM_COMMON_BASE64_H_
+#define TVM_COMMON_BASE64_H_
+
+#include <dmlc/logging.h>
+#include <dmlc/logging.h>
+#include <cctype>
+#include <cstdio>
+#include <string>
+
+namespace tvm {
+namespace common {
+/*! \brief namespace of base64 decoding and encoding table */
+namespace base64 {
+// decoding table
+const char DecodeTable[] = {
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+  62,  // '+'
+  0, 0, 0,
+  63,  // '/'
+  52, 53, 54, 55, 56, 57, 58, 59, 60, 61,  // '0'-'9'
+  0, 0, 0, 0, 0, 0, 0,
+  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
+  13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,  // 'A'-'Z'
+  0, 0, 0, 0, 0, 0,
+  26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
+  39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,  // 'a'-'z'
+};
+// encoding table
+static const char EncodeTable[] =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+}  // namespace base64
+
+/*!
+ * \brief Buffer reader from stream to avoid
+ *  virtual call overhead on each read.
+ */
+class StreamBufferReader {
+ public:
+  explicit StreamBufferReader(size_t buffer_size) {
+    buffer_.resize(buffer_size);
+  }
+  /*!
+   * \brief set input stream
+   * \param stream The stream to be set
+   */
+  void set_stream(dmlc::Stream *stream) {
+    stream_ = stream;
+    read_len_ = read_ptr_ = 1;
+  }
+  /*!
+   * \return allows quick read using get char
+   */
+  char GetChar() {
+    while (true) {
+      if (read_ptr_ < read_len_) {
+        return buffer_[read_ptr_++];
+      } else {
+        read_len_ = stream_->Read(&buffer_[0], buffer_.length());
+        if (read_len_ == 0) return EOF;
+        read_ptr_ = 0;
+      }
+    }
+  }
+  /*! \return whether we are reaching the end of file */
+  bool AtEnd() const {
+    return read_len_ == 0;
+  }
+
+ private:
+  /*! \brief the underlying stream */
+  dmlc::Stream *stream_{nullptr};
+  /*! \brief buffer to hold data */
+  std::string buffer_;
+  /*! \brief length of valid data in buffer */
+  size_t read_len_{1};
+  /*! \brief pointer in the buffer */
+  size_t read_ptr_{1};
+};
+
+/*!
+ * \brief Input stream from base64 encoding
+ */
+class Base64InStream: public dmlc::Stream {
+ public:
+  explicit Base64InStream(dmlc::Stream *fs) : reader_(256) {
+    reader_.set_stream(fs);
+  }
+  /*!
+   * \brief initialize the stream position to beginning of next base64 stream
+   * \note call this function before actually start read
+   */
+  void InitPosition(void) {
+    // get a character
+    do {
+      temp_ch_ = reader_.GetChar();
+    } while (isspace(temp_ch_));
+  }
+  /*! \brief whether current position is end of a base64 stream */
+  bool IsEOF(void) const {
+    return num_prev_ == 0 && (temp_ch_ == EOF || isspace(temp_ch_));
+  }
+  // override read function.
+  virtual size_t Read(void *ptr, size_t size) {
+    using base64::DecodeTable;
+    if (size == 0) return 0;
+    // use tlen to record left size
+    size_t tlen = size;
+    unsigned char *cptr = static_cast<unsigned char*>(ptr);
+    // if anything left, load from previous buffered result
+    if (num_prev_ != 0) {
+      if (num_prev_ == 2) {
+        if (tlen >= 2) {
+          *cptr++ = buf_prev[0];
+          *cptr++ = buf_prev[1];
+          tlen -= 2;
+          num_prev_ = 0;
+        } else {
+          // assert tlen == 1
+          *cptr++ = buf_prev[0]; --tlen;
+          buf_prev[0] = buf_prev[1];
+          num_prev_ = 1;
+        }
+      } else {
+        // assert num_prev_ == 1
+        *cptr++ = buf_prev[0]; --tlen; num_prev_ = 0;
+      }
+    }
+    if (tlen == 0) return size;
+    int nvalue;
+    // note: everything goes with 4 bytes in Base64
+    // so we process 4 bytes a unit
+    while (tlen && temp_ch_ != EOF && !isspace(temp_ch_)) {
+      // first byte
+      nvalue = DecodeTable[temp_ch_] << 18;
+      {
+        // second byte
+        temp_ch_ = reader_.GetChar();
+        CHECK(temp_ch_ != EOF && !isspace(temp_ch_)) << "invalid base64 format";
+        nvalue |= DecodeTable[temp_ch_] << 12;
+        *cptr++ = (nvalue >> 16) & 0xFF; --tlen;
+        }
+      {
+        // third byte
+        temp_ch_ = reader_.GetChar();
+        CHECK(temp_ch_ != EOF && !isspace(temp_ch_)) << "invalid base64 format";
+        // handle termination
+        if (temp_ch_ == '=') {
+          temp_ch_ = reader_.GetChar();
+          CHECK(temp_ch_ == '=') << "invalid base64 format";
+          temp_ch_ = reader_.GetChar();
+          CHECK(temp_ch_ == EOF || isspace(temp_ch_))
+              << "invalid base64 format";
+          break;
+        }
+        nvalue |= DecodeTable[temp_ch_] << 6;
+        if (tlen) {
+          *cptr++ = (nvalue >> 8) & 0xFF; --tlen;
+        } else {
+          buf_prev[num_prev_++] = (nvalue >> 8) & 0xFF;
+        }
+      }
+      {
+        // fourth byte
+        temp_ch_ = reader_.GetChar();
+        CHECK(temp_ch_ != EOF && !isspace(temp_ch_))
+            << "invalid base64 format";
+        if (temp_ch_ == '=') {
+          temp_ch_ = reader_.GetChar();
+          CHECK(temp_ch_ == EOF || isspace(temp_ch_))
+              << "invalid base64 format";
+          break;
+        }
+        nvalue |= DecodeTable[temp_ch_];
+        if (tlen) {
+          *cptr++ = nvalue & 0xFF; --tlen;
+        } else {
+          buf_prev[num_prev_ ++] = nvalue & 0xFF;
+        }
+      }
+      // get next char
+      temp_ch_ = reader_.GetChar();
+    }
+    if (kStrictCheck) {
+      CHECK_EQ(tlen, 0) << "Base64InStream: read incomplete";
+    }
+    return size - tlen;
+  }
+  virtual void Write(const void *ptr, size_t size) {
+    LOG(FATAL) << "Base64InStream do not support write";
+  }
+
+ private:
+  // internal reader
+  StreamBufferReader reader_;
+  int temp_ch_{0};
+  int num_prev_{0};
+  unsigned char buf_prev[2];
+  // whether we need to do strict check
+  static const bool kStrictCheck = false;
+};
+
+/*!
+ * \brief Stream to write to base64 format.
+ */
+class Base64OutStream: public dmlc::Stream {
+ public:
+  explicit Base64OutStream(dmlc::Stream *fp) : fp_(fp) {
+  }
+  virtual void Write(const void *ptr, size_t size) {
+    using base64::EncodeTable;
+    size_t tlen = size;
+    const unsigned char *cptr = static_cast<const unsigned char*>(ptr);
+    while (tlen) {
+      while (buf__top_ < 3  && tlen != 0) {
+        buf_[++buf__top_] = *cptr++; --tlen;
+      }
+      if (buf__top_ == 3) {
+        // flush 4 bytes out
+        PutChar(EncodeTable[buf_[1] >> 2]);
+        PutChar(EncodeTable[((buf_[1] << 4) | (buf_[2] >> 4)) & 0x3F]);
+        PutChar(EncodeTable[((buf_[2] << 2) | (buf_[3] >> 6)) & 0x3F]);
+        PutChar(EncodeTable[buf_[3] & 0x3F]);
+        buf__top_ = 0;
+      }
+    }
+  }
+  virtual size_t Read(void *ptr, size_t size) {
+    LOG(FATAL) << "Base64OutStream do not support read";
+    return 0;
+  }
+  /*!
+   * \brief finish writing of all current base64 stream, do some post processing
+   * \param endch character to put to end of stream, if it is EOF, then nothing will be appended.
+   */
+  void Finish(char endch = EOF) {
+    using base64::EncodeTable;
+    if (buf__top_ == 1) {
+      PutChar(EncodeTable[buf_[1] >> 2]);
+      PutChar(EncodeTable[(buf_[1] << 4) & 0x3F]);
+      PutChar('=');
+      PutChar('=');
+    }
+    if (buf__top_ == 2) {
+      PutChar(EncodeTable[buf_[1] >> 2]);
+      PutChar(EncodeTable[((buf_[1] << 4) | (buf_[2] >> 4)) & 0x3F]);
+      PutChar(EncodeTable[(buf_[2] << 2) & 0x3F]);
+      PutChar('=');
+    }
+    buf__top_ = 0;
+    if (endch != EOF) PutChar(endch);
+    this->Flush();
+  }
+
+ private:
+  static constexpr size_t kBufferSize = 256;
+
+  dmlc::Stream *fp_{nullptr};
+  int buf__top_{0};
+  unsigned char buf_[4];
+  std::string out_buf_;
+
+
+  void PutChar(char ch) {
+    out_buf_ += ch;
+    if (out_buf_.length() >= kBufferSize) Flush();
+  }
+  void Flush(void) {
+    if (out_buf_.length() != 0) {
+      fp_->Write(&out_buf_[0], out_buf_.length());
+      out_buf_.clear();
+    }
+  }
+};
+}  // namespace common
+}  // namespace tvm
+#endif  // TVM_COMMON_BASE64_H_
diff --git a/src/lang/reflection.cc b/src/lang/reflection.cc
index d0d8570067eb..7c4e862f0abb 100644
--- a/src/lang/reflection.cc
+++ b/src/lang/reflection.cc
@@ -7,8 +7,11 @@
 #include <tvm/expr.h>
 #include <tvm/container.h>
 #include <tvm/packed_func_ext.h>
+#include <tvm/runtime/ndarray.h>
 #include <dmlc/json.h>
+#include <dmlc/memory_io.h>
 #include <string>
+#include "../common/base64.h"
 
 namespace dmlc {
 DMLC_REGISTRY_ENABLE(::tvm::NodeFactoryReg);
@@ -23,6 +26,7 @@ inline std::string Type2String(const Type& t) {
   return os.str();
 }
 
+
 inline Type String2Type(std::string s) {
   std::istringstream is(s);
   halideir_type_code_t code = Type::Int;
@@ -52,6 +56,8 @@ class NodeIndexer : public AttrVisitor {
  public:
   std::unordered_map<Node*, size_t> node_index{{nullptr, 0}};
   std::vector<Node*> node_list{nullptr};
+  std::unordered_map<DLTensor*, size_t> tensor_index;
+  std::vector<DLTensor*> tensor_list;
 
   void Visit(const char* key, double* value) final {}
   void Visit(const char* key, int64_t* value) final {}
@@ -64,7 +70,13 @@ class NodeIndexer : public AttrVisitor {
   void Visit(const char* key, NodeRef* value) final {
     MakeIndex(value->node_.get());
   }
-
+  void Visit(const char* key, runtime::NDArray* value) final {
+    DLTensor* ptr = const_cast<DLTensor*>((*value).operator->());
+    if (tensor_index.count(ptr)) return;
+    CHECK_EQ(tensor_index.size(), tensor_list.size());
+    tensor_index[ptr] = tensor_list.size();
+    tensor_list.push_back(ptr);
+  }
   // make index of all the children of node
   void MakeIndex(Node* node) {
     if (node == nullptr) return;
@@ -140,6 +152,7 @@ struct JSONNode {
 class JSONAttrGetter : public AttrVisitor {
  public:
   const std::unordered_map<Node*, size_t>* node_index_;
+  const std::unordered_map<DLTensor*, size_t>* tensor_index_;
   JSONNode* node_;
 
   void Visit(const char* key, double* value) final {
@@ -170,6 +183,10 @@ class JSONAttrGetter : public AttrVisitor {
     node_->attrs[key] = std::to_string(
         node_index_->at(value->node_.get()));
   }
+  void Visit(const char* key, runtime::NDArray* value) final {
+    node_->attrs[key] = std::to_string(
+        tensor_index_->at(const_cast<DLTensor*>((*value).operator->())));
+  }
   // Get the node
   void Get(Node* node) {
     if (node == nullptr) {
@@ -209,6 +226,7 @@ class JSONAttrGetter : public AttrVisitor {
 class JSONAttrSetter : public AttrVisitor {
  public:
   const std::vector<std::shared_ptr<Node> >* node_list_;
+  const std::vector<runtime::NDArray>* tensor_list_;
   JSONNode* node_;
 
   std::string GetValue(const char* key) const {
@@ -254,10 +272,16 @@ class JSONAttrSetter : public AttrVisitor {
   void Visit(const char* key, NodeRef* value) final {
     size_t index;
     ParseValue(key, &index);
+    CHECK_LE(index, node_list_->size());
     value->node_ = node_list_->at(index);
   }
-
-  // Get the node
+  void Visit(const char* key, runtime::NDArray* value) final {
+    size_t index;
+    ParseValue(key, &index);
+    CHECK_LE(index, tensor_list_->size());
+    *value = tensor_list_->at(index);
+  }
+  // set node to be current JSONNode
   void Set(Node* node) {
     if (node == nullptr) return;
     if (node->is_type<ArrayNode>()) {
@@ -292,6 +316,8 @@ struct JSONGraph {
   size_t root;
   // the nodes of the graph
   std::vector<JSONNode> nodes;
+  // base64 b64ndarrays of arrays
+  std::vector<std::string> b64ndarrays;
   // global attributes
   AttrMap attrs;
 
@@ -299,6 +325,7 @@ struct JSONGraph {
     writer->BeginObject();
     writer->WriteObjectKeyValue("root", root);
     writer->WriteObjectKeyValue("nodes", nodes);
+    writer->WriteObjectKeyValue("b64ndarrays", b64ndarrays);
     if (attrs.size() != 0) {
       writer->WriteObjectKeyValue("attrs", attrs);
     }
@@ -310,6 +337,7 @@ struct JSONGraph {
     dmlc::JSONObjectReadHelper helper;
     helper.DeclareField("root", &root);
     helper.DeclareField("nodes", &nodes);
+    helper.DeclareOptionalField("b64ndarrays", &b64ndarrays);
     helper.DeclareOptionalField("attrs", &attrs);
     helper.ReadAllFields(reader);
   }
@@ -320,6 +348,7 @@ struct JSONGraph {
     indexer.MakeIndex(root.node_.get());
     JSONAttrGetter getter;
     getter.node_index_ = &indexer.node_index;
+    getter.tensor_index_ = &indexer.tensor_index;
     for (Node* n : indexer.node_list) {
       JSONNode jnode;
       getter.node_ = &jnode;
@@ -328,6 +357,15 @@ struct JSONGraph {
     }
     g.attrs["tvm_version"] = TVM_VERSION;
     g.root = indexer.node_index.at(root.node_.get());
+    // serialize tensor
+    for (DLTensor* tensor : indexer.tensor_list) {
+      std::string blob;
+      dmlc::MemoryStringStream mstrm(&blob);
+      common::Base64OutStream b64strm(&mstrm);
+      runtime::SaveDLTensor(&b64strm, tensor);
+      b64strm.Finish();
+      g.b64ndarrays.emplace_back(std::move(blob));
+    }
     return g;
   }
 };
@@ -347,6 +385,16 @@ std::shared_ptr<Node> LoadJSON_(std::string json_str) {
   // load in json graph.
   jgraph.Load(&reader);
   std::vector<std::shared_ptr<Node> > nodes;
+  std::vector<runtime::NDArray> tensors;
+  // load in tensors
+  for (const std::string& blob : jgraph.b64ndarrays) {
+    dmlc::MemoryStringStream mstrm(const_cast<std::string*>(&blob));
+    common::Base64InStream b64strm(&mstrm);
+    b64strm.InitPosition();
+    runtime::NDArray temp;
+    CHECK(temp.Load(&b64strm));
+    tensors.emplace_back(temp);
+  }
   // node 0 is always null
   nodes.reserve(jgraph.nodes.size());
   for (const JSONNode& jnode : jgraph.nodes) {
@@ -362,6 +410,7 @@ std::shared_ptr<Node> LoadJSON_(std::string json_str) {
   CHECK_EQ(nodes.size(), jgraph.nodes.size());
   JSONAttrSetter setter;
   setter.node_list_ = &nodes;
+  setter.tensor_list_ = &tensors;
 
   for (size_t i = 0; i < nodes.size(); ++i) {
     setter.node_ = &jgraph.nodes[i];
@@ -402,6 +451,9 @@ class NodeAttrSetter : public AttrVisitor {
   void Visit(const char* key, NodeRef* value) final {
     *value = GetAttr(key).operator NodeRef();
   }
+  void Visit(const char* key, runtime::NDArray* value) final {
+    *value = GetAttr(key).operator runtime::NDArray();
+  }
 
  private:
   runtime::TVMArgValue GetAttr(const char* key) {
diff --git a/src/runtime/graph/graph_runtime.cc b/src/runtime/graph/graph_runtime.cc
index 89d5e7a28258..7a75771af23b 100644
--- a/src/runtime/graph/graph_runtime.cc
+++ b/src/runtime/graph/graph_runtime.cc
@@ -4,7 +4,7 @@
  */
 #include <tvm/runtime/packed_func.h>
 #include <tvm/runtime/registry.h>
-#include <tvm/runtime/serializer.h>
+#include <tvm/runtime/ndarray.h>
 #include <dmlc/memory_io.h>
 #include <dmlc/json.h>
 #include <numeric>
@@ -399,52 +399,9 @@ class GraphRuntime : public ModuleNode {
 
 void GraphRuntime::LoadDLTensor(dmlc::Stream* strm, DLTensor* dst) {
   // always use strm->Read to maintain endianness conversion
-  uint64_t header, reserved;
-  CHECK(strm->Read(&header))
-      << "Invalid DLTensor file format";
-  CHECK(strm->Read(&reserved))
-      << "Invalid DLTensor file format";
-  CHECK(header == kTVMNDArrayMagic)
-      << "Invalid DLTensor file format";
-
-  DLTensor tensor;
-  CHECK(strm->Read(&(tensor.ctx)))
-      << "Invalid DLTensor file format";
-  CHECK(strm->Read(&(tensor.ndim)))
-      << "Invalid DLTensor file format";
-  CHECK(strm->Read(&(tensor.dtype)))
-      << "Invalid DLTensor file format";
-  std::vector<int64_t> shape(tensor.ndim);
-  if (tensor.ndim != 0) {
-    CHECK(strm->ReadArray(&shape[0], tensor.ndim))
-        << "Invalid DLTensor file format";
-  }
-  CHECK_EQ(tensor.ndim, dst->ndim) << "param dimension mismatch";
-  CHECK(tensor.dtype.bits == dst->dtype.bits &&
-        tensor.dtype.code == dst->dtype.code &&
-        tensor.dtype.lanes == dst->dtype.lanes) << "param type mismatch";
-  for (int i = 0; i < tensor.ndim; ++i) {
-    CHECK_EQ(shape[i], dst->shape[i]) << "param shape mismatch";
-  }
-  size_t bits = dst->dtype.bits * dst->dtype.lanes;
-  size_t elem_bytes = (bits + 7) / 8;
-  size_t num_elems = 1;
-  for (int i = 0; i < dst->ndim; ++i) {
-    num_elems *= dst->shape[i];
-  }
-  uint64_t data_byte_size;
-  CHECK(strm->Read(&data_byte_size))
-      << "Invalid DLTensor file format";
-  CHECK_EQ(data_byte_size, elem_bytes * num_elems)
-      << "Invalid DLTensor file format";
-  std::vector<uint8_t> bytes(data_byte_size + 1);
-  CHECK(strm->Read(&bytes[0], data_byte_size))
-      << "Invalid DLTensor file format";
-  // explicitly swap endian when necessary.
-  if (!DMLC_IO_NO_ENDIAN_SWAP) {
-    dmlc::ByteSwap(&bytes[0], elem_bytes, num_elems);
-  }
-  TVM_CCALL(TVMArrayCopyFromBytes(dst, &bytes[0], data_byte_size));
+  NDArray temp;
+  temp.Load(strm);
+  temp.CopyTo(dst);
 }
 
 void GraphRuntime::LoadParams(dmlc::Stream* strm) {
diff --git a/src/runtime/graph/graph_runtime.h b/src/runtime/graph/graph_runtime.h
index 8e2590dc6359..7ebcf7d30b33 100644
--- a/src/runtime/graph/graph_runtime.h
+++ b/src/runtime/graph/graph_runtime.h
@@ -13,8 +13,6 @@
 namespace tvm {
 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;
 

From be08b812200f5f243a1534768211a5293cec89d4 Mon Sep 17 00:00:00 2001
From: Tatsuya Nishiyama <nishiyama.tatsuya0@gmail.com>
Date: Thu, 19 Jul 2018 12:59:37 +0900
Subject: [PATCH 888/949] Fix runtime error on osx (#1449)

---
 python/setup.py | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/python/setup.py b/python/setup.py
index 5ae7a6d909e4..cbf8c5591703 100644
--- a/python/setup.py
+++ b/python/setup.py
@@ -104,10 +104,11 @@ def is_pure(self):
 
 # For bdist_wheel only
 if wheel_include_libs:
-    for path in LIB_LIST:
-        shutil.copy(path, os.path.join(CURRENT_DIR, 'tvm'))
-        _, libname = os.path.split(path)
-        fo.write("include tvm/%s\n" % libname)
+    with open("MANIFEST.in", "w") as fo:
+        for path in LIB_LIST:
+            shutil.copy(path, os.path.join(CURRENT_DIR, 'tvm'))
+            _, libname = os.path.split(path)
+            fo.write("include tvm/%s\n" % libname)
     setup_kwargs = {
         "include_package_data": True
     }
@@ -118,7 +119,7 @@ def is_pure(self):
         LIB_LIST[i] = os.path.relpath(path, curr_path)
     setup_kwargs = {
         "include_package_data": True,
-        "package_data": {'tvm': LIB_LIST}
+        "data_files": [('tvm', LIB_LIST)]
     }
 
 setup(name='tvm',

From 525eeff395065683bb7d32fda6c3decdd0cacbc8 Mon Sep 17 00:00:00 2001
From: Siva <sivar.b@huawei.com>
Date: Thu, 19 Jul 2018 22:28:12 +0530
Subject: [PATCH 889/949] Tutorial enhancement to keep it clean on docs.tvm.ai
 (#1450)

---
 nnvm/python/nnvm/frontend/tensorflow.py |  2 +-
 tutorials/nnvm/from_tensorflow.py       | 86 ++++++++++++++++++-------
 2 files changed, 64 insertions(+), 24 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/tensorflow.py b/nnvm/python/nnvm/frontend/tensorflow.py
index 31ee0a8b116f..67da035075c7 100644
--- a/nnvm/python/nnvm/frontend/tensorflow.py
+++ b/nnvm/python/nnvm/frontend/tensorflow.py
@@ -620,7 +620,7 @@ def from_tensorflow(self, graph):
                     attr['_input_shapes'] = input_shapes
                 except KeyError:
                     # TODO: Need to find clean way to handle '^CheckNumerics'
-                    print("Some Exception while inputs list:", node.input, " ignoring...")
+                    pass
 
                 inputs = self._fix_extranodes(node.op, attr, inputs)
 
diff --git a/tutorials/nnvm/from_tensorflow.py b/tutorials/nnvm/from_tensorflow.py
index 34afd0b2ef62..ee025c5b09ff 100644
--- a/tutorials/nnvm/from_tensorflow.py
+++ b/tutorials/nnvm/from_tensorflow.py
@@ -1,17 +1,20 @@
 """
 Compile Tensorflow Models
 =========================
-This article is an introductory tutorial to deploy tensorflow models with NNVM.
+This article is an introductory tutorial to deploy tensorflow models with TVM.
 
-For us to begin with, tensorflow module is required to be installed.
+For us to begin with, tensorflow python module is required to be installed.
 
-A quick solution is to install tensorlfow from
+A quick solution is to install tensorflow from
 
-https://www.tensorflow.org/install/install_sources
+https://www.tensorflow.org/install
 """
 
+# tvm and nnvm
 import nnvm
 import tvm
+
+# os and numpy
 import numpy as np
 import os.path
 
@@ -21,23 +24,39 @@
 from tensorflow.python.framework import dtypes
 from tensorflow.python.framework import tensor_util
 
+# Tensorflow utility functions
 import nnvm.testing.tf
 
+# Base location for model related files.
 repo_base = 'https://github.com/dmlc/web-data/raw/master/tensorflow/models/InceptionV1/'
+
+# Test image
 img_name = 'elephant-299.jpg'
 image_url = os.path.join(repo_base, img_name)
+
+# InceptionV1 model protobuf
+# .. note::
+#
+#   protobuf should be exported with :any:`add_shapes=True` option.
+#   Could use https://github.com/dmlc/web-data/tree/master/tensorflow/scripts/tf-to-nnvm.py
+#   to add shapes for existing models.
+#
 model_name = 'classify_image_graph_def-with_shapes.pb'
 model_url = os.path.join(repo_base, model_name)
+
+# Image label map
 map_proto = 'imagenet_2012_challenge_label_map_proto.pbtxt'
 map_proto_url = os.path.join(repo_base, map_proto)
+
+# Human readable text for labels
 lable_map = 'imagenet_synset_to_human_label_map.txt'
 lable_map_url = os.path.join(repo_base, lable_map)
 
 
 ######################################################################
-# Download processed tensorflow model
-# -----------------------------------
-# In this section, we download a pretrained Tensorflow model and classify an image.
+# Download required files
+# -----------------------
+# Download files listed above.
 from mxnet.gluon.utils import download
 
 download(image_url, img_name)
@@ -47,11 +66,11 @@
 
 
 ######################################################################
-# Creates graph from saved graph_def.pb.
-# --------------------------------------
+# Import model
+# ------------
+# Creates tensorflow graph definition from protobuf file.
 
-with tf.gfile.FastGFile(os.path.join(
-        "./", model_name), 'rb') as f:
+with tf.gfile.FastGFile(os.path.join("./", model_name), 'rb') as f:
     graph_def = tf.GraphDef()
     graph_def.ParseFromString(f.read())
     graph = tf.import_graph_def(graph_def, name='')
@@ -62,22 +81,39 @@
 ######################################################################
 # Decode image
 # ------------
+# .. note::
+#
+#   tensorflow frontend import doesn't support preprocessing ops like JpegDecode
+#   JpegDecode is bypassed (just return source node).
+#   Hence we supply decoded frame to TVM instead.
+#
+
 from PIL import Image
 image = Image.open(img_name).resize((299, 299))
 
-def transform_image(image):
-    image = np.array(image)
-    return image
-
-x = transform_image(image)
+x = np.array(image)
 
 ######################################################################
 # Import the graph to NNVM
 # ------------------------
+# Import tensorflow graph definition to nnvm.
+#
+# Results:
+#   sym: nnvm graph for given tensorflow protobuf.
+#   params: params converted from tensorflow params (tensor protobuf).
 sym, params = nnvm.frontend.from_tensorflow(graph_def)
 
+print ("Tensorflow protobuf imported as nnvm graph")
 ######################################################################
-# Now compile the graph through NNVM
+# NNVM Compilation
+# ----------------
+# Compile the graph to llvm target with given input specification.
+#
+# Results:
+#   graph: Final graph after compilation.
+#   params: final params after compilation.
+#   lib: target library which can be deployed on target with tvm runtime.
+
 import nnvm.compiler
 target = 'llvm'
 shape_dict = {'DecodeJpeg/contents': x.shape}
@@ -87,7 +123,8 @@ def transform_image(image):
 ######################################################################
 # Execute the portable graph on TVM
 # ---------------------------------
-# Now, we would like to reproduce the same forward computation using TVM.
+# Now we can try deploying the NNVM compiled model on cpu target.
+
 from tvm.contrib import graph_runtime
 ctx = tvm.cpu(0)
 dtype = 'uint8'
@@ -100,10 +137,10 @@ def transform_image(image):
 # get outputs
 tvm_output = m.get_output(0, tvm.nd.empty(((1, 1008)), 'float32'))
 
-
 ######################################################################
-# Process the output to human readable
-# ------------------------------------
+# Process the output
+# ------------------
+# Process the model output to human readable text for InceptionV1.
 predictions = tvm_output.asnumpy()
 predictions = np.squeeze(predictions)
 
@@ -111,6 +148,7 @@ def transform_image(image):
 node_lookup = nnvm.testing.tf.NodeLookup(label_lookup_path=os.path.join("./", map_proto),
                                          uid_lookup_path=os.path.join("./", lable_map))
 
+# Print top 5 predictions from TVM output.
 top_k = predictions.argsort()[-5:][::-1]
 for node_id in top_k:
     human_string = node_lookup.id_to_string(node_id)
@@ -118,8 +156,9 @@ def transform_image(image):
     print('%s (score = %.5f)' % (human_string, score))
 
 ######################################################################
-# Run the same graph with tensorflow and dump output.
-# ---------------------------------------------------
+# Inference on tensorflow
+# -----------------------
+# Run the corresponding model on tensorflow
 
 def create_graph():
     """Creates a graph from saved GraphDef file and returns a saver."""
@@ -161,6 +200,7 @@ def run_inference_on_image(image):
         node_lookup = nnvm.testing.tf.NodeLookup(label_lookup_path=os.path.join("./", map_proto),
                                                  uid_lookup_path=os.path.join("./", lable_map))
 
+        # Print top 5 predictions from tensorflow.
         top_k = predictions.argsort()[-5:][::-1]
         print ("===== TENSORFLOW RESULTS =======")
         for node_id in top_k:

From bf6d2441ccf958bca7eb938d8cd7cad83583600f Mon Sep 17 00:00:00 2001
From: Siju <sijusamuel@gmail.com>
Date: Thu, 19 Jul 2018 22:50:21 +0530
Subject: [PATCH 890/949] [DARKNET]RNN Support for darknet (#1443)

---
 nnvm/python/nnvm/frontend/darknet.py          | 479 ++++++++++--------
 nnvm/python/nnvm/testing/darknet.py           |   3 +
 .../python/frontend/darknet/test_forward.py   | 110 ++--
 tutorials/nnvm/nlp/from_darknet_rnn.py        | 184 +++++++
 4 files changed, 532 insertions(+), 244 deletions(-)
 create mode 100644 tutorials/nnvm/nlp/from_darknet_rnn.py

diff --git a/nnvm/python/nnvm/frontend/darknet.py b/nnvm/python/nnvm/frontend/darknet.py
index 878b31846406..49d608137212 100644
--- a/nnvm/python/nnvm/frontend/darknet.py
+++ b/nnvm/python/nnvm/frontend/darknet.py
@@ -227,8 +227,7 @@ def _darknet_dense(inputs, attrs):
     op_name, new_attrs = 'dense', {}
     new_attrs['units'] = _darknet_required_attr(attrs, 'num_hidden')
     out_name = {}
-    if attrs.get('use_bias', False) is True:
-        new_attrs['use_bias'] = True
+    new_attrs['use_bias'] = attrs.get('use_bias', False)
     if attrs.get('use_flatten', False) is True:
         inputs[0] = _sym.flatten(inputs[0])
     sym = _darknet_get_nnvm_op(op_name)(*inputs, **new_attrs)
@@ -397,235 +396,301 @@ def _as_list(arr):
         return arr
     return [arr]
 
-def _read_memory_buffer(shape, data, dtype):
-    length = 1
-    for x in shape:
-        length *= x
-    data_np = np.zeros(length, dtype=dtype)
-    for i in range(length):
-        data_np[i] = data[i]
-    return data_np.reshape(shape)
-
-def _get_convolution_weights(layer, opname, params, dtype):
-    """Get the convolution layer weights and biases."""
-    if layer.nweights == 0:
-        return
 
-    if (layer.n * layer.c * layer.size * layer.size) != layer.nweights:
-        raise RuntimeError("layer weights size not matching with n c h w")
+class GraphProto(object):
+    """A helper class for handling nnvm graph copying from darknet model.
+    """
 
-    weights = _read_memory_buffer((layer.n, layer.c, layer.size, layer.size), layer.weights, dtype)
+    def __init__(self, net, dtype='float32'):
+        self.net = net
+        self.dtype = dtype
+        self._sym_array = {}
+        self._tvmparams = {}
+        self._outs = []
+        self._rnn_state_ctr = 0
 
-    biases = _read_memory_buffer((layer.n, ), layer.biases, dtype)
+    def _read_memory_buffer(self, shape, data):
+        length = 1
+        for x in shape:
+            length *= x
+        data_np = np.zeros(length, dtype=self.dtype)
+        for i in range(length):
+            data_np[i] = data[i]
+        return data_np.reshape(shape)
 
-    k = _get_tvm_params_name(opname[0], 'weight')
-    params[k] = tvm.nd.array(weights)
+    def _get_convolution_weights(self, layer, opname):
+        """Get the convolution layer weights and biases."""
+        if layer.nweights == 0:
+            return
 
-    if layer.batch_normalize == 1 and layer.dontloadscales != 1:
-        _get_batchnorm_weights(layer, opname[1], params, layer.n, dtype)
-        k = _get_tvm_params_name(opname[1], 'beta')
-        params[k] = tvm.nd.array(biases)
-    else:
-        k = _get_tvm_params_name(opname[0], 'bias')
-        params[k] = tvm.nd.array(biases)
+        if (layer.n * layer.c * layer.size * layer.size) != layer.nweights:
+            raise RuntimeError("layer weights size not matching with n c h w")
 
-def _get_connected_weights(layer, opname, params, dtype):
-    """Parse the weights and biases for fully connected or dense layer."""
-    size = layer.outputs * layer.inputs
-    if size == 0:
-        return
+        shape = (layer.n, layer.c, layer.size, layer.size)
+        weights = self._read_memory_buffer(shape, layer.weights)
 
-    weights = _read_memory_buffer((layer.outputs, layer.inputs), layer.weights, dtype)
-    biases = _read_memory_buffer((layer.outputs, ), layer.biases, dtype)
+        biases = self._read_memory_buffer((layer.n, ), layer.biases)
 
-    k = _get_tvm_params_name(opname[0], 'weight')
-    params[k] = tvm.nd.array(weights)
+        k = self._get_tvm_params_name(opname[0], 'weight')
+        self._tvmparams[k] = tvm.nd.array(weights)
 
-    if layer.batch_normalize == 1 and layer.dontloadscales != 1:
-        _get_batchnorm_weights(layer, opname[1], params, layer.outputs, dtype)
-        k = _get_tvm_params_name(opname[1], 'beta')
-        params[k] = tvm.nd.array(biases)
-    else:
-        k = _get_tvm_params_name(opname[0], 'bias')
-        params[k] = tvm.nd.array(biases)
-
-def _get_batchnorm_weights(layer, opname, params, size, dtype):
-    """Parse the weights for batchnorm, which includes, scales, moving mean
-    and moving variances."""
-    scales = _read_memory_buffer((size, ), layer.scales, dtype)
-    rolling_mean = _read_memory_buffer((size, ), layer.rolling_mean, dtype)
-    rolling_variance = _read_memory_buffer((size, ), layer.rolling_variance, dtype)
-
-    k = _get_tvm_params_name(opname, 'moving_mean')
-    params[k] = tvm.nd.array(rolling_mean)
-    k = _get_tvm_params_name(opname, 'moving_var')
-    params[k] = tvm.nd.array(rolling_variance)
-    k = _get_tvm_params_name(opname, 'gamma')
-    params[k] = tvm.nd.array(scales)
-
-def _get_darknet_attrs(net, layer_num):
-    """Parse attributes of each layer and return."""
-    attr = {}
-    use_flatten = True
-    layer = net.layers[layer_num]
-    if LAYERTYPE.CONVOLUTIONAL == layer.type:
-        attr.update({'layout' : 'NCHW'})
-        attr.update({'pad' : str(layer.pad)})
-        attr.update({'num_group' : str(layer.groups)})
-        attr.update({'num_filter' : str(layer.n)})
-        attr.update({'stride' : str(layer.stride)})
-        attr.update({'kernel' : str(layer.size)})
-        attr.update({'activation' : (layer.activation)})
-
-        if layer.nbiases == 0:
-            attr.update({'use_bias' : False})
+        if layer.batch_normalize == 1 and layer.dontloadscales != 1:
+            self._get_batchnorm_weights(layer, opname[1], layer.n)
+            k = self._get_tvm_params_name(opname[1], 'beta')
+            self._tvmparams[k] = tvm.nd.array(biases)
         else:
-            attr.update({'use_bias' : True})
+            k = self._get_tvm_params_name(opname[0], 'bias')
+            self._tvmparams[k] = tvm.nd.array(biases)
+
+    def _get_connected_weights(self, layer, opname):
+        """Parse the weights and biases for fully connected or dense layer."""
+        size = layer.outputs * layer.inputs
+        if size == 0:
+            return
+
+        weights = self._read_memory_buffer((layer.outputs, layer.inputs), layer.weights)
+        biases = self._read_memory_buffer((layer.outputs, ), layer.biases)
+
+        k = self._get_tvm_params_name(opname[0], 'weight')
+        self._tvmparams[k] = tvm.nd.array(weights)
 
         if layer.batch_normalize == 1 and layer.dontloadscales != 1:
-            attr.update({'use_batchNorm' : True})
-            attr.update({'use_scales' : True})
-
-    #elif LAYERTYPE.BATCHNORM == layer.type:
-    #    attr.update({'flatten' : str('True')})
-
-    elif LAYERTYPE.CONNECTED == layer.type:
-        attr.update({'num_hidden' : str(layer.outputs)})
-        attr.update({'activation' : (layer.activation)})
-        if layer_num != 0:
-            layer_prev = net.layers[layer_num - 1]
-            if (layer_prev.out_h == layer.h and
-                    layer_prev.out_w == layer.w and
-                    layer_prev.out_c == layer.c):
-                use_flatten = False
-        attr.update({'use_flatten' : use_flatten})
-        if layer.nbiases == 0:
-            attr.update({'use_bias' : False})
+            self._get_batchnorm_weights(layer, opname[1], layer.outputs)
+            k = self._get_tvm_params_name(opname[1], 'beta')
+            self._tvmparams[k] = tvm.nd.array(biases)
         else:
+            k = self._get_tvm_params_name(opname[0], 'bias')
+            self._tvmparams[k] = tvm.nd.array(biases)
+
+    def _get_batchnorm_weights(self, layer, opname, size):
+        """Parse the weights for batchnorm, which includes, scales, moving mean
+        and moving variances."""
+        scales = self._read_memory_buffer((size, ), layer.scales)
+        rolling_mean = self._read_memory_buffer((size, ), layer.rolling_mean)
+        rolling_variance = self._read_memory_buffer((size, ), layer.rolling_variance)
+
+        k = self._get_tvm_params_name(opname, 'moving_mean')
+        self._tvmparams[k] = tvm.nd.array(rolling_mean)
+        k = self._get_tvm_params_name(opname, 'moving_var')
+        self._tvmparams[k] = tvm.nd.array(rolling_variance)
+        k = self._get_tvm_params_name(opname, 'gamma')
+        self._tvmparams[k] = tvm.nd.array(scales)
+
+    def _get_darknet_attrs(self, layer, layer_num):
+        """Parse attributes of each layer and return."""
+        attr = {}
+        use_flatten = True
+        if LAYERTYPE.CONVOLUTIONAL == layer.type:
+            attr.update({'layout' : 'NCHW'})
+            attr.update({'pad' : str(layer.pad)})
+            attr.update({'num_group' : str(layer.groups)})
+            attr.update({'num_filter' : str(layer.n)})
+            attr.update({'stride' : str(layer.stride)})
+            attr.update({'kernel' : str(layer.size)})
+            attr.update({'activation' : (layer.activation)})
+
+            if layer.nbiases == 0:
+                attr.update({'use_bias' : False})
+            else:
+                attr.update({'use_bias' : True})
+
+            if layer.batch_normalize == 1 and layer.dontloadscales != 1:
+                attr.update({'use_batchNorm' : True})
+                attr.update({'use_scales' : True})
+
+        elif LAYERTYPE.CONNECTED == layer.type:
+            attr.update({'num_hidden' : str(layer.outputs)})
+            attr.update({'activation' : (layer.activation)})
+            if layer_num != 0:
+                layer_prev = self.net.layers[layer_num - 1]
+                if (layer_prev.out_h == layer.h and
+                        layer_prev.out_w == layer.w and
+                        layer_prev.out_c == layer.c):
+                    use_flatten = False
+            attr.update({'use_flatten' : use_flatten})
             attr.update({'use_bias' : True})
-        if layer.batch_normalize == 1 and layer.dontloadscales != 1:
-            attr.update({'use_batchNorm' : True})
-            attr.update({'use_scales' : True})
-
-    elif LAYERTYPE.MAXPOOL == layer.type:
-        attr.update({'pad' : str(layer.pad)})
-        attr.update({'stride' : str(layer.stride)})
-        attr.update({'kernel' : str(layer.size)})
-        max_output = (layer.w - layer.size + 2 * layer.pad)/float(layer.stride) + 1
-        if max_output < layer.out_w:
-            extra_pad = (layer.out_w - max_output)*layer.stride
-            attr.update({'extra_pad_size' : int(extra_pad)})
-    elif LAYERTYPE.AVGPOOL == layer.type:
-        attr.update({'pad' : str(layer.pad)})
-        if layer.stride == 0:
-            attr.update({'stride' : str(1)})
-        else:
+            if layer.batch_normalize == 1 and layer.dontloadscales != 1:
+                attr.update({'use_batchNorm' : True})
+                attr.update({'use_scales' : True})
+                attr.update({'use_bias' : False})
+
+        elif LAYERTYPE.MAXPOOL == layer.type:
+            attr.update({'pad' : str(layer.pad)})
             attr.update({'stride' : str(layer.stride)})
-        if layer.size == 0 and layer.h == layer.w:
-            attr.update({'kernel' : str(layer.h)})
-        else:
             attr.update({'kernel' : str(layer.size)})
+            max_output = (layer.w - layer.size + 2 * layer.pad)/float(layer.stride) + 1
+            if max_output < layer.out_w:
+                extra_pad = (layer.out_w - max_output)*layer.stride
+                attr.update({'extra_pad_size' : int(extra_pad)})
+        elif LAYERTYPE.AVGPOOL == layer.type:
+            attr.update({'pad' : str(layer.pad)})
+            if layer.stride == 0:
+                attr.update({'stride' : str(1)})
+            else:
+                attr.update({'stride' : str(layer.stride)})
+            if layer.size == 0 and layer.h == layer.w:
+                attr.update({'kernel' : str(layer.h)})
+            else:
+                attr.update({'kernel' : str(layer.size)})
+
+        elif LAYERTYPE.DROPOUT == layer.type:
+            attr.update({'p' : str(layer.probability)})
+
+        elif LAYERTYPE.SOFTMAX == layer.type:
+            attr.update({'axis' : 1})
+            attr.update({'use_flatten' : True})
+            if layer.temperature:
+                attr.update({'temperature' : str(layer.temperature)})
+
+        elif LAYERTYPE.SHORTCUT == layer.type:
+            add_layer = self.net.layers[layer.index]
+            attr.update({'activation' : (layer.activation)})
+            attr.update({'out_channel' : (layer.out_c)})
+            attr.update({'out_size' : (layer.out_h)})
+            attr.update({'add_out_channel' : (add_layer.out_c)})
+            attr.update({'add_out_size' : (add_layer.out_h)})
+
+        elif LAYERTYPE.ROUTE == layer.type:
+            pass
+
+        elif LAYERTYPE.COST == layer.type:
+            pass
+
+        elif LAYERTYPE.REORG == layer.type:
+            attr.update({'stride' : layer.stride})
+
+        elif LAYERTYPE.REGION == layer.type:
+            attr.update({'n' : layer.n})
+            attr.update({'classes' : layer.classes})
+            attr.update({'coords' : layer.coords})
+            attr.update({'background' : layer.background})
+            attr.update({'softmax' : layer.softmax})
+        else:
+            err = "Darknet layer type {} is not supported in nnvm.".format(layer.type)
+            raise NotImplementedError(err)
 
-    elif LAYERTYPE.DROPOUT == layer.type:
-        attr.update({'p' : str(layer.probability)})
-
-    elif LAYERTYPE.SOFTMAX == layer.type:
-        attr.update({'axis' : 1})
-        attr.update({'use_flatten' : True})
-        if layer.temperature:
-            attr.update({'temperature' : str(layer.temperature)})
-
-    elif LAYERTYPE.SHORTCUT == layer.type:
-        add_layer = net.layers[layer.index]
-        attr.update({'activation' : (layer.activation)})
-        attr.update({'out_channel' : (layer.out_c)})
-        attr.update({'out_size' : (layer.out_h)})
-        attr.update({'add_out_channel' : (add_layer.out_c)})
-        attr.update({'add_out_size' : (add_layer.out_h)})
-
-    elif LAYERTYPE.ROUTE == layer.type:
-        pass
-
-    elif LAYERTYPE.COST == layer.type:
-        pass
-
-    elif LAYERTYPE.REORG == layer.type:
-        attr.update({'stride' : layer.stride})
-
-    elif LAYERTYPE.REGION == layer.type:
-        attr.update({'n' : layer.n})
-        attr.update({'classes' : layer.classes})
-        attr.update({'coords' : layer.coords})
-        attr.update({'background' : layer.background})
-        attr.update({'softmax' : layer.softmax})
-    else:
-        err = "Darknet layer type {} is not supported in nnvm.".format(layer.type)
-        raise NotImplementedError(err)
-
-    return layer.type, attr
-
-def _get_tvm_params_name(opname, arg_name):
-    """Makes the params name for the k,v pair."""
-    return opname + '_'+ arg_name
-
-def _get_darknet_params(layer, opname, tvmparams, dtype='float32'):
-    """To parse and get the darknet params."""
-    if LAYERTYPE.CONVOLUTIONAL == layer.type:
-        _get_convolution_weights(layer, opname, tvmparams, dtype)
-
-    #elif LAYERTYPE.BATCHNORM == layer.type:
-    #   size = layer.outputs
-    #   _get_batchnorm_weights(layer, opname, tvmparams, size, dtype)
-
-    elif LAYERTYPE.CONNECTED == layer.type:
-        _get_connected_weights(layer, opname, tvmparams, dtype)
-
-def _preproc_layer(net, i, sym_array):
-    """To preprocess each darknet layer, some layer doesnt need processing."""
-    layer = net.layers[i]
-    if i == 0:
-        name = 'data'
-        attribute = {}
-        sym = [_sym.Variable(name, **attribute)]
-    else:
-        sym = sym_array[i - 1]
-    skip_layer = False
-
-    if LAYERTYPE.ROUTE == layer.type:
-        sym = []
-        for j in range(layer.n):
-            sym.append(sym_array[layer.input_layers[j]])
-        if layer.n == 1:
+        return attr
+
+    def _get_tvm_params_name(self, opname, arg_name):
+        """Makes the params name for the k,v pair."""
+        return opname + '_'+ arg_name
+
+    def _get_darknet_params(self, layer, opname):
+        """To parse and get the darknet params."""
+        if LAYERTYPE.CONVOLUTIONAL == layer.type:
+            self._get_convolution_weights(layer, opname)
+
+        elif LAYERTYPE.CONNECTED == layer.type:
+            self._get_connected_weights(layer, opname)
+
+    def _preproc_layer(self, layer, layer_num):
+        """To preprocess each darknet layer, some layer doesnt need processing."""
+        if layer_num == 0:
+            name = 'data'
+            attribute = {}
+            sym = [_sym.Variable(name, **attribute)]
+        else:
+            sym = self._sym_array[layer_num - 1]
+        skip_layer = False
+
+        if LAYERTYPE.ROUTE == layer.type:
+            sym = []
+            for j in range(layer.n):
+                sym.append(self._sym_array[layer.input_layers[j]])
+            if layer.n == 1:
+                skip_layer = True
+
+        elif LAYERTYPE.COST == layer.type:
             skip_layer = True
 
-    elif LAYERTYPE.COST == layer.type:
-        skip_layer = True
+        elif LAYERTYPE.SHORTCUT == layer.type:
+            sym = [sym, self._sym_array[layer.index]]
 
-    elif LAYERTYPE.SHORTCUT == layer.type:
-        sym = [sym, sym_array[layer.index]]
+        elif LAYERTYPE.BLANK == layer.type:
+            skip_layer = True
 
-    elif LAYERTYPE.BLANK == layer.type:
-        skip_layer = True
+        if skip_layer is True:
+            self._sym_array[layer_num] = sym
 
-    if skip_layer is True:
-        sym_array[i] = sym
+        return skip_layer, sym
 
-    return skip_layer, sym
+    def _get_opname(self, layer):
+        """Returs the layer name."""
+        return layer.type
 
-def _from_darknet(net, dtype='float32'):
-    """To convert the darknet symbol to nnvm symbols."""
-    sym_array = {}
-    tvmparams = {}
-    for i in range(net.n):
-        need_skip, sym = _preproc_layer(net, i, sym_array)
-        if need_skip is True:
-            continue
-        op_name, attr = _get_darknet_attrs(net, i)
-        layer_name, sym = _darknet_convert_symbol(op_name, _as_list(sym), attr)
-        _get_darknet_params(net.layers[i], layer_name, tvmparams, dtype)
-        sym_array[i] = sym
+    def _new_rnn_state_sym(self, state=None):
+        """Returs a symbol for state"""
+        name = "rnn%d_state" % (self._rnn_state_ctr)
+        self._rnn_state_ctr += 1
+        return _sym.Variable(name=name, init=state)
+
+    def _get_rnn_state_buffer(self, layer):
+        """Get the state buffer for rnn."""
+        buffer = np.zeros((1, layer.outputs), self.dtype)
+        return self._new_rnn_state_sym(buffer)
 
-    return sym, tvmparams
+    def _get_darknet_rnn_attrs(self, layer, sym):
+        """Get the rnn converted symbol from attributes."""
+        attr = self._get_darknet_attrs(layer, 0)
+        op_name = self._get_opname(layer)
+        layer_name, sym = _darknet_convert_symbol(op_name, _as_list(sym), attr)
+        self._get_darknet_params(layer, layer_name)
+        return sym
+
+    def _handle_darknet_rnn_layers(self, layer_num, sym):
+        """Parse attributes and handle the rnn layers."""
+        attr = {}
+        layer = self.net.layers[layer_num]
+        processed = False
+
+        if LAYERTYPE.RNN == layer.type:
+            attr.update({'n' : layer.n})
+            attr.update({'batch' : layer.batch})
+            attr.update({'num_hidden' : str(layer.outputs)})
+
+            state = self._get_rnn_state_buffer(layer)
+
+            for _ in range(layer.steps):
+                input_layer = layer.input_layer
+                sym = self._get_darknet_rnn_attrs(input_layer, sym)
+
+                self_layer = layer.self_layer
+                state = self._get_darknet_rnn_attrs(self_layer, state)
+
+                op_name, new_attrs = 'elemwise_add', {}
+                new_inputs = _as_list([sym, state])
+                state = _darknet_get_nnvm_op(op_name)(*new_inputs, **new_attrs)
+                self._outs.append(state)
+
+                output_layer = layer.output_layer
+                sym = self._get_darknet_rnn_attrs(output_layer, state)
+
+            self._sym_array[layer_num] = sym
+            processed = True
+
+        return processed, sym
+
+    def from_darknet(self):
+        """To convert the darknet symbol to nnvm symbols."""
+        for i in range(self.net.n):
+            layer = self.net.layers[i]
+            need_skip, sym = self._preproc_layer(layer, i)
+            if need_skip is True:
+                continue
+
+            processed, sym = self._handle_darknet_rnn_layers(i, sym)
+            if processed is True:
+                continue
+
+            attr = self._get_darknet_attrs(layer, i)
+            op_name = self._get_opname(layer)
+            layer_name, sym = _darknet_convert_symbol(op_name, _as_list(sym), attr)
+            self._get_darknet_params(self.net.layers[i], layer_name)
+            self._sym_array[i] = sym
+        self._outs = _as_list(sym) + self._outs
+        if isinstance(self._outs, list):
+            sym = _sym.Group(self._outs)
+        return sym, self._tvmparams
 
 def from_darknet(net, dtype='float32'):
     """Convert from darknet's model into compatible NNVM format.
@@ -648,4 +713,4 @@ def from_darknet(net, dtype='float32'):
         The parameter dict to be used by nnvm
     """
 
-    return _from_darknet(net, dtype)
+    return GraphProto(net, dtype).from_darknet()
diff --git a/nnvm/python/nnvm/testing/darknet.py b/nnvm/python/nnvm/testing/darknet.py
index f6bbf8f7d951..362fd3058954 100644
--- a/nnvm/python/nnvm/testing/darknet.py
+++ b/nnvm/python/nnvm/testing/darknet.py
@@ -479,6 +479,7 @@ class ACTIVATION(object):
 void free_image(image m);
 image load_image_color(char *filename, int w, int h);
 float *network_predict_image(network *net, image im);
+float *network_predict(network *net, float *input);
 network *make_network(int n);
 layer make_convolutional_layer(int batch, int h, int w, int c, int n, int groups, int size, int stride, int padding, ACTIVATION activation, int batch_normalize, int binary, int xnor, int adam);
 layer make_connected_layer(int batch, int inputs, int outputs, ACTIVATION activation, int batch_normalize, int adam);
@@ -488,6 +489,8 @@ class ACTIVATION(object):
 layer make_batchnorm_layer(int batch, int w, int h, int c);
 layer make_reorg_layer(int batch, int w, int h, int c, int stride, int reverse, int flatten, int extra);
 layer make_region_layer(int batch, int w, int h, int n, int classes, int coords);
+layer make_softmax_layer(int batch, int inputs, int groups);
+layer make_rnn_layer(int batch, int inputs, int outputs, int steps, ACTIVATION activation, int batch_normalize, int adam);
 void free_network(network *net);
 """
                    )
diff --git a/nnvm/tests/python/frontend/darknet/test_forward.py b/nnvm/tests/python/frontend/darknet/test_forward.py
index 0abc595c426f..a8f7ccdeffd7 100644
--- a/nnvm/tests/python/frontend/darknet/test_forward.py
+++ b/nnvm/tests/python/frontend/darknet/test_forward.py
@@ -7,18 +7,20 @@
 """
 import os
 import requests
+import sys
+import urllib
 import numpy as np
+import tvm
+from tvm.contrib import graph_runtime
 from nnvm import frontend
 from nnvm.testing.darknet import __darknetffi__
 import nnvm.compiler
-import tvm
-import sys
-import urllib
 if sys.version_info >= (3,):
     import urllib.request as urllib2
 else:
     import urllib2
 
+
 def _download(url, path, overwrite=False, sizecompare=False):
     ''' Download from internet'''
     if os.path.isfile(path) and not overwrite:
@@ -48,43 +50,31 @@ def _download(url, path, overwrite=False, sizecompare=False):
 _download(DARKNETLIB_URL, DARKNET_LIB)
 LIB = __darknetffi__.dlopen('./' + DARKNET_LIB)
 
+def _get_tvm_output(net, data):
+    '''Compute TVM output'''
+    dtype = 'float32'
+    sym, params = frontend.darknet.from_darknet(net, dtype)
+
+    target = 'llvm'
+    shape_dict = {'data': data.shape}
+    graph, library, params = nnvm.compiler.build(sym, target, shape_dict, dtype, params=params)
+    # Execute on TVM
+    ctx = tvm.cpu(0)
+    m = graph_runtime.create(graph, library, ctx)
+    # set inputs
+    m.set_input('data', tvm.nd.array(data.astype(dtype)))
+    m.set_input(**params)
+    m.run()
+    # get outputs
+    out_shape = (net.outputs,)
+    tvm_out = m.get_output(0, tvm.nd.empty(out_shape, dtype)).asnumpy()
+    return tvm_out
+
 def test_forward(net):
     '''Test network with given input image on both darknet and tvm'''
     def get_darknet_output(net, img):
         return LIB.network_predict_image(net, img)
-
-    def get_tvm_output(net, img):
-        '''Compute TVM output'''
-        dtype = 'float32'
-        batch_size = 1
-        sym, params = frontend.darknet.from_darknet(net, dtype)
-        data = np.empty([batch_size, img.c, img.h, img.w], dtype)
-        i = 0
-        for c in range(img.c):
-            for h in range(img.h):
-                for k in range(img.w):
-                    data[0][c][h][k] = img.data[i]
-                    i = i + 1
-
-        target = 'llvm'
-        shape_dict = {'data': data.shape}
-        #with nnvm.compiler.build_config(opt_level=2):
-        graph, library, params = nnvm.compiler.build(sym, target, shape_dict, dtype, params=params)
-        ######################################################################
-        # Execute on TVM
-        # ---------------
-        # The process is no different from other examples.
-        from tvm.contrib import graph_runtime
-        ctx = tvm.cpu(0)
-        m = graph_runtime.create(graph, library, ctx)
-        # set inputs
-        m.set_input('data', tvm.nd.array(data.astype(dtype)))
-        m.set_input(**params)
-        m.run()
-        # get outputs
-        out_shape = (net.outputs,)
-        tvm_out = m.get_output(0, tvm.nd.empty(out_shape, dtype)).asnumpy()
-        return tvm_out
+    dtype = 'float32'
 
     test_image = 'dog.jpg'
     img_url = 'https://github.com/siju-samuel/darknet/blob/master/data/' + test_image   +'?raw=true'
@@ -94,9 +84,35 @@ def get_tvm_output(net, img):
     darknet_out = np.zeros(net.outputs, dtype='float32')
     for i in range(net.outputs):
         darknet_out[i] = darknet_output[i]
-    tvm_out = get_tvm_output(net, img)
+    batch_size = 1
+
+    data = np.empty([batch_size, img.c, img.h, img.w], dtype)
+    i = 0
+    for c in range(img.c):
+        for h in range(img.h):
+            for k in range(img.w):
+                data[0][c][h][k] = img.data[i]
+                i = i + 1
+
+    tvm_out = _get_tvm_output(net, data)
     np.testing.assert_allclose(darknet_out, tvm_out, rtol=1e-3, atol=1e-3)
 
+def test_rnn_forward(net):
+    '''Test network with given input data on both darknet and tvm'''
+    def get_darknet_network_predict(net, data):
+        return LIB.network_predict(net, data)
+    from cffi import FFI
+    ffi = FFI()
+    np_arr = np.zeros([1, net.inputs], dtype='float32')
+    np_arr[0, 84] = 1
+    cffi_arr = ffi.cast('float*', np_arr.ctypes.data)
+    tvm_out = _get_tvm_output(net, np_arr)
+    darknet_output = get_darknet_network_predict(net, cffi_arr)
+    darknet_out = np.zeros(net.outputs, dtype='float32')
+    for i in range(net.outputs):
+        darknet_out[i] = darknet_output[i]
+    np.testing.assert_allclose(darknet_out, tvm_out, rtol=1e-4, atol=1e-4)
+
 def test_forward_extraction():
     '''test extraction model'''
     model_name = 'extraction'
@@ -289,6 +305,25 @@ def test_forward_softmax_temperature():
     test_forward(net)
     LIB.free_network(net)
 
+def test_forward_rnn():
+    '''test softmax layer'''
+    net = LIB.make_network(1)
+    batch = 1
+    inputs = 256
+    outputs = 256
+    steps = 1
+    activation = 1
+    batch_normalize = 0
+    adam = 0
+    layer_1 = LIB.make_rnn_layer(batch, inputs, outputs, steps, activation, batch_normalize, adam)
+    net.layers[0] = layer_1
+    net.inputs = inputs
+    net.outputs = outputs
+    net.w = net.h = 0
+    LIB.resize_network(net, net.w, net.h)
+    test_rnn_forward(net)
+    LIB.free_network(net)
+
 if __name__ == '__main__':
     test_forward_resnet50()
     test_forward_alexnet()
@@ -303,6 +338,7 @@ def test_forward_softmax_temperature():
     test_forward_dense_batchnorm()
     test_forward_softmax()
     test_forward_softmax_temperature()
+    test_forward_rnn()
     test_forward_reorg()
     test_forward_region()
     test_forward_elu()
diff --git a/tutorials/nnvm/nlp/from_darknet_rnn.py b/tutorials/nnvm/nlp/from_darknet_rnn.py
new file mode 100644
index 000000000000..54013f04fca6
--- /dev/null
+++ b/tutorials/nnvm/nlp/from_darknet_rnn.py
@@ -0,0 +1,184 @@
+"""
+Compile Darknet Models for RNN
+==============================
+**Author**: `Siju Samuel <https://siju-samuel.github.io/>`_
+
+This article is an introductory tutorial to deploy darknet rnn models with NNVM.
+
+This script will run a character prediction model
+Each module consists of 3 fully-connected layers. The input layer propagates information from the
+input to the current state. The recurrent layer propagates information through time from the
+previous state to the current one.
+
+The input to the network is a 1-hot encoding of ASCII characters. We train the network to predict
+the next character in a stream of characters. The output is constrained to be a probability
+distribution using a softmax layer.
+
+Since each recurrent layer contains information about the current character and the past
+characters, it can use this context to predict the future characters in a word or phrase.
+
+All the required models and libraries will be downloaded from the internet
+by the script.
+"""
+import random
+import numpy as np
+from mxnet.gluon.utils import download
+import tvm
+from tvm.contrib import graph_runtime
+from nnvm.testing.darknet import __darknetffi__
+import nnvm
+import nnvm.frontend.darknet
+
+# Set the parameters
+# -----------------------
+# Set the seed value and the number of characters to predict
+
+#Model name
+MODEL_NAME = 'rnn'
+#Seed value
+seed = 'Thus'
+#Number of characters to predict
+num = 1000
+
+# Download required files
+# -----------------------
+# Download cfg and weights file if first time.
+CFG_NAME = MODEL_NAME + '.cfg'
+WEIGHTS_NAME = MODEL_NAME + '.weights'
+REPO_URL = 'https://github.com/dmlc/web-data/blob/master/darknet/'
+CFG_URL = REPO_URL + 'cfg/' + CFG_NAME + '?raw=true'
+WEIGHTS_URL = REPO_URL + 'weights/' + WEIGHTS_NAME + '?raw=true'
+
+download(CFG_URL, CFG_NAME)
+download(WEIGHTS_URL, WEIGHTS_NAME)
+
+# Download and Load darknet library
+DARKNET_LIB = 'libdarknet.so'
+DARKNET_URL = REPO_URL + 'lib/' + DARKNET_LIB + '?raw=true'
+download(DARKNET_URL, DARKNET_LIB)
+DARKNET_LIB = __darknetffi__.dlopen('./' + DARKNET_LIB)
+cfg = "./" + str(CFG_NAME)
+weights = "./" + str(WEIGHTS_NAME)
+net = DARKNET_LIB.load_network(cfg.encode('utf-8'), weights.encode('utf-8'), 0)
+dtype = 'float32'
+batch_size = 1
+
+# Import the graph to NNVM
+# ------------------------
+# Import darknet graph definition to nnvm.
+#
+# Results:
+#   sym: nnvm graph for rnn model
+#   params: params converted from darknet weights
+print("Converting darknet rnn model to nnvm symbols...")
+sym, params = nnvm.frontend.darknet.from_darknet(net, dtype)
+
+# Compile the model on NNVM
+data = np.empty([1, net.inputs], dtype)#net.inputs
+
+target = 'llvm'
+shape = {'data': data.shape}
+print("Compiling the model...")
+
+shape_dict = {'data': data.shape}
+dtype_dict = {'data': data.dtype}
+
+with nnvm.compiler.build_config(opt_level=2):
+    graph, lib, params = nnvm.compiler.build(sym, target, shape_dict, dtype_dict, params)
+
+# Execute the portable graph on TVM
+# ---------------------------------
+# Now we can try deploying the NNVM compiled model on cpu target.
+
+# Set the cpu context
+ctx = tvm.cpu(0)
+# Create graph runtime
+m = graph_runtime.create(graph, lib, ctx)
+# Set the params to runtime
+m.set_input(**params)
+
+def _init_state_memory(rnn_cells_count, dtype):
+    '''Initialize memory for states'''
+    states = {}
+    state_shape = (1024,)
+    for i in range(rnn_cells_count):
+        k = 'rnn' + str(i) + '_state'
+        states[k] = tvm.nd.array(np.zeros(state_shape, dtype).astype(dtype))
+    return states
+
+def _set_state_input(runtime, states):
+    '''Set the state inputs'''
+    for state in states:
+        runtime.set_input(state, states[state])
+
+def _get_state_output(runtime, states):
+    '''Get the state outputs and save'''
+    i = 1
+    for state in states:
+        data = states[state]
+        states[state] = runtime.get_output((i), tvm.nd.empty(data.shape, data.dtype))
+        i += 1
+
+def _proc_rnn_output(out_data):
+    '''Generate the characters from the output array'''
+    sum_array = 0
+    n = out_data.size
+    r = random.uniform(0, 1)
+    for j in range(n):
+        if out_data[j] < 0.0001:
+            out_data[j] = 0
+        sum_array += out_data[j]
+
+    for j in range(n):
+        out_data[j] *= float(1.0) / sum_array
+        r = r - out_data[j]
+        if r <= 0:
+            return j
+    return n-1
+
+print("RNN generaring text...")
+
+out_shape = (net.outputs,)
+rnn_cells_count = 3
+
+# Initialize state memory
+# -----------------------
+states = _init_state_memory(rnn_cells_count, dtype)
+
+len_seed = len(seed)
+count = len_seed + num
+out_txt = ""
+
+#Initialize random seed
+random.seed(0)
+c = ord(seed[0])
+inp_data = np.zeros([net.inputs], dtype)
+
+# Run the model
+# -------------
+
+# Predict character by character till `num`
+for i in range(count):
+    inp_data[c] = 1
+
+    # Set the input data
+    m.set_input('data', tvm.nd.array(inp_data.astype(dtype)))
+    inp_data[c] = 0
+
+    # Set the state inputs
+    _set_state_input(m, states)
+
+    # Run the model
+    m.run()
+
+    # Get the output
+    tvm_out = m.get_output(0, tvm.nd.empty(out_shape, dtype)).asnumpy()
+
+    # Get the state outputs
+    _get_state_output(m, states)
+
+    # Get the predicted character and keep buffering it
+    c = ord(seed[i])  if i < len_seed else _proc_rnn_output(tvm_out)
+    out_txt += chr(c)
+
+print("Predicted Text =", out_txt)

From 38f92c6bcf1acae51dee9e787091927d6cc5b1d0 Mon Sep 17 00:00:00 2001
From: abergeron <bergearn@iro.umontreal.ca>
Date: Thu, 19 Jul 2018 18:04:39 -0400
Subject: [PATCH 891/949] Fix conda 2 (#1456)

---
 conda/tvm-libs/meta.yaml | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/conda/tvm-libs/meta.yaml b/conda/tvm-libs/meta.yaml
index 7875931fc124..dbdfd4a7701f 100644
--- a/conda/tvm-libs/meta.yaml
+++ b/conda/tvm-libs/meta.yaml
@@ -11,13 +11,18 @@ build:
   number: 0
 
 requirements:
+  build:
+    - {{ compiler('cxx') }}  # [linux]
+    - llvmdev ==6.0.0  # [osx]
   host:
     # The OS X build will require some manual setup or it will break
     # See https://conda.io/docs/user-guide/tasks/build-packages/compiler-tools.html#macos-sdk
-    - {{ compiler('cxx') }}
-  build:
-    - llvmdev ==6.0.0
+    # It is also ass-backward because of llvm brokeness when mixed with the
+    # conda OS X compiler
+    - {{ compiler('cxx') }}  # [osx]
     - cmake
+    - llvmdev ==6.0.0  # [linux]
+    - zlib  # [linux]
 
 about:
   home: https://github.com/dmlc/tvm

From 3d1d24ed38706a8c990142554c9adfc51af3cfe0 Mon Sep 17 00:00:00 2001
From: Haibin Lin <linhaibin.eric@gmail.com>
Date: Thu, 19 Jul 2018 19:26:48 -0700
Subject: [PATCH 892/949] Minor doc fixes (#1458)

---
 tutorials/autotvm/tune_simple_template.py |  2 +-
 tutorials/cross_compilation_and_rpc.py    | 10 +++++-----
 tutorials/language/tuple_inputs.py        |  2 +-
 3 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/tutorials/autotvm/tune_simple_template.py b/tutorials/autotvm/tune_simple_template.py
index f6a0c63766cd..4e598155135a 100644
--- a/tutorials/autotvm/tune_simple_template.py
+++ b/tutorials/autotvm/tune_simple_template.py
@@ -28,7 +28,7 @@
 # tunable schedule template. You can regard the process of search space definition
 # as the parametrization of our exiting schedule code.
 #
-# To begin with, here is how we implement a blocked matrix multiplication in tvm
+# To begin with, here is how we implement a blocked matrix multiplication in tvm.
 
 # Matmul V0: Constant tiling factor
 def matmul_v0(N, L, M, dtype):
diff --git a/tutorials/cross_compilation_and_rpc.py b/tutorials/cross_compilation_and_rpc.py
index 7ef0f0fc17af..a3c01deb4518 100644
--- a/tutorials/cross_compilation_and_rpc.py
+++ b/tutorials/cross_compilation_and_rpc.py
@@ -60,7 +60,7 @@
 #
 #     make runtime
 #
-# After success of buildind runtime, we need set environment varibles
+# After building runtime successfully, we need to set environment varibles
 # in :code:`~/.bashrc` file of yourself account or :code:`/etc/profile`
 # of system enviroment variables. Assuming your TVM directory is in
 # :code:`~/tvm` and set environment variables below your account.
@@ -69,7 +69,7 @@
 #
 #    vi ~/.bashrc
 #
-# We need edit :code:`~/.bashrc` using :code:`vi ~/.bashrc` and add
+# We need to edit :code:`~/.bashrc` using :code:`vi ~/.bashrc` and add
 # lines below (Assuming your TVM directory is in :code:`~/tvm`):
 #
 #   .. code-block:: bash
@@ -92,8 +92,8 @@
 #
 #     python -m tvm.exec.rpc_server --host 0.0.0.0 --port=9090
 #
-# After executing command above, if you see these lines below, it's
-# successful to start RPC server on your device.
+# After executing the command above, if you see these lines below, it means
+# the RPC server started successfully on your device.
 #
 #    .. code-block:: bash
 #
@@ -215,7 +215,7 @@
 
 ######################################################################
 # When you want to evaluate the performance of the kernel on the remote
-# device, it is important to avoid overhead of remote function call.
+# device, it is important to avoid overhead of remote function calls.
 # :code:`time_evaluator` will returns a remote function that runs the
 # function over number times, measures the cost per run on the remote
 # device and returns the measured cost.
diff --git a/tutorials/language/tuple_inputs.py b/tutorials/language/tuple_inputs.py
index ff1928158f43..3f6ef1eb9d21 100644
--- a/tutorials/language/tuple_inputs.py
+++ b/tutorials/language/tuple_inputs.py
@@ -18,7 +18,7 @@
 # Describe Batchwise Computation
 # ------------------------------
 # For operators which have the same shape, we can put them together as
-# the inputs of :any:`tvm.compute`, if we wish they can be scheduled
+# the inputs of :any:`tvm.compute`, if we want them to be scheduled
 # together in the next schedule procedure.
 #
 n = tvm.var("n")

From 5f7b4d5cddac7694baeef092cb54728ff918088d Mon Sep 17 00:00:00 2001
From: Tatsuya Nishiyama <nishiyama.tatsuya0@gmail.com>
Date: Fri, 20 Jul 2018 12:40:15 +0900
Subject: [PATCH 893/949] [CUDA] FP16 support  (#1413)

---
 python/tvm/contrib/nvcc.py                 | 75 ++++++++++++++++++++++
 src/codegen/codegen_cuda.cc                | 37 ++++++++++-
 src/codegen/codegen_cuda.h                 |  5 ++
 src/codegen/opt/build_cuda_on.cc           | 71 +++++++++++++++++++-
 tests/python/unittest/test_codegen_cuda.py | 32 +++++++++
 5 files changed, 216 insertions(+), 4 deletions(-)
 create mode 100644 tests/python/unittest/test_codegen_cuda.py

diff --git a/python/tvm/contrib/nvcc.py b/python/tvm/contrib/nvcc.py
index 5655d3965098..1b7bb840127d 100644
--- a/python/tvm/contrib/nvcc.py
+++ b/python/tvm/contrib/nvcc.py
@@ -167,3 +167,78 @@ def callback_libdevice_path(arch):
     except RuntimeError:
         warnings.warn("Cannot find libdevice path")
         return ""
+
+
+def parse_compute_version(compute_version):
+    """Parse compute capability string to divide major and minor version
+
+    Parameters
+    ----------
+    compute_version : str
+        compute capability of a GPU (e.g. "6.0")
+
+    Returns
+    -------
+    major : int
+        major version number
+    minor : int
+        minor version number
+    """
+    split_ver = compute_version.split('.')
+    try:
+        major = int(split_ver[0])
+        minor = int(split_ver[1])
+        return major, minor
+    except (IndexError, ValueError) as err:
+        raise RuntimeError("Compute version parsing error: " + str(err))
+
+
+def have_fp16(compute_version):
+    """Either fp16 support is provided in the compute capability or not
+
+    Parameters
+    ----------
+    compute_version: str
+        compute capability of a GPU (e.g. "6.0")
+    """
+    major, minor = parse_compute_version(compute_version)
+    # fp 16 support in reference to:
+    # https://docs.nvidia.com/cuda/cuda-c-programming-guide/#arithmetic-instructions
+    if major == 5 and minor == 3:
+        return True
+    # NOTE: exclude compute capability 6.1 devices although it is actually available
+    #       to compute fp16, because these devices only have low-rate fp16 performance.
+    if major == 6 and minor != 1:
+        return True
+    if major == 7:
+        return True
+
+    return False
+
+def have_int8(compute_version):
+    """Either int8 support is provided in the compute capability or not
+
+    Parameters
+    ----------
+    compute_version : str
+        compute capability of a GPU (e.g. "6.1")
+    """
+    major, minor = parse_compute_version(compute_version)
+    if major == 6 and minor == 1:
+        return True
+
+    return False
+
+def have_tensorcore(compute_version):
+    """Either TensorCore support is provided in the compute capability or not
+
+    Parameters
+    ----------
+    compute_version : str
+        compute capability of a GPU (e.g. "7.0")
+    """
+    major, _ = parse_compute_version(compute_version)
+    if major == 7:
+        return True
+
+    return False
diff --git a/src/codegen/codegen_cuda.cc b/src/codegen/codegen_cuda.cc
index f2e038c4a2a4..38c559ba2ba5 100644
--- a/src/codegen/codegen_cuda.cc
+++ b/src/codegen/codegen_cuda.cc
@@ -29,6 +29,14 @@ void CodeGenCUDA::AddFunction(LoweredFunc f) {
   CodeGenC::AddFunction(f);
 }
 
+std::string CodeGenCUDA::Finish() {
+  if (enable_fp16_) {
+    decl_stream << "#include <cuda_fp16.h>\n";
+  }
+
+  return CodeGenC::Finish();
+}
+
 void CodeGenCUDA::VisitStmt_(const ir::For* op) {
   CHECK(is_zero(op->min));
   if (op->for_type == ir::ForType::Unrolled) {
@@ -54,7 +62,9 @@ void CodeGenCUDA::PrintType(Type t, std::ostream& os) {  // NOLINT(*)
   bool fail = false;
   if (t.is_float()) {
     switch (t.bits()) {
-      case 16: os << "half"; break;
+      case 16: os << "half";
+        enable_fp16_ = true;
+        break;
       case 32: os << "float"; break;
       case 64: os << "double"; break;
       default: fail = true; break;
@@ -258,5 +268,30 @@ void CodeGenCUDA::VisitExpr_(const Broadcast* op, std::ostream& os) {   // NOLIN
   os << ')';
 }
 
+
+inline void PrintConst(const FloatImm* op, std::ostream& os, CodeGenCUDA* p) { // NOLINT(*)
+  switch (op->type.bits()) {
+    case 64: case 32: {
+      std::ostringstream temp;
+      temp << std::scientific << op->value;
+      if (op->type.bits() == 32) temp << 'f';
+      p->MarkConst(temp.str());
+      os << temp.str();
+      break;
+    }
+    case 16: {
+      os << "__float2half_rn";
+      os << '(' << std::scientific << op->value << 'f' << ')';
+      break;
+    }
+    default: LOG(FATAL) << "Bad bit-width for float: " << op->type << "\n";
+  }
+}
+
+
+void CodeGenCUDA::VisitExpr_(const FloatImm *op, std::ostream& os) { // NOLINT(*)
+  PrintConst(op, os, this);
+}
+
 }  // namespace codegen
 }  // namespace tvm
diff --git a/src/codegen/codegen_cuda.h b/src/codegen/codegen_cuda.h
index b84b245860e0..dd559f5232f9 100644
--- a/src/codegen/codegen_cuda.h
+++ b/src/codegen/codegen_cuda.h
@@ -19,6 +19,8 @@ class CodeGenCUDA final : public CodeGenC {
   CodeGenCUDA();
   void Init(bool output_ssa);
   void AddFunction(LoweredFunc f);
+  std::string Finish();
+  bool need_include_path() { return enable_fp16_; }
   // override behavior
   void VisitStmt_(const ir::For* op) final;
   void PrintStorageSync(const Call* op) final;
@@ -35,6 +37,7 @@ class CodeGenCUDA final : public CodeGenC {
   // overload visitor
   void VisitExpr_(const Ramp* op, std::ostream& os) final; // NOLINT(*)
   void VisitExpr_(const Broadcast* op, std::ostream& os) final; // NOLINT(*)
+  void VisitExpr_(const FloatImm *op, std::ostream& os) final;
   void VisitStmt_(const Evaluate *op) final;
 
  private:
@@ -44,6 +47,8 @@ class CodeGenCUDA final : public CodeGenC {
   std::string vid_global_barrier_state_;
   // Global barrier expected node.
   std::string vid_global_barrier_expect_;
+  // whether enable fp16
+  bool enable_fp16_{false};
 };
 
 }  // namespace codegen
diff --git a/src/codegen/opt/build_cuda_on.cc b/src/codegen/opt/build_cuda_on.cc
index 267c3447e051..72fd38b925e5 100644
--- a/src/codegen/opt/build_cuda_on.cc
+++ b/src/codegen/opt/build_cuda_on.cc
@@ -5,14 +5,20 @@
  *
  * \file build_cuda.cc
  */
+#if defined(__linux__)
+#include <sys/stat.h>
+#endif
+#include <cuda_runtime.h>
 #include <tvm/base.h>
 #include <nvrtc.h>
+#include <cstdlib>
 
 #include "../codegen_cuda.h"
 #include "../build_common.h"
 #include "../../runtime/cuda/cuda_common.h"
 #include "../../runtime/cuda/cuda_module.h"
 
+
 namespace tvm {
 namespace codegen {
 
@@ -26,11 +32,69 @@ namespace codegen {
     }                                                                   \
   }
 
-std::string NVRTCCompile(const std::string& code) {
+
+std::string FindCUDAIncludePath() {
+#if defined(_WIN32)
+  const std::string delimiter = "\\";
+#else
+  const std::string delimiter = "/";
+#endif
+  std::string cuda_include_path;
+  const char* cuda_path_env = std::getenv("CUDA_PATH");
+  if (cuda_path_env != nullptr) {
+    cuda_include_path += cuda_path_env;
+    cuda_include_path += delimiter + "include";
+    return cuda_include_path;
+  }
+
+#if defined(__linux__)
+  struct stat st;
+  cuda_include_path = "/usr/local/cuda/include";
+  if (stat(cuda_include_path.c_str(), &st) == 0) {
+    return cuda_include_path;
+  }
+#endif
+  LOG(FATAL) << "Cannot find cuda include path."
+             << "CUDA_PATH is not set or CUDA is not installed in the default installation path."
+             << "In other than linux, it is necessary to set CUDA_PATH.";
+  return cuda_include_path;
+}
+
+
+std::string NVRTCCompile(const std::string& code, bool include_path = false) {
+  std::vector<std::string> compile_params;
+  std::vector<const char*> param_cstrings{};
+  int num_options = 0;
   nvrtcProgram prog;
+  cudaDeviceProp device_prop;
+  std::string cc = "30";
+  cudaError_t e = cudaGetDeviceProperties(&device_prop, 0);
+
+  if (e == cudaSuccess) {
+    cc = std::to_string(device_prop.major) + std::to_string(device_prop.minor);
+  } else {
+    LOG(WARNING) << "cannot detect compute capability from your device, "
+                 << "fall back to compute_30.";
+  }
+
+  compile_params.push_back("-arch=compute_" + cc);
+  num_options++;
+
+  if (include_path) {
+    std::string include_option = "--include-path=" + FindCUDAIncludePath();
+
+    compile_params.push_back(include_option);
+    num_options++;
+  }
+
+  for (const auto& string : compile_params) {
+      param_cstrings.push_back(string.c_str());
+  }
   NVRTC_CALL(nvrtcCreateProgram(
       &prog, code.c_str(), nullptr, 0, nullptr, nullptr));
-  nvrtcResult compile_res = nvrtcCompileProgram(prog, 0, nullptr);
+  nvrtcResult compile_res =
+      nvrtcCompileProgram(prog, param_cstrings.size(), param_cstrings.data());
+
   size_t log_size;
   NVRTC_CALL(nvrtcGetProgramLogSize(prog, &log_size));
   std::string log; log.resize(log_size);
@@ -43,6 +107,7 @@ std::string NVRTCCompile(const std::string& code) {
   ptx.resize(ptx_size);
   NVRTC_CALL(nvrtcGetPTX(prog, &ptx[0]));
   NVRTC_CALL(nvrtcDestroyProgram(&prog));
+
   return ptx;
 }
 
@@ -68,7 +133,7 @@ runtime::Module BuildCUDA(Array<LoweredFunc> funcs) {
     // TODO(tqchen) more reliable checks
     if (ptx[0] != '/') fmt = "cubin";
   } else {
-    ptx = NVRTCCompile(code);
+    ptx = NVRTCCompile(code, cg.need_include_path());
   }
   return CUDAModuleCreate(ptx, fmt, ExtractFuncInfo(funcs), code);
 }
diff --git a/tests/python/unittest/test_codegen_cuda.py b/tests/python/unittest/test_codegen_cuda.py
new file mode 100644
index 000000000000..044c40696e59
--- /dev/null
+++ b/tests/python/unittest/test_codegen_cuda.py
@@ -0,0 +1,32 @@
+import tvm
+import numpy as np
+from tvm.contrib.nvcc import have_fp16
+
+def test_cuda_vectorize_add():
+    num_thread = 8
+    def check_cuda(dtype, n, lanes):
+        if not tvm.gpu(0).exist or not tvm.module.enabled("cuda"):
+            print("skip because cuda is not enabled..")
+            return
+        if dtype == "float16" and not have_fp16(tvm.gpu(0).compute_version):
+            print("skip because gpu does not support fp16")
+            return
+        A = tvm.placeholder((n,), name='A', dtype="%sx%d" % (dtype, lanes))
+        B = tvm.compute((n,), lambda i: A[i]+tvm.const(1, A.dtype), name='B')
+        s = tvm.create_schedule(B.op)
+        xo, xi = s[B].split(B.op.axis[0], factor=num_thread)
+        s[B].bind(xo, tvm.thread_axis("blockIdx.x"))
+        s[B].bind(xi, tvm.thread_axis("threadIdx.x"))
+        fun = tvm.build(s, [A, B], "cuda")
+        ctx = tvm.gpu(0)
+        a = tvm.nd.empty((n,), A.dtype, ctx).copyfrom(
+            np.random.uniform(size=(n, lanes)))
+        c = tvm.nd.empty((n,), B.dtype, ctx)
+        fun(a, c)
+        np.testing.assert_allclose(c.asnumpy(), a.asnumpy() + 1)
+        
+    check_cuda("float32", 64, 2)
+    check_cuda("float16", 64, 2)
+
+if __name__ == "__main__":
+    test_cuda_vectorize_add()

From 5c84a98a1d25dbc7c0f322b5eb284c2ffd5cd5d1 Mon Sep 17 00:00:00 2001
From: eqy <eqy@cs.washington.edu>
Date: Thu, 19 Jul 2018 20:45:50 -0700
Subject: [PATCH 894/949] [RPC] Android RPC Performance Regression Fix, Update
 Android RPC to use Tracker (#1457)

---
 apps/android_rpc/README.md                    |  15 +-
 apps/android_rpc/app/build.gradle             |   2 +-
 .../app/src/main/AndroidManifest.xml          |  13 +-
 .../java/ml/dmlc/tvm/tvmrpc/MainActivity.java | 124 ++++++---
 .../java/ml/dmlc/tvm/tvmrpc/RPCActivity.java  |  66 +++++
 .../java/ml/dmlc/tvm/tvmrpc/RPCProcessor.java |  79 ++----
 .../java/ml/dmlc/tvm/tvmrpc/RPCService.java   |  64 -----
 .../java/ml/dmlc/tvm/tvmrpc/RPCWatchdog.java  |  82 ------
 .../app/src/main/jni/Application.mk           |   5 +-
 .../app/src/main/jni/make/config.mk           |   2 +-
 .../app/src/main/res/layout/activity_main.xml |   1 -
 .../app/src/main/res/layout/activity_rpc.xml  |  26 ++
 .../app/src/main/res/layout/content_main.xml  |  15 +-
 .../app/src/main/res/layout/content_rpc.xml   |  14 +
 .../app/src/main/res/values/strings.xml       |  14 +-
 apps/android_rpc/tests/android_rpc_test.py    |  32 +--
 .../rpc/ConnectTrackerServerProcessor.java    | 251 ++++++++++++++++++
 .../ml/dmlc/tvm/rpc/NativeServerLoop.java     |   2 +
 .../src/main/java/ml/dmlc/tvm/rpc/RPC.java    |  10 +
 .../java/ml/dmlc/tvm/rpc/RPCWatchdog.java     |  83 ++++++
 .../src/main/java/ml/dmlc/tvm/rpc/Utils.java  |  13 +
 21 files changed, 625 insertions(+), 288 deletions(-)
 create mode 100644 apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCActivity.java
 delete mode 100644 apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCService.java
 delete mode 100644 apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCWatchdog.java
 create mode 100644 apps/android_rpc/app/src/main/res/layout/activity_rpc.xml
 create mode 100644 apps/android_rpc/app/src/main/res/layout/content_rpc.xml
 create mode 100644 jvm/core/src/main/java/ml/dmlc/tvm/rpc/ConnectTrackerServerProcessor.java
 create mode 100644 jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPCWatchdog.java

diff --git a/apps/android_rpc/README.md b/apps/android_rpc/README.md
index 00cb597cec28..cedd9ab0ac0c 100644
--- a/apps/android_rpc/README.md
+++ b/apps/android_rpc/README.md
@@ -51,15 +51,15 @@ Here's a piece of example for `config.mk`.
 
 ```makefile
 APP_ABI = arm64-v8a
- 
+
 APP_PLATFORM = android-17
- 
+
 # whether enable OpenCL during compile
 USE_OPENCL = 1
- 
+
 # the additional include headers you want to add, e.g., SDK_PATH/adrenosdk/Development/Inc
 ADD_C_INCLUDES = /opt/adrenosdk-osx/Development/Inc
- 
+
 # the additional link libs you want to add, e.g., ANDROID_LIB_PATH/libOpenCL.so
 ADD_LDLIBS = libOpenCL.so
 ```
@@ -85,13 +85,14 @@ If everything goes well, you will find compile tools in `/opt/android-toolchain-
 
 ### Cross Compile and Upload to the Android Device
 
-First start a proxy server using `python -m tvm.exec.rpc_proxy` and make your Android device connect to this proxy server via TVM RPC application.
+First start an RPC tracker using `python -m tvm.exec.rpc_tracker --port [PORT]` and make your Android device connect to this RPC tracker via TVM RPC application.
 
 Then checkout [android\_rpc/tests/android\_rpc\_test.py](https://github.com/dmlc/tvm/blob/master/apps/android_rpc/tests/android_rpc_test.py) and run,
 
 ```bash
-# Specify the proxy host
-export TVM_ANDROID_RPC_PROXY_HOST=0.0.0.0
+# Specify the RPC tracker
+export TVM_TRACKER_HOST=0.0.0.0
+export TVM_TRACKER_PORT=[PORT]
 # Specify the standalone Android C++ compiler
 export TVM_NDK_CC=/opt/android-toolchain-arm64/bin/aarch64-linux-android-g++
 python android_rpc_test.py
diff --git a/apps/android_rpc/app/build.gradle b/apps/android_rpc/app/build.gradle
index 97364da5cd87..a91455fc5477 100644
--- a/apps/android_rpc/app/build.gradle
+++ b/apps/android_rpc/app/build.gradle
@@ -13,7 +13,7 @@ android {
     buildToolsVersion "26.0.1"
     defaultConfig {
         applicationId "ml.dmlc.tvm.tvmrpc"
-        minSdkVersion 17
+        minSdkVersion 24
         targetSdkVersion 26
         versionCode 1
         versionName "1.0"
diff --git a/apps/android_rpc/app/src/main/AndroidManifest.xml b/apps/android_rpc/app/src/main/AndroidManifest.xml
index d8385e3b15e2..2dbc06ece6e3 100644
--- a/apps/android_rpc/app/src/main/AndroidManifest.xml
+++ b/apps/android_rpc/app/src/main/AndroidManifest.xml
@@ -20,9 +20,16 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
-        <service android:name=".RPCService"
-                 android:process=":RPCServiceProcess"
-                 android:permission="android.permission.BIND_JOB_SERVICE" />
+        <service android:name=".RPCWatchdogService"
+         android:process=":RPCWatchdogServiceProcess"
+         android:permission="android.permission.BIND_JOB_SERVICE" />
+        <activity
+            android:name=".RPCActivity"
+            android:process=":RPCProcess"
+            android:label="@string/rpc_name"
+            android:theme="@style/AppTheme.NoActionBar"
+            android:screenOrientation="portrait">
+        </activity>
     </application>
 
 </manifest>
diff --git a/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/MainActivity.java b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/MainActivity.java
index 0fca08a2e463..d80008bbe258 100644
--- a/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/MainActivity.java
+++ b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/MainActivity.java
@@ -31,12 +31,18 @@
 import android.widget.CompoundButton;
 import android.widget.EditText;
 import android.widget.Switch;
+import android.widget.Button;
+import android.view.View;
 import android.content.Intent;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
 
 
 public class MainActivity extends AppCompatActivity {
+  private boolean skipRelaunch = true;
+  // wait time before automatic restart of RPC Activity
+  public static final int HANDLER_RESTART_DELAY = 5000;
 
-  private RPCWatchdog watchdog;
 
   private void showDialog(String title, String msg) {
     AlertDialog.Builder builder = new AlertDialog.Builder(this);
@@ -52,73 +58,107 @@ public void onClick(DialogInterface dialog, int id) {
     builder.create().show();
   }
 
+  public Intent updateRPCPrefs() {
+    System.err.println("updating preferences...");
+    EditText edProxyAddress = findViewById(R.id.input_address);
+    EditText edProxyPort = findViewById(R.id.input_port);
+    EditText edAppKey = findViewById(R.id.input_key);
+    Switch inputSwitch =  findViewById(R.id.switch_persistent);
+
+    final String proxyHost = edProxyAddress.getText().toString();
+    final int proxyPort = Integer.parseInt(edProxyPort.getText().toString());
+    final String key = edAppKey.getText().toString();
+    final boolean isChecked = inputSwitch.isChecked();
+
+    SharedPreferences pref = getApplicationContext().getSharedPreferences("RPCProxyPreference", Context.MODE_PRIVATE);
+    SharedPreferences.Editor editor = pref.edit();
+    editor.putString("input_address", proxyHost);
+    editor.putString("input_port", edProxyPort.getText().toString());
+    editor.putString("input_key", key);
+    editor.putBoolean("input_switch", isChecked);
+    editor.commit();
+
+    Intent intent = new Intent(this, RPCActivity.class);
+    intent.putExtra("host", proxyHost);
+    intent.putExtra("port", proxyPort);
+    intent.putExtra("key", key);
+    return intent;
+  }
+
+  private void setupRelaunch() {
+    final Context context = this;
+    final Switch switchPersistent = findViewById(R.id.switch_persistent);
+    final Runnable rPCStarter = new Runnable() {
+        public void run() {
+            if (switchPersistent.isChecked()) {
+              System.err.println("relaunching RPC activity in 5s...");
+              Intent intent = ((MainActivity) context).updateRPCPrefs();
+              startActivity(intent);
+            }
+        }
+    };
+    Handler handler = new Handler();
+    handler.postDelayed(rPCStarter, HANDLER_RESTART_DELAY);
+  }
+
   @Override
   protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_main);
     Toolbar toolbar = findViewById(R.id.toolbar);
     setSupportActionBar(toolbar);
+    final Context context = this;
 
-    Switch switchConnect = findViewById(R.id.switch_connect);
-    switchConnect.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+    Switch switchPersistent = findViewById(R.id.switch_persistent);
+    switchPersistent.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
       @Override
       public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
         if (isChecked) {
-          enableInputView(false);
-          connectProxy();
+          System.err.println("automatic RPC restart enabled...");
+          updateRPCPrefs();
         } else {
-          disconnect();
-          enableInputView(true);
+          System.err.println("automatic RPC restart disabled...");
+          updateRPCPrefs();
         }
       }
     });
-    
-    enableInputView(true);
 
+    Button startRPC = findViewById(R.id.button_start_rpc);
+    startRPC.setOnClickListener(new View.OnClickListener() {
+        public void onClick(View v) {
+            Intent intent = ((MainActivity) context).updateRPCPrefs();
+            startActivity(intent);
+        }
+    });
+
+    enableInputView(true);
   }
 
   @Override
-  protected void onDestroy() {
-    super.onDestroy();
-    if (watchdog != null) {
-        watchdog.disconnect();
-        watchdog = null;
+  protected void onResume() {
+    System.err.println("MainActivity onResume...");
+    System.err.println("skipRelaunch: " + skipRelaunch);
+    // if this is the first time onResume is called, do nothing, otherwise we
+    // may double launch
+    if (!skipRelaunch) {
+        enableInputView(true);
+        setupRelaunch();
+    } else {
+        skipRelaunch = false;
     }
+    super.onResume();
   }
 
-  private void connectProxy() {
-    EditText edProxyAddress = findViewById(R.id.input_address);
-    EditText edProxyPort = findViewById(R.id.input_port);
-    EditText edAppKey = findViewById(R.id.input_key);
-    final String proxyHost = edProxyAddress.getText().toString();
-    final int proxyPort = Integer.parseInt(edProxyPort.getText().toString());
-    final String key = edAppKey.getText().toString();
-
-    System.err.println("creating watchdog thread...");
-    watchdog = new RPCWatchdog(proxyHost, proxyPort, key, this);
-    
-    System.err.println("starting watchdog thread...");
-    watchdog.start();
-
-    SharedPreferences pref = getApplicationContext().getSharedPreferences("RPCProxyPreference", Context.MODE_PRIVATE);
-    SharedPreferences.Editor editor = pref.edit();
-    editor.putString("input_address", proxyHost);
-    editor.putString("input_port", edProxyPort.getText().toString());
-    editor.putString("input_key", key);
-    editor.commit();
-  }
-
-  private void disconnect() {
-    if (watchdog != null) {
-        watchdog.disconnect();
-        watchdog = null;
-    }
+  @Override
+  protected void onDestroy() {
+    super.onDestroy();
   }
 
   private void enableInputView(boolean enable) {
     EditText edProxyAddress = findViewById(R.id.input_address);
     EditText edProxyPort = findViewById(R.id.input_port);
     EditText edAppKey = findViewById(R.id.input_key);
+    Switch input_switch = findViewById(R.id.switch_persistent);
     edProxyAddress.setEnabled(enable);
     edProxyPort.setEnabled(enable);
     edAppKey.setEnabled(enable);
@@ -134,6 +174,8 @@ private void enableInputView(boolean enable) {
     String inputKey = pref.getString("input_key", null);
     if (null != inputKey)
         edAppKey.setText(inputKey);
+    boolean isChecked = pref.getBoolean("input_switch", false);
+    input_switch.setChecked(isChecked);
     }
   }
 }
diff --git a/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCActivity.java b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCActivity.java
new file mode 100644
index 000000000000..912a7c9e69a6
--- /dev/null
+++ b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCActivity.java
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+package ml.dmlc.tvm.tvmrpc;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.content.Intent;
+import android.widget.Button;
+import android.view.View;
+
+public class RPCActivity extends AppCompatActivity {
+  private RPCProcessor tvmServerWorker;
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_rpc);
+
+    Button stopRPC = findViewById(R.id.button_stop_rpc);
+    stopRPC.setOnClickListener(new View.OnClickListener() {
+        public void onClick(View v) {
+            System.err.println(tvmServerWorker == null);
+            if (tvmServerWorker != null) {
+                // currently will raise a socket closed exception
+                tvmServerWorker.disconnect();
+            }
+            finish();
+            // prevent Android from recycling the process
+            System.exit(0);
+        }
+    });
+
+    System.err.println("rpc activity onCreate...");
+    Intent intent = getIntent();
+    String host = intent.getStringExtra("host");
+    int port = intent.getIntExtra("port", 9090);
+    String key = intent.getStringExtra("key");
+
+    tvmServerWorker = new RPCProcessor();
+    tvmServerWorker.setDaemon(true);
+    tvmServerWorker.start();
+    tvmServerWorker.connect(host, port, key);
+  }
+
+  @Override
+  protected void onDestroy() {
+    System.err.println("rpc activity onDestroy");
+    tvmServerWorker.disconnect();
+    super.onDestroy();
+  }
+}
diff --git a/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCProcessor.java b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCProcessor.java
index 4099084d4ebf..6da890931104 100644
--- a/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCProcessor.java
+++ b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCProcessor.java
@@ -17,15 +17,11 @@
 
 package ml.dmlc.tvm.tvmrpc;
 
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
 import android.os.ParcelFileDescriptor;
-
 import java.net.Socket;
-
-import ml.dmlc.tvm.rpc.ConnectProxyServerProcessor;
+import ml.dmlc.tvm.rpc.ConnectTrackerServerProcessor;
 import ml.dmlc.tvm.rpc.SocketFileDescriptorGetter;
+import ml.dmlc.tvm.rpc.RPCWatchdog;
 
 /**
  * Connect to RPC proxy and deal with requests.
@@ -36,9 +32,8 @@ class RPCProcessor extends Thread {
   private String key;
   private boolean running = false;
   private long startTime;
-  private ConnectProxyServerProcessor currProcessor;
-  private boolean kill = false;
-  public static final int SESSION_TIMEOUT = 30000;
+  private ConnectTrackerServerProcessor currProcessor;
+  private boolean first = true;
 
   static final SocketFileDescriptorGetter socketFdGetter
       = new SocketFileDescriptorGetter() {
@@ -47,21 +42,10 @@ public int get(Socket socket) {
           return ParcelFileDescriptor.fromSocket(socket).getFd();
         }
       };
-  // callback to initialize the start time of an rpc session
-  class setTimeCallback implements Runnable { 
-    private RPCProcessor rPCProcessor;
-
-    public setTimeCallback(RPCProcessor rPCProcessor) {
-        this.rPCProcessor = rPCProcessor;    
-    }
-
-    @Override
-    public void run() {
-        rPCProcessor.setStartTime();
-    }
-  }
 
   @Override public void run() {
+    RPCWatchdog watchdog = new RPCWatchdog();
+    watchdog.start();
     while (true) {
       synchronized (this) {
         currProcessor = null;
@@ -71,49 +55,18 @@ public void run() {
           } catch (InterruptedException e) {
           }
         }
-        // if kill, we do nothing and wait for app restart
-        // to prevent race where timedOut was reported but restart has not
-        // happened yet
-        if (kill) {
-            System.err.println("waiting for restart...");
-            currProcessor = null;
-        }
-        else {
-            startTime = 0;
-            currProcessor = new ConnectProxyServerProcessor(host, port, key, socketFdGetter);
-            currProcessor.setStartTimeCallback(new setTimeCallback(this));
+        try {
+          currProcessor = new ConnectTrackerServerProcessor(host, port, key, socketFdGetter, watchdog);
+        } catch (Throwable e) {
+          e.printStackTrace();
+          // kill if creating a new processor failed
+          System.exit(0);
         }
       }
-        if (currProcessor != null)
-            currProcessor.run();
-    }
-  }
-
-  /**
-   * check if the current RPCProcessor has timed out while in a session
-   */
-  synchronized boolean timedOut(long curTime) {
-    if (startTime == 0) {
-        return false;
+      if (currProcessor != null)
+        currProcessor.run();
+      watchdog.finishTimeout();
     }
-    else if ((curTime - startTime) > SESSION_TIMEOUT) {
-        System.err.println("set kill flag...");
-        kill = true;
-        return true;
-    }
-    return false;
-  }
-
-  /**
-   * set the start time of the current RPC session (used in callback)
-   */
-  synchronized void setStartTime() {
-    startTime = System.currentTimeMillis();
-    System.err.println("start time set to: " + startTime);
-  }
-
-  synchronized long getStartTime() {
-    return startTime;
   }
 
   /**
@@ -139,6 +92,6 @@ synchronized void connect(String host, int port, String key) {
     this.port = port;
     this.key = key;
     running = true;
-    notify();
+    this.notify();
   }
 }
diff --git a/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCService.java b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCService.java
deleted file mode 100644
index facc230f60ae..000000000000
--- a/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCService.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package ml.dmlc.tvm.tvmrpc;
-
-import android.app.Service;
-import android.os.IBinder;
-import android.content.Intent;
-
-public class RPCService extends Service {
-    private String host;
-    private int port;
-    private String key;
-    private int intentNum;
-    private RPCProcessor tvmServerWorker;
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        synchronized(this) {
-            System.err.println("start command intent");
-            // use an alternate kill to prevent android from recycling the
-            // process
-            if (intent.getBooleanExtra("kill", false)) {
-                System.err.println("rpc service received kill...");
-                System.exit(0);
-            }
-        
-            this.host = intent.getStringExtra("host");
-            this.port = intent.getIntExtra("port", 9090);
-            this.key = intent.getStringExtra("key");
-            System.err.println("got the following: " + this.host + ", " + this.port + ", " + this.key);
-            System.err.println("intent num: " + this.intentNum);
-
-            if (tvmServerWorker == null) {
-                System.err.println("service created worker...");
-                tvmServerWorker = new RPCProcessor();
-                tvmServerWorker.setDaemon(true);
-                tvmServerWorker.start();
-                tvmServerWorker.connect(this.host, this.port, this.key);
-            }
-            else if (tvmServerWorker.timedOut(System.currentTimeMillis())) {
-                System.err.println("rpc service timed out, killing self...");
-                System.exit(0);
-            }
-            this.intentNum++; 
-        }
-        // do not restart unless watchdog/app expliciltly does so
-        return START_NOT_STICKY;
-    }
-    
-    @Override
-    public IBinder onBind(Intent intent) {
-        System.err.println("rpc service got onBind, doing nothing...");
-        return null;
-    }
-
-    @Override
-    public void onCreate() {
-        System.err.println("rpc service onCreate...");
-    }
-
-    @Override
-    public void onDestroy() {
-        tvmServerWorker.disconnect();
-        System.err.println("rpc service onDestroy...");
-    }
-}
diff --git a/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCWatchdog.java b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCWatchdog.java
deleted file mode 100644
index 548a2dfc0e7d..000000000000
--- a/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCWatchdog.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * 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.
- */
-
-package ml.dmlc.tvm.tvmrpc;
-
-import android.content.Context;
-import android.content.Intent;
-
-/**
- * Watchdog for RPCService
- */
-class RPCWatchdog extends Thread {
-  public static final int WATCHDOG_POLL_INTERVAL = 5000;
-  private String host;
-  private int port;
-  private String key;
-  private Context context;
-  private boolean done = false;
-
-  public RPCWatchdog(String host, int port, String key, Context context) {
-    super();
-    this.host = host;
-    this.port = port;
-    this.key = key;
-    this.context = context;
-  }
-
-  /**
-   * Polling loop to check on RPCService status
-   */ 
-  @Override public void run() {
-    try {
-        while (true) {
-          synchronized (this) {
-              if (done) {
-                System.err.println("watchdog done, returning...");
-                return;
-              }
-              else {
-                System.err.println("polling rpc service...");                                  
-                System.err.println("sending rpc service intent...");
-                Intent intent = new Intent(context, RPCService.class);
-                intent.putExtra("host", host);
-                intent.putExtra("port", port);
-                intent.putExtra("key", key);
-                // will implicilty restart the service if it died
-                context.startService(intent);
-              }
-          }
-          Thread.sleep(WATCHDOG_POLL_INTERVAL);
-        }
-    } catch (InterruptedException e) {
-    }
-  }
-
-  /**
-   * Disconnect from the proxy server.
-   */
-  synchronized void disconnect() {
-    // kill service
-    System.err.println("watchdog disconnect call...");
-    System.err.println("stopping rpc service...");
-    done = true;
-    Intent intent = new Intent(context, RPCService.class);
-    intent.putExtra("kill", true); 
-    context.startService(intent); 
-  }
-}
diff --git a/apps/android_rpc/app/src/main/jni/Application.mk b/apps/android_rpc/app/src/main/jni/Application.mk
index 09ee723a10d2..c5509c1f4506 100644
--- a/apps/android_rpc/app/src/main/jni/Application.mk
+++ b/apps/android_rpc/app/src/main/jni/Application.mk
@@ -12,15 +12,14 @@ include $(config)
 # 1) armeabi is deprecated in NDK r16 and removed in r17
 # 2) vulkan is not supported in armeabi
 APP_ABI := armeabi-v7a arm64-v8a x86 x86_64 mips
-
 APP_STL := c++_static
 
 APP_CPPFLAGS += -DDMLC_LOG_STACK_TRACE=0 -DTVM4J_ANDROID=1 -std=c++11 -Oz -frtti
-ifeq ($(USE_OPENCL), 1)                                                                                                                                             
+ifeq ($(USE_OPENCL), 1)
 	APP_CPPFLAGS += -DTVM_OPENCL_RUNTIME=1
 endif
 
-ifeq ($(USE_VULKAN), 1)                                                                                                                                             
+ifeq ($(USE_VULKAN), 1)
 	APP_CPPFLAGS += -DTVM_VULKAN_RUNTIME=1
 	APP_LDFLAGS += -lvulkan
 endif
diff --git a/apps/android_rpc/app/src/main/jni/make/config.mk b/apps/android_rpc/app/src/main/jni/make/config.mk
index e57a907441b0..c40ce4ba3ec7 100644
--- a/apps/android_rpc/app/src/main/jni/make/config.mk
+++ b/apps/android_rpc/app/src/main/jni/make/config.mk
@@ -14,7 +14,7 @@
 #-------------------------------------------------------------------------------
 APP_ABI = all
 
-APP_PLATFORM = android-17
+APP_PLATFORM = android-24
 
 # whether enable OpenCL during compile
 USE_OPENCL = 0
diff --git a/apps/android_rpc/app/src/main/res/layout/activity_main.xml b/apps/android_rpc/app/src/main/res/layout/activity_main.xml
index f617cf2a04bb..53d48bbd60d9 100644
--- a/apps/android_rpc/app/src/main/res/layout/activity_main.xml
+++ b/apps/android_rpc/app/src/main/res/layout/activity_main.xml
@@ -24,4 +24,3 @@
     <include layout="@layout/content_main"/>
 
 </android.support.design.widget.CoordinatorLayout>
-
diff --git a/apps/android_rpc/app/src/main/res/layout/activity_rpc.xml b/apps/android_rpc/app/src/main/res/layout/activity_rpc.xml
new file mode 100644
index 000000000000..ba3102a6033c
--- /dev/null
+++ b/apps/android_rpc/app/src/main/res/layout/activity_rpc.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="ml.dmlc.tvm.tvmrpc.RPCActivity">
+
+    <android.support.design.widget.AppBarLayout
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:theme="@style/AppTheme.AppBarOverlay">
+
+        <android.support.v7.widget.Toolbar
+            android:id="@+id/toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/actionBarSize"
+            android:background="?attr/colorPrimary"
+            app:popupTheme="@style/AppTheme.PopupOverlay" />
+
+    </android.support.design.widget.AppBarLayout>
+
+    <include layout="@layout/content_rpc"/>
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/apps/android_rpc/app/src/main/res/layout/content_main.xml b/apps/android_rpc/app/src/main/res/layout/content_main.xml
index 827cdfb01b8a..0f2564833ecd 100644
--- a/apps/android_rpc/app/src/main/res/layout/content_main.xml
+++ b/apps/android_rpc/app/src/main/res/layout/content_main.xml
@@ -64,9 +64,9 @@
         <TextView
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:text="@string/label_connect"/>
+            android:text="@string/label_persistent"/>
         <Switch
-            android:id="@+id/switch_connect"
+            android:id="@+id/switch_persistent"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:switchMinWidth="55dp"
@@ -76,4 +76,15 @@
             android:textOn="@string/switch_on" />
     </LinearLayout>
 
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content">
+        <Button
+            android:id="@+id/button_start_rpc"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:text="@string/start_rpc" />
+    </LinearLayout>
+
 </LinearLayout>
diff --git a/apps/android_rpc/app/src/main/res/layout/content_rpc.xml b/apps/android_rpc/app/src/main/res/layout/content_rpc.xml
new file mode 100644
index 000000000000..fb9ab2f66a9b
--- /dev/null
+++ b/apps/android_rpc/app/src/main/res/layout/content_rpc.xml
@@ -0,0 +1,14 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              xmlns:tools="http://schemas.android.com/tools"
+              xmlns:app="http://schemas.android.com/apk/res-auto"
+              android:orientation="vertical"
+              android:layout_width="fill_parent"
+              android:layout_height="wrap_content"
+              app:layout_behavior="@string/appbar_scrolling_view_behavior"
+              tools:showIn="@layout/activity_rpc">
+        <Button
+            android:id="@+id/button_stop_rpc"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:text="@string/stop_rpc" />
+</LinearLayout>
diff --git a/apps/android_rpc/app/src/main/res/values/strings.xml b/apps/android_rpc/app/src/main/res/values/strings.xml
index 468fbed8ceaa..33caa374b496 100644
--- a/apps/android_rpc/app/src/main/res/values/strings.xml
+++ b/apps/android_rpc/app/src/main/res/values/strings.xml
@@ -1,15 +1,19 @@
 <resources>
     <string name="app_name">TVM RPC</string>
+    <string name="rpc_name">RPC</string>
 
-    <string name="input_address">Enter the proxy server address</string>
-    <string name="input_port">Enter the proxy server port</string>
+    <string name="input_address">Enter the tracker server address</string>
+    <string name="input_port">Enter the tracker server port</string>
     <string name="input_key">Enter the app connection key</string>
 
     <string name="label_address">Address</string>
     <string name="label_port">Port</string>
     <string name="label_key">Key</string>
-    <string name="label_connect">Connect to Proxy</string>
+    <string name="label_persistent">Keep RPC Alive</string>
 
-    <string name="switch_on">Connected</string>
-    <string name="switch_off">Disconnected</string>
+    <string name="switch_on">Enabled</string>
+    <string name="switch_off">Disabled</string>
+
+    <string name="start_rpc">Start RPC</string>
+    <string name="stop_rpc">Stop RPC</string>
 </resources>
diff --git a/apps/android_rpc/tests/android_rpc_test.py b/apps/android_rpc/tests/android_rpc_test.py
index a3362fafa63a..82575e656284 100644
--- a/apps/android_rpc/tests/android_rpc_test.py
+++ b/apps/android_rpc/tests/android_rpc_test.py
@@ -11,8 +11,8 @@
 import numpy as np
 
 # Set to be address of tvm proxy.
-proxy_host = os.environ["TVM_ANDROID_RPC_PROXY_HOST"]
-proxy_port = 9090
+tracker_host = os.environ["TVM_TRACKER_HOST"]
+tracker_port = int(os.environ["TVM_TRACKER_PORT"])
 key = "android"
 
 # Change target configuration.
@@ -33,7 +33,7 @@ def test_rpc_module():
     # Build the dynamic lib.
     # If we don't want to do metal and only use cpu, just set target to be target
     f = tvm.build(s, [A, B], "opencl", target_host=target, name="myadd")
-    path_dso1 = temp.relpath("dev_lib.so")
+    path_dso1 = temp.relpath("dev_lib2.so")
     f.export_library(path_dso1, ndk.create_shared)
 
     s = tvm.create_schedule(B.op)
@@ -45,29 +45,31 @@ def test_rpc_module():
     path_dso2 = temp.relpath("cpu_lib.so")
     f.export_library(path_dso2, ndk.create_shared)
 
-    # connect to the proxy
-    remote = rpc.connect(proxy_host, proxy_port, key=key)
+    tracker = rpc.connect_tracker(tracker_host, tracker_port)
+    remote = tracker.request(key, priority=0,
+                             session_timeout=60)
 
-    print('Run GPU test ...')
-    ctx = remote.cl(0)
-    remote.upload(path_dso1)
-    f1 = remote.load_module("dev_lib.so")
+    print('Run CPU test ...')
+    ctx = remote.cpu(0)
+    remote.upload(path_dso2)
+    f2 = remote.load_module("cpu_lib.so")
     a_np = np.random.uniform(size=1024).astype(A.dtype)
     a = tvm.nd.array(a_np, ctx)
     b = tvm.nd.array(np.zeros(1024, dtype=A.dtype), ctx)
-    time_f = f1.time_evaluator(f1.entry_name, ctx, number=10)
+    time_f = f2.time_evaluator(f2.entry_name, ctx, number=10)
     cost = time_f(a, b).mean
     print('%g secs/op' % cost)
     np.testing.assert_equal(b.asnumpy(), a.asnumpy() + 1)
 
-    print('Run CPU test ...')
-    ctx = remote.cpu(0)
-    remote.upload(path_dso2)
-    f2 = remote.load_module("cpu_lib.so")
+
+    print('Run GPU test ...')
+    ctx = remote.cl(0)
+    remote.upload(path_dso1)
+    f1 = remote.load_module("dev_lib2.so")
     a_np = np.random.uniform(size=1024).astype(A.dtype)
     a = tvm.nd.array(a_np, ctx)
     b = tvm.nd.array(np.zeros(1024, dtype=A.dtype), ctx)
-    time_f = f2.time_evaluator(f2.entry_name, ctx, number=10)
+    time_f = f1.time_evaluator(f1.entry_name, ctx, number=10)
     cost = time_f(a, b).mean
     print('%g secs/op' % cost)
     np.testing.assert_equal(b.asnumpy(), a.asnumpy() + 1)
diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/ConnectTrackerServerProcessor.java b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/ConnectTrackerServerProcessor.java
new file mode 100644
index 000000000000..ca3af923c448
--- /dev/null
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/ConnectTrackerServerProcessor.java
@@ -0,0 +1,251 @@
+/*
+ * 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.
+ */
+
+package ml.dmlc.tvm.rpc;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.BindException;
+import java.net.ConnectException;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+
+
+/**
+ * Server processor with tracker connection (based on standalone).
+ * This RPC Server registers itself with an RPC Tracker for a specific queue
+ * (using its device key) and listens for incoming requests.
+ */
+public class ConnectTrackerServerProcessor implements ServerProcessor {
+  private ServerSocket server;
+  private final SocketFileDescriptorGetter socketFileDescriptorGetter;
+  private final String trackerHost;
+  private final int trackerPort;
+  // device key
+  private final String key;
+  // device key plus randomly generated key (per-session)
+  private final String matchKey;
+  private int serverPort = 5001;
+  public static final int MAX_SERVER_PORT = 5555;
+  // time to wait before aborting tracker connection (ms)
+  public static final int TRACKER_TIMEOUT = 6000;
+  // time to wait before retrying tracker connection (ms)
+  public static final int RETRY_PERIOD = TRACKER_TIMEOUT;
+  // time to wait for a connection before refreshing tracker connection (ms)
+  public static final int STALE_TRACKER_TIMEOUT = 300000;
+  // time to wait if no timeout value is specified (seconds)
+  public static final int HARD_TIMEOUT_DEFAULT = 300;
+  private RPCWatchdog watchdog;
+  private Socket trackerSocket;
+
+  /**
+   * Construct tracker server processor.
+   * @param trackerHost Tracker host.
+   * @param trackerPort Tracker port.
+   * @param key Device key.
+   * @param sockFdGetter Method to get file descriptor from Java socket.
+   */
+  public ConnectTrackerServerProcessor(String trackerHost, int trackerPort, String key,
+      SocketFileDescriptorGetter sockFdGetter, RPCWatchdog watchdog) throws IOException {
+    while (true) {
+      try {
+        this.server = new ServerSocket(serverPort);
+        server.setSoTimeout(STALE_TRACKER_TIMEOUT);
+        break;
+      } catch (BindException e) {
+        System.err.println(serverPort);
+        System.err.println(e);
+        serverPort++;
+        if (serverPort > MAX_SERVER_PORT) {
+          throw e;
+        }
+      }
+    }
+    System.err.println("using port: " + serverPort);
+    this.socketFileDescriptorGetter = sockFdGetter;
+    this.trackerHost = trackerHost;
+    this.trackerPort = trackerPort;
+    this.key = key;
+    this.matchKey = key + ":" + Math.random();
+    this.watchdog = watchdog;
+  }
+
+  public String getMatchKey() {
+    return matchKey;
+  }
+
+  @Override public void terminate() {
+    try {
+      server.close();
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+  }
+
+  @Override public void run() {
+    String recvKey = null;
+    try {
+      trackerSocket = connectToTracker();
+      // open a socket and handshake with tracker
+      register();
+      Socket socket = null;
+      InputStream in = null;
+      OutputStream out = null;
+      while (true) {
+        try {
+          System.err.println("waiting for requests...");
+          // wait for client request
+          socket = server.accept();
+          in = socket.getInputStream();
+          out = socket.getOutputStream();
+          int magic = Utils.wrapBytes(Utils.recvAll(in, 4)).getInt();
+          if (magic != RPC.RPC_MAGIC) {
+            out.write(Utils.toBytes(RPC.RPC_CODE_MISMATCH));
+            System.err.println("incorrect RPC magic");
+            Utils.closeQuietly(socket);
+            continue;
+          }
+          recvKey = Utils.recvString(in);
+          System.err.println("matchKey:" + matchKey);
+          System.err.println("key: " + recvKey);
+          // incorrect key
+          if (recvKey.indexOf(matchKey) == -1) {
+            out.write(Utils.toBytes(RPC.RPC_CODE_MISMATCH));
+            System.err.println("key mismatch, expected: " + matchKey + " got: " +  recvKey);
+            Utils.closeQuietly(socket);
+            continue;
+          }
+          // successfully got client request and completed handshake with client
+          break;
+        } catch (SocketTimeoutException e) {
+          System.err.println("no incoming connections, refreshing...");
+          // need to reregister, if the tracker died we should see a socked closed exception
+          if (!needRefreshKey()) {
+            System.err.println("reregistering...");
+            register();
+          }
+        }
+      }
+      int timeout = HARD_TIMEOUT_DEFAULT;
+      int timeoutArgIndex = recvKey.indexOf(RPC.TIMEOUT_ARG);
+      if (timeoutArgIndex != -1) {
+        timeout = Integer.parseInt(recvKey.substring(timeoutArgIndex + RPC.TIMEOUT_ARG.length()));
+      }
+      System.err.println("alloted timeout: " + timeout);
+      if (!recvKey.startsWith("client:")) {
+        System.err.println("recv key mismatch...");
+        out.write(Utils.toBytes(RPC.RPC_CODE_MISMATCH));
+      } else {
+        out.write(Utils.toBytes(RPC.RPC_MAGIC));
+        // send server key to the client
+        Utils.sendString(out, recvKey);
+      }
+
+      System.err.println("Connection from " + socket.getRemoteSocketAddress().toString());
+      // received timeout in seconds
+      watchdog.startTimeout(timeout * 1000);
+      final int sockFd = socketFileDescriptorGetter.get(socket);
+      if (sockFd != -1) {
+        new NativeServerLoop(sockFd).run();
+        System.err.println("Finish serving " + socket.getRemoteSocketAddress().toString());
+      }
+      Utils.closeQuietly(socket);
+    } catch (ConnectException e) {
+      // if the tracker connection failed, wait a bit before retrying
+      try {
+        Thread.sleep(RETRY_PERIOD);
+      } catch (InterruptedException e_) {
+        System.err.println("interrupted before retrying to connect to tracker...");
+      }
+    } catch (Throwable e) {
+      e.printStackTrace();
+    } finally {
+      try {
+        if (trackerSocket != null) {
+          trackerSocket.close();
+        }
+        server.close();
+      } catch (Throwable e) {
+        e.printStackTrace();
+      }
+    }
+  }
+
+  private Socket connectToTracker() throws IOException {
+    trackerSocket = new Socket();
+    SocketAddress address = new InetSocketAddress(trackerHost, trackerPort);
+    trackerSocket.connect(address, TRACKER_TIMEOUT);
+    InputStream trackerIn = trackerSocket.getInputStream();
+    OutputStream trackerOut = trackerSocket.getOutputStream();
+    trackerOut.write(Utils.toBytes(RPC.RPC_TRACKER_MAGIC));
+    int trackerMagic = Utils.wrapBytes(Utils.recvAll(trackerIn, 4)).getInt();
+    if (trackerMagic != RPC.RPC_TRACKER_MAGIC) {
+      throw new SocketException("failed to connect to tracker (WRONG MAGIC)");
+    }
+    return trackerSocket;
+  }
+
+  /*
+   * Register the RPC Server with the RPC Tracker.
+   */
+  private void register() throws IOException {
+    InputStream trackerIn = trackerSocket.getInputStream();
+    OutputStream trackerOut = trackerSocket.getOutputStream();
+    // send a JSON with PUT, device key, RPC server port, and the randomly
+    // generated key
+    String putJSON = generatePut(RPC.TrackerCode.PUT, key, serverPort, matchKey);
+    Utils.sendString(trackerOut, putJSON);
+    int recvCode = Integer.parseInt(Utils.recvString(trackerIn));
+    if (recvCode != RPC.TrackerCode.SUCCESS) {
+      throw new SocketException("failed to register with tracker (not SUCCESS)");
+    }
+    System.err.println("registered with tracker...");
+  }
+
+  /*
+   * Check if the RPC Tracker has our key.
+   */
+  private boolean needRefreshKey() throws IOException {
+    InputStream trackerIn = trackerSocket.getInputStream();
+    OutputStream trackerOut = trackerSocket.getOutputStream();
+    String getJSON = generateGetPendingMatchKeys(RPC.TrackerCode.GET_PENDING_MATCHKEYS);
+    Utils.sendString(trackerOut, getJSON);
+    String recvJSON = Utils.recvString(trackerIn);
+    System.err.println("pending matchkeys: " + recvJSON);
+    // fairly expensive string operation...
+    if (recvJSON.indexOf(matchKey) != -1 ) {
+      return true;
+    }
+    return false;
+  }
+
+  // handcrafted JSON
+  private String generatePut(int code, String key, int port, String matchKey) {
+    return "[" + code + ", " + "\"" + key + "\"" + ", " + "[" + port + ", "
+            + "\"" + matchKey +  "\"" + "]" + ", " + "null" + "]";
+  }
+
+  // handcrafted JSON
+  private String generateGetPendingMatchKeys(int code) {
+    return "[" + code  + "]";
+  }
+}
diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/NativeServerLoop.java b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/NativeServerLoop.java
index 4bc4c34c2d0a..666b15aed615 100644
--- a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/NativeServerLoop.java
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/NativeServerLoop.java
@@ -42,7 +42,9 @@ public NativeServerLoop(final int nativeSockFd) {
     File tempDir = null;
     try {
       tempDir = serverEnv();
+      System.err.println("starting server loop...");
       RPC.getApi("_ServerLoop").pushArg(sockFd).invoke();
+      System.err.println("done server loop...");
     } catch (IOException e) {
       e.printStackTrace();
     } finally {
diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPC.java b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPC.java
index 225bcaeefbd3..757fc0df3265 100644
--- a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPC.java
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPC.java
@@ -23,9 +23,19 @@
 import java.util.Map;
 
 public class RPC {
+  public static final int RPC_TRACKER_MAGIC = 0x2f271;
   public static final int RPC_MAGIC = 0xff271;
+  public static final int RPC_CODE_MISMATCH = RPC_MAGIC + 2;
   public static final int RPC_SESS_MASK = 128;
 
+  public static final String TIMEOUT_ARG = "-timeout=";
+
+  public class TrackerCode {
+    public static final int PUT = 3;
+    public static final int GET_PENDING_MATCHKEYS = 7;
+    public static final int SUCCESS = 0;
+  }
+
   private static ThreadLocal<Map<String, Function>> apiFuncs
       = new ThreadLocal<Map<String, Function>>() {
           @Override
diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPCWatchdog.java b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPCWatchdog.java
new file mode 100644
index 000000000000..4df858cbd6bb
--- /dev/null
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPCWatchdog.java
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+package ml.dmlc.tvm.rpc;
+
+/**
+ * Watchdog for RPC.
+ */
+public class RPCWatchdog extends Thread {
+  private int timeout = 0;
+  private boolean started = false;
+
+  public RPCWatchdog() {
+    super();
+  }
+
+  /**
+   * Start a timeout with watchdog (must be called before finishTimeout).
+   * @param timeout watchdog timeout in ms.
+   */
+  public synchronized void startTimeout(int timeout) {
+    this.timeout = timeout;
+    started = true;
+    this.notify();
+  }
+
+  /**
+   * Finish a timeout with watchdog (must be called after startTimeout).
+   */
+  public synchronized void finishTimeout() {
+    started = false;
+    this.notify();
+  }
+
+  /**
+   * Wait and kill RPC if timeout is exceeded.
+   */
+  @Override public void run() {
+    while (true) {
+      // timeout not started
+      synchronized (this) {
+        while (!started) {
+          try {
+            this.wait();
+          } catch (InterruptedException e) {
+            System.err.println("watchdog interrupted...");
+          }
+        }
+      }
+      synchronized (this) {
+        while (started) {
+          try {
+            System.err.println("waiting for timeout: " + timeout);
+            this.wait(timeout);
+            if (!started) {
+              System.err.println("watchdog woken up, ok...");
+            } else {
+              System.err.println("watchdog woke up!");
+              System.err.println("terminating...");
+              System.exit(0);
+            }
+          } catch (InterruptedException e) {
+            System.err.println("watchdog interrupted...");
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/Utils.java b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/Utils.java
index d6d9efb6e604..0f241d12c558 100644
--- a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/Utils.java
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/Utils.java
@@ -19,6 +19,7 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.net.Socket;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
@@ -76,4 +77,16 @@ public static String decodeToStr(byte[] bytes) {
     }
     return builder.toString();
   }
+
+  public static String recvString(InputStream in) throws IOException {
+    String recvString = null;
+    int len = wrapBytes(Utils.recvAll(in, 4)).getInt();
+    recvString = decodeToStr(Utils.recvAll(in, len));
+    return recvString;
+  }
+
+  public static void sendString(OutputStream out, String string) throws IOException {
+    out.write(toBytes(string.length()));
+    out.write(toBytes(string));
+  }
 }

From d32b509a5afb80e25bdbe7f189334e7a02cc7d35 Mon Sep 17 00:00:00 2001
From: Tatsuya Nishiyama <nishiyama.tatsuya0@gmail.com>
Date: Sat, 21 Jul 2018 00:54:47 +0900
Subject: [PATCH 895/949] Fix GetReduceAces (#1460)

---
 nnvm/src/top/tensor/reduce.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/src/top/tensor/reduce.cc b/nnvm/src/top/tensor/reduce.cc
index afe9475f5cbb..cb6848e1be7f 100644
--- a/nnvm/src/top/tensor/reduce.cc
+++ b/nnvm/src/top/tensor/reduce.cc
@@ -48,7 +48,7 @@ inline TShape GetReduceAxes(const uint32_t indim,
   if (!exclude) return in_axis;
   TShape r_axis(indim - in_axis.ndim());
   for (unsigned i = 0, j = 0, k = 0; i < indim; ++i) {
-    if (i == in_axis[j]) {
+    if (j < in_axis.ndim() && i == in_axis[j]) {
         ++j;
         continue;
     }

From e23446a7e1f74aa031a1965461b32a500b121235 Mon Sep 17 00:00:00 2001
From: Jared Roesch <roeschinc@gmail.com>
Date: Fri, 20 Jul 2018 09:53:20 -0700
Subject: [PATCH 896/949] Add deps for Relay (#1463)

---
 docker/install/ubuntu_install_python_package.sh | 2 +-
 docker/install/ubuntu_install_sphinx.sh         | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/docker/install/ubuntu_install_python_package.sh b/docker/install/ubuntu_install_python_package.sh
index 4f1518cb1abd..b7fae21ab567 100644
--- a/docker/install/ubuntu_install_python_package.sh
+++ b/docker/install/ubuntu_install_python_package.sh
@@ -1,3 +1,3 @@
 # install libraries for python package on ubuntu
 pip2 install nose pylint numpy nose-timer cython decorator scipy tornado
-pip3 install nose pylint numpy nose-timer cython decorator scipy tornado typed_ast
+pip3 install nose pylint numpy nose-timer cython decorator scipy tornado typed_ast pytest mypy
diff --git a/docker/install/ubuntu_install_sphinx.sh b/docker/install/ubuntu_install_sphinx.sh
index eabae1aac4fd..0efae90f90ed 100644
--- a/docker/install/ubuntu_install_sphinx.sh
+++ b/docker/install/ubuntu_install_sphinx.sh
@@ -1 +1 @@
-pip3 install sphinx sphinx-gallery sphinx_rtd_theme matplotlib Image commonmark>=0.7.3 docutils>=0.11
+pip3 install sphinx sphinx-gallery sphinx_rtd_theme sphinx_autodoc_annotator matplotlib Image commonmark>=0.7.3 docutils>=0.11

From 1f2abda7d00023f46fd14598d9452f507158683b Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 20 Jul 2018 11:48:19 -0700
Subject: [PATCH 897/949] Update VTA schedule (#1464)

---
 docker/install/ubuntu_install_sphinx.sh | 2 +-
 vta/python/vta/top/vta_conv2d.py        | 9 ++++++---
 2 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/docker/install/ubuntu_install_sphinx.sh b/docker/install/ubuntu_install_sphinx.sh
index 0efae90f90ed..ba04c2e25e6f 100644
--- a/docker/install/ubuntu_install_sphinx.sh
+++ b/docker/install/ubuntu_install_sphinx.sh
@@ -1 +1 @@
-pip3 install sphinx sphinx-gallery sphinx_rtd_theme sphinx_autodoc_annotator matplotlib Image commonmark>=0.7.3 docutils>=0.11
+pip3 install sphinx sphinx-gallery sphinx_rtd_theme sphinx_autodoc_annotation matplotlib Image commonmark>=0.7.3 docutils>=0.11
diff --git a/vta/python/vta/top/vta_conv2d.py b/vta/python/vta/top/vta_conv2d.py
index 5547faef743b..28cd8a49cb0f 100644
--- a/vta/python/vta/top/vta_conv2d.py
+++ b/vta/python/vta/top/vta_conv2d.py
@@ -276,7 +276,7 @@ def schedule_conv2d(attrs, outs, target):
         target = tvm.target.create(target)
         if target.device_name == "vta":
             return schedule_packed_conv2d(outs)
-        elif target.startswith("llvm"):
+        elif str(target).startswith("llvm"):
             return tvm.create_schedule([x.op for x in outs])
         else:
             raise RuntimeError("not support target %s" % target)
@@ -353,8 +353,11 @@ def _traverse(op):
     else:
         pad_data = None
     wrkld = _get_workload(data, pad_data, kernel, output)
-
-    plan = _WL2PLAN[wrkld]
+    if wrkld in _WL2PLAN:
+        plan = _WL2PLAN[wrkld]
+    else:
+        plan = find_schedules(wrkld, vt_only=True, best_only=True)[0]
+        logging.info("Trying to find plan for %s", wrkld)
     env = get_env()
 
     load_inp = load_wgt = load_out = store_out = env.dma_copy

From 33245b8c09009fa2c080e1f42e786ede745de963 Mon Sep 17 00:00:00 2001
From: Jian Weng <werefluke@gmail.com>
Date: Fri, 20 Jul 2018 16:03:21 -0700
Subject: [PATCH 898/949] [FRONTEND] [HYBRID] Augmented assign operator
 supported! (#1459)

---
 python/tvm/hybrid/parser.py                 |  9 ++-
 python/tvm/hybrid/var_decl.py               |  9 +++
 tests/python/unittest/test_hybrid_script.py | 75 ++++++++++++++-------
 3 files changed, 67 insertions(+), 26 deletions(-)

diff --git a/python/tvm/hybrid/parser.py b/python/tvm/hybrid/parser.py
index 1e532367a321..afb1a1a917fb 100644
--- a/python/tvm/hybrid/parser.py
+++ b/python/tvm/hybrid/parser.py
@@ -15,7 +15,7 @@
 
 def list_to_block(visit, lst):
     """Convert a list of Python IR nodes to HalideIR Block"""
-    lst = list(map(visit, lst))
+    lst = [visit(i) for i in lst]
     lst = [stmt for stmt in lst if not _ir_pass.Equal(stmt, make_nop())]
     if not lst:
         return make_nop()
@@ -162,6 +162,13 @@ def visit_Name(self, node):
     def visit_Num(self, node):
         return _api.const(node.n)
 
+    def visit_AugAssign(self, node):
+        lhs = self.visit(node.target)
+        rhs = self.visit(node.value)
+        rhs = HybridParser._binop_maker[type(node.op)](lhs, rhs)
+        if not isinstance(lhs, _expr.Call):
+            raise ValueError("The LHS of an AugAssign is supposed to be a call!")
+        return _make.Provide(lhs.func, 0, rhs, lhs.args)
 
     def visit_Assign(self, node):
         if len(node.targets) != 1:
diff --git a/python/tvm/hybrid/var_decl.py b/python/tvm/hybrid/var_decl.py
index df38bac1acba..8982b9d39ae9 100644
--- a/python/tvm/hybrid/var_decl.py
+++ b/python/tvm/hybrid/var_decl.py
@@ -14,6 +14,7 @@ def __init__(self, args):
         self.scope_level = []
         self._args = {}
         self.args = args
+        self.aug_assign_ = False
 
 
     def visit_FunctionDef(self, node):
@@ -48,6 +49,12 @@ def visit_Call(self, node):
             self.visit(elem)
 
 
+    def visit_AugAssign(self, node):
+        self.aug_assign_ = True
+        self.generic_visit(node)
+        self.aug_assign_ = False
+
+
     def visit_Name(self, node):
         # If it is from the argument list or loop variable, we do not worry about it!
         if node.id in self._args.keys():
@@ -62,6 +69,8 @@ def visit_Name(self, node):
         if node.id not in self.status.keys():
             if not isinstance(node.ctx, ast.Store):
                 raise ValueError('In Python, "first store" indicates "declaration"')
+            if self.aug_assign_:
+                raise ValueError('"First store" cannot be an AugAssign')
             self.status[node.id] = (node, self.scope_level[-1], set())
         else:
             decl, loop, usage = self.status[node.id]
diff --git a/tests/python/unittest/test_hybrid_script.py b/tests/python/unittest/test_hybrid_script.py
index 0f500d7c704f..f725d29e8b57 100644
--- a/tests/python/unittest/test_hybrid_script.py
+++ b/tests/python/unittest/test_hybrid_script.py
@@ -38,7 +38,9 @@ def tvm_val_2_py_val(val):
     module(*nd_args)
 
     for nd, np in to_check:
-        numpy.testing.assert_allclose(nd.asnumpy(), np, rtol=1e-5, atol=1e-5)
+        numpy.testing.assert_allclose(nd.asnumpy(), np, rtol=1e-3, atol=1e-3)
+
+    return module
 
 
 @script
@@ -83,7 +85,7 @@ def test_outer_product():
     func = tvm.lower(ir, [n, m, a, b, c])
     func = tvm.build(func)
 
-    run_and_check(outer_product, [n, m, a, b, c], [c], {n: 999, m: 1001})
+    run_and_check(outer_product, [n, m, a, b, c], [c], {n: 99, m: 101})
 
     for key, _ in HYBRID_GLOBALS.items():
         assert key not in globals().keys()
@@ -165,20 +167,32 @@ def fanout(n, a, b):
     run_and_check(fanout, [n, a, b], [b], {n: 10})
 
 
-@script
-def failure():
-    for i in range(1, 100):
-        i = 0
-
 def test_failure():
     try:
+        @script
+        def failure():
+            for i in range(1, 100):
+                i = 0
         tvm.hybrid.parse(failure, [])
     except IOError as err:
         assert sys.version_info[0] == 2
-        print('[Warning] Case test_failure is skipped by Python2 because "%s"' % str(err))
-    except Exception as err:
+        print('[Warning] Case test_failure.0 is skipped by Python2 because "%s"' % str(err))
+    except ValueError as err:
         assert str(err) == 'You CAN NEVER overwrite a loop variable!'
 
+    try:
+        @tvm.hybrid.script
+        def augdefine():
+            for i in range(10):
+                es += 0
+        tvm.hybrid.parse(augdefine, [])
+    except IOError as err:
+        assert sys.version_info[0] == 2
+        print('[Warning] Case test_failure.1 is skipped by Python2 because "%s"' % str(err))
+    except ValueError as err:
+        assert str(err) == '"First store" cannot be an AugAssign'
+
+
 
 def test_looptype():
     @script
@@ -280,7 +294,7 @@ def blur(a, b):
                 s = 0.0
                 for di in range(3):
                     for dj in range(3):
-                        s = s + a[i-di, j-dj]
+                        s += a[i-di, j-dj]
                 b[i-2, j-2] = s / 9.0
     try:
         a = tvm.placeholder((32, 32), 'float32', 'a')
@@ -315,29 +329,39 @@ def blur2d(a, b):
 
     a = tvm.placeholder((32, 32), 'float32', 'a')
     b = tvm.placeholder((30, 30), 'float32', 'b')
-
     run_and_check(blur2d, [a, b], [b])
 
     if tvm.gpu().exist:
         @tvm.hybrid.script
-        def share_vec_add(a, b, c):
-            shared = allocate((256, ), 'float32', 'shared')
-            for i in bind("threadIdx.x", 256):
-                shared[i] = a[i]
-            local = allocate((256, ), 'float32', 'local')
-            for i in bind("threadIdx.x", 256):
-                local[i] = b[i]
-            for i in bind("threadIdx.x", 256):
-                c[i] = shared[i] + local[i]
-
-        a = tvm.placeholder((256, ), dtype='float32', name='a')
-        b = tvm.placeholder((256, ), dtype='float32', name='b')
-        c = tvm.placeholder((256, ), dtype='float32', name='c')
-        run_and_check(share_vec_add, [a, b, c], [c], target='cuda')
+        def shared_gemm(a, b, c):
+            for io in bind('blockIdx.x', 8):
+                for ii in bind('blockIdx.y', 8):
+                    shared_b = allocate((64, 64), 'float32', 'shared')
+                    for k in range(64):
+                        shared_b[io * 8 + ii, k] = b[io * 8 + ii, k]
+                    for jo in bind('threadIdx.y', 8):
+                        for ji in bind('threadIdx.x', 8):
+                            for k in range(64):
+                                c[io*8+ii, jo*8+ji] += a[io*8+ii, k] * shared_b[k, jo*8+ji]
+
+        a = tvm.placeholder((64, 64), dtype='float32', name='a')
+        b = tvm.placeholder((64, 64), dtype='float32', name='b')
+        c = tvm.placeholder((64, 64), dtype='float32', name='c')
+        module = run_and_check(shared_gemm, [a, b, c], [c], target='cuda')
+        assert "__syncthreads()" in module.imported_modules[0].get_source()
     else:
         print('[Warning] No GPU found! Skip shared mem test!')
 
 
+def test_augassign():
+    @tvm.hybrid.script
+    def augassign(a):
+        for i in range(a.shape[0]):
+            a[i] += 1.0
+    a = tvm.placeholder((16, ), dtype='float32', name='a')
+    run_and_check(augassign, [a], [a])
+
+
 if __name__ == "__main__":
     test_outer_product()
     test_fanout()
@@ -348,4 +372,5 @@ def share_vec_add(a, b, c):
     test_math_intrin()
     test_non_zero()
     test_allocate()
+    test_augassign()
 

From 8e2f7771273f13be35ca7dccbc5d9b75da619ce7 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 20 Jul 2018 17:06:57 -0700
Subject: [PATCH 899/949] Revert "[FRONTEND] [HYBRID] Augmented assign operator
 supported! (#1459)" (#1466)

This reverts commit 33245b8c09009fa2c080e1f42e786ede745de963.
---
 python/tvm/hybrid/parser.py                 |  9 +--
 python/tvm/hybrid/var_decl.py               |  9 ---
 tests/python/unittest/test_hybrid_script.py | 75 +++++++--------------
 3 files changed, 26 insertions(+), 67 deletions(-)

diff --git a/python/tvm/hybrid/parser.py b/python/tvm/hybrid/parser.py
index afb1a1a917fb..1e532367a321 100644
--- a/python/tvm/hybrid/parser.py
+++ b/python/tvm/hybrid/parser.py
@@ -15,7 +15,7 @@
 
 def list_to_block(visit, lst):
     """Convert a list of Python IR nodes to HalideIR Block"""
-    lst = [visit(i) for i in lst]
+    lst = list(map(visit, lst))
     lst = [stmt for stmt in lst if not _ir_pass.Equal(stmt, make_nop())]
     if not lst:
         return make_nop()
@@ -162,13 +162,6 @@ def visit_Name(self, node):
     def visit_Num(self, node):
         return _api.const(node.n)
 
-    def visit_AugAssign(self, node):
-        lhs = self.visit(node.target)
-        rhs = self.visit(node.value)
-        rhs = HybridParser._binop_maker[type(node.op)](lhs, rhs)
-        if not isinstance(lhs, _expr.Call):
-            raise ValueError("The LHS of an AugAssign is supposed to be a call!")
-        return _make.Provide(lhs.func, 0, rhs, lhs.args)
 
     def visit_Assign(self, node):
         if len(node.targets) != 1:
diff --git a/python/tvm/hybrid/var_decl.py b/python/tvm/hybrid/var_decl.py
index 8982b9d39ae9..df38bac1acba 100644
--- a/python/tvm/hybrid/var_decl.py
+++ b/python/tvm/hybrid/var_decl.py
@@ -14,7 +14,6 @@ def __init__(self, args):
         self.scope_level = []
         self._args = {}
         self.args = args
-        self.aug_assign_ = False
 
 
     def visit_FunctionDef(self, node):
@@ -49,12 +48,6 @@ def visit_Call(self, node):
             self.visit(elem)
 
 
-    def visit_AugAssign(self, node):
-        self.aug_assign_ = True
-        self.generic_visit(node)
-        self.aug_assign_ = False
-
-
     def visit_Name(self, node):
         # If it is from the argument list or loop variable, we do not worry about it!
         if node.id in self._args.keys():
@@ -69,8 +62,6 @@ def visit_Name(self, node):
         if node.id not in self.status.keys():
             if not isinstance(node.ctx, ast.Store):
                 raise ValueError('In Python, "first store" indicates "declaration"')
-            if self.aug_assign_:
-                raise ValueError('"First store" cannot be an AugAssign')
             self.status[node.id] = (node, self.scope_level[-1], set())
         else:
             decl, loop, usage = self.status[node.id]
diff --git a/tests/python/unittest/test_hybrid_script.py b/tests/python/unittest/test_hybrid_script.py
index f725d29e8b57..0f500d7c704f 100644
--- a/tests/python/unittest/test_hybrid_script.py
+++ b/tests/python/unittest/test_hybrid_script.py
@@ -38,9 +38,7 @@ def tvm_val_2_py_val(val):
     module(*nd_args)
 
     for nd, np in to_check:
-        numpy.testing.assert_allclose(nd.asnumpy(), np, rtol=1e-3, atol=1e-3)
-
-    return module
+        numpy.testing.assert_allclose(nd.asnumpy(), np, rtol=1e-5, atol=1e-5)
 
 
 @script
@@ -85,7 +83,7 @@ def test_outer_product():
     func = tvm.lower(ir, [n, m, a, b, c])
     func = tvm.build(func)
 
-    run_and_check(outer_product, [n, m, a, b, c], [c], {n: 99, m: 101})
+    run_and_check(outer_product, [n, m, a, b, c], [c], {n: 999, m: 1001})
 
     for key, _ in HYBRID_GLOBALS.items():
         assert key not in globals().keys()
@@ -167,32 +165,20 @@ def fanout(n, a, b):
     run_and_check(fanout, [n, a, b], [b], {n: 10})
 
 
+@script
+def failure():
+    for i in range(1, 100):
+        i = 0
+
 def test_failure():
     try:
-        @script
-        def failure():
-            for i in range(1, 100):
-                i = 0
         tvm.hybrid.parse(failure, [])
     except IOError as err:
         assert sys.version_info[0] == 2
-        print('[Warning] Case test_failure.0 is skipped by Python2 because "%s"' % str(err))
-    except ValueError as err:
+        print('[Warning] Case test_failure is skipped by Python2 because "%s"' % str(err))
+    except Exception as err:
         assert str(err) == 'You CAN NEVER overwrite a loop variable!'
 
-    try:
-        @tvm.hybrid.script
-        def augdefine():
-            for i in range(10):
-                es += 0
-        tvm.hybrid.parse(augdefine, [])
-    except IOError as err:
-        assert sys.version_info[0] == 2
-        print('[Warning] Case test_failure.1 is skipped by Python2 because "%s"' % str(err))
-    except ValueError as err:
-        assert str(err) == '"First store" cannot be an AugAssign'
-
-
 
 def test_looptype():
     @script
@@ -294,7 +280,7 @@ def blur(a, b):
                 s = 0.0
                 for di in range(3):
                     for dj in range(3):
-                        s += a[i-di, j-dj]
+                        s = s + a[i-di, j-dj]
                 b[i-2, j-2] = s / 9.0
     try:
         a = tvm.placeholder((32, 32), 'float32', 'a')
@@ -329,39 +315,29 @@ def blur2d(a, b):
 
     a = tvm.placeholder((32, 32), 'float32', 'a')
     b = tvm.placeholder((30, 30), 'float32', 'b')
+
     run_and_check(blur2d, [a, b], [b])
 
     if tvm.gpu().exist:
         @tvm.hybrid.script
-        def shared_gemm(a, b, c):
-            for io in bind('blockIdx.x', 8):
-                for ii in bind('blockIdx.y', 8):
-                    shared_b = allocate((64, 64), 'float32', 'shared')
-                    for k in range(64):
-                        shared_b[io * 8 + ii, k] = b[io * 8 + ii, k]
-                    for jo in bind('threadIdx.y', 8):
-                        for ji in bind('threadIdx.x', 8):
-                            for k in range(64):
-                                c[io*8+ii, jo*8+ji] += a[io*8+ii, k] * shared_b[k, jo*8+ji]
-
-        a = tvm.placeholder((64, 64), dtype='float32', name='a')
-        b = tvm.placeholder((64, 64), dtype='float32', name='b')
-        c = tvm.placeholder((64, 64), dtype='float32', name='c')
-        module = run_and_check(shared_gemm, [a, b, c], [c], target='cuda')
-        assert "__syncthreads()" in module.imported_modules[0].get_source()
+        def share_vec_add(a, b, c):
+            shared = allocate((256, ), 'float32', 'shared')
+            for i in bind("threadIdx.x", 256):
+                shared[i] = a[i]
+            local = allocate((256, ), 'float32', 'local')
+            for i in bind("threadIdx.x", 256):
+                local[i] = b[i]
+            for i in bind("threadIdx.x", 256):
+                c[i] = shared[i] + local[i]
+
+        a = tvm.placeholder((256, ), dtype='float32', name='a')
+        b = tvm.placeholder((256, ), dtype='float32', name='b')
+        c = tvm.placeholder((256, ), dtype='float32', name='c')
+        run_and_check(share_vec_add, [a, b, c], [c], target='cuda')
     else:
         print('[Warning] No GPU found! Skip shared mem test!')
 
 
-def test_augassign():
-    @tvm.hybrid.script
-    def augassign(a):
-        for i in range(a.shape[0]):
-            a[i] += 1.0
-    a = tvm.placeholder((16, ), dtype='float32', name='a')
-    run_and_check(augassign, [a], [a])
-
-
 if __name__ == "__main__":
     test_outer_product()
     test_fanout()
@@ -372,5 +348,4 @@ def augassign(a):
     test_math_intrin()
     test_non_zero()
     test_allocate()
-    test_augassign()
 

From a3096a8cb7e93280f9fef82e4acb38f32d8efc4b Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Sat, 21 Jul 2018 10:11:05 -0700
Subject: [PATCH 900/949] [AUTOTVM] Misc bug fix (#1467)

---
 python/tvm/autotvm/measure/measure.py         |   5 +-
 python/tvm/autotvm/measure/measure_methods.py |  11 +-
 python/tvm/autotvm/record.py                  | 161 +++++++++++++-----
 python/tvm/autotvm/task/__init__.py           |   2 +-
 python/tvm/autotvm/task/task.py               |  27 +++
 python/tvm/autotvm/tuner/cost_model.py        |   0
 python/tvm/autotvm/tuner/model_based_tuner.py |  15 +-
 .../tvm/autotvm/tuner/sa_model_optimizer.py   |   6 +-
 python/tvm/autotvm/tuner/tuner.py             |  11 +-
 .../tvm/autotvm/tuner/xgboost_cost_model.py   |  31 +++-
 python/tvm/target.py                          |   1 +
 .../unittest/test_autotvm_xgboost_model.py    |  40 +++++
 tutorials/autotvm/tune_cuda_conv2d.py         |  45 ++++-
 tutorials/autotvm/tune_simple_template.py     |   9 +-
 14 files changed, 290 insertions(+), 74 deletions(-)
 delete mode 100644 python/tvm/autotvm/tuner/cost_model.py
 create mode 100644 tests/python/unittest/test_autotvm_xgboost_model.py

diff --git a/python/tvm/autotvm/measure/measure.py b/python/tvm/autotvm/measure/measure.py
index e301aaee8b1d..9f1bf611485a 100644
--- a/python/tvm/autotvm/measure/measure.py
+++ b/python/tvm/autotvm/measure/measure.py
@@ -6,7 +6,6 @@
 import numpy as np
 
 from ... import build, nd, target as _target
-from ...contrib.util import tempdir
 from ...rpc.tracker import Tracker
 from ...rpc.server import Server
 
@@ -209,14 +208,12 @@ def create_measure_batch(task, options):
         kwargs['rpc_device_key'] = rpc_device_key
         kwargs['rpc_tracker_addr'] = (tracker.host, tracker.port)
         kwargs['rpc_timeout'] = timeout
-        kwargs['tmp_dir'] = tempdir()
     elif mode == 'rpc':
         fmeasure = measure_methods.measure_rpc
         kwargs['rpc_device_key'] = rpc_device_key
         kwargs['rpc_priority'] = rpc_priority
         kwargs['rpc_timeout'] = rpc_timeout
         kwargs['use_ndk'] = use_ndk
-        kwargs['tmp_dir'] = tempdir()
         assert rpc_device_key, "In rpc mode, a rpc_device_key must be provided"
     elif mode == "custom":
         assert callable(custom_measure_batch), "In custom mode, custom_measure_func " \
@@ -243,7 +240,7 @@ def create_measure_batch(task, options):
         tvm_buf = [nd.array(x) for x in ref_input]
         func(*tvm_buf)
         ref_output = [x.asnumpy() for x in tvm_buf]
-        kwargs['ref_input'], kwargs['ref_outpu'] = ref_input, ref_output
+        kwargs['ref_input'], kwargs['ref_output'] = ref_input, ref_output
 
     def measure_batch(measure_inputs):
         """measure the time cost for a batch of configs in real machines"""
diff --git a/python/tvm/autotvm/measure/measure_methods.py b/python/tvm/autotvm/measure/measure_methods.py
index b10869bde4cc..9ed7f3a69c84 100644
--- a/python/tvm/autotvm/measure/measure_methods.py
+++ b/python/tvm/autotvm/measure/measure_methods.py
@@ -12,7 +12,7 @@
 
 import numpy as np
 
-from ...contrib import ndk, nvcc
+from ...contrib import ndk, nvcc, util
 from ... import rpc, ir_pass, build, build_config, nd, context, TVMError, register_func
 
 from ..util import get_const_tuple
@@ -113,8 +113,8 @@ def _measure_generic(fbuild, input_pack, ref_input, ref_output):
             if ref_input:
                 args = [nd.array(x, ctx) for x in ref_input]
             else:
-                args = [nd.array(np.random.uniform(size=get_const_tuple(x.shape)).astype(x.dtype),
-                                 ctx) for x in arg_bufs]
+                args = [nd.empty(get_const_tuple(x.shape), dtype=x.dtype,
+                                 ctx=ctx) for x in arg_bufs]
             costs = time_f(*args).results
             if len(costs) > 2:  # remove largest and smallest value to reduce variance
                 costs = list(costs)
@@ -173,7 +173,6 @@ def measure_rpc(input_pack,
                 rpc_tracker_addr=None,
                 rpc_priority=1,
                 rpc_timeout=60,
-                tmp_dir=None,
                 **kwargs):
     """Measure the time cost on a device by rpc
 
@@ -198,9 +197,6 @@ def measure_rpc(input_pack,
     rpc_timeout: int, optional
         timeout of the rpc session
 
-    tmp_dir: tvm.contrib.util.TempDirectory, optional
-        directory to store temp file
-
     kwargs: dict, optional
         Additional key word arguments
 
@@ -213,6 +209,7 @@ def _fbuild(inp):
         """ Local build function."""
         func, args = _build_func(inp, build_option, kwargs)
 
+        tmp_dir = util.tempdir()
         if not kwargs.get('use_ndk', False):
             file_name = "tmp_func_%0x.tar" % getrandbits(64)
             path = tmp_dir.relpath(file_name)
diff --git a/python/tvm/autotvm/record.py b/python/tvm/autotvm/record.py
index 72335fcad20c..d76a18e13425 100644
--- a/python/tvm/autotvm/record.py
+++ b/python/tvm/autotvm/record.py
@@ -9,11 +9,12 @@
 import pickle
 import json
 import time
+import os
 from collections import OrderedDict
 
 import numpy as np
 
-from .. import target, build, lower
+from .. import build, lower, target as _target
 
 from . import task
 from .task import DispatchContext, ConfigEntity
@@ -26,6 +27,11 @@
 except NameError:
     _unicode = ()
 
+try:
+    _long = long
+except NameError:
+    _long = int
+
 
 def measure_str_key(inp, include_config=True):
     """ get unique str key for MeasureInput
@@ -111,7 +117,7 @@ def decode(row, protocol='json'):
     if protocol == 'json':
         row = json.loads(row)
         tgt, task_name, task_args, task_kwargs, workload, config = row['i']
-        tgt = target.create(str(tgt))
+        tgt = _target.create(str(tgt))
 
         def clean_json_to_python(x):
             """1. convert all list in x to tuple (hashable)
@@ -121,6 +127,8 @@ def clean_json_to_python(x):
                 return tuple([clean_json_to_python(a) for a in x])
             if isinstance(x, _unicode):
                 return str(x)
+            if isinstance(x, (_long, int)):
+                return int(x)
             return x
 
         tsk = task.Task(clean_json_to_python(task_name), clean_json_to_python(task_args))
@@ -132,7 +140,7 @@ def clean_json_to_python(x):
         return inp, result
     elif protocol == 'pickle':
         items = row.split("\t")
-        tgt = target.create(items[0])
+        tgt = _target.create(items[0])
         task_tuple = pickle.loads(base64.b64decode(items[1].encode()))
         config = pickle.loads(base64.b64decode(items[2].encode()))
         result = pickle.loads(base64.b64decode(items[3].encode()))
@@ -168,36 +176,70 @@ class ApplyHistoryBest(DispatchContext):
     ----------
     records : str or iterator of (MeasureInput, MeasureResult)
         Collection of tuning records.
-        if is str, then it should be the filename of a records log file.
+        If is str, then it should be the filename of a records log file.
                    Each row of this file is an encoded record pair.
-        otherwise, it is an iterator
+        Otherwise, it is an iterator.
     default: ConfigEntity, optional
-        default config to return when no history records
+        The default config to return when no history records
     """
     def __init__(self, records, default=None):
         super(ApplyHistoryBest, self).__init__()
 
+        self.best_by_targetkey = {}
+        self.best_by_model = {}
+        self._default = default
+
+        self.load(records)
+
+    def load(self, records):
+        """Load records to this dispatch context
+
+        Parameters
+        ----------
+        records : str or iterator of (MeasureInput, MeasureResult)
+            Collection of tuning records.
+            If is str, then it should be the filename of a records log file.
+                       Each row of this file is an encoded record pair.
+            Otherwise, it is an iterator.
+        """
         if isinstance(records, str):
             records = load_from_file(records)
+        if not records:
+            return
+
+        best_by_targetkey = self.best_by_targetkey
+        best_by_model = self.best_by_model
 
         counter = 0
-        best_map = {}
         for inp, res in records:
             counter += 1
             if res.error_no != 0:
                 continue
+
+            # use target keys in tvm target system as key to build best map
             for k in inp.target.keys:
                 key = (k, inp.task.workload)
-                if key not in best_map:
-                    best_map[key] = (inp, res)
+                if key not in best_by_targetkey:
+                    best_by_targetkey[key] = (inp, res)
                 else:
-                    _, other_res = best_map[key]
+                    _, other_res = best_by_targetkey[key]
                     if np.mean(other_res.costs) > np.mean(res.costs):
-                        best_map[key] = (inp, res)
-        logging.info(
-            "Finish load %d records, %d entries selected", counter, len(best_map))
-        self._best_map = best_map
-        self._default = default
+                        best_by_targetkey[key] = (inp, res)
+
+            # use model as key to build best map
+            for opt in inp.target.options:
+                if opt.startswith("-model"):
+                    model = opt[7:]
+                    key = (model, inp.task.workload)
+                    if key not in best_by_model:
+                        best_by_model[key] = (inp, res)
+                    else:
+                        _, other_res = best_by_model[key]
+                        if np.mean(other_res.costs) > np.mean(res.costs):
+                            best_by_model[key] = (inp, res)
+                    break
+
+        logging.info("Finish loading %d records", counter)
 
     def query(self, target, workload):
         if target is None:
@@ -205,29 +247,25 @@ def query(self, target, workload):
                                "Hint: If your target is llvm, use `with tvm.target.create('llvm'):`"
                                " above the dispatcher call. So does other target. ")
 
+        # first try matching by model
+        for opt in target.options:
+            if opt.startswith("-model"):
+                model = opt[7:]
+                key = (model, workload)
+                if key in self.best_by_model:
+                    return self.best_by_model[key][0].config
+
+        # then try matching by target key
         for k in target.keys:
             key = (k, workload)
-            if key in self._best_map:
-                return self._best_map[key][0].config
+            if key in self.best_by_targetkey:
+                return self.best_by_targetkey[key][0].config
 
         if self._default:
             return self._default
         raise RuntimeError(
             "Cannot find config for target=%s, workload=%s" % (target, workload))
 
-    def dump_best(self, out_file):
-        """Dump the best records for each workload to a file
-
-        Parameters
-        ----------
-        out_file: str
-            filename
-        """
-        fout = open(out_file, 'a')
-        for val in self._best_map.values():
-            inp, res = val
-            fout.write(encode(inp, res) + '\n')
-
 
 def split_workload(in_file, clean=True):
     """Split a log file into separate files, each of which contains only a single workload
@@ -243,7 +281,7 @@ def split_workload(in_file, clean=True):
     tic = time.time()
     lines = list(open(in_file).readlines())
 
-    logging.info("start convert...")
+    logging.info("start converting...")
     pool = multiprocessing.Pool()
     lines = pool.map(decode, lines)
     logging.info("map done %.2f", time.time() - tic)
@@ -279,23 +317,69 @@ def split_workload(in_file, clean=True):
                 for inp, res in v:
                     fout.write(encode(inp, res) + '\n')
 
+def pick_best(in_file, out_file):
+    """
+    Pick best entries from a file and store it to another file.
+    This distill the useful log entries from a large log file.
+
+    Parameters
+    ----------
+    in_file: str
+        The filename of input
+    out_file:
+        The filename of output
+    """
+    best_context = ApplyHistoryBest(load_from_file(in_file))
+    best_set = set()
+
+    for v in best_context.best_by_model.values():
+        best_set.add(measure_str_key(v[0]))
+
+    for v in best_context.best_by_targetkey.values():
+        best_set.add(measure_str_key(v[0]))
+
+    logging.info("Extract %d best records from the log file", len(best_set))
+
+    fout = open(out_file, 'w')
+    for inp, res in load_from_file(in_file):
+        if measure_str_key(inp) in best_set:
+            fout.write(encode(inp, res) + "\n")
+
+
+def load_op_param(rootpath=os.path.join(os.path.expanduser('~'), ".tvm", "op_params")):
+    """Load pre-tuned parameters of operators.
+    This function will load all "*.log" file under root path and select best configs.
+
+    Parameters
+    ----------
+    rootpath: str
+        The root path of stored parameters
+    """
+    best_context = ApplyHistoryBest([])
+    for dirpath, _, filenames in os.walk(rootpath):
+        for filename in filenames:
+            if os.path.splitext(filename)[1] == '.log':
+                best_context.load(os.path.join(dirpath, filename))
+
+    assert not DispatchContext.current, "Cannot load pre-tuned parameters inside a dispatch context"
+    DispatchContext.current = best_context
 
 """
 Usage:
 This record executable module has three modes.
 
 * Print log file in readable format
-e.g. python -m autotvm.record --mode read --i collect_conv.tsv --begin 0 --end 5 --ir --code
+e.g. python -m autotvm.record --mode read --i collect_conv.log --begin 0 --end 5 --ir --code
 
 * Extract history best from a large log file
-e.g. python -m autotvm.record --mode best --i collect.tsv
+e.g. python -m autotvm.record --mode pick --i collect.log
 
 * Split a log file into separate files, each of which contains only a single wkl
-e.g. python -m autotvm.record --mode split --i collect.tsv
+e.g. python -m autotvm.record --mode split --i collect.log
 """
 if __name__ == '__main__':
     parser = argparse.ArgumentParser()
-    parser.add_argument("--mode", choices=['read', 'best', 'split'], default='read')
+    parser.add_argument("--mode", choices=['read', 'pick', 'split'], default='read')
     parser.add_argument("--i", type=str, help="input file")
     parser.add_argument("--o", type=str, default=None, help='output file')
     parser.add_argument("--begin", type=int, default=0)
@@ -306,10 +390,9 @@ def split_workload(in_file, clean=True):
     args = parser.parse_args()
     logging.basicConfig(level=logging.INFO)
 
-    if args.mode == 'best':
-        args.o = args.o or args.i + ".best"
-        hist_best = ApplyHistoryBest(load_from_file(args.i))
-        hist_best.dump_best(args.o)
+    if args.mode == 'pick':
+        args.o = args.o or args.i + ".best.log"
+        pick_best(args.i, args.o)
     elif args.mode == 'read':
         for i, (inp, result) in enumerate(load_from_file(args.i)):
             if args.begin <= i < args.end:
diff --git a/python/tvm/autotvm/task/__init__.py b/python/tvm/autotvm/task/__init__.py
index ff59351ef33e..6d719e00d2a4 100644
--- a/python/tvm/autotvm/task/__init__.py
+++ b/python/tvm/autotvm/task/__init__.py
@@ -6,7 +6,7 @@
 of typical tasks of interest.
 """
 
-from .task import Task, create, register, template, get_config
+from .task import Task, create, register, template, get_config, args_to_workload
 from .space import ConfigSpace, ConfigEntity
 from .code_hash import attach_code_hash, attach_code_hash_to_arg
 from .dispatcher import DispatchContext, ApplyConfig, dispatcher
diff --git a/python/tvm/autotvm/task/task.py b/python/tvm/autotvm/task/task.py
index 3b9a3291e6af..8fbb0ddd7aff 100644
--- a/python/tvm/autotvm/task/task.py
+++ b/python/tvm/autotvm/task/task.py
@@ -68,6 +68,33 @@ def instantiate(self, config):
             self.flop = config.flop
         return sch, arg_bufs
 
+    def __getstate__(self):
+        # custom pickle implementation is required for
+        # some unpickable local task functions.
+        # So we only pickle the name of the function
+        # and restore the function by name when unpickling it.
+        return {
+            "name": self.name,
+            "args": self.args,
+            "kwargs": self.kwargs,
+            "config_space": self.config_space,
+            "workload": self.workload,
+            "flop": self.flop,
+            "target": self.target,
+            "target_host": self.target_host
+        }
+
+    def __setstate__(self, state):
+        self.name = state["name"]
+        self.args = state["args"]
+        self.kwargs = state["kwargs"]
+        self.config_space = state["config_space"]
+        self.func = TASK_TABLE.get(state["name"], _raise_error)
+        self.workload = state["workload"]
+        self.flop = state["flop"]
+        self.target = state["target"]
+        self.target_host = state["target_host"]
+
     def __repr__(self):
         return "Task(func_name=%s, args=%s, kwargs=%s, workload=%s)" % (
             self.name, self.args, self.kwargs, self.workload
diff --git a/python/tvm/autotvm/tuner/cost_model.py b/python/tvm/autotvm/tuner/cost_model.py
deleted file mode 100644
index e69de29bb2d1..000000000000
diff --git a/python/tvm/autotvm/tuner/model_based_tuner.py b/python/tvm/autotvm/tuner/model_based_tuner.py
index 93d5cd704d18..91ade1abdc47 100644
--- a/python/tvm/autotvm/tuner/model_based_tuner.py
+++ b/python/tvm/autotvm/tuner/model_based_tuner.py
@@ -264,12 +264,23 @@ def update(self, inputs, results):
             self.train_ct += 1
 
     def load_history(self, data_set):
+        # filter data, only pick the data with a same task
+        data = []
+        for inp, res in data_set:
+            if inp.task.name == self.task.name and \
+                            inp.config.template_key == self.task.config_space.template_key:
+                data.append((inp, res))
+        if not data:
+            return
+
+        # fit base model
         base_model = self.cost_model.clone_new()
-        base_model.fit_log(data_set, self.plan_size)
+        base_model.fit_log(data, self.plan_size)
 
+        # use base model to select initial points
         if not self.trials:
             # no plan yet, use base model to select initial trials
-            maximums = self.model_optimizer.find_maximums(base_model, self.visited)
+            maximums = self.model_optimizer.find_maximums(base_model, self.plan_size, self.visited)
             self.trials = maximums
             self.trial_pt = 0
 
diff --git a/python/tvm/autotvm/tuner/sa_model_optimizer.py b/python/tvm/autotvm/tuner/sa_model_optimizer.py
index 264b00eeaf26..5ba305fcfbee 100644
--- a/python/tvm/autotvm/tuner/sa_model_optimizer.py
+++ b/python/tvm/autotvm/tuner/sa_model_optimizer.py
@@ -30,7 +30,7 @@ class SimulatedAnnealingOptimizer(ModelOptimizer):
         Print log every `verbose` iterations
     """
     def __init__(self, task, n_iter=500, temp=(1, 0), persistent=True, parallel_size=128,
-                 early_stop=30, verbose=50):
+                 early_stop=50, verbose=50):
         super(SimulatedAnnealingOptimizer, self).__init__()
 
         self.task = task
@@ -39,8 +39,8 @@ def __init__(self, task, n_iter=500, temp=(1, 0), persistent=True, parallel_size
         self.n_iter = n_iter
         self.temp = temp
         self.persistent = persistent
-        self.parallel_size = parallel_size
-        self.early_stop = early_stop
+        self.parallel_size = min(parallel_size, len(self.task.config_space))
+        self.early_stop = early_stop or 1e9
         self.verbose = verbose
         self.points = None
 
diff --git a/python/tvm/autotvm/tuner/tuner.py b/python/tvm/autotvm/tuner/tuner.py
index 26a6049b4cba..95afe2eaa3f5 100644
--- a/python/tvm/autotvm/tuner/tuner.py
+++ b/python/tvm/autotvm/tuner/tuner.py
@@ -27,6 +27,7 @@ def __init__(self, task, **kwargs):
         self.best_config = None
         self.best_flops = 0
         self.best_measure_pair = None
+        self.best_iter = 0
 
     def has_next(self):
         """Whether has next untried config in the space
@@ -63,7 +64,7 @@ def update(self, inputs, results):
         """
         pass
 
-    def tune(self, n_trial, measure_option, verbose=1, callbacks=()):
+    def tune(self, n_trial, measure_option, early_stop=None, verbose=1, callbacks=()):
         """Begin tuning
 
         Parameters
@@ -73,6 +74,8 @@ def tune(self, n_trial, measure_option, verbose=1, callbacks=()):
         measure_option: dict
             The options for how to measure generated code.
             You should use the return value ot autotvm.measure_option for this argument.
+        early_stop: int
+            Early stop the tuning when not finding better configs in this number of trials
         verbose: int
             0: silent mode, no output
             1: print every measurement result
@@ -84,6 +87,7 @@ def tune(self, n_trial, measure_option, verbose=1, callbacks=()):
         """
         measure_batch = create_measure_batch(self.task, measure_option)
         parallel_num = getattr(measure_batch, 'parallel_num', 1)
+        early_stop = early_stop or 1e9
 
         i = 0
         while i < n_trial:
@@ -107,6 +111,7 @@ def tune(self, n_trial, measure_option, verbose=1, callbacks=()):
                         self.best_flops = flops
                         self.best_config = config
                         self.best_measure_pair = (inp, res)
+                        self.best_iter = i + k
 
                     logging.info("No: %d\tGFLOPS: %.2f/%.2f\tresult: %s\t%s",
                                  i + k + 1, flops / 1e9, self.best_flops / 1e9,
@@ -119,6 +124,10 @@ def tune(self, n_trial, measure_option, verbose=1, callbacks=()):
             for callback in callbacks:
                 callback(self, inputs, results)
 
+            if i > self.best_iter + early_stop:
+                logging.info("Early stopped. Best iter: %d.", self.best_iter)
+                break
+
         del measure_batch
 
     def reset(self):
diff --git a/python/tvm/autotvm/tuner/xgboost_cost_model.py b/python/tvm/autotvm/tuner/xgboost_cost_model.py
index 657a75e6268b..335956f071dd 100644
--- a/python/tvm/autotvm/tuner/xgboost_cost_model.py
+++ b/python/tvm/autotvm/tuner/xgboost_cost_model.py
@@ -111,6 +111,9 @@ def __init__(self, task, feature_type, loss_type, num_threads=None, verbose=20):
         self.feature_extra_ct = 0
         self.pool = None
         self.base_model = None
+        self.upper_model = None
+
+        self._sample_size = 0
 
         self._reset_pool()
 
@@ -127,20 +130,25 @@ def _reset_pool(self):
         _extract_task = self.task
         self.pool = multiprocessing.Pool(self.num_threads)
 
+    def _base_model_discount(self):
+        return 1.0 / (2 ** (self._sample_size / 50.0))
+
     def fit(self, xs, ys, plan_size):
         tic = time.time()
         self._reset_pool()
 
         x_train = self._get_feature(xs)
         y_train = np.array(ys)
-        y_train /= np.max(y_train)
+        y_train = y_train / np.max(y_train)
 
         valid_index = y_train > 1e-6
         index = np.random.permutation(len(x_train))
         dtrain = xgb.DMatrix(x_train[index], y_train[index])
+        self._sample_size = len(x_train)
 
         if self.base_model:
-            dtrain.set_base_margin(self.base_model.predict(xs, output_margin=True))
+            dtrain.set_base_margin(self._base_model_discount() *
+                                   self.base_model.predict(xs, output_margin=True))
 
         self.bst = xgb.train(self.xgb_params, dtrain,
                              num_boost_round=8000,
@@ -164,6 +172,7 @@ def fit_log(self, records, plan_size):
         self._reset_pool()
 
         args = list(records)
+        logging.info("Load %d entries from history log file", len(args))
         if self.fea_type == 'itervar':
             feature_extract_func = _extract_itervar_feature_log
         elif self.fea_type == 'knob':
@@ -185,7 +194,7 @@ def fit_log(self, records, plan_size):
 
         plan_size *= 2
         self.bst = xgb.train(self.xgb_params, dtrain,
-                             num_boost_round=200,
+                             num_boost_round=400,
                              callbacks=[custom_callback(
                                  stopping_rounds=100,
                                  metric='tr-a-recall@%d' % plan_size,
@@ -203,12 +212,23 @@ def predict(self, xs, output_margin=False):
         dtest = xgb.DMatrix(feas)
 
         if self.base_model:
-            dtest.set_base_margin(self.base_model.predict(xs, output_margin=True))
+            dtest.set_base_margin(self._base_model_discount() *
+                                  self.base_model.predict(xs, output_margin=True))
 
         return self.bst.predict(dtest, output_margin=output_margin)
 
     def load_basemodel(self, base_model):
         self.base_model = base_model
+        if isinstance(base_model, XGBoostCostModel):
+            # share feature cache
+            base_model.feature_cache = self.feature_cache
+
+            # close thread pool
+            if base_model.pool:
+                base_model.pool.terminate()
+                base_model.pool.join()
+                del base_model.pool
+            self.base_model.upper_model = self
 
     def clone_new(self):
         return XGBoostCostModel(self.task, self.fea_type, self.loss_type,
@@ -226,7 +246,8 @@ def _get_feature(self, indexes):
         need_extract = [x for x in indexes if x not in fea_cache]
 
         if need_extract:
-            feas = self.pool.map(self.feature_extract_func, need_extract)
+            pool = self.pool if self.upper_model is None else self.upper_model.pool
+            feas = pool.map(self.feature_extract_func, need_extract)
             for i, fea in zip(need_extract, feas):
                 fea_cache[i] = fea
 
diff --git a/python/tvm/target.py b/python/tvm/target.py
index 7706fb312f9d..f983065306bf 100644
--- a/python/tvm/target.py
+++ b/python/tvm/target.py
@@ -346,6 +346,7 @@ def dispatch_func(func, *args, **kwargs):
         return func(*args, **kwargs)
     fdecorate = decorate(fdefault, dispatch_func)
     fdecorate.register = register
+    fdecorate.fdefault = fdefault
     return fdecorate
 
 
diff --git a/tests/python/unittest/test_autotvm_xgboost_model.py b/tests/python/unittest/test_autotvm_xgboost_model.py
new file mode 100644
index 000000000000..3488d0f599a5
--- /dev/null
+++ b/tests/python/unittest/test_autotvm_xgboost_model.py
@@ -0,0 +1,40 @@
+import time
+
+import numpy as np
+
+import tvm
+from tvm import autotvm
+from tvm.autotvm import MeasureInput, MeasureResult
+from tvm.autotvm.tuner.xgboost_cost_model import XGBoostCostModel
+
+from test_autotvm_common import get_sample_task, get_sample_records
+
+
+def test_fit():
+    task, target = get_sample_task()
+    records = get_sample_records(n=100)
+
+    base_model = XGBoostCostModel(task, feature_type='itervar', loss_type='rank')
+    base_model.fit_log(records, plan_size=32)
+
+    upper_model = XGBoostCostModel(task, feature_type='itervar', loss_type='rank')
+    upper_model.load_basemodel(base_model)
+
+    xs = np.arange(100)
+    ys = np.arange(100)
+
+    upper_model.fit(xs, ys, plan_size=32)
+
+
+def test_tuner():
+    task, target = get_sample_task()
+    records = get_sample_records(n=100)
+
+    tuner = autotvm.tuner.XGBTuner(task)
+    tuner.load_history(records)
+
+
+if __name__ == "__main__":
+    test_fit()
+    test_tuner()
+
diff --git a/tutorials/autotvm/tune_cuda_conv2d.py b/tutorials/autotvm/tune_cuda_conv2d.py
index 5c3facf6184a..b6096aa47a5b 100644
--- a/tutorials/autotvm/tune_cuda_conv2d.py
+++ b/tutorials/autotvm/tune_cuda_conv2d.py
@@ -1,5 +1,5 @@
 """
-How to get high performance convolution kernel on NVIDIA GPU by auto-tuning
+Tuning High Performance Convolution on NVIDIA GPUs
 =========================================================================
 **Author**: `Lianmin Zheng <https://https://github.com/merrymercy>`_
 
@@ -10,9 +10,11 @@
 
 import logging
 import sys
+import numpy as np
 
 import tvm
 import topi
+from topi.testing import conv2d_nchw_python
 
 from tvm import autotvm
 
@@ -133,9 +135,10 @@ def conv2d_no_batching(N, H, W, CI, CO, KH, KW, stride, padding):
 # logging config (for printing tuning log to screen)
 logging.basicConfig(level=logging.INFO, stream=sys.stdout)
 
-# the last layer in resnet 
+# the last layer in resnet
+N, H, W, CO, CI, KH, KW, strides, padding = 1, 7, 7, 512, 512, 3, 3, (1, 1), (1, 1)
 task = autotvm.task.create(conv2d_no_batching,
-                           args=(1, 7, 7, 512, 512, 3, 3, (1, 1), (1, 1)),
+                           args=(N, H, W, CO, CI, KH, KW, strides, padding),
                            target='cuda')
 print(task.config_space)
 
@@ -146,15 +149,43 @@ def conv2d_no_batching(N, H, W, CI, CO, KH, KW, stride, padding):
                                         parallel_num=8,
                                         timeout=20)
 
-# begin tuning, log records to file `cache.tsv`
+# begin tuning, log records to file `conv2d.tsv`
 tuner = autotvm.tuner.XGBTuner(task)
 tuner.tune(n_trial=20,
            measure_option=measure_option,
-           callbacks=[autotvm.callback.log_to_file('cache.tsv')])
+           callbacks=[autotvm.callback.log_to_file('conv2d.log')])
 
-# get best config from cache file
-dispatch_context = autotvm.apply_history_best("cache.tsv")
+#########################################################################
+# Finally we can inspect the best config from log file, check correctness,
+# and measure running time.
+
+# inspect the best config
+dispatch_context = autotvm.apply_history_best("conv2d.log")
 best_config = dispatch_context.query(task.target, task.workload)
 print("\nBest config:")
 print(best_config)
 
+# apply history best from log file
+with autotvm.apply_history_best('conv2d.log'):
+    with tvm.target.create("cuda"):
+        s, arg_bufs = conv2d_no_batching(N, H, W, CO, CI, KH, KW, strides, padding)
+        func = tvm.build(s, arg_bufs)
+
+# check correctness
+a_np = np.random.uniform(size=(N, CI, H, W)).astype(np.float32)
+w_np = np.random.uniform(size=(CO, CI, KH, KW)).astype(np.float32)
+c_np = conv2d_nchw_python(a_np, w_np, strides, padding)
+
+ctx = tvm.gpu()
+a_tvm = tvm.nd.array(a_np, ctx=ctx)
+w_tvm = tvm.nd.array(w_np, ctx=ctx)
+c_tvm = tvm.nd.empty(c_np.shape, ctx=ctx)
+func(a_tvm, w_tvm, c_tvm)
+
+np.testing.assert_allclose(c_np, c_tvm.asnumpy(), rtol=1e-2)
+
+# Evaluate running time. Here we choose a large repeat number (200) to reduce the noise
+# and the overhead of kernel launch. You can also use nvprof to validate the result.
+
+evaluator = func.time_evaluator(func.entry_name, ctx, number=200)
+print('Time cost of this operator: %f' % evaluator(a_tvm, w_tvm, c_tvm).mean)
diff --git a/tutorials/autotvm/tune_simple_template.py b/tutorials/autotvm/tune_simple_template.py
index 4e598155135a..747d4fb4d146 100644
--- a/tutorials/autotvm/tune_simple_template.py
+++ b/tutorials/autotvm/tune_simple_template.py
@@ -243,7 +243,7 @@ def matmul(N, L, M, dtype):
 # 
 # We only make 10 trials in this tutorial for demonstration. In practice,
 # you can do more trials according to your time budget.
-# We will log the tuning results into a cache file. This file can be 
+# We will log the tuning results into a log file. This file can be
 # used to get the best config later.
 
 # logging config (for printing tuning log to screen)
@@ -253,11 +253,11 @@ def matmul(N, L, M, dtype):
 measure_option = autotvm.measure_option(mode='local',
                                         number=5)
 
-# begin tuning, log records to file `cache.tsv`
+# begin tuning, log records to file `matmul.log`
 tuner = autotvm.tuner.RandomTuner(task)
 tuner.tune(n_trial=10,
            measure_option=measure_option,
-           callbacks=[autotvm.callback.log_to_file('cache.tsv')])
+           callbacks=[autotvm.callback.log_to_file('matmul.log')])
 
 #########################################################################
 # Finally we apply history best from the cache file and check its correctness.
@@ -267,7 +267,7 @@ def matmul(N, L, M, dtype):
 # with the same argument.
 
 # apply history best from log file
-with autotvm.apply_history_best('cache.tsv'):
+with autotvm.apply_history_best('matmul.log'):
     with tvm.target.create("llvm"):
         s, arg_bufs = matmul(N, L, M, 'float32')
         func = tvm.build(s, arg_bufs)
@@ -281,4 +281,3 @@ def matmul(N, L, M, dtype):
 func(tvm.nd.array(a_np), tvm.nd.array(b_np), c_tvm)
 
 np.testing.assert_allclose(c_np, c_tvm.asnumpy(), rtol=1e-2)
-

From ff98b0f7c8f853fab9c53b78a6f20355a82dbbd2 Mon Sep 17 00:00:00 2001
From: Yizhi Liu <liuyizhi@apache.org>
Date: Sat, 21 Jul 2018 17:21:38 -0700
Subject: [PATCH 901/949] [tvm4j] fix java build (#1471)

---
 Makefile                                      | 21 ++---
 jvm/README.md                                 |  2 +-
 jvm/assembly/linux-x86_64-cpu/pom.xml         | 55 -----------
 .../src/main/assembly/assembly.xml            | 35 -------
 .../pom.xml                                   |  6 +-
 .../src/main/assembly/assembly.xml            |  2 +-
 .../{osx-x86_64-cpu => osx-x86_64}/pom.xml    |  6 +-
 .../src/main/assembly/assembly.xml            |  2 +-
 jvm/assembly/pom.xml                          | 14 +--
 jvm/conf/log4j.properties                     |  7 ++
 jvm/core/pom.xml                              | 28 +++---
 jvm/core/src/main/java/ml/dmlc/tvm/Base.java  | 10 +-
 .../src/test/java/ml/dmlc/tvm/ModuleTest.java | 12 ++-
 jvm/core/src/test/scripts/test_add_gpu.py     |  3 +
 jvm/native/linux-x86_64-cpu/pom.xml           | 92 -------------------
 .../pom.xml                                   |  4 +-
 .../{osx-x86_64-cpu => osx-x86_64}/pom.xml    |  4 +-
 jvm/native/pom.xml                            | 14 +--
 18 files changed, 70 insertions(+), 247 deletions(-)
 delete mode 100644 jvm/assembly/linux-x86_64-cpu/pom.xml
 delete mode 100644 jvm/assembly/linux-x86_64-gpu/src/main/assembly/assembly.xml
 rename jvm/assembly/{linux-x86_64-gpu => linux-x86_64}/pom.xml (90%)
 rename jvm/assembly/{linux-x86_64-cpu => linux-x86_64}/src/main/assembly/assembly.xml (93%)
 rename jvm/assembly/{osx-x86_64-cpu => osx-x86_64}/pom.xml (90%)
 rename jvm/assembly/{osx-x86_64-cpu => osx-x86_64}/src/main/assembly/assembly.xml (93%)
 create mode 100644 jvm/conf/log4j.properties
 delete mode 100644 jvm/native/linux-x86_64-cpu/pom.xml
 rename jvm/native/{linux-x86_64-gpu => linux-x86_64}/pom.xml (96%)
 rename jvm/native/{osx-x86_64-cpu => osx-x86_64}/pom.xml (96%)

diff --git a/Makefile b/Makefile
index d077b6cc4173..2d3d4843c4c0 100644
--- a/Makefile
+++ b/Makefile
@@ -85,21 +85,20 @@ cyclean:
 
 # JVM build rules
 ifeq ($(OS),Windows_NT)
-	JVM_PKG_PROFILE := windows
-	SHARED_LIBRARY_SUFFIX := dll
+  JVM_PKG_PROFILE := windows
+  SHARED_LIBRARY_SUFFIX := dll
 else
-	UNAME_S := $(shell uname -s)
-	ifeq ($(UNAME_S), Darwin)
-		JVM_PKG_PROFILE := osx-x86_64
-		SHARED_LIBRARY_SUFFIX := dylib
-	else
-		JVM_PKG_PROFILE := linux-x86_64
-		SHARED_LIBRARY_SUFFIX := so
-	endif
+  UNAME_S := $(shell uname -s)
+  ifeq ($(UNAME_S), Darwin)
+    JVM_PKG_PROFILE := osx-x86_64
+    SHARED_LIBRARY_SUFFIX := dylib
+  else
+    JVM_PKG_PROFILE := linux-x86_64
+    SHARED_LIBRARY_SUFFIX := so
+  endif
 endif
 
 JVM_TEST_ARGS := $(if $(JVM_TEST_ARGS),$(JVM_TEST_ARGS),-DskipTests -Dcheckstyle.skip=true)
-JVM_PKG_PROFILE := $(JVM_PKG_PROFILE)-gpu
 
 jvmpkg:
 	(cd $(ROOTDIR)/jvm; \
diff --git a/jvm/README.md b/jvm/README.md
index 46b377674efc..7de31d1576e9 100644
--- a/jvm/README.md
+++ b/jvm/README.md
@@ -38,7 +38,7 @@ make jvmpkg
 (Optional) run unit test by
 
 ```bash
-make jvmpkg JVM_TEST_ARGS="-DskipTests=false"
+sh tests/scripts/task_java_unittest.sh
 ```
 
 After it is compiled and packaged, you can install tvm4j in your local maven repository,
diff --git a/jvm/assembly/linux-x86_64-cpu/pom.xml b/jvm/assembly/linux-x86_64-cpu/pom.xml
deleted file mode 100644
index 06eb95f4d0b8..000000000000
--- a/jvm/assembly/linux-x86_64-cpu/pom.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-  <parent>
-    <groupId>ml.dmlc.tvm</groupId>
-    <artifactId>tvm4j-full-parent</artifactId>
-    <version>0.0.1-SNAPSHOT</version>
-    <relativePath>../pom.xml</relativePath>
-  </parent>
-
-  <groupId>ml.dmlc.tvm</groupId>
-  <artifactId>tvm4j-full-linux-x86_64-cpu</artifactId>
-  <version>0.0.1-SNAPSHOT</version>
-  <name>TVM4J Package - Full Linux-x86_64 CPU-only</name>
-  <packaging>jar</packaging>
-
-  <dependencies>
-    <dependency>
-      <groupId>ml.dmlc.tvm</groupId>
-      <artifactId>tvm4j-core</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>ml.dmlc.tvm</groupId>
-      <artifactId>libtvm4j-linux-x86_64-cpu</artifactId>
-      <version>${project.version}</version>
-      <type>so</type>
-    </dependency>
-  </dependencies>
-
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-assembly-plugin</artifactId>
-        <executions>
-          <execution>
-            <phase>package</phase>
-            <goals>
-              <goal>single</goal>
-            </goals>
-            <configuration>
-              <appendAssemblyId>false</appendAssemblyId>
-              <descriptors>
-                <descriptor>src/main/assembly/assembly.xml</descriptor>
-              </descriptors>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-</project>
diff --git a/jvm/assembly/linux-x86_64-gpu/src/main/assembly/assembly.xml b/jvm/assembly/linux-x86_64-gpu/src/main/assembly/assembly.xml
deleted file mode 100644
index 6326ad432ec7..000000000000
--- a/jvm/assembly/linux-x86_64-gpu/src/main/assembly/assembly.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<assembly>
-  <id>full</id>
-  <formats>
-    <format>jar</format>
-  </formats>
-  <includeBaseDirectory>false</includeBaseDirectory>
-  <files>
-    <file>
-      <source>../../../build/libtvm_runtime.so</source>
-      <outputDirectory>lib/native</outputDirectory>
-      <fileMode>0644</fileMode>
-    </file>
-  </files>
-  <dependencySets>
-    <dependencySet>
-      <includes>
-        <include>*:*:jar</include>
-      </includes>
-      <outputDirectory>/</outputDirectory>
-      <useProjectArtifact>true</useProjectArtifact>
-      <unpack>true</unpack>
-      <scope>runtime</scope>
-    </dependencySet>
-    <dependencySet>
-      <outputDirectory>lib/native</outputDirectory>
-      <outputFileNameMapping>libtvm4j.so</outputFileNameMapping>
-      <unpack>false</unpack>
-      <useProjectArtifact>false</useProjectArtifact>
-      <useStrictFiltering>false</useStrictFiltering>
-      <includes>
-        <include>ml.dmlc.tvm:libtvm4j-linux-x86_64-gpu:so</include>
-      </includes>
-    </dependencySet>
-  </dependencySets>
-</assembly>
diff --git a/jvm/assembly/linux-x86_64-gpu/pom.xml b/jvm/assembly/linux-x86_64/pom.xml
similarity index 90%
rename from jvm/assembly/linux-x86_64-gpu/pom.xml
rename to jvm/assembly/linux-x86_64/pom.xml
index 48e1b869e97d..c539b1434b60 100644
--- a/jvm/assembly/linux-x86_64-gpu/pom.xml
+++ b/jvm/assembly/linux-x86_64/pom.xml
@@ -11,9 +11,9 @@
   </parent>
 
   <groupId>ml.dmlc.tvm</groupId>
-  <artifactId>tvm4j-full-linux-x86_64-gpu</artifactId>
+  <artifactId>tvm4j-full-linux-x86_64</artifactId>
   <version>0.0.1-SNAPSHOT</version>
-  <name>TVM4J Package - Full Linux-x86_64 GPU</name>
+  <name>TVM4J Package - Full Linux-x86_64</name>
   <packaging>jar</packaging>
 
   <dependencies>
@@ -24,7 +24,7 @@
     </dependency>
     <dependency>
       <groupId>ml.dmlc.tvm</groupId>
-      <artifactId>libtvm4j-linux-x86_64-gpu</artifactId>
+      <artifactId>libtvm4j-linux-x86_64</artifactId>
       <version>${project.version}</version>
       <type>so</type>
     </dependency>
diff --git a/jvm/assembly/linux-x86_64-cpu/src/main/assembly/assembly.xml b/jvm/assembly/linux-x86_64/src/main/assembly/assembly.xml
similarity index 93%
rename from jvm/assembly/linux-x86_64-cpu/src/main/assembly/assembly.xml
rename to jvm/assembly/linux-x86_64/src/main/assembly/assembly.xml
index 6139d94fe5a0..d70f1544a9a6 100644
--- a/jvm/assembly/linux-x86_64-cpu/src/main/assembly/assembly.xml
+++ b/jvm/assembly/linux-x86_64/src/main/assembly/assembly.xml
@@ -28,7 +28,7 @@
       <useProjectArtifact>false</useProjectArtifact>
       <useStrictFiltering>false</useStrictFiltering>
       <includes>
-        <include>ml.dmlc.tvm:libtvm4j-linux-x86_64-cpu:so</include>
+        <include>ml.dmlc.tvm:libtvm4j-linux-x86_64:so</include>
       </includes>
     </dependencySet>
   </dependencySets>
diff --git a/jvm/assembly/osx-x86_64-cpu/pom.xml b/jvm/assembly/osx-x86_64/pom.xml
similarity index 90%
rename from jvm/assembly/osx-x86_64-cpu/pom.xml
rename to jvm/assembly/osx-x86_64/pom.xml
index 6e4342fc82a1..a0cb02d295ce 100644
--- a/jvm/assembly/osx-x86_64-cpu/pom.xml
+++ b/jvm/assembly/osx-x86_64/pom.xml
@@ -11,9 +11,9 @@
   </parent>
 
   <groupId>ml.dmlc.tvm</groupId>
-  <artifactId>tvm4j-full-osx-x86_64-cpu</artifactId>
+  <artifactId>tvm4j-full-osx-x86_64</artifactId>
   <version>0.0.1-SNAPSHOT</version>
-  <name>TVM4J Package - Full OSX-x86_64 CPU-only</name>
+  <name>TVM4J Package - Full OSX-x86_64</name>
   <packaging>jar</packaging>
 
   <dependencies>
@@ -24,7 +24,7 @@
     </dependency>
     <dependency>
       <groupId>ml.dmlc.tvm</groupId>
-      <artifactId>libtvm4j-osx-x86_64-cpu</artifactId>
+      <artifactId>libtvm4j-osx-x86_64</artifactId>
       <version>${project.version}</version>
       <type>jnilib</type>
     </dependency>
diff --git a/jvm/assembly/osx-x86_64-cpu/src/main/assembly/assembly.xml b/jvm/assembly/osx-x86_64/src/main/assembly/assembly.xml
similarity index 93%
rename from jvm/assembly/osx-x86_64-cpu/src/main/assembly/assembly.xml
rename to jvm/assembly/osx-x86_64/src/main/assembly/assembly.xml
index 0e8e90580957..caefd426af66 100644
--- a/jvm/assembly/osx-x86_64-cpu/src/main/assembly/assembly.xml
+++ b/jvm/assembly/osx-x86_64/src/main/assembly/assembly.xml
@@ -28,7 +28,7 @@
       <useProjectArtifact>false</useProjectArtifact>
       <useStrictFiltering>false</useStrictFiltering>
       <includes>
-        <include>ml.dmlc.tvm:libtvm4j-osx-x86_64-cpu:jnilib</include>
+        <include>ml.dmlc.tvm:libtvm4j-osx-x86_64:jnilib</include>
       </includes>
     </dependencySet>
   </dependencySets>
diff --git a/jvm/assembly/pom.xml b/jvm/assembly/pom.xml
index 0ee2161acac2..2380df497b56 100644
--- a/jvm/assembly/pom.xml
+++ b/jvm/assembly/pom.xml
@@ -18,21 +18,15 @@
 
   <profiles>
     <profile>
-      <id>osx-x86_64-cpu</id>
+      <id>osx-x86_64</id>
       <modules>
-        <module>osx-x86_64-cpu</module>
+        <module>osx-x86_64</module>
       </modules>
     </profile>
     <profile>
-      <id>linux-x86_64-cpu</id>
+      <id>linux-x86_64</id>
       <modules>
-        <module>linux-x86_64-cpu</module>
-      </modules>
-    </profile>
-    <profile>
-      <id>linux-x86_64-gpu</id>
-      <modules>
-        <module>linux-x86_64-gpu</module>
+        <module>linux-x86_64</module>
       </modules>
     </profile>
     <profile>
diff --git a/jvm/conf/log4j.properties b/jvm/conf/log4j.properties
new file mode 100644
index 000000000000..cb92f4c5250a
--- /dev/null
+++ b/jvm/conf/log4j.properties
@@ -0,0 +1,7 @@
+# for development debugging
+log4j.rootLogger = info, stdout
+
+log4j.appender.stdout = org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.Target = System.out
+log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c] [%p] - %m%n
diff --git a/jvm/core/pom.xml b/jvm/core/pom.xml
index f8160f29dcd0..9ffdba16f5ba 100644
--- a/jvm/core/pom.xml
+++ b/jvm/core/pom.xml
@@ -17,23 +17,16 @@
 
   <profiles>
     <profile>
-      <id>osx-x86_64-cpu</id>
+      <id>osx-x86_64</id>
       <properties>
-        <platform>osx-x86_64-cpu</platform>
+        <platform>osx-x86_64</platform>
         <libtvm.so.filename>libtvm_runtime.dylib</libtvm.so.filename>
       </properties>
     </profile>
     <profile>
-      <id>linux-x86_64-cpu</id>
+      <id>linux-x86_64</id>
       <properties>
-        <platform>linux-x86_64-cpu</platform>
-        <libtvm.so.filename>libtvm_runtime.so</libtvm.so.filename>
-      </properties>
-    </profile>
-    <profile>
-      <id>linux-x86_64-gpu</id>
-      <properties>
-        <platform>linux-x86_64-gpu</platform>
+        <platform>linux-x86_64</platform>
         <libtvm.so.filename>libtvm_runtime.so</libtvm.so.filename>
       </properties>
     </profile>
@@ -92,6 +85,7 @@
           <argLine>
             -Djava.library.path=${project.parent.basedir}/native/${platform}/target
             -Dlibtvm.so.path=${project.parent.basedir}/../build/${libtvm.so.filename}
+            -Dlog4j.configuration=file://${project.parent.basedir}/conf/log4j.properties
           </argLine>
         </configuration>
         <executions>
@@ -119,5 +113,17 @@
       <version>4.11</version>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+      <version>1.7.25</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-simple</artifactId>
+      <version>1.7.25</version>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 </project>
diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/Base.java b/jvm/core/src/main/java/ml/dmlc/tvm/Base.java
index dd4a10e46de5..d380cf5a07c2 100644
--- a/jvm/core/src/main/java/ml/dmlc/tvm/Base.java
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/Base.java
@@ -155,14 +155,8 @@ private static void tryLoadLibraryOS(String libname) throws UnsatisfiedLinkError
    * @throws UnsatisfiedLinkError if loading fails
    */
   private static void tryLoadLibraryXPU(String libname, String arch) throws UnsatisfiedLinkError {
-    try {
-      // try gpu first
-      System.err.println(String.format("Try loading %s-%s-gpu from native path.", libname, arch));
-      System.loadLibrary(String.format("%s-%s-gpu", libname, arch));
-    } catch (UnsatisfiedLinkError e) {
-      System.err.println(String.format("Try loading %s-%s-cpu from native path.", libname, arch));
-      System.loadLibrary(String.format("%s-%s-cpu", libname, arch));
-    }
+    System.err.println(String.format("Try loading %s-%s from native path.", libname, arch));
+    System.loadLibrary(String.format("%s-%s", libname, arch));
   }
 
   // helper function definitions
diff --git a/jvm/core/src/test/java/ml/dmlc/tvm/ModuleTest.java b/jvm/core/src/test/java/ml/dmlc/tvm/ModuleTest.java
index cfec2d2e1f31..5209da48bccb 100644
--- a/jvm/core/src/test/java/ml/dmlc/tvm/ModuleTest.java
+++ b/jvm/core/src/test/java/ml/dmlc/tvm/ModuleTest.java
@@ -19,12 +19,16 @@
 
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import static org.junit.Assert.*;
 
 import java.io.File;
 import java.util.Random;
 
 public class ModuleTest {
+  private final Logger logger = LoggerFactory.getLogger(ModuleTest.class);
   private static String loadingDir;
 
   @BeforeClass
@@ -60,12 +64,16 @@ public void test_load_add_func_cpu() {
   public void test_load_add_func_gpu() {
     final Random RND = new Random(0);
 
+    TVMContext ctx = new TVMContext("gpu", 0);
+    if (!ctx.exist()) {
+      logger.warn("GPU does not exist. Skip the test.");
+      return;
+    }
+
     Module fadd = Module.load(loadingDir + File.separator + "add_gpu.so");
     Module faddDev = Module.load(loadingDir + File.separator + "add_gpu.ptx");
     fadd.importModule(faddDev);
 
-    TVMContext ctx = new TVMContext("gpu", 0);
-
     final int dim = 100;
     long[] shape = new long[]{dim};
     NDArray arr = NDArray.empty(shape, ctx);
diff --git a/jvm/core/src/test/scripts/test_add_gpu.py b/jvm/core/src/test/scripts/test_add_gpu.py
index 4c2415a14b94..ca0d9729c844 100644
--- a/jvm/core/src/test/scripts/test_add_gpu.py
+++ b/jvm/core/src/test/scripts/test_add_gpu.py
@@ -4,6 +4,9 @@
 from tvm.contrib import cc, util
 
 def test_add(target_dir):
+    if not tvm.module.enabled("cuda"):
+        print("skip %s because cuda is not enabled..." % __file__)
+        return
     n = tvm.var("n")
     A = tvm.placeholder((n,), name='A')
     B = tvm.placeholder((n,), name='B')
diff --git a/jvm/native/linux-x86_64-cpu/pom.xml b/jvm/native/linux-x86_64-cpu/pom.xml
deleted file mode 100644
index df920cd1c613..000000000000
--- a/jvm/native/linux-x86_64-cpu/pom.xml
+++ /dev/null
@@ -1,92 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-  <parent>
-    <groupId>ml.dmlc.tvm</groupId>
-    <artifactId>tvm4j-native-parent</artifactId>
-    <version>0.0.1-SNAPSHOT</version>
-    <relativePath>../pom.xml</relativePath>
-  </parent>
-
-  <artifactId>libtvm4j-linux-x86_64-cpu</artifactId>
-  <version>0.0.1-SNAPSHOT</version>
-  <name>TVM4J Package - Native Linux-x86_64 CPU-only</name>
-  <url>http://maven.apache.org</url>
-
-  <packaging>so</packaging>
-
-  <dependencies>
-    <dependency>
-      <groupId>ml.dmlc.tvm</groupId>
-      <artifactId>tvm4j-core</artifactId>
-      <version>${project.version}</version>
-      <type>jar</type>
-      <scope>compile</scope>
-    </dependency>
-  </dependencies>
-
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-compiler-plugin</artifactId>
-      </plugin>
-
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>native-maven-plugin</artifactId>
-        <extensions>true</extensions>
-        <configuration>
-          <!--  trigger javah -->
-          <javahOS>linux</javahOS>
-          <compilerProvider>generic-classic</compilerProvider>
-          <compilerExecutable>${cxx}</compilerExecutable>
-          <linkerExecutable>${cxx}</linkerExecutable>
-          <sources>
-            <source>
-              <directory>../src/main/native</directory>
-              <fileNames>
-                <fileName>ml_dmlc_tvm_native_c_api.cc</fileName>
-              </fileNames>
-            </source>
-          </sources>
-          <compilerStartOptions>
-            <compilerStartOption>-std=c++0x</compilerStartOption>
-          </compilerStartOptions>
-          <compilerEndOptions>
-            <compilerEndOption>-I../../../include</compilerEndOption>
-            <compilerEndOption>${cflags}</compilerEndOption>
-          </compilerEndOptions>
-          <linkerStartOptions>
-            <linkerStartOption>-shared</linkerStartOption>
-          </linkerStartOptions>
-          <linkerEndOptions>
-            <linkerEndOption>${ldflags}</linkerEndOption>
-          </linkerEndOptions>
-        </configuration>
-
-        <executions>
-          <execution>
-            <id>javah</id>
-            <phase>generate-sources</phase>
-            <configuration>
-              <javahOS>linux</javahOS>
-              <javahProvider>default</javahProvider>
-              <javahOutputDirectory>${project.build.directory}/custom-javah</javahOutputDirectory>
-              <workingDirectory>${basedir}</workingDirectory>
-              <javahOutputFileName>ml_dmlc_tvm_native_c_api.h</javahOutputFileName>
-              <javahClassNames>
-                <javahClassName>ml.dmlc.tvm.LibInfo</javahClassName>
-              </javahClassNames>
-            </configuration>
-            <goals>
-              <goal>javah</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-</project>
diff --git a/jvm/native/linux-x86_64-gpu/pom.xml b/jvm/native/linux-x86_64/pom.xml
similarity index 96%
rename from jvm/native/linux-x86_64-gpu/pom.xml
rename to jvm/native/linux-x86_64/pom.xml
index 14f4ce64a399..82fb857db356 100644
--- a/jvm/native/linux-x86_64-gpu/pom.xml
+++ b/jvm/native/linux-x86_64/pom.xml
@@ -10,9 +10,9 @@
     <relativePath>../pom.xml</relativePath>
   </parent>
 
-  <artifactId>libtvm4j-linux-x86_64-gpu</artifactId>
+  <artifactId>libtvm4j-linux-x86_64</artifactId>
   <version>0.0.1-SNAPSHOT</version>
-  <name>TVM4J Package - Native Linux-x86_64 GPU</name>
+  <name>TVM4J Package - Native Linux-x86_64</name>
   <url>http://maven.apache.org</url>
 
   <packaging>so</packaging>
diff --git a/jvm/native/osx-x86_64-cpu/pom.xml b/jvm/native/osx-x86_64/pom.xml
similarity index 96%
rename from jvm/native/osx-x86_64-cpu/pom.xml
rename to jvm/native/osx-x86_64/pom.xml
index b99bc5f839db..52203fcc9bd2 100644
--- a/jvm/native/osx-x86_64-cpu/pom.xml
+++ b/jvm/native/osx-x86_64/pom.xml
@@ -10,9 +10,9 @@
     <relativePath>../pom.xml</relativePath>
   </parent>
 
-  <artifactId>libtvm4j-osx-x86_64-cpu</artifactId>
+  <artifactId>libtvm4j-osx-x86_64</artifactId>
   <version>0.0.1-SNAPSHOT</version>
-  <name>TVM4J Package - Native OSX-x86_64 CPU-only</name>
+  <name>TVM4J Package - Native OSX-x86_64</name>
   <url>http://maven.apache.org</url>
 
   <packaging>jnilib</packaging>
diff --git a/jvm/native/pom.xml b/jvm/native/pom.xml
index f1bc54ef61de..8861a2e20662 100644
--- a/jvm/native/pom.xml
+++ b/jvm/native/pom.xml
@@ -17,21 +17,15 @@
 
   <profiles>
     <profile>
-      <id>osx-x86_64-cpu</id>
+      <id>osx-x86_64</id>
       <modules>
-        <module>osx-x86_64-cpu</module>
+        <module>osx-x86_64</module>
       </modules>
     </profile>
     <profile>
-      <id>linux-x86_64-cpu</id>
+      <id>linux-x86_64</id>
       <modules>
-        <module>linux-x86_64-cpu</module>
-      </modules>
-    </profile>
-    <profile>
-      <id>linux-x86_64-gpu</id>
-      <modules>
-        <module>linux-x86_64-gpu</module>
+        <module>linux-x86_64</module>
       </modules>
     </profile>
   </profiles>

From 953ed7ec33038e31d429d54a57f4efe0e7a3c5b9 Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@cs.washington.edu>
Date: Sun, 22 Jul 2018 21:54:45 -0700
Subject: [PATCH 902/949] [DOC, HARDWARE] Hardware developer guide, migrating
 to use Vivado 2018.2 (#1473)

---
 docs/vta/dev/config.rst                |  70 ++++++
 docs/vta/dev/hardware.rst              | 282 +++++++++++++++++++++++++
 docs/vta/dev/index.rst                 |  14 ++
 docs/vta/index.rst                     |  34 ++-
 docs/vta/install.md                    | 148 +++++++------
 vta/hardware/xilinx/scripts/vivado.tcl |  33 +--
 vta/tutorials/convolution_opt.py       |   4 +-
 vta/tutorials/matrix_multiply.py       |  10 +-
 vta/tutorials/matrix_multiply_opt.py   |   4 +-
 vta/tutorials/resnet.py                |   4 +-
 vta/tutorials/vta_get_started.py       |  10 +-
 11 files changed, 503 insertions(+), 110 deletions(-)
 create mode 100644 docs/vta/dev/config.rst
 create mode 100644 docs/vta/dev/hardware.rst
 create mode 100644 docs/vta/dev/index.rst

diff --git a/docs/vta/dev/config.rst b/docs/vta/dev/config.rst
new file mode 100644
index 000000000000..5b0ee966872b
--- /dev/null
+++ b/docs/vta/dev/config.rst
@@ -0,0 +1,70 @@
+VTA Configuration
+=================
+
+The VTA stack incorporates both a hardware accelerator stack and
+a TVM based software stack.
+VTA incorporates flexibility out of the box: by modifying the
+``vta/config/vta_config.json`` high-level configuration file,
+the user can change the shape of the tensor intrinsic,
+clock frequency, pipelining, data type width, and on-chip buffer sizes.
+
+Parameters Overview
+-------------------
+
+We explain the parameters listed in the ``vta_config.json`` file in the table
+below.
+
++-----------------------+------------+--------------------------------------------------------+
+| Attribute             | Format     | Description                                            |
++=======================+============+========================================================+
+| ``TARGET``            | String     | The TVM device target.                                 |
++-----------------------+------------+--------------------------------------------------------+
+| ``HW_TARGET``         | Int        | FPGA frequency in MHz.                                 |
++-----------------------+------------+--------------------------------------------------------+
+| ``HW_CLK_TARGET``     | Int        | FPGA clock period in ns target for HLS tool.           |
++-----------------------+------------+--------------------------------------------------------+
+| ``HW_VER``            | String     | VTA hardware version number.                           |
++-----------------------+------------+--------------------------------------------------------+
+| ``LOG_INP_WIDTH``     | Int (log2) | Input data type signed integer width.                  |
++-----------------------+------------+--------------------------------------------------------+
+| ``LOG_WGT_WIDTH``     | Int (log2) | Weight data type signed integer width.                 |
++-----------------------+------------+--------------------------------------------------------+
+| ``LOG_ACC_WIDTH``     | Int (log2) | Accumulator data type signed integer width.            |
++-----------------------+------------+--------------------------------------------------------+
+| ``LOG_OUT_WIDTH``     | Int (log2) | Output data type signed integer width.                 |
++-----------------------+------------+--------------------------------------------------------+
+| ``LOG_BATCH``         | Int (log2) | VTA matrix multiply intrinsic output dimension 0.      |
++-----------------------+------------+--------------------------------------------------------+
+| ``LOG_BLOCK_IN``      | Int (log2) | VTA matrix multiply reduction dimension.               |
++-----------------------+------------+--------------------------------------------------------+
+| ``LOG_BLOCK_OUT``     | Int (log2) | VTA matrix multiply intrinsic output dimension 1.      |
++-----------------------+------------+--------------------------------------------------------+
+| ``LOG_UOP_BUFF_SIZE`` | Int (log2) | Micro-op on-chip buffer in Bytes.                      |
++-----------------------+------------+--------------------------------------------------------+
+| ``LOG_INP_BUFF_SIZE`` | Int (log2) | Input on-chip buffer in Bytes.                         |
++-----------------------+------------+--------------------------------------------------------+
+| ``LOG_WGT_BUFF_SIZE`` | Int (log2) | Weight on-chip buffer in Bytes.                        |
++-----------------------+------------+--------------------------------------------------------+
+| ``LOG_ACC_BUFF_SIZE`` | Int (log2) | Accumulator on-chip buffer in Bytes.                   |
++-----------------------+------------+--------------------------------------------------------+
+
+
+ .. note::
+
+    When a parameter name is preceded with ``LOG``, it means that it describes a value that can only be expressed a power of two.
+    For that reason we describe these parameters by their log2 value.
+    For instance, to describe an integer width of 8-bits for the input data types, we set the ``LOG_INP_WIDTH`` to be 3, which is the log2 of 8.
+    Similarly, to descibe a 64kB micro-op buffer, we would set ``LOG_UOP_BUFF_SIZE`` to be 16.
+
+We provide additional detail below regarding each parameter:
+
+ - ``TARGET``: Can be set to ``"pynq"`` or ``"sim"``.
+ - ``HW_TARGET``: In pynq mode, can be set to ``100``, ``142``, ``167``, or ``200`` MHz.
+ - ``HW_CLK_TARGET``: The lower the target, the more pipeline stages HLS will insert to achieve timing closure during place and route (this can also slightly decrease performance).
+ - ``HW_VER``: Hardware version which increments everytime the VTA hardware design changes. This parameter is used to uniquely idenfity hardware bitstreams.
+ - ``LOG_OUT_WIDTH``: We recommend matching ``LOG_OUT_WIDTH`` to ``LOG_INP_WIDTH``.
+ - ``LOG_BATCH``: Equivalent to A in multiplication of shape (A, B) x (B, C), or typically, the batch dimension.
+ - ``LOG_BATCH``: Equivalent to A in multiplication of shape (A, B) x (B, C), or typically, the batch dimension.
+ - ``LOG_BLOCK_IN``: Equivalent to B in multiplication of shape (A, B) x (B, C), or typically, the input channel dimension.
+ - ``LOG_BLOCK_OUT``: Equivalent to C in multiplication of shape (A, B) x (B, C), or typically, the output channel dimension.
+
diff --git a/docs/vta/dev/hardware.rst b/docs/vta/dev/hardware.rst
new file mode 100644
index 000000000000..fd19f969687c
--- /dev/null
+++ b/docs/vta/dev/hardware.rst
@@ -0,0 +1,282 @@
+VTA Hardware Guide
+==================
+
+We present a top-down overview of the VTA hardware design.
+This hardware design guide covers VTA hardware at two levels:
+
+ - An architectural overview of the VTA design and its ISA hardware-software
+   interface.
+ - A micro-architectural overview of the VTA hardware modules, and the
+   micro-code specification for the compute core.
+
+VTA Overview
+------------
+
+VTA is a generic deep learning accelerator built for fast and efficient dense linear algebra.
+VTA incorporates a simple RISC-like processor that can perform dense linear algebra operations on rank 1 or 2 tensor registers.
+In addition the design adopts decoupled access-execute to hide memory access latency.
+
+
+To a broader extent, VTA can serve as a template deep learning accelerator design for full stack optimization, exposing a generic tensor computation interface to the compiler stack.
+
+.. image:: http://raw.githubusercontent.com/uwsaml/web-data/master/vta/blogpost/vta_overview.png
+   :align: center
+   :width: 80%
+
+The figure above gives a high-level overview of the VTA hardware organization.
+VTA is composed of four modules that communicate among each other via FIFO queues and local memory blocks (SRAM), to enable task-level pipeline parallelism:
+
+- The fetch module takes care of loading an instruction stream from DRAM. It also decodes those instructions to route them into one of three command queues.
+- The load module takes care of loading input and weight tensors from DRAM into data-specialized on-chip memories.
+- The compute module performs both dense linear algebra computation with its GEMM core, and general computation with its tensor ALU. It also takes care of loading data from DRAM into the register file, and loading micro-op kernels into the micro-op cache.
+- The store module stores results produced by the compute core back to DRAM.
+
+HLS Hardware Source Organization
+--------------------------------
+
+The VTA design is currently specified in Vivado HLS C++, which is only supported
+by Xilinx toolchains.
+The VTA hardware sources are contained under ``vta/hardware/xilinx/sources``:
+
+ - ``vta.cc`` contains the definitions for each VTA module, as well as a top
+   level behavioral model for the top-level VTA design.
+ - ``vta.h`` contains type definitions using Xilinx ``ap_int`` types, and
+   function prototypes declarations.
+
+In addition preprocessor macros are defined under ``vta/include/vta/hw_spec.h``.
+Much of these macro definitions are derived from the parameters listed in the
+``vta/config/vta_config.json`` file.
+The json file is processed by ``vta/config/vta_config.py`` to produce a string of
+compile flags that define the preprocessor macros.
+That string is used by the makefile in order to set those high-level
+parameters in both the HLS hardware synthesis compiler, and the C++
+compiler that builds the VTA runtime.
+
+HLS Module Example
+~~~~~~~~~~~~~~~~~~
+
+We show a definition of one of the VTA modules defined in C++:
+
+.. code-block:: c
+
+  void fetch(
+    uint32_t insn_count,
+    volatile insn_T *insns,
+    hls::stream<insn_T> &load_queue,
+    hls::stream<insn_T> &gemm_queue,
+    hls::stream<insn_T> &store_queue) {
+  #pragma HLS INTERFACE s_axilite port = insn_count bundle = CONTROL_BUS
+  #pragma HLS INTERFACE m_axi port = insns offset = slave bundle = ins_port
+  #pragma HLS INTERFACE axis port = load_queue
+  #pragma HLS INTERFACE axis port = gemm_queue
+  #pragma HLS INTERFACE axis port = store_queue
+  #pragma HLS INTERFACE s_axilite port = return bundle = CONTROL_BUS
+
+    INSN_DECODE: for (int pc = 0; pc < insn_count; pc++) {
+  #pragma HLS PIPELINE II = 1
+      // Read instruction fields
+      insn_T insn = insns[pc];
+      // Do some partial decoding
+      opcode_T opcode = insn.range(VTA_INSN_MEM_0_1, VTA_INSN_MEM_0_0);
+      memop_id_T memory_type = insn.range(VTA_INSN_MEM_5_1, VTA_INSN_MEM_5_0);
+      // Push to appropriate instruction queue
+      if (opcode == VTA_OPCODE_STORE) {
+        store_queue.write(insn);
+      } else if (opcode == VTA_OPCODE_LOAD &&
+          (memory_type == VTA_MEM_ID_INP || memory_type == VTA_MEM_ID_WGT)) {
+        load_queue.write(insn);
+      } else {
+        gemm_queue.write(insn);
+      }
+    }
+  }
+
+A few observations on HLS coding:
+ - *Parameters:* The parameter list of each function, combined with the
+   interface pragmas define the hardware interface exposed by the
+   generated hardware module.
+
+    - Parameters passed by value indicate a read-only hardware memory-mapped
+      register that the host can write to.
+      This fetch function for instance has an ``insn_count`` parameter
+      which will be synthesized as a memory mapped register for the host
+      to write to, in order to set the length of a given VTA instruction
+      sequence.
+    - Pointer parameters can mean one of two things depending on the interface
+      pragma being used.
+
+       - When used with a ``m_axi`` interface pragma, an AXI master interface
+         gets generated to provide DMA access to DRAM.
+       - When used with a ``bram`` interface pragma, a BRAM interface gets
+         generated to expose read and/or write ports to an FPGA block-RAM.
+    - HLS streams being passed by reference combined with the ``axis`` interface
+      pragma produce FIFO interfaces to the module. Hardware FIFOs provide a
+      useful synchronization mechanism between modules.
+ - *Pragmas*: Compiler pragmas are essential to define hardware implementation
+   of each module. We list several pragmas used in the VTA design to communicate
+   implementation requirements to the compiler.
+
+    - ``HLS INTERFACE``: specifies the interface of the synthesized
+      hardware module.
+    - ``HLS PIPELINE``: defines hardware pipeline performance target by setting
+      an initiation interval goal. When the ``II == 1`` target is set, it tells
+      the compiler that the synthesized hardware pipeline should be able to
+      execute one loop iteration per cycle.
+    - ``HLS DEPENDENCE``: instructs the compiler to ignore certain types
+      of dependence checks in a given loop. Consider a loop body that writes
+      and reads to the same BRAM structure, and needs to achieve an II of 1.
+      The HLS compiler has to assume worst-case scenario, whereby a read is
+      issued to an address that a past write updated the cycle prior: this
+      cannot be achieved given BRAM timing characteristics (it takes at least
+      2 cycles to see the updated value). Therefore in order to achieve an II of 1,
+      the dependence checks have to be relaxed.
+      Note that when turning this optimization on, it falls onto
+      the software stack to prevent writes followed by reads to the same address.
+
+ .. note::
+    This `reference guide <https://www.xilinx.com/support/documentation/sw_manuals/xilinx2018_2/ug902-vivado-high-level-synthesis.pdf>`_
+    provides a much more in-depth, and complete specification of HLS for the Xilinx 2018.2 toolchains.
+
+Architectural Overview
+----------------------
+
+Instruction Set Architecture
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+VTA's instruction set architecture (ISA) is composed of 4 CISC instructions that have a variable execution latency, two of which execute a micro-coded instruction sequence to perform computation.
+
+The VTA instructions are listed below:
+
+- ``LOAD`` instruction: loads a 2D tensor from DRAM into the input buffer, weight buffer, or register file. It can also load a micro-kernel into the micro-op cache. Supports dynamic padding when loading input and weight tiles.
+- ``GEMM`` instruction: performs a micro-op sequence of matrix-matrix multiplications over an input tensor and a weight tensors, and adds the result to a register-file tensor.
+- ``ALU`` instruction: performs a micro-op sequence of matrix-matrix ALU operations over register-file tensor data.
+- ``STORE`` instruction: stores a 2D tensor from the output buffer to DRAM.
+
+The ``LOAD`` instructions are executed by the load and compute modules depending on the store memory buffer location target.
+The ``GEMM`` and ``ALU`` instructions are executed by the compute module's GEMM core and tensor ALU.
+Finally, the ``STORE`` instructions are executed by the store module exclusively.
+The fields of each instruction is described in the figure below.
+The meaning of each field will be further explained in the :ref:`vta-uarch` section.
+
+.. image:: http://raw.githubusercontent.com/uwsaml/web-data/master/vta/developer/vta_instructions.png
+   :align: center
+   :width: 100%
+
+.. note::
+   Note that the VTA ISA changes as VTA's architectural parameters are modified (i.e. GEMM core shape, data type, memory size etc.), and as a result the ISA does not guarantee compatibility across all variants of VTA.
+   This is acceptable however, since the VTA runtime adapts to parameter changes, and produces binary code tailored for the version of the accelerator that gets generated.
+   This exemplifies the co-design philosophy adopted by the VTA stack which embraces fluidity of the hardware-software interface.
+
+Dataflow Execution
+~~~~~~~~~~~~~~~~~~
+
+VTA relies on dependence FIFO queues between hardware modules to synchronize the execution of concurrent tasks.
+The figure below shows how a given hardware module can execute concurrently from its producer and consumer modules in a dataflow fashion through the use of dependence FIFO queues, and single-reader/single-writer SRAM buffers.
+Each module is connected to its consumer and producer via read-after-write (RAW) and write-after-read (WAR) dependence queues.
+
+.. image:: http://raw.githubusercontent.com/uwsaml/web-data/master/vta/developer/dataflow.png
+   :align: center
+   :width: 100%
+
+The pseudo-code above describes how a module executes a given instruction predicated on dependences with other instructions.
+First, the dependence flags within each instruction are decoded in hardware.
+If the instruction has an incoming RAW dependences, execution is predicated upon receiving a RAW dependence token from the producer module.
+Similarly, if the task has an incoming WAR dependence, execution is predicated upon receiving a WAR dependence token from the consumer module.
+Finally when the task is done, we check for outgoing RAW and WAR dependences, and notify the consumer and producer modules respectively.
+
+.. note::
+   Note that the dependence tokens in this scenario are information-less.
+   This is because the instructions executed by each module cannot be reordered by design, as they arrive in FIFO order.
+
+Pipeline Expandability
+~~~~~~~~~~~~~~~~~~~~~~
+
+The default VTA design is composed of four modules that describe a 3-stage ``load-compute-store`` task pipeline.
+Following the dataflow hardware organization principle, we can extend VTA the pipeline to include more stages.
+For example, we can envision separating the tensor ALU from the GEMM core in order to maximize the utilization of the GEMM core.
+This would result in a ``load-gemm-activate-store`` task pipeline which closely reflects the TPU design.
+Adding more stages has a cost however: it can add storage and extra logic overhead, which is why we opted for a default 3-stage pipeline.
+
+.. _vta-uarch:
+Microarchitectural Overview
+----------------------
+
+We describe the modules that compose the VTA design.
+The module definitions are contained in ``vta/hardware/xilinx/sources/vta.cc``.
+
+Fetch Module
+~~~~~~~~~~~~
+
+VTA is programmed by a linear instruction stream.
+The fetch module is the entry point of VTA to the CPU and is programmed via three memory mapped registers:
+
+- The read-write ``control`` register starts the fetch module, and is read to check for its completion.
+- The write-only ``insn_count`` register sets the number of instructions to execute.
+- The write-only ``insns`` register sets the start address of the instruction stream in DRAM.
+
+The CPU prepares the instruction stream in DRAM in a physically-contiguous buffer prepared by the VTA runtime.
+When the instruction stream is ready, the CPU writes the start physical address into the ``insns`` register, the length of the instruction stream into the ``insn_count`` register, and asserts the start signal in the ``control`` register.
+This procedure starts VTA, which reads in the instruction stream from DRAM via DMA. 
+
+Upon accessing the instruction stream, the fetch module partially decodes instructions, and pushes those instructions into command queues that feed into the load, compute, and store modules:
+
+- ``STORE`` instructions are pushed to the store command queue to be processed by the store module.
+- ``GEMM`` and ``ALU`` instructions are pushed to the compute command queue to be processed by the compute module.
+- ``LOAD`` instructions that describe a load operation of micro-op kernels or register file data are pushed to the compute command queue to be processed by the compute module.
+- ``LOAD`` instructions that describe a load operation of input or weight data are pushed to the load command queue to be processed by the load module.
+
+When one of the command queues becomes full, the fetch module stalls until the queue is not full.
+Consequently, the command queues are sized to be deep enough to allow for a wide execution window, and allow multiple tasks to be in flight concurrently across the ``load-compute-store`` pipeline.
+
+
+Compute Module
+~~~~~~~~~~~~~~
+
+VTA's compute module acts as a RISC processor that performs computation on tensor registers rather than scalar registers.
+Two functional units mutate the register file: the tensor ALU, and the GEMM core.
+
+The compute module executes RISC micro-ops from the micro-op cache.
+There are two types of compute micro-ops: ALU and GEMM operations.
+To minimize the footprint of micro-op kernels, while avoiding the need for control-flow instructions such as conditional jumps, the compute module executes micro-op sequences inside a two-level nested loop that computes the location of each tensor register location via an affine function.
+This compression approach helps reduce the micro-kernel instruction footprint, and applies to both matrix multiplication and 2D convolution, commonly found in neural network operators.
+
+.. image:: http://raw.githubusercontent.com/uwsaml/web-data/master/vta/developer/gemm_core.png
+   :align: center
+   :width: 100%
+
+The **GEMM core** evaluates GEMM instructions, by executing a micro-code sequence in a 2-level nested loop described in the Figure above.
+The GEMM core can perform one input-weight matrix multiplication per cycle.
+The dimensions of the single-cycle matrix multiplication defines a hardware *tensorization intrinsic* which the TVM compiler has to lower a computation schedule onto.
+This tensorization intrinsic is defined by the dimensions of the input, weight and accumulator tensors.
+Each data type can have a different integer precision: typically both weight and input types are low-precision (8-bits or less), while the accumulator tensor has a wider type to prevent overflows (32-bits).
+In order to keep the GEMM core busy, each of the input buffer, weight buffer, and register file have to expose sufficient read/write bandwidth.
+
+.. image:: http://raw.githubusercontent.com/uwsaml/web-data/master/vta/developer/alu_core.png
+   :align: center
+   :width: 100%
+
+The **Tensor ALU** supports a set of standard operations to implement common activation, normalization, and pooling operators.
+VTA being a modular design, the range of operators that the Tensor ALU supports can be extended for higher operator coverage, at the expense of higher resource utilization.
+The Tensor ALU can perform tensor-tensor operations, as well as tensor-scalar operations on an immediate value.
+The opcode of the tensor ALU, and the immediate value are specified by the high-level CISC instruction.
+The micro-code in the context of tensor ALU computation only takes care of specifying data access patterns.
+
+.. note::
+   In terms of computational throughput, the Tensor ALU does not execute at a rate of one operation per cycle.
+   The limitation comes from the lack of read-ports: since one register file tensor can be read per cycle, the tensor ALU has an initiation interval of at least 2 (i.e. performs at most 1 operation every 2 cycles).
+   In addition, performing a single tensor-tensor operation at once can be expensive especially given that register file types are wide, typically 32-bit integers.
+   As a result, in order to balance the resource utilization footprint of the Tensor ALU with the GEMM core, a tensor-tensor operation is by default performed via vector-vector operations over multiple cycles.
+
+
+Load and Store Modules
+~~~~~~~~~~~~~~~~~~~~~~
+
+.. image:: http://raw.githubusercontent.com/uwsaml/web-data/master/vta/developer/2d_dma.png
+   :align: center
+   :width: 100%
+
+The load and store modules perform 2D DMA loads with a strided access pattern from DRAM to SRAM.
+In addition, the load module can insert 2D padding on the fly, which is useful when blocking 2D convolution.
+This means that VTA can tile 2D convolution inputs without paying the overhead of re-laying data out in DRAM to insert spatial padding around input and weight tiles.
+
+
diff --git a/docs/vta/dev/index.rst b/docs/vta/dev/index.rst
new file mode 100644
index 000000000000..788bafe34b3e
--- /dev/null
+++ b/docs/vta/dev/index.rst
@@ -0,0 +1,14 @@
+VTA Design and Developer Guide
+==============================
+
+This developer guide details the complete VTA-TVM hardware-software stack.
+
+.. image:: http://raw.githubusercontent.com/uwsaml/web-data/master/vta/blogpost/vta_stack.png
+   :align: center
+   :width: 60%
+
+.. toctree::
+   :maxdepth: 2
+
+   config
+   hardware
\ No newline at end of file
diff --git a/docs/vta/index.rst b/docs/vta/index.rst
index e240fac5cdbb..d29dd9f2ffcf 100644
--- a/docs/vta/index.rst
+++ b/docs/vta/index.rst
@@ -1,22 +1,36 @@
 VTA: Deep Learning Accelerator Stack
 ====================================
-Specialized accelerators are key enablers of future deep learning workloads. TVM stack targets specialized accelerators.
-VTA(versatile tensor accelerator) is a generic, modular open-source deep learning accelerator.
+
+The Versatile Tensor Accelerator (VTA) is an open, generic, and customizable deep learning accelerator with a complete TVM-based compiler stack. We designed VTA to expose the most salient and common characteristics of mainstream deep learning accelerators. Together TVM and VTA form an end-to-end hardware-software deep learning system stack that includes hardware design, drivers, a JIT runtime, and an optimizing compiler stack based on TVM.
+
+.. image:: http://raw.githubusercontent.com/uwsaml/web-data/master/vta/blogpost/vta_overview.png
+   :align: center
+   :width: 60%
+
+VTA has the following key features:
+
+- Generic, modular, open-source hardware.
+- Streamlined workflow to deploy to FPGAs.
+- Simulator support to prototype compilation passes on regular workstations.
+- Pynq-based driver and JIT runtime for both simulated and FPGA hardware back-end.
+- End to end TVM stack integration.
+
 This page contains links to all the resources related to VTA:
 
+
 .. toctree::
    :maxdepth: 1
 
    install
+   dev/index
    tutorials/index
 
 
-Features
---------
-VTA have the following key features:
+Literature
+----------
 
-- Generic, modular open-source hardware
-- Streamlined workflow to deploy to FPGAs.
-- Simulator support to protoype compilation passes on regular workstations.
-- Driver and JIT runtime for both simulated and FPGA hardware backend.
-- End to end TVM stack integration
+- Read the VTA `release blog post`_.
+- Read the VTA tech report: `An Open Hardware Software Stack for Deep Learning`_.
+
+.. _release blog post: https://tvm.ai/2018/07/12/vta-release-announcement.html
+.. _An Open Hardware Software Stack for Deep Learning: https://arxiv.org/abs/1807.04188
\ No newline at end of file
diff --git a/docs/vta/install.md b/docs/vta/install.md
index 8ce92e5f79d3..ca5969386e80 100644
--- a/docs/vta/install.md
+++ b/docs/vta/install.md
@@ -2,15 +2,17 @@ VTA Installation Guide
 ======================
 
 We present three installation guides, each extending on the previous one:
-1. VTA simulation-only installation
-2. VTA hardware testing setup with the [Pynq](http://www.pynq.io/) FPGA development board
-3. VTA hardware compilation tool chain installation
+1. [Simulator installation](#vta-simulator-installation)
+2. [Hardware test setup](#vta-pynq-based-test-setup)
+3. [FPGA toolchain installation](#vta-fpga-toolchain-installation)
 
-## VTA Simulation-Only Installation
+## VTA Simulator Installation
 
-You need [TVM installed](https://docs.tvm.ai/install/index.html) on your machine. For a quick and easy start, use the pre-built Docker image.
-VTA simulator is library will be built by default along with TVM.
-All you need to run the simulator is to add the vta library to your python path.
+You need [TVM installed](https://docs.tvm.ai/install/index.html) on your machine.
+For a quick and easy start, use the pre-built [TVM Docker image](https://docs.tvm.ai/install/docker.html).
+
+The VTA simulator library is built by default with TVM.
+Add the VTA library to your python path to run the VTA examples.
 
 ```bash
 export PYTHONPATH=/path/to/vta/python:${PYTHONPATH}
@@ -18,25 +20,27 @@ export PYTHONPATH=/path/to/vta/python:${PYTHONPATH}
 
 ### Testing your VTA Simulation Setup
 
-Finally to ensure that you've properly installed the VTA package, we can run simple unit tests and the ResNet-18 inference example.
-
-Let's first run the 2D convolution test bench that will only run the ResNet-18 convolution layers.
+To ensure that you've properly installed the VTA python package, run the following 2D convolution testbench.
 
 ```bash
 python <tvm root>/vta/tests/python/integration/test_benchmark_topi_conv2d.py
 ```
 
-> Note: You'll notice that for every convolution layer, the throughput gets reported in GOPS. These numbers are actually the computational throughput that the simulator achieves, by evaluating the convolution in software.
+> Note: You'll notice that for every convolution layer, the throughput gets reported in GOPS. These numbers are actually the computational throughput that the simulator achieves, by evaluating the convolutions in software.
+
+You are invited to try out our [VTA programming tutorials](https://docs.tvm.ai/vta/tutorials/index.html).
 
-You can also try out our [VTA programming tutorials](https://docs.tvm.ai/vta/tutorials/index.html) on the VTA simulator.
 
+### Advanced Configuration (optional)
 
-### Advanced Configuration
+VTA is a generic configurable deep learning accelerator.
+The configuration is specified by `vta_config.json` under the TVM root folder.
+This file provides an architectural specification of the VTA accelerator to parameterize the TVM compiler stack and the VTA hardware stack.
 
-VTA is a generic configurable hardware. The configuration is specified by a `vta_config.json` under root of the TVM folder.
-This file provides an architectural specification of the VTA accelerator that can be understood by both the TVM compiler stack and the VTA hardware stack.
-It also specifies the TVM compiler target. When `TARGET` is set to `sim`, it tells the TVM compiler to execute the TVM workloads on the VTA simulator.
-You can modify the content to reconfigure VTA to a different mode. To do so,
+The VTA configuration file also specifies the TVM compiler target.
+When `TARGET` is set to `sim`, all TVM workloads execute on the VTA simulator.
+You can modify the content of the configuration file to rebuild VTA to a different parameterization.
+To do so,
 
 ```bash
 cd <tvm root>
@@ -45,28 +49,28 @@ cp vta/config/vta_config.json vta_config.json
 make vta
 ```
 
-## VTA Pynq-Based Testing Setup
+## VTA Pynq-Based Test Setup
 
-This second guide extends the *VTA Simulation-Only Installation* guide above to allow FPGA-based hardware tests of the full TVM and VTA software-hardware stack.
+This second guide extends the *VTA Simulator Installation* guide above to run FPGA hardware tests of the complete TVM and VTA software-hardware stack.
 In terms of hardware components you'll need:
 * The [Pynq](http://www.pynq.io/) FPGA development board which can be acquired for $200, or $150 for academics from [Digilent](https://store.digilentinc.com/pynq-z1-python-productivity-for-zynq/).
-* An Ethernet-to-USB adapter to connect the Pynq board to your development computer.
-* An 8+GB micro SD card the (can be ordered with the Pynq dev kit).
-* An AC to DC 12V 3A power adapter (can be ordered with the Pynq dev kit).
+* An Ethernet-to-USB adapter to connect the Pynq board to your development machine.
+* An 8+GB micro SD card.
+* An AC to DC 12V 3A power adapter.
 
-This guide includes:
-1. Pynq board setup instructions
-2. Pynq-side RPC server build and deployment
-3. Revisiting the test examples from the *VTA Simulation-Only Installation* guide, this time executing on the Pynq board
+This guide covers the following themes:
+1. Pynq board setup instructions.
+2. Pynq-side RPC server build and deployment.
+3. Revisiting the test examples from the *VTA Simulator Installation* guide, this time executing on the Pynq board.
 
 ### Pynq Board Setup
 
-Setup your Pynq board based on the *Getting Started* tutorial for the [Pynq board](http://pynq.readthedocs.io/en/latest/getting_started.html). You should follow the instructions up to and including the *Turning On the PYNQ-Z1* steps (no need to pursue *Getting Started* tutorial beyond this point).
-* Make sure that you've downloaded the latest Pynq image, PYNQ-Z1 v2.1 (released 21 Feb 2018), and have imaged your SD card with it.
-* For this particular setup, follow the ["Connect to a Computer"](http://pynq.readthedocs.io/en/latest/getting_started.html#connect-to-a-computer) Ethernet setup instructions.
-  * To be able to talk to the board, make sure to [assign your computer a static IP address](http://pynq.readthedocs.io/en/latest/appendix.html#assign-your-computer-a-static-ip)
+Setup your Pynq board based on the [Pynq board getting started tutorial](http://pynq.readthedocs.io/en/latest/getting_started.html).
+You should follow the instructions up to and including the *Turning On the PYNQ-Z1* step (no need to pursue the tutorial beyond this point).
+* Make sure that you've downloaded the latest Pynq image, [PYNQ-Z1 v2.1](http://pynq-testing.readthedocs.io/en/image_v2.2/getting_started/pynq_image.html) (released 21 Feb 2018), and have imaged your SD card with it (we recommend the free [Etcher](https://etcher.io/) program).
+* For this test setup, follow the ["Connect to a Computer"](http://pynq.readthedocs.io/en/latest/getting_started.html#connect-to-a-computer) Ethernet setup instructions. To be able to talk to the board, make sure to [assign your computer a static IP address](http://pynq.readthedocs.io/en/latest/appendix.html#assign-your-computer-a-static-ip)
 
-Once the board is powered on and connected to your development host machine, try connecting to it to make sure you've properly set up your Pynq board:
+Once the board is powered on and connected to your development machine, try connecting to it to make sure you've properly set up your Pynq board:
 ```bash
 # To connect to the Pynq board use the [username, password] combo: [xilinx, xilinx]
 ssh xilinx@192.168.2.99
@@ -74,9 +78,10 @@ ssh xilinx@192.168.2.99
 
 ### Pynq-Side RPC Server Build & Deployment
 
-Because the direct board-to-computer connection prevents the board from directly accessing the internet, we'll need to mount the Pynq's file system to your development machine's file system with [sshfs](https://www.digitalocean.com/community/tutorials/how-to-use-sshfs-to-mount-remote-file-systems-over-ssh). Next we directly clone the VTA repository into the mountpoint from your development machine.
+Because the direct board-to-computer connection prevents the board from directly accessing the internet, we'll need to mount the Pynq's file system to your development machine's file system with [sshfs](https://www.digitalocean.com/community/tutorials/how-to-use-sshfs-to-mount-remote-file-systems-over-ssh). Next we directly clone the TVM repository into the sshfs mountpoint on your development machine.
 
 ```bash
+# On the Host-side
 mkdir <mountpoint>
 sshfs xilinx@192.168.2.99:/home/xilinx <mountpoint>
 cd <mountpoint>
@@ -95,7 +100,7 @@ ssh xilinx@192.168.2.99
 cd /home/xilinx/tvm
 mkdir build
 cp cmake/config.cmake build/.
-# copy pynq specific configuration
+# Copy pynq specific configuration
 cp vta/config/pynq_sample.json build/vta_config.json
 cd build
 cmake ..
@@ -115,10 +120,11 @@ Tips regarding the Pynq RPC Server:
 * To kill the RPC server, just send the `Ctrl + c` command. You can re-run it with `sudo ./apps/pynq_rpc/start_rpc_server.sh`.
 * If unresponsive, the board can be rebooted by power-cycling it with the physical power switch.
 
-### Testing your VTA Pynq-based Hardware Setup
+### Testing your Pynq-based Hardware Setup
 
-Before running the examples you'll need to configure your host environment as follows:
+Before running the examples on your development machine, you'll need to configure your host environment as follows:
 ```bash
+# On the Host-side
 export VTA_PYNQ_RPC_HOST=192.168.2.99
 export VTA_PYNQ_RPC_PORT=9091
 ```
@@ -128,23 +134,28 @@ Alternatively, you can copy the default `vta/config/pynq_sample.json` into the T
 > Note: in contrast to our simulation setup, there are no libraries to compile on the host side since the host offloads all of the computation to the Pynq board.
 
 ```bash
+# On the Host-side
 cd <tvm root>
 cp vta/config/pynq_sample.json vta_config.json
 ```
 
-This time again, we will run the 2D convolution testbench. But beforehand, we'll need to program the Pynq's own FPGA with a VTA bitstream, and build the VTA runtime on the Pynq via RPC. The following `test_program_rpc.py` script will perform two operations:
+This time again, we will run the 2D convolution testbench.
+Beforehand, we need to program the Pynq board FPGA with a VTA bitstream, and build the VTA runtime via RPC.
+The following `test_program_rpc.py` script will perform two operations:
 * FPGA programming, by downloading a pre-compiled bitstream from a [VTA bitstream repository](https://github.com/uwsaml/vta-distro) that matches the default `vta_config.json` configuration set by the host, and sending it over to the Pynq via RPC to program the Pynq's FPGA.
-* Runtime building on the Pynq, which needs to be run everytime the `vta_config.json` configuration is modified. This ensures that the VTA software runtime that generates the accelerator's executable via just-in-time (JIT) compilation matches the specifications of the VTA design that is programmed on the FPGA. The build process takes about 30 seconds to complete.
+* Runtime building on the Pynq, which needs to be run every time the `vta_config.json` configuration is modified. This ensures that the VTA software runtime that generates the accelerator's executable via just-in-time (JIT) compilation matches the specifications of the VTA design that is programmed on the FPGA. The build process takes about 30 seconds to complete so be patient!
 
 ```bash
+# On the Host-side
 python <tvm root>/vta/tests/python/pynq/test_program_rpc.py
 ```
 
 > Tip: You can track progress of the FPGA programming and the runtime rebuilding steps by looking at the RPC server's logging messages in your Pynq `ssh` session.
 
-We are now ready to run the 2D convolution testbench for the ResNet-18 workload in hardware.
+We are now ready to run the 2D convolution testbench in hardware.
 
 ```bash
+# On the Host-side
 python <tvm root>/vta/tests/python/integration/test_benchmark_topi_conv2d.py
 ```
 
@@ -153,28 +164,29 @@ The performance metrics measured on the Pynq board will be reported for each con
 You can also try out our [VTA programming tutorials](https://docs.tvm.ai/vta/tutorials/index.html).
 
 
-## VTA Hardware Toolchain Installation
+## VTA FPGA Toolchain Installation
 
 This third and last guide allows users to generate custom VTA bitstreams using free-to-use Xilinx compilation toolchains.
 
 ### Xilinx Toolchain Installation
 
-We recommend using `Vivado 2017.1` since our scripts have been tested to work on this version of the Xilinx toolchains. Our guide is written for Linux installation.
+We recommend using `Vivado 2018.2` since our scripts have been tested to work on this version of the Xilinx toolchains.
+Our guide is written for Linux (Ubuntu) installation.
 
-You’ll need to install Xilinx’ FPGA compilation toolchain, [Vivado HL WebPACK 2017.1](https://www.xilinx.com/products/design-tools/vivado.html), which a license-free version of the Vivado HLx toolchain.
+You’ll need to install Xilinx’ FPGA compilation toolchain, [Vivado HL WebPACK 2018.2](https://www.xilinx.com/products/design-tools/vivado.html), which a license-free version of the Vivado HLx toolchain.
 
 #### Obtaining and Launching the Vivado GUI Installer
 
-1. Go to the [download webpage](https://www.xilinx.com/support/download.html), and download the Linux Self Extracting Web Installer for Vivado HL 2017.1 WebPACK and Editions.
+1. Go to the [download webpage](https://www.xilinx.com/support/download/index.html/content/xilinx/en/downloadNav/vivado-design-tools/2018-2.html), and download the Linux Self Extracting Web Installer for Vivado HLx 2018.2: WebPACK and Editions.
 2. You’ll have to sign in with a Xilinx account. This requires a Xilinx account creation that will take 2 minutes.
-3. Complete the Name and Address Verification by clicking “Next”, and you will get the opportunity to download a binary file, called `Xilinx_Vivado_SDK_2017.1_0415_1_Lin64.bin`.
+3. Complete the Name and Address Verification by clicking “Next”, and you will get the opportunity to download a binary file, called `Xilinx_Vivado_SDK_Web_2018.2_0614_1954_Lin64.bin`.
 4. Now that the file is downloaded, go to your `Downloads` directory, and change the file permissions so it can be executed:
 ```bash
-chmod u+x Xilinx_Vivado_SDK_2017.1_0415_1_Lin64.bin
+chmod u+x Xilinx_Vivado_SDK_Web_2018.2_0614_1954_Lin64.bin
 ```
 5. Now you can execute the binary:
 ```bash
-./Xilinx_Vivado_SDK_2017.1_0415_1_Lin64.bin
+./Xilinx_Vivado_SDK_Web_2018.2_0614_1954_Lin64.bin
 ```
 
 #### Xilinx Vivado GUI Installer Steps
@@ -182,17 +194,17 @@ chmod u+x Xilinx_Vivado_SDK_2017.1_0415_1_Lin64.bin
 At this point you've launched the Vivado 2017.1 Installer GUI program.
 
 1. Click “Next” on the *Welcome* screen.
-2. Enter your Xilinx User Credentials under “User Authentication” and select the “Download and Install Now” before clicking “Next” on the *Select Install Type* screen.
-3. Accept all terms before clicking on “Next” on the *Accept License Agreements* screen.
-4. Select the “Vivado HL WebPACK” before clicking on “Next” on the *Select Edition to Install* screen.
+2. On the *Select Install Type* screen, enter your Xilinx user credentials under the “User Authentication” box and select the “Download and Install Now” option before clicking “Next” .
+3. On the *Accept License Agreements* screen, accept all terms before clicking “Next”.
+4. On the *Select Edition to Install* screen, select the “Vivado HL WebPACK” before clicking “Next” .
 5. Under the *Vivado HL WebPACK* screen, before hitting “Next", check the following options (the rest should be unchecked):
    * Design Tools -> Vivado Design Suite -> Vivado
-   * Design Tools -> Vivado Design Suite -> Vivado High Level Synthesis
-   * Devices -> Production Services -> SoCs -> Zynq-7000 Series
-6. Your total download size should be about 3GB and the amount of Disk Space Required 13GB.
-7. Set the installation directory before clicking “Next” on the *Select Destination Directory* screen. It might highlight some paths as red - that’s because the installer doesn’t have the permission to write to that directory. In that case select a path that doesn’t require special write permissions (e.g. in your home directory).
-8. Hit “Install” under the *Installation Summary* screen.
-9. An *Installation Progress Window* will pop-up to track progress of the download and the installation.
+   * Devices -> Production Devices -> SoCs -> Zynq-7000 (if you are targeting the Pynq board)
+   * Devices -> Production Devices -> SoCs -> UltraScale+ MPSoC (if you are targeting the Ultra-96 board)
+6. Your total download size should be about 5GB and the amount of Disk Space Required 23GB.
+7. On the *Select Destination Directory* screen, set the installation directory before clicking “Next”. It might highlight some paths as red - that’s because the installer doesn’t have the permission to write to the directory. In that case select a path that doesn’t require special write permissions (e.g. your home directory).
+8. On the *Installation Summary* screen, hit “Install”.
+9. An *Installation Progress* window will pop-up to track progress of the download and the installation.
 10. This process will take about 20-30 minutes depending on your connection speed.
 11. A pop-up window will inform you that the installation completed successfully. Click "OK".
 12. Finally the *Vivado License Manager* will launch. Select "Get Free ISE WebPACK, ISE/Vivado IP or PetaLinux License" and click "Connect Now" to complete the license registration process.
@@ -201,20 +213,17 @@ At this point you've launched the Vivado 2017.1 Installer GUI program.
 
 The last step is to update your `~/.bashrc` with the following lines. This will include all of the Xilinx binary paths so you can launch compilation scripts from the command line.
 ```bash
-# Xilinx Vivado 2017.1 environmentexport XILINX_VIVADO=${HOME}/Xilinx/SDx/2017.1/Vivado
-export XILINX_VIVADO=${HOME}/Xilinx/SDx/2017.1/Vivado
-export XILINX_HLS=${HOME}/Xilinx/SDx/2017.1/Vivado_HLS
-export XILINX_SDK=${HOME}/Xilinx/SDx/2017.1/SDK
+# Xilinx Vivado 2018.2 environment
+export XILINX_VIVADO=${XILINX_PATH}/Vivado/2018.2
 export PATH=${XILINX_VIVADO}/bin:${PATH}
-export PATH=${XILINX_HLS}/bin:${PATH}
-export PATH=${XILINX_SDK}/bin:${PATH}
 ```
 
 ### Custom VTA Bitstream Compilation
 
-High-level parameters are listed under `<tvm root>/vta/config/vta_config.json` and can be customized by the user. For this custom VTA Bitstream Compilation exercise, we'll change the frequency of our design, so it can be clocked a little faster.
-* Set the `HW_FREQ` field to `142`. The Pynq board supports 100, 142, 167 and 200MHz clocks. Note that the higher the frequency, the harder it will be to close timing. Increasing the frequency can lead to timing violation and thus faulty hardware.
-* Set the `HW_CLK_TARGET` to `6`. This parameters refers to the target clock period in ns passed to HLS - a lower clock period leads to more aggressive pipelining to achieve timing closure at higher frequencies. Technically a 142MHz clock would require a 7ns target, but we intentionally lower the clock target to 6ns to more aggressively pipeline our design.
+High-level hardware parameters are listed in the VTA configuration file and can be customized by the user.
+For this custom VTA bitstream compilation exercise, we'll change the frequency of our design, so it can be clocked a little faster.
+* Set the `HW_FREQ` field to `142`. The Pynq board supports 100, 142, 167 and 200MHz clocks. Note that the higher the frequency, the harder it will be to close timing. Increasing the frequency can lead to timing violation and thus faulty hardware execution.
+* Set the `HW_CLK_TARGET` to `6`. This parameters refers to the target clock period in nano seconds for HLS - a lower clock period leads to more aggressive pipelining to achieve timing closure at higher frequencies. Technically a 142MHz clock would require a 7ns target, but we intentionally lower the clock target to 6ns to more aggressively pipeline our design.
 
 Bitstream generation is driven by a top-level `Makefile` under `<tvm root>/vta/hardware/xilinx/`.
 
@@ -229,25 +238,26 @@ If you just want to generate the HLS-based VTA IP cores without launching the en
 make ip
 ```
 You'll be able to view the HLS synthesis reports under `<tvm root>/vta/build/hardware/xilinx/hls/` `<configuration>/<block>/solution0/syn/report/<block>_csynth.rpt`
-> Note: The `<configuration>` name is a string that summarizes the VTA configuration parameters specified in the `vta_config.json`. The `<block>` name refers to the specific module in the VTA pipeline.
+> Note: The `<configuration>` name is a string that summarizes the VTA configuration parameters listed in the `vta_config.json`. The `<block>` name refers to the specific module (or HLS function) that compose the high-level VTA pipeline.
 
-Finally to run the full hardware compilation and generate the bitstream, run:
+Finally to run the full hardware compilation and generate the VTA bitstream, run:
 
 ```bash
 make
 ```
 
-This process is lenghty, and can take around up to an hour to complete depending on your machine's specs. We recommend setting the `VTA_HW_COMP_THREADS` variable in the Makefile to take full advantage of all the cores on your development machine.
+This process is lengthy, and can take around up to an hour to complete depending on your machine's specs.
+We recommend setting the `VTA_HW_COMP_THREADS` variable in the Makefile to take full advantage of all the cores on your development machine.
 
 Once the compilation completes, the generated bitstream can be found under `<tvm root>/vta/build/hardware/xilinx/vivado/<configuration>/export/vta.bit`.
 
 ### Use the Custom Bitstream
 
-We can change the FPGA bitstream by simply change the bistream path to the configuring API.
+We can program the new VTA FPGA bitstream by setting the bitstream path of the `vta.program_fpga()` function in the tutorial examples, or in the `test_program_rpc.py` script.
 
 ```python
 vta.program_fpga(remote, bitstream="<tvm root>/vta/build/hardware/xilinx/vivado/<configuration>/export/vta.bit")
 ```
 
-Instead of downloading the bitstream from the bitstream repository, the programmer will instead use the custom bitstream you just generated, which is a VTA design clocked at a higher frequency.
-Do you observe a noticable performance increase on the ImageNet inference workload?
+Instead of downloading a pre-built bitstream from the VTA bitstream repository, TVM will instead use the new bitstream you just generated, which is a VTA design clocked at a higher frequency.
+Do you observe a noticeable performance increase on the ImageNet classification example?
diff --git a/vta/hardware/xilinx/scripts/vivado.tcl b/vta/hardware/xilinx/scripts/vivado.tcl
index 58a1f5008e6d..b519d540bd4e 100644
--- a/vta/hardware/xilinx/scripts/vivado.tcl
+++ b/vta/hardware/xilinx/scripts/vivado.tcl
@@ -6,7 +6,7 @@
 #
 
 # Check if script is running in correct Vivado version.
-set scripts_vivado_version 2017.1
+set scripts_vivado_version 2018.2
 set current_vivado_version [version -short]
 
 if { [string first $scripts_vivado_version $current_vivado_version] == -1 } {
@@ -53,7 +53,8 @@ if { [llength $argv] eq 12 } {
   }
 } else {
   puts "Arg list incomplete: <path to ip dir> <num threads> <clock freq> \
-    <inp width> <wgt_width> <out_width> <batch> <in_block / 1024> <out_block>"
+    <inp width> <wgt_width> <out_width> <batch> <batch> <out_block> <in_block
+    <inp_mem_size> <wgt_mem_size> <out_mem_size>"
   return 1
 }
 
@@ -66,6 +67,7 @@ if {[expr $inp_part == 0]} {
   set inp_bus_width $inp_mem_width
 }
 set inp_mem_depth [expr $inp_mem_size * 8 / ($inp_mem_width * $inp_part)]
+
 # Derive weight mem parameters
 set wgt_mem_width [expr $wgt_width * $out_block * $in_block]
 set wgt_bus_width 1024
@@ -75,6 +77,7 @@ if {[expr $wgt_part == 0]} {
   set wgt_bus_width $wgt_mem_width
 }
 set wgt_mem_depth [expr $wgt_mem_size * 8 / ($wgt_mem_width * $wgt_part)]
+
 # Derive output mem parameters
 set out_mem_width [expr $out_width * $batch * $out_block]
 set out_bus_width 1024
@@ -252,7 +255,7 @@ proc create_root_design { parentCell clk inp_part wgt_part out_part inp_bus_widt
   ] $fetch_0
 
   # Create instance: g2l_queue, and set properties
-  set g2l_queue [ create_bd_cell -type ip -vlnv xilinx.com:ip:fifo_generator:13.1 g2l_queue ]
+  set g2l_queue [ create_bd_cell -type ip -vlnv xilinx.com:ip:fifo_generator:13.2 g2l_queue ]
   set_property -dict [ list \
     CONFIG.Empty_Threshold_Assert_Value_axis {1022} \
     CONFIG.Empty_Threshold_Assert_Value_rach {14} \
@@ -273,7 +276,7 @@ proc create_root_design { parentCell clk inp_part wgt_part out_part inp_bus_widt
   ] $g2l_queue
 
   # Create instance: g2s_queue, and set properties
-  set g2s_queue [ create_bd_cell -type ip -vlnv xilinx.com:ip:fifo_generator:13.1 g2s_queue ]
+  set g2s_queue [ create_bd_cell -type ip -vlnv xilinx.com:ip:fifo_generator:13.2 g2s_queue ]
   set_property -dict [ list \
     CONFIG.Empty_Threshold_Assert_Value_axis {1022} \
     CONFIG.Empty_Threshold_Assert_Value_rach {14} \
@@ -294,7 +297,7 @@ proc create_root_design { parentCell clk inp_part wgt_part out_part inp_bus_widt
   ] $g2s_queue
 
   # Create instance: gemm_queue, and set properties
-  set gemm_queue [ create_bd_cell -type ip -vlnv xilinx.com:ip:fifo_generator:13.1 gemm_queue ]
+  set gemm_queue [ create_bd_cell -type ip -vlnv xilinx.com:ip:fifo_generator:13.2 gemm_queue ]
   set_property -dict [ list \
     CONFIG.Empty_Threshold_Assert_Value_axis {510} \
     CONFIG.Empty_Threshold_Assert_Value_rach {14} \
@@ -318,7 +321,7 @@ proc create_root_design { parentCell clk inp_part wgt_part out_part inp_bus_widt
   ] $gemm_queue
 
   # Create instance: l2g_queue, and set properties
-  set l2g_queue [ create_bd_cell -type ip -vlnv xilinx.com:ip:fifo_generator:13.1 l2g_queue ]
+  set l2g_queue [ create_bd_cell -type ip -vlnv xilinx.com:ip:fifo_generator:13.2 l2g_queue ]
   set_property -dict [ list \
     CONFIG.Empty_Threshold_Assert_Value_axis {1022} \
     CONFIG.Empty_Threshold_Assert_Value_rach {14} \
@@ -345,7 +348,7 @@ proc create_root_design { parentCell clk inp_part wgt_part out_part inp_bus_widt
   ] $load_0
 
   # Create instance: load_queue, and set properties
-  set load_queue [ create_bd_cell -type ip -vlnv xilinx.com:ip:fifo_generator:13.1 load_queue ]
+  set load_queue [ create_bd_cell -type ip -vlnv xilinx.com:ip:fifo_generator:13.2 load_queue ]
   set_property -dict [ list \
     CONFIG.Empty_Threshold_Assert_Value_axis {510} \
     CONFIG.Empty_Threshold_Assert_Value_rach {14} \
@@ -406,7 +409,7 @@ proc create_root_design { parentCell clk inp_part wgt_part out_part inp_bus_widt
   ] $processing_system7_1
 
   # Create instance: s2g_queue, and set properties
-  set s2g_queue [ create_bd_cell -type ip -vlnv xilinx.com:ip:fifo_generator:13.1 s2g_queue ]
+  set s2g_queue [ create_bd_cell -type ip -vlnv xilinx.com:ip:fifo_generator:13.2 s2g_queue ]
   set_property -dict [ list \
     CONFIG.Empty_Threshold_Assert_Value_axis {1022} \
     CONFIG.Empty_Threshold_Assert_Value_rach {14} \
@@ -433,7 +436,7 @@ CONFIG.C_M_AXI_DATA_PORT_CACHE_VALUE {"1111"} \
   ] $store_0
 
   # Create instance: store_queue, and set properties
-  set store_queue [ create_bd_cell -type ip -vlnv xilinx.com:ip:fifo_generator:13.1 store_queue ]
+  set store_queue [ create_bd_cell -type ip -vlnv xilinx.com:ip:fifo_generator:13.2 store_queue ]
   set_property -dict [ list \
     CONFIG.Empty_Threshold_Assert_Value_axis {510} \
     CONFIG.Empty_Threshold_Assert_Value_rach {14} \
@@ -466,7 +469,7 @@ CONFIG.NUM_PORTS {5} \
   if {${inp_part} > 1} {
     for {set i 0} {$i < ${inp_part}} {incr i} {
       # Create instance: inp_mem, and set properties
-      set inp_mem [ create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen:8.3 inp_mem_${i} ]
+      set inp_mem [ create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen:8.4 inp_mem_${i} ]
       set_property -dict [ list \
         CONFIG.Byte_Size {8} \
         CONFIG.Enable_32bit_Address {true} \
@@ -494,7 +497,7 @@ CONFIG.NUM_PORTS {5} \
     }
   } else {
       # Create instance: inp_mem, and set properties
-      set inp_mem [ create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen:8.3 inp_mem ]
+      set inp_mem [ create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen:8.4 inp_mem ]
       set_property -dict [ list \
         CONFIG.Byte_Size {8} \
         CONFIG.Enable_32bit_Address {true} \
@@ -525,7 +528,7 @@ CONFIG.NUM_PORTS {5} \
   if {${wgt_part} > 1} {
     for {set i 0} {$i < ${wgt_part}} {incr i} {
       # Create instance: wgt_mem, and set properties
-      set wgt_mem [ create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen:8.3 wgt_mem_${i} ]
+      set wgt_mem [ create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen:8.4 wgt_mem_${i} ]
       set_property -dict [ list \
         CONFIG.Assume_Synchronous_Clk {true} \
         CONFIG.Byte_Size {8} \
@@ -553,7 +556,7 @@ CONFIG.NUM_PORTS {5} \
     }
   } else {
       # Create instance: wgt_mem, and set properties
-      set wgt_mem [ create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen:8.3 wgt_mem ]
+      set wgt_mem [ create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen:8.4 wgt_mem ]
       set_property -dict [ list \
         CONFIG.Assume_Synchronous_Clk {true} \
         CONFIG.Byte_Size {8} \
@@ -584,7 +587,7 @@ CONFIG.NUM_PORTS {5} \
   if {${out_part} > 1} {
     for {set i 0} {$i < ${out_part}} {incr i} {
       # Create instance: out_mem, and set properties
-      set out_mem [ create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen:8.3 out_mem_${i} ]
+      set out_mem [ create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen:8.4 out_mem_${i} ]
       set_property -dict [ list \
         CONFIG.Byte_Size {8} \
         CONFIG.Enable_32bit_Address {true} \
@@ -612,7 +615,7 @@ CONFIG.NUM_PORTS {5} \
     }
   } else {
       # Create instance: out_mem, and set properties
-      set out_mem [ create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen:8.3 out_mem ]
+      set out_mem [ create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen:8.4 out_mem ]
       set_property -dict [ list \
         CONFIG.Byte_Size {8} \
         CONFIG.Enable_32bit_Address {true} \
diff --git a/vta/tutorials/convolution_opt.py b/vta/tutorials/convolution_opt.py
index e581b0c4082f..8e4b77d8b491 100644
--- a/vta/tutorials/convolution_opt.py
+++ b/vta/tutorials/convolution_opt.py
@@ -30,7 +30,7 @@
 from tvm.contrib import util
 from vta.testing import simulator
 
-# Load VTA parameters from the config.json file
+# Load VTA parameters from the vta/config/vta_config.json file
 env = vta.get_env()
 
 # We read the Pynq RPC host IP address and port number from the OS environment
@@ -38,7 +38,7 @@
 port = int(os.environ.get("VTA_PYNQ_RPC_PORT", "9091"))
 
 # We configure both the bitstream and the runtime system on the Pynq
-# to match the VTA configuration specified by the config.json file.
+# to match the VTA configuration specified by the vta_config.json file.
 if env.TARGET == "pynq":
 
     # Make sure that TVM was compiled with RPC=1
diff --git a/vta/tutorials/matrix_multiply.py b/vta/tutorials/matrix_multiply.py
index f7e9e9f66f2d..4c8e716ff665 100644
--- a/vta/tutorials/matrix_multiply.py
+++ b/vta/tutorials/matrix_multiply.py
@@ -26,7 +26,7 @@
 from tvm.contrib import util
 from vta.testing import simulator
 
-# Load VTA parameters from the config.json file
+# Load VTA parameters from the vta/config/vta_config.json file
 env = vta.get_env()
 
 # We read the Pynq RPC host IP address and port number from the OS environment
@@ -34,7 +34,7 @@
 port = int(os.environ.get("VTA_PYNQ_RPC_PORT", "9091"))
 
 # We configure both the bitstream and the runtime system on the Pynq
-# to match the VTA configuration specified by the config.json file.
+# to match the VTA configuration specified by the vta_config.json file.
 if env.TARGET == "pynq":
 
     # Make sure that TVM was compiled with RPC=1
@@ -95,7 +95,7 @@
 #        :width: 480px
 #
 #   The dimensions of that matrix-matrix multiplication are specified in
-#   the :code:`config.json` configuration file.
+#   the :code:`vta_config.json` configuration file.
 #   The activation matrix has a :code:`(BATCH, BLOCK_IN)` shape
 #   and the transposed weight matrix has a :code:`(BLOCK_OUT, BLOCK_IN)` shape,
 #   thus inferring that the resulting output matrix has a
@@ -131,7 +131,7 @@
 #   dimension of VTA's tensor core, but also to match the specific data types
 #   expected by VTA.
 #   VTA for now only supports fixed point data types, which integer width is
-#   specified in the :code:`config.json` file by :code:`INP_WIDTH` and
+#   specified in the :code:`vta_config.json` file by :code:`INP_WIDTH` and
 #   :code:`WGT_WIDTH` for the activations and weights data types respectively.
 #   In addition, the accumulator data type integer width is specified by
 #   :code:`ACC_WIDTH`.
@@ -284,7 +284,7 @@
 #      that stores input matrices of shape :code:`(env.BATCH, env.BLOCK_IN)`
 #      of type :code:`env.inp_dtype`. The input buffer contains
 #      `2 ^ LOG_INP_BUFF_SIZE` matrix elements (as specified in the
-#      :code:`config.json` file).
+#      :code:`vta_config.json` file).
 #    - :code:`env.wgt_scope`: Weight buffer, which is a read-only SRAM buffer
 #      that stores weight matrices of shape :code:`(env.BLOCK_OUT, env.BLOCK_IN)`
 #      of type :code:`env.wgt_dtype`. The weight buffer contains
diff --git a/vta/tutorials/matrix_multiply_opt.py b/vta/tutorials/matrix_multiply_opt.py
index 0150f1ed460b..4e1f4167f84b 100644
--- a/vta/tutorials/matrix_multiply_opt.py
+++ b/vta/tutorials/matrix_multiply_opt.py
@@ -29,7 +29,7 @@
 from tvm.contrib import util
 from vta.testing import simulator
 
-# Load VTA parameters from the config.json file
+# Load VTA parameters from the vta/config/vta_config.json file
 env = vta.get_env()
 
 # We read the Pynq RPC host IP address and port number from the OS environment
@@ -37,7 +37,7 @@
 port = int(os.environ.get("VTA_PYNQ_RPC_PORT", "9091"))
 
 # We configure both the bitstream and the runtime system on the Pynq
-# to match the VTA configuration specified by the config.json file.
+# to match the VTA configuration specified by the vta_config.json file.
 if env.TARGET == "pynq":
 
     # Make sure that TVM was compiled with RPC=1
diff --git a/vta/tutorials/resnet.py b/vta/tutorials/resnet.py
index d33085c8dc73..7a2b0ab50925 100644
--- a/vta/tutorials/resnet.py
+++ b/vta/tutorials/resnet.py
@@ -38,7 +38,7 @@
 from matplotlib import pyplot as plt
 from PIL import Image
 
-# Load VTA parameters from the config.json file
+# Load VTA parameters from the vta/config/vta_config.json file
 env = vta.get_env()
 
 # Helper to crop an image to a square (224, 224)
@@ -180,7 +180,7 @@ def generate_graph(graph_fn, params_fn, device="vta"):
 port = int(os.environ.get("VTA_PYNQ_RPC_PORT", "9091"))
 
 # We configure both the bitstream and the runtime system on the Pynq
-# to match the VTA configuration specified by the config.json file.
+# to match the VTA configuration specified by the vta_config.json file.
 if env.TARGET == "pynq":
 
     # Make sure that TVM was compiled with RPC=1
diff --git a/vta/tutorials/vta_get_started.py b/vta/tutorials/vta_get_started.py
index 67018372fba6..73f6e2bd5472 100644
--- a/vta/tutorials/vta_get_started.py
+++ b/vta/tutorials/vta_get_started.py
@@ -29,12 +29,12 @@
 # VTA is a modular and customizable design. Consequently, the user
 # is free to modify high-level hardware parameters that affect
 # the hardware design layout.
-# These parameters are specified in the :code:`config.json` file by their
+# These parameters are specified in the :code:`vta_config.json` file by their
 # :code:`log2` values.
 # These VTA parameters can be loaded with the :code:`vta.get_env`
 # function.
 #
-# Finally, the TVM target is specified in the :code:`config.json` file.
+# Finally, the TVM target is also specified in the :code:`vta_config.json` file.
 # When set to *sim*, execution will take place inside of a behavioral
 # VTA simulator.
 # If you want to run this tutorial on the Pynq FPGA development platform,
@@ -58,7 +58,7 @@
 port = int(os.environ.get("VTA_PYNQ_RPC_PORT", "9091"))
 
 # We configure both the bitstream and the runtime system on the Pynq
-# to match the VTA configuration specified by the config.json file.
+# to match the VTA configuration specified by the vta_config.json file.
 if env.TARGET == "pynq":
 
     # Make sure that TVM was compiled with RPC=1
@@ -110,11 +110,11 @@
 # For VTA's general purpose operations such as vector adds, the tile size is
 # :code:`(env.BATCH, env.BLOCK_OUT)`.
 # The dimensions are specified in
-# the :code:`config.json` configuration file and are set by default to
+# the :code:`vta_config.json` configuration file and are set by default to
 # a (1, 16) vector.
 #
 # In addition, A and B's data types also needs to match the :code:`env.acc_dtype`
-# which is set by the :code:`config.json` file to be a 32-bit integer.
+# which is set by the :code:`vta_config.json` file to be a 32-bit integer.
 
 # Output channel factor m - total 64 x 16 = 1024 output channels
 m = 64

From 2d36f61b40c310bdbf39c0c1a3963e1379b8b782 Mon Sep 17 00:00:00 2001
From: Meghan Cowan <cowanmeg@cs.washington.edu>
Date: Sun, 22 Jul 2018 21:56:11 -0700
Subject: [PATCH 903/949] [TOPI] Bitserial low-precision convolution (#1332)

---
 python/tvm/intrin.py                          |  25 ++
 src/codegen/llvm/llvm_module.cc               |   9 +
 tests/python/unittest/test_codegen_llvm.py    |  11 +
 topi/python/topi/generic/nn.py                |  35 ++
 topi/python/topi/nn/__init__.py               |   1 +
 topi/python/topi/nn/bitserial_conv2d.py       | 341 ++++++++++++++++
 topi/python/topi/rasp/__init__.py             |   1 +
 topi/python/topi/rasp/bitserial_conv2d.py     | 365 ++++++++++++++++++
 topi/python/topi/x86/__init__.py              |   1 +
 topi/python/topi/x86/bitserial_conv2d.py      | 316 +++++++++++++++
 .../python/test_topi_bitserial_conv2d.py      | 112 ++++++
 .../python/test_topi_bitserial_conv2d_rasp.py |  56 +++
 12 files changed, 1273 insertions(+)
 create mode 100644 topi/python/topi/nn/bitserial_conv2d.py
 create mode 100644 topi/python/topi/rasp/bitserial_conv2d.py
 create mode 100644 topi/python/topi/x86/bitserial_conv2d.py
 create mode 100644 topi/tests/python/test_topi_bitserial_conv2d.py
 create mode 100644 topi/tests/python/test_topi_bitserial_conv2d_rasp.py

diff --git a/python/tvm/intrin.py b/python/tvm/intrin.py
index 422f2d682d2b..30da873b5dcf 100644
--- a/python/tvm/intrin.py
+++ b/python/tvm/intrin.py
@@ -154,6 +154,31 @@ def call_extern(dtype, func_name, *args):
         dtype, func_name, convert(args), _Call.Extern, None, 0)
 
 
+def call_llvm_intrin(dtype, name, *args):
+    """Build expression by calling an llvm intrinsic function
+
+    Parameters
+    ----------
+    dtype : str
+       The data type of the result.
+
+    name : str
+       The name of the llvm intrinsic function.
+
+    args : list
+       Poistional arguments.
+
+    Returns
+    -------
+    call : Expr
+        The call expression.
+    """
+    import tvm
+    llvm_id = tvm.codegen.llvm_lookup_intrinsic_id(name)
+    assert llvm_id != 0, "%s is not an LLVM intrinsic" % name
+    return call_pure_intrin(dtype, 'llvm_intrin', tvm.const(llvm_id, 'uint32'), *args)
+
+
 def exp(x):
     """Take exponetial of input x.
 
diff --git a/src/codegen/llvm/llvm_module.cc b/src/codegen/llvm/llvm_module.cc
index 2bae52b194f5..99740b0dbdca 100644
--- a/src/codegen/llvm/llvm_module.cc
+++ b/src/codegen/llvm/llvm_module.cc
@@ -282,6 +282,15 @@ class LLVMModuleNode final : public runtime::ModuleNode {
   std::shared_ptr<llvm::LLVMContext> ctx_;
 };
 
+unsigned LookupLLVMIntrinsic(const std::string& name) {
+  return llvm::Function::lookupIntrinsicID(name);
+}
+
+TVM_REGISTER_API("codegen.llvm_lookup_intrinsic_id")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    *rv = static_cast<int64_t>(LookupLLVMIntrinsic(args[0]));
+  });
+
 TVM_REGISTER_API("codegen.build_llvm")
 .set_body([](TVMArgs args, TVMRetValue* rv) {
     std::shared_ptr<LLVMModuleNode> n = std::make_shared<LLVMModuleNode>();
diff --git a/tests/python/unittest/test_codegen_llvm.py b/tests/python/unittest/test_codegen_llvm.py
index f05fad10d273..e07f4aa8f40c 100644
--- a/tests/python/unittest/test_codegen_llvm.py
+++ b/tests/python/unittest/test_codegen_llvm.py
@@ -17,6 +17,16 @@ def test_llvm_intrin():
     func = tvm.ir_pass.MakeAPI(body, "prefetch", [A], 0, True)
     fcode = tvm.build(func, None, "llvm")
 
+def test_llvm_lookup_intrin():
+    ib = tvm.ir_builder.create()
+    m = tvm.var("m")
+    A = ib.pointer("uint8x8", name="A")
+    x = tvm.call_llvm_intrin("uint8x8", "llvm.ctpop.i8", tvm.const(1, 'uint32'), A)
+    ib.emit(x)
+    body = ib.get()
+    func = tvm.ir_pass.MakeAPI(body, "ctpop", [A], 1, True)
+    fcode = tvm.build(func, None, "llvm")
+
 def test_llvm_add_pipeline():
     nn = 1024
     n = tvm.convert(nn)
@@ -324,3 +334,4 @@ def test_alignment():
     test_llvm_flip_pipeline()
     test_llvm_madd_pipeline()
     test_llvm_temp_space()
+    test_llvm_lookup_intrin()
diff --git a/topi/python/topi/generic/nn.py b/topi/python/topi/generic/nn.py
index 08220c15bbf0..fe76b9715d59 100644
--- a/topi/python/topi/generic/nn.py
+++ b/topi/python/topi/generic/nn.py
@@ -143,6 +143,41 @@ def schedule_depthwise_conv2d_nhwc(outs):
     """
     return _default_schedule(outs, False)
 
+@tvm.target.generic_func
+def schedule_bitserial_conv2d_nchw(outs):
+    """Schedule for bitserial_conv2d_nchw
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+          The computation graph description of bitserial_conv2d_nchw
+          in the format of an array of tensors.
+
+    Returns
+    -------
+    sch: Schedule
+        The computation schedule for the op.
+    """
+    return _default_schedule(outs, False)
+
+
+@tvm.target.generic_func
+def schedule_bitserial_conv2d_nhwc(outs):
+    """Schedule for bitserial_conv2d_nhwc
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+          The computation graph description of bitserial_conv2d_nchw
+          in the format of an array of tensors.
+
+    Returns
+    -------
+    sch: Schedule
+        The computation schedule for the op.
+    """
+    return _default_schedule(outs, False)
+
 
 @tvm.target.override_native_generic_func("schedule_reduce")
 def schedule_reduce(outs):
diff --git a/topi/python/topi/nn/__init__.py b/topi/python/topi/nn/__init__.py
index 7b6ee4a86836..690379135e06 100644
--- a/topi/python/topi/nn/__init__.py
+++ b/topi/python/topi/nn/__init__.py
@@ -16,4 +16,5 @@
 from .bnn import *
 from .upsampling import *
 from .local_response_norm import *
+from .bitserial_conv2d import *
 from .l2_normalize import *
diff --git a/topi/python/topi/nn/bitserial_conv2d.py b/topi/python/topi/nn/bitserial_conv2d.py
new file mode 100644
index 000000000000..ca2efb0820c1
--- /dev/null
+++ b/topi/python/topi/nn/bitserial_conv2d.py
@@ -0,0 +1,341 @@
+# pylint: disable=invalid-name, unused-variable, too-many-locals, too-many-arguments, unused-argument
+"""Bitserial Conv2D operators"""
+from __future__ import absolute_import as _abs
+from collections import namedtuple
+import numpy as np
+import tvm
+from topi.transform import concatenate
+from .pad import pad
+from .util import get_pad_tuple
+from ..util import get_const_tuple, get_const_int
+
+# workload description of conv2d
+Workload = namedtuple('Workload',
+                      ['in_dtype', 'out_dtype', 'height', 'width', 'in_filter', 'out_filter',
+                       'hkernel', 'wkernel', 'hpad', 'wpad', 'hstride', 'wstride'])
+
+SpatialPackNCHW = namedtuple('SpatialPack',
+                             ['vh', 'vw', 'vc', 'ba', 'bc'])
+
+SpatialPackNHWC = namedtuple('SpatialPack',
+                             ['vh', 'vw', 'vc', 'ba', 'bc'])
+
+_WORKLOADS = [
+    # workloads of resnet18 on imagenet
+    # input_size, input_size, ic, oc, kh, kw, pad, pad, stride, stride
+    Workload('uint32', 'int32', 56, 56, 64, 64, 3, 3, 1, 1, 1, 1),
+    Workload('uint32', 'int32', 56, 56, 64, 64, 1, 1, 0, 0, 1, 1),
+    Workload('uint32', 'int32', 56, 56, 64, 128, 3, 3, 1, 1, 2, 2),
+    Workload('uint32', 'int32', 56, 56, 64, 128, 1, 1, 0, 0, 2, 2),
+    Workload('uint32', 'int32', 28, 28, 128, 128, 3, 3, 1, 1, 1, 1),
+    Workload('uint32', 'int32', 28, 28, 128, 256, 3, 3, 1, 1, 2, 2),
+    Workload('uint32', 'int32', 28, 28, 128, 256, 1, 1, 0, 0, 2, 2),
+    Workload('uint32', 'int32', 14, 14, 256, 256, 3, 3, 1, 1, 1, 1),
+    Workload('uint32', 'int32', 14, 14, 256, 512, 3, 3, 1, 1, 2, 2),
+    Workload('uint32', 'int32', 14, 14, 256, 512, 1, 1, 0, 0, 2, 2),
+    Workload('uint32', 'int32', 7, 7, 512, 512, 3, 3, 1, 1, 1, 1),
+
+    # workload of alexnet on cifar10
+    Workload('int32', 'int32', 27, 27, 96, 192, 5, 5, 2, 2, 1, 1),
+    Workload('int32', 'int32', 13, 13, 192, 384, 3, 3, 1, 1, 1, 1),
+    Workload('int32', 'int32', 13, 13, 384, 384, 3, 3, 1, 1, 1, 1),
+    Workload('int32', 'int32', 13, 13, 384, 256, 3, 3, 1, 1, 1, 1),
+]
+
+@tvm.target.generic_func
+def bitserial_conv2d(data, kernel, stride, padding, activation_bits, weight_bits,
+                     layout='NCHW', pack_dtype='uint32', out_dtype='int32', dorefa=True):
+    """Bitserial Conv2D operator.
+
+    Parameters
+    ----------
+    input : tvm.Tensor
+        4-D with shape [batch, in_channel, in_height, in_width] or
+                       [batch, in_height, in_width, in_channel]
+
+    filter : tvm.Tensor
+        4-D with shape [num_filter, in_channel, filter_height, filter_width] or
+		       [filter_height, filter_width, in_channel, num_filter]
+
+    stride : int or a list/tuple of two ints
+        stride size, or [stride_height, stride_width]
+
+    padding : int or a list/tuple of two ints
+        padding size, or [pad_height, pad_width]
+
+    layout : str
+        layout of data
+
+    activation_bits: int
+        number of bits used for activations/input elements
+
+    weight_bits: int
+        number of bits used for weight elements
+
+    out_dtype: str
+        return type of convolution
+
+    pack_dtype: str
+        bit packing type
+
+    dorefa: bool
+        preform the bitserial dot-product using 2 popcounts (required for DoReFa-Net)
+
+    Returns
+    -------
+    output : tvm.Tensor
+        4-D with shape [batch, out_channel, out_height, out_width] or
+                       [batch, out_height, out_width, out_channel]
+    """
+    # search platform specific declaration first
+    # default declaration
+    if layout == 'NCHW':
+        return spatial_pack_nchw(data, kernel, stride, padding, activation_bits, weight_bits,
+                                 pack_dtype=pack_dtype, out_dtype=out_dtype, dorefa=dorefa)
+    elif layout == 'NHWC':
+        return spatial_pack_nhwc(data, kernel, stride, padding, activation_bits, weight_bits,
+                                 pack_dtype=pack_dtype, out_dtype=out_dtype, dorefa=dorefa)
+    raise ValueError("not support this layout {} yet".format(layout))
+
+def _get_workload(data, kernel, stride, padding, out_dtype, layout):
+    """ Get the workload structure. """
+    assert layout == "NCHW" or layout == "NHWC", \
+        "Only support layouts NCHW and NHWC"
+    if layout == "NCHW":
+        _, CI, IH, IW = [x.value for x in data.shape]
+        CO, _, KH, KW = [x.value for x in kernel.shape]
+    else: # NHWC
+        IH, IW = data.shape[1].value, data.shape[2].value
+        KH, KW, CI, CO = [x for x in get_const_tuple(kernel.shape)]
+
+    HPAD, WPAD, _, _ = get_pad_tuple(padding, kernel)
+    if isinstance(stride, (tuple, list)):
+        HSTR, WSTR = stride
+    else:
+        HSTR, WSTR = stride, stride
+
+    return Workload(data.dtype, out_dtype, IH, IW, CI, CO, KH, KW, HPAD, WPAD, HSTR, WSTR)
+
+@tvm.target.generic_func
+def _get_schedule(wkl, layout):
+    # pylint: disable=unreachable
+    """ Get the platform specific schedule. """
+    target = tvm.target.current_target()
+    raise RuntimeError(
+        "No schedule for current target:{}".format(target))
+    # This return has no use, merely to supress pylint warning
+    return wkl
+
+def spatial_pack_nchw(data, kernel, stride, padding, in_bits, weight_bits,
+                      pack_dtype, out_dtype, dorefa=False):
+    """ Compute convolution with pack on spatial axes. """
+    assert data.shape[0].value == 1, "spatial pack convolution only support batch size=1"
+    data_q = bitpack(data, in_bits, pack_axis=1, bit_axis=0, pack_type=pack_dtype)
+    kernel_q = bitpack(kernel, weight_bits, pack_axis=1, bit_axis=0, pack_type=pack_dtype)
+    IB, _, CI, H, W = data_q.shape
+    KB, CO, _, KH, KW = kernel_q.shape
+    HPAD, WPAD, _, _ = get_pad_tuple(padding, kernel)
+
+    if isinstance(stride, (tuple, list)):
+        HSTR, WSTR = stride
+    else:
+        HSTR, WSTR = stride, stride
+    HCAT, WCAT = KH-1, KW-1
+
+    wkl = _get_workload(data, kernel, stride, padding, out_dtype, "NCHW")
+    sch = _get_schedule(wkl, "NCHW")
+    VH = sch.vh
+    VW = sch.vw
+    VC = sch.vc
+
+    TH = H + 2*HPAD
+    TW = W + 2*WPAD
+    OH = (H + 2*HPAD - KH) // HSTR + 1
+    OW = (W + 2*WPAD - KW) // WSTR + 1
+
+    dshape = (IB, 1, CI, H, W)
+    dpshape = (IB, 1, CI, TH, TW)
+    dvshape = (1, TH//(VH*HSTR), TW//(VW*WSTR), CI, VH*HSTR+HCAT, VW*WSTR+WCAT, IB)
+
+    kshape = (KB, CO, CI, KH, KW)
+    kvshape = (CO//VC, CI, KH, KW, KB, VC)
+
+    ovshape = (1, CO//VC, OH//VH, OW//VW, VH, VW, VC)
+    oshape = (1, CO, OH, OW)
+
+    DOPAD = (HPAD != 0 and WPAD != 0)
+    if DOPAD:
+        data_pad = pad(data_q, (0, 0, 0, HPAD, WPAD), name="data_pad")
+    else:
+        data_pad = data_q
+
+    data_vec = tvm.compute(dvshape, lambda n, h, w, ci, vh, vw, b: \
+        data_pad[b][n][ci][h*VH*HSTR+vh][w*VW*WSTR+vw], name='data_vec')
+
+    kernel_vec = tvm.compute(kvshape, lambda co, ci, dh, dw, b, vc: \
+        kernel_q[b][co*VC+vc][ci][dh][dw], name='kernel_vec')
+
+    ci = tvm.reduce_axis((0, CI), name='ci')
+    dh = tvm.reduce_axis((0, KH), name='dh')
+    dw = tvm.reduce_axis((0, KW), name='dw')
+    b1 = tvm.reduce_axis((0, IB), name='ib')
+    b2 = tvm.reduce_axis((0, KB), name='kb')
+
+    def _conv(n, co, h, w, vh, vw, vc):
+        b1b2 = (b1+b2).astype(out_dtype)
+        if dorefa:
+            return tvm.sum((tvm.popcount(
+                data_vec[n, h, w, ci, vh*HSTR+dh, vw*WSTR+dw, b1].astype(out_dtype) &
+                kernel_vec[co, ci, dh, dw, b2, vc].astype(out_dtype))  -
+                            tvm.popcount(
+                                data_vec[n, h, w, ci, vh*HSTR+dh, vw*WSTR+dw, b1].astype(out_dtype)
+                                & ~kernel_vec[co, ci, dh, dw, b2, vc]).astype(out_dtype)) << b1b2,
+                           axis=[ci, dh, dw, b1, b2])
+
+        return tvm.sum((tvm.popcount(
+            data_vec[n, h, w, ci, vh*HSTR+dh, vw*WSTR+dw, b1] &
+            kernel_vec[co, ci, dh, dw, b2, vc])).astype(out_dtype) << b1b2,
+                       axis=[ci, dh, dw, b1, b2])
+
+    conv = tvm.compute(ovshape, _conv, name='conv_out')
+
+    return tvm.compute(oshape, lambda n, co, h, w:
+                       conv[n][co//VC][h//VH][w//VW][h%VH][w%VW][co%VC],
+                       name='conv_vec', tag='spatial_bitserial_conv_nchw')
+
+def spatial_pack_nhwc(data, kernel, stride, padding, in_bits, weight_bits,
+                      pack_dtype, out_dtype, dorefa=False):
+    """ Compute convolution with pack on spatial axes. """
+    assert data.shape[0].value == 1, "spatial pack convolution only support batch size=1"
+    data_q = bitpack(data, in_bits, pack_axis=3, bit_axis=4, pack_type=pack_dtype)
+    kernel_q = bitpack(kernel, weight_bits, pack_axis=2, bit_axis=4, pack_type=pack_dtype)
+    _, H, W, CI, IB = data_q.shape
+    KH, KW, _, CO, KB = kernel_q.shape
+    HPAD, WPAD, _, _ = get_pad_tuple(padding, kernel)
+
+    if isinstance(stride, (tuple, list)):
+        HSTR, WSTR = stride
+    else:
+        HSTR, WSTR = stride, stride
+    HCAT, WCAT = KH-1, KW-1
+
+    wkl = _get_workload(data, kernel, stride, padding, out_dtype, "NHWC")
+    sch = _get_schedule(wkl, "NHWC")
+    VH = sch.vh
+    VW = sch.vw
+    VC = sch.vc
+
+    PAD_H = H + 2*HPAD
+    PAD_W = W + 2*WPAD
+    OH = (H + 2*HPAD - KH) // HSTR + 1
+    OW = (W + 2*WPAD - KW) // WSTR + 1
+
+    dvshape = (1, PAD_H//(VH*HSTR), PAD_W//(VW*WSTR), VH*HSTR+HCAT, VW*WSTR+WCAT, CI, IB)
+    kvshape = (CO, KH, KW, CI, VC, KB)
+    ovshape = (1, OH, OW, CO, VH, VW, VC)
+    oshape = (1, OH, OW, CO)
+
+    if (HPAD != 0 and WPAD != 0):
+        data_pad = pad(data_q, (0, HPAD, WPAD, 0, 0), name="data_pad")
+    else:
+        data_pad = data_q
+
+    data_vec = tvm.compute(dvshape, lambda n, h, w, vh, vw, ci, b: \
+        data_pad[n][h*VH*HSTR+vh][w*VW*WSTR+vw][ci][b], name='data_vec')
+
+    kernel_vec = tvm.compute(kvshape, lambda co, dh, dw, ci, vc, b: \
+        kernel_q[dh][dw][ci][co*VC+vc][b], name='kernel_vec')
+
+    ci = tvm.reduce_axis((0, CI), name='ci')
+    dh = tvm.reduce_axis((0, KH), name='dh')
+    dw = tvm.reduce_axis((0, KW), name='dw')
+    b1 = tvm.reduce_axis((0, IB), name='ib')
+    b2 = tvm.reduce_axis((0, KB), name='kb')
+
+    def _conv(n, h, w, co, vh, vw, vc):
+        b1b2 = (b1+b2).astype(out_dtype)
+        if dorefa:
+            return tvm.sum(
+                (tvm.popcount(data_vec[n, h, w, vh*HSTR+dh, vw*WSTR+dw, ci, b1].astype(out_dtype) &
+                              kernel_vec[co, dh, dw, ci, vc, b2].astype(out_dtype)) -
+                 tvm.popcount(data_vec[n, h, w, vh*HSTR+dh, vw*WSTR+dw, ci, b1].astype(out_dtype) &
+                              ~kernel_vec[co, dh, dw, ci, vc, b2]).astype(out_dtype)) << b1b2,
+                axis=[dh, dw, ci, b1, b2])
+
+        return tvm.sum(tvm.popcount(
+            data_vec[n, h, w, vh*HSTR+dh, vw*WSTR+dw, ci, b1] &
+            kernel_vec[co, dh, dw, ci, vc, b2]).astype(out_dtype) << b1b2,
+                       axis=[dh, dw, ci, b1, b2])
+
+    conv = tvm.compute(ovshape, _conv, name='conv')
+
+    return tvm.compute(oshape, lambda n, h, w, co:
+                       conv[n][h//VH][w//VW][co//VC][h%VH][w%VW][co%VC],
+                       name='output_unpack', tag='spatial_bitserial_conv_nhwc')
+
+def bitpack(data, bits, pack_axis, bit_axis, pack_type, name="QuantizeInput"):
+    """Packs data into format necessary for bitserial computation
+    pack_axis : int
+       index of the axis to pack in data
+    bit_axis : int
+       index of axis to place bit axis in resulting packed data"""
+    ishape = data.shape
+    n = len(ishape)
+    if pack_type == 'uint8':
+        data_width = 8
+    elif pack_type == 'uint16':
+        data_width = 16
+    elif pack_type == 'uint32':
+        data_width = 32
+    elif pack_type == 'uint64':
+        data_width = 64
+
+    # Data must be in multiples of the data_width
+    assert get_const_int(ishape[pack_axis]) % data_width == 0, "Not a multiple of word size"
+
+    shape_vec = list(ishape)
+    shape_vec[pack_axis] = (shape_vec[pack_axis] // data_width)
+    shape_vec.insert(bit_axis, 1)
+    bitserial_oshape = tuple(shape_vec)
+    masks = np.array([0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80])
+
+    # pack axis shifts if bit axis comes before
+    if bit_axis <= pack_axis:
+        pack_axis += 1
+
+    def _bitpack(*indices):
+        packed_data = [tvm.const(0, pack_type)] * bits
+        for k in range(data_width):
+            # Translate indices for packed data back to original
+            idx = [0] * n
+            j = 0
+            for i in range(n+1):
+                if i == bit_axis:
+                    continue
+                elif i == pack_axis:
+                    idx[j] = indices[i] * data_width + k
+                else:
+                    idx[j] = indices[i]
+                j += 1
+
+            element = data(*idx)
+            for b in range(bits):
+                extracted_bit = ((element & tvm.const(masks[b])) >> b).astype(pack_type)
+                packed_data[b] = (packed_data[b] | extracted_bit)
+                if k < data_width - 1:
+                    packed_data[b] = packed_data[b] << 1
+
+            if k == data_width - 1:
+                return tuple(packed_data)
+        return tuple(packed_data)
+
+    output_tuple = tvm.compute(bitserial_oshape, _bitpack, name=name, tag='bitpack')
+
+    if bits > 1:
+        return concatenate(output_tuple, axis=bit_axis)
+    return output_tuple
+
+_SCH_TO_DECL_FUNC_QUANT = {
+    SpatialPackNCHW: spatial_pack_nchw,
+    SpatialPackNHWC: spatial_pack_nhwc,
+}
diff --git a/topi/python/topi/rasp/__init__.py b/topi/python/topi/rasp/__init__.py
index 31ecea5aba4e..270a48504468 100644
--- a/topi/python/topi/rasp/__init__.py
+++ b/topi/python/topi/rasp/__init__.py
@@ -4,3 +4,4 @@
 
 from .conv2d import schedule_conv2d_nchw
 from .depthwise_conv2d import schedule_depthwise_conv2d_nchw
+from .bitserial_conv2d import schedule_bitserial_conv2d_nhwc
diff --git a/topi/python/topi/rasp/bitserial_conv2d.py b/topi/python/topi/rasp/bitserial_conv2d.py
new file mode 100644
index 000000000000..7d292db8d298
--- /dev/null
+++ b/topi/python/topi/rasp/bitserial_conv2d.py
@@ -0,0 +1,365 @@
+# pylint: disable=invalid-name,unused-variable,invalid-name
+"""Bitserial conv2d schedule on raspberry pi"""
+from __future__ import absolute_import as _abs
+from collections import namedtuple
+import tvm
+from .. import tag
+from ..nn.pad import pad
+from ..nn.bitserial_conv2d import bitserial_conv2d, _get_schedule, _get_workload, bitpack
+from ..nn.bitserial_conv2d import SpatialPackNCHW, _WORKLOADS, spatial_pack_nchw
+from ..nn.util import get_pad_tuple
+from ..util import get_const_int
+from .. import generic
+
+RaspSpatialPack = namedtuple('SpatialPack',
+                             ['vh', 'vw', 'vc', 'ba', 'bc', 'split_ci', 'kfactor'])
+
+_QUANTIZED_SCHEDULES_NHWC = [
+    RaspSpatialPack(2, 2, 8, 1, 1, False, 8),
+    RaspSpatialPack(1, 4, 8, 4, 1, False, 8),
+    RaspSpatialPack(1, 4, 8, 1, 16, False, 8),
+    RaspSpatialPack(1, 4, 8, 4, 8, False, 8),
+    RaspSpatialPack(1, 7, 8, 3, 8, False, 16),
+    RaspSpatialPack(1, 2, 8, 1, 8, False, 16),
+    RaspSpatialPack(2, 1, 8, 1, 4, False, 16),
+    RaspSpatialPack(1, 7, 8, 1, 1, True, 16),
+    RaspSpatialPack(1, 1, 8, 1, 16, True, 16),
+    RaspSpatialPack(1, 1, 8, 1, 8, True, 16),
+    RaspSpatialPack(1, 1, 8, 1, 16, True, 16),
+]
+
+_QUANTIZED_SCHEDULES_NCHW = [
+    # resnet
+    SpatialPackNCHW(2, 2, 8, 1, 1),
+    SpatialPackNCHW(1, 4, 8, 4, 1),
+    SpatialPackNCHW(1, 4, 8, 1, 16),
+    SpatialPackNCHW(1, 4, 8, 4, 8),
+    SpatialPackNCHW(1, 7, 8, 3, 8),
+    SpatialPackNCHW(1, 2, 8, 1, 8),
+    SpatialPackNCHW(2, 1, 8, 1, 4),
+    SpatialPackNCHW(1, 7, 8, 1, 1),
+    SpatialPackNCHW(1, 1, 8, 1, 16),
+    SpatialPackNCHW(1, 1, 8, 1, 8),
+    SpatialPackNCHW(1, 1, 8, 1, 16),
+]
+
+@_get_schedule.register("rasp")
+def _get_schedule_bitserial_conv2d(wkl, layout):
+    if wkl not in _WORKLOADS:
+        raise ValueError("no schedule for such workload: {}".format(wkl))
+    idx = _WORKLOADS.index(wkl)
+    if layout == "NCHW":
+        sch = _QUANTIZED_SCHEDULES_NCHW[idx]
+    elif layout == "NHWC":
+        sch = _QUANTIZED_SCHEDULES_NHWC[idx]
+    return sch
+
+
+@bitserial_conv2d.register("rasp")
+def _declaration_bitserial_conv2d(data, kernel, stride, padding, activation_bits, weight_bits,
+                                  layout='NCHW', pack_dtype=None, out_dtype=None, dorefa=False):
+    if out_dtype is None:
+        out_dtype = data.dtype
+    assert data.shape[0].value == 1, "only support batch size=1 convolution on rasp"
+    assert layout == "NCHW" or layout == "NHWC", "only support layouts NCHW and NHWC"
+    if dorefa:
+        assert layout == "NCHW", "Cannot support dorea with NHWC layout yet"
+    wkl = _get_workload(data, kernel, stride, padding, out_dtype, layout)
+    sch = _get_schedule(wkl, layout)
+    if layout == "NCHW":
+        return spatial_pack_nchw(data, kernel, stride, padding, activation_bits, weight_bits,
+                                 pack_dtype=pack_dtype, out_dtype=out_dtype, dorefa=dorefa)
+    return _spatial_pack_nhwc(data, kernel, stride, padding, activation_bits,
+                              weight_bits, out_dtype)
+
+def _kernel_vec_spatial_pack_nhwc(kernel, kernel_bits, VC):
+    kernel_q = bitpack(kernel, kernel_bits, pack_axis=2, bit_axis=2, pack_type='uint8')
+    KH, KW, KB, CI, CO = kernel_q.shape
+    kvshape = (CO//VC, KH, KW, KB, VC, CI)
+    return tvm.compute(kvshape, lambda co, dh, dw, b, vc, ci: \
+        kernel_q[dh][dw][b][ci][co*VC+vc], name='kernel_vec')
+
+def _spatial_pack_nhwc(data, kernel, stride, padding, activation_bits, weight_bits, out_dtype):
+    """ Compute convolution with pack on spatial axes. """
+    assert data.shape[0].value == 1, "spatial pack convolution only support batch size=1"
+    wkl = _get_workload(data, kernel, stride, padding, out_dtype, "NHWC")
+    sch = _get_schedule(wkl, "NHWC")
+    VH = sch.vh
+    VW = sch.vw
+    VC = sch.vc
+
+    data_q = bitpack(data, activation_bits, pack_axis=3, bit_axis=3, pack_type='uint8')
+    kernel_vec = _kernel_vec_spatial_pack_nhwc(kernel, weight_bits, VC)
+    N, H, W, IB, CI = data_q.shape
+    OCO, KH, KW, KB, VC, _ = kernel_vec.shape
+
+    CO = OCO * VC
+    HPAD, WPAD, _, _ = get_pad_tuple(padding, kernel)
+
+    if isinstance(stride, (tuple, list)):
+        HSTR, WSTR = stride
+    else:
+        HSTR, WSTR = stride, stride
+    HCAT, WCAT = KH-1, KW-1
+
+    PAD_H = H + 2*HPAD
+    PAD_W = W + 2*WPAD
+    OH = (H + 2*HPAD - KH) // HSTR + 1
+    OW = (W + 2*WPAD - KW) // WSTR + 1
+    dvshape = (N, PAD_H//(VH*HSTR), PAD_W//(VW*WSTR), VH*HSTR+HCAT, VW*WSTR+WCAT, IB, CI)
+    ovshape = (1, OH // VH, OW // VW, CO // VC, VH, VW, VC)
+    oshape = (1, OH, OW, CO)
+
+    if (HPAD != 0 and WPAD != 0):
+        data_pad = pad(data_q, (0, HPAD, WPAD, 0, 0), name="data_pad")
+    else:
+        data_pad = data_q
+
+    data_vec = tvm.compute(dvshape, lambda n, h, w, vh, vw, b, ci: \
+        data_pad[n][h*VH*HSTR+vh][w*VW*WSTR+vw][b][ci], name='data_vec')
+
+    ci = tvm.reduce_axis((0, CI), name='ci')
+    dh = tvm.reduce_axis((0, KH), name='dh')
+    dw = tvm.reduce_axis((0, KW), name='dw')
+    ib = tvm.reduce_axis((0, IB), name='ib')
+    kb = tvm.reduce_axis((0, KB), name='kb')
+
+    def _conv(n, h, w, co, vh, vw, vc):
+        return tvm.sum((tvm.popcount(
+            kernel_vec[co, dh, dw, kb, vc, ci].astype('uint16') &
+            data_vec[n, h, w, vh*HSTR+dh, vw*WSTR+dw, ib, ci].astype('uint16'))
+                        << (kb + ib).astype('uint16')), axis=[dh, dw, kb, ib, ci])
+
+    conv = tvm.compute(ovshape, _conv, name='conv')
+
+    return tvm.compute(oshape, lambda n, h, w, co:
+                       conv[n][h//VH][w//VW][co//VC][h%VH][w%VW][co%VC].astype(out_dtype),
+                       name='output_vec', tag='spatial_bitserial_conv_nhwc')
+
+def _intrin_popcount(m, k_i, w_b, x_b):
+    dtype = 'uint8'
+    w = tvm.placeholder((w_b, m, k_i), dtype=dtype, name='w')
+    x = tvm.placeholder((x_b, k_i,), dtype=dtype, name='x')
+    k = tvm.reduce_axis((0, k_i), name='k')
+    bw = tvm.reduce_axis((0, w_b), name='bw')
+    bx = tvm.reduce_axis((0, x_b), name='bx')
+    z = tvm.compute((m,), lambda i:
+                    tvm.sum(tvm.popcount(w[bw, i, k].astype('uint16') &
+                                         x[bx, k].astype('uint16'))
+                            << (bw+bx).astype('uint16'), axis=[bw, bx, k]), name='z')
+
+    Wb = tvm.decl_buffer(w.shape, w.dtype,
+                         name="W",
+                         offset_factor=k_i,
+                         strides=[tvm.var('ldw'), tvm.var('ldw'), 1])
+    Xb = tvm.decl_buffer(x.shape, x.dtype,
+                         name="X",
+                         offset_factor=k_i,
+                         strides=[tvm.var('ldw'), 1])
+
+    def _intrin_func(ins, outs):
+        ww, xx = ins
+        zz = outs[0]
+        vpadd = "llvm.arm.neon.vpadd.v8u8"
+        vpadalu = "llvm.arm.neon.vpadalu.v16u8.v8u16"
+        args_1 = tvm.const(1, 'uint32')
+        args_2 = tvm.const(2, 'uint32')
+
+        def _instr(index):
+            irb = tvm.ir_builder.create()
+            if index == 1:
+                irb.emit(zz.vstore(0, tvm.const(0, 'uint16x8')))
+                return irb.get()
+
+            cnts8 = [None] * 8
+            cnts4 = [None] * 4
+            cnts2 = [None] * 2
+            for bw in range(w_b):
+                for bx in range(x_b):
+                    if k_i == 16:
+                        for i in range(m):
+                            ands = ww.vload([bw, i, 0], 'uint8x16') & xx.vload([bx, 0], 'uint8x16')
+                            cnts = tvm.popcount(ands)
+                            upper_half = tvm.call_pure_intrin('uint8x8', 'vectorhigh', cnts)
+                            lower_half = tvm.call_pure_intrin('uint8x8', 'vectorlow', cnts)
+                            cnts8[i] = upper_half + lower_half
+                        for i in range(m//2):
+                            cnts4[i] = tvm.call_llvm_intrin('uint8x8', vpadd,
+                                                            args_1, cnts8[i*2], cnts8[i*2+1])
+                        for i in range(m//4):
+                            cnts2[i] = tvm.call_llvm_intrin('uint8x8', vpadd,
+                                                            args_1, cnts4[i*2], cnts4[i*2+1])
+                        cnts = tvm.call_pure_intrin('uint8x16', 'vectorcombine', cnts2[0], cnts2[1])
+                        shifted_cnts = cnts << tvm.const(bw+bx, dtype)
+                        out = tvm.call_llvm_intrin('uint16x8', vpadalu,
+                                                   args_2, zz.vload(0, 'uint16x8'), shifted_cnts)
+                    else: # ki == 8
+                        for i in range(m):
+                            ands = ww.vload([bw, i, 0], 'uint8x8') & xx.vload([bx, 0], 'uint8x8')
+                            cnts8[i] = tvm.popcount(ands)
+                        for i in range(m//2):
+                            cnts4[i] = tvm.call_llvm_intrin('uint8x8', vpadd,
+                                                            args_1, cnts8[i*2], cnts8[i*2+1])
+                        for i in range(m//4):
+                            cnts2[i] = tvm.call_llvm_intrin('uint8x8', vpadd,
+                                                            args_1, cnts4[i*2], cnts4[i*2+1])
+                        cnts = tvm.call_pure_intrin('uint8x16', 'vectorcombine', cnts2[0], cnts2[1])
+                        shifted_cnts = cnts << tvm.const(bw+bx, dtype)
+                        out = tvm.call_llvm_intrin('uint16x8', vpadalu,
+                                                   args_2, zz.vload(0, 'uint16x8'), shifted_cnts)
+                    irb.emit(zz.vstore(0, out))
+            return irb.get()
+        # body, reset, update
+        return _instr(0), _instr(1), _instr(2)
+    with tvm.build_config(offset_factor=1, partition_const_loop=True):
+        return tvm.decl_tensor_intrin(z.op, _intrin_func, binds={w: Wb, x:Xb})
+
+# ARM specific schedule that using custom microkernel
+def _schedule_spatial_conv2d_nhwc(s, data, data_q, data_pad, data_vec,
+                                  kernel, kernel_q, kernel_vec,
+                                  conv_out, output, last):
+    # no stride and padding info here
+    _, H, W, IB, CI = data_q.shape
+    KH, KW, KB, _, CO = kernel_q.shape
+    KB = get_const_int(KB)
+    IB = get_const_int(IB)
+
+    if data_pad is None:
+        padding = (0, 0)
+        _, in_h, in_w, _, _ = data_q.shape
+        kern_h, kern_w, _, _ = kernel.shape
+        _, out_h, out_w, _ = output.shape
+        hstride = (in_h - kern_h) // (out_h - 1)
+        wstride = (in_w - kern_w) // (out_w - 1)
+        stride = get_const_int(hstride), get_const_int(wstride)
+    else:
+        _, in_h, in_w, _, _ = data_q.shape
+        _, pad_h, pad_w, _, _ = data_pad.shape
+        hpad = (pad_h - in_h) // 2
+        wpad = (pad_w - in_w) // 2
+        padding = get_const_int(hpad), get_const_int(wpad)
+
+        _, in_h, in_w, _, _ = data_pad.shape
+        kern_h, kern_w, _, _ = kernel.shape
+        _, out_h, out_w, _ = output.shape
+        hstride = (in_h - kern_h) // (out_h - 1)
+        wstride = (in_w - kern_w) // (out_w - 1)
+        stride = get_const_int(hstride), get_const_int(wstride)
+
+    wkl = _get_workload(data, kernel, stride, padding, output.dtype, "NHWC")
+    sch = _get_schedule(wkl, "NHWC")
+
+    VH = sch.vh
+    VW = sch.vw
+    VC = sch.vc
+    ba = sch.ba
+    bc = sch.bc
+
+    ##### Schedule data packing
+    if data_pad is not None:
+        s[data_pad].compute_inline()
+
+    _, h, _, _, _, _, _ = s[data_vec].op.axis
+    if ba == 1:
+        oaxis = h
+        paxis = h
+    else:
+        oh, ih = s[data_vec].split(h, ba)
+        oaxis = oh
+        paxis = ih
+
+    s[data_vec].parallel(paxis)
+    s[data_vec].pragma(oaxis, "parallel_launch_point")
+    s[data_vec].pragma(paxis, "parallel_stride_pattern")
+    s[data_vec].pragma(oaxis, "parallel_barrier_when_finish")
+
+    ##### Schedule kernel packing
+    co, _, _, _, _, _ = s[kernel_vec].op.axis
+    if bc == 1:
+        oaxis = co
+        paxis = co
+    else:
+        oco, ico = s[kernel_vec].split(co, bc)
+        oaxis = oco
+        paxis = ico
+
+    s[kernel_vec].parallel(paxis)
+    s[kernel_vec].pragma(oaxis, "parallel_launch_point")
+    s[kernel_vec].pragma(paxis, "parallel_stride_pattern")
+    s[kernel_vec].pragma(oaxis, "parallel_barrier_when_finish")
+
+    ##### Schedule Convolution
+    n, oh, ow, co, vh, vw, vc = s[conv_out].op.axis
+    dh, dw, kb, ib, ci = s[conv_out].op.reduce_axis
+
+    kfactor = sch.kfactor
+    if sch.split_ci:
+        oci, ici = s[conv_out].split(ci, kfactor)
+        s[conv_out].reorder(n, oh, ow, co, vh, vw, dh, dw, oci, kb, ib, vc, ici)
+    else:
+        s[conv_out].reorder(n, oh, ow, co, vh, vw, dh, dw, kb, ib, vc, ci)
+
+    pc = _intrin_popcount(8, kfactor, KB, IB)
+    s[conv_out].tensorize(kb, pc)
+
+    n, h, w, co = s[last].op.axis
+    co, vc = s[last].split(co, VC)
+    oh, ow, vh, vw = s[last].tile(h, w, VH, VW)
+    s[last].reorder(n, oh, ow, co, vc, vh, vw)
+    s[last].vectorize(vw)
+    if last != output:
+        s[last].compute_inline()
+
+    s[conv_out].compute_at(s[last], ow)
+    if co == 1:
+        oaxis = oh
+        paxis = oh
+    else:
+        oho, iho = s[last].split(oh, bc)
+        oaxis = oho
+        paxis = iho
+
+    s[last].parallel(paxis)
+    s = s.normalize()
+    return s
+
+@generic.schedule_bitserial_conv2d_nhwc.register(["rasp"])
+def schedule_bitserial_conv2d_nhwc(outs):
+    """Raspverry pi schedule for bitserial conv2d"""
+    s = tvm.create_schedule([x.op for x in outs])
+    def traverse(op):
+        """Traverse operators from computation graph"""
+        # inline all one-to-one-mapping operators except the last stage (output)
+        if tag.is_broadcast(op.tag):
+            if op not in s.outputs:
+                s[op].compute_inline()
+            for tensor in op.input_tensors:
+                if tensor.op.input_tensors:
+                    traverse(tensor.op)
+
+        if 'spatial_bitserial_conv_nhwc' in op.tag:
+            output = op.output(0)
+            conv_out = op.input_tensors[0]
+            kernel_vec = conv_out.op.input_tensors[0]
+            kernel_q = kernel_vec.op.input_tensors[0]
+            kernel = kernel_q.op.input_tensors[0]
+            if "QuantizeInput" in kernel.op.name:
+                # Need to go up 1 further, from the combine in bitpack
+                kernel = kernel.op.input_tensors[0]
+            data_vec = conv_out.op.input_tensors[1]
+            data_q = data_vec.op.input_tensors[0]
+            data = data_q.op.input_tensors[0]
+            data_pad = None
+            if isinstance(data_q.op, tvm.tensor.ComputeOp) and "pad" in data_q.op.tag:
+                data_pad = data_q
+                data_q = data
+                data = data_q.op.input_tensors[0]
+            if "QuantizeInput" in data.op.name:
+                # Need to go up 1 further, from the combine in bitpack
+                data = data.op.input_tensors[0]
+
+            _schedule_spatial_conv2d_nhwc(s, data, data_q, data_pad, data_vec,
+                                          kernel, kernel_q, kernel_vec, conv_out, output, outs[0])
+
+    traverse(outs[0].op)
+    return s
diff --git a/topi/python/topi/x86/__init__.py b/topi/python/topi/x86/__init__.py
index d001b5fdca57..c146419fcec9 100644
--- a/topi/python/topi/x86/__init__.py
+++ b/topi/python/topi/x86/__init__.py
@@ -8,3 +8,4 @@
 from .nn import *
 from .injective import *
 from .pooling import schedule_pool, schedule_global_pool
+from .bitserial_conv2d import schedule_bitserial_conv2d
diff --git a/topi/python/topi/x86/bitserial_conv2d.py b/topi/python/topi/x86/bitserial_conv2d.py
new file mode 100644
index 000000000000..1c01b96f9c30
--- /dev/null
+++ b/topi/python/topi/x86/bitserial_conv2d.py
@@ -0,0 +1,316 @@
+# pylint: disable=invalid-name,unused-variable,invalid-name
+"""Bitserial conv2d schedule on x86"""
+import tvm
+from topi.util import get_const_int
+from .. import generic, tag
+from ..nn.bitserial_conv2d import bitserial_conv2d, _get_schedule, _get_workload
+from ..nn.bitserial_conv2d import SpatialPackNCHW, SpatialPackNHWC
+from ..nn.bitserial_conv2d import _WORKLOADS, _SCH_TO_DECL_FUNC_QUANT
+
+_QUANTIZED_SCHEDULES_NCHW = [
+    # resnet
+    SpatialPackNCHW(2, 2, 8, 1, 1),
+    SpatialPackNCHW(1, 4, 8, 4, 1),
+    SpatialPackNCHW(1, 4, 8, 1, 16),
+    SpatialPackNCHW(1, 4, 8, 4, 8),
+    SpatialPackNCHW(1, 7, 8, 3, 8),
+    SpatialPackNCHW(1, 2, 8, 1, 8),
+    SpatialPackNCHW(2, 1, 8, 1, 4),
+    SpatialPackNCHW(1, 7, 8, 1, 1),
+    SpatialPackNCHW(1, 1, 8, 1, 16),
+    SpatialPackNCHW(1, 1, 8, 1, 8),
+    SpatialPackNCHW(1, 1, 8, 1, 16),
+
+    SpatialPackNCHW(3, 3, 16, 3, 16),
+    SpatialPackNCHW(1, 1, 16, 2, 16),
+    SpatialPackNCHW(1, 1, 8, 1, 16),
+    SpatialPackNCHW(1, 1, 8, 1, 16),
+]
+
+_QUANTIZED_SCHEDULES_NHWC = [
+    # resnet
+    SpatialPackNHWC(2, 2, 8, 1, 1),
+    SpatialPackNHWC(1, 4, 8, 4, 1),
+    SpatialPackNHWC(1, 4, 8, 1, 16),
+    SpatialPackNHWC(1, 4, 8, 4, 8),
+    SpatialPackNHWC(1, 7, 8, 3, 8),
+    SpatialPackNHWC(1, 2, 8, 1, 8),
+    SpatialPackNHWC(2, 1, 8, 1, 4),
+    SpatialPackNHWC(1, 7, 8, 1, 1),
+    SpatialPackNHWC(1, 1, 8, 1, 16),
+    SpatialPackNHWC(1, 1, 8, 1, 8),
+    SpatialPackNHWC(1, 1, 8, 1, 16),
+]
+
+@_get_schedule.register("cpu")
+def _get_schedule_bitserial_conv2d(wkl, layout):
+    if wkl not in _WORKLOADS:
+        raise ValueError("no schedule for such workload: {}".format(wkl))
+    idx = _WORKLOADS.index(wkl)
+    if layout == "NCHW":
+        sch = _QUANTIZED_SCHEDULES_NCHW[idx]
+    elif layout == "NHWC":
+        sch = _QUANTIZED_SCHEDULES_NHWC[idx]
+    return sch
+
+@bitserial_conv2d.register("cpu")
+def _declaration_bitserial_conv2d(data, kernel, stride, padding, activation_bits, weight_bits,
+                                  layout='NCHW', pack_dtype=None, out_dtype=None, dorefa=False):
+    if out_dtype is None:
+        out_dtype = data.dtype
+    assert data.shape[0].value == 1, "only support batch size=1 convolution on rasp"
+    assert layout == "NCHW" or layout == "NHWC", "only support layouts NCHW and NHWC"
+
+    wkl = _get_workload(data, kernel, stride, padding, out_dtype, layout)
+    sch = _get_schedule(wkl, layout)
+    return _SCH_TO_DECL_FUNC_QUANT[type(sch)](data, kernel, stride, padding, activation_bits,
+                                              weight_bits, pack_dtype, out_dtype, dorefa)
+
+@generic.schedule_bitserial_conv2d_nchw.register(["cpu"])
+@generic.schedule_bitserial_conv2d_nhwc.register(["cpu"])
+def schedule_bitserial_conv2d(outs):
+    """CPU schedule for bitserial convolutions NCHW and NHWC"""
+    s = tvm.create_schedule([x.op for x in outs])
+
+    def traverse(op):
+        """Traverse operators from computation graph"""
+        output = op.output(0)
+        # inline all one-to-one-mapping operators except the last stage (output)
+        if tag.is_broadcast(op.tag) or 'elemwise' in op.tag:
+            if op not in s.outputs:
+                s[op].compute_inline()
+            for tensor in op.input_tensors:
+                if tensor.op.input_tensors:
+                    traverse(tensor.op)
+
+        elif 'spatial_bitserial_conv_nchw' in op.tag or 'spatial_bitserial_conv_nhwc' in op.tag:
+            conv_out = op.input_tensors[0]
+            kernel_vec = conv_out.op.input_tensors[1]
+            kernel_q = kernel_vec.op.input_tensors[0]
+            kernel = kernel_q.op.input_tensors[0]
+            data_vec = conv_out.op.input_tensors[0]
+            data_q = data_vec.op.input_tensors[0]
+            data = data_q.op.input_tensors[0]
+            data_pad = None
+            if isinstance(data_q.op, tvm.tensor.ComputeOp) and "pad" in data_q.op.tag:
+                data_pad = data_q
+                data_q = data
+                data = data_q.op.input_tensors[0]
+            if "QuantizeInput" in kernel.op.name:
+                # Need to go up 1 further, from the combine in bitpack
+                kernel = kernel.op.input_tensors[0]
+            if "QuantizeInput" in data.op.name:
+                # Need to go up 1 further, from the combine in bitpack
+                data = data.op.input_tensors[0]
+
+            if 'spatial_bitserial_conv_nchw' in op.tag:
+                _schedule_spatial_conv2d_nchw(s, data, data_q, data_pad, data_vec,
+                                              kernel, kernel_q, kernel_vec,
+                                              conv_out, output, outs[0])
+            elif 'spatial_bitserial_conv_nhwc' in op.tag:
+                _schedule_spatial_conv2d_nhwc(s, data, data_q, data_pad, data_vec,
+                                              kernel, kernel_q, kernel_vec,
+                                              conv_out, output, outs[0])
+
+    traverse(outs[0].op)
+    return s
+
+def _schedule_spatial_conv2d_nchw(s, data, data_q, data_pad, data_vec,
+                                  kernel, kernel_q, kernel_vec,
+                                  conv_out, output, last):
+    IB, _, CI, IH, IW = data_q.shape
+    KB, CO, _, KH, KW = kernel_q.shape
+    _, _, OH, OW = output.shape
+
+    # Infer padding and stride
+    if data_pad is None:
+        padding = (0, 0)
+        TH, TW = IH, IW
+    else:
+        _, _, _, TH, TW = data_pad.shape
+        hpad = get_const_int((TH - IH) // 2)
+        wpad = get_const_int((TW - IW) // 2)
+        padding = (hpad, wpad)
+
+    hstride = get_const_int((TH - KH) // (OH - 1))
+    wstride = get_const_int((TW - KW) // (OW - 1))
+    stride = (hstride, wstride)
+
+    wkl = _get_workload(data, kernel, stride, padding, output.dtype, "NCHW")
+    sch = _get_schedule(wkl, "NCHW")
+    VH = sch.vh
+    VW = sch.vw
+    VC = sch.vc
+    ba = sch.ba
+    bc = sch.bc
+
+    CC = s.cache_write(conv_out, "global")
+    n, co, oh, ow, vh, vw, vc = s[conv_out].op.axis
+    s[conv_out].vectorize(vc)
+
+    s[CC].compute_at(s[conv_out], ow)
+    n, co, oh, ow, vh, vw, vc = s[CC].op.axis
+    ci, dh, dw, b1, b2 = s[CC].op.reduce_axis
+    s[CC].reorder(ci, dh, vh, dw, vw, b1, b2, vc)
+    s[CC].unroll(b1)
+    s[CC].unroll(b2)
+    s[CC].vectorize(vc)
+
+    ##### Schedule A
+    if data_pad is not None:
+        s[data_pad].compute_inline()
+
+    _, h, _, _, _, _, vw = s[data_vec].op.axis
+    s[data_vec].vectorize(vw)
+    if ba == 1:
+        oaxis = h
+        paxis = h
+    else:
+        oh, ih = s[data_vec].split(h, ba)
+        oaxis = oh
+        paxis = ih
+
+    s[data_vec].parallel(paxis)
+    s[data_vec].pragma(oaxis, "parallel_launch_point")
+    s[data_vec].pragma(paxis, "parallel_stride_pattern")
+    s[data_vec].pragma(oaxis, "parallel_barrier_when_finish")
+
+
+    ##### Schedule B
+    co, _, _, _, _, vc = s[kernel_vec].op.axis
+    s[kernel_vec].vectorize(vc)
+    if bc == 1:
+        oaxis = co
+        paxis = co
+    else:
+        oco, ico = s[kernel_vec].split(co, bc)
+        oaxis = oco
+        paxis = ico
+
+    s[kernel_vec].parallel(paxis)
+    s[kernel_vec].pragma(oaxis, "parallel_launch_point")
+    s[kernel_vec].pragma(paxis, "parallel_stride_pattern")
+    s[kernel_vec].pragma(oaxis, "parallel_barrier_when_finish")
+
+
+    ##### Schedule C
+    n, co, h, w = s[last].op.axis
+    co, vc = s[last].split(co, VC)
+    oh, ow, vh, vw = s[last].tile(h, w, VH, VW)
+    s[last].reorder(n, co, oh, ow, vh, vw, vc)
+    if last != output:
+        s[output].compute_inline()
+    s[conv_out].compute_at(s[last], ow)
+
+    if bc == 1:
+        oaxis = co
+        paxis = co
+    else:
+        oco, ico = s[last].split(co, bc)
+        oaxis = oco
+        paxis = ico
+
+    s[last].parallel(paxis)
+    s[last].pragma(oaxis, "parallel_launch_point")
+    s[last].pragma(paxis, "parallel_stride_pattern")
+    s[last].pragma(oaxis, "parallel_barrier_when_finish")
+
+    return s
+
+def _schedule_spatial_conv2d_nhwc(s, data, data_q, data_pad, data_vec,
+                                  kernel, kernel_q, kernel_vec,
+                                  conv_out, output, last):
+    # no stride and padding info here
+    _, IH, IW, CI, IB = data_q.shape
+    KH, KW, _, CO, KB = kernel_q.shape
+    _, OH, OW, _ = output.shape
+    # Infer padding and stride
+    if data_pad is None:
+        padding = (0, 0)
+        TH, TW = IH, IW
+    else:
+        _, TH, TW, _, _ = data_pad.shape
+        hpad = get_const_int((TH - IH) // 2)
+        wpad = get_const_int((TW - IW) // 2)
+        padding = (hpad, wpad)
+
+    hstride = get_const_int((TH - KH) // (OH - 1))
+    wstride = get_const_int((TW - KW) // (OW - 1))
+    stride = (hstride, wstride)
+
+    wkl = _get_workload(data, kernel, stride, padding, last.dtype, "NHWC")
+    sch = _get_schedule(wkl, "NHWC")
+    VH = sch.vh
+    VW = sch.vw
+    VC = sch.vc
+    ba = sch.ba
+    bc = sch.bc
+
+    ##### Schedule data packing
+    if data_pad is not None:
+        s[data_pad].compute_inline()
+
+    _, h, _, _, _, _, _ = s[data_vec].op.axis
+    if ba == 1:
+        oaxis = h
+        paxis = h
+    else:
+        oh, ih = s[data_vec].split(h, ba)
+        oaxis = oh
+        paxis = ih
+    s[data_vec].parallel(paxis)
+    s[data_vec].pragma(oaxis, "parallel_launch_point")
+    s[data_vec].pragma(paxis, "parallel_stride_pattern")
+    s[data_vec].pragma(oaxis, "parallel_barrier_when_finish")
+
+
+    ##### Schedule kernel packing
+    co, _, _, _, _, _ = s[kernel_vec].op.axis
+    if bc == 1:
+        oaxis = co
+        paxis = co
+    else:
+        oco, ico = s[kernel_vec].split(co, bc)
+        oaxis = oco
+        paxis = ico
+
+    s[kernel_vec].parallel(paxis)
+    s[kernel_vec].pragma(oaxis, "parallel_launch_point")
+    s[kernel_vec].pragma(paxis, "parallel_stride_pattern")
+    s[kernel_vec].pragma(oaxis, "parallel_barrier_when_finish")
+
+
+    ##### Schedule Convolution
+    n, oh, ow, co, vh, vw, vc = s[conv_out].op.axis
+    dh, dw, ci, b1, b2 = s[conv_out].op.reduce_axis
+
+    s[conv_out].reorder(n, oh, ow, co, vh, vw, dh, dw, ci, vc, b1, b2)
+
+    s[conv_out].unroll(b1)
+    s[conv_out].unroll(b2)
+    s[conv_out].vectorize(vc)
+
+    # # Schedule output
+    n, h, w, co = s[last].op.axis
+    co, vc = s[last].split(co, VC)
+    oh, ow, vh, vw = s[last].tile(h, w, VH, VW)
+    s[last].reorder(n, oh, ow, co, vh, vw, vc)
+    s[last].vectorize(vc)
+    if last != output:
+        s[output].compute_inline()
+    s[conv_out].compute_at(s[last], ow)
+
+    if bc == 1:
+        oaxis = oh
+        paxis = oh
+    else:
+        oho, iho = s[last].split(oh, bc)
+        oaxis = oho
+        paxis = iho
+
+    s[last].parallel(paxis)
+    s[last].pragma(oaxis, "parallel_launch_point")
+    s[last].pragma(paxis, "parallel_stride_pattern")
+    s[last].pragma(oaxis, "parallel_barrier_when_finish")
+
+    return s
diff --git a/topi/tests/python/test_topi_bitserial_conv2d.py b/topi/tests/python/test_topi_bitserial_conv2d.py
new file mode 100644
index 000000000000..6df18483a45f
--- /dev/null
+++ b/topi/tests/python/test_topi_bitserial_conv2d.py
@@ -0,0 +1,112 @@
+import os
+import numpy as np
+import tvm
+import topi
+import topi.testing
+from tvm.contrib.pickle_memoize import memoize
+from topi.util import get_const_tuple
+from tvm.contrib import util
+from tvm.contrib.pickle_memoize import memoize
+
+def generate_quantized_np(shape, bits, out_dtype):
+    min_val = 0
+    max_val = 1 << bits
+    return np.random.randint(min_val, max_val, size=shape).astype(out_dtype)
+
+def verify_bitserial_conv2d_nchw(batch, in_size, in_channel, num_filter, kernel, stride, padding, 
+    activation_bits, weight_bits, dorefa):
+    in_height = in_width = in_size
+    input_type='uint32'
+    out_dtype='int32'
+
+    with tvm.target.create('llvm'):
+        A = tvm.placeholder((batch, in_channel, in_height, in_width), dtype=input_type, name='A')
+        W = tvm.placeholder((num_filter, in_channel, kernel, kernel), dtype=input_type, name='W')
+        B = topi.nn.bitserial_conv2d(A, W, stride, padding, activation_bits, weight_bits, 
+            out_dtype=out_dtype, layout="NCHW", dorefa=dorefa)
+        s = topi.generic.schedule_bitserial_conv2d_nchw([B])
+
+    a_shape = get_const_tuple(A.shape)
+    w_shape = get_const_tuple(W.shape)
+    dtype = A.dtype
+
+    def get_ref_data():
+        a_np = generate_quantized_np(get_const_tuple(A.shape), activation_bits, input_type)
+        w_np = generate_quantized_np(get_const_tuple(W.shape), weight_bits, input_type)
+        if dorefa:
+            w_ = np.copy(w_np).astype(out_dtype)
+            for x in np.nditer(w_, op_flags=['readwrite']):
+                x[...] = 1 if x == 1 else -1
+            b_np = topi.testing.conv2d_nchw_python(a_np.astype(out_dtype), w_, stride, padding)
+        else:
+            b_np = topi.testing.conv2d_nchw_python(a_np, w_np, stride, padding)
+        return a_np, w_np, b_np
+    a_np, w_np, b_np = get_ref_data()
+
+    ctx = tvm.cpu(0)
+    a = tvm.nd.array(a_np, ctx)
+    w = tvm.nd.array(w_np, ctx)
+    b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
+    func = tvm.build(s, [A, W, B], "llvm")
+    func(a, w, b)
+    np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+def verify_bitserial_conv2d_nhwc(batch, in_size, in_channel, num_filter, kernel, stride, padding, 
+                        activation_bits, weight_bits, dorefa):
+    in_height = in_width = in_size
+    input_type='uint32'
+    out_dtype='int32'
+
+    with tvm.target.create('llvm'):
+        A = tvm.placeholder((batch, in_height, in_width, in_channel), dtype=input_type, name='A')
+        W = tvm.placeholder((kernel, kernel, in_channel, num_filter), dtype=input_type, name='W')
+        B = topi.nn.bitserial_conv2d(A, W, stride, padding, activation_bits, weight_bits, out_dtype=out_dtype, 
+                            layout="NHWC", dorefa=dorefa)
+        s = topi.generic.schedule_bitserial_conv2d_nhwc([B])
+
+    a_shape = get_const_tuple(A.shape)
+    w_shape = get_const_tuple(W.shape)
+    dtype = A.dtype
+
+    def get_ref_data():
+        a_np = generate_quantized_np(get_const_tuple(A.shape), activation_bits, input_type)
+        w_np = generate_quantized_np(get_const_tuple(W.shape), weight_bits, input_type)
+        if dorefa:
+            w_ = np.copy(w_np).astype(out_dtype)
+            for x in np.nditer(w_, op_flags=['readwrite']):
+                x[...] = 1 if x == 1 else -1
+            b_np = topi.testing.conv2d_nhwc_python(a_np, w_, stride, padding).astype(out_dtype)
+        else:
+            b_np = topi.testing.conv2d_nhwc_python(a_np, w_np, stride, padding).astype(out_dtype)
+        return a_np, w_np, b_np
+    a_np, w_np, b_np = get_ref_data()
+
+    ctx = tvm.cpu(0)
+    a = tvm.nd.array(a_np, ctx)
+    w = tvm.nd.array(w_np, ctx)
+    b = tvm.nd.array(np.zeros(get_const_tuple(B.shape), dtype=B.dtype), ctx)
+    func = tvm.build(s, [A, W, B], 'llvm')
+
+    func(a, w, b)
+    np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
+
+def test_bitserial_conv2d():
+    in_size = 56
+    ic, oc = 64, 64
+    k = 3
+    stride = 1
+    pad = 1
+    verify_bitserial_conv2d_nchw(1, in_size, ic, oc, k, stride, pad, 1, 1, True)
+    verify_bitserial_conv2d_nchw(1, in_size, ic, oc, k, stride, pad, 2, 1, True)
+    verify_bitserial_conv2d_nchw(1, in_size, ic, oc, k, stride, pad, 1, 1, False)
+    verify_bitserial_conv2d_nchw(1, in_size, ic, oc, k, stride, pad, 2, 1, False)
+    verify_bitserial_conv2d_nchw(1, in_size, ic, oc, k, stride, pad, 2, 2, False)
+
+    verify_bitserial_conv2d_nhwc(1, in_size, ic, oc, k, stride, pad, 1, 1, True)
+    verify_bitserial_conv2d_nhwc(1, in_size, ic, oc, k, stride, pad, 2, 1, True)
+    verify_bitserial_conv2d_nhwc(1, in_size, ic, oc, k, stride, pad, 1, 1, False)
+    verify_bitserial_conv2d_nhwc(1, in_size, ic, oc, k, stride, pad, 2, 1, False)
+    verify_bitserial_conv2d_nhwc(1, in_size, ic, oc, k, stride, pad, 2, 2, False)
+
+if __name__ == "__main__":
+    test_bitserial_conv2d()
\ No newline at end of file
diff --git a/topi/tests/python/test_topi_bitserial_conv2d_rasp.py b/topi/tests/python/test_topi_bitserial_conv2d_rasp.py
new file mode 100644
index 000000000000..5789c5496205
--- /dev/null
+++ b/topi/tests/python/test_topi_bitserial_conv2d_rasp.py
@@ -0,0 +1,56 @@
+import os
+import re
+import numpy as np
+import tvm
+import topi
+import topi.testing
+from topi.util import get_const_tuple
+from tvm.contrib import util
+
+target = 'llvm -target=armv7l-none-linux-gnueabihf -mcpu=cortex-a53 -mattr=+neon'
+
+def generate_quantized_np(shape, bits, out_dtype):
+    np.random.seed(0)
+    min_val = 0
+    max_val = 1 << bits
+    return np.random.randint(min_val, max_val, size=shape).astype(out_dtype)
+
+# Verify that certain special instructions from the tensorize pass exist
+def verify_bitserial_conv2d_nhwc(batch, in_size, in_channel, num_filter, kernel, stride, padding, 
+                        activation_bits, weight_bits, dorefa):
+    in_height = in_width = in_size
+    input_type='uint32'
+    out_dtype='int32'
+
+    with tvm.target.rasp():
+        A = tvm.placeholder((batch, in_height, in_width, in_channel), dtype=input_type, name='A')
+        W = tvm.placeholder((kernel, kernel, in_channel, num_filter), dtype=input_type, name='W')
+        B = topi.nn.bitserial_conv2d(A, W, stride, padding, activation_bits, weight_bits, out_dtype=out_dtype, 
+                            layout="NHWC", dorefa=dorefa)
+        s = topi.generic.schedule_bitserial_conv2d_nhwc([B])
+
+    
+    func = tvm.build(s, [A, W, B], target)
+   
+    assembly = func.get_source('asm')
+    matches = re.findall("vpadal", assembly)
+    assert (len(matches) > 0)
+    matches = re.findall("vcnt", assembly)
+    assert (len(matches) > 0)
+    matches = re.findall("vpadd", assembly)
+    assert (len(matches) > 0)
+
+def test_bitserial_conv2d():
+    in_size = 56
+    ic, oc = 64, 64
+    k = 3
+    stride = 1
+    pad = 1
+
+
+    verify_bitserial_conv2d_nhwc(1, in_size, ic, oc, k, stride, pad, 1, 1, False)
+    verify_bitserial_conv2d_nhwc(1, in_size, ic, oc, k, stride, pad, 2, 1, False)
+
+if __name__ == "__main__":
+    test_bitserial_conv2d()
+

From 71a8d0cd4d2d6828bff2a9eb9d33914f0ad7f8ab Mon Sep 17 00:00:00 2001
From: Sergey Mironov <grrwlf@gmail.com>
Date: Mon, 23 Jul 2018 21:47:18 +0300
Subject: [PATCH 904/949] [BUILD] Fix LLVM static/dynamic link issue (#1461)

This patch prevents libtvm.so from both containing static parts of LLVM
and link with libLLVM.so by removing the latter from link list.
---
 cmake/util/FindLLVM.cmake | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/cmake/util/FindLLVM.cmake b/cmake/util/FindLLVM.cmake
index b45904c0d891..4bb58d462d12 100644
--- a/cmake/util/FindLLVM.cmake
+++ b/cmake/util/FindLLVM.cmake
@@ -18,7 +18,14 @@ macro(find_llvm use_llvm)
   if(LLVM_CONFIG STREQUAL "ON")
     find_package(LLVM REQUIRED CONFIG)
     llvm_map_components_to_libnames(LLVM_LIBS all)
-    list(REMOVE_ITEM LLVM_LIBS LTO)
+    list (FIND LLVM_LIBS "LLVM" _llvm_dynlib_index)
+    if (${_llvm_dynlib_index} GREATER -1)
+      set(LLVM_LIBS LLVM)
+      message(STATUS "Link with dynamic LLVM library")
+    else()
+      list(REMOVE_ITEM LLVM_LIBS LTO)
+      message(STATUS "Link with static LLVM libraries")
+    endif()
     set(TVM_LLVM_VERSION ${LLVM_VERSION_MAJOR}${LLVM_VERSION_MINOR})
   elseif(NOT LLVM_CONFIG STREQUAL "OFF")
     # use llvm config

From a8b1643690c074d2424294026de7b5e3de708212 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 24 Jul 2018 14:01:58 -0700
Subject: [PATCH 905/949] [IR] support general type annotation. (#1480)

---
 include/tvm/ir.h          | 10 ++++++++++
 src/lang/buffer.cc        |  4 ++--
 vta/src/runtime.cc        |  5 ++++-
 vta/src/sim/sim_driver.cc |  4 +++-
 4 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/include/tvm/ir.h b/include/tvm/ir.h
index 8c398c9a5584..97e0b44d2fec 100644
--- a/include/tvm/ir.h
+++ b/include/tvm/ir.h
@@ -514,6 +514,16 @@ using HalideIR::Internal::Shuffle;
 // ir functions
 using HalideIR::Internal::is_const_power_of_two_integer;
 
+/*!
+ * \brief Create a type annotation expression
+ * \param dtype The data type
+ * \return Expr a expression with dtype.
+ */
+inline Expr TypeAnnotation(Type dtype) {
+  return ir::Call::make(dtype,
+                        "type_annotation", {},
+                        ir::Call::PureIntrinsic);
+}
 }  // namespace ir
 }  // namespace tvm
 
diff --git a/src/lang/buffer.cc b/src/lang/buffer.cc
index 39566df45ae6..3f23c2d480bf 100644
--- a/src/lang/buffer.cc
+++ b/src/lang/buffer.cc
@@ -350,12 +350,12 @@ Expr Buffer::access_ptr(int access_mask, Type ptr_type, int content_lanes, Expr
   }
   Expr elem_offset = self->elem_offset + offset;
   if (content_lanes > 1) {
-    e_dtype = make_zero(self->dtype.with_lanes(content_lanes));
+    e_dtype = ir::TypeAnnotation(self->dtype.with_lanes(content_lanes));
     extent = extent / make_const(self->elem_offset.type(), content_lanes);
     elem_offset = self->elem_offset / make_const(self->elem_offset.type(),
                                                  content_lanes);
   } else {
-    e_dtype = make_zero(self->dtype);
+    e_dtype = ir::TypeAnnotation(self->dtype);
   }
   Array<Expr> acc_args{
     e_dtype, self->data, elem_offset,
diff --git a/vta/src/runtime.cc b/vta/src/runtime.cc
index 4d45159a10e1..ffa0096e1713 100644
--- a/vta/src/runtime.cc
+++ b/vta/src/runtime.cc
@@ -18,9 +18,12 @@
 #include <memory>
 #include <atomic>
 
-
 namespace vta {
 
+// Avoid bad configurations.
+static_assert(VTA_UOP_WIDTH == sizeof(VTAUop) * 8,
+              "VTA_UOP_WIDTH do not match VTAUop size");
+
 /*! \brief Enable coherent access between VTA and CPU. */
 static const bool kBufferCoherent = true;
 
diff --git a/vta/src/sim/sim_driver.cc b/vta/src/sim/sim_driver.cc
index 9a953e7aeadb..60645818757c 100644
--- a/vta/src/sim/sim_driver.cc
+++ b/vta/src/sim/sim_driver.cc
@@ -245,12 +245,12 @@ class SRAM {
     CHECK_LE(sram_end, kMaxNumElem);
     memset(sram_ptr, 0, kElemBytes * xtotal * op->y_pad_0);
     sram_ptr += xtotal * op->y_pad_0;
+
     for (uint32_t y = 0; y < op->y_size; ++y) {
       memset(sram_ptr, 0, kElemBytes * op->x_pad_0);
       sram_ptr += op->x_pad_0;
       memcpy(sram_ptr, dram_ptr, kElemBytes * op->x_size);
       sram_ptr += op->x_size;
-      BitPacker<kBits> src(sram_ptr);
       memset(sram_ptr, 0, kElemBytes * op->x_pad_1);
       sram_ptr += op->x_pad_1;
       dram_ptr += kElemBytes * op->x_stride;
@@ -415,12 +415,14 @@ class Device {
             uint32_t acc_idx = uop_ptr->dst_idx;
             uint32_t inp_idx = uop_ptr->src_idx;
             uint32_t wgt_idx = uop_ptr->wgt_idx;
+
             acc_idx += y * op->dst_factor_out + x * op->dst_factor_in;
             inp_idx += y * op->src_factor_out + x * op->src_factor_in;
             wgt_idx += y * op->wgt_factor_out + x * op->wgt_factor_in;
             BitPacker<VTA_ACC_WIDTH> acc(acc_.BeginPtr(acc_idx));
             BitPacker<VTA_INP_WIDTH> inp(inp_.BeginPtr(inp_idx));
             BitPacker<VTA_WGT_WIDTH> wgt(wgt_.BeginPtr(wgt_idx));
+
             // gemm loop
             for (uint32_t i = 0; i < VTA_BATCH; ++i) {
               for (uint32_t j = 0; j < VTA_BLOCK_OUT; ++j) {

From b963cf08fe1a6969917ed96f216f7eb2dd429c23 Mon Sep 17 00:00:00 2001
From: eqy <eqy@cs.washington.edu>
Date: Tue, 24 Jul 2018 14:36:04 -0700
Subject: [PATCH 906/949] update android rpc docs (#1479)

---
 apps/android_rpc/README.md                 | 8 +++++---
 apps/android_rpc/tests/android_rpc_test.py | 5 +++--
 2 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/apps/android_rpc/README.md b/apps/android_rpc/README.md
index cedd9ab0ac0c..64e7779f150a 100644
--- a/apps/android_rpc/README.md
+++ b/apps/android_rpc/README.md
@@ -1,6 +1,6 @@
 # Android TVM RPC
 
-This folder contains Android RPC app that allows us to launch an rpc server on a Android device and connect to it through python script and do testing on the python side as normal TVM RPC.
+This folder contains Android RPC app that allows us to launch an RPC server on a Android device and connect to it through python script and do testing on the python side as normal TVM RPC.
 
 You will need JDK, [Android NDK](https://developer.android.com/ndk) and an Android device to use this.
 
@@ -85,7 +85,9 @@ If everything goes well, you will find compile tools in `/opt/android-toolchain-
 
 ### Cross Compile and Upload to the Android Device
 
-First start an RPC tracker using `python -m tvm.exec.rpc_tracker --port [PORT]` and make your Android device connect to this RPC tracker via TVM RPC application.
+First start an RPC tracker using `python -m tvm.exec.rpc_tracker --port [PORT]` and connect your Android device to this RPC tracker via the TVM RPC application.
+Set the `Address` and `Port` fields to the address and port of the RPC tracker respectively.
+The key should be set to "android" if you wish to avoid modifying the default test script.
 
 Then checkout [android\_rpc/tests/android\_rpc\_test.py](https://github.com/dmlc/tvm/blob/master/apps/android_rpc/tests/android_rpc_test.py) and run,
 
@@ -98,7 +100,7 @@ export TVM_NDK_CC=/opt/android-toolchain-arm64/bin/aarch64-linux-android-g++
 python android_rpc_test.py
 ```
 
-This will compile TVM IR to shared libraries (CPU and OpenCL) and run vector additon on your Android device. On my test device, it gives following results.
+This will compile TVM IR to shared libraries (CPU and OpenCL) and run vector addition on your Android device. On my test device, it gives following results.
 
 ```bash
 TVM: Initializing cython mode...
diff --git a/apps/android_rpc/tests/android_rpc_test.py b/apps/android_rpc/tests/android_rpc_test.py
index 82575e656284..cfb04c1ca9a9 100644
--- a/apps/android_rpc/tests/android_rpc_test.py
+++ b/apps/android_rpc/tests/android_rpc_test.py
@@ -1,7 +1,8 @@
 """Testcode for Android RPC.
 
-To use it, start a rpc proxy with "python -m tvm.exec.rpc_proxy".
-And configure the proxy host field as commented.
+To use it, start an RPC tracker with "python -m tvm.exec.rpc_tracker".
+Use the tracker's address and port when configuring the RPC app.
+Use "android" as the key if you wish to avoid modifying this script.
 """
 
 import tvm

From 5643846eeadd38b3bd0c1d110ca6db593a441a27 Mon Sep 17 00:00:00 2001
From: Yizhi Liu <liuyizhi@apache.org>
Date: Tue, 24 Jul 2018 14:45:18 -0700
Subject: [PATCH 907/949] [tvm4j] add GraphRuntime (#1472)

---
 .../src/main/java/ml/dmlc/tvm/Function.java   |   4 +-
 .../src/main/java/ml/dmlc/tvm/Module.java     |  10 +-
 .../src/main/java/ml/dmlc/tvm/NDArray.java    |  14 +-
 .../main/java/ml/dmlc/tvm/NDArrayBase.java    |   3 +-
 .../src/main/java/ml/dmlc/tvm/TVMType.java    |  16 +-
 .../main/java/ml/dmlc/tvm/TVMValueHandle.java |  34 ++++
 .../java/ml/dmlc/tvm/contrib/GraphModule.java | 170 ++++++++++++++++++
 .../ml/dmlc/tvm/contrib/GraphRuntime.java     | 121 +++++++++++++
 .../src/main/java/ml/dmlc/tvm/rpc/RPC.java    |   5 +
 .../main/java/ml/dmlc/tvm/rpc/RPCSession.java |   4 +-
 .../ml/dmlc/tvm/rpc/TVMRemoteContext.java     |  30 ++++
 .../src/test/java/ml/dmlc/tvm/TestUtils.java  |  26 +++
 .../ml/dmlc/tvm/contrib/GraphRuntimeTest.java | 114 ++++++++++++
 .../test/java/ml/dmlc/tvm/rpc/RPCTest.java    |  34 ++--
 .../src/test/scripts/test_graph_runtime.py    |  47 +++++
 jvm/native/src/main/native/jni_helper_func.h  |  10 ++
 tests/scripts/task_java_unittest.sh           |   1 +
 17 files changed, 601 insertions(+), 42 deletions(-)
 create mode 100644 jvm/core/src/main/java/ml/dmlc/tvm/TVMValueHandle.java
 create mode 100644 jvm/core/src/main/java/ml/dmlc/tvm/contrib/GraphModule.java
 create mode 100644 jvm/core/src/main/java/ml/dmlc/tvm/contrib/GraphRuntime.java
 create mode 100644 jvm/core/src/main/java/ml/dmlc/tvm/rpc/TVMRemoteContext.java
 create mode 100644 jvm/core/src/test/java/ml/dmlc/tvm/TestUtils.java
 create mode 100644 jvm/core/src/test/java/ml/dmlc/tvm/contrib/GraphRuntimeTest.java
 create mode 100644 jvm/core/src/test/scripts/test_graph_runtime.py

diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/Function.java b/jvm/core/src/main/java/ml/dmlc/tvm/Function.java
index 63602f3a14d0..5b2008a757ed 100644
--- a/jvm/core/src/main/java/ml/dmlc/tvm/Function.java
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/Function.java
@@ -109,8 +109,7 @@ private static Function getGlobalFunc(String name, boolean isResident, boolean a
   /**
    * Release the Function.
    * <p>
-   * We highly recommend you to do this manually since the GC strategy is lazy
-   * and `finalize()` is not guaranteed to be called when GC happens.
+   * We highly recommend you to do this manually since the GC strategy is lazy.
    * </p>
    */
   @Override public void release() {
@@ -269,6 +268,7 @@ private static void pushArgToStack(Object arg) {
         case BYTES:
           Base._LIB.tvmFuncPushArgBytes(tvmArg.asBytes());
           break;
+        case HANDLE:
         case ARRAY_HANDLE:
         case MODULE_HANDLE:
         case FUNC_HANDLE:
diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/Module.java b/jvm/core/src/main/java/ml/dmlc/tvm/Module.java
index 6aa417e889f5..7c55add36639 100644
--- a/jvm/core/src/main/java/ml/dmlc/tvm/Module.java
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/Module.java
@@ -72,8 +72,7 @@ private static Function getApi(String name) {
   /**
    * Release the Module.
    * <p>
-   * We highly recommend you to do this manually since the GC strategy is lazy
-   * and `finalize()` is not guaranteed to be called when GC happens.
+   * We highly recommend you to do this manually since the GC strategy is lazy.
    * </p>
    */
   @Override public void release() {
@@ -122,6 +121,13 @@ public void importModule(Module module) {
     Base.checkCall(Base._LIB.tvmModImport(handle, module.handle));
   }
 
+  /**
+   * @return type key of the module.
+   */
+  public String typeKey() {
+    return getApi("_GetTypeKey").pushArg(this).invoke().asString();
+  }
+
   /**
    * Load module from file.
    * @param path The path to the module file.
diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/NDArray.java b/jvm/core/src/main/java/ml/dmlc/tvm/NDArray.java
index 1aea1a35c96d..431924c4c9b0 100644
--- a/jvm/core/src/main/java/ml/dmlc/tvm/NDArray.java
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/NDArray.java
@@ -27,10 +27,12 @@
  */
 public class NDArray extends NDArrayBase {
   private final TVMType dtype;
+  private final TVMContext context;
 
-  NDArray(long handle, boolean isView, TVMType dtype) {
+  NDArray(long handle, boolean isView, TVMType dtype, TVMContext ctx) {
     super(handle, isView);
     this.dtype = dtype;
+    this.context = ctx;
   }
 
   @Override protected void finalize() throws Throwable {
@@ -361,6 +363,14 @@ private byte[][] groupInternalBytes() {
     return units;
   }
 
+  /**
+   * Get the context of current array.
+   * @return the context.
+   */
+  public TVMContext ctx() {
+    return context;
+  }
+
   /**
    * Create an empty array given shape, type and device.
    * @param shape The shape of the array.
@@ -373,7 +383,7 @@ public static NDArray empty(long[] shape, TVMType dtype, TVMContext ctx) {
     Base.checkCall(Base._LIB.tvmArrayAlloc(
         shape, dtype.typeCode, dtype.bits, dtype.lanes,
         ctx.deviceType, ctx.deviceId, refHandle));
-    return new NDArray(refHandle.value, false, dtype);
+    return new NDArray(refHandle.value, false, dtype, ctx);
   }
 
   /**
diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/NDArrayBase.java b/jvm/core/src/main/java/ml/dmlc/tvm/NDArrayBase.java
index d15caa79384f..11c77207fd1c 100644
--- a/jvm/core/src/main/java/ml/dmlc/tvm/NDArrayBase.java
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/NDArrayBase.java
@@ -57,8 +57,7 @@ public NDArrayBase copyTo(NDArrayBase target) {
   /**
    * Release the NDArray memory.
    * <p>
-   * We highly recommend you to do this manually since the GC strategy is lazy
-   * and `finalize()` is not guaranteed to be called when GC happens.
+   * We highly recommend you to do this manually since the GC strategy is lazy.
    * </p>
    */
   public void release() {
diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/TVMType.java b/jvm/core/src/main/java/ml/dmlc/tvm/TVMType.java
index 86d6efbb908b..e6b5e2ea1b9c 100644
--- a/jvm/core/src/main/java/ml/dmlc/tvm/TVMType.java
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/TVMType.java
@@ -37,16 +37,16 @@ public TVMType(String typeStr, int lanes) {
     this.lanes = lanes;
     int bitsTemp = 0;
     if (typeStr.startsWith("int")) {
-      typeCode = 0;
+      typeCode = INT;
       bitsTemp = Integer.parseInt(typeStr.substring(3));
     } else if (typeStr.startsWith("uint")) {
-      typeCode = 1;
+      typeCode = UINT;
       bitsTemp = Integer.parseInt(typeStr.substring(4));
     } else if (typeStr.startsWith("float")) {
-      typeCode = 2;
+      typeCode = FLOAT;
       bitsTemp = Integer.parseInt(typeStr.substring(5));
     } else if (typeStr.startsWith("handle")) {
-      typeCode = 4;
+      typeCode = HANDLE;
       bitsTemp = 64;
     } else {
       throw new IllegalArgumentException("Do not know how to handle type " + typeStr);
@@ -78,16 +78,16 @@ public TVMType(String typeStr) {
   @Override public String toString() {
     String typeCodeStr;
     switch (typeCode) {
-      case 0:
+      case INT:
         typeCodeStr = "int";
         break;
-      case 1:
+      case UINT:
         typeCodeStr = "uint";
         break;
-      case 2:
+      case FLOAT:
         typeCodeStr = "float";
         break;
-      case 4:
+      case HANDLE:
         typeCodeStr = "handle";
         break;
       default:
diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/TVMValueHandle.java b/jvm/core/src/main/java/ml/dmlc/tvm/TVMValueHandle.java
new file mode 100644
index 000000000000..b4316b7e72f3
--- /dev/null
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/TVMValueHandle.java
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+package ml.dmlc.tvm;
+
+/**
+ * Java class related to TVM handles (TypeCode.HANDLE)
+ */
+public class TVMValueHandle extends TVMValue {
+  public final long value;
+
+  public TVMValueHandle(long value) {
+    super(TypeCode.HANDLE);
+    this.value = value;
+  }
+
+  @Override public long asHandle() {
+    return value;
+  }
+}
diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/contrib/GraphModule.java b/jvm/core/src/main/java/ml/dmlc/tvm/contrib/GraphModule.java
new file mode 100644
index 000000000000..208006886cac
--- /dev/null
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/contrib/GraphModule.java
@@ -0,0 +1,170 @@
+package ml.dmlc.tvm.contrib;
+
+import ml.dmlc.tvm.Function;
+import ml.dmlc.tvm.Module;
+import ml.dmlc.tvm.NDArray;
+import ml.dmlc.tvm.TVMContext;
+
+/**
+ * Wrapper runtime module.
+ * This is a thin wrapper of the underlying TVM module.
+ * you can also directly call set_input, run, and get_output
+ * of underlying module functions.
+ */
+public class GraphModule {
+  private Module module;
+  private TVMContext ctx;
+
+  private Function fsetInput;
+  private Function frun;
+  private Function fgetOutput;
+  private Function fgetInput;
+  private Function fdebugGetOutput;
+  private Function floadParams;
+
+  GraphModule(Module module, TVMContext ctx) {
+    this.module = module;
+    this.ctx = ctx;
+    fsetInput = module.getFunction("set_input");
+    frun = module.getFunction("run");
+    fgetInput = module.getFunction("get_input");
+    fgetOutput = module.getFunction("get_output");
+    try {
+      fdebugGetOutput = module.getFunction("debug_get_output");
+    } catch (IllegalArgumentException ignored) {
+      // ignore
+    }
+    floadParams = module.getFunction("load_params");
+  }
+
+  /**
+   * Release the GraphModule.
+   * <p>
+   * We highly recommend you to do this manually since the GC strategy is lazy.
+   * </p>
+   */
+  public void release() {
+    fsetInput.release();
+    frun.release();
+    fgetInput.release();
+    fgetOutput.release();
+    if (fdebugGetOutput != null) {
+      fdebugGetOutput.release();
+    }
+    floadParams.release();
+    module.release();
+  }
+
+  /**
+   * Set inputs to the module.
+   * @param key The input key.
+   * @param value The input value
+   * @return self.
+   */
+  public GraphModule setInput(String key, NDArray value) {
+    NDArray input = value;
+    if (!value.ctx().equals(ctx)) {
+      input = NDArray.empty(value.shape(), ctx);
+      value.copyTo(input);
+    }
+    fsetInput.pushArg(key).pushArg(input).invoke();
+    return this;
+  }
+
+  /**
+   * Set inputs to the module
+   * @param key The input key.
+   * @param value The input value.
+   * @return self.
+   */
+  public GraphModule setInput(int key, NDArray value) {
+    NDArray input = value;
+    if (!value.ctx().equals(ctx)) {
+      input = NDArray.empty(value.shape(), ctx);
+      value.copyTo(input);
+    }
+    fsetInput.pushArg(key).pushArg(input).invoke();
+    return this;
+  }
+
+  /**
+   * Run forward execution of the graph.
+   * @return self.
+   */
+  public GraphModule run() {
+    frun.invoke();
+    return this;
+  }
+
+  /**
+   * Get index-th input to out.
+   * @param index The input index.
+   * @param out The output array container.
+   * @return out.
+   */
+  public NDArray getInput(int index, NDArray out) {
+    fgetInput.pushArg(index).pushArg(out).invoke();
+    return out;
+  }
+
+  /**
+   * Get index-th output to out.
+   * @param index The output index.
+   * @param out The output array container.
+   * @return out.
+   */
+  public NDArray getOutput(int index, NDArray out) {
+    fgetOutput.pushArg(index).pushArg(out).invoke();
+    return out;
+  }
+
+  /**
+   * Run graph up to node and get the output to out.
+   * @param node The node name.
+   * @param out The output array container.
+   * @return out.
+   */
+  public NDArray debugGetOutput(String node, NDArray out) {
+    if (fdebugGetOutput != null) {
+      fdebugGetOutput.pushArg(node).pushArg(out).invoke();
+    } else {
+      throw new RuntimeException("Please compile runtime with USE_GRAPH_RUNTIME_DEBUG = 0");
+    }
+    return out;
+  }
+
+  /**
+   * Run graph up to node and get the output to out.
+   * @param node The node index.
+   * @param out The output array container.
+   * @return out.
+   */
+  public NDArray debugGetOutput(int node, NDArray out) {
+    if (fdebugGetOutput != null) {
+      fdebugGetOutput.pushArg(node).pushArg(out).invoke();
+    } else {
+      throw new RuntimeException("Please compile runtime with USE_GRAPH_RUNTIME_DEBUG = 0");
+    }
+    return out;
+  }
+
+  /**
+   * Load parameters from serialized byte array of parameter dict.
+   * @param params The serialized parameter.
+   * @return self.
+   */
+  public GraphModule loadParams(byte[] params) {
+    floadParams.pushArg(params).invoke();
+    return this;
+  }
+
+  /**
+   * Get internal module function.
+   * @param key The key to the module.
+   * @return The function.
+   * @throws IllegalArgumentException if function does not exist.
+   */
+  public Function getFunction(String key) {
+    return module.getFunction(key);
+  }
+}
diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/contrib/GraphRuntime.java b/jvm/core/src/main/java/ml/dmlc/tvm/contrib/GraphRuntime.java
new file mode 100644
index 000000000000..edcde0cc65ec
--- /dev/null
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/contrib/GraphRuntime.java
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+package ml.dmlc.tvm.contrib;
+
+import ml.dmlc.tvm.Function;
+import ml.dmlc.tvm.Module;
+import ml.dmlc.tvm.TVMContext;
+import ml.dmlc.tvm.TVMValue;
+import ml.dmlc.tvm.rpc.RPC;
+import ml.dmlc.tvm.rpc.RPCSession;
+import ml.dmlc.tvm.rpc.TVMRemoteContext;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class GraphRuntime {
+  /**
+   * Create a runtime executor module given a graph and module.
+   * @param graphJson The graph deployed in json format output by nnvm graph.
+   * @param libmod The module of the corresponding function.
+   * @param ctx The local or remote context to deploy the module.
+   * @return Runtime graph module that can be used to execute the graph.
+   */
+  public static GraphModule create(String graphJson, Module libmod, TVMContext ctx) {
+    Module graphModule = null;
+    if (ctx.deviceType >= RPC.RPC_SESS_MASK) {
+      if (!(ctx instanceof  TVMRemoteContext)) {
+        throw new IllegalArgumentException(
+            "Looks like you are using remote context with no RPCSession bind."
+            + "Use session.context instead.");
+      }
+      RPCSession rpcSession = ((TVMRemoteContext) ctx).rpcSession;
+      // check arguments
+      if (!"rpc".equals(libmod.typeKey())) {
+        throw new IllegalArgumentException("libmod.typeKey != rpc");
+      }
+      final int sessIndex = (int) ((Function) reflectionStaticCall(
+          RPC.class, "getApi", "_SessTableIndex"))
+          .pushArg(libmod).invoke().asLong();
+      if (sessIndex != (Integer) reflectionGetField(rpcSession, "tblIndex")) {
+        throw new IllegalArgumentException(String.format(
+            "libmod SessTableIndex=%d mismatch rpcSession.tblIndex=%d",
+            sessIndex, reflectionGetField(rpcSession, "tblIndex")));
+      }
+
+      Function rpcModuleHandle = (Function) reflectionStaticCall(
+          RPC.class, "getApi","_ModuleHandle");
+      if (rpcModuleHandle == null) {
+        throw new RuntimeException("Cannot find global function tvm.rpc._ModuleHandle."
+            + "Did you compile tvm_runtime with the correct version?");
+      }
+
+      Function fcreate = Function.getFunction("tvm.graph_runtime.remote_create");
+      if (fcreate == null) {
+        throw new RuntimeException("Cannot find global function tvm.graph_runtime.remote_create."
+            + "Did you compile tvm_runtime with correct version?");
+      }
+
+      TVMValue hmod = rpcModuleHandle.pushArg(libmod).invoke();
+      graphModule = fcreate.call(graphJson, hmod,
+          ctx.deviceType % RPC.RPC_SESS_MASK, ctx.deviceId).asModule();
+    } else {
+      Function fcreate = Function.getFunction("tvm.graph_runtime.create");
+      if (fcreate == null) {
+        throw new RuntimeException("Cannot find global function tvm.graph_runtime.create."
+            + "Did you compile tvm_runtime with correct version?");
+      }
+      graphModule = fcreate.pushArg(graphJson)
+          .pushArg(libmod).pushArg(ctx.deviceType).pushArg(ctx.deviceId)
+          .invoke().asModule();
+    }
+
+    return new GraphModule(graphModule, ctx);
+  }
+
+  private static Object reflectionGetField(Object obj, String fieldName) {
+    try {
+      Field field = obj.getClass().getDeclaredField(fieldName);
+      field.setAccessible(true);
+      return field.get(obj);
+    } catch (NoSuchFieldException e) {
+      throw new RuntimeException(e);
+    } catch (IllegalAccessException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private static Object reflectionStaticCall(Class<?> clazz, String methodName, Object ... args) {
+    Class<?>[] types = new Class<?>[args.length];
+    for (int i = 0; i < args.length; ++i) {
+      types[i] = args[i].getClass();
+    }
+    try {
+      Method method = clazz.getDeclaredMethod(methodName, types);
+      method.setAccessible(true);
+      return method.invoke(null, args);
+    } catch (NoSuchMethodException e) {
+      throw new RuntimeException(e);
+    } catch (IllegalAccessException e) {
+      throw new RuntimeException(e);
+    } catch (InvocationTargetException e) {
+      throw new RuntimeException(e);
+    }
+  }
+}
diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPC.java b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPC.java
index 757fc0df3265..ee763fc41e18 100644
--- a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPC.java
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPC.java
@@ -44,6 +44,11 @@ protected Map<String, Function> initialValue() {
           }
         };
 
+  /**
+   * Get internal function starts with namespace tvm.rpc.
+   * @param name function name.
+   * @return the function, null if not exists.
+   */
   static Function getApi(String name) {
     Function func = apiFuncs.get().get(name);
     if (func == null) {
diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPCSession.java b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPCSession.java
index 59da849000e9..0eec9224a40c 100644
--- a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPCSession.java
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPCSession.java
@@ -60,7 +60,7 @@ public Function getFunction(String name) {
   public TVMContext context(String devType, int devId) {
     TVMContext ctx = new TVMContext(devType, devId);
     int encode = (tblIndex + 1) * RPC.RPC_SESS_MASK;
-    return new TVMContext(ctx.deviceType + encode, devId);
+    return new TVMRemoteContext(ctx.deviceType + encode, devId, this);
   }
 
   /**
@@ -80,7 +80,7 @@ public TVMContext context(String devType) {
    */
   public TVMContext context(int devType, int devId) {
     int encode = (tblIndex + 1) * RPC.RPC_SESS_MASK;
-    return new TVMContext(devType + encode, devId);
+    return new TVMRemoteContext(devType + encode, devId, this);
   }
 
   /**
diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/TVMRemoteContext.java b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/TVMRemoteContext.java
new file mode 100644
index 000000000000..8b4449aee44d
--- /dev/null
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/TVMRemoteContext.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+package ml.dmlc.tvm.rpc;
+
+import ml.dmlc.tvm.TVMContext;
+
+// always related to RPCSession. Cannot construct by users.
+public class TVMRemoteContext extends TVMContext {
+  public final RPCSession rpcSession;
+
+  TVMRemoteContext(int deviceType, int deviceId, RPCSession rpcSession) {
+    super(deviceType, deviceId);
+    this.rpcSession = rpcSession;
+  }
+}
diff --git a/jvm/core/src/test/java/ml/dmlc/tvm/TestUtils.java b/jvm/core/src/test/java/ml/dmlc/tvm/TestUtils.java
new file mode 100644
index 000000000000..23e22779adae
--- /dev/null
+++ b/jvm/core/src/test/java/ml/dmlc/tvm/TestUtils.java
@@ -0,0 +1,26 @@
+package ml.dmlc.tvm;
+
+import ml.dmlc.tvm.rpc.Server;
+
+import java.io.IOException;
+
+public class TestUtils {
+  public static class RefInt {
+    public int value;
+  }
+
+  public static Server startServer(RefInt portRef) {
+    Server server = null;
+    int port = 9981;
+    for (int i = 0; i < 10; ++i) {
+      try {
+        server = new Server(port + i);
+        server.start();
+        portRef.value = port + i;
+        return server;
+      } catch (IOException e) {
+      }
+    }
+    throw new RuntimeException("Cannot find an available port.");
+  }
+}
diff --git a/jvm/core/src/test/java/ml/dmlc/tvm/contrib/GraphRuntimeTest.java b/jvm/core/src/test/java/ml/dmlc/tvm/contrib/GraphRuntimeTest.java
new file mode 100644
index 000000000000..d719eb6f61e7
--- /dev/null
+++ b/jvm/core/src/test/java/ml/dmlc/tvm/contrib/GraphRuntimeTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.
+ */
+
+package ml.dmlc.tvm.contrib;
+
+import ml.dmlc.tvm.*;
+import ml.dmlc.tvm.rpc.Client;
+import ml.dmlc.tvm.rpc.RPCSession;
+import ml.dmlc.tvm.rpc.Server;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Scanner;
+
+import static org.junit.Assert.assertArrayEquals;
+
+public class GraphRuntimeTest {
+  private final Logger logger = LoggerFactory.getLogger(GraphRuntime.class);
+  private static String loadingDir;
+
+  @BeforeClass
+  public static void beforeClass() {
+    loadingDir = System.getProperty("test.tempdir");
+  }
+
+  @Test
+  public void test_add_one_local() throws IOException {
+    Module libmod = Module.load(loadingDir + File.separator + "graph_addone_lib.so");
+    String graphJson = new Scanner(new File(
+        loadingDir + File.separator + "graph_addone.json"))
+        .useDelimiter("\\Z").next();
+
+    TVMContext ctx = TVMContext.cpu();
+    GraphModule graph = GraphRuntime.create(graphJson, libmod, ctx);
+
+    long[] shape = new long[]{4};
+    NDArray arr = NDArray.empty(shape, ctx);
+    arr.copyFrom(new float[]{1f, 2f, 3f, 4f});
+
+    NDArray out = NDArray.empty(shape, ctx);
+
+    graph.setInput("x", arr).run();
+    graph.getOutput(0, out);
+
+    assertArrayEquals(new float[]{2f, 3f, 4f, 5f}, out.asFloatArray(), 1e-3f);
+
+    arr.release();
+    out.release();
+    graph.release();
+  }
+
+  @Test
+  public void test_add_one_remote() throws IOException {
+    if (!Module.enabled("rpc")) {
+      logger.warn("RPC is not enabled. Skip.");
+      return;
+    }
+
+    String libPath = loadingDir + File.separator + "graph_addone_lib.so";
+    String graphJson = new Scanner(new File(
+        loadingDir + File.separator + "graph_addone.json"))
+        .useDelimiter("\\Z").next();
+
+    TestUtils.RefInt port = new TestUtils.RefInt();
+    Server server = null;
+    try {
+      server = TestUtils.startServer(port);
+      RPCSession remote = Client.connect("localhost", port.value);
+      TVMContext ctx = remote.cpu();
+
+      remote.upload(new File(libPath));
+      Module mlib = remote.loadModule("graph_addone_lib.so");
+
+      GraphModule graph = GraphRuntime.create(graphJson, mlib, ctx);
+
+      long[] shape = new long[]{4};
+      NDArray arr = NDArray.empty(shape, ctx);
+      arr.copyFrom(new float[]{1f, 2f, 3f, 4f});
+
+      NDArray out = NDArray.empty(shape, ctx);
+
+      graph.setInput("x", arr).run();
+      graph.getOutput(0, out);
+
+      assertArrayEquals(new float[]{2f, 3f, 4f, 5f}, out.asFloatArray(), 1e-3f);
+
+      arr.release();
+      out.release();
+      graph.release();
+    } finally {
+      if (server != null) {
+        server.terminate();
+      }
+    }
+  }
+}
diff --git a/jvm/core/src/test/java/ml/dmlc/tvm/rpc/RPCTest.java b/jvm/core/src/test/java/ml/dmlc/tvm/rpc/RPCTest.java
index 982be0b8117d..63cf5575b37d 100644
--- a/jvm/core/src/test/java/ml/dmlc/tvm/rpc/RPCTest.java
+++ b/jvm/core/src/test/java/ml/dmlc/tvm/rpc/RPCTest.java
@@ -20,36 +20,21 @@
 import ml.dmlc.tvm.Function;
 import ml.dmlc.tvm.Module;
 import ml.dmlc.tvm.TVMValue;
+import ml.dmlc.tvm.TestUtils;
 import org.junit.Ignore;
 import org.junit.Test;
-
-import java.io.IOException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import static org.junit.Assert.assertEquals;
 
 public class RPCTest {
-  static class RefInt {
-    public int value;
-  }
-
-  private static Server startServer(RefInt portRef) {
-    Server server = null;
-    int port = 9981;
-    for (int i = 0; i < 10; ++i) {
-      try {
-        server = new Server(port + i);
-        server.start();
-        portRef.value = port + i;
-        return server;
-      } catch (IOException e) {
-      }
-    }
-    throw new RuntimeException("Cannot find an available port.");
-  }
+  private final Logger logger = LoggerFactory.getLogger(RPCTest.class);
 
   @Test
   public void test_addone() {
     if (!Module.enabled("rpc")) {
+      logger.warn("RPC is not enabled. Skip.");
       return;
     }
     Function.register("test.rpc.addone", new Function.Callback() {
@@ -58,10 +43,10 @@ public void test_addone() {
         }
       });
 
-    RefInt port = new RefInt();
+    TestUtils.RefInt port = new TestUtils.RefInt();
     Server server = null;
     try {
-      server = startServer(port);
+      server = TestUtils.startServer(port);
       RPCSession client = Client.connect("localhost", port.value);
       Function func = client.getFunction("test.rpc.addone");
       assertEquals(11L, func.call(10).asLong());
@@ -75,6 +60,7 @@ public void test_addone() {
   @Test
   public void test_strcat() {
     if (!Module.enabled("rpc")) {
+      logger.warn("RPC is not enabled. Skip.");
       return;
     }
     Function.register("test.rpc.strcat", new Function.Callback() {
@@ -83,10 +69,10 @@ public void test_strcat() {
       }
     });
 
-    RefInt port = new RefInt();
+    TestUtils.RefInt port = new TestUtils.RefInt();
     Server server = null;
     try {
-      server = startServer(port);
+      server = TestUtils.startServer(port);
       RPCSession client = Client.connect("localhost", port.value);
       Function func = client.getFunction("test.rpc.strcat");
       assertEquals("abc:11", func.call("abc", 11L).asString());
diff --git a/jvm/core/src/test/scripts/test_graph_runtime.py b/jvm/core/src/test/scripts/test_graph_runtime.py
new file mode 100644
index 000000000000..a60736c2468d
--- /dev/null
+++ b/jvm/core/src/test/scripts/test_graph_runtime.py
@@ -0,0 +1,47 @@
+import os
+
+import tvm
+import json
+from tvm.contrib import graph_runtime
+
+def dump_graph_lib(target_dir):
+    dim = 4
+    A = tvm.placeholder((dim,), name='A')
+    B = tvm.compute(A.shape, lambda *i: A(*i) + 1.0, name='B')
+    sched = tvm.create_schedule(B.op)
+
+    node0 = {"op": "null", "name": "x", "inputs": []}
+    node1 = {"op": "tvm_op", "name": "add",
+             "inputs": [[0, 0, 0]],
+             "attrs": {"func_name": "myadd",
+                       "flatten_data": "1",
+                       "num_inputs" : "1",
+                    "num_outputs" : "1"}}
+    nodes = [node0, node1]
+    arg_nodes = [0]
+    node_row_ptr = [0, 1, 2]
+    outputs = [[1, 0, 0]]
+    shape = (4,)
+    attrs = {
+        "shape" : ["list_shape", [shape, shape]],
+        "dltype" : ["list_str", ["float32", "float32"]],
+        "storage_id" : ["list_int", [0, 1]],
+    }
+    graph = {"nodes": nodes,
+             "arg_nodes": arg_nodes,
+             "node_row_ptr": node_row_ptr,
+             "heads": outputs,
+             "attrs": attrs}
+
+    graph = json.dumps(graph)
+    mlib = tvm.build(sched, [A, B], "llvm", name="myadd")
+
+    mlib.export_library(os.path.join(target_dir, "graph_addone_lib.so"))
+    with open(os.path.join(target_dir, "graph_addone.json"), "w") as fo:
+        fo.write(graph)
+
+if __name__ == "__main__":
+    import sys
+    if len(sys.argv) != 2:
+        sys.exit(-1)
+    dump_graph_lib(sys.argv[1])
diff --git a/jvm/native/src/main/native/jni_helper_func.h b/jvm/native/src/main/native/jni_helper_func.h
index dc04f4191d1a..d4435bdaaba8 100644
--- a/jvm/native/src/main/native/jni_helper_func.h
+++ b/jvm/native/src/main/native/jni_helper_func.h
@@ -72,6 +72,14 @@ jstring getTVMValueStringField(JNIEnv *env, jobject obj) {
   return ret;
 }
 
+jobject newTVMValueHandle(JNIEnv *env, jlong value) {
+  jclass cls = env->FindClass("ml/dmlc/tvm/TVMValueHandle");
+  jmethodID constructor = env->GetMethodID(cls, "<init>", "(J)V");
+  jobject object = env->NewObject(cls, constructor, value);
+  env->DeleteLocalRef(cls);
+  return object;
+}
+
 jobject newTVMValueLong(JNIEnv *env, jlong value) {
   jclass cls = env->FindClass("ml/dmlc/tvm/TVMValueLong");
   jmethodID constructor = env->GetMethodID(cls, "<init>", "(J)V");
@@ -166,6 +174,8 @@ jobject tvmRetValueToJava(JNIEnv *env, TVMValue value, int tcode) {
       return newTVMValueLong(env, static_cast<jlong>(value.v_int64));
     case kDLFloat:
       return newTVMValueDouble(env, static_cast<jdouble>(value.v_float64));
+    case kHandle:
+      return newTVMValueHandle(env, reinterpret_cast<jlong>(value.v_handle));
     case kModuleHandle:
       return newModule(env, reinterpret_cast<jlong>(value.v_handle));
     case kFuncHandle:
diff --git a/tests/scripts/task_java_unittest.sh b/tests/scripts/task_java_unittest.sh
index 8ae79b5c52b2..df85e496b226 100755
--- a/tests/scripts/task_java_unittest.sh
+++ b/tests/scripts/task_java_unittest.sh
@@ -8,6 +8,7 @@ TEMP_DIR=$(mktemp -d)
 
 python $SCRIPT_DIR/test_add_cpu.py $TEMP_DIR || exit -1
 python $SCRIPT_DIR/test_add_gpu.py $TEMP_DIR || exit -1
+python $SCRIPT_DIR/test_graph_runtime.py $TEMP_DIR || exit -1
 
 # start rpc proxy server
 PORT=$(( ( RANDOM % 1000 )  + 9000 ))

From 95be7c2f0bf151f3bacff24edbe0745cfaaaa2bb Mon Sep 17 00:00:00 2001
From: Keren Zhou <robinho364@gmail.com>
Date: Tue, 24 Jul 2018 16:41:54 -0700
Subject: [PATCH 908/949] Update Application.mk (#1483)

Fix application crash problems on armv7a architectures
---
 apps/android_rpc/app/src/main/jni/Application.mk | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/apps/android_rpc/app/src/main/jni/Application.mk b/apps/android_rpc/app/src/main/jni/Application.mk
index c5509c1f4506..5bf52bdaffc0 100644
--- a/apps/android_rpc/app/src/main/jni/Application.mk
+++ b/apps/android_rpc/app/src/main/jni/Application.mk
@@ -11,7 +11,7 @@ include $(config)
 # We target every architecture except armeabi here, for two reasons:
 # 1) armeabi is deprecated in NDK r16 and removed in r17
 # 2) vulkan is not supported in armeabi
-APP_ABI := armeabi-v7a arm64-v8a x86 x86_64 mips
+APP_ABI ?= armeabi-v7a arm64-v8a x86 x86_64 mips
 APP_STL := c++_static
 
 APP_CPPFLAGS += -DDMLC_LOG_STACK_TRACE=0 -DTVM4J_ANDROID=1 -std=c++11 -Oz -frtti

From ea46b1dfdfd8bf6f5cdf83f7b04555fb1bedfef2 Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@cs.washington.edu>
Date: Tue, 24 Jul 2018 17:58:58 -0700
Subject: [PATCH 909/949] [DOC] Update VTA readme files to avoid stale
 information (#1484)

* updated readme files to avoid stale instructions

* bsp generation turned off by default
---
 vta/README.md                 | 22 +++++------
 vta/hardware/xilinx/Makefile  |  2 +-
 vta/hardware/xilinx/README.md | 72 +----------------------------------
 3 files changed, 12 insertions(+), 84 deletions(-)

diff --git a/vta/README.md b/vta/README.md
index 677c9b9103ca..4b1d17ff4bb4 100644
--- a/vta/README.md
+++ b/vta/README.md
@@ -1,18 +1,16 @@
 VTA: Open, Modular, Deep Learning Accelerator Stack
 ===================================================
-VTA(versatile tensor accelerator) is an open-source deep learning accelerator stack.
-It is not just an open-source hardware, but is an end to end solution that includes
-the entire software stack on top of VTA open-source hardware.
+VTA (versatile tensor accelerator) is an open-source deep learning accelerator complemented with an end-to-end TVM-based compiler stack.
 
-The key features include:
+The key features of VTA include:
 
-- Generic, modular open-source hardware
+- Generic, modular, open-source hardware
   - Streamlined workflow to deploy to FPGAs.
-  - Simulator support to protoype compilation passes on regular workstations.
-- Driver and JIT runtime for both simulated and FPGA hardware backend.
-- End to end TVM stack integration
-  - Direct optimization and deploy models from deep learning frameworks via TVM stack.
-  - Customized and extendible TVM compiler backend.
-  - Flexible RPC support to ease the deployment, and program FPGAs with Python
+  - Simulator support to prototype compilation passes on regular workstations.
+- Driver and JIT runtime for both simulator and FPGA hardware back-end.
+- End-to-end TVM stack integration
+  - Direct optimization and deployment of models from deep learning frameworks via TVM.
+  - Customized and extensible TVM compiler back-end.
+  - Flexible RPC support to ease deployment, and program FPGAs with the convenience of Python.
 
-VTA is part of our effort on TVM Stack.
+Learn more about VTA [here](https://docs.tvm.ai/vta/index.html).
\ No newline at end of file
diff --git a/vta/hardware/xilinx/Makefile b/vta/hardware/xilinx/Makefile
index 4a6d38d34aa8..0c365be0ed61 100644
--- a/vta/hardware/xilinx/Makefile
+++ b/vta/hardware/xilinx/Makefile
@@ -76,7 +76,7 @@ BIT_PATH = $(BUILD_DIR)/vivado/$(CONF)/export/$(CONF).bit
 
 .PHONY: all ip bit bsp clean clean_all
 
-all: bsp
+all: bit
 ip: $(IP_PATH)
 bit: $(BIT_PATH)
 
diff --git a/vta/hardware/xilinx/README.md b/vta/hardware/xilinx/README.md
index 69f66d654e6d..cd44c725711d 100644
--- a/vta/hardware/xilinx/README.md
+++ b/vta/hardware/xilinx/README.md
@@ -1,71 +1 @@
-# Hardware Compilation Guide
-
-**This hardware compilation guide aims to provide guidance on generating VTA bitstreams with the Xilinx Vivado toolchains.**
-
-As of writing this guide, we recommend using `Vivado 2017.1` since our scripts have been tested to work on this version of the Xilinx toolchains.
-
-# Vivado Toolchains Installation for Pynq Board
-
-## Ubuntu instructions
-
-You’ll need to install Xilinx’ FPGA compilation toolchain, [Vivado HL WebPACK 2017.1](https://www.xilinx.com/products/design-tools/vivado.html), which a license-free version of the Vivado HLx toolchain.
-
-### Obtaining and launching the installation binary
- 
-1. Go to the [download webpage](https://www.xilinx.com/support/download.html), and download the Linux Self Extracting Web Installer for Vivado HL 2017.1 WebPACK and Editions.
-2. You’ll have to sign in with a Xilinx account. This requires a Xilinx account creation that will take 2 minutes.
-3. Complete the Name and Address Verification by clicking “Next”, and you will get the opportunity to download a binary file, called `Xilinx_Vivado_SDK_2017.1_0415_1_Lin64.bin`.
-4. Now that the file is downloaded, go to your `Downloads` directory, and change the file permissions so it can be executed:
-```bash
-chmod u+x Xilinx_Vivado_SDK_2017.1_0415_1_Lin64.bin
-```
-5. Now you can execute the binary: 
-```bash
-./Xilinx_Vivado_SDK_2017.1_0415_1_Lin64.bin
-```
-
-### Installation Steps
-
-At this point you've launched the Vivado 2017.1 Installer GUI program.
-
-1. Click “Next” on the **Welcome** screen.
-2. Enter your Xilinx User Credentials under “User Authentication” and select the “Download and Install Now” before clicking “Next” on the **Select Install Type** screen.
-3. Accept all terms before clicking on “Next” on the **Accept License Agreements** screen.
-4. Select the “Vivado HL WebPACK” before clicking on “Next” on the **Select Edition to Install** screen.
-5. Under the **Vivado HL WebPACK** screen, before hitting “Next", check the following options (the rest should be unchecked):
-   * Design Tools -> Vivado Design Suite -> Vivado
-   * Design Tools -> Vivado Design Suite -> Vivado High Level Synthesis
-   * Devices -> Production Services -> SoCs -> Zynq-7000 Series
-6. Your total download size should be about 3GB and the amount of Disk Space Required 13GB.
-7. Set the installation directory before clicking “Next” on the **Select Destination Directory** screen. It might highlight some paths as red - that’s because the installer doesn’t have the permission to write to that directory. In that case select a path that doesn’t require special write permissions (e.g. in your home directory).
-8. Hit “Install” under the **Installation Summary** screen.
-9. An **Installation Progress Window** will pop-up to track progress of the download and the installation.
-10. This process will take about 20-30 minutes depending on your connection speed.
-11. A pop-up window will inform you that the installation completed successfully. Click "OK".
-12. Finally the **Vivado License Manager** will launch. Select "Get Free ISE WebPACK, ISE/Vivado IP or PetaLinux License" and click "Connect Now" to complete the license registration process. 
-
-### Environment Setup
-
-The last step is to update your `~/.bashrc` with the following line:
-```bash
-# Xilinx Vivado 2017.1 environment
-source <install_path>/Vivado/2017.1/settings64.sh
-```
-
-This will include all of the Xilinx binary paths so you can launch compilation scripts from the command line.
-
-Note that this will overwrite the paths to GCC required to build TVM, or NNVM. Therefore, when attempting to build TVM and NNVM, please comment this line from your `~/.bashrc` before re-sourcing it.
-
-# Bitstream compilation
-
-High-level parameters are listed under `<vta root>/make/config.mk` and can be customized by the user.
-
-Bitstream generation is driven by a makefile. All it takes is to enter the following command:
-```bash
-make
-```
-
-The local `Makefile` containts several variables that can be tweaked by the user:
-* `VTA_HW_COMP_THREADS`: determines the number of threads used for the Vivado compilation job (default 8 threads).
-
-Once the compilation completes, the generated bitstream can be found under `<vta root>/build/hardware/xilinx/vivado/<design name>/export/vta.bit`. 
\ No newline at end of file
+Complete instructions on how to build custom FPGA hardware designs are available on the [TVM documentation webpage](https://docs.tvm.ai/vta/install.html#vta-fpga-toolchain-installation).
\ No newline at end of file

From c24448fcad49e2b7ca8b0a7a6bc30643febffc6f Mon Sep 17 00:00:00 2001
From: Siju <sijusamuel@gmail.com>
Date: Wed, 25 Jul 2018 21:50:46 +0530
Subject: [PATCH 910/949] [NNVM][DARKNET]Logistic activation added (#1477)

---
 nnvm/python/nnvm/frontend/darknet.py          |  7 ++++-
 .../python/frontend/darknet/test_forward.py   | 28 +++++++++++++++++++
 2 files changed, 34 insertions(+), 1 deletion(-)

diff --git a/nnvm/python/nnvm/frontend/darknet.py b/nnvm/python/nnvm/frontend/darknet.py
index 49d608137212..50625b4b17a4 100644
--- a/nnvm/python/nnvm/frontend/darknet.py
+++ b/nnvm/python/nnvm/frontend/darknet.py
@@ -301,7 +301,9 @@ def _darknet_region(inputs, attrs):
 def _darknet_activations(inputs, attrs):
     """Process the activation function."""
     act = _darknet_required_attr(attrs, 'activation')
-    if ACTIVATION.RELU == act:
+    if ACTIVATION.LOGISTIC == act:
+        act_type = 'sigmoid'
+    elif ACTIVATION.RELU == act:
         act_type = 'relu'
     elif ACTIVATION.TANH == act:
         act_type = 'tanh'
@@ -323,6 +325,9 @@ def _darknet_activations(inputs, attrs):
         sym = _darknet_get_nnvm_op(op_name)(*inputs, **new_attrs)
     elif act_type in ['elu']:
         sym = -1 * _sym.relu(1 - _sym.exp(*inputs)) + _sym.relu(*inputs)
+    elif act_type in ['sigmoid']:
+        op_name, new_attrs = act_type, {}
+        sym = _darknet_get_nnvm_op(op_name)(*inputs, **new_attrs)
     else:
         _darknet_raise_not_supported('act_type: ' + act_type)
     return sym, None
diff --git a/nnvm/tests/python/frontend/darknet/test_forward.py b/nnvm/tests/python/frontend/darknet/test_forward.py
index a8f7ccdeffd7..961da238452c 100644
--- a/nnvm/tests/python/frontend/darknet/test_forward.py
+++ b/nnvm/tests/python/frontend/darknet/test_forward.py
@@ -324,6 +324,32 @@ def test_forward_rnn():
     test_rnn_forward(net)
     LIB.free_network(net)
 
+def test_forward_activation_logistic():
+    '''test logistic activation layer'''
+    net = LIB.make_network(1)
+    batch = 1
+    h = 224
+    w = 224
+    c = 3
+    n = 32
+    groups = 1
+    size = 3
+    stride = 2
+    padding = 0
+    activation = 0
+    batch_normalize = 0
+    binary = 0
+    xnor = 0
+    adam = 0
+    layer_1 = LIB.make_convolutional_layer(batch, h, w, c, n, groups, size, stride, padding,
+                                           activation, batch_normalize, binary, xnor, adam)
+    net.layers[0] = layer_1
+    net.w = w
+    net.h = h
+    LIB.resize_network(net, net.w, net.h)
+    test_forward(net)
+    LIB.free_network(net)
+
 if __name__ == '__main__':
     test_forward_resnet50()
     test_forward_alexnet()
@@ -342,3 +368,5 @@ def test_forward_rnn():
     test_forward_reorg()
     test_forward_region()
     test_forward_elu()
+    test_forward_rnn()
+    test_forward_activation_logistic()
\ No newline at end of file

From b061eb1d92efba7fe0881fac2fe2b4bd80eeaf0b Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 25 Jul 2018 09:30:23 -0700
Subject: [PATCH 911/949] [CODEGEN] Enable inline llvm asm code (#1486)

---
 docs/api/python/contrib.rst                |  6 ++
 include/tvm/ir.h                           |  2 +
 python/tvm/contrib/clang.py                | 96 ++++++++++++++++++++++
 python/tvm/contrib/util.py                 | 43 ++++++++++
 python/tvm/schedule.py                     |  2 +
 src/codegen/llvm/codegen_cpu.cc            |  5 ++
 src/codegen/llvm/codegen_llvm.cc           | 36 ++++++++
 src/codegen/llvm/codegen_llvm.h            |  2 +
 src/codegen/llvm/llvm_common.h             |  1 +
 src/codegen/llvm/llvm_module.cc            |  7 ++
 tests/python/unittest/test_codegen_llvm.py | 43 ++++++++++
 11 files changed, 243 insertions(+)
 create mode 100644 python/tvm/contrib/clang.py

diff --git a/docs/api/python/contrib.rst b/docs/api/python/contrib.rst
index de0946e29538..a58a3aa4fbef 100644
--- a/docs/api/python/contrib.rst
+++ b/docs/api/python/contrib.rst
@@ -8,6 +8,12 @@ tvm.contrib.cblas
     :members:
 
 
+tvm.contrib.clang
+~~~~~~~~~~~~~~~~~
+.. automodule:: tvm.contrib.clang
+    :members:
+
+
 tvm.contrib.cc
 ~~~~~~~~~~~~~~
 .. automodule:: tvm.contrib.cc
diff --git a/include/tvm/ir.h b/include/tvm/ir.h
index 97e0b44d2fec..9ea16131188d 100644
--- a/include/tvm/ir.h
+++ b/include/tvm/ir.h
@@ -179,6 +179,8 @@ constexpr const char* loop_scope = "loop_scope";
 constexpr const char* reduce_scope = "reduce_scope";
 /*! \brief Mark region is guarded by the pragma extension */
 constexpr const char* pragma_scope_prefix = "pragma_";
+/*! \brief Import llvm source or file into the final code gen module */
+constexpr const char* pragma_import_llvm = "pragma_import_llvm";
 /*!
  * \brief Mark of prefetch scope, value=offset,
  *  run prefetch of Tensor on the current loop scope
diff --git a/python/tvm/contrib/clang.py b/python/tvm/contrib/clang.py
new file mode 100644
index 000000000000..19508160d42d
--- /dev/null
+++ b/python/tvm/contrib/clang.py
@@ -0,0 +1,96 @@
+"""Util to invoke clang in the system."""
+# pylint: disable=invalid-name
+from __future__ import absolute_import as _abs
+import subprocess
+
+from .._ffi.base import py_str
+from .. import codegen
+from . import util
+
+
+def find_clang(required=True):
+    """Find clang in system.
+
+    Parameters
+    ----------
+    required : bool
+        Whether it is required,
+        runtime error will be raised if the compiler is required.
+
+    Returns
+    -------
+    valid_list : list of str
+        List of possible paths.
+
+    Note
+    ----
+    This function will first search clang that
+    matches the major llvm version that built with tvm
+    """
+    cc_list = []
+    if hasattr(codegen, "llvm_version_major"):
+        cc_list += ["clang-%d.0" % codegen.llvm_version_major()]
+    cc_list += ["clang"]
+    valid_list = [util.which(x) for x in cc_list]
+    valid_list = [x for x in valid_list if x]
+    if not valid_list and required:
+        raise RuntimeError(
+            "cannot find clang, candidates are: " + str(cc_list))
+    return valid_list
+
+
+def create_llvm(inputs,
+                output=None,
+                options=None,
+                cc=None):
+    """Create llvm text ir.
+
+    Parameters
+    ----------
+    inputs : list of str
+        List of input files name or code source.
+
+    output : str, optional
+        Output file, if it is none
+        a temporary file is created
+
+    options : list
+        The list of additional options string.
+
+    cc : str, optional
+        The clang compiler, if not specified,
+        we will try to guess the matched clang version.
+
+    Returns
+    -------
+    code : str
+        The generated llvm text IR.
+    """
+    cc = cc if cc else find_clang()[0]
+    cmd = [cc]
+    cmd += ["-S", "-emit-llvm"]
+    temp = util.tempdir()
+    output = output if output else temp.relpath("output.ll")
+    inputs = [inputs] if isinstance(inputs, str) else inputs
+    input_files = []
+    for i, code in enumerate(inputs):
+        if util.is_source_path(code):
+            input_files.append(code)
+        else:
+            temp_path = temp.relpath("input%d.cc" % i)
+            with open(temp_path, "w") as output_file:
+                output_file.write(code)
+            input_files.append(temp_path)
+    if options:
+        cmd += options
+    cmd += ["-o", output]
+    cmd += input_files
+    proc = subprocess.Popen(
+        cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+    (out, _) = proc.communicate()
+    if proc.returncode != 0:
+        msg = "Compilation error:\n"
+        msg += py_str(out)
+        raise RuntimeError(msg)
+
+    return open(output).read()
diff --git a/python/tvm/contrib/util.py b/python/tvm/contrib/util.py
index e2b4011c4c0a..fe176dee2791 100644
--- a/python/tvm/contrib/util.py
+++ b/python/tvm/contrib/util.py
@@ -86,6 +86,7 @@ def release(self):
             self.lock_file.close()
             self.lock_file = None
 
+
 def filelock(path):
     """Create a file lock which locks on path
 
@@ -99,3 +100,45 @@ def filelock(path):
     lock : File lock object
     """
     return FileLock(path)
+
+
+def is_source_path(path):
+    """Check if path is source code path.
+
+    Parameters
+    ----------
+    path : str
+        A possible path
+
+    Returns
+    -------
+    valid : bool
+        Whether path is a possible source path
+    """
+    if os.path.exists(path):
+        return True
+    if path.find("\n") != -1:
+        return False
+    spath = path.rsplit(".", 1)
+    return len(spath) == 2 and spath[1].strip() == spath[1]
+
+
+def which(exec_name):
+    """Try to find full path of exec_name
+
+    Parameters
+    ----------
+    exec_name : str
+        The executable name
+
+    Returns
+    -------
+    path : str
+        The full path of executable if found, otherwise returns None
+    """
+    base_list = ["", "/bin"] + os.environ.get("PATH", "").split(os.pathsep)
+    for path in base_list:
+        full_path = os.path.join(path, exec_name)
+        if os.path.isfile(full_path) and os.access(full_path, os.X_OK):
+            return full_path
+    return None
diff --git a/python/tvm/schedule.py b/python/tvm/schedule.py
index 5aeac51555f7..594c2f2dc8bd 100644
--- a/python/tvm/schedule.py
+++ b/python/tvm/schedule.py
@@ -603,6 +603,8 @@ def pragma(self, var, pragma_type, pragma_value=None):
           :code:`for (int i = task_id; i < end; i += num_task)`
 
         """
+        if isinstance(pragma_value, string_types):
+            pragma_value = convert(pragma_value)
         _api_internal._StagePragma(self, var, pragma_type, pragma_value)
 
     def prefetch(self, tensor, var, offset):
diff --git a/src/codegen/llvm/codegen_cpu.cc b/src/codegen/llvm/codegen_cpu.cc
index bdf7d2114368..a8a2127febde 100644
--- a/src/codegen/llvm/codegen_cpu.cc
+++ b/src/codegen/llvm/codegen_cpu.cc
@@ -701,6 +701,11 @@ void CodeGenCPU::VisitStmt_(const AttrStmt* op) {
       builder_->CreateCall(
           RuntimeTVMParallelBarrier(),
           {MakeValue(parallel_env_.task_id),  parallel_env_.penv});
+    } else if (op->attr_key == ir::attr::pragma_import_llvm) {
+      const StringImm* value = op->value.as<StringImm>();
+      CHECK(value != nullptr);
+      this->HandleImport(value->value);
+      this->VisitStmt(op->body);
     } else {
       LOG(WARNING) << "Unknown pragma " << op->attr_key;
       this->VisitStmt(op->body);
diff --git a/src/codegen/llvm/codegen_llvm.cc b/src/codegen/llvm/codegen_llvm.cc
index 329d7311c9f4..ae576c981395 100644
--- a/src/codegen/llvm/codegen_llvm.cc
+++ b/src/codegen/llvm/codegen_llvm.cc
@@ -159,6 +159,42 @@ std::unique_ptr<llvm::Module> CodeGenLLVM::Finish() {
   return std::move(module_);
 }
 
+
+void CodeGenLLVM::HandleImport(const std::string& code) {
+  std::unique_ptr<llvm::Module> mlib;
+  llvm::SMDiagnostic err;
+  if (code.length() >= 3 &&
+      (code.substr(code.length() - 3) == ".ll" ||
+       code.substr(code.length() - 3) == ".bc")) {
+    mlib = llvm::parseIRFile(code, err, *ctx_);
+    if (mlib.get() == nullptr) {
+      std::string msg = err.getMessage();
+      LOG(FATAL) << "Fail to load bitcode file " << code << "\n"
+                 << "line " << err.getLineNo() << ":" << msg;
+    }
+  } else {
+    std::unique_ptr<llvm::MemoryBuffer> buf =
+        llvm::MemoryBuffer::getMemBuffer(code);
+    mlib = llvm::parseIR(*buf, err, *ctx_);
+    if (mlib.get() == nullptr) {
+      std::string msg = err.getMessage();
+      LOG(FATAL) << "Fail to load llvm ir "
+                 << "line " << err.getLineNo() << ":" << msg
+                 << "\ncontent:\n"  << code;
+    }
+  }
+  mlib->setTargetTriple(target_machine_->getTargetTriple().str());
+  mlib->setDataLayout(target_machine_->createDataLayout());
+  // mark all the functions as force inline
+  for (llvm::Function &f : mlib->functions()) {
+    f.removeFnAttr(llvm::Attribute::NoInline);
+    f.addFnAttr(llvm::Attribute::AlwaysInline);
+    f.setLinkage(llvm::GlobalValue::AvailableExternallyLinkage);
+  }
+  // add to linker libraries.
+  this->AddLinkModule(std::move(mlib));
+}
+
 void CodeGenLLVM::AddLinkModule(std::unique_ptr<llvm::Module>&& mod) {
   link_modules_.emplace_back(std::move(mod));
 }
diff --git a/src/codegen/llvm/codegen_llvm.h b/src/codegen/llvm/codegen_llvm.h
index fbc74f092825..4e61247f4acf 100644
--- a/src/codegen/llvm/codegen_llvm.h
+++ b/src/codegen/llvm/codegen_llvm.h
@@ -178,6 +178,8 @@ class CodeGenLLVM :
   // do a scalarize call with f
   llvm::Value* CreateScalarizedCall(
       const Call* op, llvm::Function* f, const std::vector<llvm::Value*>& args);
+  // handle module import
+  void HandleImport(const std::string& code);
   // cast operatpr
   llvm::Value* CreateCast(Type from, Type to, llvm::Value* value);
   // comparison op
diff --git a/src/codegen/llvm/llvm_common.h b/src/codegen/llvm/llvm_common.h
index 35403952f21f..d5d27bf83d71 100644
--- a/src/codegen/llvm/llvm_common.h
+++ b/src/codegen/llvm/llvm_common.h
@@ -34,6 +34,7 @@
 #include <llvm/Transforms/IPO.h>
 
 #include <llvm/Support/FileSystem.h>
+#include <llvm/Support/MemoryBuffer.h>
 #include <llvm/Support/raw_ostream.h>
 #include <llvm/Support/Casting.h>
 #include <llvm/Support/TargetRegistry.h>
diff --git a/src/codegen/llvm/llvm_module.cc b/src/codegen/llvm/llvm_module.cc
index 99740b0dbdca..1b0e43f9c23a 100644
--- a/src/codegen/llvm/llvm_module.cc
+++ b/src/codegen/llvm/llvm_module.cc
@@ -298,6 +298,13 @@ TVM_REGISTER_API("codegen.build_llvm")
     *rv = runtime::Module(n);
   });
 
+TVM_REGISTER_API("codegen.llvm_version_major")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    std::ostringstream os;
+    int major = TVM_LLVM_VERSION / 10;
+    *rv = major;
+  });
+
 TVM_REGISTER_API("module.loadfile_ll")
 .set_body([](TVMArgs args, TVMRetValue* rv) {
     std::shared_ptr<LLVMModuleNode> n = std::make_shared<LLVMModuleNode>();
diff --git a/tests/python/unittest/test_codegen_llvm.py b/tests/python/unittest/test_codegen_llvm.py
index e07f4aa8f40c..9f282050df3e 100644
--- a/tests/python/unittest/test_codegen_llvm.py
+++ b/tests/python/unittest/test_codegen_llvm.py
@@ -1,4 +1,5 @@
 import tvm
+from tvm.contrib import util, clang
 import numpy as np
 import ctypes
 
@@ -17,6 +18,47 @@ def test_llvm_intrin():
     func = tvm.ir_pass.MakeAPI(body, "prefetch", [A], 0, True)
     fcode = tvm.build(func, None, "llvm")
 
+
+def test_llvm_import():
+    # extern "C" is necessary to get the correct signature
+    cc_code = """
+    extern "C" float my_add(float x, float y) {
+      return x + y;
+    }
+    """
+    n = 10
+    A = tvm.placeholder((n,), name='A')
+    B = tvm.compute((n,), lambda *i:
+                    tvm.call_pure_extern("float32", "my_add", A(*i), 1.0),
+                    name='B')
+    def check_llvm(use_file):
+        if not tvm.module.enabled("llvm"):
+            return
+        if not clang.find_clang(required=False):
+            print("skip because clang is not available")
+            return
+        temp = util.tempdir()
+        ll_path = temp.relpath("temp.ll")
+        ll_code = clang.create_llvm(cc_code, output=ll_path)
+        s = tvm.create_schedule(B.op)
+        if use_file:
+            s[B].pragma(s[B].op.axis[0], "import_llvm", ll_path)
+        else:
+            s[B].pragma(s[B].op.axis[0], "import_llvm", ll_code)
+        # BUILD and invoke the kernel.
+        f = tvm.build(s, [A, B], "llvm")
+        ctx = tvm.cpu(0)
+        # launch the kernel.
+        a = tvm.nd.array(np.random.uniform(size=n).astype(A.dtype), ctx)
+        b = tvm.nd.array(np.random.uniform(size=n).astype(B.dtype), ctx)
+        f(a, b)
+        np.testing.assert_allclose(
+            b.asnumpy(), a.asnumpy() + 1.0)
+    check_llvm(use_file=True)
+    check_llvm(use_file=False)
+
+
+
 def test_llvm_lookup_intrin():
     ib = tvm.ir_builder.create()
     m = tvm.var("m")
@@ -322,6 +364,7 @@ def test_alignment():
 
 
 if __name__ == "__main__":
+    test_llvm_import()
     test_alignment()
     test_rank_zero()
     test_llvm_bool()

From 9176753bc27cbe67ab8b9e463ffeb4aecb85aa32 Mon Sep 17 00:00:00 2001
From: Albin Joy <albin.joy@huawei.com>
Date: Wed, 25 Jul 2018 22:26:05 +0530
Subject: [PATCH 912/949] [NNVM][TENSORFLOW] LSTM operator and PTB word
 prediction frontend (#1389)

---
 nnvm/python/nnvm/frontend/tensorflow.py       | 442 +++++++++++++++++-
 nnvm/python/nnvm/testing/tf.py                | 142 ++++++
 .../frontend/tensorflow/test_forward.py       | 279 ++++++++++-
 3 files changed, 854 insertions(+), 9 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/tensorflow.py b/nnvm/python/nnvm/frontend/tensorflow.py
index 67da035075c7..27f19a1e177a 100644
--- a/nnvm/python/nnvm/frontend/tensorflow.py
+++ b/nnvm/python/nnvm/frontend/tensorflow.py
@@ -1,4 +1,4 @@
-# pylint: disable=import-self, invalid-name, unused-argument
+# pylint: disable=import-self, invalid-name, unused-argument, too-many-lines
 """TF: Tensorflow frontend."""
 from __future__ import absolute_import as _abs
 from __future__ import print_function
@@ -457,6 +457,190 @@ def _impl(inputs, attr, params):
         return inputs[0]
     return _impl
 
+def _fill():
+    def _impl(inputs, attr, params):
+        fill_arg = params.pop(inputs.pop(1).list_output_names()[0])
+        new_inputs = []
+        return AttrCvt(
+            op_name='full',
+            extras={'shape':inputs[0],
+                    'fill_value':fill_arg.asnumpy()[0], 'dtype':attr['T'].name},
+            ignores=['index_type', 'T'])(new_inputs, attr)
+    return _impl
+
+def _gather_v2():
+    "Tensorflow now support only gatherv2"
+    def _impl(inputs, attr, params):
+        axis = params[inputs.pop(2).list_output_names()[0]].asnumpy()[0]
+        new_input = []
+        new_input.append(inputs.pop(0))
+        new_input.append(inputs.pop(0))
+        return AttrCvt(
+            op_name="take",
+            extras={'axis':axis},
+            ignores=['Tindices', 'Tparams', 'validate_indices', \
+                     'Taxis', '_class'])(new_input, attr)
+    return _impl
+
+def _infer_out_shapes(inputs, params):
+    """A method to get the output shape of an intermediate node in the NNVM graph."""
+    g = _graph.create(inputs)
+    shape_dict = {k: v.shape for k, v in params.items()}
+    _, out_shapes = graph_util.infer_shape(g, **shape_dict)
+    return out_shapes
+
+def _stridedSlice():
+    def _impl(inputs, attr, params):
+        """Strided Slice.
+        Operator description: https://www.tensorflow.org/api_docs/python/tf/strided_slice
+        Tensorflow mask validation: https://github.com/tensorflow/tensorflow/blob/master/
+        tensorflow/core/util/strided_slice_op.cc#L147-L368
+        """
+        begin = params.pop(inputs[1].list_output_names()[0]).asnumpy().tolist()
+        end = params.pop(inputs[2].list_output_names()[0]).asnumpy().tolist()
+        stride = params.pop(inputs[3].list_output_names()[0]).asnumpy().tolist()
+        begin_mask = int(attr.get('begin_mask', 0))
+        end_mask = int(attr.get('end_mask', 0))
+        ellipsis_mask = int(attr.get('ellipsis_mask', 0))
+        new_axis_mask = int(attr.get('new_axis_mask', 0))
+        shrink_axis_mask = int(attr.get('shrink_axis_mask', 0))
+        data_shape = attr['_input_shapes'][inputs[0]]
+        data_dim = len(data_shape[0])
+        stride_dim = len(stride)
+
+        def _transform_mask(stride_dim, ellipsis_mask):
+            """Handle mask inputs to create new begin, end, stride and output shape"""
+            m_begin = [0] * data_dim
+            m_end = [0] * data_dim
+            m_stride = [0] * data_dim
+            #Count new axis after ellipsis_mask, consider while applying ellipsis_mask.
+            ellipsis_seen = False
+            new_axes_after_ellipsis = 0
+            for i in range(stride_dim):
+                mask = 1 << i
+                if ellipsis_seen and (mask & new_axis_mask) != 0:
+                    new_axes_after_ellipsis += 1
+                if (mask & ellipsis_mask) != 0:
+                    ellipsis_seen = True
+            if not ellipsis_seen:
+                #Used later for extending the stride attributes in the below loop.
+                ellipsis_mask |= (1 << stride_dim)
+                stride_dim += 1
+            final_index = 0
+            for index in range(stride_dim):
+                mask = 1 << index
+                if mask & ellipsis_mask:
+                    #Identify the end index for applying ellipsis_mask
+                    to_index = min(((data_dim - (stride_dim-index)) + 1 \
+                                     + new_axes_after_ellipsis), data_dim)
+                    for i in range(final_index, to_index):
+                        m_begin[final_index] = 0
+                        m_end[final_index] = data_shape[0][final_index]
+                        m_stride[final_index] = 1
+                        final_index += 1
+                elif not mask & new_axis_mask:
+                    if final_index == len(m_begin):
+                        break
+                    if mask & begin_mask:
+                        m_begin[final_index] = data_shape[0][final_index] \
+                                                     if stride[index] < 0 else 0
+                    elif begin[index]:
+                        m_begin[final_index] = begin[index]
+                    if mask & end_mask:
+                        m_end[final_index] = 0 if stride[index] < 0 \
+                                                 else data_shape[0][final_index]
+                    elif end[index]:
+                        m_end[final_index] = end[index]
+                    m_stride[final_index] = stride[index]
+                    if mask & shrink_axis_mask:
+                        #Tensorflow make axis with shrink_axis_mask as dimension 1
+                        m_begin[final_index] = data_shape[0][final_index] + begin[index] \
+                                                 if begin[index] < 0 else begin[index]
+                        m_end[final_index] = begin[index] + 1
+                        m_stride[final_index] = 1
+                    final_index += 1
+            return m_begin, m_end, m_stride
+
+        if begin_mask or end_mask or ellipsis_mask or new_axis_mask or shrink_axis_mask:
+            begin, end, stride = _transform_mask(stride_dim, ellipsis_mask)
+        out = _sym.strided_slice(inputs[0], begin=begin, end=end, stride=stride)
+        out_shape = _infer_out_shapes(out, params)[0]
+
+        #Create final output shape.
+        final_output = []
+        out_index = 0
+        index = 0
+        while out_index != len(out_shape):
+            #axis with shrink_axis_mask dimension=1 and it is ignored.
+            mask = 1 << index
+            if (new_axis_mask & mask) and not ellipsis_mask & mask:
+                final_output.append(1)
+            elif (not mask & shrink_axis_mask) or index >= stride_dim:
+                #Shrink is considered till stride_dim
+                final_output.append(out_shape[out_index])
+                out_index += 1
+            index += 1
+        return _sym.reshape(out, shape=tuple(final_output))
+    return _impl
+
+def _LSTMBlockCell():
+    def _impl(inputs, in_state_c, in_state_h, attr, params):
+        """LSTM Block cell.
+        Calculations are described in: https://github.com/tensorflow/tensorflow/blob/
+        r1.8/tensorflow/contrib/rnn/python/ops/lstm_ops.py#L41-L114
+
+        Parameters
+        ----------
+        inputs : nnvm.Symbol
+            Input data
+        in_state_c: list of nnvm.Symbol
+            Cell state input values for all the layers
+        in_state_h: list of nnvm.Symbol
+            Hidden state input values for all the layers
+        attrs : dict
+            Dict of operator attributes
+        params : dict
+            List of pretrained weights and bias
+
+        Returns
+        -------
+        sym : nnvm.Symbol
+            Converted nnvm Symbol
+        output: nnvm.Symbol
+            Output state value.
+        """
+        in_data = inputs[0]
+        in_weight = inputs[3]
+        in_bias = inputs[7]
+        forget_bias = attr.pop('forget_bias')
+        input_shape = attr['_input_shapes'][inputs[0]]
+        weight_shape = attr['_input_shapes'][inputs[3]]
+        batch_size, input_size = input_shape[0][0], input_shape[0][1]
+        num_hidden_layers = weight_shape[0][1]
+        num_hidden = num_hidden_layers // 4
+
+        in_data = _sym.reshape(in_data,
+                               shape=(batch_size, input_size))
+        ixh = _sym.concatenate(*[in_data, in_state_h], axis=1)
+        in_weight = _sym.transpose(in_weight)
+        gates = _sym.dense(ixh, in_weight, in_bias, use_bias=True,
+                           units=num_hidden_layers, name="dense")
+        gate_list = _sym.split(gates, indices_or_sections=4, axis=1)
+        in_gate = _sym.sigmoid(gate_list[0])
+        in_transform = _sym.tanh(gate_list[1])
+        forget_gate = _sym.sigmoid(gate_list[2])
+        forget_gate = forget_gate + forget_bias
+        out_gate = _sym.sigmoid(gate_list[3])
+        next_c = _sym.broadcast_add(_sym.broadcast_mul(forget_gate, in_state_c),
+                                    _sym.broadcast_mul(in_gate, in_transform))
+        next_h = out_gate * _sym.tanh(next_c)
+        out_state = _sym.concatenate(*[next_c, next_h])
+        out_state = _sym.reshape(out_state,
+                                 shape=(2, batch_size, num_hidden))
+        return next_h, out_state
+    return _impl
+
+
 # compatible operators that do NOT require any conversion.
 _identity_list = []
 
@@ -493,8 +677,192 @@ def _impl(inputs, attr, params):
     'DepthwiseConv2dNative'             : _depthwise_conv(),
     'Shape'                             : _shape(),
     'Sigmoid'                           : AttrCvt('sigmoid'),
+    'Fill'                              : _fill(),
+    'GatherV2'                          : _gather_v2(),
+    'StridedSlice'                      : _stridedSlice(),
+}
+
+# _convert_map_rnn defines maps of rnn operator name to
+# converter functor(callable) for 1 to 1 mapping.
+_convert_map_rnn = {
+    'LSTMBlockCell'                     : _LSTMBlockCell(),
 }
 
+class RecurrentNetworks(object):
+    """Recurrent network layer handlers.
+
+    Handle Layer operations.
+    ToDo: Operators like RNN/GRU layer concepts also can be handled here
+
+    Parameters
+    ----------
+    nodes : list
+        list of graph nodes used for tensorflow parsing.
+
+    out_rnn : list
+        List of RecurrentNetwork outputs. This output will be appended to the
+        'head' nodes of the graph.
+
+    graph : tensorflow graph definition object
+        The loaded tensorflow GraphDef
+
+    convert_map : dict
+        Dict of name : callable, where name is the op's name that
+        require conversion to nnvm, callable are functions which
+        take attrs and return (new_op_name, new_attrs)
+    """
+    def __init__(self, nodes, out_rnn, graph, convert_map):
+        self._graph = graph
+        self._convert_map = convert_map
+        self._nodes = nodes
+        self._out_rnn = out_rnn
+        self._cur_lstm_layer = 0
+        self._layer_name_list = []
+        self._recurrent_ops_layer_map = {
+            'LSTMBlockCell'               : self._LSTMBlockCellLayer(),
+        }
+
+    def _LSTMBlockCellLayer(self):
+        """LSTMBlockCell layer handler.
+
+        Parameters
+        ----------
+        op_name : str
+            Operator name, eg:LSTMBlockCell
+
+        layer_name : str list
+            Layer name is used for creating the state input placeholder.
+
+        inputs : nnvm.Symbol
+            Input data
+
+        attrs : dict
+            Dict of operator attributes
+
+        params : dict
+            List of pretrained weights and bias
+
+        num_layers : int
+            Total number of LSTM layer presented in the graph
+
+        Returns
+        -------
+        sym : nnvm.sym.Symbol
+            The returned nnvm symbol
+        """
+        def _impl(op_name, layer_name, inputs, attrs, params, num_layers):
+            in_state_c_name = layer_name+'_c'
+            in_state_h_name = layer_name+'_h'
+
+            def _init_state(num_layers, batch_size, num_hidden):
+                """Create the initial states for the first layer in the graph."""
+                in_state_c = _sym.Variable(in_state_c_name,
+                                           shape=(num_layers, batch_size, num_hidden))
+                in_state_h = _sym.Variable(in_state_h_name,
+                                           shape=(num_layers, batch_size, num_hidden))
+                return in_state_c, in_state_h
+
+            def _get_cur_input_state(in_state_c, in_state_h, num_layers,
+                                     layer, batch_size, num_hidden):
+                """Select the appropriate states for the current layer"""
+                in_state_c_tup = _sym.split(in_state_c,
+                                            indices_or_sections=num_layers, axis=0)
+                in_state_h_tup = _sym.split(in_state_h,
+                                            indices_or_sections=num_layers, axis=0)
+                cur_in_state_c = _sym.reshape(in_state_c_tup[layer],
+                                              shape=(batch_size, num_hidden))
+                cur_in_state_h = _sym.reshape(in_state_h_tup[layer],
+                                              shape=(batch_size, num_hidden))
+                return cur_in_state_c, cur_in_state_h
+
+            def _LSTMBlockCellWrapper(inputs, attr, params,
+                                      num_layers, layer):
+                """LSTM cell warapper to prepare the inputs"""
+                input_shape = attr['_input_shapes'][inputs[0]]
+                weight_shape = attr['_input_shapes'][inputs[3]]
+                batch_size = input_shape[0][0]
+                num_hidden = weight_shape[0][1] // 4
+
+                if layer == 0:
+                    #Create initial states placeholder in case of first layer
+                    in_state_c, in_state_h = _init_state(num_layers,
+                                                         batch_size, num_hidden)
+                else:
+                    in_state_c = self._nodes[in_state_c_name]
+                    in_state_h = self._nodes[in_state_h_name]
+
+                cur_in_state_c, cur_in_state_h = _get_cur_input_state( \
+                                                    in_state_c, in_state_h,
+                                                    num_layers, layer,
+                                                    batch_size, num_hidden)
+                output, out_state = self._convert_map[op_name](inputs, cur_in_state_c,
+                                                               cur_in_state_h,
+                                                               attr, params)
+                return output, out_state, in_state_c, in_state_h
+
+            sym, cur_out_state, in_state_c, in_state_h = \
+                    _LSTMBlockCellWrapper(inputs, attrs, params,
+                                          num_layers, self._cur_lstm_layer)
+            self._nodes[in_state_c_name] = in_state_c
+            self._nodes[in_state_h_name] = in_state_h
+            cur_out_state = _sym.expand_dims(cur_out_state, axis=0, num_newaxis=1)
+            self._out_rnn.append(cur_out_state)
+            self._cur_lstm_layer += 1
+            return sym
+        return _impl
+
+    def process_op(self, op_name, inputs, attrs, params):
+        """Process recurrent layer operators.
+
+        List '_recurrent_ops_layer_map' map each Layer based operators with its
+        layer handlers. Total number of layers are calculated to form the input
+        data shapes.
+
+        Parameters
+        ----------
+        op_name : str
+            Operator name, such as LSTMBlockCell
+
+        inputs : nnvm.Symbol
+            Input data
+
+        attrs : dict
+            Dict of operator attributes
+
+        params : dict
+            List of pretrained weights and bias
+
+        Returns
+        -------
+        sym : nnvm.sym.Symbol
+            The returned nnvm symbol
+        """
+        def _get_abs_layer_name(node):
+            """Identify the layer name is already handled. Return the absolute name
+            """
+            if not self._layer_name_list:
+                self._layer_name_list.append(node.name)
+                return node.name
+
+            for _name in self._layer_name_list:
+                if _name in node.name:
+                    abs_name = _name
+                else:
+                    self._layer_name_list.append(node.name)
+                    abs_name = node.name
+            return abs_name
+
+        #Find number of layers of this same operator node in the graph
+        #and also read the inputs name for the current op.
+        num_layers = 0
+        for _, node in enumerate(self._graph.node):
+            if node.op == op_name:
+                layer_name = _get_abs_layer_name(node)
+                num_layers += 1
+
+        sym = self._recurrent_ops_layer_map[op_name](op_name, layer_name, inputs, attrs,
+                                                     params, num_layers)
+        return sym
 
 class GraphProto(object):
     """ A helper class for handling nnvm graph copying from Tensorflow GraphDef.
@@ -510,6 +878,7 @@ def __init__(self):
         self._num_input = 0
         self._num_param = 0
         self._input_node = ''
+        self._num_rnn_layer = False
 
     def from_tensorflow(self, graph):
         """Construct nnvm nodes from tensorflow  graph definition - GraphDef.
@@ -553,16 +922,19 @@ def from_tensorflow(self, graph):
         for node in graph.node:
             # Tensorflow doesn't have seperate list for params extraction.
             # Operator name 'Const' is treated as a parameter to build NNVM params dict.
+            input_shapes = {}
             if node.op == "Placeholder":
                 # Assuming only one input graph with type 'Placeholder'
                 self._input_node = node.name
                 self._num_input += 1
-                self._nodes[node.name] = _sym.Variable(name=node.name)
 
                 try:
                     self._output_shapes[node.name] = \
                          [tensor_util.TensorShapeProtoToList(shape) \
                          for shape in self._parse_attr(node.attr)['_output_shapes']]
+                    self._nodes[node.name] = _sym.Variable(name=node.name,
+                                                           shape=self._output_shapes[node.name][0])
+                    input_shapes[self._nodes[node.name]] = self._output_shapes[node.name]
                 except KeyError:
                     raise NotImplementedError( \
                         "Please freeze the graph with add_shapes=True")
@@ -580,7 +952,6 @@ def from_tensorflow(self, graph):
                     if node.name not in self._nodes:
                         raise NotImplementedError( \
                             "Const {} couldn't be converted to Param.".format(node.name))
-
                 attr = self._parse_attr(node.attr)
                 #Variable converted to Const will not have only value attr
                 if 'value' in attr:
@@ -611,9 +982,16 @@ def from_tensorflow(self, graph):
                 # Pass the node name too in attr
                 attr["_node_name"] = node.name
 
+                #ToDo: Some of the tensorflow operators maintain internaly maintain
+                #execution layers and its output name will the layer number along with
+                #graph node name.eg: Node name:- 'Model/RNN/cell_0/RnnCell', but the
+                #output name will be 'Model/RNN/cell_0/RnnCell:0'. In this case,
+                #the digit has to be ignored.
+                if ":" in node.input[0]:
+                    in_name, _ = node.input[0].split(':')
+                    node.input[0] = in_name
                 try:
                     inputs = [self._nodes[i] for i in node.input]
-                    input_shapes = {}
                     for i in node.input:
                         if i not in self._params:
                             input_shapes[self._nodes[i]] = self._output_shapes[i]
@@ -624,12 +1002,20 @@ def from_tensorflow(self, graph):
 
                 inputs = self._fix_extranodes(node.op, attr, inputs)
 
-                op = self._convert_operator(node.op, inputs, attr)
+                op = self._convert_operator(node.op, inputs, attr, graph)
                 # Assuming only one output.
                 self._nodes[node.name] = op
                 node_output = op
         # Assume the final node is the output node
         out = node_output
+
+        #Add the RNN outputs also with 'head' nodes of the nnvm graph
+        if self._num_rnn_layer:
+            out_rnn = _sym.concatenate(*self._out_rnn, axis=0)
+            out = [out, out_rnn]
+        if isinstance(out, list):
+            out = _sym.Group(out)
+
         return out, self._params
 
     def _parse_param(self, key, value, name):
@@ -651,7 +1037,7 @@ def _parse_param(self, key, value, name):
             self._nodes[name] = _sym.Variable(name=name,
                                               shape=self._params[name].shape)
         else:
-            if key != 'dtype' and key != '_output_shapes':
+            if key != 'dtype' and key != '_output_shapes' and key != '_class':
                 raise NotImplementedError \
                     ("Other attributes for a Const(param) Node {} ? .".format(key))
 
@@ -706,7 +1092,44 @@ def _parse_attr(self, attr_proto):
 
         return attrs
 
-    def _convert_operator(self, op_name, inputs, attrs, identity_list=None, convert_map=None):
+    def _convert_rnn_operator(self, op_name, inputs,
+                              attrs, params, graph, convert_map):
+        """Convert RNN and its variant operators to NNVM operators.
+        This converter read the input states of each layers and
+        also maintain the output states of each layer in a list.
+
+        Parameters
+        ----------
+        op_name : str
+            Operator name, such as LSTMBlockCell
+        inputs : list of nnvm.Symbol
+            List of input symbols.
+        attrs : dict
+            Dict of operator attributes
+        params : dict
+            List of pretrained weights and bias
+        graph : Tensorflow graph object
+            Graph is to find the number of upcoming same operator to
+            calculate the number of layers.
+        convert_map : dict
+            Dict of name : callable, where name is the op's name that
+            require conversion to nnvm, callable are functions which
+            take attrs and return (new_op_name, new_attrs)
+
+        Returns
+        -------
+        sym : nnvm.Symbol
+            Converted nnvm Symbol
+        """
+        if not self._num_rnn_layer:
+            self._out_rnn = []
+            self.rnn = RecurrentNetworks(self._nodes, self._out_rnn, graph, convert_map)
+            self._num_rnn_layer = True
+        sym = self.rnn.process_op(op_name, inputs, attrs, params)
+        return sym
+
+    def _convert_operator(self, op_name, inputs, attrs,
+                          graph, identity_list=None, convert_map=None):
         """Convert from Tensorflow operator to nnvm operator.
         The converter must specify conversions explicity for incompatible name, and
         apply handlers to operator attributes.
@@ -733,10 +1156,15 @@ def _convert_operator(self, op_name, inputs, attrs, identity_list=None, convert_
         """
         identity_list = identity_list if identity_list else _identity_list
         convert_map = convert_map if convert_map else _convert_map
+        convert_map_rnn = _convert_map_rnn
         if op_name in identity_list:
             sym = get_nnvm_op(op_name)(*inputs, **attrs)
         elif op_name in convert_map:
             sym = convert_map[op_name](inputs, attrs, self._params)
+        elif op_name in convert_map_rnn:
+            sym = self._convert_rnn_operator(op_name, inputs, attrs,
+                                             self._params, graph,
+                                             convert_map_rnn)
         else:
             raise NotImplementedError("Operator {} not implemented.".format(op_name))
         return sym
diff --git a/nnvm/python/nnvm/testing/tf.py b/nnvm/python/nnvm/testing/tf.py
index f5ca0e11d194..0372d7450586 100644
--- a/nnvm/python/nnvm/testing/tf.py
+++ b/nnvm/python/nnvm/testing/tf.py
@@ -6,6 +6,8 @@
 """
 import re
 import os.path
+import collections
+import numpy as np
 
 # Tensorflow imports
 import tensorflow as tf
@@ -134,3 +136,143 @@ def get_workload(model_path):
         graph_def.ParseFromString(f.read())
         graph = tf.import_graph_def(graph_def, name='')
         return graph_def
+
+#######################################################################
+# PTB LSTMBlockCell Model
+# -----------------------
+
+class PTBSmallConfig(object):
+    """Small config.
+    This configurations are used when training the model
+    """
+    num_layers = 2
+    num_steps = 1
+    hidden_size = 200
+    batch_size = 1
+    vocab_size = 10000
+    init_scale = 0.1
+
+def get_config():
+    """Configuration used for training the model"""
+    return PTBSmallConfig()
+
+def pick_from_weight(weight, pows=1.0):
+    """Identify token from Softmax output.
+    This token will be mapped to word in the vocabulary.
+    """
+    weight = weight**pows
+    t = np.cumsum(weight)
+    s = np.sum(weight)
+    return int(np.searchsorted(t, 0.5 * s))
+
+def do_tf_sample(session, data, in_states, num_samples):
+    """Sampled from the model"""
+    samples = []
+    sample = None
+    #Cell inputs c and h should be passed for each layer explicitly.
+    state_input_name = ['Model/MultiRNNCellZeroState/LSTMBlockCellZeroState/zeros:0',
+                        'Model/MultiRNNCellZeroState/LSTMBlockCellZeroState/zeros_1:0',
+                        'Model/MultiRNNCellZeroState/LSTMBlockCellZeroState_1/zeros:0',
+                        'Model/MultiRNNCellZeroState/LSTMBlockCellZeroState_1/zeros_1:0']
+    state = session.run(state_input_name)
+
+    #Graph nodes to be fetched as run output. Tensorflow LSTMBlockCell create internal
+    #nodes for intermediate operations (gates) in the cell during run.
+    #Cell state (c) is ':1'and cell output (h) is ':6' for each layer.
+    fetches = [['Model/RNN/RNN/multi_rnn_cell/cell_0/lstm_cell/LSTMBlockCell:1',
+                'Model/RNN/RNN/multi_rnn_cell/cell_0/lstm_cell/LSTMBlockCell:6',
+                'Model/RNN/RNN/multi_rnn_cell/cell_0/lstm_cell/LSTMBlockCell_1:1',
+                'Model/RNN/RNN/multi_rnn_cell/cell_0/lstm_cell/LSTMBlockCell_1:6'],
+               'Model/Softmax:0']
+
+    def _get_feed_dict(input_name, input_data):
+        """Create feed dict"""
+        feed_dict = {}
+        if isinstance(input_data, list):
+            for i, e in enumerate(input_name):
+                feed_dict[e] = input_data[i]
+        else:
+            feed_dict[input_name] = input_data
+        return feed_dict
+
+    for x in data:
+        feed_dict = _get_feed_dict(state_input_name, state)
+        feed_dict['Model/Placeholder:0'] = [[x]]
+        state, probs = session.run(fetches, feed_dict)
+        sample = pick_from_weight(probs[0])
+    if sample is not None:
+        samples.append(sample)
+    else:
+        samples.append(0)
+
+    k = 1
+    while k < num_samples:
+        feed_dict = _get_feed_dict(state_input_name, state)
+        feed_dict['Model/Placeholder:0'] = [[samples[-1]]]
+        state, probs = session.run(fetches, feed_dict)
+        sample = pick_from_weight(probs[0])
+        samples.append(sample)
+        k += 1
+    return samples, state
+
+def _create_ptb_vocabulary(data_dir):
+    """Read the PTB sample data input to create vocabulary"""
+    data_path = data_dir+'simple-examples/data/'
+    file_name = 'ptb.train.txt'
+    def _read_words(filename):
+        """Read the data for creating vocabulary"""
+        with tf.gfile.GFile(filename, "r") as f:
+            return f.read().encode("utf-8").decode("utf-8").replace("\n", "<eos>").split()
+
+    def _build_vocab(filename):
+        """Create vocabulary"""
+        data = _read_words(filename)
+        counter = collections.Counter(data)
+        count_pairs = sorted(counter.items(), key=lambda x: (-x[1], x[0]))
+        words, _ = list(zip(*count_pairs))
+        word_to_id = dict(zip(words, range(len(words))))
+        #for python 3.x
+        id_to_word = dict((v, k) for k, v in word_to_id.items())
+        return word_to_id, id_to_word
+
+    def ptb_raw_data(data_path, file_name):
+        """Read the sample data and create vocabulary"""
+        train_path = os.path.join(data_path, file_name)
+        word_to_id, id_2_word = _build_vocab(train_path)
+        return word_to_id, id_2_word
+    return ptb_raw_data(data_path, file_name)
+
+def get_workload_ptb():
+    """ Import ptb workload from frozen protobuf
+
+    Parameters
+    ----------
+        Nothing.
+
+    Returns
+    -------
+    graph_def: graphdef
+        graph_def is the tensorflow workload for ptb.
+
+    word_to_id : dict
+        English word to integer id mapping
+
+    id_to_word : dict
+        Integer id to English word mapping
+    """
+    sample_repo = 'http://www.fit.vutbr.cz/~imikolov/rnnlm/'
+    sample_data_file = 'simple-examples.tgz'
+    sample_url = sample_repo+sample_data_file
+    ptb_model_file = 'RNN/ptb/ptb_model_with_lstmblockcell.pb'
+
+    import tarfile
+    from tvm.contrib.download import download
+    DATA_DIR = './ptb_data/'
+    if not os.path.exists(DATA_DIR):
+        os.mkdir(DATA_DIR)
+    download(sample_url, DATA_DIR+sample_data_file)
+    t = tarfile.open(DATA_DIR+sample_data_file, 'r')
+    t.extractall(DATA_DIR)
+
+    word_to_id, id_to_word = _create_ptb_vocabulary(DATA_DIR)
+    return word_to_id, id_to_word, get_workload(ptb_model_file)
diff --git a/nnvm/tests/python/frontend/tensorflow/test_forward.py b/nnvm/tests/python/frontend/tensorflow/test_forward.py
index 3275d6215309..045d154d9d8b 100644
--- a/nnvm/tests/python/frontend/tensorflow/test_forward.py
+++ b/nnvm/tests/python/frontend/tensorflow/test_forward.py
@@ -10,12 +10,14 @@
 import tvm
 import tensorflow as tf
 from tensorflow.python.framework import constant_op
+from tensorflow.python.framework import graph_util
 from tensorflow.python.ops import nn_ops
 from tensorflow.python.ops import array_ops
 from tensorflow.python.ops import gen_array_ops
 from tensorflow.python.ops import math_ops
 from tensorflow.python.ops import variable_scope
 from tensorflow.python.ops import variables
+from tensorflow.python.ops import init_ops
 from tensorflow.core.framework import graph_pb2
 
 import nnvm.testing.tf
@@ -55,8 +57,15 @@ def run_tvm_graph(graph_def, input_data, input_node, output_shape, output_dtype)
     # execute
     m.run()
     # get outputs
-    tvm_output = m.get_output(0, tvm.nd.empty((output_shape), output_dtype))
-    return tvm_output.asnumpy()
+    if isinstance(output_shape, list) and isinstance(output_dtype, list):
+        tvm_output_list = []
+        for i, s in enumerate(output_shape):
+            tvm_output = m.get_output(i, tvm.nd.empty((s), output_dtype[i]))
+            tvm_output_list.append(tvm_output.asnumpy())
+        return tvm_output_list
+    else:
+        tvm_output = m.get_output(0, tvm.nd.empty((output_shape), output_dtype))
+        return tvm_output.asnumpy()
 
 def run_tf_graph(sess, input_data, input_node, output_node):
     """ Generic function to execute tensorflow """
@@ -433,6 +442,159 @@ def test_forward_variable():
     _test_variable(np.random.uniform(size=(32, 100)).astype('float32'))
 
 
+#######################################################################
+# LSTM
+# ----
+def _test_lstm_cell(batch_size, num_hidden, num_layers, forget_bias, dtype):
+    tf.reset_default_graph()
+    input_size = num_hidden
+    input_data = np.full((batch_size, input_size), 1., dtype=dtype)
+    in_state_c = np.full((num_layers, batch_size, num_hidden), 0.1, dtype=dtype)
+    in_state_h = np.full((num_layers, batch_size, num_hidden), 0.1, dtype=dtype)
+
+    def _get_tensorflow_output():
+        with tf.Session() as sess:
+            with variable_scope.variable_scope(
+                "root", initializer=init_ops.constant_initializer(0.5)):
+                m0 = array_ops.zeros([batch_size, num_hidden])
+                m1 = array_ops.zeros([batch_size, num_hidden])
+                x=tf.placeholder(shape=(batch_size, input_size), dtype=dtype)
+                g, ((out_m0, out_m1)) = \
+                     tf.contrib.rnn.LSTMBlockCell(num_hidden,
+                                                  forget_bias=forget_bias)(x, ((m0, m1)))
+                sess.run([variables.global_variables_initializer()])
+                res = sess.run([g, out_m0, out_m1], {
+                    x.name: np.array([[1., 1.]]),
+                    m0.name: 0.1 * np.ones([batch_size, num_hidden]),
+                    m1.name: 0.1 * np.ones([batch_size, num_hidden]),
+                })
+            graph_def = sess.graph.as_graph_def(add_shapes=True)
+            final_graph_def = graph_util.convert_variables_to_constants(
+                sess,
+                graph_def,
+                ['root/lstm_cell/LSTMBlockCell'])
+            return final_graph_def, res
+
+    graph_def, tf_out = _get_tensorflow_output()
+    tvm_output = run_tvm_graph(graph_def, [input_data, in_state_c, in_state_h],
+                               ['root/Placeholder', 'root/lstm_cell/LSTMBlockCell_c',
+                                'root/lstm_cell/LSTMBlockCell_h'],
+                               [tf_out[0].shape, (2, batch_size, num_hidden)],
+                               [tf_out[0].dtype, tf_out[1].dtype])
+
+    if isinstance(tvm_output, list):
+        out = tvm_output[0]
+        out_state = tvm_output[1]
+        out_state_tup = np.split(out_state, indices_or_sections=2, axis=0)
+        out_state_c = np.reshape(out_state_tup[0], (batch_size, num_hidden))
+        out_state_h = np.reshape(out_state_tup[1], (batch_size, num_hidden))
+        tvm_out = [out, out_state_c, out_state_h]
+        np.testing.assert_allclose(tf_out, tvm_out, rtol=1e-3, atol=1e-3)
+
+def test_forward_lstm():
+    '''test LSTM block cell'''
+    _test_lstm_cell(1, 2, 1, 0.0, 'float32')
+
+
+#######################################################################
+# StridedSlice
+# ------------
+
+def _test_stridedslice(ip_shape, begin, end, stride, dtype,
+                             begin_mask=0, end_mask=0, new_axis_mask=0,
+                             shrink_axis_mask=0, ellipsis_mask=0):
+    tf.reset_default_graph()
+    in_data = tf.placeholder(dtype, ip_shape, name="in_data")
+    tf.strided_slice(in_data, begin, end, stride, begin_mask=begin_mask,
+                         end_mask=end_mask, new_axis_mask=new_axis_mask,
+                         shrink_axis_mask=shrink_axis_mask,
+                         ellipsis_mask=ellipsis_mask, name="strided_slice")
+    np_data = np.random.uniform(size=ip_shape).astype(dtype)
+
+    with tf.Session() as sess:
+        final_graph_def = tf.graph_util.convert_variables_to_constants(
+            sess,
+            sess.graph.as_graph_def(add_shapes=True),
+            ['strided_slice'])
+        tf_output = run_tf_graph(sess, np_data,
+                                 'in_data:0', 'strided_slice:0')
+        tvm_output = run_tvm_graph(final_graph_def, np_data,
+                                   "in_data", tf_output.shape, np_data.dtype)
+        np.testing.assert_allclose(tf_output, tvm_output, atol=1e-5, rtol=1e-5)
+        sess.close()
+
+def test_forward_stridedslice():
+    '''test StridedSlice'''
+    _test_stridedslice((3, 4, 3), [1, -1, 0], [4, -5, 3], [2, -1, 1], 'float32')
+    _test_stridedslice((3, 4, 3), [1, 0], [4, 3], [2, 1], 'float32', ellipsis_mask=8)
+    _test_stridedslice((3, 4, 3), [1, 1, 0], [4, 4, 2], [2, 1, 1], 'float32', new_axis_mask=5)
+    _test_stridedslice((3, 4, 3), [1, 1, 1], [4, 4, 1], [2, 1, 1], 'float32', ellipsis_mask=2, new_axis_mask=4)
+    _test_stridedslice((3, 4, 3), [1, 1, 2], [4, 4, 3], [2, 1, 1], 'float32', ellipsis_mask=4, new_axis_mask=2)
+    _test_stridedslice((3, 4, 3), [1, 1, 2], [4, 4, 3], [2, 1, 1], 'float32', ellipsis_mask=2, new_axis_mask=3)
+    _test_stridedslice((3, 4, 3), [1, 1, 0], [4, 4, 1], [2, 1, 1], 'float32', ellipsis_mask=2, new_axis_mask=3)
+    _test_stridedslice((3, 4, 3), [1, 1, 2], [4, 4, 3], [2, 1, 1], 'float32', ellipsis_mask=2, new_axis_mask=2)
+    _test_stridedslice((3,4), [1, 0], [4, 4], [1, 1], 'float32', shrink_axis_mask=2)
+    _test_stridedslice((3, 4, 3), [1, 1, 0], [4, 4, 3], [2, 1, 1], 'float32', shrink_axis_mask=2, new_axis_mask=2)
+    _test_stridedslice((3, 4, 3), [1, 1, 0], [4, 4, 3], [2, 1, 1], 'float32', shrink_axis_mask=1, new_axis_mask=2)
+    _test_stridedslice((3, 4, 3), [1, 1, 0], [4, 4, 3], [2, 1, 1], 'float32', shrink_axis_mask=2, new_axis_mask=1)
+    _test_stridedslice((3, 4, 5, 4, 5, 6), [0, 0], [2, 3], [1, 1], 'float32', shrink_axis_mask=5, new_axis_mask=1)
+    _test_stridedslice((3, 4, 5, 4, 5, 6), [0, 0, 1, 2, 1], [2, 3, 4, 5, 3], [1, 1, 2, 2, 1],
+                       'float32', shrink_axis_mask=5, new_axis_mask=1, ellipsis_mask=2, begin_mask=8, end_mask=8)
+    _test_stridedslice((3, 4, 5, 4, 5, 6), [0, 0, 1, 2, 1], [2, 3, 4, 5, 3], [1, 1, 2, 2, 1],
+                       'float32', shrink_axis_mask=8, new_axis_mask=1, ellipsis_mask=2, begin_mask=5, end_mask=5)
+    _test_stridedslice((3, 4, 5, 4, 5, 6), [0, 0, 1, 2, 1], [2, 3, 4, 5, 3], [1, 1, 2, 2, 1],
+                       'float32', shrink_axis_mask=16, new_axis_mask=1, ellipsis_mask=2, begin_mask=5, end_mask=5)
+    _test_stridedslice((3, 4, 5, 4, 5, 6), [1, 2, 0, -3], [4, 5, 3, 3], [2, 2, 1, 1],
+                       'float32', shrink_axis_mask=8, new_axis_mask=1, ellipsis_mask=2, begin_mask=5,
+                       end_mask=8)
+
+
+#######################################################################
+# Gather
+# ------
+
+def _test_gather(ip_shape, indice_shape, indice_value, axis, dtype):
+    tf.reset_default_graph()
+    in_data = tf.placeholder(dtype, ip_shape, name="in_data")
+    indices = tf.placeholder("int32", indice_shape, name="indices")
+    tf.gather(in_data, indices, axis=axis)
+    np_data = np.random.uniform(size=ip_shape).astype(dtype)
+
+    def _fill_indices(indice_value):
+        indices = np.array(ip_shape, dtype=dtype)
+        if isinstance(indice_value, int):
+            indices = np.array([indice_value], dtype='int32')
+        else:
+            indices = np.asarray(indice_value, dtype='int32')
+        return indices
+    np_indices = _fill_indices(indice_value)
+
+    with tf.Session() as sess:
+        final_graph_def = tf.graph_util.convert_variables_to_constants(
+            sess,
+            sess.graph.as_graph_def(add_shapes=True),
+            ['GatherV2'])
+        tf_output = run_tf_graph(sess, [np_data, np_indices], ['in_data:0',
+                                 'indices:0'], 'GatherV2:0')
+        tvm_output = run_tvm_graph(final_graph_def, [np_data, np_indices],
+                                   ['in_data', 'indices'], tf_output.shape, dtype)
+        np.testing.assert_allclose(tf_output, tvm_output, atol=1e-5, rtol=1e-5)
+        sess.close()
+
+def test_forward_gather():
+    '''test gather layer'''
+    _test_gather((4,), (1,), 1, 0, 'int32')
+    _test_gather((4,), (1,), 1, 0, 'float32')
+    _test_gather((1,4), (1,), [0], 0, 'int32')
+    _test_gather((4,), (1,2,2), [[[1,0],[0,1]]], 0, 'float32')
+    _test_gather((2,2), (1,2,2), [[[1,0],[0,1]]], 0, 'int32')
+    _test_gather((2,2), (1,2,2), [[[1,0],[0,1]]], 1, 'int32')
+    _test_gather((2,2), (1,2,2), [[[1,0],[0,1]]], 0, 'float32')
+    _test_gather((3,3,3), (1,1,2), [[[1,0]]], 0, 'int32')
+    _test_gather((3,3,3), (1,1,2), [[[1,0]]], 2, 'int32')
+    _test_gather((4,3,5,6), (1,4), [[2,1,0,0]], 0, 'float32')
+
+
 #######################################################################
 # Multi Input to graph
 # --------------------
@@ -583,6 +745,115 @@ def test_forward_mobilenet():
             tvm_output = run_tvm_graph(graph_def, data, 'input', tf_output.shape, 'float32')
             np.testing.assert_allclose(np.squeeze(tvm_output), np.squeeze(tf_output), rtol=1e-5, atol=1e-5)
 
+#######################################################################
+# PTB
+# ---
+dir(tf.contrib)
+def test_forward_ptb():
+    '''test ptb model'''
+    config = nnvm.testing.tf.get_config()
+    num_steps = config.num_steps
+    num_hidden = config.hidden_size
+    num_layers = config.num_layers
+    batch_size = config.batch_size
+    vocab_size = config.vocab_size
+    out_sample_shape = (batch_size, vocab_size)
+    out_state_shape = (num_layers, 2, batch_size, num_hidden)
+    #Sample input
+    inpt = "we have no useful information on"
+    cnt_sample = 20
+
+    def _pretty_print(items, is_char_model, id2word):
+        if not is_char_model:
+            return ' '.join([id2word[x] for x in items])
+        else:
+            return ''.join([id2word[x] for x in items]).replace('_', ' ')
+
+    def _get_tvm_graph_module(graph_def):
+        sym, params = nnvm.frontend.from_tensorflow(graph_def)
+
+        #Cell inputs 'c and 'h' consist of all layers values
+        shape_dict = {'Model/Placeholder': (batch_size, num_steps),
+                      'Model/RNN/RNN/multi_rnn_cell/cell_0/lstm_cell/LSTMBlockCell_c':(num_layers, batch_size, num_hidden),
+                      'Model/RNN/RNN/multi_rnn_cell/cell_0/lstm_cell/LSTMBlockCell_h':(num_layers, batch_size, num_hidden)}
+        dtype_dict = {'Model/Placeholder': 'int32',
+                      'Model/RNN/RNN/multi_rnn_cell/cell_0/lstm_cell/LSTMBlockCell_c':'float32',
+                      'Model/RNN/RNN/multi_rnn_cell/cell_0/lstm_cell/LSTMBlockCell_h':'float32'}
+        target = 'llvm'
+        graph, lib, params = nnvm.compiler.build(sym, target, shape_dict,
+                                                 dtype=dtype_dict, params=params)
+        from tvm.contrib import graph_runtime
+        ctx = tvm.cpu(0)
+        return params, graph_runtime.create(graph, lib, ctx)
+
+    def _do_tvm_sample(model, data, in_states, params, num_samples):
+        """Sampled from the model"""
+        samples = []
+        state = in_states
+        sample = None
+        def _get_sample(data, state):
+            input_data = np.full((batch_size, num_steps), data, dtype="int32")
+            in_state_tup = np.split(state, indices_or_sections=2, axis=1)
+            in_state_c = np.reshape(in_state_tup[0], (num_layers, batch_size, num_hidden))
+            in_state_h = np.reshape(in_state_tup[1], (num_layers, batch_size, num_hidden))
+
+            model.set_input('Model/Placeholder', tvm.nd.array(input_data.astype("int32")))
+            model.set_input('Model/RNN/RNN/multi_rnn_cell/cell_0/lstm_cell/LSTMBlockCell_c',
+                        tvm.nd.array(in_state_c.astype("float32")))
+            model.set_input('Model/RNN/RNN/multi_rnn_cell/cell_0/lstm_cell/LSTMBlockCell_h',
+                        tvm.nd.array(in_state_h.astype("float32")))
+            model.set_input(**params)
+            model.run()
+            tvm_output = model.get_output(0, tvm.nd.empty(out_sample_shape,
+                                                      "float32")).asnumpy()
+            state_output = model.get_output(1, tvm.nd.empty(out_state_shape,
+                                                        "float32")).asnumpy()
+            sample = nnvm.testing.tf.pick_from_weight(tvm_output[0])
+            return sample, state_output
+
+        for x in data:
+            sample, state = _get_sample(x, state)
+
+        if sample is not None:
+            samples.append(sample)
+        else:
+            samples.append(0)
+
+        k = 1
+        while k < num_samples:
+            sample, state = _get_sample(samples[-1], state)
+            samples.append(sample)
+            k += 1
+        return samples, state
+
+    with tf.Graph().as_default():
+        word_to_id, id_to_word, graph_def = nnvm.testing.tf.get_workload_ptb()
+        vocab_size = len(word_to_id)
+        # Call the utility to import the graph definition into default graph.
+        graph_def = nnvm.testing.tf.ProcessGraphDefParam(graph_def)
+        sess = tf.Session()
+
+    #TVM graph module creation
+    params, m = _get_tvm_graph_module(graph_def)
+
+    # Create 10 predicted statments of 20 words
+    cnt_stm = 0
+    while cnt_stm < 10:
+        cnt_stm += 1
+        in_state = np.full((num_layers, 2, batch_size, num_hidden), 0, dtype="float32")
+        seed_for_sample = inpt.split()
+        tvm_samples, tvm_state = _do_tvm_sample(m, [word_to_id[word] \
+                                                    for word in seed_for_sample],
+                                                in_state, params, cnt_sample)
+        tvm_sample_str = _pretty_print(tvm_samples, False, id_to_word)
+        tf_samples, tf_state = nnvm.testing.tf.do_tf_sample(sess,
+                                [word_to_id[word] for word in seed_for_sample],
+                                in_state, cnt_sample)
+        tf_sample_str = _pretty_print(tf_samples, False, id_to_word)
+        inpt = tvm_sample_str
+        np.testing.assert_allclose(tf_samples, tvm_samples, rtol=1e-5, atol=1e-5)
+        assert(tvm_sample_str == tf_sample_str)
+
 #######################################################################
 # Main
 # ----
@@ -600,3 +871,7 @@ def test_forward_mobilenet():
     test_forward_mobilenet()
     test_forward_variable()
     test_forward_resize_bilinear()
+    test_forward_lstm()
+    test_forward_stridedslice()
+    test_forward_gather()
+    test_forward_ptb()

From ca85bf64e240dffd69ad0ed32a35049ff335b3dc Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@cs.washington.edu>
Date: Wed, 25 Jul 2018 12:07:37 -0700
Subject: [PATCH 913/949] timing closure fix for default VTA config (#1489)

---
 vta/config/vta_config.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/vta/config/vta_config.json b/vta/config/vta_config.json
index 73a00e33d488..602af0126816 100644
--- a/vta/config/vta_config.json
+++ b/vta/config/vta_config.json
@@ -1,7 +1,7 @@
 {
   "TARGET" : "sim",
   "HW_FREQ" : 100,
-  "HW_CLK_TARGET" : 8,
+  "HW_CLK_TARGET" : 7,
   "HW_VER" : "0.0.0",
   "LOG_INP_WIDTH" : 3,
   "LOG_WGT_WIDTH" : 3,

From fc28cfa791a01234dde70bd12b71c882dfff9e6a Mon Sep 17 00:00:00 2001
From: Sergey Mironov <grrwlf@gmail.com>
Date: Wed, 25 Jul 2018 22:08:39 +0300
Subject: [PATCH 914/949] [NNVM] Add argmax and argmin operations from topi
 (#1462)

---
 nnvm/python/nnvm/top/reduction.py             |  8 +++
 nnvm/src/top/tensor/reduce.cc                 | 57 +++++++++++++++++++
 nnvm/tests/python/compiler/test_top_level4.py | 56 +++++++++++++++---
 3 files changed, 112 insertions(+), 9 deletions(-)

diff --git a/nnvm/python/nnvm/top/reduction.py b/nnvm/python/nnvm/top/reduction.py
index 61973a752cb7..fd8e2f8df56e 100644
--- a/nnvm/python/nnvm/top/reduction.py
+++ b/nnvm/python/nnvm/top/reduction.py
@@ -41,3 +41,11 @@ def _compute(attrs, inputs, out_info):
 # collapse sum
 reg.register_pattern("collapse_sum", OpPattern.COMM_REDUCE)
 reg.register_schedule("collapse_sum", _fschedule_reduce)
+
+# argmax
+reg.register_pattern("argmax", OpPattern.COMM_REDUCE)
+reg.register_schedule("argmax", _fschedule_reduce)
+
+# argmin
+reg.register_pattern("argmin", OpPattern.COMM_REDUCE)
+reg.register_schedule("argmin", _fschedule_reduce)
diff --git a/nnvm/src/top/tensor/reduce.cc b/nnvm/src/top/tensor/reduce.cc
index cb6848e1be7f..3f948720a293 100644
--- a/nnvm/src/top/tensor/reduce.cc
+++ b/nnvm/src/top/tensor/reduce.cc
@@ -262,5 +262,62 @@ NNVM_REGISTER_BASE_REDUCE_OP(collapse_sum)
     return Array<Tensor>{ topi::collapse_sum(inputs[0], inputs[1]->shape) };
 });
 
+template<int Type>
+inline bool InferFixedType(const NodeAttrs& attrs,
+                          std::vector<int>* in_attrs,
+                          std::vector<int>* out_attrs) {
+  // Static type inference for argmax operation. Argmax return indices which
+  // should have Int32 type as shapes do.
+  CHECK_EQ(in_attrs->size(), 1U);
+  CHECK_EQ(out_attrs->size(), 1U);
+  NNVM_ASSIGN_OUTPUT_TYPE(attrs, *out_attrs, 0, static_cast<int>(Type));
+  return true;
+}
+
+NNVM_REGISTER_BASE_REDUCE_OP(argmax)
+.describe(R"code(Creates an operation that finds the indices of the maximum
+values over a given axis.
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "Tensor", "The input")
+.set_attr<FInferShape>("FInferShape", ReduceShape)
+.set_attr<FInferType>("FInferType", InferFixedType<kInt32>)
+.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
+.set_num_inputs(1)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const ReduceParam& param = nnvm::get<ReduceParam>(attrs.parsed);
+    TShape r_axes = GetReduceAxes(inputs[0]->shape.size(),
+                                  param.axis, param.exclude);
+    auto axis = ShapeToArray(r_axes);
+    return Array<Tensor>{
+      topi::argmax(inputs[0], axis, param.keepdims) };
+});
+
+NNVM_REGISTER_BASE_REDUCE_OP(argmin)
+.describe(R"code(Creates an operation that finds the indices of the minimum
+values over a given axis.
+
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "Tensor", "The input")
+.set_attr<FInferShape>("FInferShape", ReduceShape)
+.set_attr<FInferType>("FInferType", InferFixedType<kInt32>)
+.set_attr<FCorrectLayout>("FCorrectLayout", ElemwiseFixedLayoutUnknownOut<1, 1>)
+.set_num_inputs(1)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+    const ReduceParam& param = nnvm::get<ReduceParam>(attrs.parsed);
+    TShape r_axes = GetReduceAxes(inputs[0]->shape.size(),
+                                  param.axis, param.exclude);
+    auto axis = ShapeToArray(r_axes);
+    return Array<Tensor>{
+      topi::argmin(inputs[0], axis, param.keepdims) };
+});
+
+
 }  // namespace top
 }  // namespace nnvm
diff --git a/nnvm/tests/python/compiler/test_top_level4.py b/nnvm/tests/python/compiler/test_top_level4.py
index 236ac8e82578..2d0c8aeb7dfa 100644
--- a/nnvm/tests/python/compiler/test_top_level4.py
+++ b/nnvm/tests/python/compiler/test_top_level4.py
@@ -71,21 +71,27 @@ def verify_transpose(dshape, axes):
         out = m.get_output(0, tvm.nd.empty(out_np.shape))
         np.testing.assert_allclose(out.asnumpy(), out_np, atol=1e-5, rtol=1e-5)
 
-
-def verify_reduce(dshape, fnp, fsym, **kwargs):
+def verify_reduce_explicit(dshape, data, result, fsym, oshape=None, otype='float32', **kwargs):
+    """ Verify reduce operations by comparign its result with `result` """
     x = sym.Variable("x")
-    y = fsym(x + 1, **kwargs)
-    dtype = "float32"
+    y = fsym(x + 0, **kwargs)
     for target, ctx in ctx_list():
         graph, lib, _ = nnvm.compiler.build(y, target, {"x": dshape})
         m = graph_runtime.create(graph, lib, ctx)
         # set input
-        data = np.random.uniform(size=dshape).astype(dtype)
-        out_np = fnp(data + 1, **kwargs)
         m.run(x=data)
-        out = m.get_output(0, tvm.nd.empty(out_np.shape))
-        np.testing.assert_allclose(out.asnumpy(), out_np, atol=1e-5, rtol=1e-5)
-
+        # oshape set to None means do not test the shape-correctness
+        oshape = result.shape if oshape is None else oshape
+        out = m.get_output(0, tvm.nd.empty(oshape, dtype=otype))
+        np.testing.assert_equal(out.asnumpy().shape, result.shape)
+        np.testing.assert_allclose(out.asnumpy(), result, atol=1e-5, rtol=1e-5)
+
+def verify_reduce(dshape, fnp, fsym, oshape=None, otype='float32', **kwargs):
+    """ Verify reduce operations by generating data at random and calling numpy
+    version as reference """
+    data = np.random.uniform(size=dshape).astype(otype)
+    result = fnp(data + 0, **kwargs)
+    verify_reduce_explicit(dshape, data, result, fsym, oshape=oshape, otype=otype, **kwargs)
 
 def verify_collapse(dshape, target_shape, fnp):
     x = sym.Variable("x", shape=dshape)
@@ -109,11 +115,43 @@ def test_transpose():
 
 
 def test_reduce():
+
+    def _with_keepdims(func):
+        """ Wrapper around numpy's argmax/argmin with `keepdims` argument supported """
+        def wrapper(data, axis=None, keepdims=False):
+            if not keepdims:
+                return func(data, axis=axis)
+            else:
+                if axis is not None:
+                    out_shape = list(data.shape)
+                    out_shape[axis] = 1
+                else:
+                    out_shape = [1 for _ in range(len(data.shape))]
+                return func(data, axis=axis).reshape(out_shape)
+        return wrapper
+
     verify_reduce((2, 3, 4), np.max, sym.max, axis=1, keepdims=True)
     verify_reduce((4, 4, 3), np.min, sym.min, keepdims=True)
     verify_reduce((4, 4, 3), np.sum, sym.sum, axis=(0, 2))
     verify_reduce((4, 4, 3), np.sum, sym.sum)
 
+    data = np.array([[[1,2],[3,4]],[[3,44],[5,6]]], dtype=np.float32)
+    verify_reduce_explicit([2,2,2], data, np.array([[1,1],[1,0]]), sym.argmax, otype='int32', axis=[0,2], exclude=True)
+    verify_reduce_explicit([2,2,2], data, np.array([[0,0],[0,1]]), sym.argmin, otype='int32', axis=[0,2], exclude=True)
+    shape = [4, 4, 3]
+    for axis in [None, 0, 1, 2]:
+        for keepdims in [True,False]:
+            kwargs = { 'keepdims':keepdims }
+            if axis is None:
+                # FIXME: NNVM doesn't support setting `axis=None` explicitly.
+                kwargs.update({'oshape': [1,1,1] if keepdims else [] })
+            else:
+                kwargs.update({'axis': axis})
+                kwargs.update({'oshape': shape[:axis]+[1]+shape[axis+1:] if keepdims else shape[:axis]+shape[axis+1:]})
+
+            verify_reduce(shape, _with_keepdims(np.argmax), sym.argmax, otype='int32', **kwargs)
+            verify_reduce(shape, _with_keepdims(np.argmin), sym.argmin, otype='int32', **kwargs)
+
 
 def test_collapse():
     verify_collapse((2, 3, 4), (1,), lambda x: x.sum())

From 115c6267945e6c611e5fd10ee27928b26750d1cf Mon Sep 17 00:00:00 2001
From: Josh Pollock <joshpollock1997@gmail.com>
Date: Wed, 25 Jul 2018 19:51:41 -0700
Subject: [PATCH 915/949] add orderedset to Ubuntu python package list (#1491)

---
 docker/install/ubuntu_install_python_package.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docker/install/ubuntu_install_python_package.sh b/docker/install/ubuntu_install_python_package.sh
index b7fae21ab567..3e5c88674079 100644
--- a/docker/install/ubuntu_install_python_package.sh
+++ b/docker/install/ubuntu_install_python_package.sh
@@ -1,3 +1,3 @@
 # install libraries for python package on ubuntu
 pip2 install nose pylint numpy nose-timer cython decorator scipy tornado
-pip3 install nose pylint numpy nose-timer cython decorator scipy tornado typed_ast pytest mypy
+pip3 install nose pylint numpy nose-timer cython decorator scipy tornado typed_ast pytest mypy orderedset

From 7bc96e07cb7e515a66c8b34eefa97ccdece7421b Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Wed, 25 Jul 2018 19:53:42 -0700
Subject: [PATCH 916/949] Fix more type annotation (#1490)

---
 src/pass/arg_binder.cc | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/pass/arg_binder.cc b/src/pass/arg_binder.cc
index 8139a5644bb6..390c918d9692 100644
--- a/src/pass/arg_binder.cc
+++ b/src/pass/arg_binder.cc
@@ -168,7 +168,7 @@ void ArgBinder::BindDLTensor(const Buffer& buffer,
   if (Bind_(buffer->data, TVMArrayGet(Handle(), handle, intrinsic::kArrData),
             arg_name + ".data", true)) {
     Var vptr(buffer->data);
-    def_handle_dtype_.Set(vptr, make_const(buffer->dtype, 0));
+    def_handle_dtype_.Set(vptr, ir::TypeAnnotation(buffer->dtype));
     // mark alignment of external bufs
     init_nest_.emplace_back(AttrStmt::make(
         vptr, ir::attr::storage_alignment,
@@ -190,7 +190,7 @@ void ArgBinder::BindDLTensor(const Buffer& buffer,
   }
   // strides field
   Var v_strides(arg_name + ".strides", Handle());
-  def_handle_dtype_.Set(v_strides, make_const(tvm_shape_type, 0));
+  def_handle_dtype_.Set(v_strides, ir::TypeAnnotation(tvm_shape_type));
   init_nest_.emplace_back(LetStmt::make(
       v_strides, TVMArrayGet(Handle(), handle, intrinsic::kArrStrides),
       nop));

From d49034e87d4e59c34834877625706bdf9fc6e27c Mon Sep 17 00:00:00 2001
From: Leyuan Wang <laurawly@gmail.com>
Date: Thu, 26 Jul 2018 09:08:15 -0700
Subject: [PATCH 917/949] Fixed bugs for conv2d (#1465)

---
 topi/python/topi/cuda/conv2d_nchw.py       | 48 +++++++++++++---------
 topi/python/topi/intel_graphics/conv2d.py  |  7 ++--
 topi/tests/python/test_topi_conv2d_nchw.py |  2 +-
 3 files changed, 34 insertions(+), 23 deletions(-)

diff --git a/topi/python/topi/cuda/conv2d_nchw.py b/topi/python/topi/cuda/conv2d_nchw.py
index 71104c59a9aa..844d4a04758c 100644
--- a/topi/python/topi/cuda/conv2d_nchw.py
+++ b/topi/python/topi/cuda/conv2d_nchw.py
@@ -13,23 +13,29 @@ def conv2d_224_3_64(s, temp, temp_R, temp_S, Filter_S, Out, Out_L, flag):
     hfactor = 2
     if flag >= 96:
         hfactor = 4
+    max_threads = int(tvm.target.current_target(allow_none=False).max_num_threads)
     ow_size = util.get_const_int(Out.shape[3])
-    num_thread = ow_size * hfactor
+    num_thread = min(max_threads, ow_size * hfactor)
     vthread = ofactor
     block_x = tvm.thread_axis("blockIdx.x")
     thread_x = tvm.thread_axis((0, num_thread), "threadIdx.x")
     thread_xz = tvm.thread_axis((0, vthread), "vthread", name="vx")
 
     i, oc, h, w = s[Out].op.axis
-    ooc, ioc = s[Out].split(oc, factor=vthread)
-    oh, ih = s[Out].split(h, factor=hfactor)
-    s[Out].reorder(ooc, oh, ioc, ih, w)
-    oc = s[Out].fuse(ooc, oh)
-    ow, _ = s[Out].split(w, nparts=ow_size)
-    w = s[Out].fuse(ow, ih)
-    s[Out].bind(w, thread_x)
-    s[Out].bind(ioc, thread_xz)
-    s[Out].bind(oc, block_x)
+    if ow_size * hfactor == num_thread:
+        ooc, ioc = s[Out].split(oc, factor=vthread)
+        oh, ih = s[Out].split(h, factor=hfactor)
+        s[Out].reorder(ooc, oh, ioc, ih, w)
+        oc = s[Out].fuse(ooc, oh)
+        ow, _ = s[Out].split(w, nparts=ow_size)
+        w = s[Out].fuse(ow, ih)
+        s[Out].bind(w, thread_x)
+        s[Out].bind(ioc, thread_xz)
+        s[Out].bind(oc, block_x)
+    else:
+        ow, w = s[Out].split(w, factor=num_thread)
+        s[Out].bind(w, thread_x)
+        s[Out].bind(ow, block_x)
 
     s[Out_L].compute_at(s[Out], w)
 
@@ -40,7 +46,7 @@ def conv2d_224_3_64(s, temp, temp_R, temp_S, Filter_S, Out, Out_L, flag):
     s[temp_S].compute_at(s[Out_L], ic)
     s[Filter_S].compute_at(s[Out_L], w)
 
-    num_thread1 = tvm.target.current_target(allow_none=False).max_num_threads
+    num_thread1 = max_threads
     thread_xx = tvm.thread_axis((0, num_thread1), "threadIdx.x")
     block_xx = tvm.thread_axis("blockIdx.x")
 
@@ -59,7 +65,8 @@ def conv2d_224_3_64(s, temp, temp_R, temp_S, Filter_S, Out, Out_L, flag):
     h = s[temp_S].fuse(h, ow)
     _, tx = s[temp_S].split(h, factor=num_thread)
     s[temp_S].bind(tx, thread_x)
-    s[temp_S].vectorize(iw)
+    if num_thread < max_threads:
+        s[temp_S].vectorize(iw)
 
     #schedule Filter_S shared mem load
     i, oc, h, w = s[Filter_S].op.axis
@@ -250,12 +257,13 @@ def conv2d_56_64_128(s, temp, temp_R, temp_S, Filter_S, Out, Out_L, flag):
 
 def conv2d_14_256_256(s, temp, temp_R, temp_S, Filter, Filter_S, Out, Out_L):
     """Schedule conv2d for specific feature_in_out_filter pattern"""
+    max_threads = int(tvm.target.current_target(allow_none=False).max_num_threads)
     if util.get_const_int(Filter.shape[0]) + util.get_const_int(Filter.shape[1]) <= 768:
         # scheduler params
         vthread_x = util.get_const_int(Out.shape[3])
         num_thread_x = 64
         ofactor = 8
-        if util.get_const_int(Filter.shape[3]) == 1:
+        if util.get_const_int(Filter.shape[3]) == 1 and vthread_x * 5 <= max_threads:
             ofactor = 64
         block_x = tvm.thread_axis("blockIdx.x")
         thread_x = tvm.thread_axis((0, num_thread_x), "threadIdx.x")
@@ -295,9 +303,9 @@ def conv2d_14_256_256(s, temp, temp_R, temp_S, Filter, Filter_S, Out, Out_L):
 
     else:
         # scheduler params
-        vthread_x = util.get_const_int(Out.shape[2])
+        vthread_x = min(8, util.get_const_int(Out.shape[2]))
         num_thread_x = 16
-        num_thread_y = util.get_const_int(Out.shape[3])
+        num_thread_y = min(max_threads // num_thread_x, util.get_const_int(Out.shape[3]))
         ofactor = 8
         block_x = tvm.thread_axis("blockIdx.x")
         thread_x = tvm.thread_axis((0, num_thread_x), "threadIdx.x")
@@ -305,11 +313,13 @@ def conv2d_14_256_256(s, temp, temp_R, temp_S, Filter, Filter_S, Out, Out_L):
         thread_xz = tvm.thread_axis((0, vthread_x), "vthread", name="vx")
 
         i, oc, h, w = s[Out].op.axis
+        ow, iw = s[Out].split(w, factor=num_thread_y)
+        oh, ih = s[Out].split(h, factor=vthread_x)
         ooc, ioc = s[Out].split(oc, factor=num_thread_x)
-        s[Out].reorder(i, ooc, h, w, ioc)
+        s[Out].reorder(i, ooc, oh, ih, ow, iw, ioc)
         s[Out].bind(ioc, thread_x)
-        s[Out].bind(w, thread_y)
-        s[Out].bind(h, thread_xz)
+        s[Out].bind(iw, thread_y)
+        s[Out].bind(ih, thread_xz)
         s[Out].bind(ooc, block_x)
 
         s[Out_L].compute_at(s[Out], ioc)
@@ -323,7 +333,7 @@ def conv2d_14_256_256(s, temp, temp_R, temp_S, Filter, Filter_S, Out, Out_L):
         s[temp_S].compute_at(s[Out_L], oic)
         s[Filter_S].compute_at(s[Out_L], oic)
 
-        num_thread = tvm.target.current_target(allow_none=False).max_num_threads
+        num_thread = max_threads
         thread_xx = tvm.thread_axis((0, num_thread), "threadIdx.x")
         block_xx = tvm.thread_axis("blockIdx.x")
 
diff --git a/topi/python/topi/intel_graphics/conv2d.py b/topi/python/topi/intel_graphics/conv2d.py
index fa16e0585d51..29b61dd02ee3 100644
--- a/topi/python/topi/intel_graphics/conv2d.py
+++ b/topi/python/topi/intel_graphics/conv2d.py
@@ -1,4 +1,4 @@
-# pylint: disable=invalid-name,unused-variable,unused-argument,no-else-return
+# pylint: disable=invalid-name,unused-variable,unused-argument,no-else-return, too-many-arguments, too-many-locals, too-many-statements, no-member, too-many-branches
 """conv2d schedule on Intel Graphics"""
 
 from __future__ import absolute_import as _abs
@@ -57,7 +57,8 @@ def _alter_conv2d_layout(attrs, inputs, tinfos):
     return sym.contrib.conv2d_NCHWc(*copy_inputs, **new_attrs)
 
 @conv2d_NCHWc.register(["intel_graphics"])
-def _decl_conv2d(data, kernel, num_filter, kernel_size, stride, padding, out_dtype='float32'):
+def _decl_conv2d(data, kernel, num_filter, kernel_size, stride, padding, layout,\
+                 out_layout, out_dtype='float32'):
     """Conv2D operator for Intel Graphics backend.
 
     Parameters
@@ -96,7 +97,7 @@ def _decl_conv2d(data, kernel, num_filter, kernel_size, stride, padding, out_dty
     return _decl_cl_spatialpack_NCHWc(data, kernel, stride, padding, out_dtype)
 
 @generic.schedule_conv2d_NCHWc.register(["intel_graphics"])
-def schedule_conv2d_NCHWc(num_filter, kernel_size, stride, padding, outs):
+def schedule_conv2d_NCHWc(num_filter, kernel_size, stride, padding, layout, out_layout, outs):
     """Schedule for conv2d_nchw for Intel Graphics
 
     Parameters
diff --git a/topi/tests/python/test_topi_conv2d_nchw.py b/topi/tests/python/test_topi_conv2d_nchw.py
index 7f3935f3aad7..c663384b8187 100644
--- a/topi/tests/python/test_topi_conv2d_nchw.py
+++ b/topi/tests/python/test_topi_conv2d_nchw.py
@@ -74,7 +74,7 @@ def test_conv2d_nchw():
     verify_conv2d_nchw(1, 256, 14, 512, 3, 2, 1)
     verify_conv2d_nchw(1, 256, 14, 512, 1, 2, 0)
     verify_conv2d_nchw(1, 512, 7, 512, 3, 1, 1)
-    # ResNet 50 workloads
+    # ResNet50 workloads
     verify_conv2d_nchw(1, 64, 56, 256, 1, 1, 0)
     verify_conv2d_nchw(1, 256, 56, 64, 1, 1, 0)
     verify_conv2d_nchw(1, 256, 56, 128, 1, 2, 0)

From ef993d738b8c701e24f5b99ce3ea49ecc88c3799 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 26 Jul 2018 09:41:19 -0700
Subject: [PATCH 918/949] [DOCS] Reword community guide (#1494)

---
 docs/contribute/community.rst | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/docs/contribute/community.rst b/docs/contribute/community.rst
index 49cc26828319..1023cf0ddccc 100644
--- a/docs/contribute/community.rst
+++ b/docs/contribute/community.rst
@@ -16,27 +16,27 @@ See `CONTRIBUTORS.md <https://github.com/dmlc/tvm/blob/master/CONTRIBUTORS.md>`_
 Project Management Committee
 ----------------------------
 
-The PMC consists of a small group of active committers that moderate the discussion, provide mentorship to committers and code owners and manage the project release. PMC members need to actively manage the general project directions. Note that most major design choices and proposed changes should reach consensus among committers.
+The PMC consists of a small group of active committers that moderate the discussion, provide mentorship to committers and code owners and manage the project release. PMC members need to actively manage the general project directions. Note that most major design choices and proposed changes should reach consensus among the committers.
 
 Committer
 ---------
 
-Committers are individuals who are granted the write access to the project. Committers oversee the general project directions and participates in the evaluation of the RFCs involving major design changes. We look for the following traits from potential comitters
+Committers are individuals who are granted the write access to the project. Committers oversee the general project directions and participate in the evaluation of the RFCs involving major design changes. Here is a list of useful things to do to help become a committer.
 
-- Own at least one module in the project
+- Deep understanding of one or a few modules in the project.
 - Good understanding of general project structure, demonstrated by discussion over RFCs, code reviews and proposals of new features
 - Active history of code reviews that demonstrate a good technical ability
 - Contribution history of high-quality documentation and tutorials to the promote project
 - History of creating clean, maintainable code and including good test cases.
 
-New committers are nominated by current committers from current code owners by providing evidence of these perspectives.
+New committers are nominated by current committers from current code owners.
 
 Code Owner
 ----------
 
-A code owner is an individual who is responsible for a specific area of the code-base. Code owners are responsible for the areas they are in charge of and oversee the code review process of the corresponding module. Changes to a specific area need to be approved by one of its owners in order to be merged. Once a pull request is approved by the designated code owner, the code can be directly merged into the repo.
+A code owner is an individual who is responsible for a specific area of the code-base. Code owners are responsible for the areas they are in charge of and oversee the code review process of the corresponding module. Changes to a specific area need to be approved by one of its owners in order to be merged. Once a pull request is approved by the designated code owner, the code can be directly merged into the repo. Code owners are essential for a high quality and healthy codebase.
 
-Code owners are promoted from contributors. In order to become code owner, one needs to make substantial contributions to a module. A candidate for code owner also needs to demonstrate good code quality, testing, and documentation in specific areas. These traits of code owners are essential for a high quality and healthy codebase. These traits are evaluated by the contributor’s participation in the project, including, but not limited to:
+We welcome new code owners that help to keep good code quality, testing, and documentation in specific areas. Here is a list of useful traits that help the community to recognize potential code owners:
 
 - High-quality, readable code contributions indicated by pull requests that can be merged without a substantial code review
 - Good coverage of tests and documentation in the contributions

From f1f30c4c4ab5e8a47a0d76c8cd2c5344c0a9096a Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Thu, 26 Jul 2018 12:14:47 -0700
Subject: [PATCH 919/949] update dependency (#1495)

---
 docker/install/ubuntu_install_python_package.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/docker/install/ubuntu_install_python_package.sh b/docker/install/ubuntu_install_python_package.sh
index 3e5c88674079..deaad2f87305 100644
--- a/docker/install/ubuntu_install_python_package.sh
+++ b/docker/install/ubuntu_install_python_package.sh
@@ -1,3 +1,3 @@
 # install libraries for python package on ubuntu
-pip2 install nose pylint numpy nose-timer cython decorator scipy tornado
-pip3 install nose pylint numpy nose-timer cython decorator scipy tornado typed_ast pytest mypy orderedset
+pip2 install nose pylint numpy nose-timer cython decorator scipy tornado request
+pip3 install nose pylint numpy nose-timer cython decorator scipy tornado request typed_ast pytest mypy orderedset

From 2fb13d94bab45f7cf6419b619ec070d1e42e396c Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Thu, 26 Jul 2018 16:56:41 -0700
Subject: [PATCH 920/949] Revert "update dependency (#1495)" (#1499)

This reverts commit f1f30c4c4ab5e8a47a0d76c8cd2c5344c0a9096a.
---
 docker/install/ubuntu_install_python_package.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/docker/install/ubuntu_install_python_package.sh b/docker/install/ubuntu_install_python_package.sh
index deaad2f87305..3e5c88674079 100644
--- a/docker/install/ubuntu_install_python_package.sh
+++ b/docker/install/ubuntu_install_python_package.sh
@@ -1,3 +1,3 @@
 # install libraries for python package on ubuntu
-pip2 install nose pylint numpy nose-timer cython decorator scipy tornado request
-pip3 install nose pylint numpy nose-timer cython decorator scipy tornado request typed_ast pytest mypy orderedset
+pip2 install nose pylint numpy nose-timer cython decorator scipy tornado
+pip3 install nose pylint numpy nose-timer cython decorator scipy tornado typed_ast pytest mypy orderedset

From bb84ef5296e4adfe5cd185b1528f6d2d969a13f4 Mon Sep 17 00:00:00 2001
From: eqy <eqy@cs.washington.edu>
Date: Thu, 26 Jul 2018 20:14:57 -0700
Subject: [PATCH 921/949] perfom full rpc tracker handshake (#1500)

---
 .../dmlc/tvm/rpc/ConnectTrackerServerProcessor.java  | 12 ++++++++++++
 jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPC.java      |  1 +
 2 files changed, 13 insertions(+)

diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/ConnectTrackerServerProcessor.java b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/ConnectTrackerServerProcessor.java
index ca3af923c448..47881eb350c3 100644
--- a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/ConnectTrackerServerProcessor.java
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/ConnectTrackerServerProcessor.java
@@ -201,6 +201,12 @@ private Socket connectToTracker() throws IOException {
     if (trackerMagic != RPC.RPC_TRACKER_MAGIC) {
       throw new SocketException("failed to connect to tracker (WRONG MAGIC)");
     }
+    String infoJSON = generateCinfo(key);
+    Utils.sendString(trackerOut, infoJSON);
+    int recvCode = Integer.parseInt(Utils.recvString(trackerIn));
+    if (recvCode != RPC.TrackerCode.SUCCESS) {
+      throw new SocketException("failed to connect to tracker (not SUCCESS)");
+    }
     return trackerSocket;
   }
 
@@ -238,6 +244,12 @@ private boolean needRefreshKey() throws IOException {
     return false;
   }
 
+  // handcrafted JSON
+  private String generateCinfo(String key) {
+    String cinfo = "{\"key\" : " + "\"server:" + key + "\"}";
+    return "[" + RPC.TrackerCode.UPDATE_INFO + ", " + cinfo + "]";
+  }
+
   // handcrafted JSON
   private String generatePut(int code, String key, int port, String matchKey) {
     return "[" + code + ", " + "\"" + key + "\"" + ", " + "[" + port + ", "
diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPC.java b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPC.java
index ee763fc41e18..8aa112d18b42 100644
--- a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPC.java
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/RPC.java
@@ -32,6 +32,7 @@ public class RPC {
 
   public class TrackerCode {
     public static final int PUT = 3;
+    public static final int UPDATE_INFO = 5;
     public static final int GET_PENDING_MATCHKEYS = 7;
     public static final int SUCCESS = 0;
   }

From a8ca691c75f3e3522c909b5271f8b10b6671e228 Mon Sep 17 00:00:00 2001
From: sgrechanik-h <41160472+sgrechanik-h@users.noreply.github.com>
Date: Fri, 27 Jul 2018 18:50:49 +0300
Subject: [PATCH 922/949] [NNVM] Fix grads for sum and expand_like (#1455)

---
 nnvm/python/nnvm/top/transform.py             |  4 ++++
 nnvm/src/top/tensor/reduce.cc                 | 10 +++++++--
 nnvm/src/top/tensor/transform.cc              | 21 +++++++++++++++----
 nnvm/tests/python/compiler/test_top_level4.py | 20 +++++++++++++++++-
 topi/python/topi/transform.py                 |  6 +++---
 5 files changed, 51 insertions(+), 10 deletions(-)

diff --git a/nnvm/python/nnvm/top/transform.py b/nnvm/python/nnvm/top/transform.py
index 5f136365c898..facb345c1abe 100644
--- a/nnvm/python/nnvm/top/transform.py
+++ b/nnvm/python/nnvm/top/transform.py
@@ -15,6 +15,10 @@
 @reg.register_compute("expand_like")
 def compute_expand_like(attrs, inputs, _):
     """Compute definition of expand_like"""
+    if len(inputs[0].shape) == len(inputs[1].shape):
+        # If the number of dimensions is not changed then it is just a broadcasting
+        return topi.broadcast_to(inputs[0], inputs[1].shape)
+
     exclude = attrs.get_bool("exclude")
     axis = attrs.get_int_tuple("axis")
     if exclude:
diff --git a/nnvm/src/top/tensor/reduce.cc b/nnvm/src/top/tensor/reduce.cc
index 3f948720a293..8e72391ac4d0 100644
--- a/nnvm/src/top/tensor/reduce.cc
+++ b/nnvm/src/top/tensor/reduce.cc
@@ -170,12 +170,18 @@ Example::
   "FGradient", [](const NodePtr& n,
                   const std::vector<NodeEntry>& ograds){
     const ReduceParam& param = nnvm::get<ReduceParam>(n->attrs.parsed);
-    std::ostringstream axis; axis << param.axis;
+    bool exclude = param.exclude;
+    TShape p_axis = param.axis;
+    if (!param.exclude && param.axis.ndim() == 0) {
+      exclude = true;
+      p_axis = TShape();
+    }
+    std::ostringstream axis; axis << p_axis;
     return std::vector<NodeEntry>{
       MakeNode("expand_like", n->attrs.name + "_grad",
                {ograds[0], n->inputs[0]},
                {{"axis", axis.str()},
-                {"exclude", std::to_string(param.exclude)}})
+                {"exclude", std::to_string(exclude)}})
   };
 });
 
diff --git a/nnvm/src/top/tensor/transform.cc b/nnvm/src/top/tensor/transform.cc
index 05adc06a801b..78255d20f040 100644
--- a/nnvm/src/top/tensor/transform.cc
+++ b/nnvm/src/top/tensor/transform.cc
@@ -251,7 +251,8 @@ will return a new array with shape ``(2,1,1,1,1,1,3,4)``.
 
 NNVM_REGISTER_OP(expand_like)
   .describe(R"code(Expand an input array with the shape of second array.
-This operation can always be composed of unsqueezing and expanding dims.
+This operation can be thought of as a composition of expand_dims and broadcast_to.
+If the dimensions are already expanded then it just broadcasts.
 Examples::
   input = [ 12.  19.  27.]
   input.shape = (3,)
@@ -282,11 +283,23 @@ Examples::
     std::ostringstream axis;
     axis << param.axis;
 
-    return std::vector<NodeEntry>{
-      MakeNode("sum", n->attrs.name + "_grad",
+    if (param.axis.ndim() == 0 && !param.exclude) {
+      // Special case needed because sum interprets axis=[] differently
+      return std::vector<NodeEntry>{
+        ograds[0],
+        MakeNode("zeros_like", n->attrs.name + "_zero_grad", {n->inputs[1]})
+      };
+    }
+
+    auto sum_node =
+      MakeNode("sum", n->attrs.name + "_sum_grad",
                {ograds[0]},
                {{"axis", axis.str()},
-                {"exclude", std::to_string(param.exclude)}}),
+                {"exclude", std::to_string(param.exclude)}});
+
+    return std::vector<NodeEntry>{
+      MakeNode("reshape_like", n->attrs.name + "_grad",
+               {sum_node, n->inputs[0]}),
       MakeNode("zeros_like", n->attrs.name + "_zero_grad", {n->inputs[1]})
     };
   })
diff --git a/nnvm/tests/python/compiler/test_top_level4.py b/nnvm/tests/python/compiler/test_top_level4.py
index 2d0c8aeb7dfa..b7f759c62f99 100644
--- a/nnvm/tests/python/compiler/test_top_level4.py
+++ b/nnvm/tests/python/compiler/test_top_level4.py
@@ -378,6 +378,13 @@ def verify_expand_like(in_shape, out_shape, axis, exclude):
 
     def forward(x, y):
         odim = len(out_shape)
+
+        if len(x.shape) == len(y.shape):
+            return np.broadcast_to(x, y.shape)
+
+        if x.shape == (1,) and len(y.shape) == odim:
+            x = np.reshape(x, ())
+
         real_axis = [i if i >= 0 else i + odim for i in axis]
         real_axis = sorted(real_axis)
         if exclude:
@@ -391,11 +398,17 @@ def forward(x, y):
 
     def backward(head_grads, x, y):
         odim = len(out_shape)
+
+        keepdims = len(x.shape) == len(y.shape)
+
+        if x.shape == (1,) and len(y.shape) == odim:
+            x = np.reshape(x, ())
+
         real_axis = [i if i >= 0 else i + odim for i in axis]
         real_axis = sorted(real_axis)
         if exclude:
             real_axis = list(set(range(odim)) - set(real_axis))
-        return [np.sum(head_grads, axis=tuple(real_axis)),
+        return [np.sum(head_grads, axis=tuple(real_axis), keepdims=keepdims),
                 np.zeros_like(y)]
 
 
@@ -410,6 +423,11 @@ def test_expand_like():
     verify_expand_like((2,), (2, 3), [1], False)
     verify_expand_like((3, 4), (3, 5, 4), [1], False)
     verify_expand_like((5, 7), (5, 6, 7, 8), [0, 2], True)
+    verify_expand_like((2, 3), (2, 3), [], False)
+    verify_expand_like((1,), (2, 3), [0, 1], False)
+    verify_expand_like((1, 1), (2, 3), [0, 1], False)
+    verify_expand_like((2, 1), (2, 3), [1], False)
+    verify_expand_like((1, 3), (2, 3), [0], False)
 
 
 def verify_elemwise_sum(num_args):
diff --git a/topi/python/topi/transform.py b/topi/python/topi/transform.py
index 871988d3c058..2ad01852c8b9 100644
--- a/topi/python/topi/transform.py
+++ b/topi/python/topi/transform.py
@@ -65,15 +65,15 @@ def expand_like(a, shape_like, axis):
     """
     odim = len(axis) + len(a.shape)
     if odim != len(shape_like.shape):
+        if len(a.shape) == 1 and len(axis) == len(shape_like.shape):
+            # A special case: `a` is a scalar represented as a 1-dim tensor
+            return tvm.compute(shape_like.shape, lambda *idxs: a(0))
         raise ValueError("shape inconsistent when expand_like ({}, {}, {})".format(
             len(axis), len(a.shape), len(shape_like.shape)))
 
     real_axis = topi.reduction._get_real_axis(len(shape_like.shape), axis)
     real_axis = sorted(real_axis)
 
-    if not real_axis:
-        return a
-
     def _compute(*idxs):
         indices = []
         axis_index = 0

From aa23952a9be67f4ce0eb649ed55adda22a22a505 Mon Sep 17 00:00:00 2001
From: Albin Joy <albin.joy@huawei.com>
Date: Fri, 27 Jul 2018 21:28:43 +0530
Subject: [PATCH 923/949] [BUILD] warning fix: new does not have an alignment
 parameter     (#1478)

---
 CMakeLists.txt | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 81630841f525..39776d53d1f1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -78,6 +78,10 @@ else(MSVC)
   check_cxx_compiler_flag("-std=c++11"    SUPPORT_CXX11)
   set(CMAKE_C_FLAGS "-O2 -Wall -fPIC ${CMAKE_C_FLAGS}")
   set(CMAKE_CXX_FLAGS "-O2 -Wall -fPIC -std=c++11 ${CMAKE_CXX_FLAGS}")
+  if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND
+      CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 7.0)
+    set(CMAKE_CXX_FLAGS "-faligned-new ${CMAKE_CXX_FLAGS}")
+  endif()
 endif(MSVC)
 
 # add source group

From f33fd5c03d8a2b3972e3b69a79a89d0c9754cd9e Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Fri, 27 Jul 2018 20:46:47 -0700
Subject: [PATCH 924/949] [NNVM] Fix check in layout parsing (#1502)

* [NNVM] Fix check in layout parsing

* add one workload
---
 nnvm/include/nnvm/layout.h       | 2 +-
 vta/python/vta/top/arm_conv2d.py | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/nnvm/include/nnvm/layout.h b/nnvm/include/nnvm/layout.h
index 918eb9a25dee..94813f5323f8 100644
--- a/nnvm/include/nnvm/layout.h
+++ b/nnvm/include/nnvm/layout.h
@@ -298,7 +298,7 @@ class Layout {
     std::ostringstream repr;
     if (is_subdim(layout_simplified_[i])) {
       auto factor = subsizeof(layout_simplified_[i]);
-      CHECK_LT(factor, 0);
+      CHECK_GT(factor, 0);
       repr << factor;
     }
     repr << layout_simplified_[i];
diff --git a/vta/python/vta/top/arm_conv2d.py b/vta/python/vta/top/arm_conv2d.py
index d8f749b3b908..79abbe9e3b21 100644
--- a/vta/python/vta/top/arm_conv2d.py
+++ b/vta/python/vta/top/arm_conv2d.py
@@ -11,6 +11,7 @@
 from topi import generic
 
 _WORKLOADS = [
+    Workload('float32', 'float32', 224, 224, 3, 64, 7, 7, 3, 3, 2, 2),
     Workload('int8', 'int32', 224, 224, 3, 64, 7, 7, 3, 3, 2, 2),
     Workload('int8', 'int32', 56, 56, 64, 64, 3, 3, 1, 1, 1, 1),
     Workload('int8', 'int32', 56, 56, 64, 64, 1, 1, 0, 0, 1, 1),
@@ -27,6 +28,7 @@
 _SCHEDULES = [
     # float32 imagenet
     SpatialPack(1, 8, 4, 1, 4, True),
+    SpatialPack(1, 8, 4, 1, 4, True),
     SpatialPack(1, 7, 4, 2, 4, True),
     SpatialPack(1, 4, 8, 4, 1, True),
     SpatialPack(1, 4, 4, 1, 16, False),

From 616f490b6930e13b302045ffcfd5920a18dd20d5 Mon Sep 17 00:00:00 2001
From: masahi <masahi129@gmail.com>
Date: Mon, 30 Jul 2018 13:54:41 +0900
Subject: [PATCH 925/949] [TOPI, x86] Adapt AVX schedules for SSE target
 (#1504)

* adapt avx schedule for sse

* fixed output type when mixed precision mode
---
 topi/python/topi/x86/conv2d.py            | 84 ++++++-----------------
 topi/python/topi/x86/conv2d_avx_common.py |  9 ++-
 2 files changed, 28 insertions(+), 65 deletions(-)

diff --git a/topi/python/topi/x86/conv2d.py b/topi/python/topi/x86/conv2d.py
index 801a264d306f..ae4a567f1dfa 100644
--- a/topi/python/topi/x86/conv2d.py
+++ b/topi/python/topi/x86/conv2d.py
@@ -117,11 +117,9 @@ def _declaration_conv(data, kernel, stride, padding, layout, out_dtype):
     out_dtype = data.dtype if out_dtype is None else out_dtype
     target = tvm.target.current_target(allow_none=False)
     wkl = _get_workload(data, kernel, stride, padding, out_dtype)
-    if 'avx' in str(target) and layout == 'NCHW':
+    if layout == 'NCHW':
         sch = _get_schedule(wkl)
         return _AVX_SCH_TO_DECL_FUNC[type(sch)](data, kernel, stride, padding, layout, out_dtype)
-    elif layout == 'NCHW':
-        return nn.conv2d_nchw(data, kernel, stride, padding, out_dtype)
     elif layout == 'HWCN':
         return nn.conv2d_hwcn(data, kernel, stride, padding, out_dtype)
     elif layout == 'NHWC':
@@ -191,77 +189,39 @@ def schedule_conv2d(outs):
     s = tvm.create_schedule([x.op for x in outs])
     target = tvm.target.current_target(allow_none=False)
 
-    def default_schedule(op):
-        """NCHW conv2d schedule for non imagenet workloads"""
-        conv = op.output(0)
-        kernel = op.input_tensors[1]
-        if isinstance(kernel.op, tvm.tensor.ComputeOp) and "dilate" in kernel.op.tag:
-            s[kernel].compute_inline()
-        data = op.input_tensors[0]
-        data_pad = None
-        if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
-            data_pad = data
-            data = data_pad.op.input_tensors[0]
-
-        n_pad, c_pad, h_pad, w_pad = data_pad.op.axis
-        pad_fused = s[data_pad].fuse(n_pad, c_pad)
-        s[data_pad].parallel(pad_fused)
-        C = conv
-        n, c, h, w = C.op.axis
-        rc, ry, rx = C.op.reduce_axis
-        fused = s[C].fuse(n, c)
-        s[C].parallel(fused)
-        wo, wi = s[C].split(w, factor=16)
-        s[C].reorder(fused, rc, h, wo, ry, rx, wi)  # move rc to outer loop
-        s[C].unroll(rx)
-        s[C].unroll(ry)
-        s[C].vectorize(wi)
-
     def traverse(op):
         """Traverse operators from computation graph"""
         # inline all one-to-one-mapping operators except the last stage (output)
         if tag.is_broadcast(op.tag):
             if op not in s.outputs:
                 s[op].compute_inline()
-            else: # inject custom schedule
-                if len(op.axis) == 4 and 'avx' not in str(target): # schedule bias + bn + relu
-                    n, c, h, w = op.axis
-                    fused = s[op].fuse(n, c)
-                    s[op].parallel(fused)
-                    s[op].vectorize(w)
             for tensor in op.input_tensors:
                 if tensor.op.input_tensors:
                     traverse(tensor.op)
 
         if 'conv2d_nchw' in op.tag:
-            if 'avx' in str(target):
-                try:
-                    output = op.output(0)
-                    conv_out = op.input_tensors[0]
-                    kernel_vec = conv_out.op.input_tensors[1]
-                    kernel = kernel_vec.op.input_tensors[0]
-                    if isinstance(kernel.op, tvm.tensor.ComputeOp) and "dilate" in kernel.op.tag:
-                        s[kernel].compute_inline()
-                    data_vec = conv_out.op.input_tensors[0]
-                    data = data_vec.op.input_tensors[0]
-                    data_pad = None
-                    if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
-                        data_pad = data
-                        data = data_pad.op.input_tensors[0]
-                    padding = infer_pad(data, data_pad)
-                    if data_pad is None:
-                        stride = infer_stride(data, kernel, output)
-                    else:
-                        stride = infer_stride(data_pad, kernel, output)
-
-                    wkl = _get_workload(data, kernel, stride, padding, output.dtype)
-                    sch = _get_schedule(wkl)
-                    _AVX_SCH_TO_SCH_FUNC[type(sch)](s, data, data_pad, data_vec,
-                                                    kernel, kernel_vec, conv_out, output, outs[0])
-                except IndexError:
-                    default_schedule(op)
+            output = op.output(0)
+            conv_out = op.input_tensors[0]
+            kernel_vec = conv_out.op.input_tensors[1]
+            kernel = kernel_vec.op.input_tensors[0]
+            if isinstance(kernel.op, tvm.tensor.ComputeOp) and "dilate" in kernel.op.tag:
+                s[kernel].compute_inline()
+            data_vec = conv_out.op.input_tensors[0]
+            data = data_vec.op.input_tensors[0]
+            data_pad = None
+            if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
+                data_pad = data
+                data = data_pad.op.input_tensors[0]
+            padding = infer_pad(data, data_pad)
+            if data_pad is None:
+                stride = infer_stride(data, kernel, output)
             else:
-                default_schedule(op)
+                stride = infer_stride(data_pad, kernel, output)
+
+            wkl = _get_workload(data, kernel, stride, padding, output.dtype)
+            sch = _get_schedule(wkl)
+            _AVX_SCH_TO_SCH_FUNC[type(sch)](s, data, data_pad, data_vec,
+                                            kernel, kernel_vec, conv_out, output, outs[0])
 
     traverse(outs[0].op)
     return s
diff --git a/topi/python/topi/x86/conv2d_avx_common.py b/topi/python/topi/x86/conv2d_avx_common.py
index 108f5e7248e3..8f8086fdebb4 100644
--- a/topi/python/topi/x86/conv2d_avx_common.py
+++ b/topi/python/topi/x86/conv2d_avx_common.py
@@ -85,13 +85,16 @@ def _declaration_conv(data, kernel, stride, padding, layout, out_dtype):
     kw = tvm.reduce_axis((0, kernel_width), name='kw')
 
     conv = tvm.compute(oshape, lambda n, oc_chunk, oh, ow, oc_block:
-                       tvm.sum(data_vec[n, ic//sch.ic_bn, oh*HSTR+kh, ic%sch.ic_bn, ow*WSTR+kw] *
-                               kernel_vec[oc_chunk, ic//sch.ic_bn, kh, kw, ic%sch.ic_bn, oc_block],
+                       tvm.sum(data_vec[n, ic//sch.ic_bn, oh*HSTR+kh, ic%sch.ic_bn, ow*WSTR+kw]
+                               .astype(out_dtype) *
+                               kernel_vec[oc_chunk, ic//sch.ic_bn, kh, kw, ic%sch.ic_bn, oc_block]
+                               .astype(out_dtype),
                                axis=[ic, kh, kw]),
                        name='conv')
 
     unpack = tvm.compute(unpack_shape,
-                         lambda n, c, h, w: conv[n, c // sch.oc_bn, h, w, c % sch.oc_bn],
+                         lambda n, c, h, w: conv[n, c // sch.oc_bn, h, w, c % sch.oc_bn]
+                         .astype(out_dtype),
                          name='output_unpack',
                          tag='conv2d_nchw')
     return unpack

From cf37a8313daada63001fbccddb11c2aec9e01409 Mon Sep 17 00:00:00 2001
From: Siju <sijusamuel@gmail.com>
Date: Mon, 30 Jul 2018 19:39:00 +0530
Subject: [PATCH 926/949] Activations for coreml added (#1508)

---
 nnvm/python/nnvm/frontend/coreml.py | 10 ++++++----
 nnvm/python/nnvm/top/image.py       |  2 +-
 2 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/coreml.py b/nnvm/python/nnvm/frontend/coreml.py
index d6a9ad2e2e2d..e80cfe23f220 100644
--- a/nnvm/python/nnvm/frontend/coreml.py
+++ b/nnvm/python/nnvm/frontend/coreml.py
@@ -102,9 +102,10 @@ def ActivationParams(op, insym, symtab):
     elif whichActivation == 'leakyReLU':
         return _sym.leaky_relu(insym, alpha=par.alpha)
     elif whichActivation == 'thresholdedReLU':
-        raise NotImplementedError('thresholdedReLU not implemented')
+        alpha_tensor = _sym.full_like(insym, fill_value=float(par.alpha))
+        return _sym.elemwise_mul(insym, _sym.greater(insym, alpha_tensor))
     elif whichActivation == 'PReLU':
-        raise NotImplementedError('PReLU not implemented')
+        return _sym.prelu(insym, alpha=par.alpha)
     elif whichActivation == 'tanh':
         return _sym.tanh(insym)
     elif whichActivation == 'scaledTanh':
@@ -113,12 +114,13 @@ def ActivationParams(op, insym, symtab):
     elif whichActivation == 'sigmoid':
         return _sym.sigmoid(insym)
     elif whichActivation == 'sigmoidHard':
-        raise NotImplementedError('sigmoidHard not immplemented')
+        transformX = (par.alpha * insym) + par.beta
+        return _sym.clip(transformX, a_min=0, a_max=1)
     elif whichActivation == 'ELU':
         return _sym.__mul_scalar__(_sym.__add_scalar__(
             _sym.exp(insym), scalar=-1), scalar=par.alpha)
     elif whichActivation == 'softsign':
-        raise NotImplementedError('softsign not implemented')
+        return insym / (1 + (_sym.relu(insym) + _sym.relu(_sym.negative(insym))))
     elif whichActivation == 'softplus':
         return _sym.log(_sym.__add_scalar__(_sym.exp(insym), scalar=1))
     elif whichActivation == 'parametricSoftplus':
diff --git a/nnvm/python/nnvm/top/image.py b/nnvm/python/nnvm/top/image.py
index f903231e3a2d..a9d0d8648c48 100644
--- a/nnvm/python/nnvm/top/image.py
+++ b/nnvm/python/nnvm/top/image.py
@@ -2,8 +2,8 @@
 """Definition of image ops"""
 from __future__ import absolute_import
 
-import topi
 import tvm
+import topi
 from . import registry as reg
 from .registry import OpPattern
 

From 02b473634b3350c3deb5d5dc744986015806ab43 Mon Sep 17 00:00:00 2001
From: Siju <sijusamuel@gmail.com>
Date: Mon, 30 Jul 2018 19:39:43 +0530
Subject: [PATCH 927/949] C-RNN layer support is added (#1492)

---
 nnvm/python/nnvm/frontend/darknet.py          | 25 ++++++++++++++++++
 .../python/frontend/darknet/test_forward.py   | 26 +++++++++++++++++++
 2 files changed, 51 insertions(+)

diff --git a/nnvm/python/nnvm/frontend/darknet.py b/nnvm/python/nnvm/frontend/darknet.py
index 50625b4b17a4..3a197a416219 100644
--- a/nnvm/python/nnvm/frontend/darknet.py
+++ b/nnvm/python/nnvm/frontend/darknet.py
@@ -673,6 +673,31 @@ def _handle_darknet_rnn_layers(self, layer_num, sym):
             self._sym_array[layer_num] = sym
             processed = True
 
+        elif LAYERTYPE.CRNN == layer.type:
+            attr.update({'n' : layer.n})
+            attr.update({'batch' : layer.batch})
+            attr.update({'num_hidden' : str(layer.outputs)})
+
+            state = self._get_rnn_state_buffer(layer)
+
+            for _ in range(layer.steps):
+                input_layer = layer.input_layer
+                sym = self._get_darknet_rnn_attrs(input_layer, sym)
+
+                self_layer = layer.self_layer
+                state = self._get_darknet_rnn_attrs(self_layer, state)
+
+                op_name, new_attrs = 'elemwise_add', {}
+                new_inputs = _as_list([sym, state])
+                state = _darknet_get_nnvm_op(op_name)(*new_inputs, **new_attrs)
+                self._outs.append(state)
+
+                output_layer = layer.output_layer
+                sym = self._get_darknet_rnn_attrs(output_layer, state)
+
+            self._sym_array[layer_num] = sym
+            processed = True
+
         return processed, sym
 
     def from_darknet(self):
diff --git a/nnvm/tests/python/frontend/darknet/test_forward.py b/nnvm/tests/python/frontend/darknet/test_forward.py
index 961da238452c..e68aed085664 100644
--- a/nnvm/tests/python/frontend/darknet/test_forward.py
+++ b/nnvm/tests/python/frontend/darknet/test_forward.py
@@ -324,6 +324,31 @@ def test_forward_rnn():
     test_rnn_forward(net)
     LIB.free_network(net)
 
+def test_forward_crnn():
+    '''test softmax layer'''
+    net = LIB.make_network(1)
+    batch = 1
+    c = 3
+    h = 224
+    w = 224
+    hidden_filters = c
+    output_filters = c
+    steps = 1
+    activation = 0
+    batch_normalize = 0
+    inputs = 256
+    outputs = 256
+    layer_1 = LIB.make_crnn_layer(batch, h, w, c, hidden_filters, output_filters,
+                                  steps, activation, batch_normalize)
+    net.layers[0] = layer_1
+    net.inputs = inputs
+    net.outputs = output_filters * h * w
+    net.w = w
+    net.h = h
+    LIB.resize_network(net, net.w, net.h)
+    test_forward(net)
+    LIB.free_network(net)
+
 def test_forward_activation_logistic():
     '''test logistic activation layer'''
     net = LIB.make_network(1)
@@ -369,4 +394,5 @@ def test_forward_activation_logistic():
     test_forward_region()
     test_forward_elu()
     test_forward_rnn()
+    test_forward_crnn()
     test_forward_activation_logistic()
\ No newline at end of file

From d72fb4fa23e17e774983a7d2c06cad53aaf934ac Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Mon, 30 Jul 2018 12:57:55 -0700
Subject: [PATCH 928/949] [TEAM] yzhliu -> committer (#1509)

---
 CONTRIBUTORS.md | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 190e206447c8..6e3cf55b94b0 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -6,16 +6,16 @@ contribute to, and influence the direction of the project. We actively invite co
 See the [community structure document](http://docs.tvm.ai/contribute/community.html) for the explanation of community structure and contribution guidelines.
 
 ## Committers
-- [Tianqi Chen](https://github.com/tqchen) (PMC), University of Washington
-- [Thierry Moreau](http://homes.cs.washington.edu/~moreau/), University of Washington
-- [Ziheng Jiang](https://github.com/ZihengJiang), Fudan University
-- [Haichen Shen](http://homes.cs.washington.edu/~haichen/), University of Washington
+- [Tianqi Chen](https://github.com/tqchen) (PMC)
+- [Thierry Moreau](http://homes.cs.washington.edu/~moreau/)
+- [Ziheng Jiang](https://github.com/ZihengJiang)
+- [Haichen Shen](http://homes.cs.washington.edu/~haichen/)
+- [Yizhi Liu](https://github.com/yzhliu)
 
 ## Code Owners
 - [Aditya Atluri](https://github.com/adityaatluri) ROCM
 - [Leyuan Wang](https://github.com/Laurawly) TOPI
 - [Yuwei Hu](https://github.com/Huyuwei) TOPI
-- [Yizhi Liu](https://github.com/yzhliu) JVM package
 - [Zhixun Tan](https://github.com/phisiart) OpenGL/WebGL backend
 - [Nick Hynes](https://github.com/nhynes) SGX and secured computing
 

From 9e12d9198a729fa5d7909a053f5ba8953746ac64 Mon Sep 17 00:00:00 2001
From: Yizhi Liu <liuyizhi@apache.org>
Date: Mon, 30 Jul 2018 12:58:30 -0700
Subject: [PATCH 929/949] [tvm4j] support kNDArrayContainer (#1510)

---
 jvm/core/src/main/java/ml/dmlc/tvm/Function.java |  7 +++++--
 jvm/core/src/main/java/ml/dmlc/tvm/TypeCode.java |  2 +-
 jvm/native/src/main/native/jni_helper_func.h     | 10 ++++++----
 3 files changed, 12 insertions(+), 7 deletions(-)

diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/Function.java b/jvm/core/src/main/java/ml/dmlc/tvm/Function.java
index 5b2008a757ed..2e21f439300e 100644
--- a/jvm/core/src/main/java/ml/dmlc/tvm/Function.java
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/Function.java
@@ -187,7 +187,8 @@ public Function pushArg(String arg) {
    * @return this
    */
   public Function pushArg(NDArrayBase arg) {
-    Base._LIB.tvmFuncPushArgHandle(arg.handle, TypeCode.ARRAY_HANDLE.id);
+    int id = arg.isView ? TypeCode.ARRAY_HANDLE.id : TypeCode.NDARRAY_CONTAINER.id;
+    Base._LIB.tvmFuncPushArgHandle(arg.handle, id);
     return this;
   }
 
@@ -247,7 +248,9 @@ private static void pushArgToStack(Object arg) {
     } else if (arg instanceof byte[]) {
       Base._LIB.tvmFuncPushArgBytes((byte[]) arg);
     } else if (arg instanceof NDArrayBase) {
-      Base._LIB.tvmFuncPushArgHandle(((NDArrayBase) arg).handle, TypeCode.ARRAY_HANDLE.id);
+      NDArrayBase nd = (NDArrayBase) arg;
+      int id = nd.isView ? TypeCode.ARRAY_HANDLE.id : TypeCode.NDARRAY_CONTAINER.id;
+      Base._LIB.tvmFuncPushArgHandle(nd.handle, id);
     } else if (arg instanceof Module) {
       Base._LIB.tvmFuncPushArgHandle(((Module) arg).handle, TypeCode.MODULE_HANDLE.id);
     } else if (arg instanceof Function) {
diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/TypeCode.java b/jvm/core/src/main/java/ml/dmlc/tvm/TypeCode.java
index 0b28746f9555..1f01fde6d307 100644
--- a/jvm/core/src/main/java/ml/dmlc/tvm/TypeCode.java
+++ b/jvm/core/src/main/java/ml/dmlc/tvm/TypeCode.java
@@ -21,7 +21,7 @@
 public enum TypeCode {
   INT(0), UINT(1), FLOAT(2), HANDLE(3), NULL(4), TVM_TYPE(5),
   TVM_CONTEXT(6), ARRAY_HANDLE(7), NODE_HANDLE(8), MODULE_HANDLE(9),
-  FUNC_HANDLE(10), STR(11), BYTES(12);
+  FUNC_HANDLE(10), STR(11), BYTES(12), NDARRAY_CONTAINER(13);
 
   public final int id;
 
diff --git a/jvm/native/src/main/native/jni_helper_func.h b/jvm/native/src/main/native/jni_helper_func.h
index d4435bdaaba8..181d9de040f1 100644
--- a/jvm/native/src/main/native/jni_helper_func.h
+++ b/jvm/native/src/main/native/jni_helper_func.h
@@ -134,10 +134,10 @@ jobject newFunction(JNIEnv *env, jlong value) {
   return object;
 }
 
-jobject newNDArray(JNIEnv *env, jlong value) {
+jobject newNDArray(JNIEnv *env, jlong handle, jboolean isview) {
   jclass cls = env->FindClass("ml/dmlc/tvm/NDArrayBase");
-  jmethodID constructor = env->GetMethodID(cls, "<init>", "(J)V");
-  jobject object = env->NewObject(cls, constructor, value);
+  jmethodID constructor = env->GetMethodID(cls, "<init>", "(JZ)V");
+  jobject object = env->NewObject(cls, constructor, handle, isview);
   env->DeleteLocalRef(cls);
   return object;
 }
@@ -181,7 +181,9 @@ jobject tvmRetValueToJava(JNIEnv *env, TVMValue value, int tcode) {
     case kFuncHandle:
       return newFunction(env, reinterpret_cast<jlong>(value.v_handle));
     case kArrayHandle:
-      return newNDArray(env, reinterpret_cast<jlong>(value.v_handle));
+      return newNDArray(env, reinterpret_cast<jlong>(value.v_handle), true);
+    case kNDArrayContainer:
+      return newNDArray(env, reinterpret_cast<jlong>(value.v_handle), false);
     case kStr:
       return newTVMValueString(env, value.v_str);
     case kBytes:

From f71185351ab452e83fc33405aa5513a75805f79a Mon Sep 17 00:00:00 2001
From: "TABATA, Keiichi" <tabata.keiichi@lab.ntt.co.jp>
Date: Wed, 1 Aug 2018 01:58:59 +0900
Subject: [PATCH 930/949] Add initial support for Intel FPGA SDK for OpenCL
 (AOCL) (#1474)

---
 cmake/config.cmake                          |  3 +
 cmake/modules/OpenCL.cmake                  | 12 +++
 docs/deploy/aocl_fpga.md                    | 92 +++++++++++++++++++++
 include/tvm/runtime/c_runtime_api.h         |  1 +
 python/tvm/_ffi/runtime_ctypes.py           |  2 +
 src/codegen/build_module.cc                 |  3 +
 src/codegen/codegen_aocl.cc                 | 62 ++++++++++++++
 src/codegen/opt/build_aocl_off.cc           | 21 +++++
 src/pass/verify_memory.cc                   |  2 +-
 src/runtime/c_runtime_api.cc                |  1 +
 src/runtime/opencl/aocl/aocl_common.h       | 42 ++++++++++
 src/runtime/opencl/aocl/aocl_device_api.cc  | 44 ++++++++++
 src/runtime/opencl/aocl/aocl_module.cc      | 58 +++++++++++++
 src/runtime/opencl/aocl/aocl_module.h       | 31 +++++++
 src/runtime/opencl/opencl_module.cc         |  2 +-
 tests/python/integration/test_ewise_fpga.py |  3 +
 16 files changed, 377 insertions(+), 2 deletions(-)
 create mode 100644 docs/deploy/aocl_fpga.md
 create mode 100644 src/codegen/codegen_aocl.cc
 create mode 100644 src/codegen/opt/build_aocl_off.cc
 create mode 100644 src/runtime/opencl/aocl/aocl_common.h
 create mode 100644 src/runtime/opencl/aocl/aocl_device_api.cc
 create mode 100644 src/runtime/opencl/aocl/aocl_module.cc
 create mode 100644 src/runtime/opencl/aocl/aocl_module.h

diff --git a/cmake/config.cmake b/cmake/config.cmake
index 89f5a0597e43..85c5102169a9 100644
--- a/cmake/config.cmake
+++ b/cmake/config.cmake
@@ -42,6 +42,9 @@ set(USE_ROCM OFF)
 # Whether enable SDAccel runtime
 set(USE_SDACCEL OFF)
 
+# Whether enable Intel FPGA SDK for OpenCL (AOCL) runtime
+set(USE_AOCL OFF)
+
 # Whether enable OpenCL runtime
 set(USE_OPENCL OFF)
 
diff --git a/cmake/modules/OpenCL.cmake b/cmake/modules/OpenCL.cmake
index 8ae4b29aff48..b30df1864522 100644
--- a/cmake/modules/OpenCL.cmake
+++ b/cmake/modules/OpenCL.cmake
@@ -19,6 +19,18 @@ else()
   list(APPEND COMPILER_SRCS src/codegen/opt/build_sdaccel_off.cc)
 endif(USE_SDACCEL)
 
+if(USE_AOCL)
+  message(STATUS "Build with Intel FPGA SDK for OpenCL support")
+  file(GLOB RUNTIME_AOCL_SRCS src/runtime/opencl/aocl/*.cc)
+  list(APPEND RUNTIME_SRCS ${RUNTIME_AOCL_SRCS})
+  if(NOT USE_OPENCL)
+    message(STATUS "Enable OpenCL support required for Intel FPGA SDK for OpenCL")
+    set(USE_OPENCL ON)
+  endif()
+else()
+  list(APPEND COMPILER_SRCS src/codegen/opt/build_aocl_off.cc)
+endif(USE_AOCL)
+
 if(USE_OPENCL)
   find_package(OpenCL REQUIRED)
   message(STATUS "Build with OpenCL support")
diff --git a/docs/deploy/aocl_fpga.md b/docs/deploy/aocl_fpga.md
new file mode 100644
index 000000000000..bd0dae97879d
--- /dev/null
+++ b/docs/deploy/aocl_fpga.md
@@ -0,0 +1,92 @@
+AOCL Backend Example
+====================
+
+TVM supports Intel FPGA SDK for OpenCL also known as AOCL.  Here is a tutorial for how to use TVM with AOCL.
+
+***Note***: This feature is still experimental.  We cannot use AOCL to deploy an end to end neural networks for now.  In addition, we only tested compilation for emulation mode of AOCL.
+
+We use two python scripts for this tutorial.
+
+- build.py - a script to synthesize FPGA bitstream.
+```
+import tvm
+
+tgt_host="llvm"
+tgt="aocl -device=s5_ref -mattr=emulator"
+
+n = tvm.var("n")
+A = tvm.placeholder((n,), name='A')
+B = tvm.placeholder((n,), name='B')
+C = tvm.compute(A.shape, lambda i: A[i] + B[i], name="C")
+
+s = tvm.create_schedule(C.op)
+px, x = s[C].split(C.op.axis[0], nparts=1)
+
+s[C].bind(px, tvm.thread_axis("pipeline"))
+
+fadd = tvm.build(s, [A, B, C], tgt, target_host=tgt_host, name="myadd")
+
+fadd.save("myadd.o")
+fadd.imported_modules[0].save("myadd.aocx")
+
+tvm.contrib.cc.create_shared("myadd.so", ["myadd.o"])
+```
+
+- run.py - a script to use FPGA as an accelerator.
+```
+import tvm
+import numpy as np
+import os
+
+tgt="aocl -device=s5_ref -mattr=emulator"
+
+fadd = tvm.module.load("myadd.so")
+fadd_dev = tvm.module.load("myadd.aocx")
+fadd.import_module(fadd_dev)
+
+ctx = tvm.context(tgt, 0)
+
+n = 1024
+a = tvm.nd.array(np.random.uniform(size=n).astype("float32"), ctx)
+b = tvm.nd.array(np.random.uniform(size=n).astype("float32"), ctx)
+c = tvm.nd.array(np.zeros(n, dtype="float32"), ctx)
+
+fadd(a, b, c)
+np.testing.assert_allclose(c.asnumpy(), a.asnumpy() + b.asnumpy())
+```
+
+Setup
+-----
+
+- Install AOCL 17.1 on Ubuntu 16.04.4 LTS.
+- Install BSP for your FPGA device.
+- Install FPGA device driver.
+- Create an ICD file at /etc/OpenCL/vendors/Altera.icd so that the OpenCL platform can be found.
+```
+/opt/intelFPGA/17.1/hld/linux64/lib/libalteracl.so
+```
+- Create an FCD file for example at /opt/Intel/OpenCL/Boards/s5_ref.fcd so that your FPGA device can be found.
+```
+/opt/intelFPGA/17.1/hld/board/s5_ref/linux64/lib/libaltera_s5_ref_mmd.so
+```
+- Setup TVM with AOCL and OpenCL enabled.
+
+Emulation
+---------
+
+- Run software emulation
+```
+export CL_CONTEXT_EMULATOR_DEVICE_INTELFPGA=1
+
+python build.py
+python run.py
+```
+
+- Run on FPGA devices (not tested)
+    - Change tgt value to "aocl -device=s5_ref" on build.py and run.py
+```
+unset CL_CONTEXT_EMULATOR_DEVICE_INTELFPGA
+
+python build.py
+python run.py
+```
diff --git a/include/tvm/runtime/c_runtime_api.h b/include/tvm/runtime/c_runtime_api.h
index 6fb7a0f3f8b3..17d00bf479aa 100644
--- a/include/tvm/runtime/c_runtime_api.h
+++ b/include/tvm/runtime/c_runtime_api.h
@@ -60,6 +60,7 @@ typedef int64_t tvm_index_t;
 
 /*! \brief Extension device types in TVM */
 typedef enum {
+  kDLAOCL = 5,
   kDLSDAccel = 6,
   kDLVulkan = 7,
   kOpenGL = 11,
diff --git a/python/tvm/_ffi/runtime_ctypes.py b/python/tvm/_ffi/runtime_ctypes.py
index 612b54649d74..4c36e82a81ec 100644
--- a/python/tvm/_ffi/runtime_ctypes.py
+++ b/python/tvm/_ffi/runtime_ctypes.py
@@ -96,6 +96,7 @@ class TVMContext(ctypes.Structure):
         1 : 'cpu',
         2 : 'gpu',
         4 : 'opencl',
+        5 : 'aocl',
         6 : 'sdaccel',
         7 : 'vulkan',
         8 : 'metal',
@@ -113,6 +114,7 @@ class TVMContext(ctypes.Structure):
         'nvptx': 2,
         'cl': 4,
         'opencl': 4,
+        'aocl' : 5,
         'sdaccel': 6,
         'vulkan': 7,
         'metal': 8,
diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc
index f9b6226f86c4..49b86626add0 100644
--- a/src/codegen/build_module.cc
+++ b/src/codegen/build_module.cc
@@ -91,6 +91,9 @@ Target CreateTarget(const std::string& target_name,
   } else if (target_name == "sdaccel") {
     t->device_type = kDLOpenCL;
     t->keys_array.push_back(ir::StringImm::make("sdaccel"));
+  } else if (target_name == "aocl") {
+    t->device_type = kDLAOCL;
+    t->keys_array.push_back(ir::StringImm::make("aocl"));
   } else if (target_name == "opengl") {
     t->device_type = kOpenGL;
     t->keys_array.push_back(ir::StringImm::make("opengl"));
diff --git a/src/codegen/codegen_aocl.cc b/src/codegen/codegen_aocl.cc
new file mode 100644
index 000000000000..8830588758ef
--- /dev/null
+++ b/src/codegen/codegen_aocl.cc
@@ -0,0 +1,62 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file codegen_aocl.cc
+ */
+#include <tvm/build_module.h>
+#include <vector>
+#include <string>
+#include "./codegen_opencl.h"
+#include "./build_common.h"
+#include "../runtime/opencl/aocl/aocl_module.h"
+#include "../runtime/file_util.h"
+
+namespace tvm {
+namespace codegen {
+
+runtime::Module BuildAOCL(Array<LoweredFunc> funcs, std::string target_str) {
+  // Get code.
+  using tvm::runtime::Registry;
+  bool output_ssa = false;
+  CodeGenOpenCL cg;
+  cg.Init(output_ssa);
+  for (LoweredFunc f : funcs) {
+    cg.AddFunction(f);
+  }
+  std::string code = cg.Finish();
+  if (const auto* f = Registry::Get("tvm_callback_opencl_postproc")) {
+    code = (*f)(code).operator std::string();
+  }
+
+  // Write a .cl file.
+  runtime::SaveBinaryToFile("aocl.cl", code.c_str());
+
+  // Compile the .cl file.
+  Target target = Target::create(target_str);
+  if (target->device_name == "") {
+    LOG(FATAL) << "AOCL device name not specified in build target.";
+  }
+  std::string cmd = "aoc aocl.cl";
+  for (std::string option : target->options()) {
+    if (option == "-mattr=emulator") {
+      cmd += " -march=emulator";
+    }
+  }
+  cmd += " -board=" + target->device_name;
+  if (system(cmd.c_str()) != 0) {
+    LOG(FATAL) << "OpenCL offline compilation error.";
+  }
+
+  // Read .aocx file
+  std::string aocxbin;
+  runtime::LoadBinaryFromFile("aocl.aocx", &aocxbin);
+
+  return AOCLModuleCreate(aocxbin, "aocx", ExtractFuncInfo(funcs), code);
+}
+
+TVM_REGISTER_API("codegen.build_aocl")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    *rv = BuildAOCL(args[0], args[1]);
+  });
+
+}  // namespace codegen
+}  // namespace tvm
diff --git a/src/codegen/opt/build_aocl_off.cc b/src/codegen/opt/build_aocl_off.cc
new file mode 100644
index 000000000000..535036016247
--- /dev/null
+++ b/src/codegen/opt/build_aocl_off.cc
@@ -0,0 +1,21 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ *  Optional module when build aocl is switched to off
+ */
+#include "../codegen_source_base.h"
+#include "../../runtime/opencl/opencl_module.h"
+
+namespace tvm {
+namespace runtime {
+
+Module AOCLModuleCreate(
+    std::string data,
+    std::string fmt,
+    std::unordered_map<std::string, FunctionInfo> fmap,
+    std::string source) {
+  LOG(WARNING) << "AOCL runtime not enabled, return a source module...";
+  return codegen::DeviceSourceModuleCreate(data, fmt, fmap, "aocl");
+}
+
+}  // namespace runtime
+}  // namespace tvm
diff --git a/src/pass/verify_memory.cc b/src/pass/verify_memory.cc
index 38cd05a4bdd0..bafaaa642ac7 100644
--- a/src/pass/verify_memory.cc
+++ b/src/pass/verify_memory.cc
@@ -145,7 +145,7 @@ class MemoryAccessVerifier final : protected IRVisitor {
   }
   /// Check if a given DLDeviceType/TVMDeviceExtType value denotes FPGA device.
   static bool IsFPGADevice(int dev_type) {
-    return kDLSDAccel == dev_type;
+    return kDLSDAccel == dev_type || kDLAOCL == dev_type;
   }
 
  private:
diff --git a/src/runtime/c_runtime_api.cc b/src/runtime/c_runtime_api.cc
index 7a7d7ab9f4db..916dfadecb4c 100644
--- a/src/runtime/c_runtime_api.cc
+++ b/src/runtime/c_runtime_api.cc
@@ -32,6 +32,7 @@ inline std::string DeviceName(int type) {
     case kDLGPU: return "gpu";
     case kDLOpenCL: return "opencl";
     case kDLSDAccel: return "sdaccel";
+    case kDLAOCL: return "aocl";
     case kDLVulkan: return "vulkan";
     case kDLMetal: return "metal";
     case kDLVPI: return "vpi";
diff --git a/src/runtime/opencl/aocl/aocl_common.h b/src/runtime/opencl/aocl/aocl_common.h
new file mode 100644
index 000000000000..234053098d1d
--- /dev/null
+++ b/src/runtime/opencl/aocl/aocl_common.h
@@ -0,0 +1,42 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file aocl_common.h
+ * \brief AOCL common header
+ */
+#ifndef TVM_RUNTIME_OPENCL_AOCL_AOCL_COMMON_H_
+#define TVM_RUNTIME_OPENCL_AOCL_AOCL_COMMON_H_
+
+#include "../opencl_common.h"
+
+namespace tvm {
+namespace runtime {
+namespace cl {
+
+/*!
+ * \brief Process global AOCL workspace.
+ */
+class AOCLWorkspace final : public OpenCLWorkspace {
+ public:
+  // override OpenCL device API
+  void Init() final;
+  bool IsOpenCLDevice(TVMContext ctx) final;
+  OpenCLThreadEntry* GetThreadEntry() final;
+  // get the global workspace
+  static const std::shared_ptr<OpenCLWorkspace>& Global();
+};
+
+
+/*! \brief Thread local workspace for AOCL */
+class AOCLThreadEntry : public OpenCLThreadEntry {
+ public:
+  // constructor
+  AOCLThreadEntry()
+      : OpenCLThreadEntry(static_cast<DLDeviceType>(kDLAOCL), AOCLWorkspace::Global()) {}
+
+  // get the global workspace
+  static AOCLThreadEntry* ThreadLocal();
+};
+}  // namespace cl
+}  // namespace runtime
+}  // namespace tvm
+#endif  // TVM_RUNTIME_OPENCL_AOCL_AOCL_COMMON_H_
diff --git a/src/runtime/opencl/aocl/aocl_device_api.cc b/src/runtime/opencl/aocl/aocl_device_api.cc
new file mode 100644
index 000000000000..e9cbc6b4cda0
--- /dev/null
+++ b/src/runtime/opencl/aocl/aocl_device_api.cc
@@ -0,0 +1,44 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file aocl_device_api.cc
+ */
+#include <tvm/runtime/registry.h>
+#include <dmlc/thread_local.h>
+#include "./aocl_common.h"
+
+namespace tvm {
+namespace runtime {
+namespace cl {
+
+OpenCLThreadEntry* AOCLWorkspace::GetThreadEntry() {
+  return AOCLThreadEntry::ThreadLocal();
+}
+
+const std::shared_ptr<OpenCLWorkspace>& AOCLWorkspace::Global() {
+  static std::shared_ptr<OpenCLWorkspace> inst = std::make_shared<AOCLWorkspace>();
+  return inst;
+}
+
+void AOCLWorkspace::Init() {
+  OpenCLWorkspace::Init("aocl", "accelerator", "Intel(R) FPGA SDK for OpenCL(TM)");
+}
+
+bool AOCLWorkspace::IsOpenCLDevice(TVMContext ctx) {
+  return ctx.device_type == static_cast<DLDeviceType>(kDLAOCL);
+}
+
+typedef dmlc::ThreadLocalStore<AOCLThreadEntry> AOCLThreadStore;
+
+AOCLThreadEntry* AOCLThreadEntry::ThreadLocal() {
+  return AOCLThreadStore::Get();
+}
+
+TVM_REGISTER_GLOBAL("device_api.aocl")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    DeviceAPI* ptr = AOCLWorkspace::Global().get();
+    *rv = static_cast<void*>(ptr);
+  });
+
+}  // namespace cl
+}  // namespace runtime
+}  // namespace tvm
diff --git a/src/runtime/opencl/aocl/aocl_module.cc b/src/runtime/opencl/aocl/aocl_module.cc
new file mode 100644
index 000000000000..a056c5cee671
--- /dev/null
+++ b/src/runtime/opencl/aocl/aocl_module.cc
@@ -0,0 +1,58 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file aocl_module.cc
+ */
+#include <dmlc/memory_io.h>
+#include <tvm/runtime/registry.h>
+#include <vector>
+#include <string>
+#include <unordered_map>
+#include "./aocl_common.h"
+#include "./aocl_module.h"
+
+namespace tvm {
+namespace runtime {
+
+class AOCLModuleNode : public OpenCLModuleNode {
+ public:
+  explicit AOCLModuleNode(std::string data,
+                          std::string fmt,
+                          std::unordered_map<std::string, FunctionInfo> fmap,
+                          std::string source)
+      : OpenCLModuleNode(data, fmt, fmap, source) {}
+  const std::shared_ptr<cl::OpenCLWorkspace>& GetGlobalWorkspace() final;
+};
+
+const std::shared_ptr<cl::OpenCLWorkspace>& AOCLModuleNode::GetGlobalWorkspace() {
+  return cl::AOCLWorkspace::Global();
+}
+
+Module AOCLModuleCreate(
+    std::string data,
+    std::string fmt,
+    std::unordered_map<std::string, FunctionInfo> fmap,
+    std::string source) {
+  std::shared_ptr<AOCLModuleNode> n =
+      std::make_shared<AOCLModuleNode>(data, fmt, fmap, source);
+  n->Init();
+  return Module(n);
+}
+
+Module AOCLModuleLoadFile(const std::string& file_name,
+                          const std::string& format) {
+  std::string data;
+  std::unordered_map<std::string, FunctionInfo> fmap;
+  std::string fmt = GetFileFormat(file_name, format);
+  std::string meta_file = GetMetaFilePath(file_name);
+  LoadBinaryFromFile(file_name, &data);
+  LoadMetaDataFromFile(meta_file, &fmap);
+  return AOCLModuleCreate(data, fmt, fmap, std::string());
+}
+
+TVM_REGISTER_GLOBAL("module.loadfile_aocx")
+.set_body([](TVMArgs args, TVMRetValue* rv) {
+    *rv = AOCLModuleLoadFile(args[0], args[1]);
+  });
+
+}  // namespace runtime
+}  // namespace tvm
diff --git a/src/runtime/opencl/aocl/aocl_module.h b/src/runtime/opencl/aocl/aocl_module.h
new file mode 100644
index 000000000000..83ddbdb358ce
--- /dev/null
+++ b/src/runtime/opencl/aocl/aocl_module.h
@@ -0,0 +1,31 @@
+/*!
+ *  Copyright (c) 2018 by Contributors
+ * \file aocl_module.h
+ * \brief Execution handling of OpenCL kernels for AOCL
+ */
+#ifndef TVM_RUNTIME_OPENCL_AOCL_AOCL_MODULE_H_
+#define TVM_RUNTIME_OPENCL_AOCL_AOCL_MODULE_H_
+
+#include <tvm/runtime/packed_func.h>
+#include <memory>
+#include <vector>
+#include <string>
+#include "../../meta_data.h"
+
+namespace tvm {
+namespace runtime {
+/*!
+ * \brief create a opencl module for AOCL from data.
+ *
+ * \param data The module data.
+ * \param fmt The format of the data, can be "aocx"
+ * \param fmap The map function information map of each function.
+ */
+Module AOCLModuleCreate(
+    std::string data,
+    std::string fmt,
+    std::unordered_map<std::string, FunctionInfo> fmap,
+    std::string source);
+}  // namespace runtime
+}  // namespace tvm
+#endif  // TVM_RUNTIME_OPENCL_AOCL_AOCL_MODULE_H_
diff --git a/src/runtime/opencl/opencl_module.cc b/src/runtime/opencl/opencl_module.cc
index aa51f17d4021..3efd789513ba 100644
--- a/src/runtime/opencl/opencl_module.cc
+++ b/src/runtime/opencl/opencl_module.cc
@@ -191,7 +191,7 @@ cl_kernel OpenCLModuleNode::InstallKernel(cl::OpenCLWorkspace* w,
         program_ = clCreateProgramWithSource(w->context, 1, &s, &len, &err);
         OPENCL_CHECK_ERROR(err);
       }
-    } else if (fmt_ == "xclbin" || fmt_ == "awsxclbin") {
+    } else if (fmt_ == "xclbin" || fmt_ == "awsxclbin" || fmt_ == "aocx") {
       const unsigned char* s = (const unsigned char *)data_.c_str();
       size_t len = data_.length();
       cl_int err;
diff --git a/tests/python/integration/test_ewise_fpga.py b/tests/python/integration/test_ewise_fpga.py
index c0a568faca2d..0abefff02778 100644
--- a/tests/python/integration/test_ewise_fpga.py
+++ b/tests/python/integration/test_ewise_fpga.py
@@ -3,6 +3,7 @@
 import os
 
 os.environ["XCL_EMULATION_MODE"] = "1"
+os.environ["CL_CONTEXT_EMULATOR_DEVICE_INTELFPGA"] = "1"
 
 @tvm.register_func
 def tvm_callback_vhls_postproc(code):
@@ -43,6 +44,7 @@ def check_device(device, host="llvm"):
     if "AWS_PLATFORM" in os.environ:
         check_device("sdaccel -device=" + os.environ.get("AWS_PLATFORM"))
 
+    check_device("aocl -device=s5_ref -mattr=emulator")
 
 def test_multi_kernel():
     # graph
@@ -80,6 +82,7 @@ def check_device(device, host="llvm"):
             d.asnumpy(), a.asnumpy() * 2 + b.asnumpy(), rtol=1e-5)
 
     check_device("sdaccel")
+    check_device("aocl -device=s5_ref -mattr=emulator")
 
 
 if __name__ == "__main__":

From 79efda8c95a331d955cc1b2a52f255d121c4ffd1 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Tue, 31 Jul 2018 10:37:03 -0700
Subject: [PATCH 931/949] update halideIR (#1515)

---
 HalideIR | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/HalideIR b/HalideIR
index a5a80bdc8232..a0b9563f4571 160000
--- a/HalideIR
+++ b/HalideIR
@@ -1 +1 @@
-Subproject commit a5a80bdc8232c9dbfe508bb5c46e8f58cdf7ec20
+Subproject commit a0b9563f45719553adf4d39fe3c14db1af0e1f40

From cd0dda25970e235696d4ab210432a9f865bb9606 Mon Sep 17 00:00:00 2001
From: Sergei Grechanik <grechanik.sergey@huawei.com>
Date: Tue, 31 Jul 2018 23:35:38 +0300
Subject: [PATCH 932/949] [NNVM] Fix gradients for broadcast_div (#1512)

---
 nnvm/src/top/tensor/broadcast.cc              | 3 +--
 nnvm/tests/python/compiler/test_top_level4.py | 2 +-
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/nnvm/src/top/tensor/broadcast.cc b/nnvm/src/top/tensor/broadcast.cc
index edf209e3e9ec..a3ac2df93f56 100644
--- a/nnvm/src/top/tensor/broadcast.cc
+++ b/nnvm/src/top/tensor/broadcast.cc
@@ -337,8 +337,7 @@ Example::
       });
     NodeEntry dy = MakeNode("broadcast_div", n->attrs.name + "_drhs_div", {
         NodeEntry{n, 0, 0},
-        MakeNode("__mul_scalar__", n->attrs.name + "_rhs_by_two",
-                 {n->inputs[1]}, {{"scalar", "2"}})
+        MakeNode("negative", n->attrs.name + "_rhs_neg", {n->inputs[1]})
       });
     NodeEntry drhs = MakeNode("collapse_sum", n->attrs.name + "_drhs_sum", {
         MakeNode("broadcast_mul", n->attrs.name + "_drhs_mul", { dy, ograds[0] }),
diff --git a/nnvm/tests/python/compiler/test_top_level4.py b/nnvm/tests/python/compiler/test_top_level4.py
index b7f759c62f99..5bf134b49a7b 100644
--- a/nnvm/tests/python/compiler/test_top_level4.py
+++ b/nnvm/tests/python/compiler/test_top_level4.py
@@ -268,7 +268,7 @@ def _backward_mul(head_grads, a, b):
     y = sym.broadcast_div(a, b)
     def _backward_div(head_grads, a, b):
         da = head_grads / b
-        db = _collapse(head_grads * a / (2 * b**2))
+        db = _collapse(- head_grads * a / b**2)
         return da, db
     helper(y, inputs, dtype, lambda a, b: a / b, _backward_div)
 

From a77006e429770a4c55bdf2a88f21cf9f16b795fe Mon Sep 17 00:00:00 2001
From: Thierry Moreau <moreau@cs.washington.edu>
Date: Wed, 1 Aug 2018 08:56:01 -0700
Subject: [PATCH 933/949] [VTA] bugfix parameter derivation (#1521)

---
 vta/config/vta_config.py      | 6 ++++--
 vta/python/vta/environment.py | 2 +-
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/vta/config/vta_config.py b/vta/config/vta_config.py
index b9c784a71d34..69c21fe80940 100644
--- a/vta/config/vta_config.py
+++ b/vta/config/vta_config.py
@@ -86,8 +86,10 @@ def main():
     if not ok_path_list:
         raise RuntimeError("Cannot find config in %s" % str(path_list))
     cfg = json.load(open(ok_path_list[0]))
-    cfg["LOG_OUT_WIDTH"] = cfg["LOG_INP_WIDTH"]
-    cfg["LOG_OUT_BUFF_SIZE"] = cfg["LOG_ACC_BUFF_SIZE"] + cfg["LOG_ACC_WIDTH"] - cfg["LOG_OUT_WIDTH"]
+    cfg["LOG_OUT_BUFF_SIZE"] = (
+        cfg["LOG_ACC_BUFF_SIZE"] +
+        cfg["LOG_OUT_WIDTH"] -
+        cfg["LOG_ACC_WIDTH"])
     pkg = get_pkg_config(cfg)
 
     if args.target:
diff --git a/vta/python/vta/environment.py b/vta/python/vta/environment.py
index e97b6d87f891..a77e29ac3a52 100644
--- a/vta/python/vta/environment.py
+++ b/vta/python/vta/environment.py
@@ -110,7 +110,7 @@ def __init__(self, cfg):
         self.INP_WIDTH = 1 << self.LOG_INP_WIDTH
         self.WGT_WIDTH = 1 << self.LOG_WGT_WIDTH
         self.ACC_WIDTH = 1 << self.LOG_ACC_WIDTH
-        self.OUT_WIDTH = self.INP_WIDTH
+        self.OUT_WIDTH = 1 << self.LOG_OUT_WIDTH
         # tensor intrinsic shape
         self.BATCH = 1 << self.LOG_BATCH
         self.BLOCK_IN = 1 << self.LOG_BLOCK_IN

From 40bf5e6e6c0a23011ec166518c3d9d674cec522a Mon Sep 17 00:00:00 2001
From: Tatsuya Nishiyama <nishiyama.tatsuya0@gmail.com>
Date: Thu, 2 Aug 2018 01:57:57 +0900
Subject: [PATCH 934/949] [TVM][CUDA] NVIDIA GPU Int8 Support (#1503)

---
 src/codegen/codegen_cuda.cc                | 20 +++++--
 src/codegen/codegen_cuda.h                 |  4 +-
 src/codegen/opt/build_cuda_on.cc           |  3 -
 tests/python/unittest/test_codegen_cuda.py | 64 +++++++++++++++++++++-
 4 files changed, 81 insertions(+), 10 deletions(-)

diff --git a/src/codegen/codegen_cuda.cc b/src/codegen/codegen_cuda.cc
index 38c559ba2ba5..70ab807c9509 100644
--- a/src/codegen/codegen_cuda.cc
+++ b/src/codegen/codegen_cuda.cc
@@ -34,6 +34,10 @@ std::string CodeGenCUDA::Finish() {
     decl_stream << "#include <cuda_fp16.h>\n";
   }
 
+  if (enable_int8_) {
+    decl_stream << "#include <sm_61_intrinsics.h>\n";
+  }
+
   return CodeGenC::Finish();
 }
 
@@ -81,13 +85,19 @@ void CodeGenCUDA::PrintType(Type t, std::ostream& os) {  // NOLINT(*)
         os << "unsigned ";
       }
     }
-    if (t.bits() == 8 && t.lanes() == 4) {
-      // directly 4 8 bit int in integer.
-      os << "int"; return;
-    }
     switch (t.bits()) {
       case 8: {
-        if (!t.is_uint() && t.lanes() == 1) {
+        if (t.lanes() == 4) {
+          // directly 4 8 bit int in integer.
+          enable_int8_ = true;
+          os << "char4"; return;
+        } else if (t.lanes() == 8) {
+          enable_int8_ = true;
+          os << "int2"; return;
+        } else if (t.lanes() == 16) {
+          enable_int8_ = true;
+          os << "int4"; return;
+        } else if (!t.is_uint() && t.lanes() == 1) {
           os << "signed char"; break;
         } else {
           os << "char"; break;
diff --git a/src/codegen/codegen_cuda.h b/src/codegen/codegen_cuda.h
index dd559f5232f9..f5d9861ec6b2 100644
--- a/src/codegen/codegen_cuda.h
+++ b/src/codegen/codegen_cuda.h
@@ -20,7 +20,7 @@ class CodeGenCUDA final : public CodeGenC {
   void Init(bool output_ssa);
   void AddFunction(LoweredFunc f);
   std::string Finish();
-  bool need_include_path() { return enable_fp16_; }
+  bool need_include_path() { return (enable_fp16_ || enable_int8_); }
   // override behavior
   void VisitStmt_(const ir::For* op) final;
   void PrintStorageSync(const Call* op) final;
@@ -49,6 +49,8 @@ class CodeGenCUDA final : public CodeGenC {
   std::string vid_global_barrier_expect_;
   // whether enable fp16
   bool enable_fp16_{false};
+  // whether enable int8
+  bool enable_int8_{false};
 };
 
 }  // namespace codegen
diff --git a/src/codegen/opt/build_cuda_on.cc b/src/codegen/opt/build_cuda_on.cc
index 72fd38b925e5..2e5766f53b76 100644
--- a/src/codegen/opt/build_cuda_on.cc
+++ b/src/codegen/opt/build_cuda_on.cc
@@ -64,7 +64,6 @@ std::string FindCUDAIncludePath() {
 std::string NVRTCCompile(const std::string& code, bool include_path = false) {
   std::vector<std::string> compile_params;
   std::vector<const char*> param_cstrings{};
-  int num_options = 0;
   nvrtcProgram prog;
   cudaDeviceProp device_prop;
   std::string cc = "30";
@@ -78,13 +77,11 @@ std::string NVRTCCompile(const std::string& code, bool include_path = false) {
   }
 
   compile_params.push_back("-arch=compute_" + cc);
-  num_options++;
 
   if (include_path) {
     std::string include_option = "--include-path=" + FindCUDAIncludePath();
 
     compile_params.push_back(include_option);
-    num_options++;
   }
 
   for (const auto& string : compile_params) {
diff --git a/tests/python/unittest/test_codegen_cuda.py b/tests/python/unittest/test_codegen_cuda.py
index 044c40696e59..664883528efc 100644
--- a/tests/python/unittest/test_codegen_cuda.py
+++ b/tests/python/unittest/test_codegen_cuda.py
@@ -1,6 +1,7 @@
 import tvm
 import numpy as np
-from tvm.contrib.nvcc import have_fp16
+from tvm.contrib.nvcc import have_fp16, have_int8
+from tvm.contrib import nvcc
 
 def test_cuda_vectorize_add():
     num_thread = 8
@@ -11,6 +12,9 @@ def check_cuda(dtype, n, lanes):
         if dtype == "float16" and not have_fp16(tvm.gpu(0).compute_version):
             print("skip because gpu does not support fp16")
             return
+        if dtype == "int8" and not have_int8(tvm.gpu(0).compute_version):
+            print("skip because gpu does not support int8")
+            return
         A = tvm.placeholder((n,), name='A', dtype="%sx%d" % (dtype, lanes))
         B = tvm.compute((n,), lambda i: A[i]+tvm.const(1, A.dtype), name='B')
         s = tvm.create_schedule(B.op)
@@ -27,6 +31,64 @@ def check_cuda(dtype, n, lanes):
         
     check_cuda("float32", 64, 2)
     check_cuda("float16", 64, 2)
+    check_cuda("int8", 64, 4)
+
+
+def test_cuda_multiply_add():
+    num_thread = 8
+    def check_cuda(dtype, n, lanes):
+        if not tvm.gpu(0).exist or not tvm.module.enabled("cuda"):
+            print("skip because cuda is not enabled..")
+            return
+        if dtype == "int8" and not have_int8(tvm.gpu(0).compute_version):
+            print("skip because gpu does not support int8")
+            return
+        A = tvm.placeholder((n,), name='A', dtype="%sx%d" % (dtype, lanes))
+        B = tvm.placeholder((n,), name='B', dtype="%sx%d" % (dtype, lanes))
+        C = tvm.placeholder((n,), name='C', dtype="int32")        
+        D = tvm.compute((n,),
+                        lambda i: tvm.call_pure_extern("int32", "__dp4a", A[i], B[i], C[i]), name='D')
+        s = tvm.create_schedule(D.op)
+        xo, xi = s[D].split(D.op.axis[0], factor=num_thread)
+        s[D].bind(xo, tvm.thread_axis("blockIdx.x"))
+        s[D].bind(xi, tvm.thread_axis("threadIdx.x"))
+        fun = tvm.build(s, [A, B, C, D], "cuda")
+        np_a = np.random.randint(low=-128, high=127, size=(n,lanes))
+        np_b = np.random.randint(low=-128, high=127, size=(n,lanes))
+        np_c = np.random.randint(low=0, high=127, size=(n,))
+        np_d = [sum(x * y) + z for x, y, z in zip(np_a, np_b, np_c)]
+        ctx = tvm.gpu(0)
+        a = tvm.nd.empty((n,), A.dtype, ctx).copyfrom(np_a)
+        b = tvm.nd.empty((n,), B.dtype, ctx).copyfrom(np_b)
+        c = tvm.nd.empty((n,), C.dtype, ctx).copyfrom(np_c)
+        d = tvm.nd.empty((n,), D.dtype, ctx)
+        fun(a, b, c, d)
+        np.testing.assert_allclose(d.asnumpy(), np_d)
+    check_cuda("int8", 64, 4)
+
+def test_cuda_vectorize_load():
+    num_thread = 8
+    def check_cuda(dtype, n, lanes):
+        if not tvm.gpu(0).exist or not tvm.module.enabled("cuda"):
+            print("skip because cuda is not enabled..")
+            return
+        ctx = tvm.gpu(0)
+        A = tvm.placeholder((n,), name='A', dtype="%sx%d" % (dtype, lanes))
+        B = tvm.compute((n,), lambda i: A[i], name='B')
+        s = tvm.create_schedule(B.op)
+        bx, tx = s[B].split(B.op.axis[0], factor=num_thread)
+        s[B].bind(bx, tvm.thread_axis("blockIdx.x"))
+        s[B].bind(tx, tvm.thread_axis("threadIdx.x"))
+        fun = tvm.build(s, [A, B], "cuda", name="vector_load")
+        np_a = np.random.randint(low=-128, high=127, size=(n,lanes))
+        a = tvm.nd.empty((n,), A.dtype, ctx).copyfrom(np_a)
+        b = tvm.nd.empty((n,), B.dtype, ctx)
+        fun(a,b)
+        np.testing.assert_allclose(a.asnumpy(), b.asnumpy())
+    check_cuda("int8", 64, 8)
+    check_cuda("int8", 64, 16)
 
 if __name__ == "__main__":
     test_cuda_vectorize_add()
+    test_cuda_multiply_add()
+    test_cuda_load_store()

From da759c447ac9c1545b823456a19da06e5060ff67 Mon Sep 17 00:00:00 2001
From: MORITA Kazutaka <morita.kazutaka@lab.ntt.co.jp>
Date: Thu, 2 Aug 2018 01:59:54 +0900
Subject: [PATCH 935/949] [NNVM] remove keepdims from expand_like arguments
 (#1517)

---
 nnvm/src/top/tensor/reduce.cc | 2 --
 1 file changed, 2 deletions(-)

diff --git a/nnvm/src/top/tensor/reduce.cc b/nnvm/src/top/tensor/reduce.cc
index 8e72391ac4d0..d8f426b4f4bc 100644
--- a/nnvm/src/top/tensor/reduce.cc
+++ b/nnvm/src/top/tensor/reduce.cc
@@ -208,7 +208,6 @@ NNVM_REGISTER_REDUCE_OP(max)
     NodeEntry sub0 = MakeNode("expand_like", n->attrs.name + "_grad_sub0",
                              {ograds[0], n->inputs[0]},
                              {{"axis", axis.str()},
-                              {"keepdims", std::to_string(param.keepdims)},
                               {"exclude", std::to_string(param.exclude)}});
     NodeEntry sub1 = MakeNode("_max_mask", n->attrs.name + "_grad_sub1",
                               {ograds[0]},
@@ -242,7 +241,6 @@ NNVM_REGISTER_REDUCE_OP(min)
     NodeEntry sub0 = MakeNode("expand_like", n->attrs.name + "_grad_sub0",
                               {ograds[0], n->inputs[0]},
                               {{"axis", axis.str()},
-                               {"keepdims", std::to_string(param.keepdims)},
                                {"exclude", std::to_string(param.exclude)}});
     NodeEntry sub1 = MakeNode("_min_mask", n->attrs.name + "_grad_sub1",
                               {ograds[0]},

From 6ff24cbf0d75d496668d8f6955b11644cfaec1c0 Mon Sep 17 00:00:00 2001
From: abergeron <bergearn@iro.umontreal.ca>
Date: Wed, 1 Aug 2018 15:27:49 -0400
Subject: [PATCH 936/949] Add some missing operators (#1524)

---
 nnvm/python/nnvm/top/tensor.py  |  8 ++++++++
 nnvm/src/top/tensor/elemwise.cc | 24 ++++++++++++++++++++++++
 2 files changed, 32 insertions(+)

diff --git a/nnvm/python/nnvm/top/tensor.py b/nnvm/python/nnvm/top/tensor.py
index 9a8caab29479..e0214d6ddf16 100644
--- a/nnvm/python/nnvm/top/tensor.py
+++ b/nnvm/python/nnvm/top/tensor.py
@@ -156,6 +156,14 @@ def _compute(attrs, x, _):
 reg.register_pattern("elemwise_div", OpPattern.BROADCAST)
 reg.register_schedule("elemwise_div", _fschedule_broadcast)
 
+# elemwise_mod
+reg.register_pattern("elemwise_mod", OpPattern.BROADCAST)
+reg.register_schedule("elemwise_mod", _fschedule_broadcast)
+
+# elemwise_pow
+reg.register_pattern("elemwise_pow", OpPattern.BROADCAST)
+reg.register_schedule("elemwise_pow", _fschedule_broadcast)
+
 # broadcast_add
 reg.register_pattern("broadcast_add", OpPattern.BROADCAST)
 reg.register_schedule("broadcast_add", _fschedule_broadcast)
diff --git a/nnvm/src/top/tensor/elemwise.cc b/nnvm/src/top/tensor/elemwise.cc
index 403e08f95653..239f44783392 100644
--- a/nnvm/src/top/tensor/elemwise.cc
+++ b/nnvm/src/top/tensor/elemwise.cc
@@ -337,6 +337,30 @@ NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_div)
     };
 });
 
+NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_mod)
+  .describe(R"code(Element-wise modulo
+
+)code" NNVM_ADD_FILELINE)
+.set_support_level(1)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+      return Array<Tensor>{ topi::mod(inputs[0], inputs[1]) };
+});
+
+NNVM_REGISTER_ELEMWISE_BINARY_OP(elemwise_pow)
+  .describe(R"code(Element-wise power
+
+)code" NNVM_ADD_FILELINE)
+.set_support_level(1)
+.set_attr<FTVMCompute>(
+  "FTVMCompute", [](const NodeAttrs& attrs,
+                    const Array<Tensor>& inputs,
+                    const Array<Tensor>& out_info) {
+      return Array<Tensor>{ topi::power(inputs[0], inputs[1]) };
+});
+
 // negative
 NNVM_REGISTER_ELEMWISE_UNARY_OP(negative)
 .describe(R"code(Elemenwise numeric negative

From 961ec04e13504d875708db25cd05b2a0dc642bf6 Mon Sep 17 00:00:00 2001
From: Siju <sijusamuel@gmail.com>
Date: Thu, 2 Aug 2018 03:09:52 +0530
Subject: [PATCH 937/949] Onnx Gather operator added (#1513)

---
 nnvm/python/nnvm/frontend/onnx.py             | 16 ++++++++--
 .../python/frontend/onnx/test_forward.py      | 29 +++++++++++++++++++
 2 files changed, 43 insertions(+), 2 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/onnx.py b/nnvm/python/nnvm/frontend/onnx.py
index ee7fcc3c57fd..cfef11d6a106 100644
--- a/nnvm/python/nnvm/frontend/onnx.py
+++ b/nnvm/python/nnvm/frontend/onnx.py
@@ -446,7 +446,6 @@ def _impl_v1(cls, inputs, attr, params):
             inputs[0] = _sym.expand_dims(inputs[0], axis=axes, num_newaxis=1)
         return inputs[0]
 
-
 class Slice(OnnxOpConverter):
     """ Operator converter for Slice.
     """
@@ -487,6 +486,19 @@ def _impl_v1(cls, inputs, attr, params):
                                    'ends': 'end'},
                        ignores=['axes'])(inputs, attr)
 
+class Gather(OnnxOpConverter):
+    """ Operator converter for Gather.
+    """
+
+    @classmethod
+    def _impl_v1(cls, inputs, attr, params):
+        axis = attr['axis']
+        indices = np.array(attr['indices'], dtype='int32')
+        name = 'gather_indices'
+        gather_indices = _sym.Variable(name=name, init=indices)
+        params[name] = indices
+        return _sym.take(inputs[0], gather_indices, axis=axis)
+
 # compatible operators that do NOT require any conversion.
 _identity_list = []
 
@@ -593,7 +605,7 @@ def _get_convert_map(opset):
         'Split': AttrCvt('split', {'split': 'indices_or_sections'}),
         'Slice': Slice.get_converter(opset),
         'Transpose': AttrCvt('transpose', {'perm': 'axes'}),
-        # 'Gather'
+        'Gather': Gather.get_converter(opset),
         'Squeeze': Renamer('squeeze'),
         'Unsqueeze': Unsqueeze.get_converter(opset),
         'Pad': Pad.get_converter(opset),
diff --git a/nnvm/tests/python/frontend/onnx/test_forward.py b/nnvm/tests/python/frontend/onnx/test_forward.py
index 56f1e006265a..bddf4a87009c 100644
--- a/nnvm/tests/python/frontend/onnx/test_forward.py
+++ b/nnvm/tests/python/frontend/onnx/test_forward.py
@@ -189,6 +189,34 @@ def test_unsqueeze():
 
     np.testing.assert_allclose(out_shape, tvm_out.shape)
 
+def verify_gather(in_shape, indices, axis=0):
+    indices_src = np.array(indices, dtype="int32")
+
+    x = np.random.uniform(size=in_shape)
+    out_np = np.take(x, indices_src, axis=axis)
+
+    y = helper.make_node("Gather", ['in'], ['out'], indices=indices, axis=axis)
+
+    graph = helper.make_graph([y],
+                              'gather_test',
+                              inputs = [helper.make_tensor_value_info("in",
+                                  TensorProto.FLOAT, list(in_shape))],
+                              outputs = [helper.make_tensor_value_info("out",
+                                  TensorProto.FLOAT, list(out_np.shape))])
+
+    model = helper.make_model(graph, producer_name='gather_test')
+
+    for target, ctx in ctx_list():
+        tvm_out = get_tvm_output(model, x, target, ctx, out_np.shape, 'float32')
+
+    np.testing.assert_allclose(out_np, tvm_out)
+
+def test_gather():
+    verify_gather((4,), [1])
+    verify_gather((4,), [0, 1, 2, 3])
+    verify_gather((4, 2), [1], 1)
+    verify_gather((4, 3, 5, 6), [2, 1, 0, 0], -2)
+
 def _test_slice_iteration(indata, outdata, starts, ends, axes=None):
     if axes:
         y = helper.make_node("Slice", ['in'], ['out'], axes=axes, starts=starts, ends=ends)
@@ -299,3 +327,4 @@ def test_matmul():
     test_ceil()
     test_clip()
     test_matmul()
+    test_gather()

From 54f5adc3e37fae0851d70c1bd2068ac5ba6562a3 Mon Sep 17 00:00:00 2001
From: Tatsuya Nishiyama <nishiyama.tatsuya0@gmail.com>
Date: Thu, 2 Aug 2018 14:35:20 +0900
Subject: [PATCH 938/949] Fix a testcase name in test_codegen_cuda (#1526)

---
 tests/python/unittest/test_codegen_cuda.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/python/unittest/test_codegen_cuda.py b/tests/python/unittest/test_codegen_cuda.py
index 664883528efc..b0ed92a30281 100644
--- a/tests/python/unittest/test_codegen_cuda.py
+++ b/tests/python/unittest/test_codegen_cuda.py
@@ -91,4 +91,4 @@ def check_cuda(dtype, n, lanes):
 if __name__ == "__main__":
     test_cuda_vectorize_add()
     test_cuda_multiply_add()
-    test_cuda_load_store()
+    test_cuda_vectorize_load()

From 9354aa29e66af72c5d027a486ccbfe31952ae752 Mon Sep 17 00:00:00 2001
From: Dayananda V <dayanandasiet@gmail.com>
Date: Thu, 2 Aug 2018 15:29:15 +0530
Subject: [PATCH 939/949] Tensorflow Split and LRN operator contribution

split and lrn operator contribute inaddition to run alexnet on tensorflow(https://www.cs.toronto.edu/~guerzhoy/tf_alexnet/)
---
 nnvm/python/nnvm/frontend/tensorflow.py       | 109 +++++++++++++-----
 .../frontend/tensorflow/test_forward.py       |  68 +++++++++++
 2 files changed, 150 insertions(+), 27 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/tensorflow.py b/nnvm/python/nnvm/frontend/tensorflow.py
index 27f19a1e177a..91ed969d0a43 100644
--- a/nnvm/python/nnvm/frontend/tensorflow.py
+++ b/nnvm/python/nnvm/frontend/tensorflow.py
@@ -91,6 +91,26 @@ def _impl(inputs, attr, *args):
         return AttrCvt(op_name="__pow_scalar__", extras={'scalar': -0.5})(inputs, attr)
     return _impl
 
+def _local_response_normalization():
+    def _impl(inputs, attr, params):
+        depth_radius = attr.get('depth_radius', 2)
+        depth_radius = depth_radius * 2 + 1
+        axis = 3 #default NHWC
+        return AttrCvt(op_name="lrn",
+                       ignores=['depth_radius'],
+                       extras={'size': depth_radius, 'axis': axis})(inputs, attr)
+    return _impl
+
+def _split():
+    def _impl(inputs, attr, params):
+        pop_node = inputs.pop(0)
+        axis = params[pop_node.list_output_names()[0]].asnumpy()[0]
+        return AttrCvt(op_name="split",
+                       ignores=['num_split'],
+                       extras={'indices_or_sections':attr['num_split'],
+                               'axis': axis})(inputs, attr)
+    return _impl
+
 def _elemwise(name):
     def _impl(inputs, attr, *args):
         assert len(inputs) == 2, "Math op take 2 inputs, {} given".format(len(inputs))
@@ -158,17 +178,26 @@ def _conv():
     def _impl(inputs, attr, params):
         attr['data_format'] = attr['data_format'].decode("utf-8")
 
+        input_shapes = attr['_input_shapes'][inputs[0]]
+        if len(input_shapes) == 1:
+            input_shapes = input_shapes[0]
+
         # Extract kernel shape from params
-        conv_param_weights = params[inputs[1].list_output_names()[0]]
+        if inputs[1] in attr['_input_shapes']:
+            conv_param_weights = tuple(attr['_input_shapes'][inputs[1]])
+            if len(conv_param_weights) == 1:
+                conv_param_weights = conv_param_weights[0]
+        else:
+            conv_param_weights = params[inputs[1].list_output_names()[0]].shape
 
         if attr['data_format'] == 'NHWC':
-            attr['kernel_shape'] = (conv_param_weights.shape[0], conv_param_weights.shape[1])
-            attr['channels'] = conv_param_weights.shape[3]
+            attr['kernel_shape'] = (conv_param_weights[0], conv_param_weights[1])
+            attr['channels'] = conv_param_weights[3]
             if 'dilations' in attr:
                 attr['dilations'] = (attr['dilations'][0], attr['dilations'][1])
         elif attr['data_format'] == 'NCHW':
-            attr['kernel_shape'] = (conv_param_weights.shape[2], conv_param_weights.shape[3])
-            attr['channels'] = conv_param_weights.shape[1]
+            attr['kernel_shape'] = (conv_param_weights[2], conv_param_weights[3])
+            attr['channels'] = conv_param_weights[1]
             if 'dilations' in attr:
                 attr['dilations'] = (attr['dilations'][2], attr['dilations'][3])
         else:
@@ -178,7 +207,6 @@ def _impl(inputs, attr, params):
         attr['strides'] = (attr['strides'][1], attr['strides'][2])
 
         # Fix padding
-        input_shapes = attr['_input_shapes'][inputs[0]]
         attr['padding'] = attr['padding'].decode("utf-8")
 
         if attr['padding'] == 'VALID':
@@ -187,11 +215,11 @@ def _impl(inputs, attr, params):
             stride_h, stride_w = attr['strides']
             kernel_h, kernel_w = attr['kernel_shape']
             if attr['data_format'] == 'NHWC':
-                in_h = input_shapes[0][1]
-                in_w = input_shapes[0][2]
+                in_h = input_shapes[1]
+                in_w = input_shapes[2]
             else:
-                in_h = input_shapes[0][2]
-                in_w = input_shapes[0][3]
+                in_h = input_shapes[2]
+                in_w = input_shapes[3]
 
             pad_v = _get_pad_pair(in_h, kernel_h, stride_h)
             pad_h = _get_pad_pair(in_w, kernel_w, stride_w)
@@ -680,6 +708,8 @@ def _impl(inputs, in_state_c, in_state_h, attr, params):
     'Fill'                              : _fill(),
     'GatherV2'                          : _gather_v2(),
     'StridedSlice'                      : _stridedSlice(),
+    'LRN'                               : _local_response_normalization(),
+    'Split'                             : _split(),
 }
 
 # _convert_map_rnn defines maps of rnn operator name to
@@ -982,23 +1012,48 @@ def from_tensorflow(self, graph):
                 # Pass the node name too in attr
                 attr["_node_name"] = node.name
 
-                #ToDo: Some of the tensorflow operators maintain internaly maintain
-                #execution layers and its output name will the layer number along with
-                #graph node name.eg: Node name:- 'Model/RNN/cell_0/RnnCell', but the
-                #output name will be 'Model/RNN/cell_0/RnnCell:0'. In this case,
-                #the digit has to be ignored.
-                if ":" in node.input[0]:
-                    in_name, _ = node.input[0].split(':')
-                    node.input[0] = in_name
-                try:
-                    inputs = [self._nodes[i] for i in node.input]
-                    for i in node.input:
-                        if i not in self._params:
-                            input_shapes[self._nodes[i]] = self._output_shapes[i]
-                    attr['_input_shapes'] = input_shapes
-                except KeyError:
-                    # TODO: Need to find clean way to handle '^CheckNumerics'
-                    pass
+                #ToDo: Tensorflow Split operators outputs need to extract properly
+                #depend on Name and ouput slot number and prepare input for next
+                #operator.eg: ['split', 'split:1']. In this case, 'split' and 'split:1'
+                #two input properly feed to next operator.
+                if "split" in node.input[0]:
+                    inputs = []
+                    input_shapes = {}
+                    try:
+                        for node_input_name in node.input:
+                            node_input_key = node_input_name.split(':')
+                            slot_num = 0
+                            if len(node_input_key) > 1:
+                                slot_num = int(node_input_key[1])
+                                node_input_key = node_input_key[0]
+                            else:
+                                node_input_key = node_input_key[0]
+                            new_input = self._nodes[node_input_key].__getitem__(slot_num)
+                            inputs.append(new_input)
+                            if node_input_name not in self._params:
+                                input_shapes[new_input] = self._output_shapes[
+                                    node_input_key].__getitem__(slot_num)
+                        attr['_input_shapes'] = input_shapes
+                    except KeyError:
+                        pass
+                else:
+                    #ToDo: Some of the tensorflow operators maintain internaly maintain
+                    #execution layers and its output name will the layer number along with
+                    #graph node name.eg: Node name:- 'Model/RNN/cell_0/RnnCell', but the
+                    #output name will be 'Model/RNN/cell_0/RnnCell:0'. In this case,
+                    #the digit has to be ignored.
+                    if ":" in node.input[0]:
+                        in_name, _ = node.input[0].split(':')
+                        node.input[0] = in_name
+                    try:
+                        inputs = [self._nodes[i] for i in node.input]
+                        for i in node.input:
+                            if i not in self._params:
+                                input_shapes[self._nodes[i]] = self._output_shapes[i]
+                        attr['_input_shapes'] = input_shapes
+                    except KeyError:
+                        # TODO: Need to find clean way to handle '^CheckNumerics'
+                        pass
 
                 inputs = self._fix_extranodes(node.op, attr, inputs)
 
diff --git a/nnvm/tests/python/frontend/tensorflow/test_forward.py b/nnvm/tests/python/frontend/tensorflow/test_forward.py
index 045d154d9d8b..8ba43458f9d9 100644
--- a/nnvm/tests/python/frontend/tensorflow/test_forward.py
+++ b/nnvm/tests/python/frontend/tensorflow/test_forward.py
@@ -595,6 +595,72 @@ def test_forward_gather():
     _test_gather((4,3,5,6), (1,4), [[2,1,0,0]], 0, 'float32')
 
 
+#######################################################################
+# LocalResponseNormalization[LRN]
+# ------
+
+def _test_lrn(ip_shape, depth_radius=2, alpha=1e-05, beta=0.75, bias=1.0):
+    tf.reset_default_graph()
+    dtype = 'float32'
+    in_data = tf.placeholder(dtype, ip_shape, name="in_data")
+    tf.nn.local_response_normalization(in_data,
+                                        depth_radius=depth_radius,
+                                        alpha=alpha,
+                                        beta=beta,
+                                        bias=bias,
+                                        name="local_response_normalization")
+    np_data = np.random.uniform(size=ip_shape).astype(dtype)
+
+    with tf.Session() as sess:
+        final_graph_def = tf.graph_util.convert_variables_to_constants(
+            sess,
+            sess.graph.as_graph_def(add_shapes=True),
+            ['local_response_normalization'])
+        tf_output = run_tf_graph(sess, [np_data], ['in_data:0'],
+                                 'local_response_normalization:0')
+        tvm_output = run_tvm_graph(final_graph_def, [np_data], ['in_data'],
+                                   tf_output.shape, dtype)
+        np.testing.assert_allclose(tf_output, tvm_output, atol=1e-5, rtol=1e-5)
+        sess.close()
+
+def test_forward_lrn():
+    '''test local_response_normalization operator'''
+    _test_lrn((4, 4, 4, 4))
+    _test_lrn((4,3,5,6), 1, 1e-05, 0.5)
+    _test_lrn((1, 3, 20, 20), 2, 1e-05, 0.5, 1.0)
+    _test_lrn((1, 3, 20, 20), 2, 1e-05, 0.75, 2.0)
+
+
+#######################################################################
+# Split
+# ------
+
+def _test_split(ip_shape, num_or_size_splits, axis):
+    tf.reset_default_graph()
+    dtype = 'float32'
+    in_data = tf.placeholder(dtype, ip_shape, name="in_data")
+    tf.split(in_data, num_or_size_splits, axis=axis, name="split")
+    np_data = np.random.uniform(size=ip_shape).astype(dtype)
+
+    with tf.Session() as sess:
+        final_graph_def = tf.graph_util.convert_variables_to_constants(
+            sess,
+            sess.graph.as_graph_def(add_shapes=True),
+            ['split'])
+        tf_output = run_tf_graph(sess, [np_data], ['in_data:0'], 'split:0')
+        tvm_output = run_tvm_graph(final_graph_def, [np_data], ['in_data'],
+                                   tf_output.shape, dtype)
+        np.testing.assert_allclose(tf_output, tvm_output, atol=1e-5, rtol=1e-5)
+        sess.close()
+
+def test_forward_split():
+    '''test split operator'''
+    _test_split((2, 3), 2, axis=0)
+    _test_split((6, 3), 3, axis=0)
+    _test_split((5, 9, 3), 3, axis=1)
+    _test_split((2,5,3,9), 3, axis=3)
+
+
 #######################################################################
 # Multi Input to graph
 # --------------------
@@ -875,3 +941,5 @@ def _get_sample(data, state):
     test_forward_stridedslice()
     test_forward_gather()
     test_forward_ptb()
+    test_forward_lrn()
+    test_forward_split()

From 32bc35452b54553721026fb106fa46f0763b1551 Mon Sep 17 00:00:00 2001
From: Dayananda V <dayanandasiet@gmail.com>
Date: Thu, 2 Aug 2018 15:38:55 +0530
Subject: [PATCH 940/949] Tensorflow Split operator contribution

split operator contribute inaddition to run alexnet on tensorflow(https://www.cs.toronto.edu/~guerzhoy/tf_alexnet/)
---
 nnvm/python/nnvm/frontend/tensorflow.py       | 13 +--
 .../frontend/tensorflow/test_forward.py       | 99 ++++++-------------
 2 files changed, 32 insertions(+), 80 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/tensorflow.py b/nnvm/python/nnvm/frontend/tensorflow.py
index 91ed969d0a43..35f282b69b47 100644
--- a/nnvm/python/nnvm/frontend/tensorflow.py
+++ b/nnvm/python/nnvm/frontend/tensorflow.py
@@ -91,16 +91,6 @@ def _impl(inputs, attr, *args):
         return AttrCvt(op_name="__pow_scalar__", extras={'scalar': -0.5})(inputs, attr)
     return _impl
 
-def _local_response_normalization():
-    def _impl(inputs, attr, params):
-        depth_radius = attr.get('depth_radius', 2)
-        depth_radius = depth_radius * 2 + 1
-        axis = 3 #default NHWC
-        return AttrCvt(op_name="lrn",
-                       ignores=['depth_radius'],
-                       extras={'size': depth_radius, 'axis': axis})(inputs, attr)
-    return _impl
-
 def _split():
     def _impl(inputs, attr, params):
         pop_node = inputs.pop(0)
@@ -706,10 +696,9 @@ def _impl(inputs, in_state_c, in_state_h, attr, params):
     'Shape'                             : _shape(),
     'Sigmoid'                           : AttrCvt('sigmoid'),
     'Fill'                              : _fill(),
+    'Split'                             : _split(),
     'GatherV2'                          : _gather_v2(),
     'StridedSlice'                      : _stridedSlice(),
-    'LRN'                               : _local_response_normalization(),
-    'Split'                             : _split(),
 }
 
 # _convert_map_rnn defines maps of rnn operator name to
diff --git a/nnvm/tests/python/frontend/tensorflow/test_forward.py b/nnvm/tests/python/frontend/tensorflow/test_forward.py
index 8ba43458f9d9..42fb4b15f714 100644
--- a/nnvm/tests/python/frontend/tensorflow/test_forward.py
+++ b/nnvm/tests/python/frontend/tensorflow/test_forward.py
@@ -442,6 +442,36 @@ def test_forward_variable():
     _test_variable(np.random.uniform(size=(32, 100)).astype('float32'))
 
 
+#######################################################################
+# Split
+# ------
+
+def _test_split(ip_shape, num_or_size_splits, axis):
+    tf.reset_default_graph()
+    dtype = 'float32'
+    in_data = tf.placeholder(dtype, ip_shape, name="in_data")
+    tf.split(in_data, num_or_size_splits, axis=axis, name="split")
+    np_data = np.random.uniform(size=ip_shape).astype(dtype)
+
+    with tf.Session() as sess:
+        final_graph_def = tf.graph_util.convert_variables_to_constants(
+            sess,
+            sess.graph.as_graph_def(add_shapes=True),
+            ['split'])
+        tf_output = run_tf_graph(sess, [np_data], ['in_data:0'], 'split:0')
+        tvm_output = run_tvm_graph(final_graph_def, [np_data], ['in_data'],
+                                   tf_output.shape, dtype)
+        np.testing.assert_allclose(tf_output, tvm_output, atol=1e-5, rtol=1e-5)
+        sess.close()
+
+def test_forward_split():
+    '''test split operator'''
+    _test_split((2, 3), 2, axis=0)
+    _test_split((6, 3), 3, axis=0)
+    _test_split((5, 9, 3), 3, axis=1)
+    _test_split((2,5,3,9), 3, axis=3)
+
+
 #######################################################################
 # LSTM
 # ----
@@ -595,72 +625,6 @@ def test_forward_gather():
     _test_gather((4,3,5,6), (1,4), [[2,1,0,0]], 0, 'float32')
 
 
-#######################################################################
-# LocalResponseNormalization[LRN]
-# ------
-
-def _test_lrn(ip_shape, depth_radius=2, alpha=1e-05, beta=0.75, bias=1.0):
-    tf.reset_default_graph()
-    dtype = 'float32'
-    in_data = tf.placeholder(dtype, ip_shape, name="in_data")
-    tf.nn.local_response_normalization(in_data,
-                                        depth_radius=depth_radius,
-                                        alpha=alpha,
-                                        beta=beta,
-                                        bias=bias,
-                                        name="local_response_normalization")
-    np_data = np.random.uniform(size=ip_shape).astype(dtype)
-
-    with tf.Session() as sess:
-        final_graph_def = tf.graph_util.convert_variables_to_constants(
-            sess,
-            sess.graph.as_graph_def(add_shapes=True),
-            ['local_response_normalization'])
-        tf_output = run_tf_graph(sess, [np_data], ['in_data:0'],
-                                 'local_response_normalization:0')
-        tvm_output = run_tvm_graph(final_graph_def, [np_data], ['in_data'],
-                                   tf_output.shape, dtype)
-        np.testing.assert_allclose(tf_output, tvm_output, atol=1e-5, rtol=1e-5)
-        sess.close()
-
-def test_forward_lrn():
-    '''test local_response_normalization operator'''
-    _test_lrn((4, 4, 4, 4))
-    _test_lrn((4,3,5,6), 1, 1e-05, 0.5)
-    _test_lrn((1, 3, 20, 20), 2, 1e-05, 0.5, 1.0)
-    _test_lrn((1, 3, 20, 20), 2, 1e-05, 0.75, 2.0)
-
-
-#######################################################################
-# Split
-# ------
-
-def _test_split(ip_shape, num_or_size_splits, axis):
-    tf.reset_default_graph()
-    dtype = 'float32'
-    in_data = tf.placeholder(dtype, ip_shape, name="in_data")
-    tf.split(in_data, num_or_size_splits, axis=axis, name="split")
-    np_data = np.random.uniform(size=ip_shape).astype(dtype)
-
-    with tf.Session() as sess:
-        final_graph_def = tf.graph_util.convert_variables_to_constants(
-            sess,
-            sess.graph.as_graph_def(add_shapes=True),
-            ['split'])
-        tf_output = run_tf_graph(sess, [np_data], ['in_data:0'], 'split:0')
-        tvm_output = run_tvm_graph(final_graph_def, [np_data], ['in_data'],
-                                   tf_output.shape, dtype)
-        np.testing.assert_allclose(tf_output, tvm_output, atol=1e-5, rtol=1e-5)
-        sess.close()
-
-def test_forward_split():
-    '''test split operator'''
-    _test_split((2, 3), 2, axis=0)
-    _test_split((6, 3), 3, axis=0)
-    _test_split((5, 9, 3), 3, axis=1)
-    _test_split((2,5,3,9), 3, axis=3)
-
-
 #######################################################################
 # Multi Input to graph
 # --------------------
@@ -940,6 +904,5 @@ def _get_sample(data, state):
     test_forward_lstm()
     test_forward_stridedslice()
     test_forward_gather()
-    test_forward_ptb()
-    test_forward_lrn()
     test_forward_split()
+    test_forward_ptb()

From b0cb54fa3ee17718da3e1ac184cdd1a99a36bfec Mon Sep 17 00:00:00 2001
From: Dayananda V <dayanandasiet@gmail.com>
Date: Thu, 2 Aug 2018 15:47:48 +0530
Subject: [PATCH 941/949] Tensorflow Split operator contribution

pylint issue fix
---
 nnvm/python/nnvm/frontend/tensorflow.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/nnvm/python/nnvm/frontend/tensorflow.py b/nnvm/python/nnvm/frontend/tensorflow.py
index 35f282b69b47..f7adff1d44bf 100644
--- a/nnvm/python/nnvm/frontend/tensorflow.py
+++ b/nnvm/python/nnvm/frontend/tensorflow.py
@@ -938,6 +938,7 @@ def from_tensorflow(self, graph):
             raise ImportError(
                 "Unable to import tensorflow which is required {}".format(e))
 
+        # pylint: disable=too-many-nested-blocks
         for node in graph.node:
             # Tensorflow doesn't have seperate list for params extraction.
             # Operator name 'Const' is treated as a parameter to build NNVM params dict.

From c343798ee16c9e5cbc95db67a014813705dafdbf Mon Sep 17 00:00:00 2001
From: Siju <sijusamuel@gmail.com>
Date: Thu, 2 Aug 2018 21:28:24 +0530
Subject: [PATCH 942/949] keras.layers.ReLU added (#1530)

---
 nnvm/python/nnvm/frontend/keras.py               | 5 ++++-
 nnvm/tests/python/frontend/keras/test_forward.py | 1 +
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/nnvm/python/nnvm/frontend/keras.py b/nnvm/python/nnvm/frontend/keras.py
index 5481a796ed12..15493d18e7bb 100644
--- a/nnvm/python/nnvm/frontend/keras.py
+++ b/nnvm/python/nnvm/frontend/keras.py
@@ -74,7 +74,9 @@ def _convert_activation(insym, keras_layer, _):
 
 def _convert_advanced_activation(insym, keras_layer, symtab):
     act_type = type(keras_layer).__name__
-    if act_type == 'LeakyReLU':
+    if act_type == 'ReLU':
+        return _sym.relu(insym)
+    elif act_type == 'LeakyReLU':
         return _sym.leaky_relu(insym, alpha=keras_layer.alpha)
     elif act_type == 'ELU':
         alpha = keras_layer.alpha if hasattr(keras_layer, "alpha") else 1
@@ -382,6 +384,7 @@ def _default_skip(insym, keras_layer, _): # pylint: disable=unused-argument
 _convert_map = {
     'Dense'                    : _convert_dense,
     'Activation'               : _convert_activation,
+    'ReLU'                     : _convert_advanced_activation,
     'LeakyReLU'                : _convert_advanced_activation,
     'PReLU'                    : _convert_advanced_activation,
     'ELU'                      : _convert_advanced_activation,
diff --git a/nnvm/tests/python/frontend/keras/test_forward.py b/nnvm/tests/python/frontend/keras/test_forward.py
index ab56698c085d..17c9fc1329d7 100644
--- a/nnvm/tests/python/frontend/keras/test_forward.py
+++ b/nnvm/tests/python/frontend/keras/test_forward.py
@@ -138,6 +138,7 @@ def test_forward_activations():
     weights = np.random.rand(1, 32, 32, 3)
     act_funcs = [keras.layers.Activation('softmax'),
                  keras.layers.Activation('softplus'),
+                 keras.layers.ReLU(),
                  keras.layers.LeakyReLU(alpha=0.3),
                  keras.layers.PReLU(weights=weights, alpha_initializer="zero"),
                  keras.layers.ELU(alpha=0.5),

From d3ca9c248eed5ebe1cb510630dbb399a644f8ea1 Mon Sep 17 00:00:00 2001
From: Lianmin Zheng <mercy_zheng@sjtu.edu.cn>
Date: Thu, 2 Aug 2018 08:59:25 -0700
Subject: [PATCH 943/949] [AUTOTVM] TOPI integration for ARM CPU (#1487)

---
 .gitignore                                    |   3 +
 apps/benchmark/README.md                      |  70 +++
 apps/benchmark/arm_cpu_imagenet_bench.py      |  96 ++++
 apps/benchmark/rasp_imagenet_bench.py         |  76 ---
 docs/api/python/autotvm.rst                   |  12 +
 docs/install/from_source.rst                  |   4 +-
 nnvm/include/nnvm/top/nn.h                    |  71 +++
 nnvm/python/nnvm/compiler/build_module.py     | 128 +++--
 nnvm/python/nnvm/top/nn.py                    |  49 +-
 nnvm/src/top/nn/convolution.cc                | 181 +++++-
 python/tvm/autotvm/__init__.py                |   9 +-
 python/tvm/autotvm/env.py                     |   1 +
 python/tvm/autotvm/measure/__init__.py        |   5 +-
 python/tvm/autotvm/measure/local_executor.py  |  37 +-
 python/tvm/autotvm/measure/measure.py         | 290 ++--------
 python/tvm/autotvm/measure/measure_methods.py | 452 ++++++++++-----
 python/tvm/autotvm/record.py                  | 135 +----
 python/tvm/autotvm/task/__init__.py           |   5 +-
 python/tvm/autotvm/task/dispatcher.py         | 143 ++++-
 python/tvm/autotvm/task/nnvm_integration.py   | 177 ++++++
 python/tvm/autotvm/task/space.py              |   9 +-
 python/tvm/autotvm/task/task.py               |   3 +-
 python/tvm/autotvm/task/topi_integration.py   | 193 +++++++
 python/tvm/autotvm/tophub.py                  | 123 +++++
 python/tvm/autotvm/tuner/callback.py          |  94 ++--
 python/tvm/autotvm/tuner/ga_tuner.py          |   3 +
 python/tvm/autotvm/tuner/gridsearch_tuner.py  |   6 +
 python/tvm/autotvm/tuner/model_based_tuner.py |   2 +-
 .../tvm/autotvm/tuner/sa_model_optimizer.py   |  30 +-
 python/tvm/autotvm/tuner/tuner.py             |  50 +-
 .../tvm/autotvm/tuner/xgboost_cost_model.py   |  52 +-
 python/tvm/autotvm/tuner/xgboost_tuner.py     |  11 +-
 python/tvm/autotvm/util.py                    |  10 +
 python/tvm/contrib/download.py                |  12 +-
 python/tvm/contrib/util.py                    |  32 ++
 python/tvm/exec/autotvm_log_editor.py         |  44 ++
 python/tvm/exec/rpc_server.py                 |  13 +-
 python/tvm/exec/rpc_tracker.py                |   7 +-
 python/tvm/exec/tophub.py                     |  36 ++
 python/tvm/rpc/__init__.py                    |   2 +
 python/tvm/rpc/client.py                      |  20 +-
 python/tvm/rpc/proxy.py                       |   4 +
 python/tvm/rpc/server.py                      |  37 +-
 python/tvm/target.py                          |  67 ++-
 src/codegen/build_module.cc                   |  14 -
 src/codegen/opt/build_opencl_off.cc           |   1 -
 tests/python/integration/test_tuning.py       |  14 +-
 .../python/unittest/test_autotvm_database.py  |  13 +-
 tests/python/unittest/test_autotvm_feature.py |   2 +-
 tests/python/unittest/test_lang_target.py     |   2 +-
 tests/scripts/task_python_vta.sh              |   3 +
 topi/python/topi/__init__.py                  |   2 +-
 topi/python/topi/arm_cpu/__init__.py          |   5 +
 .../{rasp => arm_cpu}/bitserial_conv2d.py     |   6 +-
 topi/python/topi/arm_cpu/conv2d.py            | 515 ++++++++++++++++++
 topi/python/topi/arm_cpu/depthwise_conv2d.py  |  94 ++++
 topi/python/topi/generic/nn.py                |  48 ++
 topi/python/topi/nn/conv2d.py                 | 282 +++-------
 topi/python/topi/rasp/__init__.py             |   7 -
 topi/python/topi/rasp/conv2d.py               | 358 ------------
 topi/python/topi/rasp/depthwise_conv2d.py     | 207 -------
 topi/python/topi/util.py                      |  53 ++
 .../python/test_topi_bitserial_conv2d_rasp.py |   2 +-
 topi/tests/python/test_topi_conv2d.py         |   8 +-
 ...une_cuda_conv2d.py => tune_conv2d_cuda.py} |  31 +-
 tutorials/autotvm/tune_nnvm_arm.py            | 364 +++++++++++++
 tutorials/autotvm/tune_simple_template.py     |  25 +-
 tutorials/cross_compilation_and_rpc.py        | 268 ++++-----
 tutorials/nnvm/README.txt                     |   2 +
 tutorials/nnvm/deploy_model_on_mali_gpu.py    | 153 +++---
 tutorials/nnvm/deploy_model_on_rasp.py        | 148 +++--
 tutorials/nnvm/from_mxnet.py                  |   2 +
 tutorials/nnvm/imagenet_inference_gpu.py      |  89 ---
 tutorials/nnvm_quick_start.py                 | 158 ++----
 vta/python/vta/top/arm_conv2d.py              |  66 +--
 vta/python/vta/top/vta_conv2d.py              |  11 +-
 .../integration/test_benchmark_topi_conv2d.py |  11 +-
 vta/tutorials/resnet.py                       |  42 +-
 78 files changed, 3504 insertions(+), 2306 deletions(-)
 create mode 100644 apps/benchmark/README.md
 create mode 100644 apps/benchmark/arm_cpu_imagenet_bench.py
 delete mode 100644 apps/benchmark/rasp_imagenet_bench.py
 create mode 100644 python/tvm/autotvm/task/nnvm_integration.py
 create mode 100644 python/tvm/autotvm/task/topi_integration.py
 create mode 100644 python/tvm/autotvm/tophub.py
 create mode 100644 python/tvm/exec/autotvm_log_editor.py
 create mode 100644 python/tvm/exec/tophub.py
 create mode 100644 topi/python/topi/arm_cpu/__init__.py
 rename topi/python/topi/{rasp => arm_cpu}/bitserial_conv2d.py (99%)
 create mode 100644 topi/python/topi/arm_cpu/conv2d.py
 create mode 100644 topi/python/topi/arm_cpu/depthwise_conv2d.py
 delete mode 100644 topi/python/topi/rasp/__init__.py
 delete mode 100644 topi/python/topi/rasp/conv2d.py
 delete mode 100644 topi/python/topi/rasp/depthwise_conv2d.py
 rename tutorials/autotvm/{tune_cuda_conv2d.py => tune_conv2d_cuda.py} (90%)
 create mode 100644 tutorials/autotvm/tune_nnvm_arm.py
 delete mode 100644 tutorials/nnvm/imagenet_inference_gpu.py

diff --git a/.gitignore b/.gitignore
index 7080502aaf86..3c968eb3ed47 100644
--- a/.gitignore
+++ b/.gitignore
@@ -188,3 +188,6 @@ build*
 
 # Jetbrain
 .idea
+
+# tmp file
+.nfs*
diff --git a/apps/benchmark/README.md b/apps/benchmark/README.md
new file mode 100644
index 000000000000..e83e47c46eb7
--- /dev/null
+++ b/apps/benchmark/README.md
@@ -0,0 +1,70 @@
+# Performance Benchmark
+
+## Results
+
+See results on wiki page https://github.com/dmlc/tvm/wiki/Benchmark
+
+## How to Reproduce
+
+### ARM CPU
+We use RPC infrastructure in TVM to make device management easy. So you need to use it for reproducing benchmark results.
+
+1. Start an RPC Tracker on the host machine
+```bash
+python3 -m tvm.exec.rpc_tracker
+```
+
+2. Register devices to the tracker
+* For Linux device
+  * Build tvm runtime on your device [Help](https://docs.tvm.ai/tutorials/nnvm/deploy_model_on_rasp.html#build-tvm-runtime-on-device)
+  * Register your device to tracker by
+  ```bash
+  python3 -m tvm.exec.rpc_sever --tracker=[HOST_IP]:9190 --key=[DEVICE_KEY]
+  ```
+  replace `[HOST_IP]` with the IP address of the host machine, `[DEVICE_KEY]` with the name of device.
+  
+  E.g. Here is an example command for RK3399,
+  `python3 -m tvm.exec.rpc_sever --tracker=10.77.1.123:9190 --key=rk3399`, where 10.77.1.123 is the IP address of the tracker.
+
+* For Android device
+   * Build and install tvm RPC apk on your device [Help](https://github.com/dmlc/tvm/tree/master/apps/android_rpc).
+     Make sure you can pass the android rpc test. Then you have alreadly known how to register.
+
+3. Verify the device registration  
+  We can query all registered devices by
+  ```bash
+  python3 -m tvm.exec.query_rpc_tracker
+  ```
+  You should be able to find your devices in `Queue Status`. Make sure the registration is correct before going ahead.
+
+  For our test environment, one sample output can be 
+  ```bash
+  Queue Status                
+  ------------------------------
+  key            free    pending    
+  ------------------------------
+  mate10pro      1       0   
+  p20pro         2       0  
+  pixel2         2       0 
+  rk3399         2       0
+  rasp3b         8       0
+  ```
+
+ 4. Run benchmark  
+  We did auto-tuning for Huawei P20/Mate10 Pro, Google Pixel2, Raspberry Pi3 and Firefly-RK3399,
+  and release pre-tuned parameters in [this repo](https://github.com/uwsaml/tvm-distro).
+  During compilation, TVM will download these operator parameters automatically.
+
+  ```bash
+  python3 arm_cpu_imagenet_bench.py --device rasp3b --rpc-key rasp3b
+  python3 arm_cpu_imagenet_bench.py --device rk3399 --rpc-key rk3399
+  python3 arm_cpu_imagenet_bench.py --device pixel2 --rpc-key pixel2
+  python3 arm_cpu_imagenet_bench.py --device p20pro --rpc-key p20pro
+  python3 arm_cpu_imagenet_bench.py --device mate10pro --rpc-key mate10pro  
+  ```
+
+  If your device has a same SoC of the above device, you can reuse these parameters
+  (e.g. use `llvm -device=arm_cpu -mode=rk3399 -target=aarch64-linux-gnu` as target).
+  Otherwise, you need to tune for your own device, please follow this 
+  [tutorial](https://docs.tvm.ai/tutorials/autotvm/tune_nnvm_arm.html).
+
diff --git a/apps/benchmark/arm_cpu_imagenet_bench.py b/apps/benchmark/arm_cpu_imagenet_bench.py
new file mode 100644
index 000000000000..7baf244e0dae
--- /dev/null
+++ b/apps/benchmark/arm_cpu_imagenet_bench.py
@@ -0,0 +1,96 @@
+"""Benchmark script for performance on ARM CPU.
+see README.md for the usage and results of this script.
+"""
+
+import argparse
+import time
+
+import numpy as np
+
+import nnvm.testing
+import nnvm.compiler
+import tvm
+from tvm import autotvm
+from tvm.contrib.util import tempdir
+import tvm.contrib.graph_runtime as runtime
+
+def get_network(name, batch_size):
+    """Get the symbol definition and random weight of a network"""
+    input_shape = (batch_size, 3, 224, 224)
+    output_shape = (batch_size, 1000)
+
+    if name == 'resnet-18':
+        net, params = nnvm.testing.resnet.get_workload(num_layers=18,
+                                                       batch_size=batch_size, image_shape=(3, 224, 224))
+    elif name == 'mobilenet':
+        net, params = nnvm.testing.mobilenet.get_workload(batch_size=batch_size)
+    elif name == 'squeezenet v1.1':
+        net, params = nnvm.testing.squeezenet.get_workload(batch_size=batch_size,
+                                                           version='1.1')
+    elif name == 'vgg-16':
+        net, params = nnvm.testing.vgg.get_workload(batch_size=batch_size, num_layers=16)
+    else:
+        raise RuntimeError("Unsupported network: " + name)
+
+    return net, params, input_shape, output_shape
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--network", type=str, choices=['resnet-18', 'mobilenet', 'squeezenet v1.1', 'vgg-16'])
+    parser.add_argument("--device", type=str, required=True, choices=['rk3399', 'mate10', 'mate10pro', 'p20', 'p20pro', 
+                                                                      'pixel2', 'rasp3b', 'pynq'])
+    parser.add_argument("--host", type=str, default='localhost')
+    parser.add_argument("--port", type=int, default=9190)
+    parser.add_argument("--rpc-key", type=str, required=True)
+    parser.add_argument("--number", type=int, default=6)
+    args = parser.parse_args()
+
+    dtype = 'float32'
+
+    if args.network is None:
+        networks = ['squeezenet v1.1', 'mobilenet', 'resnet-18', 'vgg-16']
+    else:
+        networks = [args.network]
+
+    target = tvm.target.arm_cpu(model=args.device)
+
+    # connect to remote device
+    tracker = tvm.rpc.connect_tracker(args.host, args.port)
+    remote = tracker.request(args.rpc_key)
+
+    print("--------------------------------------------------")
+    print("%-20s %-20s" % ("Network Name", "Mean Inference Time (std dev)"))
+    print("--------------------------------------------------")
+    for network in networks:
+        net, params, input_shape, output_shape = get_network(network, batch_size=1)
+
+        with nnvm.compiler.build_config(opt_level=2, add_pass=['AlterOpLayout']):
+            graph, lib, params = nnvm.compiler.build(
+                net, target=target, shape={'data': input_shape}, params=params, dtype=dtype)
+
+        tmp = tempdir()
+        if 'android' in str(target):
+            from tvm.contrib import ndk
+            filename = "%s.so" % network
+            lib.export_library(tmp.relpath(filename), ndk.create_shared)
+        else:
+            filename = "%s.tar" % network
+            lib.export_library(tmp.relpath(filename))
+
+        # upload library and params
+        ctx = remote.context(str(target), 0)
+        remote.upload(tmp.relpath(filename))
+        rparams = {k: tvm.nd.array(v, ctx) for k, v in params.items()}
+
+        rlib = remote.load_module(filename)
+        module = runtime.create(graph, rlib, ctx)
+        data_tvm = tvm.nd.array((np.random.uniform(size=input_shape)).astype(dtype))
+        module.set_input('data', data_tvm)
+        module.set_input(**rparams)
+
+        # evaluate
+        ftimer = module.module.time_evaluator("run", ctx, number=args.number, repeat=3)
+        prof_res = np.array(ftimer().results) * 1000  # multiply 1000 for converting to millisecond
+        print("%-20s %-19s (%s)" % (network, "%.2f ms" % np.mean(prof_res), "%.2f ms" % np.std(prof_res)))
+
diff --git a/apps/benchmark/rasp_imagenet_bench.py b/apps/benchmark/rasp_imagenet_bench.py
deleted file mode 100644
index 098ae721da40..000000000000
--- a/apps/benchmark/rasp_imagenet_bench.py
+++ /dev/null
@@ -1,76 +0,0 @@
-""" Benchmark script for performance on Raspberry Pi. For example, run the file with:
-`python rasp_imagenet_bench.py --model='modbilenet' --host='rasp0' --port=9090`. For
-more details about how to set up the inference environment on Raspberry Pi, Please
-refer to NNVM Tutorial: Deploy the Pretrained Model on Raspberry Pi """
-import time
-import argparse
-import numpy as np
-import tvm
-import nnvm.compiler
-import nnvm.testing
-from tvm.contrib import util, rpc
-from tvm.contrib import graph_runtime as runtime
-
-
-def main():
-    parser = argparse.ArgumentParser()
-    parser.add_argument('--model', type=str, required=True, choices=['resnet', 'mobilenet'],
-        help="The model type.")
-    parser.add_argument('--host', type=str, required=True, help="The host address of your Raspberry Pi.")
-    parser.add_argument('--port', type=int, required=True, help="The port number of your Raspberry Pi.")
-    parser.add_argument('--opt-level', type=int, default=1, help="Level of optimization.")
-    parser.add_argument('--num-iter', type=int, default=50, help="Number of iteration during benchmark.")
-    args = parser.parse_args()
-
-    opt_level = args.opt_level
-
-    num_iter = args.num_iter
-    batch_size = 1
-    num_classes = 1000
-    image_shape = (3, 224, 224)
-
-    data_shape = (batch_size,) + image_shape
-    out_shape = (batch_size, num_classes)
-    if args.model == 'resnet':
-        net, params = nnvm.testing.resnet.get_workload(
-            batch_size=1, image_shape=image_shape)
-    elif args.model == 'mobilenet':
-        net, params = nnvm.testing.mobilenet.get_workload(
-            batch_size=1, image_shape=image_shape)
-    else:
-        raise ValueError('no benchmark prepared for {}.'.format(args.model))
-
-
-    with nnvm.compiler.build_config(opt_level=opt_level):
-        graph, lib, params = nnvm.compiler.build(
-            net, tvm.target.rasp(), shape={"data": data_shape}, params=params)
-
-    tmp = util.tempdir()
-    lib_fname = tmp.relpath('net.o')
-    lib.save(lib_fname)
-
-    remote = rpc.connect(args.host, args.port)
-    remote.upload(lib_fname)
-
-    ctx = remote.cpu(0)
-    rlib = remote.load_module('net.o')
-    rparams = {k: tvm.nd.array(v, ctx) for k, v in params.items()}
-
-    module = runtime.create(graph, rlib, ctx)
-    module.set_input('data', tvm.nd.array(np.random.uniform(size=(data_shape)).astype("float32")))
-    module.set_input(**rparams)
-    module.run()
-    out = module.get_output(0, tvm.nd.empty(out_shape, ctx=ctx))
-    out.asnumpy()
-
-    print('benchmark args: {}'.format(args))
-    ftimer = module.module.time_evaluator("run", ctx, num_iter)
-    for i in range(3):
-        prof_res = ftimer()
-        print(prof_res)
-        # sleep for avoiding cpu overheat
-        time.sleep(45)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/docs/api/python/autotvm.rst b/docs/api/python/autotvm.rst
index e1f4906af0e7..0a2ae40f24a9 100644
--- a/docs/api/python/autotvm.rst
+++ b/docs/api/python/autotvm.rst
@@ -44,6 +44,9 @@ tvm.autotvm.tuner
 .. automodule:: tvm.autotvm.tuner.callback
     :members:
 
+.. automodule:: tvm.autotvm.tuner.graph_tuning
+    :members:
+
 tvm.autotvm.task
 ~~~~~~~~~~~~~~~~
 .. automodule:: tvm.autotvm.task
@@ -55,6 +58,15 @@ tvm.autotvm.task
 .. automodule:: tvm.autotvm.task.space
     :members:
 
+.. automodule:: tvm.autotvm.task.dispatcher
+    :members:
+
+.. automodule:: tvm.autotvm.task.topi_integration
+    :members:
+
+.. automodule:: tvm.autotvm.task.nnvm_integration
+    :members:
+
 tvm.autotvm.record
 ~~~~~~~~~~~~~~~~~~
 .. automodule:: tvm.autotvm.record
diff --git a/docs/install/from_source.rst b/docs/install/from_source.rst
index 5709ed7f0bab..edeba1ccfadc 100644
--- a/docs/install/from_source.rst
+++ b/docs/install/from_source.rst
@@ -60,6 +60,8 @@ The configuration of tvm can be modified by `config.cmake`.
 - Edit ``build/config.cmake`` to customize the compilation options
 
   - On macOS, for some versions of XCode, you need to add ``-lc++abi`` in the LDFLAGS or you'll get link errors.
+  - Change ``set(USE_CUDA OFF)`` to ``set(USE_CUDA ON)`` to enable CUDA backend. So do other backends and libraries
+    (OpenCL, RCOM, METAL, VULKAN, ...).
 
 - TVM optionally depends on LLVM. LLVM is required for CPU codegen that needs LLVM.
 
@@ -84,7 +86,7 @@ The configuration of tvm can be modified by `config.cmake`.
       cmake ..
       make -j4
 
-If everything goes well, we can go to :ref:`python-package-installation`_
+If everything goes well, we can go to :ref:`python-package-installation`
 
 Building on Windows
 ~~~~~~~~~~~~~~~~~~~
diff --git a/nnvm/include/nnvm/top/nn.h b/nnvm/include/nnvm/top/nn.h
index 86bdc60a6236..c9baa116e8aa 100644
--- a/nnvm/include/nnvm/top/nn.h
+++ b/nnvm/include/nnvm/top/nn.h
@@ -172,6 +172,77 @@ struct Conv2DParam : public dmlc::Parameter<Conv2DParam> {
   static const constexpr int kBias = 2;
 };
 
+struct WinogradWeightTransformParam : public dmlc::Parameter<WinogradWeightTransformParam> {
+    int tile_size;
+
+    DMLC_DECLARE_PARAMETER(WinogradWeightTransformParam) {
+      DMLC_DECLARE_FIELD(tile_size)
+        .describe("Tile size of winograd. E.g. 2 for F(2x2, 3x3) and 4 for F(4x4, 3x3)");
+    }
+
+    static const constexpr int kWeight = 0;
+};
+
+struct WinogradConv2DParam : public dmlc::Parameter<WinogradConv2DParam> {
+  int channels;
+  TShape kernel_size;
+  TShape strides;
+  TShape padding;
+  TShape dilation;
+  int groups;
+  std::string layout;
+  std::string kernel_layout;
+  std::string out_layout;
+  int out_dtype;
+  bool use_bias;
+  int tile_size;
+
+  DMLC_DECLARE_PARAMETER(WinogradConv2DParam) {
+    DMLC_DECLARE_FIELD(channels)
+      .describe("The dimensionality of the output space"
+                "i.e. the number of output channels in the convolution.");
+    DMLC_DECLARE_FIELD(kernel_size)
+      .describe("Specifies the dimensions of the convolution window.");
+    DMLC_DECLARE_FIELD(strides).set_default(TShape({1, 1}))
+      .describe("Specifies the strides of the convolution.");
+    DMLC_DECLARE_FIELD(padding).set_default(TShape({0, 0}))
+      .describe("If padding is non-zero, then the input is implicitly zero-padded"
+                "on both sides for padding number of points");
+    DMLC_DECLARE_FIELD(dilation).set_default(TShape({1, 1}))
+      .describe("Specifies the dilation rate to use for dilated convolution.");
+    DMLC_DECLARE_FIELD(groups).set_default(1)
+      .describe("Controls the connections between inputs and outputs."
+                "At groups=1, all inputs are convolved to all outputs."
+                "At groups=2, the operation becomes equivalent to having two convolution"
+                "layers side by side, each seeing half the input channels, and producing"
+                "half the output channels, and both subsequently concatenated.");
+    DMLC_DECLARE_FIELD(layout).set_default("NCHW")
+      .describe("Dimension ordering of input data. Can be 'NCHW', 'NHWC', etc."
+                "'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
+                "dimensions respectively. Convolution is applied on the 'H' and"
+                "'W' dimensions.");
+    DMLC_DECLARE_FIELD(out_layout).set_default("__undef__")
+      .describe("Dimension ordering of output. Can be 'NCHW', 'NHWC', etc."
+                "'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
+                "dimensions respectively. Default to be same as input layout.");
+    DMLC_DECLARE_FIELD(kernel_layout).set_default("OIHW")
+      .describe("Dimension ordering of weight. Can be 'OIHW', 'OIHW16o16i', etc."
+                "'O', 'I', 'H', 'W' stands for num_filter, input_channel, height, and width"
+                "dimensions respectively.");
+    DMLC_DECLARE_DTYPE_FIELD(out_dtype)
+      .add_enum("same", -1)
+      .set_default(-1)
+      .describe("Output data type, set to explicit type under mixed precision setting");
+    DMLC_DECLARE_FIELD(use_bias).set_default(true)
+      .describe("Whether the layer uses a bias vector.");
+    DMLC_DECLARE_FIELD(tile_size)
+      .describe("Tile size of winograd. E.g. 2 for F(2x2, 3x3) and 4 for F(4x4, 3x3)");
+  }
+  // constants
+  static const constexpr int kData = 0;
+  static const constexpr int kWeight = 1;
+  static const constexpr int kBias = 2;
+};
 
 struct Conv2DTransposeParam : public dmlc::Parameter<Conv2DTransposeParam> {
   int channels;
diff --git a/nnvm/python/nnvm/compiler/build_module.py b/nnvm/python/nnvm/compiler/build_module.py
index ed75b10414c7..fd8599bcfa93 100644
--- a/nnvm/python/nnvm/compiler/build_module.py
+++ b/nnvm/python/nnvm/compiler/build_module.py
@@ -6,6 +6,7 @@
 import tvm
 
 from tvm.contrib import graph_runtime
+from tvm import autotvm
 from . import graph_attr, graph_util
 from .. import graph as _graph
 from .. import symbol as sym
@@ -238,67 +239,74 @@ def build(graph, target=None, shape=None, dtype="float32",
         raise ValueError("Target is not set in env or passed as argument.")
     target = tvm.target.create(target)
 
-    shape = shape if shape else {}
-    if not isinstance(shape, dict):
-        raise TypeError("require shape to be dict")
-    for value in shape.values():
-        if not all(isinstance(x, int) for x in value):
-            raise TypeError("shape value must be int iterator")
-
-    cfg = BuildConfig.current
-    graph = graph if isinstance(graph, _graph.Graph) else _graph.create(graph)
-    shape, dtype = _update_shape_dtype(shape, dtype, params)
-
-    # correct layout if necessary
-    layout = layout if layout else {}
-    graph = graph_attr.set_layout_inputs(graph, layout)
-    graph = graph.apply("CorrectLayout")
-    index = graph.index
-    layouts = graph.json_attr("layout")
-    layout = {x : layouts[index.entry_id(x)] for x in index.input_names}
-
-    # Initial pass do shape type inference
-    ishape, _ = graph_util.infer_shape(graph, **shape)
-    shape.update(zip(graph.index.input_names, ishape))
-    if not isinstance(dtype, str):
-        idtype, _ = graph_util.infer_dtype(graph, **dtype)
-        dtype.update(zip(graph.index.input_names, idtype))
-    # Initialize all variables specified in _all_var_init
-    init_var = {}
-    if _all_var_init:
-        init_var = initialize_variables(shape, dtype)
-    # Apply optimization
-    with target:
-        graph = optimize(graph, shape, dtype, layout)
-
-    # Clear extra params without nodes.
-    _remove_noref_params(params, graph)
-
-    # Precompute prune
-    if params and cfg.pass_enabled("PrecomputePrune"):
-        graph, params = precompute_prune(graph, params)
-        shape, dtype = _update_shape_dtype(shape, dtype, params)
-    # Operator Fusion and generation
-    graph = graph_attr.set_shape_inputs(graph, shape)
-    graph = graph.apply("InferShape")
-    graph = graph_attr.set_dtype_inputs(graph, dtype)
-    graph._set_json_attr("target", str(target), "str")
-    if target_host is not None:
-        graph._set_json_attr("target_host", str(target_host), "str")
-    if cfg.pass_enabled("OpFusion"):
-        graph._set_json_attr("opt_level", 1, "int")
+    # if not inside an autotvm config dispatch context, load pre-tuned parameters from TopHub
+    if autotvm.task.DispatchContext.current is None:
+        tophub_context = autotvm.tophub.context(target)
     else:
-        graph._set_json_attr("opt_level", 0, "int")
-    graph = graph.apply("InferShape").apply("InferType")
-    with target:
-        graph = graph.apply("GraphFusePartition").apply("GraphFuseCompile")
-    libmod = graph_attr._move_out_module(graph, "module")
-    # Write variable initial values into params
-    if init_var:
-        if params is None:
-            params = {}
-        params.update(init_var)
-    return graph, libmod, params
+        tophub_context = autotvm.util.EmptyContext()
+
+    with tophub_context:
+        shape = shape if shape else {}
+        if not isinstance(shape, dict):
+            raise TypeError("require shape to be dict")
+        for value in shape.values():
+            if not all(isinstance(x, int) for x in value):
+                raise TypeError("shape value must be int iterator")
+
+        cfg = BuildConfig.current
+        graph = graph if isinstance(graph, _graph.Graph) else _graph.create(graph)
+        shape, dtype = _update_shape_dtype(shape, dtype, params)
+
+        # correct layout if necessary
+        layout = layout if layout else {}
+        graph = graph_attr.set_layout_inputs(graph, layout)
+        graph = graph.apply("CorrectLayout")
+        index = graph.index
+        layouts = graph.json_attr("layout")
+        layout = {x: layouts[index.entry_id(x)] for x in index.input_names}
+
+        # Initial pass do shape type inference
+        ishape, _ = graph_util.infer_shape(graph, **shape)
+        shape.update(zip(graph.index.input_names, ishape))
+        if not isinstance(dtype, str):
+            idtype, _ = graph_util.infer_dtype(graph, **dtype)
+            dtype.update(zip(graph.index.input_names, idtype))
+        # Initialize all variables specified in _all_var_init
+        init_var = {}
+        if _all_var_init:
+            init_var = initialize_variables(shape, dtype)
+        # Apply optimization
+        with target:
+            graph = optimize(graph, shape, dtype, layout)
+
+        # Clear extra params without nodes.
+        _remove_noref_params(params, graph)
+
+        # Precompute prune
+        if params and cfg.pass_enabled("PrecomputePrune"):
+            graph, params = precompute_prune(graph, params)
+            shape, dtype = _update_shape_dtype(shape, dtype, params)
+        # Operator Fusion and generation
+        graph = graph_attr.set_shape_inputs(graph, shape)
+        graph = graph.apply("InferShape")
+        graph = graph_attr.set_dtype_inputs(graph, dtype)
+        graph._set_json_attr("target", str(target), "str")
+        if target_host is not None:
+            graph._set_json_attr("target_host", str(target_host), "str")
+        if cfg.pass_enabled("OpFusion"):
+            graph._set_json_attr("opt_level", 1, "int")
+        else:
+            graph._set_json_attr("opt_level", 0, "int")
+        graph = graph.apply("InferShape").apply("InferType")
+        with target:
+            graph = graph.apply("GraphFusePartition").apply("GraphFuseCompile")
+        libmod = graph_attr._move_out_module(graph, "module")
+        # Write variable initial values into params
+        if init_var:
+            if params is None:
+                params = {}
+            params.update(init_var)
+        return graph, libmod, params
 
 def _remove_noref_params(params, graph):
     """ Helper to clear non referenced params
diff --git a/nnvm/python/nnvm/top/nn.py b/nnvm/python/nnvm/top/nn.py
index e86d545736bd..f59424203402 100644
--- a/nnvm/python/nnvm/top/nn.py
+++ b/nnvm/python/nnvm/top/nn.py
@@ -89,7 +89,7 @@ def compute_conv2d(attrs, inputs, _):
     layout = attrs["layout"]
     kernel_layout = attrs["kernel_layout"]
     out_dtype = attrs["out_dtype"]
-    out_dtype = None if out_dtype == "same" else out_dtype
+    out_dtype = inputs[0].dtype if out_dtype == "same" else out_dtype
     assert layout == "NCHW" or layout == "NHWC"
     (dilation_h, dilation_w) = dilation
     if dilation_h < 1 or dilation_w < 1:
@@ -196,6 +196,53 @@ def schedule_contrib_conv2d_NCHWc(attrs, outs, target):
 
 reg.register_pattern("_contrib_conv2d_NCHWc", OpPattern.OUT_ELEMWISE_FUSABLE)
 
+
+@reg.register_compute("_contrib_conv2d_winograd_weight_transform")
+def compute_contrib_conv2d_winograd_weight_transform(attrs, inputs, _):
+    return topi.nn.conv2d_winograd_weight_transform(inputs[0], attrs.get_int('tile_size'))
+
+@reg.register_schedule("_contrib_conv2d_winograd_weight_transform")
+def schedule_contrib_conv2d_winograd_weight_transform(attrs, outs, target):
+    with tvm.target.create(target):
+        return topi.generic.schedule_conv2d_winograd_weight_transform(outs)
+
+reg.register_pattern("_contrib_conv2d_winograd_weight_transform", OpPattern.OUT_ELEMWISE_FUSABLE)
+
+
+@reg.register_compute("_contrib_conv2d_winograd_without_weight_transform")
+def compute_contrib_conv2d_winograd_without_weight_transform(attrs, inputs, _):
+    """Compute definition of conv2d NCHWc"""
+    padding = attrs.get_int_tuple("padding")
+    strides = attrs.get_int_tuple("strides")
+    dilation = attrs.get_int_tuple("dilation")
+    groups = attrs.get_int("groups")
+    layout = attrs.get_string("layout")
+    out_dtype = attrs.get_string("out_dtype")
+    tile_size = attrs.get_int("tile_size")
+    out_dtype = inputs[0].dtype if out_dtype == "same" else out_dtype
+    assert dilation == (1, 1), "Do not support dilate now"
+    assert groups == 1, "Do not supoort arbitrary group number"
+
+    # pylint: disable=assignment-from-no-return
+    out = topi.nn.conv2d_winograd_without_weight_transform(
+        inputs[0], inputs[1], strides, padding, layout, out_dtype,
+        tile_size)
+
+    if attrs.get_bool("use_bias"):
+        bias = inputs[2]
+        bias = topi.expand_dims(bias, axis=1, num_newaxis=2)
+        out = topi.add(out, bias)
+    return out
+
+@reg.register_schedule("_contrib_conv2d_winograd_without_weight_transform")
+def schedule_contrib_conv2d_winograd_without_weight_transform(attrs, outs, target):
+    with tvm.target.create(target):
+        return topi.generic.schedule_conv2d_winograd_without_weight_transform(outs)
+
+reg.register_pattern("_contrib_conv2d_winograd_without_weight_transform",
+                     OpPattern.OUT_ELEMWISE_FUSABLE)
+
+
 # conv2d_transpose
 @reg.register_compute("conv2d_transpose")
 def compute_conv2d_transpose(attrs, inputs, _):
diff --git a/nnvm/src/top/nn/convolution.cc b/nnvm/src/top/nn/convolution.cc
index 6a0dad17a4c4..2843bea1f4ad 100644
--- a/nnvm/src/top/nn/convolution.cc
+++ b/nnvm/src/top/nn/convolution.cc
@@ -130,11 +130,110 @@ inline bool Conv2DInferShape(const nnvm::NodeAttrs& attrs,
   return true;
 }
 
+inline bool WinogradConv2DInferShape(const nnvm::NodeAttrs& attrs,
+                                     std::vector<TShape>* in_shape,
+                                     std::vector<TShape>* out_shape) {
+  static const Layout kNCHW("NCHW");
+  static const Layout kOIHW("OIHW");
+
+  const WinogradConv2DParam& param = nnvm::get<WinogradConv2DParam>(attrs.parsed);
+
+  const Layout in_layout(param.layout);
+  const Layout kernel_layout(param.kernel_layout);
+  CHECK(in_layout.convertible(kNCHW))
+    << "Conv only support input layouts that are convertible from NCHW."
+    << " But got " << in_layout;
+  CHECK(kernel_layout.convertible(kOIHW))
+    << "Conv only support kernel layouts that are convertible from OIHW."
+    << " But got "<< kernel_layout;
+
+  Layout out_layout(param.out_layout);
+  if (!out_layout.defined()) out_layout = in_layout;
+  CHECK(out_layout.convertible(kNCHW))
+    << "Conv only support output layouts that are convertible from NCHW."
+    << " But got " << out_layout;
+
+  if (param.use_bias) {
+    CHECK_EQ(in_shape->size(), 3U) << "Input:[data, weight, bias]";
+  } else {
+    CHECK_EQ(in_shape->size(), 2U) << "Input:[data, weight]";
+  }
+  CHECK_EQ(out_shape->size(), 1U);
+
+  TShape dshape = in_shape->at(0);
+  if (dshape.ndim() == 0) return false;
+  dshape = ConvertLayout(dshape, in_layout, kNCHW);
+
+  CHECK_EQ(dshape.ndim(), 4U) << "Input data should be 4D";
+  CHECK_EQ(param.kernel_size.ndim(), 2U);
+  CHECK_EQ(param.strides.ndim(), 2U)
+      << "incorrect stride size: " << param.strides;
+  CHECK_EQ(param.dilation.ndim(), 2U)
+      << "incorrect dilate size: " << param.dilation;
+  CHECK_EQ(dshape[1] % param.groups, 0U)
+      << "input channels must divide group size";
+  CHECK_EQ(param.channels % param.groups, 0U)
+      << "output channels must divide group size";
+
+  // NOTE: Do not check weight shape here!
+  // Different backend requires different layout to compute
+  // the batch gemm stage in winograd efficiently, but we want to
+  // make this NNVM symbol work for all backends.
+  // So we accept all weight shapes, and assume the TOPI developers
+  // can handle this correctly in alter_op_layout.
 
+  if (param.use_bias) {
+    static const Layout default_bias_layout("C");
+    TShape bias_shape({param.channels});
+    auto oc_block = out_layout.subsizeof('C');
+    if (oc_block > 0) {
+      size_t split_axis = (out_layout.indexof('C') < out_layout.indexof('c')) ? 1 : 0;
+      bias_shape = ConvertLayout(bias_shape, default_bias_layout,
+                                 default_bias_layout.split('C', split_axis, oc_block));
+    }
+    NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, WinogradConv2DParam::kBias, bias_shape);
+  }
+  // dilation
+  dim_t dilated_ksize_y = 1 + (param.kernel_size[0] - 1) * param.dilation[0];
+  dim_t dilated_ksize_x = 1 + (param.kernel_size[1] - 1) * param.dilation[1];
+  TShape oshape({dshape[0], param.channels, 0, 0});
+  if (dshape[2] != 0) {
+    oshape[2] = (dshape[2] + param.padding[0] * 2 - dilated_ksize_y) / param.strides[0] + 1;
+  }
+  if (dshape[3] != 0) {
+    oshape[3] = (dshape[3] + param.padding[1] * 2 - dilated_ksize_x) / param.strides[1] + 1;
+  }
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, ConvertLayout(oshape, kNCHW, out_layout));
+  // Perform incomplete shape inference. Fill in the missing values in data shape.
+  // 1) We can always fill in the batch_size.
+  // 2) We can back-calculate the input height/width if the corresponding stride is 1.
+  oshape = ConvertLayout((*out_shape)[0], out_layout, kNCHW);
+  dshape[0] = oshape[0];
+  if (oshape[2] && param.strides[0] == 1) {
+    dshape[2] = oshape[2] + dilated_ksize_y - 1 - 2 * param.padding[0];
+  }
+  if (oshape[3] && param.strides[1] == 1) {
+    dshape[3] = oshape[3] + dilated_ksize_x - 1 - 2 * param.padding[1];
+  }
+  NNVM_ASSIGN_INPUT_SHAPE(attrs, *in_shape, WinogradConv2DParam::kData,
+                          ConvertLayout(dshape, kNCHW, in_layout));
+  // Check whether the kernel sizes are valid
+  if (dshape[2] != 0) {
+    CHECK_LE(dilated_ksize_y, dshape[2] + 2 * param.padding[0])
+      << "kernel size exceed input";
+  }
+  if (dshape[3] != 0) {
+    CHECK_LE(dilated_ksize_x, dshape[3] + 2 * param.padding[1])
+      << "kernel size exceed input";
+  }
+  return true;
+}
+
+template <typename PARAM>
 inline bool Conv2DInferType(const nnvm::NodeAttrs& attrs,
                             std::vector<int>* in_type,
                             std::vector<int>* out_type) {
-  const Conv2DParam& param = nnvm::get<Conv2DParam>(attrs.parsed);
+  const PARAM& param = nnvm::get<PARAM>(attrs.parsed);
   if (param.use_bias) {
     CHECK_EQ(in_type->size(), 3U) << "Input:[data, weight, bias]";
   } else {
@@ -154,11 +253,12 @@ inline bool Conv2DInferType(const nnvm::NodeAttrs& attrs,
 }
 
 
+template<typename PARAM>
 inline bool Conv2DCorrectLayout(const NodeAttrs& attrs,
                                 std::vector<Layout> *ilayouts,
                                 const std::vector<Layout> *last_ilayouts,
                                 std::vector<Layout> *olayouts) {
-  const Conv2DParam& param = nnvm::get<Conv2DParam>(attrs.parsed);
+  const PARAM& param = nnvm::get<PARAM>(attrs.parsed);
 
   const Layout in_layout(param.layout);
   Layout out_layout(param.out_layout);
@@ -213,8 +313,8 @@ a bias vector is created and added to the outputs.
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<Conv2DParam>)
 .set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<Conv2DParam>)
 .set_attr<FInferShape>("FInferShape", Conv2DInferShape)
-.set_attr<FInferType>("FInferType", Conv2DInferType)
-.set_attr<FCorrectLayout>("FCorrectLayout", Conv2DCorrectLayout)
+.set_attr<FInferType>("FInferType", Conv2DInferType<Conv2DParam>)
+.set_attr<FCorrectLayout>("FCorrectLayout", Conv2DCorrectLayout<Conv2DParam>)
 .set_num_outputs(1)
 .set_num_inputs(UseBiasNumInputs<Conv2DParam>)
 .set_support_level(2)
@@ -238,12 +338,81 @@ NNVM_REGISTER_OP(_contrib_conv2d_NCHWc)
 .set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<Conv2DParam>)
 .set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<Conv2DParam>)
 .set_attr<FInferShape>("FInferShape", Conv2DInferShape)
-.set_attr<FInferType>("FInferType", Conv2DInferType)
-.set_attr<FCorrectLayout>("FCorrectLayout", Conv2DCorrectLayout)
+.set_attr<FInferType>("FInferType", Conv2DInferType<Conv2DParam>)
+.set_attr<FCorrectLayout>("FCorrectLayout", Conv2DCorrectLayout<Conv2DParam>)
 .set_num_outputs(1)
 .set_num_inputs(UseBiasNumInputs<Conv2DParam>)
 .set_support_level(2);
 
+
+NNVM_REGISTER_OP(_contrib_conv2d_winograd_weight_transform)
+.describe(R"code(Weight transformation of winograd fast convolution algorithm.
+Separate this into another nnvm symbol in order to enable Precompute Pass to compute the
+weight transformation in advance.
+
+- **weight**: (channels, in_channels, kernel_size[0], kernel_size[1])
+)code" NNVM_ADD_FILELINE)
+.add_argument("weight", "4D Tensor", "Weight tensor.")
+.add_arguments(WinogradWeightTransformParam::__FIELDS__())
+.set_attr_parser(ParamParser<WinogradWeightTransformParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<WinogradWeightTransformParam>)
+.set_attr<FInferShape>("FInferShape", [](const nnvm::NodeAttrs& attrs,
+                                         std::vector<TShape> *in_shape,
+                                         std::vector<TShape> *out_shape) {
+  const auto& param = nnvm::get<WinogradWeightTransformParam>(attrs.parsed);
+  const TShape &wshape = (*in_shape)[0];
+
+  CHECK_EQ(wshape.ndim(), 4) << "Weight should be a 4 dimensional tensor";
+
+  TShape oshape({param.tile_size + wshape[2] - 1,
+                 param.tile_size + wshape[3] - 1,
+                 wshape[0],
+                 wshape[1]});
+  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, oshape);
+  return true;
+  })
+.set_attr<FCorrectLayout>("FCorrectLayot", [](const NodeAttrs& attrs,
+                                              std::vector<Layout> *ilayouts,
+                                              const std::vector<Layout> *last_ilayouts,
+                                              std::vector<Layout> *olayouts) {
+  Layout layout("OIHW");
+  NNVM_ASSIGN_LAYOUT(*ilayouts, 0, layout);
+  NNVM_ASSIGN_LAYOUT(*olayouts, 0, layout);
+  return true;
+})
+.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
+.set_num_outputs(1)
+.set_num_inputs(1)
+.set_support_level(5);
+
+DMLC_REGISTER_PARAMETER(WinogradWeightTransformParam);
+
+NNVM_REGISTER_OP(_contrib_conv2d_winograd_without_weight_transform)
+.describe(R"code(Compute conv2d with winograd algorithm.
+
+- **data**: Input is 4D array of shape  (batch_size, in_channels, height, width)
+- **weight**: Any shape
+            We do not check shape for this input tensor.
+
+- **bias**: (channels,)
+- **out**:  Output is 4D array of shape (batch_size, channels, out_height, out_width)
+)code" NNVM_ADD_FILELINE)
+.add_argument("data", "4D Tensor", "Input data.")
+.add_argument("weight", "Tensor", "Transformed weight tensor.")
+.add_argument("bias", "1D Tensor", "Bias parameter.")
+.add_arguments(WinogradConv2DParam::__FIELDS__())
+.set_attr_parser(ParamParser<WinogradConv2DParam>)
+.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<WinogradConv2DParam>)
+.set_attr<FListInputNames>("FListInputNames", UseBiasListInputNames<WinogradConv2DParam>)
+.set_attr<FInferShape>("FInferShape", WinogradConv2DInferShape)
+.set_attr<FInferType>("FInferType", Conv2DInferType<WinogradConv2DParam>)
+.set_attr<FCorrectLayout>("FCorrectLayout", Conv2DCorrectLayout<WinogradConv2DParam>)
+.set_num_outputs(1)
+.set_num_inputs(UseBiasNumInputs<WinogradConv2DParam>)
+.set_support_level(5);
+
+DMLC_REGISTER_PARAMETER(WinogradConv2DParam);
+
 NNVM_REGISTER_OP(_conv2d_grad)
   .describe(R"code(2D convolution grad.
 
diff --git a/python/tvm/autotvm/__init__.py b/python/tvm/autotvm/__init__.py
index 45d053ef96f7..20426be84aa1 100644
--- a/python/tvm/autotvm/__init__.py
+++ b/python/tvm/autotvm/__init__.py
@@ -18,9 +18,12 @@
 from . import task
 from . import tuner
 from . import util
+from . import env
+from . import tophub
 
 # some shortcuts
-from .measure import measure_option, MeasureInput, MeasureResult, MeasureErrorNo
+from .measure import measure_option, MeasureInput, MeasureResult, MeasureErrorNo, use_rpc
 from .tuner import callback
-from .task import template, get_config, create, ConfigSpace, ConfigEntity
-from .record import ApplyHistoryBest as apply_history_best
+from .task import template, get_config, create, ConfigSpace, ConfigEntity, \
+    ApplyHistoryBest as apply_history_best
+from .env import GLOBAL_SCOPE
diff --git a/python/tvm/autotvm/env.py b/python/tvm/autotvm/env.py
index 3b04c98ea7ce..dc559a7bce1d 100644
--- a/python/tvm/autotvm/env.py
+++ b/python/tvm/autotvm/env.py
@@ -8,5 +8,6 @@ def __init__(self):
         AutotvmGlobalScope.current = self
 
         self.cuda_target_arch = None
+        self.in_tuning = False
 
 GLOBAL_SCOPE = AutotvmGlobalScope()
diff --git a/python/tvm/autotvm/measure/__init__.py b/python/tvm/autotvm/measure/__init__.py
index ac87194ce3e2..f75fbac61e11 100644
--- a/python/tvm/autotvm/measure/__init__.py
+++ b/python/tvm/autotvm/measure/__init__.py
@@ -1,8 +1,7 @@
 """Distributed executor infrastructure to scale up the tuning"""
 
-from .measure import MeasureInput, MeasureResult, MeasureErrorNo
-from .measure import create_measure_batch, measure_option
-from .measure_methods import request_remote
+from .measure import MeasureInput, MeasureResult, MeasureErrorNo, measure_option
+from .measure_methods import request_remote, create_measure_batch, use_rpc
 
 from .local_executor import LocalExecutor
 from .executor import Future, Executor
diff --git a/python/tvm/autotvm/measure/local_executor.py b/python/tvm/autotvm/measure/local_executor.py
index 2ab09dbb18c0..8a045ecfb4c0 100644
--- a/python/tvm/autotvm/measure/local_executor.py
+++ b/python/tvm/autotvm/measure/local_executor.py
@@ -8,7 +8,10 @@
 except ImportError:
     from Queue import Empty
 
-import psutil
+try:
+    import psutil
+except ImportError:
+    psutil = None
 
 from . import executor
 
@@ -106,22 +109,28 @@ def get(self, timeout=None):
 
 
 class LocalExecutor(executor.Executor):
-    """Local executor that runs workers on the same machine with multiprocessing."""
-    def __init__(self, timeout=None):
-        self.timeout = timeout or executor.Executor.DEFAULT_TIMEOUT
+    """Local executor that runs workers on the same machine with multiprocessing.
 
-    def submit(self, func, *args, **kwargs):
-        """
+    Parameters
+    ----------
+    timeout: float, optional
+        timeout of a job. If time is out. A TimeoutError will be returned (not raised)
+    do_fork: bool, optional
+        For some runtime systems that do not support fork after initialization
+        (e.g. cuda runtime, cudnn). Set this to False if you have used these runtime
+        before submitting jobs.
+    """
+    def __init__(self, timeout=None, do_fork=True):
+        self.timeout = timeout or executor.Executor.DEFAULT_TIMEOUT
+        self.do_fork = do_fork
 
-        Note
-        ----------
-        By default, the executor will fork a new process for a new job
-        But some runtime does not support fork (e.g. cuda runtime, cudnn).
-        In this circumstance, you should set 'fork_new_process' to False in kwargs
-        """
-        fork_new_process = kwargs.pop('fork_new_process', True)
+        if self.do_fork:
+            if not psutil:
+                raise RuntimeError("Python package psutil is missing. "
+                                   "please try `pip install psutil`")
 
-        if not fork_new_process:
+    def submit(self, func, *args, **kwargs):
+        if not self.do_fork:
             return LocalFutureNoFork(func(*args, **kwargs))
 
         queue = Queue(1)
diff --git a/python/tvm/autotvm/measure/measure.py b/python/tvm/autotvm/measure/measure.py
index 9f1bf611485a..6a05e1a6a349 100644
--- a/python/tvm/autotvm/measure/measure.py
+++ b/python/tvm/autotvm/measure/measure.py
@@ -1,18 +1,7 @@
 # pylint: disable=pointless-string-statement,consider-using-enumerate,invalid-name
 """User facing API for specifying how to measure the generated code"""
-import time
 from collections import namedtuple
 
-import numpy as np
-
-from ... import build, nd, target as _target
-from ...rpc.tracker import Tracker
-from ...rpc.server import Server
-
-from ..util import get_const_tuple
-from .local_executor import LocalExecutor
-
-
 class MeasureInput(namedtuple("MeasureInput", ["target", "task", "config"])):
     """
     Stores all the necessary inputs for a measurement.
@@ -44,6 +33,7 @@ class MeasureResult(namedtuple("MeasureResult", ["costs", "error_no", "all_cost"
         The absolute time stamp when we finish measurement.
     """
 
+
 class MeasureErrorNo(object):
     """Error type for MeasureResult"""
     NO_ERROR = 0              # no error
@@ -55,38 +45,25 @@ class MeasureErrorNo(object):
     FLEET_ERROR = 6           # error of measure infrastructure
 
 
-def measure_option(mode,
+def measure_option(measure_func,
                    number=1,
                    repeat=1,
                    timeout=60,
                    parallel_num=1,
-                   pack_size=1,
+                   do_fork=True,
+                   build_func='default',
                    check_correctness=False,
-                   build_option=None,
-                   replay_db=None,
-                   save_to_replay_db=True,
-                   rpc_device_key=None,
-                   rpc_priority=1,
-                   rpc_timeout=60,
-                   rpc_tracker_addr=None,
-                   use_ndk=False,
-                   custom_measure_batch=None):
+                   replay_db=None):
     """Configure how to do measurement
 
     Parameters
     ----------
-    mode: str
-        'local': use the local device for measurement. In this mode,
-        the tuner starts a tracker and a RPC server silently for the user.
-
-        'rpc': request devices for measurement from rpc tracker. In this mode,
-        you should start a rpc tracker in a separate processing.
-
-        'custom': use custom measure function
-
-        'local-nofork': use local device for measure but does not use multiprocessing.
-        This mode is suitable for debug, but does not support timeout and parallel.
+    measure_func: str or callable
+        'local': use the local device for measurement. The tuner will start a tracker
+        and a RPC server silently for the user.
 
+        callable: It is a callable function for measurement.
+                  See the return value of measure/measure_methods.py::use_rpc for example.
     number : int, optional
         Number of times to do the measurement for average
     repeat : int, optional
@@ -101,235 +78,50 @@ def measure_option(mode,
         The number of measurement task that can run in parallel.
         Set this according to the number of cpu cores (for compilation) and
         the number of devices you have (for measuring generate code).
-    pack_size : int, optional
-        Number of configs to measure in one RPC call.
-        Usually this can be set to 1. If your device has high cost to establish a rpc connection,
-        set this higher.
-    check_correctness: bool
-        Whether check correctness after measurement.
-    build_option: Dict, optional
-        Build options for tvm.build_config
-
-    replay_db : Database, optional
-        The database that we retrieve saved MeasureResults from
-    save_to_replay_db: bool, optional
-        Whether save measure result to database. This is useless when replay_db is None
+    do_fork: bool, optional
+        Whether use multiprocessing (based on fork) for running measure jobs in parallel.
+        Set this to False if you want to debug (see trackback) or using fork is not suitable.
+        NOTE: If this is False, parallel and timeout do not work.
+    build_func: str or callable, optional
+        'default': call default builder. This works for normal target (llvm, cuda)
 
-    rpc_priority: int, optional
-        Priority of this task, used by scheduler in tracker
-    rpc_device_key: str, optional
-        The device key of registered devices in tracker
-    rpc_timeout: int, optional
-        Timeout of rpc session
-    rpc_tracker_addr: Tuple(str, int), optional
-        The address of rpc tracker in Tuple(host, port) format.
-        If is set, will use this address.
-        If is not set, will use environment variable "TVM_TRACKER_HOST" and "TVM_TRACKER_PORT"
+        'ndk': use Android NDK to create shared library. Use this for android target.
 
-    use_ndk: bool, option
-        Whether export requires ndk
-    custom_measure_batch: callable, optional
-        custom measure function
+        callable: customized build function for other backends (e.g. VTA).
+                  See measure/measure_methods.py::default_build_func for example.
+    check_correctness: bool
+        Whether check correctness after measurement. This will use llvm cpu as reference.
+    replay_db : Database, optional
+        The database that we retrieve saved MeasureResult from.
 
     Returns
     -------
     options: dict
         A dict to store all options
+
+    Note
+    ----
+    To support customized measure, you can pass callable `measure_func` or
+    `build_func` in. The `measure_func` will call `build_func` to build binary library
+    and handle the logic of measurement.
+
+    Signature:
+    * measure_func (see the return value of measure/measure_methods.py::use_rpc for example)
+    def measure_func(input_pack, build_func, build_kwargs, number, repeat, ref_input, ref_output):
+        return measure_results
+
+    * build_func (see measure/measure_methods.py::default_build_func for example)
+    def build_func(inp, tmp_dir, **kwargs):
+        return func, args, filename
     """
     return {
-        'mode': mode,
+        'measure_func': measure_func,
         'number': number,
         'repeat': repeat,
         'timeout': timeout,
         'parallel_num': parallel_num,
-        'pack_size': pack_size,
+        'do_fork': do_fork,
+        'build_func': build_func,
         'check_correctness': check_correctness,
-        'build_option': build_option,
-
         'replay_db': replay_db,
-        'save_to_replay_db': save_to_replay_db,
-
-        'rpc_device_key': rpc_device_key,
-        'rpc_priority': rpc_priority,
-        'rpc_timeout': rpc_timeout,
-        'rpc_tracker_addr': rpc_tracker_addr,
-
-        'use_ndk': use_ndk,
-        'custom_measure_batch': custom_measure_batch
-    }
-
-
-def create_measure_batch(task, options):
-    """Get a standard measure_batch function.
-
-    Parameters
-    ----------
-    task: tvm.autotvm.task.Task
-        The tuning task
-    options: dict
-        The option for measuring generated code.
-        You should use the return value of :any:`autotvm.measure_option` for this argument
-
-    Returns
-    -------
-    measure_batch: callable
-        a callback function to measure a batch of configs
-    """
-    from . import measure_methods
-    from ..database import filter_inputs
-
-    mode = options['mode']
-    number, repeat = options['number'], options['repeat']
-    timeout, parallel_num = options['timeout'], options['parallel_num']
-    pack_size = options['pack_size']
-    check_correctness = options['check_correctness']
-    build_option = options['build_option']
-    replay_db = options['replay_db']
-    save_to_replay_db = options['save_to_replay_db']
-    rpc_device_key = options['rpc_device_key']
-    rpc_priority, rpc_timeout = options['rpc_priority'], options['rpc_timeout']
-    use_ndk = options['use_ndk']
-    custom_measure_batch = options['custom_measure_batch']
-
-    kwargs = {}
-    executor = LocalExecutor(timeout=timeout)
-
-    if mode == 'local':
-        # start temporary rpc tracker and rpc server for the user
-        tracker = Tracker('localhost', port=9000, port_end=10000,
-                          silent=True)
-        rpc_device_key = '$local$device$%d' % tracker.port
-        server = Server('localhost', port=9000, port_end=10000,
-                        key=rpc_device_key,
-                        use_popen=True, silent=True,
-                        tracker_addr=(tracker.host, tracker.port))
-
-        fmeasure = measure_methods.measure_rpc
-        kwargs['rpc_device_key'] = rpc_device_key
-        kwargs['rpc_tracker_addr'] = (tracker.host, tracker.port)
-        kwargs['rpc_timeout'] = timeout
-    elif mode == 'rpc':
-        fmeasure = measure_methods.measure_rpc
-        kwargs['rpc_device_key'] = rpc_device_key
-        kwargs['rpc_priority'] = rpc_priority
-        kwargs['rpc_timeout'] = rpc_timeout
-        kwargs['use_ndk'] = use_ndk
-        assert rpc_device_key, "In rpc mode, a rpc_device_key must be provided"
-    elif mode == "custom":
-        assert callable(custom_measure_batch), "In custom mode, custom_measure_func " \
-                                               "must be a callable object"
-    elif mode == 'local-nofork':
-        fmeasure = measure_methods.measure_local
-        kwargs['fork_new_process'] = False
-    else:
-        raise RuntimeError("Invalid mode: " + mode)
-
-    if 'cuda' in task.target.keys and 'rpc_device_key' in kwargs:  # query cuda device info
-        add_cuda_device_info(kwargs['rpc_device_key'], kwargs.get('rpc_tracker_addr'), kwargs)
-    if 'opencl' in task.target.keys and 'rpc_device_key' in kwargs:
-        add_opencl_device_info(kwargs['rpc_device_key'], kwargs.get('rpc_tracker_addr'), kwargs)
-
-    if check_correctness:
-        # use llvm to generate a reference input/output
-        # this option works for tuning topi, but might not work for you custom op
-        with _target.create("llvm"):
-            s, arg_bufs = task.instantiate(task.config_space.get(0))
-        ref_input = [np.random.uniform(size=get_const_tuple(x.shape)).astype(x.dtype)
-                     for x in arg_bufs]
-        func = build(s, arg_bufs, "llvm")
-        tvm_buf = [nd.array(x) for x in ref_input]
-        func(*tvm_buf)
-        ref_output = [x.asnumpy() for x in tvm_buf]
-        kwargs['ref_input'], kwargs['ref_output'] = ref_input, ref_output
-
-    def measure_batch(measure_inputs):
-        """measure the time cost for a batch of configs in real machines"""
-        if replay_db is not None:
-            partial_results, measure_inputs =\
-                filter_inputs(replay_db, measure_inputs, retry=False)
-
-        # pack configs
-        input_packs = []
-        for i in range(0, len(measure_inputs), pack_size):
-            input_packs.append(measure_inputs[i:i + pack_size])
-
-        # send to measure
-        futures = []
-        for input_pack in input_packs:
-            future = executor.submit(
-                fmeasure, input_pack,
-                number=number,
-                repeat=repeat,
-                build_option=build_option,
-                **kwargs
-            )
-            futures.append(future)
-
-        # transform results
-        results = []
-        for future in futures:
-            result = future.get()
-            if isinstance(result, Exception):
-                if mode == 'local-nofork':
-                    # debug usage, raise exception
-                    raise result
-                tstamp = time.time()
-                results.extend([MeasureResult((result,), MeasureErrorNo.FLEET_ERROR,
-                                              timeout, tstamp)] * pack_size)
-            else:
-                results.extend(result)
-
-        if replay_db is not None:
-            if save_to_replay_db:  # save result to database
-                for measure_input, result in zip(measure_inputs, results):
-                    replay_db.save(measure_input, result)
-
-            result_idx = 0
-            for i in range(len(partial_results)):
-                if partial_results[i] is None:
-                    partial_results[i] = results[result_idx]
-                    result_idx += 1
-            return partial_results
-        return results
-
-    if mode == 'custom':
-        measure_batch = custom_measure_batch
-
-    measure_batch.parallel_num = parallel_num
-    if mode == 'local':
-        measure_batch.aux_objects = {"server": server, "tracker": tracker}
-    return measure_batch
-
-
-def add_cuda_device_info(device_key, rpc_tracker_addr, kwargs):
-    """Query cuda device info. This is used to set the flags for nvcc compiler
-    and check the validity of a generated code."""
-    from .measure_methods import request_remote
-
-    remote = request_remote(device_key, rpc_tracker_addr)
-    ctx = remote.context('cuda', 0)
-    max_dims = ctx.max_thread_dimensions
-    kwargs['check_gpu'] = {
-        'max_shared_memory_per_block': ctx.max_shared_memory_per_block,
-        'max_threads_per_block': ctx.max_threads_per_block,
-        'max_thread_x': max_dims[0],
-        'max_thread_y': max_dims[1],
-        'max_thread_z': max_dims[2],
-    }
-
-    kwargs["cuda_arch"] = "sm_" + "".join(ctx.compute_version.split('.'))
-
-def add_opencl_device_info(device_key, rpc_tracker_addr, kwargs):
-    """Query opencl device info. This is used to check the validity of a generated code."""
-    from .measure_methods import request_remote
-
-    remote = request_remote(device_key, rpc_tracker_addr)
-    ctx = remote.context('opencl', 0)
-    max_dims = ctx.max_thread_dimensions
-    kwargs['check_gpu'] = {
-        'max_shared_memory_per_block': ctx.max_shared_memory_per_block,
-        'max_threads_per_block': ctx.max_threads_per_block,
-        'max_thread_x': max_dims[0],
-        'max_thread_y': max_dims[1],
-        'max_thread_z': max_dims[2],
     }
diff --git a/python/tvm/autotvm/measure/measure_methods.py b/python/tvm/autotvm/measure/measure_methods.py
index 9ed7f3a69c84..c2ce6ceffe79 100644
--- a/python/tvm/autotvm/measure/measure_methods.py
+++ b/python/tvm/autotvm/measure/measure_methods.py
@@ -12,20 +12,24 @@
 
 import numpy as np
 
-from ...contrib import ndk, nvcc, util
-from ... import rpc, ir_pass, build, build_config, nd, context, TVMError, register_func
+from ... import rpc, ir_pass, build, build_config, nd, context, TVMError, register_func, \
+    target as _target
+from ...contrib import nvcc, util, ndk
 
 from ..util import get_const_tuple
 from ..env import AutotvmGlobalScope
-from .measure import MeasureResult, MeasureErrorNo
 from ..task.space import InstantiationError
 
+from .measure import MeasureResult, MeasureErrorNo
+from .local_executor import LocalExecutor
+
 
 class HashMismatchError(ValueError):
     """Raised when the code hash of a submitted config doesn't match that on the
        measure side """
     pass
 
+
 def request_remote(device_key, tracker_addr=None, priority=1, timeout=60):
     """request a remote session
 
@@ -34,7 +38,9 @@ def request_remote(device_key, tracker_addr=None, priority=1, timeout=60):
     device_key: string
         device key of registered device in tracker
     tracker_addr: Tuple(string, int), optional
-        The address of rpc tracker in (host, port) format
+        The address of rpc tracker in (host, port) format.
+        If is none, will use environment variable "TVM_TRACKER_HOST"
+        and "TVM_TRACKER_PORT"
     priority: int, optional
         priority of this request, larger is more prior
     timeout: float, optional
@@ -46,8 +52,8 @@ def request_remote(device_key, tracker_addr=None, priority=1, timeout=60):
     """
     # connect to the tracker
     if tracker_addr:
-        host = tracker_addr[0]
-        port = tracker_addr[1]
+        host = tracker_addr[0] or os.environ['TVM_TRACKER_HOST']
+        port = tracker_addr[1] or int(os.environ['TVM_TRACKER_PORT'])
     else:
         host = os.environ['TVM_TRACKER_HOST']
         port = int(os.environ['TVM_TRACKER_PORT'])
@@ -58,30 +64,234 @@ def request_remote(device_key, tracker_addr=None, priority=1, timeout=60):
     return remote
 
 
-def _measure_generic(fbuild, input_pack, ref_input, ref_output):
-    """Generic measurement function
+def create_measure_batch(task, option):
+    """Get a standard measure_batch function.
+
+    Parameters
+    ----------
+    task: tvm.autotvm.task.Task
+        The tuning task
+    option: dict
+        The option for measuring generated code.
+        You should use the return value of function :any:`measure_option` for this argument.
+
+    Returns
+    -------
+    measure_batch: callable
+        a callback function to measure a batch of configs
+    """
+    from ..database import filter_inputs
+
+    measure_func = option['measure_func']
+    number, repeat = option['number'], option['repeat']
+    timeout, parallel_num, do_fork = option['timeout'], option['parallel_num'], option['do_fork']
+    build_func = option['build_func']
+    check_correctness = option['check_correctness']
+    replay_db = option['replay_db']
+
+    executor = LocalExecutor(timeout=timeout, do_fork=do_fork)
+
+    # convert convenient string to function object
+    attach_objects = None
+    if measure_func == 'local':
+        # start temporary rpc tracker and rpc server for the user
+        tracker = rpc.Tracker('localhost', port=9000, port_end=10000, silent=True)
+        device_key = '$local$device$%d' % tracker.port
+        server = rpc.Server('localhost', port=9000, port_end=10000,
+                            key=device_key,
+                            use_popen=True, silent=True,
+                            tracker_addr=(tracker.host, tracker.port))
+
+        measure_func = use_rpc(device_key, tracker.host, tracker.port)
+        attach_objects = (server, tracker)
+
+    build_kwargs = {}
+    if build_func == 'default':
+        build_func = default_build_func
+    if build_func == 'ndk':
+        build_func = default_build_func
+        build_kwargs['use_ndk'] = True
+
+    # add device info of cuda and opencl target
+    if ('cuda' in task.target.keys or 'opencl' in task.target.keys) \
+            and hasattr(measure_func, 'rpc_info'):
+        rpc_info = measure_func.rpc_info
+        add_gpu_target_info(task.target, rpc_info["key"], (rpc_info["host"], rpc_info["port"]),
+                            build_kwargs)
+
+    if check_correctness:
+        # use llvm cpu to generate a reference input/output
+        # this option works for tuning topi, but might not work for you custom op
+        with _target.create("llvm"):
+            s, arg_bufs = task.instantiate(task.config_space.get(0))
+        ref_input = [np.random.uniform(size=get_const_tuple(x.shape)).astype(x.dtype)
+                     for x in arg_bufs]
+        func = build(s, arg_bufs, "llvm")
+        tvm_buf = [nd.array(x) for x in ref_input]
+        func(*tvm_buf)
+        ref_output = [x.asnumpy() for x in tvm_buf]
+    else:
+        ref_input = ref_output = None
+
+    def measure_batch(measure_inputs):
+        """measure the time cost for a batch of configs in real machines"""
+        if replay_db is not None:
+            partial_results, measure_inputs = \
+                filter_inputs(replay_db, measure_inputs, retry=False)
+
+        # launch measure jobs in parallel
+        pack_size = getattr(measure_func, "pack_size", 1)  # measure `pack_size` inputs in one job
+        futures = []
+        for i in range(0, len(measure_inputs), pack_size):
+            input_pack = measure_inputs[i:i + pack_size]
+            ret = executor.submit(
+                measure_func,
+                input_pack,
+                build_func,
+                build_kwargs,
+                number,
+                repeat,
+                ref_input,
+                ref_output)
+            futures.append(ret)
+
+        # transform results
+        results = []
+        for future in futures:
+            result = future.get()
+            if isinstance(result, Exception):
+                tstamp = time.time()
+                results.extend([MeasureResult((result,), MeasureErrorNo.FLEET_ERROR,
+                                              timeout, tstamp)] * pack_size)
+            else:
+                results.extend(result)
+
+        if replay_db is not None:
+            result_idx = 0
+            for i in range(len(partial_results)):
+                if partial_results[i] is None:
+                    partial_results[i] = results[result_idx]
+                    result_idx += 1
+            return partial_results
+        return results
+
+    measure_batch.parallel_num = parallel_num
+    # attach server and tracker object to avoid them of being garbage-collected
+    measure_batch.attach_objects = attach_objects
+    return measure_batch
+
+
+def use_rpc(key,
+            host=None,
+            port=None,
+            priority=1,
+            session_timeout=60,
+            pack_size=1):
+    """
+    Create a standard measure_func which uses RPC Tracker for measurement.
+    This measure_func will request a device from the RPC Tracker and
+    upload the built binary library to that device for measurement.
+
+    Parameters
+    ----------
+    key: str
+        The registered key of the device in tracker. The tuner will request devices for
+        measurement by this key.
+    host: str, optional
+        The hostname of RPC Tracker. If not set, will use environment variable "TVM_TRACKER_HOST"
+    port: int, optional
+        The port of RPC Tracker. If not set, will use environment variable "TVM_TRACKER_PORT"
+    priority: int, optional
+        Priority of this task, used by scheduler in tracker
+    session_timeout: int, optional
+        Timeout of rpc session
+    pack_size: int, optional
+        The number of configs measure in one RPC session.
+        Usually this can be set to 1. If your device has high overhead to establish a
+        rpc connection, set this higher.
+    """
+    def fmeasure(input_pack, build_func, build_kwargs, number, repeat, ref_input, ref_output):
+        """Do measurement for a list of inputs inside a same RPC session.
+
+        Parameters
+        ----------
+        input_pack: List of MeasureInput
+            The inputs of measurement
+        build_func: callable
+            Function for building the code. see :any:`default_build_func` for example
+        build_kwargs: dict
+            Extra arguments for build_func
+        number : int, optional
+            Number of times to do the measurement for average
+        repeat : int, optional
+            Number of times to repeat the measurement.
+            In total, the generated code will be run (1 + number x repeat) times,
+            where the first one is warm up. The returned result contains `repeat` costs,
+            each of which is the average of `number` test run.
+        ref_input: List of numpy array
+            Reference input for correctness check
+        ref_output: List of numpy array
+            Reference output for correctness check
+
+        Returns
+        -------
+        results: List of MeasureResult
+            The results for input_pack
+        """
+        remote = request_remote(key, (host, port), priority, session_timeout)
+
+        res = _measure_common(input_pack, build_func, build_kwargs, number, repeat,
+                              ref_input, ref_output,
+                              remote)
+        return res
+
+    fmeasure.pack_size = pack_size
+    fmeasure.rpc_info = {"key": key, "host": host, "port": port}
+    return fmeasure
+
+
+def _measure_common(input_pack, build_func, build_kwargs, number, repeat,
+                    ref_input=None, ref_output=None, remote=None):
+    """Measure the time cost for a pack of inputs.
+
+    (Note: A pack is a list of inputs which will be measured inside a same RPC session)
 
     Parameters
     ----------
-    fbuild : function takes MeasureInput returns tuple of (time_func, ctx)
-        The build function used to build each input.
     input_pack : list of MeasureInput
         The inputs we need to evaluate
-    ref_input: Array of np.ndarray
+    build_func : function takes MeasureInput returns tuple of (time_func, ctx, args)
+        The build function used to build each input.
+    build_kwargs: Dict
+        The extra keyword arguments to build_func
+    number : int, optional
+        Number of times to do the measurement for average
+    repeat : int, optional
+        Number of times to repeat the measurement.
+        In total, the generated code will be run (1 + number x repeat) times,
+        where the first one is warm up. The returned result contains `repeat` costs,
+        each of which is the average of `number` test run.
+    ref_input: Array of np.ndarray, optional
         Reference input for checking correctness
-    ref_output: Array of np.ndarray
+    ref_output: Array of np.ndarray, optional
         Reference output for checking correctness
+    remote: RPCSession, optional
+        The remote RPC session
 
     Returns
     -------
-    res_pack : array of MeasureResult
-        The list of execution result of measurement.
+    res_pack : Array of MeasureResult
+        The list of results of measurement.
     """
     res_pack = []
+    tmp_dir = util.tempdir() if remote else None
+
     for inp in input_pack:
         tic = time.time()
+
+        # build function
         try:
-            time_f, ctx, arg_bufs = fbuild(inp)
+            func, arg_bufs, filename = build_func(inp, tmp_dir, **build_kwargs)
         except TVMError as exc:
             tstamp = time.time()
             msg = str(exc)
@@ -92,9 +302,7 @@ def _measure_generic(fbuild, input_pack, ref_input, ref_output):
                     msg = msg.split('\n')[-2].split(": ")[1]
                 except Exception:  # pylint: disable=broad-except
                     pass
-                res_pack.append(MeasureResult((InstantiationError(msg),),
-                                              MeasureErrorNo.INSTANTIATION_ERROR,
-                                              tstamp - tic, tstamp))
+                raise InstantiationError(msg)
             else:
                 res_pack.append(MeasureResult((RuntimeError(msg),),
                                               MeasureErrorNo.COMPILE_HOST,
@@ -107,14 +315,26 @@ def _measure_generic(fbuild, input_pack, ref_input, ref_output):
                                           tstamp - tic, tstamp))
             continue
 
+        # upload built module
+        if remote:
+            remote.upload(tmp_dir.relpath(filename))
+            func = remote.load_module(filename)
+            ctx = remote.context(str(inp.target), 0)
+            time_f = func.time_evaluator(
+                func.entry_name, ctx, number=number, repeat=repeat)
+        else:
+            ctx = context(str(inp.target), 0)
+            time_f = func.time_evaluator(
+                func.entry_name, ctx, number=number, repeat=repeat)
+
         # measure time
         errno = MeasureErrorNo.NO_ERROR
         try:
             if ref_input:
-                args = [nd.array(x, ctx) for x in ref_input]
+                args = [nd.array(x, ctx=ctx) for x in ref_input]
             else:
-                args = [nd.empty(get_const_tuple(x.shape), dtype=x.dtype,
-                                 ctx=ctx) for x in arg_bufs]
+                args = [nd.empty(get_const_tuple(x.shape), dtype=x.dtype, ctx=ctx)
+                        for x in arg_bufs]
             costs = time_f(*args).results
             if len(costs) > 2:  # remove largest and smallest value to reduce variance
                 costs = list(costs)
@@ -135,10 +355,35 @@ def _measure_generic(fbuild, input_pack, ref_input, ref_output):
         res_pack.append(MeasureResult(costs, errno, tstamp - tic, tstamp))
     return res_pack
 
-def _build_func(inp, build_option, kwargs):
-    """Build function module. Exception will be raised when error occurs"""
+
+def default_build_func(inp, tmp_dir=None, **kwargs):
+    """Build function module. Exception will be raised when any error occurs
+
+    Parameters
+    ----------
+    inp: MeasureInput
+       The input of this measurement
+    tmp_dir: tvm.contrib.util.TempDirectory, optional
+       The temporary directory for exporting built binary library.
+       If is not None (in RPC mode), the library in this directory will be uploaded to
+       remote devices.
+    kwargs: Dict, optional
+        Other extra arguments
+
+    Returns
+    -------
+    func: Function
+        TVM built function. Typically this is the return value of tvm.build.
+    args: Array of Buffer or Tensor
+        The argument list for the function. Typically this is the second argument of tvm.build.
+    filename: str
+        The filename of the output build library
+    """
+    # build function
     with inp.target:
         s, args = inp.task.instantiate(inp.config)
+
+        # check invalidity of template and code hash consistency
         if not inp.config.valid():
             raise InstantiationError(inp.config.errors)
         code_hash = getattr(s, 'code_hash', None)
@@ -146,133 +391,61 @@ def _build_func(inp, build_option, kwargs):
             raise HashMismatchError('got {0:s}, expected {1:s}'
                                     .format(str(inp.config.code_hash), str(code_hash)))
 
-        opts = build_option or {}
-        if "check_gpu" in kwargs:
-            values = kwargs['check_gpu']
-            # Add gpu verify pass to filter out invalid configs in advance.
-            # This can accelerate the tuning process
-            check_keys = ['max_shared_memory_per_block', 'max_threads_per_block',
-                          'max_thread_x', 'max_thread_y', 'max_thread_z']
-            opts["add_lower_pass"] = [
-                (2, gpu_verify_pass(**{key: values[key] for key in check_keys}))]
-
+        opts = {}
+        if "check_gpu" in kwargs:  # Add verify pass to filter out invalid configs in advance.
+            opts["add_lower_pass"] = [(2, gpu_verify_pass(**kwargs['check_gpu']))]
         if 'cuda_arch' in kwargs:
             set_cuda_target_arch(kwargs['cuda_arch'])
 
         with build_config(**opts):
             func = build(s, args, target_host=inp.task.target_host)
 
-    return func, args
-
-
-def measure_rpc(input_pack,
-                rpc_device_key,
-                number,
-                repeat=1,
-                build_option=None,
-                rpc_tracker_addr=None,
-                rpc_priority=1,
-                rpc_timeout=60,
-                **kwargs):
-    """Measure the time cost on a device by rpc
+    # export library to temp directory
+    if tmp_dir:
+        if kwargs.get('use_ndk', False):  # for Android NDK
+            filename = "tmp_func_%0x.so" % getrandbits(64)
+            func.export_library(tmp_dir.relpath(filename), ndk.create_shared)
+        else:
+            filename = "tmp_func_%0x.tar" % getrandbits(64)
+            func.export_library(tmp_dir.relpath(filename))
+    else:
+        filename = None
 
-    Parameters
-    ----------
-    input_pack : list of MeasureInput
-        The inputs we need to evaluate
-    rpc_device_key: str
-        The device key of registered devices in tracker
-    number : int
-        Number of times to get the running measurement
-    repeat : int, optional
-        How many times we want to repeat the measurement.
-    build_option: Dict
-        build options for tvm.build_config
+    return func, args, filename
 
-    rpc_tracker_addr: Tuple(string, int), optional
-        The address of rpc tracker in (host, port) format
-        If is none, will use environment variable
-    rpc_priority: int, optional
-        priority of this task, used by scheduler in tracker
-    rpc_timeout: int, optional
-        timeout of the rpc session
 
-    kwargs: dict, optional
-        Additional key word arguments
+def add_gpu_target_info(target, device_key, rpc_tracker_addr, kwargs):
+    """Add device info for gpu target.
+    The info will be used to check the validity of generated code."""
+    remote = request_remote(device_key, rpc_tracker_addr)
+    ctx = remote.context(str(target), 0)
+    max_dims = ctx.max_thread_dimensions
+    kwargs['check_gpu'] = {
+        'max_shared_memory_per_block': ctx.max_shared_memory_per_block,
+        'max_threads_per_block': ctx.max_threads_per_block,
+        'max_thread_x': max_dims[0],
+        'max_thread_y': max_dims[1],
+        'max_thread_z': max_dims[2],
+    }
 
-    Returns
-    -------
-    res_pack : Array of MeasureResult
-        The list of execution results of measurement.
-    """
-    def _fbuild(inp):
-        """ Local build function."""
-        func, args = _build_func(inp, build_option, kwargs)
-
-        tmp_dir = util.tempdir()
-        if not kwargs.get('use_ndk', False):
-            file_name = "tmp_func_%0x.tar" % getrandbits(64)
-            path = tmp_dir.relpath(file_name)
-            func.export_library(path)
-        else:
-            file_name = "tmp_func_%0x.so" % getrandbits(64)
-            path = tmp_dir.relpath(file_name)
-            func.export_library(path, ndk.create_shared)
-        remote = request_remote(rpc_device_key, rpc_tracker_addr, rpc_priority, rpc_timeout)
-        remote.upload(path)
-        func = remote.load_module(file_name)
-        ctx = remote.context(str(inp.target), 0)
-        time_f = func.time_evaluator(
-            func.entry_name, ctx, number=number, repeat=repeat)
-        return time_f, ctx, args
-
-    ret = _measure_generic(_fbuild, input_pack,
-                           kwargs.get("ref_input", None), kwargs.get("ref_output", None))
-    return ret
-
-
-def measure_local(input_pack,
-                  number,
-                  repeat=1,
-                  build_option=None,
-                  **kwargs):
-    """Measure the time cost on a local machine.
-
-    Parameters
-    ----------
-    input_pack : list of MeasureInput
-        The inputs we need to evaluate
-    number : int
-        Number of times to get the running measurement
-    repeat : int, optional
-        How many times we want to repeat the measurement.
-    build_option: dict, optional
-        Build options for tvm.build_config
-    kwargs: dict, optional
-        Additional key word arguments
+    if 'cuda' in target.keys:
+        kwargs["cuda_arch"] = "sm_" + "".join(ctx.compute_version.split('.'))
 
-    Returns
-    -------
-    res_pack : Array of MeasureResult
-        The list of execution results of measurement.
-    """
+def set_cuda_target_arch(arch):
+    """set target architecture of nvcc compiler"""
+    AutotvmGlobalScope.current.cuda_target_arch = arch
 
-    def _fbuild(inp):
-        """ Local build function """
-        func, args = _build_func(inp, build_option, kwargs)
-        ctx = context(str(inp.target), 0)
-        time_f = func.time_evaluator(
-            func.entry_name, ctx, number=number, repeat=repeat)
-        return time_f, ctx, args
 
-    ret = _measure_generic(_fbuild, input_pack,
-                           kwargs.get("ref_input", None), kwargs.get("ref_output", None))
-    return ret
+@register_func
+def tvm_callback_cuda_compile(code):
+    """use nvcc to generate ptx code for better optimization"""
+    ptx = nvcc.compile_cuda(code, target="ptx", arch=AutotvmGlobalScope.current.cuda_target_arch)
+    return ptx
 
 
 def gpu_verify_pass(**kwargs):
-    """Verify the validity of a gpu kernel
-    This pass will check shared memory size and number of threads per block.
+    """Verify the validity of a gpu kernel.
+    This pass will check memory usage and number of threads per block.
     """
     def verify_pass(stmt):
         valid = ir_pass.VerifyGPUCode(stmt, kwargs)
@@ -280,14 +453,3 @@ def verify_pass(stmt):
             raise InstantiationError("Skipped because of invalid gpu kernel")
         return stmt
     return verify_pass
-
-
-@register_func
-def tvm_callback_cuda_compile(code):
-    """use nvcc to generate ptx code for better optimization"""
-    ptx = nvcc.compile_cuda(code, target="ptx", arch=AutotvmGlobalScope.current.cuda_target_arch)
-    return ptx
-
-def set_cuda_target_arch(arch):
-    """set target architecture of nvcc compiler"""
-    AutotvmGlobalScope.current.cuda_target_arch = arch
diff --git a/python/tvm/autotvm/record.py b/python/tvm/autotvm/record.py
index d76a18e13425..a46cee9bf998 100644
--- a/python/tvm/autotvm/record.py
+++ b/python/tvm/autotvm/record.py
@@ -9,15 +9,12 @@
 import pickle
 import json
 import time
-import os
 from collections import OrderedDict
 
-import numpy as np
-
 from .. import build, lower, target as _target
 
 from . import task
-from .task import DispatchContext, ConfigEntity
+from .task import ConfigEntity, ApplyHistoryBest
 from .measure import MeasureInput, MeasureResult
 
 AUTOTVM_LOG_VERSION = 0.1
@@ -120,8 +117,8 @@ def decode(row, protocol='json'):
         tgt = _target.create(str(tgt))
 
         def clean_json_to_python(x):
-            """1. convert all list in x to tuple (hashable)
-               2. convert unicode to str for python2
+            """1. Convert all list in x to tuple (hashable)
+               2. Convert unicode to str for python2
             """
             if isinstance(x, list):
                 return tuple([clean_json_to_python(a) for a in x])
@@ -151,6 +148,7 @@ def clean_json_to_python(x):
     else:
         raise RuntimeError("Invalid log protocol: " + protocol)
 
+
 def load_from_file(filename):
     """Generator: load records from file.
     This is a generator that yields the records.
@@ -168,105 +166,6 @@ def load_from_file(filename):
         yield decode(row)
 
 
-class ApplyHistoryBest(DispatchContext):
-    """
-    Apply the history best config
-
-    Parameters
-    ----------
-    records : str or iterator of (MeasureInput, MeasureResult)
-        Collection of tuning records.
-        If is str, then it should be the filename of a records log file.
-                   Each row of this file is an encoded record pair.
-        Otherwise, it is an iterator.
-    default: ConfigEntity, optional
-        The default config to return when no history records
-    """
-    def __init__(self, records, default=None):
-        super(ApplyHistoryBest, self).__init__()
-
-        self.best_by_targetkey = {}
-        self.best_by_model = {}
-        self._default = default
-
-        self.load(records)
-
-    def load(self, records):
-        """Load records to this dispatch context
-
-        Parameters
-        ----------
-        records : str or iterator of (MeasureInput, MeasureResult)
-            Collection of tuning records.
-            If is str, then it should be the filename of a records log file.
-                       Each row of this file is an encoded record pair.
-            Otherwise, it is an iterator.
-        """
-        if isinstance(records, str):
-            records = load_from_file(records)
-        if not records:
-            return
-
-        best_by_targetkey = self.best_by_targetkey
-        best_by_model = self.best_by_model
-
-        counter = 0
-        for inp, res in records:
-            counter += 1
-            if res.error_no != 0:
-                continue
-
-            # use target keys in tvm target system as key to build best map
-            for k in inp.target.keys:
-                key = (k, inp.task.workload)
-                if key not in best_by_targetkey:
-                    best_by_targetkey[key] = (inp, res)
-                else:
-                    _, other_res = best_by_targetkey[key]
-                    if np.mean(other_res.costs) > np.mean(res.costs):
-                        best_by_targetkey[key] = (inp, res)
-
-            # use model as key to build best map
-            for opt in inp.target.options:
-                if opt.startswith("-model"):
-                    model = opt[7:]
-                    key = (model, inp.task.workload)
-                    if key not in best_by_model:
-                        best_by_model[key] = (inp, res)
-                    else:
-                        _, other_res = best_by_model[key]
-                        if np.mean(other_res.costs) > np.mean(res.costs):
-                            best_by_model[key] = (inp, res)
-                    break
-
-        logging.info("Finish loading %d records", counter)
-
-    def query(self, target, workload):
-        if target is None:
-            raise RuntimeError("Need a target context to find the history best. "
-                               "Hint: If your target is llvm, use `with tvm.target.create('llvm'):`"
-                               " above the dispatcher call. So does other target. ")
-
-        # first try matching by model
-        for opt in target.options:
-            if opt.startswith("-model"):
-                model = opt[7:]
-                key = (model, workload)
-                if key in self.best_by_model:
-                    return self.best_by_model[key][0].config
-
-        # then try matching by target key
-        for k in target.keys:
-            key = (k, workload)
-            if key in self.best_by_targetkey:
-                return self.best_by_targetkey[key][0].config
-
-        if self._default:
-            return self._default
-        raise RuntimeError(
-            "Cannot find config for target=%s, workload=%s" % (target, workload))
-
-
 def split_workload(in_file, clean=True):
     """Split a log file into separate files, each of which contains only a single workload
     This function can also delete duplicated records in log file
@@ -326,7 +225,7 @@ def pick_best(in_file, out_file):
     ----------
     in_file: str
         The filename of input
-    out_file:
+    out_file: str or file
         The filename of output
     """
     best_context = ApplyHistoryBest(load_from_file(in_file))
@@ -338,31 +237,13 @@ def pick_best(in_file, out_file):
     for v in best_context.best_by_targetkey.values():
         best_set.add(measure_str_key(v[0]))
 
-    logging.info("Extract %d best records from the log file", len(best_set))
+    logging.info("Extract %d best records from the %s", len(best_set), in_file)
+    fout = open(out_file, 'w') if isinstance(out_file, str) else out_file
 
-    fout = open(out_file, 'w')
     for inp, res in load_from_file(in_file):
         if measure_str_key(inp) in best_set:
             fout.write(encode(inp, res) + "\n")
-
-
-def load_op_param(rootpath=os.path.join(os.path.expanduser('~'), ".tvm", "op_params")):
-    """Load pre-tuned parameters of operators.
-    This function will load all "*.log" file under root path and select best configs.
-
-    Parameters
-    ----------
-    rootpath: str
-        The root path of stored parameters
-    """
-    best_context = ApplyHistoryBest([])
-    for dirpath, _, filenames in os.walk(rootpath):
-        for filename in filenames:
-            if os.path.splitext(filename)[1] == '.log':
-                best_context.load(os.path.join(dirpath, filename))
-
-    assert not DispatchContext.current, "Cannot load pre-tuned parameters inside a dispatch context"
-    DispatchContext.current = best_context
+            best_set.remove(measure_str_key(inp))
 
 """
 Usage:
diff --git a/python/tvm/autotvm/task/__init__.py b/python/tvm/autotvm/task/__init__.py
index 6d719e00d2a4..0d43f92656cd 100644
--- a/python/tvm/autotvm/task/__init__.py
+++ b/python/tvm/autotvm/task/__init__.py
@@ -9,4 +9,7 @@
 from .task import Task, create, register, template, get_config, args_to_workload
 from .space import ConfigSpace, ConfigEntity
 from .code_hash import attach_code_hash, attach_code_hash_to_arg
-from .dispatcher import DispatchContext, ApplyConfig, dispatcher
+from .dispatcher import DispatchContext, ApplyConfig, ApplyHistoryBest, dispatcher
+
+from .topi_integration import register_topi_compute, register_topi_schedule
+from .nnvm_integration import extract_from_graph
diff --git a/python/tvm/autotvm/task/dispatcher.py b/python/tvm/autotvm/task/dispatcher.py
index df118e2c69ad..beb4e4dcf204 100644
--- a/python/tvm/autotvm/task/dispatcher.py
+++ b/python/tvm/autotvm/task/dispatcher.py
@@ -12,7 +12,10 @@
 """
 from __future__ import absolute_import as _abs
 
+import logging
+
 from decorator import decorate
+import numpy as np
 
 from tvm import target as _target
 
@@ -52,25 +55,6 @@ def __exit__(self, ptype, value, trace):
         DispatchContext.current = self._old_ctx
 
 
-class ApplyConfig(DispatchContext):
-    """Apply a specific config entity during query.
-
-    Parameters
-    ----------
-    config : ConfigSpace or ConfigEntity
-        The specific configuration we care about.
-    """
-    def __init__(self, config):
-        super(ApplyConfig, self).__init__()
-        self._config = config
-        self.workload = None
-
-    def query(self, target, workload):
-        """Override query"""
-        self.workload = workload
-        return self._config
-
-
 def dispatcher(fworkload):
     """Wrap a workload dispatcher function.
 
@@ -137,3 +121,124 @@ def dispatch_func(func, *args, **kwargs):
     fdecorate = decorate(fworkload, dispatch_func)
     fdecorate.register = register
     return fdecorate
+
+
+class ApplyConfig(DispatchContext):
+    """Apply a specific config entity during query.
+
+    Parameters
+    ----------
+    config : ConfigSpace or ConfigEntity
+        The specific configuration we care about.
+    """
+    def __init__(self, config):
+        super(ApplyConfig, self).__init__()
+        self._config = config
+        self.workload = None
+
+    def query(self, target, workload):
+        """Override query"""
+        self.workload = workload
+        return self._config
+
+
+class ApplyHistoryBest(DispatchContext):
+    """
+    Apply the history best config
+
+    Parameters
+    ----------
+    records : str or iterator of (MeasureInput, MeasureResult)
+        Collection of tuning records.
+        If is str, then it should be the filename of a records log file.
+                   Each row of this file is an encoded record pair.
+        Otherwise, it is an iterator.
+    default: ConfigEntity, optional
+        The default config to return when no history records
+    """
+    def __init__(self, records, default=None):
+        super(ApplyHistoryBest, self).__init__()
+
+        self.best_by_targetkey = {}
+        self.best_by_model = {}
+        self._default = default
+
+        if records:
+            self.load(records)
+
+    def load(self, records):
+        """Load records to this dispatch context
+
+        Parameters
+        ----------
+        records : str or iterator of (MeasureInput, MeasureResult)
+            Collection of tuning records.
+            If is str, then it should be the filename of a records log file.
+                       Each row of this file is an encoded record pair.
+            Otherwise, it is an iterator.
+        """
+        from ..record import load_from_file
+
+        if isinstance(records, str):
+            records = load_from_file(records)
+        if not records:
+            return
+
+        best_by_targetkey = self.best_by_targetkey
+        best_by_model = self.best_by_model
+
+        counter = 0
+        for inp, res in records:
+            counter += 1
+            if res.error_no != 0:
+                continue
+
+            # use target keys in tvm target system as key to build best map
+            for k in inp.target.keys:
+                key = (k, inp.task.workload)
+                if key not in best_by_targetkey:
+                    best_by_targetkey[key] = (inp, res)
+                else:
+                    _, other_res = best_by_targetkey[key]
+                    if np.mean(other_res.costs) > np.mean(res.costs):
+                        best_by_targetkey[key] = (inp, res)
+
+            # use model as key to build best map
+            for opt in inp.target.options:
+                if opt.startswith("-model"):
+                    model = opt[7:]
+                    key = (model, inp.task.workload)
+                    if key not in best_by_model:
+                        best_by_model[key] = (inp, res)
+                    else:
+                        _, other_res = best_by_model[key]
+                        if np.mean(other_res.costs) > np.mean(res.costs):
+                            best_by_model[key] = (inp, res)
+                    break
+
+        logging.debug("Finish loading %d records", counter)
+
+    def query(self, target, workload):
+        if target is None:
+            raise RuntimeError("Need a target context to find the history best. "
+                               "Hint: If your target is llvm, use `with tvm.target.create('llvm'):`"
+                               " above the dispatcher call. So does other target. ")
+
+        # first try matching by model
+        for opt in target.options:
+            if opt.startswith("-model"):
+                model = opt[7:]
+                key = (model, workload)
+                if key in self.best_by_model:
+                    return self.best_by_model[key][0].config
+
+        # then try matching by target key
+        for k in target.keys:
+            key = (k, workload)
+            if key in self.best_by_targetkey:
+                return self.best_by_targetkey[key][0].config
+
+        if self._default:
+            return self._default
+        raise RuntimeError(
+            "Cannot find config for target=%s, workload=%s" % (target, workload))
diff --git a/python/tvm/autotvm/task/nnvm_integration.py b/python/tvm/autotvm/task/nnvm_integration.py
new file mode 100644
index 000000000000..a16527f9cb01
--- /dev/null
+++ b/python/tvm/autotvm/task/nnvm_integration.py
@@ -0,0 +1,177 @@
+# pylint: disable=unused-variable,invalid-name
+"""
+Decorator and utilities for the integration with TOPI and NNVM
+
+"""
+import warnings
+
+from ... import tensor, placeholder, target as _target
+
+from ..util import get_const_tuple
+from .task import create, register
+
+
+def serialize_args(args):
+    """serialize arguments of a topi function to a hashable tuple.
+
+    Parameters
+    ----------
+    args: list of hashable or Tensor
+    """
+    ret = []
+    for t in args:
+        if isinstance(t, tensor.Tensor):
+            ret.append(('TENSOR', get_const_tuple(t.shape), t.dtype))
+        else:
+            ret.append(t)
+    return tuple(ret)
+
+
+def deserialize_args(args):
+    """The inverse function of :code:`serialize_args`.
+
+    Parameters
+    ----------
+    args: list of hashable or Tensor
+    """
+    ret = []
+    for t in args:
+        if isinstance(t, tuple) and t[0] == 'TENSOR':
+            ret.append(placeholder(shape=t[1], dtype=t[2]))
+        else:
+            ret.append(t)
+    return ret
+
+
+# Task extractor for nnvm graph
+class TaskExtractEnv:
+    """Global environment for extracting tuning tasks from nnvm graph"""
+    current = None
+
+    def __init__(self):
+        import topi
+        import nnvm
+
+        self.symbol2topi = {
+            nnvm.sym.conv2d: [topi.nn.conv2d, topi.nn.depthwise_conv2d_nchw]
+        }
+
+        self.topi_to_task = {
+            topi.nn.conv2d: "topi_nn_conv2d",
+            topi.nn.depthwise_conv2d_nchw: "topi_nn_depthwise_conv2d_nchw",
+        }
+
+        self._register_dummy()
+        self._register_topi_task()
+        self.task_collection = []
+
+    def _register_dummy(self):
+        """Register dummy function to track the topi function call"""
+        for func in self.topi_to_task:
+            def _local_scope(local_func):
+                """build a scope to holds the function"""
+                @local_func.register("dummy", )
+                def _dummy_func(*args, **kwargs):
+                    assert not kwargs, "Do not support extracting tuning tasks when" \
+                                       "kwargs is used in TOPI function call." \
+                                       "Please modify it to use only positional args."
+
+                    if (self.topi_to_task[local_func], serialize_args(args)) \
+                            not in self.task_collection:
+                        self.task_collection.append((self.topi_to_task[local_func],
+                                                     serialize_args(args)))
+                    with _target.create("opencl"):
+                        return local_func(*args)
+
+            _local_scope(func)
+
+    def _register_topi_task(self):
+        """register tuning wrapper for topi function"""
+        import topi
+
+        # Tuning wrapper for topi functions
+        @register("topi_nn_conv2d")
+        def _topi_nn_conv2d(*args, **kwargs):
+            assert not kwargs, "Do not support kwargs in template function call"
+            args = deserialize_args(args)
+            A, W = args[:2]
+            layout = args[-2]
+            assert layout == 'NCHW', "only support NCHW currently"
+            C = topi.nn.conv2d(*args, **kwargs)
+            s = topi.generic.schedule_conv2d_nchw([C])
+            return s, [A, W, C]
+
+        @register("topi_nn_depthwise_conv2d_nchw")
+        def _topi_nn_depthwise_conv2d_nchw(*args, **kwargs):
+            assert not kwargs, "Do not support kwargs in template function call"
+            args = deserialize_args(args)
+            A, W = args[:2]
+            C = topi.nn.depthwise_conv2d_nchw(*args, **kwargs)
+            s = topi.generic.schedule_depthwise_conv2d_nchw([C])
+            return s, [A, W, C]
+
+    def reset(self):
+        """Reset task collections"""
+        self.task_collection = []
+
+    def get_tasks(self):
+        """Get collected tasks"""
+        return self.task_collection
+
+    @staticmethod
+    def get():
+        """Get the single instance of TaskExtractEnv"""
+        if not TaskExtractEnv.current:
+            TaskExtractEnv.current = TaskExtractEnv()
+        return TaskExtractEnv.current
+
+
+def extract_from_graph(graph, shape, dtype, target, symbols, target_host=None):
+    """ Extract tuning tasks from a nnvm graph.
+
+    This function collects tunning tasks by building the graph
+    with a "dummy" target and tracing all the calls to topi.
+
+    Parameters
+    ----------
+    graph : Graph
+        The graph to tune
+    shape : dict of str to tuple, optional
+        The input shape to the graph
+    dtype : str or dict of str to str
+        The input types to the graph
+    target: tvm.target.Target
+        The compilation target
+    symbols : Array of nnvm.symbol
+        Array of nnvm symbols
+    target_host: tvm.target.Target
+        The host compilation target
+
+    Returns
+    -------
+    task: Array of autotvm.task.Task
+        collected tasks
+    """
+    import nnvm.compiler
+
+    env = TaskExtractEnv.get()
+
+    topi_funcs = []
+    for sym_name in symbols:
+        if sym_name in env.symbol2topi:
+            topi_funcs.extend(env.symbol2topi[sym_name])
+        else:
+            warnings.warn("Symbol %s is not tunable, ignored" % sym_name)
+
+    # run compiler to collect all TOPI calls during compilation
+    env.reset()
+    dummy_target = _target.create("opencl -device=dummy")
+    nnvm.compiler.build(graph, target=dummy_target, shape=shape, dtype=dtype)
+
+    tasks = []
+    for task_name, args in env.get_tasks():
+        tasks.append(create(task_name, args,
+                            target=target, target_host=target_host,
+                            template_key='direct'))
+
+    return tasks
diff --git a/python/tvm/autotvm/task/space.py b/python/tvm/autotvm/task/space.py
index dd0efdc61635..ea823c6f2760 100644
--- a/python/tvm/autotvm/task/space.py
+++ b/python/tvm/autotvm/task/space.py
@@ -21,6 +21,11 @@
 
 Axis = namedtuple('Axis', ['space', 'index'])
 
+try:
+    _long = long
+except NameError:
+    _long = int
+
 
 class InstantiationError(ValueError):
     """Actively detected error in instantiating a template with a config,
@@ -103,7 +108,7 @@ def __init__(self, var, name=None):
             VirtualAxis.name_ct += 1
 
         self.name = name
-        if isinstance(var, int):
+        if isinstance(var, (int, _long)):
             self.length = var
         elif isinstance(var, schedule.IterVar):
             self.name = var.var.name
@@ -114,7 +119,7 @@ def __init__(self, var, name=None):
         elif isinstance(var, VirtualAxis):
             self.length = var.length
         else:
-            raise RuntimeError("Invalid type of axis")
+            raise RuntimeError("Invalid type of axis: " + str(type(var)))
 
     @staticmethod
     def get_num_output(var, name=None):
diff --git a/python/tvm/autotvm/task/task.py b/python/tvm/autotvm/task/task.py
index 8fbb0ddd7aff..7a386f1f9e67 100644
--- a/python/tvm/autotvm/task/task.py
+++ b/python/tvm/autotvm/task/task.py
@@ -362,7 +362,7 @@ def traverse(ops):
                 exp = body[0]
 
                 ret += num_element * _count_flop(exp)
-                ret += traverse([sch[t].op for t in op.input_tensors])
+                ret += traverse([t.op for t in op.input_tensors])
 
             elif isinstance(op, tensor.PlaceholderOp):
                 pass
@@ -382,5 +382,4 @@ def traverse(ops):
         raise RuntimeError("Cannot find float number operation in this operator. "
                            "Please use `cfg.add_flop` to manually set "
                            "FLOP for this operator")
-
     return ret
diff --git a/python/tvm/autotvm/task/topi_integration.py b/python/tvm/autotvm/task/topi_integration.py
new file mode 100644
index 000000000000..012ca4a214e9
--- /dev/null
+++ b/python/tvm/autotvm/task/topi_integration.py
@@ -0,0 +1,193 @@
+# pylint: disable=unused-variable,invalid-name
+"""
+Decorators for registering tunable templates to TOPI.
+
+These decorators can make your simple implementation be able to use different configurations
+for different workloads.
+Here we directly use all arguments to the TOPI call as "workload", so make sure all the arguments
+(except tvm.Tensor) in you calls are hashable. For tvm.Tensor, we will serialize it to a hashable
+tuple.
+
+See tvm/topi/python/topi/arm_cpu/depthwise_conv2d.py for example usage.
+"""
+
+from ... import _api_internal, tensor
+
+from ..util import get_func_name
+from .task import args_to_workload, dispatcher
+
+
+# A table that records all registered dispatcher for all targets
+_REGISTED_DISPATHCER = {
+}
+
+
+def register_topi_compute(topi_compute, target_keys, template_keys, func=None):
+    """Register a tunable template for a topi compute function.
+
+    After the registration. This topi compute will become a configuration dispatcher. It uses
+    all its argument as workload and dispatches configurations according to the input workload.
+
+    It also stores this "workload" to its final ComputeOp, which can be used to reconstruct
+    "workload" in the following topi_schedule call.
+
+    Parameters
+    ----------
+    topi_compute: GenericFunc
+        The topi compute function that will be overloaded
+    target_keys: str or list of str
+        The compilation target. The same as the argument of GenericFunc.register.
+    template_keys: str or list of str
+        The template key.
+        We might have several strategies for a single operator (e.g. direct, im2col, winograd).
+        The template key is used to identity the algorithm strategy.
+        Every operator must have a "direct" template, which is used by default.
+    func: None or callable
+        If it is None, return a decorator.
+        If is callable, decorate this function.
+
+    Returns
+    -------
+    decorator: callable
+        A decorator
+
+    Examples
+    --------
+    See tvm/topi/python/topi/arm_cpu/depthwise_conv2d.py for example usage.
+    """
+    fname = get_func_name(topi_compute)
+
+    def _decorator(f):
+        targets = [target_keys] if isinstance(target_keys, str) else target_keys
+        for target_key in targets:
+            if target_key not in _REGISTED_DISPATHCER:
+                _REGISTED_DISPATHCER[target_key] = {}
+            if topi_compute not in _REGISTED_DISPATHCER:
+                @topi_compute.register(target_key)
+                @dispatcher
+                def config_dispatcher(*args, **kwargs):
+                    """override topi call as a config dispatcher"""
+                    assert not kwargs, "Do not support kwargs in template function call"
+                    return (fname, ) + args_to_workload(args)
+                _REGISTED_DISPATHCER[target_key][topi_compute] = config_dispatcher
+
+            config_dispatcher = _REGISTED_DISPATHCER[target_key][topi_compute]
+
+            @config_dispatcher.register(template_keys)
+            def template_call(cfg, *args, **kwargs):
+                """call the topi func and attach workload to compute node"""
+                assert not kwargs, "Do not support kwargs in template function call"
+
+                if f == topi_compute.fdefault:
+                    node = f(*args, **kwargs)
+                else:
+                    node = f(cfg, *args, **kwargs)
+
+                # attach workload to return op
+                op = node.op
+                attrs = {}
+                for k, v in node.op.attrs.items():
+                    attrs[k] = v
+                attrs['workload'] = (fname, ) + args_to_workload(args)
+                if isinstance(op, tensor.ComputeOp):
+                    op = _api_internal._ComputeOp(
+                        op.name, op.tag, attrs, op.axis, op.body)
+                elif isinstance(op, tensor.ExternOp):
+                    op = _api_internal._ExternOp(
+                        op.name, op.tag, attrs,
+                        op.inputs, op.input_placeholders,
+                        op.output_placeholders, op.body)
+                else:
+                    raise RuntimeError("Unsupported op type: " + str(type(op)))
+
+                if isinstance(node, tensor.Tensor):
+                    return op.output(0)
+                return [op.output(i) for i in range(len(node))]
+
+        return f
+
+    if func:
+        _decorator(func)
+
+    return _decorator
+
+
+def register_topi_schedule(topi_schedule, target_keys, template_keys, func=None):
+    """Register a tunable template for a topi schedule function.
+
+    After the registration. This topi schedule will become a configuration dispatcher. It dispatches
+    configurations according to the input workload.
+
+    Note that this function will try to find "workload" from all the ComputeOp in the input.
+    You can attach "workload" to your compute op by using :any:`register_topi_compute`.
+
+    Parameters
+    ----------
+    topi_schedule: GenericFunc
+        The topi schedule function that will be overloaded
+    target_keys: str or list of str
+        The compilation target
+    template_keys: str or list of str
+        The template key.
+        We might have several strategies for a single operator (e.g. direct, im2col, winograd).
+        The template key is used to identity the algorithm strategy.
+        Every operator must have a "direct" template, which is used by default.
+    func: None or callable
+        If it is None, return a decorator.
+        If is callable, decorate this function.
+
+    Returns
+    -------
+    decorator: callable
+        A decorator
+
+    Examples
+    --------
+    See tvm/topi/python/topi/arm_cpu/depthwise_conv2d.py for example usage.
+    """
+    def _decorator(f):
+        targets = [target_keys] if isinstance(target_keys, str) else target_keys
+        for target_key in targets:
+            if target_key not in _REGISTED_DISPATHCER:
+                _REGISTED_DISPATHCER[target_key] = {}
+            if topi_schedule not in _REGISTED_DISPATHCER[target_key]:
+                @topi_schedule.register(target_key)
+                @dispatcher
+                def config_dispatcher(outs):
+                    """override topi call as a workload dispatcher"""
+                    def traverse(tensors):
+                        """traverse all ops to find attached workload"""
+                        for t in tensors:
+                            op = t.op
+                            if 'workload' in op.attrs:
+                                return op.attrs['workload']
+                            wkl = traverse(op.input_tensors)
+                            if wkl:
+                                return wkl
+                        return None
+
+                    outs = [outs] if isinstance(outs, tensor.Tensor) else outs
+                    workload = traverse(outs)
+
+                    if workload is None:
+                        raise RuntimeError("Cannot find workload in attribute of this schedule")
+
+                    return args_to_workload(workload)
+
+                _REGISTED_DISPATHCER[target_key][topi_schedule] = config_dispatcher
+
+            config_dispatcher = _REGISTED_DISPATHCER[target_key][topi_schedule]
+
+            @config_dispatcher.register(template_keys)
+            def template_call(cfg, outs):
+                """call the schedule func"""
+                if f == topi_schedule.fdefault:
+                    return f(outs)
+                return f(cfg, outs)
+
+        return f
+
+    if func:
+        _decorator(func)
+
+    return _decorator
diff --git a/python/tvm/autotvm/tophub.py b/python/tvm/autotvm/tophub.py
new file mode 100644
index 000000000000..70a3a511ec61
--- /dev/null
+++ b/python/tvm/autotvm/tophub.py
@@ -0,0 +1,123 @@
+"""
+TopHub: Tensor Operator Hub
+To get the best performance, we typically need auto-tuning for the specific devices.
+TVM releases pre-tuned parameters in TopHub for some common networks and hardware targets.
+TVM will download these parameters for you when you create the target for the first time.
+"""
+
+import logging
+import os
+import json
+
+from .task import ApplyHistoryBest
+from .. import target as _target
+from ..contrib.util import tempdir
+from ..contrib.download import download
+
+AUTOTVM_TOPHUB_ROOT_PATH = os.path.join(os.path.expanduser('~'), ".tvm", "tophub")
+
+
+def _alias(name):
+    """convert alias for some packages"""
+    table = {
+        'vtacpu': 'vta',
+    }
+    return table.get(name, name)
+
+
+def context(target, extra_files=None):
+    """Return the dispatch context with pre-tuned parameters.
+    The corresponding downloaded *.log files under tophub root path will be loaded.
+    Users can also add their own files in argument `extra_files`.
+
+    Parameters
+    ----------
+    target: Target
+        The compilation target
+    extra_files: list of str, optional
+        Extra log files to load
+    """
+    rootpath = AUTOTVM_TOPHUB_ROOT_PATH
+    best_context = ApplyHistoryBest([])
+
+    if isinstance(target, str):
+        target = _target.create(target)
+
+    big_target = str(target).split()[0]
+    if os.path.isfile(os.path.join(rootpath, big_target + ".log")):
+        best_context.load(os.path.join(rootpath, big_target + ".log"))
+
+    for opt in target.options:
+        if opt.startswith("-device"):
+            model = _alias(opt[8:])
+            if os.path.isfile(os.path.join(rootpath, model) + ".log"):
+                best_context.load(os.path.join(rootpath, model) + ".log")
+
+    if extra_files:
+        for filename in extra_files:
+            best_context.load(filename)
+
+    return best_context
+
+
+def download_package(backend):
+    """Download pre-tuned parameters of operators for a backend
+
+    Parameters
+    ----------
+    backend: str
+        The name of package
+    """
+    rootpath = AUTOTVM_TOPHUB_ROOT_PATH
+
+    if not os.path.isdir(rootpath):
+        # make directory
+        splits = os.path.split(rootpath)
+        for j in range(1, len(splits)+1):
+            path = os.path.join(*splits[:j])
+            if not os.path.isdir(path):
+                os.mkdir(path)
+
+    backend = _alias(backend)
+    logging.info("Download pre-tuned parameters for %s", backend)
+    download("https://raw.githubusercontent.com/uwsaml/tvm-distro/master/tophub/%s.log" % backend,
+             os.path.join(rootpath, backend + ".log"), True, verbose=0)
+
+
+def check_package(backend):
+    """Check whether have pre-tuned parameters of the certain target.
+    If not, will download it.
+
+    Parameters
+    ----------
+    backend: str
+        The name of package
+    """
+    backend = _alias(backend)
+
+    if os.path.isfile(os.path.join(AUTOTVM_TOPHUB_ROOT_PATH, backend + ".log")):
+        return
+    download_package(backend)
+
+
+def list_packages():
+    """List all available pre-tuned op parameters for targets
+
+    Returns
+    -------
+    ret: List
+        All available packets
+    """
+    path = tempdir()
+    filename = path.relpath("info.json")
+    logging.info("Download meta info for pre-tuned parameters")
+    download("https://raw.githubusercontent.com/uwsaml/tvm-distro/master/tophub/info.json",
+             filename, True, verbose=0)
+
+    with open(filename, "r") as fin:
+        text = "".join(fin.readlines())
+    info = json.loads(text)
+    keys = list(info.keys())
+    keys.sort()
+
+    return [(k, info[k]) for k in keys]
diff --git a/python/tvm/autotvm/tuner/callback.py b/python/tvm/autotvm/tuner/callback.py
index f8265ca70fd7..4737fe510636 100644
--- a/python/tvm/autotvm/tuner/callback.py
+++ b/python/tvm/autotvm/tuner/callback.py
@@ -1,10 +1,13 @@
 # pylint: disable=consider-using-enumerate,invalid-name
 """Namespace of callback utilities of AutoTVM"""
+import sys
+import time
 
 import numpy as np
 
 from .. import record
 
+
 def log_to_file(file_out, protocol='json'):
     """Log the tuning records into file.
     The rows of the log are stored in the format of autotvm.record.encode.
@@ -21,7 +24,6 @@ def log_to_file(file_out, protocol='json'):
     callback : callable
         Callback function to do the logging.
     """
-
     def _callback(_, inputs, results):
         """Callback implementation"""
         if isinstance(file_out, str):
@@ -34,55 +36,21 @@ def _callback(_, inputs, results):
     return _callback
 
 
-def save_tuner_state(prefix, save_every_sample=100):
-    """Save the state of tuner
+def log_to_database(db):
+    """Save the tuning records to a database object.
 
     Parameters
     ----------
-    prefix : srt
-        prefix of the filename to store state
-    save_every_sample: int
-        save the state every x samples
-
-    Returns
-    -------
-    callback : function
-        Callback function to do the auto saving.
+    db: Database
+        The database
     """
-    def _callback(tuner, inputs, results):
-        for _, __ in zip(inputs, results):
-            try:
-                ct = len(tuner.visited)
-            except AttributeError:
-                ct = 0
-            if ct % save_every_sample == 0:
-                tuner.save_state(prefix + "_%d.state" % ct)
-
-    return _callback
-
-
-def log_to_redis(host="localhost", port=6379, dbn=11):
-    """Record the tuning record to a redis DB.
-
-    Parameters
-    ----------
-    host: str, optional
-        Host address of redis db
-    port: int, optional
-        Port of redis db
-    dbn: int, optional
-        which redis db to use, default 11
-    """
-    # import here so only depend on redis when necessary
-    import redis
-    red = redis.StrictRedis(host=host, port=port, db=dbn)
-
     def _callback(_, inputs, results):
         """Callback implementation"""
         for inp, result in zip(inputs, results):
-            red.set(inp, result)
+            db.save(inp, result)
     return _callback
 
+
 class Monitor(object):
     """A monitor to collect statistic during tuning"""
     def __init__(self):
@@ -110,3 +78,47 @@ def trial_scores(self):
     def trial_timestamps(self):
         """get wall clock time stamp of all trials"""
         return np.array(self.timestamps)
+
+
+def progress_bar(total, prefix=''):
+    """Display progress bar for tuning
+
+    Parameters
+    ----------
+    total: int
+        The total number of trials
+    prefix: str
+        The prefix of output message
+    """
+    class _Context:
+        """Context to store local variables"""
+        def __init__(self):
+            self.best_flops = 0
+            self.cur_flops = 0
+            self.ct = 0
+            self.total = total
+
+        def __del__(self):
+            sys.stdout.write(' Done.\n')
+
+    ctx = _Context()
+    tic = time.time()
+
+    def _callback(tuner, inputs, results):
+        ctx.ct += len(inputs)
+
+        flops = 0
+        for inp, res in zip(inputs, results):
+            if res.error_no == 0:
+                flops = inp.task.flop / np.mean(res.costs)
+
+        ctx.cur_flops = flops
+        ctx.best_flops = tuner.best_flops
+
+        sys.stdout.write('\r%s Current/Best: %7.2f/%7.2f GFLOPS | Progress: (%d/%d) '
+                         '| %.2f s' %
+                         (prefix, ctx.cur_flops/1e9, ctx.best_flops/1e9, ctx.ct, ctx.total,
+                          time.time() - tic))
+        sys.stdout.flush()
+
+    return _callback
diff --git a/python/tvm/autotvm/tuner/ga_tuner.py b/python/tvm/autotvm/tuner/ga_tuner.py
index aed39a4e2d10..916bd4ee68c6 100644
--- a/python/tvm/autotvm/tuner/ga_tuner.py
+++ b/python/tvm/autotvm/tuner/ga_tuner.py
@@ -117,3 +117,6 @@ def update(self, inputs, results):
 
     def has_next(self):
         return len(self.visited) - (len(self.genes) - self.trial_pt) < len(self.space)
+
+    def load_history(self, data_set):
+        pass
diff --git a/python/tvm/autotvm/tuner/gridsearch_tuner.py b/python/tvm/autotvm/tuner/gridsearch_tuner.py
index cb1a0832b506..21a17a132640 100644
--- a/python/tvm/autotvm/tuner/gridsearch_tuner.py
+++ b/python/tvm/autotvm/tuner/gridsearch_tuner.py
@@ -25,6 +25,9 @@ def next_batch(self, batch_size):
     def has_next(self):
         return self.counter < len(self.task.config_space)
 
+    def load_history(self, data_set):
+        pass
+
     def __getstate__(self):
         return {"counter": self.counter}
 
@@ -56,6 +59,9 @@ def next_batch(self, batch_size):
     def has_next(self):
         return len(self.visited) < len(self.task.config_space)
 
+    def load_history(self, data_set):
+        pass
+
     def __getstate__(self):
         return {"visited": self.counter}
 
diff --git a/python/tvm/autotvm/tuner/model_based_tuner.py b/python/tvm/autotvm/tuner/model_based_tuner.py
index 91ade1abdc47..d1c1b16d3181 100644
--- a/python/tvm/autotvm/tuner/model_based_tuner.py
+++ b/python/tvm/autotvm/tuner/model_based_tuner.py
@@ -242,7 +242,7 @@ def update(self, inputs, results):
                 self.ys.append(flops)
             else:
                 self.xs.append(index)
-                self.ys.append(0)
+                self.ys.append(0.0)
 
         # if we have enough new training samples
         if len(self.xs) >= self.plan_size * (self.train_ct + 1) \
diff --git a/python/tvm/autotvm/tuner/sa_model_optimizer.py b/python/tvm/autotvm/tuner/sa_model_optimizer.py
index 5ba305fcfbee..2084e0cb0da6 100644
--- a/python/tvm/autotvm/tuner/sa_model_optimizer.py
+++ b/python/tvm/autotvm/tuner/sa_model_optimizer.py
@@ -26,11 +26,11 @@ class SimulatedAnnealingOptimizer(ModelOptimizer):
         If is an Array, then perform linear cooling from temp[0] to temp[1]
     early_stop: int, optional
         Stop iteration if the optimal set do not change in `early_stop` rounds
-    verbose: int, optional
-        Print log every `verbose` iterations
+    log_interval: int, optional
+        Print log every `log_interval` iterations
     """
     def __init__(self, task, n_iter=500, temp=(1, 0), persistent=True, parallel_size=128,
-                 early_stop=50, verbose=50):
+                 early_stop=50, log_interval=50):
         super(SimulatedAnnealingOptimizer, self).__init__()
 
         self.task = task
@@ -41,12 +41,13 @@ def __init__(self, task, n_iter=500, temp=(1, 0), persistent=True, parallel_size
         self.persistent = persistent
         self.parallel_size = min(parallel_size, len(self.task.config_space))
         self.early_stop = early_stop or 1e9
-        self.verbose = verbose
+        self.log_interval = log_interval
         self.points = None
 
     def find_maximums(self, model, num, exclusive):
         tic = time.time()
-        temp, n_iter, early_stop, verbose = self.temp, self.n_iter, self.early_stop, self.verbose
+        temp, n_iter, early_stop, log_interval = \
+                self.temp, self.n_iter, self.early_stop, self.log_interval
 
         if self.persistent and self.points is not None:
             points = self.points
@@ -100,19 +101,18 @@ def find_maximums(self, model, num, exclusive):
             k += 1
             t -= cool
 
-            if verbose >= 1 and k % verbose == 0:
+            if log_interval and k % log_interval == 0:
                 t_str = "%.2f" % t
-                logging.info("SA iter: %d\tlast_update: %d\tmax-0: %.2f\tmax-1: %.2f\ttemp: %s\t"
-                             "elapsed: %.2f",
-                             k, k_last_modify, heap_items[0][0],
-                             np.max([v for v, _ in heap_items]), t_str,
-                             time.time() - tic)
+                logging.debug("SA iter: %d\tlast_update: %d\tmax-0: %.2f\tmax-1: %.2f\ttemp: %s\t"
+                              "elapsed: %.2f",
+                              k, k_last_modify, heap_items[0][0],
+                              np.max([v for v, _ in heap_items]), t_str,
+                              time.time() - tic)
 
         heap_items.sort(key=lambda item: -item[0])
-        if verbose:
-            logging.info("SA iter: %d\tlast_update: %d\tmax-0: %.2f\tmax-1: %.2f\telapsed: %.2f",
-                         k, k_last_modify, heap_items[-1][0], heap_items[0][0], time.time() - tic)
-            logging.info("SA Maximums: %s", heap_items)
+        logging.debug("SA iter: %d\tlast_update: %d\tmax-0: %.2f\tmax-1: %.2f\telapsed: %.2f",
+                      k, k_last_modify, heap_items[-1][0], heap_items[0][0], time.time() - tic)
+        logging.debug("SA Maximums: %s", heap_items)
 
         if self.persistent:
             self.points = points
diff --git a/python/tvm/autotvm/tuner/tuner.py b/python/tvm/autotvm/tuner/tuner.py
index 95afe2eaa3f5..b737a9fc5966 100644
--- a/python/tvm/autotvm/tuner/tuner.py
+++ b/python/tvm/autotvm/tuner/tuner.py
@@ -7,6 +7,7 @@
 from ..measure import MeasureInput
 from ..measure import create_measure_batch
 
+from ..env import GLOBAL_SCOPE
 
 class Tuner(object):
     """Base class for tuners
@@ -64,7 +65,7 @@ def update(self, inputs, results):
         """
         pass
 
-    def tune(self, n_trial, measure_option, early_stop=None, verbose=1, callbacks=()):
+    def tune(self, n_trial, measure_option, early_stopping=None, callbacks=()):
         """Begin tuning
 
         Parameters
@@ -74,11 +75,8 @@ def tune(self, n_trial, measure_option, early_stop=None, verbose=1, callbacks=()
         measure_option: dict
             The options for how to measure generated code.
             You should use the return value ot autotvm.measure_option for this argument.
-        early_stop: int
+        early_stopping: int
             Early stop the tuning when not finding better configs in this number of trials
-        verbose: int
-            0: silent mode, no output
-            1: print every measurement result
         callbacks: List of callable
             A list of callback functions. The signature of callback function is
             (Tuner, List of MeasureInput, List of MeasureResult)
@@ -87,8 +85,9 @@ def tune(self, n_trial, measure_option, early_stop=None, verbose=1, callbacks=()
         """
         measure_batch = create_measure_batch(self.task, measure_option)
         parallel_num = getattr(measure_batch, 'parallel_num', 1)
-        early_stop = early_stop or 1e9
+        early_stopping = early_stopping or 1e9
 
+        GLOBAL_SCOPE.in_tuning = True
         i = 0
         while i < n_trial:
             if not self.has_next():
@@ -99,23 +98,22 @@ def tune(self, n_trial, measure_option, early_stop=None, verbose=1, callbacks=()
             inputs = [MeasureInput(self.task.target, self.task, config) for config in configs]
             results = measure_batch(inputs)
 
-            # print info
-            if verbose >= 1:
-                for k, (inp, res) in enumerate(zip(inputs, results)):
-                    config = inp.config
-                    if res.error_no == 0:
-                        flops = inp.task.flop / np.mean(res.costs)
-                    else:
-                        flops = 0
-                    if flops > self.best_flops:
-                        self.best_flops = flops
-                        self.best_config = config
-                        self.best_measure_pair = (inp, res)
-                        self.best_iter = i + k
-
-                    logging.info("No: %d\tGFLOPS: %.2f/%.2f\tresult: %s\t%s",
-                                 i + k + 1, flops / 1e9, self.best_flops / 1e9,
-                                 res, config)
+            # keep best config
+            for k, (inp, res) in enumerate(zip(inputs, results)):
+                config = inp.config
+                if res.error_no == 0:
+                    flops = inp.task.flop / np.mean(res.costs)
+                else:
+                    flops = 0
+                if flops > self.best_flops:
+                    self.best_flops = flops
+                    self.best_config = config
+                    self.best_measure_pair = (inp, res)
+                    self.best_iter = i + k
+
+                logging.debug("No: %d\tGFLOPS: %.2f/%.2f\tresult: %s\t%s",
+                              i + k + 1, flops / 1e9, self.best_flops / 1e9,
+                              res, config)
 
             i += len(results)
 
@@ -124,10 +122,12 @@ def tune(self, n_trial, measure_option, early_stop=None, verbose=1, callbacks=()
             for callback in callbacks:
                 callback(self, inputs, results)
 
-            if i > self.best_iter + early_stop:
-                logging.info("Early stopped. Best iter: %d.", self.best_iter)
+            if i > self.best_iter + early_stopping:
+                logging.debug("Early stopped. Best iter: %d.", self.best_iter)
                 break
 
+        GLOBAL_SCOPE.in_tuning = False
+
         del measure_batch
 
     def reset(self):
diff --git a/python/tvm/autotvm/tuner/xgboost_cost_model.py b/python/tvm/autotvm/tuner/xgboost_cost_model.py
index 335956f071dd..ce28842a4f37 100644
--- a/python/tvm/autotvm/tuner/xgboost_cost_model.py
+++ b/python/tvm/autotvm/tuner/xgboost_cost_model.py
@@ -42,10 +42,10 @@ class XGBoostCostModel(CostModel):
                      The cost model predicts relative rank score.
     num_threads: int, optional
         The number of threads.
-    verbose: int, optional
-        If is not none, the cost model will print training log every `verbose` iterations.
+    log_interval: int, optional
+        If is not none, the cost model will print training log every `log_interval` iterations.
     """
-    def __init__(self, task, feature_type, loss_type, num_threads=None, verbose=20):
+    def __init__(self, task, feature_type, loss_type, num_threads=None, log_interval=25):
         super(XGBoostCostModel, self).__init__()
 
         if xgb is None:
@@ -60,7 +60,7 @@ def __init__(self, task, feature_type, loss_type, num_threads=None, verbose=20):
         self.fea_type = feature_type
         self.loss_type = loss_type
         self.num_threads = num_threads
-        self.verbose = verbose
+        self.log_interval = log_interval
 
         if loss_type == 'reg':
             self.xgb_params = {
@@ -139,7 +139,8 @@ def fit(self, xs, ys, plan_size):
 
         x_train = self._get_feature(xs)
         y_train = np.array(ys)
-        y_train = y_train / np.max(y_train)
+        y_max = np.max(y_train)
+        y_train = y_train / max(y_max, 1e-8)
 
         valid_index = y_train > 1e-6
         index = np.random.permutation(len(x_train))
@@ -160,19 +161,20 @@ def fit(self, xs, ys, plan_size):
                                  fevals=[
                                      xgb_average_recalln_curve_score(plan_size),
                                  ],
-                                 verbose_eval=self.verbose)])
+                                 verbose_eval=self.log_interval)])
 
-        logging.info("train: %.2f\tobs: %d\terror: %d\tn_cache: %d",
-                     time.time() - tic, len(xs),
-                     len(xs) - np.sum(valid_index),
-                     self.feature_cache.size(self.fea_type))
+        logging.debug("XGB train: %.2f\tobs: %d\terror: %d\tn_cache: %d",
+                      time.time() - tic, len(xs),
+                      len(xs) - np.sum(valid_index),
+                      self.feature_cache.size(self.fea_type))
 
     def fit_log(self, records, plan_size):
         tic = time.time()
         self._reset_pool()
 
         args = list(records)
-        logging.info("Load %d entries from history log file", len(args))
+        logging.debug("XGB load %d entries from history log file", len(args))
+
         if self.fea_type == 'itervar':
             feature_extract_func = _extract_itervar_feature_log
         elif self.fea_type == 'knob':
@@ -187,7 +189,8 @@ def fit_log(self, records, plan_size):
 
         x_train = xs
         y_train = ys
-        y_train /= np.max(y_train)
+        y_max = np.max(y_train)
+        y_train = y_train / max(y_max, 1e-8)
 
         index = np.random.permutation(len(x_train))
         dtrain = xgb.DMatrix(x_train[index], y_train[index])
@@ -203,9 +206,9 @@ def fit_log(self, records, plan_size):
                                  fevals=[
                                      xgb_average_recalln_curve_score(plan_size),
                                  ],
-                                 verbose_eval=self.verbose)])
+                                 verbose_eval=self.log_interval)])
 
-        logging.info("train: %.2f\tobs: %d", time.time() - tic, len(xs))
+        logging.debug("XGB train: %.2f\tobs: %d", time.time() - tic, len(xs))
 
     def predict(self, xs, output_margin=False):
         feas = self._get_feature(xs)
@@ -232,7 +235,7 @@ def load_basemodel(self, base_model):
 
     def clone_new(self):
         return XGBoostCostModel(self.task, self.fea_type, self.loss_type,
-                                self.num_threads, self.verbose)
+                                self.num_threads, self.log_interval)
 
     def _get_feature(self, indexes):
         """get features for indexes, run extraction if we do not have cache for them"""
@@ -282,7 +285,7 @@ def _extract_itervar_feature_log(arg):
     if res.error_no == 0:
         y = inp.task.flop / np.mean(res.costs)
     else:
-        y = 0
+        y = 0.0
     return x, y
 
 def _extract_knob_feature_index(index):
@@ -301,7 +304,7 @@ def _extract_knob_feature_log(arg):
             inp.task.instantiate(config)
         y = inp.task.flop / np.mean(res.costs)
     else:
-        y = 0
+        y = 0.0
     return x, y
 
 def _extract_curve_feature_index(index):
@@ -325,12 +328,11 @@ def _extract_curve_feature_log(arg):
     if res.error_no == 0:
         y = inp.task.flop / np.mean(res.costs)
     else:
-        y = 0
+        y = 0.0
     return x, y
 
 
 def custom_callback(stopping_rounds, metric, fevals, evals=(), log_file=None,
-                    save_file="xgb_checkpoint", save_every=None,
                     maximize=False, verbose_eval=True):
     """callback function for xgboost to support multiple custom evaluation functions"""
     from xgboost.core import EarlyStopException
@@ -400,18 +402,12 @@ def callback(env):
                 continue
             infos.append("%s: %.6f" % (item[0], item[1]))
 
-        if not isinstance(verbose_eval, bool) and i % verbose_eval == 0:
-            logging.info("\t".join(infos))
+        if not isinstance(verbose_eval, bool) and verbose_eval and i % verbose_eval == 0:
+            logging.debug("\t".join(infos))
         if log_file:
             with open(log_file, "a") as fout:
                 fout.write("\t".join(infos) + '\n')
 
-        ##### save model #####
-        if save_every and i % save_every == 0:
-            filename = save_file + ".%05d.bst" % i
-            logging.info("save model to %s ...", filename)
-            bst.save_model(filename)
-
         ##### choose score and do early stopping #####
         score = None
         for item in eval_res:
@@ -439,7 +435,7 @@ def callback(env):
         elif env.iteration - best_iteration >= stopping_rounds:
             best_msg = state['best_msg']
             if verbose_eval and env.rank == 0:
-                logging.info("Stopping. Best iteration: %s ", best_msg)
+                logging.debug("XGB stopped. Best iteration: %s ", best_msg)
             raise EarlyStopException(best_iteration)
 
     return callback
diff --git a/python/tvm/autotvm/tuner/xgboost_tuner.py b/python/tvm/autotvm/tuner/xgboost_tuner.py
index eb3978b5c135..237ac4e19ab1 100644
--- a/python/tvm/autotvm/tuner/xgboost_tuner.py
+++ b/python/tvm/autotvm/tuner/xgboost_tuner.py
@@ -40,16 +40,21 @@ class XGBTuner(ModelBasedTuner):
         If is not None, the tuner will first select
         top-(plan_size * diversity_filter_ratio) candidates according to the cost model
         and then pick batch_size of them according to the diversity metric.
+    log_interval: int, optional
+        The verbose level.
+        If is 0, output nothing.
+        Otherwise, output debug information every `verbose` iterations.
     """
     def __init__(self, task, plan_size=32,
                  feature_type='itervar', loss_type='rank', num_threads=None,
-                 optimizer='sa', diversity_filter_ratio=None):
+                 optimizer='sa', diversity_filter_ratio=None, log_interval=50):
         cost_model = XGBoostCostModel(task,
                                       feature_type=feature_type,
                                       loss_type=loss_type,
-                                      num_threads=num_threads)
+                                      num_threads=num_threads,
+                                      log_interval=log_interval // 2)
         if optimizer == 'sa':
-            optimizer = SimulatedAnnealingOptimizer(task)
+            optimizer = SimulatedAnnealingOptimizer(task, log_interval=log_interval)
         else:
             assert isinstance(optimizer, ModelOptimizer), "Optimizer must be " \
                                                           "a supported name string" \
diff --git a/python/tvm/autotvm/util.py b/python/tvm/autotvm/util.py
index df58bde38558..99a2c85aa10e 100644
--- a/python/tvm/autotvm/util.py
+++ b/python/tvm/autotvm/util.py
@@ -8,6 +8,16 @@
 
 from .. import expr, ir_pass
 
+
+class EmptyContext(object):
+    """An empty context"""
+    def __enter__(self):
+        pass
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        pass
+
+
 def get_rank(values):
     """get rank of items
 
diff --git a/python/tvm/contrib/download.py b/python/tvm/contrib/download.py
index 6e86ca1daabf..434216a2652c 100644
--- a/python/tvm/contrib/download.py
+++ b/python/tvm/contrib/download.py
@@ -6,7 +6,7 @@
 import sys
 import time
 
-def download(url, path, overwrite=False, size_compare=False):
+def download(url, path, overwrite=False, size_compare=False, verbose=1):
     """Downloads the file from the internet.
     Set the input options correctly to overwrite or do the size comparison
 
@@ -23,9 +23,10 @@ def download(url, path, overwrite=False, size_compare=False):
 
     size_compare : bool, optional
         Whether to do size compare to check downloaded file.
-    """
 
-    import requests
+    verbose: int, optional
+        Verbose level
+    """
     if sys.version_info >= (3,):
         import urllib.request as urllib2
     else:
@@ -33,6 +34,7 @@ def download(url, path, overwrite=False, size_compare=False):
 
     if os.path.isfile(path) and not overwrite:
         if size_compare:
+            import requests
             file_size = os.path.getsize(path)
             res_head = requests.head(url)
             res_get = requests.get(url, stream=True)
@@ -45,7 +47,9 @@ def download(url, path, overwrite=False, size_compare=False):
                 return
         print('File {} exists, skip.'.format(path))
         return
-    print('Downloading from url {} to {}'.format(url, path))
+
+    if verbose >= 1:
+        print('Downloading from url {} to {}'.format(url, path))
 
     # Stateful start time
     start_time = time.time()
diff --git a/python/tvm/contrib/util.py b/python/tvm/contrib/util.py
index fe176dee2791..0d94a8da5058 100644
--- a/python/tvm/contrib/util.py
+++ b/python/tvm/contrib/util.py
@@ -142,3 +142,35 @@ def which(exec_name):
         if os.path.isfile(full_path) and os.access(full_path, os.X_OK):
             return full_path
     return None
+
+def get_lower_ir(s):
+    """Get lower ir code of a schedule.
+    This is useful for debug, since you don't have to find all inputs/outputs
+    for a schedule in a fused subgraph.
+
+    Parameters
+    ----------
+    s: Schedule
+
+    Returns
+    -------
+    ir: str
+        The lower ir
+    """
+    from .. import tensor
+    from ..build_module import lower
+
+    outputs = s.outputs
+
+    inputs = []
+    def find_all(op):
+        if isinstance(op, tensor.PlaceholderOp):
+            inputs.append(op.output(0))
+        else:
+            for x in op.input_tensors:
+                find_all(x.op)
+
+    for out in outputs:
+        find_all(out)
+
+    return lower(s, inputs, simple_mode=True)
diff --git a/python/tvm/exec/autotvm_log_editor.py b/python/tvm/exec/autotvm_log_editor.py
new file mode 100644
index 000000000000..c524fb5dc785
--- /dev/null
+++ b/python/tvm/exec/autotvm_log_editor.py
@@ -0,0 +1,44 @@
+# pylint: disable=invalid-name
+"""Pick best log entries from a large file and store them to a small file"""
+
+import argparse
+import os
+import logging
+import warnings
+
+from .. import autotvm
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--act", type=str, choices=['pick-best'],
+                        help="The action")
+    parser.add_argument("--i", type=str, help="The input file or directory")
+    parser.add_argument("--o", type=str, help="The output file")
+
+    args = parser.parse_args()
+    logging.basicConfig(level=logging.INFO)
+
+    if args.act == 'pick-best':
+        if os.path.isfile(args.i):
+            args.o = args.o or args.i + ".best.log"
+            autotvm.record.pick_best(args.i, args.o)
+        elif os.path.isdir(args.i):
+            args.o = args.o or "best.log"
+            tmp_filename = args.o + ".tmp"
+
+            with open(tmp_filename, 'w') as tmp_fout:
+                for filename in os.listdir(args.i):
+                    if filename.endswith(".log"):
+                        try:
+                            autotvm.record.pick_best(filename, tmp_fout)
+                        except Exception:  # pylint: disable=broad-except
+                            warnings.warn("Ignore invalid file %s" % filename)
+
+            logging.info("Run final filter...")
+            autotvm.record.pick_best(tmp_filename, args.o)
+            os.remove(tmp_filename)
+            logging.info("Output to %s ...", args.o)
+        else:
+            raise ValueError("Invalid input file: " + args.i)
+    else:
+        raise ValueError("Invalid action " + args.act)
diff --git a/python/tvm/exec/rpc_server.py b/python/tvm/exec/rpc_server.py
index c9f0777fad57..5998e9ffe6ac 100644
--- a/python/tvm/exec/rpc_server.py
+++ b/python/tvm/exec/rpc_server.py
@@ -40,20 +40,21 @@ def main(args):
                         help='The port of the PRC')
     parser.add_argument('--port-end', type=int, default=9199,
                         help='The end search port of the PRC')
+    parser.add_argument('--tracker', type=str,
+                        help="The address of RPC tracker in host:port format. "
+                             "e.g. (10.77.1.234:9190)")
     parser.add_argument('--key', type=str, default="",
-                        help="RPC key used to identify the connection type.")
-    parser.add_argument('--load-library', type=str, default="",
+                        help="The key used to identify the device type in tracker.")
+    parser.add_argument('--silent', action='store_true',
+                        help="Whether run in silent mode.")
+    parser.add_argument('--load-library', type=str,
                         help="Additional library to load")
-    parser.add_argument('--tracker', type=str, default="",
-                        help="Report to RPC tracker")
     parser.add_argument('--no-fork', dest='fork', action='store_false',
                         help="Use spawn mode to avoid fork. This option \
                          is able to avoid potential fork problems with Metal, OpenCL \
                          and ROCM compilers.")
     parser.add_argument('--custom-addr', type=str,
                         help="Custom IP Address to Report to RPC Tracker")
-    parser.add_argument('--silent', action='store_true',
-                        help="Whether run in silent mode.")
 
     parser.set_defaults(fork=True)
     args = parser.parse_args()
diff --git a/python/tvm/exec/rpc_tracker.py b/python/tvm/exec/rpc_tracker.py
index 3ac013d649f7..3a89014f77a4 100644
--- a/python/tvm/exec/rpc_tracker.py
+++ b/python/tvm/exec/rpc_tracker.py
@@ -6,13 +6,12 @@
 import argparse
 import multiprocessing
 import sys
-from ..rpc.tracker import Tracker
-
+from .. import rpc
 
 def main(args):
     """Main funciton"""
-    tracker = Tracker(args.host, port=args.port, port_end=args.port_end,
-                      silent=args.silent)
+    tracker = rpc.Tracker(args.host, port=args.port, port_end=args.port_end,
+                          silent=args.silent)
     tracker.proc.join()
 
 
diff --git a/python/tvm/exec/tophub.py b/python/tvm/exec/tophub.py
new file mode 100644
index 000000000000..9dd951a52701
--- /dev/null
+++ b/python/tvm/exec/tophub.py
@@ -0,0 +1,36 @@
+# pylint: disable=invalid-name
+"""Download pre-tuned parameters of ops"""
+
+import argparse
+import logging
+
+from ..autotvm.tophub import list_packages, download_package
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--download", type=str, nargs='+',
+                        help="Target to download. Use 'all' to download for all targets")
+    parser.add_argument("-l", "--list", action='store_true', help="List available packages")
+    args = parser.parse_args()
+
+    logging.basicConfig(level=logging.INFO)
+
+    if args.list:
+        info = list_packages()
+        print("\n%-20s %-20s" % ("Target", "Size"))
+        print("-" * 41)
+        for target, info in info:
+            print("%-20s %-20s" % (target, "%.2f MB" % (info['size']/1000000)))
+
+    if args.download:
+        info = list_packages()
+        all_targets = [x[0] for x in info]
+        if 'all' in args.download:
+            targets = all_targets
+        else:
+            targets = args.download
+
+        for t in targets:
+            if t not in all_targets:
+                print("Warning : cannot find tuned parameters of " + t + ". (ignored)")
+            download_package(t)
diff --git a/python/tvm/rpc/__init__.py b/python/tvm/rpc/__init__.py
index 6a356e2d64ff..974151c1e5b0 100644
--- a/python/tvm/rpc/__init__.py
+++ b/python/tvm/rpc/__init__.py
@@ -10,4 +10,6 @@
 """
 
 from .server import Server
+from .tracker import Tracker
+from .proxy import Proxy
 from .client import RPCSession, LocalSession, TrackerSession, connect, connect_tracker
diff --git a/python/tvm/rpc/client.py b/python/tvm/rpc/client.py
index 70af8c48538a..57f368b0e660 100644
--- a/python/tvm/rpc/client.py
+++ b/python/tvm/rpc/client.py
@@ -225,18 +225,24 @@ def text_summary(self):
             res += item["key"] + "\n"
         res += "----------------------------\n"
         res += "\n"
-        res += "Queue Status\n"
-        res += "----------------------------\n"
-        res += "key\tfree\tpending\n"
-        res += "----------------------------\n"
+
+        # compute max length of device key
         queue_info = data['queue_info']
         keys = list(queue_info.keys())
         if keys:
             keys.sort()
             max_key_len = max([len(k) for k in keys])
-            for k in keys:
-                res += ("%%-%d" % max_key_len + "s\t%d\t%g\n") % \
-                       (k, queue_info[k]["free"], queue_info[k]["pending"])
+        else:
+            max_key_len = 0
+
+        res += "Queue Status\n"
+        res += "----------------------------\n"
+        res += ("%%-%ds" % max_key_len + "\tfree\tpending\n") % 'key'
+        res += "----------------------------\n"
+        for k in keys:
+            res += ("%%-%ds" % max_key_len + "\t%d\t%g\n") % \
+                   (k, queue_info[k]["free"], queue_info[k]["pending"])
+
         res += "----------------------------\n"
         return res
 
diff --git a/python/tvm/rpc/proxy.py b/python/tvm/rpc/proxy.py
index 44de99e7e959..9afb9ca1a667 100644
--- a/python/tvm/rpc/proxy.py
+++ b/python/tvm/rpc/proxy.py
@@ -460,6 +460,10 @@ class Proxy(object):
     timeout_server : float, optional
         Timeout of server until it sees a matching connection.
 
+    tracker_addr: Tuple (str, int) , optional
+        The address of RPC Tracker in tuple (host, ip) format.
+        If is not None, the server will register itself to the tracker.
+
     index_page : str, optional
         Path to an index page that can be used to display at proxy index.
 
diff --git a/python/tvm/rpc/server.py b/python/tvm/rpc/server.py
index 0d6112df6089..1d6c0226f138 100644
--- a/python/tvm/rpc/server.py
+++ b/python/tvm/rpc/server.py
@@ -20,6 +20,7 @@
 import subprocess
 import time
 import sys
+import signal
 
 from .._ffi.function import register_func
 from .._ffi.base import py_str
@@ -257,7 +258,7 @@ def _popen(cmd):
 
 
 class Server(object):
-    """Start RPC server on a seperate process.
+    """Start RPC server on a separate process.
 
     This is a simple python implementation based on multi-processing.
     It is also possible to implement a similar C based sever with
@@ -284,14 +285,21 @@ class Server(object):
         This is recommended to switch on if we want to do local RPC demonstration
         for GPU devices to avoid fork safety issues.
 
-    silent: bool, optional
-        Whether run this server in silent mode.
+    tracker_addr: Tuple (str, int) , optional
+        The address of RPC Tracker in tuple(host, ip) format.
+        If is not None, the server will register itself to the tracker.
 
     key : str, optional
-        The key used to identify the server in Proxy connection.
+        The key used to identify the device type in tracker.
 
     load_library : str, optional
         List of additional libraries to be loaded during execution.
+
+    custom_addr: str, optional
+        Custom IP Address to Report to RPC Tracker
+
+    silent: bool, optional
+        Whether run this server in silent mode.
     """
     def __init__(self,
                  host,
@@ -299,11 +307,11 @@ def __init__(self,
                  port_end=9199,
                  is_proxy=False,
                  use_popen=False,
-                 silent=False,
                  tracker_addr=None,
                  key="",
                  load_library=None,
-                 custom_addr=None):
+                 custom_addr=None,
+                 silent=False):
         try:
             if base._ServerLoop is None:
                 raise RuntimeError("Please compile with USE_RPC=1")
@@ -313,6 +321,7 @@ def __init__(self,
         self.port = port
         self.libs = []
         self.custom_addr = custom_addr
+        self.use_popen = use_popen
 
         self.logger = logging.getLogger("RPCServer")
         if silent:
@@ -334,10 +343,7 @@ def __init__(self,
             if silent:
                 cmd += ["--silent"]
 
-            self.proc = multiprocessing.Process(
-                target=subprocess.check_call, args=(cmd,))
-            self.proc.deamon = True
-            self.proc.start()
+            self.proc = subprocess.Popen(cmd, preexec_fn=os.setsid)
             time.sleep(0.5)
         elif not is_proxy:
             sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -371,9 +377,14 @@ def __init__(self,
 
     def terminate(self):
         """Terminate the server process"""
-        if self.proc:
-            self.proc.terminate()
-            self.proc = None
+        if self.use_popen:
+            if self.proc:
+                os.killpg(self.proc.pid, signal.SIGTERM)
+                self.proc = None
+        else:
+            if self.proc:
+                self.proc.terminate()
+                self.proc = None
 
     def __del__(self):
         self.terminate()
diff --git a/python/tvm/target.py b/python/tvm/target.py
index f983065306bf..fed20c3914c6 100644
--- a/python/tvm/target.py
+++ b/python/tvm/target.py
@@ -40,6 +40,8 @@
 """
 from __future__ import absolute_import
 
+import warnings
+
 from ._ffi.base import _LIB_NAME
 from ._ffi.node import NodeBase, register_node
 from . import _api_internal
@@ -51,7 +53,6 @@
     if _LIB_NAME != "libtvm_runtime.so":
         raise err_msg
 
-
 def _merge_opts(opts, new_opts):
     """Helper function to merge options"""
     if isinstance(new_opts, str):
@@ -72,7 +73,7 @@ class Target(NodeBase):
     Do not use class constructor, you can create target using the following functions
 
     - :any:`tvm.target.create` create target from string
-    - :any:`tvm.target.rasp` create raspberry pi target
+    - :any:`tvm.target.arm_cpu` create arm_cpu target
     - :any:`tvm.target.cuda` create CUDA target
     - :any:`tvm.target.rocm` create ROCM target
     - :any:`tvm.target.mali` create Mali target
@@ -374,22 +375,6 @@ def rocm(options=None):
     return _api_internal._TargetCreate("rocm", *options)
 
 
-def rasp(options=None):
-    """Returns a rasp target.
-
-    Parameters
-    ----------
-    options : str or list of str
-        Additional options
-    """
-    opts = ["-device=rasp",
-            "-mtriple=armv7l-none-linux-gnueabihf",
-            "-mcpu=cortex-a53",
-            "-mattr=+neon"]
-    opts = _merge_opts(opts, options)
-    return _api_internal._TargetCreate("llvm", *opts)
-
-
 def mali(options=None):
     """Returns a ARM Mali GPU target.
 
@@ -428,6 +413,52 @@ def opengl(options=None):
     return _api_internal._TargetCreate("opengl", *options)
 
 
+def arm_cpu(model='unknown', options=None):
+    """Returns a ARM CPU target.
+    This function will also download pre-tuned op parameters when there is none.
+
+    Parameters
+    ----------
+    model: str
+        SoC name or phone name of the arm board.
+    options : str or list of str
+        Additional options
+    """
+    from . import autotvm
+
+    trans_table = {
+        "pixel2":    ["-model=snapdragon835", "-target=arm64-linux-android"],
+        "mate10":    ["-model=kirin970", "-target=arm64-linux-android"],
+        "mate10pro": ["-model=kirin970", "-target=arm64-linux-android"],
+        "p20":       ["-model=kirin970", "-target=arm64-linux-android"],
+        "p20pro":    ["-model=kirin970", "-target=arm64-linux-android"],
+        "rasp3b":    ["-model=bcm2837", "-target=armv7l-linux-gnueabihf"],
+        "rk3399":    ["-model=rk3399", "-target=aarch64-linux-gnu"],
+        "pynq":      ["-model=pynq", "-target=armv7a-linux-eabi"],
+    }
+    pre_defined_opt = trans_table.get(model, ["-model=%s" % model])
+
+    # download pre-tuned parameters for arm_cpu if there is not any.
+    autotvm.tophub.check_package('arm_cpu')
+
+    opts = ["-device=arm_cpu"] + pre_defined_opt
+    opts = _merge_opts(opts, options)
+    return _api_internal._TargetCreate("llvm", *opts)
+
+
+def rasp(options=None):
+    """Return a Raspberry 3b target.
+
+    Parameters
+    ----------
+    options : str or list of str
+        Additional options
+    """
+    warnings.warn('tvm.target.rasp() is going to be deprecated. '
+                  'Please use tvm.target.arm_cpu("rasp3b")')
+    return arm_cpu('rasp3b', options)
+
+
 def create(target_str):
     """Get a target given target string.
 
diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc
index 49b86626add0..45d10617de05 100644
--- a/src/codegen/build_module.cc
+++ b/src/codegen/build_module.cc
@@ -261,15 +261,6 @@ Target metal(const std::vector<std::string>& options) {
   return CreateTarget("metal", options);
 }
 
-Target rasp(const std::vector<std::string>& options) {
-  return CreateTarget("llvm", MergeOptions(options, {
-    "-device=rasp",
-    "-mtriple=armv7l-none-linux-gnueabihf",
-    "-mcpu=cortex-a53",
-    "-mattr=+neon"
-  }));
-}
-
 Target mali(const std::vector<std::string>& options) {
   return CreateTarget("opencl", MergeOptions(options, {
     "-device=mali"
@@ -731,11 +722,6 @@ TVM_REGISTER_API("_GetCurrentTarget")
 TVM_REGISTER_API("_EnterTargetScope")
 .set_body([](TVMArgs args, TVMRetValue* ret) {
   Target target = args[0];
-  auto current = Target::current_target();
-  if (current.defined() && target->str() != current->str()) {
-    LOG(WARNING) << "Overriding target " << current->str()
-      << " with new target scope " << target->str();
-  }
   Target::EnterTargetScope(target);
   });
 
diff --git a/src/codegen/opt/build_opencl_off.cc b/src/codegen/opt/build_opencl_off.cc
index fc962d4840e9..adadb84e9b1c 100644
--- a/src/codegen/opt/build_opencl_off.cc
+++ b/src/codegen/opt/build_opencl_off.cc
@@ -13,7 +13,6 @@ Module OpenCLModuleCreate(
     std::string fmt,
     std::unordered_map<std::string, FunctionInfo> fmap,
     std::string source) {
-  LOG(WARNING) << "OpenCL runtime not enabled, return a source module...";
   return codegen::DeviceSourceModuleCreate(data, fmt, fmap, "opencl");
 }
 
diff --git a/tests/python/integration/test_tuning.py b/tests/python/integration/test_tuning.py
index 23816b2b6f78..87da86a4654f 100644
--- a/tests/python/integration/test_tuning.py
+++ b/tests/python/integration/test_tuning.py
@@ -108,19 +108,19 @@ def test_task_tuner_without_measurement():
     """test task and tuner without measurement"""
     task, target = get_sample_task()
 
-    def measure_batch(inputs):
+    def custom_measure(input_pack, build_func, build_args, number, repeat,
+                       ref_input, ref_output):
         from tvm.autotvm import MeasureResult
 
         results = []
-        for inp in inputs:
+        for inp in input_pack:
             tic = time.time()
             # do nothing
             time.sleep(0.001)
             results.append(MeasureResult([time.time() - tic], 0,
                                          time.time() - tic, time.time()))
         return results
-    measure_option = autotvm.measure_option(mode='custom',
-                                            custom_measure_batch=measure_batch)
+    measure_option = autotvm.measure_option(custom_measure)
 
     logging.info("%s", task.config_space)
 
@@ -128,6 +128,7 @@ def measure_batch(inputs):
     for tuner_class in [autotvm.tuner.RandomTuner, autotvm.tuner.GridSearchTuner]:
         tuner = tuner_class(task)
         tuner.tune(n_trial=10, measure_option=measure_option)
+        assert tuner.best_flops > 1
 
 def test_tuning_with_measure():
     def check(target, target_host):
@@ -140,7 +141,7 @@ def check(target, target_host):
         task, target = get_sample_task(target, target_host)
         logging.info("%s", task.config_space)
 
-        measure_option = autotvm.measure_option(mode='local',
+        measure_option = autotvm.measure_option('local',
                                                 timeout=4,
                                                 number=2)
 
@@ -152,7 +153,8 @@ def check(target, target_host):
 
 if __name__ == "__main__":
     # only print log when invoked from main
-    logging.basicConfig(level=logging.INFO)
+    logging.basicConfig(level=logging.DEBUG)
 
     test_task_tuner_without_measurement()
     test_tuning_with_measure()
+
diff --git a/tests/python/unittest/test_autotvm_database.py b/tests/python/unittest/test_autotvm_database.py
index 72f0e082ca91..af4704d95e51 100644
--- a/tests/python/unittest/test_autotvm_database.py
+++ b/tests/python/unittest/test_autotvm_database.py
@@ -47,7 +47,7 @@ def test_db_filter():
 
     batch_size = 2
 
-    measure_option = autotvm.measure_option(mode='local-nofork', timeout=2)
+    measure_option = autotvm.measure_option('local', do_fork=False, timeout=2)
     measure_batch = autotvm.measure.create_measure_batch(task, measure_option)
 
     ct = 0
@@ -72,7 +72,7 @@ def test_db_filter():
     db.flush()
 
     # First setting, memoize one input at a time, check that each is saved and replayed
-    measure_option = autotvm.measure_option(mode='local-nofork', timeout=2, replay_db=db)
+    measure_option = autotvm.measure_option('local', do_fork=False, timeout=2, replay_db=db)
     measure_batch = autotvm.measure.create_measure_batch(task, measure_option)
 
     for i in range(len(all_inputs)+1):
@@ -160,9 +160,10 @@ def test_db_save_replay():
     if not ctx.exist:
         logging.warning("Skip this test because there is no supported device for test")
 
-    measure_option = autotvm.measure_option(mode='local-nofork',
+    measure_option = autotvm.measure_option('local',
+                                            do_fork=False,
                                             timeout=2,
-                                            replay_db=_db, save_to_replay_db=True)
+                                            replay_db=_db)
     measure_batch = autotvm.measure.create_measure_batch(task, measure_option)
 
     batch_size = 2
@@ -182,6 +183,8 @@ def test_db_save_replay():
         results = measure_batch(inputs)
         all_results += results
         ct += 1
+    callback = autotvm.callback.log_to_database(_db)
+    callback(None, all_inputs, all_results)
 
     assert len(_db.db.keys()) == batch_size * TRIAL_LIMIT, \
         "%d vs %d" % (len(_db.db.keys()), batch_size * TRIAL_LIMIT)
@@ -207,7 +210,7 @@ def test_check_hashmismatch():
     if not ctx.exist:
         logging.warning("Skip this test because there is no supported device for test")
 
-    measure_option = autotvm.measure_option(mode='local-nofork')
+    measure_option = autotvm.measure_option('local', do_fork=False)
     measure_batch = autotvm.measure.create_measure_batch(task, measure_option)
 
     inputs = list()
diff --git a/tests/python/unittest/test_autotvm_feature.py b/tests/python/unittest/test_autotvm_feature.py
index 1b806ded4393..43754c27e0ea 100644
--- a/tests/python/unittest/test_autotvm_feature.py
+++ b/tests/python/unittest/test_autotvm_feature.py
@@ -84,7 +84,7 @@ def get_gemm_feature(target):
     targets = [
         tvm.target.cuda(),
         tvm.target.mali(),
-        tvm.target.rasp(),
+        tvm.target.arm_cpu(),
     ]
 
     for target in targets:
diff --git a/tests/python/unittest/test_lang_target.py b/tests/python/unittest/test_lang_target.py
index 5f77010fa751..f7309fc30819 100644
--- a/tests/python/unittest/test_lang_target.py
+++ b/tests/python/unittest/test_lang_target.py
@@ -28,7 +28,7 @@ def test_target_dispatch():
     with tvm.target.create("cuda"):
         assert mygeneric(1) == 3
 
-    with tvm.target.rasp():
+    with tvm.target.arm_cpu():
         assert mygeneric(1) == 11
 
     with tvm.target.create("metal"):
diff --git a/tests/scripts/task_python_vta.sh b/tests/scripts/task_python_vta.sh
index cd2e0d0b9bae..5d8c47cfdb1a 100755
--- a/tests/scripts/task_python_vta.sh
+++ b/tests/scripts/task_python_vta.sh
@@ -2,6 +2,9 @@
 
 export PYTHONPATH=python:nnvm/python:vta/python:topi/python
 
+rm -rf python/tvm/*.pyc python/tvm/*/*.pyc python/tvm/*/*/*.pyc python/tvm/*/*/*/*.pyc
+rm -rf ~/.tvm
+
 echo "Running unittest..."
 python -m nose -v vta/tests/python/unittest || exit -1
 python3 -m nose -v vta/tests/python/unittest || exit -1
diff --git a/topi/python/topi/__init__.py b/topi/python/topi/__init__.py
index d58b37b04518..349f805cc7f2 100644
--- a/topi/python/topi/__init__.py
+++ b/topi/python/topi/__init__.py
@@ -24,7 +24,7 @@
 from . import nn
 from . import x86
 from . import cuda
-from . import rasp
+from . import arm_cpu
 from . import mali
 from . import intel_graphics
 from . import opengl
diff --git a/topi/python/topi/arm_cpu/__init__.py b/topi/python/topi/arm_cpu/__init__.py
new file mode 100644
index 000000000000..bb79769c1adc
--- /dev/null
+++ b/topi/python/topi/arm_cpu/__init__.py
@@ -0,0 +1,5 @@
+"""Schedule for ARM CPU"""
+
+from . import conv2d
+from . import depthwise_conv2d
+from . import bitserial_conv2d
diff --git a/topi/python/topi/rasp/bitserial_conv2d.py b/topi/python/topi/arm_cpu/bitserial_conv2d.py
similarity index 99%
rename from topi/python/topi/rasp/bitserial_conv2d.py
rename to topi/python/topi/arm_cpu/bitserial_conv2d.py
index 7d292db8d298..470aea0b4523 100644
--- a/topi/python/topi/rasp/bitserial_conv2d.py
+++ b/topi/python/topi/arm_cpu/bitserial_conv2d.py
@@ -43,7 +43,7 @@
     SpatialPackNCHW(1, 1, 8, 1, 16),
 ]
 
-@_get_schedule.register("rasp")
+@_get_schedule.register("arm_cpu")
 def _get_schedule_bitserial_conv2d(wkl, layout):
     if wkl not in _WORKLOADS:
         raise ValueError("no schedule for such workload: {}".format(wkl))
@@ -55,7 +55,7 @@ def _get_schedule_bitserial_conv2d(wkl, layout):
     return sch
 
 
-@bitserial_conv2d.register("rasp")
+@bitserial_conv2d.register("arm_cpu")
 def _declaration_bitserial_conv2d(data, kernel, stride, padding, activation_bits, weight_bits,
                                   layout='NCHW', pack_dtype=None, out_dtype=None, dorefa=False):
     if out_dtype is None:
@@ -323,7 +323,7 @@ def _schedule_spatial_conv2d_nhwc(s, data, data_q, data_pad, data_vec,
     s = s.normalize()
     return s
 
-@generic.schedule_bitserial_conv2d_nhwc.register(["rasp"])
+@generic.schedule_bitserial_conv2d_nhwc.register(["arm_cpu"])
 def schedule_bitserial_conv2d_nhwc(outs):
     """Raspverry pi schedule for bitserial conv2d"""
     s = tvm.create_schedule([x.op for x in outs])
diff --git a/topi/python/topi/arm_cpu/conv2d.py b/topi/python/topi/arm_cpu/conv2d.py
new file mode 100644
index 000000000000..f5dbec8e552b
--- /dev/null
+++ b/topi/python/topi/arm_cpu/conv2d.py
@@ -0,0 +1,515 @@
+# pylint: disable=invalid-name,unused-variable,no-else-return
+"""Conv2D schedule for ARM CPU"""
+from __future__ import absolute_import as _abs
+
+import numpy as np
+
+import tvm
+from tvm import autotvm
+
+from ..generic import schedule_conv2d_nchw, schedule_conv2d_winograd_without_weight_transform
+from ..util import traverse_inline, get_const_tuple, const_matrix
+from ..nn import pad, conv2d, conv2d_alter_layout, conv2d_winograd_without_weight_transform
+from ..nn.util import get_const_int, get_pad_tuple
+
+def _conv_arg_to_workload(data, kernel, strides, padding, layout, out_dtype):
+    """convert argument to workload"""
+    if len(kernel.shape) == 4:
+        raw_kernel = kernel
+    else:  # the input kernel is transformed by alter_op_layout
+        shape = get_const_tuple(kernel.shape)
+        raw_kernel = tvm.placeholder((shape[0] * shape[4], shape[1], shape[2], shape[3]),
+                                     dtype=kernel.dtype)
+    return ('conv2d', ) + autotvm.task.args_to_workload(
+        [data, raw_kernel, strides, padding, layout, out_dtype])
+
+@conv2d.register('arm_cpu')
+@autotvm.task.dispatcher
+def conv2d_arm_cpu(data, kernel, strides, padding, layout, out_dtype):
+    """TOPI compute callback. Mark this function as a dispatcher, so
+    this template can assign config according to workload"""
+    return _conv_arg_to_workload(data, kernel, strides, padding, layout, out_dtype)
+
+@conv2d_arm_cpu.register(['direct'])
+def decl_spatial_pack(cfg, data, kernel, strides, padding, layout, out_dtype):
+    """spatial packing template"""
+    return _decl_spatial_pack(cfg, data, kernel, strides, padding, layout, out_dtype, num_tile=2)
+
+@autotvm.task.register_topi_schedule(schedule_conv2d_nchw, 'arm_cpu', ['direct', 'winograd'])
+def schedule_conv2d_nchw_arm_cpu(cfg, outs):
+    """TOPI schedule callback"""
+    s = tvm.create_schedule([x.op for x in outs])
+
+    def _callback(op):
+        # schedule conv2d
+        if 'spatial_conv_output' in op.tag:
+            output = op.output(0)
+            conv = op.input_tensors[0]
+
+            data_vec = conv.op.input_tensors[0]
+            data_pad = data_vec.op.input_tensors[0]
+            s[data_pad].compute_inline()
+
+            kernel_vec = conv.op.input_tensors[1]
+            if kernel_vec.op.name == 'kernel_vec':
+                kernel = kernel_vec.op.input_tensors[0]
+            else:
+                kernel = kernel_vec
+            if isinstance(kernel.op, tvm.tensor.ComputeOp) and "dilate" in kernel.op.tag:
+                s[kernel].compute_inline()
+
+            _schedule_spatial_pack(cfg, s, data_vec, kernel_vec, conv, output, outs[0])
+
+        if 'winograd_conv_output' in op.tag:
+            output = op.output(0)
+            _schedule_winograd(cfg, s, output, outs[0])
+
+    traverse_inline(s, outs[0].op, _callback)
+    return s
+
+
+def _decl_spatial_pack(cfg, data, kernel, strides, padding, layout, out_dtype, num_tile):
+    assert layout == "NCHW", "Only support NCHW"
+    out_dtype = out_dtype or data.dtype
+
+    _, CI, IH, IW = get_const_tuple(data.shape)
+    if len(kernel.shape) == 4:
+        pre_packed = False
+        CO, _, KH, KW = get_const_tuple(kernel.shape)
+    else:  # kernel tensor is pre packed
+        pre_packed = True
+        CO, _, KH, KW, VC = get_const_tuple(kernel.shape)
+        CO = CO * VC
+
+    pad_top, pad_left, pad_down, pad_right = get_pad_tuple(padding, (KH, KW))
+    HSTR, WSTR = strides if isinstance(strides, (tuple, list)) else (strides, strides)
+
+    N = 1
+    OH = (IH + pad_top + pad_down - KH) // HSTR + 1
+    OW = (IW + pad_left + pad_right - KW) // WSTR + 1
+    data_pad = pad(data, [0, 0, pad_top, pad_left], [0, 0, pad_down, pad_right])
+
+    # ==================== define configuration space ====================
+    n, co, oh, ow = cfg.axis(N), cfg.axis(CO), cfg.axis(OH), cfg.axis(OW)
+    ci, kh, kw = cfg.reduce_axis(CI), cfg.reduce_axis(KH), cfg.reduce_axis(KW)
+
+    if num_tile == 2:     # for arm cpu
+        co, vc = cfg.define_split('tile_co', co, num_outputs=2)
+        oh, vh = cfg.define_split('tile_oh', oh, num_outputs=2)
+        ow, vw = cfg.define_split('tile_ow', ow, num_outputs=2)
+    elif num_tile == 3:   # for mali gpu
+        co, _, vc = cfg.define_split('tile_co', co, num_outputs=3)
+        oh, _, vh = cfg.define_split('tile_oh', oh, num_outputs=3)
+        ow, _, vw = cfg.define_split('tile_ow', ow, num_outputs=3)
+    else:
+        raise RuntimeError("Invalid num_tile")
+
+    cfg.define_reorder("reorder_0",
+                       [n, co, oh, ow, ci, kh, kw, vh, vw, vc],
+                       policy='candidate', candidate=[
+                           [n, co, oh, ow, ci, kh, kw, vh, vw, vc],
+                           [n, co, oh, ow, ci, kh, kw, vc, vh, vw]])
+
+    cfg.define_annotate("ann_reduce", [kh, kw], policy='try_unroll')
+    cfg.define_annotate("ann_spatial", [vh, vw, vc], policy='try_unroll_vec')
+    # ====================================================================
+
+    VC = cfg["tile_co"].size[-1]
+    VH = cfg["tile_oh"].size[-1]
+    VW = cfg["tile_ow"].size[-1]
+
+    dvshape = (N, OH // VH, OW // VW, CI, VH*HSTR + KH-1, VW*WSTR + KW-1)
+    kvshape = (CO // VC, CI, KH, KW, VC)
+    ovshape = (N, CO // VC, OH // VH, OW // VW, VH, VW, VC)
+    oshape = (N, CO, OH, OW)
+
+    data_vec = tvm.compute(dvshape, lambda n, h, w, ci, vh, vw:
+                           data_pad[n][ci][h*VH*HSTR+vh][w*VW*WSTR+vw],
+                           name='data_vec')
+
+    if pre_packed:
+        kernel_vec = kernel
+    else:
+        kernel_vec = tvm.compute(kvshape, lambda co, ci, kh, kw, vc:
+                                 kernel[co*VC+vc][ci][kh][kw],
+                                 name='kernel_vec')
+
+    ci = tvm.reduce_axis((0, CI), name='ci')
+    kh = tvm.reduce_axis((0, KH), name='kh')
+    kw = tvm.reduce_axis((0, KW), name='kw')
+
+    conv = tvm.compute(ovshape, lambda n, co, h, w, vh, vw, vc: \
+        tvm.sum(data_vec[n, h, w, ci, vh*HSTR+kh, vw*WSTR+kw].astype(out_dtype) *
+                kernel_vec[co, ci, kh, kw, vc].astype(out_dtype),
+                axis=[ci, kh, kw]), name='conv')
+
+    output = tvm.compute(oshape, lambda n, co, h, w:
+                         conv[n][co//VC][h//VH][w//VW][h%VH][w%VW][co%VC],
+                         name='output_unpack', tag='spatial_conv_output',
+                         attrs={'workload': _conv_arg_to_workload(data, kernel, strides, padding,
+                                                                  layout, out_dtype)})
+    return output
+
+def _schedule_spatial_pack(cfg, s, data_vec, kernel_vec,
+                           conv, output, last):
+    """schedule implementation"""
+    n, co, oh, ow, vh, vw, vc = s[conv].op.axis
+    ci, kh, kw = s[conv].op.reduce_axis
+
+    # schedule conv
+    cfg["reorder_0"].apply(s, conv, [n, co, oh, ow, ci, kh, kw, vh, vw, vc])
+    cfg["ann_reduce"].apply(s, conv, [kh, kw],
+                            axis_lens=[get_const_int(kh.dom.extent),
+                                       get_const_int(kw.dom.extent)],
+                            max_unroll=16,
+                            cfg=cfg)
+    cfg["ann_spatial"].apply(s, conv, [vh, vw, vc],
+                             axis_lens=[cfg['tile_oh'].size[-1],
+                                        cfg['tile_ow'].size[-1],
+                                        cfg['tile_co'].size[-1]],
+                             max_unroll=16,
+                             cfg=cfg)
+
+    # schedule fusion
+    n, co, h, w = s[last].op.axis
+    co, vc = cfg['tile_co'].apply(s, last, co)
+    oh, vh = cfg['tile_oh'].apply(s, last, h)
+    ow, vw = cfg['tile_ow'].apply(s, last, w)
+    s[last].reorder(n, co, oh, ow, vh, vw, vc)
+    if last != output:
+        s[output].compute_inline()
+        cfg["ann_spatial"].apply(s, last, [vh, vw, vc],
+                                 axis_lens=[cfg['tile_oh'].size[-1],
+                                            cfg['tile_ow'].size[-1],
+                                            cfg['tile_co'].size[-1]],
+                                 max_unroll=16,
+                                 cfg=cfg)
+    s[conv].compute_at(s[last], ow)
+
+    # mark parallel
+    s[last].parallel(co)
+
+    _, h, _, _, _, _ = s[data_vec].op.axis
+    s[data_vec].parallel(h)
+
+    if kernel_vec.op.name == 'kernel_vec':
+        co, _, _, _, _ = s[kernel_vec].op.axis
+        if autotvm.GLOBAL_SCOPE.in_tuning:
+            # kernel packing will be pre-computed during compliation, so we skip
+            # this part to make tuning records correct
+            s[kernel_vec].pragma(co, 'debug_skip_region')
+        else:
+            s[kernel_vec].parallel(co)
+
+    return s
+
+
+@conv2d_arm_cpu.register('winograd')
+def decl_winograd(cfg, data, kernel, strides, padding, layout, out_dtype):
+    tile_size = 4
+    return _decl_winograd(cfg, data, kernel, strides, padding, layout, out_dtype, tile_size)
+
+def _decl_winograd(cfg, data, kernel, strides, padding, layout, out_dtype, tile_size):
+    N, CI, IH, IW = get_const_tuple(data.shape)
+    if len(kernel.shape) == 4:
+        pre_computed = False
+        CO, _, KH, KW = get_const_tuple(kernel.shape)
+    else:
+        pre_computed = True
+        H_CAT, W_CAT, CO, CI, VC = get_const_tuple(kernel.shape)
+        CO *= VC
+        KH, KW = H_CAT - tile_size + 1, W_CAT - tile_size + 1
+    HSTR, WSTR = strides if isinstance(strides, (tuple, list)) else (strides, strides)
+    HPAD, WPAD, _, _ = get_pad_tuple(padding, kernel)
+
+    assert layout == 'NCHW'
+    assert KH == 3 and KW == 3 and HPAD == 1 and WPAD == 1 and HSTR == 1 and WSTR == 1
+    data_pad = pad(data, (0, 0, HPAD, WPAD), name="data_pad")
+
+    if tile_size == 4:
+        G_data = np.array([
+            [1 / 4.0, 0, 0],
+            [-1 / 6.0, -1 / 6.0, -1 / 6.0],
+            [-1 / 6.0, 1 / 6.0, -1 / 6.0],
+            [1 / 24.0, 1 / 12.0, 1 / 6.0],
+            [1 / 24.0, -1 / 12.0, 1 / 6.0],
+            [0, 0, 1]], dtype=np.float32)
+
+        B_data = np.array([
+            [4, 0, 0, 0, 0, 0],
+            [0, -4, 4, -2, 2, 4],
+            [-5, -4, -4, -1, -1, 0],
+            [0, 1, -1, 2, -2, -5],
+            [1, 1, 1, 1, 1, 0],
+            [0, 0, 0, 0, 0, 1]], out_dtype)
+
+        A_data = np.array([
+            [1, 0, 0, 0],
+            [1, 1, 1, 1],
+            [1, -1, 1, -1],
+            [1, 2, 4, 8],
+            [1, -2, 4, -8],
+            [0, 0, 0, 1]], out_dtype)
+    elif tile_size == 2:
+        G_data = np.array([
+            [1, 0, 0],
+            [1.0/2, 1.0/2, 1.0/2],
+            [1.0/2, -1.0/2, 1.0/2],
+            [0, 0, 1]], np.float32)
+
+        B_data = np.array([
+            [1, 0, 0, 0],
+            [0, 1, -1, 1],
+            [-1, 1, 1, 0],
+            [0, 0, 0, -1]], out_dtype)
+
+        A_data = np.array([
+            [1, 0],
+            [1, 1],
+            [1, -1],
+            [0, -1]], out_dtype)
+    else:
+        raise ValueError("Unsupported tile size for winograd: " + str(tile_size))
+
+    m = A_data.shape[1]
+    r = 3
+    alpha = m + r - 1
+    K = CO
+    C = CI
+
+    H = (IH + 2 * HPAD - 3) // HSTR + 1
+    W = (IW + 2 * WPAD - 3) // WSTR + 1
+    nH, nW = (H + m-1) // m, (W + m-1) // m
+    P = N * nH * nW
+
+    cfg.define_split('tile_p', cfg.axis(P), num_outputs=2, filter=lambda x: x.size[-1] <= 16)
+    cfg.define_split('tile_k', cfg.axis(K), num_outputs=2, filter=lambda x: x.size[-1] <= 16)
+    VP = cfg['tile_p'].size[-1]
+    VK = cfg['tile_k'].size[-1]
+
+    # pack input tile
+    input_tile = tvm.compute((C, P // VP, alpha, alpha, VP),
+                             lambda c, b, eps, nu, bb:
+                             data_pad[(b*VP+bb) // (nH*nW)][c][(b*VP+bb) // nW % nH * m + eps]
+                             [(b*VP+bb) % nW * m + nu],
+                             name='d')
+
+    # transform kernel
+    if pre_computed:
+        U = kernel
+    else:
+        G = const_matrix(G_data, 'G')
+        r_kh = tvm.reduce_axis((0, KH), 'r_kh')
+        r_kw = tvm.reduce_axis((0, KW), 'r_kw')
+        U = tvm.compute((alpha, alpha, K // VK, C, VK), lambda eps, nu, k, c, kk:
+                        tvm.sum(kernel[k * VK + kk][c][r_kh][r_kw].astype(out_dtype) *
+                                G[eps][r_kh] * G[nu][r_kw], axis=[r_kh, r_kw]), name='U')
+
+    # transform image
+    B = const_matrix(B_data, 'B')
+    r_eps = tvm.reduce_axis((0, alpha), 'r_eps')
+    r_nu = tvm.reduce_axis((0, alpha), 'r_nu')
+    V = tvm.compute((alpha, alpha, P // VP, C, VP), lambda eps, nu, b, c, bb:
+                    tvm.sum(input_tile[c][b][r_eps][r_nu][bb].astype(out_dtype) *
+                            B[r_eps][eps] * B[r_nu][nu], axis=[r_eps, r_nu]), name='V')
+
+    # batch gemm
+    c = tvm.reduce_axis((0, C), name='c')
+    M = tvm.compute((alpha, alpha, K, P), lambda eps, nu, k, b:
+                    tvm.sum(U[eps][nu][k // VK][c][k % VK] *
+                            V[eps][nu][b // VP][c][b % VP], axis=c), name='M')
+
+    # inverse transform
+    A = const_matrix(A_data, 'A')
+    r_eps = tvm.reduce_axis((0, alpha), 'r_eps')
+    r_nu = tvm.reduce_axis((0, alpha), 'r_nu')
+    Y = tvm.compute((K, P, m, m), lambda k, b, vh, vw:
+                    tvm.sum(M[r_eps][r_nu][k][b] * A[r_eps][vh] * A[r_nu][vw],
+                            axis=[r_eps, r_nu]), name='Y')
+
+    # unpack output
+    output = tvm.compute((N, K, H, W), lambda n, k, h, w:
+                         Y[k][n * nH * nW + (h//m) * nW + w//m][h % m][w % m],
+                         name='output', tag='winograd_conv_output',
+                         attrs={'workload': _winograd_conv_arg_to_workload(
+                             data, kernel, strides, padding, layout, out_dtype, tile_size)})
+
+    # we have to manually assign effective GFLOP for winogard
+    cfg.add_flop(2 * N * K * H * W * KH * KW * C)
+    return output
+
+def _schedule_winograd(cfg, s, output, last):
+    Y = output.op.input_tensors[0]
+    M, A = Y.op.input_tensors
+    U, V = M.op.input_tensors
+    d, B = V.op.input_tensors
+    data_pad = d.op.input_tensors[0]
+
+    # padding
+    s[data_pad].compute_inline()
+
+    # pack input tiles
+    s[d].compute_inline()
+
+    # transform kernel
+    if isinstance(U.op, tvm.tensor.ComputeOp):
+        kernel, G = U.op.input_tensors
+        s[G].compute_inline()
+        eps, nu, k, c, kk, = s[U].op.axis
+        r_kh, r_kw = s[U].op.reduce_axis
+        s[U].reorder(k, c, eps, nu, r_kh, r_kw, kk)
+        s[U].unroll(eps)
+        s[U].unroll(nu)
+        s[U].unroll(r_kh)
+        s[U].unroll(r_kw)
+        s[U].vectorize(kk)
+        if autotvm.GLOBAL_SCOPE.in_tuning:
+            # kernel transformation will be pre-computed during compilation, so we skip
+            # this part to make tuning records correct
+            s[U].pragma(k, 'debug_skip_region')
+        else:
+            s[U].parallel(k)
+
+    # transform image
+    DD = s.cache_read(d, 'global', [V])
+    s[B].compute_inline()
+    eps, nu, b, c, bb = s[V].op.axis
+    r_eps, r_nu = s[V].op.reduce_axis
+    s[V].reorder(b, c, eps, nu, r_eps, r_nu, bb)
+    s[V].unroll(eps)
+    s[V].unroll(nu)
+    s[V].unroll(r_eps)
+    s[V].unroll(r_nu)
+    s[DD].compute_at(s[V], c)
+    s[V].vectorize(bb)
+    s[V].parallel(b)
+
+    # batch gemm
+    eps, nu, k, b = s[M].op.axis
+    c = s[M].op.reduce_axis[0]
+    cfg.define_split('tile_c', c, num_outputs=2, filter=lambda x: x.size[-1] <= 16)
+    co, ci = cfg['tile_c'].apply(s, M, c)
+    xo, xi = cfg['tile_p'].apply(s, M, b)
+    s[M].reorder(eps, nu, xo, co, k, ci, xi)
+    cfg.define_annotate('ann_reduce', [ci], policy='try_unroll')
+    cfg.define_annotate('ann_spatial', [k, xi], policy='try_unroll_vec')
+    cfg['ann_reduce'].apply(s, M, [ci],
+                            axis_lens=[cfg['tile_c'].size[-1]],
+                            max_unroll=16,
+                            cfg=cfg)
+    cfg['ann_spatial'].apply(s, M, [k, xi])
+
+    # inverse transform
+    s[A].compute_inline()
+    k, b, vh, vw = s[Y].op.axis
+    r_eps, r_nu = s[Y].op.reduce_axis
+    s[Y].unroll(vh)
+    s[Y].unroll(vw)
+    s[Y].unroll(r_eps)
+    s[Y].unroll(r_nu)
+
+    # output
+    n, co, h, w = s[last].op.axis
+    co, coi = cfg['tile_k'].apply(s, last, co)
+    s[M].compute_at(s[last], co)
+    s[last].parallel(co)
+
+    MM = s.cache_read(M, 'global', [Y])
+    m = get_const_int(V.shape[0]) + 1 - 3
+    ho, wo, hi, wi = s[last].tile(h, w, m, m)
+    s[Y].compute_at(s[last], wo)
+    s[MM].compute_at(s[last], wo)
+
+    if output != last:
+        s[output].compute_inline()
+
+
+def _winograd_conv_arg_to_workload(data, kernel, strides, padding, layout, out_dtype, tile_size):
+    """convert argument to workload"""
+    K = 3
+    shape = get_const_tuple(kernel.shape)
+    alpha = tile_size + K - 1
+    if len(kernel.shape) == 4:
+        assert shape[2:] == (K, K)
+        CO, CI = shape[:2]
+    else:
+        assert shape[:2] == (alpha, alpha)
+        CO, CI, VCO = shape[2:]
+        CO *= VCO
+
+    raw_kernel = tvm.placeholder((CO, CI, K, K), dtype=kernel.dtype)
+    return ('conv2d', ) + autotvm.task.args_to_workload(
+        [data, raw_kernel, strides, padding, layout, out_dtype])
+
+
+@conv2d_winograd_without_weight_transform.register(['arm_cpu'])
+@autotvm.task.dispatcher
+def winograd_ww_config_dispatcher_(data, kernel, strides, padding, layout, out_dtype, tile_size):
+    return _winograd_conv_arg_to_workload(data, kernel, strides, padding, layout, out_dtype,
+                                          tile_size)
+
+
+@winograd_ww_config_dispatcher_.register(['winograd'])
+def decl_winograd_ww(cfg, data, kernel, strides, padding, layout, out_dtype, tile_size):
+    return _decl_winograd(cfg, data, kernel, strides, padding, layout, out_dtype,
+                          tile_size)
+
+
+@autotvm.task.register_topi_schedule(schedule_conv2d_winograd_without_weight_transform,
+                                     'arm_cpu', ['winograd'])
+def schedule_conv2d_winograd_without_weight_transform_(cfg, outs):
+    """TOPI schedule callback"""
+    s = tvm.create_schedule([x.op for x in outs])
+
+    def _callback(op):
+        if 'winograd_conv_output' in op.tag:
+            output = op.output(0)
+            _schedule_winograd(cfg, s, output, outs[0])
+
+    traverse_inline(s, outs[0].op, _callback)
+    return s
+
+
+@conv2d_alter_layout.register(["arm_cpu", "mali"])
+def _alter_conv2d_layout(attrs, inputs, tinfos):
+    """Alter op layout for pre-computing kernel transformation"""
+    import nnvm.symbol as sym
+    copy_inputs = [s for s in inputs]
+
+    new_attrs = {k: attrs[k] for k in attrs.keys()}
+
+    assert attrs.get_int_tuple("dilation") == (1, 1), "Does not support dilation " \
+                                                      "when alter_op_layout is enabled"
+    strides = attrs.get_int_tuple("strides")
+    padding = attrs.get_int_tuple("padding")
+    groups = attrs.get_int('groups')
+    layout = attrs["layout"]
+    out_dtype = attrs["out_dtype"]
+    out_dtype = tinfos[0].dtype if out_dtype == "same" else out_dtype
+
+    if groups == 1:
+        # query config of this workload
+        workload = _conv_arg_to_workload(tinfos[0], tinfos[1], strides, padding,
+                                         layout, out_dtype)
+        cfg = autotvm.task.DispatchContext.current.query(tvm.target.current_target(), workload)
+
+        if cfg.template_key == 'direct':  # packing weight tensor
+            new_attrs['kernel_layout'] = 'OIHW%do' % (cfg['tile_co'].size[-1])
+            return sym.conv2d(*copy_inputs, **new_attrs)
+        else:  # pre-compute weight transformation in winograd
+            tile_size = 4
+
+            weight = sym.contrib.conv2d_winograd_weight_transform(copy_inputs[1],
+                                                                  tile_size=tile_size)
+            CO, CI, KH, KW = get_const_tuple(tinfos[1].shape)
+            VC = cfg['tile_k'].size[-1]
+            weight = sym.reshape(weight,
+                                 shape=(KH + tile_size - 1, KW + tile_size - 1, CO // VC, VC, CI))
+            weight = sym.transpose(weight, axes=[0, 1, 2, 4, 3])
+
+            copy_inputs[1] = weight
+            new_attrs['tile_size'] = tile_size
+            return sym.contrib.conv2d_winograd_without_weight_transform(*copy_inputs, **new_attrs)
+
+    # do nothing for depthwise convolution
+    return None
diff --git a/topi/python/topi/arm_cpu/depthwise_conv2d.py b/topi/python/topi/arm_cpu/depthwise_conv2d.py
new file mode 100644
index 000000000000..65fabddb34df
--- /dev/null
+++ b/topi/python/topi/arm_cpu/depthwise_conv2d.py
@@ -0,0 +1,94 @@
+# pylint: disable=invalid-name,unused-variable
+"""Depthwise convolution schedule for ARM CPU"""
+
+import tvm
+from tvm import autotvm
+
+from ..generic import schedule_depthwise_conv2d_nchw
+from ..nn import depthwise_conv2d_nchw
+from ..util import traverse_inline
+
+# register original implementation of depthwise_conv2d_nchw since we don't need to change this part
+autotvm.task.register_topi_compute(depthwise_conv2d_nchw, 'arm_cpu', 'direct',
+                                   depthwise_conv2d_nchw.fdefault)
+
+# register customized schedule for arm cpu.
+@autotvm.task.register_topi_schedule(schedule_depthwise_conv2d_nchw, 'arm_cpu', 'direct')
+def schedule_depthwise_conv2d_nchw_(cfg, outs):
+    """Schedule depthwise conv2d"""
+    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
+    s = tvm.create_schedule([x.op for x in outs])
+
+    def _schedule(cfg, s, data, data_pad, kernel, output):
+        A, B, C = data, kernel, output
+        s[data_pad].compute_inline()
+
+        # define tile
+        n, c, h, w = s[output].op.axis
+        cfg.define_split('tile_c', c, num_outputs=2)
+        cfg.define_split('tile_h', h, num_outputs=2)
+        cfg.define_split('tile_w', w, num_outputs=2)
+
+        # park data to vector form  [n, c, h, w] -> [n, C, h, w, VC]
+        A0 = s.cache_read(data_pad, "global", C)
+        _, c, h, w = s[A0].op.axis
+        c, vc = cfg['tile_c'].apply(s, A0, c)
+        s[A0].reorder(c, h, w, vc)
+        A1 = s.cache_write(A0, 'global')
+        s[A0].compute_inline()
+
+        # park kernel to vector form  [co, ci, kh, kw] -> [CO, ci, kh, kw, VC]
+        B0 = s.cache_read(B, "global", C)
+        c, m, h, w = s[B0].op.axis
+        c, vc, = cfg['tile_c'].apply(s, B0, c)
+        s[B0].reorder(c, m, h, w, vc)
+        B1 = s.cache_write(B0, 'global')
+        s[B0].compute_inline()
+
+        _, c, h, w = s[C].op.axis
+        c, vc, = cfg['tile_c'].apply(s, C, c)
+        s[C].reorder(c, h, w, vc)
+
+        # depthwise conv
+        C0 = s.cache_write(C, 'global')
+        _, c, h, w, vc = s[C0].op.axis
+        dh, dw = s[C0].op.reduce_axis
+        oh, ih = cfg['tile_h'].apply(s, C0, h)
+        ow, iw = cfg['tile_w'].apply(s, C0, w)
+        s[C0].reorder(c, oh, ow, dh, dw, ih, iw, vc)
+        s[A1].compute_at(s[C0], oh)
+
+        # try unroll and vectorization
+        cfg.define_annotate('ann', [ih, iw, vc], policy='try_unroll_vec')
+        cfg['ann'].apply(s, C0, [ih, iw, vc],
+                         axis_lens=[cfg['tile_h'].size[-1],
+                                    cfg['tile_w'].size[-1],
+                                    cfg['tile_c'].size[-1]],
+                         max_unroll=16,
+                         cfg=cfg)
+
+        # mark parallel
+        n, c, h, w = s[C].op.axis
+        s[C].parallel(c)
+
+        n, c, h, w, vc = s[C0].op.axis
+        s[C0].parallel(c)
+
+        c, m, h, w, vc = s[B1].op.axis
+        s[B1].parallel(c)
+
+        return s
+
+    def _callback(op):
+        if op.tag == 'depthwise_conv2d_nchw':
+            output = op.output(0)
+            kernel = op.input_tensors[1]
+            data = op.input_tensors[0]
+            data_pad = None
+            if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
+                data_pad = data
+                data = data_pad.op.input_tensors[0]
+            _schedule(cfg, s, data, data_pad, kernel, output)
+
+    traverse_inline(s, outs[0].op, _callback)
+    return s
diff --git a/topi/python/topi/generic/nn.py b/topi/python/topi/generic/nn.py
index fe76b9715d59..1e01adb899b7 100644
--- a/topi/python/topi/generic/nn.py
+++ b/topi/python/topi/generic/nn.py
@@ -91,6 +91,54 @@ def schedule_conv2d_NCHWc(num_filter, kernel_size, strides,
     return _default_schedule(outs, False)
 
 
+@tvm.target.generic_func
+def schedule_conv2d_winograd_weight_transform(outs):
+    """Schedule for weight transformation of winograd
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+          The computation graph description of this operator
+          in the format of an array of tensors.
+
+    Returns
+    -------
+    sch: Schedule
+        The computation schedule for the op.
+    """
+    # Typically this is computed in nnvm PreCompute pass
+    # so we make a schedule here for cpu llvm
+    s = tvm.create_schedule([x.op for x in outs])
+    output = outs[0]
+    _, G = s[output].op.input_tensors
+    s[G].compute_inline()
+    eps, nu, co, ci = s[output].op.axis
+    r_kh, r_kw = s[output].op.reduce_axis
+    s[output].reorder(co, ci, r_kh, r_kw, eps, nu)
+    for axis in [r_kh, r_kw, eps, nu]:
+        s[output].unroll(axis)
+    s[output].parallel(co)
+    return s
+
+
+@tvm.target.generic_func
+def schedule_conv2d_winograd_without_weight_transform(outs):
+    """Schedule for winograd without weight transformation
+
+    Parameters
+    ----------
+    outs: Array of Tensor
+          The computation graph description of this operator
+          in the format of an array of tensors.
+
+    Returns
+    -------
+    sch: Schedule
+        The computation schedule for the op.
+    """
+    return _default_schedule(outs, False)
+
+
 @tvm.target.generic_func
 def schedule_conv2d_transpose_nchw(outs):
     """Schedule for conv2d_transpose_nchw
diff --git a/topi/python/topi/nn/conv2d.py b/topi/python/topi/nn/conv2d.py
index 43912368cf05..e0d2c403d4b4 100644
--- a/topi/python/topi/nn/conv2d.py
+++ b/topi/python/topi/nn/conv2d.py
@@ -3,78 +3,18 @@
 """Conv2D operators"""
 from __future__ import absolute_import as _abs
 from collections import namedtuple
+import numpy as np
 import tvm
+
 from .pad import pad
 from .util import get_pad_tuple
-from ..util import simplify
+from ..util import simplify, const_matrix, get_const_tuple
 
 # workload description of conv2d
 Workload = namedtuple('Workload',
                       ['in_dtype', 'out_dtype', 'height', 'width', 'in_filter', 'out_filter',
                        'hkernel', 'wkernel', 'hpad', 'wpad', 'hstride', 'wstride'])
 
-# schedule description of spatial
-SpatialPack = namedtuple('SpatialPack',
-                         ['vh', 'vw', 'vc', 'ba', 'bc', 'unroll'])
-
-# schedule description of im2col
-Im2ColPack = namedtuple('Im2ColPack',
-                        ['vp', 'vq', 'ba', 'bc', 'unroll'])
-
-_WORKLOADS = [
-    # workloads of resnet18 on imagenet
-    Workload('float32', 'float32', 224, 224, 3, 64, 7, 7, 3, 3, 2, 2),
-    Workload('float32', 'float32', 56, 56, 64, 64, 3, 3, 1, 1, 1, 1),
-    Workload('float32', 'float32', 56, 56, 64, 64, 1, 1, 0, 0, 1, 1),
-    Workload('float32', 'float32', 56, 56, 64, 128, 3, 3, 1, 1, 2, 2),
-    Workload('float32', 'float32', 56, 56, 64, 128, 1, 1, 0, 0, 2, 2),
-    Workload('float32', 'float32', 28, 28, 128, 128, 3, 3, 1, 1, 1, 1),
-    Workload('float32', 'float32', 28, 28, 128, 256, 3, 3, 1, 1, 2, 2),
-    Workload('float32', 'float32', 28, 28, 128, 256, 1, 1, 0, 0, 2, 2),
-    Workload('float32', 'float32', 14, 14, 256, 256, 3, 3, 1, 1, 1, 1),
-    Workload('float32', 'float32', 14, 14, 256, 512, 3, 3, 1, 1, 2, 2),
-    Workload('float32', 'float32', 14, 14, 256, 512, 1, 1, 0, 0, 2, 2),
-    Workload('float32', 'float32', 7, 7, 512, 512, 3, 3, 1, 1, 1, 1),
-    # workloads of mobile net on imagenet
-    Workload('float32', 'float32', 224, 224, 3, 32, 3, 3, 1, 1, 2, 2),
-    Workload('float32', 'float32', 112, 112, 32, 64, 1, 1, 0, 0, 1, 1),
-    Workload('float32', 'float32', 56, 56, 64, 128, 1, 1, 0, 0, 1, 1),
-    Workload('float32', 'float32', 56, 56, 128, 128, 1, 1, 0, 0, 1, 1),
-    Workload('float32', 'float32', 28, 28, 128, 256, 1, 1, 0, 0, 1, 1),
-    Workload('float32', 'float32', 28, 28, 256, 256, 1, 1, 0, 0, 1, 1),
-    Workload('float32', 'float32', 14, 14, 256, 512, 1, 1, 0, 0, 1, 1),
-    Workload('float32', 'float32', 14, 14, 512, 512, 1, 1, 0, 0, 1, 1),
-    Workload('float32', 'float32', 7, 7, 512, 1024, 1, 1, 0, 0, 1, 1),
-    Workload('float32', 'float32', 7, 7, 1024, 1024, 1, 1, 0, 0, 1, 1),
-    # workloads of resnet18 on imagenet (int16->int32 version)
-    Workload('int16', 'int32', 224, 224, 3, 64, 7, 7, 3, 3, 2, 2),
-    Workload('int16', 'int32', 56, 56, 64, 64, 3, 3, 1, 1, 1, 1),
-    Workload('int16', 'int32', 56, 56, 64, 64, 1, 1, 0, 0, 1, 1),
-    Workload('int16', 'int32', 56, 56, 64, 128, 3, 3, 1, 1, 2, 2),
-    Workload('int16', 'int32', 56, 56, 64, 128, 1, 1, 0, 0, 2, 2),
-    Workload('int16', 'int32', 28, 28, 128, 128, 3, 3, 1, 1, 1, 1),
-    Workload('int16', 'int32', 28, 28, 128, 256, 3, 3, 1, 1, 2, 2),
-    Workload('int16', 'int32', 28, 28, 128, 256, 1, 1, 0, 0, 2, 2),
-    Workload('int16', 'int32', 14, 14, 256, 256, 3, 3, 1, 1, 1, 1),
-    Workload('int16', 'int32', 14, 14, 256, 512, 3, 3, 1, 1, 2, 2),
-    Workload('int16', 'int32', 14, 14, 256, 512, 1, 1, 0, 0, 2, 2),
-    Workload('int16', 'int32', 7, 7, 512, 512, 3, 3, 1, 1, 1, 1),
-    # workloads of mobile net on imagenet (int16->int32 version)
-    Workload('int16', 'int32', 224, 224, 3, 32, 3, 3, 1, 1, 2, 2),
-    Workload('int16', 'int32', 112, 112, 32, 64, 1, 1, 0, 0, 1, 1),
-    Workload('int16', 'int32', 56, 56, 64, 128, 1, 1, 0, 0, 1, 1),
-    Workload('int16', 'int32', 56, 56, 128, 128, 1, 1, 0, 0, 1, 1),
-    Workload('int16', 'int32', 28, 28, 128, 256, 1, 1, 0, 0, 1, 1),
-    Workload('int16', 'int32', 28, 28, 256, 256, 1, 1, 0, 0, 1, 1),
-    Workload('int16', 'int32', 14, 14, 256, 512, 1, 1, 0, 0, 1, 1),
-    Workload('int16', 'int32', 14, 14, 512, 512, 1, 1, 0, 0, 1, 1),
-    Workload('int16', 'int32', 7, 7, 512, 1024, 1, 1, 0, 0, 1, 1),
-    Workload('int16', 'int32', 7, 7, 1024, 1024, 1, 1, 0, 0, 1, 1),
-]
-
-# platform specific schedule
-_CONV_SCHEDULE = {}
-
 @tvm.target.generic_func
 def conv2d(input, filter, strides, padding, layout='NCHW', out_dtype=None):
     """Conv2D operator.
@@ -178,137 +118,6 @@ def _get_schedule_NCHWc(wkl, layout, out_layout):
     return wkl
 
 
-def _spatial_pack(data, kernel, stride, padding, out_dtype=None):
-    """ Compute convolution with pack on spatial axes. """
-    if out_dtype is None:
-        out_dtype = data.dtype
-    assert data.shape[0].value == 1, "spatial pack convolution only support batch size=1"
-    wkl = _get_workload(data, kernel, stride, padding, out_dtype)
-    sch = _get_schedule(wkl)
-
-    H, W = wkl.height, wkl.width
-    CI, CO = wkl.in_filter, wkl.out_filter
-    KH, KW = wkl.hkernel, wkl.wkernel
-    HPAD, WPAD = wkl.hpad, wkl.wpad
-    HSTR, WSTR = wkl.hstride, wkl.wstride
-    HCAT, WCAT = KH-1, KW-1
-
-    VH = sch.vh
-    VW = sch.vw
-    VC = sch.vc
-    UNROLL = sch.unroll
-
-    TH = H + 2*HPAD
-    TW = W + 2*WPAD
-    OH = (H + 2*HPAD - KH) // HSTR + 1
-    OW = (W + 2*WPAD - KW) // WSTR + 1
-
-    dshape = (1, CI, H, W)
-    dpshape = (1, CI, TH, TW)
-    dvshape = (1, TH//(VH*HSTR), TW//(VW*WSTR), CI, VH*HSTR+HCAT, VW*WSTR+WCAT)
-
-    kshape = (CO, CI, KH, KW)
-    kvshape = (CO/VC, CI, KH, KW, VC)
-
-    ovshape = (1, CO // VC, OH // VH, OW // VW, VH, VW, VC)
-    oshape = (1, CO, OH, OW)
-
-    DOPAD = (HPAD != 0 and WPAD != 0)
-    if DOPAD:
-        data_pad = pad(data, (0, 0, HPAD, WPAD), name="data_pad")
-    else:
-        data_pad = data
-
-    data_vec = tvm.compute(dvshape, lambda n, h, w, ci, vh, vw: \
-        data_pad[n][ci][h*VH*HSTR+vh][w*VW*WSTR+vw], name='data_vec')
-
-    kernel_vec = tvm.compute(kvshape, lambda co, ci, dh, dw, vc: \
-        kernel[co*VC+vc][ci][dh][dw], name='kernel_vec')
-
-    ci = tvm.reduce_axis((0, CI), name='ci')
-    dh = tvm.reduce_axis((0, KH), name='dh')
-    dw = tvm.reduce_axis((0, KW), name='dw')
-
-    conv = tvm.compute(ovshape, lambda n, co, h, w, vh, vw, vc: \
-        tvm.sum(data_vec[n, h, w, ci, vh*HSTR+dh, vw*WSTR+dw].astype(out_dtype) *
-                kernel_vec[co, ci, dh, dw, vc].astype(out_dtype),
-                axis=[ci, dh, dw]), name='conv')
-
-    output = tvm.compute(oshape, lambda n, co, h, w:
-                         conv[n][co//VC][h/VH][w//VW][h%VH][w%VW][co%VC],
-                         name='output_unpack', tag='spatial_conv_output')
-
-    return output
-
-
-def _im2col_pack(data, kernel, stride, padding, out_dtype=None):
-    """ Compute convolution with im2col pack layout. """
-    if out_dtype is None:
-        out_dtype = data.dtype
-    assert data.shape[0].value == 1, "im2col pack convolution only support batch size=1"
-    wkl = _get_workload(data, kernel, stride, padding, out_dtype)
-    sch = _get_schedule(wkl)
-
-    N = 1
-    H, W = wkl.height, wkl.width
-    CI = wkl.in_filter
-    CO = wkl.out_filter
-    KH, KW = wkl.hkernel, wkl.wkernel
-    HPAD, WPAD = wkl.hpad, wkl.hpad
-    HSTR, WSTR = wkl.hstride, wkl.wstride
-
-    OH = (H + 2*HPAD - KH) // HSTR + 1
-    OW = (W + 2*WPAD - KW) // WSTR + 1
-
-    P = sch.vp
-    Q = sch.vq
-    UNROLL = sch.unroll
-
-    dshape = (N, CI, H, W)
-    dpshape = (N, CI, H+2*HPAD, W+2*WPAD)
-    dcshape = (N, OH, OW, CI, KH, KW)
-    dvshape = (N, OH * OW // P, CI, KH, KW, P)
-
-    kshape = (CO, CI, KH, KW)
-    kvshape = (CO // Q, CI, KH, KW, Q)
-
-    ovshape = (N, CO // Q, OH * OW // P, P, Q)
-    oshape = (N, CO, OH, OW)
-
-    ############### declaration
-
-    DO_PAD = (wkl.hpad != 0 and wkl.wpad != 0)
-    if DO_PAD:
-        data_pad = pad(data, (0, 0, HPAD, WPAD), name="data_pad")
-    else:
-        data_pad = data
-
-    data_col = tvm.compute(dcshape, lambda n, oh, ow, ci, hk, wk: \
-        data_pad[n][ci][oh*HSTR+hk][ow*WSTR+wk], name='data_col')
-
-    data_vec = tvm.compute(dvshape, lambda n, im, ci, hk, wk, vim: \
-        data_col[n][(im*P+vim)//OW][(im*P+vim)%OW][ci][hk][wk], name='data_vec')
-
-
-    kernel_vec = tvm.compute(kvshape, lambda co, ci, dh, dw, vc: \
-        kernel[co*Q+vc][ci][dh][dw], name='kernel_vec')
-
-    ci = tvm.reduce_axis((0, CI), name='ci')
-    hk = tvm.reduce_axis((0, KH), name='hk')
-    wk = tvm.reduce_axis((0, KW), name='wk')
-
-    conv = tvm.compute(ovshape, lambda n, co, im, vim, vco: \
-        tvm.sum(data_vec[n][im][ci][hk][wk][vim].astype(out_dtype) *
-                kernel_vec[co][ci][hk][wk][vco].astype(out_dtype),
-                axis=[ci, hk, wk]), name='conv')
-
-    output = tvm.compute(oshape, lambda n, co, h, w: \
-                         conv[n][co//Q][(h*OW+w)//P][(h*OW+w)%P][co%Q],
-                         name='output_vec', tag='im2col_conv_output')
-
-    return output
-
-
 def conv2d_nchw(Input, Filter, stride, padding, out_dtype=None):
     """Convolution operator in NCHW layout.
 
@@ -435,7 +244,7 @@ def conv2d_nhwc(Input, Filter, stride, padding, out_dtype='float32'):
     Returns
     -------
     output : tvm.Tensor
-        4-D with shape [batch, out_height,  out_width, out_channel]
+        4-D with shape [batch, out_height, out_width, out_channel]
     """
     assert isinstance(stride, int) or len(stride) == 2
     batch, in_height, in_width, in_channel = Input.shape
@@ -465,6 +274,7 @@ def conv2d_nhwc(Input, Filter, stride, padding, out_dtype='float32'):
         name="Conv2dOutput", tag="conv2d_nhwc")
     return Output
 
+
 @tvm.target.generic_func
 def conv2d_NCHWc(data, kernel, num_filter, kernel_size, stride,
                  padding, layout, out_layout, out_dtype='float32'):
@@ -510,8 +320,80 @@ def conv2d_NCHWc(data, kernel, num_filter, kernel_size, stride,
     # default declaration
     raise ValueError("missing register for topi.nn.conv2d_NCHWc")
 
-# map from schedule type to declaration function
-_SCH_TO_DECL_FUNC = {
-    SpatialPack: _spatial_pack,
-    Im2ColPack: _im2col_pack,
-}
+
+def conv2d_winograd_weight_transform(kernel, tile_size):
+    """Weight transformation for winograd
+
+    Parameters
+    ----------
+    kernel: Tensor
+        The raw kernel tensor with layout "NCHW". Only 3x3 kernel is supported for now
+    tile_size: int
+        Tile size of winograd transform. e.g. 2 for F(2x2, 3x3) and 4 for F(4x4, 3x3)
+
+    Returns
+    -------
+    output : tvm.Tensor
+        4-D with shape [alpha, alpha, CO, CI]
+    """
+    K = 3
+
+    shape = get_const_tuple(kernel.shape)
+    assert shape[2:] == (K, K), "Only support 3x3 kernel"
+
+    r = tile_size + K - 1
+    shape = (r, r) + shape[:2]
+
+    if tile_size == 2:
+        G_data = np.array([
+            [1, 0, 0],
+            [1.0/2, 1.0/2, 1.0/2],
+            [1.0/2, -1.0/2, 1.0/2],
+            [0, 0, 1],
+        ], dtype=kernel.dtype)
+    elif tile_size == 4:
+        G_data = np.array([
+            [1 / 4.0, 0, 0],
+            [-1 / 6.0, -1 / 6.0, -1 / 6.0],
+            [-1 / 6.0, 1 / 6.0, -1 / 6.0],
+            [1 / 24.0, 1 / 12.0, 1 / 6.0],
+            [1 / 24.0, -1 / 12.0, 1 / 6.0],
+            [0, 0, 1]
+        ], dtype=kernel.dtype)
+    else:
+        raise ValueError("Unsupoorted tile size:" + tile_size)
+
+    G = const_matrix(G_data, 'G')
+    r_kh = tvm.reduce_axis((0, K), name='r_kh')
+    r_kw = tvm.reduce_axis((0, K), name='r_kw')
+    return tvm.compute(shape, lambda eps, nu, co, ci:
+                       tvm.sum(kernel[co][ci][r_kh][r_kw] *
+                               G[eps][r_kh] * G[nu][r_kw],
+                               axis=[r_kh, r_kw]), name='transform_weight')
+
+
+@tvm.target.generic_func
+def conv2d_winograd_without_weight_transform(input, filter, strides, padding,
+                                             layout, out_dtype, tile_size):
+    """Compute convolution in winograd algorithm. The filter is supposed to be transformed
+    in advance.
+
+    Parameters
+    ----------
+    input : tvm.Tensor
+        4-D with shape [batch, in_height, in_width, in_channel]
+    filter : tvm.Tensor
+        4-D with shape [filter_height, filter_width, in_channel, num_filter]
+    strides : int or a list/tuple of two ints
+        Stride size, or [stride_height, stride_width]
+    padding : int or str
+        Padding size, or ['VALID', 'SAME']
+    tile_size: int
+        Tile size of winograd transform. e.g. 2 for F(2x2, 3x3) and 4 for F(4x4, 3x3)
+
+    Returns
+    -------
+    output : tvm.Tensor
+        4-D with shape [batch, out_height, out_width, out_channel]
+    """
+    raise ValueError("missing register for topi.nn.conv2d_winograd_without_weight_transform")
diff --git a/topi/python/topi/rasp/__init__.py b/topi/python/topi/rasp/__init__.py
deleted file mode 100644
index 270a48504468..000000000000
--- a/topi/python/topi/rasp/__init__.py
+++ /dev/null
@@ -1,7 +0,0 @@
-# pylint: disable=redefined-builtin, wildcard-import
-"""Raspberry pi specific declaration and schedules."""
-from __future__ import absolute_import as _abs
-
-from .conv2d import schedule_conv2d_nchw
-from .depthwise_conv2d import schedule_depthwise_conv2d_nchw
-from .bitserial_conv2d import schedule_bitserial_conv2d_nhwc
diff --git a/topi/python/topi/rasp/conv2d.py b/topi/python/topi/rasp/conv2d.py
deleted file mode 100644
index b958d00f2913..000000000000
--- a/topi/python/topi/rasp/conv2d.py
+++ /dev/null
@@ -1,358 +0,0 @@
-# pylint: disable=invalid-name,unused-variable,invalid-name
-"""Conv2D schedule on raspberry pi"""
-from __future__ import absolute_import as _abs
-import tvm
-from .. import tag
-from ..nn.conv2d import conv2d as _conv2d, _get_schedule
-from ..nn.conv2d import SpatialPack, Im2ColPack
-from ..nn.conv2d import _WORKLOADS, _SCH_TO_DECL_FUNC
-from ..nn.conv2d import _get_workload
-from ..nn.util import infer_pad, infer_stride
-from .. import generic
-
-_SCHEDULES = [
-    # float32 imagenet
-    SpatialPack(1, 8, 4, 1, 4, True),
-    SpatialPack(1, 7, 4, 2, 4, True),
-    SpatialPack(1, 4, 8, 4, 1, True),
-    SpatialPack(1, 4, 4, 1, 16, False),
-    SpatialPack(1, 4, 8, 4, 8, False),
-    SpatialPack(1, 7, 4, 3, 8, True),
-    SpatialPack(1, 2, 8, 1, 8, True),
-    SpatialPack(2, 1, 16, 1, 4, True),
-    SpatialPack(1, 7, 4, 1, 1, True),
-    Im2ColPack(7, 4, 1, 16, True),
-    Im2ColPack(7, 4, 1, 8, False),
-    Im2ColPack(7, 4, 1, 16, False),
-
-    # float32 mobilenet
-    SpatialPack(2, 2, 4, 28, 1, True),
-    SpatialPack(1, 4, 8, 14, 1, False),
-    SpatialPack(1, 2, 16, 8, 1, True),
-    SpatialPack(1, 4, 8, 8, 8, True),
-    SpatialPack(2, 2, 8, 1, 1, False),
-    SpatialPack(1, 4, 8, 4, 8, False),
-    SpatialPack(2, 2, 8, 1, 4, False),
-    SpatialPack(2, 2, 8, 1, 8, False),
-    Im2ColPack(7, 4, 1, 16, False),
-    Im2ColPack(7, 4, 1, 4, True),
-
-    # int8 imagenet
-    SpatialPack(2, 2, 4, 19, 8, False),
-    SpatialPack(2, 2, 8, 1, 4, True),
-    SpatialPack(2, 2, 8, 7, 4, False),
-    SpatialPack(2, 4, 4, 7, 16, False),
-    SpatialPack(1, 7, 4, 14, 4, True),
-    SpatialPack(2, 2, 8, 5, 1, False),
-    SpatialPack(1, 2, 16, 3, 8, True),
-    SpatialPack(1, 7, 4, 1, 16, True),
-    SpatialPack(2, 2, 8, 2, 16, True),
-    SpatialPack(1, 1, 8, 4, 4, True),
-    SpatialPack(1, 1, 4, 1, 8, False),
-    SpatialPack(1, 1, 8, 1, 16, True),
-
-    # int8 mobilenet
-    SpatialPack(2, 2, 8, 8, 1, True),
-    SpatialPack(1, 7, 4, 16, 4, True),
-    SpatialPack(1, 4, 8, 1, 1, True),
-    SpatialPack(1, 4, 8, 1, 1, True),
-    SpatialPack(1, 4, 8, 4, 8, True),
-    SpatialPack(1, 4, 8, 7, 1, True),
-    SpatialPack(1, 2, 8, 2, 32, True),
-    SpatialPack(1, 2, 16, 2, 16, True),
-    SpatialPack(1, 1, 32, 1, 16, False),
-    SpatialPack(1, 1, 16, 1, 32, True),
-]
-
-@_get_schedule.register("rasp")
-def _get_schedule_conv2d(wkl):
-    if wkl not in _WORKLOADS:
-        raise ValueError("no schedule for such workload: {}".format(wkl))
-    idx = _WORKLOADS.index(wkl)
-    sch = _SCHEDULES[idx]
-    return sch
-
-
-@_conv2d.register("rasp")
-def _declaration_conv2d(data, kernel, stride, padding, layout, out_dtype):
-    if out_dtype is None:
-        out_dtype = data.dtype
-    assert layout == 'NCHW', "only support NCHW convolution on rasp"
-    assert data.shape[0].value == 1, "only support batch size=1 convolution on rasp"
-    wkl = _get_workload(data, kernel, stride, padding, out_dtype)
-    sch = _get_schedule(wkl)
-    return _SCH_TO_DECL_FUNC[type(sch)](data, kernel, stride, padding, out_dtype)
-
-
-def _schedule_spatial_conv2d(s, data, data_pad, data_vec,
-                             kernel, kernel_vec,
-                             conv_out, output, last):
-    # no stride and padding info here
-    padding = infer_pad(data, data_pad)
-    if data_pad is None:
-        stride = infer_stride(data, kernel, output)
-    else:
-        stride = infer_stride(data_pad, kernel, output)
-    wkl = _get_workload(data, kernel, stride, padding, output.dtype)
-    sch = _get_schedule(wkl)
-
-    H, W = wkl.height, wkl.width
-    CI, CO = wkl.in_filter, wkl.out_filter
-    HK, WK = wkl.hkernel, wkl.wkernel
-    HPAD, WPAD = wkl.hpad, wkl.wpad
-    HSTR, WSTR = wkl.hstride, wkl.wstride
-
-    HCAT, WCAT = HK-1, WK-1
-    DOPAD = (HPAD != 0 and WPAD != 0)
-
-    VH = sch.vh
-    VW = sch.vw
-    VC = sch.vc
-    UNROLL = sch.unroll
-
-    A, B, C = data, kernel, last
-    A0, A1 = data_pad, data_vec
-    B0 = kernel_vec
-    C0, C1 = conv_out, output
-
-    CC = s.cache_write(C0, "global")
-
-    _, co, oh, ow, vh, vw, vc = s[C0].op.axis
-    if UNROLL:
-        s[C0].unroll(vw)
-    s[C0].vectorize(vc)
-
-    s[CC].compute_at(s[C0], ow)
-    _, co, oh, ow, vh, vw, vc = s[CC].op.axis
-    ci, dh, dw = s[CC].op.reduce_axis
-    s[CC].reorder(ci, dh, vh, dw, vw, vc)
-
-    if UNROLL:
-        s[CC].unroll(vw)
-    s[CC].vectorize(vc)
-
-    ##### Schedule A
-    if DOPAD:
-        s[A0].compute_inline()
-
-    _, h, _, _, _, _ = s[A1].op.axis
-    if sch.ba == 1:
-        oaxis = h
-        paxis = h
-    else:
-        oh, ih = s[A1].split(h, sch.ba)
-        oaxis = oh
-        paxis = ih
-
-    s[A1].parallel(paxis)
-    s[A1].pragma(oaxis, "parallel_launch_point")
-    s[A1].pragma(paxis, "parallel_stride_pattern")
-    s[A1].pragma(oaxis, "parallel_barrier_when_finish")
-
-
-    ##### Schedule B
-    co, _, _, _, _ = s[B0].op.axis
-    if sch.bc == 1:
-        oaxis = co
-        paxis = co
-    else:
-        oco, ico = s[B0].split(co, sch.bc)
-        oaxis = oco
-        paxis = ico
-
-    s[B0].parallel(paxis)
-    s[B0].pragma(oaxis, "parallel_launch_point")
-    s[B0].pragma(paxis, "parallel_stride_pattern")
-    s[B0].pragma(oaxis, "parallel_barrier_when_finish")
-
-
-    ##### Schedule C
-    n, co, h, w = s[C].op.axis
-    co, vc = s[C].split(co, VC)
-    oh, ow, vh, vw = s[C].tile(h, w, VH, VW)
-    s[C].reorder(n, co, oh, ow, vh, vw, vc)
-    if C != C1:
-        s[C1].compute_inline()
-    s[C0].compute_at(s[C], ow)
-
-    if sch.bc == 1:
-        oaxis = co
-        paxis = co
-    else:
-        oco, ico = s[C].split(co, sch.bc)
-        oaxis = oco
-        paxis = ico
-
-    s[C].parallel(paxis)
-    s[C].pragma(oaxis, "parallel_launch_point")
-    s[C].pragma(paxis, "parallel_stride_pattern")
-    s[C].pragma(oaxis, "parallel_barrier_when_finish")
-
-    return s
-
-def _schedule_im2col_conv2d(s, data, data_pad, data_col, data_vec,
-                            kernel, kernel_vec,
-                            conv_out, output, last):
-    # no stride and padding info here
-    padding = infer_pad(data, data_pad)
-    if data_pad is None:
-        stride = infer_stride(data, kernel, output)
-    else:
-        stride = infer_stride(data_pad, kernel, output)
-    wkl = _get_workload(data, kernel, stride, padding, output.dtype)
-    sch = _get_schedule(wkl)
-
-    H, W = wkl.height, wkl.width
-    CI = wkl.in_filter
-    CO = wkl.out_filter
-    HK, WK = wkl.hkernel, wkl.wkernel
-    HPAD, WPAD = wkl.hpad, wkl.wpad
-    HSTR, WSTR = wkl.hstride, wkl.wstride
-
-    HCAT, WCAT = HK-1, WK-1
-    DOPAD = (HPAD != 0 and WPAD != 0)
-
-    P = sch.vp
-    Q = sch.vq
-    UNROLL = sch.unroll
-
-    A, B, C = data, kernel, last
-    A0, A1, A2 = data_pad, data_col, data_vec
-    B0 = kernel_vec
-    C0, C1 = conv_out, output
-
-    CC = s.cache_write(C0, "global")
-    AA = s.cache_read(A2, "global", [CC])
-    BB = s.cache_read(B0, "global", [CC])
-
-
-    ##### Schedule CC
-    _, co, im, vim, vco = s[C0].op.axis
-    s[C0].unroll(vim)
-    s[C0].vectorize(vco)
-
-    s[CC].compute_at(s[C0], im)
-    _, co, im, vim, vco = s[CC].op.axis
-    ci, hk, wk = s[CC].op.reduce_axis
-    s[CC].reorder(ci, hk, wk, vim, vco)
-    s[CC].unroll(vim)
-    s[CC].vectorize(vco)
-    # s[CC].unroll(ccr)
-
-    ### Schedule C
-    _, co, h, w = s[C].op.axis
-    im = s[C].fuse(h, w)
-    im, vim = s[C].split(im, P)
-    co, vco = s[C].split(co, Q)
-    s[C].reorder(co, im, vim, vco)
-
-    if sch.bc == 1:
-        oaxis = co
-        paxis = co
-    else:
-        oco, ico = s[C].split(co, sch.bc)
-        oaxis = oco
-        paxis = ico
-
-    s[C].parallel(paxis)
-    s[C].pragma(oaxis, "parallel_launch_point")
-    s[C].pragma(paxis, "parallel_stride_pattern")
-    s[C].pragma(oaxis, "parallel_barrier_when_finish")
-    if C1 != C:
-        s[C1].compute_inline()
-
-    s[C0].compute_at(s[C], paxis)
-
-    ##### Schedule A
-    if DOPAD:
-        s[A0].compute_inline()
-    s[A1].compute_inline()
-    s[AA].compute_at(s[CC], wk)
-    s[AA].unroll(AA.op.axis[4])
-
-    _, im, _, _, _, _ = s[A2].op.axis
-    if sch.ba == 1:
-        oaxis = im
-        paxis = im
-    else:
-        oim, iim = s[A2].split(im, sch.ba)
-        oaxis = oim
-        paxis = iim
-
-    s[A2].parallel(paxis)
-    s[A2].pragma(oaxis, "parallel_launch_point")
-    s[A2].pragma(paxis, "parallel_stride_pattern")
-    s[A2].pragma(oaxis, "parallel_barrier_when_finish")
-
-
-    ##### Schedule B
-    s[BB].compute_at(s[CC], wk)
-    s[BB].vectorize(BB.op.axis[4])
-
-    co, _, _, _, _ = s[B0].op.axis
-    if sch.bc == 1:
-        oaxis = co
-        paxis = co
-    else:
-        oco, ico = s[B0].split(co, sch.bc)
-        oaxis = oco
-        paxis = ico
-
-    s[B0].parallel(paxis)
-    s[B0].pragma(oaxis, "parallel_launch_point")
-    s[B0].pragma(paxis, "parallel_stride_pattern")
-    s[B0].pragma(oaxis, "parallel_barrier_when_finish")
-
-    return s
-
-@generic.schedule_conv2d_nchw.register(["rasp"])
-def schedule_conv2d_nchw(outs):
-    """Create schedule for tensors"""
-    s = tvm.create_schedule([x.op for x in outs])
-
-    def traverse(op):
-        """Traverse operators from computation graph"""
-        # inline all one-to-one-mapping operators except the last stage (output)
-        if tag.is_broadcast(op.tag):
-            if op not in s.outputs:
-                s[op].compute_inline()
-            for tensor in op.input_tensors:
-                if tensor.op.input_tensors:
-                    traverse(tensor.op)
-
-        if 'spatial_conv_output' in op.tag:
-            output = op.output(0)
-            conv_out = op.input_tensors[0]
-            kernel_vec = conv_out.op.input_tensors[1]
-            kernel = kernel_vec.op.input_tensors[0]
-            if isinstance(kernel.op, tvm.tensor.ComputeOp) and "dilate" in kernel.op.tag:
-                s[kernel].compute_inline()
-            data_vec = conv_out.op.input_tensors[0]
-            data = data_vec.op.input_tensors[0]
-            data_pad = None
-            if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
-                data_pad = data
-                data = data_pad.op.input_tensors[0]
-
-            _schedule_spatial_conv2d(s, data, data_pad, data_vec,
-                                     kernel, kernel_vec,
-                                     conv_out, output, outs[0])
-
-        if 'im2col_conv_output' in op.tag:
-            output = op.output(0)
-            conv_out = op.input_tensors[0]
-            kernel_vec = conv_out.op.input_tensors[1]
-            kernel = kernel_vec.op.input_tensors[0]
-            data_vec = conv_out.op.input_tensors[0]
-            data_col = data_vec.op.input_tensors[0]
-            data = data_col.op.input_tensors[0]
-            data_pad = None
-            if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
-                data_pad = data
-                data = data_pad.op.input_tensors[0]
-            _schedule_im2col_conv2d(s, data, data_pad, data_col, data_vec,
-                                    kernel, kernel_vec,
-                                    conv_out, output, outs[0])
-
-    traverse(outs[0].op)
-    return s
diff --git a/topi/python/topi/rasp/depthwise_conv2d.py b/topi/python/topi/rasp/depthwise_conv2d.py
deleted file mode 100644
index b2ff78e46d88..000000000000
--- a/topi/python/topi/rasp/depthwise_conv2d.py
+++ /dev/null
@@ -1,207 +0,0 @@
-# pylint: disable=invalid-name,unused-variable, unused-argument
-"""Schedule for depthwise_conv2d with auto fusion"""
-from __future__ import absolute_import as _abs
-from collections import namedtuple
-import tvm
-from .. import tag
-from ..nn.util import infer_pad, infer_stride, get_pad_tuple
-from .. import generic
-
-_Workload = namedtuple('Workload',
-                       ['in_dtype', 'out_dtype', 'height', 'width', 'channel', 'multiplier',
-                        'hkernel', 'wkernel', 'hpad', 'wpad', 'hstride', 'wstride'])
-
-_Schedule = namedtuple('Schedule', ['vh', 'vw', 'vc', 'bc', 'unroll'])
-
-# workloads of depthwise conv mobile net on imagenet
-_WORKLOADS = [
-    _Workload('float32', 'float32', 112, 112, 32, 1, 3, 3, 1, 1, 1, 1),
-    _Workload('float32', 'float32', 112, 112, 64, 1, 3, 3, 1, 1, 2, 2),
-    _Workload('float32', 'float32', 56, 56, 128, 1, 3, 3, 1, 1, 1, 1),
-    _Workload('float32', 'float32', 56, 56, 128, 1, 3, 3, 1, 1, 2, 2),
-    _Workload('float32', 'float32', 28, 28, 256, 1, 3, 3, 1, 1, 1, 1),
-    _Workload('float32', 'float32', 28, 28, 256, 1, 3, 3, 1, 1, 2, 2),
-    _Workload('float32', 'float32', 14, 14, 512, 1, 3, 3, 1, 1, 1, 1),
-    _Workload('float32', 'float32', 14, 14, 512, 1, 3, 3, 1, 1, 2, 2),
-    _Workload('float32', 'float32', 7, 7, 1024, 1, 3, 3, 1, 1, 1, 1),
-    _Workload('int16', 'int32', 112, 112, 32, 1, 3, 3, 1, 1, 1, 1),
-    _Workload('int16', 'int32', 112, 112, 64, 1, 3, 3, 1, 1, 2, 2),
-    _Workload('int16', 'int32', 56, 56, 128, 1, 3, 3, 1, 1, 1, 1),
-    _Workload('int16', 'int32', 56, 56, 128, 1, 3, 3, 1, 1, 2, 2),
-    _Workload('int16', 'int32', 28, 28, 256, 1, 3, 3, 1, 1, 1, 1),
-    _Workload('int16', 'int32', 28, 28, 256, 1, 3, 3, 1, 1, 2, 2),
-    _Workload('int16', 'int32', 14, 14, 512, 1, 3, 3, 1, 1, 1, 1),
-    _Workload('int16', 'int32', 14, 14, 512, 1, 3, 3, 1, 1, 2, 2),
-    _Workload('int16', 'int32', 7, 7, 1024, 1, 3, 3, 1, 1, 1, 1),
-]
-
-_SCHEDULES = [
-    _Schedule(2, 1, 4, 1, True),
-    _Schedule(2, 4, 4, 2, True),
-    _Schedule(2, 1, 4, 2, False),
-    _Schedule(2, 4, 4, 1, True),
-    _Schedule(4, 1, 4, 8, True),
-    _Schedule(1, 1, 4, 2, True),
-    _Schedule(1, 1, 8, 8, True),
-    _Schedule(1, 1, 4, 1, False),
-    _Schedule(1, 1, 4, 4, False),
-    _Schedule(2, 4, 4, 2, False),
-    _Schedule(2, 7, 4, 1, True),
-    _Schedule(2, 4, 4, 4, False),
-    _Schedule(2, 2, 4, 4, False),
-    _Schedule(2, 2, 8, 4, False),
-    _Schedule(2, 2, 4, 4, True),
-    _Schedule(2, 2, 8, 4, False),
-    _Schedule(1, 2, 8, 4, True),
-    _Schedule(1, 1, 4, 8, True),
-]
-
-def _get_workload(data, kernel, stride, padding, out_dtype):
-    _, C, IH, IW = [x.value for x in data.shape]
-    _, MT, KH, KW = [x.value for x in kernel.shape]
-    HPAD, WPAD, _, _ = get_pad_tuple(padding, kernel)
-    if isinstance(stride, (tuple, list)):
-        HSTR, WSTR = stride
-    else:
-        HSTR, WSTR = stride, stride
-    return _Workload(data.dtype, out_dtype, IH, IW, C, MT, KH, KW, HPAD, WPAD, HSTR, WSTR)
-
-
-def _schedule(s, data, data_pad, kernel, output, last):
-    padding = infer_pad(data, data_pad)
-    if data_pad is None:
-        stride = infer_stride(data, kernel, output)
-    else:
-        stride = infer_stride(data_pad, kernel, output)
-    wkl = _get_workload(data, kernel, stride, padding, output.dtype)
-
-    if wkl not in _WORKLOADS:
-        return s
-
-    # use specified schedule
-    sch = _SCHEDULES[_WORKLOADS.index(wkl)]
-
-    H, W = wkl.height, wkl.width
-    CN = wkl.channel
-    MT = wkl.multiplier
-
-    HK, WK = wkl.hkernel, wkl.wkernel
-    HPAD, WPAD = wkl.hpad, wkl.wpad
-    HSTR, WSTR = wkl.hstride, wkl.wstride
-
-    VH, VW = sch.vh, sch.vw
-    BC = sch.bc
-    VC = sch.vc
-
-    TH = H + 2*HPAD
-    TW = W + 2*WPAD
-    OH = (H + 2*HPAD - HK) / HSTR + 1
-    OW = (W + 2*WPAD - WK) / WSTR + 1
-
-
-    A, B, C = data, kernel, output
-    A0 = data_pad
-
-    A1 = s.cache_read(A0, "global", C)
-    _, c, h, w = s[A1].op.axis
-    c, vc = s[A1].split(c, VC)
-    s[A1].reorder(c, h, w, vc)
-
-    A2 = s.cache_write(A1, 'global')
-    s[A0].compute_inline()
-    s[A1].compute_inline()
-
-    B0 = s.cache_read(B, "global", C)
-    c, m, h, w = s[B0].op.axis
-    c, vc = s[B0].split(c, VC)
-    s[B0].reorder(c, m, h, w, vc)
-
-    B1 = s.cache_write(B0, 'global')
-    s[B0].compute_inline()
-
-    _, c, h, w = s[C].op.axis
-    c, vc = s[C].split(c, VC)
-    s[C].reorder(c, h, w, vc)
-
-
-    C0 = s.cache_write(C, 'global')
-    _, c, h, w, vc = s[C0].op.axis
-    dh, dw = s[C0].op.reduce_axis
-    oh, ow, ih, iw = s[C0].tile(h, w, VH, VW)
-    s[C0].reorder(c, oh, ow, dh, dw, ih, iw, vc)
-    if sch.unroll:
-        s[C0].unroll(iw)
-    s[C0].vectorize(vc)
-
-
-    # # s[C0].compute_at(s[C0], ow)
-    launch, c, _, _ = s[C].op.axis
-    s[C].pragma(launch, "parallel_launch_point")
-
-    s[C].parallel(c)
-    s[C].pragma(c, "parallel_stride_pattern")
-    s[C].pragma(c, "parallel_barrier_when_finish")
-
-
-    s[C0].compute_at(s[C], launch)
-    _, c, h, w, vc = s[C0].op.axis
-    s[C0].parallel(c)
-    s[C0].pragma(c, "parallel_stride_pattern")
-    s[C0].pragma(c, "parallel_barrier_when_finish")
-
-
-    s[A2].compute_at(s[C0], oh)
-    # parallel(s[A2], s[A2].op.axis[1], BC)
-
-    # # s[B0].compute_at(s[C0], ow)
-    s[B1].compute_at(s[C], launch)
-    c, m, h, w, vc = s[B1].op.axis
-    s[B1].parallel(c)
-    s[B1].pragma(c, "parallel_stride_pattern")
-    s[B1].pragma(c, "parallel_barrier_when_finish")
-
-    return s
-
-
-@generic.schedule_depthwise_conv2d_nchw.register(["cpu", "rasp"])
-def schedule_depthwise_conv2d_nchw(outs):
-    """Schedule for depthwise_conv2d nchw forward.
-
-    Parameters
-    ----------
-    outs: Array of Tensor
-        The computation graph description of depthwise_conv2d
-        in the format of an array of tensors.
-
-    Returns
-    -------
-    s: Schedule
-        The computation schedule for depthwise_conv2d nchw.
-    """
-    outs = [outs] if isinstance(outs, tvm.tensor.Tensor) else outs
-    s = tvm.create_schedule([x.op for x in outs])
-
-    def traverse(op):
-        """Internal travserse function"""
-        # inline all one-to-one-mapping operators except the last stage (output)
-        if tag.is_broadcast(op.tag):
-            if op not in s.outputs:
-                s[op].compute_inline()
-            for tensor in op.input_tensors:
-                if tensor.op.input_tensors:
-                    traverse(tensor.op)
-        # schedule depthwise_conv2d
-        if op.tag == 'depthwise_conv2d_nchw':
-            output = op.output(0)
-            kernel = op.input_tensors[1]
-            if isinstance(kernel.op, tvm.tensor.ComputeOp) and "dilate" in kernel.op.tag:
-                s[kernel].compute_inline()
-            data = op.input_tensors[0]
-            data_pad = None
-            if isinstance(data.op, tvm.tensor.ComputeOp) and "pad" in data.op.tag:
-                data_pad = data
-                data = data_pad.op.input_tensors[0]
-            _schedule(s, data, data_pad, kernel, output, outs[0])
-
-    traverse(outs[0].op)
-    return s
diff --git a/topi/python/topi/util.py b/topi/python/topi/util.py
index 3625f6aaefaa..b5d5dd2b99ad 100644
--- a/topi/python/topi/util.py
+++ b/topi/python/topi/util.py
@@ -1,7 +1,30 @@
+# pylint: disable=invalid-name
 """Common topi utilities"""
 from __future__ import absolute_import as _abs
 import tvm
 
+from . import tag
+
+def traverse_inline(s, op, callback):
+    """Traverse computation graph and do auto inline
+
+    Parameters
+    ----------
+    s: schedule
+        The schedule
+    op: Operation
+        The final output operator.
+    callback: callable
+        The callback function on each op
+    """
+    if tag.is_injective(op.tag):
+        if op not in s.outputs:
+            s[op].compute_inline()
+        for tensor in op.input_tensors:
+            if tensor.op.input_tensors:
+                traverse_inline(s, tensor.op, callback)
+    callback(op)
+
 
 def prod(x):
     """Get the product of every items in the tuple.
@@ -151,3 +174,33 @@ def unravel_index(idx, shape):
         idx = idx // shape[i]
     indices = indices[::-1]
     return indices
+
+
+def const_matrix(matrix, name="const_matrix"):
+    """convert a const numpy 2-dimensional matrix to tvm tensor
+
+    Parameters
+    ----------
+    matrix: numpy.ndarray
+        Const input array
+    name: str, optional
+        The name of output op
+
+    Returns
+    -------
+    tensor: Tensor
+        The created tensor
+    """
+    row, col = matrix.shape
+    dtype = str(matrix.dtype)
+
+    def select_array(i, j):
+        now = tvm.const(0.0, dtype)
+        for ii in range(row):
+            for jj in range(col):
+                now = tvm.select(tvm.all(i % row == ii, j % col == jj),
+                                 tvm.const(matrix[ii][jj], dtype),
+                                 now)
+        return now
+
+    return tvm.compute(matrix.shape, select_array, name=name)
diff --git a/topi/tests/python/test_topi_bitserial_conv2d_rasp.py b/topi/tests/python/test_topi_bitserial_conv2d_rasp.py
index 5789c5496205..3de954abc291 100644
--- a/topi/tests/python/test_topi_bitserial_conv2d_rasp.py
+++ b/topi/tests/python/test_topi_bitserial_conv2d_rasp.py
@@ -22,7 +22,7 @@ def verify_bitserial_conv2d_nhwc(batch, in_size, in_channel, num_filter, kernel,
     input_type='uint32'
     out_dtype='int32'
 
-    with tvm.target.rasp():
+    with tvm.target.arm_cpu('rasp3b'):
         A = tvm.placeholder((batch, in_height, in_width, in_channel), dtype=input_type, name='A')
         W = tvm.placeholder((kernel, kernel, in_channel, num_filter), dtype=input_type, name='W')
         B = topi.nn.bitserial_conv2d(A, W, stride, padding, activation_bits, weight_bits, out_dtype=out_dtype, 
diff --git a/topi/tests/python/test_topi_conv2d.py b/topi/tests/python/test_topi_conv2d.py
index e7ea956eea78..124c98c65c7a 100644
--- a/topi/tests/python/test_topi_conv2d.py
+++ b/topi/tests/python/test_topi_conv2d.py
@@ -2,6 +2,7 @@
 import os
 import numpy as np
 import tvm
+from tvm import autotvm
 import topi
 import topi.testing
 from tvm.contrib.pickle_memoize import memoize
@@ -11,10 +12,10 @@
 def verify_conv2d(batch, in_size, in_channel, num_filter, kernel, stride, padding):
     in_height = in_width = in_size
 
-    with tvm.target.rasp():
+    with tvm.target.arm_cpu():
         A = tvm.placeholder((batch, in_channel, in_height, in_width), name='A')
         W = tvm.placeholder((num_filter, in_channel, kernel, kernel), name='W')
-        B = topi.nn.conv2d(A, W, stride, padding)
+        B = topi.nn.conv2d(A, W, (stride, stride), (padding, padding), 'NCHW', 'float32')
         s = topi.generic.schedule_conv2d_nchw([B])
 
     a_shape = get_const_tuple(A.shape)
@@ -39,7 +40,8 @@ def get_ref_data():
     np.testing.assert_allclose(b.asnumpy(), b_np, rtol=1e-5)
 
 def test_conv2d():
-    verify_conv2d(1, 56,  64, 64,  3, 1, 1)
+    with autotvm.tophub.context(tvm.target.arm_cpu('rasp3b')):
+        verify_conv2d(1, 56, 64, 64, 3, 1, 1)
 
 if __name__ == "__main__":
     test_conv2d()
diff --git a/tutorials/autotvm/tune_cuda_conv2d.py b/tutorials/autotvm/tune_conv2d_cuda.py
similarity index 90%
rename from tutorials/autotvm/tune_cuda_conv2d.py
rename to tutorials/autotvm/tune_conv2d_cuda.py
index b6096aa47a5b..03f319ba57f1 100644
--- a/tutorials/autotvm/tune_cuda_conv2d.py
+++ b/tutorials/autotvm/tune_conv2d_cuda.py
@@ -8,6 +8,27 @@
 vendor provided library CuDNN in many cases.
 """
 
+######################################################################
+# Install dependencies
+# ----------------------------------------
+# To use autotvm package in tvm, we need to install some extra dependencies.
+# (change "3" to "2" if you use python2):
+#
+# .. code-block:: bash
+#
+#   pip3 install --user psutil xgboost
+#
+# To make tvm run faster in tuning, it is recommended to use cython
+# as FFI of tvm. In the root directory of tvm, execute
+# (change "3" to "2" if you use python2):
+#
+# .. code-block:: bash
+#
+#   pip3 install --user cython
+#   sudo make cython3
+#
+# Now return to python code. Import packages.
+
 import logging
 import sys
 import numpy as np
@@ -133,7 +154,7 @@ def conv2d_no_batching(N, H, W, CI, CO, KH, KW, stride, padding):
 # for this template
 
 # logging config (for printing tuning log to screen)
-logging.basicConfig(level=logging.INFO, stream=sys.stdout)
+logging.basicConfig(level=logging.DEBUG, stream=sys.stdout)
 
 # the last layer in resnet
 N, H, W, CO, CI, KH, KW, strides, padding = 1, 7, 7, 512, 512, 3, 3, (1, 1), (1, 1)
@@ -144,12 +165,12 @@ def conv2d_no_batching(N, H, W, CI, CO, KH, KW, stride, padding):
 
 # use local gpu, measure 5 times for every config to reduce variance
 # run 8 parallel threads for compilation
-measure_option = autotvm.measure_option(mode='local',
-                                        number=10,
+measure_option = autotvm.measure_option('local',
+                                        number=5,
                                         parallel_num=8,
                                         timeout=20)
 
-# begin tuning, log records to file `conv2d.tsv`
+# begin tuning, log records to file `conv2d.log`
 tuner = autotvm.tuner.XGBTuner(task)
 tuner.tune(n_trial=20,
            measure_option=measure_option,
@@ -186,6 +207,6 @@ def conv2d_no_batching(N, H, W, CI, CO, KH, KW, stride, padding):
 
 # Evaluate running time. Here we choose a large repeat number (200) to reduce the noise
 # and the overhead of kernel launch. You can also use nvprof to validate the result.
-
 evaluator = func.time_evaluator(func.entry_name, ctx, number=200)
 print('Time cost of this operator: %f' % evaluator(a_tvm, w_tvm, c_tvm).mean)
+
diff --git a/tutorials/autotvm/tune_nnvm_arm.py b/tutorials/autotvm/tune_nnvm_arm.py
new file mode 100644
index 000000000000..23bd0f93ff23
--- /dev/null
+++ b/tutorials/autotvm/tune_nnvm_arm.py
@@ -0,0 +1,364 @@
+"""
+Auto-tuning a convolutional network for ARM CPU
+====================================================
+**Author**: `Lianmin Zheng <https://https://github.com/merrymercy>`_
+
+Auto-tuning for a specific ARM device is critical for getting the best
+performance. This is a tutorial about how to tune a whole convolutional
+network.
+
+The operator implementation for ARM CPU in TVM is written in template form.
+It has many tunable knobs (tile factor, vectorization, unrolling, etc).
+We will do tuning for all convolution and depthwise convolution operators
+in the neural network. After the tuning, we can get a log file which stores
+the best knob values for all required operators. When the tvm compiler compiles
+these operators, it will query this log file to get the best knob values.
+
+We also released pre-tuned parameters for some arm devices. You can go to
+`ARM CPU Benchmark <https://github.com/dmlc/tvm/wiki/Benchmark#arm-cpu>`_
+to see the results.
+"""
+
+######################################################################
+# Install dependencies
+# ----------------------------------------
+# To use autotvm package in tvm, we need to install some extra dependencies.
+# (change "3" to "2" if you use python2):
+#
+# .. code-block:: bash
+#
+#   pip3 install --user psutil xgboost
+#
+# To make tvm run faster in tuning, it is recommended to use cython
+# as FFI of tvm. In the root directory of tvm, execute
+# (change "3" to "2" if you use python2):
+#
+# .. code-block:: bash
+#
+#   pip3 install --user cython
+#   sudo make cython3
+#
+# Now return to python code. Import packages.
+
+import os
+
+import numpy as np
+
+import nnvm.testing
+import nnvm.compiler
+import tvm
+from tvm import autotvm
+from tvm.autotvm.tuner import XGBTuner, GATuner, RandomTuner, GridSearchTuner
+from tvm.contrib.util import tempdir
+import tvm.contrib.graph_runtime as runtime
+
+#################################################################
+# Define network
+# --------------
+# First we need to define the network in nnvm symbol API.
+# We can load some pre-defined network from :code:`nnvm.testing`.
+# We can also load models from MXNet, ONNX and TensorFlow (see NNVM
+# tutorials :ref:`tutorial-nnvm` for more details).
+
+def get_network(name, batch_size):
+    """Get the symbol definition and random weight of a network"""
+    shape = {"data": (batch_size, 3, 224, 224)}
+    output_shape = (batch_size, 1000)
+
+    if name =='resnet-18':
+        net, params = nnvm.testing.resnet.get_workload(num_layers=18, batch_size=batch_size)
+    elif name =='mobilenet':
+        net, params = nnvm.testing.mobilenet.get_workload(batch_size=batch_size)
+    elif name =='squeezenet v1.1':
+        net, params = nnvm.testing.squeezenet.get_workload(batch_size=batch_size, version='1.1')
+    elif name =='vgg-16':
+        net, params = nnvm.testing.vgg.get_workload(num_layers=16, batch_size=batch_size)
+    elif name =='custom':
+        # an example for custom network
+        from nnvm.testing import utils
+        net = nnvm.sym.Variable('data')
+        net = nnvm.sym.conv2d(net, channels=4, kernel_size=(3,3), padding=(1,1))
+        net = nnvm.sym.flatten(net)
+        net = nnvm.sym.dense(net, units=1000)
+        net, params = utils.create_workload(net, batch_size, (3, 224, 224))
+    elif name == 'mxnet':
+        # an example for mxnet model
+        from mxnet.gluon.model_zoo.vision import get_model
+        block = get_model('resnet18_v1', pretrained=True)
+        net, params = nnvm.frontend.from_mxnet(block)
+        net = nnvm.sym.softmax(net)
+    else:
+        raise ValueError("Unsupported network: " + name)
+
+    return net, params, shape, output_shape
+
+#################################################################
+# Start RPC Tracker
+# -----------------
+# TVM uses RPC session to communicate with ARM boards.
+# During tuning, the tuner will send the generated code to the board and
+# measure the speed of code on the board.
+#
+# To scale up the tuning, TVM uses RPC Tracker to manage distributed devices.
+# The RPC Tracker is a centralized master node. We can register all devices to
+# the tracker. For example, if we have 10 phones, we can register all of them
+# to the tracker, then we can run 10 measurements in parallel, which accelerates
+# the tuning process.
+#
+# To start an RPC tracker, run this command in the host machine. The tracker is
+# required during the whole tuning process, so we need to open a new terminal for
+# this command:
+#
+# .. code-block:: bash
+#
+#   python -m tvm.exec.rpc_tracker --host=0.0.0.0 --port=9190
+#
+# The expected output is
+#
+# .. code-block:: bash
+#
+#   INFO:RPCTracker:bind to 0.0.0.0:9190
+
+#################################################################
+# Register devices to RPC Tracker
+# -----------------------------------
+# Now we can register our devices to the tracker. The first step is to
+# build tvm runtime for the ARM devices.
+#
+# * For Linux:
+#   Follow this section :ref:`build-tvm-runtime-on-device` to build
+#   tvm runtime on the device. Then register the device to tracker by
+#
+#   .. code-block:: bash
+#
+#     python -m tvm.exec.rpc_server --tracker=[HOST_IP]:9190 --key=rk3399
+#
+#   (replace :code:`[HOST_IP]` with the IP address of your host machine)
+#
+# * For Android:
+#   Follow this `readme page <https://github.com/dmlc/tvm/tree/master/apps/android_rpc>`_ to
+#   install tvm rpc apk on the android device. Make sure you can pass the android rpc test.
+#
+# After registering devices, we can confirm it by querying rpc_tracker
+#
+# .. code-block:: bash
+#
+#   python -m tvm.exec.query_rpc_tracker --host=0.0.0.0 --port=9190
+#
+# For example, if we have 2 Huawei mate10 pro, 11 Raspberry Pi 3B and 2 rk3399,
+# the output can be
+#
+# .. code-block:: bash
+#
+#    Queue Status
+#    ----------------------------
+#    key          free    pending
+#    ----------------------------
+#    mate10pro    2       0
+#    rk3399       2       0
+#    rpi3b        11      0
+#    ----------------------------
+
+###########################################
+# Set Tuning Options
+# ------------------
+# Before tuning, we should do some configurations. Here I use an RK3399 board
+# in our environment as example. In your setting, you should modify the target
+# and device_key accordingly.
+
+# Replace "aarch64-linux-gnu" with the correct target of your board.
+# This target is used for cross compilation. You can query it by :code:`gcc -v` on your device.
+target = tvm.target.create('llvm -device=arm_cpu -target=aarch64-linux-gnu')
+
+# Also replace this with the device key in your tracker
+device_key = 'rk3399'
+
+# tuning option
+network = 'resnet-18'
+log_file = "%s.%s.log" % (device_key, network)
+dtype = 'float32'
+
+tuning_option = {
+   'log_filename': log_file,
+
+   'tuner':'xgb',
+   'n_trial': 1000,
+   'early_stopping': 200,
+
+   'measure_option': autotvm.measure_option(
+       autotvm.use_rpc(device_key, host='localhost', port=9190),
+       number=4,
+       parallel_num=1,
+       timeout=10),
+
+   'use_transfer_learning': True,
+}
+
+####################################################################
+#
+# .. note:: How to set tuning options
+#
+#   In general, the default value provided here works well. It is the same
+#   value that we used to generate pre-tuned parameters.
+#   If you have multiple devices, you can set :code:`parallel_num` to
+#   the number of devices you have. (e.g. set it to 3 if you register 3 rk3399
+#   boards to the tracker).
+#   If you have large time budget, you can set :code:`n_trial`, :code:`early_stopping` larger,
+#   which makes the tuning run longer.
+#   If your device is very slow or a single conv2d operator in your network has large FLOPs,
+#   consider setting timeout larger.
+#
+#   **For android phone**, add :code:`build_func='ndk'` to the argument list of
+#   :code:`autotvm.measure_option` to use Android NDK for creating shared library.
+#
+
+###################################################################
+# Begin Tuning
+# ------------
+# Now we can extract tuning tasks from the network and begin tuning.
+# Here we provide a simple utility function to tune a list of tasks.
+# This function is just an initial implementation which tune them in sequential order.
+# Later we will bring more sophisticated tuner scheduler.
+
+# You can skip the implementation of this function for this tutorial.
+def tune_tasks(tasks,
+               measure_option,
+               tuner='xgb',
+               n_trial=500,
+               early_stopping=200,
+               log_filename='tuning.log',
+               use_transfer_learning=True,
+               try_winograd=True):
+    if try_winograd:
+        for i in range(len(tasks)):
+            try:  # try winograd template
+                tsk = autotvm.task.create(tasks[i].name, tasks[i].args,
+                                          tasks[i].target, tasks[i].target_host, 'winograd')
+                tasks.append(tsk)
+            except Exception:
+                pass
+
+    # create tmp log file
+    tmp_log_file = log_filename + ".tmp"
+    if os.path.exists(tmp_log_file):
+        os.remove(tmp_log_file)
+
+    for i, tsk in enumerate(tasks):
+        prefix = "[Task %2d/%2d] " %(i+1, len(tasks))
+
+        # create tuner
+        if tuner == 'xgb' or tuner == 'xgb-rank':
+            tuner_obj = XGBTuner(tsk, loss_type='rank')
+        elif tuner == 'ga':
+            tuner_obj = GATuner(tsk, pop_size=50)
+        elif tuner == 'random':
+            tuner_obj = RandomTuner(tsk)
+        elif tuner == 'gridsearch':
+            tuner_obj = GridSearchTuner(tsk)
+        else:
+            raise ValueError("Invalid tuner: " + tuner)
+
+        if use_transfer_learning:
+            if os.path.isfile(tmp_log_file):
+                tuner_obj.load_history(autotvm.record.load_from_file(tmp_log_file))
+
+        # do tuning
+        tuner_obj.tune(n_trial=min(n_trial, len(tsk.config_space)),
+                       early_stopping=early_stopping,
+                       measure_option=measure_option,
+                       callbacks=[
+                           autotvm.callback.progress_bar(n_trial, prefix=prefix),
+                           autotvm.callback.log_to_file(tmp_log_file)])
+
+    # pick best records to a cache file
+    autotvm.record.pick_best(tmp_log_file, log_filename)
+    os.remove(tmp_log_file)
+
+
+########################################################################
+# Finally we launch tuning jobs and evaluate the end-to-end performance.
+
+def tune_and_evaluate():
+    # extract workloads from nnvm graph
+    net, params, shape, out_shape = get_network(network, batch_size=1)
+    tasks = autotvm.task.extract_from_graph(net, shape=shape, dtype=dtype,
+                                            symbols=(nnvm.sym.conv2d,),
+                                            target=target)
+
+    # run tuning tasks
+    tune_tasks(tasks, **tuning_option)
+
+    # compile kernels with history best records
+    with autotvm.apply_history_best(log_file):
+        print("Compile...")
+        with nnvm.compiler.build_config(opt_level=2, add_pass=['AlterOpLayout']):
+            graph, lib, params = nnvm.compiler.build(
+                net, target=target,
+                shape=shape, params=params, dtype=dtype)
+
+        # export library
+        tmp = tempdir()
+        if tuning_option['measure_option']['build_func'] == 'ndk': # for android
+            from tvm.contrib import ndk
+            filename = "net.so"
+            lib.export_library(tmp.relpath(filename), ndk.create_shared)
+        else:
+            filename = "net.tar"
+            lib.export_library(tmp.relpath(filename))
+
+        # upload module to device
+        print("Upload...")
+        remote = autotvm.measure.request_remote(device_key, timeout=10000)
+        remote.upload(tmp.relpath(filename))
+        rlib = remote.load_module(filename)
+
+        # upload parameters to device
+        ctx = remote.context(str(target), 0)
+        rparams = {k: tvm.nd.array(v, ctx) for k, v in params.items()}
+        data_tvm = tvm.nd.array((np.random.uniform(size=shape['data'])).astype(dtype))
+        module = runtime.create(graph, rlib, ctx)
+        module.set_input('data', data_tvm)
+        module.set_input(**rparams)
+
+        # evaluate
+        print("Evaluate inference time cost...")
+        ftimer = module.module.time_evaluator("run", ctx, number=1, repeat=10)
+        prof_res = np.array(ftimer().results) * 1000 # convert to millisecond
+        print("Mean inference time (std dev): %.2f ms (%.2f ms)" %
+                (np.mean(prof_res), np.std(prof_res)))
+
+# We do not run the tuning in our webpage server since it takes too long.
+# Uncomment the following line to run by yourself.
+# tune_and_evaluate()
+
+######################################################################
+# Sample Output
+# -------------
+# The tuning needs to train xgboost models and use them for prediction.
+# So a high performance CPU is recommended.
+# It takes about 1.5 hour on a 32T AMD Ryzen CPU.
+# One sample output is
+#
+# .. code-block:: bash
+#
+#    [Task  1/16]  Current/Best:   13.15/  20.49 GFLOPS | Progress: (297/1000) | 348.51 s Done.
+#    [Task  2/16]  Current/Best:   16.66/  22.64 GFLOPS | Progress: (475/1000) | 415.42 s Done.
+#    [Task  3/16]  Current/Best:   10.33/  14.19 GFLOPS | Progress: (306/1000) | 239.61 s Done.
+#    [Task  4/16]  Current/Best:   13.29/  20.88 GFLOPS | Progress: (242/1000) | 227.48 s Done.
+#    [Task  5/16]  Current/Best:   13.28/  15.61 GFLOPS | Progress: (237/1000) | 191.56 s Done.
+#    [Task  6/16]  Current/Best:   20.16/  23.86 GFLOPS | Progress: (315/1000) | 304.31 s Done.
+#    [Task  7/16]  Current/Best:    9.22/  22.00 GFLOPS | Progress: (458/1000) | 433.26 s Done.
+#    [Task  8/16]  Current/Best:   14.12/  17.80 GFLOPS | Progress: (270/1000) | 240.73 s Done.
+#    [Task  9/16]  Current/Best:   14.59/  24.02 GFLOPS | Progress: (209/1000) | 213.61 s Done.
+#    [Task 10/16]  Current/Best:    9.86/  21.74 GFLOPS | Progress: (367/1000) | 359.93 s Done.
+#    [Task 11/16]  Current/Best:    5.01/  18.86 GFLOPS | Progress: (202/1000) | 191.18 s Done.
+#    [Task 12/16]  Current/Best:    8.61/  25.23 GFLOPS | Progress: (220/1000) | 220.74 s Done.
+#    [Task 13/16]  Current/Best:   10.87/  25.79 GFLOPS | Progress: (465/1000) | 902.14 s Done.
+#    [Task 14/16]  Current/Best:   15.33/  29.38 GFLOPS | Progress: (239/1000) | 481.33 s Done.
+#    [Task 15/16]  Current/Best:   12.09/  38.60 GFLOPS | Progress: (476/1000) | 928.35 s Done.
+#    [Task 16/16]  Current/Best:   16.77/  47.08 GFLOPS | Progress: (255/1000) | 439.91 s Done.
+#    Compile...
+#    Upload...
+#    Evaluate inference time cost...
+#    Mean inference time (std dev): 156.51 ms (0.89 ms)
+#
diff --git a/tutorials/autotvm/tune_simple_template.py b/tutorials/autotvm/tune_simple_template.py
index 747d4fb4d146..f2a2ea9e1266 100644
--- a/tutorials/autotvm/tune_simple_template.py
+++ b/tutorials/autotvm/tune_simple_template.py
@@ -12,6 +12,27 @@
 The whole workflow is illustrated by a matrix multiplication example.
 """
 
+######################################################################
+# Install dependencies
+# ----------------------------------------
+# To use autotvm package in tvm, we need to install some extra dependencies.
+# (change "3" to "2" if you use python2):
+#
+# .. code-block:: bash
+#
+#   pip3 install --user psutil xgboost
+#
+# To make tvm run faster in tuning, it is recommended to use cython
+# as FFI of tvm. In the root directory of tvm, execute
+# (change "3" to "2" if you use python2):
+#
+# .. code-block:: bash
+#
+#   pip3 install --user cython
+#   sudo make cython3
+#
+# Now return to python code. Import packages.
+
 import logging
 import sys
 
@@ -247,10 +268,10 @@ def matmul(N, L, M, dtype):
 # used to get the best config later.
 
 # logging config (for printing tuning log to screen)
-logging.basicConfig(level=logging.INFO, stream=sys.stdout)
+logging.basicConfig(level=logging.DEBUG, stream=sys.stdout)
 
 # use local cpu, measure 5 times for every config to reduce variance
-measure_option = autotvm.measure_option(mode='local',
+measure_option = autotvm.measure_option('local',
                                         number=5)
 
 # begin tuning, log records to file `matmul.log`
diff --git a/tutorials/cross_compilation_and_rpc.py b/tutorials/cross_compilation_and_rpc.py
index a3c01deb4518..a770a2758e01 100644
--- a/tutorials/cross_compilation_and_rpc.py
+++ b/tutorials/cross_compilation_and_rpc.py
@@ -3,113 +3,68 @@
 
 Cross Compilation and RPC
 =========================
-**Author**: `Ziheng Jiang <https://github.com/ZihengJiang/>`_
+**Author**: `Ziheng Jiang <https://github.com/ZihengJiang/>`_, `Lianmin Zheng <https://github.com/merrymercy/>`_
 
 This tutorial introduces cross compilation and remote device
 execution with RPC in TVM.
 
 With cross compilation and RPC, you can **compile program on your
-local machine then run it on remote device**. It is useful when the
-resource of remote device is limited, like Raspberry Pi and mobile
-platforms, so you do not wish to put the compilation procedure on
-the device in order to save time and space.
-In this tutorial, I will take Raspberry Pi as our target platform
-for example.
+local machine then run it on the remote device**. It is useful when
+the resource of remote devices is limited, like Raspberry Pi and mobile
+platforms. In this tutorial, we will take Raspberry Pi for CPU example
+and Firefly-RK3399 for opencl example.
 """
 
 ######################################################################
 # Build TVM Runtime on Device
 # ---------------------------
 #
-# There're some prerequisites: similar as compiling TVM on your
-# local machine, we need build runtime on remote device.
+# The first step is to build tvm runtime on the remote device.
 #
 # .. note::
 #
 #   All instructions in both this section and next section should be
 #   executed on the target device, e.g. Raspberry Pi. And we assume it
 #   has Linux running.
+# 
+# Since we do compilation on local machine, the remote device is only used
+# for running the generated code. We only need to build tvm runtime on
+# the remote device.
 #
-# To get started, clone tvm repo from github. It is important to clone
-# the submodules along, with --recursive option (Assuming you are in
-# your home directory):
+# .. code-block:: bash
 #
-#   .. code-block:: bash
-#
-#     git clone --recursive https://github.com/dmlc/tvm
-#
-# .. note::
-#
-#   Usually device has limited resources and we only need to build
-#   runtime. The idea is we will use TVM compiler on the local server
-#   to compile and upload the compiled program to the device and run
-#   the device function remotely.
-#
-#   .. code-block:: bash
+#   git clone --recursive https://github.com/dmlc/tvm
+#   cd tvm
+#   make runtime -j2
 #
-#     cd tvm
-#     cp make/config.mk .
-#     echo USE_RPC=1>> config.mk
+# After building runtime successfully, we need to set environment variables
+# in :code:`~/.bashrc` file. We can edit :code:`~/.bashrc`
+# using :code:`vi ~/.bashrc` and add the line below (Assuming your TVM
+# directory is in :code:`~/tvm`):
 #
-#   Also make sure that you have set :code:`USE_RPC=1` in your
-#   :code:`config.mk`. We don't need LLVM when building runtime, so
-#   :code:`LLVM_CONFIG = llvm-config` in :code:`config.mk` is commented
-#   out by default. After that, build runtime!
-#
-#   .. code-block:: bash
-#
-#     make runtime
-#
-# After building runtime successfully, we need to set environment varibles
-# in :code:`~/.bashrc` file of yourself account or :code:`/etc/profile`
-# of system enviroment variables. Assuming your TVM directory is in
-# :code:`~/tvm` and set environment variables below your account.
-#
-#   .. code-block:: bash
+# .. code-block:: bash
 #
-#    vi ~/.bashrc
+#   export PYTHONPATH=$PYTHONPATH:~/tvm/python
 #
-# We need to edit :code:`~/.bashrc` using :code:`vi ~/.bashrc` and add
-# lines below (Assuming your TVM directory is in :code:`~/tvm`):
-#
-#   .. code-block:: bash
-#
-#    export TVM_HOME=~/tvm
-#    export PATH=$PATH:$TVM_HOME/lib
-#    export PYTHONPATH=$PYTHONPATH:$TVM_HOME/python
-#
-# To enable updated :code:`~/.bashrc`, execute :code:`source ~/.bashrc`.
+# To update the environment variables, execute :code:`source ~/.bashrc`.
 
 ######################################################################
 # Set Up RPC Server on Device
 # ---------------------------
-# To set up a TVM RPC server on the Raspberry Pi (our remote device),
-# we have prepared a one-line script so you only need to run this
-# command after following the installation guide to install TVM on
-# your device:
+# To start an RPC server, run the following command on your remote device
+# (Which is Raspberry Pi in this example).
 #
 #   .. code-block:: bash
 #
 #     python -m tvm.exec.rpc_server --host 0.0.0.0 --port=9090
 #
-# After executing the command above, if you see these lines below, it means
-# the RPC server started successfully on your device.
+# If you see the line below, it means the RPC server started
+# successfully on your device.
 #
 #    .. code-block:: bash
 #
-#      Loading runtime library /home/YOURNAME/code/tvm/lib/libtvm_runtime.so... exec only
 #      INFO:root:RPCServer: bind to 0.0.0.0:9090
 #
-# In the following code block, we simply start an RPC server on the
-# same machine, for demonstration. This line can be omitted if we
-# started an remote server.
-#
-from __future__ import absolute_import, print_function
-
-import tvm
-import numpy as np
-from tvm import rpc
-from tvm.contrib import util
 
 ######################################################################
 # Declare and Cross Compile Kernel on Local Machine
@@ -117,36 +72,50 @@
 #
 # .. note::
 #
-#   Now we back to the local machine, which has a full TVM installed.
+#   Now we back to the local machine, which has a full TVM installed
+#   (with LLVM).
 #
-# Here we will declare a simple kernel with TVM on the local machine:
+# Here we will declare a simple kernel on the local machine:
+
+import numpy as np
+
+import tvm
+from tvm import rpc
+from tvm.contrib import util
 
 n = tvm.convert(1024)
 A = tvm.placeholder((n,), name='A')
-B = tvm.compute(A.shape, lambda *i: A(*i) + 1.0, name='B')
+B = tvm.compute((n,), lambda i: A[i] + 1.0, name='B')
 s = tvm.create_schedule(B.op)
 
 ######################################################################
-# Then we cross compile the kernel:
-#
+# Then we cross compile the kernel.
+# The target should be 'llvm -target=armv7l-linux-gnueabihf' for
+# Raspberry Pi 3B, but we use 'llvm' here to make this tutorial runnable
+# on our webpage building server. See the detailed note in the following block.
+
+local_demo = True
 
-# the target here should be 'llvm -target=armv7l-none-linux-gnueabihf',
-# and we use 'llvm' here to make example run locally, see the detailed
-# note in the following block
-f = tvm.build(s, [A, B], target='llvm', name='myadd')
-# save the lib at local temp folder
+if local_demo:
+    target = 'llvm'
+else:
+    target = 'llvm -target=armv7l-linux-gnueabihf'
+
+func = tvm.build(s, [A, B], target=target, name='add_one')
+# save the lib at a local temp folder
 temp = util.tempdir()
-path = temp.relpath('mylib.o')
-f.save(path)
+path = temp.relpath('lib.tar')
+func.export_library(path)
 
 ######################################################################
 # .. note::
 #
-#   the argument :code:`target` in :code:`build` should be replaced
-#   :code:`'llvm'` with the target triple of your device, which might be
-#   different for different device. For example, it is
-#   :code:`'llvm -target=armv7l-none-linux-gnueabihf'` for my Raspberry
-#   Pi. Here we use :code:`'llvm'` directly to make the tutorial runable.
+#   To run this tutorial with real remote device, change :code:`local_demo`
+#   to False and replace :code:`target` in :code:`build` with the true
+#   target triple of your device. The target triple which might be
+#   different for different devices. For example, it is
+#   :code:`'llvm -target=armv7l-linux-gnueabihf'` for Raspberry Pi 3B and
+#   :code:`'llvm -target=aarch64-linux-gnu'` for RK3399.
 #
 #   Usually, you can query the target by execute :code:`gcc -v` on your
 #   device, and look for the line starting with :code:`Target:`
@@ -155,8 +124,6 @@
 #   Besides :code:`-target`, you can also set other compilation options
 #   like:
 #
-#   * -mtriple=<target triple>
-#       Specify the target triple, same as '-target'.
 #   * -mcpu=<cpuname>
 #       Specify a specific chip in the current architecture to generate code for. By default this is inferred from the target triple and autodetected to the current architecture.
 #   * -mattr=a1,+a2,-a3,...
@@ -168,13 +135,6 @@
 #         llc -mtriple=<your device target triple> -mattr=help
 #
 #   These options are consistent with `llc <http://llvm.org/docs/CommandGuide/llc.html>`_.
-#   So for my board, to get the best performance, the complete compilation
-#   option would be:
-#
-#   .. code-block:: bash
-#
-#     llvm -mtriple=armv7l-none-linux-gnueabihf -mcpu=cortex-a53 -mattr=+neon
-#
 #   It is recommended to set target triple and feature set to contain specific
 #   feature available, so we can take full advantage of the features of the
 #   board.
@@ -184,43 +144,40 @@
 ######################################################################
 # Run CPU Kernel Remotely by RPC
 # ------------------------------
-# Here we will show you how to run the kernel on the remote device:
-#
-# .. note::
-#    In order to have this tutorial runs locally to build the nice HTML, we
-#    start a RPC server on the local machine. You can ignore it if you already
-#    started the server on the target device. And then change host IP properly.
+# We show how to run the generated cpu kernel on the remote device.
+# First we obtain an RPC session from remote device.
 
-# Can be ignored if you already started the RPC server
-server = rpc.Server(host='0.0.0.0', port=9090, use_popen=True)
-host = '0.0.0.0'  # Change to your target device IP
-port = 9090
-# connect the remote device
-remote = rpc.connect(host, port)
+if local_demo:
+    remote = rpc.LocalSession()
+else:
+    # The following is my environment, change this to the IP address of your target device
+    host = '10.77.1.162'
+    port = 9090
+    remote = rpc.connect(host, port)
 
 ######################################################################
-# Here we upload the lib to the remote device, then invoke a device local
-# compiler for shared lib and load it into device memory. now `f` is a
-# remote module object.
+# Upload the lib to the remote device, then invoke a device local
+# compiler to relink them. Now `func` is a remote module object.
+
 remote.upload(path)
-f = remote.load_module('mylib.o')
+func = remote.load_module('lib.tar')
 
-# create array on the remote device
-ctx = remote.cpu(0)
+# create arrays on the remote device
+ctx = remote.cpu()
 a = tvm.nd.array(np.random.uniform(size=1024).astype(A.dtype), ctx)
 b = tvm.nd.array(np.zeros(1024, dtype=A.dtype), ctx)
 # the function will run on the remote device
-f(a, b)
+func(a, b)
 np.testing.assert_equal(b.asnumpy(), a.asnumpy() + 1)
 
 ######################################################################
 # When you want to evaluate the performance of the kernel on the remote
-# device, it is important to avoid overhead of remote function calls.
+# device, it is important to avoid the overhead of network.
 # :code:`time_evaluator` will returns a remote function that runs the
 # function over number times, measures the cost per run on the remote
-# device and returns the measured cost.
-#
-time_f = f.time_evaluator(f.entry_name, ctx, number=10)
+# device and returns the measured cost. Network overhead is excluded.
+
+time_f = func.time_evaluator(func.entry_name, ctx, number=10)
 cost = time_f(a, b).mean
 print('%g secs/op' % cost)
 
@@ -228,69 +185,54 @@
 # Run OpenCL Kernel Remotely by RPC
 # ---------------------------------
 # As for remote OpenCL devices, the workflow is almost the same as above.
-# You can define the kernel, upload files, and run by RPC. The files
-# include host object, kernel source code and module meta file. We rely
-# on remote compiler to re-link them.
+# You can define the kernel, upload files, and run by RPC. 
 #
 # .. note::
 #
 #    Raspberry Pi does not support OpenCL, the following code is tested on
 #    Firefly-RK3399. You may follow this `tutorial <https://gist.github.com/mli/585aed2cec0b5178b1a510f9f236afa2>`_
-#    to setup the RK3399 OS and OpenCL driver.
+#    to setup the OS and OpenCL driver for RK3399.
+#
+#    Also we need to build the runtime with OpenCL enabled on rk3399 board. In the tvm
+#    root directory, execute
 #
-#    The target_host should be 'llvm -target=aarch64-linux-gnu'.
-#    But here we set 'llvm' to enable this tutorial to run locally.
+# .. code-block:: bash
 #
-#    Also we need to build the runtime with the flag `USE_OPENCL=1` to
-#    build the kernel (different from cpu, we need bind axis for OpenCL)
+#    cp cmake/config.cmake .
+#    sed -i "s/USE_OPENCL OFF/USE_OPENCL ON/" config.cmake
+#    make runtime -j4
 #
-# The following functions shows how we can deploy CL
-def deploy_cl():
+# The following function shows how we run OpenCL kernel remotely
+
+def run_opencl():
+    # NOTE: This is the setting for my rk3399 board. You need to modify
+    # them according to your environment.
+    target_host = "llvm -target=aarch64-linux-gnu"
+    opencl_device_host = '10.77.1.145'
+    opencl_device_port = 9090
+
+    # create scheule for the above "add one" compute decleration
     s = tvm.create_schedule(B.op)
     xo, xi = s[B].split(B.op.axis[0], factor=32)
     s[B].bind(xo, tvm.thread_axis("blockIdx.x"))
     s[B].bind(xi, tvm.thread_axis("threadIdx.x"))
-    f = tvm.build(s, [A, B], "opencl", target_host="llvm", name="myadd")
-
-    # save files
-    path_o = temp.relpath("myadd.o")
-    path_cl = temp.relpath("myadd.cl")
-    path_json = temp.relpath("myadd.tvm_meta.json")
-    f.save(path_o)
-    f.imported_modules[0].save(path_cl)
+    func = tvm.build(s, [A, B], "opencl", target_host=target_host)
 
-    # upload files
-    remote.upload(path_o)
-    remote.upload(path_cl)
-    remote.upload(path_json)
+    remote = rpc.connect(opencl_device_host, opencl_device_port)
 
-    # load files on remote device
-    fhost = remote.load_module("myadd.o")
-    fdev = remote.load_module("myadd.cl")
-    fhost.import_module(fdev)
+    # export and upload
+    path = temp.relpath('lib_cl.tar')
+    func.export_library(path)
+    remote.upload(path)
+    func = remote.load_module('lib_cl.tar')
 
     # run
-    ctx = remote.cl(0)
+    ctx = remote.cl()
     a = tvm.nd.array(np.random.uniform(size=1024).astype(A.dtype), ctx)
     b = tvm.nd.array(np.zeros(1024, dtype=A.dtype), ctx)
-    fhost(a, b)
-    np.testing.assert_equal(b.asnumpy(), a.asnumpy() + 1)
-
-
-#####################################################################
-# Instead of uploading files separately, there is a more convinient way.
-# You can export libraray as a tar ball.
-# The following functions shows how we can deploy by tar ball
-def deploy_cl_by_tar():
-    path_tar = temp.relpath("myadd.tar")
-    f.export_library(path_tar)
-    remote.upload(path_tar)
-    fhost = remote.load_module("myadd.tar")
-    fhost(a, b)
+    func(a, b)
     np.testing.assert_equal(b.asnumpy(), a.asnumpy() + 1)
-
-# terminate the server after experiment
-server.terminate()
+    print("OpenCP test passed!")
 
 ######################################################################
 # Summary
diff --git a/tutorials/nnvm/README.txt b/tutorials/nnvm/README.txt
index d6629821c2ef..772953ce96ac 100644
--- a/tutorials/nnvm/README.txt
+++ b/tutorials/nnvm/README.txt
@@ -1,2 +1,4 @@
+.. _tutorial-nnvm:
+
 Compile Deep Learning Models
 ----------------------------
diff --git a/tutorials/nnvm/deploy_model_on_mali_gpu.py b/tutorials/nnvm/deploy_model_on_mali_gpu.py
index 51caf8dcbd26..8aacb8433d3d 100644
--- a/tutorials/nnvm/deploy_model_on_mali_gpu.py
+++ b/tutorials/nnvm/deploy_model_on_mali_gpu.py
@@ -6,14 +6,10 @@
 **Author**: `Lianmin Zheng <https://lmzheng.net/>`_, `Ziheng Jiang <https://ziheng.org/>`_
 
 This is an example of using NNVM to compile a ResNet model and
-deploy it on Firefly-RK3399 with ARM Mali GPU.  We will use the
+deploy it on Firefly-RK3399 with ARM Mali GPU. We will use the
 Mali-T860 MP4 GPU on this board to accelerate the inference.
-
-This tutorial is based on the tutorial for deploying on Raspberry Pi by `Ziheng Jiang <https://ziheng.org/>`_.
-Great thanks to the original author, I only do several lines of modification.
-
-To begin with, we import nnvm (for compilation) and TVM (for deployment).
 """
+
 import tvm
 import nnvm.compiler
 import nnvm.testing
@@ -24,92 +20,65 @@
 # Build TVM Runtime on Device
 # ---------------------------
 #
-# There're some prerequisites: we need build tvm runtime and set up
-# a RPC server on remote device.
-#
-# To get started, clone tvm repo from github. It is important to clone
-# the submodules along, with --recursive option (Assuming you are in
-# your home directory):
-#
-#   .. code-block:: bash
-#
-#     git clone --recursive https://github.com/dmlc/tvm
+# The first step is to build tvm runtime on the remote device.
 #
 # .. note::
 #
-#   Usually device has limited resources and we only need to build
-#   runtime. The idea is we will use TVM compiler on the local server
-#   to compile and upload the compiled program to the device and run
-#   the device function remotely.
+#   All instructions in both this section and next section should be
+#   executed on the target device, e.g. Rk3399. And we assume it
+#   has Linux running.
+# 
+# Since we do compilation on local machine, the remote device is only used
+# for running the generated code. We only need to build tvm runtime on
+# the remote device. Make sure you have opencl driver in your board.
+# You can refer to `tutorial <https://gist.github.com/mli/585aed2cec0b5178b1a510f9f236afa2>`_
+# to setup OS and opencl driver for rk3399.
 #
-#   .. code-block:: bash
-#
-#     make runtime
-#
-# After success of buildind runtime, we need set environment varibles
-# in :code:`~/.bashrc` file of yourself account or :code:`/etc/profile`
-# of system enviroment variables. Assuming your TVM directory is in
-# :code:`~/tvm` and set environment variables below your account.
+# .. code-block:: bash
 #
-#   .. code-block:: bash
-#
-#    vi ~/.bashrc
+#   git clone --recursive https://github.com/dmlc/tvm
+#   cd tvm
+#   cp cmake/config.cmake .
+#   sed -i "s/USE_OPENCL OFF/USE_OPENCL ON/" config.cmake 
+#   make runtime -j4
 #
-# We need edit :code:`~/.bashrc` using :code:`vi ~/.bashrc` and add
-# lines below (Assuming your TVM directory is in :code:`~/tvm`):
+# After building runtime successfully, we need to set environment varibles
+# in :code:`~/.bashrc` file. We can edit :code:`~/.bashrc`
+# using :code:`vi ~/.bashrc` and add the line below (Assuming your TVM 
+# directory is in :code:`~/tvm`):
 #
-#   .. code-block:: bash
+# .. code-block:: bash
 #
-#    export TVM_HOME=~/tvm
-#    export PATH=$PATH:$TVM_HOME/lib
-#    export PYTHONPATH=$PYTHONPATH:$TVM_HOME/python
+#   export PYTHONPATH=$PYTHONPATH:~/tvm/python
 #
-# To enable updated :code:`~/.bashrc`, execute :code:`source ~/.bashrc`.
+# To update the environment variables, execute :code:`source ~/.bashrc`.
 
 ######################################################################
 # Set Up RPC Server on Device
 # ---------------------------
-# To set up a TVM RPC server on the your ARM device (our remote device),
-# we have prepared a one-line script so you only need to run this
-# command after following the installation guide to install TVM on
-# your device:
+# To start an RPC server, run the following command on your remote device
+# (Which is RK3399 in our example).
 #
 #   .. code-block:: bash
 #
 #     python -m tvm.exec.rpc_server --host 0.0.0.0 --port=9090
 #
-# After executing command above, if you see these lines below, it's
-# successful to start RPC server on your device.
+# If you see the line below, it means the RPC server started
+# successfully on your device.
 #
 #    .. code-block:: bash
 #
-#      Loading runtime library /home/YOURNAME/code/tvm/lib/libtvm_runtime.so... exec only
 #      INFO:root:RPCServer: bind to 0.0.0.0:9090
 #
 
 ######################################################################
-# For demonstration, we simply start an RPC server on the same machine,
-# if :code:`use_mali` is False. If you have set up the remote
-# environment, please change the three lines below: change the
-# :code:`use_mali` to True, also change the :code:`host` and :code:`port`
-# with your device's host address and port number.
-
-use_mali = False
-host = '10.42.0.96'
-port = 9090
-
-if not use_mali:
-    # run server locally
-    host = 'localhost'
-    port = 9095
-    server = rpc.Server(host=host, port=port, use_popen=True)
-
-######################################################################
-# Prepare the Pretrained Model
-# ----------------------------
-# Back to the host machine, firstly, we need to download a MXNet Gluon
-# ResNet model from model zoo, which is pretrained on ImageNet. You
-# can found more details about this part at `Compile MXNet Models`
+# Prepare the Pre-trained Model
+# -----------------------------
+# Back to the host machine, which should have a full TVM installed (with LLVM).
+# 
+# We will use pre-trained model from
+# `MXNet Gluon model zoo <https://mxnet.incubator.apache.org/api/python/gluon/model_zoo.html>`_.
+# You can found more details about this part at tutorial :ref:`tutorial-from-mxnet`.
 
 from mxnet.gluon.model_zoo.vision import get_model
 from mxnet.gluon.utils import download
@@ -135,7 +104,6 @@ def transform_image(image):
 
 x = transform_image(image)
 
-
 ######################################################################
 # synset is used to transform the label from number of ImageNet class to
 # the word human can understand.
@@ -143,6 +111,7 @@ def transform_image(image):
                       '4d0b62f3d01426887599d4f7ede23ee5/raw/',
                       '596b27d23537e5a1b5751d2b0481ef172f58b539/',
                       'imagenet1000_clsid_to_human.txt'])
+
 synset_name = 'synset.txt'
 download(synset_url, synset_name)
 with open(synset_name) as f:
@@ -176,21 +145,29 @@ def transform_image(image):
 # triplet for host ARM device by setting the parameter :code:`target_host`.
 
 ######################################################################
-# If we run the example locally for demonstration, we can simply set
-# it as :code:`llvm`. If to run it on the ARM device, you need to specify
-# its instruction set. Here is the option I use for my Firefly-RK3399.
+# If we run the example on our x86 server for demonstration, we can simply
+# set it as :code:`llvm`. If running it on the RK3399, we need to
+# specify its instruction set. Set :code:`local_demo` to False if you
+# want to run this tutorial with a real device.
 
-if use_mali:
-    target_host = "llvm -target=aarch64-linux-gnu -mattr=+neon"
-    target = tvm.target.mali()
-else:
+local_demo = True
+
+if local_demo:
     target_host = "llvm"
-    target = tvm.target.cuda()
+    target = "llvm"
+else:
+    # Here is the setting for my rk3399 board
+    # If you don't use rk3399, you can query your target triple by 
+    # execute `gcc -v` on your board.
+    target_host = "llvm -target=aarch64-linux-gnu"
+
+    # set target as  `tvm.target.mali` instead of 'opencl' to enable
+    # optimization for mali
+    target = tvm.target.mali()
 
-# set target as  `tvm.target.mali` instead of 'opencl' to enable
-# target-specified optimization
-graph, lib, params = nnvm.compiler.build(net, target=target,
-        shape={"data": data_shape}, params=params, target_host=target_host)
+with nnvm.compiler.build_config(opt_level=2):
+    graph, lib, params = nnvm.compiler.build(net, target=target,
+            shape={"data": data_shape}, params=params, target_host=target_host)
 
 # After `nnvm.compiler.build`, you will get three return values: graph,
 # library and the new parameter, since we do some optimization that will
@@ -207,14 +184,20 @@ def transform_image(image):
 # With RPC, you can deploy the model remotely from your host machine
 # to the remote device.
 
-# connect the server
-remote = rpc.connect(host, port)
+# obtain an RPC session from remote device.
+if local_demo:
+    remote = rpc.LocalSession()
+else:
+    # The following is my environment, change this to the IP address of your target device
+    host = '10.77.1.145'
+    port = 9090
+    remote = rpc.connect(host, port)
 
 # upload the library to remote device and load it
 remote.upload(lib_fname)
 rlib = remote.load_module('net.tar')
 
-ctx = remote.cl(0) if use_mali else remote.gpu(0)
+ctx = remote.cpu(0) if local_demo else remote.cl(0)
 # upload the parameter
 rparams = {k: tvm.nd.array(v, ctx) for k, v in params.items()}
 
@@ -231,7 +214,3 @@ def transform_image(image):
 # get top1 result
 top1 = np.argmax(out.asnumpy())
 print('TVM prediction top-1: {}'.format(synset[top1]))
-
-if not use_mali:
-    # terminate the local server
-    server.terminate()
diff --git a/tutorials/nnvm/deploy_model_on_rasp.py b/tutorials/nnvm/deploy_model_on_rasp.py
index 37354e7a3363..c11f202c1251 100644
--- a/tutorials/nnvm/deploy_model_on_rasp.py
+++ b/tutorials/nnvm/deploy_model_on_rasp.py
@@ -7,9 +7,8 @@
 
 This is an example of using NNVM to compile a ResNet model and deploy
 it on raspberry pi.
-
-To begin with, we import nnvm(for compilation) and TVM(for deployment).
 """
+
 import tvm
 import nnvm.compiler
 import nnvm.testing
@@ -17,102 +16,73 @@
 from tvm.contrib import util, graph_runtime as runtime
 
 ######################################################################
+# .. _build-tvm-runtime-on-device:
+#
 # Build TVM Runtime on Device
 # ---------------------------
 #
-# There're some prerequisites: we need build tvm runtime and set up
-# a RPC server on remote device.
-#
-# To get started, clone tvm repo from github. It is important to clone
-# the submodules along, with --recursive option (Assuming you are in
-# your home directory):
-#
-#   .. code-block:: bash
-#
-#     git clone --recursive https://github.com/dmlc/tvm
+# The first step is to build tvm runtime on the remote device.
 #
 # .. note::
 #
-#   Usually device has limited resources and we only need to build
-#   runtime. The idea is we will use TVM compiler on the local server
-#   to compile and upload the compiled program to the device and run
-#   the device function remotely.
+#   All instructions in both this section and next section should be
+#   executed on the target device, e.g. Raspberry Pi. And we assume it
+#   has Linux running.
+# 
+# Since we do compilation on local machine, the remote device is only used
+# for running the generated code. We only need to build tvm runtime on
+# the remote device.
 #
-#   .. code-block:: bash
-#
-#     make runtime
-#
-# After success of buildind runtime, we need set environment varibles
-# in :code:`~/.bashrc` file of yourself account or :code:`/etc/profile`
-# of system enviroment variables. Assuming your TVM directory is in
-# :code:`~/tvm` and set environment variables below your account.
+# .. code-block:: bash
 #
-#   .. code-block:: bash
-#
-#    vi ~/.bashrc
+#   git clone --recursive https://github.com/dmlc/tvm
+#   cd tvm
+#   make runtime -j4
 #
-# We need edit :code:`~/.bashrc` using :code:`vi ~/.bashrc` and add
-# lines below (Assuming your TVM directory is in :code:`~/tvm`):
+# After building runtime successfully, we need to set environment varibles
+# in :code:`~/.bashrc` file. We can edit :code:`~/.bashrc`
+# using :code:`vi ~/.bashrc` and add the line below (Assuming your TVM 
+# directory is in :code:`~/tvm`):
 #
-#   .. code-block:: bash
+# .. code-block:: bash
 #
-#    export TVM_HOME=~/tvm
-#    export PATH=$PATH:$TVM_HOME/lib
-#    export PYTHONPATH=$PYTHONPATH:$TVM_HOME/python
+#   export PYTHONPATH=$PYTHONPATH:~/tvm/python
 #
-# To enable updated :code:`~/.bashrc`, execute :code:`source ~/.bashrc`.
+# To update the environment variables, execute :code:`source ~/.bashrc`.
 
 ######################################################################
 # Set Up RPC Server on Device
 # ---------------------------
-# To set up a TVM RPC server on the Raspberry Pi (our remote device),
-# we have prepared a one-line script so you only need to run this
-# command after following the installation guide to install TVM on
-# your device:
+# To start an RPC server, run the following command on your remote device
+# (Which is Raspberry Pi in our example).
 #
 #   .. code-block:: bash
 #
 #     python -m tvm.exec.rpc_server --host 0.0.0.0 --port=9090
 #
-# After executing command above, if you see these lines below, it's
-# successful to start RPC server on your device.
+# If you see the line below, it means the RPC server started
+# successfully on your device.
 #
 #    .. code-block:: bash
 #
-#      Loading runtime library /home/YOURNAME/code/tvm/lib/libtvm_runtime.so... exec only
 #      INFO:root:RPCServer: bind to 0.0.0.0:9090
-
-
-######################################################################
-# For demonstration, we simply start an RPC server on the same machine,
-# if :code:`use_rasp` is False. If you have set up the remote
-# environment, please change the three lines below: change the
-# :code:`use_rasp` to True, also change the :code:`host` and :code:`port`
-# with your device's host address and port number.
-
-use_rasp = False
-host = 'rasp0'
-port = 9090
-
-if not use_rasp:
-    # run server locally
-    host = 'localhost'
-    port = 9091
-    server = rpc.Server(host=host, port=port, use_popen=True)
+#
 
 ######################################################################
-# Prepare the Pretrained Model
-# ----------------------------
-# Back to the host machine, firstly, we need to download a MXNet Gluon
-# ResNet model from model zoo, which is pretrained on ImageNet. You
-# can found more details about this part at `Compile MXNet Models`
+# Prepare the Pre-trained Model
+# -----------------------------
+# Back to the host machine, which should have a full TVM installed (with LLVM).
+# 
+# We will use pre-trained model from
+# `MXNet Gluon model zoo <https://mxnet.incubator.apache.org/api/python/gluon/model_zoo.html>`_.
+# You can found more details about this part at tutorial :ref:`tutorial-from-mxnet`.
 
 from mxnet.gluon.model_zoo.vision import get_model
 from mxnet.gluon.utils import download
 from PIL import Image
 import numpy as np
 
-# only one line to get the model
+# one line to get the model
 block = get_model('resnet18_v1', pretrained=True)
 
 ######################################################################
@@ -131,7 +101,6 @@ def transform_image(image):
 
 x = transform_image(image)
 
-
 ######################################################################
 # synset is used to transform the label from number of ImageNet class to
 # the word human can understand.
@@ -173,29 +142,32 @@ def transform_image(image):
 # will lead to very different performance.
 
 ######################################################################
-# If we run the example locally for demonstration, we can simply set
-# it as :code:`llvm`. If to run it on the Raspberry Pi, you need to
-# specify its instruction set. Here is the option I use for my Raspberry
-# Pi, which has been proved as a good compilation configuration.
+# If we run the example on our x86 server for demonstration, we can simply
+# set it as :code:`llvm`. If running it on the Raspberry Pi, we need to
+# specify its instruction set. Set :code:`local_demo` to False if you want
+# to run this tutorial with a real device.
 
-if use_rasp:
-    target = tvm.target.rasp()
-else:
+local_demo = True
+
+if local_demo:
     target = tvm.target.create('llvm')
+else:
+    target = tvm.target.arm_cpu('rasp3b')
+    # The above line is a simple form of
+    # target = tvm.target.create('llvm -devcie=arm_cpu -target=armv7l-linux-gnueabihf')
 
-graph, lib, params = nnvm.compiler.build(
-    net, target, shape={"data": data_shape}, params=params)
+with nnvm.compiler.build_config(opt_level=2, add_pass=['AlterOpLayout']):
+    graph, lib, params = nnvm.compiler.build(
+        net, target, shape={"data": data_shape}, params=params)
 
 # After `nnvm.compiler.build`, you will get three return values: graph,
 # library and the new parameter, since we do some optimization that will
 # change the parameters but keep the result of model as the same.
 
-
 # Save the library at local temporary directory.
 tmp = util.tempdir()
-lib_fname = tmp.relpath('net.o')
-lib.save(lib_fname)
-
+lib_fname = tmp.relpath('net.tar')
+lib.export_library(lib_fname)
 
 ######################################################################
 # Deploy the Model Remotely by RPC
@@ -203,15 +175,21 @@ def transform_image(image):
 # With RPC, you can deploy the model remotely from your host machine
 # to the remote device.
 
-# connect the server
-remote = rpc.connect(host, port)
+# obtain an RPC session from remote device.
+if local_demo:
+    remote = rpc.LocalSession()
+else:
+    # The following is my environment, change this to the IP address of your target device
+    host = '10.77.1.162'
+    port = 9090
+    remote = rpc.connect(host, port)
 
 # upload the library to remote device and load it
 remote.upload(lib_fname)
-rlib = remote.load_module('net.o')
+rlib = remote.load_module('net.tar')
 
+# upload the parameter (this may take a while)
 ctx = remote.cpu(0)
-# upload the parameter
 rparams = {k: tvm.nd.array(v, ctx) for k, v in params.items()}
 
 # create the remote runtime module
@@ -227,7 +205,3 @@ def transform_image(image):
 # get top1 result
 top1 = np.argmax(out.asnumpy())
 print('TVM prediction top-1: {}'.format(synset[top1]))
-
-if not use_rasp:
-    # terminate the local server
-    server.terminate()
diff --git a/tutorials/nnvm/from_mxnet.py b/tutorials/nnvm/from_mxnet.py
index 5ea6acbd3a9b..cce3bc37126a 100644
--- a/tutorials/nnvm/from_mxnet.py
+++ b/tutorials/nnvm/from_mxnet.py
@@ -1,4 +1,6 @@
 """
+.. _tutorial-from-mxnet:
+
 Compile MXNet Models
 ====================
 **Author**: `Joshua Z. Zhang <https://zhreshold.github.io/>`_
diff --git a/tutorials/nnvm/imagenet_inference_gpu.py b/tutorials/nnvm/imagenet_inference_gpu.py
deleted file mode 100644
index 9179dedfb6fc..000000000000
--- a/tutorials/nnvm/imagenet_inference_gpu.py
+++ /dev/null
@@ -1,89 +0,0 @@
-"""
-Compile GPU Inference
-=====================
-**Author**: `Yuwei Hu <https://huyuwei.github.io/>`_
-
-This is an example of using NNVM to compile MobileNet/ResNet model and deploy its inference on GPU.
-
-To begin with, we import nnvm(for compilation) and TVM(for deployment).
-"""
-import tvm
-import numpy as np
-from tvm.contrib import nvcc, graph_runtime
-import nnvm.compiler
-import nnvm.testing
-
-######################################################################
-# Register the NVCC Compiler Option
-# ---------------------------------
-# NNVM optimizes the graph and relies on TVM to generate fast GPU code.
-# To get the maximum performance, we need to enable nvcc's compiler hook.
-# This usually gives better performance than nvrtc mode.
-
-@tvm.register_func("tvm_callback_cuda_compile", override=True)
-def tvm_callback_cuda_compile(code):
-    ptx = nvcc.compile_cuda(code, target="ptx")
-    return ptx
-
-######################################################################
-# Prepare the Benchmark
-# ---------------------
-# We construct a standard imagenet inference benchmark.
-# NNVM needs two things to compile a deep learning model:
-#
-# - net: the graph representation of the computation
-# - params: a dictionary of str to parameters
-#
-# We use nnvm's testing utility to produce the model description and random parameters
-# so that the example does not depend on a specific front-end framework.
-#
-# .. note::
-#
-#   In a typical workflow, we can get this pair from :any:`nnvm.frontend`
-#
-target = "cuda"
-ctx = tvm.gpu(0)
-batch_size = 1
-num_classes = 1000
-image_shape = (3, 224, 224)
-data_shape = (batch_size,) + image_shape
-out_shape = (batch_size, num_classes)
-# To use ResNet to do inference, run the following instead
-#net, params = nnvm.testing.resnet.get_workload(
-#    batch_size=1, image_shape=image_shape)
-net, params = nnvm.testing.mobilenet.get_workload(
-    batch_size=1, image_shape=image_shape)
-
-######################################################################
-# Compile the Graph
-# -----------------
-# To compile the graph, we call the build function with the graph
-# configuration and parameters.
-# When parameters are provided, NNVM will pre-compute certain part of the graph if possible (e.g. simplify batch normalization to scale shift),
-# and return the updated parameters.
-
-graph, lib, params = nnvm.compiler.build(
-    net, target, shape={"data": data_shape}, params=params)
-
-
-######################################################################
-# Run the Compiled Module
-# -----------------------
-#
-# To deploy the module, we call :any:`tvm.contrib.graph_runtime.create` passing in the graph, the lib, and context.
-# Thanks to TVM, we can deploy the compiled module to many platforms and languages.
-# The deployment module is designed to contain minimum dependencies.
-# This example runs on the same machine.
-#
-# Note that the code below no longer depends on NNVM, and only relies TVM's runtime to run(deploy).
-data = np.random.uniform(-1, 1, size=data_shape).astype("float32")
-module = graph_runtime.create(graph, lib, ctx)
-# set input
-module.set_input(**params)
-module.set_input("data", data)
-# run
-module.run()
-# get output
-out = module.get_output(0, tvm.nd.empty(out_shape))
-# convert to numpy
-out.asnumpy()
diff --git a/tutorials/nnvm_quick_start.py b/tutorials/nnvm_quick_start.py
index 563d71b5e179..c9f6c33591d0 100644
--- a/tutorials/nnvm_quick_start.py
+++ b/tutorials/nnvm_quick_start.py
@@ -6,9 +6,8 @@
 **Author**: `Yao Wang <https://github.com/kevinthesun>`_
 
 This example shows how to build a neural network with NNVM python frontend and
-generate runtime library for Nvidia GPU and Raspberry Pi with TVM.
-To run this notebook, you need to install tvm and nnvm.
-Notice that you need to build tvm with cuda and llvm.
+generate runtime library for Nvidia GPU with TVM.
+Notice that you need to build TVM with cuda and llvm enabled.
 """
 
 ######################################################################
@@ -22,10 +21,13 @@
 #
 # In this tutorial, we'll choose cuda and llvm as target backends.
 # To begin with, let's import NNVM and TVM.
-import tvm
+
+import numpy as np
+
 import nnvm.compiler
 import nnvm.testing
-
+import tvm
+from tvm.contrib import graph_runtime
 
 ######################################################################
 # Define Neural Network in NNVM
@@ -33,7 +35,8 @@
 # First, let's define a neural network with nnvm python frontend.
 # For simplicity, we'll use pre-defined resnet-18 network in NNVM.
 # Parameters are initialized with Xavier initializer.
-# NNVM also supports other model formats such as MXNet, CoreML and ONNX.
+# NNVM also supports other model formats such as MXNet, CoreML, ONNX and 
+# Tensorflow.
 #
 # In this tutorial, we assume we will do inference on our device
 # and the batch size is set to be 1. Input images are RGB color
@@ -46,7 +49,8 @@
 data_shape = (batch_size,) + image_shape
 out_shape = (batch_size, num_class)
 
-net, params = nnvm.testing.resnet.get_workload(batch_size=batch_size, image_shape=image_shape)
+net, params = nnvm.testing.resnet.get_workload(layers=18,
+        batch_size=batch_size, image_shape=image_shape)
 print(net.debug_str())
 
 ######################################################################
@@ -54,10 +58,8 @@
 # -----------
 # Next step is to compile the model using the NNVM/TVM pipeline.
 # Users can specify the optimization level of the compilation.
-# Currently this value can be 0 to 2, which corresponds to
-# "SimplifyInference", "OpFusion" and "PrecomputePrune" respectively.
-# In this example we set optimization level to be 0
-# and use Raspberry Pi as compile target.
+# Currently this value can be 0 to 3. The optimization passes include
+# operator fusion, pre-computation, layout transformation and so on.
 #
 # :any:`nnvm.compiler.build` returns three components: the execution graph in
 # json format, the TVM module library of compiled functions specifically
@@ -68,24 +70,50 @@
 #
 # We'll first compile for Nvidia GPU. Behind the scene, `nnvm.compiler.build`
 # first does a number of graph-level optimizations, e.g. pruning, fusing, etc.,
-# then registers the operators (i.e. the nodes of the optmized graphs) to
+# then registers the operators (i.e. the nodes of the optimized graphs) to
 # TVM implementations to generate a `tvm.module`.
-# To generate the module library, TVM will first transfer the HLO IR into the lower
-# intrinsic IR of the specified target backend, which is CUDA in this example.
-# Then the machine code will be generated as the module library.
+# To generate the module library, TVM will first transfer the High level IR
+# into the lower intrinsic IR of the specified target backend, which is CUDA
+# in this example. Then the machine code will be generated as the module library.
 
-opt_level = 0
+opt_level = 3
 target = tvm.target.cuda()
 with nnvm.compiler.build_config(opt_level=opt_level):
     graph, lib, params = nnvm.compiler.build(
         net, target, shape={"data": data_shape}, params=params)
 
+#####################################################################
+# Run the generate library
+# ------------------------
+# Now we can create graph runtime and run the module on Nvidia GPU.
+
+# create random input
+ctx = tvm.gpu()
+data = np.random.uniform(-1, 1, size=data_shape).astype("float32")
+# create module
+module = graph_runtime.create(graph, lib, ctx)
+# set input and parameters
+module.set_input("data", data)
+module.set_input(**params)
+# run
+module.run()
+# get output
+out = module.get_output(0, tvm.nd.empty(out_shape))
+# convert to numpy
+out.asnumpy()
+
+# Print first 10 elements of output
+print(out.asnumpy().flatten()[0:10])
+
 ######################################################################
-# Save Compiled Module
-# ----------------------------
-# After compilation, we can save the graph, lib and params into separate files
-# and deploy them to Nvidia GPU.
+# Save and Load Compiled Module
+# -----------------------------
+# We can also save the graph, lib and parameters into files and load them
+# back in development environment.
 
+####################################################
+
+# save the graph, lib and params into separate files
 from tvm.contrib import util
 
 temp = util.tempdir()
@@ -97,95 +125,17 @@
     fo.write(nnvm.compiler.save_param_dict(params))
 print(temp.listdir())
 
-######################################################################
-# Deploy locally to Nvidia GPU
-# ------------------------------
-# Now we can load the module back.
+####################################################
 
-import numpy as np
-from tvm.contrib import graph_runtime
-
-loaded_lib = tvm.module.load(path_lib)
+# load the module back.
 loaded_json = open(temp.relpath("deploy_graph.json")).read()
+loaded_lib = tvm.module.load(path_lib)
 loaded_params = bytearray(open(temp.relpath("deploy_param.params"), "rb").read())
+input_data = tvm.nd.array(np.random.uniform(size=data_shape).astype("float32"))
+
 module = graph_runtime.create(loaded_json, loaded_lib, tvm.gpu(0))
 module.load_params(loaded_params)
-
-input_data = tvm.nd.array(np.random.uniform(size=data_shape).astype("float32"))
 module.run(data=input_data)
-out = module.get_output(0, out=tvm.nd.empty(out_shape))
-# Print first 10 elements of output
-print(out.asnumpy()[0][0:10])
-
-######################################################################
-# Compile and Deploy the Model to Raspberry Pi Remotely with RPC
-# --------------------------------------------------------------
-# Following the steps above, we can also compile the model for Raspberry Pi.
-# TVM provides rpc module to help with remote deploying.
-#
-# For demonstration, we simply start an RPC server on the same machine,
-# if :code:`use_rasp` is False. If you have set up the remote
-# environment, please change the three lines below: change the
-# :code:`use_rasp` to True, also change the host and port with your
-# device's host address and port number.
-
-# If we run the example locally for demonstration, we can simply set the
-# compilation target as `llvm`.
-# To run it on the Raspberry Pi, you need to specify its instruction set.
-# `llvm -target=armv7l-none-linux-gnueabihf -mcpu=cortex-a53 -mattr=+neon`
-# is the recommended compilation configuration, thanks to Ziheng's work.
-
-from tvm import rpc
-
-use_rasp = False
-host = 'rasp0'
-port = 9090
-
-if not use_rasp:
-    # run server locally
-    host = 'localhost'
-    port = 9099
-    server = rpc.Server(host=host, port=port, use_popen=True)
-
-# compile and save model library
-if use_rasp:
-    target = "llvm -target=armv7l-none-linux-gnueabihf -mcpu=cortex-a53 -mattr=+neon"
-else:
-    target = "llvm"
-# use `with tvm.target.rasp` for some target-specified optimization
-with tvm.target.rasp():
-    graph, lib, params = nnvm.compiler.build(
-        net, target, shape={"data": data_shape}, params=params)
 
-temp = util.tempdir()
-path_lib = temp.relpath("deploy_lib_rasp.o")
-lib.save(path_lib)
-
-# connect the server
-remote = rpc.connect(host, port)
-
-# upload the library to remote device and load it
-remote.upload(path_lib)
-rlib = remote.load_module('deploy_lib_rasp.o')
-
-ctx = remote.cpu(0)
-# upload the parameter
-rparams = {k: tvm.nd.array(v, ctx) for k, v in params.items()}
-
-# create the remote runtime module
-module = graph_runtime.create(graph, rlib, ctx)
-# set parameter
-module.set_input(**rparams)
-# set input data
-input_data = np.random.uniform(size=data_shape)
-module.set_input('data', tvm.nd.array(input_data.astype('float32')))
-# run
-module.run()
-
-out = module.get_output(0, out=tvm.nd.empty(out_shape, ctx=ctx))
-# Print first 10 elements of output
-print(out.asnumpy()[0][0:10])
+out = module.get_output(0, out=tvm.nd.empty(out_shape))
 
-if not use_rasp:
-    # terminate the local server
-    server.terminate()
diff --git a/vta/python/vta/top/arm_conv2d.py b/vta/python/vta/top/arm_conv2d.py
index 79abbe9e3b21..634348a87cfe 100644
--- a/vta/python/vta/top/arm_conv2d.py
+++ b/vta/python/vta/top/arm_conv2d.py
@@ -1,57 +1,21 @@
-# pylint: disable=invalid-name,unused-variable,invalid-name
-"""Conv2D schedule ported from RASP
+"""Reuse conv2d schedule from ARM CPU"""
 
-Used for CPU conv2d
-"""
-from __future__ import absolute_import as _abs
+import tvm
 
-from topi.nn.conv2d import conv2d, _get_schedule
-from topi.nn.conv2d import SpatialPack, Im2ColPack, Workload
-from topi.rasp import conv2d as _rasp_conv2d
+from topi.nn import conv2d, conv2d_alter_layout
 from topi import generic
 
-_WORKLOADS = [
-    Workload('float32', 'float32', 224, 224, 3, 64, 7, 7, 3, 3, 2, 2),
-    Workload('int8', 'int32', 224, 224, 3, 64, 7, 7, 3, 3, 2, 2),
-    Workload('int8', 'int32', 56, 56, 64, 64, 3, 3, 1, 1, 1, 1),
-    Workload('int8', 'int32', 56, 56, 64, 64, 1, 1, 0, 0, 1, 1),
-    Workload('int8', 'int32', 56, 56, 64, 128, 3, 3, 1, 1, 2, 2),
-    Workload('int8', 'int32', 56, 56, 64, 128, 1, 1, 0, 0, 2, 2),
-    Workload('int8', 'int32', 28, 28, 128, 128, 3, 3, 1, 1, 1, 1),
-    Workload('int8', 'int32', 28, 28, 128, 256, 3, 3, 1, 1, 2, 2),
-    Workload('int8', 'int32', 28, 28, 128, 256, 1, 1, 0, 0, 2, 2),
-    Workload('int8', 'int32', 14, 14, 256, 256, 3, 3, 1, 1, 1, 1),
-    Workload('int8', 'int32', 14, 14, 256, 512, 3, 3, 1, 1, 2, 2),
-    Workload('int8', 'int32', 14, 14, 256, 512, 1, 1, 0, 0, 2, 2),
-    Workload('int8', 'int32', 7, 7, 512, 512, 3, 3, 1, 1, 1, 1),
-]
-_SCHEDULES = [
-    # float32 imagenet
-    SpatialPack(1, 8, 4, 1, 4, True),
-    SpatialPack(1, 8, 4, 1, 4, True),
-    SpatialPack(1, 7, 4, 2, 4, True),
-    SpatialPack(1, 4, 8, 4, 1, True),
-    SpatialPack(1, 4, 4, 1, 16, False),
-    SpatialPack(1, 4, 8, 4, 8, False),
-    SpatialPack(1, 7, 4, 3, 8, True),
-    SpatialPack(1, 2, 8, 1, 8, True),
-    SpatialPack(2, 1, 16, 1, 4, True),
-    SpatialPack(1, 7, 4, 1, 1, True),
-    Im2ColPack(7, 4, 1, 16, True),
-    Im2ColPack(7, 4, 1, 8, False),
-    Im2ColPack(7, 4, 1, 16, False),
-]
+@conv2d.register(["vtacpu", "vta"])
+def compute(*args, **kwargs):
+    with tvm.target.arm_cpu("vtacpu"):
+        return conv2d(*args, **kwargs)
 
-@_get_schedule.register(["vtacpu", "vta"])
-def _schedule_conv2d(wkl):
-    if wkl not in _WORKLOADS:
-        raise ValueError("no schedule for such workload: {}".format(wkl))
-    idx = _WORKLOADS.index(wkl)
-    sch = _SCHEDULES[idx]
-    return sch
+@generic.schedule_conv2d_nchw.register(["vtacpu", "vta"])
+def schedule(*args, **kwargs):
+    with tvm.target.arm_cpu("vtacpu"):
+        return generic.schedule_conv2d_nchw(*args, **kwargs)
 
-conv2d.register(["vtacpu", "vta"], _rasp_conv2d._declaration_conv2d)
-
-generic.schedule_conv2d_nchw.register(
-    ["vtacpu", "vta"],
-    _rasp_conv2d.schedule_conv2d_nchw)
+@conv2d_alter_layout.register(["vtacpu", "vta"])
+def alter(*args, **kwargs):
+    with tvm.target.arm_cpu("vtacpu"):
+        return conv2d_alter_layout(*args, **kwargs)
diff --git a/vta/python/vta/top/vta_conv2d.py b/vta/python/vta/top/vta_conv2d.py
index 28cd8a49cb0f..e7d584a791fc 100644
--- a/vta/python/vta/top/vta_conv2d.py
+++ b/vta/python/vta/top/vta_conv2d.py
@@ -244,8 +244,11 @@ def is_packed_layout(layout):
     return False
 
 @reg.register_alter_op_layout("conv2d", level=15)
-def alter_conv2d_layout(*_):
-    return None
+def alter_conv2d_layout(attrs, inputs, out):
+    layout = attrs['layout']
+    if is_packed_layout(layout):
+        return None
+    return _nn.alter_conv2d_layout(attrs, inputs, out)
 
 
 @reg.register_compute("conv2d", level=15)
@@ -368,7 +371,6 @@ def _traverse(op):
     oshape = topi.util.get_const_tuple(output.shape)
     s = tvm.create_schedule(output.op)
 
-
     # setup pad
     if pad_data is not None:
         cdata = pad_data
@@ -394,7 +396,6 @@ def _traverse(op):
     h_factor = (plan.h_factor if plan.h_factor else oshape[2])
     w_factor = (plan.w_factor if plan.w_factor else oshape[3])
 
-
     x_bo, x_co, x_i, x_j, x_bi, x_ci = s[output].op.axis
     x_co0, x_co1 = s[output].split(x_co, factor=oc_factor)
     x_i0, x_i1 = s[output].split(x_i, factor=h_factor)
@@ -459,6 +460,7 @@ def __init__(self,
         self.oc_nthread = oc_nthread
         self.h_nthread = h_nthread
         self.debug_sync = debug_sync
+
     def __str__(self):
         return "{}.{}.{}.{}.{}.{}.{}".format(
             self.b_factor, self.oc_factor, self.ic_factor,
@@ -483,7 +485,6 @@ def __str__(self):
     11: Workload(1, 7, 7, 512, 512, 3, 3, 1, 1, 1, 1),
 }
 
-_WL2PLAN = {}
 for idx in RESNET:
     scheds = find_schedules(RESNET[idx], vt_only=True, best_only=True)[0]
     _WL2PLAN[RESNET[idx]] = scheds
diff --git a/vta/tests/python/integration/test_benchmark_topi_conv2d.py b/vta/tests/python/integration/test_benchmark_topi_conv2d.py
index 9cf8909bc9ba..ca2451dec614 100644
--- a/vta/tests/python/integration/test_benchmark_topi_conv2d.py
+++ b/vta/tests/python/integration/test_benchmark_topi_conv2d.py
@@ -1,6 +1,7 @@
 """Testing if we can generate code in topi style"""
 
 import tvm
+from tvm import autotvm
 from tvm.contrib import util
 from tvm.contrib.pickle_memoize import memoize
 import topi
@@ -62,8 +63,7 @@ def get_ref_data():
 
         def verify(s, check_correctness):
             mod = tvm.build(s, [data, kernel, res],
-                            "llvm -device=vtacpu",
-                            env.target_host,
+                            target_host=env.target_host,
                             name="conv2d")
             temp = util.tempdir()
             mod.save(temp.relpath("conv2d.o"))
@@ -126,7 +126,11 @@ def _run(env, remote):
             print(wl)
             with tvm.target.create("llvm -device=vtacpu"):
                 run_cpu_conv2d(env, remote, key, batch_size, wl)
-    vta.testing.run(_run)
+
+    # load pre-tuned operator parameters for ARM CPU
+    autotvm.tophub.check_package('vta')
+    with autotvm.tophub.context('llvm -device=vtacpu'):
+        vta.testing.run(_run)
 
 
 def test_vta_conv2d():
@@ -172,7 +176,6 @@ def get_ref_data():
                 a_np.astype(acc_dtype), w_np.astype(acc_dtype), stride, padding).astype(acc_dtype)
             return a_np, w_np, b_np
 
-
         def verify(s, check_correctness):
             mod = vta.build(s, [data, kernel, bias, res], "ext_dev",
                             env.target_host, name="conv2d")
diff --git a/vta/tutorials/resnet.py b/vta/tutorials/resnet.py
index 7a2b0ab50925..8d33a91d5691 100644
--- a/vta/tutorials/resnet.py
+++ b/vta/tutorials/resnet.py
@@ -8,7 +8,6 @@
 
 """
 
-
 ######################################################################
 # Import Libraries
 # ----------------
@@ -17,26 +16,21 @@
 from __future__ import absolute_import, print_function
 
 import os
-import sys
-import nnvm
-import nnvm.compiler
-import tvm
-import vta
-import vta.testing
+import time
+from io import BytesIO
+
 import numpy as np
-import json
 import requests
-import time
+from matplotlib import pyplot as plt
+from PIL import Image
 
-from nnvm.compiler import graph_attr
-from tvm import rpc
+import tvm
+from tvm import rpc, autotvm
 from tvm.contrib import graph_runtime, util
 from tvm.contrib.download import download
-from vta.testing import simulator
-
-from io import BytesIO
-from matplotlib import pyplot as plt
-from PIL import Image
+import nnvm.compiler
+import vta
+import vta.testing
 
 # Load VTA parameters from the vta/config/vta_config.json file
 env = vta.get_env()
@@ -76,7 +70,6 @@ def classify(m, image):
 # Takes in a path to a graph file, params file, and device target
 # Returns the NNVM graph object, a compiled library object, and the params dict
 def generate_graph(graph_fn, params_fn, device="vta"):
-
     # Measure build start time
     build_start = time.time()
 
@@ -100,12 +93,6 @@ def generate_graph(graph_fn, params_fn, device="vta"):
     shape_dict.update({k: v.shape for k, v in params.items()})
     dtype_dict.update({k: str(v.dtype) for k, v in params.items()})
 
-    # Create NNVM graph
-    graph = nnvm.graph.create(sym)
-    graph_attr.set_shape_inputs(sym, shape_dict)
-    graph_attr.set_dtype_inputs(sym, dtype_dict)
-    graph = graph.apply("InferShape").apply("InferType")
-
     # Apply NNVM graph optimization passes
     sym = vta.graph.clean_cast(sym)
     sym = vta.graph.clean_conv_fuse(sym)
@@ -166,6 +153,9 @@ def generate_graph(graph_fn, params_fn, device="vta"):
 # Read in ImageNet Categories
 synset = eval(open(os.path.join(data_dir, categ_fn)).read())
 
+# Download pre-tuned op parameters of conv2d for ARM CPU used in VTA
+autotvm.tophub.check_package('vta')
+
 
 ######################################################################
 # Setup the Pynq Board's RPC Server
@@ -182,7 +172,6 @@ def generate_graph(graph_fn, params_fn, device="vta"):
 # We configure both the bitstream and the runtime system on the Pynq
 # to match the VTA configuration specified by the vta_config.json file.
 if env.TARGET == "pynq":
-
     # Make sure that TVM was compiled with RPC=1
     assert tvm.module.enabled("rpc")
     remote = rpc.connect(host, port)
@@ -209,8 +198,8 @@ def generate_graph(graph_fn, params_fn, device="vta"):
 # ------------------------
 # Build the ResNet graph runtime, and configure the parameters.
 
-# Set ``device=cpu`` to run inference on the CPU,
-# or ``device=vtacpu`` to run inference on the FPGA.
+# Set ``device=vtacpu`` to run inference on the CPU
+# or ``device=vta`` to run inference on the FPGA.
 device = "vta"
 
 # Device context
@@ -225,7 +214,6 @@ def generate_graph(graph_fn, params_fn, device="vta"):
 # Set the parameters
 m.set_input(**params)
 
-
 ######################################################################
 # Run ResNet-18 inference on a sample image
 # -----------------------------------------

From 2875a123c212850f470c0c6e37fde699e9137173 Mon Sep 17 00:00:00 2001
From: Siju <sijusamuel@gmail.com>
Date: Thu, 2 Aug 2018 21:31:22 +0530
Subject: [PATCH 944/949] [NNVM][TENSORFLOW]Local Response Normalization added
 for tensorflow (#1522)

---
 nnvm/python/nnvm/frontend/tensorflow.py       | 15 ++++++++
 .../frontend/tensorflow/test_forward.py       | 35 +++++++++++++++++++
 2 files changed, 50 insertions(+)

diff --git a/nnvm/python/nnvm/frontend/tensorflow.py b/nnvm/python/nnvm/frontend/tensorflow.py
index 27f19a1e177a..d923c25044e1 100644
--- a/nnvm/python/nnvm/frontend/tensorflow.py
+++ b/nnvm/python/nnvm/frontend/tensorflow.py
@@ -468,6 +468,20 @@ def _impl(inputs, attr, params):
             ignores=['index_type', 'T'])(new_inputs, attr)
     return _impl
 
+def _lrn():
+    def _impl(inputs, attr, params):
+        new_inputs = []
+        attr_new = {}
+        depth_radius = attr.get('depth_radius', 5)
+        size = (depth_radius * 2) + 1
+        attr_new['axis'] = 3 # Fix axis, NHWC format
+        attr_new['size'] = size
+        attr_new['bias'] = attr.get('bias', 1)
+        attr_new['alpha'] = attr.get('alpha', 1) * size
+        attr_new['beta'] = attr.get('beta', 0.5)
+        return AttrCvt(op_name='lrn')(new_inputs, attr_new)
+    return _impl
+
 def _gather_v2():
     "Tensorflow now support only gatherv2"
     def _impl(inputs, attr, params):
@@ -680,6 +694,7 @@ def _impl(inputs, in_state_c, in_state_h, attr, params):
     'Fill'                              : _fill(),
     'GatherV2'                          : _gather_v2(),
     'StridedSlice'                      : _stridedSlice(),
+    'LRN'                               : _lrn(),
 }
 
 # _convert_map_rnn defines maps of rnn operator name to
diff --git a/nnvm/tests/python/frontend/tensorflow/test_forward.py b/nnvm/tests/python/frontend/tensorflow/test_forward.py
index 045d154d9d8b..a5c5fdcfed2f 100644
--- a/nnvm/tests/python/frontend/tensorflow/test_forward.py
+++ b/nnvm/tests/python/frontend/tensorflow/test_forward.py
@@ -855,6 +855,40 @@ def _get_sample(data, state):
         assert(tvm_sample_str == tf_sample_str)
 
 #######################################################################
+# LRN (Local Response Normalization)
+# ----------------------------------
+
+def _test_lrn(ishape, size, axis, bias, alpha, beta):
+    """ testing local response normalization """
+    lrn_depth_radius = size / 2
+
+    inp_array = np.random.uniform(size=ishape).astype(np.float32)
+
+    with tf.Graph().as_default():
+        in1 = tf.placeholder(shape=inp_array.shape, dtype=inp_array.dtype, name="lrn0_data")
+        nn_ops.local_response_normalization(in1,
+                                            name="lrn",
+                                            depth_radius=lrn_depth_radius,
+                                            bias=bias,
+                                            alpha=alpha,
+                                            beta=beta)
+
+        with tf.Session() as sess:
+            graph_def = tf.graph_util.convert_variables_to_constants(
+                sess,
+                sess.graph.as_graph_def(add_shapes=True),
+                ['lrn'],)
+
+            tf_output = run_tf_graph(sess, inp_array, 'lrn0_data:0', 'lrn:0')
+            tvm_output = run_tvm_graph(graph_def,
+                                       inp_array,
+                                       "lrn0_data", tf_output.shape, tf_output.dtype)
+            np.testing.assert_allclose(tf_output, tvm_output, atol=1e-3, rtol=1e-3)
+            sess.close()
+
+def test_forward_lrn():
+    _test_lrn((1, 3, 20, 20), 3, 1, 1.0, 1.0, 0.5)
+
 # Main
 # ----
 if __name__ == '__main__':
@@ -875,3 +909,4 @@ def _get_sample(data, state):
     test_forward_stridedslice()
     test_forward_gather()
     test_forward_ptb()
+    test_forward_lrn()

From 54ca1493bd9808a481ca0e7ed32897c12f36796e Mon Sep 17 00:00:00 2001
From: Pariksheet Pinjari <pariksheet.pinjari@huawei.com>
Date: Thu, 2 Aug 2018 21:32:34 +0530
Subject: [PATCH 945/949] [MXNET] LRN support in MXNET frontend (#1520)

---
 nnvm/python/nnvm/frontend/mxnet.py               | 12 +++++++++++-
 nnvm/tests/python/frontend/mxnet/test_forward.py |  6 ++++++
 2 files changed, 17 insertions(+), 1 deletion(-)

diff --git a/nnvm/python/nnvm/frontend/mxnet.py b/nnvm/python/nnvm/frontend/mxnet.py
index e741be6e3744..f0217fc1ec85 100644
--- a/nnvm/python/nnvm/frontend/mxnet.py
+++ b/nnvm/python/nnvm/frontend/mxnet.py
@@ -263,6 +263,15 @@ def _expand_dims(inputs, attrs):
     new_attrs['axis'] = _required_attr(attrs, 'axis')
     return _get_nnvm_op(op_name)(*inputs, **new_attrs)
 
+def _lrn(inputs, attrs):
+    op_name, new_attrs = "lrn", {}
+    new_attrs['alpha'] = attrs.get('alpha', 0.0001)
+    new_attrs['beta'] = attrs.get('beta', 0.75)
+    new_attrs['bias'] = attrs.get('knorm', 2)
+    # NCHW format and normalization along channel axis
+    new_attrs['axis'] = 1
+    new_attrs['size'] = _required_attr(attrs, 'nsize')
+    return _get_nnvm_op(op_name)(*inputs, **new_attrs)
 
 _identity_list = ['__add_scalar__', '__add_symbol__', '__div_scalar__',
                   '__div_symbol__', '__mul_scalar__', '__mul_symbol__',
@@ -314,7 +323,8 @@ def _expand_dims(inputs, attrs):
     'sum_axis'      : _rename('sum'),
     'UpSampling'    : _upsampling,
     'clip'          : _clip,
-    'expand_dims'   : _expand_dims
+    'expand_dims'   : _expand_dims,
+    'LRN'           : _lrn
 }
 
 def _convert_symbol(op_name, inputs, attrs,
diff --git a/nnvm/tests/python/frontend/mxnet/test_forward.py b/nnvm/tests/python/frontend/mxnet/test_forward.py
index a3356689b6e4..6c086cb367e8 100644
--- a/nnvm/tests/python/frontend/mxnet/test_forward.py
+++ b/nnvm/tests/python/frontend/mxnet/test_forward.py
@@ -149,6 +149,11 @@ def test_forward_pooling():
     mx_sym = mx.sym.Pooling(data, kernel=(3, 3), pad=(1, 1), pool_type='max')
     verify_mxnet_frontend_impl(mx_sym, (1, 20, 8, 8), (1, 20, 8, 8))
 
+def test_forward_lrn():
+    data = mx.sym.var('data')
+    mx_sym = mx.sym.LRN(data, alpha=2, beta=2, knorm=1, nsize=5)
+    verify_mxnet_frontend_impl(mx_sym, (1, 10, 24, 24), (1, 10, 24, 24))
+
 if __name__ == '__main__':
     test_forward_mlp()
     test_forward_vgg()
@@ -163,3 +168,4 @@ def test_forward_pooling():
     test_forward_split_squeeze()
     test_forward_expand_dims()
     test_forward_pooling()
+    test_forward_lrn()

From 7ee9ccae6749be06c4ce9d1a0352f281a326a19d Mon Sep 17 00:00:00 2001
From: Sergey Mironov <grrwlf@gmail.com>
Date: Thu, 2 Aug 2018 19:48:41 +0300
Subject: [PATCH 946/949] [NNVM] Support argmax/argmin in tensorflow frontend
 (#1514)

---
 nnvm/python/nnvm/frontend/tensorflow.py       | 56 ++++++++++++++++---
 .../frontend/tensorflow/test_forward.py       | 31 ++++++++++
 2 files changed, 79 insertions(+), 8 deletions(-)

diff --git a/nnvm/python/nnvm/frontend/tensorflow.py b/nnvm/python/nnvm/frontend/tensorflow.py
index d923c25044e1..b66cf60e3c04 100644
--- a/nnvm/python/nnvm/frontend/tensorflow.py
+++ b/nnvm/python/nnvm/frontend/tensorflow.py
@@ -91,6 +91,20 @@ def _impl(inputs, attr, *args):
         return AttrCvt(op_name="__pow_scalar__", extras={'scalar': -0.5})(inputs, attr)
     return _impl
 
+def _argx(func, func_name):
+    """ A common wrapper for argmin and argmax operations """
+    def _impl(inputs, attr, params):
+        try:
+            # In Tensorflow, `axis` argument is a Tensor, not attribute. We
+            # support the case where it inputs from a scalar constant.
+            axis_input_name = inputs[1].list_output_names()[0]
+            axis_input_vlaue = params[axis_input_name].asnumpy()[0]
+        except (IndexError, KeyError):
+            raise TypeError( \
+                "Unsupported argument for `{}` : `axis` should be a constant".format(func_name))
+        return func(inputs[0], axis=axis_input_vlaue, keepdims=False)
+    return _impl
+
 def _elemwise(name):
     def _impl(inputs, attr, *args):
         assert len(inputs) == 2, "Math op take 2 inputs, {} given".format(len(inputs))
@@ -664,6 +678,8 @@ def _impl(inputs, in_state_c, in_state_h, attr, params):
 # for 1 to N mapping(composed), use custom callable functions
 # for N to 1 mapping, currently not supported(?)
 _convert_map = {
+    'ArgMax'                            : _argx(_sym.argmax, 'argmax'),
+    'ArgMin'                            : _argx(_sym.argmin, 'argmin'),
     'AvgPool'                           : _pooling('avg_pool'),
     'BatchNormWithGlobalNormalization'  : _batch_norm(),
     'BiasAdd'                           : _bias_add(),
@@ -879,6 +895,28 @@ def _get_abs_layer_name(node):
                                                      params, num_layers)
         return sym
 
+
+def _parse_import_prerequisites(graph):
+    """ Calculate the named preconditions from TensorFlow `graph`.
+    Return prerequisites for parsing:
+     a. Set of operator names which don't have their mapping in TVM, i.e.
+        which are not supported
+    """
+    missing_operators = set()
+    for node in graph.node:
+        if node.op == "Placeholder":
+            pass
+        elif node.op == "Const":
+            pass
+        else:
+            if any([node.op in t for t in [_identity_list, _convert_map, _convert_map_rnn]]):
+                pass
+            else:
+                missing_operators.add(node.op)
+
+    return missing_operators
+
+
 class GraphProto(object):
     """ A helper class for handling nnvm graph copying from Tensorflow GraphDef.
     Definition:
@@ -901,7 +939,7 @@ def from_tensorflow(self, graph):
         Follow the tensorflow graph definition to parse and convert it to NNVM.
         Some of the assumptions listed below.
 
-            -> First Const or Placeholder node will be considered as graph input.
+            -> First Placeholder or Const node will be considered as graph input.
             -> Rest all Const nodes are params.
             -> Last node is assumed as graph output.
             -> _output_shapes : Attribute should present in the tenserflow forzen graph.
@@ -910,6 +948,7 @@ def from_tensorflow(self, graph):
             -> CheckNumerics: No implementation as of now for this.
                               Just copies input to output.
 
+        TODO: Change algorithm to stop treating first 'Const' in a special way.
 
         Parameters
         ----------
@@ -923,10 +962,6 @@ def from_tensorflow(self, graph):
         params : dict
             A dict of name: tvm.nd.array pairs, used as pretrained weights
         """
-        # Parse throught all nodes and start extracting
-        # params aka Const nodes
-        # input nodes  : First const node
-        # normal nodes : other normal nodes
 
         try:
             from tensorflow.python.framework import tensor_util
@@ -934,12 +969,18 @@ def from_tensorflow(self, graph):
             raise ImportError(
                 "Unable to import tensorflow which is required {}".format(e))
 
+        missing_operators = _parse_import_prerequisites(graph)
+
+        if missing_operators:
+            raise NotImplementedError( \
+                "The following operators are not implemented: {}".format(missing_operators))
+
+        # Parse the nodes to re-create TF graph using Symbol API of NNVM
         for node in graph.node:
             # Tensorflow doesn't have seperate list for params extraction.
             # Operator name 'Const' is treated as a parameter to build NNVM params dict.
             input_shapes = {}
             if node.op == "Placeholder":
-                # Assuming only one input graph with type 'Placeholder'
                 self._input_node = node.name
                 self._num_input += 1
 
@@ -954,7 +995,6 @@ def from_tensorflow(self, graph):
                     raise NotImplementedError( \
                         "Please freeze the graph with add_shapes=True")
             elif node.op == "Const":
-                # Assuming first Const node as Graph Input node
                 if self._input_node == '':
                     self._input_node = node.name
                     self._num_input += 1
@@ -997,7 +1037,7 @@ def from_tensorflow(self, graph):
                 # Pass the node name too in attr
                 attr["_node_name"] = node.name
 
-                #ToDo: Some of the tensorflow operators maintain internaly maintain
+                #ToDo: Some of the tensorflow operators internaly maintain
                 #execution layers and its output name will the layer number along with
                 #graph node name.eg: Node name:- 'Model/RNN/cell_0/RnnCell', but the
                 #output name will be 'Model/RNN/cell_0/RnnCell:0'. In this case,
diff --git a/nnvm/tests/python/frontend/tensorflow/test_forward.py b/nnvm/tests/python/frontend/tensorflow/test_forward.py
index a5c5fdcfed2f..0642e46b7863 100644
--- a/nnvm/tests/python/frontend/tensorflow/test_forward.py
+++ b/nnvm/tests/python/frontend/tensorflow/test_forward.py
@@ -404,6 +404,37 @@ def test_forward_sigmoid():
 
     _test_sigmoid(np.random.uniform(size=(3, 4, 4, 3)).astype('float32'))
 
+#######################################################################
+# Argmin/Argmax
+# -------------
+
+def _test_argx(func, data, **kwargs):
+
+    with tf.Graph().as_default():
+        inp = constant_op.constant(data, shape=data.shape, dtype=data.dtype, name="c0")
+
+        # pylint: disable=unused-variable
+        out = func(inp, name="argx0", **kwargs)
+        # pylint: enable=unused-variable
+
+        with tf.Session() as sess:
+            graph_def = tf.graph_util.convert_variables_to_constants(
+                sess=sess,
+                input_graph_def=sess.graph.as_graph_def(add_shapes=True),
+                output_node_names=["argx0"])
+
+            tf_output = run_tf_graph(sess, data, input_node="c0:0", output_node="argx0:0")
+            tvm_output = run_tvm_graph(graph_def, data, "c0", tf_output.shape, output_dtype='int32')
+
+            np.testing.assert_allclose(tf_output, tvm_output, atol=1e-5, rtol=1e-5)
+
+            sess.close()
+
+def test_argmin_argmax():
+    for axis in [None,0,1,2]:
+        data = np.random.uniform(size=(8,4,9)).astype('float32')
+        _test_argx(tf.argmax, data=data, axis=axis)
+        _test_argx(tf.argmin, data=data, axis=axis)
 
 #######################################################################
 # Variable

From 0737fe19ee782a89f020a1cac181c928c0276977 Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 2 Aug 2018 16:43:23 -0700
Subject: [PATCH 947/949] Update test_rpc_exec.py

---
 nnvm/tests/python/compiler/test_rpc_exec.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nnvm/tests/python/compiler/test_rpc_exec.py b/nnvm/tests/python/compiler/test_rpc_exec.py
index 03a921c0a5ae..614fe08781ab 100644
--- a/nnvm/tests/python/compiler/test_rpc_exec.py
+++ b/nnvm/tests/python/compiler/test_rpc_exec.py
@@ -7,7 +7,7 @@
 
 def test_rpc_executor():
     host = "localhost"
-    port = 9100
+    port = 9711
     server = rpc.Server(host, port, use_popen=True)
 
     x = sym.Variable("x")

From af0b73fe9c091d049d34a02f7a97416fba05ab0e Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 2 Aug 2018 19:12:36 -0700
Subject: [PATCH 948/949] Fix rpc testcase (#1538)

---
 nnvm/tests/python/compiler/test_rpc_exec.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/nnvm/tests/python/compiler/test_rpc_exec.py b/nnvm/tests/python/compiler/test_rpc_exec.py
index 614fe08781ab..129a0a425fbc 100644
--- a/nnvm/tests/python/compiler/test_rpc_exec.py
+++ b/nnvm/tests/python/compiler/test_rpc_exec.py
@@ -7,8 +7,8 @@
 
 def test_rpc_executor():
     host = "localhost"
-    port = 9711
-    server = rpc.Server(host, port, use_popen=True)
+    port = 9120
+    server = rpc.Server(host, port)
 
     x = sym.Variable("x")
     y = sym.Variable("y")
@@ -22,7 +22,7 @@ def test_rpc_executor():
     graph, lib, _ = nnvm.compiler.build(z, "llvm", shape_dict)
     # save module
     lib.save(lib_name)
-    remote = rpc.connect(host, port)
+    remote = rpc.connect(server.host, server.port)
     remote.upload(lib_name)
     ctx = remote.cpu(0)
     # load remote

From 8056165eea6b55aa27b6bbed741f58dc3a6e639b Mon Sep 17 00:00:00 2001
From: Tianqi Chen <tqchen@users.noreply.github.com>
Date: Thu, 2 Aug 2018 21:47:04 -0700
Subject: [PATCH 949/949] Fix RPC (#1542)

---
 nnvm/tests/python/compiler/test_rpc_exec.py | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/nnvm/tests/python/compiler/test_rpc_exec.py b/nnvm/tests/python/compiler/test_rpc_exec.py
index 129a0a425fbc..111ba724e196 100644
--- a/nnvm/tests/python/compiler/test_rpc_exec.py
+++ b/nnvm/tests/python/compiler/test_rpc_exec.py
@@ -4,12 +4,13 @@
 import nnvm.symbol as sym
 import nnvm.compiler
 import numpy as np
+import time
 
 def test_rpc_executor():
     host = "localhost"
-    port = 9120
-    server = rpc.Server(host, port)
-
+    port = 9021
+    server = rpc.Server(host, port, use_popen=True)
+    time.sleep(1)
     x = sym.Variable("x")
     y = sym.Variable("y")
     z = sym.exp(y + x)
@@ -22,7 +23,7 @@ def test_rpc_executor():
     graph, lib, _ = nnvm.compiler.build(z, "llvm", shape_dict)
     # save module
     lib.save(lib_name)
-    remote = rpc.connect(server.host, server.port)
+    remote = rpc.connect(host, port)
     remote.upload(lib_name)
     ctx = remote.cpu(0)
     # load remote
